4、设备树节点与属性详解:标准节点与常用属性的语义与用法

设备树(Device Tree)本质上是一种描述硬件资源与系统拓扑的树形数据结构,其核心价值在于将硬件配置信息从内核代码中剥离出来,由Bootloader在启动时传递给操作系统。要真正掌握设备树编写,必须透彻理解两类基本元素:节点(Node)与属性(Property)。节点用于描述硬件对象(如CPU、内存控制器、I2C控制器、外设等),属性则用于描述该对象的具体特征(如寄存器地址、中断号、状态标志等)。本章将围绕标准节点的语义约定和常用属性的解析规则展开,帮助读者建立从“看树”到“写树”的完整认知框架。

根节点 "/" cpus memory chosen soc aliases 常用属性 • compatible • reg / #address-cells • interrupts • status • label / phandle 标准节点语义 • cpus: 描述处理器核心 • memory: 内存布局 • chosen: 启动参数 • soc: 片上外设总线 • aliases: 设备别名 解析规则 • 节点路径唯一 • 属性名小写 • reg 地址/长度 • 中断三要素 • status 开关 Bootloader 解析DTB 生成platform设备 设备树从描述到驱动的完整链路

上图展示了设备树的核心结构:根节点下挂载标准子节点,每个节点通过属性描述硬件特征,最终由Bootloader传递给内核解析。下面我们将逐一拆解标准节点的语义约定与常用属性的编码规则。

4.1 标准节点:根节点、cpus、memory、chosen

设备树的根节点是整棵树的起点,其路径固定为“/”,所有其他节点均为其子节点或后代节点。根节点通常不描述具体硬件,而是定义全局属性,如#address-cells、#size-cells、model、compatible等。其中model用于标识板卡型号(如“ACME, Coyote-Reach”),compatible则用于内核匹配板级驱动。

核心原理:根节点的#address-cells和#size-cells决定了其子节点中reg属性的地址字段和长度字段的位数(32位或64位)。例如,若根节点声明#address-cells = <1>,则子节点的reg中每个地址占1个32位单元;若声明#address-cells = <2>,则地址占2个32位单元(即64位地址)。

4.1.1 cpus节点:处理器拓扑描述

cpus节点是根节点的直接子节点,用于描述系统中所有处理器核心。其下通常包含一个或多个cpu子节点,每个子节点对应一个逻辑CPU。关键属性包括:

  • device_type = "cpu":固定值,标识节点类型为CPU。
  • reg:CPU编号(从0开始),用于区分多核。
  • clock-frequency:CPU主频(单位Hz),如<1200000000>表示1.2GHz。
  • enable-method:CPU启动方式,如"psci"或"spin-table"。
  • next-level-cache:指向二级缓存节点的phandle。
cpus {
    #address-cells = <1>;
    #size-cells = <0>;
    cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a53";
        reg = <0x0>;
        enable-method = "psci";
        clock-frequency = <1200000000>;
    };
    cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a53";
        reg = <0x1>;
        enable-method = "psci";
        clock-frequency = <1200000000>;
    };
};

注意:#size-cells = <0>表示reg属性中不包含长度字段,因为CPU编号不需要长度信息。

4.1.2 memory节点:内存布局描述

memory节点用于描述物理内存的起始地址和大小。其device_type固定为"memory",reg属性由地址和长度对组成。对于多段不连续内存,可定义多个memory子节点或在一个reg中列出多组地址-长度对。

memory@40000000 {
    device_type = "memory";
    reg = <0x0 0x40000000 0x0 0x80000000>;  /* 起始地址0x40000000,大小2GB */
};

这里使用了#address-cells = <2>和#size-cells = <2>,因此地址和长度各占2个32位单元,支持64位寻址。

4.1.3 chosen节点:启动参数传递

chosen节点不描述硬件,而是传递Bootloader到内核的运行时信息。常见属性包括:

  • bootargs:内核启动命令行参数,如"console=ttyS0,115200 root=/dev/mmcblk0p2 rw"。
  • stdout-path:标准输出设备路径,如"&uart0"或"/soc/serial@10000000"。
  • initrd-start / initrd-end:初始RAM磁盘的物理地址范围。
chosen {
    bootargs = "console=ttyS0,115200n8 root=/dev/mmcblk0p2 rw rootwait";
    stdout-path = &uart0;
};

经验提醒:chosen节点中的bootargs优先级高于内核配置中的CONFIG_CMDLINE。若Bootloader未设置bootargs,内核将使用默认命令行。调试串口参数务必在此处确认。

4.2 常用属性:compatible、reg、interrupts、status

属性是设备树中描述硬件特征的核心载体。掌握属性的编码规则是编写设备树的基础。以下逐一解析最常用的四个属性。

4.2.1 compatible属性:设备驱动匹配的关键

compatible属性是一个字符串列表,用于指定设备与驱动之间的匹配关系。其格式为"厂商,型号",内核通过该字符串在驱动列表中查找匹配项。匹配顺序遵循“最具体优先”原则,即列表中的第一个字符串优先匹配。

