2. 设备树基础:设备树(DTS)语法、节点、属性、值的表示方法
设备树(Device Tree)是一种描述硬件资源的数据结构,它通过树形节点和属性将非可枚举的硬件细节传递给操作系统。在嵌入式 Linux 中,设备树源文件(.dts)经过编译生成设备树二进制(.dtb),由 Bootloader 加载并传递给内核。本章将系统讲解 DTS 的语法规则、节点结构、属性定义以及值的编码方式。
reg、interrupts、compatible 等核心属性的含义;熟悉 #address-cells 和 #size-cells 对地址映射的影响。
2.1 设备树文件结构
一个典型的设备树源文件由以下部分组成:
- 根节点:
/,整个设备树的入口。 - 节点:表示一个硬件设备或总线,格式为
node-name@unit-address。 - 属性:键值对,描述设备的特性,如地址、中断、时钟等。
- 值:可以是空、32 位整数、64 位整数、字符串、字节数组或混合类型。
/dts-v1/;
/ {
compatible = "acme,coyote's-revenge";
cpus {
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB at 0x80000000 */
};
serial@101f1000 {
compatible = "ns16550";
reg = <0x101f1000 0x1000>;
interrupts = <0 4>;
};
};
compatible 属性,用于内核匹配驱动;reg 属性描述设备地址和大小,其格式由父节点的 #address-cells 和 #size-cells 决定。
2.2 节点命名与路径
节点名称由两部分组成:节点名和可选的 单元地址,中间用 @ 分隔。单元地址通常是对应设备的起始物理地址,用于区分同一层级下的多个相同类型节点。
| 节点名 | 单元地址 | 完整名称 | 说明 |
|---|---|---|---|
| cpu | 0 | cpu@0 | 第一个 CPU 核心 |
| serial | 101f1000 | serial@101f1000 | UART 控制器,基址 0x101f1000 |
| i2c | 3000 | i2c@3000 | I2C 总线控制器 |
节点路径使用 / 分隔,例如 /cpus/cpu@0 表示根节点下的 cpus 节点下的第一个 CPU。内核通过 of_find_node_by_path() 等 API 根据路径查找节点。
2.3 属性与值的表示
属性是设备树的核心信息载体,每个属性都有一个名称和对应的值。值的类型由编码方式决定,常见类型如下:
| 值类型 | 语法示例 | 说明 |
|---|---|---|
| 空 | interrupt-controller; |
仅表示存在该属性,无值 |
| 32 位整数 | clock-frequency = <50000000>; |
尖括号包裹,单个或多个整数 |
| 64 位整数 | reg = <0x00000000 0x80000000>; |
用两个 32 位整数拼接表示 |
| 字符串 | compatible = "ns16550"; |
双引号包裹 |
| 字符串列表 | compatible = "ti,omap3-uart", "ns16550"; |
逗号分隔多个字符串 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制字节 |
| 混合类型 | reg = <0x101f1000 0x1000>, [00 11 22]; |
可组合使用,用逗号分隔 |
reg 属性中匹配父节点的 #address-cells 和 #size-cells 会导致地址解析错误。例如,若父节点 #address-cells = <2>,则 reg 的第一个地址必须写两个 32 位整数。
2.4 地址映射与 cells 规则
设备树使用 #address-cells 和 #size-cells 定义地址和长度的编码宽度。这两个属性在总线节点(如 /soc、/amba)中声明,影响其子节点的 reg 属性解析。
/ {
#address-cells = <1>;
#size-cells = <1>;
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
serial@101f1000 {
compatible = "ns16550";
reg = <0x101f1000 0x1000>;
interrupts = <0 4>;
};
};
};
上例中,#address-cells = <1> 表示地址用 1 个 32 位整数表示,#size-cells = <1> 表示长度也用 1 个 32 位整数。因此 reg = <0x101f1000 0x1000> 表示基址 0x101f1000,长度 0x1000 字节。
ranges; 为空,表示直接映射(1:1);若 ranges = <0 0x10000000 0x1000>; 则表示子地址 0 映射到父地址 0x10000000,长度 0x1000。
2.5 中断属性与连接
设备树通过 interrupts、interrupt-parent 和 interrupt-controller 描述中断拓扑。每个中断控制器节点需声明 interrupt-controller; 和 #interrupt-cells 属性。
/ {
interrupt-parent = <&gic>;
gic: interrupt-controller@10480000 {
compatible = "arm,cortex-a9-gic";
reg = <0x10480000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
serial@101f1000 {
compatible = "ns16550";
reg = <0x101f1000 0x1000>;
interrupts = <0 4 1>; /* 类型 0,中断号 4,触发方式 1 */
};
};
#interrupt-cells = <3> 表示每个中断描述符由 3 个 32 位整数组成:中断类型(SPI/P PI)、中断号、触发标志。子节点通过 interrupts 属性引用中断,interrupt-parent 指定所属的中断控制器。
2.6 设备树编译与反编译
设备树源文件(.dts)通过 Device Tree Compiler(dtc)编译为二进制文件(.dtb),也可从 .dtb 反编译为 .dts 用于调试。
| 命令 | 说明 |
|---|---|
dtc -I dts -O dtb -o output.dtb input.dts |
将 DTS 编译为 DTB |
dtc -I dtb -O dts -o output.dts input.dtb |
将 DTB 反编译为 DTS |
dtc -I dts -O dtb -@ -o output.dtb input.dts |
编译时保留符号(用于插件) |
fdtdump input.dtb |
以可读格式打印 DTB 内容 |
dtc -I dts -O dtb -o test.dtb test.dts && dtc -I dtb -O dts test.dtb 可以验证 DTS 语法是否正确,并查看编译器优化后的结果。
2.7 设备树节点关系 SVG 图
下图展示了一个典型 ARM SoC 设备树的节点层级关系,包含根节点、CPU、内存、中断控制器和 UART 设备,并标注了关键属性。
2.8 设备树包含与宏
设备树支持 #include 预处理指令,可以引入头文件(.dtsi)或宏定义。这使得 SoC 厂商可以定义公用的设备树包含文件,板级文件只需包含并覆盖特定节点。
// imx6ul.dtsi (SoC 级)
#include "skeleton.dtsi"
/ {
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
uart1: serial@02020000 {
compatible = "fsl,imx6ul-uart", "fsl,imx21-uart";
reg = <0x02020000 0x4000>;
interrupts = <0 26 4>;
status = "disabled";
};
};
};
// myboard.dts (板级)
#include "imx6ul.dtsi"
/ {
model = "My i.MX6UL Board";
compatible = "my,imx6ul-board", "fsl,imx6ul";
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
};
&uart1 引用 SoC 级节点,并修改 status 属性为 "okay" 以启用设备。未覆盖的属性继承自 .dtsi 文件。这种分层设计极大减少了重复代码。
2.9 本章小结
设备树是嵌入式 Linux 驱动开发的基础设施,掌握 DTS 语法、节点命名、属性编码和地址映射规则是编写正确设备树的前提。本章重点包括:
- 节点由 名称 和可选的 单元地址 组成,路径用
/分隔。 - 属性值支持 整数、字符串、字节数组 和 混合类型,语法符号需严格匹配。
#address-cells和#size-cells决定reg属性的解析方式,是地址映射的核心。- 中断拓扑通过
interrupt-controller、#interrupt-cells和interrupts属性描述。 - 使用
#include和节点引用实现 SoC 级与板级设备树的分层复用。
dtc -I dtb -O dts /sys/firmware/fdt 可以导出当前内核使用的设备树,是逆向学习和调试的利器。