优化 DTO

本页介绍了可以对设备树叠加层 (DTO) 实现进行的优化,描述了针对叠加根节点的限制,并详细介绍了如何在 DTBO 映像中配置经过压缩的叠加层。此外还提供了示例实现说明和代码。

内核命令行

设备树 (DT) 中的原始内核命令行位于 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 的内部测试中,分别在 2,405 个 .dtb 和 283 个 .dtbo DT 节点上使用 libufdt,在编译后分别会生成 70,618 字节和 8,566 字节的文件。从 FreeBSD 移植的 DTO 实现的运行时为 124 毫秒,相比之下 libufdt DTO 的运行时为 10 毫秒,差异非常明显。

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

  • 500 次叠加(附加或覆盖)操作具有 6-8 倍的时间差异
  • 1,000 次叠加(附加或覆盖)操作具有 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 {
        ...
    };
    ...
};

使用经过压缩的叠加层

Android 9 增加了以下支持:在使用第 1 版 DT 表格头文件时,在 DTBO 映像中使用经过压缩的叠加层。使用 DTBO 头文件 v1 时,dt_table_entry 中标记字段的四个最低有效位会指明 DT 条目的压缩格式。dt_table_entry

struct dt_table_entry_v1 {
  uint32_t dt_size;
  uint32_t dt_offset;  /* offset from head of dt_table_header */
  uint32_t id;         /* optional, must be zero if unused */
  uint32_t rev;        /* optional, must be zero if unused */
  uint32_t flags;      /* For version 1 of dt_table_header, the 4 least significant bits
                        of 'flags' are used to indicate the compression
                        format of the DT entry as per the enum 'dt_compression_info' */
  uint32_t custom[3];  /* optional, must be zero if unused */
};

目前,系统支持 zlibgzip 压缩。 ‏

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9 增加了以下支持:在 VtsFirmwareDtboVerification 测试中测试经过压缩的叠加层,以帮助您验证叠加应用的正确性。

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 以启动内核:
      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);
}