diff options
author | Tom Cosgrove <tom@cvs.openbsd.org> | 2004-01-26 23:21:50 +0000 |
---|---|---|
committer | Tom Cosgrove <tom@cvs.openbsd.org> | 2004-01-26 23:21:50 +0000 |
commit | 4b4e6dcb3f2a9873179163ebb398ff619f4da907 (patch) | |
tree | 3184c8017bd9a4894bf29f6ffd23d0dc6f8e4c8c /sys/arch | |
parent | e624bc83143e9c5528e0874763e14d1947cbb62f (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.8 | 254 | ||||
-rw-r--r-- | sys/arch/i386/stand/biosboot/biosboot.S | 892 | ||||
-rw-r--r-- | sys/arch/i386/stand/installboot/installboot.8 | 90 | ||||
-rw-r--r-- | sys/arch/i386/stand/installboot/installboot.c | 479 |
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; } |