// FAT.ASM
// FAT12/16 Boot Sector
// Copyright (c) 1998, 2001, 2002 Brian Palmer



// This is a FAT12/16 file system boot sector
// that searches the entire root directory
// for the file freeldr.sys and loads it into
// memory.
//
// The stack is set to 0000:7BF2 so that the first
// WORD pushed will be placed at 0000:7BF0
//
// The DWORD at 0000:7BFC or BP-04h is the logical
// sector number of the start of the data area.
//
// The DWORD at 0000:7BF8 or BP-08h is the total
// sector count of the boot drive as reported by
// the computers bios.
//
// The WORD at 0000:7BF6 or BP-0ah is the offset
// of the ReadSectors function in the boot sector.
//
// The WORD at 0000:7BF4 or BP-0ch is the offset
// of the ReadCluster function in the boot sector.
//
// The WORD at 0000:7BF2 or BP-0eh is the offset
// of the PutChars function in the boot sector.
//
// When it locates freeldr.sys on the disk it will
// load the first sector of the file to 0000:F800
// With the help of this sector we should be able
// to load the entire file off the disk, no matter
// how fragmented it is.
//
// We load the entire FAT table into memory at
// 7000:0000. This improves the speed of floppy disk
// boots dramatically.

#include <asm.inc>
#include <freeldr/include/arch/pc/x86common.h>

#define BP_REL(x) [bp+x-offset start]

DataAreaStartHigh       =   2
DataAreaStartLow        =   4
BiosCHSDriveSizeHigh    =   6
BiosCHSDriveSizeLow     =   8
BiosCHSDriveSize        =   8
ReadSectorsOffset       =   10
ReadClusterOffset       =   12
PutCharsOffset          =   14
BootSectorStackTop      =   HEX(7c00) - 16


// org 7c00h

.code16

start:
    jmp main
    nop

OEMName:
    .ascii "FrLdr1.0"
BytesPerSector:
    .word 512
SectsPerCluster:
    .byte 1
ReservedSectors:
    .word 1
NumberOfFats:
    .byte 2
MaxRootEntries:
    .word 224
TotalSectors:
    .word 2880
MediaDescriptor:
    .byte HEX(0f0)
SectorsPerFat:
    .word 9
SectorsPerTrack:
    .word 18
NumberOfHeads:
    .word 2
HiddenSectors:
    .long 0
TotalSectorsBig:
    .long 0
BootDrive:
    .byte HEX(0ff)
Reserved:
    .byte 0
ExtendSig:
    .byte HEX(29)
SerialNumber:
    .long 00000000
VolumeLabel:
    .ascii "NO NAME    "
FileSystem:
    .ascii "FAT12   "

main:
    xor ax, ax
    mov ss, ax
    mov bp, HEX(7c00)
    mov sp, BootSectorStackTop                  // Setup a stack
    mov ds, ax                                  // Make DS correct
    mov es, ax                                  // Make ES correct

    cmp byte ptr BP_REL(BootDrive), HEX(0ff)    // If they have specified a boot drive then use it
    jne GetDriveParameters

    mov byte ptr BP_REL(BootDrive), dl          // Save the boot drive


GetDriveParameters:
    mov ah, 8
    mov dl, byte ptr BP_REL(BootDrive)          // Get boot drive in dl
    int HEX(13)                                 // Request drive parameters from the bios
    jnc CalcDriveSize                           // If the call succeeded then calculate the drive size

    // If we get here then the call to the BIOS failed
    // so just set CHS equal to the maximum addressable
    // size
    mov cx, HEX(0ffff)
    mov dh, cl

