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