From: Brian Norris briannorris@google.com
max_link_width, current_link_speed, current_link_width, secondary_bus_number, and subordinate_bus_number all access config registers, but they don't check the runtime PM state. If the device is in D3cold or a parent bridge is suspended, we may see -EINVAL, bogus values, or worse, depending on implementation details.
Wrap these access in pci_config_pm_runtime_{get,put}() like most of the rest of the similar sysfs attributes.
Notably, max_link_speed does not access config registers; it returns a cached value [1]. So it needs no changes.
[1] Caching was added to pcie_get_speed_cap() in v6.13 via commit d2bd39c0456b ("PCI: Store all PCIe Supported Link Speeds").
Fixes: 56c1af4606f0 ("PCI: Add sysfs max_link_speed/width, current_link_speed/width, etc") Cc: stable@vger.kernel.org Signed-off-by: Brian Norris briannorris@google.com Signed-off-by: Brian Norris briannorris@chromium.org ---
Changes in v2: * Don't touch max_link_speed; it's cached, so we don't actually touch the hardware * Improve commit message
drivers/pci/pci-sysfs.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index f28fdf6dfa02..af74cf02bb90 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -209,8 +209,14 @@ 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;
- return sysfs_emit(buf, "%u\n", pcie_get_width_cap(pdev)); + /* 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)); + pci_config_pm_runtime_put(pdev); + + return ret; } static DEVICE_ATTR_RO(max_link_width);
@@ -222,7 +228,10 @@ static ssize_t current_link_speed_show(struct device *dev, int err; enum pci_bus_speed speed;
+ pci_config_pm_runtime_get(pci_dev); err = pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &linkstat); + pci_config_pm_runtime_put(pci_dev); + if (err) return -EINVAL;
@@ -239,7 +248,10 @@ static ssize_t current_link_width_show(struct device *dev, u16 linkstat; int err;
+ pci_config_pm_runtime_get(pci_dev); err = pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &linkstat); + pci_config_pm_runtime_put(pci_dev); + if (err) return -EINVAL;
@@ -255,7 +267,10 @@ static ssize_t secondary_bus_number_show(struct device *dev, u8 sec_bus; int err;
+ pci_config_pm_runtime_get(pci_dev); err = pci_read_config_byte(pci_dev, PCI_SECONDARY_BUS, &sec_bus); + pci_config_pm_runtime_put(pci_dev); + if (err) return -EINVAL;
@@ -271,7 +286,10 @@ static ssize_t subordinate_bus_number_show(struct device *dev, u8 sub_bus; int err;
+ pci_config_pm_runtime_get(pci_dev); err = pci_read_config_byte(pci_dev, PCI_SUBORDINATE_BUS, &sub_bus); + pci_config_pm_runtime_put(pci_dev); + if (err) return -EINVAL;
On Wed, Sep 24, 2025 at 09:57:11AM -0700, Brian Norris wrote:
From: Brian Norris briannorris@google.com
max_link_width, current_link_speed, current_link_width, secondary_bus_number, and subordinate_bus_number all access config registers, but they don't check the runtime PM state. If the device is in D3cold or a parent bridge is suspended, we may see -EINVAL, bogus values, or worse, depending on implementation details.
Wrap these access in pci_config_pm_runtime_{get,put}() like most of the rest of the similar sysfs attributes.
Notably, max_link_speed does not access config registers; it returns a cached value [1]. So it needs no changes.
[1] Caching was added to pcie_get_speed_cap() in v6.13 via commit d2bd39c0456b ("PCI: Store all PCIe Supported Link Speeds").
Fixes: 56c1af4606f0 ("PCI: Add sysfs max_link_speed/width, current_link_speed/width, etc") Cc: stable@vger.kernel.org Signed-off-by: Brian Norris briannorris@google.com Signed-off-by: Brian Norris briannorris@chromium.org
Applied to pci/pm for v 6.18, thanks!
Changes in v2:
- Don't touch max_link_speed; it's cached, so we don't actually touch the hardware
- Improve commit message
drivers/pci/pci-sysfs.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index f28fdf6dfa02..af74cf02bb90 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -209,8 +209,14 @@ 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;
- return sysfs_emit(buf, "%u\n", pcie_get_width_cap(pdev));
- /* 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));
- pci_config_pm_runtime_put(pdev);
- return ret;
} static DEVICE_ATTR_RO(max_link_width); @@ -222,7 +228,10 @@ static ssize_t current_link_speed_show(struct device *dev, int err; enum pci_bus_speed speed;
- pci_config_pm_runtime_get(pci_dev); err = pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &linkstat);
- pci_config_pm_runtime_put(pci_dev);
- if (err) return -EINVAL;
@@ -239,7 +248,10 @@ static ssize_t current_link_width_show(struct device *dev, u16 linkstat; int err;
- pci_config_pm_runtime_get(pci_dev); err = pcie_capability_read_word(pci_dev, PCI_EXP_LNKSTA, &linkstat);
- pci_config_pm_runtime_put(pci_dev);
- if (err) return -EINVAL;
@@ -255,7 +267,10 @@ static ssize_t secondary_bus_number_show(struct device *dev, u8 sec_bus; int err;
- pci_config_pm_runtime_get(pci_dev); err = pci_read_config_byte(pci_dev, PCI_SECONDARY_BUS, &sec_bus);
- pci_config_pm_runtime_put(pci_dev);
- if (err) return -EINVAL;
@@ -271,7 +286,10 @@ static ssize_t subordinate_bus_number_show(struct device *dev, u8 sub_bus; int err;
- pci_config_pm_runtime_get(pci_dev); err = pci_read_config_byte(pci_dev, PCI_SUBORDINATE_BUS, &sub_bus);
- pci_config_pm_runtime_put(pci_dev);
- if (err) return -EINVAL;
2.51.0.536.g15c5d4f767-goog
linux-stable-mirror@lists.linaro.org