diff --git a/drivers/tee/CMakeLists.txt b/drivers/tee/CMakeLists.txt index 3f97766c258..d12fb9f15b5 100644 --- a/drivers/tee/CMakeLists.txt +++ b/drivers/tee/CMakeLists.txt @@ -1,5 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 +add_subdirectory_ifdef(CONFIG_OPTEE optee) + if (CONFIG_TEE) zephyr_library() zephyr_library_sources(tee.c) diff --git a/drivers/tee/Kconfig b/drivers/tee/Kconfig index 2e01a8f5309..dd5acce4e00 100644 --- a/drivers/tee/Kconfig +++ b/drivers/tee/Kconfig @@ -13,4 +13,6 @@ module-str = tee comment "Device Drivers" +source "drivers/tee/optee/Kconfig" + endif # TEE diff --git a/drivers/tee/optee/CMakeLists.txt b/drivers/tee/optee/CMakeLists.txt new file mode 100644 index 00000000000..f080bd18436 --- /dev/null +++ b/drivers/tee/optee/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2023 EPAM Systems +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(optee.c) diff --git a/drivers/tee/optee/Kconfig b/drivers/tee/optee/Kconfig new file mode 100644 index 00000000000..30e3d2d0f6d --- /dev/null +++ b/drivers/tee/optee/Kconfig @@ -0,0 +1,21 @@ +# Copyright (c) 2023 EPAM Systems +# SPDX-License-Identifier: Apache-2.0 + +config OPTEE + bool "OP-TEE driver" + depends on (ARM64 && ARMV8_A_NS && HAS_ARM_SMCCC) || ZTEST + help + This implements support of the OP-TEE firmware which is loaded + as BL32 image. OP-TEE is a Trust Zone OS which implements mechanisms + of the hardware isolation and rely to ARM TrustZone technology. + Driver requests functions from the OP-TEE and implements RPC mechanism + needed by OP-TEE to run services. See https://www.op-tee.org for more + information. + +config OPTEE_MAX_NOTIF + int "Max number of OP-TEE notifications" + depends on OPTEE + default 255 + help + Sets the maximum notifications from OP-TEE to the Normal World. OP-TEE using + this mechanism for the synchronization. diff --git a/drivers/tee/optee/optee.c b/drivers/tee/optee/optee.c new file mode 100644 index 00000000000..195f0ecc04c --- /dev/null +++ b/drivers/tee/optee/optee.c @@ -0,0 +1,1267 @@ +/* + * Copyright 2023 EPAM Systems + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "optee_msg.h" +#include "optee_rpc_cmd.h" +#include "optee_smc.h" +LOG_MODULE_REGISTER(optee); + +#define DT_DRV_COMPAT linaro_optee_tz + +/* amount of physical addresses that can be stored in one page */ +#define OPTEE_NUMBER_OF_ADDR_PER_PAGE (OPTEE_MSG_NONCONTIG_PAGE_SIZE / sizeof(uint64_t)) + +/* + * TEE Implementation ID + */ +#define TEE_IMPL_ID_OPTEE 1 + +/* + * OP-TEE specific capabilities + */ +#define TEE_OPTEE_CAP_TZ BIT(0) + +struct optee_rpc_param { + uint32_t a0; + uint32_t a1; + uint32_t a2; + uint32_t a3; + uint32_t a4; + uint32_t a5; + uint32_t a6; + uint32_t a7; +}; + +typedef void (*smc_call_t)(unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, unsigned long a6, unsigned long a7, + struct arm_smccc_res *res); + +struct optee_driver_config { + const char *method; +}; + +struct optee_notify { + sys_dnode_t node; + uint32_t key; + struct k_sem wait; +}; + +struct optee_supp_req { + sys_dnode_t link; + + bool in_queue; + uint32_t func; + uint32_t ret; + size_t num_params; + struct tee_param *param; + + struct k_sem complete; +}; + +struct optee_supp { + /* Serializes access to this struct */ + struct k_mutex mutex; + + int req_id; + sys_dlist_t reqs; + struct optee_supp_req *current; + struct k_sem reqs_c; +}; + +struct optee_driver_data { + smc_call_t smc_call; + + sys_bitarray_t *notif_bitmap; + + sys_dlist_t notif; + struct k_spinlock notif_lock; + struct optee_supp supp; + unsigned long sec_caps; +}; + +/* Wrapping functions so function pointer can be used */ +static void optee_smccc_smc(unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_smc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +static void optee_smccc_hvc(unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, + unsigned long a4, unsigned long a5, unsigned long a6, unsigned long a7, + struct arm_smccc_res *res) +{ + arm_smccc_hvc(a0, a1, a2, a3, a4, a5, a6, a7, res); +} + +static int param_to_msg_param(const struct tee_param *param, unsigned int num_param, + struct optee_msg_param *msg_param) +{ + int i; + const struct tee_param *tp = param; + struct optee_msg_param *mtp = msg_param; + + if (!param || !msg_param) { + return -EINVAL; + } + + for (i = 0; i < num_param; i++, tp++, mtp++) { + if (!tp || !mtp) { + LOG_ERR("Wrong param on %d iteration", i); + return -EINVAL; + } + + switch (tp->attr) { + case TEE_PARAM_ATTR_TYPE_NONE: + mtp->attr = OPTEE_MSG_ATTR_TYPE_NONE; + memset(&mtp->u, 0, sizeof(mtp->u)); + break; + case TEE_PARAM_ATTR_TYPE_VALUE_INPUT: + case TEE_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_PARAM_ATTR_TYPE_VALUE_INOUT: + mtp->attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT + tp->attr - + TEE_PARAM_ATTR_TYPE_VALUE_INPUT; + mtp->u.value.a = tp->a; + mtp->u.value.b = tp->b; + mtp->u.value.c = tp->c; + break; + case TEE_PARAM_ATTR_TYPE_MEMREF_INPUT: + case TEE_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_PARAM_ATTR_TYPE_MEMREF_INOUT: + mtp->attr = OPTEE_MSG_ATTR_TYPE_RMEM_INPUT + tp->attr - + TEE_PARAM_ATTR_TYPE_MEMREF_INPUT; + mtp->u.rmem.shm_ref = tp->c; + mtp->u.rmem.size = tp->b; + mtp->u.rmem.offs = tp->a; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static void msg_param_to_tmp_mem(struct tee_param *p, uint32_t attr, + const struct optee_msg_param *mp) +{ + struct tee_shm *shm = (struct tee_shm *)mp->u.tmem.shm_ref; + + p->attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT + attr - OPTEE_MSG_ATTR_TYPE_TMEM_INPUT; + p->b = mp->u.tmem.size; + + if (!shm) { + p->a = 0; + p->c = 0; + return; + } + + p->a = mp->u.tmem.buf_ptr - k_mem_phys_addr(shm->addr); + p->c = mp->u.tmem.shm_ref; +} + +static int msg_param_to_param(struct tee_param *param, unsigned int num_param, + const struct optee_msg_param *msg_param) +{ + int i; + struct tee_param *tp = param; + const struct optee_msg_param *mtp = msg_param; + + if (!param || !msg_param) { + return -EINVAL; + } + + for (i = 0; i < num_param; i++, tp++, mtp++) { + uint32_t attr = mtp->attr & OPTEE_MSG_ATTR_TYPE_MASK; + + if (!tp || !mtp) { + LOG_ERR("Wrong param on %d iteration", i); + return -EINVAL; + } + + switch (attr) { + case OPTEE_MSG_ATTR_TYPE_NONE: + memset(tp, 0, sizeof(*tp)); + tp->attr = TEE_PARAM_ATTR_TYPE_NONE; + break; + case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT: + tp->attr = TEE_PARAM_ATTR_TYPE_VALUE_INPUT + attr - + OPTEE_MSG_ATTR_TYPE_VALUE_INPUT; + tp->a = mtp->u.value.a; + tp->b = mtp->u.value.b; + tp->c = mtp->u.value.c; + break; + case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT: + tp->attr = TEE_PARAM_ATTR_TYPE_MEMREF_INPUT + attr - + OPTEE_MSG_ATTR_TYPE_RMEM_INPUT; + tp->b = mtp->u.rmem.size; + + if (!mtp->u.rmem.shm_ref) { + tp->a = 0; + tp->c = 0; + } else { + tp->a = mtp->u.rmem.offs; + tp->c = mtp->u.rmem.shm_ref; + } + + break; + case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT: + case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT: + msg_param_to_tmp_mem(tp, attr, mtp); + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static uint64_t regs_to_u64(uint32_t reg0, uint32_t reg1) +{ + return (uint64_t)(((uint64_t)reg0 << 32) | reg1); +} + +static void u64_to_regs(uint64_t val, uint32_t *reg0, uint32_t *reg1) +{ + *reg0 = val >> 32; + *reg1 = val; +} + +static inline bool check_param_input(struct optee_msg_arg *arg) +{ + return arg->num_params == 1 && + arg->params[0].attr == OPTEE_MSG_ATTR_TYPE_VALUE_INPUT; +} + +static void *optee_construct_page_list(void *buf, uint32_t len, uint64_t *phys_buf); + +static uint32_t optee_call_supp(const struct device *dev, uint32_t func, size_t num_params, + struct tee_param *param) +{ + struct optee_driver_data *data = (struct optee_driver_data *)dev->data; + struct optee_supp *supp = &data->supp; + struct optee_supp_req *req; + uint32_t ret; + + req = k_malloc(sizeof(*req)); + if (!req) { + return TEEC_ERROR_OUT_OF_MEMORY; + } + + k_sem_init(&req->complete, 0, 1); + req->func = func; + req->num_params = num_params; + req->param = param; + + /* Insert the request in the request list */ + k_mutex_lock(&supp->mutex, K_FOREVER); + sys_dlist_append(&supp->reqs, &req->link); + k_mutex_unlock(&supp->mutex); + + /* Tell an event listener there's a new request */ + k_sem_give(&supp->reqs_c); + + /* + * Wait for supplicant to process and return result, once we've + * returned from k_sem_take(&req->c) successfully we have + * exclusive access again. + */ + + k_sem_take(&req->complete, K_FOREVER); + + ret = req->ret; + k_free(req); + + return ret; +} + +static int cmd_alloc_suppl(const struct device *dev, size_t sz, struct tee_shm **shm) +{ + uint32_t ret; + struct tee_param param; + + param.attr = TEE_PARAM_ATTR_TYPE_VALUE_INOUT; + param.a = OPTEE_RPC_SHM_TYPE_APPL; + param.b = sz; + param.c = 0; + + ret = optee_call_supp(dev, OPTEE_RPC_CMD_SHM_ALLOC, 1, ¶m); + + if (ret) { + return ret; + } + + ret = tee_add_shm(dev, (void *)param.c, 0, param.b, 0, shm); + + return ret; +} + +static void cmd_free_suppl(const struct device *dev, struct tee_shm *shm) +{ + struct tee_param param; + + param.attr = TEE_PARAM_ATTR_TYPE_VALUE_INOUT; + param.a = OPTEE_RPC_SHM_TYPE_APPL; + param.b = (uint64_t)shm; + param.c = 0; + + optee_call_supp(dev, OPTEE_RPC_CMD_SHM_FREE, 1, ¶m); + tee_rm_shm(dev, shm); +} + +static void handle_cmd_alloc(const struct device *dev, struct optee_msg_arg *arg, + void **pages) +{ + int rc; + struct tee_shm *shm = NULL; + void *pl; + uint64_t pl_phys_and_offset; + + arg->ret_origin = TEEC_ORIGIN_COMMS; + + if (!check_param_input(arg)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + switch (arg->params[0].u.value.a) { + case OPTEE_RPC_SHM_TYPE_KERNEL: + /* TODO handle situation when shm was allocated statically so buffer can be reused*/ + rc = tee_add_shm(dev, NULL, 0, arg->params[0].u.value.b, TEE_SHM_ALLOC, &shm); + break; + case OPTEE_RPC_SHM_TYPE_APPL: + rc = cmd_alloc_suppl(dev, arg->params[0].u.value.b, &shm); + break; + default: + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + if (rc) { + if (rc == -ENOMEM) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + } else { + arg->ret = TEEC_ERROR_GENERIC; + } + return; + } + + pl = optee_construct_page_list(shm->addr, shm->size, &pl_phys_and_offset); + if (!pl) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + goto out; + } + + *pages = pl; + arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | OPTEE_MSG_ATTR_NONCONTIG; + arg->params[0].u.tmem.buf_ptr = pl_phys_and_offset; + arg->params[0].u.tmem.size = shm->size; + arg->params[0].u.tmem.shm_ref = (uint64_t)shm; + arg->ret = TEEC_SUCCESS; + return; +out: + tee_shm_free(dev, shm); +} + +static void handle_cmd_free(const struct device *dev, struct optee_msg_arg *arg) +{ + int rc = 0; + + if (!check_param_input(arg)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + switch (arg->params[0].u.value.a) { + case OPTEE_RPC_SHM_TYPE_KERNEL: + rc = tee_rm_shm(dev, (struct tee_shm *)arg->params[0].u.value.b); + break; + case OPTEE_RPC_SHM_TYPE_APPL: + cmd_free_suppl(dev, (struct tee_shm *)arg->params[0].u.value.b); + break; + default: + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + if (rc) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + return; + } + + arg->ret = TEEC_SUCCESS; +} + +static void handle_cmd_get_time(const struct device *dev, struct optee_msg_arg *arg) +{ + int64_t ticks; + int64_t up_secs; + int64_t up_nsecs; + + if (arg->num_params != 1 || + (arg->params[0].attr & OPTEE_MSG_ATTR_TYPE_MASK) + != OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + ticks = k_uptime_ticks(); + + up_secs = ticks / CONFIG_SYS_CLOCK_TICKS_PER_SEC; + up_nsecs = k_ticks_to_ns_floor64(ticks - up_secs * CONFIG_SYS_CLOCK_TICKS_PER_SEC); + arg->params[0].u.value.a = up_secs; + arg->params[0].u.value.b = up_nsecs; + + arg->ret = TEEC_SUCCESS; +} + +/* This should be called under notif_lock */ +static inline bool key_is_pending(struct optee_driver_data *data, uint32_t key) +{ + struct optee_notify *iter; + + SYS_DLIST_FOR_EACH_CONTAINER(&data->notif, iter, node) { + if (iter->key == key) { + k_sem_give(&iter->wait); + return true; + } + } + + return false; +} + +static int optee_notif_send(const struct device *dev, uint32_t key) +{ + struct optee_driver_data *data = dev->data; + k_spinlock_key_t sp_key; + + if (key > CONFIG_OPTEE_MAX_NOTIF) { + return -EINVAL; + } + + sp_key = k_spin_lock(&data->notif_lock); + if (!key_is_pending(data, key)) { + /* If nobody is waiting for key - set bit in the bitmap */ + sys_bitarray_set_bit(data->notif_bitmap, key); + } + k_spin_unlock(&data->notif_lock, sp_key); + + return 0; +} + +static int optee_notif_wait(const struct device *dev, uint32_t key) +{ + int rc = 0; + struct optee_driver_data *data = dev->data; + struct optee_notify *entry; + k_spinlock_key_t sp_key; + int prev_val; + + if (key > CONFIG_OPTEE_MAX_NOTIF) + return -EINVAL; + + entry = k_malloc(sizeof(*entry)); + if (!entry) { + return -ENOMEM; + } + + k_sem_init(&entry->wait, 0, 1); + entry->key = key; + + sp_key = k_spin_lock(&data->notif_lock); + + /* + * If notif bit was set then SEND command was already received. + * Skipping wait. + */ + rc = sys_bitarray_test_and_clear_bit(data->notif_bitmap, key, &prev_val); + if (rc || prev_val) { + goto out; + } + + /* + * If key is already registred, then skip. + */ + if (key_is_pending(data, key)) { + rc = -EBUSY; + goto out; + } + + sys_dlist_append(&data->notif, &entry->node); + + k_spin_unlock(&data->notif_lock, sp_key); + k_sem_take(&entry->wait, K_FOREVER); + sp_key = k_spin_lock(&data->notif_lock); + + sys_dlist_remove(&entry->node); +out: + k_spin_unlock(&data->notif_lock, sp_key); + + k_free(entry); + + return rc; +} + +static void handle_cmd_notify(const struct device *dev, struct optee_msg_arg *arg) +{ + if (!check_param_input(arg)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + switch (arg->params[0].u.value.a) { + case OPTEE_RPC_NOTIFICATION_SEND: + if (optee_notif_send(dev, arg->params[0].u.value.b)) { + goto err; + } + break; + case OPTEE_RPC_NOTIFICATION_WAIT: + if (optee_notif_wait(dev, arg->params[0].u.value.b)) { + goto err; + } + break; + default: + goto err; + } + + arg->ret = TEEC_SUCCESS; + return; + +err: + arg->ret = TEEC_ERROR_BAD_PARAMETERS; +} + +static void handle_cmd_wait(const struct device *dev, struct optee_msg_arg *arg) +{ + if (!check_param_input(arg)) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + return; + } + + k_sleep(K_MSEC(arg->params[0].u.value.a)); + + arg->ret = TEEC_SUCCESS; +} + +static void free_shm_pages(void **pages) +{ + /* + * Clean allocated pages if needed. Some function calls requires pages + * allocation which should be freed after processing new request. + * It is safe to free this list when another SHM op (e,g. another alloc + * or free) was received. + */ + if (*pages) { + k_free(*pages); + *pages = NULL; + } +} + +static void handle_rpc_supp_cmd(const struct device *dev, struct optee_msg_arg *arg) +{ + struct tee_param *params; + int ret; + + arg->ret_origin = TEEC_ORIGIN_COMMS; + + params = k_malloc(sizeof(*params) * arg->num_params); + if (!params) { + arg->ret = TEEC_ERROR_OUT_OF_MEMORY; + return; + } + + ret = msg_param_to_param(params, arg->num_params, arg->params); + if (ret) { + arg->ret = TEEC_ERROR_BAD_PARAMETERS; + arg->ret_origin = TEEC_ORIGIN_COMMS; + goto out; + } + + arg->ret = optee_call_supp(dev, arg->cmd, arg->num_params, params); + + ret = param_to_msg_param(params, arg->num_params, arg->params); + if (ret) { + arg->ret = TEEC_ERROR_GENERIC; + arg->ret_origin = TEEC_ORIGIN_COMMS; + } +out: + k_free(params); +} + +static uint32_t handle_func_rpc_call(const struct device *dev, struct tee_shm *shm, + void **pages) +{ + struct optee_msg_arg *arg = shm->addr; + + switch (arg->cmd) { + case OPTEE_RPC_CMD_SHM_ALLOC: + free_shm_pages(pages); + handle_cmd_alloc(dev, arg, pages); + break; + case OPTEE_RPC_CMD_SHM_FREE: + handle_cmd_free(dev, arg); + break; + case OPTEE_RPC_CMD_GET_TIME: + handle_cmd_get_time(dev, arg); + break; + case OPTEE_RPC_CMD_NOTIFICATION: + handle_cmd_notify(dev, arg); + break; + case OPTEE_RPC_CMD_SUSPEND: + handle_cmd_wait(dev, arg); + break; + case OPTEE_RPC_CMD_I2C_TRANSFER: + /* TODO: i2c transfer case is not implemented right now */ + return TEEC_ERROR_NOT_IMPLEMENTED; + default: + handle_rpc_supp_cmd(dev, arg); + break; + } + + return OPTEE_SMC_CALL_RETURN_FROM_RPC; +} + +static void handle_rpc_call(const struct device *dev, struct optee_rpc_param *param, + void **pages) +{ + struct tee_shm *shm = NULL; + uint32_t res = OPTEE_SMC_CALL_RETURN_FROM_RPC; + + switch (OPTEE_SMC_RETURN_GET_RPC_FUNC(param->a0)) { + case OPTEE_SMC_RPC_FUNC_ALLOC: + if (!tee_add_shm(dev, NULL, OPTEE_MSG_NONCONTIG_PAGE_SIZE, + param->a1, + TEE_SHM_ALLOC, &shm)) { + u64_to_regs((uint64_t)k_mem_phys_addr(shm->addr), ¶m->a1, ¶m->a2); + u64_to_regs((uint64_t)shm, ¶m->a4, ¶m->a5); + } else { + param->a1 = 0; + param->a2 = 0; + param->a4 = 0; + param->a5 = 0; + } + break; + case OPTEE_SMC_RPC_FUNC_FREE: + shm = (struct tee_shm *)regs_to_u64(param->a1, param->a2); + tee_rm_shm(dev, shm); + break; + case OPTEE_SMC_RPC_FUNC_FOREIGN_INTR: + /* Foreign interrupt was raised */ + break; + case OPTEE_SMC_RPC_FUNC_CMD: + shm = (struct tee_shm *)regs_to_u64(param->a1, param->a2); + res = handle_func_rpc_call(dev, shm, pages); + break; + default: + break; + } + + param->a0 = res; +} + +static int optee_call(const struct device *dev, struct optee_msg_arg *arg) +{ + struct optee_driver_data *data = (struct optee_driver_data *)dev->data; + struct optee_rpc_param param = { + .a0 = OPTEE_SMC_CALL_WITH_ARG + }; + void *pages = NULL; + + u64_to_regs((uint64_t)k_mem_phys_addr(arg), ¶m.a1, ¶m.a2); + while (true) { + struct arm_smccc_res res; + + data->smc_call(param.a0, param.a1, param.a2, param.a3, + param.a4, param.a5, param.a6, param.a7, &res); + + if (OPTEE_SMC_RETURN_IS_RPC(res.a0)) { + param.a0 = res.a0; + param.a1 = res.a1; + param.a2 = res.a2; + param.a3 = res.a3; + handle_rpc_call(dev, ¶m, &pages); + } else { + free_shm_pages(&pages); + return res.a0 == OPTEE_SMC_RETURN_OK ? TEEC_SUCCESS : + TEEC_ERROR_BAD_PARAMETERS; + } + } +} + +static int optee_get_version(const struct device *dev, struct tee_version_info *info) +{ + if (!info) { + return -EINVAL; + } + + /* + * TODO Version and capabilities should be requested from + * OP-TEE OS. + */ + + info->impl_id = TEE_IMPL_ID_OPTEE; + info->impl_caps = TEE_OPTEE_CAP_TZ; + info->gen_caps = TEE_GEN_CAP_GP | TEE_GEN_CAP_REG_MEM; + + return 0; +} + +static int optee_close_session(const struct device *dev, uint32_t session_id) +{ + int rc; + struct tee_shm *shm; + struct optee_msg_arg *marg; + + rc = tee_add_shm(dev, NULL, OPTEE_MSG_NONCONTIG_PAGE_SIZE, + OPTEE_MSG_GET_ARG_SIZE(0), + TEE_SHM_ALLOC, &shm); + if (rc) { + LOG_ERR("Unable to get shared memory, rc = %d", rc); + return rc; + } + + marg = shm->addr; + marg->num_params = 0; + marg->cmd = OPTEE_MSG_CMD_CLOSE_SESSION; + marg->session = session_id; + + rc = optee_call(dev, marg); + + if (tee_rm_shm(dev, shm)) { + LOG_ERR("Unable to free shared memory"); + } + + return rc; +} + +static int optee_open_session(const struct device *dev, struct tee_open_session_arg *arg, + unsigned int num_param, struct tee_param *param, + uint32_t *session_id) +{ + int rc, ret; + struct tee_shm *shm; + struct optee_msg_arg *marg; + + if (!arg || !session_id) { + return -EINVAL; + } + + rc = tee_add_shm(dev, NULL, OPTEE_MSG_NONCONTIG_PAGE_SIZE, + OPTEE_MSG_GET_ARG_SIZE(num_param + 2), + TEE_SHM_ALLOC, &shm); + if (rc) { + LOG_ERR("Unable to get shared memory, rc = %d", rc); + return rc; + } + + marg = shm->addr; + memset(marg, 0, OPTEE_MSG_GET_ARG_SIZE(num_param + 2)); + + marg->num_params = num_param + 2; + marg->cmd = OPTEE_MSG_CMD_OPEN_SESSION; + marg->params[0].attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT | OPTEE_MSG_ATTR_META; + marg->params[1].attr = OPTEE_MSG_ATTR_TYPE_VALUE_INPUT | OPTEE_MSG_ATTR_META; + + memcpy(&marg->params[0].u.value, arg->uuid, sizeof(arg->uuid)); + memcpy(&marg->params[1].u.value, arg->uuid, sizeof(arg->clnt_uuid)); + + marg->params[1].u.value.c = arg->clnt_login; + + rc = param_to_msg_param(param, num_param, marg->params + 2); + if (rc) { + goto out; + } + + arg->ret = optee_call(dev, marg); + if (arg->ret) { + arg->ret_origin = TEEC_ORIGIN_COMMS; + goto out; + } + + rc = msg_param_to_param(param, num_param, marg->params); + if (rc) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + /* + * Ret is needed here only to print an error. Param conversion error + * should be returned from the function. + */ + ret = optee_close_session(dev, marg->session); + if (ret) { + LOG_ERR("Unable to close session: %d", ret); + } + goto out; + } + + *session_id = marg->session; + + arg->ret = marg->ret; + arg->ret_origin = marg->ret_origin; +out: + ret = tee_rm_shm(dev, shm); + if (ret) { + LOG_ERR("Unable to free shared memory"); + } + + return (rc) ? rc : ret; +} + +static int optee_cancel(const struct device *dev, uint32_t session_id, uint32_t cancel_id) +{ + int rc; + struct tee_shm *shm; + struct optee_msg_arg *marg; + + rc = tee_add_shm(dev, NULL, OPTEE_MSG_NONCONTIG_PAGE_SIZE, + OPTEE_MSG_GET_ARG_SIZE(0), + TEE_SHM_ALLOC, &shm); + if (rc) { + LOG_ERR("Unable to get shared memory, rc = %d", rc); + return rc; + } + + marg = shm->addr; + marg->num_params = 0; + marg->cmd = OPTEE_MSG_CMD_CANCEL; + marg->cancel_id = cancel_id; + marg->session = session_id; + + rc = optee_call(dev, marg); + + if (tee_rm_shm(dev, shm)) { + LOG_ERR("Unable to free shared memory"); + } + + return rc; +} + +static int optee_invoke_func(const struct device *dev, struct tee_invoke_func_arg *arg, + unsigned int num_param, struct tee_param *param) +{ + int rc, ret; + struct tee_shm *shm; + struct optee_msg_arg *marg; + + if (!arg) { + return -EINVAL; + } + + rc = tee_add_shm(dev, NULL, OPTEE_MSG_NONCONTIG_PAGE_SIZE, + OPTEE_MSG_GET_ARG_SIZE(num_param), + TEE_SHM_ALLOC, &shm); + if (rc) { + LOG_ERR("Unable to get shared memory, rc = %d", rc); + return rc; + } + + marg = shm->addr; + memset(marg, 0, OPTEE_MSG_GET_ARG_SIZE(num_param)); + + marg->num_params = num_param; + marg->cmd = OPTEE_MSG_CMD_INVOKE_COMMAND; + marg->func = arg->func; + marg->session = arg->session; + + rc = param_to_msg_param(param, num_param, marg->params); + if (rc) { + goto out; + } + + arg->ret = optee_call(dev, marg); + if (arg->ret) { + arg->ret_origin = TEEC_ORIGIN_COMMS; + goto out; + } + + rc = msg_param_to_param(param, num_param, marg->params); + if (rc) { + arg->ret = TEEC_ERROR_COMMUNICATION; + arg->ret_origin = TEEC_ORIGIN_COMMS; + goto out; + } + + arg->ret = marg->ret; + arg->ret_origin = marg->ret_origin; +out: + ret = tee_rm_shm(dev, shm); + if (ret) { + LOG_ERR("Unable to free shared memory"); + } + + return (rc) ? rc : ret; +} + +static void *optee_construct_page_list(void *buf, uint32_t len, uint64_t *phys_buf) +{ + const size_t page_size = OPTEE_MSG_NONCONTIG_PAGE_SIZE; + const size_t num_pages_in_pl = OPTEE_NUMBER_OF_ADDR_PER_PAGE - 1; + uint32_t page_offset = (uintptr_t)buf & (page_size - 1); + + uint8_t *buf_page; + uint32_t num_pages; + uint32_t list_size; + + /* see description of OPTEE_MSG_ATTR_NONCONTIG */ + struct { + uint64_t pages[OPTEE_NUMBER_OF_ADDR_PER_PAGE - 1]; + uint64_t next_page; + } *pl; + + BUILD_ASSERT(sizeof(*pl) == OPTEE_MSG_NONCONTIG_PAGE_SIZE); + + num_pages = ROUND_UP(page_offset + len, page_size) / page_size; + list_size = DIV_ROUND_UP(num_pages, num_pages_in_pl) * page_size; + + pl = k_aligned_alloc(page_size, list_size); + if (!pl) { + return NULL; + } + + memset(pl, 0, list_size); + + buf_page = (uint8_t *)ROUND_DOWN((uintptr_t)buf, page_size); + + for (uint32_t pl_idx = 0; pl_idx < list_size / page_size; pl_idx++) { + for (uint32_t page_idx = 0; num_pages && page_idx < num_pages_in_pl; page_idx++) { + pl[pl_idx].pages[page_idx] = k_mem_phys_addr(buf_page); + buf_page += page_size; + num_pages--; + } + + if (!num_pages) { + break; + } + + pl[pl_idx].next_page = k_mem_phys_addr(pl + 1); + } + + /* 12 least significant bits of optee_msg_param.u.tmem.buf_ptr should hold page offset + * of user buffer + */ + *phys_buf = k_mem_phys_addr(pl) | page_offset; + + return pl; +} + +static int optee_shm_register(const struct device *dev, struct tee_shm *shm) +{ + struct tee_shm *shm_arg; + struct optee_msg_arg *msg_arg; + void *pl; + uint64_t pl_phys_and_offset; + int rc; + + rc = tee_add_shm(dev, NULL, OPTEE_MSG_NONCONTIG_PAGE_SIZE, OPTEE_MSG_GET_ARG_SIZE(1), + TEE_SHM_ALLOC, &shm_arg); + if (rc) { + return rc; + } + + msg_arg = shm_arg->addr; + + memset(msg_arg, 0, OPTEE_MSG_GET_ARG_SIZE(1)); + + pl = optee_construct_page_list(shm->addr, shm->size, &pl_phys_and_offset); + if (!pl) { + rc = -ENOMEM; + goto out; + } + + /* for this command op-tee os should support CFG_CORE_DYN_SHM */ + msg_arg->cmd = OPTEE_MSG_CMD_REGISTER_SHM; + /* op-tee OS ingnore this cmd in case when TYPE_TMEM_OUTPUT and NONCONTIG aren't set */ + msg_arg->params->attr = OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT | OPTEE_MSG_ATTR_NONCONTIG; + msg_arg->num_params = 1; + msg_arg->params->u.tmem.buf_ptr = pl_phys_and_offset; + msg_arg->params->u.tmem.shm_ref = (uint64_t)shm; + msg_arg->params->u.tmem.size = shm->size; + + if (optee_call(dev, msg_arg)) { + rc = -EINVAL; + } + + k_free(pl); +out: + tee_rm_shm(dev, shm_arg); + + return rc; +} + +static int optee_shm_unregister(const struct device *dev, struct tee_shm *shm) +{ + struct tee_shm *shm_arg; + struct optee_msg_arg *msg_arg; + int rc; + + rc = tee_add_shm(dev, NULL, OPTEE_MSG_NONCONTIG_PAGE_SIZE, OPTEE_MSG_GET_ARG_SIZE(1), + TEE_SHM_ALLOC, &shm_arg); + if (rc) { + return rc; + } + + msg_arg = shm_arg->addr; + + memset(msg_arg, 0, OPTEE_MSG_GET_ARG_SIZE(1)); + + msg_arg->cmd = OPTEE_MSG_CMD_UNREGISTER_SHM; + msg_arg->num_params = 1; + msg_arg->params[0].attr = OPTEE_MSG_ATTR_TYPE_RMEM_INPUT; + msg_arg->params[0].u.rmem.shm_ref = (uint64_t)shm; + + if (optee_call(dev, msg_arg)) { + rc = -EINVAL; + } + + tee_rm_shm(dev, shm_arg); + return rc; +} + +static int optee_suppl_recv(const struct device *dev, uint32_t *func, unsigned int *num_params, + struct tee_param *param) +{ + struct optee_driver_data *data = (struct optee_driver_data *)dev->data; + struct optee_supp *supp = &data->supp; + struct optee_supp_req *req = NULL; + + while (true) { + k_mutex_lock(&supp->mutex, K_FOREVER); + req = (struct optee_supp_req *)sys_dlist_peek_head(&supp->reqs); + + if (req) { + if (supp->current) { + LOG_ERR("Concurrent supp_recv calls are not supported"); + k_mutex_unlock(&supp->mutex); + return -EBUSY; + } + + if (*num_params < req->num_params) { + LOG_ERR("Not enough space for params, need at least %lu", + req->num_params); + k_mutex_unlock(&supp->mutex); + return -EINVAL; + } + + supp->current = req; + sys_dlist_remove(&req->link); + } + k_mutex_unlock(&supp->mutex); + + if (req) { + break; + } + + k_sem_take(&supp->reqs_c, K_FOREVER); + } + + *func = req->func; + *num_params = req->num_params; + memcpy(param, req->param, sizeof(struct tee_param) * req->num_params); + + return 0; +} + +static int optee_suppl_send(const struct device *dev, unsigned int ret, unsigned int num_params, + struct tee_param *param) +{ + struct optee_driver_data *data = (struct optee_driver_data *)dev->data; + struct optee_supp *supp = &data->supp; + struct optee_supp_req *req = NULL; + size_t n; + + k_mutex_lock(&supp->mutex, K_FOREVER); + if (supp->current && num_params >= supp->current->num_params) { + req = supp->current; + supp->current = NULL; + } else { + LOG_ERR("Invalid number of parameters, expected %lu got %u", req->num_params, + num_params); + } + k_mutex_unlock(&supp->mutex); + + if (!req) { + return -EINVAL; + } + + /* Update out and in/out parameters */ + for (n = 0; n < req->num_params; n++) { + struct tee_param *p = req->param + n; + + switch (p->attr & TEE_PARAM_ATTR_TYPE_MASK) { + case TEE_PARAM_ATTR_TYPE_VALUE_OUTPUT: + case TEE_PARAM_ATTR_TYPE_VALUE_INOUT: + p->a = param[n].a; + p->b = param[n].b; + p->c = param[n].c; + break; + case TEE_PARAM_ATTR_TYPE_MEMREF_OUTPUT: + case TEE_PARAM_ATTR_TYPE_MEMREF_INOUT: + LOG_WRN("Memref params are not fully tested"); + p->a = param[n].a; + p->b = param[n].b; + p->c = param[n].c; + break; + default: + break; + } + } + req->ret = ret; + + /* Let the requesting thread continue */ + k_mutex_lock(&supp->mutex, K_FOREVER); + supp->current = NULL; + k_mutex_unlock(&supp->mutex); + k_sem_give(&req->complete); + + return 0; +} + +static int set_optee_method(const struct device *dev) +{ + const struct optee_driver_config *conf = dev->config; + struct optee_driver_data *data = dev->data; + + if (!strcmp("hvc", conf->method)) { + data->smc_call = optee_smccc_hvc; + } else if (!strcmp("smc", conf->method)) { + data->smc_call = optee_smccc_smc; + } else { + LOG_ERR("Invalid smc_call method"); + return -EINVAL; + } + + return 0; +} + +static bool optee_check_uid(const struct device *dev) +{ + struct arm_smccc_res res; + struct optee_driver_data *data = (struct optee_driver_data *)dev->data; + + data->smc_call(OPTEE_SMC_CALLS_UID, 0, 0, 0, 0, 0, 0, 0, &res); + + if (res.a0 == OPTEE_MSG_UID_0 && res.a1 == OPTEE_MSG_UID_1 && + res.a2 == OPTEE_MSG_UID_2 && res.a3 == OPTEE_MSG_UID_3) { + return true; + } + + return false; +} + +static void optee_get_revision(const struct device *dev) +{ + struct optee_driver_data *data = (struct optee_driver_data *)dev->data; + struct arm_smccc_res res = { 0 }; + + data->smc_call(OPTEE_SMC_CALL_GET_OS_REVISION, 0, 0, 0, 0, 0, 0, 0, &res); + + if (res.a2) { + LOG_INF("OPTEE revision %lu.%lu (%08lx)", res.a0, + res.a1, res.a2); + } else { + LOG_INF("OPTEE revision %lu.%lu", res.a0, res.a1); + } +} + +static bool optee_exchange_caps(const struct device *dev, unsigned long *sec_caps) +{ + struct optee_driver_data *data = (struct optee_driver_data *)dev->data; + struct arm_smccc_res res = { 0 }; + unsigned long a1 = 0; + + if (!IS_ENABLED(CONFIG_SMP) || arch_num_cpus() == 1) { + a1 |= OPTEE_SMC_NSEC_CAP_UNIPROCESSOR; + } + + data->smc_call(OPTEE_SMC_EXCHANGE_CAPABILITIES, a1, 0, 0, 0, 0, 0, 0, &res); + + if (res.a0 != OPTEE_SMC_RETURN_OK) { + return false; + } + + *sec_caps = res.a1; + return true; +} + +static int optee_init(const struct device *dev) +{ + struct optee_driver_data *data = dev->data; + + if (set_optee_method(dev)) { + return -ENOTSUP; + } + + sys_dlist_init(&data->notif); + k_mutex_init(&data->supp.mutex); + k_sem_init(&data->supp.reqs_c, 0, 1); + sys_dlist_init(&data->supp.reqs); + + if (!optee_check_uid(dev)) { + LOG_ERR("OPTEE API UID mismatch"); + return -EINVAL; + } + + optee_get_revision(dev); + + if (!optee_exchange_caps(dev, &data->sec_caps)) { + LOG_ERR("OPTEE capabilities exchange failed\n"); + return -EINVAL; + } + + if (!(data->sec_caps & OPTEE_SMC_SEC_CAP_DYNAMIC_SHM)) { + LOG_ERR("OPTEE does not support dynamic shared memory"); + return -ENOTSUP; + } + + return 0; +} + +static const struct tee_driver_api optee_driver_api = { + .get_version = optee_get_version, + .open_session = optee_open_session, + .close_session = optee_close_session, + .cancel = optee_cancel, + .invoke_func = optee_invoke_func, + .shm_register = optee_shm_register, + .shm_unregister = optee_shm_unregister, + .suppl_recv = optee_suppl_recv, + .suppl_send = optee_suppl_send, +}; + +/* + * Bitmap of the ongoing notificatons, received from OP-TEE. Maximum number is + * CONFIG_OPTEE_MAX_NOTIF. This bitmap is needed to handle case when SEND command + * was received before WAIT command from OP-TEE. In this case WAIT will not create + * locks. + */ +#define OPTEE_DT_DEVICE_INIT(inst) \ + SYS_BITARRAY_DEFINE_STATIC(notif_bitmap_##inst, CONFIG_OPTEE_MAX_NOTIF); \ + \ + static struct optee_driver_config optee_config_##inst = { \ + .method = DT_INST_PROP(inst, method) \ + }; \ + \ + static struct optee_driver_data optee_data_##inst = { \ + .notif_bitmap = ¬if_bitmap_##inst \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, optee_init, NULL, &optee_data_##inst, \ + &optee_config_##inst, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + &optee_driver_api); \ + +DT_INST_FOREACH_STATUS_OKAY(OPTEE_DT_DEVICE_INIT)