/*
*
* 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");
|