From 2219fac4e51c77bdb678d59a797382da97ddfaa3 Mon Sep 17 00:00:00 2001 From: pantonshire Date: Sun, 6 Apr 2025 14:27:55 +0100 Subject: [PATCH] more refactoring --- .gitignore | 1 + boot0.s | 270 ----- boot1.s | 1101 ------------------- docs/mem_layout.md | 3 +- justfile | 31 +- stages/s1/s1.s => stage_1/main.s | 3 +- {stages/s3 => stage_2}/a20.s | 2 - s2.ld => stage_2/link.ld | 26 +- stages/s3/s3.s => stage_2/main.s | 10 +- stages/s2/s2.s => stage_2/prelude.s | 54 +- {stages/s4 => stage_3}/.cargo/config.toml | 0 stage_3/.gitignore | 1 + {stages/s4 => stage_3}/Cargo.lock | 2 +- stage_3/Cargo.toml | 6 + stage_3/build.rs | 213 ++++ stage_3/link.ld | 25 + {stages/s4 => stage_3}/protected32.json | 0 stage_3/src/asm/bios_call.s | 8 + stages/s4/src/lib.rs => stage_3/src/main.rs | 2 +- {stages/s4 => stage_3}/src/spin.rs | 0 {stages/s4 => stage_3}/src/vga.rs | 0 {stages/s4 => stage_3}/src/x86.rs | 0 stages/s2/s2.o | Bin 1264 -> 0 bytes stages/s3/a20.o | Bin 800 -> 0 bytes stages/s3/s3.o | Bin 1280 -> 0 bytes stages/s4/Cargo.toml | 14 - xtask/.gitignore | 1 + xtask/Cargo.toml | 6 + xtask/src/main.rs | 83 ++ xtask/src/mkimg.rs | 32 + 30 files changed, 438 insertions(+), 1456 deletions(-) delete mode 100644 boot0.s delete mode 100644 boot1.s rename stages/s1/s1.s => stage_1/main.s (99%) rename {stages/s3 => stage_2}/a20.s (99%) rename s2.ld => stage_2/link.ld (54%) rename stages/s3/s3.s => stage_2/main.s (98%) rename stages/s2/s2.s => stage_2/prelude.s (83%) rename {stages/s4 => stage_3}/.cargo/config.toml (100%) create mode 100644 stage_3/.gitignore rename {stages/s4 => stage_3}/Cargo.lock (88%) create mode 100644 stage_3/Cargo.toml create mode 100644 stage_3/build.rs create mode 100644 stage_3/link.ld rename {stages/s4 => stage_3}/protected32.json (100%) create mode 100644 stage_3/src/asm/bios_call.s rename stages/s4/src/lib.rs => stage_3/src/main.rs (96%) rename {stages/s4 => stage_3}/src/spin.rs (100%) rename {stages/s4 => stage_3}/src/vga.rs (100%) rename {stages/s4 => stage_3}/src/x86.rs (100%) delete mode 100644 stages/s2/s2.o delete mode 100644 stages/s3/a20.o delete mode 100644 stages/s3/s3.o delete mode 100644 stages/s4/Cargo.toml create mode 100644 xtask/.gitignore create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/main.rs create mode 100644 xtask/src/mkimg.rs diff --git a/.gitignore b/.gitignore index 7f6b588..05992b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +*.o *.bin .pc target/ diff --git a/boot0.s b/boot0.s deleted file mode 100644 index 2e1dd6c..0000000 --- a/boot0.s +++ /dev/null @@ -1,270 +0,0 @@ -; MEMORY LAYOUT -; R = reserved, U = usable -; -------------------------------------------------------------------- -; R | 0x000000 - 0x000400: real-mode interrupt vector table -; R | 0x000400 - 0x000500: bios data area -; U | 0x000500 - 0x004000: main stack -; U | 0x004000 - 0x006a00: globals -; U | 0x006a00 - 0x007c00: memory map -; U | 0x007c00 - 0x007e00: boot sector -; U | 0x007e00 - 0x080000: conventional usable memory -; R | 0x080000 - 0x0a0000: extended bios data area (maximum possible size) -; R | 0x0a0000 - 0x0c0000: video memory -; R | 0x0c0000 - 0x0c8000: video bios -; R | 0x0c8000 - 0x0f0000: bios expansions -; R | 0x0f0000 - 0x100000: motherboard bios - -%include "defines.s" - -; BIOS puts our boot sector at 0000:7c00 -[org BOOT0_LOADPOINT] -; We're (probably) in real mode -[bits 16] - -main: - ; Disable interrupts - cli - - xor ax, ax - - mov ds, ax - mov es, ax - - ; Put the stack base at 0x4000. - ; Stack grows high->low, so we'll grow away from our globals and program text. - mov ss, ax - mov bp, STACK_BASE - mov sp, bp - - ; Segment for VGA (0xb800 * 16 = 0xb8000) - mov ax, 0xb800 - mov fs, ax - - ; Set VGA mode - ; https://mendelson.org/wpdos/videomodes.txt - mov ax, 0x0003 - int 0x10 - - ; Store boot drive number - xor dh, dh - push dx - - ; Get drive geometry - mov di, 0x00 - mov ah, 0x08 - int 0x13 - jc panic - ; Load sectors per track into cx & spill - and cl, 0x3f - xor ch, ch - push cx - ; Load number of heads into bx & spill - movzx bx, dh - inc bx - push bx - - ; Load LBA 1. - mov ax, 1 - mov bx, 0x7e00 - call read_lba - - ; Check the GPT header magic "EFI PART" - mov cx, GPT_MAGIC_LEN - mov si, gpt_magic - mov di, 0x7e00 - repe cmpsb - jne panic - - ; Ensure the 8-byte GPT starting LBA fits in 16 bits - mov di, 0x7e00 ; The rep increments di so we need to reset it - mov eax, [di + 0x4c] - mov bx, [di + 0x4a] - or ax, bx - or eax, eax - jnz panic - ; Store the first 16 bits of the GPT starting LBA (we have made sure the remaining bits are 0) - push word [di + 0x48] - - ; Load number of partitions - mov ax, [di + 0x50] - mov bx, [di + 0x52] - or bx, bx - jz .gpt_n_partitions_loaded - ; Number of partitions overflows 16 bits, so we just concern ourselves with the first 65535. - ; That's an awful lot of partitions anyway. - mov ax, 0xffff -.gpt_n_partitions_loaded: - push ax - - ; Load GPT entry size - mov eax, [di + 0x54] ; Operand size override otherwise this is going to be painful - mov ebx, eax - ; Assert that the entry size is 128 * 2^n for some integer n>=0. This is required for a valid GPT - ; and has the nice properties that: - ; - If each entry is larger than a sector (512 bytes), they'll be sector-aligned. - ; - If each entry is smaller than a sector, an integer number of them will fit into a sector. - or eax, eax ; Test size != 0 because 128 * 2^n != 0 - jz panic - test eax, 127 ; Test size is a multiple of 128 - jnz panic - ; Use the (n & (n - 1)) == 0 trick to test if the entry size is a power of 2. Since we already - ; know it's a nonzero multiple of 128, if size is a power of 2 then size = 128 * 2^n holds. - ; Therefore we don't need to bother dividing by 128 first (shr 7), which saves a couple of bytes. - mov ecx, ebx - dec ecx - and ecx, eax - jnz panic - - ; Find the "sector stride", which is the number of sectors we increment by each time we want to - ; load a new entry. - shr eax, 9 ; Divide by sector size to get sectors per entry - cmp eax, 0xffff ; Make sure sectors per entry fits in 16 bits - ja panic - or ax, ax - jnz .gpt_sector_stride_loaded - ; Sector stride must be at least one or we'll load the same sector each time! - inc ax -.gpt_sector_stride_loaded: - push ax - - ; Find the "byte stride", which is the number of bytes we increment by each time we want to load - ; the next entry in the same sector. - cmp ebx, 512 - jb .gpt_find_entries_per_sector - push word 0 ; Arbitrary byte stride since there's only one entry per sector - push word 1 ; 1 entry per sector, since an entry is larger than a sector - jmp .gpt_found_entries_per_sector -.gpt_find_entries_per_sector: - push bx ; Store byte stride = entry length in this case - xor dx, dx - mov ax, 512 - div bx ; Find entries per sector - push ax -.gpt_found_entries_per_sector: - - ; Set up stack variables for our second stage search loop. - xor ax, ax - push ax ; Current entry - push ax ; Current entry within the current sector - push ax ; Number of sectors loaded - push word [bp - GPT_ENTRIES_START_LBA] ; Current LBA - - ; Search for the partition storing our second stage. -.loop_find_stage2: - mov dx, [bp - GPT_CURRENT_ENTRY_IDX] - cmp [bp - GPT_N_ENTRIES_16], dx - ; Panic if we've run out of partitions and haven't found the second stage yet. - jbe panic - - ; If we haven't loaded any sectors yet, load the first one. - cmp word [bp - GPT_SECTORS_LOADED], 0 - je .load_first_lba - ; If there's still more entries in the current sector, skip loading a new sector - mov ax, [bp - GPT_SECTOR_ENTRY_IDX] ; Load current entry index within the current sector - cmp [bp - GPT_ENTRIES_PER_SECTOR], ax ; Compare to entries per sector - ja .process_current_entry - - mov ax, [bp - GPT_SECTOR_STRIDE] ; Load sector stride - add word [bp - GPT_CURRENT_LBA], ax ; Increment current LBA by sector stride - mov word [bp - GPT_SECTOR_ENTRY_IDX], 0 ; Reset the current entry index within the current sector -.load_first_lba: - ; Read the current LBA to 0x8000 (just past the end of the GPT header) - mov ax, [bp - GPT_CURRENT_LBA] - mov bx, 0x8000 - call read_lba - ; Increment number of sectors loaded - inc word [bp - GPT_SECTORS_LOADED] - -.process_current_entry: - ; Calculate the address of the current GPT entry. - mov ax, [bp - GPT_SECTOR_ENTRY_IDX] ; Load current entry index within current sector - xor dx, dx - mul word [bp - GPT_BYTE_STRIDE] ; Get the byte offset in the current sector of the current entry - add ax, 0x8000 ; Convert offset to address (we loaded the sector at 0x8000) - - ; Compare entry GUID to our stage 2 partition GUID. - mov cx, GUID_LEN - mov si, guid_stage2 - mov di, ax - repe cmpsb - je .found_stage2 - - ; Next iteration - inc word [bp - GPT_CURRENT_ENTRY_IDX] ; Increment current entry index - inc word [bp - GPT_SECTOR_ENTRY_IDX] ; Increment current entry index within the current sector - jmp .loop_find_stage2 - -.found_stage2: - push ax ; Address of the GPT entry for stage 2 - mov si, ax - - ; Load partition LBA start. - mov eax, [si + 0x20] - mov ebx, [si + 0x24] - ; Ensure it fits in 16 bits. - or ebx, ebx - jnz panic - cmp ebx, 0xffff - ja panic - ; Load partition LBA end. - mov ecx, [si + 0x28] - mov edx, [si + 0x2c] - ; Assert that the end LBA is greater than or equal to the start LBA, so we have at least one - ; sector to load (end LBA is inclusive). - or edx, edx - jnz .stage2_end_lba_ok - cmp eax, ecx - ja panic -.stage2_end_lba_ok: - - mov bx, BOOT1_LOADPOINT - call read_lba - jmp bx - - ; Load a single boot disk sector. Panic on failure. - ; Inputs: - ; - ax: LBA to load - ; - bx: address to read sector to - ; Clobber: ax, cx, dx -read_lba: - ; sector - 1 = LBA % sectors_per_track - ; temp = LBA / sectors_per_track - ; head = temp % n_heads - ; cylinder = temp / n_heads - xor dx, dx - ; Divide by sectors per track. dx = mod (sector - 1), ax = div (temp) - div word [bp - SECTORS_PER_TRACK] - ; Put the sector into cx (the bios call will use cl) - mov cx, dx - inc cx - xor dx, dx - ; Divide by number of heads. dx = mod (head), ax = div (cylinder) - div word [bp - N_HEADS] - mov dh, dl - mov ch, al - mov dl, byte [bp - BOOT_DRIVE] - mov ah, 0x02 - mov al, 1 - ; Read sector - int 0x13 - jc panic - ret - -panic: - mov ax, 0x0003 - int 0x10 - mov word fs:[0x0000], 0x4f21 - hlt - -gpt_magic db "EFI PART" -GPT_MAGIC_LEN equ $ - gpt_magic - -; Our stage2 guid: fdffea69-3651-442f-a11d-88a09bf372dd -guid_stage2 db 0x69, 0xea, 0xff, 0xfd, 0x51, 0x36, 0x2f, 0x44, \ - 0xa1, 0x1d, 0x88, 0xa0, 0x9b, 0xf3, 0x72, 0xdd -GUID_LEN equ $ - guid_stage2 - -; MBR bootstrap field is 440 bytes long -%if ($ - $$) > 440 -%error "exceeded mbr bootstrap field size" -%endif diff --git a/boot1.s b/boot1.s deleted file mode 100644 index 32ddb0a..0000000 --- a/boot1.s +++ /dev/null @@ -1,1101 +0,0 @@ -%include "defines.s" - -[org BOOT1_LOADPOINT] -[bits 16] - -%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_TOTAL_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 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 - -.return: - pop ebx - pop es - fnret - - -panic_simple: - mov ax, 0x0003 - int 0x10 - mov word fs:[0x0000], 0x4f21 - hlt - - -%if ($ - $$) > 512 -%error "boot1 self-loader exceeded sector size" -%endif - - -%macro panic 1 - push word %1 - call panic_fancy -%endmacro - - -main: - ; Set VGA mode - ; https://mendelson.org/wpdos/videomodes.txt - mov ax, 0x0003 - int 0x10 - - ; Disable the cursor (don't want to look at the blink blink blink) - mov ax, 0x0100 - mov cx, 0x3f00 - int 0x10 - - mov word [GLOBALS + VGA_COL], 0x1f00 - - call vga_clear - - mov ax, msg_boot1_loaded - call vga_println - - call test_a20 - test al, al - jnz .a20_enabled - - mov ax, msg_a20_disabled - call vga_println - - mov ax, msg_a20_8042 - call vga_println - - ; Try to enable A20 using the Intel 8042 PS/2 keyboard controller. - call enable_a20_intel_8042 - call test_a20 - test al, al - jnz .a20_enabled - - ; TODO: try other methods first before we panic: - ; - [ ] BIOS interrupt - ; - [ ] Fast A20 enable - panic PANIC_TYPE_A20 - -.a20_enabled: - mov ax, msg_a20_enabled - call vga_println - - call mem_detect - jc .mem_detect_fail - - call print_memmap - - call unreal_enable - -.hlt_loop: - hlt - jmp .hlt_loop - -.mem_detect_fail: - panic PANIC_TYPE_MEM_DETECT - - -; Print a panic message then terminate. -; Arguments: -; - word [sp]: panic type -; Does not return -panic_fancy: - push bp - mov bp, sp - - ; Flags first so we don't cobber them when we sub (uwu) - pushfd ; Temp flags: bp - 0x04 - sub sp, 16 ; Buffer: bp - 0x14 - push dword 0 ; Registers: bp - 0x18 - push eax - push ebx - push ecx - push edx - push esi - push edi - push esp - push ebp - xor eax, eax - mov ax, cs - push eax - mov ax, ds - push eax - mov ax, es - push eax - mov ax, fs - push eax - mov ax, gs - push eax - mov ax, ss - push eax - mov ax, word [bp - 0x04] - push eax - - mov ax, [bp + 0x02] - mov [bp - 0x18], eax - - mov word [GLOBALS + VGA_COL], 0x4f00 - call vga_clear - - mov ax, VGA_WIDTH + 1 - mov cx, msg_panic - mov dx, VGA_WIDTH - 1 - call vga_print_raw - - xor bx, bx - mov di, VGA_WIDTH * 4 -.loop_dump_regs: - cmp bx, 16 - jae .loop_dump_regs_done - - mov si, bx - shl si, 1 - add si, table_reg_msgs - mov cx, [si] - mov ax, di - add ax, 1 - mov dx, 3 - call vga_print_raw - - push es - mov ax, ss - mov es, ax - - ; Format the current saved register value as hex - lea si, [bp - 0x18] - mov ax, bx - shl ax, 2 - sub si, ax - mov ecx, ss:[si] - lea ax, [bp - 0x14] - call dump_reg - - mov ax, di - add ax, 5 - lea cx, [bp - 0x14] - mov dx, 8 - call vga_print_raw - - pop es - inc bx - add di, VGA_WIDTH / 5 - jmp .loop_dump_regs - -.loop_dump_regs_done: - - mov bx, [bp + 0x04] - cmp bx, PANIC_TYPE_MAX - jae .print_panic_type_done - shl bx, 1 - add bx, panic_type_msgs - mov cx, [bx] - mov ax, VGA_WIDTH * 2 + 1 - mov dx, VGA_WIDTH - 1 - call vga_print_raw - -.print_panic_type_done: - - ; TODO: unwind stack - ; - Load saved bp and ip from [bp] and [bp + 2], respectively - ; - Load bp and ip before that from [prev_bp], [prev_bp + 2] - ; - Repeat - -.halt: - hlt - ; Handle non-maskable interrupts - jmp .halt - - -; Clear the VGA text buffer. -; Arguments: none -; Return: none -; Clobber: none -vga_clear: - fnstart - push di - push es - push ax - push cx - - mov ax, VGA_SEG - mov es, ax - mov ax, [GLOBALS + VGA_COL] - mov cx, VGA_WIDTH * VGA_HEIGHT - xor di, di - rep stosw - mov word [GLOBALS + TEXTBUF_LINE], 0 - - pop cx - pop ax - pop es - pop di - fnret - - -; Scroll the VGA text buffer up one line. -; Arguments: none -; Return: none -; Clobber: none -vga_scroll: - fnstart - push si - push di - push ax - push cx - push es - push ds - - mov ax, VGA_SEG - mov ds, ax - mov es, ax - - ; Copy everything up one line. - mov cx, VGA_WIDTH * (VGA_HEIGHT - 1) - mov si, VGA_WIDTH * 2 - mov di, 0 - rep movsw - - ; Clear the last line. - mov ax, [GLOBALS + VGA_COL] - mov cx, VGA_WIDTH - mov di, VGA_WIDTH * (VGA_HEIGHT - 1) * 2 - rep stosw - - pop ds - pop es - - ; Decrement the current textbuf line if it's greater than 0. - mov cx, [GLOBALS + TEXTBUF_LINE] - xor ax, ax - sub cx, 1 - cmovae ax, cx - mov [GLOBALS + TEXTBUF_LINE], ax - - pop cx - pop ax - pop di - pop si - fnret - - -; Write a null-terminated string to the given position in the VGA text buffer. -; Arguments: -; - es: output string segment -; - ax: vga buffer index -; - cx: output string offset -; - dx: maximum length of string to print -; Return: -; - ax: vga buffer index after last character written -; Clobber: none -vga_print_raw: - fnstart - push fs - push si - push di - push cx - push dx - - mov si, cx - xchg ax, cx - - ; Find the distance between the starting index and the end of the buffer. - mov ax, (VGA_WIDTH * VGA_HEIGHT) - sub ax, cx - ; If the starting index is past the end of the buffer, return early. - jc .done - ; Clamp the maximum length to the distance between the starting index and the end of the buffer. - cmp ax, dx - cmovb dx, ax - - mov di, cx - shl di, 1 - - mov ax, VGA_SEG - mov fs, ax - mov ah, [GLOBALS + VGA_COL + 1] - -.loop: - test dx, dx - jz .done - dec dx - mov al, es:[si] - test al, al - jz .done - mov fs:[di], ax - add di, 2 - inc si - inc cx - jmp .loop - -.done: - xchg ax, cx - - pop dx - pop cx - pop di - pop si - pop fs - fnret - - -; Write one line to the VGA text buffer. The string should be null-terminated; we embrace the evil -; of null-termination so this function only takes one argument, so it's slightly less of a faff to -; call in most cases. -; Arguments: -; - es: output string segment -; - ax: output string offset -; Return: none -; Clobber: none -vga_println: - fnstart - push ax - push bx - push cx - push dx - - cmp word [GLOBALS + TEXTBUF_LINE], VGA_HEIGHT - jb .scroll_done - call vga_scroll -.scroll_done: - - mov bx, ax - - xor dx, dx - mov ax, [GLOBALS + TEXTBUF_LINE] - mov cx, VGA_WIDTH - mul cx - - mov dx, VGA_WIDTH - mov cx, bx - - call vga_print_raw - - inc word [GLOBALS + TEXTBUF_LINE] - - pop dx - pop cx - pop bx - pop ax - fnret - - -; Convert the value in ecx to hex and write it to the buffer at es:ax. The buffer should be at -; least 8 bytes long. -; - es: output buffer segment -; - ax: output buffer offset -; - ecx: value to convert to hex and print -; Return: -; - ax: the address one after the last byte that was written -; Clobber: none -dump_reg: - fnstart - push bx - push dx - push ecx - - mov bx, ax - mov dx, 4 - -.loop: - test dx, dx - jz .done - dec dx - - rol ecx, 8 - - mov al, cl - shr al, 4 - call nybble_to_hex_char - mov es:[bx], al - inc bx - - mov al, cl - call nybble_to_hex_char - mov es:[bx], al - inc bx - - jmp .loop - -.done: - mov ax, bx - - pop ecx - pop dx - pop bx - fnret - - -; Convert nybble to lowercase ascii hex char. -; Arguments: -; - al: value to convert -; Return: -; - al: converted ascii hex value -; Clobber: none -nybble_to_hex_char: - ; We don't use the stack, so no need to change bp. - and al, 0x0f - cmp al, 9 - jbe .0_to_9 - add al, (0x61 - 0x0a) - jmp .done -.0_to_9: - add al, 0x30 -.done: - ret - - -; Copy a null-terminated string from ds:cx to es:ax. -; - es: output buffer segment -; - ax: output buffer offset -; - ds: input buffer segment -; - cx: input buffer offset -; Return: -; - ax: the address of the null terminator that was written -; Clobber: none -strcpy: - fnstart - push si - push di - - mov di, ax - mov si, cx - -.loop: - mov al, [si] - mov es:[di], al - test al, al - jz .done - inc si - inc di - jmp .loop - -.done: - mov ax, di - pop di - pop si - fnret - - -; Check whether the A20 line is enabled. Writes to the boot sector identifier. -; Arguments: none -; Return: -; - ax: 0 if A20 disabled, nonzero if A20 enabled -; Clobber: none -test_a20: - push bp - mov bp, sp - push gs - - ; Restore the boot sector identifier in case it was overwritten by anything. - mov word [0x7dfe], 0xaa55 - - mov ax, 0xffff - mov gs, ax - xor ax, ax - - ; If the word at 0x107dfe (1 MiB after the boot sector identifier) is different to the boot - ; sector identifier, than A20 must be enabled. - cmp word gs:[0x7e0e], 0xaa55 - setne al - jne .return - - ; Even if A20 was enabled, the two words may have been equal by chance, so we temporarily swap - ; the boot sector identifier bytes and test again. - ror word [0x7dfe], 8 - cmp word gs:[0x7e0e], 0x55aa - setne al - ror word [0x7dfe], 8 - jmp .return - -.return: - pop gs - pop bp - ret - - -; Wait for the Intel 8042 input buffer to become empty, so we can write. -; Arguments: none -; Return: none -; Clobber: al -intel_8042_wait_write: -.loop: - ; Read the 8042 status register. - in al, INTEL_8042_IN_STATUS - ; Input buffer status flag set means the input buffer is full, so loop in this case. - test al, INTEL_8042_STATUS_MASK_IBUF - jnz .loop - ret - - -; Wait for the Intel 8042 output buffer to become filled, so we can read. -; Arguments: none -; Return: none -; Clobber: al -intel_8042_wait_read: -.loop: - ; Read the 8042 status register. - in al, INTEL_8042_IN_STATUS - ; Output buffer status flag unset means output buffer is empty, so loop in this case. - test al, INTEL_8042_STATUS_MASK_OBUF - jz .loop - ret - - -; Try to enable A20 using the Intel 8042 PS/2 keyboard controller. -; Arguments: none -; Return: none -; Clobber: ax, cx, dx -enable_a20_intel_8042: - ; Temporarily disable the keyboard. - call intel_8042_wait_write - mov al, INTEL_8042_CMD_PS2_1_DISABLE - out INTEL_8042_OUT_CMD, al - - ; Read the controller output port. - call intel_8042_wait_write - mov al, INTEL_8042_CMD_CONTROLLER_OUT_PORT_READ - out INTEL_8042_OUT_CMD, al - call intel_8042_wait_read - in al, INTEL_8042_IO_DATA - - ; The second bit is "A20 enabled", so set it. - mov cl, al - or cl, 2 - - ; Write the modified byte back to the controller output port. - call intel_8042_wait_write - mov al, INTEL_8042_CMD_CONTROLLER_OUT_PORT_WRITE - out INTEL_8042_OUT_CMD, al - call intel_8042_wait_write - mov al, cl - out INTEL_8042_IO_DATA, al - - ; Re-enable the keyboard. - call intel_8042_wait_write - mov al, INTEL_8042_CMD_PS2_1_ENABLE - out INTEL_8042_OUT_CMD, al - - ; Wait for writes to finish. - call intel_8042_wait_write - - ret - - -unreal_enable: - fnstart - push ds - - ; Load GDT - lgdt [unreal_setup_gdt_slice] - - ; Switch to protected mode. - mov eax, cr0 - or al, 0x01 - mov cr0, eax - ; Set the code segment to the code segment in our GDT (offset 0x08) - jmp (GDT_DESCRIPTOR_SIZE):.protected_mode - -.protected_mode: - ; In protected mode, the value in a segment register refers to an offset into the GDT. Setting - ; a segment register updates the segment descriptor cache with the selected GDT descriptor. The - ; limit of our second GDT descriptor is 0xfffff pages, which covers the entire 32-bit address - ; space. This limit is unchanged when we return to real mode, so we'll be able to address the - ; whole 32-bit address space. - mov ax, (2 * GDT_DESCRIPTOR_SIZE) - mov ds, ax - - ; Switch back to real mode. - mov eax, cr0 - and eax, 0xfe - mov cr0, eax - ; Reset code segment back to 0 since we're using real-mode addressing again - ; (0x08 would now offset us by 16 * 8 = 128 bytes if we left it there) - jmp 0x00:.unreal_mode - -.unreal_mode: - pop ds - fnret - - -; FIXME: -; - We want this to work in a streaming fashion, so we can see all the reserved regions even if we -; don't have enough space to store all the regions. We can always rerun with a different buffer -; in high memory later. -; - We should maintain a separate list of available regions. When we see a new region: -; - If it's available: -; - Merge it with any other available regions we can. N.B. we may be able to do more than one -; merge (e.g. we get a new region which fills a gap between two others). -; - If we couldn't merge and there's space in the available region list, add it to the list. It -; may be desirable to put it in a sorted position in the list, to make merging easier. -; - If it's not available (reserved, bad etc.): -; - Store it if we can -; - Trim off any memory from the available regions which overlaps with them. N.B. this may -; require splitting an available region into two, and we may not have space to store both -; after the split. -; - We also should maintin a list of ACPI regions, so we can read them later. -mem_detect: - fnstart - push es - - mov word [GLOBALS + MEMMAP_ENTRIES], 0 - - ; Memset the memmap buffer to 0. - xor ax, ax - mov es, ax - mov di, MEMMAP - mov cx, (MEMMAP_END - MEMMAP) - rep stosb - - ; Entry number (will be incremented for us) - xor ebx, ebx - ; Buffer (es:di) - mov di, MEMMAP - -.loop: - cmp word [GLOBALS + MEMMAP_ENTRIES], MEMMAP_CAP - jae .done - - ; Init extended field to 0x1 in case e820 doesn't populate it. - mov dword [di + MEMMAP_ENT_FIELD_EXT], 0x1 - ; Size of entry for e820 to write - mov ecx, E820_ENTRY_SIZE - ; Magic number - mov edx, E820_MAGIC - ; Detect memory - mov eax, 0xe820 - clc - int 0x15 - ; Carry flag will be set if we'd already reached the end of the entries. - jc .done - - ; Test magic number - cmp eax, E820_MAGIC - jne .fail - - inc word [GLOBALS + MEMMAP_ENTRIES] - - ; Calculate and cache the entry end address. - mov eax, [di + MEMMAP_ENT_FIELD_BASE] - add eax, [di + MEMMAP_ENT_FIELD_LEN] - mov [di + MEMMAP_ENT_FIELD_END], eax - mov eax, [di + MEMMAP_ENT_FIELD_BASE + 4] - adc eax, [di + MEMMAP_ENT_FIELD_LEN + 4] - mov [di + MEMMAP_ENT_FIELD_END + 4], eax - - ; e820 _may_ return ebx=0 once we reach the last entry. - test ebx, ebx - jz .done - - add di, MEMMAP_ENT_SIZE - jmp .loop - -.fail: - stc - jmp .return - -.done: - mov ax, [GLOBALS + MEMMAP_ENTRIES] - test ax, ax - jz .fail - clc - -.return: - pop es - fnret - - -print_memmap: - fnstart - - sub sp, VGA_WIDTH - push eax - push ebx - push ecx - push edx - - mov si, MEMMAP - mov dx, [GLOBALS + MEMMAP_ENTRIES] - -.loop: - test dx, dx - jz .done - dec dx - - push es - mov ax, ss - mov es, ax - - ; Calculate region end - ; mov eax, [si] - ; mov ecx, [si + 8] - ; add eax, ecx - ; push eax - ; mov eax, [si + 4] - ; mov eax, [si + 12] - ; adc eax, ecx - ; push eax - - lea ax, [bp - VGA_WIDTH] - - mov cx, msg_memmap_base - call strcpy - - mov ecx, [si + MEMMAP_ENT_FIELD_BASE + 4] - call dump_reg - mov ecx, [si + MEMMAP_ENT_FIELD_BASE] - call dump_reg - - mov cx, msg_sep - call strcpy - mov cx, msg_memmap_len - call strcpy - - mov ecx, [si + MEMMAP_ENT_FIELD_LEN + 4] - call dump_reg - mov ecx, [si + MEMMAP_ENT_FIELD_LEN] - call dump_reg - - mov cx, msg_sep - call strcpy - mov cx, msg_memmap_end - call strcpy - - mov ecx, [si + MEMMAP_ENT_FIELD_END + 4] - call dump_reg - mov ecx, [si + MEMMAP_ENT_FIELD_END] - call dump_reg - - mov cx, msg_sep - call strcpy - mov cx, msg_memmap_type - call strcpy - - mov ecx, [si + MEMMAP_ENT_FIELD_TYPE] - call dump_reg - - mov bx, ax - mov byte [bx], 0 - - lea ax, [bp - VGA_WIDTH] - call vga_println - - pop es - add si, MEMMAP_ENT_SIZE - jmp .loop - -.done: - pop edx - pop ecx - pop ebx - pop eax - add sp, VGA_WIDTH - fnret - - -unreal_setup_gdt_slice: - dw UNREAL_SETUP_GDT_LEN - dd unreal_setup_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) -; -unreal_setup_gdt: - dq 0 - -; Code segment for low memory, bytes 0x0000 - 0xffff -.segment_code: - db 0xff, 0xff, \ - 0x00, 0x00, \ - 0x00, \ - 010011011b, \ - 00000000b, \ - 0x00 - -; Data segment for 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 - - UNREAL_SETUP_GDT_LEN equ ($ - unreal_setup_gdt) - - -msg_boot1_loaded db "boot1 loaded. hello!", 0 -msg_a20_enabled db "a20 enabled", 0 -msg_a20_disabled db "a20 not enabled", 0 -msg_a20_8042 db "trying 8042", 0 -msg_panic db "panic!", 0 - -msg_sep db ",", 0 -msg_memmap_base db "base=", 0 -msg_memmap_end db "end=", 0 -msg_memmap_len db "len=", 0 -msg_memmap_type db "type=", 0 -msg_memmap_ext db "ext=", 0 - -msg_reg_eip db "eip", 0 -msg_reg_eax db "eax", 0 -msg_reg_ebx db "ebx", 0 -msg_reg_ecx db "ecx", 0 -msg_reg_edx db "edx", 0 -msg_reg_esi db "esi", 0 -msg_reg_edi db "edi", 0 -msg_reg_esp db "esp", 0 -msg_reg_ebp db "ebp", 0 -msg_reg_cs db "cs", 0 -msg_reg_ds db "ds", 0 -msg_reg_es db "es", 0 -msg_reg_fs db "fs", 0 -msg_reg_gs db "gs", 0 -msg_reg_ss db "ss", 0 -msg_reg_flags db "flg", 0 - -table_reg_msgs: - dw msg_reg_eip - dw msg_reg_eax - dw msg_reg_ebx - dw msg_reg_ecx - dw msg_reg_edx - dw msg_reg_esi - dw msg_reg_edi - dw msg_reg_esp - dw msg_reg_ebp - dw msg_reg_cs - dw msg_reg_ds - dw msg_reg_es - dw msg_reg_fs - dw msg_reg_gs - dw msg_reg_ss - dw msg_reg_flags - dw 0 - -msg_panic_generic db "generic panic", 0 -msg_panic_a20 db "failed to enable a20 line", 0 -msg_panic_mem_detect db "failed to detect available memory", 0 - -panic_type_msgs: - PANIC_TYPE_GENERIC equ ($ - panic_type_msgs) / 2 - dw msg_panic_generic - PANIC_TYPE_A20 equ ($ - panic_type_msgs) / 2 - dw msg_panic_a20 - PANIC_TYPE_MEM_DETECT equ ($ - panic_type_msgs) / 2 - dw msg_panic_mem_detect - PANIC_TYPE_MAX equ ($ - panic_type_msgs) / 2 - dw 0 - -boot1_magic dd BOOT1_MAGIC - -BOOT1_TOTAL_LEN equ $ - $$ -BOOT1_TOTAL_SECTORS equ (BOOT1_TOTAL_LEN + 511) / 512 - -%if (BOOT1_LOADPOINT + BOOT1_TOTAL_LEN) > EBDA_START -%error "boot1 too large to be loaded" -%endif diff --git a/docs/mem_layout.md b/docs/mem_layout.md index b4ec4d3..0a4172f 100644 --- a/docs/mem_layout.md +++ b/docs/mem_layout.md @@ -17,6 +17,7 @@ | Reserved | 0x0c8000 - 0x0f0000 | bios expansions | | Reserved | 0x0f0000 - 0x100000 | motherboard bios | -TODO: ensure that we don't exceed +0x10000: stage 3 + TODO: once we're in real mode, repurpose s2 and s3 for a stack TODO: load s4 into a separate memory region diff --git a/justfile b/justfile index d954696..efc9e8a 100644 --- a/justfile +++ b/justfile @@ -1,25 +1,31 @@ include_flags := "-Iinclude" common_flags := "-werror " + include_flags +ld32 := "ld -m elf_i386" run: qemu-system-x86_64 \ -monitor stdio \ -no-reboot \ - -bios seabios/out/bios.bin \ -m 512M \ -drive format=raw,file=disk.bin -build: - nasm -f bin {{common_flags}} -o s1.bin stages/s1/s1.s - nasm -f elf {{common_flags}} -o stages/s2/s2.o stages/s2/s2.s - nasm -f elf {{common_flags}} -o stages/s3/s3.o stages/s3/s3.s - nasm -f elf {{common_flags}} -o stages/s3/a20.o stages/s3/a20.s - ld.lld -T s2.ld -o s234.bin stages/s2/*.o stages/s3/*.o stages/s4/target/protected32/release/libs4.a +#-bios seabios/out/bios.bin + +build: build_stage_1 build_stage_2 + +build_stage_1: + nasm -f bin {{common_flags}} -o stage_1/stage_1.bin stage_1/main.s + +build_stage_2: + nasm -f elf {{common_flags}} -o stage_2/prelude.o stage_2/prelude.s + nasm -f elf {{common_flags}} -o stage_2/main.o stage_2/main.s + nasm -f elf {{common_flags}} -o stage_2/a20.o stage_2/a20.s + {{ld32}} -m elf_i386 -T stage_2/link.ld -o stage_2/stage_2.bin stage_2/*.o mkimg: dd if=/dev/zero of=disk.bin bs=440 count=1 conv=notrunc - dd if=s1.bin of=disk.bin conv=notrunc - dd if=s234.bin of=disk.bin bs=512 seek=70 conv=notrunc + dd if=stage_1/stage_1.bin of=disk.bin conv=notrunc + dd if=stage_2/stage_2.bin of=disk.bin bs=512 seek=70 conv=notrunc # build: # nasm -f bin -Iinclude -o boot0.bin boot0.s @@ -45,10 +51,3 @@ partition_disk: parted --script disk.bin mkpart stage2 70s 900s parted --script disk.bin type 6 fdffea69-3651-442f-a11d-88a09bf372dd -# write_stage1: -# dd if=/dev/zero of=disk.bin bs=440 count=1 conv=notrunc -# 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/target/target_protected/release/boot1 of=disk.bin bs=512 seek=70 conv=notrunc diff --git a/stages/s1/s1.s b/stage_1/main.s similarity index 99% rename from stages/s1/s1.s rename to stage_1/main.s index c5763a2..83f6596 100644 --- a/stages/s1/s1.s +++ b/stage_1/main.s @@ -199,8 +199,9 @@ main: ja panic .stage2_end_lba_ok: - mov bx, S2_ADDR + mov bx, S2_LOAD_ADDR call read_lba + add bx, S2_TEXT_OFFSET jmp bx ; Load a single boot disk sector. Panic on failure. diff --git a/stages/s3/a20.s b/stage_2/a20.s similarity index 99% rename from stages/s3/a20.s rename to stage_2/a20.s index a2e7dbc..fe5d4aa 100644 --- a/stages/s3/a20.s +++ b/stage_2/a20.s @@ -8,8 +8,6 @@ out %2, %1 %endmacro -section .s3_text - ; Check whether the A20 line is enabled. Writes to the boot sector identifier. ; Arguments: none ; Return: diff --git a/s2.ld b/stage_2/link.ld similarity index 54% rename from s2.ld rename to stage_2/link.ld index 579c2fb..b11c7e7 100644 --- a/s2.ld +++ b/stage_2/link.ld @@ -3,25 +3,11 @@ OUTPUT_FORMAT("binary") . = 0x8200; SECTIONS { - /* Stage 2 must come first so it's in the single sector loaded by stage 1. */ - .s2_text : { - KEEP(*(.s2_text)) - *(.s2_text) + /* Prelude must come first so it's in the single sector loaded by stage 1. */ + .prelude : { + *(.prelude) } - .s3_text : { - KEEP(*(.s3_text)) - *(.s3_text) - } - - .s3_data : { - KEEP(*(.s3_data)) - *(.s3_data) - } - - /* TODO: set current address for s4 loadpoint */ - /* TODO: move magic & length */ - .text : { *(.text) *(.text.*) @@ -47,11 +33,11 @@ SECTIONS { LONG(0x544e4150) } - s234_magic = ADDR(.magic); + s2_magic = ADDR(.magic); /* Define a symbol for the total length of the binary, so the prelude knows how many blocks to * load from disk. */ - s234_bin_len = . - 0x8200; - s234_bin_sectors = (s234_bin_len + 511) / 512; + s2_bin_len = . - 0x8200; + s2_bin_sectors = (s2_bin_len + 511) / 512; } diff --git a/stages/s3/s3.s b/stage_2/main.s similarity index 98% rename from stages/s3/s3.s rename to stage_2/main.s index 19a8739..2cbfa8d 100644 --- a/stages/s3/s3.s +++ b/stage_2/main.s @@ -6,11 +6,8 @@ extern test_a20 extern enable_a20_intel_8042 -extern _start -section .s3_text - -s3_main: +s2_main: call test_a20 test al, al jnz .a20_enabled @@ -66,7 +63,8 @@ s3_main: mov ebp, REAL_STACK_BASE mov esp, ebp - jmp _start + ;jmp _start + mov eax, 0xcafebabe .halt: hlt @@ -78,7 +76,7 @@ s3_main: ; hlt ; jmp .halt -global s3_main +global s2_main section .s3_data diff --git a/stages/s2/s2.s b/stage_2/prelude.s similarity index 83% rename from stages/s2/s2.s rename to stage_2/prelude.s index 8608318..d3644e0 100644 --- a/stages/s2/s2.s +++ b/stage_2/prelude.s @@ -4,13 +4,10 @@ %include "layout.s" %include "s1_vars.s" -extern s234_bin_len -extern s234_bin_sectors -extern s234_magic -extern s3_main -extern s234_bin_len -extern s234_bin_sectors -extern s234_magic +extern s2_bin_len +extern s2_bin_sectors +extern s2_magic +extern s2_main %macro copy_stack_var_to_globals 2 mov %1, [bp - %2] @@ -18,11 +15,22 @@ extern s234_magic %endmacro -section .s2_text +section .prelude +s2_data: +.s3_bin_offset_sectors: + dw 0 +.s3_bin_len_sectors: + dw 0 +.padding: + times (S2_DATA_LEN - 4) db 0 -; Load stages 3 and 4 into memory. -load_s234: +%if ($ - $$) != S2_DATA_LEN +%error "incorrect prelude data size" +%endif + +; Load the rest of stage 2 into memory. +prelude: ; Now that we're not doing instruction byte golf like we were in stage 1, we can afford to move ; the various stage 1 stack variables to the globals section. copy_stack_var_to_globals ax, BOOT_DRIVE @@ -56,18 +64,18 @@ load_s234: jnz panic_simple ror eax, 16 - ; Calculate the s234 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 s234 end LBA. + ; Calculate the s2 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 s2 end LBA. mov bx, ax - add bx, s234_bin_sectors + add bx, s2_bin_sectors jc panic_simple - ; Panic if the s234 end LBA is after the partition end LBA. + ; Panic if the s2 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 s234 end LBA. + ; 16-bit s2 end LBA. or edx, edx jnz .end_lba_ok - ; Compare the s234 end LBA to the lower 32 bits of the partition end LBA. + ; Compare the s2 end LBA to the lower 32 bits of the partition end LBA. cmp ebx, ecx ja panic_simple @@ -77,12 +85,12 @@ load_s234: ; current LBA. inc ax push ax ; Current LBA - push bx ; s234 end LBA - mov ebx, S2_ADDR + 512 ; Current sector load address + push bx ; s2 end LBA + mov ebx, S2_LOAD_ADDR + 512 ; Current sector load address .load_loop: mov ax, [bp - 0x02] ; Load current LBA - cmp word [bp - 0x04], ax ; Compare to s234 end LBA + cmp word [bp - 0x04], ax ; Compare to s2 end LBA jb .load_done mov ecx, ebx @@ -95,15 +103,15 @@ load_s234: .load_done: - ; Check the magic bytes at the end of s234. + ; Check the magic bytes at the end of s2. push es - mov ebx, s234_magic + mov ebx, s2_magic call addr32_to_addr16 - cmp dword es:[bx], S234_MAGIC + cmp dword es:[bx], S2_MAGIC pop es jne panic_simple - jmp s3_main + jmp s2_main ; Converts a 32-bit address to a 16-bit sector and offset. diff --git a/stages/s4/.cargo/config.toml b/stage_3/.cargo/config.toml similarity index 100% rename from stages/s4/.cargo/config.toml rename to stage_3/.cargo/config.toml diff --git a/stage_3/.gitignore b/stage_3/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/stage_3/.gitignore @@ -0,0 +1 @@ +/target diff --git a/stages/s4/Cargo.lock b/stage_3/Cargo.lock similarity index 88% rename from stages/s4/Cargo.lock rename to stage_3/Cargo.lock index 5d785a4..adc3107 100644 --- a/stages/s4/Cargo.lock +++ b/stage_3/Cargo.lock @@ -3,5 +3,5 @@ version = 4 [[package]] -name = "s4" +name = "stage_3" version = "0.1.0" diff --git a/stage_3/Cargo.toml b/stage_3/Cargo.toml new file mode 100644 index 0000000..b4564cf --- /dev/null +++ b/stage_3/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "stage_3" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/stage_3/build.rs b/stage_3/build.rs new file mode 100644 index 0000000..e81e422 --- /dev/null +++ b/stage_3/build.rs @@ -0,0 +1,213 @@ +use std::{env, ffi::{OsStr, OsString}, fmt, fs, io, path::{Path, PathBuf}, process::{self, Command}}; + +fn main() { + let out_dir = env::var_os("OUT_DIR") + .expect("OUT_DIR not set"); + let out_dir = PathBuf::from(out_dir); + + let search_path = SearchPath::load(); + + let nasm_path = search_path.search("nasm") + .next() + .expect("failed to find nasm in PATH"); + let nasm = Nasm::new(nasm_path); + + build_asm(&nasm, &out_dir); + + emit_link_args(); + + rerun_if_changed("build.rs".as_ref()); +} + +fn emit_link_args() { + let linker_script_path = "link.ld"; + println!("cargo::rustc-link-arg=-T{}", linker_script_path); + rerun_if_changed(linker_script_path.as_ref()); +} + +fn rerun_if_changed(path: &Path) { + let path_str = path.to_str() + .expect("expected path to be valid utf8"); + println!("cargo::rerun-if-changed={}", path_str); +} + +fn link_obj(path: &Path) { + let path_str = path.to_str() + .expect("expected path to be valid utf8"); + println!("cargo::rustc-link-arg={}", path_str); +} + +fn build_asm(nasm: &Nasm, out_dir: &Path) { + let asm_srcs = fs::read_dir("src/asm") + .expect("failed to get asm sources"); + + for asm_src in asm_srcs { + let asm_src = asm_src.expect("failed to get asm source"); + + let ty = asm_src.file_type().expect("failed to get file type"); + if !ty.is_file() { + continue; + } + + let src_path = asm_src.path(); + + let is_asm = src_path + .extension() + .map(|ext| ext == "s") + .unwrap_or(false); + if !is_asm { + continue; + } + + let out_filename = src_path + .file_name() + .unwrap() + .apply(PathBuf::from) + .with_extension("o"); + let out_path = out_dir.join(out_filename); + + nasm.assemble( + &out_path, + &[&src_path], + &["../include".as_ref()] + ).expect("failed to assemble"); + + link_obj(&out_path); + } +} + +struct Nasm { + bin_path: PathBuf, +} + +impl Nasm { + fn new(bin_path: PathBuf) -> Self { + Self { bin_path } + } + + fn assemble(&self, output: &Path, sources: &[&Path], includes: &[&Path]) -> Result<(), CmdError> + { + for source in sources { + rerun_if_changed(source); + } + + let mut cmd = Command::new(&self.bin_path); + cmd + .arg("-Werror") + .arg("-f") + .arg("elf"); + + for include in includes { + let mut buf = OsString::new(); + buf.push("-I"); + buf.push(include); + cmd.arg(buf); + } + + cmd + .arg("-o") + .arg(output) + .args(sources); + + run_cmd(&mut cmd)?; + + Ok(()) + } +} + +struct SearchPath { + paths: Vec +} + +impl SearchPath { + fn load() -> Self { + let path_var = env::var_os("PATH").unwrap_or_default(); + let paths = env::split_paths(&path_var).collect(); + Self { paths } + } + + fn search<'a, 'b, 'c, T>(&'a self, bin: &'b T) -> impl Iterator + 'c + where + 'a: 'c, + 'b: 'c, + T: AsRef + ?Sized, + { + let bin = bin.as_ref(); + self.paths + .iter() + .filter_map(move |path| { + let path = path.join(bin); + fs::metadata(&path) + .ok() + .and_then(|meta| if meta.is_file() || meta.is_symlink() { + Some(path) + } else { + None + }) + }) + } +} + +fn run_cmd(cmd: &mut Command) -> Result { + use fmt::Write; + + cmd + .output() + .map_err(CmdErrorKind::Io) + .and_then(|out| if out.status.success() { + Ok(out) + } else { + Err(CmdErrorKind::Status(out)) + }) + .map_err(|err| { + let mut cmd_buf = String::new(); + write!(&mut cmd_buf, "{:?}", cmd).ok(); + CmdError::new(cmd_buf, err) + }) +} + +#[derive(Debug)] +struct CmdError { + cmd: String, + kind: CmdErrorKind, +} + +impl CmdError { + fn new(cmd: String, kind: CmdErrorKind) -> Self { + Self { cmd, kind } + } +} + +impl fmt::Display for CmdError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "`{}` failed: ", self.cmd)?; + match &self.kind { + CmdErrorKind::Io(err) => err.fmt(f), + CmdErrorKind::Status(out) => write!(f, "exited with status {}", out.status), + } + } +} + +#[derive(Debug)] +enum CmdErrorKind { + Io(io::Error), + Status(process::Output), +} + +trait Apply: Sized { + fn apply(self, f: F) -> T + where + F: FnOnce(Self) -> T; +} + +impl Apply for U +where + U: Sized, +{ + fn apply(self, f: F) -> T + where + F: FnOnce(Self) -> T + { + f(self) + } +} diff --git a/stage_3/link.ld b/stage_3/link.ld new file mode 100644 index 0000000..a8338cb --- /dev/null +++ b/stage_3/link.ld @@ -0,0 +1,25 @@ +OUTPUT_FORMAT("binary") + +. = 0x10000; + +SECTIONS { + .text : { + *(.text) + *(.text.*) + } + + .data : { + *(.data) + *(.data.*) + } + + .bss : { + *(.bss) + *(.bss.*) + } + + .rodata : { + *(.rodata) + *(.rodata.*) + } +} diff --git a/stages/s4/protected32.json b/stage_3/protected32.json similarity index 100% rename from stages/s4/protected32.json rename to stage_3/protected32.json diff --git a/stage_3/src/asm/bios_call.s b/stage_3/src/asm/bios_call.s new file mode 100644 index 0000000..f54f209 --- /dev/null +++ b/stage_3/src/asm/bios_call.s @@ -0,0 +1,8 @@ +section .text + +[bits 32] + +; TODO: reference sysv abi spec to see what we can mangle and what we must preserve +load_sector: + ret + diff --git a/stages/s4/src/lib.rs b/stage_3/src/main.rs similarity index 96% rename from stages/s4/src/lib.rs rename to stage_3/src/main.rs index b92e860..1f31a91 100644 --- a/stages/s4/src/lib.rs +++ b/stage_3/src/main.rs @@ -19,7 +19,7 @@ fn panic(info: &PanicInfo) -> ! { hlt() } -#[no_mangle] +#[unsafe(no_mangle)] pub extern "C" fn _start() -> ! { vga::vga_init(); diff --git a/stages/s4/src/spin.rs b/stage_3/src/spin.rs similarity index 100% rename from stages/s4/src/spin.rs rename to stage_3/src/spin.rs diff --git a/stages/s4/src/vga.rs b/stage_3/src/vga.rs similarity index 100% rename from stages/s4/src/vga.rs rename to stage_3/src/vga.rs diff --git a/stages/s4/src/x86.rs b/stage_3/src/x86.rs similarity index 100% rename from stages/s4/src/x86.rs rename to stage_3/src/x86.rs diff --git a/stages/s2/s2.o b/stages/s2/s2.o deleted file mode 100644 index 09adf8d7a7d92c9eb78775fb1b6ee98eaf8805ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1264 zcmb7ET}TvB6h5;qv#yytl!-_|f`ma#xr!h`jZpg}DQF;+45K@D-I|?|nOP_z%1UzD zsJG~SUV5yEFrskTqG&TpC<+N8DApgMLdwG2&K-C3W)O7P`}Z{T(rm zFN%3xIiNTrDuFE;`Y0pI#xnf9c^U%d-OC$o`HE6`KMbUuv>ni%c-s+nGNdK`TM zP6m;$!$GrvV>1r6-*Xg;So6d0*MNnvU&QZ!2>S7%6k|)X?|C#~6slkw8Q$B~exN6mPERit4>})s(nXxtBwGkB?`QL5 z3|AGYx-?F*?}B7|j%4#fcq&~`jH(^ zE$5OpRc*ya|HnVVf1lJ6gfB(bN^kb^J@%^{>_K|jmpI>z{3@dp$Zs?HBJycQUqfE8 zvMC2oQ152+4C)R>(>v)0<@LV6O!ba6`UL7{rJ8`}yN{gSa~f2?#Vj!zEL%ya)=n#m zKhXj!+T0@dCw19U2W-PE6;etfIRI9(oKliHXoeD(sd1HwsveiMenmEpGDXj*8OG4s zTHMf8P~vg38DFL$Q`Xe_S3$JYL`v0d+H|d1BuY{PQ>7>IPRer8RPD5>!;qpU2V^Um i8q(B1ggB^Zw)adJflWMqH=Mh0dE1doBi0V-hvrZpH?8JJ*7Nuoh!f-oCYmjIA66UhVtD4QKf zGb4#_0IE|&;)Bcp0YM-}cF6;v!~&og3lMWaX^@$cK#Xi&15ncfpco9m%!BdMLen~* z@~=oc&ib#GfgyBN+K&JK`(C6O9!O1VwB@S<3Gq)oP%58xkQbzY117vG6etW6e9geW zGA;J-3oD?h8`eHcd0_}*TmUlkfs7{!9S0juGQE%i30?#W3IiD(C!Qs|-~x%P1B!rL zb@)lj3Z~LuAd0c%*I}Rt80eLxR+KR46=xKe6qO_<0a=x~5E{aVsl!1d`TFvXS|PK>j2o`8G5@D4t+I0BF7zkmCat0Pz`$OA^yli}j0* z^%IQ@^okiuQj1ICflN3;%|lMk?{~iL<-B|L>x~T|1Q`*OrWqru zgn_be^E8k6{|NVFO@+lTK_0~#nKLh($~#0LAIqB$@e*v}CTP4F%QOA1z-P|n@KcZq zg6DuQLg%1a=rw4(-v}gcnjmK24dC-oBNH?zf^Z+sVas>C1C8hXLV*1o=lhtvqZ9Ad zWN#5BKvsy7xv_hWrhJ=7k}r+tpQ0L~%Tb1)E#Tnou~X0x{|_1ZBIqqFf4@Ld^d5l6 z6G)0a0pvo)lmD9FQ*Txq^x^kngk#qYfn|O-!3FInc#2$b^G35c?>NDy9=EL&5jt>rV@($cZJEfvuS6dcv*slJx2u-X(P z1GjFUt?=K;X00hV8r7z3)E`qs9@Mtv{U^0LU+1h7%@WR9wONg(j`7jTLNN1{#bBqI XUt&KNocDSeyIf?8#pB%*u|@v}qEp3n diff --git a/stages/s4/Cargo.toml b/stages/s4/Cargo.toml deleted file mode 100644 index 2b66a9b..0000000 --- a/stages/s4/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "s4" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["staticlib"] - -[profile.release] -opt-level = "s" -debug = 0 - -[dependencies] - diff --git a/xtask/.gitignore b/xtask/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/xtask/.gitignore @@ -0,0 +1 @@ +/target diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 0000000..e240aec --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 0000000..378bdb8 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,83 @@ +use std::{env, error::Error, fmt, path::{Path, PathBuf}, process::ExitCode}; + +mod mkimg; + +fn main() -> ExitCode { + match xtask() { + Ok(()) => ExitCode::SUCCESS, + Err(err) => { + eprintln!("{}", err); + ExitCode::FAILURE + } + } +} + +fn xtask() -> Result<(), XtaskError> { + let mut args = env::args_os().skip(1); + + let task = args + .next() + .ok_or_else(|| XtaskError::with_message("no task provided".to_owned()))?; + + let task = task.to_str().ok_or_else(|| { + XtaskError::with_message(format!("invalid utf-8 \"{}\"", task.to_string_lossy())) + })?; + + let manifest_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").ok_or_else(|| { + XtaskError::with_message("CARGO_MANIFEST_DIR not set".to_owned()) + })?); + + let workspace_dir = manifest_dir.parent().ok_or_else(|| { + XtaskError::with_message("invalid CARGO_MANIFEST_DIR".to_owned()) + })?; + + let ctx = Context { + workspace: workspace_dir, + }; + + match task { + "mkimg" => mkimg::mkimg_bios_gpt(ctx), + _ => Err(XtaskError::with_message(format!("unknown task \"{}\"", task))), + } +} + +struct Context<'a> { + workspace: &'a Path, +} + +#[derive(Debug)] +struct XtaskError { + message: String, + parent: Option>, +} + +impl XtaskError { + fn with_message(message: String) -> Self { + Self { + message, + parent: None, + } + } + + fn wrap_with_message(err: E, message: String) -> Self + where + E: Error + 'static, + { + Self { + message, + parent: Some(Box::new(err)), + } + } +} + +impl fmt::Display for XtaskError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message)?; + if let Some(parent) = &self.parent { + write!(f, ": {}", parent)?; + } + Ok(()) + } +} + +impl Error for XtaskError {} diff --git a/xtask/src/mkimg.rs b/xtask/src/mkimg.rs new file mode 100644 index 0000000..b0f65d8 --- /dev/null +++ b/xtask/src/mkimg.rs @@ -0,0 +1,32 @@ +use crate::{Context, XtaskError}; + +const BLOCK_SIZE: u64 = 512; +const PARTITION_ENTRY_SIZE: u64 = 128; + +const ATTR_REQUIRED: u64 = 1 >> 0; +const ATTR_NO_BLOCK_IO_PROTO: u64 = 1 >> 1; +const ATTR_LEGACY_BIOS_BOOTABLE: u64 = 1 >> 2; + +// LBA 0: MBR +// LBA 1: partition header +// LBA 2..33: partition table entries +// LBA 34..n: usable blocks +// LBA -33..-2: partition table entries (dup) +// LBA -1: partition header (dup) + +struct Partition { + type_guid: [u8; 16], + part_guid: [u8; 16], + lba_start: u64, + lba_end: u64, + attr: u64, + name: String, +} + + + +pub fn mkimg_bios_gpt(ctx: Context) -> Result<(), XtaskError> { + println!("dir={}", ctx.workspace.to_string_lossy()); + + Ok(()) +}