/* -*- C -*-
 * main.c -- the bare pstackdev 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.
 *
 * $Id: _main.c.in,v 1.21 2004/10/14 20:11:39 corbet Exp $
 */

#include <linux/config.h>
#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/proc_fs.h>
#include <linux/fcntl.h>	/* O_ACCMODE */
#include <linux/aio.h>
#include <asm/uaccess.h>
#include "pstackdev.h"		/* local definitions */


int pstackdev_major =   PSTACKDEV_MAJOR;
int pstackdev_devs =    PSTACKDEV_DEVS;	/* number of bare pstackdev devices */
int pstackdev_maxelements =    PSTACKDEV_MAXELEMENTS;
int pstackdev_recsize = PSTACKDEV_RECSIZE;

module_param(pstackdev_major, int, 0);
module_param(pstackdev_devs, int, 0);
module_param(pstackdev_maxelements, int, 0);
module_param(pstackdev_recsize, int, 0);
MODULE_AUTHOR("Onur Sehitoglu");
MODULE_LICENSE("Dual BSD/GPL");

struct pstackdev_dev *pstackdev_devices; /* allocated in pstackdev_init */

int pstackdev_trim(struct pstackdev_dev *dev);
void pstackdev_cleanup(void);







#ifdef PSTACKDEV_USE_PROC /* don't waste space if unused */
/*
 * The proc filesystem: function to read and entry
 */

/* FIXME: Do we need this here??  It be ugly  */
int pstackdev_read_procmem(char *buf, char **start, off_t offset,
                   int count, int *eof, void *data)
{
	int i, len = 0;
	int limit = count - 80; /* Don't print more than this */
	struct pstackdev_dev *d;

	*start = buf;
	for(i = 0; i < pstackdev_devs; i++) {
		d = &pstackdev_devices[i];
		if (down_interruptible (&d->sem))
			return -ERESTARTSYS;
		len += sprintf(buf+len,"\nDevice %i: #items: %i, max: %i, recsize: %i, top= %p\n",
			i,d->size,d->info.maxelements,d->info.recsize,
			d->top);
		if (offset>=len) {
			offset -= len;
			len=0;
		} else if (!offset) {
			*start=buf+offset;
			offset=0;
		}
		up (&pstackdev_devices[i].sem);
		if (len > limit)
			break;
	}
	*eof = 1;
	//PDEBUG("procmem %i,%i\n",i,*eof);
	return len;
}

#endif /* PSTACKDEV_USE_PROC */

/*
 * Open and close
 */

int pstackdev_open (struct inode *inode, struct file *filp)
{
	struct pstackdev_dev *dev; /* device information */

	/*  Find the device */
	dev = container_of(inode->i_cdev, struct pstackdev_dev, cdev);


	/* and use filp->private_data to point to the device data */
	filp->private_data = dev;

	return 0;          /* success */
}

