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