diff --git a/Makefile b/Makefile index 45f5b6c..52cc1ae 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ BUILD=./build # Default rule .PHONY: default -default: all +default: $(BUILD)/disk.img # Remove all build files .PHONY: clean @@ -10,16 +10,11 @@ clean: rm -drf $(BUILD) cd tetros; cargo clean -# Make everything -# (but don't run qemu) -.PHONY: all -all: img - # -# MARK: boot +# MARK: disk # -# Compile tetros as library +# Compile tetros LIB_SRC = ./tetros/Cargo.toml ./tetros/Cargo.lock $(shell find ./tetros/src -type f) $(BUILD)/tetros.lib: $(LIB_SRC) @mkdir -p $(BUILD) @@ -35,7 +30,7 @@ $(BUILD)/tetros.lib: $(LIB_SRC) -- \ --emit link="$(CURDIR)/$@" -# Link tetros +# Link tetros using custom linker script BIOS_LD = ./tetros/linkers/x86-unknown-none.ld $(BUILD)/tetros.elf: $(BUILD)/tetros.lib $(BIOS_LD) ld \ @@ -49,13 +44,13 @@ $(BUILD)/tetros.elf: $(BUILD)/tetros.lib $(BIOS_LD) objcopy --only-keep-debug "$@" "$@.sym" objcopy --strip-debug "$@" -# Wrap tetros in three-stage BIOS loader +# Wrap tetros in BIOS loader # Parameters: # - BIOS_SRC: source directory of bios assembly # - STAGE2_SECTOR: the index of the first sector of the stage 2 binary on the disk BIOS_SRC = ./bios STAGE2_SECTOR = 1 -$(BUILD)/bios.bin: $(wildcard $(BIOS_SRC)/*.asm) $(BUILD)/tetros.elf +$(BUILD)/disk.img: $(wildcard $(BIOS_SRC)/*.asm) $(BUILD)/tetros.elf @mkdir -p "$(BUILD)" nasm \ -f bin \ @@ -66,30 +61,12 @@ $(BUILD)/bios.bin: $(wildcard $(BIOS_SRC)/*.asm) $(BUILD)/tetros.elf -i "$(BIOS_SRC)" \ "$(BIOS_SRC)/main.asm" -# Extract full mbr (first 512 bytes) -$(BUILD)/mbr.bin: $(BUILD)/bios.bin - @mkdir -p "$(BUILD)" - @echo "" - dd if="$<" bs=512 count=1 of="$@" - -# Extract stage 2 (rest of file) -$(BUILD)/stage2.bin: $(BUILD)/bios.bin - @mkdir -p "$(BUILD)" - @echo "" - dd if="$<" bs=512 skip=1 of="$@" - # -# MARK: bundle +# MARK: qemu +# +# Do not use `-enable-kvm` or `-cpu host` +# this confuses gdb. # - -# Make full disk image -.PHONY: img -img: $(BUILD)/disk.img -$(BUILD)/disk.img: $(BUILD)/mbr.bin $(BUILD)/stage2.bin - @mkdir -p $(BUILD) - @echo "" - dd if="$(BUILD)/mbr.bin" of=$@ conv=notrunc bs=512 - dd if="$(BUILD)/stage2.bin" of=$@ conv=notrunc seek=$(STAGE2_SECTOR) bs=512 .PHONY: qemu qemu: $(BUILD)/disk.img @@ -129,5 +106,3 @@ qemu-gdb: $(BUILD)/disk.img -gdb tcp::26000 \ -S -# Do not use `-enable-kvm` or `-cpu host`, -# this confuses gdb. \ No newline at end of file diff --git a/bios/main.asm b/bios/main.asm index 50c62ba..1952ec1 100644 --- a/bios/main.asm +++ b/bios/main.asm @@ -4,27 +4,22 @@ sectalign off ; STAGE3, a path to the stage3 binary ; STAGE2_SECTOR, the location of stage 2 ; on the disk, in 512-byte sectors. -; On a gpt disk, this is probably 34. -; Stage 1 is MBR code, and should fit in LBA 0 -; (512 bytes). Layout is as follows: -; (Format is `offset, length: purpose`) -; 0, 424: x86 boot code -; 440, 4: Unique disk signature -; 444, 2: unknown -; 446, 16*4: Array of four legacy MBR records -; 510, 2: signature 0x55 0xAA -; 512 to end of logical block: reserved -; -; See https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html + +; BIOS loads stage 1 at 0x7C00 ORG 0x7C00 SECTION .text -; stage 1 is sector 0, loaded into memory at 0x7C00 +; Stage 1 is MBR code, and should fit in LBA 0 +; (i.e, in the first 512 bytes). %include "stage1.asm" ; Stage 1 is at most 440 bytes +; This limit is set by the GPT spec. +; See https://uefi.org/specs/UEFI/2.10/05_GUID_Partition_Table_Format.html +; +; This `times` will throw an error if the subtraction is negative. times 440-($-$$) db 0 db 0xee @@ -32,30 +27,39 @@ db 0xee times 510-($-$$) db 0 ; MBR signature. -; This isn't loaded into memory, it's -; only here for debugging. +; This tells the BIOS that this disk is bootable. db 0x55 db 0xaa + +; Include stage 2. This is loaded into memory by stage 1. +; +; Stage 2 sets up protected mode, sets up the GDT, +; and initializes a minimal environment for stage 3. +; +; On a "real" boot disk, this data will not immediately follow stage 1. +; It would be stored in a special disk partition. +; +; We don't need this kind of complexity here, though, so we store +; stage 2 right after stage 1. (This is why STAGE2_SECTOR is 1.) +; +; This is nice, because the layout of the code on our boot disk +; matches the layout of the code in memory. THIS IS NOT USUALLY THE CASE. stage2: %include "stage2.asm" align 512, db 0 stage2.end: -; The maximum size of stage2 is 4 KiB, -; This fill will throw an error if the subtraction is negative. -times (4*1024)-($-stage2) db 0 - ; Pad to 0x9000. -; This needs to match the value configured in the stage3 linker script +; This needs to match the value configured in the Stage 3 linker script +; TODO: why? can we make this smaller? times (0x9000 - 0x7c00)-($-$$) db 0 + + +; Include stage 3, the binary compiled from Rust sources. stage3: %defstr STAGE3_STR %[STAGE3] incbin STAGE3_STR align 512, db 0 .end: - -; TODO: why? Of the disk, or of memory? -; the maximum size of the boot loader portion is 384 KiB -times (384*1024)-($-$$) db 0 diff --git a/bios/print.asm b/bios/print.asm index 5e958a3..ac8e111 100644 --- a/bios/print.asm +++ b/bios/print.asm @@ -1,22 +1,21 @@ SECTION .text USE16 -; provide function for printing in x86 real mode - -; print a string and a newline -; CLOBBER -; ax +; Print a string and a newline +; +; Clobbers ax print_line: mov al, 13 call print_char mov al, 10 jmp print_char -; print a string -; IN +; Print a string +; +; Input: ; si: points at zero-terminated String -; CLOBBER -; si, ax +; +; Clobbers si, ax print: pushf cld @@ -30,8 +29,9 @@ print: popf ret -; print a character -; IN +; Print a character +; +; Input: ; al: character to print print_char: pusha @@ -42,10 +42,11 @@ print_char: ret ; print a number in hex -; IN +; +; Input: ; bx: the number -; CLOBBER -; al, cx +; +; Clobbers al, cx print_hex: mov cx, 4 .lp: diff --git a/bios/stage1.asm b/bios/stage1.asm index 5452f8e..33cc8a4 100644 --- a/bios/stage1.asm +++ b/bios/stage1.asm @@ -1,37 +1,39 @@ USE16 -stage1: ; dl comes with disk - ; initialize segment registers - xor ax, ax +stage1: + ; Initialize segment registers + xor ax, ax ; Set ax to 0 mov ds, ax mov es, ax mov ss, ax - ; initialize stack + ; Initialize stack pointer + ; (stack grows up) mov sp, 0x7C00 - ; initialize CS - ; far jump sets both CS and IP to a known-good state, - ; we don't know where the BIOS put us at startup. - ; (could be 0x00:0x7C00, could be 0x7C00:0x00. - ; Not everybody follows spec.) - push ax + ; Initialize CS + ; + ; `retf` sets both CS and IP to a known-good state. + ; This is necessary because we don't know where the BIOS put us at startup. + ; (could be 0x00:0x7C00, could be 0x7C00:0x00. Not everybody follows spec.) + push ax ; `ax` is still 0 push word .set_cs retf .set_cs: - ; save disk number + ; Save disk number. + ; BIOS sets `dl` to the number of + ; the disk we're booting from. mov [disk], dl + ; Print "Stage 1" mov si, stage_msg call print mov al, '1' call print_char call print_line - - - ; read CHS gemotry + ; read CHS gemotry, save into [chs] ; CL (bits 0-5) = maximum sector number ; CL (bits 6-7) = high bits of max cylinder number ; CH = low bits of maximum cylinder number @@ -51,11 +53,10 @@ stage1: ; dl comes with disk and cl, 0x3f mov [chs.s], cl - ; disk address of stage 2 - ; (start sector) + ; First sector of stage 2 mov eax, STAGE2_SECTOR - ; where to load stage 2 + ; Where to load stage 2 mov bx, stage2 ; length of stage2 + stage3 @@ -63,21 +64,22 @@ stage1: ; dl comes with disk mov cx, (stage3.end - stage2) / 512 mov dx, 0 + ; Consume eax, bx, cx, dx + ; and load code from disk. call load jmp stage2.entry -; load some sectors from disk to a buffer in memory -; buffer has to be below 1MiB -; IN +; Load sectors from disk to memory. +; Cannot load more than 1MiB. +; +; Input: ; ax: start sector ; bx: offset of buffer ; cx: number of sectors (512 Bytes each) ; dx: segment of buffer -; CLOBBER -; ax, bx, cx, dx, si -; TODO rewrite to (eventually) move larger parts at once -; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end) +; +; Clobbers ax, bx, cx, dx, si load: ; replaced 127 with 1. ; see https://stackoverflow.com/questions/58564895/problem-with-bios-int-13h-read-sectors-from-drive @@ -101,44 +103,32 @@ load: mov [DAPACK.count], cx mov [DAPACK.seg], dx -; This should be a subroutine, -; but we don't call/ret to save a few bytes. -; (we only use this once) -; -;call print_dapack -;print_dapack: + ; Print the data we're reading + ; TODO: decode mov bx, [DAPACK.addr + 2] call print_hex - mov bx, [DAPACK.addr] call print_hex - mov al, '#' call print_char - mov bx, [DAPACK.count] call print_hex - mov al, ' ' call print_char - mov bx, [DAPACK.seg] call print_hex - mov al, ':' call print_char - mov bx, [DAPACK.buf] call print_hex - call print_line - ;ret - ; End of print_dapack - cmp byte [chs.s], 0 - jne .chs - ;INT 0x13 extended read does not work on CDROM! + ; Read from disk. + ; + ; Note: + ; int 0x13 extended read does not work on CDROM, + ; but we don't care. We're booting from a floppy. mov dl, [disk] mov si, DAPACK mov ah, 0x42 @@ -146,51 +136,6 @@ load: jc error ; carry flag set on error ret -.chs: - ; calculate CHS - xor edx, edx - mov eax, [DAPACK.addr] - div dword [chs.s] ; divide by sectors - mov ecx, edx ; move sector remainder to ecx - xor edx, edx - div dword [chs.h] ; divide by heads - ; eax has cylinders, edx has heads, ecx has sectors - - ; Sector cannot be greater than 63 - inc ecx ; Sector is base 1 - cmp ecx, 63 - ja error_chs - - ; Head cannot be greater than 255 - cmp edx, 255 - ja error_chs - - ; Cylinder cannot be greater than 1023 - cmp eax, 1023 - ja error_chs - - ; Move CHS values to parameters - mov ch, al - shl ah, 6 - and cl, 0x3f - or cl, ah - shl dx, 8 - - ; read from disk using CHS - mov al, [DAPACK.count] - mov ah, 0x02 ; disk read (CHS) - mov bx, [DAPACK.buf] - mov dl, [disk] - push es ; save ES - mov es, [DAPACK.seg] - int 0x13 - pop es ; restore EC - jc error ; carry flag set on error - ret - -error_chs: - mov ah, 0 - error: call print_line