aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@osdl.org>2006-06-24 17:50:29 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2006-09-26 00:08:36 -0400
commit7c8265f51073bc8632a99de78d5fd19117ed78b7 (patch)
tree85efa2114f3765c98236152ca46d783dc1bd7d5b
parentceeee1fb2897651b434547eb26d93e6d2ff5a1a5 (diff)
Suspend infrastructure cleanup and extension
Allow devices to participate in the suspend process more intimately, in particular, allow the final phase (with interrupts disabled) to also be open to normal devices, not just system devices. Also, allow classes to participate in device suspend. Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/base/power/resume.c28
-rw-r--r--drivers/base/power/suspend.c122
-rw-r--r--include/linux/device.h11
-rw-r--r--include/linux/pm.h1
-rw-r--r--kernel/power/main.c4
5 files changed, 130 insertions, 36 deletions
diff --git a/drivers/base/power/resume.c b/drivers/base/power/resume.c
index 826093ef4c7e..48e3d49d7b65 100644
--- a/drivers/base/power/resume.c
+++ b/drivers/base/power/resume.c
@@ -38,13 +38,35 @@ int resume_device(struct device * dev)
38 dev_dbg(dev,"resuming\n"); 38 dev_dbg(dev,"resuming\n");
39 error = dev->bus->resume(dev); 39 error = dev->bus->resume(dev);
40 } 40 }
41 if (dev->class && dev->class->resume) {
42 dev_dbg(dev,"class resume\n");
43 error = dev->class->resume(dev);
44 }
41 up(&dev->sem); 45 up(&dev->sem);
42 TRACE_RESUME(error); 46 TRACE_RESUME(error);
43 return error; 47 return error;
44} 48}
45 49
46 50
51static int resume_device_early(struct device * dev)
52{
53 int error = 0;
47 54
55 TRACE_DEVICE(dev);
56 TRACE_RESUME(0);
57 if (dev->bus && dev->bus->resume_early) {
58 dev_dbg(dev,"EARLY resume\n");
59 error = dev->bus->resume_early(dev);
60 }
61 TRACE_RESUME(error);
62 return error;
63}
64
65/*
66 * Resume the devices that have either not gone through
67 * the late suspend, or that did go through it but also
68 * went through the early resume
69 */
48void dpm_resume(void) 70void dpm_resume(void)
49{ 71{
50 down(&dpm_list_sem); 72 down(&dpm_list_sem);
@@ -99,10 +121,8 @@ void dpm_power_up(void)
99 struct list_head * entry = dpm_off_irq.next; 121 struct list_head * entry = dpm_off_irq.next;
100 struct device * dev = to_device(entry); 122 struct device * dev = to_device(entry);
101 123
102 get_device(dev); 124 list_move_tail(entry, &dpm_off);
103 list_move_tail(entry, &dpm_active); 125 resume_device_early(dev);
104 resume_device(dev);
105 put_device(dev);
106 } 126 }
107} 127}
108 128
diff --git a/drivers/base/power/suspend.c b/drivers/base/power/suspend.c
index 69509e02f703..10e8032aee1a 100644
--- a/drivers/base/power/suspend.c
+++ b/drivers/base/power/suspend.c
@@ -65,7 +65,19 @@ int suspend_device(struct device * dev, pm_message_t state)
65 65
66 dev->power.prev_state = dev->power.power_state; 66 dev->power.prev_state = dev->power.power_state;
67 67
68 if (dev->bus && dev->bus->suspend && !dev->power.power_state.event) { 68 if (dev->class && dev->class->suspend && !dev->power.power_state.event) {
69 dev_dbg(dev, "class %s%s\n",
70 suspend_verb(state.event),
71 ((state.event == PM_EVENT_SUSPEND)
72 && device_may_wakeup(dev))
73 ? ", may wakeup"
74 : ""
75 );
76 error = dev->class->suspend(dev, state);
77 suspend_report_result(dev->class->suspend, error);
78 }
79
80 if (!error && dev->bus && dev->bus->suspend && !dev->power.power_state.event) {
69 dev_dbg(dev, "%s%s\n", 81 dev_dbg(dev, "%s%s\n",
70 suspend_verb(state.event), 82 suspend_verb(state.event),
71 ((state.event == PM_EVENT_SUSPEND) 83 ((state.event == PM_EVENT_SUSPEND)
@@ -81,15 +93,74 @@ int suspend_device(struct device * dev, pm_message_t state)
81} 93}
82 94
83 95
96/*
97 * This is called with interrupts off, only a single CPU
98 * running. We can't do down() on a semaphore (and we don't
99 * need the protection)
100 */
101static int suspend_device_late(struct device *dev, pm_message_t state)
102{
103 int error = 0;
104
105 if (dev->power.power_state.event) {
106 dev_dbg(dev, "PM: suspend_late %d-->%d\n",
107 dev->power.power_state.event, state.event);
108 }
109
110 if (dev->bus && dev->bus->suspend_late && !dev->power.power_state.event) {
111 dev_dbg(dev, "LATE %s%s\n",
112 suspend_verb(state.event),
113 ((state.event == PM_EVENT_SUSPEND)
114 && device_may_wakeup(dev))
115 ? ", may wakeup"
116 : ""
117 );
118 error = dev->bus->suspend_late(dev, state);
119 suspend_report_result(dev->bus->suspend_late, error);
120 }
121 return error;
122}
123
124/**
125 * device_prepare_suspend - save state and prepare to suspend
126 *
127 * NOTE! Devices cannot detach at this point - not only do we
128 * hold the device list semaphores over the whole prepare, but
129 * the whole point is to do non-invasive preparatory work, not
130 * the actual suspend.
131 */
132int device_prepare_suspend(pm_message_t state)
133{
134 int error = 0;
135 struct device * dev;
136
137 down(&dpm_sem);
138 down(&dpm_list_sem);
139 list_for_each_entry_reverse(dev, &dpm_active, power.entry) {
140 if (!dev->bus || !dev->bus->suspend_prepare)
141 continue;
142 error = dev->bus->suspend_prepare(dev, state);
143 if (error)
144 break;
145 }
146 up(&dpm_list_sem);
147 up(&dpm_sem);
148 return error;
149}
150
84/** 151/**
85 * device_suspend - Save state and stop all devices in system. 152 * device_suspend - Save state and stop all devices in system.
86 * @state: Power state to put each device in. 153 * @state: Power state to put each device in.
87 * 154 *
88 * Walk the dpm_active list, call ->suspend() for each device, and move 155 * Walk the dpm_active list, call ->suspend() for each device, and move
89 * it to dpm_off. 156 * it to the dpm_off list.
90 * Check the return value for each. If it returns 0, then we move the 157 *
91 * the device to the dpm_off list. If it returns -EAGAIN, we move it to 158 * (For historical reasons, if it returns -EAGAIN, that used to mean
92 * the dpm_off_irq list. If we get a different error, try and back out. 159 * that the device would be called again with interrupts disabled.
160 * These days, we use the "suspend_late()" callback for that, so we
161 * print a warning and consider it an error).
162 *
163 * If we get a different error, try and back out.
93 * 164 *
94 * If we hit a failure with any of the devices, call device_resume() 165 * If we hit a failure with any of the devices, call device_resume()
95 * above to bring the suspended devices back to life. 166 * above to bring the suspended devices back to life.
@@ -115,39 +186,27 @@ int device_suspend(pm_message_t state)
115 186
116 /* Check if the device got removed */ 187 /* Check if the device got removed */
117 if (!list_empty(&dev->power.entry)) { 188 if (!list_empty(&dev->power.entry)) {
118 /* Move it to the dpm_off or dpm_off_irq list */ 189 /* Move it to the dpm_off list */
119 if (!error) 190 if (!error)
120 list_move(&dev->power.entry, &dpm_off); 191 list_move(&dev->power.entry, &dpm_off);
121 else if (error == -EAGAIN) {
122 list_move(&dev->power.entry, &dpm_off_irq);
123 error = 0;
124 }
125 } 192 }
126 if (error) 193 if (error)
127 printk(KERN_ERR "Could not suspend device %s: " 194 printk(KERN_ERR "Could not suspend device %s: "
128 "error %d\n", kobject_name(&dev->kobj), error); 195 "error %d%s\n",
196 kobject_name(&dev->kobj), error,
197 error == -EAGAIN ? " (please convert to suspend_late)" : "");
129 put_device(dev); 198 put_device(dev);
130 } 199 }
131 up(&dpm_list_sem); 200 up(&dpm_list_sem);
132 if (error) { 201 if (error)
133 /* we failed... before resuming, bring back devices from
134 * dpm_off_irq list back to main dpm_off list, we do want
135 * to call resume() on them, in case they partially suspended
136 * despite returning -EAGAIN
137 */
138 while (!list_empty(&dpm_off_irq)) {
139 struct list_head * entry = dpm_off_irq.next;
140 list_move(entry, &dpm_off);
141 }
142 dpm_resume(); 202 dpm_resume();
143 } 203
144 up(&dpm_sem); 204 up(&dpm_sem);
145 return error; 205 return error;
146} 206}
147 207
148EXPORT_SYMBOL_GPL(device_suspend); 208EXPORT_SYMBOL_GPL(device_suspend);
149 209
150
151/** 210/**
152 * device_power_down - Shut down special devices. 211 * device_power_down - Shut down special devices.
153 * @state: Power state to enter. 212 * @state: Power state to enter.
@@ -162,14 +221,17 @@ int device_power_down(pm_message_t state)
162 int error = 0; 221 int error = 0;
163 struct device * dev; 222 struct device * dev;
164 223
165 list_for_each_entry_reverse(dev, &dpm_off_irq, power.entry) { 224 while (!list_empty(&dpm_off)) {
166 if ((error = suspend_device(dev, state))) 225 struct list_head * entry = dpm_off.prev;
167 break; 226
227 dev = to_device(entry);
228 error = suspend_device_late(dev, state);
229 if (error)
230 goto Error;
231 list_move(&dev->power.entry, &dpm_off_irq);
168 } 232 }
169 if (error) 233
170 goto Error; 234 error = sysdev_suspend(state);
171 if ((error = sysdev_suspend(state)))
172 goto Error;
173 Done: 235 Done:
174 return error; 236 return error;
175 Error: 237 Error:
diff --git a/include/linux/device.h b/include/linux/device.h
index 8a648cd94fa9..b40be6fca6fa 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -51,8 +51,12 @@ struct bus_type {
51 int (*probe)(struct device * dev); 51 int (*probe)(struct device * dev);
52 int (*remove)(struct device * dev); 52 int (*remove)(struct device * dev);
53 void (*shutdown)(struct device * dev); 53 void (*shutdown)(struct device * dev);
54 int (*suspend)(struct device * dev, pm_message_t state); 54
55 int (*resume)(struct device * dev); 55 int (*suspend_prepare)(struct device * dev, pm_message_t state);
56 int (*suspend)(struct device * dev, pm_message_t state);
57 int (*suspend_late)(struct device * dev, pm_message_t state);
58 int (*resume_early)(struct device * dev);
59 int (*resume)(struct device * dev);
56}; 60};
57 61
58extern int bus_register(struct bus_type * bus); 62extern int bus_register(struct bus_type * bus);
@@ -154,6 +158,9 @@ struct class {
154 158
155 void (*release)(struct class_device *dev); 159 void (*release)(struct class_device *dev);
156 void (*class_release)(struct class *class); 160 void (*class_release)(struct class *class);
161
162 int (*suspend)(struct device *, pm_message_t state);
163 int (*resume)(struct device *);
157}; 164};
158 165
159extern int class_register(struct class *); 166extern int class_register(struct class *);
diff --git a/include/linux/pm.h b/include/linux/pm.h
index 658c1b93d5bb..096fb6f754cf 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -190,6 +190,7 @@ extern void device_resume(void);
190extern suspend_disk_method_t pm_disk_mode; 190extern suspend_disk_method_t pm_disk_mode;
191 191
192extern int device_suspend(pm_message_t state); 192extern int device_suspend(pm_message_t state);
193extern int device_prepare_suspend(pm_message_t state);
193 194
194#define device_set_wakeup_enable(dev,val) \ 195#define device_set_wakeup_enable(dev,val) \
195 ((dev)->power.should_wakeup = !!(val)) 196 ((dev)->power.should_wakeup = !!(val))
diff --git a/kernel/power/main.c b/kernel/power/main.c
index 6d295c776794..0c3ed6ac938e 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -57,6 +57,10 @@ static int suspend_prepare(suspend_state_t state)
57 if (!pm_ops || !pm_ops->enter) 57 if (!pm_ops || !pm_ops->enter)
58 return -EPERM; 58 return -EPERM;
59 59
60 error = device_prepare_suspend(PMSG_SUSPEND);
61 if (error)
62 return error;
63
60 pm_prepare_console(); 64 pm_prepare_console();
61 65
62 disable_nonboot_cpus(); 66 disable_nonboot_cpus();