2. 设备树基础
在 Linux 设备树驱动适配工程中,最先要建立起来的不是“怎么写节点”,而是“源码、头文件、编译器、二进制、内核解析”这条链路之间的对应关系。很多初学者把 DTS、DTSI、DTB、DTC 混为一谈,导致在排查“驱动没匹配上”“节点被覆盖”“属性读不到”时无从下手。本节先把这四者的角色讲清楚,再进入设备树语法、节点、属性、标签与引用的核心规则。
一、DTS、DTSI、DTB、DTC 的职责与关系
设备树源码通常以 .dts 和 .dtsi 两种文本形式存在。.dts 是面向某一块具体板子的顶层描述文件,.dtsi 是可被包含的公共片段,类似 C 语言里的头文件。DTC(Device Tree Compiler)是设备树编译器,它把文本源码编译成内核可解析的二进制格式 DTB(Device Tree Blob)。内核启动时由 bootloader 把 DTB 地址传给内核,内核再展开为设备节点树,供 platform driver、i2c driver 等匹配使用。
因此,四者的关系可以概括为:DTS/DTSI 是源,DTC 是工具,DTB 是产物,内核是消费者。理解这条链路,是后续所有适配工作的基础。
二、设备树语法:节点、属性、标签与引用
设备树文本的语法非常简洁,核心只有两类元素:节点(node)和属性(property)。节点用大括号表示层级,属性用“键 = 值”表示。节点名通常写成 name@address 的形式,其中 @ 后面的地址用于区分同一类设备的多个实例。
/dts-v1/;
#include "soc.dtsi"
/ {
model = "My Board V1";
compatible = "vendor,myboard", "vendor,soc";
uart0: serial@10010000 {
compatible = "vendor,uart";
reg = <0x10010000 0x1000>;
interrupts = <10>;
status = "okay";
};
};
&uart0 {
pinctrl-names = "default";
pinctrl-0 = <&uart_pins>;
};
上面这段代码里出现了几个关键概念:uart0: 是标签(label),它给节点起了一个可被引用的别名;&uart0 是引用(reference),表示“找到标签为 uart0 的节点,并在此处追加或覆盖属性”;<&uart_pins> 是phandle 引用,用于把另一个节点作为参数传给当前属性。标签和引用是设备树实现“公共片段 + 板级差异”复用的关键机制。
三、属性类型与常见写法
属性值可以是空、字符串、32 位整数数组、字节数组或 phandle 引用。不同属性类型在驱动里读取方式不同,写错类型往往导致 of_property_read_u32() 返回失败。
| 属性写法 | 类型 | 典型用途 | 驱动读取接口 |
|---|---|---|---|
status = "okay"; |
字符串 | 使能/禁用节点 | of_device_is_available() |
reg = <0x1000 0x100>; |
u32 数组 | 寄存器基址与长度 | platform_get_resource() |
compatible = "v,uart"; |
字符串列表 | 驱动匹配 | of_match_device() |
pinctrl-0 = <&uart_pins>; |
phandle | 引用引脚配置节点 | of_parse_phandle() |
local-mac-address = [00 11 22 33 44 55]; |
字节数组 | MAC 地址等原始数据 | of_property_read_u8_array() |
四、典型场景:dtsi 公共定义 + dts 板级覆盖
实际项目中,SoC 厂商会提供一份 soc.dtsi,里面定义所有外设节点、寄存器地址和默认状态。板级 board.dts 只做三件事:包含 dtsi、修改 status、追加板级属性。例如:
/* soc.dtsi */
uart0: serial@10010000 {
compatible = "vendor,uart";
reg = <0x10010000 0x1000>;
status = "disabled";
};
/* board.dts */
#include "soc.dtsi"
&uart0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
};
这种“dtsi 定义骨架、dts 覆盖差异”的模式,是设备树工程化的核心。它让同一份 SoC 描述可以服务多块板子,减少重复维护。
uart0、i2c1、spi2,避免冲突。
五、常见误区与复盘
- 误区一:把 dtsi 当成“必须被编译的文件”。dtsi 本身不直接编译,它通过
#include被 dts 包含后一起编译。 - 误区二:认为 DTB 是文本。DTB 是二进制,调试时需要用
dtc -I dtb -O dts反编译回文本查看。 - 误区三:节点写了但驱动不 probe。优先检查
compatible是否与驱动 of_match_table 一致,再检查status是否为okay。 - 误区四:属性类型写错。例如把
reg写成字符串,驱动读取会失败,但编译不一定报错。
fdtdump 或 dtc -I dtb -O dts 检查实际生效的 DTB 内容。
掌握 DTS、DTSI、DTB、DTC 的关系,以及节点、属性、标签、引用的基本语法,是后续编写和调试设备树驱动的前提。只有先看清“源码如何变成内核里的 device_node”,才能在驱动适配时快速定位问题所在。