Device tree overlay

Device tree overlay

A Device Tree (DT) is a data structure that describes the named nodes and attributes of undiscoverable hardware. Kernels, such as the Linux kernel used in Android, use DT to support the various hardware configurations used by Android devices. Hardware vendors provide their ownDevice Tree Source (DTS) files that use theDevice Tree Compiler< /span>in binary format. flattened device tree file. These files are then used by the bootloader. The DTB file contains aDevice Tree Blob (DTB)Compile into

Device Tree Overlay (DTO)Enables a central device tree blob (DTB) to be overlaid on the device tree. A bootloader using DTOs can maintain system-on-chip (SoC) DTs and dynamically override device-specific DTs, adding nodes to the tree and changing properties in existing trees.

DTBO device tree blob for overlay

Updates in Android 9 version

In Android 9, the bootloader must not modify properties defined in the device tree overlay before passing the unified device tree blob to the kernel.

Load device tree

Loading the device tree in the bootloader involves building, partitioning, and running.

Figure 1.Typical implementation of loading the device tree in the bootloader.

  1. To create and refresh the device tree blob:

    1a. Use the device tree compiler ( dtc >) to compile the device tree source code ( .dts ) into a device tree blob (  file to a location accessible when the bootloader is running (see details below). .dtb ). Device tree blobs are formatted as flat device trees. 1b. Flash the.dtb

  2. To partition, identify a location in flash memory that is accessible and trusted by the bootloader runtime to place.dtb . Example location:

    boot partition

    Figure 2. By appending to image.gz and passing to kernel " =4>, put into the boot partition. mkbootimg.dtb

    unique partition

    Figure 3.Place.dtb in a unique partition (for example, thedtb partition).

  3. To load the device tree blob and start the kernel:

    3a. .dtbLoad from storage into memory. 3b. Start the kernel using the memory address of the loaded DT.

Implement DTO

Implementing a DTO involves splitting the device tree, building, partitioning, and running. After your implementation is working, you must also maintain compatibility between the two DTs and determine the strategy for ensuring the security of each DT partition.

Split DT

First split the device tree into two (2) parts:

  • Master DT. SoC-only access partial and default configurations provided by the SoC vendor.
  • Overlay DT. Device-specific configurations provided by Original Design Manufacturer (ODM)/Original Equipment Manufacturer (OEM).

After splitting the device tree, you must ensure compatibility between the main DT and the overlay DT to generate a complete DT for the device by merging the main DT and the overlay DT. For more information about DTO format and rules, see DTO Syntax. For more information about multiple device trees, seeMultiple DTs.

Build the main DT and overlay DT

To build the main DT, do the following:

  1. General DT .dts 编译为 .dtb text.
  2. Flash .dtb files to a partition accessible by the bootloader at runtime (see details below).

To build an overlay DT, do the following:

  1. Compiles overlay DT .dts into .dtbo file. Although this file format is the same as a .dtb file formatted as a flat device tree, it is distinguished from the main DT by a different file extension.
  2. Flash .dtbo files to a partition accessible by the bootloader at runtime (see details below).

For more information on compiling with DTC and validating DTO results on the host, seeCompilation and Verification.

Partition the DT

Determines a location in flash memory where the bootloader is accessible and trusted at runtime to place .dtb and .dtbo.

Example location of the main DT:

  • Part of the boot partition, attached to the kernel (image.gz).
  • A separate DT blob (.dtb), located in a private partition (dtb).

Example positions for superimposed DT:

Exclusive partition

Figure 1. Place .dtbo in a dedicated partition (for example, dtbo partition).

ODM partition

Figure 2. Place .dtbo into the odm partition (only if your bootloader can odm This is done only when data is loaded into the file system of the partition).

Note: The size of the overlay DT partition depends on the amount of changes required on the device and main DT blob. Typically, 8 MB is sufficient for current use and leaves room for future expansion if needed.

