x86: add thread support

Thread support is added for the x86 architecture. Both
the local apic and the tsc udelay() functions have a
call to thread_yield_microseconds() so as to provide an
opportunity to run pending threads.

Change-Id: Ie39b9eb565eb189676c06645bdf2a8720fe0636a
Signed-off-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: http://review.coreboot.org/3207
Tested-by: build bot (Jenkins)
Reviewed-by: Ronald G. Minnich <rminnich@gmail.com>
diff --git a/src/Kconfig b/src/Kconfig
index 4366bc8..308deaa 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -326,7 +326,7 @@
 
 config COOP_MULTITASKING
 	def_bool n
-	depends on TIMER_QUEUE
+	depends on TIMER_QUEUE && ARCH_X86
 	help
 	  Cooperative multitasking allows callbacks to be multiplexed on the
 	  main thread of ramstage. With this enabled it allows for multiple
diff --git a/src/arch/x86/lib/Makefile.inc b/src/arch/x86/lib/Makefile.inc
index 82f4e62..7cdd3c6 100644
--- a/src/arch/x86/lib/Makefile.inc
+++ b/src/arch/x86/lib/Makefile.inc
@@ -9,6 +9,8 @@
 ramstage-y += memcpy.c
 ramstage-y += ebda.c
 ramstage-y += rom_media.c
+ramstage-$(CONFIG_COOP_MULTITASKING) += thread.c
+ramstage-$(CONFIG_COOP_MULTITASKING) += thread_switch.S
 
 romstage-$(CONFIG_EARLY_CONSOLE) += romstage_console.c
 romstage-y += cbfs_and_run.c
diff --git a/src/arch/x86/lib/c_start.S b/src/arch/x86/lib/c_start.S
index 1e38acaf..c725f82 100644
--- a/src/arch/x86/lib/c_start.S
+++ b/src/arch/x86/lib/c_start.S
@@ -10,6 +10,11 @@
 _stack:
 .space CONFIG_MAX_CPUS*CONFIG_STACK_SIZE
 _estack:
+#if CONFIG_COOP_MULTITASKING
+.global thread_stacks
+thread_stacks:
+.space CONFIG_STACK_SIZE*CONFIG_NUM_THREADS
+#endif
 
 	.section ".textfirst", "ax", @progbits
 	.code32
@@ -45,6 +50,10 @@
 	/* set new stack */
 	movl	$_estack, %esp
 
+#if CONFIG_COOP_MULTITASKING
+	/* Push the thread pointer. */
+	pushl	$0
+#endif
 	/* Push the cpu index and struct cpu */
 	pushl	$0
 	pushl	$0
diff --git a/src/arch/x86/lib/thread.c b/src/arch/x86/lib/thread.c
new file mode 100644
index 0000000..06f8a15
--- /dev/null
+++ b/src/arch/x86/lib/thread.c
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2013 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <thread.h>
+
+/* The stack frame looks like the following after a pushad instruction. */
+struct pushad_regs {
+	uint32_t edi; /* Offset 0x00 */
+	uint32_t esi; /* Offset 0x04 */
+	uint32_t ebp; /* Offset 0x08 */
+	uint32_t esp; /* Offset 0x0c */
+	uint32_t ebx; /* Offset 0x10 */
+	uint32_t edx; /* Offset 0x14 */
+	uint32_t ecx; /* Offset 0x18 */
+	uint32_t eax; /* Offset 0x1c */
+};
+
+static inline uintptr_t push_stack(uintptr_t cur_stack, uintptr_t value)
+{
+	uintptr_t *addr;
+
+	cur_stack -= sizeof(value);
+	addr = (uintptr_t *)cur_stack;
+	*addr = value;
+	return cur_stack;
+}
+
+void arch_prepare_thread(struct thread *t,
+                         void asmlinkage (*thread_entry)(void *), void *arg)
+{
+	uintptr_t stack = t->stack_current;
+
+	/* Imitate thread_entry(t) with return address of 0. thread_entry()
+	 * is assumed to never return. */
+	stack = push_stack(stack, (uintptr_t)arg);
+	stack = push_stack(stack, (uintptr_t)0);
+	stack = push_stack(stack, (uintptr_t)thread_entry);
+	/* Make room for the registers. Ignore intial values. */
+	stack -= sizeof(struct pushad_regs);
+
+	t->stack_current = stack;
+}
diff --git a/src/arch/x86/lib/thread_switch.S b/src/arch/x86/lib/thread_switch.S
new file mode 100644
index 0000000..8de1948
--- /dev/null
+++ b/src/arch/x86/lib/thread_switch.S
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright (C) 2013 Google, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+.code32
+.text
+
+/*
+ * stack layout after pushad:
+ * +------------+
+ * | save stack | <-- esp + 0x28
+ * +------------+
+ * |  new stack | <-- esp + 0x24
+ * +------------+
+ * |  ret addr  | <-- esp + 0x20
+ * +------------+
+ * |    eax     | <-- esp + 0x1c
+ * +------------+
+ * |    ecx     | <-- esp + 0x18
+ * +------------+
+ * |    edx     | <-- esp + 0x14
+ * +------------+
+ * |    ebx     | <-- esp + 0x10
+ * +------------+
+ * |  orig esp  | <-- esp + 0x0c
+ * +------------+
+ * |    ebp     | <-- esp + 0x08
+ * +------------+
+ * |    esi     | <-- esp + 0x04
+ * +------------+
+ * |    edi     | <-- esp + 0x00
+ * +------------+
+ */
+.globl switch_to_thread
+switch_to_thread:
+	pusha
+	/* Save the current stack */
+	movl	0x28(%esp), %ebx
+	movl	%esp, (%ebx)
+	/* Switch to the new stack. */
+	movl	0x24(%esp), %eax
+	movl	%eax, %esp
+	popa
+	ret
diff --git a/src/cpu/intel/haswell/mp_init.c b/src/cpu/intel/haswell/mp_init.c
index deba629..357fbb2 100644
--- a/src/cpu/intel/haswell/mp_init.c
+++ b/src/cpu/intel/haswell/mp_init.c
@@ -36,6 +36,7 @@
 #include <lib.h>
 #include <smp/atomic.h>
 #include <smp/spinlock.h>
