diff options
Diffstat (limited to 'lib/efi_loader')
27 files changed, 14692 insertions, 0 deletions
diff --git a/lib/efi_loader/.gitignore b/lib/efi_loader/.gitignore new file mode 100644 index 00000000..634a600f --- /dev/null +++ b/lib/efi_loader/.gitignore @@ -0,0 +1,2 @@ +*.efi +*.so diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig new file mode 100644 index 00000000..21ef4403 --- /dev/null +++ b/lib/efi_loader/Kconfig @@ -0,0 +1,123 @@ +config EFI_LOADER + bool "Support running UEFI applications" + depends on OF_LIBFDT && ( \ + ARM && (SYS_CPU = arm1136 || \ + SYS_CPU = arm1176 || \ + SYS_CPU = armv7 || \ + SYS_CPU = armv8) || \ + X86 || RISCV || SANDBOX) + # We need EFI_STUB_64BIT to be set on x86_64 with EFI_STUB + depends on !EFI_STUB || !X86_64 || EFI_STUB_64BIT + # We need EFI_STUB_32BIT to be set on x86_32 with EFI_STUB + depends on !EFI_STUB || !X86 || X86_64 || EFI_STUB_32BIT + default y if !ARM || SYS_CPU = armv7 || SYS_CPU = armv8 + select LIB_UUID + select HAVE_BLOCK_DEVICE + select REGEX + imply CFB_CONSOLE_ANSI + help + Select this option if you want to run UEFI applications (like GNU + GRUB or iPXE) on top of U-Boot. If this option is enabled, U-Boot + will expose the UEFI API to a loaded application, enabling it to + reuse U-Boot's device drivers. + +if EFI_LOADER + +config EFI_GET_TIME + bool "GetTime() runtime service" + depends on DM_RTC + default y + help + Provide the GetTime() runtime service at boottime. This service + can be used by an EFI application to read the real time clock. + +config EFI_SET_TIME + bool "SetTime() runtime service" + depends on EFI_GET_TIME + default n + help + Provide the SetTime() runtime service at boottime. This service + can be used by an EFI application to adjust the real time clock. + +config EFI_DEVICE_PATH_TO_TEXT + bool "Device path to text protocol" + default y + help + The device path to text protocol converts device nodes and paths to + human readable strings. + +config EFI_LOADER_HII + bool "HII protocols" + default y + help + The Human Interface Infrastructure is a complicated framework that + allows UEFI applications to draw fancy menus and hook strings using + a translation framework. + + U-Boot implements enough of its features to be able to run the UEFI + Shell, but not more than that. + +config EFI_UNICODE_COLLATION_PROTOCOL2 + bool "Unicode collation protocol" + default y + help + The Unicode collation protocol is used for lexical comparisons. It is + required to run the UEFI shell. + +if EFI_UNICODE_COLLATION_PROTOCOL2 + +config EFI_UNICODE_CAPITALIZATION + bool "Support Unicode capitalization" + default y + help + Select this option to enable correct handling of the capitalization of + Unicode codepoints in the range 0x0000-0xffff. If this option is not + set, only the the correct handling of the letters of the codepage + used by the FAT file system is ensured. + +config EFI_UNICODE_COLLATION_PROTOCOL + bool "Deprecated version of the Unicode collation protocol" + default n + help + In EFI 1.10 a version of the Unicode collation protocol using ISO + 639-2 language codes existed. This protocol is not part of the UEFI + specification any longer. Unfortunately it is required to run the + UEFI Self Certification Test (SCT) II, version 2.6, 2017. + + Choose this option for testing only. It is bound to be removed. + +endif + +config EFI_LOADER_BOUNCE_BUFFER + bool "EFI Applications use bounce buffers for DMA operations" + depends on ARM64 + default n + help + Some hardware does not support DMA to full 64bit addresses. For this + hardware we can create a bounce buffer so that payloads don't have to + worry about platform details. + +config EFI_PLATFORM_LANG_CODES + string "Language codes supported by firmware" + default "en-US" + help + This value is used to initialize the PlatformLangCodes variable. Its + value is a semicolon (;) separated list of language codes in native + RFC 4646 format, e.g. "en-US;de-DE". The first language code is used + to initialize the PlatformLang variable. + +config EFI_HAVE_RUNTIME_RESET + # bool "Reset runtime service is available" + bool + default y + depends on ARCH_BCM283X || FSL_LAYERSCAPE || PSCI_RESET || SYSRESET_X86 + +config EFI_GRUB_ARM32_WORKAROUND + bool "Workaround for GRUB on 32bit ARM" + default y + depends on ARM && !ARM64 + help + GRUB prior to version 2.04 requires U-Boot to disable caches. This + workaround currently is also needed on systems with caches that + cannot be managed via CP15. +endif diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile new file mode 100644 index 00000000..7db40602 --- /dev/null +++ b/lib/efi_loader/Makefile @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2016 Alexander Graf +# + +# This file only gets included with CONFIG_EFI_LOADER set, so all +# object inclusion implicitly depends on it + +asflags-y += -DHOST_ARCH="$(HOST_ARCH)" +ccflags-y += -DHOST_ARCH="$(HOST_ARCH)" + +CFLAGS_efi_boottime.o += \ + -DFW_VERSION="0x$(VERSION)" \ + -DFW_PATCHLEVEL="0x$(PATCHLEVEL)" +CFLAGS_helloworld.o := $(CFLAGS_EFI) -Os -ffreestanding +CFLAGS_REMOVE_helloworld.o := $(CFLAGS_NON_EFI) + +ifneq ($(CONFIG_CMD_BOOTEFI_HELLO_COMPILE),) +always += helloworld.efi +endif + +obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o +obj-y += efi_bootmgr.o +obj-y += efi_boottime.o +obj-y += efi_console.o +obj-y += efi_device_path.o +obj-$(CONFIG_EFI_DEVICE_PATH_TO_TEXT) += efi_device_path_to_text.o +obj-y += efi_device_path_utilities.o +obj-y += efi_file.o +obj-$(CONFIG_EFI_LOADER_HII) += efi_hii.o efi_hii_config.o +obj-y += efi_image_loader.o +obj-y += efi_memory.o +obj-y += efi_root_node.o +obj-y += efi_runtime.o +obj-y += efi_setup.o +obj-$(CONFIG_EFI_UNICODE_COLLATION_PROTOCOL2) += efi_unicode_collation.o +obj-y += efi_variable.o +obj-y += efi_watchdog.o +obj-$(CONFIG_LCD) += efi_gop.o +obj-$(CONFIG_DM_VIDEO) += efi_gop.o +obj-$(CONFIG_PARTITIONS) += efi_disk.o +obj-$(CONFIG_NET) += efi_net.o +obj-$(CONFIG_GENERATE_ACPI_TABLE) += efi_acpi.o +obj-$(CONFIG_GENERATE_SMBIOS_TABLE) += efi_smbios.o diff --git a/lib/efi_loader/efi_acpi.c b/lib/efi_loader/efi_acpi.c new file mode 100644 index 00000000..a4e5e53d --- /dev/null +++ b/lib/efi_loader/efi_acpi.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application ACPI tables support + * + * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com> + */ + +#include <common.h> +#include <efi_loader.h> +#include <asm/acpi_table.h> + +static const efi_guid_t acpi_guid = EFI_ACPI_TABLE_GUID; + +/* + * Install the ACPI table as a configuration table. + * + * @return status code + */ +efi_status_t efi_acpi_register(void) +{ + /* Map within the low 32 bits, to allow for 32bit ACPI tables */ + u64 acpi = U32_MAX; + efi_status_t ret; + + /* Reserve 64kiB page for ACPI */ + ret = efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS, + EFI_RUNTIME_SERVICES_DATA, 16, &acpi); + if (ret != EFI_SUCCESS) + return ret; + + /* + * Generate ACPI tables - we know that efi_allocate_pages() returns + * a 4k-aligned address, so it is safe to assume that + * write_acpi_tables() will write the table at that address. + */ + assert(!(acpi & 0xf)); + write_acpi_tables(acpi); + + /* And expose them to our EFI payload */ + return efi_install_configuration_table(&acpi_guid, + (void *)(uintptr_t)acpi); +} diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c new file mode 100644 index 00000000..2ea21448 --- /dev/null +++ b/lib/efi_loader/efi_bootmgr.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI boot manager + * + * Copyright (c) 2017 Rob Clark + */ + +#include <common.h> +#include <charset.h> +#include <malloc.h> +#include <efi_loader.h> +#include <asm/unaligned.h> + +static const struct efi_boot_services *bs; +static const struct efi_runtime_services *rs; + +/* + * bootmgr implements the logic of trying to find a payload to boot + * based on the BootOrder + BootXXXX variables, and then loading it. + * + * TODO detecting a special key held (f9?) and displaying a boot menu + * like you would get on a PC would be clever. + * + * TODO if we had a way to write and persist variables after the OS + * has started, we'd also want to check OsIndications to see if we + * should do normal or recovery boot. + */ + + +/** + * efi_deserialize_load_option() - parse serialized data + * + * Parse serialized data describing a load option and transform it to the + * efi_load_option structure. + * + * @lo: pointer to target + * @data: serialized data + */ +void efi_deserialize_load_option(struct efi_load_option *lo, u8 *data) +{ + lo->attributes = get_unaligned_le32(data); + data += sizeof(u32); + + lo->file_path_length = get_unaligned_le16(data); + data += sizeof(u16); + + /* FIXME */ + lo->label = (u16 *)data; + data += (u16_strlen(lo->label) + 1) * sizeof(u16); + + /* FIXME */ + lo->file_path = (struct efi_device_path *)data; + data += lo->file_path_length; + + lo->optional_data = data; +} + +/** + * efi_serialize_load_option() - serialize load option + * + * Serialize efi_load_option structure into byte stream for BootXXXX. + * + * @data: buffer for serialized data + * @lo: load option + * Return: size of allocated buffer + */ +unsigned long efi_serialize_load_option(struct efi_load_option *lo, u8 **data) +{ + unsigned long label_len; + unsigned long size; + u8 *p; + + label_len = (u16_strlen(lo->label) + 1) * sizeof(u16); + + /* total size */ + size = sizeof(lo->attributes); + size += sizeof(lo->file_path_length); + size += label_len; + size += lo->file_path_length; + if (lo->optional_data) + size += (utf8_utf16_strlen((const char *)lo->optional_data) + + 1) * sizeof(u16); + p = malloc(size); + if (!p) + return 0; + + /* copy data */ + *data = p; + memcpy(p, &lo->attributes, sizeof(lo->attributes)); + p += sizeof(lo->attributes); + + memcpy(p, &lo->file_path_length, sizeof(lo->file_path_length)); + p += sizeof(lo->file_path_length); + + memcpy(p, lo->label, label_len); + p += label_len; + + memcpy(p, lo->file_path, lo->file_path_length); + p += lo->file_path_length; + + if (lo->optional_data) { + utf8_utf16_strcpy((u16 **)&p, (const char *)lo->optional_data); + p += sizeof(u16); /* size of trailing \0 */ + } + return size; +} + +/** + * get_var() - get UEFI variable + * + * It is the caller's duty to free the returned buffer. + * + * @name: name of variable + * @vendor: vendor GUID of variable + * @size: size of allocated buffer + * Return: buffer with variable data or NULL + */ +static void *get_var(u16 *name, const efi_guid_t *vendor, + efi_uintn_t *size) +{ + efi_guid_t *v = (efi_guid_t *)vendor; + efi_status_t ret; + void *buf = NULL; + + *size = 0; + EFI_CALL(ret = rs->get_variable(name, v, NULL, size, buf)); + if (ret == EFI_BUFFER_TOO_SMALL) { + buf = malloc(*size); + EFI_CALL(ret = rs->get_variable(name, v, NULL, size, buf)); + } + + if (ret != EFI_SUCCESS) { + free(buf); + *size = 0; + return NULL; + } + + return buf; +} + +/** + * try_load_entry() - try to load image for boot option + * + * Attempt to load load-option number 'n', returning device_path and file_path + * if successful. This checks that the EFI_LOAD_OPTION is active (enabled) + * and that the specified file to boot exists. + * + * @n: number of the boot option, e.g. 0x0a13 for Boot0A13 + * @handle: on return handle for the newly installed image + * Return: status code + */ +static efi_status_t try_load_entry(u16 n, efi_handle_t *handle) +{ + struct efi_load_option lo; + u16 varname[] = L"Boot0000"; + u16 hexmap[] = L"0123456789ABCDEF"; + void *load_option; + efi_uintn_t size; + efi_status_t ret; + + varname[4] = hexmap[(n & 0xf000) >> 12]; + varname[5] = hexmap[(n & 0x0f00) >> 8]; + varname[6] = hexmap[(n & 0x00f0) >> 4]; + varname[7] = hexmap[(n & 0x000f) >> 0]; + + load_option = get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) + return EFI_LOAD_ERROR; + + efi_deserialize_load_option(&lo, load_option); + + if (lo.attributes & LOAD_OPTION_ACTIVE) { + u32 attributes; + + debug("%s: trying to load \"%ls\" from %pD\n", + __func__, lo.label, lo.file_path); + + ret = EFI_CALL(efi_load_image(true, efi_root, lo.file_path, + NULL, 0, handle)); + if (ret != EFI_SUCCESS) { + printf("Loading from Boot%04X '%ls' failed\n", n, + lo.label); + goto error; + } + + attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS; + size = sizeof(n); + ret = EFI_CALL(efi_set_variable( + L"BootCurrent", + (efi_guid_t *)&efi_global_variable_guid, + attributes, size, &n)); + if (ret != EFI_SUCCESS) { + if (EFI_CALL(efi_unload_image(*handle)) + != EFI_SUCCESS) + printf("Unloading image failed\n"); + goto error; + } + + printf("Booting: %ls\n", lo.label); + } else { + ret = EFI_LOAD_ERROR; + } + +error: + free(load_option); + + return ret; +} + +/** + * efi_bootmgr_load() - try to load from BootNext or BootOrder + * + * Attempt to load from BootNext or in the order specified by BootOrder + * EFI variable, the available load-options, finding and returning + * the first one that can be loaded successfully. + * + * @handle: on return handle for the newly installed image + * Return: status code + */ +efi_status_t efi_bootmgr_load(efi_handle_t *handle) +{ + u16 bootnext, *bootorder; + efi_uintn_t size; + int i, num; + efi_status_t ret; + + bs = systab.boottime; + rs = systab.runtime; + + /* BootNext */ + bootnext = 0; + size = sizeof(bootnext); + ret = EFI_CALL(efi_get_variable(L"BootNext", + (efi_guid_t *)&efi_global_variable_guid, + NULL, &size, &bootnext)); + if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) { + /* BootNext does exist here */ + if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16)) + printf("BootNext must be 16-bit integer\n"); + + /* delete BootNext */ + ret = EFI_CALL(efi_set_variable( + L"BootNext", + (efi_guid_t *)&efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE, 0, + &bootnext)); + + /* load BootNext */ + if (ret == EFI_SUCCESS) { + if (size == sizeof(u16)) { + ret = try_load_entry(bootnext, handle); + if (ret == EFI_SUCCESS) + return ret; + printf("Loading from BootNext failed, falling back to BootOrder\n"); + } + } else { + printf("Deleting BootNext failed\n"); + } + } + + /* BootOrder */ + bootorder = get_var(L"BootOrder", &efi_global_variable_guid, &size); + if (!bootorder) { + printf("BootOrder not defined\n"); + ret = EFI_NOT_FOUND; + goto error; + } + + num = size / sizeof(uint16_t); + for (i = 0; i < num; i++) { + debug("%s: trying to load Boot%04X\n", __func__, bootorder[i]); + ret = try_load_entry(bootorder[i], handle); + if (ret == EFI_SUCCESS) + break; + } + + free(bootorder); + +error: + return ret; +} diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c new file mode 100644 index 00000000..88a7604b --- /dev/null +++ b/lib/efi_loader/efi_boottime.c @@ -0,0 +1,3678 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application boot time services + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <div64.h> +#include <efi_loader.h> +#include <irq_func.h> +#include <malloc.h> +#include <time.h> +#include <linux/libfdt_env.h> +#include <u-boot/crc.h> +#include <bootm.h> +#include <pe.h> +#include <u-boot/crc.h> +#include <watchdog.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Task priority level */ +static efi_uintn_t efi_tpl = TPL_APPLICATION; + +/* This list contains all the EFI objects our payload has access to */ +LIST_HEAD(efi_obj_list); + +/* List of all events */ +__efi_runtime_data LIST_HEAD(efi_events); + +/* List of queued events */ +LIST_HEAD(efi_event_queue); + +/* Flag to disable timer activity in ExitBootServices() */ +static bool timers_enabled = true; + +/* List of all events registered by RegisterProtocolNotify() */ +LIST_HEAD(efi_register_notify_events); + +/* Handle of the currently executing image */ +static efi_handle_t current_image; + +#ifdef CONFIG_ARM +/* + * The "gd" pointer lives in a register on ARM and AArch64 that we declare + * fixed when compiling U-Boot. However, the payload does not know about that + * restriction so we need to manually swap its and our view of that register on + * EFI callback entry/exit. + */ +static volatile void *efi_gd, *app_gd; +#endif + +/* 1 if inside U-Boot code, 0 if inside EFI payload code */ +static int entry_count = 1; +static int nesting_level; +/* GUID of the device tree table */ +const efi_guid_t efi_guid_fdt = EFI_FDT_GUID; +/* GUID of the EFI_DRIVER_BINDING_PROTOCOL */ +const efi_guid_t efi_guid_driver_binding_protocol = + EFI_DRIVER_BINDING_PROTOCOL_GUID; + +/* event group ExitBootServices() invoked */ +const efi_guid_t efi_guid_event_group_exit_boot_services = + EFI_EVENT_GROUP_EXIT_BOOT_SERVICES; +/* event group SetVirtualAddressMap() invoked */ +const efi_guid_t efi_guid_event_group_virtual_address_change = + EFI_EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE; +/* event group memory map changed */ +const efi_guid_t efi_guid_event_group_memory_map_change = + EFI_EVENT_GROUP_MEMORY_MAP_CHANGE; +/* event group boot manager about to boot */ +const efi_guid_t efi_guid_event_group_ready_to_boot = + EFI_EVENT_GROUP_READY_TO_BOOT; +/* event group ResetSystem() invoked (before ExitBootServices) */ +const efi_guid_t efi_guid_event_group_reset_system = + EFI_EVENT_GROUP_RESET_SYSTEM; + +static efi_status_t EFIAPI efi_disconnect_controller( + efi_handle_t controller_handle, + efi_handle_t driver_image_handle, + efi_handle_t child_handle); + +/* Called on every callback entry */ +int __efi_entry_check(void) +{ + int ret = entry_count++ == 0; +#ifdef CONFIG_ARM + assert(efi_gd); + app_gd = gd; + gd = efi_gd; +#endif + return ret; +} + +/* Called on every callback exit */ +int __efi_exit_check(void) +{ + int ret = --entry_count == 0; +#ifdef CONFIG_ARM + gd = app_gd; +#endif + return ret; +} + +/* Called from do_bootefi_exec() */ +void efi_save_gd(void) +{ +#ifdef CONFIG_ARM + efi_gd = gd; +#endif +} + +/* + * Special case handler for error/abort that just forces things back to u-boot + * world so we can dump out an abort message, without any care about returning + * back to UEFI world. + */ +void efi_restore_gd(void) +{ +#ifdef CONFIG_ARM + /* Only restore if we're already in EFI context */ + if (!efi_gd) + return; + gd = efi_gd; +#endif +} + +/** + * indent_string() - returns a string for indenting with two spaces per level + * @level: indent level + * + * A maximum of ten indent levels is supported. Higher indent levels will be + * truncated. + * + * Return: A string for indenting with two spaces per level is + * returned. + */ +static const char *indent_string(int level) +{ + const char *indent = " "; + const int max = strlen(indent); + + level = min(max, level * 2); + return &indent[max - level]; +} + +const char *__efi_nesting(void) +{ + return indent_string(nesting_level); +} + +const char *__efi_nesting_inc(void) +{ + return indent_string(nesting_level++); +} + +const char *__efi_nesting_dec(void) +{ + return indent_string(--nesting_level); +} + +/** + * efi_event_is_queued() - check if an event is queued + * + * @event: event + * Return: true if event is queued + */ +static bool efi_event_is_queued(struct efi_event *event) +{ + return !!event->queue_link.next; +} + +/** + * efi_process_event_queue() - process event queue + */ +static void efi_process_event_queue(void) +{ + while (!list_empty(&efi_event_queue)) { + struct efi_event *event; + efi_uintn_t old_tpl; + + event = list_first_entry(&efi_event_queue, struct efi_event, + queue_link); + if (efi_tpl >= event->notify_tpl) + return; + list_del(&event->queue_link); + event->queue_link.next = NULL; + event->queue_link.prev = NULL; + /* Events must be executed at the event's TPL */ + old_tpl = efi_tpl; + efi_tpl = event->notify_tpl; + EFI_CALL_VOID(event->notify_function(event, + event->notify_context)); + efi_tpl = old_tpl; + if (event->type == EVT_NOTIFY_SIGNAL) + event->is_signaled = 0; + } +} + +/** + * efi_queue_event() - queue an EFI event + * @event: event to signal + * + * This function queues the notification function of the event for future + * execution. + * + */ +static void efi_queue_event(struct efi_event *event) +{ + struct efi_event *item = NULL; + + if (!event->notify_function) + return; + + if (!efi_event_is_queued(event)) { + /* + * Events must be notified in order of decreasing task priority + * level. Insert the new event accordingly. + */ + list_for_each_entry(item, &efi_event_queue, queue_link) { + if (item->notify_tpl < event->notify_tpl) { + list_add_tail(&event->queue_link, + &item->queue_link); + event = NULL; + break; + } + } + if (event) + list_add_tail(&event->queue_link, &efi_event_queue); + } + efi_process_event_queue(); +} + +/** + * is_valid_tpl() - check if the task priority level is valid + * + * @tpl: TPL level to check + * Return: status code + */ +efi_status_t is_valid_tpl(efi_uintn_t tpl) +{ + switch (tpl) { + case TPL_APPLICATION: + case TPL_CALLBACK: + case TPL_NOTIFY: + case TPL_HIGH_LEVEL: + return EFI_SUCCESS; + default: + return EFI_INVALID_PARAMETER; + } +} + +/** + * efi_signal_event() - signal an EFI event + * @event: event to signal + * + * This function signals an event. If the event belongs to an event group all + * events of the group are signaled. If they are of type EVT_NOTIFY_SIGNAL + * their notification function is queued. + * + * For the SignalEvent service see efi_signal_event_ext. + */ +void efi_signal_event(struct efi_event *event) +{ + if (event->is_signaled) + return; + if (event->group) { + struct efi_event *evt; + + /* + * The signaled state has to set before executing any + * notification function + */ + list_for_each_entry(evt, &efi_events, link) { + if (!evt->group || guidcmp(evt->group, event->group)) + continue; + if (evt->is_signaled) + continue; + evt->is_signaled = true; + } + list_for_each_entry(evt, &efi_events, link) { + if (!evt->group || guidcmp(evt->group, event->group)) + continue; + efi_queue_event(evt); + } + } else { + event->is_signaled = true; + efi_queue_event(event); + } +} + +/** + * efi_raise_tpl() - raise the task priority level + * @new_tpl: new value of the task priority level + * + * This function implements the RaiseTpl service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: old value of the task priority level + */ +static unsigned long EFIAPI efi_raise_tpl(efi_uintn_t new_tpl) +{ + efi_uintn_t old_tpl = efi_tpl; + + EFI_ENTRY("0x%zx", new_tpl); + + if (new_tpl < efi_tpl) + EFI_PRINT("WARNING: new_tpl < current_tpl in %s\n", __func__); + efi_tpl = new_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + EFI_EXIT(EFI_SUCCESS); + return old_tpl; +} + +/** + * efi_restore_tpl() - lower the task priority level + * @old_tpl: value of the task priority level to be restored + * + * This function implements the RestoreTpl service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static void EFIAPI efi_restore_tpl(efi_uintn_t old_tpl) +{ + EFI_ENTRY("0x%zx", old_tpl); + + if (old_tpl > efi_tpl) + EFI_PRINT("WARNING: old_tpl > current_tpl in %s\n", __func__); + efi_tpl = old_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + /* + * Lowering the TPL may have made queued events eligible for execution. + */ + efi_timer_check(); + + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_allocate_pages_ext() - allocate memory pages + * @type: type of allocation to be performed + * @memory_type: usage type of the allocated memory + * @pages: number of pages to be allocated + * @memory: allocated memory + * + * This function implements the AllocatePages service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_allocate_pages_ext(int type, int memory_type, + efi_uintn_t pages, + uint64_t *memory) +{ + efi_status_t r; + + EFI_ENTRY("%d, %d, 0x%zx, %p", type, memory_type, pages, memory); + r = efi_allocate_pages(type, memory_type, pages, memory); + return EFI_EXIT(r); +} + +/** + * efi_free_pages_ext() - Free memory pages. + * @memory: start of the memory area to be freed + * @pages: number of pages to be freed + * + * This function implements the FreePages service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_free_pages_ext(uint64_t memory, + efi_uintn_t pages) +{ + efi_status_t r; + + EFI_ENTRY("%llx, 0x%zx", memory, pages); + r = efi_free_pages(memory, pages); + return EFI_EXIT(r); +} + +/** + * efi_get_memory_map_ext() - get map describing memory usage + * @memory_map_size: on entry the size, in bytes, of the memory map buffer, + * on exit the size of the copied memory map + * @memory_map: buffer to which the memory map is written + * @map_key: key for the memory map + * @descriptor_size: size of an individual memory descriptor + * @descriptor_version: version number of the memory descriptor structure + * + * This function implements the GetMemoryMap service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_get_memory_map_ext( + efi_uintn_t *memory_map_size, + struct efi_mem_desc *memory_map, + efi_uintn_t *map_key, + efi_uintn_t *descriptor_size, + uint32_t *descriptor_version) +{ + efi_status_t r; + + EFI_ENTRY("%p, %p, %p, %p, %p", memory_map_size, memory_map, + map_key, descriptor_size, descriptor_version); + r = efi_get_memory_map(memory_map_size, memory_map, map_key, + descriptor_size, descriptor_version); + return EFI_EXIT(r); +} + +/** + * efi_allocate_pool_ext() - allocate memory from pool + * @pool_type: type of the pool from which memory is to be allocated + * @size: number of bytes to be allocated + * @buffer: allocated memory + * + * This function implements the AllocatePool service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_allocate_pool_ext(int pool_type, + efi_uintn_t size, + void **buffer) +{ + efi_status_t r; + + EFI_ENTRY("%d, %zd, %p", pool_type, size, buffer); + r = efi_allocate_pool(pool_type, size, buffer); + return EFI_EXIT(r); +} + +/** + * efi_free_pool_ext() - free memory from pool + * @buffer: start of memory to be freed + * + * This function implements the FreePool service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_free_pool_ext(void *buffer) +{ + efi_status_t r; + + EFI_ENTRY("%p", buffer); + r = efi_free_pool(buffer); + return EFI_EXIT(r); +} + +/** + * efi_add_handle() - add a new handle to the object list + * + * @handle: handle to be added + * + * The protocols list is initialized. The handle is added to the list of known + * UEFI objects. + */ +void efi_add_handle(efi_handle_t handle) +{ + if (!handle) + return; + INIT_LIST_HEAD(&handle->protocols); + list_add_tail(&handle->link, &efi_obj_list); +} + +/** + * efi_create_handle() - create handle + * @handle: new handle + * + * Return: status code + */ +efi_status_t efi_create_handle(efi_handle_t *handle) +{ + struct efi_object *obj; + + obj = calloc(1, sizeof(struct efi_object)); + if (!obj) + return EFI_OUT_OF_RESOURCES; + + efi_add_handle(obj); + *handle = obj; + + return EFI_SUCCESS; +} + +/** + * efi_search_protocol() - find a protocol on a handle. + * @handle: handle + * @protocol_guid: GUID of the protocol + * @handler: reference to the protocol + * + * Return: status code + */ +efi_status_t efi_search_protocol(const efi_handle_t handle, + const efi_guid_t *protocol_guid, + struct efi_handler **handler) +{ + struct efi_object *efiobj; + struct list_head *lhandle; + + if (!handle || !protocol_guid) + return EFI_INVALID_PARAMETER; + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_INVALID_PARAMETER; + list_for_each(lhandle, &efiobj->protocols) { + struct efi_handler *protocol; + + protocol = list_entry(lhandle, struct efi_handler, link); + if (!guidcmp(protocol->guid, protocol_guid)) { + if (handler) + *handler = protocol; + return EFI_SUCCESS; + } + } + return EFI_NOT_FOUND; +} + +/** + * efi_remove_protocol() - delete protocol from a handle + * @handle: handle from which the protocol shall be deleted + * @protocol: GUID of the protocol to be deleted + * @protocol_interface: interface of the protocol implementation + * + * Return: status code + */ +efi_status_t efi_remove_protocol(const efi_handle_t handle, + const efi_guid_t *protocol, + void *protocol_interface) +{ + struct efi_handler *handler; + efi_status_t ret; + + ret = efi_search_protocol(handle, protocol, &handler); + if (ret != EFI_SUCCESS) + return ret; + if (handler->protocol_interface != protocol_interface) + return EFI_NOT_FOUND; + list_del(&handler->link); + free(handler); + return EFI_SUCCESS; +} + +/** + * efi_remove_all_protocols() - delete all protocols from a handle + * @handle: handle from which the protocols shall be deleted + * + * Return: status code + */ +efi_status_t efi_remove_all_protocols(const efi_handle_t handle) +{ + struct efi_object *efiobj; + struct efi_handler *protocol; + struct efi_handler *pos; + + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_INVALID_PARAMETER; + list_for_each_entry_safe(protocol, pos, &efiobj->protocols, link) { + efi_status_t ret; + + ret = efi_remove_protocol(handle, protocol->guid, + protocol->protocol_interface); + if (ret != EFI_SUCCESS) + return ret; + } + return EFI_SUCCESS; +} + +/** + * efi_delete_handle() - delete handle + * + * @handle: handle to delete + */ +void efi_delete_handle(efi_handle_t handle) +{ + if (!handle) + return; + efi_remove_all_protocols(handle); + list_del(&handle->link); + free(handle); +} + +/** + * efi_is_event() - check if a pointer is a valid event + * @event: pointer to check + * + * Return: status code + */ +static efi_status_t efi_is_event(const struct efi_event *event) +{ + const struct efi_event *evt; + + if (!event) + return EFI_INVALID_PARAMETER; + list_for_each_entry(evt, &efi_events, link) { + if (evt == event) + return EFI_SUCCESS; + } + return EFI_INVALID_PARAMETER; +} + +/** + * efi_create_event() - create an event + * + * @type: type of the event to create + * @notify_tpl: task priority level of the event + * @notify_function: notification function of the event + * @notify_context: pointer passed to the notification function + * @group: event group + * @event: created event + * + * This function is used inside U-Boot code to create an event. + * + * For the API function implementing the CreateEvent service see + * efi_create_event_ext. + * + * Return: status code + */ +efi_status_t efi_create_event(uint32_t type, efi_uintn_t notify_tpl, + void (EFIAPI *notify_function) ( + struct efi_event *event, + void *context), + void *notify_context, efi_guid_t *group, + struct efi_event **event) +{ + struct efi_event *evt; + efi_status_t ret; + int pool_type; + + if (event == NULL) + return EFI_INVALID_PARAMETER; + + switch (type) { + case 0: + case EVT_TIMER: + case EVT_NOTIFY_SIGNAL: + case EVT_TIMER | EVT_NOTIFY_SIGNAL: + case EVT_NOTIFY_WAIT: + case EVT_TIMER | EVT_NOTIFY_WAIT: + case EVT_SIGNAL_EXIT_BOOT_SERVICES: + pool_type = EFI_BOOT_SERVICES_DATA; + break; + case EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE: + pool_type = EFI_RUNTIME_SERVICES_DATA; + break; + default: + return EFI_INVALID_PARAMETER; + } + + if ((type & (EVT_NOTIFY_WAIT | EVT_NOTIFY_SIGNAL)) && + (!notify_function || is_valid_tpl(notify_tpl) != EFI_SUCCESS)) + return EFI_INVALID_PARAMETER; + + ret = efi_allocate_pool(pool_type, sizeof(struct efi_event), + (void **)&evt); + if (ret != EFI_SUCCESS) + return ret; + memset(evt, 0, sizeof(struct efi_event)); + evt->type = type; + evt->notify_tpl = notify_tpl; + evt->notify_function = notify_function; + evt->notify_context = notify_context; + evt->group = group; + /* Disable timers on boot up */ + evt->trigger_next = -1ULL; + list_add_tail(&evt->link, &efi_events); + *event = evt; + return EFI_SUCCESS; +} + +/* + * efi_create_event_ex() - create an event in a group + * @type: type of the event to create + * @notify_tpl: task priority level of the event + * @notify_function: notification function of the event + * @notify_context: pointer passed to the notification function + * @event: created event + * @event_group: event group + * + * This function implements the CreateEventEx service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_create_event_ex(uint32_t type, efi_uintn_t notify_tpl, + void (EFIAPI *notify_function) ( + struct efi_event *event, + void *context), + void *notify_context, + efi_guid_t *event_group, + struct efi_event **event) +{ + efi_status_t ret; + + EFI_ENTRY("%d, 0x%zx, %p, %p, %pUl", type, notify_tpl, notify_function, + notify_context, event_group); + + /* + * The allowable input parameters are the same as in CreateEvent() + * except for the following two disallowed event types. + */ + switch (type) { + case EVT_SIGNAL_EXIT_BOOT_SERVICES: + case EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE: + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = efi_create_event(type, notify_tpl, notify_function, + notify_context, event_group, event); +out: + return EFI_EXIT(ret); +} + +/** + * efi_create_event_ext() - create an event + * @type: type of the event to create + * @notify_tpl: task priority level of the event + * @notify_function: notification function of the event + * @notify_context: pointer passed to the notification function + * @event: created event + * + * This function implements the CreateEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_create_event_ext( + uint32_t type, efi_uintn_t notify_tpl, + void (EFIAPI *notify_function) ( + struct efi_event *event, + void *context), + void *notify_context, struct efi_event **event) +{ + EFI_ENTRY("%d, 0x%zx, %p, %p", type, notify_tpl, notify_function, + notify_context); + return EFI_EXIT(efi_create_event(type, notify_tpl, notify_function, + notify_context, NULL, event)); +} + +/** + * efi_timer_check() - check if a timer event has occurred + * + * Check if a timer event has occurred or a queued notification function should + * be called. + * + * Our timers have to work without interrupts, so we check whenever keyboard + * input or disk accesses happen if enough time elapsed for them to fire. + */ +void efi_timer_check(void) +{ + struct efi_event *evt; + u64 now = timer_get_us(); + + list_for_each_entry(evt, &efi_events, link) { + if (!timers_enabled) + continue; + if (!(evt->type & EVT_TIMER) || now < evt->trigger_next) + continue; + switch (evt->trigger_type) { + case EFI_TIMER_RELATIVE: + evt->trigger_type = EFI_TIMER_STOP; + break; + case EFI_TIMER_PERIODIC: + evt->trigger_next += evt->trigger_time; + break; + default: + continue; + } + evt->is_signaled = false; + efi_signal_event(evt); + } + efi_process_event_queue(); + WATCHDOG_RESET(); +} + +/** + * efi_set_timer() - set the trigger time for a timer event or stop the event + * @event: event for which the timer is set + * @type: type of the timer + * @trigger_time: trigger period in multiples of 100 ns + * + * This is the function for internal usage in U-Boot. For the API function + * implementing the SetTimer service see efi_set_timer_ext. + * + * Return: status code + */ +efi_status_t efi_set_timer(struct efi_event *event, enum efi_timer_delay type, + uint64_t trigger_time) +{ + /* Check that the event is valid */ + if (efi_is_event(event) != EFI_SUCCESS || !(event->type & EVT_TIMER)) + return EFI_INVALID_PARAMETER; + + /* + * The parameter defines a multiple of 100 ns. + * We use multiples of 1000 ns. So divide by 10. + */ + do_div(trigger_time, 10); + + switch (type) { + case EFI_TIMER_STOP: + event->trigger_next = -1ULL; + break; + case EFI_TIMER_PERIODIC: + case EFI_TIMER_RELATIVE: + event->trigger_next = timer_get_us() + trigger_time; + break; + default: + return EFI_INVALID_PARAMETER; + } + event->trigger_type = type; + event->trigger_time = trigger_time; + event->is_signaled = false; + return EFI_SUCCESS; +} + +/** + * efi_set_timer_ext() - Set the trigger time for a timer event or stop the + * event + * @event: event for which the timer is set + * @type: type of the timer + * @trigger_time: trigger period in multiples of 100 ns + * + * This function implements the SetTimer service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * + * Return: status code + */ +static efi_status_t EFIAPI efi_set_timer_ext(struct efi_event *event, + enum efi_timer_delay type, + uint64_t trigger_time) +{ + EFI_ENTRY("%p, %d, %llx", event, type, trigger_time); + return EFI_EXIT(efi_set_timer(event, type, trigger_time)); +} + +/** + * efi_wait_for_event() - wait for events to be signaled + * @num_events: number of events to be waited for + * @event: events to be waited for + * @index: index of the event that was signaled + * + * This function implements the WaitForEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_wait_for_event(efi_uintn_t num_events, + struct efi_event **event, + efi_uintn_t *index) +{ + int i; + + EFI_ENTRY("%zd, %p, %p", num_events, event, index); + + /* Check parameters */ + if (!num_events || !event) + return EFI_EXIT(EFI_INVALID_PARAMETER); + /* Check TPL */ + if (efi_tpl != TPL_APPLICATION) + return EFI_EXIT(EFI_UNSUPPORTED); + for (i = 0; i < num_events; ++i) { + if (efi_is_event(event[i]) != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event[i]->type || event[i]->type & EVT_NOTIFY_SIGNAL) + return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event[i]->is_signaled) + efi_queue_event(event[i]); + } + + /* Wait for signal */ + for (;;) { + for (i = 0; i < num_events; ++i) { + if (event[i]->is_signaled) + goto out; + } + /* Allow events to occur. */ + efi_timer_check(); + } + +out: + /* + * Reset the signal which is passed to the caller to allow periodic + * events to occur. + */ + event[i]->is_signaled = false; + if (index) + *index = i; + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_signal_event_ext() - signal an EFI event + * @event: event to signal + * + * This function implements the SignalEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * This functions sets the signaled state of the event and queues the + * notification function for execution. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_signal_event_ext(struct efi_event *event) +{ + EFI_ENTRY("%p", event); + if (efi_is_event(event) != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + efi_signal_event(event); + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_close_event() - close an EFI event + * @event: event to close + * + * This function implements the CloseEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_close_event(struct efi_event *event) +{ + struct efi_register_notify_event *item, *next; + + EFI_ENTRY("%p", event); + if (efi_is_event(event) != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* Remove protocol notify registrations for the event */ + list_for_each_entry_safe(item, next, &efi_register_notify_events, + link) { + if (event == item->event) { + struct efi_protocol_notification *hitem, *hnext; + + /* Remove signaled handles */ + list_for_each_entry_safe(hitem, hnext, &item->handles, + link) { + list_del(&hitem->link); + free(hitem); + } + list_del(&item->link); + free(item); + } + } + /* Remove event from queue */ + if (efi_event_is_queued(event)) + list_del(&event->queue_link); + + list_del(&event->link); + efi_free_pool(event); + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_check_event() - check if an event is signaled + * @event: event to check + * + * This function implements the CheckEvent service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * If an event is not signaled yet, the notification function is queued. The + * signaled state is cleared. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_check_event(struct efi_event *event) +{ + EFI_ENTRY("%p", event); + efi_timer_check(); + if (efi_is_event(event) != EFI_SUCCESS || + event->type & EVT_NOTIFY_SIGNAL) + return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event->is_signaled) + efi_queue_event(event); + if (event->is_signaled) { + event->is_signaled = false; + return EFI_EXIT(EFI_SUCCESS); + } + return EFI_EXIT(EFI_NOT_READY); +} + +/** + * efi_search_obj() - find the internal EFI object for a handle + * @handle: handle to find + * + * Return: EFI object + */ +struct efi_object *efi_search_obj(const efi_handle_t handle) +{ + struct efi_object *efiobj; + + if (!handle) + return NULL; + + list_for_each_entry(efiobj, &efi_obj_list, link) { + if (efiobj == handle) + return efiobj; + } + return NULL; +} + +/** + * efi_open_protocol_info_entry() - create open protocol info entry and add it + * to a protocol + * @handler: handler of a protocol + * + * Return: open protocol info entry + */ +static struct efi_open_protocol_info_entry *efi_create_open_info( + struct efi_handler *handler) +{ + struct efi_open_protocol_info_item *item; + + item = calloc(1, sizeof(struct efi_open_protocol_info_item)); + if (!item) + return NULL; + /* Append the item to the open protocol info list. */ + list_add_tail(&item->link, &handler->open_infos); + + return &item->info; +} + +/** + * efi_delete_open_info() - remove an open protocol info entry from a protocol + * @item: open protocol info entry to delete + * + * Return: status code + */ +static efi_status_t efi_delete_open_info( + struct efi_open_protocol_info_item *item) +{ + list_del(&item->link); + free(item); + return EFI_SUCCESS; +} + +/** + * efi_add_protocol() - install new protocol on a handle + * @handle: handle on which the protocol shall be installed + * @protocol: GUID of the protocol to be installed + * @protocol_interface: interface of the protocol implementation + * + * Return: status code + */ +efi_status_t efi_add_protocol(const efi_handle_t handle, + const efi_guid_t *protocol, + void *protocol_interface) +{ + struct efi_object *efiobj; + struct efi_handler *handler; + efi_status_t ret; + struct efi_register_notify_event *event; + + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_INVALID_PARAMETER; + ret = efi_search_protocol(handle, protocol, NULL); + if (ret != EFI_NOT_FOUND) + return EFI_INVALID_PARAMETER; + handler = calloc(1, sizeof(struct efi_handler)); + if (!handler) + return EFI_OUT_OF_RESOURCES; + handler->guid = protocol; + handler->protocol_interface = protocol_interface; + INIT_LIST_HEAD(&handler->open_infos); + list_add_tail(&handler->link, &efiobj->protocols); + + /* Notify registered events */ + list_for_each_entry(event, &efi_register_notify_events, link) { + if (!guidcmp(protocol, &event->protocol)) { + struct efi_protocol_notification *notif; + + notif = calloc(1, sizeof(*notif)); + if (!notif) { + list_del(&handler->link); + free(handler); + return EFI_OUT_OF_RESOURCES; + } + notif->handle = handle; + list_add_tail(¬if->link, &event->handles); + event->event->is_signaled = false; + efi_signal_event(event->event); + } + } + + if (!guidcmp(&efi_guid_device_path, protocol)) + EFI_PRINT("installed device path '%pD'\n", protocol_interface); + return EFI_SUCCESS; +} + +/** + * efi_install_protocol_interface() - install protocol interface + * @handle: handle on which the protocol shall be installed + * @protocol: GUID of the protocol to be installed + * @protocol_interface_type: type of the interface to be installed, + * always EFI_NATIVE_INTERFACE + * @protocol_interface: interface of the protocol implementation + * + * This function implements the InstallProtocolInterface service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_install_protocol_interface( + efi_handle_t *handle, const efi_guid_t *protocol, + int protocol_interface_type, void *protocol_interface) +{ + efi_status_t r; + + EFI_ENTRY("%p, %pUl, %d, %p", handle, protocol, protocol_interface_type, + protocol_interface); + + if (!handle || !protocol || + protocol_interface_type != EFI_NATIVE_INTERFACE) { + r = EFI_INVALID_PARAMETER; + goto out; + } + + /* Create new handle if requested. */ + if (!*handle) { + r = efi_create_handle(handle); + if (r != EFI_SUCCESS) + goto out; + EFI_PRINT("new handle %p\n", *handle); + } else { + EFI_PRINT("handle %p\n", *handle); + } + /* Add new protocol */ + r = efi_add_protocol(*handle, protocol, protocol_interface); +out: + return EFI_EXIT(r); +} + +/** + * efi_get_drivers() - get all drivers associated to a controller + * @handle: handle of the controller + * @protocol: protocol GUID (optional) + * @number_of_drivers: number of child controllers + * @driver_handle_buffer: handles of the the drivers + * + * The allocated buffer has to be freed with free(). + * + * Return: status code + */ +static efi_status_t efi_get_drivers(efi_handle_t handle, + const efi_guid_t *protocol, + efi_uintn_t *number_of_drivers, + efi_handle_t **driver_handle_buffer) +{ + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + efi_uintn_t count = 0, i; + bool duplicate; + + /* Count all driver associations */ + list_for_each_entry(handler, &handle->protocols, link) { + if (protocol && guidcmp(handler->guid, protocol)) + continue; + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_DRIVER) + ++count; + } + } + *number_of_drivers = 0; + if (!count) { + *driver_handle_buffer = NULL; + return EFI_SUCCESS; + } + /* + * Create buffer. In case of duplicate driver assignments the buffer + * will be too large. But that does not harm. + */ + *driver_handle_buffer = calloc(count, sizeof(efi_handle_t)); + if (!*driver_handle_buffer) + return EFI_OUT_OF_RESOURCES; + /* Collect unique driver handles */ + list_for_each_entry(handler, &handle->protocols, link) { + if (protocol && guidcmp(handler->guid, protocol)) + continue; + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_DRIVER) { + /* Check this is a new driver */ + duplicate = false; + for (i = 0; i < *number_of_drivers; ++i) { + if ((*driver_handle_buffer)[i] == + item->info.agent_handle) + duplicate = true; + } + /* Copy handle to buffer */ + if (!duplicate) { + i = (*number_of_drivers)++; + (*driver_handle_buffer)[i] = + item->info.agent_handle; + } + } + } + } + return EFI_SUCCESS; +} + +/** + * efi_disconnect_all_drivers() - disconnect all drivers from a controller + * @handle: handle of the controller + * @protocol: protocol GUID (optional) + * @child_handle: handle of the child to destroy + * + * This function implements the DisconnectController service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t efi_disconnect_all_drivers + (efi_handle_t handle, + const efi_guid_t *protocol, + efi_handle_t child_handle) +{ + efi_uintn_t number_of_drivers; + efi_handle_t *driver_handle_buffer; + efi_status_t r, ret; + + ret = efi_get_drivers(handle, protocol, &number_of_drivers, + &driver_handle_buffer); + if (ret != EFI_SUCCESS) + return ret; + if (!number_of_drivers) + return EFI_SUCCESS; + ret = EFI_NOT_FOUND; + while (number_of_drivers) { + r = EFI_CALL(efi_disconnect_controller( + handle, + driver_handle_buffer[--number_of_drivers], + child_handle)); + if (r == EFI_SUCCESS) + ret = r; + } + free(driver_handle_buffer); + return ret; +} + +/** + * efi_uninstall_protocol() - uninstall protocol interface + * + * @handle: handle from which the protocol shall be removed + * @protocol: GUID of the protocol to be removed + * @protocol_interface: interface to be removed + * + * This function DOES NOT delete a handle without installed protocol. + * + * Return: status code + */ +static efi_status_t efi_uninstall_protocol + (efi_handle_t handle, const efi_guid_t *protocol, + void *protocol_interface) +{ + struct efi_object *efiobj; + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + struct efi_open_protocol_info_item *pos; + efi_status_t r; + + /* Check handle */ + efiobj = efi_search_obj(handle); + if (!efiobj) { + r = EFI_INVALID_PARAMETER; + goto out; + } + /* Find the protocol on the handle */ + r = efi_search_protocol(handle, protocol, &handler); + if (r != EFI_SUCCESS) + goto out; + /* Disconnect controllers */ + efi_disconnect_all_drivers(efiobj, protocol, NULL); + /* Close protocol */ + list_for_each_entry_safe(item, pos, &handler->open_infos, link) { + if (item->info.attributes == + EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL || + item->info.attributes == EFI_OPEN_PROTOCOL_GET_PROTOCOL || + item->info.attributes == EFI_OPEN_PROTOCOL_TEST_PROTOCOL) + list_del(&item->link); + } + if (!list_empty(&handler->open_infos)) { + r = EFI_ACCESS_DENIED; + goto out; + } + r = efi_remove_protocol(handle, protocol, protocol_interface); +out: + return r; +} + +/** + * efi_uninstall_protocol_interface() - uninstall protocol interface + * @handle: handle from which the protocol shall be removed + * @protocol: GUID of the protocol to be removed + * @protocol_interface: interface to be removed + * + * This function implements the UninstallProtocolInterface service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_uninstall_protocol_interface + (efi_handle_t handle, const efi_guid_t *protocol, + void *protocol_interface) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %pUl, %p", handle, protocol, protocol_interface); + + ret = efi_uninstall_protocol(handle, protocol, protocol_interface); + if (ret != EFI_SUCCESS) + goto out; + + /* If the last protocol has been removed, delete the handle. */ + if (list_empty(&handle->protocols)) { + list_del(&handle->link); + free(handle); + } +out: + return EFI_EXIT(ret); +} + +/** + * efi_register_protocol_notify() - register an event for notification when a + * protocol is installed. + * @protocol: GUID of the protocol whose installation shall be notified + * @event: event to be signaled upon installation of the protocol + * @registration: key for retrieving the registration information + * + * This function implements the RegisterProtocolNotify service. + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_register_protocol_notify( + const efi_guid_t *protocol, + struct efi_event *event, + void **registration) +{ + struct efi_register_notify_event *item; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%pUl, %p, %p", protocol, event, registration); + + if (!protocol || !event || !registration) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + item = calloc(1, sizeof(struct efi_register_notify_event)); + if (!item) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + item->event = event; + memcpy(&item->protocol, protocol, sizeof(efi_guid_t)); + INIT_LIST_HEAD(&item->handles); + + list_add_tail(&item->link, &efi_register_notify_events); + + *registration = item; +out: + return EFI_EXIT(ret); +} + +/** + * efi_search() - determine if an EFI handle implements a protocol + * + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @handle: handle + * + * See the documentation of the LocateHandle service in the UEFI specification. + * + * Return: 0 if the handle implements the protocol + */ +static int efi_search(enum efi_locate_search_type search_type, + const efi_guid_t *protocol, efi_handle_t handle) +{ + efi_status_t ret; + + switch (search_type) { + case ALL_HANDLES: + return 0; + case BY_PROTOCOL: + ret = efi_search_protocol(handle, protocol, NULL); + return (ret != EFI_SUCCESS); + default: + /* Invalid search type */ + return -1; + } +} + +/** + * efi_check_register_notify_event() - check if registration key is valid + * + * Check that a pointer is a valid registration key as returned by + * RegisterProtocolNotify(). + * + * @key: registration key + * Return: valid registration key or NULL + */ +static struct efi_register_notify_event *efi_check_register_notify_event + (void *key) +{ + struct efi_register_notify_event *event; + + list_for_each_entry(event, &efi_register_notify_events, link) { + if (event == (struct efi_register_notify_event *)key) + return event; + } + return NULL; +} + +/** + * efi_locate_handle() - locate handles implementing a protocol + * + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @search_key: registration key + * @buffer_size: size of the buffer to receive the handles in bytes + * @buffer: buffer to receive the relevant handles + * + * This function is meant for U-Boot internal calls. For the API implementation + * of the LocateHandle service see efi_locate_handle_ext. + * + * Return: status code + */ +static efi_status_t efi_locate_handle( + enum efi_locate_search_type search_type, + const efi_guid_t *protocol, void *search_key, + efi_uintn_t *buffer_size, efi_handle_t *buffer) +{ + struct efi_object *efiobj; + efi_uintn_t size = 0; + struct efi_register_notify_event *event; + struct efi_protocol_notification *handle = NULL; + + /* Check parameters */ + switch (search_type) { + case ALL_HANDLES: + break; + case BY_REGISTER_NOTIFY: + if (!search_key) + return EFI_INVALID_PARAMETER; + /* Check that the registration key is valid */ + event = efi_check_register_notify_event(search_key); + if (!event) + return EFI_INVALID_PARAMETER; + break; + case BY_PROTOCOL: + if (!protocol) + return EFI_INVALID_PARAMETER; + break; + default: + return EFI_INVALID_PARAMETER; + } + + /* Count how much space we need */ + if (search_type == BY_REGISTER_NOTIFY) { + if (list_empty(&event->handles)) + return EFI_NOT_FOUND; + handle = list_first_entry(&event->handles, + struct efi_protocol_notification, + link); + efiobj = handle->handle; + size += sizeof(void *); + } else { + list_for_each_entry(efiobj, &efi_obj_list, link) { + if (!efi_search(search_type, protocol, efiobj)) + size += sizeof(void *); + } + if (size == 0) + return EFI_NOT_FOUND; + } + + if (!buffer_size) + return EFI_INVALID_PARAMETER; + + if (*buffer_size < size) { + *buffer_size = size; + return EFI_BUFFER_TOO_SMALL; + } + + *buffer_size = size; + + /* The buffer size is sufficient but there is no buffer */ + if (!buffer) + return EFI_INVALID_PARAMETER; + + /* Then fill the array */ + if (search_type == BY_REGISTER_NOTIFY) { + *buffer = efiobj; + list_del(&handle->link); + } else { + list_for_each_entry(efiobj, &efi_obj_list, link) { + if (!efi_search(search_type, protocol, efiobj)) + *buffer++ = efiobj; + } + } + + return EFI_SUCCESS; +} + +/** + * efi_locate_handle_ext() - locate handles implementing a protocol. + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @search_key: registration key + * @buffer_size: size of the buffer to receive the handles in bytes + * @buffer: buffer to receive the relevant handles + * + * This function implements the LocateHandle service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: 0 if the handle implements the protocol + */ +static efi_status_t EFIAPI efi_locate_handle_ext( + enum efi_locate_search_type search_type, + const efi_guid_t *protocol, void *search_key, + efi_uintn_t *buffer_size, efi_handle_t *buffer) +{ + EFI_ENTRY("%d, %pUl, %p, %p, %p", search_type, protocol, search_key, + buffer_size, buffer); + + return EFI_EXIT(efi_locate_handle(search_type, protocol, search_key, + buffer_size, buffer)); +} + +/** + * efi_remove_configuration_table() - collapses configuration table entries, + * removing index i + * + * @i: index of the table entry to be removed + */ +static void efi_remove_configuration_table(int i) +{ + struct efi_configuration_table *this = &systab.tables[i]; + struct efi_configuration_table *next = &systab.tables[i + 1]; + struct efi_configuration_table *end = &systab.tables[systab.nr_tables]; + + memmove(this, next, (ulong)end - (ulong)next); + systab.nr_tables--; +} + +/** + * efi_install_configuration_table() - adds, updates, or removes a + * configuration table + * @guid: GUID of the installed table + * @table: table to be installed + * + * This function is used for internal calls. For the API implementation of the + * InstallConfigurationTable service see efi_install_configuration_table_ext. + * + * Return: status code + */ +efi_status_t efi_install_configuration_table(const efi_guid_t *guid, + void *table) +{ + struct efi_event *evt; + int i; + + if (!guid) + return EFI_INVALID_PARAMETER; + + /* Check for GUID override */ + for (i = 0; i < systab.nr_tables; i++) { + if (!guidcmp(guid, &systab.tables[i].guid)) { + if (table) + systab.tables[i].table = table; + else + efi_remove_configuration_table(i); + goto out; + } + } + + if (!table) + return EFI_NOT_FOUND; + + /* No override, check for overflow */ + if (i >= EFI_MAX_CONFIGURATION_TABLES) + return EFI_OUT_OF_RESOURCES; + + /* Add a new entry */ + memcpy(&systab.tables[i].guid, guid, sizeof(*guid)); + systab.tables[i].table = table; + systab.nr_tables = i + 1; + +out: + /* systab.nr_tables may have changed. So we need to update the CRC32 */ + efi_update_table_header_crc32(&systab.hdr); + + /* Notify that the configuration table was changed */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && !guidcmp(evt->group, guid)) { + efi_signal_event(evt); + break; + } + } + + return EFI_SUCCESS; +} + +/** + * efi_install_configuration_table_ex() - Adds, updates, or removes a + * configuration table. + * @guid: GUID of the installed table + * @table: table to be installed + * + * This function implements the InstallConfigurationTable service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_install_configuration_table_ext(efi_guid_t *guid, + void *table) +{ + EFI_ENTRY("%pUl, %p", guid, table); + return EFI_EXIT(efi_install_configuration_table(guid, table)); +} + +/** + * efi_setup_loaded_image() - initialize a loaded image + * + * Initialize a loaded_image_info and loaded_image_info object with correct + * protocols, boot-device, etc. + * + * In case of an error \*handle_ptr and \*info_ptr are set to NULL and an error + * code is returned. + * + * @device_path: device path of the loaded image + * @file_path: file path of the loaded image + * @handle_ptr: handle of the loaded image + * @info_ptr: loaded image protocol + * Return: status code + */ +efi_status_t efi_setup_loaded_image(struct efi_device_path *device_path, + struct efi_device_path *file_path, + struct efi_loaded_image_obj **handle_ptr, + struct efi_loaded_image **info_ptr) +{ + efi_status_t ret; + struct efi_loaded_image *info = NULL; + struct efi_loaded_image_obj *obj = NULL; + struct efi_device_path *dp; + + /* In case of EFI_OUT_OF_RESOURCES avoid illegal free by caller. */ + *handle_ptr = NULL; + *info_ptr = NULL; + + info = calloc(1, sizeof(*info)); + if (!info) + return EFI_OUT_OF_RESOURCES; + obj = calloc(1, sizeof(*obj)); + if (!obj) { + free(info); + return EFI_OUT_OF_RESOURCES; + } + obj->header.type = EFI_OBJECT_TYPE_LOADED_IMAGE; + + /* Add internal object to object list */ + efi_add_handle(&obj->header); + + info->revision = EFI_LOADED_IMAGE_PROTOCOL_REVISION; + info->file_path = file_path; + info->system_table = &systab; + + if (device_path) { + info->device_handle = efi_dp_find_obj(device_path, NULL); + + dp = efi_dp_append(device_path, file_path); + if (!dp) { + ret = EFI_OUT_OF_RESOURCES; + goto failure; + } + } else { + dp = NULL; + } + ret = efi_add_protocol(&obj->header, + &efi_guid_loaded_image_device_path, dp); + if (ret != EFI_SUCCESS) + goto failure; + + /* + * When asking for the loaded_image interface, just + * return handle which points to loaded_image_info + */ + ret = efi_add_protocol(&obj->header, + &efi_guid_loaded_image, info); + if (ret != EFI_SUCCESS) + goto failure; + + *info_ptr = info; + *handle_ptr = obj; + + return ret; +failure: + printf("ERROR: Failure to install protocols for loaded image\n"); + efi_delete_handle(&obj->header); + free(info); + return ret; +} + +/** + * efi_load_image_from_path() - load an image using a file path + * + * Read a file into a buffer allocated as EFI_BOOT_SERVICES_DATA. It is the + * callers obligation to update the memory type as needed. + * + * @file_path: the path of the image to load + * @buffer: buffer containing the loaded image + * @size: size of the loaded image + * Return: status code + */ +static +efi_status_t efi_load_image_from_path(struct efi_device_path *file_path, + void **buffer, efi_uintn_t *size) +{ + struct efi_file_info *info = NULL; + struct efi_file_handle *f; + static efi_status_t ret; + u64 addr; + efi_uintn_t bs; + + /* In case of failure nothing is returned */ + *buffer = NULL; + *size = 0; + + /* Open file */ + f = efi_file_from_path(file_path); + if (!f) + return EFI_NOT_FOUND; + + /* Get file size */ + bs = 0; + EFI_CALL(ret = f->getinfo(f, (efi_guid_t *)&efi_file_info_guid, + &bs, info)); + if (ret != EFI_BUFFER_TOO_SMALL) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + info = malloc(bs); + EFI_CALL(ret = f->getinfo(f, (efi_guid_t *)&efi_file_info_guid, &bs, + info)); + if (ret != EFI_SUCCESS) + goto error; + + /* + * When reading the file we do not yet know if it contains an + * application, a boottime driver, or a runtime driver. So here we + * allocate a buffer as EFI_BOOT_SERVICES_DATA. The caller has to + * update the reservation according to the image type. + */ + bs = info->file_size; + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_BOOT_SERVICES_DATA, + efi_size_in_pages(bs), &addr); + if (ret != EFI_SUCCESS) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + + /* Read file */ + EFI_CALL(ret = f->read(f, &bs, (void *)(uintptr_t)addr)); + if (ret != EFI_SUCCESS) + efi_free_pages(addr, efi_size_in_pages(bs)); + *buffer = (void *)(uintptr_t)addr; + *size = bs; +error: + EFI_CALL(f->close(f)); + free(info); + return ret; +} + +/** + * efi_load_image() - load an EFI image into memory + * @boot_policy: true for request originating from the boot manager + * @parent_image: the caller's image handle + * @file_path: the path of the image to load + * @source_buffer: memory location from which the image is installed + * @source_size: size of the memory area from which the image is installed + * @image_handle: handle for the newly installed image + * + * This function implements the LoadImage service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_load_image(bool boot_policy, + efi_handle_t parent_image, + struct efi_device_path *file_path, + void *source_buffer, + efi_uintn_t source_size, + efi_handle_t *image_handle) +{ + struct efi_device_path *dp, *fp; + struct efi_loaded_image *info = NULL; + struct efi_loaded_image_obj **image_obj = + (struct efi_loaded_image_obj **)image_handle; + efi_status_t ret; + void *dest_buffer; + + EFI_ENTRY("%d, %p, %pD, %p, %zd, %p", boot_policy, parent_image, + file_path, source_buffer, source_size, image_handle); + + if (!image_handle || (!source_buffer && !file_path) || + !efi_search_obj(parent_image) || + /* The parent image handle must refer to a loaded image */ + !parent_image->type) { + ret = EFI_INVALID_PARAMETER; + goto error; + } + + if (!source_buffer) { + ret = efi_load_image_from_path(file_path, &dest_buffer, + &source_size); + if (ret != EFI_SUCCESS) + goto error; + } else { + if (!source_size) { + ret = EFI_LOAD_ERROR; + goto error; + } + dest_buffer = source_buffer; + } + /* split file_path which contains both the device and file parts */ + efi_dp_split_file_path(file_path, &dp, &fp); + ret = efi_setup_loaded_image(dp, fp, image_obj, &info); + if (ret == EFI_SUCCESS) + ret = efi_load_pe(*image_obj, dest_buffer, info); + if (!source_buffer) + /* Release buffer to which file was loaded */ + efi_free_pages((uintptr_t)dest_buffer, + efi_size_in_pages(source_size)); + if (ret == EFI_SUCCESS) { + info->system_table = &systab; + info->parent_handle = parent_image; + } else { + /* The image is invalid. Release all associated resources. */ + efi_delete_handle(*image_handle); + *image_handle = NULL; + free(info); + } +error: + return EFI_EXIT(ret); +} + +/** + * efi_exit_caches() - fix up caches for EFI payloads if necessary + */ +static void efi_exit_caches(void) +{ +#if defined(CONFIG_EFI_GRUB_ARM32_WORKAROUND) + /* + * Boooting Linux via GRUB prior to version 2.04 fails on 32bit ARM if + * caches are enabled. + * + * TODO: + * According to the UEFI spec caches that can be managed via CP15 + * operations should be enabled. Caches requiring platform information + * to manage should be disabled. This should not happen in + * ExitBootServices() but before invoking any UEFI binary is invoked. + * + * We want to keep the current workaround while GRUB prior to version + * 2.04 is still in use. + */ + cleanup_before_linux(); +#endif +} + +/** + * efi_exit_boot_services() - stop all boot services + * @image_handle: handle of the loaded image + * @map_key: key of the memory map + * + * This function implements the ExitBootServices service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * All timer events are disabled. For exit boot services events the + * notification function is called. The boot services are disabled in the + * system table. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_exit_boot_services(efi_handle_t image_handle, + efi_uintn_t map_key) +{ + struct efi_event *evt, *next_event; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %zx", image_handle, map_key); + + /* Check that the caller has read the current memory map */ + if (map_key != efi_memory_map_key) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Check if ExitBootServices has already been called */ + if (!systab.boottime) + goto out; + + /* Stop all timer related activities */ + timers_enabled = false; + + /* Add related events to the event group */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->type == EVT_SIGNAL_EXIT_BOOT_SERVICES) + evt->group = &efi_guid_event_group_exit_boot_services; + } + /* Notify that ExitBootServices is invoked. */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_exit_boot_services)) { + efi_signal_event(evt); + break; + } + } + + /* Make sure that notification functions are not called anymore */ + efi_tpl = TPL_HIGH_LEVEL; + + /* Notify variable services */ + efi_variables_boot_exit_notify(); + + /* Remove all events except EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE */ + list_for_each_entry_safe(evt, next_event, &efi_events, link) { + if (evt->type != EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE) + list_del(&evt->link); + } + + board_quiesce_devices(); + + /* Patch out unsupported runtime function */ + efi_runtime_detach(); + + /* Fix up caches for EFI payloads if necessary */ + efi_exit_caches(); + + /* This stops all lingering devices */ + bootm_disable_interrupts(); + + /* Disable boot time services */ + systab.con_in_handle = NULL; + systab.con_in = NULL; + systab.con_out_handle = NULL; + systab.con_out = NULL; + systab.stderr_handle = NULL; + systab.std_err = NULL; + systab.boottime = NULL; + + /* Recalculate CRC32 */ + efi_update_table_header_crc32(&systab.hdr); + + /* Give the payload some time to boot */ + efi_set_watchdog(0); + WATCHDOG_RESET(); +out: + return EFI_EXIT(ret); +} + +/** + * efi_get_next_monotonic_count() - get next value of the counter + * @count: returned value of the counter + * + * This function implements the NextMonotonicCount service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_get_next_monotonic_count(uint64_t *count) +{ + static uint64_t mono; + efi_status_t ret; + + EFI_ENTRY("%p", count); + if (!count) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + *count = mono++; + ret = EFI_SUCCESS; +out: + return EFI_EXIT(ret); +} + +/** + * efi_stall() - sleep + * @microseconds: period to sleep in microseconds + * + * This function implements the Stall service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_stall(unsigned long microseconds) +{ + u64 end_tick; + + EFI_ENTRY("%ld", microseconds); + + end_tick = get_ticks() + usec_to_tick(microseconds); + while (get_ticks() < end_tick) + efi_timer_check(); + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_set_watchdog_timer() - reset the watchdog timer + * @timeout: seconds before reset by watchdog + * @watchdog_code: code to be logged when resetting + * @data_size: size of buffer in bytes + * @watchdog_data: buffer with data describing the reset reason + * + * This function implements the SetWatchdogTimer service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_set_watchdog_timer(unsigned long timeout, + uint64_t watchdog_code, + unsigned long data_size, + uint16_t *watchdog_data) +{ + EFI_ENTRY("%ld, 0x%llx, %ld, %p", timeout, watchdog_code, + data_size, watchdog_data); + return EFI_EXIT(efi_set_watchdog(timeout)); +} + +/** + * efi_close_protocol() - close a protocol + * @handle: handle on which the protocol shall be closed + * @protocol: GUID of the protocol to close + * @agent_handle: handle of the driver + * @controller_handle: handle of the controller + * + * This function implements the CloseProtocol service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_close_protocol(efi_handle_t handle, + const efi_guid_t *protocol, + efi_handle_t agent_handle, + efi_handle_t controller_handle) +{ + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + struct efi_open_protocol_info_item *pos; + efi_status_t r; + + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, agent_handle, + controller_handle); + + if (!efi_search_obj(agent_handle) || + (controller_handle && !efi_search_obj(controller_handle))) { + r = EFI_INVALID_PARAMETER; + goto out; + } + r = efi_search_protocol(handle, protocol, &handler); + if (r != EFI_SUCCESS) + goto out; + + r = EFI_NOT_FOUND; + list_for_each_entry_safe(item, pos, &handler->open_infos, link) { + if (item->info.agent_handle == agent_handle && + item->info.controller_handle == controller_handle) { + efi_delete_open_info(item); + r = EFI_SUCCESS; + } + } +out: + return EFI_EXIT(r); +} + +/** + * efi_open_protocol_information() - provide information about then open status + * of a protocol on a handle + * @handle: handle for which the information shall be retrieved + * @protocol: GUID of the protocol + * @entry_buffer: buffer to receive the open protocol information + * @entry_count: number of entries available in the buffer + * + * This function implements the OpenProtocolInformation service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_open_protocol_information( + efi_handle_t handle, const efi_guid_t *protocol, + struct efi_open_protocol_info_entry **entry_buffer, + efi_uintn_t *entry_count) +{ + unsigned long buffer_size; + unsigned long count; + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + efi_status_t r; + + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, entry_buffer, + entry_count); + + /* Check parameters */ + if (!entry_buffer) { + r = EFI_INVALID_PARAMETER; + goto out; + } + r = efi_search_protocol(handle, protocol, &handler); + if (r != EFI_SUCCESS) + goto out; + + /* Count entries */ + count = 0; + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.open_count) + ++count; + } + *entry_count = count; + *entry_buffer = NULL; + if (!count) { + r = EFI_SUCCESS; + goto out; + } + + /* Copy entries */ + buffer_size = count * sizeof(struct efi_open_protocol_info_entry); + r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size, + (void **)entry_buffer); + if (r != EFI_SUCCESS) + goto out; + list_for_each_entry_reverse(item, &handler->open_infos, link) { + if (item->info.open_count) + (*entry_buffer)[--count] = item->info; + } +out: + return EFI_EXIT(r); +} + +/** + * efi_protocols_per_handle() - get protocols installed on a handle + * @handle: handle for which the information is retrieved + * @protocol_buffer: buffer with protocol GUIDs + * @protocol_buffer_count: number of entries in the buffer + * + * This function implements the ProtocolsPerHandleService. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_protocols_per_handle( + efi_handle_t handle, efi_guid_t ***protocol_buffer, + efi_uintn_t *protocol_buffer_count) +{ + unsigned long buffer_size; + struct efi_object *efiobj; + struct list_head *protocol_handle; + efi_status_t r; + + EFI_ENTRY("%p, %p, %p", handle, protocol_buffer, + protocol_buffer_count); + + if (!handle || !protocol_buffer || !protocol_buffer_count) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + *protocol_buffer = NULL; + *protocol_buffer_count = 0; + + efiobj = efi_search_obj(handle); + if (!efiobj) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* Count protocols */ + list_for_each(protocol_handle, &efiobj->protocols) { + ++*protocol_buffer_count; + } + + /* Copy GUIDs */ + if (*protocol_buffer_count) { + size_t j = 0; + + buffer_size = sizeof(efi_guid_t *) * *protocol_buffer_count; + r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size, + (void **)protocol_buffer); + if (r != EFI_SUCCESS) + return EFI_EXIT(r); + list_for_each(protocol_handle, &efiobj->protocols) { + struct efi_handler *protocol; + + protocol = list_entry(protocol_handle, + struct efi_handler, link); + (*protocol_buffer)[j] = (void *)protocol->guid; + ++j; + } + } + + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_locate_handle_buffer() - locate handles implementing a protocol + * @search_type: selection criterion + * @protocol: GUID of the protocol + * @search_key: registration key + * @no_handles: number of returned handles + * @buffer: buffer with the returned handles + * + * This function implements the LocateHandleBuffer service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_locate_handle_buffer( + enum efi_locate_search_type search_type, + const efi_guid_t *protocol, void *search_key, + efi_uintn_t *no_handles, efi_handle_t **buffer) +{ + efi_status_t r; + efi_uintn_t buffer_size = 0; + + EFI_ENTRY("%d, %pUl, %p, %p, %p", search_type, protocol, search_key, + no_handles, buffer); + + if (!no_handles || !buffer) { + r = EFI_INVALID_PARAMETER; + goto out; + } + *no_handles = 0; + *buffer = NULL; + r = efi_locate_handle(search_type, protocol, search_key, &buffer_size, + *buffer); + if (r != EFI_BUFFER_TOO_SMALL) + goto out; + r = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, buffer_size, + (void **)buffer); + if (r != EFI_SUCCESS) + goto out; + r = efi_locate_handle(search_type, protocol, search_key, &buffer_size, + *buffer); + if (r == EFI_SUCCESS) + *no_handles = buffer_size / sizeof(efi_handle_t); +out: + return EFI_EXIT(r); +} + +/** + * efi_locate_protocol() - find an interface implementing a protocol + * @protocol: GUID of the protocol + * @registration: registration key passed to the notification function + * @protocol_interface: interface implementing the protocol + * + * This function implements the LocateProtocol service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_locate_protocol(const efi_guid_t *protocol, + void *registration, + void **protocol_interface) +{ + struct efi_handler *handler; + efi_status_t ret; + struct efi_object *efiobj; + + EFI_ENTRY("%pUl, %p, %p", protocol, registration, protocol_interface); + + /* + * The UEFI spec explicitly requires a protocol even if a registration + * key is provided. This differs from the logic in LocateHandle(). + */ + if (!protocol || !protocol_interface) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (registration) { + struct efi_register_notify_event *event; + struct efi_protocol_notification *handle; + + event = efi_check_register_notify_event(registration); + if (!event) + return EFI_EXIT(EFI_INVALID_PARAMETER); + /* + * The UEFI spec requires to return EFI_NOT_FOUND if no + * protocol instance matches protocol and registration. + * So let's do the same for a mismatch between protocol and + * registration. + */ + if (guidcmp(&event->protocol, protocol)) + goto not_found; + if (list_empty(&event->handles)) + goto not_found; + handle = list_first_entry(&event->handles, + struct efi_protocol_notification, + link); + efiobj = handle->handle; + list_del(&handle->link); + free(handle); + ret = efi_search_protocol(efiobj, protocol, &handler); + if (ret == EFI_SUCCESS) + goto found; + } else { + list_for_each_entry(efiobj, &efi_obj_list, link) { + ret = efi_search_protocol(efiobj, protocol, &handler); + if (ret == EFI_SUCCESS) + goto found; + } + } +not_found: + *protocol_interface = NULL; + return EFI_EXIT(EFI_NOT_FOUND); +found: + *protocol_interface = handler->protocol_interface; + return EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_locate_device_path() - Get the device path and handle of an device + * implementing a protocol + * @protocol: GUID of the protocol + * @device_path: device path + * @device: handle of the device + * + * This function implements the LocateDevicePath service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_locate_device_path( + const efi_guid_t *protocol, + struct efi_device_path **device_path, + efi_handle_t *device) +{ + struct efi_device_path *dp; + size_t i; + struct efi_handler *handler; + efi_handle_t *handles; + size_t len, len_dp; + size_t len_best = 0; + efi_uintn_t no_handles; + u8 *remainder; + efi_status_t ret; + + EFI_ENTRY("%pUl, %p, %p", protocol, device_path, device); + + if (!protocol || !device_path || !*device_path) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Find end of device path */ + len = efi_dp_instance_size(*device_path); + + /* Get all handles implementing the protocol */ + ret = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, protocol, NULL, + &no_handles, &handles)); + if (ret != EFI_SUCCESS) + goto out; + + for (i = 0; i < no_handles; ++i) { + /* Find the device path protocol */ + ret = efi_search_protocol(handles[i], &efi_guid_device_path, + &handler); + if (ret != EFI_SUCCESS) + continue; + dp = (struct efi_device_path *)handler->protocol_interface; + len_dp = efi_dp_instance_size(dp); + /* + * This handle can only be a better fit + * if its device path length is longer than the best fit and + * if its device path length is shorter of equal the searched + * device path. + */ + if (len_dp <= len_best || len_dp > len) + continue; + /* Check if dp is a subpath of device_path */ + if (memcmp(*device_path, dp, len_dp)) + continue; + if (!device) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + *device = handles[i]; + len_best = len_dp; + } + if (len_best) { + remainder = (u8 *)*device_path + len_best; + *device_path = (struct efi_device_path *)remainder; + ret = EFI_SUCCESS; + } else { + ret = EFI_NOT_FOUND; + } +out: + return EFI_EXIT(ret); +} + +/** + * efi_install_multiple_protocol_interfaces() - Install multiple protocol + * interfaces + * @handle: handle on which the protocol interfaces shall be installed + * @...: NULL terminated argument list with pairs of protocol GUIDS and + * interfaces + * + * This function implements the MultipleProtocolInterfaces service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_install_multiple_protocol_interfaces + (efi_handle_t *handle, ...) +{ + EFI_ENTRY("%p", handle); + + efi_va_list argptr; + const efi_guid_t *protocol; + void *protocol_interface; + efi_handle_t old_handle; + efi_status_t r = EFI_SUCCESS; + int i = 0; + + if (!handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + efi_va_start(argptr, handle); + for (;;) { + protocol = efi_va_arg(argptr, efi_guid_t*); + if (!protocol) + break; + protocol_interface = efi_va_arg(argptr, void*); + /* Check that a device path has not been installed before */ + if (!guidcmp(protocol, &efi_guid_device_path)) { + struct efi_device_path *dp = protocol_interface; + + r = EFI_CALL(efi_locate_device_path(protocol, &dp, + &old_handle)); + if (r == EFI_SUCCESS && + dp->type == DEVICE_PATH_TYPE_END) { + EFI_PRINT("Path %pD already installed\n", + protocol_interface); + r = EFI_ALREADY_STARTED; + break; + } + } + r = EFI_CALL(efi_install_protocol_interface( + handle, protocol, + EFI_NATIVE_INTERFACE, + protocol_interface)); + if (r != EFI_SUCCESS) + break; + i++; + } + efi_va_end(argptr); + if (r == EFI_SUCCESS) + return EFI_EXIT(r); + + /* If an error occurred undo all changes. */ + efi_va_start(argptr, handle); + for (; i; --i) { + protocol = efi_va_arg(argptr, efi_guid_t*); + protocol_interface = efi_va_arg(argptr, void*); + EFI_CALL(efi_uninstall_protocol_interface(*handle, protocol, + protocol_interface)); + } + efi_va_end(argptr); + + return EFI_EXIT(r); +} + +/** + * efi_uninstall_multiple_protocol_interfaces() - uninstall multiple protocol + * interfaces + * @handle: handle from which the protocol interfaces shall be removed + * @...: NULL terminated argument list with pairs of protocol GUIDS and + * interfaces + * + * This function implements the UninstallMultipleProtocolInterfaces service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_uninstall_multiple_protocol_interfaces( + efi_handle_t handle, ...) +{ + EFI_ENTRY("%p", handle); + + efi_va_list argptr; + const efi_guid_t *protocol; + void *protocol_interface; + efi_status_t r = EFI_SUCCESS; + size_t i = 0; + + if (!handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + efi_va_start(argptr, handle); + for (;;) { + protocol = efi_va_arg(argptr, efi_guid_t*); + if (!protocol) + break; + protocol_interface = efi_va_arg(argptr, void*); + r = efi_uninstall_protocol(handle, protocol, + protocol_interface); + if (r != EFI_SUCCESS) + break; + i++; + } + efi_va_end(argptr); + if (r == EFI_SUCCESS) { + /* If the last protocol has been removed, delete the handle. */ + if (list_empty(&handle->protocols)) { + list_del(&handle->link); + free(handle); + } + return EFI_EXIT(r); + } + + /* If an error occurred undo all changes. */ + efi_va_start(argptr, handle); + for (; i; --i) { + protocol = efi_va_arg(argptr, efi_guid_t*); + protocol_interface = efi_va_arg(argptr, void*); + EFI_CALL(efi_install_protocol_interface(&handle, protocol, + EFI_NATIVE_INTERFACE, + protocol_interface)); + } + efi_va_end(argptr); + + /* In case of an error always return EFI_INVALID_PARAMETER */ + return EFI_EXIT(EFI_INVALID_PARAMETER); +} + +/** + * efi_calculate_crc32() - calculate cyclic redundancy code + * @data: buffer with data + * @data_size: size of buffer in bytes + * @crc32_p: cyclic redundancy code + * + * This function implements the CalculateCrc32 service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_calculate_crc32(const void *data, + efi_uintn_t data_size, + u32 *crc32_p) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %zu", data, data_size); + if (!data || !data_size || !crc32_p) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + *crc32_p = crc32(0, data, data_size); +out: + return EFI_EXIT(ret); +} + +/** + * efi_copy_mem() - copy memory + * @destination: destination of the copy operation + * @source: source of the copy operation + * @length: number of bytes to copy + * + * This function implements the CopyMem service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static void EFIAPI efi_copy_mem(void *destination, const void *source, + size_t length) +{ + EFI_ENTRY("%p, %p, %ld", destination, source, (unsigned long)length); + memmove(destination, source, length); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_set_mem() - Fill memory with a byte value. + * @buffer: buffer to fill + * @size: size of buffer in bytes + * @value: byte to copy to the buffer + * + * This function implements the SetMem service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static void EFIAPI efi_set_mem(void *buffer, size_t size, uint8_t value) +{ + EFI_ENTRY("%p, %ld, 0x%x", buffer, (unsigned long)size, value); + memset(buffer, value, size); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_protocol_open() - open protocol interface on a handle + * @handler: handler of a protocol + * @protocol_interface: interface implementing the protocol + * @agent_handle: handle of the driver + * @controller_handle: handle of the controller + * @attributes: attributes indicating how to open the protocol + * + * Return: status code + */ +static efi_status_t efi_protocol_open( + struct efi_handler *handler, + void **protocol_interface, void *agent_handle, + void *controller_handle, uint32_t attributes) +{ + struct efi_open_protocol_info_item *item; + struct efi_open_protocol_info_entry *match = NULL; + bool opened_by_driver = false; + bool opened_exclusive = false; + + /* If there is no agent, only return the interface */ + if (!agent_handle) + goto out; + + /* For TEST_PROTOCOL ignore interface attribute */ + if (attributes != EFI_OPEN_PROTOCOL_TEST_PROTOCOL) + *protocol_interface = NULL; + + /* + * Check if the protocol is already opened by a driver with the same + * attributes or opened exclusively + */ + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == agent_handle) { + if ((attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) && + (item->info.attributes == attributes)) + return EFI_ALREADY_STARTED; + } else { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_DRIVER) + opened_by_driver = true; + } + if (item->info.attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) + opened_exclusive = true; + } + + /* Only one controller can open the protocol exclusively */ + if (attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) { + if (opened_exclusive) + return EFI_ACCESS_DENIED; + } else if (attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) { + if (opened_exclusive || opened_by_driver) + return EFI_ACCESS_DENIED; + } + + /* Prepare exclusive opening */ + if (attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) { + /* Try to disconnect controllers */ +disconnect_next: + opened_by_driver = false; + list_for_each_entry(item, &handler->open_infos, link) { + efi_status_t ret; + + if (item->info.attributes == + EFI_OPEN_PROTOCOL_BY_DRIVER) { + ret = EFI_CALL(efi_disconnect_controller( + item->info.controller_handle, + item->info.agent_handle, + NULL)); + if (ret == EFI_SUCCESS) + /* + * Child controllers may have been + * removed from the open_infos list. So + * let's restart the loop. + */ + goto disconnect_next; + else + opened_by_driver = true; + } + } + /* Only one driver can be connected */ + if (opened_by_driver) + return EFI_ACCESS_DENIED; + } + + /* Find existing entry */ + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == agent_handle && + item->info.controller_handle == controller_handle && + item->info.attributes == attributes) + match = &item->info; + } + /* None found, create one */ + if (!match) { + match = efi_create_open_info(handler); + if (!match) + return EFI_OUT_OF_RESOURCES; + } + + match->agent_handle = agent_handle; + match->controller_handle = controller_handle; + match->attributes = attributes; + match->open_count++; + +out: + /* For TEST_PROTOCOL ignore interface attribute. */ + if (attributes != EFI_OPEN_PROTOCOL_TEST_PROTOCOL) + *protocol_interface = handler->protocol_interface; + + return EFI_SUCCESS; +} + +/** + * efi_open_protocol() - open protocol interface on a handle + * @handle: handle on which the protocol shall be opened + * @protocol: GUID of the protocol + * @protocol_interface: interface implementing the protocol + * @agent_handle: handle of the driver + * @controller_handle: handle of the controller + * @attributes: attributes indicating how to open the protocol + * + * This function implements the OpenProtocol interface. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_open_protocol + (efi_handle_t handle, const efi_guid_t *protocol, + void **protocol_interface, efi_handle_t agent_handle, + efi_handle_t controller_handle, uint32_t attributes) +{ + struct efi_handler *handler; + efi_status_t r = EFI_INVALID_PARAMETER; + + EFI_ENTRY("%p, %pUl, %p, %p, %p, 0x%x", handle, protocol, + protocol_interface, agent_handle, controller_handle, + attributes); + + if (!handle || !protocol || + (!protocol_interface && attributes != + EFI_OPEN_PROTOCOL_TEST_PROTOCOL)) { + goto out; + } + + switch (attributes) { + case EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL: + case EFI_OPEN_PROTOCOL_GET_PROTOCOL: + case EFI_OPEN_PROTOCOL_TEST_PROTOCOL: + break; + case EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER: + if (controller_handle == handle) + goto out; + /* fall-through */ + case EFI_OPEN_PROTOCOL_BY_DRIVER: + case EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE: + /* Check that the controller handle is valid */ + if (!efi_search_obj(controller_handle)) + goto out; + /* fall-through */ + case EFI_OPEN_PROTOCOL_EXCLUSIVE: + /* Check that the agent handle is valid */ + if (!efi_search_obj(agent_handle)) + goto out; + break; + default: + goto out; + } + + r = efi_search_protocol(handle, protocol, &handler); + switch (r) { + case EFI_SUCCESS: + break; + case EFI_NOT_FOUND: + r = EFI_UNSUPPORTED; + goto out; + default: + goto out; + } + + r = efi_protocol_open(handler, protocol_interface, agent_handle, + controller_handle, attributes); +out: + return EFI_EXIT(r); +} + +/** + * efi_start_image() - call the entry point of an image + * @image_handle: handle of the image + * @exit_data_size: size of the buffer + * @exit_data: buffer to receive the exit data of the called image + * + * This function implements the StartImage service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle, + efi_uintn_t *exit_data_size, + u16 **exit_data) +{ + struct efi_loaded_image_obj *image_obj = + (struct efi_loaded_image_obj *)image_handle; + efi_status_t ret; + void *info; + efi_handle_t parent_image = current_image; + + EFI_ENTRY("%p, %p, %p", image_handle, exit_data_size, exit_data); + + /* Check parameters */ + if (image_obj->header.type != EFI_OBJECT_TYPE_LOADED_IMAGE) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image, + &info, NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + image_obj->exit_data_size = exit_data_size; + image_obj->exit_data = exit_data; + + /* call the image! */ + if (setjmp(&image_obj->exit_jmp)) { + /* + * We called the entry point of the child image with EFI_CALL + * in the lines below. The child image called the Exit() boot + * service efi_exit() which executed the long jump that brought + * us to the current line. This implies that the second half + * of the EFI_CALL macro has not been executed. + */ +#ifdef CONFIG_ARM + /* + * efi_exit() called efi_restore_gd(). We have to undo this + * otherwise __efi_entry_check() will put the wrong value into + * app_gd. + */ + gd = app_gd; +#endif + /* + * To get ready to call EFI_EXIT below we have to execute the + * missed out steps of EFI_CALL. + */ + assert(__efi_entry_check()); + EFI_PRINT("%lu returned by started image\n", + (unsigned long)((uintptr_t)image_obj->exit_status & + ~EFI_ERROR_MASK)); + current_image = parent_image; + return EFI_EXIT(image_obj->exit_status); + } + + current_image = image_handle; + image_obj->header.type = EFI_OBJECT_TYPE_STARTED_IMAGE; + EFI_PRINT("Jumping into 0x%p\n", image_obj->entry); + ret = EFI_CALL(image_obj->entry(image_handle, &systab)); + + /* + * Usually UEFI applications call Exit() instead of returning. + * But because the world doesn't consist of ponies and unicorns, + * we're happy to emulate that behavior on behalf of a payload + * that forgot. + */ + return EFI_CALL(systab.boottime->exit(image_handle, ret, 0, NULL)); +} + +/** + * efi_delete_image() - delete loaded image from memory) + * + * @image_obj: handle of the loaded image + * @loaded_image_protocol: loaded image protocol + */ +static efi_status_t efi_delete_image + (struct efi_loaded_image_obj *image_obj, + struct efi_loaded_image *loaded_image_protocol) +{ + struct efi_object *efiobj; + efi_status_t r, ret = EFI_SUCCESS; + +close_next: + list_for_each_entry(efiobj, &efi_obj_list, link) { + struct efi_handler *protocol; + + list_for_each_entry(protocol, &efiobj->protocols, link) { + struct efi_open_protocol_info_item *info; + + list_for_each_entry(info, &protocol->open_infos, link) { + if (info->info.agent_handle != + (efi_handle_t)image_obj) + continue; + r = EFI_CALL(efi_close_protocol + (efiobj, protocol->guid, + info->info.agent_handle, + info->info.controller_handle + )); + if (r != EFI_SUCCESS) + ret = r; + /* + * Closing protocols may results in further + * items being deleted. To play it safe loop + * over all elements again. + */ + goto close_next; + } + } + } + + efi_free_pages((uintptr_t)loaded_image_protocol->image_base, + efi_size_in_pages(loaded_image_protocol->image_size)); + efi_delete_handle(&image_obj->header); + + return ret; +} + +/** + * efi_unload_image() - unload an EFI image + * @image_handle: handle of the image to be unloaded + * + * This function implements the UnloadImage service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_unload_image(efi_handle_t image_handle) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_object *efiobj; + struct efi_loaded_image *loaded_image_protocol; + + EFI_ENTRY("%p", image_handle); + + efiobj = efi_search_obj(image_handle); + if (!efiobj) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* Find the loaded image protocol */ + ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image, + (void **)&loaded_image_protocol, + NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + switch (efiobj->type) { + case EFI_OBJECT_TYPE_STARTED_IMAGE: + /* Call the unload function */ + if (!loaded_image_protocol->unload) { + ret = EFI_UNSUPPORTED; + goto out; + } + ret = EFI_CALL(loaded_image_protocol->unload(image_handle)); + if (ret != EFI_SUCCESS) + goto out; + break; + case EFI_OBJECT_TYPE_LOADED_IMAGE: + break; + default: + ret = EFI_INVALID_PARAMETER; + goto out; + } + efi_delete_image((struct efi_loaded_image_obj *)efiobj, + loaded_image_protocol); +out: + return EFI_EXIT(ret); +} + +/** + * efi_update_exit_data() - fill exit data parameters of StartImage() + * + * @image_obj: image handle + * @exit_data_size: size of the exit data buffer + * @exit_data: buffer with data returned by UEFI payload + * Return: status code + */ +static efi_status_t efi_update_exit_data(struct efi_loaded_image_obj *image_obj, + efi_uintn_t exit_data_size, + u16 *exit_data) +{ + efi_status_t ret; + + /* + * If exit_data is not provided to StartImage(), exit_data_size must be + * ignored. + */ + if (!image_obj->exit_data) + return EFI_SUCCESS; + if (image_obj->exit_data_size) + *image_obj->exit_data_size = exit_data_size; + if (exit_data_size && exit_data) { + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, + exit_data_size, + (void **)image_obj->exit_data); + if (ret != EFI_SUCCESS) + return ret; + memcpy(*image_obj->exit_data, exit_data, exit_data_size); + } else { + image_obj->exit_data = NULL; + } + return EFI_SUCCESS; +} + +/** + * efi_exit() - leave an EFI application or driver + * @image_handle: handle of the application or driver that is exiting + * @exit_status: status code + * @exit_data_size: size of the buffer in bytes + * @exit_data: buffer with data describing an error + * + * This function implements the Exit service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_exit(efi_handle_t image_handle, + efi_status_t exit_status, + efi_uintn_t exit_data_size, + u16 *exit_data) +{ + /* + * TODO: We should call the unload procedure of the loaded + * image protocol. + */ + efi_status_t ret; + struct efi_loaded_image *loaded_image_protocol; + struct efi_loaded_image_obj *image_obj = + (struct efi_loaded_image_obj *)image_handle; + + EFI_ENTRY("%p, %ld, %zu, %p", image_handle, exit_status, + exit_data_size, exit_data); + + /* Check parameters */ + ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image, + (void **)&loaded_image_protocol, + NULL, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (ret != EFI_SUCCESS) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Unloading of unstarted images */ + switch (image_obj->header.type) { + case EFI_OBJECT_TYPE_STARTED_IMAGE: + break; + case EFI_OBJECT_TYPE_LOADED_IMAGE: + efi_delete_image(image_obj, loaded_image_protocol); + ret = EFI_SUCCESS; + goto out; + default: + /* Handle does not refer to loaded image */ + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* A started image can only be unloaded it is the last one started. */ + if (image_handle != current_image) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Exit data is only foreseen in case of failure. */ + if (exit_status != EFI_SUCCESS) { + ret = efi_update_exit_data(image_obj, exit_data_size, + exit_data); + /* Exiting has priority. Don't return error to caller. */ + if (ret != EFI_SUCCESS) + EFI_PRINT("%s: out of memory\n", __func__); + } + if (image_obj->image_type == IMAGE_SUBSYSTEM_EFI_APPLICATION || + exit_status != EFI_SUCCESS) + efi_delete_image(image_obj, loaded_image_protocol); + + /* Make sure entry/exit counts for EFI world cross-overs match */ + EFI_EXIT(exit_status); + + /* + * But longjmp out with the U-Boot gd, not the application's, as + * the other end is a setjmp call inside EFI context. + */ + efi_restore_gd(); + + image_obj->exit_status = exit_status; + longjmp(&image_obj->exit_jmp, 1); + + panic("EFI application exited"); +out: + return EFI_EXIT(ret); +} + +/** + * efi_handle_protocol() - get interface of a protocol on a handle + * @handle: handle on which the protocol shall be opened + * @protocol: GUID of the protocol + * @protocol_interface: interface implementing the protocol + * + * This function implements the HandleProtocol service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_handle_protocol(efi_handle_t handle, + const efi_guid_t *protocol, + void **protocol_interface) +{ + return efi_open_protocol(handle, protocol, protocol_interface, efi_root, + NULL, EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); +} + +/** + * efi_bind_controller() - bind a single driver to a controller + * @controller_handle: controller handle + * @driver_image_handle: driver handle + * @remain_device_path: remaining path + * + * Return: status code + */ +static efi_status_t efi_bind_controller( + efi_handle_t controller_handle, + efi_handle_t driver_image_handle, + struct efi_device_path *remain_device_path) +{ + struct efi_driver_binding_protocol *binding_protocol; + efi_status_t r; + + r = EFI_CALL(efi_open_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + (void **)&binding_protocol, + driver_image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (r != EFI_SUCCESS) + return r; + r = EFI_CALL(binding_protocol->supported(binding_protocol, + controller_handle, + remain_device_path)); + if (r == EFI_SUCCESS) + r = EFI_CALL(binding_protocol->start(binding_protocol, + controller_handle, + remain_device_path)); + EFI_CALL(efi_close_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + driver_image_handle, NULL)); + return r; +} + +/** + * efi_connect_single_controller() - connect a single driver to a controller + * @controller_handle: controller + * @driver_image_handle: driver + * @remain_device_path: remaining path + * + * Return: status code + */ +static efi_status_t efi_connect_single_controller( + efi_handle_t controller_handle, + efi_handle_t *driver_image_handle, + struct efi_device_path *remain_device_path) +{ + efi_handle_t *buffer; + size_t count; + size_t i; + efi_status_t r; + size_t connected = 0; + + /* Get buffer with all handles with driver binding protocol */ + r = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, + &efi_guid_driver_binding_protocol, + NULL, &count, &buffer)); + if (r != EFI_SUCCESS) + return r; + + /* Context Override */ + if (driver_image_handle) { + for (; *driver_image_handle; ++driver_image_handle) { + for (i = 0; i < count; ++i) { + if (buffer[i] == *driver_image_handle) { + buffer[i] = NULL; + r = efi_bind_controller( + controller_handle, + *driver_image_handle, + remain_device_path); + /* + * For drivers that do not support the + * controller or are already connected + * we receive an error code here. + */ + if (r == EFI_SUCCESS) + ++connected; + } + } + } + } + + /* + * TODO: Some overrides are not yet implemented: + * - Platform Driver Override + * - Driver Family Override Search + * - Bus Specific Driver Override + */ + + /* Driver Binding Search */ + for (i = 0; i < count; ++i) { + if (buffer[i]) { + r = efi_bind_controller(controller_handle, + buffer[i], + remain_device_path); + if (r == EFI_SUCCESS) + ++connected; + } + } + + efi_free_pool(buffer); + if (!connected) + return EFI_NOT_FOUND; + return EFI_SUCCESS; +} + +/** + * efi_connect_controller() - connect a controller to a driver + * @controller_handle: handle of the controller + * @driver_image_handle: handle of the driver + * @remain_device_path: device path of a child controller + * @recursive: true to connect all child controllers + * + * This function implements the ConnectController service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * First all driver binding protocol handles are tried for binding drivers. + * Afterwards all handles that have opened a protocol of the controller + * with EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER are connected to drivers. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_connect_controller( + efi_handle_t controller_handle, + efi_handle_t *driver_image_handle, + struct efi_device_path *remain_device_path, + bool recursive) +{ + efi_status_t r; + efi_status_t ret = EFI_NOT_FOUND; + struct efi_object *efiobj; + + EFI_ENTRY("%p, %p, %pD, %d", controller_handle, driver_image_handle, + remain_device_path, recursive); + + efiobj = efi_search_obj(controller_handle); + if (!efiobj) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + r = efi_connect_single_controller(controller_handle, + driver_image_handle, + remain_device_path); + if (r == EFI_SUCCESS) + ret = EFI_SUCCESS; + if (recursive) { + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + + list_for_each_entry(handler, &efiobj->protocols, link) { + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.attributes & + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) { + r = EFI_CALL(efi_connect_controller( + item->info.controller_handle, + driver_image_handle, + remain_device_path, + recursive)); + if (r == EFI_SUCCESS) + ret = EFI_SUCCESS; + } + } + } + } + /* Check for child controller specified by end node */ + if (ret != EFI_SUCCESS && remain_device_path && + remain_device_path->type == DEVICE_PATH_TYPE_END) + ret = EFI_SUCCESS; +out: + return EFI_EXIT(ret); +} + +/** + * efi_reinstall_protocol_interface() - reinstall protocol interface + * @handle: handle on which the protocol shall be reinstalled + * @protocol: GUID of the protocol to be installed + * @old_interface: interface to be removed + * @new_interface: interface to be installed + * + * This function implements the ReinstallProtocolInterface service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * The old interface is uninstalled. The new interface is installed. + * Drivers are connected. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_reinstall_protocol_interface( + efi_handle_t handle, const efi_guid_t *protocol, + void *old_interface, void *new_interface) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, old_interface, + new_interface); + + /* Uninstall protocol but do not delete handle */ + ret = efi_uninstall_protocol(handle, protocol, old_interface); + if (ret != EFI_SUCCESS) + goto out; + + /* Install the new protocol */ + ret = efi_add_protocol(handle, protocol, new_interface); + /* + * The UEFI spec does not specify what should happen to the handle + * if in case of an error no protocol interface remains on the handle. + * So let's do nothing here. + */ + if (ret != EFI_SUCCESS) + goto out; + /* + * The returned status code has to be ignored. + * Do not create an error if no suitable driver for the handle exists. + */ + EFI_CALL(efi_connect_controller(handle, NULL, NULL, true)); +out: + return EFI_EXIT(ret); +} + +/** + * efi_get_child_controllers() - get all child controllers associated to a driver + * @efiobj: handle of the controller + * @driver_handle: handle of the driver + * @number_of_children: number of child controllers + * @child_handle_buffer: handles of the the child controllers + * + * The allocated buffer has to be freed with free(). + * + * Return: status code + */ +static efi_status_t efi_get_child_controllers( + struct efi_object *efiobj, + efi_handle_t driver_handle, + efi_uintn_t *number_of_children, + efi_handle_t **child_handle_buffer) +{ + struct efi_handler *handler; + struct efi_open_protocol_info_item *item; + efi_uintn_t count = 0, i; + bool duplicate; + + /* Count all child controller associations */ + list_for_each_entry(handler, &efiobj->protocols, link) { + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == driver_handle && + item->info.attributes & + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) + ++count; + } + } + /* + * Create buffer. In case of duplicate child controller assignments + * the buffer will be too large. But that does not harm. + */ + *number_of_children = 0; + *child_handle_buffer = calloc(count, sizeof(efi_handle_t)); + if (!*child_handle_buffer) + return EFI_OUT_OF_RESOURCES; + /* Copy unique child handles */ + list_for_each_entry(handler, &efiobj->protocols, link) { + list_for_each_entry(item, &handler->open_infos, link) { + if (item->info.agent_handle == driver_handle && + item->info.attributes & + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) { + /* Check this is a new child controller */ + duplicate = false; + for (i = 0; i < *number_of_children; ++i) { + if ((*child_handle_buffer)[i] == + item->info.controller_handle) + duplicate = true; + } + /* Copy handle to buffer */ + if (!duplicate) { + i = (*number_of_children)++; + (*child_handle_buffer)[i] = + item->info.controller_handle; + } + } + } + } + return EFI_SUCCESS; +} + +/** + * efi_disconnect_controller() - disconnect a controller from a driver + * @controller_handle: handle of the controller + * @driver_image_handle: handle of the driver + * @child_handle: handle of the child to destroy + * + * This function implements the DisconnectController service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +static efi_status_t EFIAPI efi_disconnect_controller( + efi_handle_t controller_handle, + efi_handle_t driver_image_handle, + efi_handle_t child_handle) +{ + struct efi_driver_binding_protocol *binding_protocol; + efi_handle_t *child_handle_buffer = NULL; + size_t number_of_children = 0; + efi_status_t r; + struct efi_object *efiobj; + + EFI_ENTRY("%p, %p, %p", controller_handle, driver_image_handle, + child_handle); + + efiobj = efi_search_obj(controller_handle); + if (!efiobj) { + r = EFI_INVALID_PARAMETER; + goto out; + } + + if (child_handle && !efi_search_obj(child_handle)) { + r = EFI_INVALID_PARAMETER; + goto out; + } + + /* If no driver handle is supplied, disconnect all drivers */ + if (!driver_image_handle) { + r = efi_disconnect_all_drivers(efiobj, NULL, child_handle); + goto out; + } + + /* Create list of child handles */ + if (child_handle) { + number_of_children = 1; + child_handle_buffer = &child_handle; + } else { + efi_get_child_controllers(efiobj, + driver_image_handle, + &number_of_children, + &child_handle_buffer); + } + + /* Get the driver binding protocol */ + r = EFI_CALL(efi_open_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + (void **)&binding_protocol, + driver_image_handle, NULL, + EFI_OPEN_PROTOCOL_GET_PROTOCOL)); + if (r != EFI_SUCCESS) { + r = EFI_INVALID_PARAMETER; + goto out; + } + /* Remove the children */ + if (number_of_children) { + r = EFI_CALL(binding_protocol->stop(binding_protocol, + controller_handle, + number_of_children, + child_handle_buffer)); + if (r != EFI_SUCCESS) { + r = EFI_DEVICE_ERROR; + goto out; + } + } + /* Remove the driver */ + if (!child_handle) { + r = EFI_CALL(binding_protocol->stop(binding_protocol, + controller_handle, + 0, NULL)); + if (r != EFI_SUCCESS) { + r = EFI_DEVICE_ERROR; + goto out; + } + } + EFI_CALL(efi_close_protocol(driver_image_handle, + &efi_guid_driver_binding_protocol, + driver_image_handle, NULL)); + r = EFI_SUCCESS; +out: + if (!child_handle) + free(child_handle_buffer); + return EFI_EXIT(r); +} + +static struct efi_boot_services efi_boot_services = { + .hdr = { + .signature = EFI_BOOT_SERVICES_SIGNATURE, + .revision = EFI_SPECIFICATION_VERSION, + .headersize = sizeof(struct efi_boot_services), + }, + .raise_tpl = efi_raise_tpl, + .restore_tpl = efi_restore_tpl, + .allocate_pages = efi_allocate_pages_ext, + .free_pages = efi_free_pages_ext, + .get_memory_map = efi_get_memory_map_ext, + .allocate_pool = efi_allocate_pool_ext, + .free_pool = efi_free_pool_ext, + .create_event = efi_create_event_ext, + .set_timer = efi_set_timer_ext, + .wait_for_event = efi_wait_for_event, + .signal_event = efi_signal_event_ext, + .close_event = efi_close_event, + .check_event = efi_check_event, + .install_protocol_interface = efi_install_protocol_interface, + .reinstall_protocol_interface = efi_reinstall_protocol_interface, + .uninstall_protocol_interface = efi_uninstall_protocol_interface, + .handle_protocol = efi_handle_protocol, + .reserved = NULL, + .register_protocol_notify = efi_register_protocol_notify, + .locate_handle = efi_locate_handle_ext, + .locate_device_path = efi_locate_device_path, + .install_configuration_table = efi_install_configuration_table_ext, + .load_image = efi_load_image, + .start_image = efi_start_image, + .exit = efi_exit, + .unload_image = efi_unload_image, + .exit_boot_services = efi_exit_boot_services, + .get_next_monotonic_count = efi_get_next_monotonic_count, + .stall = efi_stall, + .set_watchdog_timer = efi_set_watchdog_timer, + .connect_controller = efi_connect_controller, + .disconnect_controller = efi_disconnect_controller, + .open_protocol = efi_open_protocol, + .close_protocol = efi_close_protocol, + .open_protocol_information = efi_open_protocol_information, + .protocols_per_handle = efi_protocols_per_handle, + .locate_handle_buffer = efi_locate_handle_buffer, + .locate_protocol = efi_locate_protocol, + .install_multiple_protocol_interfaces = + efi_install_multiple_protocol_interfaces, + .uninstall_multiple_protocol_interfaces = + efi_uninstall_multiple_protocol_interfaces, + .calculate_crc32 = efi_calculate_crc32, + .copy_mem = efi_copy_mem, + .set_mem = efi_set_mem, + .create_event_ex = efi_create_event_ex, +}; + +static u16 __efi_runtime_data firmware_vendor[] = L"Das U-Boot"; + +struct efi_system_table __efi_runtime_data systab = { + .hdr = { + .signature = EFI_SYSTEM_TABLE_SIGNATURE, + .revision = EFI_SPECIFICATION_VERSION, + .headersize = sizeof(struct efi_system_table), + }, + .fw_vendor = firmware_vendor, + .fw_revision = FW_VERSION << 16 | FW_PATCHLEVEL << 8, + .runtime = &efi_runtime_services, + .nr_tables = 0, + .tables = NULL, +}; + +/** + * efi_initialize_system_table() - Initialize system table + * + * Return: status code + */ +efi_status_t efi_initialize_system_table(void) +{ + efi_status_t ret; + + /* Allocate configuration table array */ + ret = efi_allocate_pool(EFI_RUNTIME_SERVICES_DATA, + EFI_MAX_CONFIGURATION_TABLES * + sizeof(struct efi_configuration_table), + (void **)&systab.tables); + + /* + * These entries will be set to NULL in ExitBootServices(). To avoid + * relocation in SetVirtualAddressMap(), set them dynamically. + */ + systab.con_in = &efi_con_in; + systab.con_out = &efi_con_out; + systab.std_err = &efi_con_out; + systab.boottime = &efi_boot_services; + + /* Set CRC32 field in table headers */ + efi_update_table_header_crc32(&systab.hdr); + efi_update_table_header_crc32(&efi_runtime_services.hdr); + efi_update_table_header_crc32(&efi_boot_services.hdr); + + return ret; +} diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c new file mode 100644 index 00000000..218f7caa --- /dev/null +++ b/lib/efi_loader/efi_console.c @@ -0,0 +1,1158 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application console interface + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <charset.h> +#include <time.h> +#include <dm/device.h> +#include <efi_loader.h> +#include <env.h> +#include <stdio_dev.h> +#include <video_console.h> + +#define EFI_COUT_MODE_2 2 +#define EFI_MAX_COUT_MODE 3 + +struct cout_mode { + unsigned long columns; + unsigned long rows; + int present; +}; + +static struct cout_mode efi_cout_modes[] = { + /* EFI Mode 0 is 80x25 and always present */ + { + .columns = 80, + .rows = 25, + .present = 1, + }, + /* EFI Mode 1 is always 80x50 */ + { + .columns = 80, + .rows = 50, + .present = 0, + }, + /* Value are unknown until we query the console */ + { + .columns = 0, + .rows = 0, + .present = 0, + }, +}; + +const efi_guid_t efi_guid_text_input_ex_protocol = + EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL_GUID; +const efi_guid_t efi_guid_text_input_protocol = + EFI_SIMPLE_TEXT_INPUT_PROTOCOL_GUID; +const efi_guid_t efi_guid_text_output_protocol = + EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_GUID; + +#define cESC '\x1b' +#define ESC "\x1b" + +/* Default to mode 0 */ +static struct simple_text_output_mode efi_con_mode = { + .max_mode = 1, + .mode = 0, + .attribute = 0, + .cursor_column = 0, + .cursor_row = 0, + .cursor_visible = 1, +}; + +static int term_get_char(s32 *c) +{ + u64 timeout; + + /* Wait up to 100 ms for a character */ + timeout = timer_get_us() + 100000; + + while (!tstc()) + if (timer_get_us() > timeout) + return 1; + + *c = getc(); + return 0; +} + +/* + * Receive and parse a reply from the terminal. + * + * @n: array of return values + * @num: number of return values expected + * @end_char: character indicating end of terminal message + * @return: non-zero indicates error + */ +static int term_read_reply(int *n, int num, char end_char) +{ + s32 c; + int i = 0; + + if (term_get_char(&c) || c != cESC) + return -1; + + if (term_get_char(&c) || c != '[') + return -1; + + n[0] = 0; + while (1) { + if (!term_get_char(&c)) { + if (c == ';') { + i++; + if (i >= num) + return -1; + n[i] = 0; + continue; + } else if (c == end_char) { + break; + } else if (c > '9' || c < '0') { + return -1; + } + + /* Read one more decimal position */ + n[i] *= 10; + n[i] += c - '0'; + } else { + return -1; + } + } + if (i != num - 1) + return -1; + + return 0; +} + +static efi_status_t EFIAPI efi_cout_output_string( + struct efi_simple_text_output_protocol *this, + const efi_string_t string) +{ + struct simple_text_output_mode *con = &efi_con_mode; + struct cout_mode *mode = &efi_cout_modes[con->mode]; + char *buf, *pos; + u16 *p; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p", this, string); + + if (!this || !string) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + buf = malloc(utf16_utf8_strlen(string) + 1); + if (!buf) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + pos = buf; + utf16_utf8_strcpy(&pos, string); + fputs(stdout, buf); + free(buf); + + /* + * Update the cursor position. + * + * The UEFI spec provides advance rules for U+0000, U+0008, U+000A, + * and U000D. All other control characters are ignored. Any non-control + * character increase the column by one. + */ + for (p = string; *p; ++p) { + switch (*p) { + case '\b': /* U+0008, backspace */ + if (con->cursor_column) + con->cursor_column--; + break; + case '\n': /* U+000A, newline */ + con->cursor_column = 0; + con->cursor_row++; + break; + case '\r': /* U+000D, carriage-return */ + con->cursor_column = 0; + break; + case 0xd800 ... 0xdbff: + /* + * Ignore high surrogates, we do not want to count a + * Unicode character twice. + */ + break; + default: + /* Exclude control codes */ + if (*p > 0x1f) + con->cursor_column++; + break; + } + if (con->cursor_column >= mode->columns) { + con->cursor_column = 0; + con->cursor_row++; + } + /* + * When we exceed the row count the terminal will scroll up one + * line. We have to adjust the cursor position. + */ + if (con->cursor_row >= mode->rows && con->cursor_row) + con->cursor_row--; + } + +out: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_cout_test_string( + struct efi_simple_text_output_protocol *this, + const efi_string_t string) +{ + EFI_ENTRY("%p, %p", this, string); + return EFI_EXIT(EFI_SUCCESS); +} + +static bool cout_mode_matches(struct cout_mode *mode, int rows, int cols) +{ + if (!mode->present) + return false; + + return (mode->rows == rows) && (mode->columns == cols); +} + +/** + * query_console_serial() - query console size + * + * @rows: pointer to return number of rows + * @cols: pointer to return number of columns + * Returns: 0 on success + */ +static int query_console_serial(int *rows, int *cols) +{ + int ret = 0; + int n[2]; + + /* Empty input buffer */ + while (tstc()) + getc(); + + /* + * Not all terminals understand CSI [18t for querying the console size. + * We should adhere to escape sequences documented in the console_codes + * man page and the ECMA-48 standard. + * + * So here we follow a different approach. We position the cursor to the + * bottom right and query its position. Before leaving the function we + * restore the original cursor position. + */ + printf(ESC "7" /* Save cursor position */ + ESC "[r" /* Set scrolling region to full window */ + ESC "[999;999H" /* Move to bottom right corner */ + ESC "[6n"); /* Query cursor position */ + + /* Read {rows,cols} */ + if (term_read_reply(n, 2, 'R')) { + ret = 1; + goto out; + } + + *cols = n[1]; + *rows = n[0]; +out: + printf(ESC "8"); /* Restore cursor position */ + return ret; +} + +/* + * Update the mode table. + * + * By default the only mode available is 80x25. If the console has at least 50 + * lines, enable mode 80x50. If we can query the console size and it is neither + * 80x25 nor 80x50, set it as an additional mode. + */ +static void query_console_size(void) +{ + const char *stdout_name = env_get("stdout"); + int rows = 25, cols = 80; + + if (stdout_name && !strcmp(stdout_name, "vidconsole") && + IS_ENABLED(CONFIG_DM_VIDEO)) { + struct stdio_dev *stdout_dev = + stdio_get_by_name("vidconsole"); + struct udevice *dev = stdout_dev->priv; + struct vidconsole_priv *priv = + dev_get_uclass_priv(dev); + rows = priv->rows; + cols = priv->cols; + } else if (query_console_serial(&rows, &cols)) { + return; + } + + /* Test if we can have Mode 1 */ + if (cols >= 80 && rows >= 50) { + efi_cout_modes[1].present = 1; + efi_con_mode.max_mode = 2; + } + + /* + * Install our mode as mode 2 if it is different + * than mode 0 or 1 and set it as the currently selected mode + */ + if (!cout_mode_matches(&efi_cout_modes[0], rows, cols) && + !cout_mode_matches(&efi_cout_modes[1], rows, cols)) { + efi_cout_modes[EFI_COUT_MODE_2].columns = cols; + efi_cout_modes[EFI_COUT_MODE_2].rows = rows; + efi_cout_modes[EFI_COUT_MODE_2].present = 1; + efi_con_mode.max_mode = EFI_MAX_COUT_MODE; + efi_con_mode.mode = EFI_COUT_MODE_2; + } +} + +static efi_status_t EFIAPI efi_cout_query_mode( + struct efi_simple_text_output_protocol *this, + unsigned long mode_number, unsigned long *columns, + unsigned long *rows) +{ + EFI_ENTRY("%p, %ld, %p, %p", this, mode_number, columns, rows); + + if (mode_number >= efi_con_mode.max_mode) + return EFI_EXIT(EFI_UNSUPPORTED); + + if (efi_cout_modes[mode_number].present != 1) + return EFI_EXIT(EFI_UNSUPPORTED); + + if (columns) + *columns = efi_cout_modes[mode_number].columns; + if (rows) + *rows = efi_cout_modes[mode_number].rows; + + return EFI_EXIT(EFI_SUCCESS); +} + +static const struct { + unsigned int fg; + unsigned int bg; +} color[] = { + { 30, 40 }, /* 0: black */ + { 34, 44 }, /* 1: blue */ + { 32, 42 }, /* 2: green */ + { 36, 46 }, /* 3: cyan */ + { 31, 41 }, /* 4: red */ + { 35, 45 }, /* 5: magenta */ + { 33, 43 }, /* 6: brown, map to yellow as EDK2 does*/ + { 37, 47 }, /* 7: light gray, map to white */ +}; + +/* See EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.SetAttribute(). */ +static efi_status_t EFIAPI efi_cout_set_attribute( + struct efi_simple_text_output_protocol *this, + unsigned long attribute) +{ + unsigned int bold = EFI_ATTR_BOLD(attribute); + unsigned int fg = EFI_ATTR_FG(attribute); + unsigned int bg = EFI_ATTR_BG(attribute); + + EFI_ENTRY("%p, %lx", this, attribute); + + efi_con_mode.attribute = attribute; + if (attribute) + printf(ESC"[%u;%u;%um", bold, color[fg].fg, color[bg].bg); + else + printf(ESC"[0;37;40m"); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_cout_clear_screen( + struct efi_simple_text_output_protocol *this) +{ + EFI_ENTRY("%p", this); + + printf(ESC"[2J"); + efi_con_mode.cursor_column = 0; + efi_con_mode.cursor_row = 0; + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_cout_set_mode( + struct efi_simple_text_output_protocol *this, + unsigned long mode_number) +{ + EFI_ENTRY("%p, %ld", this, mode_number); + + if (mode_number >= efi_con_mode.max_mode) + return EFI_EXIT(EFI_UNSUPPORTED); + + if (!efi_cout_modes[mode_number].present) + return EFI_EXIT(EFI_UNSUPPORTED); + + efi_con_mode.mode = mode_number; + EFI_CALL(efi_cout_clear_screen(this)); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_cout_reset( + struct efi_simple_text_output_protocol *this, + char extended_verification) +{ + EFI_ENTRY("%p, %d", this, extended_verification); + + /* Clear screen */ + EFI_CALL(efi_cout_clear_screen(this)); + /* Set default colors */ + efi_con_mode.attribute = 0x07; + printf(ESC "[0;37;40m"); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_cout_set_cursor_position( + struct efi_simple_text_output_protocol *this, + unsigned long column, unsigned long row) +{ + efi_status_t ret = EFI_SUCCESS; + struct simple_text_output_mode *con = &efi_con_mode; + struct cout_mode *mode = &efi_cout_modes[con->mode]; + + EFI_ENTRY("%p, %ld, %ld", this, column, row); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (row >= mode->rows || column >= mode->columns) { + ret = EFI_UNSUPPORTED; + goto out; + } + + /* + * Set cursor position by sending CSI H. + * EFI origin is [0, 0], terminal origin is [1, 1]. + */ + printf(ESC "[%d;%dH", (int)row + 1, (int)column + 1); + efi_con_mode.cursor_column = column; + efi_con_mode.cursor_row = row; +out: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_cout_enable_cursor( + struct efi_simple_text_output_protocol *this, + bool enable) +{ + EFI_ENTRY("%p, %d", this, enable); + + printf(ESC"[?25%c", enable ? 'h' : 'l'); + efi_con_mode.cursor_visible = !!enable; + + return EFI_EXIT(EFI_SUCCESS); +} + +struct efi_simple_text_output_protocol efi_con_out = { + .reset = efi_cout_reset, + .output_string = efi_cout_output_string, + .test_string = efi_cout_test_string, + .query_mode = efi_cout_query_mode, + .set_mode = efi_cout_set_mode, + .set_attribute = efi_cout_set_attribute, + .clear_screen = efi_cout_clear_screen, + .set_cursor_position = efi_cout_set_cursor_position, + .enable_cursor = efi_cout_enable_cursor, + .mode = (void*)&efi_con_mode, +}; + +/** + * struct efi_cin_notify_function - registered console input notify function + * + * @link: link to list + * @key: key to notify + * @function: function to call + */ +struct efi_cin_notify_function { + struct list_head link; + struct efi_key_data key; + efi_status_t (EFIAPI *function) + (struct efi_key_data *key_data); +}; + +static bool key_available; +static struct efi_key_data next_key; +static LIST_HEAD(cin_notify_functions); + +/** + * set_shift_mask() - set shift mask + * + * @mod: Xterm shift mask + * @key_state: receives the state of the shift, alt, control, and logo keys + */ +void set_shift_mask(int mod, struct efi_key_state *key_state) +{ + key_state->key_shift_state = EFI_SHIFT_STATE_VALID; + if (mod) { + --mod; + if (mod & 1) + key_state->key_shift_state |= EFI_LEFT_SHIFT_PRESSED; + if (mod & 2) + key_state->key_shift_state |= EFI_LEFT_ALT_PRESSED; + if (mod & 4) + key_state->key_shift_state |= EFI_LEFT_CONTROL_PRESSED; + if (!mod || (mod & 8)) + key_state->key_shift_state |= EFI_LEFT_LOGO_PRESSED; + } +} + +/** + * analyze_modifiers() - analyze modifiers (shift, alt, ctrl) for function keys + * + * This gets called when we have already parsed CSI. + * + * @key_state: receives the state of the shift, alt, control, and logo keys + * @return: the unmodified code + */ +static int analyze_modifiers(struct efi_key_state *key_state) +{ + int c, mod = 0, ret = 0; + + c = getc(); + + if (c != ';') { + ret = c; + if (c == '~') + goto out; + c = getc(); + } + for (;;) { + switch (c) { + case '0'...'9': + mod *= 10; + mod += c - '0'; + /* fall through */ + case ';': + c = getc(); + break; + default: + goto out; + } + } +out: + set_shift_mask(mod, key_state); + if (!ret) + ret = c; + return ret; +} + +/** + * efi_cin_read_key() - read a key from the console input + * + * @key: - key received + * Return: - status code + */ +static efi_status_t efi_cin_read_key(struct efi_key_data *key) +{ + struct efi_input_key pressed_key = { + .scan_code = 0, + .unicode_char = 0, + }; + s32 ch; + + if (console_read_unicode(&ch)) + return EFI_NOT_READY; + + key->key_state.key_shift_state = EFI_SHIFT_STATE_INVALID; + key->key_state.key_toggle_state = EFI_TOGGLE_STATE_INVALID; + + /* We do not support multi-word codes */ + if (ch >= 0x10000) + ch = '?'; + + switch (ch) { + case 0x1b: + /* + * Xterm Control Sequences + * https://www.xfree86.org/4.8.0/ctlseqs.html + */ + ch = getc(); + switch (ch) { + case cESC: /* ESC */ + pressed_key.scan_code = 23; + break; + case 'O': /* F1 - F4, End */ + ch = getc(); + /* consider modifiers */ + if (ch == 'F') { /* End */ + pressed_key.scan_code = 6; + break; + } else if (ch < 'P') { + set_shift_mask(ch - '0', &key->key_state); + ch = getc(); + } + pressed_key.scan_code = ch - 'P' + 11; + break; + case '[': + ch = getc(); + switch (ch) { + case 'A'...'D': /* up, down right, left */ + pressed_key.scan_code = ch - 'A' + 1; + break; + case 'F': /* End */ + pressed_key.scan_code = 6; + break; + case 'H': /* Home */ + pressed_key.scan_code = 5; + break; + case '1': + ch = analyze_modifiers(&key->key_state); + switch (ch) { + case '1'...'5': /* F1 - F5 */ + pressed_key.scan_code = ch - '1' + 11; + break; + case '6'...'9': /* F5 - F8 */ + pressed_key.scan_code = ch - '6' + 15; + break; + case 'A'...'D': /* up, down right, left */ + pressed_key.scan_code = ch - 'A' + 1; + break; + case 'F': /* End */ + pressed_key.scan_code = 6; + break; + case 'H': /* Home */ + pressed_key.scan_code = 5; + break; + case '~': /* Home */ + pressed_key.scan_code = 5; + break; + } + break; + case '2': + ch = analyze_modifiers(&key->key_state); + switch (ch) { + case '0'...'1': /* F9 - F10 */ + pressed_key.scan_code = ch - '0' + 19; + break; + case '3'...'4': /* F11 - F12 */ + pressed_key.scan_code = ch - '3' + 21; + break; + case '~': /* INS */ + pressed_key.scan_code = 7; + break; + } + break; + case '3': /* DEL */ + pressed_key.scan_code = 8; + analyze_modifiers(&key->key_state); + break; + case '5': /* PG UP */ + pressed_key.scan_code = 9; + analyze_modifiers(&key->key_state); + break; + case '6': /* PG DOWN */ + pressed_key.scan_code = 10; + analyze_modifiers(&key->key_state); + break; + } /* [ */ + break; + default: + /* ALT key */ + set_shift_mask(3, &key->key_state); + } + break; + case 0x7f: + /* Backspace */ + ch = 0x08; + } + if (pressed_key.scan_code) { + key->key_state.key_shift_state |= EFI_SHIFT_STATE_VALID; + } else { + pressed_key.unicode_char = ch; + + /* + * Assume left control key for control characters typically + * entered using the control key. + */ + if (ch >= 0x01 && ch <= 0x1f) { + key->key_state.key_shift_state |= + EFI_SHIFT_STATE_VALID; + switch (ch) { + case 0x01 ... 0x07: + case 0x0b ... 0x0c: + case 0x0e ... 0x1f: + key->key_state.key_shift_state |= + EFI_LEFT_CONTROL_PRESSED; + } + } + } + key->key = pressed_key; + + return EFI_SUCCESS; +} + +/** + * efi_cin_notify() - notify registered functions + */ +static void efi_cin_notify(void) +{ + struct efi_cin_notify_function *item; + + list_for_each_entry(item, &cin_notify_functions, link) { + bool match = true; + + /* We do not support toggle states */ + if (item->key.key.unicode_char || item->key.key.scan_code) { + if (item->key.key.unicode_char != + next_key.key.unicode_char || + item->key.key.scan_code != next_key.key.scan_code) + match = false; + } + if (item->key.key_state.key_shift_state && + item->key.key_state.key_shift_state != + next_key.key_state.key_shift_state) + match = false; + + if (match) + /* We don't bother about the return code */ + EFI_CALL(item->function(&next_key)); + } +} + +/** + * efi_cin_check() - check if keyboard input is available + */ +static void efi_cin_check(void) +{ + efi_status_t ret; + + if (key_available) { + efi_signal_event(efi_con_in.wait_for_key); + return; + } + + if (tstc()) { + ret = efi_cin_read_key(&next_key); + if (ret == EFI_SUCCESS) { + key_available = true; + + /* Notify registered functions */ + efi_cin_notify(); + + /* Queue the wait for key event */ + if (key_available) + efi_signal_event(efi_con_in.wait_for_key); + } + } +} + +/** + * efi_cin_empty_buffer() - empty input buffer + */ +static void efi_cin_empty_buffer(void) +{ + while (tstc()) + getc(); + key_available = false; +} + +/** + * efi_cin_reset_ex() - reset console input + * + * @this: - the extended simple text input protocol + * @extended_verification: - extended verification + * + * This function implements the reset service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: old value of the task priority level + */ +static efi_status_t EFIAPI efi_cin_reset_ex( + struct efi_simple_text_input_ex_protocol *this, + bool extended_verification) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %d", this, extended_verification); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + efi_cin_empty_buffer(); +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_read_key_stroke_ex() - read key stroke + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_data: key read from console + * Return: status code + * + * This function implements the ReadKeyStrokeEx service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_read_key_stroke_ex( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p", this, key_data); + + /* Check parameters */ + if (!this || !key_data) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* We don't do interrupts, so check for timers cooperatively */ + efi_timer_check(); + + /* Enable console input after ExitBootServices */ + efi_cin_check(); + + if (!key_available) { + ret = EFI_NOT_READY; + goto out; + } + /* + * CTRL+A - CTRL+Z have to be signaled as a - z. + * SHIFT+CTRL+A - SHIFT+CTRL+Z have to be signaled as A - Z. + */ + switch (next_key.key.unicode_char) { + case 0x01 ... 0x07: + case 0x0b ... 0x0c: + case 0x0e ... 0x1a: + if (!(next_key.key_state.key_toggle_state & + EFI_CAPS_LOCK_ACTIVE) ^ + !(next_key.key_state.key_shift_state & + (EFI_LEFT_SHIFT_PRESSED | EFI_RIGHT_SHIFT_PRESSED))) + next_key.key.unicode_char += 0x40; + else + next_key.key.unicode_char += 0x60; + } + *key_data = next_key; + key_available = false; + efi_con_in.wait_for_key->is_signaled = false; + +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_set_state() - set toggle key state + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_toggle_state: pointer to key toggle state + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_set_state( + struct efi_simple_text_input_ex_protocol *this, + u8 *key_toggle_state) +{ + EFI_ENTRY("%p, %p", this, key_toggle_state); + /* + * U-Boot supports multiple console input sources like serial and + * net console for which a key toggle state cannot be set at all. + * + * According to the UEFI specification it is allowable to not implement + * this service. + */ + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_cin_register_key_notify() - register key notification function + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key_data: key to be notified + * @key_notify_function: function to be called if the key is pressed + * @notify_handle: handle for unregistering the notification + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_register_key_notify( + struct efi_simple_text_input_ex_protocol *this, + struct efi_key_data *key_data, + efi_status_t (EFIAPI *key_notify_function)( + struct efi_key_data *key_data), + void **notify_handle) +{ + efi_status_t ret = EFI_SUCCESS; + struct efi_cin_notify_function *notify_function; + + EFI_ENTRY("%p, %p, %p, %p", + this, key_data, key_notify_function, notify_handle); + + /* Check parameters */ + if (!this || !key_data || !key_notify_function || !notify_handle) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + EFI_PRINT("u+%04x, sc %04x, sh %08x, tg %02x\n", + key_data->key.unicode_char, + key_data->key.scan_code, + key_data->key_state.key_shift_state, + key_data->key_state.key_toggle_state); + + notify_function = calloc(1, sizeof(struct efi_cin_notify_function)); + if (!notify_function) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + notify_function->key = *key_data; + notify_function->function = key_notify_function; + list_add_tail(¬ify_function->link, &cin_notify_functions); + *notify_handle = notify_function; +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_unregister_key_notify() - unregister key notification function + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @notification_handle: handle received when registering + * Return: status code + * + * This function implements the SetState service of the + * EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_unregister_key_notify( + struct efi_simple_text_input_ex_protocol *this, + void *notification_handle) +{ + efi_status_t ret = EFI_INVALID_PARAMETER; + struct efi_cin_notify_function *item, *notify_function = + notification_handle; + + EFI_ENTRY("%p, %p", this, notification_handle); + + /* Check parameters */ + if (!this || !notification_handle) + goto out; + + list_for_each_entry(item, &cin_notify_functions, link) { + if (item == notify_function) { + ret = EFI_SUCCESS; + break; + } + } + if (ret != EFI_SUCCESS) + goto out; + + /* Remove the notify function */ + list_del(¬ify_function->link); + free(notify_function); +out: + return EFI_EXIT(ret); +} + + +/** + * efi_cin_reset() - drain the input buffer + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @extended_verification: allow for exhaustive verification + * Return: status code + * + * This function implements the Reset service of the + * EFI_SIMPLE_TEXT_INPUT_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_reset + (struct efi_simple_text_input_protocol *this, + bool extended_verification) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %d", this, extended_verification); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + efi_cin_empty_buffer(); +out: + return EFI_EXIT(ret); +} + +/** + * efi_cin_read_key_stroke() - read key stroke + * + * @this: instance of the EFI_SIMPLE_TEXT_INPUT_PROTOCOL + * @key: key read from console + * Return: status code + * + * This function implements the ReadKeyStroke service of the + * EFI_SIMPLE_TEXT_INPUT_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + */ +static efi_status_t EFIAPI efi_cin_read_key_stroke + (struct efi_simple_text_input_protocol *this, + struct efi_input_key *key) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p", this, key); + + /* Check parameters */ + if (!this || !key) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* We don't do interrupts, so check for timers cooperatively */ + efi_timer_check(); + + /* Enable console input after ExitBootServices */ + efi_cin_check(); + + if (!key_available) { + ret = EFI_NOT_READY; + goto out; + } + *key = next_key.key; + key_available = false; + efi_con_in.wait_for_key->is_signaled = false; +out: + return EFI_EXIT(ret); +} + +static struct efi_simple_text_input_ex_protocol efi_con_in_ex = { + .reset = efi_cin_reset_ex, + .read_key_stroke_ex = efi_cin_read_key_stroke_ex, + .wait_for_key_ex = NULL, + .set_state = efi_cin_set_state, + .register_key_notify = efi_cin_register_key_notify, + .unregister_key_notify = efi_cin_unregister_key_notify, +}; + +struct efi_simple_text_input_protocol efi_con_in = { + .reset = efi_cin_reset, + .read_key_stroke = efi_cin_read_key_stroke, + .wait_for_key = NULL, +}; + +static struct efi_event *console_timer_event; + +/* + * efi_console_timer_notify() - notify the console timer event + * + * @event: console timer event + * @context: not used + */ +static void EFIAPI efi_console_timer_notify(struct efi_event *event, + void *context) +{ + EFI_ENTRY("%p, %p", event, context); + efi_cin_check(); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_key_notify() - notify the wait for key event + * + * @event: wait for key event + * @context: not used + */ +static void EFIAPI efi_key_notify(struct efi_event *event, void *context) +{ + EFI_ENTRY("%p, %p", event, context); + efi_cin_check(); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_console_register() - install the console protocols + * + * This function is called from do_bootefi_exec(). + * + * Return: status code + */ +efi_status_t efi_console_register(void) +{ + efi_status_t r; + efi_handle_t console_output_handle; + efi_handle_t console_input_handle; + + /* Set up mode information */ + query_console_size(); + + /* Create handles */ + r = efi_create_handle(&console_output_handle); + if (r != EFI_SUCCESS) + goto out_of_memory; + + r = efi_add_protocol(console_output_handle, + &efi_guid_text_output_protocol, &efi_con_out); + if (r != EFI_SUCCESS) + goto out_of_memory; + systab.con_out_handle = console_output_handle; + systab.stderr_handle = console_output_handle; + + r = efi_create_handle(&console_input_handle); + if (r != EFI_SUCCESS) + goto out_of_memory; + + r = efi_add_protocol(console_input_handle, + &efi_guid_text_input_protocol, &efi_con_in); + if (r != EFI_SUCCESS) + goto out_of_memory; + systab.con_in_handle = console_input_handle; + r = efi_add_protocol(console_input_handle, + &efi_guid_text_input_ex_protocol, &efi_con_in_ex); + if (r != EFI_SUCCESS) + goto out_of_memory; + + /* Create console events */ + r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, efi_key_notify, + NULL, NULL, &efi_con_in.wait_for_key); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register WaitForKey event\n"); + return r; + } + efi_con_in_ex.wait_for_key_ex = efi_con_in.wait_for_key; + r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + efi_console_timer_notify, NULL, NULL, + &console_timer_event); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register console event\n"); + return r; + } + /* 5000 ns cycle is sufficient for 2 MBaud */ + r = efi_set_timer(console_timer_event, EFI_TIMER_PERIODIC, 50); + if (r != EFI_SUCCESS) + printf("ERROR: Failed to set console timer\n"); + return r; +out_of_memory: + printf("ERROR: Out of memory\n"); + return r; +} diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c new file mode 100644 index 00000000..73f1fe75 --- /dev/null +++ b/lib/efi_loader/efi_device_path.c @@ -0,0 +1,1076 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI device path from u-boot device-model mapping + * + * (C) Copyright 2017 Rob Clark + */ + +#include <common.h> +#include <blk.h> +#include <dm.h> +#include <usb.h> +#include <mmc.h> +#include <nvme.h> +#include <efi_loader.h> +#include <part.h> +#include <sandboxblockdev.h> +#include <asm-generic/unaligned.h> +#include <linux/compat.h> /* U16_MAX */ + +#ifdef CONFIG_SANDBOX +const efi_guid_t efi_guid_host_dev = U_BOOT_HOST_DEV_GUID; +#endif + +/* template END node: */ +static const struct efi_device_path END = { + .type = DEVICE_PATH_TYPE_END, + .sub_type = DEVICE_PATH_SUB_TYPE_END, + .length = sizeof(END), +}; + +/* template ROOT node: */ +static const struct efi_device_path_vendor ROOT = { + .dp = { + .type = DEVICE_PATH_TYPE_HARDWARE_DEVICE, + .sub_type = DEVICE_PATH_SUB_TYPE_VENDOR, + .length = sizeof(ROOT), + }, + .guid = U_BOOT_GUID, +}; + +#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC) +/* + * Determine if an MMC device is an SD card. + * + * @desc block device descriptor + * @return true if the device is an SD card + */ +static bool is_sd(struct blk_desc *desc) +{ + struct mmc *mmc = find_mmc_device(desc->devnum); + + if (!mmc) + return false; + + return IS_SD(mmc) != 0U; +} +#endif + +static void *dp_alloc(size_t sz) +{ + void *buf; + + if (efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, sz, &buf) != + EFI_SUCCESS) { + debug("EFI: ERROR: out of memory in %s\n", __func__); + return NULL; + } + + memset(buf, 0, sz); + return buf; +} + +/* + * Iterate to next block in device-path, terminating (returning NULL) + * at /End* node. + */ +struct efi_device_path *efi_dp_next(const struct efi_device_path *dp) +{ + if (dp == NULL) + return NULL; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + dp = ((void *)dp) + dp->length; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + return (struct efi_device_path *)dp; +} + +/* + * Compare two device-paths, stopping when the shorter of the two hits + * an End* node. This is useful to, for example, compare a device-path + * representing a device with one representing a file on the device, or + * a device with a parent device. + */ +int efi_dp_match(const struct efi_device_path *a, + const struct efi_device_path *b) +{ + while (1) { + int ret; + + ret = memcmp(&a->length, &b->length, sizeof(a->length)); + if (ret) + return ret; + + ret = memcmp(a, b, a->length); + if (ret) + return ret; + + a = efi_dp_next(a); + b = efi_dp_next(b); + + if (!a || !b) + return 0; + } +} + +/* + * We can have device paths that start with a USB WWID or a USB Class node, + * and a few other cases which don't encode the full device path with bus + * hierarchy: + * + * - MESSAGING:USB_WWID + * - MESSAGING:USB_CLASS + * - MEDIA:FILE_PATH + * - MEDIA:HARD_DRIVE + * - MESSAGING:URI + * + * See UEFI spec (section 3.1.2, about short-form device-paths) + */ +static struct efi_device_path *shorten_path(struct efi_device_path *dp) +{ + while (dp) { + /* + * TODO: Add MESSAGING:USB_WWID and MESSAGING:URI.. + * in practice fallback.efi just uses MEDIA:HARD_DRIVE + * so not sure when we would see these other cases. + */ + if (EFI_DP_TYPE(dp, MESSAGING_DEVICE, MSG_USB_CLASS) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, HARD_DRIVE_PATH) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, FILE_PATH)) + return dp; + + dp = efi_dp_next(dp); + } + + return dp; +} + +static struct efi_object *find_obj(struct efi_device_path *dp, bool short_path, + struct efi_device_path **rem) +{ + struct efi_object *efiobj; + efi_uintn_t dp_size = efi_dp_instance_size(dp); + + list_for_each_entry(efiobj, &efi_obj_list, link) { + struct efi_handler *handler; + struct efi_device_path *obj_dp; + efi_status_t ret; + + ret = efi_search_protocol(efiobj, + &efi_guid_device_path, &handler); + if (ret != EFI_SUCCESS) + continue; + obj_dp = handler->protocol_interface; + + do { + if (efi_dp_match(dp, obj_dp) == 0) { + if (rem) { + /* + * Allow partial matches, but inform + * the caller. + */ + *rem = ((void *)dp) + + efi_dp_instance_size(obj_dp); + return efiobj; + } else { + /* Only return on exact matches */ + if (efi_dp_instance_size(obj_dp) == + dp_size) + return efiobj; + } + } + + obj_dp = shorten_path(efi_dp_next(obj_dp)); + } while (short_path && obj_dp); + } + + return NULL; +} + +/* + * Find an efiobj from device-path, if 'rem' is not NULL, returns the + * remaining part of the device path after the matched object. + */ +struct efi_object *efi_dp_find_obj(struct efi_device_path *dp, + struct efi_device_path **rem) +{ + struct efi_object *efiobj; + + /* Search for an exact match first */ + efiobj = find_obj(dp, false, NULL); + + /* Then for a fuzzy match */ + if (!efiobj) + efiobj = find_obj(dp, false, rem); + + /* And now for a fuzzy short match */ + if (!efiobj) + efiobj = find_obj(dp, true, rem); + + return efiobj; +} + +/* + * Determine the last device path node that is not the end node. + * + * @dp device path + * @return last node before the end node if it exists + * otherwise NULL + */ +const struct efi_device_path *efi_dp_last_node(const struct efi_device_path *dp) +{ + struct efi_device_path *ret; + + if (!dp || dp->type == DEVICE_PATH_TYPE_END) + return NULL; + while (dp) { + ret = (struct efi_device_path *)dp; + dp = efi_dp_next(dp); + } + return ret; +} + +/* get size of the first device path instance excluding end node */ +efi_uintn_t efi_dp_instance_size(const struct efi_device_path *dp) +{ + efi_uintn_t sz = 0; + + if (!dp || dp->type == DEVICE_PATH_TYPE_END) + return 0; + while (dp) { + sz += dp->length; + dp = efi_dp_next(dp); + } + + return sz; +} + +/* get size of multi-instance device path excluding end node */ +efi_uintn_t efi_dp_size(const struct efi_device_path *dp) +{ + const struct efi_device_path *p = dp; + + if (!p) + return 0; + while (p->type != DEVICE_PATH_TYPE_END || + p->sub_type != DEVICE_PATH_SUB_TYPE_END) + p = (void *)p + p->length; + + return (void *)p - (void *)dp; +} + +/* copy multi-instance device path */ +struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp) +{ + struct efi_device_path *ndp; + size_t sz = efi_dp_size(dp) + sizeof(END); + + if (!dp) + return NULL; + + ndp = dp_alloc(sz); + if (!ndp) + return NULL; + memcpy(ndp, dp, sz); + + return ndp; +} + +struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1, + const struct efi_device_path *dp2) +{ + struct efi_device_path *ret; + + if (!dp1 && !dp2) { + /* return an end node */ + ret = efi_dp_dup(&END); + } else if (!dp1) { + ret = efi_dp_dup(dp2); + } else if (!dp2) { + ret = efi_dp_dup(dp1); + } else { + /* both dp1 and dp2 are non-null */ + unsigned sz1 = efi_dp_size(dp1); + unsigned sz2 = efi_dp_size(dp2); + void *p = dp_alloc(sz1 + sz2 + sizeof(END)); + if (!p) + return NULL; + memcpy(p, dp1, sz1); + /* the end node of the second device path has to be retained */ + memcpy(p + sz1, dp2, sz2 + sizeof(END)); + ret = p; + } + + return ret; +} + +struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp, + const struct efi_device_path *node) +{ + struct efi_device_path *ret; + + if (!node && !dp) { + ret = efi_dp_dup(&END); + } else if (!node) { + ret = efi_dp_dup(dp); + } else if (!dp) { + size_t sz = node->length; + void *p = dp_alloc(sz + sizeof(END)); + if (!p) + return NULL; + memcpy(p, node, sz); + memcpy(p + sz, &END, sizeof(END)); + ret = p; + } else { + /* both dp and node are non-null */ + size_t sz = efi_dp_size(dp); + void *p = dp_alloc(sz + node->length + sizeof(END)); + if (!p) + return NULL; + memcpy(p, dp, sz); + memcpy(p + sz, node, node->length); + memcpy(p + sz + node->length, &END, sizeof(END)); + ret = p; + } + + return ret; +} + +struct efi_device_path *efi_dp_create_device_node(const u8 type, + const u8 sub_type, + const u16 length) +{ + struct efi_device_path *ret; + + if (length < sizeof(struct efi_device_path)) + return NULL; + + ret = dp_alloc(length); + if (!ret) + return ret; + ret->type = type; + ret->sub_type = sub_type; + ret->length = length; + return ret; +} + +struct efi_device_path *efi_dp_append_instance( + const struct efi_device_path *dp, + const struct efi_device_path *dpi) +{ + size_t sz, szi; + struct efi_device_path *p, *ret; + + if (!dpi) + return NULL; + if (!dp) + return efi_dp_dup(dpi); + sz = efi_dp_size(dp); + szi = efi_dp_instance_size(dpi); + p = dp_alloc(sz + szi + 2 * sizeof(END)); + if (!p) + return NULL; + ret = p; + memcpy(p, dp, sz + sizeof(END)); + p = (void *)p + sz; + p->sub_type = DEVICE_PATH_SUB_TYPE_INSTANCE_END; + p = (void *)p + sizeof(END); + memcpy(p, dpi, szi); + p = (void *)p + szi; + memcpy(p, &END, sizeof(END)); + return ret; +} + +struct efi_device_path *efi_dp_get_next_instance(struct efi_device_path **dp, + efi_uintn_t *size) +{ + size_t sz; + struct efi_device_path *p; + + if (size) + *size = 0; + if (!dp || !*dp) + return NULL; + sz = efi_dp_instance_size(*dp); + p = dp_alloc(sz + sizeof(END)); + if (!p) + return NULL; + memcpy(p, *dp, sz + sizeof(END)); + *dp = (void *)*dp + sz; + if ((*dp)->sub_type == DEVICE_PATH_SUB_TYPE_INSTANCE_END) + *dp = (void *)*dp + sizeof(END); + else + *dp = NULL; + if (size) + *size = sz + sizeof(END); + return p; +} + +bool efi_dp_is_multi_instance(const struct efi_device_path *dp) +{ + const struct efi_device_path *p = dp; + + if (!p) + return false; + while (p->type != DEVICE_PATH_TYPE_END) + p = (void *)p + p->length; + return p->sub_type == DEVICE_PATH_SUB_TYPE_INSTANCE_END; +} + +#ifdef CONFIG_DM +/* size of device-path not including END node for device and all parents + * up to the root device. + */ +__maybe_unused static unsigned int dp_size(struct udevice *dev) +{ + if (!dev || !dev->driver) + return sizeof(ROOT); + + switch (dev->driver->id) { + case UCLASS_ROOT: + case UCLASS_SIMPLE_BUS: + /* stop traversing parents at this point: */ + return sizeof(ROOT); + case UCLASS_ETH: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_mac_addr); +#ifdef CONFIG_BLK + case UCLASS_BLK: + switch (dev->parent->uclass->uc_drv->id) { +#ifdef CONFIG_IDE + case UCLASS_IDE: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_atapi); +#endif +#if defined(CONFIG_SCSI) && defined(CONFIG_DM_SCSI) + case UCLASS_SCSI: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_scsi); +#endif +#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC) + case UCLASS_MMC: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_sd_mmc_path); +#endif +#if defined(CONFIG_NVME) + case UCLASS_NVME: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_nvme); +#endif +#ifdef CONFIG_SANDBOX + case UCLASS_ROOT: + /* + * Sandbox's host device will be represented + * as vendor device with extra one byte for + * device number + */ + return dp_size(dev->parent) + + sizeof(struct efi_device_path_vendor) + 1; +#endif + default: + return dp_size(dev->parent); + } +#endif +#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC) + case UCLASS_MMC: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_sd_mmc_path); +#endif + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_usb_class); + default: + /* just skip over unknown classes: */ + return dp_size(dev->parent); + } +} + +/* + * Recursively build a device path. + * + * @buf pointer to the end of the device path + * @dev device + * @return pointer to the end of the device path + */ +__maybe_unused static void *dp_fill(void *buf, struct udevice *dev) +{ + if (!dev || !dev->driver) + return buf; + + switch (dev->driver->id) { + case UCLASS_ROOT: + case UCLASS_SIMPLE_BUS: { + /* stop traversing parents at this point: */ + struct efi_device_path_vendor *vdp = buf; + *vdp = ROOT; + return &vdp[1]; + } +#ifdef CONFIG_DM_ETH + case UCLASS_ETH: { + struct efi_device_path_mac_addr *dp = + dp_fill(buf, dev->parent); + struct eth_pdata *pdata = dev->platdata; + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR; + dp->dp.length = sizeof(*dp); + memset(&dp->mac, 0, sizeof(dp->mac)); + /* We only support IPv4 */ + memcpy(&dp->mac, &pdata->enetaddr, ARP_HLEN); + /* Ethernet */ + dp->if_type = 1; + return &dp[1]; + } +#endif +#ifdef CONFIG_BLK + case UCLASS_BLK: + switch (dev->parent->uclass->uc_drv->id) { +#ifdef CONFIG_SANDBOX + case UCLASS_ROOT: { + /* stop traversing parents at this point: */ + struct efi_device_path_vendor *dp = buf; + struct blk_desc *desc = dev_get_uclass_platdata(dev); + + dp_fill(buf, dev->parent); + dp = buf; + ++dp; + dp->dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_VENDOR; + dp->dp.length = sizeof(*dp) + 1; + memcpy(&dp->guid, &efi_guid_host_dev, + sizeof(efi_guid_t)); + dp->vendor_data[0] = desc->devnum; + return &dp->vendor_data[1]; + } +#endif +#ifdef CONFIG_IDE + case UCLASS_IDE: { + struct efi_device_path_atapi *dp = + dp_fill(buf, dev->parent); + struct blk_desc *desc = dev_get_uclass_platdata(dev); + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_ATAPI; + dp->dp.length = sizeof(*dp); + dp->logical_unit_number = desc->devnum; + dp->primary_secondary = IDE_BUS(desc->devnum); + dp->slave_master = desc->devnum % + (CONFIG_SYS_IDE_MAXDEVICE / + CONFIG_SYS_IDE_MAXBUS); + return &dp[1]; + } +#endif +#if defined(CONFIG_SCSI) && defined(CONFIG_DM_SCSI) + case UCLASS_SCSI: { + struct efi_device_path_scsi *dp = + dp_fill(buf, dev->parent); + struct blk_desc *desc = dev_get_uclass_platdata(dev); + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_SCSI; + dp->dp.length = sizeof(*dp); + dp->logical_unit_number = desc->lun; + dp->target_id = desc->target; + return &dp[1]; + } +#endif +#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC) + case UCLASS_MMC: { + struct efi_device_path_sd_mmc_path *sddp = + dp_fill(buf, dev->parent); + struct blk_desc *desc = dev_get_uclass_platdata(dev); + + sddp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + sddp->dp.sub_type = is_sd(desc) ? + DEVICE_PATH_SUB_TYPE_MSG_SD : + DEVICE_PATH_SUB_TYPE_MSG_MMC; + sddp->dp.length = sizeof(*sddp); + sddp->slot_number = dev->seq; + return &sddp[1]; + } +#endif +#if defined(CONFIG_NVME) + case UCLASS_NVME: { + struct efi_device_path_nvme *dp = + dp_fill(buf, dev->parent); + u32 ns_id; + + dp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + dp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_NVME; + dp->dp.length = sizeof(*dp); + nvme_get_namespace_id(dev, &ns_id, dp->eui64); + memcpy(&dp->ns_id, &ns_id, sizeof(ns_id)); + return &dp[1]; + } +#endif + default: + debug("%s(%u) %s: unhandled parent class: %s (%u)\n", + __FILE__, __LINE__, __func__, + dev->name, dev->parent->uclass->uc_drv->id); + return dp_fill(buf, dev->parent); + } +#endif +#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC) + case UCLASS_MMC: { + struct efi_device_path_sd_mmc_path *sddp = + dp_fill(buf, dev->parent); + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct blk_desc *desc = mmc_get_blk_desc(mmc); + + sddp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + sddp->dp.sub_type = is_sd(desc) ? + DEVICE_PATH_SUB_TYPE_MSG_SD : + DEVICE_PATH_SUB_TYPE_MSG_MMC; + sddp->dp.length = sizeof(*sddp); + sddp->slot_number = dev->seq; + + return &sddp[1]; + } +#endif + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: { + struct efi_device_path_usb_class *udp = + dp_fill(buf, dev->parent); + struct usb_device *udev = dev_get_parent_priv(dev); + struct usb_device_descriptor *desc = &udev->descriptor; + + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS; + udp->dp.length = sizeof(*udp); + udp->vendor_id = desc->idVendor; + udp->product_id = desc->idProduct; + udp->device_class = desc->bDeviceClass; + udp->device_subclass = desc->bDeviceSubClass; + udp->device_protocol = desc->bDeviceProtocol; + + return &udp[1]; + } + default: + debug("%s(%u) %s: unhandled device class: %s (%u)\n", + __FILE__, __LINE__, __func__, + dev->name, dev->driver->id); + return dp_fill(buf, dev->parent); + } +} +#endif + +static unsigned dp_part_size(struct blk_desc *desc, int part) +{ + unsigned dpsize; + +#ifdef CONFIG_BLK + { + struct udevice *dev; + int ret = blk_find_device(desc->if_type, desc->devnum, &dev); + + if (ret) + dev = desc->bdev->parent; + dpsize = dp_size(dev); + } +#else + dpsize = sizeof(ROOT) + sizeof(struct efi_device_path_usb); +#endif + + if (part == 0) /* the actual disk, not a partition */ + return dpsize; + + if (desc->part_type == PART_TYPE_ISO) + dpsize += sizeof(struct efi_device_path_cdrom_path); + else + dpsize += sizeof(struct efi_device_path_hard_drive_path); + + return dpsize; +} + +/* + * Create a device node for a block device partition. + * + * @buf buffer to which the device path is written + * @desc block device descriptor + * @part partition number, 0 identifies a block device + */ +static void *dp_part_node(void *buf, struct blk_desc *desc, int part) +{ + disk_partition_t info; + + part_get_info(desc, part, &info); + + if (desc->part_type == PART_TYPE_ISO) { + struct efi_device_path_cdrom_path *cddp = buf; + + cddp->boot_entry = part; + cddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + cddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_CDROM_PATH; + cddp->dp.length = sizeof(*cddp); + cddp->partition_start = info.start; + cddp->partition_size = info.size; + + buf = &cddp[1]; + } else { + struct efi_device_path_hard_drive_path *hddp = buf; + + hddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + hddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH; + hddp->dp.length = sizeof(*hddp); + hddp->partition_number = part; + hddp->partition_start = info.start; + hddp->partition_end = info.size; + if (desc->part_type == PART_TYPE_EFI) + hddp->partmap_type = 2; + else + hddp->partmap_type = 1; + + switch (desc->sig_type) { + case SIG_TYPE_NONE: + default: + hddp->signature_type = 0; + memset(hddp->partition_signature, 0, + sizeof(hddp->partition_signature)); + break; + case SIG_TYPE_MBR: + hddp->signature_type = 1; + memset(hddp->partition_signature, 0, + sizeof(hddp->partition_signature)); + memcpy(hddp->partition_signature, &desc->mbr_sig, + sizeof(desc->mbr_sig)); + break; + case SIG_TYPE_GUID: + hddp->signature_type = 2; + memcpy(hddp->partition_signature, &desc->guid_sig, + sizeof(hddp->partition_signature)); + break; + } + + buf = &hddp[1]; + } + + return buf; +} + +/* + * Create a device path for a block device or one of its partitions. + * + * @buf buffer to which the device path is written + * @desc block device descriptor + * @part partition number, 0 identifies a block device + */ +static void *dp_part_fill(void *buf, struct blk_desc *desc, int part) +{ +#ifdef CONFIG_BLK + { + struct udevice *dev; + int ret = blk_find_device(desc->if_type, desc->devnum, &dev); + + if (ret) + dev = desc->bdev->parent; + buf = dp_fill(buf, dev); + } +#else + /* + * We *could* make a more accurate path, by looking at if_type + * and handling all the different cases like we do for non- + * legacy (i.e. CONFIG_BLK=y) case. But most important thing + * is just to have a unique device-path for if_type+devnum. + * So map things to a fictitious USB device. + */ + struct efi_device_path_usb *udp; + + memcpy(buf, &ROOT, sizeof(ROOT)); + buf += sizeof(ROOT); + + udp = buf; + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB; + udp->dp.length = sizeof(*udp); + udp->parent_port_number = desc->if_type; + udp->usb_interface = desc->devnum; + buf = &udp[1]; +#endif + + if (part == 0) /* the actual disk, not a partition */ + return buf; + + return dp_part_node(buf, desc, part); +} + +/* Construct a device-path from a partition on a block device: */ +struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part) +{ + void *buf, *start; + + start = buf = dp_alloc(dp_part_size(desc, part) + sizeof(END)); + if (!buf) + return NULL; + + buf = dp_part_fill(buf, desc, part); + + *((struct efi_device_path *)buf) = END; + + return start; +} + +/* + * Create a device node for a block device partition. + * + * @buf buffer to which the device path is written + * @desc block device descriptor + * @part partition number, 0 identifies a block device + */ +struct efi_device_path *efi_dp_part_node(struct blk_desc *desc, int part) +{ + efi_uintn_t dpsize; + void *buf; + + if (desc->part_type == PART_TYPE_ISO) + dpsize = sizeof(struct efi_device_path_cdrom_path); + else + dpsize = sizeof(struct efi_device_path_hard_drive_path); + buf = dp_alloc(dpsize); + + dp_part_node(buf, desc, part); + + return buf; +} + +/** + * path_to_uefi() - convert UTF-8 path to an UEFI style path + * + * Convert UTF-8 path to a UEFI style path (i.e. with backslashes as path + * separators and UTF-16). + * + * @src: source buffer + * @uefi: target buffer, possibly unaligned + */ +static void path_to_uefi(void *uefi, const char *src) +{ + u16 *pos = uefi; + + /* + * efi_set_bootdev() calls this routine indirectly before the UEFI + * subsystem is initialized. So we cannot assume unaligned access to be + * enabled. + */ + allow_unaligned(); + + while (*src) { + s32 code = utf8_get(&src); + + if (code < 0) + code = '?'; + else if (code == '/') + code = '\\'; + utf16_put(code, &pos); + } + *pos = 0; +} + +/* + * If desc is NULL, this creates a path with only the file component, + * otherwise it creates a full path with both device and file components + */ +struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part, + const char *path) +{ + struct efi_device_path_file_path *fp; + void *buf, *start; + size_t dpsize = 0, fpsize; + + if (desc) + dpsize = dp_part_size(desc, part); + + fpsize = sizeof(struct efi_device_path) + + 2 * (utf8_utf16_strlen(path) + 1); + if (fpsize > U16_MAX) + return NULL; + + dpsize += fpsize; + + start = buf = dp_alloc(dpsize + sizeof(END)); + if (!buf) + return NULL; + + if (desc) + buf = dp_part_fill(buf, desc, part); + + /* add file-path: */ + fp = buf; + fp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + fp->dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH; + fp->dp.length = (u16)fpsize; + path_to_uefi(fp->str, path); + buf += fpsize; + + *((struct efi_device_path *)buf) = END; + + return start; +} + +#ifdef CONFIG_NET +struct efi_device_path *efi_dp_from_eth(void) +{ +#ifndef CONFIG_DM_ETH + struct efi_device_path_mac_addr *ndp; +#endif + void *buf, *start; + unsigned dpsize = 0; + + assert(eth_get_dev()); + +#ifdef CONFIG_DM_ETH + dpsize += dp_size(eth_get_dev()); +#else + dpsize += sizeof(ROOT); + dpsize += sizeof(*ndp); +#endif + + start = buf = dp_alloc(dpsize + sizeof(END)); + if (!buf) + return NULL; + +#ifdef CONFIG_DM_ETH + buf = dp_fill(buf, eth_get_dev()); +#else + memcpy(buf, &ROOT, sizeof(ROOT)); + buf += sizeof(ROOT); + + ndp = buf; + ndp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + ndp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR; + ndp->dp.length = sizeof(*ndp); + ndp->if_type = 1; /* Ethernet */ + memcpy(ndp->mac.addr, eth_get_ethaddr(), ARP_HLEN); + buf = &ndp[1]; +#endif + + *((struct efi_device_path *)buf) = END; + + return start; +} +#endif + +/* Construct a device-path for memory-mapped image */ +struct efi_device_path *efi_dp_from_mem(uint32_t memory_type, + uint64_t start_address, + uint64_t end_address) +{ + struct efi_device_path_memory *mdp; + void *buf, *start; + + start = buf = dp_alloc(sizeof(*mdp) + sizeof(END)); + if (!buf) + return NULL; + + mdp = buf; + mdp->dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + mdp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MEMORY; + mdp->dp.length = sizeof(*mdp); + mdp->memory_type = memory_type; + mdp->start_address = start_address; + mdp->end_address = end_address; + buf = &mdp[1]; + + *((struct efi_device_path *)buf) = END; + + return start; +} + +/** + * efi_dp_split_file_path() - split of relative file path from device path + * + * Given a device path indicating a file on a device, separate the device + * path in two: the device path of the actual device and the file path + * relative to this device. + * + * @full_path: device path including device and file path + * @device_path: path of the device + * @file_path: relative path of the file or NULL if there is none + * Return: status code + */ +efi_status_t efi_dp_split_file_path(struct efi_device_path *full_path, + struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + struct efi_device_path *p, *dp, *fp = NULL; + + *device_path = NULL; + *file_path = NULL; + dp = efi_dp_dup(full_path); + if (!dp) + return EFI_OUT_OF_RESOURCES; + p = dp; + while (!EFI_DP_TYPE(p, MEDIA_DEVICE, FILE_PATH)) { + p = efi_dp_next(p); + if (!p) + goto out; + } + fp = efi_dp_dup(p); + if (!fp) + return EFI_OUT_OF_RESOURCES; + p->type = DEVICE_PATH_TYPE_END; + p->sub_type = DEVICE_PATH_SUB_TYPE_END; + p->length = sizeof(*p); + +out: + *device_path = dp; + *file_path = fp; + return EFI_SUCCESS; +} + +/** + * efi_dp_from_name() - convert U-Boot device and file path to device path + * + * @dev: U-Boot device, e.g. 'mmc' + * @devnr: U-Boot device number, e.g. 1 for 'mmc:1' + * @path: file path relative to U-Boot device, may be NULL + * @device: pointer to receive device path of the device + * @file: pointer to receive device path for the file + * Return: status code + */ +efi_status_t efi_dp_from_name(const char *dev, const char *devnr, + const char *path, + struct efi_device_path **device, + struct efi_device_path **file) +{ + int is_net; + struct blk_desc *desc = NULL; + disk_partition_t fs_partition; + int part = 0; + char filename[32] = { 0 }; /* dp->str is u16[32] long */ + char *s; + + if (path && !file) + return EFI_INVALID_PARAMETER; + + is_net = !strcmp(dev, "Net"); + if (!is_net) { + part = blk_get_device_part_str(dev, devnr, &desc, &fs_partition, + 1); + if (part < 0 || !desc) + return EFI_INVALID_PARAMETER; + + if (device) + *device = efi_dp_from_part(desc, part); + } else { +#ifdef CONFIG_NET + if (device) + *device = efi_dp_from_eth(); +#endif + } + + if (!path) + return EFI_SUCCESS; + + snprintf(filename, sizeof(filename), "%s", path); + /* DOS style file path: */ + s = filename; + while ((s = strchr(s, '/'))) + *s++ = '\\'; + *file = efi_dp_from_file(is_net ? NULL : desc, part, filename); + + if (!*file) + return EFI_INVALID_PARAMETER; + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_device_path_to_text.c b/lib/efi_loader/efi_device_path_to_text.c new file mode 100644 index 00000000..af1adbb7 --- /dev/null +++ b/lib/efi_loader/efi_device_path_to_text.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI device path interface + * + * Copyright (c) 2017 Heinrich Schuchardt + */ + +#include <common.h> +#include <efi_loader.h> + +#define MAC_OUTPUT_LEN 22 +#define UNKNOWN_OUTPUT_LEN 23 + +#define MAX_NODE_LEN 512 +#define MAX_PATH_LEN 1024 + +const efi_guid_t efi_guid_device_path_to_text_protocol = + EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID; + +/** + * efi_str_to_u16() - convert ASCII string to UTF-16 + * + * A u16 buffer is allocated from pool. The ASCII string is copied to the u16 + * buffer. + * + * @str: ASCII string + * Return: UTF-16 string. NULL if out of memory. + */ +static u16 *efi_str_to_u16(char *str) +{ + efi_uintn_t len; + u16 *out, *dst; + efi_status_t ret; + + len = sizeof(u16) * (utf8_utf16_strlen(str) + 1); + ret = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, len, (void **)&out); + if (ret != EFI_SUCCESS) + return NULL; + dst = out; + utf8_utf16_strcpy(&dst, str); + return out; +} + +static char *dp_unknown(char *s, struct efi_device_path *dp) +{ + s += sprintf(s, "UNKNOWN(%04x,%04x)", dp->type, dp->sub_type); + return s; +} + +static char *dp_hardware(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_MEMORY: { + struct efi_device_path_memory *mdp = + (struct efi_device_path_memory *)dp; + s += sprintf(s, "MemoryMapped(0x%x,0x%llx,0x%llx)", + mdp->memory_type, + mdp->start_address, + mdp->end_address); + break; + } + case DEVICE_PATH_SUB_TYPE_VENDOR: { + int i, n; + struct efi_device_path_vendor *vdp = + (struct efi_device_path_vendor *)dp; + + s += sprintf(s, "VenHw(%pUl", &vdp->guid); + n = (int)vdp->dp.length - sizeof(struct efi_device_path_vendor); + if (n > 0) { + s += sprintf(s, ","); + for (i = 0; i < n; ++i) + s += sprintf(s, "%02x", vdp->vendor_data[i]); + } + s += sprintf(s, ")"); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +static char *dp_acpi(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_ACPI_DEVICE: { + struct efi_device_path_acpi_path *adp = + (struct efi_device_path_acpi_path *)dp; + + s += sprintf(s, "Acpi(PNP%04X,%d)", EISA_PNP_NUM(adp->hid), + adp->uid); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +static char *dp_msging(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_MSG_ATAPI: { + struct efi_device_path_atapi *ide = + (struct efi_device_path_atapi *)dp; + s += sprintf(s, "Ata(%d,%d,%d)", ide->primary_secondary, + ide->slave_master, ide->logical_unit_number); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_SCSI: { + struct efi_device_path_scsi *ide = + (struct efi_device_path_scsi *)dp; + s += sprintf(s, "Scsi(%u,%u)", ide->target_id, + ide->logical_unit_number); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_USB: { + struct efi_device_path_usb *udp = + (struct efi_device_path_usb *)dp; + s += sprintf(s, "USB(0x%x,0x%x)", udp->parent_port_number, + udp->usb_interface); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR: { + int i, n = sizeof(struct efi_mac_addr); + struct efi_device_path_mac_addr *mdp = + (struct efi_device_path_mac_addr *)dp; + + if (mdp->if_type <= 1) + n = 6; + s += sprintf(s, "MAC("); + for (i = 0; i < n; ++i) + s += sprintf(s, "%02x", mdp->mac.addr[i]); + s += sprintf(s, ",%u)", mdp->if_type); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS: { + struct efi_device_path_usb_class *ucdp = + (struct efi_device_path_usb_class *)dp; + + s += sprintf(s, "UsbClass(0x%x,0x%x,0x%x,0x%x,0x%x)", + ucdp->vendor_id, ucdp->product_id, + ucdp->device_class, ucdp->device_subclass, + ucdp->device_protocol); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_NVME: { + struct efi_device_path_nvme *ndp = + (struct efi_device_path_nvme *)dp; + u32 ns_id; + int i; + + memcpy(&ns_id, &ndp->ns_id, sizeof(ns_id)); + s += sprintf(s, "NVMe(0x%x,", ns_id); + for (i = 0; i < sizeof(ndp->eui64); ++i) + s += sprintf(s, "%s%02x", i ? "-" : "", + ndp->eui64[i]); + s += sprintf(s, ")"); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_SD: + case DEVICE_PATH_SUB_TYPE_MSG_MMC: { + const char *typename = + (dp->sub_type == DEVICE_PATH_SUB_TYPE_MSG_SD) ? + "SD" : "eMMC"; + struct efi_device_path_sd_mmc_path *sddp = + (struct efi_device_path_sd_mmc_path *)dp; + s += sprintf(s, "%s(%u)", typename, sddp->slot_number); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +/* + * Convert a media device path node to text. + * + * @s output buffer + * @dp device path node + * @return next unused buffer address + */ +static char *dp_media(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH: { + struct efi_device_path_hard_drive_path *hddp = + (struct efi_device_path_hard_drive_path *)dp; + void *sig = hddp->partition_signature; + u64 start; + u64 end; + + /* Copy from packed structure to aligned memory */ + memcpy(&start, &hddp->partition_start, sizeof(start)); + memcpy(&end, &hddp->partition_end, sizeof(end)); + + switch (hddp->signature_type) { + case SIG_TYPE_MBR: { + u32 signature; + + memcpy(&signature, sig, sizeof(signature)); + s += sprintf( + s, "HD(%d,MBR,0x%08x,0x%llx,0x%llx)", + hddp->partition_number, signature, start, end); + break; + } + case SIG_TYPE_GUID: + s += sprintf( + s, "HD(%d,GPT,%pUl,0x%llx,0x%llx)", + hddp->partition_number, sig, start, end); + break; + default: + s += sprintf( + s, "HD(%d,0x%02x,0,0x%llx,0x%llx)", + hddp->partition_number, hddp->partmap_type, + start, end); + break; + } + + break; + } + case DEVICE_PATH_SUB_TYPE_CDROM_PATH: { + struct efi_device_path_cdrom_path *cddp = + (struct efi_device_path_cdrom_path *)dp; + s += sprintf(s, "CDROM(%u,0x%llx,0x%llx)", cddp->boot_entry, + cddp->partition_start, cddp->partition_size); + break; + } + case DEVICE_PATH_SUB_TYPE_FILE_PATH: { + struct efi_device_path_file_path *fp = + (struct efi_device_path_file_path *)dp; + int slen = (dp->length - sizeof(*dp)) / 2; + if (slen > MAX_NODE_LEN - 2) + slen = MAX_NODE_LEN - 2; + s += sprintf(s, "%-.*ls", slen, fp->str); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +/* + * Converts a single node to a char string. + * + * @buffer output buffer + * @dp device path or node + * @return end of string + */ +static char *efi_convert_single_device_node_to_text( + char *buffer, + struct efi_device_path *dp) +{ + char *str = buffer; + + switch (dp->type) { + case DEVICE_PATH_TYPE_HARDWARE_DEVICE: + str = dp_hardware(str, dp); + break; + case DEVICE_PATH_TYPE_ACPI_DEVICE: + str = dp_acpi(str, dp); + break; + case DEVICE_PATH_TYPE_MESSAGING_DEVICE: + str = dp_msging(str, dp); + break; + case DEVICE_PATH_TYPE_MEDIA_DEVICE: + str = dp_media(str, dp); + break; + case DEVICE_PATH_TYPE_END: + break; + default: + str = dp_unknown(str, dp); + } + + *str = '\0'; + return str; +} + +/* + * This function implements the ConvertDeviceNodeToText service of the + * EFI_DEVICE_PATH_TO_TEXT_PROTOCOL. + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * device_node device node to be converted + * display_only true if the shorter text representation shall be used + * allow_shortcuts true if shortcut forms may be used + * @return text representation of the device path + * NULL if out of memory of device_path is NULL + */ +static uint16_t EFIAPI *efi_convert_device_node_to_text( + struct efi_device_path *device_node, + bool display_only, + bool allow_shortcuts) +{ + char str[MAX_NODE_LEN]; + uint16_t *text = NULL; + + EFI_ENTRY("%p, %d, %d", device_node, display_only, allow_shortcuts); + + if (!device_node) + goto out; + efi_convert_single_device_node_to_text(str, device_node); + + text = efi_str_to_u16(str); + +out: + EFI_EXIT(EFI_SUCCESS); + return text; +} + +/* + * This function implements the ConvertDevicePathToText service of the + * EFI_DEVICE_PATH_TO_TEXT_PROTOCOL. + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * device_path device path to be converted + * display_only true if the shorter text representation shall be used + * allow_shortcuts true if shortcut forms may be used + * @return text representation of the device path + * NULL if out of memory of device_path is NULL + */ +static uint16_t EFIAPI *efi_convert_device_path_to_text( + struct efi_device_path *device_path, + bool display_only, + bool allow_shortcuts) +{ + uint16_t *text = NULL; + char buffer[MAX_PATH_LEN]; + char *str = buffer; + + EFI_ENTRY("%p, %d, %d", device_path, display_only, allow_shortcuts); + + if (!device_path) + goto out; + while (device_path && + str + MAX_NODE_LEN < buffer + MAX_PATH_LEN) { + *str++ = '/'; + str = efi_convert_single_device_node_to_text(str, device_path); + device_path = efi_dp_next(device_path); + } + + text = efi_str_to_u16(buffer); + +out: + EFI_EXIT(EFI_SUCCESS); + return text; +} + +/* helper for debug prints.. efi_free_pool() the result. */ +uint16_t *efi_dp_str(struct efi_device_path *dp) +{ + return EFI_CALL(efi_convert_device_path_to_text(dp, true, true)); +} + +const struct efi_device_path_to_text_protocol efi_device_path_to_text = { + .convert_device_node_to_text = efi_convert_device_node_to_text, + .convert_device_path_to_text = efi_convert_device_path_to_text, +}; diff --git a/lib/efi_loader/efi_device_path_utilities.c b/lib/efi_loader/efi_device_path_utilities.c new file mode 100644 index 00000000..94015329 --- /dev/null +++ b/lib/efi_loader/efi_device_path_utilities.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI device path interface + * + * Copyright (c) 2017 Leif Lindholm + */ + +#include <common.h> +#include <efi_loader.h> + +const efi_guid_t efi_guid_device_path_utilities_protocol = + EFI_DEVICE_PATH_UTILITIES_PROTOCOL_GUID; + +/* + * Get size of a device path. + * + * This function implements the GetDevicePathSize service of the device path + * utilities protocol. The device path length includes the end of path tag + * which may be an instance end. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * @return size in bytes + */ +static efi_uintn_t EFIAPI get_device_path_size( + const struct efi_device_path *device_path) +{ + efi_uintn_t sz = 0; + + EFI_ENTRY("%pD", device_path); + /* size includes the END node: */ + if (device_path) + sz = efi_dp_size(device_path) + sizeof(struct efi_device_path); + return EFI_EXIT(sz); +} + +/* + * Duplicate a device path. + * + * This function implements the DuplicateDevicePath service of the device path + * utilities protocol. + * + * The UEFI spec does not indicate what happens to the end tag. We follow the + * EDK2 logic: In case the device path ends with an end of instance tag, the + * copy will also end with an end of instance tag. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * @return copy of the device path + */ +static struct efi_device_path * EFIAPI duplicate_device_path( + const struct efi_device_path *device_path) +{ + EFI_ENTRY("%pD", device_path); + return EFI_EXIT(efi_dp_dup(device_path)); +} + +/* + * Append device path. + * + * This function implements the AppendDevicePath service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @src1 1st device path + * @src2 2nd device path + * @return concatenated device path + */ +static struct efi_device_path * EFIAPI append_device_path( + const struct efi_device_path *src1, + const struct efi_device_path *src2) +{ + EFI_ENTRY("%pD, %pD", src1, src2); + return EFI_EXIT(efi_dp_append(src1, src2)); +} + +/* + * Append device path node. + * + * This function implements the AppendDeviceNode service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * @device_node device node + * @return concatenated device path + */ +static struct efi_device_path * EFIAPI append_device_node( + const struct efi_device_path *device_path, + const struct efi_device_path *device_node) +{ + EFI_ENTRY("%pD, %p", device_path, device_node); + return EFI_EXIT(efi_dp_append_node(device_path, device_node)); +} + +/* + * Append device path instance. + * + * This function implements the AppendDevicePathInstance service of the device + * path utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path 1st device path + * @device_path_instance 2nd device path + * @return concatenated device path + */ +static struct efi_device_path * EFIAPI append_device_path_instance( + const struct efi_device_path *device_path, + const struct efi_device_path *device_path_instance) +{ + EFI_ENTRY("%pD, %pD", device_path, device_path_instance); + return EFI_EXIT(efi_dp_append_instance(device_path, + device_path_instance)); +} + +/* + * Get next device path instance. + * + * This function implements the GetNextDevicePathInstance service of the device + * path utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path_instance next device path instance + * @device_path_instance_size size of the device path instance + * @return concatenated device path + */ +static struct efi_device_path * EFIAPI get_next_device_path_instance( + struct efi_device_path **device_path_instance, + efi_uintn_t *device_path_instance_size) +{ + EFI_ENTRY("%pD, %p", device_path_instance, device_path_instance_size); + return EFI_EXIT(efi_dp_get_next_instance(device_path_instance, + device_path_instance_size)); +} + +/* + * Check if a device path contains more than one instance. + * + * This function implements the AppendDeviceNode service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @device_path device path + * @device_node device node + * @return concatenated device path + */ +static bool EFIAPI is_device_path_multi_instance( + const struct efi_device_path *device_path) +{ + EFI_ENTRY("%pD", device_path); + return EFI_EXIT(efi_dp_is_multi_instance(device_path)); +} + +/* + * Create device node. + * + * This function implements the CreateDeviceNode service of the device path + * utilities protocol. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @node_type node type + * @node_sub_type node sub type + * @node_length node length + * @return device path node + */ +static struct efi_device_path * EFIAPI create_device_node( + uint8_t node_type, uint8_t node_sub_type, uint16_t node_length) +{ + EFI_ENTRY("%u, %u, %u", node_type, node_sub_type, node_length); + return EFI_EXIT(efi_dp_create_device_node(node_type, node_sub_type, + node_length)); +} + +const struct efi_device_path_utilities_protocol efi_device_path_utilities = { + .get_device_path_size = get_device_path_size, + .duplicate_device_path = duplicate_device_path, + .append_device_path = append_device_path, + .append_device_node = append_device_node, + .append_device_path_instance = append_device_path_instance, + .get_next_device_path_instance = get_next_device_path_instance, + .is_device_path_multi_instance = is_device_path_multi_instance, + .create_device_node = create_device_node, +}; diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c new file mode 100644 index 00000000..ed7fb3f7 --- /dev/null +++ b/lib/efi_loader/efi_disk.c @@ -0,0 +1,516 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application disk support + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <blk.h> +#include <dm.h> +#include <efi_loader.h> +#include <fs.h> +#include <part.h> +#include <malloc.h> + +const efi_guid_t efi_block_io_guid = EFI_BLOCK_IO_PROTOCOL_GUID; + +/** + * struct efi_disk_obj - EFI disk object + * + * @header: EFI object header + * @ops: EFI disk I/O protocol interface + * @ifname: interface name for block device + * @dev_index: device index of block device + * @media: block I/O media information + * @dp: device path to the block device + * @part: partition + * @volume: simple file system protocol of the partition + * @offset: offset into disk for simple partition + * @desc: internal block device descriptor + */ +struct efi_disk_obj { + struct efi_object header; + struct efi_block_io ops; + const char *ifname; + int dev_index; + struct efi_block_io_media media; + struct efi_device_path *dp; + unsigned int part; + struct efi_simple_file_system_protocol *volume; + lbaint_t offset; + struct blk_desc *desc; +}; + +/** + * efi_disk_reset() - reset block device + * + * This function implements the Reset service of the EFI_BLOCK_IO_PROTOCOL. + * + * As U-Boot's block devices do not have a reset function simply return + * EFI_SUCCESS. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: pointer to the BLOCK_IO_PROTOCOL + * @extended_verification: extended verification + * Return: status code + */ +static efi_status_t EFIAPI efi_disk_reset(struct efi_block_io *this, + char extended_verification) +{ + EFI_ENTRY("%p, %x", this, extended_verification); + return EFI_EXIT(EFI_SUCCESS); +} + +enum efi_disk_direction { + EFI_DISK_READ, + EFI_DISK_WRITE, +}; + +static efi_status_t efi_disk_rw_blocks(struct efi_block_io *this, + u32 media_id, u64 lba, unsigned long buffer_size, + void *buffer, enum efi_disk_direction direction) +{ + struct efi_disk_obj *diskobj; + struct blk_desc *desc; + int blksz; + int blocks; + unsigned long n; + + diskobj = container_of(this, struct efi_disk_obj, ops); + desc = (struct blk_desc *) diskobj->desc; + blksz = desc->blksz; + blocks = buffer_size / blksz; + lba += diskobj->offset; + + EFI_PRINT("blocks=%x lba=%llx blksz=%x dir=%d\n", + blocks, lba, blksz, direction); + + /* We only support full block access */ + if (buffer_size & (blksz - 1)) + return EFI_BAD_BUFFER_SIZE; + + if (direction == EFI_DISK_READ) + n = blk_dread(desc, lba, blocks, buffer); + else + n = blk_dwrite(desc, lba, blocks, buffer); + + /* We don't do interrupts, so check for timers cooperatively */ + efi_timer_check(); + + EFI_PRINT("n=%lx blocks=%x\n", n, blocks); + + if (n != blocks) + return EFI_DEVICE_ERROR; + + return EFI_SUCCESS; +} + +static efi_status_t EFIAPI efi_disk_read_blocks(struct efi_block_io *this, + u32 media_id, u64 lba, efi_uintn_t buffer_size, + void *buffer) +{ + void *real_buffer = buffer; + efi_status_t r; + + if (!this) + return EFI_INVALID_PARAMETER; + /* TODO: check for media changes */ + if (media_id != this->media->media_id) + return EFI_MEDIA_CHANGED; + if (!this->media->media_present) + return EFI_NO_MEDIA; + /* media->io_align is a power of 2 */ + if ((uintptr_t)buffer & (this->media->io_align - 1)) + return EFI_INVALID_PARAMETER; + if (lba * this->media->block_size + buffer_size > + this->media->last_block * this->media->block_size) + return EFI_INVALID_PARAMETER; + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER + if (buffer_size > EFI_LOADER_BOUNCE_BUFFER_SIZE) { + r = efi_disk_read_blocks(this, media_id, lba, + EFI_LOADER_BOUNCE_BUFFER_SIZE, buffer); + if (r != EFI_SUCCESS) + return r; + return efi_disk_read_blocks(this, media_id, lba + + EFI_LOADER_BOUNCE_BUFFER_SIZE / this->media->block_size, + buffer_size - EFI_LOADER_BOUNCE_BUFFER_SIZE, + buffer + EFI_LOADER_BOUNCE_BUFFER_SIZE); + } + + real_buffer = efi_bounce_buffer; +#endif + + EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba, + buffer_size, buffer); + + r = efi_disk_rw_blocks(this, media_id, lba, buffer_size, real_buffer, + EFI_DISK_READ); + + /* Copy from bounce buffer to real buffer if necessary */ + if ((r == EFI_SUCCESS) && (real_buffer != buffer)) + memcpy(buffer, real_buffer, buffer_size); + + return EFI_EXIT(r); +} + +static efi_status_t EFIAPI efi_disk_write_blocks(struct efi_block_io *this, + u32 media_id, u64 lba, efi_uintn_t buffer_size, + void *buffer) +{ + void *real_buffer = buffer; + efi_status_t r; + + if (!this) + return EFI_INVALID_PARAMETER; + if (this->media->read_only) + return EFI_WRITE_PROTECTED; + /* TODO: check for media changes */ + if (media_id != this->media->media_id) + return EFI_MEDIA_CHANGED; + if (!this->media->media_present) + return EFI_NO_MEDIA; + /* media->io_align is a power of 2 */ + if ((uintptr_t)buffer & (this->media->io_align - 1)) + return EFI_INVALID_PARAMETER; + if (lba * this->media->block_size + buffer_size > + this->media->last_block * this->media->block_size) + return EFI_INVALID_PARAMETER; + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER + if (buffer_size > EFI_LOADER_BOUNCE_BUFFER_SIZE) { + r = efi_disk_write_blocks(this, media_id, lba, + EFI_LOADER_BOUNCE_BUFFER_SIZE, buffer); + if (r != EFI_SUCCESS) + return r; + return efi_disk_write_blocks(this, media_id, lba + + EFI_LOADER_BOUNCE_BUFFER_SIZE / this->media->block_size, + buffer_size - EFI_LOADER_BOUNCE_BUFFER_SIZE, + buffer + EFI_LOADER_BOUNCE_BUFFER_SIZE); + } + + real_buffer = efi_bounce_buffer; +#endif + + EFI_ENTRY("%p, %x, %llx, %zx, %p", this, media_id, lba, + buffer_size, buffer); + + /* Populate bounce buffer if necessary */ + if (real_buffer != buffer) + memcpy(real_buffer, buffer, buffer_size); + + r = efi_disk_rw_blocks(this, media_id, lba, buffer_size, real_buffer, + EFI_DISK_WRITE); + + return EFI_EXIT(r); +} + +static efi_status_t EFIAPI efi_disk_flush_blocks(struct efi_block_io *this) +{ + /* We always write synchronously */ + EFI_ENTRY("%p", this); + return EFI_EXIT(EFI_SUCCESS); +} + +static const struct efi_block_io block_io_disk_template = { + .reset = &efi_disk_reset, + .read_blocks = &efi_disk_read_blocks, + .write_blocks = &efi_disk_write_blocks, + .flush_blocks = &efi_disk_flush_blocks, +}; + +/* + * Get the simple file system protocol for a file device path. + * + * The full path provided is split into device part and into a file + * part. The device part is used to find the handle on which the + * simple file system protocol is installed. + * + * @full_path device path including device and file + * @return simple file system protocol + */ +struct efi_simple_file_system_protocol * +efi_fs_from_path(struct efi_device_path *full_path) +{ + struct efi_object *efiobj; + struct efi_handler *handler; + struct efi_device_path *device_path; + struct efi_device_path *file_path; + efi_status_t ret; + + /* Split the path into a device part and a file part */ + ret = efi_dp_split_file_path(full_path, &device_path, &file_path); + if (ret != EFI_SUCCESS) + return NULL; + efi_free_pool(file_path); + + /* Get the EFI object for the partition */ + efiobj = efi_dp_find_obj(device_path, NULL); + efi_free_pool(device_path); + if (!efiobj) + return NULL; + + /* Find the simple file system protocol */ + ret = efi_search_protocol(efiobj, &efi_simple_file_system_protocol_guid, + &handler); + if (ret != EFI_SUCCESS) + return NULL; + + /* Return the simple file system protocol for the partition */ + return handler->protocol_interface; +} + +/** + * efi_fs_exists() - check if a partition bears a file system + * + * @desc: block device descriptor + * @part: partition number + * Return: 1 if a file system exists on the partition + * 0 otherwise + */ +static int efi_fs_exists(struct blk_desc *desc, int part) +{ + if (fs_set_blk_dev_with_part(desc, part)) + return 0; + + if (fs_get_type() == FS_TYPE_ANY) + return 0; + + fs_close(); + + return 1; +} + +/* + * Create a handle for a partition or disk + * + * @parent parent handle + * @dp_parent parent device path + * @if_typename interface name for block device + * @desc internal block device + * @dev_index device index for block device + * @offset offset into disk for simple partitions + * @return disk object + */ +static efi_status_t efi_disk_add_dev( + efi_handle_t parent, + struct efi_device_path *dp_parent, + const char *if_typename, + struct blk_desc *desc, + int dev_index, + lbaint_t offset, + unsigned int part, + struct efi_disk_obj **disk) +{ + struct efi_disk_obj *diskobj; + efi_status_t ret; + + /* Don't add empty devices */ + if (!desc->lba) + return EFI_NOT_READY; + + diskobj = calloc(1, sizeof(*diskobj)); + if (!diskobj) + return EFI_OUT_OF_RESOURCES; + + /* Hook up to the device list */ + efi_add_handle(&diskobj->header); + + /* Fill in object data */ + if (part) { + struct efi_device_path *node = efi_dp_part_node(desc, part); + + diskobj->dp = efi_dp_append_node(dp_parent, node); + efi_free_pool(node); + } else { + diskobj->dp = efi_dp_from_part(desc, part); + } + diskobj->part = part; + ret = efi_add_protocol(&diskobj->header, &efi_block_io_guid, + &diskobj->ops); + if (ret != EFI_SUCCESS) + return ret; + ret = efi_add_protocol(&diskobj->header, &efi_guid_device_path, + diskobj->dp); + if (ret != EFI_SUCCESS) + return ret; + /* partitions or whole disk without partitions */ + if ((part || desc->part_type == PART_TYPE_UNKNOWN) && + efi_fs_exists(desc, part)) { + diskobj->volume = efi_simple_file_system(desc, part, + diskobj->dp); + ret = efi_add_protocol(&diskobj->header, + &efi_simple_file_system_protocol_guid, + diskobj->volume); + if (ret != EFI_SUCCESS) + return ret; + } + diskobj->ops = block_io_disk_template; + diskobj->ifname = if_typename; + diskobj->dev_index = dev_index; + diskobj->offset = offset; + diskobj->desc = desc; + + /* Fill in EFI IO Media info (for read/write callbacks) */ + diskobj->media.removable_media = desc->removable; + diskobj->media.media_present = 1; + /* + * MediaID is just an arbitrary counter. + * We have to change it if the medium is removed or changed. + */ + diskobj->media.media_id = 1; + diskobj->media.block_size = desc->blksz; + diskobj->media.io_align = desc->blksz; + diskobj->media.last_block = desc->lba - offset; + if (part != 0) + diskobj->media.logical_partition = 1; + diskobj->ops.media = &diskobj->media; + if (disk) + *disk = diskobj; + return EFI_SUCCESS; +} + +/* + * Create handles and protocols for the partitions of a block device + * + * @parent handle of the parent disk + * @blk_desc block device + * @if_typename interface type + * @diskid device number + * @pdevname device name + * @return number of partitions created + */ +int efi_disk_create_partitions(efi_handle_t parent, struct blk_desc *desc, + const char *if_typename, int diskid, + const char *pdevname) +{ + int disks = 0; + char devname[32] = { 0 }; /* dp->str is u16[32] long */ + disk_partition_t info; + int part; + struct efi_device_path *dp = NULL; + efi_status_t ret; + struct efi_handler *handler; + + /* Get the device path of the parent */ + ret = efi_search_protocol(parent, &efi_guid_device_path, &handler); + if (ret == EFI_SUCCESS) + dp = handler->protocol_interface; + + /* Add devices for each partition */ + for (part = 1; part <= MAX_SEARCH_PARTITIONS; part++) { + if (part_get_info(desc, part, &info)) + continue; + snprintf(devname, sizeof(devname), "%s:%d", pdevname, + part); + ret = efi_disk_add_dev(parent, dp, if_typename, desc, diskid, + info.start, part, NULL); + if (ret != EFI_SUCCESS) { + printf("Adding partition %s failed\n", pdevname); + continue; + } + disks++; + } + + return disks; +} + +/* + * U-Boot doesn't have a list of all online disk devices. So when running our + * EFI payload, we scan through all of the potentially available ones and + * store them in our object pool. + * + * TODO(sjg@chromium.org): Actually with CONFIG_BLK, U-Boot does have this. + * Consider converting the code to look up devices as needed. The EFI device + * could be a child of the UCLASS_BLK block device, perhaps. + * + * This gets called from do_bootefi_exec(). + */ +efi_status_t efi_disk_register(void) +{ + struct efi_disk_obj *disk; + int disks = 0; + efi_status_t ret; +#ifdef CONFIG_BLK + struct udevice *dev; + + for (uclass_first_device_check(UCLASS_BLK, &dev); dev; + uclass_next_device_check(&dev)) { + struct blk_desc *desc = dev_get_uclass_platdata(dev); + const char *if_typename = blk_get_if_type_name(desc->if_type); + + /* Add block device for the full device */ + printf("Scanning disk %s...\n", dev->name); + ret = efi_disk_add_dev(NULL, NULL, if_typename, + desc, desc->devnum, 0, 0, &disk); + if (ret == EFI_NOT_READY) { + printf("Disk %s not ready\n", dev->name); + continue; + } + if (ret) { + printf("ERROR: failure to add disk device %s, r = %lu\n", + dev->name, ret & ~EFI_ERROR_MASK); + return ret; + } + disks++; + + /* Partitions show up as block devices in EFI */ + disks += efi_disk_create_partitions( + &disk->header, desc, if_typename, + desc->devnum, dev->name); + } +#else + int i, if_type; + + /* Search for all available disk devices */ + for (if_type = 0; if_type < IF_TYPE_COUNT; if_type++) { + const struct blk_driver *cur_drvr; + const char *if_typename; + + cur_drvr = blk_driver_lookup_type(if_type); + if (!cur_drvr) + continue; + + if_typename = cur_drvr->if_typename; + printf("Scanning disks on %s...\n", if_typename); + for (i = 0; i < 4; i++) { + struct blk_desc *desc; + char devname[32] = { 0 }; /* dp->str is u16[32] long */ + + desc = blk_get_devnum_by_type(if_type, i); + if (!desc) + continue; + if (desc->type == DEV_TYPE_UNKNOWN) + continue; + + snprintf(devname, sizeof(devname), "%s%d", + if_typename, i); + + /* Add block device for the full device */ + ret = efi_disk_add_dev(NULL, NULL, if_typename, desc, + i, 0, 0, &disk); + if (ret == EFI_NOT_READY) { + printf("Disk %s not ready\n", devname); + continue; + } + if (ret) { + printf("ERROR: failure to add disk device %s, r = %lu\n", + devname, ret & ~EFI_ERROR_MASK); + return ret; + } + disks++; + + /* Partitions show up as block devices in EFI */ + disks += efi_disk_create_partitions + (&disk->header, desc, + if_typename, i, devname); + } + } +#endif + printf("Found %d disks\n", disks); + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c new file mode 100644 index 00000000..6d3f680e --- /dev/null +++ b/lib/efi_loader/efi_file.c @@ -0,0 +1,883 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI_FILE_PROTOCOL + * + * Copyright (c) 2017 Rob Clark + */ + +#include <common.h> +#include <charset.h> +#include <efi_loader.h> +#include <malloc.h> +#include <mapmem.h> +#include <fs.h> + +/* GUID for file system information */ +const efi_guid_t efi_file_system_info_guid = EFI_FILE_SYSTEM_INFO_GUID; + +/* GUID to obtain the volume label */ +const efi_guid_t efi_system_volume_label_id = EFI_FILE_SYSTEM_VOLUME_LABEL_ID; + +struct file_system { + struct efi_simple_file_system_protocol base; + struct efi_device_path *dp; + struct blk_desc *desc; + int part; +}; +#define to_fs(x) container_of(x, struct file_system, base) + +struct file_handle { + struct efi_file_handle base; + struct file_system *fs; + loff_t offset; /* current file position/cursor */ + int isdir; + u64 open_mode; + + /* for reading a directory: */ + struct fs_dir_stream *dirs; + struct fs_dirent *dent; + + char path[0]; +}; +#define to_fh(x) container_of(x, struct file_handle, base) + +static const struct efi_file_handle efi_file_handle_protocol; + +static char *basename(struct file_handle *fh) +{ + char *s = strrchr(fh->path, '/'); + if (s) + return s + 1; + return fh->path; +} + +static int set_blk_dev(struct file_handle *fh) +{ + return fs_set_blk_dev_with_part(fh->fs->desc, fh->fs->part); +} + +/** + * is_dir() - check if file handle points to directory + * + * We assume that set_blk_dev(fh) has been called already. + * + * @fh: file handle + * Return: true if file handle points to a directory + */ +static int is_dir(struct file_handle *fh) +{ + struct fs_dir_stream *dirs; + + dirs = fs_opendir(fh->path); + if (!dirs) + return 0; + + fs_closedir(dirs); + + return 1; +} + +/* + * Normalize a path which may include either back or fwd slashes, + * double slashes, . or .. entries in the path, etc. + */ +static int sanitize_path(char *path) +{ + char *p; + + /* backslash to slash: */ + p = path; + while ((p = strchr(p, '\\'))) + *p++ = '/'; + + /* handle double-slashes: */ + p = path; + while ((p = strstr(p, "//"))) { + char *src = p + 1; + memmove(p, src, strlen(src) + 1); + } + + /* handle extra /.'s */ + p = path; + while ((p = strstr(p, "/."))) { + /* + * You'd be tempted to do this *after* handling ".."s + * below to avoid having to check if "/." is start of + * a "/..", but that won't have the correct results.. + * for example, "/foo/./../bar" would get resolved to + * "/foo/bar" if you did these two passes in the other + * order + */ + if (p[2] == '.') { + p += 2; + continue; + } + char *src = p + 2; + memmove(p, src, strlen(src) + 1); + } + + /* handle extra /..'s: */ + p = path; + while ((p = strstr(p, "/.."))) { + char *src = p + 3; + + p--; + + /* find beginning of previous path entry: */ + while (true) { + if (p < path) + return -1; + if (*p == '/') + break; + p--; + } + + memmove(p, src, strlen(src) + 1); + } + + return 0; +} + +/** + * efi_create_file() - create file or directory + * + * @fh: file handle + * @attributes: attributes for newly created file + * Returns: 0 for success + */ +static int efi_create_file(struct file_handle *fh, u64 attributes) +{ + loff_t actwrite; + void *buffer = &actwrite; + + if (attributes & EFI_FILE_DIRECTORY) + return fs_mkdir(fh->path); + else + return fs_write(fh->path, map_to_sysmem(buffer), 0, 0, + &actwrite); +} + +/** + * file_open() - open a file handle + * + * @fs: file system + * @parent: directory relative to which the file is to be opened + * @file_name: path of the file to be opened. '\', '.', or '..' may + * be used as modifiers. A leading backslash indicates an + * absolute path. + * @open_mode: bit mask indicating the access mode (read, write, + * create) + * @attributes: attributes for newly created file + * Returns: handle to the opened file or NULL + */ +static struct efi_file_handle *file_open(struct file_system *fs, + struct file_handle *parent, u16 *file_name, u64 open_mode, + u64 attributes) +{ + struct file_handle *fh; + char f0[MAX_UTF8_PER_UTF16] = {0}; + int plen = 0; + int flen = 0; + + if (file_name) { + utf16_to_utf8((u8 *)f0, file_name, 1); + flen = u16_strlen(file_name); + } + + /* we could have a parent, but also an absolute path: */ + if (f0[0] == '\\') { + plen = 0; + } else if (parent) { + plen = strlen(parent->path) + 1; + } + + /* +2 is for null and '/' */ + fh = calloc(1, sizeof(*fh) + plen + (flen * MAX_UTF8_PER_UTF16) + 2); + + fh->open_mode = open_mode; + fh->base = efi_file_handle_protocol; + fh->fs = fs; + + if (parent) { + char *p = fh->path; + int exists; + + if (plen > 0) { + strcpy(p, parent->path); + p += plen - 1; + *p++ = '/'; + } + + utf16_to_utf8((u8 *)p, file_name, flen); + + if (sanitize_path(fh->path)) + goto error; + + /* check if file exists: */ + if (set_blk_dev(fh)) + goto error; + + exists = fs_exists(fh->path); + /* fs_exists() calls fs_close(), so open file system again */ + if (set_blk_dev(fh)) + goto error; + + if (!exists) { + if (!(open_mode & EFI_FILE_MODE_CREATE) || + efi_create_file(fh, attributes)) + goto error; + if (set_blk_dev(fh)) + goto error; + } + + /* figure out if file is a directory: */ + fh->isdir = is_dir(fh); + } else { + fh->isdir = 1; + strcpy(fh->path, ""); + } + + return &fh->base; + +error: + free(fh); + return NULL; +} + +static efi_status_t EFIAPI efi_file_open(struct efi_file_handle *file, + struct efi_file_handle **new_handle, + u16 *file_name, u64 open_mode, u64 attributes) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret; + + EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu", file, new_handle, + file_name, open_mode, attributes); + + /* Check parameters */ + if (!file || !new_handle || !file_name) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (open_mode != EFI_FILE_MODE_READ && + open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE) && + open_mode != (EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | + EFI_FILE_MODE_CREATE)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + /* + * The UEFI spec requires that attributes are only set in create mode. + * The SCT does not care about this and sets EFI_FILE_DIRECTORY in + * read mode. EDK2 does not check that attributes are zero if not in + * create mode. + * + * So here we only check attributes in create mode and do not check + * that they are zero otherwise. + */ + if ((open_mode & EFI_FILE_MODE_CREATE) && + (attributes & (EFI_FILE_READ_ONLY | ~EFI_FILE_VALID_ATTR))) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* Open file */ + *new_handle = file_open(fh->fs, fh, file_name, open_mode, attributes); + if (*new_handle) { + EFI_PRINT("file handle %p\n", *new_handle); + ret = EFI_SUCCESS; + } else { + ret = EFI_NOT_FOUND; + } +out: + return EFI_EXIT(ret); +} + +static efi_status_t file_close(struct file_handle *fh) +{ + fs_closedir(fh->dirs); + free(fh); + return EFI_SUCCESS; +} + +static efi_status_t EFIAPI efi_file_close(struct efi_file_handle *file) +{ + struct file_handle *fh = to_fh(file); + EFI_ENTRY("%p", file); + return EFI_EXIT(file_close(fh)); +} + +static efi_status_t EFIAPI efi_file_delete(struct efi_file_handle *file) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p", file); + + if (set_blk_dev(fh) || fs_unlink(fh->path)) + ret = EFI_WARN_DELETE_FAILURE; + + file_close(fh); + return EFI_EXIT(ret); +} + +/** + * efi_get_file_size() - determine the size of a file + * + * @fh: file handle + * @file_size: pointer to receive file size + * Return: status code + */ +static efi_status_t efi_get_file_size(struct file_handle *fh, + loff_t *file_size) +{ + if (set_blk_dev(fh)) + return EFI_DEVICE_ERROR; + + if (fs_size(fh->path, file_size)) + return EFI_DEVICE_ERROR; + + return EFI_SUCCESS; +} + +static efi_status_t file_read(struct file_handle *fh, u64 *buffer_size, + void *buffer) +{ + loff_t actread; + efi_status_t ret; + loff_t file_size; + + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) + return ret; + if (file_size < fh->offset) { + ret = EFI_DEVICE_ERROR; + return ret; + } + + if (set_blk_dev(fh)) + return EFI_DEVICE_ERROR; + if (fs_read(fh->path, map_to_sysmem(buffer), fh->offset, + *buffer_size, &actread)) + return EFI_DEVICE_ERROR; + + *buffer_size = actread; + fh->offset += actread; + + return EFI_SUCCESS; +} + +static efi_status_t dir_read(struct file_handle *fh, u64 *buffer_size, + void *buffer) +{ + struct efi_file_info *info = buffer; + struct fs_dirent *dent; + u64 required_size; + u16 *dst; + + if (set_blk_dev(fh)) + return EFI_DEVICE_ERROR; + + if (!fh->dirs) { + assert(fh->offset == 0); + fh->dirs = fs_opendir(fh->path); + if (!fh->dirs) + return EFI_DEVICE_ERROR; + fh->dent = NULL; + } + + /* + * So this is a bit awkward. Since fs layer is stateful and we + * can't rewind an entry, in the EFI_BUFFER_TOO_SMALL case below + * we might have to return without consuming the dent.. so we + * have to stash it for next call. + */ + if (fh->dent) { + dent = fh->dent; + } else { + dent = fs_readdir(fh->dirs); + } + + if (!dent) { + /* no more files in directory */ + *buffer_size = 0; + return EFI_SUCCESS; + } + + /* check buffer size: */ + required_size = sizeof(*info) + + 2 * (utf8_utf16_strlen(dent->name) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + fh->dent = dent; + return EFI_BUFFER_TOO_SMALL; + } + fh->dent = NULL; + + *buffer_size = required_size; + memset(info, 0, required_size); + + info->size = required_size; + info->file_size = dent->size; + info->physical_size = dent->size; + + if (dent->type == FS_DT_DIR) + info->attribute |= EFI_FILE_DIRECTORY; + + dst = info->file_name; + utf8_utf16_strcpy(&dst, dent->name); + + fh->offset++; + + return EFI_SUCCESS; +} + +static efi_status_t EFIAPI efi_file_read(struct efi_file_handle *file, + efi_uintn_t *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + u64 bs; + + EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer); + + if (!buffer_size || !buffer) { + ret = EFI_INVALID_PARAMETER; + goto error; + } + + bs = *buffer_size; + if (fh->isdir) + ret = dir_read(fh, &bs, buffer); + else + ret = file_read(fh, &bs, buffer); + if (bs <= SIZE_MAX) + *buffer_size = bs; + else + *buffer_size = SIZE_MAX; + +error: + return EFI_EXIT(ret); +} + +/** + * efi_file_write() - write to file + * + * This function implements the Write() service of the EFI_FILE_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @file: file handle + * @buffer_size: number of bytes to write + * @buffer: buffer with the bytes to write + * Return: status code + */ +static efi_status_t EFIAPI efi_file_write(struct efi_file_handle *file, + efi_uintn_t *buffer_size, + void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + loff_t actwrite; + + EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer); + + if (!file || !buffer_size || !buffer) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (fh->isdir) { + ret = EFI_UNSUPPORTED; + goto out; + } + if (!(fh->open_mode & EFI_FILE_MODE_WRITE)) { + ret = EFI_ACCESS_DENIED; + goto out; + } + + if (!*buffer_size) + goto out; + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + if (fs_write(fh->path, map_to_sysmem(buffer), fh->offset, *buffer_size, + &actwrite)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + *buffer_size = actwrite; + fh->offset += actwrite; + +out: + return EFI_EXIT(ret); +} + +/** + * efi_file_getpos() - get current position in file + * + * This function implements the GetPosition service of the EFI file protocol. + * See the UEFI spec for details. + * + * @file: file handle + * @pos: pointer to file position + * Return: status code + */ +static efi_status_t EFIAPI efi_file_getpos(struct efi_file_handle *file, + u64 *pos) +{ + efi_status_t ret = EFI_SUCCESS; + struct file_handle *fh = to_fh(file); + + EFI_ENTRY("%p, %p", file, pos); + + if (fh->isdir) { + ret = EFI_UNSUPPORTED; + goto out; + } + + *pos = fh->offset; +out: + return EFI_EXIT(ret); +} + +/** + * efi_file_setpos() - set current position in file + * + * This function implements the SetPosition service of the EFI file protocol. + * See the UEFI spec for details. + * + * @file: file handle + * @pos: new file position + * Return: status code + */ +static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file, + u64 pos) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %llu", file, pos); + + if (fh->isdir) { + if (pos != 0) { + ret = EFI_UNSUPPORTED; + goto error; + } + fs_closedir(fh->dirs); + fh->dirs = NULL; + } + + if (pos == ~0ULL) { + loff_t file_size; + + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) + goto error; + pos = file_size; + } + + fh->offset = pos; + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_getinfo(struct efi_file_handle *file, + const efi_guid_t *info_type, + efi_uintn_t *buffer_size, + void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + u16 *dst; + + EFI_ENTRY("%p, %pUl, %p, %p", file, info_type, buffer_size, buffer); + + if (!file || !info_type || !buffer_size || + (*buffer_size && !buffer)) { + ret = EFI_INVALID_PARAMETER; + goto error; + } + + if (!guidcmp(info_type, &efi_file_info_guid)) { + struct efi_file_info *info = buffer; + char *filename = basename(fh); + unsigned int required_size; + loff_t file_size; + + /* check buffer size: */ + required_size = sizeof(*info) + + 2 * (utf8_utf16_strlen(filename) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) + goto error; + + memset(info, 0, required_size); + + info->size = required_size; + info->file_size = file_size; + info->physical_size = file_size; + + if (fh->isdir) + info->attribute |= EFI_FILE_DIRECTORY; + + dst = info->file_name; + utf8_utf16_strcpy(&dst, filename); + } else if (!guidcmp(info_type, &efi_file_system_info_guid)) { + struct efi_file_system_info *info = buffer; + disk_partition_t part; + efi_uintn_t required_size; + int r; + + if (fh->fs->part >= 1) + r = part_get_info(fh->fs->desc, fh->fs->part, &part); + else + r = part_get_info_whole_disk(fh->fs->desc, &part); + if (r < 0) { + ret = EFI_DEVICE_ERROR; + goto error; + } + required_size = sizeof(*info) + 2; + if (*buffer_size < required_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + + memset(info, 0, required_size); + + info->size = required_size; + info->read_only = true; + info->volume_size = part.size * part.blksz; + info->free_space = 0; + info->block_size = part.blksz; + /* + * TODO: The volume label is not available in U-Boot. + */ + info->volume_label[0] = 0; + } else if (!guidcmp(info_type, &efi_system_volume_label_id)) { + if (*buffer_size < 2) { + *buffer_size = 2; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + *(u16 *)buffer = 0; + } else { + ret = EFI_UNSUPPORTED; + } + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file, + const efi_guid_t *info_type, + efi_uintn_t buffer_size, + void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_UNSUPPORTED; + + EFI_ENTRY("%p, %pUl, %zu, %p", file, info_type, buffer_size, buffer); + + if (!guidcmp(info_type, &efi_file_info_guid)) { + struct efi_file_info *info = (struct efi_file_info *)buffer; + char *filename = basename(fh); + char *new_file_name, *pos; + loff_t file_size; + + /* The buffer will always contain a file name. */ + if (buffer_size < sizeof(struct efi_file_info) + 2 || + buffer_size < info->size) { + ret = EFI_BAD_BUFFER_SIZE; + goto out; + } + /* We cannot change the directory attribute */ + if (!fh->isdir != !(info->attribute & EFI_FILE_DIRECTORY)) { + ret = EFI_ACCESS_DENIED; + goto out; + } + /* Check for renaming */ + new_file_name = malloc(utf16_utf8_strlen(info->file_name)); + if (!new_file_name) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + pos = new_file_name; + utf16_utf8_strcpy(&pos, info->file_name); + if (strcmp(new_file_name, filename)) { + /* TODO: we do not support renaming */ + EFI_PRINT("Renaming not supported\n"); + free(new_file_name); + ret = EFI_ACCESS_DENIED; + goto out; + } + free(new_file_name); + /* Check for truncation */ + ret = efi_get_file_size(fh, &file_size); + if (ret != EFI_SUCCESS) + goto out; + if (file_size != info->file_size) { + /* TODO: we do not support truncation */ + EFI_PRINT("Truncation not supported\n"); + ret = EFI_ACCESS_DENIED; + goto out; + } + /* + * We do not care for the other attributes + * TODO: Support read only + */ + ret = EFI_SUCCESS; + } else { + /* TODO: We do not support changing the volume label */ + ret = EFI_UNSUPPORTED; + } +out: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_flush(struct efi_file_handle *file) +{ + EFI_ENTRY("%p", file); + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_file_open_ex(struct efi_file_handle *file, + struct efi_file_handle **new_handle, + u16 *file_name, u64 open_mode, u64 attributes, + struct efi_file_io_token *token) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_file_read_ex(struct efi_file_handle *file, + struct efi_file_io_token *token) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_file_write_ex(struct efi_file_handle *file, + struct efi_file_io_token *token) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_file_flush_ex(struct efi_file_handle *file, + struct efi_file_io_token *token) +{ + return EFI_UNSUPPORTED; +} + +static const struct efi_file_handle efi_file_handle_protocol = { + .rev = EFI_FILE_PROTOCOL_REVISION2, + .open = efi_file_open, + .close = efi_file_close, + .delete = efi_file_delete, + .read = efi_file_read, + .write = efi_file_write, + .getpos = efi_file_getpos, + .setpos = efi_file_setpos, + .getinfo = efi_file_getinfo, + .setinfo = efi_file_setinfo, + .flush = efi_file_flush, + .open_ex = efi_file_open_ex, + .read_ex = efi_file_read_ex, + .write_ex = efi_file_write_ex, + .flush_ex = efi_file_flush_ex, +}; + +/** + * efi_file_from_path() - open file via device path + * + * @fp: device path + * @return: EFI_FILE_PROTOCOL for the file or NULL + */ +struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp) +{ + struct efi_simple_file_system_protocol *v; + struct efi_file_handle *f; + efi_status_t ret; + + v = efi_fs_from_path(fp); + if (!v) + return NULL; + + EFI_CALL(ret = v->open_volume(v, &f)); + if (ret != EFI_SUCCESS) + return NULL; + + /* Skip over device-path nodes before the file path. */ + while (fp && !EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) + fp = efi_dp_next(fp); + + /* + * Step through the nodes of the directory path until the actual file + * node is reached which is the final node in the device path. + */ + while (fp) { + struct efi_device_path_file_path *fdp = + container_of(fp, struct efi_device_path_file_path, dp); + struct efi_file_handle *f2; + u16 *filename; + + if (!EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) { + printf("bad file path!\n"); + f->close(f); + return NULL; + } + + filename = u16_strdup(fdp->str); + if (!filename) + return NULL; + EFI_CALL(ret = f->open(f, &f2, filename, + EFI_FILE_MODE_READ, 0)); + free(filename); + if (ret != EFI_SUCCESS) + return NULL; + + fp = efi_dp_next(fp); + + EFI_CALL(f->close(f)); + f = f2; + } + + return f; +} + +static efi_status_t EFIAPI +efi_open_volume(struct efi_simple_file_system_protocol *this, + struct efi_file_handle **root) +{ + struct file_system *fs = to_fs(this); + + EFI_ENTRY("%p, %p", this, root); + + *root = file_open(fs, NULL, NULL, 0, 0); + + return EFI_EXIT(EFI_SUCCESS); +} + +struct efi_simple_file_system_protocol * +efi_simple_file_system(struct blk_desc *desc, int part, + struct efi_device_path *dp) +{ + struct file_system *fs; + + fs = calloc(1, sizeof(*fs)); + fs->base.rev = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION; + fs->base.open_volume = efi_open_volume; + fs->desc = desc; + fs->part = part; + fs->dp = dp; + + return &fs->base; +} diff --git a/lib/efi_loader/efi_freestanding.c b/lib/efi_loader/efi_freestanding.c new file mode 100644 index 00000000..bd9da5bb --- /dev/null +++ b/lib/efi_loader/efi_freestanding.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Library for freestanding binary + * + * Copyright 2019, Heinrich Schuchardt <xypron.glpk@gmx.de> + * + * GCC requires that freestanding programs provide memcpy(), memmove(), + * memset(), and memcmp(). + */ + +#include <common.h> + +/** + * memcmp() - compare memory areas + * + * @s1: pointer to first area + * @s2: pointer to second area + * @n: number of bytes to compare + * Return: 0 if both memory areas are the same, otherwise the sign of the + * result value is the same as the sign of the difference between + * the first differing pair of bytes taken as u8. + */ +int memcmp(const void *s1, const void *s2, size_t n) +{ + const u8 *pos1 = s1; + const u8 *pos2 = s2; + + for (; n; --n) { + if (*pos1 != *pos2) + return *pos1 - *pos2; + ++pos1; + ++pos2; + } + return 0; +} + +/** + * memcpy() - copy memory area + * + * @dest: destination buffer + * @src: source buffer + * @n: number of bytes to copy + * Return: pointer to destination buffer + */ +void *memmove(void *dest, const void *src, size_t n) +{ + u8 *d = dest; + const u8 *s = src; + + if (d >= s) { + for (; n; --n) + *d++ = *s++; + } else { + d += n; + s += n; + for (; n; --n) + *--d = *--s; + } + return dest; +} + +/** + * memcpy() - copy memory area + * + * @dest: destination buffer + * @src: source buffer + * @n: number of bytes to copy + * Return: pointer to destination buffer + */ +void *memcpy(void *dest, const void *src, size_t n) +{ + return memmove(dest, src, n); +} + +/** + * memset() - fill memory with a constant byte + * + * @s: destination buffer + * @c: byte value + * @n: number of bytes to set + * Return: pointer to destination buffer + */ +void *memset(void *s, int c, size_t n) +{ + u8 *d = s; + + for (; n; --n) + *d++ = c; + return s; +} diff --git a/lib/efi_loader/efi_gop.c b/lib/efi_loader/efi_gop.c new file mode 100644 index 00000000..1511e3bd --- /dev/null +++ b/lib/efi_loader/efi_gop.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application disk support + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <dm.h> +#include <efi_loader.h> +#include <lcd.h> +#include <malloc.h> +#include <video.h> + +DECLARE_GLOBAL_DATA_PTR; + +static const efi_guid_t efi_gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; + +/** + * struct efi_gop_obj - graphical output protocol object + * + * @header: EFI object header + * @ops: graphical output protocol interface + * @info: graphical output mode information + * @mode: graphical output mode + * @bpix: bits per pixel + * @fb: frame buffer + */ +struct efi_gop_obj { + struct efi_object header; + struct efi_gop ops; + struct efi_gop_mode_info info; + struct efi_gop_mode mode; + /* Fields we only have access to during init */ + u32 bpix; + void *fb; +}; + +static efi_status_t EFIAPI gop_query_mode(struct efi_gop *this, u32 mode_number, + efi_uintn_t *size_of_info, + struct efi_gop_mode_info **info) +{ + struct efi_gop_obj *gopobj; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x, %p, %p", this, mode_number, size_of_info, info); + + if (!this || !size_of_info || !info || mode_number) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + gopobj = container_of(this, struct efi_gop_obj, ops); + ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, sizeof(gopobj->info), + (void **)info); + if (ret != EFI_SUCCESS) + goto out; + *size_of_info = sizeof(gopobj->info); + memcpy(*info, &gopobj->info, sizeof(gopobj->info)); + +out: + return EFI_EXIT(ret); +} + +static __always_inline struct efi_gop_pixel efi_vid16_to_blt_col(u16 vid) +{ + struct efi_gop_pixel blt = { + .reserved = 0, + }; + + blt.blue = (vid & 0x1f) << 3; + vid >>= 5; + blt.green = (vid & 0x3f) << 2; + vid >>= 6; + blt.red = (vid & 0x1f) << 3; + return blt; +} + +static __always_inline u16 efi_blt_col_to_vid16(struct efi_gop_pixel *blt) +{ + return (u16)(blt->red >> 3) << 11 | + (u16)(blt->green >> 2) << 5 | + (u16)(blt->blue >> 3); +} + +static __always_inline efi_status_t gop_blt_int(struct efi_gop *this, + struct efi_gop_pixel *bufferp, + u32 operation, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, + efi_uintn_t width, + efi_uintn_t height, + efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops); + efi_uintn_t i, j, linelen, slineoff = 0, dlineoff, swidth, dwidth; + u32 *fb32 = gopobj->fb; + u16 *fb16 = gopobj->fb; + struct efi_gop_pixel *buffer = __builtin_assume_aligned(bufferp, 4); + + if (delta) { + /* Check for 4 byte alignment */ + if (delta & 3) + return EFI_INVALID_PARAMETER; + linelen = delta >> 2; + } else { + linelen = width; + } + + /* Check source rectangle */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + break; + case EFI_BLT_BUFFER_TO_VIDEO: + if (sx + width > linelen) + return EFI_INVALID_PARAMETER; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + if (sx + width > gopobj->info.width || + sy + height > gopobj->info.height) + return EFI_INVALID_PARAMETER; + break; + default: + return EFI_INVALID_PARAMETER; + } + + /* Check destination rectangle */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_TO_VIDEO: + if (dx + width > gopobj->info.width || + dy + height > gopobj->info.height) + return EFI_INVALID_PARAMETER; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + if (dx + width > linelen) + return EFI_INVALID_PARAMETER; + break; + } + + /* Calculate line width */ + switch (operation) { + case EFI_BLT_BUFFER_TO_VIDEO: + swidth = linelen; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + swidth = gopobj->info.width; + if (!vid_bpp) + return EFI_UNSUPPORTED; + break; + case EFI_BLT_VIDEO_FILL: + swidth = 0; + break; + } + + switch (operation) { + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_VIDEO_TO_VIDEO: + dwidth = gopobj->info.width; + if (!vid_bpp) + return EFI_UNSUPPORTED; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + dwidth = linelen; + break; + } + + slineoff = swidth * sy; + dlineoff = dwidth * dy; + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { + struct efi_gop_pixel pix; + + /* Read source pixel */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + pix = *buffer; + break; + case EFI_BLT_BUFFER_TO_VIDEO: + pix = buffer[slineoff + j + sx]; + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + case EFI_BLT_VIDEO_TO_VIDEO: + if (vid_bpp == 32) + pix = *(struct efi_gop_pixel *)&fb32[ + slineoff + j + sx]; + else + pix = efi_vid16_to_blt_col(fb16[ + slineoff + j + sx]); + break; + } + + /* Write destination pixel */ + switch (operation) { + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + buffer[dlineoff + j + dx] = pix; + break; + case EFI_BLT_BUFFER_TO_VIDEO: + case EFI_BLT_VIDEO_FILL: + case EFI_BLT_VIDEO_TO_VIDEO: + if (vid_bpp == 32) + fb32[dlineoff + j + dx] = *(u32 *)&pix; + else + fb16[dlineoff + j + dx] = + efi_blt_col_to_vid16(&pix); + break; + } + } + slineoff += swidth; + dlineoff += dwidth; + } + + return EFI_SUCCESS; +} + +static efi_uintn_t gop_get_bpp(struct efi_gop *this) +{ + struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops); + efi_uintn_t vid_bpp = 0; + + switch (gopobj->bpix) { +#ifdef CONFIG_DM_VIDEO + case VIDEO_BPP32: +#else + case LCD_COLOR32: +#endif + vid_bpp = 32; + break; +#ifdef CONFIG_DM_VIDEO + case VIDEO_BPP16: +#else + case LCD_COLOR16: +#endif + vid_bpp = 16; + break; + } + + return vid_bpp; +} + +/* + * GCC can't optimize our BLT function well, but we need to make sure that + * our 2-dimensional loop gets executed very quickly, otherwise the system + * will feel slow. + * + * By manually putting all obvious branch targets into functions which call + * our generic BLT function with constants, the compiler can successfully + * optimize for speed. + */ +static efi_status_t gop_blt_video_fill(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_FILL, sx, sy, dx, + dy, width, height, delta, vid_bpp); +} + +static efi_status_t gop_blt_buf_to_vid16(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, 16); +} + +static efi_status_t gop_blt_buf_to_vid32(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, 32); +} + +static efi_status_t gop_blt_vid_to_vid(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_VIDEO, sx, sy, dx, + dy, width, height, delta, vid_bpp); +} + +static efi_status_t gop_blt_vid_to_buf(struct efi_gop *this, + struct efi_gop_pixel *buffer, + u32 foo, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta, + efi_uintn_t vid_bpp) +{ + return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_BLT_BUFFER, sx, sy, + dx, dy, width, height, delta, vid_bpp); +} + +/** + * gop_set_mode() - set graphical output mode + * + * This function implements the SetMode() service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: the graphical output protocol + * @mode_number: the mode to be set + * Return: status code + */ +static efi_status_t EFIAPI gop_set_mode(struct efi_gop *this, u32 mode_number) +{ + struct efi_gop_obj *gopobj; + struct efi_gop_pixel buffer = {0, 0, 0, 0}; + efi_uintn_t vid_bpp; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x", this, mode_number); + + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (mode_number) { + ret = EFI_UNSUPPORTED; + goto out; + } + gopobj = container_of(this, struct efi_gop_obj, ops); + vid_bpp = gop_get_bpp(this); + ret = gop_blt_video_fill(this, &buffer, EFI_BLT_VIDEO_FILL, 0, 0, 0, 0, + gopobj->info.width, gopobj->info.height, 0, + vid_bpp); +out: + return EFI_EXIT(ret); +} + +/* + * Copy rectangle. + * + * This function implements the Blt service of the EFI_GRAPHICS_OUTPUT_PROTOCOL. + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @this: EFI_GRAPHICS_OUTPUT_PROTOCOL + * @buffer: pixel buffer + * @sx: source x-coordinate + * @sy: source y-coordinate + * @dx: destination x-coordinate + * @dy: destination y-coordinate + * @width: width of rectangle + * @height: height of rectangle + * @delta: length in bytes of a line in the pixel buffer (optional) + * @return: status code + */ +efi_status_t EFIAPI gop_blt(struct efi_gop *this, struct efi_gop_pixel *buffer, + u32 operation, efi_uintn_t sx, + efi_uintn_t sy, efi_uintn_t dx, + efi_uintn_t dy, efi_uintn_t width, + efi_uintn_t height, efi_uintn_t delta) +{ + efi_status_t ret = EFI_INVALID_PARAMETER; + efi_uintn_t vid_bpp; + + EFI_ENTRY("%p, %p, %u, %zu, %zu, %zu, %zu, %zu, %zu, %zu", this, + buffer, operation, sx, sy, dx, dy, width, height, delta); + + vid_bpp = gop_get_bpp(this); + + /* Allow for compiler optimization */ + switch (operation) { + case EFI_BLT_VIDEO_FILL: + ret = gop_blt_video_fill(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + case EFI_BLT_BUFFER_TO_VIDEO: + /* This needs to be super-fast, so duplicate for 16/32bpp */ + if (vid_bpp == 32) + ret = gop_blt_buf_to_vid32(this, buffer, operation, sx, + sy, dx, dy, width, height, + delta); + else + ret = gop_blt_buf_to_vid16(this, buffer, operation, sx, + sy, dx, dy, width, height, + delta); + break; + case EFI_BLT_VIDEO_TO_VIDEO: + ret = gop_blt_vid_to_vid(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + case EFI_BLT_VIDEO_TO_BLT_BUFFER: + ret = gop_blt_vid_to_buf(this, buffer, operation, sx, sy, dx, + dy, width, height, delta, vid_bpp); + break; + default: + ret = EFI_INVALID_PARAMETER; + } + + if (ret != EFI_SUCCESS) + return EFI_EXIT(ret); + +#ifdef CONFIG_DM_VIDEO + video_sync_all(); +#else + lcd_sync(); +#endif + + return EFI_EXIT(EFI_SUCCESS); +} + +/* + * Install graphical output protocol. + * + * If no supported video device exists this is not considered as an + * error. + */ +efi_status_t efi_gop_register(void) +{ + struct efi_gop_obj *gopobj; + u32 bpix, col, row; + u64 fb_base, fb_size; + void *fb; + efi_status_t ret; + +#ifdef CONFIG_DM_VIDEO + struct udevice *vdev; + struct video_priv *priv; + + /* We only support a single video output device for now */ + if (uclass_first_device(UCLASS_VIDEO, &vdev) || !vdev) { + debug("WARNING: No video device\n"); + return EFI_SUCCESS; + } + + priv = dev_get_uclass_priv(vdev); + bpix = priv->bpix; + col = video_get_xsize(vdev); + row = video_get_ysize(vdev); + fb_base = (uintptr_t)priv->fb; + fb_size = priv->fb_size; + fb = priv->fb; +#else + int line_len; + + bpix = panel_info.vl_bpix; + col = panel_info.vl_col; + row = panel_info.vl_row; + fb_base = gd->fb_base; + fb_size = lcd_get_size(&line_len); + fb = (void*)gd->fb_base; +#endif + + switch (bpix) { +#ifdef CONFIG_DM_VIDEO + case VIDEO_BPP16: + case VIDEO_BPP32: +#else + case LCD_COLOR32: + case LCD_COLOR16: +#endif + break; + default: + /* So far, we only work in 16 or 32 bit mode */ + debug("WARNING: Unsupported video mode\n"); + return EFI_SUCCESS; + } + + gopobj = calloc(1, sizeof(*gopobj)); + if (!gopobj) { + printf("ERROR: Out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + /* Hook up to the device list */ + efi_add_handle(&gopobj->header); + + /* Fill in object data */ + ret = efi_add_protocol(&gopobj->header, &efi_gop_guid, + &gopobj->ops); + if (ret != EFI_SUCCESS) { + printf("ERROR: Failure adding GOP protocol\n"); + return ret; + } + gopobj->ops.query_mode = gop_query_mode; + gopobj->ops.set_mode = gop_set_mode; + gopobj->ops.blt = gop_blt; + gopobj->ops.mode = &gopobj->mode; + + gopobj->mode.max_mode = 1; + gopobj->mode.info = &gopobj->info; + gopobj->mode.info_size = sizeof(gopobj->info); + + gopobj->mode.fb_base = fb_base; + gopobj->mode.fb_size = fb_size; + + gopobj->info.version = 0; + gopobj->info.width = col; + gopobj->info.height = row; +#ifdef CONFIG_DM_VIDEO + if (bpix == VIDEO_BPP32) +#else + if (bpix == LCD_COLOR32) +#endif + { + gopobj->info.pixel_format = EFI_GOT_BGRA8; + } else { + gopobj->info.pixel_format = EFI_GOT_BITMASK; + gopobj->info.pixel_bitmask[0] = 0xf800; /* red */ + gopobj->info.pixel_bitmask[1] = 0x07e0; /* green */ + gopobj->info.pixel_bitmask[2] = 0x001f; /* blue */ + } + gopobj->info.pixels_per_scanline = col; + gopobj->bpix = bpix; + gopobj->fb = fb; + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_hii.c b/lib/efi_loader/efi_hii.c new file mode 100644 index 00000000..77e33028 --- /dev/null +++ b/lib/efi_loader/efi_hii.c @@ -0,0 +1,1073 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Human Interface Infrastructure ... database and packages + * + * Copyright (c) 2017 Leif Lindholm + * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + */ + +#include <common.h> +#include <efi_loader.h> +#include <malloc.h> +#include <asm/unaligned.h> + +const efi_guid_t efi_guid_hii_database_protocol + = EFI_HII_DATABASE_PROTOCOL_GUID; +const efi_guid_t efi_guid_hii_string_protocol = EFI_HII_STRING_PROTOCOL_GUID; + +static LIST_HEAD(efi_package_lists); +static LIST_HEAD(efi_keyboard_layout_list); + +struct efi_hii_packagelist { + struct list_head link; + // TODO should there be an associated efi_object? + efi_handle_t driver_handle; + u32 max_string_id; + struct list_head string_tables; /* list of efi_string_table */ + struct list_head guid_list; + struct list_head keyboard_packages; + + /* we could also track fonts, images, etc */ +}; + +static int efi_hii_packagelist_exists(efi_hii_handle_t package_list) +{ + struct efi_hii_packagelist *hii; + int found = 0; + + list_for_each_entry(hii, &efi_package_lists, link) { + if (hii == package_list) { + found = 1; + break; + } + } + + return found; +} + +static u32 efi_hii_package_type(struct efi_hii_package_header *header) +{ + u32 fields; + + fields = get_unaligned_le32(&header->fields); + + return (fields >> __EFI_HII_PACKAGE_TYPE_SHIFT) + & __EFI_HII_PACKAGE_TYPE_MASK; +} + +static u32 efi_hii_package_len(struct efi_hii_package_header *header) +{ + u32 fields; + + fields = get_unaligned_le32(&header->fields); + + return (fields >> __EFI_HII_PACKAGE_LEN_SHIFT) + & __EFI_HII_PACKAGE_LEN_MASK; +} + +struct efi_string_info { + efi_string_t string; + /* we could also track font info, etc */ +}; + +struct efi_string_table { + struct list_head link; + efi_string_id_t language_name; + char *language; + u32 nstrings; + /* + * NOTE: + * string id starts at 1 so value is stbl->strings[id-1], + * and strings[] is a array of stbl->nstrings elements + */ + struct efi_string_info *strings; +}; + +struct efi_guid_data { + struct list_head link; + struct efi_hii_guid_package package; +}; + +struct efi_keyboard_layout_data { + struct list_head link; /* in package */ + struct list_head link_sys; /* in global list */ + struct efi_hii_keyboard_layout keyboard_layout; +}; + +struct efi_keyboard_package_data { + struct list_head link; /* in package_list */ + struct list_head keyboard_layout_list; +}; + +static void free_strings_table(struct efi_string_table *stbl) +{ + int i; + + for (i = 0; i < stbl->nstrings; i++) + free(stbl->strings[i].string); + free(stbl->strings); + free(stbl->language); + free(stbl); +} + +static void remove_strings_package(struct efi_hii_packagelist *hii) +{ + while (!list_empty(&hii->string_tables)) { + struct efi_string_table *stbl; + + stbl = list_first_entry(&hii->string_tables, + struct efi_string_table, link); + list_del(&stbl->link); + free_strings_table(stbl); + } +} + +static efi_status_t +add_strings_package(struct efi_hii_packagelist *hii, + struct efi_hii_strings_package *strings_package) +{ + struct efi_hii_string_block *block; + void *end; + u32 nstrings = 0, idx = 0; + struct efi_string_table *stbl = NULL; + efi_status_t ret; + + EFI_PRINT("header_size: %08x\n", + get_unaligned_le32(&strings_package->header_size)); + EFI_PRINT("string_info_offset: %08x\n", + get_unaligned_le32(&strings_package->string_info_offset)); + EFI_PRINT("language_name: %u\n", + get_unaligned_le16(&strings_package->language_name)); + EFI_PRINT("language: %s\n", strings_package->language); + + /* count # of string entries: */ + end = ((void *)strings_package) + + efi_hii_package_len(&strings_package->header); + block = ((void *)strings_package) + + get_unaligned_le32(&strings_package->string_info_offset); + + while ((void *)block < end) { + switch (block->block_type) { + case EFI_HII_SIBT_STRING_UCS2: { + struct efi_hii_sibt_string_ucs2_block *ucs2; + + ucs2 = (void *)block; + nstrings++; + block = efi_hii_sibt_string_ucs2_block_next(ucs2); + break; + } + case EFI_HII_SIBT_END: + block = end; + break; + default: + EFI_PRINT("unknown HII string block type: %02x\n", + block->block_type); + return EFI_INVALID_PARAMETER; + } + } + + stbl = calloc(sizeof(*stbl), 1); + if (!stbl) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + stbl->strings = calloc(sizeof(stbl->strings[0]), nstrings); + if (!stbl->strings) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + stbl->language_name = + get_unaligned_le16(&strings_package->language_name); + stbl->language = strdup((char *)strings_package->language); + if (!stbl->language) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + stbl->nstrings = nstrings; + + /* and now parse string entries and populate efi_string_table */ + block = ((void *)strings_package) + + get_unaligned_le32(&strings_package->string_info_offset); + + while ((void *)block < end) { + switch (block->block_type) { + case EFI_HII_SIBT_STRING_UCS2: { + struct efi_hii_sibt_string_ucs2_block *ucs2; + + ucs2 = (void *)block; + EFI_PRINT("%4u: \"%ls\"\n", idx + 1, ucs2->string_text); + stbl->strings[idx].string = + u16_strdup(ucs2->string_text); + if (!stbl->strings[idx].string) { + ret = EFI_OUT_OF_RESOURCES; + goto error; + } + idx++; + /* FIXME: accessing u16 * here */ + block = efi_hii_sibt_string_ucs2_block_next(ucs2); + break; + } + case EFI_HII_SIBT_END: + goto out; + default: + EFI_PRINT("unknown HII string block type: %02x\n", + block->block_type); + ret = EFI_INVALID_PARAMETER; + goto error; + } + } + +out: + list_add(&stbl->link, &hii->string_tables); + if (hii->max_string_id < nstrings) + hii->max_string_id = nstrings; + + return EFI_SUCCESS; + +error: + if (stbl) { + free(stbl->language); + while (idx > 0) + free(stbl->strings[--idx].string); + free(stbl->strings); + } + free(stbl); + + return ret; +} + +static void remove_guid_package(struct efi_hii_packagelist *hii) +{ + struct efi_guid_data *data; + + while (!list_empty(&hii->guid_list)) { + data = list_first_entry(&hii->guid_list, + struct efi_guid_data, link); + list_del(&data->link); + free(data); + } +} + +static efi_status_t +add_guid_package(struct efi_hii_packagelist *hii, + struct efi_hii_guid_package *package) +{ + struct efi_guid_data *data; + + data = calloc(sizeof(*data), 1); + if (!data) + return EFI_OUT_OF_RESOURCES; + + /* TODO: we don't know any about data field */ + memcpy(&data->package, package, sizeof(*package)); + list_add_tail(&data->link, &hii->guid_list); + + return EFI_SUCCESS; +} + +static void free_keyboard_layouts(struct efi_keyboard_package_data *package) +{ + struct efi_keyboard_layout_data *layout_data; + + while (!list_empty(&package->keyboard_layout_list)) { + layout_data = list_first_entry(&package->keyboard_layout_list, + struct efi_keyboard_layout_data, + link); + list_del(&layout_data->link); + list_del(&layout_data->link_sys); + free(layout_data); + } +} + +static void remove_keyboard_package(struct efi_hii_packagelist *hii) +{ + struct efi_keyboard_package_data *package; + + while (!list_empty(&hii->keyboard_packages)) { + package = list_first_entry(&hii->keyboard_packages, + struct efi_keyboard_package_data, + link); + free_keyboard_layouts(package); + list_del(&package->link); + free(package); + } +} + +static efi_status_t +add_keyboard_package(struct efi_hii_packagelist *hii, + struct efi_hii_keyboard_package *keyboard_package) +{ + struct efi_keyboard_package_data *package_data; + struct efi_hii_keyboard_layout *layout; + struct efi_keyboard_layout_data *layout_data; + u16 layout_count, layout_length; + int i; + + package_data = malloc(sizeof(*package_data)); + if (!package_data) + return EFI_OUT_OF_RESOURCES; + INIT_LIST_HEAD(&package_data->link); + INIT_LIST_HEAD(&package_data->keyboard_layout_list); + + layout = &keyboard_package->layout[0]; + layout_count = get_unaligned_le16(&keyboard_package->layout_count); + for (i = 0; i < layout_count; i++) { + layout_length = get_unaligned_le16(&layout->layout_length); + layout_data = malloc(sizeof(*layout_data) + layout_length); + if (!layout_data) + goto out; + + memcpy(&layout_data->keyboard_layout, layout, layout_length); + list_add_tail(&layout_data->link, + &package_data->keyboard_layout_list); + list_add_tail(&layout_data->link_sys, + &efi_keyboard_layout_list); + + layout += layout_length; + } + + list_add_tail(&package_data->link, &hii->keyboard_packages); + + return EFI_SUCCESS; + +out: + free_keyboard_layouts(package_data); + free(package_data); + + return EFI_OUT_OF_RESOURCES; +} + +static struct efi_hii_packagelist *new_packagelist(void) +{ + struct efi_hii_packagelist *hii; + + hii = malloc(sizeof(*hii)); + list_add_tail(&hii->link, &efi_package_lists); + hii->max_string_id = 0; + INIT_LIST_HEAD(&hii->string_tables); + INIT_LIST_HEAD(&hii->guid_list); + INIT_LIST_HEAD(&hii->keyboard_packages); + + return hii; +} + +static void free_packagelist(struct efi_hii_packagelist *hii) +{ + remove_strings_package(hii); + remove_guid_package(hii); + remove_keyboard_package(hii); + + list_del(&hii->link); + free(hii); +} + +static efi_status_t +add_packages(struct efi_hii_packagelist *hii, + const struct efi_hii_package_list_header *package_list) +{ + struct efi_hii_package_header *package; + void *end; + efi_status_t ret = EFI_SUCCESS; + + end = ((void *)package_list) + + get_unaligned_le32(&package_list->package_length); + + EFI_PRINT("package_list: %pUl (%u)\n", &package_list->package_list_guid, + get_unaligned_le32(&package_list->package_length)); + + package = ((void *)package_list) + sizeof(*package_list); + while ((void *)package < end) { + EFI_PRINT("package=%p, package type=%x, length=%u\n", package, + efi_hii_package_type(package), + efi_hii_package_len(package)); + + switch (efi_hii_package_type(package)) { + case EFI_HII_PACKAGE_TYPE_GUID: + ret = add_guid_package(hii, + (struct efi_hii_guid_package *)package); + break; + case EFI_HII_PACKAGE_FORMS: + EFI_PRINT("Form package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_STRINGS: + ret = add_strings_package(hii, + (struct efi_hii_strings_package *)package); + break; + case EFI_HII_PACKAGE_FONTS: + EFI_PRINT("Font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_IMAGES: + EFI_PRINT("Image package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_SIMPLE_FONTS: + EFI_PRINT("Simple font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_DEVICE_PATH: + EFI_PRINT("Device path package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: + ret = add_keyboard_package(hii, + (struct efi_hii_keyboard_package *)package); + break; + case EFI_HII_PACKAGE_ANIMATIONS: + EFI_PRINT("Animation package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_END: + goto out; + case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN: + case EFI_HII_PACKAGE_TYPE_SYSTEM_END: + default: + break; + } + + if (ret != EFI_SUCCESS) + return ret; + + package = (void *)package + efi_hii_package_len(package); + } +out: + // TODO in theory there is some notifications that should be sent.. + return EFI_SUCCESS; +} + +/* + * EFI_HII_DATABASE_PROTOCOL + */ + +static efi_status_t EFIAPI +new_package_list(const struct efi_hii_database_protocol *this, + const struct efi_hii_package_list_header *package_list, + const efi_handle_t driver_handle, + efi_hii_handle_t *handle) +{ + struct efi_hii_packagelist *hii; + efi_status_t ret; + + EFI_ENTRY("%p, %p, %p, %p", this, package_list, driver_handle, handle); + + if (!package_list || !handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + hii = new_packagelist(); + if (!hii) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + ret = add_packages(hii, package_list); + if (ret != EFI_SUCCESS) { + free_packagelist(hii); + return EFI_EXIT(ret); + } + + hii->driver_handle = driver_handle; + *handle = hii; + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +remove_package_list(const struct efi_hii_database_protocol *this, + efi_hii_handle_t handle) +{ + struct efi_hii_packagelist *hii = handle; + + EFI_ENTRY("%p, %p", this, handle); + + if (!handle || !efi_hii_packagelist_exists(handle)) + return EFI_EXIT(EFI_NOT_FOUND); + + free_packagelist(hii); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +update_package_list(const struct efi_hii_database_protocol *this, + efi_hii_handle_t handle, + const struct efi_hii_package_list_header *package_list) +{ + struct efi_hii_packagelist *hii = handle; + struct efi_hii_package_header *package; + void *end; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p", this, handle, package_list); + + if (!handle || !efi_hii_packagelist_exists(handle)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!package_list) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + EFI_PRINT("package_list: %pUl (%u)\n", &package_list->package_list_guid, + get_unaligned_le32(&package_list->package_length)); + + package = ((void *)package_list) + sizeof(*package_list); + end = ((void *)package_list) + + get_unaligned_le32(&package_list->package_length); + + while ((void *)package < end) { + EFI_PRINT("package=%p, package type=%x, length=%u\n", package, + efi_hii_package_type(package), + efi_hii_package_len(package)); + + switch (efi_hii_package_type(package)) { + case EFI_HII_PACKAGE_TYPE_GUID: + remove_guid_package(hii); + break; + case EFI_HII_PACKAGE_FORMS: + EFI_PRINT("Form package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_STRINGS: + remove_strings_package(hii); + break; + case EFI_HII_PACKAGE_FONTS: + EFI_PRINT("Font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_IMAGES: + EFI_PRINT("Image package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_SIMPLE_FONTS: + EFI_PRINT("Simple font package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_DEVICE_PATH: + EFI_PRINT("Device path package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: + remove_keyboard_package(hii); + break; + case EFI_HII_PACKAGE_ANIMATIONS: + EFI_PRINT("Animation package not supported\n"); + ret = EFI_INVALID_PARAMETER; + break; + case EFI_HII_PACKAGE_END: + goto out; + case EFI_HII_PACKAGE_TYPE_SYSTEM_BEGIN: + case EFI_HII_PACKAGE_TYPE_SYSTEM_END: + default: + break; + } + + /* TODO: already removed some packages */ + if (ret != EFI_SUCCESS) + return EFI_EXIT(ret); + + package = ((void *)package) + + efi_hii_package_len(package); + } +out: + ret = add_packages(hii, package_list); + + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI +list_package_lists(const struct efi_hii_database_protocol *this, + u8 package_type, + const efi_guid_t *package_guid, + efi_uintn_t *handle_buffer_length, + efi_hii_handle_t *handle) +{ + struct efi_hii_packagelist *hii = + (struct efi_hii_packagelist *)handle; + int package_cnt, package_max; + efi_status_t ret = EFI_NOT_FOUND; + + EFI_ENTRY("%p, %u, %pUl, %p, %p", this, package_type, package_guid, + handle_buffer_length, handle); + + if (!handle_buffer_length || + (*handle_buffer_length && !handle)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if ((package_type != EFI_HII_PACKAGE_TYPE_GUID && package_guid) || + (package_type == EFI_HII_PACKAGE_TYPE_GUID && !package_guid)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + EFI_PRINT("package type=%x, guid=%pUl, length=%zu\n", (int)package_type, + package_guid, *handle_buffer_length); + + package_cnt = 0; + package_max = *handle_buffer_length / sizeof(*handle); + list_for_each_entry(hii, &efi_package_lists, link) { + switch (package_type) { + case EFI_HII_PACKAGE_TYPE_ALL: + break; + case EFI_HII_PACKAGE_TYPE_GUID: + if (!list_empty(&hii->guid_list)) + break; + continue; + case EFI_HII_PACKAGE_STRINGS: + if (!list_empty(&hii->string_tables)) + break; + continue; + case EFI_HII_PACKAGE_KEYBOARD_LAYOUT: + if (!list_empty(&hii->keyboard_packages)) + break; + continue; + default: + continue; + } + + package_cnt++; + if (package_cnt <= package_max) { + *handle++ = hii; + ret = EFI_SUCCESS; + } else { + ret = EFI_BUFFER_TOO_SMALL; + } + } + *handle_buffer_length = package_cnt * sizeof(*handle); +out: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI +export_package_lists(const struct efi_hii_database_protocol *this, + efi_hii_handle_t handle, + efi_uintn_t *buffer_size, + struct efi_hii_package_list_header *buffer) +{ + EFI_ENTRY("%p, %p, %p, %p", this, handle, buffer_size, buffer); + + if (!buffer_size || !buffer) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +register_package_notify(const struct efi_hii_database_protocol *this, + u8 package_type, + const efi_guid_t *package_guid, + const void *package_notify_fn, + efi_uintn_t notify_type, + efi_handle_t *notify_handle) +{ + EFI_ENTRY("%p, %u, %pUl, %p, %zu, %p", this, package_type, + package_guid, package_notify_fn, notify_type, + notify_handle); + + if (!notify_handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if ((package_type != EFI_HII_PACKAGE_TYPE_GUID && package_guid) || + (package_type == EFI_HII_PACKAGE_TYPE_GUID && !package_guid)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +unregister_package_notify(const struct efi_hii_database_protocol *this, + efi_handle_t notification_handle) +{ + EFI_ENTRY("%p, %p", this, notification_handle); + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +find_keyboard_layouts(const struct efi_hii_database_protocol *this, + u16 *key_guid_buffer_length, + efi_guid_t *key_guid_buffer) +{ + struct efi_keyboard_layout_data *layout_data; + int package_cnt, package_max; + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p", this, key_guid_buffer_length, key_guid_buffer); + + if (!key_guid_buffer_length || + (*key_guid_buffer_length && !key_guid_buffer)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + package_cnt = 0; + package_max = *key_guid_buffer_length / sizeof(*key_guid_buffer); + list_for_each_entry(layout_data, &efi_keyboard_layout_list, link_sys) { + package_cnt++; + if (package_cnt <= package_max) + memcpy(key_guid_buffer++, + &layout_data->keyboard_layout.guid, + sizeof(*key_guid_buffer)); + else + ret = EFI_BUFFER_TOO_SMALL; + } + *key_guid_buffer_length = package_cnt * sizeof(*key_guid_buffer); + + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI +get_keyboard_layout(const struct efi_hii_database_protocol *this, + efi_guid_t *key_guid, + u16 *keyboard_layout_length, + struct efi_hii_keyboard_layout *keyboard_layout) +{ + struct efi_keyboard_layout_data *layout_data; + u16 layout_length; + + EFI_ENTRY("%p, %pUl, %p, %p", this, key_guid, keyboard_layout_length, + keyboard_layout); + + if (!keyboard_layout_length || + (*keyboard_layout_length && !keyboard_layout)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* TODO: no notion of current keyboard layout */ + if (!key_guid) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(layout_data, &efi_keyboard_layout_list, link_sys) { + if (!guidcmp(&layout_data->keyboard_layout.guid, key_guid)) + goto found; + } + + return EFI_EXIT(EFI_NOT_FOUND); + +found: + layout_length = + get_unaligned_le16(&layout_data->keyboard_layout.layout_length); + if (*keyboard_layout_length < layout_length) { + *keyboard_layout_length = layout_length; + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + + memcpy(keyboard_layout, &layout_data->keyboard_layout, layout_length); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +set_keyboard_layout(const struct efi_hii_database_protocol *this, + efi_guid_t *key_guid) +{ + EFI_ENTRY("%p, %pUl", this, key_guid); + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +get_package_list_handle(const struct efi_hii_database_protocol *this, + efi_hii_handle_t package_list_handle, + efi_handle_t *driver_handle) +{ + struct efi_hii_packagelist *hii; + + EFI_ENTRY("%p, %p, %p", this, package_list_handle, driver_handle); + + if (!driver_handle) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(hii, &efi_package_lists, link) { + if (hii == package_list_handle) { + *driver_handle = hii->driver_handle; + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_NOT_FOUND); +} + +const struct efi_hii_database_protocol efi_hii_database = { + .new_package_list = new_package_list, + .remove_package_list = remove_package_list, + .update_package_list = update_package_list, + .list_package_lists = list_package_lists, + .export_package_lists = export_package_lists, + .register_package_notify = register_package_notify, + .unregister_package_notify = unregister_package_notify, + .find_keyboard_layouts = find_keyboard_layouts, + .get_keyboard_layout = get_keyboard_layout, + .set_keyboard_layout = set_keyboard_layout, + .get_package_list_handle = get_package_list_handle +}; + +/* + * EFI_HII_STRING_PROTOCOL + */ + +static bool language_match(char *language, char *languages) +{ + size_t n; + + n = strlen(language); + /* match primary language? */ + if (!strncasecmp(language, languages, n) && + (languages[n] == ';' || languages[n] == '\0')) + return true; + + return false; +} + +static efi_status_t EFIAPI +new_string(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + efi_string_id_t *string_id, + const u8 *language, + const u16 *language_name, + const efi_string_t string, + const struct efi_font_info *string_font_info) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + + EFI_ENTRY("%p, %p, %p, \"%s\", %p, \"%ls\", %p", this, package_list, + string_id, language, language_name, string, + string_font_info); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!string_id || !language || !string) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)language, stbl->language)) { + efi_string_id_t new_id; + void *buf; + efi_string_t str; + + new_id = ++hii->max_string_id; + if (stbl->nstrings < new_id) { + buf = realloc(stbl->strings, + sizeof(stbl->strings[0]) + * new_id); + if (!buf) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + memset(&stbl->strings[stbl->nstrings], 0, + (new_id - stbl->nstrings) + * sizeof(stbl->strings[0])); + stbl->strings = buf; + stbl->nstrings = new_id; + } + + str = u16_strdup(string); + if (!str) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + stbl->strings[new_id - 1].string = str; + *string_id = new_id; + + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +get_string(const struct efi_hii_string_protocol *this, + const u8 *language, + efi_hii_handle_t package_list, + efi_string_id_t string_id, + efi_string_t string, + efi_uintn_t *string_size, + struct efi_font_info **string_font_info) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + + EFI_ENTRY("%p, \"%s\", %p, %u, %p, %p, %p", this, language, + package_list, string_id, string, string_size, + string_font_info); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)language, stbl->language)) { + efi_string_t str; + size_t len; + + if (stbl->nstrings < string_id) + return EFI_EXIT(EFI_NOT_FOUND); + + str = stbl->strings[string_id - 1].string; + if (str) { + len = (u16_strlen(str) + 1) * sizeof(u16); + if (*string_size < len) { + *string_size = len; + + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + memcpy(string, str, len); + *string_size = len; + } else { + return EFI_EXIT(EFI_NOT_FOUND); + } + + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +set_string(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + efi_string_id_t string_id, + const u8 *language, + const efi_string_t string, + const struct efi_font_info *string_font_info) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + + EFI_ENTRY("%p, %p, %u, \"%s\", \"%ls\", %p", this, package_list, + string_id, language, string, string_font_info); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (string_id > hii->max_string_id) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!string || !language) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)language, stbl->language)) { + efi_string_t str; + + if (hii->max_string_id < string_id) + return EFI_EXIT(EFI_NOT_FOUND); + + if (stbl->nstrings < string_id) { + void *buf; + + buf = realloc(stbl->strings, + string_id + * sizeof(stbl->strings[0])); + if (!buf) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + memset(&stbl->strings[string_id - 1], 0, + (string_id - stbl->nstrings) + * sizeof(stbl->strings[0])); + stbl->strings = buf; + } + + str = u16_strdup(string); + if (!str) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + free(stbl->strings[string_id - 1].string); + stbl->strings[string_id - 1].string = str; + + return EFI_EXIT(EFI_SUCCESS); + } + } + + return EFI_EXIT(EFI_NOT_FOUND); +} + +static efi_status_t EFIAPI +get_languages(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + u8 *languages, + efi_uintn_t *languages_size) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + size_t len = 0; + char *p; + + EFI_ENTRY("%p, %p, %p, %p", this, package_list, languages, + languages_size); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!languages_size || + (*languages_size && !languages)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* figure out required size: */ + list_for_each_entry(stbl, &hii->string_tables, link) { + len += strlen((char *)stbl->language) + 1; + } + + if (*languages_size < len) { + *languages_size = len; + + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + } + + p = (char *)languages; + list_for_each_entry(stbl, &hii->string_tables, link) { + if (p != (char *)languages) + *p++ = ';'; + strcpy(p, stbl->language); + p += strlen((char *)stbl->language); + } + *p = '\0'; + + EFI_PRINT("languages: %s\n", languages); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI +get_secondary_languages(const struct efi_hii_string_protocol *this, + efi_hii_handle_t package_list, + const u8 *primary_language, + u8 *secondary_languages, + efi_uintn_t *secondary_languages_size) +{ + struct efi_hii_packagelist *hii = package_list; + struct efi_string_table *stbl; + bool found = false; + + EFI_ENTRY("%p, %p, \"%s\", %p, %p", this, package_list, + primary_language, secondary_languages, + secondary_languages_size); + + if (!package_list || !efi_hii_packagelist_exists(package_list)) + return EFI_EXIT(EFI_NOT_FOUND); + + if (!secondary_languages_size || + (*secondary_languages_size && !secondary_languages)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + list_for_each_entry(stbl, &hii->string_tables, link) { + if (language_match((char *)primary_language, stbl->language)) { + found = true; + break; + } + } + if (!found) + return EFI_EXIT(EFI_INVALID_LANGUAGE); + + /* + * TODO: What is secondary language? + * *secondary_languages = '\0'; + * *secondary_languages_size = 0; + */ + + return EFI_EXIT(EFI_NOT_FOUND); +} + +const struct efi_hii_string_protocol efi_hii_string = { + .new_string = new_string, + .get_string = get_string, + .set_string = set_string, + .get_languages = get_languages, + .get_secondary_languages = get_secondary_languages +}; diff --git a/lib/efi_loader/efi_hii_config.c b/lib/efi_loader/efi_hii_config.c new file mode 100644 index 00000000..26ea4b9b --- /dev/null +++ b/lib/efi_loader/efi_hii_config.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Human Interface Infrastructure ... Configuration + * + * Copyright (c) 2017 Leif Lindholm + * Copyright (c) 2018 AKASHI Takahiro, Linaro Limited + */ + +#include <common.h> +#include <efi_loader.h> + +const efi_guid_t efi_guid_hii_config_routing_protocol + = EFI_HII_CONFIG_ROUTING_PROTOCOL_GUID; +const efi_guid_t efi_guid_hii_config_access_protocol + = EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID; + +/* + * EFI_HII_CONFIG_ROUTING_PROTOCOL + */ + +static efi_status_t EFIAPI +extract_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t request, + efi_string_t *progress, + efi_string_t *results) +{ + EFI_ENTRY("%p, \"%ls\", %p, %p", this, request, progress, results); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +export_config(const struct efi_hii_config_routing_protocol *this, + efi_string_t *results) +{ + EFI_ENTRY("%p, %p", this, results); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +route_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t configuration, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p", this, configuration, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +block_to_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t config_request, + const u8 *block, + const efi_uintn_t block_size, + efi_string_t *config, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p, %zu, %p, %p", this, config_request, + block, block_size, config, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +config_to_block(const struct efi_hii_config_routing_protocol *this, + const efi_string_t config_resp, + const u8 *block, + const efi_uintn_t *block_size, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p, %p, %p", this, config_resp, + block, block_size, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +static efi_status_t EFIAPI +get_alt_config(const struct efi_hii_config_routing_protocol *this, + const efi_string_t config_resp, + const efi_guid_t *guid, + const efi_string_t name, + const struct efi_device_path *device_path, + const efi_string_t alt_cfg_id, + efi_string_t *alt_cfg_resp) +{ + EFI_ENTRY("%p, \"%ls\", %pUl, \"%ls\", %p, \"%ls\", %p", + this, config_resp, guid, name, device_path, + alt_cfg_id, alt_cfg_resp); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +} + +/* + * EFI_HII_ACCESS_PROTOCOL + */ + +efi_status_t EFIAPI +extract_config_access(const struct efi_hii_config_access_protocol *this, + const efi_string_t request, + efi_string_t *progress, + efi_string_t *results) +{ + EFI_ENTRY("%p, \"%ls\", %p, %p", this, request, progress, results); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +}; + +efi_status_t EFIAPI +route_config_access(const struct efi_hii_config_access_protocol *this, + const efi_string_t configuration, + efi_string_t *progress) +{ + EFI_ENTRY("%p, \"%ls\", %p", this, configuration, progress); + + return EFI_EXIT(EFI_OUT_OF_RESOURCES); +}; + +efi_status_t EFIAPI +form_callback(const struct efi_hii_config_access_protocol *this, + efi_browser_action_t action, + efi_question_id_t question_id, + u8 type, + union efi_ifr_type_value *value, + efi_browser_action_request_t *action_request) +{ + EFI_ENTRY("%p, 0x%zx, 0x%x, 0x%x, %p, %p", this, action, + question_id, type, value, action_request); + + return EFI_EXIT(EFI_DEVICE_ERROR); +}; + +const struct efi_hii_config_routing_protocol efi_hii_config_routing = { + .extract_config = extract_config, + .export_config = export_config, + .route_config = route_config, + .block_to_config = block_to_config, + .config_to_block = config_to_block, + .get_alt_config = get_alt_config +}; + +const struct efi_hii_config_access_protocol efi_hii_config_access = { + .extract_config_access = extract_config_access, + .route_config_access = route_config_access, + .form_callback = form_callback +}; diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c new file mode 100644 index 00000000..d5de6df1 --- /dev/null +++ b/lib/efi_loader/efi_image_loader.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI image loader + * + * based partly on wine code + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <cpu_func.h> +#include <efi_loader.h> +#include <pe.h> + +const efi_guid_t efi_global_variable_guid = EFI_GLOBAL_VARIABLE_GUID; +const efi_guid_t efi_guid_device_path = EFI_DEVICE_PATH_PROTOCOL_GUID; +const efi_guid_t efi_guid_loaded_image = EFI_LOADED_IMAGE_PROTOCOL_GUID; +const efi_guid_t efi_guid_loaded_image_device_path = + EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID; +const efi_guid_t efi_simple_file_system_protocol_guid = + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; +const efi_guid_t efi_file_info_guid = EFI_FILE_INFO_GUID; + +static int machines[] = { +#if defined(__aarch64__) + IMAGE_FILE_MACHINE_ARM64, +#elif defined(__arm__) + IMAGE_FILE_MACHINE_ARM, + IMAGE_FILE_MACHINE_THUMB, + IMAGE_FILE_MACHINE_ARMNT, +#endif + +#if defined(__x86_64__) + IMAGE_FILE_MACHINE_AMD64, +#elif defined(__i386__) + IMAGE_FILE_MACHINE_I386, +#endif + +#if defined(__riscv) && (__riscv_xlen == 32) + IMAGE_FILE_MACHINE_RISCV32, +#endif + +#if defined(__riscv) && (__riscv_xlen == 64) + IMAGE_FILE_MACHINE_RISCV64, +#endif + 0 }; + +/** + * efi_print_image_info() - print information about a loaded image + * + * If the program counter is located within the image the offset to the base + * address is shown. + * + * @obj: EFI object + * @image: loaded image + * @pc: program counter (use NULL to suppress offset output) + * Return: status code + */ +static efi_status_t efi_print_image_info(struct efi_loaded_image_obj *obj, + struct efi_loaded_image *image, + void *pc) +{ + printf("UEFI image"); + printf(" [0x%p:0x%p]", + image->image_base, image->image_base + image->image_size - 1); + if (pc && pc >= image->image_base && + pc < image->image_base + image->image_size) + printf(" pc=0x%zx", pc - image->image_base); + if (image->file_path) + printf(" '%pD'", image->file_path); + printf("\n"); + return EFI_SUCCESS; +} + +/** + * efi_print_image_infos() - print information about all loaded images + * + * @pc: program counter (use NULL to suppress offset output) + */ +void efi_print_image_infos(void *pc) +{ + struct efi_object *efiobj; + struct efi_handler *handler; + + list_for_each_entry(efiobj, &efi_obj_list, link) { + list_for_each_entry(handler, &efiobj->protocols, link) { + if (!guidcmp(handler->guid, &efi_guid_loaded_image)) { + efi_print_image_info( + (struct efi_loaded_image_obj *)efiobj, + handler->protocol_interface, pc); + } + } + } +} + +/** + * efi_loader_relocate() - relocate UEFI binary + * + * @rel: pointer to the relocation table + * @rel_size: size of the relocation table in bytes + * @efi_reloc: actual load address of the image + * @pref_address: preferred load address of the image + * Return: status code + */ +static efi_status_t efi_loader_relocate(const IMAGE_BASE_RELOCATION *rel, + unsigned long rel_size, void *efi_reloc, + unsigned long pref_address) +{ + unsigned long delta = (unsigned long)efi_reloc - pref_address; + const IMAGE_BASE_RELOCATION *end; + int i; + + if (delta == 0) + return EFI_SUCCESS; + + end = (const IMAGE_BASE_RELOCATION *)((const char *)rel + rel_size); + while (rel < end && rel->SizeOfBlock) { + const uint16_t *relocs = (const uint16_t *)(rel + 1); + i = (rel->SizeOfBlock - sizeof(*rel)) / sizeof(uint16_t); + while (i--) { + uint32_t offset = (uint32_t)(*relocs & 0xfff) + + rel->VirtualAddress; + int type = *relocs >> EFI_PAGE_SHIFT; + uint64_t *x64 = efi_reloc + offset; + uint32_t *x32 = efi_reloc + offset; + uint16_t *x16 = efi_reloc + offset; + + switch (type) { + case IMAGE_REL_BASED_ABSOLUTE: + break; + case IMAGE_REL_BASED_HIGH: + *x16 += ((uint32_t)delta) >> 16; + break; + case IMAGE_REL_BASED_LOW: + *x16 += (uint16_t)delta; + break; + case IMAGE_REL_BASED_HIGHLOW: + *x32 += (uint32_t)delta; + break; + case IMAGE_REL_BASED_DIR64: + *x64 += (uint64_t)delta; + break; +#ifdef __riscv + case IMAGE_REL_BASED_RISCV_HI20: + *x32 = ((*x32 & 0xfffff000) + (uint32_t)delta) | + (*x32 & 0x00000fff); + break; + case IMAGE_REL_BASED_RISCV_LOW12I: + case IMAGE_REL_BASED_RISCV_LOW12S: + /* We know that we're 4k aligned */ + if (delta & 0xfff) { + printf("Unsupported reloc offset\n"); + return EFI_LOAD_ERROR; + } + break; +#endif + default: + printf("Unknown Relocation off %x type %x\n", + offset, type); + return EFI_LOAD_ERROR; + } + relocs++; + } + rel = (const IMAGE_BASE_RELOCATION *)relocs; + } + return EFI_SUCCESS; +} + +void __weak invalidate_icache_all(void) +{ + /* If the system doesn't support icache_all flush, cross our fingers */ +} + +/** + * efi_set_code_and_data_type() - determine the memory types to be used for code + * and data. + * + * @loaded_image_info: image descriptor + * @image_type: field Subsystem of the optional header for + * Windows specific field + */ +static void efi_set_code_and_data_type( + struct efi_loaded_image *loaded_image_info, + uint16_t image_type) +{ + switch (image_type) { + case IMAGE_SUBSYSTEM_EFI_APPLICATION: + loaded_image_info->image_code_type = EFI_LOADER_CODE; + loaded_image_info->image_data_type = EFI_LOADER_DATA; + break; + case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: + loaded_image_info->image_code_type = EFI_BOOT_SERVICES_CODE; + loaded_image_info->image_data_type = EFI_BOOT_SERVICES_DATA; + break; + case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: + case IMAGE_SUBSYSTEM_EFI_ROM: + loaded_image_info->image_code_type = EFI_RUNTIME_SERVICES_CODE; + loaded_image_info->image_data_type = EFI_RUNTIME_SERVICES_DATA; + break; + default: + printf("%s: invalid image type: %u\n", __func__, image_type); + /* Let's assume it is an application */ + loaded_image_info->image_code_type = EFI_LOADER_CODE; + loaded_image_info->image_data_type = EFI_LOADER_DATA; + break; + } +} + +/** + * efi_load_pe() - relocate EFI binary + * + * This function loads all sections from a PE binary into a newly reserved + * piece of memory. On success the entry point is returned as handle->entry. + * + * @handle: loaded image handle + * @efi: pointer to the EFI binary + * @loaded_image_info: loaded image protocol + * Return: status code + */ +efi_status_t efi_load_pe(struct efi_loaded_image_obj *handle, void *efi, + struct efi_loaded_image *loaded_image_info) +{ + IMAGE_NT_HEADERS32 *nt; + IMAGE_DOS_HEADER *dos; + IMAGE_SECTION_HEADER *sections; + int num_sections; + void *efi_reloc; + int i; + const IMAGE_BASE_RELOCATION *rel; + unsigned long rel_size; + int rel_idx = IMAGE_DIRECTORY_ENTRY_BASERELOC; + uint64_t image_base; + unsigned long virt_size = 0; + int supported = 0; + + dos = efi; + if (dos->e_magic != IMAGE_DOS_SIGNATURE) { + printf("%s: Invalid DOS Signature\n", __func__); + return EFI_LOAD_ERROR; + } + + nt = (void *) ((char *)efi + dos->e_lfanew); + if (nt->Signature != IMAGE_NT_SIGNATURE) { + printf("%s: Invalid NT Signature\n", __func__); + return EFI_LOAD_ERROR; + } + + for (i = 0; machines[i]; i++) + if (machines[i] == nt->FileHeader.Machine) { + supported = 1; + break; + } + + if (!supported) { + printf("%s: Machine type 0x%04x is not supported\n", + __func__, nt->FileHeader.Machine); + return EFI_LOAD_ERROR; + } + + /* Calculate upper virtual address boundary */ + num_sections = nt->FileHeader.NumberOfSections; + sections = (void *)&nt->OptionalHeader + + nt->FileHeader.SizeOfOptionalHeader; + + for (i = num_sections - 1; i >= 0; i--) { + IMAGE_SECTION_HEADER *sec = §ions[i]; + virt_size = max_t(unsigned long, virt_size, + sec->VirtualAddress + sec->Misc.VirtualSize); + } + + /* Read 32/64bit specific header bits */ + if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + IMAGE_NT_HEADERS64 *nt64 = (void *)nt; + IMAGE_OPTIONAL_HEADER64 *opt = &nt64->OptionalHeader; + image_base = opt->ImageBase; + efi_set_code_and_data_type(loaded_image_info, opt->Subsystem); + handle->image_type = opt->Subsystem; + efi_reloc = efi_alloc(virt_size, + loaded_image_info->image_code_type); + if (!efi_reloc) { + printf("%s: Could not allocate %lu bytes\n", + __func__, virt_size); + return EFI_OUT_OF_RESOURCES; + } + handle->entry = efi_reloc + opt->AddressOfEntryPoint; + rel_size = opt->DataDirectory[rel_idx].Size; + rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + virt_size = ALIGN(virt_size, opt->SectionAlignment); + } else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + IMAGE_OPTIONAL_HEADER32 *opt = &nt->OptionalHeader; + image_base = opt->ImageBase; + efi_set_code_and_data_type(loaded_image_info, opt->Subsystem); + handle->image_type = opt->Subsystem; + efi_reloc = efi_alloc(virt_size, + loaded_image_info->image_code_type); + if (!efi_reloc) { + printf("%s: Could not allocate %lu bytes\n", + __func__, virt_size); + return EFI_OUT_OF_RESOURCES; + } + handle->entry = efi_reloc + opt->AddressOfEntryPoint; + rel_size = opt->DataDirectory[rel_idx].Size; + rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + virt_size = ALIGN(virt_size, opt->SectionAlignment); + } else { + printf("%s: Invalid optional header magic %x\n", __func__, + nt->OptionalHeader.Magic); + return EFI_LOAD_ERROR; + } + + /* Copy PE headers */ + memcpy(efi_reloc, efi, sizeof(*dos) + sizeof(*nt) + + nt->FileHeader.SizeOfOptionalHeader + + num_sections * sizeof(IMAGE_SECTION_HEADER)); + + /* Load sections into RAM */ + for (i = num_sections - 1; i >= 0; i--) { + IMAGE_SECTION_HEADER *sec = §ions[i]; + memset(efi_reloc + sec->VirtualAddress, 0, + sec->Misc.VirtualSize); + memcpy(efi_reloc + sec->VirtualAddress, + efi + sec->PointerToRawData, + sec->SizeOfRawData); + } + + /* Run through relocations */ + if (efi_loader_relocate(rel, rel_size, efi_reloc, + (unsigned long)image_base) != EFI_SUCCESS) { + efi_free_pages((uintptr_t) efi_reloc, + (virt_size + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT); + return EFI_LOAD_ERROR; + } + + /* Flush cache */ + flush_cache((ulong)efi_reloc, + ALIGN(virt_size, EFI_CACHELINE_SIZE)); + invalidate_icache_all(); + + /* Populate the loaded image interface bits */ + loaded_image_info->image_base = efi_reloc; + loaded_image_info->image_size = virt_size; + + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c new file mode 100644 index 00000000..89adf203 --- /dev/null +++ b/lib/efi_loader/efi_memory.c @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application memory management + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <efi_loader.h> +#include <init.h> +#include <malloc.h> +#include <mapmem.h> +#include <watchdog.h> +#include <linux/list_sort.h> +#include <linux/sizes.h> + +DECLARE_GLOBAL_DATA_PTR; + +/* Magic number identifying memory allocated from pool */ +#define EFI_ALLOC_POOL_MAGIC 0x1fe67ddf6491caa2 + +efi_uintn_t efi_memory_map_key; + +struct efi_mem_list { + struct list_head link; + struct efi_mem_desc desc; +}; + +#define EFI_CARVE_NO_OVERLAP -1 +#define EFI_CARVE_LOOP_AGAIN -2 +#define EFI_CARVE_OVERLAPS_NONRAM -3 + +/* This list contains all memory map items */ +LIST_HEAD(efi_mem); + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER +void *efi_bounce_buffer; +#endif + +/** + * struct efi_pool_allocation - memory block allocated from pool + * + * @num_pages: number of pages allocated + * @checksum: checksum + * @data: allocated pool memory + * + * U-Boot services each UEFI AllocatePool() request as a separate + * (multiple) page allocation. We have to track the number of pages + * to be able to free the correct amount later. + * + * The checksum calculated in function checksum() is used in FreePool() to avoid + * freeing memory not allocated by AllocatePool() and duplicate freeing. + * + * EFI requires 8 byte alignment for pool allocations, so we can + * prepend each allocation with these header fields. + */ +struct efi_pool_allocation { + u64 num_pages; + u64 checksum; + char data[] __aligned(ARCH_DMA_MINALIGN); +}; + +/** + * checksum() - calculate checksum for memory allocated from pool + * + * @alloc: allocation header + * Return: checksum, always non-zero + */ +static u64 checksum(struct efi_pool_allocation *alloc) +{ + u64 addr = (uintptr_t)alloc; + u64 ret = (addr >> 32) ^ (addr << 32) ^ alloc->num_pages ^ + EFI_ALLOC_POOL_MAGIC; + if (!ret) + ++ret; + return ret; +} + +/* + * Sorts the memory list from highest address to lowest address + * + * When allocating memory we should always start from the highest + * address chunk, so sort the memory list such that the first list + * iterator gets the highest address and goes lower from there. + */ +static int efi_mem_cmp(void *priv, struct list_head *a, struct list_head *b) +{ + struct efi_mem_list *mema = list_entry(a, struct efi_mem_list, link); + struct efi_mem_list *memb = list_entry(b, struct efi_mem_list, link); + + if (mema->desc.physical_start == memb->desc.physical_start) + return 0; + else if (mema->desc.physical_start < memb->desc.physical_start) + return 1; + else + return -1; +} + +static uint64_t desc_get_end(struct efi_mem_desc *desc) +{ + return desc->physical_start + (desc->num_pages << EFI_PAGE_SHIFT); +} + +static void efi_mem_sort(void) +{ + struct list_head *lhandle; + struct efi_mem_list *prevmem = NULL; + bool merge_again = true; + + list_sort(NULL, &efi_mem, efi_mem_cmp); + + /* Now merge entries that can be merged */ + while (merge_again) { + merge_again = false; + list_for_each(lhandle, &efi_mem) { + struct efi_mem_list *lmem; + struct efi_mem_desc *prev = &prevmem->desc; + struct efi_mem_desc *cur; + uint64_t pages; + + lmem = list_entry(lhandle, struct efi_mem_list, link); + if (!prevmem) { + prevmem = lmem; + continue; + } + + cur = &lmem->desc; + + if ((desc_get_end(cur) == prev->physical_start) && + (prev->type == cur->type) && + (prev->attribute == cur->attribute)) { + /* There is an existing map before, reuse it */ + pages = cur->num_pages; + prev->num_pages += pages; + prev->physical_start -= pages << EFI_PAGE_SHIFT; + prev->virtual_start -= pages << EFI_PAGE_SHIFT; + list_del(&lmem->link); + free(lmem); + + merge_again = true; + break; + } + + prevmem = lmem; + } + } +} + +/** efi_mem_carve_out - unmap memory region + * + * @map: memory map + * @carve_desc: memory region to unmap + * @overlap_only_ram: the carved out region may only overlap RAM + * Return Value: the number of overlapping pages which have been + * removed from the map, + * EFI_CARVE_NO_OVERLAP, if the regions don't overlap, + * EFI_CARVE_OVERLAPS_NONRAM, if the carve and map overlap, + * and the map contains anything but free ram + * (only when overlap_only_ram is true), + * EFI_CARVE_LOOP_AGAIN, if the mapping list should be + * traversed again, as it has been altered. + * + * Unmaps all memory occupied by the carve_desc region from the list entry + * pointed to by map. + * + * In case of EFI_CARVE_OVERLAPS_NONRAM it is the callers responsibility + * to re-add the already carved out pages to the mapping. + */ +static s64 efi_mem_carve_out(struct efi_mem_list *map, + struct efi_mem_desc *carve_desc, + bool overlap_only_ram) +{ + struct efi_mem_list *newmap; + struct efi_mem_desc *map_desc = &map->desc; + uint64_t map_start = map_desc->physical_start; + uint64_t map_end = map_start + (map_desc->num_pages << EFI_PAGE_SHIFT); + uint64_t carve_start = carve_desc->physical_start; + uint64_t carve_end = carve_start + + (carve_desc->num_pages << EFI_PAGE_SHIFT); + + /* check whether we're overlapping */ + if ((carve_end <= map_start) || (carve_start >= map_end)) + return EFI_CARVE_NO_OVERLAP; + + /* We're overlapping with non-RAM, warn the caller if desired */ + if (overlap_only_ram && (map_desc->type != EFI_CONVENTIONAL_MEMORY)) + return EFI_CARVE_OVERLAPS_NONRAM; + + /* Sanitize carve_start and carve_end to lie within our bounds */ + carve_start = max(carve_start, map_start); + carve_end = min(carve_end, map_end); + + /* Carving at the beginning of our map? Just move it! */ + if (carve_start == map_start) { + if (map_end == carve_end) { + /* Full overlap, just remove map */ + list_del(&map->link); + free(map); + } else { + map->desc.physical_start = carve_end; + map->desc.virtual_start = carve_end; + map->desc.num_pages = (map_end - carve_end) + >> EFI_PAGE_SHIFT; + } + + return (carve_end - carve_start) >> EFI_PAGE_SHIFT; + } + + /* + * Overlapping maps, just split the list map at carve_start, + * it will get moved or removed in the next iteration. + * + * [ map_desc |__carve_start__| newmap ] + */ + + /* Create a new map from [ carve_start ... map_end ] */ + newmap = calloc(1, sizeof(*newmap)); + newmap->desc = map->desc; + newmap->desc.physical_start = carve_start; + newmap->desc.virtual_start = carve_start; + newmap->desc.num_pages = (map_end - carve_start) >> EFI_PAGE_SHIFT; + /* Insert before current entry (descending address order) */ + list_add_tail(&newmap->link, &map->link); + + /* Shrink the map to [ map_start ... carve_start ] */ + map_desc->num_pages = (carve_start - map_start) >> EFI_PAGE_SHIFT; + + return EFI_CARVE_LOOP_AGAIN; +} + +/** + * efi_add_memory_map() - add memory area to the memory map + * + * @start: start address, must be a multiple of EFI_PAGE_SIZE + * @pages: number of pages to add + * @memory_type: type of memory added + * @overlap_only_ram: the memory area must overlap existing + * Return: status code + */ +efi_status_t efi_add_memory_map(uint64_t start, uint64_t pages, int memory_type, + bool overlap_only_ram) +{ + struct list_head *lhandle; + struct efi_mem_list *newlist; + bool carve_again; + uint64_t carved_pages = 0; + struct efi_event *evt; + + EFI_PRINT("%s: 0x%llx 0x%llx %d %s\n", __func__, + start, pages, memory_type, overlap_only_ram ? "yes" : "no"); + + if (memory_type >= EFI_MAX_MEMORY_TYPE) + return EFI_INVALID_PARAMETER; + + if (!pages) + return EFI_SUCCESS; + + ++efi_memory_map_key; + newlist = calloc(1, sizeof(*newlist)); + newlist->desc.type = memory_type; + newlist->desc.physical_start = start; + newlist->desc.virtual_start = start; + newlist->desc.num_pages = pages; + + switch (memory_type) { + case EFI_RUNTIME_SERVICES_CODE: + case EFI_RUNTIME_SERVICES_DATA: + newlist->desc.attribute = EFI_MEMORY_WB | EFI_MEMORY_RUNTIME; + break; + case EFI_MMAP_IO: + newlist->desc.attribute = EFI_MEMORY_RUNTIME; + break; + default: + newlist->desc.attribute = EFI_MEMORY_WB; + break; + } + + /* Add our new map */ + do { + carve_again = false; + list_for_each(lhandle, &efi_mem) { + struct efi_mem_list *lmem; + s64 r; + + lmem = list_entry(lhandle, struct efi_mem_list, link); + r = efi_mem_carve_out(lmem, &newlist->desc, + overlap_only_ram); + switch (r) { + case EFI_CARVE_OVERLAPS_NONRAM: + /* + * The user requested to only have RAM overlaps, + * but we hit a non-RAM region. Error out. + */ + return EFI_NO_MAPPING; + case EFI_CARVE_NO_OVERLAP: + /* Just ignore this list entry */ + break; + case EFI_CARVE_LOOP_AGAIN: + /* + * We split an entry, but need to loop through + * the list again to actually carve it. + */ + carve_again = true; + break; + default: + /* We carved a number of pages */ + carved_pages += r; + carve_again = true; + break; + } + + if (carve_again) { + /* The list changed, we need to start over */ + break; + } + } + } while (carve_again); + + if (overlap_only_ram && (carved_pages != pages)) { + /* + * The payload wanted to have RAM overlaps, but we overlapped + * with an unallocated region. Error out. + */ + return EFI_NO_MAPPING; + } + + /* Add our new map */ + list_add_tail(&newlist->link, &efi_mem); + + /* And make sure memory is listed in descending order */ + efi_mem_sort(); + + /* Notify that the memory map was changed */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_memory_map_change)) { + efi_signal_event(evt); + break; + } + } + + return EFI_SUCCESS; +} + +/** + * efi_check_allocated() - validate address to be freed + * + * Check that the address is within allocated memory: + * + * * The address must be in a range of the memory map. + * * The address may not point to EFI_CONVENTIONAL_MEMORY. + * + * Page alignment is not checked as this is not a requirement of + * efi_free_pool(). + * + * @addr: address of page to be freed + * @must_be_allocated: return success if the page is allocated + * Return: status code + */ +static efi_status_t efi_check_allocated(u64 addr, bool must_be_allocated) +{ + struct efi_mem_list *item; + + list_for_each_entry(item, &efi_mem, link) { + u64 start = item->desc.physical_start; + u64 end = start + (item->desc.num_pages << EFI_PAGE_SHIFT); + + if (addr >= start && addr < end) { + if (must_be_allocated ^ + (item->desc.type == EFI_CONVENTIONAL_MEMORY)) + return EFI_SUCCESS; + else + return EFI_NOT_FOUND; + } + } + + return EFI_NOT_FOUND; +} + +static uint64_t efi_find_free_memory(uint64_t len, uint64_t max_addr) +{ + struct list_head *lhandle; + + /* + * Prealign input max address, so we simplify our matching + * logic below and can just reuse it as return pointer. + */ + max_addr &= ~EFI_PAGE_MASK; + + list_for_each(lhandle, &efi_mem) { + struct efi_mem_list *lmem = list_entry(lhandle, + struct efi_mem_list, link); + struct efi_mem_desc *desc = &lmem->desc; + uint64_t desc_len = desc->num_pages << EFI_PAGE_SHIFT; + uint64_t desc_end = desc->physical_start + desc_len; + uint64_t curmax = min(max_addr, desc_end); + uint64_t ret = curmax - len; + + /* We only take memory from free RAM */ + if (desc->type != EFI_CONVENTIONAL_MEMORY) + continue; + + /* Out of bounds for max_addr */ + if ((ret + len) > max_addr) + continue; + + /* Out of bounds for upper map limit */ + if ((ret + len) > desc_end) + continue; + + /* Out of bounds for lower map limit */ + if (ret < desc->physical_start) + continue; + + /* Return the highest address in this map within bounds */ + return ret; + } + + return 0; +} + +/* + * Allocate memory pages. + * + * @type type of allocation to be performed + * @memory_type usage type of the allocated memory + * @pages number of pages to be allocated + * @memory allocated memory + * @return status code + */ +efi_status_t efi_allocate_pages(int type, int memory_type, + efi_uintn_t pages, uint64_t *memory) +{ + u64 len = pages << EFI_PAGE_SHIFT; + efi_status_t ret; + uint64_t addr; + + /* Check import parameters */ + if (memory_type >= EFI_PERSISTENT_MEMORY_TYPE && + memory_type <= 0x6FFFFFFF) + return EFI_INVALID_PARAMETER; + if (!memory) + return EFI_INVALID_PARAMETER; + + switch (type) { + case EFI_ALLOCATE_ANY_PAGES: + /* Any page */ + addr = efi_find_free_memory(len, -1ULL); + if (!addr) + return EFI_OUT_OF_RESOURCES; + break; + case EFI_ALLOCATE_MAX_ADDRESS: + /* Max address */ + addr = efi_find_free_memory(len, *memory); + if (!addr) + return EFI_OUT_OF_RESOURCES; + break; + case EFI_ALLOCATE_ADDRESS: + /* Exact address, reserve it. The addr is already in *memory. */ + ret = efi_check_allocated(*memory, false); + if (ret != EFI_SUCCESS) + return EFI_NOT_FOUND; + addr = *memory; + break; + default: + /* UEFI doesn't specify other allocation types */ + return EFI_INVALID_PARAMETER; + } + + /* Reserve that map in our memory maps */ + if (efi_add_memory_map(addr, pages, memory_type, true) != EFI_SUCCESS) + /* Map would overlap, bail out */ + return EFI_OUT_OF_RESOURCES; + + *memory = addr; + + return EFI_SUCCESS; +} + +void *efi_alloc(uint64_t len, int memory_type) +{ + uint64_t ret = 0; + uint64_t pages = efi_size_in_pages(len); + efi_status_t r; + + r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, memory_type, pages, + &ret); + if (r == EFI_SUCCESS) + return (void*)(uintptr_t)ret; + + return NULL; +} + +/** + * efi_free_pages() - free memory pages + * + * @memory: start of the memory area to be freed + * @pages: number of pages to be freed + * Return: status code + */ +efi_status_t efi_free_pages(uint64_t memory, efi_uintn_t pages) +{ + efi_status_t ret; + + ret = efi_check_allocated(memory, true); + if (ret != EFI_SUCCESS) + return ret; + + /* Sanity check */ + if (!memory || (memory & EFI_PAGE_MASK) || !pages) { + printf("%s: illegal free 0x%llx, 0x%zx\n", __func__, + memory, pages); + return EFI_INVALID_PARAMETER; + } + + ret = efi_add_memory_map(memory, pages, EFI_CONVENTIONAL_MEMORY, false); + /* Merging of adjacent free regions is missing */ + + if (ret != EFI_SUCCESS) + return EFI_NOT_FOUND; + + return ret; +} + +/** + * efi_allocate_pool - allocate memory from pool + * + * @pool_type: type of the pool from which memory is to be allocated + * @size: number of bytes to be allocated + * @buffer: allocated memory + * Return: status code + */ +efi_status_t efi_allocate_pool(int pool_type, efi_uintn_t size, void **buffer) +{ + efi_status_t r; + u64 addr; + struct efi_pool_allocation *alloc; + u64 num_pages = efi_size_in_pages(size + + sizeof(struct efi_pool_allocation)); + + if (!buffer) + return EFI_INVALID_PARAMETER; + + if (size == 0) { + *buffer = NULL; + return EFI_SUCCESS; + } + + r = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, pool_type, num_pages, + &addr); + if (r == EFI_SUCCESS) { + alloc = (struct efi_pool_allocation *)(uintptr_t)addr; + alloc->num_pages = num_pages; + alloc->checksum = checksum(alloc); + *buffer = alloc->data; + } + + return r; +} + +/** + * efi_free_pool() - free memory from pool + * + * @buffer: start of memory to be freed + * Return: status code + */ +efi_status_t efi_free_pool(void *buffer) +{ + efi_status_t ret; + struct efi_pool_allocation *alloc; + + if (!buffer) + return EFI_INVALID_PARAMETER; + + ret = efi_check_allocated((uintptr_t)buffer, true); + if (ret != EFI_SUCCESS) + return ret; + + alloc = container_of(buffer, struct efi_pool_allocation, data); + + /* Check that this memory was allocated by efi_allocate_pool() */ + if (((uintptr_t)alloc & EFI_PAGE_MASK) || + alloc->checksum != checksum(alloc)) { + printf("%s: illegal free 0x%p\n", __func__, buffer); + return EFI_INVALID_PARAMETER; + } + /* Avoid double free */ + alloc->checksum = 0; + + ret = efi_free_pages((uintptr_t)alloc, alloc->num_pages); + + return ret; +} + +/* + * Get map describing memory usage. + * + * @memory_map_size on entry the size, in bytes, of the memory map buffer, + * on exit the size of the copied memory map + * @memory_map buffer to which the memory map is written + * @map_key key for the memory map + * @descriptor_size size of an individual memory descriptor + * @descriptor_version version number of the memory descriptor structure + * @return status code + */ +efi_status_t efi_get_memory_map(efi_uintn_t *memory_map_size, + struct efi_mem_desc *memory_map, + efi_uintn_t *map_key, + efi_uintn_t *descriptor_size, + uint32_t *descriptor_version) +{ + efi_uintn_t map_size = 0; + int map_entries = 0; + struct list_head *lhandle; + efi_uintn_t provided_map_size; + + if (!memory_map_size) + return EFI_INVALID_PARAMETER; + + provided_map_size = *memory_map_size; + + list_for_each(lhandle, &efi_mem) + map_entries++; + + map_size = map_entries * sizeof(struct efi_mem_desc); + + *memory_map_size = map_size; + + if (provided_map_size < map_size) + return EFI_BUFFER_TOO_SMALL; + + if (!memory_map) + return EFI_INVALID_PARAMETER; + + if (descriptor_size) + *descriptor_size = sizeof(struct efi_mem_desc); + + if (descriptor_version) + *descriptor_version = EFI_MEMORY_DESCRIPTOR_VERSION; + + /* Copy list into array */ + /* Return the list in ascending order */ + memory_map = &memory_map[map_entries - 1]; + list_for_each(lhandle, &efi_mem) { + struct efi_mem_list *lmem; + + lmem = list_entry(lhandle, struct efi_mem_list, link); + *memory_map = lmem->desc; + memory_map--; + } + + if (map_key) + *map_key = efi_memory_map_key; + + return EFI_SUCCESS; +} + +/** + * efi_add_conventional_memory_map() - add a RAM memory area to the map + * + * @ram_start: start address of a RAM memory area + * @ram_end: end address of a RAM memory area + * @ram_top: max address to be used as conventional memory + * Return: status code + */ +efi_status_t efi_add_conventional_memory_map(u64 ram_start, u64 ram_end, + u64 ram_top) +{ + u64 pages; + + /* Remove partial pages */ + ram_end &= ~EFI_PAGE_MASK; + ram_start = (ram_start + EFI_PAGE_MASK) & ~EFI_PAGE_MASK; + + if (ram_end <= ram_start) { + /* Invalid mapping */ + return EFI_INVALID_PARAMETER; + } + + pages = (ram_end - ram_start) >> EFI_PAGE_SHIFT; + + efi_add_memory_map(ram_start, pages, + EFI_CONVENTIONAL_MEMORY, false); + + /* + * Boards may indicate to the U-Boot memory core that they + * can not support memory above ram_top. Let's honor this + * in the efi_loader subsystem too by declaring any memory + * above ram_top as "already occupied by firmware". + */ + if (ram_top < ram_start) { + /* ram_top is before this region, reserve all */ + efi_add_memory_map(ram_start, pages, + EFI_BOOT_SERVICES_DATA, true); + } else if ((ram_top >= ram_start) && (ram_top < ram_end)) { + /* ram_top is inside this region, reserve parts */ + pages = (ram_end - ram_top) >> EFI_PAGE_SHIFT; + + efi_add_memory_map(ram_top, pages, + EFI_BOOT_SERVICES_DATA, true); + } + + return EFI_SUCCESS; +} + +__weak void efi_add_known_memory(void) +{ + u64 ram_top = board_get_usable_ram_top(0) & ~EFI_PAGE_MASK; + int i; + + /* + * ram_top is just outside mapped memory. So use an offset of one for + * mapping the sandbox address. + */ + ram_top = (uintptr_t)map_sysmem(ram_top - 1, 0) + 1; + + /* Fix for 32bit targets with ram_top at 4G */ + if (!ram_top) + ram_top = 0x100000000ULL; + + /* Add RAM */ + for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) { + u64 ram_end, ram_start; + + ram_start = (uintptr_t)map_sysmem(gd->bd->bi_dram[i].start, 0); + ram_end = ram_start + gd->bd->bi_dram[i].size; + + efi_add_conventional_memory_map(ram_start, ram_end, ram_top); + } +} + +/* Add memory regions for U-Boot's memory and for the runtime services code */ +static void add_u_boot_and_runtime(void) +{ + unsigned long runtime_start, runtime_end, runtime_pages; + unsigned long runtime_mask = EFI_PAGE_MASK; + unsigned long uboot_start, uboot_pages; + unsigned long uboot_stack_size = 16 * 1024 * 1024; + + /* Add U-Boot */ + uboot_start = ((uintptr_t)map_sysmem(gd->start_addr_sp, 0) - + uboot_stack_size) & ~EFI_PAGE_MASK; + uboot_pages = ((uintptr_t)map_sysmem(gd->ram_top - 1, 0) - + uboot_start + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; + efi_add_memory_map(uboot_start, uboot_pages, EFI_LOADER_DATA, false); + +#if defined(__aarch64__) + /* + * Runtime Services must be 64KiB aligned according to the + * "AArch64 Platforms" section in the UEFI spec (2.7+). + */ + + runtime_mask = SZ_64K - 1; +#endif + + /* + * Add Runtime Services. We mark surrounding boottime code as runtime as + * well to fulfill the runtime alignment constraints but avoid padding. + */ + runtime_start = (ulong)&__efi_runtime_start & ~runtime_mask; + runtime_end = (ulong)&__efi_runtime_stop; + runtime_end = (runtime_end + runtime_mask) & ~runtime_mask; + runtime_pages = (runtime_end - runtime_start) >> EFI_PAGE_SHIFT; + efi_add_memory_map(runtime_start, runtime_pages, + EFI_RUNTIME_SERVICES_CODE, false); +} + +int efi_memory_init(void) +{ + efi_add_known_memory(); + + add_u_boot_and_runtime(); + +#ifdef CONFIG_EFI_LOADER_BOUNCE_BUFFER + /* Request a 32bit 64MB bounce buffer region */ + uint64_t efi_bounce_buffer_addr = 0xffffffff; + + if (efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS, EFI_LOADER_DATA, + (64 * 1024 * 1024) >> EFI_PAGE_SHIFT, + &efi_bounce_buffer_addr) != EFI_SUCCESS) + return -1; + + efi_bounce_buffer = (void*)(uintptr_t)efi_bounce_buffer_addr; +#endif + + return 0; +} diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c new file mode 100644 index 00000000..82d25958 --- /dev/null +++ b/lib/efi_loader/efi_net.c @@ -0,0 +1,946 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Simple network protocol + * PXE base code protocol + * + * Copyright (c) 2016 Alexander Graf + * + * The simple network protocol has the following statuses and services + * to move between them: + * + * Start(): EfiSimpleNetworkStopped -> EfiSimpleNetworkStarted + * Initialize(): EfiSimpleNetworkStarted -> EfiSimpleNetworkInitialized + * Shutdown(): EfiSimpleNetworkInitialized -> EfiSimpleNetworkStarted + * Stop(): EfiSimpleNetworkStarted -> EfiSimpleNetworkStopped + * Reset(): EfiSimpleNetworkInitialized -> EfiSimpleNetworkInitialized + */ + +#include <common.h> +#include <efi_loader.h> +#include <malloc.h> + +static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID; +static const efi_guid_t efi_pxe_base_code_protocol_guid = + EFI_PXE_BASE_CODE_PROTOCOL_GUID; +static struct efi_pxe_packet *dhcp_ack; +static bool new_rx_packet; +static void *new_tx_packet; +static void *transmit_buffer; + +/* + * The notification function of this event is called in every timer cycle + * to check if a new network packet has been received. + */ +static struct efi_event *network_timer_event; +/* + * This event is signaled when a packet has been received. + */ +static struct efi_event *wait_for_packet; + +/** + * struct efi_net_obj - EFI object representing a network interface + * + * @header: EFI object header + * @net: simple network protocol interface + * @net_mode: status of the network interface + * @pxe: PXE base code protocol interface + * @pxe_mode: status of the PXE base code protocol + */ +struct efi_net_obj { + struct efi_object header; + struct efi_simple_network net; + struct efi_simple_network_mode net_mode; + struct efi_pxe_base_code_protocol pxe; + struct efi_pxe_mode pxe_mode; +}; + +/* + * efi_net_start() - start the network interface + * + * This function implements the Start service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_net_start(struct efi_simple_network *this) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p", this); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (this->mode->state != EFI_NETWORK_STOPPED) { + ret = EFI_ALREADY_STARTED; + } else { + this->int_status = 0; + wait_for_packet->is_signaled = false; + this->mode->state = EFI_NETWORK_STARTED; + } +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_stop() - stop the network interface + * + * This function implements the Stop service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_net_stop(struct efi_simple_network *this) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p", this); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (this->mode->state == EFI_NETWORK_STOPPED) { + ret = EFI_NOT_STARTED; + } else { + /* Disable hardware and put it into the reset state */ + eth_halt(); + this->mode->state = EFI_NETWORK_STOPPED; + } +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_initialize() - initialize the network interface + * + * This function implements the Initialize service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @extra_rx: extra receive buffer to be allocated + * @extra_tx: extra transmit buffer to be allocated + * Return: status code + */ +static efi_status_t EFIAPI efi_net_initialize(struct efi_simple_network *this, + ulong extra_rx, ulong extra_tx) +{ + int ret; + efi_status_t r = EFI_SUCCESS; + + EFI_ENTRY("%p, %lx, %lx", this, extra_rx, extra_tx); + + /* Check parameters */ + if (!this) { + r = EFI_INVALID_PARAMETER; + goto out; + } + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + case EFI_NETWORK_STARTED: + break; + default: + r = EFI_NOT_STARTED; + goto out; + } + + /* Setup packet buffers */ + net_init(); + /* Disable hardware and put it into the reset state */ + eth_halt(); + /* Set current device according to environment variables */ + eth_set_current(); + /* Get hardware ready for send and receive operations */ + ret = eth_init(); + if (ret < 0) { + eth_halt(); + this->mode->state = EFI_NETWORK_STOPPED; + r = EFI_DEVICE_ERROR; + goto out; + } else { + this->int_status = 0; + wait_for_packet->is_signaled = false; + this->mode->state = EFI_NETWORK_INITIALIZED; + } +out: + return EFI_EXIT(r); +} + +/* + * efi_net_reset() - reinitialize the network interface + * + * This function implements the Reset service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @extended_verification: execute exhaustive verification + * Return: status code + */ +static efi_status_t EFIAPI efi_net_reset(struct efi_simple_network *this, + int extended_verification) +{ + efi_status_t ret; + + EFI_ENTRY("%p, %x", this, extended_verification); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + break; + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + default: + ret = EFI_DEVICE_ERROR; + goto out; + } + + this->mode->state = EFI_NETWORK_STARTED; + ret = EFI_CALL(efi_net_initialize(this, 0, 0)); +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_shutdown() - shut down the network interface + * + * This function implements the Shutdown service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * Return: status code + */ +static efi_status_t EFIAPI efi_net_shutdown(struct efi_simple_network *this) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p", this); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + break; + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + default: + ret = EFI_DEVICE_ERROR; + goto out; + } + + eth_halt(); + this->int_status = 0; + wait_for_packet->is_signaled = false; + this->mode->state = EFI_NETWORK_STARTED; + +out: + return EFI_EXIT(ret); +} + +/* + * efi_net_receive_filters() - mange multicast receive filters + * + * This function implements the ReceiveFilters service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @enable: bit mask of receive filters to enable + * @disable: bit mask of receive filters to disable + * @reset_mcast_filter: true resets contents of the filters + * @mcast_filter_count: number of hardware MAC addresses in the new filters list + * @mcast_filter: list of new filters + * Return: status code + */ +static efi_status_t EFIAPI efi_net_receive_filters + (struct efi_simple_network *this, u32 enable, u32 disable, + int reset_mcast_filter, ulong mcast_filter_count, + struct efi_mac_address *mcast_filter) +{ + EFI_ENTRY("%p, %x, %x, %x, %lx, %p", this, enable, disable, + reset_mcast_filter, mcast_filter_count, mcast_filter); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * efi_net_station_address() - set the hardware MAC address + * + * This function implements the StationAddress service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @reset: if true reset the address to default + * @new_mac: new MAC address + * Return: status code + */ +static efi_status_t EFIAPI efi_net_station_address + (struct efi_simple_network *this, int reset, + struct efi_mac_address *new_mac) +{ + EFI_ENTRY("%p, %x, %p", this, reset, new_mac); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * efi_net_statistics() - reset or collect statistics of the network interface + * + * This function implements the Statistics service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @reset: if true, the statistics are reset + * @stat_size: size of the statistics table + * @stat_table: table to receive the statistics + * Return: status code + */ +static efi_status_t EFIAPI efi_net_statistics(struct efi_simple_network *this, + int reset, ulong *stat_size, + void *stat_table) +{ + EFI_ENTRY("%p, %x, %p, %p", this, reset, stat_size, stat_table); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * efi_net_mcastiptomac() - translate multicast IP address to MAC address + * + * This function implements the MCastIPtoMAC service of the + * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface + * (UEFI) specification for details. + * + * @this: pointer to the protocol instance + * @ipv6: true if the IP address is an IPv6 address + * @ip: IP address + * @mac: MAC address + * Return: status code + */ +static efi_status_t EFIAPI efi_net_mcastiptomac(struct efi_simple_network *this, + int ipv6, + struct efi_ip_address *ip, + struct efi_mac_address *mac) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %x, %p, %p", this, ipv6, ip, mac); + + if (!this || !ip || !mac) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (ipv6) { + ret = EFI_UNSUPPORTED; + goto out; + } + + /* Multi-cast addresses are in the range 224.0.0.0 - 239.255.255.255 */ + if ((ip->ip_addr[0] & 0xf0) != 0xe0) { + ret = EFI_INVALID_PARAMETER; + goto out; + }; + + switch (this->mode->state) { + case EFI_NETWORK_INITIALIZED: + case EFI_NETWORK_STARTED: + break; + default: + ret = EFI_NOT_STARTED; + goto out; + } + + memset(mac, 0, sizeof(struct efi_mac_address)); + + /* + * Copy lower 23 bits of IPv4 multi-cast address + * RFC 1112, RFC 7042 2.1.1. + */ + mac->mac_addr[0] = 0x01; + mac->mac_addr[1] = 0x00; + mac->mac_addr[2] = 0x5E; + mac->mac_addr[3] = ip->ip_addr[1] & 0x7F; + mac->mac_addr[4] = ip->ip_addr[2]; + mac->mac_addr[5] = ip->ip_addr[3]; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_nvdata() - read or write NVRAM + * + * This function implements the GetStatus service of the Simple Network + * Protocol. See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @read_write: true for read, false for write + * @offset: offset in NVRAM + * @buffer_size: size of buffer + * @buffer: buffer + * Return: status code + */ +static efi_status_t EFIAPI efi_net_nvdata(struct efi_simple_network *this, + int read_write, ulong offset, + ulong buffer_size, char *buffer) +{ + EFI_ENTRY("%p, %x, %lx, %lx, %p", this, read_write, offset, buffer_size, + buffer); + + return EFI_EXIT(EFI_UNSUPPORTED); +} + +/** + * efi_net_get_status() - get interrupt status + * + * This function implements the GetStatus service of the Simple Network + * Protocol. See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @int_status: interface status + * @txbuf: transmission buffer + */ +static efi_status_t EFIAPI efi_net_get_status(struct efi_simple_network *this, + u32 *int_status, void **txbuf) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p", this, int_status, txbuf); + + efi_timer_check(); + + /* Check parameters */ + if (!this) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + switch (this->mode->state) { + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + case EFI_NETWORK_STARTED: + ret = EFI_DEVICE_ERROR; + goto out; + default: + break; + } + + if (int_status) { + *int_status = this->int_status; + this->int_status = 0; + } + if (txbuf) + *txbuf = new_tx_packet; + + new_tx_packet = NULL; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_transmit() - transmit a packet + * + * This function implements the Transmit service of the Simple Network Protocol. + * See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @header_size: size of the media header + * @buffer_size: size of the buffer to receive the packet + * @buffer: buffer to receive the packet + * @src_addr: source hardware MAC address + * @dest_addr: destination hardware MAC address + * @protocol: type of header to build + * Return: status code + */ +static efi_status_t EFIAPI efi_net_transmit + (struct efi_simple_network *this, size_t header_size, + size_t buffer_size, void *buffer, + struct efi_mac_address *src_addr, + struct efi_mac_address *dest_addr, u16 *protocol) +{ + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %lu, %lu, %p, %p, %p, %p", this, + (unsigned long)header_size, (unsigned long)buffer_size, + buffer, src_addr, dest_addr, protocol); + + efi_timer_check(); + + /* Check parameters */ + if (!this || !buffer) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* We do not support jumbo packets */ + if (buffer_size > PKTSIZE_ALIGN) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + /* At least the IP header has to fit into the buffer */ + if (buffer_size < this->mode->media_header_size) { + ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + + /* + * TODO: + * Support VLANs. Use net_set_ether() for copying the header. Use a + * U_BOOT_ENV_CALLBACK to update the media header size. + */ + if (header_size) { + struct ethernet_hdr *header = buffer; + + if (!dest_addr || !protocol || + header_size != this->mode->media_header_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (!src_addr) + src_addr = &this->mode->current_address; + + memcpy(header->et_dest, dest_addr, ARP_HLEN); + memcpy(header->et_src, src_addr, ARP_HLEN); + header->et_protlen = htons(*protocol); + } + + switch (this->mode->state) { + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + case EFI_NETWORK_STARTED: + ret = EFI_DEVICE_ERROR; + goto out; + default: + break; + } + + /* Ethernet packets always fit, just bounce */ + memcpy(transmit_buffer, buffer, buffer_size); + net_send_packet(transmit_buffer, buffer_size); + + new_tx_packet = buffer; + this->int_status |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_receive() - receive a packet from a network interface + * + * This function implements the Receive service of the Simple Network Protocol. + * See the UEFI spec for details. + * + * @this: the instance of the Simple Network Protocol + * @header_size: size of the media header + * @buffer_size: size of the buffer to receive the packet + * @buffer: buffer to receive the packet + * @src_addr: source MAC address + * @dest_addr: destination MAC address + * @protocol: protocol + * Return: status code + */ +static efi_status_t EFIAPI efi_net_receive + (struct efi_simple_network *this, size_t *header_size, + size_t *buffer_size, void *buffer, + struct efi_mac_address *src_addr, + struct efi_mac_address *dest_addr, u16 *protocol) +{ + efi_status_t ret = EFI_SUCCESS; + struct ethernet_hdr *eth_hdr; + size_t hdr_size = sizeof(struct ethernet_hdr); + u16 protlen; + + EFI_ENTRY("%p, %p, %p, %p, %p, %p, %p", this, header_size, + buffer_size, buffer, src_addr, dest_addr, protocol); + + /* Execute events */ + efi_timer_check(); + + /* Check parameters */ + if (!this || !buffer || !buffer_size) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + switch (this->mode->state) { + case EFI_NETWORK_STOPPED: + ret = EFI_NOT_STARTED; + goto out; + case EFI_NETWORK_STARTED: + ret = EFI_DEVICE_ERROR; + goto out; + default: + break; + } + + if (!new_rx_packet) { + ret = EFI_NOT_READY; + goto out; + } + /* Fill export parameters */ + eth_hdr = (struct ethernet_hdr *)net_rx_packet; + protlen = ntohs(eth_hdr->et_protlen); + if (protlen == 0x8100) { + hdr_size += 4; + protlen = ntohs(*(u16 *)&net_rx_packet[hdr_size - 2]); + } + if (header_size) + *header_size = hdr_size; + if (dest_addr) + memcpy(dest_addr, eth_hdr->et_dest, ARP_HLEN); + if (src_addr) + memcpy(src_addr, eth_hdr->et_src, ARP_HLEN); + if (protocol) + *protocol = protlen; + if (*buffer_size < net_rx_packet_len) { + /* Packet doesn't fit, try again with bigger buffer */ + *buffer_size = net_rx_packet_len; + ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + /* Copy packet */ + memcpy(buffer, net_rx_packet, net_rx_packet_len); + *buffer_size = net_rx_packet_len; + new_rx_packet = 0; + this->int_status &= ~EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; +out: + return EFI_EXIT(ret); +} + +/** + * efi_net_set_dhcp_ack() - take note of a selected DHCP IP address + * + * This function is called by dhcp_handler(). + * + * @pkt: packet received by dhcp_handler() + * @len: length of the packet received + */ +void efi_net_set_dhcp_ack(void *pkt, int len) +{ + int maxsize = sizeof(*dhcp_ack); + + if (!dhcp_ack) + dhcp_ack = malloc(maxsize); + + memcpy(dhcp_ack, pkt, min(len, maxsize)); +} + +/** + * efi_net_push() - callback for received network packet + * + * This function is called when a network packet is received by eth_rx(). + * + * @pkt: network packet + * @len: length + */ +static void efi_net_push(void *pkt, int len) +{ + new_rx_packet = true; +} + +/** + * efi_network_timer_notify() - check if a new network packet has been received + * + * This notification function is called in every timer cycle. + * + * @event: the event for which this notification function is registered + * @context: event context - not used in this function + */ +static void EFIAPI efi_network_timer_notify(struct efi_event *event, + void *context) +{ + struct efi_simple_network *this = (struct efi_simple_network *)context; + + EFI_ENTRY("%p, %p", event, context); + + /* + * Some network drivers do not support calling eth_rx() before + * initialization. + */ + if (!this || this->mode->state != EFI_NETWORK_INITIALIZED) + goto out; + + if (!new_rx_packet) { + push_packet = efi_net_push; + eth_rx(); + push_packet = NULL; + if (new_rx_packet) { + /* Check that we at least received an Ethernet header */ + if (net_rx_packet_len >= + sizeof(struct ethernet_hdr)) { + this->int_status |= + EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; + wait_for_packet->is_signaled = true; + } else { + new_rx_packet = 0; + } + } + } +out: + EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_pxe_base_code_start( + struct efi_pxe_base_code_protocol *this, + u8 use_ipv6) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_stop( + struct efi_pxe_base_code_protocol *this) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_dhcp( + struct efi_pxe_base_code_protocol *this, + u8 sort_offers) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_discover( + struct efi_pxe_base_code_protocol *this, + u16 type, u16 *layer, u8 bis, + struct efi_pxe_base_code_discover_info *info) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_mtftp( + struct efi_pxe_base_code_protocol *this, + u32 operation, void *buffer_ptr, + u8 overwrite, efi_uintn_t *buffer_size, + struct efi_ip_address server_ip, char *filename, + struct efi_pxe_base_code_mtftp_info *info, + u8 dont_use_buffer) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_udp_write( + struct efi_pxe_base_code_protocol *this, + u16 op_flags, struct efi_ip_address *dest_ip, + u16 *dest_port, + struct efi_ip_address *gateway_ip, + struct efi_ip_address *src_ip, u16 *src_port, + efi_uintn_t *header_size, void *header_ptr, + efi_uintn_t *buffer_size, void *buffer_ptr) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_udp_read( + struct efi_pxe_base_code_protocol *this, + u16 op_flags, struct efi_ip_address *dest_ip, + u16 *dest_port, struct efi_ip_address *src_ip, + u16 *src_port, efi_uintn_t *header_size, + void *header_ptr, efi_uintn_t *buffer_size, + void *buffer_ptr) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_ip_filter( + struct efi_pxe_base_code_protocol *this, + struct efi_pxe_base_code_filter *new_filter) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_arp( + struct efi_pxe_base_code_protocol *this, + struct efi_ip_address *ip_addr, + struct efi_mac_address *mac_addr) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_parameters( + struct efi_pxe_base_code_protocol *this, + u8 *new_auto_arp, u8 *new_send_guid, + u8 *new_ttl, u8 *new_tos, + u8 *new_make_callback) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_station_ip( + struct efi_pxe_base_code_protocol *this, + struct efi_ip_address *new_station_ip, + struct efi_ip_address *new_subnet_mask) +{ + return EFI_UNSUPPORTED; +} + +static efi_status_t EFIAPI efi_pxe_base_code_set_packets( + struct efi_pxe_base_code_protocol *this, + u8 *new_dhcp_discover_valid, + u8 *new_dhcp_ack_received, + u8 *new_proxy_offer_received, + u8 *new_pxe_discover_valid, + u8 *new_pxe_reply_received, + u8 *new_pxe_bis_reply_received, + EFI_PXE_BASE_CODE_PACKET *new_dchp_discover, + EFI_PXE_BASE_CODE_PACKET *new_dhcp_acc, + EFI_PXE_BASE_CODE_PACKET *new_proxy_offer, + EFI_PXE_BASE_CODE_PACKET *new_pxe_discover, + EFI_PXE_BASE_CODE_PACKET *new_pxe_reply, + EFI_PXE_BASE_CODE_PACKET *new_pxe_bis_reply) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_net_register() - register the simple network protocol + * + * This gets called from do_bootefi_exec(). + */ +efi_status_t efi_net_register(void) +{ + struct efi_net_obj *netobj = NULL; + efi_status_t r; + + if (!eth_get_dev()) { + /* No network device active, don't expose any */ + return EFI_SUCCESS; + } + + /* We only expose the "active" network device, so one is enough */ + netobj = calloc(1, sizeof(*netobj)); + if (!netobj) + goto out_of_resources; + + /* Allocate an aligned transmit buffer */ + transmit_buffer = calloc(1, PKTSIZE_ALIGN + PKTALIGN); + if (!transmit_buffer) + goto out_of_resources; + transmit_buffer = (void *)ALIGN((uintptr_t)transmit_buffer, PKTALIGN); + + /* Hook net up to the device list */ + efi_add_handle(&netobj->header); + + /* Fill in object data */ + r = efi_add_protocol(&netobj->header, &efi_net_guid, + &netobj->net); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; + r = efi_add_protocol(&netobj->header, &efi_guid_device_path, + efi_dp_from_eth()); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; + r = efi_add_protocol(&netobj->header, &efi_pxe_base_code_protocol_guid, + &netobj->pxe); + if (r != EFI_SUCCESS) + goto failure_to_add_protocol; + netobj->net.revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION; + netobj->net.start = efi_net_start; + netobj->net.stop = efi_net_stop; + netobj->net.initialize = efi_net_initialize; + netobj->net.reset = efi_net_reset; + netobj->net.shutdown = efi_net_shutdown; + netobj->net.receive_filters = efi_net_receive_filters; + netobj->net.station_address = efi_net_station_address; + netobj->net.statistics = efi_net_statistics; + netobj->net.mcastiptomac = efi_net_mcastiptomac; + netobj->net.nvdata = efi_net_nvdata; + netobj->net.get_status = efi_net_get_status; + netobj->net.transmit = efi_net_transmit; + netobj->net.receive = efi_net_receive; + netobj->net.mode = &netobj->net_mode; + netobj->net_mode.state = EFI_NETWORK_STOPPED; + memcpy(netobj->net_mode.current_address.mac_addr, eth_get_ethaddr(), 6); + netobj->net_mode.hwaddr_size = ARP_HLEN; + netobj->net_mode.media_header_size = ETHER_HDR_SIZE; + netobj->net_mode.max_packet_size = PKTSIZE; + netobj->net_mode.if_type = ARP_ETHER; + + netobj->pxe.revision = EFI_PXE_BASE_CODE_PROTOCOL_REVISION; + netobj->pxe.start = efi_pxe_base_code_start; + netobj->pxe.stop = efi_pxe_base_code_stop; + netobj->pxe.dhcp = efi_pxe_base_code_dhcp; + netobj->pxe.discover = efi_pxe_base_code_discover; + netobj->pxe.mtftp = efi_pxe_base_code_mtftp; + netobj->pxe.udp_write = efi_pxe_base_code_udp_write; + netobj->pxe.udp_read = efi_pxe_base_code_udp_read; + netobj->pxe.set_ip_filter = efi_pxe_base_code_set_ip_filter; + netobj->pxe.arp = efi_pxe_base_code_arp; + netobj->pxe.set_parameters = efi_pxe_base_code_set_parameters; + netobj->pxe.set_station_ip = efi_pxe_base_code_set_station_ip; + netobj->pxe.set_packets = efi_pxe_base_code_set_packets; + netobj->pxe.mode = &netobj->pxe_mode; + if (dhcp_ack) + netobj->pxe_mode.dhcp_ack = *dhcp_ack; + + /* + * Create WaitForPacket event. + */ + r = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, + efi_network_timer_notify, NULL, NULL, + &wait_for_packet); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register network event\n"); + return r; + } + netobj->net.wait_for_packet = wait_for_packet; + /* + * Create a timer event. + * + * The notification function is used to check if a new network packet + * has been received. + * + * iPXE is running at TPL_CALLBACK most of the time. Use a higher TPL. + */ + r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY, + efi_network_timer_notify, &netobj->net, NULL, + &network_timer_event); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register network event\n"); + return r; + } + /* Network is time critical, create event in every timer cycle */ + r = efi_set_timer(network_timer_event, EFI_TIMER_PERIODIC, 0); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to set network timer\n"); + return r; + } + + return EFI_SUCCESS; +failure_to_add_protocol: + printf("ERROR: Failure to add protocol\n"); + return r; +out_of_resources: + free(netobj); + /* free(transmit_buffer) not needed yet */ + printf("ERROR: Out of memory\n"); + return EFI_OUT_OF_RESOURCES; +} diff --git a/lib/efi_loader/efi_root_node.c b/lib/efi_loader/efi_root_node.c new file mode 100644 index 00000000..f68b0fdc --- /dev/null +++ b/lib/efi_loader/efi_root_node.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Root node for system services + * + * Copyright (c) 2018 Heinrich Schuchardt + */ + +#include <common.h> +#include <malloc.h> +#include <efi_loader.h> + +const efi_guid_t efi_u_boot_guid = U_BOOT_GUID; + +efi_handle_t efi_root = NULL; + +struct efi_root_dp { + struct efi_device_path_vendor vendor; + struct efi_device_path end; +} __packed; + +/** + * efi_root_node_register() - create root node + * + * Create the root node on which we install all protocols that are + * not related to a loaded image or a driver. + * + * Return: status code + */ +efi_status_t efi_root_node_register(void) +{ + efi_status_t ret; + struct efi_root_dp *dp; + + /* Create device path protocol */ + dp = calloc(1, sizeof(*dp)); + if (!dp) + return EFI_OUT_OF_RESOURCES; + + /* Fill vendor node */ + dp->vendor.dp.type = DEVICE_PATH_TYPE_HARDWARE_DEVICE; + dp->vendor.dp.sub_type = DEVICE_PATH_SUB_TYPE_VENDOR; + dp->vendor.dp.length = sizeof(struct efi_device_path_vendor); + dp->vendor.guid = efi_u_boot_guid; + + /* Fill end node */ + dp->end.type = DEVICE_PATH_TYPE_END; + dp->end.sub_type = DEVICE_PATH_SUB_TYPE_END; + dp->end.length = sizeof(struct efi_device_path); + + /* Create root node and install protocols */ + ret = EFI_CALL(efi_install_multiple_protocol_interfaces + (&efi_root, + /* Device path protocol */ + &efi_guid_device_path, dp, +#if CONFIG_IS_ENABLED(EFI_DEVICE_PATH_TO_TEXT) + /* Device path to text protocol */ + &efi_guid_device_path_to_text_protocol, + (void *)&efi_device_path_to_text, +#endif + /* Device path utilities protocol */ + &efi_guid_device_path_utilities_protocol, + (void *)&efi_device_path_utilities, +#if CONFIG_IS_ENABLED(EFI_UNICODE_COLLATION_PROTOCOL2) +#if CONFIG_IS_ENABLED(EFI_UNICODE_COLLATION_PROTOCOL) + /* Deprecated Unicode collation protocol */ + &efi_guid_unicode_collation_protocol, + (void *)&efi_unicode_collation_protocol, +#endif + /* Current Unicode collation protocol */ + &efi_guid_unicode_collation_protocol2, + (void *)&efi_unicode_collation_protocol2, +#endif +#if CONFIG_IS_ENABLED(EFI_LOADER_HII) + /* HII string protocol */ + &efi_guid_hii_string_protocol, + (void *)&efi_hii_string, + /* HII database protocol */ + &efi_guid_hii_database_protocol, + (void *)&efi_hii_database, + /* HII configuration routing protocol */ + &efi_guid_hii_config_routing_protocol, + (void *)&efi_hii_config_routing, +#endif + NULL)); + efi_root->type = EFI_OBJECT_TYPE_U_BOOT_FIRMWARE; + return ret; +} diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c new file mode 100644 index 00000000..df0485cd --- /dev/null +++ b/lib/efi_loader/efi_runtime.c @@ -0,0 +1,883 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application runtime services + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <command.h> +#include <cpu_func.h> +#include <dm.h> +#include <elf.h> +#include <efi_loader.h> +#include <rtc.h> +#include <u-boot/crc.h> + +/* For manual relocation support */ +DECLARE_GLOBAL_DATA_PTR; + +struct efi_runtime_mmio_list { + struct list_head link; + void **ptr; + u64 paddr; + u64 len; +}; + +/* This list contains all runtime available mmio regions */ +LIST_HEAD(efi_runtime_mmio); + +static efi_status_t __efi_runtime EFIAPI efi_unimplemented(void); + +/* + * TODO(sjg@chromium.org): These defines and structures should come from the ELF + * header for each architecture (or a generic header) rather than being repeated + * here. + */ +#if defined(__aarch64__) +#define R_RELATIVE R_AARCH64_RELATIVE +#define R_MASK 0xffffffffULL +#define IS_RELA 1 +#elif defined(__arm__) +#define R_RELATIVE R_ARM_RELATIVE +#define R_MASK 0xffULL +#elif defined(__i386__) +#define R_RELATIVE R_386_RELATIVE +#define R_MASK 0xffULL +#elif defined(__x86_64__) +#define R_RELATIVE R_X86_64_RELATIVE +#define R_MASK 0xffffffffULL +#define IS_RELA 1 +#elif defined(__riscv) +#define R_RELATIVE R_RISCV_RELATIVE +#define R_MASK 0xffULL +#define IS_RELA 1 + +struct dyn_sym { + ulong foo1; + ulong addr; + u32 foo2; + u32 foo3; +}; +#if (__riscv_xlen == 32) +#define R_ABSOLUTE R_RISCV_32 +#define SYM_INDEX 8 +#elif (__riscv_xlen == 64) +#define R_ABSOLUTE R_RISCV_64 +#define SYM_INDEX 32 +#else +#error unknown riscv target +#endif +#else +#error Need to add relocation awareness +#endif + +struct elf_rel { + ulong *offset; + ulong info; +}; + +struct elf_rela { + ulong *offset; + ulong info; + long addend; +}; + +static __efi_runtime_data struct efi_mem_desc *efi_virtmap; +static __efi_runtime_data efi_uintn_t efi_descriptor_count; +static __efi_runtime_data efi_uintn_t efi_descriptor_size; + +/* + * EFI runtime code lives in two 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 + */ + +efi_status_t efi_init_runtime_supported(void) +{ + u16 efi_runtime_services_supported = + EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP | + EFI_RT_SUPPORTED_CONVERT_POINTER; + + /* + * This value must be synced with efi_runtime_detach_list + * as well as efi_runtime_services. + */ +#ifdef CONFIG_EFI_HAVE_RUNTIME_RESET + efi_runtime_services_supported |= EFI_RT_SUPPORTED_RESET_SYSTEM; +#endif + + return EFI_CALL(efi_set_variable(L"RuntimeServicesSupported", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(efi_runtime_services_supported), + &efi_runtime_services_supported)); +} + +/** + * efi_update_table_header_crc32() - Update crc32 in table header + * + * @table: EFI table + */ +void __efi_runtime efi_update_table_header_crc32(struct efi_table_hdr *table) +{ + table->crc32 = 0; + table->crc32 = crc32(0, (const unsigned char *)table, + table->headersize); +} + +/** + * efi_reset_system_boottime() - reset system at boot time + * + * This function implements the ResetSystem() runtime service before + * SetVirtualAddressMap() is called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @reset_type: type of reset to perform + * @reset_status: status code for the reset + * @data_size: size of reset_data + * @reset_data: information about the reset + */ +static void EFIAPI efi_reset_system_boottime( + enum efi_reset_type reset_type, + efi_status_t reset_status, + unsigned long data_size, void *reset_data) +{ + struct efi_event *evt; + + EFI_ENTRY("%d %lx %lx %p", reset_type, reset_status, data_size, + reset_data); + + /* Notify reset */ + list_for_each_entry(evt, &efi_events, link) { + if (evt->group && + !guidcmp(evt->group, + &efi_guid_event_group_reset_system)) { + efi_signal_event(evt); + break; + } + } + switch (reset_type) { + case EFI_RESET_COLD: + case EFI_RESET_WARM: + case EFI_RESET_PLATFORM_SPECIFIC: + do_reset(NULL, 0, 0, NULL); + break; + case EFI_RESET_SHUTDOWN: +#ifdef CONFIG_CMD_POWEROFF + do_poweroff(NULL, 0, 0, NULL); +#endif + break; + } + + while (1) { } +} + +/** + * efi_get_time_boottime() - get current time at boot time + * + * This function implements the GetTime runtime service before + * SetVirtualAddressMap() is called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to receive current time + * @capabilities: pointer to structure to receive RTC properties + * Returns: status code + */ +static efi_status_t EFIAPI efi_get_time_boottime( + struct efi_time *time, + struct efi_time_cap *capabilities) +{ +#ifdef CONFIG_EFI_GET_TIME + efi_status_t ret = EFI_SUCCESS; + struct rtc_time tm; + struct udevice *dev; + + EFI_ENTRY("%p %p", time, capabilities); + + if (!time) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + if (uclass_get_device(UCLASS_RTC, 0, &dev) || + dm_rtc_get(dev, &tm)) { + ret = EFI_UNSUPPORTED; + goto out; + } + if (dm_rtc_get(dev, &tm)) { + ret = EFI_DEVICE_ERROR; + goto out; + } + + 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->second = tm.tm_sec; + if (tm.tm_isdst) + time->daylight = + EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT; + time->timezone = EFI_UNSPECIFIED_TIMEZONE; + + if (capabilities) { + /* Set reasonable dummy values */ + capabilities->resolution = 1; /* 1 Hz */ + capabilities->accuracy = 100000000; /* 100 ppm */ + capabilities->sets_to_zero = false; + } +out: + return EFI_EXIT(ret); +#else + EFI_ENTRY("%p %p", time, capabilities); + return EFI_EXIT(EFI_UNSUPPORTED); +#endif +} + +#ifdef CONFIG_EFI_SET_TIME + +/** + * efi_validate_time() - checks if timestamp is valid + * + * @time: timestamp to validate + * Returns: 0 if timestamp is valid, 1 otherwise + */ +static int efi_validate_time(struct efi_time *time) +{ + return (!time || + time->year < 1900 || time->year > 9999 || + !time->month || time->month > 12 || !time->day || + time->day > rtc_month_days(time->month - 1, time->year) || + time->hour > 23 || time->minute > 59 || time->second > 59 || + time->nanosecond > 999999999 || + time->daylight & + ~(EFI_TIME_IN_DAYLIGHT | EFI_TIME_ADJUST_DAYLIGHT) || + ((time->timezone < -1440 || time->timezone > 1440) && + time->timezone != EFI_UNSPECIFIED_TIMEZONE)); +} + +#endif + +/** + * efi_set_time_boottime() - set current time + * + * This function implements the SetTime() runtime service before + * SetVirtualAddressMap() is called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to with current time + * Returns: status code + */ +static efi_status_t EFIAPI efi_set_time_boottime(struct efi_time *time) +{ +#ifdef CONFIG_EFI_SET_TIME + efi_status_t ret = EFI_SUCCESS; + struct rtc_time tm; + struct udevice *dev; + + EFI_ENTRY("%p", time); + + if (efi_validate_time(time)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (uclass_get_device(UCLASS_RTC, 0, &dev)) { + ret = EFI_UNSUPPORTED; + goto out; + } + + memset(&tm, 0, sizeof(tm)); + tm.tm_year = time->year; + tm.tm_mon = time->month; + tm.tm_mday = time->day; + tm.tm_hour = time->hour; + tm.tm_min = time->minute; + tm.tm_sec = time->second; + tm.tm_isdst = time->daylight == + (EFI_TIME_ADJUST_DAYLIGHT | EFI_TIME_IN_DAYLIGHT); + /* Calculate day of week */ + rtc_calc_weekday(&tm); + + if (dm_rtc_set(dev, &tm)) + ret = EFI_DEVICE_ERROR; +out: + return EFI_EXIT(ret); +#else + EFI_ENTRY("%p", time); + return EFI_EXIT(EFI_UNSUPPORTED); +#endif +} +/** + * efi_reset_system() - reset system + * + * This function implements the ResetSystem() runtime service after + * SetVirtualAddressMap() is called. It only executes an endless loop. + * Boards may override the helpers below to implement reset functionality. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @reset_type: type of reset to perform + * @reset_status: status code for the reset + * @data_size: size of reset_data + * @reset_data: information about the reset + */ +void __weak __efi_runtime EFIAPI efi_reset_system( + enum efi_reset_type reset_type, + efi_status_t reset_status, + unsigned long data_size, void *reset_data) +{ + /* Nothing we can do */ + while (1) { } +} + +/** + * efi_reset_system_init() - initialize the reset driver + * + * Boards may override this function to initialize the reset driver. + */ +efi_status_t __weak efi_reset_system_init(void) +{ + return EFI_SUCCESS; +} + +/** + * efi_get_time() - get current time + * + * This function implements the GetTime runtime service after + * SetVirtualAddressMap() is called. As the U-Boot driver are not available + * anymore only an error code is returned. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to receive current time + * @capabilities: pointer to structure to receive RTC properties + * Returns: status code + */ +efi_status_t __weak __efi_runtime EFIAPI efi_get_time( + struct efi_time *time, + struct efi_time_cap *capabilities) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_set_time() - set current time + * + * This function implements the SetTime runtime service after + * SetVirtualAddressMap() is called. As the U-Boot driver are not available + * anymore only an error code is returned. + * + * See the Unified Extensible Firmware Interface (UEFI) specification + * for details. + * + * @time: pointer to structure to with current time + * Returns: status code + */ +efi_status_t __weak __efi_runtime EFIAPI efi_set_time(struct efi_time *time) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_is_runtime_service_pointer() - check if pointer points to runtime table + * + * @p: pointer to check + * Return: true if the pointer points to a service function pointer in the + * runtime table + */ +static bool efi_is_runtime_service_pointer(void *p) +{ + return (p >= (void *)&efi_runtime_services.get_time && + p <= (void *)&efi_runtime_services.query_variable_info) || + p == (void *)&efi_events.prev || + p == (void *)&efi_events.next; +} + +/** + * efi_runtime_detach() - detach unimplemented runtime functions + */ +void efi_runtime_detach(void) +{ + efi_runtime_services.reset_system = efi_reset_system; + efi_runtime_services.get_time = efi_get_time; + efi_runtime_services.set_time = efi_set_time; + + /* Update CRC32 */ + efi_update_table_header_crc32(&efi_runtime_services.hdr); +} + +/** + * efi_set_virtual_address_map_runtime() - change from physical to virtual + * mapping + * + * This function implements the SetVirtualAddressMap() runtime service after + * it is first called. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @memory_map_size: size of the virtual map + * @descriptor_size: size of an entry in the map + * @descriptor_version: version of the map entries + * @virtmap: virtual address mapping information + * Return: status code EFI_UNSUPPORTED + */ +static __efi_runtime efi_status_t EFIAPI efi_set_virtual_address_map_runtime( + efi_uintn_t memory_map_size, + efi_uintn_t descriptor_size, + uint32_t descriptor_version, + struct efi_mem_desc *virtmap) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_convert_pointer_runtime() - convert from physical to virtual pointer + * + * This function implements the ConvertPointer() runtime service after + * the first call to SetVirtualAddressMap(). + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @debug_disposition: indicates if pointer may be converted to NULL + * @address: pointer to be converted + * Return: status code EFI_UNSUPPORTED + */ +static __efi_runtime efi_status_t EFIAPI efi_convert_pointer_runtime( + efi_uintn_t debug_disposition, void **address) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_convert_pointer_runtime() - convert from physical to virtual pointer + * + * This function implements the ConvertPointer() runtime service until + * the first call to SetVirtualAddressMap(). + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @debug_disposition: indicates if pointer may be converted to NULL + * @address: pointer to be converted + * Return: status code EFI_UNSUPPORTED + */ +static __efi_runtime efi_status_t EFIAPI efi_convert_pointer( + efi_uintn_t debug_disposition, void **address) +{ + efi_physical_addr_t addr = (uintptr_t)*address; + efi_uintn_t i; + efi_status_t ret = EFI_NOT_FOUND; + + EFI_ENTRY("%zu %p", debug_disposition, address); + + if (!efi_virtmap) { + ret = EFI_UNSUPPORTED; + goto out; + } + + if (!address) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + for (i = 0; i < efi_descriptor_count; i++) { + struct efi_mem_desc *map = (void *)efi_virtmap + + (efi_descriptor_size * i); + + if (addr >= map->physical_start && + (addr < map->physical_start + + (map->num_pages << EFI_PAGE_SHIFT))) { + *address = (void *)(uintptr_t) + (addr + map->virtual_start - + map->physical_start); + + ret = EFI_SUCCESS; + break; + } + } + +out: + return EFI_EXIT(ret); +} + +static __efi_runtime void efi_relocate_runtime_table(ulong offset) +{ + ulong patchoff; + void **pos; + + /* Relocate the runtime services pointers */ + patchoff = offset - gd->relocaddr; + for (pos = (void **)&efi_runtime_services.get_time; + pos <= (void **)&efi_runtime_services.query_variable_info; ++pos) { + if (*pos) + *pos += patchoff; + } + + /* + * The entry for SetVirtualAddress() must point to a physical address. + * After the first execution the service must return EFI_UNSUPPORTED. + */ + efi_runtime_services.set_virtual_address_map = + &efi_set_virtual_address_map_runtime; + + /* + * The entry for ConvertPointer() must point to a physical address. + * The service is not usable after SetVirtualAddress(). + */ + efi_runtime_services.convert_pointer = &efi_convert_pointer_runtime; + + /* + * TODO: Update UEFI variable RuntimeServicesSupported removing flags + * EFI_RT_SUPPORTED_SET_VIRTUAL_ADDRESS_MAP and + * EFI_RT_SUPPORTED_CONVERT_POINTER as required by the UEFI spec 2.8. + */ + + /* Update CRC32 */ + efi_update_table_header_crc32(&efi_runtime_services.hdr); +} + +/* 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 + + debug("%s: Relocating to offset=%lx\n", __func__, offset); + 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; + + /* + * The runtime services table is updated in + * efi_relocate_runtime_table() + */ + if (map && efi_is_runtime_service_pointer(p)) + continue; + + debug("%s: rel->info=%#lx *p=%#lx rel->offset=%p\n", __func__, + rel->info, *p, rel->offset); + + switch (rel->info & R_MASK) { + case R_RELATIVE: +#ifdef IS_RELA + newaddr = rel->addend + offset - CONFIG_SYS_TEXT_BASE; +#else + newaddr = *p - lastoff + offset; +#endif + break; +#ifdef R_ABSOLUTE + case R_ABSOLUTE: { + ulong symidx = rel->info >> SYM_INDEX; + extern struct dyn_sym __dyn_sym_start[]; + newaddr = __dyn_sym_start[symidx].addr + offset; +#ifdef IS_RELA + newaddr -= CONFIG_SYS_TEXT_BASE; +#endif + break; + } +#endif + default: + printf("%s: Unknown relocation type %llx\n", + __func__, rel->info & R_MASK); + continue; + } + + /* Check if the relocation is inside bounds */ + if (map && ((newaddr < map->virtual_start) || + newaddr > (map->virtual_start + + (map->num_pages << EFI_PAGE_SHIFT)))) { + printf("%s: Relocation at %p is out of range (%lx)\n", + __func__, p, newaddr); + continue; + } + + debug("%s: Setting %p to %lx\n", __func__, p, newaddr); + *p = newaddr; + flush_dcache_range((ulong)p & ~(EFI_CACHELINE_SIZE - 1), + ALIGN((ulong)&p[1], EFI_CACHELINE_SIZE)); + } + +#ifndef IS_RELA + lastoff = offset; +#endif + + invalidate_icache_all(); +} + +/** + * efi_set_virtual_address_map() - change from physical to virtual mapping + * + * This function implements the SetVirtualAddressMap() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @memory_map_size: size of the virtual map + * @descriptor_size: size of an entry in the map + * @descriptor_version: version of the map entries + * @virtmap: virtual address mapping information + * Return: status code + */ +static efi_status_t EFIAPI efi_set_virtual_address_map( + efi_uintn_t memory_map_size, + efi_uintn_t descriptor_size, + uint32_t descriptor_version, + struct efi_mem_desc *virtmap) +{ + efi_uintn_t n = memory_map_size / descriptor_size; + efi_uintn_t i; + efi_status_t ret = EFI_INVALID_PARAMETER; + int rt_code_sections = 0; + struct efi_event *event; + + EFI_ENTRY("%zx %zx %x %p", memory_map_size, descriptor_size, + descriptor_version, virtmap); + + if (descriptor_version != EFI_MEMORY_DESCRIPTOR_VERSION || + descriptor_size < sizeof(struct efi_mem_desc)) + goto out; + + efi_virtmap = virtmap; + efi_descriptor_size = descriptor_size; + efi_descriptor_count = n; + + /* + * TODO: + * Further down we are cheating. While really we should implement + * SetVirtualAddressMap() events and ConvertPointer() to allow + * dynamically loaded drivers to expose runtime services, we don't + * today. + * + * So let's ensure we see exactly one single runtime section, as + * that is the built-in one. If we see more (or less), someone must + * have tried adding or removing to that which we don't support yet. + * In that case, let's better fail rather than expose broken runtime + * services. + */ + for (i = 0; i < n; i++) { + struct efi_mem_desc *map = (void*)virtmap + + (descriptor_size * i); + + if (map->type == EFI_RUNTIME_SERVICES_CODE) + rt_code_sections++; + } + + if (rt_code_sections != 1) { + /* + * We expose exactly one single runtime code section, so + * something is definitely going wrong. + */ + goto out; + } + + /* Notify EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE */ + list_for_each_entry(event, &efi_events, link) { + if (event->notify_function) + EFI_CALL_VOID(event->notify_function( + event, event->notify_context)); + } + + /* Rebind mmio pointers */ + for (i = 0; i < n; i++) { + struct efi_mem_desc *map = (void*)virtmap + + (descriptor_size * i); + struct list_head *lhandle; + efi_physical_addr_t map_start = map->physical_start; + efi_physical_addr_t map_len = map->num_pages << EFI_PAGE_SHIFT; + efi_physical_addr_t map_end = map_start + map_len; + u64 off = map->virtual_start - map_start; + + /* Adjust all mmio pointers in this region */ + list_for_each(lhandle, &efi_runtime_mmio) { + struct efi_runtime_mmio_list *lmmio; + + lmmio = list_entry(lhandle, + struct efi_runtime_mmio_list, + link); + if ((map_start <= lmmio->paddr) && + (map_end >= lmmio->paddr)) { + uintptr_t new_addr = lmmio->paddr + off; + *lmmio->ptr = (void *)new_addr; + } + } + if ((map_start <= (uintptr_t)systab.tables) && + (map_end >= (uintptr_t)systab.tables)) { + char *ptr = (char *)systab.tables; + + ptr += off; + systab.tables = (struct efi_configuration_table *)ptr; + } + } + + /* Relocate the runtime. See TODO above */ + 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 - + map->physical_start + gd->relocaddr; + + efi_relocate_runtime_table(new_offset); + efi_runtime_relocate(new_offset, map); + ret = EFI_SUCCESS; + goto out; + } + } + +out: + return EFI_EXIT(ret); +} + +/** + * efi_add_runtime_mmio() - add memory-mapped IO region + * + * This function adds a memory-mapped IO region to the memory map to make it + * available at runtime. + * + * @mmio_ptr: pointer to a pointer to the start of the memory-mapped + * IO region + * @len: size of the memory-mapped IO region + * Returns: status code + */ +efi_status_t efi_add_runtime_mmio(void *mmio_ptr, u64 len) +{ + struct efi_runtime_mmio_list *newmmio; + u64 pages = (len + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; + uint64_t addr = *(uintptr_t *)mmio_ptr; + efi_status_t ret; + + ret = efi_add_memory_map(addr, pages, EFI_MMAP_IO, false); + if (ret != EFI_SUCCESS) + return EFI_OUT_OF_RESOURCES; + + newmmio = calloc(1, sizeof(*newmmio)); + if (!newmmio) + return EFI_OUT_OF_RESOURCES; + newmmio->ptr = mmio_ptr; + newmmio->paddr = *(uintptr_t *)mmio_ptr; + newmmio->len = len; + list_add_tail(&newmmio->link, &efi_runtime_mmio); + + return EFI_SUCCESS; +} + +/* + * 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 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. + */ + +/** + * efi_unimplemented() - replacement function, returns EFI_UNSUPPORTED + * + * This function is used after SetVirtualAddressMap() is called as replacement + * for services that are not available anymore due to constraints of the U-Boot + * implementation. + * + * Return: EFI_UNSUPPORTED + */ +static efi_status_t __efi_runtime EFIAPI efi_unimplemented(void) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_update_capsule() - process information from operating system + * + * This function implements the UpdateCapsule() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @capsule_header_array: pointer to array of virtual pointers + * @capsule_count: number of pointers in capsule_header_array + * @scatter_gather_list: pointer to arry of physical pointers + * Returns: status code + */ +efi_status_t __efi_runtime EFIAPI efi_update_capsule( + struct efi_capsule_header **capsule_header_array, + efi_uintn_t capsule_count, + u64 scatter_gather_list) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_query_capsule_caps() - check if capsule is supported + * + * This function implements the QueryCapsuleCapabilities() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @capsule_header_array: pointer to array of virtual pointers + * @capsule_count: number of pointers in capsule_header_array + * @maximum_capsule_size: maximum capsule size + * @reset_type: type of reset needed for capsule update + * Returns: status code + */ +efi_status_t __efi_runtime EFIAPI efi_query_capsule_caps( + struct efi_capsule_header **capsule_header_array, + efi_uintn_t capsule_count, + u64 *maximum_capsule_size, + u32 *reset_type) +{ + return EFI_UNSUPPORTED; +} + +struct efi_runtime_services __efi_runtime_data efi_runtime_services = { + .hdr = { + .signature = EFI_RUNTIME_SERVICES_SIGNATURE, + .revision = EFI_SPECIFICATION_VERSION, + .headersize = sizeof(struct efi_runtime_services), + }, + .get_time = &efi_get_time_boottime, + .set_time = &efi_set_time_boottime, + .get_wakeup_time = (void *)&efi_unimplemented, + .set_wakeup_time = (void *)&efi_unimplemented, + .set_virtual_address_map = &efi_set_virtual_address_map, + .convert_pointer = efi_convert_pointer, + .get_variable = efi_get_variable, + .get_next_variable_name = efi_get_next_variable_name, + .set_variable = efi_set_variable, + .get_next_high_mono_count = (void *)&efi_unimplemented, + .reset_system = &efi_reset_system_boottime, + .update_capsule = efi_update_capsule, + .query_capsule_caps = efi_query_capsule_caps, + .query_variable_info = efi_query_variable_info, +}; diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c new file mode 100644 index 00000000..de7b616c --- /dev/null +++ b/lib/efi_loader/efi_setup.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI setup code + * + * Copyright (c) 2016-2018 Alexander Graf et al. + */ + +#include <common.h> +#include <bootm.h> +#include <efi_loader.h> + +#define OBJ_LIST_NOT_INITIALIZED 1 + +static efi_status_t efi_obj_list_initialized = OBJ_LIST_NOT_INITIALIZED; + +/* + * Allow unaligned memory access. + * + * This routine is overridden by architectures providing this feature. + */ +void __weak allow_unaligned(void) +{ +} + +/** + * efi_init_platform_lang() - define supported languages + * + * Set the PlatformLangCodes and PlatformLang variables. + * + * Return: status code + */ +static efi_status_t efi_init_platform_lang(void) +{ + efi_status_t ret; + efi_uintn_t data_size = 0; + char *lang = CONFIG_EFI_PLATFORM_LANG_CODES; + char *pos; + + /* + * Variable PlatformLangCodes defines the language codes that the + * machine can support. + */ + ret = EFI_CALL(efi_set_variable(L"PlatformLangCodes", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(CONFIG_EFI_PLATFORM_LANG_CODES), + CONFIG_EFI_PLATFORM_LANG_CODES)); + if (ret != EFI_SUCCESS) + goto out; + + /* + * Variable PlatformLang defines the language that the machine has been + * configured for. + */ + ret = EFI_CALL(efi_get_variable(L"PlatformLang", + &efi_global_variable_guid, + NULL, &data_size, &pos)); + if (ret == EFI_BUFFER_TOO_SMALL) { + /* The variable is already set. Do not change it. */ + ret = EFI_SUCCESS; + goto out; + } + + /* + * The list of supported languages is semicolon separated. Use the first + * language to initialize PlatformLang. + */ + pos = strchr(lang, ';'); + if (pos) + *pos = 0; + + ret = EFI_CALL(efi_set_variable(L"PlatformLang", + &efi_global_variable_guid, + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + 1 + strlen(lang), lang)); +out: + if (ret != EFI_SUCCESS) + printf("EFI: cannot initialize platform language settings\n"); + return ret; +} + +/** + * efi_init_obj_list() - Initialize and populate EFI object list + * + * Return: status code + */ +efi_status_t efi_init_obj_list(void) +{ + u64 os_indications_supported = 0; /* None */ + efi_status_t ret = EFI_SUCCESS; + + /* Initialize once only */ + if (efi_obj_list_initialized != OBJ_LIST_NOT_INITIALIZED) + return efi_obj_list_initialized; + + /* Allow unaligned memory access */ + allow_unaligned(); + + /* On ARM switch from EL3 or secure mode to EL2 or non-secure mode */ + switch_to_non_secure_mode(); + + /* Initialize variable services */ + ret = efi_init_variables(); + if (ret != EFI_SUCCESS) + goto out; + + /* Define supported languages */ + ret = efi_init_platform_lang(); + if (ret != EFI_SUCCESS) + goto out; + + /* Indicate supported features */ + ret = EFI_CALL(efi_set_variable(L"OsIndicationsSupported", + &efi_global_variable_guid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + sizeof(os_indications_supported), + &os_indications_supported)); + if (ret != EFI_SUCCESS) + goto out; + + /* Indicate supported runtime services */ + ret = efi_init_runtime_supported(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize system table */ + ret = efi_initialize_system_table(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize root node */ + ret = efi_root_node_register(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize EFI driver uclass */ + ret = efi_driver_init(); + if (ret != EFI_SUCCESS) + goto out; + + ret = efi_console_register(); + if (ret != EFI_SUCCESS) + goto out; +#ifdef CONFIG_PARTITIONS + ret = efi_disk_register(); + if (ret != EFI_SUCCESS) + goto out; +#endif +#if defined(CONFIG_LCD) || defined(CONFIG_DM_VIDEO) + ret = efi_gop_register(); + if (ret != EFI_SUCCESS) + goto out; +#endif +#ifdef CONFIG_NET + ret = efi_net_register(); + if (ret != EFI_SUCCESS) + goto out; +#endif +#ifdef CONFIG_GENERATE_ACPI_TABLE + ret = efi_acpi_register(); + if (ret != EFI_SUCCESS) + goto out; +#endif +#ifdef CONFIG_GENERATE_SMBIOS_TABLE + ret = efi_smbios_register(); + if (ret != EFI_SUCCESS) + goto out; +#endif + ret = efi_watchdog_register(); + if (ret != EFI_SUCCESS) + goto out; + + /* Initialize EFI runtime services */ + ret = efi_reset_system_init(); + if (ret != EFI_SUCCESS) + goto out; + +out: + efi_obj_list_initialized = ret; + return ret; +} diff --git a/lib/efi_loader/efi_smbios.c b/lib/efi_loader/efi_smbios.c new file mode 100644 index 00000000..a8148849 --- /dev/null +++ b/lib/efi_loader/efi_smbios.c @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI application tables support + * + * Copyright (c) 2016 Alexander Graf + */ + +#include <common.h> +#include <efi_loader.h> +#include <mapmem.h> +#include <smbios.h> + +static const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID; + +/* + * Install the SMBIOS table as a configuration table. + * + * @return status code + */ +efi_status_t efi_smbios_register(void) +{ + /* Map within the low 32 bits, to allow for 32bit SMBIOS tables */ + u64 dmi_addr = U32_MAX; + efi_status_t ret; + void *dmi; + + /* Reserve 4kiB page for SMBIOS */ + ret = efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS, + EFI_RUNTIME_SERVICES_DATA, 1, &dmi_addr); + + if (ret != EFI_SUCCESS) { + /* Could not find space in lowmem, use highmem instead */ + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_RUNTIME_SERVICES_DATA, 1, + &dmi_addr); + + if (ret != EFI_SUCCESS) + return ret; + } + + /* + * Generate SMBIOS tables - we know that efi_allocate_pages() returns + * a 4k-aligned address, so it is safe to assume that + * write_smbios_table() will write the table at that address. + * + * Note that on sandbox, efi_allocate_pages() unfortunately returns a + * pointer even though it uses a uint64_t type. Convert it. + */ + assert(!(dmi_addr & 0xf)); + dmi = (void *)(uintptr_t)dmi_addr; + write_smbios_table(map_to_sysmem(dmi)); + + /* And expose them to our EFI payload */ + return efi_install_configuration_table(&smbios_guid, dmi); +} diff --git a/lib/efi_loader/efi_unicode_collation.c b/lib/efi_loader/efi_unicode_collation.c new file mode 100644 index 00000000..c700be87 --- /dev/null +++ b/lib/efi_loader/efi_unicode_collation.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI Unicode collation protocol + * + * Copyright (c) 2018 Heinrich Schuchardt <xypron.glpk@gmx.de> + */ + +#include <common.h> +#include <charset.h> +#include <cp1250.h> +#include <cp437.h> +#include <efi_loader.h> + +/* Characters that may not be used in FAT 8.3 file names */ +static const char illegal[] = "+,<=>:;\"/\\|?*[]\x7f"; + +/* + * EDK2 assumes codepage 1250 when creating FAT 8.3 file names. + * Linux defaults to codepage 437 for FAT 8.3 file names. + */ +#if CONFIG_FAT_DEFAULT_CODEPAGE == 1250 +/* Unicode code points for code page 1250 characters 0x80 - 0xff */ +static const u16 codepage[] = CP1250; +#else +/* Unicode code points for code page 437 characters 0x80 - 0xff */ +static const u16 codepage[] = CP437; +#endif + +/* GUID of the EFI_UNICODE_COLLATION_PROTOCOL2 */ +const efi_guid_t efi_guid_unicode_collation_protocol2 = + EFI_UNICODE_COLLATION_PROTOCOL2_GUID; + +/** + * efi_stri_coll() - compare utf-16 strings case-insenitively + * + * @this: unicode collation protocol instance + * @s1: first string + * @s2: second string + * + * This function implements the StriColl() service of the + * EFI_UNICODE_COLLATION_PROTOCOL. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: 0: s1 == s2, > 0: s1 > s2, < 0: s1 < s2 + */ +static efi_intn_t EFIAPI efi_stri_coll( + struct efi_unicode_collation_protocol *this, u16 *s1, u16 *s2) +{ + s32 c1, c2; + efi_intn_t ret = 0; + + EFI_ENTRY("%p, %ls, %ls", this, s1, s2); + for (; *s1 | *s2; ++s1, ++s2) { + c1 = utf_to_upper(*s1); + c2 = utf_to_upper(*s2); + if (c1 < c2) { + ret = -1; + goto out; + } else if (c1 > c2) { + ret = 1; + goto out; + } + } +out: + EFI_EXIT(EFI_SUCCESS); + return ret; +} + +/** + * next_lower() - get next codepoint converted to lower case + * + * @string: pointer to u16 string, on return advanced by one codepoint + * Return: first codepoint of string converted to lower case + */ +static s32 next_lower(const u16 **string) +{ + return utf_to_lower(utf16_get(string)); +} + +/** + * metai_match() - compare utf-16 string with a pattern string case-insenitively + * + * @string: string to compare + * @pattern: pattern string + * + * The pattern string may use these: + * - * matches >= 0 characters + * - ? matches 1 character + * - [<char1><char2>...<charN>] match any character in the set + * - [<char1>-<char2>] matches any character in the range + * + * This function is called my efi_metai_match(). + * + * For '*' pattern searches this function calls itself recursively. + * Performance-wise this is suboptimal, especially for multiple '*' wildcards. + * But it results in simple code. + * + * Return: true if the string is matched. + */ +static bool metai_match(const u16 *string, const u16 *pattern) +{ + s32 first, s, p; + + for (; *string && *pattern;) { + const u16 *string_old = string; + + s = next_lower(&string); + p = next_lower(&pattern); + + switch (p) { + case '*': + /* Match 0 or more characters */ + for (;; s = next_lower(&string)) { + if (metai_match(string_old, pattern)) + return true; + if (!s) + return false; + string_old = string; + } + case '?': + /* Match any one character */ + break; + case '[': + /* Match any character in the set */ + p = next_lower(&pattern); + first = p; + if (first == ']') + /* Empty set */ + return false; + p = next_lower(&pattern); + if (p == '-') { + /* Range */ + p = next_lower(&pattern); + if (s < first || s > p) + return false; + p = next_lower(&pattern); + if (p != ']') + return false; + } else { + /* Set */ + bool hit = false; + + if (s == first) + hit = true; + for (; p && p != ']'; + p = next_lower(&pattern)) { + if (p == s) + hit = true; + } + if (!hit || p != ']') + return false; + } + break; + default: + /* Match one character */ + if (p != s) + return false; + } + } + if (!*pattern && !*string) + return true; + return false; +} + +/** + * efi_metai_match() - compare utf-16 string with a pattern string + * case-insenitively + * + * @this: unicode collation protocol instance + * @s: string to compare + * @p: pattern string + * + * The pattern string may use these: + * - * matches >= 0 characters + * - ? matches 1 character + * - [<char1><char2>...<charN>] match any character in the set + * - [<char1>-<char2>] matches any character in the range + * + * This function implements the MetaMatch() service of the + * EFI_UNICODE_COLLATION_PROTOCOL. + * + * Return: true if the string is matched. + */ +static bool EFIAPI efi_metai_match(struct efi_unicode_collation_protocol *this, + const u16 *string, const u16 *pattern) +{ + bool ret; + + EFI_ENTRY("%p, %ls, %ls", this, string, pattern); + ret = metai_match(string, pattern); + EFI_EXIT(EFI_SUCCESS); + return ret; +} + +/** + * efi_str_lwr() - convert to lower case + * + * @this: unicode collation protocol instance + * @string: string to convert + * @p: pattern string + * + * The conversion is done in place. As long as upper and lower letters use the + * same number of words this does not pose a problem. + * + * This function implements the StrLwr() service of the + * EFI_UNICODE_COLLATION_PROTOCOL. + */ +static void EFIAPI efi_str_lwr(struct efi_unicode_collation_protocol *this, + u16 *string) +{ + EFI_ENTRY("%p, %ls", this, string); + for (; *string; ++string) + *string = utf_to_lower(*string); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_str_upr() - convert to upper case + * + * @this: unicode collation protocol instance + * @string: string to convert + * @p: pattern string + * + * The conversion is done in place. As long as upper and lower letters use the + * same number of words this does not pose a problem. + * + * This function implements the StrUpr() service of the + * EFI_UNICODE_COLLATION_PROTOCOL. + */ +static void EFIAPI efi_str_upr(struct efi_unicode_collation_protocol *this, + u16 *string) +{ + EFI_ENTRY("%p, %ls", this, string); + for (; *string; ++string) + *string = utf_to_upper(*string); + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_fat_to_str() - convert an 8.3 file name from an OEM codepage to Unicode + * + * @this: unicode collation protocol instance + * @fat_size: size of the string to convert + * @fat: string to convert + * @string: converted string + * + * This function implements the FatToStr() service of the + * EFI_UNICODE_COLLATION_PROTOCOL. + */ +static void EFIAPI efi_fat_to_str(struct efi_unicode_collation_protocol *this, + efi_uintn_t fat_size, char *fat, u16 *string) +{ + efi_uintn_t i; + u16 c; + + EFI_ENTRY("%p, %zu, %s, %p", this, fat_size, fat, string); + for (i = 0; i < fat_size; ++i) { + c = (unsigned char)fat[i]; + if (c > 0x80) + c = codepage[i - 0x80]; + string[i] = c; + if (!c) + break; + } + string[i] = 0; + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_fat_to_str() - convert a utf-16 string to legal characters for a FAT + * file name in an OEM code page + * + * @this: unicode collation protocol instance + * @string: Unicode string to convert + * @fat_size: size of the target buffer + * @fat: converted string + * + * This function implements the StrToFat() service of the + * EFI_UNICODE_COLLATION_PROTOCOL. + * + * Return: true if an illegal character was substituted by '_'. + */ +static bool EFIAPI efi_str_to_fat(struct efi_unicode_collation_protocol *this, + const u16 *string, efi_uintn_t fat_size, + char *fat) +{ + efi_uintn_t i; + s32 c; + bool ret = false; + + EFI_ENTRY("%p, %ls, %zu, %p", this, string, fat_size, fat); + for (i = 0; i < fat_size;) { + c = utf16_get(&string); + switch (c) { + /* Ignore period and space */ + case '.': + case ' ': + continue; + case 0: + break; + } + c = utf_to_upper(c); + if (c >= 0x80) { + int j; + + /* Look for codepage translation */ + for (j = 0; j < 0x80; ++j) { + if (c == codepage[j]) { + c = j + 0x80; + break; + } + } + if (j >= 0x80) { + c = '_'; + ret = true; + } + } else if (c && (c < 0x20 || strchr(illegal, c))) { + c = '_'; + ret = true; + } + + fat[i] = c; + if (!c) + break; + ++i; + } + EFI_EXIT(EFI_SUCCESS); + return ret; +} + +const struct efi_unicode_collation_protocol efi_unicode_collation_protocol2 = { + .stri_coll = efi_stri_coll, + .metai_match = efi_metai_match, + .str_lwr = efi_str_lwr, + .str_upr = efi_str_upr, + .fat_to_str = efi_fat_to_str, + .str_to_fat = efi_str_to_fat, + .supported_languages = "en", +}; + +/* + * In EFI 1.10 a version of the Unicode collation protocol using ISO 639-2 + * language codes existed. This protocol is not part of the UEFI specification + * any longer. Unfortunately it is required to run the UEFI Self Certification + * Test (SCT) II, version 2.6, 2017. So we implement it here for the sole + * purpose of running the SCT. It can be removed when a compliant SCT is + * available. + */ +#if CONFIG_IS_ENABLED(EFI_UNICODE_COLLATION_PROTOCOL) + +/* GUID of the EFI_UNICODE_COLLATION_PROTOCOL */ +const efi_guid_t efi_guid_unicode_collation_protocol = + EFI_UNICODE_COLLATION_PROTOCOL_GUID; + +const struct efi_unicode_collation_protocol efi_unicode_collation_protocol = { + .stri_coll = efi_stri_coll, + .metai_match = efi_metai_match, + .str_lwr = efi_str_lwr, + .str_upr = efi_str_upr, + .fat_to_str = efi_fat_to_str, + .str_to_fat = efi_str_to_fat, + /* ISO 639-2 language code */ + .supported_languages = "eng", +}; + +#endif diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c new file mode 100644 index 00000000..c316bdfe --- /dev/null +++ b/lib/efi_loader/efi_variable.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI utils + * + * Copyright (c) 2017 Rob Clark + */ + +#include <common.h> +#include <efi_loader.h> +#include <env_internal.h> +#include <hexdump.h> +#include <malloc.h> +#include <search.h> +#include <u-boot/crc.h> + +#define READ_ONLY BIT(31) + +/* + * Mapping between EFI variables and u-boot variables: + * + * efi_$guid_$varname = {attributes}(type)value + * + * For example: + * + * efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_OsIndicationsSupported= + * "{ro,boot,run}(blob)0000000000000000" + * efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder= + * "(blob)00010000" + * + * The attributes are a comma separated list of these possible + * attributes: + * + * + ro - read-only + * + boot - boot-services access + * + run - runtime access + * + * NOTE: with current implementation, no variables are available after + * ExitBootServices, and all are persisted (if possible). + * + * If not specified, the attributes default to "{boot}". + * + * The required type is one of: + * + * + utf8 - raw utf8 string + * + blob - arbitrary length hex string + * + * Maybe a utf16 type would be useful to for a string value to be auto + * converted to utf16? + */ + +#define PREFIX_LEN (strlen("efi_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_")) + +/** + * efi_to_native() - convert the UEFI variable name and vendor GUID to U-Boot + * variable name + * + * The U-Boot variable name is a concatenation of prefix 'efi', the hexstring + * encoded vendor GUID, and the UTF-8 encoded UEFI variable name separated by + * underscores, e.g. 'efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder'. + * + * @native: pointer to pointer to U-Boot variable name + * @variable_name: UEFI variable name + * @vendor: vendor GUID + * Return: status code + */ +static efi_status_t efi_to_native(char **native, const u16 *variable_name, + const efi_guid_t *vendor) +{ + size_t len; + char *pos; + + len = PREFIX_LEN + utf16_utf8_strlen(variable_name) + 1; + *native = malloc(len); + if (!*native) + return EFI_OUT_OF_RESOURCES; + + pos = *native; + pos += sprintf(pos, "efi_%pUl_", vendor); + utf16_utf8_strcpy(&pos, variable_name); + + return EFI_SUCCESS; +} + +/** + * prefix() - skip over prefix + * + * Skip over a prefix string. + * + * @str: string with prefix + * @prefix: prefix string + * Return: string without prefix, or NULL if prefix not found + */ +static const char *prefix(const char *str, const char *prefix) +{ + size_t n = strlen(prefix); + if (!strncmp(prefix, str, n)) + return str + n; + return NULL; +} + +/** + * parse_attr() - decode attributes part of variable value + * + * Convert the string encoded attributes of a UEFI variable to a bit mask. + * TODO: Several attributes are not supported. + * + * @str: value of U-Boot variable + * @attrp: pointer to UEFI attributes + * Return: pointer to remainder of U-Boot variable value + */ +static const char *parse_attr(const char *str, u32 *attrp) +{ + u32 attr = 0; + char sep = '{'; + + if (*str != '{') { + *attrp = EFI_VARIABLE_BOOTSERVICE_ACCESS; + return str; + } + + while (*str == sep) { + const char *s; + + str++; + + if ((s = prefix(str, "ro"))) { + attr |= READ_ONLY; + } else if ((s = prefix(str, "nv"))) { + attr |= EFI_VARIABLE_NON_VOLATILE; + } else if ((s = prefix(str, "boot"))) { + attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS; + } else if ((s = prefix(str, "run"))) { + attr |= EFI_VARIABLE_RUNTIME_ACCESS; + } else { + printf("invalid attribute: %s\n", str); + break; + } + + str = s; + sep = ','; + } + + str++; + + *attrp = attr; + + return str; +} + +/** + * efi_get_variable() - retrieve value of a UEFI variable + * + * This function implements the GetVariable runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer to which the variable value is copied + * @data: buffer to which the variable value is copied + * Return: status code + */ +efi_status_t EFIAPI efi_get_variable(u16 *variable_name, + const efi_guid_t *vendor, u32 *attributes, + efi_uintn_t *data_size, void *data) +{ + char *native_name; + efi_status_t ret; + unsigned long in_size; + const char *val, *s; + u32 attr; + + EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes, + data_size, data); + + if (!variable_name || !vendor || !data_size) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + ret = efi_to_native(&native_name, variable_name, vendor); + if (ret) + return EFI_EXIT(ret); + + EFI_PRINT("get '%s'\n", native_name); + + val = env_get(native_name); + free(native_name); + if (!val) + return EFI_EXIT(EFI_NOT_FOUND); + + val = parse_attr(val, &attr); + + in_size = *data_size; + + if ((s = prefix(val, "(blob)"))) { + size_t len = strlen(s); + + /* number of hexadecimal digits must be even */ + if (len & 1) + return EFI_EXIT(EFI_DEVICE_ERROR); + + /* two characters per byte: */ + len /= 2; + *data_size = len; + + if (in_size < len) { + ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + + if (!data) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (hex2bin(data, s, len)) + return EFI_EXIT(EFI_DEVICE_ERROR); + + EFI_PRINT("got value: \"%s\"\n", s); + } else if ((s = prefix(val, "(utf8)"))) { + unsigned len = strlen(s) + 1; + + *data_size = len; + + if (in_size < len) { + ret = EFI_BUFFER_TOO_SMALL; + goto out; + } + + if (!data) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + memcpy(data, s, len); + ((char *)data)[len] = '\0'; + + EFI_PRINT("got value: \"%s\"\n", (char *)data); + } else { + EFI_PRINT("invalid value: '%s'\n", val); + return EFI_EXIT(EFI_DEVICE_ERROR); + } + +out: + if (attributes) + *attributes = attr & EFI_VARIABLE_MASK; + + return EFI_EXIT(ret); +} + +static char *efi_variables_list; +static char *efi_cur_variable; + +/** + * parse_uboot_variable() - parse a u-boot variable and get uefi-related + * information + * @variable: whole data of u-boot variable (ie. name=value) + * @variable_name_size: size of variable_name buffer in byte + * @variable_name: name of uefi variable in u16, null-terminated + * @vendor: vendor's guid + * @attributes: attributes + * + * A uefi variable is encoded into a u-boot variable as described above. + * This function parses such a u-boot variable and retrieve uefi-related + * information into respective parameters. In return, variable_name_size + * is the size of variable name including NULL. + * + * Return: EFI_SUCCESS if parsing is OK, EFI_NOT_FOUND when + * the entire variable list has been returned, + * otherwise non-zero status code + */ +static efi_status_t parse_uboot_variable(char *variable, + efi_uintn_t *variable_name_size, + u16 *variable_name, + const efi_guid_t *vendor, + u32 *attributes) +{ + char *guid, *name, *end, c; + unsigned long name_len; + u16 *p; + + guid = strchr(variable, '_'); + if (!guid) + return EFI_INVALID_PARAMETER; + guid++; + name = strchr(guid, '_'); + if (!name) + return EFI_INVALID_PARAMETER; + name++; + end = strchr(name, '='); + if (!end) + return EFI_INVALID_PARAMETER; + + name_len = end - name; + if (*variable_name_size < (name_len + 1)) { + *variable_name_size = name_len + 1; + return EFI_BUFFER_TOO_SMALL; + } + end++; /* point to value */ + + /* variable name */ + p = variable_name; + utf8_utf16_strncpy(&p, name, name_len); + variable_name[name_len] = 0; + *variable_name_size = name_len + 1; + + /* guid */ + c = *(name - 1); + *(name - 1) = '\0'; /* guid need be null-terminated here */ + uuid_str_to_bin(guid, (unsigned char *)vendor, UUID_STR_FORMAT_GUID); + *(name - 1) = c; + + /* attributes */ + parse_attr(end, attributes); + + return EFI_SUCCESS; +} + +/** + * efi_get_next_variable_name() - enumerate the current variable names + * + * @variable_name_size: size of variable_name buffer in byte + * @variable_name: name of uefi variable's name in u16 + * @vendor: vendor's guid + * + * This function implements the GetNextVariableName service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * Return: status code + */ +efi_status_t EFIAPI efi_get_next_variable_name(efi_uintn_t *variable_name_size, + u16 *variable_name, + const efi_guid_t *vendor) +{ + char *native_name, *variable; + ssize_t name_len, list_len; + char regex[256]; + char * const regexlist[] = {regex}; + u32 attributes; + int i; + efi_status_t ret; + + EFI_ENTRY("%p \"%ls\" %pUl", variable_name_size, variable_name, vendor); + + if (!variable_name_size || !variable_name || !vendor) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (variable_name[0]) { + /* check null-terminated string */ + for (i = 0; i < *variable_name_size; i++) + if (!variable_name[i]) + break; + if (i >= *variable_name_size) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* search for the last-returned variable */ + ret = efi_to_native(&native_name, variable_name, vendor); + if (ret) + return EFI_EXIT(ret); + + name_len = strlen(native_name); + for (variable = efi_variables_list; variable && *variable;) { + if (!strncmp(variable, native_name, name_len) && + variable[name_len] == '=') + break; + + variable = strchr(variable, '\n'); + if (variable) + variable++; + } + + free(native_name); + if (!(variable && *variable)) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + /* next variable */ + variable = strchr(variable, '\n'); + if (variable) + variable++; + if (!(variable && *variable)) + return EFI_EXIT(EFI_NOT_FOUND); + } else { + /* + *new search: free a list used in the previous search + */ + free(efi_variables_list); + efi_variables_list = NULL; + efi_cur_variable = NULL; + + snprintf(regex, 256, "efi_.*-.*-.*-.*-.*_.*"); + list_len = hexport_r(&env_htab, '\n', + H_MATCH_REGEX | H_MATCH_KEY, + &efi_variables_list, 0, 1, regexlist); + /* 1 indicates that no match was found */ + if (list_len <= 1) + return EFI_EXIT(EFI_NOT_FOUND); + + variable = efi_variables_list; + } + + ret = parse_uboot_variable(variable, variable_name_size, variable_name, + vendor, &attributes); + + return EFI_EXIT(ret); +} + +/** + * efi_set_variable() - set value of a UEFI variable + * + * This function implements the SetVariable runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer with the variable value + * @data: buffer with the variable value + * Return: status code + */ +efi_status_t EFIAPI efi_set_variable(u16 *variable_name, + const efi_guid_t *vendor, u32 attributes, + efi_uintn_t data_size, const void *data) +{ + char *native_name = NULL, *val = NULL, *s; + const char *old_val; + size_t old_size; + efi_status_t ret = EFI_SUCCESS; + u32 attr; + + EFI_ENTRY("\"%ls\" %pUl %x %zu %p", variable_name, vendor, attributes, + data_size, data); + + if (!variable_name || !*variable_name || !vendor || + ((attributes & EFI_VARIABLE_RUNTIME_ACCESS) && + !(attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS))) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + ret = efi_to_native(&native_name, variable_name, vendor); + if (ret) + goto out; + + old_val = env_get(native_name); + if (old_val) { + old_val = parse_attr(old_val, &attr); + + /* check read-only first */ + if (attr & READ_ONLY) { + ret = EFI_WRITE_PROTECTED; + goto out; + } + + if ((data_size == 0 && + !(attributes & EFI_VARIABLE_APPEND_WRITE)) || + !attributes) { + /* delete the variable: */ + env_set(native_name, NULL); + ret = EFI_SUCCESS; + goto out; + } + + /* attributes won't be changed */ + if (attr != (attributes & ~EFI_VARIABLE_APPEND_WRITE)) { + ret = EFI_INVALID_PARAMETER; + goto out; + } + + if (attributes & EFI_VARIABLE_APPEND_WRITE) { + if (!prefix(old_val, "(blob)")) { + ret = EFI_DEVICE_ERROR; + goto out; + } + old_size = strlen(old_val); + } else { + old_size = 0; + } + } else { + if (data_size == 0 || !attributes || + (attributes & EFI_VARIABLE_APPEND_WRITE)) { + /* + * Trying to delete or to update a non-existent + * variable. + */ + ret = EFI_NOT_FOUND; + goto out; + } + + old_size = 0; + } + + val = malloc(old_size + 2 * data_size + + strlen("{ro,run,boot,nv}(blob)") + 1); + if (!val) { + ret = EFI_OUT_OF_RESOURCES; + goto out; + } + + s = val; + + /* store attributes */ + attributes &= (EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS); + s += sprintf(s, "{"); + while (attributes) { + u32 attr = 1 << (ffs(attributes) - 1); + + if (attr == EFI_VARIABLE_NON_VOLATILE) + s += sprintf(s, "nv"); + else if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) + s += sprintf(s, "boot"); + else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) + s += sprintf(s, "run"); + + attributes &= ~attr; + if (attributes) + s += sprintf(s, ","); + } + s += sprintf(s, "}"); + + if (old_size) + /* APPEND_WRITE */ + s += sprintf(s, old_val); + else + s += sprintf(s, "(blob)"); + + /* store payload: */ + s = bin2hex(s, data, data_size); + *s = '\0'; + + EFI_PRINT("setting: %s=%s\n", native_name, val); + + if (env_set(native_name, val)) + ret = EFI_DEVICE_ERROR; + +out: + free(native_name); + free(val); + + return EFI_EXIT(ret); +} + +/** + * efi_query_variable_info() - get information about EFI variables + * + * This function implements the QueryVariableInfo() runtime service. + * + * See the Unified Extensible Firmware Interface (UEFI) specification for + * details. + * + * @attributes: bitmask to select variables to be + * queried + * @maximum_variable_storage_size: maximum size of storage area for the + * selected variable types + * @remaining_variable_storage_size: remaining size of storage are for the + * selected variable types + * @maximum_variable_size: maximum size of a variable of the + * selected type + * Returns: status code + */ +efi_status_t __efi_runtime EFIAPI efi_query_variable_info( + u32 attributes, + u64 *maximum_variable_storage_size, + u64 *remaining_variable_storage_size, + u64 *maximum_variable_size) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_get_variable_runtime() - runtime implementation of GetVariable() + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer to which the variable value is copied + * @data: buffer to which the variable value is copied + * Return: status code + */ +static efi_status_t __efi_runtime EFIAPI +efi_get_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, + u32 *attributes, efi_uintn_t *data_size, void *data) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_get_next_variable_name_runtime() - runtime implementation of + * GetNextVariable() + * + * @variable_name_size: size of variable_name buffer in byte + * @variable_name: name of uefi variable's name in u16 + * @vendor: vendor's guid + * Return: status code + */ +static efi_status_t __efi_runtime EFIAPI +efi_get_next_variable_name_runtime(efi_uintn_t *variable_name_size, + u16 *variable_name, const efi_guid_t *vendor) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_set_variable_runtime() - runtime implementation of SetVariable() + * + * @variable_name: name of the variable + * @vendor: vendor GUID + * @attributes: attributes of the variable + * @data_size: size of the buffer with the variable value + * @data: buffer with the variable value + * Return: status code + */ +static efi_status_t __efi_runtime EFIAPI +efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, + u32 attributes, efi_uintn_t data_size, + const void *data) +{ + return EFI_UNSUPPORTED; +} + +/** + * efi_variables_boot_exit_notify() - notify ExitBootServices() is called + */ +void efi_variables_boot_exit_notify(void) +{ + efi_runtime_services.get_variable = efi_get_variable_runtime; + efi_runtime_services.get_next_variable_name = + efi_get_next_variable_name_runtime; + efi_runtime_services.set_variable = efi_set_variable_runtime; + efi_update_table_header_crc32(&efi_runtime_services.hdr); +} + +/** + * efi_init_variables() - initialize variable services + * + * Return: status code + */ +efi_status_t efi_init_variables(void) +{ + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_watchdog.c b/lib/efi_loader/efi_watchdog.c new file mode 100644 index 00000000..6f69b76e --- /dev/null +++ b/lib/efi_loader/efi_watchdog.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI watchdog + * + * Copyright (c) 2017 Heinrich Schuchardt + */ + +#include <common.h> +#include <efi_loader.h> + +/* Conversion factor from seconds to multiples of 100ns */ +#define EFI_SECONDS_TO_100NS 10000000ULL + +static struct efi_event *watchdog_timer_event; + +/* + * Reset the system when the watchdog event is notified. + * + * @event: the watchdog event + * @context: not used + */ +static void EFIAPI efi_watchdog_timer_notify(struct efi_event *event, + void *context) +{ + EFI_ENTRY("%p, %p", event, context); + + printf("\nEFI: Watchdog timeout\n"); + EFI_CALL_VOID(efi_runtime_services.reset_system(EFI_RESET_COLD, + EFI_SUCCESS, 0, NULL)); + + EFI_EXIT(EFI_UNSUPPORTED); +} + +/* + * Reset the watchdog timer. + * + * This function is used by the SetWatchdogTimer service. + * + * @timeout: seconds before reset by watchdog + * @return: status code + */ +efi_status_t efi_set_watchdog(unsigned long timeout) +{ + efi_status_t r; + + if (timeout) + /* Reset watchdog */ + r = efi_set_timer(watchdog_timer_event, EFI_TIMER_RELATIVE, + EFI_SECONDS_TO_100NS * timeout); + else + /* Deactivate watchdog */ + r = efi_set_timer(watchdog_timer_event, EFI_TIMER_STOP, 0); + return r; +} + +/* + * Initialize the EFI watchdog. + * + * This function is called by efi_init_obj_list() + */ +efi_status_t efi_watchdog_register(void) +{ + efi_status_t r; + + /* + * Create a timer event. + */ + r = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, + efi_watchdog_timer_notify, NULL, NULL, + &watchdog_timer_event); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to register watchdog event\n"); + return r; + } + /* + * 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 + */ + r = efi_set_watchdog(300); + if (r != EFI_SUCCESS) { + printf("ERROR: Failed to set watchdog timer\n"); + return r; + } + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/helloworld.c b/lib/efi_loader/helloworld.c new file mode 100644 index 00000000..9ae2ee33 --- /dev/null +++ b/lib/efi_loader/helloworld.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * EFI hello world + * + * Copyright (c) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * This program demonstrates calling a boottime service. + * It writes a greeting and the load options to the console. + */ + +#include <common.h> +#include <efi_api.h> + +static const efi_guid_t loaded_image_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID; +static const efi_guid_t fdt_guid = EFI_FDT_GUID; +static const efi_guid_t acpi_guid = EFI_ACPI_TABLE_GUID; +static const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID; + +/** + * efi_main() - entry point of the EFI application. + * + * @handle: handle of the loaded image + * @systable: system table + * @return: status code + */ +efi_status_t EFIAPI efi_main(efi_handle_t handle, + struct efi_system_table *systable) +{ + struct efi_simple_text_output_protocol *con_out = systable->con_out; + struct efi_boot_services *boottime = systable->boottime; + struct efi_loaded_image *loaded_image; + efi_status_t ret; + efi_uintn_t i; + u16 rev[] = L"0.0.0"; + + /* UEFI requires CR LF */ + con_out->output_string(con_out, L"Hello, world!\r\n"); + + /* Print the revision number */ + rev[0] = (systable->hdr.revision >> 16) + '0'; + rev[4] = systable->hdr.revision & 0xffff; + for (; rev[4] >= 10;) { + rev[4] -= 10; + ++rev[2]; + } + /* Third digit is only to be shown if non-zero */ + if (rev[4]) + rev[4] += '0'; + else + rev[3] = 0; + + con_out->output_string(con_out, L"Running on UEFI "); + con_out->output_string(con_out, rev); + con_out->output_string(con_out, L"\r\n"); + + /* Get the loaded image protocol */ + ret = boottime->handle_protocol(handle, &loaded_image_guid, + (void **)&loaded_image); + if (ret != EFI_SUCCESS) { + con_out->output_string + (con_out, L"Cannot open loaded image protocol\r\n"); + goto out; + } + /* Find configuration tables */ + for (i = 0; i < systable->nr_tables; ++i) { + if (!memcmp(&systable->tables[i].guid, &fdt_guid, + sizeof(efi_guid_t))) + con_out->output_string + (con_out, L"Have device tree\r\n"); + if (!memcmp(&systable->tables[i].guid, &acpi_guid, + sizeof(efi_guid_t))) + con_out->output_string + (con_out, L"Have ACPI 2.0 table\r\n"); + if (!memcmp(&systable->tables[i].guid, &smbios_guid, + sizeof(efi_guid_t))) + con_out->output_string + (con_out, L"Have SMBIOS table\r\n"); + } + /* Output the load options */ + con_out->output_string(con_out, L"Load options: "); + if (loaded_image->load_options_size && loaded_image->load_options) + con_out->output_string(con_out, + (u16 *)loaded_image->load_options); + else + con_out->output_string(con_out, L"<none>"); + con_out->output_string(con_out, L"\r\n"); + +out: + boottime->exit(handle, ret, 0, NULL); + + /* We should never arrive here */ + return ret; +} |