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

(请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365)



最近有项工作,是修改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>

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


  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
### 回答1: ACPI(高级配置和电源接口)是现代计算机系统中负责管理硬件和电源的重要组件。编写ACPI编程代码是深入了解和掌握系统的关键。 ACPI编程的入门首先需要了解ACPI的原理和基本概念。ACPI主要包括了ACPIACPI命名空间、ACPI方法、ACPI设备和ACPI事件等组件。其中最重要的是ACPI,它是存储ACPI配置和信息的数据结构,在操作系统启动时由固件(如BIOS或UEFI)提供。 接下来,我们需要学习如何使用UEFI提供的接口来访问ACPIUEFI是用于取代传统BIOS的新一代固件接口标准,它提供了许多用于操作硬件的接口函数。在UEFI内核导读中,我们可以学习到如何使用UEFI提供的接口函数,如GetSystemTable和LocateProtocol来获取ACPI。 一旦获取了ACPI,就可以使用ACPI编程来操作和管理系统的硬件。我们可以通过解析ACPI的结构和字段来获取硬件信息,如处理器、硬盘和显卡等。同时,ACPI编程还可以用于配置和控制硬件,如设置电源管理策略、启用和禁用设备等。 在ACPI编程入门中,我们也需要学习如何处理ACPI事件。ACPI事件是指硬件状态或系统事件的通知,如按键事件、电源事件和温度事件等。通过注册和处理ACPI事件,我们可以编写更加智能和灵活的系统管理代码。 最后,了解ACPI编程的最佳实践和调试技巧也是非常重要的。ACPI编程常常需要谨慎处理硬件和系统的复杂逻辑,同时需要进行灵活的错误处理。熟悉ACPI规范和文档,以及使用调试工具来分析和解决问题,都是提高ACPI编程效率和质量的关键。 总之,ACPI编程是一项重要而复杂的任务,通过学习UEFI内核导读中的ACPI编程入门知识,我们可以更好地了解和掌握ACPI的原理和操作方法,为开发和管理现代计算机系统提供强大的工具和技术支持。 ### 回答2: 《UEFI内核导读》中的ACPI编程入门章节主要介绍了在UEFI环境下进行ACPI编程的基础知识和技巧。ACPI (Advanced Configuration and Power Interface) 是一种用于操作系统和硬件之间通信的标准接口,它定义了电源管理、设备配置配置信息传递等功能。 ACPI编程的入门主要包括以下内容: 1. ACPI的概述:介绍了ACPI的基本概念、作用和结构,说明了在UEFI中如何使用ACPI来进行设备配置和管理。 2. ACPI:解释了ACPI的类型和作用,如DSDT(Differentiated System Description Table)、SSDT(Secondary System Description Table)等,以及如何在UEFI中获取和使用这些。 3. AML语言:AML(ACPI Machine Language)是一种用于编写ACPI的高级语言,介绍了AML的基本语法和常用指令,以及如何在UEFI中加载和执行AML代码。 4. 设备电源管理:讲解了如何使用ACPI进行设备电源管理,包括睡眠、唤醒和电源状态转换等操作。 5. 事件和中断处理:说明了如何使用ACPI来处理系统事件和中断,以及如何编写ACPI方法来响应这些事件和中断。 通过学习这些内容,读者可以掌握在UEFI环境下进行ACPI编程的基础知识和技巧。ACPI编程UEFI开发中起着重要的作用,对于实现系统设备管理和电源管理等功能非常重要,对于理解和使用UEFI内核也具有重要意义。 ### 回答3: acpi(高级配置和电源接口)是一种被广泛使用在现代计算机系统中的标准,用于管理硬件设备的电源管理和配置信息。在UEFI(统一可扩展固件接口)内核导读中,acpi编程入门是其中一个重要的主题。 acpi编程入门的目的是让开发者了解如何使用acpi来控制硬件设备的电源管理和配置操作。acpi提供了一组标准的接口和语言,使开发者可以编写具体的acpi代码来实现特定的功能。通过acpi编程开发者可以实现诸如电源管理、设备状态监控和系统配置等功能。 在acpi编程入门中,首先需要了解acpi的基本概念和架构。acpi的核心是acpi,它包含系统的配置信息和控制信息。开发者需要了解如何解析和读取acpi,以获取系统的配置信息。同时,了解acpi命名空间和对象模型,可以帮助开发者理解acpi中的各个组件和其关系。 在实际的acpi编程中,开发者需要学习acpi语言(ASL)和acpi命令(AML)。ASL是一种类似于C语言的高级语言,用于编写acpi代码。AML是一种二进制格式的编码,用于实际执行acpi代码。了解如何编写和编译ASL代码,并将其转换为AML代码是开发者的关键任务。 此外,理解acpi的工作原理和常见的acpi编程技巧也是入门的重要内容。开发者需要知道如何注册和操作acpi设备,如何监听和处理acpi事件,以及如何控制电源状态和硬件配置。 总之,在UEFI内核导读中,acpi编程入门是为开发者提供的一项重要教学内容。通过学习acpi编程开发者可以更好地理解和掌握UEFI系统中的电源管理和硬件配置操作。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luobing4365

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值