/**
 * @file   drDrmIpcHandler.c
 * @brief  Implements IPC handler of the driver.
 *
 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
 */

#include "drStd.h"
#include "DrApi/DrApi.h"

#include "tlapimarshal_secdrv.h"

#include "secdrv_common.h"

#include "mcRootid.h"
#include "mcSpid.h"

#include "drCommon.h"
#include "access_control.h"

#include "secdrv_main.h"

#include "sec_fr_common.h"
#include "sec_fr_pal_common.h"

/* Thread specific definitions */
#define threadid_is_null(threadid)  (NILTHREAD == threadid)  /* returns true if thread id is NILTHREAD */

//#define FEATURE_SHOW_IMPORTANT_LOGS

#define CHECK_RET_VALUE(ID) {                                                           \
    if(drapiRet < SEC_FR_RESULT_SUCCESS ||  drapiRet == SEC_FR_PROCESS_FAIL)    {       \
        PAL_LOGE("0x%x ret err = %d", ID, drapiRet);                                    \
    }                                                                                   \
    else                                                                                \
    {                                                                                   \
        PAL_LOGV("0x%x ret = %d", ID, drapiRet);                                        \
    }                                                                                   \
}                                                                                       \

#ifndef __SUPPORT_ONLY_GET_DATA_FROM_DRV__
#if (TBASE_API_LEVEL<=5)
DECLARE_STACK(drIpchStack, 300*1024)
#else
uint32_t *drIpchStack;
#define DRIVER_IPCH_STACK_SIZE 300*1024
#endif
#else
#if (TBASE_API_LEVEL<=5)
DECLARE_STACK(drIpchStack, 125*1024)
#else
uint32_t *drIpchStack;
#define DRIVER_IPCH_STACK_SIZE 125*1024
#endif
#endif  /*  __SUPPORT_ONLY_GET_DATA_FROM_DRV__ */

/* Global variables */
threadid_t tl_notify_thread = NILTHREAD;

/**
 * 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;
    SecMarshalingParam_ptr mParam;
    uint32_t r;
    uint32_t callerRootId = 0;
    uint32_t callerSpId = 0;
    drApiResult_t ret = DRAPI_OK;
    uint32_t uuid_len = UUID_LEN;
    uint8_t uuid_buf[UUID_LEN];
//    uint32_t virtual_address;
//    uint32_t virtual_address2;
//    int32_t res = 0;

    /* init tlRet Value */
    int drapiRet = 0;
