1# 2# Copyright (c) 2001 John Baldwin <jhb@FreeBSD.org> 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions 6# are met: 7# 1. Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# 2. Redistributions in binary form must reproduce the above copyright 10# notice, this list of conditions and the following disclaimer in the 11# documentation and/or other materials provided with the distribution. 12# 13# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23# SUCH DAMAGE. 24# 25 26# 27# This program is a freestanding boot program to load an a.out binary 28# from a CD-ROM booted with no emulation mode as described by the El 29# Torito standard. Due to broken BIOSen that do not load the desired 30# number of sectors, we try to fit this in as small a space as possible. 31# 32# Basically, we first create a set of boot arguments to pass to the loaded 33# binary. Then we attempt to load /boot/loader from the CD we were booted 34# off of. 35# 36 37#include <bootargs.h> 38 39# 40# Memory locations. 41# 42 .set MEM_PAGE_SIZE,0x1000 # memory page size, 4k 43 .set MEM_ARG,0x900 # Arguments at start 44 .set MEM_ARG_BTX,0xa100 # Where we move them to so the 45 # BTX client can see them 46 .set MEM_ARG_SIZE,0x18 # Size of the arguments 47 .set MEM_BTX_ADDRESS,0x9000 # where BTX lives 48 .set MEM_BTX_ENTRY,0x9010 # where BTX starts to execute 49 .set MEM_BTX_OFFSET,MEM_PAGE_SIZE # offset of BTX in the loader 50 .set MEM_BTX_CLIENT,0xa000 # where BTX clients live 51# 52# a.out header fields 53# 54 .set AOUT_TEXT,0x04 # text segment size 55 .set AOUT_DATA,0x08 # data segment size 56 .set AOUT_BSS,0x0c # zero'd BSS size 57 .set AOUT_SYMBOLS,0x10 # symbol table 58 .set AOUT_ENTRY,0x14 # entry point 59 .set AOUT_HEADER,MEM_PAGE_SIZE # size of the a.out header 60# 61# Segment selectors. 62# 63 .set SEL_SDATA,0x8 # Supervisor data 64 .set SEL_RDATA,0x10 # Real mode data 65 .set SEL_SCODE,0x18 # PM-32 code 66 .set SEL_SCODE16,0x20 # PM-16 code 67# 68# BTX constants 69# 70 .set INT_SYS,0x30 # BTX syscall interrupt 71# 72# Constants for reading from the CD. 73# 74 .set ERROR_TIMEOUT,0x80 # BIOS timeout on read 75 .set NUM_RETRIES,3 # Num times to retry 76 .set SECTOR_SIZE,0x800 # size of a sector 77 .set SECTOR_SHIFT,11 # number of place to shift 78 .set BUFFER_LEN,0x100 # number of sectors in buffer 79 .set MAX_READ,0x10000 # max we can read at a time 80 .set MAX_READ_SEC,MAX_READ >> SECTOR_SHIFT 81 .set MEM_READ_BUFFER,0x9000 # buffer to read from CD 82 .set MEM_VOLDESC,MEM_READ_BUFFER # volume descriptor 83 .set MEM_DIR,MEM_VOLDESC+SECTOR_SIZE # Lookup buffer 84 .set VOLDESC_LBA,0x10 # LBA of vol descriptor 85 .set VD_PRIMARY,1 # Primary VD 86 .set VD_END,255 # VD Terminator 87 .set VD_ROOTDIR,156 # Offset of Root Dir Record 88 .set DIR_LEN,0 # Offset of Dir Record length 89 .set DIR_EA_LEN,1 # Offset of EA length 90 .set DIR_EXTENT,2 # Offset of 64-bit LBA 91 .set DIR_SIZE,10 # Offset of 64-bit length 92 .set DIR_NAMELEN,32 # Offset of 8-bit name len 93 .set DIR_NAME,33 # Offset of dir name 94# 95# We expect to be loaded by the BIOS at 0x7c00 (standard boot loader entry 96# point) 97# 98 .code16 99 .globl start 100 .org 0x0, 0x0 101# 102# Program start. 103# 104start: cld # string ops inc 105 xor %ax,%ax # zero %ax 106 mov %ax,%ss # setup the 107 mov $start,%sp # stack 108 mov %ax,%ds # setup the 109 mov %ax,%es # data segments 110 mov %dl,drive # Save BIOS boot device 111 mov $msg_welcome,%si # %ds:(%si) -> welcome message 112 call putstr # display the welcome message 113# 114# Setup the arguments that the loader is expecting from boot[12] 115# 116 mov $msg_bootinfo,%si # %ds:(%si) -> boot args message 117 call putstr # display the message 118 mov $MEM_ARG,%bx # %ds:(%bx) -> boot args 119 mov %bx,%di # %es:(%di) -> boot args 120 xor %eax,%eax # zero %eax 121 mov $(MEM_ARG_SIZE/4),%cx # Size of arguments in 32-bit 122 # dwords 123 rep # Clear the arguments 124 stosl # to zero 125 mov drive,%dl # Store BIOS boot device 126 mov %dl,0x4(%bx) # in kargs->bootdev 127 orb $KARGS_FLAGS_CD,0x8(%bx) # kargs->bootflags |= 128 # KARGS_FLAGS_CD 129# 130# Load Volume Descriptor 131# 132 mov $VOLDESC_LBA,%eax # Set LBA of first VD 133load_vd: push %eax # Save %eax 134 mov $1,%dh # One sector 135 mov $MEM_VOLDESC,%ebx # Destination 136 call read # Read it in 137 cmpb $VD_PRIMARY,(%bx) # Primary VD? 138 je have_vd # Yes 139 pop %eax # Prepare to 140 inc %eax # try next 141 cmpb $VD_END,(%bx) # Last VD? 142 jne load_vd # No, read next 143 mov $msg_novd,%si # No VD 144 jmp error # Halt 145have_vd: # Have Primary VD 146# 147# Try to look up the loader binary using the paths in the loader_paths 148# array. 149# 150 mov $loader_paths,%si # Point to start of array 151lookup_path: push %si # Save file name pointer 152 call lookup # Try to find file 153 pop %di # Restore file name pointer 154 jnc lookup_found # Found this file 155 xor %al,%al # Look for next 156 mov $0xffff,%cx # path name by 157 repnz # scanning for 158 scasb # nul char 159 mov %di,%si # Point %si at next path 160 mov (%si),%al # Get first char of next path 161 or %al,%al # Is it double nul? 162 jnz lookup_path # No, try it. 163 mov $msg_failed,%si # Failed message 164 jmp error # Halt 165lookup_found: # Found a loader file 166# 167# Load the binary into the buffer. Due to real mode addressing limitations 168# we have to read it in 64k chunks. 169# 170 mov DIR_SIZE(%bx),%eax # Read file length 171 add $SECTOR_SIZE-1,%eax # Convert length to sectors 172 shr $SECTOR_SHIFT,%eax 173 cmp $BUFFER_LEN,%eax 174 jbe load_sizeok 175 mov $msg_load2big,%si # Error message 176 call error 177load_sizeok: movzbw %al,%cx # Num sectors to read 178 mov DIR_EXTENT(%bx),%eax # Load extent 179 xor %edx,%edx 180 mov DIR_EA_LEN(%bx),%dl 181 add %edx,%eax # Skip extended 182 mov $MEM_READ_BUFFER,%ebx # Read into the buffer 183load_loop: mov %cl,%dh 184 cmp $MAX_READ_SEC,%cl # Truncate to max read size 185 jbe load_notrunc 186 mov $MAX_READ_SEC,%dh 187load_notrunc: sub %dh,%cl # Update count 188 push %eax # Save 189 call read # Read it in 190 pop %eax # Restore 191 add $MAX_READ_SEC,%eax # Update LBA 192 add $MAX_READ,%ebx # Update dest addr 193 jcxz load_done # Done? 194 jmp load_loop # Keep going 195load_done: 196# 197# Turn on the A20 address line 198# 199 call seta20 # Turn A20 on 200# 201# Relocate the loader and BTX using a very lazy protected mode 202# 203 mov $msg_relocate,%si # Display the 204 call putstr # relocation message 205 mov MEM_READ_BUFFER+AOUT_ENTRY,%edi # %edi is the destination 206 mov $(MEM_READ_BUFFER+AOUT_HEADER),%esi # %esi is 207 # the start of the text 208 # segment 209 mov MEM_READ_BUFFER+AOUT_TEXT,%ecx # %ecx = length of the text 210 # segment 211 push %edi # Save entry point for later 212 lgdt gdtdesc # setup our own gdt 213 cli # turn off interrupts 214 mov %cr0,%eax # Turn on 215 or $0x1,%al # protected 216 mov %eax,%cr0 # mode 217 ljmp $SEL_SCODE,$pm_start # long jump to clear the 218 # instruction pre-fetch queue 219 .code32 220pm_start: mov $SEL_SDATA,%ax # Initialize 221 mov %ax,%ds # %ds and 222 mov %ax,%es # %es to a flat selector 223 rep # Relocate the 224 movsb # text segment 225 add $(MEM_PAGE_SIZE - 1),%edi # pad %edi out to a new page 226 and $~(MEM_PAGE_SIZE - 1),%edi # for the data segment 227 mov MEM_READ_BUFFER+AOUT_DATA,%ecx # size of the data segment 228 rep # Relocate the 229 movsb # data segment 230 mov MEM_READ_BUFFER+AOUT_BSS,%ecx # size of the bss 231 xor %eax,%eax # zero %eax 232 add $3,%cl # round %ecx up to 233 shr $2,%ecx # a multiple of 4 234 rep # zero the 235 stosl # bss 236 mov MEM_READ_BUFFER+AOUT_ENTRY,%esi # %esi -> relocated loader 237 add $MEM_BTX_OFFSET,%esi # %esi -> BTX in the loader 238 mov $MEM_BTX_ADDRESS,%edi # %edi -> where BTX needs to go 239 movzwl 0xa(%esi),%ecx # %ecx -> length of BTX 240 rep # Relocate 241 movsb # BTX 242 ljmp $SEL_SCODE16,$pm_16 # Jump to 16-bit PM 243 .code16 244pm_16: mov $SEL_RDATA,%ax # Initialize 245 mov %ax,%ds # %ds and 246 mov %ax,%es # %es to a real mode selector 247 mov %cr0,%eax # Turn off 248 and $~0x1,%al # protected 249 mov %eax,%cr0 # mode 250 ljmp $0,$pm_end # Long jump to clear the 251 # instruction pre-fetch queue 252pm_end: sti # Turn interrupts back on now 253# 254# Copy the BTX client to MEM_BTX_CLIENT 255# 256 xor %ax,%ax # zero %ax and set 257 mov %ax,%ds # %ds and %es 258 mov %ax,%es # to segment 0 259 mov $MEM_BTX_CLIENT,%di # Prepare to relocate 260 mov $btx_client,%si # the simple btx client 261 mov $(btx_client_end-btx_client),%cx # length of btx client 262 rep # Relocate the 263 movsb # simple BTX client 264# 265# Copy the boot[12] args to where the BTX client can see them 266# 267 mov $MEM_ARG,%si # where the args are at now 268 mov $MEM_ARG_BTX,%di # where the args are moving to 269 mov $(MEM_ARG_SIZE/4),%cx # size of the arguments in longs 270 rep # Relocate 271 movsl # the words 272# 273# Save the entry point so the client can get to it later on 274# 275 pop %eax # Restore saved entry point 276 stosl # and add it to the end of 277 # the arguments 278# 279# Now we just start up BTX and let it do the rest 280# 281 mov $msg_jump,%si # Display the 282 call putstr # jump message 283 ljmp $0,$MEM_BTX_ENTRY # Jump to the BTX entry point 284 285# 286# Lookup the file in the path at [SI] from the root directory. 287# 288# Trashes: All but BX 289# Returns: CF = 0 (success), BX = pointer to record 290# CF = 1 (not found) 291# 292lookup: mov $VD_ROOTDIR+MEM_VOLDESC,%bx # Root directory record 293 push %si 294 mov $msg_lookup,%si # Display lookup message 295 call putstr 296 pop %si 297 push %si 298 call putstr 299 mov $msg_lookup2,%si 300 call putstr 301 pop %si 302lookup_dir: lodsb # Get first char of path 303 cmp $0,%al # Are we done? 304 je lookup_done # Yes 305 cmp $'/',%al # Skip path separator. 306 je lookup_dir 307 dec %si # Undo lodsb side effect 308 call find_file # Lookup first path item 309 jnc lookup_dir # Try next component 310 mov $msg_lookupfail,%si # Not found message 311 call putstr 312 stc # Set carry 313 ret 314 jmp error 315lookup_done: mov $msg_lookupok,%si # Success message 316 call putstr 317 clc # Clear carry 318 ret 319 320# 321# Lookup file at [SI] in directory whose record is at [BX]. 322# 323# Trashes: All but returns 324# Returns: CF = 0 (success), BX = pointer to record, SI = next path item 325# CF = 1 (not found), SI = preserved 326# 327find_file: mov DIR_EXTENT(%bx),%eax # Load extent 328 xor %edx,%edx 329 mov DIR_EA_LEN(%bx),%dl 330 add %edx,%eax # Skip extended attributes 331 mov %eax,rec_lba # Save LBA 332 mov DIR_SIZE(%bx),%eax # Save size 333 mov %eax,rec_size 334 xor %cl,%cl # Zero length 335 push %si # Save 336ff.namelen: inc %cl # Update length 337 lodsb # Read char 338 cmp $0,%al # Nul? 339 je ff.namedone # Yes 340 cmp $'/',%al # Path separator? 341 jnz ff.namelen # No, keep going 342ff.namedone: dec %cl # Adjust length and save 343 mov %cl,name_len 344 pop %si # Restore 345ff.load: mov rec_lba,%eax # Load LBA 346 mov $MEM_DIR,%ebx # Address buffer 347 mov $1,%dh # One sector 348 call read # Read directory block 349 incl rec_lba # Update LBA to next block 350ff.scan: mov %ebx,%edx # Check for EOF 351 sub $MEM_DIR,%edx 352 cmp %edx,rec_size 353 ja ff.scan.1 354 stc # EOF reached 355 ret 356ff.scan.1: cmpb $0,DIR_LEN(%bx) # Last record in block? 357 je ff.nextblock 358 push %si # Save 359 movzbw DIR_NAMELEN(%bx),%si # Find end of string 360ff.checkver: cmpb $'0',DIR_NAME-1(%bx,%si) # Less than '0'? 361 jb ff.checkver.1 362 cmpb $'9',DIR_NAME-1(%bx,%si) # Greater than '9'? 363 ja ff.checkver.1 364 dec %si # Next char 365 jnz ff.checkver 366 jmp ff.checklen # All numbers in name, so 367 # no version 368ff.checkver.1: movzbw DIR_NAMELEN(%bx),%cx 369 cmp %cx,%si # Did we find any digits? 370 je ff.checkdot # No 371 cmpb $';',DIR_NAME-1(%bx,%si) # Check for semicolon 372 jne ff.checkver.2 373 dec %si # Skip semicolon 374 mov %si,%cx 375 mov %cl,DIR_NAMELEN(%bx) # Adjust length 376 jmp ff.checkdot 377ff.checkver.2: mov %cx,%si # Restore %si to end of string 378ff.checkdot: cmpb $'.',DIR_NAME-1(%bx,%si) # Trailing dot? 379 jne ff.checklen # No 380 decb DIR_NAMELEN(%bx) # Adjust length 381ff.checklen: pop %si # Restore 382 movzbw name_len,%cx # Load length of name 383 cmp %cl,DIR_NAMELEN(%bx) # Does length match? 384 je ff.checkname # Yes, check name 385ff.nextrec: add DIR_LEN(%bx),%bl # Next record 386 adc $0,%bh 387 jmp ff.scan 388ff.nextblock: subl $SECTOR_SIZE,rec_size # Adjust size 389 jnc ff.load # If subtract ok, keep going 390 ret # End of file, so not found 391ff.checkname: lea DIR_NAME(%bx),%di # Address name in record 392 push %si # Save 393 repe cmpsb # Compare name 394 je ff.match # We have a winner! 395 pop %si # Restore 396 jmp ff.nextrec # Keep looking. 397ff.match: add $2,%sp # Discard saved %si 398 clc # Clear carry 399 ret 400 401# 402# Load DH sectors starting at LBA EAX into [EBX]. 403# 404# Trashes: EAX 405# 406read: push %si # Save 407 push %cx # Save since some BIOSs trash 408 mov %eax,edd_lba # LBA to read from 409 mov %ebx,%eax # Convert address 410 shr $4,%eax # to segment 411 mov %ax,edd_addr+0x2 # and store 412read.retry: call twiddle # Entertain the user 413 push %dx # Save 414 mov $edd_packet,%si # Address Packet 415 mov %dh,edd_len # Set length 416 mov drive,%dl # BIOS Device 417 mov $0x42,%ah # BIOS: Extended Read 418 int $0x13 # Call BIOS 419 pop %dx # Restore 420 jc read.fail # Worked? 421 pop %cx # Restore 422 pop %si 423 ret # Return 424read.fail: cmp $ERROR_TIMEOUT,%ah # Timeout? 425 je read.retry # Yes, Retry. 426read.error: mov %ah,%al # Save error 427 mov $hex_error,%di # Format it 428 call hex8 # as hex 429 mov $msg_badread,%si # Display Read error message 430 431# 432# Display error message at [SI] and halt. 433# 434error: call putstr # Display message 435halt: hlt 436 jmp halt # Spin 437 438# 439# Display a null-terminated string. 440# 441# Trashes: AX, SI 442# 443putstr: push %bx # Save 444putstr.load: lodsb # load %al from %ds:(%si) 445 test %al,%al # stop at null 446 jnz putstr.putc # if the char != null, output it 447 pop %bx # Restore 448 ret # return when null is hit 449putstr.putc: call putc # output char 450 jmp putstr.load # next char 451 452# 453# Display a single char. 454# 455putc: mov $0x7,%bx # attribute for output 456 mov $0xe,%ah # BIOS: put_char 457 int $0x10 # call BIOS, print char in %al 458 ret # Return to caller 459 460# 461# Output the "twiddle" 462# 463twiddle: push %ax # Save 464 push %bx # Save 465 mov twiddle_index,%al # Load index 466 mov $twiddle_chars,%bx # Address table 467 inc %al # Next 468 and $3,%al # char 469 mov %al,twiddle_index # Save index for next call 470 xlat # Get char 471 call putc # Output it 472 mov $8,%al # Backspace 473 call putc # Output it 474 pop %bx # Restore 475 pop %ax # Restore 476 ret 477 478# 479# Enable A20. Put an upper limit on the amount of time we wait for the 480# keyboard controller to get ready (65K x ISA access time). If 481# we wait more than that amount, the hardware is probably 482# legacy-free and simply doesn't have a keyboard controller. 483# Thus, the A20 line is already enabled. 484# 485seta20: cli # Disable interrupts 486 xor %cx,%cx # Clear 487seta20.1: inc %cx # Increment, overflow? 488 jz seta20.3 # Yes 489 in $0x64,%al # Get status 490 test $0x2,%al # Busy? 491 jnz seta20.1 # Yes 492 mov $0xd1,%al # Command: Write 493 out %al,$0x64 # output port 494seta20.2: in $0x64,%al # Get status 495 test $0x2,%al # Busy? 496 jnz seta20.2 # Yes 497 mov $0xdf,%al # Enable 498 out %al,$0x60 # A20 499seta20.3: sti # Enable interrupts 500 ret # To caller 501 502# 503# Convert AL to hex, saving the result to [EDI]. 504# 505hex8: pushl %eax # Save 506 shrb $0x4,%al # Do upper 507 call hex8.1 # 4 508 popl %eax # Restore 509hex8.1: andb $0xf,%al # Get lower 4 510 cmpb $0xa,%al # Convert 511 sbbb $0x69,%al # to hex 512 das # digit 513 orb $0x20,%al # To lower case 514 stosb # Save char 515 ret # (Recursive) 516 517# 518# BTX client to start btxldr 519# 520 .code32 521btx_client: mov $(MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE-4), %esi 522 # %ds:(%esi) -> end 523 # of boot[12] args 524 mov $(MEM_ARG_SIZE/4),%ecx # Number of words to push 525 std # Go backwards 526push_arg: lodsl # Read argument 527 push %eax # Push it onto the stack 528 loop push_arg # Push all of the arguments 529 cld # In case anyone depends on this 530 pushl MEM_ARG_BTX-MEM_BTX_CLIENT+MEM_ARG_SIZE # Entry point of 531 # the loader 532 push %eax # Emulate a near call 533 mov $0x1,%eax # 'exec' system call 534 int $INT_SYS # BTX system call 535btx_client_end: 536 .code16 537 538 .p2align 4 539# 540# Global descriptor table. 541# 542gdt: .word 0x0,0x0,0x0,0x0 # Null entry 543 .word 0xffff,0x0,0x9200,0xcf # SEL_SDATA 544 .word 0xffff,0x0,0x9200,0x0 # SEL_RDATA 545 .word 0xffff,0x0,0x9a00,0xcf # SEL_SCODE (32-bit) 546 .word 0xffff,0x0,0x9a00,0x8f # SEL_SCODE16 (16-bit) 547gdt.1: 548# 549# Pseudo-descriptors. 550# 551gdtdesc: .word gdt.1-gdt-1 # Limit 552 .long gdt # Base 553# 554# EDD Packet 555# 556edd_packet: .byte 0x10 # Length 557 .byte 0 # Reserved 558edd_len: .byte 0x0 # Num to read 559 .byte 0 # Reserved 560edd_addr: .word 0x0,0x0 # Seg:Off 561edd_lba: .quad 0x0 # LBA 562 563drive: .byte 0 564 565# 566# State for searching dir 567# 568rec_lba: .long 0x0 # LBA (adjusted for EA) 569rec_size: .long 0x0 # File size 570name_len: .byte 0x0 # Length of current name 571 572twiddle_index: .byte 0x0 573 574msg_welcome: .asciz "CD Loader 1.2\r\n\n" 575msg_bootinfo: .asciz "Building the boot loader arguments\r\n" 576msg_relocate: .asciz "Relocating the loader and the BTX\r\n" 577msg_jump: .asciz "Starting the BTX loader\r\n" 578msg_badread: .ascii "Read Error: 0x" 579hex_error: .asciz "00\r\n" 580msg_novd: .asciz "Could not find Primary Volume Descriptor\r\n" 581msg_lookup: .asciz "Looking up " 582msg_lookup2: .asciz "... " 583msg_lookupok: .asciz "Found\r\n" 584msg_lookupfail: .asciz "File not found\r\n" 585msg_load2big: .asciz "File too big\r\n" 586msg_failed: .asciz "Boot failed\r\n" 587twiddle_chars: .ascii "|/-\\" 588loader_paths: .asciz "/BOOT/LOADER" 589 .asciz "/boot/loader" 590 .byte 0 591 592