Sync to trunk r65566.
[reactos.git] / boot / freeldr / bootsect / fatx.S
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS Bootsector
4 * FILE: fatx.S
5 * PURPOSE: Combined FAT16 and FAT32 boot sector
6 * PROGRAMMERS: Brian Palmer
7 * Timo Kreuzer
8 */
9
10
11 /*
12 * Layout of a FAT volume:
13 *
14 * |---------------------------------------------------------
15 * | * BootSector |
16 * | * FS Information Sector (FAT32 only) | ReservedSectors
17 * | * ... more reserved sectors ... |
18 * |--------------------------------------------------------
19 * | * FAT 1 | NumberOfFats
20 * | * FAT 2 | *
21 * | * [more FATs] | SectorsPerFat
22 * |---------------------------------------------------------
23 * | * Root Directory (FAT12/FAT16 only) | MaxRootEntries / 16
24 * |---------------------------------------------------------
25 * | * File data |
26 * | .... |
27 * |----------------------------------------
28 */
29
30 /* INCLUDES ******************************************************************/
31
32 #include <asm.inc>
33 #include <freeldr/include/arch/pc/x86common.h>
34
35 #define ADDRESS_FOR_DIRENTRIES HEX(10000)
36
37 SizeOfDataArea = 32
38
39 /* Put the stack below the data area */
40 BootSectorStackTop = (HEX(7c00) - SizeOfDataArea)
41
42 /* Data area offsets for uninitialized data */
43 DataAreaStart = BootSectorStackTop + 0 /* dword */
44 #ifndef FAT32
45 RootDirStartSector = BootSectorStackTop + 4 /* dword */
46 #endif
47 BiosCHSDriveSize = BootSectorStackTop + 8 /* dword */
48 LBASectorsRead = BootSectorStackTop + 12 /* dword */
49 ReadSectorsOffset = BootSectorStackTop + 16 /* word */
50 ReadClusterOffset = BootSectorStackTop + 18 /* word */
51 PutCharsOffset = BootSectorStackTop + 20 /* word */
52
53 /* Macro for bp relative memory access to reduce code size */
54 #define BP_REL(x) ss:[bp + x - BootSectorStackTop]
55
56 /* The code starts at 0x7c00 */
57 // org 7c00h
58
59 .code16
60
61 /******************************************************************************
62 * BIOS Parameter Block (BPB) *
63 ******************************************************************************/
64 /* We have 3 bytes at the entry point to jump over the data area */
65 start:
66 jmp short main
67 nop
68
69 /* Here starts the BIOS Parameter Block (BPB) data.
70 The real data will be copied during install */
71 OEMName:
72 .ascii "FrLdr1.0"
73 BytesPerSector:
74 .word 512
75 SectorsPerCluster:
76 .byte 0
77 ReservedSectors:
78 .word 32
79 NumberOfFats:
80 .byte 2
81 MaxRootEntries:
82 .word 0 // Always zero for FAT32 volumes
83 TotalSectors:
84 .word 0 // Always zero for FAT32 volumes
85 MediaDescriptor:
86 .byte HEX(0f8)
87 SectorsPerFat:
88 .word 0 // Always zero for FAT32 volumes
89 SectorsPerTrack:
90 .word 0
91 NumberOfHeads:
92 .word 0
93 HiddenSectors:
94 .long 0
95 TotalSectorsBig:
96 .long 0
97
98 /* Extra data for FAT32 volumes */
99 #ifdef FAT32
100 SectorsPerFatBig:
101 .long 0
102 ExtendedFlags:
103 .word 0
104 FSVersion:
105 .word 0
106 RootDirStartCluster:
107 .long 0
108 FSInfoSector:
109 .word 0
110 BackupBootSector:
111 .word 6
112 Reserved1:
113 .byte 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
114 #endif // FAT32
115
116 BootDrive:
117 .byte 0
118 Reserved:
119 .byte 0
120 ExtendSig:
121 .byte HEX(29)
122 SerialNumber:
123 .long 0
124 VolumeLabel:
125 .ascii "NO NAME "
126 FileSystem:
127 .ascii "FATxx "
128
129
130 /******************************************************************************
131 * String data *
132 ******************************************************************************/
133
134 filename:
135 .ascii "FREELDR SYS"
136
137 msgBootFailure:
138 .ascii "Load failed!", CR, LF, NUL
139
140 msgAnyKey:
141 .ascii "Press any key to reboot...", NUL
142
143
144 /******************************************************************************
145 * Main code entry *
146 * Input: DL = Boot drive *
147 ******************************************************************************/
148 main:
149 /* First setup the segment registers */
150 xor ax, ax
151 mov ds, ax
152 mov ss, ax
153
154 /* Load the stack pointer */
155 mov sp, BootSectorStackTop
156
157 /* Load bp for relative memory access, which saves us some bytes of code
158 size, when used with 32 bit instructions */
159 mov bp, sp
160
161 /* Load the boot drive from the BPB into al */
162 mov al, byte ptr ds:[BootDrive]
163
164 /* Check if it's valid */
165 cmp al, HEX(0ff)
166 je .SaveBootDrive
167
168 /* Copy it into dl */
169 mov dl, al
170
171 .SaveBootDrive:
172 /* Save the bootdrive in the BPB */
173 mov byte ptr ds:[BootDrive], dl
174
175
176 /******************************************************************************
177 * Get drive parameters *
178 ******************************************************************************/
179
180 /* Call INT 13 to get the drive parameters:
181 AH = 08h
182 DL = drive (bit 7 set for hard disk)
183 ES:DI = 0000h:0000h to guard against BIOS bugs */
184 xor di, di
185 mov ah, 8
186 int HEX(13)
187
188 /* Return from INT 13h/08h:
189 CF set on error -> AH = status (07h)
190 CF clear if successful -> AH = 00h
191 AL = 00h on at least some BIOSes
192 BL = drive type (AT/PS2 floppies only)
193 CH = low eight bits of maximum cylinder number
194 CL = bits 0:5 maximum sector number, bits 7:8 high two bits of maximum cylinder number
195 DH = maximum head number
196 DL = number of drives
197 ES:DI -> drive parameter table (floppies only) */
198
199 /* Check for failure */
200 jc BootFailure
201
202
203 /******************************************************************************
204 * Calculate drive size *
205 ******************************************************************************/
206
207 movzx ebx, ch // Put the low 8-bits of the cylinder count into EBX
208 mov bh, cl // Put the high 2-bits in BH
209 shr bh, 6 // Shift them into position, now BX contains the cylinder count
210
211 and cl, HEX(3f) // Mask off cylinder bits from sector count
212 movzx ecx, cl // Move the sectors per track into ECX
213
214 movzx eax, dh // Move the heads into EAX
215
216 inc eax // Make it one based because the bios returns it zero based
217 inc ebx // Make the cylinder count one based also
218 mul ecx // Multiply heads with the sectors per track, result in edx:eax
219 mul ebx // Multiply the cylinders with (heads * sectors) [stored in edx:eax already]
220
221 // We now have the total number of sectors as reported
222 // by the bios in eax, so store it in our variable
223 mov dword ptr BP_REL(BiosCHSDriveSize), eax
224
225
226 /******************************************************************************
227 * Load the FAT *
228 ******************************************************************************/
229
230 /* Load the number of first sector of the FAT into eax */
231 movzx eax, word ptr BP_REL(ReservedSectors)
232 add eax, dword ptr BP_REL(HiddenSectors)
233
234 /* Load sector count into ecx */
235 #if FAT32
236 mov ecx, BP_REL(SectorsPerFatBig)
237 #else
238 movzx ecx, word ptr BP_REL(SectorsPerFat)
239 #endif
240
241 /* Save FAT sector and size for later use */
242 pushad
243
244 /* Point ES:DI to the memory that is later the disk read buffer for freeldr.
245 This way we cannot overwrite our FAT with freeldr data */
246 mov bx, DISKREADBUFFER / 16
247 mov es,bx
248 xor di, di
249
250 /* Read the sectors */
251 call ReadSectors
252
253 /* Restore FAT sector and size */
254 popad
255
256
257 /******************************************************************************
258 * Get root directory / data area start *
259 ******************************************************************************/
260
261 /* Copy reserved + hidden sectors to EBX */
262 mov ebx, eax
263
264 /* Calculate (NumberOfFats * SectorsPerFat) */
265 movzx eax, byte ptr BP_REL(NumberOfFats)
266 mul ecx
267
268 /* Add reserved sectors and hidden sectors */
269 add eax, ebx
270
271 #ifndef FAT32
272 /* Save the starting sector of the root directory */
273 mov dword ptr BP_REL(RootDirStartSector), eax
274
275 /* Calculate number of sectors for the root dir:
276 sectors = MaxRootEntries * 32 / 512 (rounded up!) */
277 movzx ebx, word ptr BP_REL(MaxRootEntries)
278 add ebx, 15
279 shr ebx, 4
280
281 /* Add the root dir start sector and save it as DataAreaStart */
282 add ebx, eax
283 mov dword ptr BP_REL(DataAreaStart), ebx
284 #else
285 mov dword ptr BP_REL(DataAreaStart), eax
286
287 /* On FAT32 volumes the root dir start cluster is stored in the BPB */
288 mov eax, dword ptr BP_REL(RootDirStartCluster)
289 #endif
290
291
292 /******************************************************************************
293 * Search the root directory for freeldr *
294 ******************************************************************************/
295 .SearchForFreeldr:
296
297 /* Load ES with the segment where we put the dir entries */
298 mov bx, ADDRESS_FOR_DIRENTRIES / 16
299 mov es, bx
300
301 /* Set the address offset to 0 */
302 xor di, di
303
304 #ifdef FAT32
305 /* Read the dir cluster. This loads the next cluster into EAX */
306 call ReadCluster
307
308 /* Calculate the numer of dir entries in this cluster:
309 dx = SectorsPerCluster * 512 / 32 */
310 movzx dx, byte ptr ds:[SectorsPerCluster]
311 shl dx, 4
312 #else
313 /* Set the number of sectors to read to 1 */
314 xor cx, cx
315 inc cx
316
317 /* Read the sector, but preserve ES */
318 push es
319 call ReadSectors
320 pop es
321
322 /* Set entry count to entries per sector */
323 mov dx, (512 / 32)
324 #endif
325
326 /* Load the start offset of the dir entries into ebx */
327 xor bx, bx
328
329 .CheckDirEntry:
330 /* Load the address of the name into di */
331 mov di, bx
332
333 /* If the first byte of the entry is 0 then we have reached the end */
334 cmp byte ptr es:[di], ch
335 jz BootFailure
336
337 /* Compare with freeldr file name */
338 mov si, offset filename
339 mov cx, 11
340 repe cmpsb
341
342 /* Check if we found the file */
343 jz .FoundFreeLoader
344
345 /* File didn't match, go to next entry */
346 add bx, 32
347
348 /* Decrement entry count and check if we reached the end */
349 dec dx
350 jnz .CheckDirEntry
351
352 #if FAT32
353 /* Check to see if this was the last cluster in the chain */
354 cmp eax, HEX(0ffffff8)
355 jnb BootFailure
356 #endif
357
358 /* Repeat the search process with the next sector / cluster.
359 eax is already incremented in ReadSectors / ReadCluster */
360 jmp .SearchForFreeldr
361
362
363 /******************************************************************************
364 * Load freeldr *
365 ******************************************************************************/
366 .FoundFreeLoader:
367
368 /* Load the cluster number of freeldr into eax */
369 #if FAT32
370 #error unsupported
371 #else
372 movzx eax, word ptr es:[bx + HEX(1A)]
373 #endif
374
375 /* Load es:di with the freeldr start address */
376 mov dx, FREELDR_BASE / 16
377 mov es, dx
378 xor di, di
379
380 .LoadNextCluster:
381 /* Load the cluster to the current address. EAX is adjusted to the next
382 cluster and ES is adjusted for the next read */
383 call ReadCluster
384
385 /* Check if this is the last cluster in the chain */
386 #if FAT32
387 cmp eax, HEX(0ffffff8)
388 #elif FAT12
389 cmp ax, HEX(0ff8)
390 #else
391 cmp ax, HEX(0fff8)
392 #endif
393 jb .LoadNextCluster
394
395 /* Load boot drive into DL, boot partition into DH */
396 mov dl, byte ptr ds:[BootDrive]
397 mov dh, byte ptr ds:[BootPartition]
398
399 /* Now the complete freeldr imag is loaded.
400 Jump to the realmode entry point. */
401 ljmp16 0, FREELDR_BASE
402
403
404
405 BootFailure:
406 mov si, offset msgBootFailure
407 call PutChars
408
409
410 Reboot:
411 /* Output "Press any key to reboot" message */
412 mov si, offset msgAnyKey
413 call PutChars
414
415 /* Wait for a keypress */
416 xor ax, ax
417 int HEX(16)
418
419 /* Reboot */
420 int HEX(19)
421
422
423 /******************************************************************************
424 * PROCEDURE ReadCluster *
425 * Input: EAX = Cluster number, ES:DI = Target *
426 * Modifies: EAX (next cluster number), BX, DX (undefined) *
427 ******************************************************************************/
428 ReadCluster:
429
430 pushad
431
432 // StartSector = ((Cluster - 2) * SectorsPerCluster) + SectorsForFat + ReservedSectors + HiddenSectors
433 // StartSector = ((Cluster - 2) * SectorsPerCluster) + DataAreaStart
434
435 /* Substract 2 */
436 dec eax
437 dec eax
438
439 /* Multiply with SectorsPerCluster */
440 movzx ecx, byte ptr BP_REL(SectorsPerCluster)
441 mul ecx
442
443 /* Add DataAreaStart */
444 add eax, dword ptr BP_REL(DataAreaStart)
445
446 /* Call ReadSectors. EAX = SectorNumber, ECX = SectorsPerCluster */
447 call ReadSectors
448
449 /* Restore the cluster number */
450 popad
451
452 /* Save ES */
453 push es
454
455 #if FAT32
456 #error FAT23 not implemented
457 #elif FAT12
458 #error FAT12 not implemented
459 #else
460 /* DX:AX = AX * 2 (since FAT16 entries are 2 bytes) */
461 mov bx, 2
462 mul bx
463
464 /* Shift DX, so that it is the segment offset: DX = DX * (64K / 16) */
465 shl dx, 12
466
467 /* Put segment address of FAT into ES */
468 add dx, DISKREADBUFFER / 16
469 mov es, dx
470
471 /* Put the FAT entry offset into EBX for indirect mov */
472 mov bx, ax
473
474 /* Put the content of the FAT entry into AX */
475 mov ax, es:[bx]
476 #endif
477
478 /* Restore ES and return */
479 pop es
480 ret
481
482
483 /******************************************************************************
484 * PROCEDURE ReadSectors *
485 * Input: EAX = Sector start number, ECX = number of sectors, ES:DI = Target *
486 * Modifies: EAX (incremented by sector count), CX = 0, ES (incremented), *
487 * EBX undefined *
488 ******************************************************************************/
489 ReadSectors:
490 /* We could possibly also implement CHS, but it's currently unimplemented */
491 //jmp $
492 ReadSectorsLBA:
493
494 /* Copy number of sectors to ebx */
495 movzx ebx, cx
496
497 /* Since the LBA calls only support 0x7F sectors at a time,
498 we will limit ourselves to 64 */
499 cmp bx, 64
500 jbe .ReadSectorsLBA2
501 mov bx, 64
502
503 .ReadSectorsLBA2:
504
505 /* Save logical sector number & sector count */
506 pushad
507
508 /* Setup the disk address packet on the stack */
509 .byte HEX(66) // size overwrite prefix for next push
510 push 0 // Put 64-bit logical block address (high part) on stack
511 push eax // Put 64-bit logical block address (low part) on stack
512 push es // Put transfer segment on stack
513 push di // Put transfer offset on stack
514 push bx // Set transfer count (for this round)
515 push 16 // Set size of packet to 16
516
517 /* Point si to the disk address packet on stack */
518 mov si, sp
519
520 /* Set the drive number */
521 mov dl, byte ptr ds:[BootDrive]
522 //jmp $
523 /* Call INT 13h, AH = 42h - Extended Read
524 Input: ...
525 Modifies: ... */
526 mov ah, HEX(42)
527 int HEX(13)
528 //jmp $
529 /* Check for failure */
530 jc BootFailure
531
532 /* Remove disk address packet from stack */
533 add sp, 16
534
535 /* Adjust ES to point to the next sector */
536 shl bx, 5
537 mov ax, es
538 add ax, bx
539 mov es, ax
540
541 /* Restore sector count & logical sector number */
542 popad
543
544 /* Adjust the sector number to the next sector we need to read
545 by adding the number of sectors that we read */
546 add eax, ebx
547
548 /* Adjust remaining sectors */
549 sub cx, bx
550 jnz ReadSectorsLBA
551
552 /* return */
553 ret
554
555
556
557 /******************************************************************************
558 * PROCEDURE PutChars *
559 * Input: ESI = Points to string to be printed *
560 * Modifies: AL, AH, SI *
561 ******************************************************************************/
562 PutChars2:
563 mov ah, HEX(0e)
564 mov bx, 7
565 int HEX(10)
566 PutChars:
567 lodsb
568 or al, al
569 jnz short PutChars2
570 ret
571
572
573 /******************************************************************************
574 * Padding and boot sector signature *
575 ******************************************************************************/
576 /* Pad to 509 bytes */
577 .org 509
578
579 BootPartition:
580 .byte 0
581
582 BootSignature:
583 .word HEX(0aa55) // BootSector signature
584
585 .endcode16
586
587 END