4、设备树语法:节点、属性、值、标签、引用

设备树(Device Tree)是一种描述硬件资源的数据结构,它通过树形层级关系将CPU地址空间中的外设、总线、时钟、中断、GPIO等资源组织起来。理解设备树的语法是编写和调试设备树源文件(.dts)的基础。本章将围绕节点(node)、属性(property)、值(value)、标签(label)与引用(reference)五个核心要素展开,结合Linux内核的解析逻辑与实战案例,帮助您建立完整的语法知识框架。

根节点 "/" cpus 节点 #address-cells = <1> soc 节点 compatible = "simple-bus" cpu@0 节点 reg = <0x0>; clock-frequency serial@10000000 节点 reg = <0x10000000 0x1000> gpio@10002000 节点 gpio-controller; #gpio-cells 标签 (Label) 示例 uart0: serial@10000000 &uart0 { status = "okay"; } 属性与值示例 compatible = "fsl,imx6q-uart" interrupts = <0 26 4> 设备树语法核心关系 节点构成树形结构 → 属性描述硬件特征 → 值定义具体参数 → 标签提供引用句柄 → 引用实现跨节点修改

4.1 节点:设备树的骨架

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或抽象功能块。节点名称由"节点名@单元地址"组成,其中单元地址用于区分同一层级下相同名称的节点。例如,serial@10000000表示位于0x10000000地址的串口控制器。节点可以嵌套,形成父子关系,根节点用/表示,是整棵树的起点。

节点的核心作用包括:

  • 硬件拓扑描述:通过父子关系表达总线与设备之间的连接结构。
  • 资源封装:将寄存器地址、中断号、时钟、GPIO等资源封装在节点内部。
  • 驱动匹配入口:内核通过节点的compatible属性查找对应的设备驱动。
  • 运行时配置:通过status属性控制设备启用或禁用状态。

节点命名需遵循以下规则:节点名应使用小写字母、数字、逗号和连字符;单元地址必须是十六进制数,且与reg属性中的首地址一致。若节点没有地址,可省略@后缀,如cpus节点。

4.2 属性与值:描述硬件特征的语言

属性是节点的"键值对",用于描述硬件的具体特征。属性值支持四种基本类型:<u32>(32位无符号整数)、<u64>(64位无符号整数)、字符串(string)和字节序列(prop-encoded-array)。实际编写中,值通常以<...>表示整数数组,以双引号表示字符串,以[ ... ]表示字节序列。

属性类型语法示例典型用途
字符串compatible = "fsl,imx6q-uart"驱动兼容性匹配
整数数组reg = <0x10000000 0x1000>寄存器地址与长度
布尔值gpio-controller;声明节点为GPIO控制器
混合类型interrupts = <0 26 4>中断号、触发类型等

标准属性如compatible、reg、interrupts、clocks等由内核定义,具有特定解析语义。自定义属性需以厂商前缀命名,如fsl,uart-dma-enabled,避免与标准属性冲突。

4.3 标签与引用:实现跨节点操作

标签(Label)是节点的别名,用于在设备树源文件中引用节点。标签定义在节点名前,格式为标签名: 节点名@地址。引用通过&标签名实现,常用于以下场景:

  1. 追加属性:在根节点或其它位置修改子节点属性,如&uart0 { status = "okay"; }。
  2. 引用资源:在clocks、interrupts等属性中引用其它节点,如clocks = <&clk_uart>。
  3. 覆盖节点:在板级设备树中覆盖SoC级设备树中的默认配置。

引用机制使得设备树具有良好的可扩展性,允许在不同层级的设备树文件(如SoC级.dtsi与板级.dts)之间进行增量修改,而无需复制整个节点。

实战案例:基于i.MX6Q平台的UART节点定义与引用

假设我们需要在板级设备树中启用SoC级设备树中默认禁用的UART2,并配置其引脚复用。初始SoC级设备树(imx6q.dtsi)中定义:

uart2: serial@21e8000 {
    compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x021e8000 0x4000>;
    interrupts = <0 27 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6QDL_CLK_UART2>;
    clock-names = "ipg";
    status = "disabled";
};

板级设备树(myboard.dts)中通过标签引用启用该节点并配置引脚:

&uart2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart2>;
    status = "okay";
};

&iomuxc {
    pinctrl_uart2: uart2grp {
        fsl,pins = <
            MX6QDL_PAD_GPIO_07__UART2_TX_DATA 0x1b0b1
            MX6QDL_PAD_GPIO_08__UART2_RX_DATA 0x1b0b1
        >;
    };
};

此案例中,&uart2引用标签uart2,追加了引脚控制配置并将状态改为okay;&iomuxc引用IOMUX控制器节点,新增了引脚复用子节点。编译后,内核解析设备树时会将两处修改合并到同一节点,最终UART2被正确初始化。

4.4 常见误区与总结复盘

在编写设备树时,以下问题需要特别注意:

  • 地址与单元地址不匹配:节点名中的@地址必须与reg属性首地址一致,否则内核可能无法正确匹配设备。
  • 标签重复定义:同一设备树中标签必须唯一,否则编译报错。
  • 引用未定义标签:使用&引用不存在的标签会导致编译失败,需检查标签拼写及文件包含关系。
  • 属性值类型错误:例如将字符串写成整数数组,或reg中地址长度与#address-cells不匹配。
  • 忽略status属性:默认情况下设备是启用的,若SoC级默认disabled,板级必须显式改为okay。

总结复盘:设备树语法的核心在于"节点组织结构、属性描述特征、值定义参数、标签提供句柄、引用实现修改"。编写时应遵循"先结构后属性、先SoC后板级"的顺序,充分利用标签引用机制减少重复代码。调试时可通过dtc -I dtb -O dts反编译设备树二进制文件,验证最终合并结果是否符合预期。

经验提醒:在多人协作或复杂平台开发中,建议为每个外设节点定义清晰的标签命名规范(如uart0、i2c2、gpio5),并在板级文件中集中管理所有引用修改,便于代码审查与维护。

风险提示:设备树中的reg属性地址必须与硬件实际地址一致,错误配置可能导致内核访问非法地址,引发系统崩溃。修改地址前务必查阅芯片参考手册。