/**
 * @file   drIpcHandler.c
 * @brief  Implements IPC handler for the driver
 *
 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
 */

#include "drStd.h"
#include "DrApi/DrApiMm.h"
#if TBASE_API_LEVEL >= 5
#include "DrApi/DrApiMmExt.h"
#include "DrApi/DrApiError.h"
#endif
#include "DrApi/DrApiThread.h"
#include "DrApi/DrApiIpcMsg.h"
#include "TlApi/TlApiError.h"

#include "mcRootid.h"
#include "mcSpid.h"

#include "drCommon.h"
#include "tlapimarshal_fchandler.h"
#include "fcdrv_hw_hal.h"
#include "dbg.h"
#include "whitelist.h"

#if TBASE_API_LEVEL >= 8
uint32_t *drIpchStack;
#define DRIVER_IPCH_STACK_SIZE 4096
#else
DECLARE_STACK(drIpchStack, 2048);
#endif

/* Global variables */
threadid_t gLastIpcClient = NILTHREAD;
uint32_t g_trusted_boot_addr;

uint32_t g_oem_flag[NUM_OEM_FLAG];
#ifdef CONFIG_KNOX_GEARPAY
uint32_t g_touch_bool;
uint32_t g_physbutton_bool;
uint32_t g_strap_bool;
uint32_t g_userauth_bool;
#endif

#define E_DRAPI__FCHANDLER_ADDRESS_TRANSLATION_ERROR 0x40003

/**
 * IPC handler loop. this is the function where IPC messages are handled
 */
