]> git.dujemihanovic.xyz Git - nameless-os.git/commitdiff
WIP: Add new FAT32 bootloader
authorDuje Mihanović <duje.mihanovic@skole.hr>
Wed, 27 Apr 2022 15:47:44 +0000 (17:47 +0200)
committerDuje Mihanović <duje.mihanovic@skole.hr>
Wed, 27 Apr 2022 15:58:47 +0000 (17:58 +0200)
Loads a LOADER.BIN which currently prints a hello message.

TODO: Make it actually load the kernel.

14 files changed:
Makefile
boot/x86/.gitignore [new file with mode: 0644]
boot/x86/Makefile [new file with mode: 0644]
boot/x86/disk.dump [new file with mode: 0644]
boot/x86/fat32-structs.s [new file with mode: 0644]
boot/x86/fat32.s [new file with mode: 0644]
boot/x86/loader.s [new file with mode: 0644]
boot/x86/mbr.s [new file with mode: 0644]
boot/x86/old/a20.s [moved from boot/x86/a20.s with 100% similarity]
boot/x86/old/boot.s [moved from boot/x86/boot.s with 100% similarity]
boot/x86/old/print.s [moved from boot/x86/print.s with 100% similarity]
boot/x86/old/protected.s [moved from boot/x86/protected.s with 100% similarity]
boot/x86/vbr-fat32.s [new file with mode: 0644]
how-to-compile

index 848c7959b14d9a1d439b3980c83cf7c6dedc0ce4..d3f2ee4be49cbe23808b4d7813da6965cb51ed2e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
-AS = yasm
-LD = i686-elf-ld
-CC = i686-elf-gcc
+export AS = yasm
+export LD = i686-elf-ld
+export CC = i686-elf-gcc
+QEMU = qemu-system-i386
 
 GIT_REV = $(shell git rev-parse --short HEAD)
 
@@ -8,14 +9,15 @@ CFLAGS = -std=gnu89 -g -Iinclude/arch/x86 -ffreestanding -DGIT_COMMIT=\"$(GIT_RE
 
 KERNEL_OBJ = kernel/entry.o kernel/arch/x86/tty/tty.o kernel/drivers/irq/i8259a.o kernel/arch/x86/irq/idt.o kernel/arch/x86/irq/sample_handler.o kernel/kernel.o
 
-all: boot.img kernel/kernel.elf
+default: kernel/kernel.elf
 
-boot.img: boot/x86/boot kernel/kernel.bin
-       cat boot/x86/boot kernel/kernel.bin > $@
-       truncate -s1440K $@
+all: default boot/x86/disk.img
 
-boot/x86/boot: boot/x86/boot.s boot/x86/a20.s boot/x86/protected.s boot/x86/print.s
-       $(AS) -f bin boot/x86/boot.s -o $@
+run: boot/x86/disk.img
+       $(QEMU) boot/x86/disk.img
+
+boot/x86/disk.img: boot/x86/mbr.s boot/x86/vbr-fat32.s boot/x86/loader.s boot/x86/disk.dump boot/x86/fat32.s boot/x86/fat32-structs.s
+       cd boot/x86 && $(MAKE) all
 
 kernel/kernel.bin: ${KERNEL_OBJ}
        $(LD) -o $@ -T kernel/linker.ld ${KERNEL_OBJ}
@@ -38,6 +40,7 @@ kernel/kernel.elf: kernel/kernel.bin
        $(LD) -o $@ -T kernel/linker.ld ${KERNEL_OBJ} --oformat=elf32-i386
 
 clean:
-       rm boot/x86/boot kernel/kernel.bin kernel/kernel.elf ${KERNEL_OBJ} boot.img
+       -rm kernel/kernel.bin kernel/kernel.elf ${KERNEL_OBJ}
+       cd boot/x86 && $(MAKE) clean
 
