diff options
author | Tom Cosgrove <tom@cvs.openbsd.org> | 2004-08-21 18:48:38 +0000 |
---|---|---|
committer | Tom Cosgrove <tom@cvs.openbsd.org> | 2004-08-21 18:48:38 +0000 |
commit | 1d0cf3361556a13c670d6364b82fd141715302b6 (patch) | |
tree | 7eb72d903f168a586384cafef2448ba7362df3e8 /sys/arch/amd64/stand/cdbr | |
parent | 1a2f17e99e40cadb8e6d9698c61424d2e7bc72c1 (diff) |
Enter cdbr, an El Torito no-emulation CD boot sector that fits
comfortably in a single CD sector of 2,048.
Based on the OpenBSD/i386 code that Toby and I put together at the
hackathon, which was in turn based on the FreeBSD equivalent by
John Baldwin, jhb (at) FreeBSD (dot) org.
"go for it" deraadt@
Diffstat (limited to 'sys/arch/amd64/stand/cdbr')
-rw-r--r-- | sys/arch/amd64/stand/cdbr/Makefile | 28 | ||||
-rw-r--r-- | sys/arch/amd64/stand/cdbr/cdbr.S | 526 |
2 files changed, 554 insertions, 0 deletions
diff --git a/sys/arch/amd64/stand/cdbr/Makefile b/sys/arch/amd64/stand/cdbr/Makefile new file mode 100644 index 00000000000..e79ea20c858 --- /dev/null +++ b/sys/arch/amd64/stand/cdbr/Makefile @@ -0,0 +1,28 @@ +# $OpenBSD: Makefile,v 1.1 2004/08/21 18:48:37 tom Exp $ +# + +PROG= cdbr +SRCS= cdbr.S +AFLAGS+=-m32 -I${.CURDIR} -I${.CURDIR}/../../.. #-Wa,-a +LD=ld +ORG= 0x0000 +LDFLAGS=-melf_i386 -nostdlib -Ttext ${ORG} -x -N -s -Bstatic -e start +LDFLAGS+=-L/usr/libdata + +NOMAN= +#MAN+= cdbr.8 + +INSTALL_STRIP= +SADIR=${.CURDIR}/.. +S= ${.CURDIR}/../../../.. + +${PROG}: $(OBJS) $(DPADD) + $(LD) $(LDFLAGS) -o $(PROG) $(OBJS) $(LDADD) + #@size $(PROG) + @if [ -x ${.OBJDIR}/${PROG} ]; then \ + objcopy -O binary ${PROG} ${.OBJDIR}/.tmp;\ + mv -f ${.OBJDIR}/.tmp ${.OBJDIR}/${PROG}; \ + ls -l ${.OBJDIR}/${PROG}; \ + fi + +.include <bsd.prog.mk> diff --git a/sys/arch/amd64/stand/cdbr/cdbr.S b/sys/arch/amd64/stand/cdbr/cdbr.S new file mode 100644 index 00000000000..4d619c62afb --- /dev/null +++ b/sys/arch/amd64/stand/cdbr/cdbr.S @@ -0,0 +1,526 @@ +/* $OpenBSD: cdbr.S,v 1.1 2004/08/21 18:48:37 tom Exp $ */ + +/* + * Copyright (c) 2004 Tom Cosgrove <tom.cosgrove@arches-consulting.com> + * Copyright (c) 2001 John Baldwin <jhb@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + .file "cdbr.S" + +/* #include <machine/asm.h> */ +/* #include <assym.h> */ + +/* + * This program is a CD boot sector, similar to the partition boot record + * (pbr, also called biosboot) used by hard disks. It is implemented as a + * "no-emulation" boot sector, as described in the "El Torito" Bootable + * CD-ROM Format Specification. + * + * The function of this boot sector is to load and start the next-stage + * cdboot program, which will load the kernel. + * + * The El Torito standard allows us to specify where we want to be loaded, + * but for maximum compatibility we choose the default load address of + * 0x07C00. + * + * Memory layout: + * + * 0x00000 -> 0x003FF real mode interrupt vector table + * 0x00400 -> 0x00500 BIOS data segment + * + * 0x00000 -> 0x073FF our stack (grows down) (from 29k) + * 0x07400 -> 0x07BFF we relocate to here (at 29k) + * 0x07C00 -> 0x08400 BIOS loads us here (at 31k, for 2k) + * 0x07C00 -> ... /cdboot + * + * The BIOS loads us at physical address 0x07C00. We then relocate to + * 0x07400, seg:offset 0740:0000. We then load /cdboot at seg:offset + * 07C0:0000. + */ +#define BOOTSEG 0x7c0 /* segment we're loaded to */ +#define BOOTSECTSIZE 0x800 /* our size in bytes */ +#define BOOTRELOCSEG 0x740 /* segment we relocate to */ +#define BOOTSTACKOFF ((BOOTRELOCSEG << 4) - 4) /* starts here, grows down */ + +/* Constants for reading from the CD */ +#define ERROR_TIMEOUT 0x80 /* BIOS timeout on read */ +#define NUM_RETRIES 3 /* Num times to retry */ +#define SECTOR_SIZE 0x800 /* size of a sector */ +#define SECTOR_SHIFT 11 /* number of place to shift */ +#define BUFFER_LEN 0x100 /* number of sectors in buffr */ +#define MAX_READ 0x10000 /* max we can read at a time */ +#define MAX_READ_PARAS MAX_READ >> 4 +#define MAX_READ_SEC MAX_READ >> SECTOR_SHIFT +#define MEM_READ_BUFFER 0x9000 /* buffer to read from CD */ +#define MEM_VOLDESC MEM_READ_BUFFER /* volume descriptor */ +#define MEM_DIR MEM_VOLDESC+SECTOR_SIZE /* Lookup buffer */ +#define VOLDESC_LBA 0x10 /* LBA of vol descriptor */ +#define VD_PRIMARY 1 /* Primary VD */ +#define VD_END 255 /* VD Terminator */ +#define VD_ROOTDIR 156 /* Offset of Root Dir Record */ +#define DIR_LEN 0 /* Offset of Dir Rec length */ +#define DIR_EA_LEN 1 /* Offset of EA length */ +#define DIR_EXTENT 2 /* Offset of 64-bit LBA */ +#define DIR_SIZE 10 /* Offset of 64-bit length */ +#define DIR_NAMELEN 32 /* Offset of 8-bit name len */ +#define DIR_NAME 33 /* Offset of dir name */ + + .text + .code16 + + .globl start +start: + /* Set up stack */ + xorw %ax, %ax + movw %ax, %ss + movw $BOOTSTACKOFF, %sp + + /* Relocate so we can load cdboot where we were */ + movw $BOOTSEG, %ax + movw %ax, %ds + movw $BOOTRELOCSEG, %ax + movw %ax, %es + xorw %si, %si + xorw %di, %di + movw $BOOTSECTSIZE, %cx /* Bytes in cdbr, relocate it all */ + cld + rep + movsb + + /* Jump to relocated self */ + ljmp $BOOTRELOCSEG, $reloc +reloc: + + /* + * Set up %ds and %es: %ds is our data segment (= %cs), %es is + * used to specify the segment address of the destination buffer + * for cd reads. We initially have %es = %ds. + */ + movw %cs, %ax + movw %ax, %ds + movw %ax, %es + + movb %dl, drive /* Store the boot drive number */ + + movw $signon, %si /* Say "hi", and give boot drive */ + call display_string + movb drive, %al + call hex_byte + movw $crlf, %si + call display_string + +/* + * Load Volume Descriptor + */ + movl $VOLDESC_LBA, %eax /* Get the sector # for vol desc */ +load_vd: + pushl %eax + movb $1, %dh /* One sector */ + movw $MEM_VOLDESC, %bx /* Destination */ + call read /* Read it in */ + popl %eax + cmpb $VD_PRIMARY, (%bx) /* Primary vol descriptor? */ + je have_vd /* Yes */ + inc %eax /* Try the next one */ + cmpb $VD_END, (%bx) /* Is it the last one? */ + jne load_vd /* No, so go try the next one */ + movw $msg_novd, %si /* No pri vol descriptor */ + jmp err_stop /* Panic */ +have_vd: /* Have Primary VD */ + +/* + * Look for the next-stage loader binary at pre-defined paths (loader_paths) + */ + movw $loader_paths, %si /* Point to start of array */ +lookup_path: + movw %si, loader /* remember the one we're looking for */ + pushw %si /* Save file name pointer */ + call lookup /* Try to find file */ + popw %di /* Restore file name pointer */ + jnc lookup_found /* Found this file */ + xorb %al, %al /* Look for next */ + movw $0xffff, %cx /* path name by */ + repnz /* scanning for */ + scasb /* nul char */ + movw %di, %si /* Point %si at next path */ + movb (%si), %al /* Get first char of next path */ + orb %al, %al /* Is it double nul? */ + jnz lookup_path /* No, try it */ + movw $msg_failed, %si /* Failed message */ + jmp err_stop /* Print it and halt */ + +lookup_found: /* Found a loader file */ + +/* + * Load the binary into the buffer. Due to real mode addressing limitations + * we have to read it in in 64k chunks. + */ + movl DIR_SIZE(%bx), %eax /* Read file length */ + add $SECTOR_SIZE-1, %eax /* Convert length to sectors */ + shr $SECTOR_SHIFT, %eax + cmp $BUFFER_LEN, %eax + jbe load_sizeok + movw $msg_load2big, %si /* Error message */ + jmp err_stop +load_sizeok: + movzbw %al, %cx /* Num sectors to read */ + movl DIR_EXTENT(%bx), %eax /* Load extent */ + xorl %edx, %edx + movb DIR_EA_LEN(%bx), %dl + addl %edx, %eax /* Skip extended */ + + /* Use %bx to hold the segment (para) number */ + movw $BOOTSEG, %bx /* We put cdboot here too */ +load_loop: + movb %cl, %dh + cmpb $MAX_READ_SEC, %cl /* Truncate to max read size */ + jbe load_notrunc + movb $MAX_READ_SEC, %dh +load_notrunc: + subb %dh, %cl /* Update count */ + pushl %eax /* Save */ + movw %bx, %es /* %bx had the segment (para) number */ + xorw %bx, %bx /* %es:0000 for destination */ + call read /* Read it in */ + popl %eax /* Restore */ + addl $MAX_READ_SEC, %eax /* Update LBA */ + addw $MAX_READ_PARAS, %bx /* Update dest addr */ + jcxz load_done /* Done? */ + jmp load_loop /* Keep going */ +load_done: + + /* Now we can start the loaded program */ + + movw loader, %cx /* Tell cdboot where it is */ + /* (Older versions of cdbr have */ + /* %cx == 0 from the jcxz load_done) */ + movb drive, %dl /* Get the boot drive number */ + ljmp $BOOTSEG, $0 /* Go run cdboot */ + +/* + * Lookup the file in the path at [SI] from the root directory. + * + * Trashes: All but BX + * Returns: CF = 0 (success), BX = pointer to record + * CF = 1 (not found) + */ +lookup: + movw $VD_ROOTDIR + MEM_VOLDESC, %bx /* Root directory record */ + +lookup_dir: + lodsb /* Get first char of path */ + cmpb $0, %al /* Are we done? */ + je lookup_done /* Yes */ + cmpb $'/', %al /* Skip path separator */ + je lookup_dir + decw %si /* Undo lodsb side effect */ + call find_file /* Lookup first path item */ + jnc lookup_dir /* Try next component */ + ret +lookup_done: + movw $msg_loading, %si /* Success message - say which file */ + call display_string + mov loader, %si + call display_string + mov $crlf, %si + call display_string + clc /* Clear carry */ + ret + +/* + * Lookup file at [SI] in directory whose record is at [BX]. + * + * Trashes: All but returns + * Returns: CF = 0 (success), BX = pointer to record, SI = next path item + * CF = 1 (not found), SI = preserved + */ +find_file: + mov DIR_EXTENT(%bx), %eax /* Load extent */ + xor %edx, %edx + mov DIR_EA_LEN(%bx), %dl + add %edx, %eax /* Skip extended attributes */ + mov %eax, rec_lba /* Save LBA */ + mov DIR_SIZE(%bx), %eax /* Save size */ + mov %eax, rec_size + xor %cl, %cl /* Zero length */ + push %si /* Save */ +ff_namelen: + inc %cl /* Update length */ + lodsb /* Read char */ + cmp $0, %al /* Nul? */ + je ff_namedone /* Yes */ + cmp $'/', %al /* Path separator? */ + jnz ff_namelen /* No, keep going */ +ff_namedone: + dec %cl /* Adjust length and save */ + mov %cl, name_len + pop %si /* Restore */ +ff_load: + mov rec_lba, %eax /* Load LBA */ + mov $MEM_DIR, %ebx /* Address buffer */ + mov $1, %dh /* One sector */ + call read /* Read directory block */ + incl rec_lba /* Update LBA to next block */ +ff_scan: + mov %ebx, %edx /* Check for EOF */ + sub $MEM_DIR, %edx + cmp %edx, rec_size + ja ff_scan_1 + stc /* EOF reached */ + ret +ff_scan_1: + cmpb $0, DIR_LEN(%bx) /* Last record in block? */ + je ff_nextblock + push %si /* Save */ + movzbw DIR_NAMELEN(%bx), %si /* Find end of string */ +ff_checkver: + cmpb $'0', DIR_NAME-1(%bx,%si) /* Less than '0'? */ + jb ff_checkver_1 + cmpb $'9', DIR_NAME-1(%bx,%si) /* Greater than '9'? */ + ja ff_checkver_1 + dec %si /* Next char */ + jnz ff_checkver + jmp ff_checklen /* All numbers in name, so */ + /* no version */ +ff_checkver_1: + movzbw DIR_NAMELEN(%bx), %cx + cmp %cx, %si /* Did we find any digits? */ + je ff_checkdot /* No */ + cmpb $';', DIR_NAME-1(%bx,%si) /* Check for semicolon */ + jne ff_checkver_2 + dec %si /* Skip semicolon */ + mov %si, %cx + mov %cl, DIR_NAMELEN(%bx) /* Adjust length */ + jmp ff_checkdot +ff_checkver_2: + mov %cx, %si /* Restore %si to end of string */ +ff_checkdot: + cmpb $'.', DIR_NAME-1(%bx,%si) /* Trailing dot? */ + jne ff_checklen /* No */ + decb DIR_NAMELEN(%bx) /* Adjust length */ +ff_checklen: + pop %si /* Restore */ + movzbw name_len, %cx /* Load length of name */ + cmp %cl, DIR_NAMELEN(%bx) /* Does length match? */ + je ff_checkname /* Yes, check name */ +ff_nextrec: + add DIR_LEN(%bx), %bl /* Next record */ + adc $0, %bh + jmp ff_scan +ff_nextblock: + subl $SECTOR_SIZE, rec_size /* Adjust size */ + jnc ff_load /* If subtract ok, keep going */ + ret /* End of file, so not found */ +ff_checkname: + lea DIR_NAME(%bx), %di /* Address name in record */ + push %si /* Save */ + repe cmpsb /* Compare name */ + jcxz ff_match /* We have a winner! */ + pop %si /* Restore */ + jmp ff_nextrec /* Keep looking */ +ff_match: + add $2, %sp /* Discard saved %si */ + clc /* Clear carry */ + ret + +/* + * Load DH sectors starting at LBA %eax into address %es:%bx. + * + * Preserves %bx, %cx, %dx, %si, %es + * Trashes %eax + */ +read: + pushw %si /* Save */ + pushw %cx /* Save since some BIOSs trash */ + movl %eax, edd_lba /* LBA to read from */ + movw %es, %ax /* Get the segment */ + movw %ax, edd_addr + 2 /* and store */ + movw %bx, edd_addr /* Store offset too */ +read_retry: + call twiddle /* Entertain the user */ + pushw %dx /* Save */ + movw $edd_packet, %si /* Address Packet */ + movb %dh, edd_len /* Set length */ + movb drive, %dl /* BIOS Device */ + movb $0x42, %ah /* BIOS: Extended Read */ + int $0x13 /* Call BIOS */ + popw %dx /* Restore */ + jc read_fail /* Worked? */ + popw %cx /* Restore */ + popw %si + ret /* Return */ +read_fail: + cmpb $ERROR_TIMEOUT, %ah /* Timeout? */ + je read_retry /* Yes, Retry */ +read_error: + pushw %ax /* Save error */ + movw $msg_badread, %si /* "Read error: 0x" */ + call display_string + popw %ax /* Retrieve error code */ + movb %ah, %al /* Into %al */ + call hex_byte /* Display error code */ + jmp stay_stopped /* ... then hang */ + +/* + * Display the ASCIZ error message in %esi then halt + */ +err_stop: + call display_string + +stay_stopped: + sti /* Ensure Ctl-Alt-Del will work */ + hlt /* (don't require power cycle) */ + jmp stay_stopped /* (Just to make sure) */ + +/* + * Output the "twiddle" + */ +twiddle: + push %ax /* Save */ + push %bx /* Save */ + mov twiddle_index, %al /* Load index */ + mov twiddle_chars, %bx /* Address table */ + inc %al /* Next */ + and $3, %al /* char */ + mov %al, twiddle_index /* Save index for next call */ + xlat /* Get char */ + call display_char /* Output it */ + mov $8, %al /* Backspace */ + call display_char /* Output it */ + pop %bx /* Restore */ + pop %ax /* Restore */ + ret + +/* + * Display the ASCIZ string pointed to by %si. + * + * Destroys %si, possibly others. + */ +display_string: + pushw %ax + cld +1: + lodsb /* %al = *%si++ */ + testb %al, %al + jz 1f + call display_char + jmp 1b + +/* + * Write out value in %eax in hex + */ +hex_long: + pushl %eax + shrl $16, %eax + call hex_word + popl %eax + /* fall thru */ + +/* + * Write out value in %ax in hex + */ +hex_word: + pushw %ax + mov %ah, %al + call hex_byte + popw %ax + /* fall thru */ +/* + * Write out value in %al in hex + */ +hex_byte: + pushw %ax + shrb $4, %al + call hex_nibble + popw %ax + /* fall thru */ + +/* Write out nibble in %al */ +hex_nibble: + and $0x0F, %al + add $'0', %al + cmpb $'9', %al + jbe display_char + addb $'A'-'9'-1, %al + /* fall thru to display_char */ + +/* + * Display the character in %al + */ +display_char: + pushw %ax + + pushw %bx + movb $0x0e, %ah + movw $1, %bx + int $0x10 + popw %bx +1: popw %ax + ret + +/* + * Data + */ +drive: .byte 0 /* Given to us by the BIOS */ +signon: .asciz "CD-ROM: " +crlf: .asciz "\r\n" +msg_load2big: .asciz "File too big" +msg_badread: .asciz "Read error: 0x" +msg_novd: .asciz "Can't find Primary Volume Descriptor" +msg_loading: .asciz "Loading " + +/* State for searching dir */ +rec_lba: .long 0x0 /* LBA (adjusted for EA) */ +rec_size: .long 0x0 /* File size */ +name_len: .byte 0x0 /* Length of current name */ + +twiddle_index: .byte 0x0 +twiddle_chars: .ascii "|/-\\" + +/* Packet for LBA (CD) read */ +edd_packet: .byte 0x10 /* Length */ + .byte 0 /* Reserved */ +edd_len: .byte 0x0 /* Num to read */ + .byte 0 /* Reserved */ +edd_addr: .word 0x0, 0x0 /* Seg:Off */ +edd_lba: .quad 0x0 /* LBA */ + +/* The data from here must be last in the file, only followed by zero bytes */ + +loader: .word 0 /* The path we end up using */ + +msg_failed: .ascii "Can't find " /* This string runs into... */ + +/* loader_paths is a list of ASCIZ strings followed by a term NUL byte */ +loader_paths: .asciz "/cdboot" + .asciz "/CDBOOT" + .byte 0 + + . = BOOTSECTSIZE + + .end |