efi: Provide a non-blocking SetVariable() operation
authorMatt Fleming <matt.fleming@intel.com>
Tue, 30 Sep 2014 20:58:52 +0000 (21:58 +0100)
committerMatt Fleming <matt.fleming@intel.com>
Fri, 3 Oct 2014 17:41:03 +0000 (18:41 +0100)
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>
drivers/firmware/efi/runtime-wrappers.c
drivers/firmware/efi/vars.c
include/linux/efi.h

index 9694cba665c46aae34a6a4d155460a3993bcf65a..4349206198b211b8ba2a6291af358e8c94bf9130 100644 (file)
@@ -200,6 +200,24 @@ static efi_status_t virt_efi_set_variable(efi_char16_t *name,
        return status;
 }
 
+static efi_status_t
+virt_efi_set_variable_nonblocking(efi_char16_t *name, efi_guid_t *vendor,
+                                 u32 attr, unsigned long data_size,
+                                 void *data)
+{
+       unsigned long flags;
+       efi_status_t status;
+
+       if (!spin_trylock_irqsave(&efi_runtime_lock, flags))
+               return EFI_NOT_READY;
+
+       status = efi_call_virt(set_variable, name, vendor, attr, data_size,
+                              data);
+       spin_unlock_irqrestore(&efi_runtime_lock, flags);
+       return status;
+}
+
+
 static efi_status_t virt_efi_query_variable_info(u32 attr,
                                                 u64 *storage_space,
                                                 u64 *remaining_space,
@@ -287,6 +305,7 @@ void efi_native_runtime_setup(void)
        efi.get_variable = virt_efi_get_variable;
        efi.get_next_variable = virt_efi_get_next_variable;
        efi.set_variable = virt_efi_set_variable;
+       efi.set_variable_nonblocking = virt_efi_set_variable_nonblocking;
        efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count;
        efi.reset_system = virt_efi_reset_system;
        efi.query_variable_info = virt_efi_query_variable_info;
index 1fa724f31b0ecd648119a124d9e35c2444711fee..fa3c66bdc1e56e1818c37157d5040bdeff035696 100644 (file)
@@ -595,6 +595,39 @@ int efivar_entry_set(struct efivar_entry *entry, u32 attributes,
 }
 EXPORT_SYMBOL_GPL(efivar_entry_set);
 
+/*
+ * efivar_entry_set_nonblocking - call set_variable_nonblocking()
+ *
+ * This function is guaranteed to not block and is suitable for calling
+ * from crash/panic handlers.
+ *
+ * Crucially, this function will not block if it cannot acquire
+ * __efivars->lock. Instead, it returns -EBUSY.
+ */
+static int
+efivar_entry_set_nonblocking(efi_char16_t *name, efi_guid_t vendor,
+                            u32 attributes, unsigned long size, void *data)
+{
+       const struct efivar_operations *ops = __efivars->ops;
+       unsigned long flags;
+       efi_status_t status;
+
+       if (!spin_trylock_irqsave(&__efivars->lock, flags))
+               return -EBUSY;
+
+       status = check_var_size(attributes, size + ucs2_strsize(name, 1024));
+       if (status != EFI_SUCCESS) {
+               spin_unlock_irqrestore(&__efivars->lock, flags);
+               return -ENOSPC;
+       }
+
+       status = ops->set_variable_nonblocking(name, &vendor, attributes,
+                                              size, data);
+
+       spin_unlock_irqrestore(&__efivars->lock, flags);
+       return efi_status_to_err(status);
+}
+
 /**
  * efivar_entry_set_safe - call set_variable() if enough space in firmware
  * @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,
        if (!ops->query_variable_store)
                return -ENOSYS;
 
+       /*
+        * If the EFI variable backend provides a non-blocking
+        * ->set_variable() operation and we're in a context where we
+        * cannot block, then we need to use it to avoid live-locks,
+        * since the implication is that the regular ->set_variable()
+        * will block.
+        *
+        * If no ->set_variable_nonblocking() is provided then
+        * ->set_variable() is assumed to be non-blocking.
+        */
+       if (!block && ops->set_variable_nonblocking)
+               return efivar_entry_set_nonblocking(name, vendor, attributes,
+                                                   size, data);
+
        if (!block) {
                if (!spin_trylock_irqsave(&__efivars->lock, flags))
                        return -EBUSY;
index 78b29b133e143dee9ed741fe0f0075a83e3b607b..0949f9c7e872e7877e64d13341b5973d5a4ebd21 100644 (file)
@@ -503,6 +503,10 @@ typedef efi_status_t efi_get_next_variable_t (unsigned long *name_size, efi_char
 typedef efi_status_t efi_set_variable_t (efi_char16_t *name, efi_guid_t *vendor, 
                                         u32 attr, unsigned long data_size,
                                         void *data);
+typedef efi_status_t
+efi_set_variable_nonblocking_t(efi_char16_t *name, efi_guid_t *vendor,
+                              u32 attr, unsigned long data_size, void *data);
+
 typedef efi_status_t efi_get_next_high_mono_count_t (u32 *count);
 typedef void efi_reset_system_t (int reset_type, efi_status_t status,
                                 unsigned long data_size, efi_char16_t *data);
@@ -822,6 +826,7 @@ extern struct efi {
        efi_get_variable_t *get_variable;
        efi_get_next_variable_t *get_next_variable;
        efi_set_variable_t *set_variable;
+       efi_set_variable_nonblocking_t *set_variable_nonblocking;
        efi_query_variable_info_t *query_variable_info;
        efi_update_capsule_t *update_capsule;
        efi_query_capsule_caps_t *query_capsule_caps;
@@ -1042,6 +1047,7 @@ struct efivar_operations {
        efi_get_variable_t *get_variable;
        efi_get_next_variable_t *get_next_variable;
        efi_set_variable_t *set_variable;
+       efi_set_variable_nonblocking_t *set_variable_nonblocking;
        efi_query_variable_store_t *query_variable_store;
 };
 
This page took 0.028459 seconds and 5 git commands to generate.