//    uint32_t size = 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(tl_notify_thread)) {
        PAL_LOGV("[IPC] threadid_is_null");
        ipcClient = tl_notify_thread;
        ipcMsg = MSG_RS;
        ipcData = E_TLAPI_DRV_UNKNOWN;
    }

    for (;;) {
        /*
        * When called first time sends ready message to IPC server and
        * then waits for IPC requests
        */
        if (E_OK != (r = drApiIpcCallToIPCH(&ipcClient, &ipcMsg, &ipcData))) {

#if 1//def IPC_DEBUG
            PAL_LOGE("drIpchLoop(): drApiIpcCallToIPCH failed",r);
#else
            PAL_LOGV("[%s:%d]\n", __FILE__, __LINE__);
#endif

            continue;
        }

        /* get UUID of the TA mapped into this driver */
        ret = drApiGetClientProperty(ipcClient, PROPERTY_UUID, uuid_buf, &uuid_len);
        if (ret != DRAPI_OK) {
            PAL_LOGE("[ERROR] Can't get client UUID info.");
            continue;
        }

#ifdef IPC_DEBUG
        PAL_LOGV("[INFO] get_command %x", drApiExtractMsgCmd(ipcMsg));
#endif

        /* Dispatch request */
        switch (drApiExtractMsgCmd(ipcMsg)) {
        case MSG_CLOSE_TRUSTLET:
#ifdef IPC_DEBUG
            PAL_LOGV("Driver FR receives MSG_CLOSE_TRUSTLET[0x%08X, 0x%08X]",
                ipcClient, ipcData);
#else
//          PAL_LOGV("[%s:%d]", __FILE__, __LINE__);
            PAL_LOGV("[dripcHandler :%d]",  __LINE__);
#endif

            /* Close active sessions owned by trustlet, which is being shutdown */
            ipcMsg = MSG_CLOSE_TRUSTLET_ACK;
            ipcData = TLAPI_OK;
            break;

        case MSG_CLOSE_DRIVER:
            /* Acknowledge */
#ifdef IPC_DEBUG
            PAL_LOGV("Driver FR receives MSG_CLOSE_DRIVER[0x%08X, 0x%08X]",
                ipcClient, ipcData);
#else
//          PAL_LOGV("[%s:%d]", __FILE__, __LINE__);
            PAL_LOGV("[dripcHandler :%d]",  __LINE__);
#endif

#ifdef IPC_DEBUG
            PAL_LOGV("[IPC] check init info");
#endif

            ipcMsg = MSG_CLOSE_DRIVER_ACK;
            ipcData = TLAPI_OK;
            break;

#if (TBASE_API_LEVEL<=5)   // deleted msg on Kinibi Sdk version 500
        case MSG_GET_DRIVER_VERSION:
            PAL_LOGV("[IPC] MSG_GET_DRIVER_VERSION");
            ipcMsg = (message_t)TLAPI_OK;
            ipcData = DRIVER_VERSION;
            break;
#endif

        case MSG_RQ_EX:
            /* Map requesting client so we can access the data in the request */
            ret = drApiMapTaskBuffer(THREADID_TO_TASKID(ipcClient),
                                     (addr_t)ipcData,
                                     sizeof(SecMarshalingParam_t),
                                     MAP_READABLE|MAP_WRITABLE,
                                     (void **)&mParam);

            if (ret != DRAPI_OK)
                break;

            if (E_OK != drApiGetClientRootAndSpId(&callerRootId, &callerSpId, ipcClient)) {
                ipcMsg = (message_t)MSG_RS;
                ipcData = 0;
                drApiUnmapTaskBuffers(THREADID_TO_TASKID(ipcClient));
                continue;
            }
            if ((callerRootId != MC_SPID_SYSTEM) || (callerSpId != MC_SPID_SYSTEM)) {
                PAL_LOGE("[ERROR]:Sec FR Driver::TA is not system TA.");
                ipcMsg = (message_t)MSG_RS;
                ipcData = 0;
                drApiUnmapTaskBuffers(THREADID_TO_TASKID(ipcClient));
                continue;
            }
            if (check_uuid(uuid_buf)) {
                PAL_LOGE("[ERROR]:Sec FR Driver::Invalid UUID.");
                ipcMsg = (message_t)MSG_RS;
                ipcData = 0;
                drApiUnmapTaskBuffers(THREADID_TO_TASKID(ipcClient));
                continue;
            }

            /* Update last IPC client */
            tl_notify_thread = ipcClient;

            /* Process the request */
            PAL_LOGV("****************************************************");
            switch (mParam->functionId) {
                case SEC_FR_DRV_CREATE: {

#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_CREATE");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x", SEC_FR_DRV_CREATE_FUNC_ID);
#endif
                drapiRet = driver_main_create();

                if(drapiRet != SEC_FR_RESULT_SUCCESS)
                {
                    PAL_LOGE("sec_fr_wrapper_create err(%d)", drapiRet);
                }

                break;
            }

            case SEC_FR_DRV_DESTROY: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_DESTROY");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x ", SEC_FR_DRV_DESTROY_FUNC_ID);
#endif

                driver_main_destroy();

                break;
            }

            case SEC_FR_DRV_GET_VERSION: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_GET_VERSION ");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x ", SEC_FR_DRV_GET_VERSION_FUNC_ID);
