【UEFI基础】UEFI变量基础2

淡淡的烟草味﹌ 2023-10-01 19:13 76阅读 0赞

说明

之前已经写过一篇变量相关的文章【UEFI基础】UEFI变量基础,该文章使用的是模拟的变量,而本文更接近于实际的变量模块。

环境设置

为了测试BIOS的变量功能,需要修改QEMU的启动选项,如下所示:

  1. qemu-system-x86_64 -machine q35,smm=on -drive format=raw,file=disk.img -drive if=pflash,format=raw,unit=0,file=OVMF.fd -net nic -net tap,ifname=tap0 -serial stdio >> log.txt

重点是增加了if=pflash,format=raw,unit=0,file=OVMF.fd,表示一个SPI Flash芯片。这样就可以通过变量驱动来修改其中的变量区域,而使用-bios参数是达不到这个目的的。

下面是使用上述命令执行一遍OVMF之后其二进制的变化:

在这里插入图片描述

左边是原始的OVMF.fd,右边是执行一次BIOS之后的OVMF.fd,可以看到整个二进制发生了变化,由于变量区就在二进制的开头,所以能够直接捕捉到,而变化的部分就是设置的变量。

模块

OVMF下BIOS开发涉及的模块如下:

  1. INF OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
  2. INF OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf
  3. INF MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf
  4. INF MdeModulePkg/Universal/Variable/RuntimeDxe/VariableRuntimeDxe.inf

其中FvbServicesRuntimeDxe.inf有一个优先执行的配置,所以它会是上述模块中第一个执行的。

  1. APRIORI DXE {
  2. INF MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
  3. INF MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
  4. INF OvmfPkg/AmdSevDxe/AmdSevDxe.inf
  5. !if $(SMM_REQUIRE) == FALSE
  6. INF OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
  7. !endif
  8. }

当前使用的是非SMM下的变量存储,所以涉及的是上述的模块,如果是SMM下模块会有所不同。这里暂时只讨论非SMM下的情况。

关于模块的依赖关系说明:































模块 依赖 输出
FvbServicesRuntimeDxe.inf NA gEfiFirmwareVolumeBlockProtocolGuid
gEfiDevicePathProtocolGuid
Fvb.inf NA gEfiFirmwareVolumeBlock2ProtocolGuid
gEfiDevicePathProtocolGuid
FaultTolerantWriteDxe.inf gEfiFirmwareVolumeBlockProtocolGuid
gEfiRuntimeArchProtocolGuid
gEfiFaultTolerantWriteProtocolGuid
VariableRuntimeDxe.inf NA gEdkiiVariableLockProtocolGuid
gEdkiiVarCheckProtocolGuid
gEfiVariableArchProtocolGuid
gEdkiiVariablePolicyProtocolGuid
gEfiVariableWriteArchProtocolGuid

需要注意gEfiFirmwareVolumeBlockProtocolGuidgEfiFirmwareVolumeBlock2ProtocolGuid两个的GUID值是一样的:

  1. ## Include/Protocol/FirmwareVolumeBlock.h
  2. gEfiFirmwareVolumeBlockProtocolGuid = {
  3. 0x8f644fa9, 0xe850, 0x4db1, {
  4. 0x9c, 0xe2, 0xb, 0x44, 0x69, 0x8e, 0x8d, 0xa4 } }
  5. ## Include/Protocol/FirmwareVolumeBlock.h
  6. gEfiFirmwareVolumeBlock2ProtocolGuid = {
  7. 0x8f644fa9, 0xe850, 0x4db1, {
  8. 0x9c, 0xe2, 0xb, 0x44, 0x69, 0x8e, 0x8d, 0xa4 } }

所以FvbServicesRuntimeDxe.inf和Fvb.inf应该是为了同样的目标。直接情况中Fvb.inf并没有完全执行,查看日志可以看到:

  1. Loading driver at 0x00007AD6000 EntryPoint=0x00007AD6384 EmuVariableFvbRuntimeDxe.efi
  2. InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 70B3E98
  3. ProtectUefiImageCommon - 0x70B3B40
  4. - 0x0000000007AD6000 - 0x0000000000003C60
  5. EMU Variable FVB Started
  6. Disabling EMU Variable FVB since flash variables appear to be supported.
  7. Error: Image at 00007AD6000 start failed: Aborted

对应的代码是:

  1. if (PcdGet64 (PcdFlashNvStorageVariableBase64) != 0) {
  2. DEBUG ((DEBUG_INFO, "Disabling EMU Variable FVB since "
  3. "flash variables appear to be supported.\n"));
  4. return EFI_ABORTED;
  5. }

之所以PcdGet64 (PcdFlashNvStorageVariableBase64)的值是0,是因为在先执行的FvbServicesRuntimeDxe.inf模块中有:

  1. VOID
  2. SetPcdFlashNvStorageBaseAddresses (
  3. VOID
  4. )
  5. {
  6. RETURN_STATUS PcdStatus;
  7. //
  8. // Set several PCD values to point to flash
  9. //
  10. PcdStatus = PcdSet64S (
  11. PcdFlashNvStorageVariableBase64,
  12. (UINTN) PcdGet32 (PcdOvmfFlashNvStorageVariableBase) // 只是0xFFC00000
  13. );
  14. // 后面略
  15. }

