162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Originally from efivars.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
662306a36Sopenharmony_ci * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define pr_fmt(fmt) "efivars: " fmt
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/types.h>
1262306a36Sopenharmony_ci#include <linux/sizes.h>
1362306a36Sopenharmony_ci#include <linux/errno.h>
1462306a36Sopenharmony_ci#include <linux/init.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/string.h>
1762306a36Sopenharmony_ci#include <linux/smp.h>
1862306a36Sopenharmony_ci#include <linux/efi.h>
1962306a36Sopenharmony_ci#include <linux/ucs2_string.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* Private pointer to registered efivars */
2262306a36Sopenharmony_cistatic struct efivars *__efivars;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic DEFINE_SEMAPHORE(efivars_lock, 1);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic efi_status_t check_var_size(bool nonblocking, u32 attributes,
2762306a36Sopenharmony_ci				   unsigned long size)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	const struct efivar_operations *fops;
3062306a36Sopenharmony_ci	efi_status_t status;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	fops = __efivars->ops;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	if (!fops->query_variable_store)
3562306a36Sopenharmony_ci		status = EFI_UNSUPPORTED;
3662306a36Sopenharmony_ci	else
3762306a36Sopenharmony_ci		status = fops->query_variable_store(attributes, size,
3862306a36Sopenharmony_ci						    nonblocking);
3962306a36Sopenharmony_ci	if (status == EFI_UNSUPPORTED)
4062306a36Sopenharmony_ci		return (size <= SZ_64K) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
4162306a36Sopenharmony_ci	return status;
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/**
4562306a36Sopenharmony_ci * efivar_is_available - check if efivars is available
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci * @return true iff evivars is currently registered
4862306a36Sopenharmony_ci */
4962306a36Sopenharmony_cibool efivar_is_available(void)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	return __efivars != NULL;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(efivar_is_available);
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/**
5662306a36Sopenharmony_ci * efivars_register - register an efivars
5762306a36Sopenharmony_ci * @efivars: efivars to register
5862306a36Sopenharmony_ci * @ops: efivars operations
5962306a36Sopenharmony_ci *
6062306a36Sopenharmony_ci * Only a single efivars can be registered at any time.
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_ciint efivars_register(struct efivars *efivars,
6362306a36Sopenharmony_ci		     const struct efivar_operations *ops)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	int rv;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (down_interruptible(&efivars_lock))
6862306a36Sopenharmony_ci		return -EINTR;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	if (__efivars) {
7162306a36Sopenharmony_ci		pr_warn("efivars already registered\n");
7262306a36Sopenharmony_ci		rv = -EBUSY;
7362306a36Sopenharmony_ci		goto out;
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	efivars->ops = ops;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	__efivars = efivars;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	pr_info("Registered efivars operations\n");
8162306a36Sopenharmony_ci	rv = 0;
8262306a36Sopenharmony_ciout:
8362306a36Sopenharmony_ci	up(&efivars_lock);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	return rv;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(efivars_register);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci/**
9062306a36Sopenharmony_ci * efivars_unregister - unregister an efivars
9162306a36Sopenharmony_ci * @efivars: efivars to unregister
9262306a36Sopenharmony_ci *
9362306a36Sopenharmony_ci * The caller must have already removed every entry from the list,
9462306a36Sopenharmony_ci * failure to do so is an error.
9562306a36Sopenharmony_ci */
9662306a36Sopenharmony_ciint efivars_unregister(struct efivars *efivars)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	int rv;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	if (down_interruptible(&efivars_lock))
10162306a36Sopenharmony_ci		return -EINTR;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (!__efivars) {
10462306a36Sopenharmony_ci		pr_err("efivars not registered\n");
10562306a36Sopenharmony_ci		rv = -EINVAL;
10662306a36Sopenharmony_ci		goto out;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (__efivars != efivars) {
11062306a36Sopenharmony_ci		rv = -EINVAL;
11162306a36Sopenharmony_ci		goto out;
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	pr_info("Unregistered efivars operations\n");
11562306a36Sopenharmony_ci	__efivars = NULL;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	rv = 0;
11862306a36Sopenharmony_ciout:
11962306a36Sopenharmony_ci	up(&efivars_lock);
12062306a36Sopenharmony_ci	return rv;
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(efivars_unregister);
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cibool efivar_supports_writes(void)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	return __efivars && __efivars->ops->set_variable;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(efivar_supports_writes);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci/*
13162306a36Sopenharmony_ci * efivar_lock() - obtain the efivar lock, wait for it if needed
13262306a36Sopenharmony_ci * @return 0 on success, error code on failure
13362306a36Sopenharmony_ci */
13462306a36Sopenharmony_ciint efivar_lock(void)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	if (down_interruptible(&efivars_lock))
13762306a36Sopenharmony_ci		return -EINTR;
13862306a36Sopenharmony_ci	if (!__efivars->ops) {
13962306a36Sopenharmony_ci		up(&efivars_lock);
14062306a36Sopenharmony_ci		return -ENODEV;
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci	return 0;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_lock, EFIVAR);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci/*
14762306a36Sopenharmony_ci * efivar_lock() - obtain the efivar lock if it is free
14862306a36Sopenharmony_ci * @return 0 on success, error code on failure
14962306a36Sopenharmony_ci */
15062306a36Sopenharmony_ciint efivar_trylock(void)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	if (down_trylock(&efivars_lock))
15362306a36Sopenharmony_ci		 return -EBUSY;
15462306a36Sopenharmony_ci	if (!__efivars->ops) {
15562306a36Sopenharmony_ci		up(&efivars_lock);
15662306a36Sopenharmony_ci		return -ENODEV;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci	return 0;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_trylock, EFIVAR);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/*
16362306a36Sopenharmony_ci * efivar_unlock() - release the efivar lock
16462306a36Sopenharmony_ci */
16562306a36Sopenharmony_civoid efivar_unlock(void)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	up(&efivars_lock);
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_unlock, EFIVAR);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci/*
17262306a36Sopenharmony_ci * efivar_get_variable() - retrieve a variable identified by name/vendor
17362306a36Sopenharmony_ci *
17462306a36Sopenharmony_ci * Must be called with efivars_lock held.
17562306a36Sopenharmony_ci */
17662306a36Sopenharmony_ciefi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
17762306a36Sopenharmony_ci				 u32 *attr, unsigned long *size, void *data)
17862306a36Sopenharmony_ci{
17962306a36Sopenharmony_ci	return __efivars->ops->get_variable(name, vendor, attr, size, data);
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_get_variable, EFIVAR);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci/*
18462306a36Sopenharmony_ci * efivar_get_next_variable() - enumerate the next name/vendor pair
18562306a36Sopenharmony_ci *
18662306a36Sopenharmony_ci * Must be called with efivars_lock held.
18762306a36Sopenharmony_ci */
18862306a36Sopenharmony_ciefi_status_t efivar_get_next_variable(unsigned long *name_size,
18962306a36Sopenharmony_ci				      efi_char16_t *name, efi_guid_t *vendor)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	return __efivars->ops->get_next_variable(name_size, name, vendor);
19262306a36Sopenharmony_ci}
19362306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_get_next_variable, EFIVAR);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci/*
19662306a36Sopenharmony_ci * efivar_set_variable_locked() - set a variable identified by name/vendor
19762306a36Sopenharmony_ci *
19862306a36Sopenharmony_ci * Must be called with efivars_lock held. If @nonblocking is set, it will use
19962306a36Sopenharmony_ci * non-blocking primitives so it is guaranteed not to sleep.
20062306a36Sopenharmony_ci */
20162306a36Sopenharmony_ciefi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor,
20262306a36Sopenharmony_ci					u32 attr, unsigned long data_size,
20362306a36Sopenharmony_ci					void *data, bool nonblocking)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	efi_set_variable_t *setvar;
20662306a36Sopenharmony_ci	efi_status_t status;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (data_size > 0) {
20962306a36Sopenharmony_ci		status = check_var_size(nonblocking, attr,
21062306a36Sopenharmony_ci					data_size + ucs2_strsize(name, 1024));
21162306a36Sopenharmony_ci		if (status != EFI_SUCCESS)
21262306a36Sopenharmony_ci			return status;
21362306a36Sopenharmony_ci	}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	/*
21662306a36Sopenharmony_ci	 * If no _nonblocking variant exists, the ordinary one
21762306a36Sopenharmony_ci	 * is assumed to be non-blocking.
21862306a36Sopenharmony_ci	 */
21962306a36Sopenharmony_ci	setvar = __efivars->ops->set_variable_nonblocking;
22062306a36Sopenharmony_ci	if (!setvar || !nonblocking)
22162306a36Sopenharmony_ci		 setvar = __efivars->ops->set_variable;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	return setvar(name, vendor, attr, data_size, data);
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_set_variable_locked, EFIVAR);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci/*
22862306a36Sopenharmony_ci * efivar_set_variable() - set a variable identified by name/vendor
22962306a36Sopenharmony_ci *
23062306a36Sopenharmony_ci * Can be called without holding the efivars_lock. Will sleep on obtaining the
23162306a36Sopenharmony_ci * lock, or on obtaining other locks that are needed in order to complete the
23262306a36Sopenharmony_ci * call.
23362306a36Sopenharmony_ci */
23462306a36Sopenharmony_ciefi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
23562306a36Sopenharmony_ci				 u32 attr, unsigned long data_size, void *data)
23662306a36Sopenharmony_ci{
23762306a36Sopenharmony_ci	efi_status_t status;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (efivar_lock())
24062306a36Sopenharmony_ci		return EFI_ABORTED;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	status = efivar_set_variable_locked(name, vendor, attr, data_size,
24362306a36Sopenharmony_ci					    data, false);
24462306a36Sopenharmony_ci	efivar_unlock();
24562306a36Sopenharmony_ci	return status;
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_set_variable, EFIVAR);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ciefi_status_t efivar_query_variable_info(u32 attr,
25062306a36Sopenharmony_ci					u64 *storage_space,
25162306a36Sopenharmony_ci					u64 *remaining_space,
25262306a36Sopenharmony_ci					u64 *max_variable_size)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	if (!__efivars->ops->query_variable_info)
25562306a36Sopenharmony_ci		return EFI_UNSUPPORTED;
25662306a36Sopenharmony_ci	return __efivars->ops->query_variable_info(attr, storage_space,
25762306a36Sopenharmony_ci			remaining_space, max_variable_size);
25862306a36Sopenharmony_ci}
25962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(efivar_query_variable_info, EFIVAR);
260