xref: /reactos/boot/freeldr/bootsect/fat.S (revision 40462c92)
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