; ======================================================================================
; SYS-DOS Bootloader
; Based Off The Mike Operating System bootloader
; Copyright (C) 2006 - 2021 MikeOS Developers and Aiden Moechnig -- see doc/LICENSE.TXT
;
; Based on a free boot loader by E Dehling. It scans the FAT12
; floppy for KERNEL.BIN (the MikeOS kernel), loads it and executes it.
; This must grow no larger than 512 bytes (one sector), with the final
; two bytes being the boot signature (AA55h). Note that in FAT12,
; a cluster is the same as a sector: 512 bytes.
; ======================================================================================


        BITS 16

        jmp short bootloader_start      ; Jump past disk description section
        nop                             ; Pad out before disk description


; ------------------------------------------------------------------
; Disk description table, to make it a valid floppy
; Note: some of these values are hard-coded in the source!
; Values are those used by IBM for 1.44 MB, 3.5" diskette

OEMLabel                db "SYSBOOT"   ; Disk label
BytesPerSector          dw 512          ; Bytes per sector
SectorsPerCluster       db 1            ; Sectors per cluster
ReservedForBoot         dw 1            ; Reserved sectors for boot record
NumberOfFats            db 2            ; Number of copies of the FAT
RootDirEntries          dw 224          ; Number of entries in root dir
                                        ; (224 * 32 = 7168 = 14 sectors to read)
LogicalSectors          dw 2880         ; Number of logical sectors
MediumByte              db 0F0h         ; Medium descriptor byte
SectorsPerFat           dw 9            ; Sectors per FAT
SectorsPerTrack         dw 18           ; Sectors per track (36/cylinder)
Sides                   dw 2            ; Number of sides/heads
HiddenSectors           dd 0            ; Number of hidden sectors
LargeSectors            dd 0            ; Number of LBA sectors
DriveNo                 dw 0            ; Drive No: 0
Signature               db 41           ; Drive signature: 41 for floppy
VolumeID                dd 00000000h    ; Volume ID: any number
VolumeLabel             db "SYSDOS     "; Volume Label: any 11 chars
FileSystem              db "FAT12   "   ; File system type: don't change!


; ------------------------------------------------------------------
; Main bootloader code

bootloader_start:
        mov ax, 07C0h                   ; Set up 4K of stack space above buffer
        add ax, 544                     ; 8k buffer = 512 paragraphs + 32 paragraphs (loader)
        cli                             ; Disable interrupts while changing stack
        mov ss, ax
        mov sp, 4096
        sti                             ; Restore interrupts

        mov ax, 07C0h                   ; Set data segment to where we're loaded
        mov ds, ax

        ; NOTE: A few early BIOSes are reported to improperly set DL

        cmp dl, 0
        je no_change
        mov [bootdev], dl               ; Save boot device number
        mov ah, 8                       ; Get drive parameters
        int 13h
        jc fatal_disk_error
        and cx, 3Fh                     ; Maximum sector number
        mov [SectorsPerTrack], cx       ; Sector numbers start at 1
        movzx dx, dh                    ; Maximum head number
        add dx, 1                       ; Head numbers start at 0 - add 1 for total
        mov [Sides], dx

no_change:
        mov eax, 0                      ; Needed for some older BIOSes


; First, we need to load the root directory from the disk. Technical details:
; Start of root = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19
; Number of root = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14
; Start of user data = (start of root) + (number of root) = logical 33

floppy_ok:                              ; Ready to read first block of data
        mov ax, 19                      ; Root dir starts at logical sector 19
        call l2hts

        mov si, buffer                  ; Set ES:BX to point to our buffer (see end of code)
        mov bx, ds
        mov es, bx
        mov bx, si

        mov ah, 2                       ; Params for int 13h: read floppy sectors
        mov al, 14                      ; And read 14 of them

        pusha                           ; Prepare to enter loop


read_root_dir:
        popa                            ; In case registers are altered by int 13h
        pusha

        stc                             ; A few BIOSes do not set properly on error
        int 13h                         ; Read sectors using BIOS

        jnc search_dir                  ; If read went OK, skip ahead
        call reset_floppy               ; Otherwise, reset floppy controller and try again
        jnc read_root_dir               ; Floppy reset OK?

        jmp reboot                      ; If not, fatal double error


search_dir:
        popa

        mov ax, ds                      ; Root dir is now in [buffer]
        mov es, ax                      ; Set DI to this info
        mov di, buffer

        mov cx, word [RootDirEntries]   ; Search all (224) entries
        mov ax, 0                       ; Searching at offset 0


next_root_entry:
        xchg cx, dx                     ; We use CX in the inner loop...

        mov si, kern_filename           ; Start searching for kernel filename
        mov cx, 11
        rep cmpsb
        je found_file_to_load           ; Pointer DI will be at offset 11

        add ax, 32                      ; Bump searched entries by 1 (32 bytes per entry)

        mov di, buffer                  ; Point to next entry
        add di, ax

        xchg dx, cx                     ; Get the original CX back
        loop next_root_entry

        mov si, file_not_found          ; If kernel is not found, bail out
        call print_string
        jmp reboot


found_file_to_load:                     ; Fetch cluster and load FAT into RAM
        mov ax, word [es:di+0Fh]        ; Offset 11 + 15 = 26, contains 1st cluster
        mov word [cluster], ax

        mov ax, 1                       ; Sector 1 = first sector of first FAT
        call l2hts

        mov di, buffer                  ; ES:BX points to our buffer
        mov bx, di

        mov ah, 2                       ; int 13h params: read (FAT) sectors
        mov al, 9                       ; All 9 sectors of 1st FAT

        pusha                           ; Prepare to enter loop


