blob: 70d91a595293efcc422fed4f5c33f5fe34167a2c [file] [log] [blame]
/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#include <slab.h>
#include <kmalloc.h>
#include <kref.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <error.h>
#include <cpio.h>
#include <pmap.h>
#include <smp.h>
#include <net/ip.h>
#include <arch/io.h>
#include <acpi.h>
#include <trap.h>
/* Rbus chains, one for each device bus: each rbus matches a device to an rdt */
struct Rbus {
struct Rbus *next;
int devno;
struct Rdt *rdt;
};
/* Each rdt describes an ioapic input pin (intin, from the bus/device) */
struct Rdt {
struct apic *apic;
int intin;
uint32_t lo; /* matches the lo in the intin, incl Im */
uint32_t hi; /* matches the hi in the intin, incl routing */
int ref; /* could map to multiple busses */
int enabled; /* times enabled */
};
enum { /* IOAPIC registers */
Ioregsel = 0x00, /* indirect register address */
Iowin = 0x10, /* indirect register data */
Ioipa = 0x08, /* IRQ Pin Assertion */
Ioeoi = 0x10, /* EOI */
Ioapicid = 0x00, /* Identification */
Ioapicver = 0x01, /* Version */
Ioapicarb = 0x02, /* Arbitration */
Ioabcfg = 0x03, /* Boot Coniguration */
Ioredtbl = 0x10, /* Redirection Table */
};
static struct Rdt rdtarray[Nrdt];
static int nrdtarray;
static struct Rbus *rdtbus[Nbus];
/* reverse mapping of IDT vector to the RDT/IOAPIC entry triggering vector */
static struct Rdt *rdtvecno[IdtMAX + 1];
static spinlock_t idtnolock;
static int idtno = IdtIOAPIC;
struct apic xioapic[Napic];
static bool ioapic_exists(void)
{
/* not foolproof, if we called this before parsing */
for (int i = 0; i < Napic; i++)
if (xioapic[i].useable)
return TRUE;
return FALSE;
}
static void rtblget(struct apic *apic, int sel, uint32_t * hi, uint32_t * lo)
{
sel = Ioredtbl + 2 * sel;
write_mmreg32(apic->addr + Ioregsel, sel + 1);
*hi = read_mmreg32(apic->addr + Iowin);
write_mmreg32(apic->addr + Ioregsel, sel);
*lo = read_mmreg32(apic->addr + Iowin);
}
static void rtblput(struct apic *apic, int sel, uint32_t hi, uint32_t lo)
{
sel = Ioredtbl + 2 * sel;
write_mmreg32(apic->addr + Ioregsel, sel + 1);
write_mmreg32(apic->addr + Iowin, hi);
write_mmreg32(apic->addr + Ioregsel, sel);
write_mmreg32(apic->addr + Iowin, lo);
}
struct Rdt *rdtlookup(struct apic *apic, int intin)
{
int i;
struct Rdt *r;
for (i = 0; i < nrdtarray; i++) {
r = rdtarray + i;
if (apic == r->apic && intin == r->intin)
return r;
}
return NULL;
}
struct Rdt *rbus_get_rdt(int busno, int devno)
{
struct Rbus *rbus;
for (rbus = rdtbus[busno]; rbus != NULL; rbus = rbus->next) {
if (rbus->devno == devno)
return rbus->rdt;
}
return 0;
}
/* builds RDT and Rbus entries, given the wiring of bus:dev to ioapicno:intin.
* - busno is the source bus
* - devno is the device number in the style of a PCI Interrupt Assignment
* Entry. Which is the irq << 2 (check MP spec D.3).
* - ioapic is the ioapic the device is connected to
* - intin is the INTIN pin on the ioapic
* - lo is the lower part of the IOAPIC apic-message, which has the polarity and
* trigger mode flags. */
void ioapicintrinit(int busno, int ioapicno, int intin, int devno, int lo)
{
struct Rbus *rbus;
struct Rdt *rdt;
struct apic *ioapic;
if (busno >= Nbus || ioapicno >= Napic || nrdtarray >= Nrdt) {
printk("Bad bus %d ioapic %d or nrdtarray %d too big\n", busno,
ioapicno, nrdtarray);
return;
}
ioapic = &xioapic[ioapicno];
if (!ioapic->useable || intin >= ioapic->nrdt) {
printk("IOAPIC unusable (%d) or not enough nrdt (%d) for %d\n",
ioapic->useable, ioapic->nrdt, intin);
return;
}
rdt = rdtlookup(ioapic, intin);
if (rdt == NULL) {
rdt = &rdtarray[nrdtarray++];
rdt->apic = ioapic;
rdt->intin = intin;
rdt->lo = lo;
rdt->hi = 0;
} else {
/* Polarity/trigger check. Stored lo also has the vector in
* 0xff */
if (lo != (rdt->lo & ~0xff)) {
printk("multi-irq botch bus %d %d/%d/%d lo %d vs %d\n",
busno, ioapicno, intin, devno, lo, rdt->lo);
return;
}
}
/* TODO: this shit is racy. (refcnt, linked list addition) */
rdt->ref++;
rbus = kzmalloc(sizeof *rbus, 0);
rbus->rdt = rdt;
rbus->devno = devno;
rbus->next = rdtbus[busno];
rdtbus[busno] = rbus;
}
static int map_polarity[4] = {
-1, IPhigh, -1, IPlow
};
static int map_edge_level[4] = {
-1, TMedge, -1, TMlevel
};
static int acpi_irq2ioapic(int irq)
{
int ioapic_idx = 0;
struct apic *ioapic;
/* with acpi, the ioapics map a global interrupt space. each covers a
* window of the space from [ibase, ibase + nrdt). */
for (ioapic = xioapic; ioapic < &xioapic[Napic]; ioapic++, ioapic_idx++)
{
/* addr check is just for sanity */
if (!ioapic->useable || !ioapic->addr)
continue;
if ((ioapic->ibase <= irq) &&
(irq < ioapic->ibase + ioapic->nrdt))
return ioapic_idx;
}
return -1;
}
/* Build an RDT route, like we would have had from the MP tables had they been
* parsed, via ACPI.
*
* This only really deals with the ISA IRQs and maybe PCI ones that happen to
* have an override. FWIW, on qemu the PCI NIC shows up as an ACPI intovr.
*
* From Brendan http://f.osdev.org/viewtopic.php?f=1&t=25951:
*
* Before parsing the MADT you should begin by assuming that redirection
* entries 0 to 15 are used for ISA IRQs 0 to 15. The MADT's "Interrupt
* Source Override Structures" will tell you when this initial/default
* assumption is wrong. For example, the MADT might tell you that ISA IRQ 9
* is connected to IO APIC 44 and is level triggered; and (in this case)
* it'd be silly to assume that ISA IRQ 9 is also connected to IO APIC
* input 9 just because IO APIC input 9 is not listed.
*
* For PCI IRQs, the MADT tells you nothing and you can't assume anything
* at all. Sadly, you have to interpret the ACPI AML to determine how PCI
* IRQs are connected to IO APIC inputs (or find some other work-around;
* like implementing a motherboard driver for each different motherboard,
* or some complex auto-detection scheme, or just configure PCI devices to
* use MSI instead). */
static int acpi_make_rdt(int tbdf, int irq, int busno, int devno)
{
struct Atable *at;
struct Apicst *st, *lst;
uint32_t lo;
int pol, edge_level, ioapic_nr, gsi_irq;
at = apics;
st = NULL;
for (int i = 0; i < at->nchildren; i++) {
lst = at->children[i]->tbl;
if (lst->type == ASintovr) {
if (lst->intovr.irq == irq) {
st = lst;
break;
}
}
}
if (st) {
pol = map_polarity[st->intovr.flags & AFpmask];
if (pol < 0) {
printk("ACPI override had bad polarity\n");
return -1;
}
edge_level = map_edge_level[(st->intovr.flags & AFlevel) >> 2];
if (edge_level < 0) {
printk("ACPI override had bad edge/level\n");
return -1;
}
lo = pol | edge_level;
gsi_irq = st->intovr.intr;
} else {
if (BUSTYPE(tbdf) == BusISA) {
lo = IPhigh | TMedge;
gsi_irq = irq;
} else {
/* Need to query ACPI at some point to handle this */
printk("Non-ISA IRQ %d not found in MADT, aborting\n",
irq);
return -1;
}
}
ioapic_nr = acpi_irq2ioapic(gsi_irq);
if (ioapic_nr < 0) {
printk("Could not find an IOAPIC for global irq %d!\n",
gsi_irq);
return -1;
}
ioapicintrinit(busno, ioapic_nr, gsi_irq - xioapic[ioapic_nr].ibase,
devno, lo);
return 0;
}
void ioapicinit(int id, int ibase, uintptr_t pa)
{
struct apic *apic;
static int base;
assert((IOAPIC_PBASE <= pa) &&
(pa + PGSIZE <= IOAPIC_PBASE + APIC_SIZE));
/*
* Mark the IOAPIC useable if it has a good ID
* and the registers can be mapped.
*/
if (id >= Napic)
return;
apic = &xioapic[id];
apic->addr = IOAPIC_BASE + (pa - IOAPIC_PBASE);
if (apic->useable)
return;
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.
*/
spin_lock(&apic->lock);
write_mmreg32(apic->addr + Ioregsel, Ioapicver);
apic->nrdt = ((read_mmreg32(apic->addr + Iowin) >> 16) & 0xff) + 1;
/* the ibase is the global system interrupt base, told to us by ACPI.
* if it's -1, we're called from mpparse, and just guess/make up our own
* assignments. */
if (ibase != -1)
apic->ibase = ibase;
else {
apic->ibase = base;
base += apic->nrdt;
}
write_mmreg32(apic->addr + Ioregsel, Ioapicid);
write_mmreg32(apic->addr + Iowin, id << 24);
spin_unlock(&apic->lock);
printk("IOAPIC initialized at %p, nrdt %d, ibase %d\n", apic->addr,
apic->nrdt, apic->ibase);
}
char *ioapicdump(char *start, char *end)
{
int i, n;
struct Rbus *rbus;
struct Rdt *rdt;
struct apic *apic;
uint32_t hi, lo;
if (!2)
return start;
for (i = 0; i < Napic; i++) {
apic = &xioapic[i];
if (!apic->useable || apic->addr == 0)
continue;
start = seprintf(start, end,
"ioapic %d addr %p nrdt %d ibase %d\n",
i, apic->addr, apic->nrdt, apic->ibase);
for (n = 0; n < apic->nrdt; n++) {
spin_lock(&apic->lock);
rtblget(apic, n, &hi, &lo);
spin_unlock(&apic->lock);
start = seprintf(start, end, " rdt %2.2d %p %p\n",
n, hi, lo);
}
}
for (i = 0; i < Nbus; i++) {
if ((rbus = rdtbus[i]) == NULL)
continue;
start = seprintf(start, end, "iointr bus %d:\n", i);
for (; rbus != NULL; rbus = rbus->next) {
rdt = rbus->rdt;
start = seprintf(start, end,
" apic %ld devno %p(%d %d) intin %d hi %p lo %p\n",
rdt->apic - xioapic, rbus->devno,
rbus->devno >> 2, rbus->devno & 0x03,
rdt->intin, rdt->hi, rdt->lo);
}
}
return start;
}
/* Zeros and masks every redirect entry in every IOAPIC */
void ioapiconline(void)
{
int i;
struct apic *apic;
for (apic = xioapic; apic < &xioapic[Napic]; apic++) {
if (!apic->useable || !apic->addr)
continue;
for (i = 0; i < apic->nrdt; i++) {
spin_lock(&apic->lock);
rtblput(apic, i, 0, Im);
spin_unlock(&apic->lock);
}
}
}
int nextvec(void)
{
unsigned int vecno;
/* TODO: half-way decent integer service (vmem) */
spin_lock(&idtnolock);
vecno = idtno;
idtno = (idtno + 1) % IdtMAX;
if (idtno < IdtIOAPIC)
idtno += IdtIOAPIC;
spin_unlock(&idtnolock);
return vecno;
}
static void msi_mask_irq(struct irq_handler *irq_h, int apic_vector)
{
pci_msi_mask(irq_h->dev_private);
}
static void msi_unmask_irq(struct irq_handler *irq_h, int apic_vector)
{
pci_msi_unmask(irq_h->dev_private);
}
static void msi_route_irq(struct irq_handler *irq_h, int apic_vector, int dest)
{
pci_msi_route(irq_h->dev_private, dest);
}
static void msix_mask_irq(struct irq_handler *irq_h, int apic_vector)
{
pci_msix_mask_vector(irq_h->dev_private);
}
static void msix_unmask_irq(struct irq_handler *irq_h, int apic_vector)
{
pci_msix_unmask_vector(irq_h->dev_private);
}
static void msix_route_irq(struct irq_handler *irq_h, int apic_vector, int dest)
{
pci_msix_route_vector(irq_h->dev_private, dest);
}
static int msi_irq_enable(struct irq_handler *irq_h, struct pci_device *p)
{
unsigned int vno, lo, hi = 0;
uint64_t msivec;
struct msix_irq_vector *linkage;
vno = nextvec();
/* routing the IRQ to core 0 (hi = 0) in physical mode (Pm) */
lo = IPlow | TMedge | Pm | vno;
msivec = (uint64_t) hi << 32 | lo;
irq_h->dev_private = pci_msix_enable(p, msivec);
if (!irq_h->dev_private) {
if (pci_msi_enable(p, msivec) == -1) {
/* TODO: should free vno here */
return -1;
}
irq_h->dev_private = p;
irq_h->check_spurious = lapic_check_spurious;
irq_h->eoi = lapic_send_eoi;
irq_h->mask = msi_mask_irq;
irq_h->unmask = msi_unmask_irq;
irq_h->route_irq = msi_route_irq;
irq_h->type = "msi";
printk("MSI irq: (%x,%x,%x): enabling %p %s vno %d\n",
p->bus, p->dev, p->func, msivec, irq_h->name, vno);
return vno;
}
irq_h->check_spurious = lapic_check_spurious;
irq_h->eoi = lapic_send_eoi;
irq_h->mask = msix_mask_irq;
irq_h->unmask = msix_unmask_irq;
irq_h->route_irq = msix_route_irq;
irq_h->type = "msi-x";
printk("MSI-X irq: (%x,%x,%x): enabling %p %s vno %d\n",
p->bus, p->dev, p->func, msivec, irq_h->name, vno);
return vno;
}
static struct Rdt *ioapic_vector2rdt(int apic_vector)
{
struct Rdt *rdt;
if (apic_vector < IdtIOAPIC || apic_vector > MaxIdtIOAPIC) {
warn("ioapic vector %d out of range", apic_vector);
return 0;
}
/* Fortunately rdtvecno[vecno] is static once assigned. o/w, we'll need
* some global sync for the callers, both for lookup and keeping rdt
* valid. */
rdt = rdtvecno[apic_vector];
if (!rdt) {
warn("vector %d has no RDT! (did you enable it?)", apic_vector);
return 0;
}
return rdt;
}
/* Routes the IRQ to the hw_coreid. Will take effect immediately. Route
* masking from rdt->lo will take effect. The early return cases are probably
* bugs in IOAPIC irq_h setup. */
static void ioapic_route_irq(struct irq_handler *unused, int apic_vector,
int hw_coreid)
{
struct Rdt *rdt = ioapic_vector2rdt(apic_vector);
if (!rdt) {
printk("Missing IOAPIC route for vector!\n", apic_vector);
return;
}
spin_lock(&rdt->apic->lock);
/* this bit gets set in apicinit, only if we found it via MP or ACPI */
if (!xlapic[hw_coreid].useable) {
printk("Can't route to uninitialized LAPIC %d!\n", hw_coreid);
spin_unlock(&rdt->apic->lock);
return;
}
rdt->hi = hw_coreid << 24;
rdt->lo |= Pm | MTf;
rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
spin_unlock(&rdt->apic->lock);
}
static void ioapic_mask_irq(struct irq_handler *unused, int apic_vector)
{
/* could store the rdt in the irq_h */
struct Rdt *rdt = ioapic_vector2rdt(apic_vector);
if (!rdt)
return;
spin_lock(&rdt->apic->lock);
/* don't allow shared vectors to be masked. whatever. */
if (rdt->enabled > 1) {
spin_unlock(&rdt->apic->lock);
return;
}
rdt->lo |= Im;
rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
spin_unlock(&rdt->apic->lock);
}
static void ioapic_unmask_irq(struct irq_handler *unused, int apic_vector)
{
struct Rdt *rdt = ioapic_vector2rdt(apic_vector);
if (!rdt)
return;
spin_lock(&rdt->apic->lock);
rdt->lo &= ~Im;
rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
spin_unlock(&rdt->apic->lock);
}
/* Attempts to init a bus interrupt, initializes irq_h, and returns the IDT
* vector to use (-1 on error). If routable, the IRQ will route to core 0. The
* IRQ will be masked, if possible. Call irq_h->unmask() when you're ready.
*
* This will determine the type of bus the device is on (LAPIC, IOAPIC, PIC,
* etc), and set the appropriate fields in isr_h. If applicable, it'll also
* allocate an IDT vector, such as for an IOAPIC, and route the IOAPIC entries
* appropriately.
*
* Callers init irq_h->dev_irq and ->tbdf. tbdf encodes the bus type and the
* classic PCI bus:dev:func. dev_irq may be ignored based on the bus type (e.g.
* PCI, esp MSI).
*
* In plan9, this was ioapicintrenable(), which also unmasked. We don't have a
* deinit/disable method that would tear down the route yet. All the plan9 one
* did was dec enabled and mask the entry. */
int bus_irq_setup(struct irq_handler *irq_h)
{
struct Rbus *rbus;
struct Rdt *rdt;
int busno, devno, vecno;
struct pci_device *pcidev;
if (!ioapic_exists()) {
switch (BUSTYPE(irq_h->tbdf)) {
case BusLAPIC:
case BusIPI:
break;
default:
irq_h->check_spurious = pic_check_spurious;
irq_h->eoi = pic_send_eoi;
irq_h->mask = pic_mask_irq;
irq_h->unmask = pic_unmask_irq;
irq_h->route_irq = 0;
irq_h->type = "pic";
/* PIC devices have vector = irq + 32 */
return irq_h->dev_irq + IdtPIC;
}
}
switch (BUSTYPE(irq_h->tbdf)) {
case BusLAPIC:
/* nxm used to set the initial 'isr' method (i think equiv to
* our check_spurious) to apiceoi for non-spurious lapic
* vectors. in effect, i think they were sending the EOI early,
* and their eoi method was 0. we're not doing that (unless we
* have to). */
irq_h->check_spurious = lapic_check_spurious;
irq_h->eoi = lapic_send_eoi;
irq_h->mask = lapic_mask_irq;
irq_h->unmask = lapic_unmask_irq;
irq_h->route_irq = 0;
irq_h->type = "lapic";
/* For the LAPIC, irq == vector */
return irq_h->dev_irq;
case BusIPI:
/* similar to LAPIC, but we don't actually have LVT entries */
irq_h->check_spurious = lapic_check_spurious;
irq_h->eoi = lapic_send_eoi;
irq_h->mask = 0;
irq_h->unmask = 0;
irq_h->route_irq = 0;
irq_h->type = "IPI";
return irq_h->dev_irq;
case BusISA:
if (mpisabusno == -1)
panic("No ISA bus allocated");
busno = mpisabusno;
/* need to track the irq in devno in PCI interrupt assignment
* entry format (see mp.c or MP spec D.3). */
devno = irq_h->dev_irq << 2;
break;
case BusPCI:
pcidev = pci_match_tbdf(irq_h->tbdf);
if (!pcidev) {
warn("No PCI dev for tbdf %p!", irq_h->tbdf);
return -1;
}
if ((vecno = msi_irq_enable(irq_h, pcidev)) != -1)
return vecno;
busno = BUSBNO(irq_h->tbdf);
assert(busno == pcidev->bus);
devno = pcidev_read8(pcidev, PciINTP);
/* this might not be a big deal - some PCI devices have no INTP.
* if so, change our devno - 1 below. */
if (devno == 0)
panic("no INTP for tbdf %p", irq_h->tbdf);
/* remember, devno is the device shifted with irq pin in bits
* 0-1. we subtract 1, since the PCI intp maps 1 -> INTA, 2 ->
* INTB, etc, and the MP spec uses 0 -> INTA, 1 -> INTB, etc. */
devno = BUSDNO(irq_h->tbdf) << 2 | (devno - 1);
break;
default:
panic("Unknown bus type, TBDF %p", irq_h->tbdf);
}
/* busno and devno are set, regardless of the bustype, enough to find
* rdt. these may differ from the values in tbdf. */
rdt = rbus_get_rdt(busno, devno);
if (!rdt) {
/* second chance. if we didn't find the item the first time,
* then (if it exists at all), it wasn't in the MP tables (or we
* had no tables). So maybe we can figure it out via ACPI. */
acpi_make_rdt(irq_h->tbdf, irq_h->dev_irq, busno, devno);
rdt = rbus_get_rdt(busno, devno);
}
if (!rdt) {
printk("Unable to build IOAPIC route for irq %d\n",
irq_h->dev_irq);
return -1;
}
/*
* what to do about devices that intrenable/intrdisable frequently?
* 1) there is no ioapicdisable yet;
* 2) it would be good to reuse freed vectors.
* Oh bugger.
* brho: plus the diff btw mask/unmask and enable/disable is unclear
*/
/*
* 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.
*/
spin_lock(&rdt->apic->lock);
/* if a destination has already been picked, we store it in the lo.
* this stays around regardless of enabled/disabled, since we don't reap
* vectors yet. nor do we really mess with enabled... */
if ((rdt->lo & 0xff) == 0) {
vecno = nextvec();
rdt->lo |= vecno;
rdtvecno[vecno] = rdt;
} else {
printd("%p: mutiple irq bus %d dev %d\n", irq_h->tbdf, busno,
devno);
}
rdt->enabled++;
rdt->hi = 0; /* route to 0 by default */
rdt->lo |= Pm | MTf;
rtblput(rdt->apic, rdt->intin, rdt->hi, rdt->lo);
vecno = rdt->lo & 0xff;
spin_unlock(&rdt->apic->lock);
irq_h->check_spurious = lapic_check_spurious;
irq_h->eoi = lapic_send_eoi;
irq_h->mask = ioapic_mask_irq;
irq_h->unmask = ioapic_unmask_irq;
irq_h->route_irq = ioapic_route_irq;
irq_h->type = "ioapic";
return vecno;
}