apm.c revision 45905
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 * $Id: apm.c,v 1.79 1999/04/18 15:10:58 dfr Exp $ 19 */ 20 21#include "opt_devfs.h" 22#include "opt_vm86.h" 23 24#include <sys/param.h> 25#include <sys/conf.h> 26#include <sys/kernel.h> 27#ifdef DEVFS 28#include <sys/devfsext.h> 29#endif /*DEVFS*/ 30#include <sys/systm.h> 31#include <sys/time.h> 32#include <sys/reboot.h> 33#include <sys/bus.h> 34#include <machine/apm_bios.h> 35#include <machine/segments.h> 36#include <machine/clock.h> 37#include <vm/vm.h> 38#include <vm/vm_param.h> 39#include <vm/pmap.h> 40#include <sys/syslog.h> 41#include <i386/apm/apm_setup.h> 42 43#ifdef VM86 44#include <machine/psl.h> 45#include <machine/vm86.h> 46#endif 47 48static int apm_display __P((int newstate)); 49static int apm_int __P((u_long *eax, u_long *ebx, u_long *ecx, u_long *edx)); 50static void apm_resume __P((void)); 51 52/* static data */ 53struct apm_softc { 54 int initialized, active; 55 int always_halt_cpu, slow_idle_cpu; 56 int disabled, disengaged; 57 u_int minorversion, majorversion; 58 u_int cs32_base, cs16_base, ds_base; 59 u_int cs16_limit, cs32_limit, ds_limit; 60 u_int cs_entry; 61 u_int intversion; 62 struct apmhook sc_suspend; 63 struct apmhook sc_resume; 64#ifdef DEVFS 65 void *sc_devfs_token; 66#endif 67}; 68 69static struct apm_softc apm_softc; 70static struct apmhook *hook[NAPM_HOOK]; /* XXX */ 71 72#define is_enabled(foo) ((foo) ? "enabled" : "disabled") 73 74/* Map version number to integer (keeps ordering of version numbers) */ 75#define INTVERSION(major, minor) ((major)*100 + (minor)) 76 77static struct callout_handle apm_timeout_ch = 78 CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch); 79 80static timeout_t apm_timeout; 81static d_open_t apmopen; 82static d_close_t apmclose; 83static d_ioctl_t apmioctl; 84 85#define CDEV_MAJOR 39 86static struct cdevsw apm_cdevsw = 87 { apmopen, apmclose, noread, nowrite, /*39*/ 88 apmioctl, nostop, nullreset, nodevtotty,/* APM */ 89 seltrue, nommap, NULL , "apm" ,NULL, -1}; 90 91/* setup APM GDT discriptors */ 92static void 93setup_apm_gdt(u_int code32_base, u_int code16_base, u_int data_base, u_int code32_limit, u_int code16_limit, u_int data_limit) 94{ 95 /* setup 32bit code segment */ 96 gdt_segs[GAPMCODE32_SEL].ssd_base = code32_base; 97 gdt_segs[GAPMCODE32_SEL].ssd_limit = code32_limit; 98 99 /* setup 16bit code segment */ 100 gdt_segs[GAPMCODE16_SEL].ssd_base = code16_base; 101 gdt_segs[GAPMCODE16_SEL].ssd_limit = code16_limit; 102 103 /* setup data segment */ 104 gdt_segs[GAPMDATA_SEL ].ssd_base = data_base; 105 gdt_segs[GAPMDATA_SEL ].ssd_limit = data_limit; 106 107 /* reflect these changes on physical GDT */ 108 ssdtosd(gdt_segs + GAPMCODE32_SEL, &gdt[GAPMCODE32_SEL].sd); 109 ssdtosd(gdt_segs + GAPMCODE16_SEL, &gdt[GAPMCODE16_SEL].sd); 110 ssdtosd(gdt_segs + GAPMDATA_SEL , &gdt[GAPMDATA_SEL ].sd); 111} 112 113/* 48bit far pointer. Do not staticize - used from apm_setup.s */ 114struct addr48 { 115 u_long offset; 116 u_short segment; 117} apm_addr; 118 119static int apm_errno; 120 121static int 122apm_int(u_long *eax, u_long *ebx, u_long *ecx, u_long *edx) 123{ 124 struct apm_bios_arg apa; 125 int cf; 126 127 apa.eax = *eax; 128 apa.ebx = *ebx; 129 apa.ecx = *ecx; 130 apa.edx = *edx; 131 cf = apm_bios_call(&apa); 132 *eax = apa.eax; 133 *ebx = apa.ebx; 134 *ecx = apa.ecx; 135 *edx = apa.edx; 136 apm_errno = ((*eax) >> 8) & 0xff; 137 return cf; 138} 139 140 141/* enable/disable power management */ 142static int 143apm_enable_disable_pm(int enable) 144{ 145 struct apm_softc *sc = &apm_softc; 146 147 u_long eax, ebx, ecx, edx; 148 149 eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM; 150 151 if (sc->intversion >= INTVERSION(1, 1)) 152 ebx = PMDV_ALLDEV; 153 else 154 ebx = 0xffff; /* APM version 1.0 only */ 155 ecx = enable; 156 edx = 0; 157 return apm_int(&eax, &ebx, &ecx, &edx); 158} 159 160static void 161apm_driver_version(int version) 162{ 163 u_long eax, ebx, ecx, edx; 164 165 /* First try APM 1.2 */ 166 eax = (APM_BIOS << 8) | APM_DRVVERSION; 167 ebx = 0x0; 168 ecx = version; 169 edx = 0; 170 if(!apm_int(&eax, &ebx, &ecx, &edx)) 171 apm_version = eax & 0xffff; 172} 173 174/* engage/disengage power management (APM 1.1 or later) */ 175static int 176apm_engage_disengage_pm(int engage) 177{ 178 u_long eax, ebx, ecx, edx; 179 180 eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM; 181 ebx = PMDV_ALLDEV; 182 ecx = engage; 183 edx = 0; 184 return(apm_int(&eax, &ebx, &ecx, &edx)); 185} 186 187/* get PM event */ 188static u_int 189apm_getevent(void) 190{ 191 u_long eax, ebx, ecx, edx; 192 193 eax = (APM_BIOS << 8) | APM_GETPMEVENT; 194 195 ebx = 0; 196 ecx = 0; 197 edx = 0; 198 if (apm_int(&eax, &ebx, &ecx, &edx)) 199 return PMEV_NOEVENT; 200 201 return ebx & 0xffff; 202} 203 204/* suspend entire system */ 205static int 206apm_suspend_system(int state) 207{ 208 u_long eax, ebx, ecx, edx; 209 210 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 211 ebx = PMDV_ALLDEV; 212 ecx = state; 213 edx = 0; 214 215 if (apm_int(&eax, &ebx, &ecx, &edx)) { 216 printf("Entire system suspend failure: errcode = %ld\n", 217 0xff & (eax >> 8)); 218 return 1; 219 } 220 return 0; 221} 222 223/* Display control */ 224/* 225 * Experimental implementation: My laptop machine can't handle this function 226 * If your laptop can control the display via APM, please inform me. 227 * HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org> 228 */ 229static int 230apm_display(int newstate) 231{ 232 u_long eax, ebx, ecx, edx; 233 234 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 235 ebx = PMDV_DISP0; 236 ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND; 237 edx = 0; 238 if (apm_int(&eax, &ebx, &ecx, &edx)) { 239 printf("Display off failure: errcode = %ld\n", 240 0xff & (eax >> 8)); 241 return 1; 242 } 243 return 0; 244} 245 246/* 247 * Turn off the entire system. 248 */ 249static void 250apm_power_off(int howto, void *junk) 251{ 252 u_long eax, ebx, ecx, edx; 253 254 /* Not halting powering off, or not active */ 255 if (!(howto & RB_POWEROFF) || !apm_softc.active) 256 return; 257 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 258 ebx = PMDV_ALLDEV; 259 ecx = PMST_OFF; 260 edx = 0; 261 apm_int(&eax, &ebx, &ecx, &edx); 262} 263 264/* APM Battery low handler */ 265static void 266apm_battery_low(void) 267{ 268 printf("\007\007 * * * BATTERY IS LOW * * * \007\007"); 269} 270 271/* APM hook manager */ 272static struct apmhook * 273apm_add_hook(struct apmhook **list, struct apmhook *ah) 274{ 275 int s; 276 struct apmhook *p, *prev; 277 278#ifdef APM_DEBUG 279 printf("Add hook \"%s\"\n", ah->ah_name); 280#endif 281 282 s = splhigh(); 283 if (ah == NULL) 284 panic("illegal apm_hook!"); 285 prev = NULL; 286 for (p = *list; p != NULL; prev = p, p = p->ah_next) 287 if (p->ah_order > ah->ah_order) 288 break; 289 290 if (prev == NULL) { 291 ah->ah_next = *list; 292 *list = ah; 293 } else { 294 ah->ah_next = prev->ah_next; 295 prev->ah_next = ah; 296 } 297 splx(s); 298 return ah; 299} 300 301static void 302apm_del_hook(struct apmhook **list, struct apmhook *ah) 303{ 304 int s; 305 struct apmhook *p, *prev; 306 307 s = splhigh(); 308 prev = NULL; 309 for (p = *list; p != NULL; prev = p, p = p->ah_next) 310 if (p == ah) 311 goto deleteit; 312 panic("Tried to delete unregistered apm_hook."); 313 goto nosuchnode; 314deleteit: 315 if (prev != NULL) 316 prev->ah_next = p->ah_next; 317 else 318 *list = p->ah_next; 319nosuchnode: 320 splx(s); 321} 322 323 324/* APM driver calls some functions automatically */ 325static void 326apm_execute_hook(struct apmhook *list) 327{ 328 struct apmhook *p; 329 330 for (p = list; p != NULL; p = p->ah_next) { 331#ifdef APM_DEBUG 332 printf("Execute APM hook \"%s.\"\n", p->ah_name); 333#endif 334 if ((*(p->ah_fun))(p->ah_arg)) 335 printf("Warning: APM hook \"%s\" failed", p->ah_name); 336 } 337} 338 339 340/* establish an apm hook */ 341struct apmhook * 342apm_hook_establish(int apmh, struct apmhook *ah) 343{ 344 if (apmh < 0 || apmh >= NAPM_HOOK) 345 return NULL; 346 347 return apm_add_hook(&hook[apmh], ah); 348} 349 350/* disestablish an apm hook */ 351void 352apm_hook_disestablish(int apmh, struct apmhook *ah) 353{ 354 if (apmh < 0 || apmh >= NAPM_HOOK) 355 return; 356 357 apm_del_hook(&hook[apmh], ah); 358} 359 360 361static struct timeval suspend_time; 362static struct timeval diff_time; 363 364static int 365apm_default_resume(void *arg) 366{ 367 int pl; 368 u_int second, minute, hour; 369 struct timeval resume_time, tmp_time; 370 371 /* modified for adjkerntz */ 372 pl = splsoftclock(); 373 inittodr(0); /* adjust time to RTC */ 374 microtime(&resume_time); 375 getmicrotime(&tmp_time); 376 timevaladd(&tmp_time, &diff_time); 377 378#ifdef FIXME 379 /* XXX THIS DOESN'T WORK!!! */ 380 time = tmp_time; 381#endif 382 383#ifdef APM_FIXUP_CALLTODO 384 /* Calculate the delta time suspended */ 385 timevalsub(&resume_time, &suspend_time); 386 /* Fixup the calltodo list with the delta time. */ 387 adjust_timeout_calltodo(&resume_time); 388#endif /* APM_FIXUP_CALLTODOK */ 389 splx(pl); 390#ifndef APM_FIXUP_CALLTODO 391 second = resume_time.tv_sec - suspend_time.tv_sec; 392#else /* APM_FIXUP_CALLTODO */ 393 /* 394 * We've already calculated resume_time to be the delta between 395 * the suspend and the resume. 396 */ 397 second = resume_time.tv_sec; 398#endif /* APM_FIXUP_CALLTODO */ 399 hour = second / 3600; 400 second %= 3600; 401 minute = second / 60; 402 second %= 60; 403 log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n", 404 hour, minute, second); 405 return 0; 406} 407 408static int 409apm_default_suspend(void *arg) 410{ 411 int pl; 412 413 pl = splsoftclock(); 414 microtime(&diff_time); 415 inittodr(0); 416 microtime(&suspend_time); 417 timevalsub(&diff_time, &suspend_time); 418 splx(pl); 419 return 0; 420} 421 422static void apm_processevent(void); 423 424/* 425 * Public interface to the suspend/resume: 426 * 427 * Execute suspend and resume hook before and after sleep, respectively. 428 * 429 */ 430 431void 432apm_suspend(int state) 433{ 434 struct apm_softc *sc = &apm_softc; 435 int error; 436 437 if (!sc) 438 return; 439 440 if (sc->initialized) { 441 error = DEVICE_SUSPEND(root_bus); 442 /* 443 * XXX Shouldn't ignore the error like this, but should 444 * instead fix the newbus code. Until that happens, 445 * I'm doing this to get suspend working again. 446 */ 447 if (error) 448 printf("DEVICE_SUSPEND error %d, ignored\n", error); 449 apm_execute_hook(hook[APM_HOOK_SUSPEND]); 450 if (apm_suspend_system(state) == 0) 451 apm_processevent(); 452 else 453 /* Failure, 'resume' the system again */ 454 apm_execute_hook(hook[APM_HOOK_RESUME]); 455 } 456} 457 458void 459apm_resume(void) 460{ 461 struct apm_softc *sc = &apm_softc; 462 463 if (!sc) 464 return; 465 466 if (sc->initialized) { 467 DEVICE_RESUME(root_bus); 468 apm_execute_hook(hook[APM_HOOK_RESUME]); 469 } 470} 471 472 473/* get APM information */ 474static int 475apm_get_info(apm_info_t aip) 476{ 477 struct apm_softc *sc = &apm_softc; 478 u_long eax, ebx, ecx, edx; 479 480 eax = (APM_BIOS << 8) | APM_GETPWSTATUS; 481 ebx = PMDV_ALLDEV; 482 ecx = 0; 483 edx = 0xffff; /* default to unknown battery time */ 484 485 if (apm_int(&eax, &ebx, &ecx, &edx)) 486 return 1; 487 488 aip->ai_infoversion = 1; 489 aip->ai_acline = (ebx >> 8) & 0xff; 490 aip->ai_batt_stat = ebx & 0xff; 491 aip->ai_batt_life = ecx & 0xff; 492 aip->ai_major = (u_int)sc->majorversion; 493 aip->ai_minor = (u_int)sc->minorversion; 494 aip->ai_status = (u_int)sc->active; 495 edx &= 0xffff; 496 if (edx == 0xffff) /* Time is unknown */ 497 aip->ai_batt_time = -1; 498 else if (edx & 0x8000) /* Time is in minutes */ 499 aip->ai_batt_time = (edx & 0x7fff) * 60; 500 else /* Time is in seconds */ 501 aip->ai_batt_time = edx; 502 503 eax = (APM_BIOS << 8) | APM_GETCAPABILITIES; 504 ebx = 0; 505 ecx = 0; 506 edx = 0; 507 if (apm_int(&eax, &ebx, &ecx, &edx)) { 508 aip->ai_batteries = -1; /* Unknown */ 509 aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */ 510 } else { 511 aip->ai_batteries = ebx & 0xff; 512 aip->ai_capabilities = ecx & 0xf; 513 } 514 515 bzero(aip->ai_spare, sizeof aip->ai_spare); 516 517 return 0; 518} 519 520 521/* inform APM BIOS that CPU is idle */ 522void 523apm_cpu_idle(void) 524{ 525 struct apm_softc *sc = &apm_softc; 526 527 if (sc->active) { 528 u_long eax, ebx, ecx, edx; 529 530 eax = (APM_BIOS <<8) | APM_CPUIDLE; 531 edx = ecx = ebx = 0; 532 apm_int(&eax, &ebx, &ecx, &edx); 533 } 534 /* 535 * Some APM implementation halts CPU in BIOS, whenever 536 * "CPU-idle" function are invoked, but swtch() of 537 * FreeBSD halts CPU, therefore, CPU is halted twice 538 * in the sched loop. It makes the interrupt latency 539 * terribly long and be able to cause a serious problem 540 * in interrupt processing. We prevent it by removing 541 * "hlt" operation from swtch() and managed it under 542 * APM driver. 543 */ 544 if (!sc->active || sc->always_halt_cpu) 545 __asm("hlt"); /* wait for interrupt */ 546} 547 548/* inform APM BIOS that CPU is busy */ 549void 550apm_cpu_busy(void) 551{ 552 struct apm_softc *sc = &apm_softc; 553 554 /* 555 * The APM specification says this is only necessary if your BIOS 556 * slows down the processor in the idle task, otherwise it's not 557 * necessary. 558 */ 559 if (sc->slow_idle_cpu && sc->active) { 560 u_long eax, ebx, ecx, edx; 561 562 eax = (APM_BIOS <<8) | APM_CPUBUSY; 563 edx = ecx = ebx = 0; 564 apm_int(&eax, &ebx, &ecx, &edx); 565 } 566} 567 568 569/* 570 * APM timeout routine: 571 * 572 * This routine is automatically called by timer once per second. 573 */ 574 575static void 576apm_timeout(void *dummy) 577{ 578 struct apm_softc *sc = &apm_softc; 579 580 apm_processevent(); 581 if (sc->active == 1) 582 /* Run slightly more oftan than 1 Hz */ 583 apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1 ); 584} 585 586/* enable APM BIOS */ 587static void 588apm_event_enable(void) 589{ 590 struct apm_softc *sc = &apm_softc; 591 592#ifdef APM_DEBUG 593 printf("called apm_event_enable()\n"); 594#endif 595 if (sc->initialized) { 596 sc->active = 1; 597 apm_timeout(sc); 598 } 599} 600 601/* disable APM BIOS */ 602static void 603apm_event_disable(void) 604{ 605 struct apm_softc *sc = &apm_softc; 606 607#ifdef APM_DEBUG 608 printf("called apm_event_disable()\n"); 609#endif 610 if (sc->initialized) { 611 untimeout(apm_timeout, NULL, apm_timeout_ch); 612 sc->active = 0; 613 } 614} 615 616/* halt CPU in scheduling loop */ 617static void 618apm_halt_cpu(void) 619{ 620 struct apm_softc *sc = &apm_softc; 621 622 if (sc->initialized) 623 sc->always_halt_cpu = 1; 624} 625 626/* don't halt CPU in scheduling loop */ 627static void 628apm_not_halt_cpu(void) 629{ 630 struct apm_softc *sc = &apm_softc; 631 632 if (sc->initialized) 633 sc->always_halt_cpu = 0; 634} 635 636/* device driver definitions */ 637 638/* 639 * probe APM (dummy): 640 * 641 * APM probing routine is placed on locore.s and apm_init.S because 642 * this process forces the CPU to turn to real mode or V86 mode. 643 * Current version uses real mode, but in a future version, we want 644 * to use V86 mode in APM initialization. 645 * 646 * XXX If VM86 is defined, we do. 647 */ 648 649static int 650apm_probe(device_t dev) 651{ 652#ifdef VM86 653 struct vm86frame vmf; 654 int i; 655#endif 656 int disabled, flags; 657 658 if (resource_int_value("apm", 0, "disabled", &disabled) == 0 659 && disabled != 0) 660 return ENXIO; 661 662 device_set_desc(dev, "APM BIOS"); 663 664 if ( device_get_unit(dev) > 0 ) { 665 printf("apm: Only one APM driver supported.\n"); 666 return ENXIO; 667 } 668 669 if (resource_int_value("apm", 0, "flags", &flags) != 0) 670 flags = 0; 671 672#ifdef VM86 673 bzero(&vmf, sizeof(struct vm86frame)); /* safety */ 674 vmf.vmf_ax = (APM_BIOS << 8) | APM_INSTCHECK; 675 vmf.vmf_bx = 0; 676 if (((i = vm86_intcall(SYSTEM_BIOS, &vmf)) == 0) && 677 !(vmf.vmf_eflags & PSL_C) && 678 (vmf.vmf_bx == 0x504d)) { 679 680 apm_version = vmf.vmf_ax; 681 apm_flags = vmf.vmf_cx; 682 683 vmf.vmf_ax = (APM_BIOS << 8) | APM_PROT32CONNECT; 684 vmf.vmf_bx = 0; 685 if (((i = vm86_intcall(SYSTEM_BIOS, &vmf)) == 0) && 686 !(vmf.vmf_eflags & PSL_C)) { 687 688 apm_cs32_base = vmf.vmf_ax; 689 apm_cs_entry = vmf.vmf_ebx; 690 apm_cs16_base = vmf.vmf_cx; 691 apm_ds_base = vmf.vmf_dx; 692 apm_cs32_limit = vmf.vmf_si; 693 if (apm_version >= 0x0102) 694 apm_cs16_limit = (vmf.esi.r_ex >> 16); 695 apm_ds_limit = vmf.vmf_di; 696#ifdef APM_DEBUG 697 printf("apm: BIOS probe/32-bit connect successful\n"); 698#endif 699 } else { 700 /* XXX constant typo! */ 701 if (vmf.vmf_ah == APME_PROT32NOTDUPPORTED) { 702 apm_version = APMINI_NOT32BIT; 703 } else { 704 apm_version = APMINI_CONNECTERR; 705 } 706#ifdef APM_DEBUG 707 printf("apm: BIOS 32-bit connect failed: error 0x%x carry %d ah 0x%x\n", 708 i, (vmf.vmf_eflags & PSL_C) ? 1 : 0, vmf.vmf_ah); 709#endif 710 } 711 } else { 712 apm_version = APMINI_CANTFIND; 713#ifdef APM_DEBUG 714 printf("apm: BIOS probe failed: error 0x%x carry %d bx 0x%x\n", 715 i, (vmf.vmf_eflags & PSL_C) ? 1 : 0, vmf.vmf_bx); 716#endif 717 } 718#endif 719 720 bzero(&apm_softc, sizeof(apm_softc)); 721 722 switch (apm_version) { 723 case APMINI_CANTFIND: 724 /* silent */ 725 return 0; 726 case APMINI_NOT32BIT: 727 printf("apm: 32bit connection is not supported.\n"); 728 return 0; 729 case APMINI_CONNECTERR: 730 printf("apm: 32-bit connection error.\n"); 731 return 0; 732 } 733 if (flags & 0x20) 734 statclock_disable = 1; 735 return 0; 736} 737 738 739/* Process APM event */ 740static void 741apm_processevent(void) 742{ 743 int apm_event; 744 745#ifdef APM_DEBUG 746# define OPMEV_DEBUGMESSAGE(symbol) case symbol: \ 747 printf("Received APM Event: " #symbol "\n"); 748#else 749# define OPMEV_DEBUGMESSAGE(symbol) case symbol: 750#endif 751 do { 752 apm_event = apm_getevent(); 753 switch (apm_event) { 754 OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ); 755 apm_suspend(PMST_STANDBY); 756 break; 757 OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ); 758 apm_suspend(PMST_SUSPEND); 759 break; 760 OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ); 761 apm_suspend(PMST_SUSPEND); 762 break; 763 OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND); 764 apm_suspend(PMST_SUSPEND); 765 break; 766 OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME); 767 apm_resume(); 768 break; 769 OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME); 770 apm_resume(); 771 break; 772 OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME); 773 apm_resume(); 774 break; 775 OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW); 776 apm_battery_low(); 777 apm_suspend(PMST_SUSPEND); 778 break; 779 OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE); 780 break; 781 OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME); 782 inittodr(0); /* adjust time to RTC */ 783 break; 784 case PMEV_NOEVENT: 785 break; 786 default: 787 printf("Unknown Original APM Event 0x%x\n", apm_event); 788 break; 789 } 790 } while (apm_event != PMEV_NOEVENT); 791} 792 793/* 794 * Attach APM: 795 * 796 * Initialize APM driver (APM BIOS itself has been initialized in locore.s) 797 */ 798 799static int 800apm_attach(device_t dev) 801{ 802#define APM_KERNBASE KERNBASE 803 struct apm_softc *sc = &apm_softc; 804 int flags; 805 806 if (resource_int_value("apm", 0, "flags", &flags) != 0) 807 flags = 0; 808 809 sc->initialized = 0; 810 811 /* Must be externally enabled */ 812 sc->active = 0; 813 814 /* setup APM parameters */ 815 sc->cs16_base = (apm_cs16_base << 4) + APM_KERNBASE; 816 sc->cs32_base = (apm_cs32_base << 4) + APM_KERNBASE; 817 sc->ds_base = (apm_ds_base << 4) + APM_KERNBASE; 818 sc->cs32_limit = apm_cs32_limit - 1; 819 if (apm_cs16_limit == 0) 820 apm_cs16_limit = apm_cs32_limit; 821 sc->cs16_limit = apm_cs16_limit - 1; 822 sc->ds_limit = apm_ds_limit - 1; 823 sc->cs_entry = apm_cs_entry; 824 825 /* Always call HLT in idle loop */ 826 sc->always_halt_cpu = 1; 827 828 sc->slow_idle_cpu = ((apm_flags & APM_CPUIDLE_SLOW) != 0); 829 sc->disabled = ((apm_flags & APM_DISABLED) != 0); 830 sc->disengaged = ((apm_flags & APM_DISENGAGED) != 0); 831 832 /* print bootstrap messages */ 833#ifdef APM_DEBUG 834 printf("apm: APM BIOS version %04x\n", apm_version); 835 printf("apm: Code32 0x%08x, Code16 0x%08x, Data 0x%08x\n", 836 sc->cs32_base, sc->cs16_base, sc->ds_base); 837 printf("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n", 838 sc->cs_entry, is_enabled(sc->slow_idle_cpu), 839 is_enabled(!sc->disabled)); 840 printf("apm: CS32_limit=0x%x, CS16_limit=0x%x, DS_limit=0x%x\n", 841 (u_short)sc->cs32_limit, (u_short)sc->cs16_limit, (u_short)sc->ds_limit); 842#endif /* APM_DEBUG */ 843 844#if 0 845 /* Workaround for some buggy APM BIOS implementations */ 846 sc->cs_limit = 0xffff; 847 sc->ds_limit = 0xffff; 848#endif 849 850 /* setup GDT */ 851 setup_apm_gdt(sc->cs32_base, sc->cs16_base, sc->ds_base, 852 sc->cs32_limit, sc->cs16_limit, sc->ds_limit); 853 854 /* setup entry point 48bit pointer */ 855 apm_addr.segment = GSEL(GAPMCODE32_SEL, SEL_KPL); 856 apm_addr.offset = sc->cs_entry; 857 858 if ((flags & 0x10)) { 859 if ((flags & 0xf) >= 0x2) { 860 apm_driver_version(0x102); 861 } 862 if (!apm_version && (flags & 0xf) >= 0x1) { 863 apm_driver_version(0x101); 864 } 865 } else { 866 apm_driver_version(0x102); 867 if (!apm_version) 868 apm_driver_version(0x101); 869 } 870 if (!apm_version) 871 apm_version = 0x100; 872 873 sc->minorversion = ((apm_version & 0x00f0) >> 4) * 10 + 874 ((apm_version & 0x000f) >> 0); 875 sc->majorversion = ((apm_version & 0xf000) >> 12) * 10 + 876 ((apm_version & 0x0f00) >> 8); 877 878 sc->intversion = INTVERSION(sc->majorversion, sc->minorversion); 879 880#ifdef APM_DEBUG 881 if (sc->intversion >= INTVERSION(1, 1)) 882 printf("apm: Engaged control %s\n", is_enabled(!sc->disengaged)); 883#endif 884 885 printf("apm: found APM BIOS version %d.%d\n", 886 sc->majorversion, sc->minorversion); 887 888#ifdef APM_DEBUG 889 printf("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu)); 890#endif 891 892 /* enable power management */ 893 if (sc->disabled) { 894 if (apm_enable_disable_pm(1)) { 895#ifdef APM_DEBUG 896 printf("apm: *Warning* enable function failed! [%x]\n", 897 apm_errno); 898#endif 899 } 900 } 901 902 /* engage power managment (APM 1.1 or later) */ 903 if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) { 904 if (apm_engage_disengage_pm(1)) { 905#ifdef APM_DEBUG 906 printf("apm: *Warning* engage function failed err=[%x]", 907 apm_errno); 908 printf(" (Docked or using external power?).\n"); 909#endif 910 } 911 } 912 913 /* default suspend hook */ 914 sc->sc_suspend.ah_fun = apm_default_suspend; 915 sc->sc_suspend.ah_arg = sc; 916 sc->sc_suspend.ah_name = "default suspend"; 917 sc->sc_suspend.ah_order = APM_MAX_ORDER; 918 919 /* default resume hook */ 920 sc->sc_resume.ah_fun = apm_default_resume; 921 sc->sc_resume.ah_arg = sc; 922 sc->sc_resume.ah_name = "default resume"; 923 sc->sc_resume.ah_order = APM_MIN_ORDER; 924 925 apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend); 926 apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume); 927 928 apm_event_enable(); 929 930 /* Power the system off using APM */ 931 at_shutdown_pri(apm_power_off, NULL, SHUTDOWN_FINAL, SHUTDOWN_PRI_LAST); 932 933 sc->initialized = 1; 934 935#ifdef DEVFS 936 sc->sc_devfs_token = 937 devfs_add_devswf(&apm_cdevsw, 0, DV_CHR, 0, 0, 0600, "apm"); 938#endif 939 return 0; 940} 941 942static int 943apmopen(dev_t dev, int flag, int fmt, struct proc *p) 944{ 945 struct apm_softc *sc = &apm_softc; 946 947 if (minor(dev) != 0 || !sc->initialized) 948 return (ENXIO); 949 950 return 0; 951} 952 953static int 954apmclose(dev_t dev, int flag, int fmt, struct proc *p) 955{ 956 return 0; 957} 958 959static int 960apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p) 961{ 962 struct apm_softc *sc = &apm_softc; 963 int error = 0; 964 int newstate; 965 966 if (minor(dev) != 0 || !sc->initialized) 967 return (ENXIO); 968#ifdef APM_DEBUG 969 printf("APM ioctl: cmd = 0x%x\n", cmd); 970#endif 971 switch (cmd) { 972 case APMIO_SUSPEND: 973 if (sc->active) 974 apm_suspend(PMST_SUSPEND); 975 else 976 error = EINVAL; 977 break; 978 979 case APMIO_STANDBY: 980 if (sc->active) 981 apm_suspend(PMST_STANDBY); 982 else 983 error = EINVAL; 984 break; 985 986 case APMIO_GETINFO_OLD: 987 { 988 struct apm_info info; 989 apm_info_old_t aiop; 990 991 if (apm_get_info(&info)) 992 error = ENXIO; 993 aiop = (apm_info_old_t)addr; 994 aiop->ai_major = info.ai_major; 995 aiop->ai_minor = info.ai_minor; 996 aiop->ai_acline = info.ai_acline; 997 aiop->ai_batt_stat = info.ai_batt_stat; 998 aiop->ai_batt_life = info.ai_batt_life; 999 aiop->ai_status = info.ai_status; 1000 } 1001 break; 1002 case APMIO_GETINFO: 1003 if (apm_get_info((apm_info_t)addr)) 1004 error = ENXIO; 1005 break; 1006 case APMIO_ENABLE: 1007 apm_event_enable(); 1008 break; 1009 case APMIO_DISABLE: 1010 apm_event_disable(); 1011 break; 1012 case APMIO_HALTCPU: 1013 apm_halt_cpu(); 1014 break; 1015 case APMIO_NOTHALTCPU: 1016 apm_not_halt_cpu(); 1017 break; 1018 case APMIO_DISPLAY: 1019 newstate = *(int *)addr; 1020 if (apm_display(newstate)) 1021 error = ENXIO; 1022 break; 1023 case APMIO_BIOS: 1024 if (apm_bios_call((struct apm_bios_arg*)addr) == 0) 1025 ((struct apm_bios_arg*)addr)->eax &= 0xff; 1026 break; 1027 default: 1028 error = EINVAL; 1029 break; 1030 } 1031 return error; 1032} 1033 1034static device_method_t apm_methods[] = { 1035 /* Device interface */ 1036 DEVMETHOD(device_probe, apm_probe), 1037 DEVMETHOD(device_attach, apm_attach), 1038 1039 { 0, 0 } 1040}; 1041 1042static driver_t apm_driver = { 1043 "apm", 1044 apm_methods, 1045 DRIVER_TYPE_MISC, 1046 1, /* no softc (XXX) */ 1047}; 1048 1049static devclass_t apm_devclass; 1050 1051CDEV_DRIVER_MODULE(apm, nexus, apm_driver, apm_devclass, 1052 CDEV_MAJOR, apm_cdevsw, 0, 0); 1053