343 lines
9.1 KiB
C
343 lines
9.1 KiB
C
|
#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");
|
||
|
|