// SPDX-License-Identifier: GPL-2.0-or-later AND LGPL-2.0-or-later AND LGPL-2.1-or-later

#include "mvb_heap.h"
#include "mvb_heap_impl.h"

#include <linux/ion.h>
#include <linux/ion_mtk.h>
#include <ion/ion.h>

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include <gst/gst.h>
#include <linux/dma-buf.h>
#include <sys/mman.h>

#include <BufferAllocator/BufferAllocatorWrapper.h>

#include "mvb_heap_debug.h"

#define MAX_HEAP_COUNT  8

static bool
check_ion_support() {
    return CheckIonSupport();
}

static int
dma_buf_heap_alloc(size_t len, uint32_t mvb_flag, int* share_fd) {
  int ret = -1;
  int dmabuf_fd = -1;
  char *heap_name = NULL;

  BufferAllocator *allocator = CreateDmabufHeapBufferAllocator();
  if (!allocator) {
    GST_ERROR("CreateDmabufHeapBufferAllocator Failed");
    goto done;
  }

  if ((mvb_flag & MVB_HEAP_FLAG_SECURE) != 0) {
    heap_name = "mtk_svp";
  } else {
    heap_name = "mtk_mm";
  }

  dmabuf_fd = DmabufHeapAlloc(allocator, heap_name, len, 0, 0);
  if (dmabuf_fd < 0) {
    GST_ERROR("DmabufHeapAlloc Failed.");
    goto done;
  }
  *share_fd = dmabuf_fd;
  ret = 0;
done:
  if (allocator)
    FreeDmabufHeapBufferAllocator(allocator);
  return ret;
}

static int
_ion_ioctl(int fd, int req, void *arg)
{
  int ret = ioctl (fd, req, arg);

  if (ret < 0)
  {
    g_print ("[fdprovider] error: ioctl\n");
    return -errno;
  }

  return ret;
}

static int
_ion_handle_alloc (int device_fd, size_t len, unsigned int heap_mask, unsigned int flags, int* share_fd)
{
  struct ion_new_allocation_data data = {
    .len          = len,
    .heap_id_mask = heap_mask,
    .flags        = flags,
  };
  int ret;
  MVB_LOGD ("mask: %x flags: %x", heap_mask, flags);

  ret = _ion_ioctl(device_fd, ION_IOC_NEW_ALLOC, &data);
  if (ret < 0) {
    g_print ("[fdprovider] can not alloc\n");
    return ret;
  }

  *share_fd = data.fd;
  return ret;
}

struct mvb_heap_ion_latest_info_t
{
  int device_fd;
  struct ion_heap_query query;
  struct ion_heap_data  heap_data[MAX_HEAP_COUNT];
};

struct mvb_slot_ion_latest_info_t
{
  uint32_t          ion_flag;
  uint32_t          ion_mask;
};

static int
_ion_heap_name_to_mask (struct ion_heap_data* heap_data, int heap_count, const char* ion_heap_name,
                        uint32_t* ion_heap_mask)
{
  int ret = -1;

  if (ion_heap_mask == NULL)
    return ret;

  for (int j = 0; j < heap_count; j++)
  {
    if (strcmp(heap_data[j].name, ion_heap_name) == 0)
    {
      *ion_heap_mask = 1 << heap_data[j].heap_id;

      ret = 0;
      break;
    }
  }

  return ret;
}

static const char*
_get_ion_alloc_param (struct mvb_heap_t* heap, uint32_t mvb_flag,
                          uint32_t* ion_flag,
                          uint32_t* ion_heap_mask)
{
  const char* ion_heap_name = NULL;
  struct mvb_heap_ion_latest_info_t* priv_info = heap->priv;

  if (!ion_flag || !ion_heap_mask)
    return ion_heap_name;

  if ((mvb_flag & MVB_HEAP_FLAG_SECURE) != 0)
  {
    int ret = 0;

    ion_heap_name = "ion_svp_heap";
    ret = _ion_heap_name_to_mask (priv_info->heap_data, priv_info->query.cnt, ion_heap_name, ion_heap_mask);
    if (ret != 0) {
      MVB_LOGE ("fail to find mask: %s", ion_heap_name);
    }

    *ion_flag = 0;
  }
  else
  {
    int ret = 0;

    ion_heap_name = "mtk_ion_mm_heap";
    ret = _ion_heap_name_to_mask (priv_info->heap_data, priv_info->query.cnt, ion_heap_name, ion_heap_mask);
    if (ret != 0) {
      MVB_LOGE ("fail to find mask: %s", ion_heap_name);
    }

    if ((mvb_flag & MVB_HEAP_FLAG_CACHED) != 0)
      *ion_flag = ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC;
    else
      *ion_flag = ION_FLAG_CACHED;
  }

  return ion_heap_name;
}

