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