_NORETURN void drIpchLoop(void)
{
	/* Set IPC parameters for initial MSG_RD to IPCH */
	threadid_t ipcClient = NILTHREAD;
	message_t ipcMsg = MSG_RD;
	uint32_t ipcData = 0;
	uint32_t ipcMsgId = 0;
	uint32_t rootId, spId;
	tlApiResult_t tlapiRet = TLAPI_OK;
	drApiResult_t ret = DRAPI_OK;
	SecMarshalingParam_ptr pMarshal;
	uint8_t uuid[UUID_LENGTH];
	uint32_t uuid_len = sizeof(uuid);

	struct update_whitelist_fc_t *pwl;
	uint8_t *wl_addr = NULL;
	uint32_t wl_version = 0;
	uint32_t wl_len = 0;

	/**
	 * Check if there is a pending client. If there is, this is an
	 * indication that IPC handler thread crashed before completing
	 * the request. Respond with failure.
	 */
	if (!threadid_is_null(gLastIpcClient)) {
		ipcClient = gLastIpcClient;
		ipcMsg = MSG_RS;
		ipcData = E_TLAPI_DRV_UNKNOWN;
	}

	for (;;) {
	/* Reset last IPC client */
	gLastIpcClient = NILTHREAD;

	/*
	 * When called first time sends ready message to IPC server and
	 * then waits for IPC requests
	 */

	if (E_OK != drApiIpcCallToIPCH(&ipcClient, &ipcMsg, &ipcData)) {
		sec_err("drIpchLoop(): drApiIpcCallToIPCH failed");
		continue;
	}

	/* Update last IPC client */
	gLastIpcClient = ipcClient;

#if TBASE_API_LEVEL >= 8
	ipcMsgId = drApiExtractMsgCmd(ipcMsg);
#endif
	/* Dispatch request */
#if TBASE_API_LEVEL >= 8
	switch (ipcMsgId) {
#else
	switch (ipcMsg) {
#endif
	case MSG_CLOSE_TRUSTLET:
		/**
		 * Trustlet close message
		 */
		ipcMsg = MSG_CLOSE_TRUSTLET_ACK;
		ipcData = TLAPI_OK;
		break;
	case MSG_CLOSE_DRIVER:
		/**
		 * Driver close message
		 */
		ipcMsg = MSG_CLOSE_DRIVER_ACK;
		ipcData = TLAPI_OK;
		break;
	case MSG_GET_DRIVER_VERSION:
		/**
		 * Driver version message
		 */
		ipcMsg = (message_t)TLAPI_OK;
		ipcData = DRIVER_VERSION;
		break;
	case MSG_RQ_EX:
	case MSG_RQ:
	{
		tlapiRet = TLAPI_OK;
		static struct secOemFlag_t secFlag_ctx;

		/* Try to get the RootID and SPID of the calling client */
		if (E_OK != drApiGetClientRootAndSpId(&rootId, &spId, ipcClient)) {
			ipcMsg = (message_t)MSG_RS;
			ipcData = 0;
			continue;
		}

		/* We only allow system trustlets to run, anything else is undefined */
		if (rootId != MC_ROOTID_SYSTEM || spId != MC_SPID_SYSTEM) {
			/* The message cannot be handled, continue */
			ipcMsg = (message_t)MSG_RS;
			ipcData = 0;
			continue;
		}
		
		memset(uuid, 0, sizeof(uuid));
		uuid_len = sizeof(uuid);
		if (E_OK != drApiGetClientProperty(
					ipcClient, PROPERTY_UUID, uuid, &uuid_len)) {
			sec_dbg("drIpchLoop(): failed to get client UUID");
			ipcMsg = (message_t) MSG_RS;
			ipcData = 0;
			continue;
		}

		/* Map requesting client so we can access the data in the request */
#if TBASE_API_LEVEL >= 5
		ret = drApiMapTaskBuffer(
				THREADID_TO_TASKID(ipcClient),
				(addr_t)ipcData,
				sizeof(SecMarshalingParam_t),
				MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
				(void **)&pMarshal);
		if (ret != DRAPI_OK) {
			sec_errh("drIpchLoop(): failed to map ipcData: ", ret);
			sec_errh("ipcClient: ", ipcClient);
			ipcMsg = MSG_RS;
			pMarshal->payload.retVal = ret;
			break;
		}
#else
		pMarshal = (SecMarshalingParam_ptr)drApiMapClientAndParams(
								ipcClient, ipcData);
#endif
		if (in_uuid_whitelist(uuid, uuid_len,pMarshal->functionId) == false) {
			if (uuid_len == UUID_LENGTH) {
				sec_dbgh("drIpchLoop(): uuid[ ", UUID_LENGTH);
				sec_dbg("]=");
				for (int i = 0; i < UUID_LENGTH; i++) {
					sec_dbgh(" ", uuid[i]);
				}
				sec_dbg("drIpchLoop(): this uuid is not in whitelist");
			} else {
				sec_dbgh("drIpchLoop(): illegal uuid length =  ", uuid_len);
			}
			ipcMsg = (message_t) MSG_RS;
			ipcData = 0;
			continue;
		}


		if (pMarshal)
		{
			/* Process the request */
			switch (pMarshal->functionId) {
			case SEC_GET_TIMA_ADDR:
			{
				uint32_t *value;

				sec_dbg("drIpchLoop(): get TIMA secure memory address");

#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.addr,
						sizeof(uint32_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&value);
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.addr: ", ret);
					break;
				}
#else
				if(NULL == (value = drApiAddrTranslateAndCheckBuffer(pMarshal->payload.addr,sizeof(uint32_t))))
				{
					tlapiRet = E_DRAPI__FCHANDLER_ADDRESS_TRANSLATION_ERROR;
					break;
				}
#endif

				*value = g_trusted_boot_addr;
				sec_dbg("drIpchLoop(): get TIMA secure memory address is done");
				break;
			}
			case SEC_GET_OEM_FLAG:
				sec_dbg("drIpchLoop(): sec get oem flag");
				secFlag_ctx.pos = pMarshal->payload.SecureFlag.pos;
#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.SecureFlag.value,
						sizeof(uint32_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&(secFlag_ctx.value));
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.SecureFlag.value: ", ret);
					break;
				}
#else
				if(NULL == (secFlag_ctx.value = drApiAddrTranslateAndCheckBuffer(pMarshal->payload.SecureFlag.value,sizeof(uint32_t))))
				{
					tlapiRet = E_DRAPI__FCHANDLER_ADDRESS_TRANSLATION_ERROR;
					break;
				}
#endif
				/* fetch flag value */
				if(secFlag_ctx.pos > NUM_OEM_FLAG - 1){
					sec_errh("drIpchLoop(): Invalid oem flag offset: ", secFlag_ctx.pos);
					break;
				}
				*(secFlag_ctx.value) = g_oem_flag[secFlag_ctx.pos];

				sec_dbgh("secFlag_ctx.flag = ", secFlag_ctx.pos);
				sec_dbgh("secFlag_ctx.value = ", *(secFlag_ctx.value));

				break;
#ifdef CONFIG_KNOX_GEARPAY
			case SEC_GET_TOUCHEV_FLAG:
			{
				uint32_t *temp_sec_ptr;
				sec_dbg("drIpchLoop(): sec get gear touch status");

#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.touchFlagPtr,
						sizeof(uint32_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&temp_sec_ptr);
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.touchFlagPtr: ", ret);
					break;
				}
#else
				temp_sec_ptr = drApiAddrTranslateAndCheck(pMarshal->payload.touchFlagPtr);
#endif
				*temp_sec_ptr = g_touch_bool;
				sec_dbgh("drIpchLoop(): sec get touch count is done: ", *temp_sec_ptr);
				break;
			}
			case SEC_GET_STRAPEV_FLAG:
			{
				uint32_t *temp_sec_ptr;
				sec_dbg("drIpchLoop(): sec get gear strap status");

#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.strapFlagPtr,
						sizeof(uint32_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&temp_sec_ptr);
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.strapFlagPtr: ", ret);
					break;
				}
#else
				temp_sec_ptr = drApiAddrTranslateAndCheck(pMarshal->payload.strapFlagPtr);
#endif
				*temp_sec_ptr = g_strap_bool;
				sec_dbgh("drIpchLoop(): sec get gear strap status is done: ", *temp_sec_ptr);
				break;
			}
			case SEC_GET_BUTTONEV_FLAG:
			{
				uint32_t *temp_sec_ptr;
				sec_dbg("drIpchLoop(): sec get gear button status");

#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.buttonFlagPtr,
						sizeof(uint32_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&temp_sec_ptr);
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.buttonFlagPtr: ", ret);
					break;
				}
#else
				temp_sec_ptr = drApiAddrTranslateAndCheck(pMarshal->payload.buttonFlagPtr);
#endif
				*temp_sec_ptr = g_physbutton_bool;
				sec_dbgh("drIpchLoop(): sec get gear button status is done: ", *temp_sec_ptr);
				break;
			}
			case SEC_GET_USERAUTH_FLAG:
			{
				uint32_t *temp_sec_ptr;
				sec_dbg("drIpchLoop(): sec get gear userauth status");
#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.userauthFlagPtr,
						sizeof(uint32_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&temp_sec_ptr);
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.userauthFlagPtr: ", ret);
					break;
				}
#else
				temp_sec_ptr = drApiAddrTranslateAndCheck(pMarshal->payload.userauthFlagPtr);
#endif
				*temp_sec_ptr = g_userauth_bool;
				sec_dbgh("drIpchLoop(): sec get gear userauth status is done: ", *temp_sec_ptr);
				break;
			}
			case SEC_SET_TOUCHEV_FLAG:
			{
				sec_dbg("drIpchLoop(): sec reset gear touch status");
				g_touch_bool = 0;
				break;
			}
			case SEC_SET_STRAPEV_FLAG:
			{
				sec_dbg("drIpchLoop(): sec reset gear strap status");
				g_strap_bool = 0;
				break;
			}
			case SEC_SET_BUTTONEV_FLAG:
			{
				sec_dbg("drIpchLoop(): sec reset gear button status");
				g_physbutton_bool = 0;
				break;
			}
			case SEC_SET_USERAUTH_FLAG:
			{
				uint32_t *temp_sec_ptr;
#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.userauthFlagPtr,
						sizeof(uint32_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&temp_sec_ptr);
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.userauthFlagPtr: ", ret);
					break;
				}
#else
				temp_sec_ptr = drApiAddrTranslateAndCheck(pMarshal->payload.userauthFlagPtr);
#endif
				g_userauth_bool = *temp_sec_ptr;
				sec_dbgh("drIpchLoop(): sec set gear userauth status:", *temp_sec_ptr);
				break;
			}
