apm.c revision 17097
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 * Copyright (c) 1996 Nate Williams <nate@FreeBSD.org> 7 * 8 * This software may be used, modified, copied, and distributed, in 9 * both source and binary form provided that the above copyright and 10 * these terms are retained. Under no circumstances is the author 11 * responsible for the proper functioning of this software, nor does 12 * the author assume any responsibility for damages incurred with its 13 * use. 14 * 15 * Sep, 1994 Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD) 16 * 17 * $Id: apm.c,v 1.45 1996/07/10 15:09:46 nate Exp $ 18 */ 19 20#include "apm.h" 21#if NAPM > 1 22#error only one APM device may be configured 23#endif 24 25#include <sys/param.h> 26#include <sys/conf.h> 27#include <sys/kernel.h> 28#ifdef DEVFS 29#include <sys/devfsext.h> 30#endif /*DEVFS*/ 31#include <sys/systm.h> 32#include <sys/malloc.h> 33#include <sys/ioctl.h> 34#include <sys/file.h> 35#include <sys/proc.h> 36#include <sys/vnode.h> 37#include "i386/isa/isa_device.h" 38#include <machine/apm_bios.h> 39#include <machine/segments.h> 40#include <machine/clock.h> 41#include <vm/vm.h> 42#include <vm/vm_param.h> 43#include <vm/pmap.h> 44#include <sys/syslog.h> 45#include <sys/devconf.h> 46#include "apm_setup.h" 47 48static int apm_display_off __P((void)); 49static int apm_int __P((u_long *eax, u_long *ebx, u_long *ecx)); 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 cs_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 kern_devconf kdc_apm = { 70 0, 0, 0, /* filled in by dev_attach */ 71 "apm", 0, { MDDT_ISA, 0 }, 72 isa_generic_externalize, 0, 0, ISA_EXTERNALLEN, 73 &kdc_isa0, /* parent */ 74 0, /* parentdata */ 75 DC_UNCONFIGURED, /* state */ 76 "Advanced Power Management BIOS", 77 DC_CLS_MISC /* class */ 78}; 79 80 81static struct apm_softc apm_softc; 82static struct apmhook *hook[NAPM_HOOK]; /* XXX */ 83 84#define is_enabled(foo) ((foo) ? "enabled" : "disabled") 85 86/* Map version number to integer (keeps ordering of version numbers) */ 87#define INTVERSION(major, minor) ((major)*100 + (minor)) 88 89static timeout_t apm_timeout; 90static d_open_t apmopen; 91static d_close_t apmclose; 92static d_ioctl_t apmioctl; 93 94#define CDEV_MAJOR 39 95static struct cdevsw apm_cdevsw = 96 { apmopen, apmclose, noread, nowrite, /*39*/ 97 apmioctl, nostop, nullreset, nodevtotty,/* APM */ 98 seltrue, nommap, NULL , "apm" ,NULL, -1}; 99 100static void 101apm_registerdev(struct isa_device *id) 102{ 103 if (kdc_apm.kdc_isa) 104 return; 105 kdc_apm.kdc_state = DC_UNCONFIGURED; 106 kdc_apm.kdc_unit = 0; 107 kdc_apm.kdc_isa = id; 108 dev_attach(&kdc_apm); 109} 110 111/* setup APM GDT discriptors */ 112static void 113setup_apm_gdt(u_int code32_base, u_int code16_base, u_int data_base, u_int code_limit, u_int data_limit) 114{ 115 /* setup 32bit code segment */ 116 gdt_segs[GAPMCODE32_SEL].ssd_base = code32_base; 117 gdt_segs[GAPMCODE32_SEL].ssd_limit = code_limit; 118 119 /* setup 16bit code segment */ 120 gdt_segs[GAPMCODE16_SEL].ssd_base = code16_base; 121 gdt_segs[GAPMCODE16_SEL].ssd_limit = code_limit; 122 123 /* setup data segment */ 124 gdt_segs[GAPMDATA_SEL ].ssd_base = data_base; 125 gdt_segs[GAPMDATA_SEL ].ssd_limit = data_limit; 126 127 /* reflect these changes on physical GDT */ 128 ssdtosd(gdt_segs + GAPMCODE32_SEL, &gdt[GAPMCODE32_SEL].sd); 129 ssdtosd(gdt_segs + GAPMCODE16_SEL, &gdt[GAPMCODE16_SEL].sd); 130 ssdtosd(gdt_segs + GAPMDATA_SEL , &gdt[GAPMDATA_SEL ].sd); 131} 132 133/* 48bit far pointer */ 134static struct addr48 { 135 u_long offset; 136 u_short segment; 137} apm_addr; 138 139static int apm_errno; 140 141inline 142int 143apm_int(u_long *eax, u_long *ebx, u_long *ecx) 144{ 145 u_long cf; 146 __asm __volatile(" 147 pushfl 148 cli 149 lcall _apm_addr 150 movl $0, %3 151 jnc 1f 152 incl %3 153 1: 154 popfl 155 " 156 : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=D" (cf) 157 : "0" (*eax), "1" (*ebx), "2" (*ecx) 158 : "dx", "si", "memory" 159 ); 160 apm_errno = ((*eax) >> 8) & 0xff; 161 return cf; 162} 163 164 165/* enable/disable power management */ 166static int 167apm_enable_disable_pm(struct apm_softc *sc, int enable) 168{ 169 u_long eax, ebx, ecx; 170 171 eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM; 172 173 if (sc->intversion >= INTVERSION(1, 1)) { 174 ebx = PMDV_ALLDEV; 175 } else { 176 ebx = 0xffff; /* APM version 1.0 only */ 177 } 178 ecx = enable; 179 return apm_int(&eax, &ebx, &ecx); 180} 181 182/* Tell APM-BIOS that WE will do 1.1 and see what they say... */ 183static void 184apm_driver_version(void) 185{ 186 u_long eax, ebx, ecx; 187 188 eax = (APM_BIOS << 8) | APM_DRVVERSION; 189 ebx = 0x0; 190 ecx = 0x0101; 191 if(!apm_int(&eax, &ebx, &ecx)) 192 apm_version = eax & 0xffff; 193} 194 195/* engage/disengage power management (APM 1.1 or later) */ 196static int 197apm_engage_disengage_pm(struct apm_softc *sc, int engage) 198{ 199 u_long eax, ebx, ecx; 200 201 eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM; 202 ebx = PMDV_ALLDEV; 203 ecx = engage; 204 return(apm_int(&eax, &ebx, &ecx)); 205} 206 207/* get PM event */ 208static u_int 209apm_getevent(struct apm_softc *sc) 210{ 211 u_long eax, ebx, ecx; 212 213 eax = (APM_BIOS << 8) | APM_GETPMEVENT; 214 215 ebx = 0; 216 ecx = 0; 217 if (apm_int(&eax, &ebx, &ecx)) 218 return PMEV_NOEVENT; 219 220 return ebx & 0xffff; 221} 222 223/* suspend entire system */ 224static int 225apm_suspend_system(struct apm_softc *sc) 226{ 227 u_long eax, ebx, ecx; 228 229 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 230 ebx = PMDV_ALLDEV; 231 ecx = PMST_SUSPEND; 232 233 if (apm_int(&eax, &ebx, &ecx)) { 234 printf("Entire system suspend failure: errcode = %ld\n", 235 0xff & (eax >> 8)); 236 return 1; 237 } 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#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 } 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#ifdef APM_DEBUG 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/* disestablish an apm hook */ 358void 359apm_hook_disestablish(int apmh, struct apmhook *ah) 360{ 361 if (apmh < 0 || apmh >= NAPM_HOOK) 362 return; 363 364 apm_del_hook(&hook[apmh], ah); 365} 366 367 368static struct timeval suspend_time; 369static struct timeval diff_time; 370 371static int 372apm_default_resume(void *arg) 373{ 374 int pl; 375 u_int second, minute, hour; 376 struct timeval resume_time, tmp_time; 377 378 /* modified for adjkerntz */ 379 pl = splsoftclock(); 380 inittodr(0); /* adjust time to RTC */ 381 microtime(&resume_time); 382 tmp_time = time; /* because 'time' is volatile */ 383 timevaladd(&tmp_time, &diff_time); 384 time = tmp_time; 385 splx(pl); 386 second = resume_time.tv_sec - suspend_time.tv_sec; 387 hour = second / 3600; 388 second %= 3600; 389 minute = second / 60; 390 second %= 60; 391 log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n", 392 hour, minute, second); 393 return 0; 394} 395 396static int 397apm_default_suspend(void *arg) 398{ 399 int pl; 400 401 pl = splsoftclock(); 402 microtime(&diff_time); 403 inittodr(0); 404 microtime(&suspend_time); 405 timevalsub(&diff_time, &suspend_time); 406 splx(pl); 407 return 0; 408} 409 410static void apm_processevent(struct apm_softc *); 411 412/* 413 * Public interface to the suspend/resume: 414 * 415 * Execute suspend and resume hook before and after sleep, respectively. 416 * 417 */ 418 419void 420apm_suspend(void) 421{ 422 struct apm_softc *sc = &apm_softc; 423 424 if (!sc) 425 return; 426 427 if (sc->initialized) { 428 apm_execute_hook(hook[APM_HOOK_SUSPEND]); 429 apm_suspend_system(sc); 430 apm_processevent(sc); 431 } 432} 433 434void 435apm_resume(void) 436{ 437 struct apm_softc *sc = &apm_softc; 438 439 if (!sc) 440 return; 441 442 if (sc->initialized) { 443 apm_execute_hook(hook[APM_HOOK_RESUME]); 444 } 445} 446 447 448/* get APM information */ 449static int 450apm_get_info(struct apm_softc *sc, apm_info_t aip) 451{ 452 u_long eax, ebx, ecx; 453 454 eax = (APM_BIOS << 8) | APM_GETPWSTATUS; 455 ebx = PMDV_ALLDEV; 456 ecx = 0; 457 458 if (apm_int(&eax, &ebx, &ecx)) 459 return 1; 460 461 aip->ai_acline = (ebx >> 8) & 0xff; 462 aip->ai_batt_stat = ebx & 0xff; 463 aip->ai_batt_life = ecx & 0xff; 464 aip->ai_major = (u_int)sc->majorversion; 465 aip->ai_minor = (u_int)sc->minorversion; 466 aip->ai_status = (u_int)sc->active; 467 468 return 0; 469} 470 471 472/* inform APM BIOS that CPU is idle */ 473void 474apm_cpu_idle(void) 475{ 476 struct apm_softc *sc = &apm_softc; 477 478 if (sc->active) { 479 u_long eax, ebx, ecx; 480 481 eax = (APM_BIOS <<8) | APM_CPUIDLE; 482 ecx = ebx = 0; 483 apm_int(&eax, &ebx, &ecx); 484 } 485 /* 486 * Some APM implementation halts CPU in BIOS, whenever 487 * "CPU-idle" function are invoked, but swtch() of 488 * FreeBSD halts CPU, therefore, CPU is halted twice 489 * in the sched loop. It makes the interrupt latency 490 * terribly long and be able to cause a serious problem 491 * in interrupt processing. We prevent it by removing 492 * "hlt" operation from swtch() and managed it under 493 * APM driver. 494 */ 495 if (!sc->active || sc->always_halt_cpu) { 496 __asm("hlt"); /* wait for interrupt */ 497 } 498} 499 500/* inform APM BIOS that CPU is busy */ 501void 502apm_cpu_busy(void) 503{ 504 struct apm_softc *sc = &apm_softc; 505 506 /* 507 * The APM specification says this is only necessary if your BIOS 508 * slows down the processor in the idle task, otherwise it's not 509 * necessary. 510 */ 511 if (sc->slow_idle_cpu && sc->active) { 512 u_long eax, ebx, ecx; 513 514 eax = (APM_BIOS <<8) | APM_CPUBUSY; 515 ecx = ebx = 0; 516 apm_int(&eax, &ebx, &ecx); 517 } 518} 519 520 521/* 522 * APM timeout routine: 523 * 524 * This routine is automatically called by timer once per second. 525 */ 526 527static void 528apm_timeout(void *arg) 529{ 530 struct apm_softc *sc = arg; 531 532 apm_processevent(sc); 533 if (sc->active == 1) { 534 timeout(apm_timeout, (void *)sc, hz - 1 ); /* More than 1 Hz */ 535 } 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->always_halt_cpu = 1; 570 } 571} 572 573/* don't halt CPU in scheduling loop */ 574static void 575apm_not_halt_cpu(struct apm_softc *sc) 576{ 577 if (sc->initialized) { 578 sc->always_halt_cpu = 0; 579 } 580} 581 582/* device driver definitions */ 583static int apmprobe (struct isa_device *); 584static int apmattach(struct isa_device *); 585struct isa_driver apmdriver = { 586 apmprobe, apmattach, "apm" }; 587 588/* 589 * probe APM (dummy): 590 * 591 * APM probing routine is placed on locore.s and apm_init.S because 592 * this process forces the CPU to turn to real mode or V86 mode. 593 * Current version uses real mode, but on future version, we want 594 * to use V86 mode in APM initialization. 595 */ 596 597static int 598apmprobe(struct isa_device *dvp) 599{ 600 if ( dvp->id_unit > 0 ) { 601 printf("apm: Only one APM driver supported.\n"); 602 return 0; 603 } 604 apm_registerdev(dvp); 605 switch (apm_version) { 606 case APMINI_CANTFIND: 607 /* silent */ 608 return 0; 609 case APMINI_NOT32BIT: 610 printf("apm: 32bit connection is not supported.\n"); 611 return 0; 612 case APMINI_CONNECTERR: 613 printf("apm: 32-bit connection error.\n"); 614 return 0; 615 } 616#ifdef APM_BROKEN_STATCLOCK 617 statclock_disable = 1; 618#endif 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("Received APM Event: " #symbol "\n"); 633#else 634# define OPMEV_DEBUGMESSAGE(symbol) case symbol: 635#endif 636 do { 637 apm_event = apm_getevent(sc); 638 switch (apm_event) { 639 OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ); 640 apm_suspend(); 641 break; 642 OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ); 643 apm_suspend(); 644 break; 645 OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ); 646 apm_suspend(); 647 break; 648 OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND); 649 apm_suspend(); 650 break; 651 OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME); 652 apm_resume(); 653 break; 654 OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME); 655 apm_resume(); 656 break; 657 OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME); 658 apm_resume(); 659 break; 660 OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW); 661 apm_battery_low(sc); 662 apm_suspend(); 663 break; 664 OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE); 665 break; 666 OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME); 667 inittodr(0); /* adjust time to RTC */ 668 break; 669 OPMEV_DEBUGMESSAGE(PMEV_NOEVENT); 670 break; 671 default: 672 printf("Unknown Original APM Event 0x%x\n", apm_event); 673 break; 674 } 675 } while (apm_event != PMEV_NOEVENT); 676} 677 678/* 679 * Attach APM: 680 * 681 * Initialize APM driver (APM BIOS itself has been initialized in locore.s) 682 */ 683 684static int 685apmattach(struct isa_device *dvp) 686{ 687#define APM_KERNBASE KERNBASE 688 struct apm_softc *sc = &apm_softc; 689#ifdef APM_DSVALUE_BUG 690 caddr_t apm_bios_work; 691 692 /* 693 * XXX - Malloc enough space for the APM DS, and then copy the 694 * current DS into the new space since the DS setup by the 695 * APM bios is going to get wiped out. 696 */ 697 apm_bios_work = (caddr_t)malloc(apm_ds_limit, M_DEVBUF, M_NOWAIT); 698 bcopy((caddr_t)((apm_ds_base << 4) + APM_KERNBASE), apm_bios_work, 699 apm_ds_limit); 700#endif /* APM_DSVALUE_BUG */ 701 702 sc->initialized = 0; 703 704 /* Must be externally enabled */ 705 sc->active = 0; 706 707 /* setup APM parameters */ 708 sc->cs16_base = (apm_cs16_base << 4) + APM_KERNBASE; 709 sc->cs32_base = (apm_cs32_base << 4) + APM_KERNBASE; 710 sc->ds_base = (apm_ds_base << 4) + APM_KERNBASE; 711 sc->cs_limit = apm_cs_limit; 712 sc->ds_limit = apm_ds_limit; 713 sc->cs_entry = apm_cs_entry; 714 715#ifdef APM_DSVALUE_BUG 716 /* Set the DS base to point to the newly made copy of the APM DS */ 717 sc->ds_base = (u_int)apm_bios_work; 718#endif /* APM_DSVALUE_BUG */ 719 720 /* Always call HLT in idle loop */ 721 sc->always_halt_cpu = 1; 722 723 sc->slow_idle_cpu = ((apm_flags & APM_CPUIDLE_SLOW) != 0); 724 sc->disabled = ((apm_flags & APM_DISABLED) != 0); 725 sc->disengaged = ((apm_flags & APM_DISENGAGED) != 0); 726 727 /* print bootstrap messages */ 728#ifdef APM_DEBUG 729 printf("apm: APM BIOS version %04x\n", apm_version); 730 printf("apm: Code32 0x%08x, Code16 0x%08x, Data 0x%08x\n", 731 sc->cs32_base, sc->cs16_base, sc->ds_base); 732 printf("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n", 733 sc->cs_entry, is_enabled(sc->slow_idle_cpu), 734 is_enabled(!sc->disabled)); 735 printf("apm: CS_limit=0x%x, DS_limit=0x%x\n", 736 sc->cs_limit, sc->ds_limit); 737#endif /* APM_DEBUG */ 738 739#ifdef 0 740 /* Workaround for some buggy APM BIOS implementations */ 741 sc->cs_limit = 0xffff; 742 sc->ds_limit = 0xffff; 743#endif 744 745 /* setup GDT */ 746 setup_apm_gdt(sc->cs32_base, sc->cs16_base, sc->ds_base, 747 sc->cs_limit, sc->ds_limit); 748 749 /* setup entry point 48bit pointer */ 750 apm_addr.segment = GSEL(GAPMCODE32_SEL, SEL_KPL); 751 apm_addr.offset = sc->cs_entry; 752 753#ifdef FORCE_APM10 754 apm_version = 0x100; 755 sc->majorversion = 1; 756 sc->minorversion = 0; 757 sc->intversion = INTVERSION(sc->majorversion, sc->minorversion); 758 printf("apm: running in APM 1.0 compatible mode\n"); 759 kcd_apm.kdc_description = 760 "Advanced Power Management BIOS (1.0 compatability mode)", 761#else 762 /* Try to kick bios into 1.1 or greater 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#ifdef APM_DEBUG 773 printf("apm: Engaged control %s\n", is_enabled(!sc->disengaged)); 774#endif 775 } 776 777 printf("apm: found APM BIOS version %d.%d\n", 778 sc->majorversion, sc->minorversion); 779#endif /* FORCE_APM10 */ 780 781#ifdef APM_DEBUG 782 printf("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu)); 783#endif 784 785 /* enable power management */ 786 if (sc->disabled) { 787 if (apm_enable_disable_pm(sc, 1)) { 788#ifdef APM_DEBUG 789 printf("apm: *Warning* enable function failed! [%x]\n", 790 apm_errno); 791#endif 792 } 793 } 794 795 /* engage power managment (APM 1.1 or later) */ 796 if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) { 797 if (apm_engage_disengage_pm(sc, 1)) { 798#ifdef APM_DEBUG 799 printf("apm: *Warning* engage function failed err=[%x]", 800 apm_errno); 801 printf(" (Docked or using external power?).\n"); 802#endif 803 } 804 } 805 806 /* default suspend hook */ 807 sc->sc_suspend.ah_fun = apm_default_suspend; 808 sc->sc_suspend.ah_arg = sc; 809 sc->sc_suspend.ah_name = "default suspend"; 810 sc->sc_suspend.ah_order = APM_MAX_ORDER; 811 812 /* default resume hook */ 813 sc->sc_resume.ah_fun = apm_default_resume; 814 sc->sc_resume.ah_arg = sc; 815 sc->sc_resume.ah_name = "default resume"; 816 sc->sc_resume.ah_order = APM_MIN_ORDER; 817 818 apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend); 819 apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume); 820 821 apm_event_enable(sc); 822 kdc_apm.kdc_state = DC_IDLE; 823 824 sc->initialized = 1; 825 826#ifdef DEVFS 827 sc->sc_devfs_token = 828 devfs_add_devswf(&apm_cdevsw, 0, DV_CHR, 0, 0, 0600, "apm"); 829#endif 830 return 0; 831} 832 833static int 834apmopen(dev_t dev, int flag, int fmt, struct proc *p) 835{ 836 struct apm_softc *sc = &apm_softc; 837 838 if (minor(dev) != 0 || !sc->initialized) 839 return (ENXIO); 840 841 return 0; 842} 843 844static int 845apmclose(dev_t dev, int flag, int fmt, struct proc *p) 846{ 847 return 0; 848} 849 850static int 851apmioctl(dev_t dev, int cmd, caddr_t addr, int flag, struct proc *p) 852{ 853 struct apm_softc *sc = &apm_softc; 854 int error = 0; 855 856 if (minor(dev) != 0 || !sc->initialized) 857 return (ENXIO); 858#ifdef APM_DEBUG 859 printf("APM ioctl: cmd = 0x%x\n", cmd); 860#endif 861 switch (cmd) { 862 case APMIO_SUSPEND: 863 if ( sc->active) { 864 apm_suspend(); 865 } else { 866 error = EINVAL; 867 } 868 break; 869 case APMIO_GETINFO: 870 if (apm_get_info(sc, (apm_info_t)addr)) { 871 error = ENXIO; 872 } 873 break; 874 case APMIO_ENABLE: 875 kdc_apm.kdc_state = DC_BUSY; 876 apm_event_enable(sc); 877 break; 878 case APMIO_DISABLE: 879 kdc_apm.kdc_state = DC_IDLE; 880 apm_event_disable(sc); 881 break; 882 case APMIO_HALTCPU: 883 apm_halt_cpu(sc); 884 break; 885 case APMIO_NOTHALTCPU: 886 apm_not_halt_cpu(sc); 887 break; 888 case APMIO_DISPLAYOFF: 889 if (apm_display_off()) { 890 error = ENXIO; 891 } 892 break; 893 default: 894 error = EINVAL; 895 break; 896 } 897 return error; 898} 899 900 901static apm_devsw_installed = 0; 902 903static void 904apm_drvinit(void *unused) 905{ 906 dev_t dev; 907 908 if( ! apm_devsw_installed ) { 909 dev = makedev(CDEV_MAJOR,0); 910 cdevsw_add(&dev,&apm_cdevsw,NULL); 911 apm_devsw_installed = 1; 912 } 913} 914 915SYSINIT(apmdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,apm_drvinit,NULL) 916