引言
在嵌入式 Linux 开发中,硬件资源的描述至关重要。驱动代码负责操作设备,而设备结构体(struct device)则描述硬件资源。问题在于,这些硬件信息如何进入内核并与驱动配对?
在单片机裸机编程中,硬件地址硬编码在代码里。但在 Linux 中,为了保证驱动的通用性,避免在代码中出现具体硬件地址是核心原则。因此,需要一种机制将硬件信息从驱动代码中剥离。
这就是本文的主题——设备树。
1. 为什么要引入设备树
1.1 当时的环境
2000 年代末期,ARM 架构迎来井喷式发展。相比 x86 架构的标准化,ARM 呈现碎片化状态。各家 SOC 厂商(高通、三星、TI 等)推出不同芯片,同一款 SOC 也可能有不同的外设配置。Linux 想统一支持所有设备,但当时 ARM 设备基本靠硬编码告诉内核硬件信息。
这导致 Linux 内核 arch/arm 目录下代码量急速膨胀。支持新板子需添加专属文件。
1.2 board file 的缺陷
早期 ARM Linux 采用 board file(板级支持文件模式),即每个设备板子都有独立的 board-xxx.c 文件。文件中硬编码了内存映射、时钟配置、GPIO 引脚复用等信息。
这种模式在 2010 年左右暴露出问题:
- 内核里有数千个 board file,arch/arm 目录下文件数量激增,大量重复代码。
- 修改通用驱动需同步改几十个 board file,合并冲突频繁。
- 一个编译好的内核只能支持特定板子,无法像 x86 那样一个内核跑所有 PC。
- 厂商维护分支,上游合并慢,主线内核支持新硬件滞后。
Linus Torvalds 曾批评 ARM 社区的碎片化,要求推动根本性变革。
1.3 设备树的引入
社区最终借鉴 PowerPC 架构的 Open Firmware 机制。核心想法是把硬件描述从 C 源码中剥离出来,用独立的数据文件描述硬件,由 bootloader 传入内核,让内核在运行时动态解析。
使用 .dts(Device Tree Source)文件描述硬件树状结构,编译成 .dtb(Device Tree Blob)文件传给内核。
好处包括:删除重复代码,一个内核镜像支持多种硬件,易读性和可维护性提升,便于上游合并。
2. 初识设备树
设备树是由节点和属性构成的树。
2.1 三个 D
- DTS:源码文件 .dts,修改硬件配置主要修改它。
- DTC:编译器,把 .dts 编译成二进制文件。
- DTB:编译出来的二进制文件 .dtb,烧录到 flash 或放在文件系统。Bootloader 加载后传给内核。
2.2 类似于头文件包含的关系
Linux 采用分层和包含机制。以 NXP i.MX6ULL 芯片为例。
2.2.1 SOC 级文件 (.dtsi)
由芯片原厂提供,如 imx6ull.dtsi,描述芯片内部固有的硬件资源。物理地址和中断号不会变。
文件体现继承与重写策略。例如引用父类标签只修改需要的部分,或删除不存在的节点。
2.2.2 板级文件 (.dts)
开发板厂商编写描述文件,如 imx6ull-14x14-evk.dts。文件开头引用 SOC 级文件,开发者只需关注板子上的外设配置。
通常在最外层使用 &标签名方式引用节点开启默认关闭的外设,或在根节点内部添加外接新设备。
2.3 如何与驱动匹配
在没有设备树时,platform_device 名字必须和 platform_driver 名字一样。
在设备树世界里,匹配规则主角变成了 compatible 属性。只要设备树节点和驱动程序的 compatible 属性相同即可匹配。
compatible 属性通常是字符串列表,格式为'厂商,芯片型号 - 模块名'。
驱动程序中会定义相应的 compatible 属性数组,Platform 总线据此匹配。
3. 内核如何处理设备树
内核在启动过程中充当中间人,把设备树从文本变成结构体。


