BIOS开发笔记 17 – UEFI Capsule Update 是如何被触发的?
平常我们在测试 WU 的时候,都是将 BIOS 做成一个驱动包的形式,像驱动一样安装后,重启便能进行 BIOS 升级。
UEFI Capsule Update 机制介绍
1. 什么是 Capsule Update?
UEFI Capsule Update 是 UEFI 规范定义的一种标准机制,允许操作系统(OS)向固件传递数据块(称为 "Capsule"或胶囊)。最常见的用途是固件更新,它可以是一个 UEFI 固件,也可以是触摸板固件。
它解决了传统固件更新需要运行特定工具的痛点,使得固件更新可以像驱动更新一样在 OS 中无缝完成。
2. 核心流程
整个过程跨越了 OS 运行期和固件启动期(重启后)。
第一阶段:OS 发起更新 (Runtime)
- 准备胶囊: OS(如 Windows Update)将固件更新包(Capsule)加载到内存中。
- 调用服务: OS 调用 UEFI Runtime Service UpdateCapsule。
- 设置标志: UpdateCapsule 函数会检查标志位。如果包含 CAPSULE_FLAGS_PERSIST_ACROSS_RESET(需要在重启后保留),它会将胶囊的内存地址列表(ScatterGatherList)保存到 UEFI 变量 CapsuleUpdateData 中。
- 重启系统: 如果需要,调用 EfiResetSystem 重启计算机。
第二阶段:固件处理更新 (Pre-Boot)
- 检测胶囊: 系统重启后,PEI (Pre-EFI Initialization) 阶段会检测内存中是否存在胶囊,或者检查 CapsuleUpdateData 变量。
- 处理胶囊: DXE (Driver Execution Environment) 阶段会处理这些胶囊。
- 分发更新:
- 固件根据胶囊中的 ImageTypeId (GUID),找到对应的 FMP 驱动(例如 BIOS 驱动、EC 驱动)。
- 调用 FMP 协议的 SetImage 函数将新固件写入闪存。
3. Capsule 文件结构
Capsule 文件本质上是一个带有特定文件头的二进制文件。对于现代固件更新,通常使用 FMP Capsule 格式。
其结构层级如下:
A. 通用胶囊头 (EFI_CAPSULE_HEADER)
所有胶囊文件都以这个头开始。定义在 UefiSpec.h 中。
typedef struct { EFI_GUID CapsuleGuid; // 胶囊类型 GUID (FMP 胶囊通常使用 gEfiFmpCapsuleGuid) UINT32 HeaderSize; // 头大小 UINT32 Flags; // 标志位 (如 PERSIST_ACROSS_RESET, POPULATE_SYSTEM_TABLE) UINT32 CapsuleImageSize; // 整个胶囊文件的大小 } EFI_CAPSULE_HEADER;
B. FMP 胶囊头 (EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER)
如果 CapsuleGuid 是 gEfiFmpCapsuleGuid,则紧随其后的是 FMP 头。定义在 Guid/FmpCapsule.h 中。
typedef struct { UINT32 Version; // 版本 (通常为 1) UINT16 EmbeddedDriverCount; // 嵌入驱动数量 (通常为 0) UINT16 PayloadItemCount; // 负载项目数量 (通常为 1) // UINT64 ItemOffsetList[]; // 偏移量列表,指向具体的 Image Header } EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER;
C. 镜像头 (EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER)
根据偏移量列表找到的具体镜像头。
typedef struct { UINT32 Version; // 版本 EFI_GUID UpdateImageTypeId; // 目标固件的 GUID (重要!用于匹配 ESRT 中的 FwClass) UINT8 UpdateImageIndex; // 索引 UINT8 reserved_bytes[3]; UINT32 UpdateImageSize; // 固件二进制大小 UINT32 UpdateVendorCodeSize; // 厂商代码大小 UINT64 UpdateHardwareInstance; // 硬件实例 ID } EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER;
D. 固件负载 (Payload)
最后是实际的固件二进制数据(如 .bin 或 .rom 文件内容)。
E. 结构示意图

4. Capsule on Disk (文件传输方式)
除了通过内存传递胶囊 (UpdateCapsule),UEFI 还支持通过文件系统传递胶囊,称为 Capsule on Disk。这种方式常用于不支持 Runtime UpdateCapsule 的系统,或者作为一种更简单的更新手段。
核心机制
关键变量与标志
要启用 Capsule on Disk,OS 需要操作以下 UEFI 变量:
-
变量名: OsIndications -
GUID: gEfiGlobalVariableGuid (Standard Global Variable) -
设置值: 需要设置第 2 位 (Bit 2)。
// UefiSpec.h #define EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED 0x0000000000000004
操作步骤:
- OS 读取 OsIndicationsSupported 变量,检查 Bit 2 是否为 1 (确认 BIOS 支持此功能)。
- OS 读取当前的 OsIndications 变量。
- OS 将 EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED (0x04) OR 到当前值中。
- OS 调用 SetVariable 写回 OsIndications。
- OS 将胶囊文件放入 EFIUpdateCapsule。
- OS 重启系统 (EfiResetWarm 或 EfiResetCold)。
手动触发 UEFI Capsule Update
手动触发 Capsule Update,首先要将固件打包成符合 Capsule 标准的文件,即为它加上上述的 Header。EDK2 的代码中,有提供相关的脚本工具,可以用于 Capsule 文件的生成。工具是一个 Python 脚本,位于:
edk2BaseToolsSourcePythonCapsuleGenerateCapsule.py
此脚本是一个通用的 Capsule 文件生成工具,它可以生成 E. 结构示意图中的结构文件。值得注意的是,它会强制加入 FMP Payload Header,像有些 BIOS (如 AMI BIOS)不会校验该区域,会导致数据解析出错,以至无法进行 Capsule update。可以通过修改如下代码去除该 Header:
FmpPayloadHeader.FwVersion = SinglePayloadDescriptor.FwVersion FmpPayloadHeader.LowestSupportedVersion = SinglePayloadDescriptor.LowestSupportedVersion FmpPayloadHeader.Payload = SinglePayloadDescriptor.Payload # 注释此行 Result = FmpPayloadHeader.Encode ()
打开命令行窗口,执行:
cd BaseToolsSourcePythonCapsule set PYTHONPATH=x:edk2BaseToolsSourcePython python.exe GenerateCapsule.py -e BIOS.rom --guid a0327fe0-1fda-4e5b-905d-b510c45a61d0 -o BIOS.cap --fw-version 1 --lsv 1
--fw-version 1 --lsv 1 是属于fmp payload header 的,根据实际情况填写,如果是去掉了,则可随意。--capflag 参数也可以加上,更新完会自动重启,如果是 In-Memory 的还要加上 Persist 参数,以标记重启保留 Capsule 区域内存。注意 Guid 是 ESRT GUID。执行成功后,Capsule 文件便生成了。

Windows 下触发
Windows 下只能使用 On-Disk 的方式,In-Memory 使用调用 UEFI 的 UpdateCapsule 运行时服务,需要特别权限,所以只能使用 On-Disk。可以自己使用 DISKGENIUS 磁盘工具,将 Capsule 文件放至 ESP 分区的 EFIUpdateCapsule 目录,再利用 PowerShell 命令设置 UEFI Variable:
Install-Module -Name UEFIv2 -Scope CurrentUser Import-Module UEFIv2 Set-UEFIVariable -VariableName OsIndications -Namespace "{8BE4DF61-93CA-11D2-AA0D-00E098032B8C}" -Value 0x04
EFI Shell 下触发
EFI Shell 下比较简单,因为 EDK2 的代码中,实现一个 Capsule App,直接用它触发更新,工具需要自己编译代码生成 EFI 文件。EDK2 CapsuleApp 路径:
edk2MdeModulePkgApplicationCapsuleApp
Capsule App 实现了 In-Memory 和 On-Disk 的方式:
CapsuleApp.efi BIOS.cap # In-Memory CapsuleApp.efi BIOS.cap -OD # On-Disk
可以查看当前 Cap 文件的 Header 信息:
CapsuleApp.efi -D BIOS.cap

版权声明:
作者:bin
链接:https://ay123.net/mystudy/bios/1920/
来源:爱影博客
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论