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