第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.dtspda_release.dts

4.5 精简后的效果评估

精简完DTS后,怎么知道效果好不好?

我一般用两个指标来衡量:

  1. DTS解析时间。在内核启动日志里,搜索 OF: fdt 相关的打印。可以看到内核解析设备树花了多少时间。精简后,这个时间应该明显缩短。
  2. 总启动时间。initcall_debug 内核参数启动,可以看到每个驱动的初始化时间。那些被删掉的节点对应的驱动,自然就不会再出现了。

我记得有一次,帮一个客户优化PDA启动。他们的DTS里居然还保留了一个SATA控制器节点。我问他们PDA上有没有SATA接口,他们说没有。我说那为什么留着?他们说“万一以后要用呢”。

这就是典型的“想太多”。嵌入式开发,尤其是PDA这种对启动速度有要求的设备,一定要有“最小化”思维。现在用不到的,就是多余的。删掉它,让系统跑得更快。

好了,这一章的内容就到这里。下一章咱们聊聊内核配置的裁剪,那是另一个能省下不少启动时间的地方。