【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.inf | NA | gEfiFirmwareVolumeBlockProtocolGuid gEfiDevicePathProtocolGuid |
Fvb.inf | NA | gEfiFirmwareVolumeBlock2ProtocolGuid gEfiDevicePathProtocolGuid |
FaultTolerantWriteDxe.inf | gEfiFirmwareVolumeBlockProtocolGuid gEfiRuntimeArchProtocolGuid | gEfiFaultTolerantWriteProtocolGuid |
VariableRuntimeDxe.inf | NA | gEdkiiVariableLockProtocolGuid gEdkiiVarCheckProtocolGuid gEfiVariableArchProtocolGuid gEdkiiVariablePolicyProtocolGuid gEfiVariableWriteArchProtocolGuid |
需要注意gEfiFirmwareVolumeBlockProtocolGuid
和gEfiFirmwareVolumeBlock2ProtocolGuid
两个的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的情况下):
- FvbServicesRuntimeDxe.inf
- FaultTolerantWriteDxe.inf
- 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的操作,这里不做赘述。
还没有评论,来说两句吧...