在UEFI Shell下可以查看0xFFC00000这个地址,它就映射到了Flash的变量区域:

在这里插入图片描述

进一步的可以看到这段地址有特殊的映射:

在这里插入图片描述

这里会在后续的代码分析中看到。

所以,我们需要关注的模块主要是如下的三个(没有开SMM的情况下):

  1. FvbServicesRuntimeDxe.inf
  2. FaultTolerantWriteDxe.inf
  3. VariableRuntimeDxe.inf

以上模块的执行顺序也是1 -> 2 -> 3。

FvbServicesRuntimeDxe.inf

模块的入口是FvbInitialize(),其大致流程如下:

QemuFlashInitialize

为全局mFvbModuleGlobal分配Runtime内存

通过Flash基址找到变量区的FV

InitializeVariableFvHeader

GetFvbInfo

初始化mFvbModuleGlobal中的FvInstance

初始化EFI_FW_VOL_BLOCK_DEVICE其中包含了EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL

安装EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL

设置变量区的内存属性

设置变量区基址到PCD阻止Fvb.inf模块运行

设置gEfiEventVirtualAddressChangeGuid回调函数

使能PcdOvmfFlashVariablesEnable

EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL的结构体定义如下:

  1. ///
  2. /// The Firmware Volume Block Protocol is the low-level interface
  3. /// to a firmware volume. File-level access to a firmware volume
  4. /// should not be done using the Firmware Volume Block Protocol.
  5. /// Normal access to a firmware volume must use the Firmware
  6. /// Volume Protocol. Typically, only the file system driver that
  7. /// produces the Firmware Volume Protocol will bind to the
  8. /// Firmware Volume Block Protocol.
  9. ///
  10. struct _EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL{
  11. EFI_FVB_GET_ATTRIBUTES GetAttributes;
  12. EFI_FVB_SET_ATTRIBUTES SetAttributes;
  13. EFI_FVB_GET_PHYSICAL_ADDRESS GetPhysicalAddress;
  14. EFI_FVB_GET_BLOCK_SIZE GetBlockSize;
  15. EFI_FVB_READ Read;
  16. EFI_FVB_WRITE Write;
  17. EFI_FVB_ERASE_BLOCKS EraseBlocks;
  18. ///
  19. /// The handle of the parent firmware volume.
  20. ///
  21. EFI_HANDLE ParentHandle;
  22. };

里面包含了读写操作,它们是变量操作的底层实现。在UEFI Shell下通过gEfiFirmwareVolumeBlockProtocolGuid来查看Protocol,代码如下:

  1. Status = gBS->LocateHandleBuffer (
  2. ByProtocol,
  3. &gEfiFirmwareVolumeBlockProtocolGuid,
  4. NULL,
  5. &Count,
  6. &Handles
  7. );
  8. if (EFI_ERROR (Status) || (0 == Count)) {
  9. Print (L"No FVB found!\n");
  10. return;
  11. } else {
  12. Print (L"%d FVB(s) found!\n", Count);
  13. Print (L"-----------------------------------\n");
  14. }
  15. for (Index = 0; Index < Count; Index++) {
  16. Status = gBS->HandleProtocol (
  17. Handles[Index],
  18. &gEfiFirmwareVolumeBlockProtocolGuid,
  19. (VOID **) &Fvb
  20. );
  21. if (EFI_ERROR (Status)) {
  22. continue;
  23. }
  24. Print (L"FVB%d:\n", Index);
  25. Status = Fvb->GetPhysicalAddress (Fvb, &Address);
  26. if (!EFI_ERROR (Status)) {
  27. Print (L" Address: 0x%016x\n", Address);
  28. }
  29. }

执行结果如下:

在这里插入图片描述

这里的地址通过如下的结构体来描述:

  1. typedef struct {
  2. UINTN FvBase;
  3. UINTN NumOfBlocks;
  4. EFI_FIRMWARE_VOLUME_HEADER VolumeHeader;
  5. } EFI_FW_VOL_INSTANCE;
  6. typedef struct {
  7. UINT32 NumFv;
  8. EFI_FW_VOL_INSTANCE *FvInstance;
  9. } ESAL_FWB_GLOBAL;
  10. extern ESAL_FWB_GLOBAL *mFvbModuleGlobal;

模块中有一个全局的变量mFvbModuleGlobal,它包含一个指针,指向了各个FV模块的基址和大小,每个FV都对应创建一个EFI_FW_VOL_BLOCK_DEVICE,里面包含了EFI_FW_VOL_BLOCK_DEVICE,由此构成了Firmware Volume Block的基本信息和基本操作。具体的地址是可以根据实际情况定制的,本例中就是上图的值。

设置变量区的内存属性这一步就是前面提到的变量区特殊MMIO的来源。从上面的示例中也可以看到0xFFC00000这个地址(另外的一个地址来自MdeModulePkg\Core\Dxe\FwVolBlock\FwVolBlock.c,这里不多作介绍)。

