Linux 内核:设备树(1)dtb格式

背景

dtb作为二进制文件被加载到内存中,然后由内核读取并进行解析,如果对dtb文件的格式不了解,那么在看设备树解析相关的内核代码时将会寸步难行,而阅读源代码才是了解设备树最好的方式。

所以,如果需要更透彻的了解设备树解析的细节,第一步就是需要了解设备树的格式。

注:本文部分参考:官方文档

dtb的由来

设备树的一般操作方式是:开发人员根据开发需求编写dts文件,然后使用dtc将dts编译成dtb文件。

dts文件是文本格式的文件,而dtb是二进制文件,在linux启动时被加载到内存中,接下来我们需要来分析设备树dtb文件的格式。

dtb格式总览

dtb的格式是这样的:

dtb header

但凡涉及到数据的记录,就一定会有一个总的描述部分,就像磁盘的超级块,书的目录,dtb当然也不例外,这个描述头部就是dtb的header部分,通过这个header部分,用户可以快速地了解到整个dtb的大致信息。

header可以用这么一个结构体来描述:

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

设备树的魔数,魔数其实就是一个用于识别的数字,表示设备树的开始,linux dtb的魔数为 0xd00dfeed.

totalsize

这个设备树的size,也可以理解为所占用的实际内存空间。

off_dt_struct

offset to dt_struct,表示整个dtb中structure部分所在内存相对头部的偏移地址

off_dt_strings

offset to dt_string,表示整个dtb中string部分所在内存相对头部的偏移地址

off_mem_rsvmap

offset to memory reserve map,dtb中memory reserve map所在内存相对头部的偏移地址,

version

设备树的版本,截至目前的最新版本为17.

last_comp_version

最新的兼容版本

boot_cpuid_phys

这部分仅在版本2中存在,后续版本不再使用。

size_dt_strings

表示整个dtb中string部分的大小

size_dt_struct

表示整个dtb中struct部分的大小

alignment gap

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

memory reserve map

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

{
uint64_t physical_address;
uint64_t size;
}

这部分存储了此结构的列表,整个部分的结尾由一个数据为0的结构来表示(即physical_address和size都为0,总共16字节)。

这一部分的数据并非是节点中的memory子节点,而是在设备开始之前(也就是第一个花括号之前)定义的,例如:

/dts-v1/
/memreserve/ 0x10000000 0x100000
/*在结构提中的表示为 physical_address=0x10000000,size=0x100000 */
{
...
}

这一部分的作用是告诉内核哪一些内存空间需要被保留而不应该被系统覆盖使用,因为在内核启动时常常需要动态申请大量的内存空间,只有提前进行注册,用户需要使用的内存才不会被系统征用而造成数据覆盖。

值得一提的是,对于设备树而言,即使不指定保留内存,系统也会默认为设备树保留相应的内存空间。

同时,这一部分需要64位(8字节)对齐。

device-tree structure

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

structure的的结构是这样的:

  • 一个node开始信号,OF_DT_BEGIN_NODE,内容为:0x00000001
  • 对于版本1-3而言,这一部分是节点的全路径,以/开头,而对于版本16及以上,这部分只是unit name(root 除外,它没有unit name),unit name是以0结尾的字符串
  • 可选的对齐字节
  • 对于每个属性字段:
    • 由OF_DT_PROP标识,数据为0x00000003
    • 32位的数据,表示属性的size
    • 32位的数据,表示属性名在string block中的偏移地址
    • 属性中的value data.
  • 如果有子节点,递归地对子节点进行描述。
  • 节点结束信号,OF_DT_END_NODE ,数据为0x00000002.

    每个节点的信息都按照上述结构被描述,需要注意的是,所有用于描述一个特定节点的属性都必须在任何子节点之前定义,虽然设备树的层次结构不会因此产生二义性,但是linux kernel的解析程序要求这么做。

device-tree strings

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

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

dtb文件解析示例

光说不练假把式,下面我就使用一个简单的示例来剖析dtb的文件格式。

下述示例仅仅是一个演示demo,不针对任何平台,为了演示方便,编写了一个非常简单的dts文件。

/dts-v1/;
/ {
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>;
};
};