CalcDriveSize:
    // Now that we have the drive geometry
    // lets calculate the drive size
    mov bl, ch              // Put the low 8-bits of the cylinder count into BL
    mov bh, cl              // Put the high 2-bits in BH
    shr bh, 6               // Shift them into position, now BX contains the cylinder count
    and cl, HEX(3f)         // Mask off cylinder bits from sector count
    // CL now contains sectors per track and DH contains head count
    movzx eax, dh           // Move the heads into EAX
    movzx ebx, bx           // Move the cylinders into EBX
    movzx ecx, cl           // Move the sectors per track into ECX
    inc eax                 // Make it one based because the bios returns it zero based
    inc ebx                 // Make the cylinder count one based also
    mul ecx                 // Multiply heads with the sectors per track, result in edx:eax
    mul ebx                 // Multiply the cylinders with (heads * sectors) [stored in edx:eax already]

    // We now have the total number of sectors as reported
    // by the bios in eax, so store it in our variable
    mov dword ptr [bp - BiosCHSDriveSize], eax


    // Now we must find our way to the first sector of the root directory
    xor ax, ax
    xor cx, cx
    mov al, byte ptr BP_REL(NumberOfFats)       // Number of fats
    mul word ptr BP_REL(SectorsPerFat)          // Times sectors per fat
    add ax, word ptr BP_REL(HiddenSectors)
    adc dx, word ptr BP_REL(HiddenSectors+2)    // Add the number of hidden sectors
    add ax, word ptr BP_REL(ReservedSectors)    // Add the number of reserved sectors
    adc dx, cx                                  // Add carry bit
    mov word ptr [bp - DataAreaStartLow], ax    // Save the starting sector of the root directory
    mov word ptr [bp - DataAreaStartHigh], dx   // Save it in the first 4 bytes before the boot sector
    mov si, word ptr BP_REL(MaxRootEntries)     // Get number of root dir entries in SI
    pusha                                       // Save 32-bit logical start sector of root dir
    // DX:AX now has the number of the starting sector of the root directory

    // Now calculate the size of the root directory
    xor dx, dx
    mov ax, 32                                  // Size of dir entry
    mul si                                      // Times the number of entries
    mov bx, word ptr BP_REL(BytesPerSector)
    add ax, bx
    dec ax
    div bx                                      // Divided by the size of a sector
    // AX now has the number of root directory sectors

    add word ptr [bp - DataAreaStartLow], ax    // Add the number of sectors of the root directory to our other value
    adc word ptr [bp - DataAreaStartHigh], cx   // Now the first 4 bytes before the boot sector contain the starting sector of the data area
    popa                                        // Restore root dir logical sector start to DX:AX

LoadRootDirSector:
    mov bx, HEX(7e0)                        // We will load the root directory sector
    mov es, bx                              // Right after the boot sector in memory
    xor bx, bx                              // We will load it to [0000:7e00h]
    xor cx, cx                              // Zero out CX
    inc cx                                  // Now increment it to 1, we are reading one sector
    xor di, di                              // Zero out di
    push es                                 // Save ES because it will get incremented by 20h
    call ReadSectors                        // Read the first sector of the root directory
    pop es                                  // Restore ES (ES:DI = 07E0:0000)

SearchRootDirSector:
    cmp byte ptr es:[di], ch                // If the first byte of the directory entry is zero then we have
    jz ErrBoot                              // reached the end of the directory and FREELDR.SYS is not here so reboot
    pusha                                   // Save all registers
    mov cl, 11                              // Put 11 in cl (length of filename in directory entry)
    mov si, offset filename                 // Put offset of filename string in DS:SI
    repe cmpsb                              // Compare this directory entry against 'FREELDR SYS'
    popa                                    // Restore all the registers
    jz FoundFreeLoader                      // If we found it then jump
    dec si                                  // SI holds MaxRootEntries, subtract one
    jz ErrBoot                              // If we are out of root dir entries then reboot
    add di, 32                              // Increment DI by the size of a directory entry
    cmp di, HEX(0200)                       // Compare DI to 512 (DI has offset to next dir entry, make sure we haven't gone over one sector)
    jc SearchRootDirSector                  // If DI is less than 512 loop again
    jmp short LoadRootDirSector             // Didn't find FREELDR.SYS in this directory sector, try again

