2. 设备树节点与属性:详解节点(node)、属性(property)、标准属性(compatible、reg、interrupts等)的编写规范

设备树(Device Tree,DT)是描述硬件拓扑结构和配置信息的树形数据结构。在嵌入式Linux系统中,设备树是驱动与硬件解耦的核心桥梁。本章将深入解析设备树的节点与属性编写规范,帮助开发者掌握从硬件原理图到设备树源文件(.dts)的转换方法。

核心认知:设备树不是代码,而是数据。它告诉内核“硬件长什么样”,驱动则根据这些数据决定“如何操作硬件”。编写规范的设备树是驱动调试的第一步。

2.1 节点(Node)的层次结构与编写规范

设备树由一系列节点组成,每个节点代表一个硬件设备、总线或控制器。节点从根节点“/”开始,形成树状层次结构。

2.1.1 节点命名规则

节点名称由两部分组成:名称@地址。其中“地址”通常是设备在总线上的片选号、寄存器基地址或端口号,用于区分同一名称下的多个实例。

组件 规则 示例
节点名称 通用名称,小写字母、数字、连字符 uart, i2c, ethernet
地址部分 十六进制地址,无0x前缀 uart@1c28000, i2c@1000
完整节点名 名称@地址 serial@44e09000
避坑指南:节点名称应使用通用硬件名称(如mmc、spi),而非厂商特定名称(如mycompany-mmc)。地址部分必须与reg属性中的第一个地址一致。

2.1.2 节点层次结构示例

以下是一个典型的设备树层次结构,展示了根节点、总线节点和设备节点的组织方式:

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    chosen {
        stdout-path = &uart0;
    };

    ocp {
        compatible = "simple-bus";
        reg = <0x44e00000 0x200000>;

        uart0: serial@44e09000 {
            compatible = "ti,am3352-uart", "ns16550a";
            reg = <0x44e09000 0x2000>;
            interrupts = <72>;
            clock-frequency = <48000000>;
            status = "okay";
        };

        i2c0: i2c@44e0b000 {
            compatible = "ti,am3352-i2c", "ti,omap4-i2c";
            reg = <0x44e0b000 0x1000>;
            interrupts = <70>;
            #address-cells = <1>;
            #size-cells = <0>;
            status = "okay";

            eeprom@50 {
                compatible = "atmel,24c32";
                reg = <0x50>;
                pagesize = <32>;
            };
        };
    };
};
注意:节点中的&符号用于引用标签(label),如&uart0。标签必须在节点定义时声明(如uart0: serial@44e09000),然后在其他位置通过&uart0引用。
设备树节点层次结构图 根节点 "/" chosen { stdout-path } ocp (simple-bus) uart0: serial@44e09000 compatible, reg, interrupts i2c0: i2c@44e0b000 #address-cells, #size-cells eeprom@50 compatible, reg, pagesize gpio@4804c000 compatible, reg, interrupts 图例说明: 根节点 总线节点 设备节点 控制器节点 子设备节点

2.2 属性(Property)的类型与编写规范

属性是节点中存储的键值对,用于描述设备的特性。属性值可以是空、整数、字符串、字节数组或这些类型的组合。

2.2.1 属性值类型

类型 语法 示例 说明
空属性 属性名; interrupt-controller; 仅表示存在,无值
字符串 属性名 = "字符串"; compatible = "ti,am3352-uart"; 单个字符串
字符串列表 属性名 = "str1", "str2"; compatible = "ti,am3352-uart", "ns16550a"; 多个字符串,逗号分隔
32位整数 属性名 = <值>; reg = <0x44e09000 0x2000>; 尖括号内为32位整数
64位整数 属性名 = <高32位 低32位>; reg = <0x0 0x44e09000 0x0 0x2000>; 两个32位整数组合
字节数组 属性名 = [十六进制字节]; local-mac-address = [00 11 22 33 44 55]; 方括号内为十六进制字节
混合类型 属性名 = <整数> "字符串"; gpios = <&gpio1 15 0>; 整数和字符串组合
关键点:属性值中的整数默认是大端序(Big-Endian)。在ARM等小端序平台上,设备树编译器(DTC)会自动处理字节序转换,但驱动开发者需要理解这一底层机制。

