Step to UEFI (218)UEFI Shell下读取 IoApic

最近在编写一个Shell 下读取 IoApic 的工具,首先实验直接调用 IoApicLib来实现,为此,在 AppPkg.dsc 中加入下面三个 Lib:

 IoApicLib|PcAtChipsetPkg/Library/BaseIoApicLib/BaseIoApicLib.inf
 LocalApicLib|UefiCpuPkg/Library/BaseXApicLib/BaseXApicLib.inf
 TimerLib|PcAtChipsetPkg/Library/AcpiTimerLib/DxeAcpiTimerLib.inf

之后在自己的App 中引用IoApicLib。但是这样编译出来的EFI 在运行期会死机,甚至没有运行到 Application第一行就会死机。经过研究发现在 DxeAcpiTimerLib.inf 中有如下设定:

CONSTRUCTOR                    = DxeAcpiTimerLibConstructor

因此,DxeAcpiTimerLibConstructor会先于我们的Application入口执行,其中的代码会导致运行时死机(但是具体原因我没有分析)。

所以,需要手工编写一个读取的工具。

首先,研究一下读取的方法,根据【参考1】给出的指引,在内存中有暴露出2个内存地址作为 Index 和Data,在目前我们的电脑上都是 0xFEC0 0000h

和0xFEC 0010h. 具体的读取操作是在 Index 给出的内存地址写入要访问的寄存器索引,之后 Data 给出的内存位置就是这个寄存器的值。

IoApic Index

具体的寄存器分布如下:

IoApic Version Register

上面的 01h(IOAPICVER) 中会给出来 Redirection Table 的实际数量,Spec 中给出只有24个,但是现在通常会多于这个数量。

最终的代码如下(其中的 IoApicLib.h 等代码也都是直接从 ED2 代码中提取组合而成):

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/DebugLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/IoLib.h>
#include <Library/ShellLib.h>
#include "IoApicLib.h"

INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
    IO_APIC_VERSION_REGISTER         Version;
    IO_APIC_REDIRECTION_TABLE_ENTRY  Entry;
    UINTN       Irq;

    ShellSetPageBreakMode(TRUE);
    
    Version.Uint32 = IoApicRead (IO_APIC_VERSION_REGISTER_INDEX);
    Print(L"Version  : %X \n",Version.Bits.Version);
    Print(L"Max Entry: %X \n",Version.Bits.MaximumRedirectionEntry);
    
    for (Irq=0;Irq<Version.Bits.MaximumRedirectionEntry; Irq++) {
        Entry.Uint32.Low = IoApicRead (IO_APIC_REDIRECTION_TABLE_ENTRY_INDEX + Irq * 2);
        Entry.Uint32.High = IoApicRead(IO_APIC_REDIRECTION_TABLE_ENTRY_INDEX + Irq * 2+1);
        Print(L"Int%02X: %08X %08X \n",Irq,Entry.Uint32.Low,Entry.Uint32.High);
    }

    return EFI_SUCCESS;
}

运行结果:

IceLake-U 运行结果

如果觉得上面的读取方式过于复杂,还可以直接使用 MMIO 的方式来访问,通过调用 IoLib 的方式实现 MmIo 的访问代码非常简单:

    for (Irq=0;Irq<Version.Bits.MaximumRedirectionEntry; Irq++) {
            MmioWrite8 (0xFEC00000, IO_APIC_REDIRECTION_TABLE_ENTRY_INDEX+(UINT8)Irq*2);
            Entry.Uint32.Low=MmioRead32 (0xFEC00000+0x10);
            MmioWrite8 (0xFEC00000, IO_APIC_REDIRECTION_TABLE_ENTRY_INDEX+(UINT8)Irq*2+1);
            Entry.Uint32.High=MmioRead32 (0xFEC00000+0x10);   
            Print(L"%08X %08X \n",Entry.Uint32.Low,Entry.Uint32.High);            
}

在实体机上这两种方法没有差别,结果是相同的。完整代码和 X64 EFI 可以在这里下载:

发表评论

电子邮件地址不会被公开。 必填项已用*标注