2 # Copyright (c) 2001 John Baldwin <jhb@FreeBSD.org>
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
8 # 1. Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # 2. Redistributions in binary form must reproduce the above copyright
11 # notice, this list of conditions and the following disclaimer in the
12 # documentation and/or other materials provided with the distribution.
13 # 3. Neither the name of the author nor the names of any co-contributors
14 # may be used to endorse or promote products derived from this software
15 # without specific prior written permission.
17 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 # This program is a freestanding boot program to load an a.out binary
34 # from a CD-ROM booted with no emulation mode as described by the El
35 # Torito standard. Due to broken BIOSen that do not load the desired
36 # number of sectors, we try to fit this in as small a space as possible.
38 # Basically, we first create a set of boot arguments to pass to the loaded
39 # binary. Then we attempt to load /boot/loader from the CD we were booted
46 .set MEM_PAGE_SIZE,0x1000 # memory page size, 4k
47 .set MEM_ARG,0x900 # Arguments at start
48 .set MEM_ARG_BTX,0xa100 # Where we move them to so the
49 # BTX client can see them
50 .set MEM_ARG_SIZE,0x18 # Size of the arguments
51 .set MEM_BTX_ADDRESS,0x9000 # where BTX lives
52 .set MEM_BTX_ENTRY,0x9010 # where BTX starts to execute
53 .set MEM_BTX_OFFSET,MEM_PAGE_SIZE # offset of BTX in the loader
54 .set MEM_BTX_CLIENT,0xa000 # where BTX clients live
58 .set AOUT_TEXT,0x04 # text segment size
59 .set AOUT_DATA,0x08 # data segment size
60 .set AOUT_BSS,0x0c # zero'd BSS size
61 .set AOUT_SYMBOLS,0x10 # symbol table
62 .set AOUT_ENTRY,0x14 # entry point
63 .set AOUT_HEADER,MEM_PAGE_SIZE # size of the a.out header
65 # Flags for kargs->bootflags
67 .set KARGS_FLAGS_CD,0x1 # flag to indicate booting from
72 .set SEL_SDATA,0x8 # Supervisor data
73 .set SEL_RDATA,0x10 # Real mode data
74 .set SEL_SCODE,0x18 # PM-32 code
75 .set SEL_SCODE16,0x20 # PM-16 code
79 .set INT_SYS,0x30 # BTX syscall interrupt
81 # Constants for reading from the CD.
83 .set ERROR_TIMEOUT,0x80 # BIOS timeout on read
84 .set NUM_RETRIES,3 # Num times to retry
85 .set SECTOR_SIZE,0x800 # size of a sector
86 .set SECTOR_SHIFT,11 # number of place to shift
87 .set BUFFER_LEN,0x100 # number of sectors in buffer
88 .set MAX_READ,0x10000 # max we can read at a time
89 .set MAX_READ_SEC,MAX_READ >> SECTOR_SHIFT
90 .set MEM_READ_BUFFER,0x9000 # buffer to read from CD
91 .set MEM_VOLDESC,MEM_READ_BUFFER # volume descriptor
92 .set MEM_DIR,MEM_VOLDESC+SECTOR_SIZE # Lookup buffer
93 .set VOLDESC_LBA,0x10 # LBA of vol descriptor
94 .set VD_PRIMARY,1 # Primary VD
95 .set VD_END,255 # VD Terminator
96 .set VD_ROOTDIR,156 # Offset of Root Dir Record
97 .set DIR_LEN,0 # Offset of Dir Record length
98 .set DIR_EA_LEN,1 # Offset of EA length
99 .set DIR_EXTENT,2 # Offset of 64-bit LBA
100 .set DIR_SIZE,10 # Offset of 64-bit length
101 .set DIR_NAMELEN,32 # Offset of 8-bit name len
102 .set DIR_NAME,33 # Offset of dir name
104 # We expect to be loaded by the BIOS at 0x7c00 (standard boot loader entry
113 start: cld # string ops inc
114 xor %ax,%ax # zero %ax
115 mov %ax,%ss # setup the
116 mov $start,%sp # stack
117 mov %ax,%ds # setup the
118 mov %ax,%es # data segments
119 mov %dl,drive # Save BIOS boot device
120 mov $msg_welcome,%si # %ds:(%si) -> welcome message
121 call putstr # display the welcome message
123 # Setup the arguments that the loader is expecting from boot[12]
125 mov $msg_bootinfo,%si # %ds:(%si) -> boot args message
126 call putstr # display the message
127 mov $MEM_ARG,%bx # %ds:(%bx) -> boot args
128 mov %bx,%di # %es:(%di) -> boot args
129 xor %eax,%eax # zero %eax
130 mov $(MEM_ARG_SIZE/4),%cx # Size of arguments in 32-bit
132 rep # Clear the arguments
134 mov drive,%dl # Store BIOS boot device
135 mov %dl,0x4(%bx) # in kargs->bootdev
136 or $KARGS_FLAGS_CD,0x8(%bx) # kargs->bootflags |=
139 # Load Volume Descriptor
141 mov $VOLDESC_LBA,%eax # Set LBA of first VD
142 load_vd: push %eax # Save %eax
143 mov $1,%dh # One sector
144 mov $MEM_VOLDESC,%ebx # Destination
145 call read # Read it in
146 cmpb $VD_PRIMARY,(%bx) # Primary VD?
148 pop %eax # Prepare to
150 cmpb $VD_END,(%bx) # Last VD?
151 jne load_vd # No, read next
152 mov $msg_novd,%si # No VD
154 have_vd: # Have Primary VD
156 # Try to look up the loader binary using the paths in the loader_paths
159 mov $loader_paths,%si # Point to start of array
160 lookup_path: push %si # Save file name pointer
161 call lookup # Try to find file
162 pop %di # Restore file name pointer
163 jnc lookup_found # Found this file
164 xor %al,%al # Look for next
165 mov $0xffff,%cx # path name by
168 mov %di,%si # Point %si at next path
169 mov (%si),%al # Get first char of next path
170 or %al,%al # Is it double nul?
171 jnz lookup_path # No, try it.
172 mov $msg_failed,%si # Failed message
174 lookup_found: # Found a loader file
176 # Load the binary into the buffer. Due to real mode addressing limitations
177 # we have to read it in 64k chunks.
179 mov DIR_SIZE(%bx),%eax # Read file length
180 add $SECTOR_SIZE-1,%eax # Convert length to sectors
181 shr $SECTOR_SHIFT,%eax
184 mov $msg_load2big,%si # Error message
186 load_sizeok: movzbw %al,%cx # Num sectors to read
187 mov DIR_EXTENT(%bx),%eax # Load extent
189 mov DIR_EA_LEN(%bx),%dl
190 add %edx,%eax # Skip extended
191 mov $MEM_READ_BUFFER,%ebx # Read into the buffer
192 load_loop: mov %cl,%dh
193 cmp $MAX_READ_SEC,%cl # Truncate to max read size
195 mov $MAX_READ_SEC,%dh
196 load_notrunc: sub %dh,%cl # Update count
198 call read # Read it in
200 add $MAX_READ_SEC,%eax # Update LBA
201 add $MAX_READ,%ebx # Update dest addr
202 jcxz load_done # Done?
203 jmp load_loop # Keep going
206 # Turn on the A20 address line
208 call seta20 # Turn A20 on
210 # Relocate the loader and BTX using a very lazy protected mode
212 mov $msg_relocate,%si # Display the
213 call putstr # relocation message
214 mov MEM_READ_BUFFER+AOUT_ENTRY,%edi # %edi is the destination
215 mov $(MEM_READ_BUFFER+AOUT_HEADER),%esi # %esi is
216 # the start of the text
218 mov MEM_READ_BUFFER+AOUT_TEXT,%ecx # %ecx = length of the text
220 push %edi # Save entry point for later
221 lgdt gdtdesc # setup our own gdt
222 cli # turn off interrupts
223 mov %cr0,%eax # Turn on
224 or $0x1,%al # protected
226 ljmp $SEL_SCODE,$pm_start # long jump to clear the
227 # instruction pre-fetch queue
229 pm_start: mov $SEL_SDATA,%ax # Initialize
230 mov %ax,%ds # %ds and
231 mov %ax,%es # %es to a flat selector
234 add $(MEM_PAGE_SIZE - 1),%edi # pad %edi out to a new page
235 and $~(MEM_PAGE_SIZE - 1),%edi # for the data segment
236 mov MEM_READ_BUFFER+AOUT_DATA,%ecx # size of the data segment
239 mov MEM_READ_BUFFER+AOUT_BSS,%ecx # size of the bss
240 xor %eax,%eax # zero %eax
241 add $3,%cl # round %ecx up to
242 shr $2,%ecx # a multiple of 4
245 mov MEM_READ_BUFFER+AOUT_ENTRY,%esi # %esi -> relocated loader
246 add $MEM_BTX_OFFSET,%esi # %esi -> BTX in the loader
247 mov $MEM_BTX_ADDRESS,%edi # %edi -> where BTX needs to go
248 movzwl 0xa(%esi),%ecx # %ecx -> length of BTX
251 ljmp $SEL_SCODE16,$pm_16 # Jump to 16-bit PM
253 pm_16: mov $SEL_RDATA,%ax # Initialize
254 mov %ax,%ds # %ds and
255 mov %ax,%es # %es to a real mode selector
256 mov %cr0,%eax # Turn off
257 and $~0x1,%al # protected
259 ljmp $0,$pm_end # Long jump to clear the
260 # instruction pre-fetch queue
261 pm_end: sti # Turn interrupts back on now
263 # Copy the BTX client to MEM_BTX_CLIENT
265 xor %ax,%ax # zero %ax and set
266 mov %ax,%ds # %ds and %es
267 mov %ax,%es # to segment 0
268 mov $MEM_BTX_CLIENT,%di # Prepare to relocate
269 mov $btx_client,%si # the simple btx client
270 mov $(btx_client_end-btx_client),%cx # length of btx client
272 movsb # simple BTX client
274 # Copy the boot[12] args to where the BTX client can see them
276 mov $MEM_ARG,%si # where the args are at now
277 mov $MEM_ARG_BTX,%di # where the args are moving to
278 mov $(MEM_ARG_SIZE/4),%cx # size of the arguments in longs
282 # Save the entry point so the client can get to it later on
284 pop %eax # Restore saved entry point
285 stosl # and add it to the end of
288 # Now we just start up BTX and let it do the rest
290 mov $msg_jump,%si # Display the
291 call putstr # jump message
292 ljmp $0,$MEM_BTX_ENTRY # Jump to the BTX entry point
295 # Lookup the file in the path at [SI] from the root directory.
297 # Trashes: All but BX
298 # Returns: CF = 0 (success), BX = pointer to record
301 lookup: mov $VD_ROOTDIR+MEM_VOLDESC,%bx # Root directory record
303 mov $msg_lookup,%si # Display lookup message
311 lookup_dir: lodsb # Get first char of path
312 cmp $0,%al # Are we done?
314 cmp $'/',%al # Skip path separator.
316 dec %si # Undo lodsb side effect
317 call find_file # Lookup first path item
318 jnc lookup_dir # Try next component
319 mov $msg_lookupfail,%si # Not found message
324 lookup_done: mov $msg_lookupok,%si # Success message
330 # Lookup file at [SI] in directory whose record is at [BX].
332 # Trashes: All but returns
333 # Returns: CF = 0 (success), BX = pointer to record, SI = next path item
334 # CF = 1 (not found), SI = preserved
336 find_file: mov DIR_EXTENT(%bx),%eax # Load extent
338 mov DIR_EA_LEN(%bx),%dl
339 add %edx,%eax # Skip extended attributes
340 mov %eax,rec_lba # Save LBA
341 mov DIR_SIZE(%bx),%eax # Save size
343 xor %cl,%cl # Zero length
345 ff.namelen: inc %cl # Update length
349 cmp $'/',%al # Path separator?
350 jnz ff.namelen # No, keep going
351 ff.namedone: dec %cl # Adjust length and save
354 ff.load: mov rec_lba,%eax # Load LBA
355 mov $MEM_DIR,%ebx # Address buffer
356 mov $1,%dh # One sector
357 call read # Read directory block
358 incl rec_lba # Update LBA to next block
359 ff.scan: mov %ebx,%edx # Check for EOF
365 ff.scan.1: cmpb $0,DIR_LEN(%bx) # Last record in block?
368 movzbw DIR_NAMELEN(%bx),%si # Find end of string
369 ff.checkver: cmpb $'0',DIR_NAME-1(%bx,%si) # Less than '0'?
371 cmpb $'9',DIR_NAME-1(%bx,%si) # Greater than '9'?
375 jmp ff.checklen # All numbers in name, so
377 ff.checkver.1: movzbw DIR_NAMELEN(%bx),%cx
378 cmp %cx,%si # Did we find any digits?
380 cmpb $';',DIR_NAME-1(%bx,%si) # Check for semicolon
382 dec %si # Skip semicolon
384 mov %cl,DIR_NAMELEN(%bx) # Adjust length
386 ff.checkver.2: mov %cx,%si # Restore %si to end of string
387 ff.checkdot: cmpb $'.',DIR_NAME-1(%bx,%si) # Trailing dot?
389 decb DIR_NAMELEN(%bx) # Adjust length
390 ff.checklen: pop %si # Restore
391 movzbw name_len,%cx # Load length of name
392 cmp %cl,DIR_NAMELEN(%bx) # Does length match?
393 je ff.checkname # Yes, check name
394 ff.nextrec: add DIR_LEN(%bx),%bl # Next record
397 ff.nextblock: subl $SECTOR_SIZE,rec_size # Adjust size
398 jnc ff.load # If subtract ok, keep going
399 ret # End of file, so not found
400 ff.checkname: lea DIR_NAME(%bx),%di # Address name in record
402 repe cmpsb # Compare name
403 je ff.match # We have a winner!
405 jmp ff.nextrec # Keep looking.
406 ff.match: add $2,%sp # Discard saved %si
411 # Load DH sectors starting at LBA EAX into [EBX].
415 read: push %si # Save
416 push %cx # Save since some BIOSs trash
417 mov %eax,edd_lba # LBA to read from
418 mov %ebx,%eax # Convert address
419 shr $4,%eax # to segment
420 mov %ax,edd_addr+0x2 # and store
421 read.retry: call twiddle # Entertain the user
423 mov $edd_packet,%si # Address Packet
424 mov %dh,edd_len # Set length
425 mov drive,%dl # BIOS Device
426 mov $0x42,%ah # BIOS: Extended Read
427 int $0x13 # Call BIOS
429 jc read.fail # Worked?
433 read.fail: cmp $ERROR_TIMEOUT,%ah # Timeout?
434 je read.retry # Yes, Retry.
435 read.error: mov %ah,%al # Save error
436 mov $hex_error,%di # Format it
438 mov $msg_badread,%si # Display Read error message
441 # Display error message at [SI] and halt.
443 error: call putstr # Display message
448 # Display a null-terminated string.
452 putstr: push %bx # Save
453 putstr.load: lodsb # load %al from %ds:(%si)
454 test %al,%al # stop at null
455 jnz putstr.putc # if the char != null, output it
457 ret # return when null is hit
458 putstr.putc: call putc # output char
459 jmp putstr.load # next char
462 # Display a single char.
464 putc: mov $0x7,%bx # attribute for output
465 mov $0xe,%ah # BIOS: put_char
466 int $0x10 # call BIOS, print char in %al
467 ret # Return to caller
470 # Output the "twiddle"
472 twiddle: push %ax # Save
474 mov twiddle_index,%al # Load index
475 mov $twiddle_chars,%bx # Address table
478 mov %al,twiddle_index # Save index for next call
480 call putc # Output it
481 mov $8,%al # Backspace
482 call putc # Output it
488 # Enable A20. Put an upper limit on the amount of time we wait for the
489 # keyboard controller to get ready (65K x ISA access time). If
490 # we wait more than that amount, the hardware is probably
491 # legacy-free and simply doesn't have a keyboard controller.
492 # Thus, the A20 line is already enabled.
494 seta20: cli # Disable interrupts
496 seta20.1: inc %cx # Increment, overflow?
498 in $0x64,%al # Get status
499 test $0x2,%al # Busy?
501 mov $0xd1,%al # Command: Write
502 out %al,$0x64 # output port
503 seta20.2: in $0x64,%al # Get status
504 test $0x2,%al # Busy?
506 mov $0xdf,%al # Enable
508 seta20.3: sti # Enable interrupts
512 # Convert AL to hex, saving the result to [EDI].
514 hex8: pushl %eax # Save
515 shrb $0x4,%al # Do upper
518 hex8.1: andb $0xf,%al # Get lower 4
519 cmpb $0xa,%al # Convert
520 sbbb $0x69,%al # to hex
522 orb $0x20,%al # To lower case
527 # BTX client to start btxldr
530 btx_client: mov $(MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE-4), %esi
533 mov $(MEM_ARG_SIZE/4),%ecx # Number of words to push
535 push_arg: lodsl # Read argument
536 push %eax # Push it onto the stack
537 loop push_arg # Push all of the arguments
538 cld # In case anyone depends on this
539 pushl MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE # Entry point of
541 push %eax # Emulate a near call
542 mov $0x1,%eax # 'exec' system call
543 int $INT_SYS # BTX system call
549 # Global descriptor table.
551 gdt: .word 0x0,0x0,0x0,0x0 # Null entry
552 .word 0xffff,0x0,0x9200,0xcf # SEL_SDATA
553 .word 0xffff,0x0,0x9200,0x0 # SEL_RDATA
554 .word 0xffff,0x0,0x9a00,0xcf # SEL_SCODE (32-bit)
555 .word 0xffff,0x0,0x9a00,0x8f # SEL_SCODE16 (16-bit)
558 # Pseudo-descriptors.
560 gdtdesc: .word gdt.1-gdt-1 # Limit
565 edd_packet: .byte 0x10 # Length
567 edd_len: .byte 0x0 # Num to read
569 edd_addr: .word 0x0,0x0 # Seg:Off
570 edd_lba: .quad 0x0 # LBA
575 # State for searching dir
577 rec_lba: .long 0x0 # LBA (adjusted for EA)
578 rec_size: .long 0x0 # File size
579 name_len: .byte 0x0 # Length of current name
581 twiddle_index: .byte 0x0
583 msg_welcome: .asciz "CD Loader 1.2\r\n\n"
584 msg_bootinfo: .asciz "Building the boot loader arguments\r\n"
585 msg_relocate: .asciz "Relocating the loader and the BTX\r\n"
586 msg_jump: .asciz "Starting the BTX loader\r\n"
587 msg_badread: .ascii "Read Error: 0x"
588 hex_error: .asciz "00\r\n"
589 msg_novd: .asciz "Could not find Primary Volume Descriptor\r\n"
590 msg_lookup: .asciz "Looking up "
591 msg_lookup2: .asciz "... "
592 msg_lookupok: .asciz "Found\r\n"
593 msg_lookupfail: .asciz "File not found\r\n"
594 msg_load2big: .asciz "File too big\r\n"
595 msg_failed: .asciz "Boot failed\r\n"
596 twiddle_chars: .ascii "|/-\\"
597 loader_paths: .asciz "/BOOT/LOADER"
598 .asciz "/boot/loader"