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