[PATCH v2 13/15] PCI: rcar-gen4: Route Root Port AER to a virtual Root Port IRQ
From: Koichiro Den
Date: Mon Sep 28 2026 - 13:06:02 EST
On R-Car Gen4, the APP block exposes the Root Port interrupt signals in
PCIEINTSTS0 and routes them to platform IRQs: AER to intreq_pcim_lperr,
and PME and bandwidth notifications to intreq_pcim_sub. On an R-Car S4
with GIC ITS, a link retrain set LnkSta.LBMS and latched PCIEINTSTS0[23]
(cfg_bw_mgt_msi), but the Root Port's MSI never fired while endpoint
MSIs worked fine. With iMSI-RX the Root Port MSI capabilities are
already hidden, but its INTx maps to intreq_pcim_sub. Note that even
before this driver took over the IRQ with
rcar_gen4_pcie_msi_irq_handler, the port service was unable to request
it, as the DWC core used that IRQ for its chained MSI handler. In either
case (ie. iMSI-RX or an external MSI controller), Root Port interrupts
are not delivered to the port services.
Set no_rp_msi so the port services always fall back to INTx. Allocate an
IRQ in a single-entry domain and return its number from the bridge's
map_irq callback for the Root Port only, leaving downstream INTx
mappings unchanged. Notifications from different platform IRQs can then
feed the one IRQ the port services share. Route the AER notifications
from the "aer" interrupt to it. rcar_gen4_pcie_host_msi_init() sets
MSICAP0.MSIE when pci_msi_enabled() is true, and hiding the capability
does not clear it, so handle both the MSI and INTx forms of the status
bits. DTs without "aer" remain supported, but the APP AER notification
sources stay masked.
Preserve the virtual IRQ's enable state across controller resets, but
keep its APP sources masked while reinitialization is pending or Root
Port events are disarmed. Clear only the MSI-form APP latch, before the
AER handler clears Root Error Status, to avoid clearing a newly latched
notification.
Handle pending reset requests before dispatching AER to avoid unsafe DBI
access. Serialize dispatches from the parent IRQs, including the "msi"
path added by the next patch, since handle_level_irq() could otherwise
acknowledge a concurrent interrupt without running its handlers. Recheck
reset requests after taking the lock in case the other parent has marked
reinitialization as pending.
Signed-off-by: Koichiro Den <den@xxxxxxxxxxxxx>
---
Changes in v2:
- Adapt to the driver taking over the iMSI-RX interrupt, and keep
AER notifications masked until .post_init, after enumeration.
- Clear only the MSI-form latch through PCIEINTSTS0CLR; the INTx bit
is reserved there.
- Serialize dispatches to the virtual Root Port IRQ.
- Revise the commit message.
drivers/pci/controller/dwc/pcie-rcar-gen4.c | 220 +++++++++++++++++++-
1 file changed, 218 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
index aa80232afaba..c01f157a7198 100644
--- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c
+++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
@@ -14,11 +14,13 @@
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/irqchip/arm-gic-v3.h>
+#include <linux/irqdomain.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/of_pci.h>
#include <linux/pci.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
@@ -60,6 +62,11 @@
#define SMLH_LINK_UP BIT(7)
#define RDLH_LINK_UP BIT(6)
+/* Root Port notifications routed to intreq_pcim_lperr ("aer") */
+#define AER_RC_ERR_MSI BIT(29)
+#define AER_RC_ERR_INT BIT(27)
+#define RP_AER_MASK (AER_RC_ERR_MSI | AER_RC_ERR_INT)
+
/* PCIe DMA Interrupt Status Enable */
#define PCIEDMAINTSTSEN 0x0314
#define PCIEDMAINTSTSEN_INIT GENMASK(15, 0)
@@ -69,6 +76,7 @@
/* PCIe Interrupt Status 0 Clear */
#define PCIEINTSTS0CLR 0x0340
+#define RP_LATCHED_MASK AER_RC_ERR_MSI
/* Port Logic Registers 89 */
#define PRTLGC89 0x0b70
@@ -134,6 +142,14 @@ struct rcar_gen4_pcie {
struct work_struct link_down_work;
/* Allow Root Port event handling after enumeration and until teardown. */
bool rp_events_armed;
+ /* Virtual Root Port INTx shared by the port services */
+ struct irq_domain *rp_irq_domain;
+ unsigned int rp_irq;
+ bool rp_irq_enabled;
+ /* Serializes dispatch to the virtual Root Port IRQ from its parent IRQs */
+ raw_spinlock_t rp_irq_lock;
+ /* intreq_pcim_lperr ("aer"): Root Port AER notifications, optional */
+ int aer_irq;
/*
* The controller needs reinitializing; the APP interrupt sources
* stay masked until a reset succeeds.
@@ -581,7 +597,7 @@ static void rcar_gen4_pcie_app_irq_sync_locked(struct rcar_gen4_pcie *rcar)
lockdep_assert_held(&rcar->app_lock);
val = readl(rcar->base + PCIEINTSTS0EN);
- val &= ~MSI_CTRL_INT;
+ val &= ~(MSI_CTRL_INT | RP_AER_MASK);
/*
* Note that rcar->rp_events_armed is not checked here because device
* removal may still need iMSI-RX interrupts after Root Port events have
@@ -589,6 +605,8 @@ static void rcar_gen4_pcie_app_irq_sync_locked(struct rcar_gen4_pcie *rcar)
*/
if (!rcar->reinit_pending && pp->use_imsi_rx && pci_msi_enabled())
val |= MSI_CTRL_INT;
+ if (rp_events && rcar->rp_irq_enabled && rcar->aer_irq > 0)
+ val |= RP_AER_MASK;
writel(val, rcar->base + PCIEINTSTS0EN);
val = readl(rcar->base + PCIERSTSTSEN);
@@ -689,6 +707,21 @@ static bool rcar_gen4_pcie_handle_link_down(struct rcar_gen4_pcie *rcar)
return rcar->reinit_pending;
}
+static void rcar_gen4_pcie_rp_irq_dispatch(struct rcar_gen4_pcie *rcar)
+{
+ /*
+ * Serialize dispatches from the parent IRQs. handle_level_irq() would
+ * ack an IRQ already in progress without running its handlers again.
+ * Recheck after taking the lock since the parent's check may be stale.
+ */
+ guard(raw_spinlock_irqsave)(&rcar->rp_irq_lock);
+
+ if (rcar_gen4_pcie_handle_link_down(rcar))
+ return;
+
+ generic_handle_domain_irq(rcar->rp_irq_domain, 0);
+}
+
static void rcar_gen4_pcie_link_down_work(struct work_struct *work)
{
struct rcar_gen4_pcie *rcar =
@@ -779,6 +812,170 @@ static void rcar_gen4_pcie_rp_events_disarm(struct rcar_gen4_pcie *rcar)
cancel_work_sync(&rcar->link_down_work);
}
+static void rcar_gen4_pcie_rp_irq_mask(struct irq_data *d)
+{
+ struct rcar_gen4_pcie *rcar = irq_data_get_irq_chip_data(d);
+
+ guard(raw_spinlock_irqsave)(&rcar->app_lock);
+ rcar->rp_irq_enabled = false;
+ rcar_gen4_pcie_app_irq_sync_locked(rcar);
+}
+
+static void rcar_gen4_pcie_rp_irq_unmask(struct irq_data *d)
+{
+ struct rcar_gen4_pcie *rcar = irq_data_get_irq_chip_data(d);
+
+ guard(raw_spinlock_irqsave)(&rcar->app_lock);
+ rcar->rp_irq_enabled = true;
+ rcar_gen4_pcie_app_irq_sync_locked(rcar);
+}
+
+static void rcar_gen4_pcie_rp_irq_ack(struct irq_data *d)
+{
+ struct rcar_gen4_pcie *rcar = irq_data_get_irq_chip_data(d);
+
+ /*
+ * Clear APP latches before the service clears Root Error Status so
+ * we don't drop back-to-back notifications.
+ */
+ writel(RP_LATCHED_MASK, rcar->base + PCIEINTSTS0CLR);
+}
+
+static const struct irq_chip rcar_gen4_pcie_rp_irq_chip = {
+ .name = "rcar-gen4-rp",
+ .irq_mask = rcar_gen4_pcie_rp_irq_mask,
+ .irq_unmask = rcar_gen4_pcie_rp_irq_unmask,
+ .irq_ack = rcar_gen4_pcie_rp_irq_ack,
+};
+
+static int rcar_gen4_pcie_rp_irq_map(struct irq_domain *domain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &rcar_gen4_pcie_rp_irq_chip,
+ handle_level_irq);
+ irq_set_chip_data(irq, domain->host_data);
+ irq_set_noprobe(irq);
+
+ return 0;
+}
+
+static const struct irq_domain_ops rcar_gen4_pcie_rp_irq_domain_ops = {
+ .map = rcar_gen4_pcie_rp_irq_map,
+};
+
+static irqreturn_t rcar_gen4_pcie_aer_irq_handler(int irq, void *data)
+{
+ struct rcar_gen4_pcie *rcar = data;
+ u32 status;
+
+ /* The line also fires on link down; reset requests come first. */
+ if (rcar_gen4_pcie_handle_link_down(rcar))
+ return IRQ_HANDLED;
+
+ status = readl(rcar->base + PCIEINTSTS0) &
+ readl(rcar->base + PCIEINTSTS0EN);
+ if (!(status & RP_AER_MASK))
+ return IRQ_NONE;
+
+ rcar_gen4_pcie_rp_irq_dispatch(rcar);
+
+ return IRQ_HANDLED;
+}
+
+static int rcar_gen4_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+{
+ struct pci_host_bridge *bridge = pci_find_host_bridge(dev->bus);
+ struct rcar_gen4_pcie *rcar = dev_get_drvdata(bridge->dev.parent);
+
+ /* Downstream devices use standard OF INTx mapping */
+ if (!pci_is_root_bus(dev->bus))
+ return of_irq_parse_and_map_pci(dev, slot, pin);
+
+ /* Root Port uses the virtual IRQ (always INTA) */
+ return rcar->rp_irq;
+}
+
+static int rcar_gen4_pcie_rp_irq_init(struct dw_pcie_rp *pp)
+{
+ struct dw_pcie *dw = to_dw_pcie_from_pp(pp);
+ struct rcar_gen4_pcie *rcar = to_rcar_gen4_pcie(dw);
+ struct device *dev = dw->dev;
+ struct fwnode_handle *fwnode;
+ int irq, ret;
+
+ irq = platform_get_irq_byname_optional(rcar->pdev, "aer");
+ if (irq == -ENXIO)
+ irq = 0; /* old DT: Root Port AER stays unavailable */
+ else if (irq < 0)
+ return dev_err_probe(dev, irq, "Failed to get AER IRQ\n");
+
+ /*
+ * Note: suspend/resume support must not tear down and recreate this
+ * domain, as the port driver keeps the mapped IRQ.
+ */
+ fwnode = irq_domain_alloc_named_fwnode(dev_name(dev));
+ if (!fwnode)
+ return -ENOMEM;
+
+ rcar->rp_irq_domain = irq_domain_create_linear(fwnode, 1,
+ &rcar_gen4_pcie_rp_irq_domain_ops,
+ rcar);
+ if (!rcar->rp_irq_domain) {
+ ret = -ENOMEM;
+ goto err_free_fwnode;
+ }
+
+ rcar->rp_irq = irq_create_mapping(rcar->rp_irq_domain, 0);
+ if (!rcar->rp_irq) {
+ ret = -ENOMEM;
+ goto err_remove_domain;
+ }
+
+ if (irq) {
+ /*
+ * Dispatch the child IRQ in hard IRQ context, including on
+ * PREEMPT_RT. Released in .deinit, like the "msi" IRQ.
+ */
+ ret = request_irq(irq, rcar_gen4_pcie_aer_irq_handler,
+ IRQF_NO_THREAD, "rcar-gen4-pcie-aer", rcar);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to request AER IRQ\n");
+ goto err_dispose_mapping;
+ }
+ rcar->aer_irq = irq;
+ }
+
+ /*
+ * The Root Port MSI capabilities are hidden (no_rp_msi), so the port
+ * services fall back to INTx; route it to the virtual IRQ.
+ */
+ pp->bridge->map_irq = rcar_gen4_pcie_map_irq;
+
+ return 0;
+
+err_dispose_mapping:
+ irq_dispose_mapping(rcar->rp_irq);
+err_remove_domain:
+ irq_domain_remove(rcar->rp_irq_domain);
+err_free_fwnode:
+ irq_domain_free_fwnode(fwnode);
+
+ return ret;
+}
+
+static void rcar_gen4_pcie_rp_irq_deinit(struct rcar_gen4_pcie *rcar)
+{
+ struct fwnode_handle *fwnode = rcar->rp_irq_domain->fwnode;
+
+ /* The port services are gone; stop the parent IRQ before the domain. */
+ if (rcar->aer_irq > 0)
+ free_irq(rcar->aer_irq, rcar);
+
+ irq_dispose_mapping(rcar->rp_irq);
+ irq_domain_remove(rcar->rp_irq_domain);
+ irq_domain_free_fwnode(fwnode);
+}
+
static int rcar_gen4_pcie_enable_device(struct pci_host_bridge *bridge,
struct pci_dev *dev)
{
@@ -879,6 +1076,8 @@ static void rcar_gen4_pcie_quiesce_irqs(struct rcar_gen4_pcie *rcar)
/* The MSI status lives in DBI; keep the handler away during the reset. */
disable_irq(rcar->msi_irq);
+ if (rcar->aer_irq > 0)
+ disable_irq(rcar->aer_irq);
}
static void rcar_gen4_pcie_resume_irqs(struct rcar_gen4_pcie *rcar,
@@ -889,6 +1088,8 @@ static void rcar_gen4_pcie_resume_irqs(struct rcar_gen4_pcie *rcar,
rcar_gen4_pcie_app_irq_sync_locked(rcar);
}
+ if (rcar->aer_irq > 0)
+ enable_irq(rcar->aer_irq);
enable_irq(rcar->msi_irq);
}
@@ -963,12 +1164,18 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp)
if (ret)
goto err_deinit;
- ret = rcar_gen4_pcie_msi_irq_init(rcar);
+ ret = rcar_gen4_pcie_rp_irq_init(pp);
if (ret)
goto err_assert_perst;
+ ret = rcar_gen4_pcie_msi_irq_init(rcar);
+ if (ret)
+ goto err_rp_irq_deinit;
+
return 0;
+err_rp_irq_deinit:
+ rcar_gen4_pcie_rp_irq_deinit(rcar);
err_assert_perst:
rcar_gen4_pcie_host_perst_assert(pp, true);
err_deinit:
@@ -984,6 +1191,8 @@ static void rcar_gen4_pcie_host_deinit(struct dw_pcie_rp *pp)
/* Stop the handler before asserting reset and disabling the clocks. */
free_irq(rcar->msi_irq, rcar);
+ rcar_gen4_pcie_rp_irq_deinit(rcar);
+
rcar_gen4_pcie_host_perst_assert(pp, true);
rcar->drvdata->deinit(rcar);
}
@@ -1007,6 +1216,7 @@ static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
return ret;
raw_spin_lock_init(&rcar->app_lock);
+ raw_spin_lock_init(&rcar->rp_irq_lock);
INIT_WORK(&rcar->link_down_work, rcar_gen4_pcie_link_down_work);
pp->num_vectors = MAX_MSI_IRQS;
@@ -1016,6 +1226,12 @@ static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
* iMSI-RX MSIs from its own handler.
*/
pp->msi_irq[0] = -ENODEV;
+ /*
+ * The APP block only latches the Root Port's own interrupts into
+ * PCIEINTSTS0; they are never delivered as MSI. Fall back to INTx,
+ * which rcar_gen4_pcie_map_irq() routes to the virtual Root Port IRQ.
+ */
+ pp->no_rp_msi = true;
pp->ops = &rcar_gen4_pcie_host_ops;
return dw_pcie_host_init(pp);
--
2.51.0