| // 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_device_path.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 <linux/string.h> | |
| 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; | |
| (*image_objp)->auth_status = EFI_IMAGE_AUTH_PASSED; | |
| (*image_objp)->entry = efi_selftest; | |
| /* 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 = do_bootefi_exec(&image_obj->header, | |
| 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, *initrd = NULL, *image_buf; | |
| unsigned long addr, size, rd_len = 0, fdt_addr = 0; | |
| void *image_addr; | |
| size_t image_size; | |
| int fdt_arg = 2; | |
| if (argc < 2) | |
| return CMD_RET_USAGE; | |
| /* Initialize EFI drivers */ | |
| ret = efi_init_obj_list(); | |
| if (ret != EFI_SUCCESS) | |
| return CMD_RET_FAILURE; | |
| if (argc > 2) { | |
| ulong rd_addr = 0; | |
| char *end = strchr(argv[2], ':'); | |
| if (end) { | |
| rd_addr = hextoul(argv[2], NULL); | |
| if (!rd_addr) | |
| return CMD_RET_USAGE; | |
| rd_len = hextoul(++end, NULL); | |
| initrd = map_sysmem(rd_addr, rd_len); | |
| ++fdt_arg; | |
| } | |
| } | |
| if (argc > fdt_arg + 1) | |
| return CMD_RET_USAGE; | |
| if (argc == fdt_arg + 1) | |
| fdt_addr = hextoul(argv[fdt_arg], NULL); | |
| if (fdt_addr) | |
| 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_SUCCESS) | |
| return CMD_RET_FAILURE; | |
| return CMD_RET_SUCCESS; | |
| } | |
| if (IS_ENABLED(CONFIG_CMD_BOOTEFI_SELFTEST) && | |
| !strcmp(argv[1], "selftest")) { | |
| ret = efi_install_fdt(fdt); | |
| 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, initrd, rd_len); | |
| if (ret != EFI_SUCCESS) | |
| return CMD_RET_FAILURE; | |
| return CMD_RET_SUCCESS; | |
| } | |
| U_BOOT_LONGHELP(bootefi, | |
| "<image address>[:<size>] [<initrd address>:<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 | |
| ); |