Add chain loader support for loader

Implement simple chain loader in loader; this update does add chain command,
taking device or file as argument to load and start new boot loader.

In case of BIOS, the chain will read the boot block to address 0000:7c00 and
jumps on it. In case of UEFI, the chain command is to be used with efi
application, typically stored in EFI System Partition.

The update also does add simple menu entry, if the variable chain_disk is set.
The value of the variable chain_disk is used as argument for chain loading.

Relnotes:	yes
Differential Revision:	https://reviews.freebsd.org/D5992
This commit is contained in:
Toomas Soome 2017-06-16 20:08:44 +00:00
parent a4110f9ffa
commit 769bad9f8a
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=320011
8 changed files with 642 additions and 2 deletions

View File

@ -795,6 +795,100 @@ command_fdt(int argc, char *argv[])
COMMAND_SET(fdt, "fdt", "flattened device tree handling", command_fdt);
#endif
/*
* Chain load another efi loader.
*/
static int
command_chain(int argc, char *argv[])
{
EFI_GUID LoadedImageGUID = LOADED_IMAGE_PROTOCOL;
EFI_HANDLE loaderhandle;
EFI_LOADED_IMAGE *loaded_image;
EFI_STATUS status;
struct stat st;
struct devdesc *dev;
char *name, *path;
void *buf;
int fd;
if (argc < 2) {
command_errmsg = "wrong number of arguments";
return (CMD_ERROR);
}
name = argv[1];
if ((fd = open(name, O_RDONLY)) < 0) {
command_errmsg = "no such file";
return (CMD_ERROR);
}
if (fstat(fd, &st) < -1) {
command_errmsg = "stat failed";
close(fd);
return (CMD_ERROR);
}
status = BS->AllocatePool(EfiLoaderCode, (UINTN)st.st_size, &buf);
if (status != EFI_SUCCESS) {
command_errmsg = "failed to allocate buffer";
close(fd);
return (CMD_ERROR);
}
if (read(fd, buf, st.st_size) != st.st_size) {
command_errmsg = "error while reading the file";
(void)BS->FreePool(buf);
close(fd);
return (CMD_ERROR);
}
close(fd);
status = BS->LoadImage(FALSE, IH, NULL, buf, st.st_size, &loaderhandle);
(void)BS->FreePool(buf);
if (status != EFI_SUCCESS) {
command_errmsg = "LoadImage failed";
return (CMD_ERROR);
}
status = BS->HandleProtocol(loaderhandle, &LoadedImageGUID,
(void **)&loaded_image);
if (argc > 2) {
int i, len = 0;
CHAR16 *argp;
for (i = 2; i < argc; i++)
len += strlen(argv[i]) + 1;
len *= sizeof (*argp);
loaded_image->LoadOptions = argp = malloc (len);
loaded_image->LoadOptionsSize = len;
for (i = 2; i < argc; i++) {
char *ptr = argv[i];
while (*ptr)
*(argp++) = *(ptr++);
*(argp++) = ' ';
}
*(--argv) = 0;
}
if (efi_getdev((void **)&dev, name, (const char **)&path) == 0)
loaded_image->DeviceHandle =
efi_find_handle(dev->d_dev, dev->d_unit);
dev_cleanup();
status = BS->StartImage(loaderhandle, NULL, NULL);
if (status != EFI_SUCCESS) {
command_errmsg = "StartImage failed";
free(loaded_image->LoadOptions);
loaded_image->LoadOptions = NULL;
status = BS->UnloadImage(loaded_image);
return (CMD_ERROR);
}
return (CMD_ERROR); /* not reached */
}
COMMAND_SET(chain, "chain", "chain load file", command_chain);
#ifdef EFI_ZFS_BOOT
static void
efi_zfs_probe(void)

View File

@ -73,8 +73,23 @@ s" currdev" getenv dup 0> [if] drop 4 s" zfs:" compare 0= [if]
set mainmenu_command[7]="3 goto_menu"
set mainmenu_keycode[7]=101
set mainansi_caption[7]="Select Boot ^[1mE^[37mnvironment..."
s" chain_disk" getenv? [if]
set mainmenu_caption[8]="Chain[L]oad ${chain_disk}"
set mainmenu_command[8]="chain ${chain_disk}"
set mainmenu_keycode[8]=108
set mainansi_caption[8]="Chain^[1mL^[moad ${chain_disk}"
[then]
[else]
s" chain_disk" getenv? [if]
set mainmenu_caption[7]="Chain[L]oad ${chain_disk}"
set mainmenu_command[7]="chain ${chain_disk}"
set mainmenu_keycode[7]=108
set mainansi_caption[7]="Chain^[1mL^[moad ${chain_disk}"
[then]
[then] [else] drop [then]
\
\ BOOT OPTIONS MENU
\

View File

