在说这个话题之前,我们先看下UEFI当中,我们的代码运行在一个什么样的环境当中,刚开始接触UEFI的时候也很疑惑我们每天看到的代码到底是在一个什么样的硬件平台和模式下工作,经过多方查找资料和请教高人之后得到了一些结果,下面来和大家分享下,这里以X86架构为例。
X86有很多种工作模式这个我们应该都是知道的,但是我们做BIOS的大概之后关注到几个模式:实模式,保护模式。模式切换大概就是从实模式 -->保护模式(32bit)-->保护模式(32bit flat 模式)-->保护模式(X64 flat);在post的过程中,在不同的阶段cpu会出在不同的模式,一般来说在SEC阶段是实模式,在PEI阶段是32bit flat 模式,而在DXE及后续阶段是保护模式的X64 flat模式。同时还需要注意的是在post过程中虽然说我们现代的CPU都是多核心,但是我们一般都是让CPU工作在单核心模式下,并且除了一个定时器中断外,所有的中断都是关掉的,这里需要注意的是这里说的中断是指普通的IRQ至于说NMI,SMI这种是不算在之列的。
好了说了半天的模式问题,现在我们正式来聊下今天的主题Event和Timer,其实timer(核心心跳时钟)的存在主要目的也是为Event来服务所以我们就直接来说Event,timer我们可以在接下来的部分慢慢聊。说白了Event也就是在我们的UEFI的工作环境下提供一个异步的事件通知机制,以此来实现一个类似于操作系统里面的多任务机制。它主要完成以下的几个常见的任务:
1.Implementation of protocols that produce an EFI_EVENT to inform protocol consumers when input is available.
2.Notification when ExitBootServices() is called by an OS Loader or OS Kernel so UEFI Drivers can place devices in a quiescent state or a state that is requiredfor OS compatibility.
3. Notification when SetVirtualAddressMap() is called by an OS Loader or OSKernel so a UEFI Runtime Driver can translate physical addresses to virtual addresses.
4.Timer events used to periodically poll for I/O completion and/or detect timeout conditions.
5.Implementation of protocols that provide non-blocking I/O capabilities where notification of an I/O completion utilizes an EFI_EVENT
从上面的三条来看主要完成了,事件同步;事件通知;周期执行任务;周期检测设备状态然后通知driver来完成操作等功能。
Event服务:
BS提供了三个级别的服务CreateEvent(), CreateEventEx(), and CloseEvent(),他们只要来实现对两种基本的服务的操作:EVT_NOTIFY_SIGNAL,EVT_NOTIFY_WAIT这两种Event的最大差别在于他们的Notify function在何时被执行。
EVT_NOTIFY_SIGNAL:当使用SignalEvent()服务使Event处于signaled 状态的时候就会被执行。
EVT_NOTIFY_WAIT:当使用CheckEvent() or WaitForEvent()服务来检测Event的状态的时候,就会被执行。这种机制用的最多的地方是当BIOS在post过程中需要侦测用户输入的时候,这个情况下我们先creat一个EVT_NOTIFY_WAIT类型的Event,然后再使用CheckEvent() or WaitForEvent()服务来检查是否有用户输入的数据存在。如Simple Text Input Protocols, Pointer Protocols, Simple Network Protocol等等。
有时候我们会希望在OS接管系统的控制权之前在BIOS里面做点什么特别东西,那么我们怎么才能知道OS何时接管系统呢,当然我们有ExitBootServices()服务,这这个服务被调用之后我们的所有的BS的服务和BS data都会消失,那么我们就需要在这个之前来做点特别的事情,这个时候Event就派上用处了,我们可以里面注册一个Event,在OS loader调用ExitBootServices()服务的时候,它会通知所有的监听这个Event的特殊服务去做一些事情。同样UEFI Runtime Driver也可能需要在SetVirtualAddressMap()服务被调用的时候,做一些特殊的动作,这个时候我们也需要Event。我们有些UEFI driver可能需要侦测设备的状态,这个时候使用Event会提高cpu的工作效率,减少等待的时间。所以看起来Event在UEFI的架构中充当了一个比较重要的角色。
Event 虽然说很有用,但是乱用也会导致很多的异常状况发生,一旦发生问题,就很难去定位所以我们必须保持一些使用的原则,也就是需要和CloseEvent服务成对出现:
1.If a UEFI Driver creates events in its
driver entry point, those events must be closed with CloseEvent() in the UEFI Driver's Unload() function
2.If a UEFI Driver creates events in its Driver Binding Protocol Start()function associated with a device, those events must be closed with CloseEvent() in its Driver Binding Protocol
Stop() function.
3.If a UEFI Driver creates events as part of an
I/O operation,
the event should be closed with CloseEvent() when the I
/O operation is completed.
下面是EVT_NOTIFY_WAIT类型,一个键盘读取按键的例子:
EVT_SIGNAL_EXIT_BOOT_SERVICES类型的Event,同时CreateEventEx服务还有支持同时EventGroup,及可以同时调用多个:
SignalEvent()服务:
通过它把Event状态设置为signaled状态。广泛用于Simple Text Input Protocols, Pointer Protocols, Simple Network Protoco以及non-blocking I/O驱动当中.
CheckEvent()服务:
用来检测Event是处于waiting 状态 或者signaled状态,这些状态通常是被SignalEvent()所改变,下面是EVT_TIMER Event的例子,使用CheckEvent()来循环查询等待Event的状态被Timer去signal:
SetTimer()服务:
如上图SetTimer()服务主要是用来设置软件Timer的触发时间间隔和触发类型(周期触发还是单次触发)。
Stall()服务:
在实际的driver或者app开发过程中经常需要延时等待的情况,这个时候就需要使用延时服务,当然延时服务我们可以使用上面的单次触发的Timer event来实现,但是如果我们需要的延时是小于10ms的话,使用timer event就不能实现,这个时候我们就可以使用stall服务。
stall服务是基于定时器的计数延时来实现的,它既可以是用和我们的核心心跳时钟定时器来实现,也可以使用其他的更高的精度的定时器来实现,比如HPET,PM timer等等来实现,但是需要注意的是在使用这些timer来实现stall服务的时候,可能会关掉中断,这就会影响到Event的触发,所以这种情况下,就应该使用Event来实现Stall服务,它也可以使用软件延时来实现比如while(delay),具体得看code的实现方式。stall服务在32bit环境下可以提供1us到1hr的定时,在64bit模式下可以实现1us到500,000 years的延时,但是这个应该是没有必要这么久的延时的。我们应该让延时尽可能的短,这样才不会影响到boot到OS的时间,毕竟这个才是重点。
Task Priority Level(TPL) Services:
上面提到了各种Event在创建的时候都会有一个优先级,关于这个参数我们一直都还没提到,这里来看下什么是优先级,有那些优先级。一般来说我们的优先级有以下几种,优先级从低到高依次如下:
TPL_APPLICATION, TPL_CALLBACK, TPL_NOTIFY, and TPL_HIGH_LEVEL,我们一般的app和driver初始的时候是运行在TPL_APPLICATION上,当event 的notification function被触发的时候,就可以把优先级迁移到高的优先级上去,而这一般是由像Event发信号来触发的。当timer中断发生的时候,如果event列表里面的某个event的时间到期了,这个时候event所注册的notification function就会,中断低优先级的任务,执行高优先级的任务,来做一些特殊的事情,比如检测设备状态,它也可以signal其他的Event,当所有的pending event notification执行完之后,就会返回到TPL_APPLICATION。我们希望他们尽可能的运行在低的TPL上,花尽可能短的时间运行在高TPL上,同时使用RaiseTPL()的时候输入的TPL的优先级必须是高于当前的TPL。但是由于以下的一些原因我们需要把当前的运行优先级使用BS提供的RaiseTPL服务来提高其TPL:
1. the implementation of a service of a
specific protocol requires execution at a specific TPL
to be UEFI conformant.不同的drive需要运行在不同的TPL上,我们需要对不同的driver做不同的设
置,这个可以在UEFI 2.3.1 SPC的6.1章节,表22查的到,下图是部分:
2. UEFI Driver that needs to implement a simple
lock,
or critical section, on
global data structures maintained by the UEFI Driver
在UefiLib使用的信号锁服务,也是用TPL来实现的:
OK,关于Cstyle的UEFI导读之Event and Timer (上篇:实战篇)就先到这儿, Cstyle的UEFI导读之Event and Timer (下篇:原理篇) 会介绍具体的Timer是如何实现的,Event的实现原理。敬请期待!