// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/compat.h>
#include <linux/amlogic/media/codec_mm/configs.h>
#include <linux/amlogic/media/codec_mm/configs_api.h>
#define MAX_OPENED_CNT 65536
#include "configs_priv.h"
#define MODULE_NAME "media-configs-dev"
#include <linux/slab.h>
#include <linux/uaccess.h>

static struct class *config_dev_class;
static unsigned int config_major;
struct mediaconfig_node {
	const char *parent;
	const char *name;
	const char *dev_name;
	int rw_flags;
	atomic_t opened_cnt;
	struct device *class_dev;
	struct mconfig_node node;

};

static ssize_t all_show(struct class *class,
			struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_nodes(NULL, buf, PAGE_SIZE,
			       LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t media_show(struct class *class,
			  struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t video_show(struct class *class,
			  struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.video", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t decoder_show(struct class *class,
			    struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.decoder", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t vdec_show(struct class *class,
			 struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.vdec", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t tsync_show(struct class *class,
			  struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.tsync", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t amports_show(struct class *class,
			    struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.amports", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t parser_show(struct class *class,
			   struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.parser", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t config_show(struct class *class,
			   struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_nodes(NULL, buf, PAGE_SIZE,
			       LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t config_store(struct class *class,
			    struct class_attribute *attr,
			    const char *buf, size_t size)
{
	ssize_t ret;

	ret = configs_set_path_valonpath(buf);
	if (ret >= 0)
		return size;
	return ret;
}

static ssize_t debug_show(struct class *class,
			  struct class_attribute *attr, char *buf)
{
	return config_dump(buf, PAGE_SIZE);
}

static ssize_t debug_store(struct class *class,
			   struct class_attribute *attr,
			   const char *buf, size_t size)
{
	configs_config_setstr(buf);
	return size;
}

static ssize_t audio_show(struct class *class,
			  struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.audio", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static ssize_t vfm_show(struct class *class,
			struct class_attribute *attr, char *buf)
{
	ssize_t s;

	s = configs_list_path_nodes("media.vfm", buf, PAGE_SIZE,
				    LIST_MODE_NODE_CMDVAL_ALL);
	if (s > 0)
		return s;
	return -EPERM;
}

static CLASS_ATTR_RO(all);
static CLASS_ATTR_RO(media);
static CLASS_ATTR_RO(video);
static CLASS_ATTR_RO(decoder);
static CLASS_ATTR_RO(amports);
static CLASS_ATTR_RO(tsync);
static CLASS_ATTR_RO(parser);
static CLASS_ATTR_RO(vdec);
static CLASS_ATTR_RO(audio);
static CLASS_ATTR_RO(vfm);
static CLASS_ATTR_RW(config);
static CLASS_ATTR_RW(debug);

static struct attribute *configs_class_attrs[] = {
	&class_attr_all.attr,
	&class_attr_media.attr,
	&class_attr_video.attr,
	&class_attr_decoder.attr,
	&class_attr_amports.attr,
	&class_attr_tsync.attr,
	&class_attr_parser.attr,
	&class_attr_vdec.attr,
	&class_attr_audio.attr,
	&class_attr_vfm.attr,
	&class_attr_config.attr,
	&class_attr_debug.attr,
	NULL
};

ATTRIBUTE_GROUPS(configs_class);

static struct class media_configs_class = {
		.name = "media-configs",
		.class_groups = configs_class_groups,
};

static struct mediaconfig_node mediaconfig_nodes[] = {
	{"", "media", "media", CONFIG_FOR_RW},
	{"media", "decoder", "media.decoder", CONFIG_FOR_RW},
	{"media", "parser", "media.parser", CONFIG_FOR_RW},
	{"media", "video", "media.video", CONFIG_FOR_RW},
	{"media", "amports", "media.amports", CONFIG_FOR_RW},
	{"media", "tsync", "media.tsync", CONFIG_FOR_RW},
	{"media", "codec_mm", "media.codec_mm", CONFIG_FOR_RW},
	{"media", "audio", "media.audio", CONFIG_FOR_RW},
	{"media", "vfm", "media.vfm", CONFIG_FOR_RW},
	{"media", "dmabuf_manage", "media.dmabuf_manage", CONFIG_FOR_RW},
};

struct config_file_private {
	struct mediaconfig_node *node;
	char common[128];
	pid_t pid;
	pid_t tgid;
	long opened_jiffies;
	long last_access_jiffies;
	long access_get_cnt;
	long access_set_cnt;
	long access_dump_cnt;
	long error_cnt;
	int enable_trace_get;
	int enable_trace_set;
	int last_read_end;
};

static int configs_open(struct inode *inode, struct file *file)
{
	struct mediaconfig_node *node;
	struct config_file_private *priv;

	if (iminor(inode) >= (sizeof(mediaconfig_nodes) / sizeof(struct mediaconfig_node)))
		return -1;
	node = &mediaconfig_nodes[iminor(inode)];

	if (atomic_read(&node->opened_cnt) > MAX_OPENED_CNT) {
		pr_err("too many files opened.!!\n");
		return -EMFILE;
	}
	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	file->private_data = priv;
	priv->pid = current->pid;
	priv->tgid = current->tgid;
	priv->opened_jiffies = jiffies;
	priv->node = node;
	atomic_inc(&node->opened_cnt);
	configs_inc_node_ref(&node->node);
	return 0;
}

static ssize_t configs_read(struct file *file, char __user *buf_user,
			    size_t count, loff_t *ppos)
{
	struct config_file_private *priv = file->private_data;
	struct mediaconfig_node *node = priv->node;
	int ret;
	void *buf;

	if (*ppos > 0)
		return 0;/*don't support seek read. read end.*/
	if (!access_ok(buf_user, count))
		return -EIO;
	buf = kmalloc(count, GFP_KERNEL);
	if (!buf)
		return 0;
	ret = copy_from_user(buf, buf_user, count);
	if (ret != count) {
		kfree(buf);
		return 0;
	}

	ret = configs_list_nodes(&node->node, buf, count,
				 LIST_MODE_NODE_CMDVAL_ALL);
	if (ret > 0)
		*ppos = ret;

	ret = copy_to_user(buf_user, buf, count);
	if (ret != count) {
		kfree(buf);
		return 0;
	}
	kfree(buf);

	return ret;
}

static int configs_close(struct inode *inode, struct file *file)
{
	struct config_file_private *priv = file->private_data;
	struct mediaconfig_node *node = priv->node;

	configs_dec_node_ref(&node->node);
	atomic_dec(&node->opened_cnt);

	kfree(priv);

	return 0;
}

static long configs_ioctl(struct file *file, unsigned int cmd, ulong arg)
{
	struct config_file_private *priv = file->private_data;
	struct mediaconfig_node *node = priv->node;
	struct media_config_io_str *io = NULL;
	struct media_config_io_str *user_io = (void *)arg;
	int r = -1;

	io = vzalloc(sizeof(*io));
	if (!io)
		return -1;

	priv->last_access_jiffies = jiffies;
	switch (cmd) {
	case MEDIA_CONFIG_SET_CMD_STR:
		priv->access_set_cnt++;
		r = copy_from_user(io->cmd_path, user_io->cmd_path,
				   sizeof(io->cmd_path));
		r |= copy_from_user(io->val, user_io->val, sizeof(io->val));
		/*pr_info("set%s:%s dev=%s\n", io.cmd_path,*/
		/*io.val, node->dev_name);*/
		if (r) {
			r = -EIO;
			break;
		}
		if (!strncmp(io->cmd_path, node->dev_name,
			     strlen(node->dev_name)))
			r = configs_set_node_nodepath_str(NULL,
							  io->cmd_path,
							  io->val);
		else
			pr_info("set %s %s  not match devname=%s\n",
				io->cmd_path, io->val, node->dev_name);
		break;
	case MEDIA_CONFIG_GET_CMD_STR:
		r = -1;
		priv->access_get_cnt++;
		r = copy_from_user(&io->cmd_path,
				   user_io->cmd_path, sizeof(io->cmd_path));
		io->cmd_path[sizeof(io->cmd_path) - 1] = '\0';
		if (r) {
			r = -EIO;
			break;
		}
		io->val[0] = '\0';
		if (!strncmp(io->cmd_path, node->dev_name,
			     strlen(node->dev_name))) {
			r = configs_get_node_nodepath_str(NULL,
							  io->cmd_path,
							io->val,
							sizeof(io->val));

			/*pr_info("configs_get_node_nodepath_str*/
			/*ret %x [%s]\n", r, io.val);*/
			if (r > 0 && r <= sizeof(io->val) - 1) {
				/*+1 for end str.*/
				if (copy_to_user(user_io->val,
						 io->val, r + 1) != 0) {
					r = -EIO;
					break;
				}
				put_user(r, &user_io->ret);
				r = 0;
			} else {
				put_user(-1, &user_io->ret);
			}
		} else {
			pr_info("set %s %s  not match devname=%s\n",
				io->cmd_path, io->val, node->dev_name);
		}
		break;
	default:
		r = -EINVAL;
		pr_err("unsupport cmd %x\n", cmd);
	}
	if (r < 0)
		priv->error_cnt++;

	vfree(io);
	return r;
}

#ifdef CONFIG_COMPAT
static long configs_compat_ioctl(struct file *file,
				 unsigned int cmd, ulong arg)
{
	return configs_ioctl(file, cmd, (ulong)compat_ptr(arg));
}
#endif
static const struct file_operations configs_fops = {
	.owner = THIS_MODULE,
	.open = configs_open,
	.read = configs_read,
	.release = configs_close,
	.unlocked_ioctl = configs_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = configs_compat_ioctl,
#endif
};

int __init configs_init_devices(void)
{
	int i;
	int num = sizeof(mediaconfig_nodes) / sizeof(struct mediaconfig_node);

	int r;

	r = class_register(&media_configs_class);
	if (r) {
		pr_err("configs class create fail.\n");
		goto error1;
	}

	r = register_chrdev(0, MODULE_NAME, &configs_fops);
	if (r < 0) {
		pr_err("Can't allocate major for config device\n");
		goto error1;
	}
	config_major = r;

	config_dev_class = class_create(THIS_MODULE, MODULE_NAME);
	num = sizeof(mediaconfig_nodes) / sizeof(struct mediaconfig_node);
	for (i = 0; i < num; i++) {
		struct mediaconfig_node *mnode = &mediaconfig_nodes[i];

		mnode->class_dev = device_create(config_dev_class, NULL,
						 MKDEV(config_major, i), NULL,
						 mnode->dev_name);
		if (IS_ERR(mnode->class_dev)) {
			pr_err("device_create %s failed\n", mnode->dev_name);
			r = -1;
			goto error2;
		}
	}
	return 0;
error2:
	for (; i >= 0; i--)
		device_destroy(config_dev_class, MKDEV(config_major, i));
	unregister_chrdev(config_major, MODULE_NAME);
error1:
	class_destroy(&media_configs_class);
	return r;
}

int __init media_configs_system_init(void)
{
	int i;
	int num = sizeof(mediaconfig_nodes) / sizeof(struct mediaconfig_node);
	struct mediaconfig_node *mnode;
	int r;

	pr_info("now: %s\n", __func__);
	configs_config_system_init();
	for (i = 0; i < num; i++) {
		mnode = &mediaconfig_nodes[i];
		configs_init_new_node(&mnode->node, mnode->name,
				      mnode->rw_flags);
		r = configs_register_path_node(mnode->parent,
					       &mnode->node);
		if (r < 0) {
			pr_err("ERR!!!.register node[%s] to [%s] failed!\n",
			       mnode->name, mnode->parent);
			return r;
		}
	}

	return 0;
}

//MODULE_DESCRIPTION("AMLOGIC config modules driver");
//MODULE_LICENSE("GPL");

