diff options
Diffstat (limited to 'src/cpu.c')
-rw-r--r-- | src/cpu.c | 515 |
1 files changed, 130 insertions, 385 deletions
@@ -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)); + } } |