一、设备树的一些概念
linux内核从3.10开始支持设备树
1.1、设备树的文件类型
DTS:设备树描述文件为.dts
格式。
DTSI:为了减少冗余,设备树头文件格式为.dtsi
文件,可以被不同的.dts
文件引用。
编写.dtc
文件使用设备树语法,则需要一个特定的编译器来编译,称为dtc
工具,源码在 Linux 内核的scripts/dtc
目录下。
DTB:设备树源码.dts
、.dtsi
文件最终经过 dtc 编译器,会生成.dtb
文件。
1.2、如何编译:
1、直接编译内核:make
2、编译所有设备树文件:make dtbs
3、编译指定设备树文件:make <xxx.dtb>
4、用dtc编译:dtc编译命令格式如下
dtc [-I input-format] [-O output-format][-o output-filename] [-V output_version] input_filename
参数说明
input-format:
- “dtb”: “blob” format
- “dts”: “source” format.
- “fs” format.
output-format:
- “dtb”: “blob” format
- “dts”: “source” format
- “asm”: assembly language file
output_version:
定义”blob”的版本,在dtb文件的字段中有表示,支持1 2 3和16,默认是3,在16版本上有许多特性改变
(1) Dts编译生成dtb
./dtc -I dts -O dtb -o B_dtb.dtb A_dts.dts
把A_dts.dts编译生成B_dtb.dtb
(2) Dtb编译生成dts
./dtc -I dtb -O dts -o A_dts.dts A_dtb.dtb
把A_dtb.dtb反编译生成为A_dts.dts
二、设备树语法
- 所有设备树都应有一个根节点,并且以下节点应出现在所有设备树的根部:
- 一个
/cpus
节点 - 至少一个
/memory
节点
2.1、设备树版本
/dts-v1/
2.2、设备树节点
设备树由一个个节点组成,其格式为:
[label:]node-name[@unit-address]{
[property definitions]
[chiled nodes]
}
label是标号,可以省略,label的作用是为了方便的引用node:
/{
uart0:uart@fe001000{
compatible = "ns16550"
reg = <0xfe001000 01000>
};
};
// 日后想要修改上边定义的串口,可以采用label直接引用
&uart0{
status = "disabled";
}
结构和约定如下:
2.2.1、节点名称
node-name@unit-address
node-name
指明了节点的名称,长度应该为 1-31 个字符,命名应该以小写或者大写字母开头,支持的字符如下:
Charactrer | Description |
---|---|
0-9 | degit |
a-z | lowercase letter |
A-Z | uppercase letter |
, . _ + - | other character |
节点名称的unit-address
表示设备地址或者寄存器首地址(具体节点具体分析,不一定是绝对地址),必须与节点 reg 属性中指定的首地址匹配。
举例:
uart1: serial@02020000
{
compatible :
"fsl,imx6ul-uart",
"fsl,imx6q-uart",
“fsl,imx21-uart";
REG : <9X02020000 0x4080>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL _HIGH>;
clocks = <&clks IMX6UL_CLK_UART1 IPG>,
<&ciks IMX6UL_CLK_UART1_SERIAL>;
clock-names = "ipg","per";
status = "disabled";
}
该节点 label 为 uart1,节点名称为 serial,设备地址(寄存器首地址)为 02020000,正是 imx6ull uart1 外设寄存器的首地址(查看data sheet)。
节点标签用:
隔开,为了方便访问节点,可用直接通过&node-lable
来访问节点
常用的节点:
2.2.2、根节点
dts文件中必须有根节点。根节点中必须有的属性:
#address-cells //在它的子节点的reg属性中,用多少个32位整数来描述地址。
#size-cells // 在它的子节点的reg属性中,用多少个u32来描述大小。
#compatible // 定义一系列的字符串,用来表示这个板子兼容哪些平台。
#model // 当前的这个板子是什么
2.2.3、CPU节点
一般不需要设置,在dtsi文件中定义好了。
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
.......
}
};
2.2.4、memory节点
芯片厂家不能事先确定你的板子使用多大的内存,因此memory节点需要板厂设置,比如:
memory{
reg = <0X80000000,0X20000000>;
};
2.2.5、chosen节点
不代表系统中的真实设备,而是描述系统固件在运行时选择或指定的参数。它应该是根节点的子节点。
通过设备树文件给内核传入一些参数,需要在chosen节点中设置bootargs属性:
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
2.3、节点常用属性
设备树中的每个节点都有描述节点特征的属性。属性由名称和值组成。
属性名称的可用字符如下表:
Charactrer | Description |
---|---|
0-9 | degit |
a-z | lowercase letter |
A-Z | uppercase letter |
, . _ + ? # | other character |
节点的合法值类型:
1、empty
值为空。当属性本身的存在或不存在具有足够的描述性时,用于传达真假信息。
2、u32大端格式的 32
位整数
示例:32
位值 0x11223344
在内存中表示为:
address 11
address+1 22
address+2 33
address+3 44
3、u64大端格式的 64
位整数
示例:64
位值 0x1122334455667788
将
表示为两个单元格:<0x11223344 0x55667788>
。
为什么两个cell?不同的平台,不同的总线,地址位长度可能不同,有32位地址,有64位地址,为了适应这个,规范规定一个32位的长度为一个cell。
该值将在内存中表示为:
address 11
address+1 22
address+2 33
address+3 44
address+4 55
address+5 66
address+6 77
address+7 88
4、string
字符串是可打印的并且以空值结尾。
示例:字符串 “hello” 将在内存中表示为:
address 68 'h'
address+1 65 'e'
address+2 6C 'l'
address+3 6C 'l'
address+4 6F 'o'
address+5 00 '\0'
5、prop-encoded-array
6、phandle
一个< u32 >值。phandle
值是一种引用设备树中另一个节点的方法。任何可以被引用的节点都定义了一个具有唯一 < u32 >
值的 phandle
属性。该数字用于具有 phandle
值类型的属性值。
7、stringlist
连接在一起的< string >值列表。
示例:字符串列表 “hello”、“world” 将在内存中表示为:
address 68 'h'
address+1 65 'e'
address+2 6C 'l'
address+3 6C 'l'
address+4 6F 'o'
address+5 00 '\0'
address+6 77 'w'
address+7 6f 'o'
address+8 72 'r'
address+9 6C 'l'
address+10 64 'd'
address+11 00 '\0'
2.3.1、compatible
值类型:< stringlist >
表示兼容属性,用来寻找驱动程序。
led{
compatible = "A","B","C";
}; // 表示这个LED或者硬件兼容驱动A、B、C
2.3.2、model
值类型:< string >
model属性和compatible属性类似,compatible属性表示这个硬件可以兼容一些驱动。
model用来准确的定义这个硬件是什么。
/{
compatible = "sasung,smdk2440","samsung,mini2440";
model = "jz2440_v3";
};
表示这个单板,可以兼容内核中smdk2440,也兼容mini2440,但是他是jz2440_v3。它可以用内核中的smdk2440,也可以用内核中mini2440来进行初始化。
推荐的格式是:“manufacturer,model”
,其中 manufacturer
是描述制造商名称的字符串(例如股票代码),model
指定型号。
model = "fsl,MPC8349EMITX";
2.3.3、phandle
值类型:< u32 >
phandle
(pointer handle 指针句柄(指向对象)属性指定一个在设备树中唯一的节点的数字标识符。 phandle
属性值由需要引用与该属性关联的节点的其他节点使用。
例如:
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
phandle
值定义为 1。另一个设备节点可以使用 phandle
值为 1
来引用 pic
节点:
another-device-node {
interrupt-parent = <1>;
};
注:DTS
中的大多数设备树将不包含显式的 phandle
属性。 DTC
工具在将 DTS
编译为二进制 DTB
格式时会自动插入 phandle
属性。
2.3.4、status
值类型:< string >
表示设备的运行状态,可用值如下表:
Value | Description |
---|---|
”okay“ | 表示设备可运行 |
”disable“ | 表示设备当前不可运行,但将来可能会运行(例如,有些东西没有插上电源或关闭电源)。禁用对给定设备意味着什么,请参阅设备绑定。 |
”fail“ | 表示设备无法运行。在设备中检测到严重错误,如果不进行修复,设备不太可能运行。 |
”fail-sss“ | 表示设备无法运行。在设备中检测到严重错误,如果不进行修复,设备不太可能运行。该值的sss部分特定于设备,并指示检测到的错误条件 |
“reserved” | 表示设备可运行,但不应使用。通常用于由其他软件组件(如平台固件)控制的设备。 |
如果单板A中没有用到公板中的串口0,那么就可以在单板A的dts文件中写入以下代码:
&uart0{
status = "disabled";
};
2.3.5、#address-cells、#size-cells
值类型:< u32 >
#addrdss-cells :用多少个32位的数据来表示地址
#size-cells:用多少个32位的数据来表示大小。
这两个属性影响到的是节点里边的reg属性。可以用在任何拥有子节点的设备中,并描述子设备节点应该如何寻址。
#addrdss-cells定义子节点 reg 属性中地址字段所占用的字长,即占用 u32 单元格的数量。
#size-cells属性定义子节点 reg 属性值的长度所占用的 u32 单元格的数量。
如果没有设置的话,内核默认"#address-cells"为2,"#size-cells"为1。
2.3.6、reg
值类型:< prop-encoded-array >任意数量的(地址,长度)二元组(对)。
用来描述设备地址空间资源信息,一般是某个外设的寄存器地址范围信息
包括起始地址和地址长度。
reg = <address1 length1 address2 length2 address3 length3……>
其中 address 是起始地址,length 是地址长度。#address-cells
表明 address 这个数据所占用的字长,#size-cells
表示 length 这个数据所占用的字长。
例子:
假设片上系统中的设备有两个寄存器块,SOC(父节点/父总线) 中偏移 0x3000 处的 32 字节块和偏移 0xFE00 处的 256 字节块。
reg 属性将编码如下(假设 #address-cells 和 #size-cells 值为 1):
reg = <0x3000 0x20 0xFE00 0x100>;
2.3.7、ranges !!!!!
值类型:< empty >
或 < prop-encoded-array >
任意数量的(子总线地址、父总线地址、长度)三元组。
Ranges
属性提供了一种定义总线地址空间(子地址空间)和总线节点的父节点地址空间(父地址空间)之间的映射或转换的方法。
range
属性值的格式是任意数量的 (child-bus-address, parentbus-address, length
)
-
子总线地址是子总线地址空间内的物理地址。表示地址的单元格数量取决于总线,可以从该节点(出现
range
属性的节点)的#address-cells
确定。 -
父总线地址是父总线地址空间内的物理地址。表示父地址的单元数取决于总线,可以从定义父地址空间的节点的
#address-cells
属性确定。 -
长度指定子地址空间中范围的大小。表示大小的单元格数量可以从该节点(出现
ranges
属性的节点)的#size-cells
确定。如果属性定义为 < empty> 值,则它指定父子地址空间相同,不需要地址转换。
如果该属性不存在于总线节点中,则假定节点的子节点与父地址空间之间不存在映射。
例子:
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges = <0x0 0xe0000000 0x00100000>;
serial@4600 {
device_type = "serial";
compatible = "ns16550";
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};
soc
节点指定了一个 range
属性<0x0 0xe0000000 0x00100000>;
此属性值指定对于 1024KB
范围的地址空间,在物理 0x0
处寻址的子节点映射到物理 0xe0000000
的父地址。
通过此映射,串行设备节点可以通过地址 0xe0004600
处的加载或存储、0x4600
(在 reg
中指定)的偏移量加上范围中指定的 0xe0000000
映射来寻址。
2.3.8、dma-ranges
值类型:< empty >
或 < prop-encoded-array >
任意数量的(子总线地址、父总线地址、长度)三元组。
dma-ranges
属性用于描述内存映射总线的直接内存访问 ( DMA
) 结构,可以从源自总线的 DMA
操作访问其设备树父级。它提供了一种定义总线物理地址空间和总线父级物理地址空间之间的映射或转换的方法。
dma-ranges
属性值的格式是(子总线地址、父总线地址、长度)的任意数量的三元组。指定的每个三元组描述了一个连续的 DMA
地址范围。
-
子总线地址是子总线地址空间内的物理地址。表示地址的单元格数量取决于总线,可以从该节点(出现
dma-ranges
属性的节点)的#address-cells
确定。 -
父总线地址是父总线地址空间内的物理地址。表示父地址的单元数取决于总线,可以从定义父地址空间的节点的
#address-cells
属性确定。 -
长度指定子地址空间中范围的大小。表示大小的单元格数量可以从该节点(出现
dma-ranges
属性的节点)的#size-cells
中确定。
2.4、节点中断相关属性:
在设备树中存在一个逻辑中断树,它表示平台硬件中中断的层次结构和路由。虽然通常被称为中断树,但它在技术上更像是一个有向无环图。
中断产生设备中会嵌入一个interrupt-parent属性,这个interrupt-parent会被赋值一个phandle变量,通过phandle指明这个设备中断物理上的连接关系。但并不是所有的中断产生设备节点都会显式定义interrupt-parent属性。如果一个中断产生设备没有定义interrupt-parent,那他的interrupt-parent就是设备树中的parent。所以根据前面的描述,中断树的连接关系是先找中断产生设备device node,然后找到这个node连接的interrupt-parent,所以和DTS的device node组织结构相比是倒过来的。
中断子树也可称之为中断域(interrupt domain)。同一个中断域下,设备的interrupt属性具有相同的格式和释义。中断树由多个中断域组成。
中断域是定义中断描述符的上下文(context)。
一个中断域的根可以是interrupt-controller也可以是一个interrupt nexus。
2.4.1、interrupts_parent
值类型:< phandle >
表示中断源到中断控制器的连接;如果中断生成设备没有中断父级属性,则假定其中断父级为其设备树父级。
见总例子。
2.4.2、interrupt-controller
值类型:< empty >
用来说明该节点描述的是一个gpio控制器
见总例子。
2.4.3、#interrupt-cells
值类型:< u32 >
#interrupt-cells
属性定义为中断域(子节点)编码中断说明符所需的单元数。
它出现,说明该节点下定义了一个interrupt domain。
用来描述子节点中**"interrupts"属性使用了父节点中的interrupts**属性的具体的哪个。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>
2.4.4、 interrputs
值类型:< prop-encoded-array >
中断说明符描述了该设备的一个或多个 中断源(中断源列表)。格式和含义是特定于中断域的,即它依赖于其中断域根节点上的属性(#interrupt-cells)。
例子:
定义了单个中断说明符,中断编号为 0xA
,级别/检测编码为 8
。
interrupts = <0xA 8>;
这种能够描述出系统中唯一中断的要素组合称为interrupt specifier
例1:GIC作为interrupt-parent
/ {
interrupt-parent = <&gic>;
aliases {
spi0 = &spi_0;
spi1 = &spi_1;
spi2 = &spi_2;
i2c0 = &i2c_0;
i2c1 = &i2c_1;
i2c2 = &i2c_2;
i2c3 = &i2c_3;
i2c4 = &i2c_4;
i2c5 = &i2c_5;
i2c6 = &i2c_6;
i2c7 = &i2c_7;
i2c8 = &i2c_8;
csis0 = &csis_0;
csis1 = &csis_1;
fimc0 = &fimc_0;
fimc1 = &fimc_1;
fimc2 = &fimc_2;
fimc3 = &fimc_3;
serial0 = &serial_0;
serial1 = &serial_1;
serial2 = &serial_2;
serial3 = &serial_3;
};
.
.
.
serial_0: serial@13800000 {
compatible = "samsung,exynos4210-uart";
reg = <0x13800000 0x100>;
interrupts = <0 52 0>;
clocks = <&clock CLK_UART0>, <&clock CLK_SCLK_UART0>;
clock-names = "uart", "clk_uart_baud0";
dmas = <&pdma0 15>, <&pdma0 16>;
dma-names = "rx", "tx";
status = "disabled";
};
串口0没有显式指定interrupt-parrent,所以他的interrupt-parent就是device node的parent,也就是:
interrupt-parent = <&gic>;
gic的dts描述:
gic: interrupt-controller@10490000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
};
这个cotroller中的interrupt-cells已经确定interrupt-specifer的格式和位数,回到串口0的DTS描述查看interrupt-specifier:
interrupts = <0 52 0>;
那三位具体是什么含义呢?这个是由驱动定义的,查看Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt
The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI interrupts.
The 2nd cell contains the interrupt number for the interrupt type.SPI interrupts are in the range [0-987]. PPI interrupts are in the range [0-15].
The 3rd cell is the flags, encoded as follows:
bits[3:0] trigger type and level flags.
1 = low-to-high edge triggered
2 = high-to-low edge triggered (invalid for SPIs)
4 = active high level-sensitive
8 = active low level-sensitive (invalid for SPIs).
所以到此就知道,uart0的中断是连接到gic的SPI中断,中断号是52,中断触发类型是0。这里的疑问是中断出发类型设置成0的含义?推测是在内核中通过request irq申请中断的时候再来设置中断触发类型,在DTS中并没有初始化中断触发类型。
例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-controller。tiny4412开发板没有使用gpio作为interrupt-parent的例子,但是类似的exynos4412-odroidx.dtsi中有个例子:
gpio_keys {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&gpio_power_key>;
power_key {
interrupt-parent = <&gpx1>;
interrupts = <3 0>;
gpios = <&gpx1 3 GPIO_ACTIVE_LOW>;
linux,code = <KEY_POWER>;
label = "power key";
debounce-interval = <10>;
gpio-key,wakeup;
};
};
手机上都会有一个power键,当手机休眠时需要按power键来唤醒。所以这个power键应该连到wakeup interrupt上。可以看到这个odroidx的power键连到的是gpx1上,gpx引脚有wakeup功能。
gpx1: gpx1 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
interrupt-parent = <&gic>;
interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
#interrupt-cells = <2>;
};
gpx1支持SPI类型中断,中断号为24~31。power key的中断描述符:
interrupts = <3 0>;
所以引用的是gpx1中的第3个中断,即SPI的26号中断。所以这里也可以看到,interrupt-parent属性是用来描述中断实际的连接关系。比如这里的外部唤醒中断源,首先连到gpx上,然后连到gic上,这就是实际的连接关系。
2.4.5、interrupts-extended
值类型:< phandle > < prop-encoded-array >
interrupts-extended
属性列出了设备生成的中断。当设备连接到多个中断控制器时,应使用 interrupts-extended
而不是interrupts
,因为它使用每个中断说明符对父 phandle
进行编码。
例子:
此示例显示了具有连接到两个独立中断控制器的两个中断输出的设备将如何使用中断扩展属性来描述连接。pic
是一个 #interrupt-cells
说明符为2的中断控制器,而 gic
是一个 #interrupts-cells
说明符为1的中断控制器。
interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;
中断和中断扩展属性是互斥的。设备节点应该其中一个,但不能同时使用两者。
如果中断扩展和中断都存在,则中断扩展优先。
2.4.6、interrupt-nexus
interrupt nexus节点描述中断映射关系
该节点通过interrupt-map,interrupt-map-mask属性描述中断映射关系。
interrupt nexus节点及他的所有直接子节点构成了一个interrupt domain,
在该interrupt domain下中断源怎样表示由#interrupt-cells属性决定,
如何由中断子设备中断源找到中断父设备中断源由interrupt-map和interrupt-map-mask属性决定,
interrupt nexus的父节点可能还是一个interrupt nexus节点,也可能是一个中断控制器。
2.4.6、interrupt-map
值类型:< prop-encoded-array >编码为任意数量的中断映射条目
中断映射是连接节点上的一个属性,它将一个中断域与一组父中断域 连接起来,并指定子域中的中断说明符 如何映射到它们各自的父域。
中断映射是一张表,其中每一行都是一个映射条目,由五个部分组成:子单元地址、子中断说明符、中断父、父单元地址、父中断说明符。
- child unit address
被映射的子节点的单元地址。指定这一点所需的 32 位单元的数量由子节点所在总线节点的 #address-cells 属性描述。
- child interrupt specifier
被映射的子节点的中断说明符。指定此组件所需的 32 位单元数由此节点的 #interrupt-cells 属性描述 - 包含中断映射属性的连接节点。
- interrupt-parent
指向子域映射到的中断父级的单个 < phandle > 值。
- parent unit address
中断父域中的单元地址。指定此地址所需的 32 位单元的数量由中断父字段指向的节点的 #address-cells 属性描述。
- parent interrupt specifier
父域中的中断说明符。指定此组件所需的 32 位单元的数量由中断父字段指向的节点的 #interrupt-cells 属性描述。
例子:
mct@10050000 {
compatible = "samsung,exynos4412-mct";
reg = <0x10050000 0x800>;
interrupt-parent = <&mct_map>;
interrupts = <0>, <1>, <2>, <3>, <4>;
clocks = <&clock CLK_FIN_PLL>, <&clock CLK_MCT>;
clock-names = "fin_pll", "mct";
mct_map: mct-map {
#interrupt-cells = <1>;
#address-cells = <0>;
#size-cells = <0>;
interrupt-map = <0 &gic 0 57 0>,
<1 &combiner 12 5>,
<2 &combiner 12 6>,
<3 &combiner 12 7>,
<4 &gic 1 12 0>;
};
};
这表示mct 0 被设置为连接到gic的SPI中断,中断号是57,mct1连到combiner中断控制寄存器的group12 ,bit是5,剩余几个中断依次map。
2.4.7、interrupt-map-mask
值类型:< prop-encoded-array >
interrupt-map-mask属性包含中断子设备地址和中断子设备中断源的bit mask,给定一个子中断源,那么首先和interrupt-map-mask做与运算,运算结果再通过interrupt-map属性查找对应的中断父设备中断源。
例3:
/ {
model = "Marvell Armada 375 family SoC";
compatible = "marvell,armada375";
soc {
#address-cells = <2>;
#size-cells = <1>;
interrupt-parent = <&gic>;
internal-regs {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
timer@c600 {
compatible = "arm,cortex-a9-twd-timer";
reg = <0xc600 0x20>;
interrupts = <GIC_PPI 13 (IRQ_TYPE_EDGE_RISING | GIC_CPU_MASK_SIMPLE(2))>;
clocks = <&coreclk 2>;
};
gic: interrupt-controller@d000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
#address-cells = <0>;
interrupt-controller;
reg = <0xd000 0x1000>,
<0xc100 0x100>;
};
}
pcie-controller {
compatible = "marvell,armada-370-pcie";
#address-cells = <3>;
#size-cells = <2>;
pcie@1,0 {
#address-cells = <3>;
#size-cells = <2>;
#interrupt-cells = <1>;
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic GIC_SPI 29 IRQ_TYPE_LEVEL_HIGH>;
};
};
}
(1)
首先看到timer@c600这个设备节点下定义了interrupts属性,这说明该设备可以产生中断,但是这个属性下描述了几个中断我们是看不出来的(根据经验,能猜出只是一个中断,现在我们按照规则确认)。
因为该节点没有interrupt-parent属性,认为设备树的父节点internal-regs就是中断父节点,在internal-regs父节点下还是没有interrupt-parent属性,继续找设备树父节点,找到了soc,在该节点下边有interrupt-parent属性。该属性引用的标签为gic,搜索整个设备树,interrupt-controller@d000的标签为gic。gic节点下有interrupt-controller属性,说明他是一个中断控制器。gic节点还有属性#interrupt-cells = <3>,说明在该控制器的interrupt domain下,中断源(interrupt specifier)用3个u32表示,我们再看timer@c600下的interrupts属性也确实由3个u32组成(参考GIC的规范,第一个u32表示中断类型,第二个是中断号,第三个是中断触发条件)。
这个例子说明如果中断产生设备的中断源和中断控制器的中断源是一一对应的,那么可以不需要interrupt nexus节点及相关的属性来表示中断映射。
(2)
再看pcie@1,0这个节点,有#interrupt-cells属性,但是没有interrupt-controller属性,这说明他是一个interrupt nexus节点。该节点的**#interrupt-cells属性为1,说明该interrupt nexus节点管辖下的中断源用1个u32表示就可以了**。
在pcie@1,0节点下边没有子节点,且也没有节点的interrupt-parent属性指向pcie@1,0节点,所以从设备树上看不到该interrupt domain下的中断产生设备,可能的原因是这些中断产生设备软件可以动态识别所以不需要设备树描述。
因为interrupt-map-mask属性是由中断产生设备的地址和中断源(interrupt specifier)组成,且中断源用1个u32表示,可推测中断产生设备地址由3个u32组成。这里需要注意的是pcie@1,0节点的#address-cells属性为3,是说该总线下边的设备地址用3个u32表示,但并不代表中断产生设备的设备地址也一定3个u32表示,要清楚中断产生设备的地址由几个u32组成是由该设备所在总线决定的,对于pcie总线也确实是3,但是其他总线可能存在其他种的情况。
现在我们来分析interrupt-map属性,前三个数字是中断设备地址,第四个数字是中断设备的中断源。因为interrupt-map-mask是全0,这样不管与什么数字做与运算结果都是0,interrupt-map属性的前4个数字也都是0,这说明在pcie@1,0下边所有的中断映射到中断父节点的中断都是一个中断。接着是指向gic的,因为gic节点下#address-cells属性为0,所以后边不需要描述中断父设备的地址了,后边3个数字都是表示中断父设备中断源的。一句话描述就是pcie@1,0下的所有中断都映射到gic,GIC_SPI类型的第29号中断,触发类型为高电平触发。这个例子说明在中断树的最下边可以是interrupt nexus节点。
以上例子中断树的根是gic,gic下边有两个孩子,一个是中断设备timer@c600,一个是interrupt nexus节点pcie@1,0。gic直接管辖的interrupt domain用3个u32表示中断源,timer@c600在这个interrupt domain下。pcie@1,0下定义了一个新的interrupt domain,在该interrupt domain下,中断源用1个u32表示,pcie@1,0用interrupt-map和interrupt-map-mask属性将下边所有设备的中断映射到一个gic下边的中断上。