基于EDK2框架使用HPET计时器的技术实现问询
EDK2下HPET时间获取与中断倒计时实现方案
一、HPET时间获取(基于现有代码优化)
你的现有代码已实现HPET ACPI表查找与寄存器读取,但UEFI环境下直接访问物理地址存在风险,需先映射到虚拟地址。以下是优化后的完整实现:
1. 查找HPET ACPI表
#include <Uefi.h> #include <Library/UefiLib.h> #include <Protocol/AcpiTable.h> #include <IndustryStandard/Acpi.h> EFI_STATUS FindHpetTable( EFI_ACPI_HIGH_PRECISION_EVENT_TIMER_TABLE_HEADER **HpetTable) { EFI_STATUS Status; EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp; EFI_ACPI_DESCRIPTION_HEADER *Xsdt; UINTN EntryCount; UINTN Index; // 获取ACPI 2.0 RSDP表 Status = EfiGetSystemConfigurationTable( &gEfiAcpi20TableGuid, (VOID **)&Rsdp); if (EFI_ERROR(Status)) { return Status; } // 定位XSDT表(64位ACPI表入口) Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Rsdp->XsdtAddress; EntryCount = (Xsdt->Length - sizeof(EFI_ACPI_DESCRIPTION_HEADER)) / sizeof(UINT64); // 遍历XSDT表项,查找HPET表 for (Index = 0; Index < EntryCount; Index++) { EFI_ACPI_DESCRIPTION_HEADER *CurrentTable = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)(((UINT64 *)(Xsdt + 1))[Index]); if (CurrentTable->Signature == EFI_ACPI_3_0_HIGH_PRECISION_EVENT_TIMER_TABLE_SIGNATURE) { *HpetTable = (EFI_ACPI_HIGH_PRECISION_EVENT_TIMER_TABLE_HEADER *)CurrentTable; return EFI_SUCCESS; } } return EFI_NOT_FOUND; }
2. 安全访问HPET寄存器
#include <Library/MemoryAllocationLib.h> #include <IndustryStandard/Hpet.h> VOID AccessHpetRegisters( EFI_ACPI_HIGH_PRECISION_EVENT_TIMER_TABLE_HEADER *HpetTable) { if (HpetTable == NULL) { return; } EFI_PHYSICAL_ADDRESS HpetPhysicalBase = HpetTable->BaseAddressLower32Bit.Address; VOID *HpetVirtualBase; EFI_STATUS Status; // 将HPET物理地址映射到虚拟地址(UEFI环境必须步骤) Status = MmMapIoSpace(HpetPhysicalBase, 0x1000, EfiMemoryMappedIO, &HpetVirtualBase); if (EFI_ERROR(Status)) { Print(L"HPET地址映射失败: %r\n", Status); return; } // 读取通用能力与ID寄存器 UINT64 CapabilitiesAndId = *(volatile UINT64 *)HpetVirtualBase; Print(L"HPET通用能力与ID寄存器: 0x%lX\n", CapabilitiesAndId); // 读取主计数器当前值 UINT64 MainCounterValue = *(volatile UINT64 *)((UINTN)HpetVirtualBase + HPET_MAIN_COUNTER_OFFSET); Print(L"HPET主计数器值: 0x%lX\n", MainCounterValue); // 读取计数器周期(单位:飞秒) UINT32 HpetPeriod = *(volatile UINT32 *)((UINTN)HpetVirtualBase + 0x4); Print(L"HPET计数器周期: %u 飞秒\n", HpetPeriod); // 计算已流逝的微秒数:(主计数器值 × 周期) ÷ 1e12(1微秒=1e12飞秒) UINT64 TimeUs = DivU64x64Remainder(MultU64x64(MainCounterValue, HpetPeriod), 1000000000000ULL, NULL); Print(L"已流逝时间(微秒): %llu\n", TimeUs); // 解除地址映射 MmUnmapIoSpace(HpetVirtualBase, 0x1000); }
3. 在UefiMain中调用
EFI_STATUS EFIAPI UefiMain( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_ACPI_HIGH_PRECISION_EVENT_TIMER_TABLE_HEADER *HpetTable; Status = FindHpetTable(&HpetTable); if (EFI_ERROR(Status)) { Print(L"未找到HPET表: %r\n", Status); return Status; } AccessHpetRegisters(HpetTable); return EFI_SUCCESS; }
二、利用HPET中断实现倒计时器
方案1:直接操作HPET寄存器
HPET包含多个独立定时器,可配置为一次性中断模式实现倒计时:
#include <Protocol/Cpu.h> // 全局变量保存CPU协议与HPET虚拟地址 EFI_CPU_ARCH_PROTOCOL *mCpuArchProtocol = NULL; VOID *mHpetVirtualBase = NULL; // HPET中断处理函数 EFI_STATUS EFIAPI HpetCountdownHandler( IN EFI_EXCEPTION_TYPE InterruptType, IN VOID *SystemContext ) { Print(L"倒计时结束!\n"); // 清除定时器0的中断标志 *(volatile UINT64 *)((UINTN)mHpetVirtualBase + 0x100) |= HPET_TIMER_CONFIG_TN_INT_STATUS; return EFI_SUCCESS; } // 初始化HPET倒计时(参数:倒计时秒数) EFI_STATUS SetupHpetCountdownDirect(UINT64 Seconds, EFI_ACPI_HIGH_PRECISION_EVENT_TIMER_TABLE_HEADER *HpetTable) { EFI_STATUS Status; UINT64 HpetPhysicalBase = HpetTable->BaseAddressLower32Bit.Address; UINT32 HpetPeriod; UINT64 CurrentCounter; UINT64 CompareValue; // 映射HPET地址 Status = MmMapIoSpace(HpetPhysicalBase, 0x1000, EfiMemoryMappedIO, &mHpetVirtualBase); if (EFI_ERROR(Status)) return Status; // 读取计数器周期 HpetPeriod = *(volatile UINT32 *)((UINTN)mHpetVirtualBase + 0x4); // 启动HPET主计数器 *(volatile UINT64 *)((UINTN)mHpetVirtualBase + 0x10) |= HPET_CONFIG_ENABLE; // 获取当前主计数器值 CurrentCounter = *(volatile UINT64 *)((UINTN)mHpetVirtualBase + HPET_MAIN_COUNTER_OFFSET); // 计算倒计时对应的比较值:当前值 + (秒数 × 1e15飞秒) / 周期 CompareValue = CurrentCounter + DivU64x64Remainder(Seconds * 1000000000000000ULL, HpetPeriod, NULL); // 获取CPU架构协议 Status = gBS->LocateProtocol(&gEfiCpuArchProtocolGuid, NULL, (VOID**)&mCpuArchProtocol); if (EFI_ERROR(Status)) goto UnmapExit; // 注册中断处理函数(HPET定时器0默认中断向量需根据ACPI表确认,这里假设为0x20) Status = mCpuArchProtocol->RegisterInterruptHandler(mCpuArchProtocol, 0x20, HpetCountdownHandler); if (EFI_ERROR(Status)) goto UnmapExit; // 配置定时器0:一次性模式、启用中断、设置中断路由 *(volatile UINT64 *)((UINTN)mHpetVirtualBase + 0x100) = HPET_TIMER_CONFIG_TN_TYPE_ONESHOT | HPET_TIMER_CONFIG_TN_INT_ENABLE; // 设置比较值 *(volatile UINT64 *)((UINTN)mHpetVirtualBase + 0x108) = CompareValue; // 启用定时器0 *(volatile UINT64 *)((UINTN)mHpetVirtualBase + 0x100) |= HPET_TIMER_CONFIG_TN_ENABLE; return EFI_SUCCESS; UnmapExit: MmUnmapIoSpace(mHpetVirtualBase, 0x1000); return Status; }
方案2:使用PcAtChipsetPkg的HpetTimerDxe协议
该驱动提供封装好的EFI_HPET_TIMER_PROTOCOL,无需直接操作寄存器:
#include <Protocol/HpetTimer.h> EFI_HPET_TIMER_PROTOCOL *mHpetTimerProtocol = NULL; EFI_CPU_ARCH_PROTOCOL *mCpuArchProtocol = NULL; // 中断处理函数 EFI_STATUS EFIAPI HpetProtocolHandler( IN EFI_EXCEPTION_TYPE InterruptType, IN VOID *SystemContext ) { Print(L"倒计时结束!\n"); mHpetTimerProtocol->ClearInterrupt(mHpetTimerProtocol, 0); // 清除定时器0中断 return EFI_SUCCESS; } // 用协议设置倒计时 EFI_STATUS SetupHpetCountdownProtocol(UINT64 Seconds) { EFI_STATUS Status; UINT64 TimerPeriod; UINT64 CurrentCounter; UINT64 CompareValue; UINTN InterruptVector; // 查找HPET定时器协议 Status = gBS->LocateProtocol(&gEfiHpetTimerProtocolGuid, NULL, (VOID**)&mHpetTimerProtocol); if (EFI_ERROR(Status)) return Status; // 查找CPU架构协议 Status = gBS->LocateProtocol(&gEfiCpuArchProtocolGuid, NULL, (VOID**)&mCpuArchProtocol); if (EFI_ERROR(Status)) return Status; // 获取HPET计数器周期 Status = mHpetTimerProtocol->GetTimerPeriod(mHpetTimerProtocol, &TimerPeriod); if (EFI_ERROR(Status)) return Status; // 获取当前主计数器值 Status = mHpetTimerProtocol->GetCounter(mHpetTimerProtocol, &CurrentCounter); if (EFI_ERROR(Status)) return Status; // 计算比较值 CompareValue = CurrentCounter + DivU64x64Remainder(Seconds * 1000000000000000ULL, TimerPeriod, NULL); // 获取定时器0的中断向量 Status = mHpetTimerProtocol->GetTimerInterruptVector(mHpetTimerProtocol, 0, &InterruptVector); if (EFI_ERROR(Status)) return Status; // 注册中断处理函数 Status = mCpuArchProtocol->RegisterInterruptHandler(mCpuArchProtocol, InterruptVector, HpetProtocolHandler); if (EFI_ERROR(Status)) return Status; // 配置定时器为一次性模式、启用中断 Status = mHpetTimerProtocol->SetTimerConfiguration(mHpetTimerProtocol, 0, HPET_TIMER_TYPE_ONESHOT | HPET_TIMER_ENABLE_INTERRUPT); if (EFI_ERROR(Status)) return Status; // 设置比较值 Status = mHpetTimerProtocol->SetCompareValue(mHpetTimerProtocol, 0, CompareValue); if (EFI_ERROR(Status)) return Status; // 启动定时器 Status = mHpetTimerProtocol->EnableTimer(mHpetTimerProtocol, 0, TRUE); if (EFI_ERROR(Status)) return Status; return EFI_SUCCESS; }
在UefiMain中调用协议方案
EFI_STATUS EFIAPI UefiMain( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; // 设置5秒倒计时 Status = SetupHpetCountdownProtocol(5); if (EFI_ERROR(Status)) { Print(L"倒计时设置失败: %r\n", Status); return Status; } // 等待中断触发,进入事件循环 while (TRUE) { gBS->Stall(1000000); // 每秒停顿一次,降低资源占用 } return EFI_SUCCESS; }
三、关键注意事项
- INF文件配置:需添加依赖包与协议,示例片段:
[Packages] MdePkg/MdePkg.dec PcAtChipsetPkg/PcAtChipsetPkg.dec [LibraryClasses] UefiLib MemoryAllocationLib BaseLib UefiApplicationEntryPoint [Protocols] gEfiAcpi20TableGuid gEfiHpetTimerProtocolGuid gEfiCpuArchProtocolGuid - 权限与中断:确保系统已启用中断,部分平台需在BIOS中开启HPET支持。
- 错误处理:所有UEFI函数调用必须检查返回值,避免未定义行为。
内容的提问来源于stack exchange,提问作者Kaiser
相关产品推荐
相关产品推荐

