From: Koichiro Den <den@valinux.co.jp>
To: "Marek Vasut" <marek.vasut+renesas@mailbox.org>,
"Geert Uytterhoeven" <geert+renesas@glider.be>,
"Yoshihiro Shimoda" <yoshihiro.shimoda.uh@renesas.com>,
"Lorenzo Pieralisi" <lpieralisi@kernel.org>,
"Krzysztof Wilczyński" <kwilczynski@kernel.org>,
"Manivannan Sadhasivam" <mani@kernel.org>,
"Rob Herring" <robh@kernel.org>,
"Bjorn Helgaas" <bhelgaas@google.com>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Magnus Damm" <magnus.damm@gmail.com>,
"Jingoo Han" <jingoohan1@gmail.com>
Cc: Philipp Zabel <p.zabel@pengutronix.de>,
Frank Li <Frank.Li@nxp.com>, Niklas Cassel <cassel@kernel.org>,
Wilfred Mallawa <wilfred.mallawa@wdc.com>,
Serge Semin <fancer.lancer@gmail.com>,
linux-pci@vger.kernel.org, linux-renesas-soc@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v3 14/18] PCI: rcar-gen4: Route Root Port AER to a virtual Root Port IRQ
Date: Tue, 6 Oct 2026 17:46:34 +0900 [thread overview]
Message-ID: <20261006084638.3821710-15-den@valinux.co.jp> (raw)
In-Reply-To: <20261006084638.3821710-1-den@valinux.co.jp>
On R-Car Gen4 SoCs, 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, iMSI-RX or an external MSI controller, Root Port
interrupts are not delivered to the port services.
Set no_rp_msi so the Root Port services use INTx regardless of the MSI
controller. Use a virtual IRQ to deliver notifications from separate
platform IRQs to the standard handlers. Route AER from the optional
"aer" interrupt. DTs without it remain supported, but Root Port AER
stays unavailable.
The APP block may still receive MSI-form notifications because hiding
the Root Port's MSI capabilities does not clear MSICAP0.MSIE. Handle
these alongside the INTx-form level signals. Clear the MSI-form latch
before the AER handler clears Root Error Status, so a late APP clear
does not erase a new notification.
Check for reset requests before dispatch: on S4, DBI accesses from
the port services can hang the host immediately after link down.
The next patch also feeds PME and bandwidth notifications from "msi"
into this IRQ, so both parents may dispatch concurrently.
handle_level_irq() calls irq_ack() before checking whether it can run
the handlers. Keep the APP latches set while the IRQ is in progress or
disabled, so the level parents can retry through the reset checks.
This avoids losing notifications without a raw lock around port
services that take regular spinlocks.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
Changes in v3:
- Create the virtual Root Port IRQ and request the AER parent in probe,
keep them for the lifetime of the driver, and enable and disable the
parent in .init() and .deinit() like the "msi" one.
- Drop the dispatch lock of v2, which nested the port services' locks
inside a raw spinlock. Keep the MSI-form latches set in irq_ack()
while the virtual IRQ is in progress or disabled instead. (Sashiko)
- Mark the virtual IRQ as level-triggered, so a pending interrupt is
re-delivered by the parent lines rather than resent in software.
- Revise the commit message.
v2: https://lore.kernel.org/r/20260928165230.3397664-14-den@valinux.co.jp/
@Marek, I did not carry your R-b tag over, as the patch changed quite a
bit. I'd appreciate another look.
drivers/pci/controller/dwc/pcie-rcar-gen4.c | 216 +++++++++++++++++++-
1 file changed, 213 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
index b8a5bfaf0bbc..db0d5362eaed 100644
--- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c
+++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
@@ -14,10 +14,12 @@
#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/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>
@@ -59,6 +61,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)
@@ -68,6 +75,7 @@
/* PCIe Interrupt Status 0 Clear */
#define PCIEINTSTS0CLR 0x0340
+#define RP_LATCHED_MASK AER_RC_ERR_MSI
/* Port Logic Registers 89 */
#define PRTLGC89 0x0b70
@@ -133,6 +141,12 @@ 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;
+ /* 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.
@@ -565,7 +579,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
@@ -573,6 +587,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);
@@ -676,6 +692,15 @@ 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)
+{
+ /* Recheck reset requests after the parent's status read. */
+ 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 =
@@ -770,6 +795,162 @@ 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);
+
+ /*
+ * handle_level_irq() may skip the handlers after calling irq_ack().
+ * Keep notifications latched if the IRQ is disabled or in progress,
+ * so they raise a parent IRQ again after unmasking. The descriptor
+ * lock serializes this check with IRQD_IRQ_INPROGRESS.
+ */
+ if (irqd_irq_disabled(d) || irqd_irq_inprogress(d))
+ return;
+
+ /* Clear APP latches before the service clears Root Error Status. */
+ 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_status_flags(irq, IRQ_LEVEL);
+ 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 rcar_gen4_pcie *rcar)
+{
+ struct device *dev = rcar->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");
+
+ 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. Enable it together with the "msi" parent IRQ.
+ */
+ ret = devm_request_irq(dev, irq, rcar_gen4_pcie_aer_irq_handler,
+ IRQF_NO_THREAD | IRQF_NO_AUTOEN,
+ "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;
+ }
+
+ 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;
+
+ 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)
{
@@ -870,6 +1051,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,
@@ -880,6 +1063,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);
}
@@ -946,6 +1131,12 @@ static int rcar_gen4_pcie_host_init(struct dw_pcie_rp *pp)
if (pp->bridge) {
pp->bridge->enable_device = rcar_gen4_pcie_enable_device;
pp->bridge->reset_root_port = rcar_gen4_pcie_reset_root_port;
+ /*
+ * 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;
}
rcar_gen4_pcie_host_perst_assert(pp, true);
@@ -1004,10 +1195,14 @@ static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
raw_spin_lock_init(&rcar->app_lock);
INIT_WORK(&rcar->link_down_work, rcar_gen4_pcie_link_down_work);
- ret = rcar_gen4_pcie_msi_irq_init(rcar);
+ ret = rcar_gen4_pcie_rp_irq_init(rcar);
if (ret)
return ret;
+ ret = rcar_gen4_pcie_msi_irq_init(rcar);
+ if (ret)
+ goto err_rp_irq_deinit;
+
pp->num_vectors = MAX_MSI_IRQS;
/* Reserve an iATU window for the generic PME_Turn_Off implementation. */
pp->use_atu_msg = true;
@@ -1017,9 +1212,23 @@ 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);
+ ret = dw_pcie_host_init(pp);
+ if (ret)
+ goto err_rp_irq_deinit;
+
+ return 0;
+
+err_rp_irq_deinit:
+ rcar_gen4_pcie_rp_irq_deinit(rcar);
+ return ret;
}
static void rcar_gen4_remove_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
@@ -1027,6 +1236,7 @@ static void rcar_gen4_remove_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
/* Cancel recovery work before removing the PCI bus */
rcar_gen4_pcie_rp_events_disarm(rcar);
dw_pcie_host_deinit(&rcar->dw.pp);
+ rcar_gen4_pcie_rp_irq_deinit(rcar);
}
/* Endpoint mode */
--
2.51.0
next prev parent reply other threads:[~2026-10-06 8:47 UTC|newest]
Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-06 8:46 [PATCH v3 00/18] PCI: rcar-gen4: Recover from link down and route Root Port interrupts Koichiro Den
2026-10-06 8:46 ` [PATCH v3 01/18] PCI: dwc: Add Renesas to the RAS DES VSEC list Koichiro Den
2026-10-06 8:50 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 02/18] PCI: dwc: Factor out the PORT_LINK_DEBUG1 link-up check Koichiro Den
2026-10-06 8:51 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 03/18] PCI: rcar-gen4: Check live link status in link_up() Koichiro Den
2026-10-06 8:53 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 04/18] dt-bindings: PCI: rcar-gen4: Add optional "aer" interrupt Koichiro Den
2026-10-06 8:52 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 05/18] PCI: dwc: Export dw_handle_msi_irq() Koichiro Den
2026-10-06 8:51 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 06/18] PCI: rcar-gen4: Move deinitialization helpers before SoC initialization Koichiro Den
2026-10-06 8:51 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 07/18] PCI: rcar-gen4: Assert resets when Gen5 SoC PHY initialization fails Koichiro Den
2026-10-06 8:54 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 08/18] PCI: rcar-gen4: Separate hardware setup from resource acquisition Koichiro Den
2026-10-06 8:50 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 09/18] PCI: rcar-gen4: Add Root Port reset support Koichiro Den
2026-10-06 8:55 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 10/18] PCI: dwc: Free the MSI domain after the host .deinit() callback Koichiro Den
2026-10-06 9:01 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 11/18] PCI: rcar-gen4: Take over the iMSI-RX interrupt Koichiro Den
2026-10-06 8:56 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 12/18] PCI: rcar-gen4: Recover the Root Port on link down Koichiro Den
2026-10-06 8:58 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 13/18] PCI: dwc: Let glue drivers hide the Root Port MSI capabilities Koichiro Den
2026-10-06 8:51 ` sashiko-bot
2026-10-06 8:46 ` Koichiro Den [this message]
2026-10-06 8:54 ` [PATCH v3 14/18] PCI: rcar-gen4: Route Root Port AER to a virtual Root Port IRQ sashiko-bot
2026-10-06 8:46 ` [PATCH v3 15/18] PCI: rcar-gen4: Route Root Port PME and bandwidth notifications Koichiro Den
2026-10-06 8:54 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 16/18] arm64: dts: renesas: r8a779f0: Describe the PCIe AER interrupts Koichiro Den
2026-10-06 8:52 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 17/18] arm64: dts: renesas: r8a779g0: " Koichiro Den
2026-10-06 8:52 ` sashiko-bot
2026-10-06 8:46 ` [PATCH v3 18/18] arm64: dts: renesas: r8a779h0: Describe the PCIe AER interrupt Koichiro Den
2026-10-06 8:53 ` sashiko-bot
2026-10-08 5:45 ` [PATCH v3 00/18] PCI: rcar-gen4: Recover from link down and route Root Port interrupts Koichiro Den
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261006084638.3821710-15-den@valinux.co.jp \
--to=den@valinux.co.jp \
--cc=Frank.Li@nxp.com \
--cc=bhelgaas@google.com \
--cc=cassel@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=fancer.lancer@gmail.com \
--cc=geert+renesas@glider.be \
--cc=jingoohan1@gmail.com \
--cc=krzk+dt@kernel.org \
--cc=kwilczynski@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=linux-renesas-soc@vger.kernel.org \
--cc=lpieralisi@kernel.org \
--cc=magnus.damm@gmail.com \
--cc=mani@kernel.org \
--cc=marek.vasut+renesas@mailbox.org \
--cc=p.zabel@pengutronix.de \
--cc=robh@kernel.org \
--cc=wilfred.mallawa@wdc.com \
--cc=yoshihiro.shimoda.uh@renesas.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox