From beb2f81792a8a619e5122b6b24a374861309c54b Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Thu, 8 Jan 2026 17:02:08 -0700 Subject: [PATCH 01/12] PCI: Mark ASM1164 SATA controller to avoid bus reset User forums report issues when assigning ASM1164 SATA controllers to VMs, especially in configurations with multiple controllers. Logs show the device fails to retrain after bus reset. Reports suggest this is an issue across multiple platforms. The device indicates support for PM reset, therefore the device still has a viable function level reset mechanism. The reporting user confirms the device is well behaved in this use case with bus reset disabled. Reported-by: Patrick Bianchi Link: https://forum.proxmox.com/threads/problems-with-pcie-passthrough-with-two-identical-devices.149003/ Signed-off-by: Alex Williamson Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260109000211.398300-1-alex.williamson@nvidia.com --- drivers/pci/quirks.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index b9c252aa6fe0..3a8d5622ee2b 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3791,6 +3791,16 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CAVIUM, 0xa100, quirk_no_bus_reset); */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_TI, 0xb005, quirk_no_bus_reset); +/* + * Reports from users making use of PCI device assignment with ASM1164 + * controllers indicate an issue with bus reset where the device fails to + * retrain. The issue appears more common in configurations with multiple + * controllers. The device does indicate PM reset support (NoSoftRst-), + * therefore this still leaves a viable reset method. + * https://forum.proxmox.com/threads/problems-with-pcie-passthrough-with-two-identical-devices.149003/ + */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_ASMEDIA, 0x1164, quirk_no_bus_reset); + static void quirk_no_pm_reset(struct pci_dev *dev) { /* From c81a2ce6b6a844d1a57d2a69833a9d0f00403f00 Mon Sep 17 00:00:00 2001 From: Johnny-CC Chang Date: Thu, 13 Nov 2025 16:44:06 +0800 Subject: [PATCH 02/12] PCI: Mark Nvidia GB10 to avoid bus reset After asserting Secondary Bus Reset to downstream devices via a GB10 Root Port, the link may not retrain correctly, e.g., the link may retrain with a lower lane count or config accesses to downstream devices may fail. Prevent use of Secondary Bus Reset for devices below GB10. Signed-off-by: Johnny-CC Chang [bhelgaas: drop pci_ids.h update (only used once), update commit log] Signed-off-by: Bjorn Helgaas Reviewed-by: Manivannan Sadhasivam Link: https://patch.msgid.link/20251113084441.2124737-1-Johnny-CC.Chang@mediatek.com --- drivers/pci/quirks.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 3a8d5622ee2b..6e3b9d7ca7ee 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3748,6 +3748,14 @@ static void quirk_no_bus_reset(struct pci_dev *dev) dev->dev_flags |= PCI_DEV_FLAGS_NO_BUS_RESET; } +/* + * After asserting Secondary Bus Reset to downstream devices via a GB10 + * Root Port, the link may not retrain correctly. + * https://lore.kernel.org/r/20251113084441.2124737-1-Johnny-CC.Chang@mediatek.com + */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NVIDIA, 0x22CE, quirk_no_bus_reset); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NVIDIA, 0x22D0, quirk_no_bus_reset); + /* * Some NVIDIA GPU devices do not work with bus reset, SBR needs to be * prevented for those affected devices. From 9368d1ee62829b08aa31836b3ca003803caf0b72 Mon Sep 17 00:00:00 2001 From: Jinhui Guo Date: Fri, 12 Dec 2025 22:55:28 +0800 Subject: [PATCH 03/12] PCI: Fix pci_slot_trylock() error handling Commit a4e772898f8b ("PCI: Add missing bridge lock to pci_bus_lock()") delegates the bridge device's pci_dev_trylock() to pci_bus_trylock() in pci_slot_trylock(), but it forgets to remove the corresponding pci_dev_unlock() when pci_bus_trylock() fails. Before a4e772898f8b, the code did: if (!pci_dev_trylock(dev)) /* <- lock bridge device */ goto unlock; if (dev->subordinate) { if (!pci_bus_trylock(dev->subordinate)) { pci_dev_unlock(dev); /* <- unlock bridge device */ goto unlock; } } After a4e772898f8b the bridge-device lock is no longer taken, but the pci_dev_unlock(dev) on the failure path was left in place, leading to the bug. This yields one of two errors: 1. A warning that the lock is being unlocked when no one holds it. 2. An incorrect unlock of a lock that belongs to another thread. Fix it by removing the now-redundant pci_dev_unlock(dev) on the failure path. [Same patch later posted by Keith at https://patch.msgid.link/20260116184150.3013258-1-kbusch@meta.com] Fixes: a4e772898f8b ("PCI: Add missing bridge lock to pci_bus_lock()") Signed-off-by: Jinhui Guo Signed-off-by: Bjorn Helgaas Reviewed-by: Dan Williams Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20251212145528.2555-1-guojinhui.liam@bytedance.com --- drivers/pci/pci.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 13dbb405dc31..59319e08fca6 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5346,10 +5346,8 @@ static int pci_slot_trylock(struct pci_slot *slot) if (!dev->slot || dev->slot != slot) continue; if (dev->subordinate) { - if (!pci_bus_trylock(dev->subordinate)) { - pci_dev_unlock(dev); + if (!pci_bus_trylock(dev->subordinate)) goto unlock; - } } else if (!pci_dev_trylock(dev)) goto unlock; } From 1f5e57c622b4dc9b8e7d291d560138d92cfbe5bf Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Fri, 30 Jan 2026 08:59:51 -0800 Subject: [PATCH 04/12] PCI: Fix pci_slot_lock () device locking Like pci_bus_lock(), pci_slot_lock() needs to lock the bridge device to prevent warnings like: pcieport 0000:e2:05.0: unlocked secondary bus reset via: pciehp_reset_slot+0x55/0xa0 Take and release the lock for the bridge providing the slot for the lock/trylock and unlock routines. Signed-off-by: Keith Busch Signed-off-by: Bjorn Helgaas Reviewed-by: Dan Williams Link: https://patch.msgid.link/20260130165953.751063-3-kbusch@meta.com --- drivers/pci/pci.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 59319e08fca6..57a5b205175f 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5290,10 +5290,9 @@ static int pci_bus_trylock(struct pci_bus *bus) /* Do any devices on or below this slot prevent a bus reset? */ static bool pci_slot_resettable(struct pci_slot *slot) { - struct pci_dev *dev; + struct pci_dev *dev, *bridge = slot->bus->self; - if (slot->bus->self && - (slot->bus->self->dev_flags & PCI_DEV_FLAGS_NO_BUS_RESET)) + if (bridge && (bridge->dev_flags & PCI_DEV_FLAGS_NO_BUS_RESET)) return false; list_for_each_entry(dev, &slot->bus->devices, bus_list) { @@ -5310,7 +5309,10 @@ static bool pci_slot_resettable(struct pci_slot *slot) /* Lock devices from the top of the tree down */ static void pci_slot_lock(struct pci_slot *slot) { - struct pci_dev *dev; + struct pci_dev *dev, *bridge = slot->bus->self; + + if (bridge) + pci_dev_lock(bridge); list_for_each_entry(dev, &slot->bus->devices, bus_list) { if (!dev->slot || dev->slot != slot) @@ -5325,7 +5327,7 @@ static void pci_slot_lock(struct pci_slot *slot) /* Unlock devices from the bottom of the tree up */ static void pci_slot_unlock(struct pci_slot *slot) { - struct pci_dev *dev; + struct pci_dev *dev, *bridge = slot->bus->self; list_for_each_entry(dev, &slot->bus->devices, bus_list) { if (!dev->slot || dev->slot != slot) @@ -5335,12 +5337,18 @@ static void pci_slot_unlock(struct pci_slot *slot) else pci_dev_unlock(dev); } + + if (bridge) + pci_dev_unlock(bridge); } /* Return 1 on successful lock, 0 on contention */ static int pci_slot_trylock(struct pci_slot *slot) { - struct pci_dev *dev; + struct pci_dev *dev, *bridge = slot->bus->self; + + if (bridge && !pci_dev_trylock(bridge)) + return 0; list_for_each_entry(dev, &slot->bus->devices, bus_list) { if (!dev->slot || dev->slot != slot) @@ -5363,6 +5371,9 @@ static int pci_slot_trylock(struct pci_slot *slot) else pci_dev_unlock(dev); } + + if (bridge) + pci_dev_unlock(bridge); return 0; } From 183c291caa34cc1e721a571058a3f972c5b35122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Fri, 16 Jan 2026 14:57:40 +0200 Subject: [PATCH 05/12] PCI: Use lockdep_assert_held(pci_bus_sem) to verify lock is held MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function comment for pci_bus_max_d3cold_delay() declares pci_bus_sem must be held while calling the function which can be automatically checked. Add lockdep_assert_held(pci_bus_sem) to confirm pci_bus_sem is held. Also mark the comment line with Context prefix. Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260116125742.1890-2-ilpo.jarvinen@linux.intel.com --- drivers/pci/pci.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 57a5b205175f..edcc22dc95d3 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -4622,7 +4623,7 @@ bool pcie_wait_for_link(struct pci_dev *pdev, bool active) * spec says 100 ms, but firmware can lower it and we allow drivers to * increase it as well. * - * Called with @pci_bus_sem locked for reading. + * Context: Called with @pci_bus_sem locked for reading. */ static int pci_bus_max_d3cold_delay(const struct pci_bus *bus) { @@ -4630,6 +4631,8 @@ static int pci_bus_max_d3cold_delay(const struct pci_bus *bus) int min_delay = 100; int max_delay = 0; + lockdep_assert_held(&pci_bus_sem); + list_for_each_entry(pdev, &bus->devices, bus_list) { if (pdev->d3cold_delay < min_delay) min_delay = pdev->d3cold_delay; From f06e0ad226fdb875cfd6278882ef28fe817283c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilpo=20J=C3=A4rvinen?= Date: Fri, 16 Jan 2026 14:57:41 +0200 Subject: [PATCH 06/12] PCI: Use device_lock_assert() to verify device lock is held MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple function comments say the function should be called with device_lock held. Check that by calling device_lock_assert(). Signed-off-by: Ilpo Järvinen Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260116125742.1890-3-ilpo.jarvinen@linux.intel.com --- drivers/pci/pci.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index edcc22dc95d3..5c109f62eb4a 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -4970,6 +4970,7 @@ static void pci_dev_save_and_disable(struct pci_dev *dev) * races with ->remove() by the device lock, which must be held by * the caller. */ + device_lock_assert(&dev->dev); if (err_handler && err_handler->reset_prepare) err_handler->reset_prepare(dev); else if (dev->driver) @@ -5040,7 +5041,9 @@ const struct pci_reset_fn_method pci_reset_fn_methods[] = { * device including MSI, bus mastering, BARs, decoding IO and memory spaces, * etc. * - * Returns 0 if the device function was successfully reset or negative if the + * Context: The caller must hold the device lock. + * + * Return: 0 if the device function was successfully reset or negative if the * device doesn't support resetting a single function. */ int __pci_reset_function_locked(struct pci_dev *dev) @@ -5049,6 +5052,7 @@ int __pci_reset_function_locked(struct pci_dev *dev) const struct pci_reset_fn_method *method; might_sleep(); + device_lock_assert(&dev->dev); /* * A reset method returns -ENOTTY if it doesn't support this device and @@ -5171,13 +5175,17 @@ EXPORT_SYMBOL_GPL(pci_reset_function); * over the reset. It also differs from pci_reset_function() in that it * requires the PCI device lock to be held. * - * Returns 0 if the device function was successfully reset or negative if the + * Context: The caller must hold the device lock. + * + * Return: 0 if the device function was successfully reset or negative if the * device doesn't support resetting a single function. */ int pci_reset_function_locked(struct pci_dev *dev) { int rc; + device_lock_assert(&dev->dev); + if (!pci_reset_supported(dev)) return -ENOTTY; From 44d2f70b1fd72c339c72983fcffa181beae3e113 Mon Sep 17 00:00:00 2001 From: Krishna Chaitanya Chundru Date: Fri, 9 Jan 2026 13:53:32 +0530 Subject: [PATCH 07/12] PCI: Add ACS quirk for Qualcomm Hamoa & Glymur The Qualcomm Hamoa & Glymur Root Ports don't advertise an ACS capability, but they do provide ACS-like features to disable peer transactions and validate bus numbers in requests. Add an ACS quirk for Hamoa & Glymur. Signed-off-by: Krishna Chaitanya Chundru Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260109-acs_quirk-v1-1-82adf95a89ae@oss.qualcomm.com --- drivers/pci/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 6e3b9d7ca7ee..eff77433fbc1 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5125,6 +5125,10 @@ static const struct pci_dev_acs_enabled { { PCI_VENDOR_ID_QCOM, 0x0401, pci_quirk_qcom_rp_acs }, /* QCOM SA8775P root port */ { PCI_VENDOR_ID_QCOM, 0x0115, pci_quirk_qcom_rp_acs }, + /* QCOM Hamoa root port */ + { PCI_VENDOR_ID_QCOM, 0x0111, pci_quirk_qcom_rp_acs }, + /* QCOM Glymur root port */ + { PCI_VENDOR_ID_QCOM, 0x0120, pci_quirk_qcom_rp_acs }, /* HXT SD4800 root ports. The ACS design is same as QCOM QDF2xxx */ { PCI_VENDOR_ID_HXT, 0x0401, pci_quirk_qcom_rp_acs }, /* Intel PCH root ports */ From 5907a90551e9f7968781f3a6ab8684458959beb3 Mon Sep 17 00:00:00 2001 From: Nicolas Cavallari Date: Mon, 19 Jan 2026 17:08:33 +0100 Subject: [PATCH 08/12] PCI: Add ACS quirk for Pericom PI7C9X2G404 switches [12d8:b404] 12d8:b404 is apparently another PCI ID for Pericom PI7C9X2G404 (as identified by the chip silkscreen and lspci). It is also affected by the PI7C9X2G errata (e.g. a network card attached to it fails under load when P2P Redirect Request is enabled), so apply the same quirk to this PCI ID too. PCI bridge [0604]: Pericom Semiconductor PI7C9X2G404 EV/SV PCIe2 4-Port/4-Lane Packet Switch [12d8:b404] (rev 01) Fixes: acd61ffb2f16 ("PCI: Add ACS quirk for Pericom PI7C9X2G switches") Closes: https://lore.kernel.org/all/a1d926f0-4cb5-4877-a4df-617902648d80@green-communications.fr/ Signed-off-by: Nicolas Cavallari Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260119160915.26456-1-nicolas.cavallari@green-communications.fr --- drivers/pci/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index eff77433fbc1..9bd9048af856 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6210,6 +6210,10 @@ DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0x2303, pci_fixup_pericom_acs_store_forward); DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0x2303, pci_fixup_pericom_acs_store_forward); +DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_PERICOM, 0xb404, + pci_fixup_pericom_acs_store_forward); +DECLARE_PCI_FIXUP_RESUME(PCI_VENDOR_ID_PERICOM, 0xb404, + pci_fixup_pericom_acs_store_forward); static void nvidia_ion_ahci_fixup(struct pci_dev *pdev) { From c41e2fb67e26b04d919257875fa954aa5f6e392e Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 2 Jan 2026 21:04:47 +0530 Subject: [PATCH 09/12] PCI: Enable ACS after configuring IOMMU for OF platforms Platform, ACPI, or IOMMU drivers call pci_request_acs(), which sets 'pci_acs_enable' to request that ACS be enabled for any devices enumerated in the future. OF platforms called pci_enable_acs() for the first device before of_iommu_configure() called pci_request_acs(), so ACS was never enabled for that device (typically a Root Port). Call pci_enable_acs() later, from pci_dma_configure(), after of_dma_configure() has had a chance to call pci_request_acs(). Here's the call path, showing the move of pci_enable_acs() from pci_acs_init() to pci_dma_configure(), where it always happens after pci_request_acs(): pci_device_add pci_init_capabilities pci_acs_init - pci_enable_acs - if (pci_acs_enable) <-- previous test - ... device_add bus_notify(BUS_NOTIFY_ADD_DEVICE) iommu_bus_notifier iommu_probe_device iommu_init_device dev->bus->dma_configure pci_dma_configure # pci_bus_type.dma_configure of_dma_configure of_iommu_configure pci_request_acs pci_acs_enable = 1 <-- set + pci_enable_acs + if (pci_acs_enable) <-- new test + ... bus_probe_device device_initial_probe ... really_probe dev->bus->dma_configure pci_dma_configure # pci_bus_type.dma_configure ... pci_enable_acs Note that we will now call pci_enable_acs() twice for every device, first from the iommu_probe_device() path and again from the really_probe() path. Presumably that's not an issue since we also call dev->bus->dma_configure() twice. For the ACPI platforms, pci_request_acs() is called during ACPI initialization time itself, independent of the IOMMU framework. Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Tested-by: Marek Szyprowski Tested-by: Naresh Kamboju Link: https://patch.msgid.link/20260102-pci_acs-v3-1-72280b94d288@oss.qualcomm.com --- drivers/pci/pci-driver.c | 8 ++++++++ drivers/pci/pci.c | 10 +--------- drivers/pci/pci.h | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 7c2d9d596258..301a9418e38e 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -1650,6 +1650,14 @@ static int pci_dma_configure(struct device *dev) ret = acpi_dma_configure(dev, acpi_get_dma_attr(adev)); } + /* + * Attempt to enable ACS regardless of capability because some Root + * Ports (e.g. those quirked with *_intel_pch_acs_*) do not have + * the standard ACS capability but still support ACS via those + * quirks. + */ + pci_enable_acs(to_pci_dev(dev)); + pci_put_host_bridge_device(bridge); /* @drv may not be valid when we're called from the IOMMU layer */ diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 5c109f62eb4a..647c3c4eb482 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -1016,7 +1016,7 @@ static void pci_std_enable_acs(struct pci_dev *dev, struct pci_acs *caps) * pci_enable_acs - enable ACS if hardware support it * @dev: the PCI device */ -static void pci_enable_acs(struct pci_dev *dev) +void pci_enable_acs(struct pci_dev *dev) { struct pci_acs caps; bool enable_acs = false; @@ -3649,14 +3649,6 @@ bool pci_acs_path_enabled(struct pci_dev *start, void pci_acs_init(struct pci_dev *dev) { dev->acs_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); - - /* - * Attempt to enable ACS regardless of capability because some Root - * Ports (e.g. those quirked with *_intel_pch_acs_*) do not have - * the standard ACS capability but still support ACS via those - * quirks. - */ - pci_enable_acs(dev); } /** diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 0e67014aa001..4592ede0ebcc 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -939,6 +939,7 @@ static inline resource_size_t pci_resource_alignment(struct pci_dev *dev, } void pci_acs_init(struct pci_dev *dev); +void pci_enable_acs(struct pci_dev *dev); #ifdef CONFIG_PCI_QUIRKS int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags); int pci_dev_specific_enable_acs(struct pci_dev *dev); From 8f05a5f6745ccc9ff784736608c5a38edb09acc8 Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 2 Jan 2026 21:04:48 +0530 Subject: [PATCH 10/12] PCI: Cache ACS Capabilities register The ACS Capability register is read-only. Cache it to allow quirks to override it and to avoid re-reading it. Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log] Signed-off-by: Bjorn Helgaas Tested-by: Marek Szyprowski Tested-by: Naresh Kamboju Link: https://patch.msgid.link/20260102-pci_acs-v3-2-72280b94d288@oss.qualcomm.com --- drivers/pci/pci.c | 24 ++++++++++++++---------- include/linux/pci.h | 1 + 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 647c3c4eb482..e4c11efce34c 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -893,7 +893,6 @@ static const char *disable_acs_redir_param; static const char *config_acs_param; struct pci_acs { - u16 cap; u16 ctrl; u16 fw_ctrl; }; @@ -996,20 +995,20 @@ static void __pci_config_acs(struct pci_dev *dev, struct pci_acs *caps, static void pci_std_enable_acs(struct pci_dev *dev, struct pci_acs *caps) { /* Source Validation */ - caps->ctrl |= (caps->cap & PCI_ACS_SV); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_SV); /* P2P Request Redirect */ - caps->ctrl |= (caps->cap & PCI_ACS_RR); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_RR); /* P2P Completion Redirect */ - caps->ctrl |= (caps->cap & PCI_ACS_CR); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_CR); /* Upstream Forwarding */ - caps->ctrl |= (caps->cap & PCI_ACS_UF); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_UF); /* Enable Translation Blocking for external devices and noats */ if (pci_ats_disabled() || dev->external_facing || dev->untrusted) - caps->ctrl |= (caps->cap & PCI_ACS_TB); + caps->ctrl |= (dev->acs_capabilities & PCI_ACS_TB); } /** @@ -1032,7 +1031,6 @@ void pci_enable_acs(struct pci_dev *dev) if (!pos) return; - pci_read_config_word(dev, pos + PCI_ACS_CAP, &caps.cap); pci_read_config_word(dev, pos + PCI_ACS_CTRL, &caps.ctrl); caps.fw_ctrl = caps.ctrl; @@ -3515,7 +3513,7 @@ void pci_configure_ari(struct pci_dev *dev) static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) { int pos; - u16 cap, ctrl; + u16 ctrl; pos = pdev->acs_cap; if (!pos) @@ -3526,8 +3524,7 @@ static bool pci_acs_flags_enabled(struct pci_dev *pdev, u16 acs_flags) * or only required if controllable. Features missing from the * capability field can therefore be assumed as hard-wired enabled. */ - pci_read_config_word(pdev, pos + PCI_ACS_CAP, &cap); - acs_flags &= (cap | PCI_ACS_EC); + acs_flags &= (pdev->acs_capabilities | PCI_ACS_EC); pci_read_config_word(pdev, pos + PCI_ACS_CTRL, &ctrl); return (ctrl & acs_flags) == acs_flags; @@ -3648,7 +3645,14 @@ bool pci_acs_path_enabled(struct pci_dev *start, */ void pci_acs_init(struct pci_dev *dev) { + int pos; + dev->acs_cap = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ACS); + pos = dev->acs_cap; + if (!pos) + return; + + pci_read_config_word(dev, pos + PCI_ACS_CAP, &dev->acs_capabilities); } /** diff --git a/include/linux/pci.h b/include/linux/pci.h index 864775651c6f..6195e040b29c 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -558,6 +558,7 @@ struct pci_dev { struct pci_tsm *tsm; /* TSM operation state */ #endif u16 acs_cap; /* ACS Capability offset */ + u16 acs_capabilities; /* ACS Capabilities */ u8 supported_speeds; /* Supported Link Speeds Vector */ phys_addr_t rom; /* Physical address if not from BAR */ size_t romlen; /* Length if not from BAR */ From b26d7fb4a53e671a95b282b4f3922e79dfb1470d Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 2 Jan 2026 21:04:49 +0530 Subject: [PATCH 11/12] PCI: Disable ACS SV for IDT 0x80b5 switch Some IDT switches incorrectly flag an ACS Source Validation error on completions for config read requests before they have captured the bus number from a previous config write, even though PCIe r7.0, sec 6.12.1.1, says that completions are never affected by ACS Source Validation. The previous workaround, aa667c6408d2 ("PCI: Workaround IDT switch ACS Source Validation erratum"), temporarily disabled ACS SV during enumeration. This was effective but didn't cover the time after switch reset, when it may lose the captured bus number. Avoid the issue by preventing use of ACS SV altogether for these switches by calling pci_disable_broken_acs_cap() from pci_acs_init() and remove the previous workaround in pci_bus_read_dev_vendor_id(). Removal of ACS SV for these switches means they no longer enforce everything in REQ_ACS_FLAGS, so downstream devices are not isolated from each other and the iommu_group may include more devices. Signed-off-by: Manivannan Sadhasivam [bhelgaas: commit log, retain specific erratum details] Signed-off-by: Bjorn Helgaas Tested-by: Marek Szyprowski Tested-by: Naresh Kamboju Link: https://patch.msgid.link/20260102-pci_acs-v3-3-72280b94d288@oss.qualcomm.com --- drivers/pci/pci.c | 1 + drivers/pci/pci.h | 3 ++- drivers/pci/probe.c | 12 ------------ drivers/pci/quirks.c | 44 ++++++++++---------------------------------- 4 files changed, 13 insertions(+), 47 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index e4c11efce34c..0ce6a7b560e7 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -3653,6 +3653,7 @@ void pci_acs_init(struct pci_dev *dev) return; pci_read_config_word(dev, pos + PCI_ACS_CAP, &dev->acs_capabilities); + pci_disable_broken_acs_cap(dev); } /** diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4592ede0ebcc..5fe5d6e84c95 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -432,7 +432,6 @@ bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl, int rrs_timeout); bool pci_bus_generic_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *pl, int rrs_timeout); -int pci_idt_bus_quirk(struct pci_bus *bus, int devfn, u32 *pl, int rrs_timeout); int pci_setup_device(struct pci_dev *dev); void __pci_size_stdbars(struct pci_dev *dev, int count, @@ -944,6 +943,7 @@ void pci_enable_acs(struct pci_dev *dev); int pci_dev_specific_acs_enabled(struct pci_dev *dev, u16 acs_flags); int pci_dev_specific_enable_acs(struct pci_dev *dev); int pci_dev_specific_disable_acs_redir(struct pci_dev *dev); +void pci_disable_broken_acs_cap(struct pci_dev *pdev); int pcie_failed_link_retrain(struct pci_dev *dev); #else static inline int pci_dev_specific_acs_enabled(struct pci_dev *dev, @@ -959,6 +959,7 @@ static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev) { return -ENOTTY; } +static inline void pci_disable_broken_acs_cap(struct pci_dev *dev) { } static inline int pcie_failed_link_retrain(struct pci_dev *dev) { return -ENOTTY; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 41183aed8f5d..c7304ac5afc2 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2547,18 +2547,6 @@ bool pci_bus_generic_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l, int timeout) { -#ifdef CONFIG_PCI_QUIRKS - struct pci_dev *bridge = bus->self; - - /* - * Certain IDT switches have an issue where they improperly trigger - * ACS Source Validation errors on completions for config reads. - */ - if (bridge && bridge->vendor == PCI_VENDOR_ID_IDT && - bridge->device == 0x80b5) - return pci_idt_bus_quirk(bus, devfn, l, timeout); -#endif - return pci_bus_generic_read_dev_vendor_id(bus, devfn, l, timeout); } EXPORT_SYMBOL(pci_bus_read_dev_vendor_id); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 9bd9048af856..6360c172ff0d 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5801,7 +5801,7 @@ DECLARE_PCI_FIXUP_CLASS_RESUME_EARLY(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, /* * Some IDT switches incorrectly flag an ACS Source Validation error on - * completions for config read requests even though PCIe r4.0, sec + * completions for config read requests even though PCIe r7.0, sec * 6.12.1.1, says that completions are never affected by ACS Source * Validation. Here's the text of IDT 89H32H8G3-YC, erratum #36: * @@ -5814,44 +5814,20 @@ DECLARE_PCI_FIXUP_CLASS_RESUME_EARLY(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, * * The workaround suggested by IDT is to issue a config write to the * downstream device before issuing the first config read. This allows the - * downstream device to capture its bus and device numbers (see PCIe r4.0, - * sec 2.2.9), thus avoiding the ACS error on the completion. + * downstream device to capture its bus and device numbers (see PCIe r7.0, + * sec 2.2.9.1), thus avoiding the ACS error on the completion. * * However, we don't know when the device is ready to accept the config - * write, so we do config reads until we receive a non-Config Request Retry - * Status, then do the config write. - * - * To avoid hitting the erratum when doing the config reads, we disable ACS - * SV around this process. + * write, and the issue affects resets of the switch as well as enumeration, + * so disable use of ACS SV for these devices altogether. */ -int pci_idt_bus_quirk(struct pci_bus *bus, int devfn, u32 *l, int timeout) +void pci_disable_broken_acs_cap(struct pci_dev *pdev) { - int pos; - u16 ctrl = 0; - bool found; - struct pci_dev *bridge = bus->self; - - pos = bridge->acs_cap; - - /* Disable ACS SV before initial config reads */ - if (pos) { - pci_read_config_word(bridge, pos + PCI_ACS_CTRL, &ctrl); - if (ctrl & PCI_ACS_SV) - pci_write_config_word(bridge, pos + PCI_ACS_CTRL, - ctrl & ~PCI_ACS_SV); + if (pdev->vendor == PCI_VENDOR_ID_IDT && + pdev->device == 0x80b5) { + pci_info(pdev, "Disabling broken ACS SV; downstream device isolation reduced\n"); + pdev->acs_capabilities &= ~PCI_ACS_SV; } - - found = pci_bus_generic_read_dev_vendor_id(bus, devfn, l, timeout); - - /* Write Vendor ID (read-only) so the endpoint latches its bus/dev */ - if (found) - pci_bus_write_config_word(bus, devfn, PCI_VENDOR_ID, 0); - - /* Re-enable ACS_SV if it was previously enabled */ - if (ctrl & PCI_ACS_SV) - pci_write_config_word(bridge, pos + PCI_ACS_CTRL, ctrl); - - return found; } /* From b5f88a3947055e4ef8c04222ec75950d2fdfa79f Mon Sep 17 00:00:00 2001 From: Manivannan Sadhasivam Date: Fri, 2 Jan 2026 21:04:50 +0530 Subject: [PATCH 12/12] PCI: Disable ACS SV for IDT 0x8090 switch The IDT switch with Device ID 0x8090 used in the ARM Juno R2 development board incorrectly raises an ACS Source Validation error on Completions for Config Read Requests, even though PCIe r7.0, sec 6.12.1.1, says that Completions are never affected by ACS Source Validation. This is already handled by the pci_disable_broken_acs_cap() quirk for the IDT 0x80b5 switch. Extend the quirk for the 0x8090 device too. Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Tested-by: Marek Szyprowski Tested-by: Naresh Kamboju Link: https://patch.msgid.link/20260102-pci_acs-v3-4-72280b94d288@oss.qualcomm.com --- drivers/pci/quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 6360c172ff0d..e5c32a21d57f 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -5824,7 +5824,7 @@ DECLARE_PCI_FIXUP_CLASS_RESUME_EARLY(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID, void pci_disable_broken_acs_cap(struct pci_dev *pdev) { if (pdev->vendor == PCI_VENDOR_ID_IDT && - pdev->device == 0x80b5) { + (pdev->device == 0x80b5 || pdev->device == 0x8090)) { pci_info(pdev, "Disabling broken ACS SV; downstream device isolation reduced\n"); pdev->acs_capabilities &= ~PCI_ACS_SV; }