f46eb75217
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
327 lines
8.1 KiB
C
327 lines
8.1 KiB
C
/*-
|
|
* 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 void efi_panic(EFI_STATUS s, const char *fmt, ...) __dead2 __printflike(2, 3);
|
|
|
|
const boot_module_t *boot_modules[] =
|
|
{
|
|
#ifdef EFI_ZFS_BOOT
|
|
&zfs_module,
|
|
#endif
|
|
#ifdef EFI_UFS_BOOT
|
|
&ufs_module
|
|
#endif
|
|
};
|
|
const UINTN num_boot_modules = nitems(boot_modules);
|
|
|
|
static EFI_GUID BlockIoProtocolGUID = BLOCK_IO_PROTOCOL;
|
|
static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL;
|
|
static EFI_GUID LoadedImageGUID = LOADED_IMAGE_PROTOCOL;
|
|
static EFI_GUID ConsoleControlGUID = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
|
|
|
|
/*
|
|
* Provide Malloc / Free / Calloc backed by EFIs AllocatePool / FreePool which ensures
|
|
* memory is correctly aligned avoiding EFI_INVALID_PARAMETER returns from
|
|
* EFI methods.
|
|
*/
|
|
|
|
void *
|
|
Malloc(size_t len, const char *file __unused, int line __unused)
|
|
{
|
|
void *out;
|
|
|
|
if (BS->AllocatePool(EfiLoaderData, len, &out) == EFI_SUCCESS)
|
|
return (out);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
void
|
|
Free(void *buf, const char *file __unused, int line __unused)
|
|
{
|
|
if (buf != NULL)
|
|
(void)BS->FreePool(buf);
|
|
}
|
|
|
|
void *
|
|
Calloc(size_t n1, size_t n2, const char *file, int line)
|
|
{
|
|
size_t bytes;
|
|
void *res;
|
|
|
|
bytes = n1 * n2;
|
|
if ((res = Malloc(bytes, file, line)) != NULL)
|
|
bzero(res, bytes);
|
|
|
|
return (res);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
EFI_STATUS
|
|
try_boot(const boot_module_t *mod, dev_info_t *dev, void *loaderbuf, size_t loadersize)
|
|
{
|
|
size_t bufsize, cmdsize;
|
|
void *buf;
|
|
char *cmd;
|
|
EFI_HANDLE loaderhandle;
|
|
EFI_LOADED_IMAGE *loaded_image;
|
|
EFI_STATUS 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
|
|
* string. loader.efi has a hack for ASCII strings, so we'll use that to
|
|
* keep the size down here. We only try to read the alternate file if
|
|
* we get EFI_NOT_FOUND because all other errors mean that the boot_module
|
|
* had troubles with the filesystem. We could return early, but we'll let
|
|
* loading the actual kernel sort all that out. Since these files are
|
|
* optional, we don't report errors in trying to read them.
|
|
*/
|
|
cmd = NULL;
|
|
cmdsize = 0;
|
|
status = mod->load(PATH_DOTCONFIG, dev, &buf, &bufsize);
|
|
if (status == EFI_NOT_FOUND)
|
|
status = mod->load(PATH_CONFIG, dev, &buf, &bufsize);
|
|
if (status == EFI_SUCCESS) {
|
|
cmdsize = bufsize + 1;
|
|
cmd = malloc(cmdsize);
|
|
if (cmd == NULL)
|
|
goto errout;
|
|
memcpy(cmd, buf, bufsize);
|
|
cmd[bufsize] = '\0';
|
|
free(buf);
|
|
buf = NULL;
|
|
}
|
|
|
|
if ((status = BS->LoadImage(TRUE, IH, efi_devpath_last_node(dev->devpath),
|
|
loaderbuf, loadersize, &loaderhandle)) != EFI_SUCCESS) {
|
|
printf("Failed to load image provided by %s, size: %zu, (%lu)\n",
|
|
mod->name, loadersize, EFI_ERROR_CODE(status));
|
|
goto errout;
|
|
}
|
|
|
|
if ((status = BS->HandleProtocol(loaderhandle, &LoadedImageGUID,
|
|
(VOID**)&loaded_image)) != EFI_SUCCESS) {
|
|
printf("Failed to query LoadedImage provided by %s (%lu)\n",
|
|
mod->name, EFI_ERROR_CODE(status));
|
|
goto errout;
|
|
}
|
|
|
|
if (cmd != NULL)
|
|
printf(" command args: %s\n", cmd);
|
|
|
|
loaded_image->DeviceHandle = dev->devhandle;
|
|
loaded_image->LoadOptionsSize = cmdsize;
|
|
loaded_image->LoadOptions = cmd;
|
|
|
|
DPRINTF("Starting '%s' in 5 seconds...", PATH_LOADER_EFI);
|
|
DSTALL(1000000);
|
|
DPRINTF(".");
|
|
DSTALL(1000000);
|
|
DPRINTF(".");
|
|
DSTALL(1000000);
|
|
DPRINTF(".");
|
|
DSTALL(1000000);
|
|
DPRINTF(".");
|
|
DSTALL(1000000);
|
|
DPRINTF(".\n");
|
|
|
|
if ((status = BS->StartImage(loaderhandle, NULL, NULL)) !=
|
|
EFI_SUCCESS) {
|
|
printf("Failed to start image provided by %s (%lu)\n",
|
|
mod->name, EFI_ERROR_CODE(status));
|
|
loaded_image->LoadOptionsSize = 0;
|
|
loaded_image->LoadOptions = NULL;
|
|
}
|
|
|
|
errout:
|
|
if (cmd != NULL)
|
|
free(cmd);
|
|
if (buf != NULL)
|
|
free(buf);
|
|
if (loaderbuf != NULL)
|
|
free(loaderbuf);
|
|
|
|
return (status);
|
|
}
|
|
|
|
EFI_STATUS
|
|
efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE *Xsystab)
|
|
{
|
|
EFI_HANDLE *handles;
|
|
EFI_LOADED_IMAGE *img;
|
|
EFI_DEVICE_PATH *imgpath;
|
|
EFI_STATUS status;
|
|
EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL;
|
|
SIMPLE_TEXT_OUTPUT_INTERFACE *conout = NULL;
|
|
UINTN i, hsize, nhandles;
|
|
CHAR16 *text;
|
|
|
|
/* Basic initialization*/
|
|
ST = Xsystab;
|
|
IH = Ximage;
|
|
BS = ST->BootServices;
|
|
RS = ST->RuntimeServices;
|
|
|
|
/* Set up the console, so printf works. */
|
|
status = BS->LocateProtocol(&ConsoleControlGUID, NULL,
|
|
(VOID **)&ConsoleControl);
|
|
if (status == EFI_SUCCESS)
|
|
(void)ConsoleControl->SetMode(ConsoleControl,
|
|
EfiConsoleControlScreenText);
|
|
/*
|
|
* Reset the console enable the cursor. Later we'll choose a better
|
|
* console size through GOP/UGA.
|
|
*/
|
|
conout = ST->ConOut;
|
|
conout->Reset(conout, TRUE);
|
|
/* Explicitly set conout to mode 0, 80x25 */
|
|
conout->SetMode(conout, 0);
|
|
conout->EnableCursor(conout, TRUE);
|
|
conout->ClearScreen(conout);
|
|
|
|
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++) {
|
|
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;
|
|
if (status == EFI_SUCCESS) {
|
|
text = efi_devpath_name(img->FilePath);
|
|
if (text != NULL) {
|
|
printf(" Load Path: %S\n", text);
|
|
efi_setenv_freebsd_wcs("Boot1Path", text);
|
|
efi_free_devpath_name(text);
|
|
}
|
|
|
|
status = BS->HandleProtocol(img->DeviceHandle, &DevicePathGUID,
|
|
(void **)&imgpath);
|
|
if (status != EFI_SUCCESS) {
|
|
DPRINTF("Failed to get image DevicePath (%lu)\n",
|
|
EFI_ERROR_CODE(status));
|
|
} else {
|
|
text = efi_devpath_name(imgpath);
|
|
if (text != NULL) {
|
|
printf(" Load Device: %S\n", text);
|
|
efi_setenv_freebsd_wcs("Boot1Dev", text);
|
|
efi_free_devpath_name(text);
|
|
}
|
|
}
|
|
}
|
|
|
|
choice_protocol(handles, nhandles, imgpath);
|
|
|
|
/* If we get here, we're out of luck... */
|
|
efi_panic(EFI_LOAD_ERROR, "No bootable partitions found!");
|
|
}
|
|
|
|
/*
|
|
* add_device adds a device to the passed devinfo list.
|
|
*/
|
|
void
|
|
add_device(dev_info_t **devinfop, dev_info_t *devinfo)
|
|
{
|
|
dev_info_t *dev;
|
|
|
|
if (*devinfop == NULL) {
|
|
*devinfop = devinfo;
|
|
return;
|
|
}
|
|
|
|
for (dev = *devinfop; dev->next != NULL; dev = dev->next)
|
|
;
|
|
|
|
dev->next = devinfo;
|
|
}
|
|
|
|
/*
|
|
* OK. We totally give up. Exit back to EFI with a sensible status so
|
|
* it can try the next option on the list.
|
|
*/
|
|
static void
|
|
efi_panic(EFI_STATUS s, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
printf("panic: ");
|
|
va_start(ap, fmt);
|
|
vprintf(fmt, ap);
|
|
va_end(ap);
|
|
printf("\n");
|
|
|
|
BS->Exit(IH, s, 0, NULL);
|
|
}
|
|
|
|
void
|
|
putchar(int c)
|
|
{
|
|
CHAR16 buf[2];
|
|
|
|
if (c == '\n') {
|
|
buf[0] = '\r';
|
|
buf[1] = 0;
|
|
ST->ConOut->OutputString(ST->ConOut, buf);
|
|
}
|
|
buf[0] = c;
|
|
buf[1] = 0;
|
|
ST->ConOut->OutputString(ST->ConOut, buf);
|
|
}
|