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 ; Deleted file? cmp byte [si], 0xe5 je .skipentry test byte [si + 11], 0x08 + 0x10 jnz .skipentry ; Make sure the file has non-zero size ; File size must be <32K anyways so don't bother with >64K cmp word [si + 28], 0 jne .isfile .skipentry: add si, 32 jmp .entry .isfile: .nor86: cmp word [si + 8], 'KR' jne .ettinos cmp byte [si + 10], 'N' jne .ettinos mov ax, [si + 26] ; First cluster mov [kernel_cluster], ax jmp .skipentry .ettinos: mov cx, 11 mov bx, ettinos_kernel_name .compare: lodsb cmp al, [bx] jne .nextentry inc bx loop .compare ; SI points to 11 bytes after the start of the ; entry, so adjust offset mov ax, [si - 11 + 26] ; First cluster mov [ettinos_kernel_cluster], ax inc cx ; Offset so that falling through will work .nextentry: ; 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: which_found: mov bx, [kernel_cluster] mov cx, [ettinos_kernel_cluster] test bx, bx jz .no_nor86 test cx, cx jz found mov si, choose_msg .loop: lodsb test al, al jz .end mov ah, 0xe int 0x10 jmp .loop .end: xor ax, ax int 0x16 cmp ah, 0x12 ; E-key je found_cx jmp found .no_nor86: test cx, cx jnz found_cx notfound: mov si, notfound_msg jmp fatal_error found_cx: mov bx, cx found: mov ax, bx 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 dec ax dec ax ; 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 jz .even .odd: shr dx, 1 shr dx, 1 shr dx, 1 shr dx, 1 .even: and dx, 0x0fff .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 ; Note: ax, cx, dx, and di will be clobbered loadsectors: .loop: mov di, 3 + 1 ; Retry thrice, + 1 is since we dec first .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 ax, 0x0201 mov dl, [drivenumber] int 0x13 jc .error mov ax, 0x0e00 + '.' int 0x10 pop cx pop ax inc ax add bx, 512 loop .loop ret .error: ; Do we still have retries remaining? mov si, diskerror_msg dec di jz fatal_error ; No, fail ; Yes, reset disk xor ah, ah int 0x13 ; Execute the loop again pop cx pop ax jmp .retry fatal_error: mov cx, 9 .loop: lodsb mov ah, 0xe int 0x10 loop .loop hang: hlt jmp hang kernel_cluster dw 0 ettinos_kernel_cluster dw 0 ettinos_kernel_name db "SYSTEM BIN" notfound_msg db "No kernel" diskerror_msg db "Disk error" choose_msg db 13, 10, "EttinOS/Nor86?", 0 times 510-($-$$) db 0 db 0x55, 0xaa _end: rootdir equ _end rootdirsectors equ _end + 2