[PATCH] PCI: Put power resources not tied to a physical node in D3cold
From: Mario Limonciello
Date: Thu Oct 07 2021 - 16:51:54 EST
I found a case that a system that two physical SATA controllers share
the same ACPI Power Resource. When a drive is connected to one of
the controllers then it will bind with PCI devices with the ahci driver
and form a relationship with the firmware node and physical node. During
s2idle I see that the constraints are met for this device as it is
transitioned into the appropriate state. However the second ACPI node
doesn't have any relationship with a physical node and stays in "D0":
```
ACPI: \_SB_.PCI0.GP18.SATA: ACPI: PM: Power state change: D0 -> D3cold
ACPI: PM: Power resource [P0SA] still in use
acpi device:2a: Power state changed to D3cold
```
Due to the refcounting used on the shared power resource putting the
device with a physical node into D3 doesn't result in the _OFF method
being called.
To help with this type of problem, make a new helper function that can
be used to check all the children of an ACPI device and put any firmware
nodes that don't have physical devices into D3cold to allow shared
resources to transition. Call this helper function after PCI devices have
been scanned and ACPI companions have had a chance to associate.
After making this change, here is what the flow looks like:
```
<snip:bootup>
ACPI: \_SB_.PCI0.GP18.SAT1: ACPI: PM: Power state change: D0 -> D3cold
ACPI: PM: Power resource [P0SA] still in use
acpi device:2c: Power state changed to D3cold
<snip:suspend>
ACPI: \_SB_.PCI0.GP18.SATA: ACPI: PM: Power state change: D0 -> D3cold
ACPI: PM: Power resource [P0SA] turned off
acpi device:2a: Power state changed to D3cold
```
Link: https://lore.kernel.org/linux-acpi/0571292a-286b-18f2-70ad-12b125a61469@xxxxxxx/T/#m042055c5ca1e49c2829655511f04b0311c142559
BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=214091
Signed-off-by: Mario Limonciello <mario.limonciello@xxxxxxx>
---
drivers/acpi/device_pm.c | 34 ++++++++++++++++++++++++++++++++++
drivers/pci/probe.c | 5 +++++
include/acpi/acpi_bus.h | 1 +
3 files changed, 40 insertions(+)
diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c
index 0028b6b51c87..0fb0bbeeae9e 100644
--- a/drivers/acpi/device_pm.c
+++ b/drivers/acpi/device_pm.c
@@ -149,6 +149,40 @@ static int acpi_dev_pm_explicit_set(struct acpi_device *adev, int state)
return 0;
}
+/**
+ * acpi_device_turn_off_absent_children - Turn off power resources for
+ * children not physically present.
+ * @parent: ACPI bridge device
+ */
+int acpi_device_turn_off_absent_children(struct acpi_device *parent)
+{
+ struct acpi_device *adev;
+ int ret = 0;
+
+ if (!parent)
+ return -EINVAL;
+
+ list_for_each_entry(adev, &parent->children, node) {
+ int state;
+
+ if (!adev->flags.power_manageable ||
+ !adev->power.flags.power_resources)
+ continue;
+ if (acpi_get_first_physical_node(adev))
+ continue;
+ ret = acpi_device_get_power(adev, &state);
+ if (ret)
+ return ret;
+ if (state == ACPI_STATE_D3_COLD)
+ continue;
+ ret = acpi_device_set_power(adev, ACPI_STATE_D3_COLD);
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(acpi_device_turn_off_absent_children);
+
/**
* acpi_device_set_power - Set power state of an ACPI device.
* @device: Device to set the power state of.
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 79177ac37880..1a45182394d1 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -2939,6 +2939,11 @@ static unsigned int pci_scan_child_bus_extend(struct pci_bus *bus,
}
}
+ /* check for and turn off dangling power resources */
+ for_each_pci_bridge(dev, bus) {
+ acpi_device_turn_off_absent_children(ACPI_COMPANION(&dev->dev));
+ }
+
/*
* We've scanned the bus and so we know all about what's on
* the other side of any bridges that may be on this bus plus
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 13d93371790e..0eba08b60e13 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -510,6 +510,7 @@ int acpi_bus_get_status(struct acpi_device *device);
int acpi_bus_set_power(acpi_handle handle, int state);
const char *acpi_power_state_string(int state);
+int acpi_device_turn_off_absent_children(struct acpi_device *parent);
int acpi_device_set_power(struct acpi_device *device, int state);
int acpi_bus_init_power(struct acpi_device *device);
int acpi_device_fix_up_power(struct acpi_device *device);
--
2.25.1