3. 设备树(DT)基础与3.x到5.x的变迁:从板级文件(board-*.c)到设备树(.dts)的迁移
3.1 为什么需要设备树?从“硬编码”到“数据驱动”
在 Linux 3.x 时代,ARM 架构的板级支持包(BSP)依赖大量的 board-*.c 文件。这些文件在 C 代码中硬编码了中断号、GPIO、内存地址、时钟频率等硬件资源信息。这种方式的弊端随着 ARM SoC 的爆发式增长而暴露无遗:
- 代码冗余:同一 SoC 的不同板卡需要复制大量相似的
board-*.c文件。 - 内核与硬件耦合:更换一个 GPIO 或调整内存分区,必须重新编译内核。
- 主线化困难:社区维护者难以接受大量重复的板级代码进入主线。
设备树(Device Tree, DT)的引入,将硬件描述从内核源码中剥离,变成一份独立的数据文件(.dts)。内核在启动时解析这份数据,动态创建平台设备、注册资源,实现了“一套内核二进制,适配多种板卡”的目标。
arch/arm/mach-xxx/board-xxx.c 中的 platform_device 和 resource 结构体,迁移到 arch/arm/boot/dts/xxx.dts 中的节点与属性。
3.2 设备树核心语法速览
设备树源文件(.dts)采用树形结构描述硬件。每个硬件外设对应一个节点(node),节点内包含若干属性(property)。
// 一个典型的 I2C 控制器节点
i2c1: i2c@40012000 {
compatible = "st,stm32f4-i2c", "st,stm32f7-i2c";
reg = <0x40012000 0x400>;
interrupts = <31 2>;
clocks = <&rcc 0 0 43>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>;
};
};
| 语法元素 | 说明 | 3.x 对应物 |
|---|---|---|
compatible |
驱动匹配的关键字符串,格式为“厂商,型号” | .name 字段 + platform_match |
reg |
地址与长度对,描述寄存器基址和范围 | struct resource 中的 .start / .end |
interrupts |
中断号与触发类型,依赖 interrupt-parent |
platform_get_irq() 硬编码 |
clocks |
时钟句柄,引用 clocks 控制器节点 |
clk_get() 通过设备名查找 |
status |
设备启用状态,"okay" 或 "disabled" |
无对应,直接注册 |
3.3 从 board-*.c 到 .dts 的迁移路径
迁移工作并非简单的“翻译”,而是对硬件描述逻辑的重新梳理。以下是一个典型的 I2C 外设迁移对照:
platform_device 注册、resource 数组、i2c_board_info 结构体,全部替换为 .dts 中的节点和属性。驱动侧则通过 of_match_table 匹配,并使用 of_* API 读取资源。
// 3.x 时代:board-xxx.c
static struct resource i2c_resources[] = {
[0] = DEFINE_RES_MEM(0x40012000, 0x400),
[1] = DEFINE_RES_IRQ(31),
};
static struct platform_device i2c_device = {
.name = "stm32-i2c",
.id = 1,
.resource = i2c_resources,
.num_resources = ARRAY_SIZE(i2c_resources),
};
static struct i2c_board_info __initdata eeprom_info = {
I2C_BOARD_INFO("24c02", 0x50),
.platform_data = &eeprom_pdata,
};
// 5.x 时代:xxx.dts
i2c1: i2c@40012000 {
compatible = "st,stm32f4-i2c";
reg = <0x40012000 0x400>;
interrupts = <31 2>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
3.4 DT 绑定文档的重要性
设备树绑定文档(DT Binding Documentation)是连接硬件设计、内核驱动和设备树文件的“契约”。它定义了:
- compatible 字符串:驱动必须声明的匹配值。
- 必需属性(required):驱动正常工作必须提供的属性,如
reg、interrupts。 - 可选属性(optional):用于配置特定功能,如
dmas、pinctrl-0。 - 子节点规范:如 I2C 子节点必须包含
reg属性表示地址。
.dts 文件,却忽略了查阅对应驱动的最新绑定文档。这往往导致驱动无法 probe,因为 5.x 内核可能废弃了某些属性,或新增了必需属性(如 power-domains、resets)。
绑定文档位于内核源码的 Documentation/devicetree/bindings/ 目录下,按厂商和子系统分类。例如,STM32 I2C 控制器的绑定文档位于:
Documentation/devicetree/bindings/i2c/st,stm32-i2c.yaml
该 YAML 文件使用 json-schema 格式,不仅描述了属性,还能被 dt_binding_check 和 dtbs_check 工具自动验证,确保 .dts 文件的正确性。
3.5 设备树变迁时间线(3.x → 5.x)
3.6 驱动适配:从 platform_driver 到 of_match_table
驱动侧的变化同样显著。3.x 时代的驱动通常使用 platform_driver 的 .id_table 或直接硬编码设备名进行匹配。5.x 时代则必须使用 .of_match_table:
// 3.x 风格:依赖设备名
static struct platform_driver my_driver = {
.driver = {
.name = "stm32-i2c",
},
.probe = my_probe,
};
// 5.x 风格:依赖 compatible 匹配
static const struct of_device_id my_of_match[] = {
{ .compatible = "st,stm32f4-i2c" },
{ .compatible = "st,stm32f7-i2c" },
{ /* sentinel */ }
};
static struct platform_driver my_driver = {
.driver = {
.name = "stm32-i2c",
.of_match_table = my_of_match,
},
.probe = my_probe,
};
在 probe 函数中,资源获取方式也从 platform_get_resource() 转变为 of_* API:
// 3.x 方式
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
// 5.x 方式(推荐)
base = devm_platform_ioremap_resource(pdev, 0);
// 或更通用的 of_* 方式
struct device_node *np = pdev->dev.of_node;
of_property_read_u32(np, "clock-frequency", &freq);
3.7 实战:一个 I2C EEPROM 驱动的迁移清单
| 步骤 | 3.x 操作 | 5.x 操作 |
|---|---|---|
| 1. 硬件描述 | 在 board-xxx.c 中定义 i2c_board_info |
在 .dts 中添加 EEPROM 子节点 |
| 2. 驱动匹配 | i2c_driver.id_table 或 .driver.name |
i2c_driver.driver.of_match_table |
| 3. 资源获取 | client->irq 或 i2c_client.dev.platform_data |
of_property_read_u32() 或 device_property_read_*() |
| 4. 时钟/电源 | clk_get(NULL, "i2c_clk") |
devm_clk_get(&pdev->dev, NULL) 配合 clocks 属性 |
| 5. 验证 | 编译内核,烧录启动 | make dtbs_check + 内核启动日志 |
dtc -I dtb -O dts 反编译设备树二进制(.dtb)为可读的 .dts,用于调试和对比。在 5.x 内核中,dtbs_check 和 dt_binding_check 是确保绑定正确性的必备工具。
3.8 本章小结
设备树的引入是嵌入式 Linux 内核架构的一次重大演进。从 3.x 到 5.x,开发者需要完成以下认知转变:
- 描述方式:从 C 结构体到树形文本文件。
- 匹配机制:从设备名到
compatible字符串。 - 资源获取:从
platform_get_resource到of_property_read_*或device_property_read_*。 - 验证手段:从人工审查到自动化 schema 校验。