2 # Copyright (c) 2001 John Baldwin
5 # Redistribution and use in source and binary forms are freely
6 # permitted provided that the above copyright notice and this
7 # paragraph and the following disclaimer are duplicated in all
10 # This software is provided "AS IS" and without any express or
11 # implied warranties, including, without limitation, the implied
12 # warranties of merchantability and fitness for a particular
16 # $FreeBSD: src/sys/boot/i386/cdboot/cdboot.s,v 1.9 2001/11/07 01:20:33 jhb Exp $
17 # $DragonFly: src/sys/boot/pc32/cdboot/cdboot.S,v 1.3 2003/11/10 06:08:35 dillon Exp $
20 # This program is a freestanding boot program to load an a.out binary
21 # from a CD-ROM booted with no emulation mode as described by the El
22 # Torito standard. Due to broken BIOSen that do not load the desired
23 # number of sectors, we try to fit this in as small a space as possible.
25 # Basically, we first create a set of boot arguments to pass to the loaded
26 # binary. Then we attempt to load /boot/loader from the CD we were booted
33 .set MEM_PAGE_SIZE,0x1000 # memory page size, 4k
34 .set MEM_ARG,0x900 # Arguments at start
35 .set MEM_ARG_BTX,0xa100 # Where we move them to so the
36 # BTX client can see them
37 .set MEM_ARG_SIZE,0x18 # Size of the arguments
38 .set MEM_BTX_ADDRESS,0x9000 # where BTX lives
39 .set MEM_BTX_ENTRY,0x9010 # where BTX starts to execute
40 .set MEM_BTX_OFFSET,MEM_PAGE_SIZE # offset of BTX in the loader
41 .set MEM_BTX_CLIENT,0xa000 # where BTX clients live
45 .set AOUT_TEXT,0x04 # text segment size
46 .set AOUT_DATA,0x08 # data segment size
47 .set AOUT_BSS,0x0c # zero'd BSS size
48 .set AOUT_SYMBOLS,0x10 # symbol table
49 .set AOUT_ENTRY,0x14 # entry point
50 .set AOUT_HEADER,MEM_PAGE_SIZE # size of the a.out header
52 # Flags for kargs->bootflags
54 .set KARGS_FLAGS_CD,0x1 # flag to indicate booting from
59 .set SEL_SDATA,0x8 # Supervisor data
60 .set SEL_RDATA,0x10 # Real mode data
61 .set SEL_SCODE,0x18 # PM-32 code
62 .set SEL_SCODE16,0x20 # PM-16 code
66 .set INT_SYS,0x30 # BTX syscall interrupt
68 # Constants for reading from the CD.
70 .set ERROR_TIMEOUT,0x80 # BIOS timeout on read
71 .set NUM_RETRIES,3 # Num times to retry
72 .set SECTOR_SIZE,0x800 # size of a sector
73 .set SECTOR_SHIFT,11 # number of place to shift
74 .set BUFFER_LEN,0x100 # number of sectors in buffer
75 .set MAX_READ,0x10000 # max we can read at a time
76 .set MAX_READ_SEC,MAX_READ >> SECTOR_SHIFT
77 .set MEM_READ_BUFFER,0x9000 # buffer to read from CD
78 .set MEM_VOLDESC,MEM_READ_BUFFER # volume descriptor
79 .set MEM_DIR,MEM_VOLDESC+SECTOR_SIZE # Lookup buffer
80 .set VOLDESC_LBA,0x10 # LBA of vol descriptor
81 .set VD_PRIMARY,1 # Primary VD
82 .set VD_END,255 # VD Terminator
83 .set VD_ROOTDIR,156 # Offset of Root Dir Record
84 .set DIR_LEN,0 # Offset of Dir Record length
85 .set DIR_EA_LEN,1 # Offset of EA length
86 .set DIR_EXTENT,2 # Offset of 64-bit LBA
87 .set DIR_SIZE,10 # Offset of 64-bit length
88 .set DIR_NAMELEN,32 # Offset of 8-bit name len
89 .set DIR_NAME,33 # Offset of dir name
91 # We expect to be loaded by the BIOS at 0x7c00 (standard boot loader entry
100 start: cld # string ops inc
101 xor %ax,%ax # zero %ax
102 mov %ax,%ss # setup the
103 mov $start,%sp # stack
104 mov %ax,%ds # setup the
105 mov %ax,%es # data segments
106 mov %dl,drive # Save BIOS boot device
107 mov $msg_welcome,%si # %ds:(%si) -> welcome message
108 call putstr # display the welcome message
110 # Setup the arguments that the loader is expecting from boot[12]
112 mov $msg_bootinfo,%si # %ds:(%si) -> boot args message
113 call putstr # display the message
114 mov $MEM_ARG,%bx # %ds:(%bx) -> boot args
115 mov %bx,%di # %es:(%di) -> boot args
116 xor %eax,%eax # zero %eax
117 mov $(MEM_ARG_SIZE/4),%cx # Size of arguments in 32-bit
119 rep # Clear the arguments
121 mov drive,%dl # Store BIOS boot device
122 mov %dl,0x4(%bx) # in kargs->bootdev
123 or $KARGS_FLAGS_CD,0x8(%bx) # kargs->bootflags |=
126 # Load Volume Descriptor
128 mov $VOLDESC_LBA,%eax # Set LBA of first VD
129 load_vd: push %eax # Save %eax
130 mov $1,%dh # One sector
131 mov $MEM_VOLDESC,%ebx # Destination
132 call read # Read it in
133 cmpb $VD_PRIMARY,(%bx) # Primary VD?
135 pop %eax # Prepare to
137 cmpb $VD_END,(%bx) # Last VD?
138 jne load_vd # No, read next
139 mov $msg_novd,%si # No VD
141 have_vd: # Have Primary VD
143 # Lookup the loader binary.
145 mov $loader_path,%si # File to lookup
146 call lookup # Try to find it
148 # Load the binary into the buffer. Due to real mode addressing limitations
149 # we have to read it in in 64k chunks.
151 mov DIR_SIZE(%bx),%eax # Read file length
152 add $SECTOR_SIZE-1,%eax # Convert length to sectors
156 mov $msg_load2big,%si # Error message
158 load_sizeok: movzbw %al,%cx # Num sectors to read
159 mov DIR_EXTENT(%bx),%eax # Load extent
161 mov DIR_EA_LEN(%bx),%dl
162 add %edx,%eax # Skip extended
163 mov $MEM_READ_BUFFER,%ebx # Read into the buffer
164 load_loop: mov %cl,%dh
165 cmp $MAX_READ_SEC,%cl # Truncate to max read size
167 mov $MAX_READ_SEC,%dh
168 load_notrunc: sub %dh,%cl # Update count
170 call read # Read it in
172 add $MAX_READ_SEC,%eax # Update LBA
173 add $MAX_READ,%ebx # Update dest addr
174 jcxz load_done # Done?
175 jmp load_loop # Keep going
178 # Turn on the A20 address line
180 call seta20 # Turn A20 on
182 # Relocate the loader and BTX using a very lazy protected mode
184 mov $msg_relocate,%si # Display the
185 call putstr # relocation message
186 mov MEM_READ_BUFFER+AOUT_ENTRY,%edi # %edi is the destination
187 mov $(MEM_READ_BUFFER+AOUT_HEADER),%esi # %esi is
188 # the start of the text
190 mov MEM_READ_BUFFER+AOUT_TEXT,%ecx # %ecx = length of the text
192 push %edi # Save entry point for later
193 lgdt gdtdesc # setup our own gdt
194 cli # turn off interrupts
195 mov %cr0,%eax # Turn on
196 or $0x1,%al # protected
198 ljmp $SEL_SCODE,$pm_start # long jump to clear the
199 # instruction pre-fetch queue
201 pm_start: mov $SEL_SDATA,%ax # Initialize
202 mov %ax,%ds # %ds and
203 mov %ax,%es # %es to a flat selector
206 add $(MEM_PAGE_SIZE - 1),%edi # pad %edi out to a new page
207 and $~(MEM_PAGE_SIZE - 1),%edi # for the data segment
208 mov MEM_READ_BUFFER+AOUT_DATA,%ecx # size of the data segment
211 mov MEM_READ_BUFFER+AOUT_BSS,%ecx # size of the bss
212 xor %eax,%eax # zero %eax
213 add $3,%cl # round %ecx up to
214 shr $2,%ecx # a multiple of 4
217 mov MEM_READ_BUFFER+AOUT_ENTRY,%esi # %esi -> relocated loader
218 add $MEM_BTX_OFFSET,%esi # %esi -> BTX in the loader
219 mov $MEM_BTX_ADDRESS,%edi # %edi -> where BTX needs to go
220 movzwl 0xa(%esi),%ecx # %ecx -> length of BTX
223 ljmp $SEL_SCODE16,$pm_16 # Jump to 16-bit PM
225 pm_16: mov $SEL_RDATA,%ax # Initialize
226 mov %ax,%ds # %ds and
227 mov %ax,%es # %es to a real mode selector
228 mov %cr0,%eax # Turn off
229 and $~0x1,%al # protected
231 ljmp $0,$pm_end # Long jump to clear the
232 # instruction pre-fetch queue
233 pm_end: sti # Turn interrupts back on now
235 # Copy the BTX client to MEM_BTX_CLIENT
237 xor %ax,%ax # zero %ax and set
238 mov %ax,%ds # %ds and %es
239 mov %ax,%es # to segment 0
240 mov $MEM_BTX_CLIENT,%di # Prepare to relocate
241 mov $btx_client,%si # the simple btx client
242 mov $(btx_client_end-btx_client),%cx # length of btx client
244 movsb # simple BTX client
246 # Copy the boot[12] args to where the BTX client can see them
248 mov $MEM_ARG,%si # where the args are at now
249 mov $MEM_ARG_BTX,%di # where the args are moving to
250 mov $(MEM_ARG_SIZE/4),%cx # size of the arguments in longs
254 # Save the entry point so the client can get to it later on
256 pop %eax # Restore saved entry point
257 stosl # and add it to the end of
260 # Now we just start up BTX and let it do the rest
262 mov $msg_jump,%si # Display the
263 call putstr # jump message
264 ljmp $0,$MEM_BTX_ENTRY # Jump to the BTX entry point
267 # Lookup the file in the path at [SI] from the root directory.
269 # Trashes: All but BX
270 # Returns: BX = pointer to record
272 lookup: mov $VD_ROOTDIR+MEM_VOLDESC,%bx # Root directory record
274 mov $msg_lookup,%si # Display lookup message
282 lookup_dir: lodsb # Get first char of path
283 cmp $0,%al # Are we done?
285 cmp $'/',%al # Skip path separator.
287 dec %si # Undo lodsb side effect
288 call find_file # Lookup first path item
289 jnc lookup_dir # Try next component
290 mov $msg_lookupfail,%si # Not found.
292 lookup_done: mov $msg_lookupok,%si # Success message
297 # Lookup file at [SI] in directory whose record is at [BX].
299 # Trashes: All but returns
300 # Returns: CF = 0 (success), BX = pointer to record, SX = next path item
301 # CF = 1 (not found), SI = preserved
303 find_file: mov DIR_EXTENT(%bx),%eax # Load extent
305 mov DIR_EA_LEN(%bx),%dl
306 add %edx,%eax # Skip extended attributes
307 mov %eax,rec_lba # Save LBA
308 mov DIR_SIZE(%bx),%eax # Save size
310 xor %cl,%cl # Zero length
312 ff.namelen: inc %cl # Update length
316 cmp $'/',%al # Path separator?
317 jnz ff.namelen # No, keep going
318 ff.namedone: dec %cl # Adjust length and save
321 ff.load: mov rec_lba,%eax # Load LBA
322 mov $MEM_DIR,%ebx # Address buffer
323 mov $1,%dh # One sector
324 call read # Read directory block
325 incl rec_lba # Update LBA to next block
326 ff.scan: mov %ebx,%edx # Check for EOF
332 ff.scan.1: cmpb $0,DIR_LEN(%bx) # Last record in block?
335 movzbw DIR_NAMELEN(%bx),%si # Find end of string
336 ff.checkver: cmpb $'0',DIR_NAME-1(%bx,%si) # Less than '0'?
338 cmpb $'9',DIR_NAME-1(%bx,%si) # Greater than '9'?
342 jmp ff.checklen # All numbers in name, so
344 ff.checkver.1: movzbw DIR_NAMELEN(%bx),%cx
345 cmp %cx,%si # Did we find any digits?
347 cmpb $';',DIR_NAME-1(%bx,%si) # Check for semicolon
349 dec %si # Skip semicolon
351 mov %cl,DIR_NAMELEN(%bx) # Adjust length
353 ff.checkver.2: mov %cx,%si # Restore %si to end of string
354 ff.checkdot: cmpb $'.',DIR_NAME-1(%bx,%si) # Trailing dot?
356 decb DIR_NAMELEN(%bx) # Adjust length
357 ff.checklen: pop %si # Restore
358 movzbw name_len,%cx # Load length of name
359 cmp %cl,DIR_NAMELEN(%bx) # Does length match?
360 je ff.checkname # Yes, check name
361 ff.nextrec: add DIR_LEN(%bx),%bl # Next record
364 ff.nextblock: subl $SECTOR_SIZE,rec_size # Adjust size
365 jnc ff.load # If subtract ok, keep going
366 ret # End of file, so not found
367 ff.checkname: lea DIR_NAME(%bx),%di # Address name in record
369 repe cmpsb # Compare name
370 jcxz ff.match # We have a winner!
372 jmp ff.nextrec # Keep looking.
373 ff.match: add $2,%sp # Discard saved %si
378 # Load DH sectors starting at LBA EAX into [EBX].
382 read: push %si # Save
383 mov %eax,edd_lba # LBA to read from
384 mov %ebx,%eax # Convert address
385 shr $4,%eax # to segment
386 mov %ax,edd_addr+0x2 # and store
387 read.retry: call twiddle # Entertain the user
389 mov $edd_packet,%si # Address Packet
390 mov %dh,edd_len # Set length
391 mov drive,%dl # BIOS Device
392 mov $0x42,%ah # BIOS: Extended Read
393 int $0x13 # Call BIOS
395 jc read.fail # Worked?
398 read.fail: cmp $ERROR_TIMEOUT,%ah # Timeout?
399 je read.retry # Yes, Retry.
400 read.error: mov %ah,%al # Save error
401 mov $hex_error,%di # Format it
403 mov $msg_badread,%si # Display Read error message
406 # Display error message at [SI] and halt.
408 error: call putstr # Display message
413 # Display a null-terminated string.
417 putstr: push %bx # Save
418 putstr.load: lodsb # load %al from %ds:(%si)
419 test %al,%al # stop at null
420 jnz putstr.putc # if the char != null, output it
422 ret # return when null is hit
423 putstr.putc: call putc # output char
424 jmp putstr.load # next char
427 # Display a single char.
429 putc: mov $0x7,%bx # attribute for output
430 mov $0xe,%ah # BIOS: put_char
431 int $0x10 # call BIOS, print char in %al
432 ret # Return to caller
435 # Output the "twiddle"
437 twiddle: push %ax # Save
439 mov twiddle_index,%al # Load index
440 mov twiddle_chars,%bx # Address table
444 call putc # Output it
445 mov $8,%al # Backspace
446 call putc # Output it
454 seta20: cli # Disable interrupts
455 seta20.1: in $0x64,%al # Get status
456 test $0x2,%al # Busy?
458 mov $0xd1,%al # Command: Write
459 out %al,$0x64 # output port
460 seta20.2: in $0x64,%al # Get status
461 test $0x2,%al # Busy?
463 mov $0xdf,%al # Enable
465 sti # Enable interrupts
469 # Convert AL to hex, saving the result to [EDI].
471 hex8: pushl %eax # Save
472 shrb $0x4,%al # Do upper
475 hex8.1: andb $0xf,%al # Get lower 4
476 cmpb $0xa,%al # Convert
477 sbbb $0x69,%al # to hex
479 orb $0x20,%al # To lower case
484 # BTX client to start btxldr
487 btx_client: mov $(MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE-4), %esi
490 mov $(MEM_ARG_SIZE/4),%ecx # Number of words to push
492 push_arg: lodsl # Read argument
493 push %eax # Push it onto the stack
494 loop push_arg # Push all of the arguments
495 cld # In case anyone depends on this
496 pushl MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE # Entry point of
498 push %eax # Emulate a near call
499 mov $0x1,%eax # 'exec' system call
500 int $INT_SYS # BTX system call
506 # Global descriptor table.
508 gdt: .word 0x0,0x0,0x0,0x0 # Null entry
509 .word 0xffff,0x0,0x9200,0xcf # SEL_SDATA
510 .word 0xffff,0x0,0x9200,0x0 # SEL_RDATA
511 .word 0xffff,0x0,0x9a00,0xcf # SEL_SCODE (32-bit)
512 .word 0xffff,0x0,0x9a00,0x8f # SEL_SCODE16 (16-bit)
515 # Pseudo-descriptors.
517 gdtdesc: .word gdt.1-gdt-1 # Limit
522 edd_packet: .byte 0x10 # Length
524 edd_len: .byte 0x0 # Num to read
526 edd_addr: .word 0x0,0x0 # Seg:Off
527 edd_lba: .quad 0x0 # LBA
532 # State for searching dir
534 rec_lba: .long 0x0 # LBA (adjusted for EA)
535 rec_size: .long 0x0 # File size
536 name_len: .byte 0x0 # Length of current name
538 twiddle_index: .byte 0x0
540 msg_welcome: .asciz "CD Loader 1.01\r\n\n"
541 msg_bootinfo: .asciz "Building the boot loader arguments\r\n"
542 msg_relocate: .asciz "Relocating the loader and the BTX\r\n"
543 msg_jump: .asciz "Starting the BTX loader\r\n"
544 msg_badread: .ascii "Read Error: 0x"
545 hex_error: .ascii "00\r\n"
546 msg_novd: .asciz "Could not find Primary Volume Descriptor\r\n"
547 msg_lookup: .asciz "Looking up "
548 msg_lookup2: .asciz "... "
549 msg_lookupok: .asciz "Found\r\n"
550 msg_lookupfail: .asciz "File not found\r\n"
551 msg_load2big: .asciz "File too big\r\n"
552 loader_path: .asciz "/BOOT/LOADER"
553 twiddle_chars: .ascii "|/-\\"