优化 DTO

本页详细介绍了您可以对 DTO 实现进行哪些优化,描述了针对叠加根节点的限制,并提供了示例实现说明和代码。

内核命令行

设备树中的原始内核命令行位于 chosen/bootargs 节点中。引导加载程序必须将此位置与内核命令行的其他源进行串联:

/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};

DTO 无法串联主 DT 和叠加 DT 的值。建议您将主 DT 的内核命令行置入 chosen/bootargs 中,将叠加 DT 的内核命令行置入 chosen/bootargs_ext 中。接下来,引导加载程序会合并这些位置,并将结果传递给内核。

main.dts overlay.dts

/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};

/dts-v1/;
/plugin/;

&chosen {
  bootargs_ext = "...";
};

libufdt

虽然最新的 libfdt 支持 DTO,但是我们建议您使用 libufdt 来实现 DTO(源文件位于AOSP 中的 platform/system/libufdt 下)。libufdt 会从扁平化设备树 (FDT) 编译真实的树结构(非扁平化设备树,简称为 ufdt),因而可以改善两个 .dtb 文件(从 O(N2) 到 O(N),其中 N 是树中的节点编号)的合并。

性能测试

在 Google 的内部测试中,进行编译后,在 2405 .dtb 和 283 .dtbo DT 节点上使用 libufdt 生成了 70,618 字节和 8,566 字节的文件大小。与从 FreeBSD 移植的 DTO 实现(运行时为 124 毫秒)相比,libufdt DTO 运行时为 10 毫秒。

在 Pixel 设备的性能测试中,我们比较了 libufdtlibfdt。基本节点数量带来的影响相似,但包含以下差异:

  • 500 次叠加(附加或覆盖)操作具有 6〜8 倍的时间差异
  • 1000 次叠加(附加或覆盖)操作具有 8〜10 倍的时间差异

附加计数设置为 X 的示例:

图 1 附加计数为 X。

覆盖计数设置为 X 的示例:

图 2. 覆盖计数为 X。

libufdt 是用一些 libfdt API 和数据结构开发的。使用 libufdt 时,您必须包含并链接 libfdt(不过,您可以在代码中使用 libfdt API 来操作 DTB 或 DTBO)。

libufdt DTO API

libufdt 中适用于 DTO 的主要 API 如下:

struct fdt_header *ufdt_apply_overlay(
        struct fdt_header *main_fdt_header,
        size_t main_fdt_size,
        void *overlay_fdt,
        size_t overlay_size);

参数 main_fdt_header 是主 DT,overlay_fdt 是包含 .dtbo 文件内容的缓冲区。返回值是一个包含合并的 DT 的新缓冲区(如果出现错误,则返回 null)。合并的 DT 会在 FDT 中进行格式化,您可以在启动内核时将其传递给内核。

来自返回值的新缓冲区由 dto_malloc()(您应在将 libufdt 移植到引导加载程序时加以实现)创建。有关参考实现,请参阅 sysdeps/libufdt_sysdeps_*.c

根节点限制

您不能将新节点或属性叠加到主 DT 的根节点,因为叠加操作依赖于标签。由于主 DT 必须定义一个标签,而叠加 DT 则会分配要叠加标签的节点,因此,我们无法为根节点提供标签(因而不能叠加根节点)。

SoC 供应商必须定义主 DT 的叠加能力;ODM/OEM 只能使用由 SoC 供应商定义的标签附加或叠加节点。要解决这个问题,您可以在基础 DT 中的根节点下定义一个 odm 节点,使叠加 DT 中的所有 ODM 节点都能够添加新节点。或者,您也可以将基础 DT 中的所有 SoC 相关节点放在根节点下的 soc 节点中,如下所述:

main.dts overlay.dts

/dts-v1/;

/ {
    compatible = "corp,bar";
    ...

    chosen: chosen {
        bootargs = "...";
    };

    /* nodes for all soc nodes */
    soc {
        ...
        soc_device@0: soc_device@0 {
            compatible = "corp,bar";
            ...
        };
        ...
    };

    odm: odm {
        /* reserved for overlay by odm */
    };
};

/dts-v1/;
/plugin/;

/ {
};

&chosen {
    bootargs_ex = "...";
};

&odm {
    odm_device@0 {
        ...
    };
    ...
};

DTO 实现示例

以下说明介绍了使用 libufdt 进行 DTO 实现的示例过程(示例代码如下)。

示例 DTO 说明

  1. 包含库。要使用 libufdt,请包含 libfdt 以用于数据结构和 API:
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. 加载主 DT 和叠加 DT。将 .dtb.dtbo 从存储加载到内存中(确切的步骤取决于您的设计)。此时,您应该设置 .dtb/.dtbo 的缓冲区和大小:
    main_size = my_load_main_dtb(main_buf, main_buf_size)
    
    overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);
    
  3. 叠加 DT:
    1. 使用 ufdt_install_blob() 获取主 DT 的 FDT 头文件:
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. 对 DTO 调用 ufdt_apply_overlay() 以获取采用 FDT 格式的合并 DT:
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. 要获取 merged_fdt 的大小,请使用 dtc_totalsize()
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. 传递合并的 DT 以启动内核。当您启动内核时,请将合并的 DT 传递给内核:
      my_kernel_entry(0, machine_type, merged_fdt);
      

示例 DTO 代码

#include <libfdt.h>
#include <ufdt_overlay.h>

…

{
  struct fdt_header *main_fdt_header;
  struct fdt_header *merged_fdt;

  /* load main dtb into memory and get the size */
  main_size = my_load_main_dtb(main_buf, main_buf_size);

  /* load overlay dtb into memory and get the size */
  overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);

  /* overlay */
  main_fdt_header = ufdt_install_blob(main_buf, main_size);
  main_fdt_size = main_size;
  merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                  overlay_buf, overlay_size);
  merged_fdt_size = dtc_totalsize(merged_fdt);

  /* pass to kernel */
  my_kernel_entry(0, machine_type, merged_fdt);
}