@ -6,7 +6,7 @@ INTERNALLIB=
SRCS= biosacpi.c bioscd.c biosdisk.c biosmem.c biospnp.c \
biospci.c biossmap.c bootinfo.c bootinfo32.c bootinfo64.c \
comconsole.c devicename.c elf32_freebsd.c \
elf64_freebsd.c multiboot.c multiboot_tramp.S \
elf64_freebsd.c multiboot.c multiboot_tramp.S relocater_tramp.S \
i386_copy.c i386_module.c nullconsole.c pxe.c pxetramp.s \
smbios.c time.c vidconsole.c amd64_tramp.S spinconsole.c
.PATH: ${.CURDIR}/../../zfs

View File

@ -60,6 +60,35 @@ struct i386_devdesc
} d_kind;
};
/*
* relocater trampoline support.
*/
struct relocate_data {
uint32_t src;
uint32_t dest;
uint32_t size;
};
extern void relocater(void);
extern uint32_t relocater_data;
extern uint32_t relocater_size;
extern uint16_t relocator_ip;
extern uint16_t relocator_cs;
extern uint16_t relocator_ds;
extern uint16_t relocator_es;
extern uint16_t relocator_fs;
extern uint16_t relocator_gs;
extern uint16_t relocator_ss;
extern uint16_t relocator_sp;
extern uint32_t relocator_esi;
extern uint32_t relocator_eax;
extern uint32_t relocator_ebx;
extern uint32_t relocator_edx;
extern uint32_t relocator_ebp;
extern uint16_t relocator_a20_enabled;
int i386_getdev(void **vdev, const char *devspec, const char **path);
char *i386_fmtdev(void *vdev);
int i386_setcurrdev(struct env_var *ev, int flags, const void *value);

View File

