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