]> 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)
 
 
 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
 
 
 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}
 
 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:
        $(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
 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
 
 
 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.