root/drivers/firmware/qcom/qcom_qseecom_uefisecapp.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Client driver for Qualcomm UEFI Secure Application (qcom.tz.uefisecapp).
 * Provides access to UEFI variables on platforms where they are secured by the
 * aforementioned Secure Execution Environment (SEE) application.
 *
 * Copyright (C) 2023 Maximilian Luz <luzmaximilian@gmail.com>
 */

#include <linux/efi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/ucs2_string.h>

#include <linux/firmware/qcom/qcom_qseecom.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/firmware/qcom/qcom_tzmem.h>

/* -- Qualcomm "uefisecapp" interface definitions. -------------------------- */

/* Maximum length of name string with null-terminator */
#define QSEE_MAX_NAME_LEN                       1024

#define QSEE_CMD_UEFI(x)                        (0x8000 | (x))
#define QSEE_CMD_UEFI_GET_VARIABLE              QSEE_CMD_UEFI(0)
#define QSEE_CMD_UEFI_SET_VARIABLE              QSEE_CMD_UEFI(1)
#define QSEE_CMD_UEFI_GET_NEXT_VARIABLE         QSEE_CMD_UEFI(2)
#define QSEE_CMD_UEFI_QUERY_VARIABLE_INFO       QSEE_CMD_UEFI(3)

/**
 * struct qsee_req_uefi_get_variable - Request for GetVariable command.
 * @command_id:  The ID of the command. Must be %QSEE_CMD_UEFI_GET_VARIABLE.
 * @length:      Length of the request in bytes, including this struct and any
 *               parameters (name, GUID) stored after it as well as any padding
 *               thereof for alignment.
 * @name_offset: Offset from the start of this struct to where the variable
 *               name is stored (as utf-16 string), in bytes.
 * @name_size:   Size of the name parameter in bytes, including null-terminator.
 * @guid_offset: Offset from the start of this struct to where the GUID
 *               parameter is stored, in bytes.
 * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
 * @data_size:   Size of the output buffer, in bytes.
 */
struct qsee_req_uefi_get_variable {
        u32 command_id;
        u32 length;
        u32 name_offset;
        u32 name_size;
        u32 guid_offset;
        u32 guid_size;
        u32 data_size;
} __packed;

/**
 * struct qsee_rsp_uefi_get_variable - Response for GetVariable command.
 * @command_id:  The ID of the command. Should be %QSEE_CMD_UEFI_GET_VARIABLE.
 * @length:      Length of the response in bytes, including this struct and the
 *               returned data.
 * @status:      Status of this command.
 * @attributes:  EFI variable attributes.
 * @data_offset: Offset from the start of this struct to where the data is
 *               stored, in bytes.
 * @data_size:   Size of the returned data, in bytes. In case status indicates
 *               that the buffer is too small, this will be the size required
 *               to store the EFI variable data.
 */
struct qsee_rsp_uefi_get_variable {
        u32 command_id;
        u32 length;
        u32 status;
        u32 attributes;
        u32 data_offset;
        u32 data_size;
} __packed;

/**
 * struct qsee_req_uefi_set_variable - Request for the SetVariable command.
 * @command_id:  The ID of the command. Must be %QSEE_CMD_UEFI_SET_VARIABLE.
 * @length:      Length of the request in bytes, including this struct and any
 *               parameters (name, GUID, data) stored after it as well as any
 *               padding thereof required for alignment.
 * @name_offset: Offset from the start of this struct to where the variable
 *               name is stored (as utf-16 string), in bytes.
 * @name_size:   Size of the name parameter in bytes, including null-terminator.
 * @guid_offset: Offset from the start of this struct to where the GUID
 *               parameter is stored, in bytes.
 * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
 * @attributes:  The EFI variable attributes to set for this variable.
 * @data_offset: Offset from the start of this struct to where the EFI variable
 *               data is stored, in bytes.
 * @data_size:   Size of EFI variable data, in bytes.
 *
 */
