Merge branch 'pci/pm'

- Account for _S0W when deciding whether to put bridges in D3 to avoid
  missing hotplug events (Rafael J. Wysocki)

* pci/pm:
  PCI/ACPI: Account for _S0W of the target bridge in acpi_pci_bridge_d3()
This commit is contained in:
Bjorn Helgaas 2023-02-22 13:47:26 -06:00
commit 08a67024a0
3 changed files with 51 additions and 14 deletions

View file

@ -484,6 +484,25 @@ void acpi_dev_power_up_children_with_adr(struct acpi_device *adev)
acpi_dev_for_each_child(adev, acpi_power_up_if_adr_present, NULL);
}
/**
* acpi_dev_power_state_for_wake - Deepest power state for wakeup signaling
* @adev: ACPI companion of the target device.
*
* Evaluate _S0W for @adev and return the value produced by it or return
* ACPI_STATE_UNKNOWN on errors (including _S0W not present).
*/
u8 acpi_dev_power_state_for_wake(struct acpi_device *adev)
{
unsigned long long state;
acpi_status status;
status = acpi_evaluate_integer(adev->handle, "_S0W", NULL, &state);
if (ACPI_FAILURE(status))
return ACPI_STATE_UNKNOWN;
return state;
}
#ifdef CONFIG_PM
static DEFINE_MUTEX(acpi_pm_notifier_lock);
static DEFINE_MUTEX(acpi_pm_notifier_install_lock);

View file

@ -976,24 +976,41 @@ bool acpi_pci_power_manageable(struct pci_dev *dev)
bool acpi_pci_bridge_d3(struct pci_dev *dev)
{
struct pci_dev *rpdev;
struct acpi_device *adev;
acpi_status status;
unsigned long long state;
struct acpi_device *adev, *rpadev;
const union acpi_object *obj;
if (acpi_pci_disabled || !dev->is_hotplug_bridge)
return false;
/* Assume D3 support if the bridge is power-manageable by ACPI. */
if (acpi_pci_power_manageable(dev))
return true;
adev = ACPI_COMPANION(&dev->dev);
if (adev) {
/*
* If the bridge has _S0W, whether or not it can go into D3
* depends on what is returned by that object. In particular,
* if the power state returned by _S0W is D2 or shallower,
* entering D3 should not be allowed.
*/
if (acpi_dev_power_state_for_wake(adev) <= ACPI_STATE_D2)
return false;
/*
* Otherwise, assume that the bridge can enter D3 so long as it
* is power-manageable via ACPI.
*/
if (acpi_device_power_manageable(adev))
return true;
}
rpdev = pcie_find_root_port(dev);
if (!rpdev)
return false;
adev = ACPI_COMPANION(&rpdev->dev);
if (!adev)
if (rpdev == dev)
rpadev = adev;
else
rpadev = ACPI_COMPANION(&rpdev->dev);
if (!rpadev)
return false;
/*
@ -1001,15 +1018,15 @@ bool acpi_pci_bridge_d3(struct pci_dev *dev)
* 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)
if (!rpadev->wakeup.flags.valid)
return false;
/*
* If the Root Port cannot wake itself from D3hot or D3cold, we
* can't use D3.
* In the bridge-below-a-Root-Port case, evaluate _S0W for the Root Port
* to verify whether or not it can signal wakeup from D3.
*/
status = acpi_evaluate_integer(adev->handle, "_S0W", NULL, &state);
if (ACPI_SUCCESS(status) && state < ACPI_STATE_D3_HOT)
if (rpadev != adev &&
acpi_dev_power_state_for_wake(rpadev) <= ACPI_STATE_D2)
return false;
/*
@ -1018,7 +1035,7 @@ bool acpi_pci_bridge_d3(struct pci_dev *dev)
* bridges *below* that Root Port can also signal hotplug events
* while in D3.
*/
if (!acpi_dev_get_property(adev, "HotPlugSupportInD3",
if (!acpi_dev_get_property(rpadev, "HotPlugSupportInD3",
ACPI_TYPE_INTEGER, &obj) &&
obj->integer.value == 1)
return true;

View file

@ -533,6 +533,7 @@ int acpi_bus_update_power(acpi_handle handle, int *state_p);
int acpi_device_update_power(struct acpi_device *device, int *state_p);
bool acpi_bus_power_manageable(acpi_handle handle);
void acpi_dev_power_up_children_with_adr(struct acpi_device *adev);
u8 acpi_dev_power_state_for_wake(struct acpi_device *adev);
int acpi_device_power_add_dependent(struct acpi_device *adev,
struct device *dev);
void acpi_device_power_remove_dependent(struct acpi_device *adev,