菜单

kaoqi
kaoqi
发布于 2026-09-15 / 4 阅读
0
0

EFI调用ACPI表并打印出来

在之前的文章中已经成功实现了基础的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();
  }
}

这里还需要进一步补充一个详细的按键处理机制,这个后面单独写文章说。


评论