☰
coreboot 中的 Flashmap 与 FMD 布局描述语言:固件分区格式与工程实践
2026/10/14 5:25:46 网站建设 项目流程
  • 固件
  • 嵌入式
  • 操作系统
  • 驱动开发

【免费下载链接】coreboot

Read-only mirror of https://review.coreboot.org/coreboot.git. Synced every hour. We don't handle Pull Requests.

项目地址:https://gitcode.com/gh_mirrors/co/coreboot
点击查看免费下载

导读

coreboot 的固件镜像(coreboot.rom)不再只是一个被 CBFS 打包的"大文件",而是可以通过 Flashmap(FMAP)将整个 flash 芯片划分为若干边界清晰的独立分区。本文以 Documentation/lib/flashmap.md 为主体,结合仓库内struct fmap的二进制定义、FMD 语言编译器(Lex/Yacc 实现)与fmaptool工具源码,系统讲解 FMAP 的二进制格式、FMD 描述语言的完整语法与工程约束,并给出从.fmd文件到最终镜像的完整工具链与运行时查询 API,帮助你理解并编写自己的 flash 布局。

Flashmap(FMAP):为什么 coreboot 需要它

Flashmap 是一种用于描述 flash 芯片内分区的二进制格式。它最初是为了满足 ChromiumOS 固件的需求而引入 coreboot 的,之后也被用于其他需要精确放置数据、或需要在运行时写入数据的场景——因为对这些场景而言,CBFS 被认为"过于脆弱":CBFS 以文件为单位组织内容,文件的位置与边界由构建工具动态决定,无法满足"某段数据必须位于某固定地址"这类硬性要求。

Flashmap 将镜像划分为清晰界定的小节(section),其中部分小节可以是 CBFS——能够容纳任意长度文件(至少有一个默认 CBFS,通常命名为COREBOOT)。工程上的通用准则是:

  • 有严格布局要求的内容(例如必须对齐到擦除块、或必须位于固定地址的数据),应拥有独立的 Flashmap 分区;
  • 其余一切内容,通常都应该放进 CBFS。

如今 coreboot 内部的 Flashmap 实现已成为事实上的标准实现。

FMAP 二进制格式:头部与分区描述

整个 Flashmap 由一个头部struct fmap和紧随其后的一组分区描述struct fmap_area组成,所有字段均为**小端(little endian)**格式。该结构在仓库中的精确定义位于 src/commonlib/bsd/include/commonlib/bsd/fmap_serialized.h。

头部(struct fmap)

字段类型/长度含义
signature8 字符固定为"__FMAP__"(十六进制0x5F5F464D41505F5F)
ver_major1 字节主版本号,目前仅为 1
ver_minor1 字节次版本号,当前值为 1
base64 位整数固件二进制镜像的地址
size32 位整数固件二进制镜像的大小(字节)
name32 字符固件二进制镜像的名称
nareas16 位整数紧随头部之后的分区(area)定义数量

源码中通过FMAP_STRLEN(32,含终止符)约束字符串长度,FMAP_VER_MAJOR/FMAP_VER_MINOR均为 1,与文档描述一致。

分区定义(struct fmap_area)

字段类型/长度含义
offset32 位整数分区起始位置,相对于头部中的base
size32 位整数分区大小(字节)
name32 字符分区描述性名称,在同一 Flashmap 内应唯一
flags16 位整数分区的属性标志(见下)

在 fmap_serialized.h 中,enum fmap_flags定义了四个标志位:

标志值说明
FMAP_AREA_STATIC1 << 0目前并未真正使用
FMAP_AREA_COMPRESSED1 << 1目前并未真正使用
FMAP_AREA_RO1 << 2目前并未真正使用
FMAP_AREA_PRESERVE1 << 3建议在固件更新时保留该分区,通常用于存放序列号、MAC 地址、校准数据或缓存数据等产品数据

也就是说,当前真正有实际语义的标志只有PRESERVE,其余三个标志在格式中预留但未投入使用。

FMAP 分区的存放位置与 COREBOOT 分区

FMAP section

整个 Flashmap(struct fmap头部 +struct fmap_area列表)应存放在一个独立的、名为FMAP的分区中,并且这个FMAP分区自身也要通过一个struct fmap_area被 Flashmap 描述出来。对于FMAP分区位于何处、多大,格式本身没有硬性限制,但由于通常需要在整个固件镜像上进行线性或二分查找来定位 Flashmap,因此选择一个对齐良好的地址会更有利。