struct qsee_req_uefi_set_variable {
        u32 command_id;
        u32 length;
        u32 name_offset;
        u32 name_size;
        u32 guid_offset;
        u32 guid_size;
        u32 attributes;
        u32 data_offset;
        u32 data_size;
} __packed;

/**
 * struct qsee_rsp_uefi_set_variable - Response for the SetVariable command.
 * @command_id:  The ID of the command. Should be %QSEE_CMD_UEFI_SET_VARIABLE.
 * @length:      The length of this response, i.e. the size of this struct in
 *               bytes.
 * @status:      Status of this command.
 * @_unknown1:   Unknown response field.
 * @_unknown2:   Unknown response field.
 */
struct qsee_rsp_uefi_set_variable {
        u32 command_id;
        u32 length;
        u32 status;
        u32 _unknown1;
        u32 _unknown2;
} __packed;

/**
 * struct qsee_req_uefi_get_next_variable - Request for the
 * GetNextVariableName command.
 * @command_id:  The ID of the command. Must be
 *               %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
 * @length:      Length of the request in bytes, including this struct and any
 *               parameters (name, GUID) stored after it as well as any padding
 *               thereof for alignment.
 * @guid_offset: Offset from the start of this struct to where the GUID
 *               parameter is stored, in bytes.
 * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
 * @name_offset: Offset from the start of this struct to where the variable
 *               name is stored (as utf-16 string), in bytes.
 * @name_size:   Size of the name parameter in bytes, including null-terminator.
 */
struct qsee_req_uefi_get_next_variable {
        u32 command_id;
        u32 length;
        u32 guid_offset;
        u32 guid_size;
        u32 name_offset;
        u32 name_size;
} __packed;

/**
 * struct qsee_rsp_uefi_get_next_variable - Response for the
 * GetNextVariableName command.
 * @command_id:  The ID of the command. Should be
 *               %QSEE_CMD_UEFI_GET_NEXT_VARIABLE.
 * @length:      Length of the response in bytes, including this struct and any
 *               parameters (name, GUID) stored after it as well as any padding
 *               thereof for alignment.
 * @status:      Status of this command.
 * @guid_offset: Offset from the start of this struct to where the GUID
 *               parameter is stored, in bytes.
 * @guid_size:   Size of the GUID parameter in bytes, i.e. sizeof(efi_guid_t).
 * @name_offset: Offset from the start of this struct to where the variable
 *               name is stored (as utf-16 string), in bytes.
 * @name_size:   Size of the name parameter in bytes, including null-terminator.
 */
struct qsee_rsp_uefi_get_next_variable {
        u32 command_id;
        u32 length;
        u32 status;
        u32 guid_offset;
        u32 guid_size;
        u32 name_offset;
        u32 name_size;
} __packed;

/**
 * struct qsee_req_uefi_query_variable_info - Response for the
 * GetNextVariableName command.
 * @command_id: The ID of the command. Must be
 *              %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
 * @length:     The length of this request, i.e. the size of this struct in
 *              bytes.
 * @attributes: The storage attributes to query the info for.
 */
struct qsee_req_uefi_query_variable_info {
        u32 command_id;
        u32 length;
        u32 attributes;
} __packed;

/**
 * struct qsee_rsp_uefi_query_variable_info - Response for the
 * GetNextVariableName command.
 * @command_id:        The ID of the command. Must be
 *                     %QSEE_CMD_UEFI_QUERY_VARIABLE_INFO.
 * @length:            The length of this response, i.e. the size of this
 *                     struct in bytes.
 * @status:            Status of this command.
 * @_pad:              Padding.
 * @storage_space:     Full storage space size, in bytes.
 * @remaining_space:   Free storage space available, in bytes.
 * @max_variable_size: Maximum variable data size, in bytes.
 */
struct qsee_rsp_uefi_query_variable_info {
        u32 command_id;
        u32 length;
        u32 status;
        u32 _pad;
        u64 storage_space;
        u64 remaining_space;
        u64 max_variable_size;
} __packed;

/* -- Alignment helpers ----------------------------------------------------- */

/*
 * Helper macro to ensure proper alignment of types (fields and arrays) when
 * stored in some (contiguous) buffer.
 *
 * Note: The driver from which this one has been reverse-engineered expects an
 * alignment of 8 bytes (64 bits) for GUIDs. Our definition of efi_guid_t,
 * however, has an alignment of 4 byte (32 bits). So far, this seems to work
 * fine here. See also the comment on the typedef of efi_guid_t.
 *
 * Note: It looks like uefisecapp is quite picky about how the memory passed to
 * it is structured and aligned. In particular the request/response setup used
 * for QSEE_CMD_UEFI_GET_VARIABLE. While qcom_qseecom_app_send(), in theory,
 * accepts separate buffers/addresses for the request and response parts, in
 * practice, however, it seems to expect them to be both part of a larger
 * contiguous block. We initially allocated separate buffers for the request
 * and response but this caused the QSEE_CMD_UEFI_GET_VARIABLE command to
 * either not write any response to the response buffer or outright crash the
 * device. Therefore, we now allocate a single contiguous block of DMA memory
 * for both and properly align the data using the macros below. In particular,
 * request and response structs are aligned at 8 byte (via __reqdata_offs()),
 * following the driver that this has been reverse-engineered from.
 */
#define qcuefi_buf_align_fields(fields...)                                      \
        ({                                                                      \
                size_t __len = 0;                                               \
                fields                                                          \
                __len;                                                          \
        })

#define __field_impl(size, align, offset)                                       \
        ({                                                                      \
                size_t *__offset = (offset);                                    \
                size_t __aligned;                                               \
                                                                                \
                __aligned = ALIGN(__len, align);                                \
                __len = __aligned + (size);                                     \
                                                                                \
                if (__offset)                                                   \
                        *__offset = __aligned;                                  \
        });

#define __array_offs(type, count, offset)                                       \
        __field_impl(sizeof(type) * (count), __alignof__(type), offset)

#define __array_offs_aligned(type, count, align, offset)                        \
        __field_impl(sizeof(type) * (count), align, offset)

#define __reqdata_offs(size, offset)                                            \
        __array_offs_aligned(u8, size, 8, offset)

#define __array(type, count)            __array_offs(type, count, NULL)
#define __field_offs(type, offset)      __array_offs(type, 1, offset)
#define __field(type)                   __array_offs(type, 1, NULL)

/* -- UEFI app interface. --------------------------------------------------- */

struct qcuefi_client {
        struct qseecom_client *client;
        struct efivars efivars;
        struct qcom_tzmem_pool *mempool;
};

static struct device *qcuefi_dev(struct qcuefi_client *qcuefi)
{
        return &qcuefi->client->aux_dev.dev;
}

static efi_status_t qsee_uefi_status_to_efi(u32 status)
{
        u64 category = status & 0xf0000000;
        u64 code = status & 0x0fffffff;

        return category << (BITS_PER_LONG - 32) | code;
}

