Yuhang Zheng

第二十三节、设备树中的中断节点以及相关的中断函数

N 人看过

本节用于介绍设备树中的中断节点以及相关的中断函数

一、设备树中的中断节点。

如果一个设备需要用到中断功能,开发人员就需要在设备树中配置好中断属性信息,因为设备树是用来描述硬件信息的,然后Linux内核通过设备树配置的中断属性来配置中断功能。

设备树中断的参考绑定文档:

Documentation/devicetree/bindings/arm/gic.txt

或者

Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt

中断实际上是非常复杂的,但是作为开发人员,我们只需要关系怎么在设备树中指定中断,怎么在代码中获得中断就可以。其他的事情,比如设备树中的中断控制器,这些都是由原厂的BSP工程师帮我们写好了,我们不需要来修改他。

比如,在fsl-ls1046a.dtsi文件,其中的gic节点就是ls1046a的中断控制器节点,如下图所示:

        gic: interrupt-controller@1400000 {
                compatible = "arm,gic-400";
                #interrupt-cells = <3>;<----------------表示它的子节点使用3个cells来描述一个中断
                interrupt-controller;<------------------表示这是一个中断控制器
                reg = <0x0 0x1410000 0 0x10000>, /* GICD */
                      <0x0 0x1420000 0 0x20000>, /* GICC */
                      <0x0 0x1440000 0 0x20000>, /* GICH */
                      <0x0 0x1460000 0 0x20000>; /* GICV */
                interrupts = <GIC_PPI 9 (GIC_CPU_MASK_RAW(0xf) |
                                         IRQ_TYPE_LEVEL_LOW)>;
        };

比如,对于GPIO来说,GPIO的节点也可以作为中断控制器,在fsl-ls1046a.dtsi文件中GPlO0的节点内容如下图所示:

                gpio0: gpio@2300000 {
                        compatible = "fsl,qoriq-gpio";
                        reg = <0x0 0x2300000 0x0 0x10000>;
                        interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;<----使用了3个cells
                        gpio-controller;
                        #gpio-cells = <2>;
                        interrupt-controller;<------------表示这是一个中断控制器
                        #interrupt-cells = <2>;<----------表示它的子节点使用2个cells来描述一个中断,如果外设使用gpio0来做中断,就用2个cells
                };

其中:

interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
//值的含义:
//GIC_SPI:共享外设的中断
//66:中断号,对于gpio0组中的32个GPIO来说,它们都使用的这一个中断号
//IRQ_TYPE_LEVEL_HIGH:中断的类型

-----------------------------------------------------------------
中断一共有三种类型:
PPI(Private Peripheral Interrupt):某个CPU私有外设的中断,这类外设的中断只能发给绑定的那个CPU。
SPI(Shared Peripheral Interrupt):共享外设的中断,这类外设的中断可以路由到任何一个CPU。
SGI(Software Generated Interrupt):软件产生的中断,可以用于多核的核间通信,一个CPU可以通过写GIC的寄存器给另外一个CPU产生中断。多核调度用的IPI_WAKEUP、IPI_TIMER、IPI_RESCHEDULE、IPI_CALL_FUNC、IPI_CALL_FUNC_SINGLE、IPI_CPU_STOP、IPI_IRQ_WORK、IPI_COMPLETION都是由SGI产生的。

这些工作都是由原厂的BSP工程师来帮我们写好的,并不需要我们来写。除非将来你有机会去原厂工作,否则我们不会从头开始写一个设备树文件的。分工是非常明确的,我们需要关注的点是怎么在设备树里面描述一个外设的中断节点,我们来看一个例子。

        key {
                #address-cells = <1>;
                #size-cells = <1>;
                status = "okay";
                compatible = "key";
                /*
                pinctrl-name = "default";
                pinctrl-0 = <&pinctrl_key>;
                */
                gpios = <&gpio0 28 0>;
                interrupt-parent = <&gpio0>;
                interrupts = <28 IRQ_TYPE_EDGE_BOTH>;
        };

在这个例子中,我们先把这个引脚设置为了gpio功能(有的平台需要使用pinctrl和gpio子系统),因为我们在使用中断的时候需要把引脚设置成输入。然后使用interrupt-parent和interrupts 属性来描述中断。interrupt-parent的属性值是gpio0,也就是他的要使用gpio0这个中断控制器,为什么是gpio0呢,因为我们的引脚使用的是gpio0里面的io28,所以我们使用的是gpio0这个中断控制器。interrupts属性设置的是中断源,为什么里面是2个cells呢,因为我们在gpio0这个中断控制器里面#interrupt-cells的值为2,参照前面的代码示例中的内容。

其中

interrupts = <28 IRQ_TYPE_EDGE_BOTH>;
//值的含义:
//28表示GPI00组的28号IO
//IRQ_TYPE_EDGE_BOTH表示上升沿和下降沿同时有效

中断类型的定义

文件位置路径:include/linux/irq.h

enum {
        IRQ_TYPE_NONE           = 0x00000000,
        IRQ_TYPE_EDGE_RISING    = 0x00000001,
        IRQ_TYPE_EDGE_FALLING   = 0x00000002,
        IRQ_TYPE_EDGE_BOTH      = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
        IRQ_TYPE_LEVEL_HIGH     = 0x00000004,
        IRQ_TYPE_LEVEL_LOW      = 0x00000008,
        IRQ_TYPE_LEVEL_MASK     = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
        IRQ_TYPE_SENSE_MASK     = 0x0000000f,
        IRQ_TYPE_DEFAULT        = IRQ_TYPE_SENSE_MASK,

        IRQ_TYPE_PROBE          = 0x00000010,

