1
linux/drivers/tee/amdtee/call.c
Sumit Garg 0439fcff30 tee: Refactor TEE subsystem header files
Since commit 25559c22ce ("tee: add kernel internal client interface"),
it has been a common include/linux/tee_drv.h header file which is shared
to hold TEE subsystem internal bits along with the APIs exposed to the
TEE client drivers. However, this practice is prone to TEE subsystem
internal APIs abuse and especially so with the new TEE implementation
drivers being added to reuse existing functionality.

In order to address this split TEE subsystem internal bits as a separate
header file: include/linux/tee_core.h which should be the one used by
TEE implementation drivers. With that include/linux/tee_drv.h lists only
APIs exposed by TEE subsystem to the TEE client drivers.

Signed-off-by: Sumit Garg <sumit.garg@linaro.org>
Signed-off-by: Balint Dobszay <balint.dobszay@arm.com>
Signed-off-by: Jens Wiklander <jens.wiklander@linaro.org>
2024-04-03 09:19:31 +02:00

452 lines
11 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright 2019 Advanced Micro Devices, Inc.
*/
#include <linux/device.h>
#include <linux/tee.h>
#include <linux/tee_core.h>
#include <linux/psp-tee.h>
#include <linux/slab.h>
#include <linux/psp.h>
#include "amdtee_if.h"
#include "amdtee_private.h"
static int tee_params_to_amd_params(struct tee_param *tee, u32 count,
struct tee_operation *amd)
{
int i, ret = 0;
u32 type;
if (!count)
return 0;
if (!tee || !amd || count > TEE_MAX_PARAMS)
return -EINVAL;
amd->param_types = 0;
for (i = 0; i < count; i++) {
/* AMD TEE does not support meta parameter */
if (tee[i].attr > TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT)
return -EINVAL;
amd->param_types |= ((tee[i].attr & 0xF) << i * 4);
}
for (i = 0; i < count; i++) {
type = TEE_PARAM_TYPE_GET(amd->param_types, i);
pr_debug("%s: type[%d] = 0x%x\n", __func__, i, type);
if (type == TEE_OP_PARAM_TYPE_INVALID)
return -EINVAL;
if (type == TEE_OP_PARAM_TYPE_NONE)
continue;
/* It is assumed that all values are within 2^32-1 */
if (type > TEE_OP_PARAM_TYPE_VALUE_INOUT) {
u32 buf_id = get_buffer_id(tee[i].u.memref.shm);
amd->params[i].mref.buf_id = buf_id;
amd->params[i].mref.offset = tee[i].u.memref.shm_offs;
amd->params[i].mref.size = tee[i].u.memref.size;
pr_debug("%s: bufid[%d] = 0x%x, offset[%d] = 0x%x, size[%d] = 0x%x\n",
__func__,
i, amd->params[i].mref.buf_id,
i, amd->params[i].mref.offset,
i, amd->params[i].mref.size);
} else {
if (tee[i].u.value.c)
pr_warn("%s: Discarding value c", __func__);
amd->params[i].val.a = tee[i].u.value.a;
amd->params[i].val.b = tee[i].u.value.b;
pr_debug("%s: a[%d] = 0x%x, b[%d] = 0x%x\n", __func__,
i, amd->params[i].val.a,
i, amd->params[i].val.b);
}
}
return ret;
}
static int amd_params_to_tee_params(struct tee_param *tee, u32 count,
struct tee_operation *amd)
{
int i, ret = 0;
u32 type;
if (!count)
return 0;
if (!tee || !amd || count > TEE_MAX_PARAMS)
return -EINVAL;
/* Assumes amd->param_types is valid */
for (i = 0; i < count; i++) {
type = TEE_PARAM_TYPE_GET(amd->param_types, i);
pr_debug("%s: type[%d] = 0x%x\n", __func__, i, type);
if (type == TEE_OP_PARAM_TYPE_INVALID ||
type > TEE_OP_PARAM_TYPE_MEMREF_INOUT)
return -EINVAL;
if (type == TEE_OP_PARAM_TYPE_NONE ||
type == TEE_OP_PARAM_TYPE_VALUE_INPUT ||
type == TEE_OP_PARAM_TYPE_MEMREF_INPUT)
continue;
/*
* It is assumed that buf_id remains unchanged for
* both open_session and invoke_cmd call
*/
if (type > TEE_OP_PARAM_TYPE_MEMREF_INPUT) {
tee[i].u.memref.shm_offs = amd->params[i].mref.offset;
tee[i].u.memref.size = amd->params[i].mref.size;
pr_debug("%s: bufid[%d] = 0x%x, offset[%d] = 0x%x, size[%d] = 0x%x\n",
__func__,
i, amd->params[i].mref.buf_id,
i, amd->params[i].mref.offset,
i, amd->params[i].mref.size);
} else {
/* field 'c' not supported by AMD TEE */
tee[i].u.value.a = amd->params[i].val.a;
tee[i].u.value.b = amd->params[i].val.b;
tee[i].u.value.c = 0;
pr_debug("%s: a[%d] = 0x%x, b[%d] = 0x%x\n",
__func__,
i, amd->params[i].val.a,
i, amd->params[i].val.b);
}
}
return ret;
}
static DEFINE_MUTEX(ta_refcount_mutex);
static LIST_HEAD(ta_list);
static u32 get_ta_refcount(u32 ta_handle)
{
struct amdtee_ta_data *ta_data;
u32 count = 0;
/* Caller must hold a mutex */
list_for_each_entry(ta_data, &ta_list, list_node)
if (ta_data->ta_handle == ta_handle)
return ++ta_data->refcount;
ta_data = kzalloc(sizeof(*ta_data), GFP_KERNEL);
if (ta_data) {
ta_data->ta_handle = ta_handle;
ta_data->refcount = 1;
count = ta_data->refcount;
list_add(&ta_data->list_node, &ta_list);
}
return count;
}
static u32 put_ta_refcount(u32 ta_handle)
{
struct amdtee_ta_data *ta_data;
u32 count = 0;
/* Caller must hold a mutex */
list_for_each_entry(ta_data, &ta_list, list_node)
if (ta_data->ta_handle == ta_handle) {
count = --ta_data->refcount;
if (count == 0) {
list_del(&ta_data->list_node);
kfree(ta_data);
break;
}
}
return count;
}
int handle_unload_ta(u32 ta_handle)
{
struct tee_cmd_unload_ta cmd = {0};
u32 status, count;
int ret;
if (!ta_handle)
return -EINVAL;
mutex_lock(&ta_refcount_mutex);
count = put_ta_refcount(ta_handle);
if (count) {
pr_debug("unload ta: not unloading %u count %u\n",
ta_handle, count);
ret = -EBUSY;
goto unlock;
}
cmd.ta_handle = ta_handle;
ret = psp_tee_process_cmd(TEE_CMD_ID_UNLOAD_TA, (void *)&cmd,
sizeof(cmd), &status);
if (!ret && status != 0) {
pr_err("unload ta: status = 0x%x\n", status);
ret = -EBUSY;
} else {
pr_debug("unloaded ta handle %u\n", ta_handle);
}
unlock:
mutex_unlock(&ta_refcount_mutex);
return ret;
}
int handle_close_session(u32 ta_handle, u32 info)
{
struct tee_cmd_close_session cmd = {0};
u32 status;
int ret;
if (ta_handle == 0)
return -EINVAL;
cmd.ta_handle = ta_handle;
cmd.session_info = info;
ret = psp_tee_process_cmd(TEE_CMD_ID_CLOSE_SESSION, (void *)&cmd,
sizeof(cmd), &status);
if (!ret && status != 0) {
pr_err("close session: status = 0x%x\n", status);
ret = -EBUSY;
}
return ret;
}
void handle_unmap_shmem(u32 buf_id)
{
struct tee_cmd_unmap_shared_mem cmd = {0};
u32 status;
int ret;
cmd.buf_id = buf_id;
ret = psp_tee_process_cmd(TEE_CMD_ID_UNMAP_SHARED_MEM, (void *)&cmd,
sizeof(cmd), &status);
if (!ret)
pr_debug("unmap shared memory: buf_id %u status = 0x%x\n",
buf_id, status);
}
int handle_invoke_cmd(struct tee_ioctl_invoke_arg *arg, u32 sinfo,
struct tee_param *p)
{
struct tee_cmd_invoke_cmd cmd = {0};
int ret;
if (!arg || (!p && arg->num_params))
return -EINVAL;
arg->ret_origin = TEEC_ORIGIN_COMMS;
if (arg->session == 0) {
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
return -EINVAL;
}
ret = tee_params_to_amd_params(p, arg->num_params, &cmd.op);
if (ret) {
pr_err("invalid Params. Abort invoke command\n");
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
return ret;
}
cmd.ta_handle = get_ta_handle(arg->session);
cmd.cmd_id = arg->func;
cmd.session_info = sinfo;
ret = psp_tee_process_cmd(TEE_CMD_ID_INVOKE_CMD, (void *)&cmd,
sizeof(cmd), &arg->ret);
if (ret) {
arg->ret = TEEC_ERROR_COMMUNICATION;
} else {
ret = amd_params_to_tee_params(p, arg->num_params, &cmd.op);
if (unlikely(ret)) {
pr_err("invoke command: failed to copy output\n");
arg->ret = TEEC_ERROR_GENERIC;
return ret;
}
arg->ret_origin = cmd.return_origin;
pr_debug("invoke command: RO = 0x%x ret = 0x%x\n",
arg->ret_origin, arg->ret);
}
return ret;
}
int handle_map_shmem(u32 count, struct shmem_desc *start, u32 *buf_id)
{
struct tee_cmd_map_shared_mem *cmd;
phys_addr_t paddr;
int ret, i;
u32 status;
if (!count || !start || !buf_id)
return -EINVAL;
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
if (!cmd)
return -ENOMEM;
/* Size must be page aligned */
for (i = 0; i < count ; i++) {
if (!start[i].kaddr || (start[i].size & (PAGE_SIZE - 1))) {
ret = -EINVAL;
goto free_cmd;
}
if ((u64)start[i].kaddr & (PAGE_SIZE - 1)) {
pr_err("map shared memory: page unaligned. addr 0x%llx",
(u64)start[i].kaddr);
ret = -EINVAL;
goto free_cmd;
}
}
cmd->sg_list.count = count;
/* Create buffer list */
for (i = 0; i < count ; i++) {
paddr = __psp_pa(start[i].kaddr);
cmd->sg_list.buf[i].hi_addr = upper_32_bits(paddr);
cmd->sg_list.buf[i].low_addr = lower_32_bits(paddr);
cmd->sg_list.buf[i].size = start[i].size;
cmd->sg_list.size += cmd->sg_list.buf[i].size;
pr_debug("buf[%d]:hi addr = 0x%x\n", i,
cmd->sg_list.buf[i].hi_addr);
pr_debug("buf[%d]:low addr = 0x%x\n", i,
cmd->sg_list.buf[i].low_addr);
pr_debug("buf[%d]:size = 0x%x\n", i, cmd->sg_list.buf[i].size);
pr_debug("list size = 0x%x\n", cmd->sg_list.size);
}
*buf_id = 0;
ret = psp_tee_process_cmd(TEE_CMD_ID_MAP_SHARED_MEM, (void *)cmd,
sizeof(*cmd), &status);
if (!ret && !status) {
*buf_id = cmd->buf_id;
pr_debug("mapped buffer ID = 0x%x\n", *buf_id);
} else {
pr_err("map shared memory: status = 0x%x\n", status);
ret = -ENOMEM;
}
free_cmd:
kfree(cmd);
return ret;
}
int handle_open_session(struct tee_ioctl_open_session_arg *arg, u32 *info,
struct tee_param *p)
{
struct tee_cmd_open_session cmd = {0};
int ret;
if (!arg || !info || (!p && arg->num_params))
return -EINVAL;
arg->ret_origin = TEEC_ORIGIN_COMMS;
if (arg->session == 0) {
arg->ret = TEEC_ERROR_GENERIC;
return -EINVAL;
}
ret = tee_params_to_amd_params(p, arg->num_params, &cmd.op);
if (ret) {
pr_err("invalid Params. Abort open session\n");
arg->ret = TEEC_ERROR_BAD_PARAMETERS;
return ret;
}
cmd.ta_handle = get_ta_handle(arg->session);
*info = 0;
ret = psp_tee_process_cmd(TEE_CMD_ID_OPEN_SESSION, (void *)&cmd,
sizeof(cmd), &arg->ret);
if (ret) {
arg->ret = TEEC_ERROR_COMMUNICATION;
} else {
ret = amd_params_to_tee_params(p, arg->num_params, &cmd.op);
if (unlikely(ret)) {
pr_err("open session: failed to copy output\n");
arg->ret = TEEC_ERROR_GENERIC;
return ret;
}
arg->ret_origin = cmd.return_origin;
*info = cmd.session_info;
pr_debug("open session: session info = 0x%x\n", *info);
}
pr_debug("open session: ret = 0x%x RO = 0x%x\n", arg->ret,
arg->ret_origin);
return ret;
}
int handle_load_ta(void *data, u32 size, struct tee_ioctl_open_session_arg *arg)
{
struct tee_cmd_unload_ta unload_cmd = {};
struct tee_cmd_load_ta load_cmd = {};
phys_addr_t blob;
int ret;
if (size == 0 || !data || !arg)
return -EINVAL;
blob = __psp_pa(data);
if (blob & (PAGE_SIZE - 1)) {
pr_err("load TA: page unaligned. blob 0x%llx", blob);
return -EINVAL;
}
load_cmd.hi_addr = upper_32_bits(blob);
load_cmd.low_addr = lower_32_bits(blob);
load_cmd.size = size;
mutex_lock(&ta_refcount_mutex);
ret = psp_tee_process_cmd(TEE_CMD_ID_LOAD_TA, (void *)&load_cmd,
sizeof(load_cmd), &arg->ret);
if (ret) {
arg->ret_origin = TEEC_ORIGIN_COMMS;
arg->ret = TEEC_ERROR_COMMUNICATION;
} else {
arg->ret_origin = load_cmd.return_origin;
if (arg->ret == TEEC_SUCCESS) {
ret = get_ta_refcount(load_cmd.ta_handle);
if (!ret) {
arg->ret_origin = TEEC_ORIGIN_COMMS;
arg->ret = TEEC_ERROR_OUT_OF_MEMORY;
/* Unload the TA on error */
unload_cmd.ta_handle = load_cmd.ta_handle;
psp_tee_process_cmd(TEE_CMD_ID_UNLOAD_TA,
(void *)&unload_cmd,
sizeof(unload_cmd), &ret);
} else {
set_session_id(load_cmd.ta_handle, 0, &arg->session);
}
}
}
mutex_unlock(&ta_refcount_mutex);
pr_debug("load TA: TA handle = 0x%x, RO = 0x%x, ret = 0x%x\n",
load_cmd.ta_handle, arg->ret_origin, arg->ret);
return 0;
}