4

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

 2 years ago
source link: http://yiiyee.cn/blog/2022/01/08/uefi%E5%BC%80%E5%8F%91%E6%8E%A2%E7%B4%A2102-acpi%E6%8E%A2%E7%A9%B601%EF%BC%88uefi%E9%85%8D%E7%BD%AE%E8%A1%A8%EF%BC%89/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

请保留-> 【原文:  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>

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

354 total views, 7 views today


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK