本节讲述设备树的dtb格式。
上节讲述了dts格式。回顾上节,在dts文件和dtsi文件中,可以使用C语言的define和include,使用方法和作用也同C语言相同。
编写dts文件后,需要使用dtc工具将dts文件编译成dtb文件。dtc工具可以检查dts文件是否存在语法或格式错误,如果发现语法或格式上有错误,那么就会提示修改这些错误。
在dts文件中,可以包含一个或多个dtsi文件,通过dtc工具将这些文件编译得到一个dtb文件。同时,可以在dts文件中重新定义覆盖dtsi文件中设置的节点属性。
本节内容来自于以下两个文档,可以查看这两个文档获取更多关于dtb文件的信息。
官方文档:
https://www.devicetree.org/specifications/内核文档:
Documentation/devicetree/booting-without-of.txt
下图是dtb文件的布局,从上往下(开始到结束)依次是:
------------------------------
base -> | struct boot_param_header |
------------------------------
| (alignment gap) (*) |
------------------------------
| memory reserve map |
------------------------------
| (alignment gap) |
------------------------------
| |
| device-tree structure |
| |
------------------------------
| (alignment gap) |
------------------------------
| |
| device-tree strings |
| |
-----> ------------------------------
|
|
--- (base + totalsize)
头部(struct ftd_header)是一个结构体,其组成如下:
结构体成员的说明如下,注意magic是大端格式/大字节序(低地址放高字节,仅对存储数值有影响)存放的:
内存保留信息块(memory reservation block)也是一个结构体,其组成如下:
对上节的dts文件稍作修改,增加内存空间的定义。
假设要将64M内存的最高1M留下自己使用,则在dts文件中添加下面这句。
/memreserve/ 0x33f00000 0x100000;
修改后的dts文件如下。
// SPDX-License-Identifier: GPL-2.0
/*
* SAMSUNG SMDK2440 board device tree source
*
* Copyright (c) 2018 [email protected]
* dtc -I dtb -O dts -o jz2440.dts jz2440.dtb
*/
#define S3C2410_GPA(_nr) ((0<<16) + (_nr))
#define S3C2410_GPB(_nr) ((1<<16) + (_nr))
#define S3C2410_GPC(_nr) ((2<<16) + (_nr))
#define S3C2410_GPD(_nr) ((3<<16) + (_nr))
#define S3C2410_GPE(_nr) ((4<<16) + (_nr))
#define S3C2410_GPF(_nr) ((5<<16) + (_nr))
#define S3C2410_GPG(_nr) ((6<<16) + (_nr))
#define S3C2410_GPH(_nr) ((7<<16) + (_nr))
#define S3C2410_GPJ(_nr) ((8<<16) + (_nr))
#define S3C2410_GPK(_nr) ((9<<16) + (_nr))
#define S3C2410_GPL(_nr) ((10<<16) + (_nr))
#define S3C2410_GPM(_nr) ((11<<16) + (_nr))
/dts-v1/;
/memreserve/ 0x33f00000 0x100000;
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
memory { /* /memory */
device_type = "memory";
reg = <0x30000000 0x4000000 0 4096>;
};
/*
cpus {
cpu {
compatible = "arm,arm926ej-s";
};
};
*/
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
led {
compatible = "jz2440_led";
pin = ;
};
};
然后编译这个dts文件,将生成的dtb文件拉到windows下,再使用Hex Editor Neo/UE打开,分析dtb文件。
如前所述,首先是头部结构体fdt_header。
fdt_header的第一个成员是magic,用于校验是否为dtb文件,按照大字节序排列,文件中为0xd00dfeed,即fdt_header.magic=0xd00dfeed,符合设备树的magic要求,说明这是一个dtb文件。
然后是第二个成员totalsize,记录的是该dtb文件的大小,fdt_header.totalsize=0x1d1=465,也就是说dtb文件的总大小为465字节。
之后是off_dt_struct,储存的是structure block相对于dtb起始位置的偏移。
fdt_header.off_dt_struct=0x48=72。
off_dt_strings,储存的是string block相对于起始地址的偏移。
fdt_header.off_dt_strings=0x188=392。
off_mem_rsvmap ,储存的是memory reservation block相对于起始位置的偏移。fdt_header.off_mem_rsvmap=0x28=40。
version,储存的是设备树数据结构的版本。
fdt_header.version=0x11=17。
last_comp_version,储存的是所用版本向后兼容的最低版本的设备树数据结构,如本版本使用的是17版本,17版本兼容16版本,但不兼容16版本之前的版本,则该成员应该为0x10=16。
boot_cpuid_phys,储存的是系统引导CPU的物理ID,它的值应该与设备树文件中CPU节点下的reg属性值相等。
由于CPU节点被注释了,所以这里为全0。
fdt_header.boot_cpuid_phys=0。
size_dt_strings,存储的是string block的长度。
fdt_header.size_dt_strings=0x49=73。
string block的偏移地址是0x188,加上长度0x49,刚好等于0x1D1,和dtb大小相等。
size_dt_struct,储存的是structure block的长度。
fdt_header.size_dt_struct=0x140=320。
根据off_dt_struct可以知道,structure block的偏移地址为0x48,0x48+0x140=0x188,所以从0x48开始到0x188结束,中间的部分都是structure block。
最后总结一下jz2440.dtb文件中,fdt_header各个成员的值:
所以dtb文件的结构如下所示:
memory reservation blcok,储存的是dts文件中,指定的需要保留的内存的物理地址和大小。
在jz2440.dts文件中,指定了保留起始地址为0x33F00000,大小为0x100000的内存空间。
下面是memory reservation blcok的格式说明:
根据ftd_header信息可知,memory reservation blcok的起始地址为0x28,struct block的起始地址为0x48,所以0x28到0x48中间的部分都为memory reservation blcok。
jz2440的CPU是32bit的,所以高32位丢弃。
如图可见,第一个保留的内存块,address为0x33f00000,size为0x100000,与jz2440.dts文件中描述的一致;第二个保留的内存块信息为全0,说明memory reservation blcok到此结束。
Struct Block是由一系列连续的片组成的,每个片的以一个32位token开始,token按照大字节序存储,某些token后面还会接着额外的数据(extra data)。所有的token都是32位对齐的,不满32位的会在前面补0。
以下是五种token:
FDT_PROP (0x00000003):FDT_PROP 表示属性的开始,后接描述该属性信息的extra data,它的extra data是按以下C结构体存储的:
struct {
uint32_t len; //以字节为单位记录了属性值的长度(长度可能为0,表示一个空值);
uint32_t nameoff; //表示属性名在string block中的偏移位置;
}
这两个成员的数据类型都是32bit的整型,按照大字节序存储。
在这个结构体后面,存储的就是属性的值。属性值被储存为长度为len的字符串。如有必要,这个值可以按32bit对齐存储(不足32bit补0)。
下面为一个设备树节点的格式:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
以LED节点为例,说明一下设备节点的存储:
led {
compatible = "jz2440_led";
pin = ;
};
其他节点的分析也与LED节点相似,以FDT_BEGIN_NODE+节点名称开始,然后是FDT_PROP描述节点的属性,然后是struct(len+nameoff)+val,该节点的属性描述完成后,使用FDT_END_NODE表示节点结束;最后,当所有节点描述完毕后,使用FDT_END结束struct block。
分析如下图所示。
dtb文件大小为465字节。
如果将两块内存分为两个节点,在节点中加入内存的起始地址来区分,则设备如下:
编译后的dtb文件如下图: