drm: possible cleanups
[linux-2.6] / 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/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  */
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];
84 };
85
86 /*
87  * The magic number in apm_user
88  */
89 #define APM_BIOS_MAGIC          0x4101
90
91 /*
92  * Local variables
93  */
94 static int                      suspends_pending;
95
96 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
97 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
98 static struct apm_user *        user_list;
99
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,
104 };
105
106 static char                     driver_version[] = "0.5";       /* no spaces */
107
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"
122 };
123 #define NR_APM_EVENT_NAME       \
124                 (sizeof(apm_event_name) / sizeof(apm_event_name[0]))
125
126 #endif
127
128 static int queue_empty(struct apm_user *as)
129 {
130         return as->event_head == as->event_tail;
131 }
132
133 static 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
139 static 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
174 static 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
183 static 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);
199 repeat:
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
233 static 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
246 static 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
288 static 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
321 static 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  */
356 static 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
374 static 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
393 static 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;
434         unsigned short  battery_status = 0;
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                         battery_status++;
450                         if (percentage < 0)
451                                 percentage = 0;
452                         if (charge < 0)
453                                 charge = 0;
454                         percentage += (pmu_batteries[i].charge * 100) /
455                                 pmu_batteries[i].max_charge;
456                         charge += pmu_batteries[i].charge;
457                         amperage += pmu_batteries[i].amperage;
458                         if (btype == 0)
459                                 btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK);
460                         real_count++;
461                         if ((pmu_batteries[i].flags & PMU_BATT_CHARGING))
462                                 charging++;
463                 }
464         }
465         if (0 == battery_status)
466                 ac_line_status = 1;
467         battery_status = 0xff;
468         if (real_count) {
469                 if (amperage < 0) {
470                         if (btype == PMU_BATT_TYPE_SMART)
471                                 time_units = (charge * 59) / (amperage * -1);
472                         else
473                                 time_units = (charge * 16440) / (amperage * -60);
474                 }
475                 percentage /= real_count;
476                 if (charging > 0) {
477                         battery_status = 0x03;
478                         battery_flag = 0x08;
479                 } else if (percentage <= APM_CRITICAL) {
480                         battery_status = 0x02;
481                         battery_flag = 0x04;
482                 } else if (percentage <= APM_LOW) {
483                         battery_status = 0x01;
484                         battery_flag = 0x02;
485                 } else {
486                         battery_status = 0x00;
487                         battery_flag = 0x01;
488                 }
489         }
490         p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
491                      driver_version,
492                      (FAKE_APM_BIOS_VERSION >> 8) & 0xff,
493                      FAKE_APM_BIOS_VERSION & 0xff,
494                      0,
495                      ac_line_status,
496                      battery_status,
497                      battery_flag,
498                      percentage,
499                      time_units,
500                      "min");
501
502         return p - buf;
503 }
504
505 static struct file_operations apm_bios_fops = {
506         .owner          = THIS_MODULE,
507         .read           = do_read,
508         .poll           = do_poll,
509         .ioctl          = do_ioctl,
510         .open           = do_open,
511         .release        = do_release,
512 };
513
514 static struct miscdevice apm_device = {
515         APM_MINOR_DEV,
516         "apm_bios",
517         &apm_bios_fops
518 };
519
520 static int __init apm_emu_init(void)
521 {
522         struct proc_dir_entry *apm_proc;
523
524         if (sys_ctrler != SYS_CTRLER_PMU) {
525                 printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
526                 return -ENODEV;
527         }
528                 
529         apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
530         if (apm_proc)
531                 apm_proc->owner = THIS_MODULE;
532
533         misc_register(&apm_device);
534
535         pmu_register_sleep_notifier(&apm_sleep_notifier);
536
537         printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
538
539         return 0;
540 }
541
542 static void __exit apm_emu_exit(void)
543 {
544         pmu_unregister_sleep_notifier(&apm_sleep_notifier);
545         misc_deregister(&apm_device);
546         remove_proc_entry("apm", NULL);
547
548         printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
549 }
550
551 module_init(apm_emu_init);
552 module_exit(apm_emu_exit);
553
554 MODULE_AUTHOR("Benjamin Herrenschmidt");
555 MODULE_DESCRIPTION("APM emulation layer for PowerMac");
556 MODULE_LICENSE("GPL");
557