作者:Maxwell Li
日期:2017/12/09
未经作者允许,禁止转载本文任何内容。如需转载请留言。
[TOC]
UEFI 事件服务
函数名 | 作用 |
---|---|
CreateEvent | 生成一个事件对象 |
CreateEventEX | 生成一个事件对象并加入到一个组内 |
CloseEvent | 关闭事件对象 |
SignalEvent | 触发事件对象 |
WaitForEvent | 等待事件数组中的任意事件触发 |
CheckEvent | 检查事件状态 |
SetTimer | 设置定时器属性 |
RaiseTPL | 提升任务优先级 |
RestoreTPL | 恢复任务优先级 |
6.1 事件函数
6.1.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 没有超时属性,如果需要只等待一定的时间,需要在事件组加入定时器事件。
6.1.2 CreateEvent 服务
CreateEvent 服务函数原型:
typedef EFI_STATUS(EFIAPI *EFICREATE_EVENT)(
IN UINT32 Type, // 事件类型
IN EFI_TPL NotifyTpl, // 事件 Notification 函数的优先级
IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL // 事件 Notification 函数
IN VOID *NotifyContext, OPTIONAL // 传给事件 Notification 函数的参数
OUT EFI_EVENT *Event // 生成的事件
);
事件的类型
CreateEvent 第一个参数为 Type。
常用事件类型
事件类型 | 事件特征 |
---|---|
EVT_TIMER | 定时器事件。普通 Timer 事件,没有 Notification 函数。生成事件后需设置时钟属性。事件可以: 1. 通过 SetTimer() 设置等待事件 2. 到期后通过 SignalEvent() 触发 3. 通过 WaitForEvent() 等待事件被触发 4. 通过 CheckEvent() 检查状态 |
EVT_NOTIFY_WAIT | 普通事件。该事件有一个 Notification 函数,当该事件通过 CheckEvent() 检查状态或通过 WaitForEvent() 等待时,Notification 函数会被放到待执行队列 gEventQueue[Event->NotifyTpl] 中。 |
EVT_NOTIFY_SIGNAL | 普通事件。该事件有一个 Notification 函数,当该事件通过 SignalEvent() 被触发时,Notification 函数会被放到待执行队列 gEventQueue[Event->NotifyTpl] 中。 |
0x00000000 | 普通事件。此类事件没有 Notification 函数。事件可以: 1. 通过SignalEvent() 被触发 2. 通过 WaitForEvent() 等待事件被触发 3. 通过 CheckEvent() 检查状态 |
EVT_TIMER | EVT_NOTIFY_WAIT |
带 Notification 函数的定时器时间。同时具有 EVT_TIMER 和 EVT_NOTIFY_WAIT 的特性。 |
EVT_TIMER | EVT_NOTIFY_SIGNAL |
带 Notification 函数的定时器时间。同时具有 EVT_TIMER 和 EVT_NOTIFY_SIGNAL 的特性。 |
两种特殊的事件:
- EVT_SIGNAL_EXIT_BOOT_SERVICES:是 EVT_NOTIFY_SIGNAL 和 0x00000001 的组合。当 ExitBootServices() 执行时,事件被触发。该事件不能和其他类型混合使用。它的 Notification 函数和子函数不能使用启动服务中的内存分配服务,也不能使用定时器服务。
- EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE:是 EVT_RUNTIME_CONTEXT、EVT_RUNTIME、EVT_NOTIFY_SIGNAL、0x00000002 的组合。不能和这四类以外的类型组合使用,当 SetVirtualAddressMap() 被调用时触发。
优先级
CreateEvent 第二个参数为 NotifyTPL,是 0-31 的一个整数。优先级别搞得任务可以中断级别低的任务。
UEFI 预定任务优先级
任务优先级 | 用法 |
---|---|
TPL_APPLICATION(4) | 应用程序(包括 Boot Manager 和 OS Loader)运行在这个级别。 当程序运行在这个级别时,任务队列中没有任何处于就绪状态的事件 Notification 函数。 |
TPL_CALLBACK(8) | 比较耗时的操作通常在该优先级执行。 例如:文件系统、磁盘操作等。 |
TPL_NOTIFY(16) | 运行在该级别的程序不允许阻塞,必须尽快执行完毕且返回。 如果需要更多操作,则使用 Event 由内核重新调度。 通常底层的 IO 操作允许在这个级别。 例如:UEFI 内核中读取键盘状态的代码。 大部分 Event 的 Notification 函数允许在这个级别。 |
TPL_HIGH_LEVEL(31) | 在该级别,中断被禁止。 UEFI 内核全局变量的修改需要允许在该级别。 |
Notification 函数 NotifyFunction
CreateEvent 第三个参数为 NotifiFunction,是 EFI_EVENT_NOTIFY 类型的函数指针。
- 事件类型是 EVT_NOTIFY_WAIT,EFI_EVENT_NOTIFY 函数会在等待此事件的过程中调用。
- 事件类型是 EVT_NOTIFY_SIGNAL,EFI_EVENT_NOTIFY 函数会在事件触发时调用。
- 若不是以上类型,Notification 参数将被忽略。
CreateEvent 第四个参数为 NotifyContext,将在 Notification 函数被调用时作为第二个参数传递给该函数,用于指向这个 Notification 函数的上下文。
6.1.3 CreateEventEx 服务
CreateEventEx 服务用于生成事件并将事件加入事件组。
CreateEventEx 服务函数原型:
typedef EFI_STATUS(EFIAPI *EFICREATE_EVENT_EX)(
IN UINT32 Type, // 事件类型
IN EFI_TPL NotifyTpl, // 事件 Notification 函数的优先级
IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL // 事件 Notification 函数
IN VOID *NotifyContext, OPTIONAL // 传给事件 Notification 函数的参数
IN CONST EFI_GUID *EventGroup OPTIONAL, // 事件组
OUT EFI_EVENT *Event // 生成的事件
);
由 CreateEventEx 生成的事件会加入到 EventGroup 中。当 EventGroup 中的任一事件被触发后,组内所有其他事件都会被触发,进而组内所有 Notification 函数都将加入到待执行队列。如果 EventGroup 为 NULL,则 CreateEventEx 与 CreateEvent 相同。
UEFI 预定义的四个 Event 组:
- EFI_EVENT_GROUP_EXIT_BOOT_SERVICES
- GUID:gEfiEventExitBootServicesGuid
- ExitBootServices() 执行时触发该组所有 Event,组内 Event 属性同于 EVT_SIGNAL_EXIT_BOOT_SERVICES。
- EFI_EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE
- GUID:fEfiEventVirtualAddressChangeGuid
- SetVirtualAddressMap() 执行时触发该组所有 Event,组内 Event 属性同于 EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE。
- EFI_EVENT_GROUP_MEMORY_MAP_CHANGE
- GUID:gEfiEventMemoryMapChangeGuid
- Memory Map 改变时触发该组所有 Event,在 Notification 函数中不能使用启动服务的内存分配函数。
- EFI_EVENT_GROUP_READY_TO_BOOT
- GUID:gEfiEventReadyToBoot
- Boot Manager 加载并且执行一个启动项时触发该组所有 Event。
6.1.4 CheckEvent 服务
CheckEvent 服务用于检查事件的状态,返回值有以下四种情况:
- 事件是 EVT_NOTIFY_SIGNAL 类型:返回 EFI_INVALID_PARAMETER。
- 事件处于触发状态:返回 EFI_SUCCESS 且事件重置为非触发状态。
- 事件处于非触发状态且事件无 Notification 函数:返回 EFI_NOT_READY。
- 事件处于非触发状态且事件有 Notification 函数(事件只可能为 EVT_NOTIFY_WAIT 类型):执行 Notification 函数,检查事件状态表示。若处于触发状态,返回 EFI_SUCCESS,否则返回 EFI_NOT_READY。
6.1.5 SignalEvent 服务
SignalEvent 服务用于将事件设置为触发状态。
- 如果事件类型为 EVT_NOTIFY_SIGNAL:将其 Notification 函数添加到队列准备执行。
- 如果该类事件属于一个组:将该组内所有事件设置为触发状态,且将组内所有该类型事件的 Notification 函数添加到队列准备执行。
6.1.6 CloseEvent 服务
事件使用完毕后必须调用 CloseEvent 关闭该事件。调用该函数后,指定事件从内核中删除。
6.2 定时器事件
SetTimer 服务函数原型:
/**设置定时器属性
@retval EFI_SUCESS 属性设置成功
@retval EFI_INVALID_PARAMETER 参数 Event 不是 EVT_TIMER 或参数非法
**/
typedef EFI_STATUS(EFIAPI *EFI_SET_TIMER)(
IN EFI_EVENT Event, // Timer 事件
IN EFI_TIMER_DELAY Type, // 定时器类别
IN UINT64 TriggerTime // 定时器过期时间,100ns 为一个单位
);
定时器类别
Type | 作用 |
---|---|
TimerCancel | 用于取消定时器触发事件,设置后定时器不再触发 |
TimerPeriodic | 重复型定时器。每 TriggerTime*100ns 定时器触发一次 |
TimerRelative | 一次性定时器。TriggerTime*100ns 定时器触发 |
- Type 为 TimerPeriodic 且 TriggerTime 为0:定时器每个时钟滴答触发一次。
- Type 为 TimerRelative 且 TriggerTime 为0:定时器在下个时钟滴答触发。
6.3 任务优先级
6.3.1 提升和恢复任务优先级
- RaiseTPL(NewTpl) 用于提升当前任务的优先级至 NewTpl,该函数的返回值为原来的任务优先级。
- RestoreTPL 用于恢复至原来的任务优先级。
RaiseTPL 和 RestoreTPL 必须成对出现。当任务优先级提升至 TPL_HEIGH_LEVEL 时,将关闭中断。当任务优先级恢复到原来值时,中断被重新打开。
UEFI 是单 CPU 单线程系统,数据竞争来自中断处理函数。通过 CoreAcquireLock 和 CoreReleaseLock 实现 UEFI 锁。
6.3.2 UEFI 中的时钟中断
时钟处理函数 CoreTimerTick
时钟处理函数 CoreTimerTick 在时钟中断中调用,是时钟中断处理函数的主体。进入该函数时需要加锁,离开时需要解锁。主要功能:维持系统时间、检查定时器事件列表中到期事件。
CoreTimerTick 函数实现:
VOID EFIAPI CoreTimerTick( IN UINT64 Duration )
{
IEVENT *Event;
CoreAcquireLock (&mEfiSystemTimeLock);
// 任务一:更新系统时间
mEfiSystemTime += Duration;
// 任务二:检查系统中的定时器时间是否到期
if (!IsListEmpty (&mEfiTimerList)) {
Event = CR (mEfiTimerList.ForwardLink, IEVENT, Timer.Link, EVENT_SIGNATURE);
// 如果到期,则触发 mEfiCheckTimerEvent 事件
// 该事件会检查并触发所有到期的定时器事件
if (Event->Timer.TriggerTime <= mEfiSystemTime) {
CoreSignalEvent (mEfiCheckTimerEvent);
}
}
CoreReleaseLock (&mEfiSystemTimeLock); // 释放锁并开中断
}
设置时钟处理函数及安装时钟中断
函数 CoreTimerTick 如何注册到时钟中断处理函数中:
(1)安装时钟中断处理函数的时机
(2)向 gTimer 注册 CoreTimerTick 函数
(3)向 CPU 注册中断处理函数 TimerInterruptHandler
(4)在 CPU 时钟中断向量中调用时钟中断处理函数
该部分待补充。
6.3.3 UEFI 事件 Notification 函数的派发
事件 Notification 函数的派发是在 gBS->RestoreTpl 服务中完成的。gBS->RestoreTpl 实际指向 CoreRestoreTpl 函数,实现如下:
VOID EFIAPI CoreRestoreTpl ( IN EFI_TPL NewTpl )
{
EFI_TPL OldTpl;
OldTpl = gEfiCurrentTpl;
ASSERT (NewTpl <= OldTpl);
ASSERT (VALID_TPL (NewTpl));
if (OldTpl >= TPL_HIGH_LEVEL && NewTpl < TPL_HIGH_LEVEL) {
gEfiCurrentTpl = TPL_HIGH_LEVEL;
}
// 事件列表中高优先级的任务就绪
while (((-2 << NewTpl) & gEventPending) != 0) {
// gEfiCurrentTpl 赋值为 gEventPending 中的为 1 的最高位
gEfiCurrentTpl = (UINTN) HighBitSet64 (gEventPending);
if (gEfiCurrentTpl < TPL_HIGH_LEVEL) {
CoreSetInterruptState (TRUE);
}
// 执行 gEventQueue[gEfiCurrentTpl] 队列中所有 Event 的 Notification 函数
CoreDispatchEventNotifies (gEfiCurrentTpl);
}
// 设置新的任务优先级
gEfiCurrentTpl = NewTpl;
// 如果优先级低于 TPL_HIGH_LEVEL,打开中断。
if (gEfiCurrentTpl < TPL_HIGH_LEVEL) {
CoreSetInterruptState (TRUE);
}
- ((-2 << NewTpl) & gEventPending) != 0 表示事件列表中有高优先级的任务待执行。
- 时钟中断每 10ms(DEFAULT_TIMER_TICK_DURATION)中断一次。进入时钟中断时,会调用 RaiseTpl,任务优先级提升至 TPL_HEIGH_LEVEL,中断调用返回时,会调用 RestoreTpl 恢复优先级。因此中断调用返回后,所有高于原优先级的 Notification 都已被执行。
网友评论