/**
 * Copyright (c) 2011 Trusted Logic S.A.
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#ifdef CONFIG_TF_TEEC

#include <asm/atomic.h>
#include <asm/system.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/pagemap.h>
#include <linux/time.h>
#include <linux/types.h>
#include <linux/vmalloc.h>

#include "tf_protocol.h"
#include "tf_defs.h"
#include "tf_util.h"
#include "tf_comm.h"
#include "tf_conn.h"
#include "tf_teec.h"

#include "tee_client_api.h"

#define TF_COMMAND_BYTES(cmd) \
	(sizeof(cmd) - sizeof(struct tf_command_header))
#define TF_COMMAND_SIZE(cmd) \
	(TF_COMMAND_BYTES(cmd) / sizeof(u32))

/* Associate TEEC errors to POSIX/Linux errors. The matching is somewhat
   arbitrary but one-to-one for supported error codes. */
int TEEC_decode_error(TEEC_Result ret)
{
	switch (ret)
	{
	case TEEC_SUCCESS:                      return 0;
	case TEEC_ERROR_GENERIC:                return -EIO;
	case TEEC_ERROR_ACCESS_DENIED:          return -EPERM;
	case TEEC_ERROR_CANCEL:                 return -ECANCELED;
	case TEEC_ERROR_ACCESS_CONFLICT:        return -EBUSY;
	case TEEC_ERROR_EXCESS_DATA:            return -E2BIG;
	case TEEC_ERROR_BAD_FORMAT:             return -EDOM;
	case TEEC_ERROR_BAD_PARAMETERS:         return -EINVAL;
	case TEEC_ERROR_BAD_STATE:              return -EBADFD;
	case TEEC_ERROR_ITEM_NOT_FOUND:         return -ENOENT;
	case TEEC_ERROR_NOT_IMPLEMENTED:        return -EPROTONOSUPPORT;
	case TEEC_ERROR_NOT_SUPPORTED:          return -ENOSYS;
	case TEEC_ERROR_NO_DATA:                return -ENODATA;
	case TEEC_ERROR_OUT_OF_MEMORY:          return -ENOMEM;
	case TEEC_ERROR_BUSY:                   return -EAGAIN;
	case TEEC_ERROR_COMMUNICATION:          return -EPIPE;
	case TEEC_ERROR_SECURITY:               return -ECONNABORTED;
	case TEEC_ERROR_SHORT_BUFFER:           return -EFBIG;
	default:                                return -EIO;
	}
}

/* Associate POSIX/Linux errors to TEEC errors. The matching is somewhat
   arbitrary, but TEEC_encode_error(TEEC_decode_error(x))==x for supported
   error codes. */
TEEC_Result TEEC_encode_error(int err)
{
	if (err >= 0) {
		return S_SUCCESS;
	}
	switch (err) {
	case 0:		          return TEEC_SUCCESS;
	case -EIO:		  return TEEC_ERROR_GENERIC;
	case -EPERM:		  return TEEC_ERROR_ACCESS_DENIED;
	case -ECANCELED:	  return TEEC_ERROR_CANCEL;
	case -EBUSY:		  return TEEC_ERROR_ACCESS_CONFLICT;
	case -E2BIG:		  return TEEC_ERROR_EXCESS_DATA;
	case -EDOM:		  return TEEC_ERROR_BAD_FORMAT;
	case -EINVAL:		  return TEEC_ERROR_BAD_PARAMETERS;
	case -EBADFD:		  return TEEC_ERROR_BAD_STATE;
	case -ENOENT:		  return TEEC_ERROR_ITEM_NOT_FOUND;
	case -EPROTONOSUPPORT:    return TEEC_ERROR_NOT_IMPLEMENTED;
	case -ENOSYS:		  return TEEC_ERROR_NOT_SUPPORTED;
	case -ENODATA:	          return TEEC_ERROR_NO_DATA;
	case -ENOMEM:		  return TEEC_ERROR_OUT_OF_MEMORY;
	case -EAGAIN:		  return TEEC_ERROR_BUSY;
	case -EPIPE:		  return TEEC_ERROR_COMMUNICATION;
	case -ECONNABORTED:	  return TEEC_ERROR_SECURITY;
	case -EFBIG:		  return TEEC_ERROR_SHORT_BUFFER;
	default:                  return TEEC_ERROR_GENERIC;
	}
}

