diff options
author | Rafael J. Wysocki <rjw@sisk.pl> | 2012-05-17 18:39:35 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-05-17 19:16:16 -0400 |
commit | 5c7dd710f691d1b44c39e32d2f05b4286ff51f99 (patch) | |
tree | 7c319291d09980496d2b01dcbb6d9138db9477be | |
parent | eea036475df8995e5b87cd6b8c8e682e071159cd (diff) |
ACPI / PCI / PM: Fix device PM regression related to D3hot/D3cold
Commit 1cc0c998fdf2 ("ACPI: Fix D3hot v D3cold confusion") introduced a
bug in __acpi_bus_set_power() and changed the behavior of
acpi_pci_set_power_state() in such a way that it generally doesn't work
as expected if PCI_D3hot is passed to it as the second argument.
First off, if ACPI_STATE_D3 (equal to ACPI_STATE_D3_COLD) is passed to
__acpi_bus_set_power() and the explicit_set flag is set for the D3cold
state, the function will try to execute AML method called "_PS4", which
doesn't exist.
Fix this by adding a check to ensure that the name of the AML method
to execute for transitions to ACPI_STATE_D3_COLD is correct in
__acpi_bus_set_power(). Also make sure that the explicit_set flag
for ACPI_STATE_D3_COLD will be set if _PS3 is present and modify
acpi_power_transition() to avoid accessing power resources for
ACPI_STATE_D3_COLD, because they don't exist.
Second, if PCI_D3hot is passed to acpi_pci_set_power_state() as the
target state, the function will request a transition to
ACPI_STATE_D3_HOT instead of ACPI_STATE_D3. However,
ACPI_STATE_D3_HOT is now only marked as supported if the _PR3 AML
method is defined for the given device, which is rare. This causes
problems to happen on systems where devices were successfully put
into ACPI D3 by pci_set_power_state(PCI_D3hot) which doesn't work
now. In particular, some unused graphics adapters are not turned
off as a result.
To fix this issue restore the old behavior of
acpi_pci_set_power_state(), which is to request a transition to
ACPI_STATE_D3 (equal to ACPI_STATE_D3_COLD) if either PCI_D3hot or
PCI_D3cold is passed to it as the argument.
This approach is not ideal, because generally power should not
be removed from devices if PCI_D3hot is the target power state,
but since this behavior is relied on, we have no choice but to
restore it at the moment and spend more time on designing a
better solution in the future.
References: https://bugzilla.kernel.org/show_bug.cgi?id=43228
Reported-by: rocko <rockorequin@hotmail.com>
Reported-by: Cristian RodrÃguez <crrodriguez@opensuse.org>
Reported-and-tested-by: Peter <lekensteyn@gmail.com>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/acpi/bus.c | 4 | ||||
-rw-r--r-- | drivers/acpi/power.c | 9 | ||||
-rw-r--r-- | drivers/acpi/scan.c | 4 | ||||
-rw-r--r-- | drivers/pci/pci-acpi.c | 2 |
4 files changed, 15 insertions, 4 deletions
diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 3263b68cdfa3..3188da3df8da 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c | |||
@@ -250,6 +250,10 @@ static int __acpi_bus_set_power(struct acpi_device *device, int state) | |||
250 | return -ENODEV; | 250 | return -ENODEV; |
251 | } | 251 | } |
252 | 252 | ||
253 | /* For D3cold we should execute _PS3, not _PS4. */ | ||
254 | if (state == ACPI_STATE_D3_COLD) | ||
255 | object_name[3] = '3'; | ||
256 | |||
253 | /* | 257 | /* |
254 | * Transition Power | 258 | * Transition Power |
255 | * ---------------- | 259 | * ---------------- |
diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 330bb4d75852..0500f719f63e 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c | |||
@@ -660,7 +660,7 @@ int acpi_power_on_resources(struct acpi_device *device, int state) | |||
660 | 660 | ||
661 | int acpi_power_transition(struct acpi_device *device, int state) | 661 | int acpi_power_transition(struct acpi_device *device, int state) |
662 | { | 662 | { |
663 | int result; | 663 | int result = 0; |
664 | 664 | ||
665 | if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3_COLD)) | 665 | if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3_COLD)) |
666 | return -EINVAL; | 666 | return -EINVAL; |
@@ -679,8 +679,11 @@ int acpi_power_transition(struct acpi_device *device, int state) | |||
679 | * (e.g. so the device doesn't lose power while transitioning). Then, | 679 | * (e.g. so the device doesn't lose power while transitioning). Then, |
680 | * we dereference all power resources used in the current list. | 680 | * we dereference all power resources used in the current list. |
681 | */ | 681 | */ |
682 | result = acpi_power_on_list(&device->power.states[state].resources); | 682 | if (state < ACPI_STATE_D3_COLD) |
683 | if (!result) | 683 | result = acpi_power_on_list( |
684 | &device->power.states[state].resources); | ||
685 | |||
686 | if (!result && device->power.state < ACPI_STATE_D3_COLD) | ||
684 | acpi_power_off_list( | 687 | acpi_power_off_list( |
685 | &device->power.states[device->power.state].resources); | 688 | &device->power.states[device->power.state].resources); |
686 | 689 | ||
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 7417267e88fa..85cbfdccc97c 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c | |||
@@ -908,6 +908,10 @@ static int acpi_bus_get_power_flags(struct acpi_device *device) | |||
908 | device->power.states[ACPI_STATE_D3].flags.valid = 1; | 908 | device->power.states[ACPI_STATE_D3].flags.valid = 1; |
909 | device->power.states[ACPI_STATE_D3].power = 0; | 909 | device->power.states[ACPI_STATE_D3].power = 0; |
910 | 910 | ||
911 | /* Set D3cold's explicit_set flag if _PS3 exists. */ | ||
912 | if (device->power.states[ACPI_STATE_D3_HOT].flags.explicit_set) | ||
913 | device->power.states[ACPI_STATE_D3_COLD].flags.explicit_set = 1; | ||
914 | |||
911 | acpi_bus_init_power(device); | 915 | acpi_bus_init_power(device); |
912 | 916 | ||
913 | return 0; | 917 | return 0; |
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 1929c0c63b75..61e2fefeedab 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c | |||
@@ -223,7 +223,7 @@ static int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state) | |||
223 | [PCI_D0] = ACPI_STATE_D0, | 223 | [PCI_D0] = ACPI_STATE_D0, |
224 | [PCI_D1] = ACPI_STATE_D1, | 224 | [PCI_D1] = ACPI_STATE_D1, |
225 | [PCI_D2] = ACPI_STATE_D2, | 225 | [PCI_D2] = ACPI_STATE_D2, |
226 | [PCI_D3hot] = ACPI_STATE_D3_HOT, | 226 | [PCI_D3hot] = ACPI_STATE_D3, |
227 | [PCI_D3cold] = ACPI_STATE_D3 | 227 | [PCI_D3cold] = ACPI_STATE_D3 |
228 | }; | 228 | }; |
229 | int error = -EINVAL; | 229 | int error = -EINVAL; |