事件
每一个事件都是一个IEvent类型
/** /Dxe/Event/Event.h **/
45 #define EVENT_SIGNATURE SIGNATURE_32('e','v','n','t')
46 typedef struct {
47 UINTN Signature;
48 UINT32 Type;
49 UINT32 SignalCount;
53 LIST_ENTRY SignalLink;
57 EFI_TPL NotifyTpl;
58 EFI_EVENT_NOTIFY NotifyFunction;
59 VOID *NotifyContext;
60 EFI_GUID EventGroup;
61 LIST_ENTRY NotifyLink;
62 UINT8 ExFlag;
66 EFI_RUNTIME_EVENT_ENTRY RuntimeData;
67 TIMER_EVENT_INFO Timer;
68 } IEVENT;
可以看到有多个链表
SignalLink
NotifyLink
这是提供给不同事件类型所使用。事件有以下几种类型。
/** MdePkg/Include/Uefi/UefiSpec.h **/
384 //
385 // These types can be ORed together as needed - for example,
386 // EVT_TIMER might be Ored with EVT_NOTIFY_WAIT or
387 // EVT_NOTIFY_SIGNAL.
388 //
389 #define EVT_TIMER 0x80000000
390 #define EVT_RUNTIME 0x40000000
391 #define EVT_NOTIFY_WAIT 0x00000100
392 #define EVT_NOTIFY_SIGNAL 0x00000200
393
394 #define EVT_SIGNAL_EXIT_BOOT_SERVICES 0x00000201
395 #define EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE 0x60000202
大致有如下特性
- Notify意味着有NotifyFunction在事件发生的时候被调用,也就是回调函数
- Wait意味着可以使用WaitForEvent来等待事件的发生,其阻塞直到事件被Signal
- Signal意味着事件可以被唤醒
- Timer意味着该事件是一个定时器,IEvent的Timer域会被填充,其中包含一个Link来链接所有的定时器
- 这些特性是可以被OR来组合到一起的,即运算符|
两个类型只在此提一下,不详述
- EVT_SIGNAL_EXIT_BOOT_SERVICES是在UEFI系统退出(ExitBootServices())的时候调用,通常用来回收资源
- EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE在操作系统加载器调用运行时服务RuntimeServices的虚拟地址服务来进行虚拟地址转换的时候调用。
主要关注两个类型
- EVT_NOTIFY_WAIT可等待事件
- EVT_NOTIFY_SIGNAL可唤醒事件
重要的全局变量
gEventSignalQueue保存所有未被唤醒、类型为EVT_NOTIFY_SIGNAL的事件
gEventQueue保存已经被唤醒、需要执行回调函数的事件,根据优先级分为多个链表
gEventPending使用位图来保存gEventQueue是否含有某个优先级的链表
mEventTable包含了所有合法的事件类型
相关接口
创建事件CreateEvent
/** /Dxe/Event/Event.c **/
378 EFI_STATUS
379 EFIAPI
380 CoreCreateEventInternal (
381 IN UINT32 Type,
382 IN EFI_TPL NotifyTpl,
383 IN EFI_EVENT_NOTIFY NotifyFunction, OPTIONAL
384 IN CONST VOID *NotifyContext, OPTIONAL
385 IN CONST EFI_GUID *EventGroup, OPTIONAL
386 OUT EFI_EVENT *Event
387 )
388 {
Type域即事件的类型
NotifyTpl是回调函数的优先级,在本系列的第二部分会介绍,NorifyFunction即回调函数本身,NofityContext即传递给回调函数的参数。
EventGroup是事件组特性,其使用一个GUID来标识该事件属于一个事件组,同组内的事件均含有相同的GUID,不同事件组的GUID必然不同。
Event是返回的、创建完成的事件,EFI_EVENT类型本质上是IEvent*类型。
1、检查事件的类型
401 Status = EFI_INVALID_PARAMETER;
402 for (Index = 0; Index < (sizeof (mEventTable) / sizeof (UINT32)); Index++) {
403 if (Type == mEventTable[Index]) {
404 Status = EFI_SUCCESS;
405 break;
406 }
407 }
408 if(EFI_ERROR (Status)) {
409 return EFI_INVALID_PARAMETER;
410 }
对类型的检查使用了全局变量mEventTable,该变量规定了UEFI系统所支持的所有事件类型,源码中注释写得很清楚了,这里由于只关注三类,所以也就不贴出注释了。
/** /Dxe/Event/Event.c **/
48 UINT32 mEventTable[] = {
53 EVT_TIMER | EVT_NOTIFY_SIGNAL,
58 EVT_TIMER,
63 EVT_NOTIFY_WAIT,
68 EVT_NOTIFY_SIGNAL,
72 EVT_SIGNAL_EXIT_BOOT_SERVICES,
76 EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
77
83 0x00000000,
88 EVT_TIMER | EVT_NOTIFY_WAIT,
89 };
2、处理事件组
如果EventGroup非空,意味着该事件属于一个事件组
EVT_SIGNAL_EXIT_BOOT_SERVICES和EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE类型的事件是不允许加入其他事件组的,这两类事件各自有自己的事件组
如果EventGroup这个GUID是gEfiEventExitBootServicesGuid,则类型必须是EVT_SIGNAL_EXIT_BOOT_SERVICES
如果EventGroup这个GUID是gEfiEventVirtualAddressChangeGuid,则类型必须是
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
415 if (EventGroup != NULL) {
420 if ((Type == EVT_SIGNAL_EXIT_BOOT_SERVICES) || (Type == EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE)) {
421 return EFI_INVALID_PARAMETER;
422 }
423 if (CompareGuid (EventGroup, &gEfiEventExitBootServicesGuid)) {
424 Type = EVT_SIGNAL_EXIT_BOOT_SERVICES;
425 } else if (CompareGuid (EventGroup, &gEfiEventVirtualAddressChangeGuid)) {
426 Type = EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE;
427 }
428 } else {
432 if (Type == EVT_SIGNAL_EXIT_BOOT_SERVICES) {
433 EventGroup = &gEfiEventExitBootServicesGuid;
434 } else if (Type == EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE) {
435 EventGroup = &gEfiEventVirtualAddressChangeGuid;
436 }
437 }
如果EventGroup为空,但类型却是EVT_SIGNAL_EXIT_BOOT_SERVICES或EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,则将其加入指定的事件组
3、处理Notify
当且仅当类型包含了EVT_NOTIFY_WAIT或EVT_NOTIFY_SIGNAL才允许拥有回调函数,且优先级(NorifyTpl)必须在合法范围内。
442 if ((Type & (EVT_NOTIFY_WAIT | EVT_NOTIFY_SIGNAL)) != 0) {
446 if ((NotifyFunction == NULL) ||
447 (NotifyTpl <= TPL_APPLICATION) ||
448 (NotifyTpl >= TPL_HIGH_LEVEL)) {
449 return EFI_INVALID_PARAMETER;
450 }
452 } else {
456 NotifyTpl = 0;
457 NotifyFunction = NULL;
458 NotifyContext = NULL;
459 }
否则将Notify变量均置为空,不允许也没必要拥有回调函数。
4、创建事件IEvent
对于运行时的事件,需要申请运行时的内存空间,避免被释放
464 if ((Type & EVT_RUNTIME) != 0) {
465 IEvent = AllocateRuntimeZeroPool (sizeof (IEVENT));
466 } else {
467 IEvent = AllocateZeroPool (sizeof (IEVENT));
468 }
赋值即可。对于事件组,则复制EventGroup这个GUID并在IEvent->ExFlag中标记上该事件属于一个事件组。
473 IEvent->Signature = EVENT_SIGNATURE;
474 IEvent->Type = Type;
475
476 IEvent->NotifyTpl = NotifyTpl;
477 IEvent->NotifyFunction = NotifyFunction;
478 IEvent->NotifyContext = (VOID *)NotifyContext;
479 if (EventGroup != NULL) {
480 CopyGuid (&IEvent->EventGroup, EventGroup);
481 IEvent->ExFlag |= EVT_EXFLAG_EVENT_GROUP;
482 }
483
484 *Event = IEvent;
5、处理运行时事件
486 if ((Type & EVT_RUNTIME) != 0) {
487 //
488 // Keep a list of all RT events so we can tell the RT AP.
489 //
490 IEvent->RuntimeData.Type = Type;
491 IEvent->RuntimeData.NotifyTpl = NotifyTpl;
492 IEvent->RuntimeData.NotifyFunction = NotifyFunction;
493 IEvent->RuntimeData.NotifyContext = (VOID *) NotifyContext;
494 IEvent->RuntimeData.Event = (EFI_EVENT *) IEvent;
495 InsertTailList (&gRuntime->EventHead, &IEvent->RuntimeData.Link);
496 }
简而言之就是所需的数据均存于IEvent的RuntimeData域并链接到全局gRuntime
6、处理可唤醒事件EVT_NOTIFY_SIGNAL
插入全局gEventSignalQueue,使用IEvent->SignalLink
这个操作需要在锁的保护下进行
498 CoreAcquireEventLock ();
499
500 if ((Type & EVT_NOTIFY_SIGNAL) != 0x00000000) {
501 //
502 // The Event's NotifyFunction must be queued whenever the event is signaled
503 //
504 InsertHeadList (&gEventSignalQueue, &IEvent->SignalLink);
505 }
506
507 CoreReleaseEventLock ();
唤醒事件SignalEvent
空检查和签名检查就不说了
527 EFI_STATUS
528 EFIAPI
529 CoreSignalEvent (
530 IN EFI_EVENT UserEvent
531 )
532 {
533 IEVENT *Event;
534
535 Event = UserEvent;
同样需要在锁的保护下进行
545 CoreAcquireEventLock ();
551 if (Event->SignalCount == 0x00000000) {
552 Event->SignalCount++;
553
557 if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) {
558 if ((Event->ExFlag & EVT_EXFLAG_EVENT_GROUP) != 0) {
563 CoreReleaseEventLock ();
564 CoreNotifySignalList (&Event->EventGroup);
565 CoreAcquireEventLock ();
566 } else {
567 CoreNotifyEvent (Event);
568 }
569 }
570 }
571
572 CoreReleaseEventLock ();
只有在SignalCount为0的时候,才会加一
只有在类型是EVT_NOTIFY_SIGNAL的时候才会调用CoreNotifyEvent来执行回调函数
事件回调NorifyEvent
220 VOID
221 CoreNotifyEvent (
222 IN IEVENT *Event
223 )
224 {
确保在锁的保护下
229 ASSERT_LOCKED (&gEventQueueLock);
1
若该事件IEvent->Notify不为空,则将其从原链表中删除
235 if (Event->NotifyLink.ForwardLink != NULL) {
236 RemoveEntryList (&Event->NotifyLink);
237 Event->NotifyLink.ForwardLink = NULL;
238 }
执行事件的回调函数其实是只是将该事件链入全局的gEventQueue。
全局变量gEventQueue根据不同的NotifyTpl即优先级,保存了所有已经唤醒、需要执行回调函数的事件。
全局变量gEventPending使用位图来保存gEventQueue中是否含有某个优先级的事件。
244 InsertTailList (&gEventQueue[Event->NotifyTpl], &Event->NotifyLink);
245 gEventPending |= (UINTN)(1 << Event->NotifyTpl);
1
2
前文的CoreNotifySignalList只是根据事件组,对改组内的所有事件调用CoreNotifyEvent函数而已。
等待事件发生WaitForEvent
该接口提供等待多个事件的机制,传递进来一个事件数组,返回发生事件的索引
659 EFI_STATUS
660 EFIAPI
661 CoreWaitForEvent (
662 IN UINTN NumberOfEvents,
663 IN EFI_EVENT *UserEvents,
664 OUT UINTN *UserIndex
665 )
666 {
主体部分即一个无限循环(忙等待),直到一个事件发生
685 for(;;) {
686
687 for(Index = 0; Index < NumberOfEvents; Index++) {
688
689 Status = CoreCheckEvent (UserEvents[Index]);
690
694 if (Status != EFI_NOT_READY) {
695 if (UserIndex != NULL) {
696 *UserIndex = Index;
697 }
698 return Status;
699 }
700 }
701
705 CoreSignalEvent (gIdleLoopEvent);
706 }
调用CoreCheckEvent来检查事件是否发生。
检查事件发生CheckEvent
588 EFI_STATUS
589 EFIAPI
590 CoreCheckEvent (
591 IN EFI_EVENT UserEvent
592 )
593 {
事件类型为EVT_NOTIFY_SIGNAL的事件是不允许CheckEvent的,也就意味着不允许被WaitForEvent
607 if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) {
608 return EFI_INVALID_PARAMETER;
609 }
主要返回三个值
- EFI_INVALID_PARAMETER参数违法
- EFI_NOT_READY事件未发生(未被唤醒)
- EFI_SUCCESS事件已发生(已唤醒)
613 if ((Event->SignalCount == 0) && ((Event->Type & EVT_NOTIFY_WAIT) != 0)) {
614
618 CoreAcquireEventLock ();
619 if (Event->SignalCount == 0) {
620 CoreNotifyEvent (Event);
621 }
622 CoreReleaseEventLock ();
623 }
若事件的SignalCount为0且该事件的确是一个EVT_NOTIFY_WAIT类型,则将该事件Norify,即加入到gEventQueue来执行回调函数。
629 if (Event->SignalCount != 0) {
630 CoreAcquireEventLock ();
631
632 if (Event->SignalCount != 0) {
633 Event->SignalCount = 0;
634 Status = EFI_SUCCESS;
635 }
636
637 CoreReleaseEventLock ();
638 }
若SignalCount不为0,则意味着该事件已发生了,将计数置为零后返回成功。
总结
接口简述
SignalEvent唤醒事件,将事件Norify
NorifyEvent回调事件,将事件加入gEventQueue来执行回调函数
WaitForEvent等待事件,循环调用CheckEvent检查事件是否被唤醒,直到事件发生
CheckEvent检查事件,若事件的SignalCount不为0则认为事件已发生,重置为零后返回事件已经发生了
事件类型EVT_NOTIFY_WAIT
该事件在创建的时候(若没有其他类型属性)则不会加入任何链表
该事件在唤醒的时候,即调用SignalEvent时,仅在SignalCount为0的情况下将其增加一,并不会被Norify
事件类型EVT_NOTIFY_SIGNAL
该事件在创建时会被加入gEventSignalQueue,使用IEvent->SignalLink
该事件可以调用SignalEvent来唤醒,当SignalCount为0时事件会被Norify
常见情景
EVT_NORIFY_WAIT类型
创建一个EVT_NORIFY_WAIT类型的事件
使用WaitForEvent来等待该事件的发生,其不断检查SignalCount
使用SignalEvent来唤醒该事件,其将SignalCount增加一
WaitForEvent调用的CheckEvent检查到事件的发生,将事件Notify来执行回调函数,并返回事件已经发生,此时WaitForEvent退出无限循环,继续往下执行
EVT_NORIFY_SIGNAL类型
创建一个EVT_NORIFY_SIGNAL类型的事件,其会被链接到gEventSignalQueue
使用SignalEvent来唤醒该事件,其Norify该事件
补充
gEventQueue保存被Norify的事件,这些事件的回调函数的执行是由时钟中断完成,更具体而言,是通过任务优先级来完成。
通常认为事件被加入到gEventQueue的时候,也就意味着回调函数被执行了。
执行回调函数的接口是CoreDispatchEventNotifies
164 VOID
165 CoreDispatchEventNotifies (
166 IN EFI_TPL Priority
167 )
168 {
获取锁,并得到gEventQueue中的指定优先级的事件链表
172 CoreAcquireEventLock ();
173 ASSERT (gEventQueueLock.OwnerTpl == Priority);
174 Head = &gEventQueue[Priority];
遍历该链表的所有事件,执行其回调函数NorifyFunction
对于EVT_NORIFY_SIGNAL类型的事件,会清除其SignalCount
179 while (!IsListEmpty (Head)) {
180
181 Event = CR (Head->ForwardLink, IEVENT, NotifyLink, EVENT_SIGNATURE);
182 RemoveEntryList (&Event->NotifyLink);
183
184 Event->NotifyLink.ForwardLink = NULL;
185
190 if ((Event->Type & EVT_NOTIFY_SIGNAL) != 0) {
191 Event->SignalCount = 0;
192 }
193
194 CoreReleaseEventLock ();
195
199 ASSERT (Event->NotifyFunction != NULL);
200 Event->NotifyFunction (Event, Event->NotifyContext);
201
205 CoreAcquireEventLock ();
206 }
最后清除gEventPending的位图标记,并释放锁
208 gEventPending &= ~(UINTN)(1 << Priority);
209 CoreReleaseEventLock ();
————————————————
版权声明:本文为CSDN博主「木艮氵」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/stringNewName/article/details/80004887
本文深入解析了UEFI事件的实现机制,包括创建事件、事件类型、信号事件、等待事件和回调函数。讲解了如何使用CreateEvent创建不同类型事件,如EVT_NOTIFY_WAIT和EVT_NOTIFY_SIGNAL,以及如何通过SignalEvent唤醒事件和WaitForEvent等待事件。核心函数如CoreDispatchEventNotifies用于调度和执行回调。此外,还探讨了事件队列、事件组和事件优先级等关键概念。

被折叠的 条评论
为什么被折叠?



