在之前的文章中已经成功实现了基础的ConOut打印,现在可以来尝试深入了解一下ACPI了。
ACPI本身是一个于UEFI协议分离的单独协议,对UEFI来说,主要是在系统表的Configuration Table中携带一个RSDP的指针指向ACPI表。
系统表中的EFI_CONFIGURATION_TABLE数组:
typedef struct {
EFI_TABLE_HEADER Hdr;
CHAR16 *FirmwareVendor;
UINT32 FirmwareRevision;
EFI_HANDLE ConsoleInHandle;
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
EFI_HANDLE ConsoleOutHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
EFI_HANDLE StandardErrorHandle;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
EFI_RUNTIME_SERVICES *RuntimeServices;
EFI_BOOT_SERVICES *BootServices;
UINTN NumberOfTableEntries;
EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;
typedef struct{
EFI_GUID VendorGuid;
VOID *VendorTable;
} EFI_CONFIGURATION_TABLE;此处只提供了一个指向本地的ConfigurationTable的指针,意味此处存在一个EFI_CONFIGURATION_TABLE变量格式的数组。
相应的RSDP的对应的GUID为:
#define EFI_ACPI_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}}其中第一个为2.0之后的新版本的GUID,第二个为老版本的GUID。
获取到RSDP之后,继续可以进一步解析出RSDT(32位)或者XSDT(64位),然后再进一步解析出各类型的系统描述表。
本文的目标,就是根据XSDT,打印能找到的所有的ACPI系统描述表,为后续的解析铺路。
需要做到的事情:
1、根据EFI_CONFIGURATION_TABLE数组指针,完成EFI的GUID查找工具的开发
2、明确RSDP、XSDT两个表象的结构,在找到XSDT后根据struct对后续的FADT等的表项进行数组内的遍历
GUID找RSDP
这里实际上需要的是一种根据GUID去遍历插叙Configuration数组并给出具体ventor指针的功能。
// EFI2.0的ACPI表RSDP的GUID
STATIC CONST EFI_GUID mTargetVendorGuid = {
0x8868e871, 0xe4f1, 0x11d3,
{0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81}
};
// 遍历GUID的对比代码
UINTN Index;
EFI_CONFIGURATION_TABLE *ConfigTable;
if (SystemTable == NULL || TargetGuid == NULL || VendorTable == NULL) {
return EFI_INVALID_PARAMETER;
}
*VendorTable = NULL;
ConfigTable = SystemTable->ConfigurationTable;
for (Index = 0; Index < SystemTable->NumberOfTableEntries; Index++) {
// 使用 CompareGuid 比较 GUID
if (CompareGuid (&ConfigTable[Index].VendorGuid, TargetGuid)) {
*VendorTable = ConfigTable[Index].VendorTable;
return EFI_SUCCESS;
}
}
// 对比函数实例化
BOOLEAN
CompareGuid (
IN CONST EFI_GUID *Guid1,
IN CONST EFI_GUID *Guid2
)
{
return (memcmp (Guid1, Guid2, sizeof (EFI_GUID)) == 0) ? TRUE : FALSE;
}获取到RSDP的vendor指针后可以做下一步的行动。
RSDP与XSDT的struct
RSDP是一个静态struct,可以在获取到vendor基址后直接展开。XSDT是一个由ACPI表头+n个系统描述表的指针组成的一个动态struct。
可以使用下面的方法,通过指针引用+表头的signature检查的方式,实现两者的解析:
typedef struct {
UINT64 Signature;
UINT8 Checksum;
UINT8 OemId[6];
UINT8 Revision;
UINT32 RsdtAddress;
UINT32 Length;
UINT64 XsdtAddress;
UINT8 ExtendedChecksum;
UINT8 Reserved[3];
} EFI_ACPI_6_6_ROOT_SYSTEM_DESCRIPTION_POINTER;
// 使用RSDP表获取XSDT表的地址
Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Rsdp->XsdtAddress;
// 计算出XSDT表的数目
NumberOfTableEntries =(Xsdt->Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) / sizeof (UINT64);
// 使用For循环来进行entry的遍历
for (Index = 0; Index < NumberOfTableEntries; Index++) {
// 根据当前Index,计算当前entry位置
CurrentTableEntry =
(VOID *)((UINT8 *)Xsdt + sizeof (EFI_ACPI_DESCRIPTION_HEADER) + Index * sizeof (UINT64));
// 根据当前entry值,转换为当前表指针
CurrentTablePointer = (UINTN)*(UINT64 *)CurrentTableEntry;
// 根据当前表指针,进行表头的struct解析
CurrentTable = (EFI_ACPI_DESCRIPTION_HEADER *)CurrentTablePointer;
// 使用一个ASCII字符比对函数,完成对FACP表的抓取
if (!AsciiStrnCmp ((CHAR8 *)&CurrentTable->Signature, "FACP", 4)) {
Fadt2Table = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *)(UINTN)
CurrentTablePointer;
Facs2Table = (EFI_ACPI_2_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *)(UINTN)
Fadt2Table->FirmwareCtrl;
DsdtTable = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Fadt2Table->Dsdt;
}
}XSDT找系统描述表并打印
在完成解析并由上文实现遍历后,可以在这个for循环中添加ConOut指针,来遵照循环去打印这一系列的信息:
for (Index = 0; Index < NumberOfTableEntries; Index++) {
// 获取当前表
CurrentTableEntry =
(VOID *)((UINT8 *)Xsdt + sizeof (EFI_ACPI_DESCRIPTION_HEADER) + Index * sizeof (UINT64));
CurrentTablePointer = (UINTN)*(UINT64 *)CurrentTableEntry;
CurrentTable = (EFI_ACPI_DESCRIPTION_HEADER *)CurrentTablePointer;
// 获取当前表signature字符
Sig = (CHAR8 *)&CurrentTable->Signature
// 调用ConOut打印出来
ConOut->OutputString(ConOut,Sig);
}相应的,你最好在ConIn的等待中,添加一个跟ACPI相符的定向命令行,用以开启这段遍历打印:
while (Running) {
// WaitForEvent:
Status = gBS->WaitForEvent(1, &ConIn->WaitForKey, &Index);
if (EFI_ERROR(Status)) {
return Status;
}
Status = ConIn->ReadKeyStroke(ConIn, &Key);
if (EFI_ERROR(Status)) {
ConOut->OutputString(ConOut,L"\nError reading key\n");
return Status;
}
if (Key.UnicodeChar != 0) {
// 这里可以触发一个动作函数
print_all_acpi();
}
}这里还需要进一步补充一个详细的按键处理机制,这个后面单独写文章说。