xref: /linux/drivers/char/apm-emulation.c (revision 831441862956fffa17b9801db37e6ea1650b0f69)
17726942fSRalf Baechle /*
27726942fSRalf Baechle  * bios-less APM driver for ARM Linux
37726942fSRalf Baechle  *  Jamey Hicks <jamey@crl.dec.com>
47726942fSRalf Baechle  *  adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
57726942fSRalf Baechle  *
67726942fSRalf Baechle  * APM 1.2 Reference:
77726942fSRalf Baechle  *   Intel Corporation, Microsoft Corporation. Advanced Power Management
87726942fSRalf Baechle  *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
97726942fSRalf Baechle  *
107726942fSRalf Baechle  * [This document is available from Microsoft at:
117726942fSRalf Baechle  *    http://www.microsoft.com/hwdev/busbios/amp_12.htm]
127726942fSRalf Baechle  */
137726942fSRalf Baechle #include <linux/module.h>
147726942fSRalf Baechle #include <linux/poll.h>
157726942fSRalf Baechle #include <linux/slab.h>
167726942fSRalf Baechle #include <linux/proc_fs.h>
177726942fSRalf Baechle #include <linux/miscdevice.h>
187726942fSRalf Baechle #include <linux/apm_bios.h>
197726942fSRalf Baechle #include <linux/capability.h>
207726942fSRalf Baechle #include <linux/sched.h>
217726942fSRalf Baechle #include <linux/pm.h>
227726942fSRalf Baechle #include <linux/apm-emulation.h>
23*83144186SRafael J. Wysocki #include <linux/freezer.h>
247726942fSRalf Baechle #include <linux/device.h>
257726942fSRalf Baechle #include <linux/kernel.h>
267726942fSRalf Baechle #include <linux/list.h>
277726942fSRalf Baechle #include <linux/init.h>
287726942fSRalf Baechle #include <linux/completion.h>
297726942fSRalf Baechle #include <linux/kthread.h>
307726942fSRalf Baechle #include <linux/delay.h>
317726942fSRalf Baechle 
327726942fSRalf Baechle #include <asm/system.h>
337726942fSRalf Baechle 
347726942fSRalf Baechle /*
357726942fSRalf Baechle  * The apm_bios device is one of the misc char devices.
367726942fSRalf Baechle  * This is its minor number.
377726942fSRalf Baechle  */
387726942fSRalf Baechle #define APM_MINOR_DEV	134
397726942fSRalf Baechle 
407726942fSRalf Baechle /*
417726942fSRalf Baechle  * See Documentation/Config.help for the configuration options.
427726942fSRalf Baechle  *
437726942fSRalf Baechle  * Various options can be changed at boot time as follows:
447726942fSRalf Baechle  * (We allow underscores for compatibility with the modules code)
457726942fSRalf Baechle  *	apm=on/off			enable/disable APM
467726942fSRalf Baechle  */
477726942fSRalf Baechle 
487726942fSRalf Baechle /*
497726942fSRalf Baechle  * Maximum number of events stored
507726942fSRalf Baechle  */
517726942fSRalf Baechle #define APM_MAX_EVENTS		16
527726942fSRalf Baechle 
537726942fSRalf Baechle struct apm_queue {
547726942fSRalf Baechle 	unsigned int		event_head;
557726942fSRalf Baechle 	unsigned int		event_tail;
567726942fSRalf Baechle 	apm_event_t		events[APM_MAX_EVENTS];
577726942fSRalf Baechle };
587726942fSRalf Baechle 
597726942fSRalf Baechle /*
607726942fSRalf Baechle  * The per-file APM data
617726942fSRalf Baechle  */
627726942fSRalf Baechle struct apm_user {
637726942fSRalf Baechle 	struct list_head	list;
647726942fSRalf Baechle 
657726942fSRalf Baechle 	unsigned int		suser: 1;
667726942fSRalf Baechle 	unsigned int		writer: 1;
677726942fSRalf Baechle 	unsigned int		reader: 1;
687726942fSRalf Baechle 
697726942fSRalf Baechle 	int			suspend_result;
707726942fSRalf Baechle 	unsigned int		suspend_state;
717726942fSRalf Baechle #define SUSPEND_NONE	0		/* no suspend pending */
727726942fSRalf Baechle #define SUSPEND_PENDING	1		/* suspend pending read */
737726942fSRalf Baechle #define SUSPEND_READ	2		/* suspend read, pending ack */
747726942fSRalf Baechle #define SUSPEND_ACKED	3		/* suspend acked */
757726942fSRalf Baechle #define SUSPEND_WAIT	4		/* waiting for suspend */
767726942fSRalf Baechle #define SUSPEND_DONE	5		/* suspend completed */
777726942fSRalf Baechle 
787726942fSRalf Baechle 	struct apm_queue	queue;
797726942fSRalf Baechle };
807726942fSRalf Baechle 
817726942fSRalf Baechle /*
827726942fSRalf Baechle  * Local variables
837726942fSRalf Baechle  */
847726942fSRalf Baechle static int suspends_pending;
857726942fSRalf Baechle static int apm_disabled;
867726942fSRalf Baechle static struct task_struct *kapmd_tsk;
877726942fSRalf Baechle 
887726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
897726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
907726942fSRalf Baechle 
917726942fSRalf Baechle /*
927726942fSRalf Baechle  * This is a list of everyone who has opened /dev/apm_bios
937726942fSRalf Baechle  */
947726942fSRalf Baechle static DECLARE_RWSEM(user_list_lock);
957726942fSRalf Baechle static LIST_HEAD(apm_user_list);
967726942fSRalf Baechle 
977726942fSRalf Baechle /*
987726942fSRalf Baechle  * kapmd info.  kapmd provides us a process context to handle
997726942fSRalf Baechle  * "APM" events within - specifically necessary if we're going
1007726942fSRalf Baechle  * to be suspending the system.
1017726942fSRalf Baechle  */
1027726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
1037726942fSRalf Baechle static DEFINE_SPINLOCK(kapmd_queue_lock);
1047726942fSRalf Baechle static struct apm_queue kapmd_queue;
1057726942fSRalf Baechle 
1067726942fSRalf Baechle static DEFINE_MUTEX(state_lock);
1077726942fSRalf Baechle 
1087726942fSRalf Baechle static const char driver_version[] = "1.13";	/* no spaces */
1097726942fSRalf Baechle 
1107726942fSRalf Baechle 
1117726942fSRalf Baechle 
1127726942fSRalf Baechle /*
1137726942fSRalf Baechle  * Compatibility cruft until the IPAQ people move over to the new
1147726942fSRalf Baechle  * interface.
1157726942fSRalf Baechle  */
1167726942fSRalf Baechle static void __apm_get_power_status(struct apm_power_info *info)
1177726942fSRalf Baechle {
1187726942fSRalf Baechle }
1197726942fSRalf Baechle 
1207726942fSRalf Baechle /*
1217726942fSRalf Baechle  * This allows machines to provide their own "apm get power status" function.
1227726942fSRalf Baechle  */
1237726942fSRalf Baechle void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
1247726942fSRalf Baechle EXPORT_SYMBOL(apm_get_power_status);
1257726942fSRalf Baechle 
1267726942fSRalf Baechle 
1277726942fSRalf Baechle /*
1287726942fSRalf Baechle  * APM event queue management.
1297726942fSRalf Baechle  */
1307726942fSRalf Baechle static inline int queue_empty(struct apm_queue *q)
1317726942fSRalf Baechle {
1327726942fSRalf Baechle 	return q->event_head == q->event_tail;
1337726942fSRalf Baechle }
1347726942fSRalf Baechle 
1357726942fSRalf Baechle static inline apm_event_t queue_get_event(struct apm_queue *q)
1367726942fSRalf Baechle {
1377726942fSRalf Baechle 	q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1387726942fSRalf Baechle 	return q->events[q->event_tail];
1397726942fSRalf Baechle }
1407726942fSRalf Baechle 
1417726942fSRalf Baechle static void queue_add_event(struct apm_queue *q, apm_event_t event)
1427726942fSRalf Baechle {
1437726942fSRalf Baechle 	q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
1447726942fSRalf Baechle 	if (q->event_head == q->event_tail) {
1457726942fSRalf Baechle 		static int notified;
1467726942fSRalf Baechle 
1477726942fSRalf Baechle 		if (notified++ == 0)
1487726942fSRalf Baechle 		    printk(KERN_ERR "apm: an event queue overflowed\n");
1497726942fSRalf Baechle 		q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1507726942fSRalf Baechle 	}
1517726942fSRalf Baechle 	q->events[q->event_head] = event;
1527726942fSRalf Baechle }
1537726942fSRalf Baechle 
1547726942fSRalf Baechle static void queue_event(apm_event_t event)
1557726942fSRalf Baechle {
1567726942fSRalf Baechle 	struct apm_user *as;
1577726942fSRalf Baechle 
1587726942fSRalf Baechle 	down_read(&user_list_lock);
1597726942fSRalf Baechle 	list_for_each_entry(as, &apm_user_list, list) {
1607726942fSRalf Baechle 		if (as->reader)
1617726942fSRalf Baechle 			queue_add_event(&as->queue, event);
1627726942fSRalf Baechle 	}
1637726942fSRalf Baechle 	up_read(&user_list_lock);
1647726942fSRalf Baechle 	wake_up_interruptible(&apm_waitqueue);
1657726942fSRalf Baechle }
1667726942fSRalf Baechle 
1677726942fSRalf Baechle /*
1687726942fSRalf Baechle  * queue_suspend_event - queue an APM suspend event.
1697726942fSRalf Baechle  *
1707726942fSRalf Baechle  * Check that we're in a state where we can suspend.  If not,
1717726942fSRalf Baechle  * return -EBUSY.  Otherwise, queue an event to all "writer"
1727726942fSRalf Baechle  * users.  If there are no "writer" users, return '1' to
1737726942fSRalf Baechle  * indicate that we can immediately suspend.
1747726942fSRalf Baechle  */
1757726942fSRalf Baechle static int queue_suspend_event(apm_event_t event, struct apm_user *sender)
1767726942fSRalf Baechle {
1777726942fSRalf Baechle 	struct apm_user *as;
1787726942fSRalf Baechle 	int ret = 1;
1797726942fSRalf Baechle 
1807726942fSRalf Baechle 	mutex_lock(&state_lock);
1817726942fSRalf Baechle 	down_read(&user_list_lock);
1827726942fSRalf Baechle 
1837726942fSRalf Baechle 	/*
1847726942fSRalf Baechle 	 * If a thread is still processing, we can't suspend, so reject
1857726942fSRalf Baechle 	 * the request.
1867726942fSRalf Baechle 	 */
1877726942fSRalf Baechle 	list_for_each_entry(as, &apm_user_list, list) {
1887726942fSRalf Baechle 		if (as != sender && as->reader && as->writer && as->suser &&
1897726942fSRalf Baechle 		    as->suspend_state != SUSPEND_NONE) {
1907726942fSRalf Baechle 			ret = -EBUSY;
1917726942fSRalf Baechle 			goto out;
1927726942fSRalf Baechle 		}
1937726942fSRalf Baechle 	}
1947726942fSRalf Baechle 
1957726942fSRalf Baechle 	list_for_each_entry(as, &apm_user_list, list) {
1967726942fSRalf Baechle 		if (as != sender && as->reader && as->writer && as->suser) {
1977726942fSRalf Baechle 			as->suspend_state = SUSPEND_PENDING;
1987726942fSRalf Baechle 			suspends_pending++;
1997726942fSRalf Baechle 			queue_add_event(&as->queue, event);
2007726942fSRalf Baechle 			ret = 0;
2017726942fSRalf Baechle 		}
2027726942fSRalf Baechle 	}
2037726942fSRalf Baechle  out:
2047726942fSRalf Baechle 	up_read(&user_list_lock);
2057726942fSRalf Baechle 	mutex_unlock(&state_lock);
2067726942fSRalf Baechle 	wake_up_interruptible(&apm_waitqueue);
2077726942fSRalf Baechle 	return ret;
2087726942fSRalf Baechle }
2097726942fSRalf Baechle 
2107726942fSRalf Baechle static void apm_suspend(void)
2117726942fSRalf Baechle {
2127726942fSRalf Baechle 	struct apm_user *as;
2137726942fSRalf Baechle 	int err = pm_suspend(PM_SUSPEND_MEM);
2147726942fSRalf Baechle 
2157726942fSRalf Baechle 	/*
2167726942fSRalf Baechle 	 * Anyone on the APM queues will think we're still suspended.
2177726942fSRalf Baechle 	 * Send a message so everyone knows we're now awake again.
2187726942fSRalf Baechle 	 */
2197726942fSRalf Baechle 	queue_event(APM_NORMAL_RESUME);
2207726942fSRalf Baechle 
2217726942fSRalf Baechle 	/*
2227726942fSRalf Baechle 	 * Finally, wake up anyone who is sleeping on the suspend.
2237726942fSRalf Baechle 	 */
2247726942fSRalf Baechle 	mutex_lock(&state_lock);
2257726942fSRalf Baechle 	down_read(&user_list_lock);
2267726942fSRalf Baechle 	list_for_each_entry(as, &apm_user_list, list) {
2277726942fSRalf Baechle 		if (as->suspend_state == SUSPEND_WAIT ||
2287726942fSRalf Baechle 		    as->suspend_state == SUSPEND_ACKED) {
2297726942fSRalf Baechle 			as->suspend_result = err;
2307726942fSRalf Baechle 			as->suspend_state = SUSPEND_DONE;
2317726942fSRalf Baechle 		}
2327726942fSRalf Baechle 	}
2337726942fSRalf Baechle 	up_read(&user_list_lock);
2347726942fSRalf Baechle 	mutex_unlock(&state_lock);
2357726942fSRalf Baechle 
2367726942fSRalf Baechle 	wake_up(&apm_suspend_waitqueue);
2377726942fSRalf Baechle }
2387726942fSRalf Baechle 
2397726942fSRalf Baechle static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
2407726942fSRalf Baechle {
2417726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2427726942fSRalf Baechle 	apm_event_t event;
2437726942fSRalf Baechle 	int i = count, ret = 0;
2447726942fSRalf Baechle 
2457726942fSRalf Baechle 	if (count < sizeof(apm_event_t))
2467726942fSRalf Baechle 		return -EINVAL;
2477726942fSRalf Baechle 
2487726942fSRalf Baechle 	if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
2497726942fSRalf Baechle 		return -EAGAIN;
2507726942fSRalf Baechle 
2517726942fSRalf Baechle 	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
2527726942fSRalf Baechle 
2537726942fSRalf Baechle 	while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
2547726942fSRalf Baechle 		event = queue_get_event(&as->queue);
2557726942fSRalf Baechle 
2567726942fSRalf Baechle 		ret = -EFAULT;
2577726942fSRalf Baechle 		if (copy_to_user(buf, &event, sizeof(event)))
2587726942fSRalf Baechle 			break;
2597726942fSRalf Baechle 
2607726942fSRalf Baechle 		mutex_lock(&state_lock);
2617726942fSRalf Baechle 		if (as->suspend_state == SUSPEND_PENDING &&
2627726942fSRalf Baechle 		    (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
2637726942fSRalf Baechle 			as->suspend_state = SUSPEND_READ;
2647726942fSRalf Baechle 		mutex_unlock(&state_lock);
2657726942fSRalf Baechle 
2667726942fSRalf Baechle 		buf += sizeof(event);
2677726942fSRalf Baechle 		i -= sizeof(event);
2687726942fSRalf Baechle 	}
2697726942fSRalf Baechle 
2707726942fSRalf Baechle 	if (i < count)
2717726942fSRalf Baechle 		ret = count - i;
2727726942fSRalf Baechle 
2737726942fSRalf Baechle 	return ret;
2747726942fSRalf Baechle }
2757726942fSRalf Baechle 
2767726942fSRalf Baechle static unsigned int apm_poll(struct file *fp, poll_table * wait)
2777726942fSRalf Baechle {
2787726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2797726942fSRalf Baechle 
2807726942fSRalf Baechle 	poll_wait(fp, &apm_waitqueue, wait);
2817726942fSRalf Baechle 	return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
2827726942fSRalf Baechle }
2837726942fSRalf Baechle 
2847726942fSRalf Baechle /*
2857726942fSRalf Baechle  * apm_ioctl - handle APM ioctl
2867726942fSRalf Baechle  *
2877726942fSRalf Baechle  * APM_IOC_SUSPEND
2887726942fSRalf Baechle  *   This IOCTL is overloaded, and performs two functions.  It is used to:
2897726942fSRalf Baechle  *     - initiate a suspend
2907726942fSRalf Baechle  *     - acknowledge a suspend read from /dev/apm_bios.
2917726942fSRalf Baechle  *   Only when everyone who has opened /dev/apm_bios with write permission
2927726942fSRalf Baechle  *   has acknowledge does the actual suspend happen.
2937726942fSRalf Baechle  */
2947726942fSRalf Baechle static int
2957726942fSRalf Baechle apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
2967726942fSRalf Baechle {
2977726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
2987726942fSRalf Baechle 	unsigned long flags;
2997726942fSRalf Baechle 	int err = -EINVAL;
3007726942fSRalf Baechle 
3017726942fSRalf Baechle 	if (!as->suser || !as->writer)
3027726942fSRalf Baechle 		return -EPERM;
3037726942fSRalf Baechle 
3047726942fSRalf Baechle 	switch (cmd) {
3057726942fSRalf Baechle 	case APM_IOC_SUSPEND:
3067726942fSRalf Baechle 		mutex_lock(&state_lock);
3077726942fSRalf Baechle 
3087726942fSRalf Baechle 		as->suspend_result = -EINTR;
3097726942fSRalf Baechle 
3107726942fSRalf Baechle 		if (as->suspend_state == SUSPEND_READ) {
3117726942fSRalf Baechle 			int pending;
3127726942fSRalf Baechle 
3137726942fSRalf Baechle 			/*
3147726942fSRalf Baechle 			 * If we read a suspend command from /dev/apm_bios,
3157726942fSRalf Baechle 			 * then the corresponding APM_IOC_SUSPEND ioctl is
3167726942fSRalf Baechle 			 * interpreted as an acknowledge.
3177726942fSRalf Baechle 			 */
3187726942fSRalf Baechle 			as->suspend_state = SUSPEND_ACKED;
3197726942fSRalf Baechle 			suspends_pending--;
3207726942fSRalf Baechle 			pending = suspends_pending == 0;
3217726942fSRalf Baechle 			mutex_unlock(&state_lock);
3227726942fSRalf Baechle 
3237726942fSRalf Baechle 			/*
3247726942fSRalf Baechle 			 * If there are no further acknowledges required,
3257726942fSRalf Baechle 			 * suspend the system.
3267726942fSRalf Baechle 			 */
3277726942fSRalf Baechle 			if (pending)
3287726942fSRalf Baechle 				apm_suspend();
3297726942fSRalf Baechle 
3307726942fSRalf Baechle 			/*
3317726942fSRalf Baechle 			 * Wait for the suspend/resume to complete.  If there
3327726942fSRalf Baechle 			 * are pending acknowledges, we wait here for them.
3337726942fSRalf Baechle 			 */
3347726942fSRalf Baechle 			flags = current->flags;
3357726942fSRalf Baechle 
3367726942fSRalf Baechle 			wait_event(apm_suspend_waitqueue,
3377726942fSRalf Baechle 				   as->suspend_state == SUSPEND_DONE);
3387726942fSRalf Baechle 		} else {
3397726942fSRalf Baechle 			as->suspend_state = SUSPEND_WAIT;
3407726942fSRalf Baechle 			mutex_unlock(&state_lock);
3417726942fSRalf Baechle 
3427726942fSRalf Baechle 			/*
3437726942fSRalf Baechle 			 * Otherwise it is a request to suspend the system.
3447726942fSRalf Baechle 			 * Queue an event for all readers, and expect an
3457726942fSRalf Baechle 			 * acknowledge from all writers who haven't already
3467726942fSRalf Baechle 			 * acknowledged.
3477726942fSRalf Baechle 			 */
3487726942fSRalf Baechle 			err = queue_suspend_event(APM_USER_SUSPEND, as);
3497726942fSRalf Baechle 			if (err < 0) {
3507726942fSRalf Baechle 				/*
3517726942fSRalf Baechle 				 * Avoid taking the lock here - this
3527726942fSRalf Baechle 				 * should be fine.
3537726942fSRalf Baechle 				 */
3547726942fSRalf Baechle 				as->suspend_state = SUSPEND_NONE;
3557726942fSRalf Baechle 				break;
3567726942fSRalf Baechle 			}
3577726942fSRalf Baechle 
3587726942fSRalf Baechle 			if (err > 0)
3597726942fSRalf Baechle 				apm_suspend();
3607726942fSRalf Baechle 
3617726942fSRalf Baechle 			/*
3627726942fSRalf Baechle 			 * Wait for the suspend/resume to complete.  If there
3637726942fSRalf Baechle 			 * are pending acknowledges, we wait here for them.
3647726942fSRalf Baechle 			 */
3657726942fSRalf Baechle 			flags = current->flags;
3667726942fSRalf Baechle 
3677726942fSRalf Baechle 			wait_event_interruptible(apm_suspend_waitqueue,
3687726942fSRalf Baechle 					 as->suspend_state == SUSPEND_DONE);
3697726942fSRalf Baechle 		}
3707726942fSRalf Baechle 
3717726942fSRalf Baechle 		current->flags = flags;
3727726942fSRalf Baechle 
3737726942fSRalf Baechle 		mutex_lock(&state_lock);
3747726942fSRalf Baechle 		err = as->suspend_result;
3757726942fSRalf Baechle 		as->suspend_state = SUSPEND_NONE;
3767726942fSRalf Baechle 		mutex_unlock(&state_lock);
3777726942fSRalf Baechle 		break;
3787726942fSRalf Baechle 	}
3797726942fSRalf Baechle 
3807726942fSRalf Baechle 	return err;
3817726942fSRalf Baechle }
3827726942fSRalf Baechle 
3837726942fSRalf Baechle static int apm_release(struct inode * inode, struct file * filp)
3847726942fSRalf Baechle {
3857726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
3867726942fSRalf Baechle 	int pending = 0;
3877726942fSRalf Baechle 
3887726942fSRalf Baechle 	filp->private_data = NULL;
3897726942fSRalf Baechle 
3907726942fSRalf Baechle 	down_write(&user_list_lock);
3917726942fSRalf Baechle 	list_del(&as->list);
3927726942fSRalf Baechle 	up_write(&user_list_lock);
3937726942fSRalf Baechle 
3947726942fSRalf Baechle 	/*
3957726942fSRalf Baechle 	 * We are now unhooked from the chain.  As far as new
3967726942fSRalf Baechle 	 * events are concerned, we no longer exist.  However, we
3977726942fSRalf Baechle 	 * need to balance suspends_pending, which means the
3987726942fSRalf Baechle 	 * possibility of sleeping.
3997726942fSRalf Baechle 	 */
4007726942fSRalf Baechle 	mutex_lock(&state_lock);
4017726942fSRalf Baechle 	if (as->suspend_state != SUSPEND_NONE) {
4027726942fSRalf Baechle 		suspends_pending -= 1;
4037726942fSRalf Baechle 		pending = suspends_pending == 0;
4047726942fSRalf Baechle 	}
4057726942fSRalf Baechle 	mutex_unlock(&state_lock);
4067726942fSRalf Baechle 	if (pending)
4077726942fSRalf Baechle 		apm_suspend();
4087726942fSRalf Baechle 
4097726942fSRalf Baechle 	kfree(as);
4107726942fSRalf Baechle 	return 0;
4117726942fSRalf Baechle }
4127726942fSRalf Baechle 
4137726942fSRalf Baechle static int apm_open(struct inode * inode, struct file * filp)
4147726942fSRalf Baechle {
4157726942fSRalf Baechle 	struct apm_user *as;
4167726942fSRalf Baechle 
4177726942fSRalf Baechle 	as = kzalloc(sizeof(*as), GFP_KERNEL);
4187726942fSRalf Baechle 	if (as) {
4197726942fSRalf Baechle 		/*
4207726942fSRalf Baechle 		 * XXX - this is a tiny bit broken, when we consider BSD
4217726942fSRalf Baechle 		 * process accounting. If the device is opened by root, we
4227726942fSRalf Baechle 		 * instantly flag that we used superuser privs. Who knows,
4237726942fSRalf Baechle 		 * we might close the device immediately without doing a
4247726942fSRalf Baechle 		 * privileged operation -- cevans
4257726942fSRalf Baechle 		 */
4267726942fSRalf Baechle 		as->suser = capable(CAP_SYS_ADMIN);
4277726942fSRalf Baechle 		as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
4287726942fSRalf Baechle 		as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
4297726942fSRalf Baechle 
4307726942fSRalf Baechle 		down_write(&user_list_lock);
4317726942fSRalf Baechle 		list_add(&as->list, &apm_user_list);
4327726942fSRalf Baechle 		up_write(&user_list_lock);
4337726942fSRalf Baechle 
4347726942fSRalf Baechle 		filp->private_data = as;
4357726942fSRalf Baechle 	}
4367726942fSRalf Baechle 
4377726942fSRalf Baechle 	return as ? 0 : -ENOMEM;
4387726942fSRalf Baechle }
4397726942fSRalf Baechle 
4407726942fSRalf Baechle static struct file_operations apm_bios_fops = {
4417726942fSRalf Baechle 	.owner		= THIS_MODULE,
4427726942fSRalf Baechle 	.read		= apm_read,
4437726942fSRalf Baechle 	.poll		= apm_poll,
4447726942fSRalf Baechle 	.ioctl		= apm_ioctl,
4457726942fSRalf Baechle 	.open		= apm_open,
4467726942fSRalf Baechle 	.release	= apm_release,
4477726942fSRalf Baechle };
4487726942fSRalf Baechle 
4497726942fSRalf Baechle static struct miscdevice apm_device = {
4507726942fSRalf Baechle 	.minor		= APM_MINOR_DEV,
4517726942fSRalf Baechle 	.name		= "apm_bios",
4527726942fSRalf Baechle 	.fops		= &apm_bios_fops
4537726942fSRalf Baechle };
4547726942fSRalf Baechle 
4557726942fSRalf Baechle 
4567726942fSRalf Baechle #ifdef CONFIG_PROC_FS
4577726942fSRalf Baechle /*
4587726942fSRalf Baechle  * Arguments, with symbols from linux/apm_bios.h.
4597726942fSRalf Baechle  *
4607726942fSRalf Baechle  *   0) Linux driver version (this will change if format changes)
4617726942fSRalf Baechle  *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
4627726942fSRalf Baechle  *   2) APM flags from APM Installation Check (0x00):
4637726942fSRalf Baechle  *	bit 0: APM_16_BIT_SUPPORT
4647726942fSRalf Baechle  *	bit 1: APM_32_BIT_SUPPORT
4657726942fSRalf Baechle  *	bit 2: APM_IDLE_SLOWS_CLOCK
4667726942fSRalf Baechle  *	bit 3: APM_BIOS_DISABLED
4677726942fSRalf Baechle  *	bit 4: APM_BIOS_DISENGAGED
4687726942fSRalf Baechle  *   3) AC line status
4697726942fSRalf Baechle  *	0x00: Off-line
4707726942fSRalf Baechle  *	0x01: On-line
4717726942fSRalf Baechle  *	0x02: On backup power (BIOS >= 1.1 only)
4727726942fSRalf Baechle  *	0xff: Unknown
4737726942fSRalf Baechle  *   4) Battery status
4747726942fSRalf Baechle  *	0x00: High
4757726942fSRalf Baechle  *	0x01: Low
4767726942fSRalf Baechle  *	0x02: Critical
4777726942fSRalf Baechle  *	0x03: Charging
4787726942fSRalf Baechle  *	0x04: Selected battery not present (BIOS >= 1.2 only)
4797726942fSRalf Baechle  *	0xff: Unknown
4807726942fSRalf Baechle  *   5) Battery flag
4817726942fSRalf Baechle  *	bit 0: High
4827726942fSRalf Baechle  *	bit 1: Low
4837726942fSRalf Baechle  *	bit 2: Critical
4847726942fSRalf Baechle  *	bit 3: Charging
4857726942fSRalf Baechle  *	bit 7: No system battery
4867726942fSRalf Baechle  *	0xff: Unknown
4877726942fSRalf Baechle  *   6) Remaining battery life (percentage of charge):
4887726942fSRalf Baechle  *	0-100: valid
4897726942fSRalf Baechle  *	-1: Unknown
4907726942fSRalf Baechle  *   7) Remaining battery life (time units):
4917726942fSRalf Baechle  *	Number of remaining minutes or seconds
4927726942fSRalf Baechle  *	-1: Unknown
4937726942fSRalf Baechle  *   8) min = minutes; sec = seconds
4947726942fSRalf Baechle  */
4957726942fSRalf Baechle static int apm_get_info(char *buf, char **start, off_t fpos, int length)
4967726942fSRalf Baechle {
4977726942fSRalf Baechle 	struct apm_power_info info;
4987726942fSRalf Baechle 	char *units;
4997726942fSRalf Baechle 	int ret;
5007726942fSRalf Baechle 
5017726942fSRalf Baechle 	info.ac_line_status = 0xff;
5027726942fSRalf Baechle 	info.battery_status = 0xff;
5037726942fSRalf Baechle 	info.battery_flag   = 0xff;
5047726942fSRalf Baechle 	info.battery_life   = -1;
5057726942fSRalf Baechle 	info.time	    = -1;
5067726942fSRalf Baechle 	info.units	    = -1;
5077726942fSRalf Baechle 
5087726942fSRalf Baechle 	if (apm_get_power_status)
5097726942fSRalf Baechle 		apm_get_power_status(&info);
5107726942fSRalf Baechle 
5117726942fSRalf Baechle 	switch (info.units) {
5127726942fSRalf Baechle 	default:	units = "?";	break;
5137726942fSRalf Baechle 	case 0: 	units = "min";	break;
5147726942fSRalf Baechle 	case 1: 	units = "sec";	break;
5157726942fSRalf Baechle 	}
5167726942fSRalf Baechle 
5177726942fSRalf Baechle 	ret = sprintf(buf, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
5187726942fSRalf Baechle 		     driver_version, APM_32_BIT_SUPPORT,
5197726942fSRalf Baechle 		     info.ac_line_status, info.battery_status,
5207726942fSRalf Baechle 		     info.battery_flag, info.battery_life,
5217726942fSRalf Baechle 		     info.time, units);
5227726942fSRalf Baechle 
5237726942fSRalf Baechle 	return ret;
5247726942fSRalf Baechle }
5257726942fSRalf Baechle #endif
5267726942fSRalf Baechle 
5277726942fSRalf Baechle static int kapmd(void *arg)
5287726942fSRalf Baechle {
5297726942fSRalf Baechle 	do {
5307726942fSRalf Baechle 		apm_event_t event;
5317726942fSRalf Baechle 		int ret;
5327726942fSRalf Baechle 
5337726942fSRalf Baechle 		wait_event_interruptible(kapmd_wait,
5347726942fSRalf Baechle 				!queue_empty(&kapmd_queue) || kthread_should_stop());
5357726942fSRalf Baechle 
5367726942fSRalf Baechle 		if (kthread_should_stop())
5377726942fSRalf Baechle 			break;
5387726942fSRalf Baechle 
5397726942fSRalf Baechle 		spin_lock_irq(&kapmd_queue_lock);
5407726942fSRalf Baechle 		event = 0;
5417726942fSRalf Baechle 		if (!queue_empty(&kapmd_queue))
5427726942fSRalf Baechle 			event = queue_get_event(&kapmd_queue);
5437726942fSRalf Baechle 		spin_unlock_irq(&kapmd_queue_lock);
5447726942fSRalf Baechle 
5457726942fSRalf Baechle 		switch (event) {
5467726942fSRalf Baechle 		case 0:
5477726942fSRalf Baechle 			break;
5487726942fSRalf Baechle 
5497726942fSRalf Baechle 		case APM_LOW_BATTERY:
5507726942fSRalf Baechle 		case APM_POWER_STATUS_CHANGE:
5517726942fSRalf Baechle 			queue_event(event);
5527726942fSRalf Baechle 			break;
5537726942fSRalf Baechle 
5547726942fSRalf Baechle 		case APM_USER_SUSPEND:
5557726942fSRalf Baechle 		case APM_SYS_SUSPEND:
5567726942fSRalf Baechle 			ret = queue_suspend_event(event, NULL);
5577726942fSRalf Baechle 			if (ret < 0) {
5587726942fSRalf Baechle 				/*
5597726942fSRalf Baechle 				 * We were busy.  Try again in 50ms.
5607726942fSRalf Baechle 				 */
5617726942fSRalf Baechle 				queue_add_event(&kapmd_queue, event);
5627726942fSRalf Baechle 				msleep(50);
5637726942fSRalf Baechle 			}
5647726942fSRalf Baechle 			if (ret > 0)
5657726942fSRalf Baechle 				apm_suspend();
5667726942fSRalf Baechle 			break;
5677726942fSRalf Baechle 
5687726942fSRalf Baechle 		case APM_CRITICAL_SUSPEND:
5697726942fSRalf Baechle 			apm_suspend();
5707726942fSRalf Baechle 			break;
5717726942fSRalf Baechle 		}
5727726942fSRalf Baechle 	} while (1);
5737726942fSRalf Baechle 
5747726942fSRalf Baechle 	return 0;
5757726942fSRalf Baechle }
5767726942fSRalf Baechle 
5777726942fSRalf Baechle static int __init apm_init(void)
5787726942fSRalf Baechle {
5797726942fSRalf Baechle 	int ret;
5807726942fSRalf Baechle 
5817726942fSRalf Baechle 	if (apm_disabled) {
5827726942fSRalf Baechle 		printk(KERN_NOTICE "apm: disabled on user request.\n");
5837726942fSRalf Baechle 		return -ENODEV;
5847726942fSRalf Baechle 	}
5857726942fSRalf Baechle 
5867726942fSRalf Baechle 	kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
5877726942fSRalf Baechle 	if (IS_ERR(kapmd_tsk)) {
5887726942fSRalf Baechle 		ret = PTR_ERR(kapmd_tsk);
5897726942fSRalf Baechle 		kapmd_tsk = NULL;
5907726942fSRalf Baechle 		return ret;
5917726942fSRalf Baechle 	}
5927726942fSRalf Baechle 	wake_up_process(kapmd_tsk);
5937726942fSRalf Baechle 
5947726942fSRalf Baechle #ifdef CONFIG_PROC_FS
5957726942fSRalf Baechle 	create_proc_info_entry("apm", 0, NULL, apm_get_info);
5967726942fSRalf Baechle #endif
5977726942fSRalf Baechle 
5987726942fSRalf Baechle 	ret = misc_register(&apm_device);
5997726942fSRalf Baechle 	if (ret != 0) {
6007726942fSRalf Baechle 		remove_proc_entry("apm", NULL);
6017726942fSRalf Baechle 		kthread_stop(kapmd_tsk);
6027726942fSRalf Baechle 	}
6037726942fSRalf Baechle 
6047726942fSRalf Baechle 	return ret;
6057726942fSRalf Baechle }
6067726942fSRalf Baechle 
6077726942fSRalf Baechle static void __exit apm_exit(void)
6087726942fSRalf Baechle {
6097726942fSRalf Baechle 	misc_deregister(&apm_device);
6107726942fSRalf Baechle 	remove_proc_entry("apm", NULL);
6117726942fSRalf Baechle 
6127726942fSRalf Baechle 	kthread_stop(kapmd_tsk);
6137726942fSRalf Baechle }
6147726942fSRalf Baechle 
6157726942fSRalf Baechle module_init(apm_init);
6167726942fSRalf Baechle module_exit(apm_exit);
6177726942fSRalf Baechle 
6187726942fSRalf Baechle MODULE_AUTHOR("Stephen Rothwell");
6197726942fSRalf Baechle MODULE_DESCRIPTION("Advanced Power Management");
6207726942fSRalf Baechle MODULE_LICENSE("GPL");
6217726942fSRalf Baechle 
6227726942fSRalf Baechle #ifndef MODULE
6237726942fSRalf Baechle static int __init apm_setup(char *str)
6247726942fSRalf Baechle {
6257726942fSRalf Baechle 	while ((str != NULL) && (*str != '\0')) {
6267726942fSRalf Baechle 		if (strncmp(str, "off", 3) == 0)
6277726942fSRalf Baechle 			apm_disabled = 1;
6287726942fSRalf Baechle 		if (strncmp(str, "on", 2) == 0)
6297726942fSRalf Baechle 			apm_disabled = 0;
6307726942fSRalf Baechle 		str = strchr(str, ',');
6317726942fSRalf Baechle 		if (str != NULL)
6327726942fSRalf Baechle 			str += strspn(str, ", \t");
6337726942fSRalf Baechle 	}
6347726942fSRalf Baechle 	return 1;
6357726942fSRalf Baechle }
6367726942fSRalf Baechle 
6377726942fSRalf Baechle __setup("apm=", apm_setup);
6387726942fSRalf Baechle #endif
6397726942fSRalf Baechle 
6407726942fSRalf Baechle /**
6417726942fSRalf Baechle  * apm_queue_event - queue an APM event for kapmd
6427726942fSRalf Baechle  * @event: APM event
6437726942fSRalf Baechle  *
6447726942fSRalf Baechle  * Queue an APM event for kapmd to process and ultimately take the
6457726942fSRalf Baechle  * appropriate action.  Only a subset of events are handled:
6467726942fSRalf Baechle  *   %APM_LOW_BATTERY
6477726942fSRalf Baechle  *   %APM_POWER_STATUS_CHANGE
6487726942fSRalf Baechle  *   %APM_USER_SUSPEND
6497726942fSRalf Baechle  *   %APM_SYS_SUSPEND
6507726942fSRalf Baechle  *   %APM_CRITICAL_SUSPEND
6517726942fSRalf Baechle  */
6527726942fSRalf Baechle void apm_queue_event(apm_event_t event)
6537726942fSRalf Baechle {
6547726942fSRalf Baechle 	unsigned long flags;
6557726942fSRalf Baechle 
6567726942fSRalf Baechle 	spin_lock_irqsave(&kapmd_queue_lock, flags);
6577726942fSRalf Baechle 	queue_add_event(&kapmd_queue, event);
6587726942fSRalf Baechle 	spin_unlock_irqrestore(&kapmd_queue_lock, flags);
6597726942fSRalf Baechle 
6607726942fSRalf Baechle 	wake_up_interruptible(&kapmd_wait);
6617726942fSRalf Baechle }
6627726942fSRalf Baechle EXPORT_SYMBOL(apm_queue_event);
663