diff options
Diffstat (limited to 'os/sa1110/devpower.c')
| -rw-r--r-- | os/sa1110/devpower.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/os/sa1110/devpower.c b/os/sa1110/devpower.c new file mode 100644 index 00000000..c7cd5095 --- /dev/null +++ b/os/sa1110/devpower.c @@ -0,0 +1,377 @@ +/* + * power management + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "../port/error.h" + +typedef struct Power Power; +typedef struct Puser Puser; + +enum{ + Qdir, + Qctl, + Qdata +}; + +static +Dirtab powertab[]={ + ".", {Qdir, 0, QTDIR}, 0, 0500, + "powerctl", {Qctl, 0}, 0, 0600, + "powerdata", {Qdata, 0}, 0, 0666, +}; + +struct Puser { + Ref; + ulong alarm; /* real time clock alarm time, if non-zero */ + QLock rl; /* mutual exclusion to protect r */ + Rendez r; /* wait for event of interest */ + int state; /* shutdown state of this process */ + Puser* next; +}; + +enum{ + Pwridle, + Pwroff, + Pwrack +}; + +static struct { + QLock; + Puser *list; + Lock l; /* protect shutdown, nwaiting */ + int shutdown; /* non-zero if currently shutting down */ + int nwaiting; /* waiting for this many processes */ + Rendez ackr; /* wait here for all acks */ +} pwrusers; + + +static Chan* +powerattach(char* spec) +{ + return devattach(L'↓', spec); +} + +static int +powerwalk(Chan* c, char* name) +{ + return devwalk(c, name, powertab, nelem(powertab), devgen); +} + +static void +powerstat(Chan* c, char* db) +{ + devstat(c, db, powertab, nelem(powertab), devgen); +} + +static Chan* +poweropen(Chan* c, int omode) +{ + Puser *p; + + if(c->qid.type & QTDIR) + return devopen(c, omode, powertab, nelem(powertab), devgen); + switch(c->qid.path){ + case Qdata: + p = mallocz(sizeof(Puser), 1); + if(p == nil) + error(Enovmem); + p->state = Pwridle; + p->ref = 1; + if(waserror()){ + free(p); + nexterror(); + } + c = devopen(c, omode, powertab, nelem(powertab), devgen); + c->aux = p; + qlock(&pwrusers); + p->next = pwrusers.list; + pwrusers.list = p; /* note: must place on front of list for correct shutdown ordering */ + qunlock(&pwrusers); + poperror(); + break; + case Qctl: + c = devopen(c, omode, powertab, nelem(powertab), devgen); + break; + } + return c; +} + +static Chan * +powerclone(Chan *c, Chan *nc) +{ + Puser *p; + + nc = devclone(c, nc); + if((p = nc->aux) != nil) + incref(p); + return nc; +} + +static void +powerclose(Chan* c) +{ + Puser *p, **l; + + if(c->qid.type & QTDIR || (c->flag & COPEN) == 0) + return; + p = c->aux; + if(p != nil && decref(p) == 0){ + /* TO DO: cancel alarm */ + qlock(&pwrusers); + for(l = &pwrusers.list; *l != nil; l = &(*l)->next) + if(*l == p){ + *l = p->next; + break; + } + qunlock(&pwrusers); + free(p); + } +} + +static int +isshutdown(void *a) +{ + return ((Puser*)a)->state == Pwroff; +} + +static long +powerread(Chan* c, void* a, long n, vlong offset) +{ + Puser *p; + char *msg; + + switch(c->qid.path & ~CHDIR){ + case Qdir: + return devdirread(c, a, n, powertab, nelem(powertab), devgen); + case Qdata: + p = c->aux; + for(;;){ + if(!canqlock(&p->rl)) + error(Einuse); /* only one reader at a time */ + if(waserror()){ + qunlock(&p->rl); + nexterror(); + } + sleep(&p->r, isshutdown, p); + poperror(); + qunlock(&p->rl); + msg = nil; + lock(p); + if(p->state == Pwroff){ + msg = "power off"; + p->state = Pwrack; + } + unlock(p); + if(msg != nil) + return readstr(offset, a, n, msg); + } + break; + case Qctl: + default: + n=0; + break; + } + return n; +} + +static int +alldown(void*) +{ + return pwrusers.nwaiting == 0; +} + +static long +powerwrite(Chan* c, void *a, long n, vlong) +{ + Cmdbuf *cmd; + Puser *p; + + if(c->qid.type & QTDIR) + error(Ebadusefd); + cmd = parsecmd(a, n); + if(waserror()){ + free(cmd); + nexterror(); + } + switch(c->qid.path & ~CHDIR){ + case Qdata: + p = c->aux; + if(cmd->nf < 2) + error(Ebadarg); + if(strcmp(cmd->f[0], "ack") == 0){ + if(strcmp(cmd->f[1], "power") == 0){ + lock(p); + if(p->state == Pwrack){ + lock(&pwrusers.l); + if(pwrusers.shutdown && pwrusers.nwaiting > 0) + pwrusers.nwaiting--; + unlock(&pwrusers.l); + wakeup(&pwrusers.ackr); + p->state = Pwridle; + } + unlock(p); + }else + error(Ebadarg); + }else if(strcmp(cmd->f[0], "alarm") == 0){ + /* set alarm */ + }else + error(Ebadarg); + break; + case Qctl: + if(cmd->nf < 1) + error(Ebadarg); + if(strcmp(cmd->f[0], "suspend") == 0){ + /* start the suspend action */ + qlock(&pwrusers); + //powersuspend(0); /* calls poweringdown, then archsuspend() */ + qunlock(&pwrusers); + }else if(strcmp(cmd->f[0], "shutdown") == 0){ + /* go to it */ + qlock(&pwrusers); + if(waserror()){ + lock(&pwrusers.l); + pwrusers.shutdown = 0; /* hard luck for those already notified */ + unlock(&pwrusers.l); + qunlock(&pwrusers); + nexterror(); + } + lock(&pwrusers.l); + pwrusers.shutdown = 1; + pwrusers.nwaiting = 0; + unlock(&pwrusers.l); + for(p = pwrusers.list; p != nil; p = p->next){ + lock(p); + if(p->state == Pwridle){ + p->state = Pwroff; + lock(&pwrusers.l); + pwrusers.nwaiting++; + unlock(&pwrusers.l); + } + unlock(p); + wakeup(&p->r); + /* putting the tsleep here does each in turn; move out of loop to multicast */ + tsleep(&pwrusers.ackr, alldown, nil, 1000); + } + poperror(); + qunlock(&pwrusers); + //powersuspend(1); + }else + error(Ebadarg); + free(cmd); + break; + default: + error(Ebadusefd); + } + poperror(); + return n; +} + +/* + * device-level power management: suspend/resume/shutdown + */ + +struct Power { + void (*f)(int); + Power* prev; + Power* next; +}; + +static struct { + Lock; + Power list; +} power; + +void +powerenablereset(void) +{ + power.list.next = power.list.prev = &power.list; + power.list.f = (void*)-1; /* something not nil */ +} + +void +powerenable(void (*f)(int)) +{ + Power *p, *l; + + p = malloc(sizeof(*p)); + p->f = f; + p->prev = nil; + p->next = nil; + ilock(&power); + for(l = power.list.next; l != &power.list; l = l->next) + if(l->f == f){ + iunlock(&power); + free(p); + return; + } + l = &power.list; + p->prev = l->prev; + l->prev = p; + p->next = l; + p->prev->next = p; + iunlock(&power); +} + +void +powerdisable(void (*f)(int)) +{ + Power *l; + + ilock(&power); + for(l = power.list.next; l != &power.list; l = l->next) + if(l->f == f){ + l->prev->next = l->next; + l->next->prev = l->prev; + free(l); + break; + } + iunlock(&power); +} + +/* + * interrupts are assumed off so there's no need to lock + */ +void +poweringup(void) +{ + Power *l; + + for(l = power.list.next; l != &power.list; l = l->next) + (*l->f)(1); +} + +void +poweringdown(void) +{ + Power *l; + + for(l = power.list.prev; l != &power.list; l = l->prev) + (*l->f)(0); +} + +Dev powerdevtab = { + L'↓', + "power", + + devreset, + devinit, + powerattach, + devdetach, + powerclone, + powerwalk, + powerstat, + poweropen, + devcreate, + powerclose, + powerread, + devbread, + powerwrite, + devbwrite, + devremove, + devwstat, +}; |