-.PHONY: all clean
+.PHONY: default all clean run
diff --git a/boot/x86/.gitignore b/boot/x86/.gitignore
new file mode 100644 (file)
index 0000000..ef679a9
--- /dev/null
@@ -0,0 +1,4 @@
+mbr
+vbr-fat32
+LOADER.BIN
+disk.img
diff --git a/boot/x86/Makefile b/boot/x86/Makefile
new file mode 100644 (file)
index 0000000..0349d2a
--- /dev/null
@@ -0,0 +1,25 @@
+default: mbr vbr-fat32 LOADER.BIN
+
+all: mbr vbr-fat32 LOADER.BIN disk.img
+
+mbr: mbr.s
+       $(AS) $(ASFLAGS) -w-zeroing -o $@ $<
+
+vbr-fat32: vbr-fat32.s fat32.s fat32-structs.s
+       $(AS) $(ASFLAGS) -o $@ $<
+
+LOADER.BIN: loader.s
+       $(AS) $(ASFLAGS) -o $@ $<
+
+disk.img: mbr vbr-fat32 LOADER.BIN disk.dump
+       truncate -s100M disk.img
+       sfdisk disk.img < disk.dump
+       mkfs.fat -F 32 --offset 2048 disk.img
+       dd if=mbr of=disk.img bs=440 count=1 conv=notrunc
+       dd if=vbr-fat32 of=disk.img bs=1 skip=90 seek=1048666 conv=notrunc
+       mcopy -i disk.img@@1M LOADER.BIN ::.
+
+clean:
+       -rm mbr vbr-fat32 LOADER.BIN disk.img
+
+.PHONY : default all clean
diff --git a/boot/x86/disk.dump b/boot/x86/disk.dump
new file mode 100644 (file)
index 0000000..2210fd2
--- /dev/null
@@ -0,0 +1,7 @@
+label: dos
+label-id: 0xc0c03566
+device: disk.img
+unit: sectors
+sector-size: 512
+
+disk.img1 : start=        2048, size=      202752, type=b, bootable
diff --git a/boot/x86/fat32-structs.s b/boot/x86/fat32-structs.s
new file mode 100644 (file)
index 0000000..23980ba
--- /dev/null
@@ -0,0 +1,56 @@
+; FAT32 data structures
+; Because they have to be defined before use
+
+struc part_entry
+       .attrib: resb 1
+       .chs_start: resb 3
+       .type: resb 1
+       .chs_end: resb 3
+       .lba_start: resd 1
+       .lba_end: resd 1
+endstruc
+
+struc dir_entry
+       .name: resb 11
+       .attr: resb 1
+       .ntres: resb 1
+       .crttimetenth: resb 1
+       .crttime: resw 1
+       .crtdate: resw 1
+       .lstaccdate: resw 1
+       .firstclushi: resw 1
+       .wrttime: resw 1
+       .wrtdate: resw 1
+       .firstcluslo: resw 1
+       .filesize: resd 1
+endstruc
+
+; BPB definition, we use offsets to bp to save space
+       %define BS_jmpBoot [bp]
+       %define BS_OemName [bp+0x3]
+       %define BPB_BytsPerSec [bp+0xb]
+       %define BPB_SecPerClus [bp+0xd]
+       %define BPB_RsvdSecCnt [bp+0xe]
+       %define BPB_NumFats [bp+0x10]
+       %define BPB_RootEntCnt [bp+0x11]
+       %define BPB_TotSec16 [bp+0x13]
+       %define BPB_Media [bp+0x15]
+       %define BPB_FatSz16 [bp+0x16] ; count of sectors
+       %define BPB_SecPerTrk [bp+0x18]
+       %define BPB_NumHeads [bp+0x1a]
+       %define BPB_HiddSec [bp+0x1c]
+       %define BPB_TotSec32 [bp+0x20]
+       %define BPB_FatSz32 [bp+0x24]
+       %define BPB_ExtFlags [bp+0x28]
+       %define BPB_FSVer [bp+0x2a]
+       %define BPB_RootClus [bp+0x2c]
+       %define BPB_FsInfo [bp+0x30]
+       %define BPB_BkBootSec [bp+0x32] ; number of sector
+       %define BPB_Reserved [bp+0x34]
+       %define BS_DrvNum [bp+0x40]
+       %define BS_Reserved1 [bp+0x41]
+       %define BS_BootSig [bp+0x42]
+       %define BS_VolId [bp+0x43]
+       %define BS_VolLab [bp+0x47]
+       %define BS_FilSysType [bp+0x52]
+
diff --git a/boot/x86/fat32.s b/boot/x86/fat32.s
new file mode 100644 (file)
index 0000000..31288cb
--- /dev/null
@@ -0,0 +1,124 @@
+; FAT32 driver
+; BOOT_DRIVE, part_table_entry and bp must be set up by the code using this driver.
+; first_data_sec is to be set up by calling get_1st_data_sec.
+
+; eax - start cluster number
+; es:di - where to load the cluster chain
+; NOTE: Cluster chain might be the root directory or a file.
+read_cluster_chain:
+       pusha
+.loop:
+       ; get the first sector of the cluster to read
+       push eax
+       sub eax, 2
+       xor ebx, ebx
+       mov bl, BPB_SecPerClus
+       mul ebx
+       add eax, [first_data_sec]
+       mov ebx, eax
+       pop eax
+
+       ; read the cluster
+       movzx cx, BPB_SecPerClus
+       call read_sectors
+
+       ; increment the load offset (and segment if needed)
+       push eax
+       xor eax, eax
+       movzx ax, BPB_SecPerClus
+       mul word BPB_BytsPerSec
+       add di, ax
+       jno .get_next_cluster
+       mov ax, es
+       add ax, 0x1000
+       mov es, ax
+
+.get_next_cluster:
+       pop eax
+       ; get FAT sector number and offset containing the next cluster number
+       xor edx, edx
+       mov ebx, 4
+       mul dword ebx
+       movzx ebx, word BPB_BytsPerSec
+       div dword ebx
+       movzx ebx, word BPB_RsvdSecCnt
+       add eax, ebx
+       push bp
+       mov bp, [part_table_entry]
+       add eax, [bp+part_entry.lba_start]
+       pop bp
+       mov ebx, edx
+       ; reminder for myself: EAX is FAT sector number, (E)BX is offset into FAT sector
+       
+       ; load the FAT sector we're looking for
+
+       push ebx ; offset
+       push es ; we want to read at 0:1000, not STAGE3_SEGMENT:1000
+       push eax ; desired LBA
+       xor ax, ax
+       mov es, ax
+       pop ebx ; pop LBA into EBX
+       mov di, 0x1000
+       mov cx, 1
+       call read_sectors
+       
+       ; find the cluster we're looking for
+       pop es ; restore STAGE3_SEGMENT
+       pop ebx ; pop FAT offset back into EBX for cmp
+       cmp dword [di+bx], 0xffffff7
+       jge .done ; if cluster number is greater than or equal to the defective cluster value, we're done
+                 ; TODO: should this perhaps be changed so that equal makes it error out?
+       ; otherwise, load the next sector number in eax and read again
+       mov eax, [di+bx]
+       jmp .loop
+.done:
+       ; cleanup and return
+       popa
+       ret
+
+; es:di - where to load the sector(s)
+; ebx - start LBA address
+; cx - how many sectors to read
+read_sectors:
+       pusha
+       mov ah, 0x42
+       push dword 0
+       push dword ebx
+       push es
+       push di
+       push cx
+       push word 0x10
+       mov si, sp
+       mov dl, [BOOT_DRIVE]
+       int 0x13
+       jc .error
+       add sp, 16
+       popa
+       ret
+       .error:
+               mov si, read_error
+               call print
+               hlt
+               jmp $-1
+
+; no arguments
+get_1st_data_sec:
+       push eax
+       push ebx
+       xor eax, eax
+       movzx ax, BPB_NumFats 
+       mul dword BPB_FatSz32 ; space occupied by all FATs
+       movzx ebx, word BPB_RsvdSecCnt
+       add eax, ebx ; space occupied by the reserved area
+       mov bx, [part_table_entry]
+       add eax, [bx+part_entry.lba_start] ; space before the partition
+       mov [first_data_sec], eax
+       pop ebx
+       pop eax
+       ret
+
+read_error: db "Read error", 0
+
+BOOT_DRIVE: db 0
+part_table_entry: dw 0
+first_data_sec: dd 0
diff --git a/boot/x86/loader.s b/boot/x86/loader.s
new file mode 100644 (file)
index 0000000..e18b87e
--- /dev/null
@@ -0,0 +1,31 @@
+bits 16
+cpu 686
+org 0x0
+
+%macro print 1
+
+       mov si, %1
+       call print_str
+       
+%endmacro
+
+_start:
+       print string
+       hlt
+       jmp $-1
+       
+print_str:
+       pusha
+       mov ah, 0xe
+       xor bh, bh
+.loop:
+       lodsb
+       cmp al, 0
+       je .done
+       int 0x10
+       jmp .loop
+.done:
+       popa
+       ret
+
+string: db "Hello from LOADER.BIN!", 0xd, 0xa, 0
diff --git a/boot/x86/mbr.s b/boot/x86/mbr.s
new file mode 100644 (file)
index 0000000..8089b2f
--- /dev/null
@@ -0,0 +1,185 @@
+; x86 bootloader for nameless-os, MBR portion
+
+bits 16
+cpu 686
+org 0x600
+
+_start:
+       cli
+       ; set up segment registers
+       xor ax, ax
+       mov ds, ax
+       mov es, ax
+       mov ss, ax
+       mov sp, 0x7c00 ; just under the soon-to-be-loaded VBR, should be more than sufficient space
+
+       ; perform self-relocation
+       cld
+       mov si, 0x7c00
+       mov di, 0x600
+       mov cx, 0x100 ; 256 words = 512 bytes
+       rep movsw
+       ; some BIOSes may set CS to 0x7c0, work around that
+       jmp 0:real_start
+real_start:
+       sti
+       ; check for int 13h ext
+       call check_int13_ext
+       mov byte [BOOT_DRIVE], dl
+       ; look for active partition
+       mov bx, part_1
+       mov cx, 4
+       .check_part_loop:
+               mov al, [bx]
+               test al, 0x80
+               jnz .part_found
+               add bx, 16
+               loop .check_part_loop
+               .error:
+                       mov si, no_os
+                       call print
+                       cli
+                       hlt
+                       jmp short $-1
+       .part_found:
+               ; look for any other active partitions, if they exist the partition table is invalid
+               cmp cx, 0
+               je .load_vbr
+               push bx
+       .look_other_loop:
+               add bx, 16
+               mov al, [bx]
+               test al, 0x80
+               jnz .invalid_mbr
+               loop .look_other_loop
+       .load_vbr:
+               ; load active partition's VBR
+               pop bx
+               mov ax, 0x7c0
+               mov edx, [bx+0x8]
+               push bx ; save pointer to partition table entry
+               xor bx, bx
+               call read_sector
+               ; check is the VBR bootable (ends with 0x55 0xaa), if not halt
+               cmp word [0x7dfe], 0xaa55
+               jne .not_bootable
+               mov dl, [BOOT_DRIVE]
+               pop si ; hand off partition table entry to VBR
+               jmp 0x7c00
+               .not_bootable:
+                       mov si, no_os
+                       call print
+                       cli
+                       hlt
+                       jmp short $-1
+       .invalid_mbr:
+               mov si, invalid_mbr
+               call print
+               cli
+               hlt
+               jmp short $-1
+check_int13_ext:
+       pusha
+       mov ah, 0x41
+       mov bx, 0x55aa
+       mov dl, 0x80
+       int 0x13
+       jc .no_ext
+       test cx, 1
+       jz .no_ext
+       popa
+       ret
+       .no_ext:
+               mov si, no_int13_ext
+               call print
+               cli
+               hlt
+               jmp short $-1
+
+; ax = segment
+; bx = offset
+; edx = start LBA of where to read
+read_sector:
+       pusha
+       ; set up temporary DAP
+       push dword 0
+       push edx
+       push ax
+       push bx
+       push word 1
+       push word 0x10
+       mov ah, 0x42
+       mov dl, byte [BOOT_DRIVE]
+       mov si, sp
+       int 0x13
+       jc .error
+       add sp, 16 ; dump our temporary DAP
+       popa
+       ret
+       .error:
+               mov si, read_fail
+               call print
+               cli
+               hlt
+               jmp short $-1
+
+; DS:SI = string
+print:
+       pusha
+       mov ah, 0xe
+       xor bh, bh
+.loop:
+       lodsb
+       cmp al, 0
+       je .done
+       int 10h
+       jmp .loop
+.done:
+       mov al, 0xd
+       int 10h
+       mov al, 0xa
+       int 10h
+       popa
+       ret
+
+BOOT_DRIVE: resb 1
+
+read_fail: db "Error reading stage 2", 0
+invalid_mbr: db "Invalid partition table", 0
+no_os: db "No OS found", 0
+no_int13_ext: db "INT 13h extensions not found", 0
+
+times 440-($-$$) db 0
+
+part_table:
+disk_id: dd 0
+reserved: dw 0
+part_1:
+       .attrib db 0
+       .chs_start times 3 db 0
+       .part_type db 0
+       .chs_end times 3 db 0
+       .lba_start dd 0
+       .sect_count dd 0
+part_2:
+       .attrib db 0
+       .chs_start times 3 db 0
+       .part_type db 0
+       .chs_end times 3 db 0
+       .lba_start dd 0
+       .sect_count dd 0
+part_3:
+       .attrib db 0
+       .chs_start times 3 db 0
+       .part_type db 0
+       .chs_end times 3 db 0
+       .lba_start dd 0
+       .sect_count dd 0
+part_4:
+       .attrib db 0
+       .chs_start times 3 db 0
+       .part_type db 0
+       .chs_end times 3 db 0
+       .lba_start dd 0
+       .sect_count dd 0
+signature: dw 0xaa55
similarity index 100%
rename from boot/x86/a20.s
rename to boot/x86/old/a20.s
similarity index 100%
rename from boot/x86/boot.s
rename to boot/x86/old/boot.s
similarity index 100%
rename from boot/x86/print.s
rename to boot/x86/old/print.s
diff --git a/boot/x86/vbr-fat32.s b/boot/x86/vbr-fat32.s
new file mode 100644 (file)
index 0000000..061ca52
--- /dev/null
@@ -0,0 +1,102 @@
+; x86 bootloader for nameless-os, FAT32 VBR portion
+; This is what's going to be on most USB sticks and HDDs, for now
+
+bits 16
+org 0x7c00
+cpu 686
+
+STAGE3_ADDRESS equ 0x8000
+STAGE3_SEGMENT equ STAGE3_ADDRESS >> 4
+STAGE3_OFFSET equ STAGE3_ADDRESS & 0xf
+
+%include "fat32-structs.s"
+
+_start:
+       jmp short real_start
+       nop
+
+times 0x57 db 0 ; skip past BPB
+
+real_start:
+       sti
+       ; no need to set up segments and stack again, because MBR did it for us
+       mov bp, 0x7c00
+
+       ; we expect the boot drive to be in DL and our partition table entry in DS:SI
+       mov [BOOT_DRIVE], dl
+       mov [part_table_entry], si
+
+       ; calculate the 1st sector of the data area
+       call get_1st_data_sec
+
+.load_root:
+       ; load the root directory
+       mov ax, 0x1000
+       mov es, ax
+       xor di, di
+       mov eax, BPB_RootClus
+       call read_cluster_chain
+
+       mov cx, 11
+.find_stage_3:
+       mov si, STAGE3_NAME
+       cmp byte [es:di], 0 ; we have no more entries to look at
+       je .stage3_missing
+       cmp byte [es:di], 0xe5 ; the entry was only previously used and as such not worth looking at
+       je .increment
+       repe cmpsb
+       je .stage3_found
+.increment:
+       add di, 32
+       jno .find_stage_3
+       mov bx, es
+       add bx, 0x1000
+       mov es, bx
+.stage3_found:
+       sub di, 0xb ; "repe cmpsb" incremented this
+       ; stage 3 has been found and ES:DI points to its directory entry
+       mov ax, [es:di+dir_entry.firstclushi] ; load high half of the 1st cluster
+       shl eax, 16
+       mov ax, [es:di+dir_entry.firstcluslo] ; load low half of the 1st cluster
+       mov bx, STAGE3_SEGMENT
+       mov es, bx
+       mov di, STAGE3_OFFSET
+       call read_cluster_chain ; read stage 3
+       mov ds, bx
+       call STAGE3_SEGMENT:STAGE3_OFFSET ; call stage 3
+       jmp .halt ; halt in case we return, which should never happen
+
+.stage3_missing:
+       mov si, stage3_missing
+       call print
+.halt:
+       cli
+       hlt
+       jmp short $-1
+
+; ds:si - string
+print:
+       pusha
+       mov ah, 0xe
+       xor bh, bh
+.loop:
+       lodsb
+       cmp al, 0
+       je .done
+       int 0x10
+       jmp .loop
+.done:
+       mov al, 0xd
+       int 0x10
+       mov al, 0xa
+       int 0x10
+       popa
+       ret
+
+%include "fat32.s"
+
+stage3_missing: db "LOADER.BIN is missing", 0
+STAGE3_NAME: db "LOADER  BIN"
+
+times 510-($-$$) db 0
+dw 0xaa55
index bcd0b8b626b9301b08ac23d78e98a0083021101a..c84edb4816ea45d339ca3f2f919bc623f35bc0b7 100644 (file)
@@ -1,10 +1,11 @@
 1. Compile an i686-elf GCC and binutils following the instructions on this page:
    https://wiki.osdev.org/GCC_Cross-Compiler
+   Or use some tool to automate it, like I use crossdev on my Gentoo machine.
 
 2. Install yasm, or modify the makefile to use nasm if you prefer it
 
 3. Run make in the source root
 
-4. Run boot.img with some emulator, I use qemu:
-   qemu-system-i386 boot.img
-   You can use the x86_64 qemu, Bochs, VirtualBox or something else if you prefer
+4. Run the OS:
+   make run
+   This will create a disk image (if needed) and run it. Assumes that sfdisk, mtools and dosfstools are installed.