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.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()
,其大致流程如下:
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()
,大致的流程: