xref: /linux/drivers/char/apm-emulation.c (revision d20a4dca47d2cd027ed58a13f91b424affd1f449)
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>
162861ead3SArnd Bergmann #include <linux/smp_lock.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 /*
437726942fSRalf Baechle  * See Documentation/Config.help for the configuration options.
447726942fSRalf Baechle  *
457726942fSRalf Baechle  * Various options can be changed at boot time as follows:
467726942fSRalf Baechle  * (We allow underscores for compatibility with the modules code)
477726942fSRalf Baechle  *	apm=on/off			enable/disable APM
487726942fSRalf Baechle  */
497726942fSRalf Baechle 
507726942fSRalf Baechle /*
517726942fSRalf Baechle  * Maximum number of events stored
527726942fSRalf Baechle  */
537726942fSRalf Baechle #define APM_MAX_EVENTS		16
547726942fSRalf Baechle 
557726942fSRalf Baechle struct apm_queue {
567726942fSRalf Baechle 	unsigned int		event_head;
577726942fSRalf Baechle 	unsigned int		event_tail;
587726942fSRalf Baechle 	apm_event_t		events[APM_MAX_EVENTS];
597726942fSRalf Baechle };
607726942fSRalf Baechle 
617726942fSRalf Baechle /*
62*d20a4dcaSJohannes Berg  * thread states (for threads using a writable /dev/apm_bios fd):
63*d20a4dcaSJohannes Berg  *
64*d20a4dcaSJohannes Berg  * SUSPEND_NONE:	nothing happening
65*d20a4dcaSJohannes Berg  * SUSPEND_PENDING:	suspend event queued for thread and pending to be read
66*d20a4dcaSJohannes Berg  * SUSPEND_READ:	suspend event read, pending acknowledgement
67*d20a4dcaSJohannes Berg  * SUSPEND_ACKED:	acknowledgement received from thread (via ioctl),
68*d20a4dcaSJohannes Berg  *			waiting for resume
69*d20a4dcaSJohannes Berg  * SUSPEND_ACKTO:	acknowledgement timeout
70*d20a4dcaSJohannes Berg  * SUSPEND_DONE:	thread had acked suspend and is now notified of
71*d20a4dcaSJohannes Berg  *			resume
72*d20a4dcaSJohannes Berg  *
73*d20a4dcaSJohannes Berg  * SUSPEND_WAIT:	this thread invoked suspend and is waiting for resume
74*d20a4dcaSJohannes Berg  *
75*d20a4dcaSJohannes Berg  * A thread migrates in one of three paths:
76*d20a4dcaSJohannes Berg  *	NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE
77*d20a4dcaSJohannes Berg  *				    -6-> ACKTO -7-> NONE
78*d20a4dcaSJohannes Berg  *	NONE -8-> WAIT -9-> NONE
79*d20a4dcaSJohannes Berg  *
80*d20a4dcaSJohannes Berg  * While in PENDING or READ, the thread is accounted for in the
81*d20a4dcaSJohannes Berg  * suspend_acks_pending counter.
82*d20a4dcaSJohannes Berg  *
83*d20a4dcaSJohannes Berg  * The transitions are invoked as follows:
84*d20a4dcaSJohannes Berg  *	1: suspend event is signalled from the core PM code
85*d20a4dcaSJohannes Berg  *	2: the suspend event is read from the fd by the userspace thread
86*d20a4dcaSJohannes Berg  *	3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack)
87*d20a4dcaSJohannes Berg  *	4: core PM code signals that we have resumed
88*d20a4dcaSJohannes Berg  *	5: APM_IOC_SUSPEND ioctl returns
89*d20a4dcaSJohannes Berg  *
90*d20a4dcaSJohannes Berg  *	6: the notifier invoked from the core PM code timed out waiting
91*d20a4dcaSJohannes Berg  *	   for all relevant threds to enter ACKED state and puts those
92*d20a4dcaSJohannes Berg  *	   that haven't into ACKTO
93*d20a4dcaSJohannes Berg  *	7: those threads issue APM_IOC_SUSPEND ioctl too late,
94*d20a4dcaSJohannes Berg  *	   get an error
95*d20a4dcaSJohannes Berg  *
96*d20a4dcaSJohannes Berg  *	8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend),
97*d20a4dcaSJohannes Berg  *	   ioctl code invokes pm_suspend()
98*d20a4dcaSJohannes Berg  *	9: pm_suspend() returns indicating resume
99*d20a4dcaSJohannes Berg  */
100*d20a4dcaSJohannes Berg enum apm_suspend_state {
101*d20a4dcaSJohannes Berg 	SUSPEND_NONE,
102*d20a4dcaSJohannes Berg 	SUSPEND_PENDING,
103*d20a4dcaSJohannes Berg 	SUSPEND_READ,
104*d20a4dcaSJohannes Berg 	SUSPEND_ACKED,
105*d20a4dcaSJohannes Berg 	SUSPEND_ACKTO,
106*d20a4dcaSJohannes Berg 	SUSPEND_WAIT,
107*d20a4dcaSJohannes Berg 	SUSPEND_DONE,
108*d20a4dcaSJohannes Berg };
109*d20a4dcaSJohannes Berg 
110*d20a4dcaSJohannes Berg /*
1117726942fSRalf Baechle  * The per-file APM data
1127726942fSRalf Baechle  */
1137726942fSRalf Baechle struct apm_user {
1147726942fSRalf Baechle 	struct list_head	list;
1157726942fSRalf Baechle 
1167726942fSRalf Baechle 	unsigned int		suser: 1;
1177726942fSRalf Baechle 	unsigned int		writer: 1;
1187726942fSRalf Baechle 	unsigned int		reader: 1;
1197726942fSRalf Baechle 
1207726942fSRalf Baechle 	int			suspend_result;
121*d20a4dcaSJohannes Berg 	enum apm_suspend_state	suspend_state;
1227726942fSRalf Baechle 
1237726942fSRalf Baechle 	struct apm_queue	queue;
1247726942fSRalf Baechle };
1257726942fSRalf Baechle 
1267726942fSRalf Baechle /*
1277726942fSRalf Baechle  * Local variables
1287726942fSRalf Baechle  */
129*d20a4dcaSJohannes Berg static atomic_t suspend_acks_pending = ATOMIC_INIT(0);
130*d20a4dcaSJohannes Berg static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0);
1317726942fSRalf Baechle static int apm_disabled;
1327726942fSRalf Baechle static struct task_struct *kapmd_tsk;
1337726942fSRalf Baechle 
1347726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
1357726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
1367726942fSRalf Baechle 
1377726942fSRalf Baechle /*
1387726942fSRalf Baechle  * This is a list of everyone who has opened /dev/apm_bios
1397726942fSRalf Baechle  */
1407726942fSRalf Baechle static DECLARE_RWSEM(user_list_lock);
1417726942fSRalf Baechle static LIST_HEAD(apm_user_list);
1427726942fSRalf Baechle 
1437726942fSRalf Baechle /*
1447726942fSRalf Baechle  * kapmd info.  kapmd provides us a process context to handle
1457726942fSRalf Baechle  * "APM" events within - specifically necessary if we're going
1467726942fSRalf Baechle  * to be suspending the system.
1477726942fSRalf Baechle  */
1487726942fSRalf Baechle static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
1497726942fSRalf Baechle static DEFINE_SPINLOCK(kapmd_queue_lock);
1507726942fSRalf Baechle static struct apm_queue kapmd_queue;
1517726942fSRalf Baechle 
1527726942fSRalf Baechle static DEFINE_MUTEX(state_lock);
1537726942fSRalf Baechle 
1547726942fSRalf Baechle static const char driver_version[] = "1.13";	/* no spaces */
1557726942fSRalf Baechle 
1567726942fSRalf Baechle 
1577726942fSRalf Baechle 
1587726942fSRalf Baechle /*
1597726942fSRalf Baechle  * Compatibility cruft until the IPAQ people move over to the new
1607726942fSRalf Baechle  * interface.
1617726942fSRalf Baechle  */
1627726942fSRalf Baechle static void __apm_get_power_status(struct apm_power_info *info)
1637726942fSRalf Baechle {
1647726942fSRalf Baechle }
1657726942fSRalf Baechle 
1667726942fSRalf Baechle /*
1677726942fSRalf Baechle  * This allows machines to provide their own "apm get power status" function.
1687726942fSRalf Baechle  */
1697726942fSRalf Baechle void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
1707726942fSRalf Baechle EXPORT_SYMBOL(apm_get_power_status);
1717726942fSRalf Baechle 
1727726942fSRalf Baechle 
1737726942fSRalf Baechle /*
1747726942fSRalf Baechle  * APM event queue management.
1757726942fSRalf Baechle  */
1767726942fSRalf Baechle static inline int queue_empty(struct apm_queue *q)
1777726942fSRalf Baechle {
1787726942fSRalf Baechle 	return q->event_head == q->event_tail;
1797726942fSRalf Baechle }
1807726942fSRalf Baechle 
1817726942fSRalf Baechle static inline apm_event_t queue_get_event(struct apm_queue *q)
1827726942fSRalf Baechle {
1837726942fSRalf Baechle 	q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1847726942fSRalf Baechle 	return q->events[q->event_tail];
1857726942fSRalf Baechle }
1867726942fSRalf Baechle 
1877726942fSRalf Baechle static void queue_add_event(struct apm_queue *q, apm_event_t event)
1887726942fSRalf Baechle {
1897726942fSRalf Baechle 	q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
1907726942fSRalf Baechle 	if (q->event_head == q->event_tail) {
1917726942fSRalf Baechle 		static int notified;
1927726942fSRalf Baechle 
1937726942fSRalf Baechle 		if (notified++ == 0)
1947726942fSRalf Baechle 		    printk(KERN_ERR "apm: an event queue overflowed\n");
1957726942fSRalf Baechle 		q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
1967726942fSRalf Baechle 	}
1977726942fSRalf Baechle 	q->events[q->event_head] = event;
1987726942fSRalf Baechle }
1997726942fSRalf Baechle 
2007726942fSRalf Baechle static void queue_event(apm_event_t event)
2017726942fSRalf Baechle {
2027726942fSRalf Baechle 	struct apm_user *as;
2037726942fSRalf Baechle 
2047726942fSRalf Baechle 	down_read(&user_list_lock);
2057726942fSRalf Baechle 	list_for_each_entry(as, &apm_user_list, list) {
2067726942fSRalf Baechle 		if (as->reader)
2077726942fSRalf Baechle 			queue_add_event(&as->queue, event);
2087726942fSRalf Baechle 	}
2097726942fSRalf Baechle 	up_read(&user_list_lock);
2107726942fSRalf Baechle 	wake_up_interruptible(&apm_waitqueue);
2117726942fSRalf Baechle }
2127726942fSRalf Baechle 
2137726942fSRalf Baechle static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
2147726942fSRalf Baechle {
2157726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2167726942fSRalf Baechle 	apm_event_t event;
2177726942fSRalf Baechle 	int i = count, ret = 0;
2187726942fSRalf Baechle 
2197726942fSRalf Baechle 	if (count < sizeof(apm_event_t))
2207726942fSRalf Baechle 		return -EINVAL;
2217726942fSRalf Baechle 
2227726942fSRalf Baechle 	if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
2237726942fSRalf Baechle 		return -EAGAIN;
2247726942fSRalf Baechle 
2257726942fSRalf Baechle 	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
2267726942fSRalf Baechle 
2277726942fSRalf Baechle 	while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
2287726942fSRalf Baechle 		event = queue_get_event(&as->queue);
2297726942fSRalf Baechle 
2307726942fSRalf Baechle 		ret = -EFAULT;
2317726942fSRalf Baechle 		if (copy_to_user(buf, &event, sizeof(event)))
2327726942fSRalf Baechle 			break;
2337726942fSRalf Baechle 
2347726942fSRalf Baechle 		mutex_lock(&state_lock);
2357726942fSRalf Baechle 		if (as->suspend_state == SUSPEND_PENDING &&
2367726942fSRalf Baechle 		    (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
2377726942fSRalf Baechle 			as->suspend_state = SUSPEND_READ;
2387726942fSRalf Baechle 		mutex_unlock(&state_lock);
2397726942fSRalf Baechle 
2407726942fSRalf Baechle 		buf += sizeof(event);
2417726942fSRalf Baechle 		i -= sizeof(event);
2427726942fSRalf Baechle 	}
2437726942fSRalf Baechle 
2447726942fSRalf Baechle 	if (i < count)
2457726942fSRalf Baechle 		ret = count - i;
2467726942fSRalf Baechle 
2477726942fSRalf Baechle 	return ret;
2487726942fSRalf Baechle }
2497726942fSRalf Baechle 
2507726942fSRalf Baechle static unsigned int apm_poll(struct file *fp, poll_table * wait)
2517726942fSRalf Baechle {
2527726942fSRalf Baechle 	struct apm_user *as = fp->private_data;
2537726942fSRalf Baechle 
2547726942fSRalf Baechle 	poll_wait(fp, &apm_waitqueue, wait);
2557726942fSRalf Baechle 	return queue_empty(&as->queue) ? 0 : POLLIN | POLLRDNORM;
2567726942fSRalf Baechle }
2577726942fSRalf Baechle 
2587726942fSRalf Baechle /*
2597726942fSRalf Baechle  * apm_ioctl - handle APM ioctl
2607726942fSRalf Baechle  *
2617726942fSRalf Baechle  * APM_IOC_SUSPEND
2627726942fSRalf Baechle  *   This IOCTL is overloaded, and performs two functions.  It is used to:
2637726942fSRalf Baechle  *     - initiate a suspend
2647726942fSRalf Baechle  *     - acknowledge a suspend read from /dev/apm_bios.
2657726942fSRalf Baechle  *   Only when everyone who has opened /dev/apm_bios with write permission
2667726942fSRalf Baechle  *   has acknowledge does the actual suspend happen.
2677726942fSRalf Baechle  */
2687726942fSRalf Baechle static int
2697726942fSRalf Baechle apm_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg)
2707726942fSRalf Baechle {
2717726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
2727726942fSRalf Baechle 	int err = -EINVAL;
2737726942fSRalf Baechle 
2747726942fSRalf Baechle 	if (!as->suser || !as->writer)
2757726942fSRalf Baechle 		return -EPERM;
2767726942fSRalf Baechle 
2777726942fSRalf Baechle 	switch (cmd) {
2787726942fSRalf Baechle 	case APM_IOC_SUSPEND:
2797726942fSRalf Baechle 		mutex_lock(&state_lock);
2807726942fSRalf Baechle 
2817726942fSRalf Baechle 		as->suspend_result = -EINTR;
2827726942fSRalf Baechle 
283*d20a4dcaSJohannes Berg 		switch (as->suspend_state) {
284*d20a4dcaSJohannes Berg 		case SUSPEND_READ:
2857726942fSRalf Baechle 			/*
2867726942fSRalf Baechle 			 * If we read a suspend command from /dev/apm_bios,
2877726942fSRalf Baechle 			 * then the corresponding APM_IOC_SUSPEND ioctl is
2887726942fSRalf Baechle 			 * interpreted as an acknowledge.
2897726942fSRalf Baechle 			 */
2907726942fSRalf Baechle 			as->suspend_state = SUSPEND_ACKED;
291*d20a4dcaSJohannes Berg 			atomic_dec(&suspend_acks_pending);
2927726942fSRalf Baechle 			mutex_unlock(&state_lock);
2937726942fSRalf Baechle 
2947726942fSRalf Baechle 			/*
295*d20a4dcaSJohannes Berg 			 * suspend_acks_pending changed, the notifier needs to
296*d20a4dcaSJohannes Berg 			 * be woken up for this
2977726942fSRalf Baechle 			 */
298*d20a4dcaSJohannes Berg 			wake_up(&apm_suspend_waitqueue);
2997726942fSRalf Baechle 
3007726942fSRalf Baechle 			/*
3017726942fSRalf Baechle 			 * Wait for the suspend/resume to complete.  If there
3027726942fSRalf Baechle 			 * are pending acknowledges, we wait here for them.
3037726942fSRalf Baechle 			 */
304cb43c54cSRafael J. Wysocki 			freezer_do_not_count();
3057726942fSRalf Baechle 
3067726942fSRalf Baechle 			wait_event(apm_suspend_waitqueue,
3077726942fSRalf Baechle 				   as->suspend_state == SUSPEND_DONE);
308cb43c54cSRafael J. Wysocki 
309cb43c54cSRafael J. Wysocki 			/*
310cb43c54cSRafael J. Wysocki 			 * Since we are waiting until the suspend is done, the
311cb43c54cSRafael J. Wysocki 			 * try_to_freeze() in freezer_count() will not trigger
312cb43c54cSRafael J. Wysocki 			 */
313cb43c54cSRafael J. Wysocki 			freezer_count();
314*d20a4dcaSJohannes Berg 			break;
315*d20a4dcaSJohannes Berg 		case SUSPEND_ACKTO:
316*d20a4dcaSJohannes Berg 			as->suspend_result = -ETIMEDOUT;
317*d20a4dcaSJohannes Berg 			mutex_unlock(&state_lock);
318*d20a4dcaSJohannes Berg 			break;
319*d20a4dcaSJohannes Berg 		default:
3207726942fSRalf Baechle 			as->suspend_state = SUSPEND_WAIT;
3217726942fSRalf Baechle 			mutex_unlock(&state_lock);
3227726942fSRalf Baechle 
3237726942fSRalf Baechle 			/*
3247726942fSRalf Baechle 			 * Otherwise it is a request to suspend the system.
325*d20a4dcaSJohannes Berg 			 * Just invoke pm_suspend(), we'll handle it from
326*d20a4dcaSJohannes Berg 			 * there via the notifier.
3277726942fSRalf Baechle 			 */
328*d20a4dcaSJohannes Berg 			as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
3297726942fSRalf Baechle 		}
3307726942fSRalf Baechle 
3317726942fSRalf Baechle 		mutex_lock(&state_lock);
3327726942fSRalf Baechle 		err = as->suspend_result;
3337726942fSRalf Baechle 		as->suspend_state = SUSPEND_NONE;
3347726942fSRalf Baechle 		mutex_unlock(&state_lock);
3357726942fSRalf Baechle 		break;
3367726942fSRalf Baechle 	}
3377726942fSRalf Baechle 
3387726942fSRalf Baechle 	return err;
3397726942fSRalf Baechle }
3407726942fSRalf Baechle 
3417726942fSRalf Baechle static int apm_release(struct inode * inode, struct file * filp)
3427726942fSRalf Baechle {
3437726942fSRalf Baechle 	struct apm_user *as = filp->private_data;
3447726942fSRalf Baechle 
3457726942fSRalf Baechle 	filp->private_data = NULL;
3467726942fSRalf Baechle 
3477726942fSRalf Baechle 	down_write(&user_list_lock);
3487726942fSRalf Baechle 	list_del(&as->list);
3497726942fSRalf Baechle 	up_write(&user_list_lock);
3507726942fSRalf Baechle 
3517726942fSRalf Baechle 	/*
3527726942fSRalf Baechle 	 * We are now unhooked from the chain.  As far as new
353*d20a4dcaSJohannes Berg 	 * events are concerned, we no longer exist.
3547726942fSRalf Baechle 	 */
3557726942fSRalf Baechle 	mutex_lock(&state_lock);
356*d20a4dcaSJohannes Berg 	if (as->suspend_state == SUSPEND_PENDING ||
357*d20a4dcaSJohannes Berg 	    as->suspend_state == SUSPEND_READ)
358*d20a4dcaSJohannes Berg 		atomic_dec(&suspend_acks_pending);
3597726942fSRalf Baechle 	mutex_unlock(&state_lock);
360*d20a4dcaSJohannes Berg 
361*d20a4dcaSJohannes Berg 	wake_up(&apm_suspend_waitqueue);
3627726942fSRalf Baechle 
3637726942fSRalf Baechle 	kfree(as);
3647726942fSRalf Baechle 	return 0;
3657726942fSRalf Baechle }
3667726942fSRalf Baechle 
3677726942fSRalf Baechle static int apm_open(struct inode * inode, struct file * filp)
3687726942fSRalf Baechle {
3697726942fSRalf Baechle 	struct apm_user *as;
3707726942fSRalf Baechle 
3712861ead3SArnd Bergmann 	lock_kernel();
3727726942fSRalf Baechle 	as = kzalloc(sizeof(*as), GFP_KERNEL);
3737726942fSRalf Baechle 	if (as) {
3747726942fSRalf Baechle 		/*
3757726942fSRalf Baechle 		 * XXX - this is a tiny bit broken, when we consider BSD
3767726942fSRalf Baechle 		 * process accounting. If the device is opened by root, we
3777726942fSRalf Baechle 		 * instantly flag that we used superuser privs. Who knows,
3787726942fSRalf Baechle 		 * we might close the device immediately without doing a
3797726942fSRalf Baechle 		 * privileged operation -- cevans
3807726942fSRalf Baechle 		 */
3817726942fSRalf Baechle 		as->suser = capable(CAP_SYS_ADMIN);
3827726942fSRalf Baechle 		as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
3837726942fSRalf Baechle 		as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
3847726942fSRalf Baechle 
3857726942fSRalf Baechle 		down_write(&user_list_lock);
3867726942fSRalf Baechle 		list_add(&as->list, &apm_user_list);
3877726942fSRalf Baechle 		up_write(&user_list_lock);
3887726942fSRalf Baechle 
3897726942fSRalf Baechle 		filp->private_data = as;
3907726942fSRalf Baechle 	}
3912861ead3SArnd Bergmann 	unlock_kernel();
3927726942fSRalf Baechle 
3937726942fSRalf Baechle 	return as ? 0 : -ENOMEM;
3947726942fSRalf Baechle }
3957726942fSRalf Baechle 
3967726942fSRalf Baechle static struct file_operations apm_bios_fops = {
3977726942fSRalf Baechle 	.owner		= THIS_MODULE,
3987726942fSRalf Baechle 	.read		= apm_read,
3997726942fSRalf Baechle 	.poll		= apm_poll,
4007726942fSRalf Baechle 	.ioctl		= apm_ioctl,
4017726942fSRalf Baechle 	.open		= apm_open,
4027726942fSRalf Baechle 	.release	= apm_release,
4037726942fSRalf Baechle };
4047726942fSRalf Baechle 
4057726942fSRalf Baechle static struct miscdevice apm_device = {
4067726942fSRalf Baechle 	.minor		= APM_MINOR_DEV,
4077726942fSRalf Baechle 	.name		= "apm_bios",
4087726942fSRalf Baechle 	.fops		= &apm_bios_fops
4097726942fSRalf Baechle };
4107726942fSRalf Baechle 
4117726942fSRalf Baechle 
4127726942fSRalf Baechle #ifdef CONFIG_PROC_FS
4137726942fSRalf Baechle /*
4147726942fSRalf Baechle  * Arguments, with symbols from linux/apm_bios.h.
4157726942fSRalf Baechle  *
4167726942fSRalf Baechle  *   0) Linux driver version (this will change if format changes)
4177726942fSRalf Baechle  *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
4187726942fSRalf Baechle  *   2) APM flags from APM Installation Check (0x00):
4197726942fSRalf Baechle  *	bit 0: APM_16_BIT_SUPPORT
4207726942fSRalf Baechle  *	bit 1: APM_32_BIT_SUPPORT
4217726942fSRalf Baechle  *	bit 2: APM_IDLE_SLOWS_CLOCK
4227726942fSRalf Baechle  *	bit 3: APM_BIOS_DISABLED
4237726942fSRalf Baechle  *	bit 4: APM_BIOS_DISENGAGED
4247726942fSRalf Baechle  *   3) AC line status
4257726942fSRalf Baechle  *	0x00: Off-line
4267726942fSRalf Baechle  *	0x01: On-line
4277726942fSRalf Baechle  *	0x02: On backup power (BIOS >= 1.1 only)
4287726942fSRalf Baechle  *	0xff: Unknown
4297726942fSRalf Baechle  *   4) Battery status
4307726942fSRalf Baechle  *	0x00: High
4317726942fSRalf Baechle  *	0x01: Low
4327726942fSRalf Baechle  *	0x02: Critical
4337726942fSRalf Baechle  *	0x03: Charging
4347726942fSRalf Baechle  *	0x04: Selected battery not present (BIOS >= 1.2 only)
4357726942fSRalf Baechle  *	0xff: Unknown
4367726942fSRalf Baechle  *   5) Battery flag
4377726942fSRalf Baechle  *	bit 0: High
4387726942fSRalf Baechle  *	bit 1: Low
4397726942fSRalf Baechle  *	bit 2: Critical
4407726942fSRalf Baechle  *	bit 3: Charging
4417726942fSRalf Baechle  *	bit 7: No system battery
4427726942fSRalf Baechle  *	0xff: Unknown
4437726942fSRalf Baechle  *   6) Remaining battery life (percentage of charge):
4447726942fSRalf Baechle  *	0-100: valid
4457726942fSRalf Baechle  *	-1: Unknown
4467726942fSRalf Baechle  *   7) Remaining battery life (time units):
4477726942fSRalf Baechle  *	Number of remaining minutes or seconds
4487726942fSRalf Baechle  *	-1: Unknown
4497726942fSRalf Baechle  *   8) min = minutes; sec = seconds
4507726942fSRalf Baechle  */
451647634dfSAlexey Dobriyan static int proc_apm_show(struct seq_file *m, void *v)
4527726942fSRalf Baechle {
4537726942fSRalf Baechle 	struct apm_power_info info;
4547726942fSRalf Baechle 	char *units;
4557726942fSRalf Baechle 
4567726942fSRalf Baechle 	info.ac_line_status = 0xff;
4577726942fSRalf Baechle 	info.battery_status = 0xff;
4587726942fSRalf Baechle 	info.battery_flag   = 0xff;
4597726942fSRalf Baechle 	info.battery_life   = -1;
4607726942fSRalf Baechle 	info.time	    = -1;
4617726942fSRalf Baechle 	info.units	    = -1;
4627726942fSRalf Baechle 
4637726942fSRalf Baechle 	if (apm_get_power_status)
4647726942fSRalf Baechle 		apm_get_power_status(&info);
4657726942fSRalf Baechle 
4667726942fSRalf Baechle 	switch (info.units) {
4677726942fSRalf Baechle 	default:	units = "?";	break;
4687726942fSRalf Baechle 	case 0: 	units = "min";	break;
4697726942fSRalf Baechle 	case 1: 	units = "sec";	break;
4707726942fSRalf Baechle 	}
4717726942fSRalf Baechle 
472647634dfSAlexey Dobriyan 	seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
4737726942fSRalf Baechle 		     driver_version, APM_32_BIT_SUPPORT,
4747726942fSRalf Baechle 		     info.ac_line_status, info.battery_status,
4757726942fSRalf Baechle 		     info.battery_flag, info.battery_life,
4767726942fSRalf Baechle 		     info.time, units);
4777726942fSRalf Baechle 
478647634dfSAlexey Dobriyan 	return 0;
4797726942fSRalf Baechle }
480647634dfSAlexey Dobriyan 
481647634dfSAlexey Dobriyan static int proc_apm_open(struct inode *inode, struct file *file)
482647634dfSAlexey Dobriyan {
483647634dfSAlexey Dobriyan 	return single_open(file, proc_apm_show, NULL);
484647634dfSAlexey Dobriyan }
485647634dfSAlexey Dobriyan 
486647634dfSAlexey Dobriyan static const struct file_operations apm_proc_fops = {
487647634dfSAlexey Dobriyan 	.owner		= THIS_MODULE,
488647634dfSAlexey Dobriyan 	.open		= proc_apm_open,
489647634dfSAlexey Dobriyan 	.read		= seq_read,
490647634dfSAlexey Dobriyan 	.llseek		= seq_lseek,
491647634dfSAlexey Dobriyan 	.release	= single_release,
492647634dfSAlexey Dobriyan };
4937726942fSRalf Baechle #endif
4947726942fSRalf Baechle 
4957726942fSRalf Baechle static int kapmd(void *arg)
4967726942fSRalf Baechle {
4977726942fSRalf Baechle 	do {
4987726942fSRalf Baechle 		apm_event_t event;
4997726942fSRalf Baechle 
5007726942fSRalf Baechle 		wait_event_interruptible(kapmd_wait,
5017726942fSRalf Baechle 				!queue_empty(&kapmd_queue) || kthread_should_stop());
5027726942fSRalf Baechle 
5037726942fSRalf Baechle 		if (kthread_should_stop())
5047726942fSRalf Baechle 			break;
5057726942fSRalf Baechle 
5067726942fSRalf Baechle 		spin_lock_irq(&kapmd_queue_lock);
5077726942fSRalf Baechle 		event = 0;
5087726942fSRalf Baechle 		if (!queue_empty(&kapmd_queue))
5097726942fSRalf Baechle 			event = queue_get_event(&kapmd_queue);
5107726942fSRalf Baechle 		spin_unlock_irq(&kapmd_queue_lock);
5117726942fSRalf Baechle 
5127726942fSRalf Baechle 		switch (event) {
5137726942fSRalf Baechle 		case 0:
5147726942fSRalf Baechle 			break;
5157726942fSRalf Baechle 
5167726942fSRalf Baechle 		case APM_LOW_BATTERY:
5177726942fSRalf Baechle 		case APM_POWER_STATUS_CHANGE:
5187726942fSRalf Baechle 			queue_event(event);
5197726942fSRalf Baechle 			break;
5207726942fSRalf Baechle 
5217726942fSRalf Baechle 		case APM_USER_SUSPEND:
5227726942fSRalf Baechle 		case APM_SYS_SUSPEND:
523*d20a4dcaSJohannes Berg 			pm_suspend(PM_SUSPEND_MEM);
5247726942fSRalf Baechle 			break;
5257726942fSRalf Baechle 
5267726942fSRalf Baechle 		case APM_CRITICAL_SUSPEND:
527*d20a4dcaSJohannes Berg 			atomic_inc(&userspace_notification_inhibit);
528*d20a4dcaSJohannes Berg 			pm_suspend(PM_SUSPEND_MEM);
529*d20a4dcaSJohannes Berg 			atomic_dec(&userspace_notification_inhibit);
5307726942fSRalf Baechle 			break;
5317726942fSRalf Baechle 		}
5327726942fSRalf Baechle 	} while (1);
5337726942fSRalf Baechle 
5347726942fSRalf Baechle 	return 0;
5357726942fSRalf Baechle }
5367726942fSRalf Baechle 
537*d20a4dcaSJohannes Berg static int apm_suspend_notifier(struct notifier_block *nb,
538*d20a4dcaSJohannes Berg 				unsigned long event,
539*d20a4dcaSJohannes Berg 				void *dummy)
540*d20a4dcaSJohannes Berg {
541*d20a4dcaSJohannes Berg 	struct apm_user *as;
542*d20a4dcaSJohannes Berg 	int err;
543*d20a4dcaSJohannes Berg 
544*d20a4dcaSJohannes Berg 	/* short-cut emergency suspends */
545*d20a4dcaSJohannes Berg 	if (atomic_read(&userspace_notification_inhibit))
546*d20a4dcaSJohannes Berg 		return NOTIFY_DONE;
547*d20a4dcaSJohannes Berg 
548*d20a4dcaSJohannes Berg 	switch (event) {
549*d20a4dcaSJohannes Berg 	case PM_SUSPEND_PREPARE:
550*d20a4dcaSJohannes Berg 		/*
551*d20a4dcaSJohannes Berg 		 * Queue an event to all "writer" users that we want
552*d20a4dcaSJohannes Berg 		 * to suspend and need their ack.
553*d20a4dcaSJohannes Berg 		 */
554*d20a4dcaSJohannes Berg 		mutex_lock(&state_lock);
555*d20a4dcaSJohannes Berg 		down_read(&user_list_lock);
556*d20a4dcaSJohannes Berg 
557*d20a4dcaSJohannes Berg 		list_for_each_entry(as, &apm_user_list, list) {
558*d20a4dcaSJohannes Berg 			if (as->suspend_state != SUSPEND_WAIT && as->reader &&
559*d20a4dcaSJohannes Berg 			    as->writer && as->suser) {
560*d20a4dcaSJohannes Berg 				as->suspend_state = SUSPEND_PENDING;
561*d20a4dcaSJohannes Berg 				atomic_inc(&suspend_acks_pending);
562*d20a4dcaSJohannes Berg 				queue_add_event(&as->queue, APM_USER_SUSPEND);
563*d20a4dcaSJohannes Berg 			}
564*d20a4dcaSJohannes Berg 		}
565*d20a4dcaSJohannes Berg 
566*d20a4dcaSJohannes Berg 		up_read(&user_list_lock);
567*d20a4dcaSJohannes Berg 		mutex_unlock(&state_lock);
568*d20a4dcaSJohannes Berg 		wake_up_interruptible(&apm_waitqueue);
569*d20a4dcaSJohannes Berg 
570*d20a4dcaSJohannes Berg 		/*
571*d20a4dcaSJohannes Berg 		 * Wait for the the suspend_acks_pending variable to drop to
572*d20a4dcaSJohannes Berg 		 * zero, meaning everybody acked the suspend event (or the
573*d20a4dcaSJohannes Berg 		 * process was killed.)
574*d20a4dcaSJohannes Berg 		 *
575*d20a4dcaSJohannes Berg 		 * If the app won't answer within a short while we assume it
576*d20a4dcaSJohannes Berg 		 * locked up and ignore it.
577*d20a4dcaSJohannes Berg 		 */
578*d20a4dcaSJohannes Berg 		err = wait_event_interruptible_timeout(
579*d20a4dcaSJohannes Berg 			apm_suspend_waitqueue,
580*d20a4dcaSJohannes Berg 			atomic_read(&suspend_acks_pending) == 0,
581*d20a4dcaSJohannes Berg 			5*HZ);
582*d20a4dcaSJohannes Berg 
583*d20a4dcaSJohannes Berg 		/* timed out */
584*d20a4dcaSJohannes Berg 		if (err == 0) {
585*d20a4dcaSJohannes Berg 			/*
586*d20a4dcaSJohannes Berg 			 * Move anybody who timed out to "ack timeout" state.
587*d20a4dcaSJohannes Berg 			 *
588*d20a4dcaSJohannes Berg 			 * We could time out and the userspace does the ACK
589*d20a4dcaSJohannes Berg 			 * right after we time out but before we enter the
590*d20a4dcaSJohannes Berg 			 * locked section here, but that's fine.
591*d20a4dcaSJohannes Berg 			 */
592*d20a4dcaSJohannes Berg 			mutex_lock(&state_lock);
593*d20a4dcaSJohannes Berg 			down_read(&user_list_lock);
594*d20a4dcaSJohannes Berg 			list_for_each_entry(as, &apm_user_list, list) {
595*d20a4dcaSJohannes Berg 				if (as->suspend_state == SUSPEND_PENDING ||
596*d20a4dcaSJohannes Berg 				    as->suspend_state == SUSPEND_READ) {
597*d20a4dcaSJohannes Berg 					as->suspend_state = SUSPEND_ACKTO;
598*d20a4dcaSJohannes Berg 					atomic_dec(&suspend_acks_pending);
599*d20a4dcaSJohannes Berg 				}
600*d20a4dcaSJohannes Berg 			}
601*d20a4dcaSJohannes Berg 			up_read(&user_list_lock);
602*d20a4dcaSJohannes Berg 			mutex_unlock(&state_lock);
603*d20a4dcaSJohannes Berg 		}
604*d20a4dcaSJohannes Berg 
605*d20a4dcaSJohannes Berg 		/* let suspend proceed */
606*d20a4dcaSJohannes Berg 		if (err >= 0)
607*d20a4dcaSJohannes Berg 			return NOTIFY_OK;
608*d20a4dcaSJohannes Berg 
609*d20a4dcaSJohannes Berg 		/* interrupted by signal */
610*d20a4dcaSJohannes Berg 		return NOTIFY_BAD;
611*d20a4dcaSJohannes Berg 
612*d20a4dcaSJohannes Berg 	case PM_POST_SUSPEND:
613*d20a4dcaSJohannes Berg 		/*
614*d20a4dcaSJohannes Berg 		 * Anyone on the APM queues will think we're still suspended.
615*d20a4dcaSJohannes Berg 		 * Send a message so everyone knows we're now awake again.
616*d20a4dcaSJohannes Berg 		 */
617*d20a4dcaSJohannes Berg 		queue_event(APM_NORMAL_RESUME);
618*d20a4dcaSJohannes Berg 
619*d20a4dcaSJohannes Berg 		/*
620*d20a4dcaSJohannes Berg 		 * Finally, wake up anyone who is sleeping on the suspend.
621*d20a4dcaSJohannes Berg 		 */
622*d20a4dcaSJohannes Berg 		mutex_lock(&state_lock);
623*d20a4dcaSJohannes Berg 		down_read(&user_list_lock);
624*d20a4dcaSJohannes Berg 		list_for_each_entry(as, &apm_user_list, list) {
625*d20a4dcaSJohannes Berg 			if (as->suspend_state == SUSPEND_ACKED) {
626*d20a4dcaSJohannes Berg 				/*
627*d20a4dcaSJohannes Berg 				 * TODO: maybe grab error code, needs core
628*d20a4dcaSJohannes Berg 				 * changes to push the error to the notifier
629*d20a4dcaSJohannes Berg 				 * chain (could use the second parameter if
630*d20a4dcaSJohannes Berg 				 * implemented)
631*d20a4dcaSJohannes Berg 				 */
632*d20a4dcaSJohannes Berg 				as->suspend_result = 0;
633*d20a4dcaSJohannes Berg 				as->suspend_state = SUSPEND_DONE;
634*d20a4dcaSJohannes Berg 			}
635*d20a4dcaSJohannes Berg 		}
636*d20a4dcaSJohannes Berg 		up_read(&user_list_lock);
637*d20a4dcaSJohannes Berg 		mutex_unlock(&state_lock);
638*d20a4dcaSJohannes Berg 
639*d20a4dcaSJohannes Berg 		wake_up(&apm_suspend_waitqueue);
640*d20a4dcaSJohannes Berg 		return NOTIFY_OK;
641*d20a4dcaSJohannes Berg 
642*d20a4dcaSJohannes Berg 	default:
643*d20a4dcaSJohannes Berg 		return NOTIFY_DONE;
644*d20a4dcaSJohannes Berg 	}
645*d20a4dcaSJohannes Berg }
646*d20a4dcaSJohannes Berg 
647*d20a4dcaSJohannes Berg static struct notifier_block apm_notif_block = {
648*d20a4dcaSJohannes Berg 	.notifier_call = apm_suspend_notifier,
649*d20a4dcaSJohannes Berg };
650*d20a4dcaSJohannes Berg 
6517726942fSRalf Baechle static int __init apm_init(void)
6527726942fSRalf Baechle {
6537726942fSRalf Baechle 	int ret;
6547726942fSRalf Baechle 
6557726942fSRalf Baechle 	if (apm_disabled) {
6567726942fSRalf Baechle 		printk(KERN_NOTICE "apm: disabled on user request.\n");
6577726942fSRalf Baechle 		return -ENODEV;
6587726942fSRalf Baechle 	}
6597726942fSRalf Baechle 
6607726942fSRalf Baechle 	kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
6617726942fSRalf Baechle 	if (IS_ERR(kapmd_tsk)) {
6627726942fSRalf Baechle 		ret = PTR_ERR(kapmd_tsk);
6637726942fSRalf Baechle 		kapmd_tsk = NULL;
664*d20a4dcaSJohannes Berg 		goto out;
6657726942fSRalf Baechle 	}
6667726942fSRalf Baechle 	wake_up_process(kapmd_tsk);
6677726942fSRalf Baechle 
6687726942fSRalf Baechle #ifdef CONFIG_PROC_FS
669647634dfSAlexey Dobriyan 	proc_create("apm", 0, NULL, &apm_proc_fops);
6707726942fSRalf Baechle #endif
6717726942fSRalf Baechle 
6727726942fSRalf Baechle 	ret = misc_register(&apm_device);
673*d20a4dcaSJohannes Berg 	if (ret)
674*d20a4dcaSJohannes Berg 		goto out_stop;
675*d20a4dcaSJohannes Berg 
676*d20a4dcaSJohannes Berg 	ret = register_pm_notifier(&apm_notif_block);
677*d20a4dcaSJohannes Berg 	if (ret)
678*d20a4dcaSJohannes Berg 		goto out_unregister;
679*d20a4dcaSJohannes Berg 
680*d20a4dcaSJohannes Berg 	return 0;
681*d20a4dcaSJohannes Berg 
682*d20a4dcaSJohannes Berg  out_unregister:
683*d20a4dcaSJohannes Berg 	misc_deregister(&apm_device);
684*d20a4dcaSJohannes Berg  out_stop:
6857726942fSRalf Baechle 	remove_proc_entry("apm", NULL);
6867726942fSRalf Baechle 	kthread_stop(kapmd_tsk);
687*d20a4dcaSJohannes Berg  out:
6887726942fSRalf Baechle 	return ret;
6897726942fSRalf Baechle }
6907726942fSRalf Baechle 
6917726942fSRalf Baechle static void __exit apm_exit(void)
6927726942fSRalf Baechle {
693*d20a4dcaSJohannes Berg 	unregister_pm_notifier(&apm_notif_block);
6947726942fSRalf Baechle 	misc_deregister(&apm_device);
6957726942fSRalf Baechle 	remove_proc_entry("apm", NULL);
6967726942fSRalf Baechle 
6977726942fSRalf Baechle 	kthread_stop(kapmd_tsk);
6987726942fSRalf Baechle }
6997726942fSRalf Baechle 
7007726942fSRalf Baechle module_init(apm_init);
7017726942fSRalf Baechle module_exit(apm_exit);
7027726942fSRalf Baechle 
7037726942fSRalf Baechle MODULE_AUTHOR("Stephen Rothwell");
7047726942fSRalf Baechle MODULE_DESCRIPTION("Advanced Power Management");
7057726942fSRalf Baechle MODULE_LICENSE("GPL");
7067726942fSRalf Baechle 
7077726942fSRalf Baechle #ifndef MODULE
7087726942fSRalf Baechle static int __init apm_setup(char *str)
7097726942fSRalf Baechle {
7107726942fSRalf Baechle 	while ((str != NULL) && (*str != '\0')) {
7117726942fSRalf Baechle 		if (strncmp(str, "off", 3) == 0)
7127726942fSRalf Baechle 			apm_disabled = 1;
7137726942fSRalf Baechle 		if (strncmp(str, "on", 2) == 0)
7147726942fSRalf Baechle 			apm_disabled = 0;
7157726942fSRalf Baechle 		str = strchr(str, ',');
7167726942fSRalf Baechle 		if (str != NULL)
7177726942fSRalf Baechle 			str += strspn(str, ", \t");
7187726942fSRalf Baechle 	}
7197726942fSRalf Baechle 	return 1;
7207726942fSRalf Baechle }
7217726942fSRalf Baechle 
7227726942fSRalf Baechle __setup("apm=", apm_setup);
7237726942fSRalf Baechle #endif
7247726942fSRalf Baechle 
7257726942fSRalf Baechle /**
7267726942fSRalf Baechle  * apm_queue_event - queue an APM event for kapmd
7277726942fSRalf Baechle  * @event: APM event
7287726942fSRalf Baechle  *
7297726942fSRalf Baechle  * Queue an APM event for kapmd to process and ultimately take the
7307726942fSRalf Baechle  * appropriate action.  Only a subset of events are handled:
7317726942fSRalf Baechle  *   %APM_LOW_BATTERY
7327726942fSRalf Baechle  *   %APM_POWER_STATUS_CHANGE
7337726942fSRalf Baechle  *   %APM_USER_SUSPEND
7347726942fSRalf Baechle  *   %APM_SYS_SUSPEND
7357726942fSRalf Baechle  *   %APM_CRITICAL_SUSPEND
7367726942fSRalf Baechle  */
7377726942fSRalf Baechle void apm_queue_event(apm_event_t event)
7387726942fSRalf Baechle {
7397726942fSRalf Baechle 	unsigned long flags;
7407726942fSRalf Baechle 
7417726942fSRalf Baechle 	spin_lock_irqsave(&kapmd_queue_lock, flags);
7427726942fSRalf Baechle 	queue_add_event(&kapmd_queue, event);
7437726942fSRalf Baechle 	spin_unlock_irqrestore(&kapmd_queue_lock, flags);
7447726942fSRalf Baechle 
7457726942fSRalf Baechle 	wake_up_interruptible(&kapmd_wait);
7467726942fSRalf Baechle }
7477726942fSRalf Baechle EXPORT_SYMBOL(apm_queue_event);
748