/*
 * RT-FIFO devices
 *
 * Written by Michael Barabanov
 * Copyright (C) VJY Associates LLC, 1996-1999
 * bug reports to bugs@fsmlabs.com
 * Released under the terms of the GNU  General Public License
 * Includes much code from Linux fs/pipe.c copyright (C) Linus Torvalds.
 *
 */

#include <linux/module.h>
#include <linux/major.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/poll.h>
#include <linux/smp.h>
#include <linux/vmalloc.h>

#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/irq.h>

#include <rtl_conf.h>
#include <rtl_sync.h>
#include <rtl_fifo.h>
#include <rtl_core.h>


#ifdef __SMP__
spinlock_t fifo_spinlock;
#define RTL_SPIN_LOCK fifo_spinlock
#endif
#define RTF_NO 64

struct rt_fifo_struct {
	int open;
	int user_open;
	char *base;
	int bufsize;
	int start;
	int len;
	int (*user_handler) (unsigned int fifo);
	struct wait_queue *wait;
};

static struct rt_fifo_struct fifos[RTF_NO];
static int rtl_fifo_to_wakeup[RTF_NO] = {0,}; 
static int rtl_fifo_irq = 0; 

#define RTF_ADDR(minor)		(&fifos[minor])

#define RTF_OPEN(minor)		(RTF_ADDR(minor)->open)
#define RTF_USER_OPEN(minor)	(RTF_ADDR(minor)->user_open)
#define RTF_BASE(minor)		(RTF_ADDR(minor)->base)
#define RTF_BUF(minor)		(RTF_ADDR(minor)->bufsize)
#define RTF_START(minor)	(RTF_ADDR(minor)->start)
#define RTF_HANDLER(minor)	(RTF_ADDR(minor)->user_handler)
#define RTF_LEN(minor)		(RTF_ADDR(minor)->len)
#define RTF_FREE(minor)		(RTF_BUF(minor) - RTF_LEN(minor))
#define RTF_WAIT(minor)		(RTF_ADDR(minor)->wait)

#define RTF_WRAP(minor,pos)	((pos) < RTF_BUF(minor)? (pos) : (pos) - RTF_BUF(minor))
#define RTF_END(minor)		RTF_WRAP(minor, RTF_START(minor)+RTF_LEN(minor))
#define RTF_EMPTY(minor)	(RTF_LEN(minor)==0)
#define RTF_FULL(minor)		(RTF_FREE(minor)==0)
#define RTF_MAX_RCHUNK(minor)	(RTF_BUF(minor) - RTF_START(minor))
#define RTF_MAX_WCHUNK(minor)	(RTF_BUF(minor) - RTF_END(minor))

#define RTL_SLEEP_POS 1
#define RTL_NEEDS_WAKE_POS 2

static int default_user_handler (unsigned int fifo)
{
	return 0;
}

static void rtf_wake_up(void *p)
{
	struct rt_fifo_struct *fifo_ptr = (struct rt_fifo_struct *) p;
	wake_up_interruptible(&(fifo_ptr->wait));
}


/* These are for use in the init and exit code of real-time modules
   DO NOT call these from a RT task  */

int rtf_resize(unsigned int minor, int size)
{
	void *mem;
	void *old;
	unsigned int interrupt_state;

	if (minor >= RTF_NO) {
		return -ENODEV;
	}
	mem = vmalloc(size);
	if (!mem) {
		return -ENOMEM;
	}
	old = RTF_BASE(minor);


	rtl_critical(interrupt_state);
	RTF_BASE(minor) = mem;
	RTF_BUF(minor) = size;
	RTF_START(minor) = 0;
	RTF_LEN(minor) = 0;
	rtl_end_critical(interrupt_state);

	if (RTF_OPEN(minor)) {
		vfree(old);
	}
	return 0;
}


