【Linux设备驱动程序(第三版)】----异步通知


【Linux设备驱动程序(第三版)】----异步通知

scullp.c

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h>//file_operations, file
#include <linux/proc_fs.h>//proc
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/cdev.h>//cdev
#include <asm/uaccess.h>//copy_to_user & copy_from_user
#include <linux/init.h>
#include <linux/ioctl.h>//ioctl

MODULE_LICENSE("Dual BSD/GPL");

static int scullp_major = 250;
static int scullp_minor = 0;
static int scull_p_nr_devs = 1;
static int scull_p_buffer = 4000;
dev_t scull_p_devno;

struct scull_pipe{
	wait_queue_head_t inq, outq;
	char *buffer, *end;
	int buffersize;
	char *rp, *wp;
	int nreaders, nwriters;
	struct fasync_struct *async_queue;
	struct semaphore sem;
	struct cdev cdev;
};

struct scull_pipe *scull_p_devices;

ssize_t scull_p_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct scull_pipe *dev = filp->private_data;

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

	while(dev->rp == dev->wp){
		up(&dev->sem);		
		if(filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		if(wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
			return -ERESTARTSYS;
		if(down_interruptible(&dev->sem))
			return -ERESTARTSYS;
	}
	if(dev->wp > dev->rp)
		count = min(count, (size_t)(dev->wp - dev->rp));	
	else
		count = min(count, (size_t)(dev->end - dev->rp));

	if(copy_to_user(buf, dev->rp, count)){
		up(&dev->sem);
		return -EFAULT;
	}

	dev->rp += count;
	if(dev->rp == dev->end)
		dev->rp = dev->buffer;
	up(&dev->sem);

	wake_up_interruptible(&dev->outq);
	return count;
}

static int spacefree(struct scull_pipe *dev)
{
	if(dev->rp == dev->wp)
		return dev->buffersize - 1;
	return ((dev->rp + dev->buffersize - dev->wp) % dev->buffersize) -1;
}

static int scull_getwritespace(struct scull_pipe *dev, struct file *filp)
{
	while(spacefree(dev) == 0){
		DEFINE_WAIT(wait);
		
		up(&dev->sem);

		if(filp->f_flags & O_NONBLOCK)
			return -EAGAIN;
		prepare_to_wait(&dev->outq, &wait, TASK_INTERRUPTIBLE);
		if(spacefree(dev) == 0)
			schedule();
		finish_wait(&dev->outq, &wait);
		if(signal_pending(current))
			return -ERESTARTSYS;
		if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	}
	return 0;
}

static int scull_p_fasync(int fd, struct file *filp, int mode)
{
	struct scull_pipe *dev = filp->private_data;

	return fasync_helper(fd, filp, mode,&dev->async_queue);
}


ssize_t scull_p_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	struct scull_pipe *dev = filp->private_data;
	int result;
	
	if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	result = scull_getwritespace(dev, filp);
	if(result)
		return result;
	count = min(count, (size_t)spacefree(dev));
	if(dev->wp >= dev->rp)
		count = min(count, (size_t)(dev->end - dev->wp));	
	else
		count = min(count, (size_t)(dev->rp - dev->wp - 1));

	if(copy_from_user(dev->wp, buf, count)){
		up(&dev->sem);
		return -EFAULT;
	}

	dev->wp += count;
	if(dev->wp == dev->end)
		dev->wp = dev->buffer;
	up(&dev->sem);

	wake_up_interruptible(&dev->inq);

	if(dev->async_queue)
		kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
	
	return count;
}

static int scull_p_open(struct inode *inode, struct file *filp)
{
	struct scull_pipe *dev;
	
	dev = container_of(inode->i_cdev, struct scull_pipe, cdev);
	filp->private_data = dev;
	
	if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	if(!dev->buffer){
		dev->buffer = kmalloc(scull_p_buffer, GFP_KERNEL);
		if(!dev->buffer){
			up(&dev->sem);
			return -ENOMEM;
		}
	}
	dev->buffersize = scull_p_buffer;
	dev->end = dev->buffer + dev->buffersize;
	dev->rp = dev->wp = dev->buffer;

	if(filp->f_mode & FMODE_READ)
		dev->nreaders++;
	if(filp->f_mode & FMODE_WRITE)
		dev->nwriters++;
	up(&dev->sem);
	
	return nonseekable_open(inode, filp);
}

