svn commit: r332416 - in head/stand/efi: include libefi loader

Warner Losh imp at FreeBSD.org
Wed Apr 11 19:46:25 UTC 2018


Author: imp
Date: Wed Apr 11 19:46:24 2018
New Revision: 332416
URL: https://svnweb.freebsd.org/changeset/base/332416

Log:
  Refactor currdev setting
  
  Refactor the currdev setting to find the device we booted from. Limit
  searching when we don't already have a reasonable currdev from that to
  the same device only. Search a little harder for ZFS volumes as that's
  needed for loader.efi to live on an ESP.
  
  Sponsored by: Netflix
  Differential Review: https://reviews.freebsd.org/D13784

Modified:
  head/stand/efi/include/efilib.h
  head/stand/efi/include/efizfs.h
  head/stand/efi/libefi/efipart.c
  head/stand/efi/libefi/efizfs.c
  head/stand/efi/loader/main.c

Modified: head/stand/efi/include/efilib.h
==============================================================================
--- head/stand/efi/include/efilib.h	Wed Apr 11 19:28:54 2018	(r332415)
+++ head/stand/efi/include/efilib.h	Wed Apr 11 19:46:24 2018	(r332416)
@@ -59,10 +59,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);
 

Modified: head/stand/efi/include/efizfs.h
==============================================================================
--- head/stand/efi/include/efizfs.h	Wed Apr 11 19:28:54 2018	(r332415)
+++ head/stand/efi/include/efizfs.h	Wed Apr 11 19:46:24 2018	(r332416)
@@ -48,6 +48,7 @@ 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
 

Modified: head/stand/efi/libefi/efipart.c
==============================================================================
--- head/stand/efi/libefi/efipart.c	Wed Apr 11 19:28:54 2018	(r332415)
+++ head/stand/efi/libefi/efipart.c	Wed Apr 11 19:46:24 2018	(r332416)
@@ -119,6 +119,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)
 {
@@ -136,6 +137,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)
 {
@@ -294,6 +329,8 @@ efipart_fdinfo_add(EFI_HANDLE handle, uint32_t uid, EF
 	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 +401,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);
 }
@@ -489,6 +528,8 @@ efipart_hdinfo_add(EFI_HANDLE disk_handle, EFI_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);
 		}
@@ -505,6 +546,8 @@ efipart_hdinfo_add(EFI_HANDLE disk_handle, EFI_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)
@@ -521,6 +564,8 @@ efipart_hdinfo_add(EFI_HANDLE disk_handle, EFI_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);
@@ -579,6 +624,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);
@@ -609,6 +656,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);

Modified: head/stand/efi/libefi/efizfs.c
==============================================================================
--- head/stand/efi/libefi/efizfs.c	Wed Apr 11 19:28:54 2018	(r332415)
+++ head/stand/efi/libefi/efizfs.c	Wed Apr 11 19:46:24 2018	(r332416)
@@ -64,6 +64,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)
 {

Modified: head/stand/efi/loader/main.c
==============================================================================
--- head/stand/efi/loader/main.c	Wed Apr 11 19:28:54 2018	(r332415)
+++ head/stand/efi/loader/main.c	Wed Apr 11 19:46:24 2018	(r332416)
@@ -78,6 +78,15 @@ EFI_GUID inputid = SIMPLE_TEXT_INPUT_PROTOCOL;
 
 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,113 +178,183 @@ out:
 }
 
 static void
-set_devdesc_currdev(struct devsw *dev, int unit)
+set_currdev_devdesc(struct devdesc *currdev)
 {
+	const char *devname;
+
+	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 void
+set_currdev_devsw(struct devsw *dev, int unit)
+{
 	struct devdesc currdev;
-	char *devname;
 
 	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);
+}
+
+#ifdef EFI_ZFS_BOOT
+static bool
+probe_zfs_currdev(uint64_t guid)
+{
+	char *devname;
+	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);
 	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;
+	CHAR16 *text;
 	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.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);
-		init_zfs_bootenv(devname);
-		return (0);
+		printf("Trying ZFS pool\n");
+		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.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 (0);
+		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(dp, pp))
+					return (0);
 			}
 		}
+	} else {
+		printf("Can't find device by handle\n");
 	}
 
-	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;
@@ -289,8 +368,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 (0);
 		}
 
 		devpath = efi_lookup_devpath(h);
@@ -304,6 +384,33 @@ find_currdev(EFI_LOADED_IMAGE *img)
 	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[])
 {
@@ -312,12 +419,13 @@ main(int argc, CHAR16 *argv[])
 	int i, j, vargood, howto;
 	UINTN k;
 	int has_kbd;
+	char *s;
+	EFI_DEVICE_PATH *imgpath;
 	CHAR16 *text;
+	EFI_STATUS status;
 	UINT16 boot_current;
 	size_t sz;
 	UINT16 boot_order[100];
-	EFI_DEVICE_PATH *imgpath;
-	EFI_STATUS status;
 #if !defined(__arm__)
 	char buf[40];
 #endif
@@ -356,12 +464,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++) {
@@ -441,6 +552,10 @@ 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", "comconsole efi" , 1);
@@ -448,13 +563,17 @@ main(int argc, CHAR16 *argv[])
 			setenv("console", "efi comconsole" , 1);
 	} else if (howto & RB_SERIAL) {
 		setenv("console", "comconsole" , 1);
-	}
+	} else
+		setenv("console", "efi", 1);
 
 	if (efi_copy_init()) {
 		printf("failed to allocate staging area\n");
 		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.
@@ -479,6 +598,7 @@ 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);
@@ -520,8 +640,16 @@ main(int argc, CHAR16 *argv[])
 	 */
 	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 */


More information about the svn-src-all mailing list