#include #include #include typedef struct Ureg Ureg; #include #include #include #include "/sys/src/libthread/assert.h" #include <9p.h> enum { /* power mgmt event codes */ NotifyStandbyRequest = 0x0001, NotifySuspendRequest = 0x0002, NotifyNormalResume = 0x0003, NotifyCriticalResume = 0x0004, NotifyBatteryLow = 0x0005, NotifyPowerStatusChange = 0x0006, NotifyUpdateTime = 0x0007, NotifyCriticalSuspend = 0x0008, NotifyUserStandbyRequest = 0x0009, NotifyUserSuspendRequest = 0x000A, NotifyStandbyResume = 0x000B, NotifyCapabilitiesChange = 0x000C, /* power device ids: add device number or All */ DevBios = 0x0000, DevAll = 0x0001, DevDisplay = 0x0100, DevStorage = 0x0200, DevLpt = 0x0300, DevEia = 0x0400, DevNetwork = 0x0500, DevPCMCIA = 0x0600, DevBattery = 0x8000, All = 0x00FF, DevMask = 0xFF00, /* power states */ PowerEnabled = 0x0000, PowerStandby = 0x0001, PowerSuspend = 0x0002, PowerOff = 0x0003, /* apm commands */ CmdInstallationCheck = 0x5300, CmdRealModeConnect = 0x5301, CmdProtMode16Connect = 0x5302, CmdProtMode32Connect = 0x5303, CmdDisconnect = 0x5304, CmdCpuIdle = 0x5305, CmdCpuBusy = 0x5306, CmdSetPowerState = 0x5307, CmdSetPowerMgmt = 0x5308, DisablePowerMgmt = 0x0000, /* CX */ EnablePowerMgmt = 0x0001, CmdRestoreDefaults = 0x5309, CmdGetPowerStatus = 0x530A, CmdGetPMEvent = 0x530B, CmdGetPowerState = 0x530C, CmdGetPowerMgmt = 0x530D, CmdDriverVersion = 0x530E, /* like CmdDisconnect but doesn't lose the interface */ CmdGagePowerMgmt = 0x530F, DisengagePowerMgmt = 0x0000, /* CX */ EngagePowerManagemenet = 0x0001, CmdGetCapabilities = 0x5310, CapStandby = 0x0001, CapSuspend = 0x0002, CapTimerResumeStandby = 0x0004, CapTimerResumeSuspend = 0x0008, CapRingResumeStandby = 0x0010, CapRingResumeSuspend = 0x0020, CapPcmciaResumeStandby = 0x0040, CapPcmciaResumeSuspend = 0x0080, CapSlowCpu = 0x0100, CmdResumeTimer = 0x5311, DisableResumeTimer = 0x00, /* CL */ GetResumeTimer = 0x01, SetResumeTimer = 0x02, CmdResumeOnRing = 0x5312, DisableResumeOnRing = 0x0000, /* CX */ EnableResumeOnRing = 0x0001, GetResumeOnRing = 0x0002, CmdTimerRequests = 0x5313, DisableTimerRequests = 0x0000, /* CX */ EnableTimerRequests = 0x0001, GetTimerRequests = 0x0002, }; static char* eventstr[] = { [NotifyStandbyRequest] "system standby request", [NotifySuspendRequest] "system suspend request", [NotifyNormalResume] "normal resume", [NotifyCriticalResume] "critical resume", [NotifyBatteryLow] "battery low", [NotifyPowerStatusChange] "power status change", [NotifyUpdateTime] "update time", [NotifyCriticalSuspend] "critical suspend", [NotifyUserStandbyRequest] "user standby request", [NotifyUserSuspendRequest] "user suspend request", [NotifyCapabilitiesChange] "capabilities change", }; static char* apmevent(int e) { static char buf[32]; if(0 <= e && e < nelem(eventstr) && eventstr[e]) return eventstr[e]; sprint(buf, "event 0x%ux", (uint)e); return buf; } static char *error[256] = { [0x01] "power mgmt disabled", [0x02] "real mode connection already established", [0x03] "interface not connected", [0x05] "16-bit protected mode connection already established", [0x06] "16-bit protected mode interface not supported", [0x07] "32-bit protected mode interface already established", [0x08] "32-bit protected mode interface not supported", [0x09] "unrecognized device id", [0x0A] "parameter value out of range", [0x0B] "interface not engaged", [0x0C] "function not supported", [0x0D] "resume timer disabled", [0x60] "unable to enter requestsed state", [0x80] "no power mgmt events pending", [0x86] "apm not present", }; static char* apmerror(int id) { char *e; static char buf[64]; if(e = error[id&0xFF]) return e; sprint(buf, "unknown error %x", id); return buf; } QLock apmlock; int apmdebug; static int _apmcall(int fd, Ureg *u) { if(apmdebug) threadprint(2, "call ax 0x%lux bx 0x%lux cx 0x%lux\n", u->ax&0xFFFF, u->bx&0xFFFF, u->cx&0xFFFF); seek(fd, 0, 0); if(write(fd, u, sizeof *u) != sizeof *u) return -1; seek(fd, 0, 0); if(read(fd, u, sizeof *u) != sizeof *u) return -1; if(apmdebug) threadprint(2, "flags 0x%lux ax 0x%lux bx 0x%lux cx 0x%lux\n", u->flags&0xFFFF, u->ax&0xFFFF, u->bx&0xFFFF, u->cx&0xFFFF); if(u->flags & 1) { /* carry flag */ werrstr("%s", apmerror(u->ax>>8)); return -1; } return 0; } static int apmcall(int fd, Ureg *u) { int r; qlock(&apmlock); r = _apmcall(fd, u); qunlock(&apmlock); return r; } typedef struct Apm Apm; typedef struct Battery Battery; struct Battery { int status; int percent; int time; }; enum { Mbattery = 4, }; struct Apm { int fd; int verhi; int verlo; int acstatus; int nbattery; int capabilities; Battery battery[Mbattery]; }; enum { AcUnknown = 0, /* Apm.acstatus */ AcOffline, AcOnline, AcBackup, BatteryUnknown = 0, /* Battery.status */ BatteryHigh, BatteryLow, BatteryCritical, BatteryCharging, }; static char* acstatusstr[] = { [AcUnknown] "unknown", [AcOffline] "offline", [AcOnline] "online", [AcBackup] "backup", }; static char* batterystatusstr[] = { [BatteryUnknown] "unknown", [BatteryHigh] "high", [BatteryLow] "low", [BatteryCritical] "critical", [BatteryCharging] "charging", }; static char* powerstatestr[] = { [PowerOff] "off", [PowerSuspend] "suspend", [PowerStandby] "standby", [PowerEnabled] "on", }; static char* xstatus(char **str, int nstr, int x) { if(0 <= x && x < nstr && str[x]) return str[x]; return "unknown"; } static char* batterystatus(int b) { return xstatus(batterystatusstr, nelem(batterystatusstr), b); } static char* powerstate(int s) { return xstatus(powerstatestr, nelem(powerstatestr), s); } static char* acstatus(int a) { return xstatus(acstatusstr, nelem(acstatusstr), a); } static int apmversion(Apm *apm) { Ureg u; u.ax = CmdDriverVersion; u.bx = 0x0000; u.cx = 0x0102; if(apmcall(apm->fd, &u) < 0) return -1; apm->verhi = u.cx>>8; apm->verlo = u.cx & 0xFF; return u.cx; } static int apmcpuidle(Apm *apm) { Ureg u; u.ax = CmdCpuIdle; return apmcall(apm->fd, &u); } static int apmcpubusy(Apm *apm) { Ureg u; u.ax = CmdCpuBusy; return apmcall(apm->fd, &u); } static int apmsetpowerstate(Apm *apm, int dev, int state) { Ureg u; u.ax = CmdSetPowerState; u.bx = dev; u.cx = state; return apmcall(apm->fd, &u); } static int apmsetpowermgmt(Apm *apm, int dev, int state) { Ureg u; u.ax = CmdSetPowerMgmt; u.bx = dev; u.cx = state; return apmcall(apm->fd, &u); } static int apmrestoredefaults(Apm *apm, int dev) { Ureg u; u.ax = CmdRestoreDefaults; u.bx = dev; return apmcall(apm->fd, &u); } static int apmgetpowerstatus(Apm *apm, int dev) { Battery *b; Ureg u; if(dev == DevAll) b = &apm->battery[0]; else if((dev & DevMask) == DevBattery) { if(dev - DevBattery < nelem(apm->battery)) b = &apm->battery[dev - DevBattery]; else b = nil; } else { werrstr("bad device number"); return -1; } u.ax = CmdGetPowerStatus; u.bx = dev; if(apmcall(apm->fd, &u) < 0) return -1; if((dev & DevMask) == DevBattery) apm->nbattery = u.si; switch(u.bx>>8) { case 0x00: apm->acstatus = AcOffline; break; case 0x01: apm->acstatus = AcOnline; break; case 0x02: apm->acstatus = AcBackup; break; default: apm->acstatus = AcUnknown; break; } if(b != nil) { switch(u.bx&0xFF) { case 0x00: b->status = BatteryHigh; break; case 0x01: b->status = BatteryLow; break; case 0x02: b->status = BatteryCritical; break; case 0x03: b->status = BatteryCharging; break; default: b->status = BatteryUnknown; break; } if((u.cx & 0xFF) == 0xFF) b->percent = -1; else b->percent = u.cx & 0xFF; if((u.dx&0xFFFF) == 0xFFFF) b->time = -1; else if(u.dx & 0x8000) b->time = 60*(u.dx & 0x7FFF); else b->time = u.dx & 0x7FFF; } return 0; } static int apmgetevent(Apm *apm) { Ureg u; u.ax = CmdGetPMEvent; u.bx = 0; u.cx = 0; //when u.bx == NotifyNormalResume or NotifyCriticalResume, //u.cx & 1 indicates PCMCIA socket was on while suspended, //u.cx & 1 == 0 indicates was off. if(apmcall(apm->fd, &u) < 0) return -1; return u.bx; } static int apmgetpowerstate(Apm *apm, int dev) { Ureg u; u.ax = CmdGetPowerState; u.bx = dev; u.cx = 0; if(apmcall(apm->fd, &u) < 0) return -1; return u.cx; } static int apmgetpowermgmt(Apm *apm, int dev) { Ureg u; u.ax = CmdGetPowerMgmt; u.bx = dev; if(apmcall(apm->fd, &u) < 0) return -1; return u.cx; } static int apmgetcapabilities(Apm *apm) { Ureg u; u.ax = CmdGetCapabilities; u.bx = DevBios; if(apmcall(apm->fd, &u) < 0) return -1; apm->nbattery = u.bx & 0xFF; apm->capabilities &= ~0xFFFF; apm->capabilities |= u.cx; return 0; } static int apminstallationcheck(Apm *apm) { Ureg u; u.ax = CmdInstallationCheck; u.bx = DevBios; if(apmcall(apm->fd, &u) < 0) return -1; if(u.cx & 0x0004) apm->capabilities |= CapSlowCpu; else apm->capabilities &= ~CapSlowCpu; return 0; } void apmsetdisplaystate(Apm *apm, int s) { apmsetpowerstate(apm, DevDisplay, s); } void apmblank(Apm *apm) { apmsetdisplaystate(apm, PowerStandby); } void apmunblank(Apm *apm) { apmsetdisplaystate(apm, PowerEnabled); } void apmsuspend(Apm *apm) { apmsetpowerstate(apm, DevAll, PowerSuspend); } void usage(void) { fprint(2, "usage: apm [-s]\n"); exits("usage"); } Apm apm; void powerprint(void) { print("%s", ctime(time(0))); if(apmgetpowerstatus(&apm, DevAll) == 0) { print("%d batteries\n", apm.nbattery); print("battery 0: status %s percent %d time %d:%.2d\n", batterystatus(apm.battery[0].status), apm.battery[0].percent, apm.battery[0].time/60, apm.battery[0].time%60); } } void* erealloc(void *v, ulong n) { v = realloc(v, n); if(v == nil) sysfatal("out of memory reallocating %lud", n); setmalloctag(v, getcallerpc(&v)); return v; } void* emalloc(ulong n) { void *v; v = malloc(n); if(v == nil) sysfatal("out of memory allocating %lud", n); memset(v, 0, n); setmalloctag(v, getcallerpc(&n)); return v; } char* estrdup(char *s) { int l; char *t; if (s == nil) return nil; l = strlen(s)+1; t = emalloc(l); memcpy(t, s, l); setmalloctag(t, getcallerpc(&s)); return t; } char* estrdupn(char *s, int n) { int l; char *t; l = strlen(s); if(l > n) l = n; t = emalloc(l+1); memmove(t, s, l); t[l] = '\0'; setmalloctag(t, getcallerpc(&s)); return t; } enum { Qroot = CHDIR, Qevent = 0, Qbattery, Qctl, }; static void rootread(Req*, void*, long*, vlong); static void eventread(Req*, void*, long*, vlong); static void ctlread(Req*, void*, long*, vlong); static void ctlwrite(Req*, void*, long*, vlong); static void batteryread(Req*, void*, long*, vlong); typedef struct Dfile Dfile; struct Dfile { ulong path; char *name; ulong mode; void (*read)(Req*, void*, long*, vlong); void (*write)(Req*, void*, long*, vlong); }; Dfile dfile[] = { { Qroot, "/", CHDIR|0555, rootread, nil, }, { Qevent, "event", 0444, eventread, nil, }, { Qbattery, "battery", 0444, batteryread, nil, }, { Qctl, "ctl", 0666, ctlread, ctlwrite, }, }; static int fillstat(ulong path, Dir *d) { int i; for(i=0; iuid, "apm"); strcpy(d->gid, "apm"); d->length = 0; strcpy(d->name, dfile[i].name); d->mode = dfile[i].mode; d->atime = d->mtime = time(0); d->qid = (Qid){dfile[i].path,0}; return 0; } static void fswalk(Req *r, Fid*, char *name, Qid *qid) { int i; if(strcmp(name, "..")==0){ *qid = (Qid){Qroot, 0}; respond(r, nil); return; } for(i=1; iqid.path){ case Qroot: case Qevent: case Qbattery: if(omode == OREAD){ respond(r, nil); return; } break; case Qctl: if((omode&~(OTRUNC|OREAD|OWRITE|ORDWR)) == 0){ respond(r, nil); return; } break; } respond(r, "permission denied"); return; } static void fsstat(Req *r, Fid *fid, Dir *d) { fillstat(fid->qid.path, d); respond(r, nil); } static void fsread(Req *r, Fid *fid, void *buf, long *count, vlong offset) { int i; for(i=0; iqid.path && dfile[i].read){ dfile[i].read(r, buf, count, offset); return; } } respond(r, "unknown file"); } static void fswrite(Req *r, Fid *fid, void *buf, long *count, vlong offset) { int i; for(i=0; iqid.path && dfile[i].write){ dfile[i].write(r, buf, count, offset); return; } } respond(r, "unknown file"); } static void rootread(Req *r, void *buf, long *count, vlong offset) { int i, j, n; Dir d; n = *count/DIRLEN; i = offset/DIRLEN; for(j=0; j sizeof(buf)-1) *count = sizeof(buf)-1; memmove(buf, v, *count); buf[*count] = '\0'; if(*count && buf[*count-1] == '\n') buf[--*count] = '\0'; q = buf; p = strchr(q, ' '); if(p==nil) p = q+strlen(q); else *p++ = '\0'; if(strcmp(q, "")==0 || strcmp(q, "system")==0) dev = DevAll; else if(strcmp(q, "display")==0) dev = DevDisplay; else if(strcmp(q, "storage")==0) dev = DevStorage; else if(strcmp(q, "lpt")==0) dev = DevLpt; else if(strcmp(q, "eia")==0) dev = DevEia; else if(strcmp(q, "network")==0) dev = DevNetwork; else if(strcmp(q, "pcmcia")==0) dev = DevPCMCIA; else{ respond(r, "unknown device"); return; } if(strcmp(p, "enable")==0) respondx(r, apmsetpowermgmt(&apm, dev, EnablePowerMgmt)); else if(strcmp(p, "disable")==0) respondx(r, apmsetpowermgmt(&apm, dev, DisablePowerMgmt)); else if(strcmp(p, "standby")==0) respondx(r, apmsetpowerstate(&apm, dev, PowerStandby)); else if(strcmp(p, "on")==0) respondx(r, apmsetpowerstate(&apm, dev, PowerEnabled)); /* else if(strcmp(p, "off")==0) respondx(r, apmsetpowerstate(&apm, dev, PowerOff)); else if(strcmp(p, "suspend")==0) respondx(r, apmsetpowerstate(&apm, dev, PowerSuspend)); */ else respond(r, "unknown verb"); } static int statusline(char *buf, int nbuf, char *name, int dev) { int s; char *state; state = "unknown"; if((s = apmgetpowerstate(&apm, dev)) >= 0) state = powerstate(s); return snprint(buf, nbuf, "%s %s\n", name, state); } static void ctlread(Req *r, void *v, long *count, vlong offset) { char buf[256+7*50], *ep, *p; p = buf; ep = buf+sizeof buf; p += snprint(p, ep-p, "ac %s\n", acstatus(apm.acstatus)); p += snprint(p, ep-p, "capabilities"); if(apm.capabilities & CapStandby) p += snprint(p, ep-p, " standby"); if(apm.capabilities & CapSuspend) p += snprint(p, ep-p, " suspend"); if(apm.capabilities & CapSlowCpu) p += snprint(p, ep-p, " slowcpu"); p += snprint(p, ep-p, "\n"); p += statusline(p, ep-p, "system", DevAll); p += statusline(p, ep-p, "display", DevDisplay); p += statusline(p, ep-p, "storage", DevStorage); p += statusline(p, ep-p, "lpt", DevLpt); p += statusline(p, ep-p, "eia", DevEia|All); p += statusline(p, ep-p, "network", DevNetwork|All); p += statusline(p, ep-p, "pcmcia", DevPCMCIA|All); USED(p); readstr(offset, v, count, buf); respond(r, nil); } enum { STACK = 16384, }; Channel *creq; Channel *cflush; Channel *cevent; Req *rlist, **tailp; int rp, wp; int nopoll; char eventq[32][80]; static void flushthread(void*) { Req *r, *or, **rq; threadsetname("flushthread"); while(r = recvp(cflush)){ or = r->oldreq; for(rq=&rlist; *rq; rq=&(*rq)->aux){ if(*rq == or){ *rq = or->aux; if(tailp==&or->aux) tailp = rq; break; } } respond(r, nil); } } static void answerany(void) { char *buf; int l, m; Req *r; if(rlist==nil || rp==wp) return; while(rlist && rp != wp){ r = rlist; rlist = r->aux; if(rlist==nil) tailp = &rlist; l = 0; buf = r->ofcall.data; m = r->ofcall.count; while(rp != wp){ if(l+strlen(eventq[rp]) <= m){ strcpy(buf+l, eventq[rp]); l += strlen(buf+l); }else if(l==0){ strncpy(buf, eventq[rp], m-1); buf[m-1] = '\0'; l += m; }else break; rp++; if(rp == nelem(eventq)) rp = 0; } r->ofcall.count = l; respond(r, nil); } } static void eventwatch(void*) { int e, s; threadsetname("eventwatch"); for(;;){ s = 0; while((e = apmgetevent(&apm)) >= 0){ sendul(cevent, e); s = 1; } if(s) sendul(cevent, -1); if(sleep(750) < 0) break; } } static void eventthread(void*) { int e; threadsetname("eventthread"); for(;;){ while((e = recvul(cevent)) >= 0){ snprint(eventq[wp], sizeof(eventq[wp])-1, "%s", apmevent(e)); strcat(eventq[wp], "\n"); wp++; if(wp==nelem(eventq)) wp = 0; if(wp+1==rp || (wp+1==nelem(eventq) && rp==0)) break; } answerany(); } } static void eventproc(void*) { Req *r; threadsetname("eventproc"); creq = chancreate(sizeof(Req*), 0); cevent = chancreate(sizeof(ulong), 0); cflush = chancreate(sizeof(Req*), 0); tailp = &rlist; if(!nopoll) proccreate(eventwatch, nil, STACK); threadcreate(eventthread, nil, STACK); threadcreate(flushthread, nil, STACK); while(r = recvp(creq)){ *tailp = r; r->aux = nil; tailp = &r->aux; answerany(); } } static void fsflush(Req *r, Req*) { sendp(cflush, r); } static void eventread(Req *r, void*, long*, vlong) { sendp(creq, r); } static void fsattach(Req *r, Fid*, char *spec, Qid *qid) { static int first = 1; if(first){ first = 0; proccreate(eventproc, nil, STACK); } if(spec && spec[0]){ respond(r, "invalid attach specifier"); return; } *qid = (Qid){Qroot, 0}; respond(r, nil); } Srv fssrv = { .attach= fsattach, .walk= fswalk, .open= fsopen, .read= fsread, .write= fswrite, .stat= fsstat, .flush= fsflush, }; void threadmain(int argc, char **argv) { char *dev, *mtpt, *srv; dev = "/dev/apm"; mtpt = "/mnt/apm"; srv = nil; ARGBEGIN{ case 'A': apmdebug = 1; break; case 'D': lib9p_chatty = 1; break; case 'P': nopoll = 1; break; case 'd': dev = EARGF(usage()); break; case 'm': mtpt = EARGF(usage()); break; case 's': srv = EARGF(usage()); break; }ARGEND if((apm.fd = open(dev, ORDWR)) < 0){ threadprint(2, "open %s: %r\n", dev); threadexitsall("open"); } if(apmversion(&apm) < 0){ threadprint(2, "cannot get apm version: %r\n"); threadexitsall("apmversion"); } if(apm.verhi < 1 || (apm.verhi==1 && apm.verlo < 2)){ threadprint(2, "apm version %d.%d not supported\n", apm.verhi, apm.verlo); threadexitsall("apmversion"); } if(apmgetcapabilities(&apm) < 0) threadprint(2, "warning: cannot read apm capabilities: %r\n"); apminstallationcheck(&apm); apmcpuidle(&apm); threadnonotes(); threadpostmountsrv(&fssrv, srv, mtpt, MREPL); }