【UEFI系列】Event


前言

不同于ARM平台不支持SMI,x86和ARM平台的bios都会支持EVENT。


一、什么是Event,Event相关函数

Event,是UEFI中一个重要的东西,UEFI不再支持interrupt中断,所有的异步操作都要通过Event来完成。
比如说我们设定了一个功能,但是我们不想立即跑,就需要设置触发条件,它触发了才会跑。
比如,一个protocol install好之后功能会跑/exitbootservice了功能会跑/每个10s功能会跑。

Boot Services为开发者提供3类函数:事件相关函数、定时器相关函数、TPL(Task priority Level任务优先级)相关函数,用于用于操作事件、定时器、TPL。

Event, Timer, and Task Priority Functions

函数名作用
CreateEvent生成一个事件对象
CreateEventEx生成一个事件对象并将该事件加入到一个组内 //一执行就全部执行,比如挂在ReadyToBoot事件组里的
CloseEvent关闭事件对象 //关了就不能再被触发了,和create是对应的,而不是signal,相当于被删了
SignalEvent触发事件对象
WaitForEvent等待事件数组中的任一事件触发
CheckEvent检查事件状态 //确认是已经被执行还是等待被执行
SetTimer设置定时器属性 //比如说是周期性触发还是一次性触发,如果是周期性,周期是多少
RaiseTPL提升任务优先级 //比如说很多event都挂在同一个事件里,设置TPL就能设置执行的优先顺序
RestoreTPL恢复任务优先级

二、Event的功能

Event的种类

Boot Services中事件相关函数有6个,CreateEvent/CreateEventEx、SignalEvent及CloseEvent、WaitForEvent和CheckEvent。

关于Event的种类,Event.c里是这么写的:

/// Enumerate the valid types
UINT32  mEventTable[] = {
  /// 0x80000200       Timer event with a notification function that is
  /// queue when the event is signaled with SignalEvent()
  EVT_TIMER | EVT_NOTIFY_SIGNAL,
  /// 0x80000000       Timer event without a notification function. It can be
  /// signaled with SignalEvent() and checked with CheckEvent() or WaitForEvent().
  EVT_TIMER,
  /// 0x00000100       Generic event with a notification function that
  /// can be waited on with CheckEvent() or WaitForEvent()
  EVT_NOTIFY_WAIT,
  /// 0x00000200       Generic event with a notification function that
  /// is queue when the event is signaled with SignalEvent()
  EVT_NOTIFY_SIGNAL,
  /// 0x00000201       ExitBootServicesEvent.
  EVT_SIGNAL_EXIT_BOOT_SERVICES,
  /// 0x60000202       SetVirtualAddressMapEvent.
  EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
  /// 0x00000000       Generic event without a notification function.
  /// It can be signaled with SignalEvent() and checked with CheckEvent()
  /// or WaitForEvent().
  0x00000000,
  /// 0x80000100       Timer event with a notification function that can be
  /// waited on with CheckEvent() or WaitForEvent()
  EVT_TIMER | EVT_NOTIFY_WAIT,
};
宏定义描述
EVT_TIMER0x80000000定时器事件
EVT_RUNTIME0x40000000在运行时(OS 启动后)仍然有效的事件
EVT_NOTIFY_WAIT0x00000100当 WaitForEvent() 被调用时触发通知函数
EVT_NOTIFY_SIGNAL0x00000200当 SignalEvent() 被调用时触发通知函数
EVT_SIGNAL_EXIT_BOOT_SERVICES0x00000201ExitBootServices 被调用时触发
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE0x60000202在 SetVirtualAddressMap 被调用时触发

不同bios厂商还会定义不同的Event Types,比如:

宏定义描述
EVT_EFI_SIGNAL_READY_TO_BOOT0x00000203ReadyToBoot 阶段触发
EVT_SIGNAL_LEGACY_BOOT0x00000204在 Legacy Boot 被触发时调用

