From 36637958726a4bbc630c0848eb3eb07cbfae89c2 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 29 Jul 2026 10:22:27 +0530 Subject: [PATCH 1/6] PCI: dwc: ep: Clear MSI iATU mapping in dw_pcie_ep_cleanup() The MSI iATU mapping is currently only cleared when the endpoint is stopped via configfs or when the host updates the MSI address/size. This avoids redundant iATU reconfiguration every time the endpoint raises an MSI interrupt. However, a fundamental reset triggered by PERST# assert/deassert resets all iATU inbound/outbound registers without going through the configfs stop path. If the host also retains the same MSI address/size after PERST# deassert, the driver never clears the stale MSI iATU mapping. It then continues using this stale mapping to raise the MSI interrupts, which can cause IOMMU faults and MSI failures on the host. Fix this by clearing the MSI iATU mapping inside dw_pcie_ep_cleanup(), which is already called as part of the PERST# assert/deassert sequence. This unmaps the MSI iATU region and sets the msi_iatu_mapped flag to false, ensuring that dw_pcie_ep_raise_msi_irq() performs a fresh iATU mapping on its next invocation, regardless of whether the host changed the MSI address/size. Fixes: 8719c64e76bf ("PCI: dwc: ep: Cache MSI outbound iATU mapping") Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260729-pci-port-reset-v9-1-53570b92064d@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware-ep.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 7d2794945704..31402ae218c7 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -1153,6 +1153,11 @@ void dw_pcie_ep_cleanup(struct dw_pcie_ep *ep) { struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + if (ep->msi_iatu_mapped) { + dw_pcie_ep_unmap_addr(ep->epc, 0, 0, ep->msi_mem_phys); + ep->msi_iatu_mapped = false; + } + dwc_pcie_debugfs_deinit(pci); dw_pcie_edma_remove(pci); } From 3fc686d550f6dba3f4fd53aec173b6dee15e7f98 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 29 Jul 2026 10:22:28 +0530 Subject: [PATCH 2/6] PCI/ERR: Add support for resetting the Root Ports in a platform-specific way Some host bridge devices require resetting the Root Ports in a platform specific way to recover them from error conditions such as Fatal AER errors, Link Down, etc. Introduce pci_host_bridge::reset_root_port() callback and call it from pcibios_reset_secondary_bus() if available. Also, save the Root Port config space before reset and restore it afterwards. The .reset_root_port() callback is responsible for resetting the given Root Port referenced by the 'pci_dev' pointer in a platform-specific way and bring it back to the working state if possible. If any error occurs during the reset operation, relevant errno should be returned. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Brian Norris Tested-by: Krishna Chaitanya Chundru Tested-by: Richard Zhu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260729-pci-port-reset-v9-2-53570b92064d@oss.qualcomm.com --- drivers/pci/pci.c | 13 +++++++++++++ drivers/pci/pcie/err.c | 5 ----- include/linux/pci.h | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 77b17b13ee61..12e099489690 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4841,6 +4841,19 @@ void pci_reset_secondary_bus(struct pci_dev *dev) void __weak pcibios_reset_secondary_bus(struct pci_dev *dev) { + struct pci_host_bridge *host = pci_find_host_bridge(dev->bus); + int ret; + + if (pci_is_root_bus(dev->bus) && host->reset_root_port) { + ret = host->reset_root_port(host, dev); + if (ret) + pci_err(dev, "Failed to reset Root Port: %d\n", ret); + else + pci_restore_state(dev); + + return; + } + pci_reset_secondary_bus(dev); } diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c index bebe4bc111d7..13b9d9eb714f 100644 --- a/drivers/pci/pcie/err.c +++ b/drivers/pci/pcie/err.c @@ -256,11 +256,6 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, } if (status == PCI_ERS_RESULT_NEED_RESET) { - /* - * TODO: Should call platform-specific - * functions to reset slot before calling - * drivers' slot_reset callbacks? - */ status = PCI_ERS_RESULT_RECOVERED; pci_dbg(bridge, "broadcast slot_reset message\n"); pci_walk_bridge(bridge, report_slot_reset, &status); diff --git a/include/linux/pci.h b/include/linux/pci.h index ebb5b9d76360..be82cc0a3371 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -646,6 +646,7 @@ struct pci_host_bridge { void (*release_fn)(struct pci_host_bridge *); int (*enable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev); void (*disable_device)(struct pci_host_bridge *bridge, struct pci_dev *dev); + int (*reset_root_port)(struct pci_host_bridge *bridge, struct pci_dev *dev); void *release_data; unsigned int ignore_reset_delay:1; /* For entire hierarchy */ unsigned int no_ext_tags:1; /* No Extended Tags */ From 4c99bace4f4efdb8dbeddf71cde4a4793f5f2228 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 29 Jul 2026 10:22:29 +0530 Subject: [PATCH 3/6] PCI: host-common: Add link down handling for Root Ports The PCIe link, when down, needs to be recovered to bring it back. But on some platforms, that cannot be done in a generic way as link recovery procedure is platform specific. Add a new pci_host_handle_link_down() that could be called by the host bridge drivers for a specific Root Port when the link goes down. pci_host_handle_link_down() accepts a 'pci_dev' corresponding to the Root Port that observed the link down event. If CONFIG_PCIEAER is enabled, it calls pcie_do_recovery() with 'pci_channel_io_frozen' as the state. This will result in the execution of the AER Fatal error handling code. Since the link down recovery is pretty much the same as AER Fatal error handling, reuse pcie_do_recovery() here. The AER .error_detected() callback will be triggered for all of the downstream devices, but not for the Root Port itself as there is nothing to do for the Root Ports in the callbacks. Finally, pci_host_reset_root_port() will be called for the Root Port, which will reset the Root Port using the .reset_root_port() callback to recover the link. Once that's done, resume message will be broadcasted to the bridge and the downstream devices, indicating successful link recovery. But if CONFIG_PCIEAER is not enabled in the kernel, only pci_host_reset_root_port() will be called, which will in turn call pci_bus_error_reset() to just reset the Root Port as there is no way we could inform the drivers about link recovery. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Brian Norris Tested-by: Krishna Chaitanya Chundru Tested-by: Richard Zhu Reviewed-by: Frank Li Link: https://patch.msgid.link/20260729-pci-port-reset-v9-3-53570b92064d@oss.qualcomm.com --- drivers/pci/controller/pci-host-common.c | 35 ++++++++++++++++++++++++ drivers/pci/controller/pci-host-common.h | 1 + drivers/pci/pci.c | 1 + drivers/pci/pcie/err.c | 1 + 4 files changed, 38 insertions(+) diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index 2ce6f4b66133..363d3f970b10 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -13,9 +13,11 @@ #include #include #include +#include #include #include +#include "../pci.h" #include "pci-host-common.h" /** @@ -342,5 +344,38 @@ bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge, } EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible); +static pci_ers_result_t pci_host_reset_root_port(struct pci_dev *dev) +{ + int ret; + + pci_lock_rescan_remove(); + ret = pci_bus_error_reset(dev); + pci_unlock_rescan_remove(); + if (ret) { + pci_err(dev, "Failed to reset Root Port: %d\n", ret); + return PCI_ERS_RESULT_DISCONNECT; + } + + pci_info(dev, "Root Port has been reset\n"); + + return PCI_ERS_RESULT_RECOVERED; +} + +static void pci_host_recover_root_port(struct pci_dev *port) +{ +#if IS_ENABLED(CONFIG_PCIEAER) + pcie_do_recovery(port, pci_channel_io_frozen, pci_host_reset_root_port); +#else + pci_host_reset_root_port(port); +#endif +} + +void pci_host_handle_link_down(struct pci_dev *port) +{ + pci_info(port, "Recovering Root Port due to Link Down\n"); + pci_host_recover_root_port(port); +} +EXPORT_SYMBOL_GPL(pci_host_handle_link_down); + MODULE_DESCRIPTION("Common library for PCI host controller drivers"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h index 9f0f36a32221..51afd65d3c4a 100644 --- a/drivers/pci/controller/pci-host-common.h +++ b/drivers/pci/controller/pci-host-common.h @@ -48,6 +48,7 @@ int pci_host_common_init(struct platform_device *pdev, struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops); void pci_host_common_remove(struct platform_device *pdev); +void pci_host_handle_link_down(struct pci_dev *port); struct pci_config_window *pci_host_common_ecam_create(struct device *dev, struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 12e099489690..ff6d5d059b21 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5700,6 +5700,7 @@ int pci_bus_error_reset(struct pci_dev *bridge) { return pci_reset_bridge(bridge, PCI_RESET_NO_RESTORE); } +EXPORT_SYMBOL_GPL(pci_bus_error_reset); int pci_try_reset_bridge(struct pci_dev *bridge) { diff --git a/drivers/pci/pcie/err.c b/drivers/pci/pcie/err.c index 13b9d9eb714f..d77403d8855b 100644 --- a/drivers/pci/pcie/err.c +++ b/drivers/pci/pcie/err.c @@ -292,3 +292,4 @@ pci_ers_result_t pcie_do_recovery(struct pci_dev *dev, return status; } +EXPORT_SYMBOL_GPL(pcie_do_recovery); From 4d88cb82a6d9fcd3815511fbf6df0fb77d9211da Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 29 Jul 2026 10:22:30 +0530 Subject: [PATCH 4/6] PCI: qcom: Implement .reset_root_port() and use for link down The PCIe link can go down under circumstances such as the device firmware crash, link instability, etc. When that happens, the Root Port needs to be reset to make it operational again. Currently, the driver is not handling the link down event, so users have to restart the machine to make PCIe link operational again. Fix it by detecting the link down event and resetting the Root Port. Since the Qcom PCIe controllers report the link down event through the 'global' IRQ, enable the link down event by setting PARF_INT_ALL_LINK_DOWN in the PARF_INT_ALL_MASK register. In the case of the event, iterate through the available Root Ports and call pci_host_handle_link_down() API with Root Port 'pci_dev' to let the PCI core handle the link down condition. Since Qcom PCIe controllers only support one Root Port per controller instance, the API will be called only once. But the looping is necessary as there is no PCI API available to fetch the Root Port instance without the child 'pci_dev'. The API will internally call the 'pci_host_bridge::reset_root_port()' callback to reset the Root Port in a platform-specific way. Implement the callback to reset the Root Port by first resetting the PCIe core, followed by reinitializing the resources and then finally starting the link again. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam [bhelgaas: subject] Signed-off-by: Bjorn Helgaas Tested-by: Krishna Chaitanya Chundru Reviewed-by: Krishna Chaitanya Chundru Link: https://patch.msgid.link/20260729-pci-port-reset-v9-4-53570b92064d@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 140 ++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index d8eb52857f69..62b222c20464 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -56,6 +56,10 @@ #define PARF_AXI_MSTR_WR_ADDR_HALT_V2 0x1a8 #define PARF_Q2A_FLUSH 0x1ac #define PARF_LTSSM 0x1b0 +#define PARF_INT_ALL_STATUS 0x224 +#define PARF_INT_ALL_CLEAR 0x228 +#define PARF_INT_ALL_MASK 0x22c +#define PARF_STATUS 0x230 #define PARF_SID_OFFSET 0x234 #define PARF_BDF_TRANSLATE_CFG 0x24c #define PARF_DBI_BASE_ADDR_V2 0x350 @@ -133,6 +137,13 @@ /* PARF_LTSSM register fields */ #define LTSSM_EN BIT(8) #define PARF_LTSSM_STATE_MASK GENMASK(5, 0) +#define SW_CLEAR_FLUSH_MODE BIT(10) +#define FLUSH_MODE BIT(11) + +/* PARF_INT_ALL_{STATUS/CLEAR/MASK} register fields */ +#define INT_ALL_LINK_DOWN 1 +#define PARF_INT_ALL_LINK_DOWN BIT(INT_ALL_LINK_DOWN) +#define PARF_INT_MSI_DEV_0_7 GENMASK(30, 23) /* PARF_NO_SNOOP_OVERRIDE register fields */ #define WR_NO_SNOOP_OVERRIDE_EN BIT(1) @@ -144,6 +155,9 @@ /* PARF_BDF_TO_SID_CFG fields */ #define BDF_TO_SID_BYPASS BIT(0) +/* PARF_STATUS fields */ +#define FLUSH_COMPLETED BIT(8) + /* ELBI_SYS_CTRL register fields */ #define ELBI_SYS_CTRL_LT_ENABLE BIT(0) #define ELBI_SYS_CTRL_PME_TURNOFF_MSG BIT(4) @@ -172,6 +186,7 @@ PCIE_CAP_SLOT_POWER_LIMIT_SCALE) #define PERST_DELAY_US 1000 +#define FLUSH_TIMEOUT_US 100 #define QCOM_PCIE_CRC8_POLYNOMIAL (BIT(2) | BIT(1) | BIT(0)) @@ -291,10 +306,13 @@ struct qcom_pcie { struct dentry *debugfs; struct list_head ports; struct gpio_desc *reset; + int global_irq; bool use_pm_opp; }; #define to_qcom_pcie(x) dev_get_drvdata((x)->dev) +static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, + struct pci_dev *pdev); static void __qcom_pcie_perst_assert(struct qcom_pcie *pcie, bool assert) { @@ -1406,6 +1424,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) goto err_assert_reset; } + pp->bridge->reset_root_port = qcom_pcie_reset_root_port; + return 0; err_assert_reset: @@ -1734,6 +1754,75 @@ static int qcom_pcie_set_max_opp(struct device *dev) return ret; } +/* + * Qcom PCIe controllers only support one Root Port per controller instance. So + * this function ignores the 'pci_dev' associated with the Root Port and just + * resets the host bridge, which in turn resets the Root Port also. + */ +static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, + struct pci_dev *pdev) +{ + struct device *dev = bridge->dev.parent; + struct qcom_pcie *pcie = dev_get_drvdata(dev); + struct dw_pcie *pci = pcie->pci; + struct dw_pcie_rp *pp = &pci->pp; + u32 val; + int ret; + + /* Wait for the pending transactions to be completed */ + ret = readl_relaxed_poll_timeout(pcie->parf + PARF_STATUS, val, + val & FLUSH_COMPLETED, 10, + FLUSH_TIMEOUT_US); + if (ret) { + dev_err(dev, "Flush completion failed: %d\n", ret); + return ret; + } + + /* Clear the FLUSH_MODE to allow the core to be reset */ + val = readl(pcie->parf + PARF_LTSSM); + val |= SW_CLEAR_FLUSH_MODE; + writel(val, pcie->parf + PARF_LTSSM); + + /* Wait for the FLUSH_MODE to clear */ + ret = readl_relaxed_poll_timeout(pcie->parf + PARF_LTSSM, val, + !(val & FLUSH_MODE), 10, + FLUSH_TIMEOUT_US); + if (ret) { + dev_err(dev, "Flush mode clear failed: %d\n", ret); + return ret; + } + + qcom_pcie_host_deinit(pp); + + ret = qcom_pcie_host_init(pp); + if (ret) { + dev_err(dev, "Host init failed\n"); + return ret; + } + + ret = dw_pcie_setup_rc(pp); + if (ret) + return ret; + + /* + * Re-enable global IRQ events as the PARF_INT_ALL_MASK register is + * non-sticky. + */ + if (pcie->global_irq) + writel_relaxed(PARF_INT_ALL_LINK_DOWN | PARF_INT_MSI_DEV_0_7, + pcie->parf + PARF_INT_ALL_MASK); + + qcom_pcie_start_link(pci); + + ret = dw_pcie_wait_for_link(pci); + if (ret) + return ret; + + dev_dbg(dev, "Root Port reset completed\n"); + + return 0; +} + static int qcom_pcie_link_transition_count(struct seq_file *s, void *data) { struct qcom_pcie *pcie = (struct qcom_pcie *)dev_get_drvdata(s->private); @@ -1771,6 +1860,27 @@ static void qcom_pcie_init_debugfs(struct qcom_pcie *pcie) qcom_pcie_link_transition_count); } +static irqreturn_t qcom_pcie_global_irq_thread(int irq, void *data) +{ + struct qcom_pcie *pcie = data; + struct dw_pcie_rp *pp = &pcie->pci->pp; + struct device *dev = pcie->pci->dev; + struct pci_dev *port; + unsigned long status = readl_relaxed(pcie->parf + PARF_INT_ALL_STATUS); + + writel_relaxed(status, pcie->parf + PARF_INT_ALL_CLEAR); + + if (test_and_clear_bit(INT_ALL_LINK_DOWN, &status)) { + dev_dbg(dev, "Received Link down event\n"); + for_each_pci_bridge(port, pp->bridge->bus) { + if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT) + pci_host_handle_link_down(port); + } + } + + return IRQ_HANDLED; +} + static void qcom_pci_free_msi(void *ptr) { struct dw_pcie_rp *pp = (struct dw_pcie_rp *)ptr; @@ -1990,7 +2100,7 @@ static int qcom_pcie_probe(struct platform_device *pdev) struct dw_pcie_rp *pp; struct resource *res; struct dw_pcie *pci; - int ret; + int ret, irq; pcie_cfg = of_device_get_match_data(dev); if (!pcie_cfg) { @@ -2135,6 +2245,32 @@ static int qcom_pcie_probe(struct platform_device *pdev) goto err_phy_exit; } + irq = platform_get_irq_byname_optional(pdev, "global"); + if (irq > 0) { + const char *name; + + name = devm_kasprintf(dev, GFP_KERNEL, "qcom_pcie_global_irq%d", + pci_domain_nr(pp->bridge->bus)); + if (!name) { + ret = -ENOMEM; + goto err_host_deinit; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + qcom_pcie_global_irq_thread, + IRQF_ONESHOT, name, pcie); + if (ret) { + dev_err_probe(&pdev->dev, ret, + "Failed to request Global IRQ\n"); + goto err_host_deinit; + } + + writel_relaxed(PARF_INT_ALL_LINK_DOWN | PARF_INT_MSI_DEV_0_7, + pcie->parf + PARF_INT_ALL_MASK); + + pcie->global_irq = irq; + } + qcom_pcie_icc_opp_update(pcie); if (pcie->mhi) @@ -2142,6 +2278,8 @@ static int qcom_pcie_probe(struct platform_device *pdev) return 0; +err_host_deinit: + dw_pcie_host_deinit(pp); err_phy_exit: list_for_each_entry_safe(port, tmp_port, &pcie->ports, list) { list_for_each_entry_safe(perst, tmp_perst, &port->perst, list) From b376b3ff9cb052709b539ddadbafc763d082f363 Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Mon, 27 Jul 2026 19:26:56 +0200 Subject: [PATCH 5/6] PCI: dw-rockchip: Implement .reset_root_port() and use for link down The PCIe link may go down in cases like firmware crashes or unstable connections. When this occurs, the Root Port must be reset to restore the functionality. However, the current driver lacks link down handling, forcing users to reboot the system to recover. Implement the .reset_root_port() callback for link down handling for the Rockchip DWC PCIe host controller. The RC is reset, reconfigured, and link training initiated to recover from the link down event. This also by extension fixes issues with sysfs-initiated bus resets. Currently, the endpoint device is non-functional after a sysfs initiated bus reset (it may link up with downgraded link status). With the link down handling support, a sysfs initiated bus reset works as intended. Testing conducted on a ROCK5B board with an M.2 NVMe drive. Signed-off-by: Wilfred Mallawa Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam [bhelgaas: subject] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260727172654.605988-4-cassel@kernel.org --- drivers/pci/controller/dwc/Kconfig | 1 + drivers/pci/controller/dwc/pcie-dw-rockchip.c | 134 +++++++++++++++++- 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index aa0b784c85b4..fe330987c3e0 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -374,6 +374,7 @@ config PCIE_ROCKCHIP_DW_HOST depends on OF select PCIE_DW_HOST select PCIE_ROCKCHIP_DW + select PCI_HOST_COMMON help Enables support for the DesignWare PCIe controller in the Rockchip SoC (except RK3399) to work in host mode. diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 731d93663cca..f9efe2d44957 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -26,6 +26,7 @@ #include #include "../../pci.h" +#include "../pci-host-common.h" #include "pcie-designware.h" /* @@ -122,6 +123,9 @@ struct rockchip_pcie_of_data { const struct pci_epc_features *epc_features; }; +static int rockchip_pcie_rc_reset_root_port(struct pci_host_bridge *bridge, + struct pci_dev *pdev); + static int rockchip_pcie_readl_apb(struct rockchip_pcie *rockchip, u32 reg) { return readl_relaxed(rockchip->apb_base + reg); @@ -436,6 +440,7 @@ static int rockchip_pcie_host_init(struct dw_pcie_rp *pp) rockchip_pcie_configure_l1ss(pci); rockchip_pcie_enable_l0s(pci); + pp->bridge->reset_root_port = rockchip_pcie_rc_reset_root_port; /* Disable Root Ports BAR0 and BAR1 as they report bogus size */ dw_pcie_writel_dbi2(pci, PCI_BASE_ADDRESS_0, 0x0); @@ -634,6 +639,32 @@ static const struct dw_pcie_ops dw_pcie_ops = { .get_ltssm = rockchip_pcie_get_ltssm, }; +static irqreturn_t rockchip_pcie_rc_sys_irq_thread(int irq, void *arg) +{ + struct rockchip_pcie *rockchip = arg; + struct dw_pcie *pci = &rockchip->pci; + struct dw_pcie_rp *pp = &pci->pp; + struct device *dev = pci->dev; + struct pci_dev *port; + u32 reg; + + reg = rockchip_pcie_readl_apb(rockchip, PCIE_CLIENT_INTR_STATUS_MISC); + rockchip_pcie_writel_apb(rockchip, reg, PCIE_CLIENT_INTR_STATUS_MISC); + + dev_dbg(dev, "PCIE_CLIENT_INTR_STATUS_MISC: %#x\n", reg); + dev_dbg(dev, "LTSSM_STATUS: %#x\n", rockchip_pcie_get_ltssm_reg(rockchip)); + + if (reg & PCIE_LINK_REQ_RST_NOT_INT) { + dev_dbg(dev, "hot reset or link-down reset\n"); + for_each_pci_bridge(port, pp->bridge->bus) { + if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT) + pci_host_handle_link_down(port); + } + } + + return IRQ_HANDLED; +} + static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg) { struct rockchip_pcie *rockchip = arg; @@ -666,14 +697,29 @@ static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg) return IRQ_HANDLED; } -static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip) +static int rockchip_pcie_configure_rc(struct platform_device *pdev, + struct rockchip_pcie *rockchip) { + struct device *dev = &pdev->dev; struct dw_pcie_rp *pp; + int irq, ret; u32 val; if (!IS_ENABLED(CONFIG_PCIE_ROCKCHIP_DW_HOST)) return -ENODEV; + irq = platform_get_irq_byname(pdev, "sys"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, NULL, + rockchip_pcie_rc_sys_irq_thread, + IRQF_ONESHOT, "pcie-sys-rc", rockchip); + if (ret) { + dev_err(dev, "failed to request PCIe sys IRQ\n"); + return ret; + } + /* LTSSM enable control mode */ val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1); rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); @@ -685,7 +731,17 @@ static int rockchip_pcie_configure_rc(struct rockchip_pcie *rockchip) pp = &rockchip->pci.pp; pp->ops = &rockchip_pcie_host_ops; - return dw_pcie_host_init(pp); + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + return ret; + } + + /* unmask hot reset/link-down reset */ + val = FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); + + return ret; } static int rockchip_pcie_configure_ep(struct platform_device *pdev, @@ -804,7 +860,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev) switch (data->mode) { case DW_PCIE_RC_TYPE: - ret = rockchip_pcie_configure_rc(rockchip); + ret = rockchip_pcie_configure_rc(pdev, rockchip); if (ret) goto deinit_clk; break; @@ -829,6 +885,78 @@ static int rockchip_pcie_probe(struct platform_device *pdev) return ret; } +static int rockchip_pcie_rc_reset_root_port(struct pci_host_bridge *bridge, + struct pci_dev *pdev) +{ + struct pci_bus *bus = bridge->bus; + struct dw_pcie_rp *pp = bus->sysdata; + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct rockchip_pcie *rockchip = to_rockchip_pcie(pci); + struct device *dev = rockchip->pci.dev; + u32 val; + int ret; + + dw_pcie_stop_link(pci); + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); + rockchip_pcie_phy_deinit(rockchip); + + ret = reset_control_assert(rockchip->rst); + if (ret) + return ret; + + ret = rockchip_pcie_phy_init(rockchip); + if (ret) + return ret; + + ret = reset_control_deassert(rockchip->rst); + if (ret) + goto deinit_phy; + + ret = rockchip_pcie_clk_init(rockchip); + if (ret) + goto deinit_phy; + + ret = pp->ops->init(pp); + if (ret) { + dev_err(dev, "Host init failed: %d\n", ret); + goto deinit_clk; + } + + /* LTSSM enable control mode */ + val = FIELD_PREP_WM16(PCIE_LTSSM_ENABLE_ENHANCE, 1); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_HOT_RESET_CTRL); + + rockchip_pcie_writel_apb(rockchip, + PCIE_CLIENT_SET_MODE(PCIE_CLIENT_MODE_RC), + PCIE_CLIENT_GENERAL_CON); + + ret = dw_pcie_setup_rc(pp); + if (ret) { + dev_err(dev, "Failed to setup RC: %d\n", ret); + goto deinit_clk; + } + + /* unmask hot reset/link-down reset */ + val = FIELD_PREP_WM16(PCIE_LINK_REQ_RST_NOT_INT, 0); + rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); + + ret = dw_pcie_start_link(pci); + if (ret) + goto deinit_clk; + + /* Ignore errors, the link may come up later */ + dw_pcie_wait_for_link(pci); + dev_dbg(dev, "Root Port reset completed\n"); + return ret; + +deinit_clk: + clk_bulk_disable_unprepare(rockchip->clk_cnt, rockchip->clks); +deinit_phy: + rockchip_pcie_phy_deinit(rockchip); + + return ret; +} + static const struct rockchip_pcie_of_data rockchip_pcie_rc_of_data_rk3568 = { .mode = DW_PCIE_RC_TYPE, }; From 29e179cb7f636dca79b047db3dd7f0e588fdba37 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Wed, 29 Jul 2026 10:22:31 +0530 Subject: [PATCH 6/6] misc: pci_endpoint_test: Add AER error handlers The Endpoint test driver doesn't need to do anything fancy in its error handlers; just restore the config space that was saved during probe and report the correct result. This helps in making sure that the AER recovery succeeds. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260729-pci-port-reset-v9-5-53570b92064d@oss.qualcomm.com --- drivers/misc/pci_endpoint_test.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index 3635741c3e7a..b7a149715700 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -1325,6 +1325,8 @@ static int pci_endpoint_test_probe(struct pci_dev *pdev, misc_device->parent = &pdev->dev; misc_device->fops = &pci_endpoint_test_fops; + pci_save_state(pdev); + ret = misc_register(misc_device); if (ret) { dev_err(dev, "Failed to register device\n"); @@ -1452,12 +1454,33 @@ static const struct pci_device_id pci_endpoint_test_tbl[] = { }; MODULE_DEVICE_TABLE(pci, pci_endpoint_test_tbl); +static pci_ers_result_t pci_endpoint_test_error_detected(struct pci_dev *pdev, + pci_channel_state_t state) +{ + if (state == pci_channel_io_perm_failure) + return PCI_ERS_RESULT_DISCONNECT; + + return PCI_ERS_RESULT_NEED_RESET; +} + +static pci_ers_result_t pci_endpoint_test_slot_reset(struct pci_dev *pdev) +{ + pci_restore_state(pdev); + return PCI_ERS_RESULT_RECOVERED; +} + +static const struct pci_error_handlers pci_endpoint_test_err_handler = { + .error_detected = pci_endpoint_test_error_detected, + .slot_reset = pci_endpoint_test_slot_reset, +}; + static struct pci_driver pci_endpoint_test_driver = { .name = DRV_MODULE_NAME, .id_table = pci_endpoint_test_tbl, .probe = pci_endpoint_test_probe, .remove = pci_endpoint_test_remove, .sriov_configure = pci_sriov_configure_simple, + .err_handler = &pci_endpoint_test_err_handler, }; module_pci_driver(pci_endpoint_test_driver);