#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

#include <linux/config.h>
#include <linux/module.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/fcntl.h>
#include <linux/poll.h>
#include <linux/unistd.h>
#include <linux/init.h>
#include <asm/system.h>
#include <asm/uaccess.h>

#ifndef min
#  define min(a,b) ((a)<(b) ? (a) : (b)) /* we use it in this file */
#endif

#define BUF_SIZE 4072
#define DEV_NAME "dm14_dev"

typedef struct DM14_Buffer {
    wait_queue_head_t inq, outq;    /* read and write queues */
    char *buffer, *end;             /* begin of buf, end of buf */
    char *rp, *wp;                  /* where to read, where to write */
    struct semaphore sem;           /* mutual exclusion semaphore */
} DM14_Buffer;
DM14_Buffer *devs;

int dm14_open(struct inode *inode, struct file *filp) {
    unsigned int num = MINOR(inode->i_rdev);

    if (filp->f_mode & FMODE_READ) {
        if (down_interruptible(&devs[num].sem))
    	    return -ERESTARTSYS;
	devs[num].rp = devs[num].buffer;
	up(&devs[num].sem);
    }

    num = (num+1)%2;
    if (filp->f_mode & FMODE_WRITE) {
        if (down_interruptible(&devs[num].sem))
    	    return -ERESTARTSYS;
	devs[num].wp = devs[num].buffer;
	up(&devs[num].sem);
    }
    
    if (filp->f_flags & (O_WRONLY | O_TRUNC)) {
        if (down_interruptible(&devs[num].sem))
    	    return -ERESTARTSYS;
        devs[num].rp = devs[num].wp = devs[num].buffer;
        memset(devs[num].buffer, 0, BUF_SIZE);
	up(&devs[num].sem);
    }

    return 0;
}

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

ssize_t dm14_read (struct file *filp, char *buf, size_t count, loff_t *f_pos) {
    unsigned int num = MINOR(filp->f_dentry->d_inode->i_rdev);
    
    if (f_pos != &filp->f_pos)
	return -ESPIPE;

    if (down_interruptible(&devs[num].sem))
        return -ERESTARTSYS;

    while (devs[num].rp == devs[num].wp) { /* nothing to read */
        up(&devs[num].sem); /* release the lock */
        if (filp->f_flags & O_NONBLOCK)
            return -EWOULDBLOCK;
        if (wait_event_interruptible(devs[num].inq, (devs[num].rp != devs[num].wp)))
            return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
        /* otherwise loop, but first reacquire the lock */
        if (down_interruptible(&devs[num].sem))
            return -ERESTARTSYS;
    }
    /* ok, data is there, return something */
    if (devs[num].wp > devs[num].rp)
        count = min(count, devs[num].wp - devs[num].rp);
    else /* the write pointer has wrapped, return data up to devs[num].end */
        count = min(count, devs[num].end - devs[num].rp);
    if (copy_to_user(buf, devs[num].rp, count)) {
        up (&devs[num].sem);
        return -EFAULT;
    }
    devs[num].rp += count;
    if (devs[num].rp == devs[num].end)
        devs[num].rp = devs[num].buffer; /* wrapped */
    up (&devs[num].sem);

    /* finally, awake any writers and return */
    wake_up_interruptible(&devs[num].outq);

    return count;
}

static inline int spacefree(int num) {
    if (devs[num].rp == devs[num].wp)
        return BUF_SIZE - 1;
    return ((devs[num].rp + BUF_SIZE - devs[num].wp) % BUF_SIZE) - 1;
}

ssize_t dm14_write(struct file *filp, const char *buf, size_t count, loff_t *f_pos) {
    unsigned int num = (MINOR(filp->f_dentry->d_inode->i_rdev)+1)%2;
    
    if (f_pos != &filp->f_pos)
	return -ESPIPE;

    if (down_interruptible(&devs[num].sem))
        return -ERESTARTSYS;
    
    /* Make sure there's space to write */
    while (spacefree(num) == 0) { /* full */
        up(&devs[num].sem);
        if (filp->f_flags & O_NONBLOCK)
            return -EWOULDBLOCK;
        if (wait_event_interruptible(devs[num].outq, spacefree(num) > 0))
            return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
        if (down_interruptible(&devs[num].sem))
            return -ERESTARTSYS;
    }

    /* ok, space is there, accept something */
    count = min(count, spacefree(num));
    if (devs[num].wp >= devs[num].rp)
        count = min(count, devs[num].end - devs[num].wp); /* up to end-of-buffer */
    else /* the write pointer has wrapped, fill up to rp-1 */
        count = min(count, devs[num].rp - devs[num].wp - 1);
    if (copy_from_user(devs[num].wp, buf, count)) {
        up (&devs[num].sem);
        return -EFAULT;
    }
    devs[num].wp += count;
    if (devs[num].wp == devs[num].end)
        devs[num].wp = devs[num].buffer; /* wrapped */
    up(&devs[num].sem);

    /* finally, awake any reader */
    wake_up_interruptible(&devs[num].inq);  /* blocked in read() and select() */

    return count;
}

int dm14_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) {
    return 0;
}

loff_t dm14_llseek(struct file *filp,  loff_t off, int whence) {
    return -ESPIPE; /* unseekable */
}

struct file_operations dm14_fops = {
    llseek:     dm14_llseek,
    read:       dm14_read,
    write:      dm14_write,
    ioctl:	dm14_ioctl,
    open:       dm14_open,
    release:    dm14_release,
};

int dm14_init(void) {
    int num=0;
    
    if ((num = register_chrdev(254, DEV_NAME, &dm14_fops)) < 0) {
	printk(KERN_WARNING "dm14: couldn't register device as 254\n");
	return num;
    }

    devs = kmalloc(2 * sizeof(DM14_Buffer), GFP_KERNEL);
    if (devs == NULL)
        return -ENOMEM;
    memset(devs, 0, 2 * sizeof(DM14_Buffer));

    for (num = 0; num < 2; num++) {
	devs[num].buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
	if (!devs[num].buffer)
	    return -ENOMEM;
	memset(devs[num].buffer, 0, BUF_SIZE);
	devs[num].rp = devs[num].wp = devs[num].buffer;
	devs[num].end = &devs[num].buffer[BUF_SIZE-1];
        init_waitqueue_head(&(devs[num].inq));
        init_waitqueue_head(&(devs[num].outq));
        sema_init(&devs[num].sem, 1);
    }
    
    printk("DM14: Init\n");

    return 0;
}

void dm14_cleanup(void) {
    int num=0;
    
    if ((num = unregister_chrdev(254, DEV_NAME)) < 0) {
	printk(KERN_WARNING "dm14: couldn't unregister device as 254\n");
    }

    if (!devs)
	return; /* nothing else to release */
    
    for (num=0; num < 2; num++) {
        if (devs[num].buffer)
            kfree(devs[num].buffer);
    }

    kfree(devs);
    devs = NULL;

    printk("DM14: Cleanup\n");
}

module_init(dm14_init);
module_exit(dm14_cleanup);

MODULE_AUTHOR("Tino Didriksen");
MODULE_LICENSE("GPL");