注意:Ready To Boot 事件发生在 UEFI Boot Manager 准备启动操作系统之前,但在调用 ExitBootServices() 之前。

CreateEvent

实际上CreateEvent的结构体原型:

typedef
EFI_STATUS
(EFIAPI *EFI_CREATE_EVENT)(
  IN  UINT32                       Type, //事件类型,就是我们之前介绍的几种type
  IN  EFI_TPL                      NotifyTpl, //事件Notify函数的优先级,是0~31的整数
  IN  EFI_EVENT_NOTIFY             NotifyFunction OPTIONAL, //事件NotifyFunction函数
  IN  VOID                         *NotifyContext OPTIONAL, //传给事件NotifyFunction函数的参数
  OUT EFI_EVENT                    *Event //生成的事件
  );
typedef
VOID
(EFIAPI *EFI_EVENT_NOTIFY)(
  IN  EFI_EVENT                Event,
  IN  VOID                     *Context
  );

优先级高的event可以中断优先级低的。关于优先级EDKII这样定义,可以看到NOTIFY的优先级相对比较高:

// Task priority level
#define TPL_APPLICATION  4
#define TPL_CALLBACK     8
#define TPL_NOTIFY       16
#define TPL_HIGH_LEVEL   31
  1. UEFI EDKII BIOS代码里的EVT_SIGNAL_EXIT_BOOT_SERVICES类型应用:
    (exit boot services时出发的event,要执行的函数就是自己定义的ExitBootServicesEventA)
VOID EFIAPI ExitBootServicesEventA ( //其实就是EFI_EVENT_NOTIFY的结构
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
    DEBUG ((DEBUG_ERROR,"ExitBootServicesEventA\n"));
}

//执行函数里的部分,前后代码懒得写了
  Status = gBS->CreateEvent (
                  EVT_SIGNAL_EXIT_BOOT_SERVICES, //事件类型,exit boot services的时候触发
                  TPL_NOTIFY, //优先级为16
                  ExitBootServicesEventA, //执行的函数
                  NULL,
                  &EfiExitBootServicesEvent //event名称
                  );
  1. UEFI EDKII BIOS代码里的EVT_NOTIFY_SIGNAL类型应用:
    (满足条件触发的event,要执行的函数就是自己定义的MyCallback)

RegisterProtocolNotify 是专门用于 UEFI 中的Event机制的一部分。它的主要作用是注册一个事件,当某个指定的 Protocol 被安装或重新安装时触发该事件,从而实现异步通知功能。

VOID EFIAPI MyCallback ( //其实就是EFI_EVENT_NOTIFY的结构
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
    DEBUG ((DEBUG_ERROR,"MyCallback\n"));
}

//执行函数里的部分,前后代码懒得写了
Status = gBS->CreateEvent(
                  EVT_NOTIFY_SIGNAL, //事件类型
                  TPL_CALLBACK, //优先级为16
                  MyCallback, //执行的函数
                  NULL, 
                  &MyEvent); //event名称

if(!EFI_ERROR(Status))
{
    Status = gBS->RegisterProtocolNotify( //只要有地方Install或者Reinstall对应的guid的protocol就会触发
                 &xxxProtocolGuid, //protocol的guid
                 MyEvent, //event名称
                 &CallBack ); //执行的函数
}

CreateEventEx

typedef
EFI_STATUS
(EFIAPI *EFI_CREATE_EVENT_EX)(
  IN       UINT32                 Type,
  IN       EFI_TPL                NotifyTpl,
  IN       EFI_EVENT_NOTIFY       NotifyFunction OPTIONAL,
  IN CONST VOID                   *NotifyContext OPTIONAL,
  IN CONST EFI_GUID               *EventGroup    OPTIONAL, //事件组,特定的guid。比起EFI_CREATE_EVENT多的参数
  OUT      EFI_EVENT              *Event
  );

当挂在这个事件组里的Event被触发后,其他地方此Event挂的函数都会被触发,根据TPL优先级执行。
比如ReadyToBoot这种,就是创建一个event在event组里,然后再触发这个event,挂在这个guid上的event组里的其他event全部触发。

