diff --git a/Makefile b/Makefile index 712ff19..302ac37 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,11 @@ $(BUILD_DIR)/myos.bin: $(OBJECTS) -include $(DEPENDS) +$(BUILD_DIR)/kernel/interrupts.o: $(SOURCE_DIR)/kernel/interrupts.c Makefile + @mkdir -p $(@D) + $(CC) -c $(CFLAGS) -mgeneral-regs-only -mno-red-zone $< -o $@ + + $(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c Makefile @mkdir -p $(@D) $(CC) -c $(CFLAGS) $< -o $@ @@ -61,3 +66,4 @@ $(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c Makefile $(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.S Makefile @mkdir -p $(@D) $(AS) $(ASFLAGS) $< -o $@ + diff --git a/interrupts.h b/interrupts.h new file mode 100644 index 0000000..b14c454 --- /dev/null +++ b/interrupts.h @@ -0,0 +1,3 @@ +#pragma once + +__attribute__((interrupt)) void interrupt_handler_1(struct interrupt_frame* frame); diff --git a/src/include/str.h b/src/include/str.h index c679e28..03096a9 100644 --- a/src/include/str.h +++ b/src/include/str.h @@ -16,3 +16,6 @@ static inline struct str str_slice(struct str s, size_t begin, size_t end) .len = s.len - begin - end, }; } + +#define CSTR_(x) #x +#define CSTR(x) CSTR_(x) diff --git a/src/kernel/gdt.c b/src/kernel/gdt.c new file mode 100644 index 0000000..1adddd6 --- /dev/null +++ b/src/kernel/gdt.c @@ -0,0 +1,54 @@ +#include "gdt.h" +#include "libc.h" +#include "str.h" + +inline uint16_t gdt_segment_selector(uint16_t index, uint16_t table_indicator, uint16_t priv_level) +{ + return (index << 3) + | ((table_indicator << 2) & 0b100) + | ((priv_level << 0) & 0b011); +} + +struct gdt_table_entry gdt_encode_entry(struct gdt_entry_content content) +{ + if (content.limit > GDT_LIMIT_MAX) + panic(str_attach("GDT cannot encode limits larger than " CSTR(GDT_LIMIT_MAX))); + + struct gdt_table_entry entry; + + entry.data[GDT_SDESC_LIMIT_0] = (content.limit >> 0) & 0xFF; + entry.data[GDT_SDESC_LIMIT_1] = (content.limit >> 8) & 0xFF; + entry.data[GDT_SDESC_LIMIT_2] = (content.limit >> 16) & 0xFF; + + entry.data[GDT_SDESC_BASE_0] = (content.base >> 0) & 0xFF; + entry.data[GDT_SDESC_BASE_1] = (content.base >> 8) & 0xFF; + entry.data[GDT_SDESC_BASE_2] = (content.base >> 16) & 0xFF; + entry.data[GDT_SDESC_BASE_3] = (content.base >> 24) & 0xFF; + + entry.data[GDT_SDESC_ACCESSBYTE] = content.access_byte; + + entry.data[GDT_SDESC_FLAGS] |= (content.flags << 4); + + return entry; +} + +void gdt_set(uint16_t size, const struct gdt_table_entry base[], uint32_t offset) +{ + base += offset; + + /* the lgdt instruction requires a packed alignment */ + struct __attribute__((packed)) gdtr { + uint16_t size; + uint32_t base; + } gdt = { + .size = size, + .base = (uint32_t)base, + }; + + __asm__ volatile ( + "lgdt %[gdt]" + : + : [gdt] "m"(gdt) + ); +} + diff --git a/src/kernel/gdt.h b/src/kernel/gdt.h new file mode 100644 index 0000000..c817b99 --- /dev/null +++ b/src/kernel/gdt.h @@ -0,0 +1,110 @@ +#pragma once +#include + +#define GDT_LIMIT_MAX 0xFFFFF + +struct gdt_table_entry { + volatile uint8_t data[8]; +}; +_Static_assert(sizeof(struct gdt_table_entry) == 8); + +struct gdt_entry_content { + volatile uint32_t base; + volatile uint32_t limit; + volatile uint8_t access_byte; + volatile uint8_t flags; +}; + +enum gdt_segment_descriptor_index { + GDT_SDESC_BASE_0 = 2, + GDT_SDESC_BASE_1 = 3, + GDT_SDESC_BASE_2 = 4, + GDT_SDESC_BASE_3 = 7, + + GDT_SDESC_LIMIT_0 = 0, + GDT_SDESC_LIMIT_1 = 1, + GDT_SDESC_LIMIT_2 = 6, + + GDT_SDESC_ACCESSBYTE = 5, + + GDT_SDESC_FLAGS = 6, +}; + +enum gdt_access_flag : uint8_t { + GDT_ACCESS_ACCESSED = (1<<0), + GDT_ACCESS_RW = (1<<1), + GDT_ACCESS_DC = (1<<2), + GDT_ACCESS_EXEC = (1<<3), + GDT_ACCESS_DESCRIPTOR = (1<<4), + + GDT_ACCESS_DPL_0 = (0<<5), + GDT_ACCESS_DPL_1 = (1<<5), + GDT_ACCESS_DPL_2 = (2<<5), + GDT_ACCESS_DPL_3 = (3<<5), + + GDT_ACCESS_PRESENT = (1<<7), +}; + +enum gdt_flags : uint8_t { + GDT_RESERVED = (1<<0), + GDT_LONG = (1<<1), + GDT_SIZE = (1<<2), + GDT_GRANULARITY = (1<<3), +}; + +void gdt_set(uint16_t limit, const struct gdt_table_entry base[], uint32_t offset); + +struct gdt_table_entry gdt_encode_entry(struct gdt_entry_content content); + +// only compiles with O1 or higher ¯\_(ツ)_/¯, might have to make this a macro +static inline void gdt_reload_granular(uint32_t code, + uint32_t data, + uint32_t extra_segment, + uint32_t general_segment_1, + uint32_t general_segment_2, + uint32_t stack_segment) +{ + __asm__ volatile ( + // Far jump to reload the CS register + "jmp %[cs], $reload_CS\n" + "reload_CS:\n" + "movw %[ds], %%ax\n" + "movw %%ax, %%ds\n" + "movw %[es], %%ax\n" + "movw %%ax, %%es\n" + "movw %[fs], %%ax\n" + "movw %%ax, %%fs\n" + "movw %[gs], %%ax\n" + "movw %%ax, %%gs\n" + "movw %[ss], %%ax\n" + "movw %%ax, %%ss\n" + : // No output operands + : [cs] "i"(code), + [ds] "i"(data), + [es] "i"(extra_segment), + [fs] "i"(general_segment_1), + [gs] "i"(general_segment_2), + [ss] "i"(stack_segment) + : "ax" // Clobbered register + ); +} + +static inline void gdt_reload(uint32_t data, uint32_t code) +{ + __asm__ volatile ( + // Far jump to reload the CS register + "jmp %[cs], $reload_CS\n" + "reload_CS:\n" + // Load data segment into AX and then move it to all segment registers + "movw %[ds], %%ax\n" + "movw %%ax, %%ds\n" + "movw %%ax, %%es\n" + "movw %%ax, %%fs\n" + "movw %%ax, %%gs\n" + "movw %%ax, %%ss\n" + : // No output operands + : [ds] "i"(data), + [cs] "i"(code) + : "ax" // Clobbered register + ); +} diff --git a/src/kernel/idt.c b/src/kernel/idt.c new file mode 100644 index 0000000..b6894c8 --- /dev/null +++ b/src/kernel/idt.c @@ -0,0 +1,43 @@ + +#include + +#include "idt.h" + +struct idt_gate_descriptor idt_encode_descriptor(uint32_t offset, uint16_t segment_selector, uint8_t ist, enum idt_flags type) +{ + struct idt_gate_descriptor output = {0}; + + output.offset_0 = offset & 0xFFFF; + offset >>= 16; + output.offset_1 = offset & 0xFFFF; + + output.segment_selector = segment_selector; + output.ist = ist; + output.flags = type | IDT_DPL_0 | IDT_PRESENT; + + return output; +} + +/* + * size: one less than the idt in bytes + * offset: linear address of the idt + * */ +void idt_load(struct idt_gate_descriptor table[], uint16_t size) +{ + struct __attribute__((packed)) { + uint16_t size; + uint32_t offset; + } idt = { + .size = size, + .offset = (uint32_t)table, + }; + _Static_assert(sizeof idt.size == 2); + _Static_assert(sizeof idt.offset == 4); + _Static_assert(sizeof idt == 6); + + __asm__ volatile ( + "lidt %[idt]" + : + : [idt] "m"(idt) + ); +} diff --git a/src/kernel/idt.h b/src/kernel/idt.h new file mode 100644 index 0000000..7438dee --- /dev/null +++ b/src/kernel/idt.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +struct __attribute__((packed)) idt_gate_descriptor { + uint16_t offset_0; + uint16_t segment_selector; + uint8_t ist; + + /* `flags` + ======= + bit 0-3: gate type + bit 4: unused + bit 5-6: dpl + bit 7: present bit + + DPL is ignored by hardware interupts and present bit must be 1 */ + uint8_t flags; + + uint16_t offset_1; +}; + +enum idt_flags : uint8_t { + IDT_GATE_TYPE_TASK_GATE = 0x5, + IDT_GATE_TYPE_INTERUPT_GATE_16 = 0x6, + IDT_GATE_TYPE_TRAP_GATE_16 = 0x7, + IDT_GATE_TYPE_INTERUPT_GATE_32 = 0xE, + IDT_GATE_TYPE_TRAP_GATE_32 = 0xF, + + IDT_DPL_0 = 0 << 5, + IDT_DPL_1 = 1 << 5, + IDT_DPL_2 = 2 << 5, + IDT_DPL_3 = 3 << 5, + + IDT_PRESENT = 1 << 7, +}; + +struct idt_gate_descriptor idt_encode_descriptor(uint32_t offset, uint16_t segment_selector, uint8_t ist, enum idt_flags type); + +void idt_load(struct idt_gate_descriptor table[], uint16_t size); + diff --git a/src/kernel/interrupts.c b/src/kernel/interrupts.c new file mode 100644 index 0000000..02c7e05 --- /dev/null +++ b/src/kernel/interrupts.c @@ -0,0 +1,21 @@ +#include +#include "libc.h" + +#ifdef __x86_64__ +typedef unsigned long long int uword_t; +#else +typedef unsigned int uword_t; +#endif + +struct __attribute__((packed)) interrupt_frame { + uword_t ip; + uword_t cs; + uword_t flags; + uword_t sp; + uword_t ss; +}; + +__attribute__((interrupt)) void interrupt_handler_1(struct interrupt_frame* frame) +{ + (void)frame; +} diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c index 0f49e37..2efb971 100644 --- a/src/kernel/kernel.c +++ b/src/kernel/kernel.c @@ -2,166 +2,88 @@ #include #include +#include "gdt.h" +#include "tss.h" +#include "idt.h" + +// Future user-space #include "libc.h" #include "tty.h" -#define STR_(x) #x -#define STR(x) STR_(x) +// TOOD: clean up this +typedef void(*func_t)(void*); -// TODO: remove -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" +static void ring3_mode(uint32_t, uint32_t, uint32_t, uint32_t, func_t); +static void user_mode_code(void*); + +void gdt_setup_flat_model() +{ +} + +/* copied and only slightly modified from an osdev wiki article with poor + taste, TODO: change + ----------------------------------------------*/ +static void user_mode_code(void*) +{ + printf(str_attach("hello world :)\n")); +} + +static void ring3_mode(uint32_t udata_offset, uint32_t udata_rpl, uint32_t ucode_offset, uint32_t ucode_rpl, func_t callback) +{ + const uint32_t udata = udata_offset | udata_rpl; + const uint32_t ucode = ucode_offset | ucode_rpl; + + __asm__ volatile ( + "mov %[udata], %%ax\n" + "mov %%ax, %%ds\n" + "mov %%ax, %%es\n" + "mov %%ax, %%fs\n" + "mov %%ax, %%gs\n" + "push %%ax\n" + + "mov %%esp, %%eax\n" + "push %%eax\n" // current esp + "pushf\n" // eflags + "push %[ucode]\n" + "push %[callback]\n" // instruction address to return to + "iret" + : + : [udata] "m"(udata), + [ucode] "m"(ucode), + [callback] "m"(callback) + : "eax" + ); +} +/*-----------------------------------------------*/ /** - * GDT Setup - * ========= + * Kernel entrypoint + * ================= + * The kernel entrypoints sets up the GDT, TSS and IDT and moves to ring 3 */ -#define GDT_LIMIT_MAX 0xFFFFF - -struct gdt_table_entry { - uint8_t data[8]; -}; - -enum gdt_segment_descriptor_index : size_t { - GDT_SDESC_BASE_0 = 2, - GDT_SDESC_BASE_1 = 3, - GDT_SDESC_BASE_2 = 4, - GDT_SDESC_BASE_3 = 7, - - GDT_SDESC_LIMIT_0 = 0, - GDT_SDESC_LIMIT_1 = 1, - GDT_SDESC_LIMIT_2 = 6, - - GDT_SDESC_ACCESSBYTE = 5, - - GDT_SDESC_FLAGS = 6, -}; - -enum gdt_access_flag : uint8_t { - GDT_ACCESS_ACCESSED = (1<<0), - GDT_ACCESS_RW = (1<<1), - GDT_ACCESS_DC = (1<<2), - GDT_ACCESS_EXEC = (1<<3), - GDT_ACCESS_DESCRIPTOR = (1<<4), - - GDT_ACCESS_DPL_0 = (0<<5), - GDT_ACCESS_DPL_1 = (1<<5), - GDT_ACCESS_DPL_2 = (2<<5), - GDT_ACCESS_DPL_3 = (3<<5), - - GDT_ACCESS_PRESENT = (1<<7), -}; - -enum gdt_flags : uint8_t { - GDT_RESERVED = (1<<0), - GDT_LONG = (1<<1), - GDT_SIZE = (1<<2), - GDT_GRANULARITY = (1<<3), -}; - -static inline uint16_t gdt_segment_selector(uint16_t index, uint16_t table_indicator, uint16_t priv_level) +void kernel_main(void) { - return (index << 3) - | ((table_indicator << 2) & 0b100) - | ((priv_level << 0) & 0b011); -} + __asm__ volatile("cli"); -struct gdt_entry_content { - uint32_t base; - uint32_t limit; - uint8_t access_byte; - uint8_t flags; -}; + static struct tss tss = {0}; -static void gdt_encode_entry(struct gdt_table_entry* target, struct gdt_entry_content content) -{ - if (content.limit > GDT_LIMIT_MAX) - panic(str_attach("GDT cannot encode limits larger than " STR(GDT_LIMIT_MAX))); - - // encode the limit - target->data[GDT_SDESC_LIMIT_0] = (content.limit >> 0) & 0xFF; - target->data[GDT_SDESC_LIMIT_1] = (content.limit >> 8) & 0xFF; - target->data[GDT_SDESC_LIMIT_2] = (content.limit >> 16) & 0xFF; - - // encode the base - target->data[GDT_SDESC_BASE_0] = (content.base >> 0) & 0xFF; - target->data[GDT_SDESC_BASE_1] = (content.base >> 8) & 0xFF; - target->data[GDT_SDESC_BASE_2] = (content.base >> 16) & 0xFF; - target->data[GDT_SDESC_BASE_3] = (content.base >> 24) & 0xFF; - - // encode the access byte - target->data[GDT_SDESC_ACCESSBYTE] = content.access_byte; - - // encode the flags - target->data[GDT_SDESC_FLAGS] |= (content.flags << 4); -} - -static void gdt_set(uint16_t limit, uint32_t base, uint32_t offset) -{ - base += offset; - - /* the lgdt instruction requires a packed alignment */ - struct __attribute__((packed)) gdtr { - uint16_t limit; - uint32_t base; + enum segment_index : size_t { + null_segment = 0, + kernel_code_segment = 1, + kernel_data_segment = 2, + user_code_segment = 3, + user_data_segment = 4, + task_state_segment = 5, + segment_count, }; - static struct gdtr gdtr; - gdtr.limit = limit; - gdtr.base = base; + static struct gdt_table_entry gdt[segment_count]; + _Static_assert(sizeof(gdt) == 0x30); - __asm__ volatile ( - "lgdt %[gdtr]" - : - : [gdtr] "m"(gdtr) - ); -} + gdt[null_segment] = gdt_encode_entry((struct gdt_entry_content){0}); -// only compiles with O1 or higher ¯\_(ツ)_/¯, might have to make this a macro -static inline void gdt_reload(uint32_t code, - uint32_t data, - uint32_t extra_segment, - uint32_t general_segment_1, - uint32_t general_segment_2, - uint32_t stack_segment) -{ - __asm__ volatile ( - // Far jump to reload the CS register - "jmp %[cs], $reload_CS\n" - "reload_CS:\n" - // Load data segment into AX and then move it to all segment registers - //"movw %[ds], %%ax\n" - "movw %[ds], %%ds\n" - "movw %[es], %%es\n" - "movw %[fs], %%fs\n" - "movw %[gs], %%gs\n" - "movw %[ss], %%ss\n" - : // No output operands - : [cs] "i"(code), - [ds] "i"(data), - [es] "i"(extra_segment), - [fs] "i"(general_segment_1), - [gs] "i"(general_segment_2), - [ss] "i"(stack_segment) - : "ax" // Clobbered register - ); -} - -// sets a flat model -void test_gdt() -{ - // disable interupts - __asm__ volatile ("cli"); - - static struct gdt_table_entry gdt_table[6]; - size_t i=0; - _Static_assert(sizeof(gdt_table) == 0x30); - - // Null Descriptor - gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){0}); - - // Kernel mode code segment - gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){ + /* kernel */ + gdt[kernel_code_segment] = gdt_encode_entry((struct gdt_entry_content){ .base = 0, .limit = GDT_LIMIT_MAX, .access_byte = GDT_ACCESS_RW @@ -172,8 +94,7 @@ void test_gdt() .flags = GDT_SIZE | GDT_GRANULARITY}); - // Kernel mode data segment - gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){ + gdt[kernel_data_segment] = gdt_encode_entry((struct gdt_entry_content){ .base = 0, .limit = GDT_LIMIT_MAX, .access_byte = GDT_ACCESS_RW @@ -183,8 +104,8 @@ void test_gdt() .flags = GDT_SIZE | GDT_GRANULARITY}); - // User mode code segment - gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){ + /* user */ + gdt[user_code_segment] = gdt_encode_entry((struct gdt_entry_content) { .base = 0, .limit = GDT_LIMIT_MAX, .access_byte = GDT_ACCESS_RW @@ -195,8 +116,7 @@ void test_gdt() .flags = GDT_SIZE | GDT_GRANULARITY}); - // User mode data segment - gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){ + gdt[user_data_segment] = gdt_encode_entry((struct gdt_entry_content) { .base = 0, .limit = GDT_LIMIT_MAX, .access_byte = GDT_ACCESS_RW @@ -206,9 +126,9 @@ void test_gdt() .flags = GDT_SIZE | GDT_GRANULARITY}); - // Task state segment - gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){ - .base = &tss, + /* tss */ + gdt[task_state_segment] = gdt_encode_entry((struct gdt_entry_content) { + .base = (uint32_t)&tss, .limit = sizeof(tss)-1, .access_byte = GDT_ACCESS_ACCESSED | GDT_ACCESS_EXEC @@ -216,25 +136,31 @@ void test_gdt() | GDT_ACCESS_PRESENT, .flags = 0}); - gdt_set(0, 0, 0); - const uint16_t code_segment = 0x8; // placeholder - const uint16_t data_segment = 0x10; // placeholder - gdt_reload(code_segment, data_segment); + gdt_set(sizeof gdt, gdt, 0); + + gdt_reload(kernel_data_segment * sizeof (struct gdt_table_entry), + kernel_code_segment * sizeof (struct gdt_table_entry)); + + /* Setup the TSS */ + tss.ss0 = kernel_data_segment * sizeof (struct gdt_table_entry); + tss.esp0 = 0; /* TODO: set kernel stack pointer */ + + tss_load(task_state_segment * sizeof (struct gdt_table_entry)); + + /* Setup the IDT */ + //static struct idt_gate_descriptor idt[256] = {0}; + + //struct idt_gate_descriptor idt_encode_descriptor(uint32_t offset, uint16_t segment_selector, uint8_t ist, enum idt_flags type); + //idt[80] = idt_encode_descriptor(interrupt_handler_1, kernel_code_segment, 0, IDT_GATE_TYPE_INTERUPT_GATE_32); + + //idt_load(idt, sizeof idt); + + /* Finally go to ring 3 */ + ring3_mode(user_data_segment * sizeof (struct gdt_table_entry), 3, + user_code_segment * sizeof (struct gdt_table_entry), 3, + user_mode_code); + + + __asm__ volatile("sti"); } -/** - * Kernel entrypoint - * ================= - */ -void kernel_main(void) -{ - terminal_clear(); - - for (size_t i = 0; i < VGA_HEIGHT / 2 + 1; i++) { - printf(str_attach("hello {u32}!\n"), i); - } -} - - -// TODO: remove -#pragma GCC diagnostic pop diff --git a/src/kernel/tss.c b/src/kernel/tss.c new file mode 100644 index 0000000..a281dde --- /dev/null +++ b/src/kernel/tss.c @@ -0,0 +1,6 @@ +#include "tss.h" + +void tss_init(struct tss* t) +{ + (void)t; +} diff --git a/src/kernel/tss.h b/src/kernel/tss.h new file mode 100644 index 0000000..c2cbd87 --- /dev/null +++ b/src/kernel/tss.h @@ -0,0 +1,68 @@ +#pragma once +#include + +struct __attribute__((packed)) tss { + /* Contains the Segment Selector for the TSS of the previous task, with + hardware task switching these form a kind of backward linked list. */ + uint16_t link; + uint16_t link_reserved; + + /* the stack pointer to load when changing to kernel mode */ + uint32_t esp0; + + /* the stack segment pointer to load when changing to kernel mode */ + uint16_t ss0; + + uint16_t ss0_reserved; + + /* everything under here is unused in software task switching */ + struct __attribute__((packed)) { + /* esp and ss 1 and 2 would be used when switching to ring 1 and 2 */ + uint32_t esp1; + uint16_t ss1; + uint16_t ss1_reserved; + uint32_t esp2; + uint16_t ss2; + uint16_t ss2_reserved; + uint32_t cr3; + uint32_t eip; + uint32_t eflags; + uint32_t eax; + uint32_t ecx; + uint32_t edx; + uint32_t ebx; + uint32_t esp; + uint32_t ebp; + uint32_t esi; + uint32_t edi; + uint16_t es; + uint16_t es_reserved; + uint16_t cs; + uint16_t cs_reserved; + uint16_t ss; + uint16_t ss_reserved; + uint16_t ds; + uint16_t ds_reserved; + uint16_t fs; + uint16_t fs_reserved; + uint16_t gs; + uint16_t gs_reserved; + uint16_t ldtr; + uint16_t ldtr_reserved; + uint16_t reserved; + uint16_t iopb; + uint32_t ssp; + } unused; +}; + +/* offset: the offset in bytes of the tss descriptor in the gdt table */ +static inline void tss_load(uint32_t offset) +{ + __asm__ volatile ( + "mov %[offset], %%ax\n\t" + "ltr %%ax" + : + : [offset] "m"(offset) + : "ax"); +} +