From a2582e05e39adf9ab82a02561cd6f70738540ae0 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sun, 9 Nov 2025 22:59:40 -0800 Subject: [PATCH 01/41] PCI: Add preceding capability position support in PCI_FIND_NEXT_*_CAP macros Add support for finding the preceding capability position in PCI capability list by extending the capability finding macros with an additional parameter. This functionality is essential for modifying PCI capability list, as it provides the necessary information to update the "next" pointer of the predecessor capability when removing entries. Modify two macros to accept a new 'prev_ptr' parameter: - PCI_FIND_NEXT_CAP - Now accepts 'prev_ptr' parameter for standard capabilities - PCI_FIND_NEXT_EXT_CAP - Now accepts 'prev_ptr' parameter for extended capabilities When a capability is found, these macros: - Store the position of the preceding capability in *prev_ptr (if prev_ptr != NULL) - Maintain all existing functionality when prev_ptr is NULL Update current callers to accommodate this API change by passing NULL to 'prev_ptr' argument if they do not care about the preceding capability position. No functional changes to driver behavior result from this commit as it maintains the existing capability finding functionality while adding the infrastructure for future capability removal operations. Signed-off-by: Qiang Yu Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251109-remove_cap-v1-1-2208f46f4dc2@oss.qualcomm.com --- drivers/pci/controller/cadence/pcie-cadence.c | 4 ++-- .../pci/controller/dwc/pcie-designware-ep.c | 2 +- drivers/pci/controller/dwc/pcie-designware.c | 6 ++--- drivers/pci/pci.c | 8 +++---- drivers/pci/pci.h | 23 +++++++++++++++---- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/drivers/pci/controller/cadence/pcie-cadence.c b/drivers/pci/controller/cadence/pcie-cadence.c index e6f1a4ac0fb7..a1eada56edba 100644 --- a/drivers/pci/controller/cadence/pcie-cadence.c +++ b/drivers/pci/controller/cadence/pcie-cadence.c @@ -13,13 +13,13 @@ u8 cdns_pcie_find_capability(struct cdns_pcie *pcie, u8 cap) { return PCI_FIND_NEXT_CAP(cdns_pcie_read_cfg, PCI_CAPABILITY_LIST, - cap, pcie); + cap, NULL, pcie); } EXPORT_SYMBOL_GPL(cdns_pcie_find_capability); u16 cdns_pcie_find_ext_capability(struct cdns_pcie *pcie, u8 cap) { - return PCI_FIND_NEXT_EXT_CAP(cdns_pcie_read_cfg, 0, cap, pcie); + return PCI_FIND_NEXT_EXT_CAP(cdns_pcie_read_cfg, 0, cap, NULL, pcie); } EXPORT_SYMBOL_GPL(cdns_pcie_find_ext_capability); diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 19571ac2b961..f6c54625486e 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -72,7 +72,7 @@ EXPORT_SYMBOL_GPL(dw_pcie_ep_reset_bar); static u8 dw_pcie_ep_find_capability(struct dw_pcie_ep *ep, u8 func_no, u8 cap) { return PCI_FIND_NEXT_CAP(dw_pcie_ep_read_cfg, PCI_CAPABILITY_LIST, - cap, ep, func_no); + cap, NULL, ep, func_no); } /** diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 75fc8b767fcc..5d7a7e6f5724 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -226,13 +226,13 @@ void dw_pcie_version_detect(struct dw_pcie *pci) u8 dw_pcie_find_capability(struct dw_pcie *pci, u8 cap) { return PCI_FIND_NEXT_CAP(dw_pcie_read_cfg, PCI_CAPABILITY_LIST, cap, - pci); + NULL, pci); } EXPORT_SYMBOL_GPL(dw_pcie_find_capability); u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap) { - return PCI_FIND_NEXT_EXT_CAP(dw_pcie_read_cfg, 0, cap, pci); + return PCI_FIND_NEXT_EXT_CAP(dw_pcie_read_cfg, 0, cap, NULL, pci); } EXPORT_SYMBOL_GPL(dw_pcie_find_ext_capability); @@ -246,7 +246,7 @@ static u16 __dw_pcie_find_vsec_capability(struct dw_pcie *pci, u16 vendor_id, return 0; while ((vsec = PCI_FIND_NEXT_EXT_CAP(dw_pcie_read_cfg, vsec, - PCI_EXT_CAP_ID_VNDR, pci))) { + PCI_EXT_CAP_ID_VNDR, NULL, pci))) { header = dw_pcie_readl_dbi(pci, vsec + PCI_VNDR_HEADER); if (PCI_VNDR_HEADER_ID(header) == vsec_id) return vsec; diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 13dbb405dc31..b1142fbbd6e5 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -426,7 +426,7 @@ found: static u8 __pci_find_next_cap(struct pci_bus *bus, unsigned int devfn, u8 pos, int cap) { - return PCI_FIND_NEXT_CAP(pci_bus_read_config, pos, cap, bus, devfn); + return PCI_FIND_NEXT_CAP(pci_bus_read_config, pos, cap, NULL, bus, devfn); } u8 pci_find_next_capability(struct pci_dev *dev, u8 pos, int cap) @@ -531,7 +531,7 @@ u16 pci_find_next_ext_capability(struct pci_dev *dev, u16 start, int cap) return 0; return PCI_FIND_NEXT_EXT_CAP(pci_bus_read_config, start, cap, - dev->bus, dev->devfn); + NULL, dev->bus, dev->devfn); } EXPORT_SYMBOL_GPL(pci_find_next_ext_capability); @@ -600,7 +600,7 @@ static u8 __pci_find_next_ht_cap(struct pci_dev *dev, u8 pos, int ht_cap) mask = HT_5BIT_CAP_MASK; pos = PCI_FIND_NEXT_CAP(pci_bus_read_config, pos, - PCI_CAP_ID_HT, dev->bus, dev->devfn); + PCI_CAP_ID_HT, NULL, dev->bus, dev->devfn); while (pos) { rc = pci_read_config_byte(dev, pos + 3, &cap); if (rc != PCIBIOS_SUCCESSFUL) @@ -611,7 +611,7 @@ static u8 __pci_find_next_ht_cap(struct pci_dev *dev, u8 pos, int ht_cap) pos = PCI_FIND_NEXT_CAP(pci_bus_read_config, pos + PCI_CAP_LIST_NEXT, - PCI_CAP_ID_HT, dev->bus, + PCI_CAP_ID_HT, NULL, dev->bus, dev->devfn); } diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 0e67014aa001..1d1742bd6a1b 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -103,17 +103,21 @@ bool pcie_cap_has_rtctl(const struct pci_dev *dev); * @read_cfg: Function pointer for reading PCI config space * @start: Starting position to begin search * @cap: Capability ID to find + * @prev_ptr: Pointer to store position of preceding capability (optional) * @args: Arguments to pass to read_cfg function * - * Search the capability list in PCI config space to find @cap. + * Search the capability list in PCI config space to find @cap. If + * found, update *prev_ptr with the position of the preceding capability + * (if prev_ptr != NULL) * Implements TTL (time-to-live) protection against infinite loops. * * Return: Position of the capability if found, 0 otherwise. */ -#define PCI_FIND_NEXT_CAP(read_cfg, start, cap, args...) \ +#define PCI_FIND_NEXT_CAP(read_cfg, start, cap, prev_ptr, args...) \ ({ \ int __ttl = PCI_FIND_CAP_TTL; \ - u8 __id, __found_pos = 0; \ + u8 __id, __found_pos = 0; \ + u8 __prev_pos = (start); \ u8 __pos = (start); \ u16 __ent; \ \ @@ -132,9 +136,12 @@ bool pcie_cap_has_rtctl(const struct pci_dev *dev); \ if (__id == (cap)) { \ __found_pos = __pos; \ + if (prev_ptr != NULL) \ + *(u8 *)prev_ptr = __prev_pos; \ break; \ } \ \ + __prev_pos = __pos; \ __pos = FIELD_GET(PCI_CAP_LIST_NEXT_MASK, __ent); \ } \ __found_pos; \ @@ -146,21 +153,26 @@ bool pcie_cap_has_rtctl(const struct pci_dev *dev); * @read_cfg: Function pointer for reading PCI config space * @start: Starting position to begin search (0 for initial search) * @cap: Extended capability ID to find + * @prev_ptr: Pointer to store position of preceding capability (optional) * @args: Arguments to pass to read_cfg function * * Search the extended capability list in PCI config space to find @cap. + * If found, update *prev_ptr with the position of the preceding capability + * (if prev_ptr != NULL) * Implements TTL protection against infinite loops using a calculated * maximum search count. * * Return: Position of the capability if found, 0 otherwise. */ -#define PCI_FIND_NEXT_EXT_CAP(read_cfg, start, cap, args...) \ +#define PCI_FIND_NEXT_EXT_CAP(read_cfg, start, cap, prev_ptr, args...) \ ({ \ u16 __pos = (start) ?: PCI_CFG_SPACE_SIZE; \ u16 __found_pos = 0; \ + u16 __prev_pos; \ int __ttl, __ret; \ u32 __header; \ \ + __prev_pos = __pos; \ __ttl = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) / 8; \ while (__ttl-- > 0 && __pos >= PCI_CFG_SPACE_SIZE) { \ __ret = read_cfg##_dword(args, __pos, &__header); \ @@ -172,9 +184,12 @@ bool pcie_cap_has_rtctl(const struct pci_dev *dev); \ if (PCI_EXT_CAP_ID(__header) == (cap) && __pos != start) {\ __found_pos = __pos; \ + if (prev_ptr != NULL) \ + *(u16 *)prev_ptr = __prev_pos; \ break; \ } \ \ + __prev_pos = __pos; \ __pos = PCI_EXT_CAP_NEXT(__header); \ } \ __found_pos; \ From 0183562f1e824c0ca6c918309a0978e9a269af3e Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sun, 9 Nov 2025 22:59:41 -0800 Subject: [PATCH 02/41] PCI: dwc: Add new APIs to remove standard and extended Capability On some platforms, certain PCIe Capabilities may be present in hardware but are not fully implemented as defined in PCIe spec. These incomplete capabilities should be hidden from the PCI framework to prevent unexpected behavior. Introduce two APIs to remove a specific PCIe Capability and Extended Capability by updating the previous capability's next offset field to skip over the unwanted capability. These APIs allow RC drivers to easily hide unsupported or partially implemented capabilities from software. Co-developed-by: Wenbin Yao Signed-off-by: Wenbin Yao Signed-off-by: Qiang Yu Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251109-remove_cap-v1-2-2208f46f4dc2@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware.c | 53 ++++++++++++++++++++ drivers/pci/controller/dwc/pcie-designware.h | 2 + 2 files changed, 55 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 5d7a7e6f5724..345365ea97c7 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -236,6 +236,59 @@ u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap) } EXPORT_SYMBOL_GPL(dw_pcie_find_ext_capability); +void dw_pcie_remove_capability(struct dw_pcie *pci, u8 cap) +{ + u8 cap_pos, pre_pos, next_pos; + u16 reg; + + cap_pos = PCI_FIND_NEXT_CAP(dw_pcie_read_cfg, PCI_CAPABILITY_LIST, cap, + &pre_pos, pci); + if (!cap_pos) + return; + + reg = dw_pcie_readw_dbi(pci, cap_pos); + next_pos = (reg & 0xff00) >> 8; + + dw_pcie_dbi_ro_wr_en(pci); + if (pre_pos == PCI_CAPABILITY_LIST) + dw_pcie_writeb_dbi(pci, PCI_CAPABILITY_LIST, next_pos); + else + dw_pcie_writeb_dbi(pci, pre_pos + 1, next_pos); + dw_pcie_dbi_ro_wr_dis(pci); +} +EXPORT_SYMBOL_GPL(dw_pcie_remove_capability); + +void dw_pcie_remove_ext_capability(struct dw_pcie *pci, u8 cap) +{ + int cap_pos, next_pos, pre_pos; + u32 pre_header, header; + + cap_pos = PCI_FIND_NEXT_EXT_CAP(dw_pcie_read_cfg, 0, cap, &pre_pos, pci); + if (!cap_pos) + return; + + header = dw_pcie_readl_dbi(pci, cap_pos); + /* + * If the first cap at offset PCI_CFG_SPACE_SIZE is removed, + * only set it's capid to zero as it cannot be skipped. + */ + if (cap_pos == PCI_CFG_SPACE_SIZE) { + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writel_dbi(pci, cap_pos, header & 0xffff0000); + dw_pcie_dbi_ro_wr_dis(pci); + return; + } + + pre_header = dw_pcie_readl_dbi(pci, pre_pos); + next_pos = PCI_EXT_CAP_NEXT(header); + + dw_pcie_dbi_ro_wr_en(pci); + dw_pcie_writel_dbi(pci, pre_pos, + (pre_header & 0xfffff) | (next_pos << 20)); + dw_pcie_dbi_ro_wr_dis(pci); +} +EXPORT_SYMBOL_GPL(dw_pcie_remove_ext_capability); + static u16 __dw_pcie_find_vsec_capability(struct dw_pcie *pci, u16 vendor_id, u16 vsec_id) { diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 31685951a080..aec4af5194b5 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -562,6 +562,8 @@ void dw_pcie_version_detect(struct dw_pcie *pci); u8 dw_pcie_find_capability(struct dw_pcie *pci, u8 cap); u16 dw_pcie_find_ext_capability(struct dw_pcie *pci, u8 cap); +void dw_pcie_remove_capability(struct dw_pcie *pci, u8 cap); +void dw_pcie_remove_ext_capability(struct dw_pcie *pci, u8 cap); u16 dw_pcie_find_rasdes_capability(struct dw_pcie *pci); u16 dw_pcie_find_ptm_capability(struct dw_pcie *pci); From f5cd8a929c825ad4df3972df041ad62ad84ca6c9 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sun, 9 Nov 2025 22:59:42 -0800 Subject: [PATCH 03/41] PCI: dwc: Remove MSI/MSIX capability for Root Port if iMSI-RX is used as MSI controller Some platforms may not support ITS (Interrupt Translation Service) and MBI (Message Based Interrupt), or there are not enough available empty SPI lines for MBI, in which case the msi-map and msi-parent property will not be provided in device tree node. For those cases, the DWC PCIe driver defaults to using the iMSI-RX module as MSI controller. However, due to DWC IP design, iMSI-RX cannot generate MSI interrupts for Root Ports even when MSI is properly configured and supported as iMSI-RX will only monitor and intercept incoming MSI TLPs from PCIe link, but the memory write generated by Root Port are internal system bus transactions instead of PCIe TLPs, so they are ignored. This leads to interrupts such as PME, AER from the Root Port not received on the host and the users have to resort to workarounds such as passing "pcie_pme=nomsi" cmdline parameter. To ensure reliable interrupt handling, remove MSI and MSI-X capabilities from Root Ports when using iMSI-RX as MSI controller, which is indicated by 'dw_pcie_rp::has_msi_ctrl == true'. This forces a fallback to INTx interrupts by default, eliminating the need for manual kernel command line workarounds. With this behavior: - Platforms with ITS/MBI support use ITS/MBI MSI for interrupts from all components. - Platforms without ITS/MBI support fall back to INTx for Root Ports and use iMSI-RX for other PCI devices. Signed-off-by: Qiang Yu [mani: reworded the comment] Signed-off-by: Manivannan Sadhasivam Tested-by: Brian Norris Reviewed-by: Brian Norris Link: https://patch.msgid.link/20251109-remove_cap-v1-3-2208f46f4dc2@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware-host.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 372207c33a85..ffc95ddcf3bf 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -1115,6 +1115,17 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp) dw_pcie_dbi_ro_wr_dis(pci); + /* + * The iMSI-RX module does not support receiving MSI or MSI-X generated + * by the Root Port. If iMSI-RX is used as the MSI controller, remove + * the MSI and MSI-X capabilities of the Root Port to allow the drivers + * to fall back to INTx instead. + */ + if (pp->has_msi_ctrl) { + dw_pcie_remove_capability(pci, PCI_CAP_ID_MSI); + dw_pcie_remove_capability(pci, PCI_CAP_ID_MSIX); + } + return 0; } EXPORT_SYMBOL_GPL(dw_pcie_setup_rc); From 7c29cd0fdc07e5e21202fdeed0b63cba2b4f10c6 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sun, 9 Nov 2025 22:59:43 -0800 Subject: [PATCH 04/41] PCI: qcom: Remove MSI-X Capability for Root Ports On some platforms like Glymur, the hardware does not support MSI-X in RC mode, yet still exposes the MSI-X capability. However, it omits the required MSI-X Table and PBA structures. This mismatch can lead to issues where the PCIe port driver requests MSI-X instead of MSI, causing the Root Port to trigger interrupts by writing to an uninitialized address, resulting in SMMU faults. To address this, remove MSI-X capability unconditionally for Root Ports of all SoCs as none of the Qcom PCIe Root Ports support MSI-X. Signed-off-by: Qiang Yu [mani: updated description] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251109-remove_cap-v1-4-2208f46f4dc2@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 7b92e7a1c0d9..8b8948fef386 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1316,6 +1316,8 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) goto err_disable_phy; } + dw_pcie_remove_capability(pcie->pci, PCI_CAP_ID_MSIX); + qcom_ep_reset_deassert(pcie); if (pcie->cfg->ops->config_sid) { From 6a1394990902f0393706d7f96f58c21d88b65df7 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Sun, 9 Nov 2025 22:59:44 -0800 Subject: [PATCH 05/41] PCI: qcom: Remove DPC Extended Capability Some platforms (e.g., X1E80100) expose Downstream Port Containment (DPC) Extended Capability registers in the PCIe Root Port config space, but do not fully support it. To prevent undefined behavior and ensure DPC cap is not visible to PCI framework and users, remove DPC Extended Capability unconditionally, since there is no Qcom platform support DPC till now. Co-developed-by: Wenbin Yao Signed-off-by: Wenbin Yao Signed-off-by: Qiang Yu Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251109-remove_cap-v1-5-2208f46f4dc2@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 8b8948fef386..60373fe1362f 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1317,6 +1317,7 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) } dw_pcie_remove_capability(pcie->pci, PCI_CAP_ID_MSIX); + dw_pcie_remove_ext_capability(pcie->pci, PCI_EXT_CAP_ID_DPC); qcom_ep_reset_deassert(pcie); From 679ec639f29cbdaf36bd79bf3e98240fffa335ee Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 12 Dec 2025 09:33:24 +0800 Subject: [PATCH 06/41] PCI: dwc: Add L1 Substates context to ltssm_status of debugfs DWC core couldn't distinguish LTSSM state among L1.0, L1.1 and L1.2. But the vendor glue driver may implement additional logic to convey this information. So add two pseudo definitions for vendor glue drivers to translate their internal L1 Substates for debugfs to show. Signed-off-by: Shawn Lin Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/1765503205-22184-1-git-send-email-shawn.lin@rock-chips.com --- drivers/pci/controller/dwc/pcie-designware-debugfs.c | 2 ++ drivers/pci/controller/dwc/pcie-designware.h | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c index 0fbf86c0b97e..df98fee69892 100644 --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -485,6 +485,8 @@ static const char *ltssm_status_string(enum dw_pcie_ltssm ltssm) DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ1); DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ2); DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ3); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_1); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_2); default: str = "DW_PCIE_LTSSM_UNKNOWN"; break; diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index aec4af5194b5..fc9cf8ce8629 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -388,6 +388,10 @@ enum dw_pcie_ltssm { DW_PCIE_LTSSM_RCVRY_EQ2 = 0x22, DW_PCIE_LTSSM_RCVRY_EQ3 = 0x23, + /* Vendor glue drivers provide pseudo L1 substates from get_ltssm() */ + DW_PCIE_LTSSM_L1_1 = 0x141, + DW_PCIE_LTSSM_L1_2 = 0x142, + DW_PCIE_LTSSM_UNKNOWN = 0xFFFFFFFF, }; From f994bb8f1c94726e0124356ccd31c3c23a8a69f4 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Fri, 12 Dec 2025 09:33:25 +0800 Subject: [PATCH 07/41] PCI: dw-rockchip: Change get_ltssm() to provide L1 Substates info Rename rockchip_pcie_get_ltssm() to rockchip_pcie_get_ltssm_reg() and add rockchip_pcie_get_ltssm() to get_ltssm() callback in order to show the proper L1 Substates. The PCIE_CLIENT_LTSSM_STATUS[5:0] register returns the same LTSSM layout as enum dw_pcie_ltssm. So the driver just need to convey L1 PM Substates by returning the proper value defined in pcie-designware.h. cat /sys/kernel/debug/dwc_pcie_a40000000.pcie/ltssm_status L1_2 (0x142) Signed-off-by: Shawn Lin Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/1765503205-22184-2-git-send-email-shawn.lin@rock-chips.com --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index f8605fe61a41..8c1c92208802 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -68,6 +68,11 @@ #define PCIE_CLKREQ_NOT_READY FIELD_PREP_WM16(BIT(0), 0) #define PCIE_CLKREQ_PULL_DOWN FIELD_PREP_WM16(GENMASK(13, 12), 1) +/* RASDES TBA information */ +#define PCIE_CLIENT_CDM_RASDES_TBA_INFO_CMN 0x154 +#define PCIE_CLIENT_CDM_RASDES_TBA_L1_1 BIT(4) +#define PCIE_CLIENT_CDM_RASDES_TBA_L1_2 BIT(5) + /* Hot Reset Control Register */ #define PCIE_CLIENT_HOT_RESET_CTRL 0x180 #define PCIE_LTSSM_APP_DLY2_EN BIT(1) @@ -181,11 +186,26 @@ static int rockchip_pcie_init_irq_domain(struct rockchip_pcie *rockchip) return 0; } -static u32 rockchip_pcie_get_ltssm(struct rockchip_pcie *rockchip) +static u32 rockchip_pcie_get_ltssm_reg(struct rockchip_pcie *rockchip) { return rockchip_pcie_readl_apb(rockchip, PCIE_CLIENT_LTSSM_STATUS); } +static enum dw_pcie_ltssm rockchip_pcie_get_ltssm(struct dw_pcie *pci) +{ + struct rockchip_pcie *rockchip = to_rockchip_pcie(pci); + u32 val = rockchip_pcie_readl_apb(rockchip, + PCIE_CLIENT_CDM_RASDES_TBA_INFO_CMN); + + if (val & PCIE_CLIENT_CDM_RASDES_TBA_L1_1) + return DW_PCIE_LTSSM_L1_1; + + if (val & PCIE_CLIENT_CDM_RASDES_TBA_L1_2) + return DW_PCIE_LTSSM_L1_2; + + return rockchip_pcie_get_ltssm_reg(rockchip) & PCIE_LTSSM_STATUS_MASK; +} + static void rockchip_pcie_enable_ltssm(struct rockchip_pcie *rockchip) { rockchip_pcie_writel_apb(rockchip, PCIE_CLIENT_ENABLE_LTSSM, @@ -201,7 +221,7 @@ static void rockchip_pcie_disable_ltssm(struct rockchip_pcie *rockchip) static bool rockchip_pcie_link_up(struct dw_pcie *pci) { struct rockchip_pcie *rockchip = to_rockchip_pcie(pci); - u32 val = rockchip_pcie_get_ltssm(rockchip); + u32 val = rockchip_pcie_get_ltssm_reg(rockchip); return FIELD_GET(PCIE_LINKUP_MASK, val) == PCIE_LINKUP; } @@ -485,6 +505,7 @@ static const struct dw_pcie_ops dw_pcie_ops = { .link_up = rockchip_pcie_link_up, .start_link = rockchip_pcie_start_link, .stop_link = rockchip_pcie_stop_link, + .get_ltssm = rockchip_pcie_get_ltssm, }; static irqreturn_t rockchip_pcie_rc_sys_irq_thread(int irq, void *arg) @@ -499,7 +520,7 @@ static irqreturn_t rockchip_pcie_rc_sys_irq_thread(int irq, void *arg) 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(rockchip)); + dev_dbg(dev, "LTSSM_STATUS: %#x\n", rockchip_pcie_get_ltssm_reg(rockchip)); if (reg & PCIE_RDLH_LINK_UP_CHGED) { if (rockchip_pcie_link_up(pci)) { @@ -526,7 +547,7 @@ static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg) 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(rockchip)); + 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"); From cfd2fdfd0a8da2e5bbfdc4009b9c4b8bf164c937 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 18 Dec 2025 17:34:52 +0530 Subject: [PATCH 08/41] PCI: dwc: Skip PME_Turn_Off broadcast and L2/L3 transition during suspend if link is not up During system suspend, if the PCIe link is not up, then there is no need to broadcast PME_Turn_Off message and wait for L2/L3 transition. So skip them. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Tested-by: Vincent Guittot Reviewed-by: Frank Li Reviewed-by: Shawn Lin Link: https://patch.msgid.link/20251218-pci-dwc-suspend-rework-v2-1-5a7778c6094a@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware-host.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index ffc95ddcf3bf..8c41b90a1db1 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -1169,8 +1169,11 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci) int dw_pcie_suspend_noirq(struct dw_pcie *pci) { u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + int ret = 0; u32 val; - int ret; + + if (!dw_pcie_link_up(pci)) + goto stop_link; /* * If L1SS is supported, then do not put the link into L2 as some @@ -1205,6 +1208,7 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) */ udelay(1); +stop_link: dw_pcie_stop_link(pci); if (pci->pp.ops->deinit) pci->pp.ops->deinit(&pci->pp); From fc6298086bfacaa7003b0bd1da4e4f42b29f7d77 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 22 Dec 2025 07:42:08 +0100 Subject: [PATCH 09/41] Revert "PCI: dw-rockchip: Don't wait for link since we can detect Link Up" This reverts commit ec9fd499b9c60a187ac8d6414c3c343c77d32e42. While this fake hotplugging was a nice idea, it has shown that this feature does not handle PCIe switches correctly: pci_bus 0004:43: busn_res: can not insert [bus 43-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:43: busn_res: [bus 43-41] end is updated to 43 pci_bus 0004:43: busn_res: can not insert [bus 43] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:00.0: devices behind bridge are unusable because [bus 43] cannot be assigned for them pci_bus 0004:44: busn_res: can not insert [bus 44-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:44: busn_res: [bus 44-41] end is updated to 44 pci_bus 0004:44: busn_res: can not insert [bus 44] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:02.0: devices behind bridge are unusable because [bus 44] cannot be assigned for them pci_bus 0004:45: busn_res: can not insert [bus 45-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:45: busn_res: [bus 45-41] end is updated to 45 pci_bus 0004:45: busn_res: can not insert [bus 45] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:06.0: devices behind bridge are unusable because [bus 45] cannot be assigned for them pci_bus 0004:46: busn_res: can not insert [bus 46-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:46: busn_res: [bus 46-41] end is updated to 46 pci_bus 0004:46: busn_res: can not insert [bus 46] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:0e.0: devices behind bridge are unusable because [bus 46] cannot be assigned for them pci_bus 0004:42: busn_res: [bus 42-41] end is updated to 46 pci_bus 0004:42: busn_res: can not insert [bus 42-46] under [bus 41] (conflicts with (null) [bus 41]) pci 0004:41:00.0: devices behind bridge are unusable because [bus 42-46] cannot be assigned for them pcieport 0004:40:00.0: bridge has subordinate 41 but max busn 46 During the initial scan, PCI core doesn't see the switch and since the Root Port is not hot plug capable, the secondary bus number gets assigned as the subordinate bus number. This means, the PCI core assumes that only one bus will appear behind the Root Port since the Root Port is not hot plug capable. This works perfectly fine for PCIe endpoints connected to the Root Port, since they don't extend the bus. However, if a PCIe switch is connected, then there is a problem when the downstream busses starts showing up and the PCI core doesn't extend the subordinate bus number and bridge resources after initial scan during boot. The long term plan is to migrate this driver to the upcoming pwrctrl APIs that are supposed to handle this problem elegantly. Suggested-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Tested-by: Shawn Lin Acked-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251222064207.3246632-9-cassel@kernel.org --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 8c1c92208802..ca808d8f7975 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -601,7 +601,6 @@ static int rockchip_pcie_configure_rc(struct platform_device *pdev, pp = &rockchip->pci.pp; pp->ops = &rockchip_pcie_host_ops; - pp->use_linkup_irq = true; ret = dw_pcie_host_init(pp); if (ret) { From 180c3cfe36786d261a55da52a161f9e279b19a6f Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 22 Dec 2025 07:42:09 +0100 Subject: [PATCH 10/41] Revert "PCI: dw-rockchip: Enumerate endpoints based on dll_link_up IRQ" This reverts commit 0e0b45ab5d770a748487ba0ae8f77d1fb0f0de3e. While this fake hotplugging was a nice idea, it has shown that this feature does not handle PCIe switches correctly: pci_bus 0004:43: busn_res: can not insert [bus 43-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:43: busn_res: [bus 43-41] end is updated to 43 pci_bus 0004:43: busn_res: can not insert [bus 43] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:00.0: devices behind bridge are unusable because [bus 43] cannot be assigned for them pci_bus 0004:44: busn_res: can not insert [bus 44-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:44: busn_res: [bus 44-41] end is updated to 44 pci_bus 0004:44: busn_res: can not insert [bus 44] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:02.0: devices behind bridge are unusable because [bus 44] cannot be assigned for them pci_bus 0004:45: busn_res: can not insert [bus 45-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:45: busn_res: [bus 45-41] end is updated to 45 pci_bus 0004:45: busn_res: can not insert [bus 45] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:06.0: devices behind bridge are unusable because [bus 45] cannot be assigned for them pci_bus 0004:46: busn_res: can not insert [bus 46-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:46: busn_res: [bus 46-41] end is updated to 46 pci_bus 0004:46: busn_res: can not insert [bus 46] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:0e.0: devices behind bridge are unusable because [bus 46] cannot be assigned for them pci_bus 0004:42: busn_res: [bus 42-41] end is updated to 46 pci_bus 0004:42: busn_res: can not insert [bus 42-46] under [bus 41] (conflicts with (null) [bus 41]) pci 0004:41:00.0: devices behind bridge are unusable because [bus 42-46] cannot be assigned for them pcieport 0004:40:00.0: bridge has subordinate 41 but max busn 46 During the initial scan, PCI core doesn't see the switch and since the Root Port is not hot plug capable, the secondary bus number gets assigned as the subordinate bus number. This means, the PCI core assumes that only one bus will appear behind the Root Port since the Root Port is not hot plug capable. This works perfectly fine for PCIe endpoints connected to the Root Port, since they don't extend the bus. However, if a PCIe switch is connected, then there is a problem when the downstream busses starts showing up and the PCI core doesn't extend the subordinate bus number and bridge resources after initial scan during boot. The long term plan is to migrate this driver to the upcoming pwrctrl APIs that are supposed to handle this problem elegantly. Suggested-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Tested-by: Shawn Lin Acked-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251222064207.3246632-10-cassel@kernel.org --- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 59 +------------------ 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index ca808d8f7975..352f513ebf03 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -508,34 +508,6 @@ 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; - 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_RDLH_LINK_UP_CHGED) { - if (rockchip_pcie_link_up(pci)) { - msleep(PCIE_RESET_CONFIG_WAIT_MS); - dev_dbg(dev, "Received Link up event. Starting enumeration!\n"); - /* Rescan the bus to enumerate endpoint devices */ - pci_lock_rescan_remove(); - pci_rescan_bus(pp->bridge->bus); - pci_unlock_rescan_remove(); - } - } - - return IRQ_HANDLED; -} - static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg) { struct rockchip_pcie *rockchip = arg; @@ -568,29 +540,14 @@ static irqreturn_t rockchip_pcie_ep_sys_irq_thread(int irq, void *arg) return IRQ_HANDLED; } -static int rockchip_pcie_configure_rc(struct platform_device *pdev, - struct rockchip_pcie *rockchip) +static int rockchip_pcie_configure_rc(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); @@ -602,17 +559,7 @@ static int rockchip_pcie_configure_rc(struct platform_device *pdev, pp = &rockchip->pci.pp; pp->ops = &rockchip_pcie_host_ops; - ret = dw_pcie_host_init(pp); - if (ret) { - dev_err(dev, "failed to initialize host\n"); - return ret; - } - - /* unmask DLL up/down indicator */ - val = FIELD_PREP_WM16(PCIE_RDLH_LINK_UP_CHGED, 0); - rockchip_pcie_writel_apb(rockchip, val, PCIE_CLIENT_INTR_MASK_MISC); - - return ret; + return dw_pcie_host_init(pp); } static int rockchip_pcie_configure_ep(struct platform_device *pdev, @@ -731,7 +678,7 @@ static int rockchip_pcie_probe(struct platform_device *pdev) switch (data->mode) { case DW_PCIE_RC_TYPE: - ret = rockchip_pcie_configure_rc(pdev, rockchip); + ret = rockchip_pcie_configure_rc(rockchip); if (ret) goto deinit_clk; break; From e9ce5b3804436301ab343bc14203a4c14b336d1b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 22 Dec 2025 07:42:10 +0100 Subject: [PATCH 11/41] Revert "PCI: qcom: Don't wait for link if we can detect Link Up" This reverts commit 36971d6c5a9a134c15760ae9fd13c6d5f9a36abb. While this fake hotplugging was a nice idea, it has shown that this feature does not handle PCIe switches correctly: pci_bus 0004:43: busn_res: can not insert [bus 43-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:43: busn_res: [bus 43-41] end is updated to 43 pci_bus 0004:43: busn_res: can not insert [bus 43] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:00.0: devices behind bridge are unusable because [bus 43] cannot be assigned for them pci_bus 0004:44: busn_res: can not insert [bus 44-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:44: busn_res: [bus 44-41] end is updated to 44 pci_bus 0004:44: busn_res: can not insert [bus 44] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:02.0: devices behind bridge are unusable because [bus 44] cannot be assigned for them pci_bus 0004:45: busn_res: can not insert [bus 45-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:45: busn_res: [bus 45-41] end is updated to 45 pci_bus 0004:45: busn_res: can not insert [bus 45] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:06.0: devices behind bridge are unusable because [bus 45] cannot be assigned for them pci_bus 0004:46: busn_res: can not insert [bus 46-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:46: busn_res: [bus 46-41] end is updated to 46 pci_bus 0004:46: busn_res: can not insert [bus 46] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:0e.0: devices behind bridge are unusable because [bus 46] cannot be assigned for them pci_bus 0004:42: busn_res: [bus 42-41] end is updated to 46 pci_bus 0004:42: busn_res: can not insert [bus 42-46] under [bus 41] (conflicts with (null) [bus 41]) pci 0004:41:00.0: devices behind bridge are unusable because [bus 42-46] cannot be assigned for them pcieport 0004:40:00.0: bridge has subordinate 41 but max busn 46 During the initial scan, PCI core doesn't see the switch and since the Root Port is not hot plug capable, the secondary bus number gets assigned as the subordinate bus number. This means, the PCI core assumes that only one bus will appear behind the Root Port since the Root Port is not hot plug capable. This works perfectly fine for PCIe endpoints connected to the Root Port, since they don't extend the bus. However, if a PCIe switch is connected, then there is a problem when the downstream busses starts showing up and the PCI core doesn't extend the subordinate bus number and bridge resources after initial scan during boot. The long term plan is to migrate this driver to the upcoming pwrctrl APIs that are supposed to handle this problem elegantly. Suggested-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Tested-by: Shawn Lin Acked-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251222064207.3246632-11-cassel@kernel.org --- drivers/pci/controller/dwc/pcie-qcom.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 60373fe1362f..e87ec6779d44 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1958,10 +1958,6 @@ static int qcom_pcie_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pcie); - irq = platform_get_irq_byname_optional(pdev, "global"); - if (irq > 0) - pp->use_linkup_irq = true; - ret = dw_pcie_host_init(pp); if (ret) { dev_err(dev, "cannot initialize host\n"); @@ -1975,6 +1971,7 @@ static int qcom_pcie_probe(struct platform_device *pdev) goto err_host_deinit; } + irq = platform_get_irq_byname_optional(pdev, "global"); if (irq > 0) { ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, qcom_pcie_global_irq_thread, From 7ebdefb87942073679e56cfbc5a72e8fc5441bfc Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 22 Dec 2025 07:42:11 +0100 Subject: [PATCH 12/41] Revert "PCI: qcom: Enable MSI interrupts together with Link up if 'Global IRQ' is supported" This reverts commit ba4a2e2317b9faeca9193ed6d3193ddc3cf2aba3. Since the Link up IRQ support is going away, revert the MSI logic that got added for it too. Suggested-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel [mani: reworded the description] Signed-off-by: Manivannan Sadhasivam Tested-by: Shawn Lin Acked-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251222064207.3246632-12-cassel@kernel.org --- drivers/pci/controller/dwc/pcie-qcom.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index e87ec6779d44..c5fcb87972e9 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -136,7 +136,6 @@ /* PARF_INT_ALL_{STATUS/CLEAR/MASK} register fields */ #define PARF_INT_ALL_LINK_UP BIT(13) -#define PARF_INT_MSI_DEV_0_7 GENMASK(30, 23) /* PARF_NO_SNOOP_OVERRIDE register fields */ #define WR_NO_SNOOP_OVERRIDE_EN BIT(1) @@ -1982,8 +1981,7 @@ static int qcom_pcie_probe(struct platform_device *pdev) goto err_host_deinit; } - writel_relaxed(PARF_INT_ALL_LINK_UP | PARF_INT_MSI_DEV_0_7, - pcie->parf + PARF_INT_ALL_MASK); + writel_relaxed(PARF_INT_ALL_LINK_UP, pcie->parf + PARF_INT_ALL_MASK); } qcom_pcie_icc_opp_update(pcie); From 9a9793b55854422652ea92625e48277c4651c0fd Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 22 Dec 2025 07:42:12 +0100 Subject: [PATCH 13/41] Revert "PCI: qcom: Enumerate endpoints based on Link up event in 'global_irq' interrupt" This reverts commit 4581403f67929d02c197cb187c4e1e811c9e762a. While this fake hotplugging was a nice idea, it has shown that this feature does not handle PCIe switches correctly: pci_bus 0004:43: busn_res: can not insert [bus 43-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:43: busn_res: [bus 43-41] end is updated to 43 pci_bus 0004:43: busn_res: can not insert [bus 43] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:00.0: devices behind bridge are unusable because [bus 43] cannot be assigned for them pci_bus 0004:44: busn_res: can not insert [bus 44-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:44: busn_res: [bus 44-41] end is updated to 44 pci_bus 0004:44: busn_res: can not insert [bus 44] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:02.0: devices behind bridge are unusable because [bus 44] cannot be assigned for them pci_bus 0004:45: busn_res: can not insert [bus 45-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:45: busn_res: [bus 45-41] end is updated to 45 pci_bus 0004:45: busn_res: can not insert [bus 45] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:06.0: devices behind bridge are unusable because [bus 45] cannot be assigned for them pci_bus 0004:46: busn_res: can not insert [bus 46-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:46: busn_res: [bus 46-41] end is updated to 46 pci_bus 0004:46: busn_res: can not insert [bus 46] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:0e.0: devices behind bridge are unusable because [bus 46] cannot be assigned for them pci_bus 0004:42: busn_res: [bus 42-41] end is updated to 46 pci_bus 0004:42: busn_res: can not insert [bus 42-46] under [bus 41] (conflicts with (null) [bus 41]) pci 0004:41:00.0: devices behind bridge are unusable because [bus 42-46] cannot be assigned for them pcieport 0004:40:00.0: bridge has subordinate 41 but max busn 46 During the initial scan, PCI core doesn't see the switch and since the Root Port is not hot plug capable, the secondary bus number gets assigned as the subordinate bus number. This means, the PCI core assumes that only one bus will appear behind the Root Port since the Root Port is not hot plug capable. This works perfectly fine for PCIe endpoints connected to the Root Port, since they don't extend the bus. However, if a PCIe switch is connected, then there is a problem when the downstream busses starts showing up and the PCI core doesn't extend the subordinate bus number and bridge resources after initial scan during boot. The long term plan is to migrate this driver to the upcoming pwrctrl APIs that are supposed to handle this problem elegantly. Suggested-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Tested-by: Shawn Lin Acked-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251222064207.3246632-13-cassel@kernel.org --- drivers/pci/controller/dwc/pcie-qcom.c | 58 +------------------------- 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index c5fcb87972e9..13e6c334e10d 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -55,9 +55,6 @@ #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_SID_OFFSET 0x234 #define PARF_BDF_TRANSLATE_CFG 0x24c #define PARF_DBI_BASE_ADDR_V2 0x350 @@ -134,9 +131,6 @@ /* PARF_LTSSM register fields */ #define LTSSM_EN BIT(8) -/* PARF_INT_ALL_{STATUS/CLEAR/MASK} register fields */ -#define PARF_INT_ALL_LINK_UP BIT(13) - /* PARF_NO_SNOOP_OVERRIDE register fields */ #define WR_NO_SNOOP_OVERRIDE_EN BIT(1) #define RD_NO_SNOOP_OVERRIDE_EN BIT(3) @@ -1635,32 +1629,6 @@ 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; - u32 status = readl_relaxed(pcie->parf + PARF_INT_ALL_STATUS); - - writel_relaxed(status, pcie->parf + PARF_INT_ALL_CLEAR); - - if (FIELD_GET(PARF_INT_ALL_LINK_UP, status)) { - msleep(PCIE_RESET_CONFIG_WAIT_MS); - dev_dbg(dev, "Received Link up event. Starting enumeration!\n"); - /* Rescan the bus to enumerate endpoint devices */ - pci_lock_rescan_remove(); - pci_rescan_bus(pp->bridge->bus); - pci_unlock_rescan_remove(); - - qcom_pcie_icc_opp_update(pcie); - } else { - dev_WARN_ONCE(dev, 1, "Received unknown event. INT_STATUS: 0x%08x\n", - status); - } - - return IRQ_HANDLED; -} - static void qcom_pci_free_msi(void *ptr) { struct dw_pcie_rp *pp = (struct dw_pcie_rp *)ptr; @@ -1805,8 +1773,7 @@ static int qcom_pcie_probe(struct platform_device *pdev) struct dw_pcie_rp *pp; struct resource *res; struct dw_pcie *pci; - int ret, irq; - char *name; + int ret; pcie_cfg = of_device_get_match_data(dev); if (!pcie_cfg) { @@ -1963,27 +1930,6 @@ static int qcom_pcie_probe(struct platform_device *pdev) goto err_phy_exit; } - 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; - } - - irq = platform_get_irq_byname_optional(pdev, "global"); - if (irq > 0) { - 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_UP, pcie->parf + PARF_INT_ALL_MASK); - } - qcom_pcie_icc_opp_update(pcie); if (pcie->mhi) @@ -1991,8 +1937,6 @@ 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, &pcie->ports, list) { phy_exit(port->phy); From 142d5869f6eec3110adda0ad2d931f5b3c22371d Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Mon, 22 Dec 2025 07:42:13 +0100 Subject: [PATCH 14/41] Revert "PCI: dwc: Don't wait for link up if driver can detect Link Up event" This reverts commit 8d3bf19f1b585a3cc0027f508b64c33484db8d0d. While this fake hotplugging was a nice idea, it has shown that this feature does not handle PCIe switches correctly: pci_bus 0004:43: busn_res: can not insert [bus 43-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:43: busn_res: [bus 43-41] end is updated to 43 pci_bus 0004:43: busn_res: can not insert [bus 43] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:00.0: devices behind bridge are unusable because [bus 43] cannot be assigned for them pci_bus 0004:44: busn_res: can not insert [bus 44-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:44: busn_res: [bus 44-41] end is updated to 44 pci_bus 0004:44: busn_res: can not insert [bus 44] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:02.0: devices behind bridge are unusable because [bus 44] cannot be assigned for them pci_bus 0004:45: busn_res: can not insert [bus 45-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:45: busn_res: [bus 45-41] end is updated to 45 pci_bus 0004:45: busn_res: can not insert [bus 45] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:06.0: devices behind bridge are unusable because [bus 45] cannot be assigned for them pci_bus 0004:46: busn_res: can not insert [bus 46-41] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci_bus 0004:46: busn_res: [bus 46-41] end is updated to 46 pci_bus 0004:46: busn_res: can not insert [bus 46] under [bus 42-41] (conflicts with (null) [bus 42-41]) pci 0004:42:0e.0: devices behind bridge are unusable because [bus 46] cannot be assigned for them pci_bus 0004:42: busn_res: [bus 42-41] end is updated to 46 pci_bus 0004:42: busn_res: can not insert [bus 42-46] under [bus 41] (conflicts with (null) [bus 41]) pci 0004:41:00.0: devices behind bridge are unusable because [bus 42-46] cannot be assigned for them pcieport 0004:40:00.0: bridge has subordinate 41 but max busn 46 During the initial scan, PCI core doesn't see the switch and since the Root Port is not hot plug capable, the secondary bus number gets assigned as the subordinate bus number. This means, the PCI core assumes that only one bus will appear behind the Root Port since the Root Port is not hot plug capable. This works perfectly fine for PCIe endpoints connected to the Root Port, since they don't extend the bus. However, if a PCIe switch is connected, then there is a problem when the downstream busses starts showing up and the PCI core doesn't extend the subordinate bus number and bridge resources after initial scan during boot. So revert the change that skipped dw_pcie_wait_for_link() if the Link up IRQ was used by a vendor glue driver. Suggested-by: Manivannan Sadhasivam Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Tested-by: Shawn Lin Acked-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251222064207.3246632-14-cassel@kernel.org --- drivers/pci/controller/dwc/pcie-designware-host.c | 10 ++-------- drivers/pci/controller/dwc/pcie-designware.h | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 8c41b90a1db1..06c02fcc76c8 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -665,14 +665,8 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) goto err_remove_edma; } - /* - * Note: Skip the link up delay only when a Link Up IRQ is present. - * If there is no Link Up IRQ, we should not bypass the delay - * because that would require users to manually rescan for devices. - */ - if (!pp->use_linkup_irq) - /* Ignore errors, the link may come up later */ - dw_pcie_wait_for_link(pci); + /* Ignore errors, the link may come up later */ + dw_pcie_wait_for_link(pci); ret = pci_host_probe(bridge); if (ret) diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index fc9cf8ce8629..a3a3f6e89a81 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -438,7 +438,6 @@ struct dw_pcie_rp { bool use_atu_msg; int msg_atu_index; struct resource *msg_res; - bool use_linkup_irq; struct pci_eq_presets presets; struct pci_config_window *cfg; bool ecam_enabled; From 8719c64e76bf258cc8f44109740c854f2e2ead2e Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Mon, 22 Dec 2025 12:01:44 +0100 Subject: [PATCH 15/41] PCI: dwc: ep: Cache MSI outbound iATU mapping dw_pcie_ep_raise_msi_irq() currently programs an outbound iATU window for the MSI target address on every interrupt and tears it down again via dw_pcie_ep_unmap_addr(). On systems that heavily use the AXI bridge interface (for example when the integrated eDMA engine is active), this means the outbound iATU registers are updated while traffic is in flight. The DesignWare endpoint databook 5.40a - "3.10.6.1 iATU Outbound Programming Overview" warns that updating iATU registers in this situation is not supported, and the behavior is undefined. Under high MSI and eDMA load this pattern results in occasional bogus outbound transactions and IOMMU faults, on the RC side, such as: ipmmu-vmsa eed40000.iommu: Unhandled fault: status 0x00001502 iova 0xfe000000 followed by the system becoming unresponsive. This is the actual output observed on Renesas R-Car S4, with its ipmmu_hc used with PCIe ch0. There is no need to reprogram the iATU region used for MSI on every interrupt. The host-provided MSI address is stable while MSI is enabled, and the endpoint driver already dedicates a scratch buffer for MSI generation. Cache the aligned MSI address and map size, program the outbound iATU once, and keep the window enabled. Subsequent interrupts only perform a write to the MSI scratch buffer, avoiding dynamic iATU reprogramming in the hot path and fixing the lockups seen under load. dw_pcie_ep_raise_msix_irq() is not modified, as each vector can have a different msg_addr, and because the msg_addr is allowed to be changed while the vector is masked. Neither problem is easy to solve with the current design. Instead, the plan is for the DWC vendor drivers to transition to dw_pcie_ep_raise_msix_irq_doorbell(), which does not rely on the iATU. Signed-off-by: Koichiro Den [cassel: improve commit message] Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251222110144.3299523-2-cassel@kernel.org --- .../pci/controller/dwc/pcie-designware-ep.c | 48 ++++++++++++++++--- drivers/pci/controller/dwc/pcie-designware.h | 5 ++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index f6c54625486e..1195d401df19 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -601,6 +601,16 @@ static void dw_pcie_ep_stop(struct pci_epc *epc) struct dw_pcie_ep *ep = epc_get_drvdata(epc); struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + /* + * Tear down the dedicated outbound window used for MSI + * generation. This avoids leaking an iATU window across + * endpoint stop/start cycles. + */ + if (ep->msi_iatu_mapped) { + dw_pcie_ep_unmap_addr(epc, 0, 0, ep->msi_mem_phys); + ep->msi_iatu_mapped = false; + } + dw_pcie_stop_link(pci); } @@ -702,15 +712,38 @@ int dw_pcie_ep_raise_msi_irq(struct dw_pcie_ep *ep, u8 func_no, msg_addr = ((u64)msg_addr_upper) << 32 | msg_addr_lower; msg_addr = dw_pcie_ep_align_addr(epc, msg_addr, &map_size, &offset); - ret = dw_pcie_ep_map_addr(epc, func_no, 0, ep->msi_mem_phys, msg_addr, - map_size); - if (ret) - return ret; + + /* + * Program the outbound iATU once and keep it enabled. + * + * The spec warns that updating iATU registers while there are + * operations in flight on the AXI bridge interface is not + * supported, so we avoid reprogramming the region on every MSI, + * specifically unmapping immediately after writel(). + */ + if (!ep->msi_iatu_mapped) { + ret = dw_pcie_ep_map_addr(epc, func_no, 0, + ep->msi_mem_phys, msg_addr, + map_size); + if (ret) + return ret; + + ep->msi_iatu_mapped = true; + ep->msi_msg_addr = msg_addr; + ep->msi_map_size = map_size; + } else if (WARN_ON_ONCE(ep->msi_msg_addr != msg_addr || + ep->msi_map_size != map_size)) { + /* + * The host changed the MSI target address or the required + * mapping size changed. Reprogramming the iATU at runtime is + * unsafe on this controller, so bail out instead of trying to + * update the existing region. + */ + return -EINVAL; + } writel(msg_data | (interrupt_num - 1), ep->msi_mem + offset); - dw_pcie_ep_unmap_addr(epc, func_no, 0, ep->msi_mem_phys); - return 0; } EXPORT_SYMBOL_GPL(dw_pcie_ep_raise_msi_irq); @@ -1087,6 +1120,9 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep) struct device *dev = pci->dev; INIT_LIST_HEAD(&ep->func_list); + ep->msi_iatu_mapped = false; + ep->msi_msg_addr = 0; + ep->msi_map_size = 0; epc = devm_pci_epc_create(dev, &epc_ops); if (IS_ERR(epc)) { diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index a3a3f6e89a81..f87c67a7a482 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -482,6 +482,11 @@ struct dw_pcie_ep { void __iomem *msi_mem; phys_addr_t msi_mem_phys; struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS]; + + /* MSI outbound iATU state */ + bool msi_iatu_mapped; + u64 msi_msg_addr; + size_t msi_map_size; }; struct dw_pcie_ops { From 68ac85fb42cfeb081cf029acdd8aace55ed375a2 Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Wed, 24 Dec 2025 09:38:52 +0800 Subject: [PATCH 16/41] PCI: dwc: Use cfg0_base as iMSI-RX target address to support 32-bit MSI devices commit f3a296405b6e ("PCI: dwc: Strengthen the MSI address allocation logic") strengthened the iMSI-RX target address allocation logic to handle 64-bit addresses for platforms without 32-bit DMA addresses. However, it still left 32-bit MSI capable endpoints (EPs) non-functional on such platforms. Per DWC databook r6.21a, sec 3.10.2.3, it states: "The iMSI-RX is programmed with an address (MSI_CTRL_ADDR_OFF and MSI_CTRL_UPPER_ADDR_OFF) that is used as the system MSI address. When an inbound MWr request is passed to the AXI bridge and matches this address as well as the conditions specified for an MSI memory write request, an MSI interrupt is detected. When this MWr is about to be driven onto the AXI bridge master interface1, it is dropped and never appears on the AXI bus." Since iMSI-RX MSI_CTRL_ADDR doesn't require actual system memory mapping, any 32-bit address that won't be used for BAR memory allocations can be assigned. So assign cfg0_base to the iMSI-RX target address as the first option if it's a 32-bit address, which satisfies this requirement. Otherwise, fallback to the existing coherent allocation. cc: Ajay Agarwal cc: Will McVicker Signed-off-by: Shawn Lin [mani: trimmed the description to exclude testbed info and used imperative tone] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/1766540332-24235-1-git-send-email-shawn.lin@rock-chips.com --- .../pci/controller/dwc/pcie-designware-host.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 06c02fcc76c8..fad0cbedefbc 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -367,10 +367,20 @@ int dw_pcie_msi_host_init(struct dw_pcie_rp *pp) * order not to miss MSI TLPs from those devices the MSI target * address has to be within the lowest 4GB. * - * Note until there is a better alternative found the reservation is - * done by allocating from the artificially limited DMA-coherent - * memory. + * Per DWC databook r6.21a, section 3.10.2.3, the incoming MWr TLP + * targeting the MSI_CTRL_ADDR is terminated by the iMSI-RX and never + * appears on the AXI bus. So MSI_CTRL_ADDR address doesn't need to be + * mapped and can be any memory that doesn't get allocated for the BAR + * memory. Since most of the platforms provide 32-bit address for + * 'config' region, try cfg0_base as the first option for the MSI target + * address if it's a 32-bit address. Otherwise, try 32-bit and 64-bit + * coherent memory allocation one by one. */ + if (!(pp->cfg0_base & GENMASK_ULL(63, 32))) { + pp->msi_data = pp->cfg0_base; + return 0; + } + ret = dma_set_coherent_mask(dev, DMA_BIT_MASK(32)); if (!ret) msi_vaddr = dmam_alloc_coherent(dev, sizeof(u64), &pp->msi_data, From 1bcf245c837bc66fdaddea222bab9eb5c978a9d7 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 20 Jan 2026 23:17:40 +0530 Subject: [PATCH 17/41] PCI: dwc: Return -ENODEV from dw_pcie_wait_for_link() if device is not found The dw_pcie_wait_for_link() function waits up to 1 second for the PCIe link to come up and returns -ETIMEDOUT for all failures without distinguishing cases where no device is present on the bus. But the callers may want to just skip the failure if the device is not found on the bus and handle failure for other reasons. So after timeout, if the LTSSM is in Detect.Quiet or Detect.Active state, return -ENODEV to indicate the callers that the device is not found on the bus and return -ETIMEDOUT otherwise. Also add kernel doc to document the parameter and return values. Signed-off-by: Manivannan Sadhasivam Tested-by: Richard Zhu Tested-by: Vincent Guittot Reviewed-by: Shawn Lin Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260120-pci-dwc-suspend-rework-v4-1-2f32d5082549@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 345365ea97c7..55c1c60f7f8f 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -692,9 +692,16 @@ void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index) dw_pcie_writel_atu(pci, dir, index, PCIE_ATU_REGION_CTRL2, 0); } +/** + * dw_pcie_wait_for_link - Wait for the PCIe link to be up + * @pci: DWC instance + * + * Returns: 0 if link is up, -ENODEV if device is not found, -ETIMEDOUT if the + * link fails to come up for other reasons. + */ int dw_pcie_wait_for_link(struct dw_pcie *pci) { - u32 offset, val; + u32 offset, val, ltssm; int retries; /* Check if the link is up or not */ @@ -706,6 +713,17 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci) } if (retries >= PCIE_LINK_WAIT_MAX_RETRIES) { + /* + * If the link is in Detect.Quiet or Detect.Active state, it + * indicates that no device is detected. + */ + ltssm = dw_pcie_get_ltssm(pci); + if (ltssm == DW_PCIE_LTSSM_DETECT_QUIET || + ltssm == DW_PCIE_LTSSM_DETECT_ACT) { + dev_info(pci->dev, "Device not found\n"); + return -ENODEV; + } + dev_info(pci->dev, "Phy link never came up\n"); return -ETIMEDOUT; } From 01d16b8afb7afcc17f999f8b4a9b9cfe6c6fae71 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 20 Jan 2026 23:17:41 +0530 Subject: [PATCH 18/41] PCI: dwc: Return -EIO from dw_pcie_wait_for_link() if device is not active There are cases where the PCIe device would be physically connected to the bus, but the device firmware might not be active. So the LTSSM will get stuck in POLL.{Active/Compliance} states. This behavior is common with endpoint devices controlled by the PCI Endpoint framework, where the device will wait for the user to start its operation through configfs. For those cases, print the relevant log and return -EIO to indicate that the device is present, but not active. This will allow the callers to skip the failure as the device might become active in the future. Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260120-pci-dwc-suspend-rework-v4-2-2f32d5082549@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 55c1c60f7f8f..aca5bbeade03 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -696,8 +696,9 @@ void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index) * dw_pcie_wait_for_link - Wait for the PCIe link to be up * @pci: DWC instance * - * Returns: 0 if link is up, -ENODEV if device is not found, -ETIMEDOUT if the - * link fails to come up for other reasons. + * Returns: 0 if link is up, -ENODEV if device is not found, -EIO if the device + * is found but not active and -ETIMEDOUT if the link fails to come up for other + * reasons. */ int dw_pcie_wait_for_link(struct dw_pcie *pci) { @@ -722,6 +723,16 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci) ltssm == DW_PCIE_LTSSM_DETECT_ACT) { dev_info(pci->dev, "Device not found\n"); return -ENODEV; + + /* + * If the link is in POLL.{Active/Compliance} state, then the + * device is found to be connected to the bus, but it is not + * active i.e., the device firmware might not yet initialized. + */ + } else if (ltssm == DW_PCIE_LTSSM_POLL_ACTIVE || + ltssm == DW_PCIE_LTSSM_POLL_COMPLIANCE) { + dev_info(pci->dev, "Device found, but not active\n"); + return -EIO; } dev_info(pci->dev, "Phy link never came up\n"); From 36dd677f7d76ddec96d50cf57f543ba7d612087c Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 20 Jan 2026 23:17:42 +0530 Subject: [PATCH 19/41] PCI: dwc: Rename and move ltssm_status_string() to pcie-designware.c Rename ltssm_status_string() to dw_pcie_ltssm_status_string() and move it to the common file pcie-designware.c so that this function could be used outside of pcie-designware-debugfs.c file. Signed-off-by: Manivannan Sadhasivam Tested-by: Richard Zhu Tested-by: Vincent Guittot Reviewed-by: Shawn Lin Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260120-pci-dwc-suspend-rework-v4-3-2f32d5082549@oss.qualcomm.com --- .../controller/dwc/pcie-designware-debugfs.c | 54 +------------------ drivers/pci/controller/dwc/pcie-designware.c | 52 ++++++++++++++++++ drivers/pci/controller/dwc/pcie-designware.h | 2 + 3 files changed, 55 insertions(+), 53 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c index df98fee69892..0d1340c9b364 100644 --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -443,65 +443,13 @@ static ssize_t counter_value_read(struct file *file, char __user *buf, return simple_read_from_buffer(buf, count, ppos, debugfs_buf, pos); } -static const char *ltssm_status_string(enum dw_pcie_ltssm ltssm) -{ - const char *str; - - switch (ltssm) { -#define DW_PCIE_LTSSM_NAME(n) case n: str = #n; break - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DETECT_QUIET); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DETECT_ACT); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_POLL_ACTIVE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_POLL_COMPLIANCE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_POLL_CONFIG); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_PRE_DETECT_QUIET); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DETECT_WAIT); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LINKWD_START); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LINKWD_ACEPT); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LANENUM_WAI); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LANENUM_ACEPT); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_COMPLETE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_IDLE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_LOCK); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_SPEED); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_RCVRCFG); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_IDLE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L0); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L0S); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L123_SEND_EIDLE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_IDLE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L2_IDLE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L2_WAKE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DISABLED_ENTRY); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DISABLED_IDLE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DISABLED); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_ENTRY); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_ACTIVE); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_EXIT); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_EXIT_TIMEOUT); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_HOT_RESET_ENTRY); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_HOT_RESET); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ0); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ1); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ2); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ3); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_1); - DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_2); - default: - str = "DW_PCIE_LTSSM_UNKNOWN"; - break; - } - - return str + strlen("DW_PCIE_LTSSM_"); -} - static int ltssm_status_show(struct seq_file *s, void *v) { struct dw_pcie *pci = s->private; enum dw_pcie_ltssm val; val = dw_pcie_get_ltssm(pci); - seq_printf(s, "%s (0x%02x)\n", ltssm_status_string(val), val); + seq_printf(s, "%s (0x%02x)\n", dw_pcie_ltssm_status_string(val), val); return 0; } diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index aca5bbeade03..f74eae79cca4 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -692,6 +692,58 @@ void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index) dw_pcie_writel_atu(pci, dir, index, PCIE_ATU_REGION_CTRL2, 0); } +const char *dw_pcie_ltssm_status_string(enum dw_pcie_ltssm ltssm) +{ + const char *str; + + switch (ltssm) { +#define DW_PCIE_LTSSM_NAME(n) case n: str = #n; break + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DETECT_QUIET); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DETECT_ACT); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_POLL_ACTIVE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_POLL_COMPLIANCE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_POLL_CONFIG); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_PRE_DETECT_QUIET); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DETECT_WAIT); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LINKWD_START); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LINKWD_ACEPT); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LANENUM_WAI); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_LANENUM_ACEPT); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_COMPLETE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_CFG_IDLE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_LOCK); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_SPEED); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_RCVRCFG); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_IDLE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L0); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L0S); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L123_SEND_EIDLE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_IDLE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L2_IDLE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L2_WAKE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DISABLED_ENTRY); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DISABLED_IDLE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_DISABLED); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_ENTRY); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_ACTIVE); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_EXIT); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_LPBK_EXIT_TIMEOUT); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_HOT_RESET_ENTRY); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_HOT_RESET); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ0); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ1); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ2); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_RCVRY_EQ3); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_1); + DW_PCIE_LTSSM_NAME(DW_PCIE_LTSSM_L1_2); + default: + str = "DW_PCIE_LTSSM_UNKNOWN"; + break; + } + + return str + strlen("DW_PCIE_LTSSM_"); +} + /** * dw_pcie_wait_for_link - Wait for the PCIe link to be up * @pci: DWC instance diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index f87c67a7a482..c1def4d9cf62 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -828,6 +828,8 @@ static inline enum dw_pcie_ltssm dw_pcie_get_ltssm(struct dw_pcie *pci) return (enum dw_pcie_ltssm)FIELD_GET(PORT_LOGIC_LTSSM_STATE_MASK, val); } +const char *dw_pcie_ltssm_status_string(enum dw_pcie_ltssm ltssm); + #ifdef CONFIG_PCIE_DW_HOST int dw_pcie_suspend_noirq(struct dw_pcie *pci); int dw_pcie_resume_noirq(struct dw_pcie *pci); From d266f63b385a3bad5fd6f23797b6de57de998677 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 20 Jan 2026 23:17:43 +0530 Subject: [PATCH 20/41] PCI: dwc: Rework the error print of dw_pcie_wait_for_link() For the cases where the link cannot come up later i.e., when LTSSM is not in Detect.{Quiet/Active} or Poll.{Active/Compliance} states, dw_pcie_wait_for_link() should log an error. So promote dev_info() to dev_err(), reword the error log to make it clear and also print the LTSSM state to aid debugging. Signed-off-by: Manivannan Sadhasivam Tested-by: Richard Zhu Tested-by: Vincent Guittot Reviewed-by: Shawn Lin Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260120-pci-dwc-suspend-rework-v4-4-2f32d5082549@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index f74eae79cca4..2fa9f6ee149e 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -787,7 +787,8 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci) return -EIO; } - dev_info(pci->dev, "Phy link never came up\n"); + dev_err(pci->dev, "Link failed to come up. LTSSM: %s\n", + dw_pcie_ltssm_status_string(ltssm)); return -ETIMEDOUT; } From 86cbb7a81068434fdc1d5afb96d91ab971fb279e Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 20 Jan 2026 23:17:44 +0530 Subject: [PATCH 21/41] PCI: dwc: Fail dw_pcie_host_init() if dw_pcie_wait_for_link() returns -ETIMEDOUT The dw_pcie_wait_for_link() API now distinguishes link failures more precisely: -ENODEV: Device not found on the bus. -EIO: Device found but inactive. -ETIMEDOUT: Link failed to come up. Out of these three errors, only -ETIMEDOUT represents a definitive link failure since it signals that something is wrong with the link. For the other two errors, there is a possibility that the link might come up later. So fail dw_pcie_host_init() if -ETIMEDOUT is returned and skip the failure otherwise. Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260120-pci-dwc-suspend-rework-v4-5-2f32d5082549@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware-host.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index fad0cbedefbc..a72406ef7e26 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -675,8 +675,13 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) goto err_remove_edma; } - /* Ignore errors, the link may come up later */ - dw_pcie_wait_for_link(pci); + /* + * Only fail on timeout error. Other errors indicate the device may + * become available later, so continue without failing. + */ + ret = dw_pcie_wait_for_link(pci); + if (ret == -ETIMEDOUT) + goto err_stop_link; ret = pci_host_probe(bridge); if (ret) From 58a17b2647ba5aac47e3ffafd0a9b92bf4a9bcbe Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Wed, 14 Jan 2026 16:33:00 +0800 Subject: [PATCH 22/41] PCI: dwc: Skip waiting for L2/L3 Ready if dw_pcie_rp::skip_l23_wait is true In NXP i.MX6QP and i.MX7D SoCs, LTSSM registers are not accessible once PME_Turn_Off message is broadcasted to the link. So there is no way to verify whether the link has entered L2/L3 Ready state or not. Hence, add a new flag 'dw_pcie_rp::skip_l23_ready' and set it to 'true' for the above mentioned SoCs. This flag when set, will allow the DWC core to skip polling for L2/L3 Ready state and just wait for 10ms as recommended in the PCIe spec r6.0, sec 5.3.3.2.1. Fixes: a528d1a72597 ("PCI: imx6: Use DWC common suspend resume method") Signed-off-by: Richard Zhu [mani: renamed flag to skip_l23_ready and reworded description] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260114083300.3689672-2-hongxing.zhu@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 5 +++++ drivers/pci/controller/dwc/pcie-designware-host.c | 10 ++++++++++ drivers/pci/controller/dwc/pcie-designware.h | 1 + 3 files changed, 16 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 4668fc9648bf..dfe814469993 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -114,6 +114,7 @@ enum imx_pcie_variants { #define IMX_PCIE_FLAG_BROKEN_SUSPEND BIT(9) #define IMX_PCIE_FLAG_HAS_LUT BIT(10) #define IMX_PCIE_FLAG_8GT_ECN_ERR051586 BIT(11) +#define IMX_PCIE_FLAG_SKIP_L23_READY BIT(12) #define imx_check_flag(pci, val) (pci->drvdata->flags & val) @@ -1777,6 +1778,8 @@ static int imx_pcie_probe(struct platform_device *pdev) */ imx_pcie_add_lut_by_rid(imx_pcie, 0); } else { + if (imx_check_flag(imx_pcie, IMX_PCIE_FLAG_SKIP_L23_READY)) + pci->pp.skip_l23_ready = true; pci->pp.use_atu_msg = true; ret = dw_pcie_host_init(&pci->pp); if (ret < 0) @@ -1838,6 +1841,7 @@ static const struct imx_pcie_drvdata drvdata[] = { .variant = IMX6QP, .flags = IMX_PCIE_FLAG_IMX_PHY | IMX_PCIE_FLAG_SPEED_CHANGE_WORKAROUND | + IMX_PCIE_FLAG_SKIP_L23_READY | IMX_PCIE_FLAG_SUPPORTS_SUSPEND, .dbi_length = 0x200, .gpr = "fsl,imx6q-iomuxc-gpr", @@ -1854,6 +1858,7 @@ static const struct imx_pcie_drvdata drvdata[] = { .variant = IMX7D, .flags = IMX_PCIE_FLAG_SUPPORTS_SUSPEND | IMX_PCIE_FLAG_HAS_APP_RESET | + IMX_PCIE_FLAG_SKIP_L23_READY | IMX_PCIE_FLAG_HAS_PHY_RESET, .gpr = "fsl,imx7d-iomuxc-gpr", .mode_off[0] = IOMUXC_GPR12, diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index a72406ef7e26..a17833dd6f9d 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -1199,6 +1199,16 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) return ret; } + /* + * Some SoCs do not support reading the LTSSM register after + * PME_Turn_Off broadcast. For those SoCs, skip waiting for L2/L3 Ready + * state and wait 10ms as recommended in PCIe spec r6.0, sec 5.3.3.2.1. + */ + if (pci->pp.skip_l23_ready) { + mdelay(PCIE_PME_TO_L2_TIMEOUT_US/1000); + goto stop_link; + } + ret = read_poll_timeout(dw_pcie_get_ltssm, val, val == DW_PCIE_LTSSM_L2_IDLE || val <= DW_PCIE_LTSSM_DETECT_WAIT, diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index c1def4d9cf62..ec4b7a689f59 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -442,6 +442,7 @@ struct dw_pcie_rp { struct pci_config_window *cfg; bool ecam_enabled; bool native_ecam; + bool skip_l23_ready; }; struct dw_pcie_ep_ops { From 86291f774fe8524178446cb2c792939640b4970c Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 24 Dec 2025 02:10:46 -0800 Subject: [PATCH 23/41] PCI: dwc: Remove duplicate dw_pcie_ep_hide_ext_capability() function Remove dw_pcie_ep_hide_ext_capability() and replace its usage with dw_pcie_remove_ext_capability(). Both functions serve the same purpose of hiding PCIe extended capabilities, but dw_pcie_remove_ext_capability() provides a cleaner API that doesn't require the caller to specify the previous capability ID. Suggested-by: Niklas Cassel Signed-off-by: Qiang Yu Signed-off-by: Manivannan Sadhasivam Tested-by: Niklas Cassel Link: https://patch.msgid.link/20251224-remove_dw_pcie_ep_hide_ext_capability-v1-1-4302c9cdc316@oss.qualcomm.com --- .../pci/controller/dwc/pcie-designware-ep.c | 39 ------------------- drivers/pci/controller/dwc/pcie-designware.h | 7 ---- drivers/pci/controller/dwc/pcie-dw-rockchip.c | 4 +- 3 files changed, 1 insertion(+), 49 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 1195d401df19..cfd59899c7b8 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -75,45 +75,6 @@ static u8 dw_pcie_ep_find_capability(struct dw_pcie_ep *ep, u8 func_no, u8 cap) cap, NULL, ep, func_no); } -/** - * dw_pcie_ep_hide_ext_capability - Hide a capability from the linked list - * @pci: DWC PCI device - * @prev_cap: Capability preceding the capability that should be hidden - * @cap: Capability that should be hidden - * - * Return: 0 if success, errno otherwise. - */ -int dw_pcie_ep_hide_ext_capability(struct dw_pcie *pci, u8 prev_cap, u8 cap) -{ - u16 prev_cap_offset, cap_offset; - u32 prev_cap_header, cap_header; - - prev_cap_offset = dw_pcie_find_ext_capability(pci, prev_cap); - if (!prev_cap_offset) - return -EINVAL; - - prev_cap_header = dw_pcie_readl_dbi(pci, prev_cap_offset); - cap_offset = PCI_EXT_CAP_NEXT(prev_cap_header); - cap_header = dw_pcie_readl_dbi(pci, cap_offset); - - /* cap must immediately follow prev_cap. */ - if (PCI_EXT_CAP_ID(cap_header) != cap) - return -EINVAL; - - /* Clear next ptr. */ - prev_cap_header &= ~GENMASK(31, 20); - - /* Set next ptr to next ptr of cap. */ - prev_cap_header |= cap_header & GENMASK(31, 20); - - dw_pcie_dbi_ro_wr_en(pci); - dw_pcie_writel_dbi(pci, prev_cap_offset, prev_cap_header); - dw_pcie_dbi_ro_wr_dis(pci); - - return 0; -} -EXPORT_SYMBOL_GPL(dw_pcie_ep_hide_ext_capability); - static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct pci_epf_header *hdr) { diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index ec4b7a689f59..53b65428fadb 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -912,7 +912,6 @@ int dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, u8 func_no, int dw_pcie_ep_raise_msix_irq_doorbell(struct dw_pcie_ep *ep, u8 func_no, u16 interrupt_num); void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar); -int dw_pcie_ep_hide_ext_capability(struct dw_pcie *pci, u8 prev_cap, u8 cap); struct dw_pcie_ep_func * dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, u8 func_no); #else @@ -970,12 +969,6 @@ static inline void dw_pcie_ep_reset_bar(struct dw_pcie *pci, enum pci_barno bar) { } -static inline int dw_pcie_ep_hide_ext_capability(struct dw_pcie *pci, - u8 prev_cap, u8 cap) -{ - return 0; -} - static inline struct dw_pcie_ep_func * dw_pcie_ep_get_func_from_ep(struct dw_pcie_ep *ep, u8 func_no) { diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 352f513ebf03..77c4e6a4ddea 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -347,9 +347,7 @@ static void rockchip_pcie_ep_hide_broken_ats_cap_rk3588(struct dw_pcie_ep *ep) if (!of_device_is_compatible(dev->of_node, "rockchip,rk3588-pcie-ep")) return; - if (dw_pcie_ep_hide_ext_capability(pci, PCI_EXT_CAP_ID_SECPCI, - PCI_EXT_CAP_ID_ATS)) - dev_err(dev, "failed to hide ATS capability\n"); + dw_pcie_remove_ext_capability(pci, PCI_EXT_CAP_ID_ATS); } static void rockchip_pcie_ep_init(struct dw_pcie_ep *ep) From e9a5415adb209f86a05e55b850127ada82e070f1 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Fri, 9 Jan 2026 19:34:30 +0800 Subject: [PATCH 24/41] PCI: dwc: Use multiple iATU windows for mapping large bridge windows and DMA ranges The DWC driver tries to use a single iATU region for mapping the individual entries of the bridge window and DMA range. If a bridge window/DMA range is larger than the iATU inbound/outbound window size, then the mapping will fail. Hence, avoid this failure by using multiple iATU windows to map the whole region. If the region runs out of iATU windows, then return failure. Signed-off-by: Charles Mirabile Signed-off-by: Samuel Holland Co-developed-by: Randolph Lin Signed-off-by: Randolph Lin [mani: reworded description, minor code cleanup] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Reviewed-by: Frank Li Acked-by: Charles Mirabile Link: https://patch.msgid.link/20260109113430.2767264-1-randolph@andestech.com --- .../pci/controller/dwc/pcie-designware-host.c | 74 ++++++++++++++----- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index a17833dd6f9d..ab17549af518 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -912,29 +912,50 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) i = 0; resource_list_for_each_entry(entry, &pp->bridge->windows) { + resource_size_t res_size; + if (resource_type(entry->res) != IORESOURCE_MEM) continue; - if (pci->num_ob_windows <= ++i) + if (pci->num_ob_windows <= i + 1) break; - atu.index = i; atu.type = PCIE_ATU_TYPE_MEM; atu.parent_bus_addr = entry->res->start - pci->parent_bus_offset; atu.pci_addr = entry->res->start - entry->offset; /* Adjust iATU size if MSG TLP region was allocated before */ if (pp->msg_res && pp->msg_res->parent == entry->res) - atu.size = resource_size(entry->res) - + res_size = resource_size(entry->res) - resource_size(pp->msg_res); else - atu.size = resource_size(entry->res); + res_size = resource_size(entry->res); - ret = dw_pcie_prog_outbound_atu(pci, &atu); - if (ret) { - dev_err(pci->dev, "Failed to set MEM range %pr\n", - entry->res); - return ret; + while (res_size > 0) { + /* + * Return failure if we run out of windows in the + * middle. Otherwise, we would end up only partially + * mapping a single resource. + */ + if (pci->num_ob_windows <= ++i) { + dev_err(pci->dev, "Exhausted outbound windows for region: %pr\n", + entry->res); + return -ENOMEM; + } + + atu.index = i; + atu.size = MIN(pci->region_limit + 1, res_size); + + ret = dw_pcie_prog_outbound_atu(pci, &atu); + if (ret) { + dev_err(pci->dev, "Failed to set MEM range %pr\n", + entry->res); + return ret; + } + + atu.parent_bus_addr += atu.size; + atu.pci_addr += atu.size; + res_size -= atu.size; } } @@ -965,20 +986,39 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) i = 0; resource_list_for_each_entry(entry, &pp->bridge->dma_ranges) { + resource_size_t res_start, res_size, window_size; + if (resource_type(entry->res) != IORESOURCE_MEM) continue; if (pci->num_ib_windows <= i) break; - ret = dw_pcie_prog_inbound_atu(pci, i++, PCIE_ATU_TYPE_MEM, - entry->res->start, - entry->res->start - entry->offset, - resource_size(entry->res)); - if (ret) { - dev_err(pci->dev, "Failed to set DMA range %pr\n", - entry->res); - return ret; + res_size = resource_size(entry->res); + res_start = entry->res->start; + while (res_size > 0) { + /* + * Return failure if we run out of windows in the + * middle. Otherwise, we would end up only partially + * mapping a single resource. + */ + if (pci->num_ib_windows <= i) { + dev_err(pci->dev, "Exhausted inbound windows for region: %pr\n", + entry->res); + return -ENOMEM; + } + + window_size = MIN(pci->region_limit + 1, res_size); + ret = dw_pcie_prog_inbound_atu(pci, i++, PCIE_ATU_TYPE_MEM, res_start, + res_start - entry->offset, window_size); + if (ret) { + dev_err(pci->dev, "Failed to set DMA range %pr\n", + entry->res); + return ret; + } + + res_start += window_size; + res_size -= window_size; } } From 8a214f64554ce4da91f35e7d3b8eaa9674f5154d Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 21 Jan 2026 23:45:18 -0800 Subject: [PATCH 25/41] PCI: dwc: Fix grammar and formatting for comment in dw_pcie_remove_ext_capability() Fix a grammatical error in the comment by changing "it's" to "its". Also add a blank line after the variable declaration for better code formatting. Signed-off-by: Qiang Yu Signed-off-by: Manivannan Sadhasivam Reviewed-by: Shawn Lin Link: https://patch.msgid.link/20260121-remove_cap_clean_up-v1-1-e78115e5d467@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 2fa9f6ee149e..18331d9e85be 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -268,9 +268,10 @@ void dw_pcie_remove_ext_capability(struct dw_pcie *pci, u8 cap) return; header = dw_pcie_readl_dbi(pci, cap_pos); + /* * If the first cap at offset PCI_CFG_SPACE_SIZE is removed, - * only set it's capid to zero as it cannot be skipped. + * only set its capid to zero as it cannot be skipped. */ if (cap_pos == PCI_CFG_SPACE_SIZE) { dw_pcie_dbi_ro_wr_en(pci); From 0ecd890e3cf54a0586247b9a384702703277e4fd Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Wed, 21 Jan 2026 23:45:19 -0800 Subject: [PATCH 26/41] PCI: dwc: Rename dw_pcie_rp::has_msi_ctrl to dw_pcie_rp::use_imsi_rx for clarity The current "has_msi_ctrl" flag name is misleading because it suggests the presence of any MSI controller, while it is specifically set for platforms that lack .msi_init() callback and don't have "msi-parent" or "msi-map" device tree properties, indicating they rely on the iMSI-RX module for MSI functionality. Rename it to "use_imsi_rx" to make the intent clear: - When true: Platform uses the iMSI-RX module for MSI handling - When false: Platform has other MSI controller support (ITS/MBI, external MSI controller) No functional changes, only improves code readability and eliminates naming confusion. Signed-off-by: Qiang Yu [mani: renamed 'uses_imsi_rx' to 'use_imsi_rx' per https://lore.kernel.org/linux-pci/09f9acc1-d1ad-4971-8488-f0268cf08799@rock-chips.com] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Shawn Lin Link: https://patch.msgid.link/20260121-remove_cap_clean_up-v1-2-e78115e5d467@oss.qualcomm.com --- .../pci/controller/dwc/pcie-designware-host.c | 16 ++++++++-------- drivers/pci/controller/dwc/pcie-designware.h | 2 +- drivers/pci/controller/dwc/pcie-qcom.c | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index ab17549af518..b3d6a474fd16 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -255,7 +255,7 @@ void dw_pcie_msi_init(struct dw_pcie_rp *pp) u64 msi_target = (u64)pp->msi_data; u32 ctrl, num_ctrls; - if (!pci_msi_enabled() || !pp->has_msi_ctrl) + if (!pci_msi_enabled() || !pp->use_imsi_rx) return; num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL; @@ -603,15 +603,15 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) } if (pci_msi_enabled()) { - pp->has_msi_ctrl = !(pp->ops->msi_init || + pp->use_imsi_rx = !(pp->ops->msi_init || of_property_present(np, "msi-parent") || of_property_present(np, "msi-map")); /* - * For the has_msi_ctrl case the default assignment is handled + * For the use_imsi_rx case the default assignment is handled * in the dw_pcie_msi_host_init(). */ - if (!pp->has_msi_ctrl && !pp->num_vectors) { + if (!pp->use_imsi_rx && !pp->num_vectors) { pp->num_vectors = MSI_DEF_NUM_VECTORS; } else if (pp->num_vectors > MAX_MSI_IRQS) { dev_err(dev, "Invalid number of vectors\n"); @@ -623,7 +623,7 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) ret = pp->ops->msi_init(pp); if (ret < 0) goto err_deinit_host; - } else if (pp->has_msi_ctrl) { + } else if (pp->use_imsi_rx) { ret = dw_pcie_msi_host_init(pp); if (ret < 0) goto err_deinit_host; @@ -701,7 +701,7 @@ err_remove_edma: dw_pcie_edma_remove(pci); err_free_msi: - if (pp->has_msi_ctrl) + if (pp->use_imsi_rx) dw_pcie_free_msi(pp); err_deinit_host: @@ -729,7 +729,7 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp) dw_pcie_edma_remove(pci); - if (pp->has_msi_ctrl) + if (pp->use_imsi_rx) dw_pcie_free_msi(pp); if (pp->ops->deinit) @@ -1170,7 +1170,7 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp) * the MSI and MSI-X capabilities of the Root Port to allow the drivers * to fall back to INTx instead. */ - if (pp->has_msi_ctrl) { + if (pp->use_imsi_rx) { dw_pcie_remove_capability(pci, PCI_CAP_ID_MSI); dw_pcie_remove_capability(pci, PCI_CAP_ID_MSIX); } diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 53b65428fadb..c3301b3aedb7 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -416,7 +416,7 @@ struct dw_pcie_host_ops { }; struct dw_pcie_rp { - bool has_msi_ctrl:1; + bool use_imsi_rx:1; bool cfg0_io_shared:1; u64 cfg0_base; void __iomem *va_cfg0_base; diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 13e6c334e10d..b65b170f02c1 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1633,7 +1633,7 @@ static void qcom_pci_free_msi(void *ptr) { struct dw_pcie_rp *pp = (struct dw_pcie_rp *)ptr; - if (pp && pp->has_msi_ctrl) + if (pp && pp->use_imsi_rx) dw_pcie_free_msi(pp); } @@ -1657,7 +1657,7 @@ static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg) if (ret) return ret; - pp->has_msi_ctrl = true; + pp->use_imsi_rx = true; dw_pcie_msi_init(pp); return devm_add_action_or_reset(dev, qcom_pci_free_msi, pp); From 06a81c5940e46cc7bddee28f16bdd29a12a76344 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:05 +0900 Subject: [PATCH 27/41] PCI: endpoint: Add dynamic_inbound_mapping EPC feature Introduce a new EPC feature bit (dynamic_inbound_mapping) that indicates whether an Endpoint Controller can update the inbound address translation for a BAR without requiring the EPF driver to clear/reset the BAR first. Endpoint Function drivers (e.g. vNTB) can use this information to decide whether it really is safe to call pci_epc_set_bar() multiple times to update inbound mappings for the BAR. Suggested-by: Niklas Cassel Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260124145012.2794108-2-den@valinux.co.jp --- include/linux/pci-epc.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 4286bfdbfdfa..4c8516756c56 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -223,6 +223,10 @@ struct pci_epc_bar_desc { /** * struct pci_epc_features - features supported by a EPC device per function * @linkup_notifier: indicate if the EPC device can notify EPF driver on link up + * @dynamic_inbound_mapping: indicate if the EPC device supports updating + * inbound mappings for an already configured BAR + * (i.e. allow calling pci_epc_set_bar() again + * without first calling pci_epc_clear_bar()) * @msi_capable: indicate if the endpoint function has MSI capability * @msix_capable: indicate if the endpoint function has MSI-X capability * @intx_capable: indicate if the endpoint can raise INTx interrupts @@ -231,6 +235,7 @@ struct pci_epc_bar_desc { */ struct pci_epc_features { unsigned int linkup_notifier : 1; + unsigned int dynamic_inbound_mapping : 1; unsigned int msi_capable : 1; unsigned int msix_capable : 1; unsigned int intx_capable : 1; From 31fb95400451040050361e22ff480476964280f0 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:06 +0900 Subject: [PATCH 28/41] PCI: endpoint: Add BAR subrange mapping support Some endpoint platforms have only a small number of usable BARs. At the same time, EPF drivers (e.g. vNTB) may need multiple independent inbound regions (control/scratchpad, one or more memory windows, and optionally MSI or other feature-related regions). Subrange mapping allows these to share a single BAR without consuming additional BARs that may not be available, or forcing a fragile layout by aggressively packing into a single contiguous memory range. Extend the PCI endpoint core to support mapping subranges within a BAR. Add an optional 'submap' field in struct pci_epf_bar so an endpoint function driver can request inbound mappings that fully cover the BAR. Introduce a new EPC feature bit, subrange_mapping, and reject submap requests from pci_epc_set_bar() unless the controller advertises both subrange_mapping and dynamic_inbound_mapping features. The submap array describes the complete BAR layout (no overlaps and no gaps are allowed to avoid exposing untranslated address ranges). This provides the generic infrastructure needed to map multiple logical regions into a single BAR at different offsets, without assuming a controller-specific inbound address translation mechanism. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260124145012.2794108-3-den@valinux.co.jp --- drivers/pci/endpoint/pci-epc-core.c | 8 ++++++++ include/linux/pci-epc.h | 4 ++++ include/linux/pci-epf.h | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index ca7f19cc973a..068155819c57 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -596,6 +596,14 @@ int pci_epc_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, if (!epc_features) return -EINVAL; + if (epf_bar->num_submap && !epf_bar->submap) + return -EINVAL; + + if (epf_bar->num_submap && + !(epc_features->dynamic_inbound_mapping && + epc_features->subrange_mapping)) + return -EINVAL; + if (epc_features->bar[bar].type == BAR_RESIZABLE && (epf_bar->size < SZ_1M || (u64)epf_bar->size > (SZ_128G * 1024))) return -EINVAL; diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 4c8516756c56..c021c7af175f 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -227,6 +227,9 @@ struct pci_epc_bar_desc { * inbound mappings for an already configured BAR * (i.e. allow calling pci_epc_set_bar() again * without first calling pci_epc_clear_bar()) + * @subrange_mapping: indicate if the EPC device can map inbound subranges for a + * BAR. This feature depends on @dynamic_inbound_mapping + * feature. * @msi_capable: indicate if the endpoint function has MSI capability * @msix_capable: indicate if the endpoint function has MSI-X capability * @intx_capable: indicate if the endpoint can raise INTx interrupts @@ -236,6 +239,7 @@ struct pci_epc_bar_desc { struct pci_epc_features { unsigned int linkup_notifier : 1; unsigned int dynamic_inbound_mapping : 1; + unsigned int subrange_mapping : 1; unsigned int msi_capable : 1; unsigned int msix_capable : 1; unsigned int intx_capable : 1; diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index 48f68c4dcfa5..7737a7c03260 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -110,6 +110,22 @@ struct pci_epf_driver { #define to_pci_epf_driver(drv) container_of_const((drv), struct pci_epf_driver, driver) +/** + * struct pci_epf_bar_submap - BAR subrange for inbound mapping + * @phys_addr: target physical/DMA address for this subrange + * @size: the size of the subrange to be mapped + * + * When pci_epf_bar.num_submap is >0, pci_epf_bar.submap describes the + * complete BAR layout. This allows an EPC driver to program multiple + * inbound translation windows for a single BAR when supported by the + * controller. The array order defines the BAR layout (submap[0] at offset + * 0, and each immediately follows the previous one). + */ +struct pci_epf_bar_submap { + dma_addr_t phys_addr; + size_t size; +}; + /** * struct pci_epf_bar - represents the BAR of EPF device * @phys_addr: physical address that should be mapped to the BAR @@ -119,6 +135,9 @@ struct pci_epf_driver { * requirement * @barno: BAR number * @flags: flags that are set for the BAR + * @num_submap: number of entries in @submap + * @submap: array of subrange descriptors allocated by the caller. See + * struct pci_epf_bar_submap for the semantics in detail. */ struct pci_epf_bar { dma_addr_t phys_addr; @@ -127,6 +146,10 @@ struct pci_epf_bar { size_t mem_size; enum pci_barno barno; int flags; + + /* Optional sub-range mapping */ + unsigned int num_submap; + struct pci_epf_bar_submap *submap; }; /** From c0f1506f63546308e894469ceb0f1fadbdf9d2f9 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:07 +0900 Subject: [PATCH 29/41] PCI: dwc: Advertise dynamic inbound mapping support The DesignWare EP core has supported updating the inbound iATU mapping for an already configured BAR (i.e. allowing pci_epc_set_bar() to be called again without a prior pci_epc_clear_bar()) since commit 4284c88fff0e ("PCI: designware-ep: Allow pci_epc_set_bar() update inbound map address"). Now that this capability is exposed via the dynamic_inbound_mapping EPC feature bit, set it for DWC-based EP glue drivers using a common initializer macro to avoid duplicating the same flag in each driver. Note that pci-layerscape-ep.c is untouched. It currently constructs the feature struct dynamically in ls_pcie_ep_init(). Once converted to a static feature definition, it will use DWC_EPC_COMMON_FEATURES as well. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260124145012.2794108-4-den@valinux.co.jp --- drivers/pci/controller/dwc/pci-dra7xx.c | 1 + drivers/pci/controller/dwc/pci-imx6.c | 3 +++ drivers/pci/controller/dwc/pci-keystone.c | 1 + drivers/pci/controller/dwc/pcie-artpec6.c | 1 + drivers/pci/controller/dwc/pcie-designware-plat.c | 1 + drivers/pci/controller/dwc/pcie-designware.h | 3 +++ drivers/pci/controller/dwc/pcie-dw-rockchip.c | 2 ++ drivers/pci/controller/dwc/pcie-keembay.c | 1 + drivers/pci/controller/dwc/pcie-qcom-ep.c | 1 + drivers/pci/controller/dwc/pcie-rcar-gen4.c | 1 + drivers/pci/controller/dwc/pcie-stm32-ep.c | 1 + drivers/pci/controller/dwc/pcie-tegra194.c | 1 + drivers/pci/controller/dwc/pcie-uniphier-ep.c | 2 ++ 13 files changed, 19 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c index 01cfd9aeb0b8..d5d26229063f 100644 --- a/drivers/pci/controller/dwc/pci-dra7xx.c +++ b/drivers/pci/controller/dwc/pci-dra7xx.c @@ -424,6 +424,7 @@ static int dra7xx_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features dra7xx_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .linkup_notifier = true, .msi_capable = true, }; diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index dfe814469993..06f45e009d7d 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -1388,6 +1388,7 @@ static int imx_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features imx8m_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .bar[BAR_1] = { .type = BAR_RESERVED, }, .bar[BAR_3] = { .type = BAR_RESERVED, }, @@ -1397,6 +1398,7 @@ static const struct pci_epc_features imx8m_pcie_epc_features = { }; static const struct pci_epc_features imx8q_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .bar[BAR_1] = { .type = BAR_RESERVED, }, .bar[BAR_3] = { .type = BAR_RESERVED, }, @@ -1417,6 +1419,7 @@ static const struct pci_epc_features imx8q_pcie_epc_features = { * BAR5 | Enable | 32-bit | 64 KB | Programmable Size */ static const struct pci_epc_features imx95_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .bar[BAR_1] = { .type = BAR_FIXED, .fixed_size = SZ_64K, }, .align = SZ_4K, diff --git a/drivers/pci/controller/dwc/pci-keystone.c b/drivers/pci/controller/dwc/pci-keystone.c index f86d9111f863..20fa4dadb82a 100644 --- a/drivers/pci/controller/dwc/pci-keystone.c +++ b/drivers/pci/controller/dwc/pci-keystone.c @@ -930,6 +930,7 @@ static int ks_pcie_am654_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features ks_pcie_am654_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .msix_capable = true, .bar[BAR_0] = { .type = BAR_RESERVED, }, diff --git a/drivers/pci/controller/dwc/pcie-artpec6.c b/drivers/pci/controller/dwc/pcie-artpec6.c index f4a136ee2daf..e994b75986c3 100644 --- a/drivers/pci/controller/dwc/pcie-artpec6.c +++ b/drivers/pci/controller/dwc/pcie-artpec6.c @@ -370,6 +370,7 @@ static int artpec6_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features artpec6_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, }; diff --git a/drivers/pci/controller/dwc/pcie-designware-plat.c b/drivers/pci/controller/dwc/pcie-designware-plat.c index 12f41886c65d..8530746ec5cb 100644 --- a/drivers/pci/controller/dwc/pcie-designware-plat.c +++ b/drivers/pci/controller/dwc/pcie-designware-plat.c @@ -61,6 +61,7 @@ static int dw_plat_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features dw_plat_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .msix_capable = true, }; diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index c3301b3aedb7..7ca9d0f6b7f2 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -305,6 +305,9 @@ /* Default eDMA LLP memory size */ #define DMA_LLP_MEM_SIZE PAGE_SIZE +/* Common struct pci_epc_feature bits among DWC EP glue drivers */ +#define DWC_EPC_COMMON_FEATURES .dynamic_inbound_mapping = true + struct dw_pcie; struct dw_pcie_rp; struct dw_pcie_ep; diff --git a/drivers/pci/controller/dwc/pcie-dw-rockchip.c b/drivers/pci/controller/dwc/pcie-dw-rockchip.c index 77c4e6a4ddea..03ad8c242366 100644 --- a/drivers/pci/controller/dwc/pcie-dw-rockchip.c +++ b/drivers/pci/controller/dwc/pcie-dw-rockchip.c @@ -382,6 +382,7 @@ static int rockchip_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features rockchip_pcie_epc_features_rk3568 = { + DWC_EPC_COMMON_FEATURES, .linkup_notifier = true, .msi_capable = true, .msix_capable = true, @@ -402,6 +403,7 @@ static const struct pci_epc_features rockchip_pcie_epc_features_rk3568 = { * BARs) would be overwritten, resulting in (all other BARs) no longer working. */ static const struct pci_epc_features rockchip_pcie_epc_features_rk3588 = { + DWC_EPC_COMMON_FEATURES, .linkup_notifier = true, .msi_capable = true, .msix_capable = true, diff --git a/drivers/pci/controller/dwc/pcie-keembay.c b/drivers/pci/controller/dwc/pcie-keembay.c index 60e74ac782af..2666a9c3d67e 100644 --- a/drivers/pci/controller/dwc/pcie-keembay.c +++ b/drivers/pci/controller/dwc/pcie-keembay.c @@ -309,6 +309,7 @@ static int keembay_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features keembay_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .msix_capable = true, .bar[BAR_0] = { .only_64bit = true, }, diff --git a/drivers/pci/controller/dwc/pcie-qcom-ep.c b/drivers/pci/controller/dwc/pcie-qcom-ep.c index f1bc0ac81a92..5e990c7a5879 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-ep.c +++ b/drivers/pci/controller/dwc/pcie-qcom-ep.c @@ -820,6 +820,7 @@ static void qcom_pcie_ep_init_debugfs(struct qcom_pcie_ep *pcie_ep) } static const struct pci_epc_features qcom_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .linkup_notifier = true, .msi_capable = true, .align = SZ_4K, diff --git a/drivers/pci/controller/dwc/pcie-rcar-gen4.c b/drivers/pci/controller/dwc/pcie-rcar-gen4.c index 80778917d2dd..a6912e85e4dd 100644 --- a/drivers/pci/controller/dwc/pcie-rcar-gen4.c +++ b/drivers/pci/controller/dwc/pcie-rcar-gen4.c @@ -420,6 +420,7 @@ static int rcar_gen4_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features rcar_gen4_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .bar[BAR_1] = { .type = BAR_RESERVED, }, .bar[BAR_3] = { .type = BAR_RESERVED, }, diff --git a/drivers/pci/controller/dwc/pcie-stm32-ep.c b/drivers/pci/controller/dwc/pcie-stm32-ep.c index 2cecf32d2b0f..c1944b40ce02 100644 --- a/drivers/pci/controller/dwc/pcie-stm32-ep.c +++ b/drivers/pci/controller/dwc/pcie-stm32-ep.c @@ -70,6 +70,7 @@ static int stm32_pcie_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features stm32_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .msi_capable = true, .align = SZ_64K, }; diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 0ddeef70726d..06571d806ab3 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -1988,6 +1988,7 @@ static int tegra_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no, } static const struct pci_epc_features tegra_pcie_epc_features = { + DWC_EPC_COMMON_FEATURES, .linkup_notifier = true, .msi_capable = true, .bar[BAR_0] = { .type = BAR_FIXED, .fixed_size = SZ_1M, diff --git a/drivers/pci/controller/dwc/pcie-uniphier-ep.c b/drivers/pci/controller/dwc/pcie-uniphier-ep.c index d6e73811216e..d52753060970 100644 --- a/drivers/pci/controller/dwc/pcie-uniphier-ep.c +++ b/drivers/pci/controller/dwc/pcie-uniphier-ep.c @@ -420,6 +420,7 @@ static const struct uniphier_pcie_ep_soc_data uniphier_pro5_data = { .init = uniphier_pcie_pro5_init_ep, .wait = NULL, .features = { + DWC_EPC_COMMON_FEATURES, .linkup_notifier = false, .msi_capable = true, .msix_capable = false, @@ -438,6 +439,7 @@ static const struct uniphier_pcie_ep_soc_data uniphier_nx1_data = { .init = uniphier_pcie_nx1_init_ep, .wait = uniphier_pcie_nx1_wait_ep, .features = { + DWC_EPC_COMMON_FEATURES, .linkup_notifier = false, .msi_capable = true, .msix_capable = false, From cc839bef7727043a66004bba563492957ca3e531 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:08 +0900 Subject: [PATCH 30/41] PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU Extend dw_pcie_ep_set_bar() to support inbound mappings for BAR subranges using Address Match Mode IB iATU when pci_epf_bar.num_submap is non-zero. Rename the existing BAR-match helper into dw_pcie_ep_ib_atu_bar() and introduce dw_pcie_ep_ib_atu_addr() for Address Match Mode. When num_submap is non-zero, read the assigned BAR base address and program one inbound iATU window per subrange. Validate the submap array before programming: - each subrange is aligned to pci->region_align - subranges cover the whole BAR (no gaps and no overlaps) Track Address Match Mode mappings and tear them down on clear_bar() and on set_bar() error paths to avoid leaving half-programmed state or untranslated BAR holes. Advertise this capability by extending the common feature bit initializer macro (DWC_EPC_COMMON_FEATURES). This enables multiple inbound windows within a single BAR, which is useful on platforms where usable BARs are scarce but EPFs need multiple inbound regions. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260124145012.2794108-5-den@valinux.co.jp --- .../pci/controller/dwc/pcie-designware-ep.c | 213 +++++++++++++++++- drivers/pci/controller/dwc/pcie-designware.h | 7 +- 2 files changed, 209 insertions(+), 11 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index cfd59899c7b8..855b2e58c338 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -100,9 +100,10 @@ static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no, return 0; } -static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no, int type, - dma_addr_t parent_bus_addr, enum pci_barno bar, - size_t size) +/* BAR Match Mode inbound iATU mapping */ +static int dw_pcie_ep_ib_atu_bar(struct dw_pcie_ep *ep, u8 func_no, int type, + dma_addr_t parent_bus_addr, enum pci_barno bar, + size_t size) { int ret; u32 free_win; @@ -135,6 +136,179 @@ static int dw_pcie_ep_inbound_atu(struct dw_pcie_ep *ep, u8 func_no, int type, return 0; } +static void dw_pcie_ep_clear_ib_maps(struct dw_pcie_ep *ep, enum pci_barno bar) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + struct device *dev = pci->dev; + unsigned int i, num; + u32 atu_index; + u32 *indexes; + + /* Tear down the BAR Match Mode mapping, if any. */ + if (ep->bar_to_atu[bar]) { + atu_index = ep->bar_to_atu[bar] - 1; + dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, atu_index); + clear_bit(atu_index, ep->ib_window_map); + ep->bar_to_atu[bar] = 0; + } + + /* Tear down all Address Match Mode mappings, if any. */ + indexes = ep->ib_atu_indexes[bar]; + num = ep->num_ib_atu_indexes[bar]; + ep->ib_atu_indexes[bar] = NULL; + ep->num_ib_atu_indexes[bar] = 0; + if (!indexes) + return; + for (i = 0; i < num; i++) { + dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, indexes[i]); + clear_bit(indexes[i], ep->ib_window_map); + } + devm_kfree(dev, indexes); +} + +static u64 dw_pcie_ep_read_bar_assigned(struct dw_pcie_ep *ep, u8 func_no, + enum pci_barno bar, int flags) +{ + u32 reg = PCI_BASE_ADDRESS_0 + (4 * bar); + u32 lo, hi; + u64 addr; + + lo = dw_pcie_ep_readl_dbi(ep, func_no, reg); + + if (flags & PCI_BASE_ADDRESS_SPACE) + return lo & PCI_BASE_ADDRESS_IO_MASK; + + addr = lo & PCI_BASE_ADDRESS_MEM_MASK; + if (!(flags & PCI_BASE_ADDRESS_MEM_TYPE_64)) + return addr; + + hi = dw_pcie_ep_readl_dbi(ep, func_no, reg + 4); + return addr | ((u64)hi << 32); +} + +static int dw_pcie_ep_validate_submap(struct dw_pcie_ep *ep, + const struct pci_epf_bar_submap *submap, + unsigned int num_submap, size_t bar_size) +{ + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + u32 align = pci->region_align; + size_t off = 0; + unsigned int i; + size_t size; + + if (!align || !IS_ALIGNED(bar_size, align)) + return -EINVAL; + + /* + * The submap array order defines the BAR layout (submap[0] starts + * at offset 0 and each entry immediately follows the previous + * one). Here, validate that it forms a strict, gapless + * decomposition of the BAR: + * - each entry has a non-zero size + * - sizes, implicit offsets and phys_addr are aligned to + * pci->region_align + * - each entry lies within the BAR range + * - the entries exactly cover the whole BAR + * + * Note: dw_pcie_prog_inbound_atu() also checks alignment for the + * PCI address and the target phys_addr, but validating up-front + * avoids partially programming iATU windows in vain. + */ + for (i = 0; i < num_submap; i++) { + size = submap[i].size; + + if (!size) + return -EINVAL; + + if (!IS_ALIGNED(size, align) || !IS_ALIGNED(off, align)) + return -EINVAL; + + if (!IS_ALIGNED(submap[i].phys_addr, align)) + return -EINVAL; + + if (off > bar_size || size > bar_size - off) + return -EINVAL; + + off += size; + } + if (off != bar_size) + return -EINVAL; + + return 0; +} + +/* Address Match Mode inbound iATU mapping */ +static int dw_pcie_ep_ib_atu_addr(struct dw_pcie_ep *ep, u8 func_no, int type, + const struct pci_epf_bar *epf_bar) +{ + const struct pci_epf_bar_submap *submap = epf_bar->submap; + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + enum pci_barno bar = epf_bar->barno; + struct device *dev = pci->dev; + u64 pci_addr, parent_bus_addr; + u64 size, base, off = 0; + int free_win, ret; + unsigned int i; + u32 *indexes; + + if (!epf_bar->num_submap || !submap || !epf_bar->size) + return -EINVAL; + + ret = dw_pcie_ep_validate_submap(ep, submap, epf_bar->num_submap, + epf_bar->size); + if (ret) + return ret; + + base = dw_pcie_ep_read_bar_assigned(ep, func_no, bar, epf_bar->flags); + if (!base) { + dev_err(dev, + "BAR%u not assigned, cannot set up sub-range mappings\n", + bar); + return -EINVAL; + } + + indexes = devm_kcalloc(dev, epf_bar->num_submap, sizeof(*indexes), + GFP_KERNEL); + if (!indexes) + return -ENOMEM; + + ep->ib_atu_indexes[bar] = indexes; + ep->num_ib_atu_indexes[bar] = 0; + + for (i = 0; i < epf_bar->num_submap; i++) { + size = submap[i].size; + parent_bus_addr = submap[i].phys_addr; + + if (off > (~0ULL) - base) { + ret = -EINVAL; + goto err; + } + + pci_addr = base + off; + off += size; + + free_win = find_first_zero_bit(ep->ib_window_map, + pci->num_ib_windows); + if (free_win >= pci->num_ib_windows) { + ret = -ENOSPC; + goto err; + } + + ret = dw_pcie_prog_inbound_atu(pci, free_win, type, + parent_bus_addr, pci_addr, size); + if (ret) + goto err; + + set_bit(free_win, ep->ib_window_map); + indexes[i] = free_win; + ep->num_ib_atu_indexes[bar] = i + 1; + } + return 0; +err: + dw_pcie_ep_clear_ib_maps(ep, bar); + return ret; +} + static int dw_pcie_ep_outbound_atu(struct dw_pcie_ep *ep, struct dw_pcie_ob_atu_cfg *atu) { @@ -165,17 +339,15 @@ static void dw_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct dw_pcie_ep *ep = epc_get_drvdata(epc); struct dw_pcie *pci = to_dw_pcie_from_ep(ep); enum pci_barno bar = epf_bar->barno; - u32 atu_index = ep->bar_to_atu[bar] - 1; - if (!ep->bar_to_atu[bar]) + if (!ep->epf_bar[bar]) return; __dw_pcie_ep_reset_bar(pci, func_no, bar, epf_bar->flags); - dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, atu_index); - clear_bit(atu_index, ep->ib_window_map); + dw_pcie_ep_clear_ib_maps(ep, bar); + ep->epf_bar[bar] = NULL; - ep->bar_to_atu[bar] = 0; } static unsigned int dw_pcie_ep_get_rebar_offset(struct dw_pcie *pci, @@ -331,11 +503,28 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, ep->epf_bar[bar]->flags != flags) return -EINVAL; + /* + * When dynamically changing a BAR, tear down any existing + * mappings before re-programming. + */ + if (ep->epf_bar[bar]->num_submap || epf_bar->num_submap) + dw_pcie_ep_clear_ib_maps(ep, bar); + /* * When dynamically changing a BAR, skip writing the BAR reg, as * that would clear the BAR's PCI address assigned by the host. */ goto config_atu; + } else { + /* + * Subrange mapping is an update-only operation. The BAR + * must have been configured once without submaps so that + * subsequent set_bar() calls can update inbound mappings + * without touching the BAR register (and clobbering the + * host-assigned address). + */ + if (epf_bar->num_submap) + return -EINVAL; } bar_type = dw_pcie_ep_get_bar_type(ep, bar); @@ -369,8 +558,12 @@ config_atu: else type = PCIE_ATU_TYPE_IO; - ret = dw_pcie_ep_inbound_atu(ep, func_no, type, epf_bar->phys_addr, bar, - size); + if (epf_bar->num_submap) + ret = dw_pcie_ep_ib_atu_addr(ep, func_no, type, epf_bar); + else + ret = dw_pcie_ep_ib_atu_bar(ep, func_no, type, + epf_bar->phys_addr, bar, size); + if (ret) return ret; diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 7ca9d0f6b7f2..8f170122ad78 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -306,7 +306,8 @@ #define DMA_LLP_MEM_SIZE PAGE_SIZE /* Common struct pci_epc_feature bits among DWC EP glue drivers */ -#define DWC_EPC_COMMON_FEATURES .dynamic_inbound_mapping = true +#define DWC_EPC_COMMON_FEATURES .dynamic_inbound_mapping = true, \ + .subrange_mapping = true struct dw_pcie; struct dw_pcie_rp; @@ -487,6 +488,10 @@ struct dw_pcie_ep { phys_addr_t msi_mem_phys; struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS]; + /* Only for Address Match Mode inbound iATU */ + u32 *ib_atu_indexes[PCI_STD_NUM_BARS]; + unsigned int num_ib_atu_indexes[PCI_STD_NUM_BARS]; + /* MSI outbound iATU state */ bool msi_iatu_mapped; u64 msi_msg_addr; From dd3ce1667a99d3ecfb1ad3b619140350796e01c0 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:09 +0900 Subject: [PATCH 31/41] Documentation: PCI: endpoint: Clarify pci_epc_set_bar() usage The current documentation implies that pci_epc_set_bar() is only used before the host enumerates the endpoint. In practice, some Endpoint Controllers support calling pci_epc_set_bar() multiple times for the same BAR (without clearing it) in order to update inbound address translations after the host has programmed the BAR base address, which some Endpoint Functions such as vNTB already rely on. Add document text for that. Also document the expected call flow for BAR subrange mapping (pci_epf_bar.num_submap / pci_epf_bar.submap), which may require a second pci_epc_set_bar() call after the host has programmed the BAR base address. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260124145012.2794108-6-den@valinux.co.jp --- Documentation/PCI/endpoint/pci-endpoint.rst | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Documentation/PCI/endpoint/pci-endpoint.rst b/Documentation/PCI/endpoint/pci-endpoint.rst index 0741c8cbd74e..4697377adeae 100644 --- a/Documentation/PCI/endpoint/pci-endpoint.rst +++ b/Documentation/PCI/endpoint/pci-endpoint.rst @@ -95,6 +95,30 @@ by the PCI endpoint function driver. Register space of the function driver is usually configured using this API. + Some endpoint controllers also support calling pci_epc_set_bar() again + for the same BAR (without calling pci_epc_clear_bar()) to update inbound + address translations after the host has programmed the BAR base address. + Endpoint function drivers can check this capability via the + dynamic_inbound_mapping EPC feature bit. + + When pci_epf_bar.num_submap is non-zero, the endpoint function driver is + requesting BAR subrange mapping using pci_epf_bar.submap. This requires + the EPC to advertise support via the subrange_mapping EPC feature bit. + + When an EPF driver wants to make use of the inbound subrange mapping + feature, it requires that the BAR base address has been programmed by + the host during enumeration. Thus, it needs to call pci_epc_set_bar() + twice for the same BAR (requires dynamic_inbound_mapping): first with + num_submap set to zero and configuring the BAR size, then after the PCIe + link is up and the host enumerates the endpoint and programs the BAR + base address, again with num_submap set to non-zero value. + + Note that when making use of the inbound subrange mapping feature, the + EPF driver must not call pci_epc_clear_bar() between the two + pci_epc_set_bar() calls, because clearing the BAR can clear/disable the + BAR register or BAR decode on the endpoint while the host still expects + the assigned BAR address to remain valid. + * pci_epc_clear_bar() The PCI endpoint function driver should use pci_epc_clear_bar() to reset From 6c5e6101423b0dbda417d92d1552a4e2c669a76c Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:10 +0900 Subject: [PATCH 32/41] PCI: endpoint: pci-epf-test: Add BAR subrange mapping test support Extend pci-epf-test so that pci_endpoint_test can exercise BAR subrange mapping end-to-end. Add BAR_SUBRANGE_SETUP/CLEAR commands that program (and tear down) a simple 2-subrange layout for a selected BAR. The endpoint deliberately permutes the physical backing regions (swap the halves) and writes a deterministic signature byte per subrange. This allows the RC to verify that the submap order is actually applied, not just that reads/writes work with an identity mapping. Advertise CAP_SUBRANGE_MAPPING only when the underlying EPC supports dynamic_inbound_mapping and subrange_mapping. Also bump the default BAR sizes (BAR0-4) to 128 KiB so that split subranges are large enough to satisfy common inbound-translation alignment constraints. E.g. for DWC EP, the default and maximum CX_ATU_MIN_REGION_SIZE is 64 kB, so 128 KiB is sufficient for DWC-based EP platforms for 2-subrange testing. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260124145012.2794108-7-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-test.c | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index debd235253c5..1cc630a2ee75 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -33,6 +33,8 @@ #define COMMAND_COPY BIT(5) #define COMMAND_ENABLE_DOORBELL BIT(6) #define COMMAND_DISABLE_DOORBELL BIT(7) +#define COMMAND_BAR_SUBRANGE_SETUP BIT(8) +#define COMMAND_BAR_SUBRANGE_CLEAR BIT(9) #define STATUS_READ_SUCCESS BIT(0) #define STATUS_READ_FAIL BIT(1) @@ -48,6 +50,10 @@ #define STATUS_DOORBELL_ENABLE_FAIL BIT(11) #define STATUS_DOORBELL_DISABLE_SUCCESS BIT(12) #define STATUS_DOORBELL_DISABLE_FAIL BIT(13) +#define STATUS_BAR_SUBRANGE_SETUP_SUCCESS BIT(14) +#define STATUS_BAR_SUBRANGE_SETUP_FAIL BIT(15) +#define STATUS_BAR_SUBRANGE_CLEAR_SUCCESS BIT(16) +#define STATUS_BAR_SUBRANGE_CLEAR_FAIL BIT(17) #define FLAG_USE_DMA BIT(0) @@ -57,6 +63,9 @@ #define CAP_MSI BIT(1) #define CAP_MSIX BIT(2) #define CAP_INTX BIT(3) +#define CAP_SUBRANGE_MAPPING BIT(4) + +#define PCI_EPF_TEST_BAR_SUBRANGE_NSUB 2 static struct workqueue_struct *kpcitest_workqueue; @@ -102,7 +111,7 @@ static struct pci_epf_header test_header = { .interrupt_pin = PCI_INTERRUPT_INTA, }; -static size_t bar_size[] = { 512, 512, 1024, 16384, 131072, 1048576 }; +static size_t bar_size[] = { 131072, 131072, 131072, 131072, 131072, 1048576 }; static void pci_epf_test_dma_callback(void *param) { @@ -806,6 +815,155 @@ set_status_err: reg->status = cpu_to_le32(status); } +static u8 pci_epf_test_subrange_sig_byte(enum pci_barno barno, + unsigned int subno) +{ + return 0x50 + (barno * 8) + subno; +} + +static void pci_epf_test_bar_subrange_setup(struct pci_epf_test *epf_test, + struct pci_epf_test_reg *reg) +{ + struct pci_epf_bar_submap *submap, *old_submap; + struct pci_epf *epf = epf_test->epf; + struct pci_epc *epc = epf->epc; + struct pci_epf_bar *bar; + unsigned int nsub = PCI_EPF_TEST_BAR_SUBRANGE_NSUB, old_nsub; + /* reg->size carries BAR number for BAR_SUBRANGE_* commands. */ + enum pci_barno barno = le32_to_cpu(reg->size); + u32 status = le32_to_cpu(reg->status); + unsigned int i, phys_idx; + size_t sub_size; + u8 *addr; + int ret; + + if (barno >= PCI_STD_NUM_BARS) { + dev_err(&epf->dev, "Invalid barno: %d\n", barno); + goto err; + } + + /* Host side should've avoided test_reg_bar, this is a safeguard. */ + if (barno == epf_test->test_reg_bar) { + dev_err(&epf->dev, "test_reg_bar cannot be used for subrange test\n"); + goto err; + } + + if (!epf_test->epc_features->dynamic_inbound_mapping || + !epf_test->epc_features->subrange_mapping) { + dev_err(&epf->dev, "epc driver does not support subrange mapping\n"); + goto err; + } + + bar = &epf->bar[barno]; + if (!bar->size || !bar->addr) { + dev_err(&epf->dev, "bar size/addr (%zu/%p) is invalid\n", + bar->size, bar->addr); + goto err; + } + + if (bar->size % nsub) { + dev_err(&epf->dev, "BAR size %zu is not divisible by %u\n", + bar->size, nsub); + goto err; + } + + sub_size = bar->size / nsub; + + submap = kcalloc(nsub, sizeof(*submap), GFP_KERNEL); + if (!submap) + goto err; + + for (i = 0; i < nsub; i++) { + /* Swap the two halves so RC can verify ordering. */ + phys_idx = i ^ 1; + submap[i].phys_addr = bar->phys_addr + (phys_idx * sub_size); + submap[i].size = sub_size; + } + + old_submap = bar->submap; + old_nsub = bar->num_submap; + + bar->submap = submap; + bar->num_submap = nsub; + + ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, bar); + if (ret) { + dev_err(&epf->dev, "pci_epc_set_bar() failed: %d\n", ret); + bar->submap = old_submap; + bar->num_submap = old_nsub; + kfree(submap); + goto err; + } + kfree(old_submap); + + /* + * Fill deterministic signatures into the physical regions that + * each BAR subrange maps to. RC verifies these to ensure the + * submap order is really applied. + */ + addr = (u8 *)bar->addr; + for (i = 0; i < nsub; i++) { + phys_idx = i ^ 1; + memset(addr + (phys_idx * sub_size), + pci_epf_test_subrange_sig_byte(barno, i), + sub_size); + } + + status |= STATUS_BAR_SUBRANGE_SETUP_SUCCESS; + reg->status = cpu_to_le32(status); + return; + +err: + status |= STATUS_BAR_SUBRANGE_SETUP_FAIL; + reg->status = cpu_to_le32(status); +} + +static void pci_epf_test_bar_subrange_clear(struct pci_epf_test *epf_test, + struct pci_epf_test_reg *reg) +{ + struct pci_epf *epf = epf_test->epf; + struct pci_epf_bar_submap *submap; + struct pci_epc *epc = epf->epc; + /* reg->size carries BAR number for BAR_SUBRANGE_* commands. */ + enum pci_barno barno = le32_to_cpu(reg->size); + u32 status = le32_to_cpu(reg->status); + struct pci_epf_bar *bar; + unsigned int nsub; + int ret; + + if (barno >= PCI_STD_NUM_BARS) { + dev_err(&epf->dev, "Invalid barno: %d\n", barno); + goto err; + } + + bar = &epf->bar[barno]; + submap = bar->submap; + nsub = bar->num_submap; + + if (!submap || !nsub) + goto err; + + bar->submap = NULL; + bar->num_submap = 0; + + ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, bar); + if (ret) { + bar->submap = submap; + bar->num_submap = nsub; + dev_err(&epf->dev, "pci_epc_set_bar() failed: %d\n", ret); + goto err; + } + kfree(submap); + + status |= STATUS_BAR_SUBRANGE_CLEAR_SUCCESS; + reg->status = cpu_to_le32(status); + return; + +err: + status |= STATUS_BAR_SUBRANGE_CLEAR_FAIL; + reg->status = cpu_to_le32(status); +} + static void pci_epf_test_cmd_handler(struct work_struct *work) { u32 command; @@ -861,6 +1019,14 @@ static void pci_epf_test_cmd_handler(struct work_struct *work) pci_epf_test_disable_doorbell(epf_test, reg); pci_epf_test_raise_irq(epf_test, reg); break; + case COMMAND_BAR_SUBRANGE_SETUP: + pci_epf_test_bar_subrange_setup(epf_test, reg); + pci_epf_test_raise_irq(epf_test, reg); + break; + case COMMAND_BAR_SUBRANGE_CLEAR: + pci_epf_test_bar_subrange_clear(epf_test, reg); + pci_epf_test_raise_irq(epf_test, reg); + break; default: dev_err(dev, "Invalid command 0x%x\n", command); break; @@ -933,6 +1099,10 @@ static void pci_epf_test_set_capabilities(struct pci_epf *epf) if (epf_test->epc_features->intx_capable) caps |= CAP_INTX; + if (epf_test->epc_features->dynamic_inbound_mapping && + epf_test->epc_features->subrange_mapping) + caps |= CAP_SUBRANGE_MAPPING; + reg->caps = cpu_to_le32(caps); } From 8cf82bb558517503a81f8e3c49914c0836360aa6 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:11 +0900 Subject: [PATCH 33/41] misc: pci_endpoint_test: Add BAR subrange mapping test case Add a new PCITEST_BAR_SUBRANGE ioctl to exercise EPC BAR subrange mapping end-to-end. The test programs a simple 2-subrange layout on the endpoint (via pci-epf-test) and verifies that: - the endpoint-provided per-subrange signature bytes are observed at the expected PCIe BAR offsets, and - writes to each subrange are routed to the correct backing region (i.e. the submap order is applied rather than accidentally working due to an identity mapping). Return -EOPNOTSUPP when the endpoint does not advertise subrange mapping, -ENODATA when the BAR is disabled, and -EBUSY when the BAR is reserved for the test register space. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260124145012.2794108-8-den@valinux.co.jp --- drivers/misc/pci_endpoint_test.c | 203 ++++++++++++++++++++++++++++++- include/uapi/linux/pcitest.h | 1 + 2 files changed, 203 insertions(+), 1 deletion(-) diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index 1c0fd185114f..74ab5b5b9011 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -39,6 +39,8 @@ #define COMMAND_COPY BIT(5) #define COMMAND_ENABLE_DOORBELL BIT(6) #define COMMAND_DISABLE_DOORBELL BIT(7) +#define COMMAND_BAR_SUBRANGE_SETUP BIT(8) +#define COMMAND_BAR_SUBRANGE_CLEAR BIT(9) #define PCI_ENDPOINT_TEST_STATUS 0x8 #define STATUS_READ_SUCCESS BIT(0) @@ -55,6 +57,10 @@ #define STATUS_DOORBELL_ENABLE_FAIL BIT(11) #define STATUS_DOORBELL_DISABLE_SUCCESS BIT(12) #define STATUS_DOORBELL_DISABLE_FAIL BIT(13) +#define STATUS_BAR_SUBRANGE_SETUP_SUCCESS BIT(14) +#define STATUS_BAR_SUBRANGE_SETUP_FAIL BIT(15) +#define STATUS_BAR_SUBRANGE_CLEAR_SUCCESS BIT(16) +#define STATUS_BAR_SUBRANGE_CLEAR_FAIL BIT(17) #define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR 0x0c #define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR 0x10 @@ -77,6 +83,7 @@ #define CAP_MSI BIT(1) #define CAP_MSIX BIT(2) #define CAP_INTX BIT(3) +#define CAP_SUBRANGE_MAPPING BIT(4) #define PCI_ENDPOINT_TEST_DB_BAR 0x34 #define PCI_ENDPOINT_TEST_DB_OFFSET 0x38 @@ -100,6 +107,8 @@ #define PCI_DEVICE_ID_ROCKCHIP_RK3588 0x3588 +#define PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB 2 + static DEFINE_IDA(pci_endpoint_test_ida); #define to_endpoint_test(priv) container_of((priv), struct pci_endpoint_test, \ @@ -414,6 +423,193 @@ static int pci_endpoint_test_bars(struct pci_endpoint_test *test) return 0; } +static u8 pci_endpoint_test_subrange_sig_byte(enum pci_barno barno, + unsigned int subno) +{ + return 0x50 + (barno * 8) + subno; +} + +static u8 pci_endpoint_test_subrange_test_byte(enum pci_barno barno, + unsigned int subno) +{ + return 0xa0 + (barno * 8) + subno; +} + +static int pci_endpoint_test_bar_subrange_cmd(struct pci_endpoint_test *test, + enum pci_barno barno, u32 command, + u32 ok_bit, u32 fail_bit) +{ + struct pci_dev *pdev = test->pdev; + struct device *dev = &pdev->dev; + int irq_type = test->irq_type; + u32 status; + + if (irq_type < PCITEST_IRQ_TYPE_INTX || + irq_type > PCITEST_IRQ_TYPE_MSIX) { + dev_err(dev, "Invalid IRQ type\n"); + return -EINVAL; + } + + reinit_completion(&test->irq_raised); + + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1); + /* Reuse SIZE as a command parameter: bar number. */ + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, barno); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, command); + + if (!wait_for_completion_timeout(&test->irq_raised, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + + status = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS); + if (status & fail_bit) + return -EIO; + + if (!(status & ok_bit)) + return -EIO; + + return 0; +} + +static int pci_endpoint_test_bar_subrange_setup(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + return pci_endpoint_test_bar_subrange_cmd(test, barno, + COMMAND_BAR_SUBRANGE_SETUP, + STATUS_BAR_SUBRANGE_SETUP_SUCCESS, + STATUS_BAR_SUBRANGE_SETUP_FAIL); +} + +static int pci_endpoint_test_bar_subrange_clear(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + return pci_endpoint_test_bar_subrange_cmd(test, barno, + COMMAND_BAR_SUBRANGE_CLEAR, + STATUS_BAR_SUBRANGE_CLEAR_SUCCESS, + STATUS_BAR_SUBRANGE_CLEAR_FAIL); +} + +static int pci_endpoint_test_bar_subrange(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + u32 nsub = PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB; + struct device *dev = &test->pdev->dev; + size_t sub_size, buf_size; + resource_size_t bar_size; + void __iomem *bar_addr; + void *read_buf = NULL; + int ret, clear_ret; + size_t off, chunk; + u32 i, exp, val; + u8 pattern; + + if (!(test->ep_caps & CAP_SUBRANGE_MAPPING)) + return -EOPNOTSUPP; + + /* + * The test register BAR is not safe to reprogram and write/read + * over its full size. BAR_TEST already special-cases it to a tiny + * range. For subrange mapping tests, let's simply skip it. + */ + if (barno == test->test_reg_bar) + return -EBUSY; + + bar_size = pci_resource_len(test->pdev, barno); + if (!bar_size) + return -ENODATA; + + bar_addr = test->bar[barno]; + if (!bar_addr) + return -ENOMEM; + + ret = pci_endpoint_test_bar_subrange_setup(test, barno); + if (ret) + return ret; + + if (bar_size % nsub || bar_size / nsub > SIZE_MAX) { + ret = -EINVAL; + goto out_clear; + } + + sub_size = bar_size / nsub; + if (sub_size < sizeof(u32)) { + ret = -ENOSPC; + goto out_clear; + } + + /* Limit the temporary buffer size */ + buf_size = min_t(size_t, sub_size, SZ_1M); + + read_buf = kmalloc(buf_size, GFP_KERNEL); + if (!read_buf) { + ret = -ENOMEM; + goto out_clear; + } + + /* + * Step 1: verify EP-provided signature per subrange. This detects + * whether the EP actually applied the submap order. + */ + for (i = 0; i < nsub; i++) { + exp = (u32)pci_endpoint_test_subrange_sig_byte(barno, i) * + 0x01010101U; + val = ioread32(bar_addr + (i * sub_size)); + if (val != exp) { + dev_err(dev, + "BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n", + barno, i, (size_t)i * sub_size, exp, val); + ret = -EIO; + goto out_clear; + } + val = ioread32(bar_addr + (i * sub_size) + sub_size - sizeof(u32)); + if (val != exp) { + dev_err(dev, + "BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n", + barno, i, + ((size_t)i * sub_size) + sub_size - sizeof(u32), + exp, val); + ret = -EIO; + goto out_clear; + } + } + + /* Step 2: write unique pattern per subrange (write all first). */ + for (i = 0; i < nsub; i++) { + pattern = pci_endpoint_test_subrange_test_byte(barno, i); + memset_io(bar_addr + (i * sub_size), pattern, sub_size); + } + + /* Step 3: read back and verify (read all after all writes). */ + for (i = 0; i < nsub; i++) { + pattern = pci_endpoint_test_subrange_test_byte(barno, i); + for (off = 0; off < sub_size; off += chunk) { + void *bad; + + chunk = min_t(size_t, buf_size, sub_size - off); + memcpy_fromio(read_buf, bar_addr + (i * sub_size) + off, + chunk); + bad = memchr_inv(read_buf, pattern, chunk); + if (bad) { + size_t bad_off = (u8 *)bad - (u8 *)read_buf; + + dev_err(dev, + "BAR%d subrange%u data mismatch @%#zx (pattern %#02x)\n", + barno, i, (size_t)i * sub_size + off + bad_off, + pattern); + ret = -EIO; + goto out_clear; + } + } + } + +out_clear: + kfree(read_buf); + clear_ret = pci_endpoint_test_bar_subrange_clear(test, barno); + return ret ?: clear_ret; +} + static int pci_endpoint_test_intx_irq(struct pci_endpoint_test *test) { u32 val; @@ -936,12 +1132,17 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd, switch (cmd) { case PCITEST_BAR: + case PCITEST_BAR_SUBRANGE: bar = arg; if (bar <= NO_BAR || bar > BAR_5) goto ret; if (is_am654_pci_dev(pdev) && bar == BAR_0) goto ret; - ret = pci_endpoint_test_bar(test, bar); + + if (cmd == PCITEST_BAR) + ret = pci_endpoint_test_bar(test, bar); + else + ret = pci_endpoint_test_bar_subrange(test, bar); break; case PCITEST_BARS: ret = pci_endpoint_test_bars(test); diff --git a/include/uapi/linux/pcitest.h b/include/uapi/linux/pcitest.h index d6023a45a9d0..710f8842223f 100644 --- a/include/uapi/linux/pcitest.h +++ b/include/uapi/linux/pcitest.h @@ -22,6 +22,7 @@ #define PCITEST_GET_IRQTYPE _IO('P', 0x9) #define PCITEST_BARS _IO('P', 0xa) #define PCITEST_DOORBELL _IO('P', 0xb) +#define PCITEST_BAR_SUBRANGE _IO('P', 0xc) #define PCITEST_CLEAR_IRQ _IO('P', 0x10) #define PCITEST_IRQ_TYPE_UNDEFINED -1 From c17b9046faf7d1f3b8bb992e4d53da873dc478fc Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:12 +0900 Subject: [PATCH 34/41] selftests: pci_endpoint: Add BAR subrange mapping test case Add BAR_SUBRANGE_TEST to the pci_endpoint kselftest suite. The test uses the PCITEST_BAR_SUBRANGE ioctl and will skip when the chosen BAR is disabled (-ENODATA), when the endpoint/controller does not support subrange mapping (-EOPNOTSUPP), or when the BAR is reserved for the test register space (-EBUSY). Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260124145012.2794108-9-den@valinux.co.jp --- .../selftests/pci_endpoint/pci_endpoint_test.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c b/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c index 23aac6f97061..eecb776c33af 100644 --- a/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c +++ b/tools/testing/selftests/pci_endpoint/pci_endpoint_test.c @@ -70,6 +70,23 @@ TEST_F(pci_ep_bar, BAR_TEST) EXPECT_FALSE(ret) TH_LOG("Test failed for BAR%d", variant->barno); } +TEST_F(pci_ep_bar, BAR_SUBRANGE_TEST) +{ + int ret; + + pci_ep_ioctl(PCITEST_SET_IRQTYPE, PCITEST_IRQ_TYPE_AUTO); + ASSERT_EQ(0, ret) TH_LOG("Can't set AUTO IRQ type"); + + pci_ep_ioctl(PCITEST_BAR_SUBRANGE, variant->barno); + if (ret == -ENODATA) + SKIP(return, "BAR is disabled"); + if (ret == -EBUSY) + SKIP(return, "BAR is test register space"); + if (ret == -EOPNOTSUPP) + SKIP(return, "Subrange map is not supported"); + EXPECT_FALSE(ret) TH_LOG("Test failed for BAR%d", variant->barno); +} + FIXTURE(pci_ep_basic) { int fd; From ffcc4850a16133d8db0f11ee6dde319201800451 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Fri, 30 Jan 2026 12:30:39 +0100 Subject: [PATCH 35/41] PCI: endpoint: pci-epf-test: Allow overriding default BAR sizes Add bar{0,1,2,3,4,5}_size attributes in configfs, so that the user is not restricted to run pci-epf-test with the hardcoded BAR size values defined in pci-epf-test.c. This code is shamelessly more or less copy pasted from pci-epf-vntb.c Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Koichiro Den Reviewed-by: Damien Le Moal Reviewed-by: Frank Li Link: https://patch.msgid.link/20260130113038.2143947-2-cassel@kernel.org --- Documentation/PCI/endpoint/pci-test-howto.rst | 19 ++++ drivers/pci/endpoint/functions/pci-epf-test.c | 99 ++++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/Documentation/PCI/endpoint/pci-test-howto.rst b/Documentation/PCI/endpoint/pci-test-howto.rst index dd66858cde46..a822866b1fb0 100644 --- a/Documentation/PCI/endpoint/pci-test-howto.rst +++ b/Documentation/PCI/endpoint/pci-test-howto.rst @@ -84,6 +84,25 @@ device, the following commands can be used:: # echo 32 > functions/pci_epf_test/func1/msi_interrupts # echo 2048 > functions/pci_epf_test/func1/msix_interrupts +By default, pci-epf-test uses the following BAR sizes:: + + # grep . functions/pci_epf_test/func1/pci_epf_test.0/bar?_size + functions/pci_epf_test/func1/pci_epf_test.0/bar0_size:131072 + functions/pci_epf_test/func1/pci_epf_test.0/bar1_size:131072 + functions/pci_epf_test/func1/pci_epf_test.0/bar2_size:131072 + functions/pci_epf_test/func1/pci_epf_test.0/bar3_size:131072 + functions/pci_epf_test/func1/pci_epf_test.0/bar4_size:131072 + functions/pci_epf_test/func1/pci_epf_test.0/bar5_size:1048576 + +The user can override a default value using e.g.:: + # echo 1048576 > functions/pci_epf_test/func1/pci_epf_test.0/bar1_size + +Overriding the default BAR sizes can only be done before binding the +pci-epf-test device to a PCI endpoint controller driver. + +Note: Some endpoint controllers might have fixed-size BARs or reserved BARs; +for such controllers, the corresponding BAR size in configfs will be ignored. + Binding pci-epf-test Device to EP Controller -------------------------------------------- diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 1cc630a2ee75..6952ee418622 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -72,6 +72,7 @@ static struct workqueue_struct *kpcitest_workqueue; struct pci_epf_test { void *reg[PCI_STD_NUM_BARS]; struct pci_epf *epf; + struct config_group group; enum pci_barno test_reg_bar; size_t msix_table_offset; struct delayed_work cmd_handler; @@ -85,6 +86,7 @@ struct pci_epf_test { bool dma_private; const struct pci_epc_features *epc_features; struct pci_epf_bar db_bar; + size_t bar_size[PCI_STD_NUM_BARS]; }; struct pci_epf_test_reg { @@ -111,7 +113,8 @@ static struct pci_epf_header test_header = { .interrupt_pin = PCI_INTERRUPT_INTA, }; -static size_t bar_size[] = { 131072, 131072, 131072, 131072, 131072, 1048576 }; +/* default BAR sizes, can be overridden by the user using configfs */ +static size_t default_bar_size[] = { 131072, 131072, 131072, 131072, 131072, 1048576 }; static void pci_epf_test_dma_callback(void *param) { @@ -1240,7 +1243,7 @@ static int pci_epf_test_alloc_space(struct pci_epf *epf) if (epc_features->bar[bar].type == BAR_FIXED) test_reg_size = epc_features->bar[bar].fixed_size; else - test_reg_size = bar_size[bar]; + test_reg_size = epf_test->bar_size[bar]; base = pci_epf_alloc_space(epf, test_reg_size, bar, epc_features, PRIMARY_INTERFACE); @@ -1312,6 +1315,94 @@ static void pci_epf_test_unbind(struct pci_epf *epf) pci_epf_test_free_space(epf); } +#define PCI_EPF_TEST_BAR_SIZE_R(_name, _id) \ +static ssize_t pci_epf_test_##_name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct pci_epf_test *epf_test = \ + container_of(group, struct pci_epf_test, group); \ + \ + return sysfs_emit(page, "%zu\n", epf_test->bar_size[_id]); \ +} + +#define PCI_EPF_TEST_BAR_SIZE_W(_name, _id) \ +static ssize_t pci_epf_test_##_name##_store(struct config_item *item, \ + const char *page, \ + size_t len) \ +{ \ + struct config_group *group = to_config_group(item); \ + struct pci_epf_test *epf_test = \ + container_of(group, struct pci_epf_test, group); \ + int val, ret; \ + \ + /* \ + * BAR sizes can only be modified before binding to an EPC, \ + * because pci_epf_test_alloc_space() is called in .bind(). \ + */ \ + if (epf_test->epf->epc) \ + return -EOPNOTSUPP; \ + \ + ret = kstrtouint(page, 0, &val); \ + if (ret) \ + return ret; \ + \ + if (!is_power_of_2(val)) \ + return -EINVAL; \ + \ + epf_test->bar_size[_id] = val; \ + \ + return len; \ +} + +PCI_EPF_TEST_BAR_SIZE_R(bar0_size, BAR_0) +PCI_EPF_TEST_BAR_SIZE_W(bar0_size, BAR_0) +PCI_EPF_TEST_BAR_SIZE_R(bar1_size, BAR_1) +PCI_EPF_TEST_BAR_SIZE_W(bar1_size, BAR_1) +PCI_EPF_TEST_BAR_SIZE_R(bar2_size, BAR_2) +PCI_EPF_TEST_BAR_SIZE_W(bar2_size, BAR_2) +PCI_EPF_TEST_BAR_SIZE_R(bar3_size, BAR_3) +PCI_EPF_TEST_BAR_SIZE_W(bar3_size, BAR_3) +PCI_EPF_TEST_BAR_SIZE_R(bar4_size, BAR_4) +PCI_EPF_TEST_BAR_SIZE_W(bar4_size, BAR_4) +PCI_EPF_TEST_BAR_SIZE_R(bar5_size, BAR_5) +PCI_EPF_TEST_BAR_SIZE_W(bar5_size, BAR_5) + +CONFIGFS_ATTR(pci_epf_test_, bar0_size); +CONFIGFS_ATTR(pci_epf_test_, bar1_size); +CONFIGFS_ATTR(pci_epf_test_, bar2_size); +CONFIGFS_ATTR(pci_epf_test_, bar3_size); +CONFIGFS_ATTR(pci_epf_test_, bar4_size); +CONFIGFS_ATTR(pci_epf_test_, bar5_size); + +static struct configfs_attribute *pci_epf_test_attrs[] = { + &pci_epf_test_attr_bar0_size, + &pci_epf_test_attr_bar1_size, + &pci_epf_test_attr_bar2_size, + &pci_epf_test_attr_bar3_size, + &pci_epf_test_attr_bar4_size, + &pci_epf_test_attr_bar5_size, + NULL, +}; + +static const struct config_item_type pci_epf_test_group_type = { + .ct_attrs = pci_epf_test_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_group *pci_epf_test_add_cfs(struct pci_epf *epf, + struct config_group *group) +{ + struct pci_epf_test *epf_test = epf_get_drvdata(epf); + struct config_group *epf_group = &epf_test->group; + struct device *dev = &epf->dev; + + config_group_init_type_name(epf_group, dev_name(dev), + &pci_epf_test_group_type); + + return epf_group; +} + static const struct pci_epf_device_id pci_epf_test_ids[] = { { .name = "pci_epf_test", @@ -1324,6 +1415,7 @@ static int pci_epf_test_probe(struct pci_epf *epf, { struct pci_epf_test *epf_test; struct device *dev = &epf->dev; + enum pci_barno bar; epf_test = devm_kzalloc(dev, sizeof(*epf_test), GFP_KERNEL); if (!epf_test) @@ -1331,6 +1423,8 @@ static int pci_epf_test_probe(struct pci_epf *epf, epf->header = &test_header; epf_test->epf = epf; + for (bar = BAR_0; bar < PCI_STD_NUM_BARS; bar++) + epf_test->bar_size[bar] = default_bar_size[bar]; INIT_DELAYED_WORK(&epf_test->cmd_handler, pci_epf_test_cmd_handler); @@ -1343,6 +1437,7 @@ static int pci_epf_test_probe(struct pci_epf *epf, static const struct pci_epf_ops ops = { .unbind = pci_epf_test_unbind, .bind = pci_epf_test_bind, + .add_cfs = pci_epf_test_add_cfs, }; static struct pci_epf_driver test_driver = { From 43d67ec26b329f8aea34ba9dff23d69b84a8e564 Mon Sep 17 00:00:00 2001 From: Aksh Garg Date: Fri, 30 Jan 2026 17:25:14 +0530 Subject: [PATCH 36/41] PCI: dwc: ep: Fix resizable BAR support for multi-PF configurations The resizable BAR support added by the commit 3a3d4cabe681 ("PCI: dwc: ep: Allow EPF drivers to configure the size of Resizable BARs") incorrectly configures the resizable BARs only for the first Physical Function (PF0) in EP mode. The resizable BAR configuration functions use generic dw_pcie_*_dbi() operations instead of physical function specific dw_pcie_ep_*_dbi() operations. This causes resizable BAR configuration to always target PF0 regardless of the requested function number. Additionally, dw_pcie_ep_init_non_sticky_registers() only initializes resizable BAR registers for PF0, leaving other PFs unconfigured during the execution of this function. Fix this by using physical function specific configuration space access operations throughout the resizable BAR code path and initializing registers for all the physical functions that support resizable BARs. Fixes: 3a3d4cabe681 ("PCI: dwc: ep: Allow EPF drivers to configure the size of Resizable BARs") Signed-off-by: Aksh Garg [mani: added stable tag] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Niklas Cassel Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260130115516.515082-2-a-garg7@ti.com --- .../pci/controller/dwc/pcie-designware-ep.c | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 855b2e58c338..1cc2985bab03 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -75,6 +75,13 @@ static u8 dw_pcie_ep_find_capability(struct dw_pcie_ep *ep, u8 func_no, u8 cap) cap, NULL, ep, func_no); } +static u16 dw_pcie_ep_find_ext_capability(struct dw_pcie_ep *ep, + u8 func_no, u8 cap) +{ + return PCI_FIND_NEXT_EXT_CAP(dw_pcie_ep_read_cfg, 0, + cap, NULL, ep, func_no); +} + static int dw_pcie_ep_write_header(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct pci_epf_header *hdr) { @@ -350,22 +357,22 @@ static void dw_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, ep->epf_bar[bar] = NULL; } -static unsigned int dw_pcie_ep_get_rebar_offset(struct dw_pcie *pci, +static unsigned int dw_pcie_ep_get_rebar_offset(struct dw_pcie_ep *ep, u8 func_no, enum pci_barno bar) { u32 reg, bar_index; unsigned int offset, nbars; int i; - offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); + offset = dw_pcie_ep_find_ext_capability(ep, func_no, PCI_EXT_CAP_ID_REBAR); if (!offset) return offset; - reg = dw_pcie_readl_dbi(pci, offset + PCI_REBAR_CTRL); + reg = dw_pcie_ep_readl_dbi(ep, func_no, offset + PCI_REBAR_CTRL); nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, reg); for (i = 0; i < nbars; i++, offset += PCI_REBAR_CTRL) { - reg = dw_pcie_readl_dbi(pci, offset + PCI_REBAR_CTRL); + reg = dw_pcie_ep_readl_dbi(ep, func_no, offset + PCI_REBAR_CTRL); bar_index = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, reg); if (bar_index == bar) return offset; @@ -386,7 +393,7 @@ static int dw_pcie_ep_set_bar_resizable(struct dw_pcie_ep *ep, u8 func_no, u32 rebar_cap, rebar_ctrl; int ret; - rebar_offset = dw_pcie_ep_get_rebar_offset(pci, bar); + rebar_offset = dw_pcie_ep_get_rebar_offset(ep, func_no, bar); if (!rebar_offset) return -EINVAL; @@ -416,16 +423,16 @@ static int dw_pcie_ep_set_bar_resizable(struct dw_pcie_ep *ep, u8 func_no, * 1 MB to 128 TB. Bits 31:16 in PCI_REBAR_CTRL define "supported sizes" * bits for sizes 256 TB to 8 EB. Disallow sizes 256 TB to 8 EB. */ - rebar_ctrl = dw_pcie_readl_dbi(pci, rebar_offset + PCI_REBAR_CTRL); + rebar_ctrl = dw_pcie_ep_readl_dbi(ep, func_no, rebar_offset + PCI_REBAR_CTRL); rebar_ctrl &= ~GENMASK(31, 16); - dw_pcie_writel_dbi(pci, rebar_offset + PCI_REBAR_CTRL, rebar_ctrl); + dw_pcie_ep_writel_dbi(ep, func_no, rebar_offset + PCI_REBAR_CTRL, rebar_ctrl); /* * The "selected size" (bits 13:8) in PCI_REBAR_CTRL are automatically * updated when writing PCI_REBAR_CAP, see "Figure 3-26 Resizable BAR * Example for 32-bit Memory BAR0" in DWC EP databook 5.96a. */ - dw_pcie_writel_dbi(pci, rebar_offset + PCI_REBAR_CAP, rebar_cap); + dw_pcie_ep_writel_dbi(ep, func_no, rebar_offset + PCI_REBAR_CAP, rebar_cap); dw_pcie_dbi_ro_wr_dis(pci); @@ -1023,20 +1030,17 @@ void dw_pcie_ep_deinit(struct dw_pcie_ep *ep) } EXPORT_SYMBOL_GPL(dw_pcie_ep_deinit); -static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) +static void dw_pcie_ep_init_rebar_registers(struct dw_pcie_ep *ep, u8 func_no) { - struct dw_pcie_ep *ep = &pci->ep; unsigned int offset; unsigned int nbars; enum pci_barno bar; u32 reg, i, val; - offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_REBAR); - - dw_pcie_dbi_ro_wr_en(pci); + offset = dw_pcie_ep_find_ext_capability(ep, func_no, PCI_EXT_CAP_ID_REBAR); if (offset) { - reg = dw_pcie_readl_dbi(pci, offset + PCI_REBAR_CTRL); + reg = dw_pcie_ep_readl_dbi(ep, func_no, offset + PCI_REBAR_CTRL); nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, reg); /* @@ -1057,16 +1061,28 @@ static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) * the controller when RESBAR_CAP_REG is written, which * is why RESBAR_CAP_REG is written here. */ - val = dw_pcie_readl_dbi(pci, offset + PCI_REBAR_CTRL); + val = dw_pcie_ep_readl_dbi(ep, func_no, offset + PCI_REBAR_CTRL); bar = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, val); if (ep->epf_bar[bar]) pci_epc_bar_size_to_rebar_cap(ep->epf_bar[bar]->size, &val); else val = BIT(4); - dw_pcie_writel_dbi(pci, offset + PCI_REBAR_CAP, val); + dw_pcie_ep_writel_dbi(ep, func_no, offset + PCI_REBAR_CAP, val); } } +} + +static void dw_pcie_ep_init_non_sticky_registers(struct dw_pcie *pci) +{ + struct dw_pcie_ep *ep = &pci->ep; + u8 funcs = ep->epc->max_functions; + u8 func_no; + + dw_pcie_dbi_ro_wr_en(pci); + + for (func_no = 0; func_no < funcs; func_no++) + dw_pcie_ep_init_rebar_registers(ep, func_no); dw_pcie_setup(pci); dw_pcie_dbi_ro_wr_dis(pci); From 72cb5ed2a5c6d87f71a409347f7d3b228fee6bee Mon Sep 17 00:00:00 2001 From: Aksh Garg Date: Fri, 30 Jan 2026 17:25:15 +0530 Subject: [PATCH 37/41] PCI: dwc: ep: Add per-PF BAR and inbound ATU mapping support The commit 24ede430fa49 ("PCI: designware-ep: Add multiple PFs support for DWC") added support for multiple PFs in the DWC driver, but the implementation was incomplete. It did not properly support MSI/MSI-X, as well as BAR and inbound ATU mapping for multiple PFs. The MSI/MSI-X issue was later fixed by commit 47a062609a30 ("PCI: designware-ep: Modify MSI and MSIX CAP way of finding") by introducing a per-PF struct dw_pcie_ep_func. However, even with both commits, the multiple PF support in the driver remains broken because BAR configuration and ATU mappings are managed globally in struct dw_pcie_ep, meaning all PFs share the same BAR-to-ATU mapping table. This causes one PF's EPF to overwrite the address translation of another PF's EPF in the internal ATU region, creating conflicts when multiple physical functions attempt to configure their BARs independently. The commit cfbc98dbf44d ("PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU") later introduced Address Match Mode support, which suffers from the same multi-PF conflict issue. Fix this by moving the required members from struct dw_pcie_ep to struct dw_pcie_ep_func, similar to what commit 47a062609a30 ("PCI: designware-ep: Modify MSI and MSIX CAP way of finding") did for MSI/MSI-X capability support, to allow proper multi-function endpoint operation, where each PF can configure its BARs and corresponding internal ATU region without interfering with other PFs. Fixes: 24ede430fa49 ("PCI: designware-ep: Add multiple PFs support for DWC") Fixes: cc839bef7727 ("PCI: dwc: ep: Support BAR subrange inbound mapping via Address Match Mode iATU") Signed-off-by: Aksh Garg Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260130115516.515082-3-a-garg7@ti.com --- .../pci/controller/dwc/pcie-designware-ep.c | 79 +++++++++++-------- drivers/pci/controller/dwc/pcie-designware.h | 12 +-- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 1cc2985bab03..6d3c35dd280f 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -115,11 +115,15 @@ static int dw_pcie_ep_ib_atu_bar(struct dw_pcie_ep *ep, u8 func_no, int type, int ret; u32 free_win; struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + struct dw_pcie_ep_func *ep_func = dw_pcie_ep_get_func_from_ep(ep, func_no); - if (!ep->bar_to_atu[bar]) + if (!ep_func) + return -EINVAL; + + if (!ep_func->bar_to_atu[bar]) free_win = find_first_zero_bit(ep->ib_window_map, pci->num_ib_windows); else - free_win = ep->bar_to_atu[bar] - 1; + free_win = ep_func->bar_to_atu[bar] - 1; if (free_win >= pci->num_ib_windows) { dev_err(pci->dev, "No free inbound window\n"); @@ -137,33 +141,37 @@ static int dw_pcie_ep_ib_atu_bar(struct dw_pcie_ep *ep, u8 func_no, int type, * Always increment free_win before assignment, since value 0 is used to identify * unallocated mapping. */ - ep->bar_to_atu[bar] = free_win + 1; + ep_func->bar_to_atu[bar] = free_win + 1; set_bit(free_win, ep->ib_window_map); return 0; } -static void dw_pcie_ep_clear_ib_maps(struct dw_pcie_ep *ep, enum pci_barno bar) +static void dw_pcie_ep_clear_ib_maps(struct dw_pcie_ep *ep, u8 func_no, enum pci_barno bar) { + struct dw_pcie_ep_func *ep_func = dw_pcie_ep_get_func_from_ep(ep, func_no); struct dw_pcie *pci = to_dw_pcie_from_ep(ep); struct device *dev = pci->dev; unsigned int i, num; u32 atu_index; u32 *indexes; + if (!ep_func) + return; + /* Tear down the BAR Match Mode mapping, if any. */ - if (ep->bar_to_atu[bar]) { - atu_index = ep->bar_to_atu[bar] - 1; + if (ep_func->bar_to_atu[bar]) { + atu_index = ep_func->bar_to_atu[bar] - 1; dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, atu_index); clear_bit(atu_index, ep->ib_window_map); - ep->bar_to_atu[bar] = 0; + ep_func->bar_to_atu[bar] = 0; } /* Tear down all Address Match Mode mappings, if any. */ - indexes = ep->ib_atu_indexes[bar]; - num = ep->num_ib_atu_indexes[bar]; - ep->ib_atu_indexes[bar] = NULL; - ep->num_ib_atu_indexes[bar] = 0; + indexes = ep_func->ib_atu_indexes[bar]; + num = ep_func->num_ib_atu_indexes[bar]; + ep_func->ib_atu_indexes[bar] = NULL; + ep_func->num_ib_atu_indexes[bar] = 0; if (!indexes) return; for (i = 0; i < num; i++) { @@ -248,6 +256,7 @@ static int dw_pcie_ep_validate_submap(struct dw_pcie_ep *ep, static int dw_pcie_ep_ib_atu_addr(struct dw_pcie_ep *ep, u8 func_no, int type, const struct pci_epf_bar *epf_bar) { + struct dw_pcie_ep_func *ep_func = dw_pcie_ep_get_func_from_ep(ep, func_no); const struct pci_epf_bar_submap *submap = epf_bar->submap; struct dw_pcie *pci = to_dw_pcie_from_ep(ep); enum pci_barno bar = epf_bar->barno; @@ -258,7 +267,7 @@ static int dw_pcie_ep_ib_atu_addr(struct dw_pcie_ep *ep, u8 func_no, int type, unsigned int i; u32 *indexes; - if (!epf_bar->num_submap || !submap || !epf_bar->size) + if (!ep_func || !epf_bar->num_submap || !submap || !epf_bar->size) return -EINVAL; ret = dw_pcie_ep_validate_submap(ep, submap, epf_bar->num_submap, @@ -279,8 +288,8 @@ static int dw_pcie_ep_ib_atu_addr(struct dw_pcie_ep *ep, u8 func_no, int type, if (!indexes) return -ENOMEM; - ep->ib_atu_indexes[bar] = indexes; - ep->num_ib_atu_indexes[bar] = 0; + ep_func->ib_atu_indexes[bar] = indexes; + ep_func->num_ib_atu_indexes[bar] = 0; for (i = 0; i < epf_bar->num_submap; i++) { size = submap[i].size; @@ -308,11 +317,11 @@ static int dw_pcie_ep_ib_atu_addr(struct dw_pcie_ep *ep, u8 func_no, int type, set_bit(free_win, ep->ib_window_map); indexes[i] = free_win; - ep->num_ib_atu_indexes[bar] = i + 1; + ep_func->num_ib_atu_indexes[bar] = i + 1; } return 0; err: - dw_pcie_ep_clear_ib_maps(ep, bar); + dw_pcie_ep_clear_ib_maps(ep, func_no, bar); return ret; } @@ -346,15 +355,16 @@ static void dw_pcie_ep_clear_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct dw_pcie_ep *ep = epc_get_drvdata(epc); struct dw_pcie *pci = to_dw_pcie_from_ep(ep); enum pci_barno bar = epf_bar->barno; + struct dw_pcie_ep_func *ep_func = dw_pcie_ep_get_func_from_ep(ep, func_no); - if (!ep->epf_bar[bar]) + if (!ep_func || !ep_func->epf_bar[bar]) return; __dw_pcie_ep_reset_bar(pci, func_no, bar, epf_bar->flags); - dw_pcie_ep_clear_ib_maps(ep, bar); + dw_pcie_ep_clear_ib_maps(ep, func_no, bar); - ep->epf_bar[bar] = NULL; + ep_func->epf_bar[bar] = NULL; } static unsigned int dw_pcie_ep_get_rebar_offset(struct dw_pcie_ep *ep, u8 func_no, @@ -481,12 +491,16 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, { struct dw_pcie_ep *ep = epc_get_drvdata(epc); struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + struct dw_pcie_ep_func *ep_func = dw_pcie_ep_get_func_from_ep(ep, func_no); enum pci_barno bar = epf_bar->barno; size_t size = epf_bar->size; enum pci_epc_bar_type bar_type; int flags = epf_bar->flags; int ret, type; + if (!ep_func) + return -EINVAL; + /* * DWC does not allow BAR pairs to overlap, e.g. you cannot combine BARs * 1 and 2 to form a 64-bit BAR. @@ -500,22 +514,22 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, * calling clear_bar() would clear the BAR's PCI address assigned by the * host). */ - if (ep->epf_bar[bar]) { + if (ep_func->epf_bar[bar]) { /* * We can only dynamically change a BAR if the new BAR size and * BAR flags do not differ from the existing configuration. */ - if (ep->epf_bar[bar]->barno != bar || - ep->epf_bar[bar]->size != size || - ep->epf_bar[bar]->flags != flags) + if (ep_func->epf_bar[bar]->barno != bar || + ep_func->epf_bar[bar]->size != size || + ep_func->epf_bar[bar]->flags != flags) return -EINVAL; /* * When dynamically changing a BAR, tear down any existing * mappings before re-programming. */ - if (ep->epf_bar[bar]->num_submap || epf_bar->num_submap) - dw_pcie_ep_clear_ib_maps(ep, bar); + if (ep_func->epf_bar[bar]->num_submap || epf_bar->num_submap) + dw_pcie_ep_clear_ib_maps(ep, func_no, bar); /* * When dynamically changing a BAR, skip writing the BAR reg, as @@ -574,7 +588,7 @@ config_atu: if (ret) return ret; - ep->epf_bar[bar] = epf_bar; + ep_func->epf_bar[bar] = epf_bar; return 0; } @@ -969,7 +983,7 @@ int dw_pcie_ep_raise_msix_irq(struct dw_pcie_ep *ep, u8 func_no, bir = FIELD_GET(PCI_MSIX_TABLE_BIR, tbl_offset); tbl_offset &= PCI_MSIX_TABLE_OFFSET; - msix_tbl = ep->epf_bar[bir]->addr + tbl_offset; + msix_tbl = ep_func->epf_bar[bir]->addr + tbl_offset; msg_addr = msix_tbl[(interrupt_num - 1)].msg_addr; msg_data = msix_tbl[(interrupt_num - 1)].msg_data; vec_ctrl = msix_tbl[(interrupt_num - 1)].vector_ctrl; @@ -1032,11 +1046,14 @@ EXPORT_SYMBOL_GPL(dw_pcie_ep_deinit); static void dw_pcie_ep_init_rebar_registers(struct dw_pcie_ep *ep, u8 func_no) { - unsigned int offset; - unsigned int nbars; + struct dw_pcie_ep_func *ep_func = dw_pcie_ep_get_func_from_ep(ep, func_no); + unsigned int offset, nbars; enum pci_barno bar; u32 reg, i, val; + if (!ep_func) + return; + offset = dw_pcie_ep_find_ext_capability(ep, func_no, PCI_EXT_CAP_ID_REBAR); if (offset) { @@ -1063,8 +1080,8 @@ static void dw_pcie_ep_init_rebar_registers(struct dw_pcie_ep *ep, u8 func_no) */ val = dw_pcie_ep_readl_dbi(ep, func_no, offset + PCI_REBAR_CTRL); bar = FIELD_GET(PCI_REBAR_CTRL_BAR_IDX, val); - if (ep->epf_bar[bar]) - pci_epc_bar_size_to_rebar_cap(ep->epf_bar[bar]->size, &val); + if (ep_func->epf_bar[bar]) + pci_epc_bar_size_to_rebar_cap(ep_func->epf_bar[bar]->size, &val); else val = BIT(4); diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 8f170122ad78..43d7606bc987 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -471,6 +471,12 @@ struct dw_pcie_ep_func { u8 func_no; u8 msi_cap; /* MSI capability offset */ u8 msix_cap; /* MSI-X capability offset */ + u8 bar_to_atu[PCI_STD_NUM_BARS]; + struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS]; + + /* Only for Address Match Mode inbound iATU */ + u32 *ib_atu_indexes[PCI_STD_NUM_BARS]; + unsigned int num_ib_atu_indexes[PCI_STD_NUM_BARS]; }; struct dw_pcie_ep { @@ -480,17 +486,11 @@ struct dw_pcie_ep { phys_addr_t phys_base; size_t addr_size; size_t page_size; - u8 bar_to_atu[PCI_STD_NUM_BARS]; phys_addr_t *outbound_addr; unsigned long *ib_window_map; unsigned long *ob_window_map; void __iomem *msi_mem; phys_addr_t msi_mem_phys; - struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS]; - - /* Only for Address Match Mode inbound iATU */ - u32 *ib_atu_indexes[PCI_STD_NUM_BARS]; - unsigned int num_ib_atu_indexes[PCI_STD_NUM_BARS]; /* MSI outbound iATU state */ bool msi_iatu_mapped; From e3c3a5d25dc090c237ec768fdded96e9184fe2ae Mon Sep 17 00:00:00 2001 From: Aksh Garg Date: Fri, 30 Jan 2026 17:25:16 +0530 Subject: [PATCH 38/41] PCI: dwc: ep: Add comment explaining controller level PTM access in multi PF setup PCIe r6.0, section 7.9.15 requires PTM capability in exactly one function to control all PTM-capable functions. This makes PTM registers controller level rather than per-function. Add a comment explaining why PTM capability registers are accessed using the standard DBI accessors instead of func_no indexed per-function accessors. Suggested-by: Niklas Cassel Signed-off-by: Aksh Garg Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Niklas Cassel Link: https://patch.msgid.link/20260130115516.515082-4-a-garg7@ti.com --- drivers/pci/controller/dwc/pcie-designware-ep.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index 6d3c35dd280f..7e7844ff0f7e 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -1187,6 +1187,18 @@ int dw_pcie_ep_init_registers(struct dw_pcie_ep *ep) if (ep->ops->init) ep->ops->init(ep); + /* + * PCIe r6.0, section 7.9.15 states that for endpoints that support + * PTM, this capability structure is required in exactly one + * function, which controls the PTM behavior of all PTM capable + * functions. This indicates the PTM capability structure + * represents controller-level registers rather than per-function + * registers. + * + * Therefore, PTM capability registers are configured using the + * standard DBI accessors, instead of func_no indexed per-function + * accessors. + */ ptm_cap_base = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_PTM); /* From 58fbf08935d9c4396417e5887df89a4e681fa7e3 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Tue, 27 Jan 2026 16:10:39 +0100 Subject: [PATCH 39/41] PCI: dwc: Fix msg_atu_index assignment When dw_pcie_iatu_setup() configures outbound address translation for both type PCIE_ATU_TYPE_MEM and PCIE_ATU_TYPE_IO, the iATU index to use is incremented before calling dw_pcie_prog_outbound_atu(). However for msg_atu_index, the index is not incremented before use, causing the iATU index to be the same as the last configured iATU index, which means that it will incorrectly use the same iATU index that is already in use, breaking outbound address translation. In total there are three problems with this code: -It assigns msg_atu_index the same index that was used for the last outbound address translation window, rather than incrementing the index before assignment. -The index should only be incremented (and msg_atu_index assigned) if the use_atu_msg feature is actually requested/in use (pp->use_atu_msg is set). -If the use_atu_msg feature is requested/in use, and there are no outbound iATUs available, the code should return an error, as otherwise when this this feature is used, it will use an iATU index that is out of bounds. Fixes: e1a4ec1a9520 ("PCI: dwc: Add generic MSG TLP support for sending PME_Turn_Off when system suspend") Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Tested-by: Maciej W. Rozycki Reviewed-by: Damien Le Moal Reviewed-by: Hans Zhang Reviewed-by: Frank Li Reviewed-by: Shawn Lin Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260127151038.1484881-6-cassel@kernel.org --- drivers/pci/controller/dwc/pcie-designware-host.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index b3d6a474fd16..d7f57d77bdf5 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -982,7 +982,14 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) dev_warn(pci->dev, "Ranges exceed outbound iATU size (%d)\n", pci->num_ob_windows); - pp->msg_atu_index = i; + if (pp->use_atu_msg) { + if (pci->num_ob_windows > ++i) { + pp->msg_atu_index = i; + } else { + dev_err(pci->dev, "Cannot add outbound window for MSG TLP\n"); + return -ENOMEM; + } + } i = 0; resource_list_for_each_entry(entry, &pp->bridge->dma_ranges) { From b5dab9b38da0a05949458276dde9227c38aa1b39 Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Tue, 27 Jan 2026 16:10:40 +0100 Subject: [PATCH 40/41] PCI: dwc: Clean up iATU index usage in dw_pcie_iatu_setup() The current iATU index usage in dw_pcie_iatu_setup() is a mess. For outbound address translation the index is incremented before usage. For inbound address translation the index is incremented after usage. Incrementing the index after usage make much more sense, and make the index usage consistent for both outbound and inbound address translation. Most likely, the overly complicated logic for the outbound address translation is because the iATU at index 0 is reserved for CFG IOs (dw_pcie_other_conf_map_bus()), however, we should be able to use the exact same logic for the indexing of the outbound and inbound iATUs. (Only the starting index should be different.) Create two new variables ob_iatu_index and ib_iatu_index, which makes it more clear from the name itself that it is a zeroes based index, and only increment the index if the iATU configuration call succeeded. Since we always check if there is an index available immediately before programming the iATU, we can remove the useless "ranges exceed outbound iATU size" warnings, as the code is already unreachable. For the same reason, we can also remove the useless breaks outside of the while loops. No functional changes intended. Signed-off-by: Niklas Cassel Signed-off-by: Manivannan Sadhasivam Tested-by: Maciej W. Rozycki Reviewed-by: Damien Le Moal Reviewed-by: Hans Zhang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260127151038.1484881-7-cassel@kernel.org --- .../pci/controller/dwc/pcie-designware-host.c | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index d7f57d77bdf5..87e6a32dbb9a 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -892,9 +892,10 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct dw_pcie_ob_atu_cfg atu = { 0 }; struct resource_entry *entry; + int ob_iatu_index; + int ib_iatu_index; int i, ret; - /* Note the very first outbound ATU is used for CFG IOs */ if (!pci->num_ob_windows) { dev_err(pci->dev, "No outbound iATU found\n"); return -EINVAL; @@ -910,16 +911,18 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) for (i = 0; i < pci->num_ib_windows; i++) dw_pcie_disable_atu(pci, PCIE_ATU_REGION_DIR_IB, i); - i = 0; + /* + * NOTE: For outbound address translation, outbound iATU at index 0 is + * reserved for CFG IOs (dw_pcie_other_conf_map_bus()), thus start at + * index 1. + */ + ob_iatu_index = 1; resource_list_for_each_entry(entry, &pp->bridge->windows) { resource_size_t res_size; if (resource_type(entry->res) != IORESOURCE_MEM) continue; - if (pci->num_ob_windows <= i + 1) - break; - atu.type = PCIE_ATU_TYPE_MEM; atu.parent_bus_addr = entry->res->start - pci->parent_bus_offset; atu.pci_addr = entry->res->start - entry->offset; @@ -937,13 +940,13 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) * middle. Otherwise, we would end up only partially * mapping a single resource. */ - if (pci->num_ob_windows <= ++i) { - dev_err(pci->dev, "Exhausted outbound windows for region: %pr\n", + if (ob_iatu_index >= pci->num_ob_windows) { + dev_err(pci->dev, "Cannot add outbound window for region: %pr\n", entry->res); return -ENOMEM; } - atu.index = i; + atu.index = ob_iatu_index; atu.size = MIN(pci->region_limit + 1, res_size); ret = dw_pcie_prog_outbound_atu(pci, &atu); @@ -953,6 +956,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) return ret; } + ob_iatu_index++; atu.parent_bus_addr += atu.size; atu.pci_addr += atu.size; res_size -= atu.size; @@ -960,8 +964,8 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) } if (pp->io_size) { - if (pci->num_ob_windows > ++i) { - atu.index = i; + if (ob_iatu_index < pci->num_ob_windows) { + atu.index = ob_iatu_index; atu.type = PCIE_ATU_TYPE_IO; atu.parent_bus_addr = pp->io_base - pci->parent_bus_offset; atu.pci_addr = pp->io_bus_addr; @@ -973,34 +977,35 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) entry->res); return ret; } + ob_iatu_index++; } else { + /* + * If there are not enough outbound windows to give I/O + * space its own iATU, the outbound iATU at index 0 will + * be shared between I/O space and CFG IOs, by + * temporarily reconfiguring the iATU to CFG space, in + * order to do a CFG IO, and then immediately restoring + * it to I/O space. + */ pp->cfg0_io_shared = true; } } - if (pci->num_ob_windows <= i) - dev_warn(pci->dev, "Ranges exceed outbound iATU size (%d)\n", - pci->num_ob_windows); - if (pp->use_atu_msg) { - if (pci->num_ob_windows > ++i) { - pp->msg_atu_index = i; - } else { + if (ob_iatu_index >= pci->num_ob_windows) { dev_err(pci->dev, "Cannot add outbound window for MSG TLP\n"); return -ENOMEM; } + pp->msg_atu_index = ob_iatu_index++; } - i = 0; + ib_iatu_index = 0; resource_list_for_each_entry(entry, &pp->bridge->dma_ranges) { resource_size_t res_start, res_size, window_size; if (resource_type(entry->res) != IORESOURCE_MEM) continue; - if (pci->num_ib_windows <= i) - break; - res_size = resource_size(entry->res); res_start = entry->res->start; while (res_size > 0) { @@ -1009,14 +1014,15 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) * middle. Otherwise, we would end up only partially * mapping a single resource. */ - if (pci->num_ib_windows <= i) { - dev_err(pci->dev, "Exhausted inbound windows for region: %pr\n", + if (ib_iatu_index >= pci->num_ib_windows) { + dev_err(pci->dev, "Cannot add inbound window for region: %pr\n", entry->res); return -ENOMEM; } window_size = MIN(pci->region_limit + 1, res_size); - ret = dw_pcie_prog_inbound_atu(pci, i++, PCIE_ATU_TYPE_MEM, res_start, + ret = dw_pcie_prog_inbound_atu(pci, ib_iatu_index, + PCIE_ATU_TYPE_MEM, res_start, res_start - entry->offset, window_size); if (ret) { dev_err(pci->dev, "Failed to set DMA range %pr\n", @@ -1024,15 +1030,12 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) return ret; } + ib_iatu_index++; res_start += window_size; res_size -= window_size; } } - if (pci->num_ib_windows <= i) - dev_warn(pci->dev, "Dma-ranges exceed inbound iATU size (%u)\n", - pci->num_ib_windows); - return 0; } From 43d324eeb08c3dd9fff7eb9a2c617afd3b96e65c Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Tue, 27 Jan 2026 16:10:41 +0100 Subject: [PATCH 41/41] PCI: dwc: Fix missing iATU setup when ECAM is enabled When ECAM is enabled, the driver skipped calling dw_pcie_iatu_setup() before configuring ECAM iATU entries. This left IO and MEM outbound windows unprogrammed, resulting in broken IO transactions. Additionally, dw_pcie_config_ecam_iatu() was only called during host initialization, so ECAM-related iATU entries were not restored after suspend/resume, leading to failures in configuration space access To resolve these issues, move the ECAM iATU configuration to dw_pcie_iatu_setup(), and invoke dw_pcie_iatu_setup() when ECAM is enabled. Furthermore, add error checks in dw_pcie_prog_outbound_atu() and dw_pcie_prog_inbound_atu() such that an error is returned if the caller is trying to program an iATU that is outside the number of iATUs supported by the controller. Fixes: f6fd357f7afb ("PCI: dwc: Prepare the driver for enabling ECAM mechanism using iATU 'CFG Shift Feature'") Reported-by: Maciej W. Rozycki Closes: https://lore.kernel.org/all/alpine.DEB.2.21.2511280256260.36486@angie.orcam.me.uk/ Signed-off-by: Krishna Chaitanya Chundru Co-developed-by: Niklas Cassel Signed-off-by: Niklas Cassel [mani: used imperative tone] Signed-off-by: Manivannan Sadhasivam Tested-by: Maciej W. Rozycki Reviewed-by: Damien Le Moal Reviewed-by: Hans Zhang Reviewed-by: Frank Li Cc: stable+noautosel@kernel.org # depends on Clean up iATU index usage in dw_pcie_iatu_setup() Link: https://patch.msgid.link/20260127151038.1484881-8-cassel@kernel.org --- .../pci/controller/dwc/pcie-designware-host.c | 33 ++++++++++++------- drivers/pci/controller/dwc/pcie-designware.c | 6 ++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index 87e6a32dbb9a..bc2e08ec515e 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -641,14 +641,6 @@ int dw_pcie_host_init(struct dw_pcie_rp *pp) if (ret) goto err_free_msi; - if (pp->ecam_enabled) { - ret = dw_pcie_config_ecam_iatu(pp); - if (ret) { - dev_err(dev, "Failed to configure iATU in ECAM mode\n"); - goto err_free_msi; - } - } - /* * Allocate the resource for MSG TLP before programming the iATU * outbound window in dw_pcie_setup_rc(). Since the allocation depends @@ -915,8 +907,21 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) * NOTE: For outbound address translation, outbound iATU at index 0 is * reserved for CFG IOs (dw_pcie_other_conf_map_bus()), thus start at * index 1. + * + * If using ECAM, outbound iATU at index 0 and index 1 is reserved for + * CFG IOs. */ - ob_iatu_index = 1; + if (pp->ecam_enabled) { + ob_iatu_index = 2; + ret = dw_pcie_config_ecam_iatu(pp); + if (ret) { + dev_err(pci->dev, "Failed to configure iATU in ECAM mode\n"); + return ret; + } + } else { + ob_iatu_index = 1; + } + resource_list_for_each_entry(entry, &pp->bridge->windows) { resource_size_t res_size; @@ -985,8 +990,14 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) * be shared between I/O space and CFG IOs, by * temporarily reconfiguring the iATU to CFG space, in * order to do a CFG IO, and then immediately restoring - * it to I/O space. + * it to I/O space. This is only implemented when using + * dw_pcie_other_conf_map_bus(), which is not the case + * when using ECAM. */ + if (pp->ecam_enabled) { + dev_err(pci->dev, "Cannot add outbound window for I/O\n"); + return -ENOMEM; + } pp->cfg0_io_shared = true; } } @@ -1157,7 +1168,7 @@ int dw_pcie_setup_rc(struct dw_pcie_rp *pp) * the platform uses its own address translation component rather than * ATU, so we should not program the ATU here. */ - if (pp->bridge->child_ops == &dw_child_pcie_ops) { + if (pp->bridge->child_ops == &dw_child_pcie_ops || pp->ecam_enabled) { ret = dw_pcie_iatu_setup(pp); if (ret) return ret; diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index 18331d9e85be..5741c09dde7f 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -532,6 +532,9 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, u32 retries, val; u64 limit_addr; + if (atu->index >= pci->num_ob_windows) + return -ENOSPC; + limit_addr = parent_bus_addr + atu->size - 1; if ((limit_addr & ~pci->region_limit) != (parent_bus_addr & ~pci->region_limit) || @@ -605,6 +608,9 @@ int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int type, u64 limit_addr = pci_addr + size - 1; u32 retries, val; + if (index >= pci->num_ib_windows) + return -ENOSPC; + if ((limit_addr & ~pci->region_limit) != (pci_addr & ~pci->region_limit) || !IS_ALIGNED(parent_bus_addr, pci->region_align) || !IS_ALIGNED(pci_addr, pci->region_align) || !size) {