For devices that supportseamless (A/B) updates, use A/B to identify the primary DT and overlay DT partitions:

Example 1

图 3. DTBO division A/B, example 1.

Example 2

图 4. DTBO division A/B, example 2.

Run in bootloader

To run, do the following:

Figure 5. Typical runtime implementation of the device tree overlay in the bootloader.

  1. Loads .dtb from storage space into memory.
  2. Loads .dtbo from storage space into memory.
  3. is superimposed with .dtb .dtbo to form a merged DT.
  4. Start the kernel (given the memory address of the merged DT).

Maintain compatibility

The main DTB (from the SoC vendor) is considered the API surface of the DTBO. After you separate the device tree into SoC-generic parts and device-specific parts, you must ensure that the two parts are compatible with each other in the future, including:

  • DT definition in the main DT (e.g., node, attribute, label). Any definition changes in the master DT may trigger changes in the overlay DT. For example, if you need to correct a node name in the main DT, define an "alias" label that maps to the original node name (to avoid changing the overlay DT).
  • Storage location of overlay DT (e.g. partition name, storage format).

ensure safety

The bootloader must ensure that the DTB/DTBO is safe, unmodified, and uncorrupted. You can use any solution to protect DTB/DTBO, for example boot image signing or AVB in VBoot 1.0 Hash footer (VBoot 2.0).

  • If the DTB/DTBO is in a dedicated partition, you can add that partition to AVB's chain of trust. The chain of trust begins at the hardware-protected root of trust and proceeds to the bootloader, thereby verifying the integrity and authenticity of the DTB/DTBO partition.
  • If the DTB/DTBO is located in an existing partition (such as the odm partition), the partition should be in AVB's trust chain. (DTBO partitions can share a public key with odm partitions).

DTO syntax

 

Device Tree Source (DTS) format is a textual representation of a device tree. The Device Tree Compiler (DTC) can process this format into a binary device tree, which is the form required by the Linux kernel.

Use references

The DTC (Device Tree Compiler + Overlay Patch) project is at dtc-format.txt The DTS format is described in manual.txt . The DTO format and rules are described in dt-object-internal.txt . These documents explain how to update the main DT using nodes fragment@x and syntax __overlay__ in the overlay DT. For example:

/ {
  fragment@0 {
    target = <&some_node>;
      __overlay__ {
        some_prop = "okay";
        ...
      };
  };
};

However, Google strongly recommends that younotuse fragment@x and syntax __overlay__, use reference syntax instead. For example:

&some_node {
  some_prop = "okay";
  ...
};

dtc will compile the reference syntax into the same object as produced using the syntax __overlay__ above. This syntax does not force you to number the clips, allowing you to easily read and write overlay DTS. If your dtc does not support this syntax sugar, please use dtc from AOSP.

Use tags

To allow undefined references to nodes that do not exist at compile time, the header file of the overlay DT .dts file must have the /plugin/ tag. For example:

/dts-v1/;
/plugin/;

Here you locate the node to be overlaid using a reference, which is an absolute node path prefixed by an ampersand (&). For example, for node@0 in the main DT:

Define labels in main DT... ...and then use tags.

[my_main_dt.dts]

/dts-v1/;

/ {
  my_node: node@0 {
    status = "disabled";

    my_child: child@0 {
      value = <0xffffffff>;
    };
  };
};

[my_overlay_dt.dts]

/dts-v1/;
/plugin/;

&my_node {
  status = "okay";
};

&my_child {
  value = <0x1>;
};

Overlay

If the reference target attribute exists in the main DT, it is superimposed after the DTO; otherwise, it is appended. For example:

main.dts overlay.dts Merge results

[my_main_dt.dts]

/dts-v1/;

/ {
  compatible = "corp,foo";

  my_node: node@0 {
    status = "disabled";
  };
};

[my_overlay_dt.dts]

/dts-v1/;
/plugin/;

&my_node {
  status = "okay";
};

/dts-v1/;

