
本文概述
设备树是嵌入式Linux开发的基础技能,也是面试中的高频考点。设备树源文件DTS通过节点和属性的树状结构描述硬件资源,编译后生成DTB二进制文件供内核解析。节点命名遵循"设备类型@单元地址"格式,属性支持字符串、32位整数、phandle引用等多种类型。编写设备树的核心要点包括正确设置compatible属性、合理使用reg描述地址资源、正确配置中断属性和引脚复用,以及利用status属性控制设备启用禁用。
核心要点速览
设备树三要素:DTS源文件、DTC编译器、DTB二进制文件
节点格式:节点名@地址 { 属性 = 值; 子节点; };
compatible属性决定驱动匹配,格式为"厂商,型号"
reg属性描述地址资源:<起始地址 长度>
常用特殊节点:根节点、chosen、aliases、memory、soc
一、面试官考察点
二、回答思路
1. 设备树是什么
设备树是一种描述硬件资源的数据结构,全称是Device Tree。它的作用是把硬件信息从内核的C代码里抽出来,用一种独立的数据格式描述。这样同一个内核镜像可以用在不同的硬件平台上,只要换不同的设备树文件就行,不用每个板子都重新编译内核。
设备树有三个核心概念:DTS是设备树源文件,是人可以读写的文本格式;DTC是设备树编译器,把DTS编译成二进制;DTB是编译后的二进制文件,内核启动的时候bootloader把DTB的地址传给内核,内核解析DTB来识别硬件。
为什么要引入设备树?
在设备树出现之前,ARM Linux的每个开发板都要在内核里写一套板级描述代码,导致内核里板级文件越来越多,维护起来很乱。引入设备树后,硬件描述和内核代码分离,内核更通用,维护成本大大降低。
2. 基本语法
设备树的语法很像C语言的结构体,但有自己的规则。最外层是根节点,用斜杠表示,里面嵌套各种子节点,每个节点可以有属性和子节点。
节点的命名规则一般是"设备类型@单元地址"。设备类型描述设备是什么,比如uart、i2c、gpio;@后面的单元地址一般是设备的基地址。如果设备没有地址,可以不写@和地址。同类型的多个设备用不同的地址来区分。
3. 属性类型
设备树的属性值有几种常见类型:
4. 常用重要属性
5. 添加一个新设备节点的步骤
实际项目中添加一个新设备的设备树节点,我一般按这个流程来:
第一步:找对位置
先确定这个设备挂在哪个总线上,就加到对应的总线节点下面。比如I2C设备加到i2c节点下面,SPI设备加到spi节点下面,内存映射的外设加到soc节点下面。
第二步:基本属性
写compatible属性,确保和驱动的match表对应;写reg属性,根据设备的地址或片选号设置;如果有中断,配置interrupts和interrupt-parent。
第三步:引脚配置
如果设备有专用引脚,在iomux或者pinctrl节点里加引脚配置,然后在设备节点里用pinctrl-0引用。
注意事项
添加节点时要注意:第一,节点名按规范来,用设备类型@地址的格式;第二,reg的格式要看父节点的#address-cells和#size-cells,位数不对编译会报错;第三,时钟、电源域这些容易漏掉的依赖也要配置上,否则驱动probe可能失败;第四,状态要设对,不使用的设备设为disabled。
三、追问环节
追问1:dts和dtsi是什么关系?怎么包含?
dtsi是设备树的头文件,include的意思。一般芯片厂商会把芯片通用的硬件描述放在dtsi文件里,比如CPU、内存控制器、外设控制器这些所有开发板都一样的部分。然后每个具体的开发板有自己的dts文件,include对应的dtsi,再根据板子的具体情况增加或修改节点。
包含的方式是用/include/ "xxx.dtsi" 语法,跟C语言的include类似。但设备树的include还有一个特性:后定义的同名节点会和前面的合并,属性如果重名会覆盖前面的值。这个特性很重要,板级dts就是通过覆盖dtsi里的属性来定制的,比如把某个外设的status从disabled改成okay。
追问2:设备树编译错误怎么排查?
编译错误一般分两类。一类是语法错误,DTC编译器会直接报哪一行哪个字符有问题,比如少了分号、括号不匹配、属性值格式不对等,根据错误信息找到对应位置改就行。
另一类是逻辑错误,编译能过但运行不正常。常见的有几种:地址或中断号写错了,驱动probe的时候资源不对;compatible不匹配,驱动根本没被调用;pinctrl没配置,引脚功能不对;时钟没配置,外设拿不到时钟就工作不起来。排查的时候,先看内核启动日志里有没有相关的probe信息,有没有报错。如果驱动根本没加载,先检查compatible对不对;如果probe失败了,看失败的错误码是什么,根据错误码找原因。
追问3:设备树里的label和&引用怎么用?
label是给节点起一个别名,方便在其他地方引用。在节点名前面加个标签名加冒号,比如uart1: uart@40010000 { ... };,这样在其他地方就可以用&uart1来引用这个节点,往里面追加或修改属性。
这个语法在板级dts里非常常用。比如dtsi里定义了uart1节点,板级dts想启用它,不用重写整个节点,只要写&uart1 { status = "okay"; }; 就行,简洁很多。也可以用这个语法往已有节点里加新属性或覆盖旧属性。
追问4:aliases节点有什么用?
aliases节点是定义别名的地方,主要用来给设备编号。比如aliases里写serial0 = &uart1; serial1 = &uart2;,这样内核就知道哪个串口是ttyS0、哪个是ttyS1,设备号就固定了,不会因为驱动加载顺序变化而变。
aliases的另一个作用是给某些需要编号的设备排序,比如I2C总线、SPI总线、以太网接口等。没有aliases的话,设备编号可能按驱动probe顺序来,每次启动可能不一样。
四、失分表达
"设备树就是个配置文件,照着抄就行"
显得很不专业。设备树虽然不难,但也是有规范和机制的,要能讲清楚语法、属性格式、驱动匹配这些。
"compatible就是个名字,随便写没事"
compatible必须和驱动里的of_match_table严格对应,写错了驱动根本匹配不上,这是非常基础的错误。
五、准备动作
面试官你好,关于Linux设备树的语法和编写要点,以上是我的理解和经验
要不要我具体举一个添加I2C设备节点的完整例子?

扫码关注




























