xref: /openbmc/linux/drivers/macintosh/apm_emu.c (revision 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2)
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 = 0xff;
434 	unsigned short  battery_status = 0xff;
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 			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 (real_count) {
465 		if (amperage < 0) {
466 			if (btype == PMU_BATT_TYPE_SMART)
467 				time_units = (charge * 59) / (amperage * -1);
468 			else
469 				time_units = (charge * 16440) / (amperage * -60);
470 		}
471 		percentage /= real_count;
472 		if (charging > 0) {
473 			battery_status = 0x03;
474 			battery_flag = 0x08;
475 		} else if (percentage <= APM_CRITICAL) {
476 			battery_status = 0x02;
477 			battery_flag = 0x04;
478 		} else if (percentage <= APM_LOW) {
479 			battery_status = 0x01;
480 			battery_flag = 0x02;
481 		} else {
482 			battery_status = 0x00;
483 			battery_flag = 0x01;
484 		}
485 	}
486 	p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
487 		     driver_version,
488 		     (FAKE_APM_BIOS_VERSION >> 8) & 0xff,
489 		     FAKE_APM_BIOS_VERSION & 0xff,
490 		     0,
491 		     ac_line_status,
492 		     battery_status,
493 		     battery_flag,
494 		     percentage,
495 		     time_units,
496 		     "min");
497 
498 	return p - buf;
499 }
500 
501 static struct file_operations apm_bios_fops = {
502 	.owner		= THIS_MODULE,
503 	.read		= do_read,
504 	.poll		= do_poll,
505 	.ioctl		= do_ioctl,
506 	.open		= do_open,
507 	.release	= do_release,
508 };
509 
510 static struct miscdevice apm_device = {
511 	APM_MINOR_DEV,
512 	"apm_bios",
513 	&apm_bios_fops
514 };
515 
516 static int __init apm_emu_init(void)
517 {
518 	struct proc_dir_entry *apm_proc;
519 
520 	if (sys_ctrler != SYS_CTRLER_PMU) {
521 		printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n");
522 		return -ENODEV;
523 	}
524 
525 	apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info);
526 	if (apm_proc)
527 		apm_proc->owner = THIS_MODULE;
528 
529 	misc_register(&apm_device);
530 
531 	pmu_register_sleep_notifier(&apm_sleep_notifier);
532 
533 	printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version);
534 
535 	return 0;
536 }
537 
538 static void __exit apm_emu_exit(void)
539 {
540 	pmu_unregister_sleep_notifier(&apm_sleep_notifier);
541 	misc_deregister(&apm_device);
542 	remove_proc_entry("apm", NULL);
543 
544 	printk(KERN_INFO "apm_emu: APM Emulation removed.\n");
545 }
546 
547 module_init(apm_emu_init);
548 module_exit(apm_emu_exit);
549 
550 MODULE_AUTHOR("Benjamin Herrenschmidt");
551 MODULE_DESCRIPTION("APM emulation layer for PowerMac");
552 MODULE_LICENSE("GPL");
553 
554