#endif

                if(mParam->version != NULL)
                {
                    memset(mParam->version, 0, SEC_FR_LIB_VERSION);

                    drapiRet = driver_main_getVersion(mParam->version);

                    if(drapiRet == SEC_FR_RESULT_SUCCESS)
                    {
                        PAL_LOGV("version (%s)", mParam->version);
                    }
                    else
                    {
                        PAL_LOGE("get version err(%d)", drapiRet);
                    }
                }
                else
                {
                    drapiRet = SEC_FR_RESULT_FAIL_GENERAL;
                }

                break;
            }

            case SEC_FR_DRV_SET_SECURE_LEVEL: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_SET_SECURE_LEVEL ");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x ", SEC_FR_DRV_SET_SECURE_LEVEL_FUNC_ID);
#endif

                driver_main_setSecureLevel(mParam->secureLevel);

                break;
            }

            case SEC_FR_DRV_SET_PARAM : {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_SET_PARAM ");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x ", SEC_FR_DRV_SET_PARAM_FUNC_ID);
#endif

                drapiRet = driver_main_setParam((secFrDrvConfParam_t *)&mParam->confParam);
                if(drapiRet != SEC_FR_RESULT_SUCCESS)
                {
                    PAL_LOGE("0x%x err = %d", SEC_FR_DRV_SET_PARAM_FUNC_ID, drapiRet);
                }

                break;
            }

            case SEC_FR_DRV_CLEAR_MEMORY: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV(" call SEC_FR_DRV_CLEAR_MEMORY");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x", SEC_FR_DRV_CLEAR_MEMORY_FUNC_ID);
#endif

                drapiRet = driver_main_clearMemory();

                if(drapiRet != SEC_FR_RESULT_SUCCESS)
                {
                    PAL_LOGE("0x%x err = %d", SEC_FR_DRV_CLEAR_MEMORY_FUNC_ID, drapiRet);
                }

                break;
            }

            case SEC_FR_DRV_GET_PREVIEW_NV21: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_GET_PREVIEW_NV21");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x ", SEC_FR_DRV_GET_PREVIEW_NV21_FUNC_ID);
#endif

                drapiRet = driver_main_get_preview_nv21((secFrDrvGetPreview_t *)&mParam->preParem);

                if(drapiRet != SEC_FR_RESULT_SUCCESS)
                {
                    PAL_LOGE("0x%x err = %d", SEC_FR_DRV_GET_PREVIEW_NV21_FUNC_ID, drapiRet);
                }

                break;
            }

            case SEC_FR_DRV_ENROLL_DETECT: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_ENROLL_DETECT");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x", SEC_FR_DRV_ENROLL_DETECT_FUNC_ID);
#endif

                drapiRet = driver_main_on_enroll_frame(&mParam->alParam, SECDRV_ENROLL_DETECT);

                CHECK_RET_VALUE(SEC_FR_DRV_ENROLL_DETECT_FUNC_ID);

                break;
            }

            case SEC_FR_DRV_ENROLL_FEATURE_EXTRACT: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_ENROLL_FEATURE_EXTRACT");
#else
                PAL_LOGV(" sec_fr_drv : call func 0x%x", SEC_FR_DRV_ENROLL_FEATURE_EXTRACT_FUNC_ID);
#endif

                drapiRet = driver_main_on_enroll_frame(&mParam->alParam, SECDRV_ENROLL_FEATURE_EXTRACT);

                CHECK_RET_VALUE(SEC_FR_DRV_ENROLL_FEATURE_EXTRACT_FUNC_ID);

                break;
            }

            case SEC_FR_DRV_ENROLL_REGISTER: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_ENROLL_REGISTER");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x", SEC_FR_DRV_ENROLL_REGISTER_FUNC_ID);
#endif

                drapiRet = driver_main_on_enroll_frame(&mParam->alParam, SECDRV_ENROLL_REGISTER);

                CHECK_RET_VALUE(SEC_FR_DRV_ENROLL_REGISTER_FUNC_ID);

                break;
            }

            case SEC_FR_DRV_AUTH_DETECT: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_AUTH_DETECT");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x", SEC_FR_DRV_AUTH_DETECT_FUNC_ID);
