From: Koichiro Den <den@valinux.co.jp>
To: "Marek Vasut" <marek.vasut+renesas@mailbox.org>,
"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>,
"Geert Uytterhoeven" <geert+renesas@glider.be>,
"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 07/11] PCI: rcar-gen4: Recover the Root Port on link down
Date: Fri, 18 Sep 2026 12:20:34 +0900 [thread overview]
Message-ID: <20260918032038.2216471-8-den@valinux.co.jp> (raw)
In-Reply-To: <20260918032038.2216471-1-den@valinux.co.jp>
On R-Car, intreq_pcim_sub carries both the integrated MSI receiver and
the controller's reset requests (smlh_req_rst_not, link_req_rst_not), so
the generic DesignWare chained handler reads the MSI status from DBI as
soon as the link goes down. On R-Car S4 that is a hazard: DBI accesses
issued within a few hundred microseconds of an unexpected link down do
not complete and hang the host. In testing, the first Root Port config
read after powering off the link partner hung unless delayed by ~300 us.
Use the pre-MSI callback to check the APP reset status before DBI is
touched. When a reset request is latched, mask the sources, ack the
request and schedule recovery work. The work calls
pci_host_handle_link_down(), which runs the AER-style recovery and
resets the controller through reset_root_port(). If the reset fails, the
sources stay masked so nothing touches the unrecovered controller.
Only unmasked status bits are handled and pending latches are cleared
when re-arming, so requests recorded during probe or the reset itself do
not trigger another recovery. Teardown only disables link-down
detection: MSI delivery has to keep working while devices are removed.
When iMSI-RX is not used (external MSI controller or pci=nomsi), the
DesignWare core does not request intreq_pcim_sub, so request it in the
driver. The DT routes downstream INTx to the same line, but the driver
has never supported INTx (no INTx domain, INTx enables never set), so
taking the line exclusively takes nothing away. Keep msi_ctrl_int
masked in this mode since nothing would clear it.
Signed-off-by: Koichiro Den <den@valinux.co.jp>
---
drivers/pci/controller/dwc/Kconfig | 1 +
drivers/pci/controller/dwc/pcie-rcar-gen4.c | 201 +++++++++++++++++++-
2 files changed, 199 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig
index dcfbe7e229fd..573fcb2e44d7 100644
--- a/drivers/pci/controller/dwc/Kconfig
+++ b/drivers/pci/controller/dwc/Kconfig
@@ -350,6 +350,7 @@ config PCIE_RCAR_GEN4_HOST
depends on PCI_MSI
select PCIE_DW_HOST
select PCIE_RCAR_GEN4
+ select PCI_HOST_COMMON
help
Say Y here if you want PCIe controller (host mode) on R-Car Gen4 SoCs.
To compile this driver as a module, choose M here: the module will be
diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
index 5b983ef6dc47..238755852045 100644
--- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c
+++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c
@@ -25,6 +25,7 @@
#include <linux/reset.h>
#include "../../pci.h"
+#include "../pci-host-common.h"
#include "pcie-designware.h"
/* Renesas-specific */
@@ -39,9 +40,18 @@
#define MSICAP0 0x0050
#define MSICAP0_MSIE BIT(16)
+/* PCIe Reset Status */
+#define PCIERSTSTS 0x0020
+#define SMLH_REQ_RST_NOT BIT(2)
+#define LINK_REQ_RST_NOT BIT(1)
+#define LINK_DOWN_RESET_MASK (SMLH_REQ_RST_NOT | LINK_REQ_RST_NOT)
+
/* PCIe Interrupt Status 0 */
#define PCIEINTSTS0 0x0084
+/* PCIe Reset Status Enable */
+#define PCIERSTSTSEN 0x0300
+
/* PCIe Interrupt Status 0 Enable */
#define PCIEINTSTS0EN 0x0310
#define MSI_CTRL_INT BIT(26)
@@ -50,6 +60,9 @@
#define PCIEDMAINTSTSEN 0x0314
#define PCIEDMAINTSTSEN_INIT GENMASK(15, 0)
+/* PCIe Reset Status Clear */
+#define PCIERSTSTSCLR 0x0330
+
/* Port Logic Registers 89 */
#define PRTLGC89 0x0b70
@@ -92,6 +105,10 @@ struct rcar_gen4_pcie_drvdata {
};
enum rcar_gen4_pcie_state {
+ /* A reset request is pending, or its recovery failed */
+ RCAR_PCIE_LINK_DOWN,
+ /* Link-down detection is being torn down */
+ RCAR_PCIE_IRQ_STOPPED,
/* The controller is being reset and reinitialized */
RCAR_PCIE_RESETTING,
};
@@ -102,6 +119,10 @@ struct rcar_gen4_pcie {
void __iomem *phy_base;
struct platform_device *pdev;
const struct rcar_gen4_pcie_drvdata *drvdata;
+ /* intreq_pcim_sub ("msi"), shared with the iMSI-RX */
+ int sub_irq;
+ bool sub_irq_owned;
+ struct work_struct link_down_work;
unsigned long state;
/* Protects APP interrupt enable registers and their software state. */
raw_spinlock_t app_lock;
@@ -361,8 +382,19 @@ static bool rcar_gen4_pcie_irqs_blocked(struct rcar_gen4_pcie *rcar)
return !!rcar->state;
}
+/*
+ * DBI must not be touched while the link is down or the controller is being
+ * reset.
+ */
+static bool rcar_gen4_pcie_dbi_unsafe(struct rcar_gen4_pcie *rcar)
+{
+ return test_bit(RCAR_PCIE_LINK_DOWN, &rcar->state) ||
+ test_bit(RCAR_PCIE_RESETTING, &rcar->state);
+}
+
static void rcar_gen4_pcie_app_irq_sync_locked(struct rcar_gen4_pcie *rcar)
{
+ struct dw_pcie_rp *pp = &rcar->dw.pp;
bool armed = !rcar_gen4_pcie_irqs_blocked(rcar);
u32 val;
@@ -370,9 +402,28 @@ static void rcar_gen4_pcie_app_irq_sync_locked(struct rcar_gen4_pcie *rcar)
val = readl(rcar->base + PCIEINTSTS0EN);
val &= ~MSI_CTRL_INT;
- if (armed && pci_msi_enabled())
+ /*
+ * MSI delivery only depends on DBI being usable; teardown must not
+ * stop it while devices are still being removed.
+ */
+ if (!rcar_gen4_pcie_dbi_unsafe(rcar) && pp->use_imsi_rx &&
+ pci_msi_enabled())
val |= MSI_CTRL_INT;
writel(val, rcar->base + PCIEINTSTS0EN);
+
+ val = readl(rcar->base + PCIERSTSTSEN);
+ if (armed && rcar->sub_irq > 0) {
+ /*
+ * Clear latches recorded while the sources were masked, so
+ * stale requests do not fire as soon as they are re-enabled.
+ */
+ if (!(val & LINK_DOWN_RESET_MASK))
+ writel(LINK_DOWN_RESET_MASK, rcar->base + PCIERSTSTSCLR);
+ val |= LINK_DOWN_RESET_MASK;
+ } else {
+ val &= ~LINK_DOWN_RESET_MASK;
+ }
+ writel(val, rcar->base + PCIERSTSTSEN);
}
static void rcar_gen4_pcie_app_irq_sync(struct rcar_gen4_pcie *rcar)
@@ -435,6 +486,134 @@ static int rcar_gen4_pcie_host_msi_init(struct dw_pcie_rp *pp)
return ret;
}
+/* Returns true if a latched reset-request status was consumed. */
+static bool rcar_gen4_pcie_handle_link_down(struct rcar_gen4_pcie *rcar)
+{
+ bool schedule = false;
+ u32 status;
+
+ scoped_guard(raw_spinlock_irqsave, &rcar->app_lock) {
+ status = readl(rcar->base + PCIERSTSTS) &
+ readl(rcar->base + PCIERSTSTSEN) & LINK_DOWN_RESET_MASK;
+ if (!status)
+ return false;
+
+ if (!test_bit(RCAR_PCIE_IRQ_STOPPED, &rcar->state) &&
+ !test_bit(RCAR_PCIE_RESETTING, &rcar->state))
+ schedule = !test_and_set_bit(RCAR_PCIE_LINK_DOWN,
+ &rcar->state);
+ rcar_gen4_pcie_app_irq_sync_locked(rcar);
+ writel(status, rcar->base + PCIERSTSTSCLR);
+ }
+
+ if (schedule)
+ schedule_work(&rcar->link_down_work);
+
+ return true;
+}
+
+static void rcar_gen4_pcie_link_down_work(struct work_struct *work)
+{
+ struct rcar_gen4_pcie *rcar =
+ container_of(work, struct rcar_gen4_pcie, link_down_work);
+ struct dw_pcie_rp *pp = &rcar->dw.pp;
+ struct pci_dev *port;
+
+ for_each_pci_bridge(port, pp->bridge->bus) {
+ if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT)
+ pci_host_handle_link_down(port);
+ }
+
+ if (test_bit(RCAR_PCIE_LINK_DOWN, &rcar->state))
+ dev_err(rcar->dw.dev,
+ "Root Port reset failed; keeping the controller's interrupts masked until a reset succeeds\n");
+}
+
+static bool rcar_gen4_pcie_pre_msi_irq(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);
+
+ rcar_gen4_pcie_handle_link_down(rcar);
+ if (rcar_gen4_pcie_dbi_unsafe(rcar))
+ return true;
+
+ /* Check once more right before the DesignWare DBI access. */
+ rcar_gen4_pcie_handle_link_down(rcar);
+
+ return rcar_gen4_pcie_dbi_unsafe(rcar);
+}
+
+static irqreturn_t rcar_gen4_pcie_sub_irq_handler(int irq, void *data)
+{
+ struct rcar_gen4_pcie *rcar = data;
+
+ return rcar_gen4_pcie_handle_link_down(rcar) ?
+ IRQ_HANDLED : IRQ_NONE;
+}
+
+/*
+ * Arm link-down detection once enumeration is done: the chained iMSI-RX
+ * handler is only installed after ops->init, and the recovery needs the
+ * Root Port to exist.
+ */
+static void rcar_gen4_pcie_link_down_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;
+ int irq, ret;
+
+ if (pp->use_imsi_rx && pp->msi_irq[0] > 0) {
+ /* The DesignWare core owns the chained handler of this interrupt. */
+ rcar->sub_irq = pp->msi_irq[0];
+ } else {
+ irq = platform_get_irq_byname_optional(rcar->pdev, "msi");
+ if (irq < 0) {
+ dev_warn(dev, "Failed to get \"msi\" IRQ; link-down detection unavailable\n");
+ return;
+ }
+
+ /*
+ * Run in hard IRQ context even on PREEMPT_RT, like the chained
+ * handler, so the reset request is masked before anything else
+ * touches DBI.
+ */
+ ret = request_irq(irq, rcar_gen4_pcie_sub_irq_handler,
+ IRQF_NO_THREAD, dev_name(dev), rcar);
+ if (ret) {
+ dev_warn(dev, "Failed to request \"msi\" IRQ; link-down detection unavailable\n");
+ return;
+ }
+
+ rcar->sub_irq = irq;
+ rcar->sub_irq_owned = true;
+ }
+
+ guard(raw_spinlock_irqsave)(&rcar->app_lock);
+ clear_bit(RCAR_PCIE_IRQ_STOPPED, &rcar->state);
+ rcar_gen4_pcie_app_irq_sync_locked(rcar);
+}
+
+static void rcar_gen4_pcie_link_down_irq_deinit(struct rcar_gen4_pcie *rcar)
+{
+ if (test_and_set_bit(RCAR_PCIE_IRQ_STOPPED, &rcar->state))
+ return;
+
+ rcar_gen4_pcie_app_irq_sync(rcar);
+
+ if (rcar->sub_irq > 0)
+ synchronize_irq(rcar->sub_irq);
+
+ cancel_work_sync(&rcar->link_down_work);
+
+ if (rcar->sub_irq_owned) {
+ free_irq(rcar->sub_irq, rcar);
+ rcar->sub_irq_owned = false;
+ }
+ rcar->sub_irq = 0;
+}
+
static int rcar_gen4_pcie_enable_device(struct pci_host_bridge *bridge,
struct pci_dev *dev)
{
@@ -524,14 +703,23 @@ static void rcar_gen4_pcie_quiesce_irqs(struct rcar_gen4_pcie *rcar)
set_bit(RCAR_PCIE_RESETTING, &rcar->state);
rcar_gen4_pcie_app_irq_sync_locked(rcar);
}
+
+ if (rcar->sub_irq > 0)
+ disable_irq(rcar->sub_irq);
}
-static void rcar_gen4_pcie_resume_irqs(struct rcar_gen4_pcie *rcar)
+static void rcar_gen4_pcie_resume_irqs(struct rcar_gen4_pcie *rcar,
+ bool recovered)
{
scoped_guard(raw_spinlock_irqsave, &rcar->app_lock) {
+ if (recovered)
+ clear_bit(RCAR_PCIE_LINK_DOWN, &rcar->state);
clear_bit(RCAR_PCIE_RESETTING, &rcar->state);
rcar_gen4_pcie_app_irq_sync_locked(rcar);
}
+
+ if (rcar->sub_irq > 0)
+ enable_irq(rcar->sub_irq);
}
/*
@@ -574,7 +762,7 @@ static int rcar_gen4_pcie_reset_root_port(struct pci_host_bridge *bridge,
dev_dbg(dev, "Root Port reset completed\n");
out:
- rcar_gen4_pcie_resume_irqs(rcar);
+ rcar_gen4_pcie_resume_irqs(rcar, !ret);
return ret;
}
@@ -610,6 +798,8 @@ static void rcar_gen4_pcie_host_deinit(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);
+ rcar_gen4_pcie_link_down_irq_deinit(rcar);
+
gpiod_set_value_cansleep(dw->pe_rst, 1);
rcar_gen4_pcie_common_deinit(rcar);
}
@@ -617,6 +807,8 @@ static void rcar_gen4_pcie_host_deinit(struct dw_pcie_rp *pp)
static const struct dw_pcie_host_ops rcar_gen4_pcie_host_ops = {
.init = rcar_gen4_pcie_host_init,
.deinit = rcar_gen4_pcie_host_deinit,
+ .post_init = rcar_gen4_pcie_link_down_irq_init,
+ .pre_msi_irq = rcar_gen4_pcie_pre_msi_irq,
};
static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
@@ -632,6 +824,7 @@ static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
return ret;
raw_spin_lock_init(&rcar->app_lock);
+ INIT_WORK(&rcar->link_down_work, rcar_gen4_pcie_link_down_work);
pp->num_vectors = MAX_MSI_IRQS;
pp->ops = &rcar_gen4_pcie_host_ops;
@@ -641,6 +834,8 @@ static int rcar_gen4_add_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
static void rcar_gen4_remove_dw_pcie_rp(struct rcar_gen4_pcie *rcar)
{
+ /* Cancel recovery work before removing the PCI bus */
+ rcar_gen4_pcie_link_down_irq_deinit(rcar);
dw_pcie_host_deinit(&rcar->dw.pp);
}
--
2.51.0
next prev parent reply other threads:[~2026-09-18 3:20 UTC|newest]
Thread overview: 45+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 3:20 [PATCH 00/11] PCI: rcar-gen4: Recover from link down and route Root Port interrupts Koichiro Den
2026-09-18 3:20 ` [PATCH 01/11] PCI: dwc: Add Renesas to the RAS DES VSEC list Koichiro Den
2026-09-18 3:24 ` sashiko-bot
2026-09-22 19:40 ` Marek Vasut
2026-09-18 3:20 ` [PATCH 02/11] PCI: rcar-gen4: Drop the APP-based link_up check Koichiro Den
2026-09-18 3:25 ` sashiko-bot
2026-09-22 20:56 ` Marek Vasut
2026-09-23 14:56 ` Koichiro Den
2026-09-27 19:59 ` Marek Vasut
2026-09-28 4:20 ` Koichiro Den
2026-09-28 15:07 ` Marek Vasut
2026-09-18 3:20 ` [PATCH 03/11] dt-bindings: PCI: rcar-gen4: Add optional "aer" interrupt Koichiro Den
2026-09-18 3:25 ` sashiko-bot
2026-09-22 20:59 ` Marek Vasut
2026-09-28 18:32 ` Rob Herring (Arm)
2026-09-18 3:20 ` [PATCH 04/11] PCI: dwc: Add a host op to run before iMSI-RX status is read Koichiro Den
2026-09-18 3:32 ` sashiko-bot
2026-09-18 3:20 ` [PATCH 05/11] PCI: rcar-gen4: Split reusable hardware initialization Koichiro Den
2026-09-18 3:27 ` sashiko-bot
2026-09-22 21:15 ` Marek Vasut
2026-09-23 15:24 ` Koichiro Den
2026-09-27 20:43 ` Marek Vasut
2026-09-18 3:20 ` [PATCH 06/11] PCI: rcar-gen4: Add Root Port reset support Koichiro Den
2026-09-18 3:29 ` sashiko-bot
2026-09-22 21:22 ` Marek Vasut
2026-09-23 16:12 ` Koichiro Den
2026-09-27 22:25 ` Marek Vasut
2026-09-28 3:50 ` Koichiro Den
2026-09-28 17:47 ` Marek Vasut
2026-09-18 3:20 ` Koichiro Den [this message]
2026-09-18 3:33 ` [PATCH 07/11] PCI: rcar-gen4: Recover the Root Port on link down sashiko-bot
2026-09-22 21:44 ` Marek Vasut
2026-09-24 16:15 ` Koichiro Den
2026-09-27 22:37 ` Marek Vasut
2026-09-28 4:06 ` Koichiro Den
2026-09-28 17:36 ` Marek Vasut
2026-09-18 3:20 ` [PATCH 08/11] PCI: dwc: Let glue drivers hide the Root Port MSI capabilities Koichiro Den
2026-09-18 3:24 ` sashiko-bot
2026-09-18 3:20 ` [PATCH 09/11] PCI: rcar-gen4: Route Root Port AER to a virtual Root Port IRQ Koichiro Den
2026-09-18 3:32 ` sashiko-bot
2026-09-18 3:20 ` [PATCH 10/11] PCI: rcar-gen4: Route Root Port PME and bandwidth notifications Koichiro Den
2026-09-18 3:30 ` sashiko-bot
2026-09-18 3:20 ` [PATCH 11/11] arm64: dts: renesas: r8a779f0: Describe the PCIe AER interrupts Koichiro Den
2026-09-18 3:27 ` sashiko-bot
2026-09-22 21:31 ` Marek Vasut
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=20260918032038.2216471-8-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