3. 设备树常用属性详解

设备树中的属性(property)是描述硬件设备特征、连接关系和资源需求的核心载体。每个节点通过一组键值对(key-value)向内核传递硬件信息。本章将逐一拆解嵌入式开发中最常用的 7 类属性,包括它们的语法、语义、典型取值和常见陷阱。

学习提示: 设备树属性是内核与硬件之间的“契约”。属性写错,轻则驱动加载失败,重则系统崩溃。建议在阅读本节时,对照实际开发板的手册或 .dts 文件进行验证。

3.1 compatible —— 设备与驱动的匹配钥匙

compatible 是设备树中最重要的属性之一,它指定了设备与内核驱动之间的匹配名称。内核通过遍历设备树节点,将节点的 compatible 值与驱动注册时声明的 of_device_id 表进行比对,从而找到正确的驱动程序。

语法格式:

compatible = "manufacturer,model", "generic-model";

匹配规则:

  • 内核优先使用列表中的第一个字符串进行精确匹配。
  • 如果第一个字符串未匹配成功,则依次尝试后续字符串(回退机制)。
  • 字符串格式通常为 "厂商名,器件型号",确保全局唯一性。
实战案例: 某 I²C 温度传感器节点
temp_sensor: tmp117@48 {
    compatible = "ti,tmp117", "ti,tmp116";
    reg = <0x48>;
};
内核先尝试匹配 ti,tmp117 驱动;若不存在,则回退匹配 ti,tmp116 驱动。这种设计提高了驱动兼容性。
常见错误: 字符串拼写错误(如大小写、连字符)、厂商名与驱动不匹配、遗漏回退型号。这些错误会导致驱动无法加载,且内核日志中通常只显示“No driver found”等模糊信息。
compatible 属性匹配流程 设备树节点 compatible = "ti,tmp117" 内核匹配引擎 遍历 of_device_id 表 匹配? 匹配成功 加载驱动 尝试下一个字符串 回退机制:若第一个字符串未匹配,内核自动尝试列表中的后续字符串,直到匹配成功或列表结束

3.2 reg —— 设备地址空间与片选信息

reg 属性用于描述设备在总线上的地址空间或片选(chip select)信息。它由一个或多个地址/长度对组成,每个对称为一个“region”。对于内存映射设备,reg 指定了基地址和映射长度;对于 I²C/SPI 设备,则指定从设备地址。

语法格式:

reg = <address1 length1 address2 length2 ...>;

不同总线下的含义:

总线类型 address 含义 length 含义 示例
内存映射(AMBA/AXI) 基地址 映射大小(字节) reg = <0x40000000 0x1000>;
I²C 7 位从设备地址 通常为 0(无长度) reg = <0x48>;
SPI 片选编号 通常为 0 reg = <0>;
PCIe Bus/Device/Function 编码 地址空间大小 reg = <0x00000800 0 0 0 0>;
多 region 示例: 一个包含两个内存块的设备
display_controller: display@1e00000 {
    compatible = "vendor,display";
    reg = <0x01e00000 0x100000    /* 控制寄存器块 */
           0x01f00000 0x200000>;  /* 帧缓冲块 */
};
内核通过 platform_get_resource(pdev, IORESOURCE_MEM, 0) 和 platform_get_resource(pdev, IORESOURCE_MEM, 1) 分别获取两个 region。
常见错误: 地址与长度顺序颠倒、地址未对齐(如 4 字节对齐)、I²C 地址未左移 1 位(某些内核版本要求 8 位地址格式)。务必查阅具体总线的 binding 文档。

3.3 interrupts —— 中断信号描述

interrupts 属性描述设备产生的中断信号,包括中断控制器、中断号和触发类型。它通常与 interrupt-parent 配合使用,后者指定设备所连接的中断控制器节点。

语法格式:

interrupt-parent = <&gic>;
interrupts = <0 89 4>;

中断描述符字段含义(以 GICv2 为例):

字段位置 含义 典型值
字段 0 中断类型:0=SPI(共享外设中断),1=PPI(私有外设中断) 0
字段 1 中断号(SPI 范围为 32~1019) 89
字段 2 触发类型与极性:1=上升沿触发,2=下降沿触发,4=高电平触发,8=低电平触发 4
多中断场景: 一个设备可能产生多个中断,例如 UART 的 TX 中断和 RX 中断。此时 interrupts 可以包含多个描述符:
interrupts = <0 33 4>, <0 34 4>;
驱动通过 platform_get_irq(pdev, index) 按索引获取每个中断号。
中断信号路由架构 UART 外设 interrupts = <0 33 4> IRQ 33 GIC-400 中断控制器 interrupt-parent CPU 内核 IRQ 处理 中断路由流程:外设产生中断 → 通过中断线发送到 GIC → GIC 仲裁后转发给 CPU interrupt-parent 指定 GIC 节点,interrupts 指定中断号与触发类型

3.4 status —— 设备启用状态控制

status 属性用于控制设备节点的启用或禁用状态。它允许在不修改硬件描述的情况下,通过设备树动态启用或禁用某个外设。这在多板型复用同一 .dtsi 文件时非常有用。

可选值:

值 含义 说明
okay 设备启用 内核将正常探测并初始化该设备
disabled 设备禁用 内核跳过该节点,不进行探测
reserved 保留状态 通常用于特殊场景,如固件保留
fail 设备故障 表示设备检测失败,内核不加载驱动
实战用法: 在 .dtsi 中默认禁用,在 .dts 中按需启用
// 在 SoC 级 .dtsi 中:
&uart2 {
    status = "disabled";
};