/* Encode a TEEC time limit into an SChannel time limit. */
static u64 TEEC_encode_timeout(const TEEC_TimeLimit *timeLimit)
{
	if (timeLimit == NULL) {
		return (u64)-1;
	} else {
		return *timeLimit;
	}
}

/* Convert a timeout into a time limit in our internal format. */
void TEEC_GetTimeLimit(TEEC_Context* sContext,
		       uint32_t nTimeout, /*ms from now*/
		       TEEC_TimeLimit* sTimeLimit)
{
	/*Use the kernel time as the TEE time*/
	struct timeval now;
	do_gettimeofday(&now);
	*sTimeLimit =
		((TEEC_TimeLimit)now.tv_sec * 1000 +
		 now.tv_usec / 1000 +
		 nTimeout);
}

#define TF_PARAM_TYPE_INPUT_FLAG                0x1
#define TF_PARAM_TYPE_OUTPUT_FLAG               0x2
#define TF_PARAM_TYPE_MEMREF_FLAG               0x4
#define TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG    0x8

/* Update the type of a whole memref with the direction deduced from
   the INPUT and OUTPUT flags of the memref. */
static void TEEC_encode_whole_memref_flags(u16 *param_types,
					   unsigned i,
					   u32 flags)
{
	if (flags & TEEC_MEM_INPUT)
		*param_types |= TF_PARAM_TYPE_INPUT_FLAG << (4*i);
	if (flags & TEEC_MEM_OUTPUT)
		*param_types |= TF_PARAM_TYPE_OUTPUT_FLAG << (4*i);
}

/* Encode the parameters and type of an operation from the TEE API format
   into an SChannel message. */
void TEEC_encode_parameters(u16 *param_types,
			    union tf_command_param *params,
			    TEEC_Operation *operation)
{
	unsigned i;
	if (operation == NULL) {
		*param_types = 0;
		return;
	}
	*param_types = operation->paramTypes;
	for (i = 0; i < 4; i++) {
		unsigned ty = TF_GET_PARAM_TYPE(operation->paramTypes, i);
		TEEC_Parameter *op = operation->params + i;
		if (ty & TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG) {
			TEEC_SharedMemory *sm = op->memref.parent;
			params[i].memref.block = sm->imp._block;
			if (ty == TEEC_MEMREF_WHOLE) {
				TEEC_encode_whole_memref_flags(param_types, i,
							       sm->flags);
				params[i].memref.size = sm->size;
				params[i].memref.offset = 0;
			} else {
				params[i].memref.size = op->memref.size;
				params[i].memref.offset = op->memref.offset;
			}
		} else if (ty & TF_PARAM_TYPE_MEMREF_FLAG) {
			/* Set up what tf_map_temp_shmem (called by
			   tf_open_client_session and
			   tf_invoke_client_command) expects:
			   .descriptor and .offset to both be set to the
			   address of the buffer. */
			u32 address = (u32)op->tmpref.buffer;
			params[i].temp_memref.descriptor = address;
			params[i].temp_memref.size = op->tmpref.size;
			params[i].temp_memref.offset = address;
		} else if (ty & TF_PARAM_TYPE_INPUT_FLAG) {
			params[i].value.a = op->value.a;
			params[i].value.b = op->value.b;
		} else {
			/* output-only value or none, so nothing to do */
		}
	}
}

