diff options
Diffstat (limited to 'arch/arm/kernel/apm.c')
-rw-r--r-- | arch/arm/kernel/apm.c | 187 |
1 files changed, 131 insertions, 56 deletions
diff --git a/arch/arm/kernel/apm.c b/arch/arm/kernel/apm.c index ecf4f9472d94..a11fb9a40c04 100644 --- a/arch/arm/kernel/apm.c +++ b/arch/arm/kernel/apm.c | |||
@@ -12,7 +12,6 @@ | |||
12 | */ | 12 | */ |
13 | #include <linux/module.h> | 13 | #include <linux/module.h> |
14 | #include <linux/poll.h> | 14 | #include <linux/poll.h> |
15 | #include <linux/timer.h> | ||
16 | #include <linux/slab.h> | 15 | #include <linux/slab.h> |
17 | #include <linux/proc_fs.h> | 16 | #include <linux/proc_fs.h> |
18 | #include <linux/miscdevice.h> | 17 | #include <linux/miscdevice.h> |
@@ -26,6 +25,7 @@ | |||
26 | #include <linux/init.h> | 25 | #include <linux/init.h> |
27 | #include <linux/completion.h> | 26 | #include <linux/completion.h> |
28 | #include <linux/kthread.h> | 27 | #include <linux/kthread.h> |
28 | #include <linux/delay.h> | ||
29 | 29 | ||
30 | #include <asm/apm.h> /* apm_power_info */ | 30 | #include <asm/apm.h> /* apm_power_info */ |
31 | #include <asm/system.h> | 31 | #include <asm/system.h> |
@@ -71,7 +71,8 @@ struct apm_user { | |||
71 | #define SUSPEND_PENDING 1 /* suspend pending read */ | 71 | #define SUSPEND_PENDING 1 /* suspend pending read */ |
72 | #define SUSPEND_READ 2 /* suspend read, pending ack */ | 72 | #define SUSPEND_READ 2 /* suspend read, pending ack */ |
73 | #define SUSPEND_ACKED 3 /* suspend acked */ | 73 | #define SUSPEND_ACKED 3 /* suspend acked */ |
74 | #define SUSPEND_DONE 4 /* suspend completed */ | 74 | #define SUSPEND_WAIT 4 /* waiting for suspend */ |
75 | #define SUSPEND_DONE 5 /* suspend completed */ | ||
75 | 76 | ||
76 | struct apm_queue queue; | 77 | struct apm_queue queue; |
77 | }; | 78 | }; |
@@ -101,6 +102,7 @@ static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait); | |||
101 | static DEFINE_SPINLOCK(kapmd_queue_lock); | 102 | static DEFINE_SPINLOCK(kapmd_queue_lock); |
102 | static struct apm_queue kapmd_queue; | 103 | static struct apm_queue kapmd_queue; |
103 | 104 | ||
105 | static DEFINE_MUTEX(state_lock); | ||
104 | 106 | ||
105 | static const char driver_version[] = "1.13"; /* no spaces */ | 107 | static const char driver_version[] = "1.13"; /* no spaces */ |
106 | 108 | ||
@@ -148,38 +150,60 @@ static void queue_add_event(struct apm_queue *q, apm_event_t event) | |||
148 | q->events[q->event_head] = event; | 150 | q->events[q->event_head] = event; |
149 | } | 151 | } |
150 | 152 | ||
151 | static void queue_event_one_user(struct apm_user *as, apm_event_t event) | 153 | static void queue_event(apm_event_t event) |
152 | { | 154 | { |
153 | if (as->suser && as->writer) { | 155 | struct apm_user *as; |
154 | switch (event) { | ||
155 | case APM_SYS_SUSPEND: | ||
156 | case APM_USER_SUSPEND: | ||
157 | /* | ||
158 | * If this user already has a suspend pending, | ||
159 | * don't queue another one. | ||
160 | */ | ||
161 | if (as->suspend_state != SUSPEND_NONE) | ||
162 | return; | ||
163 | 156 | ||
164 | as->suspend_state = SUSPEND_PENDING; | 157 | down_read(&user_list_lock); |
165 | suspends_pending++; | 158 | list_for_each_entry(as, &apm_user_list, list) { |
166 | break; | 159 | if (as->reader) |
167 | } | 160 | queue_add_event(&as->queue, event); |
168 | } | 161 | } |
169 | queue_add_event(&as->queue, event); | 162 | up_read(&user_list_lock); |
163 | wake_up_interruptible(&apm_waitqueue); | ||
170 | } | 164 | } |
171 | 165 | ||
172 | static void queue_event(apm_event_t event, struct apm_user *sender) | 166 | /* |
167 | * queue_suspend_event - queue an APM suspend event. | ||
168 | * | ||
169 | * Check that we're in a state where we can suspend. If not, | ||
170 | * return -EBUSY. Otherwise, queue an event to all "writer" | ||
171 | * users. If there are no "writer" users, return '1' to | ||
172 | * indicate that we can immediately suspend. | ||
173 | */ | ||
174 | static int queue_suspend_event(apm_event_t event, struct apm_user *sender) | ||
173 | { | 175 | { |
174 | struct apm_user *as; | 176 | struct apm_user *as; |
177 | int ret = 1; | ||
175 | 178 | ||
179 | mutex_lock(&state_lock); | ||
176 | down_read(&user_list_lock); | 180 | down_read(&user_list_lock); |
181 | |||
182 | /* | ||
183 | * If a thread is still processing, we can't suspend, so reject | ||
184 | * the request. | ||
185 | */ | ||
177 | list_for_each_entry(as, &apm_user_list, list) { | 186 | list_for_each_entry(as, &apm_user_list, list) { |
178 | if (as != sender && as->reader) | 187 | if (as != sender && as->reader && as->writer && as->suser && |
179 | queue_event_one_user(as, event); | 188 | as->suspend_state != SUSPEND_NONE) { |
189 | ret = -EBUSY; | ||
190 | goto out; | ||
191 | } | ||
180 | } | 192 | } |
193 | |||
194 | list_for_each_entry(as, &apm_user_list, list) { | ||
195 | if (as != sender && as->reader && as->writer && as->suser) { | ||
196 | as->suspend_state = SUSPEND_PENDING; | ||
197 | suspends_pending++; | ||
198 | queue_add_event(&as->queue, event); | ||
199 | ret = 0; | ||
200 | } | ||
201 | } | ||
202 | out: | ||
181 | up_read(&user_list_lock); | 203 | up_read(&user_list_lock); |
204 | mutex_unlock(&state_lock); | ||
182 | wake_up_interruptible(&apm_waitqueue); | 205 | wake_up_interruptible(&apm_waitqueue); |
206 | return ret; | ||
183 | } | 207 | } |
184 | 208 | ||
185 | static void apm_suspend(void) | 209 | static void apm_suspend(void) |
@@ -191,17 +215,22 @@ static void apm_suspend(void) | |||
191 | * Anyone on the APM queues will think we're still suspended. | 215 | * Anyone on the APM queues will think we're still suspended. |
192 | * Send a message so everyone knows we're now awake again. | 216 | * Send a message so everyone knows we're now awake again. |
193 | */ | 217 | */ |
194 | queue_event(APM_NORMAL_RESUME, NULL); | 218 | queue_event(APM_NORMAL_RESUME); |
195 | 219 | ||
196 | /* | 220 | /* |
197 | * Finally, wake up anyone who is sleeping on the suspend. | 221 | * Finally, wake up anyone who is sleeping on the suspend. |
198 | */ | 222 | */ |
223 | mutex_lock(&state_lock); | ||
199 | down_read(&user_list_lock); | 224 | down_read(&user_list_lock); |
200 | list_for_each_entry(as, &apm_user_list, list) { | 225 | list_for_each_entry(as, &apm_user_list, list) { |
201 | as->suspend_result = err; | 226 | if (as->suspend_state == SUSPEND_WAIT || |
202 | as->suspend_state = SUSPEND_DONE; | 227 | as->suspend_state == SUSPEND_ACKED) { |
228 | as->suspend_result = err; | ||
229 | as->suspend_state = SUSPEND_DONE; | ||
230 | } | ||
203 | } | 231 | } |
204 | up_read(&user_list_lock); | 232 | up_read(&user_list_lock); |
233 | mutex_unlock(&state_lock); | ||
205 | 234 | ||
206 | wake_up(&apm_suspend_waitqueue); | 235 | wake_up(&apm_suspend_waitqueue); |
207 | } | 236 | } |
@@ -227,8 +256,11 @@ static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t | |||
227 | if (copy_to_user(buf, &event, sizeof(event))) | 256 | if (copy_to_user(buf, &event, sizeof(event))) |
228 | break; | 257 | break; |
229 | 258 | ||
230 | if (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND) | 259 | mutex_lock(&state_lock); |
260 | if (as->suspend_state == SUSPEND_PENDING && | ||
261 | (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)) | ||
231 | as->suspend_state = SUSPEND_READ; | 262 | as->suspend_state = SUSPEND_READ; |
263 | mutex_unlock(&state_lock); | ||
232 | 264 | ||
233 | buf += sizeof(event); | 265 | buf += sizeof(event); |
234 | i -= sizeof(event); | 266 | i -= sizeof(event); |
@@ -270,9 +302,13 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) | |||
270 | 302 | ||
271 | switch (cmd) { | 303 | switch (cmd) { |
272 | case APM_IOC_SUSPEND: | 304 | case APM_IOC_SUSPEND: |
305 | mutex_lock(&state_lock); | ||
306 | |||
273 | as->suspend_result = -EINTR; | 307 | as->suspend_result = -EINTR; |
274 | 308 | ||
275 | if (as->suspend_state == SUSPEND_READ) { | 309 | if (as->suspend_state == SUSPEND_READ) { |
310 | int pending; | ||
311 | |||
276 | /* | 312 | /* |
277 | * If we read a suspend command from /dev/apm_bios, | 313 | * If we read a suspend command from /dev/apm_bios, |
278 | * then the corresponding APM_IOC_SUSPEND ioctl is | 314 | * then the corresponding APM_IOC_SUSPEND ioctl is |
@@ -280,47 +316,73 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) | |||
280 | */ | 316 | */ |
281 | as->suspend_state = SUSPEND_ACKED; | 317 | as->suspend_state = SUSPEND_ACKED; |
282 | suspends_pending--; | 318 | suspends_pending--; |
319 | pending = suspends_pending == 0; | ||
320 | mutex_unlock(&state_lock); | ||
321 | |||
322 | /* | ||
323 | * If there are no further acknowledges required, | ||
324 | * suspend the system. | ||
325 | */ | ||
326 | if (pending) | ||
327 | apm_suspend(); | ||
328 | |||
329 | /* | ||
330 | * Wait for the suspend/resume to complete. If there | ||
331 | * are pending acknowledges, we wait here for them. | ||
332 | * | ||
333 | * Note: we need to ensure that the PM subsystem does | ||
334 | * not kick us out of the wait when it suspends the | ||
335 | * threads. | ||
336 | */ | ||
337 | flags = current->flags; | ||
338 | current->flags |= PF_NOFREEZE; | ||
339 | |||
340 | wait_event(apm_suspend_waitqueue, | ||
341 | as->suspend_state == SUSPEND_DONE); | ||
283 | } else { | 342 | } else { |
343 | as->suspend_state = SUSPEND_WAIT; | ||
344 | mutex_unlock(&state_lock); | ||
345 | |||
284 | /* | 346 | /* |
285 | * Otherwise it is a request to suspend the system. | 347 | * Otherwise it is a request to suspend the system. |
286 | * Queue an event for all readers, and expect an | 348 | * Queue an event for all readers, and expect an |
287 | * acknowledge from all writers who haven't already | 349 | * acknowledge from all writers who haven't already |
288 | * acknowledged. | 350 | * acknowledged. |
289 | */ | 351 | */ |
290 | queue_event(APM_USER_SUSPEND, as); | 352 | err = queue_suspend_event(APM_USER_SUSPEND, as); |
291 | } | 353 | if (err < 0) { |
292 | 354 | /* | |
293 | /* | 355 | * Avoid taking the lock here - this |
294 | * If there are no further acknowledges required, suspend | 356 | * should be fine. |
295 | * the system. | 357 | */ |
296 | */ | 358 | as->suspend_state = SUSPEND_NONE; |
297 | if (suspends_pending == 0) | 359 | break; |
298 | apm_suspend(); | 360 | } |
361 | |||
362 | if (err > 0) | ||
363 | apm_suspend(); | ||
299 | 364 | ||
300 | /* | 365 | /* |
301 | * Wait for the suspend/resume to complete. If there are | 366 | * Wait for the suspend/resume to complete. If there |
302 | * pending acknowledges, we wait here for them. | 367 | * are pending acknowledges, we wait here for them. |
303 | * | 368 | * |
304 | * Note that we need to ensure that the PM subsystem does | 369 | * Note: we need to ensure that the PM subsystem does |
305 | * not kick us out of the wait when it suspends the threads. | 370 | * not kick us out of the wait when it suspends the |
306 | */ | 371 | * threads. |
307 | flags = current->flags; | 372 | */ |
308 | current->flags |= PF_NOFREEZE; | 373 | flags = current->flags; |
374 | current->flags |= PF_NOFREEZE; | ||
309 | 375 | ||
310 | /* | ||
311 | * Note: do not allow a thread which is acking the suspend | ||
312 | * to escape until the resume is complete. | ||
313 | */ | ||
314 | if (as->suspend_state == SUSPEND_ACKED) | ||
315 | wait_event(apm_suspend_waitqueue, | ||
316 | as->suspend_state == SUSPEND_DONE); | ||
317 | else | ||
318 | wait_event_interruptible(apm_suspend_waitqueue, | 376 | wait_event_interruptible(apm_suspend_waitqueue, |
319 | as->suspend_state == SUSPEND_DONE); | 377 | as->suspend_state == SUSPEND_DONE); |
378 | } | ||
320 | 379 | ||
321 | current->flags = flags; | 380 | current->flags = flags; |
381 | |||
382 | mutex_lock(&state_lock); | ||
322 | err = as->suspend_result; | 383 | err = as->suspend_result; |
323 | as->suspend_state = SUSPEND_NONE; | 384 | as->suspend_state = SUSPEND_NONE; |
385 | mutex_unlock(&state_lock); | ||
324 | break; | 386 | break; |
325 | } | 387 | } |
326 | 388 | ||
@@ -330,6 +392,8 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) | |||
330 | static int apm_release(struct inode * inode, struct file * filp) | 392 | static int apm_release(struct inode * inode, struct file * filp) |
331 | { | 393 | { |
332 | struct apm_user *as = filp->private_data; | 394 | struct apm_user *as = filp->private_data; |
395 | int pending = 0; | ||
396 | |||
333 | filp->private_data = NULL; | 397 | filp->private_data = NULL; |
334 | 398 | ||
335 | down_write(&user_list_lock); | 399 | down_write(&user_list_lock); |
@@ -342,11 +406,14 @@ static int apm_release(struct inode * inode, struct file * filp) | |||
342 | * need to balance suspends_pending, which means the | 406 | * need to balance suspends_pending, which means the |
343 | * possibility of sleeping. | 407 | * possibility of sleeping. |
344 | */ | 408 | */ |
409 | mutex_lock(&state_lock); | ||
345 | if (as->suspend_state != SUSPEND_NONE) { | 410 | if (as->suspend_state != SUSPEND_NONE) { |
346 | suspends_pending -= 1; | 411 | suspends_pending -= 1; |
347 | if (suspends_pending == 0) | 412 | pending = suspends_pending == 0; |
348 | apm_suspend(); | ||
349 | } | 413 | } |
414 | mutex_unlock(&state_lock); | ||
415 | if (pending) | ||
416 | apm_suspend(); | ||
350 | 417 | ||
351 | kfree(as); | 418 | kfree(as); |
352 | return 0; | 419 | return 0; |
@@ -470,6 +537,7 @@ static int kapmd(void *arg) | |||
470 | { | 537 | { |
471 | do { | 538 | do { |
472 | apm_event_t event; | 539 | apm_event_t event; |
540 | int ret; | ||
473 | 541 | ||
474 | wait_event_interruptible(kapmd_wait, | 542 | wait_event_interruptible(kapmd_wait, |
475 | !queue_empty(&kapmd_queue) || kthread_should_stop()); | 543 | !queue_empty(&kapmd_queue) || kthread_should_stop()); |
@@ -489,13 +557,20 @@ static int kapmd(void *arg) | |||
489 | 557 | ||
490 | case APM_LOW_BATTERY: | 558 | case APM_LOW_BATTERY: |
491 | case APM_POWER_STATUS_CHANGE: | 559 | case APM_POWER_STATUS_CHANGE: |
492 | queue_event(event, NULL); | 560 | queue_event(event); |
493 | break; | 561 | break; |
494 | 562 | ||
495 | case APM_USER_SUSPEND: | 563 | case APM_USER_SUSPEND: |
496 | case APM_SYS_SUSPEND: | 564 | case APM_SYS_SUSPEND: |
497 | queue_event(event, NULL); | 565 | ret = queue_suspend_event(event, NULL); |
498 | if (suspends_pending == 0) | 566 | if (ret < 0) { |
567 | /* | ||
568 | * We were busy. Try again in 50ms. | ||
569 | */ | ||
570 | queue_add_event(&kapmd_queue, event); | ||
571 | msleep(50); | ||
572 | } | ||
573 | if (ret > 0) | ||
499 | apm_suspend(); | 574 | apm_suspend(); |
500 | break; | 575 | break; |
501 | 576 | ||