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