18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * bios-less APM driver for ARM Linux 48c2ecf20Sopenharmony_ci * Jamey Hicks <jamey@crl.dec.com> 58c2ecf20Sopenharmony_ci * adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com) 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * APM 1.2 Reference: 88c2ecf20Sopenharmony_ci * Intel Corporation, Microsoft Corporation. Advanced Power Management 98c2ecf20Sopenharmony_ci * (APM) BIOS Interface Specification, Revision 1.2, February 1996. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * This document is available from Microsoft at: 128c2ecf20Sopenharmony_ci * http://www.microsoft.com/whdc/archive/amp_12.mspx 138c2ecf20Sopenharmony_ci */ 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/poll.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/mutex.h> 188c2ecf20Sopenharmony_ci#include <linux/proc_fs.h> 198c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 208c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 218c2ecf20Sopenharmony_ci#include <linux/apm_bios.h> 228c2ecf20Sopenharmony_ci#include <linux/capability.h> 238c2ecf20Sopenharmony_ci#include <linux/sched.h> 248c2ecf20Sopenharmony_ci#include <linux/suspend.h> 258c2ecf20Sopenharmony_ci#include <linux/apm-emulation.h> 268c2ecf20Sopenharmony_ci#include <linux/freezer.h> 278c2ecf20Sopenharmony_ci#include <linux/device.h> 288c2ecf20Sopenharmony_ci#include <linux/kernel.h> 298c2ecf20Sopenharmony_ci#include <linux/list.h> 308c2ecf20Sopenharmony_ci#include <linux/init.h> 318c2ecf20Sopenharmony_ci#include <linux/completion.h> 328c2ecf20Sopenharmony_ci#include <linux/kthread.h> 338c2ecf20Sopenharmony_ci#include <linux/delay.h> 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci/* 368c2ecf20Sopenharmony_ci * One option can be changed at boot time as follows: 378c2ecf20Sopenharmony_ci * apm=on/off enable/disable APM 388c2ecf20Sopenharmony_ci */ 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* 418c2ecf20Sopenharmony_ci * Maximum number of events stored 428c2ecf20Sopenharmony_ci */ 438c2ecf20Sopenharmony_ci#define APM_MAX_EVENTS 16 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistruct apm_queue { 468c2ecf20Sopenharmony_ci unsigned int event_head; 478c2ecf20Sopenharmony_ci unsigned int event_tail; 488c2ecf20Sopenharmony_ci apm_event_t events[APM_MAX_EVENTS]; 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci/* 528c2ecf20Sopenharmony_ci * thread states (for threads using a writable /dev/apm_bios fd): 538c2ecf20Sopenharmony_ci * 548c2ecf20Sopenharmony_ci * SUSPEND_NONE: nothing happening 558c2ecf20Sopenharmony_ci * SUSPEND_PENDING: suspend event queued for thread and pending to be read 568c2ecf20Sopenharmony_ci * SUSPEND_READ: suspend event read, pending acknowledgement 578c2ecf20Sopenharmony_ci * SUSPEND_ACKED: acknowledgement received from thread (via ioctl), 588c2ecf20Sopenharmony_ci * waiting for resume 598c2ecf20Sopenharmony_ci * SUSPEND_ACKTO: acknowledgement timeout 608c2ecf20Sopenharmony_ci * SUSPEND_DONE: thread had acked suspend and is now notified of 618c2ecf20Sopenharmony_ci * resume 628c2ecf20Sopenharmony_ci * 638c2ecf20Sopenharmony_ci * SUSPEND_WAIT: this thread invoked suspend and is waiting for resume 648c2ecf20Sopenharmony_ci * 658c2ecf20Sopenharmony_ci * A thread migrates in one of three paths: 668c2ecf20Sopenharmony_ci * NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE 678c2ecf20Sopenharmony_ci * -6-> ACKTO -7-> NONE 688c2ecf20Sopenharmony_ci * NONE -8-> WAIT -9-> NONE 698c2ecf20Sopenharmony_ci * 708c2ecf20Sopenharmony_ci * While in PENDING or READ, the thread is accounted for in the 718c2ecf20Sopenharmony_ci * suspend_acks_pending counter. 728c2ecf20Sopenharmony_ci * 738c2ecf20Sopenharmony_ci * The transitions are invoked as follows: 748c2ecf20Sopenharmony_ci * 1: suspend event is signalled from the core PM code 758c2ecf20Sopenharmony_ci * 2: the suspend event is read from the fd by the userspace thread 768c2ecf20Sopenharmony_ci * 3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack) 778c2ecf20Sopenharmony_ci * 4: core PM code signals that we have resumed 788c2ecf20Sopenharmony_ci * 5: APM_IOC_SUSPEND ioctl returns 798c2ecf20Sopenharmony_ci * 808c2ecf20Sopenharmony_ci * 6: the notifier invoked from the core PM code timed out waiting 818c2ecf20Sopenharmony_ci * for all relevant threds to enter ACKED state and puts those 828c2ecf20Sopenharmony_ci * that haven't into ACKTO 838c2ecf20Sopenharmony_ci * 7: those threads issue APM_IOC_SUSPEND ioctl too late, 848c2ecf20Sopenharmony_ci * get an error 858c2ecf20Sopenharmony_ci * 868c2ecf20Sopenharmony_ci * 8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend), 878c2ecf20Sopenharmony_ci * ioctl code invokes pm_suspend() 888c2ecf20Sopenharmony_ci * 9: pm_suspend() returns indicating resume 898c2ecf20Sopenharmony_ci */ 908c2ecf20Sopenharmony_cienum apm_suspend_state { 918c2ecf20Sopenharmony_ci SUSPEND_NONE, 928c2ecf20Sopenharmony_ci SUSPEND_PENDING, 938c2ecf20Sopenharmony_ci SUSPEND_READ, 948c2ecf20Sopenharmony_ci SUSPEND_ACKED, 958c2ecf20Sopenharmony_ci SUSPEND_ACKTO, 968c2ecf20Sopenharmony_ci SUSPEND_WAIT, 978c2ecf20Sopenharmony_ci SUSPEND_DONE, 988c2ecf20Sopenharmony_ci}; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci/* 1018c2ecf20Sopenharmony_ci * The per-file APM data 1028c2ecf20Sopenharmony_ci */ 1038c2ecf20Sopenharmony_cistruct apm_user { 1048c2ecf20Sopenharmony_ci struct list_head list; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci unsigned int suser: 1; 1078c2ecf20Sopenharmony_ci unsigned int writer: 1; 1088c2ecf20Sopenharmony_ci unsigned int reader: 1; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci int suspend_result; 1118c2ecf20Sopenharmony_ci enum apm_suspend_state suspend_state; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci struct apm_queue queue; 1148c2ecf20Sopenharmony_ci}; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci/* 1178c2ecf20Sopenharmony_ci * Local variables 1188c2ecf20Sopenharmony_ci */ 1198c2ecf20Sopenharmony_cistatic atomic_t suspend_acks_pending = ATOMIC_INIT(0); 1208c2ecf20Sopenharmony_cistatic atomic_t userspace_notification_inhibit = ATOMIC_INIT(0); 1218c2ecf20Sopenharmony_cistatic int apm_disabled; 1228c2ecf20Sopenharmony_cistatic struct task_struct *kapmd_tsk; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue); 1258c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci/* 1288c2ecf20Sopenharmony_ci * This is a list of everyone who has opened /dev/apm_bios 1298c2ecf20Sopenharmony_ci */ 1308c2ecf20Sopenharmony_cistatic DECLARE_RWSEM(user_list_lock); 1318c2ecf20Sopenharmony_cistatic LIST_HEAD(apm_user_list); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci/* 1348c2ecf20Sopenharmony_ci * kapmd info. kapmd provides us a process context to handle 1358c2ecf20Sopenharmony_ci * "APM" events within - specifically necessary if we're going 1368c2ecf20Sopenharmony_ci * to be suspending the system. 1378c2ecf20Sopenharmony_ci */ 1388c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(kapmd_wait); 1398c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(kapmd_queue_lock); 1408c2ecf20Sopenharmony_cistatic struct apm_queue kapmd_queue; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(state_lock); 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_cistatic const char driver_version[] = "1.13"; /* no spaces */ 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci/* 1498c2ecf20Sopenharmony_ci * Compatibility cruft until the IPAQ people move over to the new 1508c2ecf20Sopenharmony_ci * interface. 1518c2ecf20Sopenharmony_ci */ 1528c2ecf20Sopenharmony_cistatic void __apm_get_power_status(struct apm_power_info *info) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci/* 1578c2ecf20Sopenharmony_ci * This allows machines to provide their own "apm get power status" function. 1588c2ecf20Sopenharmony_ci */ 1598c2ecf20Sopenharmony_civoid (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status; 1608c2ecf20Sopenharmony_ciEXPORT_SYMBOL(apm_get_power_status); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci/* 1648c2ecf20Sopenharmony_ci * APM event queue management. 1658c2ecf20Sopenharmony_ci */ 1668c2ecf20Sopenharmony_cistatic inline int queue_empty(struct apm_queue *q) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci return q->event_head == q->event_tail; 1698c2ecf20Sopenharmony_ci} 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cistatic inline apm_event_t queue_get_event(struct apm_queue *q) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS; 1748c2ecf20Sopenharmony_ci return q->events[q->event_tail]; 1758c2ecf20Sopenharmony_ci} 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_cistatic void queue_add_event(struct apm_queue *q, apm_event_t event) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci q->event_head = (q->event_head + 1) % APM_MAX_EVENTS; 1808c2ecf20Sopenharmony_ci if (q->event_head == q->event_tail) { 1818c2ecf20Sopenharmony_ci static int notified; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci if (notified++ == 0) 1848c2ecf20Sopenharmony_ci printk(KERN_ERR "apm: an event queue overflowed\n"); 1858c2ecf20Sopenharmony_ci q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS; 1868c2ecf20Sopenharmony_ci } 1878c2ecf20Sopenharmony_ci q->events[q->event_head] = event; 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic void queue_event(apm_event_t event) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct apm_user *as; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci down_read(&user_list_lock); 1958c2ecf20Sopenharmony_ci list_for_each_entry(as, &apm_user_list, list) { 1968c2ecf20Sopenharmony_ci if (as->reader) 1978c2ecf20Sopenharmony_ci queue_add_event(&as->queue, event); 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci up_read(&user_list_lock); 2008c2ecf20Sopenharmony_ci wake_up_interruptible(&apm_waitqueue); 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci struct apm_user *as = fp->private_data; 2068c2ecf20Sopenharmony_ci apm_event_t event; 2078c2ecf20Sopenharmony_ci int i = count, ret = 0; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci if (count < sizeof(apm_event_t)) 2108c2ecf20Sopenharmony_ci return -EINVAL; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK) 2138c2ecf20Sopenharmony_ci return -EAGAIN; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue)); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci while ((i >= sizeof(event)) && !queue_empty(&as->queue)) { 2188c2ecf20Sopenharmony_ci event = queue_get_event(&as->queue); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci ret = -EFAULT; 2218c2ecf20Sopenharmony_ci if (copy_to_user(buf, &event, sizeof(event))) 2228c2ecf20Sopenharmony_ci break; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci mutex_lock(&state_lock); 2258c2ecf20Sopenharmony_ci if (as->suspend_state == SUSPEND_PENDING && 2268c2ecf20Sopenharmony_ci (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND)) 2278c2ecf20Sopenharmony_ci as->suspend_state = SUSPEND_READ; 2288c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci buf += sizeof(event); 2318c2ecf20Sopenharmony_ci i -= sizeof(event); 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci if (i < count) 2358c2ecf20Sopenharmony_ci ret = count - i; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci return ret; 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_cistatic __poll_t apm_poll(struct file *fp, poll_table * wait) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci struct apm_user *as = fp->private_data; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci poll_wait(fp, &apm_waitqueue, wait); 2458c2ecf20Sopenharmony_ci return queue_empty(&as->queue) ? 0 : EPOLLIN | EPOLLRDNORM; 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci/* 2498c2ecf20Sopenharmony_ci * apm_ioctl - handle APM ioctl 2508c2ecf20Sopenharmony_ci * 2518c2ecf20Sopenharmony_ci * APM_IOC_SUSPEND 2528c2ecf20Sopenharmony_ci * This IOCTL is overloaded, and performs two functions. It is used to: 2538c2ecf20Sopenharmony_ci * - initiate a suspend 2548c2ecf20Sopenharmony_ci * - acknowledge a suspend read from /dev/apm_bios. 2558c2ecf20Sopenharmony_ci * Only when everyone who has opened /dev/apm_bios with write permission 2568c2ecf20Sopenharmony_ci * has acknowledge does the actual suspend happen. 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_cistatic long 2598c2ecf20Sopenharmony_ciapm_ioctl(struct file *filp, u_int cmd, u_long arg) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci struct apm_user *as = filp->private_data; 2628c2ecf20Sopenharmony_ci int err = -EINVAL; 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci if (!as->suser || !as->writer) 2658c2ecf20Sopenharmony_ci return -EPERM; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci switch (cmd) { 2688c2ecf20Sopenharmony_ci case APM_IOC_SUSPEND: 2698c2ecf20Sopenharmony_ci mutex_lock(&state_lock); 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci as->suspend_result = -EINTR; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci switch (as->suspend_state) { 2748c2ecf20Sopenharmony_ci case SUSPEND_READ: 2758c2ecf20Sopenharmony_ci /* 2768c2ecf20Sopenharmony_ci * If we read a suspend command from /dev/apm_bios, 2778c2ecf20Sopenharmony_ci * then the corresponding APM_IOC_SUSPEND ioctl is 2788c2ecf20Sopenharmony_ci * interpreted as an acknowledge. 2798c2ecf20Sopenharmony_ci */ 2808c2ecf20Sopenharmony_ci as->suspend_state = SUSPEND_ACKED; 2818c2ecf20Sopenharmony_ci atomic_dec(&suspend_acks_pending); 2828c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci /* 2858c2ecf20Sopenharmony_ci * suspend_acks_pending changed, the notifier needs to 2868c2ecf20Sopenharmony_ci * be woken up for this 2878c2ecf20Sopenharmony_ci */ 2888c2ecf20Sopenharmony_ci wake_up(&apm_suspend_waitqueue); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci /* 2918c2ecf20Sopenharmony_ci * Wait for the suspend/resume to complete. If there 2928c2ecf20Sopenharmony_ci * are pending acknowledges, we wait here for them. 2938c2ecf20Sopenharmony_ci * wait_event_freezable() is interruptible and pending 2948c2ecf20Sopenharmony_ci * signal can cause busy looping. We aren't doing 2958c2ecf20Sopenharmony_ci * anything critical, chill a bit on each iteration. 2968c2ecf20Sopenharmony_ci */ 2978c2ecf20Sopenharmony_ci while (wait_event_freezable(apm_suspend_waitqueue, 2988c2ecf20Sopenharmony_ci as->suspend_state != SUSPEND_ACKED)) 2998c2ecf20Sopenharmony_ci msleep(10); 3008c2ecf20Sopenharmony_ci break; 3018c2ecf20Sopenharmony_ci case SUSPEND_ACKTO: 3028c2ecf20Sopenharmony_ci as->suspend_result = -ETIMEDOUT; 3038c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 3048c2ecf20Sopenharmony_ci break; 3058c2ecf20Sopenharmony_ci default: 3068c2ecf20Sopenharmony_ci as->suspend_state = SUSPEND_WAIT; 3078c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci /* 3108c2ecf20Sopenharmony_ci * Otherwise it is a request to suspend the system. 3118c2ecf20Sopenharmony_ci * Just invoke pm_suspend(), we'll handle it from 3128c2ecf20Sopenharmony_ci * there via the notifier. 3138c2ecf20Sopenharmony_ci */ 3148c2ecf20Sopenharmony_ci as->suspend_result = pm_suspend(PM_SUSPEND_MEM); 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci mutex_lock(&state_lock); 3188c2ecf20Sopenharmony_ci err = as->suspend_result; 3198c2ecf20Sopenharmony_ci as->suspend_state = SUSPEND_NONE; 3208c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 3218c2ecf20Sopenharmony_ci break; 3228c2ecf20Sopenharmony_ci } 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci return err; 3258c2ecf20Sopenharmony_ci} 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_cistatic int apm_release(struct inode * inode, struct file * filp) 3288c2ecf20Sopenharmony_ci{ 3298c2ecf20Sopenharmony_ci struct apm_user *as = filp->private_data; 3308c2ecf20Sopenharmony_ci 3318c2ecf20Sopenharmony_ci filp->private_data = NULL; 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci down_write(&user_list_lock); 3348c2ecf20Sopenharmony_ci list_del(&as->list); 3358c2ecf20Sopenharmony_ci up_write(&user_list_lock); 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci /* 3388c2ecf20Sopenharmony_ci * We are now unhooked from the chain. As far as new 3398c2ecf20Sopenharmony_ci * events are concerned, we no longer exist. 3408c2ecf20Sopenharmony_ci */ 3418c2ecf20Sopenharmony_ci mutex_lock(&state_lock); 3428c2ecf20Sopenharmony_ci if (as->suspend_state == SUSPEND_PENDING || 3438c2ecf20Sopenharmony_ci as->suspend_state == SUSPEND_READ) 3448c2ecf20Sopenharmony_ci atomic_dec(&suspend_acks_pending); 3458c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 3468c2ecf20Sopenharmony_ci 3478c2ecf20Sopenharmony_ci wake_up(&apm_suspend_waitqueue); 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci kfree(as); 3508c2ecf20Sopenharmony_ci return 0; 3518c2ecf20Sopenharmony_ci} 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_cistatic int apm_open(struct inode * inode, struct file * filp) 3548c2ecf20Sopenharmony_ci{ 3558c2ecf20Sopenharmony_ci struct apm_user *as; 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci as = kzalloc(sizeof(*as), GFP_KERNEL); 3588c2ecf20Sopenharmony_ci if (as) { 3598c2ecf20Sopenharmony_ci /* 3608c2ecf20Sopenharmony_ci * XXX - this is a tiny bit broken, when we consider BSD 3618c2ecf20Sopenharmony_ci * process accounting. If the device is opened by root, we 3628c2ecf20Sopenharmony_ci * instantly flag that we used superuser privs. Who knows, 3638c2ecf20Sopenharmony_ci * we might close the device immediately without doing a 3648c2ecf20Sopenharmony_ci * privileged operation -- cevans 3658c2ecf20Sopenharmony_ci */ 3668c2ecf20Sopenharmony_ci as->suser = capable(CAP_SYS_ADMIN); 3678c2ecf20Sopenharmony_ci as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE; 3688c2ecf20Sopenharmony_ci as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ; 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci down_write(&user_list_lock); 3718c2ecf20Sopenharmony_ci list_add(&as->list, &apm_user_list); 3728c2ecf20Sopenharmony_ci up_write(&user_list_lock); 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci filp->private_data = as; 3758c2ecf20Sopenharmony_ci } 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_ci return as ? 0 : -ENOMEM; 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_cistatic const struct file_operations apm_bios_fops = { 3818c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 3828c2ecf20Sopenharmony_ci .read = apm_read, 3838c2ecf20Sopenharmony_ci .poll = apm_poll, 3848c2ecf20Sopenharmony_ci .unlocked_ioctl = apm_ioctl, 3858c2ecf20Sopenharmony_ci .open = apm_open, 3868c2ecf20Sopenharmony_ci .release = apm_release, 3878c2ecf20Sopenharmony_ci .llseek = noop_llseek, 3888c2ecf20Sopenharmony_ci}; 3898c2ecf20Sopenharmony_ci 3908c2ecf20Sopenharmony_cistatic struct miscdevice apm_device = { 3918c2ecf20Sopenharmony_ci .minor = APM_MINOR_DEV, 3928c2ecf20Sopenharmony_ci .name = "apm_bios", 3938c2ecf20Sopenharmony_ci .fops = &apm_bios_fops 3948c2ecf20Sopenharmony_ci}; 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci 3978c2ecf20Sopenharmony_ci#ifdef CONFIG_PROC_FS 3988c2ecf20Sopenharmony_ci/* 3998c2ecf20Sopenharmony_ci * Arguments, with symbols from linux/apm_bios.h. 4008c2ecf20Sopenharmony_ci * 4018c2ecf20Sopenharmony_ci * 0) Linux driver version (this will change if format changes) 4028c2ecf20Sopenharmony_ci * 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2. 4038c2ecf20Sopenharmony_ci * 2) APM flags from APM Installation Check (0x00): 4048c2ecf20Sopenharmony_ci * bit 0: APM_16_BIT_SUPPORT 4058c2ecf20Sopenharmony_ci * bit 1: APM_32_BIT_SUPPORT 4068c2ecf20Sopenharmony_ci * bit 2: APM_IDLE_SLOWS_CLOCK 4078c2ecf20Sopenharmony_ci * bit 3: APM_BIOS_DISABLED 4088c2ecf20Sopenharmony_ci * bit 4: APM_BIOS_DISENGAGED 4098c2ecf20Sopenharmony_ci * 3) AC line status 4108c2ecf20Sopenharmony_ci * 0x00: Off-line 4118c2ecf20Sopenharmony_ci * 0x01: On-line 4128c2ecf20Sopenharmony_ci * 0x02: On backup power (BIOS >= 1.1 only) 4138c2ecf20Sopenharmony_ci * 0xff: Unknown 4148c2ecf20Sopenharmony_ci * 4) Battery status 4158c2ecf20Sopenharmony_ci * 0x00: High 4168c2ecf20Sopenharmony_ci * 0x01: Low 4178c2ecf20Sopenharmony_ci * 0x02: Critical 4188c2ecf20Sopenharmony_ci * 0x03: Charging 4198c2ecf20Sopenharmony_ci * 0x04: Selected battery not present (BIOS >= 1.2 only) 4208c2ecf20Sopenharmony_ci * 0xff: Unknown 4218c2ecf20Sopenharmony_ci * 5) Battery flag 4228c2ecf20Sopenharmony_ci * bit 0: High 4238c2ecf20Sopenharmony_ci * bit 1: Low 4248c2ecf20Sopenharmony_ci * bit 2: Critical 4258c2ecf20Sopenharmony_ci * bit 3: Charging 4268c2ecf20Sopenharmony_ci * bit 7: No system battery 4278c2ecf20Sopenharmony_ci * 0xff: Unknown 4288c2ecf20Sopenharmony_ci * 6) Remaining battery life (percentage of charge): 4298c2ecf20Sopenharmony_ci * 0-100: valid 4308c2ecf20Sopenharmony_ci * -1: Unknown 4318c2ecf20Sopenharmony_ci * 7) Remaining battery life (time units): 4328c2ecf20Sopenharmony_ci * Number of remaining minutes or seconds 4338c2ecf20Sopenharmony_ci * -1: Unknown 4348c2ecf20Sopenharmony_ci * 8) min = minutes; sec = seconds 4358c2ecf20Sopenharmony_ci */ 4368c2ecf20Sopenharmony_cistatic int proc_apm_show(struct seq_file *m, void *v) 4378c2ecf20Sopenharmony_ci{ 4388c2ecf20Sopenharmony_ci struct apm_power_info info; 4398c2ecf20Sopenharmony_ci char *units; 4408c2ecf20Sopenharmony_ci 4418c2ecf20Sopenharmony_ci info.ac_line_status = 0xff; 4428c2ecf20Sopenharmony_ci info.battery_status = 0xff; 4438c2ecf20Sopenharmony_ci info.battery_flag = 0xff; 4448c2ecf20Sopenharmony_ci info.battery_life = -1; 4458c2ecf20Sopenharmony_ci info.time = -1; 4468c2ecf20Sopenharmony_ci info.units = -1; 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci if (apm_get_power_status) 4498c2ecf20Sopenharmony_ci apm_get_power_status(&info); 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci switch (info.units) { 4528c2ecf20Sopenharmony_ci default: units = "?"; break; 4538c2ecf20Sopenharmony_ci case 0: units = "min"; break; 4548c2ecf20Sopenharmony_ci case 1: units = "sec"; break; 4558c2ecf20Sopenharmony_ci } 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n", 4588c2ecf20Sopenharmony_ci driver_version, APM_32_BIT_SUPPORT, 4598c2ecf20Sopenharmony_ci info.ac_line_status, info.battery_status, 4608c2ecf20Sopenharmony_ci info.battery_flag, info.battery_life, 4618c2ecf20Sopenharmony_ci info.time, units); 4628c2ecf20Sopenharmony_ci 4638c2ecf20Sopenharmony_ci return 0; 4648c2ecf20Sopenharmony_ci} 4658c2ecf20Sopenharmony_ci#endif 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_cistatic int kapmd(void *arg) 4688c2ecf20Sopenharmony_ci{ 4698c2ecf20Sopenharmony_ci do { 4708c2ecf20Sopenharmony_ci apm_event_t event; 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci wait_event_interruptible(kapmd_wait, 4738c2ecf20Sopenharmony_ci !queue_empty(&kapmd_queue) || kthread_should_stop()); 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci if (kthread_should_stop()) 4768c2ecf20Sopenharmony_ci break; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci spin_lock_irq(&kapmd_queue_lock); 4798c2ecf20Sopenharmony_ci event = 0; 4808c2ecf20Sopenharmony_ci if (!queue_empty(&kapmd_queue)) 4818c2ecf20Sopenharmony_ci event = queue_get_event(&kapmd_queue); 4828c2ecf20Sopenharmony_ci spin_unlock_irq(&kapmd_queue_lock); 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci switch (event) { 4858c2ecf20Sopenharmony_ci case 0: 4868c2ecf20Sopenharmony_ci break; 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci case APM_LOW_BATTERY: 4898c2ecf20Sopenharmony_ci case APM_POWER_STATUS_CHANGE: 4908c2ecf20Sopenharmony_ci queue_event(event); 4918c2ecf20Sopenharmony_ci break; 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci case APM_USER_SUSPEND: 4948c2ecf20Sopenharmony_ci case APM_SYS_SUSPEND: 4958c2ecf20Sopenharmony_ci pm_suspend(PM_SUSPEND_MEM); 4968c2ecf20Sopenharmony_ci break; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci case APM_CRITICAL_SUSPEND: 4998c2ecf20Sopenharmony_ci atomic_inc(&userspace_notification_inhibit); 5008c2ecf20Sopenharmony_ci pm_suspend(PM_SUSPEND_MEM); 5018c2ecf20Sopenharmony_ci atomic_dec(&userspace_notification_inhibit); 5028c2ecf20Sopenharmony_ci break; 5038c2ecf20Sopenharmony_ci } 5048c2ecf20Sopenharmony_ci } while (1); 5058c2ecf20Sopenharmony_ci 5068c2ecf20Sopenharmony_ci return 0; 5078c2ecf20Sopenharmony_ci} 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_cistatic int apm_suspend_notifier(struct notifier_block *nb, 5108c2ecf20Sopenharmony_ci unsigned long event, 5118c2ecf20Sopenharmony_ci void *dummy) 5128c2ecf20Sopenharmony_ci{ 5138c2ecf20Sopenharmony_ci struct apm_user *as; 5148c2ecf20Sopenharmony_ci int err; 5158c2ecf20Sopenharmony_ci unsigned long apm_event; 5168c2ecf20Sopenharmony_ci 5178c2ecf20Sopenharmony_ci /* short-cut emergency suspends */ 5188c2ecf20Sopenharmony_ci if (atomic_read(&userspace_notification_inhibit)) 5198c2ecf20Sopenharmony_ci return NOTIFY_DONE; 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci switch (event) { 5228c2ecf20Sopenharmony_ci case PM_SUSPEND_PREPARE: 5238c2ecf20Sopenharmony_ci case PM_HIBERNATION_PREPARE: 5248c2ecf20Sopenharmony_ci apm_event = (event == PM_SUSPEND_PREPARE) ? 5258c2ecf20Sopenharmony_ci APM_USER_SUSPEND : APM_USER_HIBERNATION; 5268c2ecf20Sopenharmony_ci /* 5278c2ecf20Sopenharmony_ci * Queue an event to all "writer" users that we want 5288c2ecf20Sopenharmony_ci * to suspend and need their ack. 5298c2ecf20Sopenharmony_ci */ 5308c2ecf20Sopenharmony_ci mutex_lock(&state_lock); 5318c2ecf20Sopenharmony_ci down_read(&user_list_lock); 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci list_for_each_entry(as, &apm_user_list, list) { 5348c2ecf20Sopenharmony_ci if (as->suspend_state != SUSPEND_WAIT && as->reader && 5358c2ecf20Sopenharmony_ci as->writer && as->suser) { 5368c2ecf20Sopenharmony_ci as->suspend_state = SUSPEND_PENDING; 5378c2ecf20Sopenharmony_ci atomic_inc(&suspend_acks_pending); 5388c2ecf20Sopenharmony_ci queue_add_event(&as->queue, apm_event); 5398c2ecf20Sopenharmony_ci } 5408c2ecf20Sopenharmony_ci } 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_ci up_read(&user_list_lock); 5438c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 5448c2ecf20Sopenharmony_ci wake_up_interruptible(&apm_waitqueue); 5458c2ecf20Sopenharmony_ci 5468c2ecf20Sopenharmony_ci /* 5478c2ecf20Sopenharmony_ci * Wait for the the suspend_acks_pending variable to drop to 5488c2ecf20Sopenharmony_ci * zero, meaning everybody acked the suspend event (or the 5498c2ecf20Sopenharmony_ci * process was killed.) 5508c2ecf20Sopenharmony_ci * 5518c2ecf20Sopenharmony_ci * If the app won't answer within a short while we assume it 5528c2ecf20Sopenharmony_ci * locked up and ignore it. 5538c2ecf20Sopenharmony_ci */ 5548c2ecf20Sopenharmony_ci err = wait_event_interruptible_timeout( 5558c2ecf20Sopenharmony_ci apm_suspend_waitqueue, 5568c2ecf20Sopenharmony_ci atomic_read(&suspend_acks_pending) == 0, 5578c2ecf20Sopenharmony_ci 5*HZ); 5588c2ecf20Sopenharmony_ci 5598c2ecf20Sopenharmony_ci /* timed out */ 5608c2ecf20Sopenharmony_ci if (err == 0) { 5618c2ecf20Sopenharmony_ci /* 5628c2ecf20Sopenharmony_ci * Move anybody who timed out to "ack timeout" state. 5638c2ecf20Sopenharmony_ci * 5648c2ecf20Sopenharmony_ci * We could time out and the userspace does the ACK 5658c2ecf20Sopenharmony_ci * right after we time out but before we enter the 5668c2ecf20Sopenharmony_ci * locked section here, but that's fine. 5678c2ecf20Sopenharmony_ci */ 5688c2ecf20Sopenharmony_ci mutex_lock(&state_lock); 5698c2ecf20Sopenharmony_ci down_read(&user_list_lock); 5708c2ecf20Sopenharmony_ci list_for_each_entry(as, &apm_user_list, list) { 5718c2ecf20Sopenharmony_ci if (as->suspend_state == SUSPEND_PENDING || 5728c2ecf20Sopenharmony_ci as->suspend_state == SUSPEND_READ) { 5738c2ecf20Sopenharmony_ci as->suspend_state = SUSPEND_ACKTO; 5748c2ecf20Sopenharmony_ci atomic_dec(&suspend_acks_pending); 5758c2ecf20Sopenharmony_ci } 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci up_read(&user_list_lock); 5788c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 5798c2ecf20Sopenharmony_ci } 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_ci /* let suspend proceed */ 5828c2ecf20Sopenharmony_ci if (err >= 0) 5838c2ecf20Sopenharmony_ci return NOTIFY_OK; 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci /* interrupted by signal */ 5868c2ecf20Sopenharmony_ci return notifier_from_errno(err); 5878c2ecf20Sopenharmony_ci 5888c2ecf20Sopenharmony_ci case PM_POST_SUSPEND: 5898c2ecf20Sopenharmony_ci case PM_POST_HIBERNATION: 5908c2ecf20Sopenharmony_ci apm_event = (event == PM_POST_SUSPEND) ? 5918c2ecf20Sopenharmony_ci APM_NORMAL_RESUME : APM_HIBERNATION_RESUME; 5928c2ecf20Sopenharmony_ci /* 5938c2ecf20Sopenharmony_ci * Anyone on the APM queues will think we're still suspended. 5948c2ecf20Sopenharmony_ci * Send a message so everyone knows we're now awake again. 5958c2ecf20Sopenharmony_ci */ 5968c2ecf20Sopenharmony_ci queue_event(apm_event); 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_ci /* 5998c2ecf20Sopenharmony_ci * Finally, wake up anyone who is sleeping on the suspend. 6008c2ecf20Sopenharmony_ci */ 6018c2ecf20Sopenharmony_ci mutex_lock(&state_lock); 6028c2ecf20Sopenharmony_ci down_read(&user_list_lock); 6038c2ecf20Sopenharmony_ci list_for_each_entry(as, &apm_user_list, list) { 6048c2ecf20Sopenharmony_ci if (as->suspend_state == SUSPEND_ACKED) { 6058c2ecf20Sopenharmony_ci /* 6068c2ecf20Sopenharmony_ci * TODO: maybe grab error code, needs core 6078c2ecf20Sopenharmony_ci * changes to push the error to the notifier 6088c2ecf20Sopenharmony_ci * chain (could use the second parameter if 6098c2ecf20Sopenharmony_ci * implemented) 6108c2ecf20Sopenharmony_ci */ 6118c2ecf20Sopenharmony_ci as->suspend_result = 0; 6128c2ecf20Sopenharmony_ci as->suspend_state = SUSPEND_DONE; 6138c2ecf20Sopenharmony_ci } 6148c2ecf20Sopenharmony_ci } 6158c2ecf20Sopenharmony_ci up_read(&user_list_lock); 6168c2ecf20Sopenharmony_ci mutex_unlock(&state_lock); 6178c2ecf20Sopenharmony_ci 6188c2ecf20Sopenharmony_ci wake_up(&apm_suspend_waitqueue); 6198c2ecf20Sopenharmony_ci return NOTIFY_OK; 6208c2ecf20Sopenharmony_ci 6218c2ecf20Sopenharmony_ci default: 6228c2ecf20Sopenharmony_ci return NOTIFY_DONE; 6238c2ecf20Sopenharmony_ci } 6248c2ecf20Sopenharmony_ci} 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_cistatic struct notifier_block apm_notif_block = { 6278c2ecf20Sopenharmony_ci .notifier_call = apm_suspend_notifier, 6288c2ecf20Sopenharmony_ci}; 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_cistatic int __init apm_init(void) 6318c2ecf20Sopenharmony_ci{ 6328c2ecf20Sopenharmony_ci int ret; 6338c2ecf20Sopenharmony_ci 6348c2ecf20Sopenharmony_ci if (apm_disabled) { 6358c2ecf20Sopenharmony_ci printk(KERN_NOTICE "apm: disabled on user request.\n"); 6368c2ecf20Sopenharmony_ci return -ENODEV; 6378c2ecf20Sopenharmony_ci } 6388c2ecf20Sopenharmony_ci 6398c2ecf20Sopenharmony_ci kapmd_tsk = kthread_create(kapmd, NULL, "kapmd"); 6408c2ecf20Sopenharmony_ci if (IS_ERR(kapmd_tsk)) { 6418c2ecf20Sopenharmony_ci ret = PTR_ERR(kapmd_tsk); 6428c2ecf20Sopenharmony_ci kapmd_tsk = NULL; 6438c2ecf20Sopenharmony_ci goto out; 6448c2ecf20Sopenharmony_ci } 6458c2ecf20Sopenharmony_ci wake_up_process(kapmd_tsk); 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci#ifdef CONFIG_PROC_FS 6488c2ecf20Sopenharmony_ci proc_create_single("apm", 0, NULL, proc_apm_show); 6498c2ecf20Sopenharmony_ci#endif 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_ci ret = misc_register(&apm_device); 6528c2ecf20Sopenharmony_ci if (ret) 6538c2ecf20Sopenharmony_ci goto out_stop; 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ci ret = register_pm_notifier(&apm_notif_block); 6568c2ecf20Sopenharmony_ci if (ret) 6578c2ecf20Sopenharmony_ci goto out_unregister; 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci return 0; 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci out_unregister: 6628c2ecf20Sopenharmony_ci misc_deregister(&apm_device); 6638c2ecf20Sopenharmony_ci out_stop: 6648c2ecf20Sopenharmony_ci remove_proc_entry("apm", NULL); 6658c2ecf20Sopenharmony_ci kthread_stop(kapmd_tsk); 6668c2ecf20Sopenharmony_ci out: 6678c2ecf20Sopenharmony_ci return ret; 6688c2ecf20Sopenharmony_ci} 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_cistatic void __exit apm_exit(void) 6718c2ecf20Sopenharmony_ci{ 6728c2ecf20Sopenharmony_ci unregister_pm_notifier(&apm_notif_block); 6738c2ecf20Sopenharmony_ci misc_deregister(&apm_device); 6748c2ecf20Sopenharmony_ci remove_proc_entry("apm", NULL); 6758c2ecf20Sopenharmony_ci 6768c2ecf20Sopenharmony_ci kthread_stop(kapmd_tsk); 6778c2ecf20Sopenharmony_ci} 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_cimodule_init(apm_init); 6808c2ecf20Sopenharmony_cimodule_exit(apm_exit); 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ciMODULE_AUTHOR("Stephen Rothwell"); 6838c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Advanced Power Management"); 6848c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 6858c2ecf20Sopenharmony_ci 6868c2ecf20Sopenharmony_ci#ifndef MODULE 6878c2ecf20Sopenharmony_cistatic int __init apm_setup(char *str) 6888c2ecf20Sopenharmony_ci{ 6898c2ecf20Sopenharmony_ci while ((str != NULL) && (*str != '\0')) { 6908c2ecf20Sopenharmony_ci if (strncmp(str, "off", 3) == 0) 6918c2ecf20Sopenharmony_ci apm_disabled = 1; 6928c2ecf20Sopenharmony_ci if (strncmp(str, "on", 2) == 0) 6938c2ecf20Sopenharmony_ci apm_disabled = 0; 6948c2ecf20Sopenharmony_ci str = strchr(str, ','); 6958c2ecf20Sopenharmony_ci if (str != NULL) 6968c2ecf20Sopenharmony_ci str += strspn(str, ", \t"); 6978c2ecf20Sopenharmony_ci } 6988c2ecf20Sopenharmony_ci return 1; 6998c2ecf20Sopenharmony_ci} 7008c2ecf20Sopenharmony_ci 7018c2ecf20Sopenharmony_ci__setup("apm=", apm_setup); 7028c2ecf20Sopenharmony_ci#endif 7038c2ecf20Sopenharmony_ci 7048c2ecf20Sopenharmony_ci/** 7058c2ecf20Sopenharmony_ci * apm_queue_event - queue an APM event for kapmd 7068c2ecf20Sopenharmony_ci * @event: APM event 7078c2ecf20Sopenharmony_ci * 7088c2ecf20Sopenharmony_ci * Queue an APM event for kapmd to process and ultimately take the 7098c2ecf20Sopenharmony_ci * appropriate action. Only a subset of events are handled: 7108c2ecf20Sopenharmony_ci * %APM_LOW_BATTERY 7118c2ecf20Sopenharmony_ci * %APM_POWER_STATUS_CHANGE 7128c2ecf20Sopenharmony_ci * %APM_USER_SUSPEND 7138c2ecf20Sopenharmony_ci * %APM_SYS_SUSPEND 7148c2ecf20Sopenharmony_ci * %APM_CRITICAL_SUSPEND 7158c2ecf20Sopenharmony_ci */ 7168c2ecf20Sopenharmony_civoid apm_queue_event(apm_event_t event) 7178c2ecf20Sopenharmony_ci{ 7188c2ecf20Sopenharmony_ci unsigned long flags; 7198c2ecf20Sopenharmony_ci 7208c2ecf20Sopenharmony_ci spin_lock_irqsave(&kapmd_queue_lock, flags); 7218c2ecf20Sopenharmony_ci queue_add_event(&kapmd_queue, event); 7228c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&kapmd_queue_lock, flags); 7238c2ecf20Sopenharmony_ci 7248c2ecf20Sopenharmony_ci wake_up_interruptible(&kapmd_wait); 7258c2ecf20Sopenharmony_ci} 7268c2ecf20Sopenharmony_ciEXPORT_SYMBOL(apm_queue_event); 727