#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/device.h> 

#include "char_dev.h"


static dev_t devno = 0;
static uint32_t my_fifo_major = MY_FIFO_MAJOR;
static uint32_t my_fifo_minor = 0;

static struct class *myclass;


static int my_fifo_size = MY_FIFO_SIZE;
module_param(my_fifo_size, int, S_IRUGO);


struct my_fifo_dev{
    wait_queue_head_t readq, writeq; 
    char *fifo_begain,*fifo_end;
    uint32_t fifo_size;
    char *rp,*wp;
    uint32_t nreaders,nwriters;
    struct semaphore sem;
    struct cdev my_cdev;
};

static struct my_fifo_dev *my_fifo_device_p = NULL;

/* 打开设备 */
static int my_fifo_open(struct inode *inode, struct file *filp) 
{
    struct my_fifo_dev *my_fifo_dev = NULL;

    my_fifo_dev = container_of(inode->i_cdev, struct my_fifo_dev, my_cdev);
    filp->private_data = my_fifo_dev;

    //printk(KERN_INFO "[ KERN_INFO ]  my_fifo_dev addr = 0x%x\n",my_fifo_dev);

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

    if(!my_fifo_dev->fifo_begain){
        //printk(KERN_INFO "[ KERN_INFO ]  fifo is empty, kmalloc it ...\n");
        my_fifo_dev->fifo_begain = kmalloc(my_fifo_size, GFP_KERNEL);
        if(!my_fifo_dev->fifo_begain){
            up(&my_fifo_dev->sem);
            return -ENOMEM;
        }
        
        my_fifo_dev->fifo_size = my_fifo_size;
        my_fifo_dev->fifo_end = my_fifo_dev->fifo_begain + my_fifo_size;
        my_fifo_dev->rp = my_fifo_dev->wp = my_fifo_dev->fifo_begain;
    }

    //printk(KERN_INFO "[ KERN_INFO ]  open...rp:0x%p,wp:0x%p\n",my_fifo_dev->rp,my_fifo_dev->wp);


    /* use f_mode,not  f_flags: it's cleaner (fs/open.c tells why) */
    if (filp->f_mode & FMODE_READ)
        my_fifo_dev->nreaders++;
    if (filp->f_mode & FMODE_WRITE)
        my_fifo_dev->nwriters++;
    up(&my_fifo_dev->sem);

    
    return nonseekable_open(inode,filp);
}


static ssize_t my_fifo_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    struct my_fifo_dev *my_fifo_dev = filp->private_data;
    uint32_t read_cont = 0;
    
    if (!(filp->f_mode & FMODE_READ)){
        printk(KERN_ERR "[ KERN_ERR ]  read is not permitted\n");
        return -EPERM;
    }

    if(down_interruptible(&my_fifo_dev->sem)){
        printk(KERN_ERR "[ KERN_ERR ]  down_interruptible err\n");
        return -ERESTARTSYS;
    }

    while(my_fifo_dev->rp == my_fifo_dev->wp){
        up(&my_fifo_dev->sem);

        if(filp->f_flags & O_NONBLOCK)
            return -EAGAIN;

        //printk(KERN_INFO "[ KERN_INFO ]  rp:0x%p,wp:0x%p\n",my_fifo_dev->rp,my_fifo_dev->wp);
        //printk(KERN_INFO "[ KERN_INFO ]  waiting...\n");
        
        if(wait_event_interruptible(my_fifo_dev->readq, (my_fifo_dev->rp != my_fifo_dev->wp)))
            return -ERESTARTSYS;
        
        //printk(KERN_INFO "[ KERN_INFO ]  wake up!\n");
        
        if(down_interruptible(&my_fifo_dev->sem))
            return -ERESTARTSYS;
    }
    
    if(my_fifo_dev->rp < my_fifo_dev->wp){
        read_cont = min(my_fifo_dev->wp - my_fifo_dev->rp, cnt);
    }
    else{
        read_cont = min(my_fifo_dev->fifo_end - my_fifo_dev->rp , cnt);
    }

    //printk(KERN_INFO "[ KERN_INFO ]  read %d bytes\n",read_cont);
    
    if(copy_to_user(buf, my_fifo_dev->rp, read_cont)){
        up(&my_fifo_dev->sem);
        return -EFAULT;
    }

    my_fifo_dev->rp += read_cont;

    if(my_fifo_dev->rp == my_fifo_dev->fifo_end)
        my_fifo_dev->rp = my_fifo_dev->fifo_begain;

    up(&my_fifo_dev->sem);

    wake_up_interruptible(&my_fifo_dev->writeq);
    

    return read_cont;
}

