BIOS知识枝桠——Event


UEFI不再为开发者提供中断支持,但在UEFI内部还是使用了时钟中断,所有的异步操作都要通过事件(Event)完成。在Legacy BIOS中,要处理异步事件或实现一些特定的功能,需要通过中断模式

传统的中断模式

常用中断分为硬件中断和软件中断

硬件中断

  • 敲键盘事件:
    • 敲键盘→键盘信号传输给EC→EC产生硬件中断 (IRQ1)到CPU→中断信号输入到CPU的中断控制器→OS执行对应的软件代码来获取键盘按键
  • 插入AC事件:
    • 插入AC→信号传输给EC→EC产生硬件中断 (SCI)到CPU的特定GPIO pin→中断信号输入到CPU的中断控制器→OS执行对应的软件代码来获取AC状态

软件中断

  • 比如调用显示服务,通过INT10h中断
    显示服务(Video Service – INT10H)
功能号功能
00H设置显示器模式0CH写图形像素
01H设置光标形状0DH读图形像素
02H设置光标位置0EH在Teletype模式下显示字符
03H读取光标信息0FH读取下面是其模式
04H读取光笔位置10H颜色
05H设置显示页11H字体
06H、07H初始化或滚屏12H显示器的配置
08H读光标出的字符及其属性13H在Teletype模式下显示字符串
09H在光标处按指定属性显示字符串1AH读取、设置显示组合编码
0AH在当前光标处显示字符串1BH读取功能、状态信息
0BH设置调色板,背景色或者边框1CH保存、恢复显示器状态

Event Overview

  • UEFI中没有使用中断
  • UEFI是一个单线程的系统,如果没有中断,则所有UEFI任务只能被依次执行,类似一个FIFO的任务队列。
  • Event (事件)就是当某个条件满足时, 就去执行某一段代码, 比如:
    • 当某个protocol被install时, 去执行一段代码
    • 当定时器每一秒时, 去执行一段代码
    • 当手动触发某一个事件时, 去执行一段代码
  • 当代码执行完成, 就回到原来被中断的代码处继续执行
  • Event机制也有一些扩展功能,比如在特定的条件下,一次性执行多个任务(EventGroup),节约系统资源和简化调用复杂度。
  • Event机制通过设定任务优先级(TPL)来控制任务执行时机
事件类型事件特征
EVT_TIMER定时器事件。普通Timer事件,没有Notification函数。生成事件后需调用SetTimer服务设置时钟属性。事件可以:通过SetTimer()设置等待事件、到期后通过SignalEvent()触发、通过WaitForEvent()等待事件被触发、通过CheckEvent()检査状态
EVT_NOTIFY_WAIT普通事件。这个事件有一个Notification函数,当这个事件通过CheckEventO检査状态或通过WaitForEvent()等待时,这个Notification函数会被放到待执行队列 gEventQueue[Event->NotifyTpl]中
EVT_NOTIFY_SIGNAL普通事件。这个事件有一个Notification函数,当这个事件通过SignalEvent()被触发时,这个Notification函数会被放到待执行队列gEventQueue[Event->NotifyTpl]中等待执行
0x00000000普通事件。此类事件没有Notification函数。事件可以: 通过SignalEvent()被触发、通过WaitForEvent()等待事件被触发 、通过CheckEventO检査状态
EVT_TIMER EVT_NOTIFY_WAIT带Notification函数的定时器事件。此类事件除了具有EVT_TIMER的特性外, 还有EVT_NOTIFY_WAIT的特性,即到期后通过SignalEvent()触发。当事件通过CheckEventO检査状态或通过WaitForEvent()等待时,这个Notification 函数会被放到待执行队列 gEventQueue[Event->NotifyTpl]中
EVT_TIMER EVT_NOTIFY_SIGNAL带Notification函数的定时器事件。此类事件除了具有EVT_TIMER的特性外, 还有EVT_NOTIFY_WAIT的特性,即到期后通过SignalEvent()触发。当事件通过SignalEvent()被触发时,这个Notification函数会被放到待执行队 列 gE ventQueue [ Event->N otify Tpl]中

Event Function

启动服务为开发者提供了下表所示的函数,用于操作事件、定时器及TPL (任务优先 级)。这些函数可以分为三类:事件相关函数、定时器相关函数及TPL相关函数。

