3. 设备树常用属性详解
设备树(Device Tree)通过节点(node)和属性(property)来描述硬件。属性是设备树的核心数据载体,驱动解析器通过读取这些属性来获取硬件资源信息。本章将深入解析 compatible、reg、interrupts、status、clocks、gpios 等最常用的属性,并结合 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)应放在越前面。
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 = 高电平触发 |
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>;
};
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 |
整数 | 直接指定时钟频率(用于固定频率时钟) |
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 |
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
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 有效电平标志 | 引脚号错误、标志位设置错误 |
of_* 函数族是定位属性解析问题的最高效手段。建议在开发阶段始终开启 ftrace 的 function_graph 追踪器,观察驱动 probe 过程中的属性读取行为。