[UEFI foundation] UEFI variable foundation 2

Posted by bingo333 on Sun, 20 Feb 2022 01:04:33 +0100

explain

I have written an article on variables before [UEFI basis] UEFI variable basis , this paper uses simulated variables, while this paper is closer to the actual variable module.

Environment settings

In order to test the variable function of BIOS, you need to modify the startup option of QEMU, as shown below:

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

The key point is to add if = pflash, format = raw, unit = 0, file = ovmf FD, indicates an SPI Flash chip. In this way, the variable area can be modified through variable driving, which can not be achieved by using - bios parameter.

The following is the binary change after OVMF is executed once using the above command:

On the left is the original ovmf FD, on the right is the ovmf after the BIOS is executed once FD, you can see that the whole binary has changed. Since the variable area is at the beginning of the binary, it can be directly captured, and the changed part is the set variable.

modular

The modules involved in BIOS development under OVMF are as follows:

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

Where fvbservicesruntimedxe Inf has a priority configuration, so it will be the first one in the above modules.

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
}

The variable storage under non SMM is currently used, so the above modules are involved. If it is a module under SMM, it will be different. For the time being, we will only discuss the case of non SMM.

Description of module dependencies:

modularrely onoutput
FvbServicesRuntimeDxe.infNAgEfiFirmwareVolumeBlockProtocolGuid
gEfiDevicePathProtocolGuid
Fvb.infNAgEfiFirmwareVolumeBlock2ProtocolGuid
gEfiDevicePathProtocolGuid
FaultTolerantWriteDxe.infgEfiFirmwareVolumeBlockProtocolGuid
gEfiRuntimeArchProtocolGuid
gEfiFaultTolerantWriteProtocolGuid
VariableRuntimeDxe.infNAgEdkiiVariableLockProtocolGuid
gEdkiiVarCheckProtocolGuid
gEfiVariableArchProtocolGuid
gEdkiiVariablePolicyProtocolGuid
gEfiVariableWriteArchProtocolGuid

Note that the GUID values of gfirmwarevolumeblockprotocolguid and gfirmwarevolumeblock2protocolguid are the same:

  ## 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 } }

So fvbservices runtimedxe Inf and FVB Inf should be for the same purpose. FVB in direct case Inf is not fully implemented. You can see from the log:

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

The corresponding code is:

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

The reason why the value of PcdGet64 (PcdFlashNvStorageVariableBase64) is 0 is because the fvbservicesruntimedxe. Exe executed earlier The inf module includes:

VOID
SetPcdFlashNvStorageBaseAddresses (
  VOID
  )
{
  RETURN_STATUS PcdStatus;

  //
  // Set several PCD values to point to flash
  //
  PcdStatus = PcdSet64S (
    PcdFlashNvStorageVariableBase64,
    (UINTN) PcdGet32 (PcdOvmfFlashNvStorageVariableBase)  // Only 0xFFC00000
    );
 // Slightly behind
}

You can view the address 0xFFC00000 under UEFI Shell, which is mapped to the variable area of Flash:

Further, we can see that this address has a special mapping:

This will be seen in the subsequent code analysis.

Therefore, we need to pay attention to the following three modules (without SMM):

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

The execution sequence of the above modules is also 1 - > 2 - > 3.

FvbServicesRuntimeDxe.inf

The entry of the module is FvbInitialize(), and its general flow is as follows:

EFI_ FIRMWARE_ VOLUME_ BLOCK_ The structure of protocol is defined as follows:

///
/// 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;
};

It contains read and write operations, which are the underlying implementation of variable operations. Under UEFI Shell, view the Protocol through gefifirmwarevomeblockprotocolguid. The code is as follows:

  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);
    }
  }

The results are as follows:

The address here is described by the following structure:

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;

There is a global variable mFvbModuleGlobal in the module, which contains a pointer to the base address and size of each FV module. Each FV creates an EFI corresponding to it_ FW_ VOL_ BLOCK_ Device, which contains EFI_FW_VOL_BLOCK_DEVICE, which constitutes the basic information and basic operation of Firmware Volume Block. The specific address can be customized according to the actual situation. In this example, it is the value in the figure above.

Setting the memory attribute of the variable area is the source of the special MMIO of the variable area mentioned earlier. From the above example, you can also see the address 0xFFC00000 (another address is from MdeModulePkg\Core\Dxe\FwVolBlock\FwVolBlock.c, which will not be introduced here).

Set the gefieventvirtualdaddresschangeguid callback function. The callback function here ensures that the Flash operation is still available at Runtime.

The underlying operation of Flash is not introduced here, because it is originally realized through QEMU and will not be used in real development.

FaultTolerantWriteDxe.inf

The entry of the module is faulttollantwriteinitialize(). The general process is as follows:

From the previous module analysis, we can see that the gefifirmwarevomeblockprotocolguid is in fvbservices runtimedxe Inf module, which will precede faulttollantwritedxe Inf runs, so although it is a callback, it will be executed directly in the module because gefifirmwarevomeblockprotocolguid has been installed. The main function of this callback function is to install the Protocol corresponding to gEfiFaultTolerantWriteProtocolGuid.

Another important task of this module is to initialize EFI_FTW_DEVICE structure:

//
// 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;

Ftbinstance is the Protocol installed earlier. Other information is related to FV, which will not be repeated here.

The function of this module can be seen from the name. It is realized for writing variable fault tolerance. It depends on additional variable storage space, which can also be seen in the above structure.

VariableRuntimeDxe.inf

The entry of the module is VariableServiceInitialize(). The general process is as follows:

The most important part here is to assign values to variable services in gRT and install gEfiVariableArchProtocolGuid, so that these service functions can be used later. There are also some variable related Protocol and Policy operations, which will not be repeated here.

Topics: uefi