Linux 中断实验
Linux 内核提供了完善的中断框架,我们只需要申请中断,然后注册中断处理函数即可,使用非常方便。
Linux 中断 API 函数
1、中断号
每个中断都有一个中断号,通过中断号即可区分不同的中断。
2、request_irq 函数
在 Linux 内核中要想使用某个中断是需要申请的,request_irq 函数用于申请中断,request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断,request_irq 函数原型如下:
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,这里我们介绍几个常用的中断标志。
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。
如果使用共享中断的话,request_irq 函数的 dev 参数就是唯一区分他们的标志。
IRQF_ONESHOT 单次中断,中断执行一次就结束。
IRQF_TRIGGER_NONE 无触发。
IRQF_TRIGGER_RISING 上升沿触发。
IRQF_TRIGGER_FALLING 下降沿触发。
IRQF_TRIGGER_HIGH 高电平触发。
IRQF_TRIGGER_LOW 低电平触发。
比如 I.MX6U-ALPHA 开发板的 KEY0 使用 GPIO1_IO18,按下 KEY0 后为低电平,可设置下降沿触发,也就是将 flags 设置IRQF_TRIGGER_FALLING。
其次,这些标志可以通过“|”来实现多种组合。
name:中断名字。
dev:如果 flags 设置为 IRQF_SHARED ,dev 可区分不同中断,一般将dev 设为设备结构体,dev 会传递给中断处理函数 irq_handler_t 第二个参数。
返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。
3、free_irq 函数
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。free_irq函数原型如下所示:
void free_irq(unsigned int irq,
void *dev)
irq:要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
4、中断处理函数
使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:
irqreturn_t (*irq_handler_t) (int, void *)
第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备。
5、中断使能与禁止函数
常用的中断使用和禁止函数如下所示:
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。
上半部与下半部
我们在使用request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。我们都知道中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。比如电容触摸屏通过中断通知 SOC 有触摸事件发生,SOC 响应中断,然后
通过 IIC 接口读取触摸坐标值并将其上报给系统。但是我们都知道 IIC 的速度最高也只有400Kbit/S,所以在中断中通过 IIC 读取数据就会浪费时间。我们可以将通过 IIC 读取触摸数据的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。这个时候中断处理过程就分为了两部分:
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。
这里有一些可以借鉴的参考点:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。
上半部处理很简单,直接编写中断处理函数就行,关键是下半部该怎么做呢?Linux 内核提供了多种下半部机制,接下来我们来学习一下这些下半部机制。
tasklet实现下半部
要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_init 函数原型如下:
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data);
t:要初始化的 tasklet
func:tasklet 的处理函数。
data:要传递给 func 函数的参数
在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下:
void tasklet_schedule(struct tasklet_struct *t)
t:要调度的 tasklet
关于 tasklet 的参考使用示例如下所示:
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data) {
/* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id) {
......
/* 调度 tasklet */
tasklet_schedule(&testtasklet);
......
}
/* 驱动入口函数 */
static int __init xxxx_init(void) {
......
/* 初始化 tasklet */
tasklet_init(&testtasklet, testtasklet_func, data);
/* 注册中断处理函数 */
request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
......
}
获取中断号
编写驱动的时候需要用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的中断号,函数原型如下:
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index)
dev:设备节点。
index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:
int gpio_to_irq(unsigned int gpio)
gpio:要获取的 GPIO 编号。
返回值:GPIO 对应的中断号。