dtb作为二进制文件被加载到内存中,然后由内核读取并进行解析,如果对dtb文件的格式不了解,那么在看设备树解析相关的内核代码时将会寸步难行,而阅读源代码才是了解设备树最好的方式,所以,如果需要更透彻的了解设备树解析的细节,第一步就是需要了解设备树的格式。
1,DTB整体结构
经过Device Tree Compiler编译,Device Tree source file变成了Device Tree Blob(又称作flattened device tree)的格式。Device Tree Blob的数据组织如下图所示:
2,DTB header
struct fdt_header结构体的定义如下:
struct fdt_header {
fdt32_t magic; /* 用来识别DTB的。通过这个magic,kernel可以确定bootloader传递的参数block是一个DTB还是tag list,以大端模式保存 */
fdt32_t totalsize; /* 整个DTB文件的大小 */
fdt32_t off_dt_struct; /* structure block的偏移地址 */
fdt32_t off_dt_strings; /* strings block的偏移地址 */
fdt32_t off_mem_rsvmap; /* memory reservation block的偏移地址 */
fdt32_t version; /* 该DTB的版本 */
fdt32_t last_comp_version; /* 兼容版本信息 */
/* version 2 fields below */
fdt32_t boot_cpuid_phys; /* 我们在哪一个CPU(用ID标识)上booting */
/* version 3 fields below */
fdt32_t size_dt_strings; /* device tree strings block的size。和off_dt_strings一起确定了strings block在内存中的位置 */
/* version 17 fields below */
fdt32_t size_dt_struct; /* device tree structure block的size。和和off_dt_struct一起确定了device tree structure block在内存中的位置 */
};
3,memory reserve map的格式描述
fdt_reserve_entry结构体如下:
struct fdt_reserve_entry {
fdt64_t address;
fdt64_t size;
};
该结构体用于表示memreserve的起始地址和内存空间的大小,它紧跟在struct fdt_header结构体后面。
这一部分的作用是告诉内核哪一些内存空间需要被保留而不应该被系统覆盖使用,因为在内核启动时常常需要动态申请大量的内存空间,只有提前进行注册,用户需要使用的内存才不会被系统征用而造成数据覆盖。
值得一提的是,对于设备树而言,即使不指定保留内存,系统也会默认为设备树保留相应的内存空间。
同时,这一部分需要64位(8字节)对齐。
例如:
kernel/arch/arm/boot/dts/rtd1195.dtsi
// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause)
/*
* Copyright (c) 2017-2019 Andreas Färber
*/
/memreserve/ 0x00000000 0x0000a800; /* boot code */
/memreserve/ 0x0000a800 0x000f5800;
/memreserve/ 0x17fff000 0x00001000;
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/reset/realtek,rtd1195.h>
/ {
compatible = "realtek,rtd1195";
interrupt-parent = <&gic>;
#address-cells = <1>;
#size-cells = <1>;
/memreserve/ 0x00000000 0x0000a800,fdt_reserve_entry的结构体成员address = 0x00000000,size = 0x0000a800。
4, device tree structure block的格式描述
device tree structure block区域是由若干的分片组成,每个分片开始位置都是保存了token,以此来描述该分片的属性和内容。共计有5种token:
(1)FDT_BEGIN_NODE (0x00000001)。该token描述了一个node的开始位置,紧挨着该token的就是node name(包括unit address)
(2)FDT_END_NODE (0x00000002)。该token描述了一个node的结束位置。
(3)FDT_PROP (0x00000003)。该token描述了一个property的开始位置,该token之后是两个u32的数据,分别是length和name offset。length表示该property value data的size。name offset表示该属性字符串在device tree strings block的偏移值。length和name offset之后就是长度为length具体的属性值数据。
(4)FDT_NOP (0x00000004)。
被解析设备树的程序忽略,可用于覆盖其他属性,以删除它。
(5)FDT_END (0x00000009)。该token标识了一个DTB的结束位置。
单个节点在structure block的存储格式如下图所示(子节点的存储格式也是一样):
5, device tree strings block的格式描述
在dtb中有大量的重复字符串,比如"model","compatile"等等,为了节省空间,将这些字符串统一放在某个地址,需要使用的时候直接使用索引来查看。
需要注意的是,属性部分格式为key = value,key部分被放置在strings部分,而value部分的字符串并不会放在这一部分,而是直接放在structure中。
6,dtb文件解析示例
编译生成dtb文件的源设备树test_dts.dts文件如下:
/dts-v1/;
/memreserve/ 0x00000000 0x00001000;
/ {
compatible = "brcm,bcm2835";
model = "BCM2835";
#address-cells = <1>;
#size-cells = <1>;
chosen {
stdout-path = "serial0:115200n8";
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
usbphy: phy {
compatible = "usb-nop-xceiv";
#phy-cells = <0>;
};
};
使用如下指令生成test_dtb.dtb文件
dtc -I dts -O dtb -o test_dtb.dtb test_dts.dts
hexdump -C test_dtb.dtb 显示dtb文件的内容, device Tree的文件是以大端模式储存:
1) d0 0d fe ed magic
2) 00 00 01 a3 totalsize
3) 00 00 00 48 offdtstruct 0x48
4) 00 00 01 50 offdtstrings
5) 00 00 00 28 off memreserve map
6) 00 00 00 11 version
7) 00 00 00 10 compatible version
8) 00 00 00 00 bootcpu ID
9) 00 00 00 53 size dt strings
10) 00 00 01 08 size dt structure
11) 00 00 00 00 00 00 00 00 memreserve address
12) 00 00 00 00 00 00 10 00 memreserve size
13) 00 00 00 01 所处地址为0x48,处在structure block中,0x00000001表示设备节点的开始
14) 00 00 00 00 紧跟着的是设备节点的名字,这里是根节点,所以为0x00000000
15) 00 00 00 03 0x00000003表示的开始描述设备节点的一个属性
16) 00 00 00 0d 表示这个属性值的长度为0x0d
17) 00 00 00 00 表示这个属性的名字在strings block的偏移量是0,找到strings block的地址0x0150的地方,可知这个属性的名字是compatible
18) 62 72 63 6d 2c 62 63 6d 32 38 33 35 00 这个compatible属性的值是"brcm,bcm2835",加上字符串结束符NULL,正好是13个字节
19) 00 00 00 01 chosen节点的开始
20) 63 68 6f 73 65 6e 00 chosen节点的名字
21) 00 00 00 02 chosen节点的结束
22) 00 00 00 09 structure block的结束
23) 63 6f 6d 70 61 74 69 62 6c 65 00 属性名字:compatible
7,Device Tree文件结构
通过以上分析,可以得到Device Tree文件结构如下图所示。dtb的头部首先存放的是fdt_header的结构体信息,接着是填充区域,填充大小为off_dt_struct – sizeof(struct fdt_header),填充的值为0。接着就是struct fdt_property结构体的相关信息。最后是dt_string部分。
Device Tree源文件的结构分为header、fill_area、dt_struct及dt_string四个区域。fill_area区域填充数值0。节点(node)信息使用struct fdt_node_header结构体描述。属性信息使用struct fdt_property结构体描述。各个结构体信息如下:
struct fdt_node_header {
fdt32_t tag;
char name[0];
};
struct fdt_property {
fdt32_t tag;
fdt32_t len;
fdt32_t nameoff;
char data[0];
};
struct fdt_node_header描述节点信息,tag是标识node的起始结束等信息的标志位,name指向node名称的首地址。tag的取值如下:
1. #define FDT_BEGIN_NODE 0x1 /* Start node: full name */
2. #define FDT_END_NODE 0x2 /* End node */
3. #define FDT_PROP 0x3 /* Property: name off, size, content */
4. #define FDT_NOP 0x4 /* nop */
5. #define FDT_END 0x9
FDT_BEGIN_NODE和FDT_END_NODE标识node节点的起始和结束,FDT_PROP标识node节点下面的属性起始符,FDT_END标识Device Tree的结束标识符。因此,对于每个node节点的tag标识符一般为FDT_BEGIN_NODE,对于每个node节点下面的属性的tag标识符一般是FDT_PROP。
描述属性采用struct fdt_property描述,tag标识是属性,取值为FDT_PROP;len为属性值的长度(包括‘\0’,单位:字节);nameoff为属性名称存储位置相对于off_dt_strings的偏移地址。
struct fdt_property {
fdt32_t tag;
fdt32_t len;
fdt32_t nameoff;
char data[0];
};
dt_struct在Device Tree中的结构如下图所示。节点的嵌套也带来tag标识符的嵌套。
参考链接:
https://blog.51cto.com/u_15169172/4065013
https://www.cnblogs.com/downey-blog/p/10485529.html