aboutsummaryrefslogtreecommitdiff
path: root/src/cpu/x86/lapic/lapic_cpu_init.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cpu/x86/lapic/lapic_cpu_init.c')
-rw-r--r--src/cpu/x86/lapic/lapic_cpu_init.c316
1 files changed, 316 insertions, 0 deletions
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);
+}
+