Hi All,
This series changes the handling of ACPI power resources so as to
track the state of each power resource in the kernel in addition to
using reference counting instead of relying on the values returned by
the _STA objects of power resources.
The underlying issue is that on some systems the _STA always returns
the same value for certain power resources even after changing their
state with _ON or _OFF, so it is not reliable in general.
Patch [1/3] changes the data type used for representing the state of
an ACPI power resources to u8 (cosmetics).
Patch [2/3] introduces the power resource state tracking (refer to the
changelog for details).
Patch [3/3] simplifies turning off the unused power resources with the
help of the state tracking mechanism (refer to the changelog for
details).
The series is not top of the patch at
https://patchwork.kernel.org/project/linux-acpi/patch/11762320.O9o76ZdvQC@kreacher/
which is going to be pushed as a fix for 5.13-rc.
Thanks!
From: Rafael J. Wysocki <[email protected]>
Currently, there are two ways to check the state of an ACPI power
resource and they may not be consistent with each other. The first
one is to evaluate the power resource's _STA object and the other one
is to check its reference counter value. However, on some systems
the value returned by _STA may not be consistent with the value of
the power resource's reference counter (for example, on some systems
it returns the same value every time for certain power resources).
Moreover, evaluating _STA is unnecessary overhead for a power
resource for which it has been evaluated already or whose state is
otherwise known, because either the _ON or the _OFF method has been
executed for it.
For this reason, save the state of each power resource in its
struct acpi_power_resource object and use the saved value whenever
its state needs to be checked, except when its stats is unknown, in
which case the _STA method is evaluated for it and the value
returned by that method is saved as the last known state of
the power resource.
Moreover, drop the power resource _STA method evaluation from
acpi_add_power_resource(), so as to avoid doing that unnecessarily
for power resources that will never be used.
Tested-by: Dave Olsthoorn <[email protected]>
Tested-by: Shujun Wang <[email protected]>
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/acpi/power.c | 50 ++++++++++++++++++++++++++++++++------------------
1 file changed, 32 insertions(+), 18 deletions(-)
Index: linux-pm/drivers/acpi/power.c
===================================================================
--- linux-pm.orig/drivers/acpi/power.c
+++ linux-pm/drivers/acpi/power.c
@@ -52,6 +52,7 @@ struct acpi_power_resource {
u32 order;
unsigned int ref_count;
unsigned int users;
+ u8 state;
bool wakeup_enabled;
struct mutex resource_lock;
struct list_head dependents;
@@ -177,15 +178,12 @@ int acpi_extract_power_resources(union a
return err;
}
-static int acpi_power_get_state(acpi_handle handle, u8 *state)
+static int __get_state(acpi_handle handle, u8 *state)
{
acpi_status status = AE_OK;
unsigned long long sta = 0;
u8 cur_state;
- if (!handle || !state)
- return -EINVAL;
-
status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
if (ACPI_FAILURE(status))
return -ENODEV;
@@ -199,6 +197,20 @@ static int acpi_power_get_state(acpi_han
return 0;
}
+static int acpi_power_get_state(struct acpi_power_resource *resource, u8 *state)
+{
+ if (resource->state == ACPI_POWER_RESOURCE_STATE_UNKNOWN) {
+ int ret;
+
+ ret = __get_state(resource->device.handle, &resource->state);
+ if (ret)
+ return ret;
+ }
+
+ *state = resource->state;
+ return 0;
+}
+
static int acpi_power_get_list_state(struct list_head *list, u8 *state)
{
struct acpi_power_resource_entry *entry;
@@ -210,11 +222,10 @@ static int acpi_power_get_list_state(str
/* The state of the list is 'on' IFF all resources are 'on'. */
list_for_each_entry(entry, list, node) {
struct acpi_power_resource *resource = entry->resource;
- acpi_handle handle = resource->device.handle;
int result;
mutex_lock(&resource->resource_lock);
- result = acpi_power_get_state(handle, &cur_state);
+ result = acpi_power_get_state(resource, &cur_state);
mutex_unlock(&resource->resource_lock);
if (result)
return result;
@@ -347,8 +358,12 @@ static int __acpi_power_on(struct acpi_p
acpi_status status = AE_OK;
status = acpi_evaluate_object(resource->device.handle, "_ON", NULL, NULL);
- if (ACPI_FAILURE(status))
+ if (ACPI_FAILURE(status)) {
+ resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN;
return -ENODEV;
+ }
+
+ resource->state = ACPI_POWER_RESOURCE_STATE_ON;
pr_debug("Power resource [%s] turned on\n", resource->name);
@@ -400,8 +415,12 @@ static int __acpi_power_off(struct acpi_
status = acpi_evaluate_object(resource->device.handle, "_OFF",
NULL, NULL);
- if (ACPI_FAILURE(status))
+ if (ACPI_FAILURE(status)) {
+ resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN;
return -ENODEV;
+ }
+
+ resource->state = ACPI_POWER_RESOURCE_STATE_OFF;
pr_debug("Power resource [%s] turned off\n", resource->name);
@@ -585,13 +604,12 @@ int acpi_power_wakeup_list_init(struct l
list_for_each_entry(entry, list, node) {
struct acpi_power_resource *resource = entry->resource;
- acpi_handle handle = resource->device.handle;
int result;
u8 state;
mutex_lock(&resource->resource_lock);
- result = acpi_power_get_state(handle, &state);
+ result = acpi_power_get_state(resource, &state);
if (result) {
mutex_unlock(&resource->resource_lock);
return result;
@@ -915,7 +933,6 @@ int acpi_add_power_resource(acpi_handle
struct acpi_buffer buffer = { sizeof(acpi_object), &acpi_object };
acpi_status status;
int result;
- u8 state;
acpi_bus_get_device(handle, &device);
if (device)
@@ -942,13 +959,9 @@ int acpi_add_power_resource(acpi_handle
resource->system_level = acpi_object.power_resource.system_level;
resource->order = acpi_object.power_resource.resource_order;
+ resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN;
- result = acpi_power_get_state(handle, &state);
- if (result)
- goto err;
-
- pr_info("%s [%s] (%s)\n", acpi_device_name(device),
- acpi_device_bid(device), state ? "on" : "off");
+ pr_info("%s [%s]\n", acpi_device_name(device), acpi_device_bid(device));
device->flags.match_driver = true;
result = acpi_device_add(device, acpi_release_power_resource);
@@ -980,7 +993,8 @@ void acpi_resume_power_resources(void)
mutex_lock(&resource->resource_lock);
- result = acpi_power_get_state(resource->device.handle, &state);
+ resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN;
+ result = acpi_power_get_state(resource, &state);
if (result) {
mutex_unlock(&resource->resource_lock);
continue;
From: Rafael J. Wysocki <[email protected]>
Use u8 as the data type for representing the state of an ACPI
power resource.
It is s not necessary to use int for that and because subsequent
changes are going to use ACPI_POWER_RESOURCE_STATE_UNKNOWN, it is
better to adjust the data type so that the "unknown" state is
represented by the "all ones" value.
While at it, clean up acpi_power_get_state() somewhat.
No intentional functional impact.
Tested-by: Dave Olsthoorn <[email protected]>
Tested-by: Shujun Wang <[email protected]>
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/acpi/power.c | 27 +++++++++++++++------------
1 file changed, 15 insertions(+), 12 deletions(-)
Index: linux-pm/drivers/acpi/power.c
===================================================================
--- linux-pm.orig/drivers/acpi/power.c
+++ linux-pm/drivers/acpi/power.c
@@ -182,10 +182,11 @@ int acpi_extract_power_resources(union a
return err;
}
-static int acpi_power_get_state(acpi_handle handle, int *state)
+static int acpi_power_get_state(acpi_handle handle, u8 *state)
{
acpi_status status = AE_OK;
unsigned long long sta = 0;
+ u8 cur_state;
if (!handle || !state)
return -EINVAL;
@@ -194,25 +195,24 @@ static int acpi_power_get_state(acpi_han
if (ACPI_FAILURE(status))
return -ENODEV;
- *state = (sta & 0x01)?ACPI_POWER_RESOURCE_STATE_ON:
- ACPI_POWER_RESOURCE_STATE_OFF;
+ cur_state = sta & ACPI_POWER_RESOURCE_STATE_ON;
acpi_handle_debug(handle, "Power resource is %s\n",
- *state ? "on" : "off");
+ cur_state ? "on" : "off");
+ *state = cur_state;
return 0;
}
-static int acpi_power_get_list_state(struct list_head *list, int *state)
+static int acpi_power_get_list_state(struct list_head *list, u8 *state)
{
struct acpi_power_resource_entry *entry;
- int cur_state;
+ u8 cur_state = ACPI_POWER_RESOURCE_STATE_OFF;
if (!list || !state)
return -EINVAL;
/* The state of the list is 'on' IFF all resources are 'on'. */
- cur_state = 0;
list_for_each_entry(entry, list, node) {
struct acpi_power_resource *resource = entry->resource;
acpi_handle handle = resource->device.handle;
@@ -592,7 +592,7 @@ int acpi_power_wakeup_list_init(struct l
struct acpi_power_resource *resource = entry->resource;
acpi_handle handle = resource->device.handle;
int result;
- int state;
+ u8 state;
mutex_lock(&resource->resource_lock);
@@ -789,8 +789,8 @@ int acpi_disable_wakeup_device_power(str
int acpi_power_get_inferred_state(struct acpi_device *device, int *state)
{
+ u8 list_state = ACPI_POWER_RESOURCE_STATE_OFF;
int result = 0;
- int list_state = 0;
int i = 0;
if (!device || !state)
@@ -919,7 +919,8 @@ struct acpi_device *acpi_add_power_resou
union acpi_object acpi_object;
struct acpi_buffer buffer = { sizeof(acpi_object), &acpi_object };
acpi_status status;
- int state, result = -ENODEV;
+ int result;
+ u8 state;
acpi_bus_get_device(handle, &device);
if (device)
@@ -979,7 +980,8 @@ void acpi_resume_power_resources(void)
mutex_lock(&power_resource_list_lock);
list_for_each_entry(resource, &acpi_power_resource_list, list_node) {
- int result, state;
+ int result;
+ u8 state;
mutex_lock(&resource->resource_lock);
@@ -1012,7 +1014,8 @@ static void acpi_power_turn_off_if_unuse
if (resource->users > 0)
return;
} else {
- int result, state;
+ int result;
+ u8 state;
result = acpi_power_get_state(resource->device.handle, &state);
if (result || state == ACPI_POWER_RESOURCE_STATE_OFF)
From: Rafael J. Wysocki <[email protected]>
Make turning off unused power resources (after the enumeration of
devices and during system-wide resume from S3) more straightforward
by using the observation that the power resource state stored in
struct acpi_power_resource can be used to determine whether or not
the give power resource has any users.
Namely, when the state of the power resource is unknown, its _STA
method has never been evaluated (or the evaluation of it has failed)
and its _ON and _OFF methods have never been executed (or they have
failed to execute), so for all practical purposes it can be assumed
to have no users (or to be unusable). Therefore, instead of checking
the number of power resource users, it is sufficient to check if its
state is known.
Moreover, if the last known state of a given power resource is "off",
it is not necessary to turn it off, because it has been used to
initialize the power state or the wakeup power resources list of at
least one device and either its _STA method has returned 0 ("off"),
or its _OFF method has been successfully executed already.
Accordingly, modify acpi_turn_off_unused_power_resources() to do the
above checks (which are suitable for both uses of it) instead of
using the number of power resource users or evaluating its _STA
method, drop its argument (which is not useful any more) and update
its callers.
Also drop the users field from struct acpi_power_resource as it is
not useful any more.
Tested-by: Dave Olsthoorn <[email protected]>
Tested-by: Shujun Wang <[email protected]>
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/acpi/internal.h | 2 +-
drivers/acpi/power.c | 45 +++++++++++----------------------------------
drivers/acpi/scan.c | 2 +-
drivers/acpi/sleep.c | 2 +-
4 files changed, 14 insertions(+), 37 deletions(-)
Index: linux-pm/drivers/acpi/power.c
===================================================================
--- linux-pm.orig/drivers/acpi/power.c
+++ linux-pm/drivers/acpi/power.c
@@ -52,7 +52,6 @@ struct acpi_power_resource {
u32 system_level;
u32 order;
unsigned int ref_count;
- unsigned int users;
u8 state;
bool wakeup_enabled;
struct mutex resource_lock;
@@ -174,8 +173,6 @@ int acpi_extract_power_resources(union a
err = acpi_power_resources_list_add(rhandle, list);
if (err)
break;
-
- to_power_resource(rdev)->users++;
}
if (err)
acpi_power_resources_list_free(list);
@@ -1018,39 +1015,10 @@ void acpi_resume_power_resources(void)
}
#endif
-static void acpi_power_turn_off_if_unused(struct acpi_power_resource *resource,
- bool init)
-{
- if (resource->ref_count > 0)
- return;
-
- if (init) {
- if (resource->users > 0)
- return;
- } else {
- int result;
- u8 state;
-
- result = acpi_power_get_state(resource->device.handle, &state);
- if (result || state == ACPI_POWER_RESOURCE_STATE_OFF)
- return;
- }
-
- dev_info(&resource->device.dev, "Turning OFF\n");
- __acpi_power_off(resource);
-}
-
/**
* acpi_turn_off_unused_power_resources - Turn off power resources not in use.
- * @init: Control switch.
- *
- * If @ainit is set, unconditionally turn off all of the ACPI power resources
- * without any users.
- *
- * Otherwise, turn off all ACPI power resources without active references (that
- * is, the ones that should be "off" at the moment) that are "on".
*/
-void acpi_turn_off_unused_power_resources(bool init)
+void acpi_turn_off_unused_power_resources(void)
{
struct acpi_power_resource *resource;
@@ -1059,7 +1027,16 @@ void acpi_turn_off_unused_power_resource
list_for_each_entry_reverse(resource, &acpi_power_resource_list, list_node) {
mutex_lock(&resource->resource_lock);
- acpi_power_turn_off_if_unused(resource, init);
+ /*
+ * Turn off power resources in an unknown state too, because the
+ * platform firmware on some system expects the OS to turn off
+ * power resources without any users unconditionally.
+ */
+ if (!resource->ref_count &&
+ resource->state != ACPI_POWER_RESOURCE_STATE_OFF) {
+ dev_info(&resource->device.dev, "Turning OFF\n");
+ __acpi_power_off(resource);
+ }
mutex_unlock(&resource->resource_lock);
}
Index: linux-pm/drivers/acpi/internal.h
===================================================================
--- linux-pm.orig/drivers/acpi/internal.h
+++ linux-pm/drivers/acpi/internal.h
@@ -142,7 +142,7 @@ int acpi_device_sleep_wake(struct acpi_d
int acpi_power_get_inferred_state(struct acpi_device *device, int *state);
int acpi_power_on_resources(struct acpi_device *device, int state);
int acpi_power_transition(struct acpi_device *device, int state);
-void acpi_turn_off_unused_power_resources(bool init);
+void acpi_turn_off_unused_power_resources(void);
/* --------------------------------------------------------------------------
Device Power Management
Index: linux-pm/drivers/acpi/scan.c
===================================================================
--- linux-pm.orig/drivers/acpi/scan.c
+++ linux-pm/drivers/acpi/scan.c
@@ -2356,7 +2356,7 @@ int __init acpi_scan_init(void)
}
}
- acpi_turn_off_unused_power_resources(true);
+ acpi_turn_off_unused_power_resources();
acpi_scan_initialized = true;
Index: linux-pm/drivers/acpi/sleep.c
===================================================================
--- linux-pm.orig/drivers/acpi/sleep.c
+++ linux-pm/drivers/acpi/sleep.c
@@ -504,7 +504,7 @@ static void acpi_pm_start(u32 acpi_state
*/
static void acpi_pm_end(void)
{
- acpi_turn_off_unused_power_resources(false);
+ acpi_turn_off_unused_power_resources();
acpi_scan_lock_release();
/*
* This is necessary in case acpi_pm_finish() is not called during a