4、设备树文件类型与编译:DTS、DTSI、DTB、DTC工具的使用与编译流程

设备树(Device Tree)是Linux内核中用于描述硬件资源的核心机制,它将硬件配置从内核代码中解耦,使得同一份内核镜像可以适配多种硬件平台。在实际开发中,工程师接触到的设备树文件并非单一类型,而是由DTS源文件、DTSI头文件、DTB二进制文件以及DTC编译器共同构成的一套完整工具链。理解这些文件类型的定位、编译流程以及它们之间的转换关系,是进行设备树驱动开发的基础。本章将系统梳理设备树的文件体系,深入讲解DTC编译器的使用方法和内核编译框架中的自动处理机制,并通过一个完整的实战案例展示从源码到二进制再到内核加载的全过程。

设备树文件类型与编译流程 DTS 源文件 board.dts 根节点 + 板级硬件描述 DTSI 头文件 soc.dtsi / board.dtsi SoC级定义 + 公共外设节点 C预处理器 cpp -I include 展开后的DTS 包含所有include内容 DTC 编译器 dtc -I dts -O dtb DTB 二进制文件 board.dtb 内核解析 of_platform_populate() dtc -I dtb -O dts(反编译调试) 文件类型对比 DTS: 文本, 可读 DTSI: 文本, 被include DTB: 二进制, 内核使用 DTC: 编译器工具

4.1 设备树文件类型与定位

4.1.1 核心原理:四种文件的分工与关系

设备树体系中的文件类型各有明确分工。DTS(Device Tree Source)是设备树的源文件,采用文本格式,描述具体某块板卡的硬件配置,通常以板级名称命名,如imx6ull-myboard.dts。DTS文件包含设备树的根节点以及所有板级特有的外设配置。DTSI(Device Tree Source Include)是设备树的头文件,同样为文本格式,用于存放SoC级公共定义、IP核默认配置、总线拓扑等可复用内容。例如imx6ull.dtsi定义了i.MX6ULL芯片的CPU、内存映射、GPIO控制器、UART、I2C等所有内部外设的默认状态。

DTB(Device Tree Blob)是设备树的二进制产物,由DTC编译器将DTS(含展开的DTSI)编译生成。DTB是内核或Bootloader实际加载和解析的文件,采用特定的二进制格式(FDT格式),包含结构块(structure block)、字符串块(strings block)和内存保留区(memory reserve map)。DTC(Device Tree Compiler)是设备树编译器,负责将DTS文本转换为DTB二进制,也支持反向反编译操作。

文件类型扩展名格式作用是否被内核直接使用
设备树源文件.dts文本板级硬件描述,根节点所在否
设备树头文件.dtsi文本SoC级公共定义,可被include否
设备树二进制.dtb二进制内核/Bootloader解析的最终文件是
设备树编译器dtc可执行程序DTS与DTB之间的转换工具否(工具)

4.1.2 判断方法:如何选择正确的文件类型

在实际开发中,工程师需要根据修改范围选择正确的文件类型。如果修改的是SoC级公共属性(如修改某IP核的默认时钟频率、添加新的内部外设节点),应修改对应的.dtsi文件,这样所有引用该.dtsi的板级DTS都会自动生效。如果修改的是板级特有配置(如某GPIO的默认电平、某外设的使能状态),则应修改板级.dts文件。当需要新增一个外设节点时,通常先在.dtsi中定义节点骨架(含status = "disabled"),然后在板级.dts中通过&node引用并修改属性或设置status = "okay"。

经验提醒:在Linux内核源码中,arch/arm/boot/dts/或arch/arm64/boot/dts/目录下存放所有DTS/DTSI文件。命名规则通常为soc名称.dtsi(SoC级)和厂商-板卡.dts(板级)。修改前务必确认文件归属层级,避免在错误层级修改导致其他板卡受影响。

4.2 DTC编译器与编译流程

4.2.1 核心原理:DTC的编译过程

DTC编译器的工作流程分为三个阶段:预处理、解析和输出。预处理阶段由C预处理器(cpp)完成,处理#include、#define等宏指令,将DTSI文件内容展开到DTS中。解析阶段由DTC读取展开后的DTS文本,构建设备树的内存模型,检查语法错误和节点/属性合法性。输出阶段根据指定的格式(dtb、dts、yaml等)生成目标文件。

DTC的常用命令格式如下:

# 编译DTS为DTB
dtc -I dts -O dtb -o output.dtb input.dts

# 反编译DTB为DTS(调试用)
dtc -I dtb -O dts -o output.dts input.dtb

# 带调试信息的编译
dtc -I dts -O dtb -@ -o output.dtb input.dts

# 查看DTC版本
dtc --version

其中-I指定输入格式,-O指定输出格式,-o指定输出文件名。-@选项用于生成带符号信息的DTB,便于后续动态加载overlay。

4.2.2 操作流程:内核编译框架中的自动处理

