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