Chinaunix首页 | 论坛 | 博客
  • 博客访问: 63564
  • 博文数量: 17
  • 博客积分: 1430
  • 博客等级: 上尉
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-13 12:56
文章分类
文章存档

2011年(1)

2008年(16)

我的朋友

分类: LINUX

2008-03-13 14:22:21

/*
 *
 * kf701_chardev.c
 *
 * 2006-12-06 13:22:22
 *
 * Contact me: kf701.ye AT gmail.com
 *
 * 2.6内核字符驱动示例程序,供学习。
 *
 * /dev/kf701_chardev0
 * /proc/devices
 *
 * 分配1024个字节的buffer,对/dev/kf701_chardev0的读写将
 * 作用到buffer上。目前的写操作会覆盖先前的buffer。
 *
 * 内核定时器每次向buffer里追加一个字符和一个回车。
 *
 * 测试方法:
 * $> echo "only for test" > /dev/kf701_chardev0
 * $> cat /dev/kf701_chardev0
 *
 * 2006-12-18 09:19:44 修改
 * 加入对 NONBLOCK 和 poll 的支持。
 *
 */


#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/ioctl.h>
#include <linux/poll.h>
#include <linux/wait.h>

int chardev_open(struct inode *, struct file *);
int chardev_release(struct inode *, struct file *);
ssize_t chardev_read(struct file *, char __user *, size_t, loff_t *);
ssize_t chardev_write(struct file *, const char __user *, size_t, loff_t *);
int chardev_ioctl(struct inode *,struct file *,unsigned int,unsigned long);
unsigned int chardev_poll(struct file* file, poll_table *wait);
void timer_function(unsigned long);

#define DEVICE "kf701_chardev"
#define SUCCESS 0
#define BUF_SIZE 1024
#define TIMER_INTERVAL 20 // 每20秒运行一次定时器函数


int major = 233;
int minor = 0;

// define ioctl command

#define KF701_IOC_MAGIC 'K'
#define KF701_IO_RESET _IO(KF701_IOC_MAGIC,0)
#define KF701_IO_R _IOR(KF701_IOC_MAGIC,1,int)
#define KF701_IO_W _IOW(KF701_IOC_MAGIC,2,int)
#define KF701_IO_RW _IOWR(KF701_IOC_MAGIC,3,int)
#define KF701_IOC_MAXNR 4

struct file_operations fops = {
        .owner = THIS_MODULE,
        .read = chardev_read,
        .write = chardev_write,
        .open = chardev_open,
        .release = chardev_release,
        .ioctl = chardev_ioctl,
        .poll = chardev_poll,
};

struct my_dev{
        wait_queue_head_t rq; // 读取等待队列

        uint8_t *buf;
        uint32_t size;
        uint32_t index;
        struct semaphore sem;
        struct cdev cdev;
        struct timer_list timer;
        char timer_char;
        uint32_t timer_interval;
};

struct my_dev *kf701_dev;

static int __init chardev_init(void)
{
        // 1. 分配主设备号

        dev_t devno = MKDEV( major, minor );
        int ret = register_chrdev_region( devno, 1, DEVICE );
        if( ret < 0 )
        {
          printk(KERN_DEBUG "register major number failed with %d\n", ret);
          return ret;
        }
        printk(KERN_DEBUG "%s:register major number OK\n",DEVICE);

        // 2. 注册设备

        kf701_dev = kmalloc(sizeof(struct my_dev), GFP_KERNEL);
        memset( kf701_dev, 0, sizeof(struct my_dev) );
        cdev_init( &kf701_dev->cdev, &fops );
        kf701_dev->cdev.ops = &fops;
        kf701_dev->cdev.owner = THIS_MODULE;
        ret = cdev_add( &kf701_dev->cdev, devno, 1 );
        if( ret < 0 )
        {
          printk(KERN_DEBUG "register device failed with %d\n", ret);
          return ret;
        }
        printk(KERN_DEBUG "%s:register device OK\n",DEVICE);

        // 3. 分配本驱动要使用的内存

        kf701_dev->index = 0;
        kf701_dev->size = BUF_SIZE;
        kf701_dev->buf = kmalloc( kf701_dev->size, GFP_KERNEL );
        if( NULL == kf701_dev->buf )
        {
          printk(KERN_DEBUG "kmalloc failed\n");
          return -ENOMEM;
        }
        printk(KERN_DEBUG "%s:kmalloc buffer OK\n",DEVICE);

        // 4. 初始化信号量

        init_MUTEX( &(kf701_dev->sem) );
        printk(KERN_DEBUG "%s:init semaphore OK\n",DEVICE);

        // 5. 初始化等待队列头

        init_waitqueue_head(&kf701_dev->rq);

        // 6. 初始化并启动定时器

        kf701_dev->timer_char = 'A';
        init_timer( &kf701_dev->timer );
        kf701_dev->timer_interval = HZ * TIMER_INTERVAL;
        kf701_dev->timer.function = timer_function;
        kf701_dev->timer.expires = jiffies + kf701_dev->timer_interval;
        kf701_dev->timer.data = (unsigned long)kf701_dev;
        add_timer( &kf701_dev->timer );
        printk(KERN_DEBUG "%s:init timer OK\n",DEVICE);

        return SUCCESS;
}