// 在板级 .dts 中:
&uart2 {
    status = "okay";
    pinctrl-0 = <&uart2_pins>;
};
这种模式避免了每个板型都重复定义完整节点,只需覆盖 status 和少量差异属性。
常见错误: 拼写错误(如 ok 而非 okay)、大小写不一致(内核要求全小写)、忘记在 .dts 中覆盖 status 导致设备未启用。调试时可检查 /sys/devices/platform/ 下是否存在对应设备目录。

3.5 clocks —— 时钟资源绑定

clocks 属性描述设备所需的时钟源,包括时钟控制器、时钟 ID 和时钟标志。它通常与 clock-names 配合使用,后者为每个时钟提供语义化名称,方便驱动通过名称获取时钟句柄。

语法格式:

clocks = <&clk_ctrl CLK_ID1>, <&clk_ctrl CLK_ID2>;
clock-names = "bus", "core";

时钟标志位(可选):

标志宏 值 含义
CLK_SET_RATE_PARENT 1 允许子时钟改变父时钟频率
CLK_IGNORE_UNUSED 2 即使未使用也不关闭该时钟
CLK_IS_CRITICAL 4 标记为关键时钟,不可关闭
驱动获取时钟示例:
struct clk *bus_clk, *core_clk;

bus_clk = devm_clk_get(dev, "bus");
core_clk = devm_clk_get(dev, "core");

clk_prepare_enable(bus_clk);
clk_prepare_enable(core_clk);
使用 clock-names 使代码更具可读性,避免依赖时钟索引。

3.6 gpios —— GPIO 资源描述

gpios 属性用于描述设备使用的 GPIO 引脚,包括 GPIO 控制器、引脚编号和 GPIO 标志。与时钟类似,gpio-names 可以为每个 GPIO 提供语义化名称。

语法格式:

gpios = <&gpio1 15 GPIO_ACTIVE_HIGH>,
        <&gpio2 7  GPIO_ACTIVE_LOW>;
gpio-names = "reset", "irq";

GPIO 标志位:

标志宏 值 含义
GPIO_ACTIVE_HIGH 0 高电平有效
GPIO_ACTIVE_LOW 1 低电平有效
GPIO_OPEN_DRAIN 2 开漏输出
GPIO_OPEN_SOURCE 4 开源输出
完整示例: 一个带复位和中断的按键设备
keypad: keypad@0 {
    compatible = "vendor,keypad";
    gpios = <&gpio1 15 GPIO_ACTIVE_LOW>,  /* 复位引脚 */
            <&gpio2 7  GPIO_ACTIVE_HIGH>; /* 中断引脚 */
    gpio-names = "reset", "irq";
    interrupt-parent = <&gpio2>;
    interrupts = <7 IRQ_TYPE_EDGE_RISING>;
};

3.7 属性综合对比与选择指南

以下表格总结了 7 类核心属性的用途、数据类型和典型场景,方便在实际开发中快速查阅:

属性名 用途 数据类型 典型场景
compatible 驱动匹配 字符串列表 所有设备节点
reg 地址空间描述 u32/u64 数组 内存映射、I²C、SPI 设备
interrupts 中断信号描述 u32 数组 产生中断的外设
status 启用/禁用控制 字符串 多板型复用场景
clocks 时钟源绑定 phandle + u32 需要时钟的外设
gpios GPIO 引脚描述 phandle + u32 + flags 使用 GPIO 的设备
pinctrl 引脚复用配置 phandle 列表 引脚功能选择
调试建议: 当设备驱动加载异常时,按以下顺序排查属性:
  1. 检查 compatible 是否与驱动匹配(查看 /sys/bus/platform/drivers/)。
  2. 检查 status 是否为 okay。
  3. 检查 reg 地址是否与其他设备重叠(查看 /proc/iomem)。
  4. 检查 interrupts 中断号是否冲突(查看 /proc/interrupts)。
  5. 检查 clocks 和 gpios 的 phandle 是否正确指向存在的节点。
设备树属性与内核子系统关系 设备树节点 硬件描述容器 compatible 驱动匹配 reg 地址空间 interrupts 中断信号 clocks 时钟源 gpios GPIO 引脚 status 启用控制 pinctrl 引脚复用 每个属性对应内核中的一个子系统:驱动模型、内存管理、中断子系统、时钟框架、GPIO 子系统、引脚控制子系统

3.8 属性编写最佳实践

基于多年嵌入式开发经验,总结以下属性编写原则:

  • 完整性原则: 每个设备节点至少应包含 compatible 和 reg(或 interrupts)属性,确保内核能够识别和访问设备。
  • 层次化原则: 将 SoC 级通用描述放在 .dtsi 中,板级差异(如 status、pinctrl)放在 .dts 中覆盖。
  • 命名规范原则: compatible 使用 "厂商,型号" 格式;clock-names 和 gpio-names 使用有意义的英文单词。
  • 最小化原则: 只添加驱动实际需要的属性,避免冗余描述导致解析错误。
  • 验证原则: 每次修改设备树后,使用 dtc -I dtb -O dts 反编译检查,并使用 dt-validate 工具进行 schema 校验。
完整节点示例: 一个符合最佳实践的 SPI 设备
spi@ff110000 {
    compatible = "vendor,spi-controller";
    reg = <0xff110000 0x1000>;
    interrupts = <0 56 4>;
    clocks = <&clk_ctrl SPI_CLK>;
    clock-names = "spi_clk";
    pinctrl-0 = <&spi_pins>;
    pinctrl-names = "default";
    status = "okay";

    adc@0 {
        compatible = "ti,ads1256";
        reg = <0>;  /* CS0 */
        spi-max-frequency = <10000000>;
        gpios = <&gpio3 12 GPIO_ACTIVE_LOW>;
        gpio-names = "drdy";
    };
};