2. 设备树语法入门:节点、属性、标签、路径的基本规则
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的处理器、内存、外设、中断控制器、GPIO 控制器等硬件组件组织起来。掌握设备树的语法是编写正确设备树源文件(.dts)的基础。本章将详细讲解设备树的四个核心概念:节点(node)、属性(property)、标签(label)和路径(path)。
2.1 节点(Node):设备树的骨架
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点通过树形结构组织,形成从根节点到叶子节点的层次关系。
核心规则:每个节点必须包含一个节点名,节点名格式为
name@unit-address,其中 unit-address 通常为设备在总线上的起始地址或寄存器基址(可选)。
2.1.1 节点定义语法
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0x00000000 0x1000>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>;
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart@101f1000 {
compatible = "ns16550";
reg = <0x101f1000 0x1000>;
interrupts = <0 5 4>;
};
gpio@101f2000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x101f2000 0x1000>;
interrupts = <0 6 4>;
gpio-controller;
#gpio-cells = <2>;
};
};
};
节点结构说明:
- 根节点:
/是设备树的唯一根节点,所有其他节点都是它的子节点。 - 子节点:
cpu@0、memory@80000000、soc是根节点的直接子节点。 - 嵌套节点:
uart@101f1000和gpio@101f2000是soc节点的子节点。 - 节点名:
uart@101f1000中的uart是设备类型,101f1000是寄存器基址。
最佳实践:节点名应使用小写字母,单词间用连字符分隔(如
serial-controller),避免使用下划线或大写字母。
2.1.2 节点层次结构 SVG 图
2.2 属性(Property):描述节点的特征
属性是附加在节点上的键值对,用于描述设备的硬件特性、配置参数和资源需求。每个属性都有一个名称和一个值,值可以是多种数据类型。
2.2.1 属性值类型
| 数据类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 字符串 | "string" |
compatible = "arm,cortex-a9"; |
用于标识设备兼容性,通常为 "厂商,设备型号" 格式 |
| 32 位整数 | <value> |
reg = <0x101f1000 0x1000>; |
表示地址、大小、中断号等数值 |
| 64 位整数 | <high low> |
reg = <0x00000000 0x80000000>; |
使用两个 32 位单元拼接表示 64 位值 |
| 字节数组 | [hex bytes] |
local-mac-address = [00 11 22 33 44 55]; |
用于 MAC 地址、UUID 等二进制数据 |
| 字符串列表 | "str1", "str2" |
compatible = "ti,am335x-gpio", "ti,omap-gpio"; |
多个兼容字符串,按优先级从高到低排列 |
| 布尔值 | 仅属性名,无值 | gpio-controller; |
存在即表示 true,用于开关型配置 |
| 混合类型 | 组合使用 | interrupts = <0 5 4>; |
可包含整数、字符串、字节数组的组合 |
2.2.2 常用标准属性
| 属性名 | 用途 | 示例值 |
|---|---|---|
compatible |
指定设备兼容的驱动型号 | "brcm,bcm2835-gpio" |
reg |
描述设备寄存器地址空间 | <0x101f1000 0x1000> |
interrupts |
描述设备使用的中断号 | <0 5 4> |
interrupt-parent |
指定中断控制器父节点 | &gic |
gpio-controller |
标记节点为 GPIO 控制器 | 布尔属性,无值 |
#gpio-cells |
指定 GPIO 描述符的单元数 | <2> |
status |
控制设备启用状态 | "okay" 或 "disabled" |
ranges |
定义地址转换映射 | <0x0 0x101f0000 0x10000> |
常见错误:属性值类型不匹配是设备树编译错误的常见原因。例如,
reg 属性必须使用尖括号 <> 包裹整数,而不能使用方括号 [] 或引号 ""。编译时请使用 dtc -I dts -O dtb -o output.dtb input.dts 检查语法。
2.3 标签(Label):节点的别名引用
标签是设备树中为节点定义的别名,用于在设备树源文件的其他位置引用该节点。标签通常用于中断控制器、GPIO 控制器等需要被多个设备引用的节点。
2.3.1 标签定义语法
/ {
soc {
// 定义标签:在节点名前加上 "标签名:"
gic: interrupt-controller@101f4000 {
compatible = "arm,cortex-a9-gic";
reg = <0x101f4000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
gpio0: gpio@101f2000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x101f2000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
};
// 使用标签引用节点
uart@101f1000 {
compatible = "ns16550";
reg = <0x101f1000 0x1000>;
interrupt-parent = <&gic>; // 引用 gic 节点
interrupts = <0 5 4>;
ctrl-gpios = <&gpio0 10 0>; // 引用 gpio0 节点
};
};
2.3.2 标签使用场景
| 场景 | 标签定义 | 引用方式 | 说明 |
|---|---|---|---|
| 中断控制器 | gic: interrupt-controller@... |
interrupt-parent = <&gic>; |
所有使用该中断控制器的设备通过标签引用 |
| GPIO 控制器 | gpio0: gpio@... |
ctrl-gpios = <&gpio0 10 0>; |
设备通过标签引用 GPIO 控制器,并指定引脚号 |
| 时钟控制器 | clk: clock-controller@... |
clocks = <&clk 0>; |
设备引用时钟源,指定时钟索引 |
| DMA 控制器 | dma: dma-controller@... |
dmas = <&dma 2>; |
设备引用 DMA 通道 |
标签命名规范:标签名通常使用小写字母,单词间用下划线或连字符分隔(如
gpio0、uart1、i2c_bus)。标签名在设备树中必须唯一,不能与节点名或其他标签名冲突。
2.4 路径(Path):节点的唯一标识
路径是从根节点到目标节点的完整节点名序列,用于在设备树中唯一标识一个节点。路径在设备树覆盖(overlay)、引用和调试中非常重要。
2.4.1 路径格式
// 绝对路径:从根节点 "/" 开始
/soc/uart@101f1000
/soc/gpio@101f2000
/soc/interrupt-controller@101f4000
// 相对路径:在设备树覆盖中使用
&{/soc/uart@101f1000} {
status = "okay";
};
// 使用标签的路径引用
&uart1 {
status = "okay";
};
2.4.2 路径与标签的对比
| 特性 | 路径(Path) | 标签(Label) |
|---|---|---|
| 定义方式 | 由节点层次结构自动生成 | 在节点定义时手动添加 |
| 唯一性 | 全局唯一,由树结构保证 | 必须手动保证唯一 |
| 可读性 | 较长,但结构清晰 | 简短,便于快速引用 |
| 使用场景 | 设备树覆盖、调试、脚本处理 | 设备树源文件内部引用 |
| 引用语法 | &{/path/to/node} |
&label_name |
| 编译后存在 | 在 DTB 中以字符串形式存在 | 编译后标签名被移除,仅保留路径 |
2.4.3 路径在设备树覆盖中的应用
// 设备树覆盖文件:overlay.dts
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target-path = "/soc/uart@101f1000";
__overlay__ {
status = "okay";
clock-frequency = <24000000>;
};
};
fragment@1 {
target = <&gpio0>;
__overlay__ {
// 使用标签引用更简洁
pinctrl-0 = <&uart_pins>;
};
};
};
路径使用原则:
- 在设备树源文件(
.dts)内部,优先使用标签引用节点,代码更简洁。 - 在设备树覆盖(
.dtso)中,如果目标节点没有标签,必须使用target-path指定绝对路径。 - 在调试和日志输出中,路径是识别节点的标准方式,例如
of_find_node_by_path("/soc/uart@101f1000")。 - 路径中的节点名必须与定义时完全一致,包括
@unit-address部分。
2.5 节点、属性、标签、路径的关系 SVG 图
2.6 实战练习:编写一个简单的设备树
综合运用本章知识,编写一个包含 GPIO 控制器和中断控制器的设备树片段:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
// 中断控制器节点,定义标签 gic
gic: interrupt-controller@101f4000 {
compatible = "arm,cortex-a9-gic";
reg = <0x101f4000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
// GPIO 控制器节点,定义标签 gpio0
gpio0: gpio@101f2000 {
compatible = "brcm,bcm2835-gpio";
reg = <0x101f2000 0x1000>;
interrupts = <0 6 4>;
interrupt-parent = <&gic>;
gpio-controller;
#gpio-cells = <2>;
};
// 使用 GPIO 和中断的设备节点
button@0 {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
button@10 {
label = "User Button";
gpios = <&gpio0 10 0>; // GPIO 0 的第 10 号引脚,低电平有效
interrupts = <0 10 4>; // 中断号 10,上升沿触发
interrupt-parent = <&gic>;
};
};
};
练习建议:将上述代码保存为
test.dts,使用 dtc -I dts -O dtb -o test.dtb test.dts 编译,然后使用 dtc -I dtb -O dts test.dtb 反编译,观察标签在编译后的变化。
2.7 本章小结
| 概念 | 核心要点 | 常见用途 |
|---|---|---|
| 节点 | 树形结构的基本单元,格式为 name@unit-address |
表示 CPU、内存、外设、总线等硬件组件 |
| 属性 | 键值对,支持字符串、整数、字节数组、布尔值等类型 | 描述设备兼容性、地址、中断、GPIO 等硬件特性 |
| 标签 | 节点的别名,格式为 label: node@addr |
在 DTS 内部快速引用节点,简化代码 |
| 路径 | 从根到节点的完整路径,如 /soc/uart@101f1000 |
设备树覆盖、调试、内核 API 查找节点 |