diff options
| author | Rafael J. Wysocki <rjw@sisk.pl> | 2008-03-11 19:57:22 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-04-19 22:10:24 -0400 |
| commit | 58aca23226a19983571bd3b65167521fc64f5869 (patch) | |
| tree | 1fd3f54ce5f18dc972b77970289a27a4e4a39bee /drivers/base/power | |
| parent | 6bcf19d02a5d7e627fa054f2f10e0a8d830df326 (diff) | |
PM: Handle device registrations during suspend/resume
Modify the PM core to protect its data structures, specifically the
dpm_active list, from being corrupted if a child of the currently
suspending device is registered concurrently with its ->suspend()
callback. In that case, since the new device (the child) is added
to dpm_active after its parent, the PM core will attempt to
suspend it after the parent, which is wrong.
Introduce a new member of struct dev_pm_info, called 'sleeping',
and use it to check if the parent of the device being added to
dpm_active has been suspended, in which case the device registration
fails. Also, use 'sleeping' for checking if the ordering of devices
on dpm_active is correct.
Introduce variable 'all_sleeping' that will be set to 'true' once all
devices have been suspended and make new device registrations fail
until 'all_sleeping' is reset to 'false', in order to avoid having
unsuspended devices around while the system is going into a sleep state.
Remove pm_sleep_rwsem which is not necessary any more.
Special thanks to Alan Stern for discussions and suggestions that
lead to the creation of this patch.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/base/power')
| -rw-r--r-- | drivers/base/power/main.c | 57 | ||||
| -rw-r--r-- | drivers/base/power/power.h | 23 |
2 files changed, 29 insertions, 51 deletions
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 26de2c0fda80..0e3991a437c6 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c | |||
| @@ -54,7 +54,8 @@ static LIST_HEAD(dpm_destroy); | |||
| 54 | 54 | ||
| 55 | static DEFINE_MUTEX(dpm_list_mtx); | 55 | static DEFINE_MUTEX(dpm_list_mtx); |
| 56 | 56 | ||
| 57 | static DECLARE_RWSEM(pm_sleep_rwsem); | 57 | /* 'true' if all devices have been suspended, protected by dpm_list_mtx */ |
| 58 | static bool all_sleeping; | ||
| 58 | 59 | ||
| 59 | int (*platform_enable_wakeup)(struct device *dev, int is_on); | 60 | int (*platform_enable_wakeup)(struct device *dev, int is_on); |
| 60 | 61 | ||
| @@ -62,14 +63,28 @@ int (*platform_enable_wakeup)(struct device *dev, int is_on); | |||
| 62 | * device_pm_add - add a device to the list of active devices | 63 | * device_pm_add - add a device to the list of active devices |
| 63 | * @dev: Device to be added to the list | 64 | * @dev: Device to be added to the list |
| 64 | */ | 65 | */ |
| 65 | void device_pm_add(struct device *dev) | 66 | int device_pm_add(struct device *dev) |
| 66 | { | 67 | { |
| 68 | int error = 0; | ||
| 69 | |||
| 67 | pr_debug("PM: Adding info for %s:%s\n", | 70 | pr_debug("PM: Adding info for %s:%s\n", |
| 68 | dev->bus ? dev->bus->name : "No Bus", | 71 | dev->bus ? dev->bus->name : "No Bus", |
| 69 | kobject_name(&dev->kobj)); | 72 | kobject_name(&dev->kobj)); |
| 70 | mutex_lock(&dpm_list_mtx); | 73 | mutex_lock(&dpm_list_mtx); |
| 71 | list_add_tail(&dev->power.entry, &dpm_active); | 74 | if ((dev->parent && dev->parent->power.sleeping) || all_sleeping) { |
| 75 | if (dev->parent->power.sleeping) | ||
| 76 | dev_warn(dev, | ||
| 77 | "parent %s is sleeping, will not add\n", | ||
| 78 | dev->parent->bus_id); | ||
| 79 | else | ||
| 80 | dev_warn(dev, "devices are sleeping, will not add\n"); | ||
| 81 | WARN_ON(true); | ||
| 82 | error = -EBUSY; | ||
| 83 | } else { | ||
| 84 | list_add_tail(&dev->power.entry, &dpm_active); | ||
| 85 | } | ||
| 72 | mutex_unlock(&dpm_list_mtx); | 86 | mutex_unlock(&dpm_list_mtx); |
| 87 | return error; | ||
| 73 | } | 88 | } |
| 74 | 89 | ||
| 75 | /** | 90 | /** |
| @@ -107,32 +122,6 @@ void device_pm_schedule_removal(struct device *dev) | |||
| 107 | } | 122 | } |
| 108 | EXPORT_SYMBOL_GPL(device_pm_schedule_removal); | 123 | EXPORT_SYMBOL_GPL(device_pm_schedule_removal); |
| 109 | 124 | ||
| 110 | /** | ||
| 111 | * pm_sleep_lock - mutual exclusion for registration and suspend | ||
| 112 | * | ||
| 113 | * Returns 0 if no suspend is underway and device registration | ||
| 114 | * may proceed, otherwise -EBUSY. | ||
| 115 | */ | ||
| 116 | int pm_sleep_lock(void) | ||
| 117 | { | ||
| 118 | if (down_read_trylock(&pm_sleep_rwsem)) | ||
| 119 | return 0; | ||
| 120 | |||
| 121 | return -EBUSY; | ||
| 122 | } | ||
| 123 | |||
| 124 | /** | ||
| 125 | * pm_sleep_unlock - mutual exclusion for registration and suspend | ||
| 126 | * | ||
| 127 | * This routine undoes the effect of device_pm_add_lock | ||
| 128 | * when a device's registration is complete. | ||
| 129 | */ | ||
| 130 | void pm_sleep_unlock(void) | ||
| 131 | { | ||
| 132 | up_read(&pm_sleep_rwsem); | ||
| 133 | } | ||
| 134 | |||
| 135 | |||
| 136 | /*------------------------- Resume routines -------------------------*/ | 125 | /*------------------------- Resume routines -------------------------*/ |
| 137 | 126 | ||
| 138 | /** | 127 | /** |
| @@ -242,11 +231,13 @@ static int resume_device(struct device *dev) | |||
| 242 | static void dpm_resume(void) | 231 | static void dpm_resume(void) |
| 243 | { | 232 | { |
| 244 | mutex_lock(&dpm_list_mtx); | 233 | mutex_lock(&dpm_list_mtx); |
| 234 | all_sleeping = false; | ||
| 245 | while(!list_empty(&dpm_off)) { | 235 | while(!list_empty(&dpm_off)) { |
| 246 | struct list_head *entry = dpm_off.next; | 236 | struct list_head *entry = dpm_off.next; |
| 247 | struct device *dev = to_device(entry); | 237 | struct device *dev = to_device(entry); |
| 248 | 238 | ||
| 249 | list_move_tail(entry, &dpm_active); | 239 | list_move_tail(entry, &dpm_active); |
| 240 | dev->power.sleeping = false; | ||
| 250 | mutex_unlock(&dpm_list_mtx); | 241 | mutex_unlock(&dpm_list_mtx); |
| 251 | resume_device(dev); | 242 | resume_device(dev); |
| 252 | mutex_lock(&dpm_list_mtx); | 243 | mutex_lock(&dpm_list_mtx); |
| @@ -285,7 +276,6 @@ void device_resume(void) | |||
| 285 | might_sleep(); | 276 | might_sleep(); |
| 286 | dpm_resume(); | 277 | dpm_resume(); |
| 287 | unregister_dropped_devices(); | 278 | unregister_dropped_devices(); |
| 288 | up_write(&pm_sleep_rwsem); | ||
| 289 | } | 279 | } |
| 290 | EXPORT_SYMBOL_GPL(device_resume); | 280 | EXPORT_SYMBOL_GPL(device_resume); |
| 291 | 281 | ||
| @@ -421,6 +411,9 @@ static int dpm_suspend(pm_message_t state) | |||
| 421 | struct list_head *entry = dpm_active.prev; | 411 | struct list_head *entry = dpm_active.prev; |
| 422 | struct device *dev = to_device(entry); | 412 | struct device *dev = to_device(entry); |
| 423 | 413 | ||
| 414 | WARN_ON(dev->parent && dev->parent->power.sleeping); | ||
| 415 | |||
| 416 | dev->power.sleeping = true; | ||
| 424 | mutex_unlock(&dpm_list_mtx); | 417 | mutex_unlock(&dpm_list_mtx); |
| 425 | error = suspend_device(dev, state); | 418 | error = suspend_device(dev, state); |
| 426 | mutex_lock(&dpm_list_mtx); | 419 | mutex_lock(&dpm_list_mtx); |
| @@ -432,11 +425,14 @@ static int dpm_suspend(pm_message_t state) | |||
| 432 | (error == -EAGAIN ? | 425 | (error == -EAGAIN ? |
| 433 | " (please convert to suspend_late)" : | 426 | " (please convert to suspend_late)" : |
| 434 | "")); | 427 | "")); |
| 428 | dev->power.sleeping = false; | ||
| 435 | break; | 429 | break; |
| 436 | } | 430 | } |
| 437 | if (!list_empty(&dev->power.entry)) | 431 | if (!list_empty(&dev->power.entry)) |
| 438 | list_move(&dev->power.entry, &dpm_off); | 432 | list_move(&dev->power.entry, &dpm_off); |
| 439 | } | 433 | } |
| 434 | if (!error) | ||
| 435 | all_sleeping = true; | ||
| 440 | mutex_unlock(&dpm_list_mtx); | 436 | mutex_unlock(&dpm_list_mtx); |
| 441 | 437 | ||
| 442 | return error; | 438 | return error; |
| @@ -454,7 +450,6 @@ int device_suspend(pm_message_t state) | |||
| 454 | int error; | 450 | int error; |
| 455 | 451 | ||
| 456 | might_sleep(); | 452 | might_sleep(); |
| 457 | down_write(&pm_sleep_rwsem); | ||
| 458 | error = dpm_suspend(state); | 453 | error = dpm_suspend(state); |
| 459 | if (error) | 454 | if (error) |
| 460 | device_resume(); | 455 | device_resume(); |
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index e32d3bdb92c1..a6894f2a4b99 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h | |||
| @@ -11,30 +11,13 @@ static inline struct device *to_device(struct list_head *entry) | |||
| 11 | return container_of(entry, struct device, power.entry); | 11 | return container_of(entry, struct device, power.entry); |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | extern void device_pm_add(struct device *); | 14 | extern int device_pm_add(struct device *); |
| 15 | extern void device_pm_remove(struct device *); | 15 | extern void device_pm_remove(struct device *); |
| 16 | extern int pm_sleep_lock(void); | ||
| 17 | extern void pm_sleep_unlock(void); | ||
| 18 | 16 | ||
| 19 | #else /* CONFIG_PM_SLEEP */ | 17 | #else /* CONFIG_PM_SLEEP */ |
| 20 | 18 | ||
| 21 | 19 | static inline int device_pm_add(struct device *dev) { return 0; } | |
| 22 | static inline void device_pm_add(struct device *dev) | 20 | static inline void device_pm_remove(struct device *dev) {} |
| 23 | { | ||
| 24 | } | ||
| 25 | |||
| 26 | static inline void device_pm_remove(struct device *dev) | ||
| 27 | { | ||
| 28 | } | ||
| 29 | |||
| 30 | static inline int pm_sleep_lock(void) | ||
| 31 | { | ||
| 32 | return 0; | ||
| 33 | } | ||
| 34 | |||
| 35 | static inline void pm_sleep_unlock(void) | ||
| 36 | { | ||
| 37 | } | ||
| 38 | 21 | ||
| 39 | #endif | 22 | #endif |
| 40 | 23 | ||
