/*
 * 2008+ Copyright (c) Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 * All rights reserved.
 *
 * 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/fs.h>
#include <linux/err.h>
#include <linux/miscdevice.h>

#include <asm/uaccess.h>

static struct task_struct *tcopy_thread;
static DECLARE_WAIT_QUEUE_HEAD(tcopy_wait);
static DEFINE_MUTEX(tcopy_lock);
static char *tcopy_src;
static void *tcopy_dst;
static size_t tcopy_size, tcopy_src_size = PAGE_SIZE;
static ssize_t tcopy_ret;
static struct mm_struct *tcopy_orig_mm;

static int tcopy_thread_func(void *data)
{
	struct mm_struct *mm;

	while (!kthread_should_stop()) {
		wait_event_interruptible_timeout(tcopy_wait, tcopy_dst, HZ);

		if (kthread_should_stop())
			break;

		if (!tcopy_dst)
			continue;

		mm = current->mm;

		current->mm = tcopy_orig_mm;

		tcopy_ret = copy_to_user(tcopy_dst, tcopy_src, tcopy_size);

		if (!tcopy_ret)
			tcopy_ret = tcopy_size;
		printk("%s: copy data, dst: %p, size: %zu, ret: %zd.\n", __func__, tcopy_dst, tcopy_size, tcopy_ret);
		tcopy_dst = NULL;
		tcopy_size = 0;
		
		current->mm = mm;

		wake_up(&tcopy_wait);
	}
	return 0;
}

static ssize_t tcopy_read(struct file *file, char __user *buf, size_t size, loff_t *poff)
{
	ssize_t ret;

	if (*poff)
		return 0;

	mutex_lock(&tcopy_lock);
	tcopy_size = snprintf(tcopy_src, tcopy_src_size, "%s: pid: %d, file: %p, buf: %p, size: %zu, poff: %llu.\n",
			__func__, current->pid, file, buf, size, *poff);

	if (tcopy_size > size)
		tcopy_size = size;
	tcopy_dst = buf;
	tcopy_ret = 0;
	tcopy_orig_mm = get_task_mm(current);

	wake_up(&tcopy_wait);

	wait_event_interruptible(tcopy_wait, (tcopy_ret != 0));

	ret = tcopy_ret;

	printk("%s: pid: %d, file: %p, buf: %p, size: %zu, poff: %llu, tcopy_size: %zu, ret: %zd.\n",
			__func__, current->pid, file, buf, size, *poff, tcopy_size, ret);
	

	if (ret > 0)
		*poff = *poff + ret;

	current->mm = tcopy_orig_mm;
	mmput(tcopy_orig_mm);

	mutex_unlock(&tcopy_lock);
	
	return ret;
}

static const struct file_operations button_fops = {
	.owner		= THIS_MODULE,
	.read		= tcopy_read,
};

static struct miscdevice tcopy_misc_device = {
	.minor		= MISC_DYNAMIC_MINOR,
	.name		= "tcopy",
	.fops		= &button_fops,
};

static int __init tcopy_init(void)
{
	int err = -ENOMEM;

	tcopy_src = kmalloc(tcopy_src_size, GFP_KERNEL);
	if (!tcopy_src) {
		printk("Failed to allocate src buffer.\n");
		goto err_out_exit;
	}

	err = misc_register(&tcopy_misc_device);
	if (err) {
		printk("Failed to register misc device: err: %d.\n", err);
		goto err_out_free;
	}

	tcopy_thread = kthread_run(tcopy_thread_func, NULL, "tcopy");
	if (IS_ERR(tcopy_thread)) {
		err = PTR_ERR(tcopy_thread);
		printk("Failed to create copy thread: err: %d.\n", err);
		goto err_out_unregister;
	}

	return 0;

err_out_unregister:
	misc_deregister(&tcopy_misc_device);
err_out_free:
	kfree(tcopy_src);
err_out_exit:
	return err;
}

static void __exit tcopy_exit(void)
{
	kthread_stop(tcopy_thread);
	misc_deregister(&tcopy_misc_device);
	kfree(tcopy_src);
}

module_init(tcopy_init);
module_exit(tcopy_exit);

MODULE_LICENSE("GPL");
