From d8660b288b8c3768ca9a1772165892d8fa655537 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 31 Mar 2022 22:40:03 -0500 Subject: [PATCH] PCI/ACPI: Allow D3 only if Root Port can signal and wake from D3 BugLink: https://bugs.launchpad.net/bugs/1962349 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 Fixes: 26ad34d510a87 ("PCI / ACPI: Whitelist D3 for more PCIe hotplug ports") Link: https://lore.kernel.org/r/20220401034003.3166-1-mario.limonciello@amd.com Signed-off-by: Mario Limonciello Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki (backported from https://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci.git/commit/?h=pci/hotplug&id=7e3bd4a02c9dc9eecd6cb8d41750268bccff4a77) Signed-off-by: Timo Aaltonen --- drivers/pci/pci-acpi.c | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 36bc23e217592..38e9b58a2d41c 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -936,10 +936,11 @@ static struct acpi_device *acpi_pci_find_companion(struct device *dev); static bool acpi_pci_bridge_d3(struct pci_dev *dev) { - const struct fwnode_handle *fwnode; struct acpi_device *adev; struct pci_dev *root; - u8 val; + acpi_status status; + unsigned long long state; + const union acpi_object *obj; if (!dev->is_hotplug_bridge) return false; @@ -975,11 +976,34 @@ static bool acpi_pci_bridge_d3(struct pci_dev *dev) if (!adev) return false; - fwnode = acpi_fwnode_handle(adev); - if (fwnode_property_read_u8(fwnode, "HotPlugSupportInD3", &val)) + /* + * If the Root Port cannot signal wakeup signals at all, i.e., it + * doesn't supply a wakeup GPE via _PRW, it cannot signal hotplug + * events from low-power states including D3hot and D3cold. + */ + if (!adev->wakeup.flags.valid) + return false; + + /* + * If the Root Port cannot wake itself from D3hot or D3cold, we + * can't use D3. + */ + status = acpi_evaluate_integer(adev->handle, "_S0W", NULL, &state); + if (ACPI_SUCCESS(status) && state < ACPI_STATE_D3_HOT) return false; - return val == 1; + /* + * The "HotPlugSupportInD3" property in a Root Port _DSD indicates + * the Port can signal hotplug events while in D3. We assume any + * bridges *below* that Root Port can also signal hotplug events + * while in D3. + */ + if (!acpi_dev_get_property(adev, "HotPlugSupportInD3", + ACPI_TYPE_INTEGER, &obj) && + obj->integer.value == 1) + return true; + + return false; } static bool acpi_pci_power_manageable(struct pci_dev *dev)