static efi_status_t qsee_uefi_get_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
                                           const efi_guid_t *guid, u32 *attributes,
                                           unsigned long *data_size, void *data)
{
        struct qsee_req_uefi_get_variable *req_data;
        struct qsee_rsp_uefi_get_variable *rsp_data;
        void *cmd_buf __free(qcom_tzmem) = NULL;
        unsigned long buffer_size = *data_size;
        unsigned long name_length;
        efi_status_t efi_status;
        size_t cmd_buf_size;
        size_t guid_offs;
        size_t name_offs;
        size_t req_size;
        size_t rsp_size;
        size_t req_offs;
        size_t rsp_offs;
        ssize_t status;

        if (!name || !guid)
                return EFI_INVALID_PARAMETER;

        name_length = ucs2_strnlen(name, QSEE_MAX_NAME_LEN) + 1;
        if (name_length > QSEE_MAX_NAME_LEN)
                return EFI_INVALID_PARAMETER;

        if (buffer_size && !data)
                return EFI_INVALID_PARAMETER;

        req_size = qcuefi_buf_align_fields(
                __field(*req_data)
                __array_offs(*name, name_length, &name_offs)
                __field_offs(*guid, &guid_offs)
        );

        rsp_size = qcuefi_buf_align_fields(
                __field(*rsp_data)
                __array(u8, buffer_size)
        );

        cmd_buf_size = qcuefi_buf_align_fields(
                __reqdata_offs(req_size, &req_offs)
                __reqdata_offs(rsp_size, &rsp_offs)
        );

        cmd_buf = qcom_tzmem_alloc(qcuefi->mempool, cmd_buf_size, GFP_KERNEL);
        if (!cmd_buf)
                return EFI_OUT_OF_RESOURCES;

        req_data = cmd_buf + req_offs;
        rsp_data = cmd_buf + rsp_offs;

        req_data->command_id = QSEE_CMD_UEFI_GET_VARIABLE;
        req_data->data_size = buffer_size;
        req_data->name_offset = name_offs;
        req_data->name_size = name_length * sizeof(*name);
        req_data->guid_offset = guid_offs;
        req_data->guid_size = sizeof(*guid);
        req_data->length = req_size;

        status = ucs2_strscpy(((void *)req_data) + req_data->name_offset, name, name_length);
        if (status < 0)
                return EFI_INVALID_PARAMETER;

        memcpy(((void *)req_data) + req_data->guid_offset, guid, req_data->guid_size);

        status = qcom_qseecom_app_send(qcuefi->client,
                                       cmd_buf + req_offs, req_size,
                                       cmd_buf + rsp_offs, rsp_size);
        if (status)
                return EFI_DEVICE_ERROR;

        if (rsp_data->command_id != QSEE_CMD_UEFI_GET_VARIABLE)
                return EFI_DEVICE_ERROR;

        if (rsp_data->length < sizeof(*rsp_data))
                return EFI_DEVICE_ERROR;

        if (rsp_data->status) {
                dev_dbg(qcuefi_dev(qcuefi), "%s: uefisecapp error: 0x%x\n",
                        __func__, rsp_data->status);
                efi_status = qsee_uefi_status_to_efi(rsp_data->status);

                /* Update size and attributes in case buffer is too small. */
                if (efi_status == EFI_BUFFER_TOO_SMALL) {
                        *data_size = rsp_data->data_size;
                        if (attributes)
                                *attributes = rsp_data->attributes;
                }

                return qsee_uefi_status_to_efi(rsp_data->status);
        }

        if (rsp_data->length > rsp_size)
                return EFI_DEVICE_ERROR;

        if (rsp_data->data_offset + rsp_data->data_size > rsp_data->length)
                return EFI_DEVICE_ERROR;

        /*
         * Note: We need to set attributes and data size even if the buffer is
         * too small and we won't copy any data. This is described in spec, so
         * that callers can either allocate a buffer properly (with two calls
         * to this function) or just read back attributes withouth having to
         * deal with that.
         *
         * Specifically:
         * - If we have a buffer size of zero and no buffer, just return the
         *   attributes, required size, and indicate success.
         * - If the buffer size is nonzero but too small, indicate that as an
         *   error.
         * - Otherwise, we are good to copy the data.
         *
         * Note that we have already ensured above that the buffer pointer is
         * non-NULL if its size is nonzero.
         */
        *data_size = rsp_data->data_size;
        if (attributes)
                *attributes = rsp_data->attributes;

        if (buffer_size == 0 && !data)
                return EFI_SUCCESS;

        if (buffer_size < rsp_data->data_size)
                return EFI_BUFFER_TOO_SMALL;

        memcpy(data, ((void *)rsp_data) + rsp_data->data_offset, rsp_data->data_size);

        return EFI_SUCCESS;
}