int rtf_create(unsigned int minor, int size)
{
	int ret;

	if (minor >= RTF_NO) {
		return -ENODEV;
	}
	if (RTF_OPEN(minor)) {
		return -EBUSY;
	}
	if ((ret = rtf_resize(minor, size)) < 0) {
		printk("rtf_create: failed to allocate memory, %d\n", ret);
		return -ENOMEM;
	}
	RTF_OPEN(minor) = 1;
	RTF_USER_OPEN(minor) = 0;
	RTF_HANDLER(minor) = &default_user_handler;

	init_waitqueue (&RTF_WAIT(minor));

	return 0;
}


int rtf_destroy(unsigned int minor)
{
	if (minor >= RTF_NO) {
		return -ENODEV;
	}
	if (RTF_USER_OPEN(minor)) {
		return -EINVAL;
	}
	if (!RTF_OPEN(minor)) {
		return -EINVAL;
	}
	RTF_HANDLER(minor) = &default_user_handler;
	RTF_OPEN(minor) = 0;
	vfree(RTF_BASE(minor));
	return 0;
}


int rtf_create_handler(unsigned int minor, int (*handler) (unsigned int fifo))
{
	if (minor >= RTF_NO || !RTF_OPEN(minor) || !handler) {
		return -EINVAL;
	}
	RTF_HANDLER(minor) = handler;
	return 0;
}


/* these can be called from RT tasks and interrupt handlers */
static void fifo_wake_sleepers(int );

int rtf_put(unsigned int minor, void *buf, int count)
{
	unsigned int interrupt_state;
	int chars = 0, free = 0, written = 0;
	char *pipebuf;

	if (minor >= RTF_NO) {
		return -ENODEV;
	}
	if (!RTF_OPEN(minor))
		return -EINVAL;

	rtl_critical(interrupt_state);
	if (RTF_FREE(minor) < count) {
		rtl_end_critical(interrupt_state);
		return -ENOSPC;
	}
	while (count > 0 && (free = RTF_FREE(minor))) {
		chars = RTF_MAX_WCHUNK(minor);
		if (chars > count)
			chars = count;
		if (chars > free)
			chars = free;
		pipebuf = RTF_BASE(minor) + RTF_END(minor);
		written += chars;
		RTF_LEN(minor) += chars;
		count -= chars;
		memcpy(pipebuf, buf, chars);
		buf += chars;
	}
	rtl_end_critical(interrupt_state);
	fifo_wake_sleepers(minor);
	return written;
}


int rtf_get(unsigned int minor, void *buf, int count)
{
	unsigned int interrupt_state;
	int chars = 0, size = 0, read = 0;
	char *pipebuf;

	if (minor >= RTF_NO) {
		return -ENODEV;
	}
	if (!RTF_OPEN(minor))
		return -EINVAL;

	rtl_critical(interrupt_state);
	while (count > 0 && (size = RTF_LEN(minor))) {
		chars = RTF_MAX_RCHUNK(minor);
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;

		read += chars;
		pipebuf = RTF_BASE(minor) + RTF_START(minor);
		RTF_START(minor) += chars;
		RTF_START(minor) = RTF_WRAP(minor, RTF_START(minor));
		RTF_LEN(minor) -= chars;
		count -= chars;
		memcpy(buf, pipebuf, chars);
		buf += chars;
	}
	rtl_end_critical(interrupt_state);
	fifo_wake_sleepers(minor);
	return read;
}

static void fifo_setup_sleep(unsigned int minor)
{
	clear_bit(0,&rtl_fifo_to_wakeup[minor]);
}

static void fifo_wake_sleepers(int minor)
{
	if (waitqueue_active(&RTF_WAIT(minor))) {
		set_bit(0, &rtl_fifo_to_wakeup[minor]);
		if (rtl_fifo_irq > 0) {
			rtl_global_pend_irq(rtl_fifo_irq);
		}
	}
}

static void fifo_irq_handler (int irq, void *dev_id, struct pt_regs *p)
{
	int minor;

	for(minor=0; minor < RTF_NO; minor++) {
		if (test_and_clear_bit(0,&rtl_fifo_to_wakeup[minor])) {
			rtf_wake_up(RTF_ADDR(minor));
		}
	}
}


/* 
 * these are file_operations functions
 * called by user tasks via the fops structure 
 */


