1. 设备树基础概念解析
设备树(Device Tree)是嵌入式Linux系统中用于描述硬件配置的数据结构。它最初由PowerPC架构引入,现已成为ARM架构的标准硬件描述方式。简单来说,设备树就像一份硬件"地图",告诉操作系统当前系统中有哪些硬件设备以及它们是如何连接的。
在传统嵌入式系统中,硬件信息通常直接硬编码在内核源码中。这种方式存在明显弊端:每当硬件变更时都需要重新编译内核。而设备树采用"硬件描述与内核分离"的设计理念,将硬件配置信息存储在独立的.dts(设备树源文件)中,编译后生成.dtb(设备树二进制文件)供内核使用。
设备树的核心优势在于:
- 同一内核镜像可适配不同硬件平台
- 硬件变更只需修改设备树文件,无需重新编译内核
- 支持动态加载不同的设备树配置
- 提供标准化的硬件描述语法
2. 设备树文件结构与语法详解
2.1 设备树源文件组成
典型的设备树源文件(.dts)包含以下关键部分:
c复制/dts-v1/; // 版本声明
/ {
model = "MyBoard"; // 板级描述
compatible = "myvendor,myboard";
#address-cells = <1>; // 地址长度定义
#size-cells = <1>; // 大小长度定义
cpus {
// CPU节点定义
};
memory@0 {
// 内存节点定义
};
soc {
// 片上系统节点
};
};
2.2 设备树节点语法规则
设备树采用树状结构组织硬件信息,每个节点代表一个硬件组件。节点定义语法如下:
c复制node-name@unit-address {
property1 = value;
property2 = <value>;
property3 = [hex values];
child-node {
// 子节点定义
};
};
关键属性说明:
compatible:设备驱动匹配的关键属性,格式为"厂商,设备型号"reg:描述设备寄存器地址范围interrupts:定义设备中断号status:设备状态(如"okay"、"disabled")
2.3 设备树特殊语法
设备树支持多种高级语法特性:
phandle引用:
c复制intc: interrupt-controller@1000 {
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x1000 0x100>;
};
device@2000 {
interrupts = <&intc 1 2>;
};
覆盖机制:
c复制// 基础设备树
/ {
node {
value = <1>;
};
};
// 覆盖片段
/ {
node {
value = <2>; // 覆盖原值
new-value = <3>; // 新增属性
};
};
3. 设备树编译与加载流程
3.1 设备树编译工具链
设备树编译器(DTC)是将.dts源文件编译为.dtb二进制文件的工具链。典型编译流程:
bash复制# 编译设备树
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译查看
dtc -I dtb -O dts -o myboard.dts myboard.dtb
现代Linux内核通常内置DTC工具,可通过内核Makefile编译设备树:
bash复制make ARCH=arm dtbs
3.2 设备树加载机制
设备树在内核启动过程中被加载和使用:
-
Bootloader阶段:
- U-Boot等引导加载器将.dtb文件加载到内存指定位置
- 通过ATAG或chosen节点传递内存布局信息
-
内核初始化阶段:
- 内核解压后首先解析设备树
- 建立设备树内存映射(unflatten_device_tree)
- 遍历设备树节点,初始化平台设备
-
驱动匹配阶段:
- 内核根据compatible属性匹配驱动
- 驱动通过设备树获取硬件配置信息
3.3 运行时设备树查看
Linux系统运行时可通过以下方式查看设备树:
bash复制# 查看原始设备树
hexdump /sys/firmware/fdt
# 以可读格式查看
dtc -I fs /sys/firmware/fdt
# 查看特定节点
ls /proc/device-tree/
cat /proc/device-tree/model
4. 设备树与驱动开发实践
4.1 驱动如何获取设备树信息
Linux驱动通过OF(Open Firmware)API访问设备树信息:
c复制#include <linux/of.h>
// 获取设备节点
struct device_node *np = pdev->dev.of_node;
// 读取属性值
of_property_read_u32(np, "reg", &value);
of_property_read_string(np, "compatible", &str);
// 获取中断号
int irq = irq_of_parse_and_map(np, 0);
// 获取寄存器地址
void __iomem *regs = of_iomap(np, 0);
4.2 典型设备节点定义示例
GPIO控制器:
c复制gpio: gpio-controller@10000 {
compatible = "vendor,gpio-controller";
reg = <0x10000 0x100>;
#gpio-cells = <2>;
gpio-controller;
interrupt-controller;
#interrupt-cells = <2>;
};
I2C设备:
c复制i2c@20000 {
compatible = "vendor,i2c";
reg = <0x20000 0x100>;
clock-frequency = <100000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
};
4.3 设备树覆盖技术
设备树覆盖(Overlay)允许运行时动态修改设备树:
c复制// 定义覆盖片段
/dts-v1/;
/plugin/;
&i2c1 {
new-device@76 {
compatible = "new,device";
reg = <0x76>;
};
};
加载覆盖:
bash复制# 编译覆盖
dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts
# 应用覆盖
mkdir /config/device-tree/overlays
cat overlay.dtbo > /config/device-tree/overlays/new-overlay
5. 设备树调试与问题排查
5.1 常见编译错误
-
语法错误:
bash复制
Error: myboard.dts:10.1-2 syntax error解决方法:检查节点大括号匹配和分号使用
-
引用未定义节点:
bash复制FATAL ERROR: Could not get phandle node for path '/nonexistent'解决方法:确保所有引用的节点已正确定义
-
属性类型不匹配:
bash复制
Warning (unit_address_vs_reg): /node@1: node has a unit name, but no reg property解决方法:为带地址的节点添加reg属性
5.2 运行时问题排查
驱动未加载:
- 检查
compatible字符串是否与驱动匹配 - 确认设备树节点状态为"okay"
- 检查内核配置是否启用对应驱动
资源获取失败:
- 确认reg属性地址与硬件一致
- 检查interrupts属性与硬件中断号对应
- 验证时钟、复位等资源定义
5.3 调试工具与技巧
设备树调试选项:
bash复制# 启用设备树调试
echo 1 > /proc/sys/kernel/debug/dt
# 查看设备树匹配过程
dmesg | grep of_
实用调试命令:
bash复制# 查看已注册平台设备
ls /sys/devices/platform/
# 查看设备资源
cat /proc/iomem
cat /proc/interrupts
# 检查GPIO状态
gpiodetect
gpioinfo
6. 设备树最佳实践与经验分享
6.1 设计原则
-
模块化设计:
- 将公共部分提取到.dtsi头文件
- 板级差异通过包含不同.dtsi实现
c复制#include "soc-common.dtsi" #include "board-specific.dtsi" -
版本控制:
- 设备树应与内核版本同步维护
- 重大硬件变更应创建新.dts文件
-
兼容性处理:
- 保持向后兼容的compatible字符串
c复制compatible = "newvendor,newchip", "oldvendor,oldchip";
6.2 性能优化
-
减少设备树大小:
- 移除未使用的节点
- 合并相同类型节点
-
启动加速:
- 按需加载设备树覆盖
- 预编译设备树二进制
-
内存优化:
- 使用
__attribute__((aligned))保证对齐 - 合理设置
#address-cells和#size-cells
- 使用
6.3 实际项目经验
多平台适配技巧:
c复制/ {
model = "MyBoard";
compatible = "myvendor,myboard", "myvendor,myfamily";
board-rev = <1>; // 硬件版本标识
};
&i2c1 {
status = <&board_rev == 1 ? "okay" : "disabled">;
};
动态配置示例:
c复制/ {
chosen {
bootargs = "console=ttyS0,115200";
// 运行时可修改的参数
environment {
ip-address = [00 00 00 00];
netmask = [00 00 00 00];
};
};
};
硬件抽象技巧:
c复制// 定义硬件抽象层
/ {
hal {
leds {
led0 = <&gpio 10 0>;
led1 = <&gpio 11 0>;
};
buttons {
button0 = <&gpio 20 1>;
};
};
};