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