1 中断编程
2 驱动中断处理模板
中断的基本知识在第一节已经有文档记录,这里我们给出中断处理在驱动中的使用模板
2.1 tasklet与底半部中断
/* 定义tasklet和中断底半部函数并将它们关联 */
void xxx_do_tasklet(unsigned long);
DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);
/* 中断处理底半部函数 */
void xxx_do_tasklet(unsigned long)
{
// ...
}
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
// ...
tasklet_schedule(&xxx_tasklet);//调度底半部处理
// ...
}
/* 设备驱动模块加载函数 */
int __init xxx_init(void)
{
// ...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL);
// ...
return IRQ_HANDLED;
}
/* 设备驱动模块卸载函数 */
void __exit xxx_exit(void)
{
// ...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
// ...
}
2.2 工作队列与底半部中断
/* 定义工作队列和关联函数 */
struct work_struct xxx_wq;
void xxx_do_work(struct work_struct *work);
/* 中断处理底半部 */
void xxx_do_work(struct work_struct *work)
{
//...
}
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
//...
schedule_work(&xxx_wq); //调度底半部处理
//...
return IRQ_HANDLED;
}
/* 设备驱动模块加载函数 */
int xxx_init(void)
{
//...
/* 申请中断 */
result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL);
//...
/* 初始化工作队列 */
INIT_WORK(&xxx_wq, xxx_do_work);
//...
}
/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{
//...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
//...
}
2.3 多设备共享中断
/* 中断处理顶半部 */
irqreturn_t xxx_interrupt(int irq, void *dev_id)
{
//...
int status = read_int_status(); /* 获知中断源 */
if (!is_myint(dev_id, status)) /* 判断是否为本设备中断 */
return IRQ_NONE; /* 不是本设备中断, 立即返回 */
/* 是本设备中断, 进行处理 */
//...
return IRQ_HANDLED; /* 返回 IRQ_HANDLED 表明中断已被处理 */
}
/* 设备驱动模块加载函数 */
int xxx_init(void)
{
//...
/* 申请共享中断 */
result = request_irq(sh_irq, xxx_interrupt,
IRQF_SHARED, "xxx", xxx_dev);
//...
}
/* 设备驱动模块卸载函数 */
void xxx_exit(void)
{
//...
/* 释放中断 */
free_irq(xxx_irq, xxx_interrupt);
//...
}
3 内核定时器编程
4 驱动中定时器处理模板
/* xxx 设备结构体 */
struct xxx_dev
{
struct cdev cdev;
//...
timer_list xxx_timer; /* 设备要使用的定时器 */
};
/* xxx 驱动中的某函数 */
xxx_func1( … )
{
struct xxx_dev *dev = filp->private_data;
//...
/* 初始化定时器 */
init_timer(&dev->xxx_timer);
dev->xxx_timer.function = &xxx_do_timer;
dev->xxx_timer.data = (unsigned long)dev;
/* 设备结构体指针作为定时器处理函数参数 */
dev->xxx_timer.expires = jiffies + delay;
/* 添加(注册) 定时器 */
add_timer(&dev->xxx_timer);
//...
}
/* xxx 驱动中的某函数 */
xxx_func2( … )
{
//...
/* 删除定时器 */
del_timer(&dev->xxx_timer);
//...
}
/* 定时器处理函数 */
static void xxx_do_timer(unsigned long arg)
{
struct xxx_device *dev = (struct xxx_device *)(arg);
//...
/* 调度定时器再执行 */
dev->xxx_timer.expires = jiffies + delay;
add_timer(&dev->xxx_timer);
//...
}
5 驱动定时器处理示例
//秒字符设备
//在打开时初始化定时器并添加到内核的定时器链表中,每秒输出一次当前jiffies
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define SECOND_MAJOR 232
static int second_major = SECOND_MAJOR;
module_param(second_major, int, S_IRUGO);
struct second_dev
{
struct cdev cdev;
atomic_t counter; //原子类型计数
struct timer_list s_timer; //内核定时器
};
static struct second_dev *second_devp;
static void second_timer_handler(struct timer_list* arg)
{
mod_timer(&(second_devp->s_timer), jiffies + HZ); //触发下一次定时
atomic_inc(&(second_devp->counter));
printk(KERN_INFO "current jiffies is %ld\n", jiffies);
}
static int second_open(struct inode *inode, struct file *filp)
{
//打开设备时,初始化定时器,设置处理函数
second_devp->s_timer.function = &second_timer_handler;
second_devp->s_timer.expires = jiffies + HZ;
add_timer(&(second_devp->s_timer)); //添加timer到内核
atomic_set(&(second_devp->counter), 0);
return 0;
}
static int second_release(struct inode *inode, struct file *filp)
{
del_timer(&(second_devp->s_timer)); //释放设备时,从内核删除定时器
return 0;
}
static ssize_t second_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
{
//读操作
int counter = atomic_read(&(second_devp->counter));
if (put_user(counter, (int *)buf)) //复制counter到用户空间
return -EFAULT;
else
return sizeof(unsigned int);
}
static const struct file_operations second_fops =
{
.owner = THIS_MODULE,
.open = second_open,
.release = second_release,
.read = second_read
};
//驱动模块加载函数
static void second_setup_cdev(struct second_dev* dev, int index)
{
int err, devno = MKDEV(second_major, index); //获得dev_t对象
cdev_init(&(dev->cdev), &second_fops);//初始化设备
dev->cdev.owner = THIS_MODULE;
//注册设备
err = cdev_add(&dev->cdev, devno, 1);
if (err)
{
printk(KERN_NOTICE"Error %d adding second %d", err, index);
}
}
static int __init second_init(void)
{
int ret;
dev_t devno = MKDEV(second_major, 0);
//申请设备号
if (second_major)
{
ret = register_chrdev_region(devno, 1, "second");
}
else
{
ret = alloc_chrdev_region(&devno, 0, 1, "second");
second_major = MAJOR(devno);
}
if (ret < 0)
return ret;
second_devp = kzalloc(sizeof(struct second_dev), GFP_KERNEL);
if (!second_devp)
{
//空间申请失败
ret = -ENOMEM;
goto fail_malloc;
}
second_setup_cdev(second_devp, 0);
//初始化互斥量
return 0;
fail_malloc:
unregister_chrdev_region(devno, 1);
return ret;
}
module_init(second_init);
//驱动模块卸载函数
static void __exit second_exit(void)
{
cdev_del(&second_devp->cdev);//注销设备
kfree(second_devp);
unregister_chrdev_region(MKDEV(second_major, 0), 1);//释放设备号
}
module_exit(second_exit);
//模块声明
MODULE_AUTHOR("BARRET REN <barret.ren@outlook.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A driver for virtual second charactor device");
MODULE_ALIAS("second device driver");
测试程序很简单,只有触发驱动的read就可以:
//second cdev的用户空间测试程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
int counter = 0;
int old_counter = 0;
/* 打开 /dev/second 设备文件 */
fd = open("/dev/second", O_RDONLY);
if (fd != -1)
{
while (1)
{
read(fd, &counter, sizeof(unsigned int)); /* 读目前经历的秒数 */
if (counter != old_counter)
{
printf("seconds after open /dev/second :%d\n", counter);
old_counter = counter;
}
}
}
else
{
printf("Device open failure\n");
}
}
之后创建设备号为232的/dev/second设备节点,运行测试程序就会看待控制台打印,同时模块在dmesg也会打印jiffies的值。