1 /* APM emulation layer for PowerMac 2 * 3 * Copyright 2001 Benjamin Herrenschmidt (benh@kernel.crashing.org) 4 * 5 * Lots of code inherited from apm.c, see appropriate notice in 6 * arch/i386/kernel/apm.c 7 * 8 * This program is free software; you can redistribute it and/or modify it 9 * under the terms of the GNU General Public License as published by the 10 * Free Software Foundation; either version 2, or (at your option) any 11 * later version. 12 * 13 * This program is distributed in the hope that it will be useful, but 14 * WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * General Public License for more details. 17 * 18 * 19 */ 20 21 #include <linux/module.h> 22 23 #include <linux/poll.h> 24 #include <linux/types.h> 25 #include <linux/stddef.h> 26 #include <linux/timer.h> 27 #include <linux/fcntl.h> 28 #include <linux/slab.h> 29 #include <linux/stat.h> 30 #include <linux/proc_fs.h> 31 #include <linux/miscdevice.h> 32 #include <linux/apm_bios.h> 33 #include <linux/init.h> 34 #include <linux/sched.h> 35 #include <linux/pm.h> 36 #include <linux/kernel.h> 37 #include <linux/smp_lock.h> 38 39 #include <linux/adb.h> 40 #include <linux/pmu.h> 41 42 #include <asm/system.h> 43 #include <asm/uaccess.h> 44 #include <asm/machdep.h> 45 46 #undef DEBUG 47 48 #ifdef DEBUG 49 #define DBG(args...) printk(KERN_DEBUG args) 50 //#define DBG(args...) xmon_printf(args) 51 #else 52 #define DBG(args...) do { } while (0) 53 #endif 54 55 /* 56 * The apm_bios device is one of the misc char devices. 57 * This is its minor number. 58 */ 59 #define APM_MINOR_DEV 134 60 61 /* 62 * Maximum number of events stored 63 */ 64 #define APM_MAX_EVENTS 20 65 66 #define FAKE_APM_BIOS_VERSION 0x0101 67 68 #define APM_USER_NOTIFY_TIMEOUT (5*HZ) 69 70 /* 71 * The per-file APM data 72 */ 73 struct apm_user { 74 int magic; 75 struct apm_user * next; 76 int suser: 1; 77 int suspend_waiting: 1; 78 int suspends_pending; 79 int suspends_read; 80 int event_head; 81 int event_tail; 82 apm_event_t events[APM_MAX_EVENTS]; 83 }; 84 85 /* 86 * The magic number in apm_user 87 */ 88 #define APM_BIOS_MAGIC 0x4101 89 90 /* 91 * Local variables 92 */ 93 static int suspends_pending; 94 95 static DECLARE_WAIT_QUEUE_HEAD(apm_waitqueue); 96 static DECLARE_WAIT_QUEUE_HEAD(apm_suspend_waitqueue); 97 static struct apm_user * user_list; 98 99 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when); 100 static struct pmu_sleep_notifier apm_sleep_notifier = { 101 apm_notify_sleep, 102 SLEEP_LEVEL_USERLAND, 103 }; 104 105 static char driver_version[] = "0.5"; /* no spaces */ 106 107 #ifdef DEBUG 108 static char * apm_event_name[] = { 109 "system standby", 110 "system suspend", 111 "normal resume", 112 "critical resume", 113 "low battery", 114 "power status change", 115 "update time", 116 "critical suspend", 117 "user standby", 118 "user suspend", 119 "system standby resume", 120 "capabilities change" 121 }; 122 #define NR_APM_EVENT_NAME \ 123 (sizeof(apm_event_name) / sizeof(apm_event_name[0])) 124 125 #endif 126 127 static int queue_empty(struct apm_user *as) 128 { 129 return as->event_head == as->event_tail; 130 } 131 132 static apm_event_t get_queued_event(struct apm_user *as) 133 { 134 as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS; 135 return as->events[as->event_tail]; 136 } 137 138 static void queue_event(apm_event_t event, struct apm_user *sender) 139 { 140 struct apm_user * as; 141 142 DBG("apm_emu: queue_event(%s)\n", apm_event_name[event-1]); 143 if (user_list == NULL) 144 return; 145 for (as = user_list; as != NULL; as = as->next) { 146 if (as == sender) 147 continue; 148 as->event_head = (as->event_head + 1) % APM_MAX_EVENTS; 149 if (as->event_head == as->event_tail) { 150 static int notified; 151 152 if (notified++ == 0) 153 printk(KERN_ERR "apm_emu: an event queue overflowed\n"); 154 as->event_tail = (as->event_tail + 1) % APM_MAX_EVENTS; 155 } 156 as->events[as->event_head] = event; 157 if (!as->suser) 158 continue; 159 switch (event) { 160 case APM_SYS_SUSPEND: 161 case APM_USER_SUSPEND: 162 as->suspends_pending++; 163 suspends_pending++; 164 break; 165 case APM_NORMAL_RESUME: 166 as->suspend_waiting = 0; 167 break; 168 } 169 } 170 wake_up_interruptible(&apm_waitqueue); 171 } 172 173 static int check_apm_user(struct apm_user *as, const char *func) 174 { 175 if ((as == NULL) || (as->magic != APM_BIOS_MAGIC)) { 176 printk(KERN_ERR "apm_emu: %s passed bad filp\n", func); 177 return 1; 178 } 179 return 0; 180 } 181 182 static ssize_t do_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) 183 { 184 struct apm_user * as; 185 size_t i; 186 apm_event_t event; 187 DECLARE_WAITQUEUE(wait, current); 188 189 as = fp->private_data; 190 if (check_apm_user(as, "read")) 191 return -EIO; 192 if (count < sizeof(apm_event_t)) 193 return -EINVAL; 194 if (queue_empty(as)) { 195 if (fp->f_flags & O_NONBLOCK) 196 return -EAGAIN; 197 add_wait_queue(&apm_waitqueue, &wait); 198 repeat: 199 set_current_state(TASK_INTERRUPTIBLE); 200 if (queue_empty(as) && !signal_pending(current)) { 201 schedule(); 202 goto repeat; 203 } 204 set_current_state(TASK_RUNNING); 205 remove_wait_queue(&apm_waitqueue, &wait); 206 } 207 i = count; 208 while ((i >= sizeof(event)) && !queue_empty(as)) { 209 event = get_queued_event(as); 210 DBG("apm_emu: do_read, returning: %s\n", apm_event_name[event-1]); 211 if (copy_to_user(buf, &event, sizeof(event))) { 212 if (i < count) 213 break; 214 return -EFAULT; 215 } 216 switch (event) { 217 case APM_SYS_SUSPEND: 218 case APM_USER_SUSPEND: 219 as->suspends_read++; 220 break; 221 } 222 buf += sizeof(event); 223 i -= sizeof(event); 224 } 225 if (i < count) 226 return count - i; 227 if (signal_pending(current)) 228 return -ERESTARTSYS; 229 return 0; 230 } 231 232 static unsigned int do_poll(struct file *fp, poll_table * wait) 233 { 234 struct apm_user * as; 235 236 as = fp->private_data; 237 if (check_apm_user(as, "poll")) 238 return 0; 239 poll_wait(fp, &apm_waitqueue, wait); 240 if (!queue_empty(as)) 241 return POLLIN | POLLRDNORM; 242 return 0; 243 } 244 245 static int do_ioctl(struct inode * inode, struct file *filp, 246 u_int cmd, u_long arg) 247 { 248 struct apm_user * as; 249 DECLARE_WAITQUEUE(wait, current); 250 251 as = filp->private_data; 252 if (check_apm_user(as, "ioctl")) 253 return -EIO; 254 if (!as->suser) 255 return -EPERM; 256 switch (cmd) { 257 case APM_IOC_SUSPEND: 258 /* If a suspend message was sent to userland, we 259 * consider this as a confirmation message 260 */ 261 if (as->suspends_read > 0) { 262 as->suspends_read--; 263 as->suspends_pending--; 264 suspends_pending--; 265 } else { 266 // Route to PMU suspend ? 267 break; 268 } 269 as->suspend_waiting = 1; 270 add_wait_queue(&apm_waitqueue, &wait); 271 DBG("apm_emu: ioctl waking up sleep waiter !\n"); 272 wake_up(&apm_suspend_waitqueue); 273 mb(); 274 while(as->suspend_waiting && !signal_pending(current)) { 275 set_current_state(TASK_INTERRUPTIBLE); 276 schedule(); 277 } 278 set_current_state(TASK_RUNNING); 279 remove_wait_queue(&apm_waitqueue, &wait); 280 break; 281 default: 282 return -EINVAL; 283 } 284 return 0; 285 } 286 287 static int do_release(struct inode * inode, struct file * filp) 288 { 289 struct apm_user * as; 290 291 as = filp->private_data; 292 if (check_apm_user(as, "release")) 293 return 0; 294 filp->private_data = NULL; 295 lock_kernel(); 296 if (as->suspends_pending > 0) { 297 suspends_pending -= as->suspends_pending; 298 if (suspends_pending <= 0) 299 wake_up(&apm_suspend_waitqueue); 300 } 301 if (user_list == as) 302 user_list = as->next; 303 else { 304 struct apm_user * as1; 305 306 for (as1 = user_list; 307 (as1 != NULL) && (as1->next != as); 308 as1 = as1->next) 309 ; 310 if (as1 == NULL) 311 printk(KERN_ERR "apm: filp not in user list\n"); 312 else 313 as1->next = as->next; 314 } 315 unlock_kernel(); 316 kfree(as); 317 return 0; 318 } 319 320 static int do_open(struct inode * inode, struct file * filp) 321 { 322 struct apm_user * as; 323 324 as = (struct apm_user *)kmalloc(sizeof(*as), GFP_KERNEL); 325 if (as == NULL) { 326 printk(KERN_ERR "apm: cannot allocate struct of size %d bytes\n", 327 sizeof(*as)); 328 return -ENOMEM; 329 } 330 as->magic = APM_BIOS_MAGIC; 331 as->event_tail = as->event_head = 0; 332 as->suspends_pending = 0; 333 as->suspends_read = 0; 334 /* 335 * XXX - this is a tiny bit broken, when we consider BSD 336 * process accounting. If the device is opened by root, we 337 * instantly flag that we used superuser privs. Who knows, 338 * we might close the device immediately without doing a 339 * privileged operation -- cevans 340 */ 341 as->suser = capable(CAP_SYS_ADMIN); 342 as->next = user_list; 343 user_list = as; 344 filp->private_data = as; 345 346 DBG("apm_emu: opened by %s, suser: %d\n", current->comm, (int)as->suser); 347 348 return 0; 349 } 350 351 /* Wait for all clients to ack the suspend request. APM API 352 * doesn't provide a way to NAK, but this could be added 353 * here. 354 */ 355 static int wait_all_suspend(void) 356 { 357 DECLARE_WAITQUEUE(wait, current); 358 359 add_wait_queue(&apm_suspend_waitqueue, &wait); 360 DBG("apm_emu: wait_all_suspend(), suspends_pending: %d\n", suspends_pending); 361 while(suspends_pending > 0) { 362 set_current_state(TASK_UNINTERRUPTIBLE); 363 schedule(); 364 } 365 set_current_state(TASK_RUNNING); 366 remove_wait_queue(&apm_suspend_waitqueue, &wait); 367 368 DBG("apm_emu: wait_all_suspend() - complete !\n"); 369 370 return 1; 371 } 372 373 static int apm_notify_sleep(struct pmu_sleep_notifier *self, int when) 374 { 375 switch(when) { 376 case PBOOK_SLEEP_REQUEST: 377 queue_event(APM_SYS_SUSPEND, NULL); 378 if (!wait_all_suspend()) 379 return PBOOK_SLEEP_REFUSE; 380 break; 381 case PBOOK_SLEEP_REJECT: 382 case PBOOK_WAKE: 383 queue_event(APM_NORMAL_RESUME, NULL); 384 break; 385 } 386 return PBOOK_SLEEP_OK; 387 } 388 389 #define APM_CRITICAL 10 390 #define APM_LOW 30 391 392 static int apm_emu_get_info(char *buf, char **start, off_t fpos, int length) 393 { 394 /* Arguments, with symbols from linux/apm_bios.h. Information is 395 from the Get Power Status (0x0a) call unless otherwise noted. 396 397 0) Linux driver version (this will change if format changes) 398 1) APM BIOS Version. Usually 1.0, 1.1 or 1.2. 399 2) APM flags from APM Installation Check (0x00): 400 bit 0: APM_16_BIT_SUPPORT 401 bit 1: APM_32_BIT_SUPPORT 402 bit 2: APM_IDLE_SLOWS_CLOCK 403 bit 3: APM_BIOS_DISABLED 404 bit 4: APM_BIOS_DISENGAGED 405 3) AC line status 406 0x00: Off-line 407 0x01: On-line 408 0x02: On backup power (BIOS >= 1.1 only) 409 0xff: Unknown 410 4) Battery status 411 0x00: High 412 0x01: Low 413 0x02: Critical 414 0x03: Charging 415 0x04: Selected battery not present (BIOS >= 1.2 only) 416 0xff: Unknown 417 5) Battery flag 418 bit 0: High 419 bit 1: Low 420 bit 2: Critical 421 bit 3: Charging 422 bit 7: No system battery 423 0xff: Unknown 424 6) Remaining battery life (percentage of charge): 425 0-100: valid 426 -1: Unknown 427 7) Remaining battery life (time units): 428 Number of remaining minutes or seconds 429 -1: Unknown 430 8) min = minutes; sec = seconds */ 431 432 unsigned short ac_line_status; 433 unsigned short battery_status = 0; 434 unsigned short battery_flag = 0xff; 435 int percentage = -1; 436 int time_units = -1; 437 int real_count = 0; 438 int i; 439 char * p = buf; 440 char charging = 0; 441 long charge = -1; 442 long amperage = 0; 443 unsigned long btype = 0; 444 445 ac_line_status = ((pmu_power_flags & PMU_PWR_AC_PRESENT) != 0); 446 for (i=0; i<pmu_battery_count; i++) { 447 if (pmu_batteries[i].flags & PMU_BATT_PRESENT) { 448 battery_status++; 449 if (percentage < 0) 450 percentage = 0; 451 if (charge < 0) 452 charge = 0; 453 percentage += (pmu_batteries[i].charge * 100) / 454 pmu_batteries[i].max_charge; 455 charge += pmu_batteries[i].charge; 456 amperage += pmu_batteries[i].amperage; 457 if (btype == 0) 458 btype = (pmu_batteries[i].flags & PMU_BATT_TYPE_MASK); 459 real_count++; 460 if ((pmu_batteries[i].flags & PMU_BATT_CHARGING)) 461 charging++; 462 } 463 } 464 if (0 == battery_status) 465 ac_line_status = 1; 466 battery_status = 0xff; 467 if (real_count) { 468 if (amperage < 0) { 469 if (btype == PMU_BATT_TYPE_SMART) 470 time_units = (charge * 59) / (amperage * -1); 471 else 472 time_units = (charge * 16440) / (amperage * -60); 473 } 474 percentage /= real_count; 475 if (charging > 0) { 476 battery_status = 0x03; 477 battery_flag = 0x08; 478 } else if (percentage <= APM_CRITICAL) { 479 battery_status = 0x02; 480 battery_flag = 0x04; 481 } else if (percentage <= APM_LOW) { 482 battery_status = 0x01; 483 battery_flag = 0x02; 484 } else { 485 battery_status = 0x00; 486 battery_flag = 0x01; 487 } 488 } 489 p += sprintf(p, "%s %d.%d 0x%02x 0x%02x 0x%02x 0x%02x %d%% %d %s\n", 490 driver_version, 491 (FAKE_APM_BIOS_VERSION >> 8) & 0xff, 492 FAKE_APM_BIOS_VERSION & 0xff, 493 0, 494 ac_line_status, 495 battery_status, 496 battery_flag, 497 percentage, 498 time_units, 499 "min"); 500 501 return p - buf; 502 } 503 504 static struct file_operations apm_bios_fops = { 505 .owner = THIS_MODULE, 506 .read = do_read, 507 .poll = do_poll, 508 .ioctl = do_ioctl, 509 .open = do_open, 510 .release = do_release, 511 }; 512 513 static struct miscdevice apm_device = { 514 APM_MINOR_DEV, 515 "apm_bios", 516 &apm_bios_fops 517 }; 518 519 static int __init apm_emu_init(void) 520 { 521 struct proc_dir_entry *apm_proc; 522 523 if (sys_ctrler != SYS_CTRLER_PMU) { 524 printk(KERN_INFO "apm_emu: Requires a machine with a PMU.\n"); 525 return -ENODEV; 526 } 527 528 apm_proc = create_proc_info_entry("apm", 0, NULL, apm_emu_get_info); 529 if (apm_proc) 530 apm_proc->owner = THIS_MODULE; 531 532 misc_register(&apm_device); 533 534 pmu_register_sleep_notifier(&apm_sleep_notifier); 535 536 printk(KERN_INFO "apm_emu: APM Emulation %s initialized.\n", driver_version); 537 538 return 0; 539 } 540 541 static void __exit apm_emu_exit(void) 542 { 543 pmu_unregister_sleep_notifier(&apm_sleep_notifier); 544 misc_deregister(&apm_device); 545 remove_proc_entry("apm", NULL); 546 547 printk(KERN_INFO "apm_emu: APM Emulation removed.\n"); 548 } 549 550 module_init(apm_emu_init); 551 module_exit(apm_emu_exit); 552 553 MODULE_AUTHOR("Benjamin Herrenschmidt"); 554 MODULE_DESCRIPTION("APM emulation layer for PowerMac"); 555 MODULE_LICENSE("GPL"); 556 557