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

567 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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,
};