You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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;
}

三、关键注意事项

  1. INF文件配置:需添加依赖包与协议,示例片段:
    [Packages]
    MdePkg/MdePkg.dec
    PcAtChipsetPkg/PcAtChipsetPkg.dec
    
    [LibraryClasses]
    UefiLib
    MemoryAllocationLib
    BaseLib
    UefiApplicationEntryPoint
    
    [Protocols]
    gEfiAcpi20TableGuid
    gEfiHpetTimerProtocolGuid
    gEfiCpuArchProtocolGuid
    
  2. 权限与中断:确保系统已启用中断,部分平台需在BIOS中开启HPET支持。
  3. 错误处理:所有UEFI函数调用必须检查返回值,避免未定义行为。

内容的提问来源于stack exchange,提问作者Kaiser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 05:32:05