UEFI开发探索102 – ACPI探究01(UEFI配置表)

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

最近有项工作,是修改ACPI表。问题本身并不复杂,但是由于代码需要移植到Option ROM上,遇到不少奇怪的现象。因此,花了不少时间,对ACPI进行研究。

任务是完成了,我的好奇心又被勾起来了。准备发挥“格物致知”的信念,把我认为的ACPI各方面的知识,好好地捋一捋。

1 大致规划

这次的ACPI探索的博客,估计会有不少篇章。准备从三个角度来了解ACPI的知识,包括UEFI的角度、操作系统的角度和ACPI规范的角度。

内容的编排不会那么规范,想到哪里就写到哪里,大致的计划如下:

1) UEFI配置表中的ACPI;
2) ACPI规范简介;
3) 使用UEFI Protocol分析AML Code;
4) ShellPkg中的acpiview
5) EDK2中对ACPI的实现
6) 独立于操作系统的ACPICA
7) Windows/Linux下使用ACPI分析工具
8) 其他ACPI相关课题

ACPI作为独立于操作系统的一套底层规范,在现代操作系统中发挥了巨大的作用。目前其规范已经移交给UEFI官网维护,可以在UEFI.org上下载各版本的规范文档。

本篇先借用好友lab-z博客中的方法,通过UEFI配置表,找到ACPI相关的各种表格,博客地址如下:http://www.lab-z.com/studsdt/

2 UEFI Configuration Table(配置表)

UEF Configuration Talbe的指针,包含在System Talbe中。作为UEFI的基础架构,System Table的使用贯穿了UEFI Application和UEFI driver的整个开发阶段。每个基本的UEFI模块,其入口参数中就包含了System Table。

typedef
EFI_STATUS
(EFIAPI *EFI_IMAGE_ENTRY_POINT) (
  IN EFI_HANDLE ImageHandle, 
  IN EFI_SYSTEM_TABLE *SystemTable 
);

查看下EFI_SYSTEM_TABLE的结构体:(refer to MdePkg\Include\Uefi\UefiSpec.h)

typedef struct {
  EFI_TABLE_HEADER Hdr; /// The table header for the EFI System Table.

  CHAR16 *FirmwareVendor; /// A pointer to a null terminated string that identifies the vendor
  				   /// that produces the system firmware for the platform.
  UINT32 FirmwareRevision; /// A firmware vendor specific value that identifies the revision
                           /// of the system firmware for the platform.
  EFI_HANDLE ConsoleInHandle; /// The handle for the active console input device. This handle must support
                              /// EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL.

  EFI_SIMPLE_TEXT_INPUT_PROTOCOL    *ConIn; /// A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL interface that is
                                            /// associated with ConsoleInHandle.
  EFI_HANDLE ConsoleOutHandle;              /// The handle for the active console output device.
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *ConOut;  /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface
                                              /// that is associated with ConsoleOutHandle.
  EFI_HANDLE StandardErrorHandle;  /// The handle for the active standard error console device.
                                   /// This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *StdErr; /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface
                                             /// that is associated with StandardErrorHandle.
  EFI_RUNTIME_SERVICES *RuntimeServices; /// A pointer to the EFI Runtime Services Table.
  EFI_BOOT_SERVICES *BootServices;       /// A pointer to the EFI Boot Services Table.
  UINTN NumberOfTableEntries; /// The number of system configuration tables in the buffer ConfigurationTable.
  EFI_CONFIGURATION_TABLE *ConfigurationTable;   /// A pointer to the system configuration tables.
                                                 /// The number of entries in the table is NumberOfTableEntries.
} EFI_SYSTEM_TABLE;

从中可以看到不少熟悉的Protocol,比如ConIn、Conout、BootService等。它包含了一系列的指针,指向Console设备,指向Runtime Service Table、Boot Service Table、DXE Service Table和Configuration Table,RS和BS包含很多基础函数,Configuration Table则包含了ACPI、SMBIOS等表。

