/**
 * @file driver_dcih.c
 * @brief <t-base driver DCI handler task
 * @author Viacheslav Vovchenko (v.vovchenko@samsung.com)
 * @date Created Oct 3, 2016
 * @par In Samsung Ukraine R&D Center (SURC) under a contract between
 * @par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * @par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * @par Copyright: (c) Samsung Electronics Co, Ltd 2015. All rights reserved.
 *
 * This software is proprietary of Samsung Electronics.
 * No part of this software, either material or conceptual may be copied
 * or distributed, transmitted, transcribed, stored in a retrieval system
 * or translated into any human or computer language in any form by any means,
 * electronic, mechanical, manual or otherwise, or disclosed to third parties
 * without the express written permission of Samsung Electronics.
 */

#include "driver_task.h"

#include <DrApi/DrApi.h>
#include <DrApi/DrApiMmExt.h>
#include <TlApi/TlApiError.h>

#include "tl_main.h"

#include <protocol.h>
#include <tee_internal_api.h>
#include <tee_param_utils.h>
#include <tees_log.h>

#define KEEP_WORKING   0
#define MUST_UNLOAD_TA 1

#if KINIBI_VERSION < 5
DECLARE_STACK(drDcihStack, THREAD_STACK_SIZE);
#endif

static uint32_t *stack_dcih;

extern ProtocolCmd *g_cmd_buffer;
ProtocolCmd g_tmp_tci_buffer;

static void *g_session_context = NULL;
static int g_ta_created = 0;

extern void init_properties(void *start_TA_property);

/**
 * @brief DCI handler entry point
 */
static _THREAD_ENTRY void EntryDcih(void) {
  DcihLoop();
}

/**
 * @brief Makes request to RTM to update notification thread
 * @param [in] threadno Thread number
 * @return DRAPI_OK or relevant error code.
 */
static drApiResult_t UpdateNotificationThread(threadno_t threadno);


static int HandleCommand(ProtocolCmd *command) {
  TEE_Param params[4];

  // Return origin: "TEE" must be set by default.
  command->return_origin = TEE_ORIGIN_TEE;

  command->cmd_ret = FillParamValues(command->param_types, params, command);
  if (TEE_SUCCESS != command->cmd_ret) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed to read command params!\n");
    goto exit;
  }

  if (PROTOCOL_COMMAND_UNLOAD == command->cmd_id) {
    TA_CloseSessionEntryPoint(g_session_context);
    TA_DestroyEntryPoint();
    command->return_origin = TEE_ORIGIN_TRUSTED_APP;
    goto exit;
  }

  if (PROTOCOL_COMMAND_LOAD == command->cmd_id) {
    if (!g_ta_created) {

      /* property init */
      init_properties((void *)&_TA_property);

      TA_CreateEntryPoint();
      g_ta_created++;
    }

    command->cmd_ret = TA_OpenSessionEntryPoint(command->param_types,
                                                params,
                                                &g_session_context);

  } else {
    command->cmd_ret = TA_InvokeCommandEntryPoint(g_session_context,
                                                  command->cmd_id,
                                                  command->param_types,
                                                  params);
  }

  if (TEE_SUCCESS != FillCommandArgs(command->param_types, command, params)) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed to write command params!\n");
    command->cmd_ret = TEE_ERROR_COMMUNICATION;
    goto exit;
  }

  command->return_origin = TEE_ORIGIN_TRUSTED_APP;

exit:
  return KEEP_WORKING;
}

static drApiResult_t UpdateNotificationThread(threadno_t threadno) {
  drApiResult_t ret =  E_INVALID;
  threadid_t  notificationThread;

  /* Retrieve task id */
  taskid_t task = drApiGetTaskid();
  if (task == NILTASK) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "task is NILTASK.\n");
    return ret;
  }

  /* Retrieve thread id based on task id and DCI handler thread no */
  notificationThread = drApiTaskidGetThreadid(task, threadno);
  if (notificationThread == 0) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "notificationThread is 0.\n");
    return ret;
  }

  /* IPC data for setting notification handler */
  threadid_t  ipcClient = notificationThread;
  message_t   ipcMsg    = MSG_SET_NOTIFICATION_HANDLER;
  uint32_t    ipcData   = 0;

  /* Make IPC call to IPCH and wait for a response */
  ret = drApiIpcCallToIPCH(&ipcClient, &ipcMsg, &ipcData);

  return ret;
}

#if KINIBI_VERSION < 5
static inline int InitDcihStack() {
  /* Ensure thread stack is clean */
  TEE_MemFill(GET_STACK_BOTTOM(drDcihStack), STACK_DBG_INIT_VALUE,
              GET_STACK_SIZE(drDcihStack));

  stack_dcih = getStackTop(drDcihStack);
  return 0;
}
#else
static inline int InitDcihStack() {
  int ret = drApiStackAlloc(THREAD_STACK_SIZE, (uint8_t **)&stack_dcih);
  if (ret != DRAPI_OK) {
      TEES_LOG(TEES_LOG_LEVEL_ERROR, "%s: drApiStackAlloc(): error %d.\n", ret);
      return -1;
  }
  return 0;
}
#endif

uint32_t *GetDcihStackTop(void) {
  return stack_dcih;
}

void InitDcih(void) {
  if (InitDcihStack() != 0) {
    return;
  }

  /* Start DCI handler thread. EXH thread becomes local
     exception handler of DCIH thread */
  if (DRAPI_OK != drApiStartThread(
      DRIVER_THREAD_NO_DCIH,
      FUNC_PTR(EntryDcih),
      GetDcihStackTop(),
      DCIH_PRIORITY,
      DRIVER_THREAD_NO_EXCH)) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed drApiStartThread\n");
  }
}

_NORETURN void DcihLoop(void) {
  TEES_LOG(TEES_LOG_LEVEL_DEBUG, "DCI handler thread is running\n");

  /* Update DCI handler thread so that notifications will be delivered
     to DCI handler thread */
  if (DRAPI_OK != UpdateNotificationThread(DRIVER_THREAD_NO_DCIH)) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Updating notification thread failed\n");
  }

  for(;;) {
    if (DRAPI_OK != drApiIpcSigWait()) {
      TEES_LOG(TEES_LOG_LEVEL_ERROR, "drApiIpcSigWait failed");
      continue;
    }

    memcpy(&g_tmp_tci_buffer, g_cmd_buffer, sizeof(g_tmp_tci_buffer));
    HandleCommand(&g_tmp_tci_buffer);
    memcpy(g_cmd_buffer, &g_tmp_tci_buffer, sizeof(g_tmp_tci_buffer));

    if (DRAPI_OK != drApiNotify()) {
      TEES_LOG(TEES_LOG_LEVEL_ERROR, "drApiNotify failed");
    }
  }
}

