xref: /linux/drivers/char/apm-emulation.c (revision f283d22713b0bdc147097c92c9b45855339cf1c8)
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  *
10631dd1a8SJustin P. Mattock  * This document is available from Microsoft at:
11631dd1a8SJustin P. Mattock  *    http://www.microsoft.com/whdc/archive/amp_12.mspx
127726942fSRalf Baechle  */
137726942fSRalf Baechle #include <linux/module.h>
147726942fSRalf Baechle #include <linux/poll.h>
157726942fSRalf Baechle #include <linux/slab.h>
16613655faSArnd Bergmann #include <linux/mutex.h>
177726942fSRalf Baechle #include <linux/proc_fs.h>
18647634dfSAlexey Dobriyan #include <linux/seq_file.h>
197726942fSRalf Baechle #include <linux/miscdevice.h>
207726942fSRalf Baechle #include <linux/apm_bios.h>
217726942fSRalf Baechle #include <linux/capability.h>
227726942fSRalf Baechle #include <linux/sched.h>
2395d9ffbeSRafael J. Wysocki #include <linux/suspend.h>
247726942fSRalf Baechle #include <linux/apm-emulation.h>
2583144186SRafael J. Wysocki #include <linux/freezer.h>
267726942fSRalf Baechle #include <linux/device.h>
277726942fSRalf Baechle #include <linux/kernel.h>
287726942fSRalf Baechle #include <linux/list.h>
297726942fSRalf Baechle #include <linux/init.h>
307726942fSRalf Baechle #include <linux/completion.h>
317726942fSRalf Baechle #include <linux/kthread.h>
327726942fSRalf Baechle #include <linux/delay.h>
337726942fSRalf Baechle 
347726942fSRalf Baechle #include <asm/system.h>
357726942fSRalf Baechle 
367726942fSRalf Baechle /*
377726942fSRalf Baechle  * The apm_bios device is one of the misc char devices.
387726942fSRalf Baechle  * This is its minor number.
397726942fSRalf Baechle  */
407726942fSRalf Baechle #define APM_MINOR_DEV	134
417726942fSRalf Baechle 
427726942fSRalf Baechle /*
43395cf969SPaul Bolle  * One option can be changed at boot time as follows:
447726942fSRalf Baechle  *	apm=on/off			enable/disable APM
457726942fSRalf Baechle  */
467726942fSRalf Baechle 
477726942fSRalf Baechle /*
487726942fSRalf Baechle  * Maximum number of events stored
497726942fSRalf Baechle  */
507726942fSRalf Baechle #define APM_MAX_EVENTS		16
517726942fSRalf Baechle 
527726942fSRalf Baechle struct apm_queue {
537726942fSRalf Baechle 	unsigned int		event_head;
547726942fSRalf Baechle 	unsigned int		event_tail;
557726942fSRalf Baechle 	apm_event_t		events[APM_MAX_EVENTS];
567726942fSRalf Baechle };
577726942fSRalf Baechle 
587726942fSRalf Baechle /*
59d20a4dcaSJohannes Berg  * thread states (for threads using a writable /dev/apm_bios fd):
60d20a4dcaSJohannes Berg  *
61d20a4dcaSJohannes Berg  * SUSPEND_NONE:	nothing happening
62d20a4dcaSJohannes Berg  * SUSPEND_PENDING:	suspend event queued for thread and pending to be read
63d20a4dcaSJohannes Berg  * SUSPEND_READ:	suspend event read, pending acknowledgement
64d20a4dcaSJohannes Berg  * SUSPEND_ACKED:	acknowledgement received from thread (via ioctl),
65d20a4dcaSJohannes Berg  *			waiting for resume
66d20a4dcaSJohannes Berg  * SUSPEND_ACKTO:	acknowledgement timeout
67d20a4dcaSJohannes Berg  * SUSPEND_DONE:	thread had acked suspend and is now notified of
68d20a4dcaSJohannes Berg  *			resume
69d20a4dcaSJohannes Berg  *
70d20a4dcaSJohannes Berg  * SUSPEND_WAIT:	this thread invoked suspend and is waiting for resume
71d20a4dcaSJohannes Berg  *
72d20a4dcaSJohannes Berg  * A thread migrates in one of three paths:
73d20a4dcaSJohannes Berg  *	NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE
74d20a4dcaSJohannes Berg  *				    -6-> ACKTO -7-> NONE
75d20a4dcaSJohannes Berg  *	NONE -8-> WAIT -9-> NONE
76d20a4dcaSJohannes Berg  *
77d20a4dcaSJohannes Berg  * While in PENDING or READ, the thread is accounted for in the
78d20a4dcaSJohannes Berg  * suspend_acks_pending counter.
79d20a4dcaSJohannes Berg  *
80d20a4dcaSJohannes Berg  * The transitions are invoked as follows:
81d20a4dcaSJohannes Berg  *	1: suspend event is signalled from the core PM code
82d20a4dcaSJohannes Berg  *	2: the suspend event is read from the fd by the userspace thread
83d20a4dcaSJohannes Berg  *	3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack)
84d20a4dcaSJohannes Berg  *	4: core PM code signals that we have resumed
85d20a4dcaSJohannes Berg  *	5: APM_IOC_SUSPEND ioctl returns
86d20a4dcaSJohannes Berg  *
87d20a4dcaSJohannes Berg  *	6: the notifier invoked from the core PM code timed out waiting
88d20a4dcaSJohannes Berg  *	   for all relevant threds to enter ACKED state and puts those
89d20a4dcaSJohannes Berg  *	   that haven't into ACKTO
90d20a4dcaSJohannes Berg  *	7: those threads issue APM_IOC_SUSPEND ioctl too late,
91d20a4dcaSJohannes Berg  *	   get an error
92d20a4dcaSJohannes Berg  *
93d20a4dcaSJohannes Berg  *	8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend),
94d20a4dcaSJohannes Berg  *	   ioctl code invokes pm_suspend()
95d20a4dcaSJohannes Berg  *	9: pm_suspend() returns indicating resume
96d20a4dcaSJohannes Berg  */
97d20a4dcaSJohannes Berg enum apm_suspend_state {
98d20a4dcaSJohannes Berg 	SUSPEND_NONE,
99d20a4dcaSJohannes Berg 	SUSPEND_PENDING,
100d20a4dcaSJohannes Berg 	SUSPEND_READ,
101d20a4dcaSJohannes Berg 	SUSPEND_ACKED,
102d20a4dcaSJohannes Berg 	SUSPEND_ACKTO,
103d20a4dcaSJohannes Berg 	SUSPEND_WAIT,
104d20a4dcaSJohannes Berg 	SUSPEND_DONE,
105d20a4dcaSJohannes Berg };
106d20a4dcaSJohannes Berg 
107d20a4dcaSJohannes Berg /*
1087726942fSRalf Baechle  * The per-file APM data
1097726942fSRalf Baechle  */
1107726942fSRalf Baechle struct apm_user {
1117726942fSRalf Baechle 	struct list_head	list;
1127726942fSRalf Baechle 
1137726942fSRalf Baechle 	unsigned int		suser: 1;
1147726942fSRalf Baechle 	unsigned int		writer: 1;
1157726942fSRalf Baechle 	unsigned int		reader: 1;
1167726942fSRalf Baechle 
1177726942fSRalf Baechle 	int			suspend_result;
118d20a4dcaSJohannes Berg 	enum apm_suspend_state	suspend_state;
1197726942fSRalf Baechle 
1207726942fSRalf Baechle 	struct apm_queue	queue;
1217726942fSRalf Baechle };
1227726942fSRalf Baechle 
1237726942fSRalf Baechle /*
1247726942fSRalf Baechle  * Local variables
1257726942fSRalf Baechle  */
126d20a4dcaSJohannes Berg static atomic_t suspend_acks_pending = ATOMIC_INIT(0);
127d20a4dcaSJohannes Berg static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0);
1287726942fSRalf Baechle static int apm_disabled;
1297726942fSRalf Baechle static struct task_struct *kapmd_tsk;
1307726942fSRalf Baechle 
1317726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
1327726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
1337726942fSRalf Baechle 
1347726942fSRalf Baechle /*
1357726942fSRalf Baechle  * This is a list of everyone who has opened /dev/apm_bios
1367726942fSRalf Baechle  */
1377726942fSRalf Baechle static DECLARE_RWSEM(user_list_lock);
1387726942fSRalf Baechle static LIST_HEAD(apm_user_list);
1397726942fSRalf Baechle 
1407726942fSRalf Baechle /*
1417726942fSRalf Baechle  * kapmd info.  kapmd provides us a process context to handle
1427726942fSRalf Baechle  * "APM" events within - specifically necessary if we're going
1437726942fSRalf Baechle  * to be suspending the system.
1447726942fSRalf Baechle  */
1457726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
1467726942fSRalf Baechle static DEFINE_SPINLOCK(kapmd_queue_lock);
1477726942fSRalf Baechle static struct apm_queue kapmd_queue;
1487726942fSRalf Baechle 
1497726942fSRalf Baechle static DEFINE_MUTEX(state_lock);
1507726942fSRalf Baechle 
1517726942fSRalf Baechle static const char driver_version[] = "1.13";	/* no spaces */
1527726942fSRalf Baechle 
1537726942fSRalf Baechle 
1547726942fSRalf Baechle 
1557726942fSRalf Baechle /*
1567726942fSRalf Baechle  * Compatibility cruft until the IPAQ people move over to the new
1577726942fSRalf Baechle  * interface.
1587726942fSRalf Baechle  */
1597726942fSRalf Baechle static void __apm_get_power_status(struct apm_power_info *info)
1607726942fSRalf Baechle {
1617726942fSRalf Baechle }
1627726942fSRalf Baechle 
1637726942fSRalf Baechle /*
1647726942fSRalf Baechle  * This allows machines to provide their own "apm get power status" function.
1657726942fSRalf Baechle  */
1667726942fSRalf Baechle void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
1677726942fSRalf Baechle EXPORT_SYMBOL(apm_get_power_status);
1687726942fSRalf Baechle 
1697726942fSRalf Baechle 
1707726942fSRalf Baechle /*
1717726942fSRalf Baechle  * APM event queue management.
1727726942fSRalf Baechle  */
1737726942fSRalf Baechle static inline int queue_empty(struct apm_queue *q)
1747726942fSRalf Baechle {
1757726942fSRalf Baechle 	return q->event_head == q->event_tail;
1767726942fSRalf Baechle }
1777726942fSRalf Baechle 
1787726942fSRalf Baechle static inline apm_event_t queue_get_event(struct apm_queue *q)
1797726942fSRalf Baechle {
1807726942fSRalf Baechle 	q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1817726942fSRalf Baechle 	return q->events[q->event_tail];
1827726942fSRalf Baechle }
1837726942fSRalf Baechle 
1847726942fSRalf Baechle static void queue_add_event(struct apm_queue *q, apm_event_t event)
1857726942fSRalf Baechle {
1867726942fSRalf Baechle 	q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
1877726942fSRalf Baechle 	if (q->event_head == q->event_tail) {
1887726942fSRalf Baechle 		static int notified;
1897726942fSRalf Baechle 
1907726942fSRalf Baechle 		if (notified++ == 0)
1917726942fSRalf Baechle 		    printk(KERN_ERR "apm: an event queue overflowed\n");
1927726942fSRalf Baechle 		q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1937726942fSRalf Baechle 	}
1947726942fSRalf Baechle 	q->events[q->event_head] = event;
1957726942fSRalf Baechle }
1967726942fSRalf Baechle 
1977726942fSRalf Baechle static void queue_event(apm_event_t event)
1987726942fSRalf Baechle {
1997726942fSRalf Baechle 	struct apm_user *as;
2007726942fSRalf Baechle 
2017726942fSRalf Baechle 	down_read(&user_list_lock);
2027726942fSRalf Baechle 	list_for_each_entry(as, &apm_user_list, list) {
2037726942fSRalf Baechle 		if (as->reader)
2047726942fSRalf Baechle 			queue_add_event(&as->queue, event);
2057726942fSRalf Baechle 	}
2067726942fSRalf Baechle 	up_read(&user_list_lock);
2077726942fSRalf Baechle 	wake_up_interruptible(&apm_waitqueue);
2087726942fSRalf Baechle }
2097726942fSRalf Baechle 
2107726942fSRalf Baechle static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
2117726942fSRalf Baechle {
2127726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2137726942fSRalf Baechle 	apm_event_t event;
2147726942fSRalf Baechle 	int i = count, ret = 0;
2157726942fSRalf Baechle 
2167726942fSRalf Baechle 	if (count < sizeof(apm_event_t))
2177726942fSRalf Baechle 		return -EINVAL;
2187726942fSRalf Baechle 
2197726942fSRalf Baechle 	if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
2207726942fSRalf Baechle 		return -EAGAIN;
2217726942fSRalf Baechle 
2227726942fSRalf Baechle 	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
2237726942fSRalf Baechle 
2247726942fSRalf Baechle 	while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
2257726942fSRalf Baechle 		event = queue_get_event(&as->queue);
2267726942fSRalf Baechle 
2277726942fSRalf Baechle 		ret = -EFAULT;
2287726942fSRalf Baechle 		if (copy_to_user(buf, &event, sizeof(event)))
2297726942fSRalf Baechle 			break;
2307726942fSRalf Baechle 
2317726942fSRalf Baechle 		mutex_lock(&state_lock);
2327726942fSRalf Baechle 		if (as->suspend_state == SUSPEND_PENDING &&
2337726942fSRalf Baechle 		    (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
2347726942fSRalf Baechle 			as->suspend_state = SUSPEND_READ;
2357726942fSRalf Baechle 		mutex_unlock(&state_lock);
2367726942fSRalf Baechle 
2377726942fSRalf Baechle 		buf += sizeof(event);
2387726942fSRalf Baechle 		i -= sizeof(event);
2397726942fSRalf Baechle 	}
2407726942fSRalf Baechle 
2417726942fSRalf Baechle 	if (i < count)
2427726942fSRalf Baechle 		ret = count - i;
2437726942fSRalf Baechle 
2447726942fSRalf Baechle 	return ret;
2457726942fSRalf Baechle }
2467726942fSRalf Baechle 
2477726942fSRalf Baechle static unsigned int apm_poll(struct file *fp, poll_table * wait)
2487726942fSRalf Baechle {
2497726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2507726942fSRalf Baechle 
2517726942fSRalf Baechle 	poll_wait(fp, &apm_waitqueue, wait);
2527726942fSRalf Baechle 	return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
2537726942fSRalf Baechle }
2547726942fSRalf Baechle 
2557726942fSRalf Baechle /*
2567726942fSRalf Baechle  * apm_ioctl - handle APM ioctl
2577726942fSRalf Baechle  *
2587726942fSRalf Baechle  * APM_IOC_SUSPEND
2597726942fSRalf Baechle  *   This IOCTL is overloaded, and performs two functions.  It is used to:
2607726942fSRalf Baechle  *     - initiate a suspend
2617726942fSRalf Baechle  *     - acknowledge a suspend read from /dev/apm_bios.
2627726942fSRalf Baechle  *   Only when everyone who has opened /dev/apm_bios with write permission
2637726942fSRalf Baechle  *   has acknowledge does the actual suspend happen.
2647726942fSRalf Baechle  */
26555929332SArnd Bergmann static long
26655929332SArnd Bergmann apm_ioctl(struct file *filp, u_int cmd, u_long arg)
2677726942fSRalf Baechle {
2687726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
2697726942fSRalf Baechle 	int err = -EINVAL;
2707726942fSRalf Baechle 
2717726942fSRalf Baechle 	if (!as->suser || !as->writer)
2727726942fSRalf Baechle 		return -EPERM;
2737726942fSRalf Baechle 
2747726942fSRalf Baechle 	switch (cmd) {
2757726942fSRalf Baechle 	case APM_IOC_SUSPEND:
2767726942fSRalf Baechle 		mutex_lock(&state_lock);
2777726942fSRalf Baechle 
2787726942fSRalf Baechle 		as->suspend_result = -EINTR;
2797726942fSRalf Baechle 
280d20a4dcaSJohannes Berg 		switch (as->suspend_state) {
281d20a4dcaSJohannes Berg 		case SUSPEND_READ:
2827726942fSRalf Baechle 			/*
2837726942fSRalf Baechle 			 * If we read a suspend command from /dev/apm_bios,
2847726942fSRalf Baechle 			 * then the corresponding APM_IOC_SUSPEND ioctl is
2857726942fSRalf Baechle 			 * interpreted as an acknowledge.
2867726942fSRalf Baechle 			 */
2877726942fSRalf Baechle 			as->suspend_state = SUSPEND_ACKED;
288d20a4dcaSJohannes Berg 			atomic_dec(&suspend_acks_pending);
2897726942fSRalf Baechle 			mutex_unlock(&state_lock);
2907726942fSRalf Baechle 
2917726942fSRalf Baechle 			/*
292d20a4dcaSJohannes Berg 			 * suspend_acks_pending changed, the notifier needs to
293d20a4dcaSJohannes Berg 			 * be woken up for this
2947726942fSRalf Baechle 			 */
295d20a4dcaSJohannes Berg 			wake_up(&apm_suspend_waitqueue);
2967726942fSRalf Baechle 
2977726942fSRalf Baechle 			/*
2987726942fSRalf Baechle 			 * Wait for the suspend/resume to complete.  If there
2997726942fSRalf Baechle 			 * are pending acknowledges, we wait here for them.
3001d927c3bSTejun Heo 			 * wait_event_freezable() is interruptible and pending
3011d927c3bSTejun Heo 			 * signal can cause busy looping.  We aren't doing
3021d927c3bSTejun Heo 			 * anything critical, chill a bit on each iteration.
3037726942fSRalf Baechle 			 */
3041d927c3bSTejun Heo 			while (wait_event_freezable(apm_suspend_waitqueue,
305*f283d227SNeilBrown 					as->suspend_state != SUSPEND_ACKED))
3061d927c3bSTejun Heo 				msleep(10);
307d20a4dcaSJohannes Berg 			break;
308d20a4dcaSJohannes Berg 		case SUSPEND_ACKTO:
309d20a4dcaSJohannes Berg 			as->suspend_result = -ETIMEDOUT;
310d20a4dcaSJohannes Berg 			mutex_unlock(&state_lock);
311d20a4dcaSJohannes Berg 			break;
312d20a4dcaSJohannes Berg 		default:
3137726942fSRalf Baechle 			as->suspend_state = SUSPEND_WAIT;
3147726942fSRalf Baechle 			mutex_unlock(&state_lock);
3157726942fSRalf Baechle 
3167726942fSRalf Baechle 			/*
3177726942fSRalf Baechle 			 * Otherwise it is a request to suspend the system.
318d20a4dcaSJohannes Berg 			 * Just invoke pm_suspend(), we'll handle it from
319d20a4dcaSJohannes Berg 			 * there via the notifier.
3207726942fSRalf Baechle 			 */
321d20a4dcaSJohannes Berg 			as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
3227726942fSRalf Baechle 		}
3237726942fSRalf Baechle 
3247726942fSRalf Baechle 		mutex_lock(&state_lock);
3257726942fSRalf Baechle 		err = as->suspend_result;
3267726942fSRalf Baechle 		as->suspend_state = SUSPEND_NONE;
3277726942fSRalf Baechle 		mutex_unlock(&state_lock);
3287726942fSRalf Baechle 		break;
3297726942fSRalf Baechle 	}
3307726942fSRalf Baechle 
3317726942fSRalf Baechle 	return err;
3327726942fSRalf Baechle }
3337726942fSRalf Baechle 
3347726942fSRalf Baechle static int apm_release(struct inode * inode, struct file * filp)
3357726942fSRalf Baechle {
3367726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
3377726942fSRalf Baechle 
3387726942fSRalf Baechle 	filp->private_data = NULL;
3397726942fSRalf Baechle 
3407726942fSRalf Baechle 	down_write(&user_list_lock);
3417726942fSRalf Baechle 	list_del(&as->list);
3427726942fSRalf Baechle 	up_write(&user_list_lock);
3437726942fSRalf Baechle 
3447726942fSRalf Baechle 	/*
3457726942fSRalf Baechle 	 * We are now unhooked from the chain.  As far as new
346d20a4dcaSJohannes Berg 	 * events are concerned, we no longer exist.
3477726942fSRalf Baechle 	 */
3487726942fSRalf Baechle 	mutex_lock(&state_lock);
349d20a4dcaSJohannes Berg 	if (as->suspend_state == SUSPEND_PENDING ||
350d20a4dcaSJohannes Berg 	    as->suspend_state == SUSPEND_READ)
351d20a4dcaSJohannes Berg 		atomic_dec(&suspend_acks_pending);
3527726942fSRalf Baechle 	mutex_unlock(&state_lock);
353d20a4dcaSJohannes Berg 
354d20a4dcaSJohannes Berg 	wake_up(&apm_suspend_waitqueue);
3557726942fSRalf Baechle 
3567726942fSRalf Baechle 	kfree(as);
3577726942fSRalf Baechle 	return 0;
3587726942fSRalf Baechle }
3597726942fSRalf Baechle 
3607726942fSRalf Baechle static int apm_open(struct inode * inode, struct file * filp)
3617726942fSRalf Baechle {
3627726942fSRalf Baechle 	struct apm_user *as;
3637726942fSRalf Baechle 
3647726942fSRalf Baechle 	as = kzalloc(sizeof(*as), GFP_KERNEL);
3657726942fSRalf Baechle 	if (as) {
3667726942fSRalf Baechle 		/*
3677726942fSRalf Baechle 		 * XXX - this is a tiny bit broken, when we consider BSD
3687726942fSRalf Baechle 		 * process accounting. If the device is opened by root, we
3697726942fSRalf Baechle 		 * instantly flag that we used superuser privs. Who knows,
3707726942fSRalf Baechle 		 * we might close the device immediately without doing a
3717726942fSRalf Baechle 		 * privileged operation -- cevans
3727726942fSRalf Baechle 		 */
3737726942fSRalf Baechle 		as->suser = capable(CAP_SYS_ADMIN);
3747726942fSRalf Baechle 		as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
3757726942fSRalf Baechle 		as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
3767726942fSRalf Baechle 
3777726942fSRalf Baechle 		down_write(&user_list_lock);
3787726942fSRalf Baechle 		list_add(&as->list, &apm_user_list);
3797726942fSRalf Baechle 		up_write(&user_list_lock);
3807726942fSRalf Baechle 
3817726942fSRalf Baechle 		filp->private_data = as;
3827726942fSRalf Baechle 	}
3837726942fSRalf Baechle 
3847726942fSRalf Baechle 	return as ? 0 : -ENOMEM;
3857726942fSRalf Baechle }
3867726942fSRalf Baechle 
387828c0950SAlexey Dobriyan static const struct file_operations apm_bios_fops = {
3887726942fSRalf Baechle 	.owner		= THIS_MODULE,
3897726942fSRalf Baechle 	.read		= apm_read,
3907726942fSRalf Baechle 	.poll		= apm_poll,
39155929332SArnd Bergmann 	.unlocked_ioctl	= apm_ioctl,
3927726942fSRalf Baechle 	.open		= apm_open,
3937726942fSRalf Baechle 	.release	= apm_release,
3946038f373SArnd Bergmann 	.llseek		= noop_llseek,
3957726942fSRalf Baechle };
3967726942fSRalf Baechle 
3977726942fSRalf Baechle static struct miscdevice apm_device = {
3987726942fSRalf Baechle 	.minor		= APM_MINOR_DEV,
3997726942fSRalf Baechle 	.name		= "apm_bios",
4007726942fSRalf Baechle 	.fops		= &apm_bios_fops
4017726942fSRalf Baechle };
4027726942fSRalf Baechle 
4037726942fSRalf Baechle 
4047726942fSRalf Baechle #ifdef CONFIG_PROC_FS
4057726942fSRalf Baechle /*
4067726942fSRalf Baechle  * Arguments, with symbols from linux/apm_bios.h.
4077726942fSRalf Baechle  *
4087726942fSRalf Baechle  *   0) Linux driver version (this will change if format changes)
4097726942fSRalf Baechle  *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
4107726942fSRalf Baechle  *   2) APM flags from APM Installation Check (0x00):
4117726942fSRalf Baechle  *	bit 0: APM_16_BIT_SUPPORT
4127726942fSRalf Baechle  *	bit 1: APM_32_BIT_SUPPORT
4137726942fSRalf Baechle  *	bit 2: APM_IDLE_SLOWS_CLOCK
4147726942fSRalf Baechle  *	bit 3: APM_BIOS_DISABLED
4157726942fSRalf Baechle  *	bit 4: APM_BIOS_DISENGAGED
4167726942fSRalf Baechle  *   3) AC line status
4177726942fSRalf Baechle  *	0x00: Off-line
4187726942fSRalf Baechle  *	0x01: On-line
4197726942fSRalf Baechle  *	0x02: On backup power (BIOS >= 1.1 only)
4207726942fSRalf Baechle  *	0xff: Unknown
4217726942fSRalf Baechle  *   4) Battery status
4227726942fSRalf Baechle  *	0x00: High
4237726942fSRalf Baechle  *	0x01: Low
4247726942fSRalf Baechle  *	0x02: Critical
4257726942fSRalf Baechle  *	0x03: Charging
4267726942fSRalf Baechle  *	0x04: Selected battery not present (BIOS >= 1.2 only)
4277726942fSRalf Baechle  *	0xff: Unknown
4287726942fSRalf Baechle  *   5) Battery flag
4297726942fSRalf Baechle  *	bit 0: High
4307726942fSRalf Baechle  *	bit 1: Low
4317726942fSRalf Baechle  *	bit 2: Critical
4327726942fSRalf Baechle  *	bit 3: Charging
4337726942fSRalf Baechle  *	bit 7: No system battery
4347726942fSRalf Baechle  *	0xff: Unknown
4357726942fSRalf Baechle  *   6) Remaining battery life (percentage of charge):
4367726942fSRalf Baechle  *	0-100: valid
4377726942fSRalf Baechle  *	-1: Unknown
4387726942fSRalf Baechle  *   7) Remaining battery life (time units):
4397726942fSRalf Baechle  *	Number of remaining minutes or seconds
4407726942fSRalf Baechle  *	-1: Unknown
4417726942fSRalf Baechle  *   8) min = minutes; sec = seconds
4427726942fSRalf Baechle  */
443647634dfSAlexey Dobriyan static int proc_apm_show(struct seq_file *m, void *v)
4447726942fSRalf Baechle {
4457726942fSRalf Baechle 	struct apm_power_info info;
4467726942fSRalf Baechle 	char *units;
4477726942fSRalf Baechle 
4487726942fSRalf Baechle 	info.ac_line_status = 0xff;
4497726942fSRalf Baechle 	info.battery_status = 0xff;
4507726942fSRalf Baechle 	info.battery_flag   = 0xff;
4517726942fSRalf Baechle 	info.battery_life   = -1;
4527726942fSRalf Baechle 	info.time	    = -1;
4537726942fSRalf Baechle 	info.units	    = -1;
4547726942fSRalf Baechle 
4557726942fSRalf Baechle 	if (apm_get_power_status)
4567726942fSRalf Baechle 		apm_get_power_status(&info);
4577726942fSRalf Baechle 
4587726942fSRalf Baechle 	switch (info.units) {
4597726942fSRalf Baechle 	default:	units = "?";	break;
4607726942fSRalf Baechle 	case 0: 	units = "min";	break;
4617726942fSRalf Baechle 	case 1: 	units = "sec";	break;
4627726942fSRalf Baechle 	}
4637726942fSRalf Baechle 
464647634dfSAlexey Dobriyan 	seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
4657726942fSRalf Baechle 		     driver_version, APM_32_BIT_SUPPORT,
4667726942fSRalf Baechle 		     info.ac_line_status, info.battery_status,
4677726942fSRalf Baechle 		     info.battery_flag, info.battery_life,
4687726942fSRalf Baechle 		     info.time, units);
4697726942fSRalf Baechle 
470647634dfSAlexey Dobriyan 	return 0;
4717726942fSRalf Baechle }
472647634dfSAlexey Dobriyan 
473647634dfSAlexey Dobriyan static int proc_apm_open(struct inode *inode, struct file *file)
474647634dfSAlexey Dobriyan {
475647634dfSAlexey Dobriyan 	return single_open(file, proc_apm_show, NULL);
476647634dfSAlexey Dobriyan }
477647634dfSAlexey Dobriyan 
478647634dfSAlexey Dobriyan static const struct file_operations apm_proc_fops = {
479647634dfSAlexey Dobriyan 	.owner		= THIS_MODULE,
480647634dfSAlexey Dobriyan 	.open		= proc_apm_open,
481647634dfSAlexey Dobriyan 	.read		= seq_read,
482647634dfSAlexey Dobriyan 	.llseek		= seq_lseek,
483647634dfSAlexey Dobriyan 	.release	= single_release,
484647634dfSAlexey Dobriyan };
4857726942fSRalf Baechle #endif
4867726942fSRalf Baechle 
4877726942fSRalf Baechle static int kapmd(void *arg)
4887726942fSRalf Baechle {
4897726942fSRalf Baechle 	do {
4907726942fSRalf Baechle 		apm_event_t event;
4917726942fSRalf Baechle 
4927726942fSRalf Baechle 		wait_event_interruptible(kapmd_wait,
4937726942fSRalf Baechle 				!queue_empty(&kapmd_queue) || kthread_should_stop());
4947726942fSRalf Baechle 
4957726942fSRalf Baechle 		if (kthread_should_stop())
4967726942fSRalf Baechle 			break;
4977726942fSRalf Baechle 
4987726942fSRalf Baechle 		spin_lock_irq(&kapmd_queue_lock);
4997726942fSRalf Baechle 		event = 0;
5007726942fSRalf Baechle 		if (!queue_empty(&kapmd_queue))
5017726942fSRalf Baechle 			event = queue_get_event(&kapmd_queue);
5027726942fSRalf Baechle 		spin_unlock_irq(&kapmd_queue_lock);
5037726942fSRalf Baechle 
5047726942fSRalf Baechle 		switch (event) {
5057726942fSRalf Baechle 		case 0:
5067726942fSRalf Baechle 			break;
5077726942fSRalf Baechle 
5087726942fSRalf Baechle 		case APM_LOW_BATTERY:
5097726942fSRalf Baechle 		case APM_POWER_STATUS_CHANGE:
5107726942fSRalf Baechle 			queue_event(event);
5117726942fSRalf Baechle 			break;
5127726942fSRalf Baechle 
5137726942fSRalf Baechle 		case APM_USER_SUSPEND:
5147726942fSRalf Baechle 		case APM_SYS_SUSPEND:
515d20a4dcaSJohannes Berg 			pm_suspend(PM_SUSPEND_MEM);
5167726942fSRalf Baechle 			break;
5177726942fSRalf Baechle 
5187726942fSRalf Baechle 		case APM_CRITICAL_SUSPEND:
519d20a4dcaSJohannes Berg 			atomic_inc(&userspace_notification_inhibit);
520d20a4dcaSJohannes Berg 			pm_suspend(PM_SUSPEND_MEM);
521d20a4dcaSJohannes Berg 			atomic_dec(&userspace_notification_inhibit);
5227726942fSRalf Baechle 			break;
5237726942fSRalf Baechle 		}
5247726942fSRalf Baechle 	} while (1);
5257726942fSRalf Baechle 
5267726942fSRalf Baechle 	return 0;
5277726942fSRalf Baechle }
5287726942fSRalf Baechle 
529d20a4dcaSJohannes Berg static int apm_suspend_notifier(struct notifier_block *nb,
530d20a4dcaSJohannes Berg 				unsigned long event,
531d20a4dcaSJohannes Berg 				void *dummy)
532d20a4dcaSJohannes Berg {
533d20a4dcaSJohannes Berg 	struct apm_user *as;
534d20a4dcaSJohannes Berg 	int err;
535d20a4dcaSJohannes Berg 
536d20a4dcaSJohannes Berg 	/* short-cut emergency suspends */
537d20a4dcaSJohannes Berg 	if (atomic_read(&userspace_notification_inhibit))
538d20a4dcaSJohannes Berg 		return NOTIFY_DONE;
539d20a4dcaSJohannes Berg 
540d20a4dcaSJohannes Berg 	switch (event) {
541d20a4dcaSJohannes Berg 	case PM_SUSPEND_PREPARE:
542d20a4dcaSJohannes Berg 		/*
543d20a4dcaSJohannes Berg 		 * Queue an event to all "writer" users that we want
544d20a4dcaSJohannes Berg 		 * to suspend and need their ack.
545d20a4dcaSJohannes Berg 		 */
546d20a4dcaSJohannes Berg 		mutex_lock(&state_lock);
547d20a4dcaSJohannes Berg 		down_read(&user_list_lock);
548d20a4dcaSJohannes Berg 
549d20a4dcaSJohannes Berg 		list_for_each_entry(as, &apm_user_list, list) {
550d20a4dcaSJohannes Berg 			if (as->suspend_state != SUSPEND_WAIT && as->reader &&
551d20a4dcaSJohannes Berg 			    as->writer && as->suser) {
552d20a4dcaSJohannes Berg 				as->suspend_state = SUSPEND_PENDING;
553d20a4dcaSJohannes Berg 				atomic_inc(&suspend_acks_pending);
554d20a4dcaSJohannes Berg 				queue_add_event(&as->queue, APM_USER_SUSPEND);
555d20a4dcaSJohannes Berg 			}
556d20a4dcaSJohannes Berg 		}
557d20a4dcaSJohannes Berg 
558d20a4dcaSJohannes Berg 		up_read(&user_list_lock);
559d20a4dcaSJohannes Berg 		mutex_unlock(&state_lock);
560d20a4dcaSJohannes Berg 		wake_up_interruptible(&apm_waitqueue);
561d20a4dcaSJohannes Berg 
562d20a4dcaSJohannes Berg 		/*
563d20a4dcaSJohannes Berg 		 * Wait for the the suspend_acks_pending variable to drop to
564d20a4dcaSJohannes Berg 		 * zero, meaning everybody acked the suspend event (or the
565d20a4dcaSJohannes Berg 		 * process was killed.)
566d20a4dcaSJohannes Berg 		 *
567d20a4dcaSJohannes Berg 		 * If the app won't answer within a short while we assume it
568d20a4dcaSJohannes Berg 		 * locked up and ignore it.
569d20a4dcaSJohannes Berg 		 */
570d20a4dcaSJohannes Berg 		err = wait_event_interruptible_timeout(
571d20a4dcaSJohannes Berg 			apm_suspend_waitqueue,
572d20a4dcaSJohannes Berg 			atomic_read(&suspend_acks_pending) == 0,
573d20a4dcaSJohannes Berg 			5*HZ);
574d20a4dcaSJohannes Berg 
575d20a4dcaSJohannes Berg 		/* timed out */
576d20a4dcaSJohannes Berg 		if (err == 0) {
577d20a4dcaSJohannes Berg 			/*
578d20a4dcaSJohannes Berg 			 * Move anybody who timed out to "ack timeout" state.
579d20a4dcaSJohannes Berg 			 *
580d20a4dcaSJohannes Berg 			 * We could time out and the userspace does the ACK
581d20a4dcaSJohannes Berg 			 * right after we time out but before we enter the
582d20a4dcaSJohannes Berg 			 * locked section here, but that's fine.
583d20a4dcaSJohannes Berg 			 */
584d20a4dcaSJohannes Berg 			mutex_lock(&state_lock);
585d20a4dcaSJohannes Berg 			down_read(&user_list_lock);
586d20a4dcaSJohannes Berg 			list_for_each_entry(as, &apm_user_list, list) {
587d20a4dcaSJohannes Berg 				if (as->suspend_state == SUSPEND_PENDING ||
588d20a4dcaSJohannes Berg 				    as->suspend_state == SUSPEND_READ) {
589d20a4dcaSJohannes Berg 					as->suspend_state = SUSPEND_ACKTO;
590d20a4dcaSJohannes Berg 					atomic_dec(&suspend_acks_pending);
591d20a4dcaSJohannes Berg 				}
592d20a4dcaSJohannes Berg 			}
593d20a4dcaSJohannes Berg 			up_read(&user_list_lock);
594d20a4dcaSJohannes Berg 			mutex_unlock(&state_lock);
595d20a4dcaSJohannes Berg 		}
596d20a4dcaSJohannes Berg 
597d20a4dcaSJohannes Berg 		/* let suspend proceed */
598d20a4dcaSJohannes Berg 		if (err >= 0)
599d20a4dcaSJohannes Berg 			return NOTIFY_OK;
600d20a4dcaSJohannes Berg 
601d20a4dcaSJohannes Berg 		/* interrupted by signal */
602f0c077a8SAkinobu Mita 		return notifier_from_errno(err);
603d20a4dcaSJohannes Berg 
604d20a4dcaSJohannes Berg 	case PM_POST_SUSPEND:
605d20a4dcaSJohannes Berg 		/*
606d20a4dcaSJohannes Berg 		 * Anyone on the APM queues will think we're still suspended.
607d20a4dcaSJohannes Berg 		 * Send a message so everyone knows we're now awake again.
608d20a4dcaSJohannes Berg 		 */
609d20a4dcaSJohannes Berg 		queue_event(APM_NORMAL_RESUME);
610d20a4dcaSJohannes Berg 
611d20a4dcaSJohannes Berg 		/*
612d20a4dcaSJohannes Berg 		 * Finally, wake up anyone who is sleeping on the suspend.
613d20a4dcaSJohannes Berg 		 */
614d20a4dcaSJohannes Berg 		mutex_lock(&state_lock);
615d20a4dcaSJohannes Berg 		down_read(&user_list_lock);
616d20a4dcaSJohannes Berg 		list_for_each_entry(as, &apm_user_list, list) {
617d20a4dcaSJohannes Berg 			if (as->suspend_state == SUSPEND_ACKED) {
618d20a4dcaSJohannes Berg 				/*
619d20a4dcaSJohannes Berg 				 * TODO: maybe grab error code, needs core
620d20a4dcaSJohannes Berg 				 * changes to push the error to the notifier
621d20a4dcaSJohannes Berg 				 * chain (could use the second parameter if
622d20a4dcaSJohannes Berg 				 * implemented)
623d20a4dcaSJohannes Berg 				 */
624d20a4dcaSJohannes Berg 				as->suspend_result = 0;
625d20a4dcaSJohannes Berg 				as->suspend_state = SUSPEND_DONE;
626d20a4dcaSJohannes Berg 			}
627d20a4dcaSJohannes Berg 		}
628d20a4dcaSJohannes Berg 		up_read(&user_list_lock);
629d20a4dcaSJohannes Berg 		mutex_unlock(&state_lock);
630d20a4dcaSJohannes Berg 
631d20a4dcaSJohannes Berg 		wake_up(&apm_suspend_waitqueue);
632d20a4dcaSJohannes Berg 		return NOTIFY_OK;
633d20a4dcaSJohannes Berg 
634d20a4dcaSJohannes Berg 	default:
635d20a4dcaSJohannes Berg 		return NOTIFY_DONE;
636d20a4dcaSJohannes Berg 	}
637d20a4dcaSJohannes Berg }
638d20a4dcaSJohannes Berg 
639d20a4dcaSJohannes Berg static struct notifier_block apm_notif_block = {
640d20a4dcaSJohannes Berg 	.notifier_call = apm_suspend_notifier,
641d20a4dcaSJohannes Berg };
642d20a4dcaSJohannes Berg 
6437726942fSRalf Baechle static int __init apm_init(void)
6447726942fSRalf Baechle {
6457726942fSRalf Baechle 	int ret;
6467726942fSRalf Baechle 
6477726942fSRalf Baechle 	if (apm_disabled) {
6487726942fSRalf Baechle 		printk(KERN_NOTICE "apm: disabled on user request.\n");
6497726942fSRalf Baechle 		return -ENODEV;
6507726942fSRalf Baechle 	}
6517726942fSRalf Baechle 
6527726942fSRalf Baechle 	kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
6537726942fSRalf Baechle 	if (IS_ERR(kapmd_tsk)) {
6547726942fSRalf Baechle 		ret = PTR_ERR(kapmd_tsk);
6557726942fSRalf Baechle 		kapmd_tsk = NULL;
656d20a4dcaSJohannes Berg 		goto out;
6577726942fSRalf Baechle 	}
6587726942fSRalf Baechle 	wake_up_process(kapmd_tsk);
6597726942fSRalf Baechle 
6607726942fSRalf Baechle #ifdef CONFIG_PROC_FS
661647634dfSAlexey Dobriyan 	proc_create("apm", 0, NULL, &apm_proc_fops);
6627726942fSRalf Baechle #endif
6637726942fSRalf Baechle 
6647726942fSRalf Baechle 	ret = misc_register(&apm_device);
665d20a4dcaSJohannes Berg 	if (ret)
666d20a4dcaSJohannes Berg 		goto out_stop;
667d20a4dcaSJohannes Berg 
668d20a4dcaSJohannes Berg 	ret = register_pm_notifier(&apm_notif_block);
669d20a4dcaSJohannes Berg 	if (ret)
670d20a4dcaSJohannes Berg 		goto out_unregister;
671d20a4dcaSJohannes Berg 
672d20a4dcaSJohannes Berg 	return 0;
673d20a4dcaSJohannes Berg 
674d20a4dcaSJohannes Berg  out_unregister:
675d20a4dcaSJohannes Berg 	misc_deregister(&apm_device);
676d20a4dcaSJohannes Berg  out_stop:
6777726942fSRalf Baechle 	remove_proc_entry("apm", NULL);
6787726942fSRalf Baechle 	kthread_stop(kapmd_tsk);
679d20a4dcaSJohannes Berg  out:
6807726942fSRalf Baechle 	return ret;
6817726942fSRalf Baechle }
6827726942fSRalf Baechle 
6837726942fSRalf Baechle static void __exit apm_exit(void)
6847726942fSRalf Baechle {
685d20a4dcaSJohannes Berg 	unregister_pm_notifier(&apm_notif_block);
6867726942fSRalf Baechle 	misc_deregister(&apm_device);
6877726942fSRalf Baechle 	remove_proc_entry("apm", NULL);
6887726942fSRalf Baechle 
6897726942fSRalf Baechle 	kthread_stop(kapmd_tsk);
6907726942fSRalf Baechle }
6917726942fSRalf Baechle 
6927726942fSRalf Baechle module_init(apm_init);
6937726942fSRalf Baechle module_exit(apm_exit);
6947726942fSRalf Baechle 
6957726942fSRalf Baechle MODULE_AUTHOR("Stephen Rothwell");
6967726942fSRalf Baechle MODULE_DESCRIPTION("Advanced Power Management");
6977726942fSRalf Baechle MODULE_LICENSE("GPL");
6987726942fSRalf Baechle 
6997726942fSRalf Baechle #ifndef MODULE
7007726942fSRalf Baechle static int __init apm_setup(char *str)
7017726942fSRalf Baechle {
7027726942fSRalf Baechle 	while ((str != NULL) && (*str != '\0')) {
7037726942fSRalf Baechle 		if (strncmp(str, "off", 3) == 0)
7047726942fSRalf Baechle 			apm_disabled = 1;
7057726942fSRalf Baechle 		if (strncmp(str, "on", 2) == 0)
7067726942fSRalf Baechle 			apm_disabled = 0;
7077726942fSRalf Baechle 		str = strchr(str, ',');
7087726942fSRalf Baechle 		if (str != NULL)
7097726942fSRalf Baechle 			str += strspn(str, ", \t");
7107726942fSRalf Baechle 	}
7117726942fSRalf Baechle 	return 1;
7127726942fSRalf Baechle }
7137726942fSRalf Baechle 
7147726942fSRalf Baechle __setup("apm=", apm_setup);
7157726942fSRalf Baechle #endif
7167726942fSRalf Baechle 
7177726942fSRalf Baechle /**
7187726942fSRalf Baechle  * apm_queue_event - queue an APM event for kapmd
7197726942fSRalf Baechle  * @event: APM event
7207726942fSRalf Baechle  *
7217726942fSRalf Baechle  * Queue an APM event for kapmd to process and ultimately take the
7227726942fSRalf Baechle  * appropriate action.  Only a subset of events are handled:
7237726942fSRalf Baechle  *   %APM_LOW_BATTERY
7247726942fSRalf Baechle  *   %APM_POWER_STATUS_CHANGE
7257726942fSRalf Baechle  *   %APM_USER_SUSPEND
7267726942fSRalf Baechle  *   %APM_SYS_SUSPEND
7277726942fSRalf Baechle  *   %APM_CRITICAL_SUSPEND
7287726942fSRalf Baechle  */
7297726942fSRalf Baechle void apm_queue_event(apm_event_t event)
7307726942fSRalf Baechle {
7317726942fSRalf Baechle 	unsigned long flags;
7327726942fSRalf Baechle 
7337726942fSRalf Baechle 	spin_lock_irqsave(&kapmd_queue_lock, flags);
7347726942fSRalf Baechle 	queue_add_event(&kapmd_queue, event);
7357726942fSRalf Baechle 	spin_unlock_irqrestore(&kapmd_queue_lock, flags);
7367726942fSRalf Baechle 
7377726942fSRalf Baechle 	wake_up_interruptible(&kapmd_wait);
7387726942fSRalf Baechle }
7397726942fSRalf Baechle EXPORT_SYMBOL(apm_queue_event);
740