mirror of
https://github.com/u-boot/u-boot.git
synced 2025-04-21 20:34:38 +00:00

Calling bootefi on an address that was loaded from memory (e.g., cramfs or SPI flash via "sf read", etc.), currently results in the EFI binary not being able to access the EFI image device path. For example, iPXE would fail with an error "EFI could not get loaded image's device path: Error 0x7f39e082 (https://ipxe.org/7f39e082)". This is due to an incomplete special-case in efi_binary_run, where a new device path was created but not used in all required places. Fix the in-memory special case, set the "bootefi_device_path" to the generated "file_path". iPXE will now boot, and report the device path as "/MemoryMapped(0x0,0xSTART,0xLEN)" Signed-off-by: Christian Kohlschütter <christian@kohlschutter.com> Reviewed-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
378 lines
9.5 KiB
C
378 lines
9.5 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* For the code moved from cmd/bootefi.c
|
|
* Copyright (c) 2016 Alexander Graf
|
|
*/
|
|
|
|
#define LOG_CATEGORY LOGC_EFI
|
|
|
|
#include <bootflow.h>
|
|
#include <charset.h>
|
|
#include <dm.h>
|
|
#include <efi.h>
|
|
#include <efi_loader.h>
|
|
#include <env.h>
|
|
#include <image.h>
|
|
#include <log.h>
|
|
#include <malloc.h>
|
|
#include <mapmem.h>
|
|
#include <net.h>
|
|
|
|
static struct efi_device_path *bootefi_image_path;
|
|
static struct efi_device_path *bootefi_device_path;
|
|
static void *image_addr;
|
|
static size_t image_size;
|
|
|
|
/**
|
|
* efi_get_image_parameters() - return image parameters
|
|
*
|
|
* @img_addr: address of loaded image in memory
|
|
* @img_size: size of loaded image
|
|
*/
|
|
void efi_get_image_parameters(void **img_addr, size_t *img_size)
|
|
{
|
|
*img_addr = image_addr;
|
|
*img_size = image_size;
|
|
}
|
|
|
|
/**
|
|
* efi_clear_bootdev() - clear boot device
|
|
*/
|
|
void efi_clear_bootdev(void)
|
|
{
|
|
efi_free_pool(bootefi_device_path);
|
|
efi_free_pool(bootefi_image_path);
|
|
bootefi_device_path = NULL;
|
|
bootefi_image_path = NULL;
|
|
image_addr = NULL;
|
|
image_size = 0;
|
|
}
|
|
|
|
/**
|
|
* calculate_paths() - Calculate the device and image patch from strings
|
|
*
|
|
* @dev: device, e.g. "MMC"
|
|
* @devnr: number of the device, e.g. "1:2"
|
|
* @path: path to file loaded
|
|
* @device_pathp: returns EFI device path
|
|
* @image_pathp: returns EFI image path
|
|
* Return: EFI_SUCCESS on success, else error code
|
|
*/
|
|
static efi_status_t calculate_paths(const char *dev, const char *devnr,
|
|
const char *path,
|
|
struct efi_device_path **device_pathp,
|
|
struct efi_device_path **image_pathp)
|
|
{
|
|
struct efi_device_path *image, *device;
|
|
efi_status_t ret;
|
|
|
|
#if IS_ENABLED(CONFIG_NETDEVICES)
|
|
if (!strcmp(dev, "Net") || !strcmp(dev, "Http")) {
|
|
ret = efi_net_new_dp(dev, devnr, eth_get_dev());
|
|
if (ret != EFI_SUCCESS)
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
ret = efi_dp_from_name(dev, devnr, path, &device, &image);
|
|
if (ret != EFI_SUCCESS)
|
|
return ret;
|
|
|
|
*device_pathp = device;
|
|
if (image) {
|
|
/* FIXME: image should not contain device */
|
|
struct efi_device_path *image_tmp = image;
|
|
|
|
efi_dp_split_file_path(image, &device, &image);
|
|
efi_free_pool(image_tmp);
|
|
}
|
|
*image_pathp = image;
|
|
log_debug("- boot device %pD\n", device);
|
|
if (image)
|
|
log_debug("- image %pD\n", image);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* efi_set_bootdev() - set boot device
|
|
*
|
|
* This function is called when a file is loaded, e.g. via the 'load' command.
|
|
* We use the path to this file to inform the UEFI binary about the boot device.
|
|
*
|
|
* For a valid image, it sets:
|
|
* - image_addr to the provided buffer
|
|
* - image_size to the provided buffer_size
|
|
* - bootefi_device_path to the EFI device-path
|
|
* - bootefi_image_path to the EFI image-path
|
|
*
|
|
* @dev: device, e.g. "MMC"
|
|
* @devnr: number of the device, e.g. "1:2"
|
|
* @path: path to file loaded
|
|
* @buffer: buffer with file loaded
|
|
* @buffer_size: size of file loaded
|
|
*/
|
|
void efi_set_bootdev(const char *dev, const char *devnr, const char *path,
|
|
void *buffer, size_t buffer_size)
|
|
{
|
|
efi_status_t ret;
|
|
|
|
log_debug("dev=%s, devnr=%s, path=%s, buffer=%p, size=%zx\n", dev,
|
|
devnr, path, buffer, buffer_size);
|
|
|
|
/* Forget overwritten image */
|
|
if (buffer + buffer_size >= image_addr &&
|
|
image_addr + image_size >= buffer)
|
|
efi_clear_bootdev();
|
|
|
|
/* Remember only PE-COFF and FIT images */
|
|
if (efi_check_pe(buffer, buffer_size, NULL) != EFI_SUCCESS) {
|
|
if (IS_ENABLED(CONFIG_FIT) &&
|
|
!fit_check_format(buffer, IMAGE_SIZE_INVAL)) {
|
|
/*
|
|
* FIT images of type EFI_OS are started via command
|
|
* bootm. We should not use their boot device with the
|
|
* bootefi command.
|
|
*/
|
|
buffer = 0;
|
|
buffer_size = 0;
|
|
} else {
|
|
log_debug("- not remembering image\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* efi_set_bootdev() is typically called repeatedly, recover memory */
|
|
efi_clear_bootdev();
|
|
|
|
image_addr = buffer;
|
|
image_size = buffer_size;
|
|
|
|
ret = calculate_paths(dev, devnr, path, &bootefi_device_path,
|
|
&bootefi_image_path);
|
|
if (ret) {
|
|
log_debug("- efi_dp_from_name() failed, err=%lx\n", ret);
|
|
efi_clear_bootdev();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* efi_run_image() - run loaded UEFI image
|
|
*
|
|
* @source_buffer: memory address of the UEFI image
|
|
* @source_size: size of the UEFI image
|
|
* @dp_dev: EFI device-path
|
|
* @dp_img: EFI image-path
|
|
* Return: status code
|
|
*/
|
|
static efi_status_t efi_run_image(void *source_buffer, efi_uintn_t source_size,
|
|
struct efi_device_path *dp_dev,
|
|
struct efi_device_path *dp_img)
|
|
{
|
|
efi_handle_t handle;
|
|
struct efi_device_path *msg_path, *file_path;
|
|
efi_status_t ret;
|
|
u16 *load_options;
|
|
|
|
file_path = efi_dp_concat(dp_dev, dp_img, 0);
|
|
msg_path = dp_img;
|
|
|
|
log_info("Booting %pD\n", msg_path);
|
|
|
|
ret = EFI_CALL(efi_load_image(false, efi_root, file_path, source_buffer,
|
|
source_size, &handle));
|
|
if (ret != EFI_SUCCESS) {
|
|
log_err("Loading image failed\n");
|
|
goto out;
|
|
}
|
|
|
|
/* Transfer environment variable as load options */
|
|
ret = efi_env_set_load_options(handle, "bootargs", &load_options);
|
|
if (ret != EFI_SUCCESS)
|
|
goto out;
|
|
|
|
ret = do_bootefi_exec(handle, load_options);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* efi_binary_run_dp() - run loaded UEFI image
|
|
*
|
|
* @image: memory address of the UEFI image
|
|
* @size: size of the UEFI image
|
|
* @fdt: device-tree
|
|
* @initrd: initrd
|
|
* @initrd_sz: initrd size
|
|
* @dp_dev: EFI device-path
|
|
* @dp_img: EFI image-path
|
|
*
|
|
* Execute an EFI binary image loaded at @image.
|
|
* @size may be zero if the binary is loaded with U-Boot load command.
|
|
*
|
|
* Return: status code
|
|
*/
|
|
static efi_status_t efi_binary_run_dp(void *image, size_t size, void *fdt,
|
|
void *initrd, size_t initd_sz,
|
|
struct efi_device_path *dp_dev,
|
|
struct efi_device_path *dp_img)
|
|
{
|
|
efi_status_t ret;
|
|
struct efi_device_path *dp_initrd;
|
|
|
|
/* Initialize EFI drivers */
|
|
ret = efi_init_obj_list();
|
|
if (ret != EFI_SUCCESS) {
|
|
log_err("Error: Cannot initialize UEFI sub-system, r = %lu\n",
|
|
ret & ~EFI_ERROR_MASK);
|
|
return -1;
|
|
}
|
|
|
|
ret = efi_install_fdt(fdt);
|
|
if (ret != EFI_SUCCESS)
|
|
return ret;
|
|
|
|
dp_initrd = efi_dp_from_mem(EFI_LOADER_DATA, (uintptr_t)initrd, initd_sz);
|
|
if (!dp_initrd)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
ret = efi_initrd_register(dp_initrd);
|
|
if (ret != EFI_SUCCESS)
|
|
return ret;
|
|
|
|
return efi_run_image(image, size, dp_dev, dp_img);
|
|
}
|
|
|
|
/**
|
|
* efi_binary_run() - run loaded UEFI image
|
|
*
|
|
* @image: memory address of the UEFI image
|
|
* @size: size of the UEFI image
|
|
* @fdt: device-tree
|
|
* @initrd: initrd
|
|
* @initrd_sz: initrd size
|
|
*
|
|
* Execute an EFI binary image loaded at @image.
|
|
* @size may be zero if the binary is loaded with U-Boot load command.
|
|
*
|
|
* Return: status code
|
|
*/
|
|
efi_status_t efi_binary_run(void *image, size_t size, void *fdt, void *initrd, size_t initrd_sz)
|
|
{
|
|
efi_handle_t mem_handle = NULL;
|
|
struct efi_device_path *file_path = NULL;
|
|
efi_status_t ret;
|
|
|
|
if (!bootefi_device_path || !bootefi_image_path) {
|
|
log_debug("Not loaded from disk\n");
|
|
/*
|
|
* Special case for efi payload not loaded from disk,
|
|
* such as 'bootefi hello' or for example payload
|
|
* loaded directly into memory via JTAG, etc:
|
|
*/
|
|
file_path = efi_dp_from_mem(EFI_RESERVED_MEMORY_TYPE,
|
|
(uintptr_t)image, size);
|
|
/*
|
|
* Make sure that device for device_path exist
|
|
* in load_image(). Otherwise, shell and grub will fail.
|
|
*/
|
|
ret = efi_install_multiple_protocol_interfaces(&mem_handle,
|
|
&efi_guid_device_path,
|
|
file_path, NULL);
|
|
if (ret != EFI_SUCCESS)
|
|
goto out;
|
|
|
|
bootefi_device_path = file_path;
|
|
bootefi_image_path = NULL;
|
|
} else {
|
|
log_debug("Loaded from disk\n");
|
|
}
|
|
|
|
ret = efi_binary_run_dp(image, size, fdt, initrd, initrd_sz, bootefi_device_path,
|
|
bootefi_image_path);
|
|
out:
|
|
if (mem_handle) {
|
|
efi_status_t r;
|
|
|
|
r = efi_uninstall_multiple_protocol_interfaces(mem_handle,
|
|
&efi_guid_device_path, file_path, NULL);
|
|
if (r != EFI_SUCCESS)
|
|
log_err("Uninstalling protocol interfaces failed\n");
|
|
}
|
|
efi_free_pool(file_path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* calc_dev_name() - Calculate the device name to give to EFI
|
|
*
|
|
* If not supported, this shows an error.
|
|
*
|
|
* Return name, or NULL if not supported
|
|
*/
|
|
static const char *calc_dev_name(struct bootflow *bflow)
|
|
{
|
|
const struct udevice *media_dev;
|
|
|
|
media_dev = dev_get_parent(bflow->dev);
|
|
|
|
if (!bflow->blk) {
|
|
if (device_get_uclass_id(media_dev) == UCLASS_ETH)
|
|
return "Net";
|
|
|
|
log_err("Cannot boot EFI app on media '%s'\n",
|
|
dev_get_uclass_name(media_dev));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (device_get_uclass_id(media_dev) == UCLASS_MASS_STORAGE)
|
|
return "usb";
|
|
|
|
return blk_get_uclass_name(device_get_uclass_id(media_dev));
|
|
}
|
|
|
|
efi_status_t efi_bootflow_run(struct bootflow *bflow)
|
|
{
|
|
struct efi_device_path *device, *image;
|
|
const struct udevice *media_dev;
|
|
struct blk_desc *desc = NULL;
|
|
const char *dev_name;
|
|
char devnum_str[9];
|
|
efi_status_t ret;
|
|
void *fdt;
|
|
|
|
media_dev = dev_get_parent(bflow->dev);
|
|
if (bflow->blk) {
|
|
desc = dev_get_uclass_plat(bflow->blk);
|
|
|
|
snprintf(devnum_str, sizeof(devnum_str), "%x:%x",
|
|
desc ? desc->devnum : dev_seq(media_dev), bflow->part);
|
|
} else {
|
|
*devnum_str = '\0';
|
|
}
|
|
|
|
dev_name = calc_dev_name(bflow);
|
|
log_debug("dev_name '%s' devnum_str '%s' fname '%s' media_dev '%s'\n",
|
|
dev_name, devnum_str, bflow->fname, media_dev->name);
|
|
if (!dev_name)
|
|
return EFI_UNSUPPORTED;
|
|
ret = calculate_paths(dev_name, devnum_str, bflow->fname, &device,
|
|
&image);
|
|
if (ret)
|
|
return EFI_UNSUPPORTED;
|
|
|
|
if (bflow->flags & BOOTFLOWF_USE_BUILTIN_FDT) {
|
|
log_debug("Booting with built-in fdt\n");
|
|
fdt = EFI_FDT_USE_INTERNAL;
|
|
} else {
|
|
log_debug("Booting with external fdt\n");
|
|
fdt = map_sysmem(bflow->fdt_addr, 0);
|
|
}
|
|
ret = efi_binary_run_dp(bflow->buf, bflow->size, fdt, NULL, 0, device, image);
|
|
|
|
return ret;
|
|
}
|