From 1b3b0bc2b3672e928e06997444528f8797579eb2 Mon Sep 17 00:00:00 2001 From: Christopher Obbard Date: Sat, 29 Aug 2026 11:36:00 +0100 Subject: [PATCH 1/7] PCI: qcom: Only check bridge nodes for PERST# GPIOs qcom_pcie_parse_perst() walks the PCIe hierarchy described in the device tree below its Root Port and collects the PERST# GPIOs from each bridge node so all PERST# lines can be driven in unison during controller bring-up, reset and power sequencing. However, the recursive device tree walk currently visits every available child node without checking that it is a PCI bridge. This allows the walk to leave the PCI hierarchy and collect reset lines from children of PCI Endpoint nodes, where those reset lines belong to other drivers. This is reproducible on the Qualcomm RB3Gen2, where a Renesas uPD720201 USB host controller sits behind a PCIe switch downstream port. A Genesys GL3590 USB hub connected to that controller is described as a child of the PCI endpoint: pcie@2,0 { device_type = "pci"; usb-controller@0,0 { compatible = "pci1912,0014"; hub@1 { compatible = "usb5e3,610"; reset-gpios = <&tlmm 162 GPIO_ACTIVE_HIGH>; }; }; }; The USB controller is a PCI Endpoint, and not a bridge, but the PERST# walk descends through it and incorrectly claims the hub's reset GPIO: $ gpioinfo line 162: unnamed output consumer=PERST# As a result, the onboard-usb-dev driver cannot acquire its reset GPIO during probe: onboard-usb-dev 1c08000.pcie:...:usb-controller@0,0:hub@1: \ error -EBUSY: failed to get reset GPIO onboard-usb-dev 1c08000.pcie:...:usb-controller@0,0:hub@1: \ probe with driver onboard-usb-dev failed with error -16 The GPIO is also added to the root port's PERST# list and is asserted and deasserted as part of PCIe reset sequencing. On the RB3Gen2 this causes the USB hub to repeatedly re-enumerate: [ 116.479598] hub 2-1:1.0: USB hub found [ 116.488601] hub 2-1:1.0: USB hub found [ 116.496971] hub 2-1:1.0: USB hub found ... This disrupts the hub and devices connected to it. Restrict the recursive walk to children with device_type "pci", so PERST# GPIOs are collected only from PCI bridge nodes and the walk does not descend through endpoints. qocm_pcie_parse_ports() has made the same distinction since commit 45df22935bdc ("PCI: qcom: Restrict port parsing only to PCIe bridge child nodes"), but the PERST# walk added later did not carry over that check. This bug is distinct from commit 3edb3a038d42 ("PCI: qcom: Skip PERST# GPIOs provided by downstream PCIe devices"), which filters PERST# GPIOs based on their provider. Here the GPIO is provided by the SoC TLMM; the bug is that reset-gpios is consumed from a non-bridge node at all. Fixes: 2fd60a2edb83 ("PCI: qcom: Parse PERST# from all PCIe bridge nodes") Signed-off-by: Christopher Obbard Signed-off-by: Manivannan Sadhasivam Reviewed-by: Krishna Chaitanya Chundru Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260829-wip-obbardc-drivers-pcie-qcom-rb3gen2-usb-fix-v1-1-337821131a99@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index b58a607b713f..89a476c37098 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1953,7 +1953,7 @@ static bool qcom_pcie_is_child_node(struct device *dev, return false; } -/* Parse PERST# from all nodes in depth first manner starting from @np */ +/* Collect PERST# GPIOs from PCI bridge nodes depth-first, starting at @np */ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, struct qcom_pcie_port *port, struct device_node *np) @@ -2019,6 +2019,9 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, parse_child_node: for_each_available_child_of_node_scoped(np, child) { + if (!of_node_is_type(child, "pci")) + continue; + ret = qcom_pcie_parse_perst(pcie, port, child); if (ret) return ret; From 967a73293ae5f57af68e815942078e110c1f6d08 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 19 Aug 2026 23:36:54 -0700 Subject: [PATCH 2/7] PCI: qcom: Block accesses to downstream devices on link down After a PCIe link goes down, software may still access the BAR (MMIO) space or configuration space of devices behind that link before recovery has run. As the link is down, these accesses never complete, resulting in a storm of Completion Timeout AERs. Use the controller's Outbound blocker to drop these accesses to the PCIe address space as soon as the link-down interrupt fires, so that Completion Timeout AERs are reduced. The blocked range covers the entire address space (base 0x0, all-ones limit), since the Root Port's own DBI/iATU register space remains accessible regardless. The range is programmed once in the host init path, since the range registers are wiped by BCR reset. This leaves only the OUTBOUND_BLOCKER_EN bit to be flipped from the link-down IRQ handler, so the blocker can be armed with a single fast register write for immediate effect. The subsequent Root Port reset re-initialises the controller, which clears the enable bit and re-programs the range for the fresh link. Some IP revisions implement the Outbound blocker registers but do not wire up the "global" interrupt used to deliver the link-down event that arms the blocker, so there is no way to enable the blocker on those platforms. Restrict blocker initialization to the post_init hooks of the IP revisions that do have a global IRQ wired up (qcom_pcie_post_init_2_3_3(), qcom_pcie_post_init_2_7_0() and qcom_pcie_post_init_2_9_0()), instead of unconditionally programming it from the common host_init path. The link-down IRQ thread's blocker-enable write to PARF_SYS_CTRL can race with a Root Port reset triggered independently through AER, which reprograms PARF_SYS_CTRL as part of reinitializing the controller. Serialize these with a per-controller mutex. Signed-off-by: Qiang Yu [mani: renamed hw_lock to reset_lock, ECAM blocker to Outboound blocker, used guard(mutex)] Signed-off-by: Manivannan Sadhasivam Tested-by: Konrad Dybcio # X1E80100 CRD + ASUS PA27AC Link: https://patch.msgid.link/20260819-ecam_blocker-v2-1-e7a8fdc1c5cb@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 68 ++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 89a476c37098..547c2058140f 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -73,6 +74,23 @@ #define PARF_BDF_TO_SID_TABLE_N 0x2000 #define PARF_BDF_TO_SID_CFG 0x2c00 +/* + * Outbound blocker range registers. The blocked range has a write pair + * (WR_BASE/WR_LIMIT) and a read pair (RD_BASE/RD_LIMIT); each address is + * split into a low (32-bit) and a HI (upper 32-bit) register. + */ +#define PARF_BLOCK_SLV_AXI_WR_BASE 0x360 +#define PARF_BLOCK_SLV_AXI_WR_BASE_HI 0x364 +#define PARF_BLOCK_SLV_AXI_WR_LIMIT 0x368 +#define PARF_BLOCK_SLV_AXI_WR_LIMIT_HI 0x36c +#define PARF_BLOCK_SLV_AXI_RD_BASE 0x370 +#define PARF_BLOCK_SLV_AXI_RD_BASE_HI 0x374 +#define PARF_BLOCK_SLV_AXI_RD_LIMIT 0x378 +#define PARF_BLOCK_SLV_AXI_RD_LIMIT_HI 0x37c + +#define PARF_ECAM_BASE 0x380 +#define PARF_ECAM_BASE_HI 0x384 + /* ELBI registers */ #define ELBI_SYS_CTRL 0x04 #define ELBI_SYS_STTS 0x08 @@ -90,6 +108,7 @@ /* PARF_SYS_CTRL register fields */ #define MAC_PHY_POWERDOWN_IN_P2_D_MUX_EN BIT(29) +#define OUTBOUND_BLOCKER_EN BIT(26) #define MST_WAKEUP_EN BIT(13) #define SLV_WAKEUP_EN BIT(12) #define MSTR_ACLK_CGC_DIS BIT(10) @@ -308,6 +327,7 @@ struct qcom_pcie { struct gpio_desc *reset; int global_irq; bool use_pm_opp; + struct mutex reset_lock; }; #define to_qcom_pcie(x) dev_get_drvdata((x)->dev) @@ -445,6 +465,25 @@ static void qcom_pcie_configure_dbi_atu_base(struct qcom_pcie *pcie) } } +static void qcom_pcie_init_outbound_blocker(struct qcom_pcie *pcie) +{ + struct dw_pcie *pci = pcie->pci; + + /* ECAM base must match the DBI base address */ + writel(lower_32_bits(pci->dbi_phys_addr), pcie->parf + PARF_ECAM_BASE); + writel(upper_32_bits(pci->dbi_phys_addr), pcie->parf + PARF_ECAM_BASE_HI); + + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_WR_BASE); + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_WR_BASE_HI); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_WR_LIMIT); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_WR_LIMIT_HI); + + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_RD_BASE); + writel(0, pcie->parf + PARF_BLOCK_SLV_AXI_RD_BASE_HI); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_RD_LIMIT); + writel(U32_MAX, pcie->parf + PARF_BLOCK_SLV_AXI_RD_LIMIT_HI); +} + static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie) { struct dw_pcie *pci = pcie->pci; @@ -990,6 +1029,8 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie) dw_pcie_dbi_ro_wr_dis(pci); + qcom_pcie_init_outbound_blocker(pcie); + return 0; } @@ -1104,6 +1145,8 @@ static int qcom_pcie_post_init_2_7_0(struct qcom_pcie *pcie) qcom_pcie_set_slot_cap(pcie->pci); + qcom_pcie_init_outbound_blocker(pcie); + return 0; } @@ -1322,6 +1365,8 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) for (i = 0; i < 256; i++) writel(0, pcie->parf + PARF_BDF_TO_SID_TABLE_N + (4 * i)); + qcom_pcie_init_outbound_blocker(pcie); + return 0; } @@ -1382,6 +1427,18 @@ static void qcom_pcie_configure_ports(struct qcom_pcie *pcie) dw_pcie_program_t_power_on(pcie->pci, port->l1ss_t_power_on); } +static void qcom_pcie_enable_outbound_blocker(struct qcom_pcie *pcie) +{ + u32 sys_ctrl; + + sys_ctrl = readl(pcie->parf + PARF_SYS_CTRL); + sys_ctrl |= OUTBOUND_BLOCKER_EN; + writel(sys_ctrl, pcie->parf + PARF_SYS_CTRL); + + /* Flush the write so the blocker is enabled before this function returns */ + readl(pcie->parf + PARF_SYS_CTRL); +} + static int qcom_pcie_host_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); @@ -1775,6 +1832,8 @@ static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, u32 val; int ret; + guard(mutex)(&pcie->reset_lock); + /* Wait for the pending transactions to be completed */ ret = readl_relaxed_poll_timeout(pcie->parf + PARF_STATUS, val, val & FLUSH_COMPLETED, 10, @@ -1878,6 +1937,11 @@ static irqreturn_t qcom_pcie_global_irq_thread(int irq, void *data) if (test_and_clear_bit(INT_ALL_LINK_DOWN, &status)) { dev_dbg(dev, "Received Link down event\n"); + + mutex_lock(&pcie->reset_lock); + qcom_pcie_enable_outbound_blocker(pcie); + mutex_unlock(&pcie->reset_lock); + for_each_pci_bridge(port, pp->bridge->bus) { if (pci_pcie_type(port) == PCI_EXP_TYPE_ROOT_PORT) pci_host_handle_link_down(port); @@ -2207,6 +2271,10 @@ static int qcom_pcie_probe(struct platform_device *pdev) INIT_LIST_HEAD(&pcie->ports); + ret = devm_mutex_init(dev, &pcie->reset_lock); + if (ret) + goto err_pm_runtime_put; + pci->dev = dev; pci->ops = &dw_pcie_ops; pp = &pci->pp; From 5af84e8928cb2ccc9696d90a0096a43c5bab5913 Mon Sep 17 00:00:00 2001 From: Mrinmay Sarkar Date: Mon, 31 Aug 2026 19:07:02 +0530 Subject: [PATCH 3/7] PCI: qcom: Skip system suspend/resume for firmware-managed PCIe For firmware-managed PCIe controllers, all resources, including clocks, regulators, resets, PHYs and link state, are owned and managed by firmware. The host driver must not attempt to manage these resources during system suspend or resume. Firmware will properly transition the devices/controller into relevant low power modes. Skip the suspend and resume operations for firmware-managed controllers. The associated power domain transitions are handled independently by the genpd framework through its own system suspend/resume callbacks, so no explicit action is required by the PCIe controller driver. Signed-off-by: Mrinmay Sarkar Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260831-skip_suspend-v1-1-ba2d877e22b1@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 547c2058140f..7e38c41a33ce 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -2412,6 +2412,10 @@ static int qcom_pcie_suspend_noirq(struct device *dev) { struct qcom_pcie *pcie; int ret = 0; + const struct qcom_pcie_cfg *pcie_cfg = of_device_get_match_data(dev); + + if (pcie_cfg && pcie_cfg->firmware_managed) + return 0; pcie = dev_get_drvdata(dev); if (!pcie) @@ -2470,6 +2474,10 @@ static int qcom_pcie_resume_noirq(struct device *dev) { struct qcom_pcie *pcie; int ret; + const struct qcom_pcie_cfg *pcie_cfg = of_device_get_match_data(dev); + + if (pcie_cfg && pcie_cfg->firmware_managed) + return 0; pcie = dev_get_drvdata(dev); if (!pcie) From 5121d76cb9dde74662962cd5678a64abeaf3b3e4 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Mon, 7 Sep 2026 16:33:49 +0200 Subject: [PATCH 4/7] PCI: qcom: Honor IOMMU provider's #iommu-cells in qcom_pcie_config_sid_1_9_0() qcom_pcie_config_sid_1_9_0() reads the "iommu-map" property as an array of fixed four-word {rid-base, phandle, sid, rid-len} entries to program the BDF to SID translation table. But that layout only holds for an IOMMU with '#iommu-cells = <1>'. The PCIe SMMUs on these SoCs use '#iommu-cells = <2>' (SID and mask), so per the pci-iommu binding each entry is really five cells long. This used to work only because the DTs were themselves broken. They described iommu-map with four-cell entries that omitted the SID mask, which of_map_id() tolerated via its of_check_bad_map() fallback, and the four-word parsing coincidentally matched that malformed shape. Since commit ccb2fd725d41 ("of: Respect #{iommu,msi}-cells in maps") the OF core parses such maps correctly, so the device trees were converted to proper five-cell entries, e.g. commit c41749e9554d ("arm64: dts: qcom: sm8250: Fix the PCIe iommu-map entries"). With five-cell entries the fixed four-word stride slips by one cell for each entry after the first, so qcom_pcie_config_sid_1_9_0() reads the endpoint mapping's RID from the preceding entry's length cell and its SID from the phandle cell. As the RID is the hash key, the endpoint's real RID is never programmed into the BDF to SID table. Its DMA then hashes to an unprogrammed slot, gets tagged with SID 0 and the SMMU faults like below on QCS8300: arm-smmu 15200000.iommu: Unhandled context fault: fsr=0x402, iova=0xffa00000, cbfrsynra=0x0, cb=1 To fix this, walk the map with a stride of 3 + '#iommu-cells' of the referenced IOMMU and take the SID from the first specifier cell, which is all the BDF to SID table needs. Validate the layout instead of trusting the array size. Also, preserve the legacy behavior of the old DTs by detecting the same pattern that of_check_bad_map() recognizes and falling back to a stride of four. Fixes: 4c9398822106 ("PCI: qcom: Add support for configuring BDF to SID mapping for SM8250") Reported-by: Loic Poulain Signed-off-by: Manivannan Sadhasivam Tested-by: Loic Poulain Tested-by: Neil Armstrong # on SM8650-HDK Link: https://patch.msgid.link/20260907143349.317495-1-mani@kernel.org --- drivers/pci/controller/dwc/pcie-qcom.c | 90 ++++++++++++++++++-------- 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 7e38c41a33ce..686789ec7ddc 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1186,50 +1186,90 @@ static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie) static int qcom_pcie_config_sid_1_9_0(struct qcom_pcie *pcie) { - /* iommu map structure */ - struct { - u32 bdf; - u32 phandle; - u32 smmu_sid; - u32 smmu_sid_len; - } *map; void __iomem *bdf_to_sid_base = pcie->parf + PARF_BDF_TO_SID_TABLE_N; struct device *dev = pcie->pci->dev; + struct device_node *iommu_np; u8 qcom_pcie_crc8_table[CRC8_TABLE_SIZE]; - int i, nr_map, size = 0; - u32 smmu_sid_base; + const __be32 *map; + u32 iommu_cells, entry_cells, phandle, smmu_sid_base; + int i, nr_cells, nr_map, size = 0; u32 val; - of_get_property(dev->of_node, "iommu-map", &size); - if (!size) + map = of_get_property(dev->of_node, "iommu-map", &size); + if (!map || !size) return 0; + if (size % sizeof(*map)) { + dev_err(dev, "Malformed iommu-map property\n"); + return -EINVAL; + } + nr_cells = size / sizeof(*map); + + /* + * Each iommu-map entry is: rid-base (1 cell), phandle (1 cell), + * IOMMU specifier (#iommu-cells cells), length (1 cell). Read + * #iommu-cells from the IOMMU provider referenced by the first + * entry to compute the per-entry stride. + */ + phandle = be32_to_cpu(map[1]); + iommu_np = of_find_node_by_phandle(phandle); + if (!iommu_np) { + dev_err(dev, "Failed to find IOMMU node in iommu-map\n"); + return -ENODEV; + } + + if (of_property_read_u32(iommu_np, "#iommu-cells", &iommu_cells)) + iommu_cells = 1; + of_node_put(iommu_np); + + entry_cells = 3 + iommu_cells; + + /* + * Retain backward compatibility with DTs that describe iommu-map + * with 4-cell entries against an IOMMU declaring #iommu-cells = 2, + * matching the fallback in drivers/of/base.c::of_check_bad_map(). + */ + if (iommu_cells == 2 && !(nr_cells % 4)) { + bool legacy = true; + + for (i = 0; i < nr_cells; i += 4) { + if (be32_to_cpu(map[i + 1]) != phandle || + be32_to_cpu(map[i + 3]) != 1) { + legacy = false; + break; + } + } + + if (legacy) { + dev_warn_once(dev, "iommu-map has 1-cell entries with #iommu-cells=2, using 1-cell\n"); + entry_cells = 4; + } + } + + if (nr_cells % entry_cells) { + dev_err(dev, "Malformed iommu-map property\n"); + return -EINVAL; + } + nr_map = nr_cells / entry_cells; + /* Enable BDF to SID translation by disabling bypass mode (default) */ val = readl(pcie->parf + PARF_BDF_TO_SID_CFG); val &= ~BDF_TO_SID_BYPASS; writel(val, pcie->parf + PARF_BDF_TO_SID_CFG); - map = kzalloc(size, GFP_KERNEL); - if (!map) - return -ENOMEM; - - of_property_read_u32_array(dev->of_node, "iommu-map", (u32 *)map, - size / sizeof(u32)); - - nr_map = size / (sizeof(*map)); - crc8_populate_msb(qcom_pcie_crc8_table, QCOM_PCIE_CRC8_POLYNOMIAL); /* Registers need to be zero out first */ memset_io(bdf_to_sid_base, 0, CRC8_TABLE_SIZE * sizeof(u32)); /* Extract the SMMU SID base from the first entry of iommu-map */ - smmu_sid_base = map[0].smmu_sid; + smmu_sid_base = be32_to_cpu(map[2]); /* Look for an available entry to hold the mapping */ for (i = 0; i < nr_map; i++) { - __be16 bdf_be = cpu_to_be16(map[i].bdf); - u32 val; + u32 bdf = be32_to_cpu(map[i * entry_cells]); + u32 sid = be32_to_cpu(map[i * entry_cells + 2]); + __be16 bdf_be = cpu_to_be16(bdf); u8 hash; hash = crc8(qcom_pcie_crc8_table, (u8 *)&bdf_be, sizeof(bdf_be), 0); @@ -1251,12 +1291,10 @@ static int qcom_pcie_config_sid_1_9_0(struct qcom_pcie *pcie) } /* BDF [31:16] | SID [15:8] | NEXT [7:0] */ - val = map[i].bdf << 16 | (map[i].smmu_sid - smmu_sid_base) << 8 | 0; + val = bdf << 16 | (sid - smmu_sid_base) << 8 | 0; writel(val, bdf_to_sid_base + hash * sizeof(u32)); } - kfree(map); - return 0; } From ed1d71e41c7b2e755e0ef8e9af9cab9b6b185c4b Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Wed, 16 Sep 2026 17:01:26 +0800 Subject: [PATCH 5/7] PCI: qcom-ep: Deinitialize endpoint on remove qcom_pcie_ep_probe() initializes the DesignWare PCIe endpoint with dw_pcie_ep_init(). The probe failure paths call dw_pcie_ep_deinit() to release the corresponding endpoint resources, but the remove path does not perform the matching teardown after a successful probe. As a result, resources initialized by the DesignWare endpoint core can remain allocated when the endpoint controller is removed. Notify endpoint functions about EPC deinitialization and call dw_pcie_ep_deinit() before disabling the controller resources. Keep the existing early return when the link is already disabled since Qualcomm endpoint controllers depend on the host refclk for hardware cleanup. This issue was found by manual code inspection. Fixes: f55fee56a631 ("PCI: qcom-ep: Add Qualcomm PCIe Endpoint controller driver") Signed-off-by: Guangshuo Li Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260916090126.2963064-1-lgs201920130244@gmail.com --- drivers/pci/controller/dwc/pcie-qcom-ep.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom-ep.c b/drivers/pci/controller/dwc/pcie-qcom-ep.c index 56184e6ca6e6..28949386b0e8 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-ep.c +++ b/drivers/pci/controller/dwc/pcie-qcom-ep.c @@ -944,6 +944,8 @@ static void qcom_pcie_ep_remove(struct platform_device *pdev) if (pcie_ep->link_status == QCOM_PCIE_EP_LINK_DISABLED) return; + pci_epc_deinit_notify(pcie_ep->pci.ep.epc); + dw_pcie_ep_deinit(&pcie_ep->pci.ep); qcom_pcie_disable_resources(pcie_ep); } From bea2ff3b9de1e32716bc517082b7e3c7330e2151 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Wed, 16 Sep 2026 14:37:49 +0200 Subject: [PATCH 6/7] PCI: qcom: Ignore -ENODEV after Root Port reset Root Port recovery may race with a device hot-unplug. In that case, dw_pcie_wait_for_link() returns -ENODEV because no device is detected after the controller has been successfully reset. This is a valid outcome and should not cause Root Port recovery to fail. Ignore -ENODEV while continuing to propagate other link-training errors. Fixes: 4d88cb82a6d9 ("PCI: qcom: Implement .reset_root_port() and use for link down") Signed-off-by: Konrad Dybcio Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Krishna Chaitanya Chundru Link: https://patch.msgid.link/20260916-topic-pcie_portrec_enodev-v1-1-5dc7bde39552@oss.qualcomm.com Assisted-by: LLM --- drivers/pci/controller/dwc/pcie-qcom.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 686789ec7ddc..18c96ed96f56 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1918,6 +1918,9 @@ static int qcom_pcie_reset_root_port(struct pci_host_bridge *bridge, qcom_pcie_start_link(pci); ret = dw_pcie_wait_for_link(pci); + /* This linkdown might have been a hot-unplug */ + if (ret == -ENODEV) + ret = 0; if (ret) return ret; From a67716078b5a172e8c9f43bcc66cec8bafc40637 Mon Sep 17 00:00:00 2001 From: Ayiluri Naga Rashmi Date: Thu, 17 Sep 2026 16:43:28 +0530 Subject: [PATCH 7/7] PCI: qcom: Skip DWC MSI host initialization when DT 'msi-map' is present qcom_pcie_ecam_host_init() unconditionally initializes the DesignWare internal MSI controller and sets use_imsi_rx, irrespective of whether an external MSI controller is described for the host bridge. When an 'msi-map' DT property is present, MSI interrupts are handled by the mapped external MSI controller. Initializing the internal iMSI-RX domain in this case is unnecessary. Set use_imsi_rx based on the presence of 'msi-map' in DT and initialize the DesignWare MSI host only when iMSI-RX is required. Signed-off-by: Ayiluri Naga Rashmi Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260917-skipmsi_forlpi-v1-1-14a4932a4311@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 18c96ed96f56..5cc0925b948d 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -2022,11 +2022,13 @@ static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg) */ raw_spin_lock_init(&pp->lock); - ret = dw_pcie_msi_host_init(pp); - if (ret) - return ret; + pp->use_imsi_rx = !pci_host_of_has_msi_map(dev); + if (pp->use_imsi_rx) { + ret = dw_pcie_msi_host_init(pp); + if (ret) + return ret; + } - pp->use_imsi_rx = true; dw_pcie_msi_init(pp); return devm_add_action_or_reset(dev, qcom_pci_free_msi, pp);