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