From 77bcdc27266eae18f0eb224f20bacc2d845a8c92 Mon Sep 17 00:00:00 2001 From: Alex Maltsev Date: Sun, 10 Sep 2023 05:11:03 +0300 Subject: [PATCH] Initial version of x86_64 long mode --- Makefile | 9 ++- run-qemu-detached.sh | 2 +- source/bootstrap.asm | 176 ++++++++++++++++++++++++++++++++++++++----- source/gdt.c | 32 ++++++-- source/idt.c | 16 ++-- source/types.h | 3 + source/vga_print.c | 2 +- 7 files changed, 202 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 934020f..87f7e43 100644 --- a/Makefile +++ b/Makefile @@ -15,14 +15,15 @@ ISO_FILE = $(BUILD_FOLDER)/sos.iso CROSS_COMPILE = x86_64-elf- ASM = nasm CC = gcc -CC_FLAGS = -c -g -O3 -m32 -mgeneral-regs-only -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs \ +CC_FLAGS = -c -g -O3 -m64 -mgeneral-regs-only -nostdlib -nostdinc -fno-builtin -fno-stack-protector -mno-red-zone -nostartfiles -nodefaultlibs \ -Wall -Wextra -Werror +QEMU_FLAGS = -D ./log.txt -d int,cpu_reset -no-reboot LINKER = ld all: iso run: iso - qemu-system-x86_64 -cdrom $(ISO_FILE) + qemu-system-x86_64 -cdrom $(ISO_FILE) $(QEMU_FLAGS) iso: $(ISO_FILE) @@ -41,11 +42,11 @@ $(ISO_GRUB_CFG): grub.cfg cp $< $@ $(KERNEL_ELF): $(BOOTSTRAP_ELF) $(OBJ_FILES) - $(CROSS_COMPILE)$(LINKER) -melf_i386 -Tlinker.ld $(BOOTSTRAP_ELF) $(OBJ_FILES) -o $(KERNEL_ELF) + $(CROSS_COMPILE)$(LINKER) -melf_x86_64 -Tlinker.ld $(BOOTSTRAP_ELF) $(OBJ_FILES) -o $(KERNEL_ELF) $(BOOTSTRAP_ELF): source/bootstrap.asm mkdir -p $(@D) - $(ASM) -f elf32 -o $@ $< + $(ASM) -f elf64 -o $@ $< $(BUILD_FOLDER)/%.o: source/%.c $(H_FILES) mkdir -p $(@D) diff --git a/run-qemu-detached.sh b/run-qemu-detached.sh index ac9d904..6f849cb 100755 --- a/run-qemu-detached.sh +++ b/run-qemu-detached.sh @@ -2,5 +2,5 @@ make iso -qemu-system-x86_64 -s -S -cdrom build_output/sos.iso & +qemu-system-x86_64 -D ./log.txt -d int,cpu_reset -s -S -cdrom build_output/sos.iso & ./kill-qemu-after-gdb.sh & \ No newline at end of file diff --git a/source/bootstrap.asm b/source/bootstrap.asm index e6ca7c8..9ad13a2 100644 --- a/source/bootstrap.asm +++ b/source/bootstrap.asm @@ -25,37 +25,179 @@ dd 8 ; Multiboot2 header end -extern kernel_main - start: mov esp, kernel_stack + KERNEL_STACK_SIZE - jmp kernel_main - jmp $ + ; call check_multiboot + call check_cpuid + call check_long_mode + + call set_up_page_tables + call enable_paging + + lgdt [gdt64.pointer] + jmp gdt64.code:long_mode_start + + +check_cpuid: + ; Check if CPUID is supported by attempting to flip the ID bit (bit 21) + ; in the FLAGS register. If we can flip it, CPUID is available. + + ; Copy FLAGS in to EAX via stack + pushfd + pop eax + + ; Copy to ECX as well for comparing later on + mov ecx, eax + + ; Flip the ID bit + xor eax, 1 << 21 + + ; Copy EAX to FLAGS via the stack + push eax + popfd + + ; Copy FLAGS back to EAX (with the flipped bit if CPUID is supported) + pushfd + pop eax + + ; Restore FLAGS from the old version stored in ECX (i.e. flipping the + ; ID bit back if it was ever flipped). + push ecx + popfd + + ; Compare EAX and ECX. If they are equal then that means the bit + ; wasn't flipped, and CPUID isn't supported. + cmp eax, ecx + je .no_cpuid + ret +.no_cpuid: + mov al, "1" + jmp error + + +check_multiboot: + cmp eax, 0x36d76289 + jne .no_multiboot + ret +.no_multiboot: + mov al, "0" + jmp error + + +check_long_mode: + ; test if extended processor info in available + mov eax, 0x80000000 ; implicit argument for cpuid + cpuid ; get highest supported argument + cmp eax, 0x80000001 ; it needs to be at least 0x80000001 + jb .no_long_mode ; if it's less, the CPU is too old for long mode + + ; use extended info to test if long mode is available + mov eax, 0x80000001 ; argument for extended processor info + cpuid ; returns various feature bits in ecx and edx + test edx, 1 << 29 ; test if the LM-bit is set in the D-register + jz .no_long_mode ; If it's not set, there is no long mode + ret +.no_long_mode: + mov al, "2" + jmp error + +set_up_page_tables: + ; map first P4 entry to P3 table + mov eax, p3_table + or eax, 0b11 ; present + writable + mov [p4_table], eax -global load_gdt -load_gdt: - mov eax, dword [esp + 4] - lgdt [eax] + ; map first P3 entry to P2 table + mov eax, p2_table + or eax, 0b11 ; present + writable + mov [p3_table], eax - jmp 08h:temp -temp: + mov ecx, 0 ; counter variable - mov eax, 0x10 - mov ds, eax - mov ss, eax +.map_p2_table: + ; map ecx-th P2 entry to a huge page that starts at address 2MiB*ecx + mov eax, 0x200000 ; 2MiB + mul ecx ; start address of ecx-th page + or eax, 0b10000011 ; present + writable + huge + mov [p2_table + ecx * 8], eax ; map ecx-th entry - mov eax, 0 - mov es, eax - mov fs, eax - mov gs, eax + inc ecx ; increase counter + cmp ecx, 512 ; if counter == 512, the whole P2 table is mapped + jne .map_p2_table ; else map the next entry ret + +enable_paging: + ; load P4 to cr3 register (cpu uses this to access the P4 table) + mov eax, p4_table + mov cr3, eax + + ; enable PAE-flag in cr4 (Physical Address Extension) + mov eax, cr4 + or eax, 1 << 5 + mov cr4, eax + + ; set the long mode bit in the EFER MSR (model specific register) + mov ecx, 0xC0000080 + rdmsr + or eax, 1 << 8 + wrmsr + + ; enable paging in the cr0 register + mov eax, cr0 + or eax, 1 << 31 + mov cr0, eax + + ret + +error: + mov dword [0xb8000], 0x4f524f45 + mov dword [0xb8004], 0x4f3a4f52 + mov dword [0xb8008], 0x4f204f20 + mov byte [0xb800a], al + hlt + section .bss KERNEL_STACK_SIZE equ 8192 +; temporary page tables mappings to get to long mode and resetup it later properly +align 4096 +p4_table: + resb 4096 +p3_table: + resb 4096 +p2_table: + resb 4096 + align 4 kernel_stack: resb KERNEL_STACK_SIZE + +section .rodata + +; temporary gdt to get to long mode and resetup it later properly +gdt64: + dq 0 ; zero entry +.code: equ $ - gdt64 ; new + dq (1<<43) | (1<<44) | (1<<47) | (1<<53) ; code segment +.pointer: + dw $ - gdt64 - 1 + dq gdt64 + + +bits 64 +section .text + +extern kernel_main + +long_mode_start: + ; print `OKAY` to screen + mov rax, 0x2f592f412f4b2f4f + mov qword [0xb8000], rax + jmp kernel_main + jmp $ + + diff --git a/source/gdt.c b/source/gdt.c index a94458a..ff2eabf 100644 --- a/source/gdt.c +++ b/source/gdt.c @@ -136,18 +136,34 @@ segment_descriptor gen_task_state_segment_descriptor( return result; } -extern void load_gdt(const gdt_descriptor* gdt); - void init_gdt(void) { gdt_data[0] = gen_null_segment_decriptor(); + + // in long mode default operation size flag should be zero, base and limits + // are ignored gdt_data[1] = - gen_code_segment_descriptor(0, 0xFFFFF, 1, 1, 0, 1, 1, 0, 0, 0, 0); + gen_code_segment_descriptor(0, 0xFFFFF, 1, 0, 1, 1, 1, 0, 0, 0, 0); gdt_data[2] = - gen_data_segment_descriptor(0, 0xFFFFF, 1, 1, 0, 1, 1, 0, 0, 1, 0); + gen_data_segment_descriptor(0, 0xFFFFF, 1, 0, 1, 1, 1, 0, 0, 1, 0); gdt_data[3] = - gen_code_segment_descriptor(0, 0xFFFFF, 1, 1, 0, 0, 1, 3, 0, 0, 0); + gen_code_segment_descriptor(0, 0xFFFFF, 1, 0, 1, 0, 1, 3, 0, 0, 0); gdt_data[4] = - gen_data_segment_descriptor(0, 0xFFFFF, 1, 1, 0, 0, 1, 3, 0, 1, 0); - //gdt_data[5] = gen_task_state_segment_descriptor(0, 0xFFFFF, 1, 1, 1, 0, 0); - load_gdt(&gdt); + gen_data_segment_descriptor(0, 0xFFFFF, 1, 0, 1, 0, 1, 3, 0, 1, 0); + // gdt_data[5] = gen_task_state_segment_descriptor(0, 0xFFFFF, 1, 1, 1, 0, + // 0); + + __asm__ volatile(" lgdt (%%rax)\n" + " pushq $0x8\n" + " pushq $tmp%=\n" + " retfq\n" // far ret to force cs register reloading + "tmp%=:\n" + " mov $0x10, %%rax\n" + " mov %%rax, %%ds\n" + " mov %%rax, %%ss\n" + " mov $0, %%rax\n" + " mov %%rax, %%es\n" + " mov %%rax, %%fs\n" + " mov %%rax, %%gs" + : + : "a"(&gdt)); } diff --git a/source/idt.c b/source/idt.c index ed004a8..8d0d90e 100644 --- a/source/idt.c +++ b/source/idt.c @@ -5,9 +5,11 @@ typedef struct __attribute__((__aligned__(8), __packed__)) { u16 offset_0_15; u16 segment_selector; - u8 reserved; + u8 ist; u8 flags; u16 offset_16_31; + u32 offset_32_63; + u32 zero; } interrupt_descriptor; typedef struct __attribute__((__packed__)) { @@ -23,8 +25,6 @@ const int INTERRUPT_TYPE_OFFSET = 0; interrupt_descriptor idt_data[48]; const idt_descriptor idt = {.data = idt_data, .limit = sizeof(idt_data) - 1}; -extern void load_idt(const idt_descriptor* idt); - u8 gen_interrupt_descriptor_flags(bit present, u8 dpl, bit size, bit gate_type) { @@ -33,16 +33,18 @@ u8 gen_interrupt_descriptor_flags(bit present, u8 dpl, bit size, | ((gate_type & 1) << INTERRUPT_TYPE_OFFSET) | 0b110; } -interrupt_descriptor gen_interrupt_descriptor(u16 segment_selector, u32 offset, +interrupt_descriptor gen_interrupt_descriptor(u16 segment_selector, u64 offset, bit present, u8 dpl, bit size, bit gate_type) { interrupt_descriptor result = { .offset_0_15 = offset & 0xFFFF, .segment_selector = segment_selector, - .reserved = 0, + .ist = 0, .flags = gen_interrupt_descriptor_flags(present, dpl, size, gate_type), - .offset_16_31 = (offset >> 16) & 0xFFFF}; + .offset_16_31 = (offset >> 16) & 0xFFFF, + .offset_32_63 = (offset >> 32) & 0xFFFFFFFF, + .zero = 0}; return result; } @@ -129,7 +131,7 @@ void init_pic(void) { void init_idt(void) { for (int i = 0; i < 48; i++) { idt_data[i] = gen_interrupt_descriptor( - 0x08, (u32) interrupt_handlers[i], 1, 0, 1, 0); + 0x08, (u64) interrupt_handlers[i], 1, 0, 1, 0); } __asm__ volatile("lidt %0" : : "m"(idt)); diff --git a/source/types.h b/source/types.h index e52fdff..2a9c3fc 100644 --- a/source/types.h +++ b/source/types.h @@ -10,6 +10,9 @@ #define i32 signed int #define u32 unsigned int +#define i64 long +#define u64 unsigned long + #define bit u8 #define bool u8 #define true 1 diff --git a/source/vga_print.c b/source/vga_print.c index 4ea8bc0..2326d03 100644 --- a/source/vga_print.c +++ b/source/vga_print.c @@ -3,7 +3,7 @@ const u16 COLUMN_WIDTH = 80; const u16 ROW_NUMBER = 25; -u16* const TEXT_BUFFER = (u16*) 0x000B8000; +volatile u16* const TEXT_BUFFER = (u16*) 0x000B8000; u16 cur_row = ROW_NUMBER - 1; u16 cur_col = 0;