2. 设备树语法精讲(上):节点、属性、标签、别名、根节点、compatible、reg、#address-cells 与 #size-cells

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点和键值对属性来描述系统中不可枚举的设备。本章将深入讲解设备树源文件(.dts)的核心语法元素,包括节点、属性、标签、别名以及几个关键的标准属性。

学习提示: 设备树语法是嵌入式 Linux 移植的基础。掌握本节内容后,你将能够读懂并修改大多数开发板的 .dts 文件,为后续的板级适配打下坚实基础。

2.1 节点(Node)与根节点

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

  • 根节点(Root Node): 使用 / 表示,是整个设备树的起点。所有其他节点都是根节点的子节点或后代节点。
  • 子节点: 在根节点内部定义的节点,通常对应 SoC 内部模块或外部总线。
  • 节点名称: 格式为 node-name@unit-address,其中 unit-address 是设备在总线上的地址(可选)。
/dts-v1/;

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

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

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;
    };

    serial@101f1000 {
        compatible = "ns16550";
        reg = <0x101f1000 0x1000>;
        interrupts = <0 12 4>;
    };
};
关键点: 根节点 / 必须存在,且通常包含 #address-cells 和 #size-cells 属性来定义地址空间的编码格式。

2.2 属性(Property)与标签(Label)

属性是节点的键值对,用于描述设备的特性。标签则是一种引用机制,允许在设备树的其他位置引用某个节点。

概念 语法 说明
属性 property-name = <value>; 值可以是字符串、32位整数数组、64位整数数组、字节数组或字符串列表。
标签 label: node-name { ... }; 标签是节点的别名,可在其他位置通过 &label 引用该节点。
别名(aliases) aliases { name = &label; }; 在根节点下定义,为节点提供简短易记的名称。
/ {
    aliases {
        serial0 = &uart0;
        serial1 = &uart1;
    };

    uart0: serial@101f1000 {
        compatible = "ns16550";
        reg = <0x101f1000 0x1000>;
    };

    uart1: serial@101f2000 {
        compatible = "ns16550";
        reg = <0x101f2000 0x1000>;
    };
};

/* 在 .dtsi 或 .dts 文件中通过 &uart0 引用节点 */
&uart0 {
    clock-frequency = <1843200>;
};
常见错误: 标签名不能与节点名冲突,且标签只能在节点定义时声明,不能单独存在。别名通常用于 bootloader 或早期启动代码快速查找设备。

2.3 compatible 属性

compatible 属性是设备树中最关键的属性之一,它指定了设备的驱动兼容性字符串。操作系统通过该属性将设备与对应的驱动程序绑定。

  • 格式: 字符串列表,按优先级从高到低排列。
  • 命名规范: "厂商,设备型号",例如 "ti,omap3-uart"、"arm,pl011"。
  • 匹配机制: 内核驱动通过 of_match_table 与设备树的 compatible 值进行匹配。
/* 设备树节点 */
i2c@1000 {
    compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
    reg = <0x1000 0x100>;
    interrupts = <0 56 4>;
};

/* 内核驱动匹配表 */
static const struct of_device_id imx_i2c_of_match[] = {
    { .compatible = "fsl,imx6q-i2c", .data = &imx6q_i2c_data },
    { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_data },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_i2c_of_match);
最佳实践: 始终将最具体的兼容字符串放在第一位,通用的作为后备。例如 "fsl,imx6q-i2c" 优先于 "fsl,imx21-i2c"。

2.4 reg 属性、#address-cells 与 #size-cells

这三个属性共同定义了设备在总线上的地址空间映射。reg 描述设备的地址范围,而 #address-cells 和 #size-cells 定义了地址和长度的编码格式。

属性 作用 值类型 示例
#address-cells 指定子节点 reg 属性中地址字段占用的 32 位单元数 u32 <1> 表示 32 位地址,<2> 表示 64 位地址
#size-cells 指定子节点 reg 属性中长度字段占用的 32 位单元数 u32 <1> 表示 32 位长度,<0> 表示无长度(仅地址)
reg 描述设备的地址块列表,格式为 (address, length) 对 u32 数组 <0x1000 0x100> 表示基址 0x1000,长度 0x100
/* 32 位地址空间示例 */
/ {
    #address-cells = <1>;
    #size-cells = <1>;

    memory@80000000 {
        reg = <0x80000000 0x40000000>;  /* 基址 0x80000000,大小 1GB */
    };

    gpio@1100 {
        reg = <0x1100 0x100>;           /* 基址 0x1100,大小 256 字节 */
    };
};