static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
	struct scull_pipe *dev = filp->private_data;
	unsigned int mask = 0;

	down(&dev->sem);
	poll_wait(filp, &dev->inq, wait);
	poll_wait(filp, &dev->outq, wait);
	if(dev->rp != dev->wp)	
		mask |= POLLIN | POLLRDNORM;
	if(spacefree(dev))
		mask |= POLLOUT | POLLRDNORM;
	up(&dev->sem);
	return mask;
}

static int scull_p_release(struct inode *inode, struct file *filp)
{
	struct scull_pipe *dev = filp->private_data;
	scull_p_fasync(-1, filp, 0);
	down(&dev->sem);
	if(filp->f_mode & FMODE_READ)
		dev->nreaders--;
	if(filp->f_mode & FMODE_WRITE)
		dev->nwriters--;
	if(dev->nreaders + dev->nwriters == 0){
		kfree(dev->buffer);
		dev->buffer = NULL;
	}
	up(&dev->sem);
	return 0;
}

struct file_operations scull_pipe_fops = {
	.owner = THIS_MODULE,
	.open = scull_p_open,
	.release = scull_p_release,
	.read = scull_p_read,
	.write = scull_p_write,
	.poll = scull_p_poll,
	.fasync = scull_p_fasync,
};

static void scull_p_setup_cdev(struct scull_pipe *dev, int index)
{
	int err, devno = scull_p_devno + index;

	cdev_init(&dev->cdev, &scull_pipe_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cdev, devno, 1);
	if(err)
		printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}


static void scull_cleanup_module(void)
{
	int i;

	if(!scull_p_devices)
		return;

	if(scull_p_devices){
		for(i = 0; i < scull_p_nr_devs; i++){
			cdev_del(&scull_p_devices[i].cdev);
			kfree(scull_p_devices[i].buffer);
		}
		kfree(scull_p_devices);
		
	}

	unregister_chrdev_region(scull_p_devno, scull_p_nr_devs);
	scull_p_devices = NULL;
}

static int __init scull_init_module(void)
{
	int result, i;
	dev_t firstdev = 0;

	firstdev = MKDEV(scullp_major, scullp_minor);
	result = register_chrdev_region(firstdev, scull_p_nr_devs, "scullp");

	if(result < 0){
		printk(KERN_WARNING "scull:can't get major %d\n", scullp_major);
		return result;
	}
	scull_p_devno = firstdev;

	scull_p_devices = kmalloc(scull_p_nr_devs * sizeof(struct scull_pipe), GFP_KERNEL);	
	if(!scull_p_devices){
		unregister_chrdev_region(firstdev, scull_p_nr_devs);
		return 0;
	}	
	memset(scull_p_devices, 0, scull_p_nr_devs * sizeof(struct scull_pipe));

	for(i = 0; i < scull_p_nr_devs; i++){
		init_waitqueue_head(&(scull_p_devices[i].inq));
		init_waitqueue_head(&(scull_p_devices[i].outq));
		init_MUTEX(&scull_p_devices[i].sem);
		scull_p_setup_cdev(&scull_p_devices[i], i);
	}

	return 0;
}


module_init(scull_init_module);
module_exit(scull_cleanup_module);


Makefile

 

obj-m:= pipe.o
modules-objs:= pipe.o

KDIR:= /usr/src/linux-headers-2.6.31-14-generic/
PWD:= $(shell pwd)

default:
	make -C $(KDIR) M=$(PWD) modules
clean:
	rm -rf *.ko *.mod.c *.mod.o *.o *.markers *.symvers *.order

你可能感兴趣的:(【Linux设备驱动程序(第三版)】----异步通知)