xref: /reactos/boot/freeldr/bootsect/pc98/fat12fdd.S (revision 177ae91b)
1/*
2 * PROJECT:     FreeLoader
3 * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE:     FAT12 file system boot sector for NEC PC-98 series
5 * NOTES:       The source code in this file is based on the Brian Palmer's work
6 *              (boot/freeldr/bootsect/fat.S)
7 * COPYRIGHT:   Copyright 2019 Dmitry Borisov (di.sean@protonmail.com)
8 */
9
10#include <asm.inc>
11#include <freeldr/include/arch/pc/x86common.h>
12
13#define BP_REL(x) [bp + x - offset start]
14#define VGA_WIDTH 80
15#define VGA_HEIGHT 25
16
17/*
18 * See https://www.webtech.co.jp/company/doc/undocumented_mem/memsys.txt
19 * At segment 0000h
20 */
21#define BOOT_DAUA HEX(0584)
22
23DataAreaStartHigh    = 2
24DataAreaStartLow     = 4
25BiosCHSDriveSizeHigh = 6
26BiosCHSDriveSizeLow  = 8
27BiosCHSDriveSize     = 8
28ReadSectorsOffset    = 10
29ReadClusterOffset    = 12
30PutCharsOffset       = 14
31BootSectorStackTop   = HEX(7C00) - 16
32
33if 0
34.macro DEBUG_STOP
35    hlt
36    jmp short $ - 1
37.endm
38
39.macro DEBUG_STEP
40    push ax
41    xor ah, ah
42    int HEX(18)                                 // Wait for a keypress
43    pop ax
44.endm
45endif
46
47// org 7C00h
48
49.code16
50
51start:
52    jmp main
53    nop
54
55// After running fatten tool it overwrites (BPB & EBPB)
56OEMName:
57    .ascii "FrLdr1.0"
58BytesPerSector:
59    .word 512
60SectsPerCluster:
61    .byte 1
62ReservedSectors:
63    .word 1
64NumberOfFats:
65    .byte 2
66MaxRootEntries:
67    .word 224
68TotalSectors:
69    .word 2880
70MediaDescriptor:
71    .byte HEX(0f0)
72SectorsPerFat:
73    .word 9
74SectorsPerTrack:
75    .word 18
76NumberOfHeads:
77    .word 2
78HiddenSectors:
79    .long 0
80TotalSectorsBig:
81    .long 0
82BootDrive:
83    .byte HEX(0ff)
84Reserved:
85    .byte 0
86ExtendSig:
87    .byte HEX(29)
88SerialNumber:
89    .long 00000000
90VolumeLabel:
91    .ascii "NO NAME    "
92FileSystem:
93    .ascii "FAT12   "
94
95/*
96 * Real-mode entry point
97 */
98main:
99    xor ax, ax                                  // Setup segment registers:
100    mov ds, ax                                  // Make DS correct
101    mov es, ax                                  // Make ES correct
102    mov ss, ax                                  // Make SS correct
103    mov bp, HEX(7C00)
104    mov sp, BootSectorStackTop                  // Stack grows downwards from BootSectorStackTop
105
106if 0                                            // It would have been nice to have had this check...
107    mov ax, HEX(1000)                           // Detecting hardware
108
109    /*
110     * INSTALLATION CHECK interrupt
111     * See http://www.ctyme.com/intr/rb-2293.htm
112     */
113    int HEX(1A)
114    cmp ax, HEX(1000)
115    je HardwareError                            // An IBM-compatible PC
116endif
117
118    /*
119     * IBM PC here:       NEC PC-98 here:
120     * ESI   = 000E:0000  ESI   = 0000:0000
121     * EDI   = 0000:070C  EDI   = 0000:0000
122     * EBP   = 0000:7C00  EBP   = 0000:7C00
123     * ESP   = 0000:7BF0  ESP   = 0000:7BF0
124     * CS:IP = 0000:7C4E  CS:IP = 1FE0:004E
125    */
126    mov ax, word ptr ds:[BOOT_DAUA]             // Get the Device Address/Unit Address (DA/UA)
127    mov si, ax
128
129    push si
130    push cs
131    pop ds
132    xor si, si
133    mov ax, HEX(0000)
134    mov es, ax
135    mov di, HEX(7C00)
136    mov cx, 512
137    rep movsw                                   // Move our 512 bytes boot sector to the [0000:7C00]
138    pop si
139
140    ljmp16 0, relocated                         // Jump into relocated code
141
142relocated:
143    xor ax, ax                                  // Clean-up segments
144    mov es, ax
145    mov ds, ax
146
147    test byte ptr ds:[HEX(501)], HEX(08)        // High-resolution mode check
148    jz VideoTestNormalMode
149    mov ax, HEX(0E000)
150    jmp short VideoTestDone
151VideoTestNormalMode:
152    mov ax, HEX(0A000)
153VideoTestDone:
154    mov word ptr BP_REL(VramSegment), ax
155
156    mov ax, si
157    mov byte ptr BP_REL(BootDrive), al          // Save the boot drive
158
159    /*
160     * Now we must find our way to the first sector of the root directory
161     *
162     * LBA = NumberOfFats * SectorsPerFat + HiddenSectors + ReservedSectors
163     */
164    xor ax, ax
165    xor cx, cx
166    mov al, byte ptr BP_REL(NumberOfFats)       // Number of fats
167    mul word ptr BP_REL(SectorsPerFat)          // Times sectors per fat
168    add ax, word ptr BP_REL(HiddenSectors)
169    adc dx, word ptr BP_REL(HiddenSectors + 2)  // Add the number of hidden sectors
170    add ax, word ptr BP_REL(ReservedSectors)    // Add the number of reserved sectors
171    adc dx, cx                                  // Add carry bit
172    mov word ptr [bp - DataAreaStartLow], ax    // Save the starting sector of the root directory
173    mov word ptr [bp - DataAreaStartHigh], dx   // Save it in the first 4 bytes before the boot sector
174    mov si, word ptr BP_REL(MaxRootEntries)     // Get number of root dir entries in SI
175    pusha                                       // Save 32-bit logical start sector of root dir
176    // DX:AX now has the number of the starting sector of the root directory
177
178    /*
179     * Now calculate the size of the root directory
180     *
181     * Root directory sectors = (MaxRootEntries * 32 + BytesPerSector - 1) / BytesPerSector
182     */
183    xor dx, dx
184    mov ax, 32                                  // Size of dir entry
185    mul si                                      // Times the number of entries
186    mov bx, word ptr BP_REL(BytesPerSector)
187    add ax, bx
188    dec ax
189    div bx                                      // Divided by the size of a sector
190    // AX now has the number of root directory sectors
191
192    add word ptr [bp - DataAreaStartLow], ax    // Add the number of sectors of the root directory to our other value
193    adc word ptr [bp - DataAreaStartHigh], cx   // Now the first 4 bytes before the boot sector contain the starting sector of the data area
194    popa
195
196/*
197 * Reads root directory into [0000:7E00] and finds 'FREELDR SYS'
198 *
199 * Call with:
200 *
201 * DX:AX - LBA of the starting sector of the root directory
202 */
203LoadRootDirSector:
204    mov bx, HEX(7E0)                            // We will load the root directory sector
205    mov es, bx                                  // Right after the boot sector in memory
206    xor bx, bx                                  // We will load it to [0000:7E00]
207    xor cx, cx                                  // Zero out CX
208    inc cx                                      // Now increment it to 1, we are reading one sector
209    xor di, di                                  // Zero out di
210    push es                                     // Save ES because it will get incremented by 20h
211    call ReadSectors                            // Read the first sector of the root directory
212    pop es                                      // Restore ES (ES:DI = 7E0:0000)
213
214SearchRootDirSector:
215    cmp byte ptr es:[di], ch                    // If the first byte of the directory entry is zero then we have
216    jz PrintFileNotFound                        // reached the end of the directory and FREELDR.SYS is not here so reboot
217    pusha                                       // Save all registers
218    mov cl, 11                                  // Put 11 in cl (length of filename in directory entry)
219    mov si, offset filename                     // Put offset of filename string in DS:SI
220    repe cmpsb                                  // Compare this directory entry against 'FREELDR SYS'
221    popa                                        // Restore all the registers
222    jz FoundFreeLoader                          // If we found it then jump
223    dec si                                      // SI holds MaxRootEntries, subtract one
224    jz PrintFileNotFound                        // If we are out of root dir entries then reboot
225    add di, 32                                  // Increment DI by the size of a directory entry
226    cmp di, HEX(0200)                           // Compare DI to 512 (DI has offset to next dir entry, make sure we haven't gone over one sector)
227    jc SearchRootDirSector                      // If DI is less than 512 loop again
228    jmp short LoadRootDirSector                 // Didn't find FREELDR.SYS in this directory sector, try again
229
230FoundFreeLoader:
231    /*
232     * We found freeldr.sys on the disk
233     * so we need to load the first 512 bytes of it to [0000:F800]
234     * ES:DI has dir entry (ES:DI == 07E0:XXXX)
235     */
236    mov ax, word ptr es:[di + HEX(1A)]          // Get start cluster
237    push ax                                     // Save start cluster
238    push FREELDR_BASE / 16                      // Put load segment on the stack and load it
239    pop es                                      // Into ES so that we load the cluster at [0000:F800]
240    call ReadCluster                            // Read the cluster
241    pop ax                                      // Restore start cluster of FreeLoader
242
243    /*
244     * Save the addresses of needed functions so
245     * the helper code will know where to call them
246     */
247    mov word ptr [bp - ReadSectorsOffset], offset ReadSectors   // Save the address of ReadSectors
248    mov word ptr [bp - ReadClusterOffset], offset ReadCluster   // Save the address of ReadCluster
249    mov word ptr [bp - PutCharsOffset], offset PrintString      // Save the address of PrintString
250
251    /*
252     * Now AX has start cluster of FreeLoader and we
253     * have loaded the helper code in the first 512 bytes
254     * of FreeLoader to 0000:F800. Now transfer control
255     * to the helper code. Skip the first three bytes
256     * because they contain a jump instruction to skip
257     * over the helper code in the FreeLoader image
258     */
259    ljmp16 0, FREELDR_BASE + 3
260
261/*
262 * Reads cluster number in AX into [ES:BX]
263 *
264 * Call with:
265 *
266 * AX - cluster number
267 * ES:BX - buffer to read data into
268 */
269ReadCluster:
270    /*
271     * StartSector = ((Cluster - 2) * SectorsPerCluster) + ReservedSectors + HiddenSectors
272     */
273    dec ax                                      // Adjust start cluster by 2
274    dec ax                                      // Because the data area starts on cluster 2
275    xor ch, ch
276    mov cl, byte ptr BP_REL(SectsPerCluster)
277    mul cx                                      // Times sectors per cluster
278    add ax, [bp - DataAreaStartLow]             // Add start of data area
279    adc dx, [bp - DataAreaStartHigh]            // Now we have DX:AX with the logical start sector of FREELDR.SYS
280    xor bx, bx                                  // We will load it to [ES:0000], ES loaded before function call
281
282/*
283 * Reads logical sectors into [ES:BX]
284 *
285 * Call with:
286 *
287 * DX:AX - logical sector number to read (LBA value)
288 * ES:BX - buffer to read data into
289 * CX - number of sectors to read
290 */
291ReadSectors:
292
293    .ReadSectorsLoop:
294        pusha
295
296        /*
297         * Converting LBA (Linear Block Address) into a format CHS (Cylinder:Head:Sector)
298         *
299         * C = (LBA / SPT) / HPC
300         * H = (LBA / SPT) % HPC
301         * S = (LBA % SPT) + 1
302         */
303        xchg ax, cx
304        xchg ax, dx
305        xor dx, dx
306        div word ptr BP_REL(SectorsPerTrack)
307        xchg ax, cx
308        div word ptr BP_REL(SectorsPerTrack)    // Divide logical by SectorsPerTrack
309        inc dx                                  // Sectors numbering starts at 1 not 0
310        xchg cx, dx
311        div word ptr BP_REL(NumberOfHeads)      // Number of heads
312
313        mov dh, dl                              // DH - head number (0-1)
314        mov dl, cl                              // DL - sector number (1-26)
315        mov cl, al                              // CL - cylinder number (0-76)
316
317        // TODO: This should be calculated using the equation: BytesPerSector = (CH + 1) * 128
318        mov ch, 2                               // CH - sector size (0-4): 0 (128), 1 (256), 2 (512), 3 (1024), 4 (2048)
319
320        mov al, byte ptr BP_REL(BootDrive)      // AL - DA/UA
321        push bp
322        push bx
323        mov bx, word ptr BP_REL(BytesPerSector) // BX - bytes to read
324        pop bp                                  // ES:BP - buffer to read data into
325        mov ah, HEX(56)                         // AH - read sectors from a floppy disk with SEEK, and use double-density format (MFM)
326
327        /*
328         * Disk BIOS interrupt
329         * See http://radioc.web.fc2.com/column/pc98bas/bios/int1b_06.htm
330         */
331        int HEX(1b)
332
333        pop bp
334        jc PrintDiskError                       // CF set on failure
335
336        popa
337
338        inc ax                                  // Increment sector to read
339        jnz .NoCarryCHS
340        inc dx
341
342    .NoCarryCHS:
343        push bx
344        mov bx, es
345        add bx, HEX(20)                         // Add size of dir entry to the buffer address for the next sector
346        mov es, bx
347        pop bx
348    loop .ReadSectorsLoop                       // Increment read buffer for next sector, read next sector
349
350    ret
351
352/*
353 * Prints a character
354 *
355 * Call with:
356 *
357 * AL - ASCII code
358 */
359PutChar:
360    push di
361    push es
362
363    push word ptr BP_REL(VramSegment)
364    pop es
365    mov di, word ptr BP_REL(VramOffset)         // ES:DI = VramSegment:VramOffset
366    .PutCharWrite:
367        xor ah, ah
368        stosw                                   // Write ASCII directly to the VRAM
369
370        mov word ptr BP_REL(VramOffset), di
371    pop es
372    pop di
373
374    ret
375
376/*
377 * Prints a null-terminated string
378 *
379 * Call with:
380 *
381 * DS:SI - pointer to a string
382 */
383PrintString:
384    xor ah, ah
385    lodsb                                       // Get a single char from a ptr
386
387    or al, al
388    jz short .PrintEnd                          // Found NULL
389
390    cmp al, HEX(0D)
391    jz short .PrintStringHandleCR               // Found CR
392
393    call PutChar
394    jmp short PrintString
395
396    .PrintStringHandleCR:
397        mov ax, word ptr BP_REL(VramOffset)
398        mov dl, VGA_WIDTH * 2
399        div dl
400        inc ax
401        mul dl
402        mov word ptr BP_REL(VramOffset), ax
403        inc si                                  // Skip the next LF character
404     jmp short PrintString
405
406.PrintEnd:
407    ret
408
409if 0
410/*
411 * Displays a hardware error message and reboots
412 */
413HardwareError:
414    mov si, offset msgHardwareError
415
416    .PrintStringVGA:
417        lodsb                                   // Get a single char from a ptr
418
419        or al, al
420        jz short .HardwareErrorDone             // Found NULL
421
422        mov ah, HEX(0E)                         // Teletype output
423        mov bx, 7                               // BH - video page number, BL - foreground color
424        int HEX(10)                             // Display a character via TTY mode
425    jmp short .PrintStringVGA
426
427.HardwareErrorDone:
428    xor ax, ax
429    int HEX(16)                                 // Wait for a keypress
430    int HEX(19)                                 // Reboot
431endif
432
433/*
434 * Displays a disk error message and reboots
435 */
436PrintDiskError:
437    mov si, offset msgDiskError                 // Disk error message
438    call PrintString                            // Display it
439
440    jmp short Reboot
441
442/*
443 * Displays a file not found error message and reboots
444 */
445PrintFileNotFound:
446    mov si, offset msgNotFoundError             // FreeLdr not found message
447    call PrintString                            // Display it
448
449    jmp short Reboot
450
451/*
452 * Reboots the computer after keypress
453 */
454Reboot:
455    mov si, offset msgAnyKey                    // Press any key message
456    call PrintString                            // Display it
457
458    xor ax, ax
459    int HEX(18)                                 // Wait for a keypress
460
461    /*
462     * Activate the CPU reset line
463     * See https://people.freebsd.org/~kato/pc98-arch.html#cpureset
464     * and http://www.webtech.co.jp/company/doc/undocumented_mem/io_cpu.txt
465     */
466    xor ax, ax
467    out HEX(0F0), al
468
469    hlt
470Halt:
471    jmp short Halt                              // Spin
472
473VramSegment:
474    .word 0
475VramOffset:
476    .word 0
477msgDiskError:
478    .ascii "ERR", CR, LF, NUL
479msgNotFoundError:
480    .ascii "NFE", CR, LF, NUL
481msgAnyKey:
482    .ascii "Press any key", NUL
483filename:
484    .ascii "FREELDR SYS"
485
486if 0                                            // So totally out of space here...
487msgHardwareError:
488    .ascii "It's not PC-98", NUL
489endif
490
491    .org 509                                    // Pad to 509 bytes
492
493BootPartition:
494    .byte 0
495
496BootSignature:
497    .word HEX(0AA55)                            // BootSector signature
498
499.endcode16
500
501END
502