Skip to content

Commit

Permalink
ACPI / PM: Rework the handling of devices depending on power resources
Browse files Browse the repository at this point in the history
Commit 0090def (ACPI: Add interface to register/unregister device
to/from power resources) made it possible to indicate to the ACPI
core that if the given device depends on any power resources, then
it should be resumed as soon as all of the power resources required
by it to transition to the D0 power state have been turned on.

Unfortunately, however, this was a mistake, because all devices
depending on power resources should be treated this way (i.e. they
should be resumed when all power resources required by their D0
state have been turned on) and for the majority of those devices
the ACPI core can figure out by itself which (physical) devices
depend on what power resources.

For this reason, replace the code added by commit 0090def with a
new, much more straightforward, mechanism that will be used
internally by the ACPI core and remove all references to that code
from kernel subsystems using ACPI.

For the cases when there are (physical) devices that should be
resumed whenever a not directly related ACPI device node goes into
D0 as a result of power resources configuration changes, like in
the SATA case, add two new routines, acpi_dev_pm_add_dependent()
and acpi_dev_pm_remove_dependent(), allowing subsystems to manage
such dependencies.  Convert the SATA subsystem to use the new
functions accordingly.

Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
  • Loading branch information
Rafael J. Wysocki committed Jan 17, 2013
1 parent 6a8dd80 commit bc9b640
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 155 deletions.
56 changes: 56 additions & 0 deletions drivers/acpi/device_pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -665,3 +665,59 @@ void acpi_dev_pm_detach(struct device *dev, bool power_off)
}
}
EXPORT_SYMBOL_GPL(acpi_dev_pm_detach);

/**
* acpi_dev_pm_add_dependent - Add physical device depending for PM.
* @handle: Handle of ACPI device node.
* @depdev: Device depending on that node for PM.
*/
void acpi_dev_pm_add_dependent(acpi_handle handle, struct device *depdev)
{
struct acpi_device_physical_node *dep;
struct acpi_device *adev;

if (!depdev || acpi_bus_get_device(handle, &adev))
return;

mutex_lock(&adev->physical_node_lock);

list_for_each_entry(dep, &adev->power_dependent, node)
if (dep->dev == depdev)
goto out;

dep = kzalloc(sizeof(*dep), GFP_KERNEL);
if (dep) {
dep->dev = depdev;
list_add_tail(&dep->node, &adev->power_dependent);
}

out:
mutex_unlock(&adev->physical_node_lock);
}
EXPORT_SYMBOL_GPL(acpi_dev_pm_add_dependent);

/**
* acpi_dev_pm_remove_dependent - Remove physical device depending for PM.
* @handle: Handle of ACPI device node.
* @depdev: Device depending on that node for PM.
*/
void acpi_dev_pm_remove_dependent(acpi_handle handle, struct device *depdev)
{
struct acpi_device_physical_node *dep;
struct acpi_device *adev;

if (!depdev || acpi_bus_get_device(handle, &adev))
return;

mutex_lock(&adev->physical_node_lock);

list_for_each_entry(dep, &adev->power_dependent, node)
if (dep->dev == depdev) {
list_del(&dep->node);
kfree(dep);
break;
}

mutex_unlock(&adev->physical_node_lock);
}
EXPORT_SYMBOL_GPL(acpi_dev_pm_remove_dependent);
1 change: 1 addition & 0 deletions drivers/acpi/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ static inline void acpi_debugfs_init(void) { return; }
Power Resource
-------------------------------------------------------------------------- */
int acpi_power_init(void);
void acpi_power_add_remove_device(struct acpi_device *adev, bool add);
int acpi_device_sleep_wake(struct acpi_device *dev,
int enable, int sleep_state, int dev_state);
int acpi_power_get_inferred_state(struct acpi_device *device, int *state);
Expand Down
219 changes: 82 additions & 137 deletions drivers/acpi/power.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,31 +83,20 @@ static struct acpi_driver acpi_power_driver = {
.drv.pm = &acpi_power_pm,
};

/*
* A power managed device
* A device may rely on multiple power resources.
* */
struct acpi_power_managed_device {
struct device *dev; /* The physical device */
acpi_handle *handle;
};