static int mvb_heap_ion_latest_slot_init (struct mvb_slot_t* slot, int size, uint32_t mvb_flag)
{
  struct mvb_heap_ion_latest_info_t* priv_heap_info = slot->parent->priv;
  struct mvb_slot_ion_latest_info_t* priv_slot_info = slot->priv;
  const char* ion_heap_name;
  int ret;

  MVB_LOGD ("begin");

  if (priv_heap_info == NULL) {
    MVB_LOGE ("invalid pointer to priv heap info");
    return MVB_RET_FAIL;
  }

  /* fill following info
        1) slot->size
        2) slot->mvb_flag
     */
  slot->size     = size;
  slot->mvb_flag = mvb_flag;

  /* malloc for
          1) slot->priv
       */
  priv_slot_info = slot->priv = (struct mvb_slot_ion_latest_info_t*) malloc(
        sizeof(struct mvb_slot_ion_latest_info_t));
  if (priv_slot_info == NULL) {
      MVB_LOGE("[mvb][ion_latest] fail to malloc for mvb_slot_ion_latest_info_t\n");
      return MVB_RET_FAIL;
  }

  if (check_ion_support()) {
     GST_ERROR("ion support.");

    /* fill following by mvb_flag
          1) priv_slot_info->ion_flag
          2) priv_slot_info->ion_mask
        */
    ion_heap_name = _get_ion_alloc_param(slot->parent, slot->mvb_flag,
        &(priv_slot_info->ion_flag), &(priv_slot_info->ion_mask));
    if (ion_heap_name == NULL) {
      MVB_LOGE("[mvb][ion_latest] fail to get alloc param\n");
      return MVB_RET_FAIL;
    }
    MVB_LOGD("find ion heap: %s", ion_heap_name);

    /* fill following info
          1) priv_slot_info->handle
        */
    ret = _ion_handle_alloc(priv_heap_info->device_fd,
                           slot->size,
                           priv_slot_info->ion_mask,
                           priv_slot_info->ion_flag,
                           &(slot->fd)); /* out param */
  } else {
    ret = dma_buf_heap_alloc(slot->size, slot->mvb_flag, &(slot->fd));
  }
  if (ret) {
    MVB_LOGE("[mvb][ion_latest] fail to alloc\n");
    return MVB_RET_FAIL;
  }
  GST_DEBUG("mvb_flag: %d, len: %d, fd: %d", slot->mvb_flag, slot->size, slot->fd);
  MVB_LOGD("create ion fd: %d", slot->fd);
  return MVB_RET_OK;
}

static int mvb_heap_ion_latest_slot_deinit (struct mvb_slot_t* slot)
{
  struct mvb_heap_ion_latest_info_t* priv_heap_info = slot->parent->priv;
  struct mvb_slot_ion_latest_info_t* priv_slot_info = slot->priv;

  MVB_LOGD ("begin: %p", slot);

  if (slot->priv)
  {
    close (slot->fd);
    slot->fd = -1;

    free (slot->priv);
    slot->priv = NULL;
  }

  MVB_LOGD ("end");
  return MVB_RET_OK;
}

