/*
 * scull.c -- the bare scull char module
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h>	/* printk() */
#include <linux/slab.h>		/* kmalloc() */
#include <linux/fs.h>		/* everything... */
#include <linux/errno.h>	/* error codes */
#include <linux/types.h>	/* size_t */
#include <linux/cdev.h>

#include <linux/uaccess.h>	/* copy_*_user */

#include "scull.h"		/* local definitions */

#include <linux/mutex.h>

int queue_init(void);

/*
 * Our parameters which can be set at load time.
 */

static int scull_major =   SCULL_MAJOR;
static int scull_minor =   0;
static int scull_fifo_elemsz = SCULL_FIFO_ELEMSZ_DEFAULT; /* ELEMSZ */
static int scull_fifo_size   = SCULL_FIFO_SIZE_DEFAULT;   /* N      */

/* FIFO Queue Implementation Variables */

static char* fifo_queue;  
static char* read_head;  /* start */
static char* write_head; /* end */

/* explicitly defined in */
static struct semaphore empty_slots; /* starts at N / scull_fifo_size */
static struct semaphore full_slots; /* starts at 0 */



/* Define a mutex from the linux kernel API. */
DEFINE_MUTEX(mux); 
/* Busy waits for a bit then sleeps */

module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_fifo_size, int, S_IRUGO);
module_param(scull_fifo_elemsz, int, S_IRUGO);

MODULE_AUTHOR("tshabbic"); 
MODULE_LICENSE("Dual BSD/GPL");

static struct cdev scull_cdev;		/* Char device structure */

/*
 * Open and close
 */

static int scull_open(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "scull open\n");
	return 0;          /* success */
}

static int scull_release(struct inode *inode, struct file *filp)
{
	printk(KERN_INFO "scull close\n");
	return 0;
}


 
static ssize_t scull_read(struct file *filp, char __user *buf,
                          size_t count, loff_t *f_pos)
{
	/* Size of one slot: length header followed by ELEMSZ bytes of data */
	const size_t stride = sizeof(size_t) + (size_t)scull_fifo_elemsz;

	/* Total size of the flat array, also used to detect the wrap around point */
	const size_t total  = (size_t)scull_fifo_size * stride;

	size_t stored_len;   /* length recorded in the slot's header */
	size_t n;            /* number of data bytes we will hand to the user */

	/* Sleep until at least one slot is full. Interruptible so Ctrl-c works.
	 * Nothing is held yet, so nothing needs releasing on failure. */
	if (down_interruptible(&full_slots)) {
		return -ERESTARTSYS;
	}

	/* Take the mutex protecting read_head and the slot contents.
	 * If interrupted here we already consumed a "full" count without
	 * reading anything, so give it back for another consumer. */
	if (mutex_lock_interruptible(&mux)) {
		up(&full_slots);
		return -ERESTARTSYS;
	}

	/* Pull the stored length out of the slot header first */
	memcpy(&stored_len, read_head, sizeof(size_t));

	/* Defensive clamp: a corrupt header must never make us read past
	 * the element's data area. */
	if (stored_len > (size_t)scull_fifo_elemsz) {
		stored_len = (size_t)scull_fifo_elemsz;
	}

	n = (count < stored_len) ? count : stored_len;

	/* Copy the data (just past the header) out to user space. */
	if (copy_to_user(buf, read_head + sizeof(size_t), n)) {
		mutex_unlock(&mux);
		up(&full_slots);
		return -EFAULT;
	}

	read_head += stride;
	if (read_head >=fifo_queue + total) {
		read_head =fifo_queue;
	}

	mutex_unlock(&mux);

	up(&empty_slots);

	return (ssize_t)n;
}


