ACPI / PM: Rework the handling of devices depending on power resources
authorRafael J. Wysocki <rafael.j.wysocki@intel.com>
Thu, 17 Jan 2013 13:11:05 +0000 (14:11 +0100)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Thu, 17 Jan 2013 13:11:05 +0000 (14:11 +0100)
Commit 0090def6 (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 0090def6 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>
drivers/acpi/device_pm.c
drivers/acpi/internal.h
drivers/acpi/power.c
drivers/acpi/scan.c
drivers/ata/libata-acpi.c
drivers/pci/pci-acpi.c
include/acpi/acpi_bus.h

index 995019063f646fbff57a08897d2172cf6e0b5a9e..8be4b29e38aa7310ce5bd0be6326ee9da7eff13b 100644 (file)
@@ -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);
index e050254ae143cde4cf96d828edf43ab4ab851089..b79b4258bd6b382f311afb9f7651536c55555a9d 100644 (file)
@@ -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);
index 6e7b9d5238124586ca10a000726472ff29cbc191..659386c4f0cbe6a14eca31a8a7e74a285186a5f6 100644 (file)
@@ -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;
@@ -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)
@@ -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)
@@ -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;
 }
@@ -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
@@ -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;
@@ -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);
index 7d164a966b0dd0a54ac12f816fbf5f81ec11bef7..c6d60910e8a80f444b44873f1feb69262d329c00 100644 (file)
@@ -633,6 +633,7 @@ static int acpi_device_register(struct acpi_device *device)
        INIT_LIST_HEAD(&device->wakeup_list);
        INIT_LIST_HEAD(&device->physical_node_list);
        mutex_init(&device->physical_node_lock);
+       INIT_LIST_HEAD(&device->power_dependent);
 
        new_bus_id = kzalloc(sizeof(struct acpi_device_bus_id), GFP_KERNEL);
        if (!new_bus_id) {
@@ -706,8 +707,14 @@ static void acpi_device_unregister(struct acpi_device *device)
 
        acpi_detach_data(device->handle, acpi_bus_data_handler);
 
+       acpi_power_add_remove_device(device, false);
        acpi_device_remove_files(device);
        device_unregister(&device->dev);
+       /*
+        * Drop the reference counts of all power resources the device depends
+        * on and turn off the ones that have no more references.
+        */
+       acpi_power_transition(device, ACPI_STATE_D3_COLD);
 }
 
 /* --------------------------------------------------------------------------
@@ -1441,6 +1448,7 @@ static int acpi_add_single_object(struct acpi_device **child,
 
 end:
        if (!result) {
+               acpi_power_add_remove_device(device, true);
                acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
                ACPI_DEBUG_PRINT((ACPI_DB_INFO,
                        "Adding %s [%s] parent %s\n", dev_name(&device->dev),
index ef01ac07502e54625cbf02b7ae7ff11322f5c245..6fc67f7efb224c64d076cf3679bd8878e8d278a1 100644 (file)
@@ -1029,30 +1029,20 @@ static void ata_acpi_register_power_resource(struct ata_device *dev)
 {
        struct scsi_device *sdev = dev->sdev;
        acpi_handle handle;
-       struct device *device;
 
        handle = ata_dev_acpi_handle(dev);
-       if (!handle)
-               return;
-
-       device = &sdev->sdev_gendev;
-
-       acpi_power_resource_register_device(device, handle);
+       if (handle)
+               acpi_dev_pm_remove_dependent(handle, &sdev->sdev_gendev);
 }
 
 static void ata_acpi_unregister_power_resource(struct ata_device *dev)
 {
        struct scsi_device *sdev = dev->sdev;
        acpi_handle handle;
-       struct device *device;
 
        handle = ata_dev_acpi_handle(dev);
-       if (!handle)
-               return;
-
-       device = &sdev->sdev_gendev;
-
-       acpi_power_resource_unregister_device(device, handle);
+       if (handle)
+               acpi_dev_pm_remove_dependent(handle, &sdev->sdev_gendev);
 }
 
 void ata_acpi_bind(struct ata_device *dev)
index 42736e213f25aa0e119f25262945ba6ad8c58e44..e407c61559ca0f09c452b9f130a07ac93ad3dba6 100644 (file)
@@ -345,7 +345,6 @@ static void pci_acpi_setup(struct device *dev)
                acpi_pci_irq_add_prt(handle, pci_domain_nr(pci_dev->bus), bus);
        }
 
-       acpi_power_resource_register_device(dev, handle);
        if (acpi_bus_get_device(handle, &adev) || !adev->wakeup.flags.valid)
                return;
 
@@ -368,7 +367,6 @@ static void pci_acpi_cleanup(struct device *dev)
                device_set_run_wake(dev, false);
                pci_acpi_remove_pm_notifier(adev);
        }
-       acpi_power_resource_unregister_device(dev, handle);
 
        if (pci_dev->subordinate)
                acpi_pci_irq_del_prt(pci_domain_nr(pci_dev->bus),
index 567851b4f04309b3f23aed40b11af65b31e5ce86..29a1badfca551a0c6476b441ef2d6d1c71f73e10 100644 (file)
@@ -279,6 +279,7 @@ struct acpi_device {
        struct list_head physical_node_list;
        struct mutex physical_node_lock;
        DECLARE_BITMAP(physical_node_id_bitmap, ACPI_MAX_PHYSICAL_NODE);
+       struct list_head power_dependent;
 };
 
 static inline void *acpi_driver_data(struct acpi_device *d)
@@ -334,8 +335,6 @@ int acpi_device_set_power(struct acpi_device *device, int state);
 int acpi_bus_update_power(acpi_handle handle, int *state_p);
 bool acpi_bus_power_manageable(acpi_handle handle);
 bool acpi_bus_can_wakeup(acpi_handle handle);
-int acpi_power_resource_register_device(struct device *dev, acpi_handle handle);
-void acpi_power_resource_unregister_device(struct device *dev, acpi_handle handle);
 #ifdef CONFIG_ACPI_PROC_EVENT
 int acpi_bus_generate_proc_event(struct acpi_device *device, u8 type, int data);
 int acpi_bus_generate_proc_event4(const char *class, const char *bid, u8 type, int data);
@@ -414,6 +413,8 @@ acpi_status acpi_remove_pm_notifier(struct acpi_device *adev,
 int acpi_device_power_state(struct device *dev, struct acpi_device *adev,
                            u32 target_state, int d_max_in, int *d_min_p);
 int acpi_pm_device_sleep_state(struct device *, int *, int);
+void acpi_dev_pm_add_dependent(acpi_handle handle, struct device *depdev);
+void acpi_dev_pm_remove_dependent(acpi_handle handle, struct device *depdev);
 #else
 static inline acpi_status acpi_add_pm_notifier(struct acpi_device *adev,
                                               acpi_notify_handler handler,
@@ -443,6 +444,10 @@ static inline int acpi_pm_device_sleep_state(struct device *d, int *p, int m)
 {
        return __acpi_device_power_state(m, p);
 }
+static inline void acpi_dev_pm_add_dependent(acpi_handle handle,
+                                            struct device *depdev) {}
+static inline void acpi_dev_pm_remove_dependent(acpi_handle handle,
+                                               struct device *depdev) {}
 #endif
 
 #ifdef CONFIG_PM_RUNTIME