最近看到一篇文章“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 生成的新地址就是