summaryrefslogtreecommitdiff
path: root/sys/arch/amd64/stand/cdbr
diff options
context:
space:
mode:
authorTom Cosgrove <tom@cvs.openbsd.org>2004-08-21 18:48:38 +0000
committerTom Cosgrove <tom@cvs.openbsd.org>2004-08-21 18:48:38 +0000
commit1d0cf3361556a13c670d6364b82fd141715302b6 (patch)
tree7eb72d903f168a586384cafef2448ba7362df3e8 /sys/arch/amd64/stand/cdbr
parent1a2f17e99e40cadb8e6d9698c61424d2e7bc72c1 (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/Makefile28
-rw-r--r--sys/arch/amd64/stand/cdbr/cdbr.S526
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