From patchwork Sat Jul 11 22:05:41 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Heinrich Schuchardt X-Patchwork-Id: 241382 List-Id: U-Boot discussion From: xypron.glpk at gmx.de (Heinrich Schuchardt) Date: Sun, 12 Jul 2020 00:05:41 +0200 Subject: [PATCH v4 14/17] efi_loader: memory buffer for variables In-Reply-To: <20200711220544.120976-1-xypron.glpk@gmx.de> References: <20200711220544.120976-1-xypron.glpk@gmx.de> Message-ID: <20200711220544.120976-15-xypron.glpk@gmx.de> Saving UEFI variable as encoded U-Boot environment variables does not allow support at runtime. Provide functions to manage a memory buffer with UEFI variables. Signed-off-by: Heinrich Schuchardt --- v4: Use memmove() instead of a loop for copying Set efi_current_var in efi_var_mem_compare() to speed up finding variables. --- include/efi_variable.h | 54 +++++++ lib/efi_loader/Makefile | 1 + lib/efi_loader/efi_var_mem.c | 266 +++++++++++++++++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 lib/efi_loader/efi_var_mem.c -- 2.27.0 diff --git a/include/efi_variable.h b/include/efi_variable.h index 01054209c4..bc5985cfdb 100644 --- a/include/efi_variable.h +++ b/include/efi_variable.h @@ -141,4 +141,58 @@ efi_status_t efi_var_to_file(void); */ efi_status_t efi_var_from_file(void); +/** + * efi_var_mem_init() - set-up variable list + * + * Return: status code + */ +efi_status_t efi_var_mem_init(void); + +/** + * efi_var_mem_find() - find a variable in the list + * + * @guid: GUID of the variable + * @name: name of the variable + * @next: on exit pointer to the next variable after the found one + * Return: found variable + */ +struct efi_var_entry *efi_var_mem_find(const efi_guid_t *guid, const u16 *name, + struct efi_var_entry **next); + +/** + * efi_var_mem_del() - delete a variable from the list of variables + * + * @var: variable to delete + */ +void efi_var_mem_del(struct efi_var_entry *var); + +/** + * efi_var_mem_ins() - append a variable to the list of variables + * + * The variable is appended without checking if a variable of the same name + * already exists. The two data buffers are concatenated. + * + * @variable_name: variable name + * @vendor: GUID + * @attributes: variable attributes + * @size1: size of the first data buffer + * @data1: first data buffer + * @size2: size of the second data field + * @data2: second data buffer + * @time: time of authentication (as seconds since start of epoch) + * Result: status code + */ +efi_status_t efi_var_mem_ins(u16 *variable_name, + const efi_guid_t *vendor, u32 attributes, + const efi_uintn_t size1, const void *data1, + const efi_uintn_t size2, const void *data2, + const u64 time); + +/** + * efi_var_mem_free() - determine free memory for variables + * + * Return: maximum data size plus variable name size + */ +u64 efi_var_mem_free(void); + #endif diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index c87b82db32..f81ec8d277 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -36,6 +36,7 @@ obj-y += efi_runtime.o obj-y += efi_setup.o obj-$(CONFIG_EFI_UNICODE_COLLATION_PROTOCOL2) += efi_unicode_collation.o obj-y += efi_var_common.o +obj-y += efi_var_mem.o ifeq ($(CONFIG_EFI_MM_COMM_TEE),y) obj-y += efi_variable_tee.o else diff --git a/lib/efi_loader/efi_var_mem.c b/lib/efi_loader/efi_var_mem.c new file mode 100644 index 0000000000..7a2dba7dc2 --- /dev/null +++ b/lib/efi_loader/efi_var_mem.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * File interface for UEFI variables + * + * Copyright (c) 2020, Heinrich Schuchardt + */ + +#include +#include +#include +#include + +static struct efi_var_file __efi_runtime_data *efi_var_buf; +static struct efi_var_entry __efi_runtime_data *efi_current_var; + +/** + * efi_var_mem_compare() - compare GUID and name with a variable + * + * @var: variable to compare + * @guid: GUID to compare + * @name: variable name to compare + * @next: pointer to next variable + * Return: true if match + */ +static bool __efi_runtime +efi_var_mem_compare(struct efi_var_entry *var, const efi_guid_t *guid, + const u16 *name, struct efi_var_entry **next) +{ + int i; + u8 *guid1, *guid2; + const u16 *data, *var_name; + bool match = true; + + for (guid1 = (u8 *)&var->guid, guid2 = (u8 *)guid, i = 0; + i < sizeof(efi_guid_t) && match; ++i) + match = (guid1[i] == guid2[i]); + + for (data = var->name, var_name = name;; ++data, ++var_name) { + if (match) + match = (*data == *var_name); + if (!*data) + break; + } + + ++data; + + if (next) + *next = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + + if (match) + efi_current_var = var; + + return match; +} + +struct efi_var_entry __efi_runtime +*efi_var_mem_find(const efi_guid_t *guid, const u16 *name, + struct efi_var_entry **next) +{ + struct efi_var_entry *var, *last; + + last = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + + if (!*name) { + if (next) { + *next = efi_var_buf->var; + if (*next >= last) + *next = NULL; + } + return NULL; + } + if (efi_current_var && + efi_var_mem_compare(efi_current_var, guid, name, next)) { + if (next && *next >= last) + *next = NULL; + return efi_current_var; + } + + var = efi_var_buf->var; + if (var < last) { + for (; var;) { + struct efi_var_entry *pos; + bool match; + + match = efi_var_mem_compare(var, guid, name, &pos); + if (pos >= last) + pos = NULL; + if (match) { + if (next) + *next = pos; + return var; + } + var = pos; + } + } + if (next) + *next = NULL; + return NULL; +} + +void __efi_runtime efi_var_mem_del(struct efi_var_entry *var) +{ + u16 *data; + struct efi_var_entry *next, *last; + + if (!var) + return; + + last = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + if (var <= efi_current_var) + efi_current_var = NULL; + + for (data = var->name; *data; ++data) + ; + ++data; + next = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + efi_var_buf->length -= (uintptr_t)next - (uintptr_t)var; + + memmove(var, next, (uintptr_t)last - (uintptr_t)next); + efi_var_buf->crc32 = crc32(0, (u8 *)efi_var_buf->var, + efi_var_buf->length - + sizeof(struct efi_var_file)); +} + +efi_status_t __efi_runtime efi_var_mem_ins( + u16 *variable_name, + const efi_guid_t *vendor, u32 attributes, + const efi_uintn_t size1, const void *data1, + const efi_uintn_t size2, const void *data2, + const u64 time) +{ + u16 *data; + struct efi_var_entry *var; + u32 var_name_len; + + var = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + for (var_name_len = 0; variable_name[var_name_len]; ++var_name_len) + ; + ++var_name_len; + data = var->name + var_name_len; + + if ((uintptr_t)data - (uintptr_t)efi_var_buf + size1 + size2 > + EFI_VAR_BUF_SIZE) + return EFI_OUT_OF_RESOURCES; + + var->attr = attributes; + var->length = size1 + size2; + var->time = time; + + efi_memcpy_runtime(&var->guid, vendor, sizeof(efi_guid_t)); + efi_memcpy_runtime(var->name, variable_name, + sizeof(u16) * var_name_len); + efi_memcpy_runtime(data, data1, size1); + efi_memcpy_runtime((u8 *)data + size1, data2, size2); + + var = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + efi_var_buf->length = (uintptr_t)var - (uintptr_t)efi_var_buf; + efi_var_buf->crc32 = crc32(0, (u8 *)efi_var_buf->var, + efi_var_buf->length - + sizeof(struct efi_var_file)); + + return EFI_SUCCESS; +} + +u64 __efi_runtime efi_var_mem_free(void) +{ + return EFI_VAR_BUF_SIZE - efi_var_buf->length - + sizeof(struct efi_var_entry); +} + +/** + * efi_var_mem_bs_del() - delete boot service only variables + */ +static void efi_var_mem_bs_del(void) +{ + struct efi_var_entry *var = efi_var_buf->var; + + for (;;) { + struct efi_var_entry *last; + + last = (struct efi_var_entry *) + ((uintptr_t)efi_var_buf + efi_var_buf->length); + if (var >= last) + break; + if (var->attr & EFI_VARIABLE_RUNTIME_ACCESS) { + u16 *data; + + /* skip variable */ + for (data = var->name; *data; ++data) + ; + ++data; + var = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + } else { + /* delete variable */ + efi_var_mem_del(var); + } + } +} + +/** + * efi_var_mem_notify_exit_boot_services() - ExitBootService callback + * + * @event: callback event + * @context: callback context + */ +static void EFIAPI __efi_runtime +efi_var_mem_notify_exit_boot_services(struct efi_event *event, void *context) +{ + EFI_ENTRY("%p, %p", event, context); + + /* Delete boot service only variables */ + efi_var_mem_bs_del(); + + EFI_EXIT(EFI_SUCCESS); +} + +/** + * efi_var_mem_notify_exit_boot_services() - SetVirtualMemoryMap callback + * + * @event: callback event + * @context: callback context + */ +static void EFIAPI __efi_runtime +efi_var_mem_notify_virtual_address_map(struct efi_event *event, void *context) +{ + efi_convert_pointer(0, (void **)&efi_var_buf); +} + +efi_status_t efi_var_mem_init(void) +{ + u64 memory; + efi_status_t ret; + struct efi_event *event; + + ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES, + EFI_RUNTIME_SERVICES_DATA, + efi_size_in_pages(EFI_VAR_BUF_SIZE), + &memory); + if (ret != EFI_SUCCESS) + return ret; + efi_var_buf = (struct efi_var_file *)(uintptr_t)memory; + memset(efi_var_buf, 0, EFI_VAR_BUF_SIZE); + efi_var_buf->magic = EFI_VAR_FILE_MAGIC; + efi_var_buf->length = (uintptr_t)efi_var_buf->var - + (uintptr_t)efi_var_buf; + /* crc32 for 0 bytes = 0 */ + + ret = efi_create_event(EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK, + efi_var_mem_notify_exit_boot_services, NULL, + NULL, &event); + if (ret != EFI_SUCCESS) + return ret; + ret = efi_create_event(EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, TPL_CALLBACK, + efi_var_mem_notify_virtual_address_map, NULL, + NULL, &event); + if (ret != EFI_SUCCESS) + return ret; + return ret; +}