开发 UEFI 驱动

服务型驱动的特点:
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是安装了EDBPHandle。通常这个Handle 就是 driverImageHandle,但并非绝对

EDBP的核心是SupportedStartStop这3个成员函数:
1)Supported 函数用于检测一个设备是否支持该驱动。
2)Start 用于将驱动安装到设备上。
3)Stop用于将驱动从设备上卸载。

Version 是驱动的版本号。在所有支持同一个控制器的 EDBP 中,版本号高的具有较高的优先级,优先被安装到设备上。0x0~0xOF0xFFFFFFF0~0xFFFFFFFF 保留给平台和 OEM 驱动。0x10~0xFFFFFFEF 保留给 IHV 驱动。

Supported 函数
Supported 函数用于检查一个设备控制器是否支持该驱动。如果控制器支持该驱动,则该函数返EFI_SUCCESS,否则返回EFI_UNSUPPORTEDEFI_ACCESS_DENIEDEFI_ALREADY_STARTED等。
在这里插入图片描述

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

Stop 函数
Stop 函数用于停止硬件设备并卸载驱动(调用 UninstallProtocolInterface()UninstallMultipleProtocolInterfaces()ControllerHandle 卸载驱动协议)。
在这里插入图片描述
对设备驱动来讲,NumberOfChildren0ChildHandleBufferNULL。对Bus Driver来讲,如果 NumberOfChildren不为0,那么ChildHandleBuffer 中的子节点都要被释放。

UEFI 驱动程序框架工作:
在这里插入图片描述

SortedDriverBindingProtocols[]数组存放了所有的EDBP实例,并且数组中的Protocol按优先级排序,前面的EDBP被优先测试和安装。从前至后遍历SortedDriverBindingProtocols[]中的EDBP,找到支持该控制器的驱动并安装该驱动,直到没有任何驱动支持这个设备后才退出while 循环。

CoreConnectSingleController 用于为指定的设备控制器安装驱动,它是gBS->Connect-Controller 服务的核心。
在这里插入图片描述
如果 ContextDriverlmageHandles为空,则遍历系统中的所有DriverBindingProtocol,否则就只遍历指定的 DriverBindingProtocolSortedDriverBindingProtocols[]存放了需要测试的 DriverBindingProtocol,对于每一个需要测试的DriverBindingProtocol,首先调用DriverBinding->Supported(...)测试该 DriverBindingProtocol是否支持ControllerHandle,如果Supported 函数返回 EFI_SUCCESS,则调用 DriverBinding->Start(...)ControllerHandle 安装驱动,启动设备。

在这里插入图片描述
CoreDisconnectController对应于gBS->DisconnectController 服务。若DriverImageHandle为空,则卸载 ControllerHandle上的所有驱动。

加载驱动的整个过程:
  1. 在 Shell 中使用命令 Load 将驱动文件加载到内存,加载后 UEFI 会调用 gBS->StartImage(...) 执行 DriverImage 的入口函数;
  2. 在入口函数里,Driver Binding Protocol 被加载到 Handle 上(Driver Image handle 或者其他的 Controller Handle),然后 UEFI 会遍历所有的控制器,为每个控制器调用 CoreConnectSingleController 函数;
  3. CoreConnectSingleController 中会调用 EDBPSupported 函数测试这个驱动是否支持该控制器,如果支持,则调用 Start() 安装驱动。

EFI Component Name Protocol 的作用和构成

通常每个驱动都还有一个可打印的名字,便于向用户显示驱动的信息。这个可打印名字是出 EFI Component Name Protocol (ECNP)EFI Component Name2 Protocol (ECN2P) 提供的。ECNPECN2P不是驱动必需的Protocol

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
SupportedLanguages 是此Protocol所支持的语言列表。这是一个由ISO 639-2语言代码组成的 ASCII 字符串。例如,"zho;eng"表示 ECNP 支持中文和英文;
GetDriverName用于取得驱动程序的名字;
GetControllerName 用于取得控制器或子控制器的名字。若在参数列表中指定了子控制器,则输出参数ControllerName返回子控制器的名字,否则返回控制器ControllerHande的名字。如果对应的驱动是设备驱动,则子控制器ChildHandle 一定为 Null。如果对应的驱动是总线驱动,则该驱动可以有子控制器。

ECNP 使用ISO639-2语言代码,ECNP2 使用RFC4646语言代码,区别仅仅在于语言代码格式不同。

编写设备驱动的步骤

驱动分为两部分,一部分是硬件相关的部分,这部分是驱动的内容,用于驱动硬件设备,为用户提供服务,以协议的形式出现,如DiskloBlocklo
另一部分是驱动的框架部分,需要实现 Driver Binding Protocol,主要是其三个接口(SupportedStartStop),这部分用于驱动的安装与卸载。

(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 WidthWrite 函数用于写PCI配置空间内从偏移 Offset处开始的Count个寄存器,每个寄存器的大小为 Width,写入的总字节数为Count x WidthWidth 必须是EFI_PCI_IO_PROTOCOL_WIDTH中的某一个,其枚举如下:

在这里插入图片描述

如果由OffsetWidthCount指定的地址不被控制器接受,那么Read 函数和 Write 函数将返回 EFI_UNSUPPORTED 错误值。

IO 服务用于读写 PCI 设备的 IO 空间上的寄存器:
在这里插入图片描述

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞大圣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值