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/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) { @@ -290,6 +324,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); } @@ -493,6 +531,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); } @@ -509,6 +549,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) @@ -525,6 +567,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); @@ -583,6 +627,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); @@ -613,6 +659,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/loader/main.c =================================================================== --- stand/efi/loader/main.c +++ stand/efi/loader/main.c @@ -74,6 +74,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) @@ -165,20 +174,62 @@ } 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; - devname = efi_fmtdev(&currdev); - env_setenv("currdev", EV_VOLATILE, devname, efi_setcurrdev, - env_nounset); - env_setenv("loaddev", EV_VOLATILE, devname, env_noset, env_nounset); + 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/laoder.conf", &st) == 0); } static int @@ -194,90 +245,62 @@ 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; + set_currdev_devdesc((struct devdesc *)&currdev); 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 (sanity_check_currdev()) + 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) { + set_currdev_pdinfo(dp); + if (sanity_check_currdev()) + 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; @@ -291,8 +314,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); @@ -306,6 +330,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", + now - then + fail_timeout); + 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[]) { @@ -314,6 +365,7 @@ int i, j, vargood, howto; UINTN k; int has_kbd; + char *s; #if !defined(__arm__) char buf[40]; #endif @@ -352,12 +404,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++) { @@ -437,6 +492,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); @@ -451,6 +509,9 @@ return (EFI_BUFFER_TOO_SMALL); } + if ((s = getenv("fail_timeout")) != NULL) + fail_timeout = strtol(s, NULL, 10); + /* * March through the device switch probing for things. */ @@ -482,8 +543,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 */ @@ -887,7 +956,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/net.h =================================================================== --- stand/libsa/net.h +++ stand/libsa/net.h @@ -128,6 +128,4 @@ char *intoa(n_long); /* similar to inet_ntoa */ n_long inet_addr(char *); -/* Machine-dependent functions: */ -time_t getsecs(void); #endif /* ! _STAND_NET_H */ Index: stand/libsa/stand.h =================================================================== --- stand/libsa/stand.h +++ stand/libsa/stand.h @@ -137,6 +137,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); @@ -162,13 +168,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; }; @@ -394,6 +393,7 @@ extern int devopen(struct open_file *, const char *, const char **); extern int devclose(struct open_file *f); extern void panic(const char *, ...) __dead2 __printflike(1, 2); +extern time_t getsecs(void); extern struct fs_ops *file_system[]; extern struct fs_ops *exclusive_file_system; extern struct devsw *devsw[];