嵌入式Linux设备树覆盖overlay动态配置实战 - 第1节
1.1 什么是设备树(Device Tree)
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点来记录处理器、内存、外设、中断、时钟、GPIO 等硬件信息。在嵌入式 Linux 系统中,设备树替代了传统平台代码(board file)中硬编码的硬件描述,成为内核与硬件之间的标准接口。
核心定义:设备树是一个由节点(node)和属性(property)组成的树状结构,每个节点代表一个硬件设备或总线,属性描述该设备的配置参数(如寄存器地址、中断号、时钟频率等)。
设备树在系统中的位置
设备树文件(.dts)经过编译生成设备树二进制文件(.dtb),由 bootloader(如 U-Boot)传递给内核。内核在启动阶段解析 .dtb,动态创建平台设备(platform device)并注册驱动,从而完成硬件初始化。
1.2 为什么需要设备树
在设备树出现之前,嵌入式 Linux 内核使用大量 board file(板级文件)来硬编码硬件信息。每个板子都需要在 arch/arm/mach-xxx 目录下编写数百行 C 代码,导致内核代码冗余、维护困难、升级缓慢。设备树解决了以下核心问题:
| 问题 | 传统方式(board file) | 设备树方式 |
|---|---|---|
| 硬件描述耦合 | 硬件信息硬编码在 C 代码中,修改需重新编译内核 | 硬件描述与内核代码分离,修改 .dts 后只需编译 .dtb |
| 多板支持 | 每个板子一个 board file,内核镜像膨胀 | 同一内核镜像可加载不同 .dtb,支持多板 |
| 内核升级 | board file 随内核版本变化,需持续适配 | 设备树绑定稳定,.dts 可跨内核版本复用 |
| 社区维护 | ARM 社区被 board file 淹没,维护成本极高 | 设备树统一硬件描述,社区聚焦驱动开发 |
| 动态配置 | 不支持运行时硬件变更 | 设备树覆盖(Overlay)支持热插拔与动态配置 |
关键收益:设备树使得“一次编译内核,多处部署”成为可能。Bootloader 根据实际板型选择对应的 .dtb 文件,内核无需重新编译即可适配不同硬件。
1.3 设备树源文件(.dts)基本语法与结构
.dts 文件采用树形结构,以 节点(node) 和 属性(property) 为核心元素。每个节点以 /dts-v1/; 开头,根节点用 / { ... }; 表示,子节点嵌套在父节点内部。
1.3.1 基本语法规则
- 节点定义:
node-name@unit-address { ... };,其中unit-address通常为设备基地址。 - 属性赋值:
property-name = <value>;,支持整数、字符串、字节数组、phandle 等类型。 - 标签(label):
label: node-name { ... };,用于在其它位置引用节点。 - 包含文件:
#include "soc.dtsi",.dtsi 为公共头文件,类似 C 语言的 .h 文件。 - 注释:支持
/* ... */和// ...风格。
1.3.2 典型 .dts 文件结构
/dts-v1/;
#include "imx6ull.dtsi"
/ {
model = "Embedded Linux Board v1.0";
compatible = "vendor,board-rev1", "vendor,board";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB RAM
};
leds {
compatible = "gpio-leds";
led0: led-red {
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
label = "red-led";
};
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
};
1.3.3 节点与属性详解
| 元素 | 语法示例 | 说明 |
|---|---|---|
| 根节点 | / { ... }; |
设备树的顶层容器,所有设备节点都在其内 |
| 子节点 | uart1: serial@02020000 { ... }; |
uart1 为标签,serial@02020000 为节点名+地址 |
| 属性:reg | reg = <0x02020000 0x4000>; |
描述设备地址范围(起始地址 + 长度) |
| 属性:compatible | compatible = "fsl,imx6ull-uart", "ns16550"; |
驱动匹配字符串,内核据此查找驱动 |
| 属性:interrupts | interrupts = <0 58 IRQ_TYPE_LEVEL_HIGH>; |
中断号、触发类型,依赖 interrupt-parent |
| 属性:status | status = "okay"; 或 "disabled" |
控制设备是否启用,常用于覆盖(overlay) |
| 引用节点 | &uart1 { ... }; |
通过标签引用节点,追加或修改属性 |
1.3.4 设备树覆盖(Overlay)的语法基础
设备树覆盖允许在运行时动态添加或修改节点,其语法与 .dts 基本一致,但需要额外包含 /plugin/; 声明,并使用 &node 引用目标节点:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = &uart1;
__overlay__ {
status = "okay";
baudrate = <115200>;
};
};
};
注意:覆盖文件必须使用
/plugin/; 标记,且每个 fragment 指定 target 和 __overlay__ 段。这是后续章节动态配置的核心机制。
1.3.5 常用设备树 API(内核视角)
驱动开发者通过以下 API 从设备树中读取硬件信息:
| API 函数 | 功能 | 返回值 |
|---|---|---|
of_find_node_by_name() |
根据节点名查找节点 | struct device_node * |
of_property_read_u32() |
读取 32 位整数属性 | int(0 成功) |
of_property_read_string() |
读取字符串属性 | int(0 成功) |
of_get_property() |
获取任意类型属性指针 | const void * |
of_irq_parse_one() |
解析中断描述 | int(0 成功) |
of_iomap() |
将 reg 地址映射到虚拟地址 | void __iomem * |
实战提示:编写设备树时,优先查阅内核文档
Documentation/devicetree/bindings/ 目录下的绑定文档,确保 compatible 字符串与驱动完全匹配。错误的 compatible 会导致驱动无法加载。