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