/*
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS Bootsector
 * FILE:            boot/freeldr/bootsect/faty.S
 * PURPOSE:         Combined FAT12, FAT16 and FAT32 boot sector
 * PROGRAMMERS:     Brian Palmer
 *                  Timo Kreuzer
 */

#define DISKREADBUFFER HEX(8E000)

/*
 * Layout of a FAT volume:
 *
 * |---------------------------------------------------------
 * | * BootSector                         |
 * | * FS Information Sector (FAT32 only) | ReservedSectors
 * | * ... more reserved sectors ...      |
 * |--------------------------------------------------------
 * | * FAT 1                              | NumberOfFats
 * | * FAT 2                              |      *
 * | * [more FATs]                        | SectorsPerFat
 * |---------------------------------------------------------
 * | * Root Directory (FAT12/FAT16 only)  | MaxRootEntries / 16
 * |---------------------------------------------------------
 * | * File data                          |
 * | ....                                 |
 * |----------------------------------------
 */

/* INCLUDES ******************************************************************/

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

#define ADDRESS_FOR_DIRENTRIES HEX(10000)

SizeOfDataArea        = 32

/* Put the stack below the data area */
BootSectorStackTop    = (HEX(7c00) - SizeOfDataArea)

/* Data area offsets for uninitialized data */
DataAreaStart         = BootSectorStackTop + 0  /* dword */
#ifndef FAT32
RootDirStartSector    = BootSectorStackTop + 4  /* dword */
#endif
BiosCHSDriveSize      = BootSectorStackTop + 8  /* dword */
LBASectorsRead        = BootSectorStackTop + 12 /* dword */
ReadSectorsOffset     = BootSectorStackTop + 16 /* word */
ReadClusterOffset     = BootSectorStackTop + 18 /* word */
PutCharsOffset        = BootSectorStackTop + 20 /* word */

/* Macro for bp relative memory access to reduce code size */
#define BP_REL(x) ss:[bp + x - BootSectorStackTop]

/* The code starts at 0x7c00 */
// org 7c00h

.code16

/******************************************************************************
 *                      BIOS Parameter Block (BPB)                            *
 ******************************************************************************/
/* We have 3 bytes at the entry point to jump over the data area */
start:
    jmp short main // FIXME: When compiling FAT32, assembler will complain
                   // that the label is too far... Need investigation!
    nop

/* Here starts the BIOS Parameter Block (BPB) data.
   The real data will be copied during install */
OEMName:
    .ascii "FrLdr1.0"
BytesPerSector:
    .word 512
SectorsPerCluster:
    .byte 0
ReservedSectors:
    .word 32
NumberOfFats:
    .byte 2
MaxRootEntries:
    .word 0            // Always zero for FAT32 volumes
TotalSectors:
    .word 0            // Always zero for FAT32 volumes
MediaDescriptor:
    .byte HEX(0f8)
SectorsPerFat:
    .word 0            // Always zero for FAT32 volumes
SectorsPerTrack:
    .word 0
NumberOfHeads:
    .word 0
HiddenSectors:
    .long 0
TotalSectorsBig:
    .long 0

/* Extra data for FAT32 volumes */
#ifdef FAT32
SectorsPerFatBig:
    .long    0
ExtendedFlags:
    .word    0
FSVersion:
    .word    0
RootDirStartCluster:
    .long    0
FSInfoSector:
    .word    0
BackupBootSector:
    .word    6
Reserved1:
    .space 12, 0
#endif // FAT32

BootDrive:
    .byte 0
Reserved:
    .byte 0
ExtendSig:
    .byte HEX(29)
SerialNumber:
    .long 0
VolumeLabel:
    .ascii "NO NAME    "
FileSystem:
    .ascii "FATxx   "


/******************************************************************************
 *                             String data                                    *
 ******************************************************************************/

filename:
    .ascii "FREELDR SYS"

msgBootFailure:
    .ascii "Load failed!", CR, LF, NUL