static ssize_t scull_write(struct file *filp, const char __user *buf,
                           size_t count, loff_t *f_pos)
{
	/* Size of one slot: length header followed by ELEMSZ bytes of data */
	const size_t stride = sizeof(size_t) + (size_t)scull_fifo_elemsz;
	
	/* Total size of the flat array, also used to detect the wrap around point */
	const size_t total  = (size_t)scull_fifo_size * stride;

	/* Messages longer than ELEMSZ are truncated, not rejected */
	size_t len_to_copy = (count < (size_t)scull_fifo_elemsz) ? count : (size_t)scull_fifo_elemsz;

	/* Sleep until a slot is free. Interruptible so Ctrl-C works as per the assignment spec.
	 * Nothing is held yet, so nothing needs releasing on failure. */
	if (down_interruptible(&empty_slots)) {
		return -ERESTARTSYS;
	}

	/* Take the mutex protecting write_head and the slot contents.
	 * If interrupted here we already consumed a slot, so give it back. */
	if (mutex_lock_interruptible(&mux)) {
		up(&empty_slots);
		return -ERESTARTSYS;
	}

	/* Copy the message data just past the length header of the slot.
	 * On failure, release everything we hold and leave write_head alone
	 * so the slot stays free. */
	if (copy_from_user(write_head + sizeof(size_t), buf, len_to_copy)) {
		mutex_unlock(&mux);
		up(&empty_slots);
		return -EFAULT;
	}

	/* Store the actual message length at the front of the slot.
	 * memcpy avoids alignment assumptions about the header. */
	memcpy(write_head, &len_to_copy, sizeof(size_t));

	/* Advance by one slot and wrap back to the array base if necessary */
	write_head += stride;
	if (write_head >=fifo_queue + total) {
		write_head =fifo_queue;
	}

	mutex_unlock(&mux);

	/* wake a consumer if one is waiting */
	up(&full_slots);

	return (ssize_t)len_to_copy;  /* data bytes only, header excluded */
}

/*
 * The ioctl() implementation
 */
static long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{

	int err = 0;
	int retval = 0;
    
	/*
	 * extract the type and number bitfields, and don't decode
	 * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
	 */
	if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;

	err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
	if (err) return -EFAULT;

	switch(cmd) {
	case SCULL_IOCGETELEMSZ:
		return scull_fifo_elemsz;

	default:  /* redundant, as cmd was checked against MAXNR */
		return -ENOTTY;
	}
	return retval;

}

struct file_operations scull_fops = {
	.owner 		= THIS_MODULE,
	.unlocked_ioctl = scull_ioctl,
	.open 		= scull_open,
	.release	= scull_release,
	.read 		= scull_read,
	.write 		= scull_write,
};


int queue_init(void)
{ 	
	int queue_length = (scull_fifo_size * (sizeof(size_t) + scull_fifo_elemsz)); 

	fifo_queue = kmalloc(queue_length, GFP_KERNEL);
	if (!fifo_queue)
	{
		printk(KERN_ERR "scull: kmalloc fail upon trying to create the fifo queue!\n");
		return -1;
	}

	/* initialize the semaphores declared above */
	sema_init(&empty_slots, scull_fifo_size); /* our produces are ready */
	sema_init(&full_slots, 0);

	read_head = fifo_queue;
	write_head = fifo_queue;
	return 0;
}

/*
 * The cleanup function is used to handle initialization failures as well.
 * Thefore, it must be careful to work correctly even if some of the items
 * have not been initialized
 */
void scull_cleanup_module(void)
{
	dev_t devno = MKDEV(scull_major, scull_minor);

	/* avoid an eventual kernel panic */
	if (mutex_is_locked(&mux) == 1) {
		mutex_unlock(&mux);
	}

	/* free our queue */
	kfree(fifo_queue);

	/* Get rid of the char dev entry */
	cdev_del(&scull_cdev);

	/* cleanup_module is never called if registering failed */
	unregister_chrdev_region(devno, 1);
}

int scull_init_module(void)
{
	int result;
	dev_t dev = 0;

	/*
	 * Get a range of minor numbers to work with, asking for a dynamic
	 * major unless directed otherwise at load time.
	 */
	if (scull_major) {
		dev = MKDEV(scull_major, scull_minor);
		result = register_chrdev_region(dev, 1, "scull");
	} else {
		result = alloc_chrdev_region(&dev, scull_minor, 1, "scull");
		scull_major = MAJOR(dev);
	}
	if (result < 0) {
		printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
		return result;
	}

	cdev_init(&scull_cdev, &scull_fops);
	scull_cdev.owner = THIS_MODULE;
	result = cdev_add (&scull_cdev, dev, 1);
	/* Fail gracefully if need be */
	if (result) {
		printk(KERN_NOTICE "Error %d adding scull character device", result);
		goto fail;
	}

	if (queue_init() < 0) {
		goto fail;
	}

	printk(KERN_INFO "scull: FIFO SIZE=%u, ELEMSZ=%u\n", scull_fifo_size, scull_fifo_elemsz);

	return 0; /* succeed */

  fail:
	scull_cleanup_module();
	return result;
}

module_init(scull_init_module);
module_exit(scull_cleanup_module);