1. 设备树基础概念
1.1 什么是设备树 (Device Tree)
设备树(Device Tree,简称 DT)是一种描述硬件资源的数据结构,它使用树形节点来记录系统中所有可发现的硬件设备及其连接关系、寄存器地址、中断号、时钟、DMA 通道等资源信息。设备树源文件(.dts)经过设备树编译器(dtc)编译后生成设备树二进制文件(.dtb),由 Bootloader 传递给内核。
一个典型的设备树节点结构如下:
/dts-v1/;
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a8";
reg = <0x0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512 MB */
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
1.2 为什么需要设备树
在设备树出现之前(Linux 2.6 ~ 3.0 时代),ARM 架构的嵌入式 Linux 内核中充斥着大量 board-xxx.c 文件,每个开发板都需要在 C 代码中硬编码硬件资源信息。这种方式带来了三个严重问题:
| 问题 | 具体表现 | 后果 |
|---|---|---|
| 代码膨胀 | 每个板子一个 board 文件,动辄上千行结构体赋值 | 内核源码树臃肿,维护成本极高 |
| 耦合过紧 | 硬件资源描述与驱动代码深度绑定 | 更换外设或修改地址必须重新编译内核 |
| 碎片化严重 | 厂商各自维护私有 board 文件,社区难以统一 | 主线内核支持滞后,厂商 BSP 与主线分裂 |
设备树解决了上述问题,其核心优势包括:
- 硬件描述与驱动分离:驱动代码只关心
compatible匹配,不关心具体地址和中断号。 - 单内核镜像支持多板:同一份
zImage搭配不同的.dtb即可启动不同硬件。 - 减少主线内核碎片:社区统一使用设备树,厂商只需提交
.dts文件即可。 - Bootloader 可动态修改:U-Boot 支持
fdt命令,可在启动前调整设备树内容。
1.3 设备树与平台设备 (Platform Device) 的关系
平台设备(Platform Device)是 Linux 内核中用于管理“非可枚举总线设备”的抽象层,典型代表包括:片上外设(UART、I2C、SPI、GPIO 控制器)、内存映射设备、中断控制器等。设备树与平台设备的关系可以概括为:设备树是“硬件清单”,平台设备是“运行时对象”。
内核在启动过程中,通过 of_platform_populate() 函数遍历设备树中的所有节点,为每个匹配的节点创建一个 platform_device 实例,并注册到内核的设备模型中。驱动则通过 of_match_table 与设备树节点进行匹配。
/* 驱动中的匹配表 */
static const struct of_device_id my_uart_of_match[] = {
{ .compatible = "ti,am3352-uart" },
{ .compatible = "ti,omap3-uart" },
{ /* end of list */ }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);
static struct platform_driver my_uart_driver = {
.probe = my_uart_probe,
.remove = my_uart_remove,
.driver = {
.name = "my_uart",
.of_match_table = my_uart_of_match,
},
};
module_platform_driver(my_uart_driver);
下表总结了设备树节点与平台设备之间的对应关系:
| 维度 | 设备树节点 (DT Node) | 平台设备 (Platform Device) |
|---|---|---|
| 存在形式 | 编译期文本(.dts / .dtb) | 运行时内核对象(struct platform_device) |
| 生命周期 | Bootloader 加载后常驻内存 | 驱动 probe 时创建,remove 时销毁 |
| 资源描述 | reg、interrupts、clocks 等属性 | struct resource 数组 |
| 匹配方式 | compatible 字符串 | of_match_table 匹配 |
| 创建时机 | 由内核 of 子系统解析 | of_platform_populate() 调用时 |
i2c@xxx 节点下的子节点(如 eeprom@50)属于 I2C 客户端设备,由 I2C 核心创建 i2c_client,而非平台设备。只有直接挂载在“平台总线”上的节点才会被转换为平台设备。
下图展示了设备树从源文件到平台设备实例的完整转换流程:
从图中可以看出,整个流程是一个从“文本描述”到“运行时对象”的线性转换:.dts 源文件经过 dtc 编译生成 .dtb,Bootloader 将其加载到内存并传递给内核,内核的 of 子系统解析 .dtb 并调用 of_platform_populate() 为每个匹配的节点创建 platform_device,最终触发驱动的 probe 函数。
of_match_table 中声明自己支持的 compatible 字符串,内核会自动完成从设备树节点到平台设备的创建与匹配。