/*
 * Copyright (c) 2016-2022, NVIDIA CORPORATION.  All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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.
 */

#define pr_fmt(fmt) "adspff: " fmt

#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <linux/slab.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/sched/rt.h>
#include <linux/sched/task.h>
#include <linux/semaphore.h>
#include <linux/debugfs.h>
#include <linux/platform_device.h>
#include <linux/list.h>

#include <linux/tegra_nvadsp.h>
#include <uapi/linux/sched/types.h>

#include "adspff.h"
#include "dev.h"


#define ADSPFF_MAX_OPEN_FILES	(32)

struct file_struct {
	struct file *fp;
	uint8_t file_name[ADSPFF_MAX_FILENAME_SIZE];
	unsigned int flags;
	unsigned long long wr_offset;
	unsigned long long rd_offset;
	struct list_head list;
};

static struct list_head file_list;
static spinlock_t adspff_lock;
static int open_count;

/******************************************************************************
* Kernel file functions
******************************************************************************/

static struct file *file_open(const char *path, int flags, int rights)
{
	struct file *filp = NULL;
	mm_segment_t oldfs;
	int err = 0;

	oldfs = get_fs();
	set_fs(KERNEL_DS);
	filp = filp_open(path, flags, rights);
	set_fs(oldfs);
	if (IS_ERR(filp)) {
		err = PTR_ERR(filp);
		return NULL;
	}
	return filp;
}

static void file_close(struct file *file)
{
	filp_close(file, NULL);
}

static int file_write(struct file *file, unsigned long long *offset,
				unsigned char *data, unsigned int size)
{
	mm_segment_t oldfs;
	int ret = 0;

	oldfs = get_fs();
	set_fs(KERNEL_DS);

	ret = vfs_write(file, (const char __user *)data, size, offset);

	set_fs(oldfs);
	return ret;
}

static uint32_t file_read(struct file *file, unsigned long long *offset,
				unsigned char *data, unsigned int size)
{
	mm_segment_t oldfs;
	uint32_t ret = 0;

	oldfs = get_fs();
	set_fs(KERNEL_DS);

	ret = vfs_read(file, (char __user *)data, size, offset);

	set_fs(oldfs);

	return ret;
}

static uint32_t file_size(struct file *file)
{
	mm_segment_t oldfs;
	uint32_t size = 0;

	oldfs = get_fs();
	set_fs(KERNEL_DS);

	size = vfs_llseek(file, 0, SEEK_END);

	vfs_llseek(file, 0, SEEK_SET);

	set_fs(oldfs);

	return size;
}

/******************************************************************************
* ADSPFF file functions
******************************************************************************/

static struct adspff_shared_state_t *adspff;
static struct nvadsp_mbox rx_mbox;

/**																*
 * w  - open for writing (file need not exist)					*
 * a  - open for appending (file need not exist)				*
 * r+ - open for reading and writing, start at beginning		*
 * w+ - open for reading and writing (overwrite file)			*
 * a+ - open for reading and writing (append if file exists)	*/

static void set_flags(union adspff_message_t *m, unsigned int *flags)
{
	if (0 == strcmp(m->msg.payload.fopen_msg.modes, "r+"))
		*flags = O_RDWR;

	else if (0 == strcmp(m->msg.payload.fopen_msg.modes, "w+"))
		*flags = O_CREAT | O_RDWR | O_TRUNC;

	else if (0 == strcmp(m->msg.payload.fopen_msg.modes, "a+"))
		*flags = O_APPEND | O_RDWR;

	else if (0 == strcmp(m->msg.payload.fopen_msg.modes, "r"))
		*flags = O_RDONLY;

	else if (0 == strcmp(m->msg.payload.fopen_msg.modes, "w"))
		*flags = O_CREAT | O_WRONLY | O_TRUNC;

	else if (0 == strcmp(m->msg.payload.fopen_msg.modes, "a"))
		*flags = O_CREAT | O_APPEND | O_WRONLY;

	else
		*flags = O_CREAT | O_RDWR;
}

/*
 *	checks if file is already opened
 *	if yes, then returns the struct file_struct for the file
 *	if no, then allocates a file_struct and adds to the list
 *	and returns the pointer to the newly allocated file_struct
 *	if ADSPFF_MAX_OPEN_FILES already open, returns NULL
 */