设置gEfiEventVirtualAddressChangeGuid回调函数这里的回调函数保证了在Runtime的时候Flash操作还是可用的。

关于Flash的底层操作这里就不介绍了,因为它本来就是通过QEMU实现的,真正的开发当中是不会用到的。

FaultTolerantWriteDxe.inf

模块的入口是FaultTolerantWriteInitialize(),大致的流程:

InitFtwDevice

注册安装gEfiFirmwareVolumeBlockProtocolGuid之后的回调函数

从前面的模块分析中已经看到gEfiFirmwareVolumeBlockProtocolGuid是在FvbServicesRuntimeDxe.inf模块中安装的,而这个模块会先于FaultTolerantWriteDxe.inf运行,所以虽说是回调,但是因为gEfiFirmwareVolumeBlockProtocolGuid已经安装了,所以会在模块中直接执行。该回调函数的主要作用是安装gEfiFaultTolerantWriteProtocolGuid对应的Protocol。

该模块另一个重要的工作就是初始化EFI_FTW_DEVICE结构体:

  1. //
  2. // EFI Fault tolerant protocol private data structure
  3. //
  4. typedef struct {
  5. UINTN Signature;
  6. EFI_HANDLE Handle;
  7. EFI_FAULT_TOLERANT_WRITE_PROTOCOL FtwInstance;
  8. EFI_PHYSICAL_ADDRESS WorkSpaceAddress; // Base address of working space range in flash.
  9. EFI_PHYSICAL_ADDRESS SpareAreaAddress; // Base address of spare range in flash.
  10. UINTN WorkSpaceLength; // Size of working space range in flash.
  11. UINTN NumberOfWorkSpaceBlock; // Number of the blocks in work block for work space.
  12. UINTN WorkBlockSize; // Block size in bytes of the work blocks in flash
  13. UINTN SpareAreaLength; // Size of spare range in flash.
  14. UINTN NumberOfSpareBlock; // Number of the blocks in spare block.
  15. UINTN SpareBlockSize; // Block size in bytes of the spare blocks in flash
  16. EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *FtwWorkSpaceHeader;// Pointer to Working Space Header in memory buffer
  17. EFI_FAULT_TOLERANT_WRITE_HEADER *FtwLastWriteHeader;// Pointer to last record header in memory buffer
  18. EFI_FAULT_TOLERANT_WRITE_RECORD *FtwLastWriteRecord;// Pointer to last record in memory buffer
  19. EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FtwFvBlock; // FVB of working block
  20. EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FtwBackupFvb; // FVB of spare block
  21. EFI_LBA FtwSpareLba; // Start LBA of spare block
  22. EFI_LBA FtwWorkBlockLba; // Start LBA of working block that contains working space in its last block.
  23. UINTN NumberOfWorkBlock; // Number of the blocks in work block.
  24. EFI_LBA FtwWorkSpaceLba; // Start LBA of working space
  25. UINTN FtwWorkSpaceBase; // Offset into the FtwWorkSpaceLba block.
  26. UINTN FtwWorkSpaceSize; // Size of working space range that stores write record.
  27. EFI_LBA FtwWorkSpaceLbaInSpare; // Start LBA of working space in spare block.
  28. UINTN FtwWorkSpaceBaseInSpare;// Offset into the FtwWorkSpaceLbaInSpare block.
  29. UINT8 *FtwWorkSpace; // Point to Work Space in memory buffer
  30. //
  31. // Following a buffer of FtwWorkSpace[FTW_WORK_SPACE_SIZE],
  32. // Allocated with EFI_FTW_DEVICE.
  33. //
  34. } EFI_FTW_DEVICE;

FtwInstance就是前面安装的Protocol,其它的都是跟FV相关的信息,这里不再赘述。

本模块的作用从名字中就可以看出来,是为了写变量容错而实现的。它依赖于额外的变量存储空间,这在上述的结构体中也可以看出来。

VariableRuntimeDxe.inf

模块的入口是VariableServiceInitialize(),大致的流程:

安装gEdkiiVariableLockProtocolGuid

安装gEdkiiVarCheckProtocolGuid

给gRT中的变量函数赋值

安装gEfiVariableArchProtocolGuid

注册安装gEfiFaultTolerantWriteProtocolGuid时的回调函数

注册gEfiEventVirtualAddressChangeGuid回调函数

注册OnReadyToBoot回调函数

注册OnEndOfDxe回调函数

InitVariablePolicyLib

VarCheckRegisterSetVariableCheckHandler

安装gEdkiiVariablePolicyProtocolGuid

这里最重要的部分就是给gRT中的变量服务赋值了,并安装了gEfiVariableArchProtocolGuid,这样后续才可以开始使用这些服务函数。还有一些变量相关的Protocol和Policy的操作,这里不做赘述。

发表评论

表情:
评论列表 (有 0 条评论,76人围观)

还没有评论,来说两句吧...

相关阅读

    相关 UEFI基础】GPT

    简介 GPT全称是GUID Partition Table,是硬盘分区的一种格式。 硬盘分区格式有两种,一种是MBR,另一种是GPT。 GPT是随著UEFI引入了,U