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 select_os: mov si, prompt .print: lodsb test al, al jz .end ; On the original IBM PC BIOS the int 0x10 will clobber this mov ah, 0xe int 0x10 jmp .print .end: xor ax, ax int 0x16 cmp al, 'e' jne calc_constants mov si, ettinos_kernel_name mov di, kernel_name mov cx, 11 rep movsb 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] call chs ; TODO: handle possible crossing of track boundary mov ax, [sectorsperfat] mov ah, 2 mov dl, [drivenumber] mov bx, 0x8000 int 0x13 root_dir: mov ax, [rootdir] call chs ; TODO: handle possible crossing of track boundary mov ax, [rootdirsectors] mov ah, 2 mov dl, [drivenumber] mov bx, 0x500 int 0x13 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 jz .isfile 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: ; TODO: Better error messages mov ax, 0x0e00 + '?' int 0x10 hang: hlt jmp hang found: ; SI points to 11 bytes after the start of the entry, so adjust all ; offsets ; TODO: Handle zero-length files 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 sectors pop bx xor cx, cx mov cl, [sectorspercluster] mov dl, [drivenumber] .loop: push ax push cx call chs mov ah, 2 mov al, 1 int 0x13 pop cx pop ax add bx, 512 inc ax loop .loop .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 chs: push ax push bx ; Save drive number mov bl, dl 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 ; Restore drive number mov dl, bl pop bx pop ax ret prompt: db "Select OS. 'e' for EttinOS, any other key Nor86.", 0 kernel_name: db "KERNEL BIN" ettinos_kernel_name: db "SYSTEM BIN" times 510-($-$$) db 0 db 0x55, 0xaa _end: rootdir equ _end rootdirsectors equ _end + 2