2. Linux 设备树基础:设备树(DT)的起源与作用、DTS/DTC/DTB 文件格式、设备树的基本语法
2.1 设备树的起源与核心作用
在早期的 Linux 内核中,板级硬件信息(如内存基址、中断号、外设寄存器地址)被硬编码在 arch/arm/mach-xxx 目录下的 C 语言板文件中。每更换一款硬件平台,就需要重新编译内核,导致内核镜像与硬件高度耦合,维护成本急剧上升。
设备树(Device Tree,DT)的引入正是为了解决这一痛点。它是一套用于描述硬件资源的数据结构,将硬件配置从内核源码中剥离出来,以独立文件的形式提供给内核。其核心作用包括:
- 硬件描述标准化:使用统一的节点/属性模型描述 CPU、内存、总线、外设及其连接关系。
- 内核与硬件解耦:同一内核镜像可通过更换设备树文件适配不同硬件平台,无需重新编译。
- 驱动匹配机制:驱动通过
compatible属性与设备树节点匹配,实现自动加载。 - 资源动态传递:中断、DMA、时钟、GPIO 等资源通过设备树传递给驱动,避免硬编码。
关键理解:设备树不是驱动代码,而是硬件拓扑的“数据清单”。驱动读取这份清单来知道“我在哪、用什么资源、怎么配置”。
2.2 DTS / DTC / DTB 文件格式与编译流程
设备树涉及三种文件格式,它们构成了从源码到二进制再到内核解析的完整链路:
| 文件类型 | 扩展名 | 格式 | 作用 |
|---|---|---|---|
| DTS | .dts |
文本(ASCII) | 设备树源文件,由开发者编写,描述硬件拓扑 |
| DTSI | .dtsi |
文本(ASCII) | 设备树包含文件,类似 C 语言的 .h,用于复用公共定义 |
| DTC | — | 可执行程序 | 设备树编译器(Device Tree Compiler),将 DTS 编译为 DTB |
| DTB | .dtb |
二进制(FDT 格式) | 设备树二进制 Blob,Bootloader 传递给内核解析 |
编译命令示例:
# 将 .dts 编译为 .dtb
dtc -I dts -O dtb -o my_board.dtb my_board.dts
# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o my_board_decompiled.dts my_board.dtb
2.3 设备树基本语法:节点、属性、标签
设备树采用树形层级结构,根节点为 /,每个节点代表一个硬件设备或总线,节点内包含若干属性描述其配置信息。以下是核心语法要素:
2.3.1 节点(Node)
节点是设备树的基本单元,格式为 节点名@地址 { ... };。节点名应体现设备类型,地址部分通常为寄存器基址。
/dts-v1/;
/ {
model = "TI AM335x EVM";
compatible = "ti,am335x-evm", "ti,am335x";
/* 根节点下的子节点 */
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB RAM */
};
/* 外设节点 */
&uart0 {
status = "okay";
};
};
2.3.2 属性(Property)
属性是节点内的键值对,用于描述硬件特性。常见属性类型如下:
| 属性类型 | 语法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "ti,am335x-evm"; |
驱动匹配的关键标识,格式为“厂商,型号” |
| 32 位整数 | reg = <0x48020000 0x1000>; |
地址/长度对,由 #address-cells 和 #size-cells 决定 |
| 布尔值 | status = "okay"; 或 status = "disabled"; |
控制设备启用/禁用 |
| 字符串列表 | compatible = "ti,am335x-evm", "ti,am335x"; |
多个字符串用逗号分隔,内核按顺序匹配 |
| 二进制数据 | local-mac-address = [00 11 22 33 44 55]; |
用方括号包裹十六进制字节 |
2.3.3 标签(Label)与引用
标签为节点提供一个别名,便于在其他位置引用或覆盖节点属性。标签定义在节点名之前,使用 &标签名 引用。
/* 定义标签 */
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "disabled";
};
/* 在其他 .dts 文件中通过标签引用并修改属性 */
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
};
常见错误:标签引用时不要忘记
& 符号;reg 属性中的地址和长度必须与 #address-cells、#size-cells 的数值一致,否则内核解析会出错。
2.4 设备树在 CAN 驱动集成中的关键作用
在网关驱动部署实践中,设备树是 CAN 控制器与内核驱动之间的桥梁。以 TI AM335x 平台为例,D_CAN 控制器在设备树中的典型配置如下:
&dcan0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&dcan0_pins>;
};
&dcan1 {
status = "okay";
pinctrl-0 = <&dcan1_pins>;
};
驱动通过 of_match_table 匹配 compatible 属性,自动获取 reg(寄存器地址)、interrupts(中断号)、clocks(时钟源)等资源,无需在驱动代码中硬编码任何硬件地址。
实践要点:在网关项目中,设备树不仅描述 CAN 控制器,还需配置 CAN 收发器的 GPIO 控制引脚(如 STB/EN 引脚)、CAN 时钟频率以及总线波特率参数。这些信息全部通过设备树属性传递给驱动,实现“一套驱动,多板适配”。