+#include <thread.h>
 #include "haswell.h"
 
 /* This needs to match the layout in the .module_parametrs section. */
@@ -163,6 +164,7 @@
 	info = cpu_info();
 	info->index = cpu;
 	info->cpu = cpu_devs[cpu];
+	thread_init_cpu_info_non_bsp(info);
 
 	apic_id_table[info->index] = lapicid();
 	info->cpu->path.apic.apic_id = apic_id_table[info->index];
diff --git a/src/cpu/x86/lapic/apic_timer.c b/src/cpu/x86/lapic/apic_timer.c
index 749fef0..e5ce62f 100644
--- a/src/cpu/x86/lapic/apic_timer.c
+++ b/src/cpu/x86/lapic/apic_timer.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 #include <console/console.h>
 #include <delay.h>
+#include <thread.h>
 #include <arch/io.h>
 #include <arch/cpu.h>
 #include <cpu/x86/car.h>
@@ -95,6 +96,9 @@
 {
 	u32 start, value, ticks;
 
+	if (!thread_yield_microseconds(usecs))
+		return;
+
 	if (!timer_fsb || (lapic_read(LAPIC_LVTT) &
 		(LAPIC_LVT_TIMER_PERIODIC | LAPIC_LVT_MASKED)) !=
 		(LAPIC_LVT_TIMER_PERIODIC | LAPIC_LVT_MASKED))
diff --git a/src/cpu/x86/lapic/lapic_cpu_init.c b/src/cpu/x86/lapic/lapic_cpu_init.c
index 69430d5..fbc8aa4 100644
--- a/src/cpu/x86/lapic/lapic_cpu_init.c
+++ b/src/cpu/x86/lapic/lapic_cpu_init.c
@@ -32,6 +32,7 @@
 #include <smp/spinlock.h>
 #include <cpu/cpu.h>
 #include <cpu/intel/speedstep.h>
+#include <thread.h>
 
 #if CONFIG_SMP && CONFIG_MAX_CPUS > 1
 /* This is a lot more paranoid now, since Linux can NOT handle
@@ -292,6 +293,7 @@
 	info = (struct cpu_info *)stack_end;
 	info->index = index;
 	info->cpu   = cpu;
+	thread_init_cpu_info_non_bsp(info);
 
 	/* Advertise the new stack and index to start_cpu */
 	secondary_stack = stack_end;
diff --git a/src/cpu/x86/tsc/delay_tsc.c b/src/cpu/x86/tsc/delay_tsc.c
index 0e2a9c0..b8f2503 100644
--- a/src/cpu/x86/tsc/delay_tsc.c
+++ b/src/cpu/x86/tsc/delay_tsc.c
@@ -4,6 +4,7 @@
 #include <cpu/x86/tsc.h>
 #include <smp/spinlock.h>
 #include <delay.h>
+#include <thread.h>
 
 #if !defined(__PRE_RAM__)
 
@@ -176,6 +177,9 @@
 	unsigned long long current;
 	unsigned long long clocks;
 
+	if (!thread_yield_microseconds(us))
+		return;
+
 	start = rdtscll();
 	clocks = us;
 	clocks *= get_clocks_per_usec();