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