aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/apm-emulation.c
diff options
context:
space:
mode:
authorJohannes Berg <johannes@sipsolutions.net>2008-06-11 16:03:10 -0400
committerAndi Kleen <andi@basil.nowhere.org>2008-07-16 17:27:02 -0400
commitd20a4dca47d2cd027ed58a13f91b424affd1f449 (patch)
treee84092f7e9dad9857a6997317bcc9399639a10a9 /drivers/char/apm-emulation.c
parent741438b5008791327d2183cebcd7ac9cfad64ec6 (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/apm-emulation.c')
-rw-r--r--drivers/char/apm-emulation.c346
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 */
100enum 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 */
64struct apm_user { 113struct 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 */
86static int suspends_pending; 129static atomic_t suspend_acks_pending = ATOMIC_INIT(0);
130static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0);
87static int apm_disabled; 131static int apm_disabled;
88static struct task_struct *kapmd_tsk; 132static 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 */
177static 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
212static 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
241static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) 213static 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)
386static int apm_release(struct inode * inode, struct file * filp) 341static 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
537static 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
647static struct notifier_block apm_notif_block = {
648 .notifier_call = apm_suspend_notifier,
649};
650
594static int __init apm_init(void) 651static 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
624static void __exit apm_exit(void) 691static 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