2.3 标准属性详解

设备树规范定义了一组标准属性,这些属性在几乎所有设备节点中都会出现。掌握它们的编写规范是驱动调试的基础。

2.3.1 compatible 属性:驱动匹配的关键

compatible属性是设备树与驱动之间建立关联的桥梁。它指定了设备与哪些驱动兼容,内核通过该属性查找匹配的驱动。

编写规范:

  • 格式:"厂商,型号",厂商前缀小写,型号精确
  • 优先级:最精确的兼容字符串放在最前面,通用兼容字符串放在后面
  • 匹配机制:内核遍历兼容字符串列表,找到第一个匹配的驱动即停止
// 正确示例:精确到具体型号,再到通用型号
compatible = "ti,am3352-uart", "ti,omap3-uart", "ns16550a";

// 错误示例:缺少厂商前缀
compatible = "uart";  // 不推荐,可能导致命名冲突

// 错误示例:顺序颠倒
compatible = "ns16550a", "ti,am3352-uart";  // 可能匹配到不精确的驱动
调试提示:当驱动无法加载时,首先检查compatible字符串是否与驱动中的of_match_table完全匹配。注意大小写和逗号后的空格。

2.3.2 reg 属性:描述地址空间

reg属性描述设备在总线上的地址范围,包括起始地址和长度。其解析依赖于父节点的#address-cells和#size-cells属性。

地址单元与大小单元:

父节点属性 含义 常见值
#address-cells 地址占用的32位单元数 1(32位地址)、2(64位地址)
#size-cells 大小占用的32位单元数 1(32位大小)、0(无大小)
// 32位地址空间示例
parent {
    #address-cells = <1>;  // 地址用1个32位单元
    #size-cells = <1>;     // 大小用1个32位单元

    device@44e09000 {
        reg = <0x44e09000 0x2000>;  // 起始地址0x44e09000,大小0x2000
    };
};

// 64位地址空间示例
parent {
    #address-cells = <2>;  // 地址用2个32位单元(64位)
    #size-cells = <2>;     // 大小用2个32位单元(64位)

    device@100000000 {
        reg = <0x1 0x00000000 0x0 0x100000>;  // 地址0x100000000,大小0x100000
    };
};

2.3.3 interrupts 属性:中断连接描述

interrupts属性描述设备使用的中断资源。它需要与interrupt-parent属性配合使用,后者指定中断控制器节点。

中断描述格式:

  • 中断号:在中断控制器中的中断索引
  • 中断触发类型:高电平、低电平、上升沿、下降沿等
  • 中断类型标志:如IRQ_TYPE_EDGE_RISING、IRQ_TYPE_LEVEL_HIGH
// 中断控制器定义
intc: interrupt-controller@48200000 {
    compatible = "ti,am33xx-intc";
    reg = <0x48200000 0x1000>;
    interrupt-controller;           // 声明本节点为中断控制器
    #interrupt-cells = <1>;         // 每个中断描述占用1个32位单元
};

// 设备使用中断
device@44e09000 {
    compatible = "ti,am3352-uart";
    reg = <0x44e09000 0x2000>;
    interrupt-parent = <&intc>;      // 引用中断控制器
    interrupts = <72>;              // 中断号72,默认触发类型
};

// 带触发类型的中断描述(#interrupt-cells = 2)
interrupts = <72 0x4>;  // 中断号72,触发类型0x4(高电平有效)
中断类型标志参考:0x0=上升沿触发,0x1=下降沿触发,0x2=高电平触发,0x3=低电平触发,0x4=高电平有效,0x8=低电平有效。具体值取决于中断控制器的#interrupt-cells定义。

2.3.4 其他标准属性

