【UEFI基础】UEFI变量基础2

说明

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

环境设置

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

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开发涉及的模块如下:

INF  OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
INF  OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.inf
INF  MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf
INF  MdeModulePkg/Universal/Variable/RuntimeDxe/VariableRuntimeDxe.inf

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

APRIORI DXE {
  INF  MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
  INF  MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
  INF  OvmfPkg/AmdSevDxe/AmdSevDxe.inf
!if $(SMM_REQUIRE) == FALSE
  INF  OvmfPkg/QemuFlashFvbServicesRuntimeDxe/FvbServicesRuntimeDxe.inf
!endif
}

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

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

模块依赖输出
FvbServicesRuntimeDxe.infNAgEfiFirmwareVolumeBlockProtocolGuid
gEfiDevicePathProtocolGuid
Fvb.infNAgEfiFirmwareVolumeBlock2ProtocolGuid
gEfiDevicePathProtocolGuid
FaultTolerantWriteDxe.infgEfiFirmwareVolumeBlockProtocolGuid
gEfiRuntimeArchProtocolGuid
gEfiFaultTolerantWriteProtocolGuid
VariableRuntimeDxe.infNAgEdkiiVariableLockProtocolGuid
gEdkiiVarCheckProtocolGuid
gEfiVariableArchProtocolGuid
gEdkiiVariablePolicyProtocolGuid
gEfiVariableWriteArchProtocolGuid

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

  ## Include/Protocol/FirmwareVolumeBlock.h
  gEfiFirmwareVolumeBlockProtocolGuid = { 0x8f644fa9, 0xe850, 0x4db1, {0x9c, 0xe2, 0xb, 0x44, 0x69, 0x8e, 0x8d, 0xa4 } }
  ## Include/Protocol/FirmwareVolumeBlock.h
  gEfiFirmwareVolumeBlock2ProtocolGuid = { 0x8f644fa9, 0xe850, 0x4db1, {0x9c, 0xe2, 0xb, 0x44, 0x69, 0x8e, 0x8d, 0xa4 } }

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

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

对应的代码是:

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

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

VOID
SetPcdFlashNvStorageBaseAddresses (
  VOID
  )
{
  RETURN_STATUS PcdStatus;

  //
  // Set several PCD values to point to flash
  //
  PcdStatus = PcdSet64S (
    PcdFlashNvStorageVariableBase64,
    (UINTN) PcdGet32 (PcdOvmfFlashNvStorageVariableBase)  // 只是0xFFC00000
    );
 // 后面略
}

在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的结构体定义如下:

///
/// The Firmware Volume Block Protocol is the low-level interface
/// to a firmware volume. File-level access to a firmware volume
/// should not be done using the Firmware Volume Block Protocol.
/// Normal access to a firmware volume must use the Firmware
/// Volume Protocol. Typically, only the file system driver that
/// produces the Firmware Volume Protocol will bind to the
/// Firmware Volume Block Protocol.
///
struct _EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL{
  EFI_FVB_GET_ATTRIBUTES        GetAttributes;
  EFI_FVB_SET_ATTRIBUTES        SetAttributes;
  EFI_FVB_GET_PHYSICAL_ADDRESS  GetPhysicalAddress;
  EFI_FVB_GET_BLOCK_SIZE        GetBlockSize;
  EFI_FVB_READ                  Read;
  EFI_FVB_WRITE                 Write;
  EFI_FVB_ERASE_BLOCKS          EraseBlocks;
  ///
  /// The handle of the parent firmware volume.
  ///
  EFI_HANDLE                    ParentHandle;
};

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

  Status = gBS->LocateHandleBuffer (
                ByProtocol,
                &gEfiFirmwareVolumeBlockProtocolGuid,
                NULL,
                &Count,
                &Handles
                );
  if (EFI_ERROR (Status) || (0 == Count)) {
    Print (L"No FVB found!\n");
    return;
  } else {
    Print (L"%d FVB(s) found!\n", Count);
    Print (L"-----------------------------------\n");
  }

  for (Index = 0; Index < Count; Index++) {
    Status = gBS->HandleProtocol (
                  Handles[Index],
                  &gEfiFirmwareVolumeBlockProtocolGuid,
                  (VOID **) &Fvb
                  );
    if (EFI_ERROR (Status)) {
      continue;
    }

    Print (L"FVB%d:\n", Index);

    Status = Fvb->GetPhysicalAddress (Fvb, &Address);
    if (!EFI_ERROR (Status)) {
      Print (L"  Address: 0x%016x\n", Address);
    }
  }