有四个UEFI规定的事件组:

宏名称(前略)描述
_EXIT_BOOT_SERVICES当系统调用 ExitBootServices() 时触发
_VIRTUAL_ADDRESS_CHANGE当系统调用 SetVirtualAddressMap() 时触发
_READY_TO_BOOT当系统准备启动 OS 时触发
_MEMORY_MAP_CHANGE当内存映射发生变化时触发

SignalEvent

立即触发Event,像如果出发的是事件组里的一个Event,其他此Event挂的函数也随后一起触发了。

Status = gBS->SignalEvent (XXXEvent);

CloseEvent

立即关闭Event,相当于删除,Event再也不会被触发。将Event从内核各个队列中清除,释放掉相关内存(Ex同理)。

Status = gBS->CloseEvent (XXXEvent);

RegisterProtocolNotify

看我在CreateEvent里的2.的那段应用。
用这个函数设置Install protocol的guid,等到时候install 这个guid的protocol时它就会被触发。

WaitForEvent

用于等待事件的发生,函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_WAIT_FOR_EVENT)(
  IN  UINTN                    NumberOfEvents, //event数组内的event个数
  IN  EFI_EVENT                *Event, //event数组
  OUT UINTN                    *Index //返回处于触发态的event在数组内的下标
  );

WaitForEvent是阻塞操作,直到Event数组内任一事件被触发,或任一事件导致错误出现,WaitForEvent才返回。WaitForEvent从前到后依次检查Event数组内的事件,发现有被触发的事件或遇到错误则返回,如果所有事件都没有被触发,则从头开始重新检查。
当检查到某个事件处于触发态时,*Index赋值为该事件在Event数组中的下标,返回前该事件将重置为
非触发态。
当检查到某个事件是EVT_NOTIFY_SIGNAL类型时,*Index赋值为该事件在Event数组中的下标,并返回
EFI_INVALID_PARAMETER。

举例:

Void myEventNotify(
	EFI_EVENT Event,
	VOID *Context)
{
		static UINTN times=0; //must define static type
		Print(L"myEventNotify Wait %din",times);
		times=times+1;
		if(times>5){
			Print(L"SignalEvent start\n");
			gBS->SignalEvent(Event);
			Print(L"SignalEvent end\n");
		}
}


EFI_STATUS EventDemoEntryPoint(
		IN EFI_HANDLEImageHandle,
		IN EFL_SYSTEM_TABLE*SystemTable
)
{
	EFL_ STATUS Status;
	UINTN index=0,
	EFI_EVENT myEvent;
	
	Status = gBS->CreateEvent (
			EVT_NOTIFY_WAIT,
			TPL_NOTIFY,
			myEventNotify.
			(void*)NULL,
			&myEvent );
		
	Status = gBS->WaitForEvent(1,&myEvent,&index); //会一直停下来去检查myEvent从而触发myEventNotify
	return Status;
}

Result:在WaitForEvent的循环中,每检查一次myEvent的状态,myEventNotify就执行一次。检查6次后,myEvent被触发,从而WaitForEvent结束等待。
在这里插入图片描述

CheckEvent

CheckEvent用于检测事件的状态。与WaitForEvent不同的是,CheckEvent调用后立刻返回。函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_CHECK_EVENT)(
  IN EFI_EVENT                Event
  );

根据事件的属性和状态。返回值有如下4种情况:
(1)如果事件是EVT_NOTIFY_SIGNAL类型,则返回EFI_INVALID_PARAMETER。
(2)如果事件处于触发态,则返回EFIL_SUCCESS,并且在返回前,事件重置为非触发态。
(3)如果事件处于非触发态并且事件无Notification函数,则返回EFI_NOT_READY.
(4)如果事件处于非触发态并且事件有Notification函数(此事件只可能是EVT_NOTIFY_WAIT类型),则执行Notification函数。然后,检查事件状态标识,若事件处于触发态,则返回EFI_SUCCESS,否则返回NOT_READY。