Configuration Table的类型为EFI_CONFIGURATION_TABLE,是一组GUID/Point对,数据结构如下:

typedef struct {
  ///
  /// The 128-bit GUID value that uniquely identifies the system configuration table.
  ///
  EFI_GUID                          VendorGuid;
  ///
  /// A pointer to the table associated with VendorGuid.
  ///
  VOID                              *VendorTable;
} EFI_CONFIGURATION_TABLE;

在UEFI Spec中,给出了一些UEFI配置表的GUID:

#define EFI_ACPI_20_TABLE_GUID \
{0x8868e871,0xe4f1,0x11d3,\
{0xbc,0x22,0x00,0x80,0xc7,0x3c,0x88,0x81}}
#define ACPI_TABLE_GUID \
{0xeb9d2d30,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define SAL_SYSTEM_TABLE_GUID \
{0xeb9d2d32,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define SMBIOS_TABLE_GUID \
{0xeb9d2d31,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
#define SMBIOS3_TABLE_GUID \
{0xf2fd1544, 0x9794, 0x4a2c,\
{0x99,0x2e,0xe5,0xbb,0xcf,0x20,0xe3,0x94})
#define MPS_TABLE_GUID \
{0xeb9d2d2f,0x2d88,0x11d3,\
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}}
//
// ACPI 2.0 or newer tables should use EFI_ACPI_TABLE_GUID
//
#define EFI_ACPI_TABLE_GUID \ 
{0x8868e871,0xe4f1,0x11d3,\ 
{0xbc,0x22,0x00,0x80,0xc7,0x3c,0x88,0x81}} 
?
#define EFI_ACPI_20_TABLE_GUID EFI_ACPI_TABLE_GUID?
?
#define ACPI_TABLE_GUID \ 
{0xeb9d2d30,0x2d88,0x11d3,\ 
{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}} 
 
#define ACPI_10_TABLE_GUID ACPI_TABLE_GUID

通过相应的GUID,就可以找到需要的ACPI Table指针了。

3 代码实现

本篇不打算解释ACPI中的RSDP、FADT等概念,而是根据EDK2中提供的数据结构,把一些信息打印出来。通过实验,对ACPI各种表有实际的体会,在下一篇再介绍其相互之间的关系。

实例代码,来自于篇首介绍的lab-z的文章,我只是略微修改了些语句。第一个函数,枚举配置表中包含的所有表项,代码如下:

VOID ListConfigurationTable(VOID)
{
  UINTN i;
  EFI_CONFIGURATION_TABLE *configTab = NULL;

  Print(L"Number of Configuration Tables: %d\n",gST->NumberOfTableEntries);
  configTab = gST->ConfigurationTable;
  for(i=0; i<gST->NumberOfTableEntries;i++)
  {
    Print(L"No%d. %g\n",i+1, &configTab->VendorGuid); //%g - a pointer to a GUID structure.
    configTab++;
  }
}

其实就是将SystemTable中配置表所包含的所有表项取出,将它们的GUID打印出来。

下一个函数,则演示如何通过配置表,找到ACPI中的DSDT表。代码实现如下:

VOID ListAcpiTable(VOID)
{
  UINTN     i,j,EntryCount;
  CHAR8 strBuff[20];
  UINT64    *EntryPtr;
  EFI_GUID  AcpiTableGuid  = ACPI_TABLE_GUID;
  EFI_GUID  Acpi2TableGuid = EFI_ACPI_TABLE_GUID;
  EFI_CONFIGURATION_TABLE   *configTab=NULL;  
  EFI_ACPI_DESCRIPTION_HEADER           *XSDT,*Entry,*DSDT;
  EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE   *FADT;
  EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER  *Root;

  Print(L"List ACPI Table:\n");
  configTab=gST->ConfigurationTable;
  
  for (i=0;i<gST->NumberOfTableEntries;i++)
  {   
    //Step1. Find the table for ACPI
    if ((CompareGuid(&configTab->VendorGuid,&AcpiTableGuid) == 0) ||
      (CompareGuid(&configTab->VendorGuid,&Acpi2TableGuid) == 0))
      { 
        Print(L"Found table: %g\n",&configTab->VendorGuid); 
        Print(L"Address: @[0x%p]\n",configTab);
        
        Root=configTab->VendorTable;
        Print(L"ROOT SYSTEM DESCRIPTION @[0x%p]\n",Root);
        ZeroMem(strBuff,sizeof(strBuff));
        CopyMem(strBuff,&(Root->Signature),sizeof(UINT64));
        Print(L"RSDP-Signature [%a] (",strBuff);
        for(j=0;j<8;j++)  
          Print(L"0x%x ",strBuff[j]);
        Print(L")\n");
        Print(L"RSDP-Revision [%d]\n",Root->Revision);
        ZeroMem(strBuff,sizeof(strBuff));
        for (j=0;j<6;j++) { strBuff[j]= (Root->OemId[j] & 0xFF); }
        Print(L"RSDP-OEMID [%a]\n",strBuff);
        
        Print(L"RSDT address= [0x%p], Length=[0x%X]\n",Root->RsdtAddress,Root->Length);
        Print(L"XSDT address= [0x%LX]\n",Root->XsdtAddress);
        WaitKey();
        // Step2. Check the Revision, we olny accept Revision >= 2
        if (Root->Revision >= EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION)
        {
          // Step3. Get XSDT address
          XSDT=(EFI_ACPI_DESCRIPTION_HEADER *)(UINTN) Root->XsdtAddress;
          EntryCount = (XSDT->Length - sizeof(EFI_ACPI_DESCRIPTION_HEADER)) 
                      / sizeof(UINT64);
          ZeroMem(strBuff,sizeof(strBuff));
          CopyMem(strBuff,&(XSDT->Signature),sizeof(UINT32));
          Print(L"XSDT-Sign [%a]\n",strBuff);           
          Print(L"XSDT-length [%d]\n",XSDT->Length);            
          Print(L"XSDT-Counter [%d]\n",EntryCount); 
                  
          // Step4. Check the signature of every entry
          EntryPtr=(UINT64 *)(XSDT+1);
          for (j=0;j<EntryCount; j++,EntryPtr++)
          {
            
            Entry=(EFI_ACPI_DESCRIPTION_HEADER *)((UINTN)(*EntryPtr));
            
            // Step5. Find the FADT table
            if (Entry->Signature==0x50434146) { //'FACP'
              FADT = (EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE *)(UINTN) Entry;
              Print(L"FADT->Dsdt = 0x%X\n",FADT->Dsdt);
              Print(L"FADT->xDsdt = 0x%LX\n",FADT->XDsdt);
            
              // Step6. Get DSDT address
              DSDT = (EFI_ACPI_DESCRIPTION_HEADER *) (FADT->Dsdt);
              Print(L"DSDT table @[%X]\n",DSDT);
              Print(L"DSDT-Length = 0x%x\n",DSDT->Length);
              Print(L"DSDT-Checksum = 0x%x\n",DSDT->Checksum);
            }
          }           
        }
      }
    configTab++;
  }
}

关于ACPI各表项之间的关系,在下一篇中再详细描述。

简单来说,ACPI中存在很多表,DSDT表用来描述系统中固定不变的部分。包括了电源管理、散热管理和即插即用功能。

程序通过RSDP->XSDT->FADT->DSDT这样的顺序,找到DSDT表,并把关心的一些信息打印出来。

另外,需要注意的是,EDK2中提供了大量对ACPI表处理的函数和数据结构、GUID等,包含在如下头文件中:

#include <Guid/Acpi.h>
#include <IndustryStandard/Acpi10.h>
#include <IndustryStandard/Acpi50.h>

请尝试自己编译下,在实际的机器上进行实验。

7,831 total views, 13 views today

发表评论

电子邮件地址不会被公开。