xref: /linux/drivers/char/apm-emulation.c (revision 83bd89291f5cc866f60d32c34e268896c7ba8a3d)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * bios-less APM driver for ARM Linux
4  *  Jamey Hicks <jamey@crl.dec.com>
5  *  adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
6  *
7  * APM 1.2 Reference:
8  *   Intel Corporation, Microsoft Corporation. Advanced Power Management
9  *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
10  *
11  * This document is available from Microsoft at:
12  *    http://www.microsoft.com/whdc/archive/amp_12.mspx
13  */
14 #include <linux/module.h>
15 #include <linux/poll.h>
16 #include <linux/slab.h>
17 #include <linux/mutex.h>
18 #include <linux/proc_fs.h>
19 #include <linux/seq_file.h>
20 #include <linux/miscdevice.h>
21 #include <linux/apm_bios.h>
22 #include <linux/capability.h>
23 #include <linux/sched.h>
24 #include <linux/suspend.h>
25 #include <linux/apm-emulation.h>
26 #include <linux/freezer.h>
27 #include <linux/device.h>
28 #include <linux/kernel.h>
29 #include <linux/list.h>
30 #include <linux/init.h>
31 #include <linux/completion.h>
32 #include <linux/kthread.h>
33 #include <linux/delay.h>
34 
35 /*
36  * One option can be changed at boot time as follows:
37  *	apm=on/off			enable/disable APM
38  */
39 
40 /*
41  * Maximum number of events stored
42  */
43 #define APM_MAX_EVENTS		16
44 
45 struct apm_queue {
46 	unsigned int		event_head;
47 	unsigned int		event_tail;
48 	apm_event_t		events[APM_MAX_EVENTS];
49 };
50 
51 /*
52  * thread states (for threads using a writable /dev/apm_bios fd):
53  *
54  * SUSPEND_NONE:	nothing happening
55  * SUSPEND_PENDING:	suspend event queued for thread and pending to be read
56  * SUSPEND_READ:	suspend event read, pending acknowledgement
57  * SUSPEND_ACKED:	acknowledgement received from thread (via ioctl),
58  *			waiting for resume
59  * SUSPEND_ACKTO:	acknowledgement timeout
60  * SUSPEND_DONE:	thread had acked suspend and is now notified of
61  *			resume
62  *
63  * SUSPEND_WAIT:	this thread invoked suspend and is waiting for resume
64  *
65  * A thread migrates in one of three paths:
66  *	NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE
67  *				    -6-> ACKTO -7-> NONE
68  *	NONE -8-> WAIT -9-> NONE
69  *
70  * While in PENDING or READ, the thread is accounted for in the
71  * suspend_acks_pending counter.
72  *
73  * The transitions are invoked as follows:
74  *	1: suspend event is signalled from the core PM code
75  *	2: the suspend event is read from the fd by the userspace thread
76  *	3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack)
77  *	4: core PM code signals that we have resumed
78  *	5: APM_IOC_SUSPEND ioctl returns
79  *
80  *	6: the notifier invoked from the core PM code timed out waiting
81  *	   for all relevant threds to enter ACKED state and puts those
82  *	   that haven't into ACKTO
83  *	7: those threads issue APM_IOC_SUSPEND ioctl too late,
84  *	   get an error
85  *
86  *	8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend),
87  *	   ioctl code invokes pm_suspend()
88  *	9: pm_suspend() returns indicating resume
89  */
90 enum apm_suspend_state {
91 	SUSPEND_NONE,
92 	SUSPEND_PENDING,
93 	SUSPEND_READ,
94 	SUSPEND_ACKED,
95 	SUSPEND_ACKTO,
96 	SUSPEND_WAIT,
97 	SUSPEND_DONE,
98 };
99 
100 /*
101  * The per-file APM data
102  */
103 struct apm_user {
104 	struct list_head	list;
105 
106 	unsigned int		suser: 1;
107 	unsigned int		writer: 1;
108 	unsigned int		reader: 1;
109 
110 	int			suspend_result;
111 	enum apm_suspend_state	suspend_state;
112 
113 	struct apm_queue	queue;
114 };
115 
116 /*
117  * Local variables
118  */
119 static atomic_t suspend_acks_pending = ATOMIC_INIT(0);
120 static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0);
121 static int apm_disabled;
122 static struct task_struct *kapmd_tsk;
123 
124 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
125 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
126 
127 /*
128  * This is a list of everyone who has opened /dev/apm_bios
129  */
130 static DECLARE_RWSEM(user_list_lock);
131 static LIST_HEAD(apm_user_list);
132 
133 /*
134  * kapmd info.  kapmd provides us a process context to handle
135  * "APM" events within - specifically necessary if we're going
136  * to be suspending the system.
137  */
138 static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
139 static DEFINE_SPINLOCK(kapmd_queue_lock);
140 static struct apm_queue kapmd_queue;
141 
142 static DEFINE_MUTEX(state_lock);
143 
144 
145 /*
146  * This allows machines to provide their own "apm get power status" function.
147  */
148 void (*apm_get_power_status)(struct apm_power_info *);
149 EXPORT_SYMBOL(apm_get_power_status);
150 
151 
152 /*
153  * APM event queue management.
154  */
queue_empty(struct apm_queue * q)155 static inline int queue_empty(struct apm_queue *q)
156 {
157 	return q->event_head == q->event_tail;
158 }
159 
queue_get_event(struct apm_queue * q)160 static inline apm_event_t queue_get_event(struct apm_queue *q)
161 {
162 	q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
163 	return q->events[q->event_tail];
164 }
165 
queue_add_event(struct apm_queue * q,apm_event_t event)166 static void queue_add_event(struct apm_queue *q, apm_event_t event)
167 {
168 	q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
169 	if (q->event_head == q->event_tail) {
170 		static int notified;
171 
172 		if (notified++ == 0)
173 		    printk(KERN_ERR "apm: an event queue overflowed\n");
174 		q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
175 	}
176 	q->events[q->event_head] = event;
177 }
178 
queue_event(apm_event_t event)179 static void queue_event(apm_event_t event)
180 {
181 	struct apm_user *as;
182 
183 	down_read(&user_list_lock);
184 	list_for_each_entry(as, &apm_user_list, list) {
185 		if (as->reader)
186 			queue_add_event(&as->queue, event);
187 	}
188 	up_read(&user_list_lock);
189 	wake_up_interruptible(&apm_waitqueue);
190 }
191 
apm_read(struct file * fp,char __user * buf,size_t count,loff_t * ppos)192 static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
193 {
194 	struct apm_user *as = fp->private_data;
195 	apm_event_t event;
196 	int i = count, ret = 0;
197 
198 	if (count < sizeof(apm_event_t))
199 		return -EINVAL;
200 
201 	if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
202 		return -EAGAIN;
203 
204 	wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
205 
206 	while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
207 		event = queue_get_event(&as->queue);
208 
209 		ret = -EFAULT;
210 		if (copy_to_user(buf, &event, sizeof(event)))
211 			break;
212 
213 		mutex_lock(&state_lock);
214 		if (as->suspend_state == SUSPEND_PENDING &&
215 		    (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
216 			as->suspend_state = SUSPEND_READ;
217 		mutex_unlock(&state_lock);
218 
219 		buf += sizeof(event);
220 		i -= sizeof(event);
221 	}
222 
223 	if (i < count)
224 		ret = count - i;
225 
226 	return ret;
227 }
228 
apm_poll(struct file * fp,poll_table * wait)229 static __poll_t apm_poll(struct file *fp, poll_table * wait)
230 {
231 	struct apm_user *as = fp->private_data;
232 
233 	poll_wait(fp, &apm_waitqueue, wait);
234 	return queue_empty(&as->queue) ? 0 : EPOLLIN | EPOLLRDNORM;
235 }
236 
237 /*
238  * apm_ioctl - handle APM ioctl
239  *
240  * APM_IOC_SUSPEND
241  *   This IOCTL is overloaded, and performs two functions.  It is used to:
242  *     - initiate a suspend
243  *     - acknowledge a suspend read from /dev/apm_bios.
244  *   Only when everyone who has opened /dev/apm_bios with write permission
245  *   has acknowledge does the actual suspend happen.
246  */
247 static long
apm_ioctl(struct file * filp,u_int cmd,u_long arg)248 apm_ioctl(struct file *filp, u_int cmd, u_long arg)
249 {
250 	struct apm_user *as = filp->private_data;
251 	int err = -EINVAL;
252 
253 	if (!as->suser || !as->writer)
254 		return -EPERM;
255 
256 	switch (cmd) {
257 	case APM_IOC_SUSPEND:
258 		mutex_lock(&state_lock);
259 
260 		as->suspend_result = -EINTR;
261 
262 		switch (as->suspend_state) {
263 		case SUSPEND_READ:
264 			/*
265 			 * If we read a suspend command from /dev/apm_bios,
266 			 * then the corresponding APM_IOC_SUSPEND ioctl is
267 			 * interpreted as an acknowledge.
268 			 */
269 			as->suspend_state = SUSPEND_ACKED;
270 			atomic_dec(&suspend_acks_pending);
271 			mutex_unlock(&state_lock);
272 
273 			/*
274 			 * suspend_acks_pending changed, the notifier needs to
275 			 * be woken up for this
276 			 */
277 			wake_up(&apm_suspend_waitqueue);
278 
279 			/*
280 			 * Wait for the suspend/resume to complete.  If there
281 			 * are pending acknowledges, we wait here for them.
282 			 * wait_event_freezable() is interruptible and pending
283 			 * signal can cause busy looping.  We aren't doing
284 			 * anything critical, chill a bit on each iteration.
285 			 */
286 			while (wait_event_freezable(apm_suspend_waitqueue,
287 					as->suspend_state != SUSPEND_ACKED))
288 				msleep(10);
289 			break;
290 		case SUSPEND_ACKTO:
291 			as->suspend_result = -ETIMEDOUT;
292 			mutex_unlock(&state_lock);
293 			break;
294 		default:
295 			as->suspend_state = SUSPEND_WAIT;
296 			mutex_unlock(&state_lock);
297 
298 			/*
299 			 * Otherwise it is a request to suspend the system.
300 			 * Just invoke pm_suspend(), we'll handle it from
301 			 * there via the notifier.
302 			 */
303 			as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
304 		}
305 
306 		mutex_lock(&state_lock);
307 		err = as->suspend_result;
308 		as->suspend_state = SUSPEND_NONE;
309 		mutex_unlock(&state_lock);
310 		break;
311 	}
312 
313 	return err;
314 }
315 
apm_release(struct inode * inode,struct file * filp)316 static int apm_release(struct inode * inode, struct file * filp)
317 {
318 	struct apm_user *as = filp->private_data;
319 
320 	filp->private_data = NULL;
321 
322 	down_write(&user_list_lock);
323 	list_del(&as->list);
324 	up_write(&user_list_lock);
325 
326 	/*
327 	 * We are now unhooked from the chain.  As far as new
328 	 * events are concerned, we no longer exist.
329 	 */
330 	mutex_lock(&state_lock);
331 	if (as->suspend_state == SUSPEND_PENDING ||
332 	    as->suspend_state == SUSPEND_READ)
333 		atomic_dec(&suspend_acks_pending);
334 	mutex_unlock(&state_lock);
335 
336 	wake_up(&apm_suspend_waitqueue);
337 
338 	kfree(as);
339 	return 0;
340 }
341 
apm_open(struct inode * inode,struct file * filp)342 static int apm_open(struct inode * inode, struct file * filp)
343 {
344 	struct apm_user *as;
345 
346 	as = kzalloc(sizeof(*as), GFP_KERNEL);
347 	if (as) {
348 		/*
349 		 * XXX - this is a tiny bit broken, when we consider BSD
350 		 * process accounting. If the device is opened by root, we
351 		 * instantly flag that we used superuser privs. Who knows,
352 		 * we might close the device immediately without doing a
353 		 * privileged operation -- cevans
354 		 */
355 		as->suser = capable(CAP_SYS_ADMIN);
356 		as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
357 		as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
358 
359 		down_write(&user_list_lock);
360 		list_add(&as->list, &apm_user_list);
361 		up_write(&user_list_lock);
362 
363 		filp->private_data = as;
364 	}
365 
366 	return as ? 0 : -ENOMEM;
367 }
368 
369 static const struct file_operations apm_bios_fops = {
370 	.owner		= THIS_MODULE,
371 	.read		= apm_read,
372 	.poll		= apm_poll,
373 	.unlocked_ioctl	= apm_ioctl,
374 	.open		= apm_open,
375 	.release	= apm_release,
376 	.llseek		= noop_llseek,
377 };
378 
379 static struct miscdevice apm_device = {
380 	.minor		= APM_MINOR_DEV,
381 	.name		= "apm_bios",
382 	.fops		= &apm_bios_fops
383 };
384 
385 
386 #ifdef CONFIG_PROC_FS
387 /*
388  * Arguments, with symbols from linux/apm_bios.h.
389  *
390  *   0) Linux driver version (this will change if format changes)
391  *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
392  *   2) APM flags from APM Installation Check (0x00):
393  *	bit 0: APM_16_BIT_SUPPORT
394  *	bit 1: APM_32_BIT_SUPPORT
395  *	bit 2: APM_IDLE_SLOWS_CLOCK
396  *	bit 3: APM_BIOS_DISABLED
397  *	bit 4: APM_BIOS_DISENGAGED
398  *   3) AC line status
399  *	0x00: Off-line
400  *	0x01: On-line
401  *	0x02: On backup power (BIOS >= 1.1 only)
402  *	0xff: Unknown
403  *   4) Battery status
404  *	0x00: High
405  *	0x01: Low
406  *	0x02: Critical
407  *	0x03: Charging
408  *	0x04: Selected battery not present (BIOS >= 1.2 only)
409  *	0xff: Unknown
410  *   5) Battery flag
411  *	bit 0: High
412  *	bit 1: Low
413  *	bit 2: Critical
414  *	bit 3: Charging
415  *	bit 7: No system battery
416  *	0xff: Unknown
417  *   6) Remaining battery life (percentage of charge):
418  *	0-100: valid
419  *	-1: Unknown
420  *   7) Remaining battery life (time units):
421  *	Number of remaining minutes or seconds
422  *	-1: Unknown
423  *   8) min = minutes; sec = seconds
424  */
proc_apm_show(struct seq_file * m,void * v)425 static int proc_apm_show(struct seq_file *m, void *v)
426 {
427 	static const char driver_version[] = "1.13";	/* no spaces */
428 
429 	struct apm_power_info info;
430 	char *units;
431 
432 	info.ac_line_status = 0xff;
433 	info.battery_status = 0xff;
434 	info.battery_flag   = 0xff;
435 	info.battery_life   = -1;
436 	info.time	    = -1;
437 	info.units	    = -1;
438 
439 	if (apm_get_power_status)
440 		apm_get_power_status(&info);
441 
442 	switch (info.units) {
443 	default:	units = "?";	break;
444 	case 0: 	units = "min";	break;
445 	case 1: 	units = "sec";	break;
446 	}
447 
448 	seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
449 		     driver_version, APM_32_BIT_SUPPORT,
450 		     info.ac_line_status, info.battery_status,
451 		     info.battery_flag, info.battery_life,
452 		     info.time, units);
453 
454 	return 0;
455 }
456 #endif
457 
kapmd(void * arg)458 static int kapmd(void *arg)
459 {
460 	do {
461 		apm_event_t event;
462 
463 		wait_event_interruptible(kapmd_wait,
464 				!queue_empty(&kapmd_queue) || kthread_should_stop());
465 
466 		if (kthread_should_stop())
467 			break;
468 
469 		spin_lock_irq(&kapmd_queue_lock);
470 		event = 0;
471 		if (!queue_empty(&kapmd_queue))
472 			event = queue_get_event(&kapmd_queue);
473 		spin_unlock_irq(&kapmd_queue_lock);
474 
475 		switch (event) {
476 		case 0:
477 			break;
478 
479 		case APM_LOW_BATTERY:
480 		case APM_POWER_STATUS_CHANGE:
481 			queue_event(event);
482 			break;
483 
484 		case APM_USER_SUSPEND:
485 		case APM_SYS_SUSPEND:
486 			pm_suspend(PM_SUSPEND_MEM);
487 			break;
488 
489 		case APM_CRITICAL_SUSPEND:
490 			atomic_inc(&userspace_notification_inhibit);
491 			pm_suspend(PM_SUSPEND_MEM);
492 			atomic_dec(&userspace_notification_inhibit);
493 			break;
494 		}
495 	} while (1);
496 
497 	return 0;
498 }
499 
apm_suspend_notifier(struct notifier_block * nb,unsigned long event,void * dummy)500 static int apm_suspend_notifier(struct notifier_block *nb,
501 				unsigned long event,
502 				void *dummy)
503 {
504 	struct apm_user *as;
505 	int err;
506 	unsigned long apm_event;
507 
508 	/* short-cut emergency suspends */
509 	if (atomic_read(&userspace_notification_inhibit))
510 		return NOTIFY_DONE;
511 
512 	switch (event) {
513 	case PM_SUSPEND_PREPARE:
514 	case PM_HIBERNATION_PREPARE:
515 		apm_event = (event == PM_SUSPEND_PREPARE) ?
516 			APM_USER_SUSPEND : APM_USER_HIBERNATION;
517 		/*
518 		 * Queue an event to all "writer" users that we want
519 		 * to suspend and need their ack.
520 		 */
521 		mutex_lock(&state_lock);
522 		down_read(&user_list_lock);
523 
524 		list_for_each_entry(as, &apm_user_list, list) {
525 			if (as->suspend_state != SUSPEND_WAIT && as->reader &&
526 			    as->writer && as->suser) {
527 				as->suspend_state = SUSPEND_PENDING;
528 				atomic_inc(&suspend_acks_pending);
529 				queue_add_event(&as->queue, apm_event);
530 			}
531 		}
532 
533 		up_read(&user_list_lock);
534 		mutex_unlock(&state_lock);
535 		wake_up_interruptible(&apm_waitqueue);
536 
537 		/*
538 		 * Wait for the suspend_acks_pending variable to drop to
539 		 * zero, meaning everybody acked the suspend event (or the
540 		 * process was killed.)
541 		 *
542 		 * If the app won't answer within a short while we assume it
543 		 * locked up and ignore it.
544 		 */
545 		err = wait_event_interruptible_timeout(
546 			apm_suspend_waitqueue,
547 			atomic_read(&suspend_acks_pending) == 0,
548 			5*HZ);
549 
550 		/* timed out */
551 		if (err == 0) {
552 			/*
553 			 * Move anybody who timed out to "ack timeout" state.
554 			 *
555 			 * We could time out and the userspace does the ACK
556 			 * right after we time out but before we enter the
557 			 * locked section here, but that's fine.
558 			 */
559 			mutex_lock(&state_lock);
560 			down_read(&user_list_lock);
561 			list_for_each_entry(as, &apm_user_list, list) {
562 				if (as->suspend_state == SUSPEND_PENDING ||
563 				    as->suspend_state == SUSPEND_READ) {
564 					as->suspend_state = SUSPEND_ACKTO;
565 					atomic_dec(&suspend_acks_pending);
566 				}
567 			}
568 			up_read(&user_list_lock);
569 			mutex_unlock(&state_lock);
570 		}
571 
572 		/* let suspend proceed */
573 		if (err >= 0)
574 			return NOTIFY_OK;
575 
576 		/* interrupted by signal */
577 		return notifier_from_errno(err);
578 
579 	case PM_POST_SUSPEND:
580 	case PM_POST_HIBERNATION:
581 		apm_event = (event == PM_POST_SUSPEND) ?
582 			APM_NORMAL_RESUME : APM_HIBERNATION_RESUME;
583 		/*
584 		 * Anyone on the APM queues will think we're still suspended.
585 		 * Send a message so everyone knows we're now awake again.
586 		 */
587 		queue_event(apm_event);
588 
589 		/*
590 		 * Finally, wake up anyone who is sleeping on the suspend.
591 		 */
592 		mutex_lock(&state_lock);
593 		down_read(&user_list_lock);
594 		list_for_each_entry(as, &apm_user_list, list) {
595 			if (as->suspend_state == SUSPEND_ACKED) {
596 				/*
597 				 * TODO: maybe grab error code, needs core
598 				 * changes to push the error to the notifier
599 				 * chain (could use the second parameter if
600 				 * implemented)
601 				 */
602 				as->suspend_result = 0;
603 				as->suspend_state = SUSPEND_DONE;
604 			}
605 		}
606 		up_read(&user_list_lock);
607 		mutex_unlock(&state_lock);
608 
609 		wake_up(&apm_suspend_waitqueue);
610 		return NOTIFY_OK;
611 
612 	default:
613 		return NOTIFY_DONE;
614 	}
615 }
616 
617 static struct notifier_block apm_notif_block = {
618 	.notifier_call = apm_suspend_notifier,
619 };
620 
apm_init(void)621 static int __init apm_init(void)
622 {
623 	int ret;
624 
625 	if (apm_disabled) {
626 		printk(KERN_NOTICE "apm: disabled on user request.\n");
627 		return -ENODEV;
628 	}
629 
630 	kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
631 	if (IS_ERR(kapmd_tsk)) {
632 		ret = PTR_ERR(kapmd_tsk);
633 		kapmd_tsk = NULL;
634 		goto out;
635 	}
636 	wake_up_process(kapmd_tsk);
637 
638 #ifdef CONFIG_PROC_FS
639 	proc_create_single("apm", 0, NULL, proc_apm_show);
640 #endif
641 
642 	ret = misc_register(&apm_device);
643 	if (ret)
644 		goto out_stop;
645 
646 	ret = register_pm_notifier(&apm_notif_block);
647 	if (ret)
648 		goto out_unregister;
649 
650 	return 0;
651 
652  out_unregister:
653 	misc_deregister(&apm_device);
654  out_stop:
655 	remove_proc_entry("apm", NULL);
656 	kthread_stop(kapmd_tsk);
657  out:
658 	return ret;
659 }
660 
apm_exit(void)661 static void __exit apm_exit(void)
662 {
663 	unregister_pm_notifier(&apm_notif_block);
664 	misc_deregister(&apm_device);
665 	remove_proc_entry("apm", NULL);
666 
667 	kthread_stop(kapmd_tsk);
668 }
669 
670 module_init(apm_init);
671 module_exit(apm_exit);
672 
673 MODULE_AUTHOR("Stephen Rothwell");
674 MODULE_DESCRIPTION("Advanced Power Management");
675 MODULE_LICENSE("GPL");
676 
677 #ifndef MODULE
apm_setup(char * str)678 static int __init apm_setup(char *str)
679 {
680 	while ((str != NULL) && (*str != '\0')) {
681 		if (strncmp(str, "off", 3) == 0)
682 			apm_disabled = 1;
683 		if (strncmp(str, "on", 2) == 0)
684 			apm_disabled = 0;
685 		str = strchr(str, ',');
686 		if (str != NULL)
687 			str += strspn(str, ", \t");
688 	}
689 	return 1;
690 }
691 
692 __setup("apm=", apm_setup);
693 #endif
694 
695 /**
696  * apm_queue_event - queue an APM event for kapmd
697  * @event: APM event
698  *
699  * Queue an APM event for kapmd to process and ultimately take the
700  * appropriate action.  Only a subset of events are handled:
701  *   %APM_LOW_BATTERY
702  *   %APM_POWER_STATUS_CHANGE
703  *   %APM_USER_SUSPEND
704  *   %APM_SYS_SUSPEND
705  *   %APM_CRITICAL_SUSPEND
706  */
apm_queue_event(apm_event_t event)707 void apm_queue_event(apm_event_t event)
708 {
709 	unsigned long flags;
710 
711 	spin_lock_irqsave(&kapmd_queue_lock, flags);
712 	queue_add_event(&kapmd_queue, event);
713 	spin_unlock_irqrestore(&kapmd_queue_lock, flags);
714 
715 	wake_up_interruptible(&kapmd_wait);
716 }
717 EXPORT_SYMBOL(apm_queue_event);
718