Linux下可以调用request_irq,devm_request_irq,request_threaded_irq以及devm_request_threaded_irq接口来为硬件设备注册中断,这些个内核接口函数的区别大家可以自行百度。以request_threaded_irq为例,它的原型为
request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,unsigned long flags,
const char *name, void *dev);
其中第一个参数irq是中断号,这个中断号通常不是芯片datasheet里的硬件中断号,而是经过调用irq_of_parse_and_map,of_irq_get或者platform_get_irq映射到Linux系统中的中断号(Linux IRQ number),第二个参数handler是中断处理函数(ISR),该函数运行在中断上下文,第三个参数thread_fn也是一个函数,它运行于内核线程上下文,第四个参数flags可以指定中断触发的模式(边沿触发,电平触发,是否共享,ONE_SHOT等等),第五个参数就是本文讨论的中断设备的名称,这个名称是可以在内核启动后通过cat /proc/interrupts查看的,最后一个参数dev可以指向描述硬件设备的结构体,因为多个设备可能共享中断,释放中断的时候需要用到该参数。
Linux的魅力在于一切皆文件,不管是硬件设备本身,还是设备的属性都可以当成文件来操作,例如cat /proc/interrupts就可以将当前注册到系统中的硬件设备的中断信息全部打印出来:
上图中一共有7列,第1列就是Linux IRQ number,也就是上面request_threaded_irq的第一个参数,第2列是在CPU0上中断发生的次数,第3列是在CPU1上中断发生的次数,第4列是中断控制器的名称,第5列是datasheet中描述的硬件中断号,第6列是中断的触发模式(边沿触发or电平触发),第7列就是中断设备的名称,也就是request_threaded_irq的第5个参数。由于在request_threaded_irq函数原型中限制第5个参数为const char *类型,因此它只能是字符串常量,无法以字符数组指针的形式传递进来并进行动态修改,所以对于图片中多个相同类型设备使用同一份驱动的情况,要指定中断名称就有一点小麻烦,要么全部设备都指定同样的名称,要么驱动程序中根据设备数量多次调用request_threaded_irq,但是如果设备数量是可配置的,则每次增加设备就要修改驱动程序。
这个时候设备树的中断节点就派上用场啦,可以利用中断节点的interrupt-names属性或者自定义一个属性来指定中断设备名称:
mlvds_top_0: mlvds_top@43ca0000 {
clock-names = "s_axi_aclk";
clocks = <&clkc 15>;
compatible = "xxxx,mlvds-1.0.0";
reg = <0x43ca0000 0x10000>;
interrupt-parent = <&intc>;
interrupts = <0 54 4 0 55 4>;
interrupt-names = "mlvds0_timer", "mlvds0_other";
current-speed = <50>;
};
mlvds_top_1: mlvds_top@43cb0000 {
clock-names = "s_axi_aclk";
clocks = <&clkc 15>;
compatible = "xxxx,mlvds-1.0.0";
reg = <0x43cb0000 0x10000>;
interrupt-parent = <&intc>;
interrupts = <0 56 4 0 57 4>;
interrupt-names = "mlvds1_timer", "mlvds1_other";
current-speed = <50>;
};
在驱动程序中可以这样来解析以上设备树节点,获取中断相关的属性和资源:
/* Look for the interrupt parent. */
p = of_irq_find_parent(np);
if (p == NULL){
printk(KERN_ERR "mlvds %d NO interrupt parent defined!\n", dev_id);
ret = -EINVAL;
goto fail_init;
}
/* Get size of interrupt specifier */
if (of_property_read_u32(p, "#interrupt-cells", &intsize)) {
printk(KERN_ERR "mlvds %d interrupt parent property \
#interrupt-cells NOT specified!\n", dev_id);
ret = -EINVAL;
goto fail_init;
}
mlvds->irq_timer = irq_of_parse_and_map(np, 0);
if(0 == mlvds->irq_timer){
printk(KERN_ERR "mlvds %d: irq_timer NOT specified!\n", dev_id);
ret = -ENOMEM;
goto fail_init;
}
ret = of_property_read_u32_index(np, "interrupts",
intsize-1,
&mlvds->irq_timer_trigmode);
if(0 != ret){
printk(KERN_ERR "mlvds %d int flag NOT specified!\n", dev_id);
ret = -EINVAL;
goto fail_init;
}
if (of_property_read_string_index(np, "interrupt-names", 0, \
&mlvds->irq_timer_name)) {
printk(KERN_WARNING "mlvds %d: interrupt-names property \
NOT set\n", dev_id);
mlvds->irq_timer_name = "mlvds_timer";
}
其中调用of_property_read_string_index来获取设备树节点中指定的中断设备名称,由于设备树中每个节点有两个中断号,因此需要用index来指定是获取哪个中断的名称。下面也给出硬件设备的结构体描述供参考:
struct mlvds_dev {
struct cdev cdev;
int chan; /* channel index 0, 1, ... */
u32 irq_timer; /* Linux irq number */
const char * irq_timer_name; /* Linux irq name */
u32 irq_other; /* Linux irq number */
const char * irq_other_name; /* Linux irq name */
u32 irq_timer_trigmode; /* Linux irq mode */
u32 irq_other_trigmode; /* Linux irq mode */
}
以上,通过设备树获取了中断相关的属性和资源,最后,驱动程序中调用request_threaded_irq来注册中断:
if(0 != request_threaded_irq(mlvds_device[channel].irq_timer,\
mlvds_timming_isr, mlvds_timming_isr_thread, \
mlvds_device[channel].irq_timer_trigmode | IRQF_ONESHOT, \
mlvds_device[channel].irq_timer_name, &mlvds_device[channel])){
printk(KERN_ERR "mlvds_channel_init: mlvds%d request_threaded_irq \
error!\n", \channel);
return -1;
}
if(0 != request_threaded_irq(mlvds_device[channel].irq_other, \
mlvds_msg_isr, mlvds_msg_isr_thread, \
mlvds_device[channel].irq_other_trigmode | IRQF_ONESHOT, \
mlvds_device[channel].irq_other_name, &mlvds_device[channel])){
printk(KERN_ERR "mlvds_channel_init: mlvds%d request_threaded_irq \
error!\n", channel);
return -1;
}