Linux下注册中断并指定中断名称

Linux下调用request_irq注册中断时给多个同类型设备指定不同的中断名称

  • Linux下注册中断并指定中断名称
    • cat /proc/interrupts
    • 使用设备树描述中断节点
    • 驱动程序解析设备树节点
    • 驱动程序注册中断
    • 安装驱动模块后的效果

Linux下注册中断并指定中断名称

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可以指向描述硬件设备的结构体,因为多个设备可能共享中断,释放中断的时候需要用到该参数。

cat /proc/interrupts

Linux的魅力在于一切皆文件,不管是硬件设备本身,还是设备的属性都可以当成文件来操作,例如cat /proc/interrupts就可以将当前注册到系统中的硬件设备的中断信息全部打印出来:
Linux下注册中断并指定中断名称_第1张图片
上图中一共有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;
    }

安装驱动模块后的效果

OK,大功告成,即使后面再增加多个设备,只需要修改设备树即可,无须修改驱动程序,运行效果:
Linux下注册中断并指定中断名称_第2张图片

你可能感兴趣的:(嵌入式Linux驱动开发)