2. 设备树基础回顾:语法、节点与标准属性
设备树(Device Tree,DT)是嵌入式 Linux 中用于描述硬件拓扑结构的数据结构,它替代了传统板级文件(board-*.c)中硬编码的硬件信息。内核通过解析设备树二进制(DTB)来发现设备、匹配驱动并配置资源。本节将系统回顾设备树的语法规则、节点与属性结构,以及三个最核心的标准属性:compatible、reg 和 interrupts。
2.1 设备树语法基础
设备树源文件(.dts)采用树形结构,以 /dts-v1/; 开头,根节点用 / 表示,内部包含若干子节点,每个节点描述一个硬件设备或总线。基本语法规则如下:
- 节点定义:
node-name@unit-address,其中unit-address通常为设备基地址。 - 属性赋值:
property-name = <value>;或property-name = "string";。 - 标签引用:使用
&label引用已定义的节点,用于追加或覆盖属性。 - 包含文件:
#include "soc.dtsi"引入公共 SoC 定义。
// 设备树源文件示例:描述一个 UART 控制器
/dts-v1/;
/ {
model = "Embedded Dev Board v2.0";
compatible = "vendor,dev-board";
soc {
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
status = "okay";
};
};
};
#address-cells和#size-cells定义子节点中reg属性的地址和长度字段宽度。status属性控制设备启用("okay")或禁用("disabled")。- 节点名
serial@10000000中的@10000000仅用于区分同名节点,不强制要求与reg一致。
2.2 节点与属性结构
设备树的每个节点可以包含子节点和属性。属性是键值对,值可以是以下类型之一:
| 属性值类型 | 语法示例 | 说明 |
|---|---|---|
| 空值 | property; |
仅表示存在该属性,无值 |
| 32 位整数数组 | reg = <0x1000 0x2000>; |
用尖括号包裹,空格分隔 |
| 64 位整数数组 | reg = <0x00000000 0x10000000>; |
两个 32 位单元表示一个 64 位值 |
| 字符串 | compatible = "vendor,device"; |
双引号包裹 |
| 字符串列表 | compatible = "vendor,device", "generic,device"; |
逗号分隔多个字符串 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹十六进制字节 |
| 混合结构 | gpios = <&gpio1 6 0>; |
可包含 phandle 和整数 |
节点之间的层级关系通过缩进体现,根节点 / 下通常包含 cpus、memory、chosen、soc 等顶层节点。
2.3 标准属性详解
设备树规范定义了一系列标准属性,用于统一描述硬件资源。其中 compatible、reg 和 interrupts 是驱动匹配与资源获取的三大支柱。
2.3.1 compatible 属性——驱动匹配的钥匙
compatible 属性是一个字符串列表,用于将设备节点与内核驱动绑定。内核通过 of_match_table 中的 compatible 字符串与设备树节点进行匹配。匹配顺序遵循“最具体优先”原则:
// 设备树节点
compatible = "vendor,uart-ip-v2", "vendor,uart-ip";
// 内核驱动匹配表
static const struct of_device_id uart_of_match[] = {
{ .compatible = "vendor,uart-ip-v2", .data = &v2_data },
{ .compatible = "vendor,uart-ip", .data = &v1_data },
{ /* sentinel */ }
};
compatible 字符串在驱动表中存在对应项,否则设备将无法绑定驱动。
2.3.2 reg 属性——地址空间描述
reg 属性描述设备在总线上的地址范围,由一组 (address, length) 对组成。地址和长度的单元宽度由父节点的 #address-cells 和 #size-cells 决定:
| #address-cells | #size-cells | reg 格式 | 示例 |
|---|---|---|---|
| 1 | 1 | <addr len> |
reg = <0x10000000 0x1000>; |
| 2 | 1 | <hi_addr lo_addr len> |
reg = <0x0 0x10000000 0x1000>; |
| 1 | 0 | <addr> |
reg = <0x10000000>; |
驱动通过 platform_get_resource(pdev, IORESOURCE_MEM, index) 获取 reg 中定义的地址资源。
2.3.3 interrupts 属性——中断资源描述
interrupts 属性描述设备使用的中断号、触发类型等信息。其格式依赖于中断控制器(interrupt-parent)的定义。常见格式如下:
// 设备树节点
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupt-parent = <&gic>; // 引用中断控制器
interrupts = <0 33 4>; // (中断类型, 中断号, 触发标志)
};
中断属性字段含义(以 ARM GIC 为例):
- 字段 0:中断类型——0 表示 SPI(共享外设中断),1 表示 PPI(私有外设中断)。
- 字段 1:中断号——对于 SPI,范围为 32~1019;对于 PPI,范围为 16~31。
- 字段 2:触发标志——1 表示上升沿触发,2 表示下降沿触发,4 表示高电平触发,8 表示低电平触发。
// 在 probe 函数中获取中断号
int irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
return irq;
}
// 注册中断处理函数
ret = devm_request_irq(&pdev->dev, irq, uart_isr, 0, "uart", dev);
2.4 属性继承与覆盖机制
设备树支持通过 /include/ 或 #include 引入公共定义(.dtsi 文件),并在板级文件(.dts)中覆盖或追加属性。这种机制极大减少了重复代码:
// soc.dtsi —— SoC 公共定义
soc {
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
status = "disabled"; // 默认禁用
};
};
// board.dts —— 板级文件
#include "soc.dtsi"
&uart0 {
status = "okay"; // 覆盖为启用
clock-frequency = <24000000>; // 追加属性
};
2.5 本章小结
设备树是嵌入式 Linux 驱动开发的基石。通过本节回顾,你应该掌握:
- 设备树的树形语法结构,节点与属性的定义方式。
compatible属性作为驱动匹配的唯一标识,其字符串列表的匹配优先级。reg属性描述地址空间,受#address-cells和#size-cells控制。interrupts属性描述中断资源,依赖中断控制器的格式定义。- 属性继承与覆盖机制,实现 SoC 与板级代码的分离。