本文Linux内核版本:Linux 4.1.15,使用到的工具有hexdump,fdtdump,dtc
一.DTB的结构
DTB Structure
DTB(Device Tree Blob)就是DTS经过DTC编译之后的二进制文件,也叫作Flattened Device tree。如图,DTB分为4个section,分别是ftd_header、memory reservation block、Structure block、strings block。下面将从以上四部分对DTB展开叙述。

struct fdt_header是DTB的header部分,作用就是描述了整个DTB或者DTB其他部分的内容,以便于数据的定位。路径:
scripts\dtc\libfdt\fdt.h1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18struct fdt_header {
uint32_t magic; /* magic word FDT_MAGIC */
uint32_t totalsize; /* total size of DT block */
uint32_t off_dt_struct; /* offset to structure */
uint32_t off_dt_strings; /* offset to strings */
uint32_t off_mem_rsvmap; /* offset to memory reserve map */
uint32_t version; /* format version */
uint32_t last_comp_version; /* last compatible version */
/* version 2 fields below */
uint32_t boot_cpuid_phys; /* Which physical CPU id we're
booting on */
/* version 3 fields below */
uint32_t size_dt_strings; /* size of the strings block */
/* version 17 fields below */
uint32_t size_dt_struct; /* size of the structure block */
};如下部分不管偏移量还是大小单位都是字节
- magic:magic用于校验,值一般是0xd00dfeed(大端)
- totalsize:整个device tree的大小
- off_dt_struct:struct block相对header的偏移量
- off_dt_strings:strings block相对header的偏移量
- off_mem_rsvmap:预留内存相对header的偏移量
- version:版本信息
- last_comp_version:版本兼容信息
- boot_cpuid_phys:从哪个cpu boot
- size_dt_strings:strings block的大小
- size_dt_struct:struct block的大小
memory reservation block
内存预留块一般不用于普通的内存分配,主要用来保护一些重要的数据结构不被覆盖。bootLoader在设备树里可以通过memory节点指定保留的内存区域,Linux里用struct fdt_reserve_entry来描述
路径:
scripts\dtc\libfdt\fdt.h1
2
3
4struct fdt_reserve_entry {
uint64_t address;
uint64_t size;
};address和size都是64位的,如果是32位机,那么高32位会被忽略,通过指定一个address和size就可以确定要保留的内存范围
Structure block
dts就是由node和property组成的,而structure block就是用来在描述node、property本身含义的基础上,还可以加一些token从而描述各个node的层级关系。所有的token都是4字节同时都是4字节对齐,具体的token如下:
FDT_BEGIN_NODE:标识node的起始,其值为0x1,后面应该跟着node name,对于根节点,node name为空,因此用0填充(4 byte)
FDT_END_NODE:标识node的结束,其值为0x2
FDT_PROP:标识属性的开始,FDT_PROP后面紧跟的就是描述FDT property的结构,值为0x03其结构如下
1
2
3
4
5
6struct fdt_property {
uint32_t tag;
uint32_t len;
uint32_t nameoff;
char data[0];
};- tag:tag就是property的标识,也就是属性,取值为FDT_PROP
- len:标识了property值的长度(包括‘\0’,单位:字节),也即property值的长度
- nameoff:属性名称存储位置相对于off_dt_strings的偏移地址
- data:0长数组存储了属性值,属性值长度可由len得到
FDT_NOP:解析设备树的任何程序都将忽略FDT_NOP token,其值为0x4
FDT_END:标记structure block的结尾,其值为0x9
最终DTB里的数据应该是按照下图方式排布的:

strings block
strings block里存放的就是所有出现的property的名字,property可以通过nameoff字段来得到其名称(null-terminate string)
二.DTB的实例分析
截取一个IMX6UL的设备树部分代码,我们来分析一下具体的DTB
xxxx.dts文件
1 | ; |
imx6ul.dtsi文件:
1 |
|
skeleton.dtsi文件:
1 | / { |
我们使用到的dts是xxx.dts,由于其引用了另外的dtsi,因此最终肯定把xxx.dts所引用到的dtsi全部展开,如果在其他dtsi文件里定义过的property,在xxx.dts里会覆盖掉原有的值,然后生成一个dest.dts文件,最后再由DTC编译成DTB文件。
使用hexdump查看DTB文件,前四个字节组成的数是0xd00dfeed,该magic符合条件。整个device tree的大小为0x000099fd,

off_dt_struct值为:0x38,也就是说structrue block距离header偏移量为0x38。
off_dt_strings值为:0x8cfc,也就是说strings block距离header偏移量为0x8cfc
off_mem_rsvmap:预留内存地址相对header偏移量为0x28,
version:值为0x11,即17
last_comp_version:值为0x10,即16,与规范上一致
boot_cpuid_phys:0x0,表示boot cpu 是cpu 0
size_dt_strings:值为0x0d01,意味着strings block的大小为0x0d01(相对strings block的起始地址)
size_dt_struct:值为0x8cc4,即struct block大小为0x8cc4(相对struct block的起始地址)
地址0x28-0x38这一段就是预留内存的数据组织,address和size都为0
对于xxxx.dts,首先需要处理根节点”/“,节点的开始使用FDT_BEGIN_NODE标识(TAG),由于根节点没有名字,因此len字段就用0填充,表现出来的形式就是:其中红色是FDT_BEGIN_NODE,蓝色是name字段偏移,0表示没有名字

接下来分析property字段:对于property字段,红色表示FDT_PROP(TAG),蓝色表示属性值长度,为4个字节,绿色表示属nameoff,即在strings block里的偏移,00 00 00 01表示属性值,

对于上面的property字段,我们从nameoff字段知道该property在strings block里的偏移,那现在就取出该property的name
由header的off_dt_strings可知,strings block偏移header值为:0x8cfc,0x8cfc位置的数据如下图:

由于property里的nameoff指示了偏移量为0,那么就从0x8cfc开始找,直到遇到空字符。显然红色标准的就是property name,即property name=”#address-cells”,property value之前已经得到是0x00000001,即#address-cells=<0x00000001>,这点我们可以从skeleton.dtsi头几个字段也可以得到验证,也可以反编译xxxx.dtb得到tmp.dts(见下图dts,它是最终生成的DTS),命令是:`./dtc -I dtb -O dts ../../arch/arm/boot/dts/imx6ulxxxxx.dtb > tmp.dts
