[PATCH] efi_loader: avoid superfluous variable store writes on unchanged data

Heinrich Schuchardt xypron.glpk at gmx.de
Thu Mar 12 18:20:08 CET 2026


On 3/12/26 14:27, Michal Simek wrote:
> Every SetVariable() call triggers efi_var_mem_ins() followed by
> efi_var_to_storage(), even when the variable value is not actually
> changing. This is unfriendly to flash-backed stores that suffer
> wear from unnecessary erase/write cycles.
> 
> Add a change-detection path to efi_var_mem_ins(): when size2 == 0
> (i.e. not an append) and the caller passes a non-NULL changep flag,
> look up the existing variable and compare attributes, length, time
> and data byte-by-byte. If everything matches, set *changep = false
> and return EFI_SUCCESS without touching the variable buffer.
> 
> Both efi_set_variable_int() and efi_set_variable_runtime() now
> check the flag and skip efi_var_mem_del() / efi_var_to_storage()
> when nothing changed.
> 
> Signed-off-by: Michal Simek <michal.simek at amd.com>

Thank you Michal for the patch.

I ran the VariableServices tests of the SCT with this patch on top of 
origin/next and saw no new failures. The following are failing anyway 
because our implementation of authorized variables is incomplete:

* AuthVar_Conf
* AuthVar_Func

Tested-by: Heinrich Schuchardt <xypron.glpk at gmx.de>
Reviewed-by: Heinrich Schuchardt <xypron.glpk at gmx.de>

