#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>
#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 {
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 {
u32 command_id;
u32 length;
u32 status;
u32 attributes;
u32 data_offset;
u32 data_size;
} __packed;
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 {
u32 command_id;
u32 length;
u32 status;
u32 _unknown1;
u32 _unknown2;
} __packed;
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 {
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 {
u32 command_id;
u32 length;
u32 attributes;
} __packed;
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;
#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)
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);
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;
*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;
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 (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;
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;
}
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,
};
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");