diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2008-06-11 16:03:10 -0400 |
---|---|---|
committer | Andi Kleen <andi@basil.nowhere.org> | 2008-07-16 17:27:02 -0400 |
commit | d20a4dca47d2cd027ed58a13f91b424affd1f449 (patch) | |
tree | e84092f7e9dad9857a6997317bcc9399639a10a9 /drivers/char | |
parent | 741438b5008791327d2183cebcd7ac9cfad64ec6 (diff) |
APM emulation: Notify about all suspend events, not just APM invoked ones (v2)
This revamps the apm-emulation code to get suspend notifications
regardless of what way pm_suspend() was invoked, whether via the
apm ioctl or via /sys/power/state. Also do some code cleanup and
add comments while at it.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Len Brown <len.brown@intel.com>
Signed-off-by: Andi Kleen <ak@linux.intel.com>
Diffstat (limited to 'drivers/char')
-rw-r--r-- | drivers/char/apm-emulation.c | 346 |
1 files changed, 207 insertions, 139 deletions
diff --git a/drivers/char/apm-emulation.c b/drivers/char/apm-emulation.c index da8a1658a273..aaca40283be9 100644 --- a/drivers/char/apm-emulation.c +++ b/drivers/char/apm-emulation.c | |||
@@ -59,6 +59,55 @@ struct apm_queue { | |||
59 | }; | 59 | }; |
60 | 60 | ||
61 | /* | 61 | /* |
62 | * thread states (for threads using a writable /dev/apm_bios fd): | ||
63 | * | ||
64 | * SUSPEND_NONE: nothing happening | ||
65 | * SUSPEND_PENDING: suspend event queued for thread and pending to be read | ||
66 | * SUSPEND_READ: suspend event read, pending acknowledgement | ||
67 | * SUSPEND_ACKED: acknowledgement received from thread (via ioctl), | ||
68 | * waiting for resume | ||
69 | * SUSPEND_ACKTO: acknowledgement timeout | ||
70 | * SUSPEND_DONE: thread had acked suspend and is now notified of | ||
71 | * resume | ||
72 | * | ||
73 | * SUSPEND_WAIT: this thread invoked suspend and is waiting for resume | ||
74 | * | ||
75 | * A thread migrates in one of three paths: | ||
76 | * NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE | ||
77 | * -6-> ACKTO -7-> NONE | ||
78 | * NONE -8-> WAIT -9-> NONE | ||
79 | * | ||
80 | * While in PENDING or READ, the thread is accounted for in the | ||
81 | * suspend_acks_pending counter. | ||
82 | * | ||
83 | * The transitions are invoked as follows: | ||
84 | * 1: suspend event is signalled from the core PM code | ||
85 | * 2: the suspend event is read from the fd by the userspace thread | ||
86 | * 3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack) | ||
87 | * 4: core PM code signals that we have resumed | ||
88 | * 5: APM_IOC_SUSPEND ioctl returns | ||
89 | * | ||
90 | * 6: the notifier invoked from the core PM code timed out waiting | ||
91 | * for all relevant threds to enter ACKED state and puts those | ||
92 | * that haven't into ACKTO | ||
93 | * 7: those threads issue APM_IOC_SUSPEND ioctl too late, | ||
94 | * get an error | ||
95 | * | ||
96 | * 8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend), | ||
97 | * ioctl code invokes pm_suspend() | ||
98 | * 9: pm_suspend() returns indicating resume | ||
99 | */ | ||
100 | enum apm_suspend_state { | ||
101 | SUSPEND_NONE, | ||
102 | SUSPEND_PENDING, | ||
103 | SUSPEND_READ, | ||
104 | SUSPEND_ACKED, | ||
105 | SUSPEND_ACKTO, | ||
106 | SUSPEND_WAIT, | ||
107 | SUSPEND_DONE, | ||
108 | }; | ||
109 | |||
110 | /* | ||
62 | * The per-file APM data | 111 | * The per-file APM data |
63 | */ | 112 | */ |
64 | struct apm_user { | 113 | struct apm_user { |
@@ -69,13 +118,7 @@ struct apm_user { | |||
69 | unsigned int reader: 1; | 118 | unsigned int reader: 1; |
70 | 119 | ||
71 | int suspend_result; | 120 | int suspend_result; |
72 | unsigned int suspend_state; | 121 | enum apm_suspend_state suspend_state; |
73 | #define SUSPEND_NONE 0 /* no suspend pending */ | ||
74 | #define SUSPEND_PENDING 1 /* suspend pending read */ | ||
75 | #define SUSPEND_READ 2 /* suspend read, pending ack */ | ||
76 | #define SUSPEND_ACKED 3 /* suspend acked */ | ||
77 | #define SUSPEND_WAIT 4 /* waiting for suspend */ | ||
78 | #define SUSPEND_DONE 5 /* suspend completed */ | ||
79 | 122 | ||
80 | struct apm_queue queue; | 123 | struct apm_queue queue; |
81 | }; | 124 | }; |
@@ -83,7 +126,8 @@ struct apm_user { | |||
83 | /* | 126 | /* |
84 | * Local variables | 127 | * Local variables |
85 | */ | 128 | */ |
86 | static int suspends_pending; | 129 | static atomic_t suspend_acks_pending = ATOMIC_INIT(0); |
130 | static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0); | ||
87 | static int apm_disabled; | 131 | static int apm_disabled; |
88 | static struct task_struct *kapmd_tsk; | 132 | static struct task_struct *kapmd_tsk; |
89 | 133 | ||
@@ -166,78 +210,6 @@ static void queue_event(apm_event_t event) | |||
166 | wake_up_interruptible(&apm_waitqueue); | 210 | wake_up_interruptible(&apm_waitqueue); |
167 | } | 211 | } |
168 | 212 | ||
169 | /* | ||
170 | * queue_suspend_event - queue an APM suspend event. | ||
171 | * | ||
172 | * Check that we're in a state where we can suspend. If not, | ||
173 | * return -EBUSY. Otherwise, queue an event to all "writer" | ||
174 | * users. If there are no "writer" users, return '1' to | ||
175 | * indicate that we can immediately suspend. | ||
176 | */ | ||
177 | static int queue_suspend_event(apm_event_t event, struct apm_user *sender) | ||
178 | { | ||
179 | struct apm_user *as; | ||
180 | int ret = 1; | ||
181 | |||
182 | mutex_lock(&state_lock); | ||
183 | down_read(&user_list_lock); | ||
184 | |||
185 | /* | ||
186 | * If a thread is still processing, we can't suspend, so reject | ||
187 | * the request. | ||
188 | */ | ||
189 | list_for_each_entry(as, &apm_user_list, list) { | ||
190 | if (as != sender && as->reader && as->writer && as->suser && | ||
191 | as->suspend_state != SUSPEND_NONE) { | ||
192 | ret = -EBUSY; | ||
193 | goto out; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | list_for_each_entry(as, &apm_user_list, list) { | ||
198 | if (as != sender && as->reader && as->writer && as->suser) { | ||
199 | as->suspend_state = SUSPEND_PENDING; | ||
200 | suspends_pending++; | ||
201 | queue_add_event(&as->queue, event); | ||
202 | ret = 0; | ||
203 | } | ||
204 | } | ||
205 | out: | ||
206 | up_read(&user_list_lock); | ||
207 | mutex_unlock(&state_lock); | ||
208 | wake_up_interruptible(&apm_waitqueue); | ||
209 | return ret; | ||
210 | } | ||
211 | |||
212 | static void apm_suspend(void) | ||
213 | { | ||
214 | struct apm_user *as; | ||
215 | int err = pm_suspend(PM_SUSPEND_MEM); | ||
216 | |||
217 | /* | ||
218 | * Anyone on the APM queues will think we're still suspended. | ||
219 | * Send a message so everyone knows we're now awake again. | ||
220 | */ | ||
221 | queue_event(APM_NORMAL_RESUME); | ||
222 | |||
223 | /* | ||
224 | * Finally, wake up anyone who is sleeping on the suspend. | ||
225 | */ | ||
226 | mutex_lock(&state_lock); | ||
227 | down_read(&user_list_lock); | ||
228 | list_for_each_entry(as, &apm_user_list, list) { | ||
229 | if (as->suspend_state == SUSPEND_WAIT || | ||
230 | as->suspend_state == SUSPEND_ACKED) { | ||
231 | as->suspend_result = err; | ||
232 | as->suspend_state = SUSPEND_DONE; | ||
233 | } | ||
234 | } | ||
235 | up_read(&user_list_lock); | ||
236 | mutex_unlock(&state_lock); | ||
237 | |||
238 | wake_up(&apm_suspend_waitqueue); | ||
239 | } | ||
240 | |||
241 | static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) | 213 | static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) |
242 | { | 214 | { |
243 | struct apm_user *as = fp->private_data; | 215 | struct apm_user *as = fp->private_data; |
@@ -308,25 +280,22 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) | |||
308 | 280 | ||
309 | as->suspend_result = -EINTR; | 281 | as->suspend_result = -EINTR; |
310 | 282 | ||
311 | if (as->suspend_state == SUSPEND_READ) { | 283 | switch (as->suspend_state) { |
312 | int pending; | 284 | case SUSPEND_READ: |
313 | |||
314 | /* | 285 | /* |
315 | * If we read a suspend command from /dev/apm_bios, | 286 | * If we read a suspend command from /dev/apm_bios, |
316 | * then the corresponding APM_IOC_SUSPEND ioctl is | 287 | * then the corresponding APM_IOC_SUSPEND ioctl is |
317 | * interpreted as an acknowledge. | 288 | * interpreted as an acknowledge. |
318 | */ | 289 | */ |
319 | as->suspend_state = SUSPEND_ACKED; | 290 | as->suspend_state = SUSPEND_ACKED; |
320 | suspends_pending--; | 291 | atomic_dec(&suspend_acks_pending); |
321 | pending = suspends_pending == 0; | ||
322 | mutex_unlock(&state_lock); | 292 | mutex_unlock(&state_lock); |
323 | 293 | ||
324 | /* | 294 | /* |
325 | * If there are no further acknowledges required, | 295 | * suspend_acks_pending changed, the notifier needs to |
326 | * suspend the system. | 296 | * be woken up for this |
327 | */ | 297 | */ |
328 | if (pending) | 298 | wake_up(&apm_suspend_waitqueue); |
329 | apm_suspend(); | ||
330 | 299 | ||
331 | /* | 300 | /* |
332 | * Wait for the suspend/resume to complete. If there | 301 | * Wait for the suspend/resume to complete. If there |
@@ -342,35 +311,21 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) | |||
342 | * try_to_freeze() in freezer_count() will not trigger | 311 | * try_to_freeze() in freezer_count() will not trigger |
343 | */ | 312 | */ |
344 | freezer_count(); | 313 | freezer_count(); |
345 | } else { | 314 | break; |
315 | case SUSPEND_ACKTO: | ||
316 | as->suspend_result = -ETIMEDOUT; | ||
317 | mutex_unlock(&state_lock); | ||
318 | break; | ||
319 | default: | ||
346 | as->suspend_state = SUSPEND_WAIT; | 320 | as->suspend_state = SUSPEND_WAIT; |
347 | mutex_unlock(&state_lock); | 321 | mutex_unlock(&state_lock); |
348 | 322 | ||
349 | /* | 323 | /* |
350 | * Otherwise it is a request to suspend the system. | 324 | * Otherwise it is a request to suspend the system. |
351 | * Queue an event for all readers, and expect an | 325 | * Just invoke pm_suspend(), we'll handle it from |
352 | * acknowledge from all writers who haven't already | 326 | * there via the notifier. |
353 | * acknowledged. | ||
354 | */ | ||
355 | err = queue_suspend_event(APM_USER_SUSPEND, as); | ||
356 | if (err < 0) { | ||
357 | /* | ||
358 | * Avoid taking the lock here - this | ||
359 | * should be fine. | ||
360 | */ | ||
361 | as->suspend_state = SUSPEND_NONE; | ||
362 | break; | ||
363 | } | ||
364 | |||
365 | if (err > 0) | ||
366 | apm_suspend(); | ||
367 | |||
368 | /* | ||
369 | * Wait for the suspend/resume to complete. If there | ||
370 | * are pending acknowledges, we wait here for them. | ||
371 | */ | 327 | */ |
372 | wait_event_freezable(apm_suspend_waitqueue, | 328 | as->suspend_result = pm_suspend(PM_SUSPEND_MEM); |
373 | as->suspend_state == SUSPEND_DONE); | ||
374 | } | 329 | } |
375 | 330 | ||
376 | mutex_lock(&state_lock); | 331 | mutex_lock(&state_lock); |
@@ -386,7 +341,6 @@ apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) | |||
386 | static int apm_release(struct inode * inode, struct file * filp) | 341 | static int apm_release(struct inode * inode, struct file * filp) |
387 | { | 342 | { |
388 | struct apm_user *as = filp->private_data; | 343 | struct apm_user *as = filp->private_data; |
389 | int pending = 0; | ||
390 | 344 | ||
391 | filp->private_data = NULL; | 345 | filp->private_data = NULL; |
392 | 346 | ||
@@ -396,18 +350,15 @@ static int apm_release(struct inode * inode, struct file * filp) | |||
396 | 350 | ||
397 | /* | 351 | /* |
398 | * We are now unhooked from the chain. As far as new | 352 | * We are now unhooked from the chain. As far as new |
399 | * events are concerned, we no longer exist. However, we | 353 | * events are concerned, we no longer exist. |
400 | * need to balance suspends_pending, which means the | ||
401 | * possibility of sleeping. | ||
402 | */ | 354 | */ |
403 | mutex_lock(&state_lock); | 355 | mutex_lock(&state_lock); |
404 | if (as->suspend_state != SUSPEND_NONE) { | 356 | if (as->suspend_state == SUSPEND_PENDING || |
405 | suspends_pending -= 1; | 357 | as->suspend_state == SUSPEND_READ) |
406 | pending = suspends_pending == 0; | 358 | atomic_dec(&suspend_acks_pending); |
407 | } | ||
408 | mutex_unlock(&state_lock); | 359 | mutex_unlock(&state_lock); |
409 | if (pending) | 360 | |
410 | apm_suspend(); | 361 | wake_up(&apm_suspend_waitqueue); |
411 | 362 | ||
412 | kfree(as); | 363 | kfree(as); |
413 | return 0; | 364 | return 0; |
@@ -545,7 +496,6 @@ static int kapmd(void *arg) | |||
545 | { | 496 | { |
546 | do { | 497 | do { |
547 | apm_event_t event; | 498 | apm_event_t event; |
548 | int ret; | ||
549 | 499 | ||
550 | wait_event_interruptible(kapmd_wait, | 500 | wait_event_interruptible(kapmd_wait, |
551 | !queue_empty(&kapmd_queue) || kthread_should_stop()); | 501 | !queue_empty(&kapmd_queue) || kthread_should_stop()); |
@@ -570,20 +520,13 @@ static int kapmd(void *arg) | |||
570 | 520 | ||
571 | case APM_USER_SUSPEND: | 521 | case APM_USER_SUSPEND: |
572 | case APM_SYS_SUSPEND: | 522 | case APM_SYS_SUSPEND: |
573 | ret = queue_suspend_event(event, NULL); | 523 | pm_suspend(PM_SUSPEND_MEM); |
574 | if (ret < 0) { | ||
575 | /* | ||
576 | * We were busy. Try again in 50ms. | ||
577 | */ | ||
578 | queue_add_event(&kapmd_queue, event); | ||
579 | msleep(50); | ||
580 | } | ||
581 | if (ret > 0) | ||
582 | apm_suspend(); | ||
583 | break; | 524 | break; |
584 | 525 | ||
585 | case APM_CRITICAL_SUSPEND: | 526 | case APM_CRITICAL_SUSPEND: |
586 | apm_suspend(); | 527 | atomic_inc(&userspace_notification_inhibit); |
528 | pm_suspend(PM_SUSPEND_MEM); | ||
529 | atomic_dec(&userspace_notification_inhibit); | ||
587 | break; | 530 | break; |
588 | } | 531 | } |
589 | } while (1); | 532 | } while (1); |
@@ -591,6 +534,120 @@ static int kapmd(void *arg) | |||
591 | return 0; | 534 | return 0; |
592 | } | 535 | } |
593 | 536 | ||
537 | static int apm_suspend_notifier(struct notifier_block *nb, | ||
538 | unsigned long event, | ||
539 | void *dummy) | ||
540 | { | ||
541 | struct apm_user *as; | ||
542 | int err; | ||
543 | |||
544 | /* short-cut emergency suspends */ | ||
545 | if (atomic_read(&userspace_notification_inhibit)) | ||
546 | return NOTIFY_DONE; | ||
547 | |||
548 | switch (event) { | ||
549 | case PM_SUSPEND_PREPARE: | ||
550 | /* | ||
551 | * Queue an event to all "writer" users that we want | ||
552 | * to suspend and need their ack. | ||
553 | */ | ||
554 | mutex_lock(&state_lock); | ||
555 | down_read(&user_list_lock); | ||
556 | |||
557 | list_for_each_entry(as, &apm_user_list, list) { | ||
558 | if (as->suspend_state != SUSPEND_WAIT && as->reader && | ||
559 | as->writer && as->suser) { | ||
560 | as->suspend_state = SUSPEND_PENDING; | ||
561 | atomic_inc(&suspend_acks_pending); | ||
562 | queue_add_event(&as->queue, APM_USER_SUSPEND); | ||
563 | } | ||
564 | } | ||
565 | |||
566 | up_read(&user_list_lock); | ||
567 | mutex_unlock(&state_lock); | ||
568 | wake_up_interruptible(&apm_waitqueue); | ||
569 | |||
570 | /* | ||
571 | * Wait for the the suspend_acks_pending variable to drop to | ||
572 | * zero, meaning everybody acked the suspend event (or the | ||
573 | * process was killed.) | ||
574 | * | ||
575 | * If the app won't answer within a short while we assume it | ||
576 | * locked up and ignore it. | ||
577 | */ | ||
578 | err = wait_event_interruptible_timeout( | ||
579 | apm_suspend_waitqueue, | ||
580 | atomic_read(&suspend_acks_pending) == 0, | ||
581 | 5*HZ); | ||
582 | |||
583 | /* timed out */ | ||
584 | if (err == 0) { | ||
585 | /* | ||
586 | * Move anybody who timed out to "ack timeout" state. | ||
587 | * | ||
588 | * We could time out and the userspace does the ACK | ||
589 | * right after we time out but before we enter the | ||
590 | * locked section here, but that's fine. | ||
591 | */ | ||
592 | mutex_lock(&state_lock); | ||
593 | down_read(&user_list_lock); | ||
594 | list_for_each_entry(as, &apm_user_list, list) { | ||
595 | if (as->suspend_state == SUSPEND_PENDING || | ||
596 | as->suspend_state == SUSPEND_READ) { | ||
597 | as->suspend_state = SUSPEND_ACKTO; | ||
598 | atomic_dec(&suspend_acks_pending); | ||
599 | } | ||
600 | } | ||
601 | up_read(&user_list_lock); | ||
602 | mutex_unlock(&state_lock); | ||
603 | } | ||
604 | |||
605 | /* let suspend proceed */ | ||
606 | if (err >= 0) | ||
607 | return NOTIFY_OK; | ||
608 | |||
609 | /* interrupted by signal */ | ||
610 | return NOTIFY_BAD; | ||
611 | |||
612 | case PM_POST_SUSPEND: | ||
613 | /* | ||
614 | * Anyone on the APM queues will think we're still suspended. | ||
615 | * Send a message so everyone knows we're now awake again. | ||
616 | */ | ||
617 | queue_event(APM_NORMAL_RESUME); | ||
618 | |||
619 | /* | ||
620 | * Finally, wake up anyone who is sleeping on the suspend. | ||
621 | */ | ||
622 | mutex_lock(&state_lock); | ||
623 | down_read(&user_list_lock); | ||
624 | list_for_each_entry(as, &apm_user_list, list) { | ||
625 | if (as->suspend_state == SUSPEND_ACKED) { | ||
626 | /* | ||
627 | * TODO: maybe grab error code, needs core | ||
628 | * changes to push the error to the notifier | ||
629 | * chain (could use the second parameter if | ||
630 | * implemented) | ||
631 | */ | ||
632 | as->suspend_result = 0; | ||
633 | as->suspend_state = SUSPEND_DONE; | ||
634 | } | ||
635 | } | ||
636 | up_read(&user_list_lock); | ||
637 | mutex_unlock(&state_lock); | ||
638 | |||
639 | wake_up(&apm_suspend_waitqueue); | ||
640 | return NOTIFY_OK; | ||
641 | |||
642 | default: | ||
643 | return NOTIFY_DONE; | ||
644 | } | ||
645 | } | ||
646 | |||
647 | static struct notifier_block apm_notif_block = { | ||
648 | .notifier_call = apm_suspend_notifier, | ||
649 | }; | ||
650 | |||
594 | static int __init apm_init(void) | 651 | static int __init apm_init(void) |
595 | { | 652 | { |
596 | int ret; | 653 | int ret; |
@@ -604,7 +661,7 @@ static int __init apm_init(void) | |||
604 | if (IS_ERR(kapmd_tsk)) { | 661 | if (IS_ERR(kapmd_tsk)) { |
605 | ret = PTR_ERR(kapmd_tsk); | 662 | ret = PTR_ERR(kapmd_tsk); |
606 | kapmd_tsk = NULL; | 663 | kapmd_tsk = NULL; |
607 | return ret; | 664 | goto out; |
608 | } | 665 | } |
609 | wake_up_process(kapmd_tsk); | 666 | wake_up_process(kapmd_tsk); |
610 | 667 | ||
@@ -613,16 +670,27 @@ static int __init apm_init(void) | |||
613 | #endif | 670 | #endif |
614 | 671 | ||
615 | ret = misc_register(&apm_device); | 672 | ret = misc_register(&apm_device); |
616 | if (ret != 0) { | 673 | if (ret) |
617 | remove_proc_entry("apm", NULL); | 674 | goto out_stop; |
618 | kthread_stop(kapmd_tsk); | ||
619 | } | ||
620 | 675 | ||
676 | ret = register_pm_notifier(&apm_notif_block); | ||
677 | if (ret) | ||
678 | goto out_unregister; | ||
679 | |||
680 | return 0; | ||
681 | |||
682 | out_unregister: | ||
683 | misc_deregister(&apm_device); | ||
684 | out_stop: | ||
685 | remove_proc_entry("apm", NULL); | ||
686 | kthread_stop(kapmd_tsk); | ||
687 | out: | ||
621 | return ret; | 688 | return ret; |
622 | } | 689 | } |
623 | 690 | ||
624 | static void __exit apm_exit(void) | 691 | static void __exit apm_exit(void) |
625 | { | 692 | { |
693 | unregister_pm_notifier(&apm_notif_block); | ||
626 | misc_deregister(&apm_device); | 694 | misc_deregister(&apm_device); |
627 | remove_proc_entry("apm", NULL); | 695 | remove_proc_entry("apm", NULL); |
628 | 696 | ||