前回でイベントの作成方法がわかったので、次はこれを使ってタイマー処理を行ってみます。

イベントを一定時間後、もしくは一定時間ごとに発火させるには、EFI_BOOT_SERVICES.SetTimer()を使います。

typedef
EFI_STATUS
SetTimer (
  IN EFI_EVENT       Event,
  IN EFI_TIMER_DELAY Type,
  IN UINT64          TriggerTime
  );

ここで、Typeはセットするタイマーの種類であり、以下の3つのうちのいずれか1つを設定します。

  • TimerCancel: Eventに設定されているタイマーをキャンセル
  • TimerPeriodic: 一定時間ごとにEventを発火
  • TimerRelative: 一定時間後に一度だけ発火

また、TriggerTimeは100ns単位の時間を指定します。

実際の使用例がこちら。

#include <Uefi.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiLib.h>

#define USEC 10
#define MSEC (1000 * USEC)
#define SEC (1000 * MSEC)

EFI_HANDLE gIH;
EFI_SYSTEM_TABLE *gST;
EFI_BOOT_SERVICES *gBS;

typedef struct EVENT_CONTEXT {
  UINTN Count;
} EVENT_CONTEXT;

EFI_EVENT TimerStopped;

void
EFIAPI
EventNotify (
  IN EFI_EVENT Event,
  IN VOID *Context
  )
{
  EVENT_CONTEXT *EventContext = Context;
  EventContext->Count++;
  Print(L"Event signaled; Count = %d\n", EventContext->Count);
  if (EventContext->Count >= 10) {
    gBS->SetTimer(Event, TimerCancel, 0);
    gBS->SignalEvent(TimerStopped);
  }
  return;
}

EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  gIH = ImageHandle;
  gST = SystemTable;
  gBS = SystemTable->BootServices;

  gBS->CreateEvent(0, TPL_CALLBACK, NULL, NULL, &TimerStopped);

  EFI_EVENT TimerEvent;
  EVENT_CONTEXT EventContext = { .Count = 0 };
  gBS->CreateEvent(
    EVT_TIMER | EVT_NOTIFY_SIGNAL,
    TPL_CALLBACK,
    EventNotify,
    &EventContext,
    &TimerEvent
    );

  gBS->SetTimer(TimerEvent, TimerPeriodic, 500 * MSEC);

  UINTN EventIndex;
  gBS->WaitForEvent(1, &TimerStopped, &EventIndex);

  Print(L"Timer Stopped.\n");

  gBS->CloseEvent(TimerEvent);
  gBS->CloseEvent(TimerStopped);
  return EFI_SUCCESS;
}

500ミリ秒ごとにTimerEventを発火して、10回発火されたらTimerEventの繰り返しを止め、TimerStoppedを発火しています。

main関数はタイマーをセットしたあと、TimerStoppedが来るまで待ち、その後終了します。

実行結果:

/img/post/2017-07-04-timer.png