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 */
22 static struct efivars *__efivars;
23 
24 static DEFINE_SEMAPHORE(efivars_lock, 1);
25 
check_var_size(bool nonblocking,u32 attributes,unsigned long size)26 static 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  */
efivar_is_available(void)49 bool efivar_is_available(void)
50 {
51 	return __efivars != NULL;
52 }
53 EXPORT_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  */
efivars_register(struct efivars * efivars,const struct efivar_operations * ops)62 int 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;
82 out:
83 	up(&efivars_lock);
84 
85 	return rv;
86 }
87 EXPORT_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  */
efivars_unregister(struct efivars * efivars)96 int 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;
118 out:
119 	up(&efivars_lock);
120 	return rv;
121 }
122 EXPORT_SYMBOL_GPL(efivars_unregister);
123 
efivar_supports_writes(void)124 bool efivar_supports_writes(void)
125 {
126 	return __efivars && __efivars->ops->set_variable;
127 }
128 EXPORT_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  */
efivar_lock(void)134 int 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 }
144 EXPORT_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  */
efivar_trylock(void)150 int 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 }
160 EXPORT_SYMBOL_NS_GPL(efivar_trylock, EFIVAR);
161 
162 /*
163  * efivar_unlock() - release the efivar lock
164  */
efivar_unlock(void)165 void efivar_unlock(void)
166 {
167 	up(&efivars_lock);
168 }
169 EXPORT_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  */
efivar_get_variable(efi_char16_t * name,efi_guid_t * vendor,u32 * attr,unsigned long * size,void * data)176 efi_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 }
181 EXPORT_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  */
efivar_get_next_variable(unsigned long * name_size,efi_char16_t * name,efi_guid_t * vendor)188 efi_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 }
193 EXPORT_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  */
efivar_set_variable_locked(efi_char16_t * name,efi_guid_t * vendor,u32 attr,unsigned long data_size,void * data,bool nonblocking)201 efi_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 }
225 EXPORT_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  */
efivar_set_variable(efi_char16_t * name,efi_guid_t * vendor,u32 attr,unsigned long data_size,void * data)234 efi_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 }
247 EXPORT_SYMBOL_NS_GPL(efivar_set_variable, EFIVAR);
248 
efivar_query_variable_info(u32 attr,u64 * storage_space,u64 * remaining_space,u64 * max_variable_size)249 efi_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 }
259 EXPORT_SYMBOL_NS_GPL(efivar_query_variable_info, EFIVAR);
260