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