函数名作 用
CreateEvent生成一个事件对象
CreateEventEx生成一个事件对象并将该事件加人到一个组内
CloseEvent关闭事件对象
SignalEvent触发事件对象
WaitForEvent等待事件数组中的任一事件触发
CheckEvent检査事件状态
SetTimer设置定时器属性
RaiseTPL提升任务优先级
RestoreTPL恢复任务优先级
//生成一个事件
typedef 
EFI_STATUS  (EFIAPI *EFI_CREATE_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                          //生成的事件
);

NotifyFunction

  • 当Event类型为EVT_NOTIFY_WAIT或EVT_NOTIFY_SIGNAL时,此function有效。
  • EVT_NOTIFY_WAIT类型的Event,在等待过程中,NotifyFunction被调用。
  • EVT_NOTIFY_SIGNAL类型的Event,Event被Signal时,NotifyFunction被调用。
  • NotifyFunction原型如下:
typedef VOID(EFIAPI *EFI_EVENT_NOTIFY)(
IN EFI_EVENT Event,         //拥有此函数的事件
IN VOID *Context            //上下文指针:此指针在CreateEvent时设置
);

CreateEventEx

  • 相比较CreateEvent,它多一个EventGroup参数。其它的参数均和CreateEvent一致。
  • 它会将创建成功的Event存在这个EventGroup(一个GUID类型的指针)。
  • 当组内的任意Event被触发,则所有的Event都会被触发,所有的NotifyFunction函数都会被加入到执行队列,并按优先级高低来执行。
  • 当传入的EventGroup为空时,则退化为CreateEvent函数。
  • EVT_SIGNAL_EXIT_BOOT_SERVICES和EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE这两种Type是无效的,因为已经有这两个EventGroup。
