| // 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"); |