root/drivers/firmware/efi/vars.c
// 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");