| 1 | |
| 2 | |
| 3 | * bios-less APM driver for ARM Linux
|
| 4 | * Jamey Hicks <[email protected]>
|
| 5 | * adapted from the APM BIOS driver for Linux by Stephen Rothwell ([email protected])
|
| 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 |
|
| 155 | static inline int queue_empty(struct apm_queue *q)
|
| 156 | {
|
| 157 | return q->event_head == q->event_tail;
|
| 158 | }
|
| 159 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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
|
| 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 |
|
| 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 |
|
| 342 | static int apm_open(struct inode * inode, struct file * filp)
|
| 343 | {
|
| 344 | struct apm_user *as;
|
| 345 |
|
| 346 | as = kzalloc_obj(*as);
|
| 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 |
|
| 425 | static int proc_apm_show(struct seq_file *m, void *v)
|
| 426 | {
|
| 427 | static const char driver_version[] = "1.13";
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 573 | if (err >= 0)
|
| 574 | return NOTIFY_OK;
|
| 575 |
|
| 576 |
|
| 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 |
|
| 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 |
|
| 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
|
| 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 |
|
| 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);
|