struct acpi_power_resource_device {
struct acpi_power_managed_device *device;
struct acpi_power_resource_device *next;
struct acpi_power_dependent_device {
struct list_head node;
struct acpi_device *adev;
struct work_struct work;
};

struct acpi_power_resource {
struct acpi_device * device;
struct acpi_device *device;
struct list_head dependent;
acpi_bus_id name;
u32 system_level;
u32 order;
unsigned int ref_count;
struct mutex resource_lock;

/* List of devices relying on this power resource */
struct acpi_power_resource_device *devices;
struct mutex devices_lock;
};

static struct list_head acpi_power_resource_list;
Expand Down Expand Up @@ -207,21 +196,30 @@ static int acpi_power_get_list_state(struct acpi_handle_list *list, int *state)
return 0;
}

/* Resume the device when all power resources in _PR0 are on */
static void acpi_power_on_device(struct acpi_power_managed_device *device)
static void acpi_power_resume_dependent(struct work_struct *work)
{
struct acpi_device *acpi_dev;
acpi_handle handle = device->handle;
struct acpi_power_dependent_device *dep;
struct acpi_device_physical_node *pn;
struct acpi_device *adev;
int state;

if (acpi_bus_get_device(handle, &acpi_dev))
dep = container_of(work, struct acpi_power_dependent_device, work);
adev = dep->adev;
if (acpi_power_get_inferred_state(adev, &state))
return;

if(acpi_power_get_inferred_state(acpi_dev, &state))
if (state > ACPI_STATE_D0)
return;

if (state == ACPI_STATE_D0 && pm_runtime_suspended(device->dev))
pm_request_resume(device->dev);
mutex_lock(&adev->physical_node_lock);

list_for_each_entry(pn, &adev->physical_node_list, node)
pm_request_resume(pn->dev);

list_for_each_entry(pn, &adev->power_dependent, node)
pm_request_resume(pn->dev);

mutex_unlock(&adev->physical_node_lock);
}

static int __acpi_power_on(struct acpi_power_resource *resource)
Expand All @@ -244,9 +242,7 @@ static int __acpi_power_on(struct acpi_power_resource *resource)
static int acpi_power_on(acpi_handle handle)
{
int result = 0;
bool resume_device = false;
struct acpi_power_resource *resource = NULL;
struct acpi_power_resource_device *device_list;

result = acpi_power_get_context(handle, &resource);
if (result)
Expand All @@ -260,26 +256,17 @@ static int acpi_power_on(acpi_handle handle)
resource->name));
} else {
result = __acpi_power_on(resource);
if (result)
if (result) {
resource->ref_count--;
else
resume_device = true;
}
} else {
struct acpi_power_dependent_device *dep;

mutex_unlock(&resource->resource_lock);

if (!resume_device)
return result;

mutex_lock(&resource->devices_lock);

device_list = resource->devices;
while (device_list) {
acpi_power_on_device(device_list->device);
device_list = device_list->next;
list_for_each_entry(dep, &resource->dependent, node)
schedule_work(&dep->work);
}
}

mutex_unlock(&resource->devices_lock);
mutex_unlock(&resource->resource_lock);

return result;
}
Expand Down Expand Up @@ -357,119 +344,81 @@ static int acpi_power_on_list(struct acpi_handle_list *list)
return result;
}

static void __acpi_power_resource_unregister_device(struct device *dev,
acpi_handle res_handle)
static void acpi_power_add_dependent(acpi_handle rhandle,
struct acpi_device *adev)
{
struct acpi_power_resource *resource = NULL;
struct acpi_power_resource_device *prev, *curr;
struct acpi_power_dependent_device *dep;
struct acpi_power_resource *resource;

if (acpi_power_get_context(res_handle, &resource))
if (!rhandle || !adev || acpi_power_get_context(rhandle, &resource))
return;

mutex_lock(&resource->devices_lock);
prev = NULL;
curr = resource->devices;
while (curr) {
if (curr->device->dev == dev) {
if (!prev)
resource->devices = curr->next;
else
prev->next = curr->next;

kfree(curr);
break;
}

prev = curr;
curr = curr->next;
}
mutex_unlock(&resource->devices_lock);
}

