BIOS/UEFI基础——定时器

BIOS/UEFI基础——定时器

网络整理公知网——常识知识分享网站
所属专栏: UEFI开发基础 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiangwei0512/article/details/53572387

UEFI下的定时器简介

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

  //
  // Event & Timer Services
  //
  EFI_CREATE_EVENT                  CreateEvent;
  EFI_SET_TIMER                     SetTimer;
  EFI_WAIT_FOR_EVENT                WaitForEvent;
  EFI_SIGNAL_EVENT                  SignalEvent;
  EFI_CLOSE_EVENT                   CloseEvent;
  EFI_CHECK_EVENT                   CheckEvent;
在Architecture Protocols中有一个是

EFI_GUID  gEfiTimerArchProtocolGuid = EFI_TIMER_ARCH_PROTOCOL_GUID;
这些都是跟定时器直接相关的。

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

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


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

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

  //
  // Install the Timer Architectural Protocol onto a new handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &mTimerHandle,
                  &gEfiTimerArchProtocolGuid, &mTimer,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

定时器的初始化

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

这部分内容在BIOS/UEFI基础——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寄存器:就是定时增加的计数器;

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

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

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

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

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


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


定时器的应用

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

    //
    // Initialize I/O APIC entry for HPET Timer Interrupt
    //   Fixed Delivery Mode, Level Triggered, Asserted Low
    //
    IoApicConfigureInterrupt (mTimerIrq, PcdGet8 (PcdHpetLocalApicVector), IO_APIC_DELIVERY_MODE_LOWEST_PRIORITY, TRUE, FALSE);
上述的代码配置了中断向量;

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

  //
  // Install interrupt handler for selected HPET Timer
  //
  Status = mCpu->RegisterInterruptHandler (mCpu, PcdGet8 (PcdHpetLocalApicVector), TimerInterruptHandler);
下面就要研究这个中断处理函数TimerInterruptHandler(),在这个函数中,会周期性地调用如下的代码:

    //
    // Call registered notification function passing in the time since the last
    // interrupt in 100 ns units.
    //    
    mTimerNotifyFunction (TimerPeriod);
而这里的mTimerNotifyFunction是通过TimerDriverRegisterHandler()接口注册的。

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

///
/// The Timer Architectural Protocol that this driver produces.
///
EFI_TIMER_ARCH_PROTOCOL  mTimer = {
  TimerDriverRegisterHandler,
  TimerDriverSetTimerPeriod,
  TimerDriverGetTimerPeriod,
  TimerDriverGenerateSoftInterrupt
};
这里的TimerDriverRegisterHandler就是EFI_TIMER_ARCH_PROTOCOL.RegisterHandler(),它会在该Architecture Protocol安装后回调。

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

  //
  // Do special operations for Architectural Protocols
  //

  if (CompareGuid (Entry->ProtocolGuid, &gEfiTimerArchProtocolGuid)) {
    //
    // Register the Core timer tick handler with the Timer AP
    //
    gTimer->RegisterHandler (gTimer, CoreTimerTick);
  }
这样的话,相当于CoreTimerTick()函数会被定时调用:

/**
  Called by the platform code to process a tick.

  @param  Duration               The number of 100ns elasped since the last call
                                 to TimerTick

**/
VOID
EFIAPI
CoreTimerTick (
  IN UINT64   Duration
  );

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

  //
  // If the head of the list is expired, fire the timer event
  // to process it
  //
  if (!IsListEmpty (&mEfiTimerList)) {
    Event = CR (mEfiTimerList.ForwardLink, IEVENT, Timer.Link, EVENT_SIGNATURE);

    if (Event->Timer.TriggerTime <= mEfiSystemTime) {
      CoreSignalEvent (mEfiCheckTimerEvent);
    }
  }

这里有几点需要说明:

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

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

    if (Event2->Timer.TriggerTime > TriggerTime) {
      break;
    }
  }

  InsertTailList (Link, &Event->Timer.Link);
2. 这里并没有直接Signal获取到的事件,而是Signal了另外的一个全局事件mEfiCheckTimerEvent这个全局的事件如下:

/**
  Initializes timer support.

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

  Status = CoreCreateEventInternal (
             EVT_NOTIFY_SIGNAL,
             TPL_HIGH_LEVEL - 1,
             CoreCheckTimers,
             NULL,
             NULL,
             &mEfiCheckTimerEvent
             );
  ASSERT_EFI_ERROR (Status);
}
也就是说执行的是函数CoreCheckTimers(),在这个函数中会遍历全局事件列表mEfiTimerList,并触发已经到时间的事件。

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


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

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

/**
  Sets the type of timer and the trigger time for a timer event.

  @param[in]  Event             The timer event that is to be signaled at the specified time.
  @param[in]  Type              The type of time that is specified in TriggerTime.
  @param[in]  TriggerTime       The number of 100ns units until the timer expires.
                                A TriggerTime of 0 is legal.
                                If Type is TimerRelative and TriggerTime is 0, then the timer
                                event will be signaled on the next timer tick.
                                If Type is TimerPeriodic and TriggerTime is 0, then the timer
                                event will be signaled on every timer tick.

  @retval EFI_SUCCESS           The event has been set to be signaled at the requested time.
  @retval EFI_INVALID_PARAMETER Event or Type is not valid.

**/
typedef
EFI_STATUS
(EFIAPI *EFI_SET_TIMER)(
  IN  EFI_EVENT                Event,
  IN  EFI_TIMER_DELAY          Type,
  IN  UINT64                   TriggerTime
  );
它是gBS的一个接口,其实现如下:

/**
  Sets the type of timer and the trigger time for a timer event.

  @param  UserEvent              The timer event that is to be signaled at the
                                 specified time
  @param  Type                   The type of time that is specified in
                                 TriggerTime
  @param  TriggerTime            The number of 100ns units until the timer
                                 expires

  @retval EFI_SUCCESS            The event has been set to be signaled at the
                                 requested time
  @retval EFI_INVALID_PARAMETER  Event or Type is not valid

**/
EFI_STATUS
EFIAPI
CoreSetTimer (
  IN EFI_EVENT            UserEvent,
  IN EFI_TIMER_DELAY      Type,
  IN UINT64               TriggerTime
  )
在这个实现中,事件EFI_EVENT会被转化为类型IEVENT *。

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

typedef struct {
  UINTN                   Signature;
  UINT32                  Type;
  UINT32                  SignalCount;
  ///
  /// Entry if the event is registered to be signalled
  ///
  LIST_ENTRY              SignalLink;
  ///
  /// Notification information for this event
  ///
  EFI_TPL                 NotifyTpl;
  EFI_EVENT_NOTIFY        NotifyFunction;
  VOID                    *NotifyContext;
  EFI_GUID                EventGroup;
  LIST_ENTRY              NotifyLink;
  UINT8                   ExFlag;
  ///
  /// A list of all runtime events
  ///
  EFI_RUNTIME_EVENT_ENTRY RuntimeData;
  TIMER_EVENT_INFO        Timer;
} IEVENT;

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

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

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




本页内容来自网络整理和网友上传,若有侵权请联系站长在24小时内删除

Copyright @ 2018-2019 All Rights Reserved.

联系QQ:836922329