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
This commit is contained in:
imp 2018-04-11 19:46:24 +00:00
parent b9a72b1615
commit f7706a11d9
5 changed files with 278 additions and 81 deletions

View File

@ -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);

View File

@ -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

View File

@ -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, 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);
}
@ -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 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);
}
@ -505,6 +546,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)
@ -521,6 +564,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);
@ -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);

View File

@ -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)
{

View File

@ -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 @@ has_keyboard(void)
}
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;
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.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);
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);
}
}
}
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 (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 */