[转载]Linux DTS中和中断相关属性的解释和用法

adtxl
2023-03-23 / 0 评论 / 530 阅读 / 正在检测是否收录...

版权声明:本文为CSDN博主「阿曼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/rockrockwu/article/details/96461563

1. 前言

Linux Device Tree中定义了很多和中断相关的属性,这些属性之间的关系错综复杂。为剖析这些关系,特地查阅文档后输出本文。本文基于ARM平台,主要说明如下几个属性:

  1. interrupt-controller
  2. interrupt-parent
  3. interrupt-cells
  4. interrupts
  5. interrupt domain和interrupt specifier
  6. interrupt-map

其中第5点属于中断相关文章中经常会提到的概念,并不是Device Tree中定义的属性。但理解它们也非常有必要,所以在这里一并解释下。

2. 中断控制器的硬件结构(基于Exynos4412 ARMv7)

查看Exynos4412芯片手册中断相关章节可以确认Exynos4412有两个中断控制器,一个是常见的GIC(PL390),另外一个是Interrupt Combiner。具体连接关系如下所示:

image367a315fbc9899f6.png

可以看到这里的PL390后面又级联了一个名叫Interrupt Combiner的中断控制器。所以这颗SOC有两个interrupt controller,分别是GIC和Interrupt Combiner。下面分别看下这两个控制器的硬件细节。

2.1 GIC

先看下GIC的硬件细节,如下图所示:

image4b46b1036b67f1fc.png

可以看到GIC的外设中断(除去SGI)类型有两类:

  1. SPI,共享外设中断(由GIC内部的distributor来分发到相关CPU),中断号:32~1019
  2. PPI,私有外设中断(指定CPU接收),中断号:16~31

外设中断号的分配规则如下:

  1. 32~1019给SPI
  2. 16~31给PPI

所有外设中断都支持四种触发方式:

  1. 上升沿触发
  2. 下降沿触发
  3. 高电平触发
  4. 低电平触发

所以总结,exynos4412的GIC中断控制器(interrupt controller)通过中断类型、中断号、中断触发方式这三个要素可以描述一个唯一指定的中断。这种能够描述出系统中唯一中断的要素组合称为interrupt specifier

所以DTS中接在GIC的device node的interrupts属性也是用这三个要素来描述一个具体的中断。
格式如:interrupts = <interrupt type interrupt number trigger type>

image176db202dd8b17c0.png

2.2 Interrupt Combiner

除了GIC这个controller外Exynos4412还有一个Combiner的interrupt controller。Combiner的硬件细节如下所示:

image4415c5576b6c16fa.png

可以看到,连接到combiner控制器上的多个中断源会共享一个中断号,且这些中断都属于SPI类型。所以描述一个combiner的中断应该要包含两个要素group number和interrupt number within group。

interrupts = <group number interrupt number within group>

3. 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属性来决定域内中断描述符占用<u32>的数目。比如像Open PIC中断控制器的中断描述符就需要中断号和中断触发类型两个<32>来描述

中断域是定义中断描述符的上下文(context)。一个中断域的根可以是interrupt-controller也可以是一个interrupt nexus。

1.interrupt-controller: 它是一个物理设备,需要驱动支持来处理连接到它的中断。
2.interrupt nexus(interrupt-map): 实现从一个interrupt domain转化到另一个interrupt domain。通过定义interrupt-map属性来实现。例如PCIE的中断需要映射到系统的IRQ。
3.interrupt-cells: 一个中断描述符所需<u32>的数目
4.interrupt-parent: 为了让中断树能反映实际上的物理连接层次关系,定义了interrupt-parent属性。比如一颗SoC,有外部中断通过GPIO子系统然后连接到GIC上;也有SoC集成的UART,I2C等控制器上的中断直连到GIC上。外部中断的interrupt-parent可以设为GPIO,而UART的interrupt-parent则可设置为interrupt-controller(GIC)

4. DTS中断实例解析

以exynos4412-tiny4412.dts为例解析DTS中断相关属性的用法。用exynos4412 SoC集成的UART、GPIO、MCT为例分析不同中断设备的属性设置。

因为exynos4412-tiny4412.dts使用了include其它dts,所以总共涉及的dts文件如下:

  1. exynos4412-tiny4412.dts
  2. exynos4412.dtsi
  3. exynos4x12.dtsi
  4. exynos4.dtsi
  5. exynos4x12-pinctrl.dtsi

其中第1和2中没有中断相关的属性,所以分析只需关注3、4、5三个dts文件

4.1 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中并没有初始化中断触发类型。

4.2 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上,这就是实际的连接关系。

4.3 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。

0

评论 (0)

取消