aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
authorRafael J. Wysocki <rjw@sisk.pl>2008-03-11 19:57:22 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2008-04-19 22:10:24 -0400
commit58aca23226a19983571bd3b65167521fc64f5869 (patch)
tree1fd3f54ce5f18dc972b77970289a27a4e4a39bee /drivers/base
parent6bcf19d02a5d7e627fa054f2f10e0a8d830df326 (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')
-rw-r--r--drivers/base/core.c6
-rw-r--r--drivers/base/power/main.c57
-rw-r--r--drivers/base/power/power.h23
3 files changed, 34 insertions, 52 deletions
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
55static DEFINE_MUTEX(dpm_list_mtx); 55static DEFINE_MUTEX(dpm_list_mtx);
56 56
57static DECLARE_RWSEM(pm_sleep_rwsem); 57/* 'true' if all devices have been suspended, protected by dpm_list_mtx */
58static bool all_sleeping;
58 59
59int (*platform_enable_wakeup)(struct device *dev, int is_on); 60int (*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 */
65void device_pm_add(struct device *dev) 66int 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}
108EXPORT_SYMBOL_GPL(device_pm_schedule_removal); 123EXPORT_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 */
116int 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 */
130void 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)
242static void dpm_resume(void) 231static 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}
290EXPORT_SYMBOL_GPL(device_resume); 280EXPORT_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
14extern void device_pm_add(struct device *); 14extern int device_pm_add(struct device *);
15extern void device_pm_remove(struct device *); 15extern void device_pm_remove(struct device *);
16extern int pm_sleep_lock(void);
17extern void pm_sleep_unlock(void);
18 16
19#else /* CONFIG_PM_SLEEP */ 17#else /* CONFIG_PM_SLEEP */
20 18
21 19static inline int device_pm_add(struct device *dev) { return 0; }
22static inline void device_pm_add(struct device *dev) 20static inline void device_pm_remove(struct device *dev) {}
23{
24}
25
26static inline void device_pm_remove(struct device *dev)
27{
28}
29
30static inline int pm_sleep_lock(void)
31{
32 return 0;
33}
34
35static inline void pm_sleep_unlock(void)
36{
37}
38 21
39#endif 22#endif
40 23