最近在编写一个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 给出的内存位置就是这个寄存器的值。
具体的寄存器分布如下:
上面的 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;
}
运行结果:
如果觉得上面的读取方式过于复杂,还可以直接使用 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 可以在这里下载:
它這個是使用CPU去進行計時,所以它是TSC嗎,因為我看文件沒有提及TSC的樣子。
这个程序没有使用 TSC 啊