Linux Device Tree中定义了很多和中断相关的属性,这些属性之间的关系错综复杂。为剖析这些关系,特地查阅文档后输出本文。本文基于ARM平台,主要说明如下几个属性:
- interrupt-controller
- interrupt-parent
- interrupt-cells
- interrupts
- interrupt domain和interrupt specifier
- interrupt-map
其中第5点属于中断相关文章中经常会提到的概念,并不是Device Tree中定义的属性。但理解它们也非常有必要,所以在这里一并解释下。
中断控制器的硬件结构(基于Exynos4412 ARMv7)
查看Exynos4412芯片手册中断相关章节可以确认Exynos4412有两个中断控制器,一个是常见的GIC(PL390),另外一个是Interrupt Combiner。具体连接关系如下所示:
可以看到这里的PL390后面又级联了一个名叫Interrupt Combiner的中断控制器。所以这颗SOC有两个interrupt controller,分别是GIC和Interrupt Combiner。下面分别看下这两个控制器的硬件细节。
GIC
先看下GIC的硬件细节,如下图所示:
可以看到GIC的外设中断(除去SGI)类型有两类:
- SPI,共享外设中断(由GIC内部的distributor来分发到相关CPU),中断号:32~1019
- PPI,私有外设中断(指定CPU接收),中断号:16~31
外设中断号的分配规则如下:
- 32~1019给SPI
- 16~31给PPI
所有外设中断都支持四种触发方式:
- 上升沿触发
- 下降沿触发
- 高电平触发
- 低电平触发
所以总结,exynos4412的GIC中断控制器(interrupt controller)通过中断类型、中断号、中断触发方式这三个要素可以描述一个唯一指定的中断。这种能够描述出系统中唯一中断的要素组合称为interrupt specifier
所以DTS中接在GIC的device node的interrupts属性也是用这三个要素来描述一个具体的中断。
格式如:interrupts =
Interrrupt Types | Interrrupt Number | Trigger Type |
---|---|---|
0 = SPI 1 = PPI | 32 … … 1019 | 1 = low to high 2 = high to low 4 = high level 8 = low level |
Interrupt Combiner
除了GIC这个controller外Exynos4412还有一个Combiner的interrupt controller。Combiner的硬件细节如下所示:
可以看到,连接到combiner控制器上的多个中断源会共享一个中断号,且这些中断都属于SPI类型。所以描述一个combiner的中断应该要包含两个要素。
interrupts =
DTS对中断的描述
DTS中采用中断树来描述中断的连接信息以及级联情况等。但中断树和设备树的结构不一样,中断树是一个”倒树”。设备树都是从root到leaf的顺序来描述的,比如DTS都有一个root node,cpus、mem等都是它的子节点,而cpus或者mem节点下又分cpu@0、cpu@1等子节点。但中断树不一样,中断产生设备中会嵌入一个interrupt-parent属性,这个interrupt-parent会被赋值一个phandle变量,通过phandle指明这个设备中断物理上的连接关系。但并不是所有的中断产生设备节点都会显式定义interrupt-parent属性。如果一个中断产生设备没有定义interrupt-parent,那他的interrupt-parent就是设备树中的parent。所以根据前面的描述,中断树的连接关系是先找中断产生设备device node,然后找到这个node连接的interrupt-parent,所以和DTS的device node组织结构相比是倒过来的。
中断子树也可称之为中断域(interrupt domain)。同一个中断域下,设备的interrupt属性具有相同的格式和释义。中断树由多个中断域组成。
每个中断产生设备都采用一个interrupt属性来描述该设备的中断源信息,也称之为中断描述符。中断描述符的格式和含义是由interrupt domain决定。interrupt domian的根节点会有一个interrupt-cells属性来决定域内中断描述符占用
中断域是定义中断描述符的上下文(context)。一个中断域的根可以是interrupt-controller也可以是一个interrupt nexus。
1.interrupt-controller: 它是一个物理设备,需要驱动支持来处理连接到它的中断。
2.interrupt nexus(interrupt-map): 实现从一个interrupt domain转化到另一个interrupt domain。通过定义interrupt-map属性来实现。例如PCIE的中断需要映射到系统的IRQ。
3.interrupt-cells: 一个中断描述符所需
4.interrupt-parent: 为了让中断树能反映实际上的物理连接层次关系,定义了interrupt-parent属性。比如一颗SoC,有外部中断通过GPIO子系统然后连接到GIC上;也有SoC集成的UART,I2C等控制器上的中断直连到GIC上。外部中断的interrupt-parent可以设为GPIO,而UART的interrupt-parent则可设置为interrupt-controller(GIC)
DTS中断实例解析
以exynos4412-tiny4412.dts为例解析DTS中断相关属性的用法。用exynos4412 SoC集成的UART、GPIO、MCT为例分析不同中断设备的属性设置。
因为exynos4412-tiny4412.dts使用了include其它dts,所以总共涉及的dts文件如下:
- exynos4412-tiny4412.dts
- exynos4412.dtsi
- exynos4x12.dtsi
- exynos4.dtsi
- exynos4x12-pinctrl.dtsi
其中第1和2中没有中断相关的属性,所以分析只需关注3、4、5三个dts文件
GIC & COMBINER作为interrupt-parent – 集成外设
一颗设计好的SoC上已经集成很多带有中断能力外设,比如ADC/UART/I2C等等。而这些的外设的中断最终都需要连到硬件的中断控制器上。exynos4412的中断控制器有两个,分别是GIC和COMBINER,硬件如前所示。根据前面中断控制器的描述,中断控制器总共有0~1019个中断,那外设具体从哪一条通路进入中断控制器呢?
这和SoC设计有关。有些外设的中断已经被设计成连接到固定的gic或者combiner的某个中断线上,比如exynos4412的UART和I2C中断。查阅exynos4412芯片手册的GIC和COMBINER章节的中断源表能发现,UART和I2C只连到GIC中断控制器的固定中断号上。而exynos4412的ADC MCT等中断则即能作为GIC的中断源,也能作为COMBINER的中断源,说明ADC、MCT的中断是可以配置的,通过设置寄存器可以控制中断线的连接关系。在exynos4412-tiny4412开发板上,ADC中断线被配置到COMBINER中,而MCT的某些中断被配置到了COMBINER,另外一些则被配置到了GIC上。
下面以UART为例说明GIC作为interrupt-parent时,DTS中有关中断的配置。先看UART在DTS中的描述:
26 / {
27 interrupt-parent = <&gic>;
28
29 aliases {
30 spi0 = &spi_0;
31 spi1 = &spi_1;
32 spi2 = &spi_2;
33 i2c0 = &i2c_0;
34 i2c1 = &i2c_1;
35 i2c2 = &i2c_2;
36 i2c3 = &i2c_3;
37 i2c4 = &i2c_4;
38 i2c5 = &i2c_5;
39 i2c6 = &i2c_6;
40 i2c7 = &i2c_7;
41 i2c8 = &i2c_8;
42 csis0 = &csis_0;
43 csis1 = &csis_1;
44 fimc0 = &fimc_0;
45 fimc1 = &fimc_1;
46 fimc2 = &fimc_2;
47 fimc3 = &fimc_3;
48 serial0 = &serial_0;
49 serial1 = &serial_1;
50 serial2 = &serial_2;
51 serial3 = &serial_3;
52 };
.
.
.
428 serial_0: serial@13800000 {
429 compatible = "samsung,exynos4210-uart";
430 reg = <0x13800000 0x100>;
431 interrupts = <0 52 0>;
432 clocks = <&clock CLK_UART0>, <&clock CLK_SCLK_UART0>;
433 clock-names = "uart", "clk_uart_baud0";
434 dmas = <&pdma0 15>, <&pdma0 16>;
435 dma-names = "rx", "tx";
436 status = "disabled";
437 };
可知道串口0没有显式定义interrupt-parent,所以他的interrupt-parent就是device node的parent,也就是第27行定义看到的:
27 interrupt-parent = <&gic>;
gic的dts描述:
127
128 gic: interrupt-controller@10490000 {
129 compatible = "arm,cortex-a9-gic";
130 #interrupt-cells = <3>;
131 interrupt-controller;
132 reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
133 };
这个cotroller中的interrupt-cells已经确定interrupt-specifer的格式和位数,回到串口0的DTS描述查看interrupt-specifier:
431 interrupts = <0 52 0>;
那三位具体是什么含义呢?这个是由驱动定义的,查看Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt
28 The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
29 interrupts.
30
31 The 2nd cell contains the interrupt number for the interrupt type.
32 SPI interrupts are in the range [0-987]. PPI interrupts are in the
33 range [0-15].
34
35 The 3rd cell is the flags, encoded as follows:
36 bits[3:0] trigger type and level flags.
37 1 = low-to-high edge triggered
38 2 = high-to-low edge triggered (invalid for SPIs)
39 4 = active high level-sensitive
40 8 = active low level-sensitive (invalid for SPIs).
可之interrupt的格式是<type, interrupt number, trigger type>
所以到此就知道,uart0的中断是连接到gic的SPI中断,中断号是52,中断触发类型是0。这里的疑问是中断出发类型设置成0的含义?推测是在内核中通过request irq申请中断的时候再来设置中断触发类型,在DTS中并没有初始化中断触发类型。
GPIO作为interrupt-parent
前面也提到,既然有了interrupt-controller,并且所有的中断最后总是要进到interrupt-controller来处理的。那为什么还要再有一个interrrupt-parent呢?interrupt-parent主要用于描述中断实际上的连接关系。比如说SoC上已经集成的UART、I2C、SPI等,这些小IP在设计时已经被连到了interrupt-controller上了,所以他们的interrupt-parent就是interrupt-controller。那像那些外部中断呢?比如说一个按键产生的中断,因为在外部看来,这个中断并不是直接连到interrupt-cotroller上的,而是先到gpio,然后通过gpio连到interrrupt-controller。所以如果是一个按键的中断,那它的interrupt-parent是按键所连的gpio。虽然这个外部中断最后还是得连到interrupt-controller,但为了反映真实的连接关系,这里的interrupt-controller被设置为gpio而不是interrupt-controoler。tiny4412开发板没有使用gpio作为interrupt-parent的例子,但是类似的exynos4412-odroidx.dtsi中有个例子:
26 gpio_keys {
27 compatible = "gpio-keys";
28 pinctrl-names = "default";
29 pinctrl-0 = <&gpio_power_key>;
30
31 power_key {
32 interrupt-parent = <&gpx1>;
33 interrupts = <3 0>;
34 gpios = <&gpx1 3 GPIO_ACTIVE_LOW>;
35 linux,code = <KEY_POWER>;
36 label = "power key";
37 debounce-interval = <10>;
38 gpio-key,wakeup;
39 };
40 };
手机上都会有一个power键,当手机休眠时需要按power键来唤醒。所以这个power键应该连到wakeup interrupt上。可以看到这个odroidx的power键连到的是gpx1上,gpx引脚有wakeup功能。
587 gpx1: gpx1 {
588 gpio-controller;
589 #gpio-cells = <2>;
590
591 interrupt-controller;
592 interrupt-parent = <&gic>;
593 interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
594 <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
595 #interrupt-cells = <2>;
596 };
gpx1支持SPI类型中断,中断号为24~31。power key的中断描述符:
33 interrupts = <3 0>;
所以odroidx引用的是gpx1中的第3个中断,即SPI的26号中断。所以这里也可以看到,interrupt-parent属性是用来描述中断实际的连接关系。比如这里的外部唤醒中断源,首先连到gpx上,然后连到gic上,这就是实际的连接关系。
interrupt-map
除了描述中断的连接关系,在DTS中可能还需要描述中断映射的关系。从一个中断domain映射到另一个中断domain。比如exynos4412的mct设备。
如上exynos4412的芯片手册所示,MCT支持多个中断,并且这些中断最终都要进到中断控制器中。但一个中断控制器需要支持几十或者上百个中断源,为了方便使用或者记忆,如果代码中能直接使用MCT0来表示global0,MCT1表示global1那就完美了。所以这就需要把MCT的中断号和中断控制器通过interrupt-map做一个映射。mct的DTS实现如下:
79 mct@10050000 {
80 compatible = "samsung,exynos4412-mct";
81 reg = <0x10050000 0x800>;
82 interrupt-parent = <&mct_map>;
83 interrupts = <0>, <1>, <2>, <3>, <4>;
84 clocks = <&clock CLK_FIN_PLL>, <&clock CLK_MCT>;
85 clock-names = "fin_pll", "mct";
86
87 mct_map: mct-map {
88 #interrupt-cells = <1>;
89 #address-cells = <0>;
90 #size-cells = <0>;
91 interrupt-map = <0 &gic 0 57 0>,
92 <1 &combiner 12 5>,
93 <2 &combiner 12 6>,
94 <3 &combiner 12 7>,
95 <4 &gic 1 12 0>;
96 };
97 };
mct的中断源没有写死,通过寄存器可以配置。所以可以看到:
91 interrupt-map = <0 &gic 0 57 0>,
92 <1 &combiner 12 5>,
93 <2 &combiner 12 6>,
94 <3 &combiner 12 7>,
95 <4 &gic 1 12 0>;
这表示mct 0 被设置为连接到gic的SPI中断,中断号是57,mct1连到combiner中断控制寄存器的group12 ,bit是5,剩余几个中断依次map。
总结
DTS中断相关属性需要理解interrupt-controller和interrupt-parent的区别和关系。