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: 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.8 2007/05/18 07:41:43 dillon Exp $ 18 */ 19 20/* 21 * This program is a freestanding boot program to load an a.out binary 22 * from a CD-ROM booted with no emulation mode as described by the El 23 * Torito standard. Due to broken BIOSen that do not load the desired 24 * number of sectors, we try to fit this in as small a space as possible. 25 * 26 * Basically, we first create a set of boot arguments to pass to the loaded 27 * binary. Then we attempt to load /boot/loader from the CD we were booted 28 * off of. 29 */ 30 31#include "../bootasm.h" 32 33 /* 34 * a.out header fields 35 */ 36 .set AOUT_TEXT,0x04 # text segment size 37 .set AOUT_DATA,0x08 # data segment size 38 .set AOUT_BSS,0x0c # zerod BSS size 39 .set AOUT_SYMBOLS,0x10 # symbol table 40 .set AOUT_ENTRY,0x14 # entry point 41 .set AOUT_HEADER,MEM_PAGE_SIZE # size of the a.out header 42 43 /* 44 * Flags for kargs->bootflags 45 */ 46 .set KARGS_FLAGS_CD,0x1 # flag to indicate booting from 47 # CD loader 48 /* 49 * Segment selectors. 50 */ 51 .set SEL_SDATA,0x8 # Supervisor data 52 .set SEL_RDATA,0x10 # Real mode data 53 .set SEL_SCODE,0x18 # PM-32 code 54 .set SEL_SCODE16,0x20 # PM-16 code 55 56 /* 57 * BTX constants 58 */ 59 .set INT_SYS,0x30 # BTX syscall interrupt 60 61 /* 62 * Constants for reading from the CD. 63 */ 64 .set ERROR_TIMEOUT,0x80 # BIOS timeout on read 65 .set NUM_RETRIES,3 # Num times to retry 66 .set SECTOR_SIZE,0x800 # size of a sector 67 .set SECTOR_SHIFT,11 # number of place to shift 68 .set BUFFER_LEN,0x100 # number of sectors in buffer 69# Some BIOSes just can't handle this 70# .set MAX_READ,0x10000 # max we can read at a time 71# .set MAX_READ_SEC,MAX_READ >> SECTOR_SHIFT 72 .set MAX_READ,0x800 # max we can read at a time 73 .set MAX_READ_SEC,1 74 .set MEM_READ_BUFFER,0x9000 # buffer to read from CD 75 .set MEM_VOLDESC,MEM_READ_BUFFER # volume descriptor 76 .set MEM_DIR,MEM_VOLDESC+SECTOR_SIZE # Lookup buffer 77 .set VOLDESC_LBA,0x10 # LBA of vol descriptor 78 .set VD_PRIMARY,1 # Primary VD 79 .set VD_END,255 # VD Terminator 80 .set VD_ROOTDIR,156 # Offset of Root Dir Record 81 .set DIR_LEN,0 # Offset of Dir Record length 82 .set DIR_EA_LEN,1 # Offset of EA length 83 .set DIR_EXTENT,2 # Offset of 64-bit LBA 84 .set DIR_SIZE,10 # Offset of 64-bit length 85 .set DIR_NAMELEN,32 # Offset of 8-bit name len 86 .set DIR_NAME,33 # Offset of dir name 87 88 /* 89 * Program start. 90 * 91 * We expect to be loaded by the BIOS at 0x7c00 (standard 92 * boot loader entry point) 93 */ 94 .code16 95 .globl start 96 .org 0x0, 0x0 97 98start: cld # string ops inc 99 xor %ax,%ax # zero %ax 100 mov %ax,%ss # setup the 101 mov $start,%sp # stack 102 mov %ax,%ds # setup the 103 mov %ax,%es # data segments 104 mov %dl,drive # Save BIOS boot device 105 sti # make sure intrs are enabled 106 mov $msg_welcome,%si # %ds:(%si) -> welcome message 107 call putstr # display the welcome message 108 109 /* 110 * Setup the arguments that the loader is expecting from 111 * boot[12] 112 */ 113 mov $msg_bootinfo,%si # %ds:(%si) -> boot args message 114 call putstr # display the message 115 mov $MEM_ARG,%bx # %ds:(%bx) -> boot args 116 mov %bx,%di # %es:(%di) -> boot args 117 xor %eax,%eax # zero %eax 118 mov $(MEM_ARG_SIZE/4),%cx # Size of arguments in 32-bit 119 # dwords 120 rep # Clear the arguments 121 stosl # to zero 122 mov drive,%dl # Store BIOS boot device 123 mov %dl,0x4(%bx) # in kargs->bootdev 124 or $KARGS_FLAGS_CD,0x8(%bx) # kargs->bootflags |= 125 # KARGS_FLAGS_CD 126 /* 127 * Load Volume Descriptor 128 */ 129 mov $VOLDESC_LBA,%eax # Set LBA of first VD 130load_vd: 131 mov $1,%dh # One sector 132 mov $MEM_VOLDESC,%ebx # Destination 133 call read # Read it in 134 cmpb $VD_PRIMARY,(%bx) # Primary VD? 135 je have_vd # Yes 136 inc %eax # try next 137 cmpb $VD_END,(%bx) # Last VD? 138 jne load_vd # No, read next 139 mov $msg_novd,%si # No VD 140 jmp error # Halt 141have_vd: # Have Primary VD 142 143 /* 144 * Lookup the loader binary. 145 */ 146 mov $loader_path,%si # File to lookup 147 call lookup # Try to find it 148 149 /* 150 * Load the binary into the buffer. Due to real mode 151 * addressing limitations we have to read it in in 64k 152 * chunks. 153 */ 154 mov DIR_SIZE(%bx),%eax # Read file length 155 add $SECTOR_SIZE-1,%eax # Convert length to sectors 156 shr $SECTOR_SHIFT,%eax 157 cmp $BUFFER_LEN,%eax 158 jbe load_sizeok 159 mov $msg_load2big,%si # Error message 160 call error 161load_sizeok: mov %al,%cl 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 call read # Read it in 173 add $MAX_READ_SEC,%eax # Update LBA 174 add $MAX_READ,%ebx # Update dest addr 175 testb %cl,%cl 176 jnz load_loop 177load_done: 178 /* 179 * Turn on the A20 address line. 180 */ 181 call seta20 # Turn A20 on 182 183 /* 184 * Relocate the loader and BTX using a very lazy 185 * 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_LDR_OFF,%esi # %esi -> BTX in the loader 222 mov $MEM_BTX_ORG,%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 /* 239 * Copy the BTX client to MEM_BTX_USR. 240 */ 241 xor %ax,%ax # zero %ax and set 242 mov %ax,%ds # %ds and %es 243 mov %ax,%es # to segment 0 244 mov $MEM_BTX_USR,%di # Prepare to relocate 245 mov $btx_client,%si # the simple btx client 246 mov $(btx_client_end-btx_client),%cx # length of btx client 247 rep # Relocate the 248 movsb # simple BTX client 249 250 /* 251 * Copy the boot[12] args to where the BTX client 252 * can see them. 253 */ 254 mov $MEM_ARG,%si # where the args are at now 255 mov $MEM_BTX_USR_ARG,%di # where the args are moving to 256 mov $(MEM_ARG_SIZE/4),%cx # size of the arguments in longs 257 rep # Relocate 258 movsl # the words 259 260 /* 261 * Save the entry point so the client can get to it 262 * later on 263 */ 264 pop %eax # Restore saved entry point 265 stosl # and add it to the end of 266 # the arguments 267 /* 268 * Now we just start up BTX and let it do the rest 269 */ 270 mov $msg_jump,%si # Display the 271 call putstr # jump message 272 ljmp $0,$MEM_BTX_ENTRY # Jump to the BTX entry point 273 274 /* 275 * Lookup the file in the path at [SI] from the root 276 * directory. 277 * 278 * Trashes: All but BX 279 * Returns: BX = pointer to record 280 */ 281lookup: mov $VD_ROOTDIR+MEM_VOLDESC,%bx # Root directory record 282 push %si 283 mov $msg_lookup,%si # Display lookup message 284 call putstr 285 pop %si 286 push %si 287 call putstr 288 mov $msg_lookup2,%si 289 call putstr 290 pop %si 291lookup_dir: lodsb # Get first char of path 292 cmp $0,%al # Are we done? 293 je lookup_done # Yes 294 cmp $'/',%al # Skip path separator. 295 je lookup_dir 296 dec %si # Undo lodsb side effect 297 call find_file # Lookup first path item 298 jnc lookup_dir # Try next component 299 mov $msg_lookupfail,%si # Not found. 300 jmp error 301lookup_done: mov $msg_lookupok,%si # Success message 302 call putstr 303 ret 304 305 /* 306 * Lookup file at [SI] in directory whose record is at [BX]. 307 * 308 * Trashes: All but returns 309 * 310 * Returns: CF = 0 (success) 311 * BX = pointer to record, 312 * SX = next path item 313 * CF = 1 (not found) 314 * SI = preserved 315 */ 316find_file: mov DIR_EXTENT(%bx),%eax # Load extent 317 xor %edx,%edx 318 mov DIR_EA_LEN(%bx),%dl 319 add %edx,%eax # Skip extended attributes 320 mov %eax,rec_lba # Save LBA 321 mov DIR_SIZE(%bx),%eax # Save size 322 mov %eax,rec_size 323 xor %cl,%cl # Zero length 324 push %si # Save 325ff.namelen: inc %cl # Update length 326 lodsb # Read char 327 cmp $0,%al # Nul? 328 je ff.namedone # Yes 329 cmp $'/',%al # Path separator? 330 jnz ff.namelen # No, keep going 331ff.namedone: dec %cl # Adjust length and save 332 mov %cl,name_len 333 pop %si # Restore 334ff.load: mov rec_lba,%eax # Load LBA 335 mov $MEM_DIR,%ebx # Address buffer 336 mov $1,%dh # One sector 337 call read # Read directory block 338 incl rec_lba # Update LBA to next block 339ff.scan: mov %ebx,%edx # Check for EOF 340 sub $MEM_DIR,%edx 341 cmp %edx,rec_size 342 ja ff.scan.1 343 stc # EOF reached 344 ret 345ff.scan.1: cmpb $0,DIR_LEN(%bx) # Last record in block? 346 je ff.nextblock 347 push %si # Save 348 movzbw DIR_NAMELEN(%bx),%si # Find end of string 349ff.checkver: cmpb $'0',DIR_NAME-1(%bx,%si) # Less than '0'? 350 jb ff.checkver.1 351 cmpb $'9',DIR_NAME-1(%bx,%si) # Greater than '9'? 352 ja ff.checkver.1 353 dec %si # Next char 354 jnz ff.checkver 355 jmp ff.checklen # All numbers in name, so 356 # no version 357ff.checkver.1: movzbw DIR_NAMELEN(%bx),%cx 358 cmp %cx,%si # Did we find any digits? 359 je ff.checkdot # No 360 cmpb $';',DIR_NAME-1(%bx,%si) # Check for semicolon 361 jne ff.checkver.2 362 dec %si # Skip semicolon 363 mov %si,%cx 364 mov %cl,DIR_NAMELEN(%bx) # Adjust length 365 jmp ff.checkdot 366ff.checkver.2: mov %cx,%si # Restore %si to end of string 367ff.checkdot: cmpb $'.',DIR_NAME-1(%bx,%si) # Trailing dot? 368 jne ff.checklen # No 369 decb DIR_NAMELEN(%bx) # Adjust length 370ff.checklen: pop %si # Restore 371 movzbw name_len,%cx # Load length of name 372 cmp %cl,DIR_NAMELEN(%bx) # Does length match? 373 je ff.checkname # Yes, check name 374ff.nextrec: add DIR_LEN(%bx),%bl # Next record 375 adc $0,%bh 376 jmp ff.scan 377ff.nextblock: subl $SECTOR_SIZE,rec_size # Adjust size 378 jnc ff.load # If subtract ok, keep going 379 ret # End of file, so not found 380ff.checkname: lea DIR_NAME(%bx),%di # Address name in record 381 push %si # Save 382 repe cmpsb # Compare name 383 testw %cx,%cx 384 jz ff.match # We have a winner! 385 pop %si # Restore 386 jmp ff.nextrec # Keep looking. 387ff.match: add $2,%sp # Discard saved %si 388 clc # Clear carry 389 ret 390 391 /* 392 * Load DH sectors starting at LBA EAX into [EBX]. No 393 * registers are destroyed. Don't trust the BIOS, especially 394 * with regards to the msb 16 bits of our registers. 395 */ 396read: pushal # dont screw around 397 mov %eax,edd_lba # LBA to read from 398 mov %ebx,%eax # Convert address 399 shr $4,%eax # to segment 400 mov %ax,edd_addr+0x2 # and store 401 call twiddle # Entertain the user 402 mov $edd_packet,%si # Address Packet 403 mov %dh,edd_len # Set length 404 mov drive,%dl # BIOS Device 405 mov $0x42,%ah # BIOS: Extended Read 406 int $0x13 # Call BIOS 407 jc read.fail # Worked? 408 popal 409 ret # Return 410read.fail: cmp $ERROR_TIMEOUT,%ah 411 jne read.error 412 413 # Tell the user what is going on 414 # 415 mov $msg_timeout,%si 416 call putstr 417 418 # If an error occurs wait a second and try again. Also 419 # reload the packet. In early boot CDs can timeout for 420 # a lengthy period and if we do not delay the retry 421 # the BIOS can get seriously confused and wind up returning 422 # endless timeouts. 423 # 424 movw $0200,%ax 425 int $0x1a # returns seconds in %dh 426 mov %dx,%ax 4271: push %ax 428 movw $0200,%ax # returns seconds in %dh 429 int $0x1a 430 pop %ax 431 cmp %ah,%dh # wait for seconds to cycle 432 je 1b 433 popal 434 jmp read 435 436read.error: mov %ah,%al # Save error 437 mov $hex_error,%di # Format it 438 call hex8 # as hex 439 mov $msg_badread,%si # Display Read error message 440 441 /* 442 * Display error message at [SI] and halt. 443 */ 444error: call putstr # Display message 445halt: hlt 446 jmp halt # Spin 447 448 /* 449 * Display a null-terminated string. 450 * 451 * Trashes: AX, SI 452 */ 453putstr: 454putstr.load: lodsb # load %al from %ds:(%si) 455 test %al,%al # stop at null 456 jnz putstr.putc # if the char != null, output it 457 ret # return when null is hit 458putstr.putc: call putc # output char 459 jmp putstr.load # next char 460 461 /* 462 * Display a single char(%al). Don't trust the bios to save 463 * our regs. 464 */ 465putc: pushal 466 mov $0x7,%bx # attribute for output 467 mov $0xe,%ah # BIOS: put_char 468 int $0x10 # call BIOS, print char in %al 469 popal 470 ret # Return to caller 471 472 /* 473 * Output the "twiddle" 474 */ 475twiddle: push %ax # Save 476 push %bx # Save 477 mov twiddle_index,%al # Load index 478 mov $twiddle_chars,%bx # Address table 479 inc %al # Next 480 and $3,%al # char 481 mov %al,twiddle_index # Save index for next call 482 xlat # Get char 483 call putc # Output it 484 mov $8,%al # Backspace 485 call putc # Output it 486 pop %bx # Restore 487 pop %ax # Restore 488 ret 489 490 /* 491 * Enable A20. Put upper limit on amount of time we wait for the 492 * keyboard controller to get ready (65K x ISA access time). If 493 * we wait more than that amount it's likely that the hardware 494 * is legacy-free and simply doesn't have keyboard controller 495 * and don't need enabling A20 at all. 496 */ 497seta20: cli # Disable interrupts 498 xor %cx,%cx # Clear 499seta20.1: inc %cx # Increment, overflow? 500 jz seta20.3 # Yes 501 in $0x64,%al # Get status 502 test $0x2,%al # Busy? 503 jnz seta20.1 # Yes 504 mov $0xd1,%al # Command: Write 505 out %al,$0x64 # output port 506seta20.2: in $0x64,%al # Get status 507 test $0x2,%al # Busy? 508 jnz seta20.2 # Yes 509 mov $0xdf,%al # Enable 510 out %al,$0x60 # A20 511seta20.3: sti # Enable interrupts 512 ret # To caller 513 514 /* 515 * Convert AL to hex, saving the result to [EDI]. 516 */ 517hex8: pushl %eax # Save 518 shrb $0x4,%al # Do upper 519 call hex8.1 # 4 520 popl %eax # Restore 521hex8.1: andb $0xf,%al # Get lower 4 522 cmpb $0xa,%al # Convert 523 sbbb $0x69,%al # to hex 524 das # digit 525 orb $0x20,%al # To lower case 526 stosb # Save char 527 ret # (Recursive) 528 529 /* 530 * BTX client to start btxldr 531 */ 532 .code32 533btx_client: mov $(MEM_BTX_USR_ARG-MEM_BTX_USR+MEM_ARG_SIZE-4), %esi 534 # %ds:(%esi) -> end 535 # of boot[12] args 536 mov $(MEM_ARG_SIZE/4),%ecx # Number of words to push 537 std # Go backwards 538push_arg: lodsl # Read argument 539 push %eax # Push it onto the stack 540 loop push_arg # Push all of the arguments 541 cld # In case anyone depends on this 542 pushl MEM_BTX_USR_ARG-MEM_BTX_USR+MEM_ARG_SIZE # Entry point of 543 # the loader 544 push %eax # Emulate a near call 545 mov $0x1,%eax # "exec" system call 546 int $INT_SYS # BTX system call 547btx_client_end: 548 .code16 549 550 .p2align 4 551 552 /* 553 * Global descriptor table. 554 */ 555gdt: .word 0x0,0x0,0x0,0x0 # Null entry 556 .word 0xffff,0x0,0x9200,0xcf # SEL_SDATA 557 .word 0xffff,0x0,0x9200,0x0 # SEL_RDATA 558 .word 0xffff,0x0,0x9a00,0xcf # SEL_SCODE (32-bit) 559 .word 0xffff,0x0,0x9a00,0x8f # SEL_SCODE16 (16-bit) 560gdt.1: 561 562 /* 563 * Pseudo-descriptors. 564 */ 565gdtdesc: .word gdt.1-gdt-1 # Limit 566 .long gdt # Base 567 568 /* 569 * EDD Packet 570 */ 571edd_packet: .byte 0x10 # Length 572 .byte 0 # Reserved 573edd_len: .byte 0x0 # Num to read 574 .byte 0 # Reserved 575edd_addr: .word 0x0,0x0 # Seg:Off 576edd_lba: .quad 0x0 # LBA 577 578drive: .byte 0 579 580 /* 581 * State for searching dir 582 */ 583rec_lba: .long 0x0 # LBA (adjusted for EA) 584rec_size: .long 0x0 # File size 585name_len: .byte 0x0 # Length of current name 586 587twiddle_index: .byte 0x0 588 589msg_welcome: .asciz "CD Loader 1.01\r\n\n" 590msg_bootinfo: .asciz "Building the boot loader arguments\r\n" 591msg_relocate: .asciz "Relocating the loader and the BTX\r\n" 592msg_jump: .asciz "Starting the BTX loader\r\n" 593msg_badread: .ascii "Read Error: 0x" 594hex_error: .asciz "00\r\n" 595msg_novd: .asciz "Could not find Primary Volume Descriptor\r\n" 596msg_lookup: .asciz "Looking up " 597msg_lookup2: .asciz "... " 598msg_lookupok: .asciz "Found\r\n" 599msg_lookupfail: .asciz "File not found\r\n" 600msg_load2big: .asciz "File too big\r\n" 601msg_timeout: .asciz "Drive not ready, retry\r\n" 602loader_path: .asciz "/BOOT/LOADER" 603twiddle_chars: .ascii "|/-\\" 604 605