#endif
			case SEC_UPDATE_LOADABLE_WHITELIST:
			{
				/* only CNCC TA can call this ipc call */
				sec_dbg("drIpchLoop(): SEC_UPDATE_LOADABLE_WHITELIST");
#if TBASE_API_LEVEL >= 5
				ret = drApiMapTaskBuffer(
						THREADID_TO_TASKID(ipcClient),
						(uint32_t)pMarshal->payload.wl,
						sizeof(struct update_whitelist_fc_t),
						MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
						(void **)&pwl);
				if (ret != DRAPI_OK) {
					sec_errh("drIpchLoop(): failed to map pMarshal->payload.wl: ", ret);
					break;
				}

				wl_version = pwl->wl_version;
				wl_len = pwl->wl_len;

				if (wl_len != 0) {
					ret = drApiMapTaskBuffer(
							THREADID_TO_TASKID(ipcClient),
							(uint32_t)pwl->wl_addr,
							wl_len,
							MAP_READABLE | MAP_WRITABLE | MAP_ALLOW_NONSECURE,
							(void **)&wl_addr);
					if (ret != DRAPI_OK) {
						sec_errh("drIpchLoop(): failed to map pwl->wl_addr: ", ret);
						break;
					}
				}
#else
				if (NULL ==
				    (pwl =
				     drApiAddrTranslateAndCheckBuffer(pMarshal->payload.wl,
								sizeof(struct update_whitelist_fc_t)))) {
					tlapiRet = E_DRAPI__FCHANDLER_ADDRESS_TRANSLATION_ERROR;
					sec_dbg("failed to translate payload");
					break;
				}

				wl_version = pwl->wl_version;
				wl_len = pwl->wl_len;

				if (wl_len != 0) {
					wl_addr = drApiAddrTranslateAndCheckBuffer(pwl->wl_addr, wl_len);
					if (NULL == wl_addr) {
						tlapiRet = E_DRAPI__FCHANDLER_ADDRESS_TRANSLATION_ERROR;
						sec_dbg("failed to translate wl_addr");
						break;
					}
				}
#endif
				pwl->ret_val = process_update_whitelist(
						wl_version,
						wl_addr, wl_len);
				break;
			}

			default:
				/* Unknown message has been received*/
				tlapiRet = E_TLAPI_UNKNOWN_FUNCTION;
				break;
			} /* end switch (pMarshal->functionId) */
		}		// if (pMarshal)
		ipcMsg = MSG_RS;
		ipcData = tlapiRet;
		pMarshal->payload.retVal = tlapiRet;
		break;
	}
	default:
		/* Unknown message has been received*/
		ipcClient = NILTHREAD;
		ipcMsg = MSG_RS;
		ipcData = E_TLAPI_DRV_UNKNOWN;
		break;
	} /* end switch (ipcMsg) */
