[置顶] 泰晓 RISC-V 实验箱,配套 30+ 讲嵌入式 Linux 系统开发公开课
扁平化设备树(DTB)格式剖析之三:扁平化设备树示例
Corrector: TinyCorrect v0.1-rc3 - [tables epw] Author: iOSDevLog iosdevlog@iosdevlog.com Date: 2022/08/22 Revisor: Falcon falcon@tinylab.org Project: RISC-V Linux 内核剖析 Sponsor: PLCT Lab, ISCAS
前几篇文章介绍了扁平化设备树(DTB)标准,这一篇我们将以一个真实的 DTB 文件的部分内容为例,使用图文结合的方式,详细介绍 DTB 格式展开后生成的设备树结构。
DTB 文件准备
我们以 Linux v5.19 RISC-V 默认生成的 arch/riscv/boot/dts/sifive/hifive-unmatched-a00.dtb
举例。
不过这个 dtb 文件有点大,我们先用 fdtdump
反编译成 dts
文件,然后删除大部分的内容,只保留部分有代表性的子节点和部分属性。
最后留下以下内容。
/dts-v1/;
// magic: 0xd00dfeed
// totalsize: 0x20e (526)
// off_dt_struct: 0x38
// off_dt_strings: 0x1c4
// off_mem_rsvmap: 0x28
// version: 17
// last_comp_version: 16
// boot_cpuid_phys: 0x0
// size_dt_strings: 0x4a
// size_dt_struct: 0x18c
/ {
#address-cells = <0x00000002>;
#size-cells = <0x00000002>;
compatible = "sifive,hifive-unmatched-a00", "sifive,fu740-c000", "sifive,fu740";
model = "SiFive HiFive Unmatched A00";
chosen {
stdout-path = "serial0";
};
soc {
#address-cells = <0x00000002>;
#size-cells = <0x00000002>;
compatible = "simple-bus";
ranges;
serial@10011000 {
compatible = "sifive,fu740-c000-uart", "sifive,uart0";
reg = <0x00000000 0x10011000 0x00000000 0x00001000>;
status = "okay";
};
};
};
精简后的 DTB 文件,足够我们分析它主要的结构。
工具介绍
准备好示例文件后,我们使用一个专门分析二进制文件的工具 ImHex 来高亮显示 DTB 文件的结构。
ImHex 是一款功能强大的十六进制编辑器,该工具专为逆向工程分析师、编程开发人员以及那些想好好保护自己眼睛的安全人员所设计。
哪怕你每天工作到凌晨三点(虽然不建议),也不会伤害你的眼睛!
功能介绍
- 功能丰富的十六进制数据界面:字节修复、修复管理、字节拷贝(字节、十六进制字符串、C、C++、C#、Rust、Python、Java 和 JavaScript 数组、HTML 自包含 div 等)。
- 字符串、十六进制搜索。
- 自定义 C++ 类模式语言,支持对文件内容进行解析和高亮显示。
- 数据导入:支持 Base64 文件、IPS 和 IPS32。
- 数据导出:IPS 和 IPS32。
- 数据检查器允许解释多种不同类型的数据(小端和大端)。
- 大文件支持和快速有效的加载。
- 文件哈希支持:CRC16、CRC32、MD4、MD5、SHA-1、SHA-224、SHA-256、SHA-384 和 SHA-512。
- 反汇编程序支持多种不同的体系结构:ARM32 (ARM, Thumb, Cortex-M, AArch32)、ARM64、MIPS (MIPS32, MIPS64, MIPS32R6, Micro)、x86 (16-bit, 32-bit, 64-bit)、PowerPC (32-bit, 64-bit)、SPARC、IBM SystemZ、xCORE、M68K、TMS320C64X、M680X 和 Ethereum。
- 支持书签、区域突出显示和注释。
- 数据分析:提供文件解析器和 MIME 类型数据库、字节分布图、熵图、最高平均熵、加密/压缩文件检测。
- 其他实用工具:ASCII 表、正则表达式替换、数学表达式计算器、十六进制颜色选择器。
- 在深夜使用时不会“烧坏”你的视网膜。
模式语言
ImHex 所使用的开发基于自定义类 C++ 模式语言,易于阅读、理解和学习。
感兴趣的同学可以在 ImHex 中点击 Help -> Pattern Language Cheat Sheet 来了解更多。
DTB 文件分析
根据前两篇文章的介绍,我们知道 DTB 主要由以下 4 块组成:
- 报头
- 内存保留块
- 结构体块
- 字符串块
我们先将 4 个不同的块用 ImHex 高亮显示出来。
通过上图,我们很容易看出 DTB 文件中 4 种颜色代表 4 种不同的块,接下来我们按照 4 个块的顺序,依次分析每块的结构。
报头
我们首先分析一下 DTB 报头,将 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 */
};
#define FDT_MAGIC 0xd00dfeed /* 4: version, 4: total size */
#define FDT_TAGSIZE sizeof(fdt32_t)
我们整理一个表格将报头的字段和 DTB 中对应的值列出来。
字段 | 值 | 说明 |
---|---|---|
magic | 0xd00dfeed | 值必须为 0xd00dfeed(大端) |
totalsize | 0x20e (526) | 设备树数据结构体的总大小 |
off_dt_struct | 0x38 | 结构体块相对报头开始的的偏移量 |
off_dt_strings | 0x1c4 | 字符串块相对报头开始的的偏移量 |
off_mem_rsvmap | 0x28 | 内存保留块相对报头开始的内存保留块的字节偏移量 |
version | 17 | 版本 |
last_comp_version | 16 | 向下兼容的设备树数据结构的最低版本 |
boot_cpuid_phys | 0x0 | 系统引导 CPU 的物理 ID |
size_dt_strings | 0x4a | 字符串块的字节长度 |
size_dt_struct | 0x18c | 结构体块的字节长度 |
其中,在 DTB 的报头中定义了后面 3 块的偏移量。
结构体块和字符串块的长度是不定的,在报头中不仅要定义偏移量,还需要定义块的长度。
内存保留块
内存保留块由一组 64 位大端整数对的列表组成,每对用以下 C 结构体表示。
struct fdt_reserve_entry {
uint64_t address;
uint64_t size;
};
每对都给出了保留内存区域的物理地址和大小(以字节为单位),这些给定区域不应相互覆盖。
保留块列表应以地址和大小均等于 0 的输入(结构体)结束。
这说明我们可以定义个多内存保留块,只不过我们这里一块都没有定义。
注意地址和大小值始终为 64 位。
在 32 位 CPU 上,值的高 32 位被忽略。
内存预留块中的每个 uint64_t 以及整个内存预留块都应位于距设备树 blob 开头的 8 字节对齐偏移处对齐。
结构体块
这应该是 dtb 文件里面最重要的内容了。
我们首先把结构体块相关代码贴出来。
struct fdt_node_header {
fdt32_t tag;
char name[];
};
struct fdt_property {
fdt32_t tag;
fdt32_t len;
fdt32_t nameoff;
char data[];
};
#define FDT_BEGIN_NODE 0x1 /* Start node: full name */
#define FDT_END_NODE 0x2 /* End node */
#define FDT_PROP 0x3 /* Property: name off, size, content */
#define FDT_NOP 0x4 /* nop */
#define FDT_END 0x9
为了便于分析结构体块,我们直接把 ImHex 生成的高亮图先放出来。
ImHex 有非常多专业的功能,我们这里使用的是:自定义 C++ 类模式语言,支持对文件内容进行解析和高亮显示。
通过上图可看出,在 ImHex 右上角的 Pattern editor 区域,我们可以定义 C/C++ 语言类似的模式语言。
这里简单的介绍一下 Pattern editor 的使用方法:
- 先声明使用大端模式,在算偏移量的时候会用到
fdt32_t
的类型当成u32
- 定义结构体:直接将
struct fdt_header
粘贴进来 - 报头结构体变量及地址:
fdt_header header @ 0x00;
再加上这一句就可以把报头高亮显示出来了 - 还可以通过左下文的 Pattern Data 区域看到结构化的高亮数据
根据前两篇的文章,我们先把结构体块的存储分布总结一下:
- 节点
- 以 FDT_BEGIN_NODE 开头
- 接着是节点名
- 以 FDT_END_NODE 结束
- 可以包含子节点
- 可以包含属性
- 属性
- 以 FDT_PROP 开头
- 接着指定属性值的长度
- 接着是属性名在字符串块的偏移量
- 接着是属性值,其长度在前面已指定
- 结尾
- 结构体块最后以 FDT_END 结束
这样得到的设备树是扁平的,我们后面会将扁平设备树展开成一颗真正的树。
字符串块
字符串块包含表示树中使用的所有属性名称的字符串。
这些空终止字符串在本块中简单地连接在一起,并从结构体块中通过偏移量引用到字符串块中。
字符串块没有对齐约束,可以出现在距设备树 blob 开头的任何偏移处。
总结
通过 ImHex 辅助的高亮显示,我们很容易的看到扁平设备树的结构。
我们再将扁平的树展开,就得到下图所示的树状结构。
通过上图,我们能更清楚的看到设备树 blob 的结构:
- DTB 文件分为报头,内存保留块,结构体块和字符串块
- 报头
- 一共有 10 个字段,每个字段 4 个字节
- 占 40 个字节
- 第 1 个魔幻字段内容固定为 0xd00dfeed
- 有 1 个 totalsize 字段定义了整个文件的长度
- 有 3 个字段定义了其他 3 个块的偏移量
- 有 2 个字段定义了结构体块和字符串块的大小
- 内存保留块
- 可以定义多个内存定义块
- 以地址和大小均等于 0 的输入(结构体)结束
- 结构体块
- 节点可以有子节点
- 节点可以有属性
- 节点和属性令牌后紧跟着名字在字符串块的偏移
- 最后以 FDT_END 结束结构体块
- 字符串块
- 属性的名称可能会重复
- 属性中指定在字符串块的偏移就可能在字体串块找到属性的名称
本文以一个真实 DTB 文件的部分内容为例,使用图文结合的方式,详细介绍 DTB 格式展开后生成的设备树结构。
接下来,我们将深入设备树的源码,具体分析以下 3 个问题:
- DTB 文件如何生成
device_node
树 device_node
树如何生成平台设备platform_device
- DTB 中的什么节点才能生成平台设备
platform_device
参考资料
猜你喜欢:
- 我要投稿:发表原创技术文章,收获福利、挚友与行业影响力
- 知识星球:独家 Linux 实战经验与技巧,订阅「Linux知识星球」
- 视频频道:泰晓学院,B 站,发布各类 Linux 视频课
- 开源小店:欢迎光临泰晓科技自营店,购物支持泰晓原创
- 技术交流:Linux 用户技术交流微信群,联系微信号:tinylab
支付宝打赏 ¥9.68元 | 微信打赏 ¥9.68元 | |
请作者喝杯咖啡吧 |
Read Album:
- TinyBPT 和面向 buildroot 的二进制包管理服务(3):服务端说明
- TinyBPT 和面向 buildroot 的二进制包管理服务(2):客户端说明
- TinyBPT 和面向 buildroot 的二进制包管理服务(1):设计简介与框架
- RISC-V Linux 内核及周边技术动态第 118 期
- RISC-V Linux 内核及周边技术动态第 117 期