diff options
| -rw-r--r-- | kernel/cpu.c | 137 |
1 files changed, 124 insertions, 13 deletions
diff --git a/kernel/cpu.c b/kernel/cpu.c index c11b3bb..e0ed188 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c @@ -2,43 +2,154 @@ #include <stdint.h> -#define KERNEL_CODE_DESC 0x00af9a000000ffff -#define KERNEL_DATA_DESC 0x00cf92000000ffff +#define GDT_ENTRIES 16 +#define IDT_ENTRIES 256 -#define KERNEL_CODE_SEL 0x8 -#define KERNEL_DATA_SEL 0x10 +#define CODE_SEG_INDEX 1 +#define DATA_SEG_INDEX 2 -struct gdtr64 +struct dtr64 { uint16_t limit; uint64_t base; } __attribute__((packed)); -static uint64_t gdt[16]; +struct segment_descriptor +{ + uint16_t limit0; + uint16_t base0; + uint8_t base16; + uint8_t access; + uint8_t flags_limit16; + uint8_t base24; +} __attribute__((packed)); -void cpu_init(void) +enum segment_descriptor_type +{ + CODE64_SEG, + DATA_SEG, +}; + +struct gate_descriptor +{ + uint16_t offset0; + uint16_t selector; + uint8_t ist; + uint8_t access; + uint16_t offset16; + uint64_t offset32; +} __attribute__((packed)); + +enum gate_descriptor_type +{ + INT64_GATE, + TRAP64_GATE, +}; + +// TODO: is 16 enough? +static struct segment_descriptor gdt[GDT_ENTRIES]; +static struct gate_descriptor idt[IDT_ENTRIES]; + +static void set_gdt(int index, + uint64_t base, + uint64_t limit, + enum segment_descriptor_type type) +{ + gdt[index].limit0 = limit & 0xffff; + gdt[index].base0 = base & 0xffff; + gdt[index].base16 = (base >> 16) & 0xff; + gdt[index].flags_limit16 = (limit >> 16) & 0xf; + gdt[index].base24 = (base >> 24) & 0xff; + + switch (type) + { + case CODE64_SEG: + gdt[index].access = 0x9a; + gdt[index].flags_limit16 |= 0xa0; + break; + case DATA_SEG: + gdt[index].access = 0x92; + gdt[index].flags_limit16 |= 0xc0; + break; + }; +} + +static void set_idt(int index, + uint16_t selector, + uint64_t offset, + enum gate_descriptor_type type) { - gdt[0] = 0; - gdt[1] = KERNEL_CODE_DESC; - gdt[2] = KERNEL_DATA_DESC; + idt[index].offset0 = offset & 0xffff; + idt[index].selector = selector; + idt[index].offset16 = (offset >> 16) & 0xffff; + idt[index].offset32 = (offset >> 32) & 0xffffffff; - struct gdtr64 gdtr = {sizeof(gdt) - 1, (uint64_t)&gdt}; + switch (type) + { + case INT64_GATE: + idt[index].access = 0x8e; + break; + case TRAP64_GATE: + idt[index].access = 0x8f; + break; + } +} + +static void gdt_init(void) +{ + set_gdt(CODE_SEG_INDEX, 0, (uint64_t)-1, CODE64_SEG); + set_gdt(DATA_SEG_INDEX, 0, (uint64_t)-1, DATA_SEG); + + struct dtr64 gdtr = {sizeof(gdt) - 1, (uint64_t)&gdt}; __asm__ volatile - ( "pushf\n" + ( + // can't have interrupts during GDT reload + // but don't re-enable interrupts if they were already disabled + "pushf\n" "cli\n" "lgdt %0\n" "mov %w2, %%ds\n" "mov %w2, %%es\n" "mov %w2, %%fs\n" "mov %w2, %%gs\n" + // make a far return frame on the stack + // [ss] @rsp+8 + // [rip] @rsp "pushq %1\n" "lea .flush(%%rip), %%rax\n" "push %%rax\n" + // far return, now we're in the code segment + // defined here. "lretq\n" ".flush:\n" "popf\n" - :: "m"(gdtr), "i"(KERNEL_CODE_SEL), "r"(KERNEL_DATA_SEL) + // dependency on pre-boot GDT is now gone + :: "m"(gdtr), "i"(CODE_SEG_INDEX << 3), "r"(DATA_SEG_INDEX << 3) + ); +} + +static void idt_init(void) +{ + struct dtr64 idtr = {sizeof(idt) - 1, (uint64_t)&idt}; + + __asm__ volatile + ( + // can't have interrupts enabled during IDT reload--obviously. + // as with GDT don't re-enable interrupts if they were already + // disabled. + "pushf\n" + "cli\n" + "lidt %0\n" + "popf\n" + // much more straightforward than the gdt code. + :: "m"(idtr) ); } +void cpu_init(void) +{ + gdt_init(); + idt_init(); +} + |
