cpu 8086 org 0x7c00 jmp short _code nop %ifdef F1440 ; 1440K floppy ; BPB oemidentifier db "nor86 " byterpersector dw 512 sectorspercluster db 1 reservedsectors dw 1 fats db 2 rootdirentries dw 224 totalsectors dw 2880 mediadescription db 0xf0 sectorsperfat dw 9 sectorspertrack dw 18 heads dw 2 hiddensectors dd 0 totalsectorslarge dd 0 %elifdef F360 ; 360K floppy ; BPB oemidentifier db "nor86 " byterpersector dw 512 sectorspercluster db 2 reservedsectors dw 1 fats db 2 rootdirentries dw 112 totalsectors dw 720 mediadescription db 0xfd sectorsperfat dw 2 sectorspertrack dw 9 heads dw 2 hiddensectors dd 0 totalsectorslarge dd 0 %else %error "No valid floppy format specified, specify -d F1440 or -d F360" %endif ; EBPB drivenumber db 0 ; useless on-disk, used as a variable reserved db 0 ; winnt flags signature db 0x29 ; mkdosfs uses this, dunno how 0x28 differs serial dd 0 volumelabel db "nor86 boot " fstype db "FAT12 " _code: jmp 0:_start _start: cld ; Set up segments mov ax, cs mov ds, ax mov es, ax cli mov ss, ax mov sp, 0x7c00 sti ; Save bootdrive mov [drivenumber], dl calc_constants: ; Disk organization: ; Reserved sectors (MBR) ; FAT1 ; FAT2 ; Root dir ; → Root dir starts at LBA reservedsectors + fats*sectorsperfat xor ah, ah mov al, [fats] mul word [sectorsperfat] add ax, [reservedsectors] mov [rootdir], ax ; Data area comes after root dir, so we need to add the size of ; the root dir in sectors ; One root dir entry is 32 bytes, and one sector has 512 bytes ; Therefore 512 / 32 = 16 entries correspond to a sector ; However, we can't just do a shift, since we need to round up ; For integers, ceil(x/y) = floor((x+y-1)/y) mov ax, [rootdirentries] add ax, 15 shr ax, 1 shr ax, 1 shr ax, 1 shr ax, 1 mov [rootdirsectors], ax fat: ; FAT1 (the main one) follows right after bootsector(s) mov ax, [reservedsectors] mov bx, 0x8000 mov cx, [sectorsperfat] call loadsectors root_dir: mov ax, [rootdir] mov bx, 0x500 mov cx, [rootdirsectors] call loadsectors search_root: mov bx, [rootdirentries] shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 shl bx, 1 mov byte [bx + 0x500], 0 mov si, 0x500 .entry: cmp byte [si], 0 je .end test byte [si + 11], 0x08 + 0x10 jnz .skipentry ; Make sure the file has non-zero size cmp word [si + 28], 0 jne .isfile cmp word [si + 30], 0 jne .isfile .skipentry: add si, 32 jmp .entry .isfile: mov cx, 11 mov bx, kernel_name .compare: lodsb cmp al, [bx] jne .nomatch inc bx loop .compare jmp found .nomatch: ; Each entry is 32 bytes long ; During each iteration of the compare loop, si is ; incremented and cx is decremented ; At the top of the loop, si + cx = entry_start + 11, but ; when we jump here si has been incremented by one already ; while cx is still the same ; Therefore, si + cx is one more, so add extra - 1 here add si, 32 - 11 - 1 add si, cx jmp .entry .end: notfound: mov si, notfound_msg jmp fatal_error found: ; SI points to 11 bytes after the start of the entry, so adjust all ; offsets mov ax, [si - 11 + 26] ; First cluster push ax ; Load OS at the start of the memory mov bx, 0x500 push bx .tosector: ; Adjust the cluster number to account for first two ; "clusters" in the FAT being used for metadata sub ax, 2 ; Scale by number of sectors per cluster xor bx, bx mov bl, [sectorspercluster] mul word bx ; Offset by number of non-data sectors before data area add ax, [rootdir] add ax, [rootdirsectors] .load: ; Load sectors pop bx xor cx, cx mov cl, [sectorspercluster] call loadsectors .next: pop ax ; Multiply by 1.5 to get offset into the table ; This rounds down, which is what we want in order to get ; the first byte of the address mov si, ax shl si, 1 add si, ax shr si, 1 ; Load the entry from FAT mov dx, [si + 0x8000] ; Two clusters, 0xABC and 0xXYZ, are stored as ; BC ZA XY ; ^ where we start reading on even cluster numbers ; ^^^^^ loading word → 0xZABC ; ^ where we start reading on odd cluster numbers ; ^^^^^ loading word: 0xXYZA test ax, 1 jnz .odd .even: and dx, 0x0fff jmp .check_cluster .odd: shr dx, 1 shr dx, 1 shr dx, 1 shr dx, 1 .check_cluster: mov ax, dx ; End of chain cmp ax, 0xFF8 jg execute_kernel push ax push bx jmp .tosector execute_kernel: mov dl, [drivenumber] jmp 0:0x500 ; Note: bx will point to after the read data loadsectors: push ax push cx push dx push di .loop: mov di, 3 ; Retry thrice .retry: push ax push cx .chs: xor dx, dx ; cylinder (track) - head - sector ; cylinder = LBA / sectorspertrack / heads ; head = LBA / sectorspertrack % heads ; sector = LBA % sectorspertrack + 1 div word [sectorspertrack] ; ax = LBA / sectorspertrack ; dx = LBA % sectorspertrack ; sector mov cl, dl inc cl xor dx, dx div word [heads] ; ax = LBA / sectorspertrack / heads ; dx = LBA / sectorspertrack % heads ; head mov dh, dl ; cylinder (track) mov ch, al shr ax, 1 shr ax, 1 and al, 0xC0 or cl, al mov ah, 2 mov al, 1 mov dl, [drivenumber] int 0x13 ; TODO: Handle reset & retry jc .error pop cx pop ax inc ax add bx, 512 loop .loop pop di pop dx pop cx pop ax ret .error: ; Do we still have retries remaining? mov si, diskerror_msg test di, di jz fatal_error ; No, fail dec di ; Yes, reset disk xor ah, ah int 0x13 ; Execute the loop again pop cx pop ax jmp .retry fatal_error: lodsb test al, al jz hang mov ah, 0xe int 0x10 jmp fatal_error hang: hlt jmp hang kernel_name: db "NOR86 KRN" notfound_msg: db "Kernel not found", 0 diskerror_msg: db "Disk error", 0 times 510-($-$$) db 0 db 0x55, 0xaa _end: rootdir equ _end rootdirsectors equ _end + 2