From: Alexander Graf Date: Fri, 4 Mar 2016 00:10:01 +0000 (+0100) Subject: efi_loader: Add runtime services X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=50149ea37a21dcbed675297f1536c31a7db39c19;p=u-boot.git efi_loader: Add runtime services After booting has finished, EFI allows firmware to still interact with the OS using the "runtime services". These callbacks live in a separate address space, since they are available long after U-Boot has been overwritten by the OS. This patch adds enough framework for arbitrary code inside of U-Boot to become a runtime service with the right section attributes set. For now, we don't make use of it yet though. We could maybe in the future map U-boot environment variables to EFI variables here. Signed-off-by: Alexander Graf Reviewed-by: Simon Glass Tested-by: Simon Glass --- diff --git a/arch/arm/config.mk b/arch/arm/config.mk index 8fa57ecfd8..9af6c37218 100644 --- a/arch/arm/config.mk +++ b/arch/arm/config.mk @@ -122,6 +122,10 @@ ifdef CONFIG_OF_EMBED OBJCOPYFLAGS += -j .dtb.init.rodata endif +ifdef CONFIG_EFI_LOADER +OBJCOPYFLAGS += -j .efi_runtime -j .efi_runtime_rel +endif + ifneq ($(CONFIG_IMX_CONFIG),) ifdef CONFIG_SPL ifndef CONFIG_SPL_BUILD diff --git a/arch/arm/cpu/armv8/u-boot.lds b/arch/arm/cpu/armv8/u-boot.lds index 4c12222370..fd15ad5963 100644 --- a/arch/arm/cpu/armv8/u-boot.lds +++ b/arch/arm/cpu/armv8/u-boot.lds @@ -42,6 +42,22 @@ SECTIONS . = ALIGN(8); + .efi_runtime : { + __efi_runtime_start = .; + *(efi_runtime_text) + *(efi_runtime_data) + __efi_runtime_stop = .; + } + + .efi_runtime_rel : { + __efi_runtime_rel_start = .; + *(.relaefi_runtime_text) + *(.relaefi_runtime_data) + __efi_runtime_rel_stop = .; + } + + . = ALIGN(8); + .image_copy_end : { *(.__image_copy_end) diff --git a/arch/arm/cpu/u-boot.lds b/arch/arm/cpu/u-boot.lds index e148ab7513..13aa4fa488 100644 --- a/arch/arm/cpu/u-boot.lds +++ b/arch/arm/cpu/u-boot.lds @@ -90,6 +90,36 @@ SECTIONS . = ALIGN(4); + .__efi_runtime_start : { + *(.__efi_runtime_start) + } + + .efi_runtime : { + *(efi_runtime_text) + *(efi_runtime_data) + } + + .__efi_runtime_stop : { + *(.__efi_runtime_stop) + } + + .efi_runtime_rel_start : + { + *(.__efi_runtime_rel_start) + } + + .efi_runtime_rel : { + *(.relefi_runtime_text) + *(.relefi_runtime_data) + } + + .efi_runtime_rel_stop : + { + *(.__efi_runtime_rel_stop) + } + + . = ALIGN(4); + .image_copy_end : { *(.__image_copy_end) diff --git a/arch/arm/lib/sections.c b/arch/arm/lib/sections.c index a1205c370d..6a94522418 100644 --- a/arch/arm/lib/sections.c +++ b/arch/arm/lib/sections.c @@ -27,4 +27,8 @@ char __rel_dyn_start[0] __attribute__((section(".__rel_dyn_start"))); char __rel_dyn_end[0] __attribute__((section(".__rel_dyn_end"))); char __secure_start[0] __attribute__((section(".__secure_start"))); char __secure_end[0] __attribute__((section(".__secure_end"))); +char __efi_runtime_start[0] __attribute__((section(".__efi_runtime_start"))); +char __efi_runtime_stop[0] __attribute__((section(".__efi_runtime_stop"))); +char __efi_runtime_rel_start[0] __attribute__((section(".__efi_runtime_rel_start"))); +char __efi_runtime_rel_stop[0] __attribute__((section(".__efi_runtime_rel_stop"))); char _end[0] __attribute__((section(".__end"))); diff --git a/board/ti/am335x/u-boot.lds b/board/ti/am335x/u-boot.lds index 78f294af40..a56cc8216b 100644 --- a/board/ti/am335x/u-boot.lds +++ b/board/ti/am335x/u-boot.lds @@ -59,6 +59,36 @@ SECTIONS . = ALIGN(4); + .__efi_runtime_start : { + *(.__efi_runtime_start) + } + + .efi_runtime : { + *(efi_runtime_text) + *(efi_runtime_data) + } + + .__efi_runtime_stop : { + *(.__efi_runtime_stop) + } + + .efi_runtime_rel_start : + { + *(.__efi_runtime_rel_start) + } + + .efi_runtime_rel : { + *(.relefi_runtime_text) + *(.relefi_runtime_data) + } + + .efi_runtime_rel_stop : + { + *(.__efi_runtime_rel_stop) + } + + . = ALIGN(4); + .image_copy_end : { *(.__image_copy_end) diff --git a/common/board_r.c b/common/board_r.c index 52a9b262eb..6432d2374a 100644 --- a/common/board_r.c +++ b/common/board_r.c @@ -65,6 +65,7 @@ #ifdef CONFIG_AVR32 #include #endif +#include DECLARE_GLOBAL_DATA_PTR; @@ -177,6 +178,9 @@ static int initr_reloc_global_data(void) */ gd->fdt_blob += gd->reloc_off; #endif +#ifdef CONFIG_EFI_LOADER + efi_runtime_relocate(gd->relocaddr, NULL); +#endif return 0; } diff --git a/include/efi_loader.h b/include/efi_loader.h index d9e9d8fa7b..8b3aaddd59 100644 --- a/include/efi_loader.h +++ b/include/efi_loader.h @@ -30,6 +30,7 @@ #define EFI_EXIT(ret) efi_exit_func(ret); +extern struct efi_runtime_services efi_runtime_services; extern struct efi_system_table systab; extern const struct efi_simple_text_output_protocol efi_con_out; @@ -40,6 +41,9 @@ extern const efi_guid_t efi_guid_console_control; extern const efi_guid_t efi_guid_device_path; extern const efi_guid_t efi_guid_loaded_image; +extern unsigned int __efi_runtime_start, __efi_runtime_stop; +extern unsigned int __efi_runtime_rel_start, __efi_runtime_rel_stop; + /* * While UEFI objects can have callbacks, you can also call functions on * protocols (classes) themselves. This struct maps a protocol GUID to its @@ -101,9 +105,22 @@ void efi_save_gd(void); void efi_restore_gd(void); /* Called from EFI_EXIT on callback exit to restore the gd register */ efi_status_t efi_exit_func(efi_status_t ret); +/* Call this to relocate the runtime section to an address space */ +void efi_runtime_relocate(ulong offset, struct efi_mem_desc *map); + +/* + * Use these to indicate that your code / data should go into the EFI runtime + * section and thus still be available when the OS is running + */ +#define EFI_RUNTIME_DATA __attribute__ ((section ("efi_runtime_data"))) +#define EFI_RUNTIME_TEXT __attribute__ ((section ("efi_runtime_text"))) #else /* defined(EFI_LOADER) && !defined(CONFIG_SPL_BUILD) */ +/* Without CONFIG_EFI_LOADER we don't have a runtime section, stub it out */ +#define EFI_RUNTIME_DATA +#define EFI_RUNTIME_TEXT + /* No loader configured, stub out EFI_ENTRY */ static inline void efi_restore_gd(void) { } diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index e60fae925b..87400dee1a 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -39,7 +39,7 @@ static bool efi_is_direct_boot = true; * In most cases we want to pass an FDT to the payload, so reserve one slot of * config table space for it. The pointer gets populated by do_bootefi_exec(). */ -static struct efi_configuration_table efi_conf_table[1]; +static struct efi_configuration_table EFI_RUNTIME_DATA efi_conf_table[1]; /* * The "gd" pointer lives in a register on ARM and AArch64 that we declare @@ -761,10 +761,10 @@ static const struct efi_boot_services efi_boot_services = { }; -static uint16_t firmware_vendor[] = +static uint16_t EFI_RUNTIME_DATA firmware_vendor[] = { 'D','a','s',' ','U','-','b','o','o','t',0 }; -struct efi_system_table systab = { +struct efi_system_table EFI_RUNTIME_DATA systab = { .hdr = { .signature = EFI_SYSTEM_TABLE_SIGNATURE, .revision = 0x20005, /* 2.5 */ diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c new file mode 100644 index 0000000000..22bcd089f9 --- /dev/null +++ b/lib/efi_loader/efi_runtime.c @@ -0,0 +1,290 @@ +/* + * EFI application runtime services + * + * Copyright (c) 2016 Alexander Graf + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include +#include + +/* For manual relocation support */ +DECLARE_GLOBAL_DATA_PTR; + +static efi_status_t EFI_RUNTIME_TEXT EFIAPI efi_unimplemented(void); +static efi_status_t EFI_RUNTIME_TEXT EFIAPI efi_device_error(void); +static efi_status_t EFI_RUNTIME_TEXT EFIAPI efi_invalid_parameter(void); + +#if defined(CONFIG_ARM64) +#define R_RELATIVE 1027 +#define R_MASK 0xffffffffULL +#define IS_RELA 1 +#elif defined(CONFIG_ARM) +#define R_RELATIVE 23 +#define R_MASK 0xffULL +#else +#error Need to add relocation awareness +#endif + +struct elf_rel { + ulong *offset; + ulong info; +}; + +struct elf_rela { + ulong *offset; + ulong info; + long addend; +}; + +/* + * EFI Runtime code lives in 2 stages. In the first stage, U-Boot and an EFI + * payload are running concurrently at the same time. In this mode, we can + * handle a good number of runtime callbacks + */ + +static void EFIAPI efi_reset_system(enum efi_reset_type reset_type, + efi_status_t reset_status, + unsigned long data_size, void *reset_data) +{ + EFI_ENTRY("%d %lx %lx %p", reset_type, reset_status, data_size, + reset_data); + + switch (reset_type) { + case EFI_RESET_COLD: + case EFI_RESET_WARM: + do_reset(NULL, 0, 0, NULL); + break; + case EFI_RESET_SHUTDOWN: + /* We don't have anything to map this to */ + break; + } + + EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_get_time(struct efi_time *time, + struct efi_time_cap *capabilities) +{ +#if defined(CONFIG_CMD_DATE) && defined(CONFIG_DM_RTC) + struct rtc_time tm; + int r; + struct udevice *dev; + + EFI_ENTRY("%p %p", time, capabilities); + + r = uclass_get_device(UCLASS_RTC, 0, &dev); + if (r) + return EFI_EXIT(EFI_DEVICE_ERROR); + + r = dm_rtc_get(dev, &tm); + if (r) + return EFI_EXIT(EFI_DEVICE_ERROR); + + memset(time, 0, sizeof(*time)); + time->year = tm.tm_year; + time->month = tm.tm_mon; + time->day = tm.tm_mday; + time->hour = tm.tm_hour; + time->minute = tm.tm_min; + time->daylight = tm.tm_isdst; + + return EFI_EXIT(EFI_SUCCESS); +#else + return EFI_DEVICE_ERROR; +#endif +} + +struct efi_runtime_detach_list_struct { + void *ptr; + void *patchto; +}; + +static const struct efi_runtime_detach_list_struct efi_runtime_detach_list[] = { + { + /* do_reset is gone */ + .ptr = &efi_runtime_services.reset_system, + .patchto = NULL, + }, { + /* invalidate_*cache_all are gone */ + .ptr = &efi_runtime_services.set_virtual_address_map, + .patchto = &efi_invalid_parameter, + }, { + /* RTC accessors are gone */ + .ptr = &efi_runtime_services.get_time, + .patchto = &efi_device_error, + }, +}; + +static bool efi_runtime_tobedetached(void *p) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(efi_runtime_detach_list); i++) + if (efi_runtime_detach_list[i].ptr == p) + return true; + + return false; +} + +static void efi_runtime_detach(ulong offset) +{ + int i; + ulong patchoff = offset - (ulong)gd->relocaddr; + + for (i = 0; i < ARRAY_SIZE(efi_runtime_detach_list); i++) { + ulong patchto = (ulong)efi_runtime_detach_list[i].patchto; + ulong *p = efi_runtime_detach_list[i].ptr; + ulong newaddr = patchto ? (patchto + patchoff) : 0; + +#ifdef DEBUG_EFI + printf("%s: Setting %p to %lx\n", __func__, p, newaddr); +#endif + *p = newaddr; + } +} + +/* Relocate EFI runtime to uboot_reloc_base = offset */ +void efi_runtime_relocate(ulong offset, struct efi_mem_desc *map) +{ +#ifdef IS_RELA + struct elf_rela *rel = (void*)&__efi_runtime_rel_start; +#else + struct elf_rel *rel = (void*)&__efi_runtime_rel_start; + static ulong lastoff = CONFIG_SYS_TEXT_BASE; +#endif + +#ifdef DEBUG_EFI + printf("%s: Relocating to offset=%lx\n", __func__, offset); +#endif + + for (; (ulong)rel < (ulong)&__efi_runtime_rel_stop; rel++) { + ulong base = CONFIG_SYS_TEXT_BASE; + ulong *p; + ulong newaddr; + + p = (void*)((ulong)rel->offset - base) + gd->relocaddr; + + if ((rel->info & R_MASK) != R_RELATIVE) { + continue; + } + +#ifdef IS_RELA + newaddr = rel->addend + offset - CONFIG_SYS_TEXT_BASE; +#else + newaddr = *p - lastoff + offset; +#endif + + /* Check if the relocation is inside bounds */ + if (map && ((newaddr < map->virtual_start) || + newaddr > (map->virtual_start + (map->num_pages << 12)))) { + if (!efi_runtime_tobedetached(p)) + printf("U-Boot EFI: Relocation at %p is out of " + "range (%lx)\n", p, newaddr); + continue; + } + +#ifdef DEBUG_EFI + printf("%s: Setting %p to %lx\n", __func__, p, newaddr); +#endif + + *p = newaddr; + flush_dcache_range((ulong)p, (ulong)&p[1]); + } + +#ifndef IS_RELA + lastoff = offset; +#endif + + invalidate_icache_all(); +} + +static efi_status_t EFIAPI efi_set_virtual_address_map( + unsigned long memory_map_size, + unsigned long descriptor_size, + uint32_t descriptor_version, + struct efi_mem_desc *virtmap) +{ + ulong runtime_start = (ulong)&__efi_runtime_start & ~0xfffULL; + int n = memory_map_size / descriptor_size; + int i; + + EFI_ENTRY("%lx %lx %x %p", memory_map_size, descriptor_size, + descriptor_version, virtmap); + + for (i = 0; i < n; i++) { + struct efi_mem_desc *map; + + map = (void*)virtmap + (descriptor_size * i); + if (map->type == EFI_RUNTIME_SERVICES_CODE) { + ulong new_offset = map->virtual_start - (runtime_start - gd->relocaddr); + + efi_runtime_relocate(new_offset, map); + /* Once we're virtual, we can no longer handle + complex callbacks */ + efi_runtime_detach(new_offset); + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_INVALID_PARAMETER); +} + +/* + * In the second stage, U-Boot has disappeared. To isolate our runtime code + * that at this point still exists from the rest, we put it into a special + * section. + * + * !!WARNING!! + * + * This means that we can not rely on any code outside of this file in any + * function or variable below this line. + * + * Please keep everything fully self-contained and annotated with + * EFI_RUNTIME_TEXT and EFI_RUNTIME_DATA markers. + */ + +/* + * Relocate the EFI runtime stub to a different place. We need to call this + * the first time we expose the runtime interface to a user and on set virtual + * address map calls. + */ + +static efi_status_t EFI_RUNTIME_TEXT EFIAPI efi_unimplemented(void) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFI_RUNTIME_TEXT EFIAPI efi_device_error(void) +{ + return EFI_DEVICE_ERROR; +} + +static efi_status_t EFI_RUNTIME_TEXT EFIAPI efi_invalid_parameter(void) +{ + return EFI_INVALID_PARAMETER; +} + +struct efi_runtime_services EFI_RUNTIME_DATA efi_runtime_services = { + .hdr = { + .signature = EFI_RUNTIME_SERVICES_SIGNATURE, + .revision = EFI_RUNTIME_SERVICES_REVISION, + .headersize = sizeof(struct efi_table_hdr), + }, + .get_time = &efi_get_time, + .set_time = (void *)&efi_device_error, + .get_wakeup_time = (void *)&efi_unimplemented, + .set_wakeup_time = (void *)&efi_unimplemented, + .set_virtual_address_map = &efi_set_virtual_address_map, + .convert_pointer = (void *)&efi_invalid_parameter, + .get_variable = (void *)&efi_device_error, + .get_next_variable = (void *)&efi_device_error, + .set_variable = (void *)&efi_device_error, + .get_next_high_mono_count = (void *)&efi_device_error, + .reset_system = &efi_reset_system, +};