/*
 * Copyright 2020 Xilinx, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* Update of this file by the user is not encouraged */
#include <ivas/ivas_kernel.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#ifdef XLNX_PCIe_PLATFORM
#include <xrt.h>
#include <ert.h>
#include <xclhal2.h>
#else
#include <xrt/xrt.h>
#include <xrt/ert.h>
#include <xrt/xclhal2.h>
#endif
#include <string.h>
#include <errno.h>
#include <sys/mman.h>

#undef DUMP_REG                 // dump reg_map just before sending ert cmd

enum
{
  LOG_LEVEL_ERROR,
  LOG_LEVEL_WARNING,
  LOG_LEVEL_INFO,
  LOG_LEVEL_DEBUG
};

static int log_level = LOG_LEVEL_WARNING;

#define LOG_MESSAGE(level, ...) {\
  do {\
    char *str; \
    if (level == LOG_LEVEL_ERROR)\
      str = (char*)"ERROR";\
    else if (level == LOG_LEVEL_WARNING)\
      str = (char*)"WARNING";\
    else if (level == LOG_LEVEL_INFO)\
      str = (char*)"INFO";\
    else if (level == LOG_LEVEL_DEBUG)\
      str = (char*)"DEBUG";\
    if (level <= log_level) {\
      printf("[%s:%d] %s: ", __func__, __LINE__, str);\
      printf(__VA_ARGS__);\
      printf("\n");\
    }\
  } while (0); \
}


static int
alloc_xrt_buffer (xclDeviceHandle handle, IVASFrame * frame,
    enum xclBOKind bo_kind, unsigned flags)
{
  frame->bo[0] = xclAllocBO (handle, frame->size[0], bo_kind, flags);
  if (frame->bo[0] == NULLBO) {
    LOG_MESSAGE (LOG_LEVEL_ERROR, "failed to allocate Device BO...");
    return -1;
  }

  frame->vaddr[0] = xclMapBO (handle, frame->bo[0], true);
  if (frame->vaddr[0] == NULL) {
    LOG_MESSAGE (LOG_LEVEL_ERROR, "failed to map BO...");
    xclFreeBO (handle, frame->bo[0]);
    return -1;
  }

  if (bo_kind != XCL_BO_SHARED_VIRTUAL) {
    struct xclBOProperties p;
    if (xclGetBOProperties (handle, frame->bo[0], &p)) {
      LOG_MESSAGE (LOG_LEVEL_ERROR, "failed to get physical address...");
      munmap (frame->vaddr[0], frame->size[0]);
      xclFreeBO (handle, frame->bo[0]);
      return -1;
    }
    frame->paddr[0] = p.paddr;
  }

  frame->meta_data = NULL;
  frame->app_priv = NULL;
  frame->n_planes = 1;

  LOG_MESSAGE (LOG_LEVEL_DEBUG,
      "allocated xrt buffer : bo = %d, paddr = %p, vaddr = %p and size = %d",
      frame->bo[0], (void *) frame->paddr[0], frame->vaddr[0], frame->size[0]);

  return 0;
}

static void
free_xrt_buffer (xclDeviceHandle handle, IVASFrame * frame)
{
  if (!handle || !frame) {
    LOG_MESSAGE (LOG_LEVEL_ERROR, "invalid arguments : handle %p, frame %p",
        handle, frame);
    return;
  }
  if (frame->vaddr[0] && frame->size[0])
    munmap (frame->vaddr[0], frame->size[0]);
  if (handle && frame->bo[0] > 0)
    xclFreeBO (handle, frame->bo[0]);
  memset (frame, 0x00, sizeof (IVASFrame));
}

IVASFrame *
ivas_alloc_buffer (IVASKernel * handle, uint32_t size, IVASMemoryType mem_type,
    IVASFrameProps * props)
{
  IVASFrame *frame = NULL;
  int iret = -1;

  if (!handle) {
    LOG_MESSAGE (LOG_LEVEL_ERROR, "invalid arguments : handle %p", handle);
    goto error;
  }

  frame = (IVASFrame *) calloc (1, sizeof (IVASFrame));
  if (!frame) {
    LOG_MESSAGE (LOG_LEVEL_ERROR, "failed to allocate ivas_frame");
    goto error;
  }
  frame->mem_type = mem_type;

  if (mem_type == IVAS_INTERNAL_MEMORY) {
    frame->size[0] = size;

    iret = alloc_xrt_buffer (handle->xcl_handle, frame, XCL_BO_DEVICE_RAM, 0);
    if (iret < 0) {
      LOG_MESSAGE (LOG_LEVEL_ERROR, "failed to allocate internal memory");
      goto error;
    }
  } else {
    if (!props || !props->width || !props->height
        || (props->fmt == IVAS_VMFT_UNKNOWN)) {
      LOG_MESSAGE (LOG_LEVEL_ERROR, "invalid arguments for properties");
      goto error;
    }

    memcpy (&(frame->props), props, sizeof (IVASFrameProps));
    if (!handle->alloc_func) {
      LOG_MESSAGE (LOG_LEVEL_ERROR,
          "app did not set alloc_func callback function");
      goto error;
    }

    iret = handle->alloc_func (handle, frame, handle->cb_user_data);
    if (iret < 0) {
      LOG_MESSAGE (LOG_LEVEL_ERROR, "failed to allocate frame memory");
      goto error;
    }
  }
  return frame;

error:
  if (frame) {
    ivas_free_buffer (handle, frame);
    free (frame);
  }
  return NULL;
}