/ {
  compatible = "corp,foo";

  ...

  node@0 {
    linux,phandle = <0x1>;
    phandle = <0x1>;
    status = "okay";
  };
};

Additional

If the referenced target attribute does not exist in the main DT, it is appended after the DTO. For example:

main.dts overlay.dts Merge results

[my_main_dt.dts]

/dts-v1/;

/ {
  compatible = "corp,foo";

  my_node: node@0 {
    status = "okay";
  };
};

[my_overlay_dt.dts]

/dts-v1/;
/plugin/;

&my_node {
  new_prop = "bar";
};

/dts-v1/;

/ {
  compatible = "corp,foo";

  ...

  node@0 {
    linux,phandle = <0x1>;
    phandle = <0x1>;
    status = "okay";
    new_prop = "bar";
  };
};

child node

Child node syntax example:

main.dts overlay.dts Merge results

[my_main_dt.dts]

/dts-v1/;

/ {
  compatible = "corp,foo";

  my_nodes: nodes {
    compatible = "corp,bar";

    node@0 {
      status = "disabled";
    };
  };
};

[my_overlay_dt.dts]

/dts-v1/;
/plugin/;

&my_nodes {
  new_prop1 = "abc";

  node@0 {
    status = "okay";
    new_prop2 = "xyz";
  };
};

/dts-v1/;

/ {
  compatible = "corp,foo";

  ...

  nodes {
    linux,phandle = <0x1>;
    phandle = <0x1>;
    compatible = "corp,bar";
    new_prop1 = "abc";

    node@0 {
      linux,phandle = <0x2>;
      phandle = <0x2>;
      status = "okay";
      new_prop2 = "xyz";
    };
  };
};

Compile and verify

You can use the Device Tree Compiler (DTC) to compile device tree source files. However, before applying the overlay DT to the target master DT, you should also verify the results by simulating the behavior of the DTO.

Compile via DTC

When compiling with dtc .dts , you must add the option -@ to the generated .dtbo Add __symbols__ node. __symbols__ node contains a list of all labeled nodes that the DTO library can use as a reference.

Example command to build the main DT .dts :

<span style="color:var(--devsite-code-color)">dtc -@ -O dtb -o my_main_dt.dtb my_main_dt.dts
</span>

Example command to build overlay DT .dts :

<span style="color:var(--devsite-code-color)">dtc -@ -O dtb -o my_overlay_dt.dtbo my_overlay_dt.dts
</span>

Note: If you encounter a DTC build error invalid option --'@', you may need to update your DTC version. In AOSP upstream, the official DTC supports DTO starting from version 1.4.4, and most of the patches have been merged after December 2016 . For DTO support, it is recommended that you use from AOSP, which is synchronized with the latest DTC (DTO patches have been merged where necessary). external/dtc

Verify DTO results on host

The verification process can help you identify errors that may occur when placing an overlay DT on top of the main DT. Before updating the target, you can verify the results of the overlay DT on the host by using  in to simulate the DTO behavior. .dts/include/

注意/include/ 不支持在叠加 DT 源中使用 __overlay__

图 1. 使用语法 /include/ 模拟主机上的 DTO

  1. 创建叠加层 .dts 的副本。在副本中,移除第一行头文件。示例:
    /dts-v1/;
    /plugin/;
    
    将文件另存为 my_overlay_dt_wo_header.dts(或您想使用的任何文件名)。
  2. 创建主 .dts 的副本。在副本中的最后一行后,为您在第 1 步中创建的文件附加 include 语法。例如:
    /include/ "my_overlay_dt_wo_header.dts"
    
    将文件另存为 my_main_dt_with_include.dts(或您希望的任何文件名)。
  3. 使用 dtc 编译 my_main_dt_with_include.dts 以获得合并的 DT,这应该与使用 DTO 进行编译所得到的结果相同。例如:
    <span style="color:var(--devsite-code-color)">dtc -@ -O dtb -o my_merged_dt.dtb my_main_dt_with_include.dts
    </span>
  4. 使用 dtc 转储 my_merged_dt.dto
    <span style="color:var(--devsite-code-color)">dtc -O dts -o my_merged_dt.dts my_merged_dt.dtb
    </span>