static struct file_struct *check_file_opened(const char *path)
{
	struct file_struct *file = NULL;
	struct list_head *pos;

	/* assuming files opened by ADSP will
	 * never be actually closed in kernel
	 */
	list_for_each(pos, &file_list) {
		file = list_entry(pos, struct file_struct, list);
		if (!file->fp)
			break;
		if (!strncmp(path, file->file_name,
					ADSPFF_MAX_FILENAME_SIZE)) {
			break;
		}
		file = NULL;
	}

	if (file != NULL)
		return file;

	if (open_count == ADSPFF_MAX_OPEN_FILES) {
		pr_err("adspff: %d files already opened\n",
			ADSPFF_MAX_OPEN_FILES);
		file = NULL;
	} else {
		file = kzalloc(sizeof(*file), GFP_KERNEL);
		open_count++;
		list_add_tail(&file->list, &file_list);
	}
	return file;
}

static void adspff_fopen(void)
{
	union adspff_message_t *message;
	union adspff_message_t *msg_recv;
	unsigned int flags = 0;
	int ret = 0;
	struct file_struct *file;


	message = kzalloc(sizeof(union adspff_message_t), GFP_KERNEL);
	if (!message)
		return;


	msg_recv = kzalloc(sizeof(union adspff_message_t), GFP_KERNEL);
	if (!msg_recv) {
		kfree(message);
		return;
	}

	message->msgq_msg.size = MSGQ_MSG_SIZE(struct fopen_msg_t);

	ret = msgq_dequeue_message(&adspff->msgq_send.msgq,
			(msgq_message_t *)message);

	if (ret < 0) {
		pr_err("fopen Dequeue failed %d.", ret);
		kfree(message);
		kfree(msg_recv);
		return;
	}

	file = check_file_opened(message->msg.payload.fopen_msg.fname);
	if (file && !file->fp) {
		/* open a new file */
		set_flags(message, &flags);
		pr_info("adspff: opening file %s\n",
			message->msg.payload.fopen_msg.fname);

		file->fp = file_open(
			(const char *)message->msg.payload.fopen_msg.fname,
			flags, 0777); /* S_IRWXU | S_IRWXG | S_IRWXO */

		file->wr_offset = 0;
		file->rd_offset = 0;
		memcpy(file->file_name,
				message->msg.payload.fopen_msg.fname,
				ADSPFF_MAX_FILENAME_SIZE);
		file->flags = flags;
	}

	if (file && !file->fp) {
		file = NULL;
		pr_err("File not found - %s\n",
			(const char *) message->msg.payload.fopen_msg.fname);
	}

	msg_recv->msgq_msg.size = MSGQ_MSG_SIZE(struct fopen_recv_msg_t);
	msg_recv->msg.payload.fopen_recv_msg.file = (int64_t)file;

	ret = msgq_queue_message(&adspff->msgq_recv.msgq,
			(msgq_message_t *)msg_recv);
	if (ret < 0) {
		pr_err("fopen Enqueue failed %d.", ret);

		if (file) {
			file_close(file->fp);
			file->fp = NULL;
		}

		kfree(message);
		kfree(msg_recv);
		return;
	}

	nvadsp_mbox_send(&rx_mbox, adspff_cmd_fopen_recv,
				NVADSP_MBOX_SMSG, 0, 0);
	kfree(message);
	kfree(msg_recv);
}

static inline unsigned int is_read_file(struct file_struct *file)
{
	return ((!file->flags) || (file->flags & O_RDWR));
}

static inline unsigned int is_write_file(struct file_struct *file)
{
	return file->flags & (O_WRONLY | O_RDWR);
}

static void adspff_fclose(void)
{
	union adspff_message_t *message;
	struct file_struct *file = NULL;
	int32_t ret = 0;

	message = kzalloc(sizeof(union adspff_message_t), GFP_KERNEL);
	if (!message)
		return;

	message->msgq_msg.size = MSGQ_MSG_SIZE(struct fclose_msg_t);

	ret = msgq_dequeue_message(&adspff->msgq_send.msgq,
				(msgq_message_t *)message);

	if (ret < 0) {
		pr_err("fclose Dequeue failed %d.", ret);
		kfree(message);
		return;
	}

	file = (struct file_struct *)message->msg.payload.fclose_msg.file;
	if (file) {
		if ((file->flags & O_APPEND) == 0) {
			if (is_read_file(file))
				file->rd_offset = 0;
			if (is_write_file(file))
				file->wr_offset = 0;
		}
	}
	kfree(message);
}

