2. 设备树基础:DTS语法入门、节点与属性

设备树(Device Tree)是嵌入式Linux系统中用于描述硬件拓扑结构的数据结构。在网关硬件抽象层开发中,设备树是连接SoC与外围设备(如GPIO、PWM控制器)的“硬件描述语言”。本章将系统讲解DTS语法、节点与属性的定义,以及编译流程。

核心认知:设备树不是驱动代码,而是硬件配置清单。驱动通过设备树获取寄存器地址、中断号、GPIO引脚等参数,实现与硬件解耦。

2.1 设备树源文件(DTS)语法入门

DTS文件采用树形结构,以/dts-v1/;开头声明版本。基本语法规则如下:

  • 节点(Node):以node-name@unit-address格式定义,花括号内包含属性和子节点。
  • 属性(Property):键值对,值可以是字符串、32位整数数组、字节数组或字符串列表。
  • 引用(Reference):使用&label语法引用其他节点,用于覆盖或追加属性。
  • 包含文件:使用#include导入.dtsi头文件,实现SoC级与板级分离。
// 最小DTS示例
/dts-v1/;

/ {
    model = "Gateway Board v1.0";
    compatible = "vendor,gateway-100", "vendor,gateway";

    #address-cells = <1>;
    #size-cells = <1>;

    cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a7";
        reg = <0x0>;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 512MB RAM
    };
};
语法要点:分号结束每个属性,花括号表示节点范围,@后跟地址用于区分同类型设备,#address-cells和#size-cells决定reg属性的地址长度。

2.2 节点与属性详解

设备树的核心是节点(Node)和属性(Property)。节点代表一个硬件设备或总线,属性描述该设备的配置参数。

概念 语法示例 说明
根节点 / { ... }; 设备树的顶层容器,包含所有设备节点
子节点 uart0: serial@44e09000 { ... }; uart0是标签(label),serial@44e09000是节点名+地址
标签引用 &uart0 { status = "okay"; }; 在板级文件中追加或覆盖SoC级节点的属性
属性值类型 prop = <0x1234 0x5678>; 尖括号内为32位整数数组,空格分隔
字符串属性 compatible = "vendor,device"; 双引号包裹,用于驱动匹配
布尔属性 status = "disabled"; 字符串形式表示开关状态
设备树节点层级结构 根节点 / soc@0 chosen serial@44e0 gpio@4800 pwm@4830 led-gpio pwm-fan 节点与属性关系说明 • 每个节点可以有多个属性(compatible、reg、status等) • 子节点继承父节点的#address-cells和#size-cells设置 • 标签(如uart0)允许在板级文件中通过&uart0引用追加属性 • 节点名@地址中的地址用于区分同类型设备,不一定是物理地址

2.3 标准属性详解

设备树规范定义了一组标准属性,所有设备节点都应遵循。以下是网关开发中最常用的三个标准属性:

2.3.1 compatible 属性

compatible是驱动匹配的核心属性,格式为"厂商,设备型号"。内核通过该属性查找对应的驱动。

// compatible 属性示例
pwm-controller@48300000 {
    compatible = "ti,am3359-ecap", "ti,am33xx-ecap";
    // 内核优先匹配第一个,失败则尝试第二个
    reg = <0x48300000 0x1000>;
};
兼容性策略:将最具体的型号放在第一位,通用型号放在后面。例如"vendor,board-v2"在前,"vendor,board"在后,确保新硬件优先匹配专用驱动。

2.3.2 reg 属性

reg描述设备在总线上的地址空间,由#address-cells和#size-cells决定格式。

#address-cells #size-cells reg 格式 示例
1 1 <addr size> reg = <0x48000000 0x1000>;
2 1 <hi_addr lo_addr size> reg = <0x0 0x48000000 0x1000>;
1 0 <addr> reg = <0x48000000>;(无大小)

2.3.3 interrupts 属性

interrupts描述设备使用的中断资源,需要配合interrupt-parent使用。

