UEFI基础UEFI变量基础2

Posted jiangwei0512

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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(),大致的流程:

uefi基础学习----基本的efi架构

UEFI+GPT引导基础

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (零) - 目录

UEFI 基础教程 (零) - 目录