Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

PCI/ACPI: Allow D3 only if Root Port can signal and wake from D3

acpi_pci_bridge_d3(dev) returns "true" if "dev" is a hotplug bridge that
can handle hotplug events while in D3. Previously this meant either:

- "dev" has a _PS0 or _PR0 method (acpi_pci_power_manageable()), or

- The Root Port above "dev" has a _DSD with a "HotPlugSupportInD3"
property with value 1.

This did not consider _PRW, which tells us about wakeup GPEs (ACPI v6.4,
sec 7.3.13). Without a wakeup GPE, from an ACPI perspective the Root Port
has no way of generating wakeup signals, so hotplug events will be lost if
we use D3.

Similarly, it did not consider _S0W, which tells us the deepest D-state
from which a device can wake itself (sec 7.3.20). If _S0W tells us the
device cannot wake from D3, hotplug events will again be lost if we use D3.

Some platforms, e.g., AMD Yellow Carp, supply "HotPlugSupportInD3" without
_PRW or with an _S0W that says the Root Port cannot wake from D3. On those
platforms, we previously put bridges in D3hot, hotplug events were lost,
and hotplugged devices would not be recognized without manually rescanning.

Allow bridges to be put in D3 only if the Root Port can generate wakeup
GPEs (wakeup.flags.valid), it can wake from D3 (_S0W), AND it has the
"HotPlugSupportInD3" property.

Neither Windows 10 nor Windows 11 puts the bridge in D3 when the firmware
is configured this way, and this change aligns the handling of the
situation to be the same.

[bhelgaas: commit log, tidy "HotPlugSupportInD3" check and comment]
Link: https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/07_Power_and_Performance_Mgmt/device-power-management-objects.html?highlight=s0w#s0w-s0-device-wake-state
Link: https://docs.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports#identifying-pcie-root-ports-supporting-hot-plug-in-d3
Link: https://lore.kernel.org/r/20220401034003.3166-1-mario.limonciello@amd.com
Fixes: 26ad34d510a87 ("PCI / ACPI: Whitelist D3 for more PCIe hotplug ports")
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

authored by

Mario Limonciello and committed by
Bjorn Helgaas
dff61390 31231092

+30 -11
+30 -11
drivers/pci/pci-acpi.c
··· 974 974 975 975 bool acpi_pci_bridge_d3(struct pci_dev *dev) 976 976 { 977 - const union acpi_object *obj; 978 - struct acpi_device *adev; 979 977 struct pci_dev *rpdev; 978 + struct acpi_device *adev; 979 + acpi_status status; 980 + unsigned long long state; 981 + const union acpi_object *obj; 980 982 981 983 if (acpi_pci_disabled || !dev->is_hotplug_bridge) 982 984 return false; ··· 987 985 if (acpi_pci_power_manageable(dev)) 988 986 return true; 989 987 990 - /* 991 - * The ACPI firmware will provide the device-specific properties through 992 - * _DSD configuration object. Look for the 'HotPlugSupportInD3' property 993 - * for the root port and if it is set we know the hierarchy behind it 994 - * supports D3 just fine. 995 - */ 996 988 rpdev = pcie_find_root_port(dev); 997 989 if (!rpdev) 998 990 return false; ··· 995 999 if (!adev) 996 1000 return false; 997 1001 998 - if (acpi_dev_get_property(adev, "HotPlugSupportInD3", 999 - ACPI_TYPE_INTEGER, &obj) < 0) 1002 + /* 1003 + * If the Root Port cannot signal wakeup signals at all, i.e., it 1004 + * doesn't supply a wakeup GPE via _PRW, it cannot signal hotplug 1005 + * events from low-power states including D3hot and D3cold. 1006 + */ 1007 + if (!adev->wakeup.flags.valid) 1000 1008 return false; 1001 1009 1002 - return obj->integer.value == 1; 1010 + /* 1011 + * If the Root Port cannot wake itself from D3hot or D3cold, we 1012 + * can't use D3. 1013 + */ 1014 + status = acpi_evaluate_integer(adev->handle, "_S0W", NULL, &state); 1015 + if (ACPI_SUCCESS(status) && state < ACPI_STATE_D3_HOT) 1016 + return false; 1017 + 1018 + /* 1019 + * The "HotPlugSupportInD3" property in a Root Port _DSD indicates 1020 + * the Port can signal hotplug events while in D3. We assume any 1021 + * bridges *below* that Root Port can also signal hotplug events 1022 + * while in D3. 1023 + */ 1024 + if (!acpi_dev_get_property(adev, "HotPlugSupportInD3", 1025 + ACPI_TYPE_INTEGER, &obj) && 1026 + obj->integer.value == 1) 1027 + return true; 1028 + 1029 + return false; 1003 1030 } 1004 1031 1005 1032 int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state)