msgAnyKey:
    .ascii "Press any key to reboot...", NUL


/******************************************************************************
 *                           Main code entry                                  *
 * Input: DL = Boot drive                                                     *
 ******************************************************************************/
main:
    /* First setup the segment registers */
    xor ax, ax
    mov ds, ax
    mov ss, ax

    /* Load the stack pointer */
    mov sp, BootSectorStackTop

    /* Load bp for relative memory access, which saves us some bytes of code
       size, when used with 32 bit instructions */
    mov bp, sp

    /* Load the boot drive from the BPB into al */
    mov al, byte ptr ds:[BootDrive]

    /* Check if it's valid */
    cmp al, HEX(0ff)
    je .SaveBootDrive

    /* Copy it into dl */
    mov dl, al

.SaveBootDrive:
    /* Save the bootdrive in the BPB */
    mov byte ptr ds:[BootDrive], dl


/******************************************************************************
 *                        Get drive parameters                                *
 ******************************************************************************/

    /* Call INT 13 to get the drive parameters:
       AH = 08h
       DL = drive (bit 7 set for hard disk)
       ES:DI = 0000h:0000h to guard against BIOS bugs */
    xor di, di
    mov ah, 8
    int HEX(13)

    /* Return from INT 13h/08h:
        CF set on error -> AH = status (07h)
        CF clear if successful -> AH = 00h
        AL = 00h on at least some BIOSes
        BL = drive type (AT/PS2 floppies only)
        CH = low eight bits of maximum cylinder number
        CL = bits 0:5 maximum sector number, bits 7:8 high two bits of maximum cylinder number
        DH = maximum head number
        DL = number of drives
        ES:DI -> drive parameter table (floppies only) */

    /* Check for failure */
    jc BootFailure


/******************************************************************************
 *                         Calculate drive size                               *
 ******************************************************************************/

    movzx ebx, ch           // Put the low 8-bits of the cylinder count into EBX
    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
    movzx ecx, cl           // Move the sectors per track into ECX

    movzx eax, dh           // Move the heads into EAX

    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_REL(BiosCHSDriveSize), eax


/******************************************************************************
 *                             Load the FAT                                   *
 ******************************************************************************/

    /* Load the number of first sector of the FAT into eax */
    movzx eax, word ptr BP_REL(ReservedSectors)
    add eax, dword ptr BP_REL(HiddenSectors)

    /* Load sector count into ecx */
#ifdef FAT32
    mov ecx, dword ptr BP_REL(SectorsPerFatBig)
#else
    movzx ecx, word ptr BP_REL(SectorsPerFat)
#endif

    /* Save FAT sector and size for later use */
    pushad

    /* Point ES:DI to the memory that is later the disk read buffer for freeldr.
       This way we cannot overwrite our FAT with freeldr data */
    mov bx, DISKREADBUFFER / 16
    mov es,bx
    xor di, di

    /* Read the sectors */
    call ReadSectors

    /* Restore FAT sector and size */
    popad


/******************************************************************************
 *                 Get root directory / data area start                       *
 ******************************************************************************/

    /* Copy reserved + hidden sectors to EBX */
    mov ebx, eax

    /* Calculate (NumberOfFats * SectorsPerFat) */
    movzx eax, byte ptr BP_REL(NumberOfFats)
    mul ecx

    /* Add reserved sectors and hidden sectors */
    add eax, ebx

#ifndef FAT32
    /* Save the starting sector of the root directory */
    mov dword ptr BP_REL(RootDirStartSector), eax

    /* Calculate number of sectors for the root dir:
       sectors = MaxRootEntries * 32 / 512 (rounded up!) */
    movzx ebx, word ptr BP_REL(MaxRootEntries)
    add ebx, 15
    shr ebx, 4

    /* Add the root dir start sector and save it as DataAreaStart */
    add ebx, eax
    mov dword ptr BP_REL(DataAreaStart), ebx
