第4章:设备树(DTS)精简:只保留PDA必需硬件节点
各位做PDA启动优化的朋友,咱们今天聊一个特别实在的话题——设备树精简。
我刚开始做PDA项目时,也犯过一个低级错误。从开发板参考设计里直接拿了一份DTS,稍微改改就烧进去了。结果呢?启动时间多了将近两秒。两秒啊,在PDA这种对开机速度极其敏感的设备上,这简直是灾难。
后来我仔细一查,发现DTS里塞了一大堆PDA根本用不上的硬件节点。什么HDMI控制器、PCIe根复合体、SATA控制器……这些在PDA上压根不存在。但内核在启动时,还是会逐个去解析、去尝试初始化。说白了,这就是在浪费CPU时间。
所以,设备树精简,是PDA启动优化里性价比极高的一步。你不需要改内核代码,只需要把DTS里多余的节点删掉,就能立竿见影地缩短启动时间。
4.1 为什么要精简DTS?
先问大家一个问题:内核在启动时,是怎么知道板子上有哪些硬件的?
答案就是设备树。内核会逐条解析DTS里的节点,然后为每个节点调用对应的驱动初始化函数。哪怕这个硬件根本不存在,内核也会尝试去匹配驱动、去访问寄存器。这个过程,每一步都在消耗时间。
我做过一个实验。在某个PDA平台上,原始DTS有大约1200个节点。经过精简后,只保留了大约400个节点。启动时间从原来的8.2秒降到了7.1秒。整整省了1.1秒。
你想想看,这1.1秒里,内核少做了多少无用功?
核心原则:DTS里只放PDA真正用到的硬件节点。一个多余的都不要留。
4.2 哪些节点可以删?
我总结了一个“可删除节点清单”,大家在做精简时可以对照着来。当然,具体到你的PDA硬件配置,可能还会有些差异。
| 节点类型 | 典型节点名 | PDA上是否必需 | 说明 |
|---|---|---|---|
| 显示接口 | hdmi, dsi, edp | 通常不需要 | PDA一般用内置LCD,HDMI/DP等外接显示接口可以删 |
| 高速外设 | pcie, sata, usb3.0 | 不需要 | PDA很少用到PCIe和SATA,USB通常只需要Host模式 |
| 多媒体 | gpu, vpu, codec | 视情况而定 | 如果PDA不需要视频编解码,可以删掉 |
| 调试接口 | uart_dbg, jtag | 量产版不需要 | 调试阶段保留,量产版务必删除 |
| 电源管理 | pmic, regulator | 需要保留 | 但可以精简掉未使用的regulator通道 |
| 传感器 | accelerometer, gyro | 视PDA功能而定 | 如果PDA没有陀螺仪,对应的节点就删掉 |
嗯,这里要注意一点。有些节点虽然PDA用不到,但它们是其他必需节点的依赖。比如某个时钟控制器,虽然PDA没有直接用到它的输出,但其他外设需要它提供时钟。这种节点就不能删。
怎么判断?我的习惯是:先删掉,然后编译内核,看看会不会报错。如果报错说某个节点找不到,那就加回来。反复几次,就能得到一个最小化的DTS。
4.3 实战:一步步精简DTS
光说不练假把式。咱们拿一个实际的例子来走一遍流程。
假设原始DTS文件叫 pda_original.dts,里面有一个HDMI节点:
hdmi: hdmi@fe000000 {
compatible = "rockchip,rk3399-hdmi";
reg = <0x0 0xfe000000 0x0 0x20000>;
interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH 0>;
pinctrl-names = "default";
pinctrl-0 = <&hdmi_ctl>;
power-domains = <&power RK3399_PD_HDCP>;
status = "okay";
};
这个节点在PDA上完全用不到。直接删掉。
再比如,有一个PCIe节点:
pcie0: pcie@f8000000 {
compatible = "rockchip,rk3399-pcie";
reg = <0x0 0xf8000000 0x0 0x2000000>;
interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH 0>;
...
status = "okay";
};
删掉。PDA上哪来的PCIe插槽?
但有些节点,比如I2C控制器,虽然PDA可能只接了一个触摸屏,但I2C控制器本身是必需的。你不能把整个I2C节点删掉,但可以删掉它下面未使用的子节点。
举个例子:
&i2c1 {
status = "okay";
touchscreen@38 {
compatible = "edt,edt-ft5406";
reg = <0x38>;
...
};
accelerometer@1d {
compatible = "st,lis3dh";
reg = <0x1d>;
...
};
};
如果PDA没有加速度计,就把 accelerometer@1d 这个子节点删掉。内核就不会再去解析它,也不会尝试初始化加速度计驱动。
我的小技巧:在DTS里搜索 status = "okay",这些是使能了的节点。逐个检查,看PDA是否真的需要。不需要的就改成 status = "disabled" 或者直接删掉。我个人习惯直接删掉,因为disabled的节点内核还是会解析,只是不初始化而已,仍然会消耗一点点时间。
4.4 避坑指南
精简DTS看起来简单,但坑也不少。我把自己踩过的坑分享给大家。
我曾经犯过的错:有一次,我把一个看起来没用的GPIO控制器节点删掉了。结果系统启动到一半就卡死了。后来排查了半天,发现那个GPIO控制器虽然PDA没有直接用到,但它是系统复位逻辑的一部分。删掉之后,复位信号拉不出来,系统就死在那了。
所以,我的建议是:
- 不要一次性删太多。每次删几个节点,编译、烧录、测试。确认没问题了再继续删。
- 保留备份。每次修改前,把原始DTS备份一份。万一出了问题,可以快速回退。
- 关注依赖关系。有些节点之间通过
phandle引用。删掉一个节点前,先搜索一下有没有其他地方引用了它。 - 量产版和调试版分开。调试阶段保留UART、JTAG等调试接口。量产版再删掉。我习惯维护两份DTS:
pda_debug.dts和pda_release.dts。
4.5 精简后的效果评估
精简完DTS后,怎么知道效果好不好?
我一般用两个指标来衡量:
- DTS解析时间。在内核启动日志里,搜索
OF: fdt相关的打印。可以看到内核解析设备树花了多少时间。精简后,这个时间应该明显缩短。 - 总启动时间。用
initcall_debug内核参数启动,可以看到每个驱动的初始化时间。那些被删掉的节点对应的驱动,自然就不会再出现了。
我记得有一次,帮一个客户优化PDA启动。他们的DTS里居然还保留了一个SATA控制器节点。我问他们PDA上有没有SATA接口,他们说没有。我说那为什么留着?他们说“万一以后要用呢”。
这就是典型的“想太多”。嵌入式开发,尤其是PDA这种对启动速度有要求的设备,一定要有“最小化”思维。现在用不到的,就是多余的。删掉它,让系统跑得更快。
好了,这一章的内容就到这里。下一章咱们聊聊内核配置的裁剪,那是另一个能省下不少启动时间的地方。