// 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:8000 // 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 #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 OSLOADER.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 filename: .ascii "FREELDR SYS" .org 509 // Pad to 509 bytes BootPartition: .byte 0 BootSignature: .word HEX(0aa55) // BootSector signature .endcode16 END