2. DTB文件格式详解:DTB的二进制结构、FDT头部解析、内存保留区、结构块、字符串块

设备树二进制文件(DTB)是设备树源文件(DTS)经过编译器(DTC)编译后生成的二进制格式。理解DTB的二进制结构是排查设备树加载失败、驱动无法匹配等问题的核心基础。DTB遵循扁平设备树(Flattened Device Tree, FDT)规范,其整体布局由四个主要部分组成:FDT头部、内存保留区、结构块和字符串块。

核心要点: DTB的二进制布局是固定的,Bootloader或内核在解析DTB时,首先读取头部获取各块的偏移和大小,然后按顺序解析。任何字节对齐错误或魔数不匹配都会导致加载失败。

2.1 DTB整体二进制布局

一个标准的DTB文件在内存中的布局顺序是固定的,如下图所示。所有多字节数据(如32位、64位整数)均采用大端序(Big-Endian)存储。

DTB 二进制文件整体布局 FDT 头部 (FDT Header) — 固定 40 字节 内存保留区 (Memory Reservation Block) — 8N 字节 结构块 (Structure Block) — 存放节点和属性 以 FDT_BEGIN_NODE / FDT_PROP 等令牌组成 字符串块 (Strings Block) — 存放属性名字符串 ⚠ 每个块起始地址必须 4 字节对齐,结构块内部令牌也需对齐
排查提示: 当使用 fdtget 或 dtc -I dtb -O dts 解析DTB失败时,首先检查文件头部魔数(Magic Number)是否为 0xD00DFEED。如果魔数错误,说明文件损坏或不是有效的DTB。

2.2 FDT头部解析

FDT头部固定为40字节,位于DTB文件的最开始。头部包含了整个DTB的元数据,用于定位其他三个块。下表列出了头部中所有字段及其含义:

偏移(字节) 字段名 大小(字节) 描述
0 magic 4 魔数,固定为 0xD00DFEED(大端序)
4 totalsize 4 整个DTB文件的总大小(字节)
8 off_dt_struct 4 结构块相对于文件起始的偏移地址
12 off_dt_strings 4 字符串块相对于文件起始的偏移地址
16 off_mem_rsvmap 4 内存保留区相对于文件起始的偏移地址
20 version 4 DTB版本号,当前主流版本为17
24 last_comp_version 4 向后兼容的最低版本号,通常为16
28 boot_cpuid_phys 4 引导CPU的物理ID,通常为0
32 size_dt_strings 4 字符串块的大小(字节)
36 size_dt_struct 4 结构块的大小(字节)
常见问题: 如果 off_dt_struct 或 off_dt_strings 的值超出了 totalsize 的范围,内核在解析时会直接报错并拒绝加载DTB。这通常是由于DTC编译时使用了错误的 -b 参数(指定了错误的物理地址)或文件被截断所致。

2.3 内存保留区

内存保留区用于告知内核哪些物理内存区域不能被操作系统使用(例如被固件、安全监控器或特定硬件保留)。该区域由一组 struct fdt_reserve_entry 结构体组成,每个结构体16字节,以 address=0, size=0 的条目结束。

struct fdt_reserve_entry {
    uint64_t address;  // 保留内存的起始物理地址(8字节,大端序)
    uint64_t size;     // 保留内存的大小(8字节,大端序)
};

内存保留区的总大小始终是8的倍数(因为每个条目16字节,且以空条目结束)。

内存保留区结构示例 address = 0x10000000 size = 0x00100000 address = 0x20000000 size = 0x00001000 address = 0x00000000 size = 0x00000000 保留 1MB 内存 保留 4KB 内存 结束标记
调试技巧: 使用 dtc -I dtb -O dts -o /dev/stdout your.dtb | grep -i "memreserve" 可以快速查看DTB中声明的所有保留内存区域。如果驱动申请DMA缓冲区时失败,可以检查是否与保留区冲突。

2.4 结构块

结构块是DTB的核心部分,以令牌(Token)序列的形式存储了设备树的所有节点和属性。每个令牌是一个32位的大端整数,后面可能跟随可变长度的数据。结构块以 FDT_END 令牌结束。

下表列出了结构块中使用的所有令牌及其含义:

令牌名称 值(32位) 描述
FDT_BEGIN_NODE 0x00000001 标记一个设备树节点的开始,后面紧跟节点名字符串(包含路径名,如 soc 或 uart@1000)
FDT_END_NODE 0x00000002 标记一个设备树节点的结束
FDT_PROP 0x00000003 标记一个属性,后面跟随 struct fdt_property 结构体(包含属性值长度、属性名在字符串块中的偏移、属性值数据)
FDT_NOP 0x00000004 空操作,用于填充对齐或占位,解析器应跳过
FDT_END 0x00000009 结构块的结束标记

