* [PATCH 1/5] PCI: Add disconnected device checks to user config reads
2026-10-25 0:08 [PATCH 0/5] PCI/sysfs: Wait for config access lock in sysfs attributes Matthew Wood
@ 2026-10-25 0:08 ` Matthew Wood
2026-10-25 0:08 ` [PATCH 2/5] PCI/sysfs: Use user config accessors for bus number attributes Matthew Wood
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Wood @ 2026-10-25 0:08 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel, kbusch, Nikita Vetoshkin
Since commit 4b1038834739 ("PCI: Don't attempt config access to
disconnected devices") the kernel config read accessors skip devices
marked disconnected, returning PCIBIOS_DEVICE_NOT_FOUND and ~0 data.
The user config read accessors still issue the read, and if the host
bridge completes it with ~0 data and no error, they report success.
Check pci_dev_is_disconnected() in pci_user_read_config_*() as well,
failing with -ENODEV and ~0 data. Do the check after waiting for any
pci_cfg_access_lock() holder so a reader blocked behind a reset also
sees a device that was disconnected while it waited.
This prepares for converting sysfs attributes from the kernel
accessors to the user accessors so they keep skipping disconnected
devices.
On a disconnected device, existing callers of the user read accessors
now behave as follows:
- The sysfs "config" file and /proc/bus/pci ignore the return value
and return the same ~0 data as before
- pciconfig_read() fails with -EIO
- VPD reads and VFIO config reads fail with -ENODEV instead of
returning ~0 data
- VPD writes fail with -ENODEV instead of timing out with a
"firmware bug" warning
- VFIO writes to PCI_COMMAND fail with -ENODEV, since VFIO reads the
current value first
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
Tested-by: Nikita Vetoshkin <nekto0n@meta.com>
---
drivers/pci/access.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/access.c b/drivers/pci/access.c
index b123da16b63b..18a206bb2a94 100644
--- a/drivers/pci/access.c
+++ b/drivers/pci/access.c
@@ -237,8 +237,11 @@ int pci_user_read_config_##size \
raw_spin_lock_irq(&pci_lock); \
if (unlikely(dev->block_cfg_access)) \
pci_wait_cfg(dev); \
- ret = dev->bus->ops->read(dev->bus, dev->devfn, \
- pos, sizeof(type), &data); \
+ if (pci_dev_is_disconnected(dev)) \
+ ret = PCIBIOS_DEVICE_NOT_FOUND; \
+ else \
+ ret = dev->bus->ops->read(dev->bus, dev->devfn, \
+ pos, sizeof(type), &data); \
raw_spin_unlock_irq(&pci_lock); \
if (ret) \
PCI_SET_ERROR_RESPONSE(val); \
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 2/5] PCI/sysfs: Use user config accessors for bus number attributes
2026-10-25 0:08 [PATCH 0/5] PCI/sysfs: Wait for config access lock in sysfs attributes Matthew Wood
2026-10-25 0:08 ` [PATCH 1/5] PCI: Add disconnected device checks to user config reads Matthew Wood
@ 2026-10-25 0:08 ` Matthew Wood
2026-10-25 0:08 ` [PATCH 3/5] PCI: Add pcie_capability_user_read_{word,dword}() Matthew Wood
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Wood @ 2026-10-25 0:08 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel, kbusch, Nikita Vetoshkin
secondary_bus_number and subordinate_bus_number read config space with
pci_read_config_byte(). The kernel config accessors don't check
block_cfg_access, so a read that races with a reset goes to the device
mid-reset and can fail or return a bogus bus number.
Use pci_user_read_config_byte() instead, as the "config" attribute does,
so the read waits for any pci_cfg_access_lock() holder (e.g.
pci_reset_function()) to finish.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
Tested-by: Nikita Vetoshkin <nekto0n@meta.com>
---
drivers/pci/pci-sysfs.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index 6669e9ad5bd3..b37a285d717d 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -267,7 +267,7 @@ static ssize_t secondary_bus_number_show(struct device *dev,
int err;
pci_config_pm_runtime_get(pci_dev);
- err = pci_read_config_byte(pci_dev, PCI_SECONDARY_BUS, &sec_bus);
+ err = pci_user_read_config_byte(pci_dev, PCI_SECONDARY_BUS, &sec_bus);
pci_config_pm_runtime_put(pci_dev);
if (err)
@@ -286,7 +286,7 @@ static ssize_t subordinate_bus_number_show(struct device *dev,
int err;
pci_config_pm_runtime_get(pci_dev);
- err = pci_read_config_byte(pci_dev, PCI_SUBORDINATE_BUS, &sub_bus);
+ err = pci_user_read_config_byte(pci_dev, PCI_SUBORDINATE_BUS, &sub_bus);
pci_config_pm_runtime_put(pci_dev);
if (err)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 3/5] PCI: Add pcie_capability_user_read_{word,dword}()
2026-10-25 0:08 [PATCH 0/5] PCI/sysfs: Wait for config access lock in sysfs attributes Matthew Wood
2026-10-25 0:08 ` [PATCH 1/5] PCI: Add disconnected device checks to user config reads Matthew Wood
2026-10-25 0:08 ` [PATCH 2/5] PCI/sysfs: Use user config accessors for bus number attributes Matthew Wood
@ 2026-10-25 0:08 ` Matthew Wood
2026-10-25 0:08 ` [PATCH 4/5] PCI/sysfs: Use user config accessors for link attributes Matthew Wood
2026-10-25 0:08 ` [PATCH 5/5] PCI/sysfs: Use user config accessors for serial_number Matthew Wood
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Wood @ 2026-10-25 0:08 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel, kbusch, Nikita Vetoshkin
pcie_capability_read_word() and pcie_capability_read_dword() use the
kernel config accessors which don't check block_cfg_access, so they
can't be used for reads on behalf of userspace that should wait for a
reset (or other pci_cfg_access_lock() holder) to finish.
Add pcie_capability_user_read_word() and
pcie_capability_user_read_dword() helpers, which provide the same function
using the user config accessors.
These are for use within the PCI core, so they are declared in
drivers/pci/pci.h and not exported.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
Tested-by: Nikita Vetoshkin <nekto0n@meta.com>
---
drivers/pci/access.c | 50 ++++++++++++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 2 ++
2 files changed, 52 insertions(+)
diff --git a/drivers/pci/access.c b/drivers/pci/access.c
index 18a206bb2a94..ff5a16236758 100644
--- a/drivers/pci/access.c
+++ b/drivers/pci/access.c
@@ -490,6 +490,56 @@ int pcie_capability_read_dword(struct pci_dev *dev, int pos, u32 *val)
}
EXPORT_SYMBOL(pcie_capability_read_dword);
+/*
+ * Same as pcie_capability_read_{word,dword}(), but using the user config
+ * accessors. Returns 0 on success, negative values indicate error.
+ */
+int pcie_capability_user_read_word(struct pci_dev *dev, int pos, u16 *val)
+{
+ int ret;
+
+ *val = 0;
+ if (pos & 1)
+ return -EINVAL;
+
+ if (pcie_capability_reg_implemented(dev, pos)) {
+ ret = pci_user_read_config_word(dev, pci_pcie_cap(dev) + pos,
+ val);
+ if (ret)
+ *val = 0;
+ return ret;
+ }
+
+ if (pci_is_pcie(dev) && pcie_downstream_port(dev) &&
+ pos == PCI_EXP_SLTSTA)
+ *val = PCI_EXP_SLTSTA_PDS;
+
+ return 0;
+}
+
+int pcie_capability_user_read_dword(struct pci_dev *dev, int pos, u32 *val)
+{
+ int ret;
+
+ *val = 0;
+ if (pos & 3)
+ return -EINVAL;
+
+ if (pcie_capability_reg_implemented(dev, pos)) {
+ ret = pci_user_read_config_dword(dev, pci_pcie_cap(dev) + pos,
+ val);
+ if (ret)
+ *val = 0;
+ return ret;
+ }
+
+ if (pci_is_pcie(dev) && pcie_downstream_port(dev) &&
+ pos == PCI_EXP_SLTSTA)
+ *val = PCI_EXP_SLTSTA_PDS;
+
+ return 0;
+}
+
int pcie_capability_write_word(struct pci_dev *dev, int pos, u16 val)
{
if (pos & 1)
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 77efa4c5c960..7c5a2ebc5706 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -128,6 +128,8 @@ extern struct mutex pci_rescan_remove_lock;
bool pcie_cap_has_lnkctl(const struct pci_dev *dev);
bool pcie_cap_has_lnkctl2(const struct pci_dev *dev);
bool pcie_cap_has_rtctl(const struct pci_dev *dev);
+int pcie_capability_user_read_word(struct pci_dev *dev, int pos, u16 *val);
+int pcie_capability_user_read_dword(struct pci_dev *dev, int pos, u32 *val);
/* Standard Capability finder */
/**
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 4/5] PCI/sysfs: Use user config accessors for link attributes
2026-10-25 0:08 [PATCH 0/5] PCI/sysfs: Wait for config access lock in sysfs attributes Matthew Wood
` (2 preceding siblings ...)
2026-10-25 0:08 ` [PATCH 3/5] PCI: Add pcie_capability_user_read_{word,dword}() Matthew Wood
@ 2026-10-25 0:08 ` Matthew Wood
2026-10-25 0:08 ` [PATCH 5/5] PCI/sysfs: Use user config accessors for serial_number Matthew Wood
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Wood @ 2026-10-25 0:08 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel, kbusch, Nikita Vetoshkin
max_link_width, current_link_speed and current_link_width read the PCIe
Link Capabilities and Link Status registers with the kernel config
accessors. Those don't check block_cfg_access so a read that races
with a reset goes to the device mid-reset and can fail or return a bogus
value.
Use pcie_capability_user_read_{word,dword}() instead so the read waits for
any pci_cfg_access_lock() holder (e.g. pci_reset_function()) to finish. For
max_link_width, this means inlining the pcie_get_width_cap() logic
instead of creating an additional helper with this single calling location.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
Tested-by: Nikita Vetoshkin <nekto0n@meta.com>
---
drivers/pci/pci-sysfs.c | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index b37a285d717d..a272f8583a8f 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -208,14 +208,16 @@ static ssize_t max_link_width_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pdev = to_pci_dev(dev);
- ssize_t ret;
+ u32 lnkcap;
/* We read PCI_EXP_LNKCAP, so we need the device to be accessible. */
pci_config_pm_runtime_get(pdev);
- ret = sysfs_emit(buf, "%u\n", pcie_get_width_cap(pdev));
+ pcie_capability_user_read_dword(pdev, PCI_EXP_LNKCAP, &lnkcap);
pci_config_pm_runtime_put(pdev);
- return ret;
+ return sysfs_emit(buf, "%u\n", lnkcap ?
+ FIELD_GET(PCI_EXP_LNKCAP_MLW, lnkcap) :
+ PCIE_LNK_WIDTH_UNKNOWN);
}
static DEVICE_ATTR_RO(max_link_width);
@@ -228,7 +230,8 @@ static ssize_t current_link_speed_show(struct device *dev,
enum pci_bus_speed speed;
pci_config_pm_runtime_get(pci_dev);
- err = pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &linkstat);
+ err = pcie_capability_user_read_word(pci_dev, PCI_EXP_LNKSTA,
+ &linkstat);
pci_config_pm_runtime_put(pci_dev);
if (err)
@@ -248,7 +251,8 @@ static ssize_t current_link_width_show(struct device *dev,
int err;
pci_config_pm_runtime_get(pci_dev);
- err = pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &linkstat);
+ err = pcie_capability_user_read_word(pci_dev, PCI_EXP_LNKSTA,
+ &linkstat);
pci_config_pm_runtime_put(pci_dev);
if (err)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 5/5] PCI/sysfs: Use user config accessors for serial_number
2026-10-25 0:08 [PATCH 0/5] PCI/sysfs: Wait for config access lock in sysfs attributes Matthew Wood
` (3 preceding siblings ...)
2026-10-25 0:08 ` [PATCH 4/5] PCI/sysfs: Use user config accessors for link attributes Matthew Wood
@ 2026-10-25 0:08 ` Matthew Wood
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Wood @ 2026-10-25 0:08 UTC (permalink / raw)
To: Bjorn Helgaas; +Cc: linux-pci, linux-kernel, kbusch, Nikita Vetoshkin
serial_number_show() reads the device serial number with pci_get_dsn(),
which uses the kernel config accessors which don't check
block_cfg_access, so a read that races with a reset goes to the device
mid-reset and can fail with -EIO or report an all-ones serial number.
Do the capability walk and DSN reads with the user config accessors
instead, as the "config" attribute does, so each access waits for any
pci_cfg_access_lock() holder (e.g. pci_reset_function()) to finish.
Also wrap the reads in pci_config_pm_runtime_{get,put}() like the other
attributes that read config space.
A reset can still start between two accesses, but the next access just
waits for it to finish, and the DSN capability is unchanged by a reset.
Unlike pci_find_ext_capability(), the walk doesn't check dev->cfg_size
first. serial_number is only visible if pci_get_dsn() found the DSN
capability when the device was added, and cfg_size is final by then.
Signed-off-by: Matthew Wood <thepacketgeek@gmail.com>
Tested-by: Nikita Vetoshkin <nekto0n@meta.com>
---
drivers/pci/pci-sysfs.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index a272f8583a8f..cbdd56b92d1a 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -696,10 +696,23 @@ static ssize_t serial_number_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct pci_dev *pci_dev = to_pci_dev(dev);
- u64 dsn;
+ u32 dword;
+ u64 dsn = 0;
u8 bytes[8];
+ int pos;
+
+ pci_config_pm_runtime_get(pci_dev);
+ pos = PCI_FIND_NEXT_EXT_CAP(pci_user_read_config, 0,
+ PCI_EXT_CAP_ID_DSN, NULL, pci_dev);
+ if (pos) {
+ pos += 4;
+ pci_user_read_config_dword(pci_dev, pos, &dword);
+ dsn = (u64)dword;
+ pci_user_read_config_dword(pci_dev, pos + 4, &dword);
+ dsn |= ((u64)dword) << 32;
+ }
+ pci_config_pm_runtime_put(pci_dev);
- dsn = pci_get_dsn(pci_dev);
if (!dsn)
return -EIO;
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 6+ messages in thread