// 为 EventGroup 生成一个 Event
typedef EFI_STATUS (EFIAPI *EFI_CREATE_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                          //生成的事件

UEFI目前预定了四种EventGroup:

  • EFI_EVENT_GROUP_EXIT_BOOT_SERVICES
  • EFI_EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE
  • EFI_EVENT_GROUP_MEMORY_MAP_CHANGE
  • EFI_EVENT_GROUP_READY_TO_BOOT
  • 也可以自定义EventGroup
  • 比如使用EFI_ACPI_TABLE_GUID,当ACPI Table data更改,执行InstallConfigurationTable()时,所有该组的Event都会被触发

CheckEvent

typedef EFI_STATUS (EFIAPI *EFI_CHECK_EVENT) ( IN EFI_EVENT Event);
  • 用来Check某个Event的触发状态。
  • 如果是EVT_NOTIFY_SIGNAL类型的Event,则返回EFI_INVALID_PARAMETER。
  • 如果Event处于触发态,则返回EFI_SUCCESS,并且返回前重置为非触发态。
  • 如果Event处于非触发态且无NotifyFunction,则返回EFI_NOT_READY。
  • 如果Event处于非触发态且有NotifyFunction,则将NotifyFunction加入执行队列,之后重新check状态,如果执行NotifyFunction导致Event处于触发态,则重置状态后返回EFI_SUCCESS,否则返回EFI_NOT_READY

SignalEvent

typedef EFI_STATUS SignalEvent ( IN EFI_EVENT Event );
  • 用于将给定的Event置为触发态。如果Event类型为EVT_NOTIFY_SIGNAL,则将其NotifyFunction加入到执行队列,如果该Event属于某个EventGroup,则将该Group里的所有Event都置为触发态,且将所有的NotifyFunction都加入到执行队列。
  • 当需要Signal一个EventGroup时,可以给该Group创建一个Event,之后去Signal此Event,最后再将此Event Close掉,从Group中移除

CloseEvent

typedef EFI STATUS CloseEvent ( IN EFI EVENT Event);
  • 当Event使用完毕后,必须调用CloseEvent来关闭Event。
  • 当某个Event被close之后,如果它属于某个EventGroup,则会从该Group中被移除。
  • 当Event被Close之后,会释放掉它的内存,并从内核队列中被移除,Event将不再有效,也不会被任何队列调用。

Event Use

ReadyToBootEvent

  • ReadyToBootEvent是一个UEFI预定义的EventGroup。它用来给UEFI在Boot OS之前做最后的操作使用。
  • Boot OS之前,也需要将整个EventGroup signal。
    在这里插入图片描述
    RegisterProtocolNotify
  • 它用来创建一个Event,使得当给定的Protocol被install或ReInstall的时候,对应的Event被触发
    它的用法一般分两步:
    • 创建一个需要被执行的Event,以及它的NotifyFunction。
    • 使用RegisterProtocolNotify来指定特定的Protocol,以便Protocol被Install时可以触发Event。
      下面是一个实例
    • 当gEfiLenovoEaiaInterfaceProtocolGuid protocol在EaiaDxeEntryPoint()里被Install的时候,会触发ProtocolEvent,随之该Event的NotifyFunction(InitRemovableBattery)被加入执行队列执行。
      在这里插入图片描述
      EfiNamedEventListen/ EfiNamedEventSignal
  • 前者的作用是创建一个Event,并使用RegisterProtocolNotify将这个Event Register,Event在等待指定的Protocol被Install。
  • 后者的作用是Install前者指定的Protocol,使创建的Event被触发,以执行Event的NotifyFunction。两者成对出现。
    在这里插入图片描述

TPL

优先级用来区分事件的先后执行顺序,它可以是0 ~ 31的一个整数。UEFI预定义了以下4个优先级。
UEFI不提供中断接口,但是UEFI又是一个单线程的,所以为了更好的协调各个任务,利用CPU等各类资源,UEFI引入了任务优先级的概念。

  • 比如定时器Event的NotifyFunction的优先级要大于一般的UEFI_APPLICATION。
  • gBS->Stall这种和时钟有关的任务要大于任何任务的优先级。
  • UEFI使用RaiseTPL/RestoreTPL来提升和恢复任务的优先级。
  • RaiseTPL和RestoreTPL一般成对出现,并且一旦任务优先级被Raise之后,要尽快的Restore,以免影响其他任务的执行。
  • 当任务的优先级被提升至TPL_HIGH_LEVEL时,时钟中断将被关闭,等优先级恢复后,中断会被重新打开。
  • 任务的优先级恢复到原优先级之前,所有高于原优先级的处于触发态事件的NotifyFunction都要被执行完毕。
 #define TPL_APPLICATION  4
 #define TPL_CALLBACK     8
 #define TPL_NOTIFY       16
 #define TPL_HIGH_LEVEL   31
优先级用法相关函数
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、Unloadlmage()、Variable Services、 Network Service Binding. Network Protocol下列函数运行在低于8的级别:Loadlmage()、Startlmage()
TPL_NOTIFY运行在这个级别的程序不允许阻塞, 必须尽快执行完毕并且返回。如果需要更多操作,则需要使用Event由内核重新调度。通常,底层的IO操作允 许在这个级别,例如UEFI内核中读取键盘状态的代码。大部分Event的Notification函数允许在这个级别下列函数运行在此级别或更低级别:Protocol Handler Services、Memory Allocation Services、Simple Text Output Protocol、ACPI TableProtocol、User Manager Protocol、User Credential Protocolx User Info Protocolx Authentication Info、 Device Path Utilities、Device Path From Text、 EDID Discovered、EDID Active> Graphics OutputEDID OverrideiSCSI Initiator Name、Tape IO、 Deferred Image Load Protocol、HII ProtocolssDriver Health下列函数运行在低于16的级别:ACPI Table Protoco
TPL_HIGH_LEVEL优先级最高级别。在此级别,中断 被禁止。UEFI内核全局变量的修改需 要允许在这个级别下列函数运行在此级别或更低级别:SignalEvent()、Stall()下列函数运行在低于31的级别:CheckEvent()、CloseEvent()> CreateEvent()、 SetTimer()5Event Notification Levels 运行在(TPL_ APPLICATION, TPL_HIGH_LEVEL)区间的优 先级上

RaiseTPL用来将任务优先级提升至NewTPL,返回值为任务原优先级。
RestoreTPL用于恢复(通常是降低)任务优先级至原来的优先级。

typedef EFI_TPL (EFIAPI *EFI_RAISE_TPL) ( IN EFI_TPL NewTpl );
//恢复当前任务任务优先级至原来的OldTpl
typedef VOID (EFIAPI *EFI_RESTORE_TPL) ( IN EFI_TPL OldTpl )

Timer

定时器是一类特殊的事件,生成定时器事件后,可以通过SetTimer服务设置定时器属性。

/**设置定时器属性(类型及时间>
Qretval EFI_SUCCESS               属性设置成功
Qretval EFI_INVALID_PARAMETER     参数Event不是EVT_TIMER,或参数Type非法
**/
typedef EFI_STATUS (EFIAPI *EFI 
IN EFI_EVENT Event,     // Timer 事件
IN EFI_TIMER_DELAY Type,//定时器类别
IN UINT64 TriggerTime   //定时器过期时间,100ns为一个单位
);

定时器的类别有三种:TimerCance、TimerPeriodic 、TimerRelative。

Type取值作 用
TimerCancel用于取消定时器触发时间。设置后定时器不再触发
TimerPeriodic重复型定时器。每TriggerTime* 100ns,定时器触发一次
TimerRelative一次性定时器。TriggerTime* 100ns时触发
  • 定时器事件的类型必须是EVT_TIMER
  • 如果调用SetTimer时
    - 定时器类型为TimerPeriodic,且TriggerTime为0,则Event在每个时钟Tick时被触发一次。
    - 定时器类型为TimerRelative,且TriggerTime为0,则Event在下一次始终Tick时被触发。
  • 使用定时器事件一般分两步:
    - 使用CreateEvent创建一个EVT_TIMER类型的Event。
    - 使用SetTimer设置此Event的定时器属性。
  • 定时器事件也可以和EVT_NOTIFY_SIGNAL类型搭配使用。
    - 创建Event的时候,Type类型设定为EVT_TIMER|EVT_NOTIFY_SIGNAL。
//生成Timer事件,i设置触发函数
Status = gBS->CreateEvent(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_CALLBACK, EFI_EVENT一NOTIFY)myEventNoify30, (VOID*)LnHello, Time Out!nr &myEvent); 
//设置"Timer^待时间为10秒,属性为循环等待
Status = gBS->SetTimer(myEvent,TimerPeriodic , 10 * 1000 * 1000);
WaitKey();
Status = gBS->CloseEvent(myEvent);