结构块中节点和属性的嵌套关系如下所示:

结构块令牌序列示例(简化) FDT_BEGIN_NODE "/" (根节点名) FDT_PROP "model" = "Board A" FDT_BEGIN_NODE "cpu@0" FDT_PROP "compatible" = "arm,cortex-a7" FDT_END_NODE FDT_END_NODE FDT_END 根节点开始 子节点开始 子节点结束
解析规则: 解析器遇到 FDT_BEGIN_NODE 时入栈,遇到 FDT_END_NODE 时出栈。属性(FDT_PROP)总是属于当前栈顶的节点。这种嵌套结构保证了设备树的层次关系在二进制层面得以保留。

2.5 字符串块

字符串块是一个简单的字节数组,用于存储所有属性(Property)的名字。结构块中的每个 FDT_PROP 令牌并不直接包含属性名字符串,而是包含一个 偏移量(nameoff),指向字符串块中的某个位置。这种设计避免了属性名在结构块中重复存储,节省了空间。

字符串块的结构特点:

  • 字符串块起始于 off_dt_strings 指定的偏移地址。
  • 块内包含多个以 \0 结尾的字符串,例如 "compatible\0"、"reg\0"、"status\0" 等。
  • 字符串块的总大小由头部字段 size_dt_strings 指定。
  • 属性名在字符串块中的偏移量(nameoff)是相对于字符串块起始地址的偏移。
字符串块布局示例 字符串块起始 (off_dt_strings) compatible\0 偏移 0 reg\0 偏移 11 status\0 偏移 15 model\0 偏移 22 结构块中 FDT_PROP 的 nameoff 字段指向这里
排查技巧: 如果驱动在解析设备树时报告 of_property_read_string 返回 -EINVAL 或 -ENODATA,可能是字符串块中的属性名偏移量(nameoff)超出了 size_dt_strings 的范围。使用 hexdump -C your.dtb 可以手动验证偏移值是否合理。

2.6 字节对齐规则

DTB格式对字节对齐有严格要求,违反对齐规则会导致解析器读取到错误的数据。以下是关键的对齐规则:

  • 整体对齐: DTB文件的总大小(totalsize)必须是4的倍数。
  • 块对齐: 内存保留区、结构块、字符串块的起始偏移(off_mem_rsvmap、off_dt_struct、off_dt_strings)必须4字节对齐。
  • 令牌对齐: 结构块中的每个令牌(FDT_BEGIN_NODE、FDT_END_NODE、FDT_PROP等)必须从4字节对齐的地址开始。
  • 属性值对齐: 属性值数据(紧随 struct fdt_property 之后)必须4字节对齐,必要时在属性值后面填充 FDT_NOP 令牌。
  • 节点名对齐: 节点名字符串(紧随 FDT_BEGIN_NODE 令牌之后)必须4字节对齐,不足时填充 \0 字节。
常见错误: 手动修改DTB(例如使用 fdtput 或十六进制编辑器)后,如果没有重新计算并更新 totalsize 和 size_dt_struct 字段,内核在解析时会因为大小不匹配而报错。建议始终使用 dtc 工具重新编译DTB,避免手动修改二进制文件。

2.7 使用命令行工具验证DTB结构

在实际排查中,以下命令可以帮助你快速验证DTB的二进制结构是否正确:

# 1. 查看DTB头部信息(十六进制)
hexdump -n 40 -v -e '4/1 "%02x " "\n"' your.dtb

# 2. 使用 fdtdump 解析并打印所有节点和属性
fdtdump your.dtb

# 3. 使用 dtc 反编译为 DTS 格式(如果成功,说明结构基本正确)
dtc -I dtb -O dts -o /dev/stdout your.dtb

# 4. 检查结构块大小与头部声明是否一致
# 先获取头部中的 size_dt_struct(偏移36,4字节)
hexdump -s 36 -n 4 -v -e '4/1 "%02x "' your.dtb
# 然后计算实际结构块大小(从 off_dt_struct 到文件末尾减去字符串块大小)
总结: DTB的二进制结构虽然看似复杂,但遵循严格的规范。FDT头部是入口,内存保留区管理物理内存,结构块存储层次化的节点和属性,字符串块提供属性名的集中存储。掌握这四个部分的布局和解析规则,是定位设备树加载失败、属性读取错误、驱动匹配异常等问题的关键技能。