/* Decode updated parameters from an SChannel answer into the TEE API format. */
void TEEC_decode_parameters(union tf_answer_param *params,
			    TEEC_Operation *operation)
{
	unsigned i;
	if (operation == NULL) {
		return;
	}
	for (i = 0; i < 4; i++) {
		unsigned ty = TF_GET_PARAM_TYPE(operation->paramTypes, i);
		TEEC_Parameter *op = operation->params + i;
		if (!(ty & TF_PARAM_TYPE_OUTPUT_FLAG)) {
			/* input-only or none, so nothing to do */
		} else if (ty & TF_PARAM_TYPE_REGISTERED_MEMREF_FLAG) {
			op->memref.size = params[i].size.size;
		} else if (ty & TF_PARAM_TYPE_MEMREF_FLAG) {
			op->tmpref.size = params[i].size.size;
		} else {
			op->value.a = params[i].value.a;
			op->value.b = params[i].value.b;
		}
	}
}

/* Start a potentially-cancellable operation. */
void TEEC_start_operation(TEEC_Context *context,
			  TEEC_Session *session,
			  TEEC_Operation *operation)
{
	if (operation != NULL) {
		operation->imp._pSession = session;
		/* Flush the assignment to imp._pSession, so that
		   RequestCancellation can read that field if started==1. */
		barrier();
		operation->started = 1;
	}
}

/* Mark a potentially-cancellable operation as finished. */
void TEEC_finish_operation(TEEC_Operation *operation)
{
	if (operation != NULL) {
		operation->started = 2;
		barrier();
	}
}



TEEC_Result TEEC_InitializeContext(const char *name,
				   TEEC_Context *context)
{
	int error;
	struct tf_connection *connection = NULL;

	error = tf_open(tf_get_device(), NULL, &connection);
	if (error != 0) {
		dprintk(KERN_ERR "TEEC_InitializeContext(%s): "
			"tf_open failed (error %d)!\n",
			(name == NULL ? "(null)" : name), error);
		goto error;
	}
	BUG_ON(connection == NULL);
	connection->owner = TF_CONNECTION_OWNER_KERNEL;

	error = tf_create_device_context(connection);
	if (error != 0) {
		dprintk(KERN_ERR "TEEC_InitializeContext(%s): "
			"tf_create_device_context failed (error %d)!\n",
			(name == NULL ? "(null)" : name), error);
		goto error;
	}

	context->imp._connection = connection;
	/*spin_lock_init(&context->imp._operations_lock);*/
	return S_SUCCESS;

error:
	tf_close(connection);
	return TEEC_encode_error(error);
}

void TEEC_FinalizeContext(TEEC_Context *context)
{
	struct tf_connection *connection = context->imp._connection;
	dprintk(KERN_DEBUG "TEEC_FinalizeContext: connection=%p", connection);
	tf_close(connection);
	context->imp._connection = NULL;
}

TEEC_Result TEEC_RegisterSharedMemory(TEEC_Context*      context,
				      TEEC_SharedMemory* sharedMem)
{
	union tf_command command_message = {{0}};
	struct tf_command_register_shared_memory *cmd =
		&command_message.register_shared_memory;
	union tf_answer answer_message;
	struct tf_answer_register_shared_memory *ans =
		&answer_message.register_shared_memory;
	TEEC_Result ret;
	memset(&sharedMem->imp, 0, sizeof(sharedMem->imp));

	cmd->message_size = TF_COMMAND_SIZE(*cmd);
	cmd->message_type = TF_MESSAGE_TYPE_REGISTER_SHARED_MEMORY;
	cmd->memory_flags = sharedMem->flags;
	cmd->operation_id = (u32)&answer_message;
	cmd->device_context = (u32)context;
	/*cmd->block_id will be set by tf_register_shared_memory*/
	cmd->shared_mem_size = sharedMem->size;
	cmd->shared_mem_start_offset = 0;
	cmd->shared_mem_descriptors[0] = (u32)sharedMem->buffer;

	ret = TEEC_encode_error(
		tf_register_shared_memory(context->imp._connection,
					       &command_message,
					       &answer_message));
	if (ret == TEEC_SUCCESS) {
		ret = ans->error_code;
	}
	if (ret == S_SUCCESS) {
		sharedMem->imp._context = context;
		sharedMem->imp._block = ans->block;
	}
	return ret;
}

