0%

Linux设备树DTB分析

本文Linux内核版本:Linux 4.1.15,使用到的工具有hexdump,fdtdump,dtc

一.DTB的结构

  1. DTB Structure

    DTB(Device Tree Blob)就是DTS经过DTC编译之后的二进制文件,也叫作Flattened Device tree。如图,DTB分为4个section,分别是ftd_header、memory reservation block、Structure block、strings block。下面将从以上四部分对DTB展开叙述。

    DTB二进制文件

  2. struct fdt_header是DTB的header部分,作用就是描述了整个DTB或者DTB其他部分的内容,以便于数据的定位。

    路径:scripts\dtc\libfdt\fdt.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    struct 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的大小
  3. memory reservation block

    内存预留块一般不用于普通的内存分配,主要用来保护一些重要的数据结构不被覆盖。bootLoader在设备树里可以通过memory节点指定保留的内存区域,Linux里用struct fdt_reserve_entry来描述

    路径:scripts\dtc\libfdt\fdt.h

    1
    2
    3
    4
    struct fdt_reserve_entry {
    uint64_t address;
    uint64_t size;
    };

    address和size都是64位的,如果是32位机,那么高32位会被忽略,通过指定一个address和size就可以确定要保留的内存范围

  4. 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
      6
      struct 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里的数据应该是按照下图方式排布的:

      DTB组织形式

  5. strings block

    strings block里存放的就是所有出现的property的名字,property可以通过nameoff字段来得到其名称(null-terminate string)

二.DTB的实例分析

截取一个IMX6UL的设备树部分代码,我们来分析一下具体的DTB

xxxx.dts文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/dts-v1/;

#include <dt-bindings/input/input.h>
#include "imx6ul.dtsi"

/ {
model = "Freescale i.MX6 UltraLite 14x14 EVK Board";
compatible = "fsl,imx6ul-14x14-evk", "fsl,imx6ul";

memory {
reg = <0x80000000 0x10000000>;
};

reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;

linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x4000000>;
linux,cma-default;
};
};
......

imx6ul.dtsi文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <dt-bindings/clock/imx6ul-clock.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "imx6ul-pinfunc.h"
#include "skeleton.dtsi"

/ {
aliases {
can0 = &flexcan1;
can1 = &flexcan2;
ethernet0 = &fec1;
ethernet1 = &fec2;
gpio0 = &gpio1;
gpio1 = &gpio2;
gpio2 = &gpio3;
gpio3 = &gpio4;
gpio4 = &gpio5;
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
i2c3 = &i2c4;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
serial0 = &uart1;
serial1 = &uart2;
serial2 = &uart3;
serial3 = &uart4;
serial4 = &uart5;
serial5 = &uart6;
serial6 = &uart7;
serial7 = &uart8;
spi0 = &ecspi1;
......

skeleton.dtsi文件:

1
2
3
4
5
6
7
/ {
#address-cells = <1>;
#size-cells = <1>;
chosen { };
aliases { };
memory { device_type = "memory"; reg = <0 0>; };
};

我们使用到的dts是xxx.dts,由于其引用了另外的dtsi,因此最终肯定把xxx.dts所引用到的dtsi全部展开,如果在其他dtsi文件里定义过的property,在xxx.dts里会覆盖掉原有的值,然后生成一个dest.dts文件,最后再由DTC编译成DTB文件。
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

最终的DTS文件