前面介绍了UEFI 中两种DXE Driver的函数实现以及主要区别,同时最后也说过在DXE阶段加载driver的过程中,UEFI 类型的Driver并没有被真正的执行。接下来需要详细研究的就是UEFI Driver的具体执行过程。
UEFI Driver执行的阶段
DXE阶段是UEFI Driver的被加载到内存中,而UEFI Driver中的support start stop函数真正被执行是在BDS阶段,我们可以通过EDKII代码中输出的DEBUG信息来追踪 PciBusDxe driver的执行位置
DEBUG信息中的具体位置如下
可以清楚的看到,
PCI枚举执行的实际过程是在Bds开始执行之后。
类似的也可以依据debug信息来追driver的执行过程。
BdsEntry 函数的执行表示UEFI正式进入了BDS阶段。在BdsEntry 函数中可以找到这样一段内容
深入看
PlatformBootManagerAfterConsole
函数,在这个函数刚开始执行的阶段调用了
VisitAllInstancesOfProtocol
VisitAllInstancesOfProtocol
的 入参有两个:
gEfiPciRootBridgeIoProtocolGuid
和
ConnectRootBridge
,
其中
ConnectRootBridge
函数就是真正执行了 PciBusDxe driver的函数
,在对这个函数进行详细的介绍之前简单看一下
VisitAllInstancesOfProtocol
是如何处理这两个入参的
VisitAllInstancesOfProtocol
VisitAllInstancesOfProtocol函数实现整体可以分成两个部分
- 根据入参 – GUID 查找所有安装了该GUID protocol的Handle
- 依次在Handle找到GUID对应的protocol的Instance,然后将找到的Handle & Instance 作为参数传入函数的第二入参 – CallBackFunction,继而执行CallBackFunction (CallBackFunction 的第三个传入参数在此处为Null 先不考虑)
具体到当前的传入参数
gEfiPciRootBridgeIoProtocolGuid
和
ConnectRootBridge
,可以说VisitAllInstancesOfProtocol函数执行的具体活动如下:
-
找到所有安装了
gEfiPciRootBridgeIoProtocolGuid的Handle
此处需要结合之前的众多内容认真思考一下,这里找到的到底是什么?
前面分析 代码中,曾经提到过向RootbridgeHandle 上安装Protocol
可以看到初始化的时候向 RootBridge 上安装的protocol就是此处查找的protocol, 也就是说,这里要找的就是当前所有rootbridge对应的handle,也就是当前想要找到所有的root bridge
针对上面安装protocol还有一点是我在后来再看代码的时候发现的,rootbridge handle是直到 安装protocol的时候才进行初始化的,之前进行的操作都是以root bridge instance 为基础的。可以理解成,之前是对root bridge详细信息的初始化和保存,创建handle是为这个root bridge分配了对应的"编号’',后面根据编号来找这个设备或者安装protocol,个人认为这也是UEFI中Handle的一个实际作用,以上也能很好的对应之前曾经说过的Handle就是一个不会重复的整型数字,实际上这个数字就是用来方便查找的编号。
-
找到root bridge handle上的
gEfiPciRootBridgeIoProtocolGuid对应的instance,并且将这个root bridge handle & Instance 传入到ConnectRootBridge函数中,并且执行ConnectRootBridge函数
此处也可以回顾一下现在找到的这个instance是什么?之前介绍HostBridge&RootBridge初始化的时候其实也说过,这个Instance里面是CreateRootBridge函数中就定义好的一些函数,具体如下
现在,已经非常详尽的分析了
ConnectRootBridge
函数的两个入参,接下来就详细看这个函数的具体实现
ConnectRootBridge
ConnectRootBridge
函数的实现核心就是
gBS->ConnectController
,此处需要关注的是,PciBusDxe driver的执行过程中,传入参数中的Instance是没有实际用到的,此处用到的只有
RootBridgeHandle
一个参数。
接下来就是核心关键
ConnectController
ConnectController分析
ConnectController
是
SystemTable->BootServices
中的UEFI基础服务,其函数原型
CoreConnectController
基础服务可以参考
CoreConnectController
函数的实现可以在 Driversupport.c 函数中找到,函数前面部分是对传入参数以及操作进行验证,这个函数的核心就在下面这个位置
其中传入参数的
ControllerHandle
就是RootBridge Handle,
DriverImageHandle
AlignedRemainingDevicePath
均为空,实际上核心部分就可以看成
do{
ReturnStatus =CoreConnectSingleController(
ControllerHandle,//RootBridge HandleNULL,NULL);}while(ReturnStatus == EFI_NOT_READY);CoreConnectSingleController
CoreConnectSingleController
函数是实现 driver运行加载的核心函数,这一部分就是本次研究的重点。函数比较长,并不是所有的部分都值得详细的关注,对不影响理解或者与这次学习无关的内容直接跳过。
函数中首先找到所有安装了 GUID为
gEfiDriverBindingProtocolGuid
的protocol 的handle, 将这些handle的数量记为
DriverBindingHandleCount
, 找到的handle保存在
DriverBindingHandleBuffer
中。然后根据上面找到的handle的数量,申请了一个足够大的空间。这块申请的空间最终是想要将所有找到的 gEfiDriverBindingProtocolGuid protocol (instance)按照一定的顺序进行排序保存,这块保存对应protocol的内存为
SortedDriverBindingProtocols
这一部分获取到了三个变量:
-
安装了
gEfiDriverBindingProtocolGuid的protocol的handle数量DriverBindingHandleCount -
安装了
gEfiDriverBindingProtocolGuid的protocol的handleDriverBindingHandleBuffer -
申请了足够空间准备按照保存所有的
gEfiDriverBindingProtocolGuid对应的protocol
connect PciBusDxe driver的时候,传入的参数只有root bridge handle ,因此上述部分不会执行,跳过。
接下来的这个部分可以分成四个小的部分,每一个部分都执行了类似的操作:找到某种特定的protocol,并查看当前这个protocol的image handle上是否存在 gEfiDriverBindingProtocolGuid protocol,如果存在,将其加入前面说的
SortedDriverBindingProtocols
列表中。这一系列动作实现的核心函数是
AddSortedDriverBindingProtocol
,这个函数比较简单就是查找&对比&添加到数组中,不赘述,详细的内容可以看代码
找到全部的
gEfiDriverBindingProtocolGuid
对应的protocol后,将这些protocol按照version进行从高到低的排序。
Driver Start
接下来就是核心部分
这个do-while循环会便利所有前面找到的所有
gEfiDriverBindingProtocolGuid
对应的protocol并且执行protocol中的support&start函数,直到所有的driver执行完成或者当前的driver不support对应的device。
带入 PciBusDxe driver的具体实现如下
此时传入的device handle就是 rootbridge handle,那么在上述循环中,就是要在前面所有的
gEfiDriverBindingProtocolGuid
对应的protocol中找到能够在 rootbridge handle上运行的。
别的UEFI Driver是否能够支持 rootbridge不清楚,但是当前知道是 gPciBusDriverBinding 是一定能够支持rootbridge的(不然还怎么枚举PCIe设备),所以当前这个while循环中一定会执行的函数有两个:gPciBusDriverBinding 中的 support & start函数
下面的部分就非常简单了,只需要简单的看一下这两个函数(PciBusDriverBindingStart的主要部分之前说过了)
PciBusDriverBindingSupported
UEFI Driver 的support函数想要实现的目的是 判断当前的UEFI Driver能不能在对应的device controller上运行。 根据这个目标,support函数的实现一般都以检查验证所需protocol是否安装为主
- 验证当前设备的 device path 是否有效(device path应该作为下下个目标研究… 目前还没有全部搞清楚)
- 检查 PciRootBridgeIoProtocol是否安装
- 检查 EfiDevicePathProtocol 是否安装了
可以看到,只有当上述的检查全部通过,support函数才会最终返回
EFI_SUCCESS
表示当前的UEFI Driver支持当前的 device controller
PciBusDriverBindingStart
前面已经对
PciBusDriverBindingStart
函数 的主要部分
PciEnumerator
函数进行过非常详细的介绍了
PciEnumerator
传入参数中的 Controller 就是此处进行connect driver的 root bridge handle,到此就可以将前面介绍的PCI枚举和现在的UEFI Driver形成一个闭环
总结
- UEFI Driver的加载在DXE 阶段,实际的执行是在BDS阶段
- UEFI Driver中的 Support 函数是为了验证当前Driver是否能够在对应的Device Controller执行(当前Driver是否支持对应的设备)
- UEFI Driver中的Start函数则是需要当前设备具体执行的操作
到此为止,关于UEFI Driver的内容就梳理了一个大概,这部分的内容涉及了之前的很多内容,handle protocol PCIe枚举等,个人觉得按照思路一路下来中间穿插反复的回顾整理会有一个非常好的效果,回顾了之前的内容的同时对很多概念、实现等都有了更深层次的理解
PS: 关于Driver
Driver,一般都会翻译成驱动,一直觉得很难理解这个词是什么意思,经过对UEFI Driver的简单学习,现在稍微有点感觉了。Driver就是在当前的环境&条件下,希望硬件设备实现某种功能而执行的程序。比如希望枚举所有的PCI root bridge 下的PCIe设备,就要加载PciBusDxe driver来完成枚举。日常生活中经常说需要某一种driver才能运行,感觉也可以理解成由于没有相应的程序,导致设备不知道怎样执行。


发布评论