Skip to contentSkip to conversation

ACPI: power: Keep track of power resource states

PATCH New Rafael J. Wysocki 1 participant 4 messages May 24, 2021 ACPI

lore
Share & tools
lore
#0 lore raw

cover ACPI: power: Keep track of power resource states

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!

#1 lore raw

PATCH 1/3 ACPI: power: Use u8 as the power resource state data type

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.

---
 drivers/acpi/power.c |   27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)
drivers/acpi/power.c +15 -12
Index: 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)
#2 lore raw

PATCH 2/3 ACPI: power: Save the last known state of each power resource

In reply to Rafael J. Wysocki

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.

---
 drivers/acpi/power.c |   50 ++++++++++++++++++++++++++++++++------------------
 1 file changed, 32 insertions(+), 18 deletions(-)
drivers/acpi/power.c +32 -18
Index: 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;
#3 lore raw

PATCH 3/3 ACPI: power: Rework turning off unused power resources

In reply to Rafael J. Wysocki

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.

---
 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(-)
drivers/acpi/power.c +11 -34
Index: 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);
 	}
drivers/acpi/internal.h +1 -1
Index: 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
drivers/acpi/scan.c +1 -1
Index: 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;
 
drivers/acpi/sleep.c +1 -1
Index: 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