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