FoundFreeLoader:
    // We found freeldr.sys on the disk
    // so we need to load the first 512
    // bytes of it to 0000:F800
    // ES:DI has dir entry (ES:DI == 07E0:XXXX)
    mov ax, word ptr es:[di + HEX(1a)]      // Get start cluster
    push ax                                 // Save start cluster
    push FREELDR_BASE / 16                  // Put load segment on the stack and load it
    pop es                                  // Into ES so that we load the cluster at 0000:F800
    call ReadCluster                        // Read the cluster
    pop ax                                  // Restore start cluster of FreeLoader

    // Save the addresses of needed functions so
    // the helper code will know where to call them.
    mov word ptr [bp-ReadSectorsOffset], offset ReadSectors     // Save the address of ReadSectors
    mov word ptr [bp-ReadClusterOffset], offset ReadCluster     // Save the address of ReadCluster
    mov word ptr [bp-PutCharsOffset],    offset PutChars        // Save the address of PutChars

    // Now AX has start cluster of FreeLoader and we
    // have loaded the helper code in the first 512 bytes
    // of FreeLoader to 0000:F800. Now transfer control
    // to the helper code. Skip the first three bytes
    // because they contain a jump instruction to skip
    // over the helper code in the FreeLoader image.
    ljmp16 0, FREELDR_BASE + 3




// Displays an error message
// And reboots
ErrBoot:
    mov si, offset msgFreeLdr   // FreeLdr not found message
    call PutChars               // Display it

Reboot:
//    mov si, offset msgAnyKey  // Press any key message
//    call PutChars             // Display it
    xor ax, ax
    int HEX(16)                 // Wait for a keypress
    int HEX(19)                 // Reboot

PutChars:
    lodsb
    or al,al
    jz short Done
    mov ah, HEX(0e)
    mov bx, 7
    int HEX(10)
    jmp short PutChars
Done:
    ret

// Displays a bad boot message
// And reboots
BadBoot:
    mov si, offset msgDiskError // Bad boot disk message
    call PutChars               // Display it

    jmp short Reboot


// Reads cluster number in AX into [ES:0000]
ReadCluster:
    // StartSector = ((Cluster - 2) * SectorsPerCluster) + ReservedSectors + HiddenSectors;
    dec ax                              // Adjust start cluster by 2
    dec ax                              // Because the data area starts on cluster 2
    xor ch, ch
    mov cl, byte ptr BP_REL(SectsPerCluster)
    mul cx                              // Times sectors per cluster
    add ax, [bp-DataAreaStartLow]       // Add start of data area
    adc dx, [bp-DataAreaStartHigh]      // Now we have DX:AX with the logical start sector of FREELDR.SYS
    xor bx, bx                          // We will load it to [ES:0000], ES loaded before function call
//  mov  cl,BYTE [BYTE bp+SectsPerCluster]// Sectors per cluster still in CX
//  call ReadSectors
//  ret



// Reads logical sectors into [ES:BX]
// DX:AX has logical sector number to read
// CX has number of sectors to read
ReadSectors:

    // We can't just check if the start sector is
    // in the BIOS CHS range. We have to check if
    // the start sector + length is in that range.
    pusha
    dec cx
    add ax, cx
    adc dx, 0

    cmp dx, word ptr [bp-BiosCHSDriveSizeHigh]  // Check if they are reading a sector within CHS range
    ja  ReadSectorsLBA                          // No - go to the LBA routine
    jb  ReadSectorsCHS                          // Yes - go to the old CHS routine
    cmp ax, word ptr [bp-BiosCHSDriveSizeLow]   // Check if they are reading a sector within CHS range
    jbe ReadSectorsCHS                          // Yes - go to the old CHS routine

ReadSectorsLBA:
    popa
