diff --git a/boot0.s b/boot0.s index 70522ce..a71c5d3 100644 --- a/boot0.s +++ b/boot0.s @@ -3,7 +3,8 @@ ; -------------------------------------------------------------------- ; R | 0x000000 - 0x000400: real-mode interrupt vector table ; R | 0x000400 - 0x000500: bios data area -; U | 0x000500 - 0x007c00: stack +; U | 0x000500 - 0x007000: stack +; U | 0x007000 - 0x007c00: globals ; U | 0x007c00 - 0x007e00: boot sector ; U | 0x007e00 - 0x080000: conventional usable memory ; R | 0x080000 - 0x0a0000: extended bios data area (maximum possible size) @@ -12,33 +13,12 @@ ; R | 0x0c8000 - 0x0f0000: bios expansiosn ; R | 0x0f0000 - 0x100000: motherboard bios +%include "defines.s" + ; BIOS puts our boot sector at 0000:7c00 -org 0x7c00 +[org BOOT0_LOADPOINT] ; We're (probably) in real mode -bits 16 - -; BASE STACK FRAME VARIABLE OFFSETS -; -------------------------------------------------------------------- -; The boot drive number given to us by the BIOS. -BOOT_DRIVE equ 0x02 -; Boot drive geometry -SECTORS_PER_TRACK equ 0x04 -N_HEADS equ 0x06 -; Starting LBA of the GPT partition entries array. -GPT_ENTRIES_START_LBA equ 0x08 -; Number of GPT entries, saturated to 16 bits. -GPT_N_ENTRIES_16 equ 0x0a -; Number of sectors to advance by once we've read every GPT entry in the current sector. -GPT_SECTOR_STRIDE equ 0x0c -; Number of bytes to advance by in the current sector once we've read a GPT entry. -GPT_BYTE_STRIDE equ 0x0e -; Number of GPT entries which can fit in a single sector. -GPT_ENTRIES_PER_SECTOR equ 0x10 -GPT_CURRENT_ENTRY_IDX equ 0x12 -GPT_SECTOR_ENTRY_IDX equ 0x14 -GPT_SECTORS_LOADED equ 0x16 -GPT_CURRENT_LBA equ 0x18 -STAGE2_GPT_ENTRY_ADDR equ 0x1a +[bits 16] main: ; Disable interrupts @@ -49,10 +29,10 @@ main: mov ds, ax mov es, ax - ; Put the stack base at 0x7c00. - ; Stack grows high->low, so we'll grow away from our program text at 0x7c00. + ; Put the stack base at 0x7000. + ; Stack grows high->low, so we'll grow away from our globals and program text. mov ss, ax - mov bp, 0x7c00 + mov bp, STACK_BASE mov sp, bp ; Segment for VGA (0xb800 * 16 = 0xb8000) @@ -237,7 +217,7 @@ main: ja panic .stage2_end_lba_ok: - mov bx, 0x8200 + mov bx, BOOT1_LOADPOINT call read_lba jmp bx @@ -252,8 +232,6 @@ read_lba: ; head = temp % n_heads ; cylinder = temp / n_heads xor dx, dx - ; FIXME: we should probably get our globals from somewhere else, in case we want to change bp for - ; a function call ; 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) diff --git a/boot1.s b/boot1.s index a9a7f74..86f9365 100644 --- a/boot1.s +++ b/boot1.s @@ -1,11 +1,103 @@ -org 0x8200 -bits 16 +%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 + ror eax, 16 + or ax, ax + jnz panic + 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 + + ; 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 + +.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 + + add ebx, 512 + inc word [bp - 0x02] + jmp .self_load_loop + +.self_load_done: + + mov ebx, cafebabe + call addr32_to_addr16 + mov eax, es:[ebx] + hlt + + call test_a20 + add al, 0x30 + mov byte fs:[0x0000], al + hlt + +pr_womble: ; Reveal the true nature of jen xor bx, bx -loop: +.loop: cmp bx, WOMBLE_LEN - jae done + jae .done lea si, [womble + bx] mov dh, [si] mov di, bx @@ -13,9 +105,159 @@ loop: mov byte fs:[di], dh mov byte fs:[di + 0x01], 0x1f inc bx - jmp loop -done: + jmp .loop +.done: hlt + +; 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: + push bp + mov bp, sp + 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 + pop bp + stc + ret + + +; 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 + + push bp + mov bp, sp + 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 + pop bp + ret + + +%if ($ - $$) > 512 +%error "boot1 self-loader exceeded sector size" +%endif + + +test_a20: + 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 + ret + + +panic: + mov ax, 0x0003 + int 0x10 + mov word fs:[0x0000], 0x4f21 + hlt + + womble db "jen is a womble!" WOMBLE_LEN equ $ - womble + + times 409600 db 0 +cafebabe: + dd 0xfeedface + +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/defines.s b/defines.s new file mode 100644 index 0000000..39a3b3f --- /dev/null +++ b/defines.s @@ -0,0 +1,31 @@ +%define BOOT0_LOADPOINT 0x7c00 +%define BOOT1_LOADPOINT 0x8200 + +%define GLOBALS 0x7000 +%define STACK_BASE GLOBALS + +%define EBDA_START 0x080000 + +; boot0 base stack frame variable offsets / globals +; (we use the same offsets once we copy the variables to the globals section) +; ------------------------------------------------------------------------------------------------- +; The boot drive number given to us by the BIOS. +%define BOOT_DRIVE 0x02 +; Boot drive geometry +%define SECTORS_PER_TRACK 0x04 +%define N_HEADS 0x06 +; Starting LBA of the GPT partition entries array. +%define GPT_ENTRIES_START_LBA 0x08 +; Number of GPT entries, saturated to 16 bits. +%define GPT_N_ENTRIES_16 0x0a +; Number of sectors to advance by once we've read every GPT entry in the current sector. +%define GPT_SECTOR_STRIDE 0x0c +; Number of bytes to advance by in the current sector once we've read a GPT entry. +%define GPT_BYTE_STRIDE 0x0e +; Number of GPT entries which can fit in a single sector. +%define GPT_ENTRIES_PER_SECTOR 0x10 +%define GPT_CURRENT_ENTRY_IDX 0x12 +%define GPT_SECTOR_ENTRY_IDX 0x14 +%define GPT_SECTORS_LOADED 0x16 +%define GPT_CURRENT_LBA 0x18 +%define BOOT1_GPT_ENTRY_ADDR 0x1a diff --git a/justfile b/justfile index f95e1ff..07fdf6f 100644 --- a/justfile +++ b/justfile @@ -1,26 +1,30 @@ run: - qemu-system-x86_64 -monitor stdio -drive format=raw,file=disk.bin + qemu-system-x86_64 \ + -monitor stdio \ + -bios seabios/out/bios.bin \ + -drive format=raw,file=disk.bin build: nasm -f bin -o boot0.bin boot0.s nasm -f bin -o boot1.bin boot1.s zero_disk: - dd if=/dev/zero of=disk.bin bs=64K count=16 + dd if=/dev/zero of=disk.bin bs=512 count=1000 partition_disk: parted --script disk.bin mklabel gpt parted --script disk.bin mkpart dummy1 34s 47s - parted --script disk.bin type 1 E3C9E316-0B5C-4DB8-817D-F92DF00215AE + parted --script disk.bin type 1 e3c9e316-0b5c-4db8-817d-f92df00215ae parted --script disk.bin mkpart dummy2 64s 65s - parted --script disk.bin type 2 E3C9E316-0B5C-4DB8-817D-F92DF00215AE + parted --script disk.bin type 2 e3c9e316-0b5c-4db8-817d-f92df00215ae parted --script disk.bin mkpart dummy3 66s 67s - parted --script disk.bin type 3 E3C9E316-0B5C-4DB8-817D-F92DF00215AE + parted --script disk.bin type 3 e3c9e316-0b5c-4db8-817d-f92df00215ae parted --script disk.bin mkpart dummy4 68s 69s - parted --script disk.bin type 4 E3C9E316-0B5C-4DB8-817D-F92DF00215AE + parted --script disk.bin type 4 e3c9e316-0b5c-4db8-817d-f92df00215ae parted --script disk.bin mkpart dummy5 48s 63s - parted --script disk.bin type 5 0FC63DAF-8483-4772-8E79-3D69D8477DE4 - parted --script disk.bin mkpart stage2 70s 200s + parted --script disk.bin type 5 0fc63daf-8483-4772-8e79-3d69d8477de4 + # parted --script disk.bin mkpart stage2 70s 200s + parted --script disk.bin mkpart stage2 70s 900s parted --script disk.bin type 6 fdffea69-3651-442f-a11d-88a09bf372dd write_stage1: