| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * EFI application loader |
| * |
| * Copyright (c) 2016 Alexander Graf |
| */ |
| |
| #define LOG_CATEGORY LOGC_EFI |
| |
| #include <command.h> |
| #include <efi.h> |
| #include <efi_loader.h> |
| #include <exports.h> |
| #include <log.h> |
| #include <malloc.h> |
| #include <mapmem.h> |
| #include <vsprintf.h> |
| #include <asm-generic/sections.h> |
| #include <asm/global_data.h> |
| #include <linux/string.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| static struct efi_device_path *test_image_path; |
| static struct efi_device_path *test_device_path; |
| |
| static efi_status_t bootefi_run_prepare(const char *load_options_path, |
| struct efi_device_path *device_path, |
| struct efi_device_path *image_path, |
| struct efi_loaded_image_obj **image_objp, |
| struct efi_loaded_image **loaded_image_infop) |
| { |
| efi_status_t ret; |
| u16 *load_options; |
| |
| ret = efi_setup_loaded_image(device_path, image_path, image_objp, |
| loaded_image_infop); |
| if (ret != EFI_SUCCESS) |
| return ret; |
| |
| /* Transfer environment variable as load options */ |
| return efi_env_set_load_options((efi_handle_t)*image_objp, |
| load_options_path, |
| &load_options); |
| } |
| |
| /** |
| * bootefi_test_prepare() - prepare to run an EFI test |
| * |
| * Prepare to run a test as if it were provided by a loaded image. |
| * |
| * @image_objp: pointer to be set to the loaded image handle |
| * @loaded_image_infop: pointer to be set to the loaded image protocol |
| * @path: dummy file path used to construct the device path |
| * set in the loaded image protocol |
| * @load_options_path: name of a U-Boot environment variable. Its value is |
| * set as load options in the loaded image protocol. |
| * Return: status code |
| */ |
| static efi_status_t bootefi_test_prepare |
| (struct efi_loaded_image_obj **image_objp, |
| struct efi_loaded_image **loaded_image_infop, const char *path, |
| const char *load_options_path) |
| { |
| efi_status_t ret; |
| |
| /* Construct a dummy device path */ |
| test_device_path = efi_dp_from_mem(EFI_RESERVED_MEMORY_TYPE, 0, 0); |
| if (!test_device_path) |
| return EFI_OUT_OF_RESOURCES; |
| |
| test_image_path = efi_dp_from_file(NULL, path); |
| if (!test_image_path) { |
| ret = EFI_OUT_OF_RESOURCES; |
| goto failure; |
| } |
| |
| ret = bootefi_run_prepare(load_options_path, test_device_path, |
| test_image_path, image_objp, |
| loaded_image_infop); |
| if (ret == EFI_SUCCESS) |
| return ret; |
| |
| failure: |
| efi_free_pool(test_device_path); |
| efi_free_pool(test_image_path); |
| /* TODO: not sure calling clear function is necessary */ |
| efi_clear_bootdev(); |
| return ret; |
| } |
| |
| /** |
| * do_efi_selftest() - execute EFI selftest |
| * |
| * Return: status code |
| */ |
| static int do_efi_selftest(void) |
| { |
| struct efi_loaded_image_obj *image_obj; |
| struct efi_loaded_image *loaded_image_info; |
| efi_status_t ret; |
| |
| ret = bootefi_test_prepare(&image_obj, &loaded_image_info, |
| "\\selftest", "efi_selftest"); |
| if (ret != EFI_SUCCESS) |
| return CMD_RET_FAILURE; |
| |
| /* Execute the test */ |
| ret = EFI_CALL(efi_selftest(&image_obj->header, &systab)); |
| efi_restore_gd(); |
| free(loaded_image_info->load_options); |
| efi_free_pool(test_device_path); |
| efi_free_pool(test_image_path); |
| if (ret != EFI_SUCCESS) |
| efi_delete_handle(&image_obj->header); |
| else |
| ret = efi_delete_handle(&image_obj->header); |
| |
| return ret != EFI_SUCCESS; |
| } |
| |
| /** |
| * do_bootefi() - execute `bootefi` command |
| * |
| * @cmdtp: table entry describing command |
| * @flag: bitmap indicating how the command was invoked |
| * @argc: number of arguments |
| * @argv: command line arguments |
| * Return: status code |
| */ |
| static int do_bootefi(struct cmd_tbl *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| efi_status_t ret; |
| char *p; |
| void *fdt, *image_buf; |
| unsigned long addr, size; |
| void *image_addr; |
| size_t image_size; |
| |
| if (argc < 2) |
| return CMD_RET_USAGE; |
| |
| if (argc > 2) { |
| uintptr_t fdt_addr; |
| |
| fdt_addr = hextoul(argv[2], NULL); |
| fdt = map_sysmem(fdt_addr, 0); |
| } else { |
| fdt = EFI_FDT_USE_INTERNAL; |
| } |
| |
| if (IS_ENABLED(CONFIG_CMD_BOOTEFI_BOOTMGR) && |
| !strcmp(argv[1], "bootmgr")) { |
| ret = efi_bootmgr_run(fdt); |
| |
| if (ret == EFI_INVALID_PARAMETER) |
| return CMD_RET_USAGE; |
| else if (ret) |
| return CMD_RET_FAILURE; |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| if (IS_ENABLED(CONFIG_CMD_BOOTEFI_SELFTEST) && |
| !strcmp(argv[1], "selftest")) { |
| /* 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 CMD_RET_FAILURE; |
| } |
| |
| ret = efi_install_fdt(fdt); |
| if (ret == EFI_INVALID_PARAMETER) |
| return CMD_RET_USAGE; |
| else if (ret != EFI_SUCCESS) |
| return CMD_RET_FAILURE; |
| |
| return do_efi_selftest(); |
| } |
| |
| if (!IS_ENABLED(CONFIG_CMD_BOOTEFI_BINARY)) |
| return CMD_RET_SUCCESS; |
| |
| if (IS_ENABLED(CONFIG_CMD_BOOTEFI_HELLO) && |
| !strcmp(argv[1], "hello")) { |
| image_buf = __efi_helloworld_begin; |
| size = __efi_helloworld_end - __efi_helloworld_begin; |
| /* TODO: not sure calling clear function is necessary */ |
| efi_clear_bootdev(); |
| } else { |
| addr = strtoul(argv[1], NULL, 16); |
| /* Check that a numeric value was passed */ |
| if (!addr) |
| return CMD_RET_USAGE; |
| image_buf = map_sysmem(addr, 0); |
| |
| p = strchr(argv[1], ':'); |
| if (p) { |
| size = strtoul(++p, NULL, 16); |
| if (!size) |
| return CMD_RET_USAGE; |
| efi_clear_bootdev(); |
| } else { |
| /* Image should be already loaded */ |
| efi_get_image_parameters(&image_addr, &image_size); |
| |
| if (image_buf != image_addr) { |
| log_err("No UEFI binary known at %s\n", |
| argv[1]); |
| return CMD_RET_FAILURE; |
| } |
| size = image_size; |
| } |
| } |
| |
| ret = efi_binary_run(image_buf, size, fdt); |
| |
| if (ret == EFI_INVALID_PARAMETER) |
| return CMD_RET_USAGE; |
| else if (ret) |
| return CMD_RET_FAILURE; |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| U_BOOT_LONGHELP(bootefi, |
| "<image address>[:<image size>] [<fdt address>]\n" |
| " - boot EFI payload\n" |
| #ifdef CONFIG_CMD_BOOTEFI_HELLO |
| "bootefi hello\n" |
| " - boot a sample Hello World application stored within U-Boot\n" |
| #endif |
| #ifdef CONFIG_CMD_BOOTEFI_SELFTEST |
| "bootefi selftest [fdt address]\n" |
| " - boot an EFI selftest application stored within U-Boot\n" |
| " Use environment variable efi_selftest to select a single test.\n" |
| " Use 'setenv efi_selftest list' to enumerate all tests.\n" |
| #endif |
| #ifdef CONFIG_CMD_BOOTEFI_BOOTMGR |
| "bootefi bootmgr [fdt address]\n" |
| " - load and boot EFI payload based on BootOrder/BootXXXX variables.\n" |
| "\n" |
| " If specified, the device tree located at <fdt address> gets\n" |
| " exposed as EFI configuration table.\n" |
| #endif |
| ); |
| |
| U_BOOT_CMD( |
| bootefi, 4, 0, do_bootefi, |
| "Boots an EFI payload from memory", |
| bootefi_help_text |
| ); |