Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * bios-less APM driver for ARM Linux
0004  *  Jamey Hicks <jamey@crl.dec.com>
0005  *  adapted from the APM BIOS driver for Linux by Stephen Rothwell (sfr@linuxcare.com)
0006  *
0007  * APM 1.2 Reference:
0008  *   Intel Corporation, Microsoft Corporation. Advanced Power Management
0009  *   (APM) BIOS Interface Specification, Revision 1.2, February 1996.
0010  *
0011  * This document is available from Microsoft at:
0012  *    http://www.microsoft.com/whdc/archive/amp_12.mspx
0013  */
0014 #include <linux/module.h>
0015 #include <linux/poll.h>
0016 #include <linux/slab.h>
0017 #include <linux/mutex.h>
0018 #include <linux/proc_fs.h>
0019 #include <linux/seq_file.h>
0020 #include <linux/miscdevice.h>
0021 #include <linux/apm_bios.h>
0022 #include <linux/capability.h>
0023 #include <linux/sched.h>
0024 #include <linux/suspend.h>
0025 #include <linux/apm-emulation.h>
0026 #include <linux/freezer.h>
0027 #include <linux/device.h>
0028 #include <linux/kernel.h>
0029 #include <linux/list.h>
0030 #include <linux/init.h>
0031 #include <linux/completion.h>
0032 #include <linux/kthread.h>
0033 #include <linux/delay.h>
0034 
0035 /*
0036  * One option can be changed at boot time as follows:
0037  *  apm=on/off          enable/disable APM
0038  */
0039 
0040 /*
0041  * Maximum number of events stored
0042  */
0043 #define APM_MAX_EVENTS      16
0044 
0045 struct apm_queue {
0046     unsigned int        event_head;
0047     unsigned int        event_tail;
0048     apm_event_t     events[APM_MAX_EVENTS];
0049 };
0050 
0051 /*
0052  * thread states (for threads using a writable /dev/apm_bios fd):
0053  *
0054  * SUSPEND_NONE:    nothing happening
0055  * SUSPEND_PENDING: suspend event queued for thread and pending to be read
0056  * SUSPEND_READ:    suspend event read, pending acknowledgement
0057  * SUSPEND_ACKED:   acknowledgement received from thread (via ioctl),
0058  *          waiting for resume
0059  * SUSPEND_ACKTO:   acknowledgement timeout
0060  * SUSPEND_DONE:    thread had acked suspend and is now notified of
0061  *          resume
0062  *
0063  * SUSPEND_WAIT:    this thread invoked suspend and is waiting for resume
0064  *
0065  * A thread migrates in one of three paths:
0066  *  NONE -1-> PENDING -2-> READ -3-> ACKED -4-> DONE -5-> NONE
0067  *                  -6-> ACKTO -7-> NONE
0068  *  NONE -8-> WAIT -9-> NONE
0069  *
0070  * While in PENDING or READ, the thread is accounted for in the
0071  * suspend_acks_pending counter.
0072  *
0073  * The transitions are invoked as follows:
0074  *  1: suspend event is signalled from the core PM code
0075  *  2: the suspend event is read from the fd by the userspace thread
0076  *  3: userspace thread issues the APM_IOC_SUSPEND ioctl (as ack)
0077  *  4: core PM code signals that we have resumed
0078  *  5: APM_IOC_SUSPEND ioctl returns
0079  *
0080  *  6: the notifier invoked from the core PM code timed out waiting
0081  *     for all relevant threds to enter ACKED state and puts those
0082  *     that haven't into ACKTO
0083  *  7: those threads issue APM_IOC_SUSPEND ioctl too late,
0084  *     get an error
0085  *
0086  *  8: userspace thread issues the APM_IOC_SUSPEND ioctl (to suspend),
0087  *     ioctl code invokes pm_suspend()
0088  *  9: pm_suspend() returns indicating resume
0089  */
0090 enum apm_suspend_state {
0091     SUSPEND_NONE,
0092     SUSPEND_PENDING,
0093     SUSPEND_READ,
0094     SUSPEND_ACKED,
0095     SUSPEND_ACKTO,
0096     SUSPEND_WAIT,
0097     SUSPEND_DONE,
0098 };
0099 
0100 /*
0101  * The per-file APM data
0102  */
0103 struct apm_user {
0104     struct list_head    list;
0105 
0106     unsigned int        suser: 1;
0107     unsigned int        writer: 1;
0108     unsigned int        reader: 1;
0109 
0110     int         suspend_result;
0111     enum apm_suspend_state  suspend_state;
0112 
0113     struct apm_queue    queue;
0114 };
0115 
0116 /*
0117  * Local variables
0118  */
0119 static atomic_t suspend_acks_pending = ATOMIC_INIT(0);
0120 static atomic_t userspace_notification_inhibit = ATOMIC_INIT(0);
0121 static int apm_disabled;
0122 static struct task_struct *kapmd_tsk;
0123 
0124 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue);
0125 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue);
0126 
0127 /*
0128  * This is a list of everyone who has opened /dev/apm_bios
0129  */
0130 static DECLARE_RWSEM(user_list_lock);
0131 static LIST_HEAD(apm_user_list);
0132 
0133 /*
0134  * kapmd info.  kapmd provides us a process context to handle
0135  * "APM" events within - specifically necessary if we're going
0136  * to be suspending the system.
0137  */
0138 static DECLARE_WAIT_QUEUE_HEAD(kapmd_wait);
0139 static DEFINE_SPINLOCK(kapmd_queue_lock);
0140 static struct apm_queue kapmd_queue;
0141 
0142 static DEFINE_MUTEX(state_lock);
0143 
0144 static const char driver_version[] = "1.13";    /* no spaces */
0145 
0146 
0147 
0148 /*
0149  * Compatibility cruft until the IPAQ people move over to the new
0150  * interface.
0151  */
0152 static void __apm_get_power_status(struct apm_power_info *info)
0153 {
0154 }
0155 
0156 /*
0157  * This allows machines to provide their own "apm get power status" function.
0158  */
0159 void (*apm_get_power_status)(struct apm_power_info *) = __apm_get_power_status;
0160 EXPORT_SYMBOL(apm_get_power_status);
0161 
0162 
0163 /*
0164  * APM event queue management.
0165  */
0166 static inline int queue_empty(struct apm_queue *q)
0167 {
0168     return q->event_head == q->event_tail;
0169 }
0170 
0171 static inline apm_event_t queue_get_event(struct apm_queue *q)
0172 {
0173     q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
0174     return q->events[q->event_tail];
0175 }
0176 
0177 static void queue_add_event(struct apm_queue *q, apm_event_t event)
0178 {
0179     q->event_head = (q->event_head + 1) % APM_MAX_EVENTS;
0180     if (q->event_head == q->event_tail) {
0181         static int notified;
0182 
0183         if (notified++ == 0)
0184             printk(KERN_ERR "apm: an event queue overflowed\n");
0185         q->event_tail = (q->event_tail + 1) % APM_MAX_EVENTS;
0186     }
0187     q->events[q->event_head] = event;
0188 }
0189 
0190 static void queue_event(apm_event_t event)
0191 {
0192     struct apm_user *as;
0193 
0194     down_read(&user_list_lock);
0195     list_for_each_entry(as, &apm_user_list, list) {
0196         if (as->reader)
0197             queue_add_event(&as->queue, event);
0198     }
0199     up_read(&user_list_lock);
0200     wake_up_interruptible(&apm_waitqueue);
0201 }
0202 
0203 static ssize_t apm_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos)
0204 {
0205     struct apm_user *as = fp->private_data;
0206     apm_event_t event;
0207     int i = count, ret = 0;
0208 
0209     if (count < sizeof(apm_event_t))
0210         return -EINVAL;
0211 
0212     if (queue_empty(&as->queue) && fp->f_flags & O_NONBLOCK)
0213         return -EAGAIN;
0214 
0215     wait_event_interruptible(apm_waitqueue, !queue_empty(&as->queue));
0216 
0217     while ((i >= sizeof(event)) && !queue_empty(&as->queue)) {
0218         event = queue_get_event(&as->queue);
0219 
0220         ret = -EFAULT;
0221         if (copy_to_user(buf, &event, sizeof(event)))
0222             break;
0223 
0224         mutex_lock(&state_lock);
0225         if (as->suspend_state == SUSPEND_PENDING &&
0226             (event == APM_SYS_SUSPEND || event == APM_USER_SUSPEND))
0227             as->suspend_state = SUSPEND_READ;
0228         mutex_unlock(&state_lock);
0229 
0230         buf += sizeof(event);
0231         i -= sizeof(event);
0232     }
0233 
0234     if (i < count)
0235         ret = count - i;
0236 
0237     return ret;
0238 }
0239 
0240 static __poll_t apm_poll(struct file *fp, poll_table * wait)
0241 {
0242     struct apm_user *as = fp->private_data;
0243 
0244     poll_wait(fp, &apm_waitqueue, wait);
0245     return queue_empty(&as->queue) ? 0 : EPOLLIN | EPOLLRDNORM;
0246 }
0247 
0248 /*
0249  * apm_ioctl - handle APM ioctl
0250  *
0251  * APM_IOC_SUSPEND
0252  *   This IOCTL is overloaded, and performs two functions.  It is used to:
0253  *     - initiate a suspend
0254  *     - acknowledge a suspend read from /dev/apm_bios.
0255  *   Only when everyone who has opened /dev/apm_bios with write permission
0256  *   has acknowledge does the actual suspend happen.
0257  */
0258 static long
0259 apm_ioctl(struct file *filp, u_int cmd, u_long arg)
0260 {
0261     struct apm_user *as = filp->private_data;
0262     int err = -EINVAL;
0263 
0264     if (!as->suser || !as->writer)
0265         return -EPERM;
0266 
0267     switch (cmd) {
0268     case APM_IOC_SUSPEND:
0269         mutex_lock(&state_lock);
0270 
0271         as->suspend_result = -EINTR;
0272 
0273         switch (as->suspend_state) {
0274         case SUSPEND_READ:
0275             /*
0276              * If we read a suspend command from /dev/apm_bios,
0277              * then the corresponding APM_IOC_SUSPEND ioctl is
0278              * interpreted as an acknowledge.
0279              */
0280             as->suspend_state = SUSPEND_ACKED;
0281             atomic_dec(&suspend_acks_pending);
0282             mutex_unlock(&state_lock);
0283 
0284             /*
0285              * suspend_acks_pending changed, the notifier needs to
0286              * be woken up for this
0287              */
0288             wake_up(&apm_suspend_waitqueue);
0289 
0290             /*
0291              * Wait for the suspend/resume to complete.  If there
0292              * are pending acknowledges, we wait here for them.
0293              * wait_event_freezable() is interruptible and pending
0294              * signal can cause busy looping.  We aren't doing
0295              * anything critical, chill a bit on each iteration.
0296              */
0297             while (wait_event_freezable(apm_suspend_waitqueue,
0298                     as->suspend_state != SUSPEND_ACKED))
0299                 msleep(10);
0300             break;
0301         case SUSPEND_ACKTO:
0302             as->suspend_result = -ETIMEDOUT;
0303             mutex_unlock(&state_lock);
0304             break;
0305         default:
0306             as->suspend_state = SUSPEND_WAIT;
0307             mutex_unlock(&state_lock);
0308 
0309             /*
0310              * Otherwise it is a request to suspend the system.
0311              * Just invoke pm_suspend(), we'll handle it from
0312              * there via the notifier.
0313              */
0314             as->suspend_result = pm_suspend(PM_SUSPEND_MEM);
0315         }
0316 
0317         mutex_lock(&state_lock);
0318         err = as->suspend_result;
0319         as->suspend_state = SUSPEND_NONE;
0320         mutex_unlock(&state_lock);
0321         break;
0322     }
0323 
0324     return err;
0325 }
0326 
0327 static int apm_release(struct inode * inode, struct file * filp)
0328 {
0329     struct apm_user *as = filp->private_data;
0330 
0331     filp->private_data = NULL;
0332 
0333     down_write(&user_list_lock);
0334     list_del(&as->list);
0335     up_write(&user_list_lock);
0336 
0337     /*
0338      * We are now unhooked from the chain.  As far as new
0339      * events are concerned, we no longer exist.
0340      */
0341     mutex_lock(&state_lock);
0342     if (as->suspend_state == SUSPEND_PENDING ||
0343         as->suspend_state == SUSPEND_READ)
0344         atomic_dec(&suspend_acks_pending);
0345     mutex_unlock(&state_lock);
0346 
0347     wake_up(&apm_suspend_waitqueue);
0348 
0349     kfree(as);
0350     return 0;
0351 }
0352 
0353 static int apm_open(struct inode * inode, struct file * filp)
0354 {
0355     struct apm_user *as;
0356 
0357     as = kzalloc(sizeof(*as), GFP_KERNEL);
0358     if (as) {
0359         /*
0360          * XXX - this is a tiny bit broken, when we consider BSD
0361          * process accounting. If the device is opened by root, we
0362          * instantly flag that we used superuser privs. Who knows,
0363          * we might close the device immediately without doing a
0364          * privileged operation -- cevans
0365          */
0366         as->suser = capable(CAP_SYS_ADMIN);
0367         as->writer = (filp->f_mode & FMODE_WRITE) == FMODE_WRITE;
0368         as->reader = (filp->f_mode & FMODE_READ) == FMODE_READ;
0369 
0370         down_write(&user_list_lock);
0371         list_add(&as->list, &apm_user_list);
0372         up_write(&user_list_lock);
0373 
0374         filp->private_data = as;
0375     }
0376 
0377     return as ? 0 : -ENOMEM;
0378 }
0379 
0380 static const struct file_operations apm_bios_fops = {
0381     .owner      = THIS_MODULE,
0382     .read       = apm_read,
0383     .poll       = apm_poll,
0384     .unlocked_ioctl = apm_ioctl,
0385     .open       = apm_open,
0386     .release    = apm_release,
0387     .llseek     = noop_llseek,
0388 };
0389 
0390 static struct miscdevice apm_device = {
0391     .minor      = APM_MINOR_DEV,
0392     .name       = "apm_bios",
0393     .fops       = &apm_bios_fops
0394 };
0395 
0396 
0397 #ifdef CONFIG_PROC_FS
0398 /*
0399  * Arguments, with symbols from linux/apm_bios.h.
0400  *
0401  *   0) Linux driver version (this will change if format changes)
0402  *   1) APM BIOS Version.  Usually 1.0, 1.1 or 1.2.
0403  *   2) APM flags from APM Installation Check (0x00):
0404  *  bit 0: APM_16_BIT_SUPPORT
0405  *  bit 1: APM_32_BIT_SUPPORT
0406  *  bit 2: APM_IDLE_SLOWS_CLOCK
0407  *  bit 3: APM_BIOS_DISABLED
0408  *  bit 4: APM_BIOS_DISENGAGED
0409  *   3) AC line status
0410  *  0x00: Off-line
0411  *  0x01: On-line
0412  *  0x02: On backup power (BIOS >= 1.1 only)
0413  *  0xff: Unknown
0414  *   4) Battery status
0415  *  0x00: High
0416  *  0x01: Low
0417  *  0x02: Critical
0418  *  0x03: Charging
0419  *  0x04: Selected battery not present (BIOS >= 1.2 only)
0420  *  0xff: Unknown
0421  *   5) Battery flag
0422  *  bit 0: High
0423  *  bit 1: Low
0424  *  bit 2: Critical
0425  *  bit 3: Charging
0426  *  bit 7: No system battery
0427  *  0xff: Unknown
0428  *   6) Remaining battery life (percentage of charge):
0429  *  0-100: valid
0430  *  -1: Unknown
0431  *   7) Remaining battery life (time units):
0432  *  Number of remaining minutes or seconds
0433  *  -1: Unknown
0434  *   8) min = minutes; sec = seconds
0435  */
0436 static int proc_apm_show(struct seq_file *m, void *v)
0437 {
0438     struct apm_power_info info;
0439     char *units;
0440 
0441     info.ac_line_status = 0xff;
0442     info.battery_status = 0xff;
0443     info.battery_flag   = 0xff;
0444     info.battery_life   = -1;
0445     info.time       = -1;
0446     info.units      = -1;
0447 
0448     if (apm_get_power_status)
0449         apm_get_power_status(&info);
0450 
0451     switch (info.units) {
0452     default:    units = "?";    break;
0453     case 0:     units = "min";  break;
0454     case 1:     units = "sec";  break;
0455     }
0456 
0457     seq_printf(m, "%s 1.2 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n",
0458              driver_version, APM_32_BIT_SUPPORT,
0459              info.ac_line_status, info.battery_status,
0460              info.battery_flag, info.battery_life,
0461              info.time, units);
0462 
0463     return 0;
0464 }
0465 #endif
0466 
0467 static int kapmd(void *arg)
0468 {
0469     do {
0470         apm_event_t event;
0471 
0472         wait_event_interruptible(kapmd_wait,
0473                 !queue_empty(&kapmd_queue) || kthread_should_stop());
0474 
0475         if (kthread_should_stop())
0476             break;
0477 
0478         spin_lock_irq(&kapmd_queue_lock);
0479         event = 0;
0480         if (!queue_empty(&kapmd_queue))
0481             event = queue_get_event(&kapmd_queue);
0482         spin_unlock_irq(&kapmd_queue_lock);
0483 
0484         switch (event) {
0485         case 0:
0486             break;
0487 
0488         case APM_LOW_BATTERY:
0489         case APM_POWER_STATUS_CHANGE:
0490             queue_event(event);
0491             break;
0492 
0493         case APM_USER_SUSPEND:
0494         case APM_SYS_SUSPEND:
0495             pm_suspend(PM_SUSPEND_MEM);
0496             break;
0497 
0498         case APM_CRITICAL_SUSPEND:
0499             atomic_inc(&userspace_notification_inhibit);
0500             pm_suspend(PM_SUSPEND_MEM);
0501             atomic_dec(&userspace_notification_inhibit);
0502             break;
0503         }
0504     } while (1);
0505 
0506     return 0;
0507 }
0508 
0509 static int apm_suspend_notifier(struct notifier_block *nb,
0510                 unsigned long event,
0511                 void *dummy)
0512 {
0513     struct apm_user *as;
0514     int err;
0515     unsigned long apm_event;
0516 
0517     /* short-cut emergency suspends */
0518     if (atomic_read(&userspace_notification_inhibit))
0519         return NOTIFY_DONE;
0520 
0521     switch (event) {
0522     case PM_SUSPEND_PREPARE:
0523     case PM_HIBERNATION_PREPARE:
0524         apm_event = (event == PM_SUSPEND_PREPARE) ?
0525             APM_USER_SUSPEND : APM_USER_HIBERNATION;
0526         /*
0527          * Queue an event to all "writer" users that we want
0528          * to suspend and need their ack.
0529          */
0530         mutex_lock(&state_lock);
0531         down_read(&user_list_lock);
0532 
0533         list_for_each_entry(as, &apm_user_list, list) {
0534             if (as->suspend_state != SUSPEND_WAIT && as->reader &&
0535                 as->writer && as->suser) {
0536                 as->suspend_state = SUSPEND_PENDING;
0537                 atomic_inc(&suspend_acks_pending);
0538                 queue_add_event(&as->queue, apm_event);
0539             }
0540         }
0541 
0542         up_read(&user_list_lock);
0543         mutex_unlock(&state_lock);
0544         wake_up_interruptible(&apm_waitqueue);
0545 
0546         /*
0547          * Wait for the suspend_acks_pending variable to drop to
0548          * zero, meaning everybody acked the suspend event (or the
0549          * process was killed.)
0550          *
0551          * If the app won't answer within a short while we assume it
0552          * locked up and ignore it.
0553          */
0554         err = wait_event_interruptible_timeout(
0555             apm_suspend_waitqueue,
0556             atomic_read(&suspend_acks_pending) == 0,
0557             5*HZ);
0558 
0559         /* timed out */
0560         if (err == 0) {
0561             /*
0562              * Move anybody who timed out to "ack timeout" state.
0563              *
0564              * We could time out and the userspace does the ACK
0565              * right after we time out but before we enter the
0566              * locked section here, but that's fine.
0567              */
0568             mutex_lock(&state_lock);
0569             down_read(&user_list_lock);
0570             list_for_each_entry(as, &apm_user_list, list) {
0571                 if (as->suspend_state == SUSPEND_PENDING ||
0572                     as->suspend_state == SUSPEND_READ) {
0573                     as->suspend_state = SUSPEND_ACKTO;
0574                     atomic_dec(&suspend_acks_pending);
0575                 }
0576             }
0577             up_read(&user_list_lock);
0578             mutex_unlock(&state_lock);
0579         }
0580 
0581         /* let suspend proceed */
0582         if (err >= 0)
0583             return NOTIFY_OK;
0584 
0585         /* interrupted by signal */
0586         return notifier_from_errno(err);
0587 
0588     case PM_POST_SUSPEND:
0589     case PM_POST_HIBERNATION:
0590         apm_event = (event == PM_POST_SUSPEND) ?
0591             APM_NORMAL_RESUME : APM_HIBERNATION_RESUME;
0592         /*
0593          * Anyone on the APM queues will think we're still suspended.
0594          * Send a message so everyone knows we're now awake again.
0595          */
0596         queue_event(apm_event);
0597 
0598         /*
0599          * Finally, wake up anyone who is sleeping on the suspend.
0600          */
0601         mutex_lock(&state_lock);
0602         down_read(&user_list_lock);
0603         list_for_each_entry(as, &apm_user_list, list) {
0604             if (as->suspend_state == SUSPEND_ACKED) {
0605                 /*
0606                  * TODO: maybe grab error code, needs core
0607                  * changes to push the error to the notifier
0608                  * chain (could use the second parameter if
0609                  * implemented)
0610                  */
0611                 as->suspend_result = 0;
0612                 as->suspend_state = SUSPEND_DONE;
0613             }
0614         }
0615         up_read(&user_list_lock);
0616         mutex_unlock(&state_lock);
0617 
0618         wake_up(&apm_suspend_waitqueue);
0619         return NOTIFY_OK;
0620 
0621     default:
0622         return NOTIFY_DONE;
0623     }
0624 }
0625 
0626 static struct notifier_block apm_notif_block = {
0627     .notifier_call = apm_suspend_notifier,
0628 };
0629 
0630 static int __init apm_init(void)
0631 {
0632     int ret;
0633 
0634     if (apm_disabled) {
0635         printk(KERN_NOTICE "apm: disabled on user request.\n");
0636         return -ENODEV;
0637     }
0638 
0639     kapmd_tsk = kthread_create(kapmd, NULL, "kapmd");
0640     if (IS_ERR(kapmd_tsk)) {
0641         ret = PTR_ERR(kapmd_tsk);
0642         kapmd_tsk = NULL;
0643         goto out;
0644     }
0645     wake_up_process(kapmd_tsk);
0646 
0647 #ifdef CONFIG_PROC_FS
0648     proc_create_single("apm", 0, NULL, proc_apm_show);
0649 #endif
0650 
0651     ret = misc_register(&apm_device);
0652     if (ret)
0653         goto out_stop;
0654 
0655     ret = register_pm_notifier(&apm_notif_block);
0656     if (ret)
0657         goto out_unregister;
0658 
0659     return 0;
0660 
0661  out_unregister:
0662     misc_deregister(&apm_device);
0663  out_stop:
0664     remove_proc_entry("apm", NULL);
0665     kthread_stop(kapmd_tsk);
0666  out:
0667     return ret;
0668 }
0669 
0670 static void __exit apm_exit(void)
0671 {
0672     unregister_pm_notifier(&apm_notif_block);
0673     misc_deregister(&apm_device);
0674     remove_proc_entry("apm", NULL);
0675 
0676     kthread_stop(kapmd_tsk);
0677 }
0678 
0679 module_init(apm_init);
0680 module_exit(apm_exit);
0681 
0682 MODULE_AUTHOR("Stephen Rothwell");
0683 MODULE_DESCRIPTION("Advanced Power Management");
0684 MODULE_LICENSE("GPL");
0685 
0686 #ifndef MODULE
0687 static int __init apm_setup(char *str)
0688 {
0689     while ((str != NULL) && (*str != '\0')) {
0690         if (strncmp(str, "off", 3) == 0)
0691             apm_disabled = 1;
0692         if (strncmp(str, "on", 2) == 0)
0693             apm_disabled = 0;
0694         str = strchr(str, ',');
0695         if (str != NULL)
0696             str += strspn(str, ", \t");
0697     }
0698     return 1;
0699 }
0700 
0701 __setup("apm=", apm_setup);
0702 #endif
0703 
0704 /**
0705  * apm_queue_event - queue an APM event for kapmd
0706  * @event: APM event
0707  *
0708  * Queue an APM event for kapmd to process and ultimately take the
0709  * appropriate action.  Only a subset of events are handled:
0710  *   %APM_LOW_BATTERY
0711  *   %APM_POWER_STATUS_CHANGE
0712  *   %APM_USER_SUSPEND
0713  *   %APM_SYS_SUSPEND
0714  *   %APM_CRITICAL_SUSPEND
0715  */
0716 void apm_queue_event(apm_event_t event)
0717 {
0718     unsigned long flags;
0719 
0720     spin_lock_irqsave(&kapmd_queue_lock, flags);
0721     queue_add_event(&kapmd_queue, event);
0722     spin_unlock_irqrestore(&kapmd_queue_lock, flags);
0723 
0724     wake_up_interruptible(&kapmd_wait);
0725 }
0726 EXPORT_SYMBOL(apm_queue_event);