From a86d73e6eea028baca2827aba68bdc650b2a2ab0 Mon Sep 17 00:00:00 2001 From: pantonshire Date: Mon, 26 Aug 2024 20:05:24 +0100 Subject: [PATCH] enter protected mode and jump to rust binary --- boot1/asm/prelude.s | 327 +++++++++++++++++++++++++++++++++++++++++++- boot1/link.ld | 9 ++ boot1/src/main.rs | 11 +- justfile | 9 +- 4 files changed, 343 insertions(+), 13 deletions(-) diff --git a/boot1/asm/prelude.s b/boot1/asm/prelude.s index 928da43..7200f9a 100644 --- a/boot1/asm/prelude.s +++ b/boot1/asm/prelude.s @@ -2,11 +2,330 @@ [bits 16] +extern boot1_bin_len +extern boot1_bin_sectors +extern boot1_magic +extern _start + section .prelude -extern boot1_bin_len +%macro copy_stack_var_to_globals 2 + mov %1, [bp - %2] + mov [GLOBALS + %2], %1 +%endmacro + +; boot0 loads only our first sector into memory. We must load the rest. +self_load: + ; Now that we're not doing instruction byte golf like we were in boot0, we can afford to move + ; the various boot0 stack variables to the globals section. + copy_stack_var_to_globals ax, BOOT_DRIVE + copy_stack_var_to_globals ax, SECTORS_PER_TRACK + copy_stack_var_to_globals ax, N_HEADS + copy_stack_var_to_globals ax, GPT_ENTRIES_START_LBA + copy_stack_var_to_globals ax, GPT_N_ENTRIES_16 + copy_stack_var_to_globals ax, GPT_SECTOR_STRIDE + copy_stack_var_to_globals ax, GPT_BYTE_STRIDE + copy_stack_var_to_globals ax, GPT_ENTRIES_PER_SECTOR + copy_stack_var_to_globals ax, GPT_CURRENT_ENTRY_IDX + copy_stack_var_to_globals ax, GPT_SECTOR_ENTRY_IDX + copy_stack_var_to_globals ax, GPT_SECTORS_LOADED + copy_stack_var_to_globals ax, GPT_CURRENT_LBA + copy_stack_var_to_globals ax, BOOT1_GPT_ENTRY_ADDR + + ; Reset the stack, now we've got everything we need from it. + mov sp, bp + + mov si, [GLOBALS + BOOT1_GPT_ENTRY_ADDR] + mov eax, [si + 0x20] ; Partition / boot1 start LBA lower + mov ebx, [si + 0x24] ; Partition / boot1 start LBA upper + mov ecx, [si + 0x28] ; Partition end LBA lower + mov edx, [si + 0x32] ; Partition LBA upper + + ; Panic if the partition / boot1 starting LBA overflows 16 bits. + or ebx, ebx + jnz panic_simple + ror eax, 16 + or ax, ax + jnz panic_simple + ror eax, 16 + + ; Calculate the boot1 end LBA and panic if it overflows 16 bits. + ; n.b. ebx is zero before this so both bx and ebx can be used as the boot1 end LBA. + mov bx, ax + add bx, boot1_bin_sectors + jc panic_simple + + ; Panic if the boot1 end LBA is after the partition end LBA. + ; If the upper 32 bits of the partition end LBA are nonzero, then it must be greater than our + ; 16-bit boot1 end LBA. + or edx, edx + jnz .end_lba_ok + ; Compare the boot1 end LBA to the lower 32 bits of the partition end LBA. + cmp ebx, ecx + ja panic_simple + +.end_lba_ok: + + ; The first sector has already been loaded (we're running it right now!) so increment the + ; current LBA. + inc ax + push ax ; Current LBA + push bx ; boot1 end LBA + mov ebx, BOOT1_LOADPOINT + 512 ; Current sector load address + +.self_load_loop: + mov ax, [bp - 0x02] ; Load current LBA + cmp word [bp - 0x04], ax ; Compare to boot1 end LBA + jb .self_load_done + + mov ecx, ebx + call read_sector + jc panic_simple + + add ebx, 512 + inc word [bp - 0x02] + jmp .self_load_loop + +.self_load_done: + + ; Check the magic bytes at the end of boot1. + push es + mov ebx, boot1_magic + call addr32_to_addr16 + cmp dword es:[bx], BOOT1_MAGIC + pop es + jne panic_simple + + jmp prelude_main + + +; Converts a 32-bit address to a 16-bit sector and offset. +; Arguments: +; - ebx: 32-bit address +; Return: +; - es: 16-bit address segment (unchanged on failure) +; - ebx: 16-bit address offset +; - cf: unset on success, set on failure +; Clobber: none +addr32_to_addr16: + fnstart + push es + push eax + + mov eax, ebx + ; Divide addr by 16 and saturate to 16 bits to get the segment. + shr eax, 4 + ror eax, 16 + or ax, ax + jz .segment_ok + mov eax, 0xffff0000 +.segment_ok: + ror eax, 16 + mov es, ax + + ; Calculate offset = addr - (16 * segment), failing if the offset doesn't fit in 16 bits. + shl eax, 4 + sub ebx, eax + ror ebx, 16 + or bx, bx + jnz .fail + ror ebx, 16 + + pop eax + add sp, 2 ; Discard the original es from the stack + pop bp + clc + ret + +.fail: + pop eax + pop es + stc + fnret + + +; Reads a single sector at the given LBA into memory. +; Arguments: +; - ax: start LBA +; - ecx: address to read sector to +; Return: +; - cf: unset on success, set on failure +; Clobber: eax, ecx, edx +read_sector: + ; sector - 1 = LBA % sectors_per_track + ; temp = LBA / sectors_per_track + ; head = temp % n_heads + ; cylinder = temp / n_heads + + fnstart + push es + push ebx + + mov ebx, ecx + call addr32_to_addr16 + jc .return + + ; Calculate sector and temp + xor dx, dx + ; Divide by sectors per track. dx = mod (sector - 1), ax = div (temp) + div word [GLOBALS + SECTORS_PER_TRACK] + ; Put the sector into cx (the bios call will use cl) + mov cx, dx + inc cx + + ; Calculate head and cylinder + xor dx, dx + ; Divide by number of heads. dx = mod (head), ax = div (cylinder) + div word [GLOBALS + N_HEADS] + mov dh, dl + mov ch, al + + mov dl, byte [GLOBALS + BOOT_DRIVE] + mov ah, 0x02 + mov al, 1 + ; Read sector + int 0x13 -global prelude -prelude: +.return: + pop ebx + pop es + fnret + + +panic_simple: + mov ax, 0x0003 + int 0x10 + mov word fs:[0x0000], 0x4f21 +.halt: hlt - dd boot1_bin_len + jmp .halt + + +%if ($ - $$) > 512 +%error "boot1 self-loader exceeded sector size" +%endif + + +; TODO: +; - Make sure A20 is enabled before the switch to protected mode + +prelude_main: + mov ax, 0x0003 + int 0x10 + + ; Ensure interrupts are definitely disabled. + cli + + ; Load our flat-address-space GDT. + lgdt [flat_gdt_slice] + + ; Set the protected-mode bit in cr0. + mov eax, cr0 + or al, 0x01 + mov cr0, eax + + ; Long jump to set the code segment to flat_gdt.segment_code, and to clear the instruction + ; pipeline. + jmp (flat_gdt.segment_code_32 - flat_gdt):.protected_mode + +[bits 32] +.protected_mode: + + ; Set the data segments to flat_gdt.segment_data. + mov eax, (flat_gdt.segment_data - flat_gdt) + mov ds, eax + mov es, eax + mov fs, eax + mov gs, eax + mov ss, eax + + ; Reset the stack. + mov ebp, STACK_BASE + mov esp, ebp + + jmp _start + jmp panic_simple + + +section .data +flat_gdt_slice: + dw FLAT_GDT_LEN + dd flat_gdt + +; Segment descriptor layout +; | Range (bits) | Field | +; |--------------|---------------| +; | 0-16 | limit | +; | 16-32 | base | +; | 32-40 | base cont. | +; | 40-48 | access | +; | 48-52 | limit cont. | +; | 52-56 | flags | +; | 56-64 | base cont. | +; +; Flags +; - 0: reserved +; - 1: long-mode code segment +; - 2: size +; - unset: 16-bit +; - set: 32-bit +; - 3: granularity +; - unset: limit is measured in bytes +; - set: limit is measured in 4KiB pages +; +; Access +; - 0: accessed +; - unset: CPU will set it when the segment is accessed +; - 1: readable / writable +; - data segments: is segment writable (data segments are always readable) +; - code segments: is segment readable (code segments are never writable) +; - 2: direction / conforming +; - data segments: whether segment grows down +; - code segments: whether this can be executed from a lower-privilege ring +; - 3: executable +; - unset: this is a data segment +; - set: this is a code segment +; - 4: descriptor type +; - unset: this is a task state segment +; - set: this is a data or code segment +; - 5-6: privilege level (ring number) +; - 7: present (must be set) +; +align 8 +flat_gdt: +; First GDT entry must be 0. +.segment_null: + dq 0 + +; 32-bit code segment. +; Bytes 0x0000 - 0xffff. +.segment_code_32: + db 0xff, 0xff, \ + 0x00, 0x00, \ + 0x00, \ + 10011011b, \ + 01000000b, \ + 0x00 + +; 16-bit code segment, to use if we want to switch back to real mode. +; Bytes 0x0000 - 0xffff. +.segment_code_16: + db 0xff, 0xff, \ + 0x00, 0x00, \ + 0x00, \ + 10011011b, \ + 00000000b, \ + 0x00 + +; Data segment. +; Pages 0x000000 - 0x0fffff, which covers the entire 32-bit address space (start of 0xfffff-th page +; is 0xfffff * 4096 = 0xfffff000, end of page exclusive is 0xfffff000 + 4096 = 0x100000000). +.segment_data: + db 0xff, 0xff, \ + 0x00, 0x00, \ + 0x00, \ + 10010011b, \ + 11001111b, \ + 0x00 + + FLAT_GDT_LEN equ ($ - flat_gdt) diff --git a/boot1/link.ld b/boot1/link.ld index 52c00d2..360a0f4 100644 --- a/boot1/link.ld +++ b/boot1/link.ld @@ -4,6 +4,7 @@ OUTPUT_FORMAT("binary") SECTIONS { .prelude : { + KEEP(*(.prelude)) *(.prelude) } @@ -27,8 +28,16 @@ SECTIONS { *(.rodata.*) } + .magic : { + /* Magic bytes the prelude uses to make sure it's loaded the subsequent sectors correctly. */ + LONG(0x544e4150) + } + + boot1_magic = ADDR(.magic); + /* Define a symbol for the total length of the binary, so the prelude knows how many blocks to * load from disk. */ boot1_bin_len = . - 0x8200; + boot1_bin_sectors = (boot1_bin_len + 511) / 512; } diff --git a/boot1/src/main.rs b/boot1/src/main.rs index f74d198..0bb885d 100644 --- a/boot1/src/main.rs +++ b/boot1/src/main.rs @@ -10,9 +10,9 @@ const VGA_ADDR: usize = 0xb8000; const STR: &[u8] = b"the quick brown fox jumps over the lazy dog"; -extern "C" { - fn prelude() -> !; -} +// extern "C" { +// fn prelude() -> !; +// } #[panic_handler] fn panic(_info: &PanicInfo) -> ! { @@ -45,10 +45,7 @@ pub extern "C" fn _start() -> ! { vga_buf[i] = 0x1f00 | u16::from(b); } - vga_buf[0] = 0x1f02; //smiley - - // hlt() - unsafe { prelude() }; + hlt() } #[inline] diff --git a/justfile b/justfile index 871e12c..ea5f3f4 100644 --- a/justfile +++ b/justfile @@ -1,13 +1,17 @@ run: qemu-system-x86_64 \ -monitor stdio \ + -d int \ + -no-reboot \ -bios seabios/out/bios.bin \ -m 512M \ -drive format=raw,file=disk.bin + build: nasm -f bin -Iinclude -o boot0.bin boot0.s - nasm -f bin -Iinclude -o boot1.bin boot1.s + cd boot1; cargo build --release + # nasm -f bin -Iinclude -o boot1.bin boot1.s zero_disk: dd if=/dev/zero of=disk.bin bs=512 count=1000 @@ -33,4 +37,5 @@ write_stage1: dd if=boot0.bin of=disk.bin conv=notrunc write_stage2: - dd if=boot1.bin of=disk.bin bs=512 seek=70 conv=notrunc + # dd if=boot1.bin of=disk.bin bs=512 seek=70 conv=notrunc + dd if=boot1/target/target_protected/release/boot1 of=disk.bin bs=512 seek=70 conv=notrunc