最近看到一篇文章“Intel ACPI IGD OpRegion Spec, and IntelSiliconPkg added to Tianocore”【参考1】,介绍的是Intel Jiewen Yao在开源项目 Tianocore 中加入了关于 IGD OpRegion相关内容。与这个文件相关有一份名为“Intel Integrated Graphics Device OpRegion Specification Driver Programmer’s Reference Manual” 的 datasheet【参考2】。简单的说这是介绍 Intel IGD 显卡控制接口的资料。上面提到的下面这个表格引起了我的兴趣:
就是说,如果我们能在内存中找到OpRegion 那么是有机会找到 VBT 的。如果能找到 VBT 那么就有机会 Dump 出来。更出人意料的是,这份Datasheet 直接给出了获得 OpRegion 的方法:
结合【参考1】的Code, IGD OpRegion 结构如下(【参考2】也提到了下面的结构体,但是VBT 的 Offset 是 0x500和实际对不上,有可能是因为 Datasheet 太老):
///
/// IGD OpRegion Structure
///
typedef struct {
IGD_OPREGION_HEADER Header; ///< OpRegion header (Offset 0x0, Size 0x100)
IGD_OPREGION_MBOX1 MBox1; ///< Mailbox 1: Public ACPI Methods (Offset 0x100, Size 0x100)
IGD_OPREGION_MBOX2 MBox2; ///< Mailbox 2: Software SCI Interface (Offset 0x200, Size 0x100)
IGD_OPREGION_MBOX3 MBox3; ///< Mailbox 3: BIOS to Driver Notification (Offset 0x300, Size 0x100)
IGD_OPREGION_MBOX4 MBox4; ///< Mailbox 4: Video BIOS Table (VBT) (Offset 0x400, Size 0x1800)
IGD_OPREGION_MBOX5 MBox5; ///< Mailbox 5: BIOS to Driver Notification Extension (Offset 0x1C00, Size 0x400)
} IGD_OPREGION_STRUCTURE;
接下来在 Kabylake-R 的板子上验证一下:
- 取得OpRegion,地址为 0x2FEDC018
2.查看内存
3.到 +0x400的地方查看,可以看到 $VBT 的 Sign
该处内容和我代码中的 VBT 一致。接下来的问题是,如何得知VBT 的大小。在UDK2018 的Source Code 中有下面的文件 Vlv2TbltDevicePkg\VlvPlatformInitDxe\IgdOpRegion.h,其中定义如下:
#pragma pack (1)
typedef struct {
UINT8 HeaderSignature[20];
UINT16 HeaderVersion;
UINT16 HeaderSize;
UINT16 HeaderVbtSize;
UINT8 HeaderVbtCheckSum;
UINT8 HeaderReserved;
UINT32 HeaderOffsetVbtDataBlock;
UINT32 HeaderOffsetAim1;
UINT32 HeaderOffsetAim2;
UINT32 HeaderOffsetAim3;
UINT32 HeaderOffsetAim4;
UINT8 DataHeaderSignature[16];
UINT16 DataHeaderVersion;
UINT16 DataHeaderSize;
UINT16 DataHeaderDataBlockSize;
UINT8 CoreBlockId;
UINT16 CoreBlockSize;
UINT16 CoreBlockBiosSize;
UINT8 CoreBlockBiosType;
UINT8 CoreBlockReleaseStatus;
UINT8 CoreBlockHWSupported;
UINT8 CoreBlockIntegratedHW;
UINT8 CoreBlockBiosBuild[4];
UINT8 CoreBlockBiosSignOn[155];
} VBIOS_VBT_STRUCTURE;
#pragma pack ()
于是我们也可以获得 VBT 的大小。完整的代码如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/UefiBootServicesTableLib.h> //global gST gBS gImageHandle
#include <Library/ShellLib.h>
#include "IgdOpRegion.h"
//Copied from UDK2018\Vlv2TbltDevicePkg\VlvPlatformInitDxe\IgdOpRegion.h
#pragma pack (1)
typedef struct {
UINT8 HeaderSignature[20];
UINT16 HeaderVersion;
UINT16 HeaderSize;
UINT16 HeaderVbtSize;
UINT8 HeaderVbtCheckSum;
UINT8 HeaderReserved;
UINT32 HeaderOffsetVbtDataBlock;
UINT32 HeaderOffsetAim1;
UINT32 HeaderOffsetAim2;
UINT32 HeaderOffsetAim3;
UINT32 HeaderOffsetAim4;
UINT8 DataHeaderSignature[16];
UINT16 DataHeaderVersion;
UINT16 DataHeaderSize;
UINT16 DataHeaderDataBlockSize;
UINT8 CoreBlockId;
UINT16 CoreBlockSize;
UINT16 CoreBlockBiosSize;
UINT8 CoreBlockBiosType;
UINT8 CoreBlockReleaseStatus;
UINT8 CoreBlockHWSupported;
UINT8 CoreBlockIntegratedHW;
UINT8 CoreBlockBiosBuild[4];
UINT8 CoreBlockBiosSignOn[155];
} VBIOS_VBT_STRUCTURE;
#pragma pack ()
#define MmPciAddress(Bus, Device, Function, Register) \
((UINTN) 0xE0000000 + \
(UINTN) (Bus << 20) + \
(UINTN) (Device << 15) + \
(UINTN) (Function << 12) + \
(UINTN) (Register) \
)
EFI_GUID gEfiSimpleFileSystemProtocolGuid ={ 0x964E5B22, 0x6459, 0x11D2,
{ 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
//
//Save memory address to a file
//
EFI_STATUS
SaveToFile(
IN UINT8 *FileData,
IN UINTN FileDataLength)
{
EFI_STATUS Status;
EFI_FILE_PROTOCOL *FileHandle;
UINTN BufferSize;
EFI_FILE_PROTOCOL *Root;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
Status = gBS->LocateProtocol(
&gEfiSimpleFileSystemProtocolGuid,
NULL,
(VOID **)&SimpleFileSystem);
if (EFI_ERROR(Status)) {
Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
return Status;
}
Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
if (EFI_ERROR(Status)) {
Print(L"OpenVolume error \r\n");
return Status;
}
Status = Root->Open(
Root,
&FileHandle,
L"dumpvbt.bin",
EFI_FILE_MODE_READ |
EFI_FILE_MODE_WRITE |
EFI_FILE_MODE_CREATE,
0);
if (EFI_ERROR(Status)){
Print(L"Error Open NULL [%r]\n",Status);
return Status;
}
BufferSize = FileDataLength;
Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
if (EFI_ERROR(Status)){
Print(L"Error write [%r]\n",Status);
return Status;
}
else Print(L"VBT has been saved to 'dumpvbt.bin' \n");
FileHandle->Close(FileHandle);
return Status;
}
UINT32
EFIAPI
MmioRead32 (
IN UINTN Address
)
{
UINT32 Value;
Value = *(volatile UINT32*)Address;
return Value;
}
/***
Print a welcoming message.
Establishes the main structure of the application.
@retval 0 The application exited normally.
@retval Other An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
IGD_OPREGION_STRUCTURE *IGDRegion;
VBIOS_VBT_STRUCTURE *VBT;
// If can't find IGD, print a error message
if ((UINT16)(MmPciAddress(0,2,0,0)==0xFFFF)) {
Print(L"No IGF found! \n");
return 0;
}
// Get IGD OPRegion
IGDRegion = (IGD_OPREGION_STRUCTURE*) MmioRead32((UINTN)(MmPciAddress(0,2,0,0xFC)));
Print(L"OpRegion Address [0x%X] \n",IGDRegion);
//Get VBT address
VBT=(VBIOS_VBT_STRUCTURE *) &IGDRegion->MBox4;
Print(L" VBT Address [0x%X] \n",VBT);
Print(L" VBT Size [0x%X] \n",VBT->HeaderVbtSize);
if ((VBT->HeaderVbtSize>8*1024)||(VBT->HeaderVbtSize==0))
{
Print(L"VBT Data too big, ERROR! \n");
}
else
SaveToFile((UINT8*)VBT,VBT->HeaderVbtSize);
return(0);
}
HDK KBL-R 上的运行结果如下:
比较也会发现 dump VBT 和压入的相同。
完整代码下载:
参考:
1. https://firmwaresecurity.com/2016/06/01/intel-acpi-igd-opregion-spec-and-intelsiliconpkg-added-to-tianocore/
2. https://01.org/sites/default/files/documentation/acpi_igd_opregion_spec_0.pdf
在CoffeeLake上, 這個 code 沒辦法完整dump VBT, 會少 0x9f 個 bytes, 但前面的資料都正確.
重新用 protocol gPlatformGOPPolicyGuid 的 GetVbtData function取得的 VBT 才有跟我包在BIOS裡面的一致
給你參考一下
谢谢哈,回头我研究一下
我用 Legacy VBIOS 好像都抓不正確.
取得OpRegion,地址为 0x2FEDC018?
如何得知
一般最后几位都是要 mask 掉的,另外还有对齐的需求,所以OpRegion 是0x2FEDC000不好意思,前面讲错了。这里的OpRegion是直接读取 PCI 空间上 0xFC 开始的4个字节得来的。
取得OpRegion 是配合Address offset 和 Default value 來算的嗎?
所以固定讀取PCI 空间上 0xFC 开始嗎? 還是因為SPEC 上 Table 2-2 中 Address offset 欄位為FC-FFh , 所以取0xFC
Intel SPEC 上 Table 2-2 ,给出来 Intel GFX 的偏移,所以读取 FC-FFh 的内容。
thanks 感謝解說
取得OpRegion,地址为 0x2FEDC018?
步驟是不是先核對Table 2-2 中 Address offset 欄位的FC-FFh
然後進入RU 中去找到00fc , 接著到結尾處找出地址為0x2FEDC018
how to find 0x400 VBT value on step 3?
前面提到的 0x2FEDC018 + 400 生成的新地址就是