From 750b9e3f093f34310a05ffbac7f6b1317ef786e7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Duje=20Mihanovi=C4=87?= Date: Fri, 6 May 2022 18:46:48 +0200 Subject: [PATCH] FAT32 bootloader boots the kernel Also did a fair share of refactoring in the makefiles. --- Makefile | 40 +++--- boot/x86/Makefile | 20 +-- boot/x86/disk.dump | 4 +- boot/x86/{ => fat32}/fat32-structs.s | 0 boot/x86/{ => fat32}/fat32.s | 32 +++-- boot/x86/loader.s | 31 ----- boot/x86/stage3/a20.s | 74 +++++++++++ boot/x86/stage3/gdt.s | 21 +++ boot/x86/stage3/loader.s | 184 +++++++++++++++++++++++++++ boot/x86/stage3/print.s | 126 ++++++++++++++++++ boot/x86/stage3/unreal.s | 40 ++++++ boot/x86/vbr-fat32.s | 21 ++- kernel/linker.ld | 2 +- 13 files changed, 512 insertions(+), 83 deletions(-) rename boot/x86/{ => fat32}/fat32-structs.s (100%) rename boot/x86/{ => fat32}/fat32.s (91%) delete mode 100644 boot/x86/loader.s create mode 100644 boot/x86/stage3/a20.s create mode 100644 boot/x86/stage3/gdt.s create mode 100644 boot/x86/stage3/loader.s create mode 100644 boot/x86/stage3/print.s create mode 100644 boot/x86/stage3/unreal.s diff --git a/Makefile b/Makefile index d3f2ee4..738fb0c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ export AS = yasm -export LD = i686-elf-ld export CC = i686-elf-gcc -QEMU = qemu-system-i386 +QEMU = qemu-system-i386 -monitor stdio GIT_REV = $(shell git rev-parse --short HEAD) @@ -9,38 +8,45 @@ 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 -default: kernel/kernel.elf +default: kernel/kernel.elf bootloader all: default boot/x86/disk.img -run: boot/x86/disk.img +bootloader: boot/x86/mbr boot/x86/vbr-fat32 boot/x86/stage3/LOADER.BIN + +run: all $(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 +boot/x86/mbr: boot/x86/mbr.s + $(MAKE) -C boot/x86 +boot/x86/vbr-fat32: boot/x86/vbr-fat32.s boot/x86/fat32/*.s + $(MAKE) -C boot/x86 +boot/x86/stage3/LOADER.BIN: boot/x86/stage3/*.s boot/x86/fat32/*.s + $(MAKE) -C boot/x86 + +boot/x86/disk.img: boot/x86/mbr boot/x86/vbr-fat32 boot/x86/stage3/LOADER.BIN boot/x86/disk.dump + truncate -s1G boot/x86/disk.img + sfdisk boot/x86/disk.img < boot/x86/disk.dump + mkfs.fat -F 32 --offset 2048 boot/x86/disk.img + dd if=boot/x86/mbr of=boot/x86/disk.img bs=440 count=1 conv=notrunc + dd if=boot/x86/vbr-fat32 of=boot/x86/disk.img bs=1 skip=90 seek=1048666 conv=notrunc + mcopy -i boot/x86/disk.img@@1M boot/x86/stage3/LOADER.BIN ::. + mcopy -i boot/x86/disk.img@@1M kernel/kernel.bin ::./KERNEL.BIN kernel/kernel.bin: ${KERNEL_OBJ} - $(LD) -o $@ -T kernel/linker.ld ${KERNEL_OBJ} + $(CC) -ffreestanding -nostdlib -o $@ -T kernel/linker.ld ${KERNEL_OBJ} kernel/entry.o: kernel/entry.s $(AS) -f elf kernel/entry.s -o $@ -kernel/arch/x86/tty/tty.o: kernel/arch/x86/tty/tty.c - -kernel/drivers/irq/i8259a.o: kernel/drivers/irq/i8259a.c - -kernel/arch/x86/irq/idt.o: kernel/arch/x86/irq/idt.c - kernel/arch/x86/irq/sample_handler.o: kernel/arch/x86/irq/sample_handler.c $(CC) $(CFLAGS) -mgeneral-regs-only -c kernel/arch/x86/irq/sample_handler.c -o $@ -kernel/kernel.o: kernel/kernel.c - kernel/kernel.elf: kernel/kernel.bin - $(LD) -o $@ -T kernel/linker.ld ${KERNEL_OBJ} --oformat=elf32-i386 + $(CC) -ffreestanding -nostdlib -o $@ -T kernel/linker.ld ${KERNEL_OBJ} -Wl,--oformat=elf32-i386 clean: -rm kernel/kernel.bin kernel/kernel.elf ${KERNEL_OBJ} cd boot/x86 && $(MAKE) clean -.PHONY: default all clean run +.PHONY: default all clean run bootloader diff --git a/boot/x86/Makefile b/boot/x86/Makefile index 0349d2a..0157801 100644 --- a/boot/x86/Makefile +++ b/boot/x86/Makefile @@ -1,25 +1,15 @@ -default: mbr vbr-fat32 LOADER.BIN - -all: mbr vbr-fat32 LOADER.BIN disk.img +default: mbr vbr-fat32 stage3/LOADER.BIN mbr: mbr.s $(AS) $(ASFLAGS) -w-zeroing -o $@ $< -vbr-fat32: vbr-fat32.s fat32.s fat32-structs.s +vbr-fat32: vbr-fat32.s fat32/*.s $(AS) $(ASFLAGS) -o $@ $< -LOADER.BIN: loader.s +stage3/LOADER.BIN: stage3/loader.s stage3/*.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 + -rm mbr vbr-fat32 stage3/LOADER.BIN disk.img -.PHONY : default all clean +.PHONY : default clean diff --git a/boot/x86/disk.dump b/boot/x86/disk.dump index 2210fd2..5a0329b 100644 --- a/boot/x86/disk.dump +++ b/boot/x86/disk.dump @@ -1,7 +1,7 @@ label: dos -label-id: 0xc0c03566 +label-id: 0x8bb0766f device: disk.img unit: sectors sector-size: 512 -disk.img1 : start= 2048, size= 202752, type=b, bootable +disk.img1 : start= 2048, size= 2095104, type=b, bootable diff --git a/boot/x86/fat32-structs.s b/boot/x86/fat32/fat32-structs.s similarity index 100% rename from boot/x86/fat32-structs.s rename to boot/x86/fat32/fat32-structs.s diff --git a/boot/x86/fat32.s b/boot/x86/fat32/fat32.s similarity index 91% rename from boot/x86/fat32.s rename to boot/x86/fat32/fat32.s index 75e7904..df5dd89 100644 --- a/boot/x86/fat32.s +++ b/boot/x86/fat32/fat32.s @@ -7,6 +7,7 @@ ; eax - start cluster number ; es:di - where to load the cluster chain +; RETURN: CF set = error ; NOTE: Cluster chain might be the root directory or a file. read_cluster_chain: push eax @@ -14,8 +15,10 @@ read_cluster_chain: push di .loop: call read_cluster + jc .done cmp eax, 0xffffff7 jl .loop +.done: pop di pop es pop eax @@ -24,6 +27,7 @@ read_cluster_chain: ; eax - cluster number ; es:di - where to load the cluster (incremented automatically) ; RETURN: eax - next cluster number +; CF set = error read_cluster: push ebx push ecx @@ -44,6 +48,8 @@ read_cluster: movzx cx, BPB_SecPerClus call read_sectors pop cx + cmp dl, 0 + je .error ; increment the load offset (and segment if needed) push eax @@ -78,16 +84,20 @@ read_cluster: xor ax, ax mov es, ax pop ebx ; pop LBA into EBX - mov di, 0x1000 + mov di, 0x6000 mov cx, 1 call read_sectors - + cmp dl, 0 ; find the cluster we're looking for pop es ; restore STAGE3_SEGMENT pop ebx ; pop FAT offset back into EBX for cmp mov eax, [di+bx] pop di + jne .end ; cleanup and return +.error: + stc +.end: pop edx pop ecx pop ebx @@ -96,8 +106,11 @@ read_cluster: ; es:di - where to load the sector(s) ; ebx - start LBA address ; cx - how many sectors to read +; RETURN: DL=0 means error read_sectors: - pusha + clc + push ax + push si mov ah, 0x42 push dword 0 push dword ebx @@ -108,15 +121,14 @@ read_sectors: mov si, sp mov dl, [BOOT_DRIVE] int 0x13 - jc .error + mov dl, 1 + jnc .done + mov dl, 0 +.done: add sp, 16 - popa + pop si + pop ax ret - .error: - mov si, read_error - call print - hlt - jmp $-1 ; ds:si - pointer to partition table entry ; RETURN: ecx - first data sector diff --git a/boot/x86/loader.s b/boot/x86/loader.s deleted file mode 100644 index e18b87e..0000000 --- a/boot/x86/loader.s +++ /dev/null @@ -1,31 +0,0 @@ -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/stage3/a20.s b/boot/x86/stage3/a20.s new file mode 100644 index 0000000..8748dde --- /dev/null +++ b/boot/x86/stage3/a20.s @@ -0,0 +1,74 @@ +%macro in_wait 0 + push ax +%%loop: + in al, 0x64 + test al, 2 + jnz %%loop + pop ax +%endmacro + +%macro out_wait 0 + push ax +%%loop: + in al, 0x64 + test al, 1 + jz %%loop + pop ax +%endmacro + +; CF set = A20 enabled +check_a20: + push esi + push edi + push ax + mov esi, 0x500 + mov edi, 0x100500 + + mov al, [esi] + mov ah, [edi] + + mov [esi], byte 0 + mov [edi], byte 0xff + cmp [esi], byte 0xff + + mov [esi], al + mov [edi], ah + + pop ax + pop edi + pop esi + ret + +enable_a20: + push ax + mov ax, 0x2401 + int 0x15 + jc .i8042_method + call check_a20 + jnc .i8042_method + pop ax + ret + .i8042_method: + in_wait + mov al, 0xad + out 0x64, al + in_wait + mov al, 0xd0 + out 0x64, al + out_wait + in al, 0x60 + or al, 2 + push ax + in_wait + mov al, 0xd1 + out 0x64, al + in_wait + pop ax + out 0x60, al + in_wait + mov al, 0xae + out 0x60, al + call check_a20 + pop ax + ret + diff --git a/boot/x86/stage3/gdt.s b/boot/x86/stage3/gdt.s new file mode 100644 index 0000000..d81bb64 --- /dev/null +++ b/boot/x86/stage3/gdt.s @@ -0,0 +1,21 @@ +; Initial GDT for kernel, which wants a flat code and data descriptor +gdt: + dw .end-.start-1 + dd .start +.start: dq 0 ; null descriptor +.code: + dw 0xffff + dw 0 + db 0 + db 10011010b + db 11001111b + db 0 +.data: + dw 0xffff + dw 0 + db 0 + db 10010010b + db 11001111b + db 0 +.end: + diff --git a/boot/x86/stage3/loader.s b/boot/x86/stage3/loader.s new file mode 100644 index 0000000..bd703ba --- /dev/null +++ b/boot/x86/stage3/loader.s @@ -0,0 +1,184 @@ +bits 16 +cpu 686 +org 0x8000 + +%include "../fat32/fat32-structs.s" + +%macro print 1 + push si + mov si, %1 + call print_str + pop si +%endmacro + +_start: + mov [BOOT_DRIVE], dl + call enable_unreal + print string + call check_a20 + jc .a20_enabled + call enable_a20 + jnc .a20_enable_fail +.a20_enabled: + print a20_enabled + call get_1st_data_sec + mov ax, 0x2000 + mov es, ax + mov eax, BPB_RootClus + xor di, di + call read_cluster_chain + jc critical_error + + push cx + push si +.find_kernel: + mov si, kernel_name + cmp byte [es:di], 0 ; end of root directory + je .kernel_missing + cmp byte [es:di], 0xe5 ; unused entry + je .increment + mov cx, 11 + push si + push di + repe cmpsb + pop di + pop si + je .kernel_found +.increment: + add di, 32 + jno .find_kernel + mov bx, es + add bx, 0x1000 + mov es, bx + jmp .find_kernel +.kernel_found: + pop si + pop cx + print kernel_found + mov ax, [es:di+dir_entry.firstclushi] + shl eax, 16 + mov ax, [es:di+(dir_entry.firstcluslo)] + call print_dword + mov edi, 0x100000 + call read_clus_chain_unreal ; load kernel + + cli + lgdt [gdt] + mov eax, cr0 + or al, 1 + mov cr0, eax + jmp 0x8:in_protected +.kernel_missing: + print missing_kernel + jmp .halt + +.a20_enable_fail: + print a20_fail +.halt: + hlt + jmp $-1 + +; eax - start cluster +; edi - address to load to +read_clus_chain_unreal: + push eax + push edi +.loop: + push di + push es + push ax + xor di, di + mov ax, 0x100 + mov es, ax + pop ax + call read_cluster + pop es + pop di + jc .done + push esi + push eax + push ebx + push ecx + mov esi, 0x1000 + xor ebx, ebx + movzx eax, word BPB_BytsPerSec + movzx bx, byte BPB_SecPerClus + mul ebx + mov ecx, eax + call memcpy + add edi, ecx + pop ecx + pop ebx + pop eax + pop esi + cmp eax, 0xffffff7 + jl .loop +.done: + pop edi + pop eax + ret + +; esi - copy from +; edi - copy to +; ecx - bytes to copy +memcpy: + push esi + push edi + push ecx + push dx + +.loop: + mov dl, [esi] + mov [edi], dl + dec ecx + cmp ecx, 0 + je .done + inc esi + inc edi + jmp .loop +.done: + pop dx + pop ecx + pop edi + pop esi + ret + + +%include "unreal.s" +%include "a20.s" +%include "../fat32/fat32.s" +%include "gdt.s" +%include "print.s" + +in_protected: +bits 32 + mov ax, 0x10 + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + call 0x100000 + hlt + jmp $-1 + +kernel_name: db "KERNEL BIN" +string: db "Hello from LOADER.BIN!", 0xd, 0xa, 0 +a20_enabled: db "A20 is enabled", 0xd, 0xa, 0 +a20_fail: db "Failed to enable A20, giving up!", 0xd, 0xa, 0 +crit_err: db "A critical error occurred, dumping registers now: ", 0xd, 0xa, 0 +kernel_found: db "Found kernel at cluster ", 0 +missing_kernel: db "Could not find KERNEL.BIN", 0xd, 0xa, 0 +eax_s: db "EAX: ", 0 +ebx_s: db "EBX: ", 0 +ecx_s: db "ECX: ", 0 +edx_s: db "EDX: ", 0 +esi_s: db "ESI: ", 0 +edi_s: db "EDI: ", 0 +cs_s: db "CS: ", 0 +ds_s: db "DS: ", 0 +es_s: db "ES: ", 0 +ss_s: db "SS: ", 0 +space: db " ", 0 +hex_delm: db "0x", 0 +newline: db 0xd, 0xa, 0 diff --git a/boot/x86/stage3/print.s b/boot/x86/stage3/print.s new file mode 100644 index 0000000..73b34d5 --- /dev/null +++ b/boot/x86/stage3/print.s @@ -0,0 +1,126 @@ +; Routines for printing + +print_str: + push ax + push bx + push si + mov ah, 0xe + xor bh, bh + mov bl, 0xf +.loop: + lodsb + cmp al, 0 + je .done + int 0x10 + jmp .loop +.done: + pop si + pop bx + pop ax + ret + +; AL - nibble to print +; CF - clear prints low nibble, set prints high nibble +print_nibble: + print hex_delm +.no_delm: + pushf + push ax + push bx + pushf + xor bh, bh + mov bl, 0xf + mov ah, 0xe + popf + jnc .no_shift + shr al, 4 +.no_shift: + and al, 0xf + cmp al, 0xa + jge .letter + .numeric: + add al, '0' + int 0x10 + jmp .done + .letter: + add al, 'a'-10 + int 0x10 +.done: + pop bx + pop ax + popf + ret + +; AL - byte to print +print_byte: + print hex_delm +.no_delm: + pushf + stc + call print_nibble.no_delm + clc + call print_nibble.no_delm + popf + ret + +print_word: + print hex_delm +.no_delm: + ror ax, 8 + call print_byte.no_delm + ror ax, 8 + call print_byte.no_delm + ret + +print_dword: + print hex_delm +.no_delm: + ror eax, 16 + call print_word.no_delm + ror eax, 16 + call print_word.no_delm + ret + +; Do a dump of all general purpose regs +critical_error: + print crit_err + print eax_s + call print_dword + print space + print ebx_s + mov eax, ebx + call print_dword + print space + print ecx_s + mov eax, ecx + call print_dword + print space + print edx_s + mov eax, edx + call print_dword + print newline + print esi_s + mov eax, esi + call print_dword + print space + print edi_s + mov eax, edi + call print_dword + print newline + print cs_s + mov ax, cs + call print_word + print space + print ds_s + mov ax, ds + call print_word + print space + print es_s + mov ax, es + call print_word + print space + print ss_s + mov ax, ss + call print_word + hlt + jmp $-1 diff --git a/boot/x86/stage3/unreal.s b/boot/x86/stage3/unreal.s new file mode 100644 index 0000000..cdd361b --- /dev/null +++ b/boot/x86/stage3/unreal.s @@ -0,0 +1,40 @@ +; Routine for enabling unreal mode, which allows using 32-bit offsets in real mode + +enable_unreal: + cli + push eax + push bx + push ds + + lgdt [gdt_info] + mov eax, cr0 + or al, 1 + mov cr0, eax + + jmp $+2 + + mov bx, 0x8 + mov ds, bx + + and al, 0xfe + mov cr0, eax + + pop ds + pop bx + pop eax + sti + ret + +; GDT with 1 flat data segment descriptor +gdt_info: + dw gdt_end-gdt_start-1 + dd gdt_start +gdt_start: dq 0 ; null entry +gdt_flat: + dw 0xffff + dw 0 + db 0 + db 10010010b + db 11001111b + db 0 +gdt_end: diff --git a/boot/x86/vbr-fat32.s b/boot/x86/vbr-fat32.s index b4c7148..7ec3739 100644 --- a/boot/x86/vbr-fat32.s +++ b/boot/x86/vbr-fat32.s @@ -9,7 +9,12 @@ STAGE3_ADDRESS equ 0x8000 STAGE3_SEGMENT equ STAGE3_ADDRESS >> 4 STAGE3_OFFSET equ STAGE3_ADDRESS & 0xf -%include "fat32-structs.s" +%include "fat32/fat32-structs.s" + +%macro print 1 + mov si, %1 + call print_str +%endmacro _start: jmp short real_start @@ -66,19 +71,21 @@ real_start: 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 + mov dl, [BOOT_DRIVE] + call STAGE3_ADDRESS ; call stage 3 jmp .halt ; halt in case we return, which should never happen .stage3_missing: - mov si, stage3_missing - call print + print stage3_missing + jmp .halt +.error: + print read_error .halt: hlt jmp short $-1 ; ds:si - string -print: +print_str: pusha mov ah, 0xe xor bh, bh @@ -96,7 +103,7 @@ print: popa ret -%include "fat32.s" +%include "fat32/fat32.s" stage3_missing: db "LOADER.BIN is missing", 0 STAGE3_NAME: db "LOADER BIN" diff --git a/kernel/linker.ld b/kernel/linker.ld index d68e9f2..90292e6 100644 --- a/kernel/linker.ld +++ b/kernel/linker.ld @@ -3,7 +3,7 @@ OUTPUT_FORMAT(binary) SECTIONS { - . = 0x1000; + . = 0x100000; .text : { *(.text) } .data : { *(.data) } -- 2.39.2