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