summaryrefslogtreecommitdiff
path: root/src/cpu.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cpu.c')
-rw-r--r--src/cpu.c515
1 files changed, 130 insertions, 385 deletions
diff --git a/src/cpu.c b/src/cpu.c
index 325aab5..62251e1 100644
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017 Richard Braun.
+ * Copyright (c) 2017-2018 Richard Braun.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
@@ -18,10 +18,6 @@
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
- *
- *
- * All references to the Intel 64 and IA-32 Architecture Software Developer's
- * Manual are valid for order number: 325462-061US, December 2016.
*/
#include <assert.h>
@@ -32,88 +28,41 @@
#include <lib/macros.h>
+#include "boot.h"
#include "cpu.h"
-#include "i8259.h"
+#include "nvic.h"
#include "thread.h"
+#include "timer.h"
/*
- * Segment flags.
- *
- * See Intel 64 and IA-32 Architecture Software Developer's Manual, Volume 3
- * System Programming Guide :
- * - 3.4.5 Segment Descriptors
- * - 3.5 System Descriptor Types
+ * xPSR register bits.
*/
-#define CPU_SEG_DATA_RW 0x00000200
-#define CPU_SEG_CODE_RX 0x00000900
-#define CPU_SEG_S 0x00001000
-#define CPU_SEG_P 0x00008000
-#define CPU_SEG_DB 0x00400000
-#define CPU_SEG_G 0x00800000
-
-#define CPU_IDT_SIZE (CPU_IDT_VECT_IRQ_BASE + I8259_NR_IRQ_VECTORS)
+#define CPU_PSR_8BYTE_STACK_ALIGN 0x00000200
+#define CPU_PSR_THUMB 0x01000000
/*
- * Segment descriptor.
- *
- * These entries are found in the GDT and IDT tables (described below).
- * When loading a segment register, the value of the register is a
- * segment selector, which is an index (in bytes) along with flags.
- *
- * See Intel 64 and IA-32 Architecture Software Developer's Manual,
- * Volume 3 System Programming Guide :
- * - 3.4.2 Segment Selectors
- * - 3.4.5 Segment Descriptors
+ * Declarations for C/assembly functions that are global so that they can
+ * be shared between cpu.c and cpu_asm.S, but are considered private to
+ * the cpu module.
*/
-struct cpu_seg_desc {
- uint32_t low;
- uint32_t high;
-};
+void cpu_exc_main(void);
+void cpu_exc_svcall(void);
+void cpu_exc_pendsv(void);
+void cpu_irq_main(void);
/*
- * A pseudo descriptor is an operand for the LGDT/LIDT instructions.
- *
- * See Intel 64 and IA-32 Architecture Software Developer's Manual,
- * Volume 3 System Programming Guide, 3.5.1 Segment Descriptor Tables,
- * Figure 3-11 Pseudo-Descriptor Formats.
- *
- * This structure is packed to prevent any holes between limit and base.
+ * Exception vector table.
*/
-struct cpu_pseudo_desc {
- uint16_t limit;
- uint32_t base;
-} __packed;
-
-/*
- * These segment descriptor tables are the Global Descriptor Table (GDT)
- * and the Interrupt Descriptor Table (IDT) respectively. The GDT was
- * historically used to create segments. Segmentation could be used to run
- * multiple instances of the same program at different locations in memory,
- * by changing the base address of segments. It could implement a simple
- * form of memory protection by restricting the length of segments. With
- * modern virtual memory based entirely on paging, segmentation has become
- * obsolete, and all modern systems use a flat memory model, where all
- * segments span the entire physical space. Segments may still be used to
- * provide per-processor or per-thread variables (e.g. this is how TLS,
- * thread-local storage, is implemented).
- *
- * The IDT is used for exception and interrupt handling, collectively known
- * as interrupts. Here, "exception" refers to interrupts originating from
- * the CPU such as a division by zero exception, whereas "IRQ" refers to
- * interrupts raised by external devices. These terms are often used
- * interchangeably. What's important to keep in mind is that interrupts
- * divert the flow of execution of the processor. The IDT tells the processor
- * where to branch when an interrupt occurs.
- *
- * The GDT and IDT should be 8-byte aligned for best performance.
- *
- * See Intel 64 and IA-32 Architecture Software Developer's Manual, Volume 3
- * System Programming Guide :
- * - 3.5.1 Segment Descriptor Tables (GDT)
- * - 6.10 Interrupt Descriptor Table (IDT)
- */
-static struct cpu_seg_desc cpu_gdt[CPU_GDT_SIZE] __aligned(8);
-static struct cpu_seg_desc cpu_idt[CPU_IDT_SIZE] __aligned(8);
+static const void *cpu_vector_table[] __used __section(".vectors") = {
+ [0] = &boot_stack[ARRAY_SIZE(boot_stack)],
+ [CPU_EXC_RESET] = boot_start,
+ [CPU_EXC_NMI ... CPU_EXC_USAGEFAULT] = cpu_exc_main,
+ [CPU_EXC_SVCALL] = cpu_exc_svcall,
+ [CPU_EXC_DEBUGMONITOR] = cpu_exc_main,
+ [CPU_EXC_PENDSV] = cpu_exc_pendsv,
+ [CPU_EXC_SYSTICK] = cpu_exc_main,
+ [CPU_EXC_IRQ_BASE ... CPU_EXC_IRQ_MAX] = cpu_irq_main,
+};
/*
* Handler for external interrupt requests.
@@ -128,96 +77,36 @@ struct cpu_irq_handler {
*
* Interrupts and preemption must be disabled when accessing the handlers.
*/
-static struct cpu_irq_handler cpu_irq_handlers[I8259_NR_IRQ_VECTORS];
+static struct cpu_irq_handler cpu_irq_handlers[CPU_NR_IRQS];
/*
- * The interrupt frame is the stack content forged by interrupt handlers.
+ * The exception frame is the stack content forged by exception handlers.
* They store the data needed to restore the processor to its state prior
- * to the interrupt.
+ * to the exception.
*/
-struct cpu_intr_frame {
- /* These members are pushed by the low level ISRs */
- uint32_t edi;
- uint32_t esi;
- uint32_t ebp;
- uint32_t edx;
- uint32_t ecx;
- uint32_t ebx;
- uint32_t eax;
- uint32_t vector;
-
- /*
- * This member may be pushed by either the CPU or the low level ISRs
- * for exceptions/interrupts that don't emit such an error code.
- */
- uint32_t error;
+struct cpu_exc_frame {
+ /* These members are pushed by cpu_exc_pendsv() */
+ uint32_t r4;
+ uint32_t r5;
+ uint32_t r6;
+ uint32_t r7;
+ uint32_t r8;
+ uint32_t r9;
+ uint32_t r10;
+ uint32_t r11;
/* These members are automatically pushed by the CPU */
- uint32_t eip;
- uint32_t cs;
- uint32_t eflags;
+ uint32_t r0;
+ uint32_t r1;
+ uint32_t r2;
+ uint32_t r3;
+ uint32_t r12;
+ uint32_t r14;
+ uint32_t r15;
+ uint32_t psr;
};
-/*
- * Declarations for C/assembly functions that are global so that they can
- * be shared between cpu.c and cpu_asm.S, but are considered private to
- * the cpu module.
- */
-uint32_t cpu_get_eflags(void);
-void cpu_set_eflags(uint32_t eflags);
-void cpu_load_gdt(const struct cpu_pseudo_desc *desc);
-void cpu_load_idt(const struct cpu_pseudo_desc *desc);
-void cpu_intr_main(const struct cpu_intr_frame *frame);
-
-/*
- * Low level interrupt service routines.
- *
- * These are the addresses where the CPU directly branches to when an
- * interrupt is received.
- */
-void cpu_isr_divide_error(void);
-void cpu_isr_general_protection(void);
-void cpu_isr_32(void);
-void cpu_isr_33(void);
-void cpu_isr_34(void);
-void cpu_isr_35(void);
-void cpu_isr_36(void);
-void cpu_isr_37(void);
-void cpu_isr_38(void);
-void cpu_isr_39(void);
-void cpu_isr_40(void);
-void cpu_isr_41(void);
-void cpu_isr_42(void);
-void cpu_isr_43(void);
-void cpu_isr_44(void);
-void cpu_isr_45(void);
-void cpu_isr_46(void);
-void cpu_isr_47(void);
-
-uint32_t
-cpu_intr_save(void)
-{
- uint32_t eflags;
-
- eflags = cpu_get_eflags();
- cpu_intr_disable();
- return eflags;
-}
-
-void
-cpu_intr_restore(uint32_t eflags)
-{
- cpu_set_eflags(eflags);
-}
-
-bool
-cpu_intr_enabled(void)
-{
- uint32_t eflags;
-
- eflags = cpu_get_eflags();
- return eflags & CPU_EFL_IF;
-}
+uint8_t cpu_exc_stack[CPU_EXC_STACK_SIZE] __aligned(CPU_STACK_ALIGN);
void
cpu_halt(void)
@@ -230,97 +119,13 @@ cpu_halt(void)
}
static void
-cpu_default_intr_handler(void)
-{
- printf("cpu: error: unhandled interrupt\n");
- cpu_halt();
-}
-
-static void
-cpu_seg_desc_init_null(struct cpu_seg_desc *desc)
-{
- desc->low = 0;
- desc->high = 0;
-}
-
-static void
-cpu_seg_desc_init_code(struct cpu_seg_desc *desc)
-{
- /*
- * Base: 0
- * Limit: 0xffffffff
- * Privilege level: 0 (most privileged)
- */
- desc->low = 0xffff;
- desc->high = CPU_SEG_G
- | CPU_SEG_DB
- | (0xf << 16)
- | CPU_SEG_P
- | CPU_SEG_S
- | CPU_SEG_CODE_RX;
-}
-
-static void
-cpu_seg_desc_init_data(struct cpu_seg_desc *desc)
-{
- /*
- * Base: 0
- * Limit: 0xffffffff
- * Privilege level: 0 (most privileged)
- */
- desc->low = 0xffff;
- desc->high = CPU_SEG_G
- | CPU_SEG_DB
- | (0xf << 16)
- | CPU_SEG_P
- | CPU_SEG_S
- | CPU_SEG_DATA_RW;
-}
-
-static void
-cpu_seg_desc_init_intr_gate(struct cpu_seg_desc *desc,
- void (*handler)(void))
-{
- desc->low = (CPU_GDT_SEL_CODE << 16)
- | (((uint32_t)handler) & 0xffff);
- desc->high = (((uint32_t)handler) & 0xffff0000)
- | CPU_SEG_P
- | 0xe00;
-}
-
-static void
-cpu_pseudo_desc_init(struct cpu_pseudo_desc *desc,
- const void *addr, size_t size)
-{
- assert(size <= 0x10000);
- desc->limit = size - 1;
- desc->base = (uint32_t)addr;
-}
-
-static struct cpu_seg_desc *
-cpu_get_gdt_entry(size_t selector)
-{
- size_t index;
-
- /*
- * The first 3 bits are the TI and RPL bits
- *
- * See Intel 64 and IA-32 Architecture Software Developer's Manual,
- * Volume 3 System Programming Guide, 3.4.2 Segment Selectors.
- */
- index = selector >> 3;
- assert(index < ARRAY_SIZE(cpu_gdt));
- return &cpu_gdt[index];
-}
-
-static void
cpu_irq_handler_init(struct cpu_irq_handler *handler)
{
handler->fn = NULL;
}
static struct cpu_irq_handler *
-cpu_lookup_irq_handler(unsigned int irq)
+cpu_get_irq_handler(unsigned int irq)
{
assert(irq < ARRAY_SIZE(cpu_irq_handlers));
return &cpu_irq_handlers[irq];
@@ -335,182 +140,122 @@ cpu_irq_handler_set_fn(struct cpu_irq_handler *handler,
handler->arg = arg;
}
-static void
-cpu_setup_gdt(void)
+static inline uint32_t
+cpu_read_ipsr(void)
{
- struct cpu_pseudo_desc pseudo_desc;
-
- cpu_seg_desc_init_null(cpu_get_gdt_entry(CPU_GDT_SEL_NULL));
- cpu_seg_desc_init_code(cpu_get_gdt_entry(CPU_GDT_SEL_CODE));
- cpu_seg_desc_init_data(cpu_get_gdt_entry(CPU_GDT_SEL_DATA));
+ uint32_t vector;
- cpu_pseudo_desc_init(&pseudo_desc, cpu_gdt, sizeof(cpu_gdt));
- cpu_load_gdt(&pseudo_desc);
+ asm volatile("mrs %0, ipsr" : "=r" (vector));
+ return vector;
}
-static void
-cpu_setup_idt(void)
+void
+cpu_exc_main(void)
{
- struct cpu_pseudo_desc pseudo_desc;
+ uint32_t vector, primask;
- for (size_t i = 0; i < ARRAY_SIZE(cpu_irq_handlers); i++) {
- cpu_irq_handler_init(cpu_lookup_irq_handler(i));
- }
+ vector = cpu_read_ipsr();
- for (size_t i = 0; i < ARRAY_SIZE(cpu_idt); i++) {
- cpu_seg_desc_init_intr_gate(&cpu_idt[i], cpu_default_intr_handler);
- }
+ assert(vector < CPU_EXC_IRQ_BASE);
- cpu_seg_desc_init_intr_gate(&cpu_idt[CPU_IDT_VECT_DIV],
- cpu_isr_divide_error);
- cpu_seg_desc_init_intr_gate(&cpu_idt[CPU_IDT_VECT_GP],
- cpu_isr_general_protection);
- cpu_seg_desc_init_intr_gate(&cpu_idt[32], cpu_isr_32);
- cpu_seg_desc_init_intr_gate(&cpu_idt[33], cpu_isr_33);
- cpu_seg_desc_init_intr_gate(&cpu_idt[34], cpu_isr_34);
- cpu_seg_desc_init_intr_gate(&cpu_idt[35], cpu_isr_35);
- cpu_seg_desc_init_intr_gate(&cpu_idt[36], cpu_isr_36);
- cpu_seg_desc_init_intr_gate(&cpu_idt[37], cpu_isr_37);
- cpu_seg_desc_init_intr_gate(&cpu_idt[38], cpu_isr_38);
- cpu_seg_desc_init_intr_gate(&cpu_idt[39], cpu_isr_39);
- cpu_seg_desc_init_intr_gate(&cpu_idt[40], cpu_isr_40);
- cpu_seg_desc_init_intr_gate(&cpu_idt[41], cpu_isr_41);
- cpu_seg_desc_init_intr_gate(&cpu_idt[42], cpu_isr_42);
- cpu_seg_desc_init_intr_gate(&cpu_idt[43], cpu_isr_43);
- cpu_seg_desc_init_intr_gate(&cpu_idt[44], cpu_isr_44);
- cpu_seg_desc_init_intr_gate(&cpu_idt[45], cpu_isr_45);
- cpu_seg_desc_init_intr_gate(&cpu_idt[46], cpu_isr_46);
- cpu_seg_desc_init_intr_gate(&cpu_idt[47], cpu_isr_47);
-
- cpu_pseudo_desc_init(&pseudo_desc, cpu_idt, sizeof(cpu_idt));
- cpu_load_idt(&pseudo_desc);
-}
-
-static void
-cpu_print_frame(const struct cpu_intr_frame *frame)
-{
- printf("cpu: vector: %-8x eip: %08x eax: %08x ebx: %08x\n"
- "cpu: error: %-8x esp: %08x ecx: %08x edx: %08x\n"
- "cpu: eflags: %08x ebp: %08x esi: %08x edi: %08x\n",
- (unsigned int)frame->vector, (unsigned int)frame->eip,
- (unsigned int)frame->eax, (unsigned int)frame->ebx,
- (unsigned int)frame->error, (unsigned int)(frame + 1),
- (unsigned int)frame->ecx, (unsigned int)frame->edx,
- (unsigned int)frame->eflags, (unsigned int)frame->ebp,
- (unsigned int)frame->esi, (unsigned int)frame->edi);
-}
+ /*
+ * Interrupt handlers may call functions that may in turn yield the
+ * processor. When running in interrupt context, as opposed to thread
+ * context, there is no way to yield the processor, because the context
+ * isn't saved into a scheduled structure, which is what threads are
+ * for. As a result, disable preemption to prevent an invalid context
+ * switch.
+ */
+ primask = thread_preempt_disable_intr_save();
-static void
-cpu_exc_main(const struct cpu_intr_frame *frame)
-{
- printf("cpu: exception:\n");
- cpu_print_frame(frame);
-
- switch (frame->vector)
- {
- case CPU_IDT_VECT_DIV:
- panic("cpu: divide error");
- case CPU_IDT_VECT_GP:
- panic("cpu: general protection fault");
+ switch (vector) {
+ case CPU_EXC_SYSTICK:
+ thread_report_tick();
+ timer_report_tick();
+ break;
default:
- cpu_default_intr_handler();
+ printf("cpu: error: unhandled exception:%lu\n", (unsigned long)vector);
+ cpu_halt();
}
+
+ thread_preempt_enable_intr_restore(primask);
}
void
-cpu_intr_main(const struct cpu_intr_frame *frame)
+cpu_irq_main(void)
{
struct cpu_irq_handler *handler;
+ uint32_t primask;
unsigned int irq;
- assert(!cpu_intr_enabled());
- assert(frame->vector < ARRAY_SIZE(cpu_idt));
+ irq = cpu_read_ipsr() - CPU_EXC_IRQ_BASE;
- /*
- * Interrupt handlers may call functions that may in turn yield the
- * processor. When running in interrupt context, as opposed to thread
- * context, there is no way to yield the processor, because the context
- * isn't saved into a scheduled structure, which is what threads are
- * for. As a result, disable preemption to prevent an invalid context
- * switch.
- */
- thread_preempt_disable();
-
- if (frame->vector < CPU_IDT_VECT_IRQ_BASE) {
- cpu_exc_main(frame);
- } else {
- irq = frame->vector - CPU_IDT_VECT_IRQ_BASE;
-
- /*
- * Acknowledge the IRQ as early as possible to allow another one to
- * be raised.
- */
- i8259_irq_eoi(irq);
-
- handler = cpu_lookup_irq_handler(irq);
-
- if (!handler || !handler->fn) {
- printf("cpu: error: invalid handler for irq %u\n", irq);
- } else {
- handler->fn(handler->arg);
- }
+ primask = thread_preempt_disable_intr_save();
+
+ handler = cpu_get_irq_handler(irq);
+
+ if (!handler || !handler->fn) {
+ panic("cpu: error: invalid handler for irq %u", irq);
}
- /*
- * On entry, preemption could have been either enabled or disabled.
- * If it was enabled, this call will reenable it. As a side effect,
- * it will check if the current thread was marked for yielding, e.g.
- * because the interrupt handler has awaken a higher priority thread,
- * in which case a context switch is triggerred. Such context switches
- * are called involuntary.
- *
- * Here is what the stack looks like when such a context switch occurs :
- *
- * | | Stack grows down.
- * | |
- * | stack of the interrupted thread |
- * | |
- * +---------------------------------+ <- interrupt occurs
- * | |
- * | struct cpu_intr_frame |
- * | |
- * +---------------------------------+
- * | |
- * | cpu_intr_main stack frame |
- * | |
- * +---------------------------------+
- * | |
- * | thread function stack frames |
- * | |
- * +---------------------------------+
- * | |
- * | thread context on switch | See thread_switch_context in
- * | | thread_asm.S.
- * +---------------------------------+
- */
- thread_preempt_enable();
+ handler->fn(handler->arg);
+
+ thread_preempt_enable_intr_restore(primask);
}
void
cpu_irq_register(unsigned int irq, cpu_irq_handler_fn_t fn, void *arg)
{
struct cpu_irq_handler *handler;
- uint32_t eflags;
+ uint32_t primask;
- thread_preempt_disable();
- eflags = cpu_intr_save();
+ primask = thread_preempt_disable_intr_save();
- handler = cpu_lookup_irq_handler(irq);
+ handler = cpu_get_irq_handler(irq);
cpu_irq_handler_set_fn(handler, fn, arg);
- i8259_irq_enable(irq);
+ nvic_irq_enable(irq);
- thread_preempt_enable();
- cpu_intr_restore(eflags);
+ thread_preempt_enable_intr_restore(primask);
+}
+
+void *
+cpu_stack_forge(void *stack, size_t size, thread_fn_t fn, void *arg)
+{
+ struct cpu_exc_frame *frame;
+
+ assert(P2ALIGNED((uintptr_t)stack, CPU_STACK_ALIGN));
+
+ if (size <= sizeof(*frame)) {
+ panic("cpu: error: stack too small");
+ }
+
+ frame = stack + size;
+ frame--;
+
+ frame->r4 = 4;
+ frame->r5 = 5;
+ frame->r6 = 6;
+ frame->r7 = 7;
+ frame->r8 = 8;
+ frame->r9 = 9;
+ frame->r10 = 10;
+ frame->r11 = 11;
+ frame->r0 = (uint32_t)fn;
+ frame->r1 = (uint32_t)arg;
+ frame->r2 = 2;
+ frame->r3 = 3;
+ frame->r12 = 12;
+ frame->r14 = 0;
+ frame->r15 = (uint32_t)thread_main & ~1; /* Must be halfword aligned */
+ frame->psr = CPU_PSR_THUMB;
+
+ return frame;
}
void
cpu_setup(void)
{
- cpu_setup_gdt();
- cpu_setup_idt();
+ for (size_t i = 0; i < ARRAY_SIZE(cpu_irq_handlers); i++) {
+ cpu_irq_handler_init(cpu_get_irq_handler(i));
+ }
}