Index: UPDATING =================================================================== --- UPDATING +++ UPDATING @@ -31,6 +31,10 @@ disable the most expensive debugging functionality run "ln -s 'abort:false,junk:false' /etc/malloc.conf".) +20180723: + loader.efi has been augmented to participate more fully in the + UEFI boot manager protocol. + 20180720: zfsloader's functionality has now been folded into loader. zfsloader is no longer necesasary once you've updated your Index: stand/efi/include/efilib.h =================================================================== --- stand/efi/include/efilib.h +++ stand/efi/include/efilib.h @@ -66,6 +66,7 @@ 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); +pdinfo_t *efiblk_get_pdinfo_by_device_path(EFI_DEVICE_PATH *path); void *efi_get_table(EFI_GUID *tbl); @@ -85,9 +86,12 @@ EFI_DEVICE_PATH *efi_devpath_last_node(EFI_DEVICE_PATH *); EFI_DEVICE_PATH *efi_devpath_trim(EFI_DEVICE_PATH *); bool efi_devpath_match(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *); +bool efi_devpath_match_node(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *); bool efi_devpath_is_prefix(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *); CHAR16 *efi_devpath_name(EFI_DEVICE_PATH *); void efi_free_devpath_name(CHAR16 *); +EFI_DEVICE_PATH *efi_devpath_to_media_path(EFI_DEVICE_PATH *); +UINTN efi_devpath_length(EFI_DEVICE_PATH *); int efi_status_to_errno(EFI_STATUS); EFI_STATUS errno_to_efi_status(int errno); Index: stand/efi/libefi/devpath.c =================================================================== --- stand/efi/libefi/devpath.c +++ stand/efi/libefi/devpath.c @@ -140,25 +140,33 @@ } bool -efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) +efi_devpath_match_node(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) { size_t len; if (devpath1 == NULL || devpath2 == NULL) return (false); + if (DevicePathType(devpath1) != DevicePathType(devpath2) || + DevicePathSubType(devpath1) != DevicePathSubType(devpath2)) + return (false); + len = DevicePathNodeLength(devpath1); + if (len != DevicePathNodeLength(devpath2)) + return (false); + if (memcmp(devpath1, devpath2, len) != 0) + return (false); + return (true); +} - while (true) { - if (DevicePathType(devpath1) != DevicePathType(devpath2) || - DevicePathSubType(devpath1) != DevicePathSubType(devpath2)) - return (false); - - len = DevicePathNodeLength(devpath1); - if (len != DevicePathNodeLength(devpath2)) - return (false); +bool +efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) +{ - if (memcmp(devpath1, devpath2, len) != 0) - return (false); + if (devpath1 == NULL || devpath2 == NULL) + return (false); + while (true) { + if (!efi_devpath_match_node(devpath1, devpath2)) + return false; if (IsDevicePathEnd(devpath1)) break; devpath1 = NextDevicePathNode(devpath1); @@ -195,3 +203,29 @@ } return (true); } + +/* + * Skip over the 'prefix' part of path and return the part of the path + * that starts with the first node that's a MEDIA_DEVICE_PATH. + */ +EFI_DEVICE_PATH * +efi_devpath_to_media_path(EFI_DEVICE_PATH *path) +{ + + while (!IsDevicePathEnd(path)) { + if (DevicePathType(path) == MEDIA_DEVICE_PATH) + return (path); + path = NextDevicePathNode(path); + } + return (NULL); +} + +UINTN +efi_devpath_length(EFI_DEVICE_PATH *path) +{ + EFI_DEVICE_PATH *start = path; + + while (!IsDevicePathEnd(path)) + path = NextDevicePathNode(path); + return ((UINTN)path - (UINTN)start) + DevicePathNodeLength(path); +} Index: stand/efi/libefi/efipart.c =================================================================== --- stand/efi/libefi/efipart.c +++ stand/efi/libefi/efipart.c @@ -137,6 +137,28 @@ return (pd); } +pdinfo_t * +efiblk_get_pdinfo_by_device_path(EFI_DEVICE_PATH *path) +{ + unsigned i; + EFI_DEVICE_PATH *media, *devpath; + EFI_HANDLE h; + + media = efi_devpath_to_media_path(path); + if (media == NULL) + return (NULL); + for (i = 0; i < efipart_nhandles; i++) { + h = efipart_handles[i]; + devpath = efi_lookup_devpath(h); + if (devpath == NULL) + continue; + if (!efi_devpath_match_node(media, efi_devpath_to_media_path(devpath))) + continue; + return (efiblk_get_pdinfo_by_handle(h)); + } + return (NULL); +} + static bool same_handle(pdinfo_t *pd, EFI_HANDLE h) { @@ -210,7 +232,7 @@ return (efi_status_to_errno(status)); efipart_handles = hin; - efipart_nhandles = sz; + efipart_nhandles = sz / sizeof(*hin); #ifdef EFIPART_DEBUG printf("%s: Got %d BLOCK IO MEDIA handle(s)\n", __func__, efipart_nhandles); @@ -246,7 +268,7 @@ static bool efipart_hdd(EFI_DEVICE_PATH *dp) { - unsigned i, nin; + unsigned i; EFI_DEVICE_PATH *devpath, *node; EFI_BLOCK_IO *blkio; EFI_STATUS status; @@ -264,8 +286,7 @@ * Test every EFI BLOCK IO handle to make sure dp is not device path * for CD/DVD. */ - nin = efipart_nhandles / sizeof (*efipart_handles); - for (i = 0; i < nin; i++) { + for (i = 0; i < efipart_nhandles; i++) { devpath = efi_lookup_devpath(efipart_handles[i]); if (devpath == NULL) return (false); @@ -340,10 +361,9 @@ { EFI_DEVICE_PATH *devpath, *node; ACPI_HID_DEVICE_PATH *acpi; - int i, nin; + int i; - nin = efipart_nhandles / sizeof (*efipart_handles); - for (i = 0; i < nin; i++) { + for (i = 0; i < efipart_nhandles; i++) { devpath = efi_lookup_devpath(efipart_handles[i]); if (devpath == NULL) continue; @@ -410,14 +430,13 @@ static void efipart_updatecd(void) { - int i, nin; + int i; EFI_DEVICE_PATH *devpath, *devpathcpy, *tmpdevpath, *node; EFI_HANDLE handle; EFI_BLOCK_IO *blkio; EFI_STATUS status; - nin = efipart_nhandles / sizeof (*efipart_handles); - for (i = 0; i < nin; i++) { + for (i = 0; i < efipart_nhandles; i++) { devpath = efi_lookup_devpath(efipart_handles[i]); if (devpath == NULL) continue; @@ -666,14 +685,13 @@ static void efipart_updatehd(void) { - int i, nin; + int i; EFI_DEVICE_PATH *devpath, *devpathcpy, *tmpdevpath, *node; EFI_HANDLE handle; EFI_BLOCK_IO *blkio; EFI_STATUS status; - nin = efipart_nhandles / sizeof (*efipart_handles); - for (i = 0; i < nin; i++) { + for (i = 0; i < efipart_nhandles; i++) { devpath = efi_lookup_devpath(efipart_handles[i]); if (devpath == NULL) continue; Index: stand/efi/loader/main.c =================================================================== --- stand/efi/loader/main.c +++ stand/efi/loader/main.c @@ -1,6 +1,7 @@ /*- * Copyright (c) 2008-2010 Rui Paulo * Copyright (c) 2006 Marcel Moolenaar + * Copyright (c) 2018 Netflix, Inc * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,6 +42,7 @@ #include #include +#include #include @@ -83,6 +85,11 @@ */ static int fail_timeout = 5; +/* + * Current boot variable + */ +UINT16 boot_current; + static bool has_keyboard(void) { @@ -274,8 +281,144 @@ return (sanity_check_currdev()); } +/* + * Sometimes we get filenames that are all upper case + * and/or have backslashes in them. Filter all this out + * if it looks like we need to do so. + */ +static void +fix_dosisms(char *p) +{ + while (*p) { + if (isupper(*p)) + *p = tolower(*p); + else if (*p == '\\') + *p = '/'; + p++; + } +} + +enum { BOOT_INFO_OK = 0, BAD_CHOICE = 1, NOT_SPECIFIC = 2 }; static int -find_currdev(EFI_LOADED_IMAGE *img) +match_boot_info(EFI_LOADED_IMAGE *img __unused, char *boot_info, size_t bisz) +{ + uint32_t attr; + uint16_t fplen; + size_t len; + char *walker, *ep; + EFI_DEVICE_PATH *dp, *edp, *first_dp, *last_dp; + pdinfo_t *pp; + CHAR16 *descr; + char *kernel = NULL; + FILEPATH_DEVICE_PATH *fp; + struct stat st; + + /* + * FreeBSD encodes it's boot loading path into the boot loader + * BootXXXX variable. We look for the last one in the path + * and use that to load the kernel. However, if we only fine + * one DEVICE_PATH, then there's nothing specific and we should + * fall back. + * + * In an ideal world, we'd look at the image handle we were + * passed, match up with the loader we are and then return the + * next one in the path. This would be most flexible and cover + * many chain booting scenarios where you need to use this + * boot loader to get to the next boot loader. However, that + * doesn't work. We rarely have the path to the image booted + * (just the device) so we can't count on that. So, we do the + * enxt best thing, we look through the device path(s) passed + * in the BootXXXX varaible. If there's only one, we return + * NOT_SPECIFIC. Otherwise, we look at the last one and try to + * load that. If we can, we return BOOT_INFO_OK. Otherwise we + * return BAD_CHOICE for the caller to sort out. + */ + if (bisz < sizeof(attr) + sizeof(fplen) + sizeof(CHAR16)) + return NOT_SPECIFIC; + walker = boot_info; + ep = walker + bisz; + memcpy(&attr, walker, sizeof(attr)); + walker += sizeof(attr); + memcpy(&fplen, walker, sizeof(fplen)); + walker += sizeof(fplen); + descr = (CHAR16 *)(intptr_t)walker; + len = ucs2len(descr); + walker += (len + 1) * sizeof(CHAR16); + last_dp = first_dp = dp = (EFI_DEVICE_PATH *)walker; + edp = (EFI_DEVICE_PATH *)(walker + fplen); + if ((char *)edp > ep) + return NOT_SPECIFIC; + while (dp < edp) { + last_dp = dp; + dp = (EFI_DEVICE_PATH *)((char *)dp + efi_devpath_length(dp)); + } + + /* + * If there's only one item in the list, then nothing was + * specified. + */ + if (last_dp == first_dp) + return NOT_SPECIFIC; + + /* + * OK. At this point we either have a good path or a bad one. + * Let's check. + */ + pp = efiblk_get_pdinfo_by_device_path(last_dp); + if (pp == NULL) + return BAD_CHOICE; + set_currdev_pdinfo(pp); + if (!sanity_check_currdev()) + return BAD_CHOICE; + + /* + * OK. We've found a device that matches, next we need to check the last + * component of the path. If it's a file, then we set the default kernel + * to that. Otherwise, just use this as the default root. + * + * Reminder: we're running very early, before we've parsed the defaults + * file, so we may need to have a hack override. + */ + dp = efi_devpath_last_node(last_dp); + if (DevicePathType(dp) != MEDIA_DEVICE_PATH || + DevicePathSubType(dp) != MEDIA_FILEPATH_DP) + return (BOOT_INFO_OK); /* use currdir, default kernel */ + fp = (FILEPATH_DEVICE_PATH *)dp; + ucs2_to_utf8(fp->PathName, &kernel); + if (kernel == NULL) + return (BAD_CHOICE); + if (*kernel == '\\' || isupper(*kernel)) + fix_dosisms(kernel); + if (stat(kernel, &st) != 0) { + free(kernel); + return (BAD_CHOICE); + } + setenv("kernel", kernel, 1); + free(kernel); + + return (BOOT_INFO_OK); +} + +/* + * Look at the passed-in boot_info, if any. If we find it then we need + * to see if we can find ourselves in the boot chain. If we can, and + * there's another specified thing to boot next, assume that the file + * is loaded from / and use that for the root filesystem. If can't + * find the specified thing, we must fail the boot. If we're last on + * the list, then we fallback to looking for the first available / + * candidate (ZFS, if there's a bootable zpool, otherwise a UFS + * partition that has either /boot/defaults/loader.conf on it or + * /boot/kernel/kernel (the default kernel) that we can use. + * + * We always fail if we can't find the right thing. However, as + * a concession to buggy UEFI implementations, like u-boot, if + * we have determined that the host is violating the UEFI boot + * manager protocol, we'll signal the rest of the program that + * a drop to the OK boot loader prompt is possible. + */ +static int +find_currdev(EFI_LOADED_IMAGE *img, bool do_bootmgr, bool is_last, + char *boot_info, size_t boot_info_sz) { pdinfo_t *dp, *pp; EFI_DEVICE_PATH *devpath, *copy; @@ -284,8 +427,13 @@ struct devsw *dev; int unit; uint64_t extra; + int rv; char *rootdev; + /* + * First choice: if rootdev is already set, use that, even if + * it's wrong. + */ rootdev = getenv("rootdev"); if (rootdev != NULL) { printf("Setting currdev to configured rootdev %s\n", rootdev); @@ -293,6 +441,25 @@ return (0); } + /* + * Second choice: If we can find out image boot_info, and there's + * a follow-on boot image in that boot_info, use that. In this + * case root will be the partition specified in that image and + * we'll load the kernel specified by the file path. Should there + * not be a filepath, we use the default. This filepath overrides + * loader.conf. + */ + if (do_bootmgr) { + rv = match_boot_info(img, boot_info, boot_info_sz); + switch (rv) { + case BOOT_INFO_OK: /* We found it */ + return (0); + case BAD_CHOICE: /* specified file not found -> error */ + /* XXX do we want to have an escape hatch for last in boot order? */ + return (ENOENT); + } /* Nothing specified, try normal match */ + } + #ifdef EFI_ZFS_BOOT /* * Did efi_zfs_probe() detect the boot pool? If so, use the zpool @@ -332,7 +499,7 @@ /* * Roll up the ZFS special case * for those partitions that have - * zpools on them + * zpools on them. */ if (try_as_currdev(dp, pp)) return (0); @@ -529,15 +696,17 @@ EFI_GUID *guid; int howto, i, uhowto; UINTN k; - bool has_kbd; + bool has_kbd, is_last; char *s; EFI_DEVICE_PATH *imgpath; CHAR16 *text; - EFI_STATUS status; - UINT16 boot_current; - size_t sz; + EFI_STATUS rv; + size_t sz, bosz = 0, bisz = 0; UINT16 boot_order[100]; + char boot_info[4096]; EFI_LOADED_IMAGE *img; + char buf[32]; + bool uefi_boot_mgr; archsw.arch_autoload = efi_autoload; archsw.arch_getdev = efi_getdev; @@ -669,8 +838,8 @@ efi_free_devpath_name(text); } - status = BS->HandleProtocol(img->DeviceHandle, &devid, (void **)&imgpath); - if (status == EFI_SUCCESS) { + rv = BS->HandleProtocol(img->DeviceHandle, &devid, (void **)&imgpath); + if (rv == EFI_SUCCESS) { text = efi_devpath_name(imgpath); if (text != NULL) { printf(" Load Device: %S\n", text); @@ -679,18 +848,53 @@ } } + uefi_boot_mgr = true; boot_current = 0; sz = sizeof(boot_current); - efi_global_getenv("BootCurrent", &boot_current, &sz); - printf(" BootCurrent: %04x\n", boot_current); + rv = efi_global_getenv("BootCurrent", &boot_current, &sz); + if (rv == EFI_SUCCESS) + printf(" BootCurrent: %04x\n", boot_current); + else { + boot_current = 0xffff; + uefi_boot_mgr = false; + } 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"); + rv = efi_global_getenv("BootOrder", &boot_order, &sz); + if (rv == EFI_SUCCESS) { + 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"); + is_last = boot_order[(sz / sizeof(boot_order[0])) - 1] == boot_current; + bosz = sz; + } else if (uefi_boot_mgr) { + /* + * u-boot doesn't set BootOrder, but otherwise participates in the + * boot manager protocol. So we fake it here and don't consider it + * a failure. + */ + bosz = sizeof(boot_order[0]); + boot_order[0] = boot_current; + is_last = true; + } + + /* + * Next, find the boot info structure the UEFI boot manager is + * supposed to setup. We need this so we can walk through it to + * find where we are in the booting process and what to try to + * boot next. + */ + if (uefi_boot_mgr) { + snprintf(buf, sizeof(buf), "Boot%04X", boot_current); + sz = sizeof(boot_info); + rv = efi_global_getenv(buf, &boot_info, &sz); + if (rv == EFI_SUCCESS) + bisz = sz; + else + uefi_boot_mgr = false; + } /* * Disable the watchdog timer. By default the boot manager sets @@ -710,7 +914,7 @@ * the boot protocol and also allow an escape hatch for users wishing * to try something different. */ - if (find_currdev(img) != 0) + if (find_currdev(img, uefi_boot_mgr, is_last, boot_info, bisz) != 0) if (!interactive_interrupt("Failed to find bootable partition")) return (EFI_NOT_FOUND);