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