diff options
Diffstat (limited to 'src/cpu/x86/lapic')
-rw-r--r-- | src/cpu/x86/lapic/Config.lb | 3 | ||||
-rw-r--r-- | src/cpu/x86/lapic/boot_cpu.c | 10 | ||||
-rw-r--r-- | src/cpu/x86/lapic/lapic.c | 72 | ||||
-rw-r--r-- | src/cpu/x86/lapic/lapic_cpu_init.c | 316 | ||||
-rw-r--r-- | src/cpu/x86/lapic/secondary.S | 53 |
5 files changed, 454 insertions, 0 deletions
diff --git a/src/cpu/x86/lapic/Config.lb b/src/cpu/x86/lapic/Config.lb new file mode 100644 index 0000000000..8b5eaa376e --- /dev/null +++ b/src/cpu/x86/lapic/Config.lb @@ -0,0 +1,3 @@ +object lapic.o +object lapic_cpu_init.o +object secondary.S diff --git a/src/cpu/x86/lapic/boot_cpu.c b/src/cpu/x86/lapic/boot_cpu.c new file mode 100644 index 0000000000..d3a8f6e7a7 --- /dev/null +++ b/src/cpu/x86/lapic/boot_cpu.c @@ -0,0 +1,10 @@ +#include <cpu/x86/msr.h> + +int boot_cpu(void) +{ + int bsp; + msr_t msr; + msr = rdmsr(0x1b); + bsp = !!(msr.lo & (1 << 8)); + return bsp; +} diff --git a/src/cpu/x86/lapic/lapic.c b/src/cpu/x86/lapic/lapic.c new file mode 100644 index 0000000000..8282890bf7 --- /dev/null +++ b/src/cpu/x86/lapic/lapic.c @@ -0,0 +1,72 @@ +#include <cpu/x86/lapic.h> +#include <console/console.h> +#include <cpu/x86/msr.h> +#include <cpu/x86/mtrr.h> + +void setup_lapic(void) +{ + /* this is so interrupts work. This is very limited scope -- + * linux will do better later, we hope ... + */ + /* this is the first way we learned to do it. It fails on real SMP + * stuff. So we have to do things differently ... + * see the Intel mp1.4 spec, page A-3 + */ + +#if NEED_LAPIC == 1 + /* Only Pentium Pro and later have those MSR stuff */ + msr_t msr; + + printk_info("Setting up local apic..."); + + /* Enable the local apic */ + msr = rdmsr(LAPIC_BASE_MSR); + msr.lo |= LAPIC_BASE_MSR_ENABLE; + msr.lo &= ~LAPIC_BASE_MSR_ADDR_MASK; + msr.lo |= LAPIC_DEFAULT_BASE; + wrmsr(LAPIC_BASE_MSR, msr); + + /* + * Set Task Priority to 'accept all'. + */ + lapic_write_around(LAPIC_TASKPRI, + lapic_read_around(LAPIC_TASKPRI) & ~LAPIC_TPRI_MASK); + + /* Put the local apic in virtual wire mode */ + lapic_write_around(LAPIC_SPIV, + (lapic_read_around(LAPIC_SPIV) & ~(LAPIC_VECTOR_MASK)) + | LAPIC_SPIV_ENABLE); + lapic_write_around(LAPIC_LVT0, + (lapic_read_around(LAPIC_LVT0) & + ~(LAPIC_LVT_MASKED | LAPIC_LVT_LEVEL_TRIGGER | + LAPIC_LVT_REMOTE_IRR | LAPIC_INPUT_POLARITY | + LAPIC_SEND_PENDING |LAPIC_LVT_RESERVED_1 | + LAPIC_DELIVERY_MODE_MASK)) + | (LAPIC_LVT_REMOTE_IRR |LAPIC_SEND_PENDING | + LAPIC_DELIVERY_MODE_EXTINT) + ); + lapic_write_around(LAPIC_LVT1, + (lapic_read_around(LAPIC_LVT1) & + ~(LAPIC_LVT_MASKED | LAPIC_LVT_LEVEL_TRIGGER | + LAPIC_LVT_REMOTE_IRR | LAPIC_INPUT_POLARITY | + LAPIC_SEND_PENDING |LAPIC_LVT_RESERVED_1 | + LAPIC_DELIVERY_MODE_MASK)) + | (LAPIC_LVT_REMOTE_IRR |LAPIC_SEND_PENDING | + LAPIC_DELIVERY_MODE_NMI) + ); + + printk_debug(" apic_id: %d ", lapicid()); + +#else /* !NEED_LLAPIC */ + /* Only Pentium Pro and later have those MSR stuff */ + msr_t msr; + + printk_info("Disabling local apic..."); + + msr = rdmsr(LAPIC_BASE_MSR); + msr.lo &= ~LAPIC_BASE_MSR_ENABLE; + wrmsr(LAPIC_BASE_MSR, msr); +#endif /* !NEED_LAPIC */ + printk_info("done.\n"); + post_code(0x9b); +} diff --git a/src/cpu/x86/lapic/lapic_cpu_init.c b/src/cpu/x86/lapic/lapic_cpu_init.c new file mode 100644 index 0000000000..963b1cf729 --- /dev/null +++ b/src/cpu/x86/lapic/lapic_cpu_init.c @@ -0,0 +1,316 @@ +#include <cpu/x86/lapic.h> +#include <delay.h> +#include <string.h> +#include <console/console.h> +#include <arch/hlt.h> +#include <device/device.h> +#include <device/path.h> +#include <smp/atomic.h> +#include <smp/spinlock.h> +#include <cpu/cpu.h> + + +#if CONFIG_SMP == 1 +/* This is a lot more paranoid now, since Linux can NOT handle + * being told there is a CPU when none exists. So any errors + * will return 0, meaning no CPU. + * + * We actually handling that case by noting which cpus startup + * and not telling anyone about the ones that dont. + */ +static int lapic_start_cpu(unsigned long apicid) +{ + int timeout; + unsigned long send_status, accept_status, start_eip; + int j, num_starts, maxlvt; + extern char _secondary_start[]; + + /* + * Starting actual IPI sequence... + */ + + printk_spew("Asserting INIT.\n"); + + /* + * Turn INIT on target chip + */ + lapic_write_around(LAPIC_ICR2, SET_LAPIC_DEST_FIELD(apicid)); + + /* + * Send IPI + */ + + lapic_write_around(LAPIC_ICR, LAPIC_INT_LEVELTRIG | LAPIC_INT_ASSERT + | LAPIC_DM_INIT); + + printk_spew("Waiting for send to finish...\n"); + timeout = 0; + do { + printk_spew("+"); + udelay(100); + send_status = lapic_read(LAPIC_ICR) & LAPIC_ICR_BUSY; + } while (send_status && (timeout++ < 1000)); + if (timeout >= 1000) { + printk_err("CPU %d: First apic write timed out. Disabling\n", + apicid); + // too bad. + printk_err("ESR is 0x%x\n", lapic_read(LAPIC_ESR)); + if (lapic_read(LAPIC_ESR)) { + printk_err("Try to reset ESR\n"); + lapic_write_around(LAPIC_ESR, 0); + printk_err("ESR is 0x%x\n", lapic_read(LAPIC_ESR)); + } + return 0; + } + mdelay(10); + + printk_spew("Deasserting INIT.\n"); + + /* Target chip */ + lapic_write_around(LAPIC_ICR2, SET_LAPIC_DEST_FIELD(apicid)); + + /* Send IPI */ + lapic_write_around(LAPIC_ICR, LAPIC_INT_LEVELTRIG | LAPIC_DM_INIT); + + printk_spew("Waiting for send to finish...\n"); + timeout = 0; + do { + printk_spew("+"); + udelay(100); + send_status = lapic_read(LAPIC_ICR) & LAPIC_ICR_BUSY; + } while (send_status && (timeout++ < 1000)); + if (timeout >= 1000) { + printk_err("CPU %d: Second apic write timed out. Disabling\n", + apicid); + // too bad. + return 0; + } + + start_eip = (unsigned long)_secondary_start; + printk_spew("start_eip=0x%08lx\n", start_eip); + + num_starts = 2; + + /* + * Run STARTUP IPI loop. + */ + printk_spew("#startup loops: %d.\n", num_starts); + + maxlvt = 4; + + for (j = 1; j <= num_starts; j++) { + printk_spew("Sending STARTUP #%d to %u.\n", j, apicid); + lapic_read_around(LAPIC_SPIV); + lapic_write(LAPIC_ESR, 0); + lapic_read(LAPIC_ESR); + printk_spew("After apic_write.\n"); + + /* + * STARTUP IPI + */ + + /* Target chip */ + lapic_write_around(LAPIC_ICR2, SET_LAPIC_DEST_FIELD(apicid)); + + /* Boot on the stack */ + /* Kick the second */ + lapic_write_around(LAPIC_ICR, LAPIC_DM_STARTUP + | (start_eip >> 12)); + + /* + * Give the other CPU some time to accept the IPI. + */ + udelay(300); + + printk_spew("Startup point 1.\n"); + + printk_spew("Waiting for send to finish...\n"); + timeout = 0; + do { + printk_spew("+"); + udelay(100); + send_status = lapic_read(LAPIC_ICR) & LAPIC_ICR_BUSY; + } while (send_status && (timeout++ < 1000)); + + /* + * Give the other CPU some time to accept the IPI. + */ + udelay(200); + /* + * Due to the Pentium erratum 3AP. + */ + if (maxlvt > 3) { + lapic_read_around(LAPIC_SPIV); + lapic_write(LAPIC_ESR, 0); + } + accept_status = (lapic_read(LAPIC_ESR) & 0xEF); + if (send_status || accept_status) + break; + } + printk_spew("After Startup.\n"); + if (send_status) + printk_warning("APIC never delivered???\n"); + if (accept_status) + printk_warning("APIC delivery error (%lx).\n", accept_status); + if (send_status || accept_status) + return 0; + return 1; +} + +/* Number of cpus that are currently running in linuxbios */ +static atomic_t active_cpus = ATOMIC_INIT(1); + +/* start_cpu_lock covers last_cpu_index and secondary_stack. + * Only starting one cpu at a time let's me remove the logic + * for select the stack from assembly language. + * + * In addition communicating by variables to the cpu I + * am starting allows me to veryify it has started before + * start_cpu returns. + */ + +static spinlock_t start_cpu_lock = SPIN_LOCK_UNLOCKED; +static unsigned last_cpu_index = 0; +volatile unsigned long secondary_stack; + +int start_cpu(device_t cpu) +{ + extern unsigned char _estack[]; + struct cpu_info *info; + unsigned long stack_end; + unsigned long apicid; + unsigned long index; + unsigned long count; + int result; + + spin_lock(&start_cpu_lock); + + /* Get the cpu's apicid */ + apicid = cpu->path.u.apic.apic_id; + + /* Get an index for the new processor */ + index = ++last_cpu_index; + + /* Find end of the new processors stack */ + stack_end = ((unsigned long)_estack) - (STACK_SIZE*index) - sizeof(struct cpu_info); + + /* Record the index and which cpu structure we are using */ + info = (struct cpu_info *)stack_end; + info->index = index; + info->cpu = cpu; + + /* Advertise the new stack to start_cpu */ + secondary_stack = stack_end; + + /* Until the cpu starts up report the cpu is not enabled */ + cpu->enabled = 0; + cpu->initialized = 0; + + /* Start the cpu */ + result = lapic_start_cpu(apicid); + + if (result) { + result = 0; + /* Wait 1s or until the new the new cpu calls in */ + for(count = 0; count < 100000 ; count++) { + if (secondary_stack == 0) { + result = 1; + break; + } + udelay(10); + } + } + secondary_stack = 0; + spin_unlock(&start_cpu_lock); + return result; +} + +/* C entry point of secondary cpus */ +void secondary_cpu_init(void) +{ + atomic_inc(&active_cpus); + cpu_initialize(); + atomic_dec(&active_cpus); + stop_this_cpu(); +} + +static void initialize_other_cpus(device_t root) +{ + int old_active_count, active_count; + device_t cpu; + /* Loop through the cpus once getting them started */ + for(cpu = root->link[1].children; cpu ; cpu = cpu->sibling) { + if (cpu->path.type != DEVICE_PATH_APIC) { + continue; + } + if (!cpu->enabled) { + continue; + } + if (cpu->initialized) { + continue; + } + if (!start_cpu(cpu)) { + /* Record the error in cpu? */ + printk_err("CPU %u would not start!\n", + cpu->path.u.apic.apic_id); + } + } + + /* Now loop until the other cpus have finished initializing */ + old_active_count = 1; + active_count = atomic_read(&active_cpus); + while(active_count > 1) { + if (active_count != old_active_count) { + printk_info("Waiting for %d CPUS to stop\n", active_count); + old_active_count = active_count; + } + udelay(10); + active_count = atomic_read(&active_cpus); + } + for(cpu = root->link[1].children; cpu; cpu = cpu->sibling) { + if (cpu->path.type != DEVICE_PATH_APIC) { + continue; + } + if (!cpu->initialized) { + printk_err("CPU %u did not initialize!\n", + cpu->path.u.apic.apic_id); +#warning "FIXME do I need a mainboard_cpu_fixup function?" + } + } + printk_debug("All AP CPUs stopped\n"); +} + +#else /* CONFIG_SMP */ +#define initialize_other_cpus(root) do {} while(0) +#endif /* CONFIG_SMP */ + +void initialize_cpus(device_t root) +{ + struct device_path cpu_path; + struct cpu_info *info; + + /* Find the info struct for this cpu */ + info = cpu_info(); + +#if NEED_LAPIC == 1 + /* Ensure the local apic is enabled */ + enable_lapic(); + + /* Get the device path of the boot cpu */ + cpu_path.type = DEVICE_PATH_APIC; + cpu_path.u.apic.apic_id = lapicid(); +#else + /* Get the device path of the boot cpu */ + cpu_path.type = DEVICE_PATH_BOOT_CPU; +#endif + + /* Find the device structure for the boot cpu */ + info->cpu = alloc_find_dev(&root->link[1], &cpu_path); + + /* Initialize the bootstrap processor */ + cpu_initialize(); + + /* Now initialize the rest of the cpus */ + initialize_other_cpus(root); +} + diff --git a/src/cpu/x86/lapic/secondary.S b/src/cpu/x86/lapic/secondary.S new file mode 100644 index 0000000000..786c31e532 --- /dev/null +++ b/src/cpu/x86/lapic/secondary.S @@ -0,0 +1,53 @@ +#include <arch/asm.h> +#include <arch/intel.h> +#include <cpu/x86/mtrr.h> +#include <cpu/x86/lapic_def.h> + .text + .globl _secondary_start + .balign 4096 +_secondary_start: + .code16 + cli + xorl %eax, %eax + movl %eax, %cr3 /* Invalidate TLB*/ + + /* On hyper threaded cpus, invalidating the cache here is + * very very bad. Don't. + */ + + /* setup the data segment */ + movw %cs, %ax + movw %ax, %ds + + data32 lgdt gdtaddr - _secondary_start + + movl %cr0, %eax + andl $0x7FFAFFD1, %eax /* PG,AM,WP,NE,TS,EM,MP = 0 */ + orl $0x60000001, %eax /* CD, NW, PE = 1 */ + movl %eax, %cr0 + + ljmpl $0x10, $1f +1: + .code32 + movw $0x18, %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + movw %ax, %fs + movw %ax, %gs + + /* Set the stack pointer, and flag that we are done */ + xorl %eax, %eax + movl secondary_stack, %esp + movl %eax, secondary_stack + + call secondary_cpu_init +1: hlt + jmp 1b + +gdtaddr: + .word gdt_limit /* the table limit */ + .long gdt /* we know the offset */ + + +.code32 |