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 | |
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>
-rw-r--r-- | Documentation/power/devices.txt | 5 | ||||
-rw-r--r-- | drivers/base/core.c | 6 | ||||
-rw-r--r-- | drivers/base/power/main.c | 57 | ||||
-rw-r--r-- | drivers/base/power/power.h | 23 | ||||
-rw-r--r-- | include/linux/pm.h | 1 |
5 files changed, 40 insertions, 52 deletions
diff --git a/Documentation/power/devices.txt b/Documentation/power/devices.txt index 461e4f1dbec4..421e7d00ffd0 100644 --- a/Documentation/power/devices.txt +++ b/Documentation/power/devices.txt | |||
@@ -196,6 +196,11 @@ its parent; and can't be removed or suspended after that parent. | |||
196 | 196 | ||
197 | The policy is that the device tree should match hardware bus topology. | 197 | The policy is that the device tree should match hardware bus topology. |
198 | (Or at least the control bus, for devices which use multiple busses.) | 198 | (Or at least the control bus, for devices which use multiple busses.) |
199 | In particular, this means that a device registration may fail if the parent of | ||
200 | the device is suspending (ie. has been chosen by the PM core as the next | ||
201 | device to suspend) or has already suspended, as well as after all of the other | ||
202 | devices have been suspended. Device drivers must be prepared to cope with such | ||
203 | situations. | ||
199 | 204 | ||
200 | 205 | ||
201 | Suspending Devices | 206 | Suspending Devices |
diff --git a/drivers/base/core.c b/drivers/base/core.c index 24198ad01976..79848e6c5db5 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c | |||
@@ -820,7 +820,11 @@ int device_add(struct device *dev) | |||
820 | error = dpm_sysfs_add(dev); | 820 | error = dpm_sysfs_add(dev); |
821 | if (error) | 821 | if (error) |
822 | goto PMError; | 822 | goto PMError; |
823 | device_pm_add(dev); | 823 | error = device_pm_add(dev); |
824 | if (error) { | ||
825 | dpm_sysfs_remove(dev); | ||
826 | goto PMError; | ||
827 | } | ||
824 | error = bus_add_device(dev); | 828 | error = bus_add_device(dev); |
825 | if (error) | 829 | if (error) |
826 | goto BusError; | 830 | goto BusError; |
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 | ||
diff --git a/include/linux/pm.h b/include/linux/pm.h index 015b735811b4..e6b9f29e27d7 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h | |||
@@ -183,6 +183,7 @@ typedef struct pm_message { | |||
183 | struct dev_pm_info { | 183 | struct dev_pm_info { |
184 | pm_message_t power_state; | 184 | pm_message_t power_state; |
185 | unsigned can_wakeup:1; | 185 | unsigned can_wakeup:1; |
186 | bool sleeping:1; /* Owned by the PM core */ | ||
186 | #ifdef CONFIG_PM_SLEEP | 187 | #ifdef CONFIG_PM_SLEEP |
187 | unsigned should_wakeup:1; | 188 | unsigned should_wakeup:1; |
188 | struct list_head entry; | 189 | struct list_head entry; |