#endif

                drapiRet = driver_main_on_auth_frame(&mParam->alParam, SECDRV_AUTH_DETECT);

                CHECK_RET_VALUE(SEC_FR_DRV_AUTH_DETECT_FUNC_ID);

                break;
            }

            case SEC_FR_DRV_AUTH_FEATURE_EXTRACT: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_AUTH_FEATURE_EXTRACT");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x", SEC_FR_DRV_AUTH_FEATURE_EXTRACT_FUNC_ID);
#endif

                drapiRet = driver_main_on_auth_frame(&mParam->alParam, SECDRV_AUTH_FEATURE_EXTRACT);

                CHECK_RET_VALUE(SEC_FR_DRV_AUTH_FEATURE_EXTRACT_FUNC_ID);

                break;
            }

            case SEC_FR_DRV_AUTH_VERIFY: {
#ifdef FEATURE_SHOW_IMPORTANT_LOGS
                PAL_LOGV("call SEC_FR_DRV_AUTH_VERIFY");
#else
                PAL_LOGV("sec_fr_drv : call func 0x%x", SEC_FR_DRV_AUTH_VERIFY_FUNC_ID);
#endif

                drapiRet = driver_main_on_auth_frame(&mParam->alParam, SECDRV_AUTH_VERIFY);

                CHECK_RET_VALUE(SEC_FR_DRV_AUTH_VERIFY_FUNC_ID);

                break;
            }

            default:
                /* Could not find function ID */
                PAL_LOGV("reached to default (%d): case, E_TLAPI_UNKNOWN_FUNCTION should be returned", mParam->functionId);
                break;

            } /* end switch (mParam->functionId) */

            ipcMsg = MSG_RS;
            mParam->retVal = drapiRet;

            drApiUnmapTaskBuffers(THREADID_TO_TASKID(ipcClient));

            PAL_LOGV("****************************************************");
            break;
        default:
            /* Unknown message has been received*/
            ipcClient = NILTHREAD;
            ipcMsg = MSG_RS;
            ipcData = E_TLAPI_DRV_UNKNOWN;
            break;
        } /* end switch (ipcMsg) */
    } /* end for(;;) */
}


_THREAD_ENTRY void drIpch(void) {
    drIpchLoop();
}

void drIpchInit() {
    uint32_t r;

#if (TBASE_API_LEVEL<=5)
    /* ensure thread stack is clean */
    clearStack(drIpchStack);

    /**
    * Start IPC handler thread. Exception handler thread becomes local
    * exception handler of IPC handler thread
    */
    if (E_OK != (r = drApiStartThread(
                         DRIVER_THREAD_NO_IPCH,
                         FUNC_PTR(drIpch),
                         getStackTop(drIpchStack),
                         IPCH_PRIORITY,
                         DRIVER_THREAD_NO_EXCH))) {
        PAL_LOGE("drIpchInit(): drApiStartThread failed", r);
    }
#else
    if (E_OK != (r = drApiStackAlloc(
                        DRIVER_IPCH_STACK_SIZE,
                        (uint8_t **const)&drIpchStack))){
        /* Stack allocation failed*/
        PAL_LOGE("drIpchInit(): drApiStackAlloc failed", r);
    }

    /**
    * Start IPC handler thread. Exception handler thread becomes local
    * exception handler of IPC handler thread
    */
    if (E_OK != (r = drApiStartThread(
                        DRIVER_THREAD_NO_IPCH,
                        FUNC_PTR(drIpch),
                        drIpchStack,
                        IPCH_PRIORITY,
                        DRIVER_THREAD_NO_EXCH))){
        /* Starting thread failed*/
        PAL_LOGE("drIpchInit(): drApiStartThread failed", r);
    }

#endif


}