i2c@10000000 {
    compatible = "fsl,imx6q-i2c", "fsl,imx21-i2c";
    reg = <0x10000000 0x100>;
    interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>;
    status = "okay";
};

上述示例中,内核首先尝试匹配"fsl,imx6q-i2c",若失败则尝试"fsl,imx21-i2c"。这种设计允许同一驱动兼容多个硬件版本。

匹配场景compatible值驱动匹配结果
精确匹配"ti,am335x-uart"直接匹配对应驱动
兼容匹配"ti,am335x-uart", "ti,omap3-uart"优先匹配前者,失败后匹配后者
通配匹配"simple-bus"匹配总线驱动,不匹配具体设备

4.2.2 reg属性:地址与长度编码

reg属性描述设备在总线上的地址空间,由一组或多组“地址+长度”对组成。其编码格式由父节点的#address-cells和#size-cells决定。地址和长度均以32位单元为单位。

/* 父节点定义 */
soc {
    #address-cells = <1>;
    #size-cells = <1>;
    uart@10000000 {
        reg = <0x10000000 0x1000>;  /* 起始地址0x10000000,长度0x1000 */
    };
    ethernet@20000000 {
        reg = <0x20000000 0x1000>, <0x30000000 0x100>;  /* 两段地址空间 */
    };
};

对于#address-cells = <2>的情况,地址字段占2个32位单元,常用于64位地址总线。例如:

reg = <0x0 0x40000000 0x0 0x80000000>;  /* 地址0x40000000,长度0x80000000 */

4.2.3 interrupts属性:中断描述三要素

interrupts属性描述设备的中断请求线。其编码格式依赖于中断控制器(interrupt-parent)的#interrupt-cells属性。常见的中断控制器(如GIC)使用3个cell:

  • 第一个cell:中断类型(0=SPI共享外设中断,1=PPI私有外设中断)。
  • 第二个cell:中断号(SPI从32开始,PPI从16开始)。
  • 第三个cell:触发类型(1=上升沿,2=下降沿,4=高电平,8=低电平)。
uart@10000000 {
    interrupt-parent = <&gic>;
    interrupts = <0 56 4>;  /* SPI中断56,高电平触发 */
};

若设备没有显式指定interrupt-parent,则继承父节点的中断父节点。根节点通常定义interrupt-parent = <&gic>作为全局默认。

4.2.4 status属性:设备状态控制

status属性用于控制设备是否启用。可选值包括:

  • "okay":设备可用,驱动将正常加载。
  • "disabled":设备禁用,驱动不会加载。
  • "reserved":设备保留,不用于操作系统。
  • "fail":设备检测失败,驱动不会加载。

该属性常用于板级差异配置。例如,同一SoC在不同板卡上可能启用或禁用某个外设:

&uart1 {
    status = "okay";  /* 板卡A启用UART1 */
};
&uart2 {
    status = "disabled";  /* 板卡A禁用UART2 */
};

风险提示:若status属性缺失,内核默认认为设备为“okay”。但在某些框架(如PCIe)中,缺失status可能导致设备枚举异常。建议显式声明status属性,避免歧义。

4.3 实战案例:从零编写一个I2C设备节点

假设我们需要在基于i.MX6Q的板卡上添加一个I2C温度传感器(型号LM75),挂载在I2C2总线上,地址为0x48。以下是完整步骤:

  1. 确认I2C控制器节点:在SoC的i2c@21a4000节点下添加子节点。
  2. 设置compatible:使用"national,lm75"匹配LM75驱动。
  3. 配置reg:I2C设备地址为0x48,因此reg = <0x48>。
  4. 设置status:确保为"okay"。
&i2c2 {
    clock-frequency = <100000>;  /* 100kHz标准模式 */
    status = "okay";
    lm75@48 {
        compatible = "national,lm75";
        reg = <0x48>;
        status = "okay";
    };
};

编译设备树后,内核启动时会在/sys/bus/i2c/devices/2-0048下创建设备节点,驱动通过i2c_get_clientdata访问传感器数据。

案例复盘:该案例中,clock-frequency属性用于配置I2C总线速率,但该属性属于I2C控制器节点而非设备节点。初学者常将设备属性与控制器属性混淆,导致编译错误或运行时异常。务必区分节点层级。

4.4 常见误区与总结复盘

设备树编写中,以下误区最为常见:

误区后果正确做法
忘记设置#address-cells和#size-cellsreg解析错误,设备无法访问在父节点显式声明
compatible字符串拼写错误驱动无法匹配,设备不工作与驱动源码中的MODULE_DEVICE_TABLE核对
中断号超出范围中断无法触发,设备挂死查阅SoC手册确认中断号
status误设为"disabled"设备被静默跳过确认板级配置需求
reg地址与硬件实际不符访问越界或数据错误对照原理图与SoC数据手册

总结:标准节点提供了设备树的骨架,常用属性则填充了硬件细节。编写设备树时,应遵循“先结构后属性”的顺序:先确定节点层级,再逐层添加属性。同时,务必保持与SoC数据手册、板级原理图的一致性。建议在开发初期使用dtc -I dtb -O dts反编译已验证的设备树,学习其规范写法。