Index: stand/efi/include/efilib.h =================================================================== --- stand/efi/include/efilib.h +++ stand/efi/include/efilib.h @@ -59,10 +59,13 @@ 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); Index: stand/efi/include/efizfs.h =================================================================== --- stand/efi/include/efizfs.h +++ stand/efi/include/efizfs.h @@ -48,6 +48,7 @@ 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 Index: stand/efi/libefi/devicename.c =================================================================== --- stand/efi/libefi/devicename.c +++ stand/efi/libefi/devicename.c @@ -161,7 +161,6 @@ } idev->d_dev = dv; - idev->d_type = dv->dv_type; if (dev != NULL) *dev = idev; @@ -180,7 +179,7 @@ struct devdesc *dev = (struct devdesc *)vdev; static char buf[SPECNAMELEN + 1]; - switch(dev->d_type) { + switch(dev->d_dev->dv_type) { case DEVT_NONE: strcpy(buf, "(no device)"); break; Index: stand/efi/libefi/efipart.c =================================================================== --- stand/efi/libefi/efipart.c +++ stand/efi/libefi/efipart.c @@ -136,6 +136,40 @@ 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) { @@ -294,6 +328,8 @@ 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); } @@ -364,6 +400,8 @@ 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); } @@ -489,6 +527,8 @@ 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); } @@ -505,6 +545,8 @@ 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) @@ -521,6 +563,8 @@ 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); @@ -579,6 +623,8 @@ 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); @@ -609,6 +655,8 @@ 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); Index: stand/efi/libefi/efizfs.c =================================================================== --- stand/efi/libefi/efizfs.c +++ stand/efi/libefi/efizfs.c @@ -65,6 +65,22 @@ return (NULL); } +bool +efizfs_get_guid_by_handle(EFI_HANDLE handle, uint64_t *guid) +{ + zfsinfo_t *zi; + + if (guid == NULL) + return (1); + 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) { Index: stand/efi/loader/main.c =================================================================== --- stand/efi/loader/main.c +++ stand/efi/loader/main.c @@ -78,6 +78,15 @@ 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; + #ifdef EFI_ZFS_BOOT bool efi_zfs_is_preferred(EFI_HANDLE *h) @@ -169,119 +178,169 @@ } static void -set_devdesc_currdev(struct devsw *dev, int unit) +set_currdev_devdesc(struct devdesc *currdev) { - struct devdesc currdev; char *devname; + devname = efi_fmtdev(currdev); + + env_setenv("currdev", EV_VOLATILE, devname, efi_setcurrdev, env_nounset); + env_setenv("loaddev", EV_VOLATILE, devname, env_noset, env_nounset); +} + +static void +set_currdev_devsw(struct devsw *dev, int unit) +{ + struct devdesc currdev; + currdev.d_dev = dev; - currdev.d_type = currdev.d_dev->dv_type; currdev.d_unit = unit; currdev.d_opendata = NULL; + + 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.d_dev = dp->pd_devsw; + currdev.d_opendata = NULL; + currdev.d_partition = -1; + if (dp->pd_parent == NULL) { + currdev.d_unit = dp->pd_unit; + currdev.d_slice = -1; + } else { + currdev.d_unit = dp->pd_parent->pd_unit; + currdev.d_slice = dp->pd_unit; + } + 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); +} + +#ifdef EFI_ZFS_BOOT +static bool +probe_zfs_currdev(uint64_t guid) +{ + char *devname; + struct zfs_devdesc currdev; + + currdev.d_dev = &zfs_dev; + currdev.d_unit = 0; + currdev.d_opendata = NULL; + currdev.pool_guid = guid; + currdev.root_guid = 0; + set_currdev_devdesc((struct devdesc *)&currdev); devname = efi_fmtdev(&currdev); + init_zfs_bootenv(devname); - env_setenv("currdev", EV_VOLATILE, devname, efi_setcurrdev, - env_nounset); - env_setenv("loaddev", EV_VOLATILE, devname, env_noset, env_nounset); + return (sanity_check_currdev()); +} +#endif + +static bool +try_as_currdev(pdinfo_t *hd, pdinfo_t *pp) +{ + uint64_t guid; + +#ifdef EFI_ZFS_BOOT + /* + * 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)); +#endif + /* + * All other filesystems just need the pdinfo + * initialized in the standard way. + */ + set_currdev_pdinfo(pp); + return (sanity_check_currdev()); } static int 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; struct devsw *dev; int unit; uint64_t extra; #ifdef EFI_ZFS_BOOT - /* 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. This may change in the future, however, + * if we allow specifying which pool to boot from via UEFI variables + * rather than the bootenv stuff that FreeBSD uses today. + */ if (pool_guid != 0) { - struct zfs_devdesc currdev; - - currdev.d_dev = &zfs_dev; - currdev.d_unit = 0; - currdev.d_type = currdev.d_dev->dv_type; - currdev.d_opendata = NULL; - 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); - init_zfs_bootenv(devname); - return (0); + if (probe_zfs_currdev(pool_guid)) + return (0); } #endif /* EFI_ZFS_BOOT */ - /* 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.d_dev = &efipart_hddev; - currdev.d_type = currdev.d_dev->dv_type; - currdev.d_unit = dp->pd_unit; - currdev.d_opendata = NULL; - 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); + /* + * 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) { + set_currdev_pdinfo(dp); + if (sanity_check_currdev()) return (0); - } - /* 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); + if (dp->pd_parent != NULL) { + dp = dp->pd_parent; + STAILQ_FOREACH(pp, &dp->pd_part, pd_link) { + /* + * Roll up the ZFS special case + * for those partitions that have + * zpools on them + */ + if (try_as_currdev(dp, pp)) + return (0); } } } - 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); - } - } - /* * 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 (0); } copy = NULL; @@ -295,8 +354,9 @@ 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 (0); } devpath = efi_lookup_devpath(h); @@ -310,6 +370,33 @@ return (ENOENT); } +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[]) { @@ -318,6 +405,7 @@ int i, j, vargood, howto; UINTN k; int has_kbd; + char *s; #if !defined(__arm__) char buf[40]; #endif @@ -356,12 +444,15 @@ /* * 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++) { @@ -441,6 +532,9 @@ 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", "comconsole efi" , 1); @@ -455,6 +549,9 @@ return (EFI_BUFFER_TOO_SMALL); } + if ((s = getenv("fail_timeout")) != NULL) + fail_timeout = strtol(s, NULL, 10); + /* * Scan the BLOCK IO MEDIA handles then * march through the device switch probing for things. @@ -490,8 +587,16 @@ */ BS->SetWatchdogTimer(0, 0, 0, NULL); + /* + * 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) != 0) - return (EFI_NOT_FOUND); + if (!interactive_interrupt("Failed to find bootable partition")) + return (EFI_NOT_FOUND); efi_init_environment(); setenv("LINES", "24", 1); /* optional */ @@ -848,7 +953,7 @@ struct disk_devdesc *d_dev; pdinfo_t *hd, *pd; - switch (dev->d_type) { + switch (dev->d_dev->dv_type) { #ifdef EFI_ZFS_BOOT case DEVT_ZFS: z_dev = (struct zfs_devdesc *)dev; Index: stand/libsa/stand.h =================================================================== --- stand/libsa/stand.h +++ stand/libsa/stand.h @@ -138,6 +138,12 @@ struct devsw { const char dv_name[8]; int dv_type; /* opaque type constant, arch-dependant */ +#define DEVT_NONE 0 +#define DEVT_DISK 1 +#define DEVT_NET 2 +#define DEVT_CD 3 +#define DEVT_ZFS 4 +#define DEVT_FD 5 int (*dv_init)(void); /* early probe call */ int (*dv_strategy)(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize); @@ -163,13 +169,6 @@ struct devdesc { struct devsw *d_dev; - int d_type; -#define DEVT_NONE 0 -#define DEVT_DISK 1 -#define DEVT_NET 2 -#define DEVT_CD 3 -#define DEVT_ZFS 4 -#define DEVT_FD 5 int d_unit; void *d_opendata; }; Index: stand/zfs/zfs.c =================================================================== --- stand/zfs/zfs.c +++ stand/zfs/zfs.c @@ -701,7 +701,7 @@ spa_t *spa; buf[0] = '\0'; - if (dev->d_type != DEVT_ZFS) + if (dev->d_dev->dv_type != DEVT_ZFS) return (buf); if (dev->pool_guid == 0) {