/**
 * @file driver_ipch.c
 * @brief <t-base driver IPC 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 <tee_internal_api.h>
#include <tees_log.h>
#include <tees_sys.h>

#if KINIBI_VERSION < 5
DECLARE_STACK(drIpchStack, THREAD_STACK_SIZE);
#endif

static uint32_t *stack_ipch;

threadid_t g_current_client_thread_id = NILTHREAD;
threadid_t g_last_ipc_client = NILTHREAD;

/**
 * @brief IPC handler thread entry point
 */
static _THREAD_ENTRY void EntryIpch(void) {
  IpchLoop();
}

/**
 * @brief IPC handler loop. this is the function where IPC messages are handled
 */
_NORETURN void IpchLoop(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;
  void *pMarshal;

  TEES_LOG(TEES_LOG_LEVEL_DEBUG, "IPC handler thread is running\n");

  /**
   * 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 (g_last_ipc_client != NILTHREAD)
  {
      ipcClient = g_last_ipc_client;
      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 (DRAPI_OK != drApiIpcCallToIPCH(&ipcClient, &ipcMsg, &ipcData)) {
      TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed drApiIpcCallToIPCH\n");
      continue;
    }

    ipcMsgId = drApiExtractMsgCmd(ipcMsg);

    /* Update last IPC client */
    g_last_ipc_client = ipcClient;

    /* Dispatch request */
    switch (ipcMsgId) {
      case MSG_CLOSE_TRUSTLET: {
        /* Trustlet close message */
        TEES_LOG(TEES_LOG_LEVEL_DEBUG, "Acknowledging trustlet close\n");
        ipcMsg = MSG_CLOSE_TRUSTLET_ACK;
        ipcData = TLAPI_OK;
        break;
      }
      case MSG_CLOSE_DRIVER: {
        /* Driver close message */
        TEES_LOG(TEES_LOG_LEVEL_DEBUG, "Acknowledging driver close\n");
        ipcMsg = MSG_CLOSE_DRIVER_ACK;
        ipcData = TLAPI_OK;
        break;
      }
      case MSG_RQ: {
        TEES_LOG(TEES_LOG_LEVEL_DEBUG, "Acknowledging driver rq\n");
        ipcMsg = MSG_RS;  /* send response message... */
        ipcData = E_TLAPI_DRV_INVALID_PARAMETERS;
        break;
      }
      case MSG_RQ_EX: {
        /* Handle incoming IPC requests via TL API.
           Map the caller trustlet to access to the marshaling data */
        taskid_t client_task_id = THREADID_TO_TASKID(ipcClient);
        g_current_client_thread_id = ipcClient;
        size_t ipcMsgSize = drApiExtractMsgLen(ipcMsg);
        /*
         * ipcMsgSize is aligned to page size using the following rules
         * startVal = (uint32_t)PTR_ALIGN_DOWN_4KB(payload);
         * endVal   = (uint32_t)payload + payloadSize;
         * if (endVal & MASK_4KB) {
         *   endVal = (uint32_t)PTR_ALIGN_DOWN_4KB(endVal + SIZE_4KB);
         * }
         * message_t ipcMsgAndSize = (message_t)(((uint32_t)ipcMsg) |
         *                  (((endVal - startVal) / SIZE_4KB) << 16));
         * So, if we pass ipcData and ipcMsgSize to drApiMapTaskBuffer
         * we may have an access to unavailable memory.
         * Example:
         *   in library payload = 0x000c8220, size = 0x1000
         *   in driver ipcData = 0x000c8220, size = 0x2000,
         * so drApiMapTaskBuffer will try to map range 0x000c8220 - 0x000d0220,
         * but page 0x000d0000 may not even exist.
         */
        ipcMsgSize -= ipcData & (SIZE_4KB - 1);

        drApiResult_t dr_result = drApiMapTaskBuffer(
            client_task_id, (addr_t)ipcData, ipcMsgSize,
            MAP_WRITABLE | MAP_READABLE | MAP_ALLOW_NONSECURE,
            &pMarshal);

        if (DRAPI_OK != dr_result) {
          TEES_LOG(TEES_LOG_LEVEL_ERROR, "Fail to map task buffer %x\n", dr_result);
          /* Update response data */
          ipcMsg = MSG_RS;
          ipcData = E_TLAPI_DRV_INVALID_PARAMETERS;
          continue;
        }

        if (TEES_HandleDriverCommand(0, pMarshal, ipcMsgSize, pMarshal, (uint32_t *)&ipcMsgSize) != TEE_SUCCESS) {
          TEES_LOG(TEES_LOG_LEVEL_ERROR, "Command handling error.\n");
        }

        dr_result = drApiUnmapTaskBuffers(client_task_id);
        if (DRAPI_OK != dr_result) {
          TEES_LOG(TEES_LOG_LEVEL_ERROR, "Fail to unmap task buffers %x\n", dr_result);
        }

        /* Update response data */
        ipcMsg = MSG_RS;
        ipcData = TLAPI_OK;
        break;
      }
      default:
        /* Unknown message has been received */
        TEES_LOG(TEES_LOG_LEVEL_ERROR, "Unknown message has been received\n");
#if KINIBI_VERSION < 5
        drApiIpcUnknownMessage(&ipcClient, &ipcMsg, &ipcData);
#endif
        ipcMsg = MSG_RS;
        ipcData = E_TLAPI_DRV_UNKNOWN;
        break;
    }
  }
}

#if KINIBI_VERSION < 5
static inline int InitIpchStack() {
  /* Ensure thread stack is clean */
  TEE_MemFill(GET_STACK_BOTTOM(drIpchStack), STACK_DBG_INIT_VALUE,
              GET_STACK_SIZE(drIpchStack));

  stack_ipch = getStackTop(drIpchStack);
  return 0;
}
#else
static inline int InitIpchStack() {
  int ret = drApiStackAlloc(THREAD_STACK_SIZE, (uint8_t **)&stack_ipch);
  if (ret != DRAPI_OK) {
      TEES_LOG(TEES_LOG_LEVEL_ERROR, "drApiStackAlloc(): error %d.\n", ret);
      return -1;
  }
  return 0;
}
#endif

uint32_t *GetIpchStackTop() {
  return stack_ipch;
}

/**
 * @brief IPC handler thread init
 */
void InitIpch(void) {
  if (InitIpchStack() != 0) {
    return;
  }

  /* Start IPC handler thread. Exception handler thread becomes local
     exception handler of IPC handler thread */
  if (DRAPI_OK != drApiStartThread(
                  DRIVER_THREAD_NO_IPCH,
                  FUNC_PTR(EntryIpch),
                  GetIpchStackTop(),
                  IPCH_PRIORITY,
                  DRIVER_THREAD_NO_EXCH)) {
    TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed drApiStartThread\n");
  }
}
