aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/apm-emulation.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>2007-02-09 12:08:57 -0500
committerRalf Baechle <ralf@linux-mips.org>2007-02-09 12:08:57 -0500
commit7726942fb15edd46e4fe8ab37f9a99795191e585 (patch)
tree08ce9b114d76f4247b353658bac43b8117609c0f /drivers/char/apm-emulation.c
parent5986a2ec35836a878350c54af4bd91b1de6abc59 (diff)
[APM] Add shared version of APM emulation
Currently ARM and MIPS both have nearly identical copies of the APM emulation code in their arch code. Add yet another copy of it to drivers char and make it selectable through SYS_SUPPORTS_APM_EMULATION. Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'drivers/char/apm-emulation.c')
-rw-r--r--drivers/char/apm-emulation.c672
1 files changed, 672 insertions, 0 deletions
diff --git a/drivers/char/apm-emulation.c b/drivers/char/apm-emulation.c
new file mode 100644
index 000000000000..179c7a3b6e75
--- /dev/null
+++ b/drivers/char/apm-emulation.c
@@ -0,0 +1,672 @@
1/*
2 * bios-less APM driver for ARM Linux
3 * Jamey Hicks <jamey@crl.dec.com>
4 * adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
5 *
6 * APM 1.2 Reference:
7 * Intel Corporation, Microsoft Corporation. Advanced Power Management
8 * (APM) BIOS Interface Specification, Revision 1.2, February 1996.
9 *
10 * [This document is available from Microsoft at:
11 * http://www.microsoft.com/hwdev/busbios/amp_12.htm]
12 */
13#include <linux/module.h>
14#include <linux/poll.h>
15#include <linux/slab.h>
16#include <linux/proc_fs.h>
17#include <linux/miscdevice.h>
18#include <linux/apm_bios.h>
19#include <linux/capability.h>
20#include <linux/sched.h>
21#include <linux/pm.h>
22#include <linux/apm-emulation.h>
23#include <linux/device.h>
24#include <linux/kernel.h>
25#include <linux/list.h>
26#include <linux/init.h>
27#include <linux/completion.h>
28#include <linux/kthread.h>
29#include <linux/delay.h>
30
31#include <asm/system.h>
32
33/*
34 * The apm_bios device is one of the misc char devices.
35 * This is its minor number.
36 */
37#define APM_MINOR_DEV 134
38
39/*
40 * See Documentation/Config.help for the configuration options.
41 *
42 * Various options can be changed at boot time as follows:
43 * (We allow underscores for compatibility with the modules code)
44 * apm=on/off enable/disable APM
45 */
46
47/*
48 * Maximum number of events stored
49 */
50#define APM_MAX_EVENTS 16
51
52struct apm_queue {
53 unsigned int event_head;
54 unsigned int event_tail;
55 apm_event_t events[APM_MAX_EVENTS];
56};
57
58/*
59 * The per-file APM data
60 */
61struct apm_user {
62 struct list_head list;
63
64 unsigned int suser: 1;
65 unsigned int writer: 1;
66 unsigned int reader: 1;
67
68 int suspend_result;
69 unsigned int suspend_state;
70#define SUSPEND_NONE 0 /* no suspend pending */
71#define SUSPEND_PENDING 1 /* suspend pending read */
72#define SUSPEND_READ 2 /* suspend read, pending ack */
73#define SUSPEND_ACKED 3 /* suspend acked */
74#define SUSPEND_WAIT 4 /* waiting for suspend */
75#define SUSPEND_DONE 5 /* suspend completed */
76
77 struct apm_queue queue;
78};
79
80/*
81 * Local variables
82 */
83static int suspends_pending;
84static int apm_disabled;
85static struct task_struct *kapmd_tsk;
86
87static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
88static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
89
90/*
91 * This is a list of everyone who has opened /dev/apm_bios
92 */
93static DECLARE_RWSEM(user_list_lock);
94static LIST_HEAD(apm_user_list);
95
96/*
97 * kapmd info. kapmd provides us a process context to handle
98 * "APM" events within - specifically necessary if we're going
99 * to be suspending the system.
100 */
101static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
102static DEFINE_SPINLOCK(kapmd_queue_lock);
103static struct apm_queue kapmd_queue;
104
105static DEFINE_MUTEX(state_lock);
106
107static const char driver_version[] = "1.13"; /* no spaces */
108
109
110
111/*
112 * Compatibility cruft until the IPAQ people move over to the new
113 * interface.
114 */
115static void __apm_get_power_status(struct apm_power_info *info)
116{
117}
118
119/*
120 * This allows machines to provide their own "apm get power status" function.
121 */
122void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
123EXPORT_SYMBOL(apm_get_power_status);
124
125
126/*
127 * APM event queue management.
128 */
129static inline int queue_empty(struct apm_queue *q)
130{
131 return q->event_head == q->event_tail;
132}
133
134static inline apm_event_t queue_get_event(struct apm_queue *q)
135{
136 q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
137 return q->events[q->event_tail];
138}
139
140static void queue_add_event(struct apm_queue *q, apm_event_t event)
141{
142 q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
143 if (q->event_head == q->event_tail) {
144 static int notified;
145
146 if (notified++ == 0)
147 printk(KERN_ERR "apm: an event queue overflowed\n");
148 q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
149 }
150 q->events[q->event_head] = event;
151}
152
153static void queue_event(apm_event_t event)
154{
155 struct apm_user *as;
156
157 down_read(&user_list_lock);
158 list_for_each_entry(as, &apm_user_list, list) {
159 if (as->reader)
160 queue_add_event(&as->queue, event);
161 }
162 up_read(&user_list_lock);
163 wake_up_interruptible(&apm_waitqueue);
164}
165
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 */
174static int queue_suspend_event(apm_event_t event, struct apm_user *sender)
175{
176 struct apm_user *as;
177 int ret = 1;
178
179 mutex_lock(&state_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 */
186 list_for_each_entry(as, &apm_user_list, list) {
187 if (as != sender && as->reader && as->writer && as->suser &&
188 as->suspend_state != SUSPEND_NONE) {
189 ret = -EBUSY;
190 goto out;
191 }
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:
203 up_read(&user_list_lock);
204 mutex_unlock(&state_lock);
205 wake_up_interruptible(&apm_waitqueue);
206 return ret;
207}
208
209static void apm_suspend(void)
210{
211 struct apm_user *as;
212 int err = pm_suspend(PM_SUSPEND_MEM);
213
214 /*
215 * Anyone on the APM queues will think we're still suspended.
216 * Send a message so everyone knows we're now awake again.
217 */
218 queue_event(APM_NORMAL_RESUME);
219
220 /*
221 * Finally, wake up anyone who is sleeping on the suspend.
222 */
223 mutex_lock(&state_lock);
224 down_read(&user_list_lock);
225 list_for_each_entry(as, &apm_user_list, list) {
226 if (as->suspend_state == SUSPEND_WAIT ||
227 as->suspend_state == SUSPEND_ACKED) {
228 as->suspend_result = err;
229 as->suspend_state = SUSPEND_DONE;
230 }
231 }
232 up_read(&user_list_lock);
233 mutex_unlock(&state_lock);
234
235 wake_up(&apm_suspend_waitqueue);
236}
237
238static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
239{
240 struct apm_user *as = fp->private_data;
241 apm_event_t event;
242 int i = count, ret = 0;
243
244 if (count < sizeof(apm_event_t))
245 return -EINVAL;
246
247 if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
248 return -EAGAIN;
249
250 wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
251
252 while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
253 event = queue_get_event(&as->queue);
254
255 ret = -EFAULT;
256 if (copy_to_user(buf, &event, sizeof(event)))
257 break;
258
259 mutex_lock(&state_lock);
260 if (as->suspend_state == SUSPEND_PENDING &&
261 (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
262 as->suspend_state = SUSPEND_READ;
263 mutex_unlock(&state_lock);
264
265 buf += sizeof(event);
266 i -= sizeof(event);
267 }
268
269 if (i < count)
270 ret = count - i;
271
272 return ret;
273}
274
275static unsigned int apm_poll(struct file *fp, poll_table * wait)
276{
277 struct apm_user *as = fp->private_data;
278
279 poll_wait(fp, &apm_waitqueue, wait);
280 return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
281}
282
283/*
284 * apm_ioctl - handle APM ioctl
285 *
286 * APM_IOC_SUSPEND
287 * This IOCTL is overloaded, and performs two functions. It is used to:
288 * - initiate a suspend
289 * - acknowledge a suspend read from /dev/apm_bios.
290 * Only when everyone who has opened /dev/apm_bios with write permission
291 * has acknowledge does the actual suspend happen.
292 */
293static int
294apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
295{
296 struct apm_user *as = filp->private_data;
297 unsigned long flags;
298 int err = -EINVAL;
299
300 if (!as->suser || !as->writer)
301 return -EPERM;
302
303 switch (cmd) {
304 case APM_IOC_SUSPEND:
305 mutex_lock(&state_lock);
306
307 as->suspend_result = -EINTR;
308
309 if (as->suspend_state == SUSPEND_READ) {
310 int pending;
311
312 /*
313 * If we read a suspend command from /dev/apm_bios,
314 * then the corresponding APM_IOC_SUSPEND ioctl is
315 * interpreted as an acknowledge.
316 */
317 as->suspend_state = SUSPEND_ACKED;
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);
342 } else {
343 as->suspend_state = SUSPEND_WAIT;
344 mutex_unlock(&state_lock);
345
346 /*
347 * Otherwise it is a request to suspend the system.
348 * Queue an event for all readers, and expect an
349 * acknowledge from all writers who haven't already
350 * acknowledged.
351 */
352 err = queue_suspend_event(APM_USER_SUSPEND, as);
353 if (err < 0) {
354 /*
355 * Avoid taking the lock here - this
356 * should be fine.
357 */
358 as->suspend_state = SUSPEND_NONE;
359 break;
360 }
361
362 if (err > 0)
363 apm_suspend();
364
365 /*
366 * Wait for the suspend/resume to complete. If there
367 * are pending acknowledges, we wait here for them.
368 *
369 * Note: we need to ensure that the PM subsystem does
370 * not kick us out of the wait when it suspends the
371 * threads.
372 */
373 flags = current->flags;
374 current->flags |= PF_NOFREEZE;
375
376 wait_event_interruptible(apm_suspend_waitqueue,
377 as->suspend_state == SUSPEND_DONE);
378 }
379
380 current->flags = flags;
381
382 mutex_lock(&state_lock);
383 err = as->suspend_result;
384 as->suspend_state = SUSPEND_NONE;
385 mutex_unlock(&state_lock);
386 break;
387 }
388
389 return err;
390}
391
392static int apm_release(struct inode * inode, struct file * filp)
393{
394 struct apm_user *as = filp->private_data;
395 int pending = 0;
396
397 filp->private_data = NULL;
398
399 down_write(&user_list_lock);
400 list_del(&as->list);
401 up_write(&user_list_lock);
402
403 /*
404 * We are now unhooked from the chain. As far as new
405 * events are concerned, we no longer exist. However, we
406 * need to balance suspends_pending, which means the
407 * possibility of sleeping.
408 */
409 mutex_lock(&state_lock);
410 if (as->suspend_state != SUSPEND_NONE) {
411 suspends_pending -= 1;
412 pending = suspends_pending == 0;
413 }
414 mutex_unlock(&state_lock);
415 if (pending)
416 apm_suspend();
417
418 kfree(as);
419 return 0;
420}
421
422static int apm_open(struct inode * inode, struct file * filp)
423{
424 struct apm_user *as;
425
426 as = kzalloc(sizeof(*as), GFP_KERNEL);
427 if (as) {
428 /*
429 * XXX - this is a tiny bit broken, when we consider BSD
430 * process accounting. If the device is opened by root, we
431 * instantly flag that we used superuser privs. Who knows,
432 * we might close the device immediately without doing a
433 * privileged operation -- cevans
434 */
435 as->suser = capable(CAP_SYS_ADMIN);
436 as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
437 as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
438
439 down_write(&user_list_lock);
440 list_add(&as->list, &apm_user_list);
441 up_write(&user_list_lock);
442
443 filp->private_data = as;
444 }
445
446 return as ? 0 : -ENOMEM;
447}
448
449static struct file_operations apm_bios_fops = {
450 .owner = THIS_MODULE,
451 .read = apm_read,
452 .poll = apm_poll,
453 .ioctl = apm_ioctl,
454 .open = apm_open,
455 .release = apm_release,
456};
457
458static struct miscdevice apm_device = {
459 .minor = APM_MINOR_DEV,
460 .name = "apm_bios",
461 .fops = &apm_bios_fops
462};
463
464
465#ifdef CONFIG_PROC_FS
466/*
467 * Arguments, with symbols from linux/apm_bios.h.
468 *
469 * 0) Linux driver version (this will change if format changes)
470 * 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
471 * 2) APM flags from APM Installation Check (0x00):
472 * bit 0: APM_16_BIT_SUPPORT
473 * bit 1: APM_32_BIT_SUPPORT
474 * bit 2: APM_IDLE_SLOWS_CLOCK
475 * bit 3: APM_BIOS_DISABLED
476 * bit 4: APM_BIOS_DISENGAGED
477 * 3) AC line status
478 * 0x00: Off-line
479 * 0x01: On-line
480 * 0x02: On backup power (BIOS >= 1.1 only)
481 * 0xff: Unknown
482 * 4) Battery status
483 * 0x00: High
484 * 0x01: Low
485 * 0x02: Critical
486 * 0x03: Charging
487 * 0x04: Selected battery not present (BIOS >= 1.2 only)
488 * 0xff: Unknown
489 * 5) Battery flag
490 * bit 0: High
491 * bit 1: Low
492 * bit 2: Critical
493 * bit 3: Charging
494 * bit 7: No system battery
495 * 0xff: Unknown
496 * 6) Remaining battery life (percentage of charge):
497 * 0-100: valid
498 * -1: Unknown
499 * 7) Remaining battery life (time units):
500 * Number of remaining minutes or seconds
501 * -1: Unknown
502 * 8) min = minutes; sec = seconds
503 */
504static int apm_get_info(char *buf, char **start, off_t fpos, int length)
505{
506 struct apm_power_info info;
507 char *units;
508 int ret;
509
510 info.ac_line_status = 0xff;
511 info.battery_status = 0xff;
512 info.battery_flag = 0xff;
513 info.battery_life = -1;
514 info.time = -1;
515 info.units = -1;
516
517 if (apm_get_power_status)
518 apm_get_power_status(&info);
519
520 switch (info.units) {
521 default: units = "?"; break;
522 case 0: units = "min"; break;
523 case 1: units = "sec"; break;
524 }
525
526 ret = sprintf(buf, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
527 driver_version, APM_32_BIT_SUPPORT,
528 info.ac_line_status, info.battery_status,
529 info.battery_flag, info.battery_life,
530 info.time, units);
531
532 return ret;
533}
534#endif
535
536static int kapmd(void *arg)
537{
538 do {
539 apm_event_t event;
540 int ret;
541
542 wait_event_interruptible(kapmd_wait,
543 !queue_empty(&kapmd_queue) || kthread_should_stop());
544
545 if (kthread_should_stop())
546 break;
547
548 spin_lock_irq(&kapmd_queue_lock);
549 event = 0;
550 if (!queue_empty(&kapmd_queue))
551 event = queue_get_event(&kapmd_queue);
552 spin_unlock_irq(&kapmd_queue_lock);
553
554 switch (event) {
555 case 0:
556 break;
557
558 case APM_LOW_BATTERY:
559 case APM_POWER_STATUS_CHANGE:
560 queue_event(event);
561 break;
562
563 case APM_USER_SUSPEND:
564 case APM_SYS_SUSPEND:
565 ret = queue_suspend_event(event, NULL);
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)
574 apm_suspend();
575 break;
576
577 case APM_CRITICAL_SUSPEND:
578 apm_suspend();
579 break;
580 }
581 } while (1);
582
583 return 0;
584}
585
586static int __init apm_init(void)
587{
588 int ret;
589
590 if (apm_disabled) {
591 printk(KERN_NOTICE "apm: disabled on user request.\n");
592 return -ENODEV;
593 }
594
595 kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
596 if (IS_ERR(kapmd_tsk)) {
597 ret = PTR_ERR(kapmd_tsk);
598 kapmd_tsk = NULL;
599 return ret;
600 }
601 kapmd_tsk->flags |= PF_NOFREEZE;
602 wake_up_process(kapmd_tsk);
603
604#ifdef CONFIG_PROC_FS
605 create_proc_info_entry("apm", 0, NULL, apm_get_info);
606#endif
607
608 ret = misc_register(&apm_device);
609 if (ret != 0) {
610 remove_proc_entry("apm", NULL);
611 kthread_stop(kapmd_tsk);
612 }
613
614 return ret;
615}
616
617static void __exit apm_exit(void)
618{
619 misc_deregister(&apm_device);
620 remove_proc_entry("apm", NULL);
621
622 kthread_stop(kapmd_tsk);
623}
624
625module_init(apm_init);
626module_exit(apm_exit);
627
628MODULE_AUTHOR("Stephen Rothwell");
629MODULE_DESCRIPTION("Advanced Power Management");
630MODULE_LICENSE("GPL");
631
632#ifndef MODULE
633static int __init apm_setup(char *str)
634{
635 while ((str != NULL) && (*str != '\0')) {
636 if (strncmp(str, "off", 3) == 0)
637 apm_disabled = 1;
638 if (strncmp(str, "on", 2) == 0)
639 apm_disabled = 0;
640 str = strchr(str, ',');
641 if (str != NULL)
642 str += strspn(str, ", \t");
643 }
644 return 1;
645}
646
647__setup("apm=", apm_setup);
648#endif
649
650/**
651 * apm_queue_event - queue an APM event for kapmd
652 * @event: APM event
653 *
654 * Queue an APM event for kapmd to process and ultimately take the
655 * appropriate action. Only a subset of events are handled:
656 * %APM_LOW_BATTERY
657 * %APM_POWER_STATUS_CHANGE
658 * %APM_USER_SUSPEND
659 * %APM_SYS_SUSPEND
660 * %APM_CRITICAL_SUSPEND
661 */
662void apm_queue_event(apm_event_t event)
663{
664 unsigned long flags;
665
666 spin_lock_irqsave(&kapmd_queue_lock, flags);
667 queue_add_event(&kapmd_queue, event);
668 spin_unlock_irqrestore(&kapmd_queue_lock, flags);
669
670 wake_up_interruptible(&kapmd_wait);
671}
672EXPORT_SYMBOL(apm_queue_event);