xref: /openbmc/linux/drivers/macintosh/apm_emu.c (revision 87c2ce3b)
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 
558