什么是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_RESET
Flags,则固件将在系统重置后处理胶囊。调用方必须确保使用从QueryCapsuleCapabilities获得的必需的重置值来重置系统。如果未设置此Flags,则固件将立即处理胶囊。
设置CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE
Flag的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)包,结构如下,
[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)
ImageOffse
t: 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
<Your Platform Package>/Feature/Capsule/Library/PlatformFlashAccessLib
<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} }
文件添加说明:
[Defines]
中添加CAPSULE_ENABLE
值为FALSE
,在编译是添加-D CAPSULE_ENABLE
即可打开 DEFINE CAPSULE_ENABLE = FALSE
[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
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
这里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
将在较早的步骤中实现的SystemFirmwareDescriptor PEIM添加到包含其他PEIM的FV中,以便在每次引导时分派它
!if $(CAPSULE_ENABLE)
# FMP image descriptor
INF RuleOverride = FMP_IMAGE_DESC <Your Platform Package>/Feature/Capsule/SystemFirmwareDescriptor/SystemFirmwareDescriptor.inf
!endif
!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
添加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
带有#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 收进行编译,否则无法编译进行!
将以下[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
将以下[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
添加将平台特定的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中的,这也就是SystemFirmwareReportDxe
和SystemFirmwareUpdateDxe
分开的根本原因,看着两套实现都差不多,其实SystemFirmwareReportDxe
跑的是当前运行的代码,主要功能是检测当前的capsule 包,校验dispatch,加载dispatch ,调用dispatch ,而dispatch就是SystemFirmwareUpdateDxe
,所以跑的是当前要升级包的代码;这个实现真的很强大!
执行的大致过程差不多,主要接口如下
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
联系客服