diff options
Diffstat (limited to 'stand/i386/gptzfsboot/zfsboot.c')
-rw-r--r-- | stand/i386/gptzfsboot/zfsboot.c | 717 |
1 files changed, 717 insertions, 0 deletions
diff --git a/stand/i386/gptzfsboot/zfsboot.c b/stand/i386/gptzfsboot/zfsboot.c new file mode 100644 index 000000000000..4c8eae9b65e5 --- /dev/null +++ b/stand/i386/gptzfsboot/zfsboot.c @@ -0,0 +1,717 @@ +/*- + * Copyright (c) 1998 Robert Nordier + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + */ + +#include <stand.h> + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/diskmbr.h> +#ifdef GPT +#include <sys/gpt.h> +#endif +#include <sys/reboot.h> +#include <sys/queue.h> +#ifdef LOADER_ZFS_SUPPORT +#include <sys/zfs_bootenv.h> +#endif + +#include <machine/bootinfo.h> +#include <machine/elf.h> +#include <machine/pc/bios.h> + +#include <stdarg.h> +#include <stddef.h> + +#include <a.out.h> +#include "bootstrap.h" +#include "libi386.h" +#include <btxv86.h> + +#include "lib.h" +#include "rbx.h" +#include "cons.h" +#include "bootargs.h" +#include "disk.h" +#include "part.h" +#include "paths.h" + +#include "libzfs.h" + +#define ARGS 0x900 +#define NOPT 14 +#define NDEV 3 + +#define BIOS_NUMDRIVES 0x475 +#define DRV_HARD 0x80 +#define DRV_MASK 0x7f + +#define TYPE_AD 0 +#define TYPE_DA 1 +#define TYPE_MAXHARD TYPE_DA +#define TYPE_FD 2 + +extern uint32_t _end; + +static const char optstr[NOPT] = "DhaCcdgmnpqrsv"; /* Also 'P', 'S' */ +static const unsigned char flags[NOPT] = { + RBX_DUAL, + RBX_SERIAL, + RBX_ASKNAME, + RBX_CDROM, + RBX_CONFIG, + RBX_KDB, + RBX_GDB, + RBX_MUTE, + RBX_NOINTR, + RBX_PAUSE, + RBX_QUIET, + RBX_DFLTROOT, + RBX_SINGLE, + RBX_VERBOSE +}; +uint32_t opts; + +/* + * Paths to try loading before falling back to the boot2 prompt. + * + * /boot/zfsloader must be tried before /boot/loader in order to remain + * backward compatible with ZFS boot environments where /boot/loader exists + * but does not have ZFS support, which was the case before FreeBSD 12. + * + * If no loader is found, try to load a kernel directly instead. + */ +static const struct string { + const char *p; + size_t len; +} loadpath[] = { + { PATH_LOADER_ZFS, sizeof(PATH_LOADER_ZFS) }, + { PATH_LOADER, sizeof(PATH_LOADER) }, + { PATH_KERNEL, sizeof(PATH_KERNEL) }, +}; + +static const unsigned char dev_maj[NDEV] = {30, 4, 2}; + +static struct i386_devdesc *bdev; +static char cmd[512]; +static char cmddup[512]; +static char kname[1024]; +static int comspeed = SIOSPD; +static struct bootinfo bootinfo; +static uint32_t bootdev; +static struct zfs_boot_args zfsargs; +#ifdef LOADER_GELI_SUPPORT +static struct geli_boot_args geliargs; +#endif + +extern vm_offset_t high_heap_base; +extern uint32_t bios_basemem, bios_extmem, high_heap_size; + +static char *heap_top; +static char *heap_bottom; + +void exit(int); +static void i386_zfs_probe(void); +static void load(void); +static int parse_cmd(void); + +#ifdef LOADER_GELI_SUPPORT +#include "geliboot.h" +static char gelipw[GELI_PW_MAXLEN]; +#endif + +/* + * Only because the zfs code requires access through archsw, otherwise the + * 'boot' programs don't need archsw. This is less than ideal, but this + * workaround is easier than many of the alternatives. + */ +struct arch_switch archsw = { /* MI/MD interface boundary */ + .arch_getdev = i386_getdev, + .arch_zfs_probe = i386_zfs_probe, +}; + +static char boot_devname[2 * ZFS_MAXNAMELEN + 8]; /* disk or pool:dataset */ + +struct devsw *devsw[] = { + &bioshd, +#if defined(LOADER_ZFS_SUPPORT) + &zfs_dev, +#endif + NULL +}; + +struct fs_ops *file_system[] = { +#if defined(LOADER_ZFS_SUPPORT) + &zfs_fsops, +#endif +#if defined(LOADER_UFS_SUPPORT) + &ufs_fsops, +#endif + NULL +}; + +caddr_t +ptov(uintptr_t x) +{ + return (PTOV(x)); +} + +int main(void); + +int +main(void) +{ + unsigned i; + int auto_boot, fd, nextboot = 0; + struct disk_devdesc *devdesc; + + bios_getmem(); + + if (high_heap_size > 0) { + heap_top = PTOV(high_heap_base + high_heap_size); + heap_bottom = PTOV(high_heap_base); + } else { + heap_bottom = (char *) + (roundup2(__base + (int32_t)&_end, 0x10000) - __base); + heap_top = (char *)PTOV(bios_basemem); + } + setheap(heap_bottom, heap_top); + + /* + * Initialise the block cache. Set the upper limit. + */ + bcache_init(32768, 512); + + bootinfo.bi_version = BOOTINFO_VERSION; + bootinfo.bi_size = sizeof(bootinfo); + bootinfo.bi_basemem = bios_basemem / 1024; + bootinfo.bi_extmem = bios_extmem / 1024; + bootinfo.bi_memsizes_valid++; + bootinfo.bi_bios_dev = *(uint8_t *)PTOV(ARGS); + + /* Set up fall back device name. */ + snprintf(boot_devname, sizeof (boot_devname), "disk%d:", + bd_bios2unit(bootinfo.bi_bios_dev)); + + /* Set up currdev variable to have hooks in place. */ + env_setenv("currdev", EV_VOLATILE, "", gen_setcurrdev, + env_nounset); + + devinit(); + + /* XXX assumes this will be a disk, but it looks likely give above */ + disk_parsedev((struct devdesc **)&devdesc, boot_devname, NULL); + + bootdev = MAKEBOOTDEV(dev_maj[DEVT_DISK], devdesc->d_slice + 1, + devdesc->dd.d_unit, + devdesc->d_partition >= 0 ? devdesc->d_partition : 0xff); + free(devdesc); + + /* + * devformat() can be called only after dv_init + */ + if (bdev != NULL && bdev->dd.d_dev->dv_type == DEVT_ZFS) { + /* set up proper device name string for ZFS */ + strncpy(boot_devname, devformat(&bdev->dd), sizeof (boot_devname)); + if (zfs_get_bootonce(bdev, OS_BOOTONCE, cmd, + sizeof(cmd)) == 0) { + nvlist_t *benv; + + nextboot = 1; + memcpy(cmddup, cmd, sizeof(cmd)); + if (parse_cmd()) { + if (!OPT_CHECK(RBX_QUIET)) + printf("failed to parse bootonce " + "command\n"); + exit(0); + } + if (!OPT_CHECK(RBX_QUIET)) + printf("zfs bootonce: %s\n", cmddup); + + if (zfs_get_bootenv(bdev, &benv) == 0) { + nvlist_add_string(benv, OS_BOOTONCE_USED, + cmddup); + zfs_set_bootenv(bdev, benv); + } + /* Do not process this command twice */ + *cmd = 0; + } + } + + /* now make sure we have bdev on all cases */ + free(bdev); + i386_getdev((void **)&bdev, boot_devname, NULL); + + env_setenv("currdev", EV_VOLATILE, boot_devname, gen_setcurrdev, + env_nounset); + + /* Process configuration file */ + auto_boot = 1; + + fd = open(PATH_CONFIG, O_RDONLY); + if (fd == -1) + fd = open(PATH_DOTCONFIG, O_RDONLY); + + if (fd != -1) { + ssize_t cmdlen; + + if ((cmdlen = read(fd, cmd, sizeof(cmd))) > 0) + cmd[cmdlen] = '\0'; + else + *cmd = '\0'; + close(fd); + } + + if (*cmd) { + /* + * Note that parse_cmd() is destructive to cmd[] and we also + * want to honor RBX_QUIET option that could be present in + * cmd[]. + */ + memcpy(cmddup, cmd, sizeof(cmd)); + if (parse_cmd()) + auto_boot = 0; + if (!OPT_CHECK(RBX_QUIET)) + printf("%s: %s\n", PATH_CONFIG, cmddup); + /* Do not process this command twice */ + *cmd = 0; + } + + /* Do not risk waiting at the prompt forever. */ + if (nextboot && !auto_boot) + exit(0); + + if (auto_boot && !*kname) { + /* + * Iterate through the list of loader and kernel paths, + * trying to load. If interrupted by a keypress, or in case of + * failure, drop the user to the boot2 prompt. + */ + for (i = 0; i < nitems(loadpath); i++) { + memcpy(kname, loadpath[i].p, loadpath[i].len); + if (keyhit(3)) + break; + load(); + } + } + + /* Present the user with the boot2 prompt. */ + + for (;;) { + if (!auto_boot || !OPT_CHECK(RBX_QUIET)) { + printf("\nFreeBSD/x86 boot\n"); + printf("Default: %s%s\nboot: ", boot_devname, kname); + } + if (ioctrl & IO_SERIAL) + sio_flush(); + if (!auto_boot || keyhit(5)) + getstr(cmd, sizeof(cmd)); + else if (!auto_boot || !OPT_CHECK(RBX_QUIET)) + putchar('\n'); + auto_boot = 0; + if (parse_cmd()) + putchar('\a'); + else + load(); + } +} + +/* XXX - Needed for btxld to link the boot2 binary; do not remove. */ +void +exit(int x) +{ + __exit(x); +} + +static void +load(void) +{ + union { + struct exec ex; + Elf32_Ehdr eh; + } hdr; + static Elf32_Phdr ep[2]; + static Elf32_Shdr es[2]; + caddr_t p; + uint32_t addr, x; + int fd, fmt, i, j; + ssize_t size; + + if ((fd = open(kname, O_RDONLY)) == -1) { + printf("\nCan't find %s\n", kname); + return; + } + + size = sizeof(hdr); + if (read(fd, &hdr, sizeof (hdr)) != size) { + close(fd); + return; + } + if (N_GETMAGIC(hdr.ex) == ZMAGIC) { + fmt = 0; + } else if (IS_ELF(hdr.eh)) { + fmt = 1; + } else { + printf("Invalid %s\n", "format"); + close(fd); + return; + } + if (fmt == 0) { + addr = hdr.ex.a_entry & 0xffffff; + p = PTOV(addr); + lseek(fd, PAGE_SIZE, SEEK_SET); + size = hdr.ex.a_text; + if (read(fd, p, hdr.ex.a_text) != size) { + close(fd); + return; + } + p += roundup2(hdr.ex.a_text, PAGE_SIZE); + size = hdr.ex.a_data; + if (read(fd, p, hdr.ex.a_data) != size) { + close(fd); + return; + } + p += hdr.ex.a_data + roundup2(hdr.ex.a_bss, PAGE_SIZE); + bootinfo.bi_symtab = VTOP(p); + memcpy(p, &hdr.ex.a_syms, sizeof(hdr.ex.a_syms)); + p += sizeof(hdr.ex.a_syms); + if (hdr.ex.a_syms) { + size = hdr.ex.a_syms; + if (read(fd, p, hdr.ex.a_syms) != size) { + close(fd); + return; + } + p += hdr.ex.a_syms; + size = sizeof (int); + if (read(fd, p, sizeof (int)) != size) { + close(fd); + return; + } + x = *(uint32_t *)p; + p += sizeof(int); + x -= sizeof(int); + size = x; + if (read(fd, p, x) != size) { + close(fd); + return; + } + p += x; + } + } else { + lseek(fd, hdr.eh.e_phoff, SEEK_SET); + for (j = i = 0; i < hdr.eh.e_phnum && j < 2; i++) { + size = sizeof (ep[0]); + if (read(fd, ep + j, sizeof (ep[0])) != size) { + close(fd); + return; + } + if (ep[j].p_type == PT_LOAD) + j++; + } + for (i = 0; i < 2; i++) { + p = PTOV(ep[i].p_paddr & 0xffffff); + lseek(fd, ep[i].p_offset, SEEK_SET); + size = ep[i].p_filesz; + if (read(fd, p, ep[i].p_filesz) != size) { + close(fd); + return; + } + } + p += roundup2(ep[1].p_memsz, PAGE_SIZE); + bootinfo.bi_symtab = VTOP(p); + if (hdr.eh.e_shnum == hdr.eh.e_shstrndx + 3) { + lseek(fd, hdr.eh.e_shoff + + sizeof (es[0]) * (hdr.eh.e_shstrndx + 1), + SEEK_SET); + size = sizeof(es); + if (read(fd, &es, sizeof (es)) != size) { + close(fd); + return; + } + for (i = 0; i < 2; i++) { + memcpy(p, &es[i].sh_size, + sizeof(es[i].sh_size)); + p += sizeof(es[i].sh_size); + lseek(fd, es[i].sh_offset, SEEK_SET); + size = es[i].sh_size; + if (read(fd, p, es[i].sh_size) != size) { + close(fd); + return; + } + p += es[i].sh_size; + } + } + addr = hdr.eh.e_entry & 0xffffff; + } + close(fd); + + bootinfo.bi_esymtab = VTOP(p); + bootinfo.bi_kernelname = VTOP(kname); +#ifdef LOADER_GELI_SUPPORT + explicit_bzero(gelipw, sizeof(gelipw)); +#endif + + if (bdev->dd.d_dev->dv_type == DEVT_ZFS) { + zfsargs.size = sizeof(zfsargs); + zfsargs.pool = bdev->zfs.pool_guid; + zfsargs.root = bdev->zfs.root_guid; +#ifdef LOADER_GELI_SUPPORT + export_geli_boot_data(&zfsargs.gelidata); +#endif + /* + * Note that the zfsargs struct is passed by value, not by + * pointer. Code in btxldr.S copies the values from the entry + * stack to a fixed location within loader(8) at startup due + * to the presence of KARGS_FLAGS_EXTARG. + */ + __exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK), + bootdev, + KARGS_FLAGS_ZFS | KARGS_FLAGS_EXTARG, + (uint32_t)bdev->zfs.pool_guid, + (uint32_t)(bdev->zfs.pool_guid >> 32), + VTOP(&bootinfo), + zfsargs); + } else { +#ifdef LOADER_GELI_SUPPORT + geliargs.size = sizeof(geliargs); + export_geli_boot_data(&geliargs.gelidata); +#endif + + /* + * Note that the geliargs struct is passed by value, not by + * pointer. Code in btxldr.S copies the values from the entry + * stack to a fixed location within loader(8) at startup due + * to the presence of the KARGS_FLAGS_EXTARG flag. + */ + __exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK), + bootdev, +#ifdef LOADER_GELI_SUPPORT + KARGS_FLAGS_GELI | KARGS_FLAGS_EXTARG, 0, 0, + VTOP(&bootinfo), geliargs +#else + 0, 0, 0, VTOP(&bootinfo) +#endif + ); + } +} + +static int +mount_root(char *arg) +{ + char *root; + struct i386_devdesc *ddesc; + uint8_t part; + + if (asprintf(&root, "%s:", arg) < 0) + return (1); + + if (i386_getdev((void **)&ddesc, root, NULL)) { + free(root); + return (1); + } + + /* we should have new device descriptor, free old and replace it. */ + free(bdev); + bdev = ddesc; + if (bdev->dd.d_dev->dv_type == DEVT_DISK) { + if (bdev->disk.d_partition == -1) + part = 0xff; + else + part = bdev->disk.d_partition; + bootdev = MAKEBOOTDEV(dev_maj[bdev->dd.d_dev->dv_type], + bdev->disk.d_slice + 1, bdev->dd.d_unit, part); + bootinfo.bi_bios_dev = bd_unit2bios(bdev); + } + strncpy(boot_devname, root, sizeof (boot_devname)); + setenv("currdev", root, 1); + free(root); + return (0); +} + +static void +fs_list(char *arg) +{ + int fd; + struct dirent *d; + char line[80]; + + fd = open(arg, O_RDONLY); + if (fd < 0) + return; + pager_open(); + while ((d = readdirfd(fd)) != NULL) { + sprintf(line, "%s\n", d->d_name); + if (pager_output(line)) + break; + } + pager_close(); + close(fd); +} + +static int +parse_cmd(void) +{ + char *arg = cmd; + char *ep, *p, *q; + const char *cp; + char line[80]; + int c, i, j; + + while ((c = *arg++)) { + if (c == ' ' || c == '\t' || c == '\n') + continue; + for (p = arg; *p && *p != '\n' && *p != ' ' && *p != '\t'; p++) + ; + ep = p; + if (*p) + *p++ = 0; + if (c == '-') { + while ((c = *arg++)) { + if (c == 'P') { + if (*(uint8_t *)PTOV(0x496) & 0x10) { + cp = "yes"; + } else { + opts |= OPT_SET(RBX_DUAL); + opts |= OPT_SET(RBX_SERIAL); + cp = "no"; + } + printf("Keyboard: %s\n", cp); + continue; + } else if (c == 'S') { + j = 0; + while ((unsigned int) + (i = *arg++ - '0') <= 9) + j = j * 10 + i; + if (j > 0 && i == -'0') { + comspeed = j; + break; + } + /* + * Fall through to error below + * ('S' not in optstr[]). + */ + } + for (i = 0; c != optstr[i]; i++) + if (i == NOPT - 1) + return (-1); + opts ^= OPT_SET(flags[i]); + } + ioctrl = OPT_CHECK(RBX_DUAL) ? (IO_SERIAL|IO_KEYBOARD) : + OPT_CHECK(RBX_SERIAL) ? IO_SERIAL : IO_KEYBOARD; + if (ioctrl & IO_SERIAL) { + if (sio_init(115200 / comspeed) != 0) + ioctrl &= ~IO_SERIAL; + } + } if (c == '?') { + printf("\n"); + if (*arg == '\0') + arg = (char *)"/"; + fs_list(arg); + zfs_list(arg); + return (-1); + } else { + char *ptr; + printf("\n"); + arg--; + + /* + * Report pool status if the comment is 'status'. Lets + * hope no-one wants to load /status as a kernel. + */ + if (strcmp(arg, "status") == 0) { + pager_open(); + for (i = 0; devsw[i] != NULL; i++) { + if (devsw[i]->dv_print != NULL) { + if (devsw[i]->dv_print(1)) + break; + } else { + snprintf(line, sizeof(line), + "%s: (unknown)\n", + devsw[i]->dv_name); + if (pager_output(line)) + break; + } + } + pager_close(); + return (-1); + } + + /* + * If there is "zfs:" prefix simply ignore it. + */ + ptr = arg; + if (strncmp(ptr, "zfs:", 4) == 0) + ptr += 4; + + /* + * If there is a colon, switch pools. + */ + q = strchr(ptr, ':'); + if (q) { + *q++ = '\0'; + if (mount_root(arg) != 0) { + return (-1); + } + arg = q; + } + if ((i = ep - arg)) { + if ((size_t)i >= sizeof(kname)) + return (-1); + memcpy(kname, arg, i + 1); + } + } + arg = p; + } + return (0); +} + +/* + * Probe all disks to discover ZFS pools. The idea is to walk all possible + * disk devices, however, we also need to identify possible boot pool. + * For boot pool detection we have boot disk passed us from BIOS, recorded + * in bootinfo.bi_bios_dev. + */ +static void +i386_zfs_probe(void) +{ + char devname[32]; + int boot_unit; + struct i386_devdesc dev; + uint64_t pool_guid = 0; + + dev.dd.d_dev = &bioshd; + /* Translate bios dev to our unit number. */ + boot_unit = bd_bios2unit(bootinfo.bi_bios_dev); + + /* + * Open all the disks we can find and see if we can reconstruct + * ZFS pools from them. + */ + for (dev.dd.d_unit = 0; bd_unit2bios(&dev) >= 0; dev.dd.d_unit++) { + snprintf(devname, sizeof (devname), "%s%d:", bioshd.dv_name, + dev.dd.d_unit); + /* If this is not boot disk, use generic probe. */ + if (dev.dd.d_unit != boot_unit) + zfs_probe_dev(devname, NULL, true); + else + zfs_probe_dev(devname, &pool_guid, true); + + if (pool_guid != 0 && bdev == NULL) { + bdev = malloc(sizeof (struct i386_devdesc)); + bzero(bdev, sizeof (struct i386_devdesc)); + bdev->zfs.dd.d_dev = &zfs_dev; + bdev->zfs.pool_guid = pool_guid; + } + } +} |