aboutsummaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--Documentation/power/devices.txt5
-rw-r--r--drivers/base/core.c6
-rw-r--r--drivers/base/power/main.c57
-rw-r--r--drivers/base/power/power.h23
-rw-r--r--include/linux/pm.h1
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
197The policy is that the device tree should match hardware bus topology. 197The 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.)
199In particular, this means that a device registration may fail if the parent of
200the device is suspending (ie. has been chosen by the PM core as the next
201device to suspend) or has already suspended, as well as after all of the other
202devices have been suspended. Device drivers must be prepared to cope with such
203situations.
199 204
200 205
201Suspending Devices 206Suspending 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
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
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 {
183struct dev_pm_info { 183struct 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;