1 /* APM emulation layer for PowerMac
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
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>
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>
50 #define DBG(args...) printk(KERN_DEBUG args)
51 //#define DBG(args...) xmon_printf(args)
53 #define DBG(args...) do { } while (0)
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
76 struct apm_user * next;
78 int suspend_waiting: 1;
83 apm_event_t events[APM_MAX_EVENTS];
87 * The magic number in apm_user
89 #define APM_BIOS_MAGIC 0x4101
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 = {
103 SLEEP_LEVEL_USERLAND,
106 static char driver_version[] = "0.5"; /* no spaces */
109 static char * apm_event_name[] = {
115 "power status change",
120 "system standby resume",
121 "capabilities change"
123 #define NR_APM_EVENT_NAME \
124 (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
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)
146 for (as = user_list; as != NULL; as = as->next) {
149 as->event_head = (as->event_head + 1) % APM_MAX_EVENTS;
150 if (as->event_head == as->event_tail) {
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;
161 case APM_SYS_SUSPEND:
162 case APM_USER_SUSPEND:
163 as->suspends_pending++;
166 case APM_NORMAL_RESUME:
167 as->suspend_waiting = 0;
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);
183 static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
185 struct apm_user * as;
188 DECLARE_WAITQUEUE(wait, current);
190 as = fp->private_data;
191 if (check_apm_user(as, "read"))
193 if (count < sizeof(apm_event_t))
195 if (queue_empty(as)) {
196 if (fp->f_flags & O_NONBLOCK)
198 add_wait_queue(&apm_waitqueue, &wait);
200 set_current_state(TASK_INTERRUPTIBLE);
201 if (queue_empty(as) && !signal_pending(current)) {
205 set_current_state(TASK_RUNNING);
206 remove_wait_queue(&apm_waitqueue, &wait);
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))) {
218 case APM_SYS_SUSPEND:
219 case APM_USER_SUSPEND:
223 buf += sizeof(event);
228 if (signal_pending(current))
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"))
240 poll_wait(fp, &apm_waitqueue, wait);
241 if (!queue_empty(as))
242 return POLLIN | POLLRDNORM;
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"))
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) {
264 as->suspends_pending--;
267 // Route to PMU suspend ?
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);
275 while(as->suspend_waiting && !signal_pending(current)) {
276 set_current_state(TASK_INTERRUPTIBLE);
279 set_current_state(TASK_RUNNING);
280 remove_wait_queue(&apm_waitqueue, &wait);
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"))
295 filp->private_data = NULL;
297 if (as->suspends_pending > 0) {
298 suspends_pending -= as->suspends_pending;
299 if (suspends_pending <= 0)
300 wake_up(&apm_suspend_waitqueue);
303 user_list = as->next;
305 struct apm_user * as1;
307 for (as1 = user_list;
308 (as1 != NULL) && (as1->next != as);
312 printk(KERN_ERR "apm: filp not in user list\n");
314 as1->next = as->next;
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);
327 printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n",
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;
345 filp->private_data = as;
347 DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser);
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
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);
366 set_current_state(TASK_RUNNING);
367 remove_wait_queue(&apm_suspend_waitqueue, &wait);
369 DBG("apm_emu: wait_all_suspend() - complete !\n");
374 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
377 case PBOOK_SLEEP_REQUEST:
378 queue_event(APM_SYS_SUSPEND, NULL);
379 if (!wait_all_suspend())
380 return PBOOK_SLEEP_REFUSE;
382 case PBOOK_SLEEP_REJECT:
384 queue_event(APM_NORMAL_RESUME, NULL);
387 return PBOOK_SLEEP_OK;
390 #define APM_CRITICAL 10
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
409 0x02: On backup power (BIOS >= 1.1 only)
416 0x04: Selected battery not present (BIOS >= 1.2 only)
423 bit 7: No system battery
425 6) Remaining battery life (percentage of charge):
428 7) Remaining battery life (time units):
429 Number of remaining minutes or seconds
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;
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) {
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;
458 btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK);
460 if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
466 if (btype == PMU_BATT_TYPE_SMART)
467 time_units = (charge * 59) / (amperage * -1);
469 time_units = (charge * 16440) / (amperage * -60);
471 percentage /= real_count;
473 battery_status = 0x03;
475 } else if (percentage <= APM_CRITICAL) {
476 battery_status = 0x02;
478 } else if (percentage <= APM_LOW) {
479 battery_status = 0x01;
482 battery_status = 0x00;
486 p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
488 (FAKE_APM_BIOS_VERSION >> 8) & 0xff,
489 FAKE_APM_BIOS_VERSION & 0xff,
501 static struct file_operations apm_bios_fops = {
502 .owner = THIS_MODULE,
507 .release = do_release,
510 static struct miscdevice apm_device = {
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");
525 apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
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);
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");