UEFI 之 Capsule Update (固件更新)

概要

什么是UEFI Capsule Update呢?Capsule,顾名思义,是“胶囊”的意思,所以UEFI Capsule Update可以理解为胶囊式固件更新。
UEFI规范定义了Firmware Management Protocol(FMP)、capsule格式以及EFI System Resource Table (ESRT) 用于支持系统和设备固件更新。使用FMP协议可以获取固件信息并且把ESRT报告给OS。
出于安全性考虑,Flash设备在系统启动时是锁住的,所以在操作系统中直接更新Flash是行不通的。

如下图所示,一个可行的办法是操作系统把待更新的固件信息发送至系统固件,在下一次启动过程中,系统在Flash设备上锁前更新固件。这个待更新的固件就被称作“capsule”。需要注意的是,capsule不仅仅用来做固件更新,还用于OS传递信息至固件。UEFI规范定义了一些capsule services,比如,操作系统调用UpdateCapsule()传递capsule image至固件,固件根据capsule flag决定立即处理capsule还是重启后再处理。总结一下,胶囊式固件更新是在编译阶段把到更新的固件封装成capsule,由OS传递capsule image至系统固件,然后由系统固件来处置待更新的capsule。

当操作系统加载程序调用 UpdateCapsule 函数时,将执行 CapsuleHeaderArray中包含的每个胶囊。 胶囊执行的顺序取决于 UEFI 固件实现,并且胶囊无法对其相对于其他马蹄形执行的顺序进行任何假设,或对其他马蹄形执行任何依赖项。 每个胶囊都是一个自包含负载,同时包含可执行的 UEFI 代码来管理更新和固件映像。
调用胶囊后,胶囊中包含的可执行代码负责打开与目标设备的通信通道。 适当的通道将取决于系统的设备拓扑、目标设备的功能,以及特定 UEFI 实现提供的 UEFI 启动服务和驱动程序。 胶囊实现人员可能需要咨询 UEFI BIOS 供应商,了解目标 UEFI 环境中的可用选项。 通常,通过利用给定设备的 UEFI 设备驱动程序建立通信。 使用此驱动程序,胶囊更新代码可以通过已知的设备路径(使用适当的协议)绑定到设备。
建立通信后,更新管理代码会将固件映像写入目标设备。 完成更新后,相应的返回状态代码将写入到 ESRT 中设备的固件资源条目。 然后,更新管理代码将控制权返回给 UpdateCapsule 函数。
有关 UpdateCapsule 函数、胶囊结构和 UEFI 引导服务驱动程序和协议的详细信息,请参阅uefi 规范。

系统管理模式( System Management Mode,SMM)

原理

固件更新文件较大时,升级时需要申请一段连续内存用以存放升级镜像文件。 为了避免未能申请到所需内存导致升级失败的情况出现,在更新过程中可按需把 Capsule 分成更小的 SubCapsule 数据单元。 每个 SubCapsule 和 Capsule 的结构相 同, 以SequenceNumber标识指示顺序。 InstanceId标 识 分割后 的 Capsule 块, 为 方 便 升 级 过 程 按 顺 序 组 合Capsule 块并写入 Flash。

Capsule 卷的组织结构和标准固件卷文件相同。Capsule 卷中可存储固件文件、EFI 程序、BIOS 配置信息等数据

UEFI 规范中基于 Capsule 的数据传输机制,需要经 过 数 据 结 构 设 计、 数 据 封 装 和 数 据 解 析 3 个
阶段。
(1) Capsule 数据结构设计:定义用于操作系统与固件系统之间的数据传输接口。
(2) 数据封装:把需要传输到固件系统的数据进行才 Capsule 包装,并按需分配内存。
(3) 数据解析:固件系统在 PEI,DXE,BDS 阶段对 Capsule 包 进 行 解 析, 提 取 其 中 的 固 件 文 件 和数据。

过程描述:

  • 基于 UEFI 的 Capsule 式固件更新方法在编译阶段把待更新的数据封装成 Capsule,在固件系统启动过程解析 Capsule,并把解析内容写入内存指定地址的 Flash,从而使更新、升级数据传递至固件系统,实现对指定固件卷的更新。

  • 采用 Capsule 的固件更新方法主要包括数据传输和数据更新过程。 数据传输阶段运行在操作系 统 环 境 或 UEFI Shell, 把 更 新 数 据 封 装成Capsule 并传输至固件系统。 数据更新阶段解析更新的Capsule, 为 Capsule 分配内存,把更新内容写入指定位置。

  • 更新时先通过 Capsule 头结构找到 Capsule 的固件卷位置,根据系统当前内存的空闲情况判断是否需要对 Capsule 进行分块。 在 Capsule 所需的连续空间内存未能满足时,则需 要 把 Capsule 划 分 为 更 小 的SubCapsule 数据单元,并为每 个 SubCapsule 分 配 内存, 修 改 EFI _ CAPSULE _ HEADER结 构 体 的SequenceNumber 成 员, 使 其 记 录 下 一 个 相 邻 的SubCapsule 位置。 当所需内存申请成功时,则不需要对 Capsule 进行再分块,直接把 SequenceNumber 值设为 0。 Capsule 分块为 SubCapsule 的过程如图 所示。

  • 分块过程需为每个 SubCapsule 创建包括数据起始地址、数据长度、校验和的描述信息链表。 把第一个 SubCapsule 描述符的地址存储到 CMOS 中的变量 参 数 表 或 UEFI 端 口 映 射 表 的 LocationOfThePointer 指定的内存位置。 升级、刷新过程在计算机的 S3 睡眠状态进行,更新完成后通过实时时钟( Real-time Clock,RTC) 唤醒。

  • 采用 Capsule 的固件定制式更新在数据更新阶段解析、提取 Capsule 中封装的数据,根据设定对固件系统进行定制更新。 数据更新工作主要在 PEI,DXE 阶段进行。

  • PEI 阶 段 用 PPI 识 别 并 恢 复 Capsule。 PPI 是UEFI 定义的用于 PEI 阶段模块( PEIM) 之间传递函数和数据的接口。 PEIM 将需要提供给其他 PEIM的数据和函数封装在 PEI 服务中,PEIM 通过 GUID找到所需模块,获得需要的数据。 _EFI_PEI_CAPSULE_PPI 的结构体原型定义如下:

