blob: 5733be97614b992604ca6c0b8ab8eaca6f41dd49 [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/mptables.h>
#include <arch/ioapic.h>
/*
* MultiProcessor Specification Version 1.[14].
*/
typedef struct { /* MP Floating Pointer */
uint8_t signature[4]; /* "_MP_" */
uint8_t addr[4]; /* PCMP */
uint8_t length; /* 1 */
uint8_t revision; /* [14] */
uint8_t checksum;
uint8_t feature[5];
} _MP_;
typedef struct { /* MP Configuration Table */
uint8_t signature[4]; /* "PCMP" */
uint8_t length[2];
uint8_t revision; /* [14] */
uint8_t checksum;
uint8_t string[20]; /* OEM + Product ID */
uint8_t oaddr[4]; /* OEM table pointer */
uint8_t olength[2]; /* OEM table length */
uint8_t entry[2]; /* entry count */
uint8_t apicpa[4]; /* local APIC address */
uint8_t xlength[2]; /* extended table length */
uint8_t xchecksum; /* extended table checksum */
uint8_t reserved;
uint8_t entries[];
} PCMP;
typedef struct {
char type[6];
int polarity; /* default for this bus */
int trigger; /* default for this bus */
} Mpbus;
static Mpbus mpbusdef[] = {
{"PCI ", IPlow, TMlevel,},
{"ISA ", IPhigh, TMedge,},
};
/* Editable version of the MP tables so we can fix botched entries. Kmalloced,
* never freed. Might be NULL if pcmp checks failed.*/
static PCMP *pcmp;
static Mpbus *mpbus[Nbus];
int mpisabusno = -1;
#define MP_VERBOSE_DEBUG 0
static void mpintrprint(char *s, uint8_t * p)
{
char buf[128], *b, *e;
char format[] = " type %d flags %p bus %d IRQ %d APIC %d INTIN %d\n";
b = buf;
e = b + sizeof(buf);
/* can't use seprintf yet!
b = seprintf(b, e, "mpparse: intr:");
if(s != NULL)
b = seprintf(b, e, " %s:", s);
seprintf(b, e, format, p[1], l16get(p+2), p[4], p[5], p[6], p[7]);
printd(buf);
*/
printk("mpparse: intr:");
if (s != NULL)
printk(" %s:", s);
printk(format, p[1], l16get(p + 2), p[4], p[5], p[6], p[7]);
}
/* I've seen busted MP tables routes with invalid IOAPIC ids and INTINs that are
* out of range. We can look at the INTINs to try to figure out which IOAPIC
* they meant, and then adjust the INTINs too.
*
* Specifically, the machine I saw had two IOAPICs, neither of which had good
* iointr APIC IDs. ACPI and the MP tables said I had IOAPICS 8 and 9. The
* IOINTRs APIC IDs were 0 and 2. Additionally, 2's INTINs were all beyond the
* range of the 24 nrtds for that IOAPIC. However, that IOAPIC's ibase was 24
* too.
*
* Combined, these two clues mean the INTINs are in the global ibase/route
* space, and we can tell which IOAPIC to use based on the INTIN. This works at
* least for the IOAPIC 0 (8) on my hardware (IRQ routing works). I haven't
* been able to test on devices on the upper APIC (9). */
static int repair_iointr(uint8_t *iointr)
{
struct apic *ioapic;
int ioapic_id;
int intin = iointr[7];
for (int i = 0; i < Napic; i++) {
ioapic = &xioapic[i];
if (!ioapic->useable)
continue;
if (ioapic->ibase <= intin &&
intin < ioapic->ibase + ioapic->nrdt) {
iointr[6] = i;
iointr[7] = intin - ioapic->ibase;
return 0;
}
}
return -1;
}
static uint32_t mpmkintr(uint8_t * p)
{
uint32_t v;
struct apic *apic;
int n, polarity, trigger;
/*
* Check valid bus, interrupt input pin polarity
* and trigger mode. If the APIC ID is 0xff it means
* all APICs of this type so those checks for useable
* APIC and valid INTIN must also be done later in
* the appropriate init routine in that case. It's hard
* to imagine routing a signal to all IOAPICs, the
* usual case is routing NMI and ExtINT to all LAPICs.
*/
if (mpbus[p[4]] == NULL) {
mpintrprint("no source bus", p);
return 0;
}
if (p[6] != 0xff) {
if (Napic < 256 && p[6] >= Napic) {
mpintrprint("APIC ID out of range", p);
return 0;
}
switch (p[0]) {
default:
mpintrprint("INTIN botch", p);
return 0;
case 3: /* IOINTR */
apic = &xioapic[p[6]];
if (!apic->useable) {
mpintrprint("unuseable ioapic", p);
if (repair_iointr(p)) {
mpintrprint("unrepairable iointr", p);
return 0;
}
mpintrprint("repaired iointr", p);
/* Repair found a usable apic */
apic = &xioapic[p[6]];
}
if (p[7] >= apic->nrdt) {
mpintrprint("IO INTIN out of range", p);
return 0;
}
break;
case 4: /* LINTR */
apic = &xlapic[p[6]];
if (!apic->useable) {
mpintrprint("unuseable lapic", p);
return 0;
}
if (p[7] >= ARRAY_SIZE(apic->lvt)) {
mpintrprint("LOCAL INTIN out of range", p);
return 0;
}
break;
}
}
n = l16get(p + 2);
if ((polarity = (n & 0x03)) == 2 || (trigger = ((n >> 2) & 0x03)) == 2)
{
mpintrprint("invalid polarity/trigger", p);
return 0;
}
/*
* Create the low half of the vector table entry (LVT or RDT).
* For the NMI, SMI and ExtINT cases, the polarity and trigger
* are fixed (but are not always consistent over IA-32 generations).
* For the INT case, either the polarity/trigger are given or
* it defaults to that of the source bus;
* whether INT is Fixed or Lowest Priority is left until later.
*/
v = Im;
switch (p[1]) {
default:
mpintrprint("invalid type", p);
return 0;
case 0: /* INT */
switch (polarity) {
case 0:
v |= mpbus[p[4]]->polarity;
break;
case 1:
v |= IPhigh;
break;
case 3:
v |= IPlow;
break;
}
switch (trigger) {
case 0:
v |= mpbus[p[4]]->trigger;
break;
case 1:
v |= TMedge;
break;
case 3:
v |= TMlevel;
break;
}
break;
case 1: /* NMI */
v |= TMedge | IPhigh | MTnmi;
break;
case 2: /* SMI */
v |= TMedge | IPhigh | MTsmi;
break;
case 3: /* ExtINT */
v |= TMedge | IPhigh | MTei;
break;
}
return v;
}
static int mpparse(PCMP * pcmp, int maxcores)
{
uint32_t lo;
uint8_t *e, *p;
int devno, i, n;
p = pcmp->entries;
e = ((uint8_t *) pcmp) + l16get(pcmp->length);
while (p < e)
switch (*p) {
default:
printd("mpparse: unknown PCMP type %d (e-p %#ld)\n", *p,
e - p);
for (i = 0; p < e; i++) {
if (i && ((i & 0x0f) == 0))
printd("\n");
printd(" 0x%#2.2x", *p);
p++;
}
printd("\n");
break;
case 0: /* processor */
/*
* Initialise the APIC if it is enabled (p[3] & 0x01).
* p[1] is the APIC ID, the memory mapped address comes
* from the PCMP structure as the addess is local to the
* CPU and identical for all. Indicate whether this is
* the bootstrap processor (p[3] & 0x02).
*/
printd("mpparse: cpu %d pa %p bp %d\n",
p[1], l32get(pcmp->apicpa), p[3] & 0x02);
if ((p[3] & 0x01) != 0 && maxcores > 0) {
maxcores--;
apicinit(p[1], l32get(pcmp->apicpa), p[3] &
0x02);
}
p += 20;
break;
case 1: /* bus */
printd("mpparse: bus: %d type %6.6s\n", p[1], (char *)p
+ 2);
if (p[1] >= Nbus) {
printk("mpparse: bus %d out of range\n", p[1]);
p += 8;
break;
}
if (mpbus[p[1]] != NULL) {
printk("mpparse: bus %d already allocated\n",
p[1]);
p += 8;
break;
}
for (i = 0; i < ARRAY_SIZE(mpbusdef); i++) {
if (memcmp(p + 2, mpbusdef[i].type, 6) != 0)
continue;
if (memcmp(p + 2, "ISA ", 6) == 0) {
if (mpisabusno != -1) {
printk("mpparse: bus %d already have ISA bus %d\n",
p[1], mpisabusno);
continue;
}
mpisabusno = p[1];
}
mpbus[p[1]] = &mpbusdef[i];
break;
}
if (mpbus[p[1]] == NULL)
printk("mpparse: bus %d type %6.6s unknown\n",
p[1], (char *)p + 2);
p += 8;
break;
case 2: /* IOAPIC */
/*
* Initialise the IOAPIC if it is enabled (p[3] & 0x01).
* p[1] is the APIC ID, p[4-7] is the memory mapped
* address.
*/
if (p[3] & 0x01)
ioapicinit(p[1], -1, l32get(p + 4));
p += 8;
break;
case 3: /* IOINTR */
/*
* p[1] is the interrupt type;
* p[2-3] contains the polarity and trigger mode;
* p[4] is the source bus;
* p[5] is the IRQ on the source bus;
* p[6] is the destination IOAPIC;
* p[7] is the INITIN pin on the destination IOAPIC.
*/
if (p[6] == 0xff) {
mpintrprint("routed to all IOAPICs", p);
p += 8;
break;
}
if ((lo = mpmkintr(p)) == 0) {
if (MP_VERBOSE_DEBUG)
mpintrprint("iointr skipped", p);
p += 8;
break;
}
if (MP_VERBOSE_DEBUG)
mpintrprint("iointr", p);
/*
* Always present the device number in the style
* of a PCI Interrupt Assignment Entry. For the ISA
* bus the IRQ is the device number but unencoded.
* May need to handle other buses here in the future
* (but unlikely).
*
* For PCI devices, this field's lowest two bits are
* INT#A == 0, INT#B == 1, etc. Bits 2-6 are the PCI
* device number.
*/
devno = p[5];
if (memcmp(mpbus[p[4]]->type, "PCI ", 6) != 0)
devno <<= 2;
ioapicintrinit(p[4], p[6], p[7], devno, lo);
p += 8;
break;
case 4: /* LINTR */
/*
* Format is the same as IOINTR above.
*/
if ((lo = mpmkintr(p)) == 0) {
p += 8;
break;
}
if (MP_VERBOSE_DEBUG)
mpintrprint("LINTR", p);
/*
* Everything was checked in mpmkintr above.
*/
if (p[6] == 0xff) {
for (i = 0; i < Napic; i++) {
if (!xlapic[i].useable ||
xlapic[i].addr)
continue;
xlapic[i].lvt[p[7]] = lo;
}
} else
xlapic[p[6]].lvt[p[7]] = lo;
p += 8;
break;
}
/*
* There's nothing of interest in the extended table,
* but check it for consistency.
*/
p = e;
e = p + l16get(pcmp->xlength);
while (p < e)
switch (*p) {
default:
n = p[1];
printd("mpparse: unknown extended entry %d length %d\n",
*p, n);
for (i = 0; i < n; i++) {
if (i && ((i & 0x0f) == 0))
printd("\n");
printd(" %#2.2ux", *p);
p++;
}
printd("\n");
break;
case 128:
printd("address space mapping\n");
printd(" bus %d type %d base %p length %p\n",
p[2], p[3], l64get(p + 4), l64get(p + 12));
p += p[1];
break;
case 129:
printd("bus hierarchy descriptor\n");
printd(" bus %d sd %d parent bus %d\n", p[2], p[3],
p[4]);
p += p[1];
break;
case 130:
printd("compatibility bus address space modifier\n");
printd(" bus %d pr %d range list %d\n",
p[2], p[3], l32get(p + 4));
p += p[1];
break;
}
return maxcores;
}
static void *sigsearch(char *signature)
{
uintptr_t p;
uint8_t *bda;
void *r;
#if 0
/*
* Search for the data structure:
* 1) in the first KB of the EBDA;
* 2) in the last KB of system base memory;
* 3) in the BIOS ROM between 0xe0000 and 0xfffff.
*/
bda = BIOSSEG(0x40);
if (memcmp(KADDR(0xfffd9), "EISA", 4) == 0) {
if ((p = (bda[0x0f] << 8) | bda[0x0e])) {
if ((r = sigscan(BIOSSEG(p), 1024, signature)) != NULL)
return r;
}
}
p = ((bda[0x14] << 8) | bda[0x13]) * 1024;
if ((r = sigscan(KADDR(p - 1024), 1024, signature)) != NULL)
return r;
#endif
r = sigscan(KADDR(0xe0000), 0x20000, signature);
printk("Found MP table at %p\n", r);
if (r != NULL)
return r;
return NULL;
/* and virtualbox hidden mp tables... */
// return sigscan(KADDR(0xa0000 - 1024), 1024, signature);
}
static PCMP *copy_pcmp(PCMP *pcmp)
{
PCMP *new_pcmp;
size_t n = l16get(pcmp->length) + l16get(pcmp->xlength);
new_pcmp = kmalloc(n, MEM_ATOMIC);
assert(new_pcmp);
memcpy(new_pcmp, pcmp, n);
return new_pcmp;
}
int mpsinit(int maxcores)
{
uint8_t *p;
int i;
_MP_ *mp;
if ((mp = sigsearch("_MP_")) == NULL) {
printk("No mp tables found, might have issues!\n");
return maxcores;
}
/* TODO: if an IMCR exists, we should set it to 1, though i've heard
* that ACPI-capable HW doesn't have the IMCR anymore. */
if (MP_VERBOSE_DEBUG) {
printk("_MP_ @ %#p, addr %p length %ud rev %d",
mp, l32get(mp->addr), mp->length, mp->revision);
for (i = 0; i < sizeof(mp->feature); i++)
printk(" %2.2p", mp->feature[i]);
printk("\n");
}
if (mp->revision != 1 && mp->revision != 4)
return maxcores;
if (sigchecksum(mp, mp->length * 16) != 0)
return maxcores;
if ((pcmp = KADDR_NOCHECK(l32get(mp->addr))) == NULL)
return maxcores;
if (pcmp->revision != 1 && pcmp->revision != 4) {
pcmp = NULL;
return maxcores;
}
if (sigchecksum(pcmp, l16get(pcmp->length)) != 0) {
pcmp = NULL;
return maxcores;
}
pcmp = copy_pcmp(pcmp);
if (MP_VERBOSE_DEBUG) {
printk("PCMP @ %#p length %p revision %d\n",
pcmp, l16get(pcmp->length), pcmp->revision);
printk(" %20.20s oaddr %p olength %p\n",
(char *)pcmp->string, l32get(pcmp->oaddr),
l16get(pcmp->olength));
printk(" entry %d apicpa %p\n",
l16get(pcmp->entry), l32get(pcmp->apicpa));
printk(" xlength %p xchecksum %p\n",
l16get(pcmp->xlength), pcmp->xchecksum);
}
if (pcmp->xchecksum != 0) {
p = ((uint8_t *) pcmp) + l16get(pcmp->length);
i = sigchecksum(p, l16get(pcmp->xlength));
if (((i + pcmp->xchecksum) & 0xff) != 0) {
printd("extended table checksums to %p\n", i);
return maxcores;
}
}
/*
* Parse the PCMP table and set up the datastructures
* for later interrupt enabling and application processor
* startup.
*/
return mpparse(pcmp, maxcores);
}