/* Copyright (C) Charles Forsyth * See /doc/license/NOTICE.Plan9-9k.txt for details about the licensing. */ /* Portions of this file are Copyright (C) 9front's team. * See /doc/license/9front-mit for details about the licensing. * See http://code.9front.org/hg/plan9front/ for a list of authors. */ /* * ACPI 5.0 support. overly ornate. * - split table parsing out from file server */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/error.h" #include "acpi.h" #include enum { /* ACPI PM1 control */ Pscien = 1<<0, /* Generate SCI and not SMI */ Pbmrld = 1<<1, /* busmaster → C0 */ Pgblrls = 1<<2, /* global release */ /* pm1 events */ Etimer = 1<<0, Ebme = 1<<4, Eglobal = 1<<5, Epowerbtn = 1<<8, /* power button pressed */ Esleepbtn = 1<<9, Ertc = 1<<10, Epciewake = 1<<14, Ewake = 1<<15, }; typedef struct Aconf Aconf; typedef struct Gpe Gpe; struct Aconf { Lock; int init; void (*powerbutton)(void); uint32_t eventopen; Queue *event; }; struct Gpe { uintptr_t stsio; /* port used for status */ int stsbit; /* bit number */ uintptr_t enio; /* port used for enable */ int enbit; /* bit number */ int nb; /* event number */ char* obj; /* handler object */ int id; /* id as supplied by user */ }; enum { CMgpe, /* gpe name id */ CMpowerbut, CMpower, Qdir = 0, Qctl, Qevent, }; static Cmdtab ctls[] = { {CMgpe, "gpe", 3}, {CMpowerbut, "powerbutton", 2}, {CMpower, "power", 2}, }; static Dirtab acpidir[]={ ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, "acpictl", {Qctl}, 0, 0666, "acpievent", {Qevent, 0, QTEXCL}, 0, DMEXCL|0440, }; static Gpe* gpes; /* General purpose events */ static int ngpe; static Aconf aconf; static int acpigen(Chan *c, char* _1, Dirtab *tab, int ntab, int i, Dir *dp) { Qid qid; if(i == DEVDOTDOT){ mkqid(&qid, Qdir, 0, QTDIR); devdir(c, qid, ".", 0, eve, 0555, dp); return 1; } i++; /* skip first element for . itself */ if(tab==0 || i>=ntab) return -1; tab += i; qid = tab->qid; qid.path &= ~Qdir; qid.vers = 0; devdir(c, qid, tab->name, tab->length, eve, tab->perm, dp); return 1; } /* ra/rb are int not uintmem because inb/outb are in the i/o address space. */ static uint32_t getbanked(int ra, int rb, int sz) { uint32_t r; r = 0; switch(sz){ case 1: if(ra != 0) r |= inb(ra); if(rb != 0) r |= inb(rb); break; case 2: if(ra != 0) r |= ins(ra); if(rb != 0) r |= ins(rb); break; case 4: if(ra != 0) r |= inl(ra); if(rb != 0) r |= inl(rb); break; default: jehanne_print("getbanked: wrong size\n"); } return r; } static uint32_t setbanked(int ra, int rb, int sz, int v) { uint32_t r; r = -1; switch(sz){ case 1: if(ra != 0) outb(ra, v); if(rb != 0) outb(rb, v); break; case 2: if(ra != 0) outs(ra, v); if(rb != 0) outs(rb, v); break; case 4: if(ra != 0) outl(ra, v); if(rb != 0) outl(rb, v); break; default: jehanne_print("setbanked: wrong size\n"); } return r; } /* * we must read the register group *as a whole* */ static uint32_t getpm1ctl(void) { return getbanked(fadt.pm1acntblk, fadt.pm1bcntblk, fadt.pm1cntlen); } static uint32_t getpm1sts(void) { return getbanked(fadt.pm1aevtblk, fadt.pm1bevtblk, fadt.pm1evtlen) & 0xffff; } static uint32_t getpm1en(void) { return getbanked(fadt.pm1aevtblk, fadt.pm1bevtblk, fadt.pm1evtlen)>>16; } static void setpm1en(uint32_t v) { uint32_t r; r = getbanked(fadt.pm1aevtblk, fadt.pm1bevtblk, fadt.pm1evtlen); r &= 0xffff; setbanked(fadt.pm1aevtblk, fadt.pm1bevtblk, fadt.pm1evtlen, r | v<<16); } static void setpm1sts(uint32_t v) { uint32_t r; DBG("acpi: setpm1sts %#ux\n", v); r = getbanked(fadt.pm1aevtblk, fadt.pm1bevtblk, fadt.pm1evtlen); r &= 0xffff0000; setbanked(fadt.pm1aevtblk, fadt.pm1bevtblk, fadt.pm1evtlen, r | v); } static int getgpeen(int n) { return inb(gpes[n].enio) & 1<>3); gpes[i].enbit = (n0 + i)&7; gpes[i].enio = fadt.gpe0blk + ((n0 + i)>>3); } for(i = 0; i + n0 < ngpe; i++){ gpes[i + n0].nb = fadt.gp1base + i; gpes[i + n0].stsbit = i&7; gpes[i + n0].stsio = fadt.gpe1blk + (i>>3); gpes[i + n0].enbit = (n1 + i)&7; gpes[i + n0].enio = fadt.gpe1blk + ((n1 + i)>>3); } for(i = 0; i < ngpe; i++){ setgpeen(i, 0); clrgpests(i); } } static void acpiioalloc(uint32_t addr, int len, char *name) { char buf[32]; if(addr != 0){ jehanne_snprint(buf, sizeof buf, "acpi %s", name); ioalloc(addr, len, 0, buf); } } static void init(void) { int i; aconf.powerbutton = acpipoweroff; /* should we use fadt->xpm* and fadt->xgpe* registers for 64 bits? */ acpiioalloc(fadt.smicmd, 1, "scicmd"); acpiioalloc(fadt.pm1aevtblk, fadt.pm1evtlen, "pm1aevt"); acpiioalloc(fadt.pm1bevtblk, fadt.pm1evtlen, "pm1bevt"); acpiioalloc(fadt.pm1acntblk, fadt.pm1cntlen, "pm1acnt"); acpiioalloc(fadt.pm1bcntblk, fadt.pm1cntlen, "pm1bcnt"); acpiioalloc(fadt.pm2cntblk, fadt.pm2cntlen, "pm2cnt"); acpiioalloc(fadt.pmtmrblk, fadt.pmtmrlen, "pmtmr"); acpiioalloc(fadt.gpe0blk, fadt.gpe0blklen, "gpe0"); acpiioalloc(fadt.gpe1blk, fadt.gpe1blklen, "gpe1"); initgpes(); /* * This starts ACPI, which requires we handle * power mgmt events ourselves. */ if(fadt.sciint == 0) return; if((getpm1ctl() & Pscien) == 0){ outb(fadt.smicmd, fadt.acpienable); for(i = 0;; i++){ if(i == 10){ jehanne_print("acpi: failed to enable\n"); outb(fadt.smicmd, fadt.acpidisable); return; } if(getpm1ctl() & Pscien) break; } } if(0){ jehanne_print("acpi: enable interrupt\n"); setpm1sts(getpm1sts()); setpm1en(Epowerbtn); intrenable(fadt.sciint, acpiintr, 0, BUSUNKNOWN, "acpi"); } } static Chan* acpiattach(Chan *c, Chan *ac, char *spec, int flags) { if(fadt.smicmd == 0) error("no acpi"); return devattach(L'α', spec); } static Walkqid* acpiwalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, acpidir, nelem(acpidir), acpigen); } static long acpistat(Chan *c, uint8_t *dp, long n) { return devstat(c, dp, n, acpidir, nelem(acpidir), acpigen); } static Chan* acpiopen(Chan *c, unsigned long omode) { c = devopen(c, omode, acpidir, nelem(acpidir), acpigen); switch((uint32_t)c->qid.path){ case Qevent: if(tas32(&aconf.eventopen) != 0){ c->flag &= ~COPEN; error(Einuse); } if(aconf.event == nil){ aconf.event = qopen(8*1024, Qmsg, 0, 0); if(aconf.event == nil){ c->flag &= ~COPEN; error(Enomem); } qnoblock(aconf.event, 1); }else qreopen(aconf.event); break; } return c; } static void acpiclose(Chan *c) { switch((uint32_t)c->qid.path){ case Qevent: if(c->flag & COPEN){ aconf.eventopen = 0; qhangup(aconf.event, nil); } break; } } static long acpiread(Chan *c, void *a, long n, int64_t off) { char *s, *p, *e, buf[256]; int i; long q; q = c->qid.path; switch(q){ case Qdir: return devdirread(c, a, n, acpidir, nelem(acpidir), acpigen); case Qctl: p = buf; e = buf + sizeof buf; for(i = 0; i < nelem(pwrbuttab); i++) if(pwrbuttab[i].f == aconf.powerbutton) break; if(i == nelem(pwrbuttab)) s = "??"; else s = pwrbuttab[i].name; p = jehanne_seprint(p, e, "powerbutton %s\n", s); p = jehanne_seprint(p, e, "ngpe %d\n", ngpe); USED(p); return readstr(off, a, n, buf); case Qevent: return qread(aconf.event, a, n); } error(Eperm); return -1; } static long acpiwrite(Chan *c, void *a, long n, int64_t _1) { uint32_t i; Cmdtab *ct; Cmdbuf *cb; if(c->qid.path != Qctl) error(Eperm); cb = parsecmd(a, n); if(waserror()){ jehanne_free(cb); nexterror(); } ct = lookupcmd(cb, ctls, nelem(ctls)); switch(ct->index){ case CMgpe: i = jehanne_strtoul(cb->f[1], nil, 0); if(i >= ngpe) error("gpe out of range"); kstrdup(&gpes[i].obj, cb->f[2]); DBG("gpe %d %s\n", i, gpes[i].obj); setgpeen(i, 1); break; case CMpowerbut: for(i = 0; i < nelem(pwrbuttab); i++) if(jehanne_strcmp(cb->f[1], pwrbuttab[i].name) == 0){ ilock(&aconf); aconf.powerbutton = pwrbuttab[i].f; iunlock(&aconf); break; } if(i == nelem(pwrbuttab)) error("unknown power button action"); break; case CMpower: if(jehanne_strcmp(cb->f[1], "off") == 0) aconf.powerbutton(); else error("unknown power button command"); break; } poperror(); jehanne_free(cb); return n; } Dev acpidevtab = { L'α', "acpi", devreset, init, devshutdown, acpiattach, acpiwalk, acpistat, acpiopen, devcreate, acpiclose, acpiread, devbread, acpiwrite, devbwrite, devremove, devwstat, };