三、Timer Event

定时器是一个特殊的事件,生成定时器事件后,可以通过SetTimer服务设
置定时器属性。函数原型:

typedef
EFI_STATUS
(EFIAPI *EFI_SET_TIMER)(
  IN  EFI_EVENT                Event, //Timer事件
  IN  EFI_TIMER_DELAY          Type, //定时器类别
  IN  UINT64                   TriggerTime //定时器触发时间,100ns为一个衡量单位
  );
/// Timer delay types
typedef enum {
  /// 用于取消定时器触发事件。设置后定时器不再触发
  TimerCancel,
  /// 重复性定时器。每TriggerTime*100ns,定时器触发一次
  TimerPeriodic,
  /// 一次性定时器。TriggerTime*100ns时触发
  TimerRelative
} EFI_TIMER_DELAY;

如果Type为TimerPeriodic并且TriggerTime是0,则定时器每个时钟点都会触发一次。
如果Type为TimerRelative并且TriggerTime是0,则定时器在下个时钟点触发(相当于立即出发)。

怎么create Timer Event

生成定时器事件一般分为两步:
第一步,通过CreateEvent生成一个EVT_TIMER事件。
第二步,通过SetTimer设置这个定时器事件的属性。

EFI_STATUS
TimerCreateTimer
( 	EFI_EVENT *Event,
	EFI_EVENT_NOTIFY Callback,
	VOID *Context,
	EFI_TIMER_DELAY Delay,
	UINT64 Trigger,
	EFI_TPL CallBackTPL )
{
	EFI_STATUS Status;
	UINT32 EventType = EFI_EVENT_TIMER;
	
	if (Callback != NULL )
	EventType |= EFI_EVENT_NOTIFY_SIGNAL;
	
	Status = gBS->CreateEvent(EventType,CallBackTPL,Callback,Context,Event); //第一步
	if(EFI_ERROR( Status ))
	return Status;
	
	Status = gBS->SetTimer( *Event, Delay, Trigger ); //第二步
	if (EFI ERROR( Status ))
	TimerStopTimer( Event );
	
	return Status;
}

更详细的例子:

void
myEventNotify(
	EFI_EVENT Event,
	VOID *Context)
{
	static UINTN times=0; //must define static type
	Print(L"myEventNotify Wait %d\n",times);
	times=times+1;
}

EFI_STATUS SetTimerDemoEntryPoint(
	IN EFI_HANDLE ImageHandle,
	IN EFI_SYSTEM_TABLE *SystemTable
{
	EFI_STATUS Status;
	UINTN i=O;
	EFI_EVENT myEvent;
	Print(L"Entry to SetTimerDemoEntryPoint\n");

	Status = gBS->CreateEvent (
		EVT_TIMER | EVT_NOTIFY_SIGNAL,
		TPL_CALLBACK,
		(EFL_EVENT_NOTIFY)myEventNotify,
		(void*)NULL,
		&myEvent);
		
	Status = gBS->SetTimer(myEvent, TimerPeriodic,10*1000*1000); //周期性
	
    for (i = 0; i < 5; i++) {
        Print(L"Waiting...\n");
        gBS->Stall(1000 * 1000); // 1秒,等待定时器触发
    }

	//终止信号
	gBS->SignalEvent(myEvent);
	//关闭event
	gBS->CloseEvent(myEvent);
	Print(L"End to SetTimerDemoEntryPointin");
	return Status;
}

结果:

Entry to SetTimerDemoEntryPoint
Waiting...
myEventNotify Wait 0
Waiting...
myEventNotify Wait 1
Waiting...
myEventNotify Wait 2
Waiting...
myEventNotify Wait 3
Waiting...
myEventNotify Wait 4
myEventNotify Wait 5
End to SetTimerDemoEntryPoint
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萨摩耶饭团

如果我能帮到的话嗯万一呢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值