编译当前dts文件,获取对应的dtb文件。

鉴于dtb文件为二进制文件,普通编辑器打开显示乱码,我们使用ultraEdit查看,它将数据以16进制形式显示:

整个dtb文件还是比较简单的,图中的红色框出的部分为header部分的数据,可以看到:

第1个四字节对应magic,数据为 D00DFEED.
第2四字节对应totalsize,数据为 000001BC,可以由整张图片看出,这个dtb文件的大小由0x0~0x1bb,大小刚好是0x1bc
第3个四字节对应off_dt_struct,数据为00000038。
第4个四字节对应off_dt_strings,数据为00000174,可以由整张图片看到,从0x174开始刚好是字符串开始的地方
第5个四字节对应off_mem_rsvmap,数据为00000028
第6个四字节对应version,数据为00000011,十进制为17
第7个四字节对应last_comp_version,数据为00000010,十进制为16,表示兼容版本16
第8个四字节对应boot_cpuid_phys,数据为00000000,仅在版本2中使用,这里为0
第9个四字节对应size_dt_strings,数据为00000048,表示字符串总长。
第10个四字节对应size_dt_struct,数据为0000013c,表示struct部分总长度。

整个头部为40字节,16进制为0x28,从头部信息中off_mem_rsvmap部分可以得到,reserve memory起始地址为0x28,上文中提到,这一部分使用一个16字节的struct来描述,以一个全为0的struct结尾。

后16字节全为0,可以看出,这里并没有设置reserve memory。

structure 部分

上文回顾:每一个属性都是以 key = value的形式来描述,value部分可选。

偏移地址来到0x00000038(0x28+0x10),接下来8个字节为00000003,根据上述structure中的描述,这是OF_DT_PROP,即标示属性的开始。

接下来4字节为00000018,表明该属性的value部分size为24字节。

接下来4字节是当前属性的key在string 部分的偏移地址,这里是00000000,由头部信息中off_dt_strings可以得到,string部分的开始为00000174,偏移地址为0,所以对应字符串为"compatible".

之后就是value部分,这部分的数据是字符串,可以直接从图片右侧栏看出,总共24字节的字符串"hd,test_dts", "hd,test_xxx",因为字符串之间以0结尾,所以程序可以识别出这是两个字符串。

可以看出,到这里,compatible = "hd,test_dts", "hd,test_xxx";这个属性就被描述完了,对于属性的描述还是非常简单的。

按照固有的规律,接下来就是对#address-cells = <0x1>的解析,然后是#size-cells = <0x1>...

然后就是递归的子节点chosen,memory@80000000等等都是按照上文中提到的structure解析规则来进行解析,最后以00000002结尾。

与根节点不同的是,子节点有一个unit name,即chosen,memory@80000000这些名称,并非节点中的.name属性。

而整个结构的结束由00000009来描述。

一般而言,在32位系统中,dtc在编译dts文件时会自动考虑对齐问题,所以对于设备树的对齐字节,我们只需要有所了解即可,并不会常接触到。

好了,关于linux设备树dtb文件格式的讨论就到此为止啦,如果朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言

关于linux设备树在内核启动时的解析可以参考我的另外两篇博客:

linux设备树的解析--dtb转换成device_node

linux设备树的解析--device_node转换成platform_deviec

原创博客,转载请注明出处!

祝各位早日实现项目丛中过,bug不沾身.