static void __exit chardev_exit(void)
{
        dev_t devno = MKDEV( major, minor );
        // 以相反的顺序清除

        del_timer_sync( &kf701_dev->timer );
        printk(KERN_DEBUG "%s:del timer OK\n",DEVICE);
        kfree( kf701_dev->buf );
        cdev_del( &kf701_dev->cdev );
        kfree( kf701_dev );
        printk(KERN_DEBUG "%s:kfree OK\n",DEVICE);
        unregister_chrdev_region( devno, 1 );
        printk(KERN_DEBUG "%s:unregister device OK\n",DEVICE);
}

int chardev_open(struct inode *inode, struct file *file)
{
        struct my_dev *dev = container_of(inode->i_cdev, struct my_dev, cdev);
        file->private_data = dev;

        printk(KERN_DEBUG "%s:open OK\n",DEVICE);
        return SUCCESS;
}

int chardev_release(struct inode *inode, struct file *file)
{
        printk(KERN_DEBUG "%s:release OK\n",DEVICE);
        return SUCCESS;
}

ssize_t chardev_read(struct file *file, char __user *buf, size_t count,loff_t *offset)
{
        struct my_dev *dev = file->private_data;
        ssize_t retval = 0;

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

        // 增加了对NONBLOCK的支持

        while( 0 == dev->index )
        {
                up(&dev->sem);
                if( file->f_flags & O_NONBLOCK )
                        return -EAGAIN;
                // 在这里准备睡眠,等待条件为真

                if( wait_event_interruptible(dev->rq, (0 != dev->index)) )
                        return -ERESTARTSYS; // 返回非0表示被信号中断

                if( down_interruptible(&dev->sem) )
                        return -ERESTARTSYS; // 先取得锁再检查条件

        }

        if( *offset >= dev->index )
                goto out;
        if( (*offset+count) > dev->index )
                count = dev->index - *offset;
        if( copy_to_user(buf, dev->buf + *offset, count) )
        {
                retval = -EFAULT;
                goto out;
        }
        *offset += count;
        retval = count;

out:
        up(&dev->sem);
        return retval;
}

// omit offset argument for write

ssize_t chardev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
        struct my_dev *dev = file->private_data;
        ssize_t retval = -ENOMEM;

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

        if( count > dev->size )
                count = dev->size - 1;
        if( copy_from_user(dev->buf, buf, count) )
        {
                retval = -EFAULT;
                goto out;
        }
        dev->index = count;
        retval = count;

out:
        up(&dev->sem);
        // 唤醒阻塞在 read 和 select 上的进程

        if( retval > 0 )
                wake_up_interruptible(&dev->rq);
        return retval;
}

int chardev_ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
{
        struct my_dev *dev = file->private_data;
        int retval = 0, tmp = 0;

        if( _IOC_TYPE(cmd) != KF701_IOC_MAGIC )
                return -ENOTTY;
        if( _IOC_NR(cmd) > KF701_IOC_MAXNR )
                return -ENOTTY;

        switch(cmd)
        {
                case KF701_IO_RESET:
                        printk(KERN_DEBUG "%s:IO_RESET CMD\n",DEVICE);
                        break;
                case KF701_IO_R:
                        printk(KERN_DEBUG "%s:IO READ CMD\n",DEVICE);
                        retval = put_user(dev->index, (int __user *)arg);
                        break;
                case KF701_IO_W:
                        printk(KERN_DEBUG "%s:IO WRITE CMD\n",DEVICE);
                        retval = get_user(tmp, (int __user *)arg);
                        printk(KERN_DEBUG "CMD ARG = %d\n", tmp);
                        break;
                case KF701_IO_RW:
                        printk(KERN_DEBUG "%s:IO W-R CMD\n",DEVICE);
                        break;
                default:
                        return -ENOTTY;
        }
        return retval;
}

unsigned int chardev_poll(struct file * file, poll_table * wait)
{
        struct my_dev *dev = file->private_data;
        unsigned int mask = 0;
        down(&dev->sem);
        poll_wait(file,&dev->rq,wait);
        if( 0 != dev->index )
                mask |= POLLIN | POLLRDNORM;
        up(&dev->sem);
        return mask;
}

// 此函数在进程上下文之外运行,类似中断上下文,因此:

// 1. 不能访问用户空间

// 2. current指针没有意义

// 3. 不能调用可能引起体眠和调度的代码,信号量也不行。

// 4. 最好使用 spinlock

void timer_function(unsigned long arg)
{
        struct my_dev *dev = (struct my_dev*)arg;
        if( dev->index >= (dev->size - 1) )
                return;
        if( dev->timer_char > 'Z' )
                dev->timer_char = 'A';
        if( '\n' == dev->buf[dev->index-1] )
                dev->index--;
        dev->buf[dev->index++] = (dev->timer_char)++;
        dev->buf[dev->index++] = '\n';

        // 唤醒阻塞在 read 和 select 上的进程

        wake_up_interruptible(&dev->rq);

        dev->timer.expires += dev->timer_interval;
        add_timer( &dev->timer );
}

module_init(chardev_init);
module_exit(chardev_exit);

MODULE_AUTHOR("kf701.ye at gmail.com");
MODULE_DESCRIPTION("Study for kf701");
MODULE_SUPPORTED_DEVICE(DEVICE);
MODULE_LICENSE("GPL");

阅读(1195) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~