#include #include #include typedef struct Ureg Ureg; #include #include #include #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 requested 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) fprint(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) fprint(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); } 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 = 0, Qevent, Qbattery, Qctl, }; static void rootread(Req*); static void eventread(Req*); static void ctlread(Req*); static void ctlwrite(Req*); static void batteryread(Req*); typedef struct Dfile Dfile; struct Dfile { Qid qid; char *name; ulong mode; void (*read)(Req*); void (*write)(Req*); }; Dfile dfile[] = { { {Qroot,0,QTDIR}, "/", DMDIR|0555, rootread, nil, }, { {Qevent}, "event", 0444, eventread, nil, }, { {Qbattery}, "battery", 0444, batteryread, nil, }, { {Qctl}, "ctl", 0666, ctlread, ctlwrite, }, }; static int fillstat(uvlong path, Dir *d, int doalloc) { int i; for(i=0; iuid = doalloc ? estrdup("apm") : "apm"; d->gid = doalloc ? estrdup("apm") : "apm"; d->length = 0; d->name = doalloc ? estrdup(dfile[i].name) : dfile[i].name; d->mode = dfile[i].mode; d->atime = d->mtime = time(0); d->qid = dfile[i].qid; return 0; } static char* fswalk1(Fid *fid, char *name, Qid *qid) { int i; if(strcmp(name, "..")==0){ *qid = dfile[0].qid; fid->qid = *qid; return nil; } for(i=1; iqid = *qid; return nil; } } return "file does not exist"; } static void fsopen(Req *r) { switch((ulong)r->fid->qid.path){ case Qroot: r->fid->aux = (void*)0; respond(r, nil); return; case Qevent: case Qbattery: if(r->ifcall.mode == OREAD){ respond(r, nil); return; } break; case Qctl: if((r->ifcall.mode&~(OTRUNC|OREAD|OWRITE|ORDWR)) == 0){ respond(r, nil); return; } break; } respond(r, "permission denied"); return; } static void fsstat(Req *r) { fillstat(r->fid->qid.path, &r->d, 1); respond(r, nil); } static void fsread(Req *r) { dfile[r->fid->qid.path].read(r); } static void fswrite(Req *r) { dfile[r->fid->qid.path].write(r); } static void rootread(Req *r) { int n; uvlong offset; char *p, *ep; Dir d; if(r->ifcall.offset == 0) offset = 0; else offset = (uvlong)r->fid->aux; p = r->ofcall.data; ep = r->ofcall.data+r->ifcall.count; if(offset == 0) /* skip root */ offset = 1; for(; p+2 < ep; p+=n){ if(fillstat(offset, &d, 0) < 0) break; n = convD2M(&d, (uchar*)p, ep-p); if(n <= BIT16SZ) break; offset++; } r->fid->aux = (void*)offset; r->ofcall.count = p - r->ofcall.data; respond(r, nil); } static void batteryread(Req *r) { char buf[Mbattery*80], *ep, *p; int i; apmgetpowerstatus(&apm, DevAll); p = buf; ep = buf+sizeof buf; *p = '\0'; /* could be no batteries */ for(i=0; iifcall.count; if(count > sizeof(buf)-1) count = sizeof(buf)-1; memmove(buf, r->ifcall.data, count); buf[count] = '\0'; if(count && buf[count-1] == '\n'){ --count; 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) { 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(r, 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; respond(or, "interrupted"); 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->ifcall.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) { sendp(cflush, r); } static void eventread(Req *r) { sendp(creq, r); } static void fsattach(Req *r) { char *spec; static int first = 1; spec = r->ifcall.aname; if(first){ first = 0; proccreate(eventproc, nil, STACK); } if(spec && spec[0]){ respond(r, "invalid attach specifier"); return; } r->fid->qid = dfile[0].qid; r->ofcall.qid = dfile[0].qid; respond(r, nil); } Srv fs = { .attach= fsattach, .walk1= fswalk1, .open= fsopen, .read= fsread, .write= fswrite, .stat= fsstat, .flush= fsflush, }; void usage(void) { fprint(2, "usage: aux/apm [-ADPi] [-d /dev/apm] [-m /mnt/apm] [-s service]\n"); exits("usage"); } void threadmain(int argc, char **argv) { char *dev, *mtpt, *srv; dev = nil; mtpt = "/mnt/apm"; srv = nil; ARGBEGIN{ case 'A': apmdebug = 1; break; case 'D': chatty9p = 1; break; case 'P': nopoll = 1; break; case 'd': dev = EARGF(usage()); break; case 'i': fs.nopipe++; fs.infd = 0; fs.outfd = 1; mtpt = nil; break; case 'm': mtpt = EARGF(usage()); break; case 's': srv = EARGF(usage()); break; }ARGEND if(dev == nil){ if((apm.fd = open("/dev/apm", ORDWR)) < 0 && (apm.fd = open("#P/apm", ORDWR)) < 0){ fprint(2, "open %s: %r\n", dev); threadexitsall("open"); } } else if((apm.fd = open(dev, ORDWR)) < 0){ fprint(2, "open %s: %r\n", dev); threadexitsall("open"); } if(apmversion(&apm) < 0){ fprint(2, "cannot get apm version: %r\n"); threadexitsall("apmversion"); } if(apm.verhi < 1 || (apm.verhi==1 && apm.verlo < 2)){ fprint(2, "apm version %d.%d not supported\n", apm.verhi, apm.verlo); threadexitsall("apmversion"); } if(apmgetcapabilities(&apm) < 0) fprint(2, "warning: cannot read apm capabilities: %r\n"); apminstallationcheck(&apm); apmcpuidle(&apm); rfork(RFNOTEG); threadpostmountsrv(&fs, srv, mtpt, MREPL); }