[PATCH v2 5/5] PCI/sysfs: Use user config accessors for serial_number

From: Matthew Wood

Date: Thu Oct 29 2026 - 15:22:39 EST


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.

Add a check that both DSN reads are successful, unlike pci_get_dsn(),
and fail with -EIO instead of reporting an all-ones serial number if
the device is disconnected between the capability walk and the reads.

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@xxxxxxxxx>
Tested-by: Nikita Vetoshkin <nikita.vetoshkin@xxxxxxxxx>
---
drivers/pci/pci-sysfs.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index a272f8583a8f..c2a5f53affc1 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -696,14 +696,25 @@ 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 lo = 0, hi = 0;
u8 bytes[8];
+ int pos, ret = -EIO;

- dsn = pci_get_dsn(pci_dev);
- if (!dsn)
+ 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) {
+ ret = pci_user_read_config_dword(pci_dev, pos + 4, &lo);
+ if (!ret)
+ ret = pci_user_read_config_dword(pci_dev, pos + 8,
+ &hi);
+ }
+ pci_config_pm_runtime_put(pci_dev);
+
+ if (ret || !(lo | hi))
return -EIO;

- put_unaligned_be64(dsn, bytes);
+ put_unaligned_be64(((u64)hi << 32) | lo, bytes);
return sysfs_emit(buf, "%8phD\n", bytes);
}
static DEVICE_ATTR_ADMIN_RO(serial_number);
--
2.53.0-Meta