static efi_status_t qsee_uefi_set_variable(struct qcuefi_client *qcuefi, const efi_char16_t *name,
                                           const efi_guid_t *guid, u32 attributes,
                                           unsigned long data_size, const void *data)
{
        struct qsee_req_uefi_set_variable *req_data;
        struct qsee_rsp_uefi_set_variable *rsp_data;
        void *cmd_buf __free(qcom_tzmem) = NULL;
        unsigned long name_length;
        size_t cmd_buf_size;
        size_t name_offs;
        size_t guid_offs;
        size_t data_offs;
        size_t req_size;
        size_t req_offs;
        size_t rsp_offs;
        ssize_t status;

        if (!name || !guid)
                return EFI_INVALID_PARAMETER;

        name_length = ucs2_strnlen(name, QSEE_MAX_NAME_LEN) + 1;
        if (name_length > QSEE_MAX_NAME_LEN)
                return EFI_INVALID_PARAMETER;

        /*
         * Make sure we have some data if data_size is nonzero. Note that using
         * a size of zero is a valid use-case described in spec and deletes the
         * variable.
         */
        if (data_size && !data)
                return EFI_INVALID_PARAMETER;

        req_size = qcuefi_buf_align_fields(
                __field(*req_data)
                __array_offs(*name, name_length, &name_offs)
                __field_offs(*guid, &guid_offs)
                __array_offs(u8, data_size, &data_offs)
        );

        cmd_buf_size = qcuefi_buf_align_fields(
                __reqdata_offs(req_size, &req_offs)
                __reqdata_offs(sizeof(*rsp_data), &rsp_offs)
        );

        cmd_buf = qcom_tzmem_alloc(qcuefi->mempool, cmd_buf_size, GFP_KERNEL);
        if (!cmd_buf)
                return EFI_OUT_OF_RESOURCES;

        req_data = cmd_buf + req_offs;
        rsp_data = cmd_buf + rsp_offs;

        req_data->command_id = QSEE_CMD_UEFI_SET_VARIABLE;
        req_data->attributes = attributes;
        req_data->name_offset = name_offs;
        req_data->name_size = name_length * sizeof(*name);
        req_data->guid_offset = guid_offs;
        req_data->guid_size = sizeof(*guid);
        req_data->data_offset = data_offs;
        req_data->data_size = data_size;
        req_data->length = req_size;

        status = ucs2_strscpy(((void *)req_data) + req_data->name_offset, name, name_length);
        if (status < 0)
                return EFI_INVALID_PARAMETER;

        memcpy(((void *)req_data) + req_data->guid_offset, guid, req_data->guid_size);

        if (data_size)
                memcpy(((void *)req_data) + req_data->data_offset, data, req_data->data_size);

        status = qcom_qseecom_app_send(qcuefi->client,
                                       cmd_buf + req_offs, req_size,
                                       cmd_buf + rsp_offs, sizeof(*rsp_data));
        if (status)
                return EFI_DEVICE_ERROR;

        if (rsp_data->command_id != QSEE_CMD_UEFI_SET_VARIABLE)
                return EFI_DEVICE_ERROR;

        if (rsp_data->length != sizeof(*rsp_data))
                return EFI_DEVICE_ERROR;

        if (rsp_data->status) {
                dev_dbg(qcuefi_dev(qcuefi), "%s: uefisecapp error: 0x%x\n",
                        __func__, rsp_data->status);
                return qsee_uefi_status_to_efi(rsp_data->status);
        }

        return EFI_SUCCESS;
}

