The recent change to increase the zfsboot size to 64k made a few BIOSes

unhappy (probably they don't handle crossing the 64k boundary, etc.).
Fix this by changing zfsldr to use a loop reading from the disk one
sector at a time.  To avoid trashing the saved copy of the MBR which is
used for disk I/O, read zfsboot2 at address 0x9000.  This has the
advantage that BTX no longer needs to be relocated as it is read into
the correct location.  However, the loop to relocate zfsboot2.bin can
now cross a 64k boundary, so change it to use relative segments instead.
(This will need further work if zfsboot2.bin ever exceeds 64k.)

While here, stop storing a relocated copy of zfsldr at 0x700.  This was
only used by the xread hack which has recently been removed (and even
that use was dubious).  Also, include the BIOS error code as hex when
reporting read errors to aid in debugging.

Much thanks to Henri Hennebert for patiently testing various iterations
of the patch as well as fixing the zfsboot2.bin relocation to use
relative segments.

MFC after:	1 week
This commit is contained in:
jhb 2011-06-23 15:53:17 +00:00
parent c2a28c3cb6
commit 6e30ce7ac2

View File

@ -16,7 +16,6 @@
*/
/* Memory Locations */
.set MEM_REL,0x700 # Relocation address
.set MEM_ARG,0x900 # Arguments
.set MEM_ORG,0x7c00 # Origin
.set MEM_BUF,0x8000 # Load area
@ -90,15 +89,6 @@ main: cld # String ops inc
mov %cx,%ds # data
mov %cx,%ss # Set up
mov $start,%sp # stack
/*
* Relocate ourself to MEM_REL. Since %cx == 0, the inc %ch sets
* %cx == 0x100.
*/
mov %sp,%si # Source
mov $MEM_REL,%di # Destination
incb %ch # Word count
rep # Copy
movsw # code
/*
* If we are on a hard drive, then load the MBR and look for the first
* FreeBSD slice. We use the fake partition entry below that points to
@ -106,11 +96,12 @@ main: cld # String ops inc
* FreeBSD slice. The second pass looks for the first non-active FreeBSD
* slice if the first one fails.
*/
mov $part4,%si # Partition
mov $part4,%si # Dummy partition
cmpb $0x80,%dl # Hard drive?
jb main.4 # No
movb $0x1,%dh # Block count
callw nread # Read MBR
xor %eax,%eax # Read MBR
movl $MEM_BUF,%ebx # from first
callw nread # sector
mov $0x1,%cx # Two passes
main.1: mov $MEM_BUF+PRT_OFF,%si # Partition table
movb $0x1,%dh # Partition
@ -139,52 +130,51 @@ main.4: xor %dx,%dx # Partition:drive
/*
* Ok, we have a slice and drive in %dx now, so use that to locate and
* load boot2. %si references the start of the slice we are looking
* for, so go ahead and load up the 64 sectors starting at sector 1024
* for, so go ahead and load up the 128 sectors starting at sector 1024
* (i.e. after the two vdev labels). We don't have do anything fancy
* here to allow for an extra copy of boot1 and a partition table
* (compare to this section of the UFS bootstrap) so we just load it
* all at 0x8000. The first part of boot2 is BTX, which wants to run
* all at 0x9000. The first part of boot2 is BTX, which wants to run
* at 0x9000. The boot2.bin binary starts right after the end of BTX,
* so we have to figure out where the start of it is and then move the
* binary to 0xc000. After we have moved the client, we relocate BTX
* itself to 0x9000 - doing it in this order means that none of the
* memcpy regions overlap which would corrupt the copy. Normally, BTX
* clients start at MEM_USR, or 0xa000, but when we use btxld to
* create zfsboot2, we use an entry point of 0x2000. That entry point is
* relative to MEM_USR; thus boot2.bin starts at 0xc000.
* binary to 0xc000. Normally, BTX clients start at MEM_USR, or 0xa000,
* but when we use btxld to create zfsboot2, we use an entry point of
* 0x2000. That entry point is relative to MEM_USR; thus boot2.bin
* starts at 0xc000.
*
* The load area and the target area for the client overlap so we have
* to use a decrementing string move. We also play segment register
* games with the destination address for the move so that the client
* can be larger than 16k (which would overflow the zero segment since
* the client starts at 0xc000). Relocating BTX is easy since the load
* area and target area do not overlap.
* the client starts at 0xc000).
*/
main.5: mov %dx,MEM_ARG # Save args
movb $NSECT,%dh # Sector count
mov $NSECT,%cx # Sector count
movl $1024,%eax # Offset to boot2
callw nread.1 # Read disk
main.6: mov $MEM_BUF,%si # BTX (before reloc)
mov 0xa(%si),%bx # Get BTX length and set
mov $MEM_BTX,%ebx # Destination buffer
main.6: pushal # Save params
callw nread # Read disk
popal # Restore
incl %eax # Advance to
add $SIZ_SEC,%ebx # next sector
loop main.6 # If not last, read another
mov MEM_BTX+0xa,%bx # Get BTX length
mov $NSECT*SIZ_SEC-1,%di # Size of load area (less one)
mov %di,%si # End of load
add $MEM_BUF,%si # area
mov %di,%si # End of load area, 0x9000 rel
sub %bx,%di # End of client, 0xc000 rel
mov %di,%cx # Size of
inc %cx # client
mov $(MEM_BTX)>>4,%dx # Segment
mov %dx,%ds # addressing 0x9000
mov $(MEM_USR+2*SIZ_PAG)>>4,%dx # Segment
mov %dx,%es # addressing 0xc000
std # Move with decrement
rep # Relocate
movsb # client
mov %ds,%dx # Back to
mov %dx,%es # zero segment
mov $MEM_BUF,%si # BTX (before reloc)
mov $MEM_BTX,%di # BTX
mov %bx,%cx # Get BTX length
cld # Increment this time
rep # Relocate
movsb # BTX
cld # Back to increment
xor %dx,%dx # Back
mov %ds,%dx # to zero
mov %dx,%es # segment
/*
* Enable A20 so we can access memory above 1 meg.
@ -211,32 +201,35 @@ seta20.3: sti # Enable interrupts
/*
* Trampoline used to call read from within zfsldr. Sets up an EDD
* packet on the stack and passes it to read.
* packet on the stack and passes it to read. We assume that the
* destination address is always segment-aligned.
*
* %eax - int - LBA to read in relative to partition start
* %ebx - ptr - destination address
* %dl - byte - drive to read from
* %dh - byte - num sectors to read
* %si - ptr - MBR partition entry
*/
nread: xor %eax,%eax # Sector offset in partition
nread.1: xor %ecx,%ecx # Get
nread: xor %ecx,%ecx # Get
addl 0x8(%si),%eax # LBA
adc $0,%ecx
pushl %ecx # Starting absolute block
pushl %eax # block number
push %es # Address of
push $MEM_BUF # transfer buffer
xor %ax,%ax # Number of
movb %dh,%al # blocks to
push %ax # transfer
shr $4,%ebx # Convert to segment
push %bx # Address of
push $0 # transfer buffer
push $0x1 # Read 1 sector
push $0x10 # Size of packet
mov %sp,%bp # Packet pointer
callw read # Read from disk
jc nread.1 # If error, fail
lea 0x10(%bp),%sp # Clear stack
jnc return # If success, return
mov $msg_read,%si # Otherwise, set the error
# message and fall through to
# the error routine
ret # If success, return
nread.1: mov %ah,%al # Format
mov $read_err,%di # error
call hex8 # code
mov $msg_read,%si # Set the error message and
# fall through to the error
# routine
/*
* Print out the error message pointed to by %ds:(%si) followed
* by a prompt, wait for a keypress, and then reboot the machine.
@ -258,14 +251,6 @@ putstr: lodsb # Get char
testb %al,%al # End of string?
jne putstr.0 # No
/*
* Overused return code. ereturn is used to return an error from the
* read function. Since we assume putstr succeeds, we (ab)use the
* same code when we return from putstr.
*/
ereturn: movb $0x1,%ah # Invalid
stc # argument
return: retw # To caller
/*
* Reads sectors from the disk. If EDD is enabled, then check if it is
* installed and use it if it is. If it is not installed or not enabled, then
@ -294,14 +279,30 @@ read: cmpb $0x80,%dl # Hard drive?
retw # To caller
read.1: mov $msg_chs,%si
jmp error
msg_chs: .asciz "CHS not supported"
/*
* AL to hex, saving the result to [EDI].
*/
hex8: push %ax # Save
shrb $0x4,%al # Do upper
call hex8.1 # 4
pop %ax # Restore
hex8.1: andb $0xf,%al # Get lower 4
cmpb $0xa,%al # Convert
sbbb $0x69,%al # to hex
das # digit
orb $0x20,%al # To lower case
stosb # Save char
ret # (Recursive)
/* Messages */
msg_read: .asciz "Read"
msg_part: .asciz "Boot"
msg_chs: .asciz "CHS not supported"
msg_read: .ascii "Read error: "
read_err: .asciz "XX"
msg_part: .asciz "Boot error"
prompt: .asciz " error\r\n"
prompt: .asciz "\r\n"
.org PRT_OFF,0x90