From: Duje Mihanović Date: Wed, 27 Apr 2022 15:47:44 +0000 (+0200) Subject: WIP: Add new FAT32 bootloader X-Git-Tag: 0.1.0~11 X-Git-Url: http://git.dujemihanovic.xyz/%22http:/www.sics.se/static/%7B%7B%20%24.Site.BaseURL%20%7D%7Dposts/%7B%7B%20%28.OutputFormats.Get?a=commitdiff_plain;h=dad65146188909b9568ed39b3b3fbc5e88e158c3;p=nameless-os.git WIP: Add new FAT32 bootloader Loads a LOADER.BIN which currently prints a hello message. TODO: Make it actually load the kernel. --- diff --git a/Makefile b/Makefile index 848c795..d3f2ee4 100644 --- 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 index 0000000..ef679a9 --- /dev/null +++ b/boot/x86/.gitignore @@ -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 index 0000000..0349d2a --- /dev/null +++ b/boot/x86/Makefile @@ -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 index 0000000..2210fd2 --- /dev/null +++ b/boot/x86/disk.dump @@ -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 index 0000000..23980ba --- /dev/null +++ b/boot/x86/fat32-structs.s @@ -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 index 0000000..31288cb --- /dev/null +++ b/boot/x86/fat32.s @@ -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 index 0000000..e18b87e --- /dev/null +++ b/boot/x86/loader.s @@ -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 index 0000000..8089b2f --- /dev/null +++ b/boot/x86/mbr.s @@ -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 diff --git a/boot/x86/a20.s b/boot/x86/old/a20.s similarity index 100% rename from boot/x86/a20.s rename to boot/x86/old/a20.s diff --git a/boot/x86/boot.s b/boot/x86/old/boot.s similarity index 100% rename from boot/x86/boot.s rename to boot/x86/old/boot.s diff --git a/boot/x86/print.s b/boot/x86/old/print.s similarity index 100% rename from boot/x86/print.s rename to boot/x86/old/print.s diff --git a/boot/x86/protected.s b/boot/x86/old/protected.s similarity index 100% rename from boot/x86/protected.s rename to boot/x86/old/protected.s diff --git a/boot/x86/vbr-fat32.s b/boot/x86/vbr-fat32.s new file mode 100644 index 0000000..061ca52 --- /dev/null +++ b/boot/x86/vbr-fat32.s @@ -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 diff --git a/how-to-compile b/how-to-compile index bcd0b8b..c84edb4 100644 --- a/how-to-compile +++ b/how-to-compile @@ -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.