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