【UEFI基础】定时器

心已赠人 2023-10-17 09:41 157阅读 0赞

UEFI下的定时器简介

在【UEFI基础】System Table和Architecture Protocols中有提到,在Boot Service Table中有如下的接口:

  1. //
  2. // Event & Timer Services
  3. //
  4. EFI_CREATE_EVENT CreateEvent;
  5. EFI_SET_TIMER SetTimer;
  6. EFI_WAIT_FOR_EVENT WaitForEvent;
  7. EFI_SIGNAL_EVENT SignalEvent;
  8. EFI_CLOSE_EVENT CloseEvent;
  9. EFI_CHECK_EVENT CheckEvent;

在Architecture Protocols中有一个是

  1. EFI_GUID gEfiTimerArchProtocolGuid = EFI_TIMER_ARCH_PROTOCOL_GUID;

这些都是跟定时器直接相关的。

UEFI对应Intel的平台下使用的定时器有8254和HPET两种:

20161211121023475

目前比较新和常用的是HPET定时器,本文后续以它为主要的介绍对象。

UEFI架构下的事件,轮询等机制都需要依赖于定时器。

定时器的初始化在DXE阶段,定时器初始化的结束标志就是安装了上述的Architectural Protocol:

  1. //
  2. // Install the Timer Architectural Protocol onto a new handle
  3. //
  4. Status = gBS->InstallMultipleProtocolInterfaces (
  5. &mTimerHandle,
  6. &gEfiTimerArchProtocolGuid, &mTimer,
  7. NULL
  8. );
  9. ASSERT_EFI_ERROR (Status);

定时器的初始化

下面主要讲的是HPET的初始化。

这部分内容在【x86架构】中断基础介绍中也有提到,HPET依赖的是I/O APIC或者MSI。

顺便说一句,8254定时器依赖的是8259中断控制器。

总的来说就是定时器实际上依赖的是定时触发的中断。

首先说明下HPET的全称是High Precision Event Timer。

然后介绍一下HPET对应的寄存器,如下表所示:







































































偏移地址 寄存器 尺寸 备注
000h-007h ID 64位 HPET的能力和ID
010h-017h HPET Configure 64位 HPET总配置寄存器
020h-027h HPET Satus 64位 中断状态寄存器
0F0h-0F7h HPET Main Counter 64位 HPET的计数器
100h-107h Timer #0 Configure 64位 定时器 #0
108h-10Fh Timer #0 Comparator 64位
120h-127h Timer #1 Configure 64位 定时器 #1
128h-12Fh Timer #1 Comparator 64位
定时器有多组,中间略
1E0h-1E7h Timer #N Configure 64位 定时器 #N
1E8h-1EFh Timer #N Comparator 64位

这里的定时器寄存器主要分为全局和局部的两类。下面具体介绍这些寄存器:

  1. ID寄存器:这个寄存器是只读的,高32位是HPET Main Counter的计数频率,如果得到的值是xx,就表示xxfs(飞秒)计数1次;

  2. Configure寄存器:它有两个配置位,BIT0是Overral Enable,只有它设置了1,HPET Main Counter才会计数;BIT1是Legacy Replacement Route,它设置为1时,Timer #0和Timer #1的使用时固定的;

  3. Status寄存器:记录每个Timer的中断状态;

  4. Main Counter寄存器:就是定时增加的计数器;

  5. Timer#x Comparator寄存器:这里的“比较“指的是跟Main Counter寄存器进行的比较,这个值需要我们自己设置,比如我们设置了100,而Main Counter寄存器从0涨到100之后,就会触发中断,(然后Comparator的值自动升到200,当Main Counter涨到200时又触发中断,这里的前提是这个Timer支持周期触发);

  6. Timer#x Configure寄存器:关于每个Timer的配置,这个配置根据Timer的不同也不一定一致。

以上的介绍比较简单,更详细的介绍还是需要参考对应平台的EDS手册。

另外上表中也没有写出HPET的MSI中断相关寄存器(应该是在每个Timer的Comparator寄存器之后,具体因为没有手册没法确定)。

下面是HPET初始化的一个简单流程:

20161212215509441

