在 Linux 启动过程中,U-Boot / UEFI 将一个“设备树 Blob”(DTB)文件加载到内存中,并将指向它的指针传递给内核。此 DTB 文件描述了 Linux 内核的系统硬件布局,允许将特定于平台的代码移出内核源代码并替换为可以解析 DTB 并根据需要配置系统的通用代码。
设备树源 (DTS) 文件是简单的文本文件,可以使用设备树编译器 (DTC) 工具将其编译为二进制设备树 Blob (DTB) 格式。DTC 工具位于/scripts/dtc下的 Linux 内核源代码中,也可通过一些分发包管理器(例如 Ubuntu 上的 APT)进行安装:
$ sudo apt-get update
$ sudo apt-get install device-tree-compiler
使用 DTC 反编译 DTB 是无损的,即可以将 DTB 反编译为 DTS,然后将该 DTS 编译回 DTB 而不会丢失任何信息。反编译:
$ dtc -I dtb -O dts juno.dtb > juno.dts
并重新编译:
$ dtc -I dts -O dtb juno.dts > juno.dtb
注意:通用设备树语法的完整文档可以在DeviceTree.org找到,Linux 绑定文档可以在这里找到 Linux 内核源代码
设备树源文件是由具有相关属性的节点和子节点组成的树结构。一般语法如下:
/ {
node1 {
a-string-property = "a string";
a-string-list-property = "first string", "second string";
a-byte-data-property = [0x01 0x02 0x03];
child-node1 {
first-child-property;
second-child-property
a-string-property = "child string";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3>;
child-node1 {
};
};
};
/表示树的根节点node1并且node2是根节点的子节点node1有两个子节点:child-node1和child-node2每个节点都有一组与之关联的属性:
a-string-property = "一个字符串";
细胞属性 = <1 2 3>;
二进制属性 = [0x01 0x02 0x03];
a-mixed-property = "一个字符串", [0x01 0x02 0x03], <1 2 3>;
a-string-list = "第一个字符串", "第二个字符串";
这是 Juno 的 DTS 条目示例,特别是 GIC-400:
interrupt-controller@2c010000 {
compatible = "arm,gic-400";
reg = <0x0 0x2c010000 0x0 0x1000 0x0 0x2c02f000 0x0 0x2000 0x0 0x2c04f000 0x0 0x2000 0x0 0x2c06f000 0x0 0x2000>;
#address-cells = <0x2>;
#interrupt-cells = <0x3>;
#size-cells = <0x2>;
interrupt-controller;
interrupts = <0x1 0x9 0x3f04>;
ranges = <0x0 0x0 0x0 0x2c1c0000 0x0 0x40000>;
linux,phandle = <0x1>;
phandle = <0x1>;
v2m@0 {
compatible = "arm,gic-v2m-frame";
msi-controller;
reg = <0x0 0x0 0x0 0x1000>;
};
};
让我们分解这个条目,以了解如何使用设备树节点来描述真实硬件:
interrupt-controller@2c010000 {
中断控制器位于父节点“总线”上的地址 0x2C010000(稍后会详细介绍)。在这种情况下,父节点是根节点,因此从 CPU 的角度来看,这意味着地址 0x2C010000。
compatible = "arm,gic-400";
Linux 内核使用它来决定将哪个设备驱动程序绑定到外围设备。在这种情况下,中断控制器与 ARM GIC-400 驱动程序兼容。
reg = <0x0 0x2c010000 0x0 0x1000 0x0 0x2c02f000 0x0 0x2000 0x0 0x2c04f000 0x0 0x2000 0x0 0x2c06f000 0x0 0x2000>;
reg属性定义了外围设备将响应的地址范围。该属性由许多元组组成,每个元组包含一个区域的基地址和该区域的大小。要解析属性,我们需要了解三件事:
每个单元格值是一个 32 位无符号整数
父节点的#address-cells值定义了多少个单元格值构成一个区域的基地址
父节点的#size-cells值定义了多少个单元格值构成一个区域的长度
中断控制器节点是根节点的子节点,在 Juno 上有#address-cells = #size-cells= 2。按顺序移动该reg属性并将前 2 个单元格组合为基地址,接下来的 2 个单元格为长度,接下来的 2 个单元格为基地址等给出中断控制器将响应的以下区域:
64-bit address 0x0000_0000_2C01_0000 with 64-bit length 0x0000_0000_0000_1000
64-bit address 0x0000_0000_2C02_F000 with 64-bit length 0x0000_0000_0000_2000
64-bit address 0x0000_0000_2C04_F000 with 64-bit length 0x0000_0000_0000_2000
64-bit address 0x0000_0000_2C06_F000 with 64-bit length 0x0000_0000_0000_2000
interrupt-controller;
所有中断控制器节点都必须包含一个空interrupt-controller属性。
interrupts = <0x1 0x9 0x3f04>;
GIC 的中断设备树绑定格式可以 在 Linux 内核文档中找到 :
所以这里 GIC 本身将 PPI #9 作为针对 CPU [0…5] 的活动高电平敏感中断,即 Juno 上的所有 CPU,总共有 6 个 CPU。此 PPI(中断 ID 25)对应于 Juno 上的 VGIC 维护中断,是支持虚拟化扩展的 CPU 所必需的。
ranges = <0x0 0x0 0x0 0x2c1c0000 0x0 0x40000>;
v2m@0 {
compatible = "arm,gic-v2m-frame";
msi-controller;
reg = <0x0 0x0 0x0 0x1000>;
};
节点中的所有地址都指定为该节点本地的总线地址。range 属性提供了一种将这些总线地址映射到父节点地址空间的机制。
Juno 的中断控制器节点是v2m地址 0x0 的子节点,位于中断控制器“总线”的本地。该节点用于 GICv2m 扩展,需要从中断控制器“总线”映射到 CPU 的内存角度。
reg与属性类似,ranges使用#address-cellsand#size-cells来解析 (Local Address, Parent Address, Length) 的元组,其中:
本地地址的宽度由本地节点的#address-cells值决定
父地址的宽度由父节点的#address-cells值决定
Length 的宽度由本地节点的#size-cells值决定
在 Juno 上,这些都是 2,因此范围属性被解释为“将 64 位本地地址 0x0 映射到 64 位长度为 0x40000 的 64 位父地址 0x2C1C0000”。因为v2m节点在本地地址0x0,所以会映射到父节点的地址0x2C1C0000,而父节点是根节点,这就是CPU对内存的看法。
您可能需要检查某些 dtb 的值,以进行调试,检查 dtb 在您修改后是否确实得到了更新等。虽然您无法获得已加载的设备树的类似 .dts 文件的视图,但您可以使用 /proc/device-tree 中的条目检查值。
在 Linux 的命令提示符下,您应该会在 /proc/device-tree 目录中看到类似以下内容:
root@j7-evm:~# ls /proc/device-tree/
#address-cells firmware l3-cache0
#size-cells fixedregulator-evm12v0 main_r5fss_cpsw9g_virt_mac0
__symbols__ fixedregulator-sd memory@80000000
aliases fixedregulator-vsys3v3 model
chosen fixedregulator-vsys5v0 name
compatible gpio-keys pmu
connector interconnect@100000 reserved-memory
cpus interrupt-parent serial-number
dma_buf_phys ion sound@0
dummy-panel l2-cache0 timer-cl0-cpu0
假设您不知道要查找的设备树的绝对路径。/proc/device-tree 中有一个 __symbol__ 目录。它对设备树中的每个符号标签都有。我们可以通过cat 来找到该符号的确切路径。以下是演示使用的示例:
root@j7-evm:~# cat /proc/device-tree/__symbols__/main_i2c0
/interconnect@100000/i2c@2000000
root@j7-evm:~# ls /proc/device-tree/interconnect@100000/i2c@2000000/
#address-cells clock-names gpio@20 name pinctrl-names
#size-cells clocks gpio@22 phandle power-domains
clock-frequency compatible interrupts pinctrl-0 reg