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