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