@ -0,0 +1,358 @@
/*-
* Copyright 2015 Toomas Soome <tsoome@me.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* relocate is needed to support loading code which has to be located
* below 1MB, as both BTX and loader are using low memory area.
*
* relocate and start loaded code. Since loaded code may need to be
* placed to already occupied memory area, this code is moved to safe
* memory area and then btx __exec will be called with physical pointer
* to this area. __exec will set pointer to %eax and use call *%eax,
* so on entry, we have new "base" address in %eax.
*
* Relocate will first set up and load new safe GDT to shut down BTX,
* then loaded code will be relocated to final memory location,
* then machine will be switched from 32bit protected mode to 16bit
* protected mode following by switch to real mode with A20 enabled or
* disabled. Finally the loaded code will be started and it will take
* over the whole system.
*
* For now, the known "safe" memory area for relocate is 0x600,
* the actual "free" memory is supposed to start from 0x500, leaving
* first 0x100 bytes in reserve. As relocate code+data is very small,
* it will leave enough space to set up boot blocks to 0:7c00 or load
* linux kernel below 1MB space.
*/
/*
* segment selectors
*/
.set SEL_SCODE,0x8
.set SEL_SDATA,0x10
.set SEL_RCODE,0x18
.set SEL_RDATA,0x20
.p2align 4
.globl relocater
relocater:
cli
/*
* set up GDT from new location
*/
movl %eax, %esi /* our base address */
add $(relocater.1-relocater), %eax
jmp *%eax
relocater.1:
/* set up jump */
lea (relocater.2-relocater)(%esi), %eax
movl %eax, (jump_vector-relocater) (%esi)
/* set up gdt */
lea (gdt-relocater) (%esi), %eax
movl %eax, (gdtaddr-relocater) (%esi)
/* load gdt */
lgdt (gdtdesc - relocater) (%esi)
lidt (idt-relocater) (%esi)
/* update cs */
ljmp *(jump_vector-relocater) (%esi)
.code32
relocater.2:
xorl %eax, %eax
movb $SEL_SDATA, %al
movw %ax, %ss
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movl %cr0, %eax /* disable paging */
andl $~0x80000000,%eax
movl %eax, %cr0
xorl %ecx, %ecx /* flush TLB */
movl %ecx, %cr3
cld
/*
* relocate data loop. load source, dest and size from
* relocater_data[i], 0 value will stop the loop.
* registers used for move: %esi, %edi, %ecx.
* %ebx to keep base
* %edx for relocater_data offset
*/
movl %esi, %ebx /* base address */
xorl %edx, %edx
loop.1:
movl (relocater_data-relocater)(%ebx, %edx, 4), %eax
testl %eax, %eax
jz loop.2
movl (relocater_data-relocater)(%ebx, %edx, 4), %esi
inc %edx
movl (relocater_data-relocater)(%ebx, %edx, 4), %edi
inc %edx
movl (relocater_data-relocater)(%ebx, %edx, 4), %ecx
inc %edx
rep
movsb
jmp loop.1
loop.2:
movl %ebx, %esi /* restore esi */
/*
* data is relocated, switch to 16bit mode
*/
lea (relocater.3-relocater)(%esi), %eax
movl %eax, (jump_vector-relocater) (%esi)
movl $SEL_RCODE, %eax
movl %eax, (jump_vector-relocater+4) (%esi)
ljmp *(jump_vector-relocater) (%esi)
relocater.3:
.code16
movw $SEL_RDATA, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
lidt (idt-relocater) (%esi)
lea (relocater.4-relocater)(%esi), %eax
movl %eax, (jump_vector-relocater) (%esi)
xorl %eax, %eax
movl %eax, (jump_vector-relocater+4) (%esi)
/* clear PE */
movl %cr0, %eax
dec %al
movl %eax, %cr0
ljmp *(jump_vector-relocater) (%esi)
relocater.4:
xorw %ax, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
/*
* set real mode irq offsets
*/
movw $0x7008,%bx
in $0x21,%al # Save master
push %ax # IMR
in $0xa1,%al # Save slave
push %ax # IMR
movb $0x11,%al # ICW1 to
outb %al,$0x20 # master,
outb %al,$0xa0 # slave
movb %bl,%al # ICW2 to
outb %al,$0x21 # master
movb %bh,%al # ICW2 to
outb %al,$0xa1 # slave
movb $0x4,%al # ICW3 to
outb %al,$0x21 # master
movb $0x2,%al # ICW3 to
outb %al,$0xa1 # slave
movb $0x1,%al # ICW4 to
outb %al,$0x21 # master,
outb %al,$0xa1 # slave
pop %ax # Restore slave
outb %al,$0xa1 # IMR
pop %ax # Restore master
outb %al,$0x21 # IMR
# done
/*
* Should A20 be left enabled?
*/
/* movw imm16, %ax */
.byte 0xb8
.globl relocator_a20_enabled
relocator_a20_enabled:
.word 0
test %ax, %ax
jnz a20_done
movw $0xa00, %ax
movw %ax, %sp
movw %ax, %bp
/* Disable A20 */
movw $0x2400, %ax
int $0x15
# jnc a20_done
call a20_check_state
testb %al, %al
jz a20_done
inb $0x92
andb $(~0x03), %al
outb $0x92
jmp a20_done
a20_check_state:
movw $100, %cx
1:
xorw %ax, %ax
movw %ax, %ds
decw %ax
movw %ax, %es
xorw %ax, %ax
movw $0x8000, %ax
movw %ax, %si
addw $0x10, %ax
movw %ax, %di
movb %ds:(%si), %dl
movb %es:(%di), %al
movb %al, %dh
decb %dh
movb %dh, %ds:(%si)
outb %al, $0x80
outb %al, $0x80
movb %es:(%di), %dh
subb %dh, %al
xorb $1, %al
movb %dl, %ds:(%si)
testb %al, %al
jz a20_done
loop 1b
ret
a20_done:
/*
* set up registers
*/
/* movw imm16, %ax. */
.byte 0xb8
.globl relocator_ds
relocator_ds: .word 0
movw %ax, %ds
/* movw imm16, %ax. */
.byte 0xb8
.globl relocator_es
relocator_es: .word 0
movw %ax, %es
/* movw imm16, %ax. */
.byte 0xb8
.globl relocator_fs
relocator_fs: .word 0
movw %ax, %fs
/* movw imm16, %ax. */
.byte 0xb8
.globl relocator_gs
relocator_gs: .word 0
movw %ax, %gs
/* movw imm16, %ax. */
.byte 0xb8
.globl relocator_ss
relocator_ss: .word 0
movw %ax, %ss
/* movw imm16, %ax. */
.byte 0xb8
.globl relocator_sp
relocator_sp: .word 0
movzwl %ax, %esp
/* movw imm32, %eax. */
.byte 0x66, 0xb8
.globl relocator_esi
relocator_esi: .long 0
movl %eax, %esi
/* movw imm32, %edx. */
.byte 0x66, 0xba
.globl relocator_edx
relocator_edx: .long 0
/* movw imm32, %ebx. */
.byte 0x66, 0xbb
.globl relocator_ebx
relocator_ebx: .long 0
/* movw imm32, %eax. */
.byte 0x66, 0xb8
.globl relocator_eax
relocator_eax: .long 0
/* movw imm32, %ebp. */
.byte 0x66, 0xbd
.globl relocator_ebp
relocator_ebp: .long 0
sti
.byte 0xea /* ljmp */
.globl relocator_ip
relocator_ip:
.word 0
.globl relocator_cs
relocator_cs:
.word 0
/* GDT to reset BTX */
.code32
.p2align 4
jump_vector: .long 0
.long SEL_SCODE
gdt: .word 0x0, 0x0 /* null entry */
.byte 0x0, 0x0, 0x0, 0x0
.word 0xffff, 0x0 /* SEL_SCODE */
.byte 0x0, 0x9a, 0xcf, 0x0
.word 0xffff, 0x0 /* SEL_SDATA */
.byte 0x0, 0x92, 0xcf, 0x0
.word 0xffff, 0x0 /* SEL_RCODE */
.byte 0x0, 0x9a, 0x0f, 0x0
.word 0xffff, 0x0 /* SEL_RDATA */
.byte 0x0, 0x92, 0x0f, 0x0
gdt.1:
gdtdesc: .word gdt.1 - gdt - 1 /* limit */
gdtaddr: .long 0 /* base */
idt: .word 0x3ff
.long 0
.globl relocater_data
relocater_data:
.long 0 /* src */
.long 0 /* dest */
.long 0 /* size */
.long 0 /* src */
.long 0 /* dest */
.long 0 /* size */
.long 0 /* src */
.long 0 /* dest */
.long 0 /* size */
.long 0
.globl relocater_size
relocater_size:
.long relocater_size-relocater

