As counterpart to the EFI-less bootm handlers for the different architectures, define EFI-enabled variants that will initialize the EFI loader support and load the PE by EFI means.
Signed-off-by: Ahmad Fatoum <[email protected]> --- efi/loader/Makefile | 1 + efi/loader/bootm.c | 358 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 efi/loader/bootm.c diff --git a/efi/loader/Makefile b/efi/loader/Makefile index 62483057b426..2a78361e58d5 100644 --- a/efi/loader/Makefile +++ b/efi/loader/Makefile @@ -14,3 +14,4 @@ obj-y += pe.o obj-y += loadopts.o obj-$(CONFIG_BOOT) += bootesp.o obj-$(CONFIG_EFI_LOADER_BOOTMGR) += efibootmgr.o +obj-$(CONFIG_BOOTM) += bootm.o diff --git a/efi/loader/bootm.c b/efi/loader/bootm.c new file mode 100644 index 000000000000..dffe53bcf9c8 --- /dev/null +++ b/efi/loader/bootm.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0 +// SPDX-Comment: Origin-URL: https://github.com/u-boot/u-boot/blob/162a6b3df349295bf766c1d128d71b4547e8d56e/lib/efi_loader/efi_helper.c +/* + * Copyright (c) 2016 Alexander Graf + */ +#define pr_fmt(fmt) "efi-loader: bootm: " fmt + +#include <clock.h> +#include <linux/sizes.h> +#include <memory.h> +#include <command.h> +#include <magicvar.h> +#include <init.h> +#include <driver.h> +#include <io.h> +#include <bootargs.h> +#include <malloc.h> +#include <string.h> +#include <linux/err.h> +#include <fs.h> +#include <filetype.h> +#include <libfile.h> +#include <bootm.h> +#include <wchar.h> +#include <efi/mode.h> +#include <efi/loader.h> +#include <efi/loader/object.h> +#include <efi/loader/option.h> +#include <efi/loader/table.h> +#include <efi/loader/devicepath.h> +#include <efi/loader/image.h> +#include <efi/loader/event.h> +#include <efi/guid.h> +#include <efi/services.h> +#include <efi/error.h> +#include <efi/initrd.h> +#include <efi/devicepath.h> + +/** + * copy_fdt() - Copy the device tree to a new location available to EFI + * + * The FDT is copied to a suitable location within the EFI memory map. + * Additional 12 KiB are added to the space in case the device tree needs to be + * expanded later with fdt_open_into(). + * + * @fdtp: On entry a pointer to the flattened device tree. + * On exit a pointer to the copy of the flattened device tree. + * FDT start + * Return: status code + */ +static efi_status_t copy_fdt(void **fdtp) +{ + efi_status_t efiret; + void *fdt, *new_fdt; + static u64 new_fdt_addr; + static efi_uintn_t fdt_pages; + size_t fdt_size; + + /* + * Remove the configuration table that might already be + * installed, ignoring EFI_NOT_FOUND if no device-tree + * is installed + */ + efi_install_configuration_table(&efi_fdt_guid, NULL); + + if (new_fdt_addr) { + pr_debug("%s: Found allocated memory at %#llx, with %#zx pages\n", + __func__, new_fdt_addr, fdt_pages); + + efiret = efi_free_pages(new_fdt_addr, fdt_pages); + if (efiret != EFI_SUCCESS) + pr_err("Unable to free up existing FDT memory region\n"); + + new_fdt_addr = 0; + fdt_pages = 0; + } + + /* + * Give us at least 12 KiB of breathing room in case the device tree + * needs to be expanded later. + */ + fdt = *fdtp; + fdt_pages = efi_size_in_pages(fdt_totalsize(fdt) + 0x3000); + fdt_size = fdt_pages << EFI_PAGE_SHIFT; + + /* + * Safe fdt location is at 127 MiB. + * On the sandbox convert from the sandbox address space. + */ + efiret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_ACPI_RECLAIM_MEMORY, fdt_pages, + &new_fdt_addr, "FDT"); + if (efiret != EFI_SUCCESS) { + pr_err("Failed to reserve space for FDT\n"); + goto done; + } + + new_fdt = (void *)(uintptr_t)new_fdt_addr; + memcpy(new_fdt, fdt, fdt_totalsize(fdt)); + fdt_set_totalsize(new_fdt, fdt_size); + + *fdtp = new_fdt; + +done: + return efiret; +} + +/** + * efi_install_fdt() - install device tree + * + * If fdt is not NULL, the device tree located at that memory + * address will will be installed as configuration table, otherwise the device + * tree located at the address indicated by environment variable fdt_addr or as + * fallback fdtcontroladdr will be used. + * + * On architectures using ACPI tables device trees shall not be installed as + * configuration table. + * + * @fdt: address of device tree + * the hardware device tree as indicated by environment variable + * fdt_addr or as fallback the internal device tree as indicated by + * the environment variable fdtcontroladdr + * Return: status code + */ +static efi_status_t efi_install_fdt(void *fdt) +{ + /* + * The EBBR spec requires that we have either an FDT or an ACPI table + * but not both. + */ + efi_status_t ret; + + /* Install device tree */ + if (fdt_check_header(fdt)) { + pr_err("invalid device tree\n"); + return EFI_LOAD_ERROR; + } + + /* Prepare device tree for payload */ + ret = copy_fdt(&fdt); + if (ret) { + pr_err("out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + /* Install device tree as UEFI table */ + ret = efi_install_configuration_table(&efi_fdt_guid, fdt); + if (ret != EFI_SUCCESS) { + pr_err("failed to install device tree\n"); + return ret; + } + + return EFI_SUCCESS; +} + +/** + * efi_install_initrd() - install initrd + * + * Install the initrd located at @initrd using the EFI_LOAD_FILE2 + * protocol. + * + * @initrd: address of initrd or NULL if none is provided + * @initrd_sz: size of initrd + * Return: status code + */ +static efi_status_t efi_install_initrd(struct image_data *data, + struct resource *source) +{ + const struct resource *initrd_res; + unsigned long initrd_start; + + if (!IS_ENABLED(CONFIG_BOOTM_INITRD)) + return EFI_SUCCESS; + + if (UIMAGE_IS_ADDRESS_VALID(data->initrd_address)) + initrd_start = data->initrd_address; + else + initrd_start = EFI_PAGE_ALIGN(source->end + 1); + + initrd_res = bootm_load_initrd(data, initrd_start); + if (IS_ERR(initrd_res)) + return PTR_ERR(initrd_res); + if (initrd_res) + efi_initrd_register((void *)initrd_res->start, + resource_size(initrd_res)); + + return EFI_SUCCESS; +} + +static int efi_loader_bootm(struct image_data *data) +{ + resource_size_t start, end; + void *load_option = NULL; + u32 load_option_size = 0; + efi_handle_t handle; + struct efi_device_path *file_path = NULL; + struct resource *source; + struct efi_event *evt; + size_t exit_data_size = 0; + u16 *exit_data = NULL; + efi_status_t efiret; + int ret = 0; + void *fdt; + int flags = 0; + + memory_bank_first_find_space(&start, &end); + data->os_address = start; + + source = file_to_sdram(data->os_file, data->os_address, MEMTYPE_LOADER_CODE); + if (!source) + return -EINVAL; + + if (filetype_is_linux_efi_image(data->os_type)) { + const char *options; + + options = linux_bootargs_get(); + if (options) { + load_option = xstrdup_char_to_wchar(options); + load_option_size = (strlen(options) + 1) * sizeof(wchar_t); + } + } + + file_path = efi_dp_from_file(AT_FDCWD, data->os_file); + + pr_info("Loading %pD\n", file_path); + + /* Initialize EFI drivers */ + efiret = efi_init_obj_list(); + if (efiret) { + pr_err("Cannot initialize UEFI sub-system: %pe\n", + ERR_PTR(-efi_errno(ret))); + goto out; + } + + ret = -EINVAL; + + fdt = bootm_get_devicetree(data); + if (IS_ERR(fdt)) + return PTR_ERR(fdt); + if (fdt) { + ret = efi_install_fdt(fdt); + if (ret) + return ret; + } + + efiret = efi_install_initrd(data, source); + if(efiret != EFI_SUCCESS) + goto out; + + if (data->verbose >= 1) + flags |= EFI_VERBOSE_RUN; + if (data->dryrun) + flags |= EFI_DRYRUN; + + efiret = efiloader_load_image(false, efi_root, file_path, + (void *)source->start, + resource_size(source), &handle); + if (efiret != EFI_SUCCESS) { + pr_err("Loading image failed\n"); + goto out; + } + + efiret = efi_set_load_options(handle, load_option_size, load_option); + if (efiret) + goto out; + + /* On ARM, PBL should have already moved us into EL2 here, so + * no need to switch modes + */ + + /* + * The UEFI standard requires that the watchdog timer is set to five + * minutes when invoking an EFI boot option. + * + * Unified Extensible Firmware Interface (UEFI), version 2.7 Errata A + * 7.5. Miscellaneous Boot Services - EFI_BOOT_SERVICES.SetWatchdogTimer + */ + ret = efi_set_watchdog(300); + if (ret != EFI_SUCCESS) { + pr_err("failed to set watchdog timer\n"); + goto out; + } + + /* Call our payload! */ + ret = __efi_start_image(handle, &exit_data_size, &exit_data, flags); + if (ret != EFI_SUCCESS) { + pr_err("## Application failed, r = %lu\n", + ret & ~EFI_ERROR_MASK); + if (exit_data) { + pr_err("## %ls\n", exit_data); + efi_free_pool(exit_data); + } + } + + /* Notify EFI_EVENT_GROUP_RETURN_TO_EFIBOOTMGR event group. */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !efi_guidcmp(*evt->group, + efi_guid_event_group_return_to_efibootmgr)) { + efi_signal_event(evt); + systab.boottime->close_event(evt); + break; + } + } + + /* Control is returned to us, disable EFI watchdog */ + efi_set_watchdog(0); + + return ret; + +out: + efi_initrd_unregister(); + efi_install_configuration_table(&efi_fdt_guid, NULL); + efi_free_pool(file_path); + free(load_option); + release_sdram_region(source); + + return ret ?: -efi_errno(efiret); +} + +static struct image_handler riscv_linux_efi_handler = { + .name = "RISC-V Linux/EFI image", + .bootm = efi_loader_bootm, + .check_image = bootm_efi_check_image, + .filetype = filetype_riscv_efi_linux_image, +}; + +static struct image_handler aarch64_linux_efi_handler = { + .name = "ARM aarch64 Linux/EFI image", + .bootm = efi_loader_bootm, + .check_image = bootm_efi_check_image, + .filetype = filetype_arm64_efi_linux_image, +}; + +static struct image_handler arm32_linux_efi_handler = { + .name = "ARM arm32 Linux/EFI image", + .bootm = efi_loader_bootm, + .check_image = bootm_efi_check_image, + .filetype = filetype_arm_efi_zimage, +}; + +static struct image_handler efi_image_loader = { + .name = "EFI Application", + .bootm = efi_loader_bootm, + .filetype = filetype_exe, +}; + +static int efiloader_bootm_register(void) +{ + if (IS_ENABLED(CONFIG_ARM32)) + register_image_handler(&arm32_linux_efi_handler); + if (IS_ENABLED(CONFIG_ARM64)) + register_image_handler(&aarch64_linux_efi_handler); + if (IS_ENABLED(CONFIG_RISCV)) + register_image_handler(&riscv_linux_efi_handler); + return register_image_handler(&efi_image_loader); +} +late_initcall(efiloader_bootm_register); -- 2.47.3
