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 Tested-by: Nikita Vetoshkin --- 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