diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-30 09:22:00 -0700 |
| commit | 034dd340b08be1f2f0477ad16131d609f9dbd53c (patch) | |
| tree | 2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/acpi/power.c | |
| download | linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip | |
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt:
- Fix error output of boot instance creation failure
Currently if a boot instance creation fails, instead of printing out
the name of the instance that failed, it prints "(null)". That is
because it prints "cur_str" that had already been processed by
strsep(). Print the saved name instead.
While at it, print the error code of the failure.
- Fix use-after-free for same named historgrams
Histograms can be named so that they can be used in multiple events.
But if the named histogram has a variable attached, the second event
that uses the named histogram which duplicates it and needs to free
the original after duplication leaves the old variable in place and
still visible. If another histogram uses than variable, it will use
the stale one which will try to reference the freed duplicate
histogram and crash the kernel.
Free the duplicate variables along with the duplicated histogram
data.
- Check return value of kthread_run() in event self test
The events self tests uses a kthread for testing but does not check
if it succeeded in creating a kthread. If the kthread creation were
to fail, the code will still try to call kthread_stop() on the error
returned.
- Fix race between reading trace_pipe and updating subbuffer size
If a user is reading the trace_pipe file at the same time they update
the ring buffer sub-buffer size, can cause the trace_pipe read to
read stale data. Add trace_access_lock() around updating the ring
buffer sub-buffer size.
- Fix eventfs_inode on failure path in creation of the events directory
In the creation of the "events" directory, if after allocating the
eventfs_inode a failure is detected, it calls cleanup_ei() which
calls free_ei(). The free_ei() will test if eventfs_inode being freed
has no children. It is a bug if it does. But on the failure case of
the creation of the "events" directory, the children lists have not
yet been initialized and the free will trigger a warning because
list_empty() on an uninitialized list returns false.
Move the initialization into init_ei() where it makes more sense and
makes sure that a created eventfs_inode has its lists initialized
upon creation.
- Check return value of kthread_run() in ftrace direct sample code
The sample code that shows how to use the ftrace direct calls does
not test the return of kthread_run() to see if it succeeds. Return a
failure if the kthread_run() doesn't succeed.
- Clear user events state on fork in case of alloc failure
On fork, the child gets a pointer to the parent's user events state.
It makes a copy of it then updates the child's pointer to it. But if
the allocation fails, the duplication function leaves the child with
a pointer to its parent's descriptor. When the child cleans up its
data, it will free the parent's descriptor while the parent is still
using it.
In the duplication function, set the child's user_event_mm to NULL
before testing if the allocation succeeded, and when it exits it will
not free the parent's descriptor.
- Fix retry exhaustion in simple ring buffer reader swap
simple_ring_buffer_swap_reader_page() starts with retry set to 8 and
post-decrements it only after a failed link replacement. On the final
attempt, a successful replacement leaves retry at zero, while a
failed replacement leaves it at -1.
But the check for success expects the retry value to be non-zero and
exits with an error on zero. This is the opposite result. Fix it.
- Fail nicely when the remote swap_reader_page() returns an error
Currently, if the swap_reader_page() of a remote buffer fails, it
triggers a WARN_ON_ONCE() and continues normally. Instead, have it
exit with an error and a pr_warn() print instead of a full WARNING.
* tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace:
ring-buffer: Stop remote reader update when page swap fails
tracing: Fix retry exhaustion in simple ring buffer reader swap
tracing/user_events: Clear copied tracing state before fork duplication
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify
samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify
eventfs: Initialize ei->children and ei->list in init_ei()
tracing: Fix use-after-free in trace_pipe read on sub-buffer order change
tracing: Fix crash passing ERR_PTR to kthread_stop()
tracing: Fix use-after-free with same-name named triggers
tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/acpi/power.c')
| -rw-r--r-- | drivers/acpi/power.c | 1159 |
1 files changed, 1159 insertions, 0 deletions
diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c new file mode 100644 index 000000000..23a4e207a --- /dev/null +++ b/drivers/acpi/power.c @@ -0,0 +1,1159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * drivers/acpi/power.c - ACPI Power Resources management. + * + * Copyright (C) 2001 - 2015 Intel Corp. + * Author: Andy Grover <andrew.grover@intel.com> + * Author: Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> + */ + +/* + * ACPI power-managed devices may be controlled in two ways: + * 1. via "Device Specific (D-State) Control" + * 2. via "Power Resource Control". + * The code below deals with ACPI Power Resources control. + * + * An ACPI "power resource object" represents a software controllable power + * plane, clock plane, or other resource depended on by a device. + * + * A device may rely on multiple power resources, and a power resource + * may be shared by multiple devices. + */ + +#define pr_fmt(fmt) "ACPI: PM: " fmt + +#include <linux/delay.h> +#include <linux/dmi.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/string_choices.h> +#include <linux/pm_runtime.h> +#include <linux/sysfs.h> +#include <linux/acpi.h> +#include "sleep.h" +#include "internal.h" + +#define ACPI_POWER_RESOURCE_STATE_OFF 0x00 +#define ACPI_POWER_RESOURCE_STATE_ON 0x01 +#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF + +struct acpi_power_dependent_device { + struct device *dev; + struct list_head node; +}; + +struct acpi_power_resource { + struct acpi_device device; + struct list_head list_node; + u32 system_level; + u32 order; + unsigned int ref_count; + u8 state; + struct mutex resource_lock; + struct list_head dependents; +}; + +struct acpi_power_resource_entry { + struct list_head node; + struct acpi_power_resource *resource; +}; + +static bool hp_eb_gp12pxp_quirk; +static bool unused_power_resources_quirk; + +static LIST_HEAD(acpi_power_resource_list); +static DEFINE_MUTEX(power_resource_list_lock); + +/* -------------------------------------------------------------------------- + Power Resource Management + -------------------------------------------------------------------------- */ + +static inline const char *resource_dev_name(struct acpi_power_resource *pr) +{ + return dev_name(&pr->device.dev); +} + +static inline +struct acpi_power_resource *to_power_resource(struct acpi_device *device) +{ + return container_of(device, struct acpi_power_resource, device); +} + +static struct acpi_power_resource *acpi_power_get_context(acpi_handle handle) +{ + struct acpi_device *device = acpi_fetch_acpi_dev(handle); + + if (!device) + return NULL; + + return to_power_resource(device); +} + +static int acpi_power_resources_list_add(acpi_handle handle, + struct list_head *list) +{ + struct acpi_power_resource *resource = acpi_power_get_context(handle); + struct acpi_power_resource_entry *entry; + + if (!resource || !list) + return -EINVAL; + + entry = kzalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + entry->resource = resource; + if (!list_empty(list)) { + struct acpi_power_resource_entry *e; + + list_for_each_entry(e, list, node) + if (e->resource->order > resource->order) { + list_add_tail(&entry->node, &e->node); + return 0; + } + } + list_add_tail(&entry->node, list); + return 0; +} + +void acpi_power_resources_list_free(struct list_head *list) +{ + struct acpi_power_resource_entry *entry, *e; + + list_for_each_entry_safe(entry, e, list, node) { + list_del(&entry->node); + kfree(entry); + } +} + +static bool acpi_power_resource_is_dup(union acpi_object *package, + unsigned int start, unsigned int i) +{ + acpi_handle rhandle, dup; + unsigned int j; + + /* The caller is expected to check the package element types */ + rhandle = package->package.elements[i].reference.handle; + for (j = start; j < i; j++) { + dup = package->package.elements[j].reference.handle; + if (dup == rhandle) + return true; + } + + return false; +} + +int acpi_extract_power_resources(union acpi_object *package, unsigned int start, + struct list_head *list) +{ + unsigned int i; + int err = 0; + + for (i = start; i < package->package.count; i++) { + union acpi_object *element = &package->package.elements[i]; + struct acpi_device *rdev; + acpi_handle rhandle; + + if (element->type != ACPI_TYPE_LOCAL_REFERENCE) { + err = -ENODATA; + break; + } + rhandle = element->reference.handle; + if (!rhandle) { + err = -ENODEV; + break; + } + + /* Some ACPI tables contain duplicate power resource references */ + if (acpi_power_resource_is_dup(package, start, i)) + continue; + + rdev = acpi_add_power_resource(rhandle); + if (!rdev) { + err = -ENODEV; + break; + } + err = acpi_power_resources_list_add(rhandle, list); + if (err) + break; + } + if (err) + acpi_power_resources_list_free(list); + + return err; +} + +static int __get_state(acpi_handle handle, u8 *state) +{ + acpi_status status = AE_OK; + unsigned long long sta = 0; + u8 cur_state; + + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); + if (ACPI_FAILURE(status)) + return -ENODEV; + + cur_state = sta & ACPI_POWER_RESOURCE_STATE_ON; + + acpi_handle_debug(handle, "Power resource is %s\n", + str_on_off(cur_state)); + + *state = cur_state; + 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; + 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'. */ + list_for_each_entry(entry, list, node) { + struct acpi_power_resource *resource = entry->resource; + int result; + + mutex_lock(&resource->resource_lock); + result = acpi_power_get_state(resource, &cur_state); + mutex_unlock(&resource->resource_lock); + if (result) + return result; + + if (cur_state != ACPI_POWER_RESOURCE_STATE_ON) + break; + } + + pr_debug("Power resource list is %s\n", str_on_off(cur_state)); + + *state = cur_state; + return 0; +} + +static int +acpi_power_resource_add_dependent(struct acpi_power_resource *resource, + struct device *dev) +{ + struct acpi_power_dependent_device *dep; + int ret = 0; + + mutex_lock(&resource->resource_lock); + list_for_each_entry(dep, &resource->dependents, node) { + /* Only add it once */ + if (dep->dev == dev) + goto unlock; + } + + dep = kzalloc_obj(*dep); + if (!dep) { + ret = -ENOMEM; + goto unlock; + } + + dep->dev = dev; + list_add_tail(&dep->node, &resource->dependents); + dev_dbg(dev, "added power dependency to [%s]\n", + resource_dev_name(resource)); + +unlock: + mutex_unlock(&resource->resource_lock); + return ret; +} + +static void +acpi_power_resource_remove_dependent(struct acpi_power_resource *resource, + struct device *dev) +{ + struct acpi_power_dependent_device *dep; + + mutex_lock(&resource->resource_lock); + list_for_each_entry(dep, &resource->dependents, node) { + if (dep->dev == dev) { + list_del(&dep->node); + kfree(dep); + dev_dbg(dev, "removed power dependency to [%s]\n", + resource_dev_name(resource)); + break; + } + } + mutex_unlock(&resource->resource_lock); +} + +/** + * acpi_device_power_add_dependent - Add dependent device of this ACPI device + * @adev: ACPI device pointer + * @dev: Dependent device + * + * If @adev has non-empty _PR0 the @dev is added as dependent device to all + * power resources returned by it. This means that whenever these power + * resources are turned _ON the dependent devices get runtime resumed. This + * is needed for devices such as PCI to allow its driver to re-initialize + * it after it went to D0uninitialized. + * + * If @adev does not have _PR0 this does nothing. + * + * Returns %0 in case of success and negative errno otherwise. + */ +int acpi_device_power_add_dependent(struct acpi_device *adev, + struct device *dev) +{ + struct acpi_power_resource_entry *entry; + struct list_head *resources; + int ret; + + if (!adev->flags.power_manageable) + return 0; + + resources = &adev->power.states[ACPI_STATE_D0].resources; + list_for_each_entry(entry, resources, node) { + ret = acpi_power_resource_add_dependent(entry->resource, dev); + if (ret) + goto err; + } + + return 0; + +err: + list_for_each_entry(entry, resources, node) + acpi_power_resource_remove_dependent(entry->resource, dev); + + return ret; +} + +/** + * acpi_device_power_remove_dependent - Remove dependent device + * @adev: ACPI device pointer + * @dev: Dependent device + * + * Does the opposite of acpi_device_power_add_dependent() and removes the + * dependent device if it is found. Can be called to @adev that does not + * have _PR0 as well. + */ +void acpi_device_power_remove_dependent(struct acpi_device *adev, + struct device *dev) +{ + struct acpi_power_resource_entry *entry; + struct list_head *resources; + + if (!adev->flags.power_manageable) + return; + + resources = &adev->power.states[ACPI_STATE_D0].resources; + list_for_each_entry_reverse(entry, resources, node) + acpi_power_resource_remove_dependent(entry->resource, dev); +} + +static int __acpi_power_on(struct acpi_power_resource *resource) +{ + acpi_handle handle = resource->device.handle; + struct acpi_power_dependent_device *dep; + acpi_status status = AE_OK; + + status = acpi_evaluate_object(handle, "_ON", NULL, NULL); + if (ACPI_FAILURE(status)) { + resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN; + return -ENODEV; + } + + resource->state = ACPI_POWER_RESOURCE_STATE_ON; + + acpi_handle_debug(handle, "Power resource turned on\n"); + + /* + * If there are other dependents on this power resource we need to + * resume them now so that their drivers can re-initialize the + * hardware properly after it went back to D0. + */ + if (list_empty(&resource->dependents) || + list_is_singular(&resource->dependents)) + return 0; + + list_for_each_entry(dep, &resource->dependents, node) { + dev_dbg(dep->dev, "runtime resuming because [%s] turned on\n", + resource_dev_name(resource)); + pm_request_resume(dep->dev); + } + + return 0; +} + +static int acpi_power_on_unlocked(struct acpi_power_resource *resource) +{ + int result = 0; + + if (resource->ref_count++) { + acpi_handle_debug(resource->device.handle, + "Power resource already on\n"); + } else { + result = __acpi_power_on(resource); + if (result) + resource->ref_count--; + } + return result; +} + +static int acpi_power_on(struct acpi_power_resource *resource) +{ + int result; + + mutex_lock(&resource->resource_lock); + result = acpi_power_on_unlocked(resource); + mutex_unlock(&resource->resource_lock); + return result; +} + +static int __acpi_power_off(struct acpi_power_resource *resource) +{ + acpi_handle handle = resource->device.handle; + acpi_status status; + + status = acpi_evaluate_object(handle, "_OFF", NULL, NULL); + if (ACPI_FAILURE(status)) { + resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN; + return -ENODEV; + } + + resource->state = ACPI_POWER_RESOURCE_STATE_OFF; + + acpi_handle_debug(handle, "Power resource turned off\n"); + + return 0; +} + +static int acpi_power_off_unlocked(struct acpi_power_resource *resource) +{ + int result = 0; + + if (!resource->ref_count) { + acpi_handle_debug(resource->device.handle, + "Power resource already off\n"); + return 0; + } + + if (--resource->ref_count) { + acpi_handle_debug(resource->device.handle, + "Power resource still in use\n"); + } else { + result = __acpi_power_off(resource); + if (result) + resource->ref_count++; + } + return result; +} + +static int acpi_power_off(struct acpi_power_resource *resource) +{ + int result; + + mutex_lock(&resource->resource_lock); + result = acpi_power_off_unlocked(resource); + mutex_unlock(&resource->resource_lock); + return result; +} + +static int acpi_power_off_list(struct list_head *list) +{ + struct acpi_power_resource_entry *entry; + int result = 0; + + list_for_each_entry_reverse(entry, list, node) { + result = acpi_power_off(entry->resource); + if (result) + goto err; + } + return 0; + + err: + list_for_each_entry_continue(entry, list, node) + acpi_power_on(entry->resource); + + return result; +} + +static int acpi_power_on_list(struct list_head *list) +{ + struct acpi_power_resource_entry *entry; + int result = 0; + + list_for_each_entry(entry, list, node) { + result = acpi_power_on(entry->resource); + if (result) + goto err; + } + return 0; + + err: + list_for_each_entry_continue_reverse(entry, list, node) + acpi_power_off(entry->resource); + + return result; +} + +static struct attribute *attrs[] = { + NULL, +}; + +static const struct attribute_group attr_groups[] = { + [ACPI_STATE_D0] = { + .name = "power_resources_D0", + .attrs = attrs, + }, + [ACPI_STATE_D1] = { + .name = "power_resources_D1", + .attrs = attrs, + }, + [ACPI_STATE_D2] = { + .name = "power_resources_D2", + .attrs = attrs, + }, + [ACPI_STATE_D3_HOT] = { + .name = "power_resources_D3hot", + .attrs = attrs, + }, +}; + +static const struct attribute_group wakeup_attr_group = { + .name = "power_resources_wakeup", + .attrs = attrs, +}; + +static void acpi_power_hide_list(struct acpi_device *adev, + struct list_head *resources, + const struct attribute_group *attr_group) +{ + struct acpi_power_resource_entry *entry; + + if (list_empty(resources)) + return; + + list_for_each_entry_reverse(entry, resources, node) { + struct acpi_device *res_dev = &entry->resource->device; + + sysfs_remove_link_from_group(&adev->dev.kobj, + attr_group->name, + dev_name(&res_dev->dev)); + } + sysfs_remove_group(&adev->dev.kobj, attr_group); +} + +static void acpi_power_expose_list(struct acpi_device *adev, + struct list_head *resources, + const struct attribute_group *attr_group) +{ + struct acpi_power_resource_entry *entry; + int ret; + + if (list_empty(resources)) + return; + + ret = sysfs_create_group(&adev->dev.kobj, attr_group); + if (ret) + return; + + list_for_each_entry(entry, resources, node) { + struct acpi_device *res_dev = &entry->resource->device; + + ret = sysfs_add_link_to_group(&adev->dev.kobj, + attr_group->name, + &res_dev->dev.kobj, + dev_name(&res_dev->dev)); + if (ret) { + acpi_power_hide_list(adev, resources, attr_group); + break; + } + } +} + +static void acpi_power_expose_hide(struct acpi_device *adev, + struct list_head *resources, + const struct attribute_group *attr_group, + bool expose) +{ + if (expose) + acpi_power_expose_list(adev, resources, attr_group); + else + acpi_power_hide_list(adev, resources, attr_group); +} + +void acpi_power_add_remove_device(struct acpi_device *adev, bool add) +{ + int state; + + if (adev->wakeup.flags.valid) + acpi_power_expose_hide(adev, &adev->wakeup.resources, + &wakeup_attr_group, add); + + if (!adev->power.flags.power_resources) + return; + + for (state = ACPI_STATE_D0; state <= ACPI_STATE_D3_HOT; state++) + acpi_power_expose_hide(adev, + &adev->power.states[state].resources, + &attr_groups[state], add); +} + +int acpi_power_wakeup_list_init(struct list_head *list, int *system_level_p) +{ + struct acpi_power_resource_entry *entry; + int system_level = 5; + + list_for_each_entry(entry, list, node) { + struct acpi_power_resource *resource = entry->resource; + u8 state; + + mutex_lock(&resource->resource_lock); + + /* + * Make sure that the power resource state and its reference + * counter value are consistent with each other. + */ + if (!resource->ref_count && + !acpi_power_get_state(resource, &state) && + state == ACPI_POWER_RESOURCE_STATE_ON) + __acpi_power_off(resource); + + if (system_level > resource->system_level) + system_level = resource->system_level; + + mutex_unlock(&resource->resource_lock); + } + *system_level_p = system_level; + return 0; +} + +/* -------------------------------------------------------------------------- + Device Power Management + -------------------------------------------------------------------------- */ + +/** + * acpi_device_sleep_wake - execute _DSW (Device Sleep Wake) or (deprecated in + * ACPI 3.0) _PSW (Power State Wake) + * @dev: Device to handle. + * @enable: 0 - disable, 1 - enable the wake capabilities of the device. + * @sleep_state: Target sleep state of the system. + * @dev_state: Target power state of the device. + * + * Execute _DSW (Device Sleep Wake) or (deprecated in ACPI 3.0) _PSW (Power + * State Wake) for the device, if present. On failure reset the device's + * wakeup.flags.valid flag. + * + * RETURN VALUE: + * 0 if either _DSW or _PSW has been successfully executed + * 0 if neither _DSW nor _PSW has been found + * -ENODEV if the execution of either _DSW or _PSW has failed + */ +int acpi_device_sleep_wake(struct acpi_device *dev, + int enable, int sleep_state, int dev_state) +{ + union acpi_object in_arg[3]; + struct acpi_object_list arg_list = { 3, in_arg }; + acpi_status status = AE_OK; + + /* + * Try to execute _DSW first. + * + * Three arguments are needed for the _DSW object: + * Argument 0: enable/disable the wake capabilities + * Argument 1: target system state + * Argument 2: target device state + * When _DSW object is called to disable the wake capabilities, maybe + * the first argument is filled. The values of the other two arguments + * are meaningless. + */ + in_arg[0].type = ACPI_TYPE_INTEGER; + in_arg[0].integer.value = enable; + in_arg[1].type = ACPI_TYPE_INTEGER; + in_arg[1].integer.value = sleep_state; + in_arg[2].type = ACPI_TYPE_INTEGER; + in_arg[2].integer.value = dev_state; + status = acpi_evaluate_object(dev->handle, "_DSW", &arg_list, NULL); + if (ACPI_SUCCESS(status)) { + return 0; + } else if (status != AE_NOT_FOUND) { + acpi_handle_info(dev->handle, "_DSW execution failed\n"); + dev->wakeup.flags.valid = 0; + return -ENODEV; + } + + /* Execute _PSW */ + status = acpi_execute_simple_method(dev->handle, "_PSW", enable); + if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) { + acpi_handle_info(dev->handle, "_PSW execution failed\n"); + dev->wakeup.flags.valid = 0; + return -ENODEV; + } + + return 0; +} + +/* + * Prepare a wakeup device, two steps (Ref ACPI 2.0:P229): + * 1. Power on the power resources required for the wakeup device + * 2. Execute _DSW (Device Sleep Wake) or (deprecated in ACPI 3.0) _PSW (Power + * State Wake) for the device, if present + */ +int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state) +{ + int err = 0; + + if (!dev || !dev->wakeup.flags.valid) + return -EINVAL; + + mutex_lock(&acpi_device_lock); + + dev_dbg(&dev->dev, "Enabling wakeup power (count %d)\n", + dev->wakeup.prepare_count); + + if (dev->wakeup.prepare_count++) + goto out; + + err = acpi_power_on_list(&dev->wakeup.resources); + if (err) { + dev_err(&dev->dev, "Cannot turn on wakeup power resources\n"); + dev->wakeup.flags.valid = 0; + goto out; + } + + /* + * Passing 3 as the third argument below means the device may be + * put into arbitrary power state afterward. + */ + err = acpi_device_sleep_wake(dev, 1, sleep_state, 3); + if (err) { + acpi_power_off_list(&dev->wakeup.resources); + dev->wakeup.prepare_count = 0; + goto out; + } + + dev_dbg(&dev->dev, "Wakeup power enabled\n"); + + out: + mutex_unlock(&acpi_device_lock); + return err; +} + +/* + * Shutdown a wakeup device, counterpart of above method + * 1. Execute _DSW (Device Sleep Wake) or (deprecated in ACPI 3.0) _PSW (Power + * State Wake) for the device, if present + * 2. Shutdown down the power resources + */ +int acpi_disable_wakeup_device_power(struct acpi_device *dev) +{ + struct acpi_power_resource_entry *entry; + int err = 0; + + if (!dev || !dev->wakeup.flags.valid) + return -EINVAL; + + mutex_lock(&acpi_device_lock); + + dev_dbg(&dev->dev, "Disabling wakeup power (count %d)\n", + dev->wakeup.prepare_count); + + /* Do nothing if wakeup power has not been enabled for this device. */ + if (dev->wakeup.prepare_count <= 0) + goto out; + + if (--dev->wakeup.prepare_count > 0) + goto out; + + err = acpi_device_sleep_wake(dev, 0, 0, 0); + if (err) + goto out; + + /* + * All of the power resources in the list need to be turned off even if + * there are errors. + */ + list_for_each_entry(entry, &dev->wakeup.resources, node) { + int ret; + + ret = acpi_power_off(entry->resource); + if (ret && !err) + err = ret; + } + if (err) { + dev_err(&dev->dev, "Cannot turn off wakeup power resources\n"); + dev->wakeup.flags.valid = 0; + goto out; + } + + dev_dbg(&dev->dev, "Wakeup power disabled\n"); + + out: + mutex_unlock(&acpi_device_lock); + return err; +} + +int acpi_power_get_inferred_state(struct acpi_device *device, int *state) +{ + u8 list_state = ACPI_POWER_RESOURCE_STATE_OFF; + int result = 0; + int i = 0; + + if (!device || !state) + return -EINVAL; + + /* + * We know a device's inferred power state when all the resources + * required for a given D-state are 'on'. + */ + for (i = ACPI_STATE_D0; i <= ACPI_STATE_D3_HOT; i++) { + struct list_head *list = &device->power.states[i].resources; + + if (list_empty(list)) + continue; + + result = acpi_power_get_list_state(list, &list_state); + if (result) + return result; + + if (list_state == ACPI_POWER_RESOURCE_STATE_ON) { + *state = i; + return 0; + } + } + + *state = device->power.states[ACPI_STATE_D3_COLD].flags.valid ? + ACPI_STATE_D3_COLD : ACPI_STATE_D3_HOT; + return 0; +} + +int acpi_power_on_resources(struct acpi_device *device, int state) +{ + if (!device || state < ACPI_STATE_D0 || state > ACPI_STATE_D3_HOT) + return -EINVAL; + + return acpi_power_on_list(&device->power.states[state].resources); +} + +int acpi_power_transition(struct acpi_device *device, int state) +{ + int result = 0; + + if (!device || (state < ACPI_STATE_D0) || (state > ACPI_STATE_D3_COLD)) + return -EINVAL; + + if (device->power.state == state || !device->flags.power_manageable) + return 0; + + if ((device->power.state < ACPI_STATE_D0) + || (device->power.state > ACPI_STATE_D3_COLD)) + return -ENODEV; + + /* + * First we reference all power resources required in the target list + * (e.g. so the device doesn't lose power while transitioning). Then, + * we dereference all power resources used in the current list. + */ + if (state < ACPI_STATE_D3_COLD) + result = acpi_power_on_list( + &device->power.states[state].resources); + + if (!result && device->power.state < ACPI_STATE_D3_COLD) + acpi_power_off_list( + &device->power.states[device->power.state].resources); + + /* We shouldn't change the state unless the above operations succeed. */ + device->power.state = result ? ACPI_STATE_UNKNOWN : state; + + return result; +} + +static void acpi_release_power_resource(struct device *dev) +{ + struct acpi_device *device = to_acpi_device(dev); + struct acpi_power_resource *resource; + + resource = container_of(device, struct acpi_power_resource, device); + + mutex_lock(&power_resource_list_lock); + list_del(&resource->list_node); + mutex_unlock(&power_resource_list_lock); + + acpi_free_pnp_ids(&device->pnp); + kfree(resource); +} + +static ssize_t resource_in_use_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct acpi_power_resource *resource; + + resource = to_power_resource(to_acpi_device(dev)); + return sprintf(buf, "%u\n", !!resource->ref_count); +} +static DEVICE_ATTR_RO(resource_in_use); + +static void acpi_power_sysfs_remove(struct acpi_device *device) +{ + device_remove_file(&device->dev, &dev_attr_resource_in_use); +} + +static void acpi_power_add_resource_to_list(struct acpi_power_resource *resource) +{ + mutex_lock(&power_resource_list_lock); + + if (!list_empty(&acpi_power_resource_list)) { + struct acpi_power_resource *r; + + list_for_each_entry(r, &acpi_power_resource_list, list_node) + if (r->order > resource->order) { + list_add_tail(&resource->list_node, &r->list_node); + goto out; + } + } + list_add_tail(&resource->list_node, &acpi_power_resource_list); + + out: + mutex_unlock(&power_resource_list_lock); +} + +struct acpi_device *acpi_add_power_resource(acpi_handle handle) +{ + struct acpi_device *device = acpi_fetch_acpi_dev(handle); + struct acpi_power_resource *resource; + union acpi_object acpi_object; + struct acpi_buffer buffer = { sizeof(acpi_object), &acpi_object }; + acpi_status status; + u8 state_dummy; + int result; + + if (device) + return device; + + resource = kzalloc_obj(*resource); + if (!resource) + return NULL; + + device = &resource->device; + acpi_init_device_object(device, handle, ACPI_BUS_TYPE_POWER, + acpi_release_power_resource); + mutex_init(&resource->resource_lock); + INIT_LIST_HEAD(&resource->list_node); + INIT_LIST_HEAD(&resource->dependents); + device->power.state = ACPI_STATE_UNKNOWN; + + /* Evaluate the object to get the system level and resource order. */ + status = acpi_evaluate_object(handle, NULL, NULL, &buffer); + if (ACPI_FAILURE(status)) + goto err; + + resource->system_level = acpi_object.power_resource.system_level; + resource->order = acpi_object.power_resource.resource_order; + resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN; + + /* Get the initial state or just flip it on if that fails. */ + if (acpi_power_get_state(resource, &state_dummy)) + __acpi_power_on(resource); + + acpi_handle_info(handle, "New power resource\n"); + + result = acpi_tie_acpi_dev(device); + if (result) + goto err; + + result = acpi_device_add(device); + if (result) + goto err; + + if (!device_create_file(&device->dev, &dev_attr_resource_in_use)) + device->remove = acpi_power_sysfs_remove; + + acpi_power_add_resource_to_list(resource); + acpi_device_add_finalize(device); + return device; + + err: + acpi_dev_put(device); + return NULL; +} + +#ifdef CONFIG_ACPI_SLEEP +static bool resource_is_gp12pxp(acpi_handle handle) +{ + const char *path; + bool ret; + + path = acpi_handle_path(handle); + ret = path && strcmp(path, "\\_SB_.PCI0.GP12.PXP_") == 0; + kfree(path); + + return ret; +} + +static void acpi_resume_on_eb_gp12pxp(struct acpi_power_resource *resource) +{ + acpi_handle_notice(resource->device.handle, + "HP EB quirk - turning OFF then ON\n"); + + __acpi_power_off(resource); + __acpi_power_on(resource); + + /* + * Use the same delay as DSDT uses in modem _RST method. + * + * Otherwise we get "Unable to change power state from unknown to D0, + * device inaccessible" error for the modem PCI device after thaw. + * + * This power resource is normally being enabled only during thaw (once) + * so this wait is not a performance issue. + */ + msleep(200); +} + +void acpi_resume_power_resources(void) +{ + struct acpi_power_resource *resource; + + mutex_lock(&power_resource_list_lock); + + list_for_each_entry(resource, &acpi_power_resource_list, list_node) { + int result; + u8 state; + + mutex_lock(&resource->resource_lock); + + resource->state = ACPI_POWER_RESOURCE_STATE_UNKNOWN; + result = acpi_power_get_state(resource, &state); + if (result) { + mutex_unlock(&resource->resource_lock); + continue; + } + + if (state == ACPI_POWER_RESOURCE_STATE_OFF + && resource->ref_count) { + if (hp_eb_gp12pxp_quirk && + resource_is_gp12pxp(resource->device.handle)) { + acpi_resume_on_eb_gp12pxp(resource); + } else { + acpi_handle_debug(resource->device.handle, + "Turning ON\n"); + __acpi_power_on(resource); + } + } + + mutex_unlock(&resource->resource_lock); + } + + mutex_unlock(&power_resource_list_lock); +} +#endif + +static const struct dmi_system_id dmi_hp_elitebook_gp12pxp_quirk[] = { +/* + * This laptop (and possibly similar models too) has power resource called + * "GP12.PXP_" for its WWAN modem. + * + * For this power resource to turn ON power for the modem it needs certain + * internal flag called "ONEN" to be set. + * This flag only gets set from this power resource "_OFF" method, while the + * actual modem power gets turned off during suspend by "GP12.PTS" method + * called from the global "_PTS" (Prepare To Sleep) method. + * On the other hand, this power resource "_OFF" method implementation just + * sets the aforementioned flag without actually doing anything else (it + * doesn't contain any code to actually turn off power). + * + * The above means that when upon hibernation finish we try to set this + * power resource back ON since its "_STA" method returns 0 (while the resource + * is still considered in use) its "_ON" method won't do anything since + * that "ONEN" flag is not set. + * Overall, this means the modem is dead until laptop is rebooted since its + * power has been cut by "_PTS" and its PCI configuration was lost and not able + * to be restored. + * + * The easiest way to workaround the issue is to call this power resource + * "_OFF" method before calling the "_ON" method to make sure the "ONEN" + * flag gets properly set. + */ + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HP"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP EliteBook 855 G7 Notebook PC"), + }, + }, + {} +}; + +static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { + { + /* + * The Toshiba Click Mini has a CPR3 power-resource which must + * be on for the touchscreen to work, but which is not in any + * _PR? lists. The other 2 affected power-resources are no-ops. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"), + DMI_MATCH(DMI_PRODUCT_NAME, "SATELLITE Click Mini L9W-B"), + }, + }, + { + /* + * THUNDEROBOT ZERO laptop: Due to its SSDT table bug, power + * resource 'PXP' will be shut down on initialization, making + * the NVMe #2 and the NVIDIA dGPU both unavailable (they're + * both controlled by 'PXP'). + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "THUNDEROBOT"), + DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), + } + + }, + {} +}; + +/** + * acpi_turn_off_unused_power_resources - Turn off power resources not in use. + */ +void acpi_turn_off_unused_power_resources(void) +{ + struct acpi_power_resource *resource; + + if (unused_power_resources_quirk) + return; + + mutex_lock(&power_resource_list_lock); + + list_for_each_entry_reverse(resource, &acpi_power_resource_list, list_node) { + mutex_lock(&resource->resource_lock); + + if (!resource->ref_count && + resource->state == ACPI_POWER_RESOURCE_STATE_ON) { + acpi_handle_debug(resource->device.handle, "Turning OFF\n"); + __acpi_power_off(resource); + } + + mutex_unlock(&resource->resource_lock); + } + + mutex_unlock(&power_resource_list_lock); +} + +void __init acpi_power_resources_init(void) +{ + hp_eb_gp12pxp_quirk = dmi_check_system(dmi_hp_elitebook_gp12pxp_quirk); + unused_power_resources_quirk = + dmi_check_system(dmi_leave_unused_power_resources_on); +} |
