三、常见UEFI Module类型
1.UEFI APP
UEFI Application是EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION类型的EFI Image。
UEFI APP从它的Entry Point退出或者返回时,会执行和自动卸载。
OS loader是一种特殊类型的应用程序,通常不会返回或退出。相反,它调用EFI引导服务 gBS->ExitBootServices()
来将平台的控制从固件转移到操作系统。
EFI Shell也是一种特殊的EFI APP,它提供了一个命令行界面来供用户使用。
1.1 UEFI APP介绍
1.1.1 INF 文件
对于UEFI Application, MODULE_TYPE应该是UEFI_APPLICATION。相比于 PEI、DXE、UEFI Driver,UEFI APP没有dependency relationtion部分
- INF示例:
1.1.2 Entry Point
UEFI APP的Entry Point需要在INF文件的 [Defines] 中定义,原型如下:
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
ImageHandle指的是UEFI APP的Image Handle。System Table是EFI System Table。
ImageHandle是单纯的空指针,System Table见下:
- 下面是完整的UEFI_APPLICATION示例和结构体说明:
///
/// A collection of related interfaces.
///
typedef VOID *EFI_HANDLE;
///
/// EFI System Table
///
typedef struct {
///
/// The table header for the EFI System Table.
///
EFI_TABLE_HEADER Hdr;
///
/// A pointer to a null terminated string that identifies the vendor
/// that produces the system firmware for the platform.
///
CHAR16 *FirmwareVendor;
///
/// A firmware vendor specific value that identifies the revision
/// of the system firmware for the platform.
///
UINT32 FirmwareRevision;
///
/// The handle for the active console input device. This handle must support
/// EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL.
///
EFI_HANDLE ConsoleInHandle;
///
/// A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL interface that is
/// associated with ConsoleInHandle.
///
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
///
/// The handle for the active console output device.
///
EFI_HANDLE ConsoleOutHandle;
///
/// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface
/// that is associated with ConsoleOutHandle.
///
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
///
/// The handle for the active standard error console device.
/// This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL.
///
EFI_HANDLE StandardErrorHandle;
///
/// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface
/// that is associated with StandardErrorHandle.
///
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
///
/// A pointer to the EFI Runtime Services Table.
///
EFI_RUNTIME_SERVICES *RuntimeServices;
///
/// A pointer to the EFI Boot Services Table.
///
EFI_BOOT_SERVICES *BootServices;
///
/// The number of system configuration tables in the buffer ConfigurationTable.
///
UINTN NumberOfTableEntries;
///
/// A pointer to the system configuration tables.
/// The number of entries in the table is NumberOfTableEntries.
///
EFI_CONFIGURATION_TABLE *ConfigurationTable;
} EFI_SYSTEM_TABLE;
1.1.3 Get Service Tables
UEFI APP可能采用UEFI Boot Services
、UEFI Runtime Services
和 UEFI System Table
.
UefiBootServicesTableLib
和UefiRuntimeServicesTableLib
,方便开发者访问这些服务。
- 下表列出了这些库提供的全局符号
1.1.4 和UEFI Driver通信
(1)通过Protocol
Uefi Application可以使用如下protocol service来访问Uefi Driver产生的协议接口
- 用于检索协议的服务:
- Uefi APP不可以使用InstallProcotol服务或者相应的库来install protocol。因为UEFI APP在entry point返回以后会被卸载。因此install这个protocol没有意义。
(2)通过Variable
Variable是一对 key/Value,由标识信息加上属性(Key)和任意数据(Value)组成。Variables传递数据,主要为:存储平台实现的EFI环境、EFI OS Loader和在EFI环境中运行的其他应用之间的传递。
UEFI应用程序可以通过UEFI Runtime Services
读写变量GetVariable()
和SetVariable()
。因为UEFI App必须在Dxe/UEFI驱动之后运行,不可缺少Variable Arch protocol。
2.UEFI Driver
满足UEFI Driver Model的Driver被称为UEFI Driver。UEFI Driver初始化的过程中不允许接触任何硬件资源。相反,它会在UEFI驱动的ImageHandle上安装一个EFI_DRIVER_BINDING_PROTOCOL
的实例。
之后,UEFI Driver可能会被EFI_DRIVER_BINDING_PROTOCOL
调用,来对于一个指定的Hardware进行支持性测试。这项测试是为了确定一个driver是否支持一个给定的controller。
该测试在不对controller引起负面影响的前提下,必须越快越好。Controller的大部分初始化都是在EFI_DRIVER_BINDING_PROTOCOL
服务的开始和结束时完成的。
- UEFI Driver和DXE Driver的区别见第六节
2.1 UEFI Driver介绍
2.1.1 INF文件
UEFI Driver的INF file里,[Defines] 部分的Module TYPE
必须指定为UEFI Driver
。
UEFI Driver没有 [Depex] 的部分。因为它一直依赖于DXE架构下的protocols。为了实现这一点,UEFI Driver Entry Point库实例将所有DXE架构下的protocols的依赖关系附加到了Module Image的 [Depex] 部分。
- 示例:
2.1.2 UEFI Driver Entry Point
下表列出了UEFI驱动程序入口点中最常用的Entry Point。
- 如图:
(1)示例:
2.1.3 Get Service Table
UEFI Driver可以使用UEFI Boot Services
、UEFI Runtime Services
和UEFI System Tables
。为此,EDK II 提供了UefiBootServicesTableLib
和 UefiRuntimeServicesTableLib库
,以方便开发者访问。
- Global Variables
2.1.4 UEFI Drivers之间的通信
(1)通过Protocol
UEFI驱动可以使用Protocol访问其他Module产生的Protocol接口
(2)通过Variable
UEFI驱动可以通过UEFI Runtime Services
读取和写入变量GetVariable()
和SetVariable()
。
- UEFI Driver和DXE Driver的区别
UEFI driver 和DXE 的driver的主要区别是:是否遵循UEFI Driver Module
1.UEFI Driver不需要依赖Dependency来决定执行的顺序
2.UEFI Driver必须可以被重复执行
3.UEFI Driver不需要即时启动
4.UEFI Driver支持硬件的热插拔(Hot-Plug)
5.UEFI Driver支持软件的热插拔(Unload)
6.UEFI Driver所有的Function都是Device(Handle)结合Driver(Protocol)
3.SEC Module
SEC模块是上电后执行的第一个模块。它负责配置PEI环境的内存调用堆栈。此外,该模块发现并将控制权传递给PEI Core,将信息传递给PEI Foundation。
3.1.1 INF File
(1)对于一个物理平台来说,Module_Type必须是SEC,对于一个模拟平台来说,可以是SEC,也可以是USER_DEFINED
(2)对于IA32来说,入口一定是**_ModuleEntryPoint**
(3)对于安腾处理器家族平台,入口点是可配置的,例如SAMPLE_ENTRY。但是,这个入口点==应该添加到[BuildOptions]==部分,如下所示:
4.Pre-EFI模块
Pre- EFI模块提供了一个基于标准的平台初始化。PEI阶段的责任是初始化足够的系统,来为接下来的阶段提供一个稳定的基础。
强烈推荐PEI模块只做最小要求的工作来满足后续阶段的要求。PEI Foundation建立了所有PEI Module使用的PEI Service Table。
PEI阶段允许在Memory可用之前,执行C语言代码实现的PEI Module。这是通过配置CPU的资源,例如CPU data cache来实现内存栈。
4.1 PEIM INF 文件
PEI Module文件示例:
4.2 PEI Module的EntryPoint
上述示例中,Entry Point是PeimSampleInitialize
,下面是Entry Point的原型。
FileHandle
是正被调用文件的handle,PeiServices
是PEI Services Table的间接指针
- 示例图:
4.3 获取PEI Services
EDKII 在PEI Services Library Class中提供了所有的PEI Services的API。开发人员可以使用PEI Services Library来调用PEI Services。
EDKII 为PEI Modules提供了PEI Services Table Library,来获取PEI Service Table。除了从PEI Module的Entry Point中的输入函数来获取PEI Services Table指针。EDKII 还允许使用定义在PEI Services Table Pointer Library中的GetPeiServicesTablePointer()
来获取PEI Services Table指针。
4.4 PEIM Modules之间的通信
PEIMs之间有三种通信方式:PPIs、hob和动态pcd。
4.4.1 PPI
PEIM模块可以使用一种称为PEIM-toPEIM Interface (PPI) 的结构来互相通信。每个PPI有一个GUID。PEI Service Table 提供了一些PEI Services来使用PPI的数据库。
在EDKII 中,一个PEIM Module可以通过GUID调用 PeiServicesInstallPpi()
,来Publish自己的PPI Services到PPI database中。另一个PEIM Module可以根据GUID来调用PeiServicesLocatePpi()
,在PPI database中定位PPI Services。
(1)Installing a PPI
如果一个Module A想要publish一个PPI Services的Template(假设其中包括三个API:interface1、2、3). 他可以通过使用PeiserciecesInstallPpi
来Install 这个PPI Template。
- 示例:
(2)Locating a PPI
如果Module B需要调用PPI Template提供的Interface2()
,他可以通过使用下列代码来Locate。
- 示例:
4.4.2 HOB
PEIM Modules可以build一个Hand Off Block(HOB)来传递一些信息给DXE Module和DXE Foundation。此外,其他的PEIMS可以通过使用PEI Service Table中的HOB Services来从HOB中获得相似的信息。
在EDKII 中,Hob Library为PEIMs和DXE Driver提供了通用的接口来使用HOBs。
4.4.3 PCD
PEIM可以通过Dynamic PCD来和其他PEIMs通信。和HOBs一样,只有PEIMs可以获取
动态PCD的值,这些值之前由DXE Driver设定。Get PCD的用法在附录A Dynamic PCD有介绍。
4.5 与DXE Module的通信
4.5.1 HOB
通过使用Hand-Off Block, PEIMs可以将一些信息传递给DXE Foundation和DXE模块,例如在PEI阶段发现的内存信息。
在EDK II中,Hob Library提供了一组接口来帮助构建Hob,例如BuildGuidHob ()
。Hob库还为PEIMs和DXE Drivers提供了一组api来定位Hob。
- 例如:
4.5.2 Variable
PEIMs可以读取以前由DXE驱动程序分配的Variable。PEIMs不能写入Variable。
PEIMs可以使用ReadOnlyVariable2
来获取Variables。
- 具体步骤如下:
(1)定位ReadOnlyVariable2
PPI
(2)当size为0时,调用GetVariable()
来获取Variable的实际大小。
(3)为Variable分配内存空间
(4)再一次以实际大小调用GetVariable()
代码示例:
4.5.3 PCD
PEIMs可以通过动态pcd与DXE驱动程序进行通信。PEIMs可以获得以前由DXE驱动程序设置的动态pcd值。获取PCD的用法见附录A。
4.6 Boot Mode
有时候,PEIMs需要确认boot mode(S3、S5等等),并且根据boot mode采取合适的举动。例如==VariablePei Module
在recovery boot path下时,将不会Install EFI ReadOnlyVariable2Ppi
==。
PEI Service Table提供了一系列的services来设置或者获取boot mode。PEI Service Library中相应的API为:SetBootMode()
and GetBootMode()
。
- get boot mode的示例:
4.7 PEIMs Excution in Place(XIP)
大多数PEIMs都是XIP(Excution in Place:就地执行),并且不被压缩。因为他们在permanent memory之前运行。
在代码的空间复杂性和Module的时间复杂性之间有这样的权衡:保持Modules的小还是保持代码路径的短。
PEIM 代码的数量和复杂性需要简化。例如:对于在Flash上运行的代码来说,需要避免很大的循环。
当PEIM尝试将自己load到system memory并且再次运行时,它可以使用RegisterForShadow ()
来实现。RegisterForShadow ()
在Pei Service Table中。
4.8 PEIMs的Dependency
PEIM必须有Dependency的部分。PEIM在Dependency的条件全部满足以后被dispatch。
如果一个PEIM的Dependency是True,那么其可以立刻被dispatch。在扩展INF文件中,Dependency部分包含在[Depex]部分中。PPI dependency被PPI GUID定义。
- 示例:
该模块只在Read Only Variable2 Ppi, CachePpi and CapsulePpi全部Install完成后dispatch。
5.DXE Drivers(非UEFI Drivers)
DXE驱动指的是满足PI Spec的驱动。PI Spec将DXE驱动分成两类:UEFI驱动模型的驱动和非UEFI驱动(普通DXE Driver)模型的驱动。本节重点是普通DXE Driver。
非UEFI驱动模型的驱动在DXE阶段早期执行。这些驱动是DXE Foundation产生所有要求服务的先决条件。
DXE驱动程序必须设计成不需要不可用的服务。考虑到这一限制,所有可能的工作都应该交由UEFI驱动程序完成。
- UEFI Driver和DXE Driver的区别见第六节
5.1 INF 文件
DXE驱动程序需要扩展INF文件。INF文件的基本介绍请参见第二章。
DXE驱动的 [Defines] 部分应该按照如下修改:
5.2 DXE Driver Entry Point
UEFI驱动程序入口点只允许将protocol实例安装到自己的Image Handle上,不能接触任何硬件。与UEFI驱动程序入口点不同,DXE驱动程序入口点没有这样的限制。它可以将任何protocol安装到system中,并且操控必要的硬件进行软件初始化。
在下面的例子中(来自MdeModulePkg中的WatchDogTimerDxe驱动程序),如果protocol尚未安装,DXE驱动程序入口点将安装它的Architectural Protocol。
- 函数示例:
DXE驱动程序入口点的两个参数是ImageHandle和SystemTable。
5.3获取Services Table
DXE Drivers的Services Table可能会涉及:UEFI Boot Services, UEFI Runtime Services, and DXE Services。此外,DXE Driver还可以参考UEFI System Table。
UEFI Boot Services, UEFI Runtime Services, and UEFI System Table在UEFI Spec中都有定义。DXE Services在PI中有定义。
DXE Driver可以通过下列Library Class提供的全局变量,检索这些tables。
5.4 DXE Drivers之间的通信
DXE Drivers之间的通信方式主要包括:protocol、variable和PCD
5.4.1 Protocol
UEFI Spec定义了一系列的boot services来handle protocols,包括:**install protocol的services **和 检索protocols的services。
- 如图:
首先,要使用这些protocols,Module开发人员必须在INF文件中声明Module使用的protocols,然后写代码来使用这些protocols。
(1)下面的例子演示了DXE Driver如何产生一个protocol
(2)下面演示DXE Driver如何retrieve一个protocol并且调用这个API
5.4.2 Variables
Variables被定义为 一对key/ Value ,这对键值对由key(确认信息加上属性)和 value(任意数据)组成。Variables是为了存放数据而使用,这些数据是在平台安装的EFI环境和EFI环境运行的EFI OS Loader和其他App之间传递的数据。
DXE Driver可以通过UEFI Runtime Services提供的GetVariable()
and SetVariable()
来读写Variable。这两个Services在DXE刚开始的时候并不可用。
需要对易失环境Variables进行只读或者读写的DXE Drivers,必须在INF的dependency中中加入EFI_VARIABLE_ARCH_PROTOCOL
。
需要对非易失环境Variable进行写操作的DXE Driver,必须在INF的dependency中中加入EFI_VARIABLE_WRITE_ARCH_PROTOCOL
。
环境Variable 服务的完整实现在EFI_VARIABLE_ARCH_PROTOCOL
and EFI_VARIABLE_WRITE_ARCH_PROTOCOL
安装之前不可用。
- 对Variable读写的Sample Code示例:
5.4.3 动态PCD
EDK II提供动态pcd作为模块间通信的高级机制。详见附录A。
5.5 DXE Driver和PEIMs的通信
DXE驱动程序与PEIM之间的通信通道,包括HOB、variable、PCD。
5.5.1 HOB
HOB是将数据从PEI传递到DXE的单向通道。HOB列表是在PEI阶段提供的,在DXE阶段必须将其视为只读数据结构。它传递DXE Foundation启动时系统的状态。DXE驱动程序不能修改HOB列表的内容。
HobLib提供了一组api来构建和解析HOB列表。由于DXE驱动程序只读取HOB列表,所以DXE驱动程序的模块编写者可以专注于解析HOB列表的api。
- 下面的例子展示了几种典型的使用类型:
(1)遍历HOB列表中的所有HOB
(2)仅检索HOB列表中特定类型的第一个HOB(以CPU HOB为例)
(3)遍历HOB列表中的特定类型HOB(以CPU HOB为例)
(4)仅检索HOB列表中具有特定GUID的第一个GUIDed HOB
(5)在HOB列表中使用特定的GUID遍历GUIDed HOB
5.5.2 Variable
非易失性变量可以作为从DXE向PEI传递数据的通道。因为只有DXE驱动程序可以写入变量,而PEIM只能读取变量,所以这个从DXE到PEI的通道也是一个单向通道。
5.5.3 动态PCD
非易失性动态PCD也是DXE驱动程序和PEIM之间通信的高级机制。请参考附录A。
5.6 Dependency 表达式
Dependency Expression指定DXE驱动程序需要执行的protocol。EDK II中,在INF文件的[Depex]部分指定。
- 示例(只有在安装了列出的所有四种协议之后才能执行此驱动程序):
5.7 EVT_SIGNAL_EXIT_BOOT_SERVICES
的handle
当操作系统即将完全控制平台时,一些DXE驱动程序需要将它们的控制器置于静止状态或执行其他控制器特定的操作。
在这种情况下,DXE驱动程序应该创建一个信号类型事件,当EFI OS Loader调用gBS->ExitBootServices()
时通知该事件。
此Event的通知功能不允许使用内存分配services,或者调用任何使用内存分配services的函数,并且应该只调用已知没有使用内存分配的函数services,因为这些services修改当前内存映射。
- 通知功能和事件注册模板代码如下:
5.8 DXE Runtime Driver
DXE Runtime Driver可以运行在boot services和runtime services环境下。这意味着这些Modules产生的services在ExitBootServices()
调用前和调用后皆可用,包括OS运行的时候。如果SetVirtualAddressMap()
被调用,那么根据OS提供的虚拟地址映射,这种类型的Modules会被重新定位。
DXE Foundation被认为是一个引导服务组件,所以当ExitBootServices()
被调用时,DXE Foundation也可以被发布。因此,runtime时的驱动程序可能不会使用任何UEFI Boot Services, DXE Services ,或者调用ExitBootServices()
后引导服务驱动程序产生的服务。
DXE runtime driver在INF文件中将MODULE_TYPE
定义DXE_RUNTIME_DRIVER
。此外,因为DXE Runtime Driver在其生存周期中导致了
SetVirtualAddressMap()
。它可能需要为event EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
注册一个event handle。
5.8.1 INF文件
对于DXE Runtime Driver,Module type应该是DXE Runtime Driver
。
-
示例:
5.8.2 Virtual Address Event的Handle
当OS调用SetVirtualAddressMap()
,可能需要通知DXE Runtime Driver。在此情况下,DXE Runtime Driver必须创建一个signal类型的Event,当SetVirtualAddressMap()
被OS调用的时候,该Event将被通知。调用SetVirtualAddressMap()
允许DXE Runtime Driver讲指针从物理地址翻转为虚拟地址。
DXE Runtime Driver创建的signal类型的Event所用到的通知函数不允许直接或者间接使用UEFI Boot Services、**UEFI Console Services **或者 UEFI Protocol Services 。因为当调用SetVirtualAddressMap()
之时,这些Services都将不可用。
通常,DXE Runtime Driver创建的signal类型的Event所用到的通知函数使用
ConvertPointer()
来将指针从物理转换为虚拟。
- 通知函数和Event注册模板代码如下:
5.9 DXE SMM Driver
DXE SMM Driver这种类型由load到SMRAM中的SMM Driver使用。因此这种类型只有IA32和X64 CPU可用。这些Module由SMM Foundation分配,并且永远不会损坏。这意味着这些Module产生的services在ExitBootServices()
. 之后依然可用。 SMM Driver的生命周期可以被分为两个阶段:SMM初始化和SMM Runtime,两个阶段有不同的限制条件。
SMM初始化是SMM驱动程序初始化的阶段,它从调用直到驱动程序的入口点开始,并从驱动器的入口点返回。
SMM Runtime是SMM Driver初始化的阶段。这个阶段在驱动入口点返回以后。
5.9.1 INF 文件
对于SMM驱动程序,MODULE_TYPE为DXE_SMM_DRIVER。
- 示意图:
5.9.2 限制条件
SMM驱动程序模型有类似于DXE运行时驱动程序的约束。
在SMM Runtime运行期间,drivers可能不可以使用内核protocol services。有相关的SMST based services供Drivers使用。但是UEFI System Table和其他在boot services阶段之间安装的protocols可能并不一定可用。
在SMM 初始化期间,UEFI Boot Services、UEFI Runtime Services、SMST-based services都是可用的。
5.9.3 SMM Driver初始化
当Driver加载到SMRAM并且Driver的Entry Point被调用时,SMM Driver的初始化阶段就开始了。SMM Driver的初始化阶段终止于其Entry Point的return。简单来说,整个SMM Driver初始化的过程就是SMM Driver Entry Point运行的整个过程。
在SMM Driver初始化的过程中,SMM Driver可以使用两类protocol:UEFI protocol和SMM protocol。
UEFI Protocol指的是Install和discover时使用UEFI Boot Services的protocols。SMM Driver只有在初始化的过程中,才可以locate和使用UEFI Protocols。
SMM Protocols指的是使用System Management Services Table(SMST)来install和discover的protocols。
在SMM Drivers初始化期间,SMM Drivers不允许使用UEFI Boot Services Exit()
and ExitBootServices()
。
5.9.4 SMM Driver Runtime
SMM Driver Runtime期间,SMM Driver只允许使用SMST-based Services。此外,对于不同的平台体系而言,SMM Driver可能没有权限使用SMRAM以外的内存区域。同样,UEFI Drivers可能不允许使用SMRAM里的内存区域。
这些SMM Driver Runtime的特性,导致了关于UEFI Services用法的一些限制。
(1)在SMM Driver初始化期间locate的Interface和services,在SMM Driver Runtime期间不可以被调用或者引用。
(2)SMM Driver 初始化期间创建的 Events必须在 Driver Entry Point退出之前关闭。
6.UEFI Driver和DXE Driver的区别和联系
UEFI Driver和DXE Driver的主要区别在于:是否满足UEFI Driver Model。
DXE Driver是在编写驱动的时候主动寻找设备并且对其进行初始化;UEFI Driver则是系统服务自己根据设备寻找合适的驱动,然后对其进行初始化。前者一般在驱动运行的时候就直接完成。后者需要先对驱动进行注册,然后通过调用系统服务来完成初始化。
- 两种类型的示意图:
6.1 两种Driver的Entry Point
DXE Driver和UEFI Driver的代码结构类似,主要区别在于:Driver的Entry Point做了什么。
6.1.1 DXE Driver
在一个DXE Driver的INF文件中,Entry Point
是设备初始化的函数。
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = DxeDriverInBds
FILE_GUID = 04687443-0174-498F-A2F9-08F3A5363F84
MODULE_TYPE = UEFI_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = DxeDriverEntry
6.1.2 UEFI Driver
UEFI Driver只是安装了一个Protocol。以SnpDxe这个Module为例,gSimpleNetworkDriverBinding
这个Protocol就是这个设备在DXE阶段安装的Protocol。所有符合UEFI Driver Model的驱动都会安装一个类似的Protocol。
简而言之,UEFI Driver就是在DXE阶段安装了这样的Protocol,然后在 gBS -> Connect Controller
的时候。首先将会执行 xxSupported()
函数,根据 EFI SUCCESS返回值继续执行 xxStart()
函数,该 xxStart()
函数中就实现了设备初始化的代码。
-
SnpDXE.inf的
xxSupported()
函数和主要流程如下:-
当扫描的这个设备的时候(设备用Controller表示),先判断它是否已经Install了
DevicePathProtocol
,没有就表示这个设备还没有准备好(或者说不是设备),后面的xxxStart()
不用执行; -
然后判断
NetworkInterfaceIdentifierProtocol
是否安装,这个是网卡驱动一定会装的Protocol,Snp驱动底层的操作需要依赖于它,所以一定要安装,如果没有就不会执行后面的操作; -
判断NetworkInterfaceIdentifierProtocol是否满足要求,如果不满足则不会执行
xxxStart()
函数。
-
如果以上条件都满足,就可以认为该设备是一个网卡,然后这个驱动就会被执行(执行xxxStart()
函数),而之前获取到的DevicePathProtocol和NetworkInterfaceIdentifierProtocol就会成为操作正确设备的基础。
EFI_STATUS
EFIAPI
SimpleNetworkDriverSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath
)
{
EFI_STATUS Status;
EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *NiiProtocol;
PXE_UNDI *Pxe;
Status = gBS->OpenProtocol (
Controller,
&gEfiDevicePathProtocolGuid,
NULL,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = gBS->OpenProtocol (
Controller,
&gEfiNetworkInterfaceIdentifierProtocolGuid_31,
(VOID **) &NiiProtocol,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
if (Status == EFI_ALREADY_STARTED) {
DEBUG ((EFI_D_INFO, "Support(): Already Started. on handle %p\n", Controller));
}
return Status;
}
DEBUG ((EFI_D_INFO, "Support(): UNDI3.1 found on handle %p\n", Controller));
//
// check the version, we don't want to connect to the undi16
//
if (NiiProtocol->Type != EfiNetworkInterfaceUndi) {
Status = EFI_UNSUPPORTED;
goto Done;
}
//
// Check to see if !PXE structure is valid. Paragraph alignment of !PXE structure is required.
//
if ((NiiProtocol->Id & 0x0F) != 0) {
DEBUG ((EFI_D_NET, "\n!PXE structure is not paragraph aligned.\n"));
Status = EFI_UNSUPPORTED;
goto Done;
}
Pxe = (PXE_UNDI *) (UINTN) (NiiProtocol->Id);
//
// Verify !PXE revisions.
//
if (Pxe->hw.Signature != PXE_ROMID_SIGNATURE) {
DEBUG ((EFI_D_NET, "\n!PXE signature is not valid.\n"));
Status = EFI_UNSUPPORTED;
goto Done;
}
if (Pxe->hw.Rev < PXE_ROMID_REV) {
DEBUG ((EFI_D_NET, "\n!PXE.Rev is not supported.\n"));
Status = EFI_UNSUPPORTED;
goto Done;
}
if (Pxe->hw.MajorVer < PXE_ROMID_MAJORVER) {
DEBUG ((EFI_D_NET, "\n!PXE.MajorVer is not supported.\n"));
Status = EFI_UNSUPPORTED;
goto Done;
} else if (Pxe->hw.MajorVer == PXE_ROMID_MAJORVER && Pxe->hw.MinorVer < PXE_ROMID_MINORVER) {
DEBUG ((EFI_D_NET, "\n!PXE.MinorVer is not supported."));
Status = EFI_UNSUPPORTED;
goto Done;
}
//
// Do S/W UNDI specific checks.
//
if ((Pxe->hw.Implementation & PXE_ROMID_IMP_HW_UNDI) == 0) {
if (Pxe->sw.EntryPoint < Pxe->sw.Len) {
DEBUG ((EFI_D_NET, "\n!PXE S/W entry point is not valid."));
Status = EFI_UNSUPPORTED;
goto Done;
}
if (Pxe->sw.BusCnt == 0) {
DEBUG ((EFI_D_NET, "\n!PXE.BusCnt is zero."));
Status = EFI_UNSUPPORTED;
goto Done;
}
}
Status = EFI_SUCCESS;
DEBUG ((EFI_D_INFO, "Support(): supported on %p\n", Controller));
Done:
gBS->CloseProtocol (
Controller,
&gEfiNetworkInterfaceIdentifierProtocolGuid_31,
This->DriverBindingHandle,
Controller
);
return Status;
}
6.2 两种驱动的开始处
6.2.1 DXE Driver
驱动被调用的基本函数是LoadImage()
和StartImage()
,这两个函数都是Boot Service,所以可以在DXE和BDS阶段的大部分地方调用。
DxeMain.c里面的DxeMain()函数,其中的CoreDispatcher()
就是用来执行各个驱动的,除非自己写代码,否则DXE驱动都会在这个位置执行。
6.2.2 UEFI Driver
之前已经提到,ConnectController()函数里面会执行驱动的xxxSupported()函数,对应调用位置如下:
do {
//
// Loop through the sorted Driver Binding Protocol Instances in order, and see if
// any of the Driver Binding Protocols support the controller specified by
// ControllerHandle.
//
DriverBinding = NULL;
DriverFound = FALSE;
for (Index = 0; (Index < NumberOfSortedDriverBindingProtocols) && !DriverFound; Index++) {
if (SortedDriverBindingProtocols[Index] != NULL) {
DriverBinding = SortedDriverBindingProtocols[Index];
PERF_START (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
Status = DriverBinding->Supported(
DriverBinding,
ControllerHandle,
RemainingDevicePath
);
PERF_END (DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
if (!EFI_ERROR (Status)) {
SortedDriverBindingProtocols[Index] = NULL;
DriverFound = TRUE;
//
// A driver was found that supports ControllerHandle, so attempt to start the driver
// on ControllerHandle.
//
PERF_START (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);
Status = DriverBinding->Start (
DriverBinding,
ControllerHandle,
RemainingDevicePath
);
PERF_END (DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);
if (!EFI_ERROR (Status)) {
//
// The driver was successfully started on ControllerHandle, so set a flag
//
OneStarted = TRUE;
}
}
}
}
} while (DriverFound);
四、附录
1.Dynamic PCD
Dynamic类型的PCD用来动态配置或者设置值。相比之下,Static类型的PCD如FeatureFlag, FixedPcd, PatchablePcd等等在build时已经在最后生成的FD image中被固定了。
Dynamic 决定意味着下列三种情况中的一种。
(1)PCD 设定值由Driver在执行过程中产生和使用。
(2)PCD 设定值可由用户在setup中配置
(3)PCD 设定值由平台OEM供应商在指定区域中产生。
2.什么时候使用Dynamic PCD
Module开发人员在写Source Code或者INF时,并不关心PCD是Dynamic还是Static。Dynamic PCD和Dynamic类型由平台整合商在平台DSC文件中指明。
3.Dynamic Type的类型
根据Module分发的方式,Dynamic PCD可以被分为以下几种类型:
3.1 Dynamic
如果Module在Source Code中发布,并且将以平台DSC编译,那么这个Module使用的Dynamic PCD可以用这样的方式访问:PcdGetxx(PcdSampleDynamicPcd)
在编译平台,编译工具将PcdSampleDynamicPcd
翻译为参数 Token Space Guid:Token Number
。
3.2 Dynamic Ex
如果一个Module以binary形式release,并且没有包含在平台编译中,那么这个Module使用的Dynamic PCD必须以这样的方式访问:PcdGetxxEx(gEfiMyTokenspaceGuid, PcdSampleDynamicPcd)
3.3 Default Storage
PCD值保存在PCD数据库中,PCD数据库由PCD Driver在boot time memory中维护。Default Storage类型用来在PEIM和DXE Drivers或者DXE和DXE Drivers中通信。所有的Set或者Get的值在boot time memory关闭后将会丢失。
[PcdsDynamicDefault]
在平台DSC文件中,被作为此类型的PCD的名字。[PcdsDynamicExDefault]
用于dynamicEx类型的pcd。
3.4 Variable Storage
此种PCD值保存在Variable区域。作为默认的存储类型,这种类型的PCD可以被PEI DXE的驱动通信使用。除此以外,这种类型的PCD也可以被用来保存通过Variable Interface的HII所设置的相关的值。
在PEI阶段,这个PCD值可以被获得但是不可以被设置,因为这个Variable区域是只读的。
[PcdsDynamicHii]被用作平台DSC文件中这种类型的PCD的节名。
[pcdsdynamicexhibit]表示PCD的dynamicEx类型。