xref: /linux/drivers/macintosh/apm_emu.c (revision 14b42963f64b98ab61fa9723c03d71aa5ef4f862)
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 int 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 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 = (struct apm_user *)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 int 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 	return 1;
371 }
372 
373 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when)
374 {
375 	switch(when) {
376 		case PBOOK_SLEEP_REQUEST:
377 			queue_event(APM_SYS_SUSPEND, NULL);
378 			if (!wait_all_suspend())
379 				return PBOOK_SLEEP_REFUSE;
380 			break;
381 		case PBOOK_SLEEP_REJECT:
382 		case PBOOK_WAKE:
383 			queue_event(APM_NORMAL_RESUME, NULL);
384 			break;
385 	}
386 	return PBOOK_SLEEP_OK;
387 }
388 
389 #define APM_CRITICAL		10
390 #define APM_LOW			30
391 
392 static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length)
393 {
394 	/* Arguments, with symbols from linux/apm_bios.h.  Information is
395 	   from the Get Power Status (0x0a) call unless otherwise noted.
396 
397 	   0) Linux driver version (this will change if format changes)
398 	   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
399 	   2) APM flags from APM Installation Check (0x00):
400 	      bit 0: APM_16_BIT_SUPPORT
401 	      bit 1: APM_32_BIT_SUPPORT
402 	      bit 2: APM_IDLE_SLOWS_CLOCK
403 	      bit 3: APM_BIOS_DISABLED
404 	      bit 4: APM_BIOS_DISENGAGED
405 	   3) AC line status
406 	      0x00: Off-line
407 	      0x01: On-line
408 	      0x02: On backup power (BIOS >= 1.1 only)
409 	      0xff: Unknown
410 	   4) Battery status
411 	      0x00: High
412 	      0x01: Low
413 	      0x02: Critical
414 	      0x03: Charging
415 	      0x04: Selected battery not present (BIOS >= 1.2 only)
416 	      0xff: Unknown
417 	   5) Battery flag
418 	      bit 0: High
419 	      bit 1: Low
420 	      bit 2: Critical
421 	      bit 3: Charging
422 	      bit 7: No system battery
423 	      0xff: Unknown
424 	   6) Remaining battery life (percentage of charge):
425 	      0-100: valid
426 	      -1: Unknown
427 	   7) Remaining battery life (time units):
428 	      Number of remaining minutes or seconds
429 	      -1: Unknown
430 	   8) min = minutes; sec = seconds */
431 
432 	unsigned short  ac_line_status;
433 	unsigned short  battery_status = 0;
434 	unsigned short  battery_flag   = 0xff;
435 	int		percentage     = -1;
436 	int             time_units     = -1;
437 	int		real_count     = 0;
438 	int		i;
439 	char *		p = buf;
440 	char		charging       = 0;
441 	long		charge	       = -1;
442 	long		amperage       = 0;
443 	unsigned long	btype          = 0;
444 
445 	ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0);
446 	for (i=0; i<pmu_battery_count; i++) {
447 		if (pmu_batteries[i].flags & PMU_BATT_PRESENT) {
448 			battery_status++;
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++;
462 		}
463 	}
464 	if (0 == battery_status)
465 		ac_line_status = 1;
466 	battery_status = 0xff;
467 	if (real_count) {
468 		if (amperage < 0) {
469 			if (btype == PMU_BATT_TYPE_SMART)
470 				time_units = (charge * 59) / (amperage * -1);
471 			else
472 				time_units = (charge * 16440) / (amperage * -60);
473 		}
474 		percentage /= real_count;
475 		if (charging > 0) {
476 			battery_status = 0x03;
477 			battery_flag = 0x08;
478 		} else if (percentage <= APM_CRITICAL) {
479 			battery_status = 0x02;
480 			battery_flag = 0x04;
481 		} else if (percentage <= APM_LOW) {
482 			battery_status = 0x01;
483 			battery_flag = 0x02;
484 		} else {
485 			battery_status = 0x00;
486 			battery_flag = 0x01;
487 		}
488 	}
489 	p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
490 		     driver_version,
491 		     (FAKE_APM_BIOS_VERSION >> 8) & 0xff,
492 		     FAKE_APM_BIOS_VERSION & 0xff,
493 		     0,
494 		     ac_line_status,
495 		     battery_status,
496 		     battery_flag,
497 		     percentage,
498 		     time_units,
499 		     "min");
500 
501 	return p - buf;
502 }
503 
504 static struct file_operations apm_bios_fops = {
505 	.owner		= THIS_MODULE,
506 	.read		= do_read,
507 	.poll		= do_poll,
508 	.ioctl		= do_ioctl,
509 	.open		= do_open,
510 	.release	= do_release,
511 };
512 
513 static struct miscdevice apm_device = {
514 	APM_MINOR_DEV,
515 	"apm_bios",
516 	&apm_bios_fops
517 };
518 
519 static int __init apm_emu_init(void)
520 {
521 	struct proc_dir_entry *apm_proc;
522 
523 	if (sys_ctrler != SYS_CTRLER_PMU) {
524 		printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
525 		return -ENODEV;
526 	}
527 
528 	apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
529 	if (apm_proc)
530 		apm_proc->owner = THIS_MODULE;
531 
532 	misc_register(&apm_device);
533 
534 	pmu_register_sleep_notifier(&apm_sleep_notifier);
535 
536 	printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
537 
538 	return 0;
539 }
540 
541 static void __exit apm_emu_exit(void)
542 {
543 	pmu_unregister_sleep_notifier(&apm_sleep_notifier);
544 	misc_deregister(&apm_device);
545 	remove_proc_entry("apm", NULL);
546 
547 	printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
548 }
549 
550 module_init(apm_emu_init);
551 module_exit(apm_emu_exit);
552 
553 MODULE_AUTHOR("Benjamin Herrenschmidt");
554 MODULE_DESCRIPTION("APM emulation layer for PowerMac");
555 MODULE_LICENSE("GPL");
556 
557