请保留-> 【原文: 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>
请尝试自己编译下,在实际的机器上进行实验。
8,194 total views, 5 views today