具体的代码参考EDK2源码中的PcAtChipsetPkg\HpetTimerDxe\HpetTimer.c。

定时器的应用

定时器的应用要从定时器的初始化代码中开始说起:

  1. //
  2. // Initialize I/O APIC entry for HPET Timer Interrupt
  3. // Fixed Delivery Mode, Level Triggered, Asserted Low
  4. //
  5. IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);

上述的代码配置了中断向量;

而下面的代码又为中断向量设置了中断处理函数:

  1. //
  2. // Install interrupt handler for selected HPET Timer
  3. //
  4. Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler);

下面就要研究这个中断处理函数TimerInterruptHandler(),在这个函数中,会周期性地调用如下的代码:

  1. //
  2. // Call registered notification function passing in the time since the last
  3. // interrupt in 100 ns units.
  4. //
  5. mTimerNotifyFunction (TimerPeriod);

而这里的mTimerNotifyFunction是通过TimerDriverRegisterHandler()接口注册的。

这个接口属于EFI_TIMER_ARCH_PROTOCOL的一部分:

  1. ///
  2. /// The Timer Architectural Protocol that this driver produces.
  3. ///
  4. EFI_TIMER_ARCH_PROTOCOL mTimer = {
  5. TimerDriverRegisterHandler,
  6. TimerDriverSetTimerPeriod,
  7. TimerDriverGetTimerPeriod,
  8. TimerDriverGenerateSoftInterrupt
  9. };

这里的TimerDriverRegisterHandler就是EFI_TIMER_ARCH_PROTOCOL.RegisterHandler(),它会在该Architecture Protocol安装后回调。

具体的执行位置是DxeProtocolNotify.c中的GenericProtocolNotify()函数,这个函数会在每个Architecture Protocol安装后调用,而对于EFI_TIMER_ARCH_PROTOCOL对应到如下的代码:

  1. //
  2. // Do special operations for Architectural Protocols
  3. //
  4. if (CompareGuid (Entry->ProtocolGuid, &gEfiTimerArchProtocolGuid)) {
  5. //
  6. // Register the Core timer tick handler with the Timer AP
  7. //
  8. gTimer->RegisterHandler (gTimer, CoreTimerTick);
  9. }

这样的话,相当于CoreTimerTick()函数会被定时调用:

  1. /**
  2. Called by the platform code to process a tick.
  3. @param Duration The number of 100ns elasped since the last call
  4. to TimerTick
  5. **/
  6. VOID
  7. EFIAPI
  8. CoreTimerTick (
  9. IN UINT64 Duration
  10. );

在CoreTimerTick()函数中会从mEfiTimerList中获取第一个事件,并查看事件是否到达了触发的时间,如果是,就触发mEfiCheckTimerEvent事件。

  1. //
  2. // If the head of the list is expired, fire the timer event
  3. // to process it
  4. //
  5. if (!IsListEmpty (&mEfiTimerList)) {
  6. Event = CR (mEfiTimerList.ForwardLink, IEVENT, Timer.Link, EVENT_SIGNATURE);
  7. if (Event->Timer.TriggerTime <= mEfiSystemTime) {
  8. CoreSignalEvent (mEfiCheckTimerEvent);
  9. }
  10. }

这里有几点需要说明:

  1. 为什么只取第一个来查看,这是因为事件列表mEfiTimerList是按照时间顺序排列,第一个事件肯定是最先需要执行的,具体可以看CoreInsertEventTimer()函数:

    //
    // Insert the timer into the timer database in assending sorted order
    //
    for (Link = mEfiTimerList.ForwardLink; Link != &mEfiTimerList; Link = Link->ForwardLink) {

    1. Event2 = CR (Link, IEVENT, Timer.Link, EVENT_SIGNATURE);
    2. if (Event2->Timer.TriggerTime > TriggerTime) {
    3. break;
    4. }

    }

    InsertTailList (Link, &Event->Timer.Link);

  2. 这里并没有直接Signal获取到的事件,而是Signal了另外的一个全局事件mEfiCheckTimerEvent这个全局的事件如下:

    /**
    Initializes timer support.

    **/
    VOID
    CoreInitializeTimer (
    VOID
    )
    {
    EFI_STATUS Status;

    Status = CoreCreateEventInternal (

    1. EVT_NOTIFY_SIGNAL,
    2. TPL_HIGH_LEVEL - 1,
    3. CoreCheckTimers,
    4. NULL,
    5. NULL,
    6. &mEfiCheckTimerEvent
    7. );

    ASSERT_EFI_ERROR (Status);
    }