/* How much space is free? */
static int spacefree(struct my_fifo_dev *dev)
{
	if (dev->rp == dev->wp)
		return dev->fifo_size - 1;
	return ((dev->rp + dev->fifo_size - dev->wp) % dev->fifo_size) - 1;
}


/* Wait for space for writing; caller must hold device semaphore.  On
 * error the semaphore will be released before returning. */
static int getwritespace(struct my_fifo_dev *dev, struct file *filp)
{
	while (spacefree(dev) == 0) { /* full */
		DEFINE_WAIT(wait);
		
		up(&dev->sem);
		if (filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		prepare_to_wait(&dev->writeq, &wait, TASK_INTERRUPTIBLE);
		if (spacefree(dev) == 0){
            //printk(KERN_INFO "[ KERN_INFO ]  rp:0x%p,wp:0x%p\n",dev->rp,dev->wp);
            //printk(KERN_INFO "[ KERN_INFO ]  waiting...\n");
			schedule();
            //printk(KERN_INFO "[ KERN_INFO ]  wake up!\n");
		}
		finish_wait(&dev->writeq, &wait);
		if (signal_pending(current)){
            printk(KERN_ERR "[ KERN_ERR ]  signal_pending err\n");
			return -ERESTARTSYS; /* signal: tell the fs layer to handle it */
		}
		if (down_interruptible(&dev->sem)){
            printk(KERN_ERR "[ KERN_ERR ]  down_interruptible err\n");
			return -ERESTARTSYS;
		}
	}
	return 0;
}	



static ssize_t my_fifo_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
    struct my_fifo_dev *my_fifo_dev = filp->private_data;
    uint32_t write_cont = 0;
    uint32_t ret = 0;
    
    if (!(filp->f_mode & FMODE_WRITE)){
        printk(KERN_ERR "[ KERN_ERR ]  write is not permitted\n");
        return -EPERM;
    }

    if(down_interruptible(&my_fifo_dev->sem)){
        printk(KERN_ERR "[ KERN_ERR ]  down_interruptible err\n");
        return -ERESTARTSYS;
    }

    ret = getwritespace(my_fifo_dev,filp);
    if(ret){
        printk(KERN_ERR "[ KERN_ERR ]  getwritespace failed ret = %d\n",ret);
        return ret;
    }

    write_cont = min(spacefree(my_fifo_dev),cnt);
    if(my_fifo_dev->rp <= my_fifo_dev->wp)
        write_cont = min(write_cont, (my_fifo_dev->fifo_end - my_fifo_dev->wp));
    else
        write_cont = min(write_cont, (my_fifo_dev->rp - my_fifo_dev->wp - 1));
    
    if (copy_from_user(my_fifo_dev->wp, buf, write_cont)) {
        up (&my_fifo_dev->sem);
        return -EFAULT;
    }
    
    my_fifo_dev->wp += write_cont;
    if (my_fifo_dev->wp == my_fifo_dev->fifo_end)
        my_fifo_dev->wp = my_fifo_dev->fifo_begain; /* wrapped */
    up(&my_fifo_dev->sem);

    wake_up_interruptible(&my_fifo_dev->readq);
    
    return write_cont;
}
static int my_fifo_release(struct inode *inode, struct file *filp)
{
    struct my_fifo_dev *my_fifo_dev = NULL;

    my_fifo_dev = filp->private_data;
    
    down(&my_fifo_dev->sem);
    if (filp->f_mode & FMODE_READ)
        my_fifo_dev->nreaders--;
    if (filp->f_mode & FMODE_WRITE)
        my_fifo_dev->nwriters--;
	if (my_fifo_dev->nreaders + my_fifo_dev->nwriters == 0) {
        kfree(my_fifo_dev->fifo_begain);
        my_fifo_dev->fifo_begain = NULL; 
    }
	up(&my_fifo_dev->sem);
	return 0;

}