#define TEEC_POINTER_TO_ZERO_SIZED_BUFFER ((void*)0x010)

TEEC_Result TEEC_AllocateSharedMemory(TEEC_Context* context,
				      TEEC_SharedMemory* sharedMem)
{
	TEEC_Result ret;
	dprintk(KERN_DEBUG "TEEC_AllocateSharedMemory: requested=%lu",
		(unsigned long)sharedMem->size);
	if (sharedMem->size == 0) {
		/* Allocating 0 bytes must return a non-NULL pointer, but the
		   pointer doesn't need to be to memory that is mapped
		   anywhere. So we return a pointer into an unmapped page. */
		sharedMem->buffer = TEEC_POINTER_TO_ZERO_SIZED_BUFFER;
	} else {
		sharedMem->buffer = internal_vmalloc(sharedMem->size);
		if (sharedMem->buffer == NULL)
		{
			dprintk(KERN_INFO "TEEC_AllocateSharedMemory: could not allocate %lu bytes",
				(unsigned long)sharedMem->size);
			return TEEC_ERROR_OUT_OF_MEMORY;
		}
	}

	ret = TEEC_RegisterSharedMemory(context, sharedMem);
	if (ret == TEEC_SUCCESS)
	{
		sharedMem->imp._allocated = 1;
	}
	else
	{
		internal_vfree(sharedMem->buffer);
		sharedMem->buffer = NULL;
		memset(&sharedMem->imp, 0, sizeof(sharedMem->imp));
	}
	return ret;
}

void TEEC_ReleaseSharedMemory(TEEC_SharedMemory* sharedMem)
{
	TEEC_Context *context = sharedMem->imp._context;
	union tf_command command_message = {{0}};
	struct tf_command_release_shared_memory *cmd =
		&command_message.release_shared_memory;
	union tf_answer answer_message;

	cmd->message_size = TF_COMMAND_SIZE(*cmd);
	cmd->message_type = TF_MESSAGE_TYPE_RELEASE_SHARED_MEMORY;
	cmd->operation_id = (u32)&answer_message;
	cmd->device_context = (u32)context;
	cmd->block = sharedMem->imp._block;

	tf_release_shared_memory(context->imp._connection,
				      &command_message,
				      &answer_message);
	if (sharedMem->imp._allocated) {
		if (sharedMem->buffer != TEEC_POINTER_TO_ZERO_SIZED_BUFFER) {
			internal_vfree(sharedMem->buffer);
		}
		sharedMem->buffer = NULL;
		sharedMem->size = 0;
	}
	memset(&sharedMem->imp, 0, sizeof(sharedMem->imp));
}

