2. 设备树基础:语法、节点、属性与核心概念详解
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它把原本硬编码在 Linux 内核中的板级细节剥离出来,变成一份独立于内核的文本描述文件(.dts)。内核在启动时解析设备树,动态创建 platform_device、注册中断、映射内存、配置 GPIO,从而实现“一套内核二进制,适配多种硬件变体”。
本章从设备树的语法结构出发,逐一拆解节点、属性、reg、compatible、interrupts 等核心概念,并为每个概念提供内联 SVG 结构图,帮助你在后续 GPIO 控制器节点配置中建立扎实的认知基础。
2.1 设备树语法与节点层次
设备树采用树形结构,根节点是“/”,所有硬件设备都挂载在根节点下。每个节点代表一个设备或总线,节点可以嵌套,形成父子关系。节点名称通常为 device-name@unit-address,其中 unit-address 对应设备在总线上的起始地址。
{ } 界定,属性以 key = value; 形式赋值,每行以分号结尾。注释支持 C 风格 /* */ 和 C++ 风格 //。
// 设备树最小示例
/dts-v1/;
/ {
model = "Embedded GPIO Dev Board";
compatible = "vendor,gpio-board";
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
gpio0: gpio@ff710000 {
compatible = "vendor,gpio-controller";
reg = <0xff710000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <0 32 4>;
};
};
};
gpio0: 就是标签,可以在其他节点中通过 &gpio0 引用该节点,实现跨节点属性覆盖或 phandle 引用。
2.2 核心属性详解
设备树属性是描述设备特征的关键字段。以下表格列出 GPIO 控制器节点最常用的属性及其含义:
| 属性名 | 类型 | 说明 | GPIO 控制器场景 |
|---|---|---|---|
compatible |
字符串列表 | 设备驱动匹配的关键标识,格式为 "厂商,设备型号" |
驱动通过 of_match_table 匹配该值 |
reg |
u32 数组 | 描述设备地址空间,格式为 <address size> |
指定 GPIO 控制器的寄存器基地址和映射长度 |
interrupts |
u32 数组 | 描述设备的中断资源,格式由 interrupt-parent 决定 |
GPIO 控制器作为中断控制器时,描述其输出中断 |
gpio-controller |
空属性 | 标记该节点是一个 GPIO 控制器 | 必须存在,否则内核不识别其为 GPIO 控制器 |
#gpio-cells |
u32 | 指定 GPIO 描述符需要的 cell 数量 | 通常为 2:<gpio_phandle gpio_num flags> |
interrupt-parent |
phandle | 指定该设备的中断控制器父节点 | GPIO 控制器通常连接到 GIC |
gpio-controller; 属性,或者 #gpio-cells 的值与驱动期望不符,会导致 GPIO 申请失败或系统崩溃。务必检查设备树绑定文档。
2.3 compatible 属性与驱动匹配机制
compatible 是设备树中最关键的属性之一。内核在启动时遍历设备树节点,将节点的 compatible 值与驱动注册的 of_match_table 进行比对。匹配成功后,内核调用驱动的 probe 函数。
匹配优先级:设备树中的 compatible 字符串列表按顺序排列,内核优先匹配列表中的第一个字符串。驱动可以同时支持多个兼容型号。
// 设备树节点
gpio0: gpio@ff710000 {
compatible = "vendor,gpio-v2", "vendor,gpio-v1";
// 内核优先尝试匹配 "vendor,gpio-v2"
};
// 驱动匹配表
static const struct of_device_id gpio_of_match[] = {
{ .compatible = "vendor,gpio-v2", .data = &gpio_v2_data },
{ .compatible = "vendor,gpio-v1", .data = &gpio_v1_data },
{ /* end */ }
};
MODULE_DEVICE_TABLE(of, gpio_of_match);
2.4 reg 属性与地址编码
reg 属性描述设备在总线上的地址空间。其格式由父节点的 #address-cells 和 #size-cells 决定:
- #address-cells:表示地址字段占用的 u32 数量,通常为 1(32 位地址)或 2(64 位地址)。
- #size-cells:表示长度字段占用的 u32 数量,通常为 1(32 位长度)或 0(无长度,如纯寄存器地址)。
// 32 位地址空间示例
soc {
#address-cells = <1>;
#size-cells = <1>;
gpio0: gpio@ff710000 {
reg = <0xff710000 0x1000>; // 基地址 0xff710000,长度 0x1000
};
};
// 64 位地址空间示例
soc {
#address-cells = <2>;
#size-cells = <2>;
gpio0: gpio@3000000000 {
reg = <0x0 0x3000000000 0x0 0x1000>; // 高32位 低32位 高32位 低32位
};
};
platform_get_resource(pdev, IORESOURCE_MEM, 0) 获取 reg 属性对应的内存资源,然后使用 devm_ioremap_resource() 完成寄存器映射。
2.5 interrupts 属性与中断描述
interrupts 属性描述设备的中断资源。其格式由中断控制器(interrupt-parent)的 #interrupt-cells 决定。对于 ARM GIC(通用中断控制器),通常使用 3 个 cell:
| Cell 位置 | 含义 | 常见值 |
|---|---|---|
| cell 0 | 中断类型 | 0 = SPI(共享外设中断),1 = PPI(私有外设中断) |
| cell 1 | 中断号 | SPI 中断号范围 32~1019,PPI 中断号范围 16~31 |
| cell 2 | 中断触发类型 | 1 = 上升沿触发,2 = 下降沿触发,4 = 高电平触发,8 = 低电平触发 |
// GPIO 控制器中断示例
gpio0: gpio@ff710000 {
compatible = "vendor,gpio-controller";
reg = <0xff710000 0x1000>;
interrupt-parent = <&gic>; // 连接到 GIC
interrupts = <0 32 4>; // SPI 类型,中断号 32,高电平触发
gpio-controller;
#gpio-cells = <2>;
};
<0 32 4>(64 - 32 = 32)。不同架构可能有差异,务必参考 SoC 手册。
2.6 节点引用与覆盖(&label 机制)
设备树支持通过标签引用节点,实现增量覆盖。这在多板级设备树中非常有用:基础 .dtsi 文件定义默认配置,具体板级 .dts 文件通过 &label 覆盖属性。
// 基础 .dtsi 文件
gpio0: gpio@ff710000 {
compatible = "vendor,gpio-controller";
reg = <0xff710000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
status = "disabled"; // 默认禁用
};
// 板级 .dts 文件
&gpio0 {
status = "okay"; // 覆盖为启用
gpio-line-names = "LED_RED", "LED_GREEN", "BTN_USER";
};
.dtsi 文件定义所有外设节点,板级厂商只需通过 #include 包含并覆盖需要的属性。GPIO 控制器的 gpio-line-names 属性通常只在板级文件中定义,用于给每个 GPIO 引脚命名,方便驱动和用户空间识别。