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.

Reviewed by: tsoome, bcran
Differential Revision: https://reviews.freebsd.org/D20547
This commit is contained in:
imp 2019-06-08 18:59:50 +00:00
parent c4bbb0fa7d
commit b04a82e050
5 changed files with 280 additions and 196 deletions

View File

@ -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
@ -28,7 +28,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}

View File

@ -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,66 +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, 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);
}
/*
* 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, 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 (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
@ -228,84 +182,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 0.
*/
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);
}
const char *prio_str[] = {
"error",
"not supported",
"good",
"better"
};
EFI_STATUS
efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab)
{
@ -317,10 +193,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];
int rv;
/* Basic initialization*/
ST = Xsystab;
@ -348,13 +220,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 = BS->HandleProtocol(IH, &LoadedImageGUID, (VOID**)&img);
imgpath = NULL;
@ -381,64 +267,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++) {
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();
}
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!");

View File

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

223
stand/efi/boot1/proto.c Normal file
View File

@ -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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <machine/elf.h>
#include <machine/stdarg.h>
#include <stand.h>
#include <efi.h>
#include <eficonsctl.h>
#include <efichar.h>
#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);
}

29
stand/efi/boot1/proto.h Normal file
View File

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