在 Android 9 中验证 DTO

Android 9 需要具有设备树 Blob 叠加层 (DTBO) 分区。要在 SoC DT 中添加节点或更改属性,引导加载程序必须在 SoC DT 之上动态叠加设备专用的 DT。

指示已应用的叠加层

为了保证供应商测试套件 (VTS) 能够评估叠加应用的正确性,供应商必须添加新的内核命令行参数 androidboot.dtbo_idx 来指示从 DTBO 分区中选择的叠加层。在内核版本为 5.10 或更高版本的 Android 12 环境中,此参数会通过 bootconfig 传递。例如,参数 androidboot.dtbo_idx=x,y,z 将 xy 和 z 报告为 DTBO 分区中已由引导加载程序按相同顺序应用于基础设备树 (DT) 的设备树叠加层 (DTO) 的索引,这些索引以零为起点。

叠加层可以应用于主设备树中的节点,也可以添加新节点,但不能引用之前叠加层中添加的节点。这种限制是必要的,因为叠加应用不会将叠加层符号表与主 DT 符号表合并(不合并的做法既可避免符号名称出现冲突,也可避免叠加层之间的依赖关系复杂化)。

示例:无效叠加层

在此示例中,overlay_2.dts 引用了由 overlay_1.dts 添加的节点 e。在将 overlay_1 应用于主 DT 之后,如果尝试将 overlay_2 应用于生成的 DT,叠加应用将会运行失败,并显示基础 DT 的符号表中不存在符号 e 的错误。

main.dts overlay_1.dts overlay_2.dts
[main.dts]

/dts-v1/;

/ {
  a: a {};
  b: b {};
  c: c {};
};
[overlay_1.dts]

/dts-v1/;
/plugin/;

&b { ref1 =  <&a>;
    e: e {
        prop = <0x0a>;
        phandle = <0x04>;
    };
};
[overlay_2.dts]

/dts-v1/;
/plugin/;

/* invalid! */
&e {
    prop = <0x0b>;
};
示例:有效叠加层

在此示例中,overlay_2.dts 仅引用了主 DTS 中的节点 b。将 overlay_1 和 overlay_2 依次应用于基础 DT 之后,节点 e 中属性 prop 的值(由 overlay_1.dts 设置)将被 overlay_2.dts 设置的值覆盖。

main.dts overlay_1.dts overlay_2.dts
[final.dts]

/dts-v1/;

/ {
  a: a {};
  b: b {};
  c: c {};
};
[overlay_1.dts]

/dts-v1/;
/plugin/;

&b { ref1 =  <&a>;
     e {
          prop = <0x0c>;
      };
};
[overlay_2.dts]

/dts-v1/;
/plugin/;

/* valid */
&b { ref1 =  <&c>;
     e {
          prop = <0x0d>;
      };
};

实现 DTBO 分区

要实现所需的 DTBO 分区,请确保引导加载程序可以执行以下操作:

  1. 识别它正在哪个开发板上运行,并选择要应用的相应叠加层。
  2. 将 androidboot.dtbo_idx 参数附加到内核命令行。
    • 该参数必须指示 DTBO 分区映像中按相同顺序应用于基础 DT 的 DTO 的索引,这些索引以零为起点。
    • 这些索引必须引用叠加层在 DTBO 分区中的位置。

要详细了解 DTBO 分区结构,请访问 source.android.com 上的设备树叠加层

验证 DTBO 分区