read_fat:
        popa                            ; In case registers are altered by int 13h
        pusha

        stc
        int 13h                         ; Read sectors using the BIOS

        jnc read_fat_ok                 ; If read went OK, skip ahead
        call reset_floppy               ; Otherwise, reset floppy controller and try again
        jnc read_fat                    ; Floppy reset OK?

; ******************************************************************
fatal_disk_error:
; ******************************************************************
        mov si, disk_error              ; If not, print error message and reboot
        call print_string
        jmp reboot                      ; Fatal double error


read_fat_ok:
        popa

        mov ax, 2000h                   ; Segment where we'll load the kernel
        mov es, ax
        mov bx, 0

        mov ah, 2                       ; int 13h floppy read params
        mov al, 1

        push ax                         ; Save in case we (or int calls) lose it


; Now we must load the FAT from the disk. Here's how we find out where it starts:
; FAT cluster 0 = media descriptor = 0F0h
; FAT cluster 1 = filler cluster = 0FFh
; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user)
;               = (cluster number) + 31

load_file_sector:
        mov ax, word [cluster]          ; Convert sector to logical
        add ax, 31

        call l2hts                      ; Make appropriate params for int 13h

        mov ax, 2000h                   ; Set buffer past what we've already read
        mov es, ax
        mov bx, word [pointer]

        pop ax                          ; Save in case we (or int calls) lose it
        push ax

        stc
        int 13h

        jnc calculate_next_cluster      ; If there's no error...

        call reset_floppy               ; Otherwise, reset floppy and retry
        jmp load_file_sector


        ; In the FAT, cluster values are stored in 12 bits, so we have to
        ; do a bit of maths to work out whether we're dealing with a byte
        ; and 4 bits of the next byte -- or the last 4 bits of one byte
        ; and then the subsequent byte!

calculate_next_cluster:
        mov ax, [cluster]
        mov dx, 0
        mov bx, 3
        mul bx
        mov bx, 2
        div bx                          ; DX = [cluster] mod 2
        mov si, buffer
        add si, ax                      ; AX = word in FAT for the 12 bit entry
        mov ax, word [ds:si]

        or dx, dx                       ; If DX = 0 [cluster] is even; if DX = 1 then it's odd

        jz even                         ; If [cluster] is even, drop last 4 bits of word
                                        ; with next cluster; if odd, drop first 4 bits

odd:
        shr ax, 4                       ; Shift out first 4 bits (they belong to another entry)
        jmp short next_cluster_cont


even:
        and ax, 0FFFh                   ; Mask out final 4 bits


next_cluster_cont:
        mov word [cluster], ax          ; Store cluster

        cmp ax, 0FF8h                   ; FF8h = end of file marker in FAT12
        jae end

        add word [pointer], 512         ; Increase buffer pointer 1 sector length
        jmp load_file_sector


end:                                    ; We've got the file to load!
        pop ax                          ; Clean up the stack (AX was pushed earlier)
        mov dl, byte [bootdev]          ; Provide kernel with boot device info

        jmp 2000h:0000h                 ; Jump to entry point of loaded kernel!


; ------------------------------------------------------------------
; BOOTLOADER SUBROUTINES

reboot:
        mov ax, 0
        int 16h                         ; Wait for keystroke
        mov ax, 0
        int 19h                         ; Reboot the system


print_string:                           ; Output string in SI to screen
        pusha

        mov ah, 0Eh                     ; int 10h teletype function

.repeat:
        lodsb                           ; Get char from string
        cmp al, 0
        je .done                        ; If char is zero, end of string
        int 10h                         ; Otherwise, print it
        jmp short .repeat

.done:
        popa
        ret


reset_floppy:           ; IN: [bootdev] = boot device; OUT: carry set on error
        push ax
        push dx
        mov ax, 0
        mov dl, byte [bootdev]
        stc
        int 13h
        pop dx
        pop ax
        ret


l2hts:                  ; Calculate head, track and sector settings for int 13h
                        ; IN: logical sector in AX, OUT: correct registers for int 13h
        push bx
        push ax

        mov bx, ax                      ; Save logical sector

        mov dx, 0                       ; First the sector
        div word [SectorsPerTrack]
        add dl, 01h                     ; Physical sectors start at 1
        mov cl, dl                      ; Sectors belong in CL for int 13h
        mov ax, bx

        mov dx, 0                       ; Now calculate the head
        div word [SectorsPerTrack]
        mov dx, 0
        div word [Sides]
        mov dh, dl                      ; Head/side
        mov ch, al                      ; Track

        pop ax
        pop bx

        mov dl, byte [bootdev]          ; Set correct device

        ret


; ------------------------------------------------------------------
; STRINGS AND VARIABLES

        kern_filename   db "KERNEL  BIN"        ; SYS-DOS kernel filename

        disk_error      db "Floppy error! Press any key...", 0
        file_not_found  db "KERNEL.BIN not found!", 0

        bootdev         db 0    ; Boot device number
        cluster         dw 0    ; Cluster of the file we want to load
        pointer         dw 0    ; Pointer into Buffer, for loading kernel


; ------------------------------------------------------------------
; END OF BOOT SECTOR AND BUFFER START

        times 510-($-$$) db 0   ; Pad remainder of boot sector with zeros
        dw 0AA55h               ; Boot signature (DO NOT CHANGE!)


buffer:                         ; Disk buffer begins (8k after this, stack starts)


; ==================================================================

