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