您可以使用 VTS 验证以下内容:

  • 内核命令行参数 androidboot.dtbo_idx 是否存在(方法:检查 Init 是否已自动设置相应的 ro.boot.dtbo_idx 系统属性)。
  • ro.boot.dtbo_idx 系统属性的有效性(方法:检查该属性是否至少指定了一个有效的 DTBO 映像索引)。
  • DTBO 分区的有效性(还应验证 DTBO 分区中应用于基础 DT 的叠加层的有效性)。
  • 生成的 DT 中的其他节点或属性更改是否已呈现给 Linux 内核。

例如,在以下叠加层和最终 DT 中,将 androidboot.dtbo_idx=5,3 添加到内核命令行可通过验证,而将 androidboot.dtbo_idx=3,5 添加到内核命令行不能通过验证。

索引 3 处的叠加层 DT 索引 5 处的叠加层 DT
[overlay_1.dts]

/dts-v1/;
/plugin/;

&c { prop = <0xfe>; };
[overlay_2.dts]

/dts-v1/;
/plugin/;

&c { prop = <0xff>; };
最终 DT
/dts-v1/;
/ {

	a {
		phandle = <0x1>;
	};

	b {
		phandle = <0x2>;
	};

	c {
		phandle = <0x3>;
		prop = <0xfe>;
	};

	__symbols__ {
		a = "/a";
		b = "/b";
		c = "/c";
	};
};

  

 

 使用多个 DT

很多 SoC 供应商和 ODM 都支持在一台设备上使用多个 DT,从而使一个映像能够为多个 SKU/配置提供支持。在这种情况下,引导加载程序会在运行时识别硬件,并加载相应的 DT:

图 1. 引导加载程序中的多个设备树叠加层。

注意:使用多个 DT 不是强制性要求。

设置

如需向 DTO 模型添加对多个 DT 的支持,请设置一个主 DT 列表和另一个叠加 DT 列表。

图 2. 多个 DT 的运行时 DTO 实现。

引导加载程序应该能够:

  • 读取 SoC ID 并选择相应的主设备树,并
  • 读取板 ID 并选择相应的叠加设备树。

只能选择一个主 DT 供在运行时使用。可选择多个叠加 DT,但它们必须与选定的主 DT 兼容。使用多个叠加层有助于避免 DTBO 分区内的每块板上都存储一个叠加层,并能让引导加载程序根据板 ID 或通过探测外设来确定所需叠加层的子集。例如,板 A 可能需要通过叠加层 1、3 和 5 添加的设备,而板 B 可能需要通过叠加层 1、4 和 5 添加的设备。

分区

要进行分区,请在闪存中确定引导加载程序在运行时可访问和可信的位置,以存储 DTB 和 DTBO(引导加载程序必须能够在匹配的进程中找到这些文件)。请记住,DTB 和 DTBO 不能存在于同一个分区中。如果您的 DTB/DTBO 位于 dtb/dtbo 分区中,请使用 DTB/DTBO 分区格式中详细列出的表结构和头文件格式。

在引导加载程序中运行

要运行,请执行以下操作:

  1. 标识 SoC 并将相应的 .dtb 从存储空间加载到内存中。
  2. 标识板并将相应的 .dtbo 从存储空间加载到内存中。
  3. 用 .dtbo 叠加 .dtb 形成合并的 DT。
  4. 启动内核(已给定合并 DT 的内存地址)。

DTB/DTBO 分区

如果您的 DTB/DTBO 位于专属的分区(例如 dtb 和 dtbo 分区)中,请使用以下表格结构和头文件格式:

图 1. dtb/dtbo 分区布局示例(如需了解 AVB 签名相关信息,请参阅安全性)。

数据结构

dt_table_header 适用于 dtb/dtbo 分区;您不能在 image.gz 末尾处附加此格式。如果您有一个 DTB/DTBO,则仍必须使用此格式(并且,dt_table_header 中的 dt_entry_count 为 1)。

#define DT_TABLE_MAGIC 0xd7b7ab1e

struct dt_table_header {
  uint32_t magic;             // DT_TABLE_MAGIC
  uint32_t total_size;        // includes dt_table_header + all dt_table_entry
                              // and all dtb/dtbo
  uint32_t header_size;       // sizeof(dt_table_header)

