diff options
author | Toomas Soome <tsoome@me.com> | 2018-03-08 12:37:51 +0200 |
---|---|---|
committer | Dan McDonald <danmcd@joyent.com> | 2018-12-04 19:32:46 -0500 |
commit | e3026534c008abc0cbdcccf8cff6ac302fac6b52 (patch) | |
tree | 0890ec92589df5cfe139e5a0f14722e20069fcd4 | |
parent | 3e90f8d31de34a9f1efdf9484f999cca7149860d (diff) | |
download | illumos-joyent-e3026534c008abc0cbdcccf8cff6ac302fac6b52.tar.gz |
9988 Make loader.efi dual boot, step 1
Reviewed by: C Fraire <cfraire@me.com>
Reviewed by: John Levon <john.levon@joyent.com>
Approved by: Dan McDonald <danmcd@joyent.com>
-rw-r--r-- | usr/src/boot/sys/boot/efi/include/efilib.h | 3 | ||||
-rw-r--r-- | usr/src/boot/sys/boot/efi/include/efizfs.h | 1 | ||||
-rw-r--r-- | usr/src/boot/sys/boot/efi/libefi/efipart.c | 51 | ||||
-rw-r--r-- | usr/src/boot/sys/boot/efi/libefi/efizfs.c | 16 | ||||
-rw-r--r-- | usr/src/boot/sys/boot/efi/loader/main.c | 382 |
5 files changed, 369 insertions, 84 deletions
diff --git a/usr/src/boot/sys/boot/efi/include/efilib.h b/usr/src/boot/sys/boot/efi/include/efilib.h index d477bf6f1b..db2a238515 100644 --- a/usr/src/boot/sys/boot/efi/include/efilib.h +++ b/usr/src/boot/sys/boot/efi/include/efilib.h @@ -57,10 +57,13 @@ typedef struct pdinfo uint32_t pd_unit; /* unit number */ uint32_t pd_open; /* reference counter */ void *pd_bcache; /* buffer cache data */ + struct pdinfo *pd_parent; /* Linked items (eg partitions) */ + struct devsw *pd_devsw; /* Back pointer to devsw */ } pdinfo_t; pdinfo_list_t *efiblk_get_pdinfo_list(struct devsw *dev); pdinfo_t *efiblk_get_pdinfo(struct devdesc *dev); +pdinfo_t *efiblk_get_pdinfo_by_handle(EFI_HANDLE h); void *efi_get_table(EFI_GUID *tbl); diff --git a/usr/src/boot/sys/boot/efi/include/efizfs.h b/usr/src/boot/sys/boot/efi/include/efizfs.h index 636951c0d6..4fc0ff551f 100644 --- a/usr/src/boot/sys/boot/efi/include/efizfs.h +++ b/usr/src/boot/sys/boot/efi/include/efizfs.h @@ -44,5 +44,6 @@ extern void efi_zfs_probe(void); extern zfsinfo_list_t *efizfs_get_zfsinfo_list(void); extern bool efi_zfs_is_preferred(EFI_HANDLE *h); extern EFI_HANDLE efizfs_get_handle_by_guid(uint64_t); +extern bool efizfs_get_guid_by_handle(EFI_HANDLE, uint64_t *); #endif diff --git a/usr/src/boot/sys/boot/efi/libefi/efipart.c b/usr/src/boot/sys/boot/efi/libefi/efipart.c index e272f5db6f..7a277d3c07 100644 --- a/usr/src/boot/sys/boot/efi/libefi/efipart.c +++ b/usr/src/boot/sys/boot/efi/libefi/efipart.c @@ -1,4 +1,4 @@ -/*- +/* * Copyright (c) 2010 Marcel Moolenaar * All rights reserved. * @@ -118,6 +118,7 @@ efiblk_get_pdinfo_list(struct devsw *dev) return (NULL); } +/* XXX this gets called way way too often, investigate */ pdinfo_t * efiblk_get_pdinfo(struct devdesc *dev) { @@ -135,6 +136,40 @@ efiblk_get_pdinfo(struct devdesc *dev) return (pd); } +static bool +same_handle(pdinfo_t *pd, EFI_HANDLE h) +{ + + return (pd->pd_handle == h || pd->pd_alias == h); +} + +pdinfo_t * +efiblk_get_pdinfo_by_handle(EFI_HANDLE h) +{ + pdinfo_t *dp, *pp; + + /* + * Check hard disks, then cd, then floppy + */ + STAILQ_FOREACH(dp, &hdinfo, pd_link) { + if (same_handle(dp, h)) + return (dp); + STAILQ_FOREACH(pp, &dp->pd_part, pd_link) { + if (same_handle(pp, h)) + return (pp); + } + } + STAILQ_FOREACH(dp, &cdinfo, pd_link) { + if (same_handle(dp, h)) + return (dp); + } + STAILQ_FOREACH(dp, &fdinfo, pd_link) { + if (same_handle(dp, h)) + return (dp); + } + return (NULL); +} + static int efiblk_pdinfo_count(pdinfo_list_t *pdi) { @@ -293,6 +328,8 @@ efipart_fdinfo_add(EFI_HANDLE handle, uint32_t uid, EFI_DEVICE_PATH *devpath) fd->pd_unit = uid; fd->pd_handle = handle; fd->pd_devpath = devpath; + fd->pd_parent = NULL; + fd->pd_devsw = &efipart_fddev; STAILQ_INSERT_TAIL(&fdinfo, fd, pd_link); return (0); } @@ -363,6 +400,8 @@ efipart_cdinfo_add(EFI_HANDLE handle, EFI_HANDLE alias, cd->pd_unit = unit; cd->pd_alias = alias; cd->pd_devpath = devpath; + cd->pd_parent = NULL; + cd->pd_devsw = &efipart_cddev; STAILQ_INSERT_TAIL(&cdinfo, cd, pd_link); return (0); } @@ -488,6 +527,8 @@ efipart_hdinfo_add(EFI_HANDLE disk_handle, EFI_HANDLE part_handle) pd->pd_handle = part_handle; pd->pd_unit = node->PartitionNumber; pd->pd_devpath = part_devpath; + pd->pd_parent = hd; + pd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&hd->pd_part, pd, pd_link); return (0); } @@ -504,6 +545,8 @@ efipart_hdinfo_add(EFI_HANDLE disk_handle, EFI_HANDLE part_handle) hd->pd_handle = disk_handle; hd->pd_unit = unit; hd->pd_devpath = disk_devpath; + hd->pd_parent = NULL; + hd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&hdinfo, hd, pd_link); if (part_devpath == NULL) @@ -520,6 +563,8 @@ efipart_hdinfo_add(EFI_HANDLE disk_handle, EFI_HANDLE part_handle) pd->pd_handle = part_handle; pd->pd_unit = node->PartitionNumber; pd->pd_devpath = part_devpath; + pd->pd_parent = hd; + pd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&hd->pd_part, pd, pd_link); return (0); @@ -578,6 +623,8 @@ efipart_hdinfo_add_filepath(EFI_HANDLE disk_handle) pd->pd_handle = disk_handle; pd->pd_unit = unit; pd->pd_devpath = devpath; + pd->pd_parent = NULL; + pd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&hdinfo, pd, pd_link); free(pathname); return (0); @@ -608,6 +655,8 @@ efipart_hdinfo_add_filepath(EFI_HANDLE disk_handle) pd->pd_handle = disk_handle; pd->pd_unit = unit; pd->pd_devpath = devpath; + pd->pd_parent = last; + pd->pd_devsw = &efipart_hddev; STAILQ_INSERT_TAIL(&last->pd_part, pd, pd_link); free(pathname); return (0); diff --git a/usr/src/boot/sys/boot/efi/libefi/efizfs.c b/usr/src/boot/sys/boot/efi/libefi/efizfs.c index cbe4bc1809..570a97ecfa 100644 --- a/usr/src/boot/sys/boot/efi/libefi/efizfs.c +++ b/usr/src/boot/sys/boot/efi/libefi/efizfs.c @@ -61,6 +61,22 @@ efizfs_get_handle_by_guid(uint64_t guid) return (NULL); } +bool +efizfs_get_guid_by_handle(EFI_HANDLE handle, uint64_t *guid) +{ + zfsinfo_t *zi; + + if (guid == NULL) + return (false); + STAILQ_FOREACH(zi, &zfsinfo, zi_link) { + if (zi->zi_handle == handle) { + *guid = zi->zi_pool_guid; + return (true); + } + } + return (false); +} + static void insert_zfs(EFI_HANDLE handle, uint64_t guid) { diff --git a/usr/src/boot/sys/boot/efi/loader/main.c b/usr/src/boot/sys/boot/efi/loader/main.c index 4f19452b50..dabde127b1 100644 --- a/usr/src/boot/sys/boot/efi/loader/main.c +++ b/usr/src/boot/sys/boot/efi/loader/main.c @@ -64,10 +64,77 @@ extern void efi_getsmap(void); static EFI_LOADED_IMAGE *img; +/* + * Number of seconds to wait for a keystroke before exiting with failure + * in the event no currdev is found. -2 means always break, -1 means + * never break, 0 means poll once and then reboot, > 0 means wait for + * that many seconds. "fail_timeout" can be set in the environment as + * well. + */ +static int fail_timeout = 5; + bool efi_zfs_is_preferred(EFI_HANDLE *h) { - return (h == img->DeviceHandle); + EFI_DEVICE_PATH *devpath, *dp, *node; + HARDDRIVE_DEVICE_PATH *hd; + bool ret; + extern UINT64 start_sector; /* from multiboot.S */ + + /* This check is true for chainloader case. */ + if (h == img->DeviceHandle) + return (true); + + /* + * Make sure the image was loaded from the hard disk. + */ + devpath = efi_lookup_devpath(img->DeviceHandle); + if (devpath == NULL) + return (false); + node = efi_devpath_last_node(devpath); + if (node == NULL) + return (false); + if (DevicePathType(node) != MEDIA_DEVICE_PATH && + (DevicePathSubType(node) != MEDIA_FILEPATH_DP || + DevicePathSubType(node) != MEDIA_HARDDRIVE_DP)) { + return (false); + } + + /* + * XXX We ignore the MEDIA_FILEPATH_DP here for now as it is + * used on arm and we do not support arm. + */ + ret = false; + dp = efi_devpath_trim(devpath); + devpath = NULL; + if (dp == NULL) + goto done; + + devpath = efi_lookup_devpath(h); + if (devpath == NULL) + goto done; + hd = (HARDDRIVE_DEVICE_PATH *)efi_devpath_last_node(devpath); + if (hd == NULL) { + devpath = NULL; + goto done; + } + devpath = efi_devpath_trim(devpath); + if (devpath == NULL) + goto done; + + if (!efi_devpath_match(dp, devpath)) + goto done; + + /* It is the same disk, do we have partition start? */ + if (start_sector == 0) + ret = true; + else if (start_sector == hd->PartitionStart) + ret = true; + +done: + free(dp); + free(devpath); + return (ret); } static bool @@ -153,110 +220,174 @@ out: } static void -set_devdesc_currdev(struct devsw *dev, int unit) +set_currdev_devdesc(struct devdesc *currdev) { - struct devdesc currdev; char *devname; - currdev.d_dev = dev; - currdev.d_unit = unit; - devname = efi_fmtdev(&currdev); + devname = efi_fmtdev(currdev); + + printf("Setting currdev to %s\n", devname); env_setenv("currdev", EV_VOLATILE, devname, efi_setcurrdev, env_nounset); env_setenv("loaddev", EV_VOLATILE, devname, env_noset, env_nounset); } -static int +static void +set_currdev_devsw(struct devsw *dev, int unit) +{ + struct devdesc currdev; + + currdev.d_dev = dev; + currdev.d_unit = unit; + + set_currdev_devdesc(&currdev); +} + +static void +set_currdev_pdinfo(pdinfo_t *dp) +{ + + /* + * Disks are special: they have partitions. if the parent + * pointer is non-null, we're a partition not a full disk + * and we need to adjust currdev appropriately. + */ + if (dp->pd_devsw->dv_type == DEVT_DISK) { + struct disk_devdesc currdev; + + currdev.dd.d_dev = dp->pd_devsw; + if (dp->pd_parent == NULL) { + currdev.dd.d_unit = dp->pd_unit; + currdev.d_slice = -1; + currdev.d_partition = -1; + } else { + currdev.dd.d_unit = dp->pd_parent->pd_unit; + currdev.d_slice = dp->pd_unit; + currdev.d_partition = 255; /* Assumes GPT */ + } + set_currdev_devdesc((struct devdesc *)&currdev); + } else { + set_currdev_devsw(dp->pd_devsw, dp->pd_unit); + } +} + +static bool +sanity_check_currdev(void) +{ + struct stat st; + + return (stat("/boot/defaults/loader.conf", &st) == 0); +} + +static bool +probe_zfs_currdev(uint64_t guid) +{ + struct zfs_devdesc currdev; + + currdev.dd.d_dev = &zfs_dev; + currdev.dd.d_unit = 0; + currdev.pool_guid = guid; + currdev.root_guid = 0; + set_currdev_devdesc((struct devdesc *)&currdev); + + return (sanity_check_currdev()); +} + +static bool +try_as_currdev(pdinfo_t *pp) +{ + uint64_t guid; + + /* + * If there's a zpool on this device, try it as a ZFS + * filesystem, which has somewhat different setup than all + * other types of fs due to imperfect loader integration. + * This all stems from ZFS being both a device (zpool) and + * a filesystem, plus the boot env feature. + */ + if (efizfs_get_guid_by_handle(pp->pd_handle, &guid)) + return (probe_zfs_currdev(guid)); + + /* + * All other filesystems just need the pdinfo + * initialized in the standard way. + */ + set_currdev_pdinfo(pp); + return (sanity_check_currdev()); +} + +static bool find_currdev(EFI_LOADED_IMAGE *img) { - pdinfo_list_t *pdi_list; pdinfo_t *dp, *pp; EFI_DEVICE_PATH *devpath, *copy; EFI_HANDLE h; - char *devname; + CHAR16 *text; struct devsw *dev; int unit; uint64_t extra; - /* Did efi_zfs_probe() detect the boot pool? */ + /* + * Did efi_zfs_probe() detect the boot pool? If so, use the zpool + * it found, if it's sane. ZFS is the only thing that looks for + * disks and pools to boot. + */ if (pool_guid != 0) { - struct zfs_devdesc currdev; - - currdev.dd.d_dev = &zfs_dev; - currdev.dd.d_unit = 0; - currdev.pool_guid = pool_guid; - currdev.root_guid = 0; - devname = efi_fmtdev(&currdev); - - env_setenv("currdev", EV_VOLATILE, devname, efi_setcurrdev, - env_nounset); - env_setenv("loaddev", EV_VOLATILE, devname, env_noset, - env_nounset); - return (0); + printf("Trying ZFS pool\n"); + if (probe_zfs_currdev(pool_guid)) + return (true); } - /* We have device lists for hd, cd, fd, walk them all. */ - pdi_list = efiblk_get_pdinfo_list(&efipart_hddev); - STAILQ_FOREACH(dp, pdi_list, pd_link) { - struct disk_devdesc currdev; - - currdev.dd.d_dev = &efipart_hddev; - currdev.dd.d_unit = dp->pd_unit; - currdev.d_slice = -1; - currdev.d_partition = -1; - - if (dp->pd_handle == img->DeviceHandle) { - devname = efi_fmtdev(&currdev); - - env_setenv("currdev", EV_VOLATILE, devname, - efi_setcurrdev, env_nounset); - env_setenv("loaddev", EV_VOLATILE, devname, - env_noset, env_nounset); - return (0); + /* + * Try to find the block device by its handle based on the + * image we're booting. If we can't find a sane partition, + * search all the other partitions of the disk. We do not + * search other disks because it's a violation of the UEFI + * boot protocol to do so. We fail and let UEFI go on to + * the next candidate. + */ + dp = efiblk_get_pdinfo_by_handle(img->DeviceHandle); + if (dp != NULL) { + text = efi_devpath_name(dp->pd_devpath); + if (text != NULL) { + printf("Trying ESP: %S\n", text); + efi_free_devpath_name(text); } - /* Assuming GPT partitioning. */ - STAILQ_FOREACH(pp, &dp->pd_part, pd_link) { - if (pp->pd_handle == img->DeviceHandle) { - currdev.d_slice = pp->pd_unit; - currdev.d_partition = 255; - devname = efi_fmtdev(&currdev); - - env_setenv("currdev", EV_VOLATILE, devname, - efi_setcurrdev, env_nounset); - env_setenv("loaddev", EV_VOLATILE, devname, - env_noset, env_nounset); - return (0); + set_currdev_pdinfo(dp); + if (sanity_check_currdev()) + return (true); + if (dp->pd_parent != NULL) { + dp = dp->pd_parent; + STAILQ_FOREACH(pp, &dp->pd_part, pd_link) { + text = efi_devpath_name(pp->pd_devpath); + if (text != NULL) { + printf("And now the part: %S\n", text); + efi_free_devpath_name(text); + } + /* + * Roll up the ZFS special case + * for those partitions that have + * zpools on them + */ + if (try_as_currdev(pp)) + return (true); } } - } - - pdi_list = efiblk_get_pdinfo_list(&efipart_cddev); - STAILQ_FOREACH(dp, pdi_list, pd_link) { - if (dp->pd_handle == img->DeviceHandle || - dp->pd_alias == img->DeviceHandle) { - set_devdesc_currdev(&efipart_cddev, dp->pd_unit); - return (0); - } - } - - pdi_list = efiblk_get_pdinfo_list(&efipart_fddev); - STAILQ_FOREACH(dp, pdi_list, pd_link) { - if (dp->pd_handle == img->DeviceHandle) { - set_devdesc_currdev(&efipart_fddev, dp->pd_unit); - return (0); - } + } else { + printf("Can't find device by handle\n"); } /* * Try the device handle from our loaded image first. If that * fails, use the device path from the loaded image and see if * any of the nodes in that path match one of the enumerated - * handles. + * handles. Currently, this handle list is only for netboot. */ if (efi_handle_lookup(img->DeviceHandle, &dev, &unit, &extra) == 0) { - set_devdesc_currdev(dev, unit); - return (0); + set_currdev_devsw(dev, unit); + if (sanity_check_currdev()) + return (true); } copy = NULL; @@ -270,8 +401,9 @@ find_currdev(EFI_LOADED_IMAGE *img) copy = NULL; if (efi_handle_lookup(h, &dev, &unit, &extra) == 0) { - set_devdesc_currdev(dev, unit); - return (0); + set_currdev_devsw(dev, unit); + if (sanity_check_currdev()) + return (true); } devpath = efi_lookup_devpath(h); @@ -282,19 +414,51 @@ find_currdev(EFI_LOADED_IMAGE *img) } free(copy); - return (ENOENT); + return (false); +} + +static bool +interactive_interrupt(const char *msg) +{ + time_t now, then, last; + + last = 0; + now = then = getsecs(); + printf("%s\n", msg); + if (fail_timeout == -2) /* Always break to OK */ + return (true); + if (fail_timeout == -1) /* Never break to OK */ + return (false); + do { + if (last != now) { + printf("press any key to interrupt reboot in %d seconds\r", + fail_timeout - (int)(now - then)); + last = now; + } + + /* XXX no pause or timeout wait for char */ + if (ischar()) + return (true); + now = getsecs(); + } while (now - then < fail_timeout); + return (false); } EFI_STATUS main(int argc, CHAR16 *argv[]) { char var[128]; - EFI_GUID *guid; int i, j, howto; bool vargood; void *ptr; - UINTN k; bool has_kbd; + char *s; + EFI_DEVICE_PATH *imgpath; + CHAR16 *text; + EFI_STATUS status; + UINT16 boot_current; + size_t sz; + UINT16 boot_order[100]; archsw.arch_autoload = efi_autoload; archsw.arch_getdev = efi_getdev; @@ -331,12 +495,15 @@ main(int argc, CHAR16 *argv[]) /* * Parse the args to set the console settings, etc * boot1.efi passes these in, if it can read /boot.config or /boot/config - * or iPXE may be setup to pass these in. + * or iPXE may be setup to pass these in. Or the optional argument in the + * boot environment was used to pass these arguments in (in which case + * neither /boot.config nor /boot/config are consulted). * * Loop through the args, and for each one that contains an '=' that is * not the first character, add it to the environment. This allows * loader and kernel env vars to be passed on the command line. Convert - * args from UCS-2 to ASCII (16 to 8 bit) as they are copied. + * args from UCS-2 to ASCII (16 to 8 bit) as they are copied (though this + * method is flawed for non-ASCII characters). */ howto = 0; for (i = 1; i < argc; i++) { @@ -418,6 +585,11 @@ main(int argc, CHAR16 *argv[]) for (i = 0; howto_names[i].ev != NULL; i++) if (howto & howto_names[i].mask) setenv(howto_names[i].ev, "YES", 1); + + /* + * XXX we need fallback to this stuff after looking at the ConIn, + * ConOut and ConErr variables. + */ if (howto & RB_MULTIPLE) { if (howto & RB_SERIAL) setenv("console", "ttya text" , 1); @@ -425,7 +597,11 @@ main(int argc, CHAR16 *argv[]) setenv("console", "text ttya" , 1); } else if (howto & RB_SERIAL) { setenv("console", "ttya" , 1); - } + } else + setenv("console", "text" , 1); + + if ((s = getenv("fail_timeout")) != NULL) + fail_timeout = strtol(s, NULL, 10); /* * Scan the BLOCK IO MEDIA handles then @@ -452,6 +628,38 @@ main(int argc, CHAR16 *argv[]) printf("\n%s", bootprog_info); + /* Determine the devpath of our image so we can prefer it. */ + text = efi_devpath_name(img->FilePath); + if (text != NULL) { + printf(" Load Path: %S\n", text); + efi_setenv_illumos_wcs("LoaderPath", text); + efi_free_devpath_name(text); + } + + status = BS->HandleProtocol(img->DeviceHandle, &devid, + (void **)&imgpath); + if (status == EFI_SUCCESS) { + text = efi_devpath_name(imgpath); + if (text != NULL) { + printf(" Load Device: %S\n", text); + efi_setenv_illumos_wcs("LoaderDev", text); + efi_free_devpath_name(text); + } + } + + boot_current = 0; + sz = sizeof(boot_current); + efi_global_getenv("BootCurrent", &boot_current, &sz); + printf(" BootCurrent: %04x\n", boot_current); + + sz = sizeof(boot_order); + efi_global_getenv("BootOrder", &boot_order, &sz); + printf(" BootOrder:"); + for (i = 0; i < sz / sizeof(boot_order[0]); i++) + printf(" %04x%s", boot_order[i], + boot_order[i] == boot_current ? "[*]" : ""); + printf("\n"); + /* * Disable the watchdog timer. By default the boot manager sets * the timer to 5 minutes before invoking a boot option. If we @@ -463,8 +671,16 @@ main(int argc, CHAR16 *argv[]) */ BS->SetWatchdogTimer(0, 0, 0, NULL); - if (find_currdev(img) != 0) - return (EFI_NOT_FOUND); + /* + * Try and find a good currdev based on the image that was booted. + * It might be desirable here to have a short pause to allow falling + * through to the boot loader instead of returning instantly to follow + * the boot protocol and also allow an escape hatch for users wishing + * to try something different. + */ + if (!find_currdev(img)) + if (!interactive_interrupt("Failed to find bootable partition")) + return (EFI_NOT_FOUND); efi_init_environment(); setenv("ISADIR", "amd64", 1); /* we only build 64bit */ |