jehanne/sys/src/kern/amd64/devacpi.c

567 lines
10 KiB
C
Raw Normal View History

/* 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 <aml.h>
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<<gpes[n].enbit;
}
static void
setgpeen(int n, uint32_t v)
{
int old;
DBG("acpi: setgpe %d %d\n", n, v);
old = inb(gpes[n].enio);
if(v)
outb(gpes[n].enio, old | 1<<gpes[n].enbit);
else
outb(gpes[n].enio, old & ~(1<<gpes[n].enbit));
}
static void
clrgpests(int n)
{
outb(gpes[n].stsio, 1<<gpes[n].stsbit);
}
static uint32_t
getgpests(int n)
{
return inb(gpes[n].stsio) & 1<<gpes[n].stsbit;
}
static void
setpm1ctl(uint32_t a, uint32_t b)
{
setbanked(fadt.pm1acntblk, 0, fadt.pm1cntlen, a);
setbanked(0, fadt.pm1bcntblk, fadt.pm1cntlen, b);
}
void
acpipoweroff(void)
{
uint32_t *t;
enum {
Go = 1<<13,
Sstate = 1<<10,
};
iprint("acpi: power button: power cycle\n");
/*
* bug: we're assuming that this is a fixed function
*/
t = acpicfg.sval[5];
setpm1ctl(t[0]*Sstate | Go, t[1]*Sstate | Go);
}
void
acpipowercycle(void)
{
exit(0);
}
void
acpipowernop(void)
{
}
struct {
char *name;
void (*f)(void);
} pwrbuttab[] = {
"reset", acpipowercycle, /* sic */
"off", acpipoweroff,
"nop", acpipowernop,
};
static void
acpiintr(Ureg* _1, void* _2)
{
int i;
uint32_t sts, en;
Queue *q;
for(i = 0; i < ngpe; i++)
if(getgpests(i)){
iprint("gpe %d on\n", i);
en = getgpeen(i);
setgpeen(i, 0);
clrgpests(i);
if(en != 0)
jehanne_print("acpinitr: calling gpe %d\n", i);
/* queue gpe. reenable after done */
}
sts = getpm1sts(); /* § 4.8.3.3.1 */
en = getpm1en();
iprint("acpinitr: pm1sts %#ux pm1en %#ux\n", sts, en);
if(sts&en){
iprint("acpinitr: enabled: %#ux\n", sts&en);
}
setpm1sts(sts);
if(sts&Epowerbtn){
if((q = aconf.event) == nil ||
qiwrite(q, "powerbutton\n", 12) == -1)
aconf.powerbutton();
}
}
static void
initgpes(void)
{
int i, n0, n1;
n0 = fadt.gpe0blklen/2;
n1 = fadt.gpe1blklen/2;
ngpe = n0 + n1;
gpes = jehanne_mallocz(sizeof(Gpe) * ngpe, 1);
for(i = 0; i < n0; i++){
gpes[i].nb = i;
gpes[i].stsbit = i&7;
gpes[i].stsio = fadt.gpe0blk + (i>>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,
};