1// FAT.ASM 2// FAT12/16 Boot Sector 3// Copyright (c) 1998, 2001, 2002 Brian Palmer 4 5 6 7// This is a FAT12/16 file system boot sector 8// that searches the entire root directory 9// for the file freeldr.sys and loads it into 10// memory. 11// 12// The stack is set to 0000:7BF2 so that the first 13// WORD pushed will be placed at 0000:7BF0 14// 15// The DWORD at 0000:7BFC or BP-04h is the logical 16// sector number of the start of the data area. 17// 18// The DWORD at 0000:7BF8 or BP-08h is the total 19// sector count of the boot drive as reported by 20// the computers bios. 21// 22// The WORD at 0000:7BF6 or BP-0ah is the offset 23// of the ReadSectors function in the boot sector. 24// 25// The WORD at 0000:7BF4 or BP-0ch is the offset 26// of the ReadCluster function in the boot sector. 27// 28// The WORD at 0000:7BF2 or BP-0eh is the offset 29// of the PutChars function in the boot sector. 30// 31// When it locates freeldr.sys on the disk it will 32// load the first sector of the file to 0000:F800 33// With the help of this sector we should be able 34// to load the entire file off the disk, no matter 35// how fragmented it is. 36// 37// We load the entire FAT table into memory at 38// 7000:0000. This improves the speed of floppy disk 39// boots dramatically. 40 41#include <asm.inc> 42#include <freeldr/include/arch/pc/x86common.h> 43 44#define BP_REL(x) [bp+x-offset start] 45 46DataAreaStartHigh = 2 47DataAreaStartLow = 4 48BiosCHSDriveSizeHigh = 6 49BiosCHSDriveSizeLow = 8 50BiosCHSDriveSize = 8 51ReadSectorsOffset = 10 52ReadClusterOffset = 12 53PutCharsOffset = 14 54BootSectorStackTop = HEX(7c00) - 16 55 56 57// org 7c00h 58 59.code16 60 61start: 62 jmp main 63 nop 64 65OEMName: 66 .ascii "FrLdr1.0" 67BytesPerSector: 68 .word 512 69SectsPerCluster: 70 .byte 1 71ReservedSectors: 72 .word 1 73NumberOfFats: 74 .byte 2 75MaxRootEntries: 76 .word 224 77TotalSectors: 78 .word 2880 79MediaDescriptor: 80 .byte HEX(0f0) 81SectorsPerFat: 82 .word 9 83SectorsPerTrack: 84 .word 18 85NumberOfHeads: 86 .word 2 87HiddenSectors: 88 .long 0 89TotalSectorsBig: 90 .long 0 91BootDrive: 92 .byte HEX(0ff) 93Reserved: 94 .byte 0 95ExtendSig: 96 .byte HEX(29) 97SerialNumber: 98 .long 00000000 99VolumeLabel: 100 .ascii "NO NAME " 101FileSystem: 102 .ascii "FAT12 " 103 104main: 105 xor ax, ax 106 mov ss, ax 107 mov bp, HEX(7c00) 108 mov sp, BootSectorStackTop // Setup a stack 109 mov ds, ax // Make DS correct 110 mov es, ax // Make ES correct 111 112 cmp byte ptr BP_REL(BootDrive), HEX(0ff) // If they have specified a boot drive then use it 113 jne GetDriveParameters 114 115 mov byte ptr BP_REL(BootDrive), dl // Save the boot drive 116 117 118GetDriveParameters: 119 mov ah, 8 120 mov dl, byte ptr BP_REL(BootDrive) // Get boot drive in dl 121 int HEX(13) // Request drive parameters from the bios 122 jnc CalcDriveSize // If the call succeeded then calculate the drive size 123 124 // If we get here then the call to the BIOS failed 125 // so just set CHS equal to the maximum addressable 126 // size 127 mov cx, HEX(0ffff) 128 mov dh, cl 129 130CalcDriveSize: 131 // Now that we have the drive geometry 132 // lets calculate the drive size 133 mov bl, ch // Put the low 8-bits of the cylinder count into BL 134 mov bh, cl // Put the high 2-bits in BH 135 shr bh, 6 // Shift them into position, now BX contains the cylinder count 136 and cl, HEX(3f) // Mask off cylinder bits from sector count 137 // CL now contains sectors per track and DH contains head count 138 movzx eax, dh // Move the heads into EAX 139 movzx ebx, bx // Move the cylinders into EBX 140 movzx ecx, cl // Move the sectors per track into ECX 141 inc eax // Make it one based because the bios returns it zero based 142 inc ebx // Make the cylinder count one based also 143 mul ecx // Multiply heads with the sectors per track, result in edx:eax 144 mul ebx // Multiply the cylinders with (heads * sectors) [stored in edx:eax already] 145 146 // We now have the total number of sectors as reported 147 // by the bios in eax, so store it in our variable 148 mov dword ptr [bp - BiosCHSDriveSize], eax 149 150 151 // Now we must find our way to the first sector of the root directory 152 xor ax, ax 153 xor cx, cx 154 mov al, byte ptr BP_REL(NumberOfFats) // Number of fats 155 mul word ptr BP_REL(SectorsPerFat) // Times sectors per fat 156 add ax, word ptr BP_REL(HiddenSectors) 157 adc dx, word ptr BP_REL(HiddenSectors+2) // Add the number of hidden sectors 158 add ax, word ptr BP_REL(ReservedSectors) // Add the number of reserved sectors 159 adc dx, cx // Add carry bit 160 mov word ptr [bp - DataAreaStartLow], ax // Save the starting sector of the root directory 161 mov word ptr [bp - DataAreaStartHigh], dx // Save it in the first 4 bytes before the boot sector 162 mov si, word ptr BP_REL(MaxRootEntries) // Get number of root dir entries in SI 163 pusha // Save 32-bit logical start sector of root dir 164 // DX:AX now has the number of the starting sector of the root directory 165 166 // Now calculate the size of the root directory 167 xor dx, dx 168 mov ax, 32 // Size of dir entry 169 mul si // Times the number of entries 170 mov bx, word ptr BP_REL(BytesPerSector) 171 add ax, bx 172 dec ax 173 div bx // Divided by the size of a sector 174 // AX now has the number of root directory sectors 175 176 add word ptr [bp - DataAreaStartLow], ax // Add the number of sectors of the root directory to our other value 177 adc word ptr [bp - DataAreaStartHigh], cx // Now the first 4 bytes before the boot sector contain the starting sector of the data area 178 popa // Restore root dir logical sector start to DX:AX 179 180LoadRootDirSector: 181 mov bx, HEX(7e0) // We will load the root directory sector 182 mov es, bx // Right after the boot sector in memory 183 xor bx, bx // We will load it to [0000:7e00h] 184 xor cx, cx // Zero out CX 185 inc cx // Now increment it to 1, we are reading one sector 186 xor di, di // Zero out di 187 push es // Save ES because it will get incremented by 20h 188 call ReadSectors // Read the first sector of the root directory 189 pop es // Restore ES (ES:DI = 07E0:0000) 190 191SearchRootDirSector: 192 cmp byte ptr es:[di], ch // If the first byte of the directory entry is zero then we have 193 jz ErrBoot // reached the end of the directory and FREELDR.SYS is not here so reboot 194 pusha // Save all registers 195 mov cl, 11 // Put 11 in cl (length of filename in directory entry) 196 mov si, offset filename // Put offset of filename string in DS:SI 197 repe cmpsb // Compare this directory entry against 'FREELDR SYS' 198 popa // Restore all the registers 199 jz FoundFreeLoader // If we found it then jump 200 dec si // SI holds MaxRootEntries, subtract one 201 jz ErrBoot // If we are out of root dir entries then reboot 202 add di, 32 // Increment DI by the size of a directory entry 203 cmp di, HEX(0200) // Compare DI to 512 (DI has offset to next dir entry, make sure we haven't gone over one sector) 204 jc SearchRootDirSector // If DI is less than 512 loop again 205 jmp short LoadRootDirSector // Didn't find FREELDR.SYS in this directory sector, try again 206 207FoundFreeLoader: 208 // We found freeldr.sys on the disk 209 // so we need to load the first 512 210 // bytes of it to 0000:F800 211 // ES:DI has dir entry (ES:DI == 07E0:XXXX) 212 mov ax, word ptr es:[di + HEX(1a)] // Get start cluster 213 push ax // Save start cluster 214 push FREELDR_BASE / 16 // Put load segment on the stack and load it 215 pop es // Into ES so that we load the cluster at 0000:F800 216 call ReadCluster // Read the cluster 217 pop ax // Restore start cluster of FreeLoader 218 219 // Save the addresses of needed functions so 220 // the helper code will know where to call them. 221 mov word ptr [bp-ReadSectorsOffset], offset ReadSectors // Save the address of ReadSectors 222 mov word ptr [bp-ReadClusterOffset], offset ReadCluster // Save the address of ReadCluster 223 mov word ptr [bp-PutCharsOffset], offset PutChars // Save the address of PutChars 224 225 // Now AX has start cluster of FreeLoader and we 226 // have loaded the helper code in the first 512 bytes 227 // of FreeLoader to 0000:F800. Now transfer control 228 // to the helper code. Skip the first three bytes 229 // because they contain a jump instruction to skip 230 // over the helper code in the FreeLoader image. 231 ljmp16 0, FREELDR_BASE + 3 232 233 234 235 236// Displays an error message 237// And reboots 238ErrBoot: 239 mov si, offset msgFreeLdr // FreeLdr not found message 240 call PutChars // Display it 241 242Reboot: 243// mov si, offset msgAnyKey // Press any key message 244// call PutChars // Display it 245 xor ax, ax 246 int HEX(16) // Wait for a keypress 247 int HEX(19) // Reboot 248 249PutChars: 250 lodsb 251 or al,al 252 jz short Done 253 mov ah, HEX(0e) 254 mov bx, 7 255 int HEX(10) 256 jmp short PutChars 257Done: 258 ret 259 260// Displays a bad boot message 261// And reboots 262BadBoot: 263 mov si, offset msgDiskError // Bad boot disk message 264 call PutChars // Display it 265 266 jmp short Reboot 267 268 269// Reads cluster number in AX into [ES:0000] 270ReadCluster: 271 // StartSector = ((Cluster - 2) * SectorsPerCluster) + ReservedSectors + HiddenSectors; 272 dec ax // Adjust start cluster by 2 273 dec ax // Because the data area starts on cluster 2 274 xor ch, ch 275 mov cl, byte ptr BP_REL(SectsPerCluster) 276 mul cx // Times sectors per cluster 277 add ax, [bp-DataAreaStartLow] // Add start of data area 278 adc dx, [bp-DataAreaStartHigh] // Now we have DX:AX with the logical start sector of FREELDR.SYS 279 xor bx, bx // We will load it to [ES:0000], ES loaded before function call 280// mov cl,BYTE [BYTE bp+SectsPerCluster]// Sectors per cluster still in CX 281// call ReadSectors 282// ret 283 284 285 286// Reads logical sectors into [ES:BX] 287// DX:AX has logical sector number to read 288// CX has number of sectors to read 289ReadSectors: 290 291 // We can't just check if the start sector is 292 // in the BIOS CHS range. We have to check if 293 // the start sector + length is in that range. 294 pusha 295 dec cx 296 add ax, cx 297 adc dx, 0 298 299 cmp dx, word ptr [bp-BiosCHSDriveSizeHigh] // Check if they are reading a sector within CHS range 300 ja ReadSectorsLBA // No - go to the LBA routine 301 jb ReadSectorsCHS // Yes - go to the old CHS routine 302 cmp ax, word ptr [bp-BiosCHSDriveSizeLow] // Check if they are reading a sector within CHS range 303 jbe ReadSectorsCHS // Yes - go to the old CHS routine 304 305ReadSectorsLBA: 306 popa 307ReadSectorsLBALoop: 308 pusha // Save logical sector number & sector count 309 310 push 0 311 push 0 312 push dx // Put 64-bit logical 313 push ax // block address on stack 314 push es // Put transfer segment on stack 315 push bx // Put transfer offset on stack 316 push 1 // Set transfer count to 1 sector 317 push HEX(10) // Set size of packet to 10h 318 mov si,sp // Setup disk address packet on stack 319 320// We are so totally out of space here that I am forced to 321// comment out this very beautifully written piece of code 322// It would have been nice to have had this check... 323//CheckInt13hExtensions: // Now make sure this computer supports extended reads 324// mov ah,0x41 // AH = 41h 325// mov bx,0x55aa // BX = 55AAh 326// mov dl,[BYTE bp+BootDrive] // DL = drive (80h-FFh) 327// int 13h // IBM/MS INT 13 Extensions - INSTALLATION CHECK 328// jc PrintDiskError // CF set on error (extensions not supported) 329// cmp bx,0xaa55 // BX = AA55h if installed 330// jne PrintDiskError 331// test cl,1 // CX = API subset support bitmap 332// jz PrintDiskError // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported 333 334 335 // Good, we're here so the computer supports LBA disk access 336 // So finish the extended read 337 mov dl, byte ptr BP_REL(BootDrive) // Drive number 338 mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read 339 int HEX(13) // Call BIOS 340 jc BadBoot // If the read failed then abort 341 342 add sp, 16 // Remove disk address packet from stack 343 344 popa // Restore sector count & logical sector number 345 346 inc ax // Increment Sector to Read 347 adc dx, 0 348 349 push bx 350 mov bx, es 351 add bx, HEX(20) // Increment read buffer for next sector 352 mov es, bx 353 pop bx 354 355 loop ReadSectorsLBALoop // Read next sector 356 357 ret 358 359 360// Reads logical sectors into [ES:BX] 361// DX:AX has logical sector number to read 362// CX has number of sectors to read 363// CarryFlag set on error 364ReadSectorsCHS: 365 popa 366ReadSectorsCHSLoop: 367 pusha 368 xchg ax, cx 369 xchg ax, dx 370 xor dx, dx 371 div word ptr BP_REL(SectorsPerTrack) 372 xchg ax, cx 373 div word ptr BP_REL(SectorsPerTrack) // Divide logical by SectorsPerTrack 374 inc dx // Sectors numbering starts at 1 not 0 375 xchg cx, dx 376 div word ptr BP_REL(NumberOfHeads) // Number of heads 377 mov dh, dl // Head to DH, drive to DL 378 mov dl, byte ptr BP_REL(BootDrive) // Drive number 379 mov ch, al // Cylinder in CX 380 ror ah, 2 // Low 8 bits of cylinder in CH, high 2 bits 381 // in CL shifted to bits 6 & 7 382 or cl, ah // Or with sector number 383 mov ax, HEX(0201) 384 int HEX(13) // DISK - READ SECTORS INTO MEMORY 385 // AL = number of sectors to read, CH = track, CL = sector 386 // DH = head, DL = drive, ES:BX -> buffer to fill 387 // Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read 388 389 jc BadBoot 390 391 popa 392 inc ax // Increment Sector to Read 393 jnz NoCarryCHS 394 inc dx 395 396 397NoCarryCHS: 398 push bx 399 mov bx, es 400 add bx, HEX(20) 401 mov es, bx 402 pop bx 403 // Increment read buffer for next sector 404 loop ReadSectorsCHSLoop // Read next sector 405 406 ret 407 408 409msgDiskError: 410 .ascii "Disk error", CR, LF, NUL 411msgFreeLdr: 412 .ascii "Ldr not found", CR, LF, NUL 413// Sorry, need the space... 414// msgAnyKey: 415// .ascii "Press any key to restart", CR, LF, NUL 416// .ascii "Press a key", CR, LF, NUL 417filename: 418 .ascii "FREELDR SYS" 419 420 .org 509 // Pad to 509 bytes 421 422BootPartition: 423 .byte 0 424 425BootSignature: 426 .word HEX(0aa55) // BootSector signature 427 428.endcode16 429 430END 431