// 中断属性完整示例
gpio-keys {
    compatible = "gpio-keys";
    #address-cells = <1>;
    #size-cells = <0>;
    interrupt-parent = <&gpio1>;  // 指定中断控制器

    button@0 {
        label = "SW1";
        linux,code = <KEY_ENTER>;
        gpios = <&gpio1 0 GPIO_ACTIVE_LOW>;
        interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
    };
};
标准属性与驱动匹配流程 设备树节点 compatible + reg + interrupts OF核心层 of_match_table匹配 GPIO控制器驱动 PWM控制器驱动 标准属性在驱动中的使用 • compatible → 驱动通过of_match_table匹配设备节点 • reg → 驱动调用of_iomap()将地址空间映射到虚拟内存 • interrupts → 驱动通过irq_of_parse_and_map()获取中断号 • 三者共同构成设备驱动的硬件描述基础

2.4 设备树源文件编译

DTS文件必须编译为DTB(Device Tree Blob)二进制文件,才能被引导加载程序(如U-Boot)传递给内核。编译流程如下:

  1. 预处理:C预处理器处理#include和宏定义,生成纯DTS文件。
  2. 编译:DTC(Device Tree Compiler)将DTS编译为DTB。
  3. 反编译:DTB可反编译为DTS,用于调试和验证。
# 编译DTS为DTB
dtc -I dts -O dtb -o output.dtb input.dts

# 反编译DTB为DTS
dtc -I dtb -O dts -o output.dts input.dtb

# 带调试信息的编译(显示行号)
dtc -I dts -O dtb -o output.dtb input.dts -@

# 检查DTB语法
dtc -I dtb -O dts input.dtb | head -50
编译技巧:使用-@选项生成符号节点,允许在运行时通过overlay动态加载设备树片段。网关开发中常用此特性实现外设热插拔。

2.5 设备树包含与覆盖机制

实际项目中,设备树采用分层设计:SoC级.dtsi文件定义通用硬件,板级.dts文件通过#include引入并覆盖特定属性。

// am33xx.dtsi (SoC级)
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/am33xx.h>

/ {
    ocp {
        #address-cells = <1>;
        #size-cells = <1>;
        gpio0: gpio@44e07000 {
            compatible = "ti,am3359-gpio";
            reg = <0x44e07000 0x1000>;
            interrupts = <96>;
            #gpio-cells = <2>;
            status = "disabled";
        };
    };
};

// gateway-board.dts (板级)
#include "am33xx.dtsi"

&gpio0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&gpio0_pins>;
};

&ocp {
    gpio-leds {
        compatible = "gpio-leds";
        led0: led-0 {
            label = "gateway:green:status";
            gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
        };
    };
};
设备树编译与覆盖流程 am33xx.dtsi SoC级通用定义 gateway-board.dts 板级特定配置 #include DTC编译器 dtc -O dtb gateway-board.dtb 二进制设备树 覆盖机制要点 • .dtsi定义默认值(status="disabled"),.dts通过&引用覆盖为"okay" • 同一节点多次出现时,属性合并,相同属性后覆盖前

2.6 常见错误与调试方法

错误类型 现象 解决方案
语法错误 DTC报错"Error: syntax error" 检查分号、花括号是否匹配,属性值格式是否正确
地址冲突 内核启动时"Failed to request resource" 检查reg地址是否与其他设备重叠
中断号错误 驱动probe成功但中断不触发 确认interrupt-parent指向正确的中断控制器
compatible不匹配 驱动未加载,/sys/bus/platform/devices下无设备 核对驱动of_match_table中的字符串是否完全一致
属性未生效 驱动读取属性返回默认值 使用dtc -I fs -O dts /proc/device-tree查看实际生效的设备树
调试命令集:
# 查看内核实际使用的设备树
dtc -I fs -O dts /proc/device-tree | less

# 检查特定节点属性
cat /proc/device-tree/gpio@44e07000/compatible

# 查看设备树内存占用
cat /proc/device-tree/memory/reg | xxd -p

# 验证DTB文件完整性
dtc -I dtb -O dts -o /dev/null input.dtb

2.7 本章小结

设备树是嵌入式Linux硬件抽象的核心机制。通过本章学习,应掌握:

  • DTS语法基础:节点定义、属性赋值、标签引用
  • 三大标准属性:compatible(驱动匹配)、reg(地址空间)、interrupts(中断资源)
  • 编译流程:DTS → DTC → DTB,以及反编译调试方法
  • 分层设计:.dtsi与.dts的包含与覆盖机制