优化 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 的内部测试中,在 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 {
        ...
    };
    ...
};

使用压缩覆盖层

Android 9 增加了对在 DTBO 映像中使用压缩覆盖层的支持,当使用 DT 表头版本 1 时。当使用 DTBO 标头 v1 时,*dt_table_entry* 中 flags 字段的最低有效四位指示 DT 条目的压缩格式。

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. 调用 ufdt_apply_overlay() 以 DTO 获取 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);
}