View File

@ -14,7 +14,7 @@ LOADER_NFS_SUPPORT?= yes
LOADER_TFTP_SUPPORT?= yes
# architecture-specific loader code
SRCS= main.c conf.c vers.c
SRCS= main.c conf.c vers.c chain.c
# Put LOADER_FIREWIRE_SUPPORT=yes in /etc/make.conf for FireWire/dcons support
.if defined(LOADER_FIREWIRE_SUPPORT)

View File

@ -0,0 +1,135 @@
/*-
* Copyright 2015 Toomas Soome <tsoome@me.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Chain loader to load BIOS boot block either from MBR or PBR.
*
* Note the boot block location 0000:7c000 conflicts with loader, so we need to
* read in to temporary space and relocate on exec, when btx is stopped.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <stand.h>
#include <sys/param.h>
#include <sys/linker.h>
#include <sys/diskmbr.h>
#include "bootstrap.h"
#include "libi386/libi386.h"
#include "btxv86.h"
/*
* The MBR/VBR is located in first sector of disk/partition.
* Read 512B to temporary location and set up relocation. Then
* exec relocator.
*/
#define SECTOR_SIZE (512)
COMMAND_SET(chain, "chain", "chain load boot block from device", command_chain);
static int
command_chain(int argc, char *argv[])
{
int fd, len, size = SECTOR_SIZE;
struct stat st;
vm_offset_t mem = 0x100000;
uint32_t *uintptr = &relocater_data;
struct i386_devdesc *rootdev;
if (argc == 1) {
command_errmsg = "no device or file name specified";
return (CMD_ERROR);
}
if (argc != 2) {
command_errmsg = "invalid trailing arguments";
return (CMD_ERROR);
}
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
command_errmsg = "open failed";
return (CMD_ERROR);
}
len = strlen(argv[1]);
if (argv[1][len-1] != ':') {
if (fstat(fd, &st) == -1) {
command_errmsg = "stat failed";
close(fd);
return (CMD_ERROR);
}
size = st.st_size;
} else if (strncmp(argv[1], "disk", 4) != 0) {
command_errmsg = "can only use disk device";
close(fd);
return (CMD_ERROR);
}
i386_getdev((void **)(&rootdev), argv[1], NULL);
if (rootdev == NULL) {
command_errmsg = "can't determine root device";
return (CMD_ERROR);
}
if (archsw.arch_readin(fd, mem, SECTOR_SIZE) != SECTOR_SIZE) {
command_errmsg = "failed to read disk";
close(fd);
return (CMD_ERROR);
}
close(fd);
if (*((uint16_t *)PTOV(mem + DOSMAGICOFFSET)) != DOSMAGIC) {
command_errmsg = "wrong magic";
return (CMD_ERROR);
}
uintptr[0] = mem;
uintptr[1] = 0x7C00;
uintptr[2] = SECTOR_SIZE;
relocator_edx = bd_unit2bios(rootdev->d_unit);
relocator_esi = relocater_size;
relocator_ds = 0;
relocator_es = 0;
relocator_fs = 0;
relocator_gs = 0;
relocator_ss = 0;
relocator_cs = 0;
relocator_sp = 0x7C00;
relocator_ip = 0x7C00;
relocator_a20_enabled = 0;
i386_copyin(relocater, 0x600, relocater_size);
dev_cleanup();
__exec((void *)0x600);
panic("exec returned");
return (CMD_ERROR); /* not reached */
}

View File

@ -43,3 +43,12 @@
Displays the BIOS SMAP (system memory map) table.
################################################################################
# Tchain DChain load disk block
chain disk:
chain will read stage1 (MBR or VBR) boot block from specified device
to address 0000:7C00 and attempts to run it. Use lsdev to get available
device names. Disk name must end with colon.
################################################################################