/*
 * Copyright (c) 2023-2023 Huawei Technologies Co., Ltd.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#include "tc_client_driver.h"
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/compat.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include "auth_base_impl.h"
#include "agent.h"
#ifdef CONFIG_CONFIDENTIAL_CONTAINER
#include <linux/proc_ns.h>
#include <linux/pid_namespace.h>
#endif
#ifdef CONFIG_TEE_TELEPORT_SUPPORT
#include "tee_portal.h"
#endif

#include "tee_info.h"

#if defined(CONFIG_CONFIDENTIAL_CONTAINER) || defined(CONFIG_TEE_TELEPORT_SUPPORT)
static int tc_cvm_open(struct inode *inode, struct file *file)
{
	int ret = -1;
	struct tc_ns_dev_file *dev = NULL;
	(void)inode;

#ifdef CONFIG_TEE_TELEPORT_AUTH
	ret = check_tee_teleport_auth();
#endif
#ifdef CONFIG_TEE_AGENTD_AUTH
	if (ret != 0)
		ret = check_tee_agentd_auth();
#endif
	if (ret != 0) {
		tloge("teleport/agentd auth failed, ret %d\n", ret);
		return -EACCES;
	}

	file->private_data = NULL;
	ret = tc_ns_client_open(&dev, TEE_REQ_FROM_USER_MODE);
	if (ret == 0) {
		file->private_data = dev;
	}
	return ret;
}

static int teleport_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = -EFAULT;
	void *argp = (void __user *)(uintptr_t)arg;

	switch (cmd) {
#ifdef CONFIG_TEE_TELEPORT_SUPPORT
	case TC_NS_CLIENT_IOCTL_PORTAL_REGISTER:
		ret = tee_portal_register(file->private_data, argp);
		break;
	case TC_NS_CLIENT_IOCTL_PORTAL_WORK:
		ret = tee_portal_work(file->private_data);
		break;
#endif
	default:
		tloge("invalid cmd!\n");
	}
	return ret;
}

static long tc_cvm_ioctl(struct file *file, unsigned int cmd,
	unsigned long arg)
{
	int ret = -EFAULT;
	void *argp = (void __user *)(uintptr_t)arg;
	if (file != NULL && file->private_data != NULL && ((struct tc_ns_dev_file *)(file->private_data))->vmid == 0)
		init_nsid_vmid(&(((struct tc_ns_dev_file *)(file->private_data))->nsid),
			&(((struct tc_ns_dev_file *)(file->private_data))->vmid));

	handle_cmd_prepare(cmd);

	switch (cmd) {
	case TC_NS_CLIENT_IOCTL_GET_TEE_INFO:
		ret = tc_ns_get_tee_info(file, argp);
		break;

#ifdef CONFIG_TEE_TELEPORT_SUPPORT
	case TC_NS_CLIENT_IOCTL_PORTAL_REGISTER:
	case TC_NS_CLIENT_IOCTL_PORTAL_WORK:
		if (check_tee_teleport_auth() == 0)
			ret = teleport_ioctl(file, cmd, arg);
		else
			tloge("check tee_teleport path failed\n");
		break;
#endif

#ifdef CONFIG_TEE_AGENTD_AUTH
	case TC_NS_CLIENT_IOCTL_REGISTER_AGENT:
	case TC_NS_CLIENT_IOCTL_UNREGISTER_AGENT:
	case TC_NS_CLIENT_IOCTL_SEND_EVENT_RESPONSE:
	case TC_NS_CLIENT_IOCTL_WAIT_EVENT:
	case TC_NS_CLIENT_IOCTL_LOAD_APP_REQ:
	case TC_NS_CLIENT_IOCTL_CHECK_CCOS:
		if (check_tee_agentd_auth() == 0)
			ret = public_ioctl(file, cmd, arg, false);
		else
			tloge("check agentd path failed\n");
		break;
#endif
	default:
		tloge("invalid cmd!\n");
	}

	handle_cmd_finish(cmd);
	return ret;
}

#ifdef CONFIG_COMPAT
long tc_compat_cvm_ioctl(struct file *file, unsigned int cmd,
	unsigned long arg)
{
	long ret;

	if (!file)
		return -EINVAL;

	ret = tc_cvm_ioctl(file, cmd, (unsigned long)(uintptr_t)compat_ptr(arg));
	return ret;
}
#endif

static int tc_cvm_close(struct inode *inode, struct file *file)
{
	struct tc_ns_dev_file *dev = file->private_data;
	(void)inode;

#ifdef CONFIG_TEE_TELEPORT_SUPPORT
	if (dev->portal_enabled)
		tee_portal_unregister(file->private_data);
#endif
	if (is_system_agent(dev)) {
		send_crashed_event_response_single(dev);
		free_dev(dev);
	} else {
		free_dev(dev);
	}
	file->private_data = NULL;

	return 0;
}

static const struct file_operations g_cvm_fops = {
	.owner = THIS_MODULE,
	.open = tc_cvm_open,
	.release = tc_cvm_close,
	.unlocked_ioctl = tc_cvm_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = tc_compat_cvm_ioctl,
#endif
};

static struct dev_node g_tc_cvm;
static struct dev_ops g_tc_cvm_ops = {
	.dev_node_inited = false,
	.dev_file_inited = false,
	.init_dev_node = init_dev_node,
	.destroy_dev_node = destory_dev_node
};

int init_cvm_node(void)
{
	int ret = 0;
	struct class *driver_class = NULL;
	if (g_tc_cvm_ops.dev_node_inited)
		return 0;
	
	driver_class = get_driver_class();
	if (g_tc_cvm_ops.init_dev_node != NULL && g_tc_cvm_ops.destroy_dev_node != NULL &&
		driver_class != NULL) {
			ret = g_tc_cvm_ops.init_dev_node(&g_tc_cvm, TC_NS_CVM_DEV, driver_class, &g_cvm_fops);
			if (ret != 0) {
				tloge("create tc_ns_cvm device node failed\n");
				return -1;
			}
			g_tc_cvm_ops.dev_node_inited = true;
			return 0;
	}

	return -1;
}

int init_cvm_node_file(void)
{
	int ret = 0;
	struct class *driver_class = NULL;
	if (!g_tc_cvm_ops.dev_node_inited)
		return -1;
	
	if (g_tc_cvm_ops.dev_file_inited)
		return 0;
	
	ret = cdev_add(&(g_tc_cvm.char_dev),
		            MKDEV(MAJOR(g_tc_cvm.devt), 0), 1);
	if (ret < 0) {
		tloge("create /dev/tc_ns_cvm device node file failed\n");
		driver_class = get_driver_class();
		if (g_tc_cvm_ops.destroy_dev_node != NULL && driver_class != NULL)
			g_tc_cvm_ops.destroy_dev_node(&g_tc_cvm, driver_class);
		g_tc_cvm_ops.dev_node_inited = false;
		return ret;
	}
	g_tc_cvm_ops.dev_file_inited = true;
	return 0;
}

void destroy_cvm_node_file(void)
{
	if (!g_tc_cvm_ops.dev_file_inited)
		return;
	
	cdev_del(&(g_tc_cvm.char_dev));
	g_tc_cvm_ops.dev_file_inited = false;
}

void destroy_cvm_node(void)
{
	struct class *driver_class = NULL;
	if (!g_tc_cvm_ops.dev_node_inited)
		return;
	
	driver_class = get_driver_class();
	if (g_tc_cvm_ops.destroy_dev_node != NULL && driver_class != NULL)
		g_tc_cvm_ops.destroy_dev_node(&g_tc_cvm, driver_class);
	g_tc_cvm_ops.dev_node_inited = false;
}
#endif