|  | // SPDX-License-Identifier: GPL-2.0-or-later | 
|  | /* | 
|  | * OP-TEE STM32MP BSEC PTA interface, used by STM32 ROMEM driver | 
|  | * | 
|  | * Copyright (C) 2022, STMicroelectronics - All Rights Reserved | 
|  | */ | 
|  |  | 
|  | #include <linux/tee_drv.h> | 
|  |  | 
|  | #include "stm32-bsec-optee-ta.h" | 
|  |  | 
|  | /* | 
|  | * Read OTP memory | 
|  | * | 
|  | * [in]		value[0].a		OTP start offset in byte | 
|  | * [in]		value[0].b		Access type (0:shadow, 1:fuse, 2:lock) | 
|  | * [out]	memref[1].buffer	Output buffer to store read values | 
|  | * [out]	memref[1].size		Size of OTP to be read | 
|  | * | 
|  | * Return codes: | 
|  | * TEE_SUCCESS - Invoke command success | 
|  | * TEE_ERROR_BAD_PARAMETERS - Incorrect input param | 
|  | * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller | 
|  | */ | 
|  | #define PTA_BSEC_READ_MEM		0x0 | 
|  |  | 
|  | /* | 
|  | * Write OTP memory | 
|  | * | 
|  | * [in]		value[0].a		OTP start offset in byte | 
|  | * [in]		value[0].b		Access type (0:shadow, 1:fuse, 2:lock) | 
|  | * [in]		memref[1].buffer	Input buffer to read values | 
|  | * [in]		memref[1].size		Size of OTP to be written | 
|  | * | 
|  | * Return codes: | 
|  | * TEE_SUCCESS - Invoke command success | 
|  | * TEE_ERROR_BAD_PARAMETERS - Incorrect input param | 
|  | * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller | 
|  | */ | 
|  | #define PTA_BSEC_WRITE_MEM		0x1 | 
|  |  | 
|  | /* value of PTA_BSEC access type = value[in] b */ | 
|  | #define SHADOW_ACCESS	0 | 
|  | #define FUSE_ACCESS	1 | 
|  | #define LOCK_ACCESS	2 | 
|  |  | 
|  | /* Bitfield definition for LOCK status */ | 
|  | #define LOCK_PERM			BIT(30) | 
|  |  | 
|  | /* OP-TEE STM32MP BSEC TA UUID */ | 
|  | static const uuid_t stm32mp_bsec_ta_uuid = | 
|  | UUID_INIT(0x94cf71ad, 0x80e6, 0x40b5, | 
|  | 0xa7, 0xc6, 0x3d, 0xc5, 0x01, 0xeb, 0x28, 0x03); | 
|  |  | 
|  | /* | 
|  | * Check whether this driver supports the BSEC TA in the TEE instance | 
|  | * represented by the params (ver/data) to this function. | 
|  | */ | 
|  | static int stm32_bsec_optee_ta_match(struct tee_ioctl_version_data *ver, | 
|  | const void *data) | 
|  | { | 
|  | /* Currently this driver only supports GP compliant, OP-TEE based TA */ | 
|  | if ((ver->impl_id == TEE_IMPL_ID_OPTEE) && | 
|  | (ver->gen_caps & TEE_GEN_CAP_GP)) | 
|  | return 1; | 
|  | else | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Open a session to OP-TEE for STM32MP BSEC TA */ | 
|  | static int stm32_bsec_ta_open_session(struct tee_context *ctx, u32 *id) | 
|  | { | 
|  | struct tee_ioctl_open_session_arg sess_arg; | 
|  | int rc; | 
|  |  | 
|  | memset(&sess_arg, 0, sizeof(sess_arg)); | 
|  | export_uuid(sess_arg.uuid, &stm32mp_bsec_ta_uuid); | 
|  | sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL; | 
|  | sess_arg.num_params = 0; | 
|  |  | 
|  | rc = tee_client_open_session(ctx, &sess_arg, NULL); | 
|  | if ((rc < 0) || (sess_arg.ret != 0)) { | 
|  | pr_err("%s: tee_client_open_session failed err:%#x, ret:%#x\n", | 
|  | __func__, sess_arg.ret, rc); | 
|  | if (!rc) | 
|  | rc = -EINVAL; | 
|  | } else { | 
|  | *id = sess_arg.session; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* close a session to OP-TEE for STM32MP BSEC TA */ | 
|  | static void stm32_bsec_ta_close_session(void *ctx, u32 id) | 
|  | { | 
|  | tee_client_close_session(ctx, id); | 
|  | } | 
|  |  | 
|  | /* stm32_bsec_optee_ta_open() - initialize the STM32MP BSEC TA */ | 
|  | int stm32_bsec_optee_ta_open(struct tee_context **ctx) | 
|  | { | 
|  | struct tee_context *tee_ctx; | 
|  | u32 session_id; | 
|  | int rc; | 
|  |  | 
|  | /* Open context with TEE driver */ | 
|  | tee_ctx = tee_client_open_context(NULL, stm32_bsec_optee_ta_match, NULL, NULL); | 
|  | if (IS_ERR(tee_ctx)) { | 
|  | rc = PTR_ERR(tee_ctx); | 
|  | if (rc == -ENOENT) | 
|  | return -EPROBE_DEFER; | 
|  | pr_err("%s: tee_client_open_context failed (%d)\n", __func__, rc); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* Check STM32MP BSEC TA presence */ | 
|  | rc = stm32_bsec_ta_open_session(tee_ctx, &session_id); | 
|  | if (rc) { | 
|  | tee_client_close_context(tee_ctx); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | stm32_bsec_ta_close_session(tee_ctx, session_id); | 
|  |  | 
|  | *ctx = tee_ctx; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* stm32_bsec_optee_ta_open() - release the PTA STM32MP BSEC TA */ | 
|  | void stm32_bsec_optee_ta_close(void *ctx) | 
|  | { | 
|  | tee_client_close_context(ctx); | 
|  | } | 
|  |  | 
|  | /* stm32_bsec_optee_ta_read() - nvmem read access using PTA client driver */ | 
|  | int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset, | 
|  | void *buf, size_t bytes) | 
|  | { | 
|  | struct tee_shm *shm; | 
|  | struct tee_ioctl_invoke_arg arg; | 
|  | struct tee_param param[2]; | 
|  | u8 *shm_buf; | 
|  | u32 start, num_bytes; | 
|  | int ret; | 
|  | u32 session_id; | 
|  |  | 
|  | ret = stm32_bsec_ta_open_session(ctx, &session_id); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | memset(&arg, 0, sizeof(arg)); | 
|  | memset(¶m, 0, sizeof(param)); | 
|  |  | 
|  | arg.func = PTA_BSEC_READ_MEM; | 
|  | arg.session = session_id; | 
|  | arg.num_params = 2; | 
|  |  | 
|  | /* align access on 32bits */ | 
|  | start = ALIGN_DOWN(offset, 4); | 
|  | num_bytes = round_up(offset + bytes - start, 4); | 
|  | param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; | 
|  | param[0].u.value.a = start; | 
|  | param[0].u.value.b = SHADOW_ACCESS; | 
|  |  | 
|  | shm = tee_shm_alloc_kernel_buf(ctx, num_bytes); | 
|  | if (IS_ERR(shm)) { | 
|  | ret = PTR_ERR(shm); | 
|  | goto out_tee_session; | 
|  | } | 
|  |  | 
|  | param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; | 
|  | param[1].u.memref.shm = shm; | 
|  | param[1].u.memref.size = num_bytes; | 
|  |  | 
|  | ret = tee_client_invoke_func(ctx, &arg, param); | 
|  | if (ret < 0 || arg.ret != 0) { | 
|  | pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", | 
|  | arg.ret, ret); | 
|  | if (!ret) | 
|  | ret = -EIO; | 
|  | } | 
|  | if (!ret) { | 
|  | shm_buf = tee_shm_get_va(shm, 0); | 
|  | if (IS_ERR(shm_buf)) { | 
|  | ret = PTR_ERR(shm_buf); | 
|  | pr_err("tee_shm_get_va failed for transmit (%d)\n", ret); | 
|  | } else { | 
|  | /* read data from 32 bits aligned buffer */ | 
|  | memcpy(buf, &shm_buf[offset % 4], bytes); | 
|  | } | 
|  | } | 
|  |  | 
|  | tee_shm_free(shm); | 
|  |  | 
|  | out_tee_session: | 
|  | stm32_bsec_ta_close_session(ctx, session_id); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* stm32_bsec_optee_ta_write() - nvmem write access using PTA client driver */ | 
|  | int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower, | 
|  | unsigned int offset, void *buf, size_t bytes) | 
|  | {	struct tee_shm *shm; | 
|  | struct tee_ioctl_invoke_arg arg; | 
|  | struct tee_param param[2]; | 
|  | u8 *shm_buf; | 
|  | int ret; | 
|  | u32 session_id; | 
|  |  | 
|  | ret = stm32_bsec_ta_open_session(ctx, &session_id); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | /* Allow only writing complete 32-bits aligned words */ | 
|  | if ((bytes % 4) || (offset % 4)) | 
|  | return -EINVAL; | 
|  |  | 
|  | memset(&arg, 0, sizeof(arg)); | 
|  | memset(¶m, 0, sizeof(param)); | 
|  |  | 
|  | arg.func = PTA_BSEC_WRITE_MEM; | 
|  | arg.session = session_id; | 
|  | arg.num_params = 2; | 
|  |  | 
|  | param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; | 
|  | param[0].u.value.a = offset; | 
|  | param[0].u.value.b = FUSE_ACCESS; | 
|  |  | 
|  | shm = tee_shm_alloc_kernel_buf(ctx, bytes); | 
|  | if (IS_ERR(shm)) { | 
|  | ret = PTR_ERR(shm); | 
|  | goto out_tee_session; | 
|  | } | 
|  |  | 
|  | param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; | 
|  | param[1].u.memref.shm = shm; | 
|  | param[1].u.memref.size = bytes; | 
|  |  | 
|  | shm_buf = tee_shm_get_va(shm, 0); | 
|  | if (IS_ERR(shm_buf)) { | 
|  | ret = PTR_ERR(shm_buf); | 
|  | pr_err("tee_shm_get_va failed for transmit (%d)\n", ret); | 
|  | tee_shm_free(shm); | 
|  |  | 
|  | goto out_tee_session; | 
|  | } | 
|  |  | 
|  | memcpy(shm_buf, buf, bytes); | 
|  |  | 
|  | ret = tee_client_invoke_func(ctx, &arg, param); | 
|  | if (ret < 0 || arg.ret != 0) { | 
|  | pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret); | 
|  | if (!ret) | 
|  | ret = -EIO; | 
|  | } | 
|  | pr_debug("Write OTPs %d to %zu, ret=%d\n", offset / 4, (offset + bytes) / 4, ret); | 
|  |  | 
|  | /* Lock the upper OTPs with ECC protection, word programming only */ | 
|  | if (!ret && ((offset + bytes) >= (lower * 4))) { | 
|  | u32 start, nb_lock; | 
|  | u32 *lock = (u32 *)shm_buf; | 
|  | int i; | 
|  |  | 
|  | /* | 
|  | * don't lock the lower OTPs, no ECC protection and incremental | 
|  | * bit programming, a second write is allowed | 
|  | */ | 
|  | start = max_t(u32, offset, lower * 4); | 
|  | nb_lock = (offset + bytes - start) / 4; | 
|  |  | 
|  | param[0].u.value.a = start; | 
|  | param[0].u.value.b = LOCK_ACCESS; | 
|  | param[1].u.memref.size = nb_lock * 4; | 
|  |  | 
|  | for (i = 0; i < nb_lock; i++) | 
|  | lock[i] = LOCK_PERM; | 
|  |  | 
|  | ret = tee_client_invoke_func(ctx, &arg, param); | 
|  | if (ret < 0 || arg.ret != 0) { | 
|  | pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret); | 
|  | if (!ret) | 
|  | ret = -EIO; | 
|  | } | 
|  | pr_debug("Lock upper OTPs %d to %d, ret=%d\n", | 
|  | start / 4, start / 4 + nb_lock, ret); | 
|  | } | 
|  |  | 
|  | tee_shm_free(shm); | 
|  |  | 
|  | out_tee_session: | 
|  | stm32_bsec_ta_close_session(ctx, session_id); | 
|  |  | 
|  | return ret; | 
|  | } |