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.
-
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
-
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 tokernel
" =4>, put into the boot partition.mkbootimg
.dtb
unique partition Figure 3.Place
.dtb
in a unique partition (for example, thedtb
partition). -
To load the device tree blob and start the kernel:
3a.
.dtb
Load 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:
- General DT
.dts
编译为.dtb
text. - Flash
.dtb
files to a partition accessible by the bootloader at runtime (see details below).
To build an overlay DT, do the following:
- 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. - 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.
- Loads
.dtb
from storage space into memory. - Loads
.dtbo
from storage space into memory. - is superimposed with
.dtb
.dtbo
to form a merged DT. - 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 withodm
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
- 创建叠加层
.dts
的副本。在副本中,移除第一行头文件。示例:/dts-v1/; /plugin/;
将文件另存为my_overlay_dt_wo_header.dts
(或您想使用的任何文件名)。 - 创建主
.dts
的副本。在副本中的最后一行后,为您在第 1 步中创建的文件附加 include 语法。例如:/include/ "my_overlay_dt_wo_header.dts"
将文件另存为my_main_dt_with_include.dts
(或您希望的任何文件名)。 - 使用
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>
- 使用
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
将 x
、y
和 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 分区,请确保引导加载程序可以执行以下操作:
- 识别它正在哪个开发板上运行,并选择要应用的相应叠加层。
- 将
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 分区格式中详细列出的表结构和头文件格式。
在引导加载程序中运行
要运行,请执行以下操作:
- 标识 SoC 并将相应的 .dtb 从存储空间加载到内存中。
- 标识板并将相应的
.dtbo
从存储空间加载到内存中。 - 用
.dtbo
叠加.dtb
形成合并的 DT。 - 启动内核(已给定合并 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_size
、dt_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
中的 id
、rev
、custom
是设备树的可选硬件标识,引导加载程序可以使用这些标识有效地识别要加载的 DTB/DTBO。如果引导加载程序需要获取更多信息,请将其放在 DTB/DTBO 中,引导加载程序可在这里解析 DTB/DTBO,从而读取这些信息(参见下面的示例代码)。
示例代码
以下示例代码可检查引导加载程序中的硬件标识。
check_dtbo()
函数用于检查硬件标识。 首先它会检查结构dt_table_entry
中的数据(id
、rev
等)。如果这些数据未能提供充足的信息,它会将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
支持多个命令,包括 create
、cfg_create
和 dump
。
create
使用 create
命令创建 dtb
/dtbo
映像:
mkdtimg create <image_filename> (<global-option>...) \
<ftb1_filename> (<entry1_option>...) \
<ftb2_filename> (<entry2_option>...) \
...
ftbX_filename
会在映像中生成一个 dt_table_entry
。entryX_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/:id
。mkdtimg
从 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.dtbo
)id
为0x00010000
,custom[0]
为0x00000abc
。 - 第二个
id
为0x00006800
,custom[0]
为0x00000abc
。 - 第三个
id
为0x00006801
,custom[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 说明
- 提供库。如需使用
libufdt
,请提供libfdt
以用于数据结构和 API:#include <libfdt.h> #include <ufdt_overlay.h>
- 加载主 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);
- 叠加 DT:
- 使用
ufdt_install_blob()
获取主 DT 的 FDT 头文件:main_fdt_header = ufdt_install_blob(main_buf, main_size); main_fdt_size = main_size;
- 对 DTO 调用
ufdt_apply_overlay()
以获取采用 FDT 格式的合并 DT:merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size, overlay_buf, overlay_size);
- 使用
merged_fdt
获取dtc_totalsize()
的大小:merged_fdt_size = dtc_totalsize(merged_fdt);
- 传递合并的 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); }