属性名 用途 示例
status 设备状态控制 "okay"(启用)、"disabled"(禁用)
model 设备型号描述 "TI AM335x BeagleBone Black"
clock-frequency 时钟频率(Hz) <48000000>
gpios GPIO引脚描述 <&gpio1 15 0>(GPIO1_15,无标志)
pinctrl-names 引脚复用状态名称 "default", "sleep"
pinctrl-0 引脚复用配置引用 &pinctrl_uart0_default
dmas DMA通道描述 &edma 0 2
dma-names DMA通道名称 "rx", "tx"
标准属性与驱动匹配流程 设备树节点 compatible = "ti,am3352-uart" reg = <0x44e09000 0x2000> interrupts = <72> status = "okay" 内核匹配过程 1. 读取 compatible 2. 遍历 of_match_table 3. 字符串精确匹配 驱动加载 probe() 调用 读取 reg/interrupts 初始化硬件 驱动中属性解析API调用流程 of_match_node() of_iomap() irq_of_parse_and_map() 匹配compatible 映射reg地址 解析中断

2.4 节点与属性的高级编写技巧

2.4.1 标签与引用

标签(label)是设备树中实现节点引用的关键机制。通过标签,可以在多个位置引用同一个节点,避免重复定义。

// 定义标签
uart0: serial@44e09000 {
    compatible = "ti,am3352-uart";
    reg = <0x44e09000 0x2000>;
};

// 在其他位置引用
&uart0 {
    pinctrl-names = "default";
    pinctrl-0 = &pinctrl_uart0_default;
    status = "okay";
};
最佳实践:在板级设备树(.dts)中,尽量使用&标签的方式引用SoC级设备树(.dtsi)中定义的节点,而不是重新定义整个节点。这样可以保持代码的模块化和可维护性。

2.4.2 包含文件与设备树继承

设备树使用#include指令实现文件包含,通常用于将SoC公共定义(.dtsi)与板级特定配置(.dts)分离。

// am335x-boneblack.dts
#include "am33xx.dtsi"           // 包含SoC级定义
#include "am335x-bone-common.dtsi" // 包含板级公共定义

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    // 板级特定配置
    leds {
        pinctrl-names = "default";
        pinctrl-0 = &user_leds_s0;

        led2 {
            label = "beaglebone:green:heartbeat";
            gpios = &gpio1 21 GPIO_ACTIVE_HIGH;
            linux,default-trigger = "heartbeat";
        };
    };
};

// 覆盖SoC级节点配置
&uart0 {
    status = "okay";
};

2.4.3 属性覆盖与追加

在设备树中,子节点可以覆盖父节点中定义的属性,也可以追加新的属性。这是实现板级定制的核心机制。

操作 语法 效果
覆盖属性 &节点 { 属性 = 新值; }; 完全替换原属性值
追加属性 &节点 { 新属性 = 值; }; 添加原节点不存在的属性
追加子节点 &节点 { 子节点 { }; }; 在已有节点下添加子节点
删除属性 /delete-property/ 属性名; 删除继承的属性
删除节点 /delete-node/ &标签; 删除继承的节点
// 示例:覆盖与追加
// am33xx.dtsi 中定义
uart0: serial@44e09000 {
    compatible = "ti,am3352-uart";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
    status = "disabled";  // SoC级默认禁用
};

// am335x-boneblack.dts 中覆盖
&uart0 {
    status = "okay";      // 板级启用
    pinctrl-names = "default";  // 追加属性
    pinctrl-0 = &pinctrl_uart0_default;
};
常见错误:在覆盖属性时,如果使用=赋值,会完全替换原属性。如果原属性是字符串列表,新值必须包含所有需要的字符串,否则会导致兼容字符串丢失。

2.5 设备树编译与调试

编写完设备树源文件(.dts)后,需要编译为设备树二进制文件(.dtb)供内核使用。掌握编译和反编译工具是调试设备树问题的基本技能。

2.5.1 编译与反编译命令

# 编译 .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 /dev/null input.dts  # 只检查语法,不生成文件

# 生成设备树符号表
dtc -I dts -O dtb -@ -o output.dtb input.dts  # -@ 生成符号表,支持插件

2.5.2 常见编译错误与解决方法

错误信息 原因 解决方法
ERROR: Input tree has errors 语法错误或属性值格式错误 检查括号、分号、引号是否匹配
WARNING: unit name should not have a unit-address 节点名称包含地址但无reg属性 添加reg属性或移除地址部分
ERROR: phandle 0x... not found 引用的标签不存在 检查标签定义和引用是否一致
ERROR: Missing property '#address-cells' 总线节点缺少地址单元定义