/* 64 位地址空间示例 */
/ {
    #address-cells = <2>;
    #size-cells = <2>;

    memory@800000000 {
        reg = <0x0 0x80000000 0x0 0x40000000>;  /* 高32位 0x0,低32位 0x80000000,大小 1GB */
    };
};
核心规则: #address-cells 和 #size-cells 是父节点属性,影响所有子节点的 reg 解析。每个 reg 条目由 #address-cells 个单元表示地址,#size-cells 个单元表示长度。
设备树节点与属性结构图 根节点 / #address-cells = <1> #size-cells = <1> cpu@0 memory@80000000 uart0: serial@101f1000 compatible = "arm,cortex-a9" reg = <0x0 0x100> device_type = "cpu" device_type = "memory" reg = <0x80000000 0x40000000> compatible = "ns16550" reg = <0x101f1000 0x1000> interrupts = <0 12 4> aliases { serial0 = &uart0; } 标签 uart0 通过 &uart0 被引用 图例说明 根节点 子节点 属性 别名/标签

2.5 综合示例:完整节点与属性解析

以下是一个综合示例,展示了本节所有语法元素的协同工作方式:

/dts-v1/;

/ {
    model = "MyBoard v1.0";
    compatible = "mycompany,myboard", "mycompany,myboard-v1";
    #address-cells = <1>;
    #size-cells = <1>;

    aliases {
        eth0 = &enet;
        mmc0 = &sdhci;
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            reg = <0x0>;
            clock-frequency = <1000000000>;
        };

        cpu1: cpu@1 {
            compatible = "arm,cortex-a7";
            reg = <0x1>;
            clock-frequency = <1000000000>;
        };
    };

    enet: ethernet@20000000 {
        compatible = "mycompany,enet";
        reg = <0x20000000 0x10000>;
        interrupts = <0 33 4>;
        phy-mode = "rgmii";
    };

    sdhci: mmc@30000000 {
        compatible = "mycompany,sdhci";
        reg = <0x30000000 0x1000>;
        interrupts = <0 45 4>;
        bus-width = <8>;
    };
};

/* 在板级文件中覆盖属性 */
&enet {
    local-mac-address = [00 11 22 33 44 55];
};

&sdhci {
    status = "okay";
    no-1-8-v;
};
解析要点:
  • 根节点定义了 model 和 compatible 用于板级识别。
  • cpus 节点内部 #address-cells = <1>、#size-cells = <0>,因此 reg 只包含地址,无长度。
  • 标签 cpu0、cpu1、enet、sdhci 允许在文件其他位置通过 &cpu0 等方式引用。
  • 别名 eth0、mmc0 为 bootloader 提供了快速查找接口。
reg 属性与地址空间映射图 父节点(总线) #address-cells = <1> #size-cells = <1> gpio@1100 uart@101f1000 dma@30000000 reg = <0x1100 0x100> 地址: 0x1100 (1 个 cell) 长度: 0x100 (1 个 cell) reg = <0x101f1000 0x1000> 地址: 0x101f1000 (1 个 cell) 长度: 0x1000 (1 个 cell) reg = <0x30000000 0x10000> 地址: 0x30000000 (1 个 cell) 长度: 0x10000 (1 个 cell) 地址空间映射(32位地址总线) 0x1100 0x101f1000 0x30000000 其他外设空间

2.6 常见问题与避坑指南

问题 原因 解决方案
设备无法 probe compatible 字符串与驱动不匹配 检查驱动 of_match_table 中的字符串,确保完全一致(包括大小写)
地址解析错误 #address-cells 或 #size-cells 设置错误 确认父节点中这两个属性的值,确保 reg 的 cell 数量正确
标签引用失败 标签未定义或拼写错误 使用 &label 前确保标签已在某节点定义,且名称唯一
别名不生效 别名未在根节点 aliases 中定义 所有别名必须在 /aliases 节点下声明,且值必须使用 &label 引用
重要提醒: 设备树编译工具 dtc 不会检查 compatible 字符串的语义正确性,也不会验证 reg 地址是否重叠。务必在编译后使用 dtc -I dtb -O dts 反编译检查生成的 DTB 文件,确保语法和逻辑正确。