  uint32_t dt_entry_size;     // sizeof(dt_table_entry)
  uint32_t dt_entry_count;    // number of dt_table_entry
  uint32_t dt_entries_offset; // offset to the first dt_table_entry
                              // from head of dt_table_header

  uint32_t page_size;         // flash page size we assume
  uint32_t version;       // DTBO image version, the current version is 0.
                          // The version will be incremented when the
                          // dt_table_header struct is updated.
};

struct dt_table_entry {
  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 custom[4];         // optional, must be zero if unused
};

如需读取所有 dt_table_entry,请使用 dt_entry_sizedt_entry_count 和 dt_entries_offset。例如:

my_read(entries_buf,
        header_addr + header->dt_entries_offset,
        header->dt_entry_size * header->dt_entry_count);

dt_table_entry 中的 idrevcustom 是设备树的可选硬件标识,引导加载程序可以使用这些标识有效地识别要加载的 DTB/DTBO。如果引导加载程序需要获取更多信息,请将其放在 DTB/DTBO 中,引导加载程序可在这里解析 DTB/DTBO,从而读取这些信息(参见下面的示例代码)。

示例代码

以下示例代码可检查引导加载程序中的硬件标识。

  • check_dtbo() 函数用于检查硬件标识。 首先它会检查结构 dt_table_entry 中的数据(idrev 等)。如果这些数据未能提供充足的信息,它会将 dtb 数据加载到内存中,并检查 dtb 中的值。
  • my_hw_information 和 soc_id 属性的值会在根节点中进行解析(请参见 my_dtbo_1.dts 中的示例)。

    [my_dtbo_1.dts]
    /dts-v1/;
    /plugin/;
    
    / {
      /* As DTS design, these properties only for loader, won't overlay */
      compatible = "board_manufacturer,board_model";
    
      /* These properties are examples */
      board_id = <0x00010000>;
      board_rev = <0x00010001>;
      another_hw_information = "some_data";
      soc_id = <0x68000000>;
      ...
    };
    
    &device@0 {
      value = <0x1>;
      status = "okay";
    };
    
    [my_bootloader.c]
    int check_dtbo(const dt_table_entry *entry, uint32_t header_addr) {
      ...
      if (entry->id != ... || entry->rev != ...) {
        ...
      }
      ...
      void * fdt_buf = my_load_dtb(header_addr + entry->dt_offset, entry->dt_size);
      int root_node_off = fdt_path_offset(fdt_buf, "/");
      ...
      const char *my_hw_information =
        (const char *)fdt_getprop(fdt_buf, root_node_off, "my_hw_information", NULL);
      if (my_hw_information != NULL && strcmp(my_hw_information, ...) != 0) {
        ...
      }
      const fdt32_t *soc_id = fdt_getprop(fdt_buf, root_node_off, "soc_id", NULL);
      if (soc_id != NULL && *soc_id != ...) {
        ...
      }
      ...
    }
    

mkdtimg

mkdtimg 是用于创建 dtb/dtbo 映像的工具(源代码 位于 AOSP 中的 system/libufdt 下)。mkdtimg 支持多个命令,包括 createcfg_create 和 dump

create

使用 create 命令创建 dtb/dtbo 映像:

mkdtimg create <image_filename> (<global-option>...) \
    <ftb1_filename> (<entry1_option>...) \
    <ftb2_filename> (<entry2_option>...) \
    ...

ftbX_filename 会在映像中生成一个 dt_table_entryentryX_option 是分配给 dt_table_entry 的值。这些值可以是以下任一值:

--id=<number|path>
--rev=<number|path>
--custom0=<number|path>
--custom1=<number|path>
--custom2=<number|path>
--custom3=<number|path>

数字值可以是 32 位数字(如 68000)或十六进制数字(如 0x6800)。或者,您也可以使用以下格式指定路径:

<full_node_path>:<property_name>