struct _EFI_PEI_CAPSULE_PPI {
  EFI_PEI_CAPSULE_COALESCE              Coalesce;
  EFI_PEI_CAPSULE_CHECK_CAPSULE_UPDATE  CheckCapsuleUpdate;
  EFI_PEI_CAPSULE_CREATE_STATE          CreateState;
};
  • PEI 阶段利用 PPI 的CheckCapsuleUpdate 函数查找 CMOS(flash芯片,这里是缓存芯片) 中 是 否 有 Capsule, 有 Capsule 时 调 用Coalesce函 数 将 SubCapsule 按 顺 序 合 并, 再 通 过CreateState函数把合并后的 Capsule 在内存中的位置和大小写入 HOB 链表,由 HOB 把数据从 PEI 阶段传递至 DXE 阶段。

  • DXE 阶 段 从 HOB 链 表 搜 索 Capsule 相 关 的HOB,调用服务 ProcessFirmwareVolume 从 HOB 链表中得到 Capsule 在内存中的地址、大小等参数,分离出固件卷,并为固件卷创建 Protocol,方便对其进行读写操作。 分离出的固件卷需根据校验和判断其有效性,只对有效的固件卷实施更新操作。

实现过程

1、操作系统模块调用Capsule Runtime服务EFI_RUNTIME_SERVICES.UpdateCapsule()传递带有reset属性的Capsule image至系统固件,这时系统更需要做一次重启。需要注意的是,此时的重启并非真正的重启,而是走了S3的路径,主要是为了利用S3睡眠状态memory不丢失,这样capsule image就可以一直保存在memory中。
原型位置./MdePkg/Library/UefiRuntimeLib/RuntimeLib.c:728

需要说明的是
UpdateCapsule接口有一个参数是这个结构体EFI_CAPSULE_HEADER,有一个成员是Flags,主要决定对capsule包的处理情况;
取值如下:

PersistAcrossReset,
#define CAPSULE_FLAGS_PERSIST_ACROSS_RESET 0x00010000
PopulateSystemTable,
#define CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE 0x00020000
InitiateReset 
#define CAPSULE_FLAGS_INITIATE_RESET 0x00040000

如果Capsule中设置了CAPSULE_FLAGS_PERSIST_ACROSS_RESETFlags,则固件将在系统重置后处理胶囊。调用方必须确保使用从QueryCapsuleCapabilities获得的必需的重置值来重置系统。如果未设置此Flags,则固件将立即处理胶囊。
设置CAPSULE_FLAGS_POPULATE_SYSTEM_TABLEFlag的Capsule必须设置CAPSULE_FLAGS_PERSIST_ACROSS_RESET;因为Flag中设置了CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE,封装的固件将把ScatterGatherList(分散的)中封装的内容合并到一个连续的缓冲区中,然后在重置系统后必须将一个指向此合并封装的指针放在EFI系统表中。搜索此capsule将在EFI_CONFIGURATION_TABLE中查找并搜索胶囊的GUID和关联的指针,以在重置后检索数据。

Flags 组合
a、不定义Flags:固件尝试立即处理或启动胶囊。如果无法识别胶囊,可能会出现错误。
b、CAPSULE_FLAGS_PERSIST_ACROSS_RESET:固件将尝试在重置期间处理或启动胶囊。如果无法识别胶囊,可能会出现错误。如果该处理需要平台不支持的重置,请期待一个错误。
c、CAPSULE_FLAGS_PERSIST_ACROSS_RESET +CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE:固件会将胶囊从ScatterGatherList合并到一个连续的缓冲区中,并在EFI系统表中放置一个指向合并胶囊的指针。不需要平台识别胶囊类型。如果该操作需要平台不支持的重置,则可能会出现错误。
d、CAPSULE_FLAGS_PERSIST_ACROSS_RESET +CAPSULE_FLAGS_INITIATE_RESET:固件将尝试在重置期间处理或启动胶囊。固件将启动与传入的胶囊请求兼容的重置,并且不会返回给调用者。如果未识别出胶囊,则可能会出现错误。如果该处理需要平台不支持的重置,请期待一个错误。
e、CAPSULE_FLAGS_PERSIST_ACROSS_RESET +CAPSULE_FLAGS_INITIATE_RESET +CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE:固件将启动与传入的胶囊请求兼容的重置,并且不会返回给调用者。重置后,固件会将胶囊从ScatterGatherList合并到一个连续的缓冲区中,并在EFI系统表中放置一个指向合并的胶囊的指针。不需要平台识别胶囊类型。如果操作需要平台不支持的重置,则可能会出现错误。

