// SPDX-License-Identifier: GPL-2.0-only
/**
 * Copyright (c) 2014-2023, NVIDIA CORPORATION. All rights reserved.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/tegra_nvadsp.h>
#include <linux/platform_device.h>
#include <uapi/misc/adsp_console_ioctl.h>

#include <linux/uaccess.h>

#include "dev.h"
#include "adsp_console_dbfs.h"

#define USE_RUN_APP_API

static int open_cnt;

#define ADSP_APP_CTX_MAX	32

static uint64_t adsp_app_ctx_vals[ADSP_APP_CTX_MAX];

#define ACCESS_OK(addr, size) access_ok(addr, size)

static int adsp_app_ctx_add(uint64_t ctx)
{
	int i;

	if (ctx == 0)
		return -EINVAL;

	for (i = 0; i < ADSP_APP_CTX_MAX; i++) {
		if (adsp_app_ctx_vals[i] == 0) {
			adsp_app_ctx_vals[i] = ctx;
			return 0;
		}
	}
	return -EINVAL;
}

static int adsp_app_ctx_check(uint64_t ctx)
{
	int i;

	if (ctx == 0)
		return -EINVAL;

	for (i = 0; i < ADSP_APP_CTX_MAX; i++) {
		if (adsp_app_ctx_vals[i] == ctx)
			return 0;
	}
	return -EINVAL;
}


static void adsp_app_ctx_remove(uint64_t ctx)
{
	int i;

	for (i = 0; i < ADSP_APP_CTX_MAX; i++) {
		if (adsp_app_ctx_vals[i] == ctx) {
			adsp_app_ctx_vals[i] = 0;
			return;
		}
	}
}

static int adsp_consol_open(struct inode *i, struct file *f)
{

	int ret;
	uint16_t snd_mbox_id = 30;
	struct nvadsp_cnsl *console = i->i_private;
	struct device *dev = console->dev;
	struct platform_device *pdev = to_platform_device(dev);
	struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);

	if (open_cnt)
		return -EBUSY;
	open_cnt++;
	ret = 0;
	f->private_data = console;
	if (!drv_data->adsp_os_running)
		goto exit_open;
	ret = nvadsp_mbox_open(&console->shl_snd_mbox, &snd_mbox_id,
		"adsp_send_cnsl", NULL, NULL);
	if (!ret)
		goto exit_open;
	pr_err("adsp_consol: Failed to init adsp_consol send mailbox");
	memset(&console->shl_snd_mbox, 0, sizeof(struct nvadsp_mbox));
	open_cnt--;
exit_open:
	return ret;
}
static int adsp_consol_close(struct inode *i, struct file *f)
{
	int ret = 0;
	struct nvadsp_cnsl *console = i->i_private;
	struct nvadsp_mbox *mbox = &console->shl_snd_mbox;
	struct device *dev = console->dev;
	struct platform_device *pdev = to_platform_device(dev);
	struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);

	open_cnt--;
	if (!drv_data->adsp_os_running || (0 == mbox->id))
		goto exit_close;
	ret = nvadsp_mbox_close(mbox);
	if (ret)
		pr_err("adsp_consol: Failed to close adsp_consol send mailbox)");
	memset(mbox, 0, sizeof(struct nvadsp_mbox));
exit_close:
	return ret;
}
static long
adsp_consol_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{

	int ret = 0;
	uint16_t *mid;
	uint16_t  mbxid = 0;
	uint32_t  data;
	uint64_t  ctx2;
	nvadsp_app_info_t *app_info;
	struct adsp_consol_run_app_arg_t app_args;
	struct nvadsp_cnsl *console = f->private_data;
	struct nvadsp_mbox *mbox;
	struct device *dev = console->dev;
	struct platform_device *pdev = to_platform_device(dev);
	struct nvadsp_drv_data *drv_data = platform_get_drvdata(pdev);
	void __user *uarg = (void __user *)arg;

	if (_IOC_TYPE(cmd) != NV_ADSP_CONSOLE_MAGIC)
		return -EFAULT;

	if ((_IOC_NR(cmd) != _IOC_NR(ADSP_CNSL_LOAD)) &&
		(_IOC_NR(cmd) != _IOC_NR(ADSP_CNSL_RESUME)) &&
		(!drv_data->adsp_os_running)) {
		dev_info(dev, "adsp_consol: os not running.");
		return -EPERM;
	}

	if ((_IOC_NR(cmd) != _IOC_NR(ADSP_CNSL_LOAD)) &&
		(0 == console->shl_snd_mbox.id)) {
		dev_info(dev, "adsp_consol: Mailboxes not open.");
		return -EPERM;
	}

	switch (_IOC_NR(cmd)) {
	case _IOC_NR(ADSP_CNSL_LOAD):
		ret = 0;

		if (drv_data->adsp_os_running)
			break;
		mbxid = 30;
		mbox = &console->shl_snd_mbox;
		ret = nvadsp_os_load();
		if (ret) {
			dev_info(dev, "adsp_consol: Load OS Failed.");
			break;
		}
		ret = nvadsp_os_start();
		if (ret) {
			dev_info(dev, "adsp_consol: Start OS Failed.");
			break;
		}
		ret = nvadsp_mbox_open(mbox, &mbxid,
			"adsp_send_cnsl", NULL, NULL);
		if (!ret)
			break;
		pr_err("adsp_consol: Failed to init adsp_consol send mailbox");
		memset(mbox, 0, sizeof(struct nvadsp_mbox));
		break;
	case _IOC_NR(ADSP_CNSL_SUSPEND):
		ret = nvadsp_os_suspend();
		if (ret)
			dev_info(dev, "adsp_consol: OS Suspend Failed.");
		break;
	case _IOC_NR(ADSP_CNSL_STOP):
		nvadsp_os_stop();
		break;
	case _IOC_NR(ADSP_CNSL_RESUME):
		if (!drv_data->adsp_os_suspended) {
			dev_info(dev, "adsp_consol: OS is not suspended to perform resume.");
			break;
		}
		ret = nvadsp_os_start();
		if (ret)
			dev_info(dev, "adsp_consol: OS Resume Failed.");
		break;
	case _IOC_NR(ADSP_CNSL_RUN_APP):
		if (!ACCESS_OK(uarg, sizeof(struct adsp_consol_run_app_arg_t)))
			return -EACCES;
		ret = copy_from_user(&app_args, uarg,
			sizeof(app_args));
		if (ret) {
			ret = -EACCES;
			break;
		}

		dev_info(dev, "Core ID: %d\n", app_args.core_id);
		app_args.app_name[NVADSP_NAME_SZ_MAX] = '\0';

#ifdef USE_RUN_APP_API
		app_args.ctx2 = (uint64_t)nvadsp_run_app(NULL,
			app_args.app_name,
			(nvadsp_app_args_t *)&app_args.args[0],
			NULL, 0, app_args.core_id, true);
		if (!app_args.ctx2) {
			dev_info(dev, "adsp_consol: unable to run %s\n",
				app_args.app_name);
			return -EINVAL;
		}
		if (adsp_app_ctx_add(app_args.ctx2)) {
			dev_info(dev, "adsp_consol: unable to add %s ctx\n",
				app_args.app_name);
			return -EINVAL;
		}
#else
		app_args.ctx1 = (uint64_t)nvadsp_app_load(app_args.app_path,
			app_args.app_name);
		if (!app_args.ctx1) {
			dev_info(dev,
				"adsp_consol: dynamic app load failed %s\n",
				app_args.app_name);
			return -EINVAL;
		}
		if (adsp_app_ctx_add(app_args.ctx1)) {
			dev_info(dev, "adsp_consol: unable to add %s ctx\n",
				app_args.app_name);
			return -EINVAL;
		}

		dev_info(dev, "adsp_consol: calling nvadsp_app_init\n");
		app_args.ctx2 =
			(uint64_t)nvadsp_app_init((void *)app_args.ctx1, NULL);
		if (!app_args.ctx2) {
			dev_info(dev,
				"adsp_consol: unable to initilize the app\n");
			return -EINVAL;
		}
		if (adsp_app_ctx_add(app_args.ctx2)) {
			dev_info(dev, "adsp_consol: unable to add %s ctx\n",
				app_args.app_name);
			return -EINVAL;
		}

		dev_info(dev, "adsp_consol: calling nvadsp_app_start\n");
		ret = nvadsp_app_start((void *)app_args.ctx2);
		if (ret) {
			dev_info(dev, "adsp_consol: unable to start the app\n");
			break;
		}
#endif
		ret = copy_to_user((void __user *) arg, &app_args,
			sizeof(struct adsp_consol_run_app_arg_t));
		if (ret)
			ret = -EACCES;
		break;
	case _IOC_NR(ADSP_CNSL_STOP_APP):
		if (!ACCESS_OK(uarg, sizeof(struct adsp_consol_run_app_arg_t)))
			return -EACCES;
		ret = copy_from_user(&app_args, uarg,
			sizeof(app_args));
		if (ret) {
			ret = -EACCES;
			break;
		}
#ifdef USE_RUN_APP_API
		if (!app_args.ctx2) {
			ret = -EACCES;
			break;
		}
		if (adsp_app_ctx_check(app_args.ctx2)) {
			dev_info(dev, "adsp_consol: unable to check %s ctx\n",
				 app_args.app_name);
			return -EINVAL;
		}

		app_args.ctx1 = (uint64_t)
			((nvadsp_app_info_t *)app_args.ctx2)->handle;

		nvadsp_exit_app((nvadsp_app_info_t *)app_args.ctx2, false);
		nvadsp_app_unload((const void *)app_args.ctx1);

		adsp_app_ctx_remove(app_args.ctx2);
#else
		if ((!app_args.ctx2) || (!app_args.ctx1)) {
			ret = -EACCES;
			break;
		}

		if (adsp_app_ctx_check(app_args.ctx2) ||
		    adsp_app_ctx_check(app_args.ctx1)) {
			dev_info(dev, "adsp_consol: unable to check %s ctx\n",
				 app_args.app_name);
			return -EINVAL;
		}

		nvadsp_app_deinit((void *)app_args.ctx2);
		nvadsp_app_unload((void *)app_args.ctx1);

		adsp_app_ctx_remove(app_args.ctx2);
		adsp_app_ctx_remove(app_args.ctx1);
#endif

		break;
	case _IOC_NR(ADSP_CNSL_CLR_BUFFER):
		break;
	case _IOC_NR(ADSP_CNSL_OPN_MBX):
		if (!ACCESS_OK(uarg, sizeof(ctx2)))
			return -EACCES;
		ret = copy_from_user(&ctx2, uarg, sizeof(ctx2));
		if (ret) {
			ret = -EACCES;
			break;
		}
		if (adsp_app_ctx_check(ctx2)) {
			dev_info(dev, "adsp_consol: unable to check ctx\n");
			return -EINVAL;
		}

		app_info = (nvadsp_app_info_t *)ctx2;

		if (app_info && app_info->mem.shared) {
			mid = (short *)(app_info->mem.shared);
			dev_info(dev, "adsp_consol: open %x\n", *mid);
			mbxid = *mid;
		}

		ret = nvadsp_mbox_open(&console->app_mbox, &mbxid,
			"app_mbox", NULL, NULL);
		if (ret) {
			pr_err("adsp_consol: Failed to open app mailbox");
			ret = -EACCES;
		}
		break;
	case _IOC_NR(ADSP_CNSL_CLOSE_MBX):
		mbox = &console->app_mbox;
		while (!nvadsp_mbox_recv(mbox, &data, 0, 0))
			;
		ret = nvadsp_mbox_close(mbox);
		if (ret)
			break;
		memset(mbox, 0, sizeof(struct nvadsp_mbox));
		break;
	case _IOC_NR(ADSP_CNSL_PUT_MBX):
		if (!ACCESS_OK(uarg, sizeof(uint32_t)))
			return -EACCES;
		ret = copy_from_user(&data, uarg,
			sizeof(uint32_t));
		if (ret) {
			ret = -EACCES;
			break;
		}
		ret = nvadsp_mbox_send(&console->app_mbox, data,
			NVADSP_MBOX_SMSG, 0, 0);
		break;
	case _IOC_NR(ADSP_CNSL_GET_MBX):
		if (!ACCESS_OK(uarg, sizeof(uint32_t)))
			return -EACCES;
		ret = nvadsp_mbox_recv(&console->app_mbox, &data, 0, 0);
		if (ret)
			break;
		ret = copy_to_user(uarg, &data,
			sizeof(uint32_t));
		if (ret)
			ret = -EACCES;
		break;
	case _IOC_NR(ADSP_CNSL_PUT_DATA):
		if (!ACCESS_OK(uarg, sizeof(struct adsp_consol_run_app_arg_t)))
			return -EACCES;
		ret = copy_from_user(&data, uarg, sizeof(uint32_t));
		if (ret) {
			ret = -EACCES;
			break;
		}
		return nvadsp_mbox_send(&console->shl_snd_mbox, data,
			NVADSP_MBOX_SMSG, 0, 0);
		break;
	default:
		dev_info(dev, "adsp_consol: invalid command\n");
		return -EINVAL;
	}
	return ret;
}

static const struct file_operations adsp_console_operations = {
	.open = adsp_consol_open,
	.release = adsp_consol_close,
#ifdef CONFIG_COMPAT
	.compat_ioctl = adsp_consol_ioctl,
#endif
	.unlocked_ioctl = adsp_consol_ioctl
};

int
adsp_create_cnsl(struct dentry *adsp_debugfs_root, struct nvadsp_cnsl *cnsl)
{
	int ret = 0;

	struct device *dev = cnsl->dev;

	if (IS_ERR_OR_NULL(adsp_debugfs_root)) {
		ret = -ENOENT;
		goto err_out;
	}

	if (!debugfs_create_file("adsp_console", S_IRUSR,
		adsp_debugfs_root, cnsl,
		&adsp_console_operations)) {
		dev_err(dev,
			"unable to create adsp console debug fs file\n");
		ret = -ENOENT;
		goto err_out;
	}

	memset(&cnsl->app_mbox, 0, sizeof(cnsl->app_mbox));

err_out:
	return ret;
}
