// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */
#include <linux/uaccess.h>
#include <linux/compat.h>
#include <common/mdla_device.h>
#include <common/mdla_ioctl.h>
#include <common/mdla_cmd_proc.h>
#include <common/mdla_power_ctrl.h>
#include <interface/mdla_cmd_data_v2_0.h>
#include <utilities/mdla_debug.h>
#include <utilities/mdla_util.h>
#include <comm_driver.h>

static int (*mdla_ioctl_perf)(struct file *file, u32 command,
		unsigned long arg, bool need_pwr_on);

static int ioctl_to_comm(struct comm_kmem *mem, void *data, int svp)
{
	struct ioctl_malloc *malloc;
	struct ioctl_malloc_svp *malloc_svp;
	u32 type;

	if (svp) {
		malloc_svp = (struct ioctl_malloc_svp *)data;
		mem->secure = malloc_svp->secure;
		mem->pipe_id = malloc_svp->pipe_id;
		mem->size = malloc_svp->size;
		mem->align = malloc_svp->align;
		type = malloc_svp->mem_type;
		mem->iova = malloc_svp->mva;
		mem->kva = malloc_svp->kva;

	} else {
		malloc = (struct ioctl_malloc *)data;
		mem->pipe_id = 0xFFFFFFFF;
		mem->size = malloc->size;
		mem->align = 0;
		type = malloc->type;
		mem->iova = malloc->mva;
		mem->kva = malloc->kva;
	}

	switch (type) {
	case MEM_DRAM_CACHED:
	case MEM_IOMMU_CACHED:
		mem->cache = 1;
		mem->mem_type = APU_COMM_MEM_DRAM_DMA;
		mem->iova |=  0x200000000;
		break;
	case MEM_DRAM:
	case MEM_IOMMU:
		mem->mem_type = APU_COMM_MEM_DRAM_DMA;
		mem->iova |=  0x200000000;
		break;
	case MEM_GSM:
		mem->mem_type = APU_COMM_MEM_VLM;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static int comm_to_ioctl(struct comm_kmem *mem, void *data, int svp)
{
	struct ioctl_malloc *malloc;
	struct ioctl_malloc_svp *malloc_svp;
	u32 type;
	u64 phyaddr;

	phyaddr = mem->iova;

	if (svp) {
		malloc_svp = (struct ioctl_malloc_svp *)data;
		type = malloc_svp->mem_type;
		malloc_svp->kva = (u64)mem->kva;
		malloc_svp->mva = mem->iova & 0xFFFFFFFF;
		malloc_svp->pa = mem->phy;

	} else {
		malloc = (struct ioctl_malloc *)data;
		type = malloc->type;
		malloc->kva = (u64)mem->kva;
		malloc->mva = mem->iova & 0xFFFFFFFF;
		malloc->mva_h = (mem->iova >> 32) & 0xFFFFFFFF;
		malloc->pa = mem->phy;
	}

	return 0;
}

static struct comm_user *ioctl_user(struct file *file)
{
	static struct comm_user *kernel_user;

	if (file == NULL) {
		if (kernel_user == NULL) {
			if (comm_util_get_cb()->create_comm_user(&kernel_user)) {
				mdla_err("%s: create kernel user fail\n", __func__);
				return NULL;
			}
		}
		return kernel_user;
	}

	if (file->private_data)
		return (struct comm_user *)file->private_data;

	return NULL;
}

static long mdla_ioctl_run_cmd(struct file *file, void *ioctl_cmd, int ioctl_svp)
{
	int ret;
	struct mdla_run_cmd_sync_svp cmd_ut;
	struct ioctl_run_cmd *run_cmd = ioctl_cmd;
	struct comm_user_dev *udev;

	if (!ioctl_svp) {
		memset(&cmd_ut, 0, sizeof(cmd_ut));
		cmd_ut.req.secure = 0;
		cmd_ut.req.pipe_id = UINT_MAX;
		cmd_ut.req.buf[0].size = 8;
		cmd_ut.req.buf[0].iova = run_cmd->mva;
		cmd_ut.req.buf[0].buf_type = EN_MDLA_BUF_TYPE_CODE;
		cmd_ut.req.buf[0].buf_source = MEM_IOMMU;
		cmd_ut.req.buf_count = 1;
		cmd_ut.req.cmd_count = run_cmd->count;
	} else {
		memcpy(&cmd_ut, ioctl_cmd, sizeof(struct ioctl_run_cmd_sync_svp));
	}

	cmd_ut.cmd_info.cmd_user = ioctl_user(file);

	udev = comm_util_get_cb()->lock_udev(cmd_ut.cmd_info.cmd_user);
	if (!udev)
		return -EFAULT;

	ret = mdla_cmd_ops_get()->ut_run_sync(&cmd_ut.req,
			&cmd_ut.res, mdla_get_device(0));

	comm_util_get_cb()->unlock_udev(udev);

	return ret;
}

static int mdla_ioctl_load_model(struct file *file, struct ioctl_load_model_svp *model)
{
	return mdla_cmd_plat_cb()->load_model(model);
}

static int mdla_ioctl_unload_model(struct file *file, struct ioctl_load_model_svp *model)
{
	return mdla_cmd_plat_cb()->unload_model(model);
}

static int mdla_ioctl_update_model(struct file *file, struct ioctl_update_model_svp *data)
{
	struct comm_user *u = ioctl_user(file);

	return mdla_cmd_plat_cb()->update_model_param(u, data);
}

static int mdla_ioctl_reset_model(struct file *file, struct ioctl_update_model_svp *data)
{
	struct comm_user *u = ioctl_user(file);

	return mdla_cmd_plat_cb()->reset_model_param(u, data);
}

static int mdla_ioctl_mem_alloc(struct file *file, void *ioctl_data, int from_user, int svp)
{
	struct comm_kmem mem;
	struct comm_user *u = ioctl_user(file);

	memset(&mem, 0, sizeof(mem));

	if (ioctl_to_comm(&mem, ioctl_data, svp)) {
		mdla_err("%s: fail, copy param, type:%x, dev:%lx\n",
			__func__, mem.mem_type, u);
		return -EINVAL;
	}

	if (u && comm_util_get_cb()->alloc_user_mem(u, &mem)) {
		mdla_err("%s: fail, size:%d, dev:%lx\n", __func__, mem.size, u);
		return -EFAULT;
	}

	if (from_user)
		mem.kva = 0;

	if (comm_to_ioctl(&mem, ioctl_data, svp))
		return -EINVAL;

	return 0;
}

static int mdla_ioctl_mem_free(struct file *file, void *ioctl_data, int svp)
{
	struct comm_kmem mem;
	struct comm_user *u = ioctl_user(file);

	memset(&mem, 0, sizeof(mem));

	if (ioctl_to_comm(&mem, ioctl_data, svp))
		return -EINVAL;

	if (u && comm_util_get_cb()->free_user_mem(u, &mem)) {
		mdla_err("%s: fail, iova:%llx size:%d dev:%lx\n",
				__func__, mem.iova, mem.size, u);
		return -EFAULT;
	}

	return 0;
}

static int mdla_ioctl_gsm_info(struct ioctl_config *cfg, int from_user)
{
	struct comm_kmem mem;

	mem.mem_type = APU_COMM_MEM_VLM;
	if (comm_util_get_cb()->vlm_info(&mem)) {
		mdla_err("%s: failed!!\n", __func__);
		return -EFAULT;
	}

	cfg->arg[0] = mem.size;
	cfg->arg[1] = mem.iova;
	cfg->arg[2] = mem.phy;

	if (!from_user)
		cfg->arg[3] = mem.kva;

	mdla_drv_debug("%s: size:%llu, phy:%llx, iova:%llx\n", __func__,
			cfg->arg[0],
			cfg->arg[2],
			cfg->arg[1]);

	return 0;
}

static bool in_vlm_region(unsigned long long addr, unsigned long long size)
{
	static unsigned long long vlm_start = 0, vlm_end;
	struct comm_kmem mem;

	if (!vlm_start) {
		if (comm_util_get_cb()->vlm_info(&mem))
			return false;

		vlm_start = mem.phy;
		vlm_end = mem.phy + mem.size;
	}

	if (addr >= vlm_start && addr <= vlm_end
		&& size <=  vlm_end - addr)
		return true;

	return false;
}

static int mdla_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret = -EFAULT;
	struct comm_user *u = ioctl_user(filp);
	struct comm_kmem mem;

	mem.iova = (unsigned long long)vma->vm_pgoff << PAGE_SHIFT;
	mem.size = 0; //vma region could be large than buf size

	if (comm_util_get_cb()->get_comm_user(u)) {
		mdla_err("%s(): fail, get_user %lx\n", __func__, u);
		return -EINVAL;
	}

	if (in_vlm_region(mem.iova, mem.size)) {
		ret = comm_util_get_cb()->map_uva(filp, vma);
		if (ret)
			mdla_err("%s(): fail, mapping vlm %llx\n",
					__func__, mem.iova);
	} else {
		mem.mem_type = APU_COMM_MEM_DRAM_DMA;

		if (comm_util_get_cb()->get_user_mem(u, &mem)) {
			mdla_err("%s(): fail, get_user_mem %lx/%llx\n",
					__func__, u, mem.iova);
			goto put_user;
		}

		ret = comm_util_get_cb()->map_uva(filp, vma);
		if (ret)
			mdla_err("%s(): fail, mapping iova %llx\n",
					__func__, mem.iova);

		ret = comm_util_get_cb()->put_user_mem(u, &mem);

	}

put_user:
	comm_util_get_cb()->put_comm_user(u);

	return ret;
}

static int copy_from_ioctl(void *dst, void *src,
		unsigned int size, unsigned int from_user)
{
	if (from_user) {
		if (copy_from_user(dst, src, size))
			return -EFAULT;
	} else {
		memcpy(dst, src, size);
	}

	return 0;
}

static int copy_to_ioctl(void *dst, void *src,
		unsigned int size, unsigned int from_user)
{
	if (from_user) {
		if (copy_to_user(dst, src, size))
			return -EFAULT;
	} else {
		memcpy(dst, src, size);
	}

	return 0;
}

static long mdla_ioctl_config(void *arg, unsigned int from_user)
{
	long ret = 0;
	struct ioctl_config cfg;

	if (from_user) {
		if (copy_from_user(&cfg, arg, sizeof(cfg)))
			return -EFAULT;
	} else {
		memcpy(&cfg, (void *)arg, sizeof(cfg));
	}

	switch (cfg.op) {
	case MDLA_CFG_NONE:
		break;
	case MDLA_CFG_TIMEOUT_GET:
		cfg.arg[0] = mdla_dbg_read_u32(FS_TIMEOUT);
		cfg.arg_count = 1;
		break;
	case MDLA_CFG_TIMEOUT_SET:
		if (cfg.arg_count == 1)
			mdla_dbg_write_u32(FS_TIMEOUT, cfg.arg[0]);
		break;
	case MDLA_CFG_FIFO_SZ_GET:
		cfg.arg[0] = 1;
		cfg.arg_count = 1;
		break;
	case MDLA_CFG_FIFO_SZ_SET:
		return -EINVAL;
	case MDLA_CFG_GSM_INFO:
		ret = mdla_ioctl_gsm_info(&cfg, from_user);
		cfg.arg_count = 4;
		break;
	default:
		return -EINVAL;
	}

	if (from_user) {
		if (copy_to_user((void *)arg, &cfg, sizeof(cfg)))
			return -EFAULT;
	} else {
		memcpy((void *)arg, &cfg, sizeof(cfg));
	}

	return ret;
}

static long mdla_ioctl_entry(struct file *file,
			unsigned int command,
			unsigned long arg,
			unsigned int from_user)
{
	int ret = 0;
	union {
		struct ioctl_malloc malloc;
		struct ioctl_run_cmd runcmd;
		struct ioctl_run_cmd_sync_svp runcmd_svp;
		struct ioctl_malloc_svp malloc_svp;
		struct ioctl_load_model_svp load_svp;
		struct ioctl_update_model_svp update_svp;
	} param;

	switch (command) {
	case IOCTL_CONFIG:
		ret = mdla_ioctl_config((void *)arg, from_user);
		break;
	case IOCTL_MALLOC:
		if (copy_from_ioctl(&param.malloc, (void *)arg,
					sizeof(struct ioctl_malloc),
					from_user))
			return -EFAULT;

		if (mdla_ioctl_mem_alloc(file, &param.malloc, from_user, 0))
			return -EFAULT;

		mdla_drv_debug("ioctl_malloc size:[%d] mva:[0x%x] type:[%d] pid:[%d]\n",
				param.malloc.size,
				param.malloc.mva,
				param.malloc.type,
				current->pid);

		if (copy_to_ioctl((void *)arg, &param.malloc,
					sizeof(struct ioctl_malloc),
					from_user)) {
			mdla_ioctl_mem_free(file, &param.malloc, 0);

			return -EFAULT;
		}
		break;
	case IOCTL_FREE:
		if (copy_from_ioctl(&param.malloc, (void *)arg,
					sizeof(struct ioctl_malloc),
					from_user))
			return -EFAULT;

		if (mdla_ioctl_mem_free(file, &param.malloc, 0))
			return -EFAULT;

		mdla_drv_debug("ioctl_free size:[%d] mva:[0x%x] pid:[%d]\n",
				param.malloc.size,
				param.malloc.mva,
				current->pid);
		break;
	case IOCTL_RUN_CMD_SYNC:
		if (copy_from_ioctl(&param.runcmd, (void *)arg,
					sizeof(struct ioctl_run_cmd),
					from_user))
			return -EFAULT;

		mdla_drv_debug("run_cmd_sync mva:[0x%x] count:[%d] pid:[%d]\n",
				param.runcmd.mva,
				param.runcmd.count,
				current->pid);

		ret = mdla_ioctl_run_cmd(file, &param.runcmd, 0);
		break;
	case IOCTL_MALLOC_SVP:
		if (copy_from_ioctl(&param.malloc_svp, (void *)arg,
					sizeof(struct ioctl_malloc_svp),
					from_user))
			return -EFAULT;

		if (mdla_ioctl_mem_alloc(file, &param.malloc_svp, from_user, 1))
			return -EFAULT;

		mdla_drv_debug("malloc_svp size:[%d] mva:[0x%x] pid:[%d]\n",
				param.malloc_svp.size,
				param.malloc_svp.mva,
				current->pid);

		if (copy_to_ioctl((void *)arg, &param.malloc_svp,
					sizeof(struct ioctl_malloc_svp),
					from_user))
			return -EFAULT;
		break;
	case IOCTL_FREE_SVP:
		if (copy_from_ioctl(&param.malloc_svp, (void *)arg,
					sizeof(struct ioctl_malloc_svp),
					from_user))
			return -EFAULT;

		mdla_drv_debug("ioctl_free_svp size:[%d] mva:[0x%x] pid:[%d]\n",
				param.malloc_svp.size,
				param.malloc_svp.mva,
				current->pid);

		ret = mdla_ioctl_mem_free(file, &param.malloc_svp, 1);
		break;
	case IOCTL_RUN_CMD_SYNC_SVP:
		if (copy_from_ioctl(&param.runcmd_svp, (void *)arg,
					sizeof(struct ioctl_run_cmd_sync_svp),
					from_user))
			return -EFAULT;

		mdla_drv_debug("run_cmd_svp secure:[%d] buf_count:[%d] model:[%x] pid:[%d]\n",
				param.runcmd_svp.req.secure,
				param.runcmd_svp.req.buf_count,
				param.runcmd_svp.req.model,
				current->pid);

		ret = mdla_ioctl_run_cmd(file, &param.runcmd_svp, 1);
		break;
	case IOCTL_LOAD_MODEL_SVP:
		if (copy_from_ioctl(&param.load_svp, (void *)arg,
					sizeof(struct ioctl_load_model_svp),
					from_user))
			return -EFAULT;

		mdla_drv_debug("load_model_svp model:[%d] buf_addr:[%llx] pid:[%d]\n",
				param.load_svp.model,
				param.load_svp.buf_addr,
				current->pid);

		ret = mdla_ioctl_load_model(file, &param.load_svp);
		break;
	case IOCTL_UNLOAD_MODEL_SVP:
		if (copy_from_ioctl(&param.load_svp, (void *)arg,
					sizeof(struct ioctl_load_model_svp),
					from_user))
			return -EFAULT;

		mdla_drv_debug("unload_model_svp model:[%d] buf_addr:[%llx] pid:[%d]\n",
				param.load_svp.model,
				param.load_svp.buf_addr,
				current->pid);

		ret = mdla_ioctl_unload_model(file, &param.load_svp);
		break;
	case IOCTL_UPDATE_MODEL_SVP:
		if (copy_from_ioctl(&param.update_svp, (void *)arg,
					sizeof(struct ioctl_update_model_svp),
					from_user))
			return -EFAULT;

		mdla_drv_debug("update_model num_model:[%x] num_buf:[%x] pid:[%d]\n",
				param.update_svp.num_model,
				param.update_svp.num_buf,
				current->pid);

		ret = mdla_ioctl_update_model(file, &param.update_svp);
		break;
	case IOCTL_RESET_MODEL_SVP:
		if (copy_from_ioctl(&param.update_svp, (void *)arg,
					sizeof(struct ioctl_update_model_svp),
					from_user))
			return -EFAULT;

		mdla_drv_debug("reset_model num_model:[%x] num_buf:[%x] pid:[%d]\n",
				param.update_svp.num_model,
				param.update_svp.num_buf,
				current->pid);

		ret = mdla_ioctl_reset_model(file, &param.update_svp);
		break;
	default:
		ret = -EINVAL;
	}

	return ret;
}

static long mdla_ioctl(struct file *file, unsigned int command, unsigned long arg)
{
	return mdla_ioctl_entry(file, command, arg, 1);
}

long mdla_kernel_ioctl(unsigned int command, void *arg)
{
	return mdla_ioctl_entry(NULL, command, (unsigned long)arg, 0);
}
EXPORT_SYMBOL(mdla_kernel_ioctl);

#ifdef CONFIG_COMPAT
static long mdla_compat_ioctl(struct file *file,
		unsigned int cmd, unsigned long arg)
{
	return mdla_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
}
#endif

static int mdla_open(struct inode *inodep, struct file *file)
{
	struct comm_user *u;

	if (comm_util_get_cb()->create_comm_user(&u)) {
		mdla_err("%s: create user fail\n", __func__);
		return -EFAULT;
	}

	file->private_data = u;
	mdla_drv_debug("%s(): device(%lx) has been opened\n", __func__, u);

	return 0;
}

static int mdla_release(struct inode *inodep, struct file *file)
{
	struct comm_user *u;

	u = file->private_data;

	mdla_cmd_plat_cb()->release_model_param(u);

	if (comm_util_get_cb()->delete_comm_user(u)) {
		mdla_err("%s(): delete user(%lx) fail\n", __func__, u);
		return -EBADF;
	}

	mdla_drv_debug("%s(): device(%lx) successfully closed\n", __func__, u);

	return 0;
}

const static struct file_operations fops = {
	.open		= mdla_open,
	.unlocked_ioctl	= mdla_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= mdla_compat_ioctl,
#endif
	.mmap		= mdla_mmap,
	.release	= mdla_release,
};

const struct file_operations *mdla_ioctl_get_fops(void)
{
	return &fops;
}

void mdla_ioctl_register_perf_handle(int (*pmu_ioctl)(struct file *filp,
			u32 command,
			unsigned long arg,
			bool need_pwr_on))
{
	if (pmu_ioctl)
		mdla_ioctl_perf	= pmu_ioctl;
}

void mdla_ioctl_unregister_perf_handle(void)
{
	mdla_ioctl_perf	= NULL;
}

