On Sun, Jul 13, 2025 at 4:47 PM Lukas Wunner <lukas@xxxxxxxxx> wrote: > > pci_bridge_d3_possible() is called from both pcie_portdrv_probe() and > pcie_portdrv_remove() to determine whether runtime power management shall > be enabled (on probe) or disabled (on remove) on a PCIe port. > > The underlying assumption is that pci_bridge_d3_possible() always returns > the same value, else a runtime PM reference imbalance would occur. That > assumption is not given if the PCIe port is inaccessible on remove due to > hot-unplug: pci_bridge_d3_possible() calls pciehp_is_native(), which > accesses Config Space to determine whether the port is Hot-Plug Capable. > An inaccessible port returns "all ones", which is converted to "all > zeroes" by pcie_capability_read_dword(). Hence the port no longer seems > Hot-Plug Capable on remove even though it was on probe. > > The resulting runtime PM ref imbalance causes warning messages such as: > > pcieport 0000:02:04.0: Runtime PM usage count underflow! > > Avoid the Config Space access (and thus the runtime PM ref imbalance) by > caching the Hot-Plug Capable bit in struct pci_dev. > > The struct already contains an "is_hotplug_bridge" flag, which however is > not only set on Hot-Plug Capable PCIe ports, but also Conventional PCI > Hot-Plug bridges and ACPI slots. The flag identifies bridges which are > allocated additional MMIO and bus number resources to allow for hierarchy > expansion. > > The kernel is somewhat sloppily using "is_hotplug_bridge" in a number of > places to identify Hot-Plug Capable PCIe ports, even though the flag > encompasses other devices. Subsequent commits replace these occurrences > with the new flag to clearly delineate Hot-Plug Capable PCIe ports from > other kinds of hotplug bridges. > > Document the existing "is_hotplug_bridge" and the new "is_pciehp" flag > and document the (non-obvious) requirement that pci_bridge_d3_possible() > always returns the same value across the entire lifetime of a bridge, > including its hot-removal. > > Fixes: 5352a44a561d ("PCI: pciehp: Make pciehp_is_native() stricter") > Reported-by: Laurent Bigonville <bigon@xxxxxxxx> > Closes: https://bugzilla.kernel.org/show_bug.cgi?id=220216 > Reported-by: Mario Limonciello <mario.limonciello@xxxxxxx> > Closes: https://lore.kernel.org/r/20250609020223.269407-3-superm1@xxxxxxxxxx/ > Link: https://lore.kernel.org/all/20250620025535.3425049-3-superm1@xxxxxxxxxx/T/#u > Signed-off-by: Lukas Wunner <lukas@xxxxxxxxx> > Cc: stable@xxxxxxxxxxxxxxx # v4.18+ Acked-by: Rafael J. Wysocki <rafael@xxxxxxxxxx> > --- > drivers/pci/pci-acpi.c | 4 +--- > drivers/pci/pci.c | 6 +++++- > drivers/pci/probe.c | 2 +- > include/linux/pci.h | 6 ++++++ > 4 files changed, 13 insertions(+), 5 deletions(-) > > diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c > index b78e0e417324..efe478e5073e 100644 > --- a/drivers/pci/pci-acpi.c > +++ b/drivers/pci/pci-acpi.c > @@ -816,13 +816,11 @@ int pci_acpi_program_hp_params(struct pci_dev *dev) > bool pciehp_is_native(struct pci_dev *bridge) > { > const struct pci_host_bridge *host; > - u32 slot_cap; > > if (!IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) > return false; > > - pcie_capability_read_dword(bridge, PCI_EXP_SLTCAP, &slot_cap); > - if (!(slot_cap & PCI_EXP_SLTCAP_HPC)) > + if (!bridge->is_pciehp) > return false; > > if (pcie_ports_native) > diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c > index e9448d55113b..23d8fe98ddf9 100644 > --- a/drivers/pci/pci.c > +++ b/drivers/pci/pci.c > @@ -3030,8 +3030,12 @@ static const struct dmi_system_id bridge_d3_blacklist[] = { > * pci_bridge_d3_possible - Is it possible to put the bridge into D3 > * @bridge: Bridge to check > * > - * This function checks if it is possible to move the bridge to D3. > * Currently we only allow D3 for some PCIe ports and for Thunderbolt. > + * > + * Return: Whether it is possible to move the bridge to D3. > + * > + * The return value is guaranteed to be constant across the entire lifetime > + * of the bridge, including its hot-removal. > */ > bool pci_bridge_d3_possible(struct pci_dev *bridge) > { > diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c > index 4b8693ec9e4c..cf50be63bf5f 100644 > --- a/drivers/pci/probe.c > +++ b/drivers/pci/probe.c > @@ -1678,7 +1678,7 @@ void set_pcie_hotplug_bridge(struct pci_dev *pdev) > > pcie_capability_read_dword(pdev, PCI_EXP_SLTCAP, ®32); > if (reg32 & PCI_EXP_SLTCAP_HPC) > - pdev->is_hotplug_bridge = 1; > + pdev->is_hotplug_bridge = pdev->is_pciehp = 1; > } > > static void set_pcie_thunderbolt(struct pci_dev *dev) > diff --git a/include/linux/pci.h b/include/linux/pci.h > index 05e68f35f392..d56d0dd80afb 100644 > --- a/include/linux/pci.h > +++ b/include/linux/pci.h > @@ -328,6 +328,11 @@ struct rcec_ea; > * determined (e.g., for Root Complex Integrated > * Endpoints without the relevant Capability > * Registers). > + * @is_hotplug_bridge: Hotplug bridge of any kind (e.g. PCIe Hot-Plug Capable, > + * Conventional PCI Hot-Plug, ACPI slot). > + * Such bridges are allocated additional MMIO and bus > + * number resources to allow for hierarchy expansion. > + * @is_pciehp: PCIe Hot-Plug Capable bridge. > */ > struct pci_dev { > struct list_head bus_list; /* Node in per-bus list */ > @@ -451,6 +456,7 @@ struct pci_dev { > unsigned int is_physfn:1; > unsigned int is_virtfn:1; > unsigned int is_hotplug_bridge:1; > + unsigned int is_pciehp:1; > unsigned int shpc_managed:1; /* SHPC owned by shpchp */ > unsigned int is_thunderbolt:1; /* Thunderbolt controller */ > /* > -- > 2.47.2 >