diff options
Diffstat (limited to 'lib/efi_loader/efi_helper.c')
| -rw-r--r-- | lib/efi_loader/efi_helper.c | 287 | 
1 files changed, 286 insertions, 1 deletions
| diff --git a/lib/efi_loader/efi_helper.c b/lib/efi_loader/efi_helper.c index cdfd16ea774..11066eb505d 100644 --- a/lib/efi_loader/efi_helper.c +++ b/lib/efi_loader/efi_helper.c @@ -4,14 +4,20 @@   */  #define LOG_CATEGORY LOGC_EFI -#include <common.h> +#include <bootm.h>  #include <env.h> +#include <image.h> +#include <log.h>  #include <malloc.h> +#include <mapmem.h>  #include <dm.h>  #include <fs.h> +#include <efi_api.h>  #include <efi_load_initrd.h>  #include <efi_loader.h>  #include <efi_variable.h> +#include <linux/libfdt.h> +#include <linux/list.h>  #if defined(CONFIG_CMD_EFIDEBUG) || defined(CONFIG_EFI_LOAD_FILE2_INITRD)  /* GUID used by Linux to identify the LoadFile2 protocol with the initrd */ @@ -282,3 +288,282 @@ bool efi_search_bootorder(u16 *bootorder, efi_uintn_t num, u32 target, u32 *inde  	return false;  } + +/** + * efi_env_set_load_options() - set load options from environment variable + * + * @handle:		the image handle + * @env_var:		name of the environment variable + * @load_options:	pointer to load options (output) + * Return:		status code + */ +efi_status_t efi_env_set_load_options(efi_handle_t handle, +				      const char *env_var, +				      u16 **load_options) +{ +	const char *env = env_get(env_var); +	size_t size; +	u16 *pos; +	efi_status_t ret; + +	*load_options = NULL; +	if (!env) +		return EFI_SUCCESS; +	size = sizeof(u16) * (utf8_utf16_strlen(env) + 1); +	pos = calloc(size, 1); +	if (!pos) +		return EFI_OUT_OF_RESOURCES; +	*load_options = pos; +	utf8_utf16_strcpy(&pos, env); +	ret = efi_set_load_options(handle, size, *load_options); +	if (ret != EFI_SUCCESS) { +		free(*load_options); +		*load_options = NULL; +	} +	return ret; +} + +/** + * 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) +{ +	unsigned long fdt_ram_start = -1L, fdt_pages; +	efi_status_t ret = 0; +	void *fdt, *new_fdt; +	u64 new_fdt_addr; +	uint fdt_size; +	int i; + +	for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { +		u64 ram_start = gd->bd->bi_dram[i].start; +		u64 ram_size = gd->bd->bi_dram[i].size; + +		if (!ram_size) +			continue; + +		if (ram_start < fdt_ram_start) +			fdt_ram_start = ram_start; +	} + +	/* +	 * 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; + +	ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, +				 EFI_ACPI_RECLAIM_MEMORY, fdt_pages, +				 &new_fdt_addr); +	if (ret != EFI_SUCCESS) { +		log_err("ERROR: 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 = (void *)(uintptr_t)new_fdt_addr; +done: +	return ret; +} + +/** + * get_config_table() - get configuration table + * + * @guid:	GUID of the configuration table + * Return:	pointer to configuration table or NULL + */ +static void *get_config_table(const efi_guid_t *guid) +{ +	size_t i; + +	for (i = 0; i < systab.nr_tables; i++) { +		if (!guidcmp(guid, &systab.tables[i].guid)) +			return systab.tables[i].table; +	} +	return NULL; +} + +/** + * efi_install_fdt() - install device tree + * + * If fdt is not EFI_FDT_USE_INTERNAL, the device tree located at that memory + * address 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 or EFI_FDT_USE_INTERNAL to use + *		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 + */ +efi_status_t efi_install_fdt(void *fdt) +{ +	struct bootm_headers img = { 0 }; +	efi_status_t ret; + +	/* +	 * The EBBR spec requires that we have either an FDT or an ACPI table +	 * but not both. +	 */ +	if (CONFIG_IS_ENABLED(GENERATE_ACPI_TABLE) && fdt) +		log_warning("WARNING: Can't have ACPI table and device tree - ignoring DT.\n"); + +	if (fdt == EFI_FDT_USE_INTERNAL) { +		const char *fdt_opt; +		uintptr_t fdt_addr; + +		/* Look for device tree that is already installed */ +		if (get_config_table(&efi_guid_fdt)) +			return EFI_SUCCESS; +		/* Check if there is a hardware device tree */ +		fdt_opt = env_get("fdt_addr"); +		/* Use our own device tree as fallback */ +		if (!fdt_opt) { +			fdt_opt = env_get("fdtcontroladdr"); +			if (!fdt_opt) { +				log_err("ERROR: need device tree\n"); +				return EFI_NOT_FOUND; +			} +		} +		fdt_addr = hextoul(fdt_opt, NULL); +		if (!fdt_addr) { +			log_err("ERROR: invalid $fdt_addr or $fdtcontroladdr\n"); +			return EFI_LOAD_ERROR; +		} +		fdt = map_sysmem(fdt_addr, 0); +	} + +	/* Install device tree */ +	if (fdt_check_header(fdt)) { +		log_err("ERROR: invalid device tree\n"); +		return EFI_LOAD_ERROR; +	} + +	/* Create memory reservations as indicated by the device tree */ +	efi_carve_out_dt_rsv(fdt); + +	if (CONFIG_IS_ENABLED(GENERATE_ACPI_TABLE)) +		return EFI_SUCCESS; + +	/* Prepare device tree for payload */ +	ret = copy_fdt(&fdt); +	if (ret) { +		log_err("ERROR: out of memory\n"); +		return EFI_OUT_OF_RESOURCES; +	} + +	if (image_setup_libfdt(&img, fdt, NULL)) { +		log_err("ERROR: failed to process device tree\n"); +		return EFI_LOAD_ERROR; +	} + +	efi_try_purge_kaslr_seed(fdt); + +	if (CONFIG_IS_ENABLED(EFI_TCG2_PROTOCOL_MEASURE_DTB)) { +		ret = efi_tcg2_measure_dtb(fdt); +		if (ret == EFI_SECURITY_VIOLATION) { +			log_err("ERROR: failed to measure DTB\n"); +			return ret; +		} +	} + +	/* Install device tree as UEFI table */ +	ret = efi_install_configuration_table(&efi_guid_fdt, fdt); +	if (ret != EFI_SUCCESS) { +		log_err("ERROR: failed to install device tree\n"); +		return ret; +	} + +	return EFI_SUCCESS; +} + +/** + * do_bootefi_exec() - execute EFI binary + * + * The image indicated by @handle is started. When it returns the allocated + * memory for the @load_options is freed. + * + * @handle:		handle of loaded image + * @load_options:	load options + * Return:		status code + * + * Load the EFI binary into a newly assigned memory unwinding the relocation + * information, install the loaded image protocol, and call the binary. + */ +efi_status_t do_bootefi_exec(efi_handle_t handle, void *load_options) +{ +	efi_status_t ret; +	efi_uintn_t exit_data_size = 0; +	u16 *exit_data = NULL; +	struct efi_event *evt; + +	/* On ARM switch from EL3 or secure mode to EL2 or non-secure mode */ +	switch_to_non_secure_mode(); + +	/* +	 * 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) { +		log_err("ERROR: Failed to set watchdog timer\n"); +		goto out; +	} + +	/* Call our payload! */ +	ret = EFI_CALL(efi_start_image(handle, &exit_data_size, &exit_data)); +	if (ret != EFI_SUCCESS) { +		log_err("## Application failed, r = %lu\n", +			ret & ~EFI_ERROR_MASK); +		if (exit_data) { +			log_err("## %ls\n", exit_data); +			efi_free_pool(exit_data); +		} +	} + +	efi_restore_gd(); + +out: +	free(load_options); + +	if (IS_ENABLED(CONFIG_EFI_LOAD_FILE2_INITRD)) { +		if (efi_initrd_deregister() != EFI_SUCCESS) +			log_err("Failed to remove loadfile2 for initrd\n"); +	} + +	/* Notify EFI_EVENT_GROUP_RETURN_TO_EFIBOOTMGR event group. */ +	list_for_each_entry(evt, &efi_events, link) { +		if (evt->group && +		    !guidcmp(evt->group, +			     &efi_guid_event_group_return_to_efibootmgr)) { +			efi_signal_event(evt); +			EFI_CALL(systab.boottime->close_event(evt)); +			break; +		} +	} + +	/* Control is returned to U-Boot, disable EFI watchdog */ +	efi_set_watchdog(0); + +	return ret; +} | 