static efi_status_t qsee_uefi_get_next_variable(struct qcuefi_client *qcuefi,
                                                unsigned long *name_size, efi_char16_t *name,
                                                efi_guid_t *guid)
{
        struct qsee_req_uefi_get_next_variable *req_data;
        struct qsee_rsp_uefi_get_next_variable *rsp_data;
        void *cmd_buf __free(qcom_tzmem) = NULL;
        efi_status_t efi_status;
        size_t cmd_buf_size;
        size_t guid_offs;
        size_t name_offs;
        size_t req_size;
        size_t rsp_size;
        size_t req_offs;
        size_t rsp_offs;
        ssize_t status;

        if (!name_size || !name || !guid)
                return EFI_INVALID_PARAMETER;

        if (*name_size == 0)
                return EFI_INVALID_PARAMETER;

        req_size = qcuefi_buf_align_fields(
                __field(*req_data)
                __field_offs(*guid, &guid_offs)
                __array_offs(*name, *name_size / sizeof(*name), &name_offs)
        );

        rsp_size = qcuefi_buf_align_fields(
                __field(*rsp_data)
                __field(*guid)
                __array(*name, *name_size / sizeof(*name))
        );

        cmd_buf_size = qcuefi_buf_align_fields(
                __reqdata_offs(req_size, &req_offs)
                __reqdata_offs(rsp_size, &rsp_offs)
        );

        cmd_buf = qcom_tzmem_alloc(qcuefi->mempool, cmd_buf_size, GFP_KERNEL);
        if (!cmd_buf)
                return EFI_OUT_OF_RESOURCES;

        req_data = cmd_buf + req_offs;
        rsp_data = cmd_buf + rsp_offs;

        req_data->command_id = QSEE_CMD_UEFI_GET_NEXT_VARIABLE;
        req_data->guid_offset = guid_offs;
        req_data->guid_size = sizeof(*guid);
        req_data->name_offset = name_offs;
        req_data->name_size = *name_size;
        req_data->length = req_size;

        memcpy(((void *)req_data) + req_data->guid_offset, guid, req_data->guid_size);
        status = ucs2_strscpy(((void *)req_data) + req_data->name_offset, name,
                              *name_size / sizeof(*name));
        if (status < 0)
                return EFI_INVALID_PARAMETER;

        status = qcom_qseecom_app_send(qcuefi->client,
                                       cmd_buf + req_offs, req_size,
                                       cmd_buf + rsp_offs, rsp_size);
        if (status)
                return EFI_DEVICE_ERROR;

        if (rsp_data->command_id != QSEE_CMD_UEFI_GET_NEXT_VARIABLE)
                return EFI_DEVICE_ERROR;

        if (rsp_data->length < sizeof(*rsp_data))
                return EFI_DEVICE_ERROR;

        if (rsp_data->status) {
                dev_dbg(qcuefi_dev(qcuefi), "%s: uefisecapp error: 0x%x\n",
                        __func__, rsp_data->status);
                efi_status = qsee_uefi_status_to_efi(rsp_data->status);

                /*
                 * If the buffer to hold the name is too small, update the
                 * name_size with the required size, so that callers can
                 * reallocate it accordingly.
                 */
                if (efi_status == EFI_BUFFER_TOO_SMALL)
                        *name_size = rsp_data->name_size;

                return efi_status;
        }

        if (rsp_data->length > rsp_size)
                return EFI_DEVICE_ERROR;

        if (rsp_data->name_offset + rsp_data->name_size > rsp_data->length)
                return EFI_DEVICE_ERROR;

        if (rsp_data->guid_offset + rsp_data->guid_size > rsp_data->length)
                return EFI_DEVICE_ERROR;

        if (rsp_data->name_size > *name_size) {
                *name_size = rsp_data->name_size;
                return EFI_BUFFER_TOO_SMALL;
        }

        if (rsp_data->guid_size != sizeof(*guid))
                return EFI_DEVICE_ERROR;

        memcpy(guid, ((void *)rsp_data) + rsp_data->guid_offset, rsp_data->guid_size);
        status = ucs2_strscpy(name, ((void *)rsp_data) + rsp_data->name_offset,
                              rsp_data->name_size / sizeof(*name));
        *name_size = rsp_data->name_size;

        if (status < 0)
                /*
                 * Return EFI_DEVICE_ERROR here because the buffer size should
                 * have already been validated above, causing this function to
                 * bail with EFI_BUFFER_TOO_SMALL.
                 */
                return EFI_DEVICE_ERROR;

        return EFI_SUCCESS;
}

