From d462c8e89e84bfb6417e6b4c88e0cb7cc747ba41 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Fri, 17 Apr 2026 10:51:46 +0200 Subject: [PATCH 001/168] PCI: Stop setting cached power state to 'unknown' on unbind When a PCI device is unbound from its driver, pci_device_remove() sets the cached power state in pci_dev->current_state to PCI_UNKNOWN. This was introduced by commit 2449e06a5696 ("PCI: reset pci device state to unknown state for resume") to invalidate the cached power state in case the system is subsequently put to sleep. For bound devices, the cached power state is set to PCI_UNKNOWN in pci_pm_suspend_noirq(), immediately before entering system sleep. Extend to unbound devices for consistency. This obviates the need to change the cached power state on unbind, so stop doing so. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/af7d11d3ceb231acc90829f7a5c8400c2446744f.1776415510.git.lukas@wunner.de --- drivers/pci/pci-driver.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index d10ece0889f0..2bfefd8db526 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -512,13 +512,6 @@ static void pci_device_remove(struct device *dev) /* Undo the runtime PM settings in local_pci_probe() */ pm_runtime_put_sync(dev); - /* - * If the device is still on, set the power state as "unknown", - * since it might change by the next time we load the driver. - */ - if (pci_dev->current_state == PCI_D0) - pci_dev->current_state = PCI_UNKNOWN; - /* * We would love to complain here if pci_dev->is_enabled is set, that * the driver should have called pci_disable_device(), but the @@ -893,7 +886,7 @@ static int pci_pm_suspend_noirq(struct device *dev) if (!pm) { pci_save_state(pci_dev); - goto Fixup; + goto set_unknown; } if (pm->suspend_noirq) { @@ -945,6 +938,7 @@ static int pci_pm_suspend_noirq(struct device *dev) goto Fixup; } +set_unknown: pci_pm_set_unknown_state(pci_dev); /* From ee7471fe968d210939be9046089a924cd23c8c3b Mon Sep 17 00:00:00 2001 From: Marco Nenciarini Date: Fri, 17 Apr 2026 15:24:36 +0200 Subject: [PATCH 002/168] PCI: Skip Resizable BAR restore on read error pci_restore_rebar_state() uses the Resizable BAR Control register to decide how many BARs to restore (nbars) and which BAR each iteration addresses (bar_idx). When a device does not respond, config reads typically return PCI_ERROR_RESPONSE (~0). Both fields are 3 bits wide, so nbars and bar_idx both evaluate to 7, past the spec's valid ranges for both fields. pci_resource_n() then returns an unrelated resource slot, whose size is used to derive a nonsensical value written back to the Resizable BAR Control register. Bail out if any Resizable BAR Control read returns PCI_ERROR_RESPONSE. No further BARs are touched, which is safe because a config read that returns PCI_ERROR_RESPONSE indicates the device is unreachable and restoration is pointless. Fixes: d3252ace0bc6 ("PCI: Restore resized BAR state on resume") Signed-off-by: Marco Nenciarini Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/666cac19b5daa0ab0e0ab64454e76b4d24465dbd.1776429882.git.mnencia@kcore.it --- drivers/pci/rebar.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pci/rebar.c b/drivers/pci/rebar.c index 39f8cf3b70d5..11965947c4cb 100644 --- a/drivers/pci/rebar.c +++ b/drivers/pci/rebar.c @@ -231,6 +231,9 @@ void pci_restore_rebar_state(struct pci_dev *pdev) return; pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + if (PCI_POSSIBLE_ERROR(ctrl)) + return; + nbars = FIELD_GET(PCI_REBAR_CTRL_NBAR_MASK, ctrl); for (i = 0; i < nbars; i++, pos += 8) { @@ -238,6 +241,9 @@ void pci_restore_rebar_state(struct pci_dev *pdev) int bar_idx, size; pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); + if (PCI_POSSIBLE_ERROR(ctrl)) + return; + bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; res = pci_resource_n(pdev, bar_idx); size = pci_rebar_bytes_to_size(resource_size(res)); From f34f1712229d71ce4286440fef12526fd4590b37 Mon Sep 17 00:00:00 2001 From: Marco Nenciarini Date: Fri, 17 Apr 2026 15:24:37 +0200 Subject: [PATCH 003/168] PCI/IOV: Skip VF Resizable BAR restore on read error sriov_restore_vf_rebar_state() uses the VF Resizable BAR Control register to decide how many VF BARs to restore (nbars) and which VF BAR each iteration addresses (bar_idx). bar_idx indexes into dev->sriov->barsz[], which has only PCI_SRIOV_NUM_BARS (6) entries. When a device does not respond, config reads typically return PCI_ERROR_RESPONSE (~0). Both fields are 3 bits wide, so nbars and bar_idx both evaluate to 7. The barsz[] access then goes out of bounds. UBSAN reports this as: UBSAN: array-index-out-of-bounds in drivers/pci/iov.c:948:51 index 7 is out of range for type 'resource_size_t [6]' Observed on an NVIDIA RTX PRO 1000 GPU (GB207GLM) that stopped responding during a failed GC6 power state exit. The subsequent pci_restore_state() invoked sriov_restore_vf_rebar_state() while config reads returned 0xffffffff, triggering the splat. Bail out if any VF Resizable BAR Control read returns PCI_ERROR_RESPONSE. No further VF BARs are touched, which is safe because a config read that returns PCI_ERROR_RESPONSE indicates the device is unreachable and restoration is pointless. This mirrors the guard in pci_restore_rebar_state(). Fixes: 5a8f77e24a30 ("PCI/IOV: Restore VF resizable BAR state after reset") Signed-off-by: Marco Nenciarini Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Link: https://patch.msgid.link/44a4ae53ec2825816b816c85cd378430d9a95cc6.1776429882.git.mnencia@kcore.it --- drivers/pci/iov.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 91ac4e37ecb9..08df9bace13d 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -938,12 +938,18 @@ static void sriov_restore_vf_rebar_state(struct pci_dev *dev) return; pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl); + if (PCI_POSSIBLE_ERROR(ctrl)) + return; + nbars = FIELD_GET(PCI_VF_REBAR_CTRL_NBAR_MASK, ctrl); for (i = 0; i < nbars; i++, pos += 8) { int bar_idx, size; pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl); + if (PCI_POSSIBLE_ERROR(ctrl)) + return; + bar_idx = FIELD_GET(PCI_VF_REBAR_CTRL_BAR_IDX, ctrl); size = pci_rebar_bytes_to_size(dev->sriov->barsz[bar_idx]); ctrl &= ~PCI_VF_REBAR_CTRL_BAR_SIZE; From 73ae5392b0cfcce02efa23ee7123d8764ef070a8 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 21 Apr 2026 16:11:01 +0530 Subject: [PATCH 004/168] PCI/pwrctrl: Move pci_pwrctrl_is_required() earlier in file Move pci_pwrctrl_is_required() earlier in the file so it can be used by pci_pwrctrl_power_off_device() and pci_pwrctrl_power_on_device(). Signed-off-by: Manivannan Sadhasivam [bhelgaas: split to its own patch] Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260421104102.12322-1-manivannan.sadhasivam@oss.qualcomm.com --- drivers/pci/pwrctrl/core.c | 84 +++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index 97cff5b8ca88..a692aeaee81a 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -139,6 +139,48 @@ int devm_pci_pwrctrl_device_set_ready(struct device *dev, } EXPORT_SYMBOL_GPL(devm_pci_pwrctrl_device_set_ready); +/* + * Check whether the pwrctrl device really needs to be created or not. The + * pwrctrl device will only be created if the node satisfies below requirements: + * + * 1. Presence of compatible property with "pci" prefix to match against the + * pwrctrl driver (AND) + * 2. At least one of the power supplies defined in the devicetree node of the + * device (OR) in the remote endpoint parent node to indicate pwrctrl + * requirement. + */ +static bool pci_pwrctrl_is_required(struct device_node *np) +{ + struct device_node *endpoint; + const char *compat; + int ret; + + ret = of_property_read_string(np, "compatible", &compat); + if (ret < 0) + return false; + + if (!strstarts(compat, "pci")) + return false; + + if (of_pci_supply_present(np)) + return true; + + if (of_graph_is_present(np)) { + for_each_endpoint_of_node(np, endpoint) { + struct device_node *remote __free(device_node) = + of_graph_get_remote_port_parent(endpoint); + if (remote) { + if (of_pci_supply_present(remote)) { + of_node_put(endpoint); + return true; + } + } + } + } + + return false; +} + static int __pci_pwrctrl_power_off_device(struct device *dev) { struct pci_pwrctrl *pwrctrl = dev_get_drvdata(dev); @@ -268,48 +310,6 @@ int pci_pwrctrl_power_on_devices(struct device *parent) } EXPORT_SYMBOL_GPL(pci_pwrctrl_power_on_devices); -/* - * Check whether the pwrctrl device really needs to be created or not. The - * pwrctrl device will only be created if the node satisfies below requirements: - * - * 1. Presence of compatible property with "pci" prefix to match against the - * pwrctrl driver (AND) - * 2. At least one of the power supplies defined in the devicetree node of the - * device (OR) in the remote endpoint parent node to indicate pwrctrl - * requirement. - */ -static bool pci_pwrctrl_is_required(struct device_node *np) -{ - struct device_node *endpoint; - const char *compat; - int ret; - - ret = of_property_read_string(np, "compatible", &compat); - if (ret < 0) - return false; - - if (!strstarts(compat, "pci")) - return false; - - if (of_pci_supply_present(np)) - return true; - - if (of_graph_is_present(np)) { - for_each_endpoint_of_node(np, endpoint) { - struct device_node *remote __free(device_node) = - of_graph_get_remote_port_parent(endpoint); - if (remote) { - if (of_pci_supply_present(remote)) { - of_node_put(endpoint); - return true; - } - } - } - } - - return false; -} - static int pci_pwrctrl_create_device(struct device_node *np, struct device *parent) { From 95c4920701dfca6a8cf4986112898382fa7afc0f Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 21 Apr 2026 16:11:01 +0530 Subject: [PATCH 005/168] PCI/pwrctrl: Do not try to power on/off devices that don't need pwrctrl pci_pwrctrl_is_required() detects whether a device needs PCI pwrctrl support. It is currently used in pci_pwrctrl_create_device(), but not in pci_pwrctrl_power_{on/off}_device() APIs. This leads to pwrctrl core trying to power on/off incompatible devices like USB hub downstream ports defined in DT. Add this check to prevent pwrctrl core from poking at wrong devices. Fixes: b35cf3b6aa1e ("PCI/pwrctrl: Add APIs to power on/off pwrctrl devices") Reported-by: Krishna Chaitanya Chundru Signed-off-by: Manivannan Sadhasivam [bhelgaas: split pci_pwrctrl_is_required() move to separate patch] Signed-off-by: Bjorn Helgaas Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/20260421104102.12322-1-manivannan.sadhasivam@oss.qualcomm.com --- drivers/pci/pwrctrl/core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index a692aeaee81a..b5a0a14d316e 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -199,6 +199,9 @@ static void pci_pwrctrl_power_off_device(struct device_node *np) for_each_available_child_of_node_scoped(np, child) pci_pwrctrl_power_off_device(child); + if (!pci_pwrctrl_is_required(np)) + return; + pdev = of_find_device_by_node(np); if (!pdev) return; @@ -255,6 +258,9 @@ static int pci_pwrctrl_power_on_device(struct device_node *np) return ret; } + if (!pci_pwrctrl_is_required(np)) + return 0; + pdev = of_find_device_by_node(np); if (!pdev) return 0; From 951ebc18d181af1d90deddbc0ef3269061d6e03c Mon Sep 17 00:00:00 2001 From: Matt Evans Date: Thu, 23 Apr 2026 10:30:51 -0700 Subject: [PATCH 006/168] PCI/P2PDMA: Avoid returning a provider for non_mappable_bars Extend the checks in pcim_p2pdma_init() and pcim_p2pdma_provider() to exclude functions that have pdev->non_mappable_bars set. Consumers such as VFIO were previously able to map these for access by the CPU or P2P. Update the comment on non_mappable_bars to show it refers to any access, not just userspace CPU access. Fixes: 372d6d1b8ae3c ("PCI/P2PDMA: Refactor to separate core P2P functionality from memory allocation") Suggested-by: Alex Williamson Signed-off-by: Matt Evans Signed-off-by: Bjorn Helgaas Reviewed-by: Niklas Schnelle Reviewed-by: Alex Williamson Link: https://patch.msgid.link/20260423173051.1999679-1-mattev@meta.com --- drivers/pci/p2pdma.c | 6 +++++- include/linux/pci.h | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index 7c898542af8d..adb17a4f6939 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -262,6 +262,9 @@ int pcim_p2pdma_init(struct pci_dev *pdev) struct pci_p2pdma *p2p; int i, ret; + if (pdev->non_mappable_bars) + return -EOPNOTSUPP; + p2p = rcu_dereference_protected(pdev->p2pdma, 1); if (p2p) return 0; @@ -318,7 +321,8 @@ struct p2pdma_provider *pcim_p2pdma_provider(struct pci_dev *pdev, int bar) { struct pci_p2pdma *p2p; - if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) + if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM) || + pdev->non_mappable_bars) return NULL; p2p = rcu_dereference_protected(pdev->p2pdma, 1); diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c11..1e6802017d6b 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -508,7 +508,7 @@ struct pci_dev { unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ - unsigned int non_mappable_bars:1; /* BARs can't be mapped to user-space */ + unsigned int non_mappable_bars:1; /* BARs can't be mapped by CPU or peers */ pci_dev_flags_t dev_flags; atomic_t enable_cnt; /* pci_enable_device has been called */ From bb115505a9f1f79b888aa0ccb28f42f05edea07f Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Thu, 26 Mar 2026 17:13:11 -0500 Subject: [PATCH 007/168] PCI: Remove MPS/MRRS Kconfig settings (CONFIG_PCIE_BUS_*) Revert b0e85c3c8554 ("PCI: Add Kconfig options for MPS/MRRS strategy"), which allowed build-time selection of the "off", "default", "safe", "performance", or "peer2peer" strategies for MPS and MRRS configuration. These strategies can be selected at boot-time using the "pci=pcie_bus_tune_*" kernel parameters. Per the discussion mentioned below, these Kconfig options were added to work around a hardware defect in a WiFi device used in a cable modem. The defect occurred only when the device was configured with MPS=128, and Kconfig was a way to avoid that setting. It was easier for the modem vendor to use Kconfig and update the kernel image than to change the kernel parameters. Neither Kconfig nor kernel parameters are a complete solution because the broken WiFi device may be used in other systems where it may be configured with MPS=128 and be susceptible to the defect. Remove the Kconfig settings to simplify the MPS code. If we can identify the WiFi device in question, we may be able to make a generic quirk to avoid the problem on all system. This is not a fix and should not be backported to previous kernels. Link: https://lore.kernel.org/all/CA+-6iNzd0RJO0L021qz8CKrSviSst6QehY-QtJxz_-EVY0Hj0Q@mail.gmail.com Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260326221311.1356180-1-bhelgaas@google.com --- drivers/pci/Kconfig | 57 --------------------------------------------- drivers/pci/pci.c | 10 -------- 2 files changed, 67 deletions(-) diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index 33c88432b728..0c7408509ba2 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -251,63 +251,6 @@ config PCI_DYNAMIC_OF_NODES Once this option is selected, the device tree nodes will be generated for all PCI bridges. -choice - prompt "PCI Express hierarchy optimization setting" - default PCIE_BUS_DEFAULT - depends on EXPERT - help - MPS (Max Payload Size) and MRRS (Max Read Request Size) are PCIe - device parameters that affect performance and the ability to - support hotplug and peer-to-peer DMA. - - The following choices set the MPS and MRRS optimization strategy - at compile-time. The choices are the same as those offered for - the kernel command-line parameter 'pci', i.e., - 'pci=pcie_bus_tune_off', 'pci=pcie_bus_safe', - 'pci=pcie_bus_perf', and 'pci=pcie_bus_peer2peer'. - - This is a compile-time setting and can be overridden by the above - command-line parameters. If unsure, choose PCIE_BUS_DEFAULT. - -config PCIE_BUS_TUNE_OFF - bool "Tune Off" - help - Use the BIOS defaults; don't touch MPS at all. This is the same - as booting with 'pci=pcie_bus_tune_off'. - -config PCIE_BUS_DEFAULT - bool "Default" - help - Default choice; ensure that the MPS matches upstream bridge. - -config PCIE_BUS_SAFE - bool "Safe" - help - Use largest MPS that boot-time devices support. If you have a - closed system with no possibility of adding new devices, this - will use the largest MPS that's supported by all devices. This - is the same as booting with 'pci=pcie_bus_safe'. - -config PCIE_BUS_PERFORMANCE - bool "Performance" - help - Use MPS and MRRS for best performance. Ensure that a given - device's MPS is no larger than its parent MPS, which allows us to - keep all switches/bridges to the max MPS supported by their - parent. This is the same as booting with 'pci=pcie_bus_perf'. - -config PCIE_BUS_PEER2PEER - bool "Peer2peer" - help - Set MPS = 128 for all devices. MPS configuration effected by the - other options could cause the MPS on one root port to be - different than that of the MPS on another, which may cause - hot-added devices or peer-to-peer DMA to fail. Set MPS to the - smallest possible value (128B) system-wide to avoid these issues. - This is the same as booting with 'pci=pcie_bus_peer2peer'. - -endchoice - config VGA_ARB bool "VGA Arbitration" if EXPERT default y diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8f7cfcc00090..ee1b0e34231c 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -120,17 +120,7 @@ unsigned long pci_hotplug_bus_size = DEFAULT_HOTPLUG_BUS_SIZE; /* PCIe MPS/MRRS strategy; can be overridden by kernel command-line param */ -#ifdef CONFIG_PCIE_BUS_TUNE_OFF -enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_TUNE_OFF; -#elif defined CONFIG_PCIE_BUS_SAFE -enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_SAFE; -#elif defined CONFIG_PCIE_BUS_PERFORMANCE -enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_PERFORMANCE; -#elif defined CONFIG_PCIE_BUS_PEER2PEER -enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_PEER2PEER; -#else enum pcie_bus_config_types pcie_bus_config = PCIE_BUS_DEFAULT; -#endif /* * The default CLS is used if arch didn't set CLS explicitly and not From fb4836551fc7b22f94f051846c60234c31869b64 Mon Sep 17 00:00:00 2001 From: josh ziegler Date: Mon, 20 Apr 2026 21:20:59 -0400 Subject: [PATCH 008/168] Documentation: PCI: Fix typos Fix "chose" -> "choose" in pci.rst Fix "result an" -> "result in an" in pciebus-howto.rst Signed-off-by: josh ziegler Signed-off-by: Bjorn Helgaas Acked-by: Randy Dunlap Link: https://patch.msgid.link/20260421012059.251492-1-joshziegler76@gmail.com --- Documentation/PCI/pci.rst | 2 +- Documentation/PCI/pciebus-howto.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/PCI/pci.rst b/Documentation/PCI/pci.rst index f4d2662871ab..be35e9a1ee75 100644 --- a/Documentation/PCI/pci.rst +++ b/Documentation/PCI/pci.rst @@ -338,7 +338,7 @@ the PCI_IRQ_MSI and PCI_IRQ_MSIX flags will fail, so try to always specify PCI_IRQ_INTX as well. Drivers that have different interrupt handlers for MSI/MSI-X and -legacy INTx should chose the right one based on the msi_enabled +legacy INTx should choose the right one based on the msi_enabled and msix_enabled flags in the pci_dev structure after calling pci_alloc_irq_vectors. diff --git a/Documentation/PCI/pciebus-howto.rst b/Documentation/PCI/pciebus-howto.rst index 375d9ce171f6..9cc133ccdeec 100644 --- a/Documentation/PCI/pciebus-howto.rst +++ b/Documentation/PCI/pciebus-howto.rst @@ -97,7 +97,7 @@ register its service with the PCI Express Port Bus driver (see section 5.2.1 & 5.2.2). It is important that a service driver initializes the pcie_port_service_driver data structure, included in header file /include/linux/pcieport_if.h, before calling these APIs. -Failure to do so will result an identity mismatch, which prevents +Failure to do so will result in an identity mismatch, which prevents the PCI Express Port Bus driver from loading a service driver. pcie_port_service_register From cc07b903a646bf6592182b27c63457d92f128125 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:08 +0900 Subject: [PATCH 009/168] PCI: endpoint: Add auxiliary resource query API Endpoint controller drivers may integrate auxiliary blocks (e.g. DMA engines) whose register windows and descriptor memories metadata need to be exposed to a remote peer. Endpoint function drivers need a generic way to discover such resources without hard-coding controller-specific helpers. Add pci_epc_get_aux_resources_count() / pci_epc_get_aux_resources() and the corresponding pci_epc_ops callbacks. The count helper returns the number of available resources, while the get helper fills a caller-provided array of resources described by type, physical address and size, plus type-specific metadata. Suggested-by: Manivannan Sadhasivam Suggested-by: Frank Li Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260414141514.1341429-2-den@valinux.co.jp --- drivers/pci/endpoint/pci-epc-core.c | 80 +++++++++++++++++++++++++++++ include/linux/pci-epc.h | 54 +++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/drivers/pci/endpoint/pci-epc-core.c b/drivers/pci/endpoint/pci-epc-core.c index 6c3c58185fc5..831b40458dcd 100644 --- a/drivers/pci/endpoint/pci-epc-core.c +++ b/drivers/pci/endpoint/pci-epc-core.c @@ -156,6 +156,86 @@ const struct pci_epc_features *pci_epc_get_features(struct pci_epc *epc, } EXPORT_SYMBOL_GPL(pci_epc_get_features); +/** + * pci_epc_get_aux_resources_count() - get the number of EPC-provided auxiliary resources + * @epc: EPC device + * @func_no: function number + * @vfunc_no: virtual function number + * + * Some EPC backends integrate auxiliary blocks (e.g. DMA engines) whose control + * registers and/or descriptor memories can be exposed to the host by mapping + * them into BAR space. This helper queries how many such resources the backend + * provides. + * + * Return: the number of available resources on success, -EOPNOTSUPP if the + * backend does not support auxiliary resource queries, or another -errno on + * failure. + */ +int pci_epc_get_aux_resources_count(struct pci_epc *epc, u8 func_no, + u8 vfunc_no) +{ + int count; + + if (!epc || !epc->ops) + return -EINVAL; + + if (!pci_epc_function_is_valid(epc, func_no, vfunc_no)) + return -EINVAL; + + if (!epc->ops->get_aux_resources_count) + return -EOPNOTSUPP; + + mutex_lock(&epc->lock); + count = epc->ops->get_aux_resources_count(epc, func_no, + vfunc_no); + mutex_unlock(&epc->lock); + + return count; +} +EXPORT_SYMBOL_GPL(pci_epc_get_aux_resources_count); + +/** + * pci_epc_get_aux_resources() - query EPC-provided auxiliary resources + * @epc: EPC device + * @func_no: function number + * @vfunc_no: virtual function number + * @resources: output array + * @num_resources: size of @resources array in entries + * + * Some EPC backends integrate auxiliary blocks (e.g. DMA engines) whose control + * registers and/or descriptor memories can be exposed to the host by mapping + * them into BAR space. This helper queries the backend for such resources. + * + * Return: 0 on success, -EOPNOTSUPP if the backend does not support auxiliary + * resource queries, or another -errno on failure. + */ +int pci_epc_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epc_aux_resource *resources, + int num_resources) +{ + int ret; + + if (!resources || num_resources <= 0) + return -EINVAL; + + if (!epc || !epc->ops) + return -EINVAL; + + if (!pci_epc_function_is_valid(epc, func_no, vfunc_no)) + return -EINVAL; + + if (!epc->ops->get_aux_resources) + return -EOPNOTSUPP; + + mutex_lock(&epc->lock); + ret = epc->ops->get_aux_resources(epc, func_no, vfunc_no, resources, + num_resources); + mutex_unlock(&epc->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(pci_epc_get_aux_resources); + /** * pci_epc_stop() - stop the PCI link * @epc: the link of the EPC device that has to be stopped diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h index 1eca1264815b..f247cf9bcf1a 100644 --- a/include/linux/pci-epc.h +++ b/include/linux/pci-epc.h @@ -61,6 +61,47 @@ struct pci_epc_map { void __iomem *virt_addr; }; +/** + * enum pci_epc_aux_resource_type - auxiliary resource type identifiers + * @PCI_EPC_AUX_DOORBELL_MMIO: Doorbell MMIO, that might be outside the DMA + * controller register window + * + * EPC backends may expose auxiliary blocks (e.g. DMA engines) by mapping their + * register windows and descriptor memories into BAR space. This enum + * identifies the type of each exposable resource. + */ +enum pci_epc_aux_resource_type { + PCI_EPC_AUX_DOORBELL_MMIO, +}; + +/** + * struct pci_epc_aux_resource - a physical auxiliary resource that may be + * exposed for peer use + * @type: resource type, see enum pci_epc_aux_resource_type + * @phys_addr: physical base address of the resource + * @size: size of the resource in bytes + * @bar: BAR number where this resource is already exposed to the RC + * (NO_BAR if not) + * @bar_offset: offset within @bar where the resource starts (valid iff + * @bar != NO_BAR) + * @u: type-specific metadata + */ +struct pci_epc_aux_resource { + enum pci_epc_aux_resource_type type; + phys_addr_t phys_addr; + resource_size_t size; + enum pci_barno bar; + resource_size_t bar_offset; + + union { + /* PCI_EPC_AUX_DOORBELL_MMIO */ + struct { + int irq; /* IRQ number for the doorbell handler */ + u32 data; /* write value to ring the doorbell */ + } db_mmio; + } u; +}; + /** * struct pci_epc_ops - set of function pointers for performing EPC operations * @write_header: ops to populate configuration space header @@ -84,6 +125,9 @@ struct pci_epc_map { * @start: ops to start the PCI link * @stop: ops to stop the PCI link * @get_features: ops to get the features supported by the EPC + * @get_aux_resources_count: ops to get the number of controller-owned + * auxiliary resources + * @get_aux_resources: ops to retrieve controller-owned auxiliary resources * @owner: the module owner containing the ops */ struct pci_epc_ops { @@ -115,6 +159,11 @@ struct pci_epc_ops { void (*stop)(struct pci_epc *epc); const struct pci_epc_features* (*get_features)(struct pci_epc *epc, u8 func_no, u8 vfunc_no); + int (*get_aux_resources_count)(struct pci_epc *epc, u8 func_no, + u8 vfunc_no); + int (*get_aux_resources)(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epc_aux_resource *resources, + int num_resources); struct module *owner; }; @@ -343,6 +392,11 @@ int pci_epc_start(struct pci_epc *epc); void pci_epc_stop(struct pci_epc *epc); const struct pci_epc_features *pci_epc_get_features(struct pci_epc *epc, u8 func_no, u8 vfunc_no); +int pci_epc_get_aux_resources_count(struct pci_epc *epc, u8 func_no, + u8 vfunc_no); +int pci_epc_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epc_aux_resource *resources, + int num_resources); enum pci_barno pci_epc_get_first_free_bar(const struct pci_epc_features *epc_features); enum pci_barno pci_epc_get_next_free_bar(const struct pci_epc_features From bfb9502651f689c338ba3e8aeb07d31c46e0cdf6 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:09 +0900 Subject: [PATCH 010/168] PCI: dwc: Record integrated eDMA register window Some DesignWare PCIe controllers integrate an eDMA block whose registers are located in a dedicated register window. The EP-side aux-resource code exposes an interrupt-emulation doorbell register (DOORBELL_MMIO) from that window. Its location is derived from the start of the eDMA register window plus the doorbell offset already provided by dw-edma, and the window size is used to validate the computed register location. Record the physical base and size of the integrated eDMA register window in struct dw_pcie so the EP-side DesignWare aux-resource provider can construct that doorbell resource. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Tested-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260414141514.1341429-3-den@valinux.co.jp --- drivers/pci/controller/dwc/pcie-designware.c | 4 ++++ drivers/pci/controller/dwc/pcie-designware.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c11cf61b8319..22164e0068a9 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -162,8 +162,12 @@ int dw_pcie_get_resources(struct dw_pcie *pci) pci->edma.reg_base = devm_ioremap_resource(pci->dev, res); if (IS_ERR(pci->edma.reg_base)) return PTR_ERR(pci->edma.reg_base); + pci->edma_reg_phys = res->start; + pci->edma_reg_size = resource_size(res); } else if (pci->atu_size >= 2 * DEFAULT_DBI_DMA_OFFSET) { pci->edma.reg_base = pci->atu_base + DEFAULT_DBI_DMA_OFFSET; + pci->edma_reg_phys = pci->atu_phys_addr + DEFAULT_DBI_DMA_OFFSET; + pci->edma_reg_size = pci->atu_size - DEFAULT_DBI_DMA_OFFSET; } } diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 3e69ef60165b..f3314ceff8d7 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -544,6 +544,8 @@ struct dw_pcie { int max_link_speed; u8 n_fts[2]; struct dw_edma_chip edma; + phys_addr_t edma_reg_phys; + resource_size_t edma_reg_size; bool l1ss_support; /* L1 PM Substates support */ struct clk_bulk_data app_clks[DW_PCIE_NUM_APP_CLKS]; struct clk_bulk_data core_clks[DW_PCIE_NUM_CORE_CLKS]; From ec2075cfc629eed09d8865621be76ff2197781d6 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:10 +0900 Subject: [PATCH 011/168] PCI: dwc: ep: Expose integrated eDMA resources via EPC aux-resource API Implement the EPC aux-resource API for DesignWare endpoint controllers with integrated eDMA. Currently, only report an interrupt-emulation doorbell register (PCI_EPC_AUX_DOORBELL_MMIO), including its Linux IRQ and the write data needed to trigger the interrupt. If the DMA controller MMIO window is already exposed via a platform-owned fixed BAR subregion, also provide the BAR number and offset so EPF drivers can reuse it without reprogramming the BAR. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260414141514.1341429-4-den@valinux.co.jp --- .../pci/controller/dwc/pcie-designware-ep.c | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index d4dc3b24da60..e4c6f7193495 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "pcie-designware.h" @@ -817,6 +818,122 @@ dw_pcie_ep_get_features(struct pci_epc *epc, u8 func_no, u8 vfunc_no) return ep->ops->get_features(ep); } +static const struct pci_epc_bar_rsvd_region * +dw_pcie_ep_find_bar_rsvd_region(struct dw_pcie_ep *ep, + enum pci_epc_bar_rsvd_region_type type, + enum pci_barno *bar, + resource_size_t *bar_offset) +{ + const struct pci_epc_features *features; + const struct pci_epc_bar_desc *bar_desc; + const struct pci_epc_bar_rsvd_region *r; + int i, j; + + if (!ep->ops->get_features) + return NULL; + + features = ep->ops->get_features(ep); + if (!features) + return NULL; + + for (i = BAR_0; i <= BAR_5; i++) { + bar_desc = &features->bar[i]; + + if (!bar_desc->nr_rsvd_regions || !bar_desc->rsvd_regions) + continue; + + for (j = 0; j < bar_desc->nr_rsvd_regions; j++) { + r = &bar_desc->rsvd_regions[j]; + + if (r->type != type) + continue; + + if (bar) + *bar = i; + if (bar_offset) + *bar_offset = r->offset; + return r; + } + } + + return NULL; +} + +static int +dw_pcie_ep_get_aux_resources_count(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_edma_chip *edma = &pci->edma; + + if (!pci->edma_reg_size) + return 0; + + if (edma->db_offset == ~0) + return 0; + + return 1; +} + +static int +dw_pcie_ep_get_aux_resources(struct pci_epc *epc, u8 func_no, u8 vfunc_no, + struct pci_epc_aux_resource *resources, + int num_resources) +{ + struct dw_pcie_ep *ep = epc_get_drvdata(epc); + struct dw_pcie *pci = to_dw_pcie_from_ep(ep); + const struct pci_epc_bar_rsvd_region *rsvd; + struct dw_edma_chip *edma = &pci->edma; + enum pci_barno dma_ctrl_bar = NO_BAR; + resource_size_t db_offset = edma->db_offset; + resource_size_t dma_ctrl_bar_offset = 0; + resource_size_t dma_reg_size; + int count; + + count = dw_pcie_ep_get_aux_resources_count(epc, func_no, vfunc_no); + if (count < 0) + return count; + + if (num_resources < count) + return -ENOSPC; + + if (!count) + return 0; + + dma_reg_size = pci->edma_reg_size; + + rsvd = dw_pcie_ep_find_bar_rsvd_region(ep, + PCI_EPC_BAR_RSVD_DMA_CTRL_MMIO, + &dma_ctrl_bar, + &dma_ctrl_bar_offset); + if (rsvd && rsvd->size < dma_reg_size) + dma_reg_size = rsvd->size; + + /* + * For interrupt-emulation doorbells, report a standalone resource + * instead of bundling it into the DMA controller MMIO resource. + */ + if (range_end_overflows_t(resource_size_t, db_offset, + sizeof(u32), dma_reg_size)) + return -EINVAL; + + resources[0] = (struct pci_epc_aux_resource) { + .type = PCI_EPC_AUX_DOORBELL_MMIO, + .phys_addr = pci->edma_reg_phys + db_offset, + .size = sizeof(u32), + .bar = dma_ctrl_bar, + .bar_offset = dma_ctrl_bar != NO_BAR ? + dma_ctrl_bar_offset + db_offset : 0, + .u.db_mmio = { + .irq = edma->db_irq, + .data = 0, /* write 0 to assert */ + }, + }; + + return 0; +} + static const struct pci_epc_ops epc_ops = { .write_header = dw_pcie_ep_write_header, .set_bar = dw_pcie_ep_set_bar, @@ -832,6 +949,8 @@ static const struct pci_epc_ops epc_ops = { .start = dw_pcie_ep_start, .stop = dw_pcie_ep_stop, .get_features = dw_pcie_ep_get_features, + .get_aux_resources_count = dw_pcie_ep_get_aux_resources_count, + .get_aux_resources = dw_pcie_ep_get_aux_resources, }; /** From a3a079e5c5b7748f206c4baeb92593f9eca3184a Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:11 +0900 Subject: [PATCH 012/168] PCI: endpoint: pci-ep-msi: Refactor doorbell allocation for new backends Prepare pci-ep-msi for non-MSI doorbell backends. Factor MSI doorbell allocation into a helper and extend struct pci_epf_doorbell_msg with: - irq_flags: required IRQ request flags (e.g. IRQF_SHARED for some backends) - type: doorbell backend type - bar/offset: pre-exposed doorbell target location, if any Initialize these fields for the existing MSI-backed doorbell implementation. Also add PCI_EPF_DOORBELL_EMBEDDED type, which is to be implemented in a follow-up patch. No functional changes. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Tested-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260414141514.1341429-5-den@valinux.co.jp --- drivers/pci/endpoint/pci-ep-msi.c | 54 ++++++++++++++++++++++--------- include/linux/pci-epf.h | 23 +++++++++++-- 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c index 1395919571f8..85fe46103220 100644 --- a/drivers/pci/endpoint/pci-ep-msi.c +++ b/drivers/pci/endpoint/pci-ep-msi.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -35,23 +36,13 @@ static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg) pci_epc_put(epc); } -int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db) +static int pci_epf_alloc_doorbell_msi(struct pci_epf *epf, u16 num_db) { - struct pci_epc *epc = epf->epc; + struct pci_epf_doorbell_msg *msg; struct device *dev = &epf->dev; + struct pci_epc *epc = epf->epc; struct irq_domain *domain; - void *msg; - int ret; - int i; - - /* TODO: Multi-EPF support */ - if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) { - dev_err(dev, "MSI doorbell doesn't support multiple EPF\n"); - return -EINVAL; - } - - if (epf->db_msg) - return -EBUSY; + int ret, i; domain = of_msi_map_get_device_domain(epc->dev.parent, 0, DOMAIN_BUS_PLATFORM_MSI); @@ -74,6 +65,12 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db) if (!msg) return -ENOMEM; + for (i = 0; i < num_db; i++) + msg[i] = (struct pci_epf_doorbell_msg) { + .type = PCI_EPF_DOORBELL_MSI, + .bar = NO_BAR, + }; + epf->num_db = num_db; epf->db_msg = msg; @@ -90,13 +87,40 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db) for (i = 0; i < num_db; i++) epf->db_msg[i].virq = msi_get_virq(epc->dev.parent, i); + return 0; +} + +int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db) +{ + struct pci_epc *epc = epf->epc; + struct device *dev = &epf->dev; + int ret; + + /* TODO: Multi-EPF support */ + if (list_first_entry_or_null(&epc->pci_epf, struct pci_epf, list) != epf) { + dev_err(dev, "Doorbell doesn't support multiple EPF\n"); + return -EINVAL; + } + + if (epf->db_msg) + return -EBUSY; + + ret = pci_epf_alloc_doorbell_msi(epf, num_db); + if (!ret) + return 0; + + dev_err(dev, "Failed to allocate doorbell: %d\n", ret); return ret; } EXPORT_SYMBOL_GPL(pci_epf_alloc_doorbell); void pci_epf_free_doorbell(struct pci_epf *epf) { - platform_device_msi_free_irqs_all(epf->epc->dev.parent); + if (!epf->db_msg) + return; + + if (epf->db_msg[0].type == PCI_EPF_DOORBELL_MSI) + platform_device_msi_free_irqs_all(epf->epc->dev.parent); kfree(epf->db_msg); epf->db_msg = NULL; diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index 7737a7c03260..cd747447a1ea 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -152,14 +152,33 @@ struct pci_epf_bar { struct pci_epf_bar_submap *submap; }; +enum pci_epf_doorbell_type { + PCI_EPF_DOORBELL_MSI = 0, + PCI_EPF_DOORBELL_EMBEDDED, +}; + /** * struct pci_epf_doorbell_msg - represents doorbell message - * @msg: MSI message - * @virq: IRQ number of this doorbell MSI message + * @msg: Doorbell address/data pair to be mapped into BAR space. + * For MSI-backed doorbells this is the MSI message, while for + * "embedded" doorbells this represents an MMIO write that asserts + * an interrupt on the EP side. + * @virq: IRQ number of this doorbell message + * @irq_flags: Required flags for request_irq()/request_threaded_irq(). + * Callers may OR-in additional flags (e.g. IRQF_ONESHOT). + * @type: Doorbell type. + * @bar: BAR number where the doorbell target is already exposed to the RC + * (NO_BAR if not) + * @offset: offset within @bar for the doorbell target (valid iff + * @bar != NO_BAR) */ struct pci_epf_doorbell_msg { struct msi_msg msg; int virq; + unsigned long irq_flags; + enum pci_epf_doorbell_type type; + enum pci_barno bar; + resource_size_t offset; }; /** From 432b936fa6b7a8ad5d7ea55be9522c6dc24b9554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:07 +0300 Subject: [PATCH 013/168] PCI: Log all resource claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are two ways to graft resource into resource tree in PCI, pci_assign_resource() and pci_claim_resource(). Only the former logs the action, which complicated troubleshooting the cases where resources are assigned by pci_claim_resource(), which mostly assigns the addresses inherited from the FW. Add logging into pci_claim_resource() to make troubleshooting easier. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-2-ilpo.jarvinen@linux.intel.com --- drivers/pci/setup-res.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index fbc05cda96ee..0d203325562b 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -167,6 +167,8 @@ int pci_claim_resource(struct pci_dev *dev, int resource) return -EBUSY; } + pci_dbg(dev, "%s %pR: claiming\n", res_name, res); + return 0; } EXPORT_SYMBOL(pci_claim_resource); From 0c3f82a584082a0d2e09b65e0a2e9cdcf9046d0d Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:12 +0900 Subject: [PATCH 014/168] PCI: endpoint: pci-epf-vntb: Reuse pre-exposed doorbells and IRQ flags Support doorbell backends where the doorbell target is already exposed via a platform-owned fixed BAR mapping and/or where the doorbell IRQ must be requested with specific flags. When pci_epf_alloc_doorbell() provides db_msg[].bar/offset, reuse the pre-exposed BAR window and skip programming a new inbound mapping. Also honor db_msg[].irq_flags when requesting the doorbell IRQ. Multiple doorbells may share the same Linux IRQ. Avoid duplicate request_irq() calls by requesting each unique virq once. Make pci-epf-vntb work with platform-defined or embedded doorbell backends without exposing backend-specific details to the consumer layer. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260414141514.1341429-6-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 61 ++++++++++++++++++- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 2256c3062b1a..b493a300da4d 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -134,6 +134,11 @@ struct epf_ntb { u16 vntb_vid; bool linkup; + + /* + * True when doorbells are interrupt-driven (MSI or embedded), false + * when polled. + */ bool msi_doorbell; u32 spad_size; @@ -517,6 +522,17 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) return 0; } +static bool epf_ntb_db_irq_is_duplicated(const struct pci_epf *epf, unsigned int idx) +{ + unsigned int i; + + for (i = 0; i < idx; i++) + if (epf->db_msg[i].virq == epf->db_msg[idx].virq) + return true; + + return false; +} + static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb, struct pci_epf_bar *db_bar, const struct pci_epc_features *epc_features, @@ -533,9 +549,24 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb, if (ret) return ret; + /* + * The doorbell target may already be exposed by a platform-owned fixed + * BAR. In that case, we must reuse it and the requested db_bar must + * match. + */ + if (epf->db_msg[0].bar != NO_BAR && epf->db_msg[0].bar != barno) { + ret = -EINVAL; + goto err_free_doorbell; + } + for (req = 0; req < ntb->db_count; req++) { + /* Avoid requesting duplicate handlers */ + if (epf_ntb_db_irq_is_duplicated(epf, req)) + continue; + ret = request_irq(epf->db_msg[req].virq, epf_ntb_doorbell_handler, - 0, "pci_epf_vntb_db", ntb); + epf->db_msg[req].irq_flags, "pci_epf_vntb_db", + ntb); if (ret) { dev_err(&epf->dev, @@ -545,6 +576,22 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb, } } + if (epf->db_msg[0].bar != NO_BAR) { + for (i = 0; i < ntb->db_count; i++) { + msg = &epf->db_msg[i].msg; + + if (epf->db_msg[i].bar != barno) { + ret = -EINVAL; + goto err_free_irq; + } + + ntb->reg->db_data[i] = msg->data; + ntb->reg->db_offset[i] = epf->db_msg[i].offset; + } + goto out; + } + + /* Program inbound mapping for the doorbell */ msg = &epf->db_msg[0].msg; high = 0; @@ -591,6 +638,7 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb, ntb->reg->db_offset[i] = offset; } +out: ntb->reg->db_entry_size = 0; ntb->msi_doorbell = true; @@ -598,9 +646,13 @@ static int epf_ntb_db_bar_init_msi_doorbell(struct epf_ntb *ntb, return 0; err_free_irq: - for (req--; req >= 0; req--) + for (req--; req >= 0; req--) { + if (epf_ntb_db_irq_is_duplicated(epf, req)) + continue; free_irq(epf->db_msg[req].virq, ntb); + } +err_free_doorbell: pci_epf_free_doorbell(ntb->epf); return ret; } @@ -666,8 +718,11 @@ static void epf_ntb_db_bar_clear(struct epf_ntb *ntb) if (ntb->msi_doorbell) { int i; - for (i = 0; i < ntb->db_count; i++) + for (i = 0; i < ntb->db_count; i++) { + if (epf_ntb_db_irq_is_duplicated(ntb->epf, i)) + continue; free_irq(ntb->epf->db_msg[i].virq, ntb); + } } if (ntb->epf->db_msg) From 8fda2dd209d34396cf49504e2c8dd55d182b14bc Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:13 +0900 Subject: [PATCH 015/168] PCI: endpoint: pci-epf-test: Reuse pre-exposed doorbell targets pci-epf-test advertises the doorbell target to the RC as a BAR number and an offset, and the RC rings the doorbell with a single DWORD MMIO write. Some doorbell backends may report that the doorbell target is already exposed via a platform-owned fixed BAR (db_msg[0].bar/offset). In that case, reuse the pre-exposed window and do not reprogram the BAR with pci_epc_set_bar(). Also honor db_msg[0].irq_flags when requesting the doorbell IRQ, and only restore the original BAR mapping on disable if pci-epf-test programmed it. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam [bhelgaas: wrap comment] Signed-off-by: Bjorn Helgaas Tested-by: Niklas Cassel Reviewed-by: Frank Li Link: https://patch.msgid.link/20260414141514.1341429-7-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-test.c | 86 +++++++++++++------ 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c index 591d301fa89d..4802d4f80f78 100644 --- a/drivers/pci/endpoint/functions/pci-epf-test.c +++ b/drivers/pci/endpoint/functions/pci-epf-test.c @@ -94,6 +94,7 @@ struct pci_epf_test { bool dma_private; const struct pci_epc_features *epc_features; struct pci_epf_bar db_bar; + bool db_bar_programmed; size_t bar_size[PCI_STD_NUM_BARS]; }; @@ -733,7 +734,9 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test, { u32 status = le32_to_cpu(reg->status); struct pci_epf *epf = epf_test->epf; + struct pci_epf_doorbell_msg *db; struct pci_epc *epc = epf->epc; + unsigned long irq_flags; struct msi_msg *msg; enum pci_barno bar; size_t offset; @@ -743,13 +746,28 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test, if (ret) goto set_status_err; - msg = &epf->db_msg[0].msg; - bar = pci_epc_get_next_free_bar(epf_test->epc_features, epf_test->test_reg_bar + 1); - if (bar < BAR_0) - goto err_doorbell_cleanup; + db = &epf->db_msg[0]; + msg = &db->msg; + epf_test->db_bar_programmed = false; + + if (db->bar != NO_BAR) { + /* + * The doorbell target is already exposed via a platform-owned + * fixed BAR + */ + bar = db->bar; + offset = db->offset; + } else { + bar = pci_epc_get_next_free_bar(epf_test->epc_features, + epf_test->test_reg_bar + 1); + if (bar < BAR_0) + goto err_doorbell_cleanup; + } + + irq_flags = epf->db_msg[0].irq_flags | IRQF_ONESHOT; ret = request_threaded_irq(epf->db_msg[0].virq, NULL, - pci_epf_test_doorbell_handler, IRQF_ONESHOT, + pci_epf_test_doorbell_handler, irq_flags, "pci-ep-test-doorbell", epf_test); if (ret) { dev_err(&epf->dev, @@ -761,22 +779,30 @@ static void pci_epf_test_enable_doorbell(struct pci_epf_test *epf_test, reg->doorbell_data = cpu_to_le32(msg->data); reg->doorbell_bar = cpu_to_le32(bar); - msg = &epf->db_msg[0].msg; - ret = pci_epf_align_inbound_addr(epf, bar, ((u64)msg->address_hi << 32) | msg->address_lo, - &epf_test->db_bar.phys_addr, &offset); + if (db->bar == NO_BAR) { + ret = pci_epf_align_inbound_addr(epf, bar, + ((u64)msg->address_hi << 32) | + msg->address_lo, + &epf_test->db_bar.phys_addr, + &offset); - if (ret) - goto err_free_irq; + if (ret) + goto err_free_irq; + } reg->doorbell_offset = cpu_to_le32(offset); - epf_test->db_bar.barno = bar; - epf_test->db_bar.size = epf->bar[bar].size; - epf_test->db_bar.flags = epf->bar[bar].flags; + if (db->bar == NO_BAR) { + epf_test->db_bar.barno = bar; + epf_test->db_bar.size = epf->bar[bar].size; + epf_test->db_bar.flags = epf->bar[bar].flags; - ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf_test->db_bar); - if (ret) - goto err_free_irq; + ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf_test->db_bar); + if (ret) + goto err_free_irq; + + epf_test->db_bar_programmed = true; + } status |= STATUS_DOORBELL_ENABLE_SUCCESS; reg->status = cpu_to_le32(status); @@ -806,17 +832,23 @@ static void pci_epf_test_disable_doorbell(struct pci_epf_test *epf_test, free_irq(epf->db_msg[0].virq, epf_test); pci_epf_test_doorbell_cleanup(epf_test); - /* - * The doorbell feature temporarily overrides the inbound translation - * to point to the address stored in epf_test->db_bar.phys_addr, i.e., - * it calls set_bar() twice without ever calling clear_bar(), as - * calling clear_bar() would clear the BAR's PCI address assigned by - * the host. Thus, when disabling the doorbell, restore the inbound - * translation to point to the memory allocated for the BAR. - */ - ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf->bar[bar]); - if (ret) - goto set_status_err; + if (epf_test->db_bar_programmed) { + /* + * The doorbell feature temporarily overrides the inbound + * translation to point to the address stored in + * epf_test->db_bar.phys_addr, i.e., it calls set_bar() + * twice without ever calling clear_bar(), as calling + * clear_bar() would clear the BAR's PCI address assigned + * by the host. Thus, when disabling the doorbell, restore + * the inbound translation to point to the memory allocated + * for the BAR. + */ + ret = pci_epc_set_bar(epc, epf->func_no, epf->vfunc_no, &epf->bar[bar]); + if (ret) + goto set_status_err; + + epf_test->db_bar_programmed = false; + } status |= STATUS_DOORBELL_DISABLE_SUCCESS; reg->status = cpu_to_le32(status); From e4f26243953fd3e87df93786b40293ca3a6a465e Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Tue, 14 Apr 2026 23:15:14 +0900 Subject: [PATCH 016/168] PCI: endpoint: pci-ep-msi: Add embedded doorbell fallback Some endpoint platforms cannot use platform MSI / GIC ITS to implement EP-side doorbells. In those cases, EPF drivers cannot provide an interrupt-driven doorbell and often fall back to polling. Add an "embedded" doorbell backend that uses a controller-integrated doorbell target (e.g. DesignWare integrated eDMA interrupt-emulation doorbell). The backend locates the doorbell register and a corresponding Linux IRQ via the EPC aux-resource API. If the doorbell register is already exposed via a fixed BAR mapping, provide BAR+offset. Otherwise provide the DMA address returned by dma_map_resource() (which may be an IOVA when an IOMMU is enabled) so EPF drivers can map it into BAR space. When MSI doorbell allocation fails with -ENODEV, pci_epf_alloc_doorbell() falls back to this embedded backend. Suggested-by: Manivannan Sadhasivam Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260414141514.1341429-8-den@valinux.co.jp --- drivers/pci/endpoint/pci-ep-msi.c | 131 +++++++++++++++++++++++++++++- include/linux/pci-epf.h | 8 ++ 2 files changed, 136 insertions(+), 3 deletions(-) diff --git a/drivers/pci/endpoint/pci-ep-msi.c b/drivers/pci/endpoint/pci-ep-msi.c index 85fe46103220..0855c7930abb 100644 --- a/drivers/pci/endpoint/pci-ep-msi.c +++ b/drivers/pci/endpoint/pci-ep-msi.c @@ -6,6 +6,8 @@ * Author: Frank Li */ +#include +#include #include #include #include @@ -36,6 +38,109 @@ static void pci_epf_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg) pci_epc_put(epc); } +static int pci_epf_alloc_doorbell_embedded(struct pci_epf *epf, u16 num_db) +{ + const struct pci_epc_aux_resource *doorbell = NULL; + struct pci_epf_doorbell_msg *msg; + struct pci_epc *epc = epf->epc; + size_t map_size = 0, off = 0; + dma_addr_t iova_base = 0; + phys_addr_t phys_base; + int count, ret, i; + u64 addr; + + count = pci_epc_get_aux_resources_count(epc, epf->func_no, + epf->vfunc_no); + if (count < 0) + return count; + if (!count) + return -ENODEV; + + struct pci_epc_aux_resource *res __free(kfree) = + kcalloc(count, sizeof(*res), GFP_KERNEL); + if (!res) + return -ENOMEM; + + ret = pci_epc_get_aux_resources(epc, epf->func_no, epf->vfunc_no, + res, count); + if (ret) + return ret; + + /* TODO: Support multiple DOORBELL_MMIO resources per EPC. */ + for (i = 0; i < count; i++) { + if (res[i].type != PCI_EPC_AUX_DOORBELL_MMIO) + continue; + + doorbell = &res[i]; + break; + } + if (!doorbell) + return -ENODEV; + addr = doorbell->phys_addr; + if (!IS_ALIGNED(addr, sizeof(u32))) + return -EINVAL; + + /* + * Reuse the pre-exposed BAR window if available. Otherwise map the MMIO + * doorbell resource here. Any required IOMMU mapping is handled + * internally, matching the MSI doorbell semantics. + */ + if (doorbell->bar == NO_BAR) { + phys_base = addr & PAGE_MASK; + off = addr - phys_base; + map_size = PAGE_ALIGN(off + sizeof(u32)); + + iova_base = dma_map_resource(epc->dev.parent, phys_base, + map_size, DMA_FROM_DEVICE, 0); + if (dma_mapping_error(epc->dev.parent, iova_base)) + return -EIO; + + addr = iova_base + off; + } + + msg = kcalloc(num_db, sizeof(*msg), GFP_KERNEL); + if (!msg) { + ret = -ENOMEM; + goto err_unmap; + } + + /* + * Embedded doorbell backends (e.g. DesignWare eDMA interrupt emulation) + * typically provide a single IRQ and do not offer per-doorbell + * distinguishable address/data pairs. The EPC aux resource therefore + * exposes one DOORBELL_MMIO entry (u.db_mmio.irq). + * + * Still, pci_epf_alloc_doorbell() allows requesting multiple doorbells. + * For such backends we replicate the same address/data for each entry + * and mark the IRQ as shared (IRQF_SHARED). Consumers must treat them + * as equivalent "kick" doorbells. + */ + for (i = 0; i < num_db; i++) + msg[i] = (struct pci_epf_doorbell_msg) { + .msg.address_lo = (u32)addr, + .msg.address_hi = (u32)(addr >> 32), + .msg.data = doorbell->u.db_mmio.data, + .virq = doorbell->u.db_mmio.irq, + .irq_flags = IRQF_SHARED, + .type = PCI_EPF_DOORBELL_EMBEDDED, + .bar = doorbell->bar, + .offset = (doorbell->bar == NO_BAR) ? 0 : + doorbell->bar_offset, + .iova_base = iova_base, + .iova_size = map_size, + }; + + epf->num_db = num_db; + epf->db_msg = msg; + return 0; + +err_unmap: + if (map_size) + dma_unmap_resource(epc->dev.parent, iova_base, map_size, + DMA_FROM_DEVICE, 0); + return ret; +} + static int pci_epf_alloc_doorbell_msi(struct pci_epf *epf, u16 num_db) { struct pci_epf_doorbell_msg *msg; @@ -109,18 +214,38 @@ int pci_epf_alloc_doorbell(struct pci_epf *epf, u16 num_db) if (!ret) return 0; - dev_err(dev, "Failed to allocate doorbell: %d\n", ret); - return ret; + /* + * Fall back to embedded doorbell only when platform MSI is unavailable + * for this EPC. + */ + if (ret != -ENODEV) + return ret; + + ret = pci_epf_alloc_doorbell_embedded(epf, num_db); + if (ret) { + dev_err(dev, "Failed to allocate doorbell: %d\n", ret); + return ret; + } + + dev_info(dev, "Using embedded (DMA) doorbell fallback\n"); + return 0; } EXPORT_SYMBOL_GPL(pci_epf_alloc_doorbell); void pci_epf_free_doorbell(struct pci_epf *epf) { + struct pci_epf_doorbell_msg *msg0; + struct pci_epc *epc = epf->epc; + if (!epf->db_msg) return; - if (epf->db_msg[0].type == PCI_EPF_DOORBELL_MSI) + msg0 = &epf->db_msg[0]; + if (msg0->type == PCI_EPF_DOORBELL_MSI) platform_device_msi_free_irqs_all(epf->epc->dev.parent); + else if (msg0->type == PCI_EPF_DOORBELL_EMBEDDED && msg0->iova_size) + dma_unmap_resource(epc->dev.parent, msg0->iova_base, + msg0->iova_size, DMA_FROM_DEVICE, 0); kfree(epf->db_msg); epf->db_msg = NULL; diff --git a/include/linux/pci-epf.h b/include/linux/pci-epf.h index cd747447a1ea..8a6c64a35890 100644 --- a/include/linux/pci-epf.h +++ b/include/linux/pci-epf.h @@ -171,6 +171,12 @@ enum pci_epf_doorbell_type { * (NO_BAR if not) * @offset: offset within @bar for the doorbell target (valid iff * @bar != NO_BAR) + * @iova_base: Internal: base DMA address returned by dma_map_resource() for the + * embedded doorbell MMIO window (used only for unmapping). Valid + * when @type is PCI_EPF_DOORBELL_EMBEDDED and @iova_size is + * non-zero. + * @iova_size: Internal: size of the dma_map_resource() mapping at @iova_base. + * Zero when no mapping was created (e.g. pre-exposed fixed BAR). */ struct pci_epf_doorbell_msg { struct msi_msg msg; @@ -179,6 +185,8 @@ struct pci_epf_doorbell_msg { enum pci_epf_doorbell_type type; enum pci_barno bar; resource_size_t offset; + dma_addr_t iova_base; + size_t iova_size; }; /** From 42ec65b46a4fc7565d48daa42bf025fdc67800eb Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 1 May 2026 00:24:05 +0800 Subject: [PATCH 017/168] PCI: Use FIELD_MODIFY() instead of open-coding it Use FIELD_MODIFY() to remove open-coded bit manipulation. No functional change intended. Signed-off-by: Hans Zhang <18255117159@163.com> [bhelgaas: squash together] Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li # pcie-nxp-s32g.c Link: https://patch.msgid.link/20260430162420.42839-2-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-3-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-4-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-5-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-6-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-7-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-8-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-9-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-10-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-11-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-12-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-13-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-14-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-15-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-16-18255117159@163.com Link: https://patch.msgid.link/20260430162420.42839-17-18255117159@163.com --- drivers/pci/controller/dwc/pcie-al.c | 12 ++---- .../controller/dwc/pcie-designware-debugfs.c | 23 ++++------- .../pci/controller/dwc/pcie-designware-ep.c | 3 +- drivers/pci/controller/dwc/pcie-designware.c | 3 +- drivers/pci/controller/dwc/pcie-eswin.c | 3 +- drivers/pci/controller/dwc/pcie-nxp-s32g.c | 3 +- drivers/pci/controller/dwc/pcie-qcom-common.c | 40 +++++++------------ drivers/pci/controller/dwc/pcie-qcom-ep.c | 6 +-- drivers/pci/controller/dwc/pcie-tegra194.c | 8 ++-- drivers/pci/controller/pci-mvebu.c | 3 +- drivers/pci/controller/pcie-mediatek-gen3.c | 3 +- drivers/pci/ide.c | 6 +-- drivers/pci/iov.c | 3 +- drivers/pci/msi/msi.c | 11 ++--- drivers/pci/pci.c | 3 +- drivers/pci/pcie/ptm.c | 3 +- drivers/pci/rebar.c | 6 +-- drivers/pci/setup-cardbus.c | 3 +- drivers/pci/tph.c | 10 ++--- 19 files changed, 51 insertions(+), 101 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-al.c b/drivers/pci/controller/dwc/pcie-al.c index 345c281c74fe..ef8c8ce7c78c 100644 --- a/drivers/pci/controller/dwc/pcie-al.c +++ b/drivers/pci/controller/dwc/pcie-al.c @@ -253,7 +253,6 @@ static int al_pcie_config_prepare(struct al_pcie *pcie) u8 subordinate_bus; u8 secondary_bus; u32 cfg_control; - u32 reg; ft = resource_list_first_type(&pp->bridge->windows, IORESOURCE_BUS); if (!ft) @@ -285,14 +284,9 @@ static int al_pcie_config_prepare(struct al_pcie *pcie) CFG_CONTROL; cfg_control = al_pcie_controller_readl(pcie, cfg_control_offset); - - reg = cfg_control & - ~(CFG_CONTROL_SEC_BUS_MASK | CFG_CONTROL_SUBBUS_MASK); - - reg |= FIELD_PREP(CFG_CONTROL_SUBBUS_MASK, subordinate_bus) | - FIELD_PREP(CFG_CONTROL_SEC_BUS_MASK, secondary_bus); - - al_pcie_controller_writel(pcie, cfg_control_offset, reg); + FIELD_MODIFY(CFG_CONTROL_SUBBUS_MASK, &cfg_control, subordinate_bus); + FIELD_MODIFY(CFG_CONTROL_SEC_BUS_MASK, &cfg_control, secondary_bus); + al_pcie_controller_writel(pcie, cfg_control_offset, cfg_control); return 0; } diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c index d0884253be97..945f8f9b6d0e 100644 --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -265,8 +265,7 @@ static ssize_t lane_detect_write(struct file *file, const char __user *buf, return ret; val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG); - val &= ~(LANE_SELECT); - val |= FIELD_PREP(LANE_SELECT, lane); + FIELD_MODIFY(LANE_SELECT, &val, lane); dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + SD_STATUS_L1LANE_REG, val); return count; @@ -339,14 +338,10 @@ static ssize_t err_inj_write(struct file *file, const char __user *buf, val |= ((err_inj_list[pdata->idx].err_inj_type << EINJ_TYPE_SHIFT) & type_mask); val |= FIELD_PREP(EINJ_COUNT, counter); - if (err_group == 1 || err_group == 4) { - val &= ~(EINJ_VAL_DIFF); - val |= FIELD_PREP(EINJ_VAL_DIFF, val_diff); - } - if (err_group == 4) { - val &= ~(EINJ_VC_NUM); - val |= FIELD_PREP(EINJ_VC_NUM, vc_num); - } + if (err_group == 1 || err_group == 4) + FIELD_MODIFY(EINJ_VAL_DIFF, &val, val_diff); + if (err_group == 4) + FIELD_MODIFY(EINJ_VC_NUM, &val, vc_num); dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + ERR_INJ0_OFF + (0x4 * err_group), val); dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + ERR_INJ_ENABLE_REG, (0x1 << err_group)); @@ -362,9 +357,8 @@ static void set_event_number(struct dwc_pcie_rasdes_priv *pdata, val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG); val &= ~EVENT_COUNTER_ENABLE; - val &= ~(EVENT_COUNTER_GROUP_SELECT | EVENT_COUNTER_EVENT_SELECT); - val |= FIELD_PREP(EVENT_COUNTER_GROUP_SELECT, event_list[pdata->idx].group_no); - val |= FIELD_PREP(EVENT_COUNTER_EVENT_SELECT, event_list[pdata->idx].event_no); + FIELD_MODIFY(EVENT_COUNTER_GROUP_SELECT, &val, event_list[pdata->idx].group_no); + FIELD_MODIFY(EVENT_COUNTER_EVENT_SELECT, &val, event_list[pdata->idx].event_no); dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG, val); } @@ -469,8 +463,7 @@ static ssize_t counter_lane_write(struct file *file, const char __user *buf, mutex_lock(&rinfo->reg_event_lock); set_event_number(pdata, pci, rinfo); val = dw_pcie_readl_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG); - val &= ~(EVENT_COUNTER_LANE_SELECT); - val |= FIELD_PREP(EVENT_COUNTER_LANE_SELECT, lane); + FIELD_MODIFY(EVENT_COUNTER_LANE_SELECT, &val, lane); dw_pcie_writel_dbi(pci, rinfo->ras_cap_offset + RAS_DES_EVENT_COUNTER_CTRL_REG, val); mutex_unlock(&rinfo->reg_event_lock); diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index d4dc3b24da60..88e7fc3d5e9d 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -707,8 +707,7 @@ static int dw_pcie_ep_set_msi(struct pci_epc *epc, u8 func_no, u8 vfunc_no, reg = ep_func->msi_cap + PCI_MSI_FLAGS; val = dw_pcie_ep_readw_dbi(ep, func_no, reg); - val &= ~PCI_MSI_FLAGS_QMASK; - val |= FIELD_PREP(PCI_MSI_FLAGS_QMASK, mmc); + FIELD_MODIFY(PCI_MSI_FLAGS_QMASK, &val, mmc); dw_pcie_dbi_ro_wr_en(pci); dw_pcie_ep_writew_dbi(ep, func_no, reg, val); dw_pcie_dbi_ro_wr_dis(pci); diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c11cf61b8319..bcfc7bfcf232 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -938,8 +938,7 @@ static void dw_pcie_link_set_max_link_width(struct dw_pcie *pci, u32 num_lanes) cap = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); lnkcap = dw_pcie_readl_dbi(pci, cap + PCI_EXP_LNKCAP); - lnkcap &= ~PCI_EXP_LNKCAP_MLW; - lnkcap |= FIELD_PREP(PCI_EXP_LNKCAP_MLW, num_lanes); + FIELD_MODIFY(PCI_EXP_LNKCAP_MLW, &lnkcap, num_lanes); dw_pcie_writel_dbi(pci, cap + PCI_EXP_LNKCAP, lnkcap); } diff --git a/drivers/pci/controller/dwc/pcie-eswin.c b/drivers/pci/controller/dwc/pcie-eswin.c index 2845832b3824..ce8d64f8a395 100644 --- a/drivers/pci/controller/dwc/pcie-eswin.c +++ b/drivers/pci/controller/dwc/pcie-eswin.c @@ -211,8 +211,7 @@ static int eswin_pcie_host_init(struct dw_pcie_rp *pp) /* Configure Root Port type */ val = readl_relaxed(pci->elbi_base + PCIEELBI_CTRL0_OFFSET); - val &= ~PCIEELBI_CTRL0_DEV_TYPE; - val |= FIELD_PREP(PCIEELBI_CTRL0_DEV_TYPE, PCI_EXP_TYPE_ROOT_PORT); + FIELD_MODIFY(PCIEELBI_CTRL0_DEV_TYPE, &val, PCI_EXP_TYPE_ROOT_PORT); writel_relaxed(val, pci->elbi_base + PCIEELBI_CTRL0_OFFSET); list_for_each_entry(port, &pcie->ports, list) { diff --git a/drivers/pci/controller/dwc/pcie-nxp-s32g.c b/drivers/pci/controller/dwc/pcie-nxp-s32g.c index b3ec38099fa3..31e1169b8ab6 100644 --- a/drivers/pci/controller/dwc/pcie-nxp-s32g.c +++ b/drivers/pci/controller/dwc/pcie-nxp-s32g.c @@ -139,8 +139,7 @@ static int s32g_init_pcie_controller(struct dw_pcie_rp *pp) /* Set RP mode */ val = s32g_pcie_readl_ctrl(s32g_pp, PCIE_S32G_PE0_GEN_CTRL_1); - val &= ~DEVICE_TYPE_MASK; - val |= FIELD_PREP(DEVICE_TYPE_MASK, PCI_EXP_TYPE_ROOT_PORT); + FIELD_MODIFY(DEVICE_TYPE_MASK, &val, PCI_EXP_TYPE_ROOT_PORT); /* Use default CRNS */ val &= ~SRIS_MODE; diff --git a/drivers/pci/controller/dwc/pcie-qcom-common.c b/drivers/pci/controller/dwc/pcie-qcom-common.c index 5aa73c628737..0da73caf2011 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-common.c +++ b/drivers/pci/controller/dwc/pcie-qcom-common.c @@ -30,20 +30,15 @@ void qcom_pcie_common_set_equalization(struct dw_pcie *pci) reg = dw_pcie_readl_dbi(pci, GEN3_RELATED_OFF); reg &= ~GEN3_RELATED_OFF_GEN3_ZRXDC_NONCOMPL; - reg &= ~GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK; - reg |= FIELD_PREP(GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK, - speed - PCIE_SPEED_8_0GT); + FIELD_MODIFY(GEN3_RELATED_OFF_RATE_SHADOW_SEL_MASK, ®, + speed - PCIE_SPEED_8_0GT); dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, reg); reg = dw_pcie_readl_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE_OFF); - reg &= ~(GEN3_EQ_FMDC_T_MIN_PHASE23 | - GEN3_EQ_FMDC_N_EVALS | - GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA | - GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA); - reg |= FIELD_PREP(GEN3_EQ_FMDC_T_MIN_PHASE23, 0x1) | - FIELD_PREP(GEN3_EQ_FMDC_N_EVALS, 0xd) | - FIELD_PREP(GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA, 0x5) | - FIELD_PREP(GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA, 0x5); + FIELD_MODIFY(GEN3_EQ_FMDC_T_MIN_PHASE23, ®, 0x1); + FIELD_MODIFY(GEN3_EQ_FMDC_N_EVALS, ®, 0xd); + FIELD_MODIFY(GEN3_EQ_FMDC_MAX_PRE_CURSOR_DELTA, ®, 0x5); + FIELD_MODIFY(GEN3_EQ_FMDC_MAX_POST_CURSOR_DELTA, ®, 0x5); dw_pcie_writel_dbi(pci, GEN3_EQ_FB_MODE_DIR_CHANGE_OFF, reg); reg = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); @@ -61,14 +56,10 @@ void qcom_pcie_common_set_16gt_lane_margining(struct dw_pcie *pci) u32 reg; reg = dw_pcie_readl_dbi(pci, GEN4_LANE_MARGINING_1_OFF); - reg &= ~(MARGINING_MAX_VOLTAGE_OFFSET | - MARGINING_NUM_VOLTAGE_STEPS | - MARGINING_MAX_TIMING_OFFSET | - MARGINING_NUM_TIMING_STEPS); - reg |= FIELD_PREP(MARGINING_MAX_VOLTAGE_OFFSET, 0x24) | - FIELD_PREP(MARGINING_NUM_VOLTAGE_STEPS, 0x78) | - FIELD_PREP(MARGINING_MAX_TIMING_OFFSET, 0x32) | - FIELD_PREP(MARGINING_NUM_TIMING_STEPS, 0x10); + FIELD_MODIFY(MARGINING_MAX_VOLTAGE_OFFSET, ®, 0x24); + FIELD_MODIFY(MARGINING_NUM_VOLTAGE_STEPS, ®, 0x78); + FIELD_MODIFY(MARGINING_MAX_TIMING_OFFSET, ®, 0x32); + FIELD_MODIFY(MARGINING_NUM_TIMING_STEPS, ®, 0x10); dw_pcie_writel_dbi(pci, GEN4_LANE_MARGINING_1_OFF, reg); reg = dw_pcie_readl_dbi(pci, GEN4_LANE_MARGINING_2_OFF); @@ -76,13 +67,10 @@ void qcom_pcie_common_set_16gt_lane_margining(struct dw_pcie *pci) MARGINING_SAMPLE_REPORTING_METHOD | MARGINING_IND_LEFT_RIGHT_TIMING | MARGINING_VOLTAGE_SUPPORTED; - reg &= ~(MARGINING_IND_UP_DOWN_VOLTAGE | - MARGINING_MAXLANES | - MARGINING_SAMPLE_RATE_TIMING | - MARGINING_SAMPLE_RATE_VOLTAGE); - reg |= FIELD_PREP(MARGINING_MAXLANES, pci->num_lanes) | - FIELD_PREP(MARGINING_SAMPLE_RATE_TIMING, 0x3f) | - FIELD_PREP(MARGINING_SAMPLE_RATE_VOLTAGE, 0x3f); + reg &= ~MARGINING_IND_UP_DOWN_VOLTAGE; + FIELD_MODIFY(MARGINING_MAXLANES, ®, pci->num_lanes); + FIELD_MODIFY(MARGINING_SAMPLE_RATE_TIMING, ®, 0x3f); + FIELD_MODIFY(MARGINING_SAMPLE_RATE_VOLTAGE, ®, 0x3f); dw_pcie_writel_dbi(pci, GEN4_LANE_MARGINING_2_OFF, reg); } EXPORT_SYMBOL_GPL(qcom_pcie_common_set_16gt_lane_margining); diff --git a/drivers/pci/controller/dwc/pcie-qcom-ep.c b/drivers/pci/controller/dwc/pcie-qcom-ep.c index 257c2bcb5f76..56184e6ca6e6 100644 --- a/drivers/pci/controller/dwc/pcie-qcom-ep.c +++ b/drivers/pci/controller/dwc/pcie-qcom-ep.c @@ -494,15 +494,13 @@ static int qcom_pcie_perst_deassert(struct dw_pcie *pci) /* Set the L0s Exit Latency to 2us-4us = 0x6 */ offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); - val &= ~PCI_EXP_LNKCAP_L0SEL; - val |= FIELD_PREP(PCI_EXP_LNKCAP_L0SEL, 0x6); + FIELD_MODIFY(PCI_EXP_LNKCAP_L0SEL, &val, 0x6); dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, val); /* Set the L1 Exit Latency to be 32us-64 us = 0x6 */ offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); val = dw_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); - val &= ~PCI_EXP_LNKCAP_L1EL; - val |= FIELD_PREP(PCI_EXP_LNKCAP_L1EL, 0x6); + FIELD_MODIFY(PCI_EXP_LNKCAP_L1EL, &val, 0x6); dw_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, val); dw_pcie_dbi_ro_wr_dis(pci); diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 9dcfa194050e..3c831331338e 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -872,8 +872,7 @@ static void config_gen3_gen4_eq_presets(struct tegra_pcie_dw *pcie) dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); - val &= ~GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC; - val |= FIELD_PREP(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, 0x3ff); + FIELD_MODIFY(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, &val, 0x3ff); val &= ~GEN3_EQ_CONTROL_OFF_FB_MODE; dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val); @@ -883,9 +882,8 @@ static void config_gen3_gen4_eq_presets(struct tegra_pcie_dw *pcie) dw_pcie_writel_dbi(pci, GEN3_RELATED_OFF, val); val = dw_pcie_readl_dbi(pci, GEN3_EQ_CONTROL_OFF); - val &= ~GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC; - val |= FIELD_PREP(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, - pcie->of_data->gen4_preset_vec); + FIELD_MODIFY(GEN3_EQ_CONTROL_OFF_PSET_REQ_VEC, &val, + pcie->of_data->gen4_preset_vec); val &= ~GEN3_EQ_CONTROL_OFF_FB_MODE; dw_pcie_writel_dbi(pci, GEN3_EQ_CONTROL_OFF, val); diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c index a72aa57591c0..d6eb65b8cd7f 100644 --- a/drivers/pci/controller/pci-mvebu.c +++ b/drivers/pci/controller/pci-mvebu.c @@ -263,8 +263,7 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port) * not set correctly then link with endpoint card is not established. */ lnkcap = mvebu_readl(port, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP); - lnkcap &= ~PCI_EXP_LNKCAP_MLW; - lnkcap |= FIELD_PREP(PCI_EXP_LNKCAP_MLW, port->is_x4 ? 4 : 1); + FIELD_MODIFY(PCI_EXP_LNKCAP_MLW, &lnkcap, port->is_x4 ? 4 : 1); mvebu_writel(port, lnkcap, PCIE_CAP_PCIEXP + PCI_EXP_LNKCAP); /* Disable Root Bridge I/O space, memory space and bus mastering. */ diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c index b0accd828589..f36a616a8b52 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -494,8 +494,7 @@ static int mtk_pcie_startup_port(struct mtk_gen3_pcie *pcie) /* Set Link Control 2 (LNKCTL2) speed restriction, if any */ if (pcie->max_link_speed) { val = readl_relaxed(pcie->base + PCIE_CONF_LINK2_CTL_STS); - val &= ~PCIE_CONF_LINK2_LCR2_LINK_SPEED; - val |= FIELD_PREP(PCIE_CONF_LINK2_LCR2_LINK_SPEED, pcie->max_link_speed); + FIELD_MODIFY(PCIE_CONF_LINK2_LCR2_LINK_SPEED, &val, pcie->max_link_speed); writel_relaxed(val, pcie->base + PCIE_CONF_LINK2_CTL_STS); } diff --git a/drivers/pci/ide.c b/drivers/pci/ide.c index be74e8f0ae21..beb67b8fb5c5 100644 --- a/drivers/pci/ide.c +++ b/drivers/pci/ide.c @@ -170,8 +170,7 @@ void pci_ide_init(struct pci_dev *pdev) pci_read_config_dword(pdev, pos + PCI_IDE_SEL_CTL, &val); if (val & PCI_IDE_SEL_CTL_EN) continue; - val &= ~PCI_IDE_SEL_CTL_ID; - val |= FIELD_PREP(PCI_IDE_SEL_CTL_ID, PCI_IDE_RESERVED_STREAM_ID); + FIELD_MODIFY(PCI_IDE_SEL_CTL_ID, &val, PCI_IDE_RESERVED_STREAM_ID); pci_write_config_dword(pdev, pos + PCI_IDE_SEL_CTL, val); } @@ -182,8 +181,7 @@ void pci_ide_init(struct pci_dev *pdev) pci_read_config_dword(pdev, pos, &val); if (val & PCI_IDE_LINK_CTL_EN) continue; - val &= ~PCI_IDE_LINK_CTL_ID; - val |= FIELD_PREP(PCI_IDE_LINK_CTL_ID, PCI_IDE_RESERVED_STREAM_ID); + FIELD_MODIFY(PCI_IDE_LINK_CTL_ID, &val, PCI_IDE_RESERVED_STREAM_ID); pci_write_config_dword(pdev, pos, val); } diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 91ac4e37ecb9..fdae70abe804 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -946,8 +946,7 @@ static void sriov_restore_vf_rebar_state(struct pci_dev *dev) pci_read_config_dword(dev, pos + PCI_VF_REBAR_CTRL, &ctrl); bar_idx = FIELD_GET(PCI_VF_REBAR_CTRL_BAR_IDX, ctrl); size = pci_rebar_bytes_to_size(dev->sriov->barsz[bar_idx]); - ctrl &= ~PCI_VF_REBAR_CTRL_BAR_SIZE; - ctrl |= FIELD_PREP(PCI_VF_REBAR_CTRL_BAR_SIZE, size); + FIELD_MODIFY(PCI_VF_REBAR_CTRL_BAR_SIZE, &ctrl, size); pci_write_config_dword(dev, pos + PCI_VF_REBAR_CTRL, ctrl); } } diff --git a/drivers/pci/msi/msi.c b/drivers/pci/msi/msi.c index 81d24a270a79..33c8b5b98684 100644 --- a/drivers/pci/msi/msi.c +++ b/drivers/pci/msi/msi.c @@ -201,8 +201,7 @@ static inline void pci_write_msg_msi(struct pci_dev *dev, struct msi_desc *desc, u16 msgctl; pci_read_config_word(dev, pos + PCI_MSI_FLAGS, &msgctl); - msgctl &= ~PCI_MSI_FLAGS_QSIZE; - msgctl |= FIELD_PREP(PCI_MSI_FLAGS_QSIZE, desc->pci.msi_attrib.multiple); + FIELD_MODIFY(PCI_MSI_FLAGS_QSIZE, &msgctl, desc->pci.msi_attrib.multiple); pci_write_config_word(dev, pos + PCI_MSI_FLAGS, msgctl); pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO, msg->address_lo); @@ -532,9 +531,8 @@ void __pci_restore_msi_state(struct pci_dev *dev) pci_read_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, &control); pci_msi_update_mask(entry, 0, 0); - control &= ~PCI_MSI_FLAGS_QSIZE; - control |= PCI_MSI_FLAGS_ENABLE | - FIELD_PREP(PCI_MSI_FLAGS_QSIZE, entry->pci.msi_attrib.multiple); + FIELD_MODIFY(PCI_MSI_FLAGS_QSIZE, &control, entry->pci.msi_attrib.multiple); + control |= PCI_MSI_FLAGS_ENABLE; pci_write_config_word(dev, dev->msi_cap + PCI_MSI_FLAGS, control); } @@ -970,8 +968,7 @@ int pci_msix_write_tph_tag(struct pci_dev *pdev, unsigned int index, u16 tag) if (!msi_desc || msi_desc->pci.msi_attrib.is_virtual) return -ENXIO; - msi_desc->pci.msix_ctrl &= ~PCI_MSIX_ENTRY_CTRL_ST; - msi_desc->pci.msix_ctrl |= FIELD_PREP(PCI_MSIX_ENTRY_CTRL_ST, tag); + FIELD_MODIFY(PCI_MSIX_ENTRY_CTRL_ST, &msi_desc->pci.msix_ctrl, tag); pci_msix_write_vector_ctrl(msi_desc, msi_desc->pci.msix_ctrl); /* Flush the write */ readl(pci_msix_desc_addr(msi_desc)); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8f7cfcc00090..942f70f6a441 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5771,8 +5771,7 @@ int pcix_set_mmrbc(struct pci_dev *dev, int mmrbc) if (v > o && (dev->bus->bus_flags & PCI_BUS_FLAGS_NO_MMRBC)) return -EIO; - cmd &= ~PCI_X_CMD_MAX_READ; - cmd |= FIELD_PREP(PCI_X_CMD_MAX_READ, v); + FIELD_MODIFY(PCI_X_CMD_MAX_READ, &cmd, v); if (pci_write_config_word(dev, cap + PCI_X_CMD, cmd)) return -EIO; } diff --git a/drivers/pci/pcie/ptm.c b/drivers/pci/pcie/ptm.c index a41ffd1914de..bd3bd39f6372 100644 --- a/drivers/pci/pcie/ptm.c +++ b/drivers/pci/pcie/ptm.c @@ -152,8 +152,7 @@ static int __pci_enable_ptm(struct pci_dev *dev) pci_read_config_dword(dev, ptm + PCI_PTM_CTRL, &ctrl); ctrl |= PCI_PTM_CTRL_ENABLE; - ctrl &= ~PCI_PTM_GRANULARITY_MASK; - ctrl |= FIELD_PREP(PCI_PTM_GRANULARITY_MASK, dev->ptm_granularity); + FIELD_MODIFY(PCI_PTM_GRANULARITY_MASK, &ctrl, dev->ptm_granularity); if (dev->ptm_root) ctrl |= PCI_PTM_CTRL_ROOT; diff --git a/drivers/pci/rebar.c b/drivers/pci/rebar.c index 39f8cf3b70d5..e3e0415fc29a 100644 --- a/drivers/pci/rebar.c +++ b/drivers/pci/rebar.c @@ -211,8 +211,7 @@ int pci_rebar_set_size(struct pci_dev *pdev, int bar, int size) return pos; pci_read_config_dword(pdev, pos + PCI_REBAR_CTRL, &ctrl); - ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; - ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); + FIELD_MODIFY(PCI_REBAR_CTRL_BAR_SIZE, &ctrl, size); pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); if (pci_resource_is_iov(bar)) @@ -241,8 +240,7 @@ void pci_restore_rebar_state(struct pci_dev *pdev) bar_idx = ctrl & PCI_REBAR_CTRL_BAR_IDX; res = pci_resource_n(pdev, bar_idx); size = pci_rebar_bytes_to_size(resource_size(res)); - ctrl &= ~PCI_REBAR_CTRL_BAR_SIZE; - ctrl |= FIELD_PREP(PCI_REBAR_CTRL_BAR_SIZE, size); + FIELD_MODIFY(PCI_REBAR_CTRL_BAR_SIZE, &ctrl, size); pci_write_config_dword(pdev, pos + PCI_REBAR_CTRL, ctrl); } } diff --git a/drivers/pci/setup-cardbus.c b/drivers/pci/setup-cardbus.c index 1ebd13a1f730..f7c62054f227 100644 --- a/drivers/pci/setup-cardbus.c +++ b/drivers/pci/setup-cardbus.c @@ -253,8 +253,7 @@ int pci_cardbus_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, * yenta.c forces a secondary latency timer of 176. * Copy that behaviour here. */ - buses &= ~PCI_SEC_LATENCY_TIMER_MASK; - buses |= FIELD_PREP(PCI_SEC_LATENCY_TIMER_MASK, CARDBUS_LATENCY_TIMER); + FIELD_MODIFY(PCI_SEC_LATENCY_TIMER_MASK, &buses, CARDBUS_LATENCY_TIMER); /* We need to blast all three values with a single write */ pci_write_config_dword(dev, PCI_PRIMARY_BUS, buses); diff --git a/drivers/pci/tph.c b/drivers/pci/tph.c index 91145e8d9d95..655ffd60e62f 100644 --- a/drivers/pci/tph.c +++ b/drivers/pci/tph.c @@ -139,8 +139,7 @@ static void set_ctrl_reg_req_en(struct pci_dev *pdev, u8 req_type) pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, ®); - reg &= ~PCI_TPH_CTRL_REQ_EN_MASK; - reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, req_type); + FIELD_MODIFY(PCI_TPH_CTRL_REQ_EN_MASK, ®, req_type); pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, reg); } @@ -427,11 +426,8 @@ int pcie_enable_tph(struct pci_dev *pdev, int mode) /* Write them into TPH control register */ pci_read_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, ®); - reg &= ~PCI_TPH_CTRL_MODE_SEL_MASK; - reg |= FIELD_PREP(PCI_TPH_CTRL_MODE_SEL_MASK, pdev->tph_mode); - - reg &= ~PCI_TPH_CTRL_REQ_EN_MASK; - reg |= FIELD_PREP(PCI_TPH_CTRL_REQ_EN_MASK, pdev->tph_req_type); + FIELD_MODIFY(PCI_TPH_CTRL_MODE_SEL_MASK, ®, pdev->tph_mode); + FIELD_MODIFY(PCI_TPH_CTRL_REQ_EN_MASK, ®, pdev->tph_req_type); pci_write_config_dword(pdev, pdev->tph_cap + PCI_TPH_CTRL, reg); From 5e6c21c56998e1e58d2f314e70779989ea0fee5d Mon Sep 17 00:00:00 2001 From: Ben Reed Date: Tue, 5 May 2026 10:16:33 -0600 Subject: [PATCH 018/168] PCI: switchtec: Add Gen6 Device IDs Add device IDs for the next generation of switchtec products. No changes to the driver were required with the new version of the hardware. [logang: rewrote commit message] Signed-off-by: Ben Reed Signed-off-by: Logan Gunthorpe Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260505161633.67454-1-logang@deltatee.com --- drivers/pci/switch/switchtec.c | 16 ++++++++++++++++ include/linux/switchtec.h | 1 + 2 files changed, 17 insertions(+) diff --git a/drivers/pci/switch/switchtec.c b/drivers/pci/switch/switchtec.c index 93ebec94b763..41fc4b512708 100644 --- a/drivers/pci/switch/switchtec.c +++ b/drivers/pci/switch/switchtec.c @@ -1852,6 +1852,22 @@ static const struct pci_device_id switchtec_pci_tbl[] = { SWITCHTEC_PCI_DEVICE(0x5552, SWITCHTEC_GEN5), /* PAXA 52XG5 */ SWITCHTEC_PCI_DEVICE(0x5536, SWITCHTEC_GEN5), /* PAXA 36XG5 */ SWITCHTEC_PCI_DEVICE(0x5528, SWITCHTEC_GEN5), /* PAXA 28XG5 */ + SWITCHTEC_PCI_DEVICE(0x6048, SWITCHTEC_GEN6), /* PFXs 48XG6 */ + SWITCHTEC_PCI_DEVICE(0x6064, SWITCHTEC_GEN6), /* PFXs 64XG6 */ + SWITCHTEC_PCI_DEVICE(0x6044, SWITCHTEC_GEN6), /* PFXs 144XG6 */ + SWITCHTEC_PCI_DEVICE(0x6060, SWITCHTEC_GEN6), /* PFXs 160XG6 */ + SWITCHTEC_PCI_DEVICE(0x6148, SWITCHTEC_GEN6), /* PSXs 48XG6 */ + SWITCHTEC_PCI_DEVICE(0x6164, SWITCHTEC_GEN6), /* PSXs 64XG6 */ + SWITCHTEC_PCI_DEVICE(0x6144, SWITCHTEC_GEN6), /* PSXs 144XG6 */ + SWITCHTEC_PCI_DEVICE(0x6160, SWITCHTEC_GEN6), /* PSXs 160XG6 */ + SWITCHTEC_PCI_DEVICE(0x6248, SWITCHTEC_GEN6), /* PFX 48XG6 */ + SWITCHTEC_PCI_DEVICE(0x6264, SWITCHTEC_GEN6), /* PFX 64XG6 */ + SWITCHTEC_PCI_DEVICE(0x6244, SWITCHTEC_GEN6), /* PFX 144XG6 */ + SWITCHTEC_PCI_DEVICE(0x6260, SWITCHTEC_GEN6), /* PFX 160XG6 */ + SWITCHTEC_PCI_DEVICE(0x6348, SWITCHTEC_GEN6), /* PSX 48XG6 */ + SWITCHTEC_PCI_DEVICE(0x6364, SWITCHTEC_GEN6), /* PSX 64XG6 */ + SWITCHTEC_PCI_DEVICE(0x6344, SWITCHTEC_GEN6), /* PSX 144XG6 */ + SWITCHTEC_PCI_DEVICE(0x6360, SWITCHTEC_GEN6), /* PSX 160XG6 */ SWITCHTEC_PCI100X_DEVICE(0x1001, SWITCHTEC_GEN4), /* PCI1001 16XG4 */ SWITCHTEC_PCI100X_DEVICE(0x1002, SWITCHTEC_GEN4), /* PCI1002 12XG4 */ SWITCHTEC_PCI100X_DEVICE(0x1003, SWITCHTEC_GEN4), /* PCI1003 16XG4 */ diff --git a/include/linux/switchtec.h b/include/linux/switchtec.h index cdb58d61c152..724da6c08bf7 100644 --- a/include/linux/switchtec.h +++ b/include/linux/switchtec.h @@ -42,6 +42,7 @@ enum switchtec_gen { SWITCHTEC_GEN3, SWITCHTEC_GEN4, SWITCHTEC_GEN5, + SWITCHTEC_GEN6, }; struct mrpc_regs { From 8d846a0a6f95cd717df892b077a1c990a86b29fa Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 6 May 2026 10:47:24 +0200 Subject: [PATCH 019/168] PCI: amd-mdb: Use the right GPIO header The driver includes the legacy GPIO header but does not use any symbols from it and actually wants , so fix this up. Signed-off-by: Andy Shevchenko Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260506084858.867884-2-andriy.shevchenko@linux.intel.com --- drivers/pci/controller/dwc/pcie-amd-mdb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-amd-mdb.c b/drivers/pci/controller/dwc/pcie-amd-mdb.c index 7e50e11fbffd..88208e967201 100644 --- a/drivers/pci/controller/dwc/pcie-amd-mdb.c +++ b/drivers/pci/controller/dwc/pcie-amd-mdb.c @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include #include From 800c861cfcbedd033bdffa65c00188fefdcf0767 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 6 May 2026 10:47:25 +0200 Subject: [PATCH 020/168] PCI: designware-plat: Drop unused include This file does not use the symbols from the legacy header, so drop it. Signed-off-by: Andy Shevchenko Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260506084858.867884-3-andriy.shevchenko@linux.intel.com --- drivers/pci/controller/dwc/pcie-designware-plat.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-plat.c b/drivers/pci/controller/dwc/pcie-designware-plat.c index d103ab759c4e..7d896752d43c 100644 --- a/drivers/pci/controller/dwc/pcie-designware-plat.c +++ b/drivers/pci/controller/dwc/pcie-designware-plat.c @@ -8,7 +8,6 @@ */ #include #include -#include #include #include #include From 71d007f6fb17f519c2fe51d35e0bee5f602169e6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 6 May 2026 10:47:26 +0200 Subject: [PATCH 021/168] PCI: fu740: Drop unused include This file does not use the symbols from the legacy header, so drop it. Signed-off-by: Andy Shevchenko Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260506084858.867884-4-andriy.shevchenko@linux.intel.com --- drivers/pci/controller/dwc/pcie-fu740.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-fu740.c b/drivers/pci/controller/dwc/pcie-fu740.c index 66367252032b..d0a34f680397 100644 --- a/drivers/pci/controller/dwc/pcie-fu740.c +++ b/drivers/pci/controller/dwc/pcie-fu740.c @@ -13,7 +13,6 @@ #include #include -#include #include #include #include From 7cc21269ef74269e423d64defc940808ccbf9774 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 6 May 2026 10:47:27 +0200 Subject: [PATCH 022/168] PCI: visconti: Drop unused include This file does not use the symbols from the legacy header, so drop it. Signed-off-by: Andy Shevchenko Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260506084858.867884-5-andriy.shevchenko@linux.intel.com --- drivers/pci/controller/dwc/pcie-visconti.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-visconti.c b/drivers/pci/controller/dwc/pcie-visconti.c index cdeac6177143..f21775c3b3a1 100644 --- a/drivers/pci/controller/dwc/pcie-visconti.c +++ b/drivers/pci/controller/dwc/pcie-visconti.c @@ -10,7 +10,6 @@ #include #include -#include #include #include #include From 72780f7964684939d7d2f69c348876213b184484 Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Mon, 8 Dec 2025 19:24:29 +0000 Subject: [PATCH 023/168] PCI: Always lift 2.5GT/s restriction in PCIe failed link retraining Discard Vendor:Device ID matching in the PCIe failed link retraining quirk and ignore the link status for the removal of the 2.5GT/s speed clamp, whether applied by the quirk itself or the firmware earlier on. Revert to the original target link speed if this final link retraining has failed. This is so that link training noise in hot-plug scenarios does not make a link remain clamped to the 2.5GT/s speed where an event race has led the quirk to apply the speed clamp for one device, only to leave it in place for a subsequent device to be plugged in. Refer to the Link Capabilities register directly for the maximum link speed determination so as to streamline backporting. Fixes: a89c82249c37 ("PCI: Work around PCIe link training failures") Signed-off-by: Maciej W. Rozycki Signed-off-by: Bjorn Helgaas Tested-by: Alok Tiwari Cc: stable@vger.kernel.org # v6.5+ Link: https://patch.msgid.link/alpine.DEB.2.21.2512080331530.49654@angie.orcam.me.uk --- drivers/pci/quirks.c | 51 ++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index caaed1a01dc0..dd0025d3914e 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -80,11 +80,10 @@ static bool pcie_lbms_seen(struct pci_dev *dev, u16 lnksta) * Restrict the speed to 2.5GT/s then with the Target Link Speed field, * request a retrain and check the result. * - * If this turns out successful and we know by the Vendor:Device ID it is - * safe to do so, then lift the restriction, letting the devices negotiate - * a higher speed. Also check for a similar 2.5GT/s speed restriction the - * firmware may have already arranged and lift it with ports that already - * report their data link being up. + * If this turns out successful, or where a 2.5GT/s speed restriction has + * been previously arranged by the firmware and the port reports its link + * already being up, lift the restriction, in a hope it is safe to do so, + * letting the devices negotiate a higher speed. * * Otherwise revert the speed to the original setting and request a retrain * again to remove any residual state, ignoring the result as it's supposed @@ -95,51 +94,37 @@ static bool pcie_lbms_seen(struct pci_dev *dev, u16 lnksta) */ int pcie_failed_link_retrain(struct pci_dev *dev) { - static const struct pci_device_id ids[] = { - { PCI_VDEVICE(ASMEDIA, 0x2824) }, /* ASMedia ASM2824 */ - {} - }; - u16 lnksta, lnkctl2; + u16 lnksta, lnkctl2, oldlnkctl2; int ret = -ENOTTY; + u32 lnkcap; if (!pci_is_pcie(dev) || !pcie_downstream_port(dev) || !pcie_cap_has_lnkctl2(dev) || !dev->link_active_reporting) return ret; pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); + pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) { - u16 oldlnkctl2; - pci_info(dev, "broken device, retraining non-functional downstream link at 2.5GT/s\n"); - - pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); ret = pcie_set_target_speed(dev, PCIE_SPEED_2_5GT, false); - if (ret) { - pci_info(dev, "retraining failed\n"); - pcie_set_target_speed(dev, PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2), - true); - return ret; - } - - pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); + if (ret) + goto err; } pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &lnkctl2); - - if ((lnksta & PCI_EXP_LNKSTA_DLLLA) && - (lnkctl2 & PCI_EXP_LNKCTL2_TLS) == PCI_EXP_LNKCTL2_TLS_2_5GT && - pci_match_id(ids, dev)) { - u32 lnkcap; - + pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnkcap); + if ((lnkctl2 & PCI_EXP_LNKCTL2_TLS) == PCI_EXP_LNKCTL2_TLS_2_5GT && + (lnkcap & PCI_EXP_LNKCAP_SLS) != PCI_EXP_LNKCAP_SLS_2_5GB) { pci_info(dev, "removing 2.5GT/s downstream link speed restriction\n"); - pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnkcap); ret = pcie_set_target_speed(dev, PCIE_LNKCAP_SLS2SPEED(lnkcap), false); - if (ret) { - pci_info(dev, "retraining failed\n"); - return ret; - } + if (ret) + goto err; } + return ret; +err: + pci_info(dev, "retraining failed\n"); + pcie_set_target_speed(dev, PCIE_LNKCTL2_TLS2SPEED(oldlnkctl2), true); return ret; } From 30a361308085ebf9a848be2d5828ed913d3572f5 Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Mon, 8 Dec 2025 19:24:34 +0000 Subject: [PATCH 024/168] PCI: Use pcie_get_speed_cap() in PCIe failed link retraining Rewrite a check for the maximum link speed in the Link Capabilities register in terms of pcie_get_speed_cap(). No functional change. Signed-off-by: Maciej W. Rozycki Signed-off-by: Bjorn Helgaas Tested-by: Alok Tiwari Link: https://patch.msgid.link/alpine.DEB.2.21.2512080348310.49654@angie.orcam.me.uk --- drivers/pci/quirks.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index dd0025d3914e..81ee3f69b918 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -95,8 +95,8 @@ static bool pcie_lbms_seen(struct pci_dev *dev, u16 lnksta) int pcie_failed_link_retrain(struct pci_dev *dev) { u16 lnksta, lnkctl2, oldlnkctl2; + enum pci_bus_speed speed_cap; int ret = -ENOTTY; - u32 lnkcap; if (!pci_is_pcie(dev) || !pcie_downstream_port(dev) || !pcie_cap_has_lnkctl2(dev) || !dev->link_active_reporting) @@ -111,12 +111,12 @@ int pcie_failed_link_retrain(struct pci_dev *dev) goto err; } + speed_cap = pcie_get_speed_cap(dev); pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &lnkctl2); - pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &lnkcap); if ((lnkctl2 & PCI_EXP_LNKCTL2_TLS) == PCI_EXP_LNKCTL2_TLS_2_5GT && - (lnkcap & PCI_EXP_LNKCAP_SLS) != PCI_EXP_LNKCAP_SLS_2_5GB) { + speed_cap > PCIE_SPEED_2_5GT) { pci_info(dev, "removing 2.5GT/s downstream link speed restriction\n"); - ret = pcie_set_target_speed(dev, PCIE_LNKCAP_SLS2SPEED(lnkcap), false); + ret = pcie_set_target_speed(dev, speed_cap, false); if (ret) goto err; } From 64d63fd28177abd1bbc3133610771aa15c22c223 Mon Sep 17 00:00:00 2001 From: "Maciej W. Rozycki" Date: Mon, 8 Dec 2025 19:24:38 +0000 Subject: [PATCH 025/168] PCI: Bail out early for 2.5GT/s devices in PCIe failed link retraining There's no point in retraining a failed 2.5GT/s device at 2.5GT/s, so just don't and return early. While such devices might be unlikely to implement Link Active reporting, we need to retrieve the maximum link speed and use it in a conditional later on anyway, so the early check comes for free. Signed-off-by: Maciej W. Rozycki Signed-off-by: Bjorn Helgaas Tested-by: Alok Tiwari Link: https://patch.msgid.link/alpine.DEB.2.21.2512080356070.49654@angie.orcam.me.uk --- drivers/pci/quirks.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 81ee3f69b918..1b4ae046dd69 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -102,6 +102,10 @@ int pcie_failed_link_retrain(struct pci_dev *dev) !pcie_cap_has_lnkctl2(dev) || !dev->link_active_reporting) return ret; + speed_cap = pcie_get_speed_cap(dev); + if (speed_cap <= PCIE_SPEED_2_5GT) + return ret; + pcie_capability_read_word(dev, PCI_EXP_LNKSTA, &lnksta); pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &oldlnkctl2); if (!(lnksta & PCI_EXP_LNKSTA_DLLLA) && pcie_lbms_seen(dev, lnksta)) { @@ -111,10 +115,8 @@ int pcie_failed_link_retrain(struct pci_dev *dev) goto err; } - speed_cap = pcie_get_speed_cap(dev); pcie_capability_read_word(dev, PCI_EXP_LNKCTL2, &lnkctl2); - if ((lnkctl2 & PCI_EXP_LNKCTL2_TLS) == PCI_EXP_LNKCTL2_TLS_2_5GT && - speed_cap > PCIE_SPEED_2_5GT) { + if ((lnkctl2 & PCI_EXP_LNKCTL2_TLS) == PCI_EXP_LNKCTL2_TLS_2_5GT) { pci_info(dev, "removing 2.5GT/s downstream link speed restriction\n"); ret = pcie_set_target_speed(dev, speed_cap, false); if (ret) From c855c9921da72e535c24737c748f603a52d03f7e Mon Sep 17 00:00:00 2001 From: Carlos Bilbao Date: Mon, 27 Apr 2026 21:01:04 -0700 Subject: [PATCH 026/168] PCI/ASPM: Don't reconfigure ASPM entering low-power state Reconfiguring ASPM when a device transitions to low-power state can enable L1.1/L1.2 substates on the PCIe link at a time when the device is sleeping and may be unable to exit them. ASPM should be reconfigured on D0 entry (resume), not on the way down. pci_set_low_power_state() calls pcie_aspm_pm_state_change() after writing D3hot to PCI_PM_CTRL. pcie_aspm_pm_state_change() resets link->aspm_capable to link->aspm_support and then calls pcie_config_aspm_path(), which can enable ASPM L1.1/L1.2 substates on the PCIe link. If the device cannot recover the link from L1.2 while in D3hot, subsequent config space reads return 0xFFFF ("device inaccessible") and pci_power_up() fails with messages like: vfio-pci 0000:5d:00.0: Unable to change power state from D3hot to D0, device inaccessible This was observed on NVIDIA H100 SXM5 GPUs bound to vfio-pci when Linux runtime PM suspends them to D3hot: the GPU becomes permanently inaccessible and disappears from the PCIe bus. The call to pcie_aspm_pm_state_change() in pci_set_low_power_state() was restored by f93e71aea6c6 ("Revert "PCI/ASPM: Remove pcie_aspm_pm_state_change()""), which reverted 08d0cc5f3426 ("PCI/ASPM: Remove pcie_aspm_pm_state_change()"). The revert was necessary because the removal broke suspend/resume on certain platforms that required ASPM to be reconfigured on D0 entry. However, the revert restored the call in both pci_set_full_power_state() (D0 entry) and pci_set_low_power_state() (low-power entry). Only the D0-entry call is needed to fix the suspend/resume regression. The low-power-entry call is harmful: reconfiguring ASPM immediately after putting a device into D3hot can enable link substates that the device or platform cannot exit while the device is sleeping. Remove the pcie_aspm_pm_state_change() call from pci_set_low_power_state(). ASPM will still be reconfigured correctly when the device returns to D0 via pci_set_full_power_state(). Fixes: f93e71aea6c6 ("Revert "PCI/ASPM: Remove pcie_aspm_pm_state_change()"") Signed-off-by: Carlos Bilbao (Lambda) Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260428040104.78524-1-carlos.bilbao@kernel.org --- drivers/pci/pci.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8f7cfcc00090..f97a300058ef 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1514,9 +1514,6 @@ static int pci_set_low_power_state(struct pci_dev *dev, pci_power_t state, bool pci_power_name(dev->current_state), pci_power_name(state)); - if (dev->bus->self) - pcie_aspm_pm_state_change(dev->bus->self, locked); - return 0; } From 113e86bc58a918f85d250723436a4d541a873358 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Fri, 8 May 2026 16:21:27 +0800 Subject: [PATCH 027/168] PCI: Introduce named defines for PCI ROM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert the magic numbers associated with PCI ROM into named definitions. Some of these definitions will be used in the second fix patch. Signed-off-by: Guixin Liu Signed-off-by: Bjorn Helgaas Reviewed-by: Andy Shevchenko Reviewed-by: Krzysztof Wilczyński Link: https://patch.msgid.link/20260508082128.3344255-2-kanie@linux.alibaba.com --- drivers/pci/rom.c | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index e18d3a4383ba..d4a141bd148b 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -5,13 +5,28 @@ * (C) Copyright 2004 Jon Smirl * (C) Copyright 2004 Silicon Graphics, Inc. Jesse Barnes */ + +#include #include #include #include +#include #include #include "pci.h" +#define PCI_ROM_HEADER_SIZE 0x1A +#define PCI_ROM_POINTER_TO_DATA_STRUCT 0x18 +#define PCI_ROM_LAST_IMAGE_INDICATOR 0x15 +#define PCI_ROM_LAST_IMAGE_INDICATOR_BIT BIT(7) +#define PCI_ROM_IMAGE_LEN 0x10 +#define PCI_ROM_IMAGE_SECTOR_SIZE SZ_512 +#define PCI_ROM_IMAGE_SIGNATURE 0xAA55 + +/* Data structure signature is "PCIR" in ASCII representation */ +#define PCI_ROM_DATA_STRUCT_SIGNATURE 0x52494350 +#define PCI_ROM_DATA_STRUCT_LEN 0x0A + /** * pci_enable_rom - enable ROM decoding for a PCI device * @pdev: PCI device to enable @@ -91,26 +106,27 @@ static size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom, do { void __iomem *pds; /* Standard PCI ROMs start out with these bytes 55 AA */ - if (readw(image) != 0xAA55) { - pci_info(pdev, "Invalid PCI ROM header signature: expecting 0xaa55, got %#06x\n", - readw(image)); + if (readw(image) != PCI_ROM_IMAGE_SIGNATURE) { + pci_info(pdev, "Invalid PCI ROM header signature: expecting %#06x, got %#06x\n", + PCI_ROM_IMAGE_SIGNATURE, readw(image)); break; } - /* get the PCI data structure and check its "PCIR" signature */ - pds = image + readw(image + 24); - if (readl(pds) != 0x52494350) { - pci_info(pdev, "Invalid PCI ROM data signature: expecting 0x52494350, got %#010x\n", - readl(pds)); + /* Get the PCI data structure and check its "PCIR" signature */ + pds = image + readw(image + PCI_ROM_POINTER_TO_DATA_STRUCT); + if (readl(pds) != PCI_ROM_DATA_STRUCT_SIGNATURE) { + pci_info(pdev, "Invalid PCI ROM data signature: expecting %#010x, got %#010x\n", + PCI_ROM_DATA_STRUCT_SIGNATURE, readl(pds)); break; } - last_image = readb(pds + 21) & 0x80; - length = readw(pds + 16); - image += length * 512; + last_image = readb(pds + PCI_ROM_LAST_IMAGE_INDICATOR) & + PCI_ROM_LAST_IMAGE_INDICATOR_BIT; + length = readw(pds + PCI_ROM_IMAGE_LEN); + image += length * PCI_ROM_IMAGE_SECTOR_SIZE; /* Avoid iterating through memory outside the resource window */ if (image >= rom + size) break; if (!last_image) { - if (readw(image) != 0xAA55) { + if (readw(image) != PCI_ROM_IMAGE_SIGNATURE) { pci_info(pdev, "No more image in the PCI ROM\n"); break; } From 538796b807fcfb81b2ce40cc97a614fd8588feb5 Mon Sep 17 00:00:00 2001 From: Guixin Liu Date: Fri, 8 May 2026 16:21:28 +0800 Subject: [PATCH 028/168] PCI: Check ROM header and data structure addr before accessing We meet a crash when running stress-ng on x86_64 machine: BUG: unable to handle page fault for address: ffa0000007f40000 RIP: 0010:pci_get_rom_size+0x52/0x220 Call Trace: pci_map_rom+0x80/0x130 pci_read_rom+0x4b/0xe0 kernfs_file_read_iter+0x96/0x180 vfs_read+0x1b1/0x300 Our analysis reveals that the ROM space's start address is 0xffa0000007f30000, and size is 0x10000. Because of broken ROM space, before calling readl(pds), the pds's value is 0xffa0000007f3ffff, which is already pointed to the ROM space end, invoking readl() would read 4 bytes therefore cause an out-of-bounds access and trigger a crash. Fix this by adding image header and data structure checking. We also found another crash on arm64 machine: Unable to handle kernel paging request at virtual address ffff8000dd1393ff Mem abort info: ESR = 0x0000000096000021 EC = 0x25: DABT (current EL), IL = 32 bits SET = 0, FnV = 0 EA = 0, S1PTW = 0 FSC = 0x21: alignment fault The call trace is the same with x86_64, but the crash reason is that the data structure addr is not aligned with 4, and arm64 machine report "alignment fault". Fix this by adding alignment checking. Fixes: 47b975d234ea ("PCI: Avoid iterating through memory outside the resource window") Suggested-by: Guanghui Feng Signed-off-by: Guixin Liu [bhelgaas: shorten function names, wrap comments] Signed-off-by: Bjorn Helgaas Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/20260508082128.3344255-3-kanie@linux.alibaba.com --- drivers/pci/rom.c | 123 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 18 deletions(-) diff --git a/drivers/pci/rom.c b/drivers/pci/rom.c index d4a141bd148b..f2105c6ceef5 100644 --- a/drivers/pci/rom.c +++ b/drivers/pci/rom.c @@ -6,9 +6,12 @@ * (C) Copyright 2004 Silicon Graphics, Inc. Jesse Barnes */ +#include #include #include #include +#include +#include #include #include #include @@ -27,6 +30,15 @@ #define PCI_ROM_DATA_STRUCT_SIGNATURE 0x52494350 #define PCI_ROM_DATA_STRUCT_LEN 0x0A +/* + * Per PCI Firmware r3.3, sec 5.1.3, a conformant PCI Data Structure is at + * least 24 bytes (0x18), large enough to cover every fixed field this + * driver reads (up to the Indicator byte at offset 0x15). Reject smaller + * device-claimed lengths so the follow-up readers in pci_get_rom_size() + * cannot escape the mapped ROM window. + */ +#define PCI_ROM_DATA_STRUCT_MIN_LEN 0x18 + /** * pci_enable_rom - enable ROM decoding for a PCI device * @pdev: PCI device to enable @@ -84,6 +96,91 @@ void pci_disable_rom(struct pci_dev *pdev) } EXPORT_SYMBOL_GPL(pci_disable_rom); +static bool pci_rom_header_valid(struct pci_dev *pdev, void __iomem *image, + void __iomem *rom, size_t size, + bool expect_valid) +{ + unsigned long rom_end = (unsigned long)rom + size - 1; + unsigned long header_end; + u16 signature; + + /* + * Per PCI Firmware r3.3, sec 5.1, each image must start on a + * 512-byte boundary and must contain the PCI Expansion ROM header. + * Because @rom is page-aligned (returned by ioremap()), checking + * 512-byte alignment of @image is equivalent to enforcing the + * spec's sector-aligned layout within the ROM. This also + * satisfies the natural-alignment requirement of readw() on archs + * such as arm64 that disallow unaligned IOMEM access. + */ + if (!IS_ALIGNED((unsigned long)image, PCI_ROM_IMAGE_SECTOR_SIZE)) + return false; + + if (check_add_overflow((unsigned long)image, PCI_ROM_HEADER_SIZE - 1, + &header_end)) + return false; + + if (image < rom || header_end > rom_end) + return false; + + /* Standard PCI ROMs start out with these bytes 55 AA */ + signature = readw(image); + if (signature != PCI_ROM_IMAGE_SIGNATURE) { + if (expect_valid) { + pci_info(pdev, "Invalid PCI ROM header signature: expecting %#06x, got %#06x\n", + PCI_ROM_IMAGE_SIGNATURE, signature); + } else { + pci_info(pdev, "No more images in PCI ROM\n"); + } + return false; + } + + return true; +} + +static bool pci_rom_data_struct_valid(struct pci_dev *pdev, void __iomem *pds, + void __iomem *rom, size_t size) +{ + unsigned long rom_end = (unsigned long)rom + size - 1; + unsigned long end; + u32 signature; + u16 data_len; + + /* + * Some CPU architectures require IOMEM access addresses to be + * aligned, for example arm64, so since we're about to call + * readl(), check here for 4-byte alignment. + */ + if (!IS_ALIGNED((unsigned long)pds, 4)) + return false; + + if (check_add_overflow((unsigned long)pds, PCI_ROM_DATA_STRUCT_LEN + 1, + &end)) + return false; + + if (pds < rom || end > rom_end) + return false; + + signature = readl(pds); + if (signature != PCI_ROM_DATA_STRUCT_SIGNATURE) { + pci_info(pdev, "Invalid PCI ROM data signature: expecting %#010x, got %#010x\n", + PCI_ROM_DATA_STRUCT_SIGNATURE, signature); + return false; + } + + data_len = readw(pds + PCI_ROM_DATA_STRUCT_LEN); + if (data_len < PCI_ROM_DATA_STRUCT_MIN_LEN || data_len == U16_MAX) + return false; + + if (check_add_overflow((unsigned long)pds, data_len - 1, &end)) + return false; + + if (end > rom_end) + return false; + + return true; +} + /** * pci_get_rom_size - obtain the actual size of the ROM image * @pdev: target PCI device @@ -99,38 +196,28 @@ static size_t pci_get_rom_size(struct pci_dev *pdev, void __iomem *rom, size_t size) { void __iomem *image; - int last_image; unsigned int length; + bool last_image; image = rom; do { void __iomem *pds; - /* Standard PCI ROMs start out with these bytes 55 AA */ - if (readw(image) != PCI_ROM_IMAGE_SIGNATURE) { - pci_info(pdev, "Invalid PCI ROM header signature: expecting %#06x, got %#06x\n", - PCI_ROM_IMAGE_SIGNATURE, readw(image)); + if (!pci_rom_header_valid(pdev, image, rom, size, true)) break; - } + /* Get the PCI data structure and check its "PCIR" signature */ pds = image + readw(image + PCI_ROM_POINTER_TO_DATA_STRUCT); - if (readl(pds) != PCI_ROM_DATA_STRUCT_SIGNATURE) { - pci_info(pdev, "Invalid PCI ROM data signature: expecting %#010x, got %#010x\n", - PCI_ROM_DATA_STRUCT_SIGNATURE, readl(pds)); + if (!pci_rom_data_struct_valid(pdev, pds, rom, size)) break; - } + last_image = readb(pds + PCI_ROM_LAST_IMAGE_INDICATOR) & PCI_ROM_LAST_IMAGE_INDICATOR_BIT; length = readw(pds + PCI_ROM_IMAGE_LEN); image += length * PCI_ROM_IMAGE_SECTOR_SIZE; - /* Avoid iterating through memory outside the resource window */ - if (image >= rom + size) + + if (!last_image && + !pci_rom_header_valid(pdev, image, rom, size, false)) break; - if (!last_image) { - if (readw(image) != PCI_ROM_IMAGE_SIGNATURE) { - pci_info(pdev, "No more image in the PCI ROM\n"); - break; - } - } } while (length && !last_image); /* never return a size larger than the PCI resource window */ From d29eac59214327e61ff47a980cceeadb7f25d01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:20 +0000 Subject: [PATCH 029/168] PCI/sysfs: Use PCI resource accessor macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace direct pdev->resource[] accesses with pci_resource_n(), and pdev->resource[].flags accesses with pci_resource_flags(). No functional changes intended. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260508043543.217179-2-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index d37860841260..1fbc3daf87cc 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -177,7 +177,7 @@ static ssize_t resource_show(struct device *dev, struct device_attribute *attr, max = PCI_BRIDGE_RESOURCES; for (i = 0; i < max; i++) { - struct resource *res = &pci_dev->resource[i]; + struct resource *res = pci_resource_n(pci_dev, i); struct resource zerores = {}; /* For backwards compatibility */ @@ -689,7 +689,7 @@ static ssize_t boot_vga_show(struct device *dev, struct device_attribute *attr, return sysfs_emit(buf, "%u\n", (pdev == vga_dev)); return sysfs_emit(buf, "%u\n", - !!(pdev->resource[PCI_ROM_RESOURCE].flags & + !!(pci_resource_flags(pdev, PCI_ROM_RESOURCE) & IORESOURCE_ROM_SHADOW)); } static DEVICE_ATTR_RO(boot_vga); @@ -1082,7 +1082,7 @@ static int pci_mmap_resource(struct kobject *kobj, const struct bin_attribute *a struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); int bar = (unsigned long)attr->private; enum pci_mmap_state mmap_type; - struct resource *res = &pdev->resource[bar]; + struct resource *res = pci_resource_n(pdev, bar); int ret; ret = security_locked_down(LOCKDOWN_PCI_ACCESS); @@ -1286,7 +1286,7 @@ static int pci_create_resource_files(struct pci_dev *pdev) retval = pci_create_attr(pdev, i, 0); /* for prefetchable resources, create a WC mappable file */ if (!retval && arch_can_pci_mmap_wc() && - pdev->resource[i].flags & IORESOURCE_PREFETCH) + pci_resource_flags(pdev, i) & IORESOURCE_PREFETCH) retval = pci_create_attr(pdev, i, 1); if (retval) { pci_remove_resource_files(pdev); From 77c5f3def45937f3f90ea4018cfdf9342b78b78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:21 +0000 Subject: [PATCH 030/168] PCI: Add pci_resource_is_io() and pci_resource_is_mem() helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add helpers to check whether a PCI resource is of I/O port or memory type. These replace the open-coded pci_resource_flags() with IORESOURCE_IO and IORESOURCE_MEM pattern used across the tree. Suggested-by: Ilpo Järvinen Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260508043543.217179-3-kwilczynski@kernel.org --- include/linux/pci.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c11..e93fbe6b57fe 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -2300,6 +2300,31 @@ int pci_iobar_pfn(struct pci_dev *pdev, int bar, struct vm_area_struct *vma); CONCATENATE(__pci_dev_for_each_res, COUNT_ARGS(__VA_ARGS__)) \ (dev, res, __VA_ARGS__) +/** + * pci_resource_is_io - check if a PCI resource is of I/O port type. + * @dev: PCI device to check. + * @resno: The resource number (BAR index) to check. + * + * Returns true if the resource type is I/O port. + */ +static inline bool pci_resource_is_io(const struct pci_dev *dev, int resno) +{ + return resource_type(pci_resource_n(dev, resno)) == IORESOURCE_IO; +} + +/** + * pci_resource_is_mem - check if a PCI resource is of memory type. + * @dev: PCI device to check. + * @resno: The resource number (BAR index) to check. + * + * Returns true if the resource type is memory, including + * prefetchable memory. + */ +static inline bool pci_resource_is_mem(const struct pci_dev *dev, int resno) +{ + return resource_type(pci_resource_n(dev, resno)) == IORESOURCE_MEM; +} + /* * Similar to the helpers above, these manipulate per-pci_dev * driver-specific data. They are really just a wrapper around From 04fbecc947ec3a2149680d29b9cdc147fc012fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:22 +0000 Subject: [PATCH 031/168] PCI/sysfs: Only allow supported resource types in I/O and MMIO helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, when the sysfs attributes for PCI resources are added dynamically, the resource access callbacks are only set when the underlying BAR type matches, using .read and .write for IORESOURCE_IO, and .mmap for IORESOURCE_MEM or IORESOURCE_IO with arch_can_pci_mmap_io() support. As such, when the callback is not set, the operation inherently fails. After the conversion to static attributes, visibility callbacks will control which resource files appear for each BAR, but the callbacks themselves will always be set. Add a type check to pci_resource_io() and pci_mmap_resource() to return -EIO for an unsupported resource type. Use the new pci_resource_is_io() and pci_resource_is_mem() helpers for the type checks, replacing the open-coded bitwise flag tests and also drop the local struct resource pointer in pci_mmap_resource(). Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260508043543.217179-4-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 1fbc3daf87cc..2e4e226e78d4 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1082,20 +1082,24 @@ static int pci_mmap_resource(struct kobject *kobj, const struct bin_attribute *a struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); int bar = (unsigned long)attr->private; enum pci_mmap_state mmap_type; - struct resource *res = pci_resource_n(pdev, bar); int ret; ret = security_locked_down(LOCKDOWN_PCI_ACCESS); if (ret) return ret; - if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start)) + if (!pci_resource_is_mem(pdev, bar) && + !(pci_resource_is_io(pdev, bar) && arch_can_pci_mmap_io())) + return -EIO; + + if (pci_resource_is_mem(pdev, bar) && + iomem_is_exclusive(pci_resource_start(pdev, bar))) return -EINVAL; if (!pci_mmap_fits(pdev, bar, vma, PCI_MMAP_SYSFS)) return -EINVAL; - mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io; + mmap_type = pci_resource_is_mem(pdev, bar) ? pci_mmap_mem : pci_mmap_io; return pci_mmap_resource_range(pdev, bar, vma, mmap_type, write_combine); } @@ -1123,6 +1127,9 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj, int bar = (unsigned long)attr->private; unsigned long port = off; + if (!pci_resource_is_io(pdev, bar)) + return -EIO; + port += pci_resource_start(pdev, bar); if (port > pci_resource_end(pdev, bar)) From 77723f417199f79b011336b3922586641fff29b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:23 +0000 Subject: [PATCH 032/168] PCI/sysfs: Split pci_llseek_resource() for device and legacy attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both legacy and resource attributes set .f_mapping = iomem_get_mapping, so the default generic_file_llseek() would consult iomem_inode for the file size, which knows nothing about the attribute. That is why custom llseek callbacks exist. Currently, the legacy and resource attributes have .size set at creation time, as such, using the attr->size is sufficient. However, the upcoming static resource attributes will have .size == 0 set, since they are const, and the .bin_size() callback will be used to provide the real size to kernfs instead. The legacy attributes operate on a struct pci_bus, not struct pci_dev, so calling to_pci_dev() on them would be invalid. Thus, split pci_llseek_resource() into two functions: - pci_llseek_resource(), which derives the file size from the BAR using pci_resource_len(). - pci_llseek_resource_legacy(), which uses attr->size directly. Update the dynamic legacy attribute creation to use the new pci_llseek_resource_legacy() callback. The original pci_llseek_resource() was added in commit 24de09c16f97 ("PCI: Implement custom llseek for sysfs resource entries"). Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-5-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 2e4e226e78d4..2280b7edb41f 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -881,13 +881,26 @@ static const struct attribute_group pci_dev_config_attr_group = { * llseek operation for mmappable PCI resources. * May be left unused if the arch doesn't provide them. */ +static __maybe_unused loff_t +pci_llseek_resource_legacy(struct file *filep, + struct kobject *kobj __always_unused, + const struct bin_attribute *attr, + loff_t offset, int whence) +{ + return fixed_size_llseek(filep, offset, whence, attr->size); +} + static __maybe_unused loff_t pci_llseek_resource(struct file *filep, - struct kobject *kobj __always_unused, + struct kobject *kobj, const struct bin_attribute *attr, loff_t offset, int whence) { - return fixed_size_llseek(filep, offset, whence, attr->size); + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + int bar = (unsigned long)attr->private; + + return fixed_size_llseek(filep, offset, whence, + pci_resource_len(pdev, bar)); } #ifdef HAVE_PCI_LEGACY @@ -1022,7 +1035,7 @@ void pci_create_legacy_files(struct pci_bus *b) b->legacy_io->read = pci_read_legacy_io; b->legacy_io->write = pci_write_legacy_io; /* See pci_create_attr() for motivation */ - b->legacy_io->llseek = pci_llseek_resource; + b->legacy_io->llseek = pci_llseek_resource_legacy; b->legacy_io->mmap = pci_mmap_legacy_io; b->legacy_io->f_mapping = iomem_get_mapping; pci_adjust_legacy_attr(b, pci_mmap_io); @@ -1038,7 +1051,7 @@ void pci_create_legacy_files(struct pci_bus *b) b->legacy_mem->attr.mode = 0600; b->legacy_mem->mmap = pci_mmap_legacy_mem; /* See pci_create_attr() for motivation */ - b->legacy_mem->llseek = pci_llseek_resource; + b->legacy_mem->llseek = pci_llseek_resource_legacy; b->legacy_mem->f_mapping = iomem_get_mapping; pci_adjust_legacy_attr(b, pci_mmap_mem); error = device_create_bin_file(&b->dev, b->legacy_mem); From 25ec7f57deceec633f27bc93b615f951e2ade814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:24 +0000 Subject: [PATCH 033/168] PCI/sysfs: Add CAP_SYS_ADMIN check to __resource_resize_store() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the __resource_resize_store() allows writing to the resourceN_resize sysfs attribute to change a BAR's size without checking for capabilities, currently relying only on the file access check. Resizing a BAR modifies PCI device configuration and can disrupt active drivers. After the upcoming conversion to static attributes, it will also trigger resource file updates via sysfs_update_groups(). Add a CAP_SYS_ADMIN check to prevent unprivileged users from performing BAR resize operations. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260508043543.217179-6-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 2280b7edb41f..dac780597727 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1596,6 +1596,9 @@ static ssize_t __resource_resize_store(struct device *dev, int n, int ret; u16 cmd; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (kstrtoul(buf, 0, &size) < 0) return -EINVAL; From b15ac7e008bf0039f981da21e983b2df130152ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:25 +0000 Subject: [PATCH 034/168] PCI/sysfs: Add static PCI resource attribute macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add three macros for declaring static binary attributes for PCI resource files: - pci_dev_resource_io_attr(), for I/O BAR resources (read/write) - pci_dev_resource_uc_attr(), for memory BAR resources (mmap uncached) - pci_dev_resource_wc_attr(), for write-combine resources (mmap WC) Each macro only sets the callbacks its resource type needs. The I/O macro conditionally includes mmap support via __PCI_RESOURCE_IO_MMAP_ATTRS on architectures where arch_can_pci_mmap_io() is true at compile time (such as PowerPC, SPARC, and Xtensa). Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-7-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index dac780597727..a74eca96918b 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1197,6 +1197,47 @@ static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj, return pci_resource_io(filp, kobj, attr, buf, off, count, true); } +/* + * generic_file_llseek() consults f_mapping->host to determine + * the file size. As iomem_inode knows nothing about the + * attribute, it's not going to work, so override it as well. + */ +#if arch_can_pci_mmap_io() +# define __PCI_RESOURCE_IO_MMAP_ATTRS \ + .f_mapping = iomem_get_mapping, \ + .llseek = pci_llseek_resource, \ + .mmap = pci_mmap_resource_uc, +#else +# define __PCI_RESOURCE_IO_MMAP_ATTRS +#endif + +#define pci_dev_resource_io_attr(_bar) \ +static const struct bin_attribute dev_resource##_bar##_io_attr = { \ + .attr = { .name = "resource" __stringify(_bar), .mode = 0600 }, \ + .private = (void *)(unsigned long)(_bar), \ + .read = pci_read_resource_io, \ + .write = pci_write_resource_io, \ + __PCI_RESOURCE_IO_MMAP_ATTRS \ +} + +#define pci_dev_resource_uc_attr(_bar) \ +static const struct bin_attribute dev_resource##_bar##_uc_attr = { \ + .attr = { .name = "resource" __stringify(_bar), .mode = 0600 }, \ + .private = (void *)(unsigned long)(_bar), \ + .f_mapping = iomem_get_mapping, \ + .llseek = pci_llseek_resource, \ + .mmap = pci_mmap_resource_uc, \ +} + +#define pci_dev_resource_wc_attr(_bar) \ +static const struct bin_attribute dev_resource##_bar##_wc_attr = { \ + .attr = { .name = "resource" __stringify(_bar) "_wc", .mode = 0600 }, \ + .private = (void *)(unsigned long)(_bar), \ + .f_mapping = iomem_get_mapping, \ + .llseek = pci_llseek_resource, \ + .mmap = pci_mmap_resource_wc, \ +} + /** * pci_remove_resource_files - cleanup resource files * @pdev: dev to cleanup From 75479bed53b11f81e37fb38c0608a2af4ddc9b1e Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Fri, 17 Apr 2026 10:35:46 +0200 Subject: [PATCH 035/168] PCI: intel-gw: Remove unused PCIE_APP_INTX_OFST definition The C preprocessor define 'PCIE_APP_INTX_OFST' is not used in the sources. Delete it. Signed-off-by: Florian Eckert Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260417-pcie-intel-gw-v5-2-0a2b933fe04f@dev.tdt.de --- drivers/pci/controller/dwc/pcie-intel-gw.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c index c21906eced61..80d1607c46cb 100644 --- a/drivers/pci/controller/dwc/pcie-intel-gw.c +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c @@ -47,7 +47,6 @@ #define PCIE_APP_IRN_INTD BIT(16) #define PCIE_APP_IRN_MSG_LTR BIT(18) #define PCIE_APP_IRN_SYS_ERR_RC BIT(29) -#define PCIE_APP_INTX_OFST 12 #define PCIE_APP_IRN_INT \ (PCIE_APP_IRN_AER_REPORT | PCIE_APP_IRN_PME | \ From abddc0539e5931f4ad2f589a03cbba5a6a64485f Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Fri, 17 Apr 2026 10:35:47 +0200 Subject: [PATCH 036/168] PCI: intel-gw: Move interrupt enable to own function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To improve the readability of the code, move the interrupt enable instructions to a separate function. That is already done for the disable interrupt instruction. In addition, clear and disable all pending interrupts, as is done in intel_pcie_core_irq_disable(). After that, enable all relevant interrupts again. The 'PCIE_APP_IRNEN' definition contains all the relevant interrupts that are of interest. This change is also done in the MaxLinear SDK [1]. As I unfortunately don’t have any documentation for this IP core, I suspect that the intention is to set the IP core for interrupt handling to a specific state. Perhaps the problem is that the IP core did not reinitialize the interrupt register properly after a power cycle. In my view, it can’t do any harm to switch the interrupt off and then on again to set them to a specific state. The reason why the MaxLinear SDK is used as a reference here is, that this PCIe DWC IP is used in the URX851 and URX850 SoC. This SoC was originally developed by Intel when they acquired Lantiq’s home networking division in 2015 [2]. In 2020 the home network division was sold to MaxLinear [3]. Since then, this SoC belongs to MaxLinear. They use their own SDK, which runs on kernel version '5.15.x'. [1] https://github.com/maxlinear/linux/blob/updk_9.1.90/drivers/pci/controller/dwc/pcie-intel-gw.c#L431 [2] https://www.intc.com/news-events/press-releases/detail/364/intel-to-acquire-lantiq-advancing-the-connected-home [3] https://investors.maxlinear.com/press-releases/detail/395/maxlinear-to-acquire-intels-home-gateway-platform Signed-off-by: Florian Eckert Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260417-pcie-intel-gw-v5-3-0a2b933fe04f@dev.tdt.de --- drivers/pci/controller/dwc/pcie-intel-gw.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c index 80d1607c46cb..e88b8243cc41 100644 --- a/drivers/pci/controller/dwc/pcie-intel-gw.c +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c @@ -195,6 +195,13 @@ static void intel_pcie_device_rst_deassert(struct intel_pcie *pcie) gpiod_set_value_cansleep(pcie->reset_gpio, 0); } +static void intel_pcie_core_irq_enable(struct intel_pcie *pcie) +{ + pcie_app_wr(pcie, PCIE_APP_IRNEN, 0); + pcie_app_wr(pcie, PCIE_APP_IRNCR, PCIE_APP_IRN_INT); + pcie_app_wr(pcie, PCIE_APP_IRNEN, PCIE_APP_IRN_INT); +} + static void intel_pcie_core_irq_disable(struct intel_pcie *pcie) { pcie_app_wr(pcie, PCIE_APP_IRNEN, 0); @@ -316,9 +323,7 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie) if (ret) goto app_init_err; - /* Enable integrated interrupts */ - pcie_app_wr_mask(pcie, PCIE_APP_IRNEN, PCIE_APP_IRN_INT, - PCIE_APP_IRN_INT); + intel_pcie_core_irq_enable(pcie); return 0; From febf9ed3c35e5eec7ea384ebbd55a5296e3ca5e9 Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Fri, 17 Apr 2026 10:35:48 +0200 Subject: [PATCH 037/168] PCI: intel-gw: Enable clock before PHY init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To ensure that the boot sequence is correct, the DWC PCIe core clock must be switched on before PHY init call [1]. This changes are based on patched kernel sources of the MaxLinear SDK. The reason why the MaxLinear SDK is used as a reference here is, that this PCIe DWC IP is used in the URX851 and URX850 SoC. This SoC was originally developed by Intel when they acquired Lantiq’s home networking division in 2015 [2]. In 2020 the home network division was sold to MaxLinear [3]. Since then, this SoC belongs to MaxLinear. They use their own SDK, which runs on kernel version '5.15.x'. [1] https://github.com/maxlinear/linux/blob/updk_9.1.90/drivers/pci/controller/dwc/pcie-intel-gw.c#L544 [2] https://www.intc.com/news-events/press-releases/detail/364/intel-to-acquire-lantiq-advancing-the-connected-home [3] https://investors.maxlinear.com/press-releases/detail/395/maxlinear-to-acquire-intels-home-gateway-platform Signed-off-by: Florian Eckert Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260417-pcie-intel-gw-v5-4-0a2b933fe04f@dev.tdt.de --- drivers/pci/controller/dwc/pcie-intel-gw.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c index e88b8243cc41..6d9499d95467 100644 --- a/drivers/pci/controller/dwc/pcie-intel-gw.c +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c @@ -291,13 +291,9 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie) intel_pcie_core_rst_assert(pcie); intel_pcie_device_rst_assert(pcie); - - ret = phy_init(pcie->phy); - if (ret) - return ret; - intel_pcie_core_rst_deassert(pcie); + /* Controller clock must be provided earlier than PHY */ ret = clk_prepare_enable(pcie->core_clk); if (ret) { dev_err(pcie->pci.dev, "Core clock enable failed: %d\n", ret); @@ -306,13 +302,17 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie) pci->atu_base = pci->dbi_base + 0xC0000; + ret = phy_init(pcie->phy); + if (ret) + goto phy_err; + intel_pcie_ltssm_disable(pcie); intel_pcie_link_setup(pcie); intel_pcie_init_n_fts(pci); ret = dw_pcie_setup_rc(&pci->pp); if (ret) - goto app_init_err; + goto err; dw_pcie_upconfig_setup(pci); @@ -321,17 +321,18 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie) ret = dw_pcie_wait_for_link(pci); if (ret) - goto app_init_err; + goto err; intel_pcie_core_irq_enable(pcie); return 0; -app_init_err: +err: + phy_exit(pcie->phy); +phy_err: clk_disable_unprepare(pcie->core_clk); clk_err: intel_pcie_core_rst_assert(pcie); - phy_exit(pcie->phy); return ret; } From 1eedabe7c6170b5c73c7d801f427c127be74916e Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Fri, 17 Apr 2026 10:35:49 +0200 Subject: [PATCH 038/168] PCI: intel-gw: Add .start_link() callback The pcie-intel-gw driver had no .start_link() callback. Add one so the driver works again and does not abort with the following error messages during probing: intel-gw-pcie d1000000.pcie: host bridge /soc/pcie@d1000000 ranges: intel-gw-pcie d1000000.pcie: MEM 0x00dc000000..0x00ddffffff -> 0x00dc000000 intel-combo-phy d0c00000.combo-phy: Set combo mode: combophy[1]: mode: PCIe single lane mode intel-gw-pcie d1000000.pcie: No outbound iATU found intel-gw-pcie d1000000.pcie: Cannot initialize host intel-gw-pcie d1000000.pcie: probe with driver intel-gw-pcie failed with error -22 intel-gw-pcie c1100000.pcie: host bridge /soc/pcie@c1100000 ranges: intel-gw-pcie c1100000.pcie: MEM 0x00ce000000..0x00cfffffff -> 0x00ce000000 intel-combo-phy c0c00000.combo-phy: Set combo mode: combophy[3]: mode: PCIe single lane mode intel-gw-pcie c1100000.pcie: No outbound iATU found intel-gw-pcie c1100000.pcie: Cannot initialize host intel-gw-pcie c1100000.pcie: probe with driver intel-gw-pcie failed with error -22 Fixes: c5097b9869a1 ("Revert "PCI: dwc: Wait for link up only if link is started"") Fixes: da56a1bfbab5 ("PCI: dwc: Wait for link up only if link is started") Signed-off-by: Florian Eckert Signed-off-by: Manivannan Sadhasivam [bhelgaas: remove timestamps] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260417-pcie-intel-gw-v5-5-0a2b933fe04f@dev.tdt.de --- drivers/pci/controller/dwc/pcie-intel-gw.c | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c index 6d9499d95467..afd933050c92 100644 --- a/drivers/pci/controller/dwc/pcie-intel-gw.c +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c @@ -284,6 +284,16 @@ static void intel_pcie_turn_off(struct intel_pcie *pcie) pcie_rc_cfg_wr_mask(pcie, PCI_COMMAND, PCI_COMMAND_MEMORY, 0); } +static int intel_pcie_start_link(struct dw_pcie *pci) +{ + struct intel_pcie *pcie = dev_get_drvdata(pci->dev); + + intel_pcie_device_rst_deassert(pcie); + intel_pcie_ltssm_enable(pcie); + + return 0; +} + static int intel_pcie_host_setup(struct intel_pcie *pcie) { int ret; @@ -310,25 +320,12 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie) intel_pcie_link_setup(pcie); intel_pcie_init_n_fts(pci); - ret = dw_pcie_setup_rc(&pci->pp); - if (ret) - goto err; - dw_pcie_upconfig_setup(pci); - intel_pcie_device_rst_deassert(pcie); - intel_pcie_ltssm_enable(pcie); - - ret = dw_pcie_wait_for_link(pci); - if (ret) - goto err; - intel_pcie_core_irq_enable(pcie); return 0; -err: - phy_exit(pcie->phy); phy_err: clk_disable_unprepare(pcie->core_clk); clk_err: @@ -386,6 +383,7 @@ static int intel_pcie_rc_init(struct dw_pcie_rp *pp) } static const struct dw_pcie_ops intel_pcie_ops = { + .start_link = intel_pcie_start_link, }; static const struct dw_pcie_host_ops intel_pcie_dw_ops = { From 19f90be49c117db2d70c49f5d73ec428c84dbb1e Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Fri, 17 Apr 2026 10:35:50 +0200 Subject: [PATCH 039/168] PCI: intel-gw: Fix ATU base address setup and add optional DT 'atu' region The ATU base address was set in intel_pcie_host_setup(), which is called via pp->ops->init(). However, dw_pcie_get_resources() runs before this callback and sets a default atu_base of 0x300000, which then gets overwritten by the driver's value of 0xC0000. But this ordering is broken because atu_base must be set before dw_pcie_get_resources() runs, not after. So move the atu_base assignment from intel_pcie_host_setup() to intel_pcie_probe() to fix the initialization order. The call stack is: intel_pcie_probe dw_pcie_host_init dw_pcie_host_get_resources dw_pcie_get_resources <- sets atu_base = 0x300000 pp->ops->init intel_pcie_rc_init intel_pcie_host_setup <- was overwriting atu_base here Additionally, add support for parsing the ATU region from the device tree. If an 'atu' region is present in DT, the DWC core parses it via dw_pcie_get_resources() and the driver does not set atu_base explicitly. If 'atu' is absent, the driver falls back to the hardcoded offset (0xC0000 from DBI base) for backwards compatibility, with a warning to the user. Signed-off-by: Florian Eckert [mani: commit log] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260417-pcie-intel-gw-v5-6-0a2b933fe04f@dev.tdt.de --- drivers/pci/controller/dwc/pcie-intel-gw.c | 29 ++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-intel-gw.c b/drivers/pci/controller/dwc/pcie-intel-gw.c index afd933050c92..2674cd376f49 100644 --- a/drivers/pci/controller/dwc/pcie-intel-gw.c +++ b/drivers/pci/controller/dwc/pcie-intel-gw.c @@ -310,8 +310,6 @@ static int intel_pcie_host_setup(struct intel_pcie *pcie) goto clk_err; } - pci->atu_base = pci->dbi_base + 0xC0000; - ret = phy_init(pcie->phy); if (ret) goto phy_err; @@ -395,6 +393,7 @@ static int intel_pcie_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct intel_pcie *pcie; struct dw_pcie_rp *pp; + struct resource *res; struct dw_pcie *pci; int ret; @@ -419,6 +418,32 @@ static int intel_pcie_probe(struct platform_device *pdev) pci->ops = &intel_pcie_ops; pp->ops = &intel_pcie_dw_ops; + /* + * If the 'atu' region is not available in the devicetree, use the + * default offset from DBI region for backwards compatibility. The + * 'atu' region should always be specified in the devicetree, as + * this is a hardware-specific address that should not be defined + * in the driver. + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "atu"); + if (!res) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); + pci->dbi_base = devm_pci_remap_cfg_resource(pci->dev, res); + if (IS_ERR(pci->dbi_base)) + return PTR_ERR(pci->dbi_base); + + pci->dbi_phys_addr = res->start; + pci->atu_base = devm_ioremap(dev, res->start + 0xC0000, SZ_4K); + if (!pci->atu_base) { + dev_err(dev, "failed to remap ATU space\n"); + return -ENOMEM; + } + + pci->atu_size = SZ_4K; + pci->atu_phys_addr = res->start + 0xC0000; + dev_warn(dev, "ATU region not specified in DT. Using default offset\n"); + } + ret = dw_pcie_host_init(pp); if (ret) { dev_err(dev, "Cannot initialize host\n"); From e220b668b17f329476d0fcea6783bb06cceeca6d Mon Sep 17 00:00:00 2001 From: Florian Eckert Date: Fri, 17 Apr 2026 10:35:51 +0200 Subject: [PATCH 040/168] dt-bindings: PCI: intel,lgm-pcie: Add 'atu' resource MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'atu' information is already set in the dwc core, if it is specified in the devicetree. The driver uses its own default, if not set in the devicetree. This information is hardware-specific and should therefore be maintained in the devicetree rather than in the source. To be backward compatible, this field is not mandatory. If 'atu' resource is not specified in the devicetree, the driver’s default value is used. Signed-off-by: Florian Eckert Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260417-pcie-intel-gw-v5-7-0a2b933fe04f@dev.tdt.de --- Documentation/devicetree/bindings/pci/intel-gw-pcie.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/intel-gw-pcie.yaml b/Documentation/devicetree/bindings/pci/intel-gw-pcie.yaml index 54e2890ae631..394bb46b38e6 100644 --- a/Documentation/devicetree/bindings/pci/intel-gw-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/intel-gw-pcie.yaml @@ -27,16 +27,20 @@ properties: - const: snps,dw-pcie reg: + minItems: 3 items: - description: Controller control and status registers. - description: PCIe configuration registers. - description: Controller application registers. + - description: Internal Address Translation Unit (iATU) registers. reg-names: + minItems: 3 items: - const: dbi - const: config - const: app + - const: atu ranges: maxItems: 1 @@ -95,8 +99,9 @@ examples: #size-cells = <2>; reg = <0xd0e00000 0x1000>, <0xd2000000 0x800000>, - <0xd0a41000 0x1000>; - reg-names = "dbi", "config", "app"; + <0xd0a41000 0x1000>, + <0xd0ec0000 0x1000>; + reg-names = "dbi", "config", "app", "atu"; linux,pci-domain = <0>; max-link-speed = <4>; bus-range = <0x00 0x08>; From 1389ab9bf9f627d4daed86f492091b00f110aa86 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 1 May 2026 02:45:23 +0800 Subject: [PATCH 041/168] PCI: loongson: Do not ignore downstream devices on external bridges Loongson PCI host controllers have a hardware quirk that requires software to ignore downstream devices with device number > 0 on the internal bridges. The current implementation applies the workaround to all non-root buses, which breaks external bridges (e.g., PCIe switches) with multiple downstream devices. Fix it by only applying the workaround to internal bridges. Tested on Loongson-LS3A4000-7A1000-NUC-SE, using AMD Promontory 21 chipset add-in card [1]. $ lspci -tnnnvvv -[0000:00]-+-00.0 Loongson Technology LLC 7A1000 Chipset Hyper Transport Bridge Controller [0014:7a00] +-00.1 Loongson Technology LLC 7A2000 Chipset Hyper Transport Bridge Controller [0014:7a10] +-03.0 Loongson Technology LLC 2K1000/2000 / 7A1000 Chipset Gigabit Ethernet Controller [0014:7a03] +-04.0 Loongson Technology LLC 2K1000 / 7A1000/2000 Chipset USB OHCI Controller [0014:7a24] +-04.1 Loongson Technology LLC 2K1000 / 7A1000/2000 Chipset USB EHCI Controller [0014:7a14] +-05.0 Loongson Technology LLC 2K1000 / 7A1000/2000 Chipset USB OHCI Controller [0014:7a24] +-05.1 Loongson Technology LLC 2K1000 / 7A1000/2000 Chipset USB EHCI Controller [0014:7a14] +-06.0 Loongson Technology LLC 7A1000 Chipset Vivante GC1000 GPU [0014:7a15] +-06.1 Loongson Technology LLC 2K1000 / 7A1000 Chipset Display Controller [0014:7a06] +-07.0 Loongson Technology LLC 2K1000/2000/3000 / 3B6000M / 7A1000/2000 Chipset HD Audio Controller [0014:7a07] +-08.0 Loongson Technology LLC 2K1000 / 7A1000 Chipset 3Gb/s SATA AHCI Controller [0014:7a08] +-08.1 Loongson Technology LLC 2K1000 / 7A1000 Chipset 3Gb/s SATA AHCI Controller [0014:7a08] +-08.2 Loongson Technology LLC 2K1000 / 7A1000 Chipset 3Gb/s SATA AHCI Controller [0014:7a08] +-09.0-[01]----00.0 Qualcomm Technologies, Inc QCNFA765 Wireless Network Adapter [17cb:1103] +-0a.0-[02]----00.0 Etron Technology, Inc. EJ188/EJ198 USB 3.0 Host Controller [1b6f:7052] +-0f.0-[03-08]----00.0-[04-08]--+-00.0-[05]----00.0 Shenzhen Longsys Electronics Co., Ltd. FORESEE XP1000 / Lexar Professional CFexpress Type B Gold series, NM620 PCIe NVME SSD (DRAM-less) [1d97:5216] | +-08.0-[06]----00.0 MAXIO Technology (Hangzhou) Ltd. NVMe SSD Controller MAP1202 (DRAM-less) [1e4b:1202] | +-0c.0-[07]----00.0 Advanced Micro Devices, Inc. [AMD] 600 Series Chipset USB 3.2 Controller [1022:43f7] | \-0d.0-[08]----00.0 Advanced Micro Devices, Inc. [AMD] 600 Series Chipset SATA Controller [1022:43f6] \-16.0 Loongson Technology LLC 7A1000 Chipset SPI Controller [0014:7a0b] Fixes: 2410e3301fcc ("PCI: loongson: Don't access non-existent devices") Co-developed-by: Jiaxun Yang Signed-off-by: Jiaxun Yang Co-developed-by: Lain "Fearyncess" Yang Signed-off-by: Lain "Fearyncess" Yang Signed-off-by: Rong Zhang Signed-off-by: Manivannan Sadhasivam Link: https://oshwhub.com/wesd/b650 [1] Link: https://patch.msgid.link/20260501-ls7a-bridge-fixes-v2-1-69fa93683805@rong.moe --- drivers/pci/controller/pci-loongson.c | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index bc630ab8a283..de5e809a537d 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -80,6 +80,18 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_LOONGSON, DEV_LS7A_LPC, system_bus_quirk); +static const struct pci_device_id loongson_internal_bridge_devids[] = { + { PCI_VDEVICE(LOONGSON, DEV_LS2K_PCIE_PORT0) }, + { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT0) }, + { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT1) }, + { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT2) }, + { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT3) }, + { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT4) }, + { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT5) }, + { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT6) }, + { 0, }, +}; + /* * Some Loongson PCIe ports have hardware limitations on their Maximum Read * Request Size. They can't handle anything larger than this. Sane @@ -92,24 +104,13 @@ static void loongson_set_min_mrrs_quirk(struct pci_dev *pdev) { struct pci_bus *bus = pdev->bus; struct pci_dev *bridge; - static const struct pci_device_id bridge_devids[] = { - { PCI_VDEVICE(LOONGSON, DEV_LS2K_PCIE_PORT0) }, - { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT0) }, - { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT1) }, - { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT2) }, - { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT3) }, - { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT4) }, - { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT5) }, - { PCI_VDEVICE(LOONGSON, DEV_LS7A_PCIE_PORT6) }, - { 0, }, - }; /* look for the matching bridge */ while (!pci_is_root_bus(bus)) { bridge = bus->self; bus = bus->parent; - if (pci_match_id(bridge_devids, bridge)) { + if (pci_match_id(loongson_internal_bridge_devids, bridge)) { if (pcie_get_readrq(pdev) > 256) { pci_info(pdev, "limiting MRRS to 256\n"); pcie_set_readrq(pdev, 256); @@ -230,11 +231,11 @@ static void __iomem *pci_loongson_map_bus(struct pci_bus *bus, struct loongson_pci *priv = pci_bus_to_loongson_pci(bus); /* - * Do not read more than one device on the bus other than - * the host bus. + * Do not read more than one device on the internal bridges. */ if ((priv->data->flags & FLAG_DEV_FIX) && bus->self) { - if (!pci_is_root_bus(bus) && (device > 0)) + if (!pci_is_root_bus(bus) && (device > 0) && + pci_match_id(loongson_internal_bridge_devids, bus->self)) return NULL; } From 0492461a0508d88f2b63de4f7f4e71b38078de20 Mon Sep 17 00:00:00 2001 From: Sai Krishna Musham Date: Mon, 27 Apr 2026 17:42:27 +0530 Subject: [PATCH 042/168] PCI: amd-mdb: Assert PERST# on shutdown Add a shutdown handler for the AMD MDB PCIe host controller that asserts the PERST# signal via GPIO before the system powers off or reboots. This ensures the connected PCIe endpoint is held in reset during shutdown. Signed-off-by: Sai Krishna Musham [mani: removed conditional check since GPIO APIs return safely if desc is NULL] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260427121227.290604-1-sai.krishna.musham@amd.com --- drivers/pci/controller/dwc/pcie-amd-mdb.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-amd-mdb.c b/drivers/pci/controller/dwc/pcie-amd-mdb.c index 7e50e11fbffd..fe568d871579 100644 --- a/drivers/pci/controller/dwc/pcie-amd-mdb.c +++ b/drivers/pci/controller/dwc/pcie-amd-mdb.c @@ -507,6 +507,13 @@ static int amd_mdb_pcie_probe(struct platform_device *pdev) return amd_mdb_add_pcie_port(pcie, pdev); } +static void amd_mdb_pcie_shutdown(struct platform_device *pdev) +{ + struct amd_mdb_pcie *pcie = platform_get_drvdata(pdev); + + gpiod_set_value_cansleep(pcie->perst_gpio, 1); +} + static const struct of_device_id amd_mdb_pcie_of_match[] = { { .compatible = "amd,versal2-mdb-host", @@ -521,6 +528,7 @@ static struct platform_driver amd_mdb_pcie_driver = { .suppress_bind_attrs = true, }, .probe = amd_mdb_pcie_probe, + .shutdown = amd_mdb_pcie_shutdown, }; builtin_platform_driver(amd_mdb_pcie_driver); From d9ff07f459552eabd04f8831bcaa76d761dfe264 Mon Sep 17 00:00:00 2001 From: Jia Wang Date: Mon, 27 Apr 2026 09:32:11 +0800 Subject: [PATCH 043/168] dt-bindings: PCI: Add UltraRISC DP1000 PCIe controller Add UltraRISC DP1000 SoC PCIe controller devicetree bindings. Signed-off-by: Jia Wang Signed-off-by: Manivannan Sadhasivam [bhelgaas: squash MAINTAINERS to driver commit update to touch only once] Signed-off-by: Bjorn Helgaas Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260427-ultrarisc-pcie-v4-2-98935f6cdfb5@ultrarisc.com --- .../bindings/pci/ultrarisc,dp1000-pcie.yaml | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 Documentation/devicetree/bindings/pci/ultrarisc,dp1000-pcie.yaml diff --git a/Documentation/devicetree/bindings/pci/ultrarisc,dp1000-pcie.yaml b/Documentation/devicetree/bindings/pci/ultrarisc,dp1000-pcie.yaml new file mode 100644 index 000000000000..512b935bf5d1 --- /dev/null +++ b/Documentation/devicetree/bindings/pci/ultrarisc,dp1000-pcie.yaml @@ -0,0 +1,93 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pci/ultrarisc,dp1000-pcie.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: UltraRISC DP1000 PCIe Host Controller + +description: + UltraRISC DP1000 SoC PCIe host controller is based on the DesignWare PCIe IP. + +maintainers: + - Xincheng Zhang + - Jia Wang + +allOf: + - $ref: /schemas/pci/snps,dw-pcie.yaml# + +properties: + compatible: + const: ultrarisc,dp1000-pcie + + reg: + items: + - description: Data Bus Interface (DBI) registers. + - description: PCIe configuration space region. + + reg-names: + items: + - const: dbi + - const: config + + num-lanes: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [4, 16] + description: Number of lanes to use. + + interrupts: + items: + - description: MSI interrupt + - description: Legacy INTA interrupt + - description: Legacy INTB interrupt + - description: Legacy INTC interrupt + - description: Legacy INTD interrupt + + interrupt-names: + items: + - const: msi + - const: inta + - const: intb + - const: intc + - const: intd + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + +unevaluatedProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + pcie@21000000 { + compatible = "ultrarisc,dp1000-pcie"; + reg = <0x0 0x21000000 0x0 0x01000000>, + <0x0 0x4fff0000 0x0 0x00010000>; + reg-names = "dbi", "config"; + ranges = <0x81000000 0x0 0x4fbf0000 0x0 0x4fbf0000 0x0 0x00400000>, + <0x82000000 0x0 0x40000000 0x0 0x40000000 0x0 0x0fbf0000>, + <0xc3000000 0x40 0x00000000 0x40 0x00000000 0xd 0x00000000>; + #address-cells = <3>; + #size-cells = <2>; + #interrupt-cells = <1>; + device_type = "pci"; + dma-coherent; + bus-range = <0x0 0xff>; + num-lanes = <16>; + interrupt-parent = <&plic>; + interrupts = <43>, <44>, <45>, <46>, <47>; + interrupt-names = "msi", "inta", "intb", "intc", "intd"; + interrupt-map-mask = <0x0 0x0 0x0 0x7>; + interrupt-map = <0x0 0x0 0x0 0x1 &plic 44>, + <0x0 0x0 0x0 0x2 &plic 45>, + <0x0 0x0 0x0 0x3 &plic 46>, + <0x0 0x0 0x0 0x4 &plic 47>; + }; + }; From 5fc35740c3b32d2c820c97d041282e7bca4ad0bf Mon Sep 17 00:00:00 2001 From: Xincheng Zhang Date: Mon, 27 Apr 2026 09:32:12 +0800 Subject: [PATCH 044/168] PCI: ultrarisc: Add UltraRISC DP1000 PCIe Root Complex driver Add DP1000 SoC PCIe Root Complex driver. The controller only supports 32-bit aligned configuration space accesses. Signed-off-by: Xincheng Zhang Signed-off-by: Jia Wang [mani: changed to builtin_platform_driver() to prevent irqchip removal] Signed-off-by: Manivannan Sadhasivam [bhelgaas: squash MAINTAINERS update here] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260427-ultrarisc-pcie-v4-3-98935f6cdfb5@ultrarisc.com --- MAINTAINERS | 8 + drivers/pci/controller/dwc/Kconfig | 13 ++ drivers/pci/controller/dwc/Makefile | 1 + drivers/pci/controller/dwc/pcie-designware.h | 24 +++ drivers/pci/controller/dwc/pcie-ultrarisc.c | 175 +++++++++++++++++++ 5 files changed, 221 insertions(+) create mode 100644 drivers/pci/controller/dwc/pcie-ultrarisc.c diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..b52ea4bfe7da 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20759,6 +20759,14 @@ S: Maintained F: Documentation/devicetree/bindings/pci/starfive,jh7110-pcie.yaml F: drivers/pci/controller/plda/pcie-starfive.c +PCIE DRIVER FOR ULTRARISC DP1000 +M: Xincheng Zhang +M: Jia Wang +L: linux-pci@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/pci/ultrarisc,dp1000-pcie.yaml +F: drivers/pci/controller/dwc/pcie-ultrarisc.c + PCIE ENDPOINT DRIVER FOR QUALCOMM M: Manivannan Sadhasivam L: linux-pci@vger.kernel.org diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index f2fde13107f2..216ede0a867e 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -560,4 +560,17 @@ config PCIE_VISCONTI_HOST Say Y here if you want PCIe controller support on Toshiba Visconti SoC. This driver supports TMPV7708 SoC. +config PCIE_ULTRARISC + tristate "UltraRISC PCIe host controller" + depends on ARCH_ULTRARISC || COMPILE_TEST + select PCIE_DW_HOST + select PCI_MSI + default y if ARCH_ULTRARISC + help + Enables support for the PCIe controller in the UltraRISC SoC. + This driver supports UR-DP1000 SoC. + + By default, this symbol is enabled when ARCH_ULTRARISC is active, + requiring no further configuration on that platform. + endmenu diff --git a/drivers/pci/controller/dwc/Makefile b/drivers/pci/controller/dwc/Makefile index 7177451db8aa..d4f88cfc6229 100644 --- a/drivers/pci/controller/dwc/Makefile +++ b/drivers/pci/controller/dwc/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_PCIE_RCAR_GEN4) += pcie-rcar-gen4.o obj-$(CONFIG_PCIE_SPACEMIT_K1) += pcie-spacemit-k1.o obj-$(CONFIG_PCIE_STM32_HOST) += pcie-stm32.o obj-$(CONFIG_PCIE_STM32_EP) += pcie-stm32-ep.o +obj-$(CONFIG_PCIE_ULTRARISC) += pcie-ultrarisc.o # The following drivers are for devices that use the generic ACPI # pci_root.c driver but don't support standard ECAM config access. diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 3e69ef60165b..865c69866c04 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -71,6 +71,8 @@ /* Synopsys-specific PCIe configuration registers */ #define PCIE_PORT_FORCE 0x708 +/* Bit[7:0] LINK_NUM: Link Number. Not used for endpoint */ +#define PORT_LINK_NUM_MASK GENMASK(7, 0) #define PORT_FORCE_DO_DESKEW_FOR_SRIS BIT(23) #define PCIE_PORT_AFR 0x70C @@ -98,6 +100,28 @@ #define PCIE_PORT_LANE_SKEW 0x714 #define PORT_LANE_SKEW_INSERT_MASK GENMASK(23, 0) +/* + * PCIE_TIMER_CTRL_MAX_FUNC_NUM: Timer Control and Max Function Number + * Register. + * + * This register holds the ack frequency, latency, replay, fast link + * scaling timers, and max function number values. + * + * Bit[30:29] FAST_LINK_SCALING_FACTOR: Fast Link Timer Scaling Factor. + * 0x0 (SF_1024): Scaling Factor is 1024 (1ms is 1us). + * When the LTSSM is in Config or L12 Entry State, 1ms + * timer is 2us, 2ms timer is 4us and 3ms timer is 6us. + * 0x1 (SF_256): Scaling Factor is 256 (1ms is 4us) + * 0x2 (SF_64): Scaling Factor is 64 (1ms is 16us) + * 0x3 (SF_16): Scaling Factor is 16 (1ms is 64us) + */ +#define PCIE_TIMER_CTRL_MAX_FUNC_NUM 0x718 +#define PORT_FLT_SF_MASK GENMASK(30, 29) +#define PORT_FLT_SF_VAL_1024 0x0 +#define PORT_FLT_SF_VAL_256 0x1 +#define PORT_FLT_SF_VAL_64 0x2 +#define PORT_FLT_SF_VAL_16 0x3 + #define PCIE_PORT_DEBUG0 0x728 #define PORT_LOGIC_LTSSM_STATE_MASK 0x3f #define PORT_LOGIC_LTSSM_STATE_L0 0x11 diff --git a/drivers/pci/controller/dwc/pcie-ultrarisc.c b/drivers/pci/controller/dwc/pcie-ultrarisc.c new file mode 100644 index 000000000000..6ee661ceff67 --- /dev/null +++ b/drivers/pci/controller/dwc/pcie-ultrarisc.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * DWC PCIe RC driver for UltraRISC SoCs + * + * Copyright (C) 2026 UltraRISC Technology (Shanghai) Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include + +#include "pcie-designware.h" + +#define PCIE_CUS_CORE 0x400000 + +#define LTSSM_ENABLE BIT(7) +#define FAST_LINK_MODE BIT(12) +#define HOLD_PHY_RST BIT(14) +#define L1SUB_DISABLE BIT(15) + +#define ULTRARISC_PCIE_COMP_TIMEOUT_65_210MS 0x6 + +static struct pci_ops ultrarisc_pci_ops = { + .map_bus = dw_pcie_own_conf_map_bus, + .read = pci_generic_config_read32, + .write = pci_generic_config_write32, +}; + +static int ultrarisc_pcie_host_init(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + struct pci_host_bridge *bridge = pp->bridge; + u8 cap_exp; + u32 val; + + bridge->ops = &ultrarisc_pci_ops; + + if (dw_pcie_link_up(pci)) + return 0; + + val = dw_pcie_readl_dbi(pci, PCIE_CUS_CORE); + val &= ~FAST_LINK_MODE; + dw_pcie_writel_dbi(pci, PCIE_CUS_CORE, val); + + val = dw_pcie_readl_dbi(pci, PCIE_TIMER_CTRL_MAX_FUNC_NUM); + FIELD_MODIFY(PORT_FLT_SF_MASK, &val, PORT_FLT_SF_VAL_64); + dw_pcie_writel_dbi(pci, PCIE_TIMER_CTRL_MAX_FUNC_NUM, val); + + cap_exp = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); + val = dw_pcie_readl_dbi(pci, cap_exp + PCI_EXP_LNKCTL2); + FIELD_MODIFY(PCI_EXP_LNKCTL2_TLS, &val, PCI_EXP_LNKCTL2_TLS_16_0GT); + dw_pcie_writel_dbi(pci, cap_exp + PCI_EXP_LNKCTL2, val); + + val = dw_pcie_readl_dbi(pci, PCIE_PORT_FORCE); + FIELD_MODIFY(PORT_LINK_NUM_MASK, &val, 0); + dw_pcie_writel_dbi(pci, PCIE_PORT_FORCE, val); + + val = dw_pcie_readl_dbi(pci, cap_exp + PCI_EXP_DEVCTL2); + FIELD_MODIFY(PCI_EXP_DEVCTL2_COMP_TIMEOUT, &val, + ULTRARISC_PCIE_COMP_TIMEOUT_65_210MS); + dw_pcie_writel_dbi(pci, cap_exp + PCI_EXP_DEVCTL2, val); + + val = dw_pcie_readl_dbi(pci, PCIE_CUS_CORE); + val &= ~(HOLD_PHY_RST | L1SUB_DISABLE); + dw_pcie_writel_dbi(pci, PCIE_CUS_CORE, val); + + return 0; +} + +static void ultrarisc_pcie_pme_turn_off(struct dw_pcie_rp *pp) +{ + /* + * DP1000 does not support sending PME_Turn_Off from the RC. + * Keep this callback empty to skip the generic MSG TLP path. + */ +} + +static const struct dw_pcie_host_ops ultrarisc_pcie_host_ops = { + .init = ultrarisc_pcie_host_init, + .pme_turn_off = ultrarisc_pcie_pme_turn_off, +}; + +static int ultrarisc_pcie_start_link(struct dw_pcie *pci) +{ + u32 val; + + val = dw_pcie_readl_dbi(pci, PCIE_CUS_CORE); + val |= LTSSM_ENABLE; + dw_pcie_writel_dbi(pci, PCIE_CUS_CORE, val); + + return 0; +} + +static const struct dw_pcie_ops dw_pcie_ops = { + .start_link = ultrarisc_pcie_start_link, +}; + +static int ultrarisc_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dw_pcie_rp *pp; + struct dw_pcie *pci; + int ret; + + pci = devm_kzalloc(dev, sizeof(*pci), GFP_KERNEL); + if (!pci) + return -ENOMEM; + + pci->dev = dev; + pci->ops = &dw_pcie_ops; + + /* Set a default value suitable for at most 16 in and 16 out windows */ + pci->atu_size = SZ_8K; + + pp = &pci->pp; + + platform_set_drvdata(pdev, pci); + + pp->num_vectors = MAX_MSI_IRQS; + /* No L2/L3 Ready indication is available on this platform */ + pp->skip_l23_ready = true; + pp->ops = &ultrarisc_pcie_host_ops; + + ret = dw_pcie_host_init(pp); + if (ret) { + dev_err(dev, "Failed to initialize host\n"); + return ret; + } + + return 0; +} + +static int ultrarisc_pcie_suspend_noirq(struct device *dev) +{ + struct dw_pcie *pci = dev_get_drvdata(dev); + + return dw_pcie_suspend_noirq(pci); +} + +static int ultrarisc_pcie_resume_noirq(struct device *dev) +{ + struct dw_pcie *pci = dev_get_drvdata(dev); + + return dw_pcie_resume_noirq(pci); +} + +static const struct dev_pm_ops ultrarisc_pcie_pm_ops = { + NOIRQ_SYSTEM_SLEEP_PM_OPS(ultrarisc_pcie_suspend_noirq, + ultrarisc_pcie_resume_noirq) +}; + +static const struct of_device_id ultrarisc_pcie_of_match[] = { + { + .compatible = "ultrarisc,dp1000-pcie", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, ultrarisc_pcie_of_match); + +static struct platform_driver ultrarisc_pcie_driver = { + .driver = { + .name = "ultrarisc-pcie", + .of_match_table = ultrarisc_pcie_of_match, + .suppress_bind_attrs = true, + .pm = &ultrarisc_pcie_pm_ops, + }, + .probe = ultrarisc_pcie_probe, +}; +builtin_platform_driver(ultrarisc_pcie_driver); + +MODULE_DESCRIPTION("UltraRISC DP1000 DWC PCIe host controller"); +MODULE_LICENSE("GPL"); From 1e1b554c2b910591aa3fffdb8077a2ca33bf3cb2 Mon Sep 17 00:00:00 2001 From: Manikanta Maddireddy Date: Fri, 10 Apr 2026 11:55:07 +0530 Subject: [PATCH 045/168] PCI: dwc: Apply ECRC workaround for DesignWare cores prior to 5.10a The ECRC (TLP digest) workaround was originally applied only for DesignWare core version 4.90a. Per discussion in Synopsys case, the dependency of the iATU TD bit on ECRC generation was removed in 5.10a, so apply the workaround for all DWC versions below that release. Replace the misleading comment that referred to raw version constants with readable DesignWare release name to help readability. Fixes: b210b1595606 ("PCI: dwc: Apply ECRC workaround to DesignWare 5.00a as well") Signed-off-by: Manikanta Maddireddy [mani: corrected fixes tag format] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Jon Hunter Link: https://patch.msgid.link/20260410062507.657453-1-mmaddireddy@nvidia.com --- drivers/pci/controller/dwc/pcie-designware.c | 16 ++++++++-------- drivers/pci/controller/dwc/pcie-designware.h | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c11cf61b8319..76c9a0a10367 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -487,13 +487,13 @@ static inline void dw_pcie_writel_atu_ob(struct dw_pcie *pci, u32 index, u32 reg static inline u32 dw_pcie_enable_ecrc(u32 val) { /* - * DWC versions 0x3530302a and 0x3536322a have a design issue where - * the 'TD' bit in the Control register-1 of the ATU outbound - * region acts like an override for the ECRC setting, i.e., the - * presence of TLP Digest (ECRC) in the outgoing TLPs is solely - * determined by this bit. This is contrary to the PCIe spec which - * says that the enablement of the ECRC is solely determined by the - * AER registers. + * DesignWare core versions prior to 5.10A have a design issue where the + * 'TD' bit in the Control register-1 of the ATU outbound region acts + * like an override for the ECRC setting, i.e., the presence of TLP + * Digest (ECRC) in the outgoing TLPs is solely determined by this + * bit. This is contrary to the PCIe spec which says that the + * enablement of the ECRC is solely determined by the AER + * registers. * * Because of this, even when the ECRC is enabled through AER * registers, the transactions going through ATU won't have TLP @@ -563,7 +563,7 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, if (upper_32_bits(limit_addr) > upper_32_bits(parent_bus_addr) && dw_pcie_ver_is_ge(pci, 460A)) val |= PCIE_ATU_INCREASE_REGION_SIZE; - if (dw_pcie_ver_is(pci, 490A) || dw_pcie_ver_is(pci, 500A)) + if (!dw_pcie_ver_is_ge(pci, 510A)) val = dw_pcie_enable_ecrc(val); dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL1, val); diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 3e69ef60165b..a07b7abda41f 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -35,6 +35,7 @@ #define DW_PCIE_VER_480A 0x3438302a #define DW_PCIE_VER_490A 0x3439302a #define DW_PCIE_VER_500A 0x3530302a +#define DW_PCIE_VER_510A 0x3531302a #define DW_PCIE_VER_520A 0x3532302a #define DW_PCIE_VER_540A 0x3534302a #define DW_PCIE_VER_562A 0x3536322a From 5dc31cd4a91a7f006d23efa97a5594a7e3ac7790 Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Thu, 16 Apr 2026 21:16:25 -0700 Subject: [PATCH 046/168] PCI: qcom: Set max OPP before DBI access during resume During resume, qcom_pcie_icc_opp_update() may access DBI registers before the OPP votes are restored, triggering NoC errors. Set the PCIe controller to the maximum OPP first in resume_noirq(), then proceed with link/DBI accesses. The OPP is later updated again based on the actual link bandwidth requirements. Introduce a helper to reuse the max-OPP setup code and share it with probe(). Fixes: 5b6272e0efd5 ("PCI: qcom: Add OPP support to scale performance") Signed-off-by: Qiang Yu [mani: commit log and error log rewording] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260416-setmaxopp-v1-1-6a74e2d945a0@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 42 ++++++++++++++++---------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index af6bf5cce65b..f21f3806fc1f 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1613,6 +1613,22 @@ static void qcom_pcie_icc_opp_update(struct qcom_pcie *pcie) } } +static int qcom_pcie_set_max_opp(struct device *dev) +{ + unsigned long max_freq = ULONG_MAX; + struct dev_pm_opp *opp; + int ret; + + opp = dev_pm_opp_find_freq_floor(dev, &max_freq); + if (IS_ERR(opp)) + return PTR_ERR(opp); + + ret = dev_pm_opp_set_opp(dev, opp); + dev_pm_opp_put(opp); + + return ret; +} + static int qcom_pcie_link_transition_count(struct seq_file *s, void *data) { struct qcom_pcie *pcie = (struct qcom_pcie *)dev_get_drvdata(s->private); @@ -1845,9 +1861,7 @@ static int qcom_pcie_probe(struct platform_device *pdev) struct qcom_pcie_perst *perst, *tmp_perst; struct qcom_pcie_port *port, *tmp_port; const struct qcom_pcie_cfg *pcie_cfg; - unsigned long max_freq = ULONG_MAX; struct device *dev = &pdev->dev; - struct dev_pm_opp *opp; struct qcom_pcie *pcie; struct dw_pcie_rp *pp; struct resource *res; @@ -1951,21 +1965,9 @@ static int qcom_pcie_probe(struct platform_device *pdev) * probe(), OPP will be updated using qcom_pcie_icc_opp_update(). */ if (!ret) { - opp = dev_pm_opp_find_freq_floor(dev, &max_freq); - if (IS_ERR(opp)) { - ret = PTR_ERR(opp); - dev_err_probe(pci->dev, ret, - "Unable to find max freq OPP\n"); - goto err_pm_runtime_put; - } else { - ret = dev_pm_opp_set_opp(dev, opp); - } - - dev_pm_opp_put(opp); + ret = qcom_pcie_set_max_opp(dev); if (ret) { - dev_err_probe(pci->dev, ret, - "Failed to set OPP for freq %lu\n", - max_freq); + dev_err_probe(dev, ret, "Failed to set max OPP\n"); goto err_pm_runtime_put; } @@ -2100,6 +2102,14 @@ static int qcom_pcie_resume_noirq(struct device *dev) return 0; if (pm_suspend_target_state != PM_SUSPEND_MEM) { + if (pcie->use_pm_opp) { + ret = qcom_pcie_set_max_opp(dev); + if (ret) { + dev_err(dev, "Failed to set max OPP: %d\n", ret); + return ret; + } + } + ret = icc_enable(pcie->icc_cpu); if (ret) { dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret); From 3718315f58f8ad663cc9c0225e3a9f3fc365dc8c Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 2 May 2026 00:10:07 +0800 Subject: [PATCH 047/168] PCI: dra7xx: Use common mode field in struct dw_pcie Remove the redundant mode field from struct dra7xx_pcie and use the existing mode field in struct dw_pcie instead. This avoids duplication and prevents potential inconsistencies between the two mode fields. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Reviewed-by: Bjorn Helgaas Link: https://patch.msgid.link/20260501161010.71688-2-18255117159@163.com --- drivers/pci/controller/dwc/pci-dra7xx.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-dra7xx.c b/drivers/pci/controller/dwc/pci-dra7xx.c index cd904659c321..3fc889944f02 100644 --- a/drivers/pci/controller/dwc/pci-dra7xx.c +++ b/drivers/pci/controller/dwc/pci-dra7xx.c @@ -92,7 +92,6 @@ struct dra7xx_pcie { struct phy **phy; struct irq_domain *irq_domain; struct clk *clk; - enum dw_pcie_device_mode mode; }; struct dra7xx_pcie_of_data { @@ -328,7 +327,7 @@ static irqreturn_t dra7xx_pcie_irq_handler(int irq, void *arg) dev_dbg(dev, "Link Request Reset\n"); if (reg & LINK_UP_EVT) { - if (dra7xx->mode == DW_PCIE_EP_TYPE) + if (dra7xx->pci->mode == DW_PCIE_EP_TYPE) dw_pcie_ep_linkup(ep); dev_dbg(dev, "Link-up state change\n"); } @@ -828,7 +827,7 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) default: dev_err(dev, "INVALID device type %d\n", mode); } - dra7xx->mode = mode; + dra7xx->pci->mode = mode; ret = devm_request_threaded_irq(dev, irq, NULL, dra7xx_pcie_irq_handler, IRQF_SHARED | IRQF_ONESHOT, @@ -841,7 +840,7 @@ static int dra7xx_pcie_probe(struct platform_device *pdev) return 0; err_deinit: - if (dra7xx->mode == DW_PCIE_RC_TYPE) + if (dra7xx->pci->mode == DW_PCIE_RC_TYPE) dw_pcie_host_deinit(&dra7xx->pci->pp); else dw_pcie_ep_deinit(&dra7xx->pci->ep); @@ -865,7 +864,7 @@ static int dra7xx_pcie_suspend(struct device *dev) struct dw_pcie *pci = dra7xx->pci; u32 val; - if (dra7xx->mode != DW_PCIE_RC_TYPE) + if (pci->mode != DW_PCIE_RC_TYPE) return 0; /* clear MSE */ @@ -882,7 +881,7 @@ static int dra7xx_pcie_resume(struct device *dev) struct dw_pcie *pci = dra7xx->pci; u32 val; - if (dra7xx->mode != DW_PCIE_RC_TYPE) + if (pci->mode != DW_PCIE_RC_TYPE) return 0; /* set MSE */ From 5d47f6aeffdf55524fb1130666057266a741e97e Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 2 May 2026 00:10:08 +0800 Subject: [PATCH 048/168] PCI: artpec6: Use common mode field in struct dw_pcie Remove the redundant mode field from struct artpec6_pcie and use the existing mode field in struct dw_pcie instead. This avoids duplication and prevents potential inconsistencies between the two mode fields. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Reviewed-by: Bjorn Helgaas Link: https://patch.msgid.link/20260501161010.71688-3-18255117159@163.com --- drivers/pci/controller/dwc/pcie-artpec6.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-artpec6.c b/drivers/pci/controller/dwc/pcie-artpec6.c index 55cb957ae1f3..5cd227dda9a1 100644 --- a/drivers/pci/controller/dwc/pcie-artpec6.c +++ b/drivers/pci/controller/dwc/pcie-artpec6.c @@ -34,7 +34,6 @@ struct artpec6_pcie { struct regmap *regmap; /* DT axis,syscon-pcie */ void __iomem *phy_base; /* DT phy */ enum artpec_pcie_variants variant; - enum dw_pcie_device_mode mode; }; struct artpec_pcie_of_data { @@ -100,7 +99,7 @@ static u64 artpec6_pcie_cpu_addr_fixup(struct dw_pcie *pci, u64 cpu_addr) struct dw_pcie_rp *pp = &pci->pp; struct dw_pcie_ep *ep = &pci->ep; - switch (artpec6_pcie->mode) { + switch (artpec6_pcie->pci->mode) { case DW_PCIE_RC_TYPE: return cpu_addr - pp->cfg0_base; case DW_PCIE_EP_TYPE: @@ -413,7 +412,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev) artpec6_pcie->pci = pci; artpec6_pcie->variant = variant; - artpec6_pcie->mode = mode; + artpec6_pcie->pci->mode = mode; artpec6_pcie->phy_base = devm_platform_ioremap_resource_byname(pdev, "phy"); @@ -428,7 +427,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev) platform_set_drvdata(pdev, artpec6_pcie); - switch (artpec6_pcie->mode) { + switch (artpec6_pcie->pci->mode) { case DW_PCIE_RC_TYPE: if (!IS_ENABLED(CONFIG_PCIE_ARTPEC6_HOST)) return -ENODEV; @@ -464,7 +463,7 @@ static int artpec6_pcie_probe(struct platform_device *pdev) break; default: - dev_err(dev, "INVALID device type %d\n", artpec6_pcie->mode); + dev_err(dev, "INVALID device type %d\n", artpec6_pcie->pci->mode); } return 0; From a8bf471f87f4c5c180641a2c6f7d82b1aa0f61fd Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 2 May 2026 00:10:09 +0800 Subject: [PATCH 049/168] PCI: dwc: Use common mode field in struct dw_pcie Remove the redundant mode field from struct dw_plat_pcie and use the existing mode field in struct dw_pcie instead. This avoids duplication and prevents potential inconsistencies between the two mode fields. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Reviewed-by: Bjorn Helgaas Link: https://patch.msgid.link/20260501161010.71688-4-18255117159@163.com --- drivers/pci/controller/dwc/pcie-designware-plat.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-plat.c b/drivers/pci/controller/dwc/pcie-designware-plat.c index d103ab759c4e..d02286678a0a 100644 --- a/drivers/pci/controller/dwc/pcie-designware-plat.c +++ b/drivers/pci/controller/dwc/pcie-designware-plat.c @@ -22,7 +22,6 @@ struct dw_plat_pcie { struct dw_pcie *pci; - enum dw_pcie_device_mode mode; }; struct dw_plat_pcie_of_data { @@ -118,11 +117,11 @@ static int dw_plat_pcie_probe(struct platform_device *pdev) pci->dev = dev; dw_plat_pcie->pci = pci; - dw_plat_pcie->mode = mode; + dw_plat_pcie->pci->mode = mode; platform_set_drvdata(pdev, dw_plat_pcie); - switch (dw_plat_pcie->mode) { + switch (dw_plat_pcie->pci->mode) { case DW_PCIE_RC_TYPE: if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_HOST)) return -ENODEV; @@ -148,7 +147,7 @@ static int dw_plat_pcie_probe(struct platform_device *pdev) break; default: - dev_err(dev, "INVALID device type %d\n", dw_plat_pcie->mode); + dev_err(dev, "INVALID device type %d\n", dw_plat_pcie->pci->mode); ret = -EINVAL; break; } From f02459d0342f94fbbc6296fca0f221783d3a37de Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 2 May 2026 00:10:10 +0800 Subject: [PATCH 050/168] PCI: keembay: Use common mode field in struct dw_pcie Remove the redundant mode field from struct keembay_pcie and use the existing mode field in struct dw_pcie instead. This avoids duplication and prevents potential inconsistencies between the two mode fields. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Reviewed-by: Bjorn Helgaas Link: https://patch.msgid.link/20260501161010.71688-5-18255117159@163.com --- drivers/pci/controller/dwc/pcie-keembay.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-keembay.c b/drivers/pci/controller/dwc/pcie-keembay.c index 7cf2c312ecec..2459c4d66b88 100644 --- a/drivers/pci/controller/dwc/pcie-keembay.c +++ b/drivers/pci/controller/dwc/pcie-keembay.c @@ -58,7 +58,6 @@ struct keembay_pcie { struct dw_pcie pci; void __iomem *apb_base; - enum dw_pcie_device_mode mode; struct clk *clk_master; struct clk *clk_aux; @@ -117,7 +116,7 @@ static int keembay_pcie_start_link(struct dw_pcie *pci) u32 val; int ret; - if (pcie->mode == DW_PCIE_EP_TYPE) + if (pcie->pci.mode == DW_PCIE_EP_TYPE) return 0; keembay_pcie_ltssm_set(pcie, false); @@ -409,7 +408,7 @@ static int keembay_pcie_probe(struct platform_device *pdev) pci->dev = dev; pci->ops = &keembay_pcie_ops; - pcie->mode = mode; + pcie->pci.mode = mode; pcie->apb_base = devm_platform_ioremap_resource_byname(pdev, "apb"); if (IS_ERR(pcie->apb_base)) @@ -417,7 +416,7 @@ static int keembay_pcie_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pcie); - switch (pcie->mode) { + switch (pcie->pci.mode) { case DW_PCIE_RC_TYPE: if (!IS_ENABLED(CONFIG_PCIE_KEEMBAY_HOST)) return -ENODEV; @@ -443,7 +442,7 @@ static int keembay_pcie_probe(struct platform_device *pdev) break; default: - dev_err(dev, "Invalid device type %d\n", pcie->mode); + dev_err(dev, "Invalid device type %d\n", pcie->pci.mode); return -ENODEV; } From 5ef4bac02189bee0b7c170e352d7a38e13fe9678 Mon Sep 17 00:00:00 2001 From: Mahesh Vaidya Date: Thu, 30 Apr 2026 13:43:29 -0700 Subject: [PATCH 051/168] PCI: altera: Do not dispose parent IRQ mapping altera_pcie_irq_teardown() calls irq_dispose_mapping() on pcie->irq. However, pcie->irq is the parent IRQ returned by platform_get_irq(), not the mapping created by Altera INTx irq_domain. The Altera driver only sets the chained handler on the parent IRQ. It should detach that handler during teardown, but it should not dispose the parent IRQ mapping, which belongs to the parent interrupt controller's irq_domain. Drop irq_dispose_mapping(pcie->irq) from the teardown path. Note that during irqchip remove(), the child IRQs should've disposed. But since the chained handler itself is removed, there is no way the stale child IRQs (if exists) could fire. So it is safe here. Fixes: ec15c4d0d5d2 ("PCI: altera: Allow building as module") Signed-off-by: Mahesh Vaidya [mani: added a note about IRQ disposal] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Subhransu S. Prusty Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260430204330.3121003-2-mahesh.vaidya@altera.com --- drivers/pci/controller/pcie-altera.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/pcie-altera.c b/drivers/pci/controller/pcie-altera.c index 3dbb7adc421c..3d3519b8d88f 100644 --- a/drivers/pci/controller/pcie-altera.c +++ b/drivers/pci/controller/pcie-altera.c @@ -868,7 +868,6 @@ static void altera_pcie_irq_teardown(struct altera_pcie *pcie) { irq_set_chained_handler_and_data(pcie->irq, NULL, NULL); irq_domain_remove(pcie->irq_domain); - irq_dispose_mapping(pcie->irq); } static int altera_pcie_parse_dt(struct altera_pcie *pcie) From 7a94138caeb27f3c49c1dbd93bf422098925bb28 Mon Sep 17 00:00:00 2001 From: Mahesh Vaidya Date: Thu, 30 Apr 2026 13:43:30 -0700 Subject: [PATCH 052/168] PCI: altera: Fix resource leaks on probe failure The chained IRQ handler is set during probe, but is only removed during the driver remove(). If pci_host_probe() fails, the handler and INTx IRQ domain remain set even though the devm-managed host bridge storage containing struct altera_pcie will be released, leaving the handler with a stale data pointer. Interrupts are also enabled before pci_host_probe() is called. If probe fails after that point, the controller interrupt source should be disabled before the chained handler and INTx domain are removed. So set the chained handler only after the INTx domain has been created. Disable controller interrupts during IRQ teardown, and tear the IRQ setup down if pci_host_probe() fails. Fixes: c63aed7334c2 ("PCI: altera: Use pci_host_probe() to register host") Signed-off-by: Mahesh Vaidya [mani: commit log] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Subhransu S. Prusty Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260430204330.3121003-3-mahesh.vaidya@altera.com --- drivers/pci/controller/pcie-altera.c | 35 ++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/pcie-altera.c b/drivers/pci/controller/pcie-altera.c index 3d3519b8d88f..76f3823d9613 100644 --- a/drivers/pci/controller/pcie-altera.c +++ b/drivers/pci/controller/pcie-altera.c @@ -864,8 +864,23 @@ static int altera_pcie_init_irq_domain(struct altera_pcie *pcie) return 0; } +static void altera_pcie_disable_irq(struct altera_pcie *pcie) +{ + if (pcie->pcie_data->version == ALTERA_PCIE_V1 || + pcie->pcie_data->version == ALTERA_PCIE_V2) { + /* Disable all P2A interrupts */ + cra_writel(pcie, 0, P2A_INT_ENABLE); + } else if (pcie->pcie_data->version == ALTERA_PCIE_V3) { + /* Disable port-level interrupts (CFG_AER, etc.) */ + writel(0, pcie->hip_base + + pcie->pcie_data->port_conf_offset + + pcie->pcie_data->port_irq_enable_offset); + } +} + static void altera_pcie_irq_teardown(struct altera_pcie *pcie) { + altera_pcie_disable_irq(pcie); irq_set_chained_handler_and_data(pcie->irq, NULL, NULL); irq_domain_remove(pcie->irq_domain); } @@ -890,7 +905,6 @@ static int altera_pcie_parse_dt(struct altera_pcie *pcie) if (pcie->irq < 0) return pcie->irq; - irq_set_chained_handler_and_data(pcie->irq, pcie->pcie_data->ops->rp_isr, pcie); return 0; } @@ -1019,6 +1033,14 @@ static int altera_pcie_probe(struct platform_device *pdev) return ret; } + /* + * The chained handler uses pcie->irq_domain, so set it only after the + * INTx domain has been created. + */ + irq_set_chained_handler_and_data(pcie->irq, + pcie->pcie_data->ops->rp_isr, + pcie); + if (pcie->pcie_data->version == ALTERA_PCIE_V1 || pcie->pcie_data->version == ALTERA_PCIE_V2) { /* clear all interrupts */ @@ -1036,7 +1058,16 @@ static int altera_pcie_probe(struct platform_device *pdev) bridge->busnr = pcie->root_bus_nr; bridge->ops = &altera_pcie_ops; - return pci_host_probe(bridge); + ret = pci_host_probe(bridge); + if (ret) + goto err_teardown_irq; + + return 0; + +err_teardown_irq: + altera_pcie_irq_teardown(pcie); + + return ret; } static void altera_pcie_remove(struct platform_device *pdev) From 22fc8822d14663cad66f2852e7a14f442e1d3ab5 Mon Sep 17 00:00:00 2001 From: Jian Yang Date: Mon, 13 Apr 2026 15:13:55 +0800 Subject: [PATCH 053/168] PCI: mediatek-gen3: Fix PERST# control timing during system startup Some MediaTek chips stop generating REFCLK if the PCIE_PHY_RSTB signal of PCIe controller is asserted at the start of mtk_pcie_devices_power_up(). But the driver deasserts PCIE_PHY_RSTB together with PCIE_PE_RSTB signal that is used to deassert PERST#. This violates PCIe CEM r6.0, sec 2.11.2, which mandates waiting for 100ms (PCIE_T_PVPERL_MS) after power becomes stable. Move the MAC, PHY and BRG reset deassert code above the PCIE_T_PVPERL_MS delay and leave the PCIE_PE_RSTB deassertion after the delay. Add the 10ms delay mentioned in the MediaTek datasheet after asserting PCIE_BRG_RSTB and before accessing the PCIE_RST_CTRL_REG register. Signed-off-by: Jian Yang [mani: commit log and comments rewording] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260413071401.1151-2-jian.yang@mediatek.com --- drivers/pci/controller/pcie-mediatek-gen3.c | 26 ++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c index b0accd828589..01badc4f9311 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -63,6 +63,12 @@ #define PCIE_BRG_RSTB BIT(2) #define PCIE_PE_RSTB BIT(3) +/* + * As described in the datasheet of MediaTek PCIe Gen3 controller, wait 10ms + * after setting PCIE_BRG_RSTB, and before accessing PCIe internal registers. + */ +#define PCIE_BRG_RST_RDY_MS 10 + #define PCIE_LTSSM_STATUS_REG 0x150 #define PCIE_LTSSM_STATE_MASK GENMASK(28, 24) #define PCIE_LTSSM_STATE(val) ((val & PCIE_LTSSM_STATE_MASK) >> 24) @@ -430,6 +436,21 @@ static int mtk_pcie_devices_power_up(struct mtk_gen3_pcie *pcie) return err; } + /* + * Some of MediaTek's chips won't output REFCLK when PCIE_PHY_RSTB is + * asserted, we have to de-assert MAC & PHY & BRG reset signals first + * to allow the REFCLK to be stable. While PCIE_BRG_RSTB is asserted, + * there is a short period during which the PCIe internal register + * cannot be accessed, so we need to wait 10ms here. + */ + msleep(PCIE_BRG_RST_RDY_MS); + + if (!(pcie->soc->flags & SKIP_PCIE_RSTB)) { + /* De-assert MAC, PHY and BRG reset signals */ + val &= ~(PCIE_MAC_RSTB | PCIE_PHY_RSTB | PCIE_BRG_RSTB); + writel_relaxed(val, pcie->base + PCIE_RST_CTRL_REG); + } + /* * Described in PCIe CEM specification revision 6.0. * @@ -439,9 +460,8 @@ static int mtk_pcie_devices_power_up(struct mtk_gen3_pcie *pcie) msleep(PCIE_T_PVPERL_MS); if (!(pcie->soc->flags & SKIP_PCIE_RSTB)) { - /* De-assert reset signals */ - val &= ~(PCIE_MAC_RSTB | PCIE_PHY_RSTB | PCIE_BRG_RSTB | - PCIE_PE_RSTB); + /* De-assert PERST# signal */ + val &= ~PCIE_PE_RSTB; writel_relaxed(val, pcie->base + PCIE_RST_CTRL_REG); } From 7a0e17e7a0816d532c0f02e8d64f603a4dc283ee Mon Sep 17 00:00:00 2001 From: Jian Yang Date: Mon, 13 Apr 2026 15:13:56 +0800 Subject: [PATCH 054/168] PCI: mediatek-gen3: Add a .shutdown() callback to control PERST# signal Add a .shutdown() callback to control the timing of PERST# and power during system shutdown to ensure that PERST# is asserted before power to the connector is removed, as required by PCIe CEM r6.0, sec 2.2. Signed-off-by: Jian Yang Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260413071401.1151-3-jian.yang@mediatek.com --- drivers/pci/controller/pcie-mediatek-gen3.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c index 01badc4f9311..8aec57626f5f 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -1286,6 +1286,14 @@ static void mtk_pcie_remove(struct platform_device *pdev) mtk_pcie_irq_teardown(pcie); } +static void mtk_pcie_shutdown(struct platform_device *pdev) +{ + struct mtk_gen3_pcie *pcie = platform_get_drvdata(pdev); + + mtk_pcie_devices_power_down(pcie); + mtk_pcie_power_down(pcie); +} + static void mtk_pcie_irq_save(struct mtk_gen3_pcie *pcie) { int i; @@ -1424,6 +1432,7 @@ MODULE_DEVICE_TABLE(of, mtk_pcie_of_match); static struct platform_driver mtk_pcie_driver = { .probe = mtk_pcie_probe, .remove = mtk_pcie_remove, + .shutdown = mtk_pcie_shutdown, .driver = { .name = "mtk-pcie-gen3", .of_match_table = mtk_pcie_of_match, From d39d55d7411c18ca6aeb63aafa8035f4ad8b317f Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 5 May 2026 18:59:16 +0800 Subject: [PATCH 055/168] PCI: mediatek-gen3: Do full device power down on removal When power control for downstream devices was introduced in the mediatek-gen3 PCIe controller driver, only the power to the downstream devices was cut when the controller driver is removed. This matched existing behavior, but in hindsight a proper power down sequence should have been followed. Call mtk_pcie_devices_power_down() on driver removal so that in addition to removing power from the downstream devices, PERST# is asserted. Fixes: 1a152e21940a ("PCI: mediatek-gen3: Integrate new pwrctrl API") Signed-off-by: Chen-Yu Tsai Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260505105918.1823170-1-wenst@chromium.org --- drivers/pci/controller/pcie-mediatek-gen3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c index 8aec57626f5f..1da2166d1017 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -1280,7 +1280,7 @@ static void mtk_pcie_remove(struct platform_device *pdev) pci_remove_root_bus(host->bus); pci_unlock_rescan_remove(); - pci_pwrctrl_power_off_devices(pcie->dev); + mtk_pcie_devices_power_down(pcie); mtk_pcie_power_down(pcie); pci_pwrctrl_destroy_devices(pcie->dev); mtk_pcie_irq_teardown(pcie); From 6113cea475959372e8e07144fa3824642440f388 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 18 May 2026 14:12:18 -0500 Subject: [PATCH 056/168] PCI: Log device readiness timeouts as errors pci_dev_wait() waits for a device to be Configuration-Ready after a reset, such as a Function-Level Reset (FLR), a soft reset during a D3hot-> D0uninitialized transition when No_Soft_Reset == 0), or a power-up sequence from D3cold->D0uninitialized. If pci_dev_wait() returns success, the device is guaranteed to respond to configuration requests with Successful Completion status. If it times out, device is completely non-responsive. Upgrade the log level from pci_warn() to pci_err() to reflect this failure state. Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260518191220.636213-2-bhelgaas@google.com --- drivers/pci/pci.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8f7cfcc00090..5a9af0bb2c71 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1253,8 +1253,8 @@ static int pci_dev_wait(struct pci_dev *dev, char *reset_type, int timeout) } if (delay > timeout) { - pci_warn(dev, "not ready %dms after %s; giving up\n", - delay - 1, reset_type); + pci_err(dev, "not ready %dms after %s; giving up\n", + delay - 1, reset_type); return -ENOTTY; } From 41167a1e98536b4baf0846fd259c8124bd1c4e1b Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 18 May 2026 14:12:19 -0500 Subject: [PATCH 057/168] PCI: Wait for device readiness after D3hot -> D0uninitialized transition For a device that advertises No_Soft_Reset == 0, a transition from D3hot to D0uninitialized is a soft reset, and the resulting internal device state is undefined. Per PCIe r7.0, sec 2.3.1, a transition from D3hot to D0uninitialized mandates a minimum 10 ms delay before accessing the device. Following this delay, the device is permitted to respond to initial configuration requests with a Request Retry Status (RRS) completion status if it needs more time to initialize. Call pci_dev_wait() after pci_power_up() performs a D3hot->D0uninitialized transition to ensure the device is ready to accept config accesses, as is done after the similar transition in pci_pm_reset(). If the device is already ready, this is essentially a no-op except for one additional config read. Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki (Intel) Link: https://patch.msgid.link/20260518191220.636213-3-bhelgaas@google.com --- drivers/pci/pci.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 5a9af0bb2c71..8228d2782f95 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1300,7 +1300,18 @@ int pci_power_up(struct pci_dev *dev) bool need_restore; pci_power_t state; u16 pmcsr; + int ret; + /* + * When setting power state to D0, platform_pci_set_power_state() + * ensures main power is on. If it puts the device in D0, it also + * completes any required delays after the transition; if it leaves + * the device in D1, D2, or D3hot, we use the PM Capability to + * transition to D0. + * + * In all cases, the device is either Configuration-Ready or + * inaccessible upon return. + */ platform_pci_set_power_state(dev, PCI_D0); if (!dev->pm_cap) { @@ -1341,10 +1352,19 @@ int pci_power_up(struct pci_dev *dev) pci_write_config_word(dev, dev->pm_cap + PCI_PM_CTRL, 0); /* Mandatory transition delays; see PCI PM 1.2. */ - if (state == PCI_D3hot) + if (state == PCI_D3hot) { pci_dev_d3_sleep(dev); - else if (state == PCI_D2) + if (!(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET)) { + ret = pci_dev_wait(dev, "power up D3hot->D0uninitialized", + PCIE_RESET_READY_POLL_MS); + if (ret) { + dev->current_state = PCI_D3cold; + return -EIO; + } + } + } else if (state == PCI_D2) { udelay(PCI_PM_D2_DELAY); + } end: dev->current_state = PCI_D0; From 10baa9b4df4005ca53c59c30c2d4b774469e10b6 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Sun, 3 May 2026 15:34:46 +0200 Subject: [PATCH 058/168] PCI: Drop unnecessary retries when restoring BARs In 2012, commit 26f41062f28d ("PCI: check for pci bar restore completion and retry") amended pci_restore_state() to attempt BAR restoration up to 10 times. This was necessary because back in the day, only a 100 msec delay was observed after pcie_flr() carried out a Function Level Reset. The retries ensured that BARs were restored even if devices needed more time to come out of reset. In 2016, commit 5adecf817dd6 ("PCI: Wait for up to 1000ms after FLR reset") extended the delay to 1 sec. Commit a2758b6b8fdb ("PCI: Rename pci_flr_wait() to pci_dev_wait() and make it generic") subsequently extended it further to 60 sec. The lengthened delay makes it unnecessary to retry BAR restoration, so drop it. Reported-by: Bjorn Helgaas Closes: https://lore.kernel.org/r/20260416225745.GA41850@bhelgaas/ Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/785c98b50a7a00d0698848c75d51b8f5669ad18f.1777814679.git.lukas@wunner.de --- drivers/pci/pci.c | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8228d2782f95..a21b0075e1de 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1784,7 +1784,7 @@ int pci_save_state(struct pci_dev *dev) EXPORT_SYMBOL(pci_save_state); static void pci_restore_config_dword(struct pci_dev *pdev, int offset, - u32 saved_val, int retry, bool force) + u32 saved_val, bool force) { u32 val; @@ -1792,52 +1792,42 @@ static void pci_restore_config_dword(struct pci_dev *pdev, int offset, if (!force && val == saved_val) return; - for (;;) { - pci_dbg(pdev, "restore config %#04x: %#010x -> %#010x\n", - offset, val, saved_val); - pci_write_config_dword(pdev, offset, saved_val); - if (retry-- <= 0) - return; + pci_dbg(pdev, "restore config %#04x: %#010x -> %#010x\n", offset, val, + saved_val); - pci_read_config_dword(pdev, offset, &val); - if (val == saved_val) - return; - - mdelay(1); - } + pci_write_config_dword(pdev, offset, saved_val); } static void pci_restore_config_space_range(struct pci_dev *pdev, - int start, int end, int retry, - bool force) + int start, int end, bool force) { int index; for (index = end; index >= start; index--) pci_restore_config_dword(pdev, 4 * index, pdev->saved_config_space[index], - retry, force); + force); } static void pci_restore_config_space(struct pci_dev *pdev) { if (pdev->hdr_type == PCI_HEADER_TYPE_NORMAL) { - pci_restore_config_space_range(pdev, 10, 15, 0, false); + pci_restore_config_space_range(pdev, 10, 15, false); /* Restore BARs before the command register. */ - pci_restore_config_space_range(pdev, 4, 9, 10, false); - pci_restore_config_space_range(pdev, 0, 3, 0, false); + pci_restore_config_space_range(pdev, 4, 9, false); + pci_restore_config_space_range(pdev, 0, 3, false); } else if (pdev->hdr_type == PCI_HEADER_TYPE_BRIDGE) { - pci_restore_config_space_range(pdev, 12, 15, 0, false); + pci_restore_config_space_range(pdev, 12, 15, false); /* * Force rewriting of prefetch registers to avoid S3 resume * issues on Intel PCI bridges that occur when these * registers are not explicitly written. */ - pci_restore_config_space_range(pdev, 9, 11, 0, true); - pci_restore_config_space_range(pdev, 0, 8, 0, false); + pci_restore_config_space_range(pdev, 9, 11, true); + pci_restore_config_space_range(pdev, 0, 8, false); } else { - pci_restore_config_space_range(pdev, 0, 15, 0, false); + pci_restore_config_space_range(pdev, 0, 15, false); } } From 548f3d287d92bcbc908b02db57fb889f8a8276a0 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 18 May 2026 12:07:00 +0200 Subject: [PATCH 059/168] PCI/pwrctrl: Lock device when calling device_is_bound() The kerneldoc for device_is_bound() states that it must be called with the device lock taken. Synchronize the two calls in pwrctrl core. Fixes: b35cf3b6aa1e ("PCI/pwrctrl: Add APIs to power on/off pwrctrl devices") Signed-off-by: Bartosz Golaszewski Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260518100700.47581-1-bartosz.golaszewski@oss.qualcomm.com --- drivers/pci/pwrctrl/core.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/pci/pwrctrl/core.c b/drivers/pci/pwrctrl/core.c index b5a0a14d316e..de3b78fa4b5b 100644 --- a/drivers/pci/pwrctrl/core.c +++ b/drivers/pci/pwrctrl/core.c @@ -206,10 +206,12 @@ static void pci_pwrctrl_power_off_device(struct device_node *np) if (!pdev) return; - if (device_is_bound(&pdev->dev)) { - ret = __pci_pwrctrl_power_off_device(&pdev->dev); - if (ret) - dev_err(&pdev->dev, "Failed to power off device: %d", ret); + scoped_guard(device, &pdev->dev) { + if (device_is_bound(&pdev->dev)) { + ret = __pci_pwrctrl_power_off_device(&pdev->dev); + if (ret) + dev_err(&pdev->dev, "Failed to power off device: %d", ret); + } } platform_device_put(pdev); @@ -250,7 +252,7 @@ static int __pci_pwrctrl_power_on_device(struct device *dev) static int pci_pwrctrl_power_on_device(struct device_node *np) { struct platform_device *pdev; - int ret; + int ret = 0; for_each_available_child_of_node_scoped(np, child) { ret = pci_pwrctrl_power_on_device(child); @@ -265,12 +267,14 @@ static int pci_pwrctrl_power_on_device(struct device_node *np) if (!pdev) return 0; - if (device_is_bound(&pdev->dev)) { - ret = __pci_pwrctrl_power_on_device(&pdev->dev); - } else { - /* FIXME: Use blocking wait instead of probe deferral */ - dev_dbg(&pdev->dev, "driver is not bound\n"); - ret = -EPROBE_DEFER; + scoped_guard(device, &pdev->dev) { + if (device_is_bound(&pdev->dev)) { + ret = __pci_pwrctrl_power_on_device(&pdev->dev); + } else { + /* FIXME: Use blocking wait instead of probe deferral */ + dev_dbg(&pdev->dev, "driver is not bound\n"); + ret = -EPROBE_DEFER; + } } platform_device_put(pdev); From aad953fb4eed0df5486cd54ccad80ac197678e01 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Thu, 19 Mar 2026 17:08:44 +0800 Subject: [PATCH 060/168] PCI: imx6: Fix IMX6SX_GPR12_PCIE_TEST_POWERDOWN handling The IMX6SX_GPR12_PCIE_TEST_POWERDOWN bit does not control the PCIe reference clock on i.MX6SX. Instead, it is part of i.MX6SX PCIe core reset sequence. Move the IMX6SX_GPR12_PCIE_TEST_POWERDOWN assertion/deassertion into the core reset functions to properly reflect its purpose. Remove the .enable_ref_clk() callback for i.MX6SX since it was incorrectly manipulating this bit. Fixes: e3c06cd063d6 ("PCI: imx6: Add initial imx6sx support") Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260319090844.444987-1-hongxing.zhu@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index e35044cc5218..1034ac5c5f5c 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -665,14 +665,6 @@ static int imx_pcie_attach_pd(struct device *dev) return 0; } -static int imx6sx_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) -{ - regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, - IMX6SX_GPR12_PCIE_TEST_POWERDOWN, - enable ? 0 : IMX6SX_GPR12_PCIE_TEST_POWERDOWN); - return 0; -} - static int imx6q_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) { if (enable) { @@ -786,6 +778,9 @@ static int imx6sx_pcie_core_reset(struct imx_pcie *imx_pcie, bool assert) if (assert) regmap_set_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, IMX6SX_GPR12_PCIE_TEST_POWERDOWN); + else + regmap_clear_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR12, + IMX6SX_GPR12_PCIE_TEST_POWERDOWN); /* Force PCIe PHY reset */ regmap_update_bits(imx_pcie->iomuxc_gpr, IOMUXC_GPR5, IMX6SX_GPR5_PCIE_BTNRST_RESET, @@ -1877,7 +1872,6 @@ static const struct imx_pcie_drvdata drvdata[] = { .mode_off[0] = IOMUXC_GPR12, .mode_mask[0] = IMX6Q_GPR12_DEVICE_TYPE, .init_phy = imx6sx_pcie_init_phy, - .enable_ref_clk = imx6sx_pcie_enable_ref_clk, .core_reset = imx6sx_pcie_core_reset, .ops = &imx_pcie_host_ops, }, From e3d334156b593c465153700b3f9fbdcac5af9cbd Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:38 +0800 Subject: [PATCH 061/168] dt-bindings: PCI: fsl,imx6q-pcie: Add reset GPIO in Root Port node Update fsl,imx6q-pcie.yaml to include the standard reset-gpios property for the Root Port node. The reset-gpios property is already defined in pci-bus-common.yaml for PERST#, so use it instead of the local reset-gpio property. Keep the existing reset-gpio property in the bridge node for backward compatibility, but mark it as deprecated. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260422093549.407022-2-sherry.sun@nxp.com --- .../bindings/pci/fsl,imx6q-pcie.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml index 9d1349855b42..e8b8131f5f23 100644 --- a/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/fsl,imx6q-pcie.yaml @@ -66,16 +66,34 @@ properties: - const: dma reset-gpio: + deprecated: true description: Should specify the GPIO for controlling the PCI bus device reset signal. It's not polarity aware and defaults to active-low reset sequence (L=reset state, H=operation state) (optional required). + This property is deprecated, instead of referencing this property from the + host bridge node, use the reset-gpios property from the root port node. reset-gpio-active-high: + deprecated: true description: If present then the reset sequence using the GPIO specified in the "reset-gpio" property is reversed (H=reset state, L=operation state) (optional required). + This property is deprecated along with the reset-gpio property above, use + the reset-gpios property from the root port node. type: boolean + pcie@0: + description: + Describe the i.MX6 PCIe Root Port. + type: object + $ref: /schemas/pci/pci-pci-bridge.yaml# + + properties: + reg: + maxItems: 1 + + unevaluatedProperties: false + required: - compatible - reg @@ -236,6 +254,7 @@ unevaluatedProperties: false examples: - | #include + #include #include pcie: pcie@1ffc000 { @@ -262,5 +281,18 @@ examples: <&clks IMX6QDL_CLK_LVDS1_GATE>, <&clks IMX6QDL_CLK_PCIE_REF_125M>; clock-names = "pcie", "pcie_bus", "pcie_phy"; + + pcie_port0: pcie@0 { + compatible = "pciclass,0604"; + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + bus-range = <0x01 0xff>; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + reset-gpios = <&gpio7 12 GPIO_ACTIVE_LOW>; + }; }; ... From b269bb5e4cc3cf04a592d516a3dc260d9d893f24 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:39 +0800 Subject: [PATCH 062/168] PCI: host-generic: Add common helpers for parsing Root Port properties Introduce generic helper functions to parse Root Port device tree nodes and extract common properties like reset GPIOs. This allows multiple PCI host controller drivers to share the same parsing logic. Define struct pci_host_port to hold common Root Port properties (currently only list of PERST# GPIO descriptors) and add pci_host_common_parse_ports() to parse Root Port nodes from device tree. Also add the 'ports' list to struct pci_host_bridge to better maintain parsed Root Port information. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260422093549.407022-3-sherry.sun@nxp.com --- drivers/pci/controller/pci-host-common.c | 169 +++++++++++++++++++++++ drivers/pci/controller/pci-host-common.h | 28 ++++ drivers/pci/probe.c | 1 + include/linux/pci.h | 1 + 4 files changed, 199 insertions(+) diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index d6258c1cffe5..9bf66c256f81 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,174 @@ #include "pci-host-common.h" +/** + * pci_host_common_delete_ports - Cleanup function for port list + * @data: Pointer to the port list head + */ +void pci_host_common_delete_ports(void *data) +{ + struct list_head *ports = data; + struct pci_host_perst *perst, *tmp_perst; + struct pci_host_port *port, *tmp_port; + + list_for_each_entry_safe(port, tmp_port, ports, list) { + list_for_each_entry_safe(perst, tmp_perst, &port->perst, list) + list_del(&perst->list); + list_del(&port->list); + } +} +EXPORT_SYMBOL_GPL(pci_host_common_delete_ports); + +/** + * pci_host_common_parse_perst - Parse PERST# from all nodes, depth first + * @dev: Device pointer + * @port: PCI host port + * @np: Device tree node to start parsing from + * + * Recursively parse PERST# GPIO from all PCIe bridge nodes starting from + * @np in a depth-first manner. + * + * Return: 0 on success, negative error code on failure. + */ +static int pci_host_common_parse_perst(struct device *dev, + struct pci_host_port *port, + struct device_node *np) +{ + struct pci_host_perst *perst; + struct gpio_desc *reset; + int ret; + + if (!of_property_present(np, "reset-gpios")) + goto parse_child_node; + + reset = devm_fwnode_gpiod_get(dev, of_fwnode_handle(np), "reset", + GPIOD_ASIS, "PERST#"); + if (IS_ERR(reset)) { + /* + * FIXME: GPIOLIB currently supports exclusive GPIO access only. + * Non exclusive access is broken. But shared PERST# requires + * non-exclusive access. So once GPIOLIB properly supports it, + * implement it here. + */ + if (PTR_ERR(reset) == -EBUSY) + dev_err(dev, "Shared PERST# is not supported\n"); + + return PTR_ERR(reset); + } + + perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL); + if (!perst) + return -ENOMEM; + + INIT_LIST_HEAD(&perst->list); + perst->desc = reset; + list_add_tail(&perst->list, &port->perst); + +parse_child_node: + for_each_available_child_of_node_scoped(np, child) { + ret = pci_host_common_parse_perst(dev, port, child); + if (ret) + return ret; + } + + return 0; +} + +/** + * pci_host_common_parse_port - Parse a single Root Port node + * @dev: Device pointer + * @bridge: PCI host bridge + * @node: Device tree node of the Root Port + * + * Parse Root Port properties from the device tree. Currently it only + * handles the PERST# GPIO (including PERST# GPIOs from all PCIe bridge + * nodes under this Root Port), which is optional. + * + * NOTE: This helper fetches resources (like PERST# GPIO) optionally. If a + * controller driver has a hard dependency on certain resources (PHY, + * clocks, regulators, etc.), those resources MUST be modeled correctly in + * the DT binding and validated in DTS. This helper cannot enforce such + * dependencies and the driver may fail to operate if required resources + * are missing. + * + * Return: 0 on success, -ENODEV if PERST# found in RC node (legacy binding + * should be used), Other negative error codes on failure. + */ +static int pci_host_common_parse_port(struct device *dev, + struct pci_host_bridge *bridge, + struct device_node *node) +{ + struct pci_host_port *port; + int ret; + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + INIT_LIST_HEAD(&port->perst); + + ret = pci_host_common_parse_perst(dev, port, node); + if (ret) + return ret; + + /* + * 1. PERST# found in RP or its child nodes - list is not empty, + * continue + * + * 2. PERST# not found in RP/children, but found in RC node - + * return -ENODEV to fallback legacy binding + * + * 3. PERST# not found anywhere - list is empty, continue (optional + * PERST#) + */ + if (list_empty(&port->perst)) { + if (of_property_present(dev->of_node, "reset-gpios") || + of_property_present(dev->of_node, "reset-gpio")) + return -ENODEV; + } + + INIT_LIST_HEAD(&port->list); + list_add_tail(&port->list, &bridge->ports); + + return 0; +} + +/** + * pci_host_common_parse_ports - Parse Root Port nodes from device tree + * @dev: Device pointer + * @bridge: PCI host bridge + * + * Iterate through child nodes of the host bridge and parse Root Port + * properties (currently only reset GPIOs). + * + * Return: 0 on success, -ENODEV if no ports found or PERST# found in RC + * node (legacy binding should be used), Other negative error codes on + * failure. + */ +int pci_host_common_parse_ports(struct device *dev, struct pci_host_bridge *bridge) +{ + int ret = -ENODEV; + + for_each_available_child_of_node_scoped(dev->of_node, of_port) { + if (!of_node_is_type(of_port, "pci")) + continue; + ret = pci_host_common_parse_port(dev, bridge, of_port); + if (ret) + goto err_cleanup; + } + + if (ret) + return ret; + + return devm_add_action_or_reset(dev, pci_host_common_delete_ports, + &bridge->ports); + +err_cleanup: + pci_host_common_delete_ports(&bridge->ports); + return ret; +} +EXPORT_SYMBOL_GPL(pci_host_common_parse_ports); + static void gen_pci_unmap_cfg(void *ptr) { pci_ecam_free((struct pci_config_window *)ptr); diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h index b5075d4bd7eb..0f5fcc02e778 100644 --- a/drivers/pci/controller/pci-host-common.h +++ b/drivers/pci/controller/pci-host-common.h @@ -12,6 +12,34 @@ struct pci_ecam_ops; +/** + * struct pci_host_perst - PERST# GPIO descriptor + * @list: List node for linking multiple PERST# GPIOs + * @desc: GPIO descriptor for PERST# signal + * + * This structure holds a single PERST# GPIO descriptor. + */ +struct pci_host_perst { + struct list_head list; + struct gpio_desc *desc; +}; + +/** + * struct pci_host_port - Generic Root Port properties + * @list: List node for linking multiple ports + * @perst: List of PERST# GPIO descriptors for this port and its children + * + * This structure contains common properties that can be parsed from + * Root Port device tree nodes. + */ +struct pci_host_port { + struct list_head list; + struct list_head perst; +}; + +void pci_host_common_delete_ports(void *data); +int pci_host_common_parse_ports(struct device *dev, + struct pci_host_bridge *bridge); int pci_host_common_probe(struct platform_device *pdev); int pci_host_common_init(struct platform_device *pdev, struct pci_host_bridge *bridge, diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index b63cd0c310bc..6094b6c1fc90 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -660,6 +660,7 @@ static void pci_init_host_bridge(struct pci_host_bridge *bridge) { INIT_LIST_HEAD(&bridge->windows); INIT_LIST_HEAD(&bridge->dma_ranges); + INIT_LIST_HEAD(&bridge->ports); /* * We assume we can manage these PCIe features. Some systems may diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c11..cb5f3e7e8e48 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -636,6 +636,7 @@ struct pci_host_bridge { int domain_nr; struct list_head windows; /* resource_entry */ struct list_head dma_ranges; /* dma ranges resource list */ + struct list_head ports; /* Root Port list (pci_host_port) */ #ifdef CONFIG_PCI_IDE u16 nr_ide_streams; /* Max streams possibly active in @ide_stream_ida */ struct ida ide_stream_ida; From 610fa91d9863d9bc0ce496c5129328459c75174b Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:40 +0800 Subject: [PATCH 063/168] PCI: imx6: Assert PERST# before enabling regulators The PCIe endpoint may start responding or driving signals as soon as its supply is enabled, even before the reference clock is stable. Asserting PERST# before enabling the regulator ensures that the endpoint remains in reset throughout the entire power-up sequence, until both power and refclk are known to be stable and link initialization can safely begin. Currently, the driver enables the vpcie3v3aux regulator in imx_pcie_probe() before PERST# is asserted in imx_pcie_host_init(), which may cause PCIe endpoint undefined behavior during early power-up. However, there is no issue so far because PERST# is requested as GPIOD_OUT_HIGH in imx_pcie_probe(), which guarantees that PERST# is asserted before enabling the vpcie3v3aux regulator. This prepares for an upcoming changes that will parse the reset property using the new Root Port binding, which will use GPIOD_ASIS when requesting the reset GPIO. With GPIOD_ASIS, the GPIO state is not guaranteed, so explicit sequencing is required. Fix the power sequencing by: 1. Moving vpcie3v3aux regulator enable from probe to imx_pcie_host_init(), where it can be properly sequenced with PERST#. 2. Moving imx_pcie_assert_perst() before regulator and clock enable to ensure correct ordering. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu Link: https://patch.msgid.link/20260422093549.407022-4-sherry.sun@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 50 +++++++++++++++++++++------ 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 1034ac5c5f5c..5335c2b140fb 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -168,6 +168,8 @@ struct imx_pcie { u32 tx_swing_full; u32 tx_swing_low; struct regulator *vpcie; + struct regulator *vpcie_aux; + bool vpcie_aux_enabled; struct regulator *vph; void __iomem *phy_base; @@ -1217,6 +1219,13 @@ static void imx_pcie_disable_device(struct pci_host_bridge *bridge, imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev)); } +static void imx_pcie_vpcie_aux_disable(void *data) +{ + struct regulator *vpcie_aux = data; + + regulator_disable(vpcie_aux); +} + static void imx_pcie_assert_perst(struct imx_pcie *imx_pcie, bool assert) { if (assert) { @@ -1237,6 +1246,24 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) struct imx_pcie *imx_pcie = to_imx_pcie(pci); int ret; + imx_pcie_assert_perst(imx_pcie, true); + + /* Keep 3.3Vaux supply enabled for entire PCIe controller lifecycle */ + if (imx_pcie->vpcie_aux && !imx_pcie->vpcie_aux_enabled) { + ret = regulator_enable(imx_pcie->vpcie_aux); + if (ret) { + dev_err(dev, "failed to enable vpcie_aux regulator: %d\n", + ret); + return ret; + } + imx_pcie->vpcie_aux_enabled = true; + + ret = devm_add_action_or_reset(dev, imx_pcie_vpcie_aux_disable, + imx_pcie->vpcie_aux); + if (ret) + return ret; + } + if (imx_pcie->vpcie) { ret = regulator_enable(imx_pcie->vpcie); if (ret) { @@ -1246,25 +1273,24 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } + ret = imx_pcie_clk_enable(imx_pcie); + if (ret) { + dev_err(dev, "unable to enable pcie clocks: %d\n", ret); + goto err_reg_disable; + } + if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { pp->bridge->enable_device = imx_pcie_enable_device; pp->bridge->disable_device = imx_pcie_disable_device; } imx_pcie_assert_core_reset(imx_pcie); - imx_pcie_assert_perst(imx_pcie, true); if (imx_pcie->drvdata->init_phy) imx_pcie->drvdata->init_phy(imx_pcie); imx_pcie_configure_type(imx_pcie); - ret = imx_pcie_clk_enable(imx_pcie); - if (ret) { - dev_err(dev, "unable to enable pcie clocks: %d\n", ret); - goto err_reg_disable; - } - if (imx_pcie->phy) { ret = phy_init(imx_pcie->phy); if (ret) { @@ -1777,9 +1803,13 @@ static int imx_pcie_probe(struct platform_device *pdev) of_property_read_u32(node, "fsl,max-link-speed", &pci->max_link_speed); imx_pcie->supports_clkreq = of_property_read_bool(node, "supports-clkreq"); - ret = devm_regulator_get_enable_optional(&pdev->dev, "vpcie3v3aux"); - if (ret < 0 && ret != -ENODEV) - return dev_err_probe(dev, ret, "failed to enable Vaux supply\n"); + imx_pcie->vpcie_aux = devm_regulator_get_optional(&pdev->dev, + "vpcie3v3aux"); + if (IS_ERR(imx_pcie->vpcie_aux)) { + if (PTR_ERR(imx_pcie->vpcie_aux) != -ENODEV) + return PTR_ERR(imx_pcie->vpcie_aux); + imx_pcie->vpcie_aux = NULL; + } imx_pcie->vpcie = devm_regulator_get_optional(&pdev->dev, "vpcie"); if (IS_ERR(imx_pcie->vpcie)) { From 250eea5c06f5291603612cdf0c60326db4e19141 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 22 Apr 2026 17:35:41 +0800 Subject: [PATCH 064/168] PCI: imx6: Parse 'reset-gpios' in Root Port nodes The current DT binding for pci-imx6 specifies the 'reset-gpios' property in the host bridge node. However, the PERST# signal logically belongs to individual Root Ports rather than the host bridge itself. This becomes important when supporting PCIe Key E connector and the PCI power control framework for pci-imx6 driver, which requires properties to be specified in Root Port nodes. Parse 'reset-gpios' from Root Port nodes and the PCIe bridge nodes under the Root Port using the common helper pci_host_common_parse_ports(), and update the reset GPIO handling to use the parsed port list from bridge->ports. To maintain DT backwards compatibility, fall back to the legacy method of parsing the host bridge node if the reset property is not present in the Root Port nodes. Since now the reset GPIO is obtained with GPIOD_ASIS flag, it may be in input mode, so use gpiod_direction_output() instead of gpiod_set_value_cansleep() to ensure the reset GPIO is properly configured as output before setting its value. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Acked-by: Richard Zhu Link: https://patch.msgid.link/20260422093549.407022-5-sherry.sun@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 88 ++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 5335c2b140fb..773ab65b2afa 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -34,6 +34,7 @@ #include #include "../../pci.h" +#include "../pci-host-common.h" #include "pcie-designware.h" #define IMX8MQ_GPR_PCIE_REF_USE_PAD BIT(9) @@ -152,7 +153,6 @@ struct imx_lut_data { struct imx_pcie { struct dw_pcie *pci; - struct gpio_desc *reset_gpiod; struct clk_bulk_data *clks; int num_clks; bool supports_clkreq; @@ -1219,6 +1219,41 @@ static void imx_pcie_disable_device(struct pci_host_bridge *bridge, imx_pcie_remove_lut(imx_pcie, pci_dev_id(pdev)); } +static int imx_pcie_parse_legacy_binding(struct imx_pcie *pcie) +{ + struct device *dev = pcie->pci->dev; + struct pci_host_bridge *bridge = pcie->pci->pp.bridge; + struct pci_host_port *port; + struct pci_host_perst *perst; + struct gpio_desc *reset; + + reset = devm_gpiod_get_optional(dev, "reset", GPIOD_ASIS); + if (IS_ERR(reset)) + return PTR_ERR(reset); + + if (!reset) + return 0; + + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL); + if (!perst) + return -ENOMEM; + + INIT_LIST_HEAD(&port->perst); + perst->desc = reset; + INIT_LIST_HEAD(&perst->list); + list_add_tail(&perst->list, &port->perst); + + INIT_LIST_HEAD(&port->list); + list_add_tail(&port->list, &bridge->ports); + + return devm_add_action_or_reset(dev, pci_host_common_delete_ports, + &bridge->ports); +} + static void imx_pcie_vpcie_aux_disable(void *data) { struct regulator *vpcie_aux = data; @@ -1228,14 +1263,26 @@ static void imx_pcie_vpcie_aux_disable(void *data) static void imx_pcie_assert_perst(struct imx_pcie *imx_pcie, bool assert) { + struct dw_pcie *pci = imx_pcie->pci; + struct pci_host_bridge *bridge = pci->pp.bridge; + struct pci_host_perst *perst; + struct pci_host_port *port; + + if (!bridge || list_empty(&bridge->ports)) + return; + if (assert) { - gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 1); - } else { - if (imx_pcie->reset_gpiod) { - msleep(PCIE_T_PVPERL_MS); - gpiod_set_value_cansleep(imx_pcie->reset_gpiod, 0); - msleep(PCIE_RESET_CONFIG_WAIT_MS); + list_for_each_entry(port, &bridge->ports, list) { + list_for_each_entry(perst, &port->perst, list) + gpiod_direction_output(perst->desc, 1); } + } else { + mdelay(PCIE_T_PVPERL_MS); + list_for_each_entry(port, &bridge->ports, list) { + list_for_each_entry(perst, &port->perst, list) + gpiod_direction_output(perst->desc, 0); + } + mdelay(PCIE_RESET_CONFIG_WAIT_MS); } } @@ -1244,8 +1291,28 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct device *dev = pci->dev; struct imx_pcie *imx_pcie = to_imx_pcie(pci); + struct pci_host_bridge *bridge = pp->bridge; int ret; + if (bridge && list_empty(&bridge->ports)) { + /* Parse Root Port nodes if present */ + ret = pci_host_common_parse_ports(dev, bridge); + if (ret) { + if (ret != -ENODEV) { + dev_err(dev, "Failed to parse Root Port nodes: %d\n", ret); + return ret; + } + + /* + * Fall back to legacy binding for DT backwards + * compatibility + */ + ret = imx_pcie_parse_legacy_binding(imx_pcie); + if (ret) + return ret; + } + } + imx_pcie_assert_perst(imx_pcie, true); /* Keep 3.3Vaux supply enabled for entire PCIe controller lifecycle */ @@ -1699,13 +1766,6 @@ static int imx_pcie_probe(struct platform_device *pdev) return PTR_ERR(imx_pcie->phy_base); } - /* Fetch GPIOs */ - imx_pcie->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(imx_pcie->reset_gpiod)) - return dev_err_probe(dev, PTR_ERR(imx_pcie->reset_gpiod), - "unable to get reset gpio\n"); - gpiod_set_consumer_name(imx_pcie->reset_gpiod, "PCIe reset"); - /* Fetch clocks */ imx_pcie->num_clks = devm_clk_bulk_get_all(dev, &imx_pcie->clks); if (imx_pcie->num_clks < 0) From d25f1dd6b986070e2324bf373097f0fced1e57e1 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 8 May 2026 21:34:32 +0800 Subject: [PATCH 065/168] PCI: dwc: Use DEFINE_SHOW_ATTRIBUTE for ltssm_status debugfs Replace the custom open function and file_operations with the standard DEFINE_SHOW_ATTRIBUTE macro to reduce boilerplate code. This also adds the previously missing .release() callback and fixes the seq_file leak during close. Signed-off-by: Hans Zhang <18255117159@163.com> [mani: added a note about implicit release callback change spotted by Sashiko] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260508133432.1964491-1-18255117159@163.com --- drivers/pci/controller/dwc/pcie-designware-debugfs.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c index d0884253be97..87f5ec9c48eb 100644 --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -507,11 +507,6 @@ static int ltssm_status_show(struct seq_file *s, void *v) return 0; } -static int ltssm_status_open(struct inode *inode, struct file *file) -{ - return single_open(file, ltssm_status_show, inode->i_private); -} - #define dwc_debugfs_create(name) \ debugfs_create_file(#name, 0644, rasdes_debug, pci, \ &dbg_ ## name ## _fops) @@ -548,10 +543,7 @@ static const struct file_operations dwc_pcie_counter_value_ops = { .read = counter_value_read, }; -static const struct file_operations dwc_pcie_ltssm_status_ops = { - .open = ltssm_status_open, - .read = seq_read, -}; +DEFINE_SHOW_ATTRIBUTE(ltssm_status); static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci) { @@ -642,7 +634,7 @@ static int dwc_pcie_rasdes_debugfs_init(struct dw_pcie *pci, struct dentry *dir) static void dwc_pcie_ltssm_debugfs_init(struct dw_pcie *pci, struct dentry *dir) { debugfs_create_file("ltssm_status", 0444, dir, pci, - &dwc_pcie_ltssm_status_ops); + <ssm_status_fops); } static int dw_pcie_ptm_check_capability(void *drvdata) From 87f493041e20759ffc27262cc0490c41628a5ee2 Mon Sep 17 00:00:00 2001 From: Manikanta Maddireddy Date: Fri, 15 May 2026 12:37:52 +0530 Subject: [PATCH 066/168] PCI: tegra194: Use aspm-l1-entry-delay-ns DT property for L1 entrance latency Program the Synopsys DesignWare PORT_AFR L1 entrance latency field from the optional aspm-l1-entry-delay-ns device tree property (nanoseconds). Convert delay to whole microseconds with ceiling division (DIV_ROUND_UP), then derive the 3-bit hw encoding as the minimum of order_base_2(us) and 7. If the property is not present or cannot be read, default to 7. Hardware encoding (PORT_AFR L1 entrance latency, bits 27:29): +--------------------------+----------+ | Advertised maximum | Code | +--------------------------+----------+ | Maximum of 1 us | 000b | +--------------------------+----------+ | Maximum of 2 us | 001b | +--------------------------+----------+ | Maximum of 4 us | 010b | +--------------------------+----------+ | Maximum of 8 us | 011b | +--------------------------+----------+ | Maximum of 16 us | 100b | +--------------------------+----------+ | Maximum of 32 us | 101b | +--------------------------+----------+ | Maximum of 64 us | 110b | +--------------------------+----------+ | Rest | 111b | +--------------------------+----------+ Signed-off-by: Manikanta Maddireddy Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260515070753.3852840-2-mmaddireddy@nvidia.com --- drivers/pci/controller/dwc/pcie-tegra194.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-tegra194.c b/drivers/pci/controller/dwc/pcie-tegra194.c index 9dcfa194050e..5309a2f1356d 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194.c +++ b/drivers/pci/controller/dwc/pcie-tegra194.c @@ -272,6 +272,7 @@ struct tegra_pcie_dw { u32 aspm_cmrt; u32 aspm_pwr_on_t; u32 aspm_l0s_enter_lat; + u32 aspm_l1_enter_lat; struct regulator *pex_ctl_supply; struct regulator *slot_ctl_3v3; @@ -715,6 +716,8 @@ static void init_host_aspm(struct tegra_pcie_dw *pcie) val = dw_pcie_readl_dbi(pci, PCIE_PORT_AFR); val &= ~PORT_AFR_L0S_ENTRANCE_LAT_MASK; val |= (pcie->aspm_l0s_enter_lat << PORT_AFR_L0S_ENTRANCE_LAT_SHIFT); + val &= ~PORT_AFR_L1_ENTRANCE_LAT_MASK; + val |= (pcie->aspm_l1_enter_lat << PORT_AFR_L1_ENTRANCE_LAT_SHIFT); val |= PORT_AFR_ENTER_ASPM; dw_pcie_writel_dbi(pci, PCIE_PORT_AFR, val); } @@ -1115,6 +1118,7 @@ static int tegra_pcie_dw_parse_dt(struct tegra_pcie_dw *pcie) { struct platform_device *pdev = to_platform_device(pcie->dev); struct device_node *np = pcie->dev->of_node; + u32 val; int ret; pcie->dbi_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); @@ -1141,6 +1145,15 @@ static int tegra_pcie_dw_parse_dt(struct tegra_pcie_dw *pcie) dev_info(pcie->dev, "Failed to read ASPM L0s Entrance latency: %d\n", ret); + /* Default to max latency of 7. */ + pcie->aspm_l1_enter_lat = 7; + ret = of_property_read_u32(np, "aspm-l1-entry-delay-ns", &val); + if (!ret) { + u32 us = DIV_ROUND_UP(val, 1000); + + pcie->aspm_l1_enter_lat = min_t(u32, order_base_2(us), 7); + } + ret = of_property_read_u32(np, "num-lanes", &pcie->num_lanes); if (ret < 0) { dev_err(pcie->dev, "Failed to read num-lanes: %d\n", ret); From 94ac934d2c054fba4a22d8dc84749094c5fa0ec0 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Tue, 12 May 2026 13:17:55 +0300 Subject: [PATCH 067/168] PCI: dwc: Fix signedness bug in fault injection test code The kstrtou32() function returns negative error code or zero on success. However, in this case "val" is a u32 and the function returns signed long, so negative error codes from kstrtou32() are returned as high positive values. Store the error code in an int instead. Fixes: d20ee8e2dbd6 ("PCI: dwc: Add debugfs based Error Injection support for DWC") Signed-off-by: Dan Carpenter Signed-off-by: Manivannan Sadhasivam Reviewed-by: Hans Zhang <18255117159@163.com> Link: https://patch.msgid.link/agL-Uwfn26SI4Gb0@stanley.mountain --- drivers/pci/controller/dwc/pcie-designware-debugfs.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c index 87f5ec9c48eb..ddbce69b2e9f 100644 --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -306,6 +306,7 @@ static ssize_t err_inj_write(struct file *file, const char __user *buf, u32 val, counter, vc_num, err_group, type_mask; int val_diff = 0; char *kern_buf; + int ret; err_group = err_inj_list[pdata->idx].err_inj_group; type_mask = err_inj_type_mask[err_group]; @@ -327,10 +328,10 @@ static ssize_t err_inj_write(struct file *file, const char __user *buf, return -EINVAL; } } else { - val = kstrtou32(kern_buf, 0, &counter); - if (val) { + ret = kstrtou32(kern_buf, 0, &counter); + if (ret) { kfree(kern_buf); - return val; + return ret; } } From 8ba433753d9b131c2e43b1ff7ba8c5730cef8231 Mon Sep 17 00:00:00 2001 From: Chen-Yu Tsai Date: Tue, 12 May 2026 18:33:45 +0800 Subject: [PATCH 068/168] PCI: mediatek-gen3: Fix incorrectly skipped pwrctrl error message When pwrctrl integration was added, the error message for pci_pwrctrl_create_devices() failure was incorrectly added after the goto statement, causing it to be skipped. Move the goto statement after the dev_err_probe() call so that the error message actually gets printed (or saved if probe is deferred). Fixes: 1a152e21940a ("PCI: mediatek-gen3: Integrate new pwrctrl API") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/adjNaKB5KGpl6qIp@stanley.mountain/ Signed-off-by: Chen-Yu Tsai Signed-off-by: Manivannan Sadhasivam Reviewed-by: Hans Zhang <18255117159@163.com> Link: https://patch.msgid.link/20260512103347.1751080-1-wenst@chromium.org --- drivers/pci/controller/pcie-mediatek-gen3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c index 1da2166d1017..654e63f8fb57 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -1242,8 +1242,8 @@ static int mtk_pcie_probe(struct platform_device *pdev) err = pci_pwrctrl_create_devices(pcie->dev); if (err) { - goto err_tear_down_irq; dev_err_probe(dev, err, "failed to create pwrctrl devices\n"); + goto err_tear_down_irq; } err = mtk_pcie_setup(pcie); From e373c789bac0ad73b472d8b44714df3bd18a4edf Mon Sep 17 00:00:00 2001 From: Ziyao Li Date: Sun, 12 Apr 2026 18:17:31 +0800 Subject: [PATCH 069/168] PCI: loongson: Override PCIe bridge supported speeds for Loongson-3C6000 series Older steppings of the Loongson-3C6000 series incorrectly report the supported link speeds on their PCIe bridges (device IDs 0x3c19, 0x3c29) as only 2.5 GT/s, despite the upstream bus supporting speeds from 2.5 GT/s up to 16 GT/s. As a result, since commit 774c71c52aa4 ("PCI/bwctrl: Enable only if more than one speed is supported"), bwctrl will be disabled if there's only one 2.5 GT/s value in vector 'supported_speeds'. Manually override the 'supported_speeds' field for affected PCIe bridges with those found on the upstream bus to correctly reflect the supported link speeds. Updating the speeds to reflect what the hardware actually supports avoids quirks in drivers consuming the speed information. This commit was originally found from AOSC OS[1]. Fixes: cd89edda4002 ("PCI: loongson: Add ACPI init support") Signed-off-by: Ayden Meng Signed-off-by: Mingcong Bai [Ziyao Li: move from drivers/pci/quirks.c to drivers/pci/controller/pci-loongson.c] Signed-off-by: Ziyao Li [Xi Ruoyao: Fixed falling through logic, added debug log, Fixes tag and rebased to 7.0-rc7] Signed-off-by: Xi Ruoyao Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log, https://lore.kernel.org/all/9d815df3b33a63223112b97440c01247935363c1.camel@xry111.site] Signed-off-by: Bjorn Helgaas Tested-by: Lain Fearyncess Yang Tested-by: Ayden Meng Tested-by: Mingcong Bai Reviewed-by: Huacai Chen Cc: stable@vger.kernel.org Link: https://github.com/AOSC-Tracking/linux/commit/4392f441363abdf6fa0a0433d73175a17f493454 Link: https://github.com/AOSC-Tracking/linux/pull/2 #1 Link: https://patch.msgid.link/20260412101731.107059-1-xry111@xry111.site --- drivers/pci/controller/pci-loongson.c | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index de5e809a537d..d0c643996476 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -177,6 +177,42 @@ static void loongson_pci_msi_quirk(struct pci_dev *dev) } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_LOONGSON, DEV_LS7A_PCIE_PORT5, loongson_pci_msi_quirk); +/* + * Older steppings of the Loongson-3C6000 series incorrectly report the + * supported link speeds on their PCIe bridges (device IDs 0x3c19, + * 0x3c29) as only 2.5 GT/s, despite the upstream bus supporting speeds + * from 2.5 GT/s up to 16 GT/s. + */ +static void loongson_pci_bridge_speed_quirk(struct pci_dev *pdev) +{ + u8 old_supported_speeds = pdev->supported_speeds; + + switch (pdev->bus->max_bus_speed) { + case PCIE_SPEED_16_0GT: + pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_16_0GB; + fallthrough; + case PCIE_SPEED_8_0GT: + pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_8_0GB; + fallthrough; + case PCIE_SPEED_5_0GT: + pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_5_0GB; + fallthrough; + case PCIE_SPEED_2_5GT: + pdev->supported_speeds |= PCI_EXP_LNKCAP2_SLS_2_5GB; + break; + default: + pci_warn(pdev, "unexpected max bus speed"); + + return; + } + + if (pdev->supported_speeds != old_supported_speeds) + pci_info(pdev, "fixed up supported link speeds: 0x%x => 0x%x", + old_supported_speeds, pdev->supported_speeds); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_LOONGSON, 0x3c19, loongson_pci_bridge_speed_quirk); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_LOONGSON, 0x3c29, loongson_pci_bridge_speed_quirk); + static struct loongson_pci *pci_bus_to_loongson_pci(struct pci_bus *bus) { struct pci_config_window *cfg; From 1d2a29ccd3a17d8e3ab8c3a4821b96d19c799b12 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 29 Apr 2026 12:12:23 +0530 Subject: [PATCH 070/168] PCI: host-common: Add pci_host_common_d3cold_possible() helper Add a common helper, pci_host_common_d3cold_possible(), to determine whether PCIe devices under host bridge can safely transition to D3cold. This helper is intended to be used by PCI host controller drivers to decide whether they can safely put the endpoint devices into D3cold based on their power state and wakeup capabilities. Once the devices are transitioned into D3cold, the host controller can be safely powered off. The helper walks all devices on the all downstream buses and only allows the devices to enter D3cold if all PCIe endpoints are already in PCI_D3hot. This ensures that the host controller driver does not broadcast PME_Turn_Off or power down the controller while any active endpoint still requires the link to remain powered. For devices that may wake the system, the helper additionally requires that the device supports PME wake from D3cold (via WAKE#). Devices that do not have wakeup enabled are not restricted by this check and do not block the devices under host bridge from entering D3cold. Devices without a bound driver and with PCI not enabled via sysfs are treated as inactive and therefore do not prevent the devices under host bridge from entering D3cold. This allows controllers to power down more aggressively when there are no actively managed endpoints. Some devices (e.g. M.2 without auxiliary power) lose PME detection when main power is removed. Even if such devices advertise PME-from-D3cold capability, entering D3cold may break wakeup. Return PME-from-D3cold capability via 'pme_capable' parameter so PCIe controller drivers can apply platform-specific handling to preserve wakeup functionality. Signed-off-by: Krishna Chaitanya Chundru [mani: commit log and removed the device checks for d3cold] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429-d3cold-v5-1-89e9735b9df6@oss.qualcomm.com --- drivers/pci/controller/pci-host-common.c | 62 ++++++++++++++++++++++++ drivers/pci/controller/pci-host-common.h | 3 ++ 2 files changed, 65 insertions(+) diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index d6258c1cffe5..2e785449d02c 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -17,6 +17,9 @@ #include "pci-host-common.h" +#define PCI_HOST_D3COLD_ALLOWED BIT(0) +#define PCI_HOST_PME_D3COLD_CAPABLE BIT(1) + static void gen_pci_unmap_cfg(void *ptr) { pci_ecam_free((struct pci_config_window *)ptr); @@ -106,5 +109,64 @@ void pci_host_common_remove(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(pci_host_common_remove); +static int __pci_host_common_d3cold_possible(struct pci_dev *pdev, + void *userdata) +{ + u32 *flags = userdata; + + if (!pdev->dev.driver && !pci_is_enabled(pdev)) + return 0; + + if (pdev->current_state != PCI_D3hot) + goto exit; + + if (device_may_wakeup(&pdev->dev)) { + if (!pci_pme_capable(pdev, PCI_D3cold)) + goto exit; + else + *flags |= PCI_HOST_PME_D3COLD_CAPABLE; + } + + return 0; + +exit: + *flags &= ~PCI_HOST_D3COLD_ALLOWED; + + return -EOPNOTSUPP; +} + +/** + * pci_host_common_d3cold_possible - Determine whether the host bridge can + * transition the devices into D3cold. + * + * @bridge: PCI host bridge to check + * @pme_capable: Pointer to update if there is any device capable of generating + * PME from D3cold. + * + * Walk downstream PCIe endpoint devices and determine whether the host bridge + * is permitted to transition the devices into D3cold. + * + * Devices under host bridge can enter D3cold only if all active PCIe + * endpoints are in PCI_D3hot and any wakeup-enabled endpoint is capable of + * generating PME from D3cold. Inactive endpoints are ignored. + * + * The @pme_capable output allows PCIe controller drivers to apply + * platform-specific handling to preserve wakeup functionality. + * + * Return: %true if the host bridge may enter D3cold, otherwise %false. + */ +bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge, + bool *pme_capable) +{ + u32 flags = PCI_HOST_D3COLD_ALLOWED; + + pci_walk_bus(bridge->bus, __pci_host_common_d3cold_possible, &flags); + + *pme_capable = !!(flags & PCI_HOST_PME_D3COLD_CAPABLE); + + return !!(flags & PCI_HOST_D3COLD_ALLOWED); +} +EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible); + MODULE_DESCRIPTION("Common library for PCI host controller drivers"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h index b5075d4bd7eb..164985878265 100644 --- a/drivers/pci/controller/pci-host-common.h +++ b/drivers/pci/controller/pci-host-common.h @@ -20,4 +20,7 @@ void pci_host_common_remove(struct platform_device *pdev); struct pci_config_window *pci_host_common_ecam_create(struct device *dev, struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops); + +bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge, + bool *pme_capable); #endif From 131a93dbcb9546683384e31dc4057d4aaf38fa21 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 29 Apr 2026 12:12:24 +0530 Subject: [PATCH 071/168] PCI: qcom: Add .get_ltssm() callback to query LTSSM status For older SoCs like SC7280, reading DBI LTSSM register after sending PME_Turn_Off message causes NOC error. To avoid unsafe DBI accesses, introduce qcom_pcie_get_ltssm() to retrieve the LTSSM state without DBI. For newer platforms, read the LTSSM state from the PARF_LTSSM register; for older platforms continue to retrieve it from ELBI_SYS_STTS. This helper is used in place of direct DBI-based link state checks in the D3cold path after sending PME_Turn_Off message, ensuring the LTSSM state can be queried safely even after DBI access is no longer valid. Signed-off-by: Krishna Chaitanya Chundru [mani: commit log and fixed get_ltssm() check] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429-d3cold-v5-2-89e9735b9df6@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index f21f3806fc1f..881746efe3b6 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -71,6 +71,7 @@ /* ELBI registers */ #define ELBI_SYS_CTRL 0x04 +#define ELBI_SYS_STTS 0x08 /* DBI registers */ #define AXI_MSTR_RESP_COMP_CTRL0 0x818 @@ -131,6 +132,7 @@ /* PARF_LTSSM register fields */ #define LTSSM_EN BIT(8) +#define PARF_LTSSM_STATE_MASK GENMASK(5, 0) /* PARF_NO_SNOOP_OVERRIDE register fields */ #define WR_NO_SNOOP_OVERRIDE_EN BIT(1) @@ -145,6 +147,9 @@ /* ELBI_SYS_CTRL register fields */ #define ELBI_SYS_CTRL_LT_ENABLE BIT(0) +/* ELBI_SYS_STTS register fields */ +#define ELBI_SYS_STTS_LTSSM_STATE_MASK GENMASK(17, 12) + /* AXI_MSTR_RESP_COMP_CTRL0 register fields */ #define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_2K 0x4 #define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_4K 0x5 @@ -245,6 +250,7 @@ struct qcom_pcie_ops { void (*deinit)(struct qcom_pcie *pcie); void (*ltssm_enable)(struct qcom_pcie *pcie); int (*config_sid)(struct qcom_pcie *pcie); + enum dw_pcie_ltssm (*get_ltssm)(struct qcom_pcie *pcie); }; /** @@ -428,6 +434,15 @@ static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie) writel(val, pci->elbi_base + ELBI_SYS_CTRL); } +static enum dw_pcie_ltssm qcom_pcie_2_1_0_get_ltssm(struct qcom_pcie *pcie) +{ + struct dw_pcie *pci = pcie->pci; + u32 val; + + val = readl(pci->elbi_base + ELBI_SYS_STTS); + return (enum dw_pcie_ltssm)FIELD_GET(ELBI_SYS_STTS_LTSSM_STATE_MASK, val); +} + static int qcom_pcie_get_resources_2_1_0(struct qcom_pcie *pcie) { struct qcom_pcie_resources_2_1_0 *res = &pcie->res.v2_1_0; @@ -1260,6 +1275,19 @@ static bool qcom_pcie_link_up(struct dw_pcie *pci) return val & PCI_EXP_LNKSTA_DLLLA; } +static enum dw_pcie_ltssm qcom_pcie_get_ltssm(struct dw_pcie *pci) +{ + struct qcom_pcie *pcie = to_qcom_pcie(pci); + u32 val; + + if (pcie->cfg->ops->get_ltssm) + return pcie->cfg->ops->get_ltssm(pcie); + + val = readl(pcie->parf + PARF_LTSSM); + + return (enum dw_pcie_ltssm)FIELD_GET(PARF_LTSSM_STATE_MASK, val); +} + static void qcom_pcie_phy_power_off(struct qcom_pcie *pcie) { struct qcom_pcie_port *port; @@ -1385,6 +1413,7 @@ static const struct qcom_pcie_ops ops_2_1_0 = { .post_init = qcom_pcie_post_init_2_1_0, .deinit = qcom_pcie_deinit_2_1_0, .ltssm_enable = qcom_pcie_2_1_0_ltssm_enable, + .get_ltssm = qcom_pcie_2_1_0_get_ltssm, }; /* Qcom IP rev.: 1.0.0 Synopsys IP rev.: 4.11a */ @@ -1394,6 +1423,7 @@ static const struct qcom_pcie_ops ops_1_0_0 = { .post_init = qcom_pcie_post_init_1_0_0, .deinit = qcom_pcie_deinit_1_0_0, .ltssm_enable = qcom_pcie_2_1_0_ltssm_enable, + .get_ltssm = qcom_pcie_2_1_0_get_ltssm, }; /* Qcom IP rev.: 2.3.2 Synopsys IP rev.: 4.21a */ @@ -1512,6 +1542,7 @@ static const struct qcom_pcie_cfg cfg_fw_managed = { static const struct dw_pcie_ops dw_pcie_ops = { .link_up = qcom_pcie_link_up, .start_link = qcom_pcie_start_link, + .get_ltssm = qcom_pcie_get_ltssm, }; static int qcom_pcie_icc_init(struct qcom_pcie *pcie) From 8a847d3e9e5f1700beb5a0196e682f71837dfe5c Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 29 Apr 2026 12:12:25 +0530 Subject: [PATCH 072/168] PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks Some Qcom PCIe controller variants bring the PHY out of test power-down (PHY_TEST_PWR_DOWN) during init. When the link is later transitioned to D3cold and the driver disables PCIe clocks and/or regulators without explicitly re-asserting PHY_TEST_PWR_DOWN, the PHY can remain partially powered, leading to avoidable power leakage. Update the init-path comments to reflect that PARF_PHY_CTRL is used to power the PHY on. Also, for controller revisions that enable PHY power in init (2.3.2, 2.3.3, 2.4.0, 2.7.0 and 2.9.0), explicitly power the PHY down via PARF_PHY_CTRL in the deinit path before disabling clocks or regulators. This ensures the PHY is put into a defined low-power state prior to removing its supplies, preventing leakage when entering D3cold. Signed-off-by: Krishna Chaitanya Chundru Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429-d3cold-v5-3-89e9735b9df6@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 38 ++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 881746efe3b6..179d26038da8 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -532,7 +532,7 @@ static int qcom_pcie_post_init_2_1_0(struct qcom_pcie *pcie) u32 val; int ret; - /* enable PCIe clocks and resets */ + /* Force PHY out of lowest power state */ val = readl(pcie->parf + PARF_PHY_CTRL); val &= ~PHY_TEST_PWR_DOWN; writel(val, pcie->parf + PARF_PHY_CTRL); @@ -699,6 +699,12 @@ static int qcom_pcie_get_resources_2_3_2(struct qcom_pcie *pcie) static void qcom_pcie_deinit_2_3_2(struct qcom_pcie *pcie) { struct qcom_pcie_resources_2_3_2 *res = &pcie->res.v2_3_2; + u32 val; + + /* Force PHY to lowest power state*/ + val = readl(pcie->parf + PARF_PHY_CTRL); + val |= PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); clk_bulk_disable_unprepare(res->num_clks, res->clks); regulator_bulk_disable(ARRAY_SIZE(res->supplies), res->supplies); @@ -731,7 +737,7 @@ static int qcom_pcie_post_init_2_3_2(struct qcom_pcie *pcie) { u32 val; - /* enable PCIe clocks and resets */ + /* Force PHY out of lowest power state */ val = readl(pcie->parf + PARF_PHY_CTRL); val &= ~PHY_TEST_PWR_DOWN; writel(val, pcie->parf + PARF_PHY_CTRL); @@ -795,6 +801,12 @@ static int qcom_pcie_get_resources_2_4_0(struct qcom_pcie *pcie) static void qcom_pcie_deinit_2_4_0(struct qcom_pcie *pcie) { struct qcom_pcie_resources_2_4_0 *res = &pcie->res.v2_4_0; + u32 val; + + /* Force PHY to lowest power state*/ + val = readl(pcie->parf + PARF_PHY_CTRL); + val |= PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); reset_control_bulk_assert(res->num_resets, res->resets); clk_bulk_disable_unprepare(res->num_clks, res->clks); @@ -863,6 +875,12 @@ static int qcom_pcie_get_resources_2_3_3(struct qcom_pcie *pcie) static void qcom_pcie_deinit_2_3_3(struct qcom_pcie *pcie) { struct qcom_pcie_resources_2_3_3 *res = &pcie->res.v2_3_3; + u32 val; + + /* Force PHY to lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val |= PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); clk_bulk_disable_unprepare(res->num_clks, res->clks); } @@ -918,6 +936,7 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie) u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); u32 val; + /* Force PHY out of lowest power state */ val = readl(pcie->parf + PARF_PHY_CTRL); val &= ~PHY_TEST_PWR_DOWN; writel(val, pcie->parf + PARF_PHY_CTRL); @@ -1013,7 +1032,7 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie) /* configure PCIe to RC mode */ writel(DEVICE_TYPE_RC, pcie->parf + PARF_DEVICE_TYPE); - /* enable PCIe clocks and resets */ + /* Force PHY out of lowest power state */ val = readl(pcie->parf + PARF_PHY_CTRL); val &= ~PHY_TEST_PWR_DOWN; writel(val, pcie->parf + PARF_PHY_CTRL); @@ -1084,6 +1103,12 @@ static void qcom_pcie_host_post_init_2_7_0(struct qcom_pcie *pcie) static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie) { struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0; + u32 val; + + /* Force PHY to lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val |= PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); clk_bulk_disable_unprepare(res->num_clks, res->clks); @@ -1188,6 +1213,12 @@ static int qcom_pcie_get_resources_2_9_0(struct qcom_pcie *pcie) static void qcom_pcie_deinit_2_9_0(struct qcom_pcie *pcie) { struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0; + u32 val; + + /* Force PHY to lowest power state */ + val = readl(pcie->parf + PARF_PHY_CTRL); + val |= PHY_TEST_PWR_DOWN; + writel(val, pcie->parf + PARF_PHY_CTRL); clk_bulk_disable_unprepare(res->num_clks, res->clks); } @@ -1228,6 +1259,7 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie) u32 val; int i; + /* Force PHY out of lowest power state */ val = readl(pcie->parf + PARF_PHY_CTRL); val &= ~PHY_TEST_PWR_DOWN; writel(val, pcie->parf + PARF_PHY_CTRL); From 56378c03c1a80aeeab45f39b303cc92a3bb7716e Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 29 Apr 2026 12:12:26 +0530 Subject: [PATCH 073/168] PCI: dwc: Use common D3cold eligibility helper in suspend path Previously, the driver skipped putting the link into L2 and device state in D3cold whenever L1 ASPM was enabled, since some devices (e.g. NVMe) expect low resume latency and may not tolerate deeper power states. However, such devices typically remain in D0 and are already covered by the new helper's requirement that all endpoints be in D3hot before the devices under host bridge may enter D3cold. Replace the local L1/L1SS-based check in dw_pcie_suspend_noirq() with the shared pci_host_common_d3cold_possible() helper to decide whether the devices under host bridge can safely transition to D3cold. In addition, propagate PME-from-D3cold capability information from the helper and record it in skip_pwrctrl_off. Some devices (e.g. M.2 cards without auxiliary power) cannot send PME when the main power is removed, even if they advertise PME-from-D3cold support. This allows controller power-off to be skipped when required to preserve wakeup functionality. While at it, update the 'dw_pcie::suspended' flag in dw_pcie_resume_noirq() only after the PCIe link resumes successfully, to avoid marking the controller as active when link resume fails. Signed-off-by: Krishna Chaitanya Chundru [mani: commit log and added TODO to query Vaux] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429-d3cold-v5-4-89e9735b9df6@oss.qualcomm.com --- .../pci/controller/dwc/pcie-designware-host.c | 23 ++++++++++++------- drivers/pci/controller/dwc/pcie-designware.h | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index c9517a348836..cffb34f6f3a9 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -16,9 +16,11 @@ #include #include #include +#include #include #include +#include "../pci-host-common.h" #include "../../pci.h" #include "pcie-designware.h" @@ -1218,18 +1220,14 @@ 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); + bool pme_capable = false; int ret = 0; u32 val; if (!dw_pcie_link_up(pci)) goto stop_link; - /* - * If L1SS is supported, then do not put the link into L2 as some - * devices such as NVMe expect low resume latency. - */ - if (dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM_L1) + if (!pci_host_common_d3cold_possible(pci->pp.bridge, &pme_capable)) return 0; if (pci->pp.ops->pme_turn_off) { @@ -1273,6 +1271,15 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci) udelay(1); stop_link: + /* + * TODO: "pme_capable" means some downstream device is wakeup- + * enabled and is capable of generating PME from D3cold, which + * requires auxiliary power. Instead of always skipping power off + * if PME is supported from D3cold, query the pwrctrl core and skip + * power off only if device supports PME from D3cold and Vaux is + * not supported. + */ + pci->pp.skip_pwrctrl_off = pme_capable; dw_pcie_stop_link(pci); if (pci->pp.ops->deinit) pci->pp.ops->deinit(&pci->pp); @@ -1290,8 +1297,6 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci) if (!pci->suspended) return 0; - pci->suspended = false; - if (pci->pp.ops->init) { ret = pci->pp.ops->init(&pci->pp); if (ret) { @@ -1313,6 +1318,8 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci) if (pci->pp.ops->post_init) pci->pp.ops->post_init(&pci->pp); + pci->suspended = false; + return 0; err_stop_link: diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 3e69ef60165b..e759c5c7257e 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -450,6 +450,7 @@ struct dw_pcie_rp { bool ecam_enabled; bool native_ecam; bool skip_l23_ready; + bool skip_pwrctrl_off; }; struct dw_pcie_ep_ops { From 2cc0e7454c7891345f92e96b2f812b808be7fbdb Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Wed, 29 Apr 2026 12:12:27 +0530 Subject: [PATCH 074/168] PCI: qcom: Add D3cold support Add support for transitioning PCIe endpoints under host bridge into D3cold by integrating with the DWC core suspend/resume helpers. Implement PME_Turn_Off message generation via ELBI_SYS_CTRL and hook it into the DWC host operations so the controller follows the standard PME_Turn_Off based power-down sequence before entering D3cold. When the device is suspended into D3cold, fully tear down interconnect bandwidth and OPP votes. If D3cold is not entered, retain existing behavior by keeping the required interconnect and OPP votes. Use dw_pcie::skip_pwrctrl_off to avoid powering off devices during suspend to preserve wakeup capability of the devices and also not to power on the devices in the init path. Finally, drop the qcom_pcie::suspended flag and rely on the existing dw_pcie::suspended state, which now drives both the power-management flow and the interconnect/OPP handling. Signed-off-by: Krishna Chaitanya Chundru [mani: commit log] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429-d3cold-v5-5-89e9735b9df6@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 163 ++++++++++++++++--------- 1 file changed, 103 insertions(+), 60 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 179d26038da8..bc5a31efc0d0 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -146,6 +146,7 @@ /* ELBI_SYS_CTRL register fields */ #define ELBI_SYS_CTRL_LT_ENABLE BIT(0) +#define ELBI_SYS_CTRL_PME_TURNOFF_MSG BIT(4) /* ELBI_SYS_STTS register fields */ #define ELBI_SYS_STTS_LTSSM_STATE_MASK GENMASK(17, 12) @@ -288,7 +289,6 @@ struct qcom_pcie { const struct qcom_pcie_cfg *cfg; struct dentry *debugfs; struct list_head ports; - bool suspended; bool use_pm_opp; }; @@ -1364,13 +1364,17 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) if (ret) goto err_deinit; - ret = pci_pwrctrl_create_devices(pci->dev); - if (ret) - goto err_disable_phy; + if (!pci->suspended) { + ret = pci_pwrctrl_create_devices(pci->dev); + if (ret) + goto err_disable_phy; + } - ret = pci_pwrctrl_power_on_devices(pci->dev); - if (ret) - goto err_pwrctrl_destroy; + if (!pp->skip_pwrctrl_off) { + ret = pci_pwrctrl_power_on_devices(pci->dev); + if (ret) + goto err_pwrctrl_destroy; + } if (pcie->cfg->ops->post_init) { ret = pcie->cfg->ops->post_init(pcie); @@ -1395,9 +1399,10 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp) err_assert_reset: qcom_pcie_perst_assert(pcie); err_pwrctrl_power_off: - pci_pwrctrl_power_off_devices(pci->dev); + if (!pp->skip_pwrctrl_off) + pci_pwrctrl_power_off_devices(pci->dev); err_pwrctrl_destroy: - if (ret != -EPROBE_DEFER) + if (ret != -EPROBE_DEFER && !pci->suspended) pci_pwrctrl_destroy_devices(pci->dev); err_disable_phy: qcom_pcie_phy_power_off(pcie); @@ -1414,11 +1419,14 @@ static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp) qcom_pcie_perst_assert(pcie); - /* - * No need to destroy pwrctrl devices as this function only gets called - * during system suspend as of now. - */ - pci_pwrctrl_power_off_devices(pci->dev); + if (!pci->pp.skip_pwrctrl_off) { + /* + * No need to destroy pwrctrl devices as this function only + * gets called during system suspend as of now. + */ + pci_pwrctrl_power_off_devices(pci->dev); + } + qcom_pcie_phy_power_off(pcie); pcie->cfg->ops->deinit(pcie); } @@ -1432,10 +1440,18 @@ static void qcom_pcie_host_post_init(struct dw_pcie_rp *pp) pcie->cfg->ops->host_post_init(pcie); } +static void qcom_pcie_host_pme_turn_off(struct dw_pcie_rp *pp) +{ + struct dw_pcie *pci = to_dw_pcie_from_pp(pp); + + writel(ELBI_SYS_CTRL_PME_TURNOFF_MSG, pci->elbi_base + ELBI_SYS_CTRL); +} + static const struct dw_pcie_host_ops qcom_pcie_dw_ops = { .init = qcom_pcie_host_init, .deinit = qcom_pcie_host_deinit, .post_init = qcom_pcie_host_post_init, + .pme_turn_off = qcom_pcie_host_pme_turn_off, }; /* Qcom IP rev.: 2.1.0 Synopsys IP rev.: 4.01a */ @@ -2104,53 +2120,51 @@ static int qcom_pcie_suspend_noirq(struct device *dev) if (!pcie) return 0; - /* - * Set minimum bandwidth required to keep data path functional during - * suspend. - */ - if (pcie->icc_mem) { - ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1)); - if (ret) { - dev_err(dev, - "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n", - ret); - return ret; - } - } + ret = dw_pcie_suspend_noirq(pcie->pci); + if (ret) + return ret; - /* - * Turn OFF the resources only for controllers without active PCIe - * devices. For controllers with active devices, the resources are kept - * ON and the link is expected to be in L0/L1 (sub)states. - * - * Turning OFF the resources for controllers with active PCIe devices - * will trigger access violation during the end of the suspend cycle, - * as kernel tries to access the PCIe devices config space for masking - * MSIs. - * - * Also, it is not desirable to put the link into L2/L3 state as that - * implies VDD supply will be removed and the devices may go into - * powerdown state. This will affect the lifetime of the storage devices - * like NVMe. - */ - if (!dw_pcie_link_up(pcie->pci)) { - qcom_pcie_host_deinit(&pcie->pci->pp); - pcie->suspended = true; - } + if (pcie->pci->suspended) { + ret = icc_disable(pcie->icc_mem); + if (ret) + dev_err(dev, "Failed to disable PCIe-MEM interconnect path: %d\n", ret); - /* - * Only disable CPU-PCIe interconnect path if the suspend is non-S2RAM. - * Because on some platforms, DBI access can happen very late during the - * S2RAM and a non-active CPU-PCIe interconnect path may lead to NoC - * error. - */ - if (pm_suspend_target_state != PM_SUSPEND_MEM) { ret = icc_disable(pcie->icc_cpu); if (ret) dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ret); if (pcie->use_pm_opp) dev_pm_opp_set_opp(pcie->pci->dev, NULL); + } else { + /* + * Set minimum bandwidth required to keep data path + * functional during suspend. + */ + if (pcie->icc_mem) { + ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1)); + if (ret) { + dev_err(dev, + "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n", + ret); + return ret; + } + } + + /* + * Only disable CPU-PCIe interconnect path if the suspend + * is non-S2RAM. On some platforms, DBI access can happen + * very late during S2RAM and a non-active CPU-PCIe + * interconnect path may lead to NoC error. + */ + if (pm_suspend_target_state != PM_SUSPEND_MEM) { + ret = icc_disable(pcie->icc_cpu); + if (ret) + dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", + ret); + + if (pcie->use_pm_opp) + dev_pm_opp_set_opp(pcie->pci->dev, NULL); + } } return ret; } @@ -2164,7 +2178,7 @@ static int qcom_pcie_resume_noirq(struct device *dev) if (!pcie) return 0; - if (pm_suspend_target_state != PM_SUSPEND_MEM) { + if (pcie->pci->suspended) { if (pcie->use_pm_opp) { ret = qcom_pcie_set_max_opp(dev); if (ret) { @@ -2178,19 +2192,48 @@ static int qcom_pcie_resume_noirq(struct device *dev) dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret); return ret; } - } - if (pcie->suspended) { - ret = qcom_pcie_host_init(&pcie->pci->pp); - if (ret) - return ret; + ret = icc_enable(pcie->icc_mem); + if (ret) { + dev_err(dev, "Failed to enable PCIe-MEM interconnect path: %d\n", ret); + goto disable_icc_cpu; + } - pcie->suspended = false; + /* + * Ignore -ENODEV & -EIO here since it is expected when no + * endpoint is connected to the PCIe link. + */ + ret = dw_pcie_resume_noirq(pcie->pci); + if (ret && ret != -ENODEV && ret != -EIO) + goto disable_icc_mem; + } else { + if (pm_suspend_target_state != PM_SUSPEND_MEM) { + if (pcie->use_pm_opp) { + ret = qcom_pcie_set_max_opp(dev); + if (ret) { + dev_err(dev, "Failed to set max OPP: %d\n", ret); + return ret; + } + } + + ret = icc_enable(pcie->icc_cpu); + if (ret) { + dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", + ret); + return ret; + } + } } qcom_pcie_icc_opp_update(pcie); return 0; +disable_icc_mem: + icc_disable(pcie->icc_mem); +disable_icc_cpu: + icc_disable(pcie->icc_cpu); + + return ret; } static const struct of_device_id qcom_pcie_match[] = { From ada02d0894fd0ae65bda4d5fbf88dad0a6cb0788 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 13:41:20 +0530 Subject: [PATCH 075/168] PCI: Add pci_suspend_retains_context() to check if device state is preserved during suspend Currently, PCI endpoint drivers (e.g. nvme) use pm_suspend_via_firmware() to check whether device state is preserved during system suspend. If firmware will be invoked at the end of suspend, we don't know whether devices will retain their internal state. But device context might be lost due to platform issues as well. Having those checks in endpoint drivers will not scale and will cause a lot of code duplication. Add pci_suspend_retains_context() as a sole point of truth that the endpoint drivers can rely on to check whether they can expect the device context to be retained or not. If pci_suspend_retains_context() returns 'false', drivers need to prepare for context loss by performing actions such as resetting the device, saving the context, shutting it down etc. If it returns 'true', drivers do not need to perform any special action and can leave the device in active state. Right now, this API only incorporates pm_suspend_via_firmware(), but will be extended in future commits. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260519-l1ss-fix-v2-1-b2c3a4bdeb15@oss.qualcomm.com --- drivers/pci/pci.c | 23 +++++++++++++++++++++++ include/linux/pci.h | 7 +++++++ 2 files changed, 30 insertions(+) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 8f7cfcc00090..86961551ec51 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "pci.h" DEFINE_MUTEX(pci_slot_mutex); @@ -2899,6 +2900,28 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev) pm_runtime_put_sync(parent); } +/** + * pci_suspend_retains_context - Check if the platform can retain the device + * context during system suspend + * @pdev: PCI device to check + * + * Return: true if the platform can guarantee to retain the device context, + * false otherwise. + */ +bool pci_suspend_retains_context(struct pci_dev *pdev) +{ + /* + * If the platform firmware (like ACPI) is involved at the end of + * system suspend, device context may not be retained. + */ + if (pm_suspend_via_firmware()) + return false; + + /* Assume that the context is retained by default */ + return true; +} +EXPORT_SYMBOL_GPL(pci_suspend_retains_context); + static const struct dmi_system_id bridge_d3_blacklist[] = { #ifdef CONFIG_X86 { diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c11..f60f9e4e7b39 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -2086,6 +2086,8 @@ pci_release_mem_regions(struct pci_dev *pdev) pci_select_bars(pdev, IORESOURCE_MEM)); } +bool pci_suspend_retains_context(struct pci_dev *pdev); + #else /* CONFIG_PCI is not enabled */ static inline void pci_set_flags(int flags) { } @@ -2244,6 +2246,11 @@ pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs, static inline void pci_free_irq_vectors(struct pci_dev *dev) { } + +static inline bool pci_suspend_retains_context(struct pci_dev *pdev) +{ + return true; +} #endif /* CONFIG_PCI */ /* Include architecture-dependent settings and functions */ From b583b8fda6ddcf81fe1b02bec0d3c7fb43efc6de Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 13:41:21 +0530 Subject: [PATCH 076/168] PCI: Indicate context lost if L1SS exit is broken during resume from system suspend Per PCIe v7.0, sec 5.5.3.3.1, when exiting L1.2 due to an endpoint asserting CLKREQ# signal, the REFCLK must be turned on within the latency advertised in the LTR message. This requirement applies to L1.1 as well. On some platforms like Qcom, these requirements are satisfied during OS runtime, but not while resuming from the system suspend. This happens because the PCIe RC driver may remove all resource votes and turn off the PHY analog circuitry during suspend to maximize power savings while keeping the link in L1SS. Consequently, when the endpoint asserts CLKREQ# to wake up, the RC driver must restore the PHY and enable the REFCLK. When this recovery process exceeds the L1SS exit latency time (roughly L10_REFCLK_ON + T_COMMONMODE), the endpoint may treat it as a fatal condition and trigger Link Down (LDn). This results in a reset that destroys the internal device state. So to indicate this platform limitation to the client drivers, introduce a new flag 'pci_host_bridge::broken_l1ss_resume' and check it in pci_suspend_retains_context(). If the flag is set by the RC driver, the API will return 'false' indicating the client drivers that the device context may not be retained and the drivers must be prepared for context loss. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260519-l1ss-fix-v2-2-b2c3a4bdeb15@oss.qualcomm.com --- drivers/pci/pci.c | 12 ++++++++++++ include/linux/pci.h | 2 ++ 2 files changed, 14 insertions(+) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 86961551ec51..2f7e7e186b47 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -2910,6 +2910,8 @@ void pci_config_pm_runtime_put(struct pci_dev *pdev) */ bool pci_suspend_retains_context(struct pci_dev *pdev) { + struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus); + /* * If the platform firmware (like ACPI) is involved at the end of * system suspend, device context may not be retained. @@ -2917,6 +2919,16 @@ bool pci_suspend_retains_context(struct pci_dev *pdev) if (pm_suspend_via_firmware()) return false; + /* + * Some host bridges power off the PHY to enter deep low-power + * modes during system suspend. Exiting L1SS from this condition + * may violate timing requirements and result in Link Down (LDn), + * which causes a reset of the device. On such platforms, the + * endpoint must be prepared for context loss. + */ + if (bridge && bridge->broken_l1ss_resume) + return false; + /* Assume that the context is retained by default */ return true; } diff --git a/include/linux/pci.h b/include/linux/pci.h index f60f9e4e7b39..4fee00ec59d7 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -660,6 +660,8 @@ struct pci_host_bridge { unsigned int preserve_config:1; /* Preserve FW resource setup */ unsigned int size_windows:1; /* Enable root bus sizing */ unsigned int msi_domain:1; /* Bridge wants MSI domain */ + unsigned int broken_l1ss_resume:1; /* Resuming from L1SS during + system suspend is broken */ /* Resource alignment requirements */ resource_size_t (*align_resource)(struct pci_dev *dev, From ced835ae1f62c5a316a4229dde5762c63d0ac27b Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 13:41:22 +0530 Subject: [PATCH 077/168] PCI: qcom: Indicate broken L1SS exit during resume from system suspend Qcom PCIe RCs can successfully exit from L1SS during OS runtime. However, during system suspend, the Qcom PCIe RC driver may remove all resource votes and turn off the PHY to maximize power savings. Consequently, when the host is in system suspend with the link in L1SS and the endpoint asserts CLKREQ#, the RC driver must restore the PHY and enable the REFCLK. This recovery process causes the L1SS exit latency time to be exceeded (roughly L10_REFCLK_ON + T_COMMONMODE). If the RC driver were to retain all votes during suspend, L1SS exit would succeed without issue but at the expense of higher power consumption. When the host fails to move the link from L1SS to L0 within the L10_REFCLK_ON + T_COMMONMODE time, the endpoint may treat it as a fatal condition and trigger Link Down (LDn) during resume. This LDn results in a reset that destroys the internal device state. To ensure that the client drivers can properly handle this scenario, let them know about this platform limitation by setting the 'pci_host_bridge::broken_l1ss_resume' flag. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260519-l1ss-fix-v2-3-b2c3a4bdeb15@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index af6bf5cce65b..a0b59b4ef1d2 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1368,6 +1368,19 @@ static void qcom_pcie_host_post_init(struct dw_pcie_rp *pp) struct dw_pcie *pci = to_dw_pcie_from_pp(pp); struct qcom_pcie *pcie = to_qcom_pcie(pci); + /* + * During system suspend, the Qcom RC driver may turn off the + * analog circuitry of PHY and remove controller votes to save + * power. If the link is in L1SS and the endpoint asserts CLKREQ# + * to exit L1SS, the time required to wake the system and restore + * the PHY/REFCLK may exceed the L1SS exit timing (L10_REFCLK_ON + + * T_COMMONMODE), resulting in Link Down (LDn) and a reset of the + * endpoint. Set this flag to indicate this limitation to client + * drivers so that they can avoid relying on device state being + * preserved during system suspend. + */ + pp->bridge->broken_l1ss_resume = true; + if (pcie->cfg->ops->host_post_init) pcie->cfg->ops->host_post_init(pcie); } From 748a927e081bffc9a9be17b4d96088515e1c5f01 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 19 May 2026 13:41:23 +0530 Subject: [PATCH 078/168] nvme-pci: Use pci_suspend_retains_context() during suspend pci_suspend_retains_context() lets PCI client drivers know if the platform can retain the device context during suspend. This is decided based on several factors like: - Firmware involvement at the end of suspend - Any platform limitation in waking from low power state (e.g., L1SS) This API may be extended in the future to cover other platform specific issues impacting the device low power mode during system suspend. Use this API instead of checks like pm_suspend_via_firmware(). When pci_suspend_retains_context() returns false, assume the platform cannot retain the context and shutdown the controller. If it returns true, assume that the context will be retained and keep the device in low power mode. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Christoph Hellwig Link: https://patch.msgid.link/20260519-l1ss-fix-v2-4-b2c3a4bdeb15@oss.qualcomm.com --- drivers/nvme/host/pci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index db5fc9bf6627..a6664983ce5d 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -3915,6 +3915,7 @@ static int nvme_suspend(struct device *dev) * use host managed nvme power settings for lowest idle power if * possible. This should have quicker resume latency than a full device * shutdown. But if the firmware is involved after the suspend or the + * platform has any limitation in waking from low power states or the * device does not support any non-default power states, shut down the * device fully. * @@ -3923,7 +3924,7 @@ static int nvme_suspend(struct device *dev) * down, so as to allow the platform to achieve its minimum low-power * state (which may not be possible if the link is up). */ - if (pm_suspend_via_firmware() || !ctrl->npss || + if (!pci_suspend_retains_context(pdev) || !ctrl->npss || !pcie_aspm_enabled(pdev) || (ndev->ctrl.quirks & NVME_QUIRK_SIMPLE_SUSPEND)) return nvme_disable_prepare_reset(ndev, true); From 1a23bcb452d95f099e530414504c0d99ee076b3f Mon Sep 17 00:00:00 2001 From: Qiang Yu Date: Fri, 8 May 2026 02:54:19 -0700 Subject: [PATCH 079/168] PCI: qcom: Handle mixed PERST#/PHY DT configuration The driver currently supports two PERST# and PHY DT configurations. In one case, PHY and PERST# are described in the RC node. In the other case, they are described in the RP node. A mixed setup is not supported. One common example is PHY on the RP node while PERST# remains on the RC node. In that case the driver goes through the RP parse path, does not find PERST# on RP, and does not report an error because PERST# is optional. Probe can then succeed silently while PERST# is left uncontrolled, and PCIe endpoints fail to work later. This silent probe success makes debugging difficult. Handle this mixed case in the RP parse path by checking whether PERST# is present on RC and, if so, using the RC PERST# GPIO for RP ports while keeping RP parsing for PHY. Emit a warning to indicate mixed DT content so it can be fixed. This keeps mixed systems functional and makes the configuration issue visible instead of failing later at endpoint bring-up. Suggested-by: Manivannan Sadhasivam Signed-off-by: Qiang Yu [mani: folded the fix: https://lore.kernel.org/linux-pci/20260526-fix_perst_gpio_handling-v1-1-9170507bb4e9@oss.qualcomm.com] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Konrad Dybcio Link: https://patch.msgid.link/20260508-mix_perst_phy_dts-v1-1-9eff6ee9b51a@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index bc5a31efc0d0..9173369cca87 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -289,6 +289,7 @@ struct qcom_pcie { const struct qcom_pcie_cfg *cfg; struct dentry *debugfs; struct list_head ports; + struct gpio_desc *reset; bool use_pm_opp; }; @@ -1798,6 +1799,13 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, struct gpio_desc *reset; int ret; + if (pcie->reset) { + dev_warn_once(dev, + "Reusing PERST# from Root Complex node. DT needs to be fixed!\n"); + reset = pcie->reset; + goto skip_perst_parsing; + } + if (!of_find_property(np, "reset-gpios", NULL)) goto parse_child_node; @@ -1816,6 +1824,7 @@ static int qcom_pcie_parse_perst(struct qcom_pcie *pcie, return PTR_ERR(reset); } +skip_perst_parsing: perst = devm_kzalloc(dev, sizeof(*perst), GFP_KERNEL); if (!perst) return -ENOMEM; @@ -1873,6 +1882,13 @@ static int qcom_pcie_parse_ports(struct qcom_pcie *pcie) struct device *dev = pcie->pci->dev; int ret = -ENODEV; + if (of_find_property(dev->of_node, "perst-gpios", NULL)) { + pcie->reset = devm_gpiod_get_optional(dev, "perst", + GPIOD_OUT_HIGH); + if (IS_ERR(pcie->reset)) + return PTR_ERR(pcie->reset); + } + for_each_available_child_of_node_scoped(dev->of_node, of_port) { if (!of_node_is_type(of_port, "pci")) continue; @@ -1899,7 +1915,6 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie) struct device *dev = pcie->pci->dev; struct qcom_pcie_perst *perst; struct qcom_pcie_port *port; - struct gpio_desc *reset; struct phy *phy; int ret; @@ -1907,10 +1922,6 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie) if (IS_ERR(phy)) return PTR_ERR(phy); - reset = devm_gpiod_get_optional(dev, "perst", GPIOD_OUT_HIGH); - if (IS_ERR(reset)) - return PTR_ERR(reset); - ret = phy_init(phy); if (ret) return ret; @@ -1927,7 +1938,7 @@ static int qcom_pcie_parse_legacy_binding(struct qcom_pcie *pcie) INIT_LIST_HEAD(&port->list); list_add_tail(&port->list, &pcie->ports); - perst->desc = reset; + perst->desc = pcie->reset; INIT_LIST_HEAD(&port->perst); INIT_LIST_HEAD(&perst->list); list_add_tail(&perst->list, &port->perst); From c93db46192779ff82a85eec571b2d0324e18beec Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Tue, 28 Apr 2026 14:07:15 +0530 Subject: [PATCH 080/168] PCI/ASPM: Add pcie_encode_t_power_on() helper to encode L1SS T_POWER_ON fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add pcie_encode_t_power_on() to encode the PCIe L1 PM Substates T_POWER_ON parameter into the T_POWER_ON Scale and T_POWER_ON Value fields. This helper can be used by the controller drivers to change the default/wrong value of T_POWER_ON in L1SS capability register to avoid incorrect calculation of LTR_L1.2_THRESHOLD value. The helper converts a T_POWER_ON time specified in microseconds into the appropriate scale/value encoding defined by PCIe r7.0, sec 7.8.3.2. Values that exceed the maximum encodable range are clamped to the largest representable encoding. Signed-off-by: Krishna Chaitanya Chundru [mani: changed t_power_on_us to u32, added helper name to subject] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Shawn Lin Reviewed-by: Shawn Lin Reviewed-by: Ilpo Järvinen Acked-by: Bjorn Helgaas Link: https://patch.msgid.link/20260428-t_power_on_fux-v5-1-f1ef926a91ff@oss.qualcomm.com --- drivers/pci/pci.h | 6 ++++++ drivers/pci/pcie/aspm.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4a14f88e543a..52953f2cef63 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -1110,6 +1110,7 @@ void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked); void pcie_aspm_powersave_config_link(struct pci_dev *pdev); void pci_configure_ltr(struct pci_dev *pdev); void pci_bridge_reconfigure_ltr(struct pci_dev *pdev); +void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value); #else static inline void pcie_aspm_remove_cap(struct pci_dev *pdev, u32 lnkcap) { } static inline void pcie_aspm_init_link_state(struct pci_dev *pdev) { } @@ -1118,6 +1119,11 @@ static inline void pcie_aspm_pm_state_change(struct pci_dev *pdev, bool locked) static inline void pcie_aspm_powersave_config_link(struct pci_dev *pdev) { } static inline void pci_configure_ltr(struct pci_dev *pdev) { } static inline void pci_bridge_reconfigure_ltr(struct pci_dev *pdev) { } +static inline void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value) +{ + *scale = 0; + *value = 0; +} #endif #ifdef CONFIG_PCIE_ECRC diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 925373b98dff..172783e7f519 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -525,6 +525,46 @@ static u32 calc_l12_pwron(struct pci_dev *pdev, u32 scale, u32 val) return 0; } +/** + * pcie_encode_t_power_on - Encode T_POWER_ON into scale and value fields + * @t_power_on_us: T_POWER_ON time in microseconds + * @scale: Encoded T_POWER_ON Scale (0..2) + * @value: Encoded T_POWER_ON Value + * + * T_POWER_ON is encoded as: + * T_POWER_ON(us) = scale_unit(us) * value + * + * where scale_unit is selected by @scale: + * 0: 2us + * 1: 10us + * 2: 100us + * + * If @t_power_on_us exceeds the maximum representable value, the result + * is clamped to the largest encodable T_POWER_ON. + * + * See PCIe r7.0, sec 7.8.3.2. + */ +void pcie_encode_t_power_on(u32 t_power_on_us, u8 *scale, u8 *value) +{ + u8 maxv = FIELD_MAX(PCI_L1SS_CAP_P_PWR_ON_VALUE); + + /* T_POWER_ON_Value ("value") is a 5-bit field with max value of 31. */ + if (t_power_on_us <= 2 * maxv) { + *scale = 0; /* Value times 2us */ + *value = DIV_ROUND_UP(t_power_on_us, 2); + } else if (t_power_on_us <= 10 * maxv) { + *scale = 1; /* Value times 10us */ + *value = DIV_ROUND_UP(t_power_on_us, 10); + } else if (t_power_on_us <= 100 * maxv) { + *scale = 2; /* value times 100us */ + *value = DIV_ROUND_UP(t_power_on_us, 100); + } else { + *scale = 2; + *value = maxv; + } +} +EXPORT_SYMBOL(pcie_encode_t_power_on); + /* * Encode an LTR_L1.2_THRESHOLD value for the L1 PM Substates Control 1 * register. Ports enter L1.2 when the most recent LTR value is greater From d697e90672484186e2676426bf810ad6fe269579 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Tue, 28 Apr 2026 14:07:16 +0530 Subject: [PATCH 081/168] PCI: dwc: Add dw_pcie_program_t_power_on() to program T_POWER_ON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The T_POWER_ON indicates the time (in μs) that a Port requires the port on the opposite side of Link to wait in L1.2.Exit after sampling CLKREQ# asserted before actively driving the interface. This value is used by the ASPM driver to compute the LTR_L1.2_THRESHOLD. Currently, some controllers expose T_POWER_ON value of zero in the L1SS capability registers, leading to incorrect LTR_L1.2_THRESHOLD calculations, which can result in improper L1.2 exit behavior and if AER happens to be supported and enabled, the error may be *reported* via AER. Add a helper to override T_POWER_ON value by the DWC controller drivers. Signed-off-by: Krishna Chaitanya Chundru [mani: changed t_power_on to u32] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Shawn Lin Reviewed-by: Shawn Lin Link: https://patch.msgid.link/20260428-t_power_on_fux-v5-2-f1ef926a91ff@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-designware.c | 28 ++++++++++++++++++++ drivers/pci/controller/dwc/pcie-designware.h | 1 + 2 files changed, 29 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c11cf61b8319..a78553a6f5d6 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -1249,6 +1249,34 @@ void dw_pcie_hide_unsupported_l1ss(struct dw_pcie *pci) dw_pcie_writel_dbi(pci, l1ss + PCI_L1SS_CAP, l1ss_cap); } +/* TODO: Need to handle multi Root Ports */ +void dw_pcie_program_t_power_on(struct dw_pcie *pci, u32 t_power_on) +{ + u8 scale, value; + u16 offset; + u32 val; + + if (!t_power_on) + return; + + offset = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_L1SS); + if (!offset) + return; + + pcie_encode_t_power_on(t_power_on, &scale, &value); + + dw_pcie_dbi_ro_wr_en(pci); + + val = dw_pcie_readl_dbi(pci, offset + PCI_L1SS_CAP); + val &= ~(PCI_L1SS_CAP_P_PWR_ON_SCALE | PCI_L1SS_CAP_P_PWR_ON_VALUE); + FIELD_MODIFY(PCI_L1SS_CAP_P_PWR_ON_SCALE, &val, scale); + FIELD_MODIFY(PCI_L1SS_CAP_P_PWR_ON_VALUE, &val, value); + + dw_pcie_writel_dbi(pci, offset + PCI_L1SS_CAP, val); + + dw_pcie_dbi_ro_wr_dis(pci); +} + void dw_pcie_setup(struct dw_pcie *pci) { u32 val; diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index e759c5c7257e..ace141b90774 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -606,6 +606,7 @@ int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index, u8 bar, size_t size); void dw_pcie_disable_atu(struct dw_pcie *pci, u32 dir, int index); void dw_pcie_hide_unsupported_l1ss(struct dw_pcie *pci); +void dw_pcie_program_t_power_on(struct dw_pcie *pci, u32 t_power_on); void dw_pcie_setup(struct dw_pcie *pci); void dw_pcie_iatu_detect(struct dw_pcie *pci); int dw_pcie_edma_detect(struct dw_pcie *pci); From e9769bb172696b7e924eb30fc94909e0c30786cf Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Tue, 28 Apr 2026 14:07:17 +0530 Subject: [PATCH 082/168] PCI: qcom: Program T_POWER_ON Some platforms have incorrect T_POWER_ON value programmed in hardware. Generally these will be corrected by bootloaders, but not all targets support bootloaders to program correct values. That means the LTR_L1.2_THRESHOLD value calculated by aspm.c can be wrong, which can result in improper L1.2 exit behavior. If AER happens to be supported and enabled, the error may be *reported* via AER. Parse the 't-power-on-us' property from each Root Port node and program it as part of host initialization using dw_pcie_program_t_power_on() before link training. This property in added to the dtschema here [1]. Signed-off-by: Krishna Chaitanya Chundru [mani: reworded comment] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link[1]: https://lore.kernel.org/all/20260205093346.667898-1-krishna.chundru@oss.qualcomm.com/ Link: https://patch.msgid.link/20260428-t_power_on_fux-v5-3-f1ef926a91ff@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 9173369cca87..65688e28f10d 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -276,6 +276,7 @@ struct qcom_pcie_perst { struct qcom_pcie_port { struct list_head list; struct phy *phy; + u32 l1ss_t_power_on; struct list_head perst; }; @@ -1349,6 +1350,14 @@ static int qcom_pcie_phy_power_on(struct qcom_pcie *pcie) return 0; } +static void qcom_pcie_configure_ports(struct qcom_pcie *pcie) +{ + struct qcom_pcie_port *port; + + list_for_each_entry(port, &pcie->ports, list) + dw_pcie_program_t_power_on(pcie->pci, port->l1ss_t_power_on); +} + static int qcom_pcie_host_init(struct dw_pcie_rp *pp) { struct dw_pcie *pci = to_dw_pcie_from_pp(pp); @@ -1387,6 +1396,8 @@ 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_pcie_configure_ports(pcie); + qcom_pcie_perst_deassert(pcie); if (pcie->cfg->ops->config_sid) { @@ -1868,6 +1879,9 @@ static int qcom_pcie_parse_port(struct qcom_pcie *pcie, struct device_node *node if (ret) return ret; + /* TODO: Move to DWC core after multi Root Port support is added */ + of_property_read_u32(node, "t-power-on-us", &port->l1ss_t_power_on); + port->phy = phy; INIT_LIST_HEAD(&port->list); list_add_tail(&port->list, &pcie->ports); From 29f692985819f4089f02a86e151a72f6d4cdd90d Mon Sep 17 00:00:00 2001 From: Shawn Guo Date: Sun, 19 Apr 2026 17:39:34 +0800 Subject: [PATCH 083/168] PCI: qcom: Disable ASPM L0s for SA8775P Due to a hardware issue, L0s is not properly supported by the PCIe controller on the SA8775p SoC. If enabled, the L0s to L0 transition triggers below correctable AER errors and may also affect link stability: pcieport 0000:00:00.0: PME: Signaling with IRQ 332 pcieport 0000:00:00.0: AER: enabled with IRQ 332 pcieport 0000:00:00.0: AER: Correctable error message received from 0000:01:00.0 pci 0000:01:00.0: PCIe Bus Error: severity=Correctable, type=Data Link Layer, (Transmitter ID) pci 0000:01:00.0: device [17cb:1103] error status/mask=00001000/0000e000 pci 0000:01:00.0: [12] Timeout pcieport 0000:00:00.0: AER: Multiple Correctable error message received from 0000:01:00.0 pcieport 0000:00:00.0: PCIe Bus Error: severity=Correctable, type=Data Link Layer, (Transmitter ID) pcieport 0000:00:00.0: device [17cb:0115] error status/mask=00001000/0000e000 pcieport 0000:00:00.0: [12] Timeout Hence, disable L0s for the SA8775p SoC to allow it to properly function by sacrificing a little bit of power saving. Fixes: 58d0d3e032b3 ("PCI: qcom-ep: Add support for SA8775P SOC") Assisted-by: Claude:claude-4-6-sonnet Signed-off-by: Shawn Guo [mani: commit log, corrected fixes tag] Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260419093934.1223027-1-shengchao.guo@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 65688e28f10d..8cf0a27d166b 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1563,6 +1563,7 @@ static const struct qcom_pcie_cfg cfg_1_9_0 = { static const struct qcom_pcie_cfg cfg_1_34_0 = { .ops = &ops_1_9_0, .override_no_snoop = true, + .no_l0s = true, }; static const struct qcom_pcie_cfg cfg_2_1_0 = { From e0779713a1e2f891aeec53e629dbbd33f423c629 Mon Sep 17 00:00:00 2001 From: Yadu M G Date: Thu, 4 Jun 2026 17:54:18 +0530 Subject: [PATCH 084/168] PCI: qcom: Initialize DWC MSI lock for firmware-managed ECAM hosts A lockdep warning is observed during boot on a Qcom firmware-managed platform: INFO: trying to register non-static key. The code is fine but needs lockdep annotation, or maybe you didn't initialize this object before use? turning off the locking correctness validator. ... Call trace: register_lock_class+0x128/0x4d8 __lock_acquire+0x110/0x1db0 lock_acquire+0x278/0x3d8 _raw_spin_lock_irq+0x6c/0xc0 dw_pcie_irq_domain_alloc+0x48/0x190 irq_domain_alloc_irqs_parent+0x2c/0x48 msi_domain_alloc+0x90/0x160 ... dw_pcie_irq_domain_alloc() takes pp->lock while allocating MSI interrupts. pp->lock is normally initialized by dw_pcie_host_init(), but Qcom firmware-managed hosts use the ECAM init path instead: pci_host_common_ecam_create() pci_ecam_create() qcom_pcie_ecam_host_init() dw_pcie_msi_host_init() dw_pcie_allocate_domains() That path constructs a fresh struct dw_pcie_rp and calls dw_pcie_msi_host_init() directly, without going through dw_pcie_host_init(). As a result, pp->lock was not initialized, which triggers the warning. Initialize pp->lock in qcom_pcie_ecam_host_init() before registering the MSI domains so the firmware-managed ECAM path matches the normal DWC host initialization sequence. Fixes: 7d944c0f1469 ("PCI: qcom: Add support for Qualcomm SA8255p based PCIe Root Complex") Signed-off-by: Yadu M G [mani: added fixes tag and CCed stable] Signed-off-by: Manivannan Sadhasivam Cc: stable@kernel.org Link: https://patch.msgid.link/20260604122418.727274-1-yadu.mg@oss.qualcomm.com --- drivers/pci/controller/dwc/pcie-qcom.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c index 8cf0a27d166b..4f76d42457c2 100644 --- a/drivers/pci/controller/dwc/pcie-qcom.c +++ b/drivers/pci/controller/dwc/pcie-qcom.c @@ -1782,6 +1782,12 @@ static int qcom_pcie_ecam_host_init(struct pci_config_window *cfg) pci->dbi_base = cfg->win; pp->num_vectors = MSI_DEF_NUM_VECTORS; + /* + * dw_pcie_msi_host_init() is called directly here, bypassing + * dw_pcie_host_init() where pp->lock is normally initialized. + */ + raw_spin_lock_init(&pp->lock); + ret = dw_pcie_msi_host_init(pp); if (ret) return ret; From 282305d7e9c0e27fd8b4df34b7cd5506a1eccdd6 Mon Sep 17 00:00:00 2001 From: Li RongQing Date: Thu, 14 May 2026 20:55:52 -0400 Subject: [PATCH 085/168] PCI: mediatek: Fix operator precedence in PCIE_FTS_NUM_L0 macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original PCIE_FTS_NUM_L0(x) macro was buggy due to improper operator precedence, where ((x) & 0xff << 8) was evaluated as ((x) & 0xff00). Instead of just fixing the parentheses, use the standard FIELD_PREP() macro. This makes the code more robust by automatically handling masks and shifts, while also adding compile-time type and range checking to ensure the value fits within PCIE_FTS_NUM_MASK. Fixes: 637cfacae96f ("PCI: mediatek: Add MediaTek PCIe host controller support") Signed-off-by: Li RongQing [mani: added the bitfield header include spotted by Sashiko] Signed-off-by: Manivannan Sadhasivam Reviewed-by: Krzysztof Wilczyński Link: https://patch.msgid.link/20260515005552.2343-1-lirongqing@baidu.com --- drivers/pci/controller/pcie-mediatek.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index 75722524fe74..dcfc7408340f 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -7,6 +7,7 @@ * Honghui Zhang */ +#include #include #include #include @@ -61,7 +62,7 @@ /* MediaTek specific configuration registers */ #define PCIE_FTS_NUM 0x70c #define PCIE_FTS_NUM_MASK GENMASK(15, 8) -#define PCIE_FTS_NUM_L0(x) ((x) & 0xff << 8) +#define PCIE_FTS_NUM_L0(x) FIELD_PREP(PCIE_FTS_NUM_MASK, x) #define PCIE_FC_CREDIT 0x73c #define PCIE_FC_CREDIT_MASK (GENMASK(31, 31) | GENMASK(28, 16)) From 286db45fb7ef5fe950a60a47c963156d6ece356f Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 16 May 2026 23:36:55 +0800 Subject: [PATCH 086/168] PCI: Add common TLP type macros and convert aspeed/mediatek Introduce a set of unified TLP type macros in pci.h according to PCIe spec r7.0, sec 2.2.1: - PCIE_TLP_TYPE_MEM_RDWR (0x00) for Memory Read/Write - PCIE_TLP_TYPE_IO_RDWR (0x02) for I/O Read/Write - PCIE_TLP_TYPE_CFG0_RDWR (0x04) for Type 0 Config Read/Write - PCIE_TLP_TYPE_CFG1_RDWR (0x05) for Type 1 Config Read/Write - PCIE_TLP_TYPE_MSG (0x10) for Message Request (routing to RC) These replace the old per-driver hardcoded values or local macros, and also replace the previous PCIE_TLP_TYPE_CFG0_RD/WR and PCIE_TLP_TYPE_CFG1_RD/WR definitions which had identical numeric values. The read/write distinction is already handled by the TLP Format field (Fmt), so a single type macro suffices. Convert the aspeed and mediatek drivers to use the new macros, and remove the obsolete definitions from pci.h. No functional change intended. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260516153657.65214-2-18255117159@163.com --- drivers/pci/controller/pcie-aspeed.c | 8 ++++---- drivers/pci/controller/pcie-mediatek.c | 8 ++------ drivers/pci/pci.h | 9 +++++---- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/pci/controller/pcie-aspeed.c b/drivers/pci/controller/pcie-aspeed.c index 6acfae7d026e..9aa9e14c6148 100644 --- a/drivers/pci/controller/pcie-aspeed.c +++ b/drivers/pci/controller/pcie-aspeed.c @@ -127,19 +127,19 @@ #define CFG0_READ_FMTTYPE \ FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \ ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_NO_DATA, \ - PCIE_TLP_TYPE_CFG0_RD)) + PCIE_TLP_TYPE_CFG0_RDWR)) #define CFG0_WRITE_FMTTYPE \ FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \ ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_DATA, \ - PCIE_TLP_TYPE_CFG0_WR)) + PCIE_TLP_TYPE_CFG0_RDWR)) #define CFG1_READ_FMTTYPE \ FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \ ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_NO_DATA, \ - PCIE_TLP_TYPE_CFG1_RD)) + PCIE_TLP_TYPE_CFG1_RDWR)) #define CFG1_WRITE_FMTTYPE \ FIELD_PREP(ASPEED_TLP_COMMON_FIELDS, \ ASPEED_TLP_FMT_TYPE(PCIE_TLP_FMT_3DW_DATA, \ - PCIE_TLP_TYPE_CFG1_WR)) + PCIE_TLP_TYPE_CFG1_RDWR)) #define CFG_PAYLOAD_SIZE 0x01 /* 1 DWORD */ #define TLP_HEADER_BYTE_EN(x, y) ((GENMASK((x) - 1, 0) << ((y) % 4))) #define TLP_GET_VALUE(x, y, z) \ diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index 75722524fe74..fda4658ba9f1 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -111,10 +111,6 @@ #define APP_CFG_REQ BIT(0) #define APP_CPL_STATUS GENMASK(7, 5) -#define CFG_WRRD_TYPE_0 4 -#define CFG_WR_FMT 2 -#define CFG_RD_FMT 0 - #define CFG_DW0_LENGTH(length) ((length) & GENMASK(9, 0)) #define CFG_DW0_TYPE(type) (((type) << 24) & GENMASK(28, 24)) #define CFG_DW0_FMT(fmt) (((fmt) << 29) & GENMASK(31, 29)) @@ -295,7 +291,7 @@ static int mtk_pcie_hw_rd_cfg(struct mtk_pcie_port *port, u32 bus, u32 devfn, u32 tmp; /* Write PCIe configuration transaction header for Cfgrd */ - writel(CFG_HEADER_DW0(CFG_WRRD_TYPE_0, CFG_RD_FMT), + writel(CFG_HEADER_DW0(PCIE_TLP_TYPE_CFG0_RDWR, PCIE_TLP_FMT_3DW_NO_DATA), port->base + PCIE_CFG_HEADER0); writel(CFG_HEADER_DW1(where, size), port->base + PCIE_CFG_HEADER1); writel(CFG_HEADER_DW2(where, PCI_FUNC(devfn), PCI_SLOT(devfn), bus), @@ -325,7 +321,7 @@ static int mtk_pcie_hw_wr_cfg(struct mtk_pcie_port *port, u32 bus, u32 devfn, int where, int size, u32 val) { /* Write PCIe configuration transaction header for Cfgwr */ - writel(CFG_HEADER_DW0(CFG_WRRD_TYPE_0, CFG_WR_FMT), + writel(CFG_HEADER_DW0(PCIE_TLP_TYPE_CFG0_RDWR, PCIE_TLP_FMT_3DW_DATA), port->base + PCIE_CFG_HEADER0); writel(CFG_HEADER_DW1(where, size), port->base + PCIE_CFG_HEADER1); writel(CFG_HEADER_DW2(where, PCI_FUNC(devfn), PCI_SLOT(devfn), bus), diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4a14f88e543a..23ad51e4c434 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -71,10 +71,11 @@ struct pcie_tlp_log; #define PCIE_TLP_FMT_4DW_DATA 0x03 /* 4DW header, with data */ /* Type of TLP; PCIe r7.0, sec 2.2.1 */ -#define PCIE_TLP_TYPE_CFG0_RD 0x04 /* Config Type 0 Read Request */ -#define PCIE_TLP_TYPE_CFG0_WR 0x04 /* Config Type 0 Write Request */ -#define PCIE_TLP_TYPE_CFG1_RD 0x05 /* Config Type 1 Read Request */ -#define PCIE_TLP_TYPE_CFG1_WR 0x05 /* Config Type 1 Write Request */ +#define PCIE_TLP_TYPE_MEM_RDWR 0x00 /* Memory Read/Write Request */ +#define PCIE_TLP_TYPE_IO_RDWR 0x02 /* I/O Read/Write Request */ +#define PCIE_TLP_TYPE_CFG0_RDWR 0x04 /* Config Type 0 Read/Write Request */ +#define PCIE_TLP_TYPE_CFG1_RDWR 0x05 /* Config Type 1 Read/Write Request */ +#define PCIE_TLP_TYPE_MSG 0x10 /* Message With/Without data Request */ /* Message Routing (r[2:0]); PCIe r6.0, sec 2.2.8 */ #define PCIE_MSG_TYPE_R_RC 0 From 3136184508d2889a11092dbaa097ed6580bc4214 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 16 May 2026 23:36:56 +0800 Subject: [PATCH 087/168] PCI: dwc: Replace ATU type macros with common TLP type macros The dwc driver defines its own ATU type macros (PCIE_ATU_TYPE_MEM, PCIE_ATU_TYPE_IO, PCIE_ATU_TYPE_CFG0, PCIE_ATU_TYPE_CFG1, PCIE_ATU_TYPE_MSG) with the same numerical values as the newly introduced common TLP type macros. Remove the local definitions and switch all DWC users to the common PCIE_TLP_TYPE_* macros. This eliminates redundancy and improves consistency across PCI controller drivers. No functional change intended. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260516153657.65214-3-18255117159@163.com --- .../pci/controller/dwc/pcie-designware-ep.c | 6 +++--- .../pci/controller/dwc/pcie-designware-host.c | 20 +++++++++---------- drivers/pci/controller/dwc/pcie-designware.c | 2 +- drivers/pci/controller/dwc/pcie-designware.h | 5 ----- .../pci/controller/dwc/pcie-tegra194-acpi.c | 4 ++-- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware-ep.c b/drivers/pci/controller/dwc/pcie-designware-ep.c index d4dc3b24da60..461f7fc62e85 100644 --- a/drivers/pci/controller/dwc/pcie-designware-ep.c +++ b/drivers/pci/controller/dwc/pcie-designware-ep.c @@ -584,9 +584,9 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no, config_atu: if (!(flags & PCI_BASE_ADDRESS_SPACE)) - type = PCIE_ATU_TYPE_MEM; + type = PCIE_TLP_TYPE_MEM_RDWR; else - type = PCIE_ATU_TYPE_IO; + type = PCIE_TLP_TYPE_IO_RDWR; if (epf_bar->num_submap) ret = dw_pcie_ep_ib_atu_addr(ep, func_no, type, epf_bar); @@ -659,7 +659,7 @@ static int dw_pcie_ep_map_addr(struct pci_epc *epc, u8 func_no, u8 vfunc_no, struct dw_pcie_ob_atu_cfg atu = { 0 }; atu.func_no = func_no; - atu.type = PCIE_ATU_TYPE_MEM; + atu.type = PCIE_TLP_TYPE_MEM_RDWR; atu.parent_bus_addr = addr - pci->parent_bus_offset; atu.pci_addr = pci_addr; atu.size = size; diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index c9517a348836..b0ff421dbb5b 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -434,7 +434,7 @@ static int dw_pcie_config_ecam_iatu(struct dw_pcie_rp *pp) * remaining buses need type 1 iATU configuration. */ atu.index = 0; - atu.type = PCIE_ATU_TYPE_CFG0; + atu.type = PCIE_TLP_TYPE_CFG0_RDWR; atu.parent_bus_addr = pp->cfg0_base + SZ_1M; /* 1MiB is to cover 1 (bus) * 32 (devices) * 8 (functions) */ atu.size = SZ_1M; @@ -450,7 +450,7 @@ static int dw_pcie_config_ecam_iatu(struct dw_pcie_rp *pp) /* Configure remaining buses in type 1 iATU configuration */ atu.index = 1; - atu.type = PCIE_ATU_TYPE_CFG1; + atu.type = PCIE_TLP_TYPE_CFG1_RDWR; atu.parent_bus_addr = pp->cfg0_base + SZ_2M; atu.size = (SZ_1M * bus_range_max) - SZ_2M; atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE; @@ -745,9 +745,9 @@ static void __iomem *dw_pcie_other_conf_map_bus(struct pci_bus *bus, PCIE_ATU_FUNC(PCI_FUNC(devfn)); if (pci_is_root_bus(bus->parent)) - type = PCIE_ATU_TYPE_CFG0; + type = PCIE_TLP_TYPE_CFG0_RDWR; else - type = PCIE_ATU_TYPE_CFG1; + type = PCIE_TLP_TYPE_CFG1_RDWR; atu.type = type; atu.parent_bus_addr = pp->cfg0_base - pci->parent_bus_offset; @@ -774,7 +774,7 @@ static int dw_pcie_rd_other_conf(struct pci_bus *bus, unsigned int devfn, return ret; if (pp->cfg0_io_shared) { - atu.type = PCIE_ATU_TYPE_IO; + atu.type = PCIE_TLP_TYPE_IO_RDWR; atu.parent_bus_addr = pp->io_base - pci->parent_bus_offset; atu.pci_addr = pp->io_bus_addr; atu.size = pp->io_size; @@ -800,7 +800,7 @@ static int dw_pcie_wr_other_conf(struct pci_bus *bus, unsigned int devfn, return ret; if (pp->cfg0_io_shared) { - atu.type = PCIE_ATU_TYPE_IO; + atu.type = PCIE_TLP_TYPE_IO_RDWR; atu.parent_bus_addr = pp->io_base - pci->parent_bus_offset; atu.pci_addr = pp->io_bus_addr; atu.size = pp->io_size; @@ -908,7 +908,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) if (resource_type(entry->res) != IORESOURCE_MEM) continue; - atu.type = PCIE_ATU_TYPE_MEM; + atu.type = PCIE_TLP_TYPE_MEM_RDWR; atu.parent_bus_addr = entry->res->start - pci->parent_bus_offset; atu.pci_addr = entry->res->start - entry->offset; @@ -951,7 +951,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) if (pp->io_size) { if (ob_iatu_index < pci->num_ob_windows) { atu.index = ob_iatu_index; - atu.type = PCIE_ATU_TYPE_IO; + atu.type = PCIE_TLP_TYPE_IO_RDWR; atu.parent_bus_addr = pp->io_base - pci->parent_bus_offset; atu.pci_addr = pp->io_bus_addr; atu.size = pp->io_size; @@ -1013,7 +1013,7 @@ static int dw_pcie_iatu_setup(struct dw_pcie_rp *pp) window_size = MIN(pci->region_limit + 1, res_size); ret = dw_pcie_prog_inbound_atu(pci, ib_iatu_index, - PCIE_ATU_TYPE_MEM, res_start, + PCIE_TLP_TYPE_MEM_RDWR, res_start, res_start - entry->offset, window_size); if (ret) { dev_err(pci->dev, "Failed to set DMA range %pr\n", @@ -1194,7 +1194,7 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci) atu.code = PCIE_MSG_CODE_PME_TURN_OFF; atu.routing = PCIE_MSG_TYPE_R_BC; - atu.type = PCIE_ATU_TYPE_MSG; + atu.type = PCIE_TLP_TYPE_MSG; atu.size = resource_size(pci->pp.msg_res); atu.index = pci->pp.msg_atu_index; diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c11cf61b8319..813f4baa7c62 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -568,7 +568,7 @@ int dw_pcie_prog_outbound_atu(struct dw_pcie *pci, dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL1, val); val = PCIE_ATU_ENABLE | atu->ctrl2; - if (atu->type == PCIE_ATU_TYPE_MSG) { + if (atu->type == PCIE_TLP_TYPE_MSG) { /* The data-less messages only for now */ val |= PCIE_ATU_INHIBIT_PAYLOAD | atu->code; } diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h index 3e69ef60165b..d8d83156fb9d 100644 --- a/drivers/pci/controller/dwc/pcie-designware.h +++ b/drivers/pci/controller/dwc/pcie-designware.h @@ -170,11 +170,6 @@ #define PCIE_ATU_VIEWPORT_SIZE 0x2C #define PCIE_ATU_REGION_CTRL1 0x000 #define PCIE_ATU_INCREASE_REGION_SIZE BIT(13) -#define PCIE_ATU_TYPE_MEM 0x0 -#define PCIE_ATU_TYPE_IO 0x2 -#define PCIE_ATU_TYPE_CFG0 0x4 -#define PCIE_ATU_TYPE_CFG1 0x5 -#define PCIE_ATU_TYPE_MSG 0x10 #define PCIE_ATU_TD BIT(8) #define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20) #define PCIE_ATU_REGION_CTRL2 0x004 diff --git a/drivers/pci/controller/dwc/pcie-tegra194-acpi.c b/drivers/pci/controller/dwc/pcie-tegra194-acpi.c index 55f61914a986..2d737b49ea8f 100644 --- a/drivers/pci/controller/dwc/pcie-tegra194-acpi.c +++ b/drivers/pci/controller/dwc/pcie-tegra194-acpi.c @@ -86,11 +86,11 @@ static void __iomem *tegra194_map_bus(struct pci_bus *bus, if (bus->parent->number == cfg->busr.start) { if (PCI_SLOT(devfn) == 0) - type = PCIE_ATU_TYPE_CFG0; + type = PCIE_TLP_TYPE_CFG0_RDWR; else return NULL; } else { - type = PCIE_ATU_TYPE_CFG1; + type = PCIE_TLP_TYPE_CFG1_RDWR; } program_outbound_atu(pcie_ecam, 0, type, cfg->res.start, busdev, From 52015335e5db7e9b0d62d1ee1178de1b601ac7ba Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Sat, 16 May 2026 23:36:57 +0800 Subject: [PATCH 088/168] PCI: cadence: Use common TLP type macros The Cadence HPA driver uses hardcoded constants (0x0, 0x2, 0x4, 0x5, 0x10) to program the outbound region type. Replace them with the newly introduced common TLP type macros from pci.h for better readability and maintainability. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260516153657.65214-4-18255117159@163.com --- .../pci/controller/cadence/pcie-cadence-hpa-regs.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/cadence/pcie-cadence-hpa-regs.h b/drivers/pci/controller/cadence/pcie-cadence-hpa-regs.h index 026e131600de..72299121fd44 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-hpa-regs.h +++ b/drivers/pci/controller/cadence/pcie-cadence-hpa-regs.h @@ -14,6 +14,8 @@ #include #include +#include "../../pci.h" + /* High Performance Architecture (HPA) PCIe controller registers */ #define CDNS_PCIE_HPA_IP_REG_BANK 0x01000000 #define CDNS_PCIE_HPA_IP_CFG_CTRL_REG_BANK 0x01003C00 @@ -119,15 +121,15 @@ #define CDNS_PCIE_HPA_AT_OB_REGION_DESC0(r) (0x1008 + ((r) & 0x1F) * 0x0080) #define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK GENMASK(28, 24) #define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MEM \ - FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x0) + FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_MEM_RDWR) #define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_IO \ - FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x2) + FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_IO_RDWR) #define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_CONF_TYPE0 \ - FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x4) + FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_CFG0_RDWR) #define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_CONF_TYPE1 \ - FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x5) + FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_CFG1_RDWR) #define CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_NORMAL_MSG \ - FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, 0x10) + FIELD_PREP(CDNS_PCIE_HPA_AT_OB_REGION_DESC0_TYPE_MASK, PCIE_TLP_TYPE_MSG) /* Region r Outbound PCIe Descriptor Register */ #define CDNS_PCIE_HPA_AT_OB_REGION_DESC1(r) (0x100C + ((r) & 0x1F) * 0x0080) From 9f22b92259bb5ac43e2b9007103787d4418fec56 Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Fri, 22 May 2026 09:06:46 +0200 Subject: [PATCH 089/168] PCI: Avoid FLR for MediaTek MT7925 WiFi The MediaTek MT7925 WiFi device advertises FLR capability, but it does not work correctly. This manifests in VFIO passthrough scenarios. Normal VM operation works fine, including clean shutdown/reboot. However, when the VM terminates uncleanly (crash, force-off), VFIO attempts to reset the device before it can be assigned to another VM. Because FLR is broken, the reset fails, preventing reuse. This is similar to its predecessor MT7922 (see 81f64e925c29 ("PCI: Avoid FLR for Mediatek MT7922 WiFi")), but with different symptoms. The MT7922 issue manifests as config read failures (returning ~0) after FLR. The MT7925 shows different behavior: config reads work correctly after FLR, but firmware communication fails. First VM start with MT7925 works fine: mt7925e 0000:08:00.0: ASIC revision: 79250000 mt7925e 0000:08:00.0: WM Firmware Version: ____000000, Build Time: 20260106153120 After force reset or VM crash, when VFIO attempts FLR to reset the device for reassignment, firmware initialization fails: mt7925e 0000:08:00.0: ASIC revision: 79250000 mt7925e 0000:08:00.0: Message 00000010 (seq 1) timeout mt7925e 0000:08:00.0: Failed to get patch semaphore [Repeats with increasing sequence numbers 2-10] mt7925e 0000:08:00.0: hardware init failed The driver cannot acquire the patch semaphore needed for firmware initialization, indicating that FLR does not properly reset the firmware state. The device remains in this broken state until physical power cycle. Disable FLR for MT7925 so the PCI core falls back to other reset methods, e.g., Secondary Bus Reset, which successfully resets the device and allows reinitialization for VFIO passthrough reuse. Signed-off-by: Jose Ignacio Tornos Martinez Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260522070646.203115-1-jtornosm@redhat.com --- drivers/pci/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index caaed1a01dc0..e49136ac5dbf 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5605,6 +5605,7 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x443, quirk_intel_qat_vf_cap); * Intel 82579LM Gigabit Ethernet Controller 0x1502 * Intel 82579V Gigabit Ethernet Controller 0x1503 * Mediatek MT7922 802.11ax PCI Express Wireless Network Adapter + * Mediatek MT7925 802.11be PCI Express Wireless Network Adapter */ static void quirk_no_flr(struct pci_dev *dev) { @@ -5619,6 +5620,7 @@ DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD, 0x17f0, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1502, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_INTEL, 0x1503, quirk_no_flr); DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_MEDIATEK, 0x0616, quirk_no_flr); +DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_MEDIATEK, 0x7925, quirk_no_flr); /* FLR may cause the SolidRun SNET DPU (rev 0x1) to hang */ static void quirk_no_flr_snet(struct pci_dev *dev) From 29fbf582e75015c031e7965fdd4084af123b9ca2 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Mon, 18 May 2026 08:42:40 +0800 Subject: [PATCH 090/168] PCI: Add pci_host_common_link_train_delay() helper PCIe r6.0, sec 6.6.1 (Conventional Reset) requires that for a Downstream Port supporting Link speeds greater than 5.0 GT/s, software must wait a minimum of 100 ms after Link training completes before sending any Configuration Request. Introduce a static inline helper pci_host_common_link_train_delay() that checks the given max_link_speed (2 = 5.0 GT/s, 3 = 8.0 GT/s, etc.) and calls msleep(100) only when the speed is greater than 5.0 GT/s. This allows multiple host controller drivers to share the same mandatory delay without duplicating the logic. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260518004246.1384532-2-18255117159@163.com --- drivers/pci/controller/pci-host-common.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h index b5075d4bd7eb..d709f7e3e11a 100644 --- a/drivers/pci/controller/pci-host-common.h +++ b/drivers/pci/controller/pci-host-common.h @@ -10,6 +10,9 @@ #ifndef _PCI_HOST_COMMON_H #define _PCI_HOST_COMMON_H +#include +#include "../pci.h" + struct pci_ecam_ops; int pci_host_common_probe(struct platform_device *pdev); @@ -20,4 +23,18 @@ void pci_host_common_remove(struct platform_device *pdev); struct pci_config_window *pci_host_common_ecam_create(struct device *dev, struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops); + +/** + * pci_host_common_link_train_delay - Wait 100 ms if link speed > 5 GT/s + * @max_link_speed: the maximum link speed (2 = 5.0 GT/s, 3 = 8.0 GT/s, ...) + * + * Must be called after Link training completes and before the first + * Configuration Request is sent. + */ +static inline void pci_host_common_link_train_delay(int max_link_speed) +{ + if (max_link_speed > 2) + msleep(PCIE_RESET_CONFIG_WAIT_MS); +} + #endif From 869317b95fd735684057666a65dd8ef95d4bd669 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Mon, 18 May 2026 08:42:41 +0800 Subject: [PATCH 091/168] PCI: cadence: Add post-link delay for LGA and j721e glue driver The Cadence LGA (Legacy Architecture IP) PCIe host controller currently lacks the mandatory 100 ms delay after link training completes for speeds > 5.0 GT/s, as required by PCIe r6.0 sec 6.6.1. Add a 'max_link_speed' field to struct cdns_pcie. In the common host layer function cdns_pcie_host_start_link(), after the link has been successfully established, call pci_host_common_link_train_delay() to insert the required delay. For the j721e glue driver, set cdns_pcie.max_link_speed from the existing link speed logic. For other LGA-based glue drivers (sky1, sg2042), the common LGA host setup (pcie-cadence-host.c) provides a fallback reading of the device tree property "max-link-speed" when available. This ensures that the delay is not missed on those platforms once they enable the property. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20260518004246.1384532-3-18255117159@163.com --- drivers/pci/controller/cadence/pci-j721e.c | 1 + drivers/pci/controller/cadence/pcie-cadence-host-common.c | 4 ++++ drivers/pci/controller/cadence/pcie-cadence-host.c | 4 ++++ drivers/pci/controller/cadence/pcie-cadence.h | 2 ++ 4 files changed, 11 insertions(+) diff --git a/drivers/pci/controller/cadence/pci-j721e.c b/drivers/pci/controller/cadence/pci-j721e.c index bfdfe98d5aba..ae916e7b1927 100644 --- a/drivers/pci/controller/cadence/pci-j721e.c +++ b/drivers/pci/controller/cadence/pci-j721e.c @@ -206,6 +206,7 @@ static int j721e_pcie_set_link_speed(struct j721e_pcie *pcie, (pcie_get_link_speed(link_speed) == PCI_SPEED_UNKNOWN)) link_speed = 2; + pcie->cdns_pcie->max_link_speed = link_speed; val = link_speed - 1; ret = regmap_update_bits(syscon, offset, GENERATION_SEL_MASK, val); if (ret) diff --git a/drivers/pci/controller/cadence/pcie-cadence-host-common.c b/drivers/pci/controller/cadence/pcie-cadence-host-common.c index 2b0211870f02..18e4b6c760b5 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-host-common.c +++ b/drivers/pci/controller/cadence/pcie-cadence-host-common.c @@ -14,6 +14,7 @@ #include "pcie-cadence.h" #include "pcie-cadence-host-common.h" +#include "../pci-host-common.h" #define LINK_RETRAIN_TIMEOUT HZ @@ -115,6 +116,9 @@ int cdns_pcie_host_start_link(struct cdns_pcie_rc *rc, if (!ret && rc->quirk_retrain_flag) ret = cdns_pcie_retrain(pcie, pcie_link_up); + if (!ret) + pci_host_common_link_train_delay(pcie->max_link_speed); + return ret; } EXPORT_SYMBOL_GPL(cdns_pcie_host_start_link); diff --git a/drivers/pci/controller/cadence/pcie-cadence-host.c b/drivers/pci/controller/cadence/pcie-cadence-host.c index 0bc9e6e90e0e..058e4e619654 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-host.c +++ b/drivers/pci/controller/cadence/pcie-cadence-host.c @@ -13,6 +13,7 @@ #include "pcie-cadence.h" #include "pcie-cadence-host-common.h" +#include "../../pci.h" static u8 bar_aperture_mask[] = { [RP_BAR0] = 0x1F, @@ -397,6 +398,9 @@ int cdns_pcie_host_setup(struct cdns_pcie_rc *rc) rc->device_id = 0xffff; of_property_read_u32(np, "device-id", &rc->device_id); + if (pcie->max_link_speed < 1) + pcie->max_link_speed = of_pci_get_max_link_speed(np); + pcie->reg_base = devm_platform_ioremap_resource_byname(pdev, "reg"); if (IS_ERR(pcie->reg_base)) { dev_err(dev, "missing \"reg\"\n"); diff --git a/drivers/pci/controller/cadence/pcie-cadence.h b/drivers/pci/controller/cadence/pcie-cadence.h index 574e9cf4d003..042a4c49bb9a 100644 --- a/drivers/pci/controller/cadence/pcie-cadence.h +++ b/drivers/pci/controller/cadence/pcie-cadence.h @@ -86,6 +86,7 @@ struct cdns_plat_pcie_of_data { * @ops: Platform-specific ops to control various inputs from Cadence PCIe * wrapper * @cdns_pcie_reg_offsets: Register bank offsets for different SoC + * @max_link_speed: Maximum supported link speed */ struct cdns_pcie { void __iomem *reg_base; @@ -98,6 +99,7 @@ struct cdns_pcie { struct device_link **link; const struct cdns_pcie_ops *ops; const struct cdns_plat_pcie_of_data *cdns_pcie_reg_offsets; + int max_link_speed; }; /** From 0c26b1c34d12d4debfb5363cc0be6cdf68e87ba2 Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 18 May 2026 15:27:14 +0800 Subject: [PATCH 092/168] PCI: imx6: Configure REF_USE_PAD before PHY reset for i.MX95 According to the i.MX95 PCIe PHY Databook, the ref_use_pad signal in the Common Block Signals section selects the reference clock source connected to the PHY pads. Per the specification, any change to this input must be followed by a PHY reset assertion to take effect. Move the REF_USE_PAD configuration before the PHY reset toggle to comply with the required initialization sequence. Fixes: 47f54a902dcd ("PCI: imx6: Toggle the core reset for i.MX95 PCIe") Signed-off-by: Richard Zhu [mani: renamed the callback and helper to match the usecase] Signed-off-by: Manivannan Sadhasivam Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260518072715.3166514-2-hongxing.zhu@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index 773ab65b2afa..b32748816ac8 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -138,6 +138,7 @@ struct imx_pcie_drvdata { const u32 mode_off[IMX_PCIE_MAX_INSTANCES]; const u32 mode_mask[IMX_PCIE_MAX_INSTANCES]; const struct pci_epc_features *epc_features; + int (*select_ref_clk_src)(struct imx_pcie *pcie); int (*init_phy)(struct imx_pcie *pcie); int (*enable_ref_clk)(struct imx_pcie *pcie, bool enable); int (*core_reset)(struct imx_pcie *pcie, bool assert); @@ -249,6 +250,24 @@ static unsigned int imx_pcie_grp_offset(const struct imx_pcie *imx_pcie) return imx_pcie->controller_id == 1 ? IOMUXC_GPR16 : IOMUXC_GPR14; } +static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie) +{ + bool ext = imx_pcie->enable_ext_refclk; + + /* + * Regarding the Signal Descriptions of i.MX95 PCIe PHY, ref_use_pad is + * used to select reference clock connected to a pair of pads. + * + * Any change in this input must be followed by phy_reset assertion. + */ + + regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL, + IMX95_PCIE_REF_USE_PAD, + ext ? IMX95_PCIE_REF_USE_PAD : 0); + + return 0; +} + static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) { bool ext = imx_pcie->enable_ext_refclk; @@ -271,9 +290,6 @@ static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) IMX95_PCIE_PHY_CR_PARA_SEL, IMX95_PCIE_PHY_CR_PARA_SEL); - regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_PHY_GEN_CTRL, - IMX95_PCIE_REF_USE_PAD, - ext ? IMX95_PCIE_REF_USE_PAD : 0); regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, IMX95_PCIE_REF_CLKEN, ext ? 0 : IMX95_PCIE_REF_CLKEN); @@ -1351,6 +1367,9 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) pp->bridge->disable_device = imx_pcie_disable_device; } + if (imx_pcie->drvdata->select_ref_clk_src) + imx_pcie->drvdata->select_ref_clk_src(imx_pcie); + imx_pcie_assert_core_reset(imx_pcie); if (imx_pcie->drvdata->init_phy) @@ -2051,6 +2070,7 @@ static const struct imx_pcie_drvdata drvdata[] = { .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, .core_reset = imx95_pcie_core_reset, .init_phy = imx95_pcie_init_phy, + .select_ref_clk_src = imx95_pcie_select_ref_clk_src, .wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock, .enable_ref_clk = imx95_pcie_enable_ref_clk, .clr_clkreq_override = imx95_pcie_clr_clkreq_override, @@ -2106,6 +2126,7 @@ static const struct imx_pcie_drvdata drvdata[] = { .ltssm_mask = IMX95_PCIE_LTSSM_EN, .mode_off[0] = IMX95_PE0_GEN_CTRL_1, .mode_mask[0] = IMX95_PCIE_DEVICE_TYPE, + .select_ref_clk_src = imx95_pcie_select_ref_clk_src, .init_phy = imx95_pcie_init_phy, .core_reset = imx95_pcie_core_reset, .wait_pll_lock = imx95_pcie_wait_for_phy_pll_lock, From 9dda3f83ba677b9cc2613cecd9120123000ae50f Mon Sep 17 00:00:00 2001 From: Richard Zhu Date: Mon, 18 May 2026 15:27:15 +0800 Subject: [PATCH 093/168] PCI: imx6: Assert ref_clk_en after reference clock stabilizes on i.MX95 According to the PHY Databook Common Block Signals section, the ref_clk_en signal must remain de-asserted until the reference clock is running at the appropriate frequency. Once the clock is stable, ref_clk_en can be asserted. For lower power states where the reference clock to the PHY is disabled, ref_clk_en should also be de-asserted. Move the ref_clk_en bit manipulation into imx95_pcie_enable_ref_clk() to ensure the reference clock stabilizes before ref_clk_en is asserted and before the PHY reset is de-asserted. This aligns with the timing requirements specified in the PHY documentation. Fixes: d8574ce57d76 ("PCI: imx6: Add external reference clock input mode support") Signed-off-by: Richard Zhu Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260518072715.3166514-3-hongxing.zhu@nxp.com --- drivers/pci/controller/dwc/pci-imx6.c | 28 +++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index b32748816ac8..cce78a7a1624 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -270,8 +270,6 @@ static int imx95_pcie_select_ref_clk_src(struct imx_pcie *imx_pcie) static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) { - bool ext = imx_pcie->enable_ext_refclk; - /* * ERR051624: The Controller Without Vaux Cannot Exit L23 Ready * Through Beacon or PERST# De-assertion @@ -290,10 +288,6 @@ static int imx95_pcie_init_phy(struct imx_pcie *imx_pcie) IMX95_PCIE_PHY_CR_PARA_SEL, IMX95_PCIE_PHY_CR_PARA_SEL); - regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, - IMX95_PCIE_REF_CLKEN, - ext ? 0 : IMX95_PCIE_REF_CLKEN); - return 0; } @@ -742,7 +736,29 @@ static void imx95_pcie_clkreq_override(struct imx_pcie *imx_pcie, bool enable) static int imx95_pcie_enable_ref_clk(struct imx_pcie *imx_pcie, bool enable) { + bool ext = imx_pcie->enable_ext_refclk; + imx95_pcie_clkreq_override(imx_pcie, enable); + /* + * The ref_clk_en signal must remain de-asserted until the + * reference clock is running at appropriate frequency, at which + * point this bit can be asserted. For lower power states where + * the reference clock to the PHY is disabled, it may also be + * de-asserted. + * +------------------- -+--------+----------------+ + * | External clock mode | Enable | PCIE_REF_CLKEN | + * +---------------------+--------+----------------+ + * | TRUE | X | 1b'0 | + * +---------------------+--------+----------------+ + * | FALSE | TRUE | 1b'1 | + * +---------------------+--------+----------------+ + * | FALSE | FALSE | 1b'0 | + * +---------------------+--------+----------------+ + */ + regmap_update_bits(imx_pcie->iomuxc_gpr, IMX95_PCIE_SS_RW_REG_0, + IMX95_PCIE_REF_CLKEN, + ext || !enable ? 0 : IMX95_PCIE_REF_CLKEN); + return 0; } From b12341b98d5ac52f48ca1390e1e371aed81346c8 Mon Sep 17 00:00:00 2001 From: Shuvam Pandey Date: Mon, 18 May 2026 13:49:40 +0545 Subject: [PATCH 094/168] PCI: meson: Propagate devm_add_action_or_reset() failure meson_pcie_probe_clock() enables a clock and then registers a devres action to disable it during teardown. If devm_add_action_or_reset() fails, it runs the action immediately, disabling the clock. The return value is currently ignored, so on that failure path, meson_pcie_probe_clock() returns the disabled clock and probe continues. Return the error so the existing probe error path unwinds normally. Fixes: 9c0ef6d34fdbf ("PCI: amlogic: Add the Amlogic Meson PCIe controller driver") Signed-off-by: Shuvam Pandey Signed-off-by: Manivannan Sadhasivam Reviewed-by: Neil Armstrong Link: https://patch.msgid.link/177909148011.9588.6639767953842842291@gmail.com --- drivers/pci/controller/dwc/pci-meson.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c index 0694084f612b..8d495bcc3a41 100644 --- a/drivers/pci/controller/dwc/pci-meson.c +++ b/drivers/pci/controller/dwc/pci-meson.c @@ -204,7 +204,9 @@ static inline struct clk *meson_pcie_probe_clock(struct device *dev, return ERR_PTR(ret); } - devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); + ret = devm_add_action_or_reset(dev, meson_pcie_disable_clock, clk); + if (ret) + return ERR_PTR(ret); return clk; } From 4b0dc84b293984f75598881809fb2d3daf54a2a8 Mon Sep 17 00:00:00 2001 From: Shuvam Pandey Date: Mon, 18 May 2026 22:44:18 +0545 Subject: [PATCH 095/168] PCI: meson: Add missing remove callback meson_pcie_probe() powers on the PHY and registers the DesignWare host bridge with dw_pcie_host_init(), but the driver has no remove callback. On driver unbind or module unload, the driver core therefore proceeds to devres cleanup without first unregistering the host bridge or powering off the PHY. Add a remove callback that deinitializes the DesignWare host bridge and powers off the PHY while device-managed resources are still valid. Fixes: 9c0ef6d34fdb ("PCI: amlogic: Add the Amlogic Meson PCIe controller driver") Signed-off-by: Shuvam Pandey Signed-off-by: Manivannan Sadhasivam Link: https://patch.msgid.link/1a0c86ab264cdc1c79c917e984b90991af51d827.1779123847.git.shuvampandey1@gmail.com --- drivers/pci/controller/dwc/pci-meson.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/pci/controller/dwc/pci-meson.c b/drivers/pci/controller/dwc/pci-meson.c index 8d495bcc3a41..225d887cd0a3 100644 --- a/drivers/pci/controller/dwc/pci-meson.c +++ b/drivers/pci/controller/dwc/pci-meson.c @@ -453,6 +453,14 @@ static int meson_pcie_probe(struct platform_device *pdev) return ret; } +static void meson_pcie_remove(struct platform_device *pdev) +{ + struct meson_pcie *mp = platform_get_drvdata(pdev); + + dw_pcie_host_deinit(&mp->pci.pp); + meson_pcie_power_off(mp); +} + static const struct of_device_id meson_pcie_of_match[] = { { .compatible = "amlogic,axg-pcie", @@ -466,6 +474,7 @@ MODULE_DEVICE_TABLE(of, meson_pcie_of_match); static struct platform_driver meson_pcie_driver = { .probe = meson_pcie_probe, + .remove = meson_pcie_remove, .driver = { .name = "meson-pcie", .of_match_table = meson_pcie_of_match, From f865a57896bd92d7662eb2818d8f48872e2cbbc7 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 21 May 2026 23:16:17 +0530 Subject: [PATCH 096/168] PCI: mediatek: Fix IRQ domain leak when port fails to enable When mtk_pcie_enable_port() fails, mtk_pcie_port_free() removes the port from pcie->ports and frees the port structure. However, the IRQ domains set up earlier by mtk_pcie_init_irq_domain() are never freed. Fix this by refactoring mtk_pcie_irq_teardown() into a per-port helper, mtk_pcie_irq_teardown_port(), and calling it from mtk_pcie_setup() when mtk_pcie_enable_port() fails. Since the IRQ teardown must only happen in the probe error path (during resume, child devices may have active MSI mappings and the NOIRQ context prohibits sleeping locks), mtk_pcie_enable_port() is changed to return an error code so callers can distinguish the two paths and act accordingly. This issue was reported by Sashiko while reviewing the EcoNet EN7528 SoC support series. Fixes: b099631df160 ("PCI: mediatek: Add controller support for MT2712 and MT7622") Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Cc: stable@vger.kernel.org # 5.10 Cc: Caleb James DeLisle Link: https://patch.msgid.link/20260521174617.17692-1-mani@kernel.org --- drivers/pci/controller/pcie-mediatek.c | 65 ++++++++++++++++---------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index dcfc7408340f..f34d91e495bc 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -530,23 +530,27 @@ static void mtk_pcie_enable_msi(struct mtk_pcie_port *port) writel(val, port->base + PCIE_INT_MASK); } +static void mtk_pcie_irq_teardown_port(struct mtk_pcie_port *port) +{ + irq_set_chained_handler_and_data(port->irq, NULL, NULL); + + if (port->irq_domain) + irq_domain_remove(port->irq_domain); + + if (IS_ENABLED(CONFIG_PCI_MSI)) { + if (port->inner_domain) + irq_domain_remove(port->inner_domain); + } + + irq_dispose_mapping(port->irq); +} + static void mtk_pcie_irq_teardown(struct mtk_pcie *pcie) { struct mtk_pcie_port *port, *tmp; - list_for_each_entry_safe(port, tmp, &pcie->ports, list) { - irq_set_chained_handler_and_data(port->irq, NULL, NULL); - - if (port->irq_domain) - irq_domain_remove(port->irq_domain); - - if (IS_ENABLED(CONFIG_PCI_MSI)) { - if (port->inner_domain) - irq_domain_remove(port->inner_domain); - } - - irq_dispose_mapping(port->irq); - } + list_for_each_entry_safe(port, tmp, &pcie->ports, list) + mtk_pcie_irq_teardown_port(port); } static int mtk_pcie_intx_map(struct irq_domain *domain, unsigned int irq, @@ -866,7 +870,7 @@ static int mtk_pcie_startup_port_an7583(struct mtk_pcie_port *port) return mtk_pcie_startup_port_v2(port); } -static void mtk_pcie_enable_port(struct mtk_pcie_port *port) +static int mtk_pcie_enable_port(struct mtk_pcie_port *port) { struct mtk_pcie *pcie = port->pcie; struct device *dev = pcie->dev; @@ -875,7 +879,7 @@ static void mtk_pcie_enable_port(struct mtk_pcie_port *port) err = clk_prepare_enable(port->sys_ck); if (err) { dev_err(dev, "failed to enable sys_ck%d clock\n", port->slot); - goto err_sys_clk; + return err; } err = clk_prepare_enable(port->ahb_ck); @@ -923,11 +927,15 @@ static void mtk_pcie_enable_port(struct mtk_pcie_port *port) goto err_phy_on; } - if (!pcie->soc->startup(port)) - return; + err = pcie->soc->startup(port); + if (err) { + dev_info(dev, "Port%d link down\n", port->slot); + goto err_soc_startup; + } - dev_info(dev, "Port%d link down\n", port->slot); + return 0; +err_soc_startup: phy_power_off(port->phy); err_phy_on: phy_exit(port->phy); @@ -943,8 +951,8 @@ static void mtk_pcie_enable_port(struct mtk_pcie_port *port) clk_disable_unprepare(port->ahb_ck); err_ahb_clk: clk_disable_unprepare(port->sys_ck); -err_sys_clk: - mtk_pcie_port_free(port); + + return err; } static int mtk_pcie_parse_port(struct mtk_pcie *pcie, @@ -1110,8 +1118,13 @@ static int mtk_pcie_setup(struct mtk_pcie *pcie) return err; /* enable each port, and then check link status */ - list_for_each_entry_safe(port, tmp, &pcie->ports, list) - mtk_pcie_enable_port(port); + list_for_each_entry_safe(port, tmp, &pcie->ports, list) { + err = mtk_pcie_enable_port(port); + if (err) { + mtk_pcie_irq_teardown_port(port); + mtk_pcie_port_free(port); + } + } /* power down PCIe subsys if slots are all empty (link down) */ if (list_empty(&pcie->ports)) @@ -1210,14 +1223,18 @@ static int mtk_pcie_resume_noirq(struct device *dev) { struct mtk_pcie *pcie = dev_get_drvdata(dev); struct mtk_pcie_port *port, *tmp; + int err; if (list_empty(&pcie->ports)) return 0; clk_prepare_enable(pcie->free_ck); - list_for_each_entry_safe(port, tmp, &pcie->ports, list) - mtk_pcie_enable_port(port); + list_for_each_entry_safe(port, tmp, &pcie->ports, list) { + err = mtk_pcie_enable_port(port); + if (err) + mtk_pcie_port_free(port); + } /* In case of EP was removed while system suspend. */ if (list_empty(&pcie->ports)) From 0ba76b19fd4c7256787eab0283c759b18eb76876 Mon Sep 17 00:00:00 2001 From: Lukas Wunner Date: Thu, 4 Jun 2026 17:12:08 +0200 Subject: [PATCH 097/168] PCI/P2PDMA: Add Intel QAT, DSA, IAA devices to whitelist The first device on a PCI root bus determines whether the host bridge is whitelisted for P2PDMA. All Intel Xeon chips since Ice Lake (ICX, 2021) expose a device with ID 0x09a2 as first device. It is loosely associated with the IOMMU. All these Xeon chips support P2PDMA, so since the addition of the device with commit feaea1fe8b36 ("PCI/P2PDMA: Add Intel 3rd Gen Intel Xeon Scalable Processors to whitelist"), P2PDMA has been allowed on all new Xeons without the need to amend the whitelist: Xeons with Performance Cores: Sapphire Rapids (SPR, 2023) Emerald Rapids (EMR, 2023) Granite Rapids (GNR, 2024) Diamond Rapids (DMR, 2026) Xeons with Efficiency Cores: Sierra Forest (SRF, 2024) Clearwater Forest (CWF, 2026) However these Xeons also expose accelerators as first device on a root bus of its own: QuickAssist Technology (QAT, crypto & compression accelerator) Data Streaming Accelerator (DSA, dma engine) In-Memory Analytics Accelerator (IAA, compression accelerator) Whitelist them for P2PDMA as well. Move their Device ID macros from the accelerator drivers to for reuse by P2PDMA code. Unfortunately the Device IDs vary across Xeon generations as additional features were added to the accelerators. This currently necessitates an amendment for each new Xeon chip. For future chips, this need shall be avoided by an ongoing effort to extend ACPI HMAT with PCIe P2PDMA characteristics (latency, bandwidth, ordering constraints). The PCI core will be able look up in this BIOS-provided ACPI table whether P2PDMA is supported, instead of relying on a whitelist that needs to be amended continuously. Signed-off-by: Lukas Wunner Signed-off-by: Bjorn Helgaas Acked-by: Vinicius Costa Gomes Acked-by: Giovanni Cabiddu # QAT Cc: stable@vger.kernel.org Link: https://patch.msgid.link/6aac4922b5fe7070b11874427a9285e42ddd05a4.1780585518.git.lukas@wunner.de --- .../crypto/intel/qat/qat_common/adf_accel_devices.h | 5 ----- drivers/dma/idxd/registers.h | 3 --- drivers/pci/p2pdma.c | 10 ++++++++++ include/linux/pci_ids.h | 8 ++++++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h b/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h index 03a4e9690208..cbd1d1eda5a3 100644 --- a/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h +++ b/drivers/crypto/intel/qat/qat_common/adf_accel_devices.h @@ -28,15 +28,10 @@ #define ADF_4XXX_DEVICE_NAME "4xxx" #define ADF_420XX_DEVICE_NAME "420xx" #define ADF_6XXX_DEVICE_NAME "6xxx" -#define PCI_DEVICE_ID_INTEL_QAT_4XXX 0x4940 #define PCI_DEVICE_ID_INTEL_QAT_4XXXIOV 0x4941 -#define PCI_DEVICE_ID_INTEL_QAT_401XX 0x4942 #define PCI_DEVICE_ID_INTEL_QAT_401XXIOV 0x4943 -#define PCI_DEVICE_ID_INTEL_QAT_402XX 0x4944 #define PCI_DEVICE_ID_INTEL_QAT_402XXIOV 0x4945 -#define PCI_DEVICE_ID_INTEL_QAT_420XX 0x4946 #define PCI_DEVICE_ID_INTEL_QAT_420XXIOV 0x4947 -#define PCI_DEVICE_ID_INTEL_QAT_6XXX 0x4948 #define PCI_DEVICE_ID_INTEL_QAT_6XXX_IOV 0x4949 #define ADF_DEVICE_FUSECTL_OFFSET 0x40 diff --git a/drivers/dma/idxd/registers.h b/drivers/dma/idxd/registers.h index f95411363ea9..1dce26d4da83 100644 --- a/drivers/dma/idxd/registers.h +++ b/drivers/dma/idxd/registers.h @@ -10,9 +10,6 @@ #endif /* PCI Config */ -#define PCI_DEVICE_ID_INTEL_DSA_GNRD 0x11fb -#define PCI_DEVICE_ID_INTEL_DSA_DMR 0x1212 -#define PCI_DEVICE_ID_INTEL_IAA_DMR 0x1216 #define PCI_DEVICE_ID_INTEL_IAA_PTL 0xb02d #define PCI_DEVICE_ID_INTEL_IAA_WCL 0xfd2d diff --git a/drivers/pci/p2pdma.c b/drivers/pci/p2pdma.c index adb17a4f6939..b2d5266f8653 100644 --- a/drivers/pci/p2pdma.c +++ b/drivers/pci/p2pdma.c @@ -552,6 +552,16 @@ static const struct pci_p2pdma_whitelist_entry { {PCI_VENDOR_ID_INTEL, 0x2033, 0}, {PCI_VENDOR_ID_INTEL, 0x2020, 0}, {PCI_VENDOR_ID_INTEL, 0x09a2, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_DSA_SPR0, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_IAX_SPR0, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_DSA_GNRD, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_DSA_DMR, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_IAA_DMR, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_4XXX, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_401XX, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_402XX, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_420XX, 0}, + {PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_QAT_6XXX, 0}, /* Google SoCs. */ {PCI_VENDOR_ID_GOOGLE, PCI_ANY_ID, 0}, {} diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 24cb42f66e4b..1c9d40e09107 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2732,6 +2732,9 @@ #define PCI_DEVICE_ID_INTEL_82815_MC 0x1130 #define PCI_DEVICE_ID_INTEL_82815_CGC 0x1132 #define PCI_DEVICE_ID_INTEL_SST_TNG 0x119a +#define PCI_DEVICE_ID_INTEL_DSA_GNRD 0x11fb +#define PCI_DEVICE_ID_INTEL_DSA_DMR 0x1212 +#define PCI_DEVICE_ID_INTEL_IAA_DMR 0x1216 #define PCI_DEVICE_ID_INTEL_82092AA_0 0x1221 #define PCI_DEVICE_ID_INTEL_82437 0x122d #define PCI_DEVICE_ID_INTEL_82371FB_0 0x122e @@ -3052,6 +3055,11 @@ #define PCI_DEVICE_ID_INTEL_5400_FBD1 0x4036 #define PCI_DEVICE_ID_INTEL_HDA_TGL_H 0x43c8 #define PCI_DEVICE_ID_INTEL_HDA_DG1 0x490d +#define PCI_DEVICE_ID_INTEL_QAT_4XXX 0x4940 +#define PCI_DEVICE_ID_INTEL_QAT_401XX 0x4942 +#define PCI_DEVICE_ID_INTEL_QAT_402XX 0x4944 +#define PCI_DEVICE_ID_INTEL_QAT_420XX 0x4946 +#define PCI_DEVICE_ID_INTEL_QAT_6XXX 0x4948 #define PCI_DEVICE_ID_INTEL_HDA_EHL_0 0x4b55 #define PCI_DEVICE_ID_INTEL_HDA_EHL_3 0x4b58 #define PCI_DEVICE_ID_INTEL_HDA_WCL 0x4d28 From 85c1fcfa740d4c737f5575fc7251883e54227a51 Mon Sep 17 00:00:00 2001 From: Sherry Sun Date: Wed, 20 May 2026 16:48:57 +0800 Subject: [PATCH 098/168] PCI: imx6: Integrate new pwrctrl API Integrate the PCI pwrctrl framework into the pci-imx6 driver to provide standardized power management for PCI devices. Legacy regulator handling (vpcie-supply at controller level) is maintained for backward compatibility with existing device trees. New device trees should specify power supplies at the Root Port level to utilize the pwrctrl framework. Signed-off-by: Sherry Sun Signed-off-by: Manivannan Sadhasivam Reviewed-by: Frank Li Link: https://patch.msgid.link/20260520084904.2424253-2-sherry.sun@oss.nxp.com --- drivers/pci/controller/dwc/Kconfig | 1 + drivers/pci/controller/dwc/pci-imx6.c | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/dwc/Kconfig b/drivers/pci/controller/dwc/Kconfig index f2fde13107f2..327b0dc65550 100644 --- a/drivers/pci/controller/dwc/Kconfig +++ b/drivers/pci/controller/dwc/Kconfig @@ -114,6 +114,7 @@ config PCI_IMX6_HOST depends on PCI_MSI select PCIE_DW_HOST select PCI_IMX6 + select PCI_PWRCTRL_GENERIC help Enables support for the PCIe controller in the i.MX SoCs to work in Root Complex mode. The PCI controller on i.MX is based diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index cce78a7a1624..63637fa5f108 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1363,6 +1364,7 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) return ret; } + /* Legacy regulator handling for DT backward compatibility. */ if (imx_pcie->vpcie) { ret = regulator_enable(imx_pcie->vpcie); if (ret) { @@ -1372,10 +1374,22 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) } } + ret = pci_pwrctrl_create_devices(dev); + if (ret) { + dev_err(dev, "failed to create pwrctrl devices\n"); + goto err_reg_disable; + } + + ret = pci_pwrctrl_power_on_devices(dev); + if (ret) { + dev_err(dev, "failed to power on pwrctrl devices\n"); + goto err_pwrctrl_destroy; + } + ret = imx_pcie_clk_enable(imx_pcie); if (ret) { dev_err(dev, "unable to enable pcie clocks: %d\n", ret); - goto err_reg_disable; + goto err_pwrctrl_power_off; } if (pp->bridge && imx_check_flag(imx_pcie, IMX_PCIE_FLAG_HAS_LUT)) { @@ -1437,6 +1451,11 @@ static int imx_pcie_host_init(struct dw_pcie_rp *pp) phy_exit(imx_pcie->phy); err_clk_disable: imx_pcie_clk_disable(imx_pcie); +err_pwrctrl_power_off: + pci_pwrctrl_power_off_devices(dev); +err_pwrctrl_destroy: + if (ret != -EPROBE_DEFER) + pci_pwrctrl_destroy_devices(dev); err_reg_disable: if (imx_pcie->vpcie) regulator_disable(imx_pcie->vpcie); @@ -1455,6 +1474,7 @@ static void imx_pcie_host_exit(struct dw_pcie_rp *pp) } imx_pcie_clk_disable(imx_pcie); + pci_pwrctrl_power_off_devices(pci->dev); if (imx_pcie->vpcie) regulator_disable(imx_pcie->vpcie); } @@ -1966,6 +1986,8 @@ static void imx_pcie_shutdown(struct platform_device *pdev) /* bring down link, so bootloader gets clean state in case of reboot */ imx_pcie_assert_core_reset(imx_pcie); imx_pcie_assert_perst(imx_pcie, true); + pci_pwrctrl_power_off_devices(&pdev->dev); + pci_pwrctrl_destroy_devices(&pdev->dev); } static const struct imx_pcie_drvdata drvdata[] = { From 6ba90ce2069ae923b0ec787aebdf2d786e5d2a58 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 21 May 2026 10:12:56 +0100 Subject: [PATCH 099/168] PCI: rcar-host: Remove unused LIST_HEAD(res) Remove the unused LIST_HEAD(res) declaration from rcar_pcie_hw_enable(). The macro instantiation defines an unused 'struct list_head res' variable, which conflicts with a valid resource loop-local 'struct resource *res' declaration further down in the function, triggering a compiler variable shadowing warning: drivers/pci/controller/pcie-rcar-host.c:357:34: warning: declaration of 'res' shadows a previous local [-Wshadow] 357 | struct resource *res = win->res; Fixes: ce351636c67f75a9 ("PCI: rcar: Add suspend/resume") Signed-off-by: Lad Prabhakar Signed-off-by: Manivannan Sadhasivam Reviewed-by: Geert Uytterhoeven Reviewed-by: Marek Vasut Link: https://patch.msgid.link/20260521091256.15737-1-prabhakar.mahadev-lad.rj@bp.renesas.com --- drivers/pci/controller/pcie-rcar-host.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/controller/pcie-rcar-host.c b/drivers/pci/controller/pcie-rcar-host.c index 213028052aa5..cd9171eebc28 100644 --- a/drivers/pci/controller/pcie-rcar-host.c +++ b/drivers/pci/controller/pcie-rcar-host.c @@ -346,7 +346,6 @@ static void rcar_pcie_hw_enable(struct rcar_pcie_host *host) struct rcar_pcie *pcie = &host->pcie; struct pci_host_bridge *bridge = pci_host_bridge_from_priv(host); struct resource_entry *win; - LIST_HEAD(res); int i = 0; /* Try setting 5 GT/s link speed */ From 6a4f64c3a3ada43e71ef1e06da89beb36bdaeefa Mon Sep 17 00:00:00 2001 From: Jose Ignacio Tornos Martinez Date: Wed, 3 Jun 2026 12:58:53 +0200 Subject: [PATCH 100/168] PCI: Avoid SBR for Qualcomm WCN6855/WCN7850 WiFi, SDX62/SDX65 modems Some Qualcomm PCIe devices (WCN6855/WCN7850 WiFi cards, SDX62/SDX65 modems) do not properly support Secondary Bus Reset (SBR). Testing confirms this is device-specific, not deployment-specific: MediaTek MT7925e successfully uses bus reset through the same passive M.2-to-PCIe adapters where Qualcomm devices fail, proving PERST# is properly wired through the adapters. Prevent use of Secondary Bus Reset for these devices. Signed-off-by: Jose Ignacio Tornos Martinez Signed-off-by: Bjorn Helgaas Link: https://lore.kernel.org/all/20260609163649.319755-4-jtornosm@redhat.com --- drivers/pci/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index e49136ac5dbf..431c021d7414 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3790,6 +3790,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x003c, quirk_no_bus_reset); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0033, quirk_no_bus_reset); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x0034, quirk_no_bus_reset); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ATHEROS, 0x003e, quirk_no_bus_reset); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x1103, quirk_no_bus_reset); /* WCN6855 */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x1107, quirk_no_bus_reset); /* WCN7850 */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_QCOM, 0x0308, quirk_no_bus_reset); /* SDX62/SDX65 */ /* * Root port on some Cavium CN8xxx chips do not successfully complete a bus From ebc1d9906894703286d12306a6f242d90cfb49e8 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Thu, 21 May 2026 17:19:49 +0000 Subject: [PATCH 101/168] PCI: mediatek: Use actual physical address instead of virt_to_phys() The driver previously used virt_to_phys() on the ioremapped register base (port->base) to compute the MSI message address. Using virt_to_phys() on an IO mapped address is incorrect because it expects a kernel virtual address. To fix it, store the physical start of the I/O register region in mtk_pcie_port->phys_base and use it to build the MSI address. This replaces the incorrect virt_to_phys() usage and ensures MSI addresses are generated correctly. Fixes: 43e6409db64d ("PCI: mediatek: Add MSI support for MT2712 and MT7622") Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Tested-by: Caleb James DeLisle Link: https://patch.msgid.link/20260521171951.1495781-2-cjd@cjdns.fr --- drivers/pci/controller/pcie-mediatek.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index f34d91e495bc..1bb8839c3cb0 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -176,6 +176,7 @@ struct mtk_pcie_soc { /** * struct mtk_pcie_port - PCIe port information * @base: IO mapped register base + * @phys_base: Physical address of the I/O register base region * @list: port list * @pcie: pointer to PCIe host info * @reset: pointer to port reset control @@ -197,6 +198,7 @@ struct mtk_pcie_soc { */ struct mtk_pcie_port { void __iomem *base; + phys_addr_t phys_base; struct list_head list; struct mtk_pcie *pcie; struct reset_control *reset; @@ -406,7 +408,7 @@ static void mtk_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) phys_addr_t addr; /* MT2712/MT7622 only support 32-bit MSI addresses */ - addr = virt_to_phys(port->base + PCIE_MSI_VECTOR); + addr = port->phys_base + PCIE_MSI_VECTOR; msg->address_hi = 0; msg->address_lo = lower_32_bits(addr); @@ -521,7 +523,7 @@ static void mtk_pcie_enable_msi(struct mtk_pcie_port *port) u32 val; phys_addr_t msg_addr; - msg_addr = virt_to_phys(port->base + PCIE_MSI_VECTOR); + msg_addr = port->phys_base + PCIE_MSI_VECTOR; val = lower_32_bits(msg_addr); writel(val, port->base + PCIE_IMSI_ADDR); @@ -962,6 +964,7 @@ static int mtk_pcie_parse_port(struct mtk_pcie *pcie, struct mtk_pcie_port *port; struct device *dev = pcie->dev; struct platform_device *pdev = to_platform_device(dev); + struct resource *res; char name[20]; int err; @@ -970,7 +973,14 @@ static int mtk_pcie_parse_port(struct mtk_pcie *pcie, return -ENOMEM; snprintf(name, sizeof(name), "port%d", slot); - port->base = devm_platform_ioremap_resource_byname(pdev, name); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); + if (!res) { + dev_err(dev, "failed to get port%d base\n", slot); + return -EINVAL; + } + + port->phys_base = res->start; + port->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(port->base)) { dev_err(dev, "failed to map port%d base\n", slot); return PTR_ERR(port->base); From 843044971a5167087a9484f8f6eec81da30f2a71 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Thu, 21 May 2026 17:19:50 +0000 Subject: [PATCH 102/168] dt-bindings: PCI: mediatek: Add support for EcoNet EN7528 Introduce EcoNet EN7528 SoC compatible in MediaTek PCIe controller binding. EcoNet PCIe controller has the same configuration model as Mediatek v2 but is initialized more similarly to an MT7621 PCIe. Signed-off-by: Caleb James DeLisle Signed-off-by: Manivannan Sadhasivam Acked-by: Conor Dooley Link: https://patch.msgid.link/20260521171951.1495781-3-cjd@cjdns.fr --- .../bindings/pci/mediatek-pcie.yaml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/mediatek-pcie.yaml b/Documentation/devicetree/bindings/pci/mediatek-pcie.yaml index 0b8c78ec4f91..c009a7a52bc6 100644 --- a/Documentation/devicetree/bindings/pci/mediatek-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/mediatek-pcie.yaml @@ -14,6 +14,7 @@ properties: oneOf: - enum: - airoha,an7583-pcie + - econet,en7528-pcie - mediatek,mt2712-pcie - mediatek,mt7622-pcie - mediatek,mt7629-pcie @@ -226,6 +227,31 @@ allOf: mediatek,pbus-csr: false + - if: + properties: + compatible: + contains: + const: econet,en7528-pcie + then: + properties: + clocks: + maxItems: 1 + + clock-names: + maxItems: 1 + + resets: false + + reset-names: false + + power-domains: false + + mediatek,pbus-csr: false + + required: + - phys + - phy-names + unevaluatedProperties: false examples: From 26b67fa10ef84ea667942491b50e6261a45f098d Mon Sep 17 00:00:00 2001 From: Shuvam Pandey Date: Mon, 18 May 2026 22:44:17 +0545 Subject: [PATCH 103/168] PCI: dwc: Avoid dwc_pcie_rasdes_debugfs_deinit() NULL dereference when no RAS DES capability dwc_pcie_rasdes_debugfs_init() returns success when the controller has no RAS DES capability, leaving pci->debugfs->rasdes_info unset. The common debugfs teardown path still calls dwc_pcie_rasdes_debugfs_deinit(), which dereferences rasdes_info unconditionally. Return early when no RAS DES state was allocated. In that case no RAS DES mutex was initialized, so there is nothing to destroy. Fixes: 4fbfa17f9a07 ("PCI: dwc: Add debugfs based Silicon Debug support for DWC") Signed-off-by: Shuvam Pandey [mani: reworded subject] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/0f97352506d8d813f70f441de4d63fcd5b7d1c3e.1779123847.git.shuvampandey1@gmail.com --- drivers/pci/controller/dwc/pcie-designware-debugfs.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-debugfs.c b/drivers/pci/controller/dwc/pcie-designware-debugfs.c index ddbce69b2e9f..c24cbc4677a3 100644 --- a/drivers/pci/controller/dwc/pcie-designware-debugfs.c +++ b/drivers/pci/controller/dwc/pcie-designware-debugfs.c @@ -550,6 +550,9 @@ static void dwc_pcie_rasdes_debugfs_deinit(struct dw_pcie *pci) { struct dwc_pcie_rasdes_info *rinfo = pci->debugfs->rasdes_info; + if (!rinfo) + return; + mutex_destroy(&rinfo->reg_event_lock); } From 7cb033507068936f9da3c59e5c31a54e4e8dafa6 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 25 May 2026 21:40:16 -0700 Subject: [PATCH 104/168] PCI: mvebu: Use fixed-width interrupt masks to avoid truncation in 64-bit builds Use u32-typed BIT and GENMASK helpers for PCIe interrupt register masks. This keeps inverted masks in the same width as the registers and avoids truncation warnings on 64-bit compile-test builds. Fixes below and similar warnings: drivers/pci/controller/pci-mvebu.c:316:21: error: implicit conversion from 'unsigned long' to 'u32' (aka 'unsigned int') changes value from 18446744069414584320 to 0 [-Werror,-Wconstant-conversion] mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF); Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260526044016.1025613-1-rosenp@gmail.com --- drivers/pci/controller/pci-mvebu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c index a72aa57591c0..07ed45bab5d0 100644 --- a/drivers/pci/controller/pci-mvebu.c +++ b/drivers/pci/controller/pci-mvebu.c @@ -57,9 +57,9 @@ #define PCIE_CONF_DATA_OFF 0x18fc #define PCIE_INT_CAUSE_OFF 0x1900 #define PCIE_INT_UNMASK_OFF 0x1910 -#define PCIE_INT_INTX(i) BIT(24+i) -#define PCIE_INT_PM_PME BIT(28) -#define PCIE_INT_ALL_MASK GENMASK(31, 0) +#define PCIE_INT_INTX(i) BIT_U32(24 + (i)) +#define PCIE_INT_PM_PME BIT_U32(28) +#define PCIE_INT_ALL_MASK GENMASK_U32(31, 0) #define PCIE_CTRL_OFF 0x1a00 #define PCIE_CTRL_X1_MODE 0x0001 #define PCIE_CTRL_RC_MODE BIT(1) From 3c2e6cc6affa8acdb99a580be1f8f297edf54204 Mon Sep 17 00:00:00 2001 From: Mark Tomlinson Date: Thu, 30 Apr 2026 14:16:28 +1200 Subject: [PATCH 105/168] PCI: iproc: Restore .map_irq() for the platform bus driver Commit b64aa11eb2dd ("PCI: Set bridge map_irq and swizzle_irq to default functions") moved the assignment of default .map_irq() callback to devm_of_pci_bridge_init() and removed the initialization of 'iproc_pcie::map_irq' in platform bus driver. This led to the callback getting assigned the NULL pointer for platform bus driver, thereby breaking the INTx functionality, since 'iproc_pcie::map_irq' overrides the 'pci_host_bridge::map_irq' callback in iproc_pcie_setup(). This issue only affected the iproc platform bus driver as this driver relies on the default callback for non-PAXC controllers. iproc-brcm driver was already providing the custom mapping function, so it was unaffected. Restore the original (and intended) behaviour to use the default map_irq function by removing the local 'iproc_pcie::map_irq' pointer and directly assigning the 'pci_host_bridge::map_irq' callback in iproc-bcma driver. This ensures that the default 'map_irq' callback is used for platform bus driver and only iproc-brcm driver overrides it with a custom one. Fixes: b64aa11eb2dd ("PCI: Set bridge map_irq and swizzle_irq to default functions") Signed-off-by: Mark Tomlinson [mani: commit log] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Acked-by: Ray Jui Link: https://patch.msgid.link/20260430021628.1343154-1-mark.tomlinson@alliedtelesis.co.nz --- drivers/pci/controller/pcie-iproc-bcma.c | 2 +- drivers/pci/controller/pcie-iproc-platform.c | 2 +- drivers/pci/controller/pcie-iproc.c | 1 - drivers/pci/controller/pcie-iproc.h | 2 -- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/pcie-iproc-bcma.c b/drivers/pci/controller/pcie-iproc-bcma.c index 99a99900444d..593418c2bc3a 100644 --- a/drivers/pci/controller/pcie-iproc-bcma.c +++ b/drivers/pci/controller/pcie-iproc-bcma.c @@ -64,7 +64,7 @@ static int iproc_bcma_pcie_probe(struct bcma_device *bdev) if (ret) return ret; - pcie->map_irq = iproc_bcma_pcie_map_irq; + bridge->map_irq = iproc_bcma_pcie_map_irq; bcma_set_drvdata(bdev, pcie); diff --git a/drivers/pci/controller/pcie-iproc-platform.c b/drivers/pci/controller/pcie-iproc-platform.c index 0cb78c583c7e..4c9a0c4bb923 100644 --- a/drivers/pci/controller/pcie-iproc-platform.c +++ b/drivers/pci/controller/pcie-iproc-platform.c @@ -98,7 +98,7 @@ static int iproc_pltfm_pcie_probe(struct platform_device *pdev) switch (pcie->type) { case IPROC_PCIE_PAXC: case IPROC_PCIE_PAXC_V2: - pcie->map_irq = NULL; + bridge->map_irq = NULL; break; default: break; diff --git a/drivers/pci/controller/pcie-iproc.c b/drivers/pci/controller/pcie-iproc.c index ccf71993ea35..c22d0aecaaac 100644 --- a/drivers/pci/controller/pcie-iproc.c +++ b/drivers/pci/controller/pcie-iproc.c @@ -1502,7 +1502,6 @@ int iproc_pcie_setup(struct iproc_pcie *pcie, struct list_head *res) host->ops = &iproc_pcie_ops; host->sysdata = pcie; - host->map_irq = pcie->map_irq; ret = pci_host_probe(host); if (ret < 0) { diff --git a/drivers/pci/controller/pcie-iproc.h b/drivers/pci/controller/pcie-iproc.h index 969ded03b8c2..c4443f236ca3 100644 --- a/drivers/pci/controller/pcie-iproc.h +++ b/drivers/pci/controller/pcie-iproc.h @@ -61,7 +61,6 @@ struct iproc_msi; * @base_addr: PCIe host controller register base physical address * @mem: host bridge memory window resource * @phy: optional PHY device that controls the Serdes - * @map_irq: function callback to map interrupts * @ep_is_internal: indicates an internal emulated endpoint device is connected * @iproc_cfg_read: indicates the iProc config read function should be used * @rej_unconfig_pf: indicates the root complex needs to detect and reject @@ -91,7 +90,6 @@ struct iproc_pcie { phys_addr_t base_addr; struct resource mem; struct phy *phy; - int (*map_irq)(const struct pci_dev *, u8, u8); bool ep_is_internal; bool iproc_cfg_read; bool rej_unconfig_pf; From a8759c8ac48c0419f5899e95a6ffc611b07c965b Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:16 +0800 Subject: [PATCH 106/168] PCI: altera: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-4-18255117159@163.com --- drivers/pci/controller/pcie-altera.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/pcie-altera.c b/drivers/pci/controller/pcie-altera.c index 3dbb7adc421c..7e1db267ae34 100644 --- a/drivers/pci/controller/pcie-altera.c +++ b/drivers/pci/controller/pcie-altera.c @@ -1045,8 +1045,10 @@ static void altera_pcie_remove(struct platform_device *pdev) struct altera_pcie *pcie = platform_get_drvdata(pdev); struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie); + pci_lock_rescan_remove(); pci_stop_root_bus(bridge->bus); pci_remove_root_bus(bridge->bus); + pci_unlock_rescan_remove(); altera_pcie_irq_teardown(pcie); } From 20b7aba83c0eac13bf9d46b0fa7575df5765f205 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:17 +0800 Subject: [PATCH 107/168] PCI: brcmstb: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-5-18255117159@163.com --- drivers/pci/controller/pcie-brcmstb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c index 714bcab97b60..7ca27f0756c9 100644 --- a/drivers/pci/controller/pcie-brcmstb.c +++ b/drivers/pci/controller/pcie-brcmstb.c @@ -1894,8 +1894,10 @@ static void brcm_pcie_remove(struct platform_device *pdev) struct brcm_pcie *pcie = platform_get_drvdata(pdev); struct pci_host_bridge *bridge = pci_host_bridge_from_priv(pcie); + pci_lock_rescan_remove(); pci_stop_root_bus(bridge->bus); pci_remove_root_bus(bridge->bus); + pci_unlock_rescan_remove(); if (pcie->cfg->has_err_report) brcm_unregister_die_notifiers(pcie); From 713331969ce89489c84af917058df6d9910cff97 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:14 +0800 Subject: [PATCH 108/168] PCI: cadence: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-2-18255117159@163.com --- drivers/pci/controller/cadence/pcie-cadence-host.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/cadence/pcie-cadence-host.c b/drivers/pci/controller/cadence/pcie-cadence-host.c index 0bc9e6e90e0e..87fd8afa301b 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-host.c +++ b/drivers/pci/controller/cadence/pcie-cadence-host.c @@ -365,8 +365,10 @@ void cdns_pcie_host_disable(struct cdns_pcie_rc *rc) struct pci_host_bridge *bridge; bridge = pci_host_bridge_from_priv(rc); + pci_lock_rescan_remove(); pci_stop_root_bus(bridge->bus); pci_remove_root_bus(bridge->bus); + pci_unlock_rescan_remove(); cdns_pcie_host_deinit(rc); cdns_pcie_host_link_disable(rc); From 26335696498ab502e907a556e97c7039bc80a87e Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:15 +0800 Subject: [PATCH 109/168] PCI: dwc: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-3-18255117159@163.com --- drivers/pci/controller/dwc/pcie-designware-host.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c index c9517a348836..f856ac2fb15d 100644 --- a/drivers/pci/controller/dwc/pcie-designware-host.c +++ b/drivers/pci/controller/dwc/pcie-designware-host.c @@ -703,8 +703,10 @@ void dw_pcie_host_deinit(struct dw_pcie_rp *pp) dwc_pcie_debugfs_deinit(pci); + pci_lock_rescan_remove(); pci_stop_root_bus(pp->bridge->bus); pci_remove_root_bus(pp->bridge->bus); + pci_unlock_rescan_remove(); dw_pcie_stop_link(pci); From a6a64e150f12ad5391e0a0d60f6a3d119b06ce50 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:18 +0800 Subject: [PATCH 110/168] PCI: iproc: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-6-18255117159@163.com --- drivers/pci/controller/pcie-iproc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/pcie-iproc.c b/drivers/pci/controller/pcie-iproc.c index ccf71993ea35..c8f0a87cf28a 100644 --- a/drivers/pci/controller/pcie-iproc.c +++ b/drivers/pci/controller/pcie-iproc.c @@ -1529,8 +1529,10 @@ void iproc_pcie_remove(struct iproc_pcie *pcie) { struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie); + pci_lock_rescan_remove(); pci_stop_root_bus(host->bus); pci_remove_root_bus(host->bus); + pci_unlock_rescan_remove(); iproc_pcie_msi_disable(pcie); From a29812a55da8d0dbeb071b26ac428c338e3fc389 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:19 +0800 Subject: [PATCH 111/168] PCI: mediatek: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-7-18255117159@163.com --- drivers/pci/controller/pcie-mediatek.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/pcie-mediatek.c b/drivers/pci/controller/pcie-mediatek.c index 75722524fe74..2fedb6d2939d 100644 --- a/drivers/pci/controller/pcie-mediatek.c +++ b/drivers/pci/controller/pcie-mediatek.c @@ -1172,8 +1172,10 @@ static void mtk_pcie_remove(struct platform_device *pdev) struct mtk_pcie *pcie = platform_get_drvdata(pdev); struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie); + pci_lock_rescan_remove(); pci_stop_root_bus(host->bus); pci_remove_root_bus(host->bus); + pci_unlock_rescan_remove(); mtk_pcie_free_resources(pcie); mtk_pcie_irq_teardown(pcie); From 4e4f9745f016c1631d00a4035b06f6e75d449e01 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:22 +0800 Subject: [PATCH 112/168] PCI: plda: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-10-18255117159@163.com --- drivers/pci/controller/plda/pcie-plda-host.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/plda/pcie-plda-host.c b/drivers/pci/controller/plda/pcie-plda-host.c index 3c2f68383010..f9a34f323ad8 100644 --- a/drivers/pci/controller/plda/pcie-plda-host.c +++ b/drivers/pci/controller/plda/pcie-plda-host.c @@ -640,8 +640,10 @@ EXPORT_SYMBOL_GPL(plda_pcie_host_init); void plda_pcie_host_deinit(struct plda_pcie_rp *port) { + pci_lock_rescan_remove(); pci_stop_root_bus(port->bridge->bus); pci_remove_root_bus(port->bridge->bus); + pci_unlock_rescan_remove(); plda_pcie_irq_domain_deinit(port); From 0bd9611587bb494c33566d825fe34b2705e4b167 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Fri, 22 May 2026 00:18:20 +0800 Subject: [PATCH 113/168] PCI: rockchip: Protect root bus removal with rescan lock Hold the pci_rescan_remove_lock lock while stopping and removing a root bus to avoid racing with concurrent rescan or hotplug operations triggered via sysfs. Such races may lead to use-after-free issues or system crashes. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260521161822.132996-8-18255117159@163.com --- drivers/pci/controller/pcie-rockchip-host.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/pci/controller/pcie-rockchip-host.c b/drivers/pci/controller/pcie-rockchip-host.c index ee1822ca01db..d203c4876d30 100644 --- a/drivers/pci/controller/pcie-rockchip-host.c +++ b/drivers/pci/controller/pcie-rockchip-host.c @@ -1012,8 +1012,10 @@ static void rockchip_pcie_remove(struct platform_device *pdev) struct rockchip_pcie *rockchip = dev_get_drvdata(dev); struct pci_host_bridge *bridge = pci_host_bridge_from_priv(rockchip); + pci_lock_rescan_remove(); pci_stop_root_bus(bridge->bus); pci_remove_root_bus(bridge->bus); + pci_unlock_rescan_remove(); irq_domain_remove(rockchip->irq_domain); rockchip_pcie_deinit_phys(rockchip); From fda8749ba73638f5bbca3ffb39bc6861eb3b23fa Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Tue, 14 Apr 2026 13:47:30 +0530 Subject: [PATCH 114/168] PCI: host-common: Request bus reassignment when not probe-only pci_host_common_init() is used by several generic ECAM host drivers. After PCI core changes around pci_flags and preserve_config, these hosts no longer opted into full bus number reassignment the way they did before, which broke enumeration of devices on a Marvell CN106XX board. When PCI_PROBE_ONLY is not set, add PCI_REASSIGN_ALL_BUS so pci_scan_bridge_extend() takes the reassignment path: bus numbers can be assigned from firmware EA data (e.g. pci_ea_fixed_busnrs()). Skip the flag in probe-only mode so existing assignments are not overridden. Fixes: 7246a4520b4b ("PCI: Use preserve_config in place of pci_flags") Closes: https://lore.kernel.org/all/abkqm_LCd9zAM8cW@rkannoth-OptiPlex-7090/ Signed-off-by: Ratheesh Kannoth [mani: added stable tag] Signed-off-by: Manivannan Sadhasivam [bhelgaas: add problem report link] Signed-off-by: Bjorn Helgaas Cc: stable@vger.kernel.org Cc: Vidya Sagar Link: https://patch.msgid.link/20260414081730.3864372-1-rkannoth@marvell.com --- drivers/pci/controller/pci-host-common.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c index d6258c1cffe5..99952fb7189b 100644 --- a/drivers/pci/controller/pci-host-common.c +++ b/drivers/pci/controller/pci-host-common.c @@ -68,6 +68,10 @@ int pci_host_common_init(struct platform_device *pdev, if (IS_ERR(cfg)) return PTR_ERR(cfg); + /* Do not reassign bus numbers if probe only */ + if (!pci_has_flag(PCI_PROBE_ONLY)) + pci_add_flags(PCI_REASSIGN_ALL_BUS); + bridge->sysdata = cfg; bridge->ops = (struct pci_ops *)&ops->pci_ops; bridge->enable_device = ops->enable_device; From 854bd081c7680029d7886689f6bef8f740625fde Mon Sep 17 00:00:00 2001 From: Carlos Bilbao Date: Fri, 10 Apr 2026 16:02:59 -0700 Subject: [PATCH 115/168] misc: pci_endpoint_test: Validate BAR index in doorbell test pci_endpoint_test_doorbell() reads the BAR number directly from an endpoint test register and uses it as an index into test->bar[]. Add a defensive bounds check before the dereference: positive values >= PCI_STD_NUM_BARS are out of range, and NO_BAR (-1) as a negative signed value would slip past an upper-bound-only check. Signed-off-by: Carlos Bilbao (Lambda) [mani: changed errno to -ERANGE] Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260410230300.135631-2-carlos.bilbao@kernel.org --- drivers/misc/pci_endpoint_test.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index dbd017cabbb9..64ac7c7c90af 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -1108,6 +1108,11 @@ static int pci_endpoint_test_doorbell(struct pci_endpoint_test *test) pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0); bar = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_BAR); + if (bar < BAR_0 || bar >= PCI_STD_NUM_BARS) { + dev_err(dev, "BAR %d reported by endpoint out of range [0, %u]\n", + bar, PCI_STD_NUM_BARS - 1); + return -ERANGE; + } writel(data, test->bar[bar] + addr); From 7b6e7e975705159f0ce7915811cf10f56133fdda Mon Sep 17 00:00:00 2001 From: Carlos Bilbao Date: Fri, 10 Apr 2026 16:03:00 -0700 Subject: [PATCH 116/168] misc: pci_endpoint_test: Remove dead BAR read before doorbell trigger The assignment before the writel sequence is dead code (bar is unconditionally overwritten by the re-read immediately after) so remove the assignment entirely. Note that the DB_BAR register is a plain value written by the endpoint firmware; reading it carries no side effect. Signed-off-by: Carlos Bilbao (Lambda) Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Koichiro Den Link: https://patch.msgid.link/20260410230300.135631-3-carlos.bilbao@kernel.org --- drivers/misc/pci_endpoint_test.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index 64ac7c7c90af..3635741c3e7a 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -1100,7 +1100,6 @@ static int pci_endpoint_test_doorbell(struct pci_endpoint_test *test) data = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_DATA); addr = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_OFFSET); - bar = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_DB_BAR); pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type); pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1); From fcba26efe5efc7441f5505f4ccc69791214b40be Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 4 Mar 2026 17:30:27 +0900 Subject: [PATCH 117/168] NTB: epf: Fix request_irq() unwind in ntb_epf_init_isr() ntb_epf_init_isr() requests multiple MSI/MSI-X vectors in a loop. If request_irq() fails part-way through, it jumps straight to pci_free_irq_vectors() without freeing already requested IRQs. Fix the error path by freeing any successfully requested IRQs before releasing the vectors. Fixes: 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dave Jiang Cc: stable@vger.kernel.org # v5.12+ Link: https://patch.msgid.link/20260304083028.1391068-2-den@valinux.co.jp --- drivers/ntb/hw/epf/ntb_hw_epf.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index d3ecf25a5162..5a35f341f821 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -355,7 +355,7 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max) 0, "ntb_epf", ndev); if (ret) { dev_err(dev, "Failed to request irq\n"); - goto err_request_irq; + goto err_free_irq; } } @@ -365,16 +365,14 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max) argument | irq); if (ret) { dev_err(dev, "Failed to configure doorbell\n"); - goto err_configure_db; + goto err_free_irq; } return 0; -err_configure_db: - for (i = 0; i < ndev->db_count + 1; i++) +err_free_irq: + while (i--) free_irq(pci_irq_vector(pdev, i), ndev); - -err_request_irq: pci_free_irq_vectors(pdev); return ret; From 4dcddc1c794d1c65eda68f1f8dd04a0fecc0870f Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 4 Mar 2026 17:30:28 +0900 Subject: [PATCH 118/168] NTB: epf: Avoid calling pci_irq_vector() from hardirq context ntb_epf_vec_isr() calls pci_irq_vector() in hardirq context to derive the vector number. pci_irq_vector() calls msi_get_virq() that takes a mutex and can therefore trigger "scheduling while atomic" splats: BUG: scheduling while atomic: kworker/u33:0/55/0x00010001 ... Call trace: ... schedule+0x38/0x110 schedule_preempt_disabled+0x28/0x50 __mutex_lock.constprop.0+0x848/0x908 __mutex_lock_slowpath+0x18/0x30 mutex_lock+0x4c/0x60 msi_domain_get_virq+0xe8/0x138 pci_irq_vector+0x2c/0x60 ntb_epf_vec_isr+0x28/0x120 [ntb_hw_epf] __handle_irq_event_percpu+0x70/0x3a8 handle_irq_event+0x48/0x100 handle_edge_irq+0x100/0x1c8 ... Cache the Linux IRQ number for vector 0 when vectors are allocated and use it as a base in the ISR. Running the ISR in a threaded IRQ handler would also avoid the problem, but that would be unnecessary here. Fixes: 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dave Jiang Cc: stable@vger.kernel.org # v5.12+ Link: https://patch.msgid.link/20260304083028.1391068-3-den@valinux.co.jp --- drivers/ntb/hw/epf/ntb_hw_epf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index 5a35f341f821..8925c688930c 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -92,6 +92,7 @@ struct ntb_epf_dev { int db_val; u64 db_valid_mask; + int irq_base; }; #define ntb_ndev(__ntb) container_of(__ntb, struct ntb_epf_dev, ntb) @@ -318,7 +319,7 @@ static irqreturn_t ntb_epf_vec_isr(int irq, void *dev) struct ntb_epf_dev *ndev = dev; int irq_no; - irq_no = irq - pci_irq_vector(ndev->ntb.pdev, 0); + irq_no = irq - ndev->irq_base; ndev->db_val = irq_no + 1; if (irq_no == 0) @@ -350,6 +351,7 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max) argument &= ~MSIX_ENABLE; } + ndev->irq_base = pci_irq_vector(pdev, 0); for (i = 0; i < irq; i++) { ret = request_irq(pci_irq_vector(pdev, i), ntb_epf_vec_isr, 0, "ntb_epf", ndev); From 33bd1ea748bc897c4d13437284e08c658e8d1340 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 7 Apr 2026 18:14:20 +0530 Subject: [PATCH 119/168] PCI: endpoint: pci-epf-vntb: Add check to detect 'db_count' value of 0 epf_ntb->db_count value should be within 1 to MAX_DB_COUNT. Current code only checks for the upper bound, while the lower bound is unchecked. This can cause a lot of issues in the driver if the user passes 'db_count' as 0. Add a check for 0 also. While at it, remove the redundant 'db_count' assignment. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Koichiro Den Reviewed-by: Frank Li Link: https://patch.msgid.link/20260407124421.282766-2-mani@kernel.org --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index b493a300da4d..d59870fd3430 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -488,7 +488,6 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) { const struct pci_epc_features *epc_features; struct device *dev; - u32 db_count; int ret; dev = &ntb->epf->dev; @@ -500,14 +499,12 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) return -EINVAL; } - db_count = ntb->db_count; - if (db_count > MAX_DB_COUNT) { - dev_err(dev, "DB count cannot be more than %d\n", MAX_DB_COUNT); + if (!ntb->db_count || ntb->db_count > MAX_DB_COUNT) { + dev_err(dev, "DB count %d out of range (1 - %d)\n", + ntb->db_count, MAX_DB_COUNT); return -EINVAL; } - ntb->db_count = db_count; - if (epc_features->msi_capable) { ret = pci_epc_set_msi(ntb->epf->epc, ntb->epf->func_no, From d1db6d7c2485ee475a04d8354fbb5b26ea10d978 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Tue, 7 Apr 2026 18:14:21 +0530 Subject: [PATCH 120/168] PCI: endpoint: pci-epf-ntb: Add check to detect 'db_count' value of 0 epf_ntb->db_count value should be within 1 to MAX_DB_COUNT. Current code only checks for the upper bound, while the lower bound is unchecked. This can cause a lot of issues in the driver if the user passes 'db_count' as 0. Add a check for 0 also. While at it, remove the redundant 'db_count' variable from epf_ntb_configure_interrupt(). Fixes: 8b821cf76150 ("PCI: endpoint: Add EP function driver to provide NTB functionality") Signed-off-by: Manivannan Sadhasivam Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260407124421.282766-3-mani@kernel.org --- drivers/pci/endpoint/functions/pci-epf-ntb.c | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-ntb.c b/drivers/pci/endpoint/functions/pci-epf-ntb.c index 2bdcc35b652c..5314aca2188a 100644 --- a/drivers/pci/endpoint/functions/pci-epf-ntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-ntb.c @@ -559,12 +559,15 @@ static int epf_ntb_configure_db(struct epf_ntb *ntb, struct pci_epc *epc; int ret; - if (db_count > MAX_DB_COUNT) - return -EINVAL; - ntb_epc = ntb->epc[type]; epc = ntb_epc->epc; + if (!db_count || db_count > MAX_DB_COUNT) { + dev_err(&epc->dev, "DB count %d out of range (1 - %d)\n", + db_count, MAX_DB_COUNT); + return -EINVAL; + } + if (msix) ret = epf_ntb_configure_msix(ntb, type, db_count); else @@ -1278,7 +1281,6 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb, u8 func_no, vfunc_no; struct pci_epc *epc; struct device *dev; - u32 db_count; int ret; ntb_epc = ntb->epc[type]; @@ -1296,17 +1298,16 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb, func_no = ntb_epc->func_no; vfunc_no = ntb_epc->vfunc_no; - db_count = ntb->db_count; - if (db_count > MAX_DB_COUNT) { - dev_err(dev, "DB count cannot be more than %d\n", MAX_DB_COUNT); + if (!ntb->db_count || ntb->db_count > MAX_DB_COUNT) { + dev_err(dev, "DB count %d out of range (1 - %d)\n", + ntb->db_count, MAX_DB_COUNT); return -EINVAL; } - ntb->db_count = db_count; epc = ntb_epc->epc; if (msi_capable) { - ret = pci_epc_set_msi(epc, func_no, vfunc_no, db_count); + ret = pci_epc_set_msi(epc, func_no, vfunc_no, ntb->db_count); if (ret) { dev_err(dev, "%s intf: MSI configuration failed\n", pci_epc_interface_string(type)); @@ -1315,7 +1316,7 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb, } if (msix_capable) { - ret = pci_epc_set_msix(epc, func_no, vfunc_no, db_count, + ret = pci_epc_set_msix(epc, func_no, vfunc_no, ntb->db_count, ntb_epc->msix_bar, ntb_epc->msix_table_offset); if (ret) { From 6a7db4fcc6c23b1d25e676400d764bdcb7dc1ccb Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:12 +0900 Subject: [PATCH 121/168] PCI: endpoint: pci-epf-vntb: Document legacy MSI doorbell offset vntb_epf_peer_db_set() raises an MSI interrupt to notify the RC side of a doorbell event. pci_epc_raise_irq(..., PCI_IRQ_MSI, interrupt_num) takes a 1-based MSI interrupt number. The ntb_hw_epf driver reserves MSI #1 for link events, so doorbells would naturally start at MSI #2 (doorbell bit 0 -> MSI #2). However, pci-epf-vntb has historically applied an extra offset and mapped doorbell bit 0 to MSI #3. This matches the legacy behavior of ntb_hw_epf and has been preserved since commit e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP"). This offset has not surfaced as a functional issue because: - ntb_hw_epf typically allocates enough MSI vectors, so the off-by-one still hits a valid MSI vector, and - ntb_hw_epf does not implement .db_vector_count()/.db_vector_mask(), so client drivers such as ntb_transport effectively ignore the vector number and schedule all QPs. Correcting the MSI number would break interoperability with peers running older kernels. Document the legacy offset to avoid confusion when enabling per-db-vector handling. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-2-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index d59870fd3430..668d25abc7f2 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1419,6 +1419,25 @@ static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits) func_no = ntb->epf->func_no; vfunc_no = ntb->epf->vfunc_no; + /* + * pci_epc_raise_irq() for MSI expects a 1-based interrupt number. + * ffs() returns a 1-based index (bit 0 -> 1). interrupt_num has already + * been computed as ffs(db_bits) + 1 above. Adding one more +1 when + * calling pci_epc_raise_irq() therefore results in: + * + * doorbell bit 0 -> MSI #3 + * + * Legacy mapping (kept for compatibility): + * + * MSI #1 : link event (reserved) + * MSI #2 : unused (historical offset) + * MSI #3 : doorbell bit 0 (DB#0) + * MSI #4 : doorbell bit 1 (DB#1) + * ... + * + * Do not change this mapping to avoid breaking interoperability with + * older peers. + */ ret = pci_epc_raise_irq(ntb->epf->epc, func_no, vfunc_no, PCI_IRQ_MSI, interrupt_num + 1); if (ret) From 18355c1e986582aaff2c488b1a2ce79bac8f3cf9 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:13 +0900 Subject: [PATCH 122/168] PCI: endpoint: pci-epf-vntb: Defer pci_epc_raise_irq() out of atomic context The NTB .peer_db_set() callback may be invoked from atomic context. pci-epf-vntb currently calls pci_epc_raise_irq() directly, but pci_epc_raise_irq() may sleep (it takes epc->lock). Avoid sleeping in atomic context by coalescing doorbell bits into an atomic64 pending mask and raising MSIs from a work item. Limit the amount of work per run to avoid monopolizing the workqueue under a doorbell storm. Clear stale pending bits before enabling the work item and after disabling it during cleanup. Also mask requested doorbells against the currently valid doorbell mask before queueing work, and iterate the pending u64 with __ffs64() so high doorbell bits are handled correctly. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-3-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 112 +++++++++++++----- 1 file changed, 85 insertions(+), 27 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 668d25abc7f2..cc0b356973f3 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -37,6 +37,7 @@ */ #include +#include #include #include #include @@ -69,6 +70,9 @@ static struct workqueue_struct *kpcintb_workqueue; #define MAX_DB_COUNT 32 #define MAX_MW 4 +/* Limit per-work execution to avoid monopolizing kworker on doorbell storms. */ +#define VNTB_PEER_DB_WORK_BUDGET 5 + enum epf_ntb_bar { BAR_CONFIG, BAR_DB, @@ -129,6 +133,8 @@ struct epf_ntb { u32 spad_count; u64 mws_size[MAX_MW]; atomic64_t db; + atomic64_t peer_db_pending; + struct work_struct peer_db_work; u32 vbus_number; u16 vntb_pid; u16 vntb_vid; @@ -972,6 +978,9 @@ static int epf_ntb_epc_init(struct epf_ntb *ntb) INIT_DELAYED_WORK(&ntb->cmd_handler, epf_ntb_cmd_handler); queue_work(kpcintb_workqueue, &ntb->cmd_handler.work); + atomic64_set(&ntb->peer_db_pending, 0); + enable_work(&ntb->peer_db_work); + return 0; err_write_header: @@ -995,6 +1004,8 @@ static int epf_ntb_epc_init(struct epf_ntb *ntb) static void epf_ntb_epc_cleanup(struct epf_ntb *ntb) { disable_delayed_work_sync(&ntb->cmd_handler); + disable_work_sync(&ntb->peer_db_work); + atomic64_set(&ntb->peer_db_pending, 0); epf_ntb_mw_bar_clear(ntb, ntb->num_mws); epf_ntb_db_bar_clear(ntb); epf_ntb_config_sspad_bar_clear(ntb); @@ -1409,41 +1420,84 @@ static int vntb_epf_peer_spad_write(struct ntb_dev *ndev, int pidx, int idx, u32 return 0; } -static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits) +static void vntb_epf_peer_db_work(struct work_struct *work) { - u32 interrupt_num = ffs(db_bits) + 1; - struct epf_ntb *ntb = ntb_ndev(ndev); + struct epf_ntb *ntb = container_of(work, struct epf_ntb, peer_db_work); + struct pci_epf *epf = ntb->epf; + unsigned int budget = VNTB_PEER_DB_WORK_BUDGET; u8 func_no, vfunc_no; + unsigned int db_bit; + u32 interrupt_num; + u64 db_bits; int ret; - func_no = ntb->epf->func_no; - vfunc_no = ntb->epf->vfunc_no; + if (!epf || !epf->epc) + return; + + func_no = epf->func_no; + vfunc_no = epf->vfunc_no; /* - * pci_epc_raise_irq() for MSI expects a 1-based interrupt number. - * ffs() returns a 1-based index (bit 0 -> 1). interrupt_num has already - * been computed as ffs(db_bits) + 1 above. Adding one more +1 when - * calling pci_epc_raise_irq() therefore results in: - * - * doorbell bit 0 -> MSI #3 - * - * Legacy mapping (kept for compatibility): - * - * MSI #1 : link event (reserved) - * MSI #2 : unused (historical offset) - * MSI #3 : doorbell bit 0 (DB#0) - * MSI #4 : doorbell bit 1 (DB#1) - * ... - * - * Do not change this mapping to avoid breaking interoperability with - * older peers. + * Drain doorbells from peer_db_pending in snapshots (atomic64_xchg()). + * Limit the number of snapshots handled per run so we don't monopolize + * the workqueue under a doorbell storm. */ - ret = pci_epc_raise_irq(ntb->epf->epc, func_no, vfunc_no, - PCI_IRQ_MSI, interrupt_num + 1); - if (ret) - dev_err(&ntb->ntb.dev, "Failed to raise IRQ\n"); + while (budget--) { + db_bits = atomic64_xchg(&ntb->peer_db_pending, 0); + if (!db_bits) + return; - return ret; + while (db_bits) { + /* + * pci_epc_raise_irq() for MSI expects a 1-based + * interrupt number. db_bit is zero-based, so add 3 to + * preserve the historical slot offset. + * + * Legacy mapping (kept for compatibility): + * + * MSI #1 : link event (reserved) + * MSI #2 : unused (historical offset) + * MSI #3 : doorbell bit 0 (DB#0) + * MSI #4 : doorbell bit 1 (DB#1) + * ... + * + * Do not change this mapping to avoid breaking + * interoperability with older peers. + */ + db_bit = __ffs64(db_bits); + interrupt_num = db_bit + 3; + db_bits &= ~BIT_ULL(db_bit); + + ret = pci_epc_raise_irq(epf->epc, func_no, vfunc_no, + PCI_IRQ_MSI, interrupt_num); + if (ret) + dev_err(&ntb->ntb.dev, + "Failed to raise IRQ for interrupt_num %u: %d\n", + interrupt_num, ret); + } + } + + if (atomic64_read(&ntb->peer_db_pending)) + queue_work(kpcintb_workqueue, &ntb->peer_db_work); +} + +static int vntb_epf_peer_db_set(struct ntb_dev *ndev, u64 db_bits) +{ + struct epf_ntb *ntb = ntb_ndev(ndev); + + db_bits &= vntb_epf_db_valid_mask(ndev); + if (!db_bits) + return 0; + + /* + * .peer_db_set() may be called from atomic context. pci_epc_raise_irq() + * can sleep (it takes epc->lock), so defer MSI raising to process + * context. Doorbell requests are coalesced in peer_db_pending. + */ + atomic64_or(db_bits, &ntb->peer_db_pending); + queue_work(kpcintb_workqueue, &ntb->peer_db_work); + + return 0; } static u64 vntb_epf_db_read(struct ntb_dev *ndev) @@ -1690,6 +1744,10 @@ static int epf_ntb_probe(struct pci_epf *epf, ntb->epf = epf; ntb->vbus_number = 0xff; + INIT_WORK(&ntb->peer_db_work, vntb_epf_peer_db_work); + disable_work(&ntb->peer_db_work); + atomic64_set(&ntb->peer_db_pending, 0); + /* Initially, no bar is assigned */ for (i = 0; i < VNTB_BAR_NUM; i++) ntb->epf_ntb_bar[i] = NO_BAR; From 91fb4488cd615a39360bc4160a10cb3236189ba1 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:14 +0900 Subject: [PATCH 123/168] PCI: endpoint: pci-epf-vntb: Report 0-based doorbell vector via ntb_db_event() ntb_db_event() expects the vector number to be relative to the first doorbell vector starting at 0. pci-epf-vntb reserves vector 0 for link events and uses higher vector indices for doorbells. By passing the raw slot index to ntb_db_event(), it effectively assumes that doorbell 0 maps to vector 1. However, because the host uses a legacy slot layout and writes doorbell 0 into the third slot, doorbell 0 ultimately appears as vector 2 from the NTB core perspective. Adjust pci-epf-vntb to: - skip the unused second slot, and - report doorbells as 0-based vectors (DB#0 -> vector 0). This change does not introduce a behavioral difference until .db_vector_count()/.db_vector_mask() are implemented, because without those callbacks NTB clients effectively ignore the vector number. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-4-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index cc0b356973f3..d31e2eee0869 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -83,6 +83,12 @@ enum epf_ntb_bar { VNTB_BAR_NUM, }; +enum epf_irq_slot { + EPF_IRQ_LINK = 0, + EPF_IRQ_RESERVED_DB, /* Historically skipped slot */ + EPF_IRQ_DB_START, +}; + /* * +--------------------------------------------------+ Base * | | @@ -272,10 +278,11 @@ static void epf_ntb_cmd_handler(struct work_struct *work) ntb = container_of(work, struct epf_ntb, cmd_handler.work); - for (i = 1; i < ntb->db_count && !ntb->msi_doorbell; i++) { + for (i = EPF_IRQ_DB_START; i < ntb->db_count && !ntb->msi_doorbell; + i++) { if (ntb->epf_db[i]) { - atomic64_or(1 << (i - 1), &ntb->db); - ntb_db_event(&ntb->ntb, i); + atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db); + ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START); ntb->epf_db[i] = 0; } } @@ -341,10 +348,10 @@ static irqreturn_t epf_ntb_doorbell_handler(int irq, void *data) struct epf_ntb *ntb = data; int i; - for (i = 1; i < ntb->db_count; i++) + for (i = EPF_IRQ_DB_START; i < ntb->db_count; i++) if (irq == ntb->epf->db_msg[i].virq) { - atomic64_or(1 << (i - 1), &ntb->db); - ntb_db_event(&ntb->ntb, i); + atomic64_or(1 << (i - EPF_IRQ_DB_START), &ntb->db); + ntb_db_event(&ntb->ntb, i - EPF_IRQ_DB_START); } return IRQ_HANDLED; @@ -1450,8 +1457,8 @@ static void vntb_epf_peer_db_work(struct work_struct *work) while (db_bits) { /* * pci_epc_raise_irq() for MSI expects a 1-based - * interrupt number. db_bit is zero-based, so add 3 to - * preserve the historical slot offset. + * interrupt number. The first usable doorbell starts + * at EPF_IRQ_DB_START in the legacy slot layout. * * Legacy mapping (kept for compatibility): * @@ -1465,7 +1472,7 @@ static void vntb_epf_peer_db_work(struct work_struct *work) * interoperability with older peers. */ db_bit = __ffs64(db_bits); - interrupt_num = db_bit + 3; + interrupt_num = db_bit + EPF_IRQ_DB_START + 1; db_bits &= ~BIT_ULL(db_bit); ret = pci_epc_raise_irq(epf->epc, func_no, vfunc_no, From f417c400a6683c62cdead42013f60872fda84013 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:15 +0900 Subject: [PATCH 124/168] PCI: endpoint: pci-epf-vntb: Reject unusable doorbell counts pci-epf-vntb reserves slot 0 for link events and keeps slot 1 unused for legacy layout compatibility. A db_count smaller than MIN_DB_COUNT leaves no usable doorbell slot after those reservations. Reject such configurations when configuring interrupts. While at it, move MAX_DB_COUNT next to MIN_DB_COUNT. They are used as a pair in the range check, and keeping them together makes the valid doorbell range easier to read. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-5-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index d31e2eee0869..818ae5999976 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -67,7 +67,6 @@ static struct workqueue_struct *kpcintb_workqueue; #define NTB_MW_OFFSET 2 #define DB_COUNT_MASK GENMASK(15, 0) #define MSIX_ENABLE BIT(16) -#define MAX_DB_COUNT 32 #define MAX_MW 4 /* Limit per-work execution to avoid monopolizing kworker on doorbell storms. */ @@ -89,6 +88,9 @@ enum epf_irq_slot { EPF_IRQ_DB_START, }; +#define MIN_DB_COUNT (EPF_IRQ_DB_START + 1) +#define MAX_DB_COUNT 32 + /* * +--------------------------------------------------+ Base * | | @@ -512,9 +514,9 @@ static int epf_ntb_configure_interrupt(struct epf_ntb *ntb) return -EINVAL; } - if (!ntb->db_count || ntb->db_count > MAX_DB_COUNT) { - dev_err(dev, "DB count %d out of range (1 - %d)\n", - ntb->db_count, MAX_DB_COUNT); + if (ntb->db_count < MIN_DB_COUNT || ntb->db_count > MAX_DB_COUNT) { + dev_err(dev, "DB count %d out of range (%d - %d)\n", + ntb->db_count, MIN_DB_COUNT, MAX_DB_COUNT); return -EINVAL; } From 6c39350aaa2d6338ec30ef0a5632b0d6dad856ec Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:16 +0900 Subject: [PATCH 125/168] PCI: endpoint: pci-epf-vntb: Guard configfs writes after EPC attach db_count controls how many doorbell slots are allocated and exposed. It is also used by the doorbell mask helpers. After an EPC has been attached, changing it from configfs can leave runtime paths using a different count than the one used to set up the doorbell resources. Reject db_count writes after EPC attach, and reject values outside MIN_DB_COUNT..MAX_DB_COUNT before attach. Now that MIN_DB_COUNT documents the usable doorbell floor, use it in the store path too. While at it, apply the same after-attach guard to the other vNTB configfs knobs. BAR choices, spad_count, memory-window counts and sizes, and the virtual PCI IDs are also consumed during bind, so changing them later at runtime is meaningless and unsafe. Return -EOPNOTSUPP for after-attach writes. The value itself may be valid, but changing it in that state is not supported. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-6-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 818ae5999976..524355a8b4be 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1020,6 +1020,11 @@ static void epf_ntb_epc_cleanup(struct epf_ntb *ntb) epf_ntb_config_sspad_bar_clear(ntb); } +static bool epf_ntb_epc_attached(struct epf_ntb *ntb) +{ + return ntb->epf->epc || ntb->epf->sec_epc; +} + #define EPF_NTB_R(_name) \ static ssize_t epf_ntb_##_name##_show(struct config_item *item, \ char *page) \ @@ -1039,6 +1044,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \ u32 val; \ int ret; \ \ + if (epf_ntb_epc_attached(ntb)) \ + return -EOPNOTSUPP; \ + \ ret = kstrtou32(page, 0, &val); \ if (ret) \ return ret; \ @@ -1081,6 +1089,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \ u64 val; \ int ret; \ \ + if (epf_ntb_epc_attached(ntb)) \ + return -EOPNOTSUPP; \ + \ ret = kstrtou64(page, 0, &val); \ if (ret) \ return ret; \ @@ -1119,6 +1130,9 @@ static ssize_t epf_ntb_##_name##_store(struct config_item *item, \ int val; \ int ret; \ \ + if (epf_ntb_epc_attached(ntb)) \ + return -EOPNOTSUPP; \ + \ ret = kstrtoint(page, 0, &val); \ if (ret) \ return ret; \ @@ -1139,6 +1153,9 @@ static ssize_t epf_ntb_num_mws_store(struct config_item *item, u32 val; int ret; + if (epf_ntb_epc_attached(ntb)) + return -EOPNOTSUPP; + ret = kstrtou32(page, 0, &val); if (ret) return ret; @@ -1151,10 +1168,32 @@ static ssize_t epf_ntb_num_mws_store(struct config_item *item, return len; } +static ssize_t epf_ntb_db_count_store(struct config_item *item, + const char *page, size_t len) +{ + struct config_group *group = to_config_group(item); + struct epf_ntb *ntb = to_epf_ntb(group); + u32 val; + int ret; + + if (epf_ntb_epc_attached(ntb)) + return -EOPNOTSUPP; + + ret = kstrtou32(page, 0, &val); + if (ret) + return ret; + + if (val < MIN_DB_COUNT || val > MAX_DB_COUNT) + return -EINVAL; + + WRITE_ONCE(ntb->db_count, val); + + return len; +} + EPF_NTB_R(spad_count) EPF_NTB_W(spad_count) EPF_NTB_R(db_count) -EPF_NTB_W(db_count) EPF_NTB_R(num_mws) EPF_NTB_R(vbus_number) EPF_NTB_W(vbus_number) From 823468a4ea1613f1c1235bd16af8b9c6eb9c9677 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:17 +0900 Subject: [PATCH 126/168] PCI: endpoint: pci-epf-vntb: Exclude reserved slots from db_valid_mask In pci-epf-vntb, db_count represents the total number of doorbell slots exposed to the peer, including: - slot #0 reserved for link events, and - slot #1 historically unused (kept for compatibility). Only the remaining slots correspond to actual doorbell bits. The current db_valid_mask() exposes all slots as valid doorbells. Limit db_valid_mask() to the real doorbell bits by returning BIT_ULL(db_count - 2) - 1, and guard against db_count < 2. Fixes: e35f56bb0330 ("PCI: endpoint: Support NTB transfer between RC and EP") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-7-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 524355a8b4be..58e41d95d029 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1364,7 +1364,10 @@ static int vntb_epf_peer_mw_count(struct ntb_dev *ntb) static u64 vntb_epf_db_valid_mask(struct ntb_dev *ntb) { - return BIT_ULL(ntb_ndev(ntb)->db_count) - 1; + if (ntb_ndev(ntb)->db_count < EPF_IRQ_DB_START) + return 0; + + return BIT_ULL(ntb_ndev(ntb)->db_count - EPF_IRQ_DB_START) - 1; } static int vntb_epf_db_set_mask(struct ntb_dev *ntb, u64 db_bits) From 2579f3f7f52090463596765040aaad71e91ef4d5 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:18 +0900 Subject: [PATCH 127/168] PCI: endpoint: pci-epf-vntb: Implement .db_vector_count()/mask() for doorbells Implement .db_vector_count() and .db_vector_mask() so NTB core/clients can map doorbell events to per-vector work and avoid the thundering-herd behavior. pci-epf-vntb reserves two slots in db_count: slot 0 for link events and slot 1 which is historically unused. Therefore the number of doorbell vectors is (db_count - 2). Report vectors as 0..N-1 and return BIT_ULL(db_vector) for the corresponding doorbell bit. Build db_valid_mask from a validated vector count so out-of-range db_count values cannot create invalid shifts. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-8-den@valinux.co.jp --- drivers/pci/endpoint/functions/pci-epf-vntb.c | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/drivers/pci/endpoint/functions/pci-epf-vntb.c b/drivers/pci/endpoint/functions/pci-epf-vntb.c index 58e41d95d029..c3caec927d74 100644 --- a/drivers/pci/endpoint/functions/pci-epf-vntb.c +++ b/drivers/pci/endpoint/functions/pci-epf-vntb.c @@ -1362,12 +1362,48 @@ static int vntb_epf_peer_mw_count(struct ntb_dev *ntb) return ntb_ndev(ntb)->num_mws; } -static u64 vntb_epf_db_valid_mask(struct ntb_dev *ntb) +static int vntb_epf_db_vector_count(struct ntb_dev *ntb) { - if (ntb_ndev(ntb)->db_count < EPF_IRQ_DB_START) + struct epf_ntb *ndev = ntb_ndev(ntb); + u32 db_count = READ_ONCE(ndev->db_count); + + /* + * db_count is the total number of doorbell slots exposed to + * the peer, including: + * - slot #0 reserved for link events + * - slot #1 historically unused (kept for protocol compatibility) + * + * Report only usable per-vector doorbell interrupts. + */ + if (db_count < MIN_DB_COUNT || db_count > MAX_DB_COUNT) return 0; - return BIT_ULL(ntb_ndev(ntb)->db_count - EPF_IRQ_DB_START) - 1; + return db_count - EPF_IRQ_DB_START; +} + +static u64 vntb_epf_db_valid_mask(struct ntb_dev *ntb) +{ + int nr_vec = vntb_epf_db_vector_count(ntb); + + if (!nr_vec) + return 0; + + return GENMASK_ULL(nr_vec - 1, 0); +} + +static u64 vntb_epf_db_vector_mask(struct ntb_dev *ntb, int db_vector) +{ + int nr_vec; + + /* + * Doorbell vectors are numbered [0 .. nr_vec - 1], where nr_vec + * excludes the two reserved slots described above. + */ + nr_vec = vntb_epf_db_vector_count(ntb); + if (db_vector < 0 || db_vector >= nr_vec) + return 0; + + return BIT_ULL(db_vector); } static int vntb_epf_db_set_mask(struct ntb_dev *ntb, u64 db_bits) @@ -1617,6 +1653,8 @@ static const struct ntb_dev_ops vntb_epf_ops = { .spad_count = vntb_epf_spad_count, .peer_mw_count = vntb_epf_peer_mw_count, .db_valid_mask = vntb_epf_db_valid_mask, + .db_vector_count = vntb_epf_db_vector_count, + .db_vector_mask = vntb_epf_db_vector_mask, .db_set_mask = vntb_epf_db_set_mask, .mw_set_trans = vntb_epf_mw_set_trans, .mw_clear_trans = vntb_epf_mw_clear_trans, From 84af8a5f5ef24b74f44470e7e6af7089ef125e84 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:19 +0900 Subject: [PATCH 128/168] NTB: epf: Document legacy doorbell slot offset in ntb_epf_peer_db_set() ntb_epf_peer_db_set() uses ffs(db_bits) to select a doorbell to ring. ffs() returns a 1-based bit index (bit 0 -> 1). Entry 0 is reserved for link events, so doorbell bit 0 must map to entry 1. However, since the initial commit 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge"), the implementation has been adding an extra +1, ending up using entry 2 for bit 0. Fixing the extra increment would break interoperability with peers running older kernels. Keep the legacy behavior and document the offset and the resulting slot layout to avoid confusion when enabling per-db-vector handling. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-9-den@valinux.co.jp --- drivers/ntb/hw/epf/ntb_hw_epf.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index 8925c688930c..21d942824983 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -43,6 +43,18 @@ #define NTB_EPF_DB_DATA(n) (0x34 + (n) * 4) #define NTB_EPF_DB_OFFSET(n) (0xB4 + (n) * 4) +/* + * Legacy doorbell slot layout when paired with pci-epf-*ntb: + * + * slot 0 : reserved for link events + * slot 1 : unused (historical extra offset) + * slot 2 : DB#0 + * slot 3 : DB#1 + * ... + * + * Thus, NTB_EPF_MIN_DB_COUNT=3 means that we at least create vectors for + * doorbells DB#0 and DB#1. + */ #define NTB_EPF_MIN_DB_COUNT 3 #define NTB_EPF_MAX_DB_COUNT 31 @@ -473,6 +485,14 @@ static int ntb_epf_peer_mw_get_addr(struct ntb_dev *ntb, int idx, static int ntb_epf_peer_db_set(struct ntb_dev *ntb, u64 db_bits) { struct ntb_epf_dev *ndev = ntb_ndev(ntb); + /* + * ffs() returns a 1-based bit index (bit 0 -> 1). + * + * With slot 0 reserved for link events, DB#0 would naturally map to + * slot 1. Historically an extra +1 offset was added, so DB#0 maps to + * slot 2 and slot 1 remains unused. Keep this mapping for + * backward-compatibility. + */ u32 interrupt_num = ffs(db_bits) + 1; struct device *dev = ndev->dev; u32 db_entry_size; From 6eb7e28f1f24a28234add38755687a7ed8bc2654 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:20 +0900 Subject: [PATCH 129/168] NTB: epf: Make db_valid_mask cover only real doorbell bits ndev->db_count includes an unused doorbell slot due to the legacy extra offset in the peer doorbell path. db_valid_mask must cover only the real doorbell bits and exclude the unused slot. Set db_valid_mask to BIT_ULL(db_count - 1) - 1. Fixes: 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge") Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Frank Li Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260513024923.451765-10-den@valinux.co.jp --- drivers/ntb/hw/epf/ntb_hw_epf.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index 21d942824983..c0bab3292075 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -580,7 +580,11 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev) return ret; } - ndev->db_valid_mask = BIT_ULL(ndev->db_count) - 1; + /* + * ndev->db_count includes an extra skipped slot due to the legacy + * doorbell layout, hence -1. + */ + ndev->db_valid_mask = BIT_ULL(ndev->db_count - 1) - 1; ndev->mw_count = readl(ndev->ctrl_reg + NTB_EPF_MW_COUNT); ndev->spad_count = readl(ndev->ctrl_reg + NTB_EPF_SPAD_COUNT); From 3147f0964cecca15e349cbfbdd6a28eb6d50379d Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:21 +0900 Subject: [PATCH 130/168] NTB: epf: Report 0-based doorbell vector via ntb_db_event() ntb_db_event() expects the vector number to be relative to the first doorbell vector starting at 0. Vector 0 is reserved for link events in the EPF driver, so doorbells start at vector 1. However, both supported peers (ntb_hw_epf with pci-epf-ntb, and pci-epf-vntb) have historically skipped vector 1 and started doorbells at vector 2. Pass (irq_no - 2) to ntb_db_event() so doorbells are reported as 0..N-1. If irq_no == 1 is ever observed, warn and ignore it, since the slot is reserved in the legacy layout and reporting it as DB#0 would collide with the real DB#0 slot. Fixes: 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge") Suggested-by: Dave Jiang Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-11-den@valinux.co.jp --- drivers/ntb/hw/epf/ntb_hw_epf.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index c0bab3292075..7b0fc7ef00c6 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -81,6 +81,12 @@ enum epf_ntb_bar { NTB_BAR_NUM, }; +enum epf_irq_slot { + EPF_IRQ_LINK = 0, + EPF_IRQ_RESERVED_DB, /* Historically skipped slot */ + EPF_IRQ_DB_START, +}; + #define NTB_EPF_MAX_MW_COUNT (NTB_BAR_NUM - BAR_MW1) struct ntb_epf_dev { @@ -334,10 +340,14 @@ static irqreturn_t ntb_epf_vec_isr(int irq, void *dev) irq_no = irq - ndev->irq_base; ndev->db_val = irq_no + 1; - if (irq_no == 0) + if (irq_no == EPF_IRQ_LINK) { ntb_link_event(&ndev->ntb); - else - ntb_db_event(&ndev->ntb, irq_no); + } else if (irq_no == EPF_IRQ_RESERVED_DB) { + dev_warn_ratelimited(ndev->dev, + "Unexpected reserved doorbell slot IRQ received\n"); + } else { + ntb_db_event(&ndev->ntb, irq_no - EPF_IRQ_DB_START); + } return IRQ_HANDLED; } From 4fdea8dbb62d746429498e07385a3ba0b0f6d1ab Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:22 +0900 Subject: [PATCH 131/168] NTB: epf: Fix doorbell bitmask and IRQ vector handling The EPF driver currently stores the incoming doorbell as a vector number (irq_no + 1) in db_val and db_clear() clears all bits unconditionally. This breaks db_read()/db_clear() semantics when multiple doorbells are used. Store doorbells as a bitmask (BIT_ULL(vector)) and make db_clear(db_bits) clear only the specified bits. Use atomic64 operations as db_val is updated from interrupt context. Once db_val is stored as a bitmask, the ISR's doorbell vector is used not only for ntb_db_event(), but also as the bit index for BIT_ULL(). The existing ISR derives that vector by subtracting pci_irq_vector(pdev, 0) from the Linux IRQ number passed to the handler, but Linux IRQ numbers are not guaranteed to be contiguous. Pass per-vector context as request_irq() dev_id instead, so the ISR gets the device vector directly. Validate the doorbell vector before updating db_val or calling ntb_db_event(), so an unexpected vector cannot create an invalid shift or be reported to NTB clients. While at it, read and validate mw_count before requesting interrupt vectors. An unsupported memory-window count does not need IRQs, and failing before ntb_epf_init_isr() keeps the probe error path simple. Fixes: 812ce2f8d14e ("NTB: Add support for EPF PCI Non-Transparent Bridge") Suggested-by: Dave Jiang Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dave Jiang Reviewed-by: Frank Li Link: https://patch.msgid.link/20260513024923.451765-12-den@valinux.co.jp --- drivers/ntb/hw/epf/ntb_hw_epf.c | 61 +++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index 7b0fc7ef00c6..10618e462229 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -6,6 +6,7 @@ * Author: Kishon Vijay Abraham I */ +#include #include #include #include @@ -89,6 +90,13 @@ enum epf_irq_slot { #define NTB_EPF_MAX_MW_COUNT (NTB_BAR_NUM - BAR_MW1) +struct ntb_epf_dev; + +struct ntb_epf_irq_ctx { + struct ntb_epf_dev *ndev; + unsigned int irq_no; +}; + struct ntb_epf_dev { struct ntb_dev ntb; struct device *dev; @@ -108,9 +116,9 @@ struct ntb_epf_dev { unsigned int self_spad; unsigned int peer_spad; - int db_val; + atomic64_t db_val; u64 db_valid_mask; - int irq_base; + struct ntb_epf_irq_ctx irq_ctx[NTB_EPF_MAX_DB_COUNT + 1]; }; #define ntb_ndev(__ntb) container_of(__ntb, struct ntb_epf_dev, ntb) @@ -334,11 +342,10 @@ static int ntb_epf_link_disable(struct ntb_dev *ntb) static irqreturn_t ntb_epf_vec_isr(int irq, void *dev) { - struct ntb_epf_dev *ndev = dev; - int irq_no; - - irq_no = irq - ndev->irq_base; - ndev->db_val = irq_no + 1; + struct ntb_epf_irq_ctx *ctx = dev; + struct ntb_epf_dev *ndev = ctx->ndev; + unsigned int db_vector; + unsigned int irq_no = ctx->irq_no; if (irq_no == EPF_IRQ_LINK) { ntb_link_event(&ndev->ntb); @@ -346,7 +353,17 @@ static irqreturn_t ntb_epf_vec_isr(int irq, void *dev) dev_warn_ratelimited(ndev->dev, "Unexpected reserved doorbell slot IRQ received\n"); } else { - ntb_db_event(&ndev->ntb, irq_no - EPF_IRQ_DB_START); + db_vector = irq_no - EPF_IRQ_DB_START; + if (ndev->db_count < NTB_EPF_MIN_DB_COUNT || + db_vector >= ndev->db_count - 1) { + dev_warn_ratelimited(ndev->dev, + "Unexpected doorbell vector %u (db_count %u)\n", + db_vector, ndev->db_count); + return IRQ_HANDLED; + } + + atomic64_or(BIT_ULL(db_vector), &ndev->db_val); + ntb_db_event(&ndev->ntb, db_vector); } return IRQ_HANDLED; @@ -373,18 +390,18 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max) argument &= ~MSIX_ENABLE; } - ndev->irq_base = pci_irq_vector(pdev, 0); + ndev->db_count = irq - 1; for (i = 0; i < irq; i++) { + ndev->irq_ctx[i].ndev = ndev; + ndev->irq_ctx[i].irq_no = i; ret = request_irq(pci_irq_vector(pdev, i), ntb_epf_vec_isr, - 0, "ntb_epf", ndev); + 0, "ntb_epf", &ndev->irq_ctx[i]); if (ret) { dev_err(dev, "Failed to request irq\n"); goto err_free_irq; } } - ndev->db_count = irq - 1; - ret = ntb_epf_send_command(ndev, CMD_CONFIGURE_DOORBELL, argument | irq); if (ret) { @@ -396,7 +413,7 @@ static int ntb_epf_init_isr(struct ntb_epf_dev *ndev, int msi_min, int msi_max) err_free_irq: while (i--) - free_irq(pci_irq_vector(pdev, i), ndev); + free_irq(pci_irq_vector(pdev, i), &ndev->irq_ctx[i]); pci_free_irq_vectors(pdev); return ret; @@ -529,7 +546,7 @@ static u64 ntb_epf_db_read(struct ntb_dev *ntb) { struct ntb_epf_dev *ndev = ntb_ndev(ntb); - return ndev->db_val; + return atomic64_read(&ndev->db_val); } static int ntb_epf_db_clear_mask(struct ntb_dev *ntb, u64 db_bits) @@ -541,7 +558,7 @@ static int ntb_epf_db_clear(struct ntb_dev *ntb, u64 db_bits) { struct ntb_epf_dev *ndev = ntb_ndev(ntb); - ndev->db_val = 0; + atomic64_and(~db_bits, &ndev->db_val); return 0; } @@ -582,6 +599,12 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev) struct device *dev = ndev->dev; int ret; + ndev->mw_count = readl(ndev->ctrl_reg + NTB_EPF_MW_COUNT); + if (ndev->mw_count > NTB_EPF_MAX_MW_COUNT) { + dev_err(dev, "Unsupported MW count: %u\n", ndev->mw_count); + return -EINVAL; + } + /* One Link interrupt and rest doorbell interrupt */ ret = ntb_epf_init_isr(ndev, NTB_EPF_MIN_DB_COUNT + 1, NTB_EPF_MAX_DB_COUNT + 1); @@ -595,14 +618,8 @@ static int ntb_epf_init_dev(struct ntb_epf_dev *ndev) * doorbell layout, hence -1. */ ndev->db_valid_mask = BIT_ULL(ndev->db_count - 1) - 1; - ndev->mw_count = readl(ndev->ctrl_reg + NTB_EPF_MW_COUNT); ndev->spad_count = readl(ndev->ctrl_reg + NTB_EPF_SPAD_COUNT); - if (ndev->mw_count > NTB_EPF_MAX_MW_COUNT) { - dev_err(dev, "Unsupported MW count: %u\n", ndev->mw_count); - return -EINVAL; - } - return 0; } @@ -696,7 +713,7 @@ static void ntb_epf_cleanup_isr(struct ntb_epf_dev *ndev) ntb_epf_send_command(ndev, CMD_TEARDOWN_DOORBELL, ndev->db_count + 1); for (i = 0; i < ndev->db_count + 1; i++) - free_irq(pci_irq_vector(pdev, i), ndev); + free_irq(pci_irq_vector(pdev, i), &ndev->irq_ctx[i]); pci_free_irq_vectors(pdev); } From 1fda82e37e00eb679d762756867b2e7508dd73f9 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Wed, 13 May 2026 11:49:23 +0900 Subject: [PATCH 132/168] NTB: epf: Implement .db_vector_count()/mask() for doorbells Implement .db_vector_count() and .db_vector_mask() so NTB core/clients can map doorbell events to per-vector work. Report vectors as 0..(db_count - 2) (skipping the unused slot) and return BIT_ULL(db_vector) for the corresponding doorbell bit. Use ntb_epf_db_vector_count() for bounds checks in ntb_epf_db_vector_mask(), so the same lower-bound guard is applied before building the bitmask. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Dave Jiang Link: https://patch.msgid.link/20260513024923.451765-13-den@valinux.co.jp --- drivers/ntb/hw/epf/ntb_hw_epf.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/drivers/ntb/hw/epf/ntb_hw_epf.c b/drivers/ntb/hw/epf/ntb_hw_epf.c index 10618e462229..af5755472842 100644 --- a/drivers/ntb/hw/epf/ntb_hw_epf.c +++ b/drivers/ntb/hw/epf/ntb_hw_epf.c @@ -434,6 +434,36 @@ static u64 ntb_epf_db_valid_mask(struct ntb_dev *ntb) return ntb_ndev(ntb)->db_valid_mask; } +static int ntb_epf_db_vector_count(struct ntb_dev *ntb) +{ + struct ntb_epf_dev *ndev = ntb_ndev(ntb); + unsigned int db_count = ndev->db_count; + + /* + * db_count includes an extra skipped slot due to the legacy + * doorbell layout. Expose only the real doorbell vectors. + */ + if (db_count < NTB_EPF_MIN_DB_COUNT) + return 0; + + return db_count - 1; +} + +static u64 ntb_epf_db_vector_mask(struct ntb_dev *ntb, int db_vector) +{ + int nr_vec; + + /* + * db_count includes one skipped slot in the legacy layout. Valid + * doorbell vectors are therefore [0 .. (db_count - 2)]. + */ + nr_vec = ntb_epf_db_vector_count(ntb); + if (db_vector < 0 || db_vector >= nr_vec) + return 0; + + return BIT_ULL(db_vector); +} + static int ntb_epf_db_set_mask(struct ntb_dev *ntb, u64 db_bits) { return 0; @@ -568,6 +598,8 @@ static const struct ntb_dev_ops ntb_epf_ops = { .spad_count = ntb_epf_spad_count, .peer_mw_count = ntb_epf_peer_mw_count, .db_valid_mask = ntb_epf_db_valid_mask, + .db_vector_count = ntb_epf_db_vector_count, + .db_vector_mask = ntb_epf_db_vector_mask, .db_set_mask = ntb_epf_db_set_mask, .mw_set_trans = ntb_epf_mw_set_trans, .mw_clear_trans = ntb_epf_mw_clear_trans, From 29bfc3523fa4e41aef2b67ce086dbfb2ccb2564f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:08 +0300 Subject: [PATCH 133/168] PCI: Rename 'added' to 'add_list' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resource fitting algorithm uses different names from the list holding the optional sizes: added, add_head, add_list, and realloc_head. 'add_list' sounds the most natural and some of the related variables also use 'add' such as 'add_size'. To reduce variation, rename 'added' and 'add_head' to 'add_list'. Also rename some 'realloc_head' cases selectively to 'add_list'. While it would be nice to rename every 'realloc_head' to 'add_list' for consistency, it might create a backport headache with all the work going into this algorithm that may need to be eventually backported. Thus, it's better to leave 'realloc_head' as is for now. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-3-ilpo.jarvinen@linux.intel.com --- drivers/pci/pci.h | 2 +- drivers/pci/setup-bus.c | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4a14f88e543a..4fcf5a25ad9e 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -515,7 +515,7 @@ int pci_dev_res_add_to_list(struct list_head *head, struct pci_dev *dev, void __pci_bus_size_bridges(struct pci_bus *bus, struct list_head *realloc_head); void __pci_bus_assign_resources(const struct pci_bus *bus, - struct list_head *realloc_head, + struct list_head *add_list, struct list_head *fail_head); bool pci_bus_clip_resource(struct pci_dev *dev, int idx); void pci_walk_bus_locked(struct pci_bus *top, diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 4cf120ebe5ad..3765693e95f0 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -756,13 +756,13 @@ static void __assign_resources_sorted(struct list_head *head, } static void pdev_assign_resources_sorted(struct pci_dev *dev, - struct list_head *add_head, + struct list_head *add_list, struct list_head *fail_head) { LIST_HEAD(head); pdev_sort_resources(dev, &head); - __assign_resources_sorted(&head, add_head, fail_head); + __assign_resources_sorted(&head, add_list, fail_head); } @@ -1502,13 +1502,13 @@ static void pdev_assign_fixed_resources(struct pci_dev *dev) } void __pci_bus_assign_resources(const struct pci_bus *bus, - struct list_head *realloc_head, + struct list_head *add_list, struct list_head *fail_head) { struct pci_bus *b; struct pci_dev *dev; - pbus_assign_resources_sorted(bus, realloc_head, fail_head); + pbus_assign_resources_sorted(bus, add_list, fail_head); list_for_each_entry(dev, &bus->devices, bus_list) { pdev_assign_fixed_resources(dev); @@ -1517,7 +1517,7 @@ void __pci_bus_assign_resources(const struct pci_bus *bus, if (!b) continue; - __pci_bus_assign_resources(b, realloc_head, fail_head); + __pci_bus_assign_resources(b, add_list, fail_head); switch (dev->hdr_type) { case PCI_HEADER_TYPE_BRIDGE: @@ -1613,19 +1613,19 @@ void pci_bus_claim_resources(struct pci_bus *b) EXPORT_SYMBOL(pci_bus_claim_resources); static void __pci_bridge_assign_resources(const struct pci_dev *bridge, - struct list_head *add_head, + struct list_head *add_list, struct list_head *fail_head) { struct pci_bus *b; pdev_assign_resources_sorted((struct pci_dev *)bridge, - add_head, fail_head); + add_list, fail_head); b = bridge->subordinate; if (!b) return; - __pci_bus_assign_resources(b, add_head, fail_head); + __pci_bus_assign_resources(b, add_list, fail_head); switch (bridge->class >> 8) { case PCI_CLASS_BRIDGE_PCI: @@ -2303,7 +2303,7 @@ static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource * unsigned long type = res->flags; struct pci_dev_resource *dev_res; struct pci_dev *bridge = NULL; - LIST_HEAD(added); + LIST_HEAD(add_list); LIST_HEAD(failed); unsigned int i; int ret = 0; @@ -2337,10 +2337,10 @@ static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource * if (!bridge) return -ENOENT; - __pci_bus_size_bridges(bridge->subordinate, &added); - __pci_bridge_assign_resources(bridge, &added, &failed); - if (WARN_ON_ONCE(!list_empty(&added))) - pci_dev_res_free_list(&added); + __pci_bus_size_bridges(bridge->subordinate, &add_list); + __pci_bridge_assign_resources(bridge, &add_list, &failed); + if (WARN_ON_ONCE(!list_empty(&add_list))) + pci_dev_res_free_list(&add_list); if (!list_empty(&failed)) { if (pci_required_resource_failed(&failed, type)) From 2c90aeab5a5598f8bb17214579a889f1eed71bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:09 +0300 Subject: [PATCH 134/168] PCI: Consolidate add_list (aka realloc_head) empty sanity checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Callers of __pci_bridge_assign_resources() and __pci_bus_assign_resources() perform WARN_ON_ONCE(list_empty(add_list))) checks to sanity check that all optional sizes were processed (and removed) from the list. The empty list sanity check is duplicated code so the more appropriate place for it would be inside the called function. Placing the empty list check into __pci_bus_assign_resources() also ensures all callsites do perform the sanity check which currently is not the case when being called from enable_slot(). This inconsistency was noted by Sashiko though only inside its in depth log but not flagged as a real problem, possibly because this is only a sanity check that should never fire. Nonetheless, this sanity check has been very useful to catch problems early in the past so it's good to do it consistently everywhere. As __pci_bus_assign_resources() is a recursive function, it needs to be renamed to __pci_bus_assign_resources_one() to only perform the empty list check at the end of processing the entire hierarchy in __pci_bus_assign_resources(). Suggested-by: sashiko.dev # Sanity check missing from enable_slot() Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-4-ilpo.jarvinen@linux.intel.com --- drivers/pci/setup-bus.c | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 3765693e95f0..1e0e28efe8b8 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1501,9 +1501,9 @@ static void pdev_assign_fixed_resources(struct pci_dev *dev) } } -void __pci_bus_assign_resources(const struct pci_bus *bus, - struct list_head *add_list, - struct list_head *fail_head) +static void __pci_bus_assign_resources_one(const struct pci_bus *bus, + struct list_head *add_list, + struct list_head *fail_head) { struct pci_bus *b; struct pci_dev *dev; @@ -1517,7 +1517,7 @@ void __pci_bus_assign_resources(const struct pci_bus *bus, if (!b) continue; - __pci_bus_assign_resources(b, add_list, fail_head); + __pci_bus_assign_resources_one(b, add_list, fail_head); switch (dev->hdr_type) { case PCI_HEADER_TYPE_BRIDGE: @@ -1537,6 +1537,16 @@ void __pci_bus_assign_resources(const struct pci_bus *bus, } } +void __pci_bus_assign_resources(const struct pci_bus *bus, + struct list_head *add_list, + struct list_head *fail_head) +{ + __pci_bus_assign_resources_one(bus, add_list, fail_head); + + if (WARN_ON_ONCE(add_list && !list_empty(add_list))) + pci_dev_res_free_list(add_list); +} + void pci_bus_assign_resources(const struct pci_bus *bus) { __pci_bus_assign_resources(bus, NULL, NULL); @@ -1641,6 +1651,9 @@ static void __pci_bridge_assign_resources(const struct pci_dev *bridge, pci_domain_nr(b), b->number); break; } + + if (WARN_ON_ONCE(add_list && !list_empty(add_list))) + pci_dev_res_free_list(add_list); } static void pci_bridge_release_resources(struct pci_bus *bus, @@ -2205,8 +2218,6 @@ void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus) /* Depth last, allocate resources and update the hardware. */ __pci_bus_assign_resources(bus, add_list, &fail_head); - if (WARN_ON_ONCE(add_list && !list_empty(add_list))) - pci_dev_res_free_list(add_list); tried_times++; /* Any device complain? */ @@ -2268,8 +2279,6 @@ void pci_assign_unassigned_bridge_resources(struct pci_dev *bridge) pci_bridge_distribute_available_resources(bridge, &add_list); __pci_bridge_assign_resources(bridge, &add_list, &fail_head); - if (WARN_ON_ONCE(!list_empty(&add_list))) - pci_dev_res_free_list(&add_list); tried_times++; if (list_empty(&fail_head)) @@ -2339,8 +2348,6 @@ static int pbus_reassign_bridge_resources(struct pci_bus *bus, struct resource * __pci_bus_size_bridges(bridge->subordinate, &add_list); __pci_bridge_assign_resources(bridge, &add_list, &failed); - if (WARN_ON_ONCE(!list_empty(&add_list))) - pci_dev_res_free_list(&add_list); if (!list_empty(&failed)) { if (pci_required_resource_failed(&failed, type)) @@ -2473,7 +2480,5 @@ void pci_assign_unassigned_bus_resources(struct pci_bus *bus) __pci_bus_size_bridges(dev->subordinate, &add_list); up_read(&pci_bus_sem); __pci_bus_assign_resources(bus, &add_list, NULL); - if (WARN_ON_ONCE(!list_empty(&add_list))) - pci_dev_res_free_list(&add_list); } EXPORT_SYMBOL_GPL(pci_assign_unassigned_bus_resources); From 854f9522a20b6b61a0d6fc9db53e8a1437f65f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:10 +0300 Subject: [PATCH 135/168] PCI: Remove const removal cast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit __pci_bridge_assign_resources() inputs const pci_dev *bridge, but then immediately casts const away to pass the bridge to pdev_assign_resources_sorted(). As pdev_assign_resources_sorted() performs assignment of resources, it is not possible to make its input parameter to const. Neither of the __pci_bridge_assign_resources() callers requires the bridge parameter to be const. Thus, simply remove the out of place cast and convert the input parameter to non-const. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-5-ilpo.jarvinen@linux.intel.com --- drivers/pci/setup-bus.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/pci/setup-bus.c b/drivers/pci/setup-bus.c index 1e0e28efe8b8..c0a949f2c995 100644 --- a/drivers/pci/setup-bus.c +++ b/drivers/pci/setup-bus.c @@ -1622,14 +1622,13 @@ void pci_bus_claim_resources(struct pci_bus *b) } EXPORT_SYMBOL(pci_bus_claim_resources); -static void __pci_bridge_assign_resources(const struct pci_dev *bridge, +static void __pci_bridge_assign_resources(struct pci_dev *bridge, struct list_head *add_list, struct list_head *fail_head) { struct pci_bus *b; - pdev_assign_resources_sorted((struct pci_dev *)bridge, - add_list, fail_head); + pdev_assign_resources_sorted(bridge, add_list, fail_head); b = bridge->subordinate; if (!b) From 71c6e7808ee99a6e1b29bc30486baf825f9ec728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:11 +0300 Subject: [PATCH 136/168] resource: Make resource_alignment() input const resource MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resource_alignment() does not need to change resource so it can be made const. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-6-ilpo.jarvinen@linux.intel.com --- include/linux/ioport.h | 2 +- kernel/resource.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/ioport.h b/include/linux/ioport.h index 3c73c9c0d4f7..f7930b3dfd0a 100644 --- a/include/linux/ioport.h +++ b/include/linux/ioport.h @@ -261,7 +261,7 @@ extern int allocate_resource(struct resource *root, struct resource *new, struct resource *lookup_resource(struct resource *root, resource_size_t start); int adjust_resource(struct resource *res, resource_size_t start, resource_size_t size); -resource_size_t resource_alignment(struct resource *res); +resource_size_t resource_alignment(const struct resource *res); /** * resource_set_size - Calculate resource end address from size and start diff --git a/kernel/resource.c b/kernel/resource.c index d02a53fb95d8..3d17e3196a3e 100644 --- a/kernel/resource.c +++ b/kernel/resource.c @@ -1238,7 +1238,7 @@ reserve_region_with_split(struct resource *root, resource_size_t start, * * Returns alignment on success, 0 (invalid alignment) on failure. */ -resource_size_t resource_alignment(struct resource *res) +resource_size_t resource_alignment(const struct resource *res) { switch (res->flags & (IORESOURCE_SIZEALIGN | IORESOURCE_STARTALIGN)) { case IORESOURCE_SIZEALIGN: From 9f331c50b31fd03c7b9e36cc5790e81675a054f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:12 +0300 Subject: [PATCH 137/168] powerpc/pseries: Make pseries_get_iov_fw_value() & pnv_iov_get() pci_dev const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert input pci_dev for pseries_get_iov_fw_value() and pnv_iov_get() to const to be able to convert pcibios_iov_resource_alignment() as well in an upcoming change. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-7-ilpo.jarvinen@linux.intel.com --- arch/powerpc/platforms/powernv/pci.h | 2 +- arch/powerpc/platforms/pseries/setup.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/powerpc/platforms/powernv/pci.h b/arch/powerpc/platforms/powernv/pci.h index 42075501663b..032b2081aedb 100644 --- a/arch/powerpc/platforms/powernv/pci.h +++ b/arch/powerpc/platforms/powernv/pci.h @@ -251,7 +251,7 @@ struct pnv_iov_data { struct resource holes[PCI_SRIOV_NUM_BARS]; }; -static inline struct pnv_iov_data *pnv_iov_get(struct pci_dev *pdev) +static inline struct pnv_iov_data *pnv_iov_get(const struct pci_dev *pdev) { return pdev->dev.archdata.iov_data; } diff --git a/arch/powerpc/platforms/pseries/setup.c b/arch/powerpc/platforms/pseries/setup.c index 50b26ed8432d..b670c6fdfcea 100644 --- a/arch/powerpc/platforms/pseries/setup.c +++ b/arch/powerpc/platforms/pseries/setup.c @@ -658,7 +658,8 @@ enum get_iov_fw_value_index { WDW_SIZE = 3 /* Get Window Size */ }; -static resource_size_t pseries_get_iov_fw_value(struct pci_dev *dev, int resno, +static resource_size_t pseries_get_iov_fw_value(const struct pci_dev *dev, + int resno, enum get_iov_fw_value_index value) { const int *indexes; From 79a648209dadcc0f2ff0f27100fd79d1c890ccf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:13 +0300 Subject: [PATCH 138/168] PCI: Make pci_sriov_resource_alignment() pci_dev const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_sriov_resource_alignment() inputs struct pci_dev which it should not need to alter to calculate alignment. Make pci_dev pci_sriov_resource_alignment() inputs const. It requires making pci_iov_resource_size() input const as well. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-8-ilpo.jarvinen@linux.intel.com --- arch/powerpc/include/asm/machdep.h | 2 +- arch/powerpc/kernel/pci-common.c | 2 +- arch/powerpc/platforms/powernv/pci-sriov.c | 4 ++-- arch/powerpc/platforms/powernv/pci.h | 3 ++- arch/powerpc/platforms/pseries/setup.c | 2 +- drivers/pci/iov.c | 7 ++++--- drivers/pci/pci.h | 5 +++-- include/linux/pci.h | 8 +++++--- 8 files changed, 19 insertions(+), 14 deletions(-) diff --git a/arch/powerpc/include/asm/machdep.h b/arch/powerpc/include/asm/machdep.h index 3298eec123a3..256f9309bf4f 100644 --- a/arch/powerpc/include/asm/machdep.h +++ b/arch/powerpc/include/asm/machdep.h @@ -169,7 +169,7 @@ struct machdep_calls { #ifdef CONFIG_PCI_IOV void (*pcibios_fixup_sriov)(struct pci_dev *pdev); - resource_size_t (*pcibios_iov_resource_alignment)(struct pci_dev *, int resno); + resource_size_t (*pcibios_iov_resource_alignment)(const struct pci_dev *, int resno); int (*pcibios_sriov_enable)(struct pci_dev *pdev, u16 num_vfs); int (*pcibios_sriov_disable)(struct pci_dev *pdev); #endif /* CONFIG_PCI_IOV */ diff --git a/arch/powerpc/kernel/pci-common.c b/arch/powerpc/kernel/pci-common.c index 8efe95a0c4ff..3c4ca90e2ab7 100644 --- a/arch/powerpc/kernel/pci-common.c +++ b/arch/powerpc/kernel/pci-common.c @@ -254,7 +254,7 @@ resource_size_t pcibios_default_alignment(void) } #ifdef CONFIG_PCI_IOV -resource_size_t pcibios_iov_resource_alignment(struct pci_dev *pdev, int resno) +resource_size_t pcibios_iov_resource_alignment(const struct pci_dev *pdev, int resno) { if (ppc_md.pcibios_iov_resource_alignment) return ppc_md.pcibios_iov_resource_alignment(pdev, resno); diff --git a/arch/powerpc/platforms/powernv/pci-sriov.c b/arch/powerpc/platforms/powernv/pci-sriov.c index 7105a573aec4..8652078801f2 100644 --- a/arch/powerpc/platforms/powernv/pci-sriov.c +++ b/arch/powerpc/platforms/powernv/pci-sriov.c @@ -244,8 +244,8 @@ void pnv_pci_ioda_fixup_iov(struct pci_dev *pdev) } } -resource_size_t pnv_pci_iov_resource_alignment(struct pci_dev *pdev, - int resno) +resource_size_t pnv_pci_iov_resource_alignment(const struct pci_dev *pdev, + int resno) { resource_size_t align = pci_iov_resource_size(pdev, resno); struct pnv_phb *phb = pci_bus_to_pnvhb(pdev->bus); diff --git a/arch/powerpc/platforms/powernv/pci.h b/arch/powerpc/platforms/powernv/pci.h index 032b2081aedb..3ac718d471c2 100644 --- a/arch/powerpc/platforms/powernv/pci.h +++ b/arch/powerpc/platforms/powernv/pci.h @@ -257,7 +257,8 @@ static inline struct pnv_iov_data *pnv_iov_get(const struct pci_dev *pdev) } void pnv_pci_ioda_fixup_iov(struct pci_dev *pdev); -resource_size_t pnv_pci_iov_resource_alignment(struct pci_dev *pdev, int resno); +resource_size_t pnv_pci_iov_resource_alignment(const struct pci_dev *pdev, + int resno); int pnv_pcibios_sriov_enable(struct pci_dev *pdev, u16 num_vfs); int pnv_pcibios_sriov_disable(struct pci_dev *pdev); diff --git a/arch/powerpc/platforms/pseries/setup.c b/arch/powerpc/platforms/pseries/setup.c index b670c6fdfcea..1223dc961242 100644 --- a/arch/powerpc/platforms/pseries/setup.c +++ b/arch/powerpc/platforms/pseries/setup.c @@ -789,7 +789,7 @@ static void pseries_pci_fixup_iov_resources(struct pci_dev *pdev) pseries_disable_sriov_resources(pdev); } -static resource_size_t pseries_pci_iov_resource_alignment(struct pci_dev *pdev, +static resource_size_t pseries_pci_iov_resource_alignment(const struct pci_dev *pdev, int resno) { const __be32 *reg; diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c index 91ac4e37ecb9..c86409835f73 100644 --- a/drivers/pci/iov.c +++ b/drivers/pci/iov.c @@ -150,7 +150,7 @@ static void virtfn_remove_bus(struct pci_bus *physbus, struct pci_bus *virtbus) pci_remove_bus(virtbus); } -resource_size_t pci_iov_resource_size(struct pci_dev *dev, int resno) +resource_size_t pci_iov_resource_size(const struct pci_dev *dev, int resno) { if (!dev->is_physfn) return 0; @@ -1084,7 +1084,7 @@ void pci_iov_update_resource(struct pci_dev *dev, int resno) } } -resource_size_t __weak pcibios_iov_resource_alignment(struct pci_dev *dev, +resource_size_t __weak pcibios_iov_resource_alignment(const struct pci_dev *dev, int resno) { return pci_iov_resource_size(dev, resno); @@ -1100,7 +1100,8 @@ resource_size_t __weak pcibios_iov_resource_alignment(struct pci_dev *dev, * the VF BAR size multiplied by the number of VFs. The alignment * is just the VF BAR size. */ -resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno) +resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev, + int resno) { return pcibios_iov_resource_alignment(dev, resno); } diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4fcf5a25ad9e..710803be3a79 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -947,7 +947,8 @@ int pci_iov_init(struct pci_dev *dev); void pci_iov_release(struct pci_dev *dev); void pci_iov_remove(struct pci_dev *dev); void pci_iov_update_resource(struct pci_dev *dev, int resno); -resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, int resno); +resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev, + int resno); void pci_restore_iov_state(struct pci_dev *dev); int pci_iov_bus_range(struct pci_bus *bus); void pci_iov_resource_set_size(struct pci_dev *dev, int resno, int size); @@ -981,7 +982,7 @@ static inline int pci_iov_init(struct pci_dev *dev) static inline void pci_iov_release(struct pci_dev *dev) { } static inline void pci_iov_remove(struct pci_dev *dev) { } static inline void pci_iov_update_resource(struct pci_dev *dev, int resno) { } -static inline resource_size_t pci_sriov_resource_alignment(struct pci_dev *dev, +static inline resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev, int resno) { return 0; diff --git a/include/linux/pci.h b/include/linux/pci.h index 2c4454583c11..0ced3bbd08c0 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -2540,7 +2540,7 @@ int pci_vfs_assigned(struct pci_dev *dev); int pci_sriov_set_totalvfs(struct pci_dev *dev, u16 numvfs); int pci_sriov_get_totalvfs(struct pci_dev *dev); int pci_sriov_configure_simple(struct pci_dev *dev, int nr_virtfn); -resource_size_t pci_iov_resource_size(struct pci_dev *dev, int resno); +resource_size_t pci_iov_resource_size(const struct pci_dev *dev, int resno); int pci_iov_vf_bar_set_size(struct pci_dev *dev, int resno, int size); u32 pci_iov_vf_bar_get_sizes(struct pci_dev *dev, int resno, int num_vfs); void pci_vf_drivers_autoprobe(struct pci_dev *dev, bool probe); @@ -2548,7 +2548,8 @@ void pci_vf_drivers_autoprobe(struct pci_dev *dev, bool probe); /* Arch may override these (weak) */ int pcibios_sriov_enable(struct pci_dev *pdev, u16 num_vfs); int pcibios_sriov_disable(struct pci_dev *pdev); -resource_size_t pcibios_iov_resource_alignment(struct pci_dev *dev, int resno); +resource_size_t pcibios_iov_resource_alignment(const struct pci_dev *dev, + int resno); #else static inline int pci_iov_virtfn_bus(struct pci_dev *dev, int id) { @@ -2593,7 +2594,8 @@ static inline int pci_sriov_set_totalvfs(struct pci_dev *dev, u16 numvfs) static inline int pci_sriov_get_totalvfs(struct pci_dev *dev) { return 0; } #define pci_sriov_configure_simple NULL -static inline resource_size_t pci_iov_resource_size(struct pci_dev *dev, int resno) +static inline resource_size_t pci_iov_resource_size(const struct pci_dev *dev, + int resno) { return 0; } static inline int pci_iov_vf_bar_set_size(struct pci_dev *dev, int resno, int size) { return -ENODEV; } From 1845201aa572807e1639fe20b06625d738391fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:14 +0300 Subject: [PATCH 139/168] PCI: Convert pci_resource_alignment() input parameters to const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_resource_alignment() calculates resource alignment and should not alter its input structs. Make its input parameters const. It requires making also pci_cardbus_resource_alignment() input const. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-9-ilpo.jarvinen@linux.intel.com --- drivers/pci/pci.h | 8 ++++---- drivers/pci/setup-cardbus.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 710803be3a79..e0fcc33dfef6 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -419,7 +419,7 @@ static inline bool pci_is_cardbus_bridge(struct pci_dev *dev) return dev->hdr_type == PCI_HEADER_TYPE_CARDBUS; } #ifdef CONFIG_CARDBUS -unsigned long pci_cardbus_resource_alignment(struct resource *res); +unsigned long pci_cardbus_resource_alignment(const struct resource *res); int pci_bus_size_cardbus_bridge(struct pci_bus *bus, struct list_head *realloc_head); int pci_cardbus_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, @@ -428,7 +428,7 @@ int pci_cardbus_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, int pci_setup_cardbus(char *str); #else -static inline unsigned long pci_cardbus_resource_alignment(struct resource *res) +static inline unsigned long pci_cardbus_resource_alignment(const struct resource *res) { return 0; } @@ -1044,8 +1044,8 @@ static inline void pci_suspend_ptm(struct pci_dev *dev) { } static inline void pci_resume_ptm(struct pci_dev *dev) { } #endif -static inline resource_size_t pci_resource_alignment(struct pci_dev *dev, - struct resource *res) +static inline resource_size_t pci_resource_alignment(const struct pci_dev *dev, + const struct resource *res) { int resno = pci_resource_num(dev, res); diff --git a/drivers/pci/setup-cardbus.c b/drivers/pci/setup-cardbus.c index 1ebd13a1f730..0cba404080ad 100644 --- a/drivers/pci/setup-cardbus.c +++ b/drivers/pci/setup-cardbus.c @@ -22,7 +22,7 @@ static unsigned long pci_cardbus_io_size = DEFAULT_CARDBUS_IO_SIZE; static unsigned long pci_cardbus_mem_size = DEFAULT_CARDBUS_MEM_SIZE; -unsigned long pci_cardbus_resource_alignment(struct resource *res) +unsigned long pci_cardbus_resource_alignment(const struct resource *res) { if (res->flags & IORESOURCE_IO) return pci_cardbus_io_size; From e9310aa3d2a1fa155565e253841fff841e31db64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Wed, 29 Apr 2026 15:26:15 +0300 Subject: [PATCH 140/168] PCI: Move pci_resource_alignment() to setup-res.c file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_resource_alignment() is a bit on the complex side to have in a header so put it into setup-res.c. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260429122617.7324-10-ilpo.jarvinen@linux.intel.com --- drivers/pci/pci.h | 13 ++----------- drivers/pci/setup-res.c | 12 ++++++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index e0fcc33dfef6..472b6c2f7c4d 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -1044,17 +1044,8 @@ static inline void pci_suspend_ptm(struct pci_dev *dev) { } static inline void pci_resume_ptm(struct pci_dev *dev) { } #endif -static inline resource_size_t pci_resource_alignment(const struct pci_dev *dev, - const struct resource *res) -{ - int resno = pci_resource_num(dev, res); - - if (pci_resource_is_iov(resno)) - return pci_sriov_resource_alignment(dev, resno); - if (dev->class >> 8 == PCI_CLASS_BRIDGE_CARDBUS) - return pci_cardbus_resource_alignment(res); - return resource_alignment(res); -} +resource_size_t pci_resource_alignment(const struct pci_dev *dev, + const struct resource *res); resource_size_t pci_min_window_alignment(struct pci_bus *bus, unsigned long type); diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index 0d203325562b..18e8775ea848 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -246,6 +246,18 @@ static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev, return 0; } +resource_size_t pci_resource_alignment(const struct pci_dev *dev, + const struct resource *res) +{ + int resno = pci_resource_num(dev, res); + + if (pci_resource_is_iov(resno)) + return pci_sriov_resource_alignment(dev, resno); + if (dev->class >> 8 == PCI_CLASS_BRIDGE_CARDBUS) + return pci_cardbus_resource_alignment(res); + return resource_alignment(res); +} + /* * For mem bridge windows, try to relocate tail remainder space to space * before res->start if there's enough free space there. This enables From c6d51515ee0b79a52b7e532b6b20067fa0cec96f Mon Sep 17 00:00:00 2001 From: Han Gao Date: Wed, 1 Apr 2026 01:12:47 +0800 Subject: [PATCH 141/168] dt-bindings: PCI: sophgo: Add dma-coherent property for SG2042 Add dma-coherent as an allowed property in the SG2042 PCIe host controller binding. SG2042's PCIe Root Complexes are cache-coherent with the CPU. Signed-off-by: Han Gao Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260331171248.973014-2-gaohan@iscas.ac.cn --- .../devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml b/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml index f8b7ca57fff1..ab482488b047 100644 --- a/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml +++ b/Documentation/devicetree/bindings/pci/sophgo,sg2042-pcie-host.yaml @@ -30,6 +30,8 @@ properties: device-id: const: 0x2042 + dma-coherent: true + msi-parent: true allOf: @@ -60,5 +62,6 @@ examples: vendor-id = <0x1f1c>; device-id = <0x2042>; cdns,no-bar-match-nbits = <48>; + dma-coherent; msi-parent = <&msi>; }; From e8433f632171c5a8c9c43dfa61d347b09ab1ecc3 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 1 May 2026 11:24:07 +0100 Subject: [PATCH 142/168] dt-bindings: PCI: renesas,r9a08g045-pcie: Add RZ/V2N support Document the Renesas RZ/V2N PCIe host controller, which is compatible with the RZ/G3E PCIe IP and therefore uses it as a fallback compatible. The only difference is that it uses device ID 0x003B. Make the binding title generic to avoid extending the title for each new SoC, and update the description to list the supported SoCs and their capabilities. Signed-off-by: Lad Prabhakar Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Claudiu Beznea Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260501102407.29462-1-prabhakar.mahadev-lad.rj@bp.renesas.com --- .../bindings/pci/renesas,r9a08g045-pcie.yaml | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml b/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml index a67108c48feb..90086909e921 100644 --- a/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml +++ b/Documentation/devicetree/bindings/pci/renesas,r9a08g045-pcie.yaml @@ -4,21 +4,27 @@ $id: http://devicetree.org/schemas/pci/renesas,r9a08g045-pcie.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Renesas RZ/G3S PCIe host controller +title: Renesas RZ/G3S PCIe host controller (and similar SoCs) maintainers: - Claudiu Beznea -description: - Renesas RZ/G3{E,S} PCIe host controllers comply with PCIe - Base Specification 4.0 and support up to 5 GT/s (Gen2) for RZ/G3S and - up to 8 GT/s (Gen3) for RZ/G3E. +description: | + PCIe host controller found in Renesas RZ/G3S and similar SoCs complies + with PCIe Base Specification 4.0 and supports different link speeds + depending on the SoC variant: + - Gen2 (5 GT/s): RZ/G3S + - Gen3 (8 GT/s): RZ/G3E, RZ/V2N properties: compatible: - enum: - - renesas,r9a08g045-pcie # RZ/G3S - - renesas,r9a09g047-pcie # RZ/G3E + oneOf: + - enum: + - renesas,r9a08g045-pcie # RZ/G3S + - renesas,r9a09g047-pcie # RZ/G3E + - items: + - const: renesas,r9a09g056-pcie # RZ/V2N + - const: renesas,r9a09g047-pcie reg: maxItems: 1 @@ -152,6 +158,7 @@ patternProperties: enum: - 0x0033 - 0x0039 + - 0x003b clocks: items: From ad4c2a8fd8fda50bc97ca40ad679d5c92311ae15 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Mon, 8 Jun 2026 14:18:14 +0530 Subject: [PATCH 143/168] dt-bindings: PCI: qcom,pcie-sm8550: Add Eliza compatible PCIe controller present in Eliza SoC is backwards compatible with the controller present in SM8550 SoC. Hence, add the compatible with SM8550 fallback. Eliza requires 6 reg entries, 8 clocks and 9 interrupts, so add the corresponding allOf constraints. Signed-off-by: Krishna Chaitanya Chundru Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260608-eliza-v3-2-9bdeb7434b28@oss.qualcomm.com --- .../bindings/pci/qcom,pcie-sm8550.yaml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml index 3a94a9c1bb15..fb706b1397a3 100644 --- a/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml +++ b/Documentation/devicetree/bindings/pci/qcom,pcie-sm8550.yaml @@ -20,6 +20,7 @@ properties: - const: qcom,pcie-sm8550 - items: - enum: + - qcom,eliza-pcie - qcom,kaanapali-pcie - qcom,sar2130p-pcie - qcom,pcie-sm8650 @@ -91,6 +92,55 @@ required: allOf: - $ref: qcom,pcie-common.yaml# + - if: + properties: + compatible: + contains: + const: qcom,eliza-pcie + then: + properties: + reg: + minItems: 6 + reg-names: + minItems: 6 + + - if: + properties: + compatible: + contains: + const: qcom,eliza-pcie + then: + properties: + clocks: + minItems: 8 + maxItems: 8 + clock-names: + minItems: 8 + maxItems: 8 + + - if: + properties: + compatible: + contains: + const: qcom,eliza-pcie + then: + properties: + interrupts: + minItems: 9 + interrupt-names: + minItems: 9 + + - if: + properties: + compatible: + contains: + const: qcom,eliza-pcie + then: + properties: + resets: + minItems: 2 + reset-names: + minItems: 2 unevaluatedProperties: false From 57d3400df05222ee8ab32223150df2472a471d02 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Mon, 18 May 2026 08:42:42 +0800 Subject: [PATCH 144/168] PCI: cadence-hpa: Add post-link delay The Cadence HPA (High Performance Architecture IP) specific link setup function cdns_pcie_hpa_host_link_setup() waits for the link to come up but does not implement the required 100 ms delay after link training completes for speeds > 5.0 GT/s (PCIe r6.0 sec 6.6.1). Add a call to pci_host_common_link_train_delay() immediately after the link is confirmed to be up, using the max_link_speed field. Also, in the HPA host setup function, read the device tree property "max-link-speed" to initialize max_link_speed if not already set by a glue driver. This ensures compliance for HPA-based platforms. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam [bhelgaas: driver tag "cadence: HPA:" -> "cadence-hpa:"] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260518004246.1384532-4-18255117159@163.com --- drivers/pci/controller/cadence/pcie-cadence-host-hpa.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/pci/controller/cadence/pcie-cadence-host-hpa.c b/drivers/pci/controller/cadence/pcie-cadence-host-hpa.c index 0f540bed58e8..8ef58ed01daa 100644 --- a/drivers/pci/controller/cadence/pcie-cadence-host-hpa.c +++ b/drivers/pci/controller/cadence/pcie-cadence-host-hpa.c @@ -15,6 +15,8 @@ #include "pcie-cadence.h" #include "pcie-cadence-host-common.h" +#include "../pci-host-common.h" +#include "../../pci.h" static u8 bar_aperture_mask[] = { [RP_BAR0] = 0x3F, @@ -304,6 +306,8 @@ int cdns_pcie_hpa_host_link_setup(struct cdns_pcie_rc *rc) ret = cdns_pcie_host_wait_for_link(pcie, cdns_pcie_hpa_link_up); if (ret) dev_dbg(dev, "PCIe link never came up\n"); + else + pci_host_common_link_train_delay(pcie->max_link_speed); return ret; } @@ -313,6 +317,7 @@ int cdns_pcie_hpa_host_setup(struct cdns_pcie_rc *rc) { struct device *dev = rc->pcie.dev; struct platform_device *pdev = to_platform_device(dev); + struct device_node *np = dev->of_node; struct pci_host_bridge *bridge; enum cdns_pcie_rp_bar bar; struct cdns_pcie *pcie; @@ -343,6 +348,9 @@ int cdns_pcie_hpa_host_setup(struct cdns_pcie_rc *rc) rc->cfg_res = res; } + if (pcie->max_link_speed < 1) + pcie->max_link_speed = of_pci_get_max_link_speed(np); + /* Put EROM Bar aperture to 0 */ cdns_pcie_hpa_writel(pcie, REG_BANK_IP_CFG_CTRL_REG, CDNS_PCIE_EROM, 0x0); From 0a46957df45af0382ccf0cc43f9b0a1df438605d Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Mon, 18 May 2026 08:42:43 +0800 Subject: [PATCH 145/168] PCI: dwc: Use common pci_host_common_link_train_delay() helper The DWC driver already implements the 100 ms delay required by PCIe r6.0 sec 6.6.1 by checking pci->max_link_speed and calling msleep(100). Replace the open-coded msleep() with the new common helper pci_host_common_link_train_delay() to reduce code duplication and improve maintainability. No functional change intended. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260518004246.1384532-5-18255117159@163.com --- drivers/pci/controller/dwc/pcie-designware.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/pci/controller/dwc/pcie-designware.c b/drivers/pci/controller/dwc/pcie-designware.c index c11cf61b8319..7021d21bb601 100644 --- a/drivers/pci/controller/dwc/pcie-designware.c +++ b/drivers/pci/controller/dwc/pcie-designware.c @@ -22,6 +22,7 @@ #include #include +#include "../pci-host-common.h" #include "../../pci.h" #include "pcie-designware.h" @@ -799,13 +800,7 @@ int dw_pcie_wait_for_link(struct dw_pcie *pci) return -ETIMEDOUT; } - /* - * As per PCIe r6.0, sec 6.6.1, a Downstream Port that supports Link - * speeds greater than 5.0 GT/s, software must wait a minimum of 100 ms - * after Link training completes before sending a Configuration Request. - */ - if (pci->max_link_speed > 2) - msleep(PCIE_RESET_CONFIG_WAIT_MS); + pci_host_common_link_train_delay(pci->max_link_speed); offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP); val = dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKSTA); From 6bba1de54cebcded567563311710f9b3111e2652 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Mon, 18 May 2026 08:42:44 +0800 Subject: [PATCH 146/168] PCI: aardvark: Add 100 ms delay after link training The Aardvark PCIe controller driver waits for the link to come up but does not implement the mandatory 100 ms delay after link training completes for speeds greater than 5.0 GT/s (PCIe r6.0 sec 6.6.1). The driver already maintains a 'link_gen' field that holds the negotiated link speed. Use it together with pci_host_common_link_train_delay() to insert the required delay immediately after confirming that the link is up. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260518004246.1384532-6-18255117159@163.com --- drivers/pci/controller/pci-aardvark.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c index e34bea1ff0ac..fd9c7d53e8a7 100644 --- a/drivers/pci/controller/pci-aardvark.c +++ b/drivers/pci/controller/pci-aardvark.c @@ -26,6 +26,7 @@ #include #include +#include "pci-host-common.h" #include "../pci.h" #include "../pci-bridge-emul.h" @@ -350,8 +351,10 @@ static int advk_pcie_wait_for_link(struct advk_pcie *pcie) /* check if the link is up or not */ for (retries = 0; retries < LINK_WAIT_MAX_RETRIES; retries++) { - if (advk_pcie_link_up(pcie)) + if (advk_pcie_link_up(pcie)) { + pci_host_common_link_train_delay(pcie->link_gen); return 0; + } usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); } From d24e3fab6ee23c2d7076b0e5ffe5c7210cc9dae3 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Mon, 18 May 2026 08:42:45 +0800 Subject: [PATCH 147/168] PCI: mediatek-gen3: Add 100 ms delay after link up The MediaTek Gen3 PCIe host driver lacks the required 100 ms delay after link training completes for speeds > 5.0 GT/s, as specified in PCIe r6.0 sec 6.6.1. The driver already stores max_link_speed (from the device tree). After mtk_pcie_startup_port() successfully brings up the link, call pci_host_common_link_train_delay() to comply with the specification. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260518004246.1384532-7-18255117159@163.com --- drivers/pci/controller/pcie-mediatek-gen3.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/pci/controller/pcie-mediatek-gen3.c b/drivers/pci/controller/pcie-mediatek-gen3.c index b0accd828589..5abddec4e9be 100644 --- a/drivers/pci/controller/pcie-mediatek-gen3.c +++ b/drivers/pci/controller/pcie-mediatek-gen3.c @@ -30,6 +30,7 @@ #include #include +#include "pci-host-common.h" #include "../pci.h" #define PCIE_BASE_CFG_REG 0x14 @@ -570,6 +571,8 @@ static int mtk_pcie_startup_port(struct mtk_gen3_pcie *pcie) goto err_power_down_device; } + pci_host_common_link_train_delay(pcie->max_link_speed); + return 0; err_power_down_device: From 2b0d1a605ef22c063be9de475d7a66c311b710f4 Mon Sep 17 00:00:00 2001 From: Hans Zhang <18255117159@163.com> Date: Mon, 18 May 2026 08:42:46 +0800 Subject: [PATCH 148/168] PCI: rzg3s-host: Use common pci_host_common_link_train_delay() helper Replace the unconditional msleep(100) with the common helper pci_host_common_link_train_delay(). The helper only waits when max_link_speed > 2, as required by PCIe r6.0 sec 6.6.1. This avoids unnecessary delay for Gen1/Gen2 links while retaining the mandatory 100 ms for higher speeds. Signed-off-by: Hans Zhang <18255117159@163.com> Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260518004246.1384532-8-18255117159@163.com --- drivers/pci/controller/pcie-rzg3s-host.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-rzg3s-host.c b/drivers/pci/controller/pcie-rzg3s-host.c index d86e7516dcc2..66f687304c1c 100644 --- a/drivers/pci/controller/pcie-rzg3s-host.c +++ b/drivers/pci/controller/pcie-rzg3s-host.c @@ -35,6 +35,7 @@ #include #include +#include "pci-host-common.h" #include "../pci.h" /* AXI registers */ @@ -1663,7 +1664,7 @@ rzg3s_pcie_host_setup(struct rzg3s_pcie_host *host, if (ret) dev_info(dev, "Failed to set max link speed\n"); - msleep(PCIE_RESET_CONFIG_WAIT_MS); + pci_host_common_link_train_delay(host->max_link_speed); return 0; From 8857f6578b001bcf5f53c8c6a3936647f05291a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Thu, 11 Jun 2026 15:05:43 +0000 Subject: [PATCH 149/168] PCI/proc: Fix race between pci_proc_init() and pci_bus_add_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_proc_attach_device() creates procfs entries for PCI devices and is called from pci_bus_add_device(). It lazily creates the per-bus procfs directory (bus->procdir) via proc_mkdir() on first use, and returns early if proc_initialized is not yet set. On x86 with ACPI, PCI enumeration occurs at subsys_initcall, before pci_proc_init() sets proc_initialized at device_initcall. The for_each_pci_dev() loop in pci_proc_init() then creates procfs entries for these already-enumerated devices, but runs without holding pci_rescan_remove_lock. On ARM64 with devicetree, PCI host bridges probe at device_initcall. With async probing enabled, pci_bus_add_device() can run concurrently with pci_proc_init(), and both may call pci_proc_attach_device() for the same device or for different devices on the same bus. As pci_host_probe() holds pci_rescan_remove_lock while pci_proc_init() does not, there is no serialisation between the two paths. When two threads concurrently call pci_proc_attach_device() for devices on the same bus, both observe bus->procdir as NULL and both call proc_mkdir(). The proc filesystem serialises directory creation internally, so only one caller succeeds. The other results in a warning like: proc_dir_entry '000c:00/00.0' already registered The caller receives NULL (duplicate entry) and unconditionally stores it to bus->procdir, corrupting the valid pointer set by the first caller. Serialise access to proc_initialized, proc_bus_pci_dir, bus->procdir and dev->procent with a new mutex local to drivers/pci/proc.c, and store the created entries to bus->procdir and dev->procent only on success, so a failed creation can never overwrite a valid pointer. Additionally, wrap the for_each_pci_dev() loop in pci_proc_init() with pci_lock_rescan_remove() to serialise against concurrent PCI bus operations, add an early return in pci_proc_attach_device() when dev->procent is already set to make the function idempotent, and clear bus->procdir in pci_proc_detach_bus() to prevent use of a dangling pointer after proc_remove(). Reported-by: Shuan He Closes: https://lore.kernel.org/linux-pci/20250702155112.40124-2-heshuan@bytedance.com/ Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Link: https://lore.kernel.org/r/20260611150543.511422-1-kwilczynski@kernel.org --- drivers/pci/proc.c | 79 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c index ce36e35681e8..71ad289fcb8e 100644 --- a/drivers/pci/proc.c +++ b/drivers/pci/proc.c @@ -18,6 +18,7 @@ #include "pci.h" static int proc_initialized; /* = 0 */ +static DEFINE_MUTEX(pci_proc_lock); static loff_t proc_bus_pci_lseek(struct file *file, loff_t off, int whence) { @@ -416,40 +417,64 @@ static const struct seq_operations proc_bus_pci_devices_op = { static struct proc_dir_entry *proc_bus_pci_dir; -int pci_proc_attach_device(struct pci_dev *dev) +static int __pci_proc_attach_bus(struct pci_bus *bus) { - struct pci_bus *bus = dev->bus; - struct proc_dir_entry *e; + struct proc_dir_entry *dir; char name[16]; + lockdep_assert_held(&pci_proc_lock); + if (!proc_initialized) return -EACCES; - if (!bus->procdir) { - if (pci_proc_domain(bus)) { - sprintf(name, "%04x:%02x", pci_domain_nr(bus), - bus->number); - } else { - sprintf(name, "%02x", bus->number); - } - bus->procdir = proc_mkdir(name, proc_bus_pci_dir); - if (!bus->procdir) - return -ENOMEM; - } + if (bus->procdir) + return 0; + + if (pci_proc_domain(bus)) + sprintf(name, "%04x:%02x", pci_domain_nr(bus), bus->number); + else + sprintf(name, "%02x", bus->number); + + dir = proc_mkdir(name, proc_bus_pci_dir); + if (!dir) + return -ENOMEM; + + bus->procdir = dir; + + return 0; +} + +int pci_proc_attach_device(struct pci_dev *dev) +{ + struct pci_bus *bus = dev->bus; + struct proc_dir_entry *entry; + char name[16]; + int ret; + + guard(mutex)(&pci_proc_lock); + + if (dev->procent) + return 0; + + ret = __pci_proc_attach_bus(bus); + if (ret) + return ret; sprintf(name, "%02x.%x", PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn)); - e = proc_create_data(name, S_IFREG | S_IRUGO | S_IWUSR, bus->procdir, - &proc_bus_pci_ops, dev); - if (!e) + entry = proc_create_data(name, S_IFREG | S_IRUGO | S_IWUSR, + bus->procdir, &proc_bus_pci_ops, dev); + if (!entry) return -ENOMEM; - proc_set_size(e, dev->cfg_size); - dev->procent = e; + + proc_set_size(entry, dev->cfg_size); + dev->procent = entry; return 0; } int pci_proc_detach_device(struct pci_dev *dev) { + guard(mutex)(&pci_proc_lock); proc_remove(dev->procent); dev->procent = NULL; return 0; @@ -457,19 +482,27 @@ int pci_proc_detach_device(struct pci_dev *dev) int pci_proc_detach_bus(struct pci_bus *bus) { + guard(mutex)(&pci_proc_lock); proc_remove(bus->procdir); + bus->procdir = NULL; return 0; } static int __init pci_proc_init(void) { struct pci_dev *dev = NULL; - proc_bus_pci_dir = proc_mkdir("bus/pci", NULL); - proc_create_seq("devices", 0, proc_bus_pci_dir, - &proc_bus_pci_devices_op); - proc_initialized = 1; + + scoped_guard(mutex, &pci_proc_lock) { + proc_bus_pci_dir = proc_mkdir("bus/pci", NULL); + proc_create_seq("devices", 0, proc_bus_pci_dir, + &proc_bus_pci_devices_op); + proc_initialized = 1; + } + + pci_lock_rescan_remove(); for_each_pci_dev(dev) pci_proc_attach_device(dev); + pci_unlock_rescan_remove(); return 0; } From ca617c60af9f6bb74216645ce4362dcb96697d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:26 +0000 Subject: [PATCH 150/168] PCI/sysfs: Convert PCI resource files to static attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the PCI resource files (resourceN, resourceN_wc) are dynamically created by pci_create_sysfs_dev_files(), called from both pci_bus_add_device() and the pci_sysfs_init() late_initcall, with only a sysfs_initialized flag for synchronisation. This has caused warnings and boot panics when both paths race on the same device, e.g.: sysfs: cannot create duplicate filename '/devices/pci0000:3c/0000:3c:01.0/0000:3e:00.2/resource2' This is especially likely on Devicetree-based platforms, where the PCI host controllers are platform drivers that probe via the driver model, which can happen during or after the late_initcall. As such, pci_bus_add_device() and pci_sysfs_init() are more likely to overlap. Convert to static const attributes with three attribute groups (I/O, UC, WC), each with an .is_bin_visible() callback that checks resource flags, BAR length, and non_mappable_bars. A .bin_size() callback provides pci_resource_len() to the kernfs node for correct stat and lseek behaviour. As part of this conversion: - Rename pci_read_resource_io() and pci_write_resource_io() to pci_read_resource() and pci_write_resource() since the callbacks are no longer I/O-specific in the static attribute context. - Update __resource_resize_store() to use sysfs_create_groups() and sysfs_remove_groups(), which re-evaluates visibility and runs the .bin_size() callback for the static resource attribute groups. - Remove pci_create_resource_files(), pci_remove_resource_files(), and pci_create_attr() which are no longer needed. - Move the __weak stubs outside the #if guard so they remain available for callers converted in subsequent commits. Platforms that do not define the HAVE_PCI_MMAP macro or the ARCH_GENERIC_PCI_MMAP_RESOURCE macro, such as Alpha architecture, continue using their platform-specific resource file creation. For reference, the dynamic creation dates back to the pre-Git era: https://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git/commit/drivers/pci/pci-sysfs.c?id=42298be0eeb5ae98453b3374c36161b05a46c5dc The write-combine support was added in commit 45aec1ae72fc ("x86: PAT export resource_wc in pci sysfs"). Many other reports mentioned in the cover letter (first Link: below). Link: https://lore.kernel.org/r/20260508043543.217179-1-kwilczynski@kernel.org/ Closes: https://bugzilla.kernel.org/show_bug.cgi?id=215515 Closes: https://github.com/openwrt/openwrt/issues/17143 Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-8-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 285 +++++++++++++++++++++------------------- include/linux/pci.h | 2 - 2 files changed, 152 insertions(+), 135 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index a74eca96918b..3bb808e0e80a 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -890,19 +890,6 @@ pci_llseek_resource_legacy(struct file *filep, return fixed_size_llseek(filep, offset, whence, attr->size); } -static __maybe_unused loff_t -pci_llseek_resource(struct file *filep, - struct kobject *kobj, - const struct bin_attribute *attr, - loff_t offset, int whence) -{ - struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); - int bar = (unsigned long)attr->private; - - return fixed_size_llseek(filep, offset, whence, - pci_resource_len(pdev, bar)); -} - #ifdef HAVE_PCI_LEGACY /** * pci_read_legacy_io - read byte(s) from legacy I/O port space @@ -1177,14 +1164,14 @@ static ssize_t pci_resource_io(struct file *filp, struct kobject *kobj, #endif } -static ssize_t pci_read_resource_io(struct file *filp, struct kobject *kobj, +static ssize_t pci_read_resource(struct file *filp, struct kobject *kobj, const struct bin_attribute *attr, char *buf, loff_t off, size_t count) { return pci_resource_io(filp, kobj, attr, buf, off, count, false); } -static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj, +static ssize_t pci_write_resource(struct file *filp, struct kobject *kobj, const struct bin_attribute *attr, char *buf, loff_t off, size_t count) { @@ -1197,6 +1184,18 @@ static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj, return pci_resource_io(filp, kobj, attr, buf, off, count, true); } +static loff_t pci_llseek_resource(struct file *filep, + struct kobject *kobj, + const struct bin_attribute *attr, + loff_t offset, int whence) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + int bar = (unsigned long)attr->private; + + return fixed_size_llseek(filep, offset, whence, + pci_resource_len(pdev, bar)); +} + /* * generic_file_llseek() consults f_mapping->host to determine * the file size. As iomem_inode knows nothing about the @@ -1215,8 +1214,8 @@ static ssize_t pci_write_resource_io(struct file *filp, struct kobject *kobj, static const struct bin_attribute dev_resource##_bar##_io_attr = { \ .attr = { .name = "resource" __stringify(_bar), .mode = 0600 }, \ .private = (void *)(unsigned long)(_bar), \ - .read = pci_read_resource_io, \ - .write = pci_write_resource_io, \ + .read = pci_read_resource, \ + .write = pci_write_resource, \ __PCI_RESOURCE_IO_MMAP_ATTRS \ } @@ -1238,129 +1237,144 @@ static const struct bin_attribute dev_resource##_bar##_wc_attr = { \ .mmap = pci_mmap_resource_wc, \ } -/** - * pci_remove_resource_files - cleanup resource files - * @pdev: dev to cleanup - * - * If we created resource files for @pdev, remove them from sysfs and - * free their resources. - */ -static void pci_remove_resource_files(struct pci_dev *pdev) +static inline umode_t +__pci_resource_attr_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int bar, bool write_combine, + unsigned long flags) { - int i; + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); - for (i = 0; i < PCI_STD_NUM_BARS; i++) { - struct bin_attribute *res_attr; - - res_attr = pdev->res_attr[i]; - if (res_attr) { - sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); - kfree(res_attr); - } - - res_attr = pdev->res_attr_wc[i]; - if (res_attr) { - sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); - kfree(res_attr); - } - } -} - -static int pci_create_attr(struct pci_dev *pdev, int num, int write_combine) -{ - /* allocate attribute structure, piggyback attribute name */ - int name_len = write_combine ? 13 : 10; - struct bin_attribute *res_attr; - char *res_attr_name; - int retval; - - res_attr = kzalloc(sizeof(*res_attr) + name_len, GFP_ATOMIC); - if (!res_attr) - return -ENOMEM; - - res_attr_name = (char *)(res_attr + 1); - - sysfs_bin_attr_init(res_attr); - if (write_combine) { - sprintf(res_attr_name, "resource%d_wc", num); - res_attr->mmap = pci_mmap_resource_wc; - } else { - sprintf(res_attr_name, "resource%d", num); - if (pci_resource_flags(pdev, num) & IORESOURCE_IO) { - res_attr->read = pci_read_resource_io; - res_attr->write = pci_write_resource_io; - if (arch_can_pci_mmap_io()) - res_attr->mmap = pci_mmap_resource_uc; - } else { - res_attr->mmap = pci_mmap_resource_uc; - } - } - if (res_attr->mmap) { - res_attr->f_mapping = iomem_get_mapping; - /* - * generic_file_llseek() consults f_mapping->host to determine - * the file size. As iomem_inode knows nothing about the - * attribute, it's not going to work, so override it as well. - */ - res_attr->llseek = pci_llseek_resource; - } - res_attr->attr.name = res_attr_name; - res_attr->attr.mode = 0600; - res_attr->size = pci_resource_len(pdev, num); - res_attr->private = (void *)(unsigned long)num; - retval = sysfs_create_bin_file(&pdev->dev.kobj, res_attr); - if (retval) { - kfree(res_attr); - return retval; - } - - if (write_combine) - pdev->res_attr_wc[num] = res_attr; - else - pdev->res_attr[num] = res_attr; - - return 0; -} - -/** - * pci_create_resource_files - create resource files in sysfs for @dev - * @pdev: dev in question - * - * Walk the resources in @pdev creating files for each resource available. - */ -static int pci_create_resource_files(struct pci_dev *pdev) -{ - int i; - int retval; - - /* Skip devices with non-mappable BARs */ if (pdev->non_mappable_bars) return 0; - /* Expose the PCI resources from this device as files */ - for (i = 0; i < PCI_STD_NUM_BARS; i++) { + if (!pci_resource_len(pdev, bar)) + return 0; - /* skip empty resources */ - if (!pci_resource_len(pdev, i)) - continue; + if ((pci_resource_flags(pdev, bar) & flags) != flags) + return 0; - retval = pci_create_attr(pdev, i, 0); - /* for prefetchable resources, create a WC mappable file */ - if (!retval && arch_can_pci_mmap_wc() && - pci_resource_flags(pdev, i) & IORESOURCE_PREFETCH) - retval = pci_create_attr(pdev, i, 1); - if (retval) { - pci_remove_resource_files(pdev); - return retval; - } - } - return 0; + if (write_combine && !arch_can_pci_mmap_wc()) + return 0; + + return a->attr.mode; } -#else /* !(defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE)) */ -int __weak pci_create_resource_files(struct pci_dev *dev) { return 0; } -void __weak pci_remove_resource_files(struct pci_dev *dev) { return; } + +static umode_t pci_dev_resource_io_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int n) +{ + return __pci_resource_attr_is_visible(kobj, a, n, false, + IORESOURCE_IO); +} + +static umode_t pci_dev_resource_uc_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int n) +{ + return __pci_resource_attr_is_visible(kobj, a, n, false, + IORESOURCE_MEM); +} + +static umode_t pci_dev_resource_wc_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int n) +{ + return __pci_resource_attr_is_visible(kobj, a, n, true, + IORESOURCE_MEM | IORESOURCE_PREFETCH); +} + +static size_t pci_dev_resource_bin_size(struct kobject *kobj, + const struct bin_attribute *a, + int n) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + + return pci_resource_len(pdev, n); +} + +pci_dev_resource_io_attr(0); +pci_dev_resource_io_attr(1); +pci_dev_resource_io_attr(2); +pci_dev_resource_io_attr(3); +pci_dev_resource_io_attr(4); +pci_dev_resource_io_attr(5); + +pci_dev_resource_uc_attr(0); +pci_dev_resource_uc_attr(1); +pci_dev_resource_uc_attr(2); +pci_dev_resource_uc_attr(3); +pci_dev_resource_uc_attr(4); +pci_dev_resource_uc_attr(5); + +pci_dev_resource_wc_attr(0); +pci_dev_resource_wc_attr(1); +pci_dev_resource_wc_attr(2); +pci_dev_resource_wc_attr(3); +pci_dev_resource_wc_attr(4); +pci_dev_resource_wc_attr(5); + +static const struct bin_attribute *const pci_dev_resource_io_attrs[] = { + &dev_resource0_io_attr, + &dev_resource1_io_attr, + &dev_resource2_io_attr, + &dev_resource3_io_attr, + &dev_resource4_io_attr, + &dev_resource5_io_attr, + NULL, +}; + +static const struct bin_attribute *const pci_dev_resource_uc_attrs[] = { + &dev_resource0_uc_attr, + &dev_resource1_uc_attr, + &dev_resource2_uc_attr, + &dev_resource3_uc_attr, + &dev_resource4_uc_attr, + &dev_resource5_uc_attr, + NULL, +}; + +static const struct bin_attribute *const pci_dev_resource_wc_attrs[] = { + &dev_resource0_wc_attr, + &dev_resource1_wc_attr, + &dev_resource2_wc_attr, + &dev_resource3_wc_attr, + &dev_resource4_wc_attr, + &dev_resource5_wc_attr, + NULL, +}; + +static const struct attribute_group pci_dev_resource_io_attr_group = { + .bin_attrs = pci_dev_resource_io_attrs, + .is_bin_visible = pci_dev_resource_io_is_visible, + .bin_size = pci_dev_resource_bin_size, +}; + +static const struct attribute_group pci_dev_resource_uc_attr_group = { + .bin_attrs = pci_dev_resource_uc_attrs, + .is_bin_visible = pci_dev_resource_uc_is_visible, + .bin_size = pci_dev_resource_bin_size, +}; + +static const struct attribute_group pci_dev_resource_wc_attr_group = { + .bin_attrs = pci_dev_resource_wc_attrs, + .is_bin_visible = pci_dev_resource_wc_is_visible, + .bin_size = pci_dev_resource_bin_size, +}; + +static const struct attribute_group *pci_dev_resource_attr_groups[] = { + &pci_dev_resource_io_attr_group, + &pci_dev_resource_uc_attr_group, + &pci_dev_resource_wc_attr_group, + NULL, +}; +#else +#define pci_dev_resource_attr_groups NULL #endif +int __weak pci_create_resource_files(struct pci_dev *dev) { return 0; } +void __weak pci_remove_resource_files(struct pci_dev *dev) { } + /** * pci_write_rom - used to enable access to the PCI ROM display * @filp: sysfs file @@ -1662,14 +1676,14 @@ static ssize_t __resource_resize_store(struct device *dev, int n, pci_write_config_word(pdev, PCI_COMMAND, cmd & ~PCI_COMMAND_MEMORY); - pci_remove_resource_files(pdev); + sysfs_remove_groups(&pdev->dev.kobj, pci_dev_resource_attr_groups); ret = pci_resize_resource(pdev, n, size, 0); pci_assign_unassigned_bus_resources(bus); - if (pci_create_resource_files(pdev)) - pci_warn(pdev, "Failed to recreate resource files after BAR resizing\n"); + if (sysfs_create_groups(&pdev->dev.kobj, pci_dev_resource_attr_groups)) + pci_warn(pdev, "Failed to recreate resource groups after BAR resizing\n"); pci_write_config_word(pdev, PCI_COMMAND, cmd); pm_put: @@ -1838,6 +1852,11 @@ static const struct attribute_group pci_dev_group = { const struct attribute_group *pci_dev_groups[] = { &pci_dev_group, +#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) + &pci_dev_resource_io_attr_group, + &pci_dev_resource_uc_attr_group, + &pci_dev_resource_wc_attr_group, +#endif &pci_dev_config_attr_group, &pci_dev_rom_attr_group, &pci_dev_reset_attr_group, diff --git a/include/linux/pci.h b/include/linux/pci.h index e93fbe6b57fe..974605c9fce2 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -2531,10 +2531,8 @@ int pcibios_alloc_irq(struct pci_dev *dev); void pcibios_free_irq(struct pci_dev *dev); resource_size_t pcibios_default_alignment(void); -#if !defined(HAVE_PCI_MMAP) && !defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) extern int pci_create_resource_files(struct pci_dev *dev); extern void pci_remove_resource_files(struct pci_dev *dev); -#endif #if defined(CONFIG_PCI_MMCONFIG) || defined(CONFIG_ACPI_MCFG) void __init pci_mmcfg_early_init(void); From cf616e0b188beac528f46d656bffd065f4f19529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:27 +0000 Subject: [PATCH 151/168] PCI/sysfs: Warn about BAR resize failure in __resource_resize_store() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a pci_warn() to __resource_resize_store(), so that BAR resize failures are visible to the user, which can help troubleshoot any potential resource resize issues. While at it, rename the resource_resize_is_visible() to resource_resize_attr_is_visible() along with the corresponding group variable to align with the naming convention used by the resource attribute groups. Also, change the order of pci_dev_groups[] such that the resize group is now located alongside the other resource groups. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-9-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 3bb808e0e80a..3db44df63b53 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1679,6 +1679,9 @@ static ssize_t __resource_resize_store(struct device *dev, int n, sysfs_remove_groups(&pdev->dev.kobj, pci_dev_resource_attr_groups); ret = pci_resize_resource(pdev, n, size, 0); + if (ret) + pci_warn(pdev, "Failed to resize BAR %d: %pe\n", + n, ERR_PTR(ret)); pci_assign_unassigned_bus_resources(bus); @@ -1726,7 +1729,7 @@ static struct attribute *resource_resize_attrs[] = { NULL, }; -static umode_t resource_resize_is_visible(struct kobject *kobj, +static umode_t resource_resize_attr_is_visible(struct kobject *kobj, struct attribute *a, int n) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); @@ -1734,9 +1737,9 @@ static umode_t resource_resize_is_visible(struct kobject *kobj, return pci_rebar_get_current_size(pdev, n) < 0 ? 0 : a->mode; } -static const struct attribute_group pci_dev_resource_resize_group = { +static const struct attribute_group pci_dev_resource_resize_attr_group = { .attrs = resource_resize_attrs, - .is_visible = resource_resize_is_visible, + .is_visible = resource_resize_attr_is_visible, }; int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev) @@ -1857,6 +1860,7 @@ const struct attribute_group *pci_dev_groups[] = { &pci_dev_resource_uc_attr_group, &pci_dev_resource_wc_attr_group, #endif + &pci_dev_resource_resize_attr_group, &pci_dev_config_attr_group, &pci_dev_rom_attr_group, &pci_dev_reset_attr_group, @@ -1868,7 +1872,6 @@ const struct attribute_group *pci_dev_groups[] = { #ifdef CONFIG_ACPI &pci_dev_acpi_attr_group, #endif - &pci_dev_resource_resize_group, ARCH_PCI_DEV_GROUPS NULL, }; From 2c31a9675f50a61c9ceed6dab2545be7aa896d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:28 +0000 Subject: [PATCH 152/168] PCI/sysfs: Add stubs for pci_{create,remove}_sysfs_dev_files() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On platforms with HAVE_PCI_MMAP or ARCH_GENERIC_PCI_MMAP_RESOURCE, resource files are now handled by static attribute groups registered via pci_dev_groups[]. Stub out the pci_create_sysfs_dev_files() and pci_remove_sysfs_dev_files(), as the dynamic resource file creation is no longer needed. Also, simplify pci_sysfs_init() on these platforms to only iterate buses for legacy attributes creation, skipping the per-device loop. Move the __weak stubs for pci_create_resource_files() and pci_remove_resource_files() into the #else branch since only platforms without HAVE_PCI_MMAP (such as Alpha architecture) still need them. Guard the res_attr[] and res_attr_wc[] fields in struct pci_dev the same way. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-10-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 15 ++++++++++++--- include/linux/pci.h | 4 ++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 3db44df63b53..102147500596 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1370,10 +1370,9 @@ static const struct attribute_group *pci_dev_resource_attr_groups[] = { }; #else #define pci_dev_resource_attr_groups NULL -#endif - int __weak pci_create_resource_files(struct pci_dev *dev) { return 0; } void __weak pci_remove_resource_files(struct pci_dev *dev) { } +#endif /** * pci_write_rom - used to enable access to the PCI ROM display @@ -1742,6 +1741,10 @@ static const struct attribute_group pci_dev_resource_resize_attr_group = { .is_visible = resource_resize_attr_is_visible, }; +#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) +int pci_create_sysfs_dev_files(struct pci_dev *pdev) { return 0; } +void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { } +#else int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev) { if (!sysfs_initialized) @@ -1763,9 +1766,15 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) pci_remove_resource_files(pdev); } +#endif static int __init pci_sysfs_init(void) { +#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) + struct pci_bus *pbus = NULL; + + sysfs_initialized = 1; +#else struct pci_dev *pdev = NULL; struct pci_bus *pbus = NULL; int retval; @@ -1778,7 +1787,7 @@ static int __init pci_sysfs_init(void) return retval; } } - +#endif while ((pbus = pci_find_next_bus(pbus))) pci_create_legacy_files(pbus); diff --git a/include/linux/pci.h b/include/linux/pci.h index 974605c9fce2..b998a56f6010 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -515,8 +515,10 @@ struct pci_dev { spinlock_t pcie_cap_lock; /* Protects RMW ops in capability accessors */ u32 saved_config_space[16]; /* Config space saved at suspend time */ struct hlist_head saved_cap_space; +#if !defined(HAVE_PCI_MMAP) && !defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */ struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */ +#endif #ifdef CONFIG_HOTPLUG_PCI_PCIE unsigned int broken_cmd_compl:1; /* No compl for some cmds */ @@ -2531,8 +2533,10 @@ int pcibios_alloc_irq(struct pci_dev *dev); void pcibios_free_irq(struct pci_dev *dev); resource_size_t pcibios_default_alignment(void); +#if !defined(HAVE_PCI_MMAP) && !defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) extern int pci_create_resource_files(struct pci_dev *dev); extern void pci_remove_resource_files(struct pci_dev *dev); +#endif #if defined(CONFIG_PCI_MMCONFIG) || defined(CONFIG_ACPI_MCFG) void __init pci_mmcfg_early_init(void); From 4eee7eef239c61d11a2fdd03691d60c5cd91940d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:29 +0000 Subject: [PATCH 153/168] PCI/sysfs: Limit pci_sysfs_init() late_initcall compile scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, pci_sysfs_init() and sysfs_initialized compile unconditionally, even on platforms where static attribute groups handle all resource file creation. Place them behind a new HAVE_PCI_SYSFS_INIT macro, especially as the late_initcall is only needed when: - HAVE_PCI_LEGACY is set, to iterate buses and create legacy I/O and memory files. - Neither HAVE_PCI_MMAP nor ARCH_GENERIC_PCI_MMAP_RESOURCE is set, to iterate devices and create resource files via the __weak pci_create_resource_files() stub override (this is how the Alpha architecture handles this currently). On most systems both conditions are false and the entire late_initcall compiles away. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-11-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 102147500596..0ba4fbf40cdc 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -37,7 +37,14 @@ #define ARCH_PCI_DEV_GROUPS #endif +#if defined(HAVE_PCI_LEGACY) || \ + !defined(HAVE_PCI_MMAP) && !defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) +#define HAVE_PCI_SYSFS_INIT +#endif + +#ifdef HAVE_PCI_SYSFS_INIT static int sysfs_initialized; /* = 0 */ +#endif /* show configuration fields */ #define pci_config_attr(field, format_string) \ @@ -1768,6 +1775,7 @@ void pci_remove_sysfs_dev_files(struct pci_dev *pdev) } #endif +#ifdef HAVE_PCI_SYSFS_INIT static int __init pci_sysfs_init(void) { #if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) @@ -1794,6 +1802,7 @@ static int __init pci_sysfs_init(void) return 0; } late_initcall(pci_sysfs_init); +#endif static struct attribute *pci_dev_dev_attrs[] = { &dev_attr_boot_vga.attr, From 78a228f0aa0e9eba31955950c8a40a9945e2c8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:30 +0000 Subject: [PATCH 154/168] alpha/PCI: Add security_locked_down() check to pci_mmap_resource() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, Alpha's pci_mmap_resource() does not check security_locked_down(LOCKDOWN_PCI_ACCESS) before allowing userspace to mmap PCI BARs. The generic version has had this check since commit eb627e17727e ("PCI: Lock down BAR access when the kernel is locked down") to prevent DMA attacks when the kernel is locked down. Add the same check to Alpha's pci_mmap_resource(). Fixes: eb627e17727e ("PCI: Lock down BAR access when the kernel is locked down") Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-12-kwilczynski@kernel.org --- arch/alpha/kernel/pci-sysfs.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 3048758304b5..2324720c3e83 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -71,7 +72,11 @@ static int pci_mmap_resource(struct kobject *kobj, struct resource *res = attr->private; enum pci_mmap_state mmap_type; struct pci_bus_region bar; - int i; + int i, ret; + + ret = security_locked_down(LOCKDOWN_PCI_ACCESS); + if (ret) + return ret; for (i = 0; i < PCI_STD_NUM_BARS; i++) if (res == &pdev->resource[i]) From 7d9779cb10265f34f405eff1eb50bb955d3f6e7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:31 +0000 Subject: [PATCH 155/168] alpha/PCI: Use BAR index in sysfs attr->private instead of resource pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, Alpha's pci_create_one_attr() stores a resource pointer in attr->private, and pci_mmap_resource() loops through all BARs to find the matching index. Store the BAR index directly in attr->private and retrieve the resource via pci_resource_n(). This eliminates the loop and aligns with the convention used by the generic PCI sysfs code. The PCI core change was first added in the commit dca40b186b75 ("PCI: Use BAR index in sysfs attr->private instead of resource pointer"). Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-13-kwilczynski@kernel.org --- arch/alpha/kernel/pci-sysfs.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 2324720c3e83..2330ab84d59c 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -69,25 +69,20 @@ static int pci_mmap_resource(struct kobject *kobj, struct vm_area_struct *vma, int sparse) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); - struct resource *res = attr->private; + int barno = (unsigned long)attr->private; + struct resource *res = pci_resource_n(pdev, barno); enum pci_mmap_state mmap_type; struct pci_bus_region bar; - int i, ret; + int ret; ret = security_locked_down(LOCKDOWN_PCI_ACCESS); if (ret) return ret; - for (i = 0; i < PCI_STD_NUM_BARS; i++) - if (res == &pdev->resource[i]) - break; - if (i >= PCI_STD_NUM_BARS) - return -ENODEV; - if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start)) return -EINVAL; - if (!__pci_mmap_fits(pdev, i, vma, sparse)) + if (!__pci_mmap_fits(pdev, barno, vma, sparse)) return -EINVAL; pcibios_resource_to_bus(pdev->bus, &bar, res); @@ -170,7 +165,7 @@ static int pci_create_one_attr(struct pci_dev *pdev, int num, char *name, res_attr->attr.name = name; res_attr->attr.mode = S_IRUSR | S_IWUSR; res_attr->size = sparse ? size << 5 : size; - res_attr->private = &pdev->resource[num]; + res_attr->private = (void *)(unsigned long)num; return sysfs_create_bin_file(&pdev->dev.kobj, res_attr); } From 30d01a8c3a13d217921985324ebdce5b404c1ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:32 +0000 Subject: [PATCH 156/168] alpha/PCI: Use PCI resource accessor macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace direct pdev->resource[] accesses with pci_resource_n(), and open-coded res->flags type checks with pci_resource_is_mem() and pci_resource_start() helpers. While at it, move the pci_resource_n() call directly into pcibios_resource_to_bus() and drop the local struct resource pointer. No functional changes intended. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-14-kwilczynski@kernel.org --- arch/alpha/kernel/pci-sysfs.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 2330ab84d59c..5c29f1d2821c 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -70,7 +70,6 @@ static int pci_mmap_resource(struct kobject *kobj, { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); int barno = (unsigned long)attr->private; - struct resource *res = pci_resource_n(pdev, barno); enum pci_mmap_state mmap_type; struct pci_bus_region bar; int ret; @@ -79,15 +78,16 @@ static int pci_mmap_resource(struct kobject *kobj, if (ret) return ret; - if (res->flags & IORESOURCE_MEM && iomem_is_exclusive(res->start)) + if (pci_resource_is_mem(pdev, barno) && + iomem_is_exclusive(pci_resource_start(pdev, barno))) return -EINVAL; if (!__pci_mmap_fits(pdev, barno, vma, sparse)) return -EINVAL; - pcibios_resource_to_bus(pdev->bus, &bar, res); + pcibios_resource_to_bus(pdev->bus, &bar, pci_resource_n(pdev, barno)); vma->vm_pgoff += bar.start >> (PAGE_SHIFT - (sparse ? 5 : 0)); - mmap_type = res->flags & IORESOURCE_MEM ? pci_mmap_mem : pci_mmap_io; + mmap_type = pci_resource_is_mem(pdev, barno) ? pci_mmap_mem : pci_mmap_io; return hose_mmap_page_range(pdev->sysdata, vma, mmap_type, sparse); } @@ -141,7 +141,7 @@ static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num) long dense_offset; unsigned long sparse_size; - pcibios_resource_to_bus(pdev->bus, &bar, &pdev->resource[num]); + pcibios_resource_to_bus(pdev->bus, &bar, pci_resource_n(pdev, num)); /* All core logic chips have 4G sparse address space, except CIA which has 16G (see xxx_SPARSE_MEM and xxx_DENSE_MEM @@ -181,7 +181,7 @@ static int pci_create_attr(struct pci_dev *pdev, int num) suffix = ""; /* Assume bwx machine, normal resourceN files. */ nlen1 = 10; - if (pdev->resource[num].flags & IORESOURCE_MEM) { + if (pci_resource_is_mem(pdev, num)) { sparse_base = hose->sparse_mem_base; dense_base = hose->dense_mem_base; if (sparse_base && !sparse_mem_mmap_fits(pdev, num)) { From 802a3b3f470b9bc1148f26e01fc9cbfeb4dfcb57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:33 +0000 Subject: [PATCH 157/168] alpha/PCI: Fix __pci_mmap_fits() overflow for zero-length BARs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, __pci_mmap_fits() computes the BAR size using "pci_resource_len() - 1", which wraps to a large value when the BAR length is zero, causing the bounds check to incorrectly succeed. Add an early return for empty resources. Fixes: 10a0ef39fbd1 ("PCI/alpha: pci sysfs resources") Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-15-kwilczynski@kernel.org --- arch/alpha/kernel/pci-sysfs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 5c29f1d2821c..8802f955256e 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -37,12 +37,16 @@ static int hose_mmap_page_range(struct pci_controller *hose, static int __pci_mmap_fits(struct pci_dev *pdev, int num, struct vm_area_struct *vma, int sparse) { + resource_size_t len = pci_resource_len(pdev, num); unsigned long nr, start, size; int shift = sparse ? 5 : 0; + if (!len) + return 0; + nr = vma_pages(vma); start = vma->vm_pgoff; - size = ((pci_resource_len(pdev, num) - 1) >> (PAGE_SHIFT - shift)) + 1; + size = ((len - 1) >> (PAGE_SHIFT - shift)) + 1; if (start < size && size - start >= nr) return 1; From 29840080f5a68de131ae5bf89138d6ae11c591d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:34 +0000 Subject: [PATCH 158/168] alpha/PCI: Remove WARN from __pci_mmap_fits() and __legacy_mmap_fits() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the WARN() that fires when userspace attempts to mmap beyond the BAR bounds. The check still returns 0 to reject the mapping, but the warning is excessive for normal operation. A similar warning was removed from the PCI core in the commit 3b519e4ea618 ("PCI: fix size checks for mmap() on /proc/bus/pci files"). Signed-off-by: Krzysztof Wilczyński [bhelgaas: squash https://lore.kernel.org/all/20260508045824.GA3160093@rocinante] Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Reviewed-by: Ilpo Järvinen Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-16-kwilczynski@kernel.org --- arch/alpha/kernel/pci-sysfs.c | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 8802f955256e..6cf688621ea9 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -48,13 +48,7 @@ static int __pci_mmap_fits(struct pci_dev *pdev, int num, start = vma->vm_pgoff; size = ((len - 1) >> (PAGE_SHIFT - shift)) + 1; - if (start < size && size - start >= nr) - return 1; - WARN(1, "process \"%s\" tried to map%s 0x%08lx-0x%08lx on %s BAR %d " - "(size 0x%08lx)\n", - current->comm, sparse ? " sparse" : "", start, start + nr, - pci_name(pdev), num, size); - return 0; + return start < size && size - start >= nr; } /** @@ -257,9 +251,8 @@ int pci_create_resource_files(struct pci_dev *pdev) /* Legacy I/O bus mapping stuff. */ -static int __legacy_mmap_fits(struct pci_controller *hose, - struct vm_area_struct *vma, - unsigned long res_size, int sparse) +static int __legacy_mmap_fits(struct vm_area_struct *vma, + unsigned long res_size) { unsigned long nr, start, size; @@ -267,13 +260,7 @@ static int __legacy_mmap_fits(struct pci_controller *hose, start = vma->vm_pgoff; size = ((res_size - 1) >> PAGE_SHIFT) + 1; - if (start < size && size - start >= nr) - return 1; - WARN(1, "process \"%s\" tried to map%s 0x%08lx-0x%08lx on hose %d " - "(size 0x%08lx)\n", - current->comm, sparse ? " sparse" : "", start, start + nr, - hose->index, size); - return 0; + return start < size && size - start >= nr; } static inline int has_sparse(struct pci_controller *hose, @@ -296,7 +283,7 @@ int pci_mmap_legacy_page_range(struct pci_bus *bus, struct vm_area_struct *vma, res_size = (mmap_type == pci_mmap_mem) ? bus->legacy_mem->size : bus->legacy_io->size; - if (!__legacy_mmap_fits(hose, vma, res_size, sparse)) + if (!__legacy_mmap_fits(vma, res_size)) return -EINVAL; return hose_mmap_page_range(hose, vma, mmap_type, sparse); From 5980dcbc4b25e0164cfebc90c32bca7cff33d1c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:35 +0000 Subject: [PATCH 159/168] alpha/PCI: Add static PCI resource attribute macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add macros for declaring static binary attributes for Alpha's PCI resource files: - pci_dev_resource_attr(), for dense/BWX systems (mmap dense) - pci_dev_resource_sparse_attr(), for sparse systems (mmap sparse) - pci_dev_resource_dense_attr(), for dense companion files (mmap dense) Each macro creates a const bin_attribute with the BAR index stored in the .private property and the appropriate .mmap() callback. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-17-kwilczynski@kernel.org --- arch/alpha/kernel/pci-sysfs.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 6cf688621ea9..e1a4546ac2db 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -104,6 +104,26 @@ static int pci_mmap_resource_dense(struct file *filp, struct kobject *kobj, return pci_mmap_resource(kobj, attr, vma, 0); } +#define __pci_dev_resource_attr(_bar, _name, _suffix, _mmap) \ +static const struct bin_attribute \ +pci_dev_resource##_bar##_suffix##_attr = { \ + .attr = { .name = __stringify(_name), .mode = 0600 }, \ + .private = (void *)(unsigned long)(_bar), \ + .mmap = (_mmap), \ +} + +#define pci_dev_resource_attr(_bar) \ + __pci_dev_resource_attr(_bar, resource##_bar,, \ + pci_mmap_resource_dense) + +#define pci_dev_resource_sparse_attr(_bar) \ + __pci_dev_resource_attr(_bar, resource##_bar##_sparse, _sparse, \ + pci_mmap_resource_sparse) + +#define pci_dev_resource_dense_attr(_bar) \ + __pci_dev_resource_attr(_bar, resource##_bar##_dense, _dense, \ + pci_mmap_resource_dense) + /** * pci_remove_resource_files - cleanup resource files * @pdev: pci_dev to cleanup From b7a57ffd4d9f4db3e95001db427c63053b78cf1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:36 +0000 Subject: [PATCH 160/168] alpha/PCI: Convert resource files to static attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, Alpha's PCI resource files (resourceN, resourceN_sparse, resourceN_dense) were dynamically created by pci_create_resource_files(), which overrides the generic __weak implementation. The previous code allocated bin_attributes at runtime and managed them via the res_attr[] and res_attr_wc[] fields in struct pci_dev. Convert to static const attributes with three attribute groups (plain, sparse, dense), each with an .is_bin_visible() callback that checks resource length, has_sparse(), and sparse_mem_mmap_fits(). A .bin_size() callback provides the resource size to the kernfs node, with the sparse variant shifting by 5 bits for byte-level addressing. Register the groups via ARCH_PCI_DEV_GROUPS so the driver model handles creation and removal automatically. Use the new pci_resource_is_mem() helper for the type check, replacing the open-coded bitwise flag test. Finally, remove pci_create_resource_files(), pci_remove_resource_files(), pci_create_attr(), and pci_create_one_attr() which are no longer needed. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-18-kwilczynski@kernel.org --- arch/alpha/include/asm/pci.h | 9 ++ arch/alpha/kernel/pci-sysfs.c | 291 +++++++++++++++++++--------------- 2 files changed, 172 insertions(+), 128 deletions(-) diff --git a/arch/alpha/include/asm/pci.h b/arch/alpha/include/asm/pci.h index 6c04fcbdc8ed..ef19295f2e33 100644 --- a/arch/alpha/include/asm/pci.h +++ b/arch/alpha/include/asm/pci.h @@ -88,4 +88,13 @@ extern void pci_adjust_legacy_attr(struct pci_bus *bus, enum pci_mmap_state mmap_type); #define HAVE_PCI_LEGACY 1 +extern const struct attribute_group pci_dev_resource_attr_group; +extern const struct attribute_group pci_dev_resource_sparse_attr_group; +extern const struct attribute_group pci_dev_resource_dense_attr_group; + +#define ARCH_PCI_DEV_GROUPS \ + &pci_dev_resource_attr_group, \ + &pci_dev_resource_sparse_attr_group, \ + &pci_dev_resource_dense_attr_group, + #endif /* __ALPHA_PCI_H */ diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index e1a4546ac2db..435654ffa4a0 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -12,8 +12,6 @@ #include #include -#include -#include #include static int hose_mmap_page_range(struct pci_controller *hose, @@ -124,34 +122,6 @@ pci_dev_resource##_bar##_suffix##_attr = { \ __pci_dev_resource_attr(_bar, resource##_bar##_dense, _dense, \ pci_mmap_resource_dense) -/** - * pci_remove_resource_files - cleanup resource files - * @pdev: pci_dev to cleanup - * - * If we created resource files for @dev, remove them from sysfs and - * free their resources. - */ -void pci_remove_resource_files(struct pci_dev *pdev) -{ - int i; - - for (i = 0; i < PCI_STD_NUM_BARS; i++) { - struct bin_attribute *res_attr; - - res_attr = pdev->res_attr[i]; - if (res_attr) { - sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); - kfree(res_attr); - } - - res_attr = pdev->res_attr_wc[i]; - if (res_attr) { - sysfs_remove_bin_file(&pdev->dev.kobj, res_attr); - kfree(res_attr); - } - } -} - static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num) { struct pci_bus_region bar; @@ -171,104 +141,6 @@ static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num) return bar.end < sparse_size; } -static int pci_create_one_attr(struct pci_dev *pdev, int num, char *name, - char *suffix, struct bin_attribute *res_attr, - unsigned long sparse) -{ - size_t size = pci_resource_len(pdev, num); - - sprintf(name, "resource%d%s", num, suffix); - res_attr->mmap = sparse ? pci_mmap_resource_sparse : - pci_mmap_resource_dense; - res_attr->attr.name = name; - res_attr->attr.mode = S_IRUSR | S_IWUSR; - res_attr->size = sparse ? size << 5 : size; - res_attr->private = (void *)(unsigned long)num; - return sysfs_create_bin_file(&pdev->dev.kobj, res_attr); -} - -static int pci_create_attr(struct pci_dev *pdev, int num) -{ - /* allocate attribute structure, piggyback attribute name */ - int retval, nlen1, nlen2 = 0, res_count = 1; - unsigned long sparse_base, dense_base; - struct bin_attribute *attr; - struct pci_controller *hose = pdev->sysdata; - char *suffix, *attr_name; - - suffix = ""; /* Assume bwx machine, normal resourceN files. */ - nlen1 = 10; - - if (pci_resource_is_mem(pdev, num)) { - sparse_base = hose->sparse_mem_base; - dense_base = hose->dense_mem_base; - if (sparse_base && !sparse_mem_mmap_fits(pdev, num)) { - sparse_base = 0; - suffix = "_dense"; - nlen1 = 16; /* resourceN_dense */ - } - } else { - sparse_base = hose->sparse_io_base; - dense_base = hose->dense_io_base; - } - - if (sparse_base) { - suffix = "_sparse"; - nlen1 = 17; - if (dense_base) { - nlen2 = 16; /* resourceN_dense */ - res_count = 2; - } - } - - attr = kzalloc(sizeof(*attr) * res_count + nlen1 + nlen2, GFP_ATOMIC); - if (!attr) - return -ENOMEM; - - /* Create bwx, sparse or single dense file */ - attr_name = (char *)(attr + res_count); - pdev->res_attr[num] = attr; - retval = pci_create_one_attr(pdev, num, attr_name, suffix, attr, - sparse_base); - if (retval || res_count == 1) - return retval; - - /* Create dense file */ - attr_name += nlen1; - attr++; - pdev->res_attr_wc[num] = attr; - return pci_create_one_attr(pdev, num, attr_name, "_dense", attr, 0); -} - -/** - * pci_create_resource_files - create resource files in sysfs for @pdev - * @pdev: pci_dev in question - * - * Walk the resources in @dev creating files for each resource available. - * - * Return: %0 on success, or negative error code - */ -int pci_create_resource_files(struct pci_dev *pdev) -{ - int i; - int retval; - - /* Expose the PCI resources from this device as files */ - for (i = 0; i < PCI_STD_NUM_BARS; i++) { - - /* skip empty resources */ - if (!pci_resource_len(pdev, i)) - continue; - - retval = pci_create_attr(pdev, i); - if (retval) { - pci_remove_resource_files(pdev); - return retval; - } - } - return 0; -} - /* Legacy I/O bus mapping stuff. */ static int __legacy_mmap_fits(struct vm_area_struct *vma, @@ -381,3 +253,166 @@ int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, size_t size) } return -EINVAL; } + +pci_dev_resource_attr(0); +pci_dev_resource_attr(1); +pci_dev_resource_attr(2); +pci_dev_resource_attr(3); +pci_dev_resource_attr(4); +pci_dev_resource_attr(5); + +pci_dev_resource_sparse_attr(0); +pci_dev_resource_sparse_attr(1); +pci_dev_resource_sparse_attr(2); +pci_dev_resource_sparse_attr(3); +pci_dev_resource_sparse_attr(4); +pci_dev_resource_sparse_attr(5); + +pci_dev_resource_dense_attr(0); +pci_dev_resource_dense_attr(1); +pci_dev_resource_dense_attr(2); +pci_dev_resource_dense_attr(3); +pci_dev_resource_dense_attr(4); +pci_dev_resource_dense_attr(5); + +static inline enum pci_mmap_state pci_bar_mmap_type(struct pci_dev *pdev, + int bar) +{ + return pci_resource_is_mem(pdev, bar) ? pci_mmap_mem : pci_mmap_io; +} + +static inline umode_t __pci_dev_resource_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int bar) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + + if (!pci_resource_len(pdev, bar)) + return 0; + + return a->attr.mode; +} + +static umode_t pci_dev_resource_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int bar) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + struct pci_controller *hose = pdev->sysdata; + + if (has_sparse(hose, pci_bar_mmap_type(pdev, bar))) + return 0; + + return __pci_dev_resource_is_visible(kobj, a, bar); +} + +static umode_t pci_dev_resource_sparse_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int bar) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + struct pci_controller *hose = pdev->sysdata; + enum pci_mmap_state type = pci_bar_mmap_type(pdev, bar); + + if (!has_sparse(hose, type)) + return 0; + + if (type == pci_mmap_mem && !sparse_mem_mmap_fits(pdev, bar)) + return 0; + + return __pci_dev_resource_is_visible(kobj, a, bar); +} + +static umode_t pci_dev_resource_dense_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int bar) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + struct pci_controller *hose = pdev->sysdata; + enum pci_mmap_state type = pci_bar_mmap_type(pdev, bar); + unsigned long dense_base; + + if (!has_sparse(hose, type)) + return 0; + + if (type == pci_mmap_mem && !sparse_mem_mmap_fits(pdev, bar)) + return __pci_dev_resource_is_visible(kobj, a, bar); + + dense_base = (type == pci_mmap_mem) ? hose->dense_mem_base : + hose->dense_io_base; + if (!dense_base) + return 0; + + return __pci_dev_resource_is_visible(kobj, a, bar); +} + +static inline size_t __pci_dev_resource_bin_size(struct kobject *kobj, + int bar, bool sparse) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + size_t size = pci_resource_len(pdev, bar); + + return sparse ? size << 5 : size; +} + +static size_t pci_dev_resource_bin_size(struct kobject *kobj, + const struct bin_attribute *a, + int bar) +{ + return __pci_dev_resource_bin_size(kobj, bar, false); +} + +static size_t pci_dev_resource_sparse_bin_size(struct kobject *kobj, + const struct bin_attribute *a, + int bar) +{ + return __pci_dev_resource_bin_size(kobj, bar, true); +} + +static const struct bin_attribute *const pci_dev_resource_attrs[] = { + &pci_dev_resource0_attr, + &pci_dev_resource1_attr, + &pci_dev_resource2_attr, + &pci_dev_resource3_attr, + &pci_dev_resource4_attr, + &pci_dev_resource5_attr, + NULL, +}; + +static const struct bin_attribute *const pci_dev_resource_sparse_attrs[] = { + &pci_dev_resource0_sparse_attr, + &pci_dev_resource1_sparse_attr, + &pci_dev_resource2_sparse_attr, + &pci_dev_resource3_sparse_attr, + &pci_dev_resource4_sparse_attr, + &pci_dev_resource5_sparse_attr, + NULL, +}; + +static const struct bin_attribute *const pci_dev_resource_dense_attrs[] = { + &pci_dev_resource0_dense_attr, + &pci_dev_resource1_dense_attr, + &pci_dev_resource2_dense_attr, + &pci_dev_resource3_dense_attr, + &pci_dev_resource4_dense_attr, + &pci_dev_resource5_dense_attr, + NULL, +}; + +const struct attribute_group pci_dev_resource_attr_group = { + .bin_attrs = pci_dev_resource_attrs, + .is_bin_visible = pci_dev_resource_is_visible, + .bin_size = pci_dev_resource_bin_size, +}; + +const struct attribute_group pci_dev_resource_sparse_attr_group = { + .bin_attrs = pci_dev_resource_sparse_attrs, + .is_bin_visible = pci_dev_resource_sparse_is_visible, + .bin_size = pci_dev_resource_sparse_bin_size, +}; + +const struct attribute_group pci_dev_resource_dense_attr_group = { + .bin_attrs = pci_dev_resource_dense_attrs, + .is_bin_visible = pci_dev_resource_dense_is_visible, + .bin_size = pci_dev_resource_bin_size, +}; From b45bebbfa0be8b1c8be5d3c5946f8f04d556fdf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:37 +0000 Subject: [PATCH 161/168] PCI/sysfs: Remove pci_{create,remove}_sysfs_dev_files() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, pci_create_sysfs_dev_files() and pci_remove_sysfs_dev_files() are no-op stubs. With both the generic and Alpha resource files now handled by static attribute groups, no platform needs dynamic per-device sysfs file creation. Remove both functions, their declarations, and the call sites in pci_bus_add_device() and pci_stop_dev(). Remove __weak pci_create_resource_files() and pci_remove_resource_files() stubs and their declarations in pci.h, as no architecture overrides them anymore. Remove the res_attr[] and res_attr_wc[] fields from struct pci_dev which were used to track dynamically allocated resource attributes. Finally, simplify pci_sysfs_init() to only handle legacy file creation under HAVE_PCI_LEGACY, removing the per-device loop and the HAVE_PCI_SYSFS_INIT helper added earlier. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-19-kwilczynski@kernel.org --- drivers/pci/bus.c | 1 - drivers/pci/pci-sysfs.c | 52 ++--------------------------------------- drivers/pci/pci.h | 4 ---- drivers/pci/remove.c | 1 - include/linux/pci.h | 9 ------- 5 files changed, 2 insertions(+), 65 deletions(-) diff --git a/drivers/pci/bus.c b/drivers/pci/bus.c index 6c1ad1f542d9..655ed53436d3 100644 --- a/drivers/pci/bus.c +++ b/drivers/pci/bus.c @@ -354,7 +354,6 @@ void pci_bus_add_device(struct pci_dev *dev) pci_fixup_device(pci_fixup_final, dev); if (pci_is_bridge(dev)) of_pci_make_dev_node(dev); - pci_create_sysfs_dev_files(dev); pci_proc_attach_device(dev); pci_bridge_d3_update(dev); diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 0ba4fbf40cdc..97a482d6d67d 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -37,12 +37,7 @@ #define ARCH_PCI_DEV_GROUPS #endif -#if defined(HAVE_PCI_LEGACY) || \ - !defined(HAVE_PCI_MMAP) && !defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) -#define HAVE_PCI_SYSFS_INIT -#endif - -#ifdef HAVE_PCI_SYSFS_INIT +#ifdef HAVE_PCI_LEGACY static int sysfs_initialized; /* = 0 */ #endif @@ -1377,8 +1372,6 @@ static const struct attribute_group *pci_dev_resource_attr_groups[] = { }; #else #define pci_dev_resource_attr_groups NULL -int __weak pci_create_resource_files(struct pci_dev *dev) { return 0; } -void __weak pci_remove_resource_files(struct pci_dev *dev) { } #endif /** @@ -1748,54 +1741,13 @@ static const struct attribute_group pci_dev_resource_resize_attr_group = { .is_visible = resource_resize_attr_is_visible, }; -#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) -int pci_create_sysfs_dev_files(struct pci_dev *pdev) { return 0; } -void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { } -#else -int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev) -{ - if (!sysfs_initialized) - return -EACCES; - - return pci_create_resource_files(pdev); -} - -/** - * pci_remove_sysfs_dev_files - cleanup PCI specific sysfs files - * @pdev: device whose entries we should free - * - * Cleanup when @pdev is removed from sysfs. - */ -void pci_remove_sysfs_dev_files(struct pci_dev *pdev) -{ - if (!sysfs_initialized) - return; - - pci_remove_resource_files(pdev); -} -#endif - -#ifdef HAVE_PCI_SYSFS_INIT +#ifdef HAVE_PCI_LEGACY static int __init pci_sysfs_init(void) { -#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) struct pci_bus *pbus = NULL; sysfs_initialized = 1; -#else - struct pci_dev *pdev = NULL; - struct pci_bus *pbus = NULL; - int retval; - sysfs_initialized = 1; - for_each_pci_dev(pdev) { - retval = pci_create_sysfs_dev_files(pdev); - if (retval) { - pci_dev_put(pdev); - return retval; - } - } -#endif while ((pbus = pci_find_next_bus(pbus))) pci_create_legacy_files(pbus); diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4a14f88e543a..71a1fde1e505 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -393,16 +393,12 @@ static inline int pci_no_d1d2(struct pci_dev *dev) } #ifdef CONFIG_SYSFS -int pci_create_sysfs_dev_files(struct pci_dev *pdev); -void pci_remove_sysfs_dev_files(struct pci_dev *pdev); extern const struct attribute_group *pci_dev_groups[]; extern const struct attribute_group *pci_dev_attr_groups[]; extern const struct attribute_group *pcibus_groups[]; extern const struct attribute_group *pci_bus_groups[]; extern const struct attribute_group pci_doe_sysfs_group; #else -static inline int pci_create_sysfs_dev_files(struct pci_dev *pdev) { return 0; } -static inline void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { } #define pci_dev_groups NULL #define pci_dev_attr_groups NULL #define pcibus_groups NULL diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index e9d519993853..6e796dbc5b29 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -26,7 +26,6 @@ static void pci_stop_dev(struct pci_dev *dev) device_release_driver(&dev->dev); pci_proc_detach_device(dev); - pci_remove_sysfs_dev_files(dev); of_pci_remove_node(dev); } diff --git a/include/linux/pci.h b/include/linux/pci.h index b998a56f6010..c56f2cf0d2ab 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -515,10 +515,6 @@ struct pci_dev { spinlock_t pcie_cap_lock; /* Protects RMW ops in capability accessors */ u32 saved_config_space[16]; /* Config space saved at suspend time */ struct hlist_head saved_cap_space; -#if !defined(HAVE_PCI_MMAP) && !defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) - struct bin_attribute *res_attr[DEVICE_COUNT_RESOURCE]; /* sysfs file for resources */ - struct bin_attribute *res_attr_wc[DEVICE_COUNT_RESOURCE]; /* sysfs file for WC mapping of resources */ -#endif #ifdef CONFIG_HOTPLUG_PCI_PCIE unsigned int broken_cmd_compl:1; /* No compl for some cmds */ @@ -2533,11 +2529,6 @@ int pcibios_alloc_irq(struct pci_dev *dev); void pcibios_free_irq(struct pci_dev *dev); resource_size_t pcibios_default_alignment(void); -#if !defined(HAVE_PCI_MMAP) && !defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) -extern int pci_create_resource_files(struct pci_dev *dev); -extern void pci_remove_resource_files(struct pci_dev *dev); -#endif - #if defined(CONFIG_PCI_MMCONFIG) || defined(CONFIG_ACPI_MCFG) void __init pci_mmcfg_early_init(void); void __init pci_mmcfg_late_init(void); From e52e497e3778c99c3fb26ef6a47af6a219133f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:38 +0000 Subject: [PATCH 162/168] PCI: Add macros for legacy I/O and memory address space sizes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add defines for the standard PCI legacy address space sizes, replacing the raw literals used by the legacy sysfs attributes. Then, replace open-coded values with the newly added macros. No functional changes intended. Suggested-by: Ilpo Järvinen Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-20-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 4 ++-- include/linux/pci.h | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 97a482d6d67d..1e60f072f746 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1019,7 +1019,7 @@ void pci_create_legacy_files(struct pci_bus *b) sysfs_bin_attr_init(b->legacy_io); b->legacy_io->attr.name = "legacy_io"; - b->legacy_io->size = 0xffff; + b->legacy_io->size = PCI_LEGACY_IO_SIZE; b->legacy_io->attr.mode = 0600; b->legacy_io->read = pci_read_legacy_io; b->legacy_io->write = pci_write_legacy_io; @@ -1036,7 +1036,7 @@ void pci_create_legacy_files(struct pci_bus *b) b->legacy_mem = b->legacy_io + 1; sysfs_bin_attr_init(b->legacy_mem); b->legacy_mem->attr.name = "legacy_mem"; - b->legacy_mem->size = 1024*1024; + b->legacy_mem->size = PCI_LEGACY_MEM_SIZE; b->legacy_mem->attr.mode = 0600; b->legacy_mem->mmap = pci_mmap_legacy_mem; /* See pci_create_attr() for motivation */ diff --git a/include/linux/pci.h b/include/linux/pci.h index c56f2cf0d2ab..e37677a8dd3c 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -1169,6 +1170,10 @@ enum { /* These external functions are only available when PCI support is enabled */ #ifdef CONFIG_PCI +/* PCI legacy I/O port and memory address space sizes. */ +#define PCI_LEGACY_IO_SIZE (SZ_64K - 1) +#define PCI_LEGACY_MEM_SIZE SZ_1M + extern unsigned int pci_flags; static inline void pci_set_flags(int flags) { pci_flags = flags; } From 385ec1d40756e3bcb868814e305089b4edd32149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:39 +0000 Subject: [PATCH 163/168] alpha/PCI: Compute legacy size in pci_mmap_legacy_page_range() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, pci_mmap_legacy_page_range() reads the legacy resource size from bus->legacy_mem->size or bus->legacy_io->size. This couples the mmap bounds check to the struct pci_bus fields that will be removed when legacy attributes are converted to static definitions. Compute the size directly using PCI_LEGACY_MEM_SIZE (0x100000) and PCI_LEGACY_IO_SIZE (0xffff) macros, and shift by 5 bits for sparse systems. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Magnus Lindholm Tested-by: Shivaprasad G Bhat Acked-by: Magnus Lindholm Link: https://patch.msgid.link/20260508043543.217179-21-kwilczynski@kernel.org --- arch/alpha/kernel/pci-sysfs.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 435654ffa4a0..2038acbcfd2a 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -173,8 +173,11 @@ int pci_mmap_legacy_page_range(struct pci_bus *bus, struct vm_area_struct *vma, int sparse = has_sparse(hose, mmap_type); unsigned long res_size; - res_size = (mmap_type == pci_mmap_mem) ? bus->legacy_mem->size : - bus->legacy_io->size; + res_size = (mmap_type == pci_mmap_mem) ? PCI_LEGACY_MEM_SIZE : + PCI_LEGACY_IO_SIZE; + if (sparse) + res_size <<= 5; + if (!__legacy_mmap_fits(vma, res_size)) return -EINVAL; From 574b5470f8d43c85bf3dcb8c48efc2788a2bfb0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:40 +0000 Subject: [PATCH 164/168] PCI/sysfs: Add __weak pci_legacy_has_sparse() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, Alpha's sparse/dense legacy attribute handling is done via pci_adjust_legacy_attr(), which updates dynamically allocated attributes at runtime. The upcoming conversion to static attributes needs a way to determine sparse support at visibility check time. Add a __weak pci_legacy_has_sparse() that returns false by default. Alpha overrides it to check has_sparse() on the bus host controller. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-22-kwilczynski@kernel.org --- arch/alpha/include/asm/pci.h | 2 ++ arch/alpha/kernel/pci-sysfs.c | 7 +++++++ drivers/pci/pci-sysfs.c | 6 ++++++ drivers/pci/pci.h | 4 ++++ 4 files changed, 19 insertions(+) diff --git a/arch/alpha/include/asm/pci.h b/arch/alpha/include/asm/pci.h index ef19295f2e33..95de7ffd59e8 100644 --- a/arch/alpha/include/asm/pci.h +++ b/arch/alpha/include/asm/pci.h @@ -86,6 +86,8 @@ extern int pci_mmap_legacy_page_range(struct pci_bus *bus, enum pci_mmap_state mmap_state); extern void pci_adjust_legacy_attr(struct pci_bus *bus, enum pci_mmap_state mmap_type); +extern bool pci_legacy_has_sparse(struct pci_bus *bus, + enum pci_mmap_state type); #define HAVE_PCI_LEGACY 1 extern const struct attribute_group pci_dev_resource_attr_group; diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 2038acbcfd2a..20736a0c1a39 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -184,6 +184,13 @@ int pci_mmap_legacy_page_range(struct pci_bus *bus, struct vm_area_struct *vma, return hose_mmap_page_range(hose, vma, mmap_type, sparse); } +bool pci_legacy_has_sparse(struct pci_bus *bus, enum pci_mmap_state type) +{ + struct pci_controller *hose = bus->sysdata; + + return has_sparse(hose, type); +} + /** * pci_adjust_legacy_attr - adjustment of legacy file attributes * @bus: bus to create files under diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 1e60f072f746..c1ba706844c9 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -983,6 +983,12 @@ static int pci_mmap_legacy_io(struct file *filp, struct kobject *kobj, return pci_mmap_legacy_page_range(bus, vma, pci_mmap_io); } +bool __weak pci_legacy_has_sparse(struct pci_bus *bus, + enum pci_mmap_state type) +{ + return false; +} + /** * pci_adjust_legacy_attr - adjustment of legacy file attributes * @b: bus to create files under diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 71a1fde1e505..c64c7f5f0bcf 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -392,6 +392,10 @@ static inline int pci_no_d1d2(struct pci_dev *dev) } +#ifdef HAVE_PCI_LEGACY +bool pci_legacy_has_sparse(struct pci_bus *bus, enum pci_mmap_state type); +#endif + #ifdef CONFIG_SYSFS extern const struct attribute_group *pci_dev_groups[]; extern const struct attribute_group *pci_dev_attr_groups[]; From 4e14b965a625f2c2813bec0a6a7530426ae508fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:41 +0000 Subject: [PATCH 165/168] PCI/sysfs: Convert legacy I/O and memory attributes to static definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, legacy_io and legacy_mem are dynamically allocated and created by pci_create_legacy_files(), with pci_adjust_legacy_attr() updating the attributes at runtime on Alpha to rename them and shift the size for sparse addressing. Convert to four static const attributes (legacy_io, legacy_io_sparse, legacy_mem, legacy_mem_sparse) with .is_bin_visible() callbacks that use pci_legacy_has_sparse() to select the appropriate variant per bus. The sizes are compile-time constants and .size is set directly on each attribute. Register the groups in pcibus_groups[] under a HAVE_PCI_LEGACY guard so the driver model handles creation and removal automatically. Stub out pci_create_legacy_files() and pci_remove_legacy_files() as the dynamic creation is no longer needed. Remove the __weak pci_adjust_legacy_attr(), Alpha's override, and its declaration from both Alpha and PowerPC asm/pci.h headers. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-23-kwilczynski@kernel.org --- arch/alpha/include/asm/pci.h | 2 - arch/alpha/kernel/pci-sysfs.c | 38 ++---- arch/powerpc/include/asm/pci.h | 2 - drivers/pci/pci-sysfs.c | 221 +++++++++++++++++++-------------- 4 files changed, 135 insertions(+), 128 deletions(-) diff --git a/arch/alpha/include/asm/pci.h b/arch/alpha/include/asm/pci.h index 95de7ffd59e8..ad5d1391e1fa 100644 --- a/arch/alpha/include/asm/pci.h +++ b/arch/alpha/include/asm/pci.h @@ -84,8 +84,6 @@ extern int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, extern int pci_mmap_legacy_page_range(struct pci_bus *bus, struct vm_area_struct *vma, enum pci_mmap_state mmap_state); -extern void pci_adjust_legacy_attr(struct pci_bus *bus, - enum pci_mmap_state mmap_type); extern bool pci_legacy_has_sparse(struct pci_bus *bus, enum pci_mmap_state type); #define HAVE_PCI_LEGACY 1 diff --git a/arch/alpha/kernel/pci-sysfs.c b/arch/alpha/kernel/pci-sysfs.c index 20736a0c1a39..94dbc470cd6c 100644 --- a/arch/alpha/kernel/pci-sysfs.c +++ b/arch/alpha/kernel/pci-sysfs.c @@ -191,30 +191,6 @@ bool pci_legacy_has_sparse(struct pci_bus *bus, enum pci_mmap_state type) return has_sparse(hose, type); } -/** - * pci_adjust_legacy_attr - adjustment of legacy file attributes - * @bus: bus to create files under - * @mmap_type: I/O port or memory - * - * Adjust file name and size for sparse mappings. - */ -void pci_adjust_legacy_attr(struct pci_bus *bus, enum pci_mmap_state mmap_type) -{ - struct pci_controller *hose = bus->sysdata; - - if (!has_sparse(hose, mmap_type)) - return; - - if (mmap_type == pci_mmap_mem) { - bus->legacy_mem->attr.name = "legacy_mem_sparse"; - bus->legacy_mem->size <<= 5; - } else { - bus->legacy_io->attr.name = "legacy_io_sparse"; - bus->legacy_io->size <<= 5; - } - return; -} - /* Legacy I/O bus read/write functions */ int pci_legacy_read(struct pci_bus *bus, loff_t port, u32 *val, size_t size) { @@ -291,9 +267,9 @@ static inline enum pci_mmap_state pci_bar_mmap_type(struct pci_dev *pdev, return pci_resource_is_mem(pdev, bar) ? pci_mmap_mem : pci_mmap_io; } -static inline umode_t __pci_dev_resource_is_visible(struct kobject *kobj, - const struct bin_attribute *a, - int bar) +static inline umode_t __pci_resource_attr_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int bar) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); @@ -313,7 +289,7 @@ static umode_t pci_dev_resource_is_visible(struct kobject *kobj, if (has_sparse(hose, pci_bar_mmap_type(pdev, bar))) return 0; - return __pci_dev_resource_is_visible(kobj, a, bar); + return __pci_resource_attr_is_visible(kobj, a, bar); } static umode_t pci_dev_resource_sparse_is_visible(struct kobject *kobj, @@ -330,7 +306,7 @@ static umode_t pci_dev_resource_sparse_is_visible(struct kobject *kobj, if (type == pci_mmap_mem && !sparse_mem_mmap_fits(pdev, bar)) return 0; - return __pci_dev_resource_is_visible(kobj, a, bar); + return __pci_resource_attr_is_visible(kobj, a, bar); } static umode_t pci_dev_resource_dense_is_visible(struct kobject *kobj, @@ -346,14 +322,14 @@ static umode_t pci_dev_resource_dense_is_visible(struct kobject *kobj, return 0; if (type == pci_mmap_mem && !sparse_mem_mmap_fits(pdev, bar)) - return __pci_dev_resource_is_visible(kobj, a, bar); + return __pci_resource_attr_is_visible(kobj, a, bar); dense_base = (type == pci_mmap_mem) ? hose->dense_mem_base : hose->dense_io_base; if (!dense_base) return 0; - return __pci_dev_resource_is_visible(kobj, a, bar); + return __pci_resource_attr_is_visible(kobj, a, bar); } static inline size_t __pci_dev_resource_bin_size(struct kobject *kobj, diff --git a/arch/powerpc/include/asm/pci.h b/arch/powerpc/include/asm/pci.h index 46a9c4491ed0..72f286e74786 100644 --- a/arch/powerpc/include/asm/pci.h +++ b/arch/powerpc/include/asm/pci.h @@ -82,8 +82,6 @@ extern int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, extern int pci_mmap_legacy_page_range(struct pci_bus *bus, struct vm_area_struct *vma, enum pci_mmap_state mmap_state); -extern void pci_adjust_legacy_attr(struct pci_bus *bus, - enum pci_mmap_state mmap_type); #define HAVE_PCI_LEGACY 1 extern void pcibios_claim_one_bus(struct pci_bus *b); diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index c1ba706844c9..ae5470c185fa 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -676,11 +676,6 @@ static const struct attribute_group pcibus_group = { .attrs = pcibus_attrs, }; -const struct attribute_group *pcibus_groups[] = { - &pcibus_group, - NULL, -}; - static ssize_t boot_vga_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -879,19 +874,6 @@ static const struct attribute_group pci_dev_config_attr_group = { .bin_size = pci_dev_config_attr_bin_size, }; -/* - * llseek operation for mmappable PCI resources. - * May be left unused if the arch doesn't provide them. - */ -static __maybe_unused loff_t -pci_llseek_resource_legacy(struct file *filep, - struct kobject *kobj __always_unused, - const struct bin_attribute *attr, - loff_t offset, int whence) -{ - return fixed_size_llseek(filep, offset, whence, attr->size); -} - #ifdef HAVE_PCI_LEGACY /** * pci_read_legacy_io - read byte(s) from legacy I/O port space @@ -989,91 +971,144 @@ bool __weak pci_legacy_has_sparse(struct pci_bus *bus, return false; } -/** - * pci_adjust_legacy_attr - adjustment of legacy file attributes - * @b: bus to create files under - * @mmap_type: I/O port or memory - * - * Stub implementation. Can be overridden by arch if necessary. - */ -void __weak pci_adjust_legacy_attr(struct pci_bus *b, - enum pci_mmap_state mmap_type) +static inline umode_t __pci_legacy_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + enum pci_mmap_state type, + bool sparse) { + struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj)); + + if (pci_legacy_has_sparse(bus, type) != sparse) + return 0; + + return a->attr.mode; } -/** - * pci_create_legacy_files - create legacy I/O port and memory files - * @b: bus to create files under - * - * Some platforms allow access to legacy I/O port and ISA memory space on - * a per-bus basis. This routine creates the files and ties them into - * their associated read, write and mmap files from pci-sysfs.c - * - * On error unwind, but don't propagate the error to the caller - * as it is ok to set up the PCI bus without these files. - */ -void pci_create_legacy_files(struct pci_bus *b) +static umode_t pci_legacy_io_is_visible(struct kobject *kobj, + const struct bin_attribute *a, int n) { - int error; - - if (!sysfs_initialized) - return; - - b->legacy_io = kzalloc_objs(struct bin_attribute, 2, GFP_ATOMIC); - if (!b->legacy_io) - goto kzalloc_err; - - sysfs_bin_attr_init(b->legacy_io); - b->legacy_io->attr.name = "legacy_io"; - b->legacy_io->size = PCI_LEGACY_IO_SIZE; - b->legacy_io->attr.mode = 0600; - b->legacy_io->read = pci_read_legacy_io; - b->legacy_io->write = pci_write_legacy_io; - /* See pci_create_attr() for motivation */ - b->legacy_io->llseek = pci_llseek_resource_legacy; - b->legacy_io->mmap = pci_mmap_legacy_io; - b->legacy_io->f_mapping = iomem_get_mapping; - pci_adjust_legacy_attr(b, pci_mmap_io); - error = device_create_bin_file(&b->dev, b->legacy_io); - if (error) - goto legacy_io_err; - - /* Allocated above after the legacy_io struct */ - b->legacy_mem = b->legacy_io + 1; - sysfs_bin_attr_init(b->legacy_mem); - b->legacy_mem->attr.name = "legacy_mem"; - b->legacy_mem->size = PCI_LEGACY_MEM_SIZE; - b->legacy_mem->attr.mode = 0600; - b->legacy_mem->mmap = pci_mmap_legacy_mem; - /* See pci_create_attr() for motivation */ - b->legacy_mem->llseek = pci_llseek_resource_legacy; - b->legacy_mem->f_mapping = iomem_get_mapping; - pci_adjust_legacy_attr(b, pci_mmap_mem); - error = device_create_bin_file(&b->dev, b->legacy_mem); - if (error) - goto legacy_mem_err; - - return; - -legacy_mem_err: - device_remove_bin_file(&b->dev, b->legacy_io); -legacy_io_err: - kfree(b->legacy_io); - b->legacy_io = NULL; -kzalloc_err: - dev_warn(&b->dev, "could not create legacy I/O port and ISA memory resources in sysfs\n"); + return __pci_legacy_is_visible(kobj, a, pci_mmap_io, false); } -void pci_remove_legacy_files(struct pci_bus *b) +static umode_t pci_legacy_io_sparse_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int n) { - if (b->legacy_io) { - device_remove_bin_file(&b->dev, b->legacy_io); - device_remove_bin_file(&b->dev, b->legacy_mem); - kfree(b->legacy_io); /* both are allocated here */ - } + return __pci_legacy_is_visible(kobj, a, pci_mmap_io, true); } + +static umode_t pci_legacy_mem_is_visible(struct kobject *kobj, + const struct bin_attribute *a, int n) +{ + return __pci_legacy_is_visible(kobj, a, pci_mmap_mem, false); +} + +static umode_t pci_legacy_mem_sparse_is_visible(struct kobject *kobj, + const struct bin_attribute *a, + int n) +{ + return __pci_legacy_is_visible(kobj, a, pci_mmap_mem, true); +} + +static loff_t pci_llseek_resource_legacy(struct file *filep, + struct kobject *kobj __always_unused, + const struct bin_attribute *attr, + loff_t offset, int whence) +{ + return fixed_size_llseek(filep, offset, whence, attr->size); +} + +static const struct bin_attribute pci_legacy_io_attr = { + .attr = { .name = "legacy_io", .mode = 0600 }, + .size = PCI_LEGACY_IO_SIZE, + .read = pci_read_legacy_io, + .write = pci_write_legacy_io, + .mmap = pci_mmap_legacy_io, + .llseek = pci_llseek_resource_legacy, + .f_mapping = iomem_get_mapping, +}; + +static const struct bin_attribute pci_legacy_io_sparse_attr = { + .attr = { .name = "legacy_io_sparse", .mode = 0600 }, + .size = PCI_LEGACY_IO_SIZE << 5, + .read = pci_read_legacy_io, + .write = pci_write_legacy_io, + .mmap = pci_mmap_legacy_io, + .llseek = pci_llseek_resource_legacy, + .f_mapping = iomem_get_mapping, +}; + +static const struct bin_attribute pci_legacy_mem_attr = { + .attr = { .name = "legacy_mem", .mode = 0600 }, + .size = PCI_LEGACY_MEM_SIZE, + .mmap = pci_mmap_legacy_mem, + .llseek = pci_llseek_resource_legacy, + .f_mapping = iomem_get_mapping, +}; + +static const struct bin_attribute pci_legacy_mem_sparse_attr = { + .attr = { .name = "legacy_mem_sparse", .mode = 0600 }, + .size = PCI_LEGACY_MEM_SIZE << 5, + .mmap = pci_mmap_legacy_mem, + .llseek = pci_llseek_resource_legacy, + .f_mapping = iomem_get_mapping, +}; + +static const struct bin_attribute *const pci_legacy_io_attrs[] = { + &pci_legacy_io_attr, + NULL, +}; + +static const struct bin_attribute *const pci_legacy_io_sparse_attrs[] = { + &pci_legacy_io_sparse_attr, + NULL, +}; + +static const struct bin_attribute *const pci_legacy_mem_attrs[] = { + &pci_legacy_mem_attr, + NULL, +}; + +static const struct bin_attribute *const pci_legacy_mem_sparse_attrs[] = { + &pci_legacy_mem_sparse_attr, + NULL, +}; + +static const struct attribute_group pci_legacy_io_group = { + .bin_attrs = pci_legacy_io_attrs, + .is_bin_visible = pci_legacy_io_is_visible, +}; + +static const struct attribute_group pci_legacy_io_sparse_group = { + .bin_attrs = pci_legacy_io_sparse_attrs, + .is_bin_visible = pci_legacy_io_sparse_is_visible, +}; + +static const struct attribute_group pci_legacy_mem_group = { + .bin_attrs = pci_legacy_mem_attrs, + .is_bin_visible = pci_legacy_mem_is_visible, +}; + +static const struct attribute_group pci_legacy_mem_sparse_group = { + .bin_attrs = pci_legacy_mem_sparse_attrs, + .is_bin_visible = pci_legacy_mem_sparse_is_visible, +}; + +void pci_create_legacy_files(struct pci_bus *b) { } +void pci_remove_legacy_files(struct pci_bus *b) { } #endif /* HAVE_PCI_LEGACY */ +const struct attribute_group *pcibus_groups[] = { + &pcibus_group, +#ifdef HAVE_PCI_LEGACY + &pci_legacy_io_group, + &pci_legacy_io_sparse_group, + &pci_legacy_mem_group, + &pci_legacy_mem_sparse_group, +#endif + NULL, +}; + #if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) /** * pci_mmap_resource - map a PCI resource into user memory space From 2800a911ced1540baa78de7494218aaf1c7dc4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:42 +0000 Subject: [PATCH 166/168] PCI/sysfs: Remove pci_create_legacy_files() and pci_sysfs_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, pci_create_legacy_files() and pci_remove_legacy_files() are no-op stubs. With legacy attributes now handled by static groups registered via pcibus_groups[], no call site needs them. Remove both functions, their declarations, and the call sites in pci_register_host_bridge(), pci_alloc_child_bus(), and pci_remove_bus(). Remove the pci_sysfs_init() late_initcall and sysfs_initialized. The late_initcall originally existed to create all the dynamic PCI sysfs files, but with both resource and legacy attributes now handled by static groups, it is no longer needed. Remove the legacy_io and legacy_mem fields from struct pci_bus which were used to track the dynamically allocated legacy attributes. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-24-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 21 --------------------- drivers/pci/pci.h | 8 -------- drivers/pci/probe.c | 6 ------ drivers/pci/remove.c | 2 -- include/linux/pci.h | 2 -- 5 files changed, 39 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index ae5470c185fa..15969930b4d1 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -37,10 +37,6 @@ #define ARCH_PCI_DEV_GROUPS #endif -#ifdef HAVE_PCI_LEGACY -static int sysfs_initialized; /* = 0 */ -#endif - /* show configuration fields */ #define pci_config_attr(field, format_string) \ static ssize_t \ @@ -1094,8 +1090,6 @@ static const struct attribute_group pci_legacy_mem_sparse_group = { .is_bin_visible = pci_legacy_mem_sparse_is_visible, }; -void pci_create_legacy_files(struct pci_bus *b) { } -void pci_remove_legacy_files(struct pci_bus *b) { } #endif /* HAVE_PCI_LEGACY */ const struct attribute_group *pcibus_groups[] = { @@ -1782,21 +1776,6 @@ static const struct attribute_group pci_dev_resource_resize_attr_group = { .is_visible = resource_resize_attr_is_visible, }; -#ifdef HAVE_PCI_LEGACY -static int __init pci_sysfs_init(void) -{ - struct pci_bus *pbus = NULL; - - sysfs_initialized = 1; - - while ((pbus = pci_find_next_bus(pbus))) - pci_create_legacy_files(pbus); - - return 0; -} -late_initcall(pci_sysfs_init); -#endif - static struct attribute *pci_dev_dev_attrs[] = { &dev_attr_boot_vga.attr, &dev_attr_serial_number.attr, diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index c64c7f5f0bcf..4d17dab4662c 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -358,14 +358,6 @@ static inline int pci_proc_detach_bus(struct pci_bus *bus) { return 0; } int pci_hp_add_bridge(struct pci_dev *dev); bool pci_hp_spurious_link_change(struct pci_dev *pdev); -#if defined(CONFIG_SYSFS) && defined(HAVE_PCI_LEGACY) -void pci_create_legacy_files(struct pci_bus *bus); -void pci_remove_legacy_files(struct pci_bus *bus); -#else -static inline void pci_create_legacy_files(struct pci_bus *bus) { } -static inline void pci_remove_legacy_files(struct pci_bus *bus) { } -#endif - /* Lock for read/write access to pci device and bus lists */ extern struct rw_semaphore pci_bus_sem; extern struct mutex pci_slot_mutex; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index b63cd0c310bc..748c7a198262 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1073,9 +1073,6 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge) dev_err(&bus->dev, "failed to add bus: %d\n", err); } - /* Create legacy_io and legacy_mem files for this bus */ - pci_create_legacy_files(bus); - if (parent) dev_info(parent, "PCI host bridge to bus %s\n", name); else @@ -1281,9 +1278,6 @@ static struct pci_bus *pci_alloc_child_bus(struct pci_bus *parent, dev_err(&child->dev, "failed to add bus: %d\n", ret); } - /* Create legacy_io and legacy_mem files for this bus */ - pci_create_legacy_files(child); - return child; } diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index 6e796dbc5b29..d8bffa21498a 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -65,8 +65,6 @@ void pci_remove_bus(struct pci_bus *bus) list_del(&bus->node); pci_bus_release_busn_res(bus); up_write(&pci_bus_sem); - pci_remove_legacy_files(bus); - if (bus->ops->remove_bus) bus->ops->remove_bus(bus); diff --git a/include/linux/pci.h b/include/linux/pci.h index e37677a8dd3c..74b767012766 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -726,8 +726,6 @@ struct pci_bus { pci_bus_flags_t bus_flags; /* Inherited by child buses */ struct device *bridge; struct device dev; - struct bin_attribute *legacy_io; /* Legacy I/O for this bus */ - struct bin_attribute *legacy_mem; /* Legacy mem */ unsigned int is_added:1; unsigned int unsafe_warn:1; /* warned about RW1C config write */ unsigned int flit_mode:1; /* Link in Flit mode */ From c9b4bd6c0839c855b7cc221af0283c1900036fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 8 May 2026 04:35:43 +0000 Subject: [PATCH 167/168] PCI/sysfs: Limit BAR resize attribute scope to platforms with PCI mmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, __resource_resize_store() uses sysfs_remove_groups() and sysfs_create_groups() on pci_dev_resource_attr_groups to tear down and recreate the resourceN files after a BAR resize, so the updated BAR sizes are visible in sysfs. The resourceN files only exist on platforms that define HAVE_PCI_MMAP or ARCH_GENERIC_PCI_MMAP_RESOURCE. On platforms that define neither, pci_dev_resource_attr_groups is NULL and the sysfs_remove_groups() and sysfs_create_groups() calls in __resource_resize_store() become no-ops. Resizable BAR (ReBAR) is a PCI Express Extended Capability (PCI_EXT_CAP_ID_REBAR) that requires PCIe extended config space. Every PCIe-capable architecture defines HAVE_PCI_MMAP or ARCH_GENERIC_PCI_MMAP_RESOURCE (via arch headers or the asm-generic/pci.h fallback). Architectures without either only support conventional PCI and cannot have any ReBAR-capable devices. Move the resize show and store helpers, the per-BAR attribute definitions, and the attribute group behind the existing #ifdef HAVE_PCI_MMAP || ARCH_GENERIC_PCI_MMAP_RESOURCE guard, and fold the group reference in pci_dev_groups[] into the existing #if block. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Tested-by: Shivaprasad G Bhat Link: https://patch.msgid.link/20260508043543.217179-25-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 15969930b4d1..d4b5f385a6cd 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1661,6 +1661,7 @@ static const struct attribute_group pci_dev_reset_method_attr_group = { .is_visible = pci_dev_reset_attr_is_visible, }; +#if defined(HAVE_PCI_MMAP) || defined(ARCH_GENERIC_PCI_MMAP_RESOURCE) static ssize_t __resource_resize_show(struct device *dev, int n, char *buf) { struct pci_dev *pdev = to_pci_dev(dev); @@ -1775,6 +1776,7 @@ static const struct attribute_group pci_dev_resource_resize_attr_group = { .attrs = resource_resize_attrs, .is_visible = resource_resize_attr_is_visible, }; +#endif static struct attribute *pci_dev_dev_attrs[] = { &dev_attr_boot_vga.attr, @@ -1849,8 +1851,8 @@ const struct attribute_group *pci_dev_groups[] = { &pci_dev_resource_io_attr_group, &pci_dev_resource_uc_attr_group, &pci_dev_resource_wc_attr_group, -#endif &pci_dev_resource_resize_attr_group, +#endif &pci_dev_config_attr_group, &pci_dev_rom_attr_group, &pci_dev_reset_attr_group, From 92742802ecbf215a2b60dcfd326d2213595010f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wilczy=C5=84ski?= Date: Fri, 12 Jun 2026 18:24:48 +0000 Subject: [PATCH 168/168] PCI/sysfs: Use kstrtobool() to parse the ROM attribute input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_write_rom() controls access to the ROM content through the corresponding sysfs attribute, and treats the input as a request to disable only when it matches the string "0\n" exactly: if ((off == 0) && (*buf == '0') && (count == 2)) The count == 2 condition encodes the trailing newline that echo(1) appends. This was found when userspace wrote "0" without a trailing newline aiming to disable access, which failed to match the condition above and enabled access instead. For example: $ echo 0 > rom # "0\n", count 2, access disabled $ echo -n 0 > rom # "0", count 1, access enabled $ echo > rom # "", count 1, access enabled (likely not desirable) Parse the input with kstrtobool(), which handles common boolean inputs such as "0", "1", "n", "y" or "off", "on", with or without a trailing newline, so both of the above disable access, and update the now stale comment. As a side effect, input that does not parse as a boolean is rejected with -EINVAL rather than enabling access. The documented "0" and "1" continue to work as before, and rejecting malformed input brings the attribute in line with how sysfs attributes typically handle it. Signed-off-by: Krzysztof Wilczyński Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260612182448.552406-1-kwilczynski@kernel.org --- drivers/pci/pci-sysfs.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index d4b5f385a6cd..5ec0b245a69b 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1418,18 +1418,19 @@ static const struct attribute_group *pci_dev_resource_attr_groups[] = { * @off: file offset * @count: number of byte in input * - * writing anything except 0 enables it + * Writing a boolean value enables or disables the ROM display. */ static ssize_t pci_write_rom(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buf, loff_t off, size_t count) { struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + bool enable; - if ((off == 0) && (*buf == '0') && (count == 2)) - pdev->rom_attr_enabled = 0; - else - pdev->rom_attr_enabled = 1; + if (kstrtobool(buf, &enable)) + return -EINVAL; + + pdev->rom_attr_enabled = enable; return count; }