/*-
* 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 <bootstrap.h>
#include "boot_module.h"
#include "paths.h"
struct arch_switch archsw;
struct fs_ops *file_system[] = {
NULL
};
static const boot_module_t *boot_modules[] =
{
#ifdef EFI_ZFS_BOOT
&zfs_module,
#endif
#ifdef EFI_UFS_BOOT
&ufs_module
#endif
};
#define NUM_BOOT_MODULES (sizeof(boot_modules) / sizeof(boot_module_t*))
/* The initial number of handles used to query EFI for partitions. */
#define NUM_HANDLES_INIT 24
EFI_STATUS efi_main(EFI_HANDLE Ximage, EFI_SYSTEM_TABLE* Xsystab);
EFI_SYSTEM_TABLE *systab;
EFI_BOOT_SERVICES *bs;
static EFI_HANDLE *image;
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 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)
{
(void)bs->FreePool(buf);
}
/*
* nodes_match returns TRUE if the imgpath isn't NULL and the nodes match,
* FALSE otherwise.
*/
static BOOLEAN
nodes_match(EFI_DEVICE_PATH *imgpath, EFI_DEVICE_PATH *devpath)
{
int len;
if (imgpath == NULL || imgpath->Type != devpath->Type ||
imgpath->SubType != devpath->SubType)
return (FALSE);
len = DevicePathNodeLength(imgpath);
if (len != DevicePathNodeLength(devpath))
return (FALSE);
return (memcmp(imgpath, devpath, (size_t)len) == 0);
}
/*
* device_paths_match returns TRUE if the imgpath isn't NULL and all nodes
* in imgpath and devpath match up to their respect occurances of a media
* node, FALSE otherwise.
*/
static BOOLEAN
device_paths_match(EFI_DEVICE_PATH *imgpath, EFI_DEVICE_PATH *devpath)
{
if (imgpath == NULL)
return (FALSE);
while (!IsDevicePathEnd(imgpath) && !IsDevicePathEnd(devpath)) {
if (IsDevicePathType(imgpath, MEDIA_DEVICE_PATH) &&
IsDevicePathType(devpath, MEDIA_DEVICE_PATH))
return (TRUE);
if (!nodes_match(imgpath, devpath))
return (FALSE);
imgpath = NextDevicePathNode(imgpath);
devpath = NextDevicePathNode(devpath);
}
return (FALSE);
}
/*
* devpath_last returns the last non-path end node in devpath.
*/
static EFI_DEVICE_PATH *
devpath_last(EFI_DEVICE_PATH *devpath)
{
while (!IsDevicePathEnd(NextDevicePathNode(devpath)))
devpath = NextDevicePathNode(devpath);
return (devpath);
}
/*
* devpath_node_str is a basic output method for a devpath node which
* only understands a subset of the available sub types.
*
* If we switch to UEFI 2.x then we should update it to use:
* EFI_DEVICE_PATH_TO_TEXT_PROTOCOL.
*/
static int
devpath_node_str(char *buf, size_t size, EFI_DEVICE_PATH *devpath)
{
switch (devpath->Type) {
case MESSAGING_DEVICE_PATH:
switch (devpath->SubType) {
case MSG_ATAPI_DP: {
ATAPI_DEVICE_PATH *atapi;
atapi = (ATAPI_DEVICE_PATH *)(void *)devpath;
return snprintf(buf, size, "ata(%s,%s,0x%x)",
(atapi->PrimarySecondary == 1) ? "Sec" : "Pri",
(atapi->SlaveMaster == 1) ? "Slave" : "Master",
atapi->Lun);
}
case MSG_USB_DP: {
USB_DEVICE_PATH *usb;
usb = (USB_DEVICE_PATH *)devpath;
return snprintf(buf, size, "usb(0x%02x,0x%02x)",
usb->ParentPortNumber, usb->InterfaceNumber);
}
case MSG_SCSI_DP: {
SCSI_DEVICE_PATH *scsi;
scsi = (SCSI_DEVICE_PATH *)(void *)devpath;
return snprintf(buf, size, "scsi(0x%02x,0x%02x)",
scsi->Pun, scsi->Lun);
}
case MSG_SATA_DP: {
SATA_DEVICE_PATH *sata;
sata = (SATA_DEVICE_PATH *)(void *)devpath;
return snprintf(buf, size, "sata(0x%x,0x%x,0x%x)",
sata->HBAPortNumber, sata->PortMultiplierPortNumber,
sata->Lun);
}
default:
return snprintf(buf, size, "msg(0x%02x)",
devpath->SubType);
}
break;
case HARDWARE_DEVICE_PATH:
switch (devpath->SubType) {
case HW_PCI_DP: {
PCI_DEVICE_PATH *pci;
pci = (PCI_DEVICE_PATH *)devpath;
return snprintf(buf, size, "pci(0x%02x,0x%02x)",
pci->Device, pci->Function);
}
default:
return snprintf(buf, size, "hw(0x%02x)",
devpath->SubType);
}
break;
case ACPI_DEVICE_PATH: {
ACPI_HID_DEVICE_PATH *acpi;
acpi = (ACPI_HID_DEVICE_PATH *)(void *)devpath;
if ((acpi->HID & PNP_EISA_ID_MASK) == PNP_EISA_ID_CONST) {
switch (EISA_ID_TO_NUM(acpi->HID)) {
case 0x0a03:
return snprintf(buf, size, "pciroot(0x%x)",
acpi->UID);
case 0x0a08:
return snprintf(buf, size, "pcieroot(0x%x)",
acpi->UID);
case 0x0604:
return snprintf(buf, size, "floppy(0x%x)",
acpi->UID);
case 0x0301:
return snprintf(buf, size, "keyboard(0x%x)",
acpi->UID);
case 0x0501:
return snprintf(buf, size, "serial(0x%x)",
acpi->UID);
case 0x0401:
return snprintf(buf, size, "parallelport(0x%x)",
acpi->UID);
default:
return snprintf(buf, size, "acpi(pnp%04x,0x%x)",
EISA_ID_TO_NUM(acpi->HID), acpi->UID);
}
}
return snprintf(buf, size, "acpi(0x%08x,0x%x)", acpi->HID,
acpi->UID);
}
case MEDIA_DEVICE_PATH:
switch (devpath->SubType) {
case MEDIA_CDROM_DP: {
CDROM_DEVICE_PATH *cdrom;
cdrom = (CDROM_DEVICE_PATH *)(void *)devpath;
return snprintf(buf, size, "cdrom(%x)",
cdrom->BootEntry);
}
case MEDIA_HARDDRIVE_DP: {
HARDDRIVE_DEVICE_PATH *hd;
hd = (HARDDRIVE_DEVICE_PATH *)(void *)devpath;
return snprintf(buf, size, "hd(%x)",
hd->PartitionNumber);
}
default:
return snprintf(buf, size, "media(0x%02x)",
devpath->SubType);
}
case BBS_DEVICE_PATH:
return snprintf(buf, size, "bbs(0x%02x)", devpath->SubType);
case END_DEVICE_PATH_TYPE:
return (0);
}
return snprintf(buf, size, "type(0x%02x, 0x%02x)", devpath->Type,
devpath->SubType);
}
/*
* devpath_strlcat appends a text description of devpath to buf but not more
* than size - 1 characters followed by NUL-terminator.
*/
int
devpath_strlcat(char *buf, size_t size, EFI_DEVICE_PATH *devpath)
{
size_t len, used;
const char *sep;
sep = "";
used = 0;
while (!IsDevicePathEnd(devpath)) {
len = snprintf(buf, size - used, "%s", sep);
used += len;
if (used > size)
return (used);
buf += len;
len = devpath_node_str(buf, size - used, devpath);
used += len;
if (used > size)
return (used);
buf += len;
devpath = NextDevicePathNode(devpath);
sep = ":";
}
return (used);
}
/*
* devpath_str is convenience method which returns the text description of
* devpath using a static buffer, so it isn't thread safe!
*/
char *
devpath_str(EFI_DEVICE_PATH *devpath)
{
static char buf[256];
devpath_strlcat(buf, sizeof(buf), devpath);
return buf;
}
/*
* 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++) {
if (boot_modules[i] == NULL)
continue;
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)
{
size_t bufsize, loadersize, cmdsize;
void *buf, *loaderbuf;
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
* 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, image, devpath_last(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);
}
/*
* 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 = bs->HandleProtocol(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);
}
DPRINTF("probing: %s\n", devpath_str(devpath));
status = bs->HandleProtocol(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 = device_paths_match(imgpath, devpath);
/* Run through each module, see if it can load this partition */
for (i = 0; i < NUM_BOOT_MODULES; i++) {
if (boot_modules[i] == NULL)
continue;
if ((status = bs->AllocatePool(EfiLoaderData,
sizeof(*devinfo), (void **)&devinfo)) !=
EFI_SUCCESS) {
DPRINTF("\nFailed to allocate devinfo (%lu)\n",
EFI_ERROR_CODE(status));
continue;
}
devinfo->dev = blkio;
devinfo->devpath = devpath;
devinfo->devhandle = h;
devinfo->devdata = NULL;
devinfo->preferred = *preferred;
devinfo->next = NULL;
status = boot_modules[i]->probe(devinfo);
if (status == EFI_SUCCESS)
return (EFI_SUCCESS);
(void)bs->FreePool(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 = 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)
{
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, max_dim, best_mode, cols, rows, hsize, nhandles;
/* Basic initialization*/
systab = Xsystab;
image = Ximage;
bs = Xsystab->BootServices;
/* 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 and find the best text mode.
*/
conout = systab->ConOut;
conout->Reset(conout, TRUE);
max_dim = best_mode = 0;
for (i = 0; ; i++) {
status = conout->QueryMode(conout, i, &cols, &rows);
if (EFI_ERROR(status))
break;
if (cols * rows > max_dim) {
max_dim = cols * rows;
best_mode = i;
}
}
if (max_dim > 0)
conout->SetMode(conout, best_mode);
conout->EnableCursor(conout, TRUE);
conout->ClearScreen(conout);
printf("\n>> illumos EFI boot block\n");
printf(" Loader path: %s\n\n", PATH_LOADER_EFI);
printf(" Initializing modules:");
for (i = 0; i < NUM_BOOT_MODULES; i++) {
if (boot_modules[i] == NULL)
continue;
printf(" %s", boot_modules[i]->name);
if (boot_modules[i]->init != NULL)
boot_modules[i]->init();
}
putchar('\n');
/* Get all the device handles */
hsize = (UINTN)NUM_HANDLES_INIT * sizeof(EFI_HANDLE);
if ((status = bs->AllocatePool(EfiLoaderData, hsize, (void **)&handles))
!= EFI_SUCCESS)
panic("Failed to allocate %d handles (%lu)", NUM_HANDLES_INIT,
EFI_ERROR_CODE(status));
status = bs->LocateHandle(ByProtocol, &BlockIoProtocolGUID, NULL,
&hsize, handles);
switch (status) {
case EFI_SUCCESS:
break;
case EFI_BUFFER_TOO_SMALL:
(void)bs->FreePool(handles);
if ((status = bs->AllocatePool(EfiLoaderData, hsize,
(void **)&handles) != EFI_SUCCESS)) {
panic("Failed to allocate %zu handles (%lu)", hsize /
sizeof(*handles), EFI_ERROR_CODE(status));
}
status = bs->LocateHandle(ByProtocol, &BlockIoProtocolGUID,
NULL, &hsize, handles);
if (status != EFI_SUCCESS)
panic("Failed to get device handles (%lu)\n",
EFI_ERROR_CODE(status));
break;
default:
panic("Failed to get device handles (%lu)",
EFI_ERROR_CODE(status));
}
/* Scan all partitions, probing with all modules. */
nhandles = hsize / sizeof(*handles);
printf(" Probing %zu block devices...", nhandles);
DPRINTF("\n");
/* Determine the devpath of our image so we can prefer it. */
status = bs->HandleProtocol(image, &LoadedImageGUID, (VOID**)&img);
imgpath = NULL;
if (status == EFI_SUCCESS) {
status = bs->HandleProtocol(img->DeviceHandle, &DevicePathGUID,
(void **)&imgpath);
if (status != EFI_SUCCESS)
DPRINTF("Failed to get image DevicePath (%lu)\n",
EFI_ERROR_CODE(status));
DPRINTF("boot1 imagepath: %s\n", devpath_str(imgpath));
}
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++) {
if (boot_modules[i] != NULL) {
printf(" ");
boot_modules[i]->status();
}
}
try_boot();
/* If we get here, we're out of luck... */
panic("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;
}
void
panic(const char *fmt, ...)
{
va_list ap;
printf("panic: ");
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf("\n");
while (1) {}
}
void
putchar(int c)
{
CHAR16 buf[2];
if (c == '\n') {
buf[0] = '\r';
buf[1] = 0;
systab->ConOut->OutputString(systab->ConOut, buf);
}
buf[0] = c;
buf[1] = 0;
systab->ConOut->OutputString(systab->ConOut, buf);
}
int
getchar(void)
{
EFI_INPUT_KEY key;
EFI_STATUS status;
UINTN junk;
status = systab->ConIn->ReadKeyStroke(systab->ConIn, &key);
if (status == EFI_NOT_READY) {
bs->WaitForEvent(1, &systab->ConIn->WaitForKey, &junk);
status = systab->ConIn->ReadKeyStroke(systab->ConIn, &key);
}
return (key.UnicodeChar);
}