本文参考戴正华《UEFI原理与编程》
1.等待事件的服务WaitForEvent
启动服务中的WaitForEvent服务的函数原型:
/**等待Event数组内任一事件被触发@retval EFI_SUCCESS 下表为*index的事件被触发@retval EFI_UNSUPPORTED 当前的TPL不是TPL_APPLICATION@retval EFI_INVALID_PARAMETER 下标为*index的事件类型为EVT_NOTIFY_SIGNAL
**/
typedef EFI_STATUS (EFIAPI *EFI_WAIT_FOR_EVENT) (IN UINTN NumberOfEvents, //Event数组内Event的个数IN EFI_EVENT *Event, //Event数组OUT UINTN *Index //返回处于触发态的事件在数组内的下标);
WaitForEvent是阻塞操作,直到Event数组内任一事件被触发,或任一事件导致错误出现,WaitForEvent才返回。WaitForEvent从前到后依次检查Event数组内的事件,发现有被触发的事件或遇到错误则返回,如果所有事件都没有被触发,则从头开始重新检查。
当检查到某个事件处于触发态时,*Index赋值为该事件在Event数组中的下标,返回前该事件将重置为非触发态。
当检查到某个事件是EVT_NOTIFY_SIGNAL类型时,*Index赋值为该事件在Event数组中的下标,并返回EFI_INVALID_PARAMETER。
WaitForEvent必须运行在TPL_APPLICATION级别,否则将返回EFI_UNSUPPORTED。
2.生成事件的服务CreateEvent
启动服务中的CreateEvent服务的函数原型
//生成一个事件
typedef EFI_STATUS (EFIAPI *EFI_CREATE_EVENT) (IN UINT32 Type, //事件类型IN EFI_TPL NotifyTpl, //事件Notification函数的优先级IN EFI_EVENT_NOTIFY NotifyFunction, //事件Notification函数IN VOID *NotifyContext, //传给事件Notification函数的参数OUT EFI_EVENT *Event //生成的事件
);
参数IN UINT32 Type是事件的类型,常用的几种事件类型如表1所示。
表1
事件类型 | 事件特征 |
---|---|
EVT_TIMER | 定时器事件。普通Timer事件,没有Notification函数。生成事件后需调用SetTimer服务设置时钟属性。事件可以: ①通过SetTimer()设置等待事件 ②到期后通过SignalEvent()触发 ③通过WaitForEvent()等待事件被触发④通过CheckEvent()检查状态 |
EVT_NOTIFY_WAIT | 普通事件。这个事件有一个Notification函数,当这个事件通过CheckEvent()检查状态或通过WaitForEvent()等待时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中 |
EVT_NOTIFY_SIGNAL | 普通事件。这个事件有一个Notification函数,当这个事件通过SignalEvent()被触发时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中 |
0x00000000 | 普通事件。此类事件没有Notification函数。事件可以: ①通过SignalEvent()被触发②通过WaitForEvent()等待事件被触发③通过CheckEvent()检查状态 |
EVT_TIMER | EVT_NOTIFY_WAIT | 带Notification函数的定时器事件。此类事件除了具有EVT_TIMER的特性外,还有EVT_NOTIFY_WAIT的特性,即到后期通过SignalEvent()触发。当事件通过CheckEvent()检查状态或通过WaitForEvent()等待时,这个Notification函数回被放到执行队列gEventQueue[Event->NotifyTpl]中 |
EVT_TIMER | EVT_NOTIFY_SIGNAL | 带Notification函数的定时器事件。此类事件除了具有EVT_TIMER的特性外,还有EVT_NOTIFY_SIGNAL的特性,即到后期通过SignalEvent()触发。当这个事件通过SignalEvent()被触发时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中 |
CreateEvent的第二个参数为NotifyTPL(即任务优先级),它可以是0~31的一个整数。UEFI预定义了以下4个优先级。
#define TPL_APPLICATION 4
#define TPL_CALLBACK 8
#define TPL_NOTIFY 16
#define TPL_HIGH_LEVEL 31
优先级别高的任务可以中断级别低的任务,并且从高优先级返回低优先级前会完成所有高于低优先级的任务。表2列出了UEFI预定义的4个任务优先级。
表2
任务优先级 | 用法 | 函数 |
---|---|---|
TPL_APPLICATION | 这是预定义的4个级别中最低的一个优先级。应用程序运行(包括Boot Manager和OS Loader)在这个级别。当程序运行在这个级别时,任务队列中没有任何处于就绪状态的事件Notification函数 | 下列函数运行在此级别: ExitBootServices()、WaitForEvent()、User Manager Protocol/Identify()、Form Browser2 Protocol/SendForm 下列函数运行在此级别或更低级别: Simple Input Protocol |
TPL_CALLBACK | 比较耗时的操作通常在这个优先级执行,如文件系统、磁盘操作等 | 下列函数运行在此级别或更低级别: Exit();Serial I/O Protocol、UnloadImage()、Variable Services、NetWork Service Binding、Network Protocol 下列函数运行在低于8的级别: LoadImage()、StartImage() |
TPL_NOTIFY | 运行在这个级别的程序不允许阻塞,必须尽快执行完毕并且返回。如果需要更多操作,则需要使用Event由内核重新调度。通常,底层的IO操作允许在这个级别,例如UEFI内核中读取键盘状态的代码。大部分Event的Notification函数允许在这个级别 | 下列函数运行在此级别或更低级别: Protocol Handle Services、Memory Allocation Services、Simple Text Output Protocol、ACPI Table Protocol、User Manager Protocol、User Credential Protocol、User Info Protocol、Authentication Info、Device Path Utilities、Device Path Form Text、EDID Discovered、EDID Active、Graphics Output EDID Override、iSCSI Initiator Name、Tape IO、Deferred Image Load Protocol、HII Protocols、Driver Health 下列函数运行在低于16的级别: ACPI Table Protocol |
TPL_HIGH_LEVEL | 优先级最高级别。在此级别,中断被禁止。UEFI 内核全局变量的修改需要允许在这个级别 | 下列函数运行在此级别或更低级别: SignalEvent()、Stall() 下列函数运行在低于31的级别: CheckEvent()、CloseEvent()、CreateEvent()、SetTimer()、Event Notofication Levels运行在(TPL_APPLICATION,TPL_HIGH_LEVEL)区间的优先级上 |
CreateEvent的第三个参数NotifyFunction是EFI_EVENT_NOTIFY类型的函数指针,它的函数原型是:
/**Invoke a notification event@param[in] Event Event whose notification function is being invoked.@param[in] Context The pointer to the notification function's context,which is implementation-dependent.**/
typedef
VOID
(EFIAPI *EFI_EVENT_NOTIFY)(IN EFI_EVENT Event,IN VOID *Context);
如果事件的类型是EVENT_NOTIFY_WAIT,则EFI_EVENT_NOTIFY函数会在等待此事件的过程中被调用;如果事件的类型是EVT_NOTIFY_SIGNAL,则EFI_EVENT_NOTIFY函数会在事件触发时被调用。既没有EVT_NOTIFY_WAIT属性也没有EVT_NOTIFY_SIGNAL属性的事件,Notification参数将被忽略。
CreateEvent的第4个参数是NotifyContext,将在Notification函数被调用时作为Notification函数的第2个参数传递给该函数,用于指向这个Notification函数的上下文。
3.CreateEventEx服务
函数原型(其函数指针):
/**Creates an event in a group.@param[in] Type The type of event to create and its mode and attributes.@param[in] NotifyTpl The task priority level of event notifications,if needed.@param[in] NotifyFunction The pointer to the event's notification function, if any.@param[in] NotifyContext The pointer to the notification function's context; corresponds to parameterContext in the notification function.@param[in] EventGroup The pointer to the unique identifier of the group to which this event belongs.If this is NULL, then the function behaves as if the parameters were passedto CreateEvent.@param[out] Event The pointer to the newly created event if the call succeeds; undefinedotherwise.@retval EFI_SUCCESS The event structure was created.@retval EFI_INVALID_PARAMETER One or more parameters are invalid.@retval EFI_OUT_OF_RESOURCES The event could not be allocated.**/
typedef
EFI_STATUS
(EFIAPI *EFI_CREATE_EVENT_EX)(IN UINT32 Type, //事件类型IN EFI_TPL NotifyTpl, //事件Notification函数的优先级IN EFI_EVENT_NOTIFY NotifyFunction OPTIONAL, //事件Notification函数IN CONST VOID *NotifyContext OPTIONAL, //传给事件Notification函数的参数IN CONST EFI_GUID *EventGroup OPTIONAL, //事件组OUT EFI_EVENT *Event //生成的事件);
由CreateEventEx生成的事件会加入到EventGroup中,当EventGroup中的任一事件被触发后,组中的所有其他事件都会被触发,进而同组内所有的Notification函数都将被加入到待执行队列。同组内NotifyTpl(优先级)高的Notification函数会先被执行。
如果输入参数EventGroup为NULL,则CreateEventEx退化为CreateEvent。
关于CreateEventEx的示例代码可以看我的另一篇博客:UEFI学习——使用gRT->GetVariable读取Setup选项值
在这个博客中对CreateEventEx的使用过程如下图: