瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第七期_设备树_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
设备树 Blob (DTB) 格式是设备树数据的平面二进制编码。它用于在软件程序之间交换设备树数据。例如,在启动操作系统时,固件会将 DTB 传递给操作系统内核。
DTB 格式在单个、线性、无指针数据结构中对设备树数据进行编码。它由一个小头部和三个可变大小的部分组成:内存保留块、结构块和字符串块。这些应该以该顺序出现在展平的设备树中。因此,设备树结构作为一个整体,当加载到内存地址时,将类似于下图(图 62-1)所示:
图 62-1
本节课将以下面的设备树为例对设备树的二进制文件格式进行讲解:
/dts-v1/;
/ {
model = "This is my devicetree!";
#address-cells = <1>;
#size-cells = <1>;
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
};
cpu1: cpu@1 {
device_type = "cpu";
compatible = "arm,cortex-a35", "arm,armv8";
reg = <0x0 0x1>;
};
aliases {
led1 = "/gpio@22020101";
};
node1 {
#address-cells = <1>;
#size-cells = <1>;
gpio@22020102 {
reg = <0x20220102 0x40>;
};
};
node2 {
node1-child {
pinnum = <01234>;
};
};
gpio@22020101 {
compatible = "led";
reg = <0x20220101 0x40>;
status = "okay";
};
};
而我们之后要分析的是二进制的dtb文件,所以需要使用dtc工具将上面的dts文件编译成dtb文件,具体命令如下(图 62-2)所示:
图 62-2
为了方便用户学习,已经将本章节要讲解的设备树dts文件和dtb文件放在了对应的网盘路径下,同时也将pxBinaryViewerSetup二进制分析软件放在了同一目录下,iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\49_dt_format,如下图(图 62-3)所示:
图 152-3
使用二进制分析软件打开deb文件并设置大端模式之后如下(图 62-4)所示:
图 62-4
在接下来的小节中将会对读取出的设备树二进制内容进行讲解。
devicetree 的头布局由以下 C 结构定义。所有的头字段都是 32 位整数,以大端格式存储。
struct fdt_header {
uint32_t magic; // 设备树头部的魔数
uint32_t totalsize; // 设备树文件的总大小
uint32_t off_dt_struct; // 设备树结构体(节点数据)相对于文件开头的偏移量
uint32_t off_dt_strings; // 设备树字符串表相对于文件开头的偏移量
uint32_t off_mem_rsvmap; // 内存保留映射表相对于文件开头的偏移量
uint32_t version; // 设备树版本号
uint32_t last_comp_version; // 最后一个兼容版本号
uint32_t boot_cpuid_phys; // 启动 CPU 的物理 ID
uint32_t size_dt_strings; // 设备树字符串表的大小
uint32_t size_dt_struct; // 设备树结构体(节点数据)的大小
};
每个字段的描述如下所示:
字段 |
描述 |
---|---|
magic |
该字段为固定值 0xd00dfeed(大端)。 |
totalsize |
该字段包含设备树数据结构的总大小(以字节为单位)。此大小应包含结构的所有部分:标题、内存保留块、结构块和字符串块,以及块之间或最后一个块之后的任何空闲空间间隙。 |
off_dt_struct |
该字段包含结构块从头开始的以字节为单位的偏移量。 |
off_dt_strings |
该字段包含字符串块从头开始的以字节为单位的偏移量。 |
off_mem_rsvmap |
该字段包含从头开始的内存保留块的字节偏移量。 |
version |
该字段包含设备树数据结构的版本。 |
last_comp_version |
向后兼容的设备树数据结构的最低版本。 |
boot_cpuid_phys |
与设备树CPU 节点的reg属性对应 |
size_dt_strings |
设备树字符串块部分的字节长度。 |
size_dt_struct |
设备树结构块部分的字节长度。 |
然后来查看二进制文件,其中4个字节表示一个单位,前十个单位分别代表上述的十个字段如下图(图 62-5)所示:
字段 |
十六进制数值 |
代表含义 |
---|---|---|
magic |
D00DFEED |
固定值 |
totalsize |
000002A4 |
转换为十进制之后为676,表示该文件大小为676字节 |
off_dt_struct |
00000038 |
表示结构块从00000038这个地址开始,和后面的size_dt_struct结构块大小参数一起可以确定结构块的存储范围 |
off_dt_strings |
0000024C |
表示字符串块从0000024C这个地址开始,和后面的size_dt_strings字符串块大小参数一起可以确定字符串块的存储范围 |
off_mem_rsvmap |
00000028 |
表示内存保留块的偏移为00000028, header之后结构快之前都是属于内存保留块。 |
version |
00000011 |
11转换为十进制之后为17,表示当前设备树结构版本为17 |
last_comp_version |
00000010 |
10转换为十进制之后为16,表示向前兼容的设备树结构版本为16 |
boot_cpuid_phys |
00000000 |
表示设备树的teg属性为0 |
size_dt_strings |
00000058 |
表示字符串块的大小为00000058 ,和前面的off_dt_strings字符串块偏移值一起可以确定字符串块的范围 |
size_dt_struct |
00000214 |
表示结构块的大小为00000214,和前面的off_dt_struct结构块偏移值一起可以确定结构块的范围 |
在接下来的小节中将会对header提到的内存保留块、结构块和字符串块进行更详细的讲解。
内存保留块(Memory Reserved Block)是用于客户端程序的保护和保留物理内存区域的列表。这些保留区域不应被用于一般的内存分配,而是用于保护重要数据结构,以防止客户端程序覆盖这些数据。内存保留块的目的是确保特定的内存区域在客户端程序运行时不被修改或使用。由于在示例设备树中没有设置内存保留块,所以相应的区域都为0,如下(图 62-6)所示:
图 172-6
保留区域列表: 内存保留块是一个由一组 64 位大端整数对构成的列表。每对整数对应一个保留内存区域,其中包含物理地址和区域的大小(以字节为单位)。这些保留区域应该彼此不重叠。
保留区域的用途: 客户端程序不应访问内存保留块中的保留区域,除非引导程序提供的其他信息明确指示可以访问。引导程序可以使用特定的方式来指示客户端程序可以访问保留内存的部分内容。引导程序可能会在文档、可选的扩展或特定于平台的文档中说明保留内存的特定用途。
格式: 内存保留块中的每个保留区域由一个64位大端整数对表示。每对由以下 C 结构表示:
struct fdt_reserve_entry {
uint64_t address;
uint64_t size;
};
其中的第一个整数表示保留区域的物理地址,第二个整数表示保留区域的大小(以字节为单位)。每个整数都以 64 位的形式表示,即使在32位架构上也是如此。在32位CPU上,整数的高 32 位将被忽略。
内存保留块为设备树提供了保护和保留物理内存区域的功能。它确保了特定的内存区域在客户端程序运行时不被修改或使用。这样可以确保引导程序和其他关键组件在需要的情况下能够访问保留内存的特定部分,并保护关键数据结构免受意外修改。
结构块是设备树中描述设备树本身结构和内容的部分。它由一系列带有数据的令牌序列组成,这些令牌按照线性树结构进行组织。
结构块中的令牌分为五种类型,每种类型用于不同的目的。
a. FDT_BEGIN_NODE (0x00000001): FDT_BEGIN_NODE 标记表示一个节点的开始。它后面跟着节点的单元名称作为额外数据。节点名称以以空字符结尾的字符串形式存储,并且可以包括单元地址。节点名称后可能需要填充零字节以对齐,然后是下一个标记,可以是除了 FDT_END 之外的任何标记。
b. FDT_END_NODE (0x00000002): FDT_END_NODE 标记表示一个节点的结束。该标记没有额外的数据,紧随其后的是下一个标记,可以是除了 FDT_PROP 之外的任何标记。
c. FDT_PROP (0x00000003): FDT_PROP 标记表示设备树中属性的开始。它后面跟着描述属性的额外数据,该数据首先由属性的长度和名称组成,表示为以下 C 结构
struct {
uint32_t len;
uint32_t nameoff;
}
长度表示属性值的字节长度,名称偏移量指向字符串块中存储属性名称的位置。在这个结构之后,属性的值作为字节字符串给出。属性值后可能需要填充零字节以对齐,然后是下一个令牌,可以是除了 FDT_END 之外的任何标记。
d. FDT_NOP (0x00000004): FDT_NOP 令牌可以被解析设备树的程序忽略。该令牌没有额外的数据,紧随其后的是下一个令牌,可以是任何有效的令牌。使用 FDT_NOP 令牌可以覆盖树中的属性或节点定义,从而将其从树中删除,而无需移动设备树 blob 中的其他部分。
e. FDT_END (0x00000009): FDT_END 标记表示结构块的结束。应该只有一个 FDT_END 标记,并且应该是结构块中的最后一个标记。该标记没有额外的数据,紧随其后的字节应该位于结构块的开头偏移处,该偏移等于设备树 blob 标头中的 size_dt_struct 字段的值。
设备树的结构以线性树的形式表示。每个节点由 FDT_BEGIN_NODE 标记开始,由 FDT_END_NODE 标记结束。节点的属性和子节点在 FDT_END_NODE 之前表示,因此子节点的 FDT_BEGIN_NODE 和 FDT_END_NODE 令牌嵌套在父节点的令牌中。
结构块以单个 FDT_END 标记结束。该标记没有额外的数据,它位于结构块的末尾,并且是结构块中的最后一个标记。FDT_END 标记之后的字节应位于结构块的开头偏移处,该偏移等于设备树 blob 标头中的 size_dt_struct 字段的值。
最后对结构块开头的部分内容进行讲解,具体内容如下(图 62-7)所示:
十六进制数值 |
代表含义 |
00000001 |
根节点的开始 |
00000000 |
根节点没有节点名,所以这里名字为0 |
00000003 |
设备树中属性的开始 |
00000017 |
代表该属性的大小,换算成十进制为23,也就是"This is my devicetree!"这一字符串的长度 |
00000000 |
代表该属性在字符串块的偏移量,这里为0,表示无偏移 |
54686973-65210000 |
model的具体值 |
通过使用结构块,设备树可以以一种层次化的方式组织和描述系统中的设备和资源。每个节点可以包含属性和子节点,从而实现更加灵活和可扩展的设备树表示。
字符串块用于存储设备树中使用的所有属性名称。它由一系列以空字符结尾的字符串组成,这些字符串在字符串块中简单地连接在一起,具体示例如下(图 62-8)所示:
图 192-8
(1)字符串连接
字符串块中的字符串以空字符(\0)作为终止符来连接。这意味着每个字符串都以空字符结尾,并且下一个字符串紧跟在上一个字符串的末尾。这种连接方式使得字符串块中的所有字符串形成一个连续的字符序列。
(2)偏移量引用
在结构块中,属性的名称是通过偏移量来引用字符串块中的相应字符串的。偏移量是一个无符号整数值,它表示字符串在字符串块中的位置。通过使用偏移量引用,设备树可以节省空间,并且在属性名称发生变化时也更加灵活,因为只需要更新偏移量,而不需要修改结构块中的属性引用。
(3)对齐约束:
字符串块没有对齐约束,这意味着它可以出现在设备树 blob 的任何偏移处。这使得字符串块的位置在设备树 blob 中是灵活的,并且可以根据需要进行调整,而不会对设备树的解析和处理造成影响。
字符串块是设备树中用于存储属性名称的部分。它由字符串连接而成,并通过偏移量在结构块中进行引用。字符串块的灵活位置使得设备树的表示更加紧凑和可扩展。