服务型驱动的特点:
1)在 Image 的入口函数中执行安装;
2)服务型驱动不需要驱动特定硬件,可以安装到任意控制器上;
3)没有提供卸载函数。
一个设备 / 总线驱动程序在安装时首先要找到对应的硬件设备(在 UEFI 中是要找到对应的控制器),然后执行安装操作,将驱动程序安装到硬件设备的控制器上。有时,还需要卸载驱动、更新驱动(先卸载旧的驱动,然后安装新的驱动)。有时,安装操作可能需要执行多次,例如,第一次安装时发现设备没有准备好,或者所依赖的某个Protocol没有安装,就需要退出安装,执行其他操作,然后进行第二次安装。
一个完整的驱动程序框架需要三个部分:
1)Findout():找出对应的硬件设备;
2)Install / Start():安装驱动到指定的硬件设备;
3)Uninstall / Stop():从硬件设备中卸载驱动。
另外,系统中支持该驱动的设备可能不止一个,因而框架必须支持多次安装。通常服务型驱动是不能多次安装的(仅在模块入口函数中执行安装)。
UEFI 驱动模型
UEFI 驱动模型的核心是通过 EFI Driver Binding Protocol 管理驱动程序。
一个完整的驱动程序包含两个核心部分:EFI Driver Binding Protocol 以及驱动服务本身。作为一个用户友好的驱动程序,通常它还要包含一个EFI Component Name Protocol。
EFI Driver Binding Protocol 的构成
在 UEFI 驱动的入口函数中,安装 EFI Driver Binding Protocol (EDBP) 到某个 Handle(大部分情况下是自身,即 ImageHandle,有时也会安装到其他Handle上),这个EBDP实例会常驻内存,用于驱动的安装和卸载。
使用EBDP可以多次操作(查找设备,安装卸载)驱动。

EDBP 有 3 个成员函数和3个成员变量。
成员变量ImageHandle是生成EDBP的映像文件句柄。DriverBindingHandle是安装了EDBP的Handle。通常这个Handle 就是 driver的ImageHandle,但并非绝对。
EDBP的核心是Supported、Start和Stop这3个成员函数:
1)Supported 函数用于检测一个设备是否支持该驱动。
2)Start 用于将驱动安装到设备上。
3)Stop用于将驱动从设备上卸载。
Version 是驱动的版本号。在所有支持同一个控制器的 EDBP 中,版本号高的具有较高的优先级,优先被安装到设备上。0x0~0xOF和 0xFFFFFFF0~0xFFFFFFFF 保留给平台和 OEM 驱动。0x10~0xFFFFFFEF 保留给 IHV 驱动。
Supported 函数
Supported 函数用于检查一个设备控制器是否支持该驱动。如果控制器支持该驱动,则该函数返EFI_SUCCESS,否则返回EFI_UNSUPPORTED、EFI_ACCESS_DENIED或EFI_ALREADY_STARTED等。

Start 函数
Start 函数用来将驱动安装到设备上并启动硬件设备,在函数中最重要的事情是调用InstallProtocolInterface()或者 InstallMultipleProtocolInterfaces()在ControllerHandle上安装驱动 Protocol。

Stop 函数
Stop 函数用于停止硬件设备并卸载驱动(调用 UninstallProtocolInterface() 或 UninstallMultipleProtocolInterfaces() 从 ControllerHandle 卸载驱动协议)。

对设备驱动来讲,NumberOfChildren为0,ChildHandleBuffer为NULL。对Bus Driver来讲,如果 NumberOfChildren不为0,那么ChildHandleBuffer 中的子节点都要被释放。
UEFI 驱动程序框架工作:

SortedDriverBindingProtocols[]数组存放了所有的EDBP实例,并且数组中的Protocol按优先级排序,前面的EDBP被优先测试和安装。从前至后遍历SortedDriverBindingProtocols[]中的EDBP,找到支持该控制器的驱动并安装该驱动,直到没有任何驱动支持这个设备后才退出while 循环。
CoreConnectSingleController 用于为指定的设备控制器安装驱动,它是gBS->Connect-Controller 服务的核心。

如果 ContextDriverlmageHandles为空,则遍历系统中的所有DriverBindingProtocol,否则就只遍历指定的 DriverBindingProtocol。SortedDriverBindingProtocols[]存放了需要测试的 DriverBindingProtocol,对于每一个需要测试的DriverBindingProtocol,首先调用DriverBinding->Supported(...)测试该 DriverBindingProtocol是否支持ControllerHandle,如果Supported 函数返回 EFI_SUCCESS,则调用 DriverBinding->Start(...)向ControllerHandle 安装驱动,启动设备。

CoreDisconnectController对应于gBS->DisconnectController 服务。若DriverImageHandle为空,则卸载 ControllerHandle上的所有驱动。
- 在 Shell 中使用命令
Load将驱动文件加载到内存,加载后 UEFI 会调用gBS->StartImage(...)执行 DriverImage 的入口函数; - 在入口函数里,
Driver Binding Protocol被加载到Handle上(Driver Image handle 或者其他的 Controller Handle),然后 UEFI 会遍历所有的控制器,为每个控制器调用CoreConnectSingleController函数; - 在
CoreConnectSingleController中会调用EDBP的Supported函数测试这个驱动是否支持该控制器,如果支持,则调用Start()安装驱动。
EFI Component Name Protocol 的作用和构成
通常每个驱动都还有一个可打印的名字,便于向用户显示驱动的信息。这个可打印名字是出 EFI Component Name Protocol (ECNP) 或 EFI Component Name2 Protocol (ECN2P) 提供的。ECNP和ECN2P不是驱动必需的Protocol。