static void adspff_fsize(void)
{
	union adspff_message_t *msg_recv;
	union adspff_message_t message;
	struct file_struct *file = NULL;
	int32_t ret = 0;
	uint32_t size = 0;

	msg_recv = kzalloc(sizeof(union adspff_message_t), GFP_KERNEL);
	msg_recv->msgq_msg.size = MSGQ_MSG_SIZE(struct ack_msg_t);

	message.msgq_msg.size = MSGQ_MSG_SIZE(struct fsize_msg_t);
	ret = msgq_dequeue_message(&adspff->msgq_send.msgq,
				(msgq_message_t *)&message);

	if (ret < 0) {
		pr_err("fsize Dequeue failed %d.", ret);
		kfree(msg_recv);
		return;
	}
	file = (struct file_struct *)message.msg.payload.fsize_msg.file;
	if (file) {
		size = file_size(file->fp);
	}

	/* send ack */
	msg_recv->msg.payload.ack_msg.size = size;
	ret = msgq_queue_message(&adspff->msgq_recv.msgq,
			(msgq_message_t *)msg_recv);

	if (ret < 0) {
		pr_err("fsize Enqueue failed %d.", ret);
		kfree(msg_recv);
		return;
	}
	nvadsp_mbox_send(&rx_mbox, adspff_cmd_ack,
			NVADSP_MBOX_SMSG, 0, 0);
	kfree(msg_recv);
}

static void adspff_fwrite(void)
{
	union adspff_message_t message;
	union adspff_message_t *msg_recv;
	struct file_struct *file = NULL;
	int ret = 0;
	uint32_t size = 0;
	uint32_t bytes_to_write = 0;
	uint32_t bytes_written = 0;

	msg_recv = kzalloc(sizeof(union adspff_message_t), GFP_KERNEL);
	if (!msg_recv)
		return;

	msg_recv->msgq_msg.size = MSGQ_MSG_SIZE(struct ack_msg_t);

	message.msgq_msg.size = MSGQ_MSG_SIZE(struct fwrite_msg_t);
	ret = msgq_dequeue_message(&adspff->msgq_send.msgq,
				(msgq_message_t *)&message);
	if (ret < 0) {
		pr_err("fwrite Dequeue failed %d.", ret);
		kfree(msg_recv);
		return;
	}

	file = (struct file_struct *)message.msg.payload.fwrite_msg.file;
	size = message.msg.payload.fwrite_msg.size;

	bytes_to_write = ((adspff->write_buf.read_index + size) < ADSPFF_SHARED_BUFFER_SIZE) ?
		size : (ADSPFF_SHARED_BUFFER_SIZE - adspff->write_buf.read_index);
	ret = file_write(file->fp, &file->wr_offset,
			adspff->write_buf.data + adspff->write_buf.read_index, bytes_to_write);
	bytes_written += ret;

	if ((size - bytes_to_write) > 0) {
		ret = file_write(file->fp, &file->wr_offset,
				adspff->write_buf.data, size - bytes_to_write);
		bytes_written += ret;
	}

	adspff->write_buf.read_index =
		(adspff->write_buf.read_index + size) % ADSPFF_SHARED_BUFFER_SIZE;

	/* send ack */
	msg_recv->msg.payload.ack_msg.size = bytes_written;
	ret = msgq_queue_message(&adspff->msgq_recv.msgq,
			(msgq_message_t *)msg_recv);

	if (ret < 0) {
		pr_err("adspff: fwrite Enqueue failed %d.", ret);
		kfree(msg_recv);
		return;
	}
	nvadsp_mbox_send(&rx_mbox, adspff_cmd_ack,
			NVADSP_MBOX_SMSG, 0, 0);
	kfree(msg_recv);
}

