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