From 24934406d5d39e013e22a9e6f4138c4169460d71 Mon Sep 17 00:00:00 2001 From: dzwdz Date: Thu, 25 Jul 2024 20:15:40 +0200 Subject: kernel: set up the GDT in assembly This is just for simplicity's sake. I think I could even omit the `movw $TSS, (GdtTss + 2)` and have the linker fill that out as a relocation, but that would probably be more complex overall. --- src/kernel/arch/amd64/32/boot.S | 114 +++++++++++++++++++++++++++++++++ src/kernel/arch/amd64/32/boot.s | 81 ----------------------- src/kernel/arch/amd64/32/gdt.c | 111 -------------------------------- src/kernel/arch/amd64/boot64.S | 33 ++++++++++ src/kernel/arch/amd64/boot64.s | 12 ---- src/kernel/arch/amd64/interrupts/idt.c | 2 - 6 files changed, 147 insertions(+), 206 deletions(-) create mode 100644 src/kernel/arch/amd64/32/boot.S delete mode 100644 src/kernel/arch/amd64/32/boot.s delete mode 100644 src/kernel/arch/amd64/32/gdt.c create mode 100644 src/kernel/arch/amd64/boot64.S delete mode 100644 src/kernel/arch/amd64/boot64.s (limited to 'src') diff --git a/src/kernel/arch/amd64/32/boot.S b/src/kernel/arch/amd64/32/boot.S new file mode 100644 index 0000000..3f5798f --- /dev/null +++ b/src/kernel/arch/amd64/32/boot.S @@ -0,0 +1,114 @@ +.section .text +.global _start +.type _start, @function +_start: + cli + mov $_stack_top, %esp + push %ebx // save the address of the multiboot struct + + mov $0x80000000, %eax // check CPUID extended functions + cpuid + cmp $0x80000001, %eax + jb panic_early + + mov $0x80000001, %eax + cpuid + test $(1<<29), %edx // check long mode support + jz panic_early + + mov %cr4, %eax + or $(1<<5 | 1<<9 | 1<<10), %eax // PAE | SSE | SSE + mov %eax, %cr4 + + call pml4_identity_init + mov $pml4_identity, %eax + mov %eax, %cr3 + + mov $0xC0000080, %ecx // EFER MSR + rdmsr + or $(1 | 1<<8 | 1<<11), %eax // syscall/ret | long mode | NX + wrmsr + + mov %cr0, %eax + or $0x80000002, %eax // enable paging, coprocessor monitoring + and $(~4), %eax // disable coprocessor emulation + mov %eax, %cr0 + + /* fill out the TSS's address */ + movw $TSS, (GdtTss + 2) + lgdt (GdtPointer) + + pop %edi + + mov $(2 << 3), %eax // SEG_r0data + mov %eax, %ds + mov %eax, %ss + mov %eax, %es + mov %eax, %fs + mov %eax, %gs + + ljmp $(1 << 3), $boot64 // SEG_r0code + +panic_early: + // output a vga Fuck + movl $0x4F754F46, 0xB872A + movl $0x4F6B4F63, 0xB872E + jmp cpu_halt + +.global cpu_shutdown +.type cpu_shutdown, @function +cpu_shutdown: +/* This quits QEMU. While I couldn't find this officially documented anywhere, + * it is used by QEMU in tests/tcg/i386/system/boot.S (as of commit 40d6ee), so + * I assume that this is safe-ish to use */ + mov $0x604, %edx + mov $0x2000, %eax + outw %ax, %dx + +.global cpu_halt +.type cpu_halt, @function +cpu_halt: + cli +1: hlt + jmp 1b + + +.global cpu_pause +.type cpu_pause, @function +cpu_pause: + sti + hlt + cli + ret + +.section .shared +.global GDT +.align 8 +GDT: +#define GdtLimit(p) (p & 0xFFFF) | ((p >> 16) << 48) /* doesn't check if p fits in 20 bits */ +#define GdtAccessed (1<<40) +#define GdtReadWrite (1<<41) +#define GdtCode (1<<43) +#define GdtCodeOrData (1<<44) +#define GdtRing(r) (r<<45) +#define GdtPresent (1<<47) +#define GdtAvailable (1<<52) +#define GdtLongMode (1<<53) +#define GdtPageGran (1<<55) /* limit is in pages */ +#define GdtCommon (GdtLimit(0xFFFFF) | GdtPageGran | GdtAccessed | \ + GdtReadWrite | GdtCodeOrData | GdtPresent | GdtAvailable) +.8byte 0 /* [0] = SEG_null */ +.8byte GdtCommon | GdtRing(0) | GdtLongMode | GdtCode /* [1] = SEG_r0code */ +.8byte GdtCommon | GdtRing(0) | GdtLongMode /* [2] = SEG_r0data */ +.8byte GdtCommon | GdtRing(3) | GdtCode /* [3] = SEG_r3code32 */ +.8byte GdtCommon | GdtRing(3) | GdtLongMode /* [4] = SEG_r3data */ +.8byte GdtCommon | GdtRing(3) | GdtLongMode | GdtCode /* [5] = SEG_r3code */ +GdtTss: +.8byte GdtLimit(104) | (9<<40) | GdtPresent | GdtAvailable /* [6] = SEG_TSS */ +.8byte 0 /* [7] = SEG_TSS2 */ + +.section .data +.global GdtPointer +GdtPointer: +.2byte 63 /* size of the GDT - 1 */ +.4byte GDT /* address of the GDT */ diff --git a/src/kernel/arch/amd64/32/boot.s b/src/kernel/arch/amd64/32/boot.s deleted file mode 100644 index b0808d3..0000000 --- a/src/kernel/arch/amd64/32/boot.s +++ /dev/null @@ -1,81 +0,0 @@ -.section .text -.global _start -.type _start, @function -_start: - cli - mov $_stack_top, %esp - push %ebx // save the address of the multiboot struct - - mov $0x80000000, %eax // check CPUID extended functions - cpuid - cmp $0x80000001, %eax - jb panic_early - - mov $0x80000001, %eax - cpuid - test $(1<<29), %edx // check long mode support - jz panic_early - - mov %cr4, %eax - or $(1<<5 | 1<<9 | 1<<10), %eax // PAE | SSE | SSE - mov %eax, %cr4 - - call pml4_identity_init - mov $pml4_identity, %eax - mov %eax, %cr3 - - mov $0xC0000080, %ecx // EFER MSR - rdmsr - or $(1 | 1<<8 | 1<<11), %eax // syscall/ret | long mode | NX - wrmsr - - mov %cr0, %eax - or $0x80000002, %eax // enable paging, coprocessor monitoring - and $(~4), %eax // disable coprocessor emulation - mov %eax, %cr0 - - call gdt_init - lgdt (lgdt_arg) - - pop %edi - - mov $(2 << 3), %eax // SEG_r0data - mov %eax, %ds - mov %eax, %ss - mov %eax, %es - mov %eax, %fs - mov %eax, %gs - - ljmp $(1 << 3), $boot64 // SEG_r0code - -panic_early: - // output a vga Fuck - movl $0x4F754F46, 0xB872A - movl $0x4F6B4F63, 0xB872E - jmp cpu_halt - -.global cpu_shutdown -.type cpu_shutdown, @function -cpu_shutdown: -/* This quits QEMU. While I couldn't find this officially documented anywhere, - * it is used by QEMU in tests/tcg/i386/system/boot.S (as of commit 40d6ee), so - * I assume that this is safe-ish to use */ - mov $0x604, %edx - mov $0x2000, %eax - outw %ax, %dx - -.global cpu_halt -.type cpu_halt, @function -cpu_halt: - cli -1: hlt - jmp 1b - - -.global cpu_pause -.type cpu_pause, @function -cpu_pause: - sti - hlt - cli - ret diff --git a/src/kernel/arch/amd64/32/gdt.c b/src/kernel/arch/amd64/32/gdt.c deleted file mode 100644 index e643f27..0000000 --- a/src/kernel/arch/amd64/32/gdt.c +++ /dev/null @@ -1,111 +0,0 @@ -#include -#include -#include -#include - -extern char _isr_mini_stack; - -struct gdt_entry { - uint64_t limit_low : 16; - uint64_t base_low : 24; - uint64_t accessed : 1; // set by the processor - // CODE | DATA - uint64_t rw : 1; // readable? | writeable? - uint64_t conforming : 1; // conforming? | expands down? - uint64_t code : 1; // 1 | 0 - - uint64_t codeordata : 1; // 1 for everything other than TSS and LDT - uint64_t ring : 2; - uint64_t present : 1; // always 1 - uint64_t limit_high : 4; - uint64_t available : 1; // ??? - uint64_t long_mode : 1; - uint64_t x32 : 1; - uint64_t gran : 1; // 1 - 4kb, 0 - 1b - uint64_t base_high : 8; -} __attribute__((packed)); - -struct tss_entry { - uint32_t reserved0; - uint64_t rsp[3]; - uint64_t ist[8]; - uint64_t reserved2; - uint16_t reserved3; - uint16_t iopb; -} __attribute__((packed)); - -struct lgdt_arg { - uint16_t limit; - uint32_t base; -} __attribute__((packed)); - -__attribute__((section(".shared"))) -static struct gdt_entry GDT[SEG_end]; -__attribute__((section(".shared"))) -static struct tss_entry TSS; - -struct lgdt_arg lgdt_arg; - -static void gdt_fillout(struct gdt_entry* entry, uint8_t ring, bool code) { - *entry = (struct gdt_entry) { - // set up the identity mapping - .limit_low = 0xFFFF, - .limit_high = 0xF, - .gran = 1, - .base_low = 0, - .base_high = 0, - - .ring = ring, - .code = code, - - .accessed = 0, - .rw = 1, - .conforming = 0, - .codeordata = 1, - .present = 1, - .long_mode = 1, - .available = 1, - .x32 = 0, - }; -} - -#pragma GCC diagnostic ignored "-Wpointer-to-int-cast" -void gdt_init(void) { - GDT[SEG_null].present = 0; - - gdt_fillout(&GDT[SEG_r0code], 0, true); - gdt_fillout(&GDT[SEG_r0data], 0, false); - gdt_fillout(&GDT[SEG_r3code32], 3, true); - gdt_fillout(&GDT[SEG_r3data], 3, false); - gdt_fillout(&GDT[SEG_r3code], 3, true); - - lgdt_arg.limit = sizeof(GDT) - 1; - lgdt_arg.base = (uint64_t)&GDT; - - - memset32(&TSS, 0, sizeof(TSS)); - for (int i = 0; i < 3; i++) - TSS.rsp[i] = (uint64_t)&_isr_mini_stack; - TSS.ist[1] = (uint64_t)&_isr_mini_stack; - - uint64_t tss_addr = (uint64_t)&TSS; - GDT[SEG_TSS] = (struct gdt_entry) { - .limit_low = sizeof(TSS), - .limit_high = sizeof(TSS) >> 16, - .gran = 0, - .base_low = tss_addr, - .base_high = tss_addr >> 24, - - .accessed = 1, - .rw = 0, - .conforming = 0, - .code = 1, - .codeordata = 0, - .ring = 0, // was 3 pre-port - .present = 1, - .available = 1, - .long_mode = 0, - .x32 = 0, - }; - memset32(&GDT[SEG_TSS2], 0, sizeof GDT[SEG_TSS2]); -} diff --git a/src/kernel/arch/amd64/boot64.S b/src/kernel/arch/amd64/boot64.S new file mode 100644 index 0000000..f7ec60e --- /dev/null +++ b/src/kernel/arch/amd64/boot64.S @@ -0,0 +1,33 @@ +.global boot64 +boot64: + lgdt (GdtPointer) // try reloading gdt again + mov $(6 << 3 | 3), %ax // SEG_TSS + ltr %ax + + push %rdi // preserve multiboot struct + call sysenter_setup + pop %rdi + + // multiboot struct in %rdi + jmp kmain_early + + +.section .shared +/* https://wiki.osdev.org/Task_State_Segment#Long_Mode */ +.global TSS +.align 8 +TSS: + .4byte 0 /* reserved */ + .rept 3 + .8byte _isr_mini_stack /* stacks for privilege level changes */ + .endr + .8byte 0 /* reserved */ + .rept 7 + .8byte _isr_mini_stack /* IST - stack pointer loaded for interrupts */ + .endr + .8byte 0 /* reserved */ + .4byte 0 /* reserved + IOPB (unused) */ + .if . - TSS != 104 + .error "bad tss size" + .abort + .endif diff --git a/src/kernel/arch/amd64/boot64.s b/src/kernel/arch/amd64/boot64.s deleted file mode 100644 index 9bd9f0c..0000000 --- a/src/kernel/arch/amd64/boot64.s +++ /dev/null @@ -1,12 +0,0 @@ -.global boot64 -boot64: - lgdt (lgdt_arg) // try reloading gdt again - mov $(6 << 3 | 3), %ax // SEG_TSS - ltr %ax - - push %rdi // preserve multiboot struct - call sysenter_setup - pop %rdi - - // multiboot struct in %rdi - jmp kmain_early diff --git a/src/kernel/arch/amd64/interrupts/idt.c b/src/kernel/arch/amd64/interrupts/idt.c index 8f37cb9..2a5e8f7 100644 --- a/src/kernel/arch/amd64/interrupts/idt.c +++ b/src/kernel/arch/amd64/interrupts/idt.c @@ -17,8 +17,6 @@ struct idt_entry { uint32_t zero2; } __attribute__((packed)); -// is exactly the same as lgdt_arg, i should combine them into a single struct -// later struct lidt_arg { uint16_t limit; uintptr_t base; -- cgit v1.2.3