apm.c revision 14537
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@mt.cs.keio.ac.jp> 6 * 7 * This software may be used, modified, copied, and distributed, in 8 * both source and binary form provided that the above copyright and 9 * these terms are retained. Under no circumstances is the author 10 * responsible for the proper functioning of this software, nor does 11 * the author assume any responsibility for damages incurred with its 12 * use. 13 * 14 * Sep, 1994 Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD) 15 * 16 * $Id: apm.c,v 1.23 1995/12/27 16:16:02 bde Exp $ 17 */ 18 19#include "apm.h" 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/malloc.h> 29#include <sys/ioctl.h> 30#include <sys/file.h> 31#include <sys/proc.h> 32#include <sys/vnode.h> 33#include "i386/isa/isa.h" 34#include "i386/isa/isa_device.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#include "apm_setup.h" 43 44static int apm_display_off __P((void)); 45static int apm_int __P((u_long *eax, u_long *ebx, u_long *ecx)); 46static void apm_resume __P((void)); 47 48/* static data */ 49struct apm_softc { 50 int initialized, active, halt_cpu; 51 u_int minorversion, majorversion; 52 u_int cs32_base, cs16_base, ds_base; 53 u_int cs_limit, ds_limit; 54 u_int cs_entry; 55 u_int intversion; 56 int idle_cpu, disabled, disengaged; 57 struct apmhook sc_suspend; 58 struct apmhook sc_resume; 59 void *sc_devfs_token; 60}; 61 62static struct apm_softc apm_softc[NAPM]; 63static struct apm_softc *master_softc = NULL; /* XXX */ 64static struct apmhook *hook[NAPM_HOOK]; /* XXX */ 65#ifdef APM_SLOWSTART 66int apm_slowstart = 0; 67int apm_ss_cnt = 0; 68static int apm_slowstart_p = 0; 69int apm_slowstart_stat = 0; 70#endif /* APM_SLOWSTART */ 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 timeout_t apm_timeout; 78static d_open_t apmopen; 79static d_close_t apmclose; 80static d_ioctl_t apmioctl; 81 82#define CDEV_MAJOR 39 83static struct cdevsw apm_cdevsw = 84 { apmopen, apmclose, noread, nowrite, /*39*/ 85 apmioctl, nostop, nullreset, nodevtotty,/* APM */ 86 seltrue, nommap, NULL , "apm" ,NULL, -1}; 87 88/* setup APM GDT discriptors */ 89static void 90setup_apm_gdt(u_int code32_base, u_int code16_base, u_int data_base, u_int code_limit, u_int data_limit) 91{ 92 /* setup 32bit code segment */ 93 gdt_segs[GAPMCODE32_SEL].ssd_base = code32_base; 94 gdt_segs[GAPMCODE32_SEL].ssd_limit = code_limit; 95 96 /* setup 16bit code segment */ 97 gdt_segs[GAPMCODE16_SEL].ssd_base = code16_base; 98 gdt_segs[GAPMCODE16_SEL].ssd_limit = code_limit; 99 100 /* setup data segment */ 101 gdt_segs[GAPMDATA_SEL ].ssd_base = data_base; 102 gdt_segs[GAPMDATA_SEL ].ssd_limit = data_limit; 103 104 /* reflect these changes on physical GDT */ 105 ssdtosd(gdt_segs + GAPMCODE32_SEL, &gdt[GAPMCODE32_SEL].sd); 106 ssdtosd(gdt_segs + GAPMCODE16_SEL, &gdt[GAPMCODE16_SEL].sd); 107 ssdtosd(gdt_segs + GAPMDATA_SEL , &gdt[GAPMDATA_SEL ].sd); 108} 109 110/* 48bit far pointer */ 111static struct addr48 { 112 u_long offset; 113 u_short segment; 114} apm_addr; 115 116static int apm_errno; 117 118inline 119int 120apm_int(u_long *eax, u_long *ebx, u_long *ecx) 121{ 122 u_long cf; 123 __asm ("pushl %%ebp 124 pushl %%edx 125 pushl %%esi 126 xorl %3,%3 127 movl %3,%%esi 128 lcall _apm_addr 129 jnc 1f 130 incl %3 131 1: 132 popl %%esi 133 popl %%edx 134 popl %%ebp" 135 : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=D" (cf) 136 : "0" (*eax), "1" (*ebx), "2" (*ecx) 137 ); 138 apm_errno = ((*eax) >> 8) & 0xff; 139 return cf; 140} 141 142 143/* enable/disable power management */ 144static int 145apm_enable_disable_pm(struct apm_softc *sc, int enable) 146{ 147 u_long eax, ebx, ecx; 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 } 156 ecx = enable; 157 return apm_int(&eax, &ebx, &ecx); 158} 159 160/* Tell APM-BIOS that WE will do 1.1 and see what they say... */ 161static void 162apm_driver_version(void) 163{ 164 u_long eax, ebx, ecx; 165 166#ifdef APM_DEBUG 167 eax = (APM_BIOS<<8) | APM_INSTCHECK; 168 ebx = 0x0; 169 ecx = 0x0101; 170 i = apm_int(&eax, &ebx, &ecx); 171 printf("[%04lx %04lx %04lx %ld %02x]\n", 172 eax, ebx, ecx, i, apm_errno); 173#endif 174 175 eax = (APM_BIOS << 8) | APM_DRVVERSION; 176 ebx = 0x0; 177 ecx = 0x0101; 178 if(!apm_int(&eax, &ebx, &ecx)) 179 apm_version = eax & 0xffff; 180 181#ifdef APM_DEBUG 182 eax = (APM_BIOS << 8) | APM_INSTCHECK; 183 ebx = 0x0; 184 ecx = 0x0101; 185 i = apm_int(&eax, &ebx, &ecx); 186 printf("[%04lx %04lx %04lx %ld %02x]\n", 187 eax, ebx, ecx, i, apm_errno); 188#endif 189} 190 191/* engage/disengage power management (APM 1.1 or later) */ 192static int 193apm_engage_disengage_pm(struct apm_softc *sc, int engage) 194{ 195 u_long eax, ebx, ecx, i; 196 197 eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM; 198 ebx = PMDV_ALLDEV; 199 ecx = engage; 200 i = apm_int(&eax, &ebx, &ecx); 201 return i; 202} 203 204/* get PM event */ 205static u_int 206apm_getevent(struct apm_softc *sc) 207{ 208 u_long eax, ebx, ecx; 209 210 eax = (APM_BIOS << 8) | APM_GETPMEVENT; 211 212 ebx = 0; 213 ecx = 0; 214 if (apm_int(&eax, &ebx, &ecx)) 215 return PMEV_NOEVENT; 216 217 return ebx & 0xffff; 218} 219 220/* suspend entire system */ 221static int 222apm_suspend_system(struct apm_softc *sc) 223{ 224 u_long eax, ebx, ecx; 225 226 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 227 ebx = PMDV_ALLDEV; 228 ecx = PMST_SUSPEND; 229 230 __asm("cli"); 231 if (apm_int(&eax, &ebx, &ecx)) { 232 __asm("sti"); 233 printf("Entire system suspend failure: errcode = %ld\n", 234 0xff & (eax >> 8)); 235 return 1; 236 } 237 __asm("sti"); 238 return 0; 239} 240 241/* Display control */ 242/* 243 * Experimental implementation: My laptop machine can't handle this function 244 * If your laptop can control the display via APM, please inform me. 245 * HOSOKAWA, Tatsumi <hosokawa@mt.cs.keio.ac.jp> 246 */ 247static int 248apm_display_off(void) 249{ 250 u_long eax, ebx, ecx; 251 252 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 253 ebx = PMDV_2NDSTORAGE0; 254 ecx = PMST_STANDBY; 255 if (apm_int(&eax, &ebx, &ecx)) { 256 printf("Display off failure: errcode = %ld\n", 257 0xff & (eax >> 8)); 258 return 1; 259 } 260 261 return 0; 262} 263 264/* APM Battery low handler */ 265static void 266apm_battery_low(struct apm_softc *sc) 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#if 0 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 } 286 prev = NULL; 287 for (p = *list; p != NULL; prev = p, p = p->ah_next) { 288 if (p->ah_order > ah->ah_order) { 289 break; 290 } 291 } 292 293 if (prev == NULL) { 294 ah->ah_next = *list; 295 *list = ah; 296 } else { 297 ah->ah_next = prev->ah_next; 298 prev->ah_next = ah; 299 } 300 splx(s); 301 return ah; 302} 303 304static void 305apm_del_hook(struct apmhook **list, struct apmhook *ah) 306{ 307 int s; 308 struct apmhook *p, *prev; 309 310 s = splhigh(); 311 prev = NULL; 312 for (p = *list; p != NULL; prev = p, p = p->ah_next) { 313 if (p == ah) { 314 goto deleteit; 315 } 316 } 317 panic("Tried to delete unregistered apm_hook."); 318 goto nosuchnode; 319deleteit: 320 if (prev != NULL) { 321 prev->ah_next = p->ah_next; 322 } else { 323 *list = p->ah_next; 324 } 325nosuchnode: 326 splx(s); 327} 328 329 330/* APM driver calls some functions automatically */ 331static void 332apm_execute_hook(struct apmhook *list) 333{ 334 struct apmhook *p; 335 336 for (p = list; p != NULL; p = p->ah_next) { 337#if 0 338 printf("Execute APM hook \"%s.\"\n", p->ah_name); 339#endif 340 if ((*(p->ah_fun))(p->ah_arg)) { 341 printf("Warning: APM hook \"%s\" failed", p->ah_name); 342 } 343 } 344} 345 346 347/* establish an apm hook */ 348struct apmhook * 349apm_hook_establish(int apmh, struct apmhook *ah) 350{ 351 if (apmh < 0 || apmh >= NAPM_HOOK) 352 return NULL; 353 354 return apm_add_hook(&hook[apmh], ah); 355} 356 357#ifdef notused 358/* disestablish an apm hook */ 359void 360apm_hook_disestablish(int apmh, struct apmhook *ah) 361{ 362 if (apmh < 0 || apmh >= NAPM_HOOK) 363 return; 364 365 apm_del_hook(&hook[apmh], ah); 366} 367#endif /* notused */ 368 369 370static struct timeval suspend_time; 371static struct timeval diff_time; 372 373static int 374apm_default_resume(void *arg) 375{ 376 int pl; 377 u_int second, minute, hour; 378 struct timeval resume_time, tmp_time; 379 380 /* modified for adjkerntz */ 381 pl = splsoftclock(); 382 inittodr(0); /* adjust time to RTC */ 383 microtime(&resume_time); 384 tmp_time = time; /* because 'time' is volatile */ 385 timevaladd(&tmp_time, &diff_time); 386 time = tmp_time; 387 splx(pl); 388 second = resume_time.tv_sec - suspend_time.tv_sec; 389 hour = second / 3600; 390 second %= 3600; 391 minute = second / 60; 392 second %= 60; 393 log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n", 394 hour, minute, second); 395 return 0; 396} 397 398static int 399apm_default_suspend(void *arg) 400{ 401 int pl; 402 403 pl = splsoftclock(); 404 microtime(&diff_time); 405 inittodr(0); 406 microtime(&suspend_time); 407 timevalsub(&diff_time, &suspend_time); 408 splx(pl); 409 return 0; 410} 411 412static void apm_processevent(struct apm_softc *); 413 414/* 415 * Public interface to the suspend/resume: 416 * 417 * Execute suspend and resume hook before and after sleep, respectively. 418 * 419 */ 420 421void 422apm_suspend(void) 423{ 424 struct apm_softc *sc; 425 426 sc = master_softc; /* XXX */ 427 if (!sc) 428 return; 429 430 if (sc->initialized) { 431 apm_execute_hook(hook[APM_HOOK_SUSPEND]); 432 apm_suspend_system(sc); 433 apm_processevent(sc); 434 } 435} 436 437void 438apm_resume(void) 439{ 440 struct apm_softc *sc; 441 442 sc = master_softc; /* XXX */ 443 if (!sc) 444 return; 445 446 if (sc->initialized) { 447 apm_execute_hook(hook[APM_HOOK_RESUME]); 448 } 449} 450 451 452/* get APM information */ 453static int 454apm_get_info(struct apm_softc *sc, apm_info_t aip) 455{ 456 u_long eax, ebx, ecx; 457 458 eax = (APM_BIOS << 8) | APM_GETPWSTATUS; 459 ebx = PMDV_ALLDEV; 460 ecx = 0; 461 462 if (apm_int(&eax, &ebx, &ecx)) 463 return 1; 464 465 aip->ai_acline = (ebx >> 8) & 0xff; 466 aip->ai_batt_stat = ebx & 0xff; 467 aip->ai_batt_life = ecx & 0xff; 468 aip->ai_major = (u_int)sc->majorversion; 469 aip->ai_minor = (u_int)sc->minorversion; 470 return 0; 471} 472 473 474#ifdef APM_SLOWSTART 475/* inform APM BIOS that CPU is idle */ 476void 477apm_cpu_idle(void) 478{ 479 struct apm_softc *sc = master_softc; /* XXX */ 480 481 if (sc->idle_cpu) { 482 if (sc->active) { 483 __asm ("movw $0x5305, %ax; lcall _apm_addr"); 484 } 485 } 486 /* 487 * Some APM implementation halts CPU in BIOS, whenever 488 * "CPU-idle" function are invoked, but swtch() of 489 * FreeBSD halts CPU, therefore, CPU is halted twice 490 * in the sched loop. It makes the interrupt latency 491 * terribly long and be able to cause a serious problem 492 * in interrupt processing. We prevent it by removing 493 * "hlt" operation from swtch() and managed it under 494 * APM driver. 495 */ 496 /* 497 * UKAI Note: on NetBSD, idle() called from cpu_switch() 498 * doesn't halt CPU, so halt_cpu may not need on NetBSD/i386 499 * or only "sti" operation would be needed. 500 */ 501 502 if (!sc->active || sc->halt_cpu) { 503 __asm("sti ; hlt"); /* wait for interrupt */ 504 } 505} 506#endif /* APM_SLOWSTART */ 507 508#if APM_SLOWSTART > 0 509/* inform APM BIOS that CPU is busy */ 510void 511apm_cpu_busy(void) 512{ 513 struct apm_softc *sc = master_softc; /* XXX */ 514 515 if (sc->idle_cpu && sc->active) { 516 __asm("movw $0x5306, %ax; lcall _apm_addr"); 517 } 518} 519#endif /* APM_SLOWSTART > 0 */ 520 521 522/* 523 * APM timeout routine: 524 * 525 * This routine is automatically called by timer once per second. 526 */ 527 528static void 529apm_timeout(void *arg) 530{ 531 struct apm_softc *sc = arg; 532 533 apm_processevent(sc); 534 timeout(apm_timeout, (void *)sc, hz - 1 ); /* More than 1 Hz */ 535} 536 537/* enable APM BIOS */ 538static void 539apm_event_enable(struct apm_softc *sc) 540{ 541#ifdef APM_DEBUG 542 printf("called apm_event_enable()\n"); 543#endif 544 if (sc->initialized) { 545 sc->active = 1; 546 apm_timeout(sc); 547 } 548} 549 550/* disable APM BIOS */ 551static void 552apm_event_disable(struct apm_softc *sc) 553{ 554#ifdef APM_DEBUG 555 printf("called apm_event_disable()\n"); 556#endif 557 if (sc->initialized) { 558 untimeout(apm_timeout, NULL); 559 sc->active = 0; 560 } 561} 562 563/* halt CPU in scheduling loop */ 564static void 565apm_halt_cpu(struct apm_softc *sc) 566{ 567 if (sc->initialized) { 568 sc->halt_cpu = 1; 569 } 570#ifdef APM_SLOWSTART 571 apm_slowstart = 0; 572#endif /* APM_SLOWSTART */ 573} 574 575/* don't halt CPU in scheduling loop */ 576static void 577apm_not_halt_cpu(struct apm_softc *sc) 578{ 579 if (sc->initialized) { 580 sc->halt_cpu = 0; 581 } 582#ifdef APM_SLOWSTART 583 apm_slowstart = apm_slowstart_p; 584#endif /* APM_SLOWSTART */ 585} 586 587/* device driver definitions */ 588static int apmprobe (struct isa_device *); 589static int apmattach(struct isa_device *); 590struct isa_driver apmdriver = { 591 apmprobe, apmattach, "apm" }; 592 593/* 594 * probe APM (dummy): 595 * 596 * APM probing routine is placed on locore.s and apm_init.S because 597 * this process forces the CPU to turn to real mode or V86 mode. 598 * Current version uses real mode, but on future version, we want 599 * to use V86 mode in APM initialization. 600 */ 601 602static int 603apmprobe(struct isa_device *dvp) 604{ 605 int unit = dvp->id_unit; 606 607 switch (apm_version) { 608 case APMINI_CANTFIND: 609 /* silent */ 610 return 0; 611 case APMINI_NOT32BIT: 612 printf("apm%d: 32bit connection is not supported.\n", unit); 613 return 0; 614 case APMINI_CONNECTERR: 615 printf("apm%d: 32-bit connection error.\n", unit); 616 return 0; 617 } 618 619 return -1; 620} 621 622 623/* Process APM event */ 624static void 625apm_processevent(struct apm_softc *sc) 626{ 627 int apm_event; 628 629#ifdef APM_DEBUG 630# define OPMEV_DEBUGMESSAGE(symbol) case symbol: \ 631 printf("Original APM Event: " #symbol "\n"); 632#else 633# define OPMEV_DEBUGMESSAGE(symbol) case symbol: 634#endif 635 while (1) { 636 apm_event = apm_getevent(sc); 637 if (apm_event == PMEV_NOEVENT) 638 break; 639 switch (apm_event) { 640 OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ); 641 apm_suspend(); 642 break; 643 OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ); 644 apm_suspend(); 645 break; 646 OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ); 647 apm_suspend(); 648 break; 649 OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND); 650 apm_suspend(); 651 break; 652 OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME); 653 apm_resume(); 654 break; 655 OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME); 656 apm_resume(); 657 break; 658 OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME); 659 apm_resume(); 660 break; 661 OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW); 662 apm_battery_low(sc); 663 apm_suspend(); 664 break; 665 666 OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE); 667 break; 668 669 OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME); 670 inittodr(0); /* adjust time to RTC */ 671 break; 672 673 default: 674 printf("Unknown Original APM Event 0x%x\n", apm_event); 675 break; 676 } 677 } 678} 679 680/* 681 * Attach APM: 682 * 683 * Initialize APM driver (APM BIOS itself has been initialized in locore.s) 684 * 685 * Now, unless I'm mad, (not quite ruled out yet), the APM-1.1 spec is bogus: 686 * 687 * Appendix C says under the header "APM 1.0/APM 1.1 Modal BIOS Behavior" 688 * that "When an APM Driver connects with an APM 1.1 BIOS, the APM 1.1 BIOS 689 * will default to an APM 1.0 connection. After an APM Driver calls the APM 690 * Driver Version function, specifying that it supports APM 1.1, and [sic!] 691 * APM BIOS will change its behavior to an APM 1.1 connection. If the APM 692 * BIOS is an APM 1.0 BIOS, the APM Driver Version function call will fail, 693 * and the connection will remain an APM 1.0 connection." 694 * 695 * OK so I can establish a 1.0 connection, and then tell that I'm a 1.1 696 * and maybe then the BIOS will tell that it too is a 1.1. 697 * Fine. 698 * Now how will I ever get the segment-limits for instance ? There is no 699 * way I can see that I can get a 1.1 response back from an "APM Protected 700 * Mode 32-bit Interface Connect" function ??? 701 * 702 * Who made this, Intel and Microsoft ? -- How did you guess ! 703 * 704 * /phk 705 */ 706 707static int 708apmattach(struct isa_device *dvp) 709{ 710 int unit = dvp->id_unit; 711 char name[32]; 712#define APM_KERNBASE KERNBASE 713 struct apm_softc *sc = &apm_softc[unit]; 714 715 master_softc = sc; /* XXX */ 716 sc->initialized = 0; 717 sc->active = 0; 718 sc->halt_cpu = 1; 719 720 /* setup APM parameters */ 721 sc->cs16_base = (apm_cs32_base << 4) + APM_KERNBASE; 722 sc->cs32_base = (apm_cs16_base << 4) + APM_KERNBASE; 723 sc->ds_base = (apm_ds_base << 4) + APM_KERNBASE; 724 sc->cs_limit = apm_cs_limit; 725 sc->ds_limit = apm_ds_limit; 726 sc->cs_entry = apm_cs_entry; 727 728 sc->idle_cpu = ((apm_flags & APM_CPUIDLE_SLOW) != 0); 729 sc->disabled = ((apm_flags & APM_DISABLED) != 0); 730 sc->disengaged = ((apm_flags & APM_DISENGAGED) != 0); 731 732#ifdef APM_SLOWSTART 733 if (sc->idle_cpu) { 734 apm_slowstart = apm_slowstart_p = 1; 735 } 736#endif 737 738 /* print bootstrap messages */ 739#ifdef APM_DEBUG 740 printf(" found APM BIOS version %04x\n", apm_version); 741 printf("apm%d: Code32 0x%08x, Code16 0x%08x, Data 0x%08x\n", 742 unit, sc->cs32_base, sc->cs16_base, sc->ds_base); 743 printf("apm%d: Code entry 0x%08x, Idling CPU %s, Management %s\n", 744 unit, sc->cs_entry, is_enabled(sc->idle_cpu), 745 is_enabled(!sc->disabled)); 746 printf("apm%d: CS_limit=%x, DS_limit=%x\n", 747 unit, sc->cs_limit, sc->ds_limit); 748#endif /* APM_DEBUG */ 749 750 sc->cs_limit = 0xffff; 751 sc->ds_limit = 0xffff; 752 753 /* setup GDT */ 754 setup_apm_gdt(sc->cs32_base, sc->cs16_base, sc->ds_base, 755 sc->cs_limit, sc->ds_limit); 756 757 /* setup entry point 48bit pointer */ 758 apm_addr.segment = GSEL(GAPMCODE32_SEL, SEL_KPL); 759 apm_addr.offset = sc->cs_entry; 760 761 /* Try to kick bios into 1.1 mode */ 762 apm_driver_version(); 763 sc->minorversion = ((apm_version & 0x00f0) >> 4) * 10 + 764 ((apm_version & 0x000f) >> 0); 765 sc->majorversion = ((apm_version & 0xf000) >> 12) * 10 + 766 ((apm_version & 0x0f00) >> 8); 767 768 sc->intversion = INTVERSION(sc->majorversion, sc->minorversion); 769 770 if (sc->intversion >= INTVERSION(1, 1)) { 771 printf("apm%d: Engaged control %s\n", 772 unit, is_enabled(!sc->disengaged)); 773 } 774 775 printf(" found APM BIOS version %d.%d\n", 776 sc->majorversion, sc->minorversion); 777 printf("apm%d: Idling CPU %s\n", unit, is_enabled(sc->idle_cpu)); 778 779 /* enable power management */ 780 if (sc->disabled) { 781 if (apm_enable_disable_pm(sc, 1)) { 782 printf("Warning: APM enable function failed! [%x]\n", 783 apm_errno); 784 } 785 } 786 787 /* engage power managment (APM 1.1 or later) */ 788 if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) { 789 if (apm_engage_disengage_pm(sc, 1)) { 790 printf("Warning: APM engage function failed [%x]\n", 791 apm_errno); 792 } 793 } 794 795 /* default suspend hook */ 796 sc->sc_suspend.ah_fun = apm_default_suspend; 797 sc->sc_suspend.ah_arg = sc; 798 sc->sc_suspend.ah_name = "default suspend"; 799 sc->sc_suspend.ah_order = APM_MAX_ORDER; 800 801 /* default resume hook */ 802 sc->sc_resume.ah_fun = apm_default_resume; 803 sc->sc_resume.ah_arg = sc; 804 sc->sc_resume.ah_name = "default resume"; 805 sc->sc_resume.ah_order = APM_MIN_ORDER; 806 807 apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend); 808 apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume); 809 810 apm_event_enable(sc); 811 812 sc->initialized = 1; 813 814#ifdef DEVFS 815 sprintf(name,"apm%d",unit); 816 sc->sc_devfs_token = devfs_add_devsw( 817 "/", name, &apm_cdevsw, unit, DV_CHR, 0, 0, 0600); 818#endif 819 return 0; 820} 821 822static int 823apmopen(dev_t dev, int flag, int fmt, struct proc *p) 824{ 825 struct apm_softc *sc = &apm_softc[minor(dev)]; 826 827 if (minor(dev) >= NAPM) { 828 return (ENXIO); 829 } 830 if (!sc->initialized) { 831 return ENXIO; 832 } 833 return 0; 834} 835 836static int 837apmclose(dev_t dev, int flag, int fmt, struct proc *p) 838{ 839 return 0; 840} 841 842static int 843apmioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p) 844{ 845 struct apm_softc *sc = &apm_softc[minor(dev)]; 846 int error = 0; 847 848#ifdef APM_DEBUG 849 printf("APM ioctl: minor = %d, cmd = 0x%x\n", minor(dev), cmd); 850#endif 851 852 if (minor(dev) >= NAPM) { 853 return ENXIO; 854 } 855 if (!sc->initialized) { 856 return ENXIO; 857 } 858 switch (cmd) { 859 case APMIO_SUSPEND: 860 apm_suspend(); 861 break; 862 case APMIO_GETINFO: 863 if (apm_get_info(sc, (apm_info_t)addr)) { 864 error = ENXIO; 865 } 866 break; 867 case APMIO_ENABLE: 868 apm_event_enable(sc); 869 break; 870 case APMIO_DISABLE: 871 apm_event_disable(sc); 872 break; 873 case APMIO_HALTCPU: 874 apm_halt_cpu(sc); 875 break; 876 case APMIO_NOTHALTCPU: 877 apm_not_halt_cpu(sc); 878 break; 879 case APMIO_DISPLAYOFF: 880 if (apm_display_off()) { 881 error = ENXIO; 882 } 883 break; 884 default: 885 error = EINVAL; 886 break; 887 } 888 return error; 889} 890 891 892static apm_devsw_installed = 0; 893 894static void 895apm_drvinit(void *unused) 896{ 897 dev_t dev; 898 899 if( ! apm_devsw_installed ) { 900 dev = makedev(CDEV_MAJOR,0); 901 cdevsw_add(&dev,&apm_cdevsw,NULL); 902 apm_devsw_installed = 1; 903 } 904} 905 906SYSINIT(apmdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,apm_drvinit,NULL) 907