On Qualcomm SoC based platforms, UEFI stores EFI variables within the Replay Protected Memory Block (RPMB) located within either the UFS, eMMC or SPI-NOR storage. The RPMB key which is one-time programmed into the storage controller to allow authentication of the RPMB frames is generated by and only available to the Qualcomm Trusted Execution Environment (QTEE).
The legacy QSEECOM protocol used for communicating with the QTEE is deprecated and replaced with the use-case agnostic SMCInvoke protocol starting with the Qualcomm SM8x50 series. On platforms where the QSEECOM still driver probes, it does not support a listener interface with QTEE to enable writing of non-volatile EFI variables to the RPMB for UFS and eMMC storage. Therefore on such platforms, a TEE client driver which communicates with QTEE via the SMCInvoke protocol implemented by the QCOMTEE driver (and registered with the TEE subsystem) must be used to update such EFI variables.
Add support for a TEE based uefisecapp client driver which installs efivar operations after obtaining an object reference to the uefisecapp service. This enables the kernel/user-space to access or modify both volatile EFI variables stored by the Secure Application (in-memory) and non-volatile ones stored within RPMB.
Signed-off-by: Harshal Dev harshal.dev@oss.qualcomm.com --- MAINTAINERS | 6 + arch/arm64/configs/defconfig | 1 + drivers/firmware/qcom/Kconfig | 31 ++ drivers/firmware/qcom/Makefile | 1 + drivers/firmware/qcom/qcom_tee_uefisecapp.c | 636 ++++++++++++++++++++++++++++ 5 files changed, 675 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index 30c1cdd0fb38..7ccedad8d388 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22981,6 +22981,12 @@ L: linux-arm-msm@vger.kernel.org S: Maintained F: drivers/firmware/qcom/qcom_qseecom_uefisecapp.c
+QUALCOMM TEE UEFISECAPP DRIVER +M: Harshal Dev harshal.dev@oss.qualcomm.com +L: linux-arm-msm@vger.kernel.org +S: Maintained +F: drivers/firmware/qcom/qcom_tee_uefisecapp.c + QUALCOMM PINCTRL DRIVERS M: Bartosz Golaszewski brgl@kernel.org L: linux-arm-msm@vger.kernel.org diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig index fce418fe6ff6..a8525878c679 100644 --- a/arch/arm64/configs/defconfig +++ b/arch/arm64/configs/defconfig @@ -277,6 +277,7 @@ CONFIG_IMX_SCU=y CONFIG_QCOM_TZMEM_MODE_SHMBRIDGE=y CONFIG_QCOM_QSEECOM=y CONFIG_QCOM_QSEECOM_UEFISECAPP=y +CONFIG_QCOM_TEE_UEFISECAPP=m CONFIG_EXYNOS_ACPM_PROTOCOL=m CONFIG_TEGRA_BPMP=y CONFIG_ZYNQMP_FIRMWARE_DEBUG=y diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig index 3ad22f8fc3e5..694d98fef4f4 100644 --- a/drivers/firmware/qcom/Kconfig +++ b/drivers/firmware/qcom/Kconfig @@ -79,4 +79,35 @@ config QCOM_QSEECOM_UEFISECAPP Select Y here to provide access to EFI variables on the aforementioned platforms.
+config QCOM_TEE_UEFISECAPP + tristate "Qualcomm TEE UEFI Secure App client driver" + depends on QCOMTEE + depends on EFI + help + The QSEECOM protocol used for communicating with the Qualcomm Trusted + Execution Environment (QTEE) is deprecated and replaced with the SMCInvoke + protocol starting with the Qualcomm SM8x50 series. On platforms where the + QSEECOM protocol still works (the QSEECOM driver probes) the driver does + not support a listener interface with QTEE to enable writing of + non-volatile EFI variables (via listener requests to Linux) in the Replay + Protected Memory Block (RPMB) located on UFS/eMMC storage. + Therefore on such platforms, the TEE based uefisecapp client driver + (which communicates with QTEE via the SMCInvoke protocol) must be used + to update such EFI variables through the RPMB service hosted in the QTEE + supplicant user-space daemon (github.com/qualcomm/minkipc) which forwards + RPMB packets to the RPMB device. + NOTE: Qualcomm Compute platforms with SPI-NOR storage are an exception to + this scenario. Since they do not have a firmware running on their SPI-NOR + storage controller which must be programmed with a RPMB key, QTEE has a + SPI-NOR driver which holds the key, and so the QSEECOM driver can be used + for updating EFI variables on these platforms because QTEE never makes a + listener request to Linux (QTEE doesn't need the Linux SPI-NOR driver). + + This module provides a TEE client driver for uefisecapp, installing efivar + operations to allow the kernel and user-space access to EFI variables. + + Select m here to provide access to EFI variables on the aforementioned + platforms if your Linux distribution has QTEE supplicant installed and + running. + endmenu diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile index 88ce74d74c3e..237192b74de3 100644 --- a/drivers/firmware/qcom/Makefile +++ b/drivers/firmware/qcom/Makefile @@ -9,5 +9,6 @@ CFLAGS_qcom_scm-smc.o := -I$(src) obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o +obj-$(CONFIG_QCOM_TEE_UEFISECAPP) += qcom_tee_uefisecapp.o obj-$(CONFIG_QCOM_PAS) += qcom_pas.o obj-$(CONFIG_QCOM_PAS_TEE) += qcom_pas_tee.o diff --git a/drivers/firmware/qcom/qcom_tee_uefisecapp.c b/drivers/firmware/qcom/qcom_tee_uefisecapp.c new file mode 100644 index 000000000000..71438961cd49 --- /dev/null +++ b/drivers/firmware/qcom/qcom_tee_uefisecapp.c @@ -0,0 +1,636 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/efi.h> +#include <linux/tee.h> +#include <linux/tee_drv.h> +#include <linux/ucs2_string.h> + +#define QCOMTEE_OP_CLIENT_ENV_OPEN 0 +#define QCOMTEE_ROOT_OP_REG_WITH_CREDENTIALS 5 + +/* Each service exposed by QTEE is identified by a 32-bit UID */ +#define QCOMTEE_UEFI_SEC_UID 413 + +/* Operations supported by the UEFI Sec App service */ +#define QCOMTEE_UEFI_SEC_OP_GET_VAR 0 +#define QCOMTEE_UEFI_SEC_OP_SET_VAR 1 +#define QCOMTEE_UEFI_SEC_OP_QUERY_VAR_INFO 2 +#define QCOMTEE_UEFI_SEC_OP_GET_NEXT_VAR_NAME 3 + +/* Error codes returned by the UEFI Sec App service */ +#define QCOMTEE_UEFI_SEC_SUCCESS 0 +#define QCOMTEE_UEFI_SEC_ERROR_INVALID_PARAMETER 10 +#define QCOMTEE_UEFI_SEC_ERROR_UNSUPPORTED 11 +#define QCOMTEE_UEFI_SEC_ERROR_WRITE_PROTECTED 12 +#define QCOMTEE_UEFI_SEC_ERROR_SECURITY_VIOLATION 13 +#define QCOMTEE_UEFI_SEC_ERROR_DEVICE_ERROR 14 +#define QCOMTEE_UEFI_SEC_ERROR_OUT_OF_RESOURCES 15 +#define QCOMTEE_UEFI_SEC_ERROR_VOLUME_CORRUPTED 16 +#define QCOMTEE_UEFI_SEC_ERROR_SIZE_OUT 17 +#define QCOMTEE_UEFI_SEC_ERROR_NOT_FOUND 18 +#define QCOMTEE_UEFI_SEC_ERROR_ALREADY_STARTED 19 + +/* Operations for objects are 32-bit. QCOMTEE transport uses the upper 16 bits. */ +#define QCOMTEE_MSG_OBJECT_OP_MASK GENMASK(15, 0) +#define QCOMTEE_MSG_OBJECT_OP_RELEASE (QCOMTEE_MSG_OBJECT_OP_MASK - 0) + +/** + * struct qcomtee_uefisec_app - An instance of UEFI Secure Application. + * @dev: TEE client device on the TEE bus which represents uefisecapp. + * @ctx: The context opened with the TEE subsystem by the uefisecapp client. + * @uefisec_svc_obj: A TEE object representing the uefisecapp service. + * @efivars: EFI variables registered with the EFI subsystem. + */ +struct qcomtee_uefisec_app { + struct device *dev; + struct tee_context *ctx; + struct tee_param_objref uefisec_svc_obj; + struct efivars efivars; +}; + +#define UEFISECAPP_UUID \ + UUID_INIT(0x01f95dcd, 0x2d7e, 0x58be, \ + 0xa1, 0x43, 0x81, 0x32, 0xa1, 0x72, 0xdb, 0x7d) + +/* Short-hands for these long attribute names */ +#define UBUF_INPUT TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_INPUT +#define UBUF_OUTPUT TEE_IOCTL_PARAM_ATTR_TYPE_UBUF_OUTPUT +#define OBJREF_INPUT TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_INPUT +#define OBJREF_OUTPUT TEE_IOCTL_PARAM_ATTR_TYPE_OBJREF_OUTPUT + +/* Init instance of 'struct tee_param_objref'. */ +#define SET_TEE_PARAM_OBJREF(param, attri, obj_id, obj_flag) do { \ + (param).attr = (attri); \ + (param).u.objref.id = (obj_id); \ + (param).u.objref.flags = (obj_flag); \ + } while (0) + +/* Init instance of 'struct tee_param_ubuf'. */ +#define SET_TEE_PARAM_UBUF(param, attri, ubuff) do { \ + (param).attr = (attri); \ + (param).u.ubuf = (ubuff); \ + } while (0) + +#define TEE_PARAM_UBUF(x) ((struct tee_param_ubuf){ .addr = &(x), sizeof(x) }) + +#define SET_INVOKE_ARG(arg, object_id, opp, nparam) do { \ + (arg).id = (object_id); \ + (arg).op = (opp); \ + (arg).num_params = (nparam); \ + } while (0) + +static inline efi_status_t uefisecapp_err_to_efi_status(u32 err) +{ + switch (err) { + case QCOMTEE_UEFI_SEC_SUCCESS: + return EFI_SUCCESS; + + case QCOMTEE_UEFI_SEC_ERROR_INVALID_PARAMETER: + return EFI_INVALID_PARAMETER; + + case QCOMTEE_UEFI_SEC_ERROR_UNSUPPORTED: + return EFI_UNSUPPORTED; + + case QCOMTEE_UEFI_SEC_ERROR_WRITE_PROTECTED: + return EFI_WRITE_PROTECTED; + + case QCOMTEE_UEFI_SEC_ERROR_SECURITY_VIOLATION: + return EFI_SECURITY_VIOLATION; + + case QCOMTEE_UEFI_SEC_ERROR_DEVICE_ERROR: + return EFI_DEVICE_ERROR; + + case QCOMTEE_UEFI_SEC_ERROR_OUT_OF_RESOURCES: + return EFI_OUT_OF_RESOURCES; + + case QCOMTEE_UEFI_SEC_ERROR_SIZE_OUT: + return EFI_BUFFER_TOO_SMALL; + + case QCOMTEE_UEFI_SEC_ERROR_NOT_FOUND: + return EFI_NOT_FOUND; + + /* No matching on EFI_* list. */ + case QCOMTEE_UEFI_SEC_ERROR_ALREADY_STARTED: /* EFI_ALREADY_STARTED. */ + case QCOMTEE_UEFI_SEC_ERROR_VOLUME_CORRUPTED: /* EFI_VOLUME_CORRUPTED. */ + default: + return EFI_DEVICE_ERROR; + } +} + +static struct qcomtee_uefisec_app uefisec_app; + +static int qcuefi_get_variable(struct tee_param_ubuf in_variable, efi_guid_t *guid, + struct tee_param_ubuf in_attributes, + struct tee_param_ubuf *data, + u32 *out_attributes, u32 *out_errno) +{ + int ret; + struct tee_ioctl_object_invoke_arg inv_arg; + u64 obj_id = uefisec_app.uefisec_svc_obj.id; + u32 nparams = 5; + struct tee_param param[nparams]; + + struct { + efi_guid_t guid; + u32 in_data_size; + } in_cong = { 0 }; + + struct { + u32 out_data_size; + u32 attributes; + u32 errno; + } out_cong = { 0 }; + + in_cong.guid = *guid; + in_cong.in_data_size = data->size; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + SET_INVOKE_ARG(inv_arg, obj_id, QCOMTEE_UEFI_SEC_OP_GET_VAR, nparams); + SET_TEE_PARAM_UBUF(param[0], UBUF_INPUT, TEE_PARAM_UBUF(in_cong)); + SET_TEE_PARAM_UBUF(param[1], UBUF_INPUT, in_variable); + SET_TEE_PARAM_UBUF(param[2], UBUF_INPUT, in_attributes); + SET_TEE_PARAM_UBUF(param[3], UBUF_OUTPUT, TEE_PARAM_UBUF(out_cong)); + SET_TEE_PARAM_UBUF(param[4], UBUF_OUTPUT, *data); + + ret = tee_client_object_invoke_func(uefisec_app.ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(uefisec_app.dev, "QCOMTEE_UEFI_SEC_OP_GET_VAR invoke ret: %d, err: 0x%x\n", + ret, inv_arg.ret); + return ret ?: inv_arg.ret; + } + + data->size = out_cong.out_data_size; + *out_attributes = out_cong.attributes; + *out_errno = out_cong.errno; + + return ret; +} + +static efi_status_t qcomtee_uefi_get_variable(efi_char16_t *name, efi_guid_t *guid, + u32 *attr, unsigned long *data_size, + void *data) +{ + int ret; + u32 in_attr, out_attributes, out_errno; + struct tee_param_ubuf in_data, in_var, in_attributes; + + if (!name || !guid) + return EFI_INVALID_PARAMETER; + + /* 'attr' can be NULL, however an input attribute is always expected + * by UefiSecApp TA + */ + in_attr = 0; + if (attr) + in_attr = *attr; + + in_data = (struct tee_param_ubuf){ .addr = data, *data_size }; + in_var = (struct tee_param_ubuf){ .addr = name, + (ucs2_strlen(name) + 1) * sizeof(*name) }; + in_attributes = (struct tee_param_ubuf){ .addr = &in_attr, sizeof(u32) }; + + /* On SUCCESS, 'data' member of 'in_data' has already been updated. */ + ret = qcuefi_get_variable(in_var, guid, in_attributes, &in_data, + &out_attributes, &out_errno); + + if (ret) + return EFI_DEVICE_ERROR; + + if (!out_errno || out_errno == QCOMTEE_UEFI_SEC_ERROR_SIZE_OUT) { + /* If 'attr' is NULL 'out_attributes' is not updated. */ + if (attr) + *attr = out_attributes; + + *data_size = in_data.size; + } + + return uefisecapp_err_to_efi_status(out_errno); +} + +static int qcuefi_set_variable(struct tee_param_ubuf in_variable, efi_guid_t *guid, + u32 attributes, struct tee_param_ubuf data, + u32 *out_errno) +{ + int ret; + struct tee_ioctl_object_invoke_arg inv_arg; + u64 obj_id = uefisec_app.uefisec_svc_obj.id; + u32 nparams = 4; + struct tee_param param[nparams]; + + struct { + efi_guid_t guid; + u32 attributes; + u32 in_data_size; + } in_cong = { 0 }; + + in_cong.guid = *guid; + in_cong.attributes = attributes; + in_cong.in_data_size = data.size; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + SET_INVOKE_ARG(inv_arg, obj_id, QCOMTEE_UEFI_SEC_OP_SET_VAR, nparams); + SET_TEE_PARAM_UBUF(param[0], UBUF_INPUT, TEE_PARAM_UBUF(in_cong)); + SET_TEE_PARAM_UBUF(param[1], UBUF_INPUT, in_variable); + SET_TEE_PARAM_UBUF(param[2], UBUF_INPUT, data); + SET_TEE_PARAM_UBUF(param[3], UBUF_OUTPUT, TEE_PARAM_UBUF(*out_errno)); + + ret = tee_client_object_invoke_func(uefisec_app.ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(uefisec_app.dev, "QCOMTEE_UEFI_SEC_OP_SET_VAR invoke ret: %d, err: 0x%x\n", + ret, inv_arg.ret); + return ret ?: inv_arg.ret; + } + + return ret; +} + +static efi_status_t qcomtee_uefi_set_variable(efi_char16_t *name, efi_guid_t *guid, + u32 attr, unsigned long data_size, + void *data) +{ + int ret; + u32 out_errno; + struct tee_param_ubuf in_data, in_var; + + if (!name || !guid) + return EFI_INVALID_PARAMETER; + + in_data = (struct tee_param_ubuf){ .addr = data, data_size }; + in_var = (struct tee_param_ubuf){ .addr = name, + (ucs2_strlen(name) + 1) * sizeof(*name) }; + + ret = qcuefi_set_variable(in_var, guid, attr, in_data, &out_errno); + if (ret) + return EFI_DEVICE_ERROR; + + return uefisecapp_err_to_efi_status(out_errno); +} + +static int qcuefi_get_next_variable(struct tee_param_ubuf in_variable, efi_guid_t *guid, + struct tee_param_ubuf *out_variable, + efi_guid_t *out_vendor_guid, u32 *out_errno) +{ + int ret; + struct tee_ioctl_object_invoke_arg inv_arg; + u64 obj_id = uefisec_app.uefisec_svc_obj.id; + u32 nparams = 4; + struct tee_param param[nparams]; + + struct { + efi_guid_t guid; + u32 in_data_size; + } in_cong = { 0 }; + + struct { + efi_guid_t guid; + u32 out_data_size; + u32 errno; + } out_cong = { 0 }; + + /* Pass size of available buffer */ + in_cong.in_data_size = out_variable->size; + in_cong.guid = *guid; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + SET_INVOKE_ARG(inv_arg, obj_id, QCOMTEE_UEFI_SEC_OP_GET_NEXT_VAR_NAME, nparams); + SET_TEE_PARAM_UBUF(param[0], UBUF_INPUT, TEE_PARAM_UBUF(in_cong)); + SET_TEE_PARAM_UBUF(param[1], UBUF_INPUT, in_variable); + SET_TEE_PARAM_UBUF(param[2], UBUF_OUTPUT, TEE_PARAM_UBUF(out_cong)); + SET_TEE_PARAM_UBUF(param[3], UBUF_OUTPUT, *out_variable); + + ret = tee_client_object_invoke_func(uefisec_app.ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(uefisec_app.dev, "QCOMTEE_UEFI_SEC_OP_GET_NEXT_VAR_NAME invoke ret: %d, err: 0x%x\n", + ret, inv_arg.ret); + return ret ?: inv_arg.ret; + } + + /* UefiSecApp TA does not touch 'out_variable.size'. Update it here. + * On SUCCESS (!out_errno), 'out_data_size' is length of name in 'out_variable.addr'. + * On failure (out_errno == QCOMTEE_UEFI_SEC_ERROR_SIZE_OUT), 'out_data_size' is + * actual name length. + * Otherwise, it's undefined. + */ + out_variable->size = out_cong.out_data_size; + *out_vendor_guid = out_cong.guid; + *out_errno = out_cong.errno; + + return ret; +} + +static efi_status_t qcomtee_uefi_get_next_variable(unsigned long *name_size, + efi_char16_t *name, + efi_guid_t *guid) +{ + int ret; + u32 out_errno; + efi_guid_t out_guid; + struct tee_param_ubuf in_var, out_var; + + if (!name_size || !name || !guid) + return EFI_INVALID_PARAMETER; + + if (*name_size == 0) + return EFI_INVALID_PARAMETER; + + /* For 'in_var', 'name_size' is not necessarily size of 'name'; + * could be size of buffer where 'name' has been stored. TA expects a + * NULL-terminated string in 'name' and ignores the size. + * For 'out_var', 'name_size' is size of buffer pointed by 'name'. + */ + in_var = (struct tee_param_ubuf){ .addr = name, *name_size }; + out_var = (struct tee_param_ubuf){ .addr = name, *name_size }; + + ret = qcuefi_get_next_variable(in_var, guid, &out_var, &out_guid, + &out_errno); + if (ret) + return EFI_DEVICE_ERROR; + + if (!out_errno) + *guid = out_guid; + + if (!out_errno || out_errno == QCOMTEE_UEFI_SEC_ERROR_SIZE_OUT) + *name_size = out_var.size; + + /* On SUCCESS, 'name' stores the next variable name. */ + return uefisecapp_err_to_efi_status(out_errno); +} + +static int qcuefi_query_variable_info(u32 attributes, + u64 *maximum_variable_storage_size, + u64 *remaining_variable_storage_size, + u64 *maximum_variable_size, u32 *out_errno) +{ + int ret; + struct tee_ioctl_object_invoke_arg inv_arg; + u64 obj_id = uefisec_app.uefisec_svc_obj.id; + u32 nparams = 2; + struct tee_param param[nparams]; + + struct { + u64 max_var_storage_size; + u64 remaining_var_storage_size; + u64 maximum_var_size; + u32 errno; + } out_cong = { 0 }; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + SET_INVOKE_ARG(inv_arg, obj_id, QCOMTEE_UEFI_SEC_OP_QUERY_VAR_INFO, nparams); + SET_TEE_PARAM_UBUF(param[0], UBUF_INPUT, TEE_PARAM_UBUF(attributes)); + SET_TEE_PARAM_UBUF(param[1], UBUF_OUTPUT, TEE_PARAM_UBUF(out_cong)); + + ret = tee_client_object_invoke_func(uefisec_app.ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(uefisec_app.dev, "QCOMTEE_UEFI_SEC_OP_QUERY_VAR_INFO invoke ret: %d, err: 0x%x\n", + ret, inv_arg.ret); + return ret ?: inv_arg.ret; + } + + *maximum_variable_storage_size = out_cong.max_var_storage_size; + *remaining_variable_storage_size = out_cong.remaining_var_storage_size; + *maximum_variable_size = out_cong.maximum_var_size; + *out_errno = out_cong.errno; + + return ret; +} + +static efi_status_t qcomtee_uefi_query_variable_info(u32 attr, u64 *storage_space, + u64 *remaining_space, + u64 *max_variable_size) +{ + int ret; + u32 out_errno; + u64 maximum_variable_storage_size; + u64 remaining_variable_storage_size; + u64 maximum_variable_size; + + if (!storage_space || !remaining_space || !max_variable_size) + return EFI_INVALID_PARAMETER; + + ret = qcuefi_query_variable_info(attr, + &maximum_variable_storage_size, + &remaining_variable_storage_size, + &maximum_variable_size, + &out_errno); + + if (ret) + return EFI_DEVICE_ERROR; + + if (!out_errno) { + *storage_space = maximum_variable_storage_size; + *remaining_space = remaining_variable_storage_size; + *max_variable_size = maximum_variable_size; + } + + return uefisecapp_err_to_efi_status(out_errno); +} + +/** + * qcomtee_release_object() - Release an object returned by QTEE. + * + * Each object returned by QTEE repesents a secure service exposed to the + * client. Whenever an secure service is opened, QTEE may allocate resources + * on the client's behalf. Therefore, once the client is done accessing the + * secure service, the object representing it should be explicitly released + * so that QTEE can release the associated resources as well. + * + * @ctx: TEE context. + * @object: The object to release. + */ +static void qcomtee_release_object(struct tee_context *ctx, + struct tee_param_objref object) +{ + struct tee_ioctl_object_invoke_arg inv_arg; + + memset(&inv_arg, 0, sizeof(inv_arg)); + SET_INVOKE_ARG(inv_arg, object.id, QCOMTEE_MSG_OBJECT_OP_RELEASE, 0); + tee_client_object_invoke_func(ctx, &inv_arg, NULL); +} + +/** + * qcomtee_get_uefisec_svc_obj() - Get a UEFI Secure App service object to + * begin communication with the service. + * @ctx: TEE context. + * @client_env_obj: The client environment object returned earlier by QTEE. + * @uefisec_svc_obj: The UEFI Secure App service object. + * + * Returns 0 on success. + * Returns < 0 if client environment object invocation failed. + * Returns > 0 if client environment invocation was success but UEFI Secure App + * service object could not be returned for some other reason (represented by the + * returned value) + */ +static int qcomtee_get_uefisec_svc_obj(struct tee_context *ctx, + struct tee_param_objref client_env_obj, + struct tee_param_objref *uefisec_svc_obj) +{ + int ret; + struct tee_ioctl_object_invoke_arg inv_arg; + u64 obj_id = client_env_obj.id; + u32 nparams = 2; + struct tee_param param[nparams]; + u32 uefisec_uid = QCOMTEE_UEFI_SEC_UID; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + SET_INVOKE_ARG(inv_arg, obj_id, QCOMTEE_OP_CLIENT_ENV_OPEN, nparams); + SET_TEE_PARAM_UBUF(param[0], UBUF_INPUT, TEE_PARAM_UBUF(uefisec_uid)); + SET_TEE_PARAM_OBJREF(param[1], OBJREF_OUTPUT, 0, 0); + + ret = tee_client_object_invoke_func(ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(uefisec_app.dev, "QCOMTEE_CLIENT_ENV_OPEN invoke ret: %d, err: 0x%x\n", + ret, inv_arg.ret); + return ret ?: inv_arg.ret; + } + + *uefisec_svc_obj = param[1].u.objref; + return ret; +} + +/** + * qcomtee_get_client_env_obj() - Get a client environment object to begin + * object exchange with QTEE. + * @ctx: TEE context. + * @client_env_obj: The client environment object returned by QTEE. + * + * Returns 0 on success. + * Returns < 0 if root object invocation failed. + * Returns > 0 if root object invocation was success but client environment + * object could not be returned for some other reason (represented by the + * returned value) + */ +static int qcomtee_get_client_env_obj(struct tee_context *ctx, + struct tee_param_objref *client_env_obj) +{ + int ret; + struct tee_ioctl_object_invoke_arg inv_arg; + u32 nparams = 2; + struct tee_param param[nparams]; + + memset(&inv_arg, 0, sizeof(inv_arg)); + memset(¶m, 0, sizeof(param)); + + SET_INVOKE_ARG(inv_arg, TEE_OBJREF_NULL, + QCOMTEE_ROOT_OP_REG_WITH_CREDENTIALS, nparams); + SET_TEE_PARAM_OBJREF(param[0], OBJREF_INPUT, TEE_OBJREF_NULL, 0); + SET_TEE_PARAM_OBJREF(param[1], OBJREF_OUTPUT, 0, 0); + + ret = tee_client_object_invoke_func(ctx, &inv_arg, param); + if (ret < 0 || inv_arg.ret != 0) { + dev_err(uefisec_app.dev, "QCOMTEE_ROOT_OP_REG_WITH_CREDENTIALS invoke ret: %d, err: 0x%x\n", + ret, inv_arg.ret); + return ret ?: inv_arg.ret; + } + + *client_env_obj = param[1].u.objref; + return ret; +} + +static const struct efivar_operations qcom_efivar_ops = { + .get_variable = qcomtee_uefi_get_variable, + .set_variable = qcomtee_uefi_set_variable, + .get_next_variable = qcomtee_uefi_get_next_variable, + .query_variable_info = qcomtee_uefi_query_variable_info, +}; + +static int qcomtee_ctx_match(struct tee_ioctl_version_data *ver, + const void *data) +{ + return (ver->impl_id == TEE_IMPL_ID_QTEE); +} + +static int qcomtee_uefisecapp_probe(struct tee_client_device *tee_dev) +{ + int ret, err; + struct tee_param_objref client_env_obj; + struct tee_param_objref uefisec_svc_obj; + + uefisec_app.dev = &tee_dev->dev; + /* Open context with QCOMTEE driver */ + uefisec_app.ctx = tee_client_open_context(NULL, qcomtee_ctx_match, NULL, + NULL); + if (IS_ERR(uefisec_app.ctx)) + return -ENODEV; + + /* Obtain a reference to client_env object to begin object exchange + * with QTEE + */ + ret = qcomtee_get_client_env_obj(uefisec_app.ctx, &client_env_obj); + if (ret) { + err = -EINVAL; + goto err_get_client_env; + } + + /* Obtain a reference to the uefisec_svc object which provides access to + * the EFI var storage. + */ + ret = qcomtee_get_uefisec_svc_obj(uefisec_app.ctx, client_env_obj, + &uefisec_svc_obj); + if (ret) { + err = -EINVAL; + goto err_get_uefisec_svc; + } + uefisec_app.uefisec_svc_obj = uefisec_svc_obj; + + ret = efivars_register(&uefisec_app.efivars, &qcom_efivar_ops); + if (ret) { + err = ret; + goto err_efi_vars_reg; + } + + /* We don't need to keep a reference to this object anymore, we only + * needed it to obtain the uefisec_svc object. + */ + qcomtee_release_object(uefisec_app.ctx, client_env_obj); + return 0; + +err_efi_vars_reg: + qcomtee_release_object(uefisec_app.ctx, uefisec_svc_obj); +err_get_uefisec_svc: + qcomtee_release_object(uefisec_app.ctx, client_env_obj); +err_get_client_env: + tee_client_close_context(uefisec_app.ctx); + + return err; +} + +static void qcomtee_uefisecapp_remove(struct tee_client_device *tee_dev) +{ + efivars_unregister(&uefisec_app.efivars); + qcomtee_release_object(uefisec_app.ctx, uefisec_app.uefisec_svc_obj); + tee_client_close_context(uefisec_app.ctx); +} + +static const struct tee_client_device_id qcomtee_uefisecapp_id_table[] = { + {UEFISECAPP_UUID}, + {} +}; +MODULE_DEVICE_TABLE(tee, qcomtee_uefisecapp_id_table); + +static struct tee_client_driver qcomtee_uefisecapp_driver = { + .id_table = qcomtee_uefisecapp_id_table, + .probe = qcomtee_uefisecapp_probe, + .remove = qcomtee_uefisecapp_remove, + .driver = { + .name = "qcom-tee-uefisecapp", + }, +}; + +module_tee_client_driver(qcomtee_uefisecapp_driver); + +MODULE_AUTHOR("Qualcomm"); +MODULE_DESCRIPTION("TEE client driver for Qualcomm TEE UEFI Secure App"); +MODULE_LICENSE("GPL");