#if TBASE_API_LEVEL >= 5
	ret = drApiUnmapTaskBuffers(THREADID_TO_TASKID(ipcClient));
	if (ret != DRAPI_OK) {
		sec_errh("drIpchLoop(): failed to UnmapTaskBuffers: ", ret);
		sec_errh("ipcClient: ", ipcClient);
		ipcMsg = MSG_RS;
		pMarshal->payload.retVal = ret;
	}
#endif
	} /* end for(;;) */
}

_THREAD_ENTRY void drIpch(void)
{
	sec_dbg("drIpch(): drIpch thread started");
	drIpchLoop();
}

void drIpchInit(void)
{
#if TBASE_API_LEVEL >= 8
	sec_dbg("drIpchInit(): start drIpch thread");
	if (E_OK != drApiStackAlloc(DRIVER_IPCH_STACK_SIZE,
				    &drIpchStack))
		sec_dbg("drIpchInit(): Stack allocation failed");

	if (E_OK != drApiStartThread(DRIVER_THREAD_NO_IPCH,
				     FUNC_PTR(drIpch),
				     drIpchStack,
				     IPCH_PRIORITY,
				     DRIVER_THREAD_NO_EXCH))
		sec_dbg("drIpchInit(): drApiStartThread failed");
#else
	/* ensure thread stack is clean */
	clearStack(drIpchStack);

	sec_dbg("drIpchInit(): start drIpch thread");

	/**
	 * Start IPC handler thread. Exception handler thread becomes local
	 * exception handler of IPC handler thread
	 */
	if (E_OK != drApiStartThread(
			DRIVER_THREAD_NO_IPCH,
			FUNC_PTR(drIpch),
			getStackTop(drIpchStack),
			IPCH_PRIORITY,
			DRIVER_THREAD_NO_EXCH))
		sec_dbg("drIpchInit(): drApiStartThread failed");
#endif

}
