#include "dr_bf.h"

#include "access_control.h"
#include "driver_ipc.h"
#include "driver_log.h"
#include "entry.h"

#include <errno.h>
#include <driver.h>
#include <driver/mem/phys.h>
#include <tees_sys.h>
#include <unistd.h>

#include "memory.h"

#if defined(CONFIG_TEEGRIS30) || defined(CONFIG_TEEGRIS40)
#include <tees_extension.h>
#define RegisterDriver            TEES_InitDriver
#define ReleaseDriver(driver)     TEES_FiniDriver(driver)
#define IsREESharedMemory         TEES_IsREESharedMemory
#else
#define RegisterDriver            TEES_RegisterDriver
#define ReleaseDriver(driver)     TEES_ReleaseDriver(&driver)
#define IsREESharedMemory(...)    (TEE_SUCCESS)
#endif

#if defined(CONFIG_TEEGRIS40)
#define PHYS_DEV_NAME "/dev/phys"
#endif

static struct usr_drv_info* gDrvInfo = NULL;
static int g_file_descriptor_mmap = -1;

static struct drv_info *g_current_caller = NULL;

struct drv_info *GetCurrentCaller(void) {
  return g_current_caller;
}

int GetFileDescriptorMmap(void) {
  return g_file_descriptor_mmap;
}

static int DrvOpen(struct drv_info *info, const char *drvPath, ...) {
  (void)info;
  (void)drvPath;
  LOG_D("DrvOpen() start\n");

  return 0;
}

static int DrvClose(struct drv_info *info) {
  (void)info;
  LOG_D("DrvClose() start\n");

  return 0;
}

static int DrvIoctl(struct drv_info *info, int ioctl_cmd,
                    unsigned long ioctlData) {
  PaTzDrvParam *vaddr = NULL;
  int ret = 0;
  size_t vsize = (AlignToPageUp(ioctlData) - ioctlData);
  LOG_D("ioctl_cmd is %d, size %d\n", ioctl_cmd, vsize);
  LOG_D("ioctl_data %lx aligned %lx\n", ioctlData, AlignToPageUp(ioctlData));
  g_current_caller = info;

  vaddr = TEES_AcquireUserBuffer(info, (uint64_t)ioctlData,
      vsize, PROT_READ | PROT_WRITE);
  if (vaddr) {
    ret = (int)TEES_HandleDriverCommand(ioctl_cmd, vaddr, vsize, vaddr, &vsize);
    TEES_ReleaseUserBuffer(vaddr, vsize);
  } else {
    LOG_E("Cannot acquire user buffer!");
    ret = -1;
  }

  g_current_caller = NULL;
  return ret;
}

TEE_Result TA_CreateEntryPoint(void) {
  static struct fops fileOps = {0};

  fileOps.open = DrvOpen;
  fileOps.close = DrvClose;
  fileOps.ioctl = DrvIoctl;

  int ret = RegisterDriver(kProcaTeegrisDriverName, &fileOps, ACC_PERM_SEC_DRV,
      &gDrvInfo);
  if (ret) {
    LOG_D("TEES_RegisterDriver failed, ret = %d, errno = %d\n", ret, errno);
    return TEE_ERROR_GENERIC;
  }

  LOG_I("Driver registered\n");

  g_file_descriptor_mmap = open(PHYS_DEV_NAME, O_RDWR);
  if (g_file_descriptor_mmap == -1) {
    LOG_E("Failed open device.\n");
    LOG_D("Device %s errno %d\n", PHYS_DEV_NAME, errno);
    return TEE_ERROR_GENERIC;
  }

  return TEE_SUCCESS;
}

void TA_DestroyEntryPoint(void) {
  if (close(g_file_descriptor_mmap)) {
    LOG_E("Failed close device.\n");
    LOG_D("Device %s errno %d\n", PHYS_DEV_NAME, errno);
  }

  int ret = ReleaseDriver(gDrvInfo);
  if (ret) {
    LOG_D("TEES_ReleaseDriver failed, ret = %d\n'", ret);
  }

  LOG_I("Driver released\n");
}

TEE_Result TA_OpenSessionEntryPoint(uint32_t paramTypes, TEE_Param params[4],
                                    void **sessionContext) {
  (void)paramTypes;
  (void)sessionContext;
  (void)params;

  return Entry();
}

void TA_CloseSessionEntryPoint(void *sessionContext) {
  (void)sessionContext;
}

TEE_Result TA_InvokeCommandEntryPoint(void *sessionContext, uint32_t commandID,
                                      uint32_t paramTypes, TEE_Param params[4]) {
  (void)sessionContext;

  if (paramTypes != TEE_PARAM_TYPES(TEE_PARAM_TYPE_MEMREF_INOUT,
                                    TEE_PARAM_TYPE_VALUE_OUTPUT,
                                    TEE_PARAM_TYPE_NONE,
                                    TEE_PARAM_TYPE_NONE)) {
    LOG_E("Type of input parameters have other type\n");
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (IsREESharedMemory(
      TEE_MEMORY_ACCESS_READ | TEE_MEMORY_ACCESS_WRITE,
      params[0].memref.buffer, params[0].memref.size) != TEE_SUCCESS) {
    LOG_E("Memory access check error\n");
    return TEE_ERROR_ACCESS_DENIED;
  }

  TEE_Result status = TEES_HandleDriverCommand(commandID,
      (void *)params[0].memref.buffer, params[0].memref.size,
      (void *)params[0].memref.buffer, &(params[0].memref.size));
  if (status != TEE_SUCCESS) {
    LOG_E("TEES_HandleDriverCommand return error.\n");
    LOG_D("Received result: 0x%x.\n", status);
  }

  params[1].value.a = status;

  return TEE_SUCCESS;
}

TEE_Result PlatformGetCallerUuid(TEE_UUID *uuid) {
  if (!uuid) {
    LOG_E("Invalid argument.\n");
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (sizeof(*uuid) != sizeof(g_current_caller->uuid)) {
    LOG_E("UUIDs have different sizes.\n");
    return TEE_ERROR_SHORT_BUFFER;
  }

  TEE_MemMove(uuid, &g_current_caller->uuid,
              sizeof(g_current_caller->uuid));

  return TEE_SUCCESS;
}