2、重启时,平台PEI模块调用PEI_CAPSULE_PPI.CheckCapsuleUpdate()判断是否有Capsule固件待处理(是否处于胶囊启动更新模式)。CapsulePei模块读取CapsuleUpdateDate Variable检测是否有capsule在memory中,如果条件满足,PEI模块将会把启动模式设置为BOOT_ON_FLASH_UPDATE
原型位置./MdeModulePkg/Universal/CapsulePei/UefiCapsule.c:1123:CheckCapsuleUpdate

3、 Memory初始化完成后,PEI module调用PEI_CAPSULE_PPI.CapsuleCoalesce()判断一系列Capsule片段的当前位置,把它们合并成一个连续的内存区域,把合并后的capsule拷贝至PEI memory中,并且 通过CreateState创建EFI_HOB_UEFI_CAPUSLE.
原型位置: ./MdeModulePkg/Universal/CapsulePei/UefiCapsule.c:941
说明:
操作系统为capsule image分配的是连续的虚拟地址空间,而实际上则是一系列不连续的物理地址区域片段。OS会把虚拟地址以及物理地址一并传递到固件。这样固件可以立即处理或者在重启后处理capsule。如下图所示,左边是OS的角度看到的存放capsule的连续虚拟地址空间,中间的图表示capsule在系统内存中实际分布。最右边是传递至固件的Capsule block descriptor scatter gather list。Capsule Coalesce会遍历这个list,把所有capsule片段组合在一起放在连续的系统内存中。

4、进入BDS阶段后,BDS判断当前启动模式如果为BOOT_ON_FLASH_UPDATE,则调用CapsuleLib:ProcessCapsules()处理Capsule image。第一次调用需在EFI_END_OF_DXE_EVENT之前,因为系统flash设备会在EFI_END_OF_DXE_EVENT之后锁住。
原型位置:MdeModulePkg/Library/DxeCapsuleLibFmp/DxeCapsuleProcessLib.c:50 0

5、ProcessCapsules()通过CAPSULE_HOB获取满足条件的capsule images,如果capsule image是UEFI FMP(Firmware management protocol)格式的capsule,则调用ProcessFmpCapsuleImage()
原型位置:MdeModulePkg/Library/DxeCapsuleLibFmp/DxeCapsuleLib.c:1304

6、ProcessFmpCapsuleImage()通过DumpAllFmpInfo ();调用FMP协议接口 Fmp->GetImageInfo()获取固件信息并且和FMP capsule对比。只有满足了一系列验证条件后,比如签名验证,ProcessFmpCapsuleImage()才会通过SetFmpImageData调用Fmp->SetImage()执行固件更新。更新成功后,需设置RecordFmpCapsuleStatus避免同一个capsule image下次仍然被执行。

补充:
FMP(Firmware Management Protocol)包,结构如下,

  • 系统固件更新配置文件(.ini)
    第一部分是head,NumOfUpdate必须设置为组件个数,UpdateX是组件名称
[Head]
NumOfUpdate = 1
Update0 = MyPlatformFvMain

[MyPlatformFvMain]
FirmwareType = 0            # SystemFirmware
AddressType  = 0             # 0 - relative address, 1 - absolute address.
BaseAddress  = 0x00500000    # Base address offset on flash
Length       = 0x001E0000    # Length
ImageOffset  = 0x00500000    # Image offset of this SystemFirmware image
FileGuid     = 26961C31-66DC-48A5-891C-25438BDE1430  # PcdEdkiiSystemFirmwareFileGuid

FirmwareType: 0 - SystemFirmware, 1 - NvRam.
AddressType: 0 - relative address, 1 - absolute address.
BaseAddress: Base address offset of non-volatile storage device in hexidecimal (e.g. 0x00400000)
Length: Image Length in hexidecimal (e.g. 0x00001000)
ImageOffset: Image offset of data in FileGuid FFS file in hexidecimal (e.g. 0x00400000)
FileGuid: Register format GUID of FFS File GUID in FmpPayload (e.g. 5151a274-2614-4ea7-c660-2dbe3ca2ccb3)
注:name可有有不同的FileGuid,只有FileGuid与PcdEdkiiSystemFirmwareFileGuid匹配才会使用,不匹配将被忽略;
配置文件SysFirmUpdateConfig.ini,可以参考QuarkPlatformPkg/Feature/Capsule/SystemFirmwareUpdateConfig/SystemFirmwareUpdateConfig.ini
Vlv2TbltDevicePkg/Feature/Capsule/SystemFirmwareUpdateConfig/SystemFirmwareUpdateConfig.ini
Vlv2TbltDevicePkg/Feature/Capsule/SystemFirmwareUpdateConfig/SystemFirmwareUpdateConfigGcc.ini
推荐使用:
<Your Platform Package>/Feature/Capsule/SystemFirmwareUpdateConfig

  • 配置的定义地方如下:SignedCapsulePkg/Include/Guid/EdkiiSystemFmpCapsule.h
  • system firmware update FMP driver包 (SysFirmUpdate.efi). 位置:SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareUpdateDxe.c
  • 更新到Flash需要平台库的支持,<Your Platform Package>/Feature/Capsule/Library/PlatformFlashAccessLib
    默认是SignedCapsulePkg/Library/PlatformFlashAccessLibNull,一个空接口,
    可以参考:
    QuarkPlatformPkg/Feature/Capsule/Library/PlatformFlashAccessLib/PlatformFlashAccessLibDxe.inf
    Vlv2TbltDevicePkg/Feature/Capsule/Library/PlatformFlashAccessLib
  • 系统固件描述PEIM
    该描述符提供当前系统固件版本信息。 此信息存储在PEIM FFS文件的RAW部分中。 PEIM负责读取RAW部分,并将名为gEfiSignedCapsulePkgTokenSpaceGuid.PcdEdkiiSystemFirmwareImageDescriptor的VOID * PCD设置为RAW部分的内容。 系统固件的固件管理协议使用此PCD来确定系统固件的当前版本。
    可以参考如下:
    QuarkPlatformPkg/Feature/Capsule/SystemFirmwareDescriptor
    Vlv2TbltDevicePkg/Feature/Capsule/SystemFirmwareDescriptor
    建议将系统固件描述符PEIM实现放置在平台包目录下:
    <Your Platform Package>/Feature/Capsule/SystemFirmwareDescriptor
    系统固件描述符信息在文件SystemFirmwareDescriptor.aslc中实现。版本和字符串信息 必须针对系统固件更新要求,IMAGE_TYPE_ID_GUID定义设置为自己新的GUID值,这个GUID值必须是平台支持Capsule Base System固件更新的GUID值,在DSC中的gEfiMdeModulePkgTokenSpaceGuid.PcdSystemFmpCapsuleImageTypeIdGuid有定义对应的GUID,多个可以使用数组的形式,这里我新生成了这个:5f17196b-7bc3-c816-7ea8-c837d33e0715

需要为自己平台更新SystemFirmwareDescriptor.aslc中的以下宏定义

#define PACKAGE_VERSION                     0xFFFFFFFF
#define PACKAGE_VERSION_STRING              L"<Package Version String>"

#define CURRENT_FIRMWARE_VERSION            0x00000002
#define CURRENT_FIRMWARE_VERSION_STRING     L"<Current firmware version string>"
#define LOWEST_SUPPORTED_FIRMWARE_VERSION   0x00000001

#define IMAGE_ID                            SIGNATURE_64('_', '_', '_', '_', '_', '_', '_', '_')
#define IMAGE_ID_STRING                     L"<Image ID string>"

#define IMAGE_TYPE_ID_GUID    {0xe698490d, 0xc1fa, 0x47fd, { 0x86, 0x3, 0x69, 0xcd, 0x67, 0x8c, 0x5c, 0x39} }



  • 详细文档查考这里
    https://github.com/tianocore/tianocore.github.io/wiki/Capsule-Based-Firmware-Update-and-Firmware-Recovery
    https://github.com/tianocore/tianocore.github.io/wiki/Capsule-Based-System-Firmware-Update-DSC-FDF

文件添加说明:

  • 平台 DSC [Defines]中添加
    [Defines]中添加CAPSULE_ENABLE 值为FALSE,在编译是添加-D CAPSULE_ENABLE即可打开
 DEFINE CAPSULE_ENABLE = FALSE
  • DSC [LibraryClasses]添加
[LibraryClasses]
  OpensslLib|CryptoPkg/Library/OpensslLib/OpensslLib.inf
  IntrinsicLib|CryptoPkg/Library/IntrinsicLib/IntrinsicLib.inf
  BaseCryptLib|CryptoPkg/Library/BaseCryptLib/BaseCryptLib.inf
!if $(CAPSULE_ENABLE)
  CapsuleLib|MdeModulePkg/Library/DxeCapsuleLibFmp/DxeCapsuleLib.inf
!else
  CapsuleLib|MdeModulePkg/Library/DxeCapsuleLibNull/DxeCapsuleLibNull.inf
!endif
  EdkiiSystemCapsuleLib|SignedCapsulePkg/Library/EdkiiSystemCapsuleLib/EdkiiSystemCapsuleLib.inf
  FmpAuthenticationLib|MdeModulePkg/Library/FmpAuthenticationLibNull/FmpAuthenticationLibNull.inf
  IniParsingLib|SignedCapsulePkg/Library/IniParsingLib/IniParsingLib.inf
  PlatformFlashAccessLib|<Your Platform Package>/Feature/Capsule/Library/PlatformFlashAccessLib/PlatformFlashAccessLibDxe.inf

在[LibraryClasses.common.DXE_RUNTIME_DRIVER]添加如下:

[LibraryClasses.common.DXE_RUNTIME_DRIVER]
!if $(CAPSULE_ENABLE)
  CapsuleLib|MdeModulePkg/Library/DxeCapsuleLibFmp/DxeRuntimeCapsuleLib.inf
!endif
  • 在 DSC中添加 [Pcds]
    将PCD PcdEdkiiSystemFirmwareImageDescriptor添加到[PcdsDynamicExDefault]部分,其值为{0x0},并且最大大小足以容纳EDKII_SYSTEM_FIRMWARE_IMAGE_DESCRIPTOR结构及其关联的Unicode字符串,该字符串在.aslc文件中实现。

将PCD PcdSystemFmpCapsuleImageTypeIdGuid添加到同一[PcdsDynamicExDefault]部分。此PCD是一个或多个IMAGE_TYPE_ID_GUID值的数组。在最简单的配置中,此PCD设置为此处描述的.aslc文件中的一个IMAGE_TYPE_ID_GUID值。 PCD值是一个16字节的数组。

PCD PcdEdkiiSystemFirmwareFileGuid添加到同一[PcdsDynamicExDefault]部分。此PCD设置为一个16字节数组的GUID值。 GUID值可以在此处所述的系统固件更新配置INI文件的FileGuid语句中设置为相同的GUID值。在最简单的配置中,此PCD在DSC文件中设置为INI文件中的FileGuid值。

注意:INI文件语法确实支持更复杂的基于胶囊的系统固件更新方案,其中胶囊可能包含多个FFS文件,这些文件的更新内容可以支持多个平台。对于此用例,平台模块必须检测当前平台并设置适当的PcdEdkiiSystemFirmwareFileGuid值。

说明:

下面的示例使用0x100字节的PcdEdkiiSystemFirmwareImageDescriptor大小

[PcdsDynamicExDefault.common.DEFAULT]
!if $(CAPSULE_ENABLE)
  gEfiSignedCapsulePkgTokenSpaceGuid.PcdEdkiiSystemFirmwareImageDescriptor|{0x0}|VOID*|0x100
  gEfiMdeModulePkgTokenSpaceGuid.PcdSystemFmpCapsuleImageTypeIdGuid|{0xc0, 0x20, 0xaf, 0x62, 0x16, 0x70, 0x4a, 0x42, 0x9b, 0xf8, 0x9c, 0xcc, 0x86, 0x58, 0x40, 0x90}
  gEfiSignedCapsulePkgTokenSpaceGuid.PcdEdkiiSystemFirmwareFileGuid|{0x31, 0x1c, 0x96, 0x20, 0xdc, 0x66, 0xa5, 0x48, 0x89, 0x1c, 0x25, 0x43, 0x8b, 0xde, 0x14, 0x30}
!endif
  • DSC [Components]

这里CPU架构是IA32的,根据自己平台进行添加

[Components.IA32]
!if $(CAPSULE_ENABLE)
  # FMP image descriptor
  <Your Platform Package>/Feature/Capsule/SystemFirmwareDescriptor/SystemFirmwareDescriptor.inf
!endif

在 BdsDxe.inf <LibraryClasses>中添加如下

[Components.X64]
  MdeModulePkg/Universal/BdsDxe/BdsDxe.inf {
    <LibraryClasses>
!if $(CAPSULE_ENABLE)
      FmpAuthenticationLib|SecurityPkg/Library/FmpAuthenticationLibPkcs7/FmpAuthenticationLibPkcs7.inf
!else
      FmpAuthenticationLib|MdeModulePkg/Library/FmpAuthenticationLibNull/FmpAuthenticationLibNull.inf
!endif
  }

!if $(CAPSULE_ENABLE)
  MdeModulePkg/Universal/EsrtDxe/EsrtDxe.inf

  SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareReportDxe.inf {
    <LibraryClasses>
      FmpAuthenticationLib|SecurityPkg/Library/FmpAuthenticationLibPkcs7/FmpAuthenticationLibPkcs7.inf
  }
  SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareUpdateDxe.inf {
    <LibraryClasses>
      FmpAuthenticationLib|SecurityPkg/Library/FmpAuthenticationLibPkcs7/FmpAuthenticationLibPkcs7.inf
  }

  MdeModulePkg/Application/CapsuleApp/CapsuleApp.inf {
    <LibraryClasses>
      PcdLib|MdePkg/Library/DxePcdLib/DxePcdLib.inf
  }
!endif
  • 在FDF文件的 PEI [FV]

将在较早的步骤中实现的SystemFirmwareDescriptor PEIM添加到包含其他PEIM的FV中,以便在每次引导时分派它

!if $(CAPSULE_ENABLE)
  # FMP image descriptor
INF RuleOverride = FMP_IMAGE_DESC <Your Platform Package>/Feature/Capsule/SystemFirmwareDescriptor/SystemFirmwareDescriptor.inf
!endif
  • FDF DXE [FV]
    在PEIMs之前添加
!if $(CAPSULE_ENABLE)
INF  MdeModulePkg/Universal/EsrtDxe/EsrtDxe.inf
INF  SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareReportDxe.inf

FILE FREEFORM = PCD(gEfiSignedCapsulePkgTokenSpaceGuid.PcdEdkiiPkcs7TestPublicKeyFileGuid) {
     SECTION RAW = BaseTools/Source/Python/Pkcs7Sign/TestRoot.cer
     SECTION UI = "Pkcs7TestRoot"
     }
!endif
  • FDF DXE [FV]

添加EsrtDxe, SystemFirmwareReportDxe, 和 test signing key file 到FV

!if $(CAPSULE_ENABLE)
INF  MdeModulePkg/Universal/EsrtDxe/EsrtDxe.inf
INF  SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareReportDxe.inf

FILE FREEFORM = PCD(gEfiSignedCapsulePkgTokenSpaceGuid.PcdEdkiiPkcs7TestPublicKeyFileGuid) {
     SECTION RAW = BaseTools/Source/Python/Pkcs7Sign/TestRoot.cer
     SECTION UI = "Pkcs7TestRoot"
     }
!endif
  • FDF [FV] 胶囊生成生成包
    将以下[FV]部分添加到平台FDF文件中,构建包含具有更新有效负载的FFS文件的FV。 这些FV被添加到FMP有效载荷中,该FMP有效载荷被包装到使用PKCS7证书签名的胶囊中。

带有#PcdEdkiiSystemFirmwareFileGuid注释的FILE 二进制语句必须使用配置文件INI文件中的FileGuid。 在最简单的配置中,INI文件中仅使用一个FileGuid GUID值。 如果一个INI文件描述了多个平台或板的更新,则可以使用多个FileGuid GUID值。 [FV]部分中必须提供每个FileGuid值的FILE RAW语句。

!if $(CAPSULE_ENABLE)
[FV.CapsuleDispatchFv]
FvAlignment        = 16
ERASE_POLARITY     = 1
MEMORY_MAPPED      = TRUE
STICKY_WRITE       = TRUE
LOCK_CAP           = TRUE
LOCK_STATUS        = TRUE
WRITE_DISABLED_CAP = TRUE
WRITE_ENABLED_CAP  = TRUE
WRITE_STATUS       = TRUE
WRITE_LOCK_CAP     = TRUE
WRITE_LOCK_STATUS  = TRUE
READ_DISABLED_CAP  = TRUE
READ_ENABLED_CAP   = TRUE
READ_STATUS        = TRUE
READ_LOCK_CAP      = TRUE
READ_LOCK_STATUS   = TRUE

INF  SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareUpdateDxe.inf

[FV.SystemFirmwareUpdateCargo]
FvAlignment        = 16
ERASE_POLARITY     = 1
MEMORY_MAPPED      = TRUE
STICKY_WRITE       = TRUE
LOCK_CAP           = TRUE
LOCK_STATUS        = TRUE
WRITE_DISABLED_CAP = TRUE
WRITE_ENABLED_CAP  = TRUE
WRITE_STATUS       = TRUE
WRITE_LOCK_CAP     = TRUE
WRITE_LOCK_STATUS  = TRUE
READ_DISABLED_CAP  = TRUE
READ_ENABLED_CAP   = TRUE
READ_STATUS        = TRUE
READ_LOCK_CAP      = TRUE
READ_LOCK_STATUS   = TRUE

FILE RAW = 26961C31-66DC-48A5-891C-25438BDE1430 { # PcdEdkiiSystemFirmwareFileGuid
    FD = <Your Platform FD Name>
  }

FILE RAW = ce57b167-b0e4-41e8-a897-5f4feb781d40 { # gEdkiiSystemFmpCapsuleDriverFvFileGuid
    FV = CapsuleDispatchFv
  }

FILE RAW = 812136D3-4D3A-433A-9418-29BB9BF78F6E { # gEdkiiSystemFmpCapsuleConfigFileGuid
    <Your Platform Package>/Feature/Capsule/SystemFirmwareUpdateConfig/SystemFirmwareUpdateConfig.ini
  }
!endif

说明:
1、.ini文件是要放入Cap包中的,在更新的时候通过验证后会使用ExtractConfigImage接口获取ini;
2、可以不使用ini文件,在FDF文件不添加,在更新的时候找不到ini文件,会默认将FD从0地址进行更新;
3、.ini文件内容其实与编译过程没有多大关系,就是在升级过程中进行参考的,参考哪块要进行更新,这个查看都是我们在.ini中指定的,如下定义;

只更新sec、pei、dxe,指定基地址,长度、镜像偏移就可以了,这些要根据平台实际的Layout进行填写,否则将无法起来;

 [Head]
NumOfUpdate = 3
Update0 = SECFV
Update1 = PEIFV
Update2 = DXEFV

[SECFV]
FirmwareType = 0            # SystemFirmware
AddressType = 0             # 0 - relative address, 1 - absolute address.
BaseAddress = 0x00060000    # Base address offset on flash
Length      = 0x00030000    # Length
ImageOffset = 0x00060000    # Image offset of this SystemFirmware image
FileGuid    = 5151a274-2614-4ea7-c660-2dbe3ca2ccb3  # PcdEdkiiSystemFirmwareFileGuid

[PEIFV]
FirmwareType = 0            # SystemFirmware
AddressType = 0             # 0 - relative address, 1 - absolute address.
BaseAddress = 0x00090000    # Base address offset on flash
Length      = 0x00080000    # Length
ImageOffset = 0x00090000    # Image offset of this SystemFirmware image
FileGuid    = 5151a274-2614-4ea7-c660-2dbe3ca2ccb3  # PcdEdkiiSystemFirmwareFileGuid

[DXEFV]
FirmwareType = 0            # SystemFirmware
AddressType = 0             # 0 - relative address, 1 - absolute address.
BaseAddress = 0x00110000    # Base address offset on flash
Length      = 0x002f0000    # Length
ImageOffset = 0x00110000    # Image offset of this SystemFirmware image
FileGuid    = 5151a274-2614-4ea7-c660-2dbe3ca2ccb3  # PcdEdkiiSystemFirmwareFileGuid

也可以只更新一个镜像,这里只更新一个FD,会将Flash整个更新

[Head]
NumOfUpdate = 1
Update0 = LOONGSONFD

[LOONGSONFD]
FirmwareType = 0            # SystemFirmware
AddressType = 0             # 0 - relative address, 1 - absolute address.
BaseAddress = 0x00000000    # Base address offset on flash
Length      = 0x00400000    # Length
ImageOffset = 0x00000000    # Image offset of this SystemFirmware image
FileGuid    = 5151a274-2614-4ea7-c660-2dbe3ca2ccb3  # PcdEdkiiSystemFirmwareFileGuid

注意:需要注意的是修改ini文件的内容后要clean 收进行编译,否则无法编译进行!

  • FDF [FmpPayload] Section

将以下[FmpPayload]部分添加到平台FDF文件中以构建FMP有效负载,并将其包装到使用PKCS7证书签名的胶囊中。
IMAGE_TYPE_ID 根据# PcdSystemFmpCapsuleImageTypeIdGuid设置为IMAGE_TYPE_ID_GUID,在.aslc中有定义;

!if $(CAPSULE_ENABLE)
[FmpPayload.FmpPayloadSystemFirmwarePkcs7]
IMAGE_HEADER_INIT_VERSION = 0x02
IMAGE_TYPE_ID             = 62af20c0-7016-424a-9bf8-9ccc86584090 # PcdSystemFmpCapsuleImageTypeIdGuid
IMAGE_INDEX               = 0x1
HARDWARE_INSTANCE         = 0x0
MONOTONIC_COUNT           = 0x2
CERTIFICATE_GUID          = 4AAFD29D-68DF-49EE-8AA9-347D375665A7 # PKCS7

FV = SystemFirmwareUpdateCargo
!endif
  • FDF [Capsule] Section

将以下[Capsule]部分添加到平台FDF文件中,以将FMP有效载荷包装到使用PKCS7证书签名的胶囊中

!if $(CAPSULE_ENABLE)
[Capsule.<YourPlatformName>FirmwareUpdateCapsuleFmpPkcs7]
CAPSULE_GUID                = 6dcbd5ed-e82d-4c44-bda1-7194199ad92a # gEfiFmpCapsuleGuid
CAPSULE_FLAGS               = PersistAcrossReset,InitiateReset
CAPSULE_HEADER_SIZE         = 0x20
CAPSULE_HEADER_INIT_VERSION = 0x1

FMP_PAYLOAD = FmpPayloadSystemFirmwarePkcs7
!endif
  • FDF [Rule]

添加将平台特定的SystemFirmwareDescriptor PEIM添加到PEI FV所需的以下[Rule]。

[Rule.Common.PEIM.FMP_IMAGE_DESC]
  FILE PEIM = $(NAMED_GUID) {
     RAW BIN                  |.acpi
     PEI_DEPEX PEI_DEPEX Optional        $(INF_OUTPUT)/$(MODULE_NAME).depex
     PE32      PE32    Align=4K          $(INF_OUTPUT)/$(MODULE_NAME).efi
     UI       STRING="$(MODULE_NAME)" Optional
     VERSION  STRING="$(INF_VERSION)" Optional BUILD_NUM=$(BUILD_NUMBER)
  }
  • 升级的解析过程
    首先执行的是SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareReportDxe.c 中的验证,只有这个过了以后,才会执行SignedCapsulePkg/Universal/SystemFirmwareUpdate/SystemFirmwareUpdateDxe.c

重点:
突然发现SystemFirmwareUpdateDxe是实时更新的,也就是你刚编译完成的包放进去升级跑的就是刚编译的改动,查看发现SystemFirmwareUpdateDxe是放在Dispatch中的,这也就是SystemFirmwareReportDxeSystemFirmwareUpdateDxe分开的根本原因,看着两套实现都差不多,其实SystemFirmwareReportDxe跑的是当前运行的代码,主要功能是检测当前的capsule 包,校验dispatch,加载dispatch ,调用dispatch ,而dispatch就是SystemFirmwareUpdateDxe,所以跑的是当前要升级包的代码;这个实现真的很强大!

执行的大致过程差不多,主要接口如下

  • FmpSetImage
  • SystemFirmwareAuthenticatedUpdate 入口
  • CapsuleAuthenticateSystemFirmware 验证进行,并提取数据包
  • ExtractAuthenticatedImage从FMP胶囊图像中提取经过身份验证的镜像
  • ExtractSystemFirmwareImage从镜像提取系统固件镜像
  • ExtractSystemFirmwareImageFmpInfo 从系统固件中提取ImageFmpInfo信息
  • ExtractSystemFirmwareImage从完成身份验证的镜像 提取系统固件镜像
  • ExtractConfigImage从完成身份验证的镜像 提取ini文件

认证检测

固件在更新前必须按照NIST(National Institute of Standards and Technology)标准完成一些认证检测,比如签名认证。待更新capsule必须经过签名,更新机制才可以判断待更新内容是否可信。UEFI capsule 使用EFI_FIRMWARE_IMAGE_AUTHENTICATION认证,认证信息为PKCS7签名。固件更新机制从系统固件中提取可信证书,用可信证书验证PKCS7签名。如果capsule没有使用EFI_FIRMWARE_IMAGE_AUTHENTICATION认证或者认证失败,此capsule不会被用来更新固件。具体请参考:

更新流程图

代码实现

Capsule 头结构体

///
/// EFI Capsule Header.
///
typedef struct {
  ///  
  /// A GUID that defines the contents of a capsule.
  ///  
  EFI_GUID          CapsuleGuid;
  ///  
  /// The size of the capsule header. This may be larger than the size of
  /// the EFI_CAPSULE_HEADER since CapsuleGuid may imply
  /// extended header entries
  ///  
  UINT32            HeaderSize;
  ///  
  /// Bit-mapped list describing the capsule attributes. The Flag values
  /// of 0x0000 - 0xFFFF are defined by CapsuleGuid. Flag values
  /// of 0x10000 - 0xFFFFFFFF are defined by this specification
  ///  
  UINT32            Flags;
  ///  
  /// Size in bytes of the capsule.
  ///  
  UINT32            CapsuleImageSize;
} EFI_CAPSULE_HEADER;

CapsuleApp位置

~/vUDK2018-loongson/MdeModulePkg/Application/CapsuleApp

更新之前先检测是否支持
Status = gRT->QueryCapsuleCapabilities (CapsuleHeaderArray, CapsuleNum, &MaxCapsuleSize, &ResetType);
更新封装实现:
Status = gRT->UpdateCapsule(CapsuleHeaderArray,CapsuleNum,(UINTN) BlockDescriptors)
完成之后调用重启
gRT->ResetSystem (ResetType, EFI_SUCCESS, 0, NULL);
函数原型位置
/MdeModulePkg/Universal/CapsuleRuntimeDxe/CapsuleService.c

添加openssl库
下载源码
https://github.com/openssl/openssl.git
..\edk2-vUDK2018\CryptoPkg\Library\OpensslLib\中,存在一个openssl目录,将其删除后,将openssl-OpenSSL_1_1_0g.zip解压到该目录下并重命名为openssl
添加/vUDK2018-loongson/CryptoPkg/Library/OpensslLib/openssl/crypto/bio/bss_log.c 25行添加# define NO_SYSLOG定义,解决关于log(LOG_DAEMON)宏报错的问题
/vUDK2018-loongson/CryptoPkg/Library/OpensslLib/openssl/crypto/conf/conf_mall.c:30: undefined reference to `conf_add_ssl_module’
在vUDK2018-loongson/CryptoPkg/Library/OpensslLib/OpensslLib.inf添加$(OPENSSL_PATH)/crypto/conf/conf_ssl.c

///
/// EFI Capsule Block Descriptor
///
typedef struct {
  ///
  /// Length in bytes of the data pointed to by DataBlock/ContinuationPointer.
  ///DataBlock / ContinuationPointer指向的数据长度(以字节为单位)
  UINT64                  Length;
  union {
    ///
    /// Physical address of the data block. This member of the union is
    /// used if Length is not equal to zero.
    ///数据块的物理地址。 如果Length不等于零,则使用该联合的成员。
    EFI_PHYSICAL_ADDRESS  DataBlock;
    ///
	/// EFI_CAPSULE_BLOCK_DESCRIPTOR结构的另一个块的物理地址。 如果Length等于零,则使用该联合的成员。 如果ContinuationPointer为零,则该条目表示列表的结尾。
    ///
    EFI_PHYSICAL_ADDRESS  ContinuationPointer; 
  } Union;
} EFI_CAPSULE_BLOCK_DESCRIPTOR;

windowns 中文说明文档
https://github.com/MicrosoftDocs/windows-driver-docs.zh-cn/blob/a3c6b8c60b5f08928a571c4d536b03a8278cc0a0/windows-driver-docs-pr/bringup/windows-uefi-firmware-update-platform.md

  • 11
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值