apm.c revision 49185
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@jp.FreeBSD.org> 6 * Copyright (c) 1996 Nate Williams <nate@FreeBSD.org> 7 * Copyright (c) 1997 Poul-Henning Kamp <phk@FreeBSD.org> 8 * 9 * This software may be used, modified, copied, and distributed, in 10 * both source and binary form provided that the above copyright and 11 * these terms are retained. Under no circumstances is the author 12 * responsible for the proper functioning of this software, nor does 13 * the author assume any responsibility for damages incurred with its 14 * use. 15 * 16 * Sep, 1994 Implemented on FreeBSD 1.1.5.1R (Toshiba AVS001WD) 17 * 18 * $Id: apm.c,v 1.92 1999/07/28 19:37:32 msmith Exp $ 19 */ 20 21#include "opt_devfs.h" 22#include "opt_smp.h" 23 24#include <sys/param.h> 25#include <sys/systm.h> 26#include <sys/conf.h> 27#include <sys/kernel.h> 28#ifdef DEVFS 29#include <sys/devfsext.h> 30#endif /*DEVFS*/ 31#include <sys/time.h> 32#include <sys/reboot.h> 33#include <sys/bus.h> 34#include <sys/select.h> 35#include <sys/poll.h> 36#include <sys/fcntl.h> 37#include <sys/proc.h> 38#include <sys/uio.h> 39#include <sys/signalvar.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 48#include <machine/psl.h> 49#include <machine/vm86.h> 50 51#ifdef SMP 52#include <machine/smp.h> 53#endif 54 55static int apm_display __P((int newstate)); 56static int apm_int __P((u_long *eax, u_long *ebx, u_long *ecx, u_long *edx)); 57static void apm_resume __P((void)); 58 59extern int apm_bios_call __P((struct apm_bios_arg *)); /* in apm_setup.s */ 60 61static u_long apm_version; 62static u_long apm_cs_entry; 63static u_short apm_cs32_base; 64static u_short apm_cs16_base; 65static u_short apm_ds_base; 66static u_short apm_cs32_limit; 67static u_short apm_cs16_limit; 68static u_short apm_ds_limit; 69static u_short apm_flags; 70 71#define APM_NEVENTS 16 72#define APM_NPMEV 13 73 74int apm_evindex; 75 76/* static data */ 77struct apm_softc { 78 int initialized, active; 79 int always_halt_cpu, slow_idle_cpu; 80 int disabled, disengaged; 81 u_int minorversion, majorversion; 82 u_int cs32_base, cs16_base, ds_base; 83 u_int cs16_limit, cs32_limit, ds_limit; 84 u_int cs_entry; 85 u_int intversion; 86 struct apmhook sc_suspend; 87 struct apmhook sc_resume; 88 struct selinfo sc_rsel; 89 int sc_flags; 90 int event_count; 91 int event_ptr; 92 struct apm_event_info event_list[APM_NEVENTS]; 93 u_char event_filter[APM_NPMEV]; 94#ifdef DEVFS 95 void *sc_devfs_token; 96#endif 97}; 98#define SCFLAG_ONORMAL 0x0000001 99#define SCFLAG_OCTL 0x0000002 100#define SCFLAG_OPEN (SCFLAG_ONORMAL|SCFLAG_OCTL) 101 102#define APMDEV(dev) (minor(dev)&0x0f) 103#define APMDEV_NORMAL 0 104#define APMDEV_CTL 8 105 106static struct apm_softc apm_softc; 107static struct apmhook *hook[NAPM_HOOK]; /* XXX */ 108 109#define is_enabled(foo) ((foo) ? "enabled" : "disabled") 110 111/* Map version number to integer (keeps ordering of version numbers) */ 112#define INTVERSION(major, minor) ((major)*100 + (minor)) 113 114static struct callout_handle apm_timeout_ch = 115 CALLOUT_HANDLE_INITIALIZER(&apm_timeout_ch); 116 117static timeout_t apm_timeout; 118static d_open_t apmopen; 119static d_close_t apmclose; 120static d_write_t apmwrite; 121static d_ioctl_t apmioctl; 122static d_poll_t apmpoll; 123 124#define CDEV_MAJOR 39 125static struct cdevsw apm_cdevsw = { 126 /* open */ apmopen, 127 /* close */ apmclose, 128 /* read */ noread, 129 /* write */ apmwrite, 130 /* ioctl */ apmioctl, 131 /* stop */ nostop, 132 /* reset */ noreset, 133 /* devtotty */ nodevtotty, 134 /* poll */ apmpoll, 135 /* mmap */ nommap, 136 /* strategy */ nostrategy, 137 /* name */ "apm", 138 /* parms */ noparms, 139 /* maj */ CDEV_MAJOR, 140 /* dump */ nodump, 141 /* psize */ nopsize, 142 /* flags */ 0, 143 /* maxio */ 0, 144 /* bmaj */ -1 145}; 146 147/* setup APM GDT discriptors */ 148static void 149setup_apm_gdt(u_int code32_base, u_int code16_base, u_int data_base, u_int code32_limit, u_int code16_limit, u_int data_limit) 150{ 151#ifdef SMP 152 int x; 153#endif 154 155 /* setup 32bit code segment */ 156 gdt_segs[GAPMCODE32_SEL].ssd_base = code32_base; 157 gdt_segs[GAPMCODE32_SEL].ssd_limit = code32_limit; 158 159 /* setup 16bit code segment */ 160 gdt_segs[GAPMCODE16_SEL].ssd_base = code16_base; 161 gdt_segs[GAPMCODE16_SEL].ssd_limit = code16_limit; 162 163 /* setup data segment */ 164 gdt_segs[GAPMDATA_SEL ].ssd_base = data_base; 165 gdt_segs[GAPMDATA_SEL ].ssd_limit = data_limit; 166 167 /* reflect these changes on physical GDT */ 168 ssdtosd(gdt_segs + GAPMCODE32_SEL, &gdt[GAPMCODE32_SEL].sd); 169 ssdtosd(gdt_segs + GAPMCODE16_SEL, &gdt[GAPMCODE16_SEL].sd); 170 ssdtosd(gdt_segs + GAPMDATA_SEL , &gdt[GAPMDATA_SEL ].sd); 171 172#ifdef SMP 173 for (x = 1; x < NCPU; x++) { 174 gdt[x * NGDT + GAPMCODE32_SEL].sd = gdt[GAPMCODE32_SEL].sd; 175 gdt[x * NGDT + GAPMCODE16_SEL].sd = gdt[GAPMCODE16_SEL].sd; 176 gdt[x * NGDT + GAPMDATA_SEL ].sd = gdt[GAPMDATA_SEL ].sd; 177 } 178#endif 179} 180 181/* 48bit far pointer. Do not staticize - used from apm_setup.s */ 182struct addr48 { 183 u_long offset; 184 u_short segment; 185} apm_addr; 186 187static int apm_errno; 188 189static int 190apm_int(u_long *eax, u_long *ebx, u_long *ecx, u_long *edx) 191{ 192 struct apm_bios_arg apa; 193 int cf; 194 195 apa.eax = *eax; 196 apa.ebx = *ebx; 197 apa.ecx = *ecx; 198 apa.edx = *edx; 199 cf = apm_bios_call(&apa); 200 *eax = apa.eax; 201 *ebx = apa.ebx; 202 *ecx = apa.ecx; 203 *edx = apa.edx; 204 apm_errno = ((*eax) >> 8) & 0xff; 205 return cf; 206} 207 208 209/* enable/disable power management */ 210static int 211apm_enable_disable_pm(int enable) 212{ 213 struct apm_softc *sc = &apm_softc; 214 215 u_long eax, ebx, ecx, edx; 216 217 eax = (APM_BIOS << 8) | APM_ENABLEDISABLEPM; 218 219 if (sc->intversion >= INTVERSION(1, 1)) 220 ebx = PMDV_ALLDEV; 221 else 222 ebx = 0xffff; /* APM version 1.0 only */ 223 ecx = enable; 224 edx = 0; 225 return apm_int(&eax, &ebx, &ecx, &edx); 226} 227 228static void 229apm_driver_version(int version) 230{ 231 u_long eax, ebx, ecx, edx; 232 233 /* First try APM 1.2 */ 234 eax = (APM_BIOS << 8) | APM_DRVVERSION; 235 ebx = 0x0; 236 ecx = version; 237 edx = 0; 238 if(!apm_int(&eax, &ebx, &ecx, &edx)) 239 apm_version = eax & 0xffff; 240} 241 242/* engage/disengage power management (APM 1.1 or later) */ 243static int 244apm_engage_disengage_pm(int engage) 245{ 246 u_long eax, ebx, ecx, edx; 247 248 eax = (APM_BIOS << 8) | APM_ENGAGEDISENGAGEPM; 249 ebx = PMDV_ALLDEV; 250 ecx = engage; 251 edx = 0; 252 return(apm_int(&eax, &ebx, &ecx, &edx)); 253} 254 255/* get PM event */ 256static u_int 257apm_getevent(void) 258{ 259 u_long eax, ebx, ecx, edx; 260 261 eax = (APM_BIOS << 8) | APM_GETPMEVENT; 262 263 ebx = 0; 264 ecx = 0; 265 edx = 0; 266 if (apm_int(&eax, &ebx, &ecx, &edx)) 267 return PMEV_NOEVENT; 268 269 return ebx & 0xffff; 270} 271 272/* suspend entire system */ 273static int 274apm_suspend_system(int state) 275{ 276 u_long eax, ebx, ecx, edx; 277 278 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 279 ebx = PMDV_ALLDEV; 280 ecx = state; 281 edx = 0; 282 283 if (apm_int(&eax, &ebx, &ecx, &edx)) { 284 printf("Entire system suspend failure: errcode = %ld\n", 285 0xff & (eax >> 8)); 286 return 1; 287 } 288 return 0; 289} 290 291/* Display control */ 292/* 293 * Experimental implementation: My laptop machine can't handle this function 294 * If your laptop can control the display via APM, please inform me. 295 * HOSOKAWA, Tatsumi <hosokawa@jp.FreeBSD.org> 296 */ 297static int 298apm_display(int newstate) 299{ 300 u_long eax, ebx, ecx, edx; 301 302 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 303 ebx = PMDV_DISP0; 304 ecx = newstate ? PMST_APMENABLED:PMST_SUSPEND; 305 edx = 0; 306 if (apm_int(&eax, &ebx, &ecx, &edx)) { 307 printf("Display off failure: errcode = %ld\n", 308 0xff & (eax >> 8)); 309 return 1; 310 } 311 return 0; 312} 313 314/* 315 * Turn off the entire system. 316 */ 317static void 318apm_power_off(int howto, void *junk) 319{ 320 u_long eax, ebx, ecx, edx; 321 322 /* Not halting powering off, or not active */ 323 if (!(howto & RB_POWEROFF) || !apm_softc.active) 324 return; 325 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 326 ebx = PMDV_ALLDEV; 327 ecx = PMST_OFF; 328 edx = 0; 329 apm_int(&eax, &ebx, &ecx, &edx); 330} 331 332/* APM Battery low handler */ 333static void 334apm_battery_low(void) 335{ 336 printf("\007\007 * * * BATTERY IS LOW * * * \007\007"); 337} 338 339/* APM hook manager */ 340static struct apmhook * 341apm_add_hook(struct apmhook **list, struct apmhook *ah) 342{ 343 int s; 344 struct apmhook *p, *prev; 345 346#ifdef APM_DEBUG 347 printf("Add hook \"%s\"\n", ah->ah_name); 348#endif 349 350 s = splhigh(); 351 if (ah == NULL) 352 panic("illegal apm_hook!"); 353 prev = NULL; 354 for (p = *list; p != NULL; prev = p, p = p->ah_next) 355 if (p->ah_order > ah->ah_order) 356 break; 357 358 if (prev == NULL) { 359 ah->ah_next = *list; 360 *list = ah; 361 } else { 362 ah->ah_next = prev->ah_next; 363 prev->ah_next = ah; 364 } 365 splx(s); 366 return ah; 367} 368 369static void 370apm_del_hook(struct apmhook **list, struct apmhook *ah) 371{ 372 int s; 373 struct apmhook *p, *prev; 374 375 s = splhigh(); 376 prev = NULL; 377 for (p = *list; p != NULL; prev = p, p = p->ah_next) 378 if (p == ah) 379 goto deleteit; 380 panic("Tried to delete unregistered apm_hook."); 381 goto nosuchnode; 382deleteit: 383 if (prev != NULL) 384 prev->ah_next = p->ah_next; 385 else 386 *list = p->ah_next; 387nosuchnode: 388 splx(s); 389} 390 391 392/* APM driver calls some functions automatically */ 393static void 394apm_execute_hook(struct apmhook *list) 395{ 396 struct apmhook *p; 397 398 for (p = list; p != NULL; p = p->ah_next) { 399#ifdef APM_DEBUG 400 printf("Execute APM hook \"%s.\"\n", p->ah_name); 401#endif 402 if ((*(p->ah_fun))(p->ah_arg)) 403 printf("Warning: APM hook \"%s\" failed", p->ah_name); 404 } 405} 406 407 408/* establish an apm hook */ 409struct apmhook * 410apm_hook_establish(int apmh, struct apmhook *ah) 411{ 412 if (apmh < 0 || apmh >= NAPM_HOOK) 413 return NULL; 414 415 return apm_add_hook(&hook[apmh], ah); 416} 417 418/* disestablish an apm hook */ 419void 420apm_hook_disestablish(int apmh, struct apmhook *ah) 421{ 422 if (apmh < 0 || apmh >= NAPM_HOOK) 423 return; 424 425 apm_del_hook(&hook[apmh], ah); 426} 427 428 429static struct timeval suspend_time; 430static struct timeval diff_time; 431 432static int 433apm_default_resume(void *arg) 434{ 435 int pl; 436 u_int second, minute, hour; 437 struct timeval resume_time, tmp_time; 438 439 /* modified for adjkerntz */ 440 pl = splsoftclock(); 441 inittodr(0); /* adjust time to RTC */ 442 microtime(&resume_time); 443 getmicrotime(&tmp_time); 444 timevaladd(&tmp_time, &diff_time); 445 446#ifdef FIXME 447 /* XXX THIS DOESN'T WORK!!! */ 448 time = tmp_time; 449#endif 450 451#ifdef APM_FIXUP_CALLTODO 452 /* Calculate the delta time suspended */ 453 timevalsub(&resume_time, &suspend_time); 454 /* Fixup the calltodo list with the delta time. */ 455 adjust_timeout_calltodo(&resume_time); 456#endif /* APM_FIXUP_CALLTODOK */ 457 splx(pl); 458#ifndef APM_FIXUP_CALLTODO 459 second = resume_time.tv_sec - suspend_time.tv_sec; 460#else /* APM_FIXUP_CALLTODO */ 461 /* 462 * We've already calculated resume_time to be the delta between 463 * the suspend and the resume. 464 */ 465 second = resume_time.tv_sec; 466#endif /* APM_FIXUP_CALLTODO */ 467 hour = second / 3600; 468 second %= 3600; 469 minute = second / 60; 470 second %= 60; 471 log(LOG_NOTICE, "resumed from suspended mode (slept %02d:%02d:%02d)\n", 472 hour, minute, second); 473 return 0; 474} 475 476static int 477apm_default_suspend(void *arg) 478{ 479 int pl; 480 481 pl = splsoftclock(); 482 microtime(&diff_time); 483 inittodr(0); 484 microtime(&suspend_time); 485 timevalsub(&diff_time, &suspend_time); 486 splx(pl); 487 return 0; 488} 489 490static int apm_record_event __P((struct apm_softc *, u_int)); 491static void apm_processevent(void); 492 493static u_int apm_op_inprog = 0; 494 495static void 496apm_lastreq_notify(void) 497{ 498 u_long eax, ebx, ecx, edx; 499 500 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 501 ebx = PMDV_ALLDEV; 502 ecx = PMST_LASTREQNOTIFY; 503 edx = 0; 504 505 apm_int(&eax, &ebx, &ecx, &edx); 506} 507 508static int 509apm_lastreq_rejected(void) 510{ 511 u_long eax, ebx, ecx, edx; 512 513 if (apm_op_inprog == 0) { 514 return 1; /* no operation in progress */ 515 } 516 517 eax = (APM_BIOS << 8) | APM_SETPWSTATE; 518 ebx = PMDV_ALLDEV; 519 ecx = PMST_LASTREQREJECT; 520 edx = 0; 521 522 if (apm_int(&eax, &ebx, &ecx, &edx)) { 523#ifdef APM_DEBUG 524 printf("apm_lastreq_rejected: failed\n"); 525#endif 526 return 1; 527 } 528 529 apm_op_inprog = 0; 530 531 return 0; 532} 533 534/* 535 * Public interface to the suspend/resume: 536 * 537 * Execute suspend and resume hook before and after sleep, respectively. 538 * 539 */ 540 541void 542apm_suspend(int state) 543{ 544 struct apm_softc *sc = &apm_softc; 545 int error; 546 547 if (!sc) 548 return; 549 550 apm_op_inprog = 0; 551 552 if (sc->initialized) { 553 error = DEVICE_SUSPEND(root_bus); 554 /* 555 * XXX Shouldn't ignore the error like this, but should 556 * instead fix the newbus code. Until that happens, 557 * I'm doing this to get suspend working again. 558 */ 559 if (error) 560 printf("DEVICE_SUSPEND error %d, ignored\n", error); 561 apm_execute_hook(hook[APM_HOOK_SUSPEND]); 562 if (apm_suspend_system(state) == 0) 563 apm_processevent(); 564 else 565 /* Failure, 'resume' the system again */ 566 apm_execute_hook(hook[APM_HOOK_RESUME]); 567 } 568} 569 570void 571apm_resume(void) 572{ 573 struct apm_softc *sc = &apm_softc; 574 575 if (!sc) 576 return; 577 578 if (sc->initialized) { 579 DEVICE_RESUME(root_bus); 580 apm_execute_hook(hook[APM_HOOK_RESUME]); 581 } 582} 583 584 585/* get APM information */ 586static int 587apm_get_info(apm_info_t aip) 588{ 589 struct apm_softc *sc = &apm_softc; 590 u_long eax, ebx, ecx, edx; 591 592 eax = (APM_BIOS << 8) | APM_GETPWSTATUS; 593 ebx = PMDV_ALLDEV; 594 ecx = 0; 595 edx = 0xffff; /* default to unknown battery time */ 596 597 if (apm_int(&eax, &ebx, &ecx, &edx)) 598 return 1; 599 600 aip->ai_infoversion = 1; 601 aip->ai_acline = (ebx >> 8) & 0xff; 602 aip->ai_batt_stat = ebx & 0xff; 603 aip->ai_batt_life = ecx & 0xff; 604 aip->ai_major = (u_int)sc->majorversion; 605 aip->ai_minor = (u_int)sc->minorversion; 606 aip->ai_status = (u_int)sc->active; 607 edx &= 0xffff; 608 if (edx == 0xffff) /* Time is unknown */ 609 aip->ai_batt_time = -1; 610 else if (edx & 0x8000) /* Time is in minutes */ 611 aip->ai_batt_time = (edx & 0x7fff) * 60; 612 else /* Time is in seconds */ 613 aip->ai_batt_time = edx; 614 615 eax = (APM_BIOS << 8) | APM_GETCAPABILITIES; 616 ebx = 0; 617 ecx = 0; 618 edx = 0; 619 if (apm_int(&eax, &ebx, &ecx, &edx)) { 620 aip->ai_batteries = -1; /* Unknown */ 621 aip->ai_capabilities = 0xff00; /* Unknown, with no bits set */ 622 } else { 623 aip->ai_batteries = ebx & 0xff; 624 aip->ai_capabilities = ecx & 0xf; 625 } 626 627 bzero(aip->ai_spare, sizeof aip->ai_spare); 628 629 return 0; 630} 631 632 633/* inform APM BIOS that CPU is idle */ 634void 635apm_cpu_idle(void) 636{ 637 struct apm_softc *sc = &apm_softc; 638 639 if (sc->active) { 640 u_long eax, ebx, ecx, edx; 641 642 eax = (APM_BIOS <<8) | APM_CPUIDLE; 643 edx = ecx = ebx = 0; 644 apm_int(&eax, &ebx, &ecx, &edx); 645 } 646 /* 647 * Some APM implementation halts CPU in BIOS, whenever 648 * "CPU-idle" function are invoked, but swtch() of 649 * FreeBSD halts CPU, therefore, CPU is halted twice 650 * in the sched loop. It makes the interrupt latency 651 * terribly long and be able to cause a serious problem 652 * in interrupt processing. We prevent it by removing 653 * "hlt" operation from swtch() and managed it under 654 * APM driver. 655 */ 656 if (!sc->active || sc->always_halt_cpu) 657 __asm("hlt"); /* wait for interrupt */ 658} 659 660/* inform APM BIOS that CPU is busy */ 661void 662apm_cpu_busy(void) 663{ 664 struct apm_softc *sc = &apm_softc; 665 666 /* 667 * The APM specification says this is only necessary if your BIOS 668 * slows down the processor in the idle task, otherwise it's not 669 * necessary. 670 */ 671 if (sc->slow_idle_cpu && sc->active) { 672 u_long eax, ebx, ecx, edx; 673 674 eax = (APM_BIOS <<8) | APM_CPUBUSY; 675 edx = ecx = ebx = 0; 676 apm_int(&eax, &ebx, &ecx, &edx); 677 } 678} 679 680 681/* 682 * APM timeout routine: 683 * 684 * This routine is automatically called by timer once per second. 685 */ 686 687static void 688apm_timeout(void *dummy) 689{ 690 struct apm_softc *sc = &apm_softc; 691 692 if (apm_op_inprog) 693 apm_lastreq_notify(); 694 695 apm_processevent(); 696 697 if (sc->active == 1) 698 /* Run slightly more oftan than 1 Hz */ 699 apm_timeout_ch = timeout(apm_timeout, NULL, hz - 1 ); 700} 701 702/* enable APM BIOS */ 703static void 704apm_event_enable(void) 705{ 706 struct apm_softc *sc = &apm_softc; 707 708#ifdef APM_DEBUG 709 printf("called apm_event_enable()\n"); 710#endif 711 if (sc->initialized) { 712 sc->active = 1; 713 apm_timeout(sc); 714 } 715} 716 717/* disable APM BIOS */ 718static void 719apm_event_disable(void) 720{ 721 struct apm_softc *sc = &apm_softc; 722 723#ifdef APM_DEBUG 724 printf("called apm_event_disable()\n"); 725#endif 726 if (sc->initialized) { 727 untimeout(apm_timeout, NULL, apm_timeout_ch); 728 sc->active = 0; 729 } 730} 731 732/* halt CPU in scheduling loop */ 733static void 734apm_halt_cpu(void) 735{ 736 struct apm_softc *sc = &apm_softc; 737 738 if (sc->initialized) 739 sc->always_halt_cpu = 1; 740} 741 742/* don't halt CPU in scheduling loop */ 743static void 744apm_not_halt_cpu(void) 745{ 746 struct apm_softc *sc = &apm_softc; 747 748 if (sc->initialized) 749 sc->always_halt_cpu = 0; 750} 751 752/* device driver definitions */ 753 754/* 755 * probe APM 756 */ 757 758static int 759apm_probe(device_t dev) 760{ 761 struct vm86frame vmf; 762 int i; 763 int disabled, flags; 764 765 if (resource_int_value("apm", 0, "disabled", &disabled) == 0 766 && disabled != 0) 767 return ENXIO; 768 769 device_set_desc(dev, "APM BIOS"); 770 771 if ( device_get_unit(dev) > 0 ) { 772 printf("apm: Only one APM driver supported.\n"); 773 return ENXIO; 774 } 775 776 if (resource_int_value("apm", 0, "flags", &flags) != 0) 777 flags = 0; 778 779 bzero(&vmf, sizeof(struct vm86frame)); /* safety */ 780 vmf.vmf_ax = (APM_BIOS << 8) | APM_INSTCHECK; 781 vmf.vmf_bx = 0; 782 if (((i = vm86_intcall(SYSTEM_BIOS, &vmf)) == 0) && 783 !(vmf.vmf_eflags & PSL_C) && 784 (vmf.vmf_bx == 0x504d)) { 785 786 apm_version = vmf.vmf_ax; 787 apm_flags = vmf.vmf_cx; 788 789 vmf.vmf_ax = (APM_BIOS << 8) | APM_PROT32CONNECT; 790 vmf.vmf_bx = 0; 791 if (((i = vm86_intcall(SYSTEM_BIOS, &vmf)) == 0) && 792 !(vmf.vmf_eflags & PSL_C)) { 793 794 apm_cs32_base = vmf.vmf_ax; 795 apm_cs_entry = vmf.vmf_ebx; 796 apm_cs16_base = vmf.vmf_cx; 797 apm_ds_base = vmf.vmf_dx; 798 apm_cs32_limit = vmf.vmf_si; 799 if (apm_version >= 0x0102) 800 apm_cs16_limit = (vmf.esi.r_ex >> 16); 801 apm_ds_limit = vmf.vmf_di; 802#ifdef APM_DEBUG 803 printf("apm: BIOS probe/32-bit connect successful\n"); 804#endif 805 } else { 806 /* XXX constant typo! */ 807 if (vmf.vmf_ah == APME_PROT32NOTDUPPORTED) { 808 apm_version = APMINI_NOT32BIT; 809 } else { 810 apm_version = APMINI_CONNECTERR; 811 } 812#ifdef APM_DEBUG 813 printf("apm: BIOS 32-bit connect failed: error 0x%x carry %d ah 0x%x\n", 814 i, (vmf.vmf_eflags & PSL_C) ? 1 : 0, vmf.vmf_ah); 815#endif 816 } 817 } else { 818 apm_version = APMINI_CANTFIND; 819#ifdef APM_DEBUG 820 printf("apm: BIOS probe failed: error 0x%x carry %d bx 0x%x\n", 821 i, (vmf.vmf_eflags & PSL_C) ? 1 : 0, vmf.vmf_bx); 822#endif 823 } 824 825 bzero(&apm_softc, sizeof(apm_softc)); 826 827 switch (apm_version) { 828 case APMINI_CANTFIND: 829 /* silent */ 830 return ENXIO; 831 case APMINI_NOT32BIT: 832 printf("apm: 32bit connection is not supported.\n"); 833 return ENXIO; 834 case APMINI_CONNECTERR: 835 printf("apm: 32-bit connection error.\n"); 836 return ENXIO; 837 } 838 if (flags & 0x20) 839 statclock_disable = 1; 840 return 0; 841} 842 843/* 844 * return 0 if the user will notice and handle the event, 845 * return 1 if the kernel driver should do so. 846 */ 847static int 848apm_record_event(struct apm_softc *sc, u_int event_type) 849{ 850 struct apm_event_info *evp; 851 852 if ((sc->sc_flags & SCFLAG_OPEN) == 0) 853 return 1; /* no user waiting */ 854 if (sc->event_count == APM_NEVENTS) 855 return 1; /* overflow */ 856 if (sc->event_filter[event_type] == 0) 857 return 1; /* not registered */ 858 evp = &sc->event_list[sc->event_ptr]; 859 sc->event_count++; 860 sc->event_ptr++; 861 sc->event_ptr %= APM_NEVENTS; 862 evp->type = event_type; 863 evp->index = ++apm_evindex; 864 selwakeup(&sc->sc_rsel); 865 return (sc->sc_flags & SCFLAG_OCTL) ? 0 : 1; /* user may handle */ 866} 867 868/* Process APM event */ 869static void 870apm_processevent(void) 871{ 872 int apm_event; 873 struct apm_softc *sc = &apm_softc; 874 875#ifdef APM_DEBUG 876# define OPMEV_DEBUGMESSAGE(symbol) case symbol: \ 877 printf("Received APM Event: " #symbol "\n"); 878#else 879# define OPMEV_DEBUGMESSAGE(symbol) case symbol: 880#endif 881 do { 882 apm_event = apm_getevent(); 883 switch (apm_event) { 884 OPMEV_DEBUGMESSAGE(PMEV_STANDBYREQ); 885 if (apm_op_inprog == 0) { 886 apm_op_inprog++; 887 if (apm_record_event(sc, apm_event)) { 888 apm_suspend(PMST_STANDBY); 889 } 890 } 891 break; 892 OPMEV_DEBUGMESSAGE(PMEV_SUSPENDREQ); 893 apm_lastreq_notify(); 894 if (apm_op_inprog == 0) { 895 apm_op_inprog++; 896 if (apm_record_event(sc, apm_event)) { 897 apm_suspend(PMST_SUSPEND); 898 } 899 } 900 return; /* XXX skip the rest */ 901 OPMEV_DEBUGMESSAGE(PMEV_USERSUSPENDREQ); 902 apm_lastreq_notify(); 903 if (apm_op_inprog == 0) { 904 apm_op_inprog++; 905 if (apm_record_event(sc, apm_event)) { 906 apm_suspend(PMST_SUSPEND); 907 } 908 } 909 return; /* XXX skip the rest */ 910 OPMEV_DEBUGMESSAGE(PMEV_CRITSUSPEND); 911 apm_suspend(PMST_SUSPEND); 912 break; 913 OPMEV_DEBUGMESSAGE(PMEV_NORMRESUME); 914 apm_record_event(sc, apm_event); 915 apm_resume(); 916 break; 917 OPMEV_DEBUGMESSAGE(PMEV_CRITRESUME); 918 apm_record_event(sc, apm_event); 919 apm_resume(); 920 break; 921 OPMEV_DEBUGMESSAGE(PMEV_STANDBYRESUME); 922 apm_record_event(sc, apm_event); 923 apm_resume(); 924 break; 925 OPMEV_DEBUGMESSAGE(PMEV_BATTERYLOW); 926 if (apm_record_event(sc, apm_event)) { 927 apm_battery_low(); 928 apm_suspend(PMST_SUSPEND); 929 } 930 break; 931 OPMEV_DEBUGMESSAGE(PMEV_POWERSTATECHANGE); 932 apm_record_event(sc, apm_event); 933 break; 934 OPMEV_DEBUGMESSAGE(PMEV_UPDATETIME); 935 apm_record_event(sc, apm_event); 936 inittodr(0); /* adjust time to RTC */ 937 break; 938 case PMEV_NOEVENT: 939 break; 940 default: 941 printf("Unknown Original APM Event 0x%x\n", apm_event); 942 break; 943 } 944 } while (apm_event != PMEV_NOEVENT); 945} 946 947/* 948 * Attach APM: 949 * 950 * Initialize APM driver 951 */ 952 953static int 954apm_attach(device_t dev) 955{ 956#define APM_KERNBASE KERNBASE 957 struct apm_softc *sc = &apm_softc; 958 int flags; 959 960 if (resource_int_value("apm", 0, "flags", &flags) != 0) 961 flags = 0; 962 963 sc->initialized = 0; 964 965 /* Must be externally enabled */ 966 sc->active = 0; 967 968 /* setup APM parameters */ 969 sc->cs16_base = (apm_cs16_base << 4) + APM_KERNBASE; 970 sc->cs32_base = (apm_cs32_base << 4) + APM_KERNBASE; 971 sc->ds_base = (apm_ds_base << 4) + APM_KERNBASE; 972 sc->cs32_limit = apm_cs32_limit - 1; 973 if (apm_cs16_limit == 0) 974 apm_cs16_limit = apm_cs32_limit; 975 sc->cs16_limit = apm_cs16_limit - 1; 976 sc->ds_limit = apm_ds_limit - 1; 977 sc->cs_entry = apm_cs_entry; 978 979 /* Always call HLT in idle loop */ 980 sc->always_halt_cpu = 1; 981 982 sc->slow_idle_cpu = ((apm_flags & APM_CPUIDLE_SLOW) != 0); 983 sc->disabled = ((apm_flags & APM_DISABLED) != 0); 984 sc->disengaged = ((apm_flags & APM_DISENGAGED) != 0); 985 986 /* print bootstrap messages */ 987#ifdef APM_DEBUG 988 printf("apm: APM BIOS version %04x\n", apm_version); 989 printf("apm: Code32 0x%08x, Code16 0x%08x, Data 0x%08x\n", 990 sc->cs32_base, sc->cs16_base, sc->ds_base); 991 printf("apm: Code entry 0x%08x, Idling CPU %s, Management %s\n", 992 sc->cs_entry, is_enabled(sc->slow_idle_cpu), 993 is_enabled(!sc->disabled)); 994 printf("apm: CS32_limit=0x%x, CS16_limit=0x%x, DS_limit=0x%x\n", 995 (u_short)sc->cs32_limit, (u_short)sc->cs16_limit, (u_short)sc->ds_limit); 996#endif /* APM_DEBUG */ 997 998#if 0 999 /* Workaround for some buggy APM BIOS implementations */ 1000 sc->cs_limit = 0xffff; 1001 sc->ds_limit = 0xffff; 1002#endif 1003 1004 /* setup GDT */ 1005 setup_apm_gdt(sc->cs32_base, sc->cs16_base, sc->ds_base, 1006 sc->cs32_limit, sc->cs16_limit, sc->ds_limit); 1007 1008 /* setup entry point 48bit pointer */ 1009 apm_addr.segment = GSEL(GAPMCODE32_SEL, SEL_KPL); 1010 apm_addr.offset = sc->cs_entry; 1011 1012 if ((flags & 0x10)) { 1013 if ((flags & 0xf) >= 0x2) { 1014 apm_driver_version(0x102); 1015 } 1016 if (!apm_version && (flags & 0xf) >= 0x1) { 1017 apm_driver_version(0x101); 1018 } 1019 } else { 1020 apm_driver_version(0x102); 1021 if (!apm_version) 1022 apm_driver_version(0x101); 1023 } 1024 if (!apm_version) 1025 apm_version = 0x100; 1026 1027 sc->minorversion = ((apm_version & 0x00f0) >> 4) * 10 + 1028 ((apm_version & 0x000f) >> 0); 1029 sc->majorversion = ((apm_version & 0xf000) >> 12) * 10 + 1030 ((apm_version & 0x0f00) >> 8); 1031 1032 sc->intversion = INTVERSION(sc->majorversion, sc->minorversion); 1033 1034#ifdef APM_DEBUG 1035 if (sc->intversion >= INTVERSION(1, 1)) 1036 printf("apm: Engaged control %s\n", is_enabled(!sc->disengaged)); 1037#endif 1038 1039 printf("apm: found APM BIOS version %d.%d\n", 1040 sc->majorversion, sc->minorversion); 1041 1042#ifdef APM_DEBUG 1043 printf("apm: Slow Idling CPU %s\n", is_enabled(sc->slow_idle_cpu)); 1044#endif 1045 1046 /* enable power management */ 1047 if (sc->disabled) { 1048 if (apm_enable_disable_pm(1)) { 1049#ifdef APM_DEBUG 1050 printf("apm: *Warning* enable function failed! [%x]\n", 1051 apm_errno); 1052#endif 1053 } 1054 } 1055 1056 /* engage power managment (APM 1.1 or later) */ 1057 if (sc->intversion >= INTVERSION(1, 1) && sc->disengaged) { 1058 if (apm_engage_disengage_pm(1)) { 1059#ifdef APM_DEBUG 1060 printf("apm: *Warning* engage function failed err=[%x]", 1061 apm_errno); 1062 printf(" (Docked or using external power?).\n"); 1063#endif 1064 } 1065 } 1066 1067 /* default suspend hook */ 1068 sc->sc_suspend.ah_fun = apm_default_suspend; 1069 sc->sc_suspend.ah_arg = sc; 1070 sc->sc_suspend.ah_name = "default suspend"; 1071 sc->sc_suspend.ah_order = APM_MAX_ORDER; 1072 1073 /* default resume hook */ 1074 sc->sc_resume.ah_fun = apm_default_resume; 1075 sc->sc_resume.ah_arg = sc; 1076 sc->sc_resume.ah_name = "default resume"; 1077 sc->sc_resume.ah_order = APM_MIN_ORDER; 1078 1079 apm_hook_establish(APM_HOOK_SUSPEND, &sc->sc_suspend); 1080 apm_hook_establish(APM_HOOK_RESUME , &sc->sc_resume); 1081 1082 apm_event_enable(); 1083 1084 /* Power the system off using APM */ 1085 at_shutdown_pri(apm_power_off, NULL, SHUTDOWN_FINAL, SHUTDOWN_PRI_LAST); 1086 1087 sc->initialized = 1; 1088 1089#ifdef DEVFS 1090 sc->sc_devfs_token = 1091 devfs_add_devswf(&apm_cdevsw, 0, DV_CHR, 0, 0, 0600, "apm"); 1092#endif 1093 return 0; 1094} 1095 1096static int 1097apmopen(dev_t dev, int flag, int fmt, struct proc *p) 1098{ 1099 struct apm_softc *sc = &apm_softc; 1100 int ctl = APMDEV(dev); 1101 1102 if (!sc->initialized) 1103 return (ENXIO); 1104 1105 switch (ctl) { 1106 case APMDEV_CTL: 1107 if (!(flag & FWRITE)) 1108 return EINVAL; 1109 if (sc->sc_flags & SCFLAG_OCTL) 1110 return EBUSY; 1111 sc->sc_flags |= SCFLAG_OCTL; 1112 bzero(sc->event_filter, sizeof sc->event_filter); 1113 break; 1114 case APMDEV_NORMAL: 1115 sc->sc_flags |= SCFLAG_ONORMAL; 1116 break; 1117 default: 1118 return ENXIO; 1119 break; 1120 } 1121 return 0; 1122} 1123 1124static int 1125apmclose(dev_t dev, int flag, int fmt, struct proc *p) 1126{ 1127 struct apm_softc *sc = &apm_softc; 1128 int ctl = APMDEV(dev); 1129 1130 switch (ctl) { 1131 case APMDEV_CTL: 1132 apm_lastreq_rejected(); 1133 sc->sc_flags &= ~SCFLAG_OCTL; 1134 bzero(sc->event_filter, sizeof sc->event_filter); 1135 break; 1136 case APMDEV_NORMAL: 1137 sc->sc_flags &= ~SCFLAG_ONORMAL; 1138 break; 1139 } 1140 if ((sc->sc_flags & SCFLAG_OPEN) == 0) { 1141 sc->event_count = 0; 1142 sc->event_ptr = 0; 1143 } 1144 return 0; 1145} 1146 1147static int 1148apmioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p) 1149{ 1150 struct apm_softc *sc = &apm_softc; 1151 int error = 0; 1152 int newstate; 1153 1154 if (!sc->initialized) 1155 return (ENXIO); 1156#ifdef APM_DEBUG 1157 printf("APM ioctl: cmd = 0x%x\n", cmd); 1158#endif 1159 switch (cmd) { 1160 case APMIO_SUSPEND: 1161 if (sc->active) 1162 apm_suspend(PMST_SUSPEND); 1163 else 1164 error = EINVAL; 1165 break; 1166 1167 case APMIO_STANDBY: 1168 if (sc->active) 1169 apm_suspend(PMST_STANDBY); 1170 else 1171 error = EINVAL; 1172 break; 1173 1174 case APMIO_GETINFO_OLD: 1175 { 1176 struct apm_info info; 1177 apm_info_old_t aiop; 1178 1179 if (apm_get_info(&info)) 1180 error = ENXIO; 1181 aiop = (apm_info_old_t)addr; 1182 aiop->ai_major = info.ai_major; 1183 aiop->ai_minor = info.ai_minor; 1184 aiop->ai_acline = info.ai_acline; 1185 aiop->ai_batt_stat = info.ai_batt_stat; 1186 aiop->ai_batt_life = info.ai_batt_life; 1187 aiop->ai_status = info.ai_status; 1188 } 1189 break; 1190 case APMIO_GETINFO: 1191 if (apm_get_info((apm_info_t)addr)) 1192 error = ENXIO; 1193 break; 1194 case APMIO_ENABLE: 1195 apm_event_enable(); 1196 break; 1197 case APMIO_DISABLE: 1198 apm_event_disable(); 1199 break; 1200 case APMIO_HALTCPU: 1201 apm_halt_cpu(); 1202 break; 1203 case APMIO_NOTHALTCPU: 1204 apm_not_halt_cpu(); 1205 break; 1206 case APMIO_DISPLAY: 1207 newstate = *(int *)addr; 1208 if (apm_display(newstate)) 1209 error = ENXIO; 1210 break; 1211 case APMIO_BIOS: 1212 if (apm_bios_call((struct apm_bios_arg*)addr) == 0) 1213 ((struct apm_bios_arg*)addr)->eax &= 0xff; 1214 break; 1215 default: 1216 error = EINVAL; 1217 break; 1218 } 1219 1220 /* for /dev/apmctl */ 1221 if (APMDEV(dev) == APMDEV_CTL) { 1222 struct apm_event_info *evp; 1223 int i; 1224 1225 error = 0; 1226 switch (cmd) { 1227 case APMIO_NEXTEVENT: 1228 if (!sc->event_count) { 1229 error = EAGAIN; 1230 } else { 1231 evp = (struct apm_event_info *)addr; 1232 i = sc->event_ptr + APM_NEVENTS - sc->event_count; 1233 i %= APM_NEVENTS; 1234 *evp = sc->event_list[i]; 1235 sc->event_count--; 1236 } 1237 break; 1238 case APMIO_REJECTLASTREQ: 1239 if (apm_lastreq_rejected()) { 1240 error = EINVAL; 1241 } 1242 break; 1243 default: 1244 error = EINVAL; 1245 break; 1246 } 1247 } 1248 1249 return error; 1250} 1251 1252static int 1253apmwrite(dev_t dev, struct uio *uio, int ioflag) 1254{ 1255 struct apm_softc *sc = &apm_softc; 1256 u_int event_type; 1257 int error; 1258 u_char enabled; 1259 1260 if (APMDEV(dev) != APMDEV_CTL) 1261 return(ENODEV); 1262 if (uio->uio_resid != sizeof(u_int)) 1263 return(E2BIG); 1264 1265 if ((error = uiomove((caddr_t)&event_type, sizeof(u_int), uio))) 1266 return(error); 1267 1268 if (event_type < 0 || event_type >= APM_NPMEV) 1269 return(EINVAL); 1270 1271 if (sc->event_filter[event_type] == 0) { 1272 enabled = 1; 1273 } else { 1274 enabled = 0; 1275 } 1276 sc->event_filter[event_type] = enabled; 1277#ifdef APM_DEBUG 1278 printf("apmwrite: event 0x%x %s\n", event_type, is_enabled(enabled)); 1279#endif 1280 1281 return uio->uio_resid; 1282} 1283 1284static int 1285apmpoll(dev_t dev, int events, struct proc *p) 1286{ 1287 struct apm_softc *sc = &apm_softc; 1288 int revents = 0; 1289 1290 if (events & (POLLIN | POLLRDNORM)) { 1291 if (sc->event_count) { 1292 revents |= events & (POLLIN | POLLRDNORM); 1293 } else { 1294 selrecord(p, &sc->sc_rsel); 1295 } 1296 } 1297 1298 return (revents); 1299} 1300 1301static device_method_t apm_methods[] = { 1302 /* Device interface */ 1303 DEVMETHOD(device_probe, apm_probe), 1304 DEVMETHOD(device_attach, apm_attach), 1305 1306 { 0, 0 } 1307}; 1308 1309static driver_t apm_driver = { 1310 "apm", 1311 apm_methods, 1312 1, /* no softc (XXX) */ 1313}; 1314 1315static devclass_t apm_devclass; 1316 1317DEV_DRIVER_MODULE(apm, nexus, apm_driver, apm_devclass, apm_cdevsw, 0, 0); 1318