在嵌入式Linux开发中,设备树(Device Tree)作为硬件描述的核心机制,其设计哲学是"描述而非编码"。当我们遇到需要修改平台级设备树节点属性时,直接操作平台提供的.dts文件往往会引发一系列问题。以memory_ssmr_features节点为例,这个在mtxxx.dts中定义的节点带有全局标签,理论上可以被任何设备树文件引用——但实际操作中却会遇到语法冲突。
问题的根源在于设备树编译器的处理机制。每个.dts文件都必须包含完整的设备树结构:
/dts-v1/;/当我们在项目DTS中直接#include "mediatek/mtxxx.dts"时,相当于将两个完整的设备树文件强行合并,导致:
/dts-v1/;声明这种结构违背了设备树语法规范,dtc编译器会直接报错终止编译。我曾在一个客户项目中亲眼见过工程师花费三天时间排查这类问题,最终发现是多个.dts文件包含导致的层级混乱。
.dtsi(Device Tree Source Include)文件是解决这个问题的金钥匙。与完整的.dts不同,.dtsi的特点是:
通过将平台mtxxx.dts转换为mtxxx.dtsi,我们实现了:
提示:在大型芯片厂商的BSP中,通常已经做好了这种分离。比如高通的
msm-xxx.dtsi和msm-xxx.dts就是典型范例。但某些平台可能未严格遵循这个规范。
bash复制cd arch/arm64/boot/dts/mediatek/
cp mtxxx.dts mtxxx.dtsi
需要进行的修改包括:
/dts-v1/;#include和节点定义修改后的文件结构示例:
dts复制// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2022 MediaTek Inc.
*/
#include <dt-bindings/clock/mtxxx-clk.h>
#include <dt-bindings/power/mtxxx-power.h>
memory_ssmr_features: memory-ssmr-features {
compatible = "mediatek,memory-ssmr-features";
svp-region-based-size = <0 0x18000000>;
// 其他属性保持不变...
};
// 其他SOC级节点定义...
原mtxxx.dts应简化为:
dts复制/dts-v1/;
#include "mtxxx.dtsi"
在xxxxxx_xxx.dts中:
dts复制/dts-v1/;
#include "mtxxx.dtsi"
&memory_ssmr_features {
svp-region-based-size = <0 0xc000000>;
};
推荐两种验证方式:
bash复制make dtbs
bash复制make arch/arm64/boot/dts/mediatek/xxxxxx_xxx.dtb
验证时建议使用fdtdump检查最终生成的属性值:
bash复制fdtdump xxxxxx_xxx.dtb | grep svp-region
设备树中的标签(如memory_ssmr_features:)本质上是一种编译时的符号引用。其作用域规则表现为:
#include形成的包含链决定了标签的可见性这种设计带来一个重要特性:我们可以通过包含关系控制标签的可见性,而不需要像C语言那样使用头文件保护宏。
当使用&label语法覆盖属性时,设备树编译器实际上执行的是:
这个过程发生在编译阶段,不会影响原始.dtsi文件。我在调试一个摄像头驱动时曾通过这种方式动态修改了i2c频率,而无需重新定义整个i2c控制器节点。
dts复制/dts-v1/;
/* 错误!标签还未定义 */
&memory_ssmr_features {
svp-region-based-size = <0 0xc000000>;
};
#include "mtxxx.dtsi"
dts复制&memory_ssmr_features {
/* 只覆盖部分属性会导致其他属性被重置为默认值 */
svp-region-based-size = <0 0xc000000>;
};
dts复制#include "../../../mtxxx.dtsi" // 脆弱的相对路径
code复制dts/
├── vendor/
│ ├── platform.dtsi
│ └── soc.dtsi
└── project/
├── board.dts
└── overlay.dtsi
makefile复制ifeq ($(CONFIG_PROJECT_A),y)
DTS_FILE := project_a.dts
endif
dts复制/*
* v1.1 - 2023.05.20
* - 修改内存分区配置
* - 增加GPIO覆盖
*/
对于需要多层继承的项目,可以采用:
dts复制// base.dtsi
node {
prop = <1>;
};
// middle.dtsi
#include "base.dtsi"
&node {
prop = <2>;
};
// final.dts
#include "middle.dtsi"
&node {
prop = <3>;
};
最终prop的值为3,形成完整的覆盖链。
通过预处理宏实现差异化配置:
dts复制#ifdef CONFIG_HIGH_MEM
&memory_ssmr_features {
svp-region-based-size = <0 0x10000000>;
};
#else
&memory_ssmr_features {
svp-region-based-size = <0 0x8000000>;
};
#endif
bash复制gcc -E -x assembler-with-cpp -P xxxxxx_xxx.dts -o preprocessed.dts
bash复制dtc -I dts -O dts xxxxxx_xxx.dts | grep memory_ssmr_features
bash复制fdtdump old.dtb > old.txt
fdtdump new.dtb > new.txt
diff -u old.txt new.txt
在实际项目中,我曾用这些方法快速定位过一个内存分区配置被意外覆盖的问题。当时发现某个项目的配置始终不生效,最终排查是第三方模块的.dtsi中包含了同名但不同值的属性定义。