596 lines
12 KiB
C
596 lines
12 KiB
C
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
|
|
#include "apic.h"
|
|
#include "io.h"
|
|
#include "adr.h"
|
|
|
|
typedef struct Rbus Rbus;
|
|
typedef struct Rdt Rdt;
|
|
|
|
struct Rbus {
|
|
Rbus *next;
|
|
int bustype;
|
|
int devno;
|
|
Rdt *rdt;
|
|
};
|
|
|
|
struct Rdt {
|
|
IOapic *apic;
|
|
int intin;
|
|
uint32_t lo;
|
|
|
|
int ref; /* could map to multiple busses */
|
|
int enabled; /* times enabled */
|
|
};
|
|
|
|
enum { /* IOAPIC registers */
|
|
Ioregsel = 0x00, /* indirect register address */
|
|
Iowin = 0x04, /* indirect register data */
|
|
Ioipa = 0x08, /* IRQ Pin Assertion */
|
|
Ioeoi = 0x10, /* EOI */
|
|
|
|
IOapicid = 0x00, /* Identification */
|
|
IOapicver = 0x01, /* Version */
|
|
IOapicarb = 0x02, /* Arbitration */
|
|
Ioabcfg = 0x03, /* Boot Configuration */
|
|
Ioredtbl = 0x10, /* Redirection Table */
|
|
};
|
|
|
|
static Rdt rdtarray[Nrdt];
|
|
static int nrdtarray;
|
|
static int gsib;
|
|
static Rbus* rdtbus[Nbus];
|
|
static Rdt* rdtvecno[IdtMAX+1];
|
|
static int dfpolicy = 1; /* round-robin */
|
|
|
|
static Lock idtnolock;
|
|
static int idtno = IdtIOAPIC;
|
|
|
|
static IOapic xioapic[Napic];
|
|
static int isabusno = -1;
|
|
|
|
/* BOTCH: no need for this concept; we've got the bustype */
|
|
static void
|
|
ioapicisabus(int busno)
|
|
{
|
|
if(isabusno != -1){
|
|
if(busno == isabusno)
|
|
return;
|
|
jehanne_print("ioapic: isabus redefined: %d ↛ %d\n", isabusno, busno);
|
|
// return;
|
|
}
|
|
DBG("ioapic: isa busno %d\n", busno);
|
|
isabusno = busno;
|
|
}
|
|
|
|
IOapic*
|
|
ioapiclookup(uint32_t id)
|
|
{
|
|
IOapic *a;
|
|
|
|
if(id > nelem(xioapic))
|
|
return nil;
|
|
a = xioapic + id;
|
|
if(a->useable)
|
|
return a;
|
|
return nil;
|
|
}
|
|
|
|
int
|
|
gsitoapicid(int gsi, uint32_t *intin)
|
|
{
|
|
int i;
|
|
IOapic *a;
|
|
|
|
for(i=0; i<Napic; i++){
|
|
a = xioapic + i;
|
|
if(!a->useable)
|
|
continue;
|
|
if(gsi >= a->gsib && gsi < a->gsib+a->nrdt){
|
|
if(intin != nil)
|
|
*intin = gsi - a->gsib;
|
|
return a - xioapic;
|
|
}
|
|
}
|
|
// jehanne_print("gsitoapicid: no ioapic found for gsi %d\n", gsi);
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
rtblget(IOapic* apic, int sel, uint32_t* hi, uint32_t* lo)
|
|
{
|
|
sel = Ioredtbl + 2*sel;
|
|
|
|
apic->addr[Ioregsel] = sel+1;
|
|
*hi = apic->addr[Iowin];
|
|
apic->addr[Ioregsel] = sel;
|
|
*lo = apic->addr[Iowin];
|
|
}
|
|
|
|
static void
|
|
rtblput(IOapic* apic, int sel, uint32_t hi, uint32_t lo)
|
|
{
|
|
sel = Ioredtbl + 2*sel;
|
|
|
|
apic->addr[Ioregsel] = sel+1;
|
|
apic->addr[Iowin] = hi;
|
|
apic->addr[Ioregsel] = sel;
|
|
apic->addr[Iowin] = lo;
|
|
}
|
|
|
|
Rdt*
|
|
rdtlookup(IOapic *apic, int intin)
|
|
{
|
|
int i;
|
|
Rdt *r;
|
|
|
|
for(i = 0; i < nrdtarray; i++){
|
|
r = rdtarray + i;
|
|
if(apic == r->apic && intin == r->intin)
|
|
return r;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
void
|
|
ioapicintrinit(int bustype, int busno, int apicno, int intin, int devno, uint32_t lo)
|
|
{
|
|
Rbus *rbus;
|
|
Rdt *rdt;
|
|
IOapic *apic;
|
|
|
|
if(busno >= Nbus || apicno >= Napic || nrdtarray >= Nrdt)
|
|
return;
|
|
|
|
if(bustype == BusISA)
|
|
ioapicisabus(busno);
|
|
|
|
apic = &xioapic[apicno];
|
|
if(!apic->useable || intin >= apic->nrdt)
|
|
panic("ioapic: intrinit: usable %d nrdt %d: bus %d apic %d intin %d dev %d lo %.8ux\n",
|
|
apic->useable, apic->nrdt, busno, apicno, intin, devno, lo);
|
|
|
|
rdt = rdtlookup(apic, intin);
|
|
if(rdt == nil){
|
|
if(nrdtarray == nelem(rdtarray)){
|
|
jehanne_print("ioapic: intrinit: rdtarray too small\n");
|
|
return;
|
|
}
|
|
rdt = &rdtarray[nrdtarray++];
|
|
rdt->apic = apic;
|
|
rdt->intin = intin;
|
|
rdt->lo = lo;
|
|
}else{
|
|
if(lo != rdt->lo){
|
|
if(bustype == BusISA && intin < 16 && lo == (Im|IPhigh|TMedge)){
|
|
DBG("override: isa %d %.8ux\n", intin, rdt->lo);
|
|
return; /* expected; default was overridden*/
|
|
}
|
|
jehanne_print("multiple irq botch type %d bus %d %d/%d/%d lo %.8ux vs %.8ux\n",
|
|
bustype, busno, apicno, intin, devno, lo, rdt->lo);
|
|
return;
|
|
}
|
|
DBG("dup rdt %d %d %d %d %.8ux\n", busno, apicno, intin, devno, lo);
|
|
}
|
|
rdt->ref++;
|
|
rbus = jehanne_malloc(sizeof(*rbus));
|
|
rbus->rdt = rdt;
|
|
rbus->bustype = bustype;
|
|
rbus->devno = devno;
|
|
rbus->next = rdtbus[busno];
|
|
rdtbus[busno] = rbus;
|
|
}
|
|
|
|
/*
|
|
* deal with ioapics at the same physical address. seen on
|
|
* certain supermicro atom systems. the hope is that only
|
|
* one will be used, and it will be the second one initialized.
|
|
* (the pc kernel ignores this issue.) it could be that mp and
|
|
* acpi have different numbering?
|
|
*/
|
|
static IOapic*
|
|
dupaddr(uintmem pa)
|
|
{
|
|
int i;
|
|
IOapic *p;
|
|
|
|
for(i = 0; i < nelem(xioapic); i++){
|
|
p = xioapic + i;
|
|
if(p->paddr == pa)
|
|
return p;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
IOapic*
|
|
ioapicinit(int id, int ibase, uintmem pa)
|
|
{
|
|
IOapic *apic, *p;
|
|
|
|
/*
|
|
* Mark the IOAPIC useable if it has a good ID
|
|
* and the registers can be mapped.
|
|
*/
|
|
if(id >= Napic)
|
|
return nil;
|
|
if((apic = xioapic+id)->useable)
|
|
return apic;
|
|
|
|
if((p = dupaddr(pa)) != nil){
|
|
jehanne_print("ioapic%d: same pa as apic%ld\n", id, p-xioapic);
|
|
if(ibase != -1)
|
|
return nil; /* mp irqs reference mp apic#s */
|
|
apic->addr = p->addr;
|
|
}
|
|
else{
|
|
//adrmapck(pa, 1024, Ammio, Mfree, Cnone); /* not in adr? */ /* TO DO */
|
|
if((apic->addr = vmap(pa, 1024)) == nil){
|
|
jehanne_print("ioapic%d: can't vmap %#P\n", id, pa);
|
|
return nil;
|
|
}
|
|
}
|
|
apic->useable = 1;
|
|
apic->paddr = pa;
|
|
|
|
/*
|
|
* Initialise the I/O APIC.
|
|
* The MultiProcessor Specification says it is the
|
|
* responsibility of the O/S to set the APIC ID.
|
|
*/
|
|
lock(apic);
|
|
apic->addr[Ioregsel] = IOapicver;
|
|
apic->nrdt = ((apic->addr[Iowin]>>16) & 0xff) + 1;
|
|
if(ibase != -1)
|
|
apic->gsib = ibase;
|
|
else{
|
|
apic->gsib = gsib;
|
|
gsib += apic->nrdt;
|
|
}
|
|
apic->addr[Ioregsel] = IOapicid;
|
|
apic->addr[Iowin] = id<<24;
|
|
unlock(apic);
|
|
|
|
return apic;
|
|
}
|
|
|
|
void
|
|
iordtdump(void)
|
|
{
|
|
int i;
|
|
Rbus *rbus;
|
|
Rdt *rdt;
|
|
|
|
if(!DBGFLG)
|
|
return;
|
|
for(i = 0; i < Nbus; i++){
|
|
if((rbus = rdtbus[i]) == nil)
|
|
continue;
|
|
jehanne_print("iointr bus %d:\n", i);
|
|
for(; rbus != nil; rbus = rbus->next){
|
|
rdt = rbus->rdt;
|
|
jehanne_print(" apic %ld devno %#ux (%d %d) intin %d lo %#ux ref %d\n",
|
|
rdt->apic-xioapic, rbus->devno, rbus->devno>>2,
|
|
rbus->devno & 0x03, rdt->intin, rdt->lo, rdt->ref);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ioapicdump(void)
|
|
{
|
|
int i, n;
|
|
IOapic *apic;
|
|
uint32_t hi, lo;
|
|
|
|
if(!DBGFLG)
|
|
return;
|
|
for(i = 0; i < Napic; i++){
|
|
apic = &xioapic[i];
|
|
if(!apic->useable || apic->addr == 0)
|
|
continue;
|
|
jehanne_print("ioapic %d addr %#p nrdt %d ibase %d\n",
|
|
i, apic->addr, apic->nrdt, apic->gsib);
|
|
for(n = 0; n < apic->nrdt; n++){
|
|
lock(apic);
|
|
rtblget(apic, n, &hi, &lo);
|
|
unlock(apic);
|
|
jehanne_print(" rdt %2.2d %#8.8ux %#8.8ux\n", n, hi, lo);
|
|
}
|
|
}
|
|
}
|
|
|
|
static char*
|
|
ioapicprint(char *p, char *e, IOapic *a, int i)
|
|
{
|
|
char *s;
|
|
|
|
s = "ioapic";
|
|
p = jehanne_seprint(p, e, "%-8s ", s);
|
|
p = jehanne_seprint(p, e, "%8ux ", i);
|
|
p = jehanne_seprint(p, e, "%6d ", a->gsib);
|
|
p = jehanne_seprint(p, e, "%6d ", a->gsib+a->nrdt-1);
|
|
p = jehanne_seprint(p, e, "%#P ", a->paddr);
|
|
p = jehanne_seprint(p, e, "\n");
|
|
return p;
|
|
}
|
|
|
|
static long
|
|
ioapicread(Chan* _1, void *a, long n, int64_t off)
|
|
{
|
|
char *s, *e, *p;
|
|
long i, r;
|
|
|
|
s = jehanne_malloc(READSTR);
|
|
e = s+READSTR;
|
|
p = s;
|
|
|
|
for(i = 0; i < nelem(xioapic); i++)
|
|
if(xioapic[i].useable)
|
|
p = ioapicprint(p, e, xioapic + i, i);
|
|
r = -1;
|
|
if(!waserror()){
|
|
r = readstr(off, a, n, s);
|
|
poperror();
|
|
}
|
|
jehanne_free(s);
|
|
return r;
|
|
}
|
|
|
|
void
|
|
ioapiconline(void)
|
|
{
|
|
int i;
|
|
IOapic *apic;
|
|
|
|
addarchfile("ioapic", 0444, ioapicread, nil);
|
|
for(apic = xioapic; apic < &xioapic[Napic]; apic++){
|
|
if(!apic->useable || apic->addr == nil)
|
|
continue;
|
|
for(i = 0; i < apic->nrdt; i++){
|
|
lock(apic);
|
|
rtblput(apic, i, 0, Im);
|
|
unlock(apic);
|
|
}
|
|
}
|
|
jehanne_print("init ioapic dump\n");
|
|
ioapicdump();
|
|
}
|
|
|
|
static int
|
|
ioapicintrdd(uint32_t* hi, uint32_t* lo)
|
|
{
|
|
Lapic *lapic;
|
|
Mach *mach;
|
|
int i;
|
|
static int df;
|
|
|
|
/*
|
|
* Set delivery mode (lo) and destination field (hi)
|
|
*
|
|
* Currently, assign each interrupt to a different CPU
|
|
* using physical mode delivery. Using the topology
|
|
* (packages/cores/threads) could be helpful.
|
|
*/
|
|
switch(dfpolicy){
|
|
case 0:
|
|
i = sys->machptr[0]->apicno;
|
|
break;
|
|
default: /* round-robin */
|
|
for(;;){
|
|
i = df;
|
|
if(++df >= Napic)
|
|
df = 0;
|
|
if((lapic = lapiclookup(i)) != nil &&
|
|
(mach = sys->machptr[lapic->machno]) != nil &&
|
|
mach->online)
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
*hi = i<<24;
|
|
*lo |= Pm|MTf;
|
|
return i;
|
|
}
|
|
|
|
int
|
|
nextvec(void)
|
|
{
|
|
uint32_t vecno;
|
|
|
|
lock(&idtnolock);
|
|
vecno = idtno;
|
|
idtno = (idtno+8) % IdtMAX;
|
|
if(idtno < IdtIOAPIC)
|
|
idtno += IdtIOAPIC;
|
|
unlock(&idtnolock);
|
|
|
|
return vecno;
|
|
}
|
|
|
|
static int
|
|
msimask(Vctl *v, int mask)
|
|
{
|
|
Pcidev *p;
|
|
|
|
p = pcimatchtbdf(v->tbdf);
|
|
if(p == nil)
|
|
return -1;
|
|
return pcimsimask(p, mask);
|
|
}
|
|
|
|
static int
|
|
intrenablemsi(Vctl* v, Pcidev *p)
|
|
{
|
|
uint32_t vno, lo, hi;
|
|
uint64_t msivec;
|
|
|
|
vno = nextvec();
|
|
|
|
lo = IPlow | TMedge | vno;
|
|
v->affinity = ioapicintrdd(&hi, &lo);
|
|
|
|
if(lo & Lm)
|
|
lo |= MTlp;
|
|
|
|
msivec = (uint64_t)hi<<32 | lo;
|
|
if(pcimsienable(p, msivec) == -1)
|
|
return -1;
|
|
v->isr = lapicisr;
|
|
v->eoi = lapiceoi;
|
|
v->vno = vno;
|
|
v->type = "msi";
|
|
v->mask = msimask;
|
|
|
|
DBG("msiirq: %T: enabling %.16llux %s irq %d vno %d\n", p->tbdf, msivec, v->name, v->irq, vno);
|
|
return vno;
|
|
}
|
|
|
|
int
|
|
disablemsi(Vctl* _1, Pcidev *p)
|
|
{
|
|
if(p == nil)
|
|
return -1;
|
|
return pcimsimask(p, 1);
|
|
}
|
|
|
|
int
|
|
ioapicintrenable(Vctl* v)
|
|
{
|
|
Rbus *rbus;
|
|
Rdt *rdt;
|
|
uint32_t hi, lo;
|
|
int bustype, busno, devno, vecno;
|
|
|
|
if(v->tbdf == BUSUNKNOWN){
|
|
if(v->irq >= IdtLINT0 && v->irq <= IdtMAX){
|
|
if(v->irq != IdtSPURIOUS)
|
|
v->isr = lapiceoi;
|
|
v->type = "lapic";
|
|
return v->irq;
|
|
}
|
|
else{
|
|
/*
|
|
* Legacy ISA.
|
|
* Make a busno and devno using the
|
|
* ISA bus number and the irq.
|
|
*/
|
|
if(isabusno == -1)
|
|
panic("no ISA bus allocated");
|
|
busno = isabusno;
|
|
devno = v->irq;
|
|
bustype = BusISA;
|
|
}
|
|
}
|
|
else if((bustype = BUSTYPE(v->tbdf)) == BusPCI){
|
|
/*
|
|
* PCI.
|
|
* Make a devno from BUSDNO(tbdf) and pcidev->intp.
|
|
*/
|
|
Pcidev *pcidev;
|
|
|
|
busno = BUSBNO(v->tbdf);
|
|
if((pcidev = pcimatchtbdf(v->tbdf)) == nil)
|
|
panic("no PCI dev for tbdf %T", v->tbdf);
|
|
if((vecno = intrenablemsi(v, pcidev)) != -1)
|
|
return vecno;
|
|
disablemsi(v, pcidev);
|
|
if((devno = pcicfgr8(pcidev, PciINTP)) == 0)
|
|
panic("no INTP for tbdf %T", v->tbdf);
|
|
devno = BUSDNO(v->tbdf)<<2|(devno-1);
|
|
DBG("ioapicintrenable: tbdf %T busno %d devno %d\n",
|
|
v->tbdf, busno, devno);
|
|
}
|
|
else{
|
|
SET(busno, devno);
|
|
panic("unknown tbdf %T", v->tbdf);
|
|
}
|
|
rdt = nil;
|
|
for(rbus = rdtbus[busno]; rbus != nil; rbus = rbus->next)
|
|
if(rbus->devno == devno && rbus->bustype == bustype){
|
|
rdt = rbus->rdt;
|
|
break;
|
|
}
|
|
if(rdt == nil){
|
|
/*
|
|
* PCI devices defaulted to ISA (ACPI).
|
|
*/
|
|
if((busno = isabusno) == -1)
|
|
return -1;
|
|
devno = v->irq;
|
|
for(rbus = rdtbus[busno]; rbus != nil; rbus = rbus->next)
|
|
if(rbus->devno == devno){
|
|
rdt = rbus->rdt;
|
|
break;
|
|
}
|
|
DBG("isa: tbdf %T busno %d devno %d %#p\n",
|
|
v->tbdf, busno, devno, rdt);
|
|
}
|
|
if(rdt == nil)
|
|
return -1;
|
|
|
|
/*
|
|
* Assume this is a low-frequency event so just lock
|
|
* the whole IOAPIC to initialise the RDT entry
|
|
* rather than putting a Lock in each entry.
|
|
*/
|
|
lock(rdt->apic);
|
|
DBG("%T: %ld/%d/%d (%d)\n", v->tbdf, rdt->apic - xioapic, rbus->devno, rdt->intin, devno);
|
|
if((rdt->lo & 0xff) == 0){
|
|
vecno = nextvec();
|
|
rdt->lo |= vecno;
|
|
rdtvecno[vecno] = rdt;
|
|
}else
|
|
DBG("%T: mutiple irq bus %d dev %d\n", v->tbdf, busno, devno);
|
|
|
|
rdt->enabled++;
|
|
lo = (rdt->lo & ~Im);
|
|
v->affinity = ioapicintrdd(&hi, &lo);
|
|
rtblput(rdt->apic, rdt->intin, hi, lo);
|
|
vecno = lo & 0xff;
|
|
unlock(rdt->apic);
|
|
|
|
DBG("busno %d devno %d hi %#.8ux lo %#.8ux vecno %d\n",
|
|
busno, devno, hi, lo, vecno);
|
|
v->isr = lapicisr;
|
|
v->eoi = lapiceoi;
|
|
v->vno = vecno;
|
|
v->type = "ioapic";
|
|
|
|
return vecno;
|
|
}
|
|
|
|
int
|
|
ioapicintrdisable(int vecno)
|
|
{
|
|
Rdt *rdt;
|
|
|
|
/*
|
|
* FOV. Oh dear. This isn't very good.
|
|
* Fortunately rdtvecno[vecno] is static
|
|
* once assigned.
|
|
* Must do better.
|
|
*
|
|
* What about any pending interrupts?
|
|
*/
|
|
if(vecno < 0 || vecno > IdtMAX){
|
|
panic("ioapicintrdisable: vecno %d out of range", vecno);
|
|
return -1;
|
|
}
|
|
if((rdt = rdtvecno[vecno]) == nil){
|
|
panic("ioapicintrdisable: vecno %d has no rdt", vecno);
|
|
return -1;
|
|
}
|
|
|
|
lock(rdt->apic);
|
|
rdt->enabled--;
|
|
if(rdt->enabled == 0)
|
|
rtblput(rdt->apic, rdt->intin, 0, rdt->lo);
|
|
unlock(rdt->apic);
|
|
|
|
return 0;
|
|
}
|