Commit d20a4dca47d2cd027ed58a13f91b424affd1f449
Committed by
Andi Kleen
1 parent
741438b500
Exists in
master
and in
7 other branches
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>
Showing 1 changed file with 207 additions and 139 deletions Side-by-side Diff
drivers/char/apm-emulation.c
... | ... | @@ -59,6 +59,55 @@ |
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 | 111 | * The per-file APM data |
63 | 112 | */ |
64 | 113 | struct apm_user { |
... | ... | @@ -69,13 +118,7 @@ |
69 | 118 | unsigned int reader: 1; |
70 | 119 | |
71 | 120 | int suspend_result; |
72 | - unsigned int 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 */ | |
121 | + enum apm_suspend_state suspend_state; | |
79 | 122 | |
80 | 123 | struct apm_queue queue; |
81 | 124 | }; |
... | ... | @@ -83,7 +126,8 @@ |
83 | 126 | /* |
84 | 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 | 131 | static int apm_disabled; |
88 | 132 | static struct task_struct *kapmd_tsk; |
89 | 133 | |
... | ... | @@ -166,78 +210,6 @@ |
166 | 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 | 213 | static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) |
242 | 214 | { |
243 | 215 | struct apm_user *as = fp->private_data; |
244 | 216 | |
245 | 217 | |
246 | 218 | |
... | ... | @@ -308,25 +280,22 @@ |
308 | 280 | |
309 | 281 | as->suspend_result = -EINTR; |
310 | 282 | |
311 | - if (as->suspend_state == SUSPEND_READ) { | |
312 | - int pending; | |
313 | - | |
283 | + switch (as->suspend_state) { | |
284 | + case SUSPEND_READ: | |
314 | 285 | /* |
315 | 286 | * If we read a suspend command from /dev/apm_bios, |
316 | 287 | * then the corresponding APM_IOC_SUSPEND ioctl is |
317 | 288 | * interpreted as an acknowledge. |
318 | 289 | */ |
319 | 290 | as->suspend_state = SUSPEND_ACKED; |
320 | - suspends_pending--; | |
321 | - pending = suspends_pending == 0; | |
291 | + atomic_dec(&suspend_acks_pending); | |
322 | 292 | mutex_unlock(&state_lock); |
323 | 293 | |
324 | 294 | /* |
325 | - * If there are no further acknowledges required, | |
326 | - * suspend the system. | |
295 | + * suspend_acks_pending changed, the notifier needs to | |
296 | + * be woken up for this | |
327 | 297 | */ |
328 | - if (pending) | |
329 | - apm_suspend(); | |
298 | + wake_up(&apm_suspend_waitqueue); | |
330 | 299 | |
331 | 300 | /* |
332 | 301 | * Wait for the suspend/resume to complete. If there |
333 | 302 | |
334 | 303 | |
... | ... | @@ -342,35 +311,21 @@ |
342 | 311 | * try_to_freeze() in freezer_count() will not trigger |
343 | 312 | */ |
344 | 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 | 320 | as->suspend_state = SUSPEND_WAIT; |
347 | 321 | mutex_unlock(&state_lock); |
348 | 322 | |
349 | 323 | /* |
350 | 324 | * Otherwise it is a request to suspend the system. |
351 | - * Queue an event for all readers, and expect an | |
352 | - * acknowledge from all writers who haven't already | |
353 | - * acknowledged. | |
325 | + * Just invoke pm_suspend(), we'll handle it from | |
326 | + * there via the notifier. | |
354 | 327 | */ |
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 | - */ | |
372 | - wait_event_freezable(apm_suspend_waitqueue, | |
373 | - as->suspend_state == SUSPEND_DONE); | |
328 | + as->suspend_result = pm_suspend(PM_SUSPEND_MEM); | |
374 | 329 | } |
375 | 330 | |
376 | 331 | mutex_lock(&state_lock); |
... | ... | @@ -386,7 +341,6 @@ |
386 | 341 | static int apm_release(struct inode * inode, struct file * filp) |
387 | 342 | { |
388 | 343 | struct apm_user *as = filp->private_data; |
389 | - int pending = 0; | |
390 | 344 | |
391 | 345 | filp->private_data = NULL; |
392 | 346 | |
393 | 347 | |
394 | 348 | |
395 | 349 | |
... | ... | @@ -396,19 +350,16 @@ |
396 | 350 | |
397 | 351 | /* |
398 | 352 | * We are now unhooked from the chain. As far as new |
399 | - * events are concerned, we no longer exist. However, we | |
400 | - * need to balance suspends_pending, which means the | |
401 | - * possibility of sleeping. | |
353 | + * events are concerned, we no longer exist. | |
402 | 354 | */ |
403 | 355 | mutex_lock(&state_lock); |
404 | - if (as->suspend_state != SUSPEND_NONE) { | |
405 | - suspends_pending -= 1; | |
406 | - pending = suspends_pending == 0; | |
407 | - } | |
356 | + if (as->suspend_state == SUSPEND_PENDING || | |
357 | + as->suspend_state == SUSPEND_READ) | |
358 | + atomic_dec(&suspend_acks_pending); | |
408 | 359 | mutex_unlock(&state_lock); |
409 | - if (pending) | |
410 | - apm_suspend(); | |
411 | 360 | |
361 | + wake_up(&apm_suspend_waitqueue); | |
362 | + | |
412 | 363 | kfree(as); |
413 | 364 | return 0; |
414 | 365 | } |
... | ... | @@ -545,7 +496,6 @@ |
545 | 496 | { |
546 | 497 | do { |
547 | 498 | apm_event_t event; |
548 | - int ret; | |
549 | 499 | |
550 | 500 | wait_event_interruptible(kapmd_wait, |
551 | 501 | !queue_empty(&kapmd_queue) || kthread_should_stop()); |
552 | 502 | |
... | ... | @@ -570,20 +520,13 @@ |
570 | 520 | |
571 | 521 | case APM_USER_SUSPEND: |
572 | 522 | case APM_SYS_SUSPEND: |
573 | - ret = queue_suspend_event(event, NULL); | |
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(); | |
523 | + pm_suspend(PM_SUSPEND_MEM); | |
583 | 524 | break; |
584 | 525 | |
585 | 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 | 530 | break; |
588 | 531 | } |
589 | 532 | } while (1); |
... | ... | @@ -591,6 +534,120 @@ |
591 | 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 | 651 | static int __init apm_init(void) |
595 | 652 | { |
596 | 653 | int ret; |
... | ... | @@ -604,7 +661,7 @@ |
604 | 661 | if (IS_ERR(kapmd_tsk)) { |
605 | 662 | ret = PTR_ERR(kapmd_tsk); |
606 | 663 | kapmd_tsk = NULL; |
607 | - return ret; | |
664 | + goto out; | |
608 | 665 | } |
609 | 666 | wake_up_process(kapmd_tsk); |
610 | 667 | |
611 | 668 | |
612 | 669 | |
... | ... | @@ -613,16 +670,27 @@ |
613 | 670 | #endif |
614 | 671 | |
615 | 672 | ret = misc_register(&apm_device); |
616 | - if (ret != 0) { | |
617 | - remove_proc_entry("apm", NULL); | |
618 | - kthread_stop(kapmd_tsk); | |
619 | - } | |
673 | + if (ret) | |
674 | + goto out_stop; | |
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 | 688 | return ret; |
622 | 689 | } |
623 | 690 | |
624 | 691 | static void __exit apm_exit(void) |
625 | 692 | { |
693 | + unregister_pm_notifier(&apm_notif_block); | |
626 | 694 | misc_deregister(&apm_device); |
627 | 695 | remove_proc_entry("apm", NULL); |
628 | 696 |