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 | |
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 | */ |
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 | */ |
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(sem: &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(nh: &efivar_ops_nh, val: event, NULL); |
87 | |
88 | pr_info("Registered efivars operations\n" ); |
89 | rv = 0; |
90 | out: |
91 | up(sem: &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 | */ |
104 | int efivars_unregister(struct efivars *efivars) |
105 | { |
106 | int rv; |
107 | |
108 | if (down_interruptible(sem: &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(sem: &efivars_lock); |
128 | return rv; |
129 | } |
130 | EXPORT_SYMBOL_GPL(efivars_unregister); |
131 | |
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 | */ |
142 | int efivar_lock(void) |
143 | { |
144 | if (down_interruptible(sem: &efivars_lock)) |
145 | return -EINTR; |
146 | if (!__efivars->ops) { |
147 | up(sem: &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 | */ |
158 | int efivar_trylock(void) |
159 | { |
160 | if (down_trylock(sem: &efivars_lock)) |
161 | return -EBUSY; |
162 | if (!__efivars->ops) { |
163 | up(sem: &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 | */ |
173 | void efivar_unlock(void) |
174 | { |
175 | up(sem: &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 | */ |
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 | */ |
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 | */ |
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, attributes: attr, |
218 | size: data_size + ucs2_strsize(data: name, maxlength: 1024)); |
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 | */ |
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 | |
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 | |