例如,/board/:idmkdtimg 从 DTB/DTBO 文件中的路径读取值,并将值(32 位)分配给 dt_table_entry 中的相对属性。或者,您也可以将 global_option 作为所有条目的默认选项。dt_table_header 中 page_size 的默认值为 2048;可使用 global_option --page_size=<number> 分配其他值。

例如:

[board1.dts]
/dts-v1/;
/plugin/;

/ {
  compatible = "board_manufacturer,board_model";
  board_id = <0x00010000>;
  board_rev = <0x00010001>;
  another_hw_information = "some_data";
  ...
};

&device@0 {
  value = <0x1>;
  status = "okay";
};

mkdtimg create dtbo.img --id=/:board_id --custom0=0xabc \
  board1.dtbo \
  board2.dtbo --id=0x6800 \
  board3.dtbo --id=0x6801 --custom0=0x123
  • 第一个 dt_table_entry (board1.dtboid 为 0x00010000custom[0] 为 0x00000abc
  • 第二个 id 为 0x00006800custom[0] 为 0x00000abc
  • 第三个 id 为 0x00006801custom[0] 为 0x00000123
  • 所有其他项均使用默认值 (0)。

cfg_create

cfg_create 命令使用以下格式的配置文件创建映像:

# global options
  <global_option>
  ...
# entries
<ftb1_filename>     # comment
  <entry1_option>   # comment
  ...
<ftb2_filename>
  <entry2_option>
  ...
...

选项 global_option 和 entryX_option 必须以一个或多个空格字符开头(这些选项与 create 选项相同,不带 -- 前缀)。空行或者以 # 开头的行将被忽略。

例如:

[dtboimg.cfg]
# global options
  id=/:board_id
  rev=/:board_rev
  custom0=0xabc

board1.dtbo

board2.dtbo
  id=0x6800       # override the value of id in global options

board2.dtbo
  id=0x6801       # override the value of id in global options
  custom0=0x123   # override the value of custom0 in global options

mkdtimg cfg_create dtbo.img dtboimg.cfg

mkdtimg 不会处理 .dtb/.dtbo 文件的对齐方式,而是将它们附加到映像上。使用 dtc 将 .dts 编译为 .dtb/.dtbo 时,必须添加选项 -a。例如,添加选项 -a 4 会增加内边距,使得 .dtb/.dtbo 的大小调整为 4 个字节。

多个 DT 表格条目可以共享一个 .dtb/.dtbo。如果您为不同的条目使用同一个文件名,则系统只会在具有相同 dt_offset 和 dt_size 的映像中存储一份内容。使用具有相同 DT 的不同硬件时,这种方式非常有用。

dump

对于 dtb/dtbo 映像,请使用 dump 命令打印映像中的信息。例如:

mkdtimg dump dtbo.img
dt_table_header:
               magic = d7b7ab1e
          total_size = 1300
         header_size = 32
       dt_entry_size = 32
      dt_entry_count = 3
   dt_entries_offset = 32
           page_size = 2048
             version = 0
dt_table_entry[0]:
             dt_size = 380
           dt_offset = 128
                  id = 00010000
                 rev = 00010001
           custom[0] = 00000abc
           custom[1] = 00000000
           custom[2] = 00000000
           custom[3] = 00000000
           (FDT)size = 380
     (FDT)compatible = board_manufacturer,board_model
...

优化 DTO

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

内核命令行

设备树中的原始内核命令行位于 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 设备的性能测试中,我们比较了 libufdt 和 libfdt。基本节点数量带来的影响相似,但包含以下差异:

  • 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 版设备树表格头文件时,在 DTBO 映像中使用经过压缩的叠加层。 使用 DTBO 头文件 v1 时,dt_table_entry 中标记字段的四个最低有效位会指明 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' will be 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 */
};

目前,系统支持 zlib 和 gzip 压缩。

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);
}

 

Guess you like

Origin blog.csdn.net/yangzex/article/details/132968547