在 coreboot 的 FMD 工具链中,FMAP分区名由常量SECTION_NAME_FMAP定义(见 util/cbfstool/cbfs_sections.h),并且fmaptool会强制要求描述文件中必须存在名为FMAP的分区,否则直接报错退出(FMAPTOOL_EXIT_MISSING_FMAP_SECTION)。

COREBOOT section

coreboot 固件镜像(coreboot.rom)应当至少有一个保留给 CBFS 的 Flashmap 分区,通常命名为COREBOOT。在工具层面,cbfs_sections.h中定义了主 CBFS 分区名SECTION_NAME_PRIMARY_CBFS = "COREBOOT",并规定必须存在一个标注了(CBFS)的COREBOOT分区(cbfs_sections_primary_cbfs_accounted_for()),否则fmaptool同样会拒绝生成(FMAPTOOL_EXIT_MISSING_PRIMARY_CBFS)。此外还预定义了COREBOOT_B(次 CBFS)、BOOTBLOCK、BOOTBLOCK_B等常用分区名约定。

Flashmap Descriptor(FMD):描述 flash 布局的纯文本语言

由于 coreboot 开始使用 Flashmap 的"分区"概念来描述 flash 芯片布局(无论是运行时还是刷写新镜像时),项目需要一个在源码树中表达能力足够强的纯文本格式来表示这些分区。于是产生了 Flashmap Descriptor(FMD)——位于 corebootutil/cbfstool目录下的一个语言及其编译器,可用它生成符合 Flashmap 格式的最终固件镜像(即coreboot.rom)。

FMD 的实现由三部分组成:

  • fmd.h:数据结构与解析前端 API;
  • fmd_scanner.l:Lex 词法规则(同时有生成的fmd_scanner.c_shipped);
  • fmd_parser.y:Yacc 语法规则(同时有生成的fmd_parser.c_shipped)。

文档中给出的非正式语法如下:

# <line comment> <image name>[@<memory-mapped address>] <image size> { <section name>[(flags)][@<offset from start of image>] [<section size>] [{ <subsection name>[@<offset from start of parent section>] [<subsection size>] [{ # Sections can be nested as deeply as desired <subsubsection name>[(flags)][@...] [...] [{...}] }] [<subsection name>[(flags)][@...] [...] [{...}]] # There can be many subsections at each level of nesting: they will be inserted # sequentially, and although gaps are allowed, any provided offsets are always # relative to the closest parent node's and must be strictly increasing with neither # overlapping nor degenerate-size sections. }] }

需要特别强调的是,上述示例中带有<.*>尖括号的部分属于元语法,在真实文件中并不存在;[.*]表示该部分可以整体省略。各参数的实际规则如下:

  • 名称:是无引号、无其他定界符的单个单词(不含空白)的字符串,由除@、{、}之外的所有符号组成;
  • 数值:非负整数,可用十进制或十六进制书写;两种写法后都可以(无空白地)追加K、M或G作为倍率后缀;
  • 注释:以#开始,直到本行行末的所有内容均为注释。

可选字段与标志的规则

[.*]表示文件中的某一部分可以整体省略,但"可选"并不意味着任何情况下都可省,是否可省取决于其他信息是否给出:

  • 标志(flags):为给定分区指定属性或类型。最重要的标志是CBFS,表示该分区将包含 CBFS 结构。在 fmd.h 中,union flashmap_flags定义了三种标志位:cbfs(包含 CBFS 区域)、preserve(更新前保留该分区)、ext(分区存在扩展区,仅可用于 CBFS 分区),对应解析器中的FLAG_CBFS、FLAG_PRESERVE、FLAG_EXT三个 token;
  • CBFS 标志只能放在叶子分区上:一旦给某分区添加了子分区,就不能再在其父分区上放置 CBFS 标志。这些标志只用于决定在哪里创建 CBFS 的空文件头,不会在最终 FMAP 分区中存储任何额外元数据(这一点在 fmd_parser.y 中体现为:CBFS 标志会触发fmd_process_flag_cbfs()回调来通知并校验客户端);
  • 在 fmd_parser.y 的parse_descriptor()中还有两条硬性约束:分区名不能以ECBFS_开头(该前缀被保留给 CBFS 扩展区),且非 CBFS 分区不能带ext标志。

