3. 设备树常用属性详解

设备树(Device Tree)通过节点(node)和属性(property)来描述硬件。属性是设备树的核心数据载体,驱动解析器通过读取这些属性来获取硬件资源信息。本章将深入解析 compatible、reg、interrupts、status、clocks、gpios 等最常用的属性,并结合 ftrace 追踪其在内核中的解析路径。

调试提示: 在嵌入式调试中,设备树属性写错是导致驱动加载失败的最常见原因。使用 ftrace 追踪 of_property_read_* 系列函数,可以快速定位属性读取失败的位置。

3.1 compatible 属性:驱动匹配的“身份证”

compatible 属性是设备树中最重要的属性,它建立了设备节点与内核驱动之间的匹配桥梁。该属性是一个字符串列表,格式通常为 "厂商,设备型号"。

// 设备树节点示例
uart0: serial@10000000 {
    compatible = "fsl,imx6q-uart", "fsl,imx6ul-uart", "ns16550";
    reg = <0x10000000 0x4000>;
    interrupts = <0 58 4>;
};

内核驱动通过 of_match_table 来声明支持的设备列表。当内核解析设备树时,会按照 compatible 字符串的顺序与驱动表中的条目进行匹配,匹配成功则调用驱动的 probe 函数。

匹配优先级规则:
  • 内核优先匹配 compatible 列表中的第一个字符串。
  • 如果第一个字符串没有匹配到驱动,则依次尝试后续字符串。
  • 越具体的兼容性字符串(如 fsl,imx6q-uart)应放在越前面。
compatible 属性驱动匹配流程 设备树节点 compatible 列表 ① "fsl,imx6q-uart" ② "fsl,imx6ul-uart" ③ "ns16550" 内核匹配引擎 驱动 of_match_table ┌─────────────────────┐ │ "fsl,imx6q-uart" │ → probe() │ "fsl,imx6ul-uart" │ → probe() │ "ns16550" │ → probe() │ "snps,dw-apb-uart" │ → probe() │ ... │ 匹配成功 → 调用 probe()

3.2 reg 属性:描述设备地址空间

reg 属性用于描述设备在总线上的地址空间,通常包含一个或多个地址/长度对。其格式由父节点的 #address-cells 和 #size-cells 属性决定。

属性 含义 典型值
#address-cells 地址字段占用的 32 位单元数 1(32位地址)或 2(64位地址)
#size-cells 长度字段占用的 32 位单元数 1 或 0(无长度)
reg 地址和长度的组合列表 <0x10000000 0x4000>
// 不同地址宽度的 reg 示例
// 32位地址,1个地址单元 + 1个长度单元
soc@0 {
    #address-cells = <1>;
    #size-cells = <1>;
    
    uart@10000000 {
        reg = <0x10000000 0x4000>;  // 基址0x10000000,长度0x4000
    };
};

// 64位地址,2个地址单元 + 1个长度单元
memory@80000000 {
    #address-cells = <2>;
    #size-cells = <1>;
    
    region@0 {
        reg = <0x00000000 0x80000000 0x20000000>;  // 高32位+低32位=64位地址,长度0x20000000
    };
};
常见错误: #address-cells 和 #size-cells 不匹配是导致 reg 解析错误的常见原因。例如,父节点声明 #address-cells = <2>,但子节点 reg 只写了 2 个 32 位值(1个地址+1个长度),会导致地址解析错误。

3.3 interrupts 属性:中断资源描述

interrupts 属性描述设备使用的中断资源,其格式由父节点的 #interrupt-cells 属性定义。常见的中断控制器(如 GIC)使用 3 个 cell 来描述中断。

// GIC 中断控制器定义(3 cells)
intc: interrupt-controller@00a01000 {
    compatible = "arm,cortex-a9-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x00a01000 0x1000>;
};

// 使用中断的设备节点
ethernet@2188000 {
    compatible = "fsl,imx6q-fec";
    reg = <0x02188000 0x4000>;
    interrupts = <0 118 4>;  // 类型=0(SPI),中断号=118,触发方式=4(高电平)
};
Cell 位置 GIC 中断格式 说明
Cell 0 中断类型 0 = SPI(共享外设中断),1 = PPI(私有外设中断)
Cell 1 中断号 SPI 范围为 32~1019,PPI 范围为 16~31
Cell 2 触发方式 1 = 上升沿触发,4 = 高电平触发
interrupts 属性解析与映射流程 设备节点 interrupts <0 118 4> 类型 号 触发 中断控制器 (GIC) #interrupt-cells = <3> Cell 0: 类型 → SPI Cell 1: 中断号 → 118 Cell 2: 触发 → 高电平 → 映射到 Linux IRQ 150 驱动中断处理 request_irq(150, handler, ...) devm_request_irq(&pdev->dev, ...)

3.4 status 属性:设备启用控制

status 属性用于控制设备节点的启用状态,是设备树中实现“硬件裁剪”的关键属性。它允许在不修改设备树源文件的情况下,通过覆盖(override)来启用或禁用设备。

status 值 含义 内核行为
"okay" 设备启用 内核会为该设备创建 platform_device 并尝试匹配驱动
"disabled" 设备禁用 内核跳过该设备,不创建 platform_device
"fail" 设备故障 设备存在但标记为不可用,驱动应检查此状态
"reserved" 保留 设备保留给其他用途(如固件)
// 设备树中的 status 使用示例
// 基础板级 dts 中默认禁用
&uart1 {
    status = "disabled";
};