执行结果如下:

在这里插入图片描述

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

typedef struct {
  UINTN                       FvBase;
  UINTN                       NumOfBlocks;
  EFI_FIRMWARE_VOLUME_HEADER  VolumeHeader;
} EFI_FW_VOL_INSTANCE;

typedef struct {
  UINT32              NumFv;
  EFI_FW_VOL_INSTANCE *FvInstance;
} ESAL_FWB_GLOBAL;

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结构体:

//
// EFI Fault tolerant protocol private data structure
//
typedef struct {
  UINTN                                   Signature;
  EFI_HANDLE                              Handle;
  EFI_FAULT_TOLERANT_WRITE_PROTOCOL       FtwInstance;
  EFI_PHYSICAL_ADDRESS                    WorkSpaceAddress;   // Base address of working space range in flash.
  EFI_PHYSICAL_ADDRESS                    SpareAreaAddress;   // Base address of spare range in flash.
  UINTN                                   WorkSpaceLength;    // Size of working space range in flash.
  UINTN                                   NumberOfWorkSpaceBlock; // Number of the blocks in work block for work space.
  UINTN                                   WorkBlockSize;      // Block size in bytes of the work blocks in flash
  UINTN                                   SpareAreaLength;    // Size of spare range in flash.
  UINTN                                   NumberOfSpareBlock; // Number of the blocks in spare block.
  UINTN                                   SpareBlockSize;     // Block size in bytes of the spare blocks in flash
  EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *FtwWorkSpaceHeader;// Pointer to Working Space Header in memory buffer
  EFI_FAULT_TOLERANT_WRITE_HEADER         *FtwLastWriteHeader;// Pointer to last record header in memory buffer
  EFI_FAULT_TOLERANT_WRITE_RECORD         *FtwLastWriteRecord;// Pointer to last record in memory buffer
  EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL      *FtwFvBlock;        // FVB of working block
  EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL      *FtwBackupFvb;      // FVB of spare block
  EFI_LBA                                 FtwSpareLba;        // Start LBA of spare block
  EFI_LBA                                 FtwWorkBlockLba;    // Start LBA of working block that contains working space in its last block.
  UINTN                                   NumberOfWorkBlock;  // Number of the blocks in work block.
  EFI_LBA                                 FtwWorkSpaceLba;    // Start LBA of working space
  UINTN                                   FtwWorkSpaceBase;   // Offset into the FtwWorkSpaceLba block.
  UINTN                                   FtwWorkSpaceSize;   // Size of working space range that stores write record.
  EFI_LBA                                 FtwWorkSpaceLbaInSpare; // Start LBA of working space in spare block.
  UINTN                                   FtwWorkSpaceBaseInSpare;// Offset into the FtwWorkSpaceLbaInSpare block.
  UINT8                                   *FtwWorkSpace;      // Point to Work Space in memory buffer
  //
  // Following a buffer of FtwWorkSpace[FTW_WORK_SPACE_SIZE],
  // Allocated with EFI_FTW_DEVICE.
  //
} EFI_FTW_DEVICE;

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

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

VariableRuntimeDxe.inf

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

安装gEdkiiVariableLockProtocolGuid
安装gEdkiiVarCheckProtocolGuid
给gRT中的变量函数赋值
安装gEfiVariableArchProtocolGuid
注册安装gEfiFaultTolerantWriteProtocolGuid时的回调函数
注册gEfiEventVirtualAddressChangeGuid回调函数
注册OnReadyToBoot回调函数
注册OnEndOfDxe回调函数
InitVariablePolicyLib
VarCheckRegisterSetVariableCheckHandler
安装gEdkiiVariablePolicyProtocolGuid

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

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值