From patchwork Fri Mar 27 05:27:54 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Heinrich Schuchardt X-Patchwork-Id: 244400 List-Id: U-Boot discussion From: xypron.glpk at gmx.de (Heinrich Schuchardt) Date: Fri, 27 Mar 2020 06:27:54 +0100 Subject: [PATCH 10/16] efi_loader: UEFI variable persistence In-Reply-To: <20200327052800.11022-1-xypron.glpk@gmx.de> References: <20200327052800.11022-1-xypron.glpk@gmx.de> Message-ID: <20200327052800.11022-11-xypron.glpk@gmx.de> Persist non-volatile UEFI variables in a file on the EFI system partition. The file is written: * whenever a non-volatile UEFI variable is changed after initialization of the UEFI sub-system. * upon ExitBootServices() The file is read during the UEFI sub-system initialization to restore non-volatile UEFI variables. Signed-off-by: Heinrich Schuchardt --- include/efi_variable.h | 36 +++++ lib/efi_loader/Kconfig | 8 + lib/efi_loader/Makefile | 1 + lib/efi_loader/efi_variable.c | 12 +- lib/efi_loader/efi_variables_file.c | 235 ++++++++++++++++++++++++++++ 5 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 include/efi_variable.h create mode 100644 lib/efi_loader/efi_variables_file.c -- 2.25.1 diff --git a/include/efi_variable.h b/include/efi_variable.h new file mode 100644 index 0000000000..fb8294fc2e --- /dev/null +++ b/include/efi_variable.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * File interface for UEFI variables + * + * Copyright (c) 2020, Heinrich Schuchardt + */ + +#ifndef _EFI_VARIABLE_H +#define _EFI_VARIABLE_H + +#define EFI_VAR_FILE_NAME "ubootefi.var" + +#define EFI_VAR_BUF_SIZE 0x4000 + +#define EFI_VAR_FILE_MAGIC 0x7261566966456255 /* UbEfiVar */ + +struct efi_var_entry { + u32 length; + u32 attr; + efi_guid_t guid; + u16 name[0]; +}; + +struct efi_var_file { + u64 reserved; /* May be overwritten by memory probing */ + u64 magic; + u32 length; + u32 crc32; + struct efi_var_entry var[0]; +}; + +efi_status_t efi_var_to_file(void); + +efi_status_t efi_var_from_file(void); + +#endif diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index e10ca05549..41705fc252 100644 --- a/lib/efi_loader/Kconfig +++ b/lib/efi_loader/Kconfig @@ -27,6 +27,14 @@ config EFI_LOADER if EFI_LOADER +config EFI_VARIABLE_FILE_STORE + bool "Store non-volatile UEFI variables as file" + depends on FAT_WRITE + default y + help + Select tis option if you want non-volatile UEFI variables to be stored + as file /ubootefi.var on the EFI system partition. + config EFI_GET_TIME bool "GetTime() runtime service" depends on DM_RTC diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 9b3b704473..621a767ab3 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -35,6 +35,7 @@ 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_variables_file.o obj-y += efi_watchdog.o obj-$(CONFIG_LCD) += efi_gop.o obj-$(CONFIG_DM_VIDEO) += efi_gop.o diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index d99ad6ddae..952a0a0db7 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -604,6 +605,11 @@ efi_status_t efi_set_variable_int(u16 *variable_name, if (env_set(native_name, val)) ret = EFI_DEVICE_ERROR; + /* Write non-volatile EFI variables to file */ + if (attributes && EFI_VARIABLE_NON_VOLATILE && + ret == EFI_SUCCESS && efi_obj_list_initialized == EFI_SUCCESS) + efi_var_to_file(); + out: free(native_name); free(val); @@ -694,6 +700,10 @@ efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, */ void efi_variables_boot_exit_notify(void) { + /* Write non-volatile EFI variables to file */ + efi_var_to_file(); + + /* Switch variable services functions to runtime version */ efi_runtime_services.get_variable = efi_get_variable_runtime; efi_runtime_services.get_next_variable_name = efi_get_next_variable_name_runtime; @@ -708,5 +718,5 @@ void efi_variables_boot_exit_notify(void) */ efi_status_t efi_init_variables(void) { - return EFI_SUCCESS; + return efi_var_from_file(); } diff --git a/lib/efi_loader/efi_variables_file.c b/lib/efi_loader/efi_variables_file.c new file mode 100644 index 0000000000..4a918d3fde --- /dev/null +++ b/lib/efi_loader/efi_variables_file.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * File interface for UEFI variables + * + * Copyright (c) 2020, Heinrich Schuchardt + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define PART_STR_LEN 10 + +/** + * efi_set_blk_dev_to_system_partition() - select EFI system partition + * + * Set the EFI system partition as current block device. + * + * Return: status code + */ +static efi_status_t __maybe_unused efi_set_blk_dev_to_system_partition(void) +{ + char part_str[PART_STR_LEN]; + int r; + + if (!efi_system_partition.if_type) + return EFI_NOT_FOUND; + snprintf(part_str, PART_STR_LEN, "%u:%u", + efi_system_partition.devnum, efi_system_partition.part); + r = fs_set_blk_dev(blk_get_if_type_name(efi_system_partition.if_type), + part_str, FS_TYPE_ANY); + if (r) { + printf("Cannot read EFI system partition\n"); + return EFI_DEVICE_ERROR; + } + return EFI_SUCCESS; +} + +/** + * efi_var_collect() - collect non-volatile variables in buffer + * + * A buffer is allocated and filled with all non-volatile variables in a + * format ready to be written to disk. + * + * @bufp: pointer to pointer of buffer with collected variables + * @lenp: pointer to length of buffer + * Return: status code + */ +static efi_status_t __maybe_unused efi_var_collect(struct efi_var_file **bufp, + loff_t *lenp) +{ + size_t len = EFI_VAR_BUF_SIZE; + struct efi_var_file *buf; + struct efi_var_entry *var, *old_var; + size_t old_var_name_length = 2; + + *bufp = NULL; /* Avoid double free() */ + buf = calloc(1, len); + if (!buf) + return EFI_OUT_OF_RESOURCES; + var = buf->var; + old_var = var; + for (;;) { + efi_uintn_t data_length, var_name_length; + u8 *data; + efi_status_t ret; + + if ((uintptr_t)buf + len <= + (uintptr_t)var->name + old_var_name_length) + return EFI_BUFFER_TOO_SMALL; + + var_name_length = (uintptr_t)buf + len - (uintptr_t)var->name; + memcpy(var->name, old_var->name, old_var_name_length); + guidcpy(&var->guid, &old_var->guid); + ret = efi_get_next_variable_name_int( + &var_name_length, var->name, &var->guid); + if (ret == EFI_NOT_FOUND) + break; + if (ret != EFI_SUCCESS) { + free(buf); + return ret; + } + old_var_name_length = var_name_length; + old_var = var; + + data = (u8 *)var->name + old_var_name_length; + data_length = (uintptr_t)buf + len - (uintptr_t)data; + ret = efi_get_variable_int(var->name, &var->guid, + &var->attr, &data_length, data); + if (ret != EFI_SUCCESS) { + free(buf); + return ret; + } + if (!(var->attr & EFI_VARIABLE_NON_VOLATILE)) + continue; + var->length = data_length; + var = (struct efi_var_entry *) + ALIGN((uintptr_t)data + data_length, 8); + } + + buf->reserved = 0; + buf->magic = EFI_VAR_FILE_MAGIC; + len = (uintptr_t)var - (uintptr_t)buf; + buf->crc32 = crc32(0, (u8 *)buf->var, + len - sizeof(struct efi_var_file)); + buf->length = len; + *bufp = buf; + *lenp = len; + + return EFI_SUCCESS; +} + +/** + * efi_var_to_file() - save non-volatile variables as file + * + * File ubootefi.var is created on the EFI system partion. + * + * Return: status code + */ +efi_status_t efi_var_to_file(void) +{ +#ifdef CONFIG_EFI_VARIABLE_FILE_STORE + efi_status_t ret; + struct efi_var_file *buf; + loff_t len; + loff_t actlen; + int r; + + ret = efi_var_collect(&buf, &len); + if (ret != EFI_SUCCESS) + goto error; + + ret = efi_set_blk_dev_to_system_partition(); + if (ret != EFI_SUCCESS) + goto error; + + r = fs_write(EFI_VAR_FILE_NAME, map_to_sysmem(buf), 0, len, &actlen); + if (r || len != actlen) + ret = EFI_DEVICE_ERROR; + +error: + if (ret != EFI_SUCCESS) + printf("Failed to persist EFI variables\n"); + free(buf); + return ret; +#else + return EFI_SUCCESS; +#endif +} + +/** + * efi_var_restore() - restore EFI variables from buffer + * + * @buf: buffer + * Return: status code + */ +static efi_status_t __maybe_unused efi_var_restore(struct efi_var_file *buf) +{ + struct efi_var_entry *var, *last_var; + efi_status_t ret; + + if (buf->reserved || buf->magic != EFI_VAR_FILE_MAGIC || + buf->crc32 != crc32(0, (u8 *)buf->var, + buf->length - sizeof(struct efi_var_file))) { + printf("Invalid EFI variables file\n"); + return EFI_INVALID_PARAMETER; + } + + var = buf->var; + last_var = (struct efi_var_entry *)((u8 *)buf + buf->length); + while (var < last_var) { + u16 *data = var->name + u16_strlen(var->name) + 1; + + if (var->attr & EFI_VARIABLE_NON_VOLATILE && var->length) { + ret = efi_set_variable_int(var->name, &var->guid, + var->attr, var->length, + data); + if (ret != EFI_SUCCESS) + printf("Failed to set EFI variable %ls\n", + var->name); + } + var = (struct efi_var_entry *) + ALIGN((uintptr_t)data + var->length, 8); + } + return EFI_SUCCESS; +} + +/** + * efi_var_from_file() - read variables from file + * + * File ubootefi.var is read from the EFI system partitions and the variables + * stored in the file are created. + * + * In case the file does not exist yet or a variable cannot be set EFI_SUCCESS + * is returned + * + * Return: status code + */ +efi_status_t efi_var_from_file(void) +{ +#ifdef CONFIG_EFI_VARIABLE_FILE_STORE + struct efi_var_file *buf; + loff_t len; + efi_status_t ret; + int r; + + buf = calloc(1, EFI_VAR_BUF_SIZE); + if (!buf) { + printf("Out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + ret = efi_set_blk_dev_to_system_partition(); + if (ret != EFI_SUCCESS) { + printf("No EFI system partition\n"); + goto error; + } + r = fs_read(EFI_VAR_FILE_NAME, map_to_sysmem(buf), 0, EFI_VAR_BUF_SIZE, + &len); + if (r || len < sizeof(struct efi_var_file)) { + printf("Failed to load EFI variables\n"); + goto error; + } + if (buf->length != len || efi_var_restore(buf) != EFI_SUCCESS) + printf("Invalid EFI variables file\n"); +error: + free(buf); +#endif + return EFI_SUCCESS; +}