static struct file_operations my_fifo_fops = {
.owner = THIS_MODULE, 
.open = my_fifo_open,
.read = my_fifo_read,
.write = my_fifo_write,
.release = my_fifo_release,
};


static int __init my_fifo_init(void)
{
    uint32_t ret = 0;
    printk(KERN_INFO "[ KERN_INFO ]  my fifo  Module Init start\n");

    if(my_fifo_major){
        devno = MKDEV(my_fifo_major,my_fifo_minor);
        ret = register_chrdev_region(devno, 1, "my_fifo");
    }
    else{

        ret = alloc_chrdev_region(&devno, 0, 1, "my_fifo");
        my_fifo_major = MAJOR(devno);
    }
    if(ret < 0){
        printk(KERN_ERR "[ KERN_ERR ]  chrdev alloc region failed\n");
        goto devno_fail;
    }

    printk(KERN_INFO "[ KERN_INFO ]  devno is %u(0x%x)(major:%u,minor:%u)\n",devno,devno,my_fifo_major,my_fifo_minor);

    my_fifo_device_p = kmalloc(sizeof(struct my_fifo_dev), GFP_KERNEL);
    if(!my_fifo_device_p){
        printk(KERN_ERR "[ KERN_ERR ]  kmalloc failed!\n");
        goto kmalloc_fail;
    }
    memset(my_fifo_device_p, 0, sizeof(struct my_fifo_dev));
    
    init_waitqueue_head(&(my_fifo_device_p->readq));
    init_waitqueue_head(&(my_fifo_device_p->writeq));
    sema_init(&my_fifo_device_p->sem,1);

    cdev_init(&my_fifo_device_p->my_cdev, &my_fifo_fops);
	my_fifo_device_p->my_cdev.owner = THIS_MODULE;
	ret = cdev_add (&my_fifo_device_p->my_cdev, devno, 1);

    if(ret){
        printk(KERN_ERR "[ KERN_ERR ]  cdev_add failed!\n");
        goto cdev_add_fail;
    }

    myclass = class_create(THIS_MODULE, "myclass");
    if(IS_ERR(myclass))
    {
        printk(KERN_ERR" create class faild!/n");
        ret = -EBUSY;
        goto class_create_fail;
    }

    device_create(myclass,NULL,MKDEV(my_fifo_major,my_fifo_minor),NULL,"myfifo");

    return 0;

class_create_fail:
    cdev_del(&my_fifo_device_p->my_cdev);
cdev_add_fail:
    kfree(my_fifo_device_p);
kmalloc_fail:
    unregister_chrdev_region(devno, 1);
devno_fail:
 	return ret;
}

static void __exit my_fifo_exit(void)
{

    device_destroy(myclass,MKDEV(my_fifo_major,my_fifo_minor));

    class_destroy(myclass);
    
    cdev_del(&my_fifo_device_p->my_cdev);

    if(my_fifo_device_p->fifo_begain != NULL)
        kfree(my_fifo_device_p->fifo_begain);

    kfree(my_fifo_device_p);
    
    if(!devno)
        unregister_chrdev_region(devno, 1);
    
    printk("[ default ]   my_fifo  Module Exit\n");
}

module_init(my_fifo_init);
module_exit(my_fifo_exit);


MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuchao");

