aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware/efi/vars.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/firmware/efi/vars.c')
-rw-r--r--drivers/firmware/efi/vars.c258
1 files changed, 165 insertions, 93 deletions
diff --git a/drivers/firmware/efi/vars.c b/drivers/firmware/efi/vars.c
index cae590bd08f2..932435945c85 100644
--- a/drivers/firmware/efi/vars.c
+++ b/drivers/firmware/efi/vars.c
@@ -298,14 +298,10 @@ efivar_variable_is_removable(efi_guid_t vendor, const char *var_name,
}
EXPORT_SYMBOL_GPL(efivar_variable_is_removable);
-static efi_status_t
-check_var_size(u32 attributes, unsigned long size)
+efi_status_t check_var_size(u32 attributes, unsigned long size)
{
const struct efivar_operations *fops;
- if (!__efivars)
- return EFI_UNSUPPORTED;
-
fops = __efivars->ops;
if (!fops->query_variable_store)
@@ -313,15 +309,12 @@ check_var_size(u32 attributes, unsigned long size)
return fops->query_variable_store(attributes, size, false);
}
+EXPORT_SYMBOL_NS_GPL(check_var_size, EFIVAR);
-static efi_status_t
-check_var_size_nonblocking(u32 attributes, unsigned long size)
+efi_status_t check_var_size_nonblocking(u32 attributes, unsigned long size)
{
const struct efivar_operations *fops;
- if (!__efivars)
- return EFI_UNSUPPORTED;
-
fops = __efivars->ops;
if (!fops->query_variable_store)
@@ -329,6 +322,7 @@ check_var_size_nonblocking(u32 attributes, unsigned long size)
return fops->query_variable_store(attributes, size, true);
}
+EXPORT_SYMBOL_NS_GPL(check_var_size_nonblocking, EFIVAR);
static bool variable_is_present(efi_char16_t *variable_name, efi_guid_t *vendor,
struct list_head *head)
@@ -450,9 +444,6 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
&vendor_guid);
switch (status) {
case EFI_SUCCESS:
- if (duplicates)
- up(&efivars_lock);
-
variable_name_size = var_name_strnsize(variable_name,
variable_name_size);
@@ -476,14 +467,6 @@ int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
if (err)
status = EFI_NOT_FOUND;
}
-
- if (duplicates) {
- if (down_interruptible(&efivars_lock)) {
- err = -EINTR;
- goto free;
- }
- }
-
break;
case EFI_UNSUPPORTED:
err = -EOPNOTSUPP;
@@ -527,19 +510,23 @@ int efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
EXPORT_SYMBOL_GPL(efivar_entry_add);
/**
+ * __efivar_entry_add - add entry to variable list
+ * @entry: entry to add to list
+ * @head: list head
+ */
+void __efivar_entry_add(struct efivar_entry *entry, struct list_head *head)
+{
+ list_add(&entry->list, head);
+}
+EXPORT_SYMBOL_GPL(__efivar_entry_add);
+
+/**
* efivar_entry_remove - remove entry from variable list
* @entry: entry to remove from list
- *
- * Returns 0 on success, or a kernel error code on failure.
*/
-int efivar_entry_remove(struct efivar_entry *entry)
+void efivar_entry_remove(struct efivar_entry *entry)
{
- if (down_interruptible(&efivars_lock))
- return -EINTR;
list_del(&entry->list);
- up(&efivars_lock);
-
- return 0;
}
EXPORT_SYMBOL_GPL(efivar_entry_remove);
@@ -827,16 +814,8 @@ struct efivar_entry *efivar_entry_find(efi_char16_t *name, efi_guid_t guid,
if (!found)
return NULL;
- if (remove) {
- if (entry->scanning) {
- /*
- * The entry will be deleted
- * after scanning is completed.
- */
- entry->deleting = true;
- } else
- list_del(&entry->list);
- }
+ if (remove)
+ list_del(&entry->list);
return entry;
}
@@ -1056,59 +1035,6 @@ void efivar_entry_iter_end(void)
EXPORT_SYMBOL_GPL(efivar_entry_iter_end);
/**
- * __efivar_entry_iter - iterate over variable list
- * @func: callback function
- * @head: head of the variable list
- * @data: function-specific data to pass to callback
- * @prev: entry to begin iterating from
- *
- * Iterate over the list of EFI variables and call @func with every
- * entry on the list. It is safe for @func to remove entries in the
- * list via efivar_entry_delete().
- *
- * You MUST call efivar_entry_iter_begin() before this function, and
- * efivar_entry_iter_end() afterwards.
- *
- * It is possible to begin iteration from an arbitrary entry within
- * the list by passing @prev. @prev is updated on return to point to
- * the last entry passed to @func. To begin iterating from the
- * beginning of the list @prev must be %NULL.
- *
- * The restrictions for @func are the same as documented for
- * efivar_entry_iter().
- */
-int __efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
- struct list_head *head, void *data,
- struct efivar_entry **prev)
-{
- struct efivar_entry *entry, *n;
- int err = 0;
-
- if (!prev || !*prev) {
- list_for_each_entry_safe(entry, n, head, list) {
- err = func(entry, data);
- if (err)
- break;
- }
-
- if (prev)
- *prev = entry;
-
- return err;
- }
-
-
- list_for_each_entry_safe_continue((*prev), n, head, list) {
- err = func(*prev, data);
- if (err)
- break;
- }
-
- return err;
-}
-EXPORT_SYMBOL_GPL(__efivar_entry_iter);
-
-/**
* efivar_entry_iter - iterate over variable list
* @func: callback function
* @head: head of variable list
@@ -1125,12 +1051,18 @@ EXPORT_SYMBOL_GPL(__efivar_entry_iter);
int efivar_entry_iter(int (*func)(struct efivar_entry *, void *),
struct list_head *head, void *data)
{
+ struct efivar_entry *entry, *n;
int err = 0;
err = efivar_entry_iter_begin();
if (err)
return err;
- err = __efivar_entry_iter(func, head, data, NULL);
+
+ list_for_each_entry_safe(entry, n, head, list) {
+ err = func(entry, data);
+ if (err)
+ break;
+ }
efivar_entry_iter_end();
return err;
@@ -1220,3 +1152,143 @@ int efivar_supports_writes(void)
return __efivars && __efivars->ops->set_variable;
}
EXPORT_SYMBOL_GPL(efivar_supports_writes);
+
+/*
+ * efivar_lock() - obtain the efivar lock, wait for it if needed
+ * @return 0 on success, error code on failure
+ */
+int efivar_lock(void)
+{
+ if (down_interruptible(&efivars_lock))
+ return -EINTR;
+ if (!__efivars->ops) {
+ up(&efivars_lock);
+ return -ENODEV;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(efivar_lock, EFIVAR);
+
+/*
+ * efivar_lock() - obtain the efivar lock if it is free
+ * @return 0 on success, error code on failure
+ */
+int efivar_trylock(void)
+{
+ if (down_trylock(&efivars_lock))
+ return -EBUSY;
+ if (!__efivars->ops) {
+ up(&efivars_lock);
+ return -ENODEV;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(efivar_trylock, EFIVAR);
+
+/*
+ * efivar_unlock() - release the efivar lock
+ */
+void efivar_unlock(void)
+{
+ up(&efivars_lock);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_unlock, EFIVAR);
+
+/*
+ * efivar_get_variable() - retrieve a variable identified by name/vendor
+ *
+ * Must be called with efivars_lock held.
+ */
+efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 *attr, unsigned long *size, void *data)
+{
+ return __efivars->ops->get_variable(name, vendor, attr, size, data);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_get_variable, EFIVAR);
+
+/*
+ * efivar_get_next_variable() - enumerate the next name/vendor pair
+ *
+ * Must be called with efivars_lock held.
+ */
+efi_status_t efivar_get_next_variable(unsigned long *name_size,
+ efi_char16_t *name, efi_guid_t *vendor)
+{
+ return __efivars->ops->get_next_variable(name_size, name, vendor);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_get_next_variable, EFIVAR);
+
+/*
+ * efivar_set_variable_blocking() - local helper function for set_variable
+ *
+ * Must be called with efivars_lock held.
+ */
+static efi_status_t
+efivar_set_variable_blocking(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size, void *data)
+{
+ efi_status_t status;
+
+ if (data_size > 0) {
+ status = check_var_size(attr, data_size +
+ ucs2_strsize(name, 1024));
+ if (status != EFI_SUCCESS)
+ return status;
+ }
+ return __efivars->ops->set_variable(name, vendor, attr, data_size, data);
+}
+
+/*
+ * efivar_set_variable_locked() - set a variable identified by name/vendor
+ *
+ * Must be called with efivars_lock held. If @nonblocking is set, it will use
+ * non-blocking primitives so it is guaranteed not to sleep.
+ */
+efi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size,
+ void *data, bool nonblocking)
+{
+ efi_set_variable_t *setvar;
+ efi_status_t status;
+
+ if (!nonblocking)
+ return efivar_set_variable_blocking(name, vendor, attr,
+ data_size, data);
+
+ /*
+ * If no _nonblocking variant exists, the ordinary one
+ * is assumed to be non-blocking.
+ */
+ setvar = __efivars->ops->set_variable_nonblocking ?:
+ __efivars->ops->set_variable;
+
+ if (data_size > 0) {
+ status = check_var_size_nonblocking(attr, data_size +
+ ucs2_strsize(name, 1024));
+ if (status != EFI_SUCCESS)
+ return status;
+ }
+ return setvar(name, vendor, attr, data_size, data);
+}
+EXPORT_SYMBOL_NS_GPL(efivar_set_variable_locked, EFIVAR);
+
+/*
+ * efivar_set_variable() - set a variable identified by name/vendor
+ *
+ * Can be called without holding the efivars_lock. Will sleep on obtaining the
+ * lock, or on obtaining other locks that are needed in order to complete the
+ * call.
+ */
+efi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
+ u32 attr, unsigned long data_size, void *data)
+{
+ efi_status_t status;
+
+ if (efivar_lock())
+ return EFI_ABORTED;
+
+ status = efivar_set_variable_blocking(name, vendor, attr, data_size, data);
+ efivar_unlock();
+ return status;
+}
+EXPORT_SYMBOL_NS_GPL(efivar_set_variable, EFIVAR);