3. 启动固件与镜像格式解析

在 Ryzen Embedded 平台上,安全启动并不是“把固件烧进去就能跑”这么简单。上电后,片内 ROM 首先接管执行,它只信任一段经过 AMD 私钥签名的引导代码;这段代码再去验证后续的固件卷、PSP 目录表以及各级镜像。因此,理解启动固件与镜像格式,本质上是在理解“谁先被信任、信任如何逐级传递、每一级用什么结构描述自己”。本节围绕固件卷(Firmware Volume)、PSP 目录表(PSP Directory Table)、签名头(Signature Header)与镜像布局(Image Layout)四个核心对象展开,说明它们在启动链中的位置、字段含义与解析方法。

一、启动链中的信任起点与镜像层级

Ryzen Embedded 的启动流程可以粗略分为三个阶段:片内 ROM 阶段、PSP 阶段和 x86 阶段。片内 ROM 固化在芯片内部,不可修改,它只做一件事——从外部 SPI Flash 的固定偏移处读取 PSP 目录表,并用内置的 AMD 公钥验证其中的签名头。验证通过后,PSP(Platform Security Processor,平台安全处理器)才开始执行目录表指向的引导加载程序。PSP 随后负责验证并加载 x86 侧的固件卷,最终把控制权交给 BIOS/UEFI 固件。

这里的关键在于:信任不是一次性建立的,而是逐级传递的。ROM 信任 PSP 目录表,PSP 目录表信任其签名头,签名头保护目录项,目录项再指向具体的固件卷和镜像。任何一级的签名验证失败,启动都会在对应阶段停止,而不是“跳过继续”。

Ryzen Embedded 启动镜像层级与信任传递 SPI Flash 物理布局(示例偏移,单位 KB) 0x00000 PSP 目录表 0x40000 PSP 引导加载程序 0x80000 固件卷 FV_MAIN 0x100000 固件卷 FV_RECOVERY 0x180000 签名头 + 目录项 信任链:片内 ROM → 验证 PSP 目录表签名头 → PSP 执行 → 验证固件卷签名 → x86 固件执行 每一级只信任下一级的签名,签名失败即停止启动,不降级、不跳过 PSP 目录表结构(简化) Signature Header(RSA-4096 + SHA-384) Directory Header(版本、条目数、总长度) Entry[0]:PSP 引导加载程序(偏移/长度/类型) Entry[1]:固件卷 FV_MAIN(偏移/长度/类型) 签名头关键字段 Magic:0x50535024("PSP$") Signature Type:RSA-4096 / SHA-384 Public Key:AMD 公钥或 OEM 公钥 Signature:对目录表内容的签名值 Checksum:目录表完整性校验

二、固件卷:把多个镜像打包成一个可验证单元

固件卷(Firmware Volume,FV)是 UEFI/PI 规范中的概念,在 Ryzen Embedded 上它被 PSP 用作“可验证的镜像容器”。一个固件卷内部可以包含多个文件(File),每个文件又由若干节(Section)组成。PSP 在验证时,并不逐个验证文件,而是验证整个固件卷的签名头,验证通过后再按卷内偏移解析文件。

固件卷的头部通常包含以下字段:卷头长度、卷长度、文件系统 GUID、卷签名、以及一个可选的扩展头。卷内文件通过 FFS(Firmware File System)组织,每个文件头包含文件名 GUID、类型、属性、大小和校验和。常见的文件类型包括:SEC(安全核心)、PEI(预 EFI 初始化)、DXE(驱动执行环境)、RAW(原始数据)等。

在实际工程中,我们通常用 UEFITool 或 AMD 提供的固件工具来查看固件卷布局。例如,一个典型的 FV_MAIN 卷可能包含:SEC 文件(约 32 KB)、PEI 文件(约 128 KB)、DXE 文件(约 512 KB)、以及若干 RAW 文件。这些文件在卷内按 8 字节对齐排列,每个文件头前有 4 字节对齐填充。

三、PSP 目录表:启动镜像的“总索引”

PSP 目录表是 Ryzen Embedded 启动流程中最关键的结构之一。它位于 SPI Flash 的固定偏移处(通常为 0x00000 或 0x20000,具体取决于平台配置),由片内 ROM 直接读取。目录表由签名头、目录头和若干目录项组成。

