Step to UEFI (175)Shell 下读取 VBT

最近看到一篇文章“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 的板子上验证一下:

  1. 取得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

《Step to UEFI (175)Shell 下读取 VBT》有12个想法

  1. 在CoffeeLake上, 這個 code 沒辦法完整dump VBT, 會少 0x9f 個 bytes, 但前面的資料都正確.
    重新用 protocol gPlatformGOPPolicyGuid 的 GetVbtData function取得的 VBT 才有跟我包在BIOS裡面的一致

    給你參考一下

    1. 一般最后几位都是要 mask 掉的,另外还有对齐的需求,所以OpRegion 是0x2FEDC000

      不好意思,前面讲错了。这里的OpRegion是直接读取 PCI 空间上 0xFC 开始的4个字节得来的。

  2. 取得OpRegion,地址为 0x2FEDC018?
    步驟是不是先核對Table 2-2 中 Address offset 欄位的FC-FFh
    然後進入RU 中去找到00fc , 接著到結尾處找出地址為0x2FEDC018

回复 Simon 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注