一、设备树:解决内核“虚胖”的关键
在Linux内核3.x版本前,ARM架构的硬件信息(如CPU、外设、内存地址等)全靠C代码硬编码在内核里。每新增一款开发板,就得写大量重复的板级代码,导致内核越来越臃肿,可移植性极差。Linux之父Linus对此提出批评后,ARM社区引入了设备树,将硬件描述从内核代码中剥离,用独立文件管理,让内核瘦身,也让驱动和硬件解耦。
设备树的核心是DTS文件,它以树形结构描述开发板上的设备,比如CPU数量、内存基地址、I2C和SPI接口挂载的设备等,清晰呈现硬件连接关系。
二、DTS、DTC与DTB:设备树的“三剑客”
DTS:设备树源文件,是ASCII文本格式,人类可直接阅读和修改,后缀为.dts。
DTB:DTS编译后的二进制文件,体积小,便于U-Boot传递给内核,后缀为.dtb。
DTC:编译工具,类似C语言的gcc,能把DTS编译成DTB,源码在Linux内核的scripts/dtc目录下。
编译设备树时,推荐用make dtbs命令,比make all更高效。具体编译哪个DTS,由arch/arm/boot/dts/Makefile控制,比如使用I.MX6ULL芯片的开发板,对应的DTS会被统一编译成DTB,新增开发板只需添加对应DTS即可。
三、DTS语法:简洁易懂的硬件描述规则
DTS语法简单灵活,是文本文件,修改和阅读都很方便,核心规则如下:
1. 头文件引用
DTS可通过#include引用三类文件:.h(C语言头文件)、.dtsi(设备树头文件)、.dts(其他设备树文件)。其中.dtsi是主流,用于描述SOC级信息,比如CPU架构、主频、外设控制器地址范围等,像I.MX6ULL的imx6ull.dtsi就是典型例子,不同开发板可复用这份SOC信息,避免重复编写。
2. 节点与属性
节点:对应硬件设备,格式为[标签:]节点名[@地址],比如cpu@0表示0号CPU节点,标签方便后续引用。
属性:描述节点的具体信息,格式是属性名=值;。关键属性有:compatible:驱动匹配的核心,格式是“厂商,型号”,比如"arm,cortex-a7",内核靠它找到对应驱动。
device_type:标识节点类型,比如"cpu"代表CPU,"memory"代表内存。
reg:设备的寄存器地址和长度,比如reg=<0x2020000 0x4000>,指定地址和占用空间。
interrupts:设备的中断号和触发方式,内核靠它处理中断。
status:设备状态,"okay"表示启用,"disabled"表示禁用。
3. 层级与引用
设备树以树形结构组织,根节点是/,子节点挂载在父节点下,比如CPU节点在cpus节点内。引用节点时,用&标签,比如&uart0就能指向UART0节点,方便在多个地方复用或修改节点配置。
四、设备树的工作流程
设备树的运行流程清晰,串联起硬件和驱动:
U-Boot启动时,把内核镜像和DTB文件加载到内存,将DTB地址传递给内核。
内核启动后,解析DTB,构建内部设备树结构,遍历所有节点。
内核根据节点的compatible属性,在驱动的匹配表中查找对应驱动。
匹配成功后,调用驱动的probe函数,同时把设备树中的资源(地址、中断、gpio等)传递给驱动,驱动就能正常操作硬件。
五、学习与实践建议
别从零写DTS:直接基于芯片厂商提供的模板修改,比如I.MX6ULL开发板,参考厂商的imx6ull-evk.dts调整,效率更高。
抓核心配置:重点关注compatible是否和驱动匹配、reg地址是否正确、status是否启用、GPIO和中断配置是否准确,这些是驱动能否正常运行的关键。
善用调试手段:查看/proc/device-tree/,能直观看到内核解析后的设备树;用dtc工具反编译DTB,生成DTS文本,方便排查问题。
六、核心总结
设备树的本质是让驱动和硬件解耦:传统方式把硬件信息硬编码在驱动里,换板子就得改驱动;设备树则把硬件描述独立出来,同一驱动能适配多平台,内核不再臃肿,也符合现代嵌入式Linux的开发逻辑。
对嵌入式驱动开发者来说,掌握设备树是必备技能。建议结合ARM开发板的DTS文件,从修改简单设备节点入手,再结合驱动代码理解of_get_named_gpio、of_property_read_u32等内核API,就能快速掌握设备树的应用。