linux-stable/drivers/tee/amdtee/call.c
Rijo Thomas 9f015b3765 tee: amdtee: unload TA only when its refcount becomes 0
Same Trusted Application (TA) can be loaded in multiple TEE contexts.

If it is a single instance TA, the TA should not get unloaded from AMD
Secure Processor, while it is still in use in another TEE context.

Therefore reference count TA and unload it when the count becomes zero.

Fixes: 757cc3e9ff ("tee: add AMD-TEE driver")
Reviewed-by: Devaraj Rangasamy <Devaraj.Rangasamy@amd.com>
Signed-off-by: Rijo Thomas <Rijo-john.Thomas@amd.com>
Acked-by: Dan Carpenter <dan.carpenter@oracle.com>
Signed-off-by: Jens Wiklander <jens.wiklander@linaro.org>
2021-05-05 13:00:11 +02:00

447 lines
10 KiB
C

// SPDX-License-Identifier: MIT
/*
* Copyright 2019 Advanced Micro Devices, Inc.
*/
#include <linux/device.h>
#include <linux/tee.h>
#include <linux/tee_drv.h>
#include <linux/psp-tee.h>
#include <linux/slab.h>
#include <linux/psp-sev.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 struct list_head ta_list = LIST_HEAD_INIT(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 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;
}