static void adspff_fread(void)
{
	union adspff_message_t *message;
	union adspff_message_t *msg_recv;
	struct file_struct *file = NULL;
	uint32_t bytes_free;
	uint32_t wi = adspff->read_buf.write_index;
	uint32_t ri = adspff->read_buf.read_index;
	uint8_t can_wrap = 0;
	uint32_t size = 0, size_read = 0;
	int32_t ret = 0;

	if (ri <= wi) {
		bytes_free = ADSPFF_SHARED_BUFFER_SIZE - wi + ri - 1;
		can_wrap = 1;
	} else {
		bytes_free = ri - wi - 1;
		can_wrap = 0;
	}
	message = kzalloc(sizeof(union adspff_message_t), GFP_KERNEL);
	if (!message)
		return;

	msg_recv = kzalloc(sizeof(union adspff_message_t), GFP_KERNEL);
	if (!msg_recv) {
		kfree(message);
		return;
	}

	msg_recv->msgq_msg.size = MSGQ_MSG_SIZE(struct ack_msg_t);
	message->msgq_msg.size = MSGQ_MSG_SIZE(struct fread_msg_t);

	ret = msgq_dequeue_message(&adspff->msgq_send.msgq,
				(msgq_message_t *)message);

	if (ret < 0) {
		pr_err("fread Dequeue failed %d.", ret);
		kfree(message);
		kfree(msg_recv);
		return;
	}

	file = (struct file_struct *)message->msg.payload.fread_msg.file;
	size = message->msg.payload.fread_msg.size;
	if (bytes_free < size) {
		size_read = 0;
		goto send_ack;
	}

	if (can_wrap) {
		uint32_t bytes_to_read = (size < (ADSPFF_SHARED_BUFFER_SIZE - wi)) ?
			size : (ADSPFF_SHARED_BUFFER_SIZE - wi);
		ret = file_read(file->fp, &file->rd_offset,
				adspff->read_buf.data + wi, bytes_to_read);
		size_read = ret;
		if (ret < bytes_to_read)
			goto send_ack;
		if ((size - bytes_to_read) > 0) {
			ret = file_read(file->fp, &file->rd_offset,
					adspff->read_buf.data, size - bytes_to_read);
			size_read += ret;
			goto send_ack;
		}
	} else {
		ret = file_read(file->fp, &file->rd_offset,
				adspff->read_buf.data + wi, size);
		size_read = ret;
		goto send_ack;
	}
send_ack:
	msg_recv->msg.payload.ack_msg.size = size_read;
	ret = msgq_queue_message(&adspff->msgq_recv.msgq,
			(msgq_message_t *)msg_recv);

	if (ret < 0) {
		pr_err("fread Enqueue failed %d.", ret);
		kfree(message);
		kfree(msg_recv);
		return;
	}
	adspff->read_buf.write_index =
		(adspff->read_buf.write_index + size_read) % ADSPFF_SHARED_BUFFER_SIZE;

	nvadsp_mbox_send(&rx_mbox, adspff_cmd_ack,
			NVADSP_MBOX_SMSG, 0, 0);
	kfree(message);
	kfree(msg_recv);
}

#if KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE
static const struct sched_param param = {
	.sched_priority = 1,
};
#endif
static struct task_struct *adspff_kthread;
static struct list_head adspff_kthread_msgq_head;
static wait_queue_head_t  wait_queue;

struct adspff_kthread_msg {
	uint32_t msg_id;
	struct list_head list;
};


static int adspff_kthread_fn(void *data)
{
	int ret = 0;
	struct adspff_kthread_msg *kmsg;
	unsigned long flags;

	while (1) {

		ret = wait_event_interruptible(wait_queue, kthread_should_stop()
				 || !list_empty(&adspff_kthread_msgq_head));

		if (kthread_should_stop())
			do_exit(0);

		if (!list_empty(&adspff_kthread_msgq_head)) {
			kmsg = list_first_entry(&adspff_kthread_msgq_head,
					struct adspff_kthread_msg, list);
			switch (kmsg->msg_id) {
			case adspff_cmd_fopen:
				adspff_fopen();
				break;
			case adspff_cmd_fclose:
				adspff_fclose();
				break;
			case adspff_cmd_fwrite:
				adspff_fwrite();
				break;
			case adspff_cmd_fread:
				adspff_fread();
				break;
			case adspff_cmd_fsize:
				adspff_fsize();
				break;
			default:
				pr_warn("adspff: kthread unsupported msg %d\n",
					kmsg->msg_id);
			}
			spin_lock_irqsave(&adspff_lock, flags);
			list_del(&kmsg->list);
			spin_unlock_irqrestore(&adspff_lock, flags);
			kfree(kmsg);
		}
	}

	do_exit(ret);
}

