; MEMORY LAYOUT ; R = reserved, U = usable ; -------------------------------------------------------------------- ; R | 0x000000 - 0x000400: real-mode interrupt vector table ; R | 0x000400 - 0x000500: bios data area ; U | 0x000500 - 0x007c00: stack ; U | 0x007c00 - 0x007e00: boot sector ; U | 0x007e00 - 0x080000: conventional usable memory ; R | 0x080000 - 0x0a0000: extended bios data area (maximum possible size) ; R | 0x0a0000 - 0x0c0000: video memory ; R | 0x0c0000 - 0x0c8000: video bios ; R | 0x0c8000 - 0x0f0000: bios expansiosn ; R | 0x0f0000 - 0x100000: motherboard bios ; BASE STACK FRAME ; -------------------------------------------------------------------- ; bp - 0x02: drive number ; bp - 0x04: sectors per track ; bp - 0x06: number of heads ; bp - 0x08: partition array starting LBA ; bp - 0x0a: number of GPT partitions (saturated to 16-bits) ; bp - 0x0c: sector stride when loading GPT entries ; bp - 0x0e: byte stride when loading GPT entries ; bp - 0x10: entries per sector when loding GPT entries ; BIOS puts our boot sector at 0000:7c00 org 0x7c00 ; We're (probably) in real mode bits 16 ; Disable interrupts cli xor ax, ax mov ds, ax mov es, ax ; Put the stack base at 0x7c00. ; Stack grows high->low, so we'll grow away from our program text at 0x7c00. mov ss, ax mov bp, 0x7c00 mov sp, bp ; Segment for VGA (0xb800 * 16 = 0xb8000) mov ax, 0xb800 mov fs, ax ; Store boot drive number xor dh, dh push dx ; Get drive geometry mov di, 0x00 mov ah, 0x08 int 0x13 jc .panic ; Load sectors per track into cx & spill and cl, 0x3f xor ch, ch push cx ; Load number of heads into bx & spill movzx bx, dh inc bx push bx ; Load LBA 1. cmp cl, 1 ; Division of 1 by nonzero can be done with cmp sete al ; temp = LBA / (sectors per track) setne cl ; sector - 1 = LBA % (sectors per track) inc cl xor ah, ah ; Zero-extend temp xor dx, dx ; div by 16-bit register divides dx:ax, so we zero dx div bx ; ah = mod (head), al = div (cylinder) mov dh, ah ; Head mov ch, al ; Cylinder ; We already have sector in cl mov ah, 2 ; Read disk mov al, 1 ; Load one sector mov bx, 0x7e00 mov dl, [bp - 0x2] ; Drive number int 0x13 jc .panic ; head = temp % number of heads ; cylinder = temp / number of heads ; Set VGA mode ; https://mendelson.org/wpdos/videomodes.txt mov ax, 0x0003 int 0x10 mov cx, 8 mov si, .gpt_magic mov di, 0x7e00 repe cmpsb jne .panic ; Ensure the 8-byte GPT starting LBA fits in 16 bits mov di, 0x7e00 ; The rep increments di so we need to reset it xor bx, bx mov ax, [di + 0x4a] or bx, ax mov ax, [di + 0x4c] or bx, ax mov ax, [di + 0x4e] or bx, ax jnz .panic ; Load and spill the GPT starting LBA mov ax, [di + 0x48] push ax ; Load number of partitions mov ax, [di + 0x50] mov bx, [di + 0x52] or bx, bx jz .gpt_n_partitions_loaded ; Number of partitions overflows 16 bits, so we just concern ourselves with the first 65535. ; That's an awful lot of partitions anyway. mov ax, 0xffff .gpt_n_partitions_loaded: push ax ; Load GPT entry size mov eax, [di + 0x54] ; Operand size override otherwise this is going to be painful mov ebx, eax ; Assert that the entry size is 128 * 2^n for some integer n>=0. This is required for a valid GPT ; and has the nice properties that: ; - If each entry is larger than a sector (512 bytes), they'll be sector-aligned. ; - If each entry is smaller than a sector, an integer number of them will fit into a sector. or eax, eax ; Test size != 0 because 128 * 2^n != 0 jz .panic and eax, 127 ; Test size is a multiple of 128 jnz .panic mov eax, ebx shr eax, 7 ; Divide by 128 mov ecx, eax dec eax and eax, ecx ; Test size/128 is a power of 2. popcnt gives an exception for some reason... jnz .panic mov eax, ebx ; Find the "sector stride", which is the number of sectors we increment by each time we want to ; load a new entry. shr eax, 9 ; Divide by sector size to get sectors per entry cmp eax, 0xffff ; Make sure sectors per entry fits in 16 bits ja .panic or ax, ax jnz .gpt_sector_stride_loaded ; Sector stride must be at least one or we'll load the same sector each time! inc ax .gpt_sector_stride_loaded: push ax cmp ebx, 512 jb .gpt_find_entries_per_sector push word 0 ; Arbitrary byte stride since there's only one entry per sector push word 1 ; 1 entry per sector, since an entry is larger than a sector jmp .gpt_found_entries_per_sector .gpt_find_entries_per_sector: push bx ; Store byte stride = entry length in this case xor dx, dx mov ax, 512 div bx ; Find entries per sector push ax .gpt_found_entries_per_sector: mov al, [0x7e00] mov byte fs:[0x0000], al mov al, [0x7e01] mov byte fs:[0x0002], al mov al, [0x7e02] mov byte fs:[0x0004], al ; mov word fs:[0x0000], 0xc048 ; mov word fs:[0x0002], 0xc069 hlt .panic: mov ax, 0x0003 int 0x10 mov ax, 0xb800 mov ds, ax mov word [0x0000], 0x4f46 mov word [0x0002], 0x4f41 mov word [0x0004], 0x4f49 mov word [0x0006], 0x4f4c hlt ; TODO: enable A20 ; TODO: load a second stage ; TODO: grab all the info we can from bios interrupts and deliver it to ths OS nicely ; e.g. in a fixed memory location ; TODO: ; - Generate GPT in justfile ; - Parse global parition table ; - Load second stage from GPT partition with a particular UUID / name like GRUB does ; (it's Hah!IdontNeedEFI in GRUB) ; - https://en.wikipedia.org/wiki/BIOS_boot_partition ; - Future work: ; - Boot from UEFI ; - Boot on non-GPT partitioned disk .gpt_magic: db "EFI PART" ; MBR bootstrap field is 440 bytes long %if ($ - $$) > 440 %error "exceeded mbr bootstrap field size" %endif