// 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 && ret != -ECHILD)
			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 && ret != -ECHILD)
			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);
