From b711db4c07fd235d3bfc2c1dc33ab62d2e0daf8e Mon Sep 17 00:00:00 2001 From: Kyle Evans Date: Thu, 24 Oct 2019 03:16:23 +0000 Subject: [PATCH] MFC r348658-r348659, r348674-r348675, r348678, r348760, r348766, r348768, r348811-r348812, r348814, r349008 r348658: Introduce efi_devpath_same_disk This is like efi_devpath_match, but allows differing device media paths. Those just specify the partition information. r348659: Use newly minted efi_devpath_same_disk() instead of efi_devpath_match(). This fixes a regression in r347193. r348674: Don't shadow a global zfsmount variable. r348675: ufs_module.c can't currently be compiled with -Wcast-align, but the code is safe enough. Turn off the warning for now until I can find the right construct to silence it in the code. r348678: Eliminate unused uuid parameters from gptread and gptread_table. We only need it for the gptfind() function, where it's used. r348760: Use simple malloc/free instead of dropping down to the UEFI BootServices AllocatePool/FreePool calls. They are simpler to use and result in the same thing happening. r348766: Remove left-over status variables r348768: Rework the reporting of the priority. Simplify the code a bit and rework how we report the results of the probing. r348811: Break out the disk selection protocol from the rest of boot1. Segregate the disk probing and selection protocol from the rest of the boot loader. r348812: Create gptboot.efi This is a primary boot loader that is intended to implement the gptboot partition selection algorithm just like we did for BIOS booting. While the preferred method for UEFI is to use the UEFI Boot Manager protocol, there are situations where that can't be done: some BIOS makers interfere with the protocol in unhelpful ways, there's a new standard for a zero variable write from the client OS, and finally for USB drives that might be mobile between systems with multiple partitions there needs to be a media stable way to select. r348814: Add stuff to disable warning for %S Add the customary warnings to disable format checking on armv7. Code move to new files, and the unconditional setting of WARNS to 6 provoked it on tinerbox... r349008: Fix gcc build by removing redeclaration --- stand/efi/Makefile | 2 +- stand/efi/boot1/Makefile | 9 +- stand/efi/boot1/boot1.c | 238 +++-------------------------- stand/efi/boot1/boot_module.h | 4 +- stand/efi/boot1/proto.c | 223 +++++++++++++++++++++++++++ stand/efi/boot1/proto.h | 29 ++++ stand/efi/boot1/ufs_module.c | 13 +- stand/efi/boot1/zfs_module.c | 29 ++-- stand/efi/gptboot/Makefile | 17 +++ stand/efi/gptboot/drv.h | 41 +++++ stand/efi/gptboot/proto.c | 279 ++++++++++++++++++++++++++++++++++ stand/efi/include/efilib.h | 1 + stand/efi/libefi/devpath.c | 28 +++- stand/i386/gptboot/gptboot.c | 2 +- stand/libsa/gpt.c | 12 +- stand/libsa/gpt.h | 2 +- 16 files changed, 676 insertions(+), 253 deletions(-) create mode 100644 stand/efi/boot1/proto.c create mode 100644 stand/efi/boot1/proto.h create mode 100644 stand/efi/gptboot/Makefile create mode 100644 stand/efi/gptboot/drv.h create mode 100644 stand/efi/gptboot/proto.c diff --git a/stand/efi/Makefile b/stand/efi/Makefile index 0772609a725..2e0daba6dab 100644 --- a/stand/efi/Makefile +++ b/stand/efi/Makefile @@ -9,7 +9,7 @@ NO_OBJ=t .if ${COMPILER_TYPE} != "gcc" || ${COMPILER_VERSION} >= 40500 SUBDIR.${MK_FDT}+= fdt -SUBDIR.yes+= libefi boot1 +SUBDIR.yes+= libefi boot1 gptboot SUBDIR.${MK_FORTH}+= loader_4th SUBDIR.${MK_LOADER_LUA}+= loader_lua SUBDIR.yes+= loader_simp diff --git a/stand/efi/boot1/Makefile b/stand/efi/boot1/Makefile index ecfbee80f3e..d596cd06388 100644 --- a/stand/efi/boot1/Makefile +++ b/stand/efi/boot1/Makefile @@ -5,7 +5,7 @@ BOOT1?= boot1 PROG= ${BOOT1}.sym INTERNALPROG= -WARNS?= 6 +WARNS= 6 CFLAGS+= -DEFI_BOOT1 # We implement a slightly non-standard %S in that it always takes a @@ -13,8 +13,13 @@ CFLAGS+= -DEFI_BOOT1 # seems to matter on arm64 where wchar_t defaults to an int instead # of a short. There's no good cast to use here so just ignore the # warnings for now. +CWARNFLAGS.proto.c+= -Wno-format CWARNFLAGS.boot1.c+= -Wno-format +# Disable bogus alignment issues +CWARNFLAGS.ufs_module.c += -Wno-format +CWARNFLAGS.ufs_module.c += -Wno-cast-align + # Disable warnings that are currently incompatible with the zfs boot code CWARNFLAGS.zfs_module.c += -Wno-array-bounds CWARNFLAGS.zfs_module.c += -Wno-cast-align @@ -25,7 +30,7 @@ CWARNFLAGS.zfs_module.c += -Wno-unused-parameter CWARNFLAGS.zfs_module.c += -Wno-unused-function # architecture-specific loader code -SRCS+= boot1.c self_reloc.c start.S ufs_module.c devpath.c +SRCS+= boot1.c proto.c self_reloc.c start.S ufs_module.c devpath.c .if ${MK_LOADER_ZFS} != "no" SRCS+= zfs_module.c CFLAGS.zfs_module.c+= -I${ZFSSRC} diff --git a/stand/efi/boot1/boot1.c b/stand/efi/boot1/boot1.c index bb647b58b42..b0c85c5dfee 100644 --- a/stand/efi/boot1/boot1.c +++ b/stand/efi/boot1/boot1.c @@ -33,10 +33,11 @@ __FBSDID("$FreeBSD$"); #include "boot_module.h" #include "paths.h" +#include "proto.h" static void efi_panic(EFI_STATUS s, const char *fmt, ...) __dead2 __printflike(2, 3); -static const boot_module_t *boot_modules[] = +const boot_module_t *boot_modules[] = { #ifdef EFI_ZFS_BOOT &zfs_module, @@ -45,8 +46,7 @@ static const boot_module_t *boot_modules[] = &ufs_module #endif }; - -#define NUM_BOOT_MODULES nitems(boot_modules) +const UINTN num_boot_modules = nitems(boot_modules); static EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL; static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL; @@ -90,67 +90,20 @@ Calloc(size_t n1, size_t n2, const char *file, int line) return (res); } -/* - * load_loader attempts to load the loader image data. - * - * It tries each module and its respective devices, identified by mod->probe, - * in order until a successful load occurs at which point it returns EFI_SUCCESS - * and EFI_NOT_FOUND otherwise. - * - * Only devices which have preferred matching the preferred parameter are tried. - */ -static EFI_STATUS -load_loader(const boot_module_t **modp, dev_info_t **devinfop, void **bufp, - size_t *bufsize, BOOLEAN preferred) -{ - UINTN i; - dev_info_t *dev; - const boot_module_t *mod; - - for (i = 0; i < NUM_BOOT_MODULES; i++) { - mod = boot_modules[i]; - for (dev = mod->devices(); dev != NULL; dev = dev->next) { - if (dev->preferred != preferred) - continue; - - if (mod->load(PATH_LOADER_EFI, dev, bufp, bufsize) == - EFI_SUCCESS) { - *devinfop = dev; - *modp = mod; - return (EFI_SUCCESS); - } - } - } - - return (EFI_NOT_FOUND); -} - /* * try_boot only returns if it fails to load the loader. If it succeeds * it simply boots, otherwise it returns the status of last EFI call. */ -static EFI_STATUS -try_boot(void) +EFI_STATUS +try_boot(const boot_module_t *mod, dev_info_t *dev, void *loaderbuf, size_t loadersize) { - size_t bufsize, loadersize, cmdsize; - void *buf, *loaderbuf; + size_t bufsize, cmdsize; + void *buf; char *cmd; - dev_info_t *dev; - const boot_module_t *mod; EFI_HANDLE loaderhandle; EFI_LOADED_IMAGE *loaded_image; EFI_STATUS status; - status = load_loader(&mod, &dev, &loaderbuf, &loadersize, TRUE); - if (status != EFI_SUCCESS) { - status = load_loader(&mod, &dev, &loaderbuf, &loadersize, - FALSE); - if (status != EFI_SUCCESS) { - printf("Failed to load '%s'\n", PATH_LOADER_EFI); - return (status); - } - } - /* * Read in and parse the command line from /boot.config or /boot/config, * if present. We'll pass it the next stage via a simple ASCII @@ -230,111 +183,6 @@ try_boot(void) return (status); } -/* - * probe_handle determines if the passed handle represents a logical partition - * if it does it uses each module in order to probe it and if successful it - * returns EFI_SUCCESS. - */ -static EFI_STATUS -probe_handle(EFI_HANDLE h, EFI_DEVICE_PATH *imgpath, BOOLEAN *preferred) -{ - dev_info_t *devinfo; - EFI_BLOCK_IO *blkio; - EFI_DEVICE_PATH *devpath; - EFI_STATUS status; - UINTN i; - - /* Figure out if we're dealing with an actual partition. */ - status = OpenProtocolByHandle(h, &DevicePathGUID, (void **)&devpath); - if (status == EFI_UNSUPPORTED) - return (status); - - if (status != EFI_SUCCESS) { - DPRINTF("\nFailed to query DevicePath (%lu)\n", - EFI_ERROR_CODE(status)); - return (status); - } -#ifdef EFI_DEBUG - { - CHAR16 *text = efi_devpath_name(devpath); - DPRINTF("probing: %S\n", text); - efi_free_devpath_name(text); - } -#endif - status = OpenProtocolByHandle(h, &BlockIoProtocolGUID, (void **)&blkio); - if (status == EFI_UNSUPPORTED) - return (status); - - if (status != EFI_SUCCESS) { - DPRINTF("\nFailed to query BlockIoProtocol (%lu)\n", - EFI_ERROR_CODE(status)); - return (status); - } - - if (!blkio->Media->LogicalPartition) - return (EFI_UNSUPPORTED); - - *preferred = efi_devpath_match(imgpath, devpath); - - /* Run through each module, see if it can load this partition */ - devinfo = malloc(sizeof(*devinfo)); - if (devinfo == NULL) { - DPRINTF("\nFailed to allocate devinfo\n"); - return (EFI_UNSUPPORTED); - } - devinfo->dev = blkio; - devinfo->devpath = devpath; - devinfo->devhandle = h; - devinfo->preferred = *preferred; - devinfo->next = NULL; - - for (i = 0; i < NUM_BOOT_MODULES; i++) { - devinfo->devdata = NULL; - status = boot_modules[i]->probe(devinfo); - if (status == EFI_SUCCESS) - return (EFI_SUCCESS); - } - free(devinfo); - - return (EFI_UNSUPPORTED); -} - -/* - * probe_handle_status calls probe_handle and outputs the returned status - * of the call. - */ -static void -probe_handle_status(EFI_HANDLE h, EFI_DEVICE_PATH *imgpath) -{ - EFI_STATUS status; - BOOLEAN preferred; - - preferred = FALSE; - status = probe_handle(h, imgpath, &preferred); - - DPRINTF("probe: "); - switch (status) { - case EFI_UNSUPPORTED: - printf("."); - DPRINTF(" not supported\n"); - break; - case EFI_SUCCESS: - if (preferred) { - printf("%c", '*'); - DPRINTF(" supported (preferred)\n"); - } else { - printf("%c", '+'); - DPRINTF(" supported\n"); - } - break; - default: - printf("x"); - DPRINTF(" error (%lu)\n", EFI_ERROR_CODE(status)); - break; - } - DSTALL(500000); -} - EFI_STATUS efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab) { @@ -346,9 +194,6 @@ efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab) SIMPLE_TEXT_OUTPUT_INTERFACE *conout = NULL; UINTN i, hsize, nhandles; CHAR16 *text; - UINT16 boot_current; - size_t sz; - UINT16 boot_order[100]; /* Basic initialization*/ ST = Xsystab; @@ -376,13 +221,27 @@ efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab) printf("\n>> FreeBSD EFI boot block\n"); printf(" Loader path: %s\n\n", PATH_LOADER_EFI); printf(" Initializing modules:"); - for (i = 0; i < NUM_BOOT_MODULES; i++) { + for (i = 0; i < num_boot_modules; i++) { printf(" %s", boot_modules[i]->name); if (boot_modules[i]->init != NULL) boot_modules[i]->init(); } putchar('\n'); + /* Fetch all the block I/O handles, we have to search through them later */ + hsize = 0; + BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID, NULL, + &hsize, NULL); + handles = malloc(hsize); + if (handles == NULL) + efi_panic(EFI_OUT_OF_RESOURCES, "Failed to allocate %d handles\n", + hsize); + status = BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID, + NULL, &hsize, handles); + if (status != EFI_SUCCESS) + efi_panic(status, "Failed to get device handles\n"); + nhandles = hsize / sizeof(*handles); + /* Determine the devpath of our image so we can prefer it. */ status = OpenProtocolByHandle(IH, &LoadedImageGUID, (void **)&img); imgpath = NULL; @@ -409,58 +268,7 @@ efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab) } } - boot_current = 0; - sz = sizeof(boot_current); - if (efi_global_getenv("BootCurrent", &boot_current, &sz) == EFI_SUCCESS) { - printf(" BootCurrent: %04x\n", boot_current); - - sz = sizeof(boot_order); - if (efi_global_getenv("BootOrder", &boot_order, &sz) == 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"); - } - } - -#ifdef TEST_FAILURE - /* - * For testing failover scenarios, it's nice to be able to fail fast. - * Define TEST_FAILURE to create a boot1.efi that always fails after - * reporting the boot manager protocol details. - */ - BS->Exit(IH, EFI_OUT_OF_RESOURCES, 0, NULL); -#endif - - hsize = 0; - BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID, NULL, - &hsize, NULL); - handles = malloc(hsize); - if (handles == NULL) - efi_panic(EFI_OUT_OF_RESOURCES, "Failed to allocate %d handles\n", - hsize); - status = BS->LocateHandle(ByProtocol, &BlockIoProtocolGUID, - NULL, &hsize, handles); - if (status != EFI_SUCCESS) - efi_panic(status, "Failed to get device handles\n"); - - /* Scan all partitions, probing with all modules. */ - nhandles = hsize / sizeof(*handles); - printf(" Probing %zu block devices...", nhandles); - DPRINTF("\n"); - - for (i = 0; i < nhandles; i++) - probe_handle_status(handles[i], imgpath); - printf(" done\n"); - - /* Status summary. */ - for (i = 0; i < NUM_BOOT_MODULES; i++) { - printf(" "); - boot_modules[i]->status(); - } - - try_boot(); + choice_protocol(handles, nhandles, imgpath); /* If we get here, we're out of luck... */ efi_panic(EFI_LOAD_ERROR, "No bootable partitions found!"); diff --git a/stand/efi/boot1/boot_module.h b/stand/efi/boot1/boot_module.h index e196c194611..99046fb5357 100644 --- a/stand/efi/boot1/boot_module.h +++ b/stand/efi/boot1/boot_module.h @@ -96,6 +96,9 @@ typedef struct boot_module_t dev_info_t *(*devices)(void); } boot_module_t; +extern const boot_module_t *boot_modules[]; +extern const UINTN num_boot_modules; + /* Standard boot modules. */ #ifdef EFI_UFS_BOOT extern const boot_module_t ufs_module; @@ -106,5 +109,4 @@ extern const boot_module_t zfs_module; /* Functions available to modules. */ extern void add_device(dev_info_t **devinfop, dev_info_t *devinfo); -extern int vsnprintf(char *str, size_t sz, const char *fmt, va_list ap); #endif diff --git a/stand/efi/boot1/proto.c b/stand/efi/boot1/proto.c new file mode 100644 index 00000000000..039ee0142d4 --- /dev/null +++ b/stand/efi/boot1/proto.c @@ -0,0 +1,223 @@ +/*- + * Copyright (c) 1998 Robert Nordier + * All rights reserved. + * Copyright (c) 2001 Robert Drehmel + * All rights reserved. + * Copyright (c) 2014 Nathan Whitehorn + * All rights reserved. + * Copyright (c) 2015 Eric McCorkle + * All rights reserved. + * + * Redistribution and use in source and binary forms are freely + * permitted provided that the above copyright notice and this + * paragraph and the following disclaimer are duplicated in all + * such forms. + * + * This software is provided "AS IS" and without any express or + * implied warranties, including, without limitation, the implied + * warranties of merchantability and fitness for a particular + * purpose. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include + +#include "boot_module.h" +#include "paths.h" +#include "proto.h" + +static EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL; +static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL; + +static const char *prio_str[] = { + "error", + "not supported", + "good", + "better" +}; + +/* + * probe_handle determines if the passed handle represents a logical partition + * if it does it uses each module in order to probe it and if successful it + * returns EFI_SUCCESS. + */ +static int +probe_handle(EFI_HANDLE h, EFI_DEVICE_PATH *imgpath) +{ + dev_info_t *devinfo; + EFI_BLOCK_IO *blkio; + EFI_DEVICE_PATH *devpath; + EFI_STATUS status; + UINTN i; + int preferred; + + /* Figure out if we're dealing with an actual partition. */ + status = BS->HandleProtocol(h, &DevicePathGUID, (void **)&devpath); + if (status == EFI_UNSUPPORTED) + return (0); + + if (status != EFI_SUCCESS) { + DPRINTF("\nFailed to query DevicePath (%lu)\n", + EFI_ERROR_CODE(status)); + return (-1); + } +#ifdef EFI_DEBUG + { + CHAR16 *text = efi_devpath_name(devpath); + DPRINTF("probing: %S ", text); + efi_free_devpath_name(text); + } +#endif + status = BS->HandleProtocol(h, &BlockIoProtocolGUID, (void **)&blkio); + if (status == EFI_UNSUPPORTED) + return (0); + + if (status != EFI_SUCCESS) { + DPRINTF("\nFailed to query BlockIoProtocol (%lu)\n", + EFI_ERROR_CODE(status)); + return (-1); + } + + if (!blkio->Media->LogicalPartition) + return (0); + + preferred = efi_devpath_same_disk(imgpath, devpath); + + /* Run through each module, see if it can load this partition */ + devinfo = malloc(sizeof(*devinfo)); + if (devinfo == NULL) { + DPRINTF("\nFailed to allocate devinfo\n"); + return (-1); + } + devinfo->dev = blkio; + devinfo->devpath = devpath; + devinfo->devhandle = h; + devinfo->preferred = preferred; + devinfo->next = NULL; + + for (i = 0; i < num_boot_modules; i++) { + devinfo->devdata = NULL; + + status = boot_modules[i]->probe(devinfo); + if (status == EFI_SUCCESS) + return (preferred + 1); + } + free(devinfo); + + return (0); +} + +/* + * load_loader attempts to load the loader image data. + * + * It tries each module and its respective devices, identified by mod->probe, + * in order until a successful load occurs at which point it returns EFI_SUCCESS + * and EFI_NOT_FOUND otherwise. + * + * Only devices which have preferred matching the preferred parameter are tried. + */ +static EFI_STATUS +load_loader(const boot_module_t **modp, dev_info_t **devinfop, void **bufp, + size_t *bufsize, int preferred) +{ + UINTN i; + dev_info_t *dev; + const boot_module_t *mod; + + for (i = 0; i < num_boot_modules; i++) { + mod = boot_modules[i]; + for (dev = mod->devices(); dev != NULL; dev = dev->next) { + if (dev->preferred != preferred) + continue; + + if (mod->load(PATH_LOADER_EFI, dev, bufp, bufsize) == + EFI_SUCCESS) { + *devinfop = dev; + *modp = mod; + return (EFI_SUCCESS); + } + } + } + + return (EFI_NOT_FOUND); +} + +void +choice_protocol(EFI_HANDLE *handles, UINTN nhandles, EFI_DEVICE_PATH *imgpath) +{ + UINT16 boot_current; + size_t sz; + UINT16 boot_order[100]; + unsigned i; + int rv; + EFI_STATUS status; + const boot_module_t *mod; + dev_info_t *dev; + void *loaderbuf; + size_t loadersize; + + /* Report UEFI Boot Manager Protocol details */ + boot_current = 0; + sz = sizeof(boot_current); + if (efi_global_getenv("BootCurrent", &boot_current, &sz) == EFI_SUCCESS) { + printf(" BootCurrent: %04x\n", boot_current); + + sz = sizeof(boot_order); + if (efi_global_getenv("BootOrder", &boot_order, &sz) == 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"); + } + } + +#ifdef TEST_FAILURE + /* + * For testing failover scenarios, it's nice to be able to fail fast. + * Define TEST_FAILURE to create a boot1.efi that always fails after + * reporting the boot manager protocol details. + */ + BS->Exit(IH, EFI_OUT_OF_RESOURCES, 0, NULL); +#endif + + /* Scan all partitions, probing with all modules. */ + printf(" Probing %zu block devices...", nhandles); + DPRINTF("\n"); + for (i = 0; i < nhandles; i++) { + rv = probe_handle(handles[i], imgpath); +#ifdef EFI_DEBUG + printf("%c", "x.+*"[rv + 1]); +#else + printf("%s\n", prio_str[rv + 1]); +#endif + } + printf(" done\n"); + + + /* Status summary. */ + for (i = 0; i < num_boot_modules; i++) { + printf(" "); + boot_modules[i]->status(); + } + + status = load_loader(&mod, &dev, &loaderbuf, &loadersize, 1); + if (status != EFI_SUCCESS) { + status = load_loader(&mod, &dev, &loaderbuf, &loadersize, 0); + if (status != EFI_SUCCESS) { + printf("Failed to load '%s'\n", PATH_LOADER_EFI); + return; + } + } + + try_boot(mod, dev, loaderbuf, loadersize); +} diff --git a/stand/efi/boot1/proto.h b/stand/efi/boot1/proto.h new file mode 100644 index 00000000000..5d80f907a5a --- /dev/null +++ b/stand/efi/boot1/proto.h @@ -0,0 +1,29 @@ +/*- + * Copyright (c) 2019 Netflix, Inc + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +void choice_protocol(EFI_HANDLE *handles, UINTN nhandles, EFI_DEVICE_PATH *imgpath); +EFI_STATUS try_boot(const boot_module_t *mod, dev_info_t *dev, void *loaderbuf, size_t loadersize); diff --git a/stand/efi/boot1/ufs_module.c b/stand/efi/boot1/ufs_module.c index 1611be3e450..fa0e5fc051a 100644 --- a/stand/efi/boot1/ufs_module.c +++ b/stand/efi/boot1/ufs_module.c @@ -140,7 +140,6 @@ static EFI_STATUS load(const char *filepath, dev_info_t *dev, void **bufp, size_t *bufsize) { ufs_ino_t ino; - EFI_STATUS status; size_t size; ssize_t read; void *buf; @@ -167,18 +166,18 @@ load(const char *filepath, dev_info_t *dev, void **bufp, size_t *bufsize) return (EFI_INVALID_PARAMETER); } - if ((status = BS->AllocatePool(EfiLoaderData, size, &buf)) != - EFI_SUCCESS) { - printf("Failed to allocate read buffer %zu for '%s' (%lu)\n", - size, filepath, EFI_ERROR_CODE(status)); - return (status); + buf = malloc(size); + if (buf == NULL) { + printf("Failed to allocate read buffer %zu for '%s'\n", + size, filepath); + return (EFI_OUT_OF_RESOURCES); } read = fsread(ino, buf, size); if ((size_t)read != size) { printf("Failed to read '%s' (%zd != %zu)\n", filepath, read, size); - (void)BS->FreePool(buf); + free(buf); return (EFI_INVALID_PARAMETER); } diff --git a/stand/efi/boot1/zfs_module.c b/stand/efi/boot1/zfs_module.c index e1d1a5a35f1..5550879a322 100644 --- a/stand/efi/boot1/zfs_module.c +++ b/stand/efi/boot1/zfs_module.c @@ -115,19 +115,17 @@ probe(dev_info_t *dev) { spa_t *spa; dev_info_t *tdev; - EFI_STATUS status; /* ZFS consumes the dev on success so we need a copy. */ - if ((status = BS->AllocatePool(EfiLoaderData, sizeof(*dev), - (void**)&tdev)) != EFI_SUCCESS) { - DPRINTF("Failed to allocate tdev (%lu)\n", - EFI_ERROR_CODE(status)); - return (status); + tdev = malloc(sizeof(*dev)); + if (tdev == NULL) { + DPRINTF("Failed to allocate tdev\n"); + return (EFI_OUT_OF_RESOURCES); } memcpy(tdev, dev, sizeof(*dev)); if (vdev_probe(vdev_read, tdev, &spa) != 0) { - (void)BS->FreePool(tdev); + free(tdev); return (EFI_UNSUPPORTED); } @@ -141,12 +139,11 @@ static EFI_STATUS load(const char *filepath, dev_info_t *devinfo, void **bufp, size_t *bufsize) { spa_t *spa; - struct zfsmount zfsmount; + struct zfsmount zmount; dnode_phys_t dn; struct stat st; int err; void *buf; - EFI_STATUS status; spa = devinfo->devdata; @@ -163,12 +160,12 @@ load(const char *filepath, dev_info_t *devinfo, void **bufp, size_t *bufsize) return (EFI_NOT_FOUND); } - if ((err = zfs_mount(spa, 0, &zfsmount)) != 0) { + if ((err = zfs_mount(spa, 0, &zmount)) != 0) { DPRINTF("Failed to mount pool '%s' (%d)\n", spa->spa_name, err); return (EFI_NOT_FOUND); } - if ((err = zfs_lookup(&zfsmount, filepath, &dn)) != 0) { + if ((err = zfs_lookup(&zmount, filepath, &dn)) != 0) { if (err == ENOENT) { DPRINTF("Failed to find '%s' on pool '%s' (%d)\n", filepath, spa->spa_name, err); @@ -185,17 +182,17 @@ load(const char *filepath, dev_info_t *devinfo, void **bufp, size_t *bufsize) return (EFI_INVALID_PARAMETER); } - if ((status = BS->AllocatePool(EfiLoaderData, (UINTN)st.st_size, &buf)) - != EFI_SUCCESS) { - printf("Failed to allocate load buffer %jd for pool '%s' for '%s' " - "(%lu)\n", (intmax_t)st.st_size, spa->spa_name, filepath, EFI_ERROR_CODE(status)); + buf = malloc(st.st_size); + if (buf == NULL) { + printf("Failed to allocate load buffer %jd for pool '%s' for '%s' ", + (intmax_t)st.st_size, spa->spa_name, filepath); return (EFI_INVALID_PARAMETER); } if ((err = dnode_read(spa, &dn, 0, buf, st.st_size)) != 0) { printf("Failed to read node from %s (%d)\n", spa->spa_name, err); - (void)BS->FreePool(buf); + free(buf); return (EFI_INVALID_PARAMETER); } diff --git a/stand/efi/gptboot/Makefile b/stand/efi/gptboot/Makefile new file mode 100644 index 00000000000..e4af52b3120 --- /dev/null +++ b/stand/efi/gptboot/Makefile @@ -0,0 +1,17 @@ +# $FreeBSD$ + +# ZFS is not supported, we want debugging until this is vetted and +# we don't want the gptboot.efifat thing created. +MK_LOADER_ZFS=no +EFI_DEBUG=yes +NOFAT=yes + +BOOT1?= gptboot +.PATH: ${SRCTOP}/stand/efi/boot1 ${SRCTOP}/stand/libsa +CFLAGS+= -I${SRCTOP}/stand/efi/boot1 +CFLAGS+= -I${.CURDIR} +CFLAGS+= -DBOOTPROG=\"gptboot.efi\" +SRCS+= gpt.c +CWARNFLAGS.gpt.c+= -Wno-sign-compare -Wno-cast-align +WARNS=6 +.include "${.CURDIR}/../boot1/Makefile" diff --git a/stand/efi/gptboot/drv.h b/stand/efi/gptboot/drv.h new file mode 100644 index 00000000000..d7df6a10f1e --- /dev/null +++ b/stand/efi/gptboot/drv.h @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2019 Netflix, Inc + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _DRV_H_ +#define _DRV_H_ + +struct dsk { + int part; + daddr_t start; + void *devinfo; /* Really a dev_into_t *, but that's not in scope */ +}; + +int drvread(struct dsk *dskp, void *buf, daddr_t lba, unsigned nblk); +int drvwrite(struct dsk *dskp, void *buf, daddr_t lba, unsigned nblk); +uint64_t drvsize(struct dsk *dskp); + +#endif /* !_DRV_H_ */ diff --git a/stand/efi/gptboot/proto.c b/stand/efi/gptboot/proto.c new file mode 100644 index 00000000000..84888b496f8 --- /dev/null +++ b/stand/efi/gptboot/proto.c @@ -0,0 +1,279 @@ +/*- + * Copyright (c) 2019 Netflix, Inc + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include + +#include "boot_module.h" +#include "paths.h" +#include "proto.h" + +#include "gpt.h" +#include +static const uuid_t freebsd_ufs_uuid = GPT_ENT_TYPE_FREEBSD_UFS; +static char secbuf[4096]; +static struct dsk dsk; +static dev_info_t *devices = NULL; +static dev_info_t *raw_device = NULL; + +static EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL; +static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL; + +/* + * Shim routine for the gpt code to read in the gpt table. The + * devinfo is always going to be for the raw device. + */ +int +drvread(struct dsk *dskp, void *buf, daddr_t lba, unsigned nblk) +{ + int size; + EFI_STATUS status; + dev_info_t *devinfo = (dev_info_t *)dskp->devinfo; + EFI_BLOCK_IO *dev = devinfo->dev; + + lba = lba / (dev->Media->BlockSize / DEV_BSIZE); + size = nblk * DEV_BSIZE; + + status = dev->ReadBlocks(dev, dev->Media->MediaId, lba, size, buf); + if (status != EFI_SUCCESS) { + DPRINTF("dskread: failed dev: %p, id: %u, lba: %ju, size: %d, " + "status: %lu\n", devinfo->dev, + dev->Media->MediaId, (uintmax_t)lba, size, + EFI_ERROR_CODE(status)); + return (-1); + } + + return (0); +} + +/* + * Shim routine for the gpt code to write in the gpt table. The + * devinfo is always going to be for the raw device. + */ +int +drvwrite(struct dsk *dskp, void *buf, daddr_t lba, unsigned nblk) +{ + int size; + EFI_STATUS status; + dev_info_t *devinfo = (dev_info_t *)dskp->devinfo; + EFI_BLOCK_IO *dev = devinfo->dev; + + if (dev->Media->ReadOnly) + return -1; + + lba = lba / (dev->Media->BlockSize / DEV_BSIZE); + size = nblk * DEV_BSIZE; + + status = dev->WriteBlocks(dev, dev->Media->MediaId, lba, size, buf); + if (status != EFI_SUCCESS) { + DPRINTF("dskread: failed dev: %p, id: %u, lba: %ju, size: %d, " + "status: %lu\n", devinfo->dev, + dev->Media->MediaId, (uintmax_t)lba, size, + EFI_ERROR_CODE(status)); + return (-1); + } + + return (0); +} + +/* + * Return the number of LBAs the drive has. + */ +uint64_t +drvsize(struct dsk *dskp) +{ + dev_info_t *devinfo = (dev_info_t *)dskp->devinfo; + EFI_BLOCK_IO *dev = devinfo->dev; + + return (dev->Media->LastBlock + 1); +} + +static int +partition_number(EFI_DEVICE_PATH *devpath) +{ + EFI_DEVICE_PATH *md; + HARDDRIVE_DEVICE_PATH *hd; + + md = efi_devpath_last_node(devpath); + if (md == NULL) + return (-1); + if (DevicePathSubType(md) != MEDIA_HARDDRIVE_DP) + return (-1); + hd = (HARDDRIVE_DEVICE_PATH *)md; + return (hd->PartitionNumber); +} + +/* + * Find the raw partition for the imgpath and save it + */ +static void +probe_handle(EFI_HANDLE h, EFI_DEVICE_PATH *imgpath) +{ + dev_info_t *devinfo; + EFI_BLOCK_IO *blkio; + EFI_DEVICE_PATH *devpath, *trimmed = NULL; + EFI_STATUS status; + + /* Figure out if we're dealing with an actual partition. */ + status = BS->HandleProtocol(h, &DevicePathGUID, (void **)&devpath); + if (status != EFI_SUCCESS) + return; +#ifdef EFI_DEBUG + { + CHAR16 *text = efi_devpath_name(devpath); + DPRINTF("probing: %S ", text); + efi_free_devpath_name(text); + } +#endif + /* + * The RAW device is the same as the imgpath with the last + * MEDIA_DEVICE bit trimmed off. imgpath will end with the + * MEDIA_DEVICE for the ESP we booted off of. + */ + if (!efi_devpath_same_disk(imgpath, devpath)) { + trimmed = efi_devpath_trim(imgpath); + if (!efi_devpath_match(trimmed, devpath)) { + free(trimmed); + DPRINTF("Not the same disk\n"); + return; + } + } + status = BS->HandleProtocol(h, &BlockIoProtocolGUID, (void **)&blkio); + if (status != EFI_SUCCESS) { + DPRINTF("Can't get the block I/O protocol block\n"); + return; + } + devinfo = malloc(sizeof(*devinfo)); + if (devinfo == NULL) { + DPRINTF("Failed to allocate devinfo\n"); + return; + } + devinfo->dev = blkio; + devinfo->devpath = devpath; + devinfo->devhandle = h; + devinfo->preferred = 1; + devinfo->next = NULL; + devinfo->devdata = NULL; + if (trimmed == NULL) { + DPRINTF("Found partition %d\n", partition_number(devpath)); + add_device(&devices, devinfo); + } else { + free(trimmed); + DPRINTF("Found raw device\n"); + if (raw_device) { + printf(BOOTPROG": Found two raw devices, inconceivable?\n"); + return; + } + raw_device = devinfo; + } +} + +static void +probe_handles(EFI_HANDLE *handles, UINTN nhandles, EFI_DEVICE_PATH *imgpath) +{ + UINTN i; + + for (i = 0; i < nhandles; i++) + probe_handle(handles[i], imgpath); +} + +static dev_info_t * +find_partition(int part) +{ + dev_info_t *dev; + + if (part == 0) + return (NULL); + for (dev = devices; dev != NULL; dev = dev->next) + if (partition_number(dev->devpath) == part) + break; + return (dev); +} + +void +choice_protocol(EFI_HANDLE *handles, UINTN nhandles, EFI_DEVICE_PATH *imgpath) +{ + const boot_module_t *mod = &ufs_module; + dev_info_t *bootdev; + void *loaderbuf; + size_t loadersize; + int parts; + const char *fn = PATH_LOADER_EFI; + + /* + * Probe the provided handles to find the partitions that + * are on the same drive. + */ + probe_handles(handles, nhandles, imgpath); + dsk.devinfo = raw_device; + if (dsk.devinfo == NULL) { + printf(BOOTPROG": unable to find raw disk to read gpt\n"); + return; + } + + /* + * Read in the GPT table, and then find the right partition. + * gptread, gptfind and gptfaileboot are shared with the + * BIOS version of the gptboot program. + */ + if (gptread(&dsk, secbuf) == -1) { + printf(BOOTPROG ": unable to load GPT\n"); + return; + } + // XXX: + // real gptboot can parse a command line before trying this loop. + // But since we don't parse anything at all, hard wire the partition + // to be -1 (meaning look for the next one). + parts = 0; + while (gptfind(&freebsd_ufs_uuid, &dsk, -1) != -1) { + parts++; + bootdev = find_partition(dsk.part); + if (bootdev == NULL) { + printf(BOOTPROG": Can't find partition %d\n", + dsk.part); + goto next; + } + if (mod->load(fn, bootdev, &loaderbuf, &loadersize) != + EFI_SUCCESS) { + printf(BOOTPROG": Can't load %s from partition %d\n", + fn, dsk.part); + goto next; + } + try_boot(mod, bootdev, loaderbuf, loadersize); +next: + gptbootfailed(&dsk); + } + if (parts == 0) + printf("%s: no UFS partition was found\n", BOOTPROG); +} diff --git a/stand/efi/include/efilib.h b/stand/efi/include/efilib.h index b29622dc2f6..98ab0db1de8 100644 --- a/stand/efi/include/efilib.h +++ b/stand/efi/include/efilib.h @@ -91,6 +91,7 @@ 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 *); +bool efi_devpath_same_disk(EFI_DEVICE_PATH *, EFI_DEVICE_PATH *); EFI_DEVICE_PATH *efi_devpath_to_media_path(EFI_DEVICE_PATH *); UINTN efi_devpath_length(EFI_DEVICE_PATH *); EFI_HANDLE efi_devpath_to_handle(EFI_DEVICE_PATH *path, EFI_HANDLE *handles, unsigned nhandles); diff --git a/stand/efi/libefi/devpath.c b/stand/efi/libefi/devpath.c index 65982ec2f3b..846fdfe0528 100644 --- a/stand/efi/libefi/devpath.c +++ b/stand/efi/libefi/devpath.c @@ -199,14 +199,19 @@ efi_devpath_match_node(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) return (true); } -bool -efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) +static bool +_efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2, + bool ignore_media) { if (devpath1 == NULL || devpath2 == NULL) return (false); while (true) { + if (ignore_media && + IsDevicePathType(devpath1, MEDIA_DEVICE_PATH) && + IsDevicePathType(devpath2, MEDIA_DEVICE_PATH)) + return (true); if (!efi_devpath_match_node(devpath1, devpath2)) return false; if (IsDevicePathEnd(devpath1)) @@ -216,6 +221,25 @@ efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) } return (true); } +/* + * Are two devpaths identical? + */ +bool +efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) +{ + return _efi_devpath_match(devpath1, devpath2, false); +} + +/* + * Like efi_devpath_match, but stops at when we hit the media device + * path node that specifies the partition information. If we match + * up to that point, then we're on the same disk. + */ +bool +efi_devpath_same_disk(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2) +{ + return _efi_devpath_match(devpath1, devpath2, true); +} bool efi_devpath_is_prefix(EFI_DEVICE_PATH *prefix, EFI_DEVICE_PATH *path) diff --git a/stand/i386/gptboot/gptboot.c b/stand/i386/gptboot/gptboot.c index d9e11864c0a..68ef55073ec 100644 --- a/stand/i386/gptboot/gptboot.c +++ b/stand/i386/gptboot/gptboot.c @@ -232,7 +232,7 @@ static int gptinit(void) { - if (gptread(&freebsd_ufs_uuid, &gdsk.dsk, dmadat->secbuf) == -1) { + if (gptread(&gdsk.dsk, dmadat->secbuf) == -1) { printf("%s: unable to load GPT\n", BOOTPROG); return (-1); } diff --git a/stand/libsa/gpt.c b/stand/libsa/gpt.c index 900f2d5c79f..25db6b6d7d9 100644 --- a/stand/libsa/gpt.c +++ b/stand/libsa/gpt.c @@ -275,8 +275,8 @@ gptbootconv(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, } static int -gptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp, - struct gpt_hdr *hdr, struct gpt_ent *table) +gptread_table(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, + struct gpt_ent *table) { struct gpt_ent *ent; int entries_per_sec; @@ -313,7 +313,7 @@ gptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp, } int -gptread(const uuid_t *uuid, struct dsk *dskp, char *buf) +gptread(struct dsk *dskp, char *buf) { uint64_t altlba; @@ -328,8 +328,7 @@ gptread(const uuid_t *uuid, struct dsk *dskp, char *buf) dskp->start = 0; if (gptread_hdr("primary", dskp, &hdr_primary, 1) == 0 && - gptread_table("primary", uuid, dskp, &hdr_primary, - table_primary) == 0) { + gptread_table("primary", dskp, &hdr_primary, table_primary) == 0) { hdr_primary_lba = hdr_primary.hdr_lba_self; gpthdr = &hdr_primary; gpttable = table_primary; @@ -349,8 +348,7 @@ gptread(const uuid_t *uuid, struct dsk *dskp, char *buf) if (altlba == 0) printf("%s: unable to locate backup GPT header\n", BOOTPROG); else if (gptread_hdr("backup", dskp, &hdr_backup, altlba) == 0 && - gptread_table("backup", uuid, dskp, &hdr_backup, - table_backup) == 0) { + gptread_table("backup", dskp, &hdr_backup, table_backup) == 0) { hdr_backup_lba = hdr_backup.hdr_lba_self; if (hdr_primary_lba == 0) { gpthdr = &hdr_backup; diff --git a/stand/libsa/gpt.h b/stand/libsa/gpt.h index 9d48564b5ad..6710aa45ca7 100644 --- a/stand/libsa/gpt.h +++ b/stand/libsa/gpt.h @@ -34,7 +34,7 @@ #define MAXTBLENTS 128 -int gptread(const uuid_t *uuid, struct dsk *dskp, char *buf); +int gptread(struct dsk *dskp, char *buf); int gptfind(const uuid_t *uuid, struct dsk *dskp, int part); void gptbootfailed(struct dsk *dskp); -- 2.45.0