/*
 * The MIT License (MIT)
 *
 * COPYRIGHT (C) 2017 Institute of Electronics and Computer Science (EDI), Latvia.
 * AUTHOR: Rihards Novickis (rihards.novickis@edi.lv)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

/*!
 *  Copyright (c) 2018 by Contributors
 * \file cma_api.cc
 * \brief Application layer implementation for contigous memory allocation.
 */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include "cma_api.h"

#ifndef CMA_IOCTL_MAGIC
#define CMA_IOCTL_MAGIC       0xf2
#endif

#define CMA_ALLOC_CACHED      _IOC(_IOC_WRITE|_IOC_READ,  CMA_IOCTL_MAGIC, 1, 4)
#define CMA_ALLOC_NONCACHED   _IOC(_IOC_WRITE|_IOC_READ,  CMA_IOCTL_MAGIC, 2, 4)
#define CMA_FREE              _IOC(_IOC_WRITE,            CMA_IOCTL_MAGIC, 3, 4)
#define CMA_GET_PHY_ADDR      _IOC(_IOC_WRITE|_IOC_READ,  CMA_IOCTL_MAGIC, 4, 4)
#define CMA_GET_SIZE          _IOC(_IOC_WRITE|_IOC_READ,  CMA_IOCTL_MAGIC, 5, 4)

#define CMA_IOCTL_MAXNR       5

#ifndef CMA_DEBUG
  #define CMA_DEBUG           0
#endif
#ifndef DRIVER_NODE_NAME
  #define DRIVER_NODE_NAME    "cma"
#endif

#if CMA_DEBUG == 1
  #define __DEBUG(fmt, args...)  printf("CMA_API_DEBUG: " fmt, ##args)
#else
  #define __DEBUG(fmt, args...)
#endif

#define ROUND_UP(N, S)     ((((N) + (S) - 1) / (S)) * (S))


/* Private functions */
void *cma_alloc(size_t size, unsigned ioctl_cmd);

/* Global file descriptor */
int cma_fd = 0;

int cma_init(void) {
  __DEBUG("Opening \"/dev/" DRIVER_NODE_NAME "\" file\n");

  cma_fd = open("/dev/" DRIVER_NODE_NAME, O_RDWR);
  if (cma_fd == -1) {
    __DEBUG("Failed to initialize api - \"%s\"\n", strerror(errno));
    return -1;
  }

  return 0;
}

int cma_release(void) {
  __DEBUG("Closing \"/dev/" DRIVER_NODE_NAME "\" file\n");

  if (close(cma_fd) == -1) {
    __DEBUG("Failed to finilize api - \"%s\"\n", strerror(errno));
    return -1;
  }

  return 0;
}

void *cma_alloc_cached(size_t size) {
  return cma_alloc(size, CMA_ALLOC_CACHED);
}

void *cma_alloc_noncached(size_t size) {
  return cma_alloc(size, CMA_ALLOC_NONCACHED);
}

int cma_free(void *mem) {
  __DEBUG("Releasing contigous memory from 0x%x\n", (unsigned)mem);
  unsigned data, v_addr;

  /* save user space pointer value */
  data   = (unsigned)mem;
  v_addr = (unsigned)mem;

  if ( ioctl(cma_fd, CMA_GET_SIZE, &data) == -1 ) {
    __DEBUG("cma_free - ioctl command unsuccsessful - 0\n");
    return -1;
  }
  /* data now contains size */

  /* unmap memory */
  munmap(mem, data);

  /* free cma entry */
  if ( ioctl(cma_fd, CMA_FREE, &v_addr) == -1 ) {
    __DEBUG("cma_free - ioctl command unsuccsessful - 1\n");
    return -1;
  }

  return 0;
}

unsigned cma_get_phy_addr(void *mem) {
  unsigned data;
  __DEBUG("Getting physical address from 0x%x\n", (unsigned)mem);

  /* save user space pointer value */
  data = (unsigned)mem;

  /* get physical address */
  if ( ioctl(cma_fd, CMA_GET_PHY_ADDR, &data) == -1 ) {
    __DEBUG("cma_free - ioctl command unsuccsessful\n");
    return 0;
  }
  /* data now contains physical address */

  return data;
}


void *cma_alloc(size_t size, unsigned ioctl_cmd) {
  unsigned data;
  void   *mem;
  __DEBUG("Allocating 0x%x bytes of contigous memory\n", size);

  /* Page align size */
  size = ROUND_UP(size, getpagesize());

  /* ioctl cmd to allocate contigous memory */
  data = (unsigned)size;
  if ( ioctl(cma_fd, ioctl_cmd, &data) == -1 ) {
    __DEBUG("cma_alloc - ioctl command unsuccsessful\n");
    return NULL;
  }

  /* at this point phy_addr is written to data */

  /* mmap memory */
  mem = mmap(NULL, size, PROT_WRITE | PROT_READ, MAP_SHARED, cma_fd, data);
  if (mem == MAP_FAILED) {
    __DEBUG("cma_alloc - mmap unsuccsessful\n");
    return NULL;
  }

  return mem;
}