签名头是目录表的“信任锚”。它包含魔数、签名类型、公钥和签名值。片内 ROM 使用内置的 AMD 公钥验证签名头中的公钥,再用该公钥验证目录表内容的签名。如果验证通过,ROM 就认为目录表是可信的,并继续解析目录项。

目录头包含版本号、目录项数量、目录表总长度等信息。每个目录项描述一个启动镜像,字段包括:镜像类型(如 PSP 引导加载程序、固件卷、恢复镜像等)、镜像在 Flash 中的偏移、镜像长度、以及可选的加载地址。PSP 根据目录项逐个加载并验证镜像,验证顺序通常由目录项的顺序决定。

下面是一个简化的目录项示例(以 C 结构体形式表示,仅用于说明字段关系):

typedef struct {
    uint32_t type;        // 镜像类型:0x01=PSP BL, 0x02=FV, 0x03=RECOVERY
    uint32_t offset;      // 在 SPI Flash 中的偏移(字节)
    uint32_t size;        // 镜像长度(字节)
    uint32_t load_addr;   // 加载到 PSP SRAM 的目标地址
    uint8_t  hash[48];    // SHA-384 哈希值
} PSP_DIRECTORY_ENTRY;

需要注意的是,目录项中的哈希值是对镜像内容计算的,PSP 在加载镜像后会重新计算哈希并与目录项中的值比对。如果比对失败,启动停止。因此,目录表不仅是索引,也是完整性校验的基准。

四、镜像布局与对齐规则

Ryzen Embedded 对镜像布局有明确的对齐要求。PSP 目录表通常要求 4 KB 对齐,固件卷要求 64 KB 对齐,签名头要求 4 KB 对齐。这些对齐规则不是随意的,而是与 SPI Flash 的擦除块大小和 PSP 的读取粒度有关。如果镜像偏移未对齐,PSP 可能无法正确读取,导致启动失败。

一个典型的镜像布局如下:0x00000 处是 PSP 目录表(含签名头),0x40000 处是 PSP 引导加载程序,0x80000 处是 FV_MAIN,0x100000 处是 FV_RECOVERY,0x180000 处是签名头和目录项的备份副本。备份副本用于主目录表损坏时的恢复,但恢复过程仍然需要签名验证通过。

在实际烧录时,我们通常使用 AMD 提供的 PSPTool 或固件打包工具来生成符合布局要求的镜像。工具会按照目录表、引导加载程序、固件卷的顺序依次排列,并在每个镜像前插入签名头和必要的对齐填充。烧录后,可以通过读取 SPI Flash 的前 4 KB 来确认目录表魔数是否为 0x50535024,以此判断镜像是否被正确写入。

实践提示:如果你在调试启动失败,可以先用编程器读取 SPI Flash 的前 256 KB,检查 0x00000 处是否有有效的 PSP 目录表魔数,再检查 0x40000 处是否有 PSP 引导加载程序的签名头。这两个位置是启动链的第一道和第二道关卡,优先排查它们往往能快速定位问题。

五、解析工具与验证方法

解析 Ryzen Embedded 启动固件时,常用的工具包括:UEFITool(查看固件卷内部结构)、PSPTool(解析 PSP 目录表和签名头)、以及 AMD 提供的 Firmware Analysis Toolkit。PSPTool 可以直接读取 SPI Flash 镜像,列出所有目录项、签名头字段和固件卷偏移,是排查启动问题的首选工具。

验证方法上,可以分三步走:第一步,用 PSPTool 确认目录表签名头有效,公钥与平台匹配;第二步,确认目录项指向的镜像偏移和长度与实际 Flash 内容一致;第三步,用 UEFITool 打开固件卷,确认卷内文件完整、无缺失或损坏。如果三步都通过,启动链的静态部分就是完整的,剩下的问题通常出在运行时配置或硬件连接上。

最后需要强调的是,镜像格式解析不是一次性的工作。每次更新固件、更换 SPI Flash 或调整启动配置后,都应该重新检查目录表和固件卷的布局。只有把“信任链的每一级都验证过”变成习惯,安全启动才能真正成为可依赖的机制,而不是一个“看起来配了但不知道有没有生效”的开关。