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