服务型驱动的特点:
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 原理与编程》。。。。