1
0
This commit is contained in:
Mark 2025-03-02 22:05:01 -08:00
parent cbf6c48b22
commit 5b34321456
4 changed files with 86 additions and 161 deletions

View File

@ -2,7 +2,7 @@ BUILD=./build
# Default rule # Default rule
.PHONY: default .PHONY: default
default: all default: $(BUILD)/disk.img
# Remove all build files # Remove all build files
.PHONY: clean .PHONY: clean
@ -10,16 +10,11 @@ clean:
rm -drf $(BUILD) rm -drf $(BUILD)
cd tetros; cargo clean 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) LIB_SRC = ./tetros/Cargo.toml ./tetros/Cargo.lock $(shell find ./tetros/src -type f)
$(BUILD)/tetros.lib: $(LIB_SRC) $(BUILD)/tetros.lib: $(LIB_SRC)
@mkdir -p $(BUILD) @mkdir -p $(BUILD)
@ -35,7 +30,7 @@ $(BUILD)/tetros.lib: $(LIB_SRC)
-- \ -- \
--emit link="$(CURDIR)/$@" --emit link="$(CURDIR)/$@"
# Link tetros # Link tetros using custom linker script
BIOS_LD = ./tetros/linkers/x86-unknown-none.ld BIOS_LD = ./tetros/linkers/x86-unknown-none.ld
$(BUILD)/tetros.elf: $(BUILD)/tetros.lib $(BIOS_LD) $(BUILD)/tetros.elf: $(BUILD)/tetros.lib $(BIOS_LD)
ld \ ld \
@ -49,13 +44,13 @@ $(BUILD)/tetros.elf: $(BUILD)/tetros.lib $(BIOS_LD)
objcopy --only-keep-debug "$@" "$@.sym" objcopy --only-keep-debug "$@" "$@.sym"
objcopy --strip-debug "$@" objcopy --strip-debug "$@"
# Wrap tetros in three-stage BIOS loader # Wrap tetros in BIOS loader
# Parameters: # Parameters:
# - BIOS_SRC: source directory of bios assembly # - BIOS_SRC: source directory of bios assembly
# - STAGE2_SECTOR: the index of the first sector of the stage 2 binary on the disk # - STAGE2_SECTOR: the index of the first sector of the stage 2 binary on the disk
BIOS_SRC = ./bios BIOS_SRC = ./bios
STAGE2_SECTOR = 1 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)" @mkdir -p "$(BUILD)"
nasm \ nasm \
-f bin \ -f bin \
@ -66,30 +61,12 @@ $(BUILD)/bios.bin: $(wildcard $(BIOS_SRC)/*.asm) $(BUILD)/tetros.elf
-i "$(BIOS_SRC)" \ -i "$(BIOS_SRC)" \
"$(BIOS_SRC)/main.asm" "$(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 .PHONY: qemu
qemu: $(BUILD)/disk.img qemu: $(BUILD)/disk.img
@ -129,5 +106,3 @@ qemu-gdb: $(BUILD)/disk.img
-gdb tcp::26000 \ -gdb tcp::26000 \
-S -S
# Do not use `-enable-kvm` or `-cpu host`,
# this confuses gdb.

View File

@ -4,27 +4,22 @@ sectalign off
; STAGE3, a path to the stage3 binary ; STAGE3, a path to the stage3 binary
; STAGE2_SECTOR, the location of stage 2 ; STAGE2_SECTOR, the location of stage 2
; on the disk, in 512-byte sectors. ; 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 ORG 0x7C00
SECTION .text 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" %include "stage1.asm"
; Stage 1 is at most 440 bytes ; 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 times 440-($-$$) db 0
db 0xee db 0xee
@ -32,30 +27,39 @@ db 0xee
times 510-($-$$) db 0 times 510-($-$$) db 0
; MBR signature. ; MBR signature.
; This isn't loaded into memory, it's ; This tells the BIOS that this disk is bootable.
; only here for debugging.
db 0x55 db 0x55
db 0xaa 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: stage2:
%include "stage2.asm" %include "stage2.asm"
align 512, db 0 align 512, db 0
stage2.end: 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. ; 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 times (0x9000 - 0x7c00)-($-$$) db 0
; Include stage 3, the binary compiled from Rust sources.
stage3: stage3:
%defstr STAGE3_STR %[STAGE3] %defstr STAGE3_STR %[STAGE3]
incbin STAGE3_STR incbin STAGE3_STR
align 512, db 0 align 512, db 0
.end: .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

View File

@ -1,22 +1,21 @@
SECTION .text SECTION .text
USE16 USE16
; provide function for printing in x86 real mode ; Print a string and a newline
;
; print a string and a newline ; Clobbers ax
; CLOBBER
; ax
print_line: print_line:
mov al, 13 mov al, 13
call print_char call print_char
mov al, 10 mov al, 10
jmp print_char jmp print_char
; print a string ; Print a string
; IN ;
; Input:
; si: points at zero-terminated String ; si: points at zero-terminated String
; CLOBBER ;
; si, ax ; Clobbers si, ax
print: print:
pushf pushf
cld cld
@ -30,8 +29,9 @@ print:
popf popf
ret ret
; print a character ; Print a character
; IN ;
; Input:
; al: character to print ; al: character to print
print_char: print_char:
pusha pusha
@ -42,10 +42,11 @@ print_char:
ret ret
; print a number in hex ; print a number in hex
; IN ;
; Input:
; bx: the number ; bx: the number
; CLOBBER ;
; al, cx ; Clobbers al, cx
print_hex: print_hex:
mov cx, 4 mov cx, 4
.lp: .lp:

View File

@ -1,37 +1,39 @@
USE16 USE16
stage1: ; dl comes with disk stage1:
; initialize segment registers ; Initialize segment registers
xor ax, ax xor ax, ax ; Set ax to 0
mov ds, ax mov ds, ax
mov es, ax mov es, ax
mov ss, ax mov ss, ax
; initialize stack ; Initialize stack pointer
; (stack grows up)
mov sp, 0x7C00 mov sp, 0x7C00
; initialize CS ; 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. ; `retf` sets both CS and IP to a known-good state.
; (could be 0x00:0x7C00, could be 0x7C00:0x00. ; This is necessary because we don't know where the BIOS put us at startup.
; Not everybody follows spec.) ; (could be 0x00:0x7C00, could be 0x7C00:0x00. Not everybody follows spec.)
push ax push ax ; `ax` is still 0
push word .set_cs push word .set_cs
retf retf
.set_cs: .set_cs:
; save disk number ; Save disk number.
; BIOS sets `dl` to the number of
; the disk we're booting from.
mov [disk], dl mov [disk], dl
; Print "Stage 1"
mov si, stage_msg mov si, stage_msg
call print call print
mov al, '1' mov al, '1'
call print_char call print_char
call print_line call print_line
; read CHS gemotry, save into [chs]
; read CHS gemotry
; CL (bits 0-5) = maximum sector number ; CL (bits 0-5) = maximum sector number
; CL (bits 6-7) = high bits of max cylinder number ; CL (bits 6-7) = high bits of max cylinder number
; CH = low bits of maximum cylinder number ; CH = low bits of maximum cylinder number
@ -51,11 +53,10 @@ stage1: ; dl comes with disk
and cl, 0x3f and cl, 0x3f
mov [chs.s], cl mov [chs.s], cl
; disk address of stage 2 ; First sector of stage 2
; (start sector)
mov eax, STAGE2_SECTOR mov eax, STAGE2_SECTOR
; where to load stage 2 ; Where to load stage 2
mov bx, stage2 mov bx, stage2
; length of stage2 + stage3 ; length of stage2 + stage3
@ -63,21 +64,22 @@ stage1: ; dl comes with disk
mov cx, (stage3.end - stage2) / 512 mov cx, (stage3.end - stage2) / 512
mov dx, 0 mov dx, 0
; Consume eax, bx, cx, dx
; and load code from disk.
call load call load
jmp stage2.entry jmp stage2.entry
; load some sectors from disk to a buffer in memory ; Load sectors from disk to memory.
; buffer has to be below 1MiB ; Cannot load more than 1MiB.
; IN ;
; Input:
; ax: start sector ; ax: start sector
; bx: offset of buffer ; bx: offset of buffer
; cx: number of sectors (512 Bytes each) ; cx: number of sectors (512 Bytes each)
; dx: segment of buffer ; dx: segment of buffer
; CLOBBER ;
; ax, bx, cx, dx, si ; Clobbers 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)
load: load:
; replaced 127 with 1. ; replaced 127 with 1.
; see https://stackoverflow.com/questions/58564895/problem-with-bios-int-13h-read-sectors-from-drive ; 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.count], cx
mov [DAPACK.seg], dx mov [DAPACK.seg], dx
; This should be a subroutine, ; Print the data we're reading
; but we don't call/ret to save a few bytes. ; TODO: decode
; (we only use this once)
;
;call print_dapack
;print_dapack:
mov bx, [DAPACK.addr + 2] mov bx, [DAPACK.addr + 2]
call print_hex call print_hex
mov bx, [DAPACK.addr] mov bx, [DAPACK.addr]
call print_hex call print_hex
mov al, '#' mov al, '#'
call print_char call print_char
mov bx, [DAPACK.count] mov bx, [DAPACK.count]
call print_hex call print_hex
mov al, ' ' mov al, ' '
call print_char call print_char
mov bx, [DAPACK.seg] mov bx, [DAPACK.seg]
call print_hex call print_hex
mov al, ':' mov al, ':'
call print_char call print_char
mov bx, [DAPACK.buf] mov bx, [DAPACK.buf]
call print_hex call print_hex
call print_line call print_line
;ret
; End of print_dapack
cmp byte [chs.s], 0 ; Read from disk.
jne .chs ;
;INT 0x13 extended read does not work on CDROM! ; 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 dl, [disk]
mov si, DAPACK mov si, DAPACK
mov ah, 0x42 mov ah, 0x42
@ -146,51 +136,6 @@ load:
jc error ; carry flag set on error jc error ; carry flag set on error
ret 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: error:
call print_line call print_line