summaryrefslogtreecommitdiff
path: root/src/cpu.c
diff options
context:
space:
mode:
authorRichard Braun <rbraun@sceen.net>2017-10-14 23:45:04 +0200
committerRichard Braun <rbraun@sceen.net>2018-01-04 01:57:38 +0100
commit9437f135da9fab16180fc64cdd64e2a3bb3d5b7a (patch)
tree8cd3d9e769c2af24463d58e8ba416aae9de9ce7b /src/cpu.c
Initial commit
Diffstat (limited to 'src/cpu.c')
-rw-r--r--src/cpu.c493
1 files changed, 493 insertions, 0 deletions
diff --git a/src/cpu.c b/src/cpu.c
new file mode 100644
index 0000000..c336c5e
--- /dev/null
+++ b/src/cpu.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 2017 Richard Braun.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * 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>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <lib/macros.h>
+
+#include "cpu.h"
+#include "error.h"
+#include "i8259.h"
+#include "thread.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
+ */
+#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)
+
+/*
+ * 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
+ */
+struct cpu_seg_desc {
+ uint32_t low;
+ uint32_t high;
+};
+
+/*
+ * 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.
+ */
+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);
+
+/*
+ * Handler for external interrupt requests.
+ */
+struct cpu_irq_handler {
+ cpu_irq_handler_fn_t fn;
+ void *arg;
+};
+
+/*
+ * Array where driver IRQ handlers are registered.
+ *
+ * Interrupts and preemption must be disabled when accessing the handlers.
+ */
+static struct cpu_irq_handler cpu_irq_handlers[I8259_NR_IRQ_VECTORS];
+
+/*
+ * The interrupt frame is the stack content forged by interrupt handlers.
+ * They store the data needed to restore the processor to its state prior
+ * to the interrupt.
+ */
+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;
+
+ /* These members are automatically pushed by the CPU */
+ uint32_t eip;
+ uint32_t cs;
+ uint32_t eflags;
+};
+
+/*
+ * 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;
+}
+
+void
+cpu_halt(void)
+{
+ cpu_intr_disable();
+
+ for (;;) {
+ cpu_idle();
+ }
+}
+
+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)
+{
+ assert(irq < ARRAY_SIZE(cpu_irq_handlers));
+ return &cpu_irq_handlers[irq];
+}
+
+static void
+cpu_irq_handler_set_fn(struct cpu_irq_handler *handler,
+ cpu_irq_handler_fn_t fn, void *arg)
+{
+ assert(handler->fn == NULL);
+ handler->fn = fn;
+ handler->arg = arg;
+}
+
+static void
+cpu_setup_gdt(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));
+
+ cpu_pseudo_desc_init(&pseudo_desc, cpu_gdt, sizeof(cpu_gdt));
+ cpu_load_gdt(&pseudo_desc);
+}
+
+static void
+cpu_setup_idt(void)
+{
+ struct cpu_pseudo_desc pseudo_desc;
+
+ for (size_t i = 0; i < ARRAY_SIZE(cpu_irq_handlers); i++) {
+ cpu_irq_handler_init(cpu_lookup_irq_handler(i));
+ }
+
+ for (size_t i = 0; i < ARRAY_SIZE(cpu_idt); i++) {
+ cpu_seg_desc_init_intr_gate(&cpu_idt[i], cpu_default_intr_handler);
+ }
+
+ 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);
+}
+
+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");
+ default:
+ cpu_default_intr_handler();
+ }
+}
+
+void
+cpu_intr_main(const struct cpu_intr_frame *frame)
+{
+ struct cpu_irq_handler *handler;
+ unsigned int irq;
+
+ assert(!cpu_intr_enabled());
+ assert(frame->vector < ARRAY_SIZE(cpu_idt));
+
+ /*
+ * 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);
+ }
+ }
+
+ /*
+ * 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.
+ */
+ thread_preempt_enable();
+}
+
+void
+cpu_irq_register(unsigned int irq, cpu_irq_handler_fn_t fn, void *arg)
+{
+ struct cpu_irq_handler *handler;
+ uint32_t eflags;
+
+ thread_preempt_disable();
+ eflags = cpu_intr_save();
+
+ handler = cpu_lookup_irq_handler(irq);
+ cpu_irq_handler_set_fn(handler, fn, arg);
+ i8259_irq_enable(irq);
+
+ thread_preempt_disable();
+ cpu_intr_restore(eflags);
+}
+
+void
+cpu_setup(void)
+{
+ cpu_setup_gdt();
+ cpu_setup_idt();
+}