Commit 472831d4 authored by Ard Biesheuvel's avatar Ard Biesheuvel

efi: vars: Add thin wrapper around EFI get/set variable interface

The current efivars layer is a jumble of list iterators, shadow data
structures and safe variable manipulation helpers that really belong in
the efivarfs pseudo file system once the obsolete sysfs access method to
EFI variables is removed.

So split off a minimal efivar get/set variable API that reuses the
existing efivars_lock semaphore to mediate access to the various runtime
services, primarily to ensure that performing a SetVariable() on one CPU
while another is calling GetNextVariable() in a loop to enumerate the
contents of the EFI variable store does not result in surprises.
Signed-off-by: default avatarArd Biesheuvel <ardb@kernel.org>
parent ec3507b2
......@@ -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)
......@@ -1220,3 +1214,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);
......@@ -1099,6 +1099,26 @@ bool efivar_validate(efi_guid_t vendor, efi_char16_t *var_name, u8 *data,
bool efivar_variable_is_removable(efi_guid_t vendor, const char *name,
size_t len);
int efivar_lock(void);
int efivar_trylock(void);
void efivar_unlock(void);
efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
u32 *attr, unsigned long *size, void *data);
efi_status_t efivar_get_next_variable(unsigned long *name_size,
efi_char16_t *name, efi_guid_t *vendor);
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_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
u32 attr, unsigned long data_size, void *data);
efi_status_t check_var_size(u32 attributes, unsigned long size);
efi_status_t check_var_size_nonblocking(u32 attributes, unsigned long size);
#if IS_ENABLED(CONFIG_EFI_CAPSULE_LOADER)
extern bool efi_capsule_pending(int *reset_type);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment