/**
 * @file driver_exch.c
 * @brief <t-base driver exception 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 <tees_log.h>

/**
 * @brief Kernel exceptions
 */
typedef enum {
  kUnknown,             /*!< unknown exception */
  kSyscall,             /*!< invalid syscall number */
  kSegmentation,        /*!< illegal memory access */
  kAlignment,           /*!< misaligned memory access */
  kUndefineInstruction  /*!< undefined instruction */
} KernelExceptions;

/**
 * @brief Exchange registers of current thread or another thread.
 * @param [in] thread Thread no
 * @param [in] ip IP value to be set
 * @param [in] sp SP value to be set
 * @retval DRAPI_OK or relevant error code.
 */
static drApiResult_t RestartThread(threadno_t thread, addr_t ip, addr_t sp);

/**
 * @brief Cleanup function
 */
static void DoCleanup(void) {
  /* TODO: Add cleanup code here */
}

/**
 * @brief Exception handler loop
 */
_NORETURN void ExchLoop(void) {
  threadno_t faulted_thread;
  threadid_t ipc_partner;
  uint32_t mr0, mr1, mr2;
  addr_t ip;
  addr_t sp;

  TEES_LOG(TEES_LOG_LEVEL_VERBOSE, "Exception handler thread is running.\n");

  for (;;) {
    ipc_partner = 0;
    mr0 = 0;
    mr1 = 0;
    mr2 = 0;

    /* Wait for exception */
    if (DRAPI_OK != drApiIpcWaitForMessage(&ipc_partner, &mr0, &mr1, &mr2)) {
      /* Unable to receive IPC message */
      TEES_LOG(TEES_LOG_LEVEL_ERROR, "Failed drApiIpcWaitForMessage.\n");
      continue;
    }

    TEES_LOG(TEES_LOG_LEVEL_INFO,
             "Exception handler obtained message %d from 0x%x.\n", mr1, mr0);

    /* mr0 holds threadid value of thread that caused the exception */
    faulted_thread = GET_THREADNO(mr0);

    /* Process exception */
    switch (mr1) {
      case kSegmentation: {
        TEES_LOG(TEES_LOG_LEVEL_ERROR, "Segmentation error in %d thread.\n",
                 faulted_thread);

        switch (faulted_thread) {
          case DRIVER_THREAD_NO_IPCH: {
            /* Update sp and ip accordingly */
            ip = FUNC_PTR(IpchLoop);
            sp = GetIpchStackTop();

            /* Resume thread execution */
            if (DRAPI_OK != RestartThread(faulted_thread, ip, sp)) {
              TEES_LOG(TEES_LOG_LEVEL_ERROR, "Restarting IPC thread failed.\n");
            }
            break;
          }
          case DRIVER_THREAD_NO_DCIH: {
            /* Update sp and ip accordingly */
            ip = FUNC_PTR(DcihLoop);
            sp = GetDcihStackTop();

            /* Resume thread execution */
            if (DRAPI_OK != RestartThread(faulted_thread, ip, sp)) {
              TEES_LOG(TEES_LOG_LEVEL_ERROR, "Restarting DCI thread failed.\n");
            }
            break;
          }
          default: {
            TEES_LOG(TEES_LOG_LEVEL_ERROR,
                     "Unknown thread. This should never happen.\n");
            break;
          }
        }

        break;
      }
      case kAlignment:
      case kUndefineInstruction: {
        /* This should never happen. If it does, do the cleanup and exit gracefully */
        DoCleanup();

        TEES_LOG(TEES_LOG_LEVEL_ERROR,
                 "Fatal exception is occurred: %x. Stop all threads.\n", mr1);

        /* Stop DCI handler thread */
        if (DRAPI_OK != drApiStopThread(DRIVER_THREAD_NO_DCIH)) {
          TEES_LOG(TEES_LOG_LEVEL_ERROR,
                   "Unable to stop DCI handler thread.\n");
        }

        /* Stop IPC handler thread */
        if (DRAPI_OK != drApiStopThread(DRIVER_THREAD_NO_IPCH)) {
          TEES_LOG(TEES_LOG_LEVEL_ERROR,
                   "Unable to stop IPC handler thread.\n");
        }

        /* Stop main thread */
        if (DRAPI_OK != drApiStopThread(NILTHREAD)) {
          TEES_LOG(TEES_LOG_LEVEL_ERROR, "Unable to stop main thread.\n");
        }

        /* Will not come to this point */
        break;
      }
      default: {
        TEES_LOG(TEES_LOG_LEVEL_ERROR,
                   "Fatal exception is occurred: %x.\n", mr1);

        /* TODO: Update this sestion accordingly
           Unknown exception occured.
           Do cleanup in case */
        DoCleanup();
        break;
      }
    }
  }
}

static drApiResult_t RestartThread(threadno_t thread, addr_t ip, addr_t sp) {
  drApiResult_t ret = E_INVALID;
  uint32_t ctrl = THREAD_EX_REGS_IP | THREAD_EX_REGS_SP;

  /* Set ip and sp registers */
  ret = drApiThreadExRegs(thread, ctrl, ip, sp);
  if (ret != DRAPI_OK) {
    return ret;
  }

  /* Resume thread */
  ret = drApiResumeThread(thread);

  return ret;
}