TEEC_Result TEEC_OpenSessionEx(TEEC_Context* context,
			       TEEC_Session* session,
			       const TEEC_TimeLimit* timeLimit,
			       const TEEC_UUID* destination,
			       u32 connectionMethod,
			       void* connectionData,
			       TEEC_Operation* operation,
			       u32* errorOrigin)
{
	union tf_command command_message = {{0}};
	struct tf_command_open_client_session *cmd =
		&command_message.open_client_session;
	union tf_answer answer_message = {{0}};
	struct tf_answer_open_client_session *ans =
		&answer_message.open_client_session;
	TEEC_Result ret;

	/* Note that we set the message size to the whole size of the
	   structure. tf_open_client_session will adjust it down
	   to trim the unnecessary portion of the login_data field. */
	cmd->message_size = TF_COMMAND_SIZE(*cmd);
	cmd->message_type = TF_MESSAGE_TYPE_OPEN_CLIENT_SESSION;
	cmd->operation_id = (u32)&answer_message;
	cmd->device_context = (u32)context;
	cmd->cancellation_id = (u32)operation;
	cmd->timeout = TEEC_encode_timeout(timeLimit);
	memcpy(&cmd->destination_uuid, destination,
	       sizeof(cmd->destination_uuid));
	cmd->login_type = connectionMethod;
	TEEC_encode_parameters(&cmd->param_types, cmd->params, operation);

	switch (connectionMethod)
	{
	case TEEC_LOGIN_PRIVILEGED:
	case TEEC_LOGIN_PUBLIC:
		break;
	case TEEC_LOGIN_APPLICATION:
	case TEEC_LOGIN_USER:
	case TEEC_LOGIN_USER_APPLICATION:
	case TEEC_LOGIN_GROUP:
	case TEEC_LOGIN_GROUP_APPLICATION:
	default:
		return TEEC_ERROR_NOT_IMPLEMENTED;
	}

	TEEC_start_operation(context, session, operation);

	ret = TEEC_encode_error(
		tf_open_client_session(context->imp._connection,
					    &command_message,
					    &answer_message));

	TEEC_finish_operation(operation);
	TEEC_decode_parameters(ans->answers, operation);
	if (errorOrigin != NULL) {
		*errorOrigin = (ret == TEEC_SUCCESS ?
				ans->error_origin :
				TEEC_ORIGIN_COMMS);
	}

	if (ret == TEEC_SUCCESS) {
		ret = ans->error_code;
	}
	if (ret == S_SUCCESS) {
		session->imp._client_session = ans->client_session;
		session->imp._context = context;
	}
	return ret;
}

TEEC_Result TEEC_OpenSession(TEEC_Context* context,
			     TEEC_Session* session,
			     const TEEC_UUID* destination,
			     u32 connectionMethod,
			     void* connectionData,
			     TEEC_Operation* operation,
			     u32* errorOrigin)
{
	return TEEC_OpenSessionEx(context, session,
				  NULL, /*timeLimit*/
				  destination,
				  connectionMethod, connectionData,
				  operation, errorOrigin);
}

void TEEC_CloseSession(TEEC_Session* session)
{
	if (session != NULL) {
		TEEC_Context *context = session->imp._context;
		union tf_command command_message = {{0}};
		struct tf_command_close_client_session *cmd =
			&command_message.close_client_session;
		union tf_answer answer_message;

		cmd->message_size = TF_COMMAND_SIZE(*cmd);
		cmd->message_type = TF_MESSAGE_TYPE_CLOSE_CLIENT_SESSION;
		cmd->operation_id = (u32)&answer_message;
		cmd->device_context = (u32)context;
		cmd->client_session = session->imp._client_session;

		tf_close_client_session(context->imp._connection,
					     &command_message,
					     &answer_message);

		session->imp._client_session = 0;
		session->imp._context = NULL;
	}
}

