DTB

dtb文件由dts通过dtc命令编译而来。

具体结构应该如下:

2120938-20210626072341835-858949611.png

alignment gap

对齐中间的alignment gap部分表示对齐间隙,它并非是必须的,它是否被提供以及大小由具体的平台对数据对齐和的要求以及数据是否已经对齐来决定。

elf header

DTB文件跟elf文件一样,都有一个header,也就是meta-data。dtb文件的meta-data大小为40字节。

可以看下linux代码中对于dtb头信息的定义:

// In scripts/dtc/libfdt/fdt.h
struct fdt_header {
fdt32_t magic; /* magic word FDT_MAGIC */
fdt32_t totalsize; /* total size of DT block */
fdt32_t off_dt_struct; /* offset to structure */
fdt32_t off_dt_strings; /* offset to strings */
fdt32_t off_mem_rsvmap; /* offset to memory reserve map */
fdt32_t version; /* format version */
fdt32_t last_comp_version; /* last compatible version */

/* version 2 fields below */
fdt32_t boot_cpuid_phys; /* Which physical CPU id we're
booting on */
/* version 3 fields below */
fdt32_t size_dt_strings; /* size of the strings block */

/* version 17 fields below */
fdt32_t size_dt_struct; /* size of the structure block */
};

各成员含义为:

  • magic_number: 魔数,必须为0xd00dfeed
  • total_size: 该dtb文件总大小
  • off_dt_struct: dtb文件中,节点起始结构体的偏移地址
  • off_dt_strings: string部分的偏移地址
  • off_mem_rsvmap: 保留内存的偏移地址
  • version: 设备树版本号
  • last_comp_version: 兼容的版本号
  • boot_cpuid_phys: 没什么用
  • size_dt_strings: strings部分的大小
  • size_dt_struct: 节点结构体的总大小

dt strings

在dtb中有大量的重复字符串,比如”model”,”compatile”等等,为了节省空间,将这些字符串统一放在某个地址,需要使用的时候直接使用索引来查看。

需要注意的是,属性部分格式为key = value,key部分被放置在strings部分,而value部分的字符串并不会放在这一部分,而是直接放在structure中。

dt struct

每个节点都会被描述为一个struct,节点之间可以嵌套,因此也会有嵌套的struct。

struct结构体如下:

  • 0x00000001: 节点开始信号,表明一个节点的开始
  • 对于版本1-3而言,这一部分是节点的全路径,以/开头,而对于版本16及以上,这部分只是unit name(root 除外,它没有unit name),unit name是以0结尾的字符串
  • 可选的对齐字节
    • 如果unit name之后不是以4字节对齐的,用00填充直到4字节对齐
  • 对于某个属性的字段
    • 0x00000003: 节点属性信号,出现这个表明接下来是节点内的属性
    • 32位的数据,表明size
    • 32位的数据,表明属性名也就是key在strings中的偏移地址
    • 属性value
  • 如果有子节点,直接从开头0x00000001开始递归。
  • 0x00000002: 节点结束信号,表明一个节点的结束

mem rsvmap

描述保留的内存部分,这个map的数据结构是这样的:

  • 8字节的起始地址
  • 8字节的大小

在dts中使用/memreserve/ <start_addr> <size>来描述

手动解析dtb

先来一个dts例子:

/dts-v1/;
/memreserve/ 0xa00000000 0x10000000;
/ {
compatible = "hd,test_dts", "hd,test_xxx";
#address-cells = <0x1>;
#size-cells = <0x1>;
model = "HD test dts";

chosen {
stdout-path = "/ocp/serial@ffff";
};

memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};

led1:led@2000000 {
compatible = "test_led";
#address-cells = <0x1>;
#size-cells = <0x1>;
reg = <0x200 0x4>;
};
};

其中通过/memreserve/定义了保留内存。

定义了根节点,根节点下有属性,有三个字节点,每个字节点有自己的属性。

通过以下命令编译dtb:

dtc -I dts -O dtb -o test.dtb ./test.dts

使用如下命令打印输出:

❯ hexdump test.dtb -C
00000000 d0 0d fe ed 00 00 01 cc 00 00 00 48 00 00 01 84 |...........H....|
00000010 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 |...(............|
00000020 00 00 00 48 00 00 01 3c 00 00 00 0a 00 00 00 00 |...H...<........|
00000030 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 |................|
00000050 00 00 00 03 00 00 00 18 00 00 00 00 68 64 2c 74 |............hd,t|
00000060 65 73 74 5f 64 74 73 00 68 64 2c 74 65 73 74 5f |est_dts.hd,test_|
00000070 78 78 78 00 00 00 00 03 00 00 00 04 00 00 00 0b |xxx.............|
00000080 00 00 00 01 00 00 00 03 00 00 00 04 00 00 00 1a |................|
00000090 00 00 00 01 00 00 00 03 00 00 00 0c 00 00 00 26 |...............&|
000000a0 48 44 20 74 65 73 74 20 64 74 73 00 00 00 00 01 |HD test dts.....|
000000b0 63 68 6f 73 65 6e 00 00 00 00 00 03 00 00 00 11 |chosen..........|
000000c0 00 00 00 2c 2f 6f 63 70 2f 73 65 72 69 61 6c 40 |...,/ocp/serial@|
000000d0 66 66 66 66 00 00 00 00 00 00 00 02 00 00 00 01 |ffff............|
000000e0 6d 65 6d 6f 72 79 40 38 30 30 30 30 30 30 30 00 |memory@80000000.|
000000f0 00 00 00 03 00 00 00 07 00 00 00 38 6d 65 6d 6f |...........8memo|
00000100 72 79 00 00 00 00 00 03 00 00 00 08 00 00 00 44 |ry.............D|
00000110 80 00 00 00 10 00 00 00 00 00 00 02 00 00 00 01 |................|
00000120 6c 65 64 40 32 30 30 30 30 30 30 00 00 00 00 03 |led@2000000.....|
00000130 00 00 00 09 00 00 00 00 74 65 73 74 5f 6c 65 64 |........test_led|
00000140 00 00 00 00 00 00 00 03 00 00 00 04 00 00 00 0b |................|
00000150 00 00 00 01 00 00 00 03 00 00 00 04 00 00 00 1a |................|
00000160 00 00 00 01 00 00 00 03 00 00 00 08 00 00 00 44 |...............D|
00000170 00 00 02 00 00 00 00 04 00 00 00 02 00 00 00 02 |................|
00000180 00 00 00 09 63 6f 6d 70 61 74 69 62 6c 65 00 23 |....compatible.#|
00000190 61 64 64 72 65 73 73 2d 63 65 6c 6c 73 00 23 73 |address-cells.#s|
000001a0 69 7a 65 2d 63 65 6c 6c 73 00 6d 6f 64 65 6c 00 |ize-cells.model.|
000001b0 73 74 64 6f 75 74 2d 70 61 74 68 00 64 65 76 69 |stdout-path.devi|
000001c0 63 65 5f 74 79 70 65 00 72 65 67 00 |ce_type.reg.|
000001cc

解析dtb header

dtb header占用40个字节,也就是0x28个字节。

前40个字节如下:

00000000  d0 0d fe ed 00 00 01 cc  00 00 00 48 00 00 01 84  |...........H....|
00000010 00 00 00 28 00 00 00 11 00 00 00 10 00 00 00 00 |...(............|
00000020 00 00 00 48 00 00 01 3c

解析如下:

0xd00dfeed为magic,没什么好说的

0x000001cc是totalsize,hexdump结尾也给出了正确的大小:

000001c0  63 65 5f 74 79 70 65 00  72 65 67 00              |ce_type.reg.|
000001cc //这里就是文件大小

0x00000048是节点结构体偏移地址

0x00000184是strings的偏移地址

0x00000028是保留内存的偏移地址

0x00000011是设备树版本,17

0x00000010是兼容版本,16

0x00000000是boot_cpuid_phys,没啥用

0x00000048是string的占用字节

0x0000013c是节点结构体占用字节

保留内存分析

从dtb header中得到0x28是保留内存的偏移地址,那我们就从0x28开始看:

00000020  .. .. .. .. .. .. .. ..  00 00 00 0a 00 00 00 00  |...H...<........|
00000030 00 00 00 00 10 00 00 00 .. .. .. .. .. .. .. .. |................|

..为不需要关注的内容,0x28地址是0x0000000a00000000,去掉前导0就是0xa00000000。

与dts中设置的一致:

/memreserve/ 0xa00000000 0x10000000;

后面的0x10000000是大小,就不过多解释了。

节点结构体分析

从dtb header中得到0x48是节点结构体的偏移地址,那么从0x48开始看。

00000040  .. .. .. .. .. .. .. ..  00 00 00 01 00 00 00 00  |................|
00000050 00 00 00 03 00 00 00 18 00 00 00 00 68 64 2c 74 |............hd,t|
00000060 65 73 74 5f 64 74 73 00 68 64 2c 74 65 73 74 5f |est_dts.hd,test_|
00000070 78 78 78 00 00 00 00 03 00 00 00 04 00 00 00 0b |xxx.............|
00000080 00 00 00 01 00 00 00 03 00 00 00 04 00 00 00 1a |................|

在0x48处出现了0x00000001,根据节点结构体定义,可以知道这是一个节点开始的信号。

接下来是unit name,为0x00000000,说明这是个根节点,没有unit name.

0x00000003说明是一个属性的标志,0x00000018代表了value的size,0x00000000代表在string中的偏移地址,value是68 64 2c 74 65 73 74 5f 64 74 73 00 68 64 2c 74 65 73 74 5f 78 78 78 00

综上所述,该属性:

  • key在strings里面的偏移地址是0
  • value的大小是0x18
  • value是68 64 2c 74 65 73 74 5f 64 74 73 00 68 64 2c 74 65 73 74 5f 78 78 78 00

其中,strings的偏移地址是0x00000184,该属性的key在strings的偏移地址是0,那么该属性key的偏移地址就是strings的偏移地址。

strings如下:

00000180  .. .. .. .. 63 6f 6d 70  61 74 69 62 6c 65 00 23  |....compatible.#|
00000190 61 64 64 72 65 73 73 2d 63 65 6c 6c 73 00 23 73 |address-cells.#s|
000001a0 69 7a 65 2d 63 65 6c 6c 73 00 6d 6f 64 65 6c 00 |ize-cells.model.|
000001b0 73 74 64 6f 75 74 2d 70 61 74 68 00 64 65 76 69 |stdout-path.devi|
000001c0 63 65 5f 74 79 70 65 00 72 65 67 00 |ce_type.reg.|

可以看到偏移地址0处的ascii码就是63 6f 6d 70 61 74 69 62 6c 65

而且value正好是0x18个字节,并且ascii码与我们dts中一致:

compatible = "hd,test_dts", "hd,test_xxx";

其他的都跟根节点一样,不再赘述。

Ref

link1