【UEFI基础】Event(二)

本文深入解析了UEFI事件的实现机制,包括创建事件、事件类型、信号事件、等待事件和回调函数。讲解了如何使用CreateEvent创建不同类型事件,如EVT_NOTIFY_WAIT和EVT_NOTIFY_SIGNAL,以及如何通过SignalEvent唤醒事件和WaitForEvent等待事件。核心函数如CoreDispatchEventNotifies用于调度和执行回调。此外,还探讨了事件队列、事件组和事件优先级等关键概念。

事件

每一个事件都是一个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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值