很多人做嵌入式 Linux bring-up 时,最先遇到的不是驱动不会写,而是 DTS 怎么写都不顺。一个节点看起来已经把 reg、interrupts、clocks 都填了,内核启动后设备还是起不来;一个板级 dts 明明只是改了几个 GPIO,结果连别的外设也跟着异常。最后大家常常把问题归结成“设备树太烦了”,其实更准确的说法应该是:设备树本身没有那么难,难的是很多人没有按硬件建模思路去写它。
Linux 设备树的价值,不只是“把硬件描述从 C 代码里搬出来”。更重要的是,它把 SoC 公共信息、板级差异、驱动匹配关系、资源声明方式统一到了一个结构化模型里。驱动在 probe() 阶段读到的寄存器地址、中断号、时钟、pinctrl 配置,本质上都来自这份硬件模型。你如果在 DTS 层就把建模做歪了,后面驱动再优雅也救不回来。
所以做 DTS 开发时,真正要建立的是三个意识:第一,节点不是“为了编译过”而写,而是为了准确描述硬件;第二,dtsi 和 dts 的职责要分开;第三,设备树调试是内核 bring-up 的一部分,不能等驱动写完再补。
坑一:把 SoC 公共能力和板级差异混在一起
最常见的问题,就是把本来应该放在 xxx-soc.dtsi 的通用控制器节点,直接写进板级 dts;或者反过来,把板子独有的引脚、电源使能、外设启停也塞进通用 dtsi。这样短期看省事,后面一旦同一颗 SoC 派生两三块板子,维护成本会迅速爆炸。
更合理的原则是:
- SoC 公共外设框架、地址空间、控制器能力放
dtsi - 复用率高的公共外设组,再抽到独立 include 片段
坑二:compatible 写得模糊,导致驱动匹配错位
很多人以为 compatible 只是个字符串,能匹配上就行。实际上,这个字段直接影响驱动绑定顺序和兼容策略。写得太随意,后面驱动升级或复用时问题就会冒出来。
一个典型节点应该至少把资源关系写清楚:

my_uart@40004000 { compatible = "xingu,my-uart"; reg = <0x40004000 0x1000>; interrupts = <42>; clocks = <&clkctrl 5>; pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay";};
这段描述的价值不在于字段多,而在于它把驱动真正需要的硬件入口都建出来了。驱动进入 probe() 之后,可以通过统一的 OF API 去拿寄存器、中断和时钟,不需要再硬编码平台差异。
坑三:只写节点,不验证驱动侧到底怎么取资源
很多 DTS 问题最后变成驱动问题,本质上是两边没有对齐。比如节点里配了中断,但驱动拿资源时用的是错误索引;节点里有多个时钟,驱动却默认只取一个 unnamed clock。你如果不把驱动侧资源获取逻辑一起看,DTS 很容易变成“看起来完整,实际不可用”。

staticint mydev_probe(struct platform_device *pdev){struct resource *res;void __iomem *base;int irq;struct clk *clk; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(base))return PTR_ERR(base); irq = platform_get_irq(pdev, 0);if (irq < 0)return irq; clk = devm_clk_get(&pdev->dev, NULL);if (IS_ERR(clk))return PTR_ERR(clk);return devm_request_irq(&pdev->dev, irq, mydev_irq, 0, dev_name(&pdev->dev), base);}
驱动侧这样写,DTS 就要确保资源顺序和命名符合预期。否则你会看到一种很典型的现象:节点“看着都在”,驱动也能进 probe(),但设备就是不工作。
坑四:status = "okay" 只会改,不理解继承关系
很多工程师知道把某个节点从 disabled 改成 okay,却没意识到设备树是带继承和覆盖关系的。一个节点即便在 dtsi 里写成了 okay,后续 include 的板级文件仍然可能把它关掉;反过来也一样。带着这种理解去排查,效率会高很多。
坑五:pinctrl、电源和时钟经常被当成“附属配置”
实际 bring-up 里,最容易出问题的往往不是 reg,而是 pinctrl、电源域和时钟。很多外设节点表面上描述齐了,真正缺的却是 pinmux 没切、供电没拉起、父时钟没配。DTS 层如果只盯着设备节点本身,很容易漏掉这些依赖。
坑六:不做 schema 和启动日志联动检查
现在 Linux 主线越来越强调 YAML binding 和 schema 校验,这不是形式主义,而是为了把很多低级错误在编译阶段就拦住。即便你暂时没完整接入 schema,也至少应该形成一个习惯:改完 DTS 后,同时看 dtc 警告、内核启动日志和驱动 probe() 日志,把三者对齐。
一个更高效的 DTS 开发顺序
真正高效的做法通常不是“先写一大坨节点再启动”,而是:先确认 binding,再搭 SoC 公共节点,再补板级覆盖,接着联动驱动侧资源获取,最后用日志和 schema 反复收口。这样每一步都知道自己在验证什么,而不是靠猜。
Linux 设备树开发真正难的地方,不在语法,而在建模。只要你把 SoC 公共能力、板级差异、资源依赖和驱动入口这四层关系理顺,DTS 就不会再像黑盒。写得好的设备树,价值不只是“让驱动跑起来”,而是让你的平台后续更容易移植、裁剪和维护。这一点,在量产项目里尤其重要。
大家好,我是四哥,一个深耕嵌入式14年的老工程师。
分享大家一份不错的C语言电子书,以非常通俗的语言跟大家讲解C语言,把复杂的技术讲得连小学生都能听得懂,绝不是AI生成那种晦涩难懂的电子垃圾。
免费领取,下方扫码添加,备注「C语言」👇:
C语言电子书目录如下: