xref: /linux/drivers/char/apm-emulation.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
27726942fSRalf Baechle /*
37726942fSRalf Baechle  * bios-less APM driver for ARM Linux
47726942fSRalf Baechle  *  Jamey Hicks <jamey@crl.dec.com>
57726942fSRalf Baechle  *  adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
67726942fSRalf Baechle  *
77726942fSRalf Baechle  * APM 1.2 Reference:
87726942fSRalf Baechle  *   Intel Corporation, Microsoft Corporation. Advanced Power Management
97726942fSRalf Baechle  *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
107726942fSRalf Baechle  *
11631dd1a8SJustin P. Mattock  * This document is available from Microsoft at:
12631dd1a8SJustin P. Mattock  *    http://www.microsoft.com/whdc/archive/amp_12.mspx
137726942fSRalf Baechle  */
147726942fSRalf Baechle #include <linux/module.h>
157726942fSRalf Baechle #include <linux/poll.h>
167726942fSRalf Baechle #include <linux/slab.h>
17613655faSArnd Bergmann #include <linux/mutex.h>
187726942fSRalf Baechle #include <linux/proc_fs.h>
19647634dfSAlexey Dobriyan #include <linux/seq_file.h>
207726942fSRalf Baechle #include <linux/miscdevice.h>
217726942fSRalf Baechle #include <linux/apm_bios.h>
227726942fSRalf Baechle #include <linux/capability.h>
237726942fSRalf Baechle #include <linux/sched.h>
2495d9ffbeSRafael J. Wysocki #include <linux/suspend.h>
257726942fSRalf Baechle #include <linux/apm-emulation.h>
2683144186SRafael J. Wysocki #include <linux/freezer.h>
277726942fSRalf Baechle #include <linux/device.h>
287726942fSRalf Baechle #include <linux/kernel.h>
297726942fSRalf Baechle #include <linux/list.h>
307726942fSRalf Baechle #include <linux/init.h>
317726942fSRalf Baechle #include <linux/completion.h>
327726942fSRalf Baechle #include <linux/kthread.h>
337726942fSRalf Baechle #include <linux/delay.h>
347726942fSRalf Baechle 
357726942fSRalf Baechle /*
36395cf969SPaul Bolle  * One option can be changed at boot time as follows:
377726942fSRalf Baechle  *	apm=on/off			enable/disable APM
387726942fSRalf Baechle  */
397726942fSRalf Baechle 
407726942fSRalf Baechle /*
417726942fSRalf Baechle  * Maximum number of events stored
427726942fSRalf Baechle  */
437726942fSRalf Baechle #define APM_MAX_EVENTS		16
447726942fSRalf Baechle 
457726942fSRalf Baechle struct apm_queue {
467726942fSRalf Baechle 	unsigned int		event_head;
477726942fSRalf Baechle 	unsigned int		event_tail;
487726942fSRalf Baechle 	apm_event_t		events[APM_MAX_EVENTS];
497726942fSRalf Baechle };
507726942fSRalf Baechle 
517726942fSRalf Baechle /*
52d20a4dcaSJohannes Berg  * thread states (for threads using a writable /dev/apm_bios fd):
53d20a4dcaSJohannes Berg  *
54d20a4dcaSJohannes Berg  * SUSPEND_NONE:	nothing happening
55d20a4dcaSJohannes Berg  * SUSPEND_PENDING:	suspend event queued for thread and pending to be read
56d20a4dcaSJohannes Berg  * SUSPEND_READ:	suspend event read, pending acknowledgement
57d20a4dcaSJohannes Berg  * SUSPEND_ACKED:	acknowledgement received from thread (via ioctl),
58d20a4dcaSJohannes Berg  *			waiting for resume
59d20a4dcaSJohannes Berg  * SUSPEND_ACKTO:	acknowledgement timeout
60d20a4dcaSJohannes Berg  * SUSPEND_DONE:	thread had acked suspend and is now notified of
61d20a4dcaSJohannes Berg  *			resume
62d20a4dcaSJohannes Berg  *
63d20a4dcaSJohannes Berg  * SUSPEND_WAIT:	this thread invoked suspend and is waiting for resume
64d20a4dcaSJohannes Berg  *
65d20a4dcaSJohannes Berg  * A thread migrates in one of three paths:
66d20a4dcaSJohannes Berg  *	NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE
67d20a4dcaSJohannes Berg  *				    -6-> ACKTO -7-> NONE
68d20a4dcaSJohannes Berg  *	NONE -8-> WAIT -9-> NONE
69d20a4dcaSJohannes Berg  *
70d20a4dcaSJohannes Berg  * While in PENDING or READ, the thread is accounted for in the
71d20a4dcaSJohannes Berg  * suspend_acks_pending counter.
72d20a4dcaSJohannes Berg  *
73d20a4dcaSJohannes Berg  * The transitions are invoked as follows:
74d20a4dcaSJohannes Berg  *	1: suspend event is signalled from the core PM code
75d20a4dcaSJohannes Berg  *	2: the suspend event is read from the fd by the userspace thread
76d20a4dcaSJohannes Berg  *	3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack)
77d20a4dcaSJohannes Berg  *	4: core PM code signals that we have resumed
78d20a4dcaSJohannes Berg  *	5: APM_IOC_SUSPEND ioctl returns
79d20a4dcaSJohannes Berg  *
80d20a4dcaSJohannes Berg  *	6: the notifier invoked from the core PM code timed out waiting
81d20a4dcaSJohannes Berg  *	   for all relevant threds to enter ACKED state and puts those
82d20a4dcaSJohannes Berg  *	   that haven't into ACKTO
83d20a4dcaSJohannes Berg  *	7: those threads issue APM_IOC_SUSPEND ioctl too late,
84d20a4dcaSJohannes Berg  *	   get an error
85d20a4dcaSJohannes Berg  *
86d20a4dcaSJohannes Berg  *	8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend),
87d20a4dcaSJohannes Berg  *	   ioctl code invokes pm_suspend()
88d20a4dcaSJohannes Berg  *	9: pm_suspend() returns indicating resume
89d20a4dcaSJohannes Berg  */
90d20a4dcaSJohannes Berg enum apm_suspend_state {
91d20a4dcaSJohannes Berg 	SUSPEND_NONE,
92d20a4dcaSJohannes Berg 	SUSPEND_PENDING,
93d20a4dcaSJohannes Berg 	SUSPEND_READ,
94d20a4dcaSJohannes Berg 	SUSPEND_ACKED,
95d20a4dcaSJohannes Berg 	SUSPEND_ACKTO,
96d20a4dcaSJohannes Berg 	SUSPEND_WAIT,
97d20a4dcaSJohannes Berg 	SUSPEND_DONE,
98d20a4dcaSJohannes Berg };
99d20a4dcaSJohannes Berg 
100d20a4dcaSJohannes Berg /*
1017726942fSRalf Baechle  * The per-file APM data
1027726942fSRalf Baechle  */
1037726942fSRalf Baechle struct apm_user {
1047726942fSRalf Baechle 	struct list_head	list;
1057726942fSRalf Baechle 
1067726942fSRalf Baechle 	unsigned int		suser: 1;
1077726942fSRalf Baechle 	unsigned int		writer: 1;
1087726942fSRalf Baechle 	unsigned int		reader: 1;
1097726942fSRalf Baechle 
1107726942fSRalf Baechle 	int			suspend_result;
111d20a4dcaSJohannes Berg 	enum apm_suspend_state	suspend_state;
1127726942fSRalf Baechle 
1137726942fSRalf Baechle 	struct apm_queue	queue;
1147726942fSRalf Baechle };
1157726942fSRalf Baechle 
1167726942fSRalf Baechle /*
1177726942fSRalf Baechle  * Local variables
1187726942fSRalf Baechle  */
119d20a4dcaSJohannes Berg static atomic_t suspend_acks_pending = ATOMIC_INIT(0);
120d20a4dcaSJohannes Berg static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0);
1217726942fSRalf Baechle static int apm_disabled;
1227726942fSRalf Baechle static struct task_struct *kapmd_tsk;
1237726942fSRalf Baechle 
1247726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
1257726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
1267726942fSRalf Baechle 
1277726942fSRalf Baechle /*
1287726942fSRalf Baechle  * This is a list of everyone who has opened /dev/apm_bios
1297726942fSRalf Baechle  */
1307726942fSRalf Baechle static DECLARE_RWSEM(user_list_lock);
1317726942fSRalf Baechle static LIST_HEAD(apm_user_list);
1327726942fSRalf Baechle 
1337726942fSRalf Baechle /*
1347726942fSRalf Baechle  * kapmd info.  kapmd provides us a process context to handle
1357726942fSRalf Baechle  * "APM" events within - specifically necessary if we're going
1367726942fSRalf Baechle  * to be suspending the system.
1377726942fSRalf Baechle  */
1387726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
1397726942fSRalf Baechle static DEFINE_SPINLOCK(kapmd_queue_lock);
1407726942fSRalf Baechle static struct apm_queue kapmd_queue;
1417726942fSRalf Baechle 
1427726942fSRalf Baechle static DEFINE_MUTEX(state_lock);
1437726942fSRalf Baechle 
1447726942fSRalf Baechle static const char driver_version[] = "1.13";	/* no spaces */
1457726942fSRalf Baechle 
1467726942fSRalf Baechle 
1477726942fSRalf Baechle 
1487726942fSRalf Baechle /*
1497726942fSRalf Baechle  * Compatibility cruft until the IPAQ people move over to the new
1507726942fSRalf Baechle  * interface.
1517726942fSRalf Baechle  */
__apm_get_power_status(struct apm_power_info * info)1527726942fSRalf Baechle static void __apm_get_power_status(struct apm_power_info *info)
1537726942fSRalf Baechle {
1547726942fSRalf Baechle }
1557726942fSRalf Baechle 
1567726942fSRalf Baechle /*
1577726942fSRalf Baechle  * This allows machines to provide their own "apm get power status" function.
1587726942fSRalf Baechle  */
1597726942fSRalf Baechle void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
1607726942fSRalf Baechle EXPORT_SYMBOL(apm_get_power_status);
1617726942fSRalf Baechle 
1627726942fSRalf Baechle 
1637726942fSRalf Baechle /*
1647726942fSRalf Baechle  * APM event queue management.
1657726942fSRalf Baechle  */
queue_empty(struct apm_queue * q)1667726942fSRalf Baechle static inline int queue_empty(struct apm_queue *q)
1677726942fSRalf Baechle {
1687726942fSRalf Baechle 	return q->event_head == q->event_tail;
1697726942fSRalf Baechle }
1707726942fSRalf Baechle 
queue_get_event(struct apm_queue * q)1717726942fSRalf Baechle static inline apm_event_t queue_get_event(struct apm_queue *q)
1727726942fSRalf Baechle {
1737726942fSRalf Baechle 	q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1747726942fSRalf Baechle 	return q->events[q->event_tail];
1757726942fSRalf Baechle }
1767726942fSRalf Baechle 
queue_add_event(struct apm_queue * q,apm_event_t event)1777726942fSRalf Baechle static void queue_add_event(struct apm_queue *q, apm_event_t event)
1787726942fSRalf Baechle {
1797726942fSRalf Baechle 	q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
1807726942fSRalf Baechle 	if (q->event_head == q->event_tail) {
1817726942fSRalf Baechle 		static int notified;
1827726942fSRalf Baechle 
1837726942fSRalf Baechle 		if (notified++ == 0)
1847726942fSRalf Baechle 		    printk(KERN_ERR "apm: an event queue overflowed\n");
1857726942fSRalf Baechle 		q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1867726942fSRalf Baechle 	}
1877726942fSRalf Baechle 	q->events[q->event_head] = event;
1887726942fSRalf Baechle }
1897726942fSRalf Baechle 
queue_event(apm_event_t event)1907726942fSRalf Baechle static void queue_event(apm_event_t event)
1917726942fSRalf Baechle {
1927726942fSRalf Baechle 	struct apm_user *as;
1937726942fSRalf Baechle 
1947726942fSRalf Baechle 	down_read(&user_list_lock);
1957726942fSRalf Baechle 	list_for_each_entry(as, &apm_user_list, list) {
1967726942fSRalf Baechle 		if (as->reader)
1977726942fSRalf Baechle 			queue_add_event(&as->queue, event);
1987726942fSRalf Baechle 	}
1997726942fSRalf Baechle 	up_read(&user_list_lock);
2007726942fSRalf Baechle 	wake_up_interruptible(&apm_waitqueue);
2017726942fSRalf Baechle }
2027726942fSRalf Baechle 
apm_read(struct file * fp,char __user * buf,size_t count,loff_t * ppos)2037726942fSRalf Baechle static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
2047726942fSRalf Baechle {
2057726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2067726942fSRalf Baechle 	apm_event_t event;
2077726942fSRalf Baechle 	int i = count, ret = 0;
2087726942fSRalf Baechle 
2097726942fSRalf Baechle 	if (count < sizeof(apm_event_t))
2107726942fSRalf Baechle 		return -EINVAL;
2117726942fSRalf Baechle 
2127726942fSRalf Baechle 	if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
2137726942fSRalf Baechle 		return -EAGAIN;
2147726942fSRalf Baechle 
2157726942fSRalf Baechle 	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
2167726942fSRalf Baechle 
2177726942fSRalf Baechle 	while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
2187726942fSRalf Baechle 		event = queue_get_event(&as->queue);
2197726942fSRalf Baechle 
2207726942fSRalf Baechle 		ret = -EFAULT;
2217726942fSRalf Baechle 		if (copy_to_user(buf, &event, sizeof(event)))
2227726942fSRalf Baechle 			break;
2237726942fSRalf Baechle 
2247726942fSRalf Baechle 		mutex_lock(&state_lock);
2257726942fSRalf Baechle 		if (as->suspend_state == SUSPEND_PENDING &&
2267726942fSRalf Baechle 		    (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
2277726942fSRalf Baechle 			as->suspend_state = SUSPEND_READ;
2287726942fSRalf Baechle 		mutex_unlock(&state_lock);
2297726942fSRalf Baechle 
2307726942fSRalf Baechle 		buf += sizeof(event);
2317726942fSRalf Baechle 		i -= sizeof(event);
2327726942fSRalf Baechle 	}
2337726942fSRalf Baechle 
2347726942fSRalf Baechle 	if (i < count)
2357726942fSRalf Baechle 		ret = count - i;
2367726942fSRalf Baechle 
2377726942fSRalf Baechle 	return ret;
2387726942fSRalf Baechle }
2397726942fSRalf Baechle 
apm_poll(struct file * fp,poll_table * wait)240afc9a42bSAl Viro static __poll_t apm_poll(struct file *fp, poll_table * wait)
2417726942fSRalf Baechle {
2427726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2437726942fSRalf Baechle 
2447726942fSRalf Baechle 	poll_wait(fp, &apm_waitqueue, wait);
245a9a08845SLinus Torvalds 	return queue_empty(&as->queue) ? 0 : EPOLLIN | EPOLLRDNORM;
2467726942fSRalf Baechle }
2477726942fSRalf Baechle 
2487726942fSRalf Baechle /*
2497726942fSRalf Baechle  * apm_ioctl - handle APM ioctl
2507726942fSRalf Baechle  *
2517726942fSRalf Baechle  * APM_IOC_SUSPEND
2527726942fSRalf Baechle  *   This IOCTL is overloaded, and performs two functions.  It is used to:
2537726942fSRalf Baechle  *     - initiate a suspend
2547726942fSRalf Baechle  *     - acknowledge a suspend read from /dev/apm_bios.
2557726942fSRalf Baechle  *   Only when everyone who has opened /dev/apm_bios with write permission
2567726942fSRalf Baechle  *   has acknowledge does the actual suspend happen.
2577726942fSRalf Baechle  */
25855929332SArnd Bergmann static long
apm_ioctl(struct file * filp,u_int cmd,u_long arg)25955929332SArnd Bergmann apm_ioctl(struct file *filp, u_int cmd, u_long arg)
2607726942fSRalf Baechle {
2617726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
2627726942fSRalf Baechle 	int err = -EINVAL;
2637726942fSRalf Baechle 
2647726942fSRalf Baechle 	if (!as->suser || !as->writer)
2657726942fSRalf Baechle 		return -EPERM;
2667726942fSRalf Baechle 
2677726942fSRalf Baechle 	switch (cmd) {
2687726942fSRalf Baechle 	case APM_IOC_SUSPEND:
2697726942fSRalf Baechle 		mutex_lock(&state_lock);
2707726942fSRalf Baechle 
2717726942fSRalf Baechle 		as->suspend_result = -EINTR;
2727726942fSRalf Baechle 
273d20a4dcaSJohannes Berg 		switch (as->suspend_state) {
274d20a4dcaSJohannes Berg 		case SUSPEND_READ:
2757726942fSRalf Baechle 			/*
2767726942fSRalf Baechle 			 * If we read a suspend command from /dev/apm_bios,
2777726942fSRalf Baechle 			 * then the corresponding APM_IOC_SUSPEND ioctl is
2787726942fSRalf Baechle 			 * interpreted as an acknowledge.
2797726942fSRalf Baechle 			 */
2807726942fSRalf Baechle 			as->suspend_state = SUSPEND_ACKED;
281d20a4dcaSJohannes Berg 			atomic_dec(&suspend_acks_pending);
2827726942fSRalf Baechle 			mutex_unlock(&state_lock);
2837726942fSRalf Baechle 
2847726942fSRalf Baechle 			/*
285d20a4dcaSJohannes Berg 			 * suspend_acks_pending changed, the notifier needs to
286d20a4dcaSJohannes Berg 			 * be woken up for this
2877726942fSRalf Baechle 			 */
288d20a4dcaSJohannes Berg 			wake_up(&apm_suspend_waitqueue);
2897726942fSRalf Baechle 
2907726942fSRalf Baechle 			/*
2917726942fSRalf Baechle 			 * Wait for the suspend/resume to complete.  If there
2927726942fSRalf Baechle 			 * are pending acknowledges, we wait here for them.
2931d927c3bSTejun Heo 			 * wait_event_freezable() is interruptible and pending
2941d927c3bSTejun Heo 			 * signal can cause busy looping.  We aren't doing
2951d927c3bSTejun Heo 			 * anything critical, chill a bit on each iteration.
2967726942fSRalf Baechle 			 */
2971d927c3bSTejun Heo 			while (wait_event_freezable(apm_suspend_waitqueue,
298f283d227SNeilBrown 					as->suspend_state != SUSPEND_ACKED))
2991d927c3bSTejun Heo 				msleep(10);
300d20a4dcaSJohannes Berg 			break;
301d20a4dcaSJohannes Berg 		case SUSPEND_ACKTO:
302d20a4dcaSJohannes Berg 			as->suspend_result = -ETIMEDOUT;
303d20a4dcaSJohannes Berg 			mutex_unlock(&state_lock);
304d20a4dcaSJohannes Berg 			break;
305d20a4dcaSJohannes Berg 		default:
3067726942fSRalf Baechle 			as->suspend_state = SUSPEND_WAIT;
3077726942fSRalf Baechle 			mutex_unlock(&state_lock);
3087726942fSRalf Baechle 
3097726942fSRalf Baechle 			/*
3107726942fSRalf Baechle 			 * Otherwise it is a request to suspend the system.
311d20a4dcaSJohannes Berg 			 * Just invoke pm_suspend(), we'll handle it from
312d20a4dcaSJohannes Berg 			 * there via the notifier.
3137726942fSRalf Baechle 			 */
314d20a4dcaSJohannes Berg 			as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
3157726942fSRalf Baechle 		}
3167726942fSRalf Baechle 
3177726942fSRalf Baechle 		mutex_lock(&state_lock);
3187726942fSRalf Baechle 		err = as->suspend_result;
3197726942fSRalf Baechle 		as->suspend_state = SUSPEND_NONE;
3207726942fSRalf Baechle 		mutex_unlock(&state_lock);
3217726942fSRalf Baechle 		break;
3227726942fSRalf Baechle 	}
3237726942fSRalf Baechle 
3247726942fSRalf Baechle 	return err;
3257726942fSRalf Baechle }
3267726942fSRalf Baechle 
apm_release(struct inode * inode,struct file * filp)3277726942fSRalf Baechle static int apm_release(struct inode * inode, struct file * filp)
3287726942fSRalf Baechle {
3297726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
3307726942fSRalf Baechle 
3317726942fSRalf Baechle 	filp->private_data = NULL;
3327726942fSRalf Baechle 
3337726942fSRalf Baechle 	down_write(&user_list_lock);
3347726942fSRalf Baechle 	list_del(&as->list);
3357726942fSRalf Baechle 	up_write(&user_list_lock);
3367726942fSRalf Baechle 
3377726942fSRalf Baechle 	/*
3387726942fSRalf Baechle 	 * We are now unhooked from the chain.  As far as new
339d20a4dcaSJohannes Berg 	 * events are concerned, we no longer exist.
3407726942fSRalf Baechle 	 */
3417726942fSRalf Baechle 	mutex_lock(&state_lock);
342d20a4dcaSJohannes Berg 	if (as->suspend_state == SUSPEND_PENDING ||
343d20a4dcaSJohannes Berg 	    as->suspend_state == SUSPEND_READ)
344d20a4dcaSJohannes Berg 		atomic_dec(&suspend_acks_pending);
3457726942fSRalf Baechle 	mutex_unlock(&state_lock);
346d20a4dcaSJohannes Berg 
347d20a4dcaSJohannes Berg 	wake_up(&apm_suspend_waitqueue);
3487726942fSRalf Baechle 
3497726942fSRalf Baechle 	kfree(as);
3507726942fSRalf Baechle 	return 0;
3517726942fSRalf Baechle }
3527726942fSRalf Baechle 
apm_open(struct inode * inode,struct file * filp)3537726942fSRalf Baechle static int apm_open(struct inode * inode, struct file * filp)
3547726942fSRalf Baechle {
3557726942fSRalf Baechle 	struct apm_user *as;
3567726942fSRalf Baechle 
3577726942fSRalf Baechle 	as = kzalloc(sizeof(*as), GFP_KERNEL);
3587726942fSRalf Baechle 	if (as) {
3597726942fSRalf Baechle 		/*
3607726942fSRalf Baechle 		 * XXX - this is a tiny bit broken, when we consider BSD
3617726942fSRalf Baechle 		 * process accounting. If the device is opened by root, we
3627726942fSRalf Baechle 		 * instantly flag that we used superuser privs. Who knows,
3637726942fSRalf Baechle 		 * we might close the device immediately without doing a
3647726942fSRalf Baechle 		 * privileged operation -- cevans
3657726942fSRalf Baechle 		 */
3667726942fSRalf Baechle 		as->suser = capable(CAP_SYS_ADMIN);
3677726942fSRalf Baechle 		as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
3687726942fSRalf Baechle 		as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
3697726942fSRalf Baechle 
3707726942fSRalf Baechle 		down_write(&user_list_lock);
3717726942fSRalf Baechle 		list_add(&as->list, &apm_user_list);
3727726942fSRalf Baechle 		up_write(&user_list_lock);
3737726942fSRalf Baechle 
3747726942fSRalf Baechle 		filp->private_data = as;
3757726942fSRalf Baechle 	}
3767726942fSRalf Baechle 
3777726942fSRalf Baechle 	return as ? 0 : -ENOMEM;
3787726942fSRalf Baechle }
3797726942fSRalf Baechle 
380828c0950SAlexey Dobriyan static const struct file_operations apm_bios_fops = {
3817726942fSRalf Baechle 	.owner		= THIS_MODULE,
3827726942fSRalf Baechle 	.read		= apm_read,
3837726942fSRalf Baechle 	.poll		= apm_poll,
38455929332SArnd Bergmann 	.unlocked_ioctl	= apm_ioctl,
3857726942fSRalf Baechle 	.open		= apm_open,
3867726942fSRalf Baechle 	.release	= apm_release,
3876038f373SArnd Bergmann 	.llseek		= noop_llseek,
3887726942fSRalf Baechle };
3897726942fSRalf Baechle 
3907726942fSRalf Baechle static struct miscdevice apm_device = {
3917726942fSRalf Baechle 	.minor		= APM_MINOR_DEV,
3927726942fSRalf Baechle 	.name		= "apm_bios",
3937726942fSRalf Baechle 	.fops		= &apm_bios_fops
3947726942fSRalf Baechle };
3957726942fSRalf Baechle 
3967726942fSRalf Baechle 
3977726942fSRalf Baechle #ifdef CONFIG_PROC_FS
3987726942fSRalf Baechle /*
3997726942fSRalf Baechle  * Arguments, with symbols from linux/apm_bios.h.
4007726942fSRalf Baechle  *
4017726942fSRalf Baechle  *   0) Linux driver version (this will change if format changes)
4027726942fSRalf Baechle  *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
4037726942fSRalf Baechle  *   2) APM flags from APM Installation Check (0x00):
4047726942fSRalf Baechle  *	bit 0: APM_16_BIT_SUPPORT
4057726942fSRalf Baechle  *	bit 1: APM_32_BIT_SUPPORT
4067726942fSRalf Baechle  *	bit 2: APM_IDLE_SLOWS_CLOCK
4077726942fSRalf Baechle  *	bit 3: APM_BIOS_DISABLED
4087726942fSRalf Baechle  *	bit 4: APM_BIOS_DISENGAGED
4097726942fSRalf Baechle  *   3) AC line status
4107726942fSRalf Baechle  *	0x00: Off-line
4117726942fSRalf Baechle  *	0x01: On-line
4127726942fSRalf Baechle  *	0x02: On backup power (BIOS >= 1.1 only)
4137726942fSRalf Baechle  *	0xff: Unknown
4147726942fSRalf Baechle  *   4) Battery status
4157726942fSRalf Baechle  *	0x00: High
4167726942fSRalf Baechle  *	0x01: Low
4177726942fSRalf Baechle  *	0x02: Critical
4187726942fSRalf Baechle  *	0x03: Charging
4197726942fSRalf Baechle  *	0x04: Selected battery not present (BIOS >= 1.2 only)
4207726942fSRalf Baechle  *	0xff: Unknown
4217726942fSRalf Baechle  *   5) Battery flag
4227726942fSRalf Baechle  *	bit 0: High
4237726942fSRalf Baechle  *	bit 1: Low
4247726942fSRalf Baechle  *	bit 2: Critical
4257726942fSRalf Baechle  *	bit 3: Charging
4267726942fSRalf Baechle  *	bit 7: No system battery
4277726942fSRalf Baechle  *	0xff: Unknown
4287726942fSRalf Baechle  *   6) Remaining battery life (percentage of charge):
4297726942fSRalf Baechle  *	0-100: valid
4307726942fSRalf Baechle  *	-1: Unknown
4317726942fSRalf Baechle  *   7) Remaining battery life (time units):
4327726942fSRalf Baechle  *	Number of remaining minutes or seconds
4337726942fSRalf Baechle  *	-1: Unknown
4347726942fSRalf Baechle  *   8) min = minutes; sec = seconds
4357726942fSRalf Baechle  */
proc_apm_show(struct seq_file * m,void * v)436647634dfSAlexey Dobriyan static int proc_apm_show(struct seq_file *m, void *v)
4377726942fSRalf Baechle {
4387726942fSRalf Baechle 	struct apm_power_info info;
4397726942fSRalf Baechle 	char *units;
4407726942fSRalf Baechle 
4417726942fSRalf Baechle 	info.ac_line_status = 0xff;
4427726942fSRalf Baechle 	info.battery_status = 0xff;
4437726942fSRalf Baechle 	info.battery_flag   = 0xff;
4447726942fSRalf Baechle 	info.battery_life   = -1;
4457726942fSRalf Baechle 	info.time	    = -1;
4467726942fSRalf Baechle 	info.units	    = -1;
4477726942fSRalf Baechle 
4487726942fSRalf Baechle 	if (apm_get_power_status)
4497726942fSRalf Baechle 		apm_get_power_status(&info);
4507726942fSRalf Baechle 
4517726942fSRalf Baechle 	switch (info.units) {
4527726942fSRalf Baechle 	default:	units = "?";	break;
4537726942fSRalf Baechle 	case 0: 	units = "min";	break;
4547726942fSRalf Baechle 	case 1: 	units = "sec";	break;
4557726942fSRalf Baechle 	}
4567726942fSRalf Baechle 
457647634dfSAlexey Dobriyan 	seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
4587726942fSRalf Baechle 		     driver_version, APM_32_BIT_SUPPORT,
4597726942fSRalf Baechle 		     info.ac_line_status, info.battery_status,
4607726942fSRalf Baechle 		     info.battery_flag, info.battery_life,
4617726942fSRalf Baechle 		     info.time, units);
4627726942fSRalf Baechle 
463647634dfSAlexey Dobriyan 	return 0;
4647726942fSRalf Baechle }
4657726942fSRalf Baechle #endif
4667726942fSRalf Baechle 
kapmd(void * arg)4677726942fSRalf Baechle static int kapmd(void *arg)
4687726942fSRalf Baechle {
4697726942fSRalf Baechle 	do {
4707726942fSRalf Baechle 		apm_event_t event;
4717726942fSRalf Baechle 
4727726942fSRalf Baechle 		wait_event_interruptible(kapmd_wait,
4737726942fSRalf Baechle 				!queue_empty(&kapmd_queue) || kthread_should_stop());
4747726942fSRalf Baechle 
4757726942fSRalf Baechle 		if (kthread_should_stop())
4767726942fSRalf Baechle 			break;
4777726942fSRalf Baechle 
4787726942fSRalf Baechle 		spin_lock_irq(&kapmd_queue_lock);
4797726942fSRalf Baechle 		event = 0;
4807726942fSRalf Baechle 		if (!queue_empty(&kapmd_queue))
4817726942fSRalf Baechle 			event = queue_get_event(&kapmd_queue);
4827726942fSRalf Baechle 		spin_unlock_irq(&kapmd_queue_lock);
4837726942fSRalf Baechle 
4847726942fSRalf Baechle 		switch (event) {
4857726942fSRalf Baechle 		case 0:
4867726942fSRalf Baechle 			break;
4877726942fSRalf Baechle 
4887726942fSRalf Baechle 		case APM_LOW_BATTERY:
4897726942fSRalf Baechle 		case APM_POWER_STATUS_CHANGE:
4907726942fSRalf Baechle 			queue_event(event);
4917726942fSRalf Baechle 			break;
4927726942fSRalf Baechle 
4937726942fSRalf Baechle 		case APM_USER_SUSPEND:
4947726942fSRalf Baechle 		case APM_SYS_SUSPEND:
495d20a4dcaSJohannes Berg 			pm_suspend(PM_SUSPEND_MEM);
4967726942fSRalf Baechle 			break;
4977726942fSRalf Baechle 
4987726942fSRalf Baechle 		case APM_CRITICAL_SUSPEND:
499d20a4dcaSJohannes Berg 			atomic_inc(&userspace_notification_inhibit);
500d20a4dcaSJohannes Berg 			pm_suspend(PM_SUSPEND_MEM);
501d20a4dcaSJohannes Berg 			atomic_dec(&userspace_notification_inhibit);
5027726942fSRalf Baechle 			break;
5037726942fSRalf Baechle 		}
5047726942fSRalf Baechle 	} while (1);
5057726942fSRalf Baechle 
5067726942fSRalf Baechle 	return 0;
5077726942fSRalf Baechle }
5087726942fSRalf Baechle 
apm_suspend_notifier(struct notifier_block * nb,unsigned long event,void * dummy)509d20a4dcaSJohannes Berg static int apm_suspend_notifier(struct notifier_block *nb,
510d20a4dcaSJohannes Berg 				unsigned long event,
511d20a4dcaSJohannes Berg 				void *dummy)
512d20a4dcaSJohannes Berg {
513d20a4dcaSJohannes Berg 	struct apm_user *as;
514d20a4dcaSJohannes Berg 	int err;
51515820439SBin Shi 	unsigned long apm_event;
516d20a4dcaSJohannes Berg 
517d20a4dcaSJohannes Berg 	/* short-cut emergency suspends */
518d20a4dcaSJohannes Berg 	if (atomic_read(&userspace_notification_inhibit))
519d20a4dcaSJohannes Berg 		return NOTIFY_DONE;
520d20a4dcaSJohannes Berg 
521d20a4dcaSJohannes Berg 	switch (event) {
522d20a4dcaSJohannes Berg 	case PM_SUSPEND_PREPARE:
52315820439SBin Shi 	case PM_HIBERNATION_PREPARE:
52415820439SBin Shi 		apm_event = (event == PM_SUSPEND_PREPARE) ?
52515820439SBin Shi 			APM_USER_SUSPEND : APM_USER_HIBERNATION;
526d20a4dcaSJohannes Berg 		/*
527d20a4dcaSJohannes Berg 		 * Queue an event to all "writer" users that we want
528d20a4dcaSJohannes Berg 		 * to suspend and need their ack.
529d20a4dcaSJohannes Berg 		 */
530d20a4dcaSJohannes Berg 		mutex_lock(&state_lock);
531d20a4dcaSJohannes Berg 		down_read(&user_list_lock);
532d20a4dcaSJohannes Berg 
533d20a4dcaSJohannes Berg 		list_for_each_entry(as, &apm_user_list, list) {
534d20a4dcaSJohannes Berg 			if (as->suspend_state != SUSPEND_WAIT && as->reader &&
535d20a4dcaSJohannes Berg 			    as->writer && as->suser) {
536d20a4dcaSJohannes Berg 				as->suspend_state = SUSPEND_PENDING;
537d20a4dcaSJohannes Berg 				atomic_inc(&suspend_acks_pending);
53815820439SBin Shi 				queue_add_event(&as->queue, apm_event);
539d20a4dcaSJohannes Berg 			}
540d20a4dcaSJohannes Berg 		}
541d20a4dcaSJohannes Berg 
542d20a4dcaSJohannes Berg 		up_read(&user_list_lock);
543d20a4dcaSJohannes Berg 		mutex_unlock(&state_lock);
544d20a4dcaSJohannes Berg 		wake_up_interruptible(&apm_waitqueue);
545d20a4dcaSJohannes Berg 
546d20a4dcaSJohannes Berg 		/*
547*1cb53f04SJiang Jian 		 * Wait for the suspend_acks_pending variable to drop to
548d20a4dcaSJohannes Berg 		 * zero, meaning everybody acked the suspend event (or the
549d20a4dcaSJohannes Berg 		 * process was killed.)
550d20a4dcaSJohannes Berg 		 *
551d20a4dcaSJohannes Berg 		 * If the app won't answer within a short while we assume it
552d20a4dcaSJohannes Berg 		 * locked up and ignore it.
553d20a4dcaSJohannes Berg 		 */
554d20a4dcaSJohannes Berg 		err = wait_event_interruptible_timeout(
555d20a4dcaSJohannes Berg 			apm_suspend_waitqueue,
556d20a4dcaSJohannes Berg 			atomic_read(&suspend_acks_pending) == 0,
557d20a4dcaSJohannes Berg 			5*HZ);
558d20a4dcaSJohannes Berg 
559d20a4dcaSJohannes Berg 		/* timed out */
560d20a4dcaSJohannes Berg 		if (err == 0) {
561d20a4dcaSJohannes Berg 			/*
562d20a4dcaSJohannes Berg 			 * Move anybody who timed out to "ack timeout" state.
563d20a4dcaSJohannes Berg 			 *
564d20a4dcaSJohannes Berg 			 * We could time out and the userspace does the ACK
565d20a4dcaSJohannes Berg 			 * right after we time out but before we enter the
566d20a4dcaSJohannes Berg 			 * locked section here, but that's fine.
567d20a4dcaSJohannes Berg 			 */
568d20a4dcaSJohannes Berg 			mutex_lock(&state_lock);
569d20a4dcaSJohannes Berg 			down_read(&user_list_lock);
570d20a4dcaSJohannes Berg 			list_for_each_entry(as, &apm_user_list, list) {
571d20a4dcaSJohannes Berg 				if (as->suspend_state == SUSPEND_PENDING ||
572d20a4dcaSJohannes Berg 				    as->suspend_state == SUSPEND_READ) {
573d20a4dcaSJohannes Berg 					as->suspend_state = SUSPEND_ACKTO;
574d20a4dcaSJohannes Berg 					atomic_dec(&suspend_acks_pending);
575d20a4dcaSJohannes Berg 				}
576d20a4dcaSJohannes Berg 			}
577d20a4dcaSJohannes Berg 			up_read(&user_list_lock);
578d20a4dcaSJohannes Berg 			mutex_unlock(&state_lock);
579d20a4dcaSJohannes Berg 		}
580d20a4dcaSJohannes Berg 
581d20a4dcaSJohannes Berg 		/* let suspend proceed */
582d20a4dcaSJohannes Berg 		if (err >= 0)
583d20a4dcaSJohannes Berg 			return NOTIFY_OK;
584d20a4dcaSJohannes Berg 
585d20a4dcaSJohannes Berg 		/* interrupted by signal */
586f0c077a8SAkinobu Mita 		return notifier_from_errno(err);
587d20a4dcaSJohannes Berg 
588d20a4dcaSJohannes Berg 	case PM_POST_SUSPEND:
58915820439SBin Shi 	case PM_POST_HIBERNATION:
59015820439SBin Shi 		apm_event = (event == PM_POST_SUSPEND) ?
59115820439SBin Shi 			APM_NORMAL_RESUME : APM_HIBERNATION_RESUME;
592d20a4dcaSJohannes Berg 		/*
593d20a4dcaSJohannes Berg 		 * Anyone on the APM queues will think we're still suspended.
594d20a4dcaSJohannes Berg 		 * Send a message so everyone knows we're now awake again.
595d20a4dcaSJohannes Berg 		 */
59615820439SBin Shi 		queue_event(apm_event);
597d20a4dcaSJohannes Berg 
598d20a4dcaSJohannes Berg 		/*
599d20a4dcaSJohannes Berg 		 * Finally, wake up anyone who is sleeping on the suspend.
600d20a4dcaSJohannes Berg 		 */
601d20a4dcaSJohannes Berg 		mutex_lock(&state_lock);
602d20a4dcaSJohannes Berg 		down_read(&user_list_lock);
603d20a4dcaSJohannes Berg 		list_for_each_entry(as, &apm_user_list, list) {
604d20a4dcaSJohannes Berg 			if (as->suspend_state == SUSPEND_ACKED) {
605d20a4dcaSJohannes Berg 				/*
606d20a4dcaSJohannes Berg 				 * TODO: maybe grab error code, needs core
607d20a4dcaSJohannes Berg 				 * changes to push the error to the notifier
608d20a4dcaSJohannes Berg 				 * chain (could use the second parameter if
609d20a4dcaSJohannes Berg 				 * implemented)
610d20a4dcaSJohannes Berg 				 */
611d20a4dcaSJohannes Berg 				as->suspend_result = 0;
612d20a4dcaSJohannes Berg 				as->suspend_state = SUSPEND_DONE;
613d20a4dcaSJohannes Berg 			}
614d20a4dcaSJohannes Berg 		}
615d20a4dcaSJohannes Berg 		up_read(&user_list_lock);
616d20a4dcaSJohannes Berg 		mutex_unlock(&state_lock);
617d20a4dcaSJohannes Berg 
618d20a4dcaSJohannes Berg 		wake_up(&apm_suspend_waitqueue);
619d20a4dcaSJohannes Berg 		return NOTIFY_OK;
620d20a4dcaSJohannes Berg 
621d20a4dcaSJohannes Berg 	default:
622d20a4dcaSJohannes Berg 		return NOTIFY_DONE;
623d20a4dcaSJohannes Berg 	}
624d20a4dcaSJohannes Berg }
625d20a4dcaSJohannes Berg 
626d20a4dcaSJohannes Berg static struct notifier_block apm_notif_block = {
627d20a4dcaSJohannes Berg 	.notifier_call = apm_suspend_notifier,
628d20a4dcaSJohannes Berg };
629d20a4dcaSJohannes Berg 
apm_init(void)6307726942fSRalf Baechle static int __init apm_init(void)
6317726942fSRalf Baechle {
6327726942fSRalf Baechle 	int ret;
6337726942fSRalf Baechle 
6347726942fSRalf Baechle 	if (apm_disabled) {
6357726942fSRalf Baechle 		printk(KERN_NOTICE "apm: disabled on user request.\n");
6367726942fSRalf Baechle 		return -ENODEV;
6377726942fSRalf Baechle 	}
6387726942fSRalf Baechle 
6397726942fSRalf Baechle 	kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
6407726942fSRalf Baechle 	if (IS_ERR(kapmd_tsk)) {
6417726942fSRalf Baechle 		ret = PTR_ERR(kapmd_tsk);
6427726942fSRalf Baechle 		kapmd_tsk = NULL;
643d20a4dcaSJohannes Berg 		goto out;
6447726942fSRalf Baechle 	}
6457726942fSRalf Baechle 	wake_up_process(kapmd_tsk);
6467726942fSRalf Baechle 
6477726942fSRalf Baechle #ifdef CONFIG_PROC_FS
6483f3942acSChristoph Hellwig 	proc_create_single("apm", 0, NULL, proc_apm_show);
6497726942fSRalf Baechle #endif
6507726942fSRalf Baechle 
6517726942fSRalf Baechle 	ret = misc_register(&apm_device);
652d20a4dcaSJohannes Berg 	if (ret)
653d20a4dcaSJohannes Berg 		goto out_stop;
654d20a4dcaSJohannes Berg 
655d20a4dcaSJohannes Berg 	ret = register_pm_notifier(&apm_notif_block);
656d20a4dcaSJohannes Berg 	if (ret)
657d20a4dcaSJohannes Berg 		goto out_unregister;
658d20a4dcaSJohannes Berg 
659d20a4dcaSJohannes Berg 	return 0;
660d20a4dcaSJohannes Berg 
661d20a4dcaSJohannes Berg  out_unregister:
662d20a4dcaSJohannes Berg 	misc_deregister(&apm_device);
663d20a4dcaSJohannes Berg  out_stop:
6647726942fSRalf Baechle 	remove_proc_entry("apm", NULL);
6657726942fSRalf Baechle 	kthread_stop(kapmd_tsk);
666d20a4dcaSJohannes Berg  out:
6677726942fSRalf Baechle 	return ret;
6687726942fSRalf Baechle }
6697726942fSRalf Baechle 
apm_exit(void)6707726942fSRalf Baechle static void __exit apm_exit(void)
6717726942fSRalf Baechle {
672d20a4dcaSJohannes Berg 	unregister_pm_notifier(&apm_notif_block);
6737726942fSRalf Baechle 	misc_deregister(&apm_device);
6747726942fSRalf Baechle 	remove_proc_entry("apm", NULL);
6757726942fSRalf Baechle 
6767726942fSRalf Baechle 	kthread_stop(kapmd_tsk);
6777726942fSRalf Baechle }
6787726942fSRalf Baechle 
6797726942fSRalf Baechle module_init(apm_init);
6807726942fSRalf Baechle module_exit(apm_exit);
6817726942fSRalf Baechle 
6827726942fSRalf Baechle MODULE_AUTHOR("Stephen Rothwell");
6837726942fSRalf Baechle MODULE_DESCRIPTION("Advanced Power Management");
6847726942fSRalf Baechle MODULE_LICENSE("GPL");
6857726942fSRalf Baechle 
6867726942fSRalf Baechle #ifndef MODULE
apm_setup(char * str)6877726942fSRalf Baechle static int __init apm_setup(char *str)
6887726942fSRalf Baechle {
6897726942fSRalf Baechle 	while ((str != NULL) && (*str != '\0')) {
6907726942fSRalf Baechle 		if (strncmp(str, "off", 3) == 0)
6917726942fSRalf Baechle 			apm_disabled = 1;
6927726942fSRalf Baechle 		if (strncmp(str, "on", 2) == 0)
6937726942fSRalf Baechle 			apm_disabled = 0;
6947726942fSRalf Baechle 		str = strchr(str, ',');
6957726942fSRalf Baechle 		if (str != NULL)
6967726942fSRalf Baechle 			str += strspn(str, ", \t");
6977726942fSRalf Baechle 	}
6987726942fSRalf Baechle 	return 1;
6997726942fSRalf Baechle }
7007726942fSRalf Baechle 
7017726942fSRalf Baechle __setup("apm=", apm_setup);
7027726942fSRalf Baechle #endif
7037726942fSRalf Baechle 
7047726942fSRalf Baechle /**
7057726942fSRalf Baechle  * apm_queue_event - queue an APM event for kapmd
7067726942fSRalf Baechle  * @event: APM event
7077726942fSRalf Baechle  *
7087726942fSRalf Baechle  * Queue an APM event for kapmd to process and ultimately take the
7097726942fSRalf Baechle  * appropriate action.  Only a subset of events are handled:
7107726942fSRalf Baechle  *   %APM_LOW_BATTERY
7117726942fSRalf Baechle  *   %APM_POWER_STATUS_CHANGE
7127726942fSRalf Baechle  *   %APM_USER_SUSPEND
7137726942fSRalf Baechle  *   %APM_SYS_SUSPEND
7147726942fSRalf Baechle  *   %APM_CRITICAL_SUSPEND
7157726942fSRalf Baechle  */
apm_queue_event(apm_event_t event)7167726942fSRalf Baechle void apm_queue_event(apm_event_t event)
7177726942fSRalf Baechle {
7187726942fSRalf Baechle 	unsigned long flags;
7197726942fSRalf Baechle 
7207726942fSRalf Baechle 	spin_lock_irqsave(&kapmd_queue_lock, flags);
7217726942fSRalf Baechle 	queue_add_event(&kapmd_queue, event);
7227726942fSRalf Baechle 	spin_unlock_irqrestore(&kapmd_queue_lock, flags);
7237726942fSRalf Baechle 
7247726942fSRalf Baechle 	wake_up_interruptible(&kapmd_wait);
7257726942fSRalf Baechle }
7267726942fSRalf Baechle EXPORT_SYMBOL(apm_queue_event);
727