static efi_status_t qsee_uefi_query_variable_info(struct qcuefi_client *qcuefi, u32 attr,
                                                  u64 *storage_space, u64 *remaining_space,
                                                  u64 *max_variable_size)
{
        struct qsee_req_uefi_query_variable_info *req_data;
        struct qsee_rsp_uefi_query_variable_info *rsp_data;
        void *cmd_buf __free(qcom_tzmem) = NULL;
        size_t cmd_buf_size;
        size_t req_offs;
        size_t rsp_offs;
        int status;

        cmd_buf_size = qcuefi_buf_align_fields(
                __reqdata_offs(sizeof(*req_data), &req_offs)
                __reqdata_offs(sizeof(*rsp_data), &rsp_offs)
        );

        cmd_buf = qcom_tzmem_alloc(qcuefi->mempool, cmd_buf_size, GFP_KERNEL);
        if (!cmd_buf)
                return EFI_OUT_OF_RESOURCES;

        req_data = cmd_buf + req_offs;
        rsp_data = cmd_buf + rsp_offs;

        req_data->command_id = QSEE_CMD_UEFI_QUERY_VARIABLE_INFO;
        req_data->attributes = attr;
        req_data->length = sizeof(*req_data);

        status = qcom_qseecom_app_send(qcuefi->client,
                                       cmd_buf + req_offs, sizeof(*req_data),
                                       cmd_buf + rsp_offs, sizeof(*rsp_data));
        if (status)
                return EFI_DEVICE_ERROR;

        if (rsp_data->command_id != QSEE_CMD_UEFI_QUERY_VARIABLE_INFO)
                return EFI_DEVICE_ERROR;

        if (rsp_data->length != sizeof(*rsp_data))
                return EFI_DEVICE_ERROR;

        if (rsp_data->status) {
                dev_dbg(qcuefi_dev(qcuefi), "%s: uefisecapp error: 0x%x\n",
                        __func__, rsp_data->status);
                return qsee_uefi_status_to_efi(rsp_data->status);
        }

        if (storage_space)
                *storage_space = rsp_data->storage_space;

        if (remaining_space)
                *remaining_space = rsp_data->remaining_space;

        if (max_variable_size)
                *max_variable_size = rsp_data->max_variable_size;

        return EFI_SUCCESS;
}

/* -- Global efivar interface. ---------------------------------------------- */

static struct qcuefi_client *__qcuefi;
static DEFINE_MUTEX(__qcuefi_lock);

static int qcuefi_set_reference(struct qcuefi_client *qcuefi)
{
        guard(mutex)(&__qcuefi_lock);

        if (qcuefi && __qcuefi)
                return -EEXIST;

        __qcuefi = qcuefi;

        return 0;
}

static struct qcuefi_client *qcuefi_acquire(void)
        __acquires(__qcuefi_lock)
{
        mutex_lock(&__qcuefi_lock);
        if (!__qcuefi) {
                mutex_unlock(&__qcuefi_lock);
                return NULL;
        }
        return __qcuefi;
}

static void qcuefi_release(void)
        __releases(__qcuefi_lock)
{
        mutex_unlock(&__qcuefi_lock);
}

static efi_status_t qcuefi_get_variable(efi_char16_t *name, efi_guid_t *vendor, u32 *attr,
                                        unsigned long *data_size, void *data)
{
        struct qcuefi_client *qcuefi;
        efi_status_t status;

        qcuefi = qcuefi_acquire();
        if (!qcuefi)
                return EFI_NOT_READY;

        status = qsee_uefi_get_variable(qcuefi, name, vendor, attr, data_size, data);

        qcuefi_release();
        return status;
}

static efi_status_t qcuefi_set_variable(efi_char16_t *name, efi_guid_t *vendor,
                                        u32 attr, unsigned long data_size, void *data)
{
        struct qcuefi_client *qcuefi;
        efi_status_t status;

        qcuefi = qcuefi_acquire();
        if (!qcuefi)
                return EFI_NOT_READY;

        status = qsee_uefi_set_variable(qcuefi, name, vendor, attr, data_size, data);

        qcuefi_release();
        return status;
}

