上一节我们学习了dts文件的格式,dts文件是方便我们书写和读写的格式。本节我们来学习一下经过Device Tree Compiler编译,Device Tree source file变成了Device Tree Blob(又称作flattened device tree)的格式。
首先先给出dtb文件的格式
一个dtb文件分为4个段,分别是ftd_header,memory reservation block,structure block,strings block
struct fdt_header {
uint32_t magic;
uint32_t totalsize;
uint32_t off_dt_struct;
uint32_t off_dt_strings;
uint32_t off_mem_rsvmap;
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys;
uint32_t size_dt_strings;
uint32_t size_dt_struct;
};
这个区域包括了若干的reserve memory描述符。每个reserve memory描述符是由address和size组成。
struct fdt_reserve_entry {
uint64_t address;
uint64_t size;
};
结构块描述了设备本身的结构和内容。 它由一系列令牌组成有数据。 它们被组织成线性树结构。
结构块中的每个标记,以及结构块本身,应位于距离的4字节对齐的偏移处devicetree blob的开头。
下面列出总共的5种令牌格式:
FDT_BEGIN_NODE (0x00000001):FDT_BEGIN_NODE标记标记节点表示的开始。 它后面应该是节点的单元名称作为额外数据。 该名称存储为以空字符结尾的字符串,并且应包括单元地址(如果有)。 如果需要进行对齐,则节点名称后跟填充的填充字节,然后是下一个令牌,可以是除FDT_END之外的任何令牌。
FDT_END_NODE (0x00000002):FDT_END_NODE标记标记节点表示的结束。 此令牌没有额外数据; 所以它紧接着是下一个标记,它可以是除FDT_PROP之外的任何标记。
FDT_PROP(0x00000003):FDT_PROP标记标记了设备中一个属性的表示的开始。 随后应该有描述该属性的额外数据。 此数据首先包含属性的长度和名称,表示为以下C语言结构:
struct {
uint32_t len;
uint32_t nameoff;
}
这个结构中的两个字段都是32位大端整数。
len给出属性值的长度(以字节为单位)(可以为零,表示空属性)。
nameoff在字符串块中给出一个偏移量,在该块中,属性的名称存储为以null结尾的字符串。 在此结构之后,属性的值以长度为len的字节字符串给出。 该值之后是归零填充字节(如果需要)以对齐到下一个32位边界,然后是下一个令牌,可以是除FDT_END之外的任何令牌。
FDT_NOP(0x00000004)解析设备树的任何程序都将忽略FDT_NOP令牌。 此令牌没有额外数据; 所以紧接着是下一个令牌,它可以是任何有效的令牌。 可以使用FDT_NOP标记覆盖树中的属性或节点定义,以将其从树中移除,而无需在devicetree blob中移动树的表示的其他部分。
FDT_END(0x00000009)FDT_END标记标记结构块的结尾。 只有一个FDT_END标记,它应该是结构块中的最后一个标记。 它没有额外的数据; 所以紧接在FDT_END令牌之后的字节从结构块的开头偏移等于设备树blob头中的size_dt_struct字段的值。
strings块包含表示树中使用的所有属性名称的字符串。 这些空终止字符串在本节中简单地连接在一起,并通过字符串块中的偏移量从结构块中引用。
字符串块没有对齐约束,可能出现在devicetree blob开头的任何偏移处。
下面我们举一个例子:
为了方便学习,我们对上一节的dts文件精简如下(尽可能减少节点,好分析);
/dts-v1/;
/memreserve/ 0x4ff00000 0x100000;
/ {
model = "YIC System SMDKV210 based on S5PV210";
compatible = "yic,smdkv210", "samsung,s5pv210";
#address-cells = <1>;
#size-cells = <1>;
chosen {
bootargs = "console=ttySAC2,115200n8 root=/dev/nfs nfsroot=192.168.0.101:/home/run/work/rootfs/";
};
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x20000000>;
};
};
对齐进行编译,之后在linux下用hexdump工具打开
hexdump -C s5pv210-x210_simp.dtb
应为dtb文件是以大字节序排列的,所以分析时要注意转换。
fdt_header的第一个32位数代表,魔数magic,0xd00dfeed
fdt_header的第二个32位数代表,总字节数totalsize,0x1cd(由下面文件的左下角也可以看到)
fdt_header的第三个32位数代表,结构块的偏移量off_dt_struct,0x48
fdt_header的第四个32位数代表,字符串偏移量off_dt_strings,0x188
fdt_header的第五个32位数代表,预留内存的偏移量off_mem_rsvmap,0x28
fdt_header的第六个32位数代表,设备树版本,0x11,即17版本
fdt_header的第七个32位数代表,版本兼容信息0x16,即兼容16版本的设备树
fdt_header的第八个32位数代表,从哪个cpu启动,没选择从哪个cpu启动,那就是第0个
fdt_header的第九个32位数代表,字符串块的大小,0x45字节。69个字节
fdt_header的第十个32位数代表,结构块大小,0x140,即320个字节
上图中标出了魔数和结构块的起始地址0x48,结合结构块的起始令牌是0x1也能得出结论。
上图标出了字符串块在dtb文件的起始地址。
同时因为字符串块在结构块后面,结构块的最后一个标记FDT_END(0x00000009)可能得出结论。
上图表明字符串块占0x45个字节。
上图表明预留内存的偏移量在0x28处。
struct fdt_reserve_entry {
uint64_t address;
uint64_t size;
};
同时因为这里的起始地址和大小都是64bit的,所以为我表红的8个字节。
接下来我们分析一下结构块和字符串块。因为结构块和字符串快块是dtb文件中最重要的一部分,也稍微复杂一些。
首先结合上面分析的我们标出结构块的起始位置和字符串块的起始位置。
前面知道了用
FDT_BEGIN_NODE (0x00000001)标记一个节点开始。
FDT_END_NODE (0x00000002)标记一个节点结束。
FDT_PROP (0x00000003)标记一个节点的属性。
我们就以第一个为例
用红色标记的0x01表示一个节点的开始。
橘黄色的一般应该是节点的名字,根节点没名字,所以这里用四个字节的0保留。
蓝色的由0x03开始表示一个属性的开始,其后面跟着一个如下格式的结构体。
struct {
uint32_t len; //属性值的长度
uint32_t nameoff; //属性名字在字符串块中的偏移量
}
其中属性值长度为0x25,属性名字在字符串块的偏移量为0。
再后面用紫色标记的就属于属性的值了。
这里就是对应的就死描述dts文件中下面这句属性。
model = "YIC System SMDKV210 based on S5PV210";
接下来继续分析下一句(上一条结构块中不满4字节的会自动用0填充)
接下来紧接着就是下一个属性,该属性值长度为0x1d,属性名字在字符串块的偏移量为6.
compatible = "yic,smdkv210", "samsung,s5pv210";
接下来继续分析
接下来又是一个属性,属性值长度4字节,属性名字在字符串中的偏移量为0x11。
这里的属性值为0x01。
这一句对应着下面的dts文件中的代码
#address-cells = <1>;
接下来的#size-cells = <1>;也是相同的,这句就跳过不分析了。直接分析下一句。
这里要仔细说一下了。
黑色框起来的,0x01表示一个节点开始。
红色框起来的表示一个节点的名字。这里是closen
橙色框起来的表示一个属性,其中属性值长0x54个字节,属性名字在字符串快的偏移量为0x2c位置。
紫色框起来的表示,属性的值。
绿色框起来的0x02表示这个节点的结束。
当然绿色的就是属性的名字了。
当然memory属性和chosen分析方法是一样的,也就不再分析了。
再看一下树结构是如何在结构快里表示的。
黑色的起始可以理解为整棵大树,中间包含了两个字节点。
0x09表示整个结构块的结束。
最后,说一下,为什么要所有的属性名称放在字符串中?
其实这是一个取巧的方式,比如一个SOC的设备树文件中,有很多个节点都有compatible这样的属性,难道要备份很多份??
其实不然,这些相同名字的字符串通常保留一份就可以了。无论哪个节点的属性名字是compatible,在字符串块中名字的偏移量都是一个值就可以了。
当然我们平时写代码时的常量字符串如果有完全一样的,通常编译器也会优化成一句,以节省内存空间。