summaryrefslogtreecommitdiff
path: root/sys/arch
diff options
context:
space:
mode:
authorTom Cosgrove <tom@cvs.openbsd.org>2004-01-26 23:21:50 +0000
committerTom Cosgrove <tom@cvs.openbsd.org>2004-01-26 23:21:50 +0000
commit4b4e6dcb3f2a9873179163ebb398ff619f4da907 (patch)
tree3184c8017bd9a4894bf29f6ffd23d0dc6f8e4c8c /sys/arch
parente624bc83143e9c5528e0874763e14d1947cbb62f (diff)
Major overhaul of biosboot and installboot, using EDD (LBA) reads if
the BIOS supports it. File location data now geometry-independent (biosboot groks part of the inode), so installboot loses -h and -s. Many thanks to all those brave enough to try the snapshots. Thanks for the test reports, everyone. ok deraadt@
Diffstat (limited to 'sys/arch')
-rw-r--r--sys/arch/i386/stand/biosboot/biosboot.8254
-rw-r--r--sys/arch/i386/stand/biosboot/biosboot.S892
-rw-r--r--sys/arch/i386/stand/installboot/installboot.890
-rw-r--r--sys/arch/i386/stand/installboot/installboot.c479
4 files changed, 1216 insertions, 499 deletions
diff --git a/sys/arch/i386/stand/biosboot/biosboot.8 b/sys/arch/i386/stand/biosboot/biosboot.8
index 595af1723da..763a43ea70d 100644
--- a/sys/arch/i386/stand/biosboot/biosboot.8
+++ b/sys/arch/i386/stand/biosboot/biosboot.8
@@ -1,5 +1,6 @@
-.\" $OpenBSD: biosboot.8,v 1.17 2003/06/06 21:45:33 jmc Exp $
+.\" $OpenBSD: biosboot.8,v 1.18 2004/01/26 23:21:49 tom Exp $
.\"
+.\" Copyright (c) 2003 Tom Cosgrove <tom.cosgrove@arches-consulting.com>
.\" Copyright (c) 1997 Michael Shalayeff
.\" All rights reserved.
.\"
@@ -24,7 +25,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
-.Dd September 1, 1997
+.Dd December 23, 2003
.Dt BIOSBOOT 8 i386
.Os
.Sh NAME
@@ -33,71 +34,199 @@
i386-specific first-stage system bootstrap
.Sh DESCRIPTION
This small program (roughly 512 bytes of code) is responsible for
-loading the second stage
+loading the second-stage
.Xr boot 8
-program, which in turn will load the kernel.
-It takes no input or options directly.
+program (typically /boot), which in turn will load the kernel.
.Pp
-In order for
.Nm
-to load the second-stage bootstrap, it has a table inside it which
-describes
-.Xr boot 8 's
-location.
-.Nm
-must be installed by, and have its block table filled in by, the
+must be installed by
+.Xr installboot 8 .
+As part of the installation,
.Xr installboot 8
-program.
-As this data is BIOS dependent, you must re-run
+patches
+.Nm
+with information about the location of
+.Xr boot 8
+on disk.
+Specifically, it writes the filesystem block number of
+.Xr boot 8 's
+inode,
+the offset within this block of the inode,
+and various filesystem parameters (taken from the superblock)
+required to convert filesystem blocks to disk sectors.
+.Pp
+You must re-run
.Xr installboot 8
-each time you reinstall
+whenever
.Xr boot 8
-or move your disk or software between machines and/or controllers.
+is changed, as its inode may change.
+While it should not be necessary,
+it may also be advisable to re-run
+.Xr installboot 8
+if you move your disk between machines and/or controllers.
.Pp
When
.Nm
-receives control from either the BIOS or the MBR it will print the message:
+receives control from either the BIOS or the
+master boot record (MBR) it will print the message:
+.Pp
+.Dl Loading
.Pp
-.Dl loading /boot
+followed by a dot for every filesystem block it attempts to load.
+If /boot is loaded successfully,
+.Nm
+will put the cursor on the next line just before
+transferring control to the newly-loaded program.
.Pp
-followed by a dot for every successfully loaded block,
-and then put the cursor on the next line right before switching into
-protected mode and transferring control to the just loaded /boot program.
+If possible,
+.Nm
+will read disk sectors using calls detailed in the Phoenix
+Enhanced Disk Drive Specification (EDD, sometimes known as LBA, reads).
+It will fall back to CHS reads only if EDD calls are not available.
+However, to allow users to boot on hardware that claims LBA capability,
+but which requires CHS reads in order to boot,
+the user may hold down either Shift key during boot.
+If
+.Nm
+detects this, it will force itself to use CHS calls, ignoring
+any LBA capability.
+This will of course prevent booting if /boot lies above the 8 GB
+CHS limit.
+There is an exported symbol
+.Dq force_chs
+of type u_int8_t
+which may be set to 1 to force CHS reads always.
+(However, no tool is currently provided to set this flag.)
.Sh DIAGNOSTICS
-Diagnostics consist of two error messages:
-.Bl -tag -width read_error_
-.It Er Read error
-Some kind of error returned by the BIOS read call.
-This might be any media error, including bad sectors (common on floppy disks),
-and invalid sectors (can occur with messed up geometry translations).
-.It Er Bad magic
-The just loaded /boot image contains a bad magic in its header.
-This might indicate some kind of hardware problem, the
+.Nm
+prints a
+.Sq !\&
+before the
+.Dq Loading
+message if it is being forced to use CHS rather than LBA reads
+(by the user holding down either Shift key during boot,
+or having set the
+.Dq force_chs
+flag in the boot sector).
+.Pp
+.Nm
+prints a
+.Sq ;\&
+after the
+.Dq Loading
+message if it is going to use CHS reads for any reason.
+For example, when booting from floppy or CD-ROM.
+.Pp
+.Nm
+may fail with any of the following error messages:
+.Bl -tag -width ERR_X__
+.It Er ERR I
+Too many indirect blocks.
+.Nm
+is capable of reading the direct blocks in
+.Xr boot 8 's
+inode (the location of which is patched into
+.Nm
+by
+.Xr installboot 8 )
+and the first indirect block,
+but it is not capable of reading further indirect blocks.
+This error indicates that further such indirect blocks were found.
+The system will not be able to boot.
+.Pp
+This is unlikely to ever happen in practice, as
+.Xr boot 8
+has to be quite large for this to be an issue.
+The smallest possible filesystem block size is 512 bytes
+(one sector per filesystem block).
+On such a system, there are 140 filesystem blocks that
+.Nm
+can read, so
+.Xr boot 8
+can be up to 70 KB.
+.Pp
+However, even on floppy disks the filesystem block size is 1024 bytes.
+This allows
+.Xr boot 8
+to occupy up to 268 disk blocks,
+i.e. to be 268 KB.
+On hard disks (default filesystem block size 16 KB)
+4,108 disk blocks are available, to allow
+.Xr boot 8
+to be over 64 MB in size!
+(Only direct blocks are required for
+.Xr boot 8 s
+of up to 192 KB.)
+.It Er ERR M
+Bad magic.
+The ELF
+.Dq magic number
+\e7fELF in
+.Xr boot 8 's
+header was not found.
+This indicates that the first block of
+.Xr boot 8
+was not read correctly.
+This could be due to disk corruption,
+failing to run
+.Xr installboot 8 ,
+giving an invalid
+.Xr boot 8
+program as the
.Ar boot
-argument to the
-.Xr installboot 8
-command was not a valid /boot program, or /boot has been moved or
-changed.
-.El
+argument to
+.Xr installboot 8 ,
+or
+incorrect geometry translation.
+.It Er ERR R
+Read error.
+The BIOS returned an error indication when
+.Nm
+attempted to read a disk sector.
+This might be any media error, including bad sectors (common on floppy disks),
+and invalid sectors (can occur with bad geometry translations).
.Pp
-Other common reasons for these errors to appear is that a wrong BIOS geometry
-has been used in
-.Xr installboot 8
-for the device you are booting from.
-.Sh NOTES
-The practice of making
-.Ox
-use the whole disk (ie: having
+If this error occurs during an LBA boot (no
+.Sq ;\&
+after
+.Dq Loading ) ,
+then a CHS boot may succeed.
+To do this, you should reboot, then hold down either Shift key
+before
.Nm
-as the MBR) has been deprecated, and will not work on certain BIOS versions.
-There is a lot of strange behaviour with different BIOSes; one well
-known lobotomy case is that the BIOS does not pass the right boot drive
-number to the
+starts.
+You should see a
+.Sq !\&
+before
+.Dq Loading
+as confirmation that your
+override was accepted.
+.It Er ERR X
+Can't boot.
+Issued when trying to read sectors in CHS mode,
+but the BIOS call
+.Em get\ drive\ parameters
+failed or gave a value of 0 for the number of sectors per track.
+In either case, it is not possible for
.Nm
-program.
-This is one of the main reasons that having
+to calculate the (cylinder, head, sector) values required to
+read any sectors.
+.Sh NOTES
+Using
.Nm
-as the MBR has been deprecated.
+as the MBR,
+as has been done in the past,
+is not recommended, and is not supported.
+Instead, create a single
+.Xr fdisk 8
+partition that spans the entire disk.
+.Pp
+Despite the support for
+.Xr boot 8
+over the 8 GB boundary,
+good
+.Xr disklabel 8
+partitioning practices should still be followed.
.Sh FILES
.Bl -tag -width /usr/mdec/biosbootxx -compact
.It Pa /usr/mdec/mbr
@@ -107,24 +236,35 @@ primary bootstrap
.It Pa /boot
secondary bootstrap
.It Pa /bsd
-system code
+.Ox
+kernel
.El
.Sh SEE ALSO
.Xr boot 8 ,
.Xr boot_i386 8 ,
+.Xr disklabel 8 ,
.Xr fdisk 8 ,
.Xr installboot 8
.Sh HISTORY
-This program was written by Michael Shalayeff for
+.Nm
+was originally written by Michael Shalayeff for
.Ox 2.1 .
-However it's based on bootstrap code from older versions of this
+However it was based on bootstrap code from older versions of this
operating system, other operating systems, other programs, and
other people's work.
+.Pp
+It was significantly revised in December 2003 by Tom Cosgrove,
+in order to support LBA disk access (via the Phoenix Enhanced Disk
+Drive Specification API).
+At that time the internal table of disk blocks was removed, and
+.Nm
+modified to read filesystem block numbers from the inode.
.Sh BUGS
-It should do a checksum over the loaded /boot image, and check that against
-a value that
-.Xr installboot 8
-has precomputed.
+.Nm
+should perform and verify a checksum across the entire loaded
+.Xr boot 8
+image,
+rather than just checking the magic number in the first block.
.Pp
There is no BIOS error number reported nor is the location of the error
reported.
diff --git a/sys/arch/i386/stand/biosboot/biosboot.S b/sys/arch/i386/stand/biosboot/biosboot.S
index 51f3c0ca4a2..5d877056d24 100644
--- a/sys/arch/i386/stand/biosboot/biosboot.S
+++ b/sys/arch/i386/stand/biosboot/biosboot.S
@@ -1,7 +1,8 @@
-/* $OpenBSD: biosboot.S,v 1.35 2003/06/27 18:27:53 weingart Exp $ */
+/* $OpenBSD: biosboot.S,v 1.36 2004/01/26 23:21:49 tom Exp $ */
/*
* Copyright (c) 2003 Tobias Weingartner
+ * Copyright (c) 2003 Tom Cosgrove <tom.cosgrove@arches-consulting.com>
* Copyright (c) 1997 Michael Shalayeff, Tobias Weingartner
* All rights reserved.
*
@@ -32,83 +33,198 @@
#include <machine/asm.h>
#include <assym.h>
-#define BLKCNT 63
+/* Error indicators */
+#define PBR_READ_ERROR 'R'
+#define PBR_CANT_BOOT 'X'
+#define PBR_BAD_MAGIC 'M'
+#define PBR_TOO_MANY_INDIRECTS 'I'
+#define CHAR_BLOCK_READ '.'
+#define CHAR_CHS_READ ';'
+
+/*
+ * Memory layout:
+ *
+ * 0x00000 -> 0x079FF our stack (to 30k5)
+ * 0x07A00 -> 0x07BFF typical MBR loc (at 30k5)
+ * 0x07C00 -> 0x07DFF our code (at 31k)
+ * 0x07E00 -> ... /boot inode block (at 31k5)
+ * 0x07E00 -> ... (indirect block if nec)
+ * 0x40000 -> ... /boot (at 256k)
+ *
+ * The BIOS loads the MBR at physical address 0x07C00. It then relocates
+ * itself to (typically) 0x07A00.
+ *
+ * The MBR then loads us at physical address 0x07C00.
+ *
+ * We use a long jmp to normalise our address to seg:offset 07C0:0000.
+ * (In real mode on x86, segment registers contain a base address in
+ * paragraphs (16 bytes). 0000:00010 is the same as 0001:0000.)
+ *
+ * We set the stack to start at 0000:79FC (grows down on i386)
+ *
+ * We then read the inode for /boot into memory just above us at
+ * 07E0:0000, and run through the direct block table (and the first
+ * indirect block table, if necessary).
+ *
+ * We load /boot at seg:offset 4000:0000.
+ *
+ * Previous versions limited the size of /boot to 64k (loaded in a single
+ * segment). This version does not have this limitation.
+ */
+#define INODESEG 0x07e0 /* where we put /boot's inode's block */
+#define INDIRECTSEG 0x07e0 /* where we put indirect table, if nec */
#define BOOTSEG 0x07c0 /* biosboot loaded here */
-#define BOOTSTACK 0xfffc /* stack starts here */
-#define LFMAGIC 0x464c /* LFMAGIC (only uses two bytes of \7fELF */
+#define BOOTSTACKOFF ((BOOTSEG << 4) - 4) /* stack starts here, grows down */
+#define LFMAGIC 0x464c /* LFMAGIC (last two bytes of \7fELF) */
+#define ELFMAGIC 0x464c457f /* ELFMAGIC ("\7fELF") */
+#define INODEOFF ((INODESEG-BOOTSEG) << 4)
-/* Clobbers %al - maybe more */
-#define putc(c) \
- movb $c, %al; \
- call Lchr;
+/*
+ * The data passed by installboot is:
+ *
+ * inodeblk uint32 the filesystem block that holds /boot's inode
+ * inodedbl uint32 the memory offset to the beginning of the
+ * direct block list (di_db[]). (This is the
+ * offset within the block + $INODEOFF, which is
+ * where we load the block to.)
+ * fs_bsize_p uint16 the filesystem block size _in paragraphs_
+ * (i.e. fs_bsize / 16)
+ * fs_bsize_s uint16 the number of 512-byte sectors in a filesystem
+ * block (i.e. fs_bsize / 512). Directly written
+ * into the LBA command block, at lba_count.
+ * XXX LIMITED TO 127 BY PHOENIX EDD SPEC.
+ * fsbtodb uint8 shift count to convert filesystem blocks to
+ * disk blocks (sectors). Note that this is NOT
+ * log2 fs_bsize, since fragmentation allows
+ * the trailing part of a file to use part of a
+ * filesystem block. In other words, filesystem
+ * block numbers can point into the middle of
+ * filesystem blocks.
+ * p_offset uint32 the starting disk block (sector) of the
+ * filesystem
+ * nblocks uint16 the number of filesystem blocks to read.
+ * While this can be calculated as
+ * howmany(di_size, fs_bsize) it takes us too
+ * many code bytes to do it.
+ *
+ * All of these are patched directly into the code where they are used
+ * (once only, each), to save space.
+ *
+ * One more symbol is exported, in anticipation of a "-c" flag in
+ * installboot to force CHS reads:
+ *
+ * force_chs uint8 set to the value 1 to force biosboot to use CHS
+ * reads (this will of course cause the boot sequence
+ * to fail if /boot is above 8 GB).
+ */
-/* Clobbers %esi - maybe more */
-#define puts(s) \
- mov $s, %si; \
- call Lmessage
+ .globl inodeblk, inodedbl, fs_bsize_p, fsbtodb, p_offset, nblocks
+ .globl fs_bsize_s, force_chs
+ .type inodeblk, @function
+ .type inodedbl, @function
+ .type fs_bsize_p, @function
+ .type fs_bsize_s, @function
+ .type fsbtodb, @function
+ .type p_offset, @function
+ .type nblocks, @function
+ .type force_chs, @function
+
+
+/* Clobbers %ax, maybe more */
+#define putc(c) movb $c, %al; call Lchr
+
+/* Clobbers %ax, %si, maybe more */
+#define puts(s) movw $s, %si; call Lmessage
.text
.code16
.globl _start
_start:
- jmp 1f
+ jmp begin
nop
+ /*
+ * BIOS Parameter Block. Read by many disk utilities.
+ *
+ * We would have liked biosboot to go from the superblock to
+ * the root directory to the inode for /boot, thence to read
+ * its blocks into memory.
+ *
+ * As code and data space is quite tight in the 512-byte
+ * partition boot sector, we instead get installboot to pass
+ * us some pre-processed fields.
+ *
+ * We would have liked to put these in the BIOS parameter block,
+ * as that seems to be the right place to put them (it's really
+ * the equivalent of the superblock for FAT filesystems), but
+ * caution prevents us.
+ *
+ * For now, these fields are either directly in the code (when they
+ * are used once only) or at the end of this sector.
+ */
+
. = _start + 3
+
.asciz "OpenBSD"
+
/* BPB */
. = _start + 0x0b
-bpb: .word DEV_BSIZE /* sector size */
- .byte 1 /* sectors/cluster */
- .word 0 /* reserved sectors */
- .byte 0 /* # of FAT */
- .word 0 /* root entries */
- .word 0 /* small sectors */
- .byte 0xf8 /* media type (hd) */
- .word 0 /* sectors/fat */
- .word 0 /* sectors per track */
- .word 0 /* # of heads */
+bpb: .word DEV_BSIZE /* sector size */
+ .byte 2 /* sectors/cluster */
+ .word 0 /* reserved sectors */
+ .byte 0 /* # of FAT */
+ .word 0 /* root entries */
+ .word 0 /* small sectors */
+ .byte 0xf8 /* media type (hd) */
+ .word 0 /* sectors/fat */
+ .word 0 /* sectors per track */
+ .word 0 /* # of heads */
/* EBPB */
. = _start + 0x1c
-ebpb: .long 16 /* hidden sectors */
- .long 0 /* large sectors */
- .word 0 /* physical disk */
- .byte 0x29 /* signature, needed by NT */
- .space 4, 0 /* volume serial number */
- .asciz "UNIX LABEL"
+ebpb: .long 16 /* hidden sectors */
+ .long 0 /* large sectors */
+ .word 0 /* physical disk */
+ .byte 0x29 /* signature, needed by NT */
+ .space 4, 0 /* volume serial number */
+ .ascii "UNIX LABEL"
.asciz "UFS 4.4"
/* boot code */
. = _start + 0x3e
-1:
+begin:
/* Fix up %cs just in case */
- ljmp $BOOTSEG, $1f
+ ljmp $BOOTSEG, $main
-load_msg:
- .asciz "reading boot"
+ /*
+ * Come here if we have to do a CHS boot, but we get an error from
+ * BIOS get drive parameters, or it returns nsectors == 0 (in which
+ * case we can't do the division we need to convert LBA sector
+ * number to CHS).
+ */
+cant_boot:
+ movb $PBR_CANT_BOOT, %al
+ jmp err_print_crlf
-1:
- /* set up stack (%ss:%sp) */
- cli /* disable interrupts w/o stack */
- xor %ax, %ax
- mov %ax, %ss
- mov $BOOTSTACK, %sp
- sti /* we have stack, do ints */
+main:
+ /* Set up stack */
+ xorw %ax, %ax
+ movw %ax, %ss
+ movw $BOOTSTACKOFF, %sp
/* Set up needed data segment reg */
- mov $BOOTSEG, %ax
- mov %ax, %ds
+ pushw %cs
+ popw %ds /* Now %cs == %ds, != %ss (%ss == 0) */
#ifdef SERIAL
- # Initialize the serial port to 9600 baud, 8N1.
+ /* Initialize the serial port to 9600 baud, 8N1 */
push %dx
- mov $0x00e3, %ax
- mov SERIAL, %dx
+ movw $0x00e3, %ax
+ movw SERIAL, %dx
int $0x14
pop %dx
#endif
@@ -117,65 +233,351 @@ load_msg:
putc('R')
#endif
+ /*
+ * We're going to print our sign-on message.
+ *
+ * We're now LBA-aware, and will use LBA to load /boot if the
+ * BIOS says it's available. However, we have seen machines
+ * where CHS is required even when LBA is available. Therefore
+ * we provide a way to force CHS use:
+ *
+ * If the SHIFT key is held down on entry, force CHS reads.
+ */
+ movw $load_msg+1, %si /* "Loading" */
+ movb %dl, %dh
+
+ /*
+ * BIOS call "INT 0x16 Get Keyboard Shift Flags
+ * Call with %ah = 0x02
+ * Return:
+ * %al = shift flags
+ * %ah - undefined by many BIOSes
+ */
+ movb $0x02, %ah
+ int $0x16
+
+ /*
+ * We provide the ability to force CHS use without having to hold
+ * down the SHIFT key each boot. Just set the byte at force_chs
+ * to 1 (more accurately any value with either of the bottom two
+ * bits set, but the use of 1 is recommended).
+ */
+force_chs = .+1
+ orb $0, %al
+
+ testb $0x3, %al /* Either shift key down? */
+ jz no_force_chs
+
+ decw %si /* "!Loading" indicates forced CHS */
+ xorb %dh, %dh /* Pretend a floppy, so no LBA use */
+
+no_force_chs:
/* Print pretty message */
- puts(load_msg)
+ call Lmessage
- /* set up %es, (where we will load /boot to) */
- mov $(LOADADDR >> 4), %ax
- mov %ax, %es
+ /*
+ * We will use LBA reads if we have LBA support, so find out.
+ */
- xorw %bx, %bx /* put it at %es:0 */
- movb block_count, %cl /* how many to read */
- movzbw %cl, %cx
- movw $block_table, %si
+ /*
+ * But don't even try on floppies, OR if forcing to CHS.
+ *
+ * (We're really testing %dl, but use %dh so we can force the
+ * top bit to zero to force CHS boot.)
+ */
+ testb $0x80, %dh
+ jz no_lba
+
+ /*
+ * BIOS call "INT 0x13 Extensions Installation Check"
+ * Call with %ah = 0x41
+ * %bx = 0x55AA
+ * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc)
+ * Return:
+ * carry set: failure
+ * %ah = error code (0x01, invalid func)
+ * carry clear: success
+ * %bx = 0xAA55 (must verify)
+ * %ah = major version of extensions
+ * %al (internal use)
+ * %cx = capabilities bitmap
+ * 0x0001 - extnd disk access funcs
+ * 0x0002 - rem. drive ctrl funcs
+ * 0x0004 - EDD functions with EBP
+ * %dx (extension version?)
+ */
-1:
- push %cx
- putc('.') /* show progress indicator */
- cld
- lodsw /* word */ /* cylinder/sector */
- mov %ax, %cx
- lodsb /* head */
- movb %al, %dh
- lodsb /* # of sectors to load */
- movb $0x2, %ah
- push %ax
+ pushw %dx /* Save the drive number (%dl) */
+ movw $0x55AA, %bx
+ movb $0x41, %ah
int $0x13
- jnc 3f
+ popw %dx /* Retrieve drive number */
+
+ jc no_lba /* Did the command work? Jump if not */
+ cmpw $0xAA55, %bx /* Check that bl, bh exchanged */
+ jne no_lba /* If not, don't have EDD extensions */
+ testb $0x01, %cl /* And do we have "read" available? */
+ jz no_lba /* Again, use CHS if not */
+
+ /* We have LBA support, so that's the vector to use */
+
+ movw $load_lba, load_fsblock
+ jmp get_going
+
+no_lba:
+ pushw %dx
+
+ /*
+ * BIOS call "INT 0x13 Function 0x08" to get drive parameters
+ * Call with %ah = 0x08
+ * %dl = drive (0x80 for 1st hd, 0x81 for 2nd...)
+ * Return:
+ * carry set: failure
+ * %ah = err code
+ * carry clear: success
+ * %ah = 0x00
+ * %al = 0x00 (some BIOSes)
+ * %ch = 0x00 (some BIOSes)
+ * %ch = max-cylinder & 0xFF
+ * %cl = max sector | rest of max-cyl bits
+ * %dh = max head number
+ * %dl = number of drives
+ * (according to Ralph Brown Int List)
+ */
+ movb $0x08, %ah
+ int $0x13 /* We need to know heads & sectors */
- /* read error */
- puts(2f)
- jmp halt
-2: .asciz "\r\nRead error\r\n"
+ jc cant_boot /* If error, can't boot */
+
+ movb %dh, maxheads /* Remember this */
+
+ andb $0x3F, %cl
+ jz cant_boot
+ movb %cl, nsectors
+
+ putc(CHAR_CHS_READ) /* Indicate (subtly) CHS reads */
+
+ popw %dx /* Retrieve the drive number */
+
+get_going:
+ /*
+ * Older versions of biosboot used to set up the destination
+ * segment, and increase the target offset every time a number
+ * of blocks was read. That limits /boot to 64k.
+ *
+ * In order to support /boots > 64k, we always read to offset
+ * 0000 in the target segment, and just increase the target segment
+ * each time.
+ */
+
+ /*
+ * We would do movl inodeblk, %eax here, but that instruction
+ * is 4 bytes long; add 4 bytes for data takes 8 bytes. Using
+ * a load immediate takes 6 bytes, and we just get installboot
+ * to patch here, rather than data anywhere else.
+ */
+inodeblk = .+2
+ movl $0x90909090, %eax /* mov $inodeblk, %eax */
-3: /* read next block */
+ movw $INODESEG, %bx /* Where to put /boot's inode */
- pop %ax
- movzbw %al, %ax
- shl $9, %ax /* 512 bytes sectors */
- add %ax, %bx
- pop %cx
- loop 1b
+ /*
+ * %eax - filesystem block to read
+ * %bx - target segment (target offset is 0000)
+ * %dl - BIOS drive number
+ */
+ call *load_fsblock /* This will crash'n'burn on errs */
+
+ /*
+ * We now have /boot's inode in memory.
+ *
+ * /usr/include/ufs/ufs/dinode.h for the details:
+ *
+ * Offset 8 (decimal): 64-bit file size (only use low 32 bits)
+ * Offset 40 (decimal): list of NDADDR (12) direct disk blocks
+ * Offset 88 (decimal): list of NIADDR (3) indirect disk blocks
+ *
+ * NOTE: list of indirect blocks immediately follows list of
+ * direct blocks. We use this fact in the code.
+ *
+ * We only support loading from direct blocks plus the first
+ * indirect block. This is the same as the previous biosboot/
+ * installboot limit. Note that, with default 16,384-bytes
+ * filesystem blocks, the direct block list supports files up
+ * to 192 KB. /boot is currently around 60 KB.
+ *
+ * The on-disk format can't change (filesystems with this format
+ * already exist) so okay to hardcode offsets here.
+ *
+ * The nice thing about doing things with filesystem blocks
+ * rather than sectors is that filesystem blocks numbers have
+ * 32 bits, so fit into a single register (even if "e"d).
+ *
+ * Note that this code does need updating if booting from a new
+ * filesystem is required.
+ */
+#define NDADDR 12
+#define di_db 40 /* Not used; addr put in by instboot */
+#define di_ib 88 /* Not used; run on from direct blks */
+
+ /*
+ * Register usage:
+ *
+ * %eax - block number for load_fsblock
+ * %bx - target segment (target offset is 0000) for load_fsblock
+ * %dl - BIOS drive number for load_fsblock
+ * %esi - points to block table in inode/indirect block
+ * %cx - number of blocks to load within loop (i.e. from current
+ * block list, which is either the direct block list di_db[]
+ * or the indirect block list)
+ * %di - total number of blocks to load
+ */
+
+ /*
+ * We would do movl inodedbl, %esi here, but that instruction
+ * is 4 bytes long; add 4 bytes for data takes 8 bytes. Using
+ * a load immediate takes 6 bytes, and we just get installboot
+ * to patch here, rather than in data anywhere else.
+ */
+inodedbl = .+2
+ movl $0x90909090, %esi /* mov $inodedbl, %esi */
+ /* Now esi -> di_db[] */
- puts(2f)
+nblocks = .+1
+ movw $0x9090, %di /* mov nblocks, %di */
+ movw %di, %cx
+ cmpw $NDADDR, %cx
+ jc 1f
+ movw $NDADDR, %cx
+1: /* %cx = min(nblocks, $NADDR) */
+
+ movw $(LOADADDR >> 4), %bx /* Target segment for /boot */
+
+load_blocks:
+ putc(CHAR_BLOCK_READ) /* Show progress indicator */
- xor %si, %si
cld
- /* check /boot magic */
- es;lodsw;es;lodsw /* no need for high word */
- cmp $LFMAGIC, %ax
- je 3f
- puts(1f)
-halt:
+ /* Get the next filesystem block number into %eax */
+ lodsl /* %eax = *(%si++), make sure 0x66 0xad */
+
+ pushal /* Save all 32-bit registers */
+
+ /*
+ * Read a single filesystem block (will almost certainly be multiple
+ * disk sectors)
+ *
+ * %eax - filesystem block to read
+ * %bx - target segment (target offset is 0000)
+ * %dl - BIOS drive number
+ */
+ call *load_fsblock /* This will crash'n'burn on errs */
+
+ popal /* Restore 32-bit registers */
+
+ /*
+ * We want to put addw fs_bsize_p, %bx, which takes 4 bytes
+ * of code and two bytes of data.
+ *
+ * Instead, use an immediate load, and have installboot patch
+ * here directly.
+ */
+ /* Move on one filesystem block */
+fs_bsize_p = .+2
+ addw $0x9090, %bx /* addw $fs_bsize_p, %bx */
+
+ decw %di
+ loop load_blocks
+
+ /* %cx == 0 ... important it stays this way (used later) */
+
+ /*
+ * Finished reading a set of blocks.
+ *
+ * This was either the direct blocks, and there may or may not
+ * be indirect blocks to read, or it was the indirect blocks,
+ * and we may or may not have read in all of /boot. (Ideally
+ * will have read in all of /boot.)
+ */
+ orw %di, %di
+ jz done_load /* No more sectors to read */
+
+ /* We have more blocks to load */
+
+ /* We only support a single indirect block (the same as previous
+ * versions of installboot. This is required for the boot floppies.
+ *
+ * We use a bit of the code to store a flag that indicates
+ * whether we have read the first indirect block or not.
+ *
+ * If we've already read the indirect list, we can't load this /boot.
+ *
+ * indirect uint8 0 => running through load_blocks loop reading
+ * direct blocks. If != 0, we're reading the
+ * indirect blocks. Must use a field that is
+ * initialised to 0.
+ */
+indirect = .+2
+ movw $PBR_TOO_MANY_INDIRECTS, %ax /* movb $PRB_TOO..., %al */
+ /* movb indirect, %ah */
+ orb %ah, %ah
+ jnz err_print_crlf
+
+ incb indirect /* No need to worry about wrap */
+ /* around, as this will only be done */
+ /* once before we fail */
+
+ /* Okay, let's read in the indirect block */
+
+ lodsl /* Get blk num of 1st indirect blk */
+
+ pushw %bx /* Remember where we got to */
+ movw $INODESEG, %bx
+ call *load_fsblock /* This will crash'n'burn on errs */
+ popw %bx /* Indirect blocks get added on to */
+ /* just after where we got to */
+ movl $INODEOFF, %esi
+ movw %di, %cx /* How many blocks left to read */
+
+ jmp load_blocks
+
+done_load:
+ puts(crlf)
+
+ /* %cx == 0 from loop above... keep it that way */
+
+ /*
+ * Check the magic signature at the beginning of /boot.
+ * Since /boot is now ELF, this should be 0xFF E L F.
+ */
+ movw $(LOADADDR >> 4), %ax /* Target segment */
+ movw %ax, %es
+
+ /*
+ * We cheat a little here, and only check the L and F.
+ *
+ * (Saves 3 bytes of code... the two signature bytes we
+ * don't check, and the operand size prefix that's not
+ * needed.)
+ */
+ cmpw $LFMAGIC, %es:2(,1)
+ je exec_boot
+
+ movb $PBR_BAD_MAGIC, %al
+
+err_print:
+ movw $err_txt, %si
+err_print2:
+ movb %al, err_id
+err_stop:
+ call Lmessage
+stay_stopped:
cli
-99:
hlt
- jmp 99b
-1: .ascii "Bad magic"
-2: .asciz "\r\n"
+ jmp stay_stopped /* Just to make sure :-) */
-3: /* At this point we could try to use the entry point in
+exec_boot:
+ /* At this point we could try to use the entry point in
* the image we just loaded. But if we do that, we also
* have to potentially support loading that image where it
* is supposed to go. Screw it, just assume that the image
@@ -185,63 +587,309 @@ halt:
putc('P')
#endif
- movzbl %dl, %eax /* drive number is in the lowest byte */
- pushl %eax
+ /* %cx == 0 from loop above... keep it that way */
+
+ /*
+ * We want to do movzbl %dl, %eax ; pushl %eax to zero-extend the
+ * drive number to 32 bits and pass it to /boot. However, this
+ * takes 6 bytes.
+ *
+ * Doing it this way saves 2 bytes.
+ */
+ pushw %cx
+ movb %dl, %cl
+ pushw %cx
+
pushl $BOOTMAGIC /* use some magic */
/* jmp /boot */
ljmp $(LINKADDR >> 4), $0
/* not reached */
+
+/*
+ * Load a single filesystem block into memory using CHS calls.
+ *
+ * Input: %eax - 32-bit filesystem block number
+ * %bx - target segment (target offset is 0000)
+ * %dl - BIOS drive number
+ *
+ * Output: block successfully read in (panics if not)
+ * all general purpose registers may have been trashed
+ */
+load_chs:
+ /*
+ * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into
+ * memory.
+ * Call with %ah = 0x42
+ * %ah = 0x2
+ * %al = number of sectors
+ * %ch = cylinder & 0xFF
+ * %cl = sector (0-63) | rest of cylinder bits
+ * %dh = head
+ * %dl = drive (0x80 for 1st hd, 0x81 for 2nd...)
+ * %es:%bx = segment:offset of buffer
+ * Return:
+ * carry set: failure
+ * %ah = err code
+ * %al = number of sectors transferred
+ * carry clear: success
+ * %al = 0x0 OR number of sectors transferred
+ * (depends on BIOS!)
+ * (according to Ralph Brown Int List)
+ */
+
+ /* Convert the filesystem block into a sector value */
+ call fsbtosector
+ movl lba_sector, %eax /* we can only use 24 bits, really */
+
+ movw fs_bsize_s, %cx /* sectors per filesystem block */
+
+ /*
+ * Some BIOSes require that reads don't cross track boundaries.
+ * Therefore we do all CHS reads single-sector.
+ */
+calc_chs:
+ pushal
+ movw %bx, %es /* Set up target segment */
+
+ pushw %dx /* Save drive number (in %dl) */
+ xorl %edx, %edx
+ movl %edx, %ecx
+
+nsectors = .+1
+ movb $0x90, %cl /* movb $nsectors, %cl */
+ /* Doing it this way saves 4-2 = 2 bytes code */
+ /* bytes (no data, since we would overload) */
+
+ divl %ecx, %eax
+ /* Now have sector number in %dl */
+ pushw %dx /* Remember for later */
+
+ xorl %edx, %edx
+
+maxheads = .+1
+ movb $0x90, %cl /* movb $maxheads, %cl; 0 <= maxheads <= 255 */
+ /* Doing it this way saves 4-2 = 2 code */
+ /* bytes (no data, since we would overload */
+
+ incw %cx /* Number of heads is 1..256, no "/0" worries */
+
+ divl %ecx, %eax
+ /* Have head number in %dl */
+ /* Cylinder number in %ax */
+ movb %al, %ch /* Bottom 8 bits of cyl number */
+ shlb $6, %ah /* Move up top 2 bits of cyl number */
+ movb %ah, %cl /* Top 2 bits of cyl number in here */
+
+ popw %bx /* (pushed %dx, but need %dl for now */
+ incb %bl /* Sector numbers run from 1, not 0 */
+ orb %bl, %cl /* Or the sector number into top bits cyl */
+
+ /* Remember, %dl has head number */
+ popw %ax
+ /* %al has BIOS drive number -> %dl */
+
+ movb %dl, %dh /* Now %dh has head number (from 0) */
+ movb %al, %dl /* Now %dl has BIOS drive number */
+
+ xorw %bx, %bx /* Set up target offset */
+
+ movw $0x0201, %ax /* %al = 1 - read one sector at a time */
+ /* %ah = 2 - int 0x13 function for CHS read */
+
+ call do_int_13 /* saves us 1 byte :-) */
+
+ /* Get the next sector */
+
+ popal
+ incl %eax
+ addw $32, %bx /* Number of segments/paras in a sector */
+ loop calc_chs
+
+ ret
+
+ /* read error */
+read_error:
+ movb $PBR_READ_ERROR, %al
+err_print_crlf:
+ movw $err_txt_crlf, %si
+ jmp err_print2
+
+
+/*
+ * Load a single filesystem block into memory using LBA calls.
+ *
+ * Input: %eax - 32-bit filesystem block number
+ * %bx - target segment (target offset is 0000)
+ * %dl - BIOS drive number
+ *
+ * Output: block successfully read in (panics if not)
+ * all general purpose registers may have been trashed
+ */
+load_lba:
+ /*
+ * BIOS call "INT 0x13 Extensions Extended Read"
+ * Call with %ah = 0x42
+ * %dl = drive (0x80 for 1st hd, 0x81 for 2nd, etc)
+ * %ds:%si = segment:offset of command packet
+ * Return:
+ * carry set: failure
+ * %ah = error code (0x01, invalid func)
+ * command packet's sector count field set
+ * to the number of sectors successfully
+ * transferred
+ * carry clear: success
+ * %ah = 0 (success)
+ * Command Packet:
+ * 0x0000 BYTE packet size (0x10 or 0x18)
+ * 0x0001 BYTE reserved (should be 0)
+ * 0x0002 WORD sectors to transfer (max 127)
+ * 0x0004 DWORD seg:offset of transfer buffer
+ * 0x0008 QWORD starting sector number
+ */
+ call fsbtosector /* Set up lba_sector & lba_sector+4 */
+
+ /* movb %dh, lba_count <- XXX done by installboot */
+ movw %bx, lba_seg
+ movw $lba_command, %si
+ movb $0x42, %ah
+do_int_13:
+ int $0x13
+ jc read_error
+
+ ret
+
+
+/*
+ * Converts a given filesystem block number into a disk sector
+ * at lba_sector and lba_sector+4.
+ *
+ * Input: %eax - 32-bit filesystem block number
+ *
+ * Output: lba_sector and lba_sector+4 set up
+ * XXX
+ */
+fsbtosector:
+ /*
+ * We want to do
+ *
+ * movb fsbtodb, %ch /# Shift counts we'll need #/
+ * movb $32, %cl
+ *
+ * which is 6 bytes of code + 1 byte of data.
+ *
+ * We'll actually code it with an immediate 16-bit load into %cx,
+ * which is just 3 bytes of data (saves 4 bytes).
+ */
+fsbtodb = .+2
+ movw $0x9020, %cx /* %ch = fsbtodb, %cl = 0x20 */
+
+ pushl %eax
+ subb %ch, %cl
+ shrl %cl, %eax
+ movl %eax, lba_sector+4
+ popl %eax
+
+ movb %ch, %cl
+ shll %cl, %eax
+
+ /*
+ * And add p_offset, which is the block offset to the start
+ * of the filesystem.
+ *
+ * We would do addl p_offset, %eax, which is 5 bytes of code
+ * and 4 bytes of data, but it's more efficient to have
+ * installboot patch directly in the code (this variable is
+ * only used here) for 6 bytes of code (but no data).
+ */
+p_offset = .+2
+ addl $0x90909090, %eax /* addl $p_offset, %eax */
+
+ movl %eax, lba_sector
+ jnc 1f
+
+ incl lba_sector+4
+1:
+ ret
+
+
/*
* Display string
*/
Lmessage:
- push %ax
cld
1:
- lodsb # load a byte into %al
- testb %al, %al
+ lodsb /* load a byte into %al */
+ orb %al, %al
jz 1f
call Lchr
jmp 1b
-#
-# Lchr: write the character in %al to console
-#
+/*
+ * Lchr: write the character in %al to console
+ */
Lchr:
- push %ax
-
-#ifndef SERIAL
- push %bx
- movb $0x0e, %ah
- xor %bx, %bx
- inc %bx /* movw $0x01, %bx */
- int $0x10
- pop %bx
-#else
- push %dx
+#ifdef SERIAL
+ pushw %dx
movb $0x01, %ah
- xor %dx, %dx
+ xorw %dx, %dx
movb SERIAL, %dl
int $0x14
- pop %dx
+ popw %dx
+#else
+ pushw %bx
+ movb $0x0e, %ah
+ xorw %bx, %bx
+ incw %bx /* movw $0x01, %bx */
+ int $0x10
+ popw %bx
#endif
1:
- pop %ax
ret
- #.data
- .globl block_table, block_count
- .type block_count, @function
- .type block_table, @function
-block_count:
- .byte BLKCNT /* entries in block_table */
-block_table:
- .word 0 /* cylinder/sector */
- .byte 0 /* head */
- .byte 0 /* nsect */
- . = block_table + BLKCNT*4
+ /* .data */
+
+/* vector to the routine to read a particular filesystem block for us */
+load_fsblock:
+ .word load_chs
+
+
+/* This next block is used for the EDD command packet used to read /boot
+ * sectors.
+ *
+ * lba_count is set up for us by installboot. It is the number of sectors
+ * in a filesystem block. (Max value 127.)
+ *
+ * XXX The EDD limit of 127 sectors in one read means that we currently
+ * restrict filesystem blocks to 127 sectors, or < 64 KB. That is
+ * effectively a 32 KB block limit, as filesystem block sizes are
+ * powers of two. The default filesystem block size is 16 KB.
+ *
+ * I say we run with this limitation and see where it bites us...
+ */
+
+lba_command:
+ .byte 0x10 /* size of command packet */
+ .byte 0x00 /* reserved */
+fs_bsize_s:
+lba_count:
+ .word 0 /* sectors to transfer, max 127 */
+ .word 0 /* target buffer, offset */
+lba_seg:
+ .word 0 /* target buffer, segment */
+lba_sector:
+ .long 0, 0 /* sector number */
+
+load_msg:
+ .asciz "!Loading"
+err_txt_crlf:
+ .ascii "\r\n"
+err_txt:
+ .ascii "ERR "
+err_id:
+ .ascii "?"
+crlf: .asciz "\r\n"
. = 0x200 - 2
/* a little signature */
diff --git a/sys/arch/i386/stand/installboot/installboot.8 b/sys/arch/i386/stand/installboot/installboot.8
index f107578d472..4979b80aba1 100644
--- a/sys/arch/i386/stand/installboot/installboot.8
+++ b/sys/arch/i386/stand/installboot/installboot.8
@@ -1,4 +1,4 @@
-.\" $OpenBSD: installboot.8,v 1.26 2003/06/06 21:45:33 jmc Exp $
+.\" $OpenBSD: installboot.8,v 1.27 2004/01/26 23:21:49 tom Exp $
.\"
.\" Copyright (c) 1997 Michael Shalayeff
.\" All rights reserved.
@@ -25,7 +25,7 @@
.\" SUCH DAMAGE.
.\"
.\"
-.Dd September 1, 1997
+.Dd December 23, 2003
.Dt INSTALLBOOT 8 i386
.Os
.Sh NAME
@@ -33,49 +33,31 @@
.Nd installs a bootstrap on an FFS disk or partition
.Sh SYNOPSIS
.Nm installboot
-.Op Fl n
-.Op Fl v
-.Oo Fl s Ar sectors-per-track Oc
-.Oo Fl h Ar tracks-per-cyl Oc
+.Op Fl nv
.Ar boot
.Ar biosboot
.Ar disk
.Sh DESCRIPTION
-.Nm installboot
+.Nm
is used to install a
.Dq first-stage
-boot program into the boot area
-of an FFS disk partition, and initialize the table of block numbers the
-.Ar biosboot
-program uses to load the second-stage boot program.
+boot program into the boot area of an FFS disk partition.
+It inserts the block number and offset of the inode of
+the second-stage boot program
+.Xr boot 8
+so that the
+.Xr biosboot 8
+program can load it.
+Various filesystem parameters are also patched into the boot block.
.Pp
The options are as follows:
.Bl -tag -width flag_opt
.It Fl n
Do not actually write anything on the disk.
.It Fl v
-Be verbose, printing out the CHS (Cylinder, Head, Sector)
-triples that are stored in
+Be verbose, printing out the data that are stored in
.Ar biosboot
along with lots of other information.
-.It Fl s Ar sectors-per-track
-Used to specify the sectors-per-track value if the drive has
-sector translation activated, and
-.Nm
-is unable to determine the translated geometry.
-.\" If not specified, this defaults to d_nsectors from the disklabel.
-If not specified, this defaults to the value retrieved from the BIOS
-at boot time, available via
-.Xr sysctl 8 .
-.It Fl h Ar tracks-per-cyl
-Used to specify the tracks-per-cylinder value if the drive has
-sector translation activated, and
-.Nm
-is unable to determine the translated geometry.
-.\" If not specified, this defaults to d_ntracks from the disklabel.
-If not specified, this defaults to the value retrieved from the BIOS
-at boot time, available via
-.Xr sysctl 8 .
.El
.Pp
The arguments are:
@@ -91,7 +73,7 @@ usually
.It Ar disk
The name of the disk containing the partition in which the second-stage
boot program resides and the first-stage boot program is to be installed.
-This can either be specified in short form (i.e.,
+This can either be specified in short form (e.g.,
.Sq sd0
or
.Sq wd0 ) ,
@@ -116,42 +98,38 @@ disk on i386.
.El
.Sh EXAMPLES
The typical use is
-.Pp
-.Dl # cp /usr/mdec/boot /boot
-.Dl # /usr/mdec/installboot -v -n /boot /usr/mdec/biosboot sd0
+.Bd -literal -offset indent
+# cp /usr/mdec/boot /boot
+# /usr/mdec/installboot -n -v /boot /usr/mdec/biosboot sd0
+.Ed
.Pp
And if the information supplied looks right, run the above without the
.Fl n
flag.
-If you are upgrading an old system, you may need to preface
-the above steps with some more steps... and do the following:
-.Pp
-.Dl boot the floppy.fs filesystem floppy
-.Dl # fsck /dev/rsd0a
-.Dl # mount /dev/sd0a /mnt
-.Dl # cp /usr/mdec/boot /mnt/boot
-.Dl # /usr/mdec/installboot -v /mnt/boot /usr/mdec/biosboot sd0
-.Pp
-If you need to find the BIOS geometry of the disk for
-.Fl s
-and
-.Fl h
-you can use the boot block command:
-.Pp
-.Dl boot> machine diskinfo
+If you are upgrading an old system, you may need to perform
+some additional steps first.
+For example:
+.Bd -literal -offset indent
+boot the floppy.fs filesystem floppy
+# fsck /dev/rsd0a
+# mount /dev/sd0a /mnt
+# cp /usr/mdec/boot /mnt/boot
+# /usr/mdec/installboot -v /mnt/boot /usr/mdec/biosboot sd0
+.Ed
.Sh SEE ALSO
.Xr biosboot 8 ,
+.Xr boot 8 ,
.Xr disklabel 8 ,
.Xr fdisk 8 ,
.Xr init 8
-.Sh BUGS
+.Sh CAVEATS
The disklabel
.Va d_type
field must be set to a value other than
.Dq unknown .
.Pp
-You cannot run
-.Nm installboot
-for a drive/partition other than the one you want the
.Pa /boot
-to be loaded from.
+must be on the drive/partition specified by
+.Pa disk ;
+you cannot perform cross-device
+.Nm Ns s .
diff --git a/sys/arch/i386/stand/installboot/installboot.c b/sys/arch/i386/stand/installboot/installboot.c
index bcb58623909..5585eeb1bbb 100644
--- a/sys/arch/i386/stand/installboot/installboot.c
+++ b/sys/arch/i386/stand/installboot/installboot.c
@@ -1,7 +1,8 @@
-/* $OpenBSD: installboot.c,v 1.42 2004/01/20 23:02:26 tom Exp $ */
+/* $OpenBSD: installboot.c,v 1.43 2004/01/26 23:21:49 tom Exp $ */
/* $NetBSD: installboot.c,v 1.5 1995/11/17 23:23:50 gwr Exp $ */
/*
+ * Copyright (c) 2003 Tom Cosgrove <tom.cosgrove@arches-consulting.com>
* Copyright (c) 1997 Michael Shalayeff
* Copyright (c) 1994 Paul Kranenburg
* All rights reserved.
@@ -61,37 +62,51 @@
#include <unistd.h>
#include <util.h>
+struct sym_data {
+ char *sym_name; /* Must be initialised */
+ int sym_size; /* And this one */
+ int sym_set; /* Rest set at runtime */
+ u_int32_t sym_value;
+};
+
extern char *__progname;
-int verbose, nowrite, nheads, nsectors, userspec = 0;
+int verbose, nowrite = 0;
char *boot, *proto, *dev, *realdev;
-struct nlist nl[] = {
-#define X_BLOCK_COUNT 0
- {{"_block_count"}},
-#define X_BLOCK_TABLE 1
- {{"_block_table"}},
- {{NULL}}
+struct sym_data pbr_symbols[] = {
+ {"_fs_bsize_p", 2},
+ {"_fs_bsize_s", 2},
+ {"_fsbtodb", 1},
+ {"_p_offset", 4},
+ {"_inodeblk", 4},
+ {"_inodedbl", 4},
+ {"_nblocks", 2},
+ {NULL}
};
-u_int8_t *block_count_p; /* block count var. in prototype image */
-u_int8_t *block_table_p; /* block number array in prototype image */
-int maxblocknum; /* size of this array */
+#define INODESEG 0x07e0 /* where we will put /boot's inode's block */
+#define BOOTSEG 0x07c0 /* biosboot loaded here */
-int biosdev;
+#define INODEOFF ((INODESEG-BOOTSEG) << 4)
-char *loadprotoblocks(char *, long *);
-int loadblocknums(char *, int, struct disklabel *);
+static char *loadproto(char *, long *);
+static int getbootparams(char *, int, struct disklabel *);
static void devread(int, void *, daddr_t, size_t, char *);
+static void sym_set_value(struct sym_data *, char *, u_int32_t);
+static void pbr_set_symbols(char *, char *, struct sym_data *);
static void usage(void);
-static int record_block(u_int8_t *, daddr_t, u_int, struct disklabel *);
static void
usage(void)
{
- fprintf(stderr, "usage: %s [-n] [-v] [-s sec-per-track] [-h track-per-cyl] "
- "boot biosboot device\n", __progname);
+ fprintf(stderr, "usage: %s [-nv] boot biosboot device\n", __progname);
exit(1);
}
+/*
+ * Read information about /boot's inode and filesystem parameters, then
+ * put biosboot (partition boot record) on the target drive with these
+ * parameters patched in.
+ */
int
main(int argc, char *argv[])
{
@@ -99,37 +114,20 @@ main(int argc, char *argv[])
int devfd;
char *protostore;
long protosize;
- struct stat sb;
- struct disklabel dl;
- struct dos_mbr mbr;
- struct dos_partition *dp;
- off_t startoff = 0;
- int mib[4];
- size_t size;
- dev_t devno;
- bios_diskinfo_t di;
-
- nsectors = nheads = -1;
- while ((c = getopt(argc, argv, "vnh:s:")) != -1) {
+ struct stat sb;
+ struct disklabel dl;
+ struct dos_mbr mbr;
+ struct dos_partition *dp;
+ off_t startoff = 0;
+
+ while ((c = getopt(argc, argv, "vn")) != -1) {
switch (c) {
- case 'h':
- nheads = atoi(optarg);
- if (nheads < 1 || nheads > 256)
- errx(1, "invalid value for -h");
- userspec = 1;
- break;
- case 's':
- nsectors = atoi(optarg);
- if (nsectors < 1 || nsectors > 63)
- errx(1, "invalid value for -s");
- userspec = 1;
- break;
case 'n':
- /* Do not actually write the bootblock to disk */
+ /* Do not actually write the bootblock to disk. */
nowrite = 1;
break;
case 'v':
- /* Chat */
+ /* Give more information. */
verbose = 1;
break;
default:
@@ -137,17 +135,16 @@ main(int argc, char *argv[])
}
}
- if (argc - optind < 3) {
+ if (argc - optind < 3)
usage();
- }
boot = argv[optind];
proto = argv[optind + 1];
realdev = dev = argv[optind + 2];
- /* Open and check raw disk device */
+ /* Open and check raw disk device. */
if ((devfd = opendev(dev, (nowrite? O_RDONLY:O_RDWR),
- OPENDEV_PART, &realdev)) < 0)
+ OPENDEV_PART, &realdev)) < 0)
err(1, "open: %s", realdev);
if (verbose) {
@@ -159,23 +156,23 @@ main(int argc, char *argv[])
if (ioctl(devfd, DIOCGDINFO, &dl) != 0)
err(1, "disklabel: %s", realdev);
- /* check disklabel */
+ /* Check disklabel. */
if (dl.d_magic != DISKMAGIC)
err(1, "bad disklabel magic=%0x8x", dl.d_magic);
- /* warn on unknown disklabel types */
+ /* Warn on unknown disklabel types. */
if (dl.d_type == 0)
warnx("disklabel type unknown");
- /* Load proto blocks into core */
- if ((protostore = loadprotoblocks(proto, &protosize)) == NULL)
+ /* Load proto blocks into core. */
+ if ((protostore = loadproto(proto, &protosize)) == NULL)
exit(1);
/* XXX - Paranoia: Make sure size is aligned! */
if (protosize & (DEV_BSIZE - 1))
err(1, "proto %s bad size=%ld", proto, protosize);
- /* Write patched proto bootblocks into the superblock */
+ /* Write patched proto bootblock(s) into the superblock. */
if (protosize > SBSIZE - DEV_BSIZE)
errx(1, "proto bootblocks too big");
@@ -183,37 +180,19 @@ main(int argc, char *argv[])
err(1, "stat: %s", realdev);
if (!S_ISCHR(sb.st_mode))
- errx(1, "%s: Not a character device", realdev);
-
- if (nheads == -1 || nsectors == -1) {
- mib[0] = CTL_MACHDEP;
- mib[1] = CPU_CHR2BLK;
- mib[2] = sb.st_rdev;
- size = sizeof(devno);
- if(sysctl(mib, 3, &devno, &size, NULL, 0) >= 0) {
- devno = MAKEBOOTDEV(major(devno), 0, 0,
- DISKUNIT(devno), RAW_PART);
- mib[0] = CTL_MACHDEP;
- mib[1] = CPU_BIOS;
- mib[2] = BIOS_DISKINFO;
- mib[3] = devno;
- size = sizeof(di);
- if(sysctl(mib, 4, &di, &size, NULL, 0) >= 0) {
- nheads = di.bios_heads;
- nsectors = di.bios_sectors;
- }
- }
- }
+ errx(1, "%s: not a character device", realdev);
- if (nheads == -1 || nsectors == -1)
- errx(1, "Unable to get BIOS geometry, must specify -h and -s");
-
- /* Extract and load block numbers */
- if (loadblocknums(boot, devfd, &dl) != 0)
+ /* Get bootstrap parameters that are to be patched into proto. */
+ if (getbootparams(boot, devfd, &dl) != 0)
exit(1);
- /* Sync filesystems (to clean in-memory superblock?) */
- sync(); sleep(1);
+ /* Patch the parameters into the proto bootstrap sector. */
+ pbr_set_symbols(proto, protostore, pbr_symbols);
+
+ if (!nowrite) {
+ /* Sync filesystems (to clean in-memory superblock?). */
+ sync(); sleep(1);
+ }
if (dl.d_type != 0 && dl.d_type != DTYPE_FLOPPY &&
dl.d_type != DTYPE_VND) {
@@ -225,18 +204,19 @@ main(int argc, char *argv[])
errx(1, "broken MBR");
/* Find OpenBSD partition. */
- for (dp = mbr.dmbr_parts; dp < &mbr.dmbr_parts[NDOSPART]; dp++) {
+ for (dp = mbr.dmbr_parts; dp < &mbr.dmbr_parts[NDOSPART];
+ dp++) {
if (dp->dp_size && dp->dp_typ == DOSPTYP_OPENBSD) {
startoff = (off_t)dp->dp_start * dl.d_secsize;
fprintf(stderr, "using MBR partition %ld: "
- "type %d (0x%02x) offset %d (0x%x)\n",
- (long)(dp - mbr.dmbr_parts),
- dp->dp_typ, dp->dp_typ,
- dp->dp_start, dp->dp_start);
+ "type %d (0x%02x) offset %d (0x%x)\n",
+ (long)(dp - mbr.dmbr_parts),
+ dp->dp_typ, dp->dp_typ,
+ dp->dp_start, dp->dp_start);
break;
}
}
- /* don't check for old part number, that is ;-p */
+ /* Don't check for old part number, that is ;-p */
if (dp >= &mbr.dmbr_parts[NDOSPART])
errx(1, "no OpenBSD partition");
}
@@ -252,48 +232,30 @@ main(int argc, char *argv[])
return 0;
}
-char *
-loadprotoblocks(char *fname, long *size)
+/*
+ * Load the prototype boot sector (biosboot) into memory.
+ */
+static char *
+loadproto(char *fname, long *size)
{
int fd;
size_t tdsize; /* text+data size */
char *bp;
- struct nlist *nlp;
Elf_Ehdr eh;
Elf_Word phsize;
Elf_Phdr *ph;
- fd = -1;
- bp = NULL;
+ if ((fd = open(fname, O_RDONLY)) < 0)
+ err(1, "%s", fname);
- /* Locate block number array in proto file */
- if (nlist(fname, nl) != 0) {
- warnx("nlist: %s: symbols not found", fname);
- return NULL;
- }
- /* Validate symbol types (global data). */
- for (nlp = nl; nlp->n_un.n_name; nlp++) {
- if (nlp->n_type != (N_TEXT)) {
- warnx("nlist: %s: wrong type %x", nlp->n_un.n_name,
- nlp->n_type);
- return NULL;
- }
- }
+ if (read(fd, &eh, sizeof(eh)) != sizeof(eh))
+ errx(1, "%s: read failed", fname);
- if ((fd = open(fname, O_RDONLY)) < 0) {
- warn("open: %s", fname);
- return NULL;
- }
- if (read(fd, &eh, sizeof(eh)) != sizeof(eh)) {
- warn("read: %s", fname);
- goto bad;
- }
- if (!IS_ELF(eh)) {
+ if (!IS_ELF(eh))
errx(1, "%s: bad magic: 0x%02x%02x%02x%02x",
- boot,
- eh.e_ident[EI_MAG0], eh.e_ident[EI_MAG1],
- eh.e_ident[EI_MAG2], eh.e_ident[EI_MAG3]);
- }
+ boot,
+ eh.e_ident[EI_MAG0], eh.e_ident[EI_MAG1],
+ eh.e_ident[EI_MAG2], eh.e_ident[EI_MAG3]);
/*
* We have to include the exec header in the beginning of
@@ -301,21 +263,20 @@ loadprotoblocks(char *fname, long *size)
* the actual write to disk wants to skip the header.
*/
- /* program load header */
- if (eh.e_phnum != 1) {
- errx(1, "%s: only supports one ELF load section", boot);
- }
+ /* Program load header. */
+ if (eh.e_phnum != 1)
+ errx(1, "%s: %u ELF load sections (only support 1)",
+ boot, eh.e_phnum);
+
phsize = eh.e_phnum * sizeof(Elf_Phdr);
ph = malloc(phsize);
- if (ph == NULL) {
- errx(1, "%s: unable to allocate program header space",
- boot);
- }
+ if (ph == NULL)
+ err(1, NULL);
+
lseek(fd, eh.e_phoff, SEEK_SET);
- if (read(fd, ph, phsize) != phsize) {
- errx(1, "%s: unable to read program header space", boot);
- }
+ if (read(fd, ph, phsize) != phsize)
+ errx(1, "%s: can't read header", boot);
tdsize = ph->p_filesz;
@@ -325,39 +286,24 @@ loadprotoblocks(char *fname, long *size)
* This prevents reading beyond the end of the buffer.
*/
if ((bp = calloc(tdsize, 1)) == NULL) {
- warnx("malloc: %s: no memory", fname);
- goto bad;
+ err(1, NULL);
}
+
/* Read the rest of the file. */
lseek(fd, ph->p_offset, SEEK_SET);
if (read(fd, bp, tdsize) != tdsize) {
- warn("read: %s", fname);
- goto bad;
+ errx(1, "%s: read failed", fname);
}
*size = tdsize; /* not aligned to DEV_BSIZE */
- /* Calculate the symbols' locations within the proto file */
- block_count_p = (u_int8_t *) (bp + nl[X_BLOCK_COUNT].n_value);
- block_table_p = (u_int8_t *) (bp + nl[X_BLOCK_TABLE].n_value);
- maxblocknum = *block_count_p;
-
if (verbose) {
fprintf(stderr, "%s: entry point %#x\n", fname, eh.e_entry);
fprintf(stderr, "proto bootblock size %ld\n", *size);
- fprintf(stderr, "room for %d filesystem blocks at %#lx\n",
- maxblocknum, nl[X_BLOCK_TABLE].n_value);
}
close(fd);
return bp;
-
- bad:
- if (bp)
- free(bp);
- if (fd >= 0)
- close(fd);
- return NULL;
}
static void
@@ -372,10 +318,14 @@ devread(int fd, void *buf, daddr_t blk, size_t size, char *msg)
static char sblock[SBSIZE];
-int
-loadblocknums(char *boot, int devfd, struct disklabel *dl)
+/*
+ * Read information about /boot's inode, then put this and filesystem
+ * parameters from the superblock into pbr_symbols.
+ */
+static int
+getbootparams(char *boot, int devfd, struct disklabel *dl)
{
- int i, fd;
+ int fd;
struct stat statbuf, sb;
struct statfs statfsbuf;
struct partition *pl;
@@ -384,14 +334,16 @@ loadblocknums(char *boot, int devfd, struct disklabel *dl)
daddr_t blk, *ap;
struct ufs1_dinode *ip;
int ndb;
- u_int8_t *bt;
- int mib[4];
+ int mib[4];
size_t size;
- dev_t dev;
+ dev_t dev;
/*
- * Open 2nd-level boot program and record the block numbers
- * it occupies on the filesystem represented by `devfd'.
+ * Open 2nd-level boot program and record enough details about
+ * where it is on the filesystem represented by `devfd'
+ * (inode block, offset within that block, and various filesystem
+ * parameters essentially taken from the superblock) for biosboot
+ * to be able to load it later.
*/
/* Make sure the (probably new) boot file is on disk. */
@@ -404,20 +356,18 @@ loadblocknums(char *boot, int devfd, struct disklabel *dl)
err(1, "statfs: %s", boot);
if (strncmp(statfsbuf.f_fstypename, "ffs", MFSNAMELEN) &&
- strncmp(statfsbuf.f_fstypename, "ufs", MFSNAMELEN) ) {
- errx(1, "%s: must be on an FFS filesystem", boot);
- }
+ strncmp(statfsbuf.f_fstypename, "ufs", MFSNAMELEN) )
+ errx(1, "%s: not on an FFS filesystem", boot);
#if 0
- if (read(fd, &eh, sizeof(eh)) != sizeof(eh)) {
+ if (read(fd, &eh, sizeof(eh)) != sizeof(eh))
errx(1, "read: %s", boot);
- }
if (!IS_ELF(eh)) {
errx(1, "%s: bad magic: 0x%02x%02x%02x%02x",
- boot,
- eh.e_ident[EI_MAG0], eh.e_ident[EI_MAG1],
- eh.e_ident[EI_MAG2], eh.e_ident[EI_MAG3]);
+ boot,
+ eh.e_ident[EI_MAG0], eh.e_ident[EI_MAG1],
+ eh.e_ident[EI_MAG2], eh.e_ident[EI_MAG3]);
}
#endif
@@ -430,19 +380,20 @@ loadblocknums(char *boot, int devfd, struct disklabel *dl)
if (fstat(devfd, &sb) != 0)
err(1, "fstat: %s", realdev);
- /* check devices */
+ /* Check devices. */
mib[0] = CTL_MACHDEP;
mib[1] = CPU_CHR2BLK;
mib[2] = sb.st_rdev;
size = sizeof(dev);
- if (sysctl(mib, 3, &dev, &size, NULL, 0) >= 0)
+ if (sysctl(mib, 3, &dev, &size, NULL, 0) >= 0) {
if (statbuf.st_dev / MAXPARTITIONS != dev / MAXPARTITIONS)
errx(1, "cross-device install");
+ }
pl = &dl->d_partitions[DISKPART(statbuf.st_dev)];
close(fd);
- /* Read superblock */
+ /* Read superblock. */
devread(devfd, sblock, pl->p_offset + SBLOCK, SBSIZE, "superblock");
fs = (struct fs *)sblock;
@@ -452,129 +403,129 @@ loadblocknums(char *boot, int devfd, struct disklabel *dl)
if (fs->fs_inopb <= 0)
err(1, "Bad inopb=%d in superblock", fs->fs_inopb);
- /* Read inode */
+ /* Read inode. */
if ((buf = malloc(fs->fs_bsize)) == NULL)
- errx(1, "No memory for filesystem block");
+ err(1, NULL);
blk = fsbtodb(fs, ino_to_fsba(fs, statbuf.st_ino));
+
devread(devfd, buf, pl->p_offset + blk, fs->fs_bsize, "inode");
ip = (struct ufs1_dinode *)(buf) + ino_to_fsbo(fs, statbuf.st_ino);
/*
- * Have the inode. Figure out how many blocks we need.
+ * Have the inode. Figure out how many filesystem blocks (not disk
+ * sectors) there are for biosboot to load.
*/
ndb = howmany(ip->di_size, fs->fs_bsize);
if (ndb <= 0)
errx(1, "No blocks to load");
if (verbose)
- fprintf(stderr, "Will load %d blocks of size %d each.\n",
- ndb, fs->fs_bsize);
-
- if ((dl->d_type != 0 && dl->d_type != DTYPE_FLOPPY &&
- dl->d_type != DTYPE_VND) || userspec ) {
- /* adjust disklabel w/ synthetic geometry */
- dl->d_nsectors = nsectors;
- dl->d_secpercyl = dl->d_nsectors * nheads;
- }
-
- if (verbose)
- fprintf(stderr, "Using disk geometry of %u sectors and %u heads.\n",
- dl->d_nsectors, dl->d_secpercyl/dl->d_nsectors);
+ fprintf(stderr, "%s is %d blocks x %d bytes\n",
+ boot, ndb, fs->fs_bsize);
/*
- * Get the block numbers; we don't handle fragments
+ * Now set the values that will need to go into biosboot
+ * (the partition boot record, a.k.a. the PBR).
*/
+ sym_set_value(pbr_symbols, "_fs_bsize_p", (fs->fs_bsize / 16));
+ sym_set_value(pbr_symbols, "_fs_bsize_s", (fs->fs_bsize / 512));
+ sym_set_value(pbr_symbols, "_fsbtodb", fs->fs_fsbtodb);
+ sym_set_value(pbr_symbols, "_p_offset", pl->p_offset);
+ sym_set_value(pbr_symbols, "_inodeblk",
+ ino_to_fsba(fs, statbuf.st_ino));
ap = ip->di_db;
- bt = block_table_p;
- for (i = 0; i < NDADDR && *ap && ndb; i++, ap++, ndb--) {
- bt += record_block(bt, pl->p_offset + fsbtodb(fs, *ap),
- fs->fs_bsize / 512, dl);
- if ((bt - block_table_p) / 4 > maxblocknum)
- errx(1, "Too many blocks");
- }
- if (ndb != 0) {
-
- /*
- * Just one level of indirections; there isn't much room
- * for more in the 2nd-level /boot anyway.
- */
- blk = fsbtodb(fs, ip->di_ib[0]);
- devread(devfd, buf, pl->p_offset + blk, fs->fs_bsize,
- "indirect block");
- ap = (daddr_t *)buf;
- for (; i < NINDIR(fs) && *ap && ndb; i++, ap++, ndb--) {
- bt += record_block(bt, pl->p_offset + fsbtodb(fs, *ap),
- fs->fs_bsize / 512, dl);
- if ((bt - block_table_p) / 4 > maxblocknum)
- errx(1, "Too many blocks");
- }
+ sym_set_value(pbr_symbols, "_inodedbl",
+ ((((char *)ap) - buf) + INODEOFF));
+ sym_set_value(pbr_symbols, "_nblocks", ndb);
+
+ return 0;
+}
+
+static void
+sym_set_value(struct sym_data *sym_list, char *sym, u_int32_t value)
+{
+ struct sym_data *p;
+
+ for (p = sym_list; p->sym_name != NULL; p++) {
+ if (strcmp(p->sym_name, sym) == 0)
+ break;
}
- /* write out remaining piece */
- bt += record_block(bt, 0, 0, dl);
- /* and again */
- bt += record_block(bt, 0, 0, dl);
- *block_count_p = (bt - block_table_p) / 4;
- if (*block_count_p > maxblocknum)
- errx(1, "Too many blocks");
+ if (p->sym_name == NULL)
+ errx(1, "%s: no such symbol", sym);
- if (verbose)
- fprintf(stderr, "%s: %d entries total\n",
- boot, *block_count_p);
+ if (p->sym_set)
+ errx(1, "%s already set", p->sym_name);
- return 0;
+ p->sym_value = value;
+ p->sym_set = 1;
}
-static int
-record_block(u_int8_t *bt, daddr_t blk, u_int bs, struct disklabel *dl)
+/*
+ * Write the parameters stored in sym_list into the in-memory copy of
+ * the prototype biosboot (proto), ready for it to be written to disk.
+ */
+static void
+pbr_set_symbols(char *fname, char *proto, struct sym_data *sym_list)
{
- static u_int ss = 0, l = 0, i = 0; /* start and len of group */
- int ret = 0;
-
- /* printf("%u, %u\n", blk, bs); */
- if (ss == 0) { /* very beginning */
- ss = blk;
- l = bs;
- return 0;
- } else if (l == 0)
- return 0;
-
- /* record on track boundary or non-contig blocks or last call */
- if ((ss + l) != blk ||
- (ss % dl->d_nsectors + l) >= dl->d_nsectors) {
- register u_int c = ss / dl->d_secpercyl,
- s = ss % dl->d_nsectors + 1;
-
- /* nsectors */
- if ((ss % dl->d_nsectors + l) >= dl->d_nsectors)
- bt[3] = dl->d_nsectors - s + 1;
- else
- bt[3] = l; /* non-contig or last block piece */
-
- bt[2] = (ss % dl->d_secpercyl) / dl->d_nsectors; /* head */
- *(u_int16_t *)bt = (s & 0x3f) | /* sect, cyl */
- ((c & 0xff) << 8) | ((c & 0x300) >> 2);
+ struct sym_data *sym;
+ struct nlist *nl;
+ char *vp;
+ u_int32_t *lp;
+ u_int16_t *wp;
+ u_int8_t *bp;
+
+ for (sym = sym_list; sym->sym_name != NULL; sym++) {
+ if (!sym->sym_set)
+ errx(1, "%s not set", sym->sym_name);
+
+ /* Allocate space for 2; second is null-terminator for list. */
+ nl = calloc(2, sizeof(struct nlist));
+ if (nl == NULL)
+ err(1, NULL);
+
+ nl->n_un.n_name = sym->sym_name;
+
+ if (nlist(fname, nl) != 0)
+ errx(1, "%s: symbol %s not found",
+ fname, sym->sym_name);
+
+ if (nl->n_type != (N_TEXT))
+ errx(1, "%s: %s: wrong type (%x)",
+ fname, sym->sym_name, nl->n_type);
+
+ /* Get a pointer to where the symbol's value needs to go. */
+ vp = proto + nl->n_value;
+
+ switch (sym->sym_size) {
+ case 4: /* u_int32_t */
+ lp = (u_int32_t *) vp;
+ *lp = sym->sym_value;
+ break;
+ case 2: /* u_int16_t */
+ if (sym->sym_value >= 0x10000) /* out of range */
+ errx(1, "%s: symbol out of range (%u)",
+ sym->sym_name, sym->sym_value);
+ wp = (u_int16_t *) vp;
+ *wp = (u_int16_t) sym->sym_value;
+ break;
+ case 1: /* u_int16_t */
+ if (sym->sym_value >= 0x100) /* out of range */
+ errx(1, "%s: symbol out of range (%u)",
+ sym->sym_name, sym->sym_value);
+ bp = (u_int8_t *) vp;
+ *bp = (u_int8_t) sym->sym_value;
+ break;
+ default:
+ errx(1, "%s: bad symbol size %d",
+ sym->sym_name, sym->sym_size);
+ /* NOTREACHED */
+ }
+
+ free(nl);
if (verbose)
- fprintf(stderr, "%2d: %2d @(%d %d %d) (%d-%d)\n",
- i, bt[3], c, bt[2], s, ss, ss + bt[3] - 1);
- i++;
-
- ss += bt[3];
- l -= bt[3];
- if ((ss + l) != blk) {
- /* printf ("l=%u\n", l); */
- if (l)
- ret += record_block(bt + 4, 0, 0, dl);
- ss = blk;
- l = bs;
- } else
- l += bs;
-
- ret += 4;
- } else {
- l += bs;
+ fprintf(stderr, "%s = %u\n",
+ sym->sym_name, sym->sym_value);
}
-
- return ret;
}