#else
    mov dword ptr BP_REL(DataAreaStart), eax

    /* On FAT32 volumes the root dir start cluster is stored in the BPB */
    mov eax, dword ptr BP_REL(RootDirStartCluster)
#endif


/******************************************************************************
 *                 Search the root directory for freeldr                      *
 ******************************************************************************/
.SearchForFreeldr:

    /* Load ES with the segment where we put the dir entries */
    mov bx, ADDRESS_FOR_DIRENTRIES / 16
    mov es, bx

    /* Set the address offset to 0 */
    xor di, di

#ifdef FAT32
    /* Read the dir cluster. This loads the next cluster into EAX */
    call ReadCluster

    /* Calculate the numer of dir entries in this cluster:
       dx = SectorsPerCluster * 512 / 32 */
    movzx dx, byte ptr ds:[SectorsPerCluster]
    shl dx, 4
#else
    /* Set the number of sectors to read to 1 */
    xor cx, cx
    inc cx

    /* Read the sector, but preserve ES */
    push es
    call ReadSectors
    pop es

    /* Set entry count to entries per sector */
    mov dx, (512 / 32)
#endif

    /* Load the start offset of the dir entries into ebx */
    xor bx, bx

.CheckDirEntry:
    /* Load the address of the name into di */
    mov di, bx

    /* If the first byte of the entry is 0 then we have reached the end */
    cmp byte ptr es:[di], ch
    jz BootFailure

    /* Compare with freeldr file name */
    mov si, offset filename
    mov cx, 11
    repe cmpsb

    /* Check if we found the file */
    jz .FoundFreeLoader

    /* File didn't match, go to next entry */
    add bx, 32

    /* Decrement entry count and check if we reached the end */
    dec dx
    jnz .CheckDirEntry

#ifdef FAT32
    /* Check to see if this was the last cluster in the chain */
    cmp eax, HEX(0ffffff8)
    jnb BootFailure
#endif

    /* Repeat the search process with the next sector / cluster.
       eax is already incremented in ReadSectors / ReadCluster */
    jmp .SearchForFreeldr


/******************************************************************************
 *                            Load freeldr                                    *
 ******************************************************************************/
.FoundFreeLoader:

    /* Load the cluster number of freeldr into eax */
#ifdef FAT32
#error unsupported
#else
    movzx eax, word ptr es:[bx + HEX(1A)]
#endif

    /* Load es:di with the freeldr start address */
    mov dx, FREELDR_BASE / 16
    mov es, dx
    xor di, di

.LoadNextCluster:
    /* Load the cluster to the current address. EAX is adjusted to the next
       cluster and ES is adjusted for the next read */
    call ReadCluster

    /* Check if this is the last cluster in the chain */
#if defined(FAT32)
    cmp eax, HEX(0ffffff8)
#elif defined(FAT12)
    cmp ax, HEX(0ff8)
#else
    cmp ax, HEX(0fff8)
#endif
    jb .LoadNextCluster

    /* Load boot drive into DL, boot partition into DH */
    mov  dl, byte ptr ds:[BootDrive]
    mov  dh, byte ptr ds:[BootPartition]

    /* Now the complete freeldr imag is loaded.
       Jump to the realmode entry point. */
    ljmp16 0, FREELDR_BASE



BootFailure:
    mov  si, offset msgBootFailure
    call PutChars


Reboot:
    /* Output "Press any key to reboot" message */
    mov  si, offset msgAnyKey
    call PutChars

    /* Wait for a keypress */
    xor  ax, ax
    int  HEX(16)

    /* Reboot */
    int  HEX(19)


/******************************************************************************
 * PROCEDURE ReadCluster                                                      *
 * Input: EAX = Cluster number, ES:DI = Target                                *
 * Modifies: EAX (next cluster number), BX, DX (undefined)                    *
 ******************************************************************************/
