3. 设备树基础:设备树语法、节点属性、reg/interrupts/compatible属性详解
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用节点(node)和属性(property)来描述系统中的设备、总线、中断控制器、内存映射等硬件信息。在嵌入式 Linux 系统中,设备树被传递给内核,内核通过解析设备树来发现硬件并加载对应的驱动程序。本章将深入讲解设备树的语法、节点结构以及三个核心属性:compatible、reg 和 interrupts。
compatible、reg 和 interrupts 属性。
3.1 设备树语法与节点结构
设备树源文件(.dts)采用树形结构,每个硬件设备或总线对应一个节点。节点可以包含子节点,形成层级关系。每个节点由节点名、节点路径和若干属性组成。
3.1.1 基本语法规则
- 节点定义:
node-name@unit-address { ... };,其中unit-address通常为设备基地址。 - 属性赋值:
property-name = <value>;或property-name = "string";。 - 标签(label):用于引用节点,如
uart0: serial@10000000 { ... };。 - 包含文件:使用
#include引入 .dtsi 头文件,实现复用。 - 注释:支持 C 风格
/* */和 C++ 风格//注释。
3.1.2 节点路径与别名
每个节点都有一个从根节点开始的绝对路径,例如 /soc/serial@10000000。根节点用 / 表示。别名(aliases)节点用于为常用设备分配简短名称,方便引用。
/ {
aliases {
serial0 = &uart0;
};
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
};
};
@unit-address 部分是可选的,但当同一层级存在多个同类型设备时,必须通过地址区分。标签(如 uart0:)在编译时会被替换为节点路径,常用于驱动中引用节点。
3.1.3 设备树层级结构示例
下图展示了一个典型的 ARM SoC 设备树层级结构,包含根节点、CPU 节点、内存节点、中断控制器节点以及外设节点。
3.2 核心属性详解
设备树中每个节点都包含若干属性,用于描述设备的硬件特性。其中 compatible、reg 和 interrupts 是最核心的三个属性,它们直接决定了内核能否正确匹配驱动、访问硬件寄存器以及处理中断。
3.2.1 compatible 属性:驱动匹配的关键
compatible 属性是一个字符串列表,用于指定设备与驱动之间的匹配关系。内核通过设备树中的 compatible 值与驱动中 of_match_table 表进行比对,找到对应的驱动程序。
| 属性 | 格式 | 示例 | 说明 |
|---|---|---|---|
compatible |
"vendor,device" |
"ti,am3359-uart" |
厂商前缀 + 设备型号,推荐使用反向域名风格 |
| 匹配优先级 | 列表顺序 | "ti,am3359-uart", "ns16550" |
内核优先匹配第一个字符串,失败后尝试后续 |
| 通配符 | 无 | — | 不支持通配符,必须精确匹配 |
// 设备树节点
uart0: serial@10000000 {
compatible = "ti,am3359-uart", "ns16550";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
};
// 内核驱动匹配表
static const struct of_device_id uart_of_match[] = {
{ .compatible = "ti,am3359-uart", .data = &am3359_data },
{ .compatible = "ns16550", .data = &ns16550_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);
"厂商,设备型号" 格式,并参考芯片厂商提供的设备树绑定文档。
3.2.2 reg 属性:描述地址空间
reg 属性用于描述设备在系统总线上的地址范围,通常包含一个或多个地址/长度对。内核通过 reg 属性获取设备的基地址和内存区域大小,并调用 ioremap() 映射到内核虚拟地址空间。
| 属性 | 格式 | 示例 | 说明 |
|---|---|---|---|
reg |
<address length> |
<0x10000000 0x1000> |
基地址 + 地址长度,支持多组 |
#address-cells |
整数 | 1 或 2 |
父节点定义,表示地址字段占用的 32 位单元数 |
#size-cells |
整数 | 1 或 2 |
父节点定义,表示长度字段占用的 32 位单元数 |
// 父节点定义地址格式
soc {
#address-cells = <1>; // 地址用 1 个 32 位单元表示
#size-cells = <1>; // 长度用 1 个 32 位单元表示
uart0: serial@10000000 {
reg = <0x10000000 0x1000>; // 基地址 0x10000000,长度 0x1000
};
uart1: serial@10001000 {
reg = <0x10001000 0x1000>;
};
};
// 内核驱动中读取 reg 属性
static int uart_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
// 使用 base 访问硬件寄存器
return 0;
}
3.2.3 interrupts 属性:中断资源描述
interrupts 属性用于描述设备使用的中断资源,包括中断号、触发类型等。中断控制器节点通过 #interrupt-cells 属性定义每个中断描述需要多少个 32 位单元。设备节点通过 interrupt-parent 属性指定所属的中断控制器。
| 属性 | 格式 | 示例 | 说明 |
|---|---|---|---|
interrupts |
<中断描述> |
<0 33 4> |
内容由中断控制器定义 |
interrupt-parent |
&phandle |
&gic |
引用中断控制器节点 |
#interrupt-cells |
整数 | 3 |
中断控制器节点定义,表示每个中断描述占用的单元数 |
// 中断控制器节点(GIC)
gic: interrupt-controller@1c800000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>; // 每个中断用 3 个单元描述
interrupt-controller; // 标记该节点为中断控制器
reg = <0x1c800000 0x1000>;
};
// 外设节点引用中断
uart0: serial@10000000 {
compatible = "ti,am3359-uart";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>; // 指定中断控制器
interrupts = <0 33 4>; // 中断类型 0(SPI),中断号 33,触发类型 4(高电平触发)
};
// 内核驱动中获取中断号
static int uart_probe(struct platform_device *pdev)
{
int irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq, uart_isr, 0, "uart", dev);
if (ret)
return ret;
return 0;
}
3.3 属性组合与中断映射关系
在实际设备树中,compatible、reg 和 interrupts 三个属性通常同时出现,共同描述一个完整的硬件设备。中断映射关系通过 interrupt-parent 和 interrupts 属性建立,内核在解析设备树时会自动构建中断域(irq domain)映射表。
下图展示了设备节点、中断控制器与内核驱动之间的数据流关系:
3.4 常见问题与调试技巧
- 使用
dtc -I dtb -O dts反编译 DTB 文件,检查设备树内容是否正确。 - 内核启动时添加
earlyprintk和of_debug参数,查看设备树解析日志。 - 在驱动中使用
of_property_read_u32()等 API 手动读取属性值,验证解析结果。 - 检查
/proc/device-tree/目录下的文件,确认设备树节点是否被正确导出。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 驱动 probe 函数未调用 | compatible 不匹配 | 核对设备树与驱动中的 compatible 字符串 |
| ioremap 返回 NULL | reg 地址或长度错误 | 检查 #address-cells 和 #size-cells 配置 |
| 中断无法触发 | interrupts 描述错误 | 确认中断号、触发类型与硬件手册一致 |
| 中断号冲突 | interrupt-parent 引用错误 | 确保 phandle 指向正确的中断控制器 |
3.5 本章小结
设备树是嵌入式 Linux 系统中硬件描述的核心机制。本章详细讲解了设备树的语法结构、节点定义方式以及 compatible、reg、interrupts 三个核心属性的使用方法和内核匹配流程。掌握这些基础知识后,你将能够阅读和编写设备树文件,为后续的中断控制器驱动开发和调试打下坚实基础。