整个文件的整体属性

除逐条语法外,FMD 语言还有以下整体性质:

  • 除字符串和数字内部外,空白一律被忽略;
  • 分区名必须全局唯一,但允许其中一个(非强制)与镜像名相同;
  • 以0开头的非零数字是语法错误——语言有意不引入八进制;
  • 镜像的内存映射地址(@后的值)只能出现在内存映射型芯片的布局文件中;
  • 所有分区偏移都仅相对于直接父节点,不存在从 flash 起始处的绝对偏移。因此只要改动不会导致位置和大小重叠或出现零大小,在某一嵌套层级内重排分区顺序是安全的;
  • 省略偏移的分区,默认从其父节点内尽可能低的位置开始(不考虑对齐);省略大小的分区,默认填充到下一个兄弟分区之前或父分区末尾;
  • 只要在兄弟分区和父分区大小的语境下位置和大小仍然无歧义,任意分区都可以省略偏移、大小或两者都省略。但注意:推断时不会利用某分区的子分区信息,也不会利用其祖父的兄弟分区信息;
  • 分区不要求必须有子分区,但整个 flash 芯片必须至少有一个分区;
  • 无子分区的分区可以省略花括号,但一旦写了花括号,里面就必须至少有一个子分区。

从 .fmd 到 .rom:fmaptool 与 FMDFILE 集成

fmaptool 命令行用法

将.fmd描述文件编译为 FMAP 二进制块的入口是 util/cbfstool/fmaptool.c。其完整用法如下:

fmaptool [-h <header output file>] [-R <region output file>] <fmd input file> <binary output file>

参数说明:

  • <fmd input file>:输入描述文件;传入-表示从标准输入读取;
  • <binary output file>:输出的 FMAP 二进制文件,必须为普通文件;
  • -h <header output file>:同时生成一个 C 头文件,其中定义FMAP_OFFSET(FMAP 分区的 flash 偏移,见 fmaptool.c);
  • -R <region output file>:同时生成一个文本文件,逐行列出包含 CBFS 的分区名(逗号分隔)。

成功运行时,fmaptool 会向标准错误输出工作摘要,并向标准输出打印所有包含 CBFS 的分区名(逗号分隔、主分区COREBOOT在前);若描述文件缺少FMAP分区或缺少带(CBFS)标注的主 CBFS 分区,工具会以非零退出码终止并打印FATAL错误。

生成的 C 头文件除了FMAP_OFFSET和FMAP_SIZE外,还会为描述树中的每个分区生成FMAP_SECTION_<NAME>_START(绝对 flash 偏移)与FMAP_SECTION_<NAME>_SIZE宏,以及一个FMAP_TERMINAL_SECTIONS字符串宏(列出所有无子分区的叶子分区名,见 fmaptool.c)。

构建系统中的 FMDFILE

在 coreboot 构建系统中,主板的 flash 布局文件通过FMDFILEKconfig 选项接入:fmaptool将$(FMDFILE)编译成 FMAP 段,再与 CBFS 一起打包进最终的coreboot.rom。例如 src/mainboard/emulation/qemu-q35/Kconfig 中会根据 vboot 的VBOOT_SLOTS_RW_A/VBOOT_SLOTS_RW_AB配置分别选用vboot-rwa-8M.fmd或vboot-rwab-8M.fmd。

真实布局示例解读

以 src/mainboard/51nb/x210/board.fmd 为例,该文件是一个手写 FMD,目的是在 BIOS 区域底部为 EC 预留空间:

FLASH 8M { BIOS@0x200000 0x600000 { EC@0x0 0x10000 RW_MRC_CACHE@0x10000 0x10000 SMMSTORE@0x20000 0x40000 CONSOLE@0x60000 0x20000 FMAP@0x80000 0x200 COREBOOT(CBFS) } }