int pstackdev_release (struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * Data management: read and write
 */

ssize_t pstackdev_read (struct file *filp, char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct pstackdev_dev *dev = filp->private_data; 
	struct pstackdev_item *first=dev->top;
	struct pstackdev_item *next;
	struct pstackdevrec *result;


	int length;
	ssize_t retval = 0;

	if (dev->size== 0)
		return -EFAULT;
	if (first == NULL)
		return -EFAULT;

	if (down_interruptible (&dev->sem))
		return -ERESTARTSYS;

	//PDEBUG("reading %i bytes. Top: %p, size: %i, data:%p\n",
		//count, first, first->size,first->data);
	length=first->size+sizeof(int);
	length=(length<count)?length:count; /* return only first bytes */
	result=(struct pstackdevrec *)kmalloc(length,GFP_KERNEL);

	result->size=first->size;
	memcpy(result->data,first->data,length-sizeof(int));

	if (copy_to_user (buf, (void *)result,length)) {
		retval = -EFAULT;
		kfree(result);
		goto nothing;
	}
	kfree(result);
	next=first->next;
	kfree(first->data);
	kfree(first);
	dev->top=next;
	dev->size--;
	up (&dev->sem);

	//PDEBUG("read %i bytes. Top: %p, Size:%i\n",
		//length, dev->top, dev->size);

	return length;

  nothing:
	up (&dev->sem);
	return retval;
}



ssize_t pstackdev_write (struct file *filp, const char __user *buf, size_t count,
                loff_t *f_pos)
{
	struct pstackdev_dev *dev = filp->private_data; /* the first listitem */
	struct pstackdev_item *first,*next=dev->top;
	struct pstackdevrec *result;


	ssize_t retval = -ENOMEM; /* our most likely error */

	if (dev->size >= dev->info.maxelements)
		return -EFAULT;

	if (down_interruptible (&dev->sem))
		return -ERESTARTSYS;


	result=(struct pstackdevrec *)kmalloc(count,GFP_KERNEL);
	if (copy_from_user (result, buf, count)) {
		retval = -EFAULT;
		goto nomem;
	}
	
	next=dev->top;
	first=kmalloc(sizeof(struct pstackdev_item),GFP_KERNEL);
	first->size=count-sizeof(int);
	if (first->size > dev->info.recsize)
		first->size = dev->info.recsize;
	//PDEBUG("writing %i for recsize %i\n",first->size,dev->info.recsize);
	first->data=kmalloc(first->size,GFP_KERNEL);
	memcpy(first->data,result->data,first->size);

	first->next=next;
	dev->top=first;
	dev->size++;

	kfree(result);
	up (&dev->sem);
	return count;

  nomem:
  	kfree(result);
	up (&dev->sem);
	return retval;
}

/*
 * The ioctl() implementation
 */

int pstackdev_ioctl (struct inode *inode, struct file *filp,
                 unsigned int cmd, void * arg)
{

	struct pstackdev_dev *dev = filp->private_data; 
	int err = 0, ret = 0;
	struct pstackinfo info;

	//PDEBUG("number: %i\n",cmd);
	if (_IOC_TYPE(cmd) != PSTACKDEV_IOC_MAGIC) return -ENOTTY;
	if (_IOC_NR(cmd) > PSTACKDEV_IOC_MAXNR) return -ENOTTY;

	if (_IOC_DIR(cmd) & _IOC_READ)
		err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		err =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
	if (err)
		return -EFAULT;


	switch(cmd) {

	case PSTACKDEV_IOCFREE:
		pstackdev_trim(dev);
		dev->size=0;
		dev->info.maxelements=pstackdev_maxelements;
		dev->info.recsize=pstackdev_recsize;
		break;
	case PSTACKDEV_IOCINIT:
		copy_from_user((void *)&info,arg,sizeof(struct pstackinfo));
		/*__get_user(info, (struct pstackinfo __user *) arg);*/
		dev->info=info;
		break;
	default: 
		return -ENOTTY;
	}

	return ret;
}


 

/*
 * The fops
 */

struct file_operations pstackdev_fops = {
	.owner =     THIS_MODULE,
	.read =	     pstackdev_read,
	.write =     pstackdev_write,
	.ioctl =     pstackdev_ioctl,
	.open =	     pstackdev_open,
	.release =   pstackdev_release,
};

int pstackdev_trim(struct pstackdev_dev *dev)
{
	struct pstackdev_item *first,*next;

	for (first = dev->top; first;first=next) { 
		if (first->data) {
			kfree(first->data);
		}
		next=first->next;
		kfree(first);
	}
	dev->size = 0;
	dev->top = NULL;
	return 0;
}


static void pstackdev_setup_cdev(struct pstackdev_dev *dev, int index)
{
	int err, devno = MKDEV(pstackdev_major, index);
    
	cdev_init(&dev->cdev, &pstackdev_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &pstackdev_fops;
	err = cdev_add (&dev->cdev, devno, 1);
	/* Fail gracefully if need be */
	if (err)
		printk(KERN_NOTICE "Error %d adding pstackdev%d", err, index);
}



/*
 * Finally, the module stuff
 */

int pstackdev_init(void)
{
	int result, i;
	dev_t dev = MKDEV(pstackdev_major, 0);
	
	/*
	 * Register your major, and accept a dynamic number.
	 */
	if (pstackdev_major)
		result = register_chrdev_region(dev, pstackdev_devs, "pstackdev");
	else {
		result = alloc_chrdev_region(&dev, 0, pstackdev_devs, "pstackdev");
		pstackdev_major = MAJOR(dev);
	}
	if (result < 0)
		return result;

	
	/* 
	 * allocate the devices -- we can't have them static, as the number
	 * can be specified at load time
	 */
	pstackdev_devices = kmalloc(pstackdev_devs*sizeof (struct pstackdev_dev), GFP_KERNEL);
	if (!pstackdev_devices) {
		result = -ENOMEM;
		goto fail_malloc;
	}
	memset(pstackdev_devices, 0, pstackdev_devs*sizeof (struct pstackdev_dev));
	for (i = 0; i < pstackdev_devs; i++) {
		pstackdev_devices[i].size=0;
		pstackdev_devices[i].info.maxelements=pstackdev_maxelements ;
		pstackdev_devices[i].info.recsize=pstackdev_recsize ;
		sema_init (&pstackdev_devices[i].sem, 1);
		pstackdev_setup_cdev(pstackdev_devices + i, i);
	}


#ifdef PSTACKDEV_USE_PROC /* only when available */
	create_proc_read_entry("pstackdevmem", 0, NULL, pstackdev_read_procmem, NULL);
#endif
	return 0; /* succeed */

  fail_malloc:
	unregister_chrdev_region(dev, pstackdev_devs);
	return result;
}



void pstackdev_cleanup(void)
{
	int i;

#ifdef PSTACKDEV_USE_PROC
	remove_proc_entry("pstackdevmem", NULL);
#endif

	for (i = 0; i < pstackdev_devs; i++) {
		cdev_del(&pstackdev_devices[i].cdev);
		pstackdev_trim(pstackdev_devices + i);
	}
	kfree(pstackdev_devices);

	unregister_chrdev_region(MKDEV (pstackdev_major, 0), pstackdev_devs);
}


module_init(pstackdev_init);
module_exit(pstackdev_cleanup);