在实际内核编译中,工程师通常不需要手动执行dtc命令,而是通过内核的Kbuild系统自动完成。内核Makefile中定义了dtb-y变量,将需要编译的DTS文件列出,编译时自动调用DTC生成对应的DTB。以ARM平台为例,编译设备树的流程如下:

  1. 在arch/arm/boot/dts/Makefile中添加目标DTB名称,如dtb-$(CONFIG_SOC_MYBOARD) += myboard.dtb。
  2. 执行make myboard.dtb或make dtbs命令,Kbuild系统自动查找myboard.dts,调用cpp预处理,再调用DTC编译生成myboard.dtb。
  3. 生成的DTB位于arch/arm/boot/dts/目录下,可被Bootloader(如U-Boot)加载并传递给内核。

手动编译DTS时,需要确保包含正确的头文件路径。例如:

cpp -nostdinc -I include -I arch/arm/boot/dts -undef -D__DTS__ -x assembler-with-cpp myboard.dts | dtc -I dts -O dtb -o myboard.dtb

4.2.3 案例拆解:从DTS到DTB的完整编译实战

假设我们正在为一块基于i.MX6ULL的定制板卡开发设备树,板卡名称为myboard。初始状态:内核源码已下载,arch/arm/boot/dts/目录下已有imx6ull.dtsi(SoC级定义)和imx6ull-14x14-evk.dts(参考板卡)。我们的目标是创建myboard.dts并编译生成myboard.dtb。

第一步:创建板级DTS文件。复制参考板卡文件并修改:

cp arch/arm/boot/dts/imx6ull-14x14-evk.dts arch/arm/boot/dts/myboard.dts

编辑myboard.dts,修改模型名称和兼容性字符串:

/dts-v1/;
#include "imx6ull.dtsi"

/ {
    model = "MyBoard i.MX6ULL Board";
    compatible = "myboard,imx6ull", "fsl,imx6ull";
    // ... 其他板级配置
};

第二步:修改Makefile。在arch/arm/boot/dts/Makefile中添加:

dtb-$(CONFIG_SOC_IMX6ULL) += myboard.dtb

第三步:编译DTB。执行编译命令:

make ARCH=arm myboard.dtb

编译过程中,Kbuild系统自动执行cpp预处理(展开imx6ull.dtsi)和DTC编译。如果DTS中存在语法错误,DTC会报告错误行号和原因。编译成功后,在arch/arm/boot/dts/目录下生成myboard.dtb。

第四步:验证DTB内容。使用反编译命令检查生成的DTB是否符合预期:

dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
diff myboard.dts myboard_decompiled.dts

对比原始DTS和反编译DTS,可以确认编译过程中是否有属性丢失或格式变化。正常情况下,两者在语义上应完全一致,仅格式和注释有所差异。

案例复盘:该案例展示了从创建DTS到生成DTB的完整流程。关键点在于:①正确使用#include引入DTSI;②Makefile中正确配置dtb-y变量;③编译后务必反编译验证。实际项目中,如果DTS中引用了未定义的节点或属性,DTC会给出警告(warning)而非错误(error),这些警告往往暗示潜在的硬件配置问题,应逐一排查。

4.3 常见误区与总结复盘

4.3.1 常见误区

  • 误区一:直接修改DTB文件。DTB是二进制文件,不可直接编辑。正确的做法是修改DTS源文件后重新编译。如果只能拿到DTB,应先用dtc -I dtb -O dts反编译为DTS,修改后再编译回DTB。
  • 误区二:忽略DTSI的层级关系。DTSI可以嵌套包含,例如imx6ull.dtsi可能包含imx6ull-pinfunc.h等头文件。修改DTSI时需评估影响范围,避免影响其他板卡。
  • 误区三:DTC版本不匹配。不同版本DTC对语法支持有差异,老版本DTC可能不支持新语法(如/delete-node/、/delete-property/)。建议使用内核自带的DTC(scripts/dtc/dtc)而非系统安装的DTC。
  • 误区四:忘记更新Bootloader加载的DTB。编译生成新的DTB后,需要将其拷贝到Bootloader的加载位置(如FAT分区、TFTP服务器),否则内核仍会使用旧的DTB。

4.3.2 总结复盘

设备树文件类型与编译流程是Linux驱动开发的基石。DTS/DTSI作为文本源文件,描述了硬件拓扑和资源配置;DTC作为编译器,将文本转换为内核可解析的DTB二进制。理解这一流程的关键在于:明确文件层级(SoC级DTSI vs 板级DTS)、掌握编译命令(手动dtc命令和内核Kbuild自动编译)、熟练使用反编译调试。在实际开发中,建议建立以下工作习惯:每次修改DTS后立即编译并反编译验证;将DTB文件纳入版本管理;在Makefile中清晰注释每个DTB对应的板卡型号。

风险提示:设备树编译错误通常不会导致内核编译失败,而是生成错误的DTB,导致内核启动时外设初始化异常。这类问题排查困难,因为错误可能在运行时才暴露。务必在编译后使用dtc -I dtb -O dts反编译检查,并对比关键节点和属性。