xref: /reactos/boot/freeldr/bootsect/isoboot.S (revision 764881a9)
1/*
2 * PROJECT:     ReactOS Boot Sector for ISO file system (based on ISOLINUX)
3 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE:     Booting ReactOS off a CD-ROM using the El Torito boot standard in "no emulation mode"
5 * COPYRIGHT:   Copyright 1994-2009 H. Peter Anvin
6 *              Copyright 2002 Michael K. Ter Louw
7 *              Copyright 2002 Eric Kohl
8 *              Copyright 2009 Intel Corporation *author: H. Peter Anvin
9 *              Copyright 2011 Timo Kreuzer (timo.kreuzer@reactos.org)
10 *              Copyright 2017 Colin Finck (colin@reactos.org)
11 */
12
13/* INCLUDES ******************************************************************/
14#include <asm.inc>
15#include <freeldr/include/arch/pc/x86common.h>
16
17#ifndef ROS_REGTEST
18#define WAIT_FOR_KEY
19#endif
20
21
22.code16
23ASSUME CS:.text, DS:.text, ES:.text
24
25/* CONSTANTS ******************************************************************/
26BIOS_timer = HEX(046C)      // Timer ticks (1 word)
27BIOS_magic = HEX(0472)      // BIOS reset magic (1 word)
28
29// Memory below this point is reserved for the BIOS and the MBR
30trackbuf = HEX(1000)        // Track buffer goes here (8192 bytes)
31trackbufsize = 8192         // trackbuf ends at 3000h
32
33// struct open_file_t
34file_sector = 0             // Sector pointer (0 = structure free)
35file_bytesleft = 4          // Number of bytes left
36file_left = 8               // Number of sectors left
37// Another unused DWORD follows here in ISOLINUX
38#define open_file_t_size 16
39
40// struct dir_t
41dir_lba = 0                 // Directory start (LBA)
42dir_len = 4                 // Length in bytes
43dir_clust = 8               // Length in clusters
44#define dir_t_size 12
45
46MAX_OPEN_LG2 = 2            // log2(Max number of open files)
47MAX_OPEN = 4
48SECTOR_SHIFT = 11           // 2048 bytes/sector (El Torito requirement)
49SECTOR_SIZE = 2048
50retry_count = 6             // How patient are we with the BIOS?
51
52/* UNINITIALIZED VARIABLES ****************************************************/
53absolute HEX(5000)          // Here we keep our BSS stuff
54
55resb ISOFileName, 64        // ISO filename canonicalization buffer
56resb ISOFileNameEnd, 1
57resb CurrentDir, dir_t_size // Current directory
58resb RootDir, dir_t_size    // Root directory
59resb DiskSys, 2             // Last INT 13h call
60resb GetlinsecPtr, 2        // The sector-read pointer
61resb DiskError, 1           // Error code for disk I/O
62resb DriveNumber, 1         // CD-ROM BIOS drive number
63resb ISOFlags, 1            // Flags for ISO directory search
64resb RetryCount, 1          // Used for disk access retries
65
66//align open_file_t_size
67absolute HEX(5070)
68resb Files, (MAX_OPEN * open_file_t_size)
69
70
71/* ENTRY POINTS ***************************************************************/
72
73// Entry point when booted from CD (El Torito standard)
74start:
75    mov bx, offset getlinsec_cdrom
76    // Fall through
77
78start_common:
79    // Set up our stack and a flat addressing model.
80    cli
81    xor ax, ax
82    mov ss, ax
83    mov sp, offset start
84    mov ds, ax
85    mov es, ax
86    mov fs, ax
87    mov gs, ax
88    sti
89
90    // Our boot sector has been loaded to address 0x7C00.
91    // Relocate our 2048 bytes boot sector to the given base address (should be 0x7000).
92    cld
93    mov cx, 2048 / 4
94    mov si, HEX(7C00)
95    mov di, offset start
96    rep movsd
97
98    ljmp16 0, relocated     // jump into relocated code
99
100.org 64
101hybrid_signature:
102    .long HEX(7078c0fb)
103
104// Entry point when booted through ISOMBR from a drive (isohybrid mode)
105start_hybrid:
106    mov bx, offset getlinsec_ebios
107    jmp start_common
108
109relocated:
110    // Save our passed variables (BX from the entry point, DL from the BIOS) before anybody clobbers the registers.
111    mov word ptr ds:[GetlinsecPtr], bx
112    mov byte ptr ds:[DriveNumber], dl
113
114    // Make sure the keyboard buffer is empty
115    call pollchar_and_empty
116
117    // If we're booting in hybrid mode and our boot drive is the first HDD (drive 80h),
118    // we have no other option than booting into FREELDR.
119    cmp word ptr ds:[GetlinsecPtr], offset getlinsec_ebios
120    jne .read_mbr
121    cmp byte ptr ds:[DriveNumber], HEX(80)
122    je .boot_freeldr
123
124.read_mbr:
125    // Read the first sector (MBR) from the first hard disk (drive 80h) to 7C00h.
126    // If we then decide to boot from HDD, we already have it at the right place.
127    // In case of an error (indicated by the Carry Flag), just boot FREELDR from our ReactOS medium.
128    mov ax, HEX(0201)
129    mov dx, HEX(0080)
130    mov cx, HEX(0001)
131    mov bx, HEX(7C00)
132    call int13
133    jc .boot_freeldr
134
135    // Verify the signature of the read MBR.
136    // If it's invalid, there is probably no OS installed and we just boot FREELDR from our ReactOS medium.
137    mov ax, word ptr ds:[HEX(7C00)+510]
138    cmp ax, HEX(AA55)
139    jne .boot_freeldr
140
141#ifdef WAIT_FOR_KEY
142    // We could either boot from the ReactOS medium or from hard disk. Let the user decide!
143    // Display the 'Press key' message.
144    call crlf_early
145    mov si, offset presskey_msg
146    call writestr_early
147
148    // Count down 5 seconds.
149    mov cx, 5
150
151.next_second:
152    // Count in seconds using the BIOS Timer, which runs roughly at 19 ticks per second.
153    // Load its value plus one second into EAX for comparison later.
154    mov eax, ds:[BIOS_timer]
155    add eax, 19
156
157.poll_again:
158    // Check for a keypress, boot FREELDR from our ReactOS medium if a key was pressed.
159    call pollchar_and_empty
160    jnz .boot_freeldr
161
162    // Check if another second has passed (in BIOS Timer ticks).
163    mov ebx, ds:[BIOS_timer]
164    cmp eax, ebx
165    jnz .poll_again
166
167    // Another second has passed, so print the dot and decrement the second counter.
168    // If the user hasn't pressed a key after the entire 5 seconds have elapsed, just boot from the first hard disk.
169    mov si, offset dot_msg
170    call writestr_early
171    dec cx
172    jz .boot_harddisk
173    jmp .next_second
174#endif
175
176.boot_harddisk:
177    // Restore a clean context for the hard disk MBR and boot the already loaded MBR.
178    call crlf_early
179    mov ax, cs
180    mov ds, ax
181    mov es, ax
182    mov fs, ax
183    mov gs, ax
184    mov dx, HEX(0080)
185
186    ljmp16 0, HEX(7C00)
187
188.boot_freeldr:
189#ifdef WAIT_FOR_KEY
190    call crlf_early
191    call crlf_early
192#endif
193
194    // The BIOS gave us a boot drive number, so in a perfect world we could just use that one now.
195    // Unfortunately, there are many broken BIOSes around, which is why ISOLINUX verifies it and applies some hacks if the number is wrong.
196    // Let's do exactly the same here to achieve maximum compatibility.
197
198    // Don't do this if we are running in hybrid mode.
199    cmp word ptr ds:[GetlinsecPtr], offset getlinsec_ebios
200    je found_drive
201
202    // Use the INT 13 function 4B01h (Get Disk Emulation Status) to fetch the El Torito Spec Packet.
203    // We can use this information to verify that our passed boot drive number really belongs to our CD.
204    mov ax, HEX(4B01)
205    mov dl, byte ptr ds:[DriveNumber]
206    mov si, offset spec_packet
207    call int13
208
209    // If this INT 13 function yields an error, we may be on a broken AWARD BIOS.
210    // Check this and patch if possible.
211    jc award_hack
212
213    // Check that our passed boot drive number and the number in the Spec Packet match.
214    // If not, try some workarounds to find our drive anyway.
215    mov dl, byte ptr ds:[DriveNumber]
216    cmp byte ptr ds:[sp_drive], dl
217    jne spec_query_failed
218
219found_drive:
220    // Clear Files structures
221    mov di, Files
222    mov cx, (MAX_OPEN*open_file_t_size)/4
223    xor eax, eax
224    rep stosd
225
226    // Read the entire 2K-sized ISO9660 Primary Volume Descriptor at sector 16 (32K).
227    // This calculation only holds for single-session ISOs, but we should never encounter anything else.
228    mov eax, 16
229    mov bx, trackbuf
230    call getonesec
231
232    // Read the LBA address (offset 2 in the Directory Record) of the root directory (offset 156 in the Primary Volume Descriptor).
233    mov eax, dword ptr ds:[trackbuf+156+2]
234    mov dword ptr ds:[RootDir+dir_lba], eax
235    mov dword ptr ds:[CurrentDir+dir_lba], eax
236
237    // Read the data length (offset 10 in the Directory Record) of the root directory (offset 156 in the Primary Volume Descriptor).
238    mov eax, dword ptr ds:[trackbuf+156+10]
239    mov dword ptr ds:[RootDir+dir_len], eax
240    mov dword ptr ds:[CurrentDir+dir_len], eax
241
242    // Calculate the number of clusters and write that to our RootDir and CurrentDir structures.
243    add eax, SECTOR_SIZE-1
244    shr eax, SECTOR_SHIFT
245    mov dword ptr ds:[RootDir+dir_clust],eax
246    mov dword ptr ds:[CurrentDir+dir_clust],eax
247
248    // Look for the "LOADER" directory (directory is indicated by AL = 2 when using searchdir_iso).
249    mov di, offset loader_dir
250    mov al, 2
251    call searchdir_iso
252    jnz .dir_found
253
254    // No directory was found, so bail out with an error message.
255    mov si, offset no_dir_msg
256    call writemsg
257    jmp kaboom
258
259.dir_found:
260    // The directory was found, so update the information in our CurrentDir structure.
261    // Free the file pointer entry at SI in the process.
262    mov dword ptr ds:[CurrentDir+dir_len], eax
263    mov eax, dword ptr ds:[si+file_left]
264    mov dword ptr ds:[CurrentDir+dir_clust], eax
265    xor eax, eax
266    xchg eax, dword ptr ds:[si+file_sector]
267    mov dword ptr ds:[CurrentDir+dir_lba], eax
268
269    // Look for the "FREELDR.SYS" file.
270    mov di, offset freeldr_sys
271    call searchdir
272    jnz .freeldr_found
273
274    // The FREELDR file was not found, so bail out with an error message.
275    mov si, offset no_freeldr_msg
276    call writemsg
277    jmp kaboom
278
279.freeldr_found:
280    // Calculate the rounded up number of 2K sectors that need to be read.
281    mov ecx, eax
282    shr ecx, SECTOR_SHIFT
283    test eax, HEX(7FF)
284    jz .load_freeldr
285    inc ecx
286
287.load_freeldr:
288    // Load the entire FREELDR.SYS (parameter CX = FFFFh) to its designated base address FREELDR_BASE.
289    // Using a high segment address with offset 0 instead of segment 0 with offset FREELDR_BASE apparently increases compatibility with some BIOSes.
290    mov bx, FREELDR_BASE / 16
291    mov es, bx
292    xor ebx, ebx
293    mov cx, HEX(FFFF)
294    call getfssec
295
296    // Pass two parameters to FREELDR:
297    //    DL = BIOS Drive Number
298    //    DH = Boot Partition (0 for HDD booting in hybrid mode, FFh for CD booting)
299    movzx dx, byte ptr ds:[DriveNumber]
300    cmp word ptr ds:[GetlinsecPtr], offset getlinsec_ebios
301    je .jump_to_freeldr
302    mov dh, HEX(FF)
303
304.jump_to_freeldr:
305    // Transfer execution to the bootloader.
306    ljmp16 0, FREELDR_BASE
307
308
309/* FUNCTIONS *****************************************************************/
310
311///////////////////////////////////////////////////////////////////////////////
312// Start of BrokenAwardHack --- 10-nov-2002           Knut_Petersen@t-online.de
313///////////////////////////////////////////////////////////////////////////////
314//
315// There is a problem with certain versions of the AWARD BIOS ...
316// the boot sector will be loaded and executed correctly, but, because the
317// int 13 vector points to the wrong code in the BIOS, every attempt to
318// load the spec packet will fail. We scan for the equivalent of
319//
320//     mov ax,0201h
321//     mov bx,7c00h
322//     mov cx,0006h
323//     mov dx,0180h
324//     pushf
325//     call <direct far>
326//
327// and use <direct far> as the new vector for int 13. The code above is
328// used to load the boot code into ram, and there should be no reason
329// for anybody to change it now or in the future. There are no opcodes
330// that use encodings relativ to IP, so scanning is easy. If we find the
331// code above in the BIOS code we can be pretty sure to run on a machine
332// with an broken AWARD BIOS ...
333//
334///////////////////////////////////////////////////////////////////////////////
335award_oldint13:
336    .long 0
337award_string:
338    .byte HEX(0b8),1,2,HEX(0bb),0,HEX(7c),HEX(0b9),6,0,HEX(0ba),HEX(80),1,HEX(09c),HEX(09a)
339
340award_hack:
341    mov si, offset spec_err_msg                         // Moved to this place from
342    call writemsg                                       // spec_query_failed
343
344    mov eax, dword ptr ds:[HEX(13)*4]
345    mov dword ptr ds:[award_oldint13], eax
346
347    push es
348    mov ax, HEX(F000)                                   // ES = BIOS Seg
349    mov es, ax
350    cld
351    xor di, di                                          // start at ES:DI = f000:0
352award_loop:
353    push di                                             // save DI
354    mov si, offset award_string                         // scan for award_string
355    mov cx, 7                                           // length of award_string = 7dw
356    repz cmpsw                                          // compare
357    pop di                                              // restore DI
358    jcxz award_found                                    // jmp if found
359    inc di                                              // not found, inc di
360    jno award_loop
361
362award_failed:
363    pop es                                              // No, not this way :-((
364award_fail2:
365    mov eax, dword ptr ds:[award_oldint13]              // restore the original int
366    or eax, eax                                         // 13 vector if there is one
367    jz spec_query_failed                                // and try other workarounds
368    mov dword ptr ds:[HEX(13)*4], eax
369    jmp spec_query_failed
370
371award_found:
372    mov eax, dword ptr es:[di+HEX(0e)]                  // load possible int 13 addr
373    pop es                                              // restore ES
374
375    cmp eax, dword ptr ds:[award_oldint13]              // give up if this is the
376    jz award_failed                                     // active int 13 vector,
377    mov dword ptr ds:[HEX(13)*4], eax                   // otherwise change 0:13h*4
378
379    mov ax, HEX(4B01)                                   // try to read the spec packet
380    mov dl, byte ptr ds:[DriveNumber]                   // now ... it should not fail
381    mov si, offset spec_packet                          // any longer
382    int HEX(13)
383    jc award_fail2
384
385    jmp found_drive                                     // and leave error recovery code
386///////////////////////////////////////////////////////////////////////////////
387// End of BrokenAwardHack ----            10-nov-2002 Knut_Petersen@t-online.de
388///////////////////////////////////////////////////////////////////////////////
389
390
391// INT 13h, AX=4B01h, DL=<passed in value> failed.
392// Try to scan the entire 80h-FFh from the end.
393spec_query_failed:
394    // some code moved to BrokenAwardHack
395
396    mov dl, HEX(FF)
397
398.test_loop:
399    pusha
400    mov ax, HEX(4B01)
401    mov si, offset spec_packet
402    mov byte ptr ds:[si], HEX(13)                       // Size of buffer
403    call int13
404    popa
405    jc .still_broken
406
407    mov si, offset maybe_msg
408    call writemsg
409    mov al, dl
410    call writehex2
411    call crlf_early
412
413    cmp byte ptr ds:[sp_drive], dl
414    jne .maybe_broken
415
416    // Okay, good enough...
417    mov si, offset alright_msg
418    call writemsg
419.found_drive0:
420    mov byte ptr ds:[DriveNumber], dl
421.found_drive:
422    jmp found_drive
423
424    // Award BIOS 4.51 apparently passes garbage in sp_drive,
425    // but if this was the drive number originally passed in
426    // DL then consider it "good enough"
427.maybe_broken:
428    mov al, byte ptr ds:[DriveNumber]
429    cmp al, dl
430    je .found_drive
431
432    // Intel Classic R+ computer with Adaptec 1542CP BIOS 1.02
433    // passes garbage in sp_drive, and the drive number originally
434    // passed in DL does not have 80h bit set.
435    or al, HEX(80)
436    cmp al, dl
437    je .found_drive0
438
439.still_broken:
440    dec dx
441    cmp dl, HEX(80)
442    jnb .test_loop
443
444    // No spec packet anywhere.  Some particularly pathetic
445    // BIOSes apparently don't even implement function
446    // 4B01h, so we can't query a spec packet no matter
447    // what.  If we got a drive number in DL, then try to
448    // use it, and if it works, then well...
449    mov dl, byte ptr ds:[DriveNumber]
450    cmp dl, HEX(81)                                     // Should be 81-FF at least
451    jb fatal_error                                      // If not, it's hopeless
452
453    // Write a warning to indicate we're on *very* thin ice now
454    mov si, offset nospec_msg
455    call writemsg
456    mov al, dl
457    call writehex2
458    call crlf_early
459    jmp .found_drive                                    // Pray that this works...
460
461fatal_error:
462    mov si, offset nothing_msg
463    call writemsg
464
465.norge:
466    jmp short .norge
467
468//
469// searchdir:
470//
471//      Open a file
472//
473//          On entry:
474//              DS:DI   = filename
475//          If successful:
476//              ZF clear
477//              SI      = file pointer
478//              EAX     = file length in bytes
479//          If unsuccessful
480//              ZF set
481//
482// Assumes CS == DS == ES, and trashes BX and CX.
483//
484// searchdir_iso is a special entry point for ISOLINUX only.  In addition
485// to the above, searchdir_iso passes a file flag mask in AL.  This is useful
486// for searching for directories.
487//
488alloc_failure:
489    xor ax, ax                                  // ZF <- 1
490    ret
491
492searchdir:
493    xor al, al
494searchdir_iso:
495    mov byte ptr ds:[ISOFlags], al
496    call allocate_file                          // Temporary file structure for directory
497    jnz alloc_failure
498    push es
499    push ds
500    pop es                                      // ES = DS
501    mov si, offset CurrentDir
502    cmp byte ptr ds:[di], '/'                   // If filename begins with slash
503    jne .not_rooted
504    inc di                                      // Skip leading slash
505    mov si, offset RootDir                      // Reference root directory instead
506.not_rooted:
507    mov eax, dword ptr ds:[si+dir_clust]
508    mov dword ptr ds:[bx+file_left], eax
509    shl eax, SECTOR_SHIFT
510    mov dword ptr ds:[bx+file_bytesleft], eax
511    mov eax, dword ptr ds:[si+dir_lba]
512    mov dword ptr ds:[bx+file_sector], eax
513    mov edx, dword ptr ds:[si+dir_len]
514
515.look_for_slash:
516    mov ax, di
517.scan:
518    mov cl, byte ptr ds:[di]
519    inc di
520    and cl, cl
521    jz .isfile
522    cmp cl, '/'
523    jne .scan
524    mov byte ptr ds:[di-1], 0                   // Terminate at directory name
525    mov cl, 2                                   // Search for directory
526    xchg cl, byte ptr ds:[ISOFlags]
527
528    push di                                     // Save these...
529    push cx
530
531    // Create recursion stack frame...
532    push offset .resume                         // Where to "return" to
533    push es
534.isfile:
535    xchg ax, di
536
537.getsome:
538    // Get a chunk of the directory
539    // This relies on the fact that ISOLINUX doesn't change SI
540    mov si, trackbuf
541    pushad
542    xchg bx, si
543    mov cx, word ptr ds:[BufSafe]
544    call getfssec
545    popad
546
547.compare:
548    movzx eax, byte ptr ds:[si]                 // Length of directory entry
549    cmp al, 33
550    jb .next_sector
551    mov cl, byte ptr ds:[si+25]
552    xor cl, byte ptr ds:[ISOFlags]
553    test cl, HEX(8E)                            // Unwanted file attributes!
554    jnz .not_file
555    pusha
556    movzx cx, byte ptr ds:[si+32]               // File identifier length
557    add si, 33                                  // File identifier offset
558    call iso_compare_names
559    popa
560    je .success
561.not_file:
562    sub edx, eax                                // Decrease bytes left
563    jbe .failure
564    add si, ax                                  // Advance pointer
565
566.check_overrun:
567    // Did we finish the buffer?
568    cmp si, trackbuf+trackbufsize
569    jb .compare                                 // No, keep going
570
571    jmp short .getsome                          // Get some more directory
572
573.next_sector:
574    // Advance to the beginning of next sector
575    lea ax, [si+SECTOR_SIZE-1]
576    and ax, not (SECTOR_SIZE-1)
577    sub ax, si
578    jmp short .not_file                         // We still need to do length checks
579
580.failure:
581    xor eax, eax                                // ZF = 1
582    mov dword ptr ds:[bx+file_sector], eax
583    pop es
584    ret
585
586.success:
587    mov eax, dword ptr ds:[si+2]                // Location of extent
588    mov dword ptr ds:[bx+file_sector], eax
589    mov eax, dword ptr ds:[si+10]               // Data length
590    mov dword ptr ds:[bx+file_bytesleft], eax
591    push eax
592    add eax, SECTOR_SIZE-1
593    shr eax, SECTOR_SHIFT
594    mov dword ptr ds:[bx+file_left], eax
595    pop eax
596    jz .failure                                 // Empty file?
597    // ZF = 0
598    mov si, bx
599    pop es
600    ret
601
602.resume:
603    // We get here if we were only doing part of a lookup
604    // This relies on the fact that .success returns bx == si
605    xchg edx, eax                               // Directory length in edx
606    pop cx                                      // Old ISOFlags
607    pop di                                      // Next filename pointer
608    mov byte ptr ds:[di-1], '/'                 // Restore slash
609    mov byte ptr ds:[ISOFlags], cl              // Restore the flags
610    jz .failure                                 // Did we fail?  If so fail for real!
611    jmp .look_for_slash                         // Otherwise, next level
612
613//
614// allocate_file: Allocate a file structure
615//
616//        If successful:
617//          ZF set
618//          BX = file pointer
619//        In unsuccessful:
620//          ZF clear
621//
622allocate_file:
623    push cx
624    mov bx, Files
625    mov cx, MAX_OPEN
626.check:
627    cmp dword ptr ds:[bx], 0
628    je .found
629    add bx, open_file_t_size                    // ZF = 0
630    loop .check
631    // ZF = 0 if we fell out of the loop
632.found:
633    pop cx
634    ret
635
636//
637// iso_compare_names:
638//    Compare the names DS:SI and DS:DI and report if they are
639//    equal from an ISO 9660 perspective.  SI is the name from
640//    the filesystem; CX indicates its length, and ';' terminates.
641//    DI is expected to end with a null.
642//
643//    Note: clobbers AX, CX, SI, DI; assumes DS == ES == base segment
644//
645iso_compare_names:
646    // First, terminate and canonicalize input filename
647    push di
648    mov di, offset ISOFileName
649.canon_loop:
650    jcxz .canon_end
651    lodsb
652    dec cx
653    cmp al, ';'
654    je .canon_end
655    and al, al
656    je .canon_end
657    stosb
658    cmp di, offset ISOFileNameEnd-1             // Guard against buffer overrun
659    jb .canon_loop
660.canon_end:
661    cmp di, ISOFileName
662    jbe .canon_done
663    cmp byte ptr ds:[di-1], '.'                 // Remove terminal dots
664    jne .canon_done
665    dec di
666    jmp short .canon_end
667.canon_done:
668    mov byte ptr ds:[di], 0                     // Null-terminate string
669    pop di
670    mov si, ISOFileName
671.compare2:
672    lodsb
673    mov ah, byte ptr ds:[di]
674    inc di
675    and ax, ax
676    jz .success2                                // End of string for both
677    and al, al                                  // Is either one end of string?
678    jz .failure2                                // If so, failure
679    and ah, ah
680    jz .failure2
681    or ax, HEX(2020)                            // Convert to lower case
682    cmp al, ah
683    je .compare2
684.failure2:
685    and ax, ax                                  // ZF = 0 (at least one will be nonzero)
686.success2:
687    ret
688
689//
690// getfssec: Get multiple clusters from a file, given the file pointer.
691//
692//  On entry:
693//       ES:BX   -> Buffer
694//       SI      -> File pointer
695//       CX      -> Cluster count
696//  On exit:
697//       SI      -> File pointer (or 0 on EOF)
698//       CF = 1  -> Hit EOF
699//       ECX     -> Bytes actually read
700//
701getfssec:
702    push ds
703    push cs
704    pop ds                                      // DS <- CS
705
706    movzx ecx, cx
707    cmp ecx, dword ptr ds:[si+file_left]
708    jna .ok_size
709    mov ecx, dword ptr ds:[si+file_left]
710.ok_size:
711    pushad
712    mov eax, dword ptr ds:[si+file_sector]
713    mov bp, cx
714    call getlinsec
715    popad
716
717    // ECX[31:16] == 0 here...
718    add dword ptr ds:[si+file_sector], ecx
719    sub dword ptr ds:[si+file_left], ecx
720    shl ecx, SECTOR_SHIFT                       // Convert to bytes
721    cmp ecx, dword ptr ds:[si+file_bytesleft]
722    jb .not_all
723    mov ecx, dword ptr ds:[si+file_bytesleft]
724.not_all:
725    sub dword ptr ds:[si+file_bytesleft], ecx
726    jnz .ret                                    // CF = 0 in this case...
727    push eax
728    xor eax, eax
729    mov dword ptr ds:[si+file_sector], eax      // Unused
730    mov si, ax
731    pop eax
732    stc
733.ret:
734    pop ds
735    ret
736
737//
738// Information message (DS:SI) output
739// Prefix with "ISOBOOT: "
740//
741writemsg:
742    push ax
743    push si
744    mov si, offset isoboot_str
745    call writestr_early
746    pop si
747    call writestr_early
748    pop ax
749    ret
750
751writestr_early:
752    pushfd
753    pushad
754.top:
755    lodsb
756    and al, al
757    jz .end_writestr
758    call writechr
759    jmp short .top
760.end_writestr:
761    popad
762    popfd
763    ret
764
765crlf_early:
766    push ax
767    mov al, 13
768    call writechr
769    mov al, 10
770    call writechr
771    pop ax
772    ret
773
774//
775// writechr: Write a character to the screen.
776//
777writechr:
778    pushfd
779    pushad
780    mov ah, HEX(0E)
781    xor bx, bx
782    int HEX(10)
783    popad
784    popfd
785    ret
786
787//
788// int13: save all the segment registers and call INT 13h.
789// Some CD-ROM BIOSes have been found to corrupt segment registers
790// and/or disable interrupts.
791//
792int13:
793    pushf
794    push bp
795    push ds
796    push es
797    push fs
798    push gs
799    int HEX(13)
800    mov bp, sp
801    setc byte ptr ds:[bp+10]                    // Propagate CF to the caller
802    pop gs
803    pop fs
804    pop es
805    pop ds
806    pop bp
807    popf
808    ret
809
810//
811// Get one sector.  Convenience entry point.
812//
813getonesec:
814    mov bp, 1
815    // Fall through to getlinsec
816
817//
818// Get linear sectors - EBIOS LBA addressing, 2048-byte sectors.
819//
820getlinsec:
821    jmp word ptr cs:[GetlinsecPtr]
822
823//
824// getlinsec_ebios:
825//
826// getlinsec implementation for floppy/HDD EBIOS (EDD)
827//
828getlinsec_ebios:
829    xor edx, edx
830    shld edx, eax, 2
831    shl eax, 2                                  // Convert to HDD sectors
832    shl bp, 2
833
834.loop_ebios:
835    push bp                                     // Sectors left
836.retry2:
837    call maxtrans                               // Enforce maximum transfer size
838    movzx edi, bp                               // Sectors we are about to read
839    mov cx, retry_count
840.retry:
841    // Form DAPA on stack
842    push edx
843    push eax
844    push es
845    push bx
846    push di
847    push 16
848    mov si, sp
849    pushad
850    mov dl, byte ptr ds:[DriveNumber]
851    push ds
852    push ss
853    pop ds                                      // DS <- SS
854    mov ah, HEX(42)                             // Extended Read
855    call int13
856    pop ds
857    popad
858    lea sp, [si+16]                             // Remove DAPA
859    jc .error_ebios
860    pop bp
861    add eax, edi                                // Advance sector pointer
862    adc edx, 0
863    sub bp, di                                  // Sectors left
864    shl di, 9                                   // 512-byte sectors
865    add bx, di                                  // Advance buffer pointer
866    jnc .no_overflow                            // Check if we have read more than 64K and need to adjust ES
867    mov di, es
868    add di, HEX(1000)                           // Adjust segment by 64K (1000h * 16 = 10000h = 64K + 1)
869    mov es, di
870.no_overflow:
871    and bp, bp
872    jnz .loop_ebios
873
874    ret
875
876.error_ebios:
877    pushad                                      // Try resetting the device
878    xor ax, ax
879    mov dl, byte ptr ds:[DriveNumber]
880    call int13
881    popad
882    loop .retry                                 // CX-- and jump if not zero
883
884    // Total failure.
885    jmp kaboom
886
887//
888// Truncate BP to MaxTransfer
889//
890maxtrans:
891    cmp bp, word ptr ds:[MaxTransfer]
892    jna .ok
893    mov bp, word ptr ds:[MaxTransfer]
894.ok:
895    ret
896
897//
898// This is the variant we use for real CD-ROMs:
899// LBA, 2K sectors, some special error handling.
900//
901getlinsec_cdrom:
902    mov si, offset dapa                         // Load up the DAPA
903    mov word ptr ds:[si+4], bx
904    mov word ptr ds:[si+6], es
905    mov dword ptr ds:[si+8], eax
906.loop_cdrom:
907    push bp                                     // Sectors left
908    cmp bp, word ptr ds:[MaxTransferCD]
909    jbe .bp_ok
910    mov bp, word ptr ds:[MaxTransferCD]
911.bp_ok:
912    mov word ptr ds:[si+2], bp
913    push si
914    mov dl, byte ptr ds:[DriveNumber]
915    mov ah, HEX(42)                             // Extended Read
916    call xint13
917    pop si
918    pop bp
919    movzx eax, word ptr ds:[si+2]               // Sectors we read
920    add dword ptr ds:[si+8], eax                // Advance sector pointer
921    sub bp, ax                                  // Sectors left
922    shl ax, SECTOR_SHIFT-4                      // 2048-byte sectors -> segment
923    add word ptr ds:[si+6], ax                  // Advance buffer pointer
924    and bp, bp
925    jnz .loop_cdrom
926    mov eax, dword ptr ds:[si+8]                // Next sector
927    ret
928
929    // INT 13h with retry
930xint13:
931    mov byte ptr ds:[RetryCount], retry_count
932.try:
933    pushad
934    call int13
935    jc .error_cdrom
936    add sp, 8*4                                 // Clean up stack
937    ret
938.error_cdrom:
939    mov byte ptr ds:[DiskError], ah             // Save error code
940    popad
941    mov word ptr ds:[DiskSys], ax               // Save system call number
942    dec byte ptr ds:[RetryCount]
943    jz .real_error
944    push ax
945    mov al, byte ptr ds:[RetryCount]
946    mov ah, byte ptr ds:[dapa+2]                // Sector transfer count
947    cmp al, 2                                   // Only 2 attempts left
948    ja .nodanger
949    mov ah, 1                                   // Drop transfer size to 1
950    jmp short .setsize
951.nodanger:
952    cmp al, retry_count-2
953    ja .again                                   // First time, just try again
954    shr ah, 1                                   // Otherwise, try to reduce
955    adc ah, 0                                   // the max transfer size, but not to 0
956.setsize:
957    mov byte ptr ds:[MaxTransferCD], ah
958    mov byte ptr ds:[dapa+2], ah
959.again:
960    pop ax
961    jmp .try
962
963.real_error:
964    mov si, offset diskerr_msg
965    call writemsg
966    mov al, byte ptr ds:[DiskError]
967    call writehex2
968    mov si, offset oncall_str
969    call writestr_early
970    mov ax, word ptr ds:[DiskSys]
971    call writehex4
972    mov si, offset ondrive_str
973    call writestr_early
974    mov al, dl
975    call writehex2
976    call crlf_early
977    // Fall through to kaboom
978
979//
980// kaboom: write a message and bail out.  Wait for a user keypress,
981//      then do a hard reboot.
982//
983kaboom:
984    // Restore a clean context.
985    mov ax, cs
986    mov ds, ax
987    mov es, ax
988    mov fs, ax
989    mov gs, ax
990    sti
991
992    // Display the failure message.
993    mov si, offset err_bootfailed
994    call writestr_early
995
996    // Wait for a keypress.
997    xor ax, ax
998    int HEX(16)
999
1000    // Disable interrupts and reset the system through a magic BIOS call.
1001    cli
1002    mov word ptr ds:[BIOS_magic], 0
1003    ljmp16 HEX(0F000), HEX(0FFF0)
1004
1005//
1006// writehex[248]: Write a hex number in (AL, AX, EAX) to the console
1007//
1008writehex2:
1009    pushfd
1010    pushad
1011    rol eax, 24
1012    mov cx,2
1013    jmp short writehex_common
1014writehex4:
1015    pushfd
1016    pushad
1017    rol eax, 16
1018    mov cx, 4
1019    jmp short writehex_common
1020writehex8:
1021    pushfd
1022    pushad
1023    mov cx, 8
1024writehex_common:
1025.loop_writehex:
1026    rol eax, 4
1027    push eax
1028    and al, HEX(0F)
1029    cmp al, 10
1030    jae .high
1031.low:
1032    add al, '0'
1033    jmp short .ischar
1034.high:
1035    add al, 'A'-10
1036.ischar:
1037    call writechr
1038    pop eax
1039    loop .loop_writehex
1040    popad
1041    popfd
1042    ret
1043
1044//
1045// pollchar_and_empty: Check if we have an input character pending (ZF = 0)
1046// and empty the input buffer afterwards.
1047//
1048pollchar_and_empty:
1049    pushad
1050    mov ah, 1                                   // Did the user press a key?
1051    int HEX(16)
1052    jz .end_pollchar                            // No, then we're done
1053    mov ah, 0                                   // Otherwise empty the buffer by reading it
1054    int HEX(16)
1055.end_pollchar:
1056    popad
1057    ret
1058
1059
1060/* INITIALIZED VARIABLES *****************************************************/
1061presskey_msg:
1062    .ascii "Press any key to boot from the ReactOS medium", NUL
1063dot_msg:
1064    .ascii ".", NUL
1065isoboot_str:
1066    .ascii "ISOBOOT: ", NUL
1067spec_err_msg:
1068    .ascii "Loading spec packet failed, trying to wing it...", CR, LF, NUL
1069maybe_msg:
1070    .ascii "Found something at drive = ", NUL
1071alright_msg:
1072    .ascii "Looks reasonable, continuing...", CR, LF, NUL
1073nospec_msg:
1074    .ascii "Extremely broken BIOS detected, last attempt with drive = ", NUL
1075nothing_msg:
1076    .ascii "Failed to locate CD-ROM device; boot failed.", CR, LF, NUL
1077diskerr_msg:
1078    .ascii "Disk error ", NUL
1079oncall_str:
1080    .ascii ", AX = ", NUL
1081ondrive_str:
1082    .ascii ", drive ", NUL
1083err_bootfailed:
1084    .ascii CR, LF, "Boot failed: press a key to retry...", NUL
1085loader_dir:
1086    .ascii "/LOADER", NUL
1087no_dir_msg:
1088    .ascii "LOADER dir not found.", CR, LF, NUL
1089freeldr_sys:
1090    .ascii "FREELDR.SYS", NUL
1091no_freeldr_msg:
1092    .ascii "FREELDR.SYS not found.", CR, LF, NUL
1093
1094.align 4
1095BufSafe:
1096    .word trackbufsize/SECTOR_SIZE              // Clusters we can load into trackbuf
1097
1098// Maximum transfer size
1099.align 4
1100MaxTransfer:
1101    .word 127                                   // Hard disk modes
1102MaxTransferCD:
1103    .word 32                                    // CD mode
1104
1105//
1106// El Torito spec packet
1107//
1108.align 8
1109spec_packet:
1110    .byte HEX(13)                               // Size of packet
1111sp_media:
1112    .byte 0                                     // Media type
1113sp_drive:
1114    .byte 0                                     // Drive number
1115sp_controller:
1116    .byte 0                                     // Controller index
1117sp_lba:
1118    .long 0                                     // LBA for emulated disk image
1119sp_devspec:
1120    .word 0                                     // IDE/SCSI information
1121sp_buffer:
1122    .word 0                                     // User-provided buffer
1123sp_loadseg:
1124    .word 0                                     // Load segment
1125sp_sectors:
1126    .word 0                                     // Sector count
1127sp_chs:
1128    .byte 0,0,0                                 // Simulated CHS geometry
1129sp_dummy:
1130    .byte 0                                     // Scratch, safe to overwrite
1131
1132//
1133// EBIOS disk address packet
1134//
1135.align 8
1136dapa:
1137    .word 16                                    // Packet size
1138.count:
1139    .word 0                                     // Block count
1140.off:
1141    .word 0                                     // Offset of buffer
1142.seg:
1143    .word 0                                     // Segment of buffer
1144.lba:
1145    .long 0                                     // LBA (LSW)
1146    .long 0                                     // LBA (MSW)
1147
1148
1149// Extend the size to cover one 2K-sized sector
1150.org 2046
1151    .word HEX(0aa55)    // BootSector signature
1152
1153.endcode16
1154
1155END
1156