第2章:设备树基础与调试——让硬件描述不再神秘
设备树(Device Tree),说白了就是一份硬件描述清单。Linux内核通过它才知道你板子上接了哪些外设、内存多大、中断怎么连的。我刚接触Jacinto平台时,第一件事就是翻它的设备树——嗯,不看这个,你连GPIO都点不亮。
2.1 设备树语法与绑定规则
设备树的语法其实不复杂。一个节点就是一个设备,属性就是它的配置参数。我习惯把设备树想象成JSON的树形版本,只不过它用文本文件写,编译后变成二进制。
核心语法三要素:
- 节点(Node):用
/ { }定义,根节点是/ - 属性(Property):键值对,值可以是字符串、整数、字节数组
- 标签(Label):用
&label引用,方便覆盖或追加
/dts-v1/;
/ {
model = "TI Jacinto 7 EVM";
compatible = "ti,j721e-evm", "ti,j721e";
memory@80000000 {
device_type = "memory";
reg = <0x00000000 0x80000000>;
};
chosen {
stdout-path = &main_uart0;
};
};
绑定规则(Binding)是设备树的“契约”。每个外设驱动都会声明它需要哪些属性。比如I2C控制器,必须有 #address-cells 和 #size-cells,否则驱动解析地址时会出错。我在项目中遇到过有人漏写 #address-cells,结果I2C设备一个都枚举不出来——查了两天才发现。
| 属性 | 含义 | 必填 |
|---|---|---|
compatible |
驱动匹配字符串 | 是 |
reg |
寄存器地址/长度 | 是 |
interrupts |
中断号与触发类型 | 视设备而定 |
clocks |
时钟句柄 | 视设备而定 |
我的习惯:写设备树前,先去内核源码 Documentation/devicetree/bindings/ 下翻对应厂商的YAML绑定文件。TI的绑定文档写得还算清楚,照着填不容易翻车。
2.2 Jacinto平台设备树结构分析
Jacinto 7(DRA829/TDA4VM)的设备树结构,我总结为“三层嵌套+两大片区”。
- 第一层:SoC dtsi(如
k3-j721e.dtsi)——定义芯片内部所有IP,包括Cortex-A72、Cortex-R5F、C7x DSP、各种外设控制器。这一层基本不动。 - 第二层:SOM dtsi(如
k3-j721e-som-p0.dtsi)——定义板级模块,比如DDR颗粒型号、eMMC、PMIC。这一层偶尔改。 - 第三层:Board dts(如
k3-j721e-evm.dts)——最终板级配置,GPIO复用、外设使能、引脚mux全在这里。这一层天天改。
两大片区指的是 主域(Main Domain) 和 MCU域(MCU Domain)。Jacinto把安全关键的外设放在MCU域,由R5F管理;主域跑Linux。设备树里用 &main_* 和 &mcu_* 区分。
// 主域UART
&main_uart0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&main_uart0_pins_default>;
};
// MCU域I2C
&mcu_i2c0 {
status = "okay";
clock-frequency = <400000>;
};
注意:Jacinto的引脚复用(Pinmux)必须在设备树里配好,否则外设不工作。我曾经漏配了一组SPI的引脚,结果SPI通信一直超时——不是驱动问题,是引脚根本没连上。
2.3 设备树编译与反编译
设备树源文件(.dts)需要编译成二进制(.dtb)才能被内核加载。工具链很简单:
# 编译
dtc -I dts -O dtb -o k3-j721e-evm.dtb k3-j721e-evm.dts
# 反编译(调试时常用)
dtc -I dtb -O dts -o k3-j721e-evm.dts k3-j721e-evm.dtb
# 查看dtb内部结构
fdtdump k3-j721e-evm.dtb | less
我个人调试时最常用的是反编译。内核启动后,/sys/firmware/devicetree/base 下能看到实际生效的设备树。但有时候内核加载的dtb和源码不一致——反编译一下,一对比就知道问题出在哪。
避坑指南:dtc版本不同,编译结果可能有差异。我建议用内核自带的dtc(scripts/dtc/),别用系统自带的。曾经有个bug,系统dtc 1.5.0编译出来的dtb,内核解析中断时顺序错了——升级到1.6.0才解决。
2.4 设备树调试技巧:Overlay与动态加载
设备树覆盖(Overlay)是调试利器。你不需要重新编译整个dtb,只需要写一个overlay片段,动态加载到内核里。我经常用它来快速验证外设配置。
// my_spi_test.dts
/dts-v1/;
/plugin/;
&main_spi0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
spidev@0 {
compatible = "rohm,dh2228fv";
reg = <0>;
spi-max-frequency = <10000000>;
};
};
加载方法:
- 编译overlay:
dtc -@ -I dts -O dtb -o my_spi_test.dtbo my_spi_test.dts - 复制到 /lib/firmware/
- 通过configfs加载:
mkdir /sys/kernel/config/device-tree/overlays/spi_test
cat my_spi_test.dtbo > /sys/kernel/config/device-tree/overlays/spi_test/dtbo
动态加载的典型场景:
- 调试新外设时,先overlay验证,确认无误再合入主dts
- 运行时切换引脚复用功能(比如GPIO和PWM互切)
- 热插拔FPGA子卡,动态加载对应设备树
卸载也很简单:rmdir /sys/kernel/config/device-tree/overlays/spi_test。内核会自动移除对应设备。
我曾经踩过的坑:overlay里引用的标签(&main_spi0)必须在基础dtb中存在。如果基础dtb里这个节点被删了(status = "disabled"且没有保留phandle),overlay加载时会报“symbol not found”。解决办法:基础dtsi里给所有可能被覆盖的节点加上标签,哪怕暂时不用。
知识结构总览
下面这张图是我自己整理的设备树调试知识体系,你可以把它当作快速索引。