void apm_set_ver(struct apm_softc *self) { struct apmregs regs; int rv = 0; bzero(®s, sizeof(regs)); regs.cx = APM_VERSION; if (APM_MAJOR(apm_flags) == 1 && APM_MINOR(apm_flags) == 2 && (rv = apmcall(APM_DRIVER_VERSION, APM_DEV_APM_BIOS, ®s)) == 0) { apm_majver = APM_CONN_MAJOR(®s); apm_minver = APM_CONN_MINOR(®s); } else { #ifdef APMDEBUG if (rv) apm_perror("set version 1.2", ®s); #endif /* try downgrading to 1.1 */ bzero(®s, sizeof(regs)); regs.cx = 0x0101; if (apmcall(APM_DRIVER_VERSION, APM_DEV_APM_BIOS, ®s) == 0) { apm_majver = 1; apm_minver = 1; } else { #ifdef APMDEBUG apm_perror("set version 1.1", ®s); #endif /* stay w/ flags then */ apm_majver = APM_MAJOR(apm_flags); apm_minver = APM_MINOR(apm_flags); /* fix version for some endianess-challenged compaqs */ if (!apm_majver) { apm_majver = 1; apm_minver = 0; } } } printf(": Power Management spec V%d.%d", apm_majver, apm_minver); #ifdef DIAGNOSTIC if (apm_flags & APM_IDLE_SLOWS) printf(" (slowidle)"); if (apm_flags & APM_BIOS_PM_DISABLED) printf(" (BIOS management disabled)"); if (apm_flags & APM_BIOS_PM_DISENGAGED) printf(" (BIOS managing devices)"); #endif printf("\n"); }
static void apm_periodic_check(struct apm_softc *sc) { int error; u_int event_code, event_info; /* * tell the BIOS we're working on it, if asked to do a * suspend/standby */ if (apm_op_inprog) (*sc->sc_ops->aa_set_powstate)(sc->sc_cookie, APM_DEV_ALLDEVS, APM_LASTREQ_INPROG); while ((error = (*sc->sc_ops->aa_get_event)(sc->sc_cookie, &event_code, &event_info)) == 0 && !apm_damn_fool_bios) apm_event_handle(sc, event_code, event_info); if (error != APM_ERR_NOEVENTS) apm_perror("get event", error); if (apm_suspends) { apm_op_inprog = 0; apm_suspend(sc); } else if (apm_standbys || apm_userstandbys) { apm_op_inprog = 0; apm_standby(sc); } apm_suspends = apm_standbys = apm_battlow = apm_userstandbys = 0; apm_damn_fool_bios = 0; }
void apm_disconnect(struct apm_softc *sc) { struct apmregs regs; bzero(®s, sizeof(regs)); if (apmcall(APM_SYSTEM_DEFAULTS, (apm_minver == 1 ? APM_DEV_ALLDEVS : APM_DEFAULTS_ALL), ®s)) apm_perror("system defaults failed", ®s); if (apmcall(APM_DISCONNECT, APM_DEV_APM_BIOS, ®s)) apm_perror("disconnect failed", ®s); else printf("%s: disconnected\n", sc->sc_dev.dv_xname); apm_flags |= APM_BIOS_PM_DISABLED; }
void apm_cpu_idle(void) { struct apmregs regs; static u_int64_t call_apm_idle = 0; /* * We call the bios APM_IDLE routine here only when we * have been idle for some time - otherwise we just hlt. */ if (call_apm_idle != curcpu()->ci_schedstate.spc_cp_time[CP_IDLE]) { /* Always call BIOS halt/idle stuff */ bzero(®s, sizeof(regs)); if (apmcall(APM_CPU_IDLE, 0, ®s) != 0) { #ifdef DIAGNOSTIC apm_perror("set CPU idle", ®s); #endif } /* If BIOS did not halt, halt now! */ if (apm_flags & APM_IDLE_SLOWS) { __asm __volatile("sti;hlt"); } call_apm_idle = curcpu()->ci_schedstate.spc_cp_time[CP_IDLE]; } else { __asm __volatile("sti;hlt"); } }
void apm_powmgt_enable(int onoff) { struct apmregs regs; bzero(®s, sizeof(regs)); regs.cx = onoff ? APM_MGT_ENABLE : APM_MGT_DISABLE; if (apmcall(APM_PWR_MGT_ENABLE, (apm_minver? APM_DEV_APM_BIOS : APM_MGT_ALL), ®s) != 0) apm_perror("power management enable", ®s); }
int apm_periodic_check(struct apm_softc *sc) { struct apmregs regs; int ret = 0; if (apm_op_inprog) apm_set_powstate(APM_DEV_ALLDEVS, APM_LASTREQ_INPROG); while (1) { if (apm_get_event(®s) != 0) { /* i think some bioses combine the error codes */ if (!(APM_ERR_CODE(®s) & APM_ERR_NOEVENTS)) apm_perror("get event", ®s); break; } /* If the APM BIOS tells us to suspend, don't do it twice */ if (regs.bx == APM_SUSPEND_REQ) apm_lidclose = 0; if (apm_handle_event(sc, ®s)) break; } if (apm_error || APM_ERR_CODE(®s) == APM_ERR_NOTCONN) ret = -1; if (apm_lidclose) { apm_lidclose = 0; /* Fake a suspend request */ regs.bx = APM_SUSPEND_REQ; apm_handle_event(sc, ®s); } if (apm_suspends /*|| (apm_battlow && apm_userstandbys)*/) { apm_op_inprog = 0; apm_suspend(APM_SYS_SUSPEND); } else if (apm_standbys || apm_userstandbys) { apm_op_inprog = 0; apm_suspend(APM_SYS_STANDBY); } apm_suspends = apm_standbys = apm_battlow = apm_userstandbys = 0; apm_error = 0; if (apm_resumes) apm_resumes--; return (ret); }
void apm_cpu_busy(void) { struct apmregs regs; if (!apm_slow_called) return; if (apm_flags & APM_IDLE_SLOWS) { bzero(®s, sizeof(regs)); if (apmcall(APM_CPU_BUSY, 0, ®s) != 0) { #ifdef DIAGNOSTIC apm_perror("set CPU busy", ®s); #endif } apm_slow_called = 0; } }
void apm_cpu_slow(void) { struct apmregs regs; static u_int64_t call_apm_slow = 0; if (call_apm_slow != curcpu()->ci_schedstate.spc_cp_time[CP_IDLE]) { /* Always call BIOS halt/idle stuff */ bzero(®s, sizeof(regs)); if (apmcall(APM_CPU_IDLE, 0, ®s) != 0) { #ifdef DIAGNOSTIC apm_perror("set CPU slow", ®s); #endif } apm_slow_called = 1; call_apm_slow = curcpu()->ci_schedstate.spc_cp_time[CP_IDLE]; } }
int apm_set_powstate(u_int dev, u_int state) { struct apmregs regs; if (!apm_cd.cd_ndevs || (apm_minver == 0 && state > APM_SYS_OFF)) return EINVAL; bzero(®s, sizeof(regs)); regs.cx = state; if (apmcall(APM_SET_PWR_STATE, dev, ®s) != 0) { apm_perror("set power state", ®s); if (APM_ERR_CODE(®s) == APM_ERR_UNRECOG_DEV) return ENXIO; else return EIO; } return 0; }
int apmioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l) { struct apm_softc *sc = device_lookup_private(&apm_cd, APMUNIT(dev)); struct apm_power_info *powerp; struct apm_event_info *evp; #if 0 struct apm_ctl *actl; #endif int i, error = 0; int batt_flags; struct apm_ctl *actl; APM_LOCK(sc); switch (cmd) { case APM_IOC_STANDBY: if (!apm_do_standby) { error = EOPNOTSUPP; break; } if ((flag & FWRITE) == 0) { error = EBADF; break; } apm_userstandbys++; break; case APM_IOC_DEV_CTL: actl = (struct apm_ctl *)data; if ((flag & FWRITE) == 0) { error = EBADF; break; } #if 0 apm_get_powstate(actl->dev); /* XXX */ #endif error = (*sc->sc_ops->aa_set_powstate)(sc->sc_cookie, actl->dev, actl->mode); apm_suspends++; break; case APM_IOC_SUSPEND: if ((flag & FWRITE) == 0) { error = EBADF; break; } apm_suspends++; break; case APM_IOC_NEXTEVENT: if (!sc->sc_event_count) error = EAGAIN; else { evp = (struct apm_event_info *)data; i = sc->sc_event_ptr + APM_NEVENTS - sc->sc_event_count; i %= APM_NEVENTS; *evp = sc->sc_event_list[i]; sc->sc_event_count--; } break; case OAPM_IOC_GETPOWER: case APM_IOC_GETPOWER: powerp = (struct apm_power_info *)data; if ((error = (*sc->sc_ops->aa_get_powstat)(sc->sc_cookie, 0, powerp)) != 0) { apm_perror("ioctl get power status", error); error = EIO; break; } switch (apm_minver) { case 0: break; case 1: default: batt_flags = powerp->battery_flags; powerp->battery_state = APM_BATT_UNKNOWN; if (batt_flags & APM_BATT_FLAG_HIGH) powerp->battery_state = APM_BATT_HIGH; else if (batt_flags & APM_BATT_FLAG_LOW) powerp->battery_state = APM_BATT_LOW; else if (batt_flags & APM_BATT_FLAG_CRITICAL) powerp->battery_state = APM_BATT_CRITICAL; else if (batt_flags & APM_BATT_FLAG_CHARGING) powerp->battery_state = APM_BATT_CHARGING; else if (batt_flags & APM_BATT_FLAG_NO_SYSTEM_BATTERY) powerp->battery_state = APM_BATT_ABSENT; break; } break; default: error = ENOTTY; } APM_UNLOCK(sc); return (error); }
int apmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) { struct apm_softc *sc; struct apmregs regs; int error = 0; /* apm0 only */ if (!apm_cd.cd_ndevs || APMUNIT(dev) != 0 || !(sc = apm_cd.cd_devs[APMUNIT(dev)])) return ENXIO; rw_enter_write(&sc->sc_lock); switch (cmd) { /* some ioctl names from linux */ case APM_IOC_STANDBY: if ((flag & FWRITE) == 0) error = EBADF; else apm_userstandbys++; break; case APM_IOC_SUSPEND: if ((flag & FWRITE) == 0) error = EBADF; else apm_suspends++; break; case APM_IOC_PRN_CTL: if ((flag & FWRITE) == 0) error = EBADF; else { int flag = *(int *)data; DPRINTF(( "APM_IOC_PRN_CTL: %d\n", flag )); switch (flag) { case APM_PRINT_ON: /* enable printing */ sc->sc_flags &= ~SCFLAG_PRINT; break; case APM_PRINT_OFF: /* disable printing */ sc->sc_flags &= ~SCFLAG_PRINT; sc->sc_flags |= SCFLAG_NOPRINT; break; case APM_PRINT_PCT: /* disable some printing */ sc->sc_flags &= ~SCFLAG_PRINT; sc->sc_flags |= SCFLAG_PCTPRINT; break; default: error = EINVAL; break; } } break; case APM_IOC_DEV_CTL: if ((flag & FWRITE) == 0) error = EBADF; else { struct apm_ctl *actl = (struct apm_ctl *)data; bzero(®s, sizeof(regs)); if (!apmcall(APM_GET_POWER_STATE, actl->dev, ®s)) printf("%s: dev %04x state %04x\n", sc->sc_dev.dv_xname, dev, regs.cx); error = apm_set_powstate(actl->dev, actl->mode); } break; case APM_IOC_GETPOWER: if (apm_get_powstat(®s) == 0) { struct apm_power_info *powerp = (struct apm_power_info *)data; bzero(powerp, sizeof(*powerp)); if (BATT_LIFE(®s) != APM_BATT_LIFE_UNKNOWN) powerp->battery_life = BATT_LIFE(®s); powerp->ac_state = AC_STATE(®s); switch (apm_minver) { case 0: if (!(BATT_FLAGS(®s) & APM_BATT_FLAG_NOBATTERY)) powerp->battery_state = BATT_STATE(®s); break; case 1: default: if (BATT_FLAGS(®s) & APM_BATT_FLAG_HIGH) powerp->battery_state = APM_BATT_HIGH; else if (BATT_FLAGS(®s) & APM_BATT_FLAG_LOW) powerp->battery_state = APM_BATT_LOW; else if (BATT_FLAGS(®s) & APM_BATT_FLAG_CRITICAL) powerp->battery_state = APM_BATT_CRITICAL; else if (BATT_FLAGS(®s) & APM_BATT_FLAG_CHARGING) powerp->battery_state = APM_BATT_CHARGING; else if (BATT_FLAGS(®s) & APM_BATT_FLAG_NOBATTERY) powerp->battery_state = APM_BATTERY_ABSENT; else powerp->battery_state = APM_BATT_UNKNOWN; if (BATT_REM_VALID(®s)) { powerp->minutes_left = BATT_REMAINING(®s); if (sc->be_batt) powerp->minutes_left = swap16(powerp->minutes_left); } } } else { apm_perror("ioctl get power status", ®s); error = EIO; } break; case APM_IOC_STANDBY_REQ: if ((flag & FWRITE) == 0) error = EBADF; /* only fails if no one cares. apmd at least should */ else if (apm_record_event(sc, APM_USER_STANDBY_REQ)) error = EINVAL; /* ? */ break; case APM_IOC_SUSPEND_REQ: if ((flag & FWRITE) == 0) error = EBADF; /* only fails if no one cares. apmd at least should */ else if (apm_record_event(sc, APM_USER_SUSPEND_REQ)) error = EINVAL; /* ? */ break; default: error = ENOTTY; } rw_exit_write(&sc->sc_lock); return error; }
void apmattach(struct device *parent, struct device *self, void *aux) { struct bios_attach_args *ba = aux; bios_apminfo_t *ap = ba->ba_apmp; struct apm_softc *sc = (void *)self; struct apmregs regs; u_int cbase, clen, l; bus_space_handle_t ch16, ch32, dh; apm_flags = ap->apm_detail; /* * set up GDT descriptors for APM */ if (apm_flags & APM_32BIT_SUPPORTED) { /* truncate segments' limits to a page */ ap->apm_code_len -= (ap->apm_code32_base + ap->apm_code_len + 1) & 0xfff; ap->apm_code16_len -= (ap->apm_code16_base + ap->apm_code16_len + 1) & 0xfff; ap->apm_data_len -= (ap->apm_data_base + ap->apm_data_len + 1) & 0xfff; /* adjust version */ if ((sc->sc_dev.dv_cfdata->cf_flags & APM_VERMASK) && (apm_flags & APM_VERMASK) != (sc->sc_dev.dv_cfdata->cf_flags & APM_VERMASK)) apm_flags = (apm_flags & ~APM_VERMASK) | (sc->sc_dev.dv_cfdata->cf_flags & APM_VERMASK); if (sc->sc_dev.dv_cfdata->cf_flags & APM_NOCLI) { extern int apm_cli; /* from apmcall.S */ apm_cli = 0; } if (sc->sc_dev.dv_cfdata->cf_flags & APM_BEBATT) sc->be_batt = 1; apm_ep.seg = GSEL(GAPM32CODE_SEL,SEL_KPL); apm_ep.entry = ap->apm_entry; cbase = min(ap->apm_code32_base, ap->apm_code16_base); clen = max(ap->apm_code32_base + ap->apm_code_len, ap->apm_code16_base + ap->apm_code16_len) - cbase; if ((cbase <= ap->apm_data_base && cbase + clen >= ap->apm_data_base) || (ap->apm_data_base <= cbase && ap->apm_data_base + ap->apm_data_len >= cbase)) { l = max(ap->apm_data_base + ap->apm_data_len + 1, cbase + clen + 1) - min(ap->apm_data_base, cbase); bus_space_map(ba->ba_memt, min(ap->apm_data_base, cbase), l, 1, &dh); ch16 = dh; if (ap->apm_data_base < cbase) ch16 += cbase - ap->apm_data_base; else dh += ap->apm_data_base - cbase; } else { bus_space_map(ba->ba_memt, cbase, clen + 1, 1, &ch16); bus_space_map(ba->ba_memt, ap->apm_data_base, ap->apm_data_len + 1, 1, &dh); } ch32 = ch16; if (ap->apm_code16_base == cbase) ch32 += ap->apm_code32_base - cbase; else ch16 += ap->apm_code16_base - cbase; setgdt(GAPM32CODE_SEL, (void *)ch32, ap->apm_code_len, SDT_MEMERA, SEL_KPL, 1, 0); setgdt(GAPM16CODE_SEL, (void *)ch16, ap->apm_code16_len, SDT_MEMERA, SEL_KPL, 0, 0); setgdt(GAPMDATA_SEL, (void *)dh, ap->apm_data_len, SDT_MEMRWA, SEL_KPL, 1, 0); DPRINTF((": flags %x code 32:%x/%x[%x] 16:%x/%x[%x] " "data %x/%x/%x ep %x (%x:%x)\n%s", apm_flags, ap->apm_code32_base, ch32, ap->apm_code_len, ap->apm_code16_base, ch16, ap->apm_code16_len, ap->apm_data_base, dh, ap->apm_data_len, ap->apm_entry, apm_ep.seg, ap->apm_entry+ch32, sc->sc_dev.dv_xname)); apm_set_ver(sc); if (apm_flags & APM_BIOS_PM_DISABLED) apm_powmgt_enable(1); /* Engage cooperative power management on all devices (v1.1) */ apm_powmgt_engage(1, APM_DEV_ALLDEVS); bzero(®s, sizeof(regs)); if (apm_get_powstat(®s) != 0) apm_perror("get power status", ®s); apm_cpu_busy(); rw_init(&sc->sc_lock, "apmlk"); /* * Do a check once, ignoring any errors. This avoids * gratuitous APM disconnects on laptops where the first * event in the queue (after a boot) is non-recognizable. * The IBM ThinkPad 770Z is one of those. */ apm_periodic_check(sc); if (apm_periodic_check(sc) == -1) { apm_disconnect(sc); /* Failed, nuke APM idle loop */ cpu_idle_enter_fcn = NULL; cpu_idle_cycle_fcn = NULL; cpu_idle_leave_fcn = NULL; } else { kthread_create_deferred(apm_thread_create, sc); /* Setup APM idle loop */ if (apm_flags & APM_IDLE_SLOWS) { cpu_idle_enter_fcn = apm_cpu_slow; cpu_idle_cycle_fcn = NULL; cpu_idle_leave_fcn = apm_cpu_busy; } else { cpu_idle_enter_fcn = NULL; cpu_idle_cycle_fcn = apm_cpu_idle; cpu_idle_leave_fcn = NULL; } /* All is well, let the rest of the world know */ acpiapm_open = apmopen; acpiapm_close = apmclose; acpiapm_ioctl = apmioctl; acpiapm_kqfilter = apmkqfilter; apm_attached = 1; } } else { setgdt(GAPM32CODE_SEL, NULL, 0, 0, 0, 0, 0); setgdt(GAPM16CODE_SEL, NULL, 0, 0, 0, 0, 0); setgdt(GAPMDATA_SEL, NULL, 0, 0, 0, 0, 0); } }
int apm_handle_event(struct apm_softc *sc, struct apmregs *regs) { struct apmregs nregs; int ret = 0; switch (regs->bx) { case APM_NOEVENT: ret++; break; case APM_USER_STANDBY_REQ: if (apm_resumes || apm_op_inprog) break; DPRINTF(("user wants STANDBY--fat chance\n")); apm_op_inprog++; if (apm_record_event(sc, regs->bx)) { DPRINTF(("standby ourselves\n")); apm_userstandbys++; } break; case APM_STANDBY_REQ: if (apm_resumes || apm_op_inprog) break; DPRINTF(("standby requested\n")); if (apm_standbys || apm_suspends) { DPRINTF(("premature standby\n")); apm_error++; ret++; } apm_op_inprog++; if (apm_record_event(sc, regs->bx)) { DPRINTF(("standby ourselves\n")); apm_standbys++; } break; case APM_USER_SUSPEND_REQ: if (apm_resumes || apm_op_inprog) break; DPRINTF(("user wants suspend--fat chance!\n")); apm_op_inprog++; if (apm_record_event(sc, regs->bx)) { DPRINTF(("suspend ourselves\n")); apm_suspends++; } break; case APM_SUSPEND_REQ: if (apm_resumes || apm_op_inprog) break; DPRINTF(("suspend requested\n")); if (apm_standbys || apm_suspends) { DPRINTF(("premature suspend\n")); apm_error++; ret++; } apm_op_inprog++; if (apm_record_event(sc, regs->bx)) { DPRINTF(("suspend ourselves\n")); apm_suspends++; } break; case APM_POWER_CHANGE: DPRINTF(("power status change\n")); apm_get_powstat(&nregs); apm_record_event(sc, regs->bx); break; case APM_NORMAL_RESUME: DPRINTF(("system resumed\n")); apm_resume(sc, regs); break; case APM_CRIT_RESUME: DPRINTF(("system resumed without us!\n")); apm_resume(sc, regs); break; case APM_SYS_STANDBY_RESUME: DPRINTF(("system standby resume\n")); apm_resume(sc, regs); break; case APM_UPDATE_TIME: DPRINTF(("update time, please\n")); inittodr(time_second); apm_record_event(sc, regs->bx); break; case APM_CRIT_SUSPEND_REQ: DPRINTF(("suspend required immediately\n")); apm_record_event(sc, regs->bx); apm_suspend(APM_SYS_SUSPEND); break; case APM_BATTERY_LOW: DPRINTF(("Battery low!\n")); apm_battlow++; apm_record_event(sc, regs->bx); break; case APM_CAPABILITY_CHANGE: DPRINTF(("capability change\n")); if (apm_minver < 2) { DPRINTF(("adult event\n")); } else { if (apmcall(APM_GET_CAPABILITIES, APM_DEV_APM_BIOS, &nregs) != 0) { apm_perror("get capabilities", &nregs); } else { apm_get_powstat(&nregs); } } break; default: { #ifdef APMDEBUG char *p; switch (regs->bx >> 8) { case 0: p = "reserved system"; break; case 1: p = "reserved device"; break; case 2: p = "OEM defined"; break; default:p = "reserved"; break; } #endif DPRINTF(("apm_handle_event: %s event, code %d\n", p, regs->bx)); } } return ret; }