// 具体板级 dtsi 中启用
&uart1 {
    status = "okay";
    pinctrl-0 = <&pinctrl_uart1>;
};
ftrace 追踪点: 内核在 of_platform_device_create_pdata() 函数中会检查 status 属性。使用 ftrace 追踪 of_device_is_available 函数可以观察设备是否被启用。

3.5 clocks 属性:时钟资源绑定

clocks 属性将设备连接到时钟框架中的时钟源,通常配合 clock-names 属性使用。时钟框架是 Linux 内核中管理时钟资源的子系统。

// 时钟属性示例
uart1: serial@02020000 {
    compatible = "fsl,imx6q-uart", "fsl,imx6ul-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <0 26 4>;
    clocks = <&clks IMX6QDL_CLK_UART1_IPG>,
             <&clks IMX6QDL_CLK_UART1_SERIAL>;
    clock-names = "ipg", "serial";
};
属性 格式 说明
clocks <phandle clock-specifier> 时钟 phandle 和时钟 specifier 的列表
clock-names 字符串列表 为每个时钟指定名称,驱动通过名称获取时钟
clock-frequency 整数 直接指定时钟频率(用于固定频率时钟)
clocks 属性与时钟框架绑定 UART1 设备节点 clocks = <&clks UART1_IPG> <&clks UART1_SERIAL> clock-names = "ipg","serial" 时钟控制器 (CCM) #clock-cells = <1> IMX6QDL_CLK_UART1_IPG IMX6QDL_CLK_UART1_SERIAL → 提供时钟频率与使能控制 UART 驱动 clk_get(dev, "ipg") clk_get(dev, "serial") clk_prepare_enable(ipg) clk_prepare_enable(serial)

3.6 gpios 属性:GPIO 资源描述

gpios 属性用于描述设备使用的 GPIO 引脚,通常配合 gpio-names 属性使用。GPIO 子系统是 Linux 内核中管理通用输入输出引脚的框架。

// GPIO 属性示例
leds {
    compatible = "gpio-leds";
    
    led-red {
        label = "red-led";
        gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
        linux,default-trigger = "heartbeat";
    };
    
    led-green {
        label = "green-led";
        gpios = <&gpio2 3 GPIO_ACTIVE_HIGH>;
    };
};

// 带 GPIO 控制的设备
sdhci@2190000 {
    compatible = "fsl,imx6q-usdhc";
    reg = <0x02190000 0x4000>;
    interrupts = <0 22 4>;
    cd-gpios = <&gpio1 4 GPIO_ACTIVE_LOW>;  // 卡检测
    wp-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;  // 写保护
};
GPIO 描述格式 说明
<&gpio_controller pin flags> 标准 GPIO 描述,包含 GPIO 控制器 phandle、引脚号和标志
GPIO_ACTIVE_HIGH (0) 高电平有效
GPIO_ACTIVE_LOW (1) 低电平有效
gpio-names 为每个 GPIO 指定名称,驱动通过名称获取 GPIO
调试技巧: 使用 ftrace 追踪 of_get_named_gpio_flags 函数,可以观察驱动读取 GPIO 属性的过程。如果返回 -EPROBE_DEFER,说明 GPIO 控制器尚未准备好,驱动会延迟探测。

3.7 属性解析的 ftrace 追踪实战

以下示例展示如何使用 ftrace 追踪设备树属性的解析过程,帮助定位属性读取失败的问题。

# 1. 设置 ftrace 追踪 of_property_read 系列函数
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo of_property_read_* > /sys/kernel/debug/tracing/set_ftrace_filter
echo of_get_named_gpio_flags > /sys/kernel/debug/tracing/set_ftrace_filter
echo of_device_is_available > /sys/kernel/debug/tracing/set_ftrace_filter

# 2. 启动追踪
echo 1 > /sys/kernel/debug/tracing/tracing_on

# 3. 触发驱动探测(例如重新绑定驱动)
echo -n "2020000.serial" > /sys/bus/platform/drivers/imx-uart/unbind
echo -n "2020000.serial" > /sys/bus/platform/drivers/imx-uart/bind

# 4. 停止追踪并查看结果
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
ftrace 输出解读示例:
  imx_uart_probe() {
    of_property_read_string() {
      of_get_property();
    }
    of_property_read_u32() {
      of_find_property();
    }
    of_get_named_gpio_flags() {
      of_parse_phandle();
      gpiod_get_from_of_node();
    }
    clk_get() {
      of_parse_phandle();
      __of_clk_get();
    }
  }

通过追踪输出,可以清晰看到驱动在 probe 过程中依次读取了哪些属性,以及每个属性的解析耗时。

3.8 属性使用最佳实践总结

属性 最佳实践 常见错误
compatible 使用"厂商,型号"格式,具体在前通用在后 拼写错误、缺少厂商前缀
reg 确保 #address-cells/#size-cells 与父节点一致 地址/长度单元数不匹配
interrupts 确认中断控制器的 #interrupt-cells 定义 中断号超出范围、触发方式错误
status 基础 dtsi 中默认 disabled,板级 dts 中启用 忘记设置 status = "okay"
clocks 配合 clock-names 使用,驱动通过名称获取 时钟 phandle 错误、名称不匹配
gpios 明确 GPIO 有效电平标志 引脚号错误、标志位设置错误
调试红线: 设备树属性错误通常不会导致内核崩溃,但会导致设备无法正常工作。使用 ftrace 追踪 of_* 函数族是定位属性解析问题的最高效手段。建议在开发阶段始终开启 ftrace 的 function_graph 追踪器,观察驱动 probe 过程中的属性读取行为。