ReadSectorsLBALoop:
    pusha                                   // Save logical sector number & sector count

    push 0
    push 0
    push dx                                 // Put 64-bit logical
    push ax                                 // block address on stack
    push es                                 // Put transfer segment on stack
    push bx                                 // Put transfer offset on stack
    push 1                                  // Set transfer count to 1 sector
    push HEX(10)                            // Set size of packet to 10h
    mov  si,sp                              // Setup disk address packet on stack

// We are so totally out of space here that I am forced to
// comment out this very beautifully written piece of code
// It would have been nice to have had this check...
//CheckInt13hExtensions:                            // Now make sure this computer supports extended reads
//      mov  ah,0x41                            // AH = 41h
//      mov  bx,0x55aa                          // BX = 55AAh
//      mov  dl,[BYTE bp+BootDrive]             // DL = drive (80h-FFh)
//      int  13h                                // IBM/MS INT 13 Extensions - INSTALLATION CHECK
//      jc   PrintDiskError                     // CF set on error (extensions not supported)
//      cmp  bx,0xaa55                          // BX = AA55h if installed
//      jne  PrintDiskError
//      test cl,1                               // CX = API subset support bitmap
//      jz   PrintDiskError                     // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported


    // Good, we're here so the computer supports LBA disk access
    // So finish the extended read
    mov dl, byte ptr BP_REL(BootDrive)      // Drive number
    mov ah, HEX(42)                         // Int 13h, AH = 42h - Extended Read
    int HEX(13)                             // Call BIOS
    jc BadBoot                              // If the read failed then abort

    add sp, 16                              // Remove disk address packet from stack

    popa                                    // Restore sector count & logical sector number

    inc ax                                  // Increment Sector to Read
    adc dx, 0

    push bx
    mov bx, es
    add bx, HEX(20)                         // Increment read buffer for next sector
    mov es, bx
    pop bx

    loop ReadSectorsLBALoop                 // Read next sector

    ret


// Reads logical sectors into [ES:BX]
// DX:AX has logical sector number to read
// CX has number of sectors to read
// CarryFlag set on error
ReadSectorsCHS:
    popa
ReadSectorsCHSLoop:
    pusha
    xchg ax, cx
    xchg ax, dx
    xor dx, dx
    div word ptr BP_REL(SectorsPerTrack)
    xchg ax, cx
    div  word ptr BP_REL(SectorsPerTrack)   // Divide logical by SectorsPerTrack
    inc  dx                                 // Sectors numbering starts at 1 not 0
    xchg cx, dx
    div  word ptr BP_REL(NumberOfHeads)     // Number of heads
    mov  dh, dl                             // Head to DH, drive to DL
    mov  dl, byte ptr BP_REL(BootDrive)     // Drive number
    mov  ch, al                             // Cylinder in CX
    ror  ah, 2                              // Low 8 bits of cylinder in CH, high 2 bits
                                            //     in CL shifted to bits 6 & 7
    or   cl, ah                             // Or with sector number
    mov  ax, HEX(0201)
    int  HEX(13)     // DISK - READ SECTORS INTO MEMORY
                     // AL = number of sectors to read, CH = track, CL = sector
                     // DH = head, DL = drive, ES:BX -> buffer to fill
                     // Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read

    jc   BadBoot

    popa
    inc ax                                  // Increment Sector to Read
    jnz NoCarryCHS
    inc dx


NoCarryCHS:
    push bx
    mov  bx, es
    add  bx, HEX(20)
    mov  es, bx
    pop  bx
                            // Increment read buffer for next sector
    loop ReadSectorsCHSLoop // Read next sector

    ret


msgDiskError:
    .ascii "Disk error", CR, LF, NUL
msgFreeLdr:
    .ascii "Ldr not found", CR, LF, NUL
// Sorry, need the space...
// msgAnyKey:
//  .ascii "Press any key to restart", CR, LF, NUL
//  .ascii "Press a key", CR, LF, NUL
filename:
    .ascii "FREELDR SYS"

    .org 509    // Pad to 509 bytes

BootPartition:
    .byte 0

BootSignature:
    .word HEX(0aa55)    // BootSector signature

.endcode16

END