TEEC_Result TEEC_InvokeCommandEx(TEEC_Session* session,
				    const TEEC_TimeLimit* timeLimit,
				    u32 commandID,
				    TEEC_Operation* operation,
				    u32* errorOrigin)
{
	TEEC_Context *context = session->imp._context;
	union tf_command command_message = {{0}};
	struct tf_command_invoke_client_command *cmd =
		&command_message.invoke_client_command;
	union tf_answer answer_message = {{0}};
	struct tf_answer_invoke_client_command *ans =
		&answer_message.invoke_client_command;
	TEEC_Result ret;

	cmd->message_size = TF_COMMAND_SIZE(*cmd);
	cmd->message_type = TF_MESSAGE_TYPE_INVOKE_CLIENT_COMMAND;
	cmd->operation_id = (u32)&answer_message;
	cmd->device_context = (u32)context;
	cmd->client_session = session->imp._client_session;
	cmd->timeout = TEEC_encode_timeout(timeLimit);
	cmd->cancellation_id = (u32)operation;
	cmd->client_command_identifier = commandID;
	TEEC_encode_parameters(&cmd->param_types, cmd->params, operation);

	TEEC_start_operation(context, session, operation);

	ret = TEEC_encode_error(
		tf_invoke_client_command(context->imp._connection,
					      &command_message,
					      &answer_message));

	TEEC_finish_operation(operation);
	TEEC_decode_parameters(ans->answers, operation);
	if (errorOrigin != NULL) {
		*errorOrigin = (ret == TEEC_SUCCESS ?
				ans->error_origin :
				TEEC_ORIGIN_COMMS);
	}

	if (ret == TEEC_SUCCESS) {
		ret = ans->error_code;
	}
	return ret;
}

TEEC_Result TEEC_InvokeCommand(TEEC_Session* session,
			       u32 commandID,
			       TEEC_Operation* operation,
			       u32* errorOrigin)
{
	return TEEC_InvokeCommandEx(session,
				    NULL, /*timeLimit*/
				    commandID,
				    operation, errorOrigin);
}

TEEC_Result TEEC_send_cancellation_message(TEEC_Context *context,
					   u32 client_session,
					   u32 cancellation_id)
{
	union tf_command command_message = {{0}};
	struct tf_command_cancel_client_operation *cmd =
		&command_message.cancel_client_operation;
	union tf_answer answer_message = {{0}};
	struct tf_answer_cancel_client_operation *ans =
		&answer_message.cancel_client_operation;
	TEEC_Result ret;

	cmd->message_size = TF_COMMAND_SIZE(*cmd);
	cmd->message_type = TF_MESSAGE_TYPE_CANCEL_CLIENT_COMMAND;
	cmd->operation_id = (u32)&answer_message;
	cmd->device_context = (u32)context;
	cmd->client_session = client_session;
	cmd->cancellation_id = cancellation_id;

	ret = TEEC_encode_error(
		tf_cancel_client_command(context->imp._connection,
					      &command_message,
					      &answer_message));

	if (ret == TEEC_SUCCESS) {
		ret = ans->error_code;
	}
	return ret;
}

void TEEC_RequestCancellation(TEEC_Operation* operation)
{
	TEEC_Result ret;
	while (1) {
		u32 state = operation->started;
		switch (state) {
		case 0: /*The operation data structure isn't initialized yet*/
			break;

		case 1: /*operation is in progress in the client*/
			ret = TEEC_send_cancellation_message(
				operation->imp._pSession->imp._context,
				operation->imp._pSession->imp._client_session,
				(u32)operation);
			if (ret == TEEC_SUCCESS) {
				/*The cancellation was successful*/
				return;
			}
			/* The command has either not reached the secure world
			   yet or has completed already. Either way, retry. */
			break;

		case 2: /*operation has completed already*/
			return;
		}
		/* Since we're busy-waiting for the operation to be started
		   or finished, yield. */
		schedule();
	}
}

EXPORT_SYMBOL(TEEC_encode_error);
EXPORT_SYMBOL(TEEC_decode_error);
EXPORT_SYMBOL(TEEC_InitializeContext);
EXPORT_SYMBOL(TEEC_FinalizeContext);
EXPORT_SYMBOL(TEEC_RegisterSharedMemory);
EXPORT_SYMBOL(TEEC_AllocateSharedMemory);
EXPORT_SYMBOL(TEEC_ReleaseSharedMemory);
EXPORT_SYMBOL(TEEC_OpenSession);
EXPORT_SYMBOL(TEEC_CloseSession);
EXPORT_SYMBOL(TEEC_InvokeCommand);
EXPORT_SYMBOL(TEEC_RequestCancellation);

#endif /* defined(CONFIG_TF_TEEC) */
