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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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