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:
modular | rely on | output |
---|---|---|
FvbServicesRuntimeDxe.inf | NA | gEfiFirmwareVolumeBlockProtocolGuid gEfiDevicePathProtocolGuid |
Fvb.inf | NA | gEfiFirmwareVolumeBlock2ProtocolGuid gEfiDevicePathProtocolGuid |
FaultTolerantWriteDxe.inf | gEfiFirmwareVolumeBlockProtocolGuid gEfiRuntimeArchProtocolGuid | gEfiFaultTolerantWriteProtocolGuid |
VariableRuntimeDxe.inf | NA | gEdkiiVariableLockProtocolGuid 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):
- FvbServicesRuntimeDxe.inf
- FaultTolerantWriteDxe.inf
- 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.