Loads a LOADER.BIN which currently prints a hello message.
TODO: Make it actually load the kernel.
-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)
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}
$(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
--- /dev/null
+mbr
+vbr-fat32
+LOADER.BIN
+disk.img
--- /dev/null
+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
--- /dev/null
+label: dos
+label-id: 0xc0c03566
+device: disk.img
+unit: sectors
+sector-size: 512
+
+disk.img1 : start= 2048, size= 202752, type=b, bootable
--- /dev/null
+; 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]
+
--- /dev/null
+; 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
--- /dev/null
+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
--- /dev/null
+; 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
--- /dev/null
+; 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
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.