static efi_status_t qcuefi_get_next_variable(unsigned long *name_size, efi_char16_t *name,
                                             efi_guid_t *vendor)
{
        struct qcuefi_client *qcuefi;
        efi_status_t status;

        qcuefi = qcuefi_acquire();
        if (!qcuefi)
                return EFI_NOT_READY;

        status = qsee_uefi_get_next_variable(qcuefi, name_size, name, vendor);

        qcuefi_release();
        return status;
}

static efi_status_t qcuefi_query_variable_info(u32 attr, u64 *storage_space, u64 *remaining_space,
                                               u64 *max_variable_size)
{
        struct qcuefi_client *qcuefi;
        efi_status_t status;

        qcuefi = qcuefi_acquire();
        if (!qcuefi)
                return EFI_NOT_READY;

        status = qsee_uefi_query_variable_info(qcuefi, attr, storage_space, remaining_space,
                                               max_variable_size);

        qcuefi_release();
        return status;
}

static const struct efivar_operations qcom_efivar_ops = {
        .get_variable = qcuefi_get_variable,
        .set_variable = qcuefi_set_variable,
        .get_next_variable = qcuefi_get_next_variable,
        .query_variable_info = qcuefi_query_variable_info,
};

/* -- Driver setup. --------------------------------------------------------- */

static int qcom_uefisecapp_probe(struct auxiliary_device *aux_dev,
                                 const struct auxiliary_device_id *aux_dev_id)
{
        struct qcom_tzmem_pool_config pool_config;
        struct qcuefi_client *qcuefi;
        int status;

        qcuefi = devm_kzalloc(&aux_dev->dev, sizeof(*qcuefi), GFP_KERNEL);
        if (!qcuefi)
                return -ENOMEM;

        qcuefi->client = container_of(aux_dev, struct qseecom_client, aux_dev);

        memset(&pool_config, 0, sizeof(pool_config));
        pool_config.initial_size = SZ_4K;
        pool_config.policy = QCOM_TZMEM_POLICY_MULTIPLIER;
        pool_config.increment = 2;
        pool_config.max_size = SZ_256K;

        qcuefi->mempool = devm_qcom_tzmem_pool_new(&aux_dev->dev, &pool_config);
        if (IS_ERR(qcuefi->mempool))
                return PTR_ERR(qcuefi->mempool);

        auxiliary_set_drvdata(aux_dev, qcuefi);
        status = qcuefi_set_reference(qcuefi);
        if (status)
                return status;

        status = efivars_register(&qcuefi->efivars, &qcom_efivar_ops);
        if (status)
                qcuefi_set_reference(NULL);

        return status;
}

static void qcom_uefisecapp_remove(struct auxiliary_device *aux_dev)
{
        struct qcuefi_client *qcuefi = auxiliary_get_drvdata(aux_dev);

        efivars_unregister(&qcuefi->efivars);
        qcuefi_set_reference(NULL);
}

static const struct auxiliary_device_id qcom_uefisecapp_id_table[] = {
        { .name = "qcom_qseecom.uefisecapp" },
        {}
};
MODULE_DEVICE_TABLE(auxiliary, qcom_uefisecapp_id_table);

static struct auxiliary_driver qcom_uefisecapp_driver = {
        .probe = qcom_uefisecapp_probe,
        .remove = qcom_uefisecapp_remove,
        .id_table = qcom_uefisecapp_id_table,
        .driver = {
                .name = "qcom_qseecom_uefisecapp",
                .probe_type = PROBE_PREFER_ASYNCHRONOUS,
        },
};
module_auxiliary_driver(qcom_uefisecapp_driver);

MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("Client driver for Qualcomm SEE UEFI Secure App");
MODULE_LICENSE("GPL");