Linux-2.6.12-rc2
[deliverable/linux.git] / drivers / macintosh / apm_emu.c
CommitLineData
1da177e4
LT
1/* APM emulation layer for PowerMac
2 *
3 * Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
4 *
5 * Lots of code inherited from apm.c, see appropriate notice in
6 * arch/i386/kernel/apm.c
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2, or (at your option) any
11 * later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 *
19 */
20
21#include <linux/config.h>
22#include <linux/module.h>
23
24#include <linux/poll.h>
25#include <linux/types.h>
26#include <linux/stddef.h>
27#include <linux/timer.h>
28#include <linux/fcntl.h>
29#include <linux/slab.h>
30#include <linux/stat.h>
31#include <linux/proc_fs.h>
32#include <linux/miscdevice.h>
33#include <linux/apm_bios.h>
34#include <linux/init.h>
35#include <linux/sched.h>
36#include <linux/pm.h>
37#include <linux/kernel.h>
38#include <linux/smp_lock.h>
39
40#include <linux/adb.h>
41#include <linux/pmu.h>
42
43#include <asm/system.h>
44#include <asm/uaccess.h>
45#include <asm/machdep.h>
46
47#undef DEBUG
48
49#ifdef DEBUG
50#define DBG(args...) printk(KERN_DEBUG args)
51//#define DBG(args...) xmon_printf(args)
52#else
53#define DBG(args...) do { } while (0)
54#endif
55
56/*
57 * The apm_bios device is one of the misc char devices.
58 * This is its minor number.
59 */
60#define APM_MINOR_DEV 134
61
62/*
63 * Maximum number of events stored
64 */
65#define APM_MAX_EVENTS 20
66
67#define FAKE_APM_BIOS_VERSION 0x0101
68
69#define APM_USER_NOTIFY_TIMEOUT (5*HZ)
70
71/*
72 * The per-file APM data
73 */
74struct apm_user {
75 int magic;
76 struct apm_user * next;
77 int suser: 1;
78 int suspend_waiting: 1;
79 int suspends_pending;
80 int suspends_read;
81 int event_head;
82 int event_tail;
83 apm_event_t events[APM_MAX_EVENTS];
84};
85
86/*
87 * The magic number in apm_user
88 */
89#define APM_BIOS_MAGIC 0x4101
90
91/*
92 * Local variables
93 */
94static int suspends_pending;
95
96static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
97static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
98static struct apm_user * user_list;
99
100static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when);
101static struct pmu_sleep_notifier apm_sleep_notifier = {
102 apm_notify_sleep,
103 SLEEP_LEVEL_USERLAND,
104};
105
106static char driver_version[] = "0.5"; /* no spaces */
107
108#ifdef DEBUG
109static char * apm_event_name[] = {
110 "system standby",
111 "system suspend",
112 "normal resume",
113 "critical resume",
114 "low battery",
115 "power status change",
116 "update time",
117 "critical suspend",
118 "user standby",
119 "user suspend",
120 "system standby resume",
121 "capabilities change"
122};
123#define NR_APM_EVENT_NAME \
124 (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
125
126#endif
127
128static int queue_empty(struct apm_user *as)
129{
130 return as->event_head == as->event_tail;
131}
132
133static apm_event_t get_queued_event(struct apm_user *as)
134{
135 as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
136 return as->events[as->event_tail];
137}
138
139static void queue_event(apm_event_t event, struct apm_user *sender)
140{
141 struct apm_user * as;
142
143 DBG("apm_emu: queue_event(%s)\n", apm_event_name[event-1]);
144 if (user_list == NULL)
145 return;
146 for (as = user_list; as != NULL; as = as->next) {
147 if (as == sender)
148 continue;
149 as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
150 if (as->event_head == as->event_tail) {
151 static int notified;
152
153 if (notified++ == 0)
154 printk(KERN_ERR "apm_emu: an event queue overflowed\n");
155 as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
156 }
157 as->events[as->event_head] = event;
158 if (!as->suser)
159 continue;
160 switch (event) {
161 case APM_SYS_SUSPEND:
162 case APM_USER_SUSPEND:
163 as->suspends_pending++;
164 suspends_pending++;
165 break;
166 case APM_NORMAL_RESUME:
167 as->suspend_waiting = 0;
168 break;
169 }
170 }
171 wake_up_interruptible(&apm_waitqueue);
172}
173
174static int check_apm_user(struct apm_user *as, const char *func)
175{
176 if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
177 printk(KERN_ERR "apm_emu: %s passed bad filp\n", func);
178 return 1;
179 }
180 return 0;
181}
182
183static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
184{
185 struct apm_user * as;
186 size_t i;
187 apm_event_t event;
188 DECLARE_WAITQUEUE(wait, current);
189
190 as = fp->private_data;
191 if (check_apm_user(as, "read"))
192 return -EIO;
193 if (count < sizeof(apm_event_t))
194 return -EINVAL;
195 if (queue_empty(as)) {
196 if (fp->f_flags & O_NONBLOCK)
197 return -EAGAIN;
198 add_wait_queue(&apm_waitqueue, &wait);
199repeat:
200 set_current_state(TASK_INTERRUPTIBLE);
201 if (queue_empty(as) && !signal_pending(current)) {
202 schedule();
203 goto repeat;
204 }
205 set_current_state(TASK_RUNNING);
206 remove_wait_queue(&apm_waitqueue, &wait);
207 }
208 i = count;
209 while ((i >= sizeof(event)) && !queue_empty(as)) {
210 event = get_queued_event(as);
211 DBG("apm_emu: do_read, returning: %s\n", apm_event_name[event-1]);
212 if (copy_to_user(buf, &event, sizeof(event))) {
213 if (i < count)
214 break;
215 return -EFAULT;
216 }
217 switch (event) {
218 case APM_SYS_SUSPEND:
219 case APM_USER_SUSPEND:
220 as->suspends_read++;
221 break;
222 }
223 buf += sizeof(event);
224 i -= sizeof(event);
225 }
226 if (i < count)
227 return count - i;
228 if (signal_pending(current))
229 return -ERESTARTSYS;
230 return 0;
231}
232
233static unsigned int do_poll(struct file *fp, poll_table * wait)
234{
235 struct apm_user * as;
236
237 as = fp->private_data;
238 if (check_apm_user(as, "poll"))
239 return 0;
240 poll_wait(fp, &apm_waitqueue, wait);
241 if (!queue_empty(as))
242 return POLLIN | POLLRDNORM;
243 return 0;
244}
245
246static int do_ioctl(struct inode * inode, struct file *filp,
247 u_int cmd, u_long arg)
248{
249 struct apm_user * as;
250 DECLARE_WAITQUEUE(wait, current);
251
252 as = filp->private_data;
253 if (check_apm_user(as, "ioctl"))
254 return -EIO;
255 if (!as->suser)
256 return -EPERM;
257 switch (cmd) {
258 case APM_IOC_SUSPEND:
259 /* If a suspend message was sent to userland, we
260 * consider this as a confirmation message
261 */
262 if (as->suspends_read > 0) {
263 as->suspends_read--;
264 as->suspends_pending--;
265 suspends_pending--;
266 } else {
267 // Route to PMU suspend ?
268 break;
269 }
270 as->suspend_waiting = 1;
271 add_wait_queue(&apm_waitqueue, &wait);
272 DBG("apm_emu: ioctl waking up sleep waiter !\n");
273 wake_up(&apm_suspend_waitqueue);
274 mb();
275 while(as->suspend_waiting && !signal_pending(current)) {
276 set_current_state(TASK_INTERRUPTIBLE);
277 schedule();
278 }
279 set_current_state(TASK_RUNNING);
280 remove_wait_queue(&apm_waitqueue, &wait);
281 break;
282 default:
283 return -EINVAL;
284 }
285 return 0;
286}
287
288static int do_release(struct inode * inode, struct file * filp)
289{
290 struct apm_user * as;
291
292 as = filp->private_data;
293 if (check_apm_user(as, "release"))
294 return 0;
295 filp->private_data = NULL;
296 lock_kernel();
297 if (as->suspends_pending > 0) {
298 suspends_pending -= as->suspends_pending;
299 if (suspends_pending <= 0)
300 wake_up(&apm_suspend_waitqueue);
301 }
302 if (user_list == as)
303 user_list = as->next;
304 else {
305 struct apm_user * as1;
306
307 for (as1 = user_list;
308 (as1 != NULL) && (as1->next != as);
309 as1 = as1->next)
310 ;
311 if (as1 == NULL)
312 printk(KERN_ERR "apm: filp not in user list\n");
313 else
314 as1->next = as->next;
315 }
316 unlock_kernel();
317 kfree(as);
318 return 0;
319}
320
321static int do_open(struct inode * inode, struct file * filp)
322{
323 struct apm_user * as;
324
325 as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL);
326 if (as == NULL) {
327 printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
328 sizeof(*as));
329 return -ENOMEM;
330 }
331 as->magic = APM_BIOS_MAGIC;
332 as->event_tail = as->event_head = 0;
333 as->suspends_pending = 0;
334 as->suspends_read = 0;
335 /*
336 * XXX - this is a tiny bit broken, when we consider BSD
337 * process accounting. If the device is opened by root, we
338 * instantly flag that we used superuser privs. Who knows,
339 * we might close the device immediately without doing a
340 * privileged operation -- cevans
341 */
342 as->suser = capable(CAP_SYS_ADMIN);
343 as->next = user_list;
344 user_list = as;
345 filp->private_data = as;
346
347 DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser);
348
349 return 0;
350}
351
352/* Wait for all clients to ack the suspend request. APM API
353 * doesn't provide a way to NAK, but this could be added
354 * here.
355 */
356static int wait_all_suspend(void)
357{
358 DECLARE_WAITQUEUE(wait, current);
359
360 add_wait_queue(&apm_suspend_waitqueue, &wait);
361 DBG("apm_emu: wait_all_suspend(), suspends_pending: %d\n", suspends_pending);
362 while(suspends_pending > 0) {
363 set_current_state(TASK_UNINTERRUPTIBLE);
364 schedule();
365 }
366 set_current_state(TASK_RUNNING);
367 remove_wait_queue(&apm_suspend_waitqueue, &wait);
368
369 DBG("apm_emu: wait_all_suspend() - complete !\n");
370
371 return 1;
372}
373
374static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
375{
376 switch(when) {
377 case PBOOK_SLEEP_REQUEST:
378 queue_event(APM_SYS_SUSPEND, NULL);
379 if (!wait_all_suspend())
380 return PBOOK_SLEEP_REFUSE;
381 break;
382 case PBOOK_SLEEP_REJECT:
383 case PBOOK_WAKE:
384 queue_event(APM_NORMAL_RESUME, NULL);
385 break;
386 }
387 return PBOOK_SLEEP_OK;
388}
389
390#define APM_CRITICAL 10
391#define APM_LOW 30
392
393static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length)
394{
395 /* Arguments, with symbols from linux/apm_bios.h. Information is
396 from the Get Power Status (0x0a) call unless otherwise noted.
397
398 0) Linux driver version (this will change if format changes)
399 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2.
400 2) APM flags from APM Installation Check (0x00):
401 bit 0: APM_16_BIT_SUPPORT
402 bit 1: APM_32_BIT_SUPPORT
403 bit 2: APM_IDLE_SLOWS_CLOCK
404 bit 3: APM_BIOS_DISABLED
405 bit 4: APM_BIOS_DISENGAGED
406 3) AC line status
407 0x00: Off-line
408 0x01: On-line
409 0x02: On backup power (BIOS >= 1.1 only)
410 0xff: Unknown
411 4) Battery status
412 0x00: High
413 0x01: Low
414 0x02: Critical
415 0x03: Charging
416 0x04: Selected battery not present (BIOS >= 1.2 only)
417 0xff: Unknown
418 5) Battery flag
419 bit 0: High
420 bit 1: Low
421 bit 2: Critical
422 bit 3: Charging
423 bit 7: No system battery
424 0xff: Unknown
425 6) Remaining battery life (percentage of charge):
426 0-100: valid
427 -1: Unknown
428 7) Remaining battery life (time units):
429 Number of remaining minutes or seconds
430 -1: Unknown
431 8) min = minutes; sec = seconds */
432
433 unsigned short ac_line_status = 0xff;
434 unsigned short battery_status = 0xff;
435 unsigned short battery_flag = 0xff;
436 int percentage = -1;
437 int time_units = -1;
438 int real_count = 0;
439 int i;
440 char * p = buf;
441 char charging = 0;
442 long charge = -1;
443 long amperage = 0;
444 unsigned long btype = 0;
445
446 ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0);
447 for (i=0; i<pmu_battery_count; i++) {
448 if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
449 if (percentage < 0)
450 percentage = 0;
451 if (charge < 0)
452 charge = 0;
453 percentage += (pmu_batteries[i].charge * 100) /
454 pmu_batteries[i].max_charge;
455 charge += pmu_batteries[i].charge;
456 amperage += pmu_batteries[i].amperage;
457 if (btype == 0)
458 btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK);
459 real_count++;
460 if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
461 charging++;
462 }
463 }
464 if (real_count) {
465 if (amperage < 0) {
466 if (btype == PMU_BATT_TYPE_SMART)
467 time_units = (charge * 59) / (amperage * -1);
468 else
469 time_units = (charge * 16440) / (amperage * -60);
470 }
471 percentage /= real_count;
472 if (charging > 0) {
473 battery_status = 0x03;
474 battery_flag = 0x08;
475 } else if (percentage <= APM_CRITICAL) {
476 battery_status = 0x02;
477 battery_flag = 0x04;
478 } else if (percentage <= APM_LOW) {
479 battery_status = 0x01;
480 battery_flag = 0x02;
481 } else {
482 battery_status = 0x00;
483 battery_flag = 0x01;
484 }
485 }
486 p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
487 driver_version,
488 (FAKE_APM_BIOS_VERSION >> 8) & 0xff,
489 FAKE_APM_BIOS_VERSION & 0xff,
490 0,
491 ac_line_status,
492 battery_status,
493 battery_flag,
494 percentage,
495 time_units,
496 "min");
497
498 return p - buf;
499}
500
501static struct file_operations apm_bios_fops = {
502 .owner = THIS_MODULE,
503 .read = do_read,
504 .poll = do_poll,
505 .ioctl = do_ioctl,
506 .open = do_open,
507 .release = do_release,
508};
509
510static struct miscdevice apm_device = {
511 APM_MINOR_DEV,
512 "apm_bios",
513 &apm_bios_fops
514};
515
516static int __init apm_emu_init(void)
517{
518 struct proc_dir_entry *apm_proc;
519
520 if (sys_ctrler != SYS_CTRLER_PMU) {
521 printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
522 return -ENODEV;
523 }
524
525 apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
526 if (apm_proc)
527 apm_proc->owner = THIS_MODULE;
528
529 misc_register(&apm_device);
530
531 pmu_register_sleep_notifier(&apm_sleep_notifier);
532
533 printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
534
535 return 0;
536}
537
538static void __exit apm_emu_exit(void)
539{
540 pmu_unregister_sleep_notifier(&apm_sleep_notifier);
541 misc_deregister(&apm_device);
542 remove_proc_entry("apm", NULL);
543
544 printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
545}
546
547module_init(apm_emu_init);
548module_exit(apm_emu_exit);
549
550MODULE_AUTHOR("Benjamin Herrenschmidt");
551MODULE_DESCRIPTION("APM emulation layer for PowerMac");
552MODULE_LICENSE("GPL");
553
This page took 0.073887 seconds and 5 git commands to generate.