1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Originally from efivars.c
4 *
5 * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com>
6 * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com>
7 */
8
9#define pr_fmt(fmt) "efivars: " fmt
10
11#include <linux/types.h>
12#include <linux/sizes.h>
13#include <linux/errno.h>
14#include <linux/init.h>
15#include <linux/module.h>
16#include <linux/string.h>
17#include <linux/smp.h>
18#include <linux/efi.h>
19#include <linux/ucs2_string.h>
20
21/* Private pointer to registered efivars */
22static struct efivars *__efivars;
23
24static DEFINE_SEMAPHORE(efivars_lock, 1);
25
26static efi_status_t check_var_size(bool nonblocking, u32 attributes,
27				   unsigned long size)
28{
29	const struct efivar_operations *fops;
30	efi_status_t status;
31
32	fops = __efivars->ops;
33
34	if (!fops->query_variable_store)
35		status = EFI_UNSUPPORTED;
36	else
37		status = fops->query_variable_store(attributes, size,
38						    nonblocking);
39	if (status == EFI_UNSUPPORTED)
40		return (size <= SZ_64K) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES;
41	return status;
42}
43
44/**
45 * efivar_is_available - check if efivars is available
46 *
47 * @return true iff evivars is currently registered
48 */
49bool efivar_is_available(void)
50{
51	return __efivars != NULL;
52}
53EXPORT_SYMBOL_GPL(efivar_is_available);
54
55/**
56 * efivars_register - register an efivars
57 * @efivars: efivars to register
58 * @ops: efivars operations
59 *
60 * Only a single efivars can be registered at any time.
61 */
62int efivars_register(struct efivars *efivars,
63		     const struct efivar_operations *ops)
64{
65	int rv;
66
67	if (down_interruptible(&efivars_lock))
68		return -EINTR;
69
70	if (__efivars) {
71		pr_warn("efivars already registered\n");
72		rv = -EBUSY;
73		goto out;
74	}
75
76	efivars->ops = ops;
77
78	__efivars = efivars;
79
80	pr_info("Registered efivars operations\n");
81	rv = 0;
82out:
83	up(&efivars_lock);
84
85	return rv;
86}
87EXPORT_SYMBOL_GPL(efivars_register);
88
89/**
90 * efivars_unregister - unregister an efivars
91 * @efivars: efivars to unregister
92 *
93 * The caller must have already removed every entry from the list,
94 * failure to do so is an error.
95 */
96int efivars_unregister(struct efivars *efivars)
97{
98	int rv;
99
100	if (down_interruptible(&efivars_lock))
101		return -EINTR;
102
103	if (!__efivars) {
104		pr_err("efivars not registered\n");
105		rv = -EINVAL;
106		goto out;
107	}
108
109	if (__efivars != efivars) {
110		rv = -EINVAL;
111		goto out;
112	}
113
114	pr_info("Unregistered efivars operations\n");
115	__efivars = NULL;
116
117	rv = 0;
118out:
119	up(&efivars_lock);
120	return rv;
121}
122EXPORT_SYMBOL_GPL(efivars_unregister);
123
124bool efivar_supports_writes(void)
125{
126	return __efivars && __efivars->ops->set_variable;
127}
128EXPORT_SYMBOL_GPL(efivar_supports_writes);
129
130/*
131 * efivar_lock() - obtain the efivar lock, wait for it if needed
132 * @return 0 on success, error code on failure
133 */
134int efivar_lock(void)
135{
136	if (down_interruptible(&efivars_lock))
137		return -EINTR;
138	if (!__efivars->ops) {
139		up(&efivars_lock);
140		return -ENODEV;
141	}
142	return 0;
143}
144EXPORT_SYMBOL_NS_GPL(efivar_lock, EFIVAR);
145
146/*
147 * efivar_lock() - obtain the efivar lock if it is free
148 * @return 0 on success, error code on failure
149 */
150int efivar_trylock(void)
151{
152	if (down_trylock(&efivars_lock))
153		 return -EBUSY;
154	if (!__efivars->ops) {
155		up(&efivars_lock);
156		return -ENODEV;
157	}
158	return 0;
159}
160EXPORT_SYMBOL_NS_GPL(efivar_trylock, EFIVAR);
161
162/*
163 * efivar_unlock() - release the efivar lock
164 */
165void efivar_unlock(void)
166{
167	up(&efivars_lock);
168}
169EXPORT_SYMBOL_NS_GPL(efivar_unlock, EFIVAR);
170
171/*
172 * efivar_get_variable() - retrieve a variable identified by name/vendor
173 *
174 * Must be called with efivars_lock held.
175 */
176efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
177				 u32 *attr, unsigned long *size, void *data)
178{
179	return __efivars->ops->get_variable(name, vendor, attr, size, data);
180}
181EXPORT_SYMBOL_NS_GPL(efivar_get_variable, EFIVAR);
182
183/*
184 * efivar_get_next_variable() - enumerate the next name/vendor pair
185 *
186 * Must be called with efivars_lock held.
187 */
188efi_status_t efivar_get_next_variable(unsigned long *name_size,
189				      efi_char16_t *name, efi_guid_t *vendor)
190{
191	return __efivars->ops->get_next_variable(name_size, name, vendor);
192}
193EXPORT_SYMBOL_NS_GPL(efivar_get_next_variable, EFIVAR);
194
195/*
196 * efivar_set_variable_locked() - set a variable identified by name/vendor
197 *
198 * Must be called with efivars_lock held. If @nonblocking is set, it will use
199 * non-blocking primitives so it is guaranteed not to sleep.
200 */
201efi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor,
202					u32 attr, unsigned long data_size,
203					void *data, bool nonblocking)
204{
205	efi_set_variable_t *setvar;
206	efi_status_t status;
207
208	if (data_size > 0) {
209		status = check_var_size(nonblocking, attr,
210					data_size + ucs2_strsize(name, 1024));
211		if (status != EFI_SUCCESS)
212			return status;
213	}
214
215	/*
216	 * If no _nonblocking variant exists, the ordinary one
217	 * is assumed to be non-blocking.
218	 */
219	setvar = __efivars->ops->set_variable_nonblocking;
220	if (!setvar || !nonblocking)
221		 setvar = __efivars->ops->set_variable;
222
223	return setvar(name, vendor, attr, data_size, data);
224}
225EXPORT_SYMBOL_NS_GPL(efivar_set_variable_locked, EFIVAR);
226
227/*
228 * efivar_set_variable() - set a variable identified by name/vendor
229 *
230 * Can be called without holding the efivars_lock. Will sleep on obtaining the
231 * lock, or on obtaining other locks that are needed in order to complete the
232 * call.
233 */
234efi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
235				 u32 attr, unsigned long data_size, void *data)
236{
237	efi_status_t status;
238
239	if (efivar_lock())
240		return EFI_ABORTED;
241
242	status = efivar_set_variable_locked(name, vendor, attr, data_size,
243					    data, false);
244	efivar_unlock();
245	return status;
246}
247EXPORT_SYMBOL_NS_GPL(efivar_set_variable, EFIVAR);
248
249efi_status_t efivar_query_variable_info(u32 attr,
250					u64 *storage_space,
251					u64 *remaining_space,
252					u64 *max_variable_size)
253{
254	if (!__efivars->ops->query_variable_info)
255		return EFI_UNSUPPORTED;
256	return __efivars->ops->query_variable_info(attr, storage_space,
257			remaining_space, max_variable_size);
258}
259EXPORT_SYMBOL_NS_GPL(efivar_query_variable_info, EFIVAR);
260