[BOOTSECTORS] Use the new header with SPDX license identifier for the ISO boot sector...
[reactos.git] / reactos / boot / freeldr / bootsect / isoboot.S
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
23 ASSUME CS:.text, DS:.text, ES:.text
24
25 /* CONSTANTS ******************************************************************/
26 BIOS_timer = HEX(046C) // Timer ticks (1 word)
27 BIOS_magic = HEX(0472) // BIOS reset magic (1 word)
28
29 // Memory below this point is reserved for the BIOS and the MBR
30 trackbuf = HEX(1000) // Track buffer goes here (8192 bytes)
31 trackbufsize = 8192 // trackbuf ends at 3000h
32
33 // struct open_file_t
34 file_sector = 0 // Sector pointer (0 = structure free)
35 file_bytesleft = 4 // Number of bytes left
36 file_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
41 dir_lba = 0 // Directory start (LBA)
42 dir_len = 4 // Length in bytes
43 dir_clust = 8 // Length in clusters
44 #define dir_t_size 12
45
46 MAX_OPEN_LG2 = 2 // log2(Max number of open files)
47 MAX_OPEN = 4
48 SECTOR_SHIFT = 11 // 2048 bytes/sector (El Torito requirement)
49 SECTOR_SIZE = 2048
50 retry_count = 6 // How patient are we with the BIOS?
51
52 /* UNINITIALIZED VARIABLES ****************************************************/
53 absolute HEX(5000) // Here we keep our BSS stuff
54
55 resb ISOFileName, 64 // ISO filename canonicalization buffer
56 resb ISOFileNameEnd, 1
57 resb CurrentDir, dir_t_size // Current directory
58 resb RootDir, dir_t_size // Root directory
59 resb DiskSys, 2 // Last INT 13h call
60 resb GetlinsecPtr, 2 // The sector-read pointer
61 resb DiskError, 1 // Error code for disk I/O
62 resb DriveNumber, 1 // CD-ROM BIOS drive number
63 resb ISOFlags, 1 // Flags for ISO directory search
64 resb RetryCount, 1 // Used for disk access retries
65
66 //align open_file_t_size
67 absolute HEX(5070)
68 resb Files, (MAX_OPEN * open_file_t_size)
69
70
71 /* ENTRY POINTS ***************************************************************/
72
73 // Entry point when booted from CD (El Torito standard)
74 start:
75 mov bx, offset getlinsec_cdrom
76 // Fall through
77
78 start_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
101 hybrid_signature:
102 .long HEX(7078c0fb)
103
104 // Entry point when booted through ISOMBR from a drive (isohybrid mode)
105 start_hybrid:
106 mov bx, offset getlinsec_ebios
107 jmp start_common
108
109 relocated:
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 SETUPLDR.
119 cmp word ptr ds:[GetlinsecPtr], offset getlinsec_ebios
120 jne .read_mbr
121 cmp byte ptr ds:[DriveNumber], HEX(80)
122 je .boot_setupldr
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 SETUPLDR 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_setupldr
134
135 // Verify the signature of the read MBR.
136 // If it's invalid, there is probably no OS installed and we just boot SETUPLDR from our ReactOS medium.
137 mov ax, word ptr ds:[HEX(7C00)+510]
138 cmp ax, HEX(AA55)
139 jne .boot_setupldr
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 SETUPLDR from our ReactOS medium if a key was pressed.
159 call pollchar_and_empty
160 jnz .boot_setupldr
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_setupldr:
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
219 found_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 "SETUPLDR.SYS" file.
270 mov di, offset setupldr_sys
271 call searchdir
272 jnz .setupldr_found
273
274 // The SETUPLDR file was not found, so bail out with an error message.
275 mov si, offset no_setupldr_msg
276 call writemsg
277 jmp kaboom
278
279 .setupldr_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_setupldr
285 inc ecx
286
287 .load_setupldr:
288 // Load the entire SETUPLDR.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 SETUPLDR:
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_setupldr
302 mov dh, HEX(FF)
303
304 .jump_to_setupldr:
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 ///////////////////////////////////////////////////////////////////////////////
335 award_oldint13:
336 .long 0
337 award_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
340 award_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
352 award_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
362 award_failed:
363 pop es // No, not this way :-((
364 award_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
371 award_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.
393 spec_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
461 fatal_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 //
488 alloc_failure:
489 xor ax, ax // ZF <- 1
490 ret
491
492 searchdir:
493 xor al, al
494 searchdir_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 //
622 allocate_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 //
645 iso_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 //
701 getfssec:
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 //
741 writemsg:
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
751 writestr_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
765 crlf_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 //
777 writechr:
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 //
792 int13:
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 //
813 getonesec:
814 mov bp, 1
815 // Fall through to getlinsec
816
817 //
818 // Get linear sectors - EBIOS LBA addressing, 2048-byte sectors.
819 //
820 getlinsec:
821 jmp word ptr cs:[GetlinsecPtr]
822
823 //
824 // getlinsec_ebios:
825 //
826 // getlinsec implementation for floppy/HDD EBIOS (EDD)
827 //
828 getlinsec_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 //
890 maxtrans:
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 //
901 getlinsec_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
930 xint13:
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 //
983 kaboom:
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 //
1008 writehex2:
1009 pushfd
1010 pushad
1011 rol eax, 24
1012 mov cx,2
1013 jmp short writehex_common
1014 writehex4:
1015 pushfd
1016 pushad
1017 rol eax, 16
1018 mov cx, 4
1019 jmp short writehex_common
1020 writehex8:
1021 pushfd
1022 pushad
1023 mov cx, 8
1024 writehex_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 //
1048 pollchar_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 *****************************************************/
1061 presskey_msg:
1062 .ascii "Press any key to boot from the ReactOS medium", NUL
1063 dot_msg:
1064 .ascii ".", NUL
1065 isoboot_str:
1066 .ascii "ISOBOOT: ", NUL
1067 spec_err_msg:
1068 .ascii "Loading spec packet failed, trying to wing it...", CR, LF, NUL
1069 maybe_msg:
1070 .ascii "Found something at drive = ", NUL
1071 alright_msg:
1072 .ascii "Looks reasonable, continuing...", CR, LF, NUL
1073 nospec_msg:
1074 .ascii "Extremely broken BIOS detected, last attempt with drive = ", NUL
1075 nothing_msg:
1076 .ascii "Failed to locate CD-ROM device; boot failed.", CR, LF, NUL
1077 diskerr_msg:
1078 .ascii "Disk error ", NUL
1079 oncall_str:
1080 .ascii ", AX = ", NUL
1081 ondrive_str:
1082 .ascii ", drive ", NUL
1083 err_bootfailed:
1084 .ascii CR, LF, "Boot failed: press a key to retry...", NUL
1085 loader_dir:
1086 .ascii "/LOADER", NUL
1087 no_dir_msg:
1088 .ascii "LOADER dir not found.", CR, LF, NUL
1089 setupldr_sys:
1090 .ascii "SETUPLDR.SYS", NUL
1091 no_setupldr_msg:
1092 .ascii "SETUPLDR.SYS not found.", CR, LF, NUL
1093
1094 .align 4
1095 BufSafe:
1096 .word trackbufsize/SECTOR_SIZE // Clusters we can load into trackbuf
1097
1098 // Maximum transfer size
1099 .align 4
1100 MaxTransfer:
1101 .word 127 // Hard disk modes
1102 MaxTransferCD:
1103 .word 32 // CD mode
1104
1105 //
1106 // El Torito spec packet
1107 //
1108 .align 8
1109 spec_packet:
1110 .byte HEX(13) // Size of packet
1111 sp_media:
1112 .byte 0 // Media type
1113 sp_drive:
1114 .byte 0 // Drive number
1115 sp_controller:
1116 .byte 0 // Controller index
1117 sp_lba:
1118 .long 0 // LBA for emulated disk image
1119 sp_devspec:
1120 .word 0 // IDE/SCSI information
1121 sp_buffer:
1122 .word 0 // User-provided buffer
1123 sp_loadseg:
1124 .word 0 // Load segment
1125 sp_sectors:
1126 .word 0 // Sector count
1127 sp_chs:
1128 .byte 0,0,0 // Simulated CHS geometry
1129 sp_dummy:
1130 .byte 0 // Scratch, safe to overwrite
1131
1132 //
1133 // EBIOS disk address packet
1134 //
1135 .align 8
1136 dapa:
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 2047
1151 .byte 0
1152
1153 .endcode16
1154
1155 END