| #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 <trap.h> |
| |
| static char *apicregnames[] = { |
| [MSR_LAPIC_ID] "Identification", |
| [MSR_LAPIC_VERSION] "Version", |
| [MSR_LAPIC_TPR] "Task Priority", |
| // [Ap] "Arbitration Priority", |
| [MSR_LAPIC_PPR] "Processor Priority", |
| [MSR_LAPIC_EOI] "EOI", |
| [MSR_LAPIC_LDR] "Logical Destination", |
| [MSR_LAPIC_SPURIOUS] "Spurious Interrupt Vector", |
| [MSR_LAPIC_ISR_START] "Interrupt Status (8)", |
| [MSR_LAPIC_TMR_START] "Trigger Mode (8)", |
| [MSR_LAPIC_IRR_START] "Interrupt Request (8)", |
| [MSR_LAPIC_ESR] "Error Status", |
| [MSR_LAPIC_ICR] "Interrupt Command", |
| [MSR_LAPIC_INITIAL_COUNT] "Timer Initial Count", |
| [MSR_LAPIC_CURRENT_COUNT] "Timer Current Count", |
| [MSR_LAPIC_DIVIDE_CONFIG_REG] "Timer Divide Configuration", |
| |
| [MSR_LAPIC_LVT_TIMER] "Timer", |
| [MSR_LAPIC_LVT_LINT0] "Local Interrupt 0", |
| [MSR_LAPIC_LVT_LINT1] "Local Interrupt 1", |
| [MSR_LAPIC_LVT_ERROR_REG] "Error", |
| [MSR_LAPIC_LVT_PERFMON] "Performance Counter", |
| [MSR_LAPIC_LVT_THERMAL] "Thermal Sensor", |
| }; |
| |
| enum { /* Siv */ |
| Swen = 0x00000100, /* Software Enable */ |
| Fdis = 0x00000200, /* Focus Disable */ |
| }; |
| |
| enum { /* Iclo */ |
| Lassert = 0x00004000, /* Assert level */ |
| |
| DSnone = 0x00000000, /* Use Destination Field */ |
| DSself = 0x00040000, /* Self is only destination */ |
| DSallinc = 0x00080000, /* All including self */ |
| DSallexc = 0x000c0000, /* All Excluding self */ |
| }; |
| |
| enum { /* Tlvt */ |
| Periodic = 0x00020000, /* Periodic Timer Mode */ |
| }; |
| |
| enum { /* Tdc */ |
| DivX2 = 0x00000000, /* Divide by 2 */ |
| DivX4 = 0x00000001, /* Divide by 4 */ |
| DivX8 = 0x00000002, /* Divide by 8 */ |
| DivX16 = 0x00000003, /* Divide by 16 */ |
| DivX32 = 0x00000008, /* Divide by 32 */ |
| DivX64 = 0x00000009, /* Divide by 64 */ |
| DivX128 = 0x0000000a, /* Divide by 128 */ |
| DivX1 = 0x0000000b, /* Divide by 1 */ |
| }; |
| |
| static uintptr_t apicbase; |
| static int apmachno = 1; |
| static uint32_t apicr310; |
| |
| struct apic xlapic[Napic]; |
| |
| static void __apic_ir_dump(uint64_t r); |
| |
| static void __apic_ir_dump(uint64_t r) |
| { |
| int i; |
| uint32_t val; |
| |
| if (r != MSR_LAPIC_ISR_START && r != MSR_LAPIC_IRR_START && |
| r != MSR_LAPIC_TMR_START) |
| panic("Invalid register dump offset!"); |
| |
| for (i = 7; i >= 0; i--) { |
| val = apicrget(r+i); |
| if (val) { |
| printk("Register at range (%d,%d]: 0x%08x\n", |
| ((i + 1) * 32), i * 32, val); |
| } |
| } |
| } |
| |
| void apic_isr_dump(void) |
| { |
| printk("ISR DUMP\n"); |
| __apic_ir_dump(MSR_LAPIC_ISR_START); |
| } |
| |
| void apic_irr_dump(void) |
| { |
| printk("IRR DUMP\n"); |
| __apic_ir_dump(MSR_LAPIC_IRR_START); |
| } |
| |
| uint32_t apicrget(uint64_t r) |
| { |
| uint32_t val; |
| |
| if (r >= MSR_LAPIC_END) |
| panic("%s: OUT OF BOUNDS: register 0x%x\n", __func__, r); |
| if (r != MSR_LAPIC_SPURIOUS && r != MSR_LAPIC_DIVIDE_CONFIG_REG) |
| printd("%s: Reading from register 0x%llx\n", |
| __func__, r); |
| |
| val = read_msr(r); |
| printd("apicrget: %s returns %p\n", apicregnames[r], val); |
| if (r == MSR_LAPIC_ID) { |
| printd("APIC ID: 0x%lx\n", val); |
| printd("APIC LOGICAL ID: 0x%lx\n", |
| apicrget(MSR_LAPIC_LDR)); |
| } |
| return val; |
| } |
| |
| void apicrput(uint64_t r, uint32_t data) |
| { |
| uint64_t temp_data = 0; |
| |
| if (r >= MSR_LAPIC_END) |
| panic("%s: OUT OF BOUNDS: register 0x%x\n", __func__, r); |
| if (r != MSR_LAPIC_INITIAL_COUNT && r != MSR_LAPIC_LVT_TIMER && |
| r != MSR_LAPIC_DIVIDE_CONFIG_REG && r != MSR_LAPIC_EOI) |
| printd("%s: Writing to register 0x%llx, value 0x%lx\n", |
| __func__, r, data); |
| if (r == MSR_LAPIC_ID) |
| panic("ILLEGAL WRITE TO ID"); |
| printd("apicrput: %s = %p\n", apicregnames[r], data); |
| |
| temp_data |= data; |
| |
| write_msr(r, temp_data); |
| } |
| |
| void apicsendipi(uint64_t data) |
| { |
| printd("SENDING IPI: 0x%016lx\n", data); |
| write_msr(MSR_LAPIC_ICR, data); |
| } |
| |
| void apicinit(int apicno, uintptr_t pa, int isbp) |
| { |
| struct apic *apic; |
| uint64_t msr_val; |
| |
| /* |
| * Mark the APIC useable if it has a good ID |
| * and the registers can be mapped. |
| * The APIC Extended Broadcast and ID bits in the HyperTransport |
| * Transaction Control register determine whether 4 or 8 bits |
| * are used for the APIC ID. There is also xAPIC and x2APIC |
| * to be dealt with sometime. |
| */ |
| printd("apicinit: apicno %d pa %#p isbp %d\n", apicno, pa, isbp); |
| if (apicno >= Napic) { |
| printd("apicinit%d: out of range\n", apicno); |
| return; |
| } |
| if ((apic = &xlapic[apicno])->useable) { |
| printd("apicinit%d: already initialised\n", apicno); |
| return; |
| } |
| assert(pa == LAPIC_PBASE); |
| apic->useable = 1; |
| |
| /* plan 9 used to set up a mapping btw apic and pcpui like so: |
| pcpui->apicno = apicno; // acpino is the hw_coreid |
| apic->machno = apmachno++; // machno is the os_coreid |
| * akaros does its own remapping of hw <-> os coreid during smp_boot */ |
| |
| //X2APIC INIT |
| msr_val = read_msr(IA32_APIC_BASE); |
| write_msr(IA32_APIC_BASE, msr_val | (3<<10)); |
| } |
| |
| static char *apicdump0(char *start, char *end, struct apic *apic, int i) |
| { |
| if (!apic->useable || apic->addr != 0) |
| return start; |
| start = seprintf(start, end, |
| "apic%d: oscore %d lint0 %#8.8p lint1 %#8.8p\n", i, |
| get_os_coreid(i), apic->lvt[0], apic->lvt[1]); |
| start = seprintf(start, end, " tslvt %#8.8p pclvt %#8.8p elvt %#8.8p\n", |
| apicrget(MSR_LAPIC_LVT_THERMAL), |
| apicrget(MSR_LAPIC_LVT_PERFMON), |
| apicrget(MSR_LAPIC_LVT_ERROR_REG)); |
| start = seprintf(start, end, |
| " tlvt %#8.8p lint0 %#8.8p lint1 %#8.8p siv %#8.8p\n", |
| apicrget(MSR_LAPIC_LVT_TIMER), |
| apicrget(MSR_LAPIC_LVT_LINT0), |
| apicrget(MSR_LAPIC_LVT_LINT1), |
| apicrget(MSR_LAPIC_SPURIOUS)); |
| return start; |
| } |
| |
| char *apicdump(char *start, char *end) |
| { |
| int i; |
| |
| if (!2) |
| return start; |
| |
| start = seprintf(start, end, "apicbase %#p apmachno %d\n", apicbase, |
| apmachno); |
| for (i = 0; i < Napic; i++) |
| start = apicdump0(start, end, xlapic + i, i); |
| for (i = 0; i < Napic; i++) |
| start = apicdump0(start, end, xioapic + i, i); |
| return start; |
| } |
| |
| void handle_lapic_error(struct hw_trapframe *hw_tf, void *data) |
| { |
| uint32_t err; |
| |
| apicrput(MSR_LAPIC_ESR, 0); |
| err = apicrget(MSR_LAPIC_ESR); |
| /* i get a shitload of these on my nehalem, many with err == 0 */ |
| printd("LAPIC error vector, got 0x%08x\n", err); |
| } |
| |
| int apiconline(void) |
| { |
| struct apic *apic; |
| uint64_t tsc; |
| uint32_t dfr, ver; |
| int apicno, nlvt; |
| uint64_t msr_val; |
| |
| //X2APIC INIT |
| msr_val = read_msr(IA32_APIC_BASE); |
| write_msr(IA32_APIC_BASE, msr_val | (3<<10)); |
| |
| apicno = lapic_get_id(); |
| if (apicno >= Napic) { |
| printk("Bad apicno %d on HW core %d!!\n", apicno, hw_core_id()); |
| return 0; |
| } |
| apic = &xlapic[apicno]; |
| /* The addr check tells us if it is an IOAPIC or not... */ |
| if (!apic->useable || apic->addr) { |
| printk("Unsuitable apicno %d on HW core %d!!\n", apicno, |
| hw_core_id()); |
| return 0; |
| } |
| /* Things that can only be done when on the processor owning the APIC, |
| * apicinit above runs on the bootstrap processor. */ |
| ver = apicrget(MSR_LAPIC_VERSION); |
| nlvt = ((ver >> 16) & 0xff) + 1; |
| if (nlvt > ARRAY_SIZE(apic->lvt)) { |
| printk("apiconline%d: nlvt %d > max (%d)\n", |
| apicno, nlvt, ARRAY_SIZE(apic->lvt)); |
| nlvt = ARRAY_SIZE(apic->lvt); |
| } |
| apic->nlvt = nlvt; |
| apic->ver = ver & 0xff; |
| |
| /* These don't really matter in Physical mode; set the defaults anyway. |
| * If we have problems with logical IPIs on AMD, check this out: */ |
| //if (memcmp(m->cpuinfo, "AuthenticAMD", 12) == 0) |
| // dfr = 0xf0000000; |
| //else |
| // dfr = 0xffffffff; |
| //apicrput(Df, dfr); |
| //apicrput(MSR_LAPIC_LDR, 0x00000000); |
| |
| /* Disable interrupts until ready by setting the Task Priority register |
| * to 0xff. */ |
| apicrput(MSR_LAPIC_TPR, 0xff); |
| |
| /* Software-enable the APIC in the Spurious Interrupt Vector register |
| * and set the vector number. The vector number must have bits 3-0 0x0f |
| * unless the Extended Spurious Vector Enable bit is set in the |
| * HyperTransport Transaction Control register. */ |
| apicrput(MSR_LAPIC_SPURIOUS, Swen | IdtLAPIC_SPURIOUS); |
| |
| /* Acknowledge any outstanding interrupts. */ |
| apicrput(MSR_LAPIC_EOI, 0); |
| |
| /* Mask interrupts on Performance Counter overflow and Thermal Sensor if |
| * implemented, and on Lintr0 (Legacy INTR), Lintr1 (Legacy NMI), and |
| * the Timer. Clear any Error Status (write followed by read) and |
| * enable the Error interrupt. */ |
| switch (apic->nlvt) { |
| case 6: |
| apicrput(MSR_LAPIC_LVT_THERMAL, Im); |
| /* fall-through */ |
| case 5: |
| apicrput(MSR_LAPIC_LVT_PERFMON, Im); |
| /* fall-through */ |
| default: |
| break; |
| } |
| /* lvt[0] and [1] were set to 0 in the BSS */ |
| apicrput(MSR_LAPIC_LVT_LINT1, apic->lvt[1] | Im | IdtLAPIC_LINT1); |
| apicrput(MSR_LAPIC_LVT_LINT0, apic->lvt[0] | Im | IdtLAPIC_LINT0); |
| apicrput(MSR_LAPIC_LVT_TIMER, Im); |
| |
| apicrput(MSR_LAPIC_ESR, 0); |
| apicrget(MSR_LAPIC_ESR); |
| apicrput(MSR_LAPIC_LVT_ERROR_REG, IdtLAPIC_ERROR | Im); |
| |
| /* Not sure we need this from plan 9, Akaros never did: |
| * |
| * Issue an INIT Level De-Assert to synchronise arbitration ID's. |
| * (Necessary in this implementation? - not if Pentium 4 or Xeon (APIC |
| * Version >= 0x14), or AMD). */ |
| //apicrput(Ichi, 0); |
| //apicrput(Iclo, DSallinc | Lassert | MTir); |
| //while (apicrget(Iclo) & Ds) |
| // cpu_relax(); |
| |
| /* this is to enable the APIC interrupts. we did a SW lapic_enable() |
| * earlier. if we ever have issues where the lapic seems offline, check |
| * here. */ |
| apicrput(MSR_LAPIC_TPR, 0); |
| return 1; |
| } |