3. 设备树常用属性详解:compatible、reg、interrupts、clocks、gpios、status 等核心属性的含义与用法
设备树(Device Tree)中的属性是描述硬件配置与连接关系的基本单元。每个节点通过一组标准属性向内核驱动传递硬件特征信息。本章将逐一剖析最常用的六大核心属性,包括其语法规范、绑定约定及驱动接口中的读取方式。
3.1 compatible 属性:设备与驱动的匹配钥匙
compatible 是设备树中最关键的属性,它建立了硬件设备与内核驱动之间的绑定关系。该属性是一个字符串列表,用于标识设备的具体型号和兼容的驱动。
3.1.1 语法与规范
compatible = "manufacturer,model", "generic,driver";
- 第一优先:精确的厂商与型号,如
"ti,am335x-uart"。 - 后续备选:更通用的兼容驱动,如
"ns16550a"。 - 内核匹配时按列表顺序查找驱动,找到即停止。
3.1.2 驱动匹配示例
/* 设备树节点 */
uart0: serial@44e09000 {
compatible = "ti,am335x-uart", "ns16550a";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
/* 驱动端匹配表 */
static const struct of_device_id my_uart_of_match[] = {
{ .compatible = "ti,am335x-uart", .data = &am335x_data },
{ .compatible = "ns16550a", .data = &ns16550_data },
{ /* end */ }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);
调试技巧:使用
cat /sys/firmware/devicetree/base/.../compatible 可查看实际生效的兼容字符串列表。驱动 probe 失败时,优先检查 compatible 字符串是否完全匹配(包括大小写和标点)。
3.2 reg 属性:描述设备地址空间
reg 属性用于描述设备在总线上的地址范围,通常包含起始地址和长度。其格式由父节点的 #address-cells 和 #size-cells 决定。
3.2.1 地址单元与大小单元
| 属性 | 含义 | 典型值 |
|---|---|---|
#address-cells |
地址字段占用的 32 位单元数 | 1(32位地址)或 2(64位地址) |
#size-cells |
长度字段占用的 32 位单元数 | 1(32位长度)或 0(无长度) |
3.2.2 典型用法
/* 32位地址空间 */
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 起始0x80000000,长度512MB */
};
/* 带片选的外设 */
spi@e0006000 {
compatible = "xlnx,zynq-spi";
reg = <0xe0006000 0x1000>; /* 寄存器基址+范围 */
interrupts = <0 26 4>;
};
3.2.3 驱动读取接口
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
3.3 interrupts 属性:中断连接描述
interrupts 属性描述设备产生的中断信号,需要配合 interrupt-parent 和中断控制器节点使用。
3.3.1 中断属性结构
interrupts = <中断类型 中断号 触发方式>;
interrupt-parent = <&gic>; /* 引用中断控制器 */
| 字段 | 说明 | 常见值 |
|---|---|---|
| 中断类型 | SPI(共享外设中断)或 PPI(私有外设中断) | 0 (SPI), 1 (PPI) |
| 中断号 | 中断控制器内部的编号 | 0~987 |
| 触发方式 | 电平或边沿触发 | 1 (上升沿), 4 (高电平) |
3.3.2 完整示例
gic: interrupt-controller@f8f01000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xf8f01000 0x1000>;
};
uart1: serial@e0001000 {
compatible = "xlnx,xuartps";
reg = <0xe0001000 0x1000>;
interrupts = <0 50 4>; /* SPI, 中断号50, 高电平触发 */
interrupt-parent = <&gic>;
};
常见错误:中断号在设备树中通常从 0 开始编号,但某些架构(如 ARM GIC)的 SPI 中断号在驱动中会加上 32 的偏移。调试时使用
cat /proc/interrupts 对比实际注册的中断号。
3.4 clocks 属性:时钟源绑定
clocks 属性将设备连接到时钟框架中的时钟源,配合 clock-names 实现多时钟管理。
3.4.1 标准格式
clocks = <&clkc 15>, <&clkc 16>;
clock-names = "uart_clk", "pclk";
- clocks:引用时钟控制器节点及输出索引。
- clock-names:为每个时钟指定名称,驱动通过名称获取句柄。
3.4.2 驱动获取时钟
struct clk *clk_uart, *clk_p;
clk_uart = devm_clk_get(&pdev->dev, "uart_clk");
if (IS_ERR(clk_uart))
return PTR_ERR(clk_uart);
clk_prepare_enable(clk_uart);
3.5 gpios 属性:GPIO 引脚描述
gpios 属性用于描述设备使用的 GPIO 引脚,包括 GPIO 控制器引用、引脚号和标志位。
3.5.1 属性格式
gpios = <&gpio0 12 GPIO_ACTIVE_LOW>;
/* 或使用命名方式 */
reset-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
enable-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>;
| 字段 | 说明 |
|---|---|
&gpio0 |
GPIO 控制器 phandle |
12 |
控制器内部的引脚编号 |
GPIO_ACTIVE_LOW |
标志位:低电平有效(值为 1) |
3.5.2 驱动使用示例
struct gpio_desc *reset_gpio;
reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(reset_gpio))
return PTR_ERR(reset_gpio);
gpiod_set_value(reset_gpio, 0); /* 拉低复位 */
msleep(10);
gpiod_set_value(reset_gpio, 1); /* 释放复位 */
3.6 status 属性:设备启用控制
status 属性控制设备节点的启用状态,是板级配置中常用的开关属性。
3.6.1 可选值
| 值 | 含义 | 说明 |
|---|---|---|
"okay" |
设备启用 | 驱动正常 probe |
"disabled" |
设备禁用 | 驱动不会 probe |
"reserved" |
保留状态 | 特殊用途,通常不用于普通外设 |
"fail" |
设备故障 | 硬件检测失败时使用 |
3.6.2 典型应用场景
/* SoC dtsi 中默认禁用 */
uart2: serial@e0002000 {
compatible = "xlnx,xuartps";
reg = <0xe0002000 0x1000>;
interrupts = <0 51 4>;
status = "disabled";
};
/* 板级 dts 中启用 */
&uart2 {
status = "okay";
clocks = <&clkc 17>, <&clkc 18>;
clock-names = "uart_clk", "pclk";
};
调试要点:
status = "disabled" 的设备不会出现在 /sys/devices/platform/ 中。如果驱动未加载,首先检查设备树中 status 是否为 "okay",以及是否存在覆盖(override)冲突。
3.7 核心属性关系总览
3.8 属性调试速查表
| 属性 | 驱动读取接口 | 调试文件路径 | 常见问题 |
|---|---|---|---|
compatible |
of_match_device() |
/sys/firmware/devicetree/base/.../compatible |
字符串拼写错误、大小写不匹配 |
reg |
platform_get_resource() |
/proc/iomem |
地址重叠、#address-cells 配置错误 |
interrupts |
platform_get_irq() |
/proc/interrupts |
中断号偏移、interrupt-parent 缺失 |
clocks |
devm_clk_get() |
/sys/kernel/debug/clk/clk_summary |
时钟名不匹配、时钟未使能 |
gpios |
devm_gpiod_get() |
/sys/kernel/debug/gpio |
引脚号错误、标志位配置反相 |
status |
of_device_is_available() |
/sys/devices/platform/ 目录存在性 |
覆盖冲突、拼写非 "okay" |
3.9 综合示例:完整设备节点与驱动匹配
/* 设备树节点 */
ethernet@e000b000 {
compatible = "xlnx,zynq-gem", "cdns,gem";
reg = <0xe000b000 0x1000>;
interrupts = <0 56 4>;
interrupt-parent = <&gic>;
clocks = <&clkc 30>, <&clkc 31>, <&clkc 29>;
clock-names = "pclk", "hclk", "tx_clk";
phy-mode = "rgmii-id";
status = "okay";
phy-handle = <&phy0>;
mdio {
#address-cells = <1>;
#size-cells = <0>;
phy0: ethernet-phy@7 {
reg = <7>;
reset-gpios = <&gpio0 89 GPIO_ACTIVE_LOW>;
};
};
};
/* 驱动 probe 函数片段 */
static int gem_probe(struct platform_device *pdev)
{
struct resource *res;
struct clk *pclk, *hclk;
int irq;
/* 匹配 compatible */
if (!of_match_node(gem_of_match, pdev->dev.of_node))
return -ENODEV;
/* 获取 reg 地址 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
/* 获取中断 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 获取时钟 */
pclk = devm_clk_get(&pdev->dev, "pclk");
hclk = devm_clk_get(&pdev->dev, "hclk");
if (IS_ERR(pclk) || IS_ERR(hclk))
return -EPROBE_DEFER;
/* 检查 status */
if (!of_device_is_available(pdev->dev.of_node))
return -ENODEV;
/* 获取 PHY 复位 GPIO */
phy_reset = devm_gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_HIGH);
/* 继续初始化... */
return 0;
}
最佳实践:编写驱动时,始终使用
devm_* 系列接口获取资源,它们会在驱动卸载时自动释放。对于可选属性(如某些 GPIO),使用 _optional 变体避免强制要求。