SupportedLanguages 是此Protocol所支持的语言列表。这是一个由ISO 639-2语言代码组成的 ASCII 字符串。例如,"zho;eng"表示 ECNP 支持中文和英文;
GetDriverName用于取得驱动程序的名字;
GetControllerName 用于取得控制器或子控制器的名字。若在参数列表中指定了子控制器,则输出参数ControllerName返回子控制器的名字,否则返回控制器ControllerHande的名字。如果对应的驱动是设备驱动,则子控制器ChildHandle 一定为 Null。如果对应的驱动是总线驱动,则该驱动可以有子控制器。
ECNP 使用ISO639-2语言代码,ECNP2 使用RFC4646语言代码,区别仅仅在于语言代码格式不同。
编写设备驱动的步骤
驱动分为两部分,一部分是硬件相关的部分,这部分是驱动的内容,用于驱动硬件设备,为用户提供服务,以协议的形式出现,如Disklo、Blocklo;
另一部分是驱动的框架部分,需要实现 Driver Binding Protocol,主要是其三个接口(Supported、Start和Stop),这部分用于驱动的安装与卸载。
(1) Supported 函数要点
- 1)忽略参数
RemainingDevicePath; - 2)使用函数
OpenProtocol()打开所有需要的Protocol。标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。如果要独占某个Protocol,首先要关闭所有使用该Protocol的其他驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol; - 3)如果 2)中
OpenProtocol()返回错误,则调用CloseProtocol()关闭所有打开的Protocol并返回错误代码; - 4)所需的所有
Protocol成功打开后,测试这个Driver是否支持此Controller。有时使用这些Protocol足以完成测试,有时还需要此Controller的其他特征。如果任一项测试失败,则用CloseProtocol()关闭所有打开的Protocol,返回EFI_UNSUPPORTED。 - 5)测试成功,调用
CloseProtocol()关闭已经打开的Protocol; - 6)返回
EFI_SUCCESS。
(2)Start 函数要点
- 1)忽略参数
RemainingDevicePath。 - 2)使用函数
OpenProtocol()打开所有需要的Protocol。标准的驱动要用EFI_OPEN_PROTOCOL_BY_DRIVER属性打开Protocol。如果要独占某个Protocol,首先要关闭所有使用该Protocol的其他驱动,然后用属性EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE打开Protocol; - 3)如果 2)中
OpenProtocol()返回错误,则调用CloseProtocol()关闭所有已经打开的Protocol并返回错误代码; - 4)初始化
ControllerHandle所指定的设备。如果有错误,则关闭所有已打开的Protocol并返回EFI_DEVICE_ERROR; - 5)分配并初始化要用到的数据结构,这些数据结构包括驱动
Protocol及其他相关的私有数据结构。如果分配资源时发生错误,则关闭所有已打开的Protocol,释放已经得到的资源,返回EFI_OUT_OF_RESOURCES; - 6)用
InstallMultipleProtocolInterfaces()安装驱动协议到ControllerHandle。如果有错误发生,则关闭所有已打开的Protocol,并返回错误代码。 - 7)返回
EFI_SUCCESS。
(3)Stop 函数要点
- 1)用
UninstallMultipleProtocolInterfaces()载所安装的Protocol; - 2)关闭所有已打开的
Protocol; - 3)释放所有已申请的资源。
PCI 设备驱动基础
每个 PCI 设备都有三种地址空间:配置空间、IO 空间和内存空间。
系统初始化时系统会初始化每个 PCI 设备的配置空间寄存器。配置地址空间大小为256字节,前64字节是标准的,后面的寄存器由设备自定义用途。

PCI 设备中的 IO 和内存空间被划分为1~6个互不重叠的子空间,每个子空间用于完成一组相对独立的子功能。BaseAddress0 ~ BaseAddress5表示子空间的基地址(物理地址)。对设备的操作主要是通过对子空间的读写来实现的。
UEFI 提供了EFI_PCI_IO_PROTOCOL(简称PciIo)来操作 PCI 设备:

Pci服务用于读写配置空间:

Pci 服务的Read函数用于读取 PCI 配置空间内从偏移Offset处开始的Count个寄存器,每个寄存器的大小为 Width,读取的总字节数为Count x Width。Write 函数用于写PCI配置空间内从偏移 Offset处开始的Count个寄存器,每个寄存器的大小为 Width,写入的总字节数为Count x Width。Width 必须是EFI_PCI_IO_PROTOCOL_WIDTH中的某一个,其枚举如下:

如果由Offset、Width和Count指定的地址不被控制器接受,那么Read 函数和 Write 函数将返回 EFI_UNSUPPORTED 错误值。
IO 服务用于读写 PCI 设备的 IO 空间上的寄存器:

内容来源于《UEFI 原理与编程》。。。。