ReadCluster:

    pushad

    // StartSector = ((Cluster - 2) * SectorsPerCluster) + SectorsForFat + ReservedSectors + HiddenSectors
    // StartSector = ((Cluster - 2) * SectorsPerCluster) + DataAreaStart

    /* Substract 2 */
    dec eax
    dec eax

    /* Multiply with SectorsPerCluster */
    movzx ecx, byte ptr BP_REL(SectorsPerCluster)
    mul ecx

    /* Add DataAreaStart */
    add eax, dword ptr BP_REL(DataAreaStart)

    /* Call ReadSectors. EAX = SectorNumber, ECX = SectorsPerCluster */
    call ReadSectors

    /* Restore the cluster number */
    popad

    /* Save ES */
    push es

#if defined(FAT32)
#error FAT32 not implemented
#elif defined(FAT12)
#error FAT12 not implemented
#else
    /* DX:AX = AX * 2 (since FAT16 entries are 2 bytes) */
    mov bx, 2
    mul bx

    /* Shift DX, so that it is the segment offset: DX = DX * (64K / 16) */
    shl dx, 12

    /* Put segment address of FAT into ES */
    add dx, DISKREADBUFFER / 16
    mov es, dx

    /* Put the FAT entry offset into EBX for indirect mov */
    mov bx, ax

    /* Put the content of the FAT entry into AX */
    mov ax, es:[bx]
#endif

    /* Restore ES and return */
    pop  es
    ret


/******************************************************************************
 * PROCEDURE ReadSectors                                                      *
 * Input: EAX = Sector start number, ECX = number of sectors, ES:DI = Target  *
 * Modifies: EAX (incremented by sector count), CX = 0, ES (incremented),     *
 *           EBX undefined                                                    *
 ******************************************************************************/
ReadSectors:
    /* We could possibly also implement CHS, but it's currently unimplemented */
//jmp $
ReadSectorsLBA:

    /* Copy number of sectors to ebx */
    movzx ebx, cx

    /* Since the LBA calls only support 0x7F sectors at a time,
       we will limit ourselves to 64 */
    cmp bx, 64
    jbe .ReadSectorsLBA2
    mov bx, 64

.ReadSectorsLBA2:

    /* Save logical sector number & sector count */
    pushad

    /* Setup the disk address packet on the stack */
    .byte HEX(66) // size overwrite prefix for next push
    push 0    // Put 64-bit logical block address (high part) on stack
    push eax  // Put 64-bit logical block address (low part) on stack
    push es   // Put transfer segment on stack
    push di   // Put transfer offset on stack
    push bx   // Set transfer count (for this round)
    push 16   // Set size of packet to 16

    /* Point si to the disk address packet on stack */
    mov si, sp

    /* Set the drive number */
    mov dl, byte ptr ds:[BootDrive]
//jmp $
    /* Call INT 13h, AH = 42h - Extended Read
       Input: ...
       Modifies: ... */
    mov ah, HEX(42)
    int HEX(13)
//jmp $
    /* Check for failure */
    jc BootFailure

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

    /* Adjust ES to point to the next sector */
    shl bx, 5
    mov ax, es
    add ax, bx
    mov es, ax

    /* Restore sector count & logical sector number */
    popad

    /* Adjust the sector number to the next sector we need to read
       by adding the number of sectors that we read */
    add eax, ebx

    /* Adjust remaining sectors */
    sub cx, bx
    jnz ReadSectorsLBA

    /* return */
    ret



/******************************************************************************
 * PROCEDURE PutChars                                                         *
 * Input: ESI = Points to string to be printed                                *
 * Modifies: AL, AH, SI                                                       *
 ******************************************************************************/
PutChars2:
    mov ah, HEX(0e)
    mov bx, 7
    int HEX(10)
PutChars:
    lodsb
    or al, al
    jnz short PutChars2
    ret


/******************************************************************************
 *               Padding and boot sector signature                            *
 ******************************************************************************/
    /* Pad to 509 bytes */
    .org 509

BootPartition:
    .byte 0

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

.endcode16

END
