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