也就是说执行的是函数CoreCheckTimers(),在这个函数中会遍历全局事件列表mEfiTimerList,并触发已经到时间的事件。

以上就是整个通过定时器触发事件的流程。

事件跟定时器的关系密切,尤其是定时事件。

为一个事件添加定时器,使用的是如下的接口:

  1. /**
  2. Sets the type of timer and the trigger time for a timer event.
  3. @param[in] Event The timer event that is to be signaled at the specified time.
  4. @param[in] Type The type of time that is specified in TriggerTime.
  5. @param[in] TriggerTime The number of 100ns units until the timer expires.
  6. A TriggerTime of 0 is legal.
  7. If Type is TimerRelative and TriggerTime is 0, then the timer
  8. event will be signaled on the next timer tick.
  9. If Type is TimerPeriodic and TriggerTime is 0, then the timer
  10. event will be signaled on every timer tick.
  11. @retval EFI_SUCCESS The event has been set to be signaled at the requested time.
  12. @retval EFI_INVALID_PARAMETER Event or Type is not valid.
  13. **/
  14. typedef
  15. EFI_STATUS
  16. (EFIAPI *EFI_SET_TIMER)(
  17. IN EFI_EVENT Event,
  18. IN EFI_TIMER_DELAY Type,
  19. IN UINT64 TriggerTime
  20. );

它是gBS的一个接口,其实现如下:

  1. /**
  2. Sets the type of timer and the trigger time for a timer event.
  3. @param UserEvent The timer event that is to be signaled at the
  4. specified time
  5. @param Type The type of time that is specified in
  6. TriggerTime
  7. @param TriggerTime The number of 100ns units until the timer
  8. expires
  9. @retval EFI_SUCCESS The event has been set to be signaled at the
  10. requested time
  11. @retval EFI_INVALID_PARAMETER Event or Type is not valid
  12. **/
  13. EFI_STATUS
  14. EFIAPI
  15. CoreSetTimer (
  16. IN EFI_EVENT UserEvent,
  17. IN EFI_TIMER_DELAY Type,
  18. IN UINT64 TriggerTime
  19. )

在这个实现中,事件EFI_EVENT会被转化为类型IEVENT *。

EFI_EVENT的类型是VOID *,而IEVENT的类型如下:

  1. typedef struct {
  2. UINTN Signature;
  3. UINT32 Type;
  4. UINT32 SignalCount;
  5. ///
  6. /// Entry if the event is registered to be signalled
  7. ///
  8. LIST_ENTRY SignalLink;
  9. ///
  10. /// Notification information for this event
  11. ///
  12. EFI_TPL NotifyTpl;
  13. EFI_EVENT_NOTIFY NotifyFunction;
  14. VOID *NotifyContext;
  15. EFI_GUID EventGroup;
  16. LIST_ENTRY NotifyLink;
  17. UINT8 ExFlag;
  18. ///
  19. /// A list of all runtime events
  20. ///
  21. EFI_RUNTIME_EVENT_ENTRY RuntimeData;
  22. TIMER_EVENT_INFO Timer;
  23. } IEVENT;

这里需要注意一下类型的转换,实际上gBs->CreateEvent()创建的事件的实际类型就是IEVENT *,只不过后来转换成了VOID *。

在CoreSetTimer()函数中对应的IEVENT会被加载进全局变量mEfiTimerList。

这样该事件也就加入了全局的定时器循环事件的过程中。

发表评论

表情:
评论列表 (有 0 条评论,157人围观)

还没有评论,来说两句吧...

相关阅读

    相关 UEFI基础】GPT

    简介 GPT全称是GUID Partition Table,是硬盘分区的一种格式。 硬盘分区格式有两种,一种是MBR,另一种是GPT。 GPT是随著UEFI引入了,U