1.Device Tree
1.内核不包含对硬件的描述,它以二进制的形式单独存储在另外的位置:the device tree blob。
2.bootloader需要加载两个二进制文件:内核镜像和DTB内核镜像仍然是uImage或者zImage,DTB文件在arch/arm/boot/dts中,每一个board对应一个dts文件;
3.bootloader通过r2寄存器来传递DTB地址,通过修改DTB可以修改内存信息,kernel command line,以及潜在的其它信息;
4.不再有machine type;
5.U-Boot的内核启动命令:bootm <kernel img addr> - <dtb addr>
2.设备树原理
1.首先要了解硬件配置和系统运行参数,并把这些信息组织成Device Tree source file。
2.通过DTC(Device Tree Compiler),可以将这些适合人类阅读的Device Tree source file变成适合机器处理的Device Tree binary file(也叫DTB,device tree blob)。
3.在系统启动的时候,boot program(例如:firmware、bootloader)可以将保存在flash中的DTB copy到内存(或通过bootloader的交互式命令加载DTB,或者firmware可以探测到device的信息,组织成DTB保存在内存中),
并把DTB的起始地址传递给client program(例如OS kernel,bootloader或者其他特殊功能的程序)。对于计算机系统(computer system),一般是firmware->bootloader->OS,对于嵌入式系统,一般是bootloader->OS。
4.本质上,Device Tree改变了原来用hardcode方式将HW 配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。
2.1设备树的特性
1.每个设备树文件都有一个根节点,每个设备都是一个节点。
2.节点由 节点名 + 属性 组成。
3.节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。
4.每个设备的属性都用一组key-value对(键值对)来描述。
5.每个属性的描述用;结束
3.DTS语法结构
1.DTS的一些属性(设备树源文件中有几个基本的数据表示形式)
空属性:empty-property
字符串属性:string-property
文本字符串(无结束符)可以用双引号表示: string-property = "a string"
字符串列表属性:string-list-property
逗号也可用于创建字符串列表: string-list = "red fish", "blue fish";
Cells(u32整型)属性:cell-property
Cells是 32 位无符号整数,用尖括号限定: cell-property = <0xbeef 123 0xabcd1234>
二进制属性:binary-property
二进制数据用方括号限定: binary-property = [01 23 45 67];
不同表示形式的数据可以使用逗号连在一起: mixed-property = "a string", [01 23 45 67], <0x12345678>;
2.DTC (Compiler 编译工具)
DTC为编译工具,dtc编译器可以把dts文件编译成为dtb,也可把dtb编译成为dts文件。在linux下,make dtbs ,这里的s代表复数可单独编译dtb。
DTC编译.dts生成的二进制文件(.dtb),bootloader在引导内核时,会预先读取.dtb到内存,进而由内核解析。
(1) dts编译生成dtb
./scripts/dtc/dtc -I dts -O dtb -o B_dtb.dtb A_dts.dts # 把A_dts.dts编译生成B_dtb.dtb
(2) dtb编译生成dts(反汇编dtb)
./scripts/dtc/dtc -I dtb -O dts -o A_dts.dts A_dtb.dtb # 把A_dtb.dtb反编译生成为A_dts.dts
4.设备树基本框架
/表示板子,它的子节点node1表示SoC上的某个控制器,控制器中的子节点node2表示挂接在这个控制器上的设备(们)。
/ { //根节点
node1{ //node1是节点名,是/的子节点
key=value; //node1的属性
...
node2{ //node2是node1的子节点
key=value; //node2的属性
...
}
} //node1的描述到此为止
node3{
key=value;
...
}
}
5.使用引用的好处:
编译设备树的时候,相同的节点的不同属性信息都会被合并,相同节点的相同的属性会被重写(覆盖前值),使用引用可以避免移植者四处找节点,直接在板级.dts增改即可。
/include/ "imx53.dtsi"
/ {
model = "Freescale i.MX53 Automotive Reference Design Board";
compatible = "fsl,imx53-ard", "fsl,imx53";
memory {
reg = <0x70000000 0x40000000>;
};
eim-cs1@f4000000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "fsl,eim-bus", "simple-bus";
reg = <0xf4000000 0x3ff0000>;
lan9220@f4000000 {
compatible = "smsc,lan9220", "smsc,lan9115";
reg = <0xf4000000 0x2000000>;
phy-mode = "mii";
interrupt-parent = <&gpio2>; // 直接使用引用
vdd33a-supply = <®_3p3v>;
};
};
regulators {
compatible = "simple-bus";
reg_3p3v: 3p3v { // 定义一个引用
compatible = "regulator-fixed";
regulator-name = "3P3V";
};
};
...
// 引用一个节点,新增/修改其属性。
®_3p3v {
regulator-always-on;
}
6.compatible案例介绍
随便在设备树中出一个网卡设备,找到 compatible 属性中的 <model>值。
// 文件节选于:arch/arm/boot/dts/vexpress-v2m-rs1.dtsi
ethernet@2,02000000 {
compatible = "smsc,lan9118", "smsc,lan9115";
reg = <2 0x02000000 0x10000>;
interrupts = <15>;
phy-mode = "mii";
reg-io-width = <4>;
smsc,irq-active-high;
smsc,irq-push-pull;
vdd33a-supply = <&v2m_fixed_3v3>;
vddvario-supply = <&v2m_fixed_3v3>;
};
在驱动中(为了方便读者理解,这里在内核源码根目录下查找,实际上就是在driver目录中),找到对应的.compatible关键字所在的文件以及行数。
$ find . 2>/dev/null | grep lan9115
arch/arm/boot/dts/vexpress-v2m-rs1.dtsi:50: compatible = "smsc,lan9118", "smsc,lan9115";
arch/arm/boot/dts/vexpress-v2m.dtsi:49: compatible = "smsc,lan9118", "smsc,lan9115";
drivers/net/ethernet/smsc/smsc911x.c:2578: { .compatible = "smsc,lan9115", },
顺藤摸瓜,找到所在行,也就找到了用来描述设备信息的结构体of_device_id。
// 节选于 drivers/net/ethernet/smsc/smsc911x.c
#ifdef CONFIG_OF
static const struct of_device_id smsc911x_dt_ids[] = {
{ .compatible = "smsc,lan9115", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, smsc911x_dt_ids);
#endif
可以看出,驱动中用于匹配的结构使用的compatible和设备树中一模一样,且,字符串需要严格匹配。
7.address案例介绍
aips-bus@02000000 { /* AIPS1 */
compatible = "fsl,aips-bus", "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x02000000 0x100000>;
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
rtc: rtc@68 {
compatible = "stm,mt41t62";
reg = <0x68>;
};
};
};
aips-bus@02000000 是 i2c@021a0000 的父节点;i2c@021a0000 是 rtc@68 的父节点。
aips-bus@02000000的 #address-cells 和#size-cells均为1,所以 i2c@021a0000 中的 `reg` 格式为: `<address length>`
i2c@021a0000的 #address-cells 和#size-cells分别为1和0,所以 rtc@68 中的 `reg` 格式为: `<address>`
通俗来讲,如果现在有 一个节点A的 #address-cells 和#size-cells分别为2和1;那么A的子节点B 的 `reg`格式为 `<address address length>`
8.interrupts中断属性案例介绍
中断产生设备用interrupts属性描述中断源(interrupt specifier),因为不同的硬件描述中断源需要的数据量不同,所以interrupts属性的类型也是。
为了明确表示一个中断由几个u32表示,又引入了#interrupt-cells属性,#interrupt-cells属性的类型是u32,假如一个中断源需要2个u32表示(一个表示中断号,另一个表示中断类型),那么#interrupt-cells就设置成2。
有些情况下,设备树的父节点不是中断的父节点(主要是中断控制器一般不是父节点),为此引入了interrupt-parent属性,该属性的类型是,
用来引用中断父节点(我们前边说过,一般用父节点的标签,这个地方说中断父节点而不是中断控制器是有原因的)。
如果设备树的父节点就是中断父节点,那么可以不用设置interrupt-parent属性。interrupts属性和interrupt-parent属性都是中断产生设备节点的属性,
但是#interrupt-cells属性不是,#interrupt-cells属性是中断控制器节点以及interrupt nexus节点的属性,这两类节点都可能是中断父节点。
一个计算机系统中大量设备都是通过中断请求CPU服务的,所以设备节点中就需要在指定中断号。常用的属性有:
interrupt-controller: 一个空属性用来声明这个node接收中断信号,即这个node是一个中断控制器。
#interrupt-cells :是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,,用来描述子节点中interrupts属性使用了父节点中的interrupts属性的具体的哪个值。
一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>
interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的
interrupts:一个中断标识符列表,表示每一个中断输出信号。
interrupts 属性,在ARM GIC(Generic Interrupt Controller)中,ARM GIC 说明文档位于:Documentation/devicetree/bindings/arm/gic.txt。
当interrupt-cells为3时,interrupts包含三个cells,如interrupts = <0 168 4>
第一个cell代表中断类型:0 表示SPI中断,1 表示PPI中断。
第二个cell代表具体的中断类型:
PPI中断:私有外设中断(Private Peripheral Interrupt),是每个CPU私有的中断。最多支持16个PPI中断,范围【0 - 15】。
SPI中断类型:公用外设中断(Shared Peripheral Interrupt),最多可以支持988个外设中断,范围【0 - 987】
第三个cell代表中断触发标志:
bits [ 3 :0 ] 触发类型和级别标志:1 = 低- 至- 高边沿触发,2 = 高- 到- 低边沿触发,4 = 活跃的高水平 - 敏感,8 = 低电平有效 - 敏感
bits [ 15 :8 ] PPI中断cpu掩码。每个位对应于每个位附加到GIC的8个可能的cpu。指示设置为"1"的位中断被连接到该CPU 。只有有效的PPI中断。
当interrupt-cells为2时,interrupts包含2个cells,如interrupts = <2 4>:
第一个cell代表具体的中断类型:
SGI中断:软件触发中断(Software Generated Interrupt),通常用于多核间通讯,最多支持16个SGI中断,硬件中断号从ID0~ID15。
PPI中断:私有外设中断(Private Peripheral Interrupt),是每个CPU私有的中断。最多支持16个PPI中断,硬件中断号从ID16~ID31。
SPI中断类型:公用外设中断(Shared Peripheral Interrupt),最多可以支持988个外设中断,硬件中断号从ID32~ID1019。
第二个cell代表中断触发标志:
bits [ 3 :0 ] 触发类型和级别标志:1 = 低- 至- 高边沿触发,2 = 高- 到- 低边沿触发,4 = 活跃的高水平- 敏感,8 = 低电平有效- 敏感。
bits [ 15 :8 ] PPI中断cpu掩码。每个位对应于每个位附加到GIC的8个可能的cpu。指示设置为"1"的位中断被连接到该CPU 。只有有效的PPI中断。
reg : 在schips todo
/ {
compatible = "acme,coyotes-revenge";
#address-cells = <1>;
#size-cells = <1>;
interrupt-parent = <&intc>;//指定依附的中断控制器是intc
serial@101f0000 { //子节点:串口设备
compatible = "arm,pl011";
reg = <0x101f0000 0x1000 >;
interrupts = < 1 0 >;
};
intc: interrupt-controller@10140000 { //intc中断控制器
compatible = "arm,pl190";
reg = <0x10140000 0x1000 >;
interrupt-controller;//定义为中断控制器设备
#interrupt-cells = <2>;
};
}
9.GPIO属性
gpio是最常见的IO口,常用的属性有:
"gpio-controller",用来说明该节点描述的是一个gpio控制器
"#gpio-cells",用来描述gpio使用节点的属性一个cell的内容,即 `属性 = <&引用GPIO节点别名 GPIO标号 工作模式>
通过上面的属性定义以后,就可以使用它,例如:
&spi_1 {
status = "okay";
cs-gpios = <&gpa2 5 GPIO_ACTIVE_HIGH>; // 使用 GPIO A2 第5个引脚,
w25q80bw@0 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "w25x80";
reg = <0>;
spi-max-frequency = <1000000>;
controller-data {
samsung,spi-feedback-delay = <0>;
};
10.别名与选择器
通过 aliases 节点为设备提供简化的名称。
aliases {
serial0 = &uart0;
ethernet0 = ð0;
};
11.覆盖与修改(overlay)
可以通过 fragment 和 target 节点来修改现有的 Device Tree。
/dts-v1/;
/plugin/;
fragment@0 {
target-path = "/";
__overlay__ {
led@0 {
compatible = "gpio-leds";
gpios = <&gpio1 24 0>;
};
};
};
附录:补充对于interrupt-parent的一些知识点
为什么会有interrupt-parent?
1.Linux设备管理中对中断的设计思路演变:
随着linux kernel的发展,在内核中将interrupt controller抽象成irqchip这个概念越来越流行,甚至GPIO controller也可以被看出一个interrupt controller chip,
这样,系统中至少有两个中断控制器了。另外,在硬件上,随着系统复杂度加大,外设中断数据增加,在这种趋势下,内核中原本的中断源直接到中断号的方式已经很难继续发展了,
为了解决这些问题,linux kernel的大牛们就创造了irq domain(中断域)这个概念。domain在内核中有很多,除了irqdomain,还有power domain,clock 这些domain等等;
所谓domain,就是领域,范围的意思(即:任何的定义出了这个范围就没有意义了)。
实际上系统可以需要多个中断控制器进行级联,形成事实上的硬件中断处理结构:

如上所述,系统中所有的interrupt controller会形成树状结构,对于每个interrupt controller都可以连接若干个外设的中断请求(interrupt source,中断源),
interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。
有了irq domain这个概念之后,这个编号仅仅限制在本interrupt controller范围内。
有了这样的设计,CPU(Linux 内核)就可以根据级联的规则一级一级的找到想要访问的中断。当然,通常我们关心的只是内核中的中断号,具体这个中断号是怎么找到相应的中断源的,我们作为程序员往往不需要关心。
在写设备树的时候,设备树就是要描述嵌入式软件开发中涉及的所有硬件信息。所以,设备树就需要准确描述硬件上处理中断的这种树状结构,如此,就有了我们的interrupt-parant这样的概念:
用来连接这样的树状结构的上下级,用于表示这个中断归属于哪个interrupt controller,比如,一个接在GPIO上的按键,它的组织形式就是:
中断源--interrupt parent-->GPIO--interrupt parent-->GIC1--interrupt parent-->GIC2--...-->CPU
有了parant,我们就可以使用一级一级的偏移量来最终获得当前中断的绝对编号。
可以看出,在我板子上的dm9000的的设备节点中,它的"interrupt-parent"引用了"exynos4x12-pinctrl.dtsi"(被板级设备树的exynos4412.dtsi包含)中的gpx0节点:

而在gpx0节点中,指定了#interrupt-cells = <2>;,所以在dm9000中的属性interrupts = <6 4>;表示dm9000的的中断在作为irq parant的gpx0中的中断偏移量,
即gpx0中的属性interrupts中的<0 22 0>,通过查阅exynos4412的手册知道,对应的中断号是EINT[6]。

浙公网安备 33010602011771号