> ---
> 
>   include/efi_variable.h          |  8 +++++++-
>   lib/efi_loader/efi_var_common.c |  2 +-
>   lib/efi_loader/efi_var_mem.c    | 29 ++++++++++++++++++++++++++++-
>   lib/efi_loader/efi_variable.c   | 27 ++++++++++++++++++---------
>   4 files changed, 54 insertions(+), 12 deletions(-)
> 
> diff --git a/include/efi_variable.h b/include/efi_variable.h
> index fc1184e5ca1a..c3229c717d89 100644
> --- a/include/efi_variable.h
> +++ b/include/efi_variable.h
> @@ -216,6 +216,11 @@ void efi_var_mem_del(struct efi_var_entry *var);
>    * The variable is appended without checking if a variable of the same name
>    * already exists. The two data buffers are concatenated.
>    *
> + * When @changep is non-NULL and @size2 is 0, the function compares the new
> + * value against an existing variable with the same name and vendor. If
> + * attributes and data are identical the insertion is skipped and *@changep
> + * is set to false, avoiding superfluous writes.
> + *
>    * @variable_name:	variable name
>    * @vendor:		GUID
>    * @attributes:		variable attributes
> @@ -224,13 +229,14 @@ void efi_var_mem_del(struct efi_var_entry *var);
>    * @size2:		size of the second data field
>    * @data2:		second data buffer
>    * @time:		time of authentication (as seconds since start of epoch)
> + * @changep:		pointer to change flag (may be NULL)
>    * Result:		status code
>    */
>   efi_status_t efi_var_mem_ins(const 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);
> +			     const u64 time, bool *changep);
>   
>   /**
>    * efi_var_mem_free() - determine free memory for variables
> diff --git a/lib/efi_loader/efi_var_common.c b/lib/efi_loader/efi_var_common.c
> index c89a4fce4ff8..d63c2d1b1cd8 100644
> --- a/lib/efi_loader/efi_var_common.c
> +++ b/lib/efi_loader/efi_var_common.c
> @@ -526,7 +526,7 @@ efi_status_t efi_var_restore(struct efi_var_file *buf, bool safe)
>   			continue;
>   		ret = efi_var_mem_ins(var->name, &var->guid, var->attr,
>   				      var->length, data, 0, NULL,
> -				      var->time);
> +				      var->time, NULL);
>   		if (ret != EFI_SUCCESS)
>   			log_err("Failed to set EFI variable %ls\n", var->name);
>   	}
> diff --git a/lib/efi_loader/efi_var_mem.c b/lib/efi_loader/efi_var_mem.c
> index 31180df9e3a0..e364672db81a 100644
> --- a/lib/efi_loader/efi_var_mem.c
> +++ b/lib/efi_loader/efi_var_mem.c
> @@ -159,12 +159,39 @@ efi_status_t __efi_runtime efi_var_mem_ins(
>   				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)
> +				const u64 time, bool *changep)
>   {
>   	u16 *data;
>   	struct efi_var_entry *var;
>   	u32 var_name_len;
>   
> +	if (changep)
> +		*changep = true;
> +
> +	/*
> +	 * If this is not an append (size2 == 0), check whether the variable
> +	 * already exists with identical attributes and data. When nothing
> +	 * changed we can skip the write and avoid superfluous erases.
> +	 */
> +	if (!size2 && changep) {
> +		struct efi_var_entry *old;
> +
> +		old = efi_var_mem_find(vendor, variable_name, NULL);
> +		if (old && old->attr == attributes &&
> +		    old->length == size1 && old->time == time) {
> +			u16 *old_data;
> +
> +			for (old_data = old->name; *old_data; ++old_data)
> +				;
> +			++old_data;
> +
> +			if (!memcmp(old_data, data1, size1)) {
> +				*changep = false;
> +				return EFI_SUCCESS;
> +			}
> +		}
> +	}
> +
>   	var = (struct efi_var_entry *)
>   	      ((uintptr_t)efi_var_buf + efi_var_buf->length);
>   	var_name_len = u16_strlen(variable_name) + 1;
> diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c
> index 8512bc20f11e..9923936c1b59 100644
> --- a/lib/efi_loader/efi_variable.c
> +++ b/lib/efi_loader/efi_variable.c
> @@ -277,6 +277,7 @@ efi_status_t efi_set_variable_int(const u16 *variable_name,
>   	struct efi_var_entry *var;
>   	efi_uintn_t ret;
>   	bool append, delete;
> +	bool changed = false;
>   	u64 time = 0;
>   	enum efi_auth_var_type var_type;
>   
> @@ -366,6 +367,7 @@ efi_status_t efi_set_variable_int(const u16 *variable_name,
>   	if (delete) {
>   		/* EFI_NOT_FOUND has been handled before */
>   		attributes = var->attr;
> +		changed = true;
>   		ret = EFI_SUCCESS;
>   	} else if (append && var) {
>   		/*
> @@ -380,15 +382,19 @@ efi_status_t efi_set_variable_int(const u16 *variable_name,
>   		ret = efi_var_mem_ins(variable_name, vendor,
>   				      attributes & ~EFI_VARIABLE_APPEND_WRITE,
>   				      var->length, old_data, data_size, data,
> -				      time);
> +				      time, &changed);
>   	} else {
>   		ret = efi_var_mem_ins(variable_name, vendor, attributes,
> -				      data_size, data, 0, NULL, time);
> +				      data_size, data, 0, NULL, time,
> +				      &changed);
>   	}
>   
>   	if (ret != EFI_SUCCESS)
>   		return ret;
>   
> +	if (!changed)
> +		return EFI_SUCCESS;
> +
>   	efi_var_mem_del(var);
>   
>   	if (var_type == EFI_AUTH_VAR_PK)
> @@ -396,10 +402,7 @@ efi_status_t efi_set_variable_int(const u16 *variable_name,
>   	else
>   		ret = EFI_SUCCESS;
>   
> -	/*
> -	 * Write non-volatile EFI variables
> -	 * TODO: check if a value change has occured to avoid superfluous writes
> -	 */
> +	/* Write non-volatile EFI variables to storage */
>   	if (attributes & EFI_VARIABLE_NON_VOLATILE) {
>   		if (IS_ENABLED(CONFIG_EFI_VARIABLE_NO_STORE))
>   			return EFI_SUCCESS;
> @@ -498,6 +501,7 @@ efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor,
>   	struct efi_var_entry *var;
>   	efi_uintn_t ret;
>   	bool append, delete;
> +	bool changed = false;
>   	u64 time = 0;
>   
>   	if (!IS_ENABLED(CONFIG_EFI_RT_VOLATILE_STORE))
> @@ -549,6 +553,7 @@ efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor,
>   	if (delete) {
>   		/* EFI_NOT_FOUND has been handled before */
>   		attributes = var->attr;
> +		changed = true;
>   		ret = EFI_SUCCESS;
>   	} else if (append && var) {
>   		u16 *old_data = (void *)((uintptr_t)var->name +
> @@ -556,15 +561,19 @@ efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor,
>   
>   		ret = efi_var_mem_ins(variable_name, vendor, attributes,
>   				      var->length, old_data, data_size, data,
> -				      time);
> +				      time, &changed);
>   	} else {
>   		ret = efi_var_mem_ins(variable_name, vendor, attributes,
> -				      data_size, data, 0, NULL, time);
> +				      data_size, data, 0, NULL, time,
> +				      &changed);
>   	}
>   
>   	if (ret != EFI_SUCCESS)
>   		return ret;
> -	/* We are always inserting new variables, get rid of the old copy */
> +
> +	if (!changed)
> +		return EFI_SUCCESS;
> +
>   	efi_var_mem_del(var);
>   
>   	return EFI_SUCCESS;



More information about the U-Boot mailing list