]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blob - drivers/macintosh/apm_emu.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/shaggy...
[mirror_ubuntu-zesty-kernel.git] / drivers / macintosh / apm_emu.c
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/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 */
73 struct 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 */
93 static int suspends_pending;
94
95 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
96 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
97 static struct apm_user * user_list;
98
99 static void apm_notify_sleep(struct pmu_sleep_notifier *self, int when);
100 static struct pmu_sleep_notifier apm_sleep_notifier = {
101 apm_notify_sleep,
102 SLEEP_LEVEL_USERLAND,
103 };
104
105 static const char driver_version[] = "0.5"; /* no spaces */
106
107 #ifdef DEBUG
108 static 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
127 static int queue_empty(struct apm_user *as)
128 {
129 return as->event_head == as->event_tail;
130 }
131
132 static 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
138 static 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
173 static 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
182 static 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);
198 repeat:
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
232 static 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
245 static 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
287 static 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
320 static int do_open(struct inode * inode, struct file * filp)
321 {
322 struct apm_user * as;
323
324 as = kmalloc(sizeof(*as), GFP_KERNEL);
325 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 */
355 static void wait_all_suspend(void)
356 {
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");
369 }
370
371 static void apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
372 {
373 switch(when) {
374 case PBOOK_SLEEP_REQUEST:
375 queue_event(APM_SYS_SUSPEND, NULL);
376 wait_all_suspend();
377 break;
378 case PBOOK_WAKE:
379 queue_event(APM_NORMAL_RESUME, NULL);
380 break;
381 }
382 }
383
384 #define APM_CRITICAL 10
385 #define APM_LOW 30
386
387 static 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
427 unsigned short ac_line_status;
428 unsigned short battery_status = 0;
429 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) {
443 battery_status++;
444 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 }
459 if (0 == battery_status)
460 ac_line_status = 1;
461 battery_status = 0xff;
462 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
499 static const struct file_operations apm_bios_fops = {
500 .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
508 static struct miscdevice apm_device = {
509 APM_MINOR_DEV,
510 "apm_bios",
511 &apm_bios_fops
512 };
513
514 static 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
527 if (misc_register(&apm_device) != 0)
528 printk(KERN_INFO "Could not create misc. device for apm\n");
529
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
537 static 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
546 module_init(apm_emu_init);
547 module_exit(apm_emu_exit);
548
549 MODULE_AUTHOR("Benjamin Herrenschmidt");
550 MODULE_DESCRIPTION("APM emulation layer for PowerMac");
551 MODULE_LICENSE("GPL");
552