/******************************************************************************
* ADSP mailbox message handler
******************************************************************************/


static int adspff_msg_handler(uint32_t msg, void *data)
{
	unsigned long flags;
	struct adspff_kthread_msg *kmsg;

	spin_lock_irqsave(&adspff_lock, flags);
	kmsg = kzalloc(sizeof(*kmsg), GFP_ATOMIC);
	if (!kmsg) {
		spin_unlock_irqrestore(&adspff_lock, flags);
		return -ENOMEM;
	}

	kmsg->msg_id = msg;
	list_add_tail(&kmsg->list, &adspff_kthread_msgq_head);
	wake_up(&wait_queue);
	spin_unlock_irqrestore(&adspff_lock, flags);

	return 0;
}

static int adspff_set(void *data, u64 val)
{
	struct file_struct *file;
	struct list_head *pos, *n;

	if (val != 1)
		return 0;
	list_for_each_safe(pos, n, &file_list) {
		file = list_entry(pos, struct file_struct, list);
		list_del(pos);
		if (file->fp)
			file_close(file->fp);
		kfree(file);
	}

	open_count = 0;

	return 0;
}
DEFINE_SIMPLE_ATTRIBUTE(adspff_fops, NULL, adspff_set, "%llu\n");

#ifdef CONFIG_DEBUG_FS
static int adspff_debugfs_init(struct nvadsp_drv_data *drv)
{
	int ret = -ENOMEM;
	struct dentry *d, *dir;

	if (!drv->adsp_debugfs_root)
		return ret;
	dir = debugfs_create_dir("adspff", drv->adsp_debugfs_root);
	if (!dir)
		return ret;

	d = debugfs_create_file(
			"close_files", 0200, /* S_IWUSR */
			dir, NULL, &adspff_fops);
	if (!d)
		return ret;

	return 0;
}
#endif

int adspff_init(struct platform_device *pdev)
{
	int ret = 0;
	nvadsp_app_handle_t handle;
	nvadsp_app_info_t *app_info;

#ifdef CONFIG_DEBUG_FS
	struct nvadsp_drv_data *drv = platform_get_drvdata(pdev);
#endif

	handle = nvadsp_app_load("adspff", "adspff.elf");
	if (!handle)
		return -ENOENT;

	app_info = nvadsp_app_init(handle, NULL);
	if (IS_ERR_OR_NULL(app_info)) {
		pr_err("unable to init app adspff\n");
		return -1;
	}

	adspff_kthread = kthread_create(adspff_kthread_fn,
		NULL, "adspp_kthread");
	if ((adspff_kthread == ERR_PTR(-ENOMEM)) ||
			(adspff_kthread == ERR_PTR(-EINTR))) {
		pr_err("adspff kthread_create failed, error = %s\n",
			(adspff_kthread == ERR_PTR(-ENOMEM)) ?
					"-ENOMEM" : "-EINTR");
		return -1;
	}

	adspff = ADSPFF_SHARED_STATE(app_info->mem.shared);

	ret = nvadsp_mbox_open(&rx_mbox, &adspff->mbox_id,
			"adspff", adspff_msg_handler, NULL);

	if (ret < 0) {
		pr_err("Failed to open mbox %d", adspff->mbox_id);
		return -1;
	}

	spin_lock_init(&adspff_lock);

#ifdef CONFIG_DEBUG_FS
	ret = adspff_debugfs_init(drv);
	if (ret)
		pr_warn("adspff: failed to create debugfs entry\n");
#endif

	INIT_LIST_HEAD(&adspff_kthread_msgq_head);
	INIT_LIST_HEAD(&file_list);

	init_waitqueue_head(&wait_queue);

#if KERNEL_VERSION(5, 9, 0) > LINUX_VERSION_CODE
	sched_setscheduler(adspff_kthread, SCHED_FIFO, &param);
#else
	sched_set_fifo_low(adspff_kthread);
#endif

	get_task_struct(adspff_kthread);
	wake_up_process(adspff_kthread);

	return ret;
}

void adspff_exit(void)
{
	nvadsp_mbox_close(&rx_mbox);
	kthread_stop(adspff_kthread);
	put_task_struct(adspff_kthread);
}