Linux 内核:设备树(1)dtb格式的更多相关文章

  1. linux设备驱动程序-设备树(0)-dtb格式

    linux设备树dtb格式 设备树的一般操作方式是:开发人员根据开发需求编写dts文件,然后使用dtc将dts编译成dtb文件. dts文件是文本格式的文件,而dtb是二进制文件,在linux启动时被 ...

  2. Linux内核 设备树操作常用API【转】

    转自:https://www.linuxidc.com/Linux/2017-02/140818.htm 一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在&qu ...

  3. Linux内核 设备树操作常用API

    Linux设备树语法详解一文中介绍了设备树的语法,这里主要介绍内核中提供的操作设备树的API,这些API通常都在"include/of.h"中声明. device_node 内核中 ...

  4. linux设备驱动程序-设备树(1)-dtb转换成device_node

    linux设备驱动程序-设备树(1)-dtb转换成device_node 本设备树解析基于arm平台 从start_kernel开始 linux最底层的初始化部分在HEAD.s中,这是汇编代码,我们暂 ...

  5. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

  6. Linux dts 设备树详解(一) 基础知识

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 1 前言 2 概念 2.1 什么是设备树 dts(device tree)? 2. ...

  7. Linux 获取设备树源文件(DTS)里描述的资源

    Linux 获取设备树源文件(DTS)里的资源 韩大卫@吉林师范大学 在linux使用platform_driver_register() 注册 platform_driver 时, 需要在 plat ...

  8. (转)Linux内核基数树应用分析

    Linux内核基数树应用分析 ——lvyilong316 基数树(Radix tree)可看做是以二进制位串为关键字的trie树,是一种多叉树结构,同时又类似多层索引表,每个中间节点包含指向多个节点的 ...

  9. Linux 获取设备树源文件(DTS)里的资源【转】

    本文转载自:http://blog.csdn.net/keleming1/article/details/51036000 http://www.cnblogs.com/dyllove98/archi ...

  10. Linux 获取设备树源文件(DTS)里描述的资源【转】

    转自:http://www.linuxidc.com/Linux/2013-07/86839.htm 转自:http://blog.sina.com.cn/s/blog_636a55070101mce ...

随机推荐

  1. ESP32 SNTP校时

    一.连接WIFI 在进行时间同步之前,先连接WIFI #include "wifi.h" #include <string.h> #include <stdlib ...

  2. 13、web 中间件加固-Nginx 加固

    1.隐藏版本信息 避免被针对版本直接使用漏洞 修改 nginx.conf 文件 在 http 模块中添加信息:server_tokens off; 2.限制目录权限 某些目录为运维页面,不要公开访问 ...

  3. SQL中常用的字符串REVERSE函数和SUBSTRING函数详解!

    今天继续整理日常可能经常遇到的一些处理字符串的函数,记得点赞收藏!以备不时之需! REVERSE(expression)函数解析:SQL Server中的此函数用于反转(颠倒)指定的字符串,也就是说把 ...

  4. mysql如何优雅的备份数据

    MySQL 有多种备份方式,以下是几种常用的备份方式: 使用 mysqldump 命令备份数据 mysqldump 是 MySQL 自带的备份工具,可以备份指定数据库或表的数据为 SQL 文件.可以通 ...

  5. LLM实战:LLM微调加速神器-Unsloth + LLama3

    1. 背景 五一结束后,本qiang~又投入了LLM的技术海洋中,本期将给大家带来LLM微调神器:Unsloth. 正如Unsloth官方的对外宣贯:Easily finetune & tra ...

  6. 两个List合并,List集合中的对象根据某个相同的属性,合并另外属性

    简介 (Introduction): 背景 需要对数据进行拼接,拼接的数据是存在两个不同的表中,但是,拼接后要作为一个对象显示,但是,这样的对象又是多个的. 结构图数据库模型 id name 1008 ...

  7. debug技巧之远程调试

    一.前言 大家好啊,我是summo,今天给大家分享一下我平时是怎么调试代码的,不是权威也不是教学,就是简单分享一下,如果大家还有更好的调试方式也可以多多交流哦. 当我们的应用发布到线上之后,就不能随意 ...

  8. 大数据之Hadoop中HDFS的故障排除

    NameNode故障处理 1)需求 NameNode进程挂了并且存储的数据也丢失了 2)故障模拟 (1)kill -9 NameNode进程 kill -9 19886 (2)删除NameNode储存 ...

  9. 高精度离线免费 的C#文字识别PaddleOCR库

    随便打开一个Microsoft Visual Studio,新建一个WinForms项目,从下面列表中随便选择一个NET框架.目标平台要设置成X64,该OCR仅支持64位. net35;net40;n ...

  10. UINavgationBar事件穿透

    一.事件起因 最近在开发一版本的需求中,遇到一个问题,需要在一个ViewController的顶部,UINavgationBar的下面放置一个View,这个View需要能够正常收到事件 将我们的Vie ...