逐行解读:

  • FLASH 8M:整个 flash 镜像名为FLASH、大小8M(8 MiB,即8 * 1024 * 1024字节,M倍率在此生效);
  • BIOS@0x200000 0x600000:一级分区BIOS,从 flash 起始偏移0x200000处开始、大小0x600000;此处的@偏移相对其父节点(flash 根);
  • 内层五个子分区均为叶子分区:EC(0x0,64KiB)、RW_MRC_CACHE(0x10000,64KiB)、SMMSTORE(0x20000,256KiB)、CONSOLE(0x60000,128KiB)、FMAP(0x80000,512B),所有@偏移都相对父分区BIOS的起始处;
  • COREBOOT(CBFS):主 CBFS 分区,省略了偏移和大小——它将自动填充到BIOS分区内剩余的全部空间,并带(CBFS)标志表明内部为 CBFS 结构。

注意FMAP@0x80000 0x200自身也被列入描述中:这正符合"FMAP 分区也必须是 Flashmap 所描述分区之一"的要求。

运行时如何利用 FMAP:fmap.c 的查询 API 与缓存机制

FMAP 不只是构建期的布局工具,固件运行时同样依赖它来定位各个分区。核心实现位于 src/lib/fmap.c,对外提供的主要 API 包括:

  • fmap_locate_area(name, &region):按分区名查找,返回其在 flash 中的偏移与大小(fmap.c);
  • fmap_locate_area_as_rdev()/fmap_locate_area_as_rdev_rw():以只读/读写region_device形式返回分区;
  • fmap_find_region_name():反向操作,给定区域查其名称;
  • fmap_read_area()/fmap_overwrite_area():直接对命名分区做整段读取或"擦除+重写"。

这些 API 的底层流程值得注意:find_fmap_directory()会优先使用 FMAP 缓存(pre-RAM 阶段将 flash 上的 FMAP 拷贝进_fmap_cache区域,见 fmap.c),并且verify_fmap()会校验"__FMAP__"签名;若开启了CBFS_VERIFICATION,在初始阶段还会对 FMAP 做元数据哈希校验,失败则直接die(),以防止 TOCTOU 攻击。ramstage 之后,FMAP 还会被复制进 CBMEM(CBMEM_ID_FMAP)并通过CBMEM_READY_HOOK注册缓存,供后续阶段乃至 OS 使用。分区的名称匹配与偏移换算都是按小端序从二进制区逐一扫描完成的,与 fmap_serialized.h 中的结构定义严格对应。

正是通过这套"构建期 fmaptool 生成 + 运行期 fmap.c 查询"的组合,coreboot 才能可靠地把 MRC 缓存、SMMSTORE、串口日志(CONSOLE)等需要运行时读写的区域精确安置在 flash 的固定位置,同时让普通文件继续使用灵活、动态的 CBFS。

编写 FMD 的常见约束小结

综合文档语法与工具源码的校验逻辑,编写自己的.fmd布局文件时需要特别注意:

  1. 描述树中必须有名为FMAP的分区,且必须有一个标注(CBFS)的分区名为COREBOOT(cbfs_sections.h);
  2. CBFS、PRESERVE标志只能写在叶子分区上;非 CBFS 分区不能带ext标志;分区名不得以ECBFS_开头;
  3. 分区名长度不能超过FMAP_STRLEN - 1(31 字符),镜像名同理,否则fmap_from_fmd()会拒绝生成(见 fmap_from_fmd.c);
  4. 非零数字不能以0开头(避免八进制歧义);大小与偏移可用十进制/十六进制 +K/M/G后缀;
  5. 同一层级内的分区,其偏移(无论显式还是推断)必须严格递增且不得重叠、不得为零大小;省略大小即"填充剩余空间",省略偏移即"尽可能靠前";
  6. 只有内存映射型芯片的布局才应在镜像名后写@<地址>。

遵循这些规则,你就可以为任意主板编写一份可编译、可被fmaptool与运行时代码共同消费的 flash 布局描述,精确掌控固件镜像中每一字节的去向。

  • 固件
  • 嵌入式
  • 操作系统
  • 驱动开发

【免费下载链接】coreboot

Read-only mirror of https://review.coreboot.org/coreboot.git. Synced every hour. We don't handle Pull Requests.

项目地址:https://gitcode.com/gh_mirrors/co/coreboot
点击查看免费下载
上一篇:GIST-Embedding-v0-openmind未来路线图:即将推出的新功能与改进
下一篇:Webpack Bundle Analyzer 常见问题解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询