/* Unlink dev from all power resources in _PR0 */
void acpi_power_resource_unregister_device(struct device *dev, acpi_handle handle)
{
struct acpi_device *acpi_dev;
struct acpi_handle_list *list;
int i;
mutex_lock(&resource->resource_lock);

if (!dev || !handle)
return;
list_for_each_entry(dep, &resource->dependent, node)
if (dep->adev == adev)
goto out;

if (acpi_bus_get_device(handle, &acpi_dev))
return;
dep = kzalloc(sizeof(*dep), GFP_KERNEL);
if (!dep)
goto out;

list = &acpi_dev->power.states[ACPI_STATE_D0].resources;
dep->adev = adev;
INIT_WORK(&dep->work, acpi_power_resume_dependent);
list_add_tail(&dep->node, &resource->dependent);

for (i = 0; i < list->count; i++)
__acpi_power_resource_unregister_device(dev,
list->handles[i]);
out:
mutex_unlock(&resource->resource_lock);
}
EXPORT_SYMBOL_GPL(acpi_power_resource_unregister_device);

static int __acpi_power_resource_register_device(
struct acpi_power_managed_device *powered_device, acpi_handle handle)
static void acpi_power_remove_dependent(acpi_handle rhandle,
struct acpi_device *adev)
{
struct acpi_power_resource *resource = NULL;
struct acpi_power_resource_device *power_resource_device;
int result;
struct acpi_power_dependent_device *dep;
struct acpi_power_resource *resource;
struct work_struct *work = NULL;

result = acpi_power_get_context(handle, &resource);
if (result)
return result;
if (!rhandle || !adev || acpi_power_get_context(rhandle, &resource))
return;

power_resource_device = kzalloc(
sizeof(*power_resource_device), GFP_KERNEL);
if (!power_resource_device)
return -ENOMEM;
mutex_lock(&resource->resource_lock);

power_resource_device->device = powered_device;
list_for_each_entry(dep, &resource->dependent, node)
if (dep->adev == adev) {
list_del(&dep->node);
work = &dep->work;
break;
}

mutex_lock(&resource->devices_lock);
power_resource_device->next = resource->devices;
resource->devices = power_resource_device;
mutex_unlock(&resource->devices_lock);
mutex_unlock(&resource->resource_lock);

return 0;
if (work) {
cancel_work_sync(work);
kfree(dep);
}
}

/* Link dev to all power resources in _PR0 */
int acpi_power_resource_register_device(struct device *dev, acpi_handle handle)
void acpi_power_add_remove_device(struct acpi_device *adev, bool add)
{
struct acpi_device *acpi_dev;
struct acpi_handle_list *list;
struct acpi_power_managed_device *powered_device;
int i, ret;
if (adev->power.flags.power_resources) {
struct acpi_device_power_state *ps;
int j;

if (!dev || !handle)
return -ENODEV;

ret = acpi_bus_get_device(handle, &acpi_dev);
if (ret || !acpi_dev->power.flags.power_resources)
return -ENODEV;
ps = &adev->power.states[ACPI_STATE_D0];
for (j = 0; j < ps->resources.count; j++) {
acpi_handle rhandle = ps->resources.handles[j];

powered_device = kzalloc(sizeof(*powered_device), GFP_KERNEL);
if (!powered_device)
return -ENOMEM;

powered_device->dev = dev;
powered_device->handle = handle;

list = &acpi_dev->power.states[ACPI_STATE_D0].resources;

for (i = 0; i < list->count; i++) {
ret = __acpi_power_resource_register_device(powered_device,
list->handles[i]);

if (ret) {
acpi_power_resource_unregister_device(dev, handle);
break;
if (add)
acpi_power_add_dependent(rhandle, adev);
else
acpi_power_remove_dependent(rhandle, adev);
}
}

return ret;
}
EXPORT_SYMBOL_GPL(acpi_power_resource_register_device);

/* --------------------------------------------------------------------------
Device Power Management
-------------------------------------------------------------------------- */

/**
* acpi_device_sleep_wake - execute _DSW (Device Sleep Wake) or (deprecated in
Expand Down Expand Up @@ -623,10 +572,6 @@ int acpi_disable_wakeup_device_power(struct acpi_device *dev)
return err;
}

/* --------------------------------------------------------------------------
Device Power Management
-------------------------------------------------------------------------- */

int acpi_power_get_inferred_state(struct acpi_device *device, int *state)
{
int result = 0;
Expand Down Expand Up @@ -725,7 +670,7 @@ static int acpi_power_add(struct acpi_device *device)

resource->device = device;
mutex_init(&resource->resource_lock);
mutex_init(&resource->devices_lock);
INIT_LIST_HEAD(&resource->dependent);
strcpy(resource->name, device->pnp.bus_id);
strcpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME);
strcpy(acpi_device_class(device), ACPI_POWER_CLASS);
Expand Down
Loading

0 comments on commit bc9b640

Please sign in to comment.