static int rtf_open(struct inode *inode, struct file *filp)
{
	unsigned int minor = MINOR(inode->i_rdev);

	if (minor >= RTF_NO)
		return -ENODEV;

	if (!RTF_OPEN(minor)) {
		return -ENODEV;
	}
	if (RTF_USER_OPEN(minor)) {
		return -EACCES;
	}
	RTF_USER_OPEN(minor) = 1;

	MOD_INC_USE_COUNT;
	return 0;
}


static int rtf_release(struct inode *inode, struct file *file)
{
	unsigned int minor = MINOR(inode->i_rdev);

	if (!RTF_USER_OPEN(minor)) {
		printk("rtf: release on a not opened descriptor inode=%d\n",(int)inode->i_ino);
		return 0;  /* that was just a warning */
	}
	RTF_USER_OPEN(minor) = 0;
	MOD_DEC_USE_COUNT;
	return 0;
}


static loff_t rtf_llseek(struct file *file, loff_t offset, int origin)
{
	return -ESPIPE;
}

#define RTL_FIFO_TIMEOUT (HZ/10)

static ssize_t rtf_read(struct file *filp, char *buf, size_t count, loff_t* ppos)
{
	struct inode* inode = filp->f_dentry->d_inode;
	unsigned int minor = MINOR(inode->i_rdev);
	unsigned int interrupt_state;
	int result;

	int chars = 0, size = 0, read = 0;
	char *pipebuf;

	if (filp->f_flags & O_NONBLOCK) {
		/*      if (RTF_LOCK(minor))
		   return -EAGAIN;
		 */
		if (RTF_EMPTY(minor))
			return 0;
	} else
		while (RTF_EMPTY(minor)  ) {
			if (signal_pending(current))
				return -ERESTARTSYS;
			fifo_setup_sleep(minor);
			interruptible_sleep_on_timeout(&RTF_WAIT(minor),
					RTL_FIFO_TIMEOUT);
		}
/*      RTF_LOCK(minor)++; */
	while (count > 0 && (size = RTF_LEN(minor))) {
		chars = RTF_MAX_RCHUNK(minor);
		if (chars > count)
			chars = count;
		if (chars > size)
			chars = size;

		read += chars;
		pipebuf = RTF_BASE(minor) + RTF_START(minor);
		count -= chars;
		copy_to_user(buf, pipebuf, chars); 
		rtl_critical(interrupt_state);
		RTF_START(minor) += chars;
		RTF_START(minor) = RTF_WRAP(minor, RTF_START(minor));
		RTF_LEN(minor) -= chars;
		rtl_end_critical(interrupt_state);
		buf += chars;
	}
/*      RTF_LOCK(minor)--; */
	if (read) {
		inode->i_atime = CURRENT_TIME;
		if ((result = (*RTF_HANDLER(minor)) (minor)) < 0) {
			return result;
		}
		return read;
	}
	return 0;
}


static ssize_t rtf_write(struct file *filp, const char *buf, size_t count, loff_t* ppos)
{
	struct inode* inode = filp->f_dentry->d_inode;
	unsigned int minor = MINOR(inode->i_rdev);
	unsigned int interrupt_state;
	int chars = 0, free = 0, written = 0;
	char *pipebuf;
	int result;

	if (count <= RTF_BUF(minor))
		free = count;
	else
		free = 1;

	while (count > 0) {
		while (RTF_FREE(minor) < free)  {
			if (signal_pending(current))
				return written ? : -ERESTARTSYS;
			if (filp->f_flags & O_NONBLOCK)
				return written ? : -EAGAIN;
			fifo_setup_sleep(minor);
			interruptible_sleep_on_timeout(&RTF_WAIT(minor),
					RTL_FIFO_TIMEOUT);
		}
		/*      RTF_LOCK(minor)++; */
		while (count > 0 && (free = RTF_FREE(minor))) {
			chars = RTF_MAX_WCHUNK(minor);
			if (chars > count)
				chars = count;
			if (chars > free)
				chars = free;
			rtl_critical(interrupt_state);
			pipebuf = RTF_BASE(minor) + RTF_END(minor);
			rtl_end_critical(interrupt_state);
			count -= chars;
			written += chars;

			copy_from_user(pipebuf, buf, chars);
			rtl_critical(interrupt_state);
			RTF_LEN(minor) += chars;
			rtl_end_critical(interrupt_state);
			buf += chars;
		}
		/*      RTF_LOCK(minor)--; */
		free = 1;
	}
	inode->i_ctime = inode->i_mtime = CURRENT_TIME;
	if ((result = (*RTF_HANDLER(minor)) (minor)) < 0) {
		return result;
	}
	return written;
}

