600 lines
16 KiB
C
Executable File
600 lines
16 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2013-2019 TRUSTONIC LIMITED
|
|
* All Rights Reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/export.h>
|
|
|
|
#include "public/GP/tee_client_api.h"
|
|
#include "public/mc_user.h"
|
|
|
|
#include "main.h"
|
|
#include "mci/mcinq.h" /* TA termination codes */
|
|
#include "client.h"
|
|
#include "protocol.h"
|
|
|
|
/* Macros */
|
|
#define _TEEC_GET_PARAM_TYPE(t, i) (((t) >> (4 * (i))) & 0xF)
|
|
|
|
/* Parameter number */
|
|
#define _TEEC_PARAMETER_NUMBER 4
|
|
|
|
/**teec_shared_memory
|
|
* These error codes are still to be decided by GP and as we do not wish to
|
|
* expose any part of the GP TAF as of yet, for now they will have to live here
|
|
* until we decide what to do about them.
|
|
*/
|
|
#define TEEC_ERROR_TA_LOCKED 0xFFFF0257
|
|
#define TEEC_ERROR_SD_BLOCKED 0xFFFF0258
|
|
#define TEEC_ERROR_TARGET_KILLED 0xFFFF0259
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(operations_wq);
|
|
|
|
static void _lib_uuid_to_array(const struct teec_uuid *uuid, u8 *uuid_array)
|
|
{
|
|
u8 *identifier_cursor = (u8 *)uuid;
|
|
/* offsets and syntax constants. See explanations above */
|
|
#ifdef S_BIG_ENDIAN
|
|
u32 offsets = 0;
|
|
#else
|
|
u32 offsets = 0xF1F1DF13;
|
|
#endif
|
|
u32 i;
|
|
|
|
for (i = 0; i < sizeof(struct teec_uuid); i++) {
|
|
/* Two-digit hex number */
|
|
s32 offset = ((s32)((offsets & 0xF) << 28)) >> 28;
|
|
u8 number = identifier_cursor[offset];
|
|
|
|
offsets >>= 4;
|
|
identifier_cursor++;
|
|
|
|
uuid_array[i] = number;
|
|
}
|
|
}
|
|
|
|
static u32 _teec_to_gp_operation(struct teec_operation *teec_op,
|
|
struct gp_operation *gp_op)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
|
|
for (i = 0; i < _TEEC_PARAMETER_NUMBER; i++) {
|
|
switch (_TEEC_GET_PARAM_TYPE(teec_op->param_types, i)) {
|
|
case TEEC_VALUE_INPUT:
|
|
case TEEC_VALUE_INOUT:
|
|
gp_op->params[i].value.a = teec_op->params[i].value.a;
|
|
gp_op->params[i].value.b = teec_op->params[i].value.b;
|
|
break;
|
|
case TEEC_MEMREF_TEMP_INPUT:
|
|
case TEEC_MEMREF_TEMP_OUTPUT:
|
|
case TEEC_MEMREF_TEMP_INOUT:
|
|
gp_op->params[i].tmpref.buffer =
|
|
(uintptr_t)teec_op->params[i].tmpref.buffer;
|
|
gp_op->params[i].tmpref.size =
|
|
teec_op->params[i].tmpref.size;
|
|
break;
|
|
case TEEC_MEMREF_WHOLE:
|
|
case TEEC_MEMREF_PARTIAL_INPUT:
|
|
case TEEC_MEMREF_PARTIAL_OUTPUT:
|
|
case TEEC_MEMREF_PARTIAL_INOUT:
|
|
gp_op->params[i].memref.offset =
|
|
teec_op->params[i].memref.offset;
|
|
gp_op->params[i].memref.size =
|
|
teec_op->params[i].memref.size;
|
|
gp_op->params[i].memref.parent.buffer =
|
|
(uintptr_t)teec_op->params[i].memref.parent->buffer;
|
|
gp_op->params[i].memref.parent.size =
|
|
teec_op->params[i].memref.parent->size;
|
|
gp_op->params[i].memref.parent.flags =
|
|
teec_op->params[i].memref.parent->flags;
|
|
break;
|
|
case TEEC_NONE:
|
|
case TEEC_VALUE_OUTPUT:
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
}
|
|
gp_op->param_types = teec_op->param_types;
|
|
return ret;
|
|
}
|
|
|
|
static void _teec_from_gp_operation(struct gp_operation *gp_op,
|
|
struct teec_operation *teec_op)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < _TEEC_PARAMETER_NUMBER; i++) {
|
|
switch (_TEEC_GET_PARAM_TYPE(gp_op->param_types, i)) {
|
|
case TEEC_VALUE_OUTPUT:
|
|
case TEEC_VALUE_INOUT:
|
|
teec_op->params[i].value.a = gp_op->params[i].value.a;
|
|
teec_op->params[i].value.b = gp_op->params[i].value.b;
|
|
break;
|
|
case TEEC_MEMREF_TEMP_INPUT:
|
|
case TEEC_MEMREF_TEMP_OUTPUT:
|
|
case TEEC_MEMREF_TEMP_INOUT:
|
|
teec_op->params[i].tmpref.size =
|
|
gp_op->params[i].tmpref.size;
|
|
break;
|
|
case TEEC_MEMREF_WHOLE:
|
|
break;
|
|
case TEEC_MEMREF_PARTIAL_INPUT:
|
|
case TEEC_MEMREF_PARTIAL_OUTPUT:
|
|
case TEEC_MEMREF_PARTIAL_INOUT:
|
|
teec_op->params[i].memref.size =
|
|
gp_op->params[i].memref.size;
|
|
break;
|
|
case TEEC_NONE:
|
|
case TEEC_VALUE_INPUT:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static u32 _teec_convert_error(int errno)
|
|
{
|
|
switch (errno) {
|
|
case ENOENT:
|
|
return TEEC_ERROR_ITEM_NOT_FOUND;
|
|
case EACCES:
|
|
return TEEC_ERROR_ACCESS_DENIED;
|
|
case EINVAL:
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
case ENOSPC:
|
|
return TEEC_ERROR_OUT_OF_MEMORY;
|
|
case ECONNREFUSED:
|
|
return TEEC_ERROR_SD_BLOCKED;
|
|
case ECONNABORTED:
|
|
return TEEC_ERROR_TA_LOCKED;
|
|
case ECONNRESET:
|
|
return TEEC_ERROR_TARGET_KILLED;
|
|
case EBUSY:
|
|
return TEEC_ERROR_BUSY;
|
|
case EKEYREJECTED:
|
|
return TEEC_ERROR_SECURITY;
|
|
case ETIME:
|
|
return TEEC_ERROR_TARGET_DEAD;
|
|
default:
|
|
return TEEC_ERROR_GENERIC;
|
|
}
|
|
}
|
|
|
|
/* teec_initialize_context: TEEC_SUCCESS, Another error code from Table 4-2 */
|
|
u32 teec_initialize_context(const char *name, struct teec_context *context)
|
|
{
|
|
struct tee_client *client;
|
|
int ret;
|
|
(void)name;
|
|
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
if (!context) {
|
|
mc_dev_devel("context is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
|
|
/* Make sure TEE was started */
|
|
ret = mc_wait_tee_start();
|
|
if (ret) {
|
|
mc_dev_err(ret, "TEE failed to start, now or in the past");
|
|
return TEEC_ERROR_BAD_STATE;
|
|
}
|
|
|
|
/* Create client */
|
|
client = client_create(true, protocol_vm_id());
|
|
if (!client)
|
|
return TEEC_ERROR_OUT_OF_MEMORY;
|
|
|
|
/* Store client in context */
|
|
context->imp.client = client;
|
|
|
|
return TEEC_SUCCESS;
|
|
}
|
|
EXPORT_SYMBOL(teec_initialize_context);
|
|
|
|
/*
|
|
* The implementation of this function MUST NOT be able to fail: after this
|
|
* function returns the Client Application must be able to consider that the
|
|
* Context has been closed
|
|
*/
|
|
void teec_finalize_context(struct teec_context *context)
|
|
{
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
/* The parameter context MUST point to an initialized TEE Context */
|
|
if (!context) {
|
|
mc_dev_devel("context is NULL");
|
|
return;
|
|
}
|
|
|
|
/* The implementation of this function MUST NOT be able to fail: after
|
|
* this function returns the Client Application must be able to
|
|
* consider that the Context has been closed
|
|
*/
|
|
client_close(context->imp.client);
|
|
context->imp.client = NULL;
|
|
}
|
|
EXPORT_SYMBOL(teec_finalize_context);
|
|
|
|
/*
|
|
* If the return_origin is different from TEEC_ORIGIN_TRUSTED_APP, an error code
|
|
* from Table 4-2. If the return_origin is equal to TEEC_ORIGIN_TRUSTED_APP, a
|
|
* return code defined by the protocol between the Client Application and the
|
|
* Trusted Application
|
|
*/
|
|
u32 teec_open_session(struct teec_context *context,
|
|
struct teec_session *session,
|
|
const struct teec_uuid *destination,
|
|
u32 connection_method,
|
|
const void *connection_data,
|
|
struct teec_operation *operation,
|
|
u32 *return_origin)
|
|
{
|
|
struct mc_uuid_t uuid;
|
|
struct mc_identity identity = {0};
|
|
struct tee_client *client = NULL;
|
|
struct gp_operation gp_op;
|
|
struct gp_return gp_ret;
|
|
int ret = 0, timeout;
|
|
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
gp_ret.value = TEEC_SUCCESS;
|
|
if (return_origin)
|
|
*return_origin = TEEC_ORIGIN_API;
|
|
|
|
/* The parameter context MUST point to an initialized TEE Context */
|
|
if (!context) {
|
|
mc_dev_devel("context is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
|
|
if (!context->imp.client) {
|
|
mc_dev_devel("context not initialized");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
client = context->imp.client;
|
|
|
|
if (!session) {
|
|
mc_dev_devel("session is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
|
|
connection_method = TEEC_TT_LOGIN_KERNEL;
|
|
session->imp.active = false;
|
|
|
|
_lib_uuid_to_array(destination, uuid.value);
|
|
|
|
memset(&gp_op, 0, sizeof(gp_op));
|
|
if (operation) {
|
|
operation->imp.session = &session->imp;
|
|
ret = _teec_to_gp_operation(operation, &gp_op);
|
|
if (ret)
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
|
|
identity.login_type = (enum mc_login_type)connection_method;
|
|
|
|
/* Wait for GP loading to be possible, maximum 30s */
|
|
timeout = 30;
|
|
do {
|
|
ret = client_gp_open_session(client, &uuid, &gp_op, &identity,
|
|
&gp_ret, &session->imp.session_id);
|
|
if (ret != -ECHILD ||
|
|
gp_ret.value != TEEC_ERROR_BUSY ||
|
|
gp_ret.origin != TEEC_ORIGIN_TEE)
|
|
break;
|
|
|
|
msleep(1000);
|
|
} while (--timeout);
|
|
|
|
if (ret || gp_ret.value != TEEC_SUCCESS) {
|
|
mc_dev_devel("client_gp_open_session failed(%08x) %08x", ret,
|
|
gp_ret.value);
|
|
if (ret)
|
|
gp_ret.value = _teec_convert_error(-ret);
|
|
else if (return_origin)
|
|
/* Update origin as it's not the API */
|
|
*return_origin = gp_ret.origin;
|
|
} else {
|
|
mc_dev_devel(" created session ID %x", session->imp.session_id);
|
|
session->imp.context = context->imp;
|
|
session->imp.active = true;
|
|
if (operation)
|
|
_teec_from_gp_operation(&gp_op, operation);
|
|
}
|
|
|
|
mc_dev_devel(" %s() = 0x%x", __func__, gp_ret.value);
|
|
return gp_ret.value;
|
|
}
|
|
EXPORT_SYMBOL(teec_open_session);
|
|
|
|
u32 teec_invoke_command(struct teec_session *session,
|
|
u32 command_id,
|
|
struct teec_operation *operation,
|
|
u32 *return_origin)
|
|
{
|
|
struct tee_client *client = NULL;
|
|
struct gp_operation gp_op = {0};
|
|
struct gp_return gp_ret = {0};
|
|
int ret = 0;
|
|
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
gp_ret.value = TEEC_SUCCESS;
|
|
if (return_origin)
|
|
*return_origin = TEEC_ORIGIN_API;
|
|
|
|
if (!session) {
|
|
mc_dev_devel("session is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
|
|
if (!session->imp.active) {
|
|
mc_dev_devel("session is inactive");
|
|
return TEEC_ERROR_BAD_STATE;
|
|
}
|
|
client = session->imp.context.client;
|
|
|
|
if (operation) {
|
|
operation->imp.session = &session->imp;
|
|
if (_teec_to_gp_operation(operation, &gp_op))
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
} else {
|
|
gp_op.param_types = 0;
|
|
}
|
|
|
|
ret = client_gp_invoke_command(client, session->imp.session_id,
|
|
command_id, &gp_op, &gp_ret);
|
|
|
|
if (ret || gp_ret.value != TEEC_SUCCESS) {
|
|
mc_dev_devel("client_gp_invoke_command failed(%08x) %08x", ret,
|
|
gp_ret.value);
|
|
if (ret)
|
|
gp_ret.value = _teec_convert_error(-ret);
|
|
else if (return_origin)
|
|
/* Update origin as it's not the API */
|
|
*return_origin = gp_ret.origin;
|
|
} else if (operation) {
|
|
_teec_from_gp_operation(&gp_op, operation);
|
|
}
|
|
|
|
mc_dev_devel(" %s() = 0x%x", __func__, gp_ret.value);
|
|
return gp_ret.value;
|
|
}
|
|
EXPORT_SYMBOL(teec_invoke_command);
|
|
|
|
void teec_close_session(struct teec_session *session)
|
|
{
|
|
int ret = 0;
|
|
struct tee_client *client = NULL;
|
|
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
/* The implementation MUST do nothing if session is NULL */
|
|
if (!session) {
|
|
mc_dev_devel("session is NULL");
|
|
return;
|
|
}
|
|
client = session->imp.context.client;
|
|
|
|
if (session->imp.active) {
|
|
ret = client_gp_close_session(client, session->imp.session_id);
|
|
|
|
if (ret)
|
|
/* continue even in case of error */
|
|
mc_dev_devel("client_gp_close failed(%08x)", ret);
|
|
|
|
session->imp.active = false;
|
|
}
|
|
|
|
mc_dev_devel(" %s() = 0x%x", __func__, ret);
|
|
}
|
|
EXPORT_SYMBOL(teec_close_session);
|
|
|
|
/*
|
|
* Implementation note. We handle internally 2 kind of pointers : kernel memory
|
|
* (kmalloc, get_pages, ...) and dynamic memory (vmalloc). A global pointer from
|
|
* a kernel module has the same format as a vmalloc buffer. However, our code
|
|
* cannot detect that, so it considers it a kmalloc buffer. The TA trying to use
|
|
* that shared buffer is likely to crash
|
|
*/
|
|
u32 teec_register_shared_memory(struct teec_context *context,
|
|
struct teec_shared_memory *shared_mem)
|
|
{
|
|
struct gp_shared_memory memref;
|
|
struct gp_return gp_ret;
|
|
int ret = 0;
|
|
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
/* The parameter context MUST point to an initialized TEE Context */
|
|
if (!context) {
|
|
mc_dev_devel("context is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
/*
|
|
* The parameter shared_mem MUST point to the Shared Memory structure
|
|
* defining the memory region to register
|
|
*/
|
|
if (!shared_mem) {
|
|
mc_dev_devel("shared_mem is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
/*
|
|
* The buffer field MUST point to the memory region to be shared,
|
|
* and MUST not be NULL
|
|
*/
|
|
if (!shared_mem->buffer) {
|
|
mc_dev_devel("shared_mem->buffer is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
if (shared_mem->flags & ~TEEC_MEM_INOUT) {
|
|
mc_dev_devel("shared_mem->flags is incorrect");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
if (!shared_mem->flags) {
|
|
mc_dev_devel("shared_mem->flags is incorrect");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
|
|
memref.buffer = (uintptr_t)shared_mem->buffer;
|
|
memref.flags = shared_mem->flags;
|
|
memref.size = shared_mem->size;
|
|
ret = client_gp_register_shared_mem(context->imp.client, NULL, NULL,
|
|
&memref, &gp_ret);
|
|
|
|
if (ret)
|
|
return _teec_convert_error(-ret);
|
|
|
|
shared_mem->imp.client = context->imp.client;
|
|
shared_mem->imp.implementation_allocated = false;
|
|
|
|
return TEEC_SUCCESS;
|
|
}
|
|
EXPORT_SYMBOL(teec_register_shared_memory);
|
|
|
|
u32 teec_allocate_shared_memory(struct teec_context *context,
|
|
struct teec_shared_memory *shared_mem)
|
|
{
|
|
struct gp_shared_memory memref;
|
|
struct gp_return gp_ret;
|
|
int ret = 0;
|
|
|
|
/* No connection to "context"? */
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
/* The parameter context MUST point to an initialized TEE Context */
|
|
if (!context) {
|
|
mc_dev_devel("context is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
/*
|
|
* The parameter shared_mem MUST point to the Shared Memory structure
|
|
* defining the memory region to register
|
|
*/
|
|
if (!shared_mem) {
|
|
mc_dev_devel("shared_mem is NULL");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
if (shared_mem->flags & ~TEEC_MEM_INOUT) {
|
|
mc_dev_devel("shared_mem->flags is incorrect");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
if (!shared_mem->flags) {
|
|
mc_dev_devel("shared_mem->flags is incorrect");
|
|
return TEEC_ERROR_BAD_PARAMETERS;
|
|
}
|
|
|
|
shared_mem->buffer = vmalloc(shared_mem->size);
|
|
if (!shared_mem->buffer)
|
|
return TEEC_ERROR_OUT_OF_MEMORY;
|
|
|
|
memref.buffer = (uintptr_t)shared_mem->buffer;
|
|
memref.flags = shared_mem->flags;
|
|
memref.size = shared_mem->size;
|
|
ret = client_gp_register_shared_mem(context->imp.client, NULL, NULL,
|
|
&memref, &gp_ret);
|
|
|
|
if (ret) {
|
|
vfree(shared_mem->buffer);
|
|
shared_mem->buffer = NULL;
|
|
shared_mem->size = 0;
|
|
return _teec_convert_error(-ret);
|
|
}
|
|
|
|
shared_mem->imp.client = context->imp.client;
|
|
shared_mem->imp.implementation_allocated = true;
|
|
|
|
return TEEC_SUCCESS;
|
|
}
|
|
EXPORT_SYMBOL(teec_allocate_shared_memory);
|
|
|
|
void teec_release_shared_memory(struct teec_shared_memory *shared_mem)
|
|
{
|
|
struct gp_shared_memory memref;
|
|
|
|
/* No connection to "context"? */
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
/* The implementation MUST do nothing if shared_mem is NULL */
|
|
if (!shared_mem) {
|
|
mc_dev_devel("shared_mem is NULL");
|
|
return;
|
|
}
|
|
|
|
memref.buffer = (uintptr_t)shared_mem->buffer;
|
|
memref.flags = shared_mem->flags;
|
|
memref.size = shared_mem->size;
|
|
(void)client_gp_release_shared_mem(shared_mem->imp.client, &memref);
|
|
|
|
/*
|
|
* For a memory buffer allocated using teec_allocate_shared_memory the
|
|
* Implementation MUST free the underlying memory
|
|
*/
|
|
if (shared_mem->imp.implementation_allocated) {
|
|
if (shared_mem->buffer) {
|
|
vfree(shared_mem->buffer);
|
|
shared_mem->buffer = NULL;
|
|
shared_mem->size = 0;
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(teec_release_shared_memory);
|
|
|
|
void teec_request_cancellation(struct teec_operation *operation)
|
|
{
|
|
struct teec_session_imp *session;
|
|
int ret;
|
|
|
|
mc_dev_devel("== %s() ==============", __func__);
|
|
|
|
ret = wait_event_interruptible(operations_wq, operation->started);
|
|
if (ret == -ERESTARTSYS) {
|
|
mc_dev_devel("signal received");
|
|
return;
|
|
}
|
|
|
|
mc_dev_devel("operation->started changed from 0 to %d",
|
|
operation->started);
|
|
|
|
if (operation->started > 1) {
|
|
mc_dev_devel("the operation has finished");
|
|
return;
|
|
}
|
|
|
|
session = operation->imp.session;
|
|
operation->started = 2;
|
|
wake_up_interruptible(&operations_wq);
|
|
|
|
if (!session->active) {
|
|
mc_dev_devel("Corresponding session is not active");
|
|
return;
|
|
}
|
|
|
|
/* TODO: handle cancellation */
|
|
|
|
/* Signal the Trustlet */
|
|
ret = client_notify_session(session->context.client,
|
|
session->session_id);
|
|
if (ret)
|
|
mc_dev_devel("Notify failed: %d", ret);
|
|
}
|
|
EXPORT_SYMBOL(teec_request_cancellation);
|