UEFIはデフォルトでイベント処理機能を提供しており、これを利用して簡単に非同期処理などを行うことができます。

UEFIにおけるイベントを表すEFI_EVENTは、EFI_BOOT_SERVICES.CreateEvent()を使って作ることができます。

typedef
EFI_STATUS
CreateEvent (
  IN  UINT32           Type,
  IN  EFI_TPL          NotifyTpl,
  IN  EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL
  IN  VOID             *NotifyContext, OPTIONAL
  OUT EFI_EVENT        *Event
  );

引数を上から説明していきます。

Typeは割り込みの種類で、以下のいずれか(もしくはそれらのOR)を指定することができます。

  • EVT_TIMER: EFI_BOOT_SERVICES.SetTimer()で使われるタイマー割り込み
  • EVT_RUNTIME: BootServicesが終了した後でも使われる
  • EVT_NOTIFY_WAIT: イベントが発火されていない間NotifyFunctionが呼ばれる
  • EVT_NOTIFY_SIGNAL: イベントが発火された時にNotifyFunctionが呼ばれる
  • EVT_SIGNAL_EXIT_BOOT_SERVICES: ExitBootServices()が呼ばれた時のイベント
  • EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE: SetVirtualAddressMap()が呼ばれた時のイベント

NofityTPLは割り込みの優先順位を表し、以下の値が指定できます。

  • TPL_APPLICATION
  • TPL_CALLBACK
  • TPL_NOTIFY
  • TPL_HIGH_LEVEL

優先度は、TPL_APPLICATION < TPL_CALLBACK < TPL_NOTIFY < ファームウェア割り込み < TPL_HIGH_LEVELの順に高くなっています。

NotifyFunctionは以下の型で指定される関数ポインタで、EVT_NOFITY_WAITもしくはEVT_NOTIFY_SIGNALが指定されているイベントに対して使用され、上で説明した条件をみたす場合に呼ばれます。

typedef
void
(EFIAPI *EFI_EVENT_NOTIFY) (
  IN EFI_EVENT Event,
  IN VOID *Context
);

NotifyContextはイベントの状態を表すデータで、任意の型を使うことができます。NotifyFunctionにはここで渡されたNofityContextが引数として与えられます。

また、作ったイベントはEFI_BOOT_SERVICES.SignalEvent()により発火することができます。

実際のコードがこちら。

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

EFI_HANDLE gIH;
EFI_SYSTEM_TABLE *gST;
EFI_BOOT_SERVICES *gBS;

typedef struct EVENT_CONTEXT {
  UINTN Count;
} EVENT_CONTEXT;

void
EFIAPI
EventNotify (
  IN EFI_EVENT Event,
  IN VOID *Context
  )
{
  EVENT_CONTEXT *EventContext = Context;
  EventContext->Count++;
  Print(L"Event notified; Count = %d\n", EventContext->Count);
  return;
}

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

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

  gBS->SignalEvent(Event);

  Print(L"done.\n");
  gBS->CloseEvent(Event);
  return EFI_SUCCESS;
}

CreateEvent()で作ったイベントを一回発火させるだけのコードです。

ここで作ったイベントはEVT_NOTIFY_SIGNALが指定されているので、gBS->SignalEvent(Event)が実行された時にEventNotifyFunctionであるEventNotifyが呼び出されます。

/img/post/2017-07-03-event-test-01.png

EVT_NOTIFY_WAITを使ったバージョンがこちら。今度は、イベントが発火されていない時にNotifyFunctionが呼ばれます。

void
EFIAPI
EventNotify (
  IN EFI_EVENT Event,
  IN VOID *Context
  )
{
  EVENT_CONTEXT *EventContext = Context;
  EventContext->Count++;
  Print(L"Event is waiting; Count = %d\n", EventContext->Count);
  return;
}

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

  EFI_EVENT Event;
  EVENT_CONTEXT EventContext = { .Count = 0 };
  gBS->CreateEvent(
    EVT_NOTIFY_WAIT,
    TPL_CALLBACK,
    EventNotify,
    &EventContext,
    &Event
    );

  // Signalせずに待つ
  UINTN EventIndex;
  gBS->WaitForEvent(1, &Event, &EventIndex);

  Print(L"done.\n");
  gBS->CloseEvent(Event);
  return EFI_SUCCESS;
}

EFI_BOOT_SERVICES.WaitFotEvent(NumEvents, EventArray, &EventIndex)で、EventArray内のいずれかのイベントが発火されるまで処理を止めることができます。

この場合、イベントは永遠に発火されないので、EventNotifyが永遠に呼ばれ続けます。

/img/post/2017-07-03-event-test-02.png