        IRQ_LEVEL               = (1 <<  8),
        IRQ_PER_CPU             = (1 <<  9),
        IRQ_NOPROBE             = (1 << 10),
        IRQ_NOREQUEST           = (1 << 11),
        IRQ_NOAUTOEN            = (1 << 12),
        IRQ_NO_BALANCING        = (1 << 13),
        IRQ_MOVE_PCNTXT         = (1 << 14),
        IRQ_NESTED_THREAD       = (1 << 15),
        IRQ_NOTHREAD            = (1 << 16),
        IRQ_PER_CPU_DEVID       = (1 << 17),
        IRQ_IS_POLLED           = (1 << 18),
        IRQ_DISABLE_UNLAZY      = (1 << 19),
};

所以我们在设备树里面配置中断的时候只需要两个步骤即可,第一个步骤是把管脚设置为gpio功能。第二个步骤是使用interrupt-parent和interrupts属性来描述中断。

二、中断相关函数

<1>获取中断号相关函数

编写驱动的时候需要用到中断号,每一个中断都有中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过irq_of_parse_and_map函数从interupts属性中提取到对应的设备号,函数原型如下:

文件位置路径:include/linux/of_irq.h

实际函数位置路径:drivers/of/irq.c

extern unsigned int irq_of_parse_and_map(struct device_node *node, int index);
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
        struct of_phandle_args oirq;

        if (of_irq_parse_one(dev, index, &oirq))
                return 0;

        return irq_create_of_mapping(&oirq);
}
//参数
//dev:设备节点
//index:索引号,interrupts属性可能包含多条中断信息,通过index指定要获取的信息。
//返回值
//中断号

如果使用GPIO的话,可以使用gpio_to_irq函数来获取gpio对应的中断号,函数原型如下:

文件位置路径:include/linux/gpio.h

static inline int gpio_to_irq(unsigned int gpio)
{
        return __gpio_to_irq(gpio);
}
//参数
//gpio:要获取的GPIO编号
//返回值
//GPIO对应的中断号

<2>申请中断函数
同GPIO一样,在Linux内核里面,如果我们要使用某个中断也是需要申请的,申请中断我们使用的函数是request_irq

注意:中断标志的使用,对于某些平台可能不是所有的中断标志都是可以使用的,如LS1046平台无法使用上升沿触发,在使用之前可以通过命令先验证一下,如

echo 508 > /sys/class/gpio/export
echo "in" > /sys/class/gpio/gpio508/direction
echo "rising" > /sys/class/gpio/gpio508/edge
[  455.624730] genirq: Setting trigger mode 1 for irq 72 failed (mpc8xxx_irq_set_type+0x0/0xf8)
-bash: echo: write error: Invalid argumente
echo "both" > /sys/class/gpio/gpio508/edge
echo "falling" > /sys/class/gpio/gpio508/edge

函数原型:

文件位置路径:include/linux/interrupt.h

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
{
        return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
//参数
//irq:要申请中断的中断号
//handler:中断处理函数,当中断发生以后就会执行此中断处理函数
//flags:中断标志
//name:中断名字,设置以后可以在/proc/interrupts文件中看到对应的中断名字
//dev:如果将flags设置为IRQF_SHARED的话,dev用来区分不同的中断,一般情况下将dev设置为设备结构体,dev会传递给中断处理函数irq_handler_t的第二个参数
//返回值
//返回0,中断申请成功
//返回其他负值,中断申请失败,如果返回-EBUSY的话表示中断已经被申请了

中断标志可以在文件include/linux/interrupt.h里面查看所有的中断标志,这里我们介绍几个常用的中断标志,如下图所示:

标志 功能
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq函数的dev参数就是唯一区分他们的标志。
IRQF_ONESHOT 单次中断,中断执行一次就结束。
IRQF_TRIGGER_NONE 无触发。
IRQF_TRIGGER_RISING 上升沿触发。
IRQF_TRIGGER_FALLING 下降沿触发。
IRQF_TRIGGER_HIGH 高电平触发。
IRQF_TRIGGER_LOW 低电平触发。

<3>中断处理函数

使用request_irq函数申请中断的时候需要设置中断处理函数,也就是request_irq函数中的第二个参数,中断处理函数格式如下所示:

文件位置路径:include/linux/interrupt.h

typedef irqreturn_t (*irq_handler_t)(int, void *);
//参数
//int:要中断处理函数要相应的中断号
//void *:一个指向void的指针,也就是个通用指针,需要与request_irq函数的dev参数保持一致。用于区分共享中断的不同设备,dev也可以指向设备数据结构。
//返回值
//irqreturn_t类型

irqreturnt类型定义如下所示

文件位置路径:include/linux/irqreturn.h

enum irqreturn {
        IRQ_NONE                = (0 << 0),//不是本驱动的中断,所以不做处理
        IRQ_HANDLED             = (1 << 0),//最常用的,正常处理
        IRQ_WAKE_THREAD         = (1 << 1),//使用中断下文处理
};

typedef enum irqreturn irqreturn_t;

可以看出irqreturn_t是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED);

<4>free_irq函数

中断使用完成以后就要通过free_irq函数释放掉相应的中断。如果中断不是共享的,那么free_irq会删除中断处理函数并且禁止中断。free_irq函数原型如下所示:

文件位置路径:include/linux/irqreturn.h

实际函数位置路径:kernel/irq/manage.c

extern const void *free_irq(unsigned int, void *);
const void *free_irq(unsigned int irq, void *dev_id)
//参数
//irq:要释放的中断号
//dev_id:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

其他:

查看某个中断号发生的次数

cat /proc/irq/71/spurious