static	unsigned int rtf_poll(struct file *filp, poll_table *wait)
{
	struct inode* inode = filp->f_dentry->d_inode;
	unsigned int minor = MINOR(inode->i_rdev);
	fifo_setup_sleep(minor);
	poll_wait(filp, &RTF_WAIT(minor), wait);
	if ( ! RTF_EMPTY(minor)) {
		return POLLIN | POLLRDNORM;
	} else {
		return POLLOUT | POLLWRNORM;
	}
	return 0;
}

static struct file_operations rtf_fops =
{
	rtf_llseek,
	rtf_read,
	rtf_write,
	NULL,
	rtf_poll,
	NULL,
	NULL,
	rtf_open,
	NULL,
	rtf_release,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};




int rtf_init (void)
{
	int irq = -1;
	int i;

	spin_lock_init (&fifo_spinlock);
	if (register_chrdev (RTF_MAJOR, "rtf", &rtf_fops)) {
		printk ("RT-FIFO: unable to get major %d\n", RTF_MAJOR);
		return -EIO;
	}
	for (i = 0; i < RTF_NO; i++) {
		rtl_fifo_to_wakeup[i] = 0;
	}
	irq = rtl_get_soft_irq (fifo_irq_handler, "fifo");
	if (irq > 0) {
		rtl_fifo_irq = irq;
	} else {
		unregister_chrdev (RTF_MAJOR, "rtf");
		printk ("Can't get an irq for rt fifos");
		return -EIO;	/* should have a different return */
	}
	return 0;
}
		
void rtf_uninit(void)
{
	if (rtl_fifo_irq) {
		rtl_free_soft_irq(rtl_fifo_irq);
	}
	unregister_chrdev(RTF_MAJOR, "rtf");
}


#ifdef CONFIG_RTL_POSIX_IO
#include <rtl_posixio.h>

static int rtl_rtf_open (struct rtl_file *filp)
{
	if (!(filp->f_flags & O_NONBLOCK)) {
		return -EACCES; /* TODO: implement blocking IO */
	}
	return 0;
}

static int rtl_rtf_release (struct rtl_file *filp)
{
	return 0;
}

static ssize_t rtl_rtf_write(struct rtl_file *filp, const char *buf, size_t count, loff_t* ppos)
{
	return rtf_put(RTL_MINOR_FROM_FILEPTR(filp), (char *) buf, count);
	
}

static ssize_t rtl_rtf_read(struct rtl_file *filp, char *buf, size_t count, loff_t* ppos)
{
	return rtf_get(RTL_MINOR_FROM_FILEPTR(filp), buf, count);
	
}

static struct rtl_file_operations rtl_fifo_fops = {
       	NULL,
	rtl_rtf_read,
	rtl_rtf_write,
	NULL,
	NULL,
	NULL,
	rtl_rtf_open,
	rtl_rtf_release
};
#endif


#ifdef MODULE


int init_module(void)
{
	int ret;
	ret = rtf_init();
	if (ret < 0) {
		return ret;
	}
#ifdef CONFIG_RTL_POSIX_IO
	if (rtl_register_chrdev (RTF_MAJOR, "rtf", &rtl_fifo_fops)) {
		printk ("RT-FIFO: unable to get RTL major %d\n", RTF_MAJOR);
		rtf_uninit();
		return -EIO;
	}
#endif
	return 0;

}


void cleanup_module(void)
{
#ifdef CONFIG_RTL_POSIX_IO
	rtl_unregister_chrdev(RTF_MAJOR, "rtf");
#endif
	rtf_uninit();
}


#endif				/* MODULE */