系统时钟中断驱动的定时器及相关函数
DXE Foundation 提供了时钟中断的服务例程,但设计上DXE Foundation 是硬件无关的,所以它不能直接操作系统时钟中断,而是依赖Timer Architectural Protocol 来设置自己的时钟中断服务。位于DxeProtocolNotify.c

  //
  // Do special operations for Architectural Protocols
  //

  if (CompareGuid (Entry->ProtocolGuid, &gEfiTimerArchProtocolGuid)) {
    //
    // Register the Core timer tick handler with the Timer AP
    //
    gTimer->RegisterHandler (gTimer, CoreTimerTick);
  }

从上面这段代码 可以看出,当DXE Foundation 发现Timer Architectural Protocol 被安装,就会调用Timer Artchitectural Protocol 的RegisterHandler() 函数去注册自己的时钟中断服务函数CoreTimerTick() , 该函数会在每次时钟中断发生时被调用 。

UEFI 的时钟处理函数是 CoreTimerTick,位于 MdeModulePkg\Core\Dxe\Event\Event. c文件中。它在时钟中断中调用,是时钟中断处理函数的主体。该函数执行期间必须关中 断并且不能被其他任何任务干扰,因而进入函数时需要加锁,离开函数时需要解锁。它的 主要功能是维持系统时间,检查定时器事件列表中是否有到期的事件。

VOID
EFIAPI
CoreTimerTick (
  IN UINT64   Duration
  )
{
  IEVENT          *Event;

  //
  // Check runtiem flag in case there are ticks while exiting boot services
  //
  CoreAcquireLock (&mEfiSystemTimeLock);

  //
  // Update the system time
  //
  mEfiSystemTime += Duration;

  //
  // If the head of the list is expired, fire the timer event
  // to process it
  //
  if (!IsListEmpty (&mEfiTimerList)) {
    Event = CR (mEfiTimerList.ForwardLink, IEVENT, Timer.Link, EVENT_SIGNATURE);

    if (Event->Timer.TriggerTime <= mEfiSystemTime) {
      CoreSignalEvent (mEfiCheckTimerEvent);
    }
  }

  CoreReleaseLock (&mEfiSystemTimeLock);
}

首先,CoreTimerTick() 取得保护mEfiSystemTime 全局变量的锁。DXE Foundation 用mEfiSystemTime 全局变量追踪时钟中断启动以来经过的时间(以100 ns) 。每次时钟中断发生时,此次时钟中断距上次时钟中断的时间差被加入mEfiSystemTime.

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值