xref: /linux/drivers/firmware/efi/vars.c (revision cda30c65)
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 	int event;
67 
68 	if (down_interruptible(&efivars_lock))
69 		return -EINTR;
70 
71 	if (__efivars) {
72 		pr_warn("efivars already registered\n");
73 		rv = -EBUSY;
74 		goto out;
75 	}
76 
77 	efivars->ops = ops;
78 
79 	__efivars = efivars;
80 
81 	if (efivar_supports_writes())
82 		event = EFIVAR_OPS_RDWR;
83 	else
84 		event = EFIVAR_OPS_RDONLY;
85 
86 	blocking_notifier_call_chain(&efivar_ops_nh, event, NULL);
87 
88 	pr_info("Registered efivars operations\n");
89 	rv = 0;
90 out:
91 	up(&efivars_lock);
92 
93 	return rv;
94 }
95 EXPORT_SYMBOL_GPL(efivars_register);
96 
97 /**
98  * efivars_unregister - unregister an efivars
99  * @efivars: efivars to unregister
100  *
101  * The caller must have already removed every entry from the list,
102  * failure to do so is an error.
103  */
efivars_unregister(struct efivars * efivars)104 int efivars_unregister(struct efivars *efivars)
105 {
106 	int rv;
107 
108 	if (down_interruptible(&efivars_lock))
109 		return -EINTR;
110 
111 	if (!__efivars) {
112 		pr_err("efivars not registered\n");
113 		rv = -EINVAL;
114 		goto out;
115 	}
116 
117 	if (__efivars != efivars) {
118 		rv = -EINVAL;
119 		goto out;
120 	}
121 
122 	pr_info("Unregistered efivars operations\n");
123 	__efivars = NULL;
124 
125 	rv = 0;
126 out:
127 	up(&efivars_lock);
128 	return rv;
129 }
130 EXPORT_SYMBOL_GPL(efivars_unregister);
131 
efivar_supports_writes(void)132 bool efivar_supports_writes(void)
133 {
134 	return __efivars && __efivars->ops->set_variable;
135 }
136 EXPORT_SYMBOL_GPL(efivar_supports_writes);
137 
138 /*
139  * efivar_lock() - obtain the efivar lock, wait for it if needed
140  * @return 0 on success, error code on failure
141  */
efivar_lock(void)142 int efivar_lock(void)
143 {
144 	if (down_interruptible(&efivars_lock))
145 		return -EINTR;
146 	if (!__efivars->ops) {
147 		up(&efivars_lock);
148 		return -ENODEV;
149 	}
150 	return 0;
151 }
152 EXPORT_SYMBOL_NS_GPL(efivar_lock, EFIVAR);
153 
154 /*
155  * efivar_lock() - obtain the efivar lock if it is free
156  * @return 0 on success, error code on failure
157  */
efivar_trylock(void)158 int efivar_trylock(void)
159 {
160 	if (down_trylock(&efivars_lock))
161 		 return -EBUSY;
162 	if (!__efivars->ops) {
163 		up(&efivars_lock);
164 		return -ENODEV;
165 	}
166 	return 0;
167 }
168 EXPORT_SYMBOL_NS_GPL(efivar_trylock, EFIVAR);
169 
170 /*
171  * efivar_unlock() - release the efivar lock
172  */
efivar_unlock(void)173 void efivar_unlock(void)
174 {
175 	up(&efivars_lock);
176 }
177 EXPORT_SYMBOL_NS_GPL(efivar_unlock, EFIVAR);
178 
179 /*
180  * efivar_get_variable() - retrieve a variable identified by name/vendor
181  *
182  * Must be called with efivars_lock held.
183  */
efivar_get_variable(efi_char16_t * name,efi_guid_t * vendor,u32 * attr,unsigned long * size,void * data)184 efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor,
185 				 u32 *attr, unsigned long *size, void *data)
186 {
187 	return __efivars->ops->get_variable(name, vendor, attr, size, data);
188 }
189 EXPORT_SYMBOL_NS_GPL(efivar_get_variable, EFIVAR);
190 
191 /*
192  * efivar_get_next_variable() - enumerate the next name/vendor pair
193  *
194  * Must be called with efivars_lock held.
195  */
efivar_get_next_variable(unsigned long * name_size,efi_char16_t * name,efi_guid_t * vendor)196 efi_status_t efivar_get_next_variable(unsigned long *name_size,
197 				      efi_char16_t *name, efi_guid_t *vendor)
198 {
199 	return __efivars->ops->get_next_variable(name_size, name, vendor);
200 }
201 EXPORT_SYMBOL_NS_GPL(efivar_get_next_variable, EFIVAR);
202 
203 /*
204  * efivar_set_variable_locked() - set a variable identified by name/vendor
205  *
206  * Must be called with efivars_lock held. If @nonblocking is set, it will use
207  * non-blocking primitives so it is guaranteed not to sleep.
208  */
efivar_set_variable_locked(efi_char16_t * name,efi_guid_t * vendor,u32 attr,unsigned long data_size,void * data,bool nonblocking)209 efi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor,
210 					u32 attr, unsigned long data_size,
211 					void *data, bool nonblocking)
212 {
213 	efi_set_variable_t *setvar;
214 	efi_status_t status;
215 
216 	if (data_size > 0) {
217 		status = check_var_size(nonblocking, attr,
218 					data_size + ucs2_strsize(name, EFI_VAR_NAME_LEN));
219 		if (status != EFI_SUCCESS)
220 			return status;
221 	}
222 
223 	/*
224 	 * If no _nonblocking variant exists, the ordinary one
225 	 * is assumed to be non-blocking.
226 	 */
227 	setvar = __efivars->ops->set_variable_nonblocking;
228 	if (!setvar || !nonblocking)
229 		 setvar = __efivars->ops->set_variable;
230 
231 	return setvar(name, vendor, attr, data_size, data);
232 }
233 EXPORT_SYMBOL_NS_GPL(efivar_set_variable_locked, EFIVAR);
234 
235 /*
236  * efivar_set_variable() - set a variable identified by name/vendor
237  *
238  * Can be called without holding the efivars_lock. Will sleep on obtaining the
239  * lock, or on obtaining other locks that are needed in order to complete the
240  * call.
241  */
efivar_set_variable(efi_char16_t * name,efi_guid_t * vendor,u32 attr,unsigned long data_size,void * data)242 efi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor,
243 				 u32 attr, unsigned long data_size, void *data)
244 {
245 	efi_status_t status;
246 
247 	if (efivar_lock())
248 		return EFI_ABORTED;
249 
250 	status = efivar_set_variable_locked(name, vendor, attr, data_size,
251 					    data, false);
252 	efivar_unlock();
253 	return status;
254 }
255 EXPORT_SYMBOL_NS_GPL(efivar_set_variable, EFIVAR);
256 
efivar_query_variable_info(u32 attr,u64 * storage_space,u64 * remaining_space,u64 * max_variable_size)257 efi_status_t efivar_query_variable_info(u32 attr,
258 					u64 *storage_space,
259 					u64 *remaining_space,
260 					u64 *max_variable_size)
261 {
262 	if (!__efivars->ops->query_variable_info)
263 		return EFI_UNSUPPORTED;
264 	return __efivars->ops->query_variable_info(attr, storage_space,
265 			remaining_space, max_variable_size);
266 }
267 EXPORT_SYMBOL_NS_GPL(efivar_query_variable_info, EFIVAR);
268