
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/cdev.h>
#include <linux/timer.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/page.h>
#include <asm/uaccess.h>

#include "cachetest.h"
#include "cachetestcommands.h"

//#define MEM_SIZE 		PAGE_SIZE

struct cacheTest_dev {
	struct cdev cdev;
	struct shared_struct * sharedData;
	struct timer_list timerList;
	wait_queue_head_t dataAvailableQ;
	unsigned int counter;
};

struct cacheTest_dev* cacheTestDev = NULL;

static void cacheTest_add_data(struct cacheTest_dev *dev)
{
	int i;
	dev->sharedData->data[dev->sharedData->controlData.writeIndex].counter = dev->counter++;
	for(i=0;i<KRNL_SIZE;i++){
		dev->sharedData->data[dev->sharedData->controlData.writeIndex].data[i]=KRNL_VAL;
	}
	dev->sharedData->controlData.writeIndex = (dev->sharedData->controlData.writeIndex + 1)%NR_DATA_BUFFERS;
	printk("Sending packet with counter %u to user space\n",dev->counter-1);
	wake_up_interruptible(&dev->dataAvailableQ);
}

static void cacheTest_remove_data(struct cacheTest_dev* dev)
{
	int i;
	printk("removing packet with counter %lu\n",dev->sharedData->data[dev->sharedData->controlData.startIndex].counter);
	for(i=KRNL_SIZE;i<DATA_SIZE;i++){
		if(dev->sharedData->data[dev->sharedData->controlData.startIndex].data[i]!=USR_VAL){
			printk("Value %u of packet %lu is not equal.\n",i,dev->sharedData->data[dev->sharedData->controlData.startIndex].counter);
			//break;
		}
	}
	dev->sharedData->controlData.startIndex = (dev->sharedData->controlData.startIndex + 1)%NR_DATA_BUFFERS;
}

void cacheTest_timer_timeout(unsigned long arg)
{
	struct cacheTest_dev *dev = (struct cacheTest_dev *)arg;
	dev->timerList.expires += msecs_to_jiffies(REQ_RATE);
	cacheTest_add_data(dev);
	add_timer(&dev->timerList);
}

void cacheTest_timer_init(struct cacheTest_dev *dev)
{
	init_timer(&dev->timerList);
	dev->timerList.expires = jiffies + msecs_to_jiffies(REQ_RATE);
	dev->timerList.data = (unsigned long) dev;
	dev->timerList.function = cacheTest_timer_timeout;
	add_timer(&dev->timerList);
}

void cacheTest_timer_release(struct cacheTest_dev *dev)
{
	del_timer(&dev->timerList);
}

static int cacheTest_mem_init(struct cacheTest_dev *dev)
{
	//int i;
	void * temp_ptr;
	temp_ptr = kzalloc((PAGE_SIZE*2) + sizeof(struct shared_struct), GFP_ATOMIC);
	dev->sharedData = (struct shared_struct *)(((unsigned long)temp_ptr + PAGE_SIZE -1) & PAGE_MASK);
	/*
	printk("sizeof struct control_data %d\n",sizeof(struct control_data));
	printk("sizeof struct shared_data %d\n",sizeof(struct shared_data));
	for(i = 0;i<NR_DATA_BUFFERS;i++)
	{
		printk("Offset of data buffer %d: %d\n",i,(unsigned long)&dev->sharedData->data[i] - (unsigned long)temp_ptr);
	}
	*/
	return 0;
}

static int cacheTest_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	switch(cmd)
	{
	case CACHE_TEST_GET_MMAP_ADDRESS:
	{
		unsigned long phys_addr = (unsigned long) virt_to_phys(((struct cacheTest_dev *)filp->private_data)->sharedData);
		put_user(phys_addr, (unsigned long __user *)arg);
		return 0;
	}
	case CACHE_TEST_REMOVE_REQUEST:
		cacheTest_remove_data((struct cacheTest_dev *)filp->private_data);
		return 0;
	}
	return -ENOTTY;
}

static int cacheTest_open(struct inode *inode, struct file *filp)
{
	struct cacheTest_dev *dev = container_of(inode->i_cdev, struct cacheTest_dev, cdev);
	filp->private_data = dev;
	dev->counter = 0;
	dev->sharedData->controlData.readIndex=0;
	dev->sharedData->controlData.writeIndex=0;
	dev->sharedData->controlData.startIndex=0;
	cacheTest_timer_init(dev);
	return 0;
}

static int cacheTest_release(struct inode *inode, struct file *filp)
{
	struct cacheTest_dev *dev = (struct cacheTest_dev *)filp->private_data;
	cacheTest_timer_release(dev);
	return 0;
}

static unsigned int cacheTest_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask=0;
	struct cacheTest_dev *dev = (struct cacheTest_dev *)filp->private_data;
	poll_wait(filp, &dev->dataAvailableQ, wait);
	if(dev->sharedData->controlData.readIndex!=dev->sharedData->controlData.writeIndex){
		mask = POLLIN | POLLRDNORM;
	}
	return mask;
}

static int cacheTest_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int i;
	if(remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start,vma->vm_page_prot)){
		return -EAGAIN;
	}
	for (i = 0; i < (vma->vm_end - vma->vm_start)/PAGE_SIZE; i++) {
		SetPageReserved(pfn_to_page(vma->vm_pgoff + i));
	}
	return 0;
}

struct file_operations cacheTest_fop = {
		.owner = THIS_MODULE,
		.ioctl = cacheTest_ioctl,
		.open = cacheTest_open,
		.release = cacheTest_release,
		.poll = cacheTest_poll,
		.mmap = cacheTest_mmap,
};

static int cacheTest_char_init(struct cacheTest_dev *dev)
{
	int err;
	dev_t tempDev;

	err = alloc_chrdev_region(&tempDev, 0, 1, "cacheTest");
	if (err != 0) {
		printk(KERN_ERR "%s: error in alloc_chrdev_region.\n",__FUNCTION__);
		return -1;
	}

	cdev_init(&dev->cdev, &cacheTest_fop);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev, tempDev, 1);
	if (err != 0) {
		printk(KERN_ERR "%s: error in cdev_add (%i) \n", __FUNCTION__, err);
		return -1;
	}
	printk(KERN_INFO "%s: /dev/cacheTest registered with major %d.\n",__FUNCTION__,MAJOR(tempDev));
	return 0;
}

int cacheTest_module_init(void)
{
	cacheTestDev = kzalloc(sizeof(struct cacheTest_dev), GFP_KERNEL);
	cacheTest_mem_init(cacheTestDev);
	init_waitqueue_head(&cacheTestDev->dataAvailableQ);
	cacheTest_char_init(cacheTestDev);
	return 0;
}

void cacheTest_module_cleanup(void)
{

}
module_init(cacheTest_module_init);
module_exit(cacheTest_module_cleanup);

MODULE_AUTHOR("Bart Jonkers");
MODULE_LICENSE("GPL");
