From 010569fb21e4decfef0852de51235cfdccb17f0b Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 7 Feb 2022 10:30:29 -0700 Subject: [PATCH] Add some long mode support --- Makefile | 25 +++-- asm/x86-unknown-none/bootloader.asm | 6 +- asm/x86-unknown-none/descriptor_flags.inc | 46 -------- asm/x86-unknown-none/gdt.asm | 128 ++++++++++++++++++++++ asm/x86-unknown-none/gdt_entry.inc | 8 -- asm/x86-unknown-none/long_mode.asm | 56 ++++++++++ asm/x86-unknown-none/memory_map.asm | 1 + asm/x86-unknown-none/print.asm | 1 + asm/x86-unknown-none/protected_mode.asm | 36 ++++++ asm/x86-unknown-none/stage2.asm | 77 +------------ asm/x86-unknown-none/thunk.asm | 3 + asm/x86-unknown-none/unreal.asm | 23 +--- src/lib.rs | 8 +- src/paging.rs | 78 +++++++++++++ src/panic.rs | 4 +- 15 files changed, 338 insertions(+), 162 deletions(-) delete mode 100644 asm/x86-unknown-none/descriptor_flags.inc create mode 100644 asm/x86-unknown-none/gdt.asm delete mode 100644 asm/x86-unknown-none/gdt_entry.inc create mode 100644 asm/x86-unknown-none/long_mode.asm create mode 100644 asm/x86-unknown-none/protected_mode.asm create mode 100644 src/paging.rs diff --git a/Makefile b/Makefile index 98f4a34777..2323fe944b 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,23 @@ TARGET?=x86-unknown-none - -ifeq ($(TARGET),x86-unknown-none) - export LD=ld -m elf_i386 - export OBJCOPY=objcopy - export PARTED=parted - export QEMU=qemu-system-x86_64 -else - $(error target $(TARGET) not supported by bootloader yet) -endif - BUILD=build/$(TARGET) export RUST_TARGET_PATH=$(CURDIR)/targets +ifeq ($(TARGET),x86-unknown-none) + +export LD=ld -m elf_i386 +export OBJCOPY=objcopy +export PARTED=parted +export QEMU=qemu-system-x86_64 + all: $(BUILD)/bootloader.bin +else + +all: + $(error target $(TARGET) not supported by bootloader yet) + +endif + clean: rm -rf build @@ -61,6 +65,7 @@ qemu: $(BUILD)/harddrive.bin $(QEMU) \ -d cpu_reset \ -d guest_errors \ + -no-reboot \ -smp 4 -m 2048 \ -chardev stdio,id=debug,signal=off,mux=on \ -serial chardev:debug \ diff --git a/asm/x86-unknown-none/bootloader.asm b/asm/x86-unknown-none/bootloader.asm index 2434024965..95ec773de5 100644 --- a/asm/x86-unknown-none/bootloader.asm +++ b/asm/x86-unknown-none/bootloader.asm @@ -8,7 +8,7 @@ stage2: stage2.end: stage3: - %defstr STAGE3_STR %[STAGE3] - incbin STAGE3_STR -align 512, db 0 + %defstr STAGE3_STR %[STAGE3] + incbin STAGE3_STR + align 512, db 0 .end: diff --git a/asm/x86-unknown-none/descriptor_flags.inc b/asm/x86-unknown-none/descriptor_flags.inc deleted file mode 100644 index 210c98d5ef..0000000000 --- a/asm/x86-unknown-none/descriptor_flags.inc +++ /dev/null @@ -1,46 +0,0 @@ -attrib: - .present equ 1 << 7 - .ring1 equ 1 << 5 - .ring2 equ 1 << 6 - .ring3 equ 1 << 5 | 1 << 6 - .user equ 1 << 4 -;user - .code equ 1 << 3 -; code - .conforming equ 1 << 2 - .readable equ 1 << 1 -; data - .expand_down equ 1 << 2 - .writable equ 1 << 1 - .accessed equ 1 << 0 -;system -; legacy - .tssAvailabe16 equ 0x1 - .ldt equ 0x2 - .tssBusy16 equ 0x3 - .call16 equ 0x4 - .task equ 0x5 - .interrupt16 equ 0x6 - .trap16 equ 0x7 - .tssAvailabe32 equ 0x9 - .tssBusy32 equ 0xB - .call32 equ 0xC - .interrupt32 equ 0xE - .trap32 equ 0xF -; long mode - .ldt32 equ 0x2 - .tssAvailabe64 equ 0x9 - .tssBusy64 equ 0xB - .call64 equ 0xC - .interrupt64 equ 0xE - .trap64 equ 0xF - -flags: - .granularity equ 1 << 7 - .available equ 1 << 4 -;user - .default_operand_size equ 1 << 6 -; code - .long_mode equ 1 << 5 -; data - .reserved equ 1 << 5 diff --git a/asm/x86-unknown-none/gdt.asm b/asm/x86-unknown-none/gdt.asm new file mode 100644 index 0000000000..773139a79a --- /dev/null +++ b/asm/x86-unknown-none/gdt.asm @@ -0,0 +1,128 @@ +SECTION .text ; cannot use .data + +struc GDTEntry + .limitl resw 1 + .basel resw 1 + .basem resb 1 + .attribute resb 1 + .flags__limith resb 1 + .baseh resb 1 +endstruc + +gdt_attr: + .present equ 1 << 7 + .ring1 equ 1 << 5 + .ring2 equ 1 << 6 + .ring3 equ 1 << 5 | 1 << 6 + .user equ 1 << 4 +;user + .code equ 1 << 3 +; code + .conforming equ 1 << 2 + .readable equ 1 << 1 +; data + .expand_down equ 1 << 2 + .writable equ 1 << 1 + .accessed equ 1 << 0 +;system +; legacy + .tssAvailabe16 equ 0x1 + .ldt equ 0x2 + .tssBusy16 equ 0x3 + .call16 equ 0x4 + .task equ 0x5 + .interrupt16 equ 0x6 + .trap16 equ 0x7 + .tssAvailabe32 equ 0x9 + .tssBusy32 equ 0xB + .call32 equ 0xC + .interrupt32 equ 0xE + .trap32 equ 0xF +; long mode + .ldt32 equ 0x2 + .tssAvailabe64 equ 0x9 + .tssBusy64 equ 0xB + .call64 equ 0xC + .interrupt64 equ 0xE + .trap64 equ 0xF + +gdt_flag: + .granularity equ 1 << 7 + .available equ 1 << 4 +;user + .default_operand_size equ 1 << 6 +; code + .long_mode equ 1 << 5 +; data + .reserved equ 1 << 5 + +gdtr: + dw gdt.end + 1 ; size + dd gdt ; offset + +gdt: +.null equ $ - gdt + dq 0 + +.lm64_code equ $ - gdt +istruc GDTEntry + at GDTEntry.limitl, dw 0 + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code + at GDTEntry.flags__limith, db gdt_flag.long_mode + at GDTEntry.baseh, db 0 +iend + +.lm64_data equ $ - gdt +istruc GDTEntry + at GDTEntry.limitl, dw 0 + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 +; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable + at GDTEntry.flags__limith, db 0 + at GDTEntry.baseh, db 0 +iend + +.pm32_code equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable + at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size + at GDTEntry.baseh, db 0 + iend + +.pm32_data equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable + at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size + at GDTEntry.baseh, db 0 + iend + +.pm16_code equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable + at GDTEntry.flags__limith, db 0xF + at GDTEntry.baseh, db 0 + iend + +.pm16_data equ $ - gdt + istruc GDTEntry + at GDTEntry.limitl, dw 0xFFFF + at GDTEntry.basel, dw 0 + at GDTEntry.basem, db 0 + at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable + at GDTEntry.flags__limith, db 0xF + at GDTEntry.baseh, db 0 + iend + +.end equ $ - gdt diff --git a/asm/x86-unknown-none/gdt_entry.inc b/asm/x86-unknown-none/gdt_entry.inc deleted file mode 100644 index 861a78be89..0000000000 --- a/asm/x86-unknown-none/gdt_entry.inc +++ /dev/null @@ -1,8 +0,0 @@ -struc GDTEntry - .limitl resw 1 - .basel resw 1 - .basem resb 1 - .attribute resb 1 - .flags__limith resb 1 - .baseh resb 1 -endstruc diff --git a/asm/x86-unknown-none/long_mode.asm b/asm/x86-unknown-none/long_mode.asm new file mode 100644 index 0000000000..de5f802038 --- /dev/null +++ b/asm/x86-unknown-none/long_mode.asm @@ -0,0 +1,56 @@ +SECTION .text +USE32 + +long_mode: +.func: dq 0 +.page_table: dd 0 + +.entry: + ; disable interrupts + cli + + ;disable paging + mov eax, cr0 + and eax, 0x7FFFFFFF + mov cr0, eax + + ;cr3 holds pointer to PML4 + mov eax, [.page_table] + mov cr3, eax + + ;enable OSXSAVE, FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension + mov eax, cr4 + or eax, 1 << 18 | 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4 + mov cr4, eax + + ; load long mode GDT + lgdt [gdtr] + + ; enable long mode + mov ecx, 0xC0000080 ; Read from the EFER MSR. + rdmsr + or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit. + wrmsr + + ; enabling paging and protection simultaneously + mov eax, cr0 + or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode + mov cr0, eax + + ; far jump to enable Long Mode and load CS with 64 bit segment + jmp gdt.lm64_code:.inner + +USE64 + +.inner: + ; load all the other segments with 64 bit data segments + mov rax, gdt.lm64_data + mov ds, rax + mov es, rax + mov fs, rax + mov gs, rax + mov ss, rax + + ; jump to specified function + mov rax, [.func] + jmp rax diff --git a/asm/x86-unknown-none/memory_map.asm b/asm/x86-unknown-none/memory_map.asm index a5cdc70c5f..460ea27b74 100644 --- a/asm/x86-unknown-none/memory_map.asm +++ b/asm/x86-unknown-none/memory_map.asm @@ -1,5 +1,6 @@ SECTION .text USE16 + ;Generate a memory map at 0x500 to 0x5000 (available memory not used for kernel or bootloader) memory_map: .start equ 0x0500 diff --git a/asm/x86-unknown-none/print.asm b/asm/x86-unknown-none/print.asm index a2340d9133..5e958a3c34 100644 --- a/asm/x86-unknown-none/print.asm +++ b/asm/x86-unknown-none/print.asm @@ -1,5 +1,6 @@ SECTION .text USE16 + ; provide function for printing in x86 real mode ; print a string and a newline diff --git a/asm/x86-unknown-none/protected_mode.asm b/asm/x86-unknown-none/protected_mode.asm new file mode 100644 index 0000000000..8edc6219af --- /dev/null +++ b/asm/x86-unknown-none/protected_mode.asm @@ -0,0 +1,36 @@ +SECTION .text +USE16 + +protected_mode: + +.func: dd 0 + +.entry: + ; disable interrupts + cli + + ; load protected mode GDT + lgdt [gdtr] + + ; set protected mode bit of cr0 + mov eax, cr0 + or eax, 1 + mov cr0, eax + + ; far jump to load CS with 32 bit segment + jmp gdt.pm32_code:.inner + +USE32 + +.inner: + ; load all the other segments with 32 bit data segments + mov eax, gdt.pm32_data + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + ; jump to specified function + mov eax, [.func] + jmp eax diff --git a/asm/x86-unknown-none/stage2.asm b/asm/x86-unknown-none/stage2.asm index 769d358011..f04b11c696 100644 --- a/asm/x86-unknown-none/stage2.asm +++ b/asm/x86-unknown-none/stage2.asm @@ -20,16 +20,8 @@ stage2.entry: shr ecx, 9 call load_extent - ; load protected mode GDT and IDT - cli - lgdt [gdtr] - ; set protected mode bit of cr0 - mov eax, cr0 - or eax, 1 - mov cr0, eax - - ; far jump to load CS with 32 bit segment - jmp gdt.pm32_code:protected_mode + mov dword [protected_mode.func], stage3.entry + jmp protected_mode.entry args: .stage3_base dq 0x100000 @@ -108,22 +100,15 @@ load_extent: call print_line ret -%include "descriptor_flags.inc" -%include "gdt_entry.inc" +%include "gdt.asm" %include "memory_map.asm" %include "thunk.asm" %include "unreal.asm" +%include "protected_mode.asm" +%include "long_mode.asm" USE32 -protected_mode: - ; load all the other segments with 32 bit data segments - mov eax, gdt.pm32_data - mov ds, eax - mov es, eax - mov fs, eax - mov gs, eax - mov ss, eax - +stage3.entry: mov esp, 0x800000 - 128 ; entry point @@ -144,53 +129,3 @@ protected_mode: cli hlt jmp .halt - -gdtr: - dw gdt.end + 1 ; size - dd gdt ; offset - -gdt: -.null equ $ - gdt - dq 0 - -.pm32_code equ $ - gdt - istruc GDTEntry - at GDTEntry.limitl, dw 0xFFFF - at GDTEntry.basel, dw 0 - at GDTEntry.basem, db 0 - at GDTEntry.attribute, db attrib.present | attrib.user | attrib.code | attrib.readable - at GDTEntry.flags__limith, db 0xF | flags.granularity | flags.default_operand_size - at GDTEntry.baseh, db 0 - iend - -.pm32_data equ $ - gdt - istruc GDTEntry - at GDTEntry.limitl, dw 0xFFFF - at GDTEntry.basel, dw 0 - at GDTEntry.basem, db 0 - at GDTEntry.attribute, db attrib.present | attrib.user | attrib.writable - at GDTEntry.flags__limith, db 0xF | flags.granularity | flags.default_operand_size - at GDTEntry.baseh, db 0 - iend - -.pm16_code equ $ - gdt - istruc GDTEntry - at GDTEntry.limitl, dw 0xFFFF - at GDTEntry.basel, dw 0 - at GDTEntry.basem, db 0 - at GDTEntry.attribute, db attrib.present | attrib.user | attrib.code | attrib.readable - at GDTEntry.flags__limith, db 0xF - at GDTEntry.baseh, db 0 - iend - -.pm16_data equ $ - gdt - istruc GDTEntry - at GDTEntry.limitl, dw 0xFFFF - at GDTEntry.basel, dw 0 - at GDTEntry.basem, db 0 - at GDTEntry.attribute, db attrib.present | attrib.user | attrib.writable - at GDTEntry.flags__limith, db 0xF - at GDTEntry.baseh, db 0 - iend - -.end equ $ - gdt diff --git a/asm/x86-unknown-none/thunk.asm b/asm/x86-unknown-none/thunk.asm index cb7662e713..0e47f9dba2 100644 --- a/asm/x86-unknown-none/thunk.asm +++ b/asm/x86-unknown-none/thunk.asm @@ -1,4 +1,6 @@ +SECTION .text USE32 + thunk: .int10: mov dword [.func], .int10_real @@ -55,6 +57,7 @@ thunk: ret USE16 + .int10_real: int 0x10 ret diff --git a/asm/x86-unknown-none/unreal.asm b/asm/x86-unknown-none/unreal.asm index a1059ea9a3..b26397102e 100644 --- a/asm/x86-unknown-none/unreal.asm +++ b/asm/x86-unknown-none/unreal.asm @@ -5,7 +5,7 @@ USE16 unreal: cli - lgdt [unreal_gdtr] + lgdt [gdtr] push es push ds @@ -22,7 +22,7 @@ unreal: ; the switch back to real mode, these values are not modified, regardless of ; what value is in the 16-bit segment register. So the 64k limit is no longer ; valid and 32-bit offsets can be used with the real-mode addressing rules - mov bx, unreal_gdt.data + mov bx, gdt.pm32_data mov es, bx mov ds, bx @@ -33,22 +33,3 @@ unreal: pop es sti ret - - -unreal_gdtr: - dw unreal_gdt.end + 1 ; size - dd unreal_gdt ; offset - -unreal_gdt: -.null equ $ - unreal_gdt - dq 0 -.data equ $ - unreal_gdt - istruc GDTEntry - at GDTEntry.limitl, dw 0xFFFF - at GDTEntry.basel, dw 0x0 - at GDTEntry.basem, db 0x0 - at GDTEntry.attribute, db attrib.present | attrib.user | attrib.writable - at GDTEntry.flags__limith, db 0xF | flags.granularity | flags.default_operand_size - at GDTEntry.baseh, db 0x0 - iend -.end equ $ - unreal_gdt diff --git a/src/lib.rs b/src/lib.rs index 7bf287f451..ad715ac46d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ use self::vga::{VgaTextBlock, VgaTextColor, Vga}; mod disk; mod logger; +mod paging; mod panic; mod thunk; mod vbe; @@ -43,6 +44,8 @@ static ALLOCATOR: LockedHeap = LockedHeap::empty(); static mut VGA: Vga = unsafe { Vga::new(VGA_ADDR as *mut VgaTextBlock, 80, 25) }; +static mut KERNEL_PHYS: u64 = 0; + #[no_mangle] pub unsafe extern "C" fn kstart( boot_disk: usize, @@ -77,7 +80,8 @@ pub unsafe extern "C" fn kstart( } } - // Initialize allocator at the end of stage 3 with a meager 1 MiB + // Initialize allocator at the end of stage 3 with a meager 1 MiB, which we can be pretty + // sure will be available. extern "C" { static mut __end: u8; } @@ -119,6 +123,8 @@ pub unsafe extern "C" fn kstart( } } + //let page_phys = paging::paging_create(KERNEL_PHYS); + let mut modes = Vec::new(); { // Get card info diff --git a/src/paging.rs b/src/paging.rs new file mode 100644 index 0000000000..2f1e9d77e5 --- /dev/null +++ b/src/paging.rs @@ -0,0 +1,78 @@ +use core::alloc::{GlobalAlloc, Layout}; +use core::slice; + +unsafe fn paging_allocate() -> Option<&'static mut [u64]> { + let ptr = crate::ALLOCATOR.alloc_zeroed( + Layout::from_size_align(4096, 4096).unwrap() + ); + if ! ptr.is_null() { + Some(slice::from_raw_parts_mut( + ptr as *mut u64, + 512 // page size divided by u64 size + )) + } else { + None + } +} + +pub unsafe fn paging_create(kernel_phys: u64) -> Option { + // Create PML4 + let pml4 = paging_allocate()?; + + // Recursive mapping for compatibility + pml4[511] = pml4.as_ptr() as u64 | 1 << 1 | 1; + + { + // Create PDP for identity mapping + let pdp = paging_allocate()?; + + // Link first user and first kernel PML4 entry to PDP + pml4[0] = pdp.as_ptr() as u64 | 1 << 1 | 1; + pml4[256] = pdp.as_ptr() as u64 | 1 << 1 | 1; + + // Identity map 8 GiB pages + for pdp_i in 0..8 { + let pd = paging_allocate()?; + pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1; + for pd_i in 0..pd.len() { + let pt = paging_allocate()?; + pd[pd_i] = pt.as_ptr() as u64 | 1 << 1 | 1; + for pt_i in 0..pt.len() { + let addr = + pdp_i as u64 * 0x4000_0000 + + pd_i as u64 * 0x20_0000 + + pt_i as u64 * 0x1000; + pt[pt_i] = addr | 1 << 1 | 1; + } + } + } + } + + { + // Create PDP for kernel mapping + let pdp = paging_allocate()?; + + // Link second to last PML4 entry to PDP + pml4[510] = pdp.as_ptr() as u64 | 1 << 1 | 1; + + // Map 1 GiB at kernel offset + for pdp_i in 0..1 { + let pd = paging_allocate()?; + pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1; + for pd_i in 0..pd.len() { + let pt = paging_allocate()?; + pd[pd_i] = pt.as_ptr() as u64 | 1 << 1 | 1; + for pt_i in 0..pt.len() { + let addr = + pdp_i as u64 * 0x4000_0000 + + pd_i as u64 * 0x20_0000 + + pt_i as u64 * 0x1000 + + kernel_phys; + pt[pt_i] = addr | 1 << 1 | 1; + } + } + } + } + + Some(pml4.as_ptr() as u64) +} diff --git a/src/panic.rs b/src/panic.rs index d2c3259b48..9d817bfe15 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -11,7 +11,7 @@ pub extern "C" fn rust_eh_personality() {} #[panic_handler] #[no_mangle] pub extern "C" fn rust_begin_unwind(info: &PanicInfo) -> ! { - //println!("BOOTLOADER PANIC: {}", info); + log::error!("BOOTLOADER PANIC: {}", info); loop { unsafe { llvm_asm!("hlt" : : : : "intel", "volatile"); @@ -23,7 +23,7 @@ pub extern "C" fn rust_begin_unwind(info: &PanicInfo) -> ! { #[no_mangle] #[allow(improper_ctypes_definitions)] // Layout is not repr(C) pub extern fn rust_oom(_layout: Layout) -> ! { - panic!("kernel memory allocation failed"); + panic!("memory allocation failed"); } #[allow(non_snake_case)]