apm.c revision 103751
1/* 2 * APM (Advanced Power Management) BIOS Device Driver 3 * 4 * Copyright (c) 1994 UKAI, Fumitoshi. 5 * Copyright (c) 1994-1995 by HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org> 6 * Copyright (c) 1996 Nate Williams <nate@FreeBSD.org> 7 * Copyright (c) 1997 Poul-Henning Kamp <phk@FreeBSD.org> 8 * 9 * This software may be used, modified, copied, and distributed, in 10 * both source and binary form provided that the above copyright and 11 * these terms are retained. Under no circumstances is the author 12 * responsible for the proper functioning of this software, nor does 13 * the author assume any responsibility for damages incurred with its 14 * use. 15 * 16 * Sep, 1994 Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD) 17 * 18 * $FreeBSD: head/sys/i386/bios/apm.c 103751 2002-09-21 18:30:12Z markm $ 19 */ 20 21#include <sys/param.h> 22#include <sys/systm.h> 23#include <sys/bus.h> 24#include <sys/conf.h> 25#include <sys/eventhandler.h> 26#include <sys/fcntl.h> 27#include <sys/kernel.h> 28#include <sys/poll.h> 29#include <sys/power.h> 30#include <sys/reboot.h> 31#include <sys/selinfo.h> 32#include <sys/signalvar.h> 33#include <sys/sysctl.h> 34#include <sys/syslog.h> 35#include <sys/time.h> 36#include <sys/uio.h> 37 38#include <machine/apm_bios.h> 39#include <machine/clock.h> 40#include <machine/pc/bios.h> 41#include <machine/segments.h> 42#include <machine/stdarg.h> 43#include <machine/vm86.h> 44 45#include <vm/vm.h> 46#include <vm/pmap.h> 47#include <vm/vm_param.h> 48 49#include <i386/apm/apm.h> 50 51/* Used by the apm_saver screen saver module */ 52int apm_display(int newstate); 53struct apm_softc apm_softc; 54 55static void apm_resume(void); 56static int apm_bioscall(void); 57static int apm_check_function_supported(u_int version, u_int func); 58 59static int apm_pm_func(u_long, void*, ...); 60 61static u_long apm_version; 62 63int apm_evindex; 64 65#define SCFLAG_ONORMAL 0x0000001 66#define SCFLAG_OCTL 0x0000002 67#define SCFLAG_OPEN (SCFLAG_ONORMAL|SCFLAG_OCTL) 68 69#define APMDEV(dev) (minor(dev)&0x0f) 70#define APMDEV_NORMAL 0 71#define APMDEV_CTL 8 72 73static struct apmhook *hook[NAPM_HOOK]; /* XXX */ 74 75#define is_enabled(foo) ((foo) ? "enabled" : "disabled") 76 77/* Map version number to integer (keeps ordering of version numbers) */ 78#define INTVERSION(major, minor) ((major)*100 + (minor)) 79 80static struct callout_handle apm_timeout_ch = 81 CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch); 82 83static timeout_t apm_timeout; 84static d_open_t apmopen; 85static d_close_t apmclose; 86static d_write_t apmwrite; 87static d_ioctl_t apmioctl; 88static d_poll_t apmpoll; 89 90#define CDEV_MAJOR 39 91static struct cdevsw apm_cdevsw = { 92 /* open */ apmopen, 93 /* close */ apmclose, 94 /* read */ noread, 95 /* write */ apmwrite, 96 /* ioctl */ apmioctl, 97 /* poll */ apmpoll, 98 /* mmap */ nommap, 99 /* strategy */ nostrategy, 100 /* name */ "apm", 101 /* maj */ CDEV_MAJOR, 102 /* dump */ nodump, 103 /* psize */ nopsize, 104 /* flags */ 0, 105}; 106 107static int apm_suspend_delay = 1; 108static int apm_standby_delay = 1; 109static int apm_debug = 0; 110 111#define APM_DPRINT(args...) do { \ 112 if (apm_debug) { \ 113 printf(args); \ 114 } \ 115} while (0) 116 117SYSCTL_INT(_machdep, OID_AUTO, apm_suspend_delay, CTLFLAG_RW, &apm_suspend_delay, 1, ""); 118SYSCTL_INT(_machdep, OID_AUTO, apm_standby_delay, CTLFLAG_RW, &apm_standby_delay, 1, ""); 119SYSCTL_INT(_debug, OID_AUTO, apm_debug, CTLFLAG_RW, &apm_debug, 0, ""); 120 121/* 122 * return 0 if the function successfull, 123 * return 1 if the function unsuccessfull, 124 * return -1 if the function unsupported. 125 */ 126static int 127apm_bioscall(void) 128{ 129 struct apm_softc *sc = &apm_softc; 130 int errno = 0; 131 u_int apm_func = sc->bios.r.eax & 0xff; 132 133 if (!apm_check_function_supported(sc->intversion, apm_func)) { 134 APM_DPRINT("apm_bioscall: function 0x%x is not supported in v%d.%d\n", 135 apm_func, sc->majorversion, sc->minorversion); 136 return (-1); 137 } 138 139 sc->bios_busy = 1; 140 if (sc->connectmode == APM_PROT32CONNECT) { 141 set_bios_selectors(&sc->bios.seg, 142 BIOSCODE_FLAG | BIOSDATA_FLAG); 143 errno = bios32(&sc->bios.r, 144 sc->bios.entry, GSEL(GBIOSCODE32_SEL, SEL_KPL)); 145 } else { 146 errno = bios16(&sc->bios, NULL); 147 } 148 sc->bios_busy = 0; 149 return (errno); 150} 151 152/* check whether APM function is supported (1) or not (0). */ 153static int 154apm_check_function_supported(u_int version, u_int func) 155{ 156 /* except driver version */ 157 if (func == APM_DRVVERSION) { 158 return (1); 159 } 160 161 switch (version) { 162 case INTVERSION(1, 0): 163 if (func > APM_GETPMEVENT) { 164 return (0); /* not supported */ 165 } 166 break; 167 case INTVERSION(1, 1): 168 if (func > APM_ENGAGEDISENGAGEPM && 169 func < APM_OEMFUNC) { 170 return (0); /* not supported */ 171 } 172 break; 173 case INTVERSION(1, 2): 174 break; 175 } 176 177 return (1); /* supported */ 178} 179 180/* enable/disable power management */ 181static int 182apm_enable_disable_pm(int enable) 183{ 184 struct apm_softc *sc = &apm_softc; 185 186 sc->bios.r.eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM; 187 188 if (sc->intversion >= INTVERSION(1, 1)) 189 sc->bios.r.ebx = PMDV_ALLDEV; 190 else 191 sc->bios.r.ebx = 0xffff; /* APM version 1.0 only */ 192 sc->bios.r.ecx = enable; 193 sc->bios.r.edx = 0; 194 return (apm_bioscall()); 195} 196 197/* register driver version (APM 1.1 or later) */ 198static int 199apm_driver_version(int version) 200{ 201 struct apm_softc *sc = &apm_softc; 202 203 sc->bios.r.eax = (APM_BIOS << 8) | APM_DRVVERSION; 204 sc->bios.r.ebx = 0x0; 205 sc->bios.r.ecx = version; 206 sc->bios.r.edx = 0; 207 208 if (apm_bioscall() == 0 && sc->bios.r.eax == version) 209 return (0); 210 211 /* Some old BIOSes don't return the connection version in %ax. */ 212 if (sc->bios.r.eax == ((APM_BIOS << 8) | APM_DRVVERSION)) 213 return (0); 214 215 return (1); 216} 217 218/* engage/disengage power management (APM 1.1 or later) */ 219static int 220apm_engage_disengage_pm(int engage) 221{ 222 struct apm_softc *sc = &apm_softc; 223 224 sc->bios.r.eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM; 225 sc->bios.r.ebx = PMDV_ALLDEV; 226 sc->bios.r.ecx = engage; 227 sc->bios.r.edx = 0; 228 return (apm_bioscall()); 229} 230 231/* get PM event */ 232static u_int 233apm_getevent(void) 234{ 235 struct apm_softc *sc = &apm_softc; 236 237 sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPMEVENT; 238 239 sc->bios.r.ebx = 0; 240 sc->bios.r.ecx = 0; 241 sc->bios.r.edx = 0; 242 if (apm_bioscall()) 243 return (PMEV_NOEVENT); 244 return (sc->bios.r.ebx & 0xffff); 245} 246 247/* suspend entire system */ 248static int 249apm_suspend_system(int state) 250{ 251 struct apm_softc *sc = &apm_softc; 252 253 sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE; 254 sc->bios.r.ebx = PMDV_ALLDEV; 255 sc->bios.r.ecx = state; 256 sc->bios.r.edx = 0; 257 258 if (apm_bioscall()) { 259 printf("Entire system suspend failure: errcode = %d\n", 260 0xff & (sc->bios.r.eax >> 8)); 261 return 1; 262 } 263 return 0; 264} 265 266/* Display control */ 267/* 268 * Experimental implementation: My laptop machine can't handle this function 269 * If your laptop can control the display via APM, please inform me. 270 * HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org> 271 */ 272int 273apm_display(int newstate) 274{ 275 struct apm_softc *sc = &apm_softc; 276 277 sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE; 278 sc->bios.r.ebx = PMDV_DISP0; 279 sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND; 280 sc->bios.r.edx = 0; 281 if (apm_bioscall() == 0) { 282 return 0; 283 } 284 285 /* If failed, then try to blank all display devices instead. */ 286 sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE; 287 sc->bios.r.ebx = PMDV_DISPALL; /* all display devices */ 288 sc->bios.r.ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND; 289 sc->bios.r.edx = 0; 290 if (apm_bioscall() == 0) { 291 return 0; 292 } 293 printf("Display off failure: errcode = %d\n", 294 0xff & (sc->bios.r.eax >> 8)); 295 return 1; 296} 297 298/* 299 * Turn off the entire system. 300 */ 301static void 302apm_power_off(void *junk, int howto) 303{ 304 struct apm_softc *sc = &apm_softc; 305 306 /* Not halting powering off, or not active */ 307 if (!(howto & RB_POWEROFF) || !apm_softc.active) 308 return; 309 sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE; 310 sc->bios.r.ebx = PMDV_ALLDEV; 311 sc->bios.r.ecx = PMST_OFF; 312 sc->bios.r.edx = 0; 313 (void) apm_bioscall(); 314} 315 316/* APM Battery low handler */ 317static void 318apm_battery_low(void) 319{ 320 printf("\007\007 * * * BATTERY IS LOW * * * \007\007"); 321} 322 323/* APM hook manager */ 324static struct apmhook * 325apm_add_hook(struct apmhook **list, struct apmhook *ah) 326{ 327 int s; 328 struct apmhook *p, *prev; 329 330 APM_DPRINT("Add hook \"%s\"\n", ah->ah_name); 331 332 s = splhigh(); 333 if (ah == NULL) 334 panic("illegal apm_hook!"); 335 prev = NULL; 336 for (p = *list; p != NULL; prev = p, p = p->ah_next) 337 if (p->ah_order > ah->ah_order) 338 break; 339 340 if (prev == NULL) { 341 ah->ah_next = *list; 342 *list = ah; 343 } else { 344 ah->ah_next = prev->ah_next; 345 prev->ah_next = ah; 346 } 347 splx(s); 348 return ah; 349} 350 351static void 352apm_del_hook(struct apmhook **list, struct apmhook *ah) 353{ 354 int s; 355 struct apmhook *p, *prev; 356 357 s = splhigh(); 358 prev = NULL; 359 for (p = *list; p != NULL; prev = p, p = p->ah_next) 360 if (p == ah) 361 goto deleteit; 362 panic("Tried to delete unregistered apm_hook."); 363 goto nosuchnode; 364deleteit: 365 if (prev != NULL) 366 prev->ah_next = p->ah_next; 367 else 368 *list = p->ah_next; 369nosuchnode: 370 splx(s); 371} 372 373 374/* APM driver calls some functions automatically */ 375static void 376apm_execute_hook(struct apmhook *list) 377{ 378 struct apmhook *p; 379 380 for (p = list; p != NULL; p = p->ah_next) { 381 APM_DPRINT("Execute APM hook \"%s.\"\n", p->ah_name); 382 if ((*(p->ah_fun))(p->ah_arg)) 383 printf("Warning: APM hook \"%s\" failed", p->ah_name); 384 } 385} 386 387 388/* establish an apm hook */ 389struct apmhook * 390apm_hook_establish(int apmh, struct apmhook *ah) 391{ 392 if (apmh < 0 || apmh >= NAPM_HOOK) 393 return NULL; 394 395 return apm_add_hook(&hook[apmh], ah); 396} 397 398/* disestablish an apm hook */ 399void 400apm_hook_disestablish(int apmh, struct apmhook *ah) 401{ 402 if (apmh < 0 || apmh >= NAPM_HOOK) 403 return; 404 405 apm_del_hook(&hook[apmh], ah); 406} 407 408static int apm_record_event(struct apm_softc *, u_int); 409static void apm_processevent(void); 410 411static u_int apm_op_inprog = 0; 412 413static void 414apm_do_suspend(void) 415{ 416 struct apm_softc *sc = &apm_softc; 417 int error; 418 419 if (!sc) 420 return; 421 422 apm_op_inprog = 0; 423 sc->suspends = sc->suspend_countdown = 0; 424 425 if (sc->initialized) { 426 error = DEVICE_SUSPEND(root_bus); 427 if (error) { 428 DEVICE_RESUME(root_bus); 429 } else { 430 apm_execute_hook(hook[APM_HOOK_SUSPEND]); 431 if (apm_suspend_system(PMST_SUSPEND) == 0) { 432 sc->suspending = 1; 433 apm_processevent(); 434 } else { 435 /* Failure, 'resume' the system again */ 436 apm_execute_hook(hook[APM_HOOK_RESUME]); 437 DEVICE_RESUME(root_bus); 438 } 439 } 440 } 441} 442 443static void 444apm_do_standby(void) 445{ 446 struct apm_softc *sc = &apm_softc; 447 448 if (!sc) 449 return; 450 451 apm_op_inprog = 0; 452 sc->standbys = sc->standby_countdown = 0; 453 454 if (sc->initialized) { 455 /* 456 * As far as standby, we don't need to execute 457 * all of suspend hooks. 458 */ 459 if (apm_suspend_system(PMST_STANDBY) == 0) 460 apm_processevent(); 461 } 462} 463 464static void 465apm_lastreq_notify(void) 466{ 467 struct apm_softc *sc = &apm_softc; 468 469 sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE; 470 sc->bios.r.ebx = PMDV_ALLDEV; 471 sc->bios.r.ecx = PMST_LASTREQNOTIFY; 472 sc->bios.r.edx = 0; 473 apm_bioscall(); 474} 475 476static int 477apm_lastreq_rejected(void) 478{ 479 struct apm_softc *sc = &apm_softc; 480 481 if (apm_op_inprog == 0) { 482 return 1; /* no operation in progress */ 483 } 484 485 sc->bios.r.eax = (APM_BIOS << 8) | APM_SETPWSTATE; 486 sc->bios.r.ebx = PMDV_ALLDEV; 487 sc->bios.r.ecx = PMST_LASTREQREJECT; 488 sc->bios.r.edx = 0; 489 490 if (apm_bioscall()) { 491 APM_DPRINT("apm_lastreq_rejected: failed\n"); 492 return 1; 493 } 494 apm_op_inprog = 0; 495 return 0; 496} 497 498/* 499 * Public interface to the suspend/resume: 500 * 501 * Execute suspend and resume hook before and after sleep, respectively. 502 * 503 */ 504 505void 506apm_suspend(int state) 507{ 508 struct apm_softc *sc = &apm_softc; 509 510 if (!sc->initialized) 511 return; 512 513 switch (state) { 514 case PMST_SUSPEND: 515 if (sc->suspends) 516 return; 517 sc->suspends++; 518 sc->suspend_countdown = apm_suspend_delay; 519 break; 520 case PMST_STANDBY: 521 if (sc->standbys) 522 return; 523 sc->standbys++; 524 sc->standby_countdown = apm_standby_delay; 525 break; 526 default: 527 printf("apm_suspend: Unknown Suspend state 0x%x\n", state); 528 return; 529 } 530 531 apm_op_inprog++; 532 apm_lastreq_notify(); 533} 534 535void 536apm_resume(void) 537{ 538 struct apm_softc *sc = &apm_softc; 539 540 if (!sc) 541 return; 542 543 if (sc->suspending == 0) 544 return; 545 546 sc->suspending = 0; 547 if (sc->initialized) { 548 apm_execute_hook(hook[APM_HOOK_RESUME]); 549 DEVICE_RESUME(root_bus); 550 } 551} 552 553 554/* get power status per battery */ 555static int 556apm_get_pwstatus(apm_pwstatus_t app) 557{ 558 struct apm_softc *sc = &apm_softc; 559 560 if (app->ap_device != PMDV_ALLDEV && 561 (app->ap_device < PMDV_BATT0 || app->ap_device > PMDV_BATT_ALL)) 562 return 1; 563 564 sc->bios.r.eax = (APM_BIOS << 8) | APM_GETPWSTATUS; 565 sc->bios.r.ebx = app->ap_device; 566 sc->bios.r.ecx = 0; 567 sc->bios.r.edx = 0xffff; /* default to unknown battery time */ 568 569 if (apm_bioscall()) 570 return 1; 571 572 app->ap_acline = (sc->bios.r.ebx >> 8) & 0xff; 573 app->ap_batt_stat = sc->bios.r.ebx & 0xff; 574 app->ap_batt_flag = (sc->bios.r.ecx >> 8) & 0xff; 575 app->ap_batt_life = sc->bios.r.ecx & 0xff; 576 sc->bios.r.edx &= 0xffff; 577 if (sc->bios.r.edx == 0xffff) /* Time is unknown */ 578 app->ap_batt_time = -1; 579 else if (sc->bios.r.edx & 0x8000) /* Time is in minutes */ 580 app->ap_batt_time = (sc->bios.r.edx & 0x7fff) * 60; 581 else /* Time is in seconds */ 582 app->ap_batt_time = sc->bios.r.edx; 583 584 return 0; 585} 586 587 588/* get APM information */ 589static int 590apm_get_info(apm_info_t aip) 591{ 592 struct apm_softc *sc = &apm_softc; 593 struct apm_pwstatus aps; 594 595 bzero(&aps, sizeof(aps)); 596 aps.ap_device = PMDV_ALLDEV; 597 if (apm_get_pwstatus(&aps)) 598 return 1; 599 600 aip->ai_infoversion = 1; 601 aip->ai_acline = aps.ap_acline; 602 aip->ai_batt_stat = aps.ap_batt_stat; 603 aip->ai_batt_life = aps.ap_batt_life; 604 aip->ai_batt_time = aps.ap_batt_time; 605 aip->ai_major = (u_int)sc->majorversion; 606 aip->ai_minor = (u_int)sc->minorversion; 607 aip->ai_status = (u_int)sc->active; 608 609 sc->bios.r.eax = (APM_BIOS << 8) | APM_GETCAPABILITIES; 610 sc->bios.r.ebx = 0; 611 sc->bios.r.ecx = 0; 612 sc->bios.r.edx = 0; 613 if (apm_bioscall()) { 614 aip->ai_batteries = -1; /* Unknown */ 615 aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */ 616 } else { 617 aip->ai_batteries = sc->bios.r.ebx & 0xff; 618 aip->ai_capabilities = sc->bios.r.ecx & 0xf; 619 } 620 621 bzero(aip->ai_spare, sizeof aip->ai_spare); 622 623 return 0; 624} 625 626 627/* inform APM BIOS that CPU is idle */ 628void 629apm_cpu_idle(void) 630{ 631 struct apm_softc *sc = &apm_softc; 632 633 if (sc->active) { 634 635 sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUIDLE; 636 sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0; 637 (void) apm_bioscall(); 638 } 639 /* 640 * Some APM implementation halts CPU in BIOS, whenever 641 * "CPU-idle" function are invoked, but swtch() of 642 * FreeBSD halts CPU, therefore, CPU is halted twice 643 * in the sched loop. It makes the interrupt latency 644 * terribly long and be able to cause a serious problem 645 * in interrupt processing. We prevent it by removing 646 * "hlt" operation from swtch() and managed it under 647 * APM driver. 648 */ 649 if (!sc->active || sc->always_halt_cpu) 650 __asm("hlt"); /* wait for interrupt */ 651} 652 653/* inform APM BIOS that CPU is busy */ 654void 655apm_cpu_busy(void) 656{ 657 struct apm_softc *sc = &apm_softc; 658 659 /* 660 * The APM specification says this is only necessary if your BIOS 661 * slows down the processor in the idle task, otherwise it's not 662 * necessary. 663 */ 664 if (sc->slow_idle_cpu && sc->active) { 665 666 sc->bios.r.eax = (APM_BIOS <<8) | APM_CPUBUSY; 667 sc->bios.r.edx = sc->bios.r.ecx = sc->bios.r.ebx = 0; 668 apm_bioscall(); 669 } 670} 671 672 673/* 674 * APM timeout routine: 675 * 676 * This routine is automatically called by timer once per second. 677 */ 678 679static void 680apm_timeout(void *dummy) 681{ 682 struct apm_softc *sc = &apm_softc; 683 684 if (apm_op_inprog) 685 apm_lastreq_notify(); 686 687 if (sc->standbys && sc->standby_countdown-- <= 0) 688 apm_do_standby(); 689 690 if (sc->suspends && sc->suspend_countdown-- <= 0) 691 apm_do_suspend(); 692 693 if (!sc->bios_busy) 694 apm_processevent(); 695 696 if (sc->active == 1) 697 /* Run slightly more oftan than 1 Hz */ 698 apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1); 699} 700 701/* enable APM BIOS */ 702static void 703apm_event_enable(void) 704{ 705 struct apm_softc *sc = &apm_softc; 706 707 APM_DPRINT("called apm_event_enable()\n"); 708 if (sc->initialized) { 709 sc->active = 1; 710 apm_timeout(sc); 711 } 712} 713 714/* disable APM BIOS */ 715static void 716apm_event_disable(void) 717{ 718 struct apm_softc *sc = &apm_softc; 719 720 APM_DPRINT("called apm_event_disable()\n"); 721 if (sc->initialized) { 722 untimeout(apm_timeout, NULL, apm_timeout_ch); 723 sc->active = 0; 724 } 725} 726 727/* halt CPU in scheduling loop */ 728static void 729apm_halt_cpu(void) 730{ 731 struct apm_softc *sc = &apm_softc; 732 733 if (sc->initialized) 734 sc->always_halt_cpu = 1; 735} 736 737/* don't halt CPU in scheduling loop */ 738static void 739apm_not_halt_cpu(void) 740{ 741 struct apm_softc *sc = &apm_softc; 742 743 if (sc->initialized) 744 sc->always_halt_cpu = 0; 745} 746 747/* device driver definitions */ 748 749/* 750 * Module event 751 */ 752 753static int 754apm_modevent(struct module *mod, int event, void *junk) 755{ 756 757 switch (event) { 758 case MOD_LOAD: 759 if (!cold) 760 return (EPERM); 761 break; 762 case MOD_UNLOAD: 763 if (!cold && power_pm_get_type() == POWER_PM_TYPE_APM) 764 return (EBUSY); 765 break; 766 default: 767 break; 768 } 769 770 return (0); 771} 772 773/* 774 * Create "connection point" 775 */ 776static void 777apm_identify(driver_t *driver, device_t parent) 778{ 779 device_t child; 780 781 if (!cold) { 782 printf("Don't load this driver from userland!!\n"); 783 return; 784 } 785 786 child = BUS_ADD_CHILD(parent, 0, "apm", 0); 787 if (child == NULL) 788 panic("apm_identify"); 789} 790 791/* 792 * probe for APM BIOS 793 */ 794static int 795apm_probe(device_t dev) 796{ 797#define APM_KERNBASE KERNBASE 798 struct vm86frame vmf; 799 struct apm_softc *sc = &apm_softc; 800 int disabled, flags; 801 802 device_set_desc(dev, "APM BIOS"); 803 804 if (resource_int_value("apm", 0, "disabled", &disabled) != 0) 805 disabled = 0; 806 if (disabled) 807 return ENXIO; 808 809 if (device_get_unit(dev) > 0) { 810 printf("apm: Only one APM driver supported.\n"); 811 return ENXIO; 812 } 813 814 if (power_pm_get_type() != POWER_PM_TYPE_NONE && 815 power_pm_get_type() != POWER_PM_TYPE_APM) { 816 printf("apm: Other PM system enabled.\n"); 817 return ENXIO; 818 } 819 820 if (resource_int_value("apm", 0, "flags", &flags) != 0) 821 flags = 0; 822 823 bzero(&vmf, sizeof(struct vm86frame)); /* safety */ 824 bzero(&apm_softc, sizeof(apm_softc)); 825 vmf.vmf_ah = APM_BIOS; 826 vmf.vmf_al = APM_INSTCHECK; 827 vmf.vmf_bx = 0; 828 if (vm86_intcall(APM_INT, &vmf)) 829 return ENXIO; /* APM not found */ 830 if (vmf.vmf_bx != 0x504d) { 831 printf("apm: incorrect signature (0x%x)\n", vmf.vmf_bx); 832 return ENXIO; 833 } 834 if ((vmf.vmf_cx & (APM_32BIT_SUPPORT | APM_16BIT_SUPPORT)) == 0) { 835 printf("apm: protected mode connections are not supported\n"); 836 return ENXIO; 837 } 838 839 apm_version = vmf.vmf_ax; 840 sc->slow_idle_cpu = ((vmf.vmf_cx & APM_CPUIDLE_SLOW) != 0); 841 sc->disabled = ((vmf.vmf_cx & APM_DISABLED) != 0); 842 sc->disengaged = ((vmf.vmf_cx & APM_DISENGAGED) != 0); 843 844 vmf.vmf_ah = APM_BIOS; 845 vmf.vmf_al = APM_DISCONNECT; 846 vmf.vmf_bx = 0; 847 vm86_intcall(APM_INT, &vmf); /* disconnect, just in case */ 848 849 if ((vmf.vmf_cx & APM_32BIT_SUPPORT) != 0) { 850 vmf.vmf_ah = APM_BIOS; 851 vmf.vmf_al = APM_PROT32CONNECT; 852 vmf.vmf_bx = 0; 853 if (vm86_intcall(APM_INT, &vmf)) { 854 printf("apm: 32-bit connection error.\n"); 855 return (ENXIO); 856 } 857 sc->bios.seg.code32.base = (vmf.vmf_ax << 4) + APM_KERNBASE; 858 sc->bios.seg.code32.limit = 0xffff; 859 sc->bios.seg.code16.base = (vmf.vmf_cx << 4) + APM_KERNBASE; 860 sc->bios.seg.code16.limit = 0xffff; 861 sc->bios.seg.data.base = (vmf.vmf_dx << 4) + APM_KERNBASE; 862 sc->bios.seg.data.limit = 0xffff; 863 sc->bios.entry = vmf.vmf_ebx; 864 sc->connectmode = APM_PROT32CONNECT; 865 } else { 866 /* use 16-bit connection */ 867 vmf.vmf_ah = APM_BIOS; 868 vmf.vmf_al = APM_PROT16CONNECT; 869 vmf.vmf_bx = 0; 870 if (vm86_intcall(APM_INT, &vmf)) { 871 printf("apm: 16-bit connection error.\n"); 872 return (ENXIO); 873 } 874 sc->bios.seg.code16.base = (vmf.vmf_ax << 4) + APM_KERNBASE; 875 sc->bios.seg.code16.limit = 0xffff; 876 sc->bios.seg.data.base = (vmf.vmf_cx << 4) + APM_KERNBASE; 877 sc->bios.seg.data.limit = 0xffff; 878 sc->bios.entry = vmf.vmf_bx; 879 sc->connectmode = APM_PROT16CONNECT; 880 } 881 return(0); 882} 883 884 885/* 886 * return 0 if the user will notice and handle the event, 887 * return 1 if the kernel driver should do so. 888 */ 889static int 890apm_record_event(struct apm_softc *sc, u_int event_type) 891{ 892 struct apm_event_info *evp; 893 894 if ((sc->sc_flags & SCFLAG_OPEN) == 0) 895 return 1; /* no user waiting */ 896 if (sc->event_count == APM_NEVENTS) 897 return 1; /* overflow */ 898 if (sc->event_filter[event_type] == 0) 899 return 1; /* not registered */ 900 evp = &sc->event_list[sc->event_ptr]; 901 sc->event_count++; 902 sc->event_ptr++; 903 sc->event_ptr %= APM_NEVENTS; 904 evp->type = event_type; 905 evp->index = ++apm_evindex; 906 selwakeup(&sc->sc_rsel); 907 return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */ 908} 909 910/* Power profile */ 911static void 912apm_power_profile(struct apm_softc *sc) 913{ 914 int state; 915 struct apm_info info; 916 static int apm_acline = 0; 917 918 if (apm_get_info(&info)) 919 return; 920 921 if (apm_acline != info.ai_acline) { 922 apm_acline = info.ai_acline; 923 state = apm_acline ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY; 924 power_profile_set_state(state); 925 } 926} 927 928/* Process APM event */ 929static void 930apm_processevent(void) 931{ 932 int apm_event; 933 struct apm_softc *sc = &apm_softc; 934 935#define OPMEV_DEBUGMESSAGE(symbol) case symbol: \ 936 APM_DPRINT("Received APM Event: " #symbol "\n"); 937 938 do { 939 apm_event = apm_getevent(); 940 switch (apm_event) { 941 OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ); 942 if (apm_op_inprog == 0) { 943 apm_op_inprog++; 944 if (apm_record_event(sc, apm_event)) { 945 apm_suspend(PMST_STANDBY); 946 } 947 } 948 break; 949 OPMEV_DEBUGMESSAGE(PMEV_USERSTANDBYREQ); 950 if (apm_op_inprog == 0) { 951 apm_op_inprog++; 952 if (apm_record_event(sc, apm_event)) { 953 apm_suspend(PMST_STANDBY); 954 } 955 } 956 break; 957 OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ); 958 apm_lastreq_notify(); 959 if (apm_op_inprog == 0) { 960 apm_op_inprog++; 961 if (apm_record_event(sc, apm_event)) { 962 apm_do_suspend(); 963 } 964 } 965 return; /* XXX skip the rest */ 966 OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ); 967 apm_lastreq_notify(); 968 if (apm_op_inprog == 0) { 969 apm_op_inprog++; 970 if (apm_record_event(sc, apm_event)) { 971 apm_do_suspend(); 972 } 973 } 974 return; /* XXX skip the rest */ 975 OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND); 976 apm_do_suspend(); 977 break; 978 OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME); 979 apm_record_event(sc, apm_event); 980 apm_resume(); 981 break; 982 OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME); 983 apm_record_event(sc, apm_event); 984 apm_resume(); 985 break; 986 OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME); 987 apm_record_event(sc, apm_event); 988 break; 989 OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW); 990 if (apm_record_event(sc, apm_event)) { 991 apm_battery_low(); 992 apm_suspend(PMST_SUSPEND); 993 } 994 break; 995 OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE); 996 apm_record_event(sc, apm_event); 997 apm_power_profile(sc); 998 break; 999 OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME); 1000 apm_record_event(sc, apm_event); 1001 inittodr(0); /* adjust time to RTC */ 1002 break; 1003 OPMEV_DEBUGMESSAGE(PMEV_CAPABILITIESCHANGE); 1004 apm_record_event(sc, apm_event); 1005 apm_power_profile(sc); 1006 break; 1007 case PMEV_NOEVENT: 1008 break; 1009 default: 1010 printf("Unknown Original APM Event 0x%x\n", apm_event); 1011 break; 1012 } 1013 } while (apm_event != PMEV_NOEVENT); 1014} 1015 1016/* 1017 * Attach APM: 1018 * 1019 * Initialize APM driver 1020 */ 1021 1022static int 1023apm_attach(device_t dev) 1024{ 1025 struct apm_softc *sc = &apm_softc; 1026 int flags; 1027 int drv_version; 1028 1029 if (resource_int_value("apm", 0, "flags", &flags) != 0) 1030 flags = 0; 1031 1032 if (flags & 0x20) 1033 statclock_disable = 1; 1034 1035 sc->initialized = 0; 1036 1037 /* Must be externally enabled */ 1038 sc->active = 0; 1039 1040 /* Always call HLT in idle loop */ 1041 sc->always_halt_cpu = 1; 1042 1043 getenv_int("debug.apm_debug", &apm_debug); 1044 1045 /* print bootstrap messages */ 1046 APM_DPRINT("apm: APM BIOS version %04lx\n", apm_version); 1047 APM_DPRINT("apm: Code16 0x%08x, Data 0x%08x\n", 1048 sc->bios.seg.code16.base, sc->bios.seg.data.base); 1049 APM_DPRINT("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n", 1050 sc->bios.entry, is_enabled(sc->slow_idle_cpu), 1051 is_enabled(!sc->disabled)); 1052 APM_DPRINT("apm: CS_limit=0x%x, DS_limit=0x%x\n", 1053 sc->bios.seg.code16.limit, sc->bios.seg.data.limit); 1054 1055 /* 1056 * In one test, apm bios version was 1.02; an attempt to register 1057 * a 1.04 driver resulted in a 1.00 connection! Registering a 1058 * 1.02 driver resulted in a 1.02 connection. 1059 */ 1060 drv_version = apm_version > 0x102 ? 0x102 : apm_version; 1061 for (; drv_version > 0x100; drv_version--) 1062 if (apm_driver_version(drv_version) == 0) 1063 break; 1064 sc->minorversion = ((drv_version & 0x00f0) >> 4) * 10 + 1065 ((drv_version & 0x000f) >> 0); 1066 sc->majorversion = ((drv_version & 0xf000) >> 12) * 10 + 1067 ((apm_version & 0x0f00) >> 8); 1068 1069 sc->intversion = INTVERSION(sc->majorversion, sc->minorversion); 1070 1071 if (sc->intversion >= INTVERSION(1, 1)) 1072 APM_DPRINT("apm: Engaged control %s\n", is_enabled(!sc->disengaged)); 1073 device_printf(dev, "found APM BIOS v%ld.%ld, connected at v%d.%d\n", 1074 ((apm_version & 0xf000) >> 12) * 10 + ((apm_version & 0x0f00) >> 8), 1075 ((apm_version & 0x00f0) >> 4) * 10 + ((apm_version & 0x000f) >> 0), 1076 sc->majorversion, sc->minorversion); 1077 1078 1079 APM_DPRINT("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu)); 1080 /* enable power management */ 1081 if (sc->disabled) { 1082 if (apm_enable_disable_pm(1)) { 1083 APM_DPRINT("apm: *Warning* enable function failed! [%x]\n", 1084 (sc->bios.r.eax >> 8) & 0xff); 1085 } 1086 } 1087 1088 /* engage power managment (APM 1.1 or later) */ 1089 if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) { 1090 if (apm_engage_disengage_pm(1)) { 1091 APM_DPRINT("apm: *Warning* engage function failed err=[%x]", 1092 (sc->bios.r.eax >> 8) & 0xff); 1093 APM_DPRINT(" (Docked or using external power?).\n"); 1094 } 1095 } 1096 1097 /* Power the system off using APM */ 1098 EVENTHANDLER_REGISTER(shutdown_final, apm_power_off, NULL, 1099 SHUTDOWN_PRI_LAST); 1100 1101 /* Register APM again to pass the correct argument of pm_func. */ 1102 power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, sc); 1103 1104 sc->initialized = 1; 1105 sc->suspending = 0; 1106 1107 make_dev(&apm_cdevsw, 0, 0, 5, 0664, "apm"); 1108 make_dev(&apm_cdevsw, 8, 0, 5, 0660, "apmctl"); 1109 return 0; 1110} 1111 1112static int 1113apmopen(dev_t dev, int flag, int fmt, struct thread *td) 1114{ 1115 struct apm_softc *sc = &apm_softc; 1116 int ctl = APMDEV(dev); 1117 1118 if (!sc->initialized) 1119 return (ENXIO); 1120 1121 switch (ctl) { 1122 case APMDEV_CTL: 1123 if (!(flag & FWRITE)) 1124 return EINVAL; 1125 if (sc->sc_flags & SCFLAG_OCTL) 1126 return EBUSY; 1127 sc->sc_flags |= SCFLAG_OCTL; 1128 bzero(sc->event_filter, sizeof sc->event_filter); 1129 break; 1130 case APMDEV_NORMAL: 1131 sc->sc_flags |= SCFLAG_ONORMAL; 1132 break; 1133 default: 1134 return ENXIO; 1135 break; 1136 } 1137 return 0; 1138} 1139 1140static int 1141apmclose(dev_t dev, int flag, int fmt, struct thread *td) 1142{ 1143 struct apm_softc *sc = &apm_softc; 1144 int ctl = APMDEV(dev); 1145 1146 switch (ctl) { 1147 case APMDEV_CTL: 1148 apm_lastreq_rejected(); 1149 sc->sc_flags &= ~SCFLAG_OCTL; 1150 bzero(sc->event_filter, sizeof sc->event_filter); 1151 break; 1152 case APMDEV_NORMAL: 1153 sc->sc_flags &= ~SCFLAG_ONORMAL; 1154 break; 1155 } 1156 if ((sc->sc_flags & SCFLAG_OPEN) == 0) { 1157 sc->event_count = 0; 1158 sc->event_ptr = 0; 1159 } 1160 return 0; 1161} 1162 1163static int 1164apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td) 1165{ 1166 struct apm_softc *sc = &apm_softc; 1167 struct apm_bios_arg *args; 1168 int error = 0; 1169 int ret; 1170 int newstate; 1171 1172 if (!sc->initialized) 1173 return (ENXIO); 1174 APM_DPRINT("APM ioctl: cmd = 0x%lx\n", cmd); 1175 switch (cmd) { 1176 case APMIO_SUSPEND: 1177 if (!(flag & FWRITE)) 1178 return (EPERM); 1179 if (sc->active) 1180 apm_suspend(PMST_SUSPEND); 1181 else 1182 error = EINVAL; 1183 break; 1184 1185 case APMIO_STANDBY: 1186 if (!(flag & FWRITE)) 1187 return (EPERM); 1188 if (sc->active) 1189 apm_suspend(PMST_STANDBY); 1190 else 1191 error = EINVAL; 1192 break; 1193 1194 case APMIO_GETINFO_OLD: 1195 { 1196 struct apm_info info; 1197 apm_info_old_t aiop; 1198 1199 if (apm_get_info(&info)) 1200 error = ENXIO; 1201 aiop = (apm_info_old_t)addr; 1202 aiop->ai_major = info.ai_major; 1203 aiop->ai_minor = info.ai_minor; 1204 aiop->ai_acline = info.ai_acline; 1205 aiop->ai_batt_stat = info.ai_batt_stat; 1206 aiop->ai_batt_life = info.ai_batt_life; 1207 aiop->ai_status = info.ai_status; 1208 } 1209 break; 1210 case APMIO_GETINFO: 1211 if (apm_get_info((apm_info_t)addr)) 1212 error = ENXIO; 1213 break; 1214 case APMIO_GETPWSTATUS: 1215 if (apm_get_pwstatus((apm_pwstatus_t)addr)) 1216 error = ENXIO; 1217 break; 1218 case APMIO_ENABLE: 1219 if (!(flag & FWRITE)) 1220 return (EPERM); 1221 apm_event_enable(); 1222 break; 1223 case APMIO_DISABLE: 1224 if (!(flag & FWRITE)) 1225 return (EPERM); 1226 apm_event_disable(); 1227 break; 1228 case APMIO_HALTCPU: 1229 if (!(flag & FWRITE)) 1230 return (EPERM); 1231 apm_halt_cpu(); 1232 break; 1233 case APMIO_NOTHALTCPU: 1234 if (!(flag & FWRITE)) 1235 return (EPERM); 1236 apm_not_halt_cpu(); 1237 break; 1238 case APMIO_DISPLAY: 1239 if (!(flag & FWRITE)) 1240 return (EPERM); 1241 newstate = *(int *)addr; 1242 if (apm_display(newstate)) 1243 error = ENXIO; 1244 break; 1245 case APMIO_BIOS: 1246 if (!(flag & FWRITE)) 1247 return (EPERM); 1248 /* XXX compatibility with the old interface */ 1249 args = (struct apm_bios_arg *)addr; 1250 sc->bios.r.eax = args->eax; 1251 sc->bios.r.ebx = args->ebx; 1252 sc->bios.r.ecx = args->ecx; 1253 sc->bios.r.edx = args->edx; 1254 sc->bios.r.esi = args->esi; 1255 sc->bios.r.edi = args->edi; 1256 if ((ret = apm_bioscall())) { 1257 /* 1258 * Return code 1 means bios call was unsuccessful. 1259 * Error code is stored in %ah. 1260 * Return code -1 means bios call was unsupported 1261 * in the APM BIOS version. 1262 */ 1263 if (ret == -1) { 1264 error = EINVAL; 1265 } 1266 } else { 1267 /* 1268 * Return code 0 means bios call was successful. 1269 * We need only %al and can discard %ah. 1270 */ 1271 sc->bios.r.eax &= 0xff; 1272 } 1273 args->eax = sc->bios.r.eax; 1274 args->ebx = sc->bios.r.ebx; 1275 args->ecx = sc->bios.r.ecx; 1276 args->edx = sc->bios.r.edx; 1277 args->esi = sc->bios.r.esi; 1278 args->edi = sc->bios.r.edi; 1279 break; 1280 default: 1281 error = EINVAL; 1282 break; 1283 } 1284 1285 /* for /dev/apmctl */ 1286 if (APMDEV(dev) == APMDEV_CTL) { 1287 struct apm_event_info *evp; 1288 int i; 1289 1290 error = 0; 1291 switch (cmd) { 1292 case APMIO_NEXTEVENT: 1293 if (!sc->event_count) { 1294 error = EAGAIN; 1295 } else { 1296 evp = (struct apm_event_info *)addr; 1297 i = sc->event_ptr + APM_NEVENTS - sc->event_count; 1298 i %= APM_NEVENTS; 1299 *evp = sc->event_list[i]; 1300 sc->event_count--; 1301 } 1302 break; 1303 case APMIO_REJECTLASTREQ: 1304 if (apm_lastreq_rejected()) { 1305 error = EINVAL; 1306 } 1307 break; 1308 default: 1309 error = EINVAL; 1310 break; 1311 } 1312 } 1313 1314 return error; 1315} 1316 1317static int 1318apmwrite(dev_t dev, struct uio *uio, int ioflag) 1319{ 1320 struct apm_softc *sc = &apm_softc; 1321 u_int event_type; 1322 int error; 1323 u_char enabled; 1324 1325 if (APMDEV(dev) != APMDEV_CTL) 1326 return(ENODEV); 1327 if (uio->uio_resid != sizeof(u_int)) 1328 return(E2BIG); 1329 1330 if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio))) 1331 return(error); 1332 1333 if (event_type < 0 || event_type >= APM_NPMEV) 1334 return(EINVAL); 1335 1336 if (sc->event_filter[event_type] == 0) { 1337 enabled = 1; 1338 } else { 1339 enabled = 0; 1340 } 1341 sc->event_filter[event_type] = enabled; 1342 APM_DPRINT("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled)); 1343 1344 return uio->uio_resid; 1345} 1346 1347static int 1348apmpoll(dev_t dev, int events, struct thread *td) 1349{ 1350 struct apm_softc *sc = &apm_softc; 1351 int revents = 0; 1352 1353 if (events & (POLLIN | POLLRDNORM)) { 1354 if (sc->event_count) { 1355 revents |= events & (POLLIN | POLLRDNORM); 1356 } else { 1357 selrecord(td, &sc->sc_rsel); 1358 } 1359 } 1360 1361 return (revents); 1362} 1363 1364static device_method_t apm_methods[] = { 1365 /* Device interface */ 1366 DEVMETHOD(device_identify, apm_identify), 1367 DEVMETHOD(device_probe, apm_probe), 1368 DEVMETHOD(device_attach, apm_attach), 1369 1370 { 0, 0 } 1371}; 1372 1373static driver_t apm_driver = { 1374 "apm", 1375 apm_methods, 1376 1, /* no softc (XXX) */ 1377}; 1378 1379static devclass_t apm_devclass; 1380 1381DRIVER_MODULE(apm, nexus, apm_driver, apm_devclass, apm_modevent, 0); 1382MODULE_VERSION(apm, 1); 1383 1384static int 1385apm_pm_func(u_long cmd, void *arg, ...) 1386{ 1387 int state, apm_state; 1388 int error; 1389 va_list ap; 1390 1391 error = 0; 1392 switch (cmd) { 1393 case POWER_CMD_SUSPEND: 1394 va_start(ap, arg); 1395 state = va_arg(ap, int); 1396 va_end(ap); 1397 1398 switch (state) { 1399 case POWER_SLEEP_STATE_STANDBY: 1400 apm_state = PMST_STANDBY; 1401 break; 1402 case POWER_SLEEP_STATE_SUSPEND: 1403 case POWER_SLEEP_STATE_HIBERNATE: 1404 apm_state = PMST_SUSPEND; 1405 break; 1406 default: 1407 error = EINVAL; 1408 goto out; 1409 } 1410 1411 apm_suspend(apm_state); 1412 break; 1413 1414 default: 1415 error = EINVAL; 1416 goto out; 1417 } 1418 1419out: 1420 return (error); 1421} 1422 1423static void 1424apm_pm_register(void *arg) 1425{ 1426 int disabled = 0; 1427 1428 resource_int_value("apm", 0, "disabled", &disabled); 1429 if (disabled == 0) 1430 power_pm_register(POWER_PM_TYPE_APM, apm_pm_func, NULL); 1431} 1432 1433SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, apm_pm_register, 0); 1434