diff options
Diffstat (limited to 'drivers/firmware/efi/vars.c')
-rw-r--r-- | drivers/firmware/efi/vars.c | 258 |
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); |