static int mvb_heap_ion_latest_init (struct mvb_heap_t* heap)
{
  struct mvb_heap_ion_latest_info_t* priv_info;
  int ret;

  MVB_LOGD ("begin");
  heap->priv = priv_info = (struct mvb_heap_ion_latest_info_t*) malloc (
      sizeof (struct mvb_heap_ion_latest_info_t));
  if (heap->priv == NULL) {
    MVB_LOGE("[mvb][ion_latest] fail to malloc for mvb_heap_ion_latest_info_t\n");
    return MVB_RET_FAIL;
  }

  if (check_ion_support()) {
    priv_info->device_fd = open ("/dev/ion", O_RDWR);
    if (priv_info->device_fd < 0) {
      g_print ("[mvb][ion_latest] fail to open /dev/ion\n");
      free (priv_info);
      return MVB_RET_FAIL;
    }
  }

  /* query heap list */
  memset(&(priv_info->query), 0, sizeof(struct ion_heap_query));
  priv_info->query.cnt   = MAX_HEAP_COUNT;
  priv_info->query.heaps = priv_info->heap_data;
  if (check_ion_support()) {
    ret = ioctl(priv_info->device_fd, ION_IOC_HEAP_QUERY, &(priv_info->query));
    if (ret < 0) {
      MVB_LOGE ("query failed: %d\n", ret);
      free (priv_info);
      return MVB_RET_FAIL;
    }
  }

  MVB_LOGD ("end");
  return MVB_RET_OK;
}

static int mvb_heap_ion_latest_deinit (struct mvb_heap_t* heap)
{
  struct mvb_heap_ion_latest_info_t* priv_info = heap->priv;

  MVB_LOGD ("begin");

  if (priv_info) {
    if (priv_info->device_fd > 0)
      close (priv_info->device_fd);
    priv_info->device_fd = -1;

    free (heap->priv);
    heap->priv = NULL;
  }

  MVB_LOGD ("end");

  return MVB_RET_OK;
}

int buffer_begin_cpu_access(int share_fd)
{
  struct dma_buf_sync sync;
  int ret;

  MVB_LOGD ("share_fd %d\n", share_fd);

  sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW;  /* more flag types */
  ret = ioctl(share_fd, DMA_BUF_IOCTL_SYNC, &sync);

  if (ret)
    MVB_LOGE ("failed <%s>\n", strerror(ret));
  return ret;
}

int buffer_end_cpu_access(int share_fd)
{
  struct dma_buf_sync sync;
  int ret;
  MVB_LOGD ("share_fd %d\n", share_fd);

  sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW;  /* more flag types */
  ret = ioctl(share_fd, DMA_BUF_IOCTL_SYNC, &sync);

  if (ret)
    MVB_LOGE("failed <%s>\n", strerror(ret));
  return ret;
}


static int mvb_heap_ion_latest_mmap (struct mvb_slot_t* slot)
{
  slot->va =  mmap(NULL, slot->size, PROT_READ | PROT_WRITE, MAP_SHARED, slot->fd, 0);
  if (slot->va == MAP_FAILED)
  {
    MVB_LOGE("mmap failed %p\n", slot->va);
  }

  MVB_LOGD ("mmap fd: %d va: %p", slot->fd, slot->va);

  buffer_begin_cpu_access (slot->fd);

  return MVB_RET_OK;
}

static int mvb_heap_ion_latest_umap (struct mvb_slot_t* slot)
{
  int ret;

  if (slot->va == NULL || slot->fd < 0)
    return MVB_RET_FAIL;

  buffer_end_cpu_access (slot->fd);

  ret = munmap(slot->va, slot->size);

  if (slot->va == MAP_FAILED) {
    MVB_LOGE ("munmap failed fd = %d, addr = 0x%p, len = %zu, %d: %s\n",
        slot->fd, slot->va, slot->size, ret, strerror(errno));
  }

  MVB_LOGD ("umap fd: %d va: %p", slot->fd, slot->va);

  slot->va = NULL;

  return MVB_RET_OK;
}
struct mvb_heap_ops_t mvb_heap_ops_ion_latest = {
  .slot_init   = mvb_heap_ion_latest_slot_init,
  .slot_deinit = mvb_heap_ion_latest_slot_deinit,
  .slot_mmap   = mvb_heap_ion_latest_mmap,
  .slot_umap   = mvb_heap_ion_latest_umap,
  .heap_init   = mvb_heap_ion_latest_init,
  .heap_deinit = mvb_heap_ion_latest_deinit,
};

