[PATCH] uml: increase granularity of host capability checking
[linux-2.6/suspend2-2.6.18.git] / drivers / macintosh / apm_emu.c
blob19d3e05d68252a74f5fbc7eacc3e0f61648f94eb
1 /* APM emulation layer for PowerMac
2 *
3 * Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org)
5 * Lots of code inherited from apm.c, see appropriate notice in
6 * arch/i386/kernel/apm.c
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.
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.
21 #include <linux/config.h>
22 #include <linux/module.h>
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>
40 #include <linux/adb.h>
41 #include <linux/pmu.h>
43 #include <asm/system.h>
44 #include <asm/uaccess.h>
45 #include <asm/machdep.h>
47 #undef DEBUG
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
57 * The apm_bios device is one of the misc char devices.
58 * This is its minor number.
60 #define APM_MINOR_DEV 134
63 * Maximum number of events stored
65 #define APM_MAX_EVENTS 20
67 #define FAKE_APM_BIOS_VERSION 0x0101
69 #define APM_USER_NOTIFY_TIMEOUT (5*HZ)
72 * The per-file APM data
74 struct 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];
87 * The magic number in apm_user
89 #define APM_BIOS_MAGIC 0x4101
92 * Local variables
94 static int suspends_pending;
96 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
97 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
98 static struct apm_user * user_list;
100 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when);
101 static struct pmu_sleep_notifier apm_sleep_notifier = {
102 apm_notify_sleep,
103 SLEEP_LEVEL_USERLAND,
106 static char driver_version[] = "0.5"; /* no spaces */
108 #ifdef DEBUG
109 static 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"
123 #define NR_APM_EVENT_NAME \
124 (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
126 #endif
128 static int queue_empty(struct apm_user *as)
130 return as->event_head == as->event_tail;
133 static apm_event_t get_queued_event(struct apm_user *as)
135 as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS;
136 return as->events[as->event_tail];
139 static void queue_event(apm_event_t event, struct apm_user *sender)
141 struct apm_user * as;
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;
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;
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;
171 wake_up_interruptible(&apm_waitqueue);
174 static int check_apm_user(struct apm_user *as, const char *func)
176 if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) {
177 printk(KERN_ERR "apm_emu: %s passed bad filp\n", func);
178 return 1;
180 return 0;
183 static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
185 struct apm_user * as;
186 size_t i;
187 apm_event_t event;
188 DECLARE_WAITQUEUE(wait, current);
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);
199 repeat:
200 set_current_state(TASK_INTERRUPTIBLE);
201 if (queue_empty(as) && !signal_pending(current)) {
202 schedule();
203 goto repeat;
205 set_current_state(TASK_RUNNING);
206 remove_wait_queue(&apm_waitqueue, &wait);
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;
217 switch (event) {
218 case APM_SYS_SUSPEND:
219 case APM_USER_SUSPEND:
220 as->suspends_read++;
221 break;
223 buf += sizeof(event);
224 i -= sizeof(event);
226 if (i < count)
227 return count - i;
228 if (signal_pending(current))
229 return -ERESTARTSYS;
230 return 0;
233 static unsigned int do_poll(struct file *fp, poll_table * wait)
235 struct apm_user * as;
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;
246 static int do_ioctl(struct inode * inode, struct file *filp,
247 u_int cmd, u_long arg)
249 struct apm_user * as;
250 DECLARE_WAITQUEUE(wait, current);
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
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;
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();
279 set_current_state(TASK_RUNNING);
280 remove_wait_queue(&apm_waitqueue, &wait);
281 break;
282 default:
283 return -EINVAL;
285 return 0;
288 static int do_release(struct inode * inode, struct file * filp)
290 struct apm_user * as;
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);
302 if (user_list == as)
303 user_list = as->next;
304 else {
305 struct apm_user * as1;
307 for (as1 = user_list;
308 (as1 != NULL) && (as1->next != as);
309 as1 = as1->next)
311 if (as1 == NULL)
312 printk(KERN_ERR "apm: filp not in user list\n");
313 else
314 as1->next = as->next;
316 unlock_kernel();
317 kfree(as);
318 return 0;
321 static int do_open(struct inode * inode, struct file * filp)
323 struct apm_user * as;
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;
331 as->magic = APM_BIOS_MAGIC;
332 as->event_tail = as->event_head = 0;
333 as->suspends_pending = 0;
334 as->suspends_read = 0;
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
342 as->suser = capable(CAP_SYS_ADMIN);
343 as->next = user_list;
344 user_list = as;
345 filp->private_data = as;
347 DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser);
349 return 0;
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.
356 static int wait_all_suspend(void)
358 DECLARE_WAITQUEUE(wait, current);
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();
366 set_current_state(TASK_RUNNING);
367 remove_wait_queue(&apm_suspend_waitqueue, &wait);
369 DBG("apm_emu: wait_all_suspend() - complete !\n");
371 return 1;
374 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
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;
387 return PBOOK_SLEEP_OK;
390 #define APM_CRITICAL 10
391 #define APM_LOW 30
393 static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length)
395 /* Arguments, with symbols from linux/apm_bios.h. Information is
396 from the Get Power Status (0x0a) call unless otherwise noted.
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 */
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;
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++;
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);
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;
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,
491 ac_line_status,
492 battery_status,
493 battery_flag,
494 percentage,
495 time_units,
496 "min");
498 return p - buf;
501 static 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,
510 static struct miscdevice apm_device = {
511 APM_MINOR_DEV,
512 "apm_bios",
513 &apm_bios_fops
516 static int __init apm_emu_init(void)
518 struct proc_dir_entry *apm_proc;
520 if (sys_ctrler != SYS_CTRLER_PMU) {
521 printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
522 return -ENODEV;
525 apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
526 if (apm_proc)
527 apm_proc->owner = THIS_MODULE;
529 misc_register(&apm_device);
531 pmu_register_sleep_notifier(&apm_sleep_notifier);
533 printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
535 return 0;
538 static void __exit apm_emu_exit(void)
540 pmu_unregister_sleep_notifier(&apm_sleep_notifier);
541 misc_deregister(&apm_device);
542 remove_proc_entry("apm", NULL);
544 printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
547 module_init(apm_emu_init);
548 module_exit(apm_emu_exit);
550 MODULE_AUTHOR("Benjamin Herrenschmidt");
551 MODULE_DESCRIPTION("APM emulation layer for PowerMac");
552 MODULE_LICENSE("GPL");