|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | * Originally from efivars.c | 
|  | * | 
|  | * Copyright (C) 2001,2003,2004 Dell <Matt_Domsch@dell.com> | 
|  | * Copyright (C) 2004 Intel Corporation <matthew.e.tolentino@intel.com> | 
|  | */ | 
|  |  | 
|  | #define pr_fmt(fmt) "efivars: " fmt | 
|  |  | 
|  | #include <linux/types.h> | 
|  | #include <linux/sizes.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/smp.h> | 
|  | #include <linux/efi.h> | 
|  | #include <linux/ucs2_string.h> | 
|  |  | 
|  | /* Private pointer to registered efivars */ | 
|  | static struct efivars *__efivars; | 
|  |  | 
|  | static DEFINE_SEMAPHORE(efivars_lock, 1); | 
|  |  | 
|  | static efi_status_t check_var_size(bool nonblocking, u32 attributes, | 
|  | unsigned long size) | 
|  | { | 
|  | const struct efivar_operations *fops; | 
|  | efi_status_t status; | 
|  |  | 
|  | fops = __efivars->ops; | 
|  |  | 
|  | if (!fops->query_variable_store) | 
|  | status = EFI_UNSUPPORTED; | 
|  | else | 
|  | status = fops->query_variable_store(attributes, size, | 
|  | nonblocking); | 
|  | if (status == EFI_UNSUPPORTED) | 
|  | return (size <= SZ_64K) ? EFI_SUCCESS : EFI_OUT_OF_RESOURCES; | 
|  | return status; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * efivar_is_available - check if efivars is available | 
|  | * | 
|  | * @return true iff evivars is currently registered | 
|  | */ | 
|  | bool efivar_is_available(void) | 
|  | { | 
|  | return __efivars != NULL; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(efivar_is_available); | 
|  |  | 
|  | /** | 
|  | * efivars_register - register an efivars | 
|  | * @efivars: efivars to register | 
|  | * @ops: efivars operations | 
|  | * | 
|  | * Only a single efivars can be registered at any time. | 
|  | */ | 
|  | int efivars_register(struct efivars *efivars, | 
|  | const struct efivar_operations *ops) | 
|  | { | 
|  | int rv; | 
|  | int event; | 
|  |  | 
|  | if (down_interruptible(&efivars_lock)) | 
|  | return -EINTR; | 
|  |  | 
|  | if (__efivars) { | 
|  | pr_warn("efivars already registered\n"); | 
|  | rv = -EBUSY; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | efivars->ops = ops; | 
|  |  | 
|  | __efivars = efivars; | 
|  |  | 
|  | if (efivar_supports_writes()) | 
|  | event = EFIVAR_OPS_RDWR; | 
|  | else | 
|  | event = EFIVAR_OPS_RDONLY; | 
|  |  | 
|  | blocking_notifier_call_chain(&efivar_ops_nh, event, NULL); | 
|  |  | 
|  | pr_info("Registered efivars operations\n"); | 
|  | rv = 0; | 
|  | out: | 
|  | up(&efivars_lock); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(efivars_register); | 
|  |  | 
|  | /** | 
|  | * efivars_unregister - unregister an efivars | 
|  | * @efivars: efivars to unregister | 
|  | * | 
|  | * The caller must have already removed every entry from the list, | 
|  | * failure to do so is an error. | 
|  | */ | 
|  | int efivars_unregister(struct efivars *efivars) | 
|  | { | 
|  | int rv; | 
|  |  | 
|  | if (down_interruptible(&efivars_lock)) | 
|  | return -EINTR; | 
|  |  | 
|  | if (!__efivars) { | 
|  | pr_err("efivars not registered\n"); | 
|  | rv = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (__efivars != efivars) { | 
|  | rv = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | pr_info("Unregistered efivars operations\n"); | 
|  | __efivars = NULL; | 
|  |  | 
|  | rv = 0; | 
|  | out: | 
|  | up(&efivars_lock); | 
|  | return rv; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(efivars_unregister); | 
|  |  | 
|  | bool efivar_supports_writes(void) | 
|  | { | 
|  | return __efivars && __efivars->ops->set_variable; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(efivar_supports_writes); | 
|  |  | 
|  | /* | 
|  | * efivar_lock() - obtain the efivar lock, wait for it if needed | 
|  | * @return 0 on success, error code on failure | 
|  | */ | 
|  | int efivar_lock(void) | 
|  | { | 
|  | if (down_interruptible(&efivars_lock)) | 
|  | return -EINTR; | 
|  | if (!__efivars->ops) { | 
|  | up(&efivars_lock); | 
|  | return -ENODEV; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_lock, "EFIVAR"); | 
|  |  | 
|  | /* | 
|  | * efivar_lock() - obtain the efivar lock if it is free | 
|  | * @return 0 on success, error code on failure | 
|  | */ | 
|  | int efivar_trylock(void) | 
|  | { | 
|  | if (down_trylock(&efivars_lock)) | 
|  | return -EBUSY; | 
|  | if (!__efivars->ops) { | 
|  | up(&efivars_lock); | 
|  | return -ENODEV; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_trylock, "EFIVAR"); | 
|  |  | 
|  | /* | 
|  | * efivar_unlock() - release the efivar lock | 
|  | */ | 
|  | void efivar_unlock(void) | 
|  | { | 
|  | up(&efivars_lock); | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_unlock, "EFIVAR"); | 
|  |  | 
|  | /* | 
|  | * efivar_get_variable() - retrieve a variable identified by name/vendor | 
|  | * | 
|  | * Must be called with efivars_lock held. | 
|  | */ | 
|  | efi_status_t efivar_get_variable(efi_char16_t *name, efi_guid_t *vendor, | 
|  | u32 *attr, unsigned long *size, void *data) | 
|  | { | 
|  | return __efivars->ops->get_variable(name, vendor, attr, size, data); | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_get_variable, "EFIVAR"); | 
|  |  | 
|  | /* | 
|  | * efivar_get_next_variable() - enumerate the next name/vendor pair | 
|  | * | 
|  | * Must be called with efivars_lock held. | 
|  | */ | 
|  | efi_status_t efivar_get_next_variable(unsigned long *name_size, | 
|  | efi_char16_t *name, efi_guid_t *vendor) | 
|  | { | 
|  | return __efivars->ops->get_next_variable(name_size, name, vendor); | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_get_next_variable, "EFIVAR"); | 
|  |  | 
|  | /* | 
|  | * efivar_set_variable_locked() - set a variable identified by name/vendor | 
|  | * | 
|  | * Must be called with efivars_lock held. If @nonblocking is set, it will use | 
|  | * non-blocking primitives so it is guaranteed not to sleep. | 
|  | */ | 
|  | efi_status_t efivar_set_variable_locked(efi_char16_t *name, efi_guid_t *vendor, | 
|  | u32 attr, unsigned long data_size, | 
|  | void *data, bool nonblocking) | 
|  | { | 
|  | efi_set_variable_t *setvar; | 
|  | efi_status_t status; | 
|  |  | 
|  | if (data_size > 0) { | 
|  | status = check_var_size(nonblocking, attr, | 
|  | data_size + ucs2_strsize(name, EFI_VAR_NAME_LEN)); | 
|  | if (status != EFI_SUCCESS) | 
|  | return status; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * If no _nonblocking variant exists, the ordinary one | 
|  | * is assumed to be non-blocking. | 
|  | */ | 
|  | setvar = __efivars->ops->set_variable_nonblocking; | 
|  | if (!setvar || !nonblocking) | 
|  | setvar = __efivars->ops->set_variable; | 
|  |  | 
|  | return setvar(name, vendor, attr, data_size, data); | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_set_variable_locked, "EFIVAR"); | 
|  |  | 
|  | /* | 
|  | * efivar_set_variable() - set a variable identified by name/vendor | 
|  | * | 
|  | * Can be called without holding the efivars_lock. Will sleep on obtaining the | 
|  | * lock, or on obtaining other locks that are needed in order to complete the | 
|  | * call. | 
|  | */ | 
|  | efi_status_t efivar_set_variable(efi_char16_t *name, efi_guid_t *vendor, | 
|  | u32 attr, unsigned long data_size, void *data) | 
|  | { | 
|  | efi_status_t status; | 
|  |  | 
|  | if (efivar_lock()) | 
|  | return EFI_ABORTED; | 
|  |  | 
|  | status = efivar_set_variable_locked(name, vendor, attr, data_size, | 
|  | data, false); | 
|  | efivar_unlock(); | 
|  | return status; | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_set_variable, "EFIVAR"); | 
|  |  | 
|  | efi_status_t efivar_query_variable_info(u32 attr, | 
|  | u64 *storage_space, | 
|  | u64 *remaining_space, | 
|  | u64 *max_variable_size) | 
|  | { | 
|  | if (!__efivars->ops->query_variable_info) | 
|  | return EFI_UNSUPPORTED; | 
|  | return __efivars->ops->query_variable_info(attr, storage_space, | 
|  | remaining_space, max_variable_size); | 
|  | } | 
|  | EXPORT_SYMBOL_NS_GPL(efivar_query_variable_info, "EFIVAR"); |