void
ivas_free_buffer (IVASKernel * handle, IVASFrame * ivas_frame)
{
  if (ivas_frame->mem_type == IVAS_INTERNAL_MEMORY) {
    free_xrt_buffer (handle, ivas_frame);
  } else {
    if (!handle->free_func) {
      LOG_MESSAGE (LOG_LEVEL_ERROR,
          "app did not set free_func callback function");
    } else {
      handle->free_func (handle, ivas_frame, handle->cb_user_data);
    }
  }
  free (ivas_frame);
}

void
ivas_register_write (IVASKernel * handle, void *src, size_t size, size_t offset)
{
  if (handle->is_multiprocess) {
    struct ert_start_kernel_cmd *ert_cmd =
        (struct ert_start_kernel_cmd *) (handle->ert_cmd_buf->user_ptr);
    uint32_t *src_array = (uint32_t *) src;
    size_t cur_min = offset;
    size_t cur_max = offset + size;
    int32_t entries = size / sizeof (uint32_t);
    int32_t start = offset / sizeof (uint32_t);
    int32_t i;

    for (i = 0; i < entries; i++)
      ert_cmd->data[start + i] = src_array[i];

    if (cur_max > handle->max_offset)
      handle->max_offset = cur_max;

    if (cur_min < handle->min_offset)
      handle->min_offset = cur_min;
  } else {
      uint32_t *value = (uint32_t*)src;
      xclRegWrite((xclDeviceHandle )handle->xcl_handle, handle->cu_idx, offset, value[0]);
  }
  return;
}

void ivas_register_read (IVASKernel *handle, void *src, size_t size, size_t offset)
{
  uint32_t *value = (uint32_t*)src;
  xclRegRead((xclDeviceHandle )handle->xcl_handle, handle->cu_idx, offset, &(value[0]));
}

int32_t
ivas_kernel_start (IVASKernel * handle)
{
  struct ert_start_kernel_cmd *ert_cmd =
      (struct ert_start_kernel_cmd *) (handle->ert_cmd_buf->user_ptr);

  ert_cmd->state = ERT_CMD_STATE_NEW;

#ifdef XLNX_PCIe_PLATFORM
  ert_cmd->opcode = handle->is_softkernel ? ERT_SK_START: ERT_START_CU;
#else
  ert_cmd->opcode = ERT_START_CU;
#endif
  ert_cmd->cu_mask = 1 << handle->cu_idx;
  ert_cmd->count = (handle->max_offset >> 2) + 1;

#ifdef DUMP_REG
  {
    int i;
    for (i = 0; i < ert_cmd->count; i++)
      printf ("index 0x%x - 0x%x\n", i * sizeof (uint32_t), ert_cmd->data[i]);
  }
#endif

  if (xclExecBuf (handle->xcl_handle, handle->ert_cmd_buf->bo)) {
    LOG_MESSAGE (LOG_LEVEL_ERROR, "failed to issue XRT command");
    return -1;
  }
  LOG_MESSAGE (LOG_LEVEL_DEBUG, "Submitted command to kernel");

  return 0;
}

int32_t
ivas_kernel_done (IVASKernel * handle, int32_t timeout)
{
  struct ert_start_kernel_cmd *ert_cmd =
      (struct ert_start_kernel_cmd *) (handle->ert_cmd_buf->user_ptr);
  int ret;
  int retry_count = MAX_EXEC_WAIT_RETRY_CNT;

  LOG_MESSAGE (LOG_LEVEL_DEBUG, "Going to wait for kernel command to finish");

  do {
    ret = xclExecWait (handle->xcl_handle, timeout);
    if (ret < 0) {
      LOG_MESSAGE (LOG_LEVEL_ERROR, "ExecWait ret = %d. reason : %s", ret,
          strerror (errno));
      return -1;
    } else if (!ret) {
      LOG_MESSAGE (LOG_LEVEL_WARNING, "timeout...retry execwait");
      if (retry_count-- <= 0) {
        LOG_MESSAGE (LOG_LEVEL_ERROR,
            "max retry count %d reached..returning error",
            MAX_EXEC_WAIT_RETRY_CNT);
        return -1;
      }
    }
  } while (ert_cmd->state != ERT_CMD_STATE_COMPLETED);

  LOG_MESSAGE (LOG_LEVEL_DEBUG, "successfully completed kernel command");

  return 0;
}

#ifdef XLNX_PCIe_PLATFORM
int32_t
ivas_sync_data (IVASKernel *handle, IVASSyncDataFlag flag, IVASFrame *frame)
{
  int iret, plane_id;
  enum xclBOSyncDirection sync_flag = (flag == IVAS_SYNC_DATA_TO_DEVICE) ? XCL_BO_SYNC_BO_TO_DEVICE:IVAS_SYNC_DATA_FROM_DEVICE;

  for (plane_id = 0; plane_id < frame->n_planes; plane_id++) {
    LOG_MESSAGE(LOG_LEVEL_DEBUG, "plane %d syncing %s : bo = %d, size = %d",
        plane_id, sync_flag == XCL_BO_SYNC_BO_TO_DEVICE ? "to device" : "from device",
        frame->bo[plane_id], frame->size[plane_id]);

    iret = xclSyncBO(handle->xcl_handle, frame->bo[plane_id], sync_flag, frame->size[plane_id], 0);
    if (iret != 0) {
      LOG_MESSAGE(LOG_LEVEL_ERROR, "xclSyncBO failed %d, reason : %s", iret, strerror(errno));
      return iret;
    }
  }
  return 0;
}
#endif
