Skip to content

Commit 6d80dba

Browse files
author
Matt Fleming
committed
efi: Provide a non-blocking SetVariable() operation
There are some circumstances that call for trying to write an EFI variable in a non-blocking way. One such scenario is when writing pstore data in efi_pstore_write() via the pstore_dump() kdump callback. Now that we have an EFI runtime spinlock we need a way of aborting if there is contention instead of spinning, since when writing pstore data from the kdump callback, the runtime lock may already be held by the CPU that's running the callback if we crashed in the middle of an EFI variable operation. The situation is sufficiently special that a new EFI variable operation is warranted. Introduce ->set_variable_nonblocking() for this use case. It is an optional EFI backend operation, and need only be implemented by those backends that usually acquire locks to serialize access to EFI variables, as is the case for virt_efi_set_variable() where we now grab the EFI runtime spinlock. Cc: Peter Zijlstra <peterz@infradead.org> Cc: Ingo Molnar <mingo@kernel.org> Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org> Cc: Matthew Garrett <mjg59@srcf.ucam.org> Signed-off-by: Matt Fleming <matt.fleming@intel.com>
1 parent 77e21e8 commit 6d80dba

File tree

3 files changed

+72
-0
lines changed

3 files changed

+72
-0
lines changed

drivers/firmware/efi/runtime-wrappers.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,24 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
200200
return status;
201201
}
202202

203+
static efi_status_t
204+
virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor,
205+
u32 attr, unsigned long data_size,
206+
void *data)
207+
{
208+
unsigned long flags;
209+
efi_status_t status;
210+
211+
if (!spin_trylock_irqsave(&efi_runtime_lock, flags))
212+
return EFI_NOT_READY;
213+
214+
status = efi_call_virt(set_variable, name, vendor, attr, data_size,
215+
data);
216+
spin_unlock_irqrestore(&efi_runtime_lock, flags);
217+
return status;
218+
}
219+
220+
203221
static efi_status_t virt_efi_query_variable_info(u32 attr,
204222
u64 *storage_space,
205223
u64 *remaining_space,
@@ -287,6 +305,7 @@ void efi_native_runtime_setup(void)
287305
efi.get_variable = virt_efi_get_variable;
288306
efi.get_next_variable = virt_efi_get_next_variable;
289307
efi.set_variable = virt_efi_set_variable;
308+
efi.set_variable_nonblocking = virt_efi_set_variable_nonblocking;
290309
efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count;
291310
efi.reset_system = virt_efi_reset_system;
292311
efi.query_variable_info = virt_efi_query_variable_info;

drivers/firmware/efi/vars.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,39 @@ int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
595595
}
596596
EXPORT_SYMBOL_GPL(efivar_entry_set);
597597

598+
/*
599+
* efivar_entry_set_nonblocking - call set_variable_nonblocking()
600+
*
601+
* This function is guaranteed to not block and is suitable for calling
602+
* from crash/panic handlers.
603+
*
604+
* Crucially, this function will not block if it cannot acquire
605+
* __efivars->lock. Instead, it returns -EBUSY.
606+
*/
607+
static int
608+
efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
609+
u32 attributes, unsigned long size, void *data)
610+
{
611+
const struct efivar_operations *ops = __efivars->ops;
612+
unsigned long flags;
613+
efi_status_t status;
614+
615+
if (!spin_trylock_irqsave(&__efivars->lock, flags))
616+
return -EBUSY;
617+
618+
status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
619+
if (status != EFI_SUCCESS) {
620+
spin_unlock_irqrestore(&__efivars->lock, flags);
621+
return -ENOSPC;
622+
}
623+
624+
status = ops->set_variable_nonblocking(name, &vendor, attributes,
625+
size, data);
626+
627+
spin_unlock_irqrestore(&__efivars->lock, flags);
628+
return efi_status_to_err(status);
629+
}
630+
598631
/**
599632
* efivar_entry_set_safe - call set_variable() if enough space in firmware
600633
* @name: buffer containing the variable name
@@ -622,6 +655,20 @@ int efivar_entry_set_safe(efi_char16_t *name, efi_guid_t vendor, u32 attributes,
622655
if (!ops->query_variable_store)
623656
return -ENOSYS;
624657

658+
/*
659+
* If the EFI variable backend provides a non-blocking
660+
* ->set_variable() operation and we're in a context where we
661+
* cannot block, then we need to use it to avoid live-locks,
662+
* since the implication is that the regular ->set_variable()
663+
* will block.
664+
*
665+
* If no ->set_variable_nonblocking() is provided then
666+
* ->set_variable() is assumed to be non-blocking.
667+
*/
668+
if (!block && ops->set_variable_nonblocking)
669+
return efivar_entry_set_nonblocking(name, vendor, attributes,
670+
size, data);
671+
625672
if (!block) {
626673
if (!spin_trylock_irqsave(&__efivars->lock, flags))
627674
return -EBUSY;

include/linux/efi.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,10 @@ typedef efi_status_t efi_get_next_variable_t (unsigned long *name_size, efi_char
503503
typedef efi_status_t efi_set_variable_t (efi_char16_t *name, efi_guid_t *vendor,
504504
u32 attr, unsigned long data_size,
505505
void *data);
506+
typedef efi_status_t
507+
efi_set_variable_nonblocking_t(efi_char16_t *name, efi_guid_t *vendor,
508+
u32 attr, unsigned long data_size, void *data);
509+
506510
typedef efi_status_t efi_get_next_high_mono_count_t (u32 *count);
507511
typedef void efi_reset_system_t (int reset_type, efi_status_t status,
508512
unsigned long data_size, efi_char16_t *data);
@@ -822,6 +826,7 @@ extern struct efi {
822826
efi_get_variable_t *get_variable;
823827
efi_get_next_variable_t *get_next_variable;
824828
efi_set_variable_t *set_variable;
829+
efi_set_variable_nonblocking_t *set_variable_nonblocking;
825830
efi_query_variable_info_t *query_variable_info;
826831
efi_update_capsule_t *update_capsule;
827832
efi_query_capsule_caps_t *query_capsule_caps;
@@ -1042,6 +1047,7 @@ struct efivar_operations {
10421047
efi_get_variable_t *get_variable;
10431048
efi_get_next_variable_t *get_next_variable;
10441049
efi_set_variable_t *set_variable;
1050+
efi_set_variable_nonblocking_t *set_variable_nonblocking;
10451051
efi_query_variable_store_t *query_variable_store;
10461052
};
10471053

0 commit comments

Comments
 (0)