EDK2设备驱动模型

UEFI设备驱动模型


1. UEFI Drivers

UEFI Drivers是UEFI Image的一种,UEFI Drivers与UEFI Applications的区别:

EDK2设备驱动模型_第1张图片

Objects managed by UEFI-based firmware:

EDK2设备驱动模型_第2张图片

对UEFI Drivers来说,比较重要的是EFI System Table, Memory, Handles, Images, Events。

driver和app的区别:

UEFI Driver主要用于管理设备,在DXE阶段被加载,在BDS阶段被启用,DXE负责把当前系统中所有的Driver,不管DXE Driver,DXE RunTime Driver,还是UEFI Driver 全部给加载起来,把entrypoint都执行完,DXE Driver是不负责加载Application的,Application是到BDS加载。在BDS阶段要把所有的Device值都初始化好,启动加载的Driver,让相应的Driver对Device进行管理。此时就可以提供相应的输入输出设备,启动设备。

2.UEFI Drivers的分类

1.从类型上看,UEFI驱动可分为:

  • 启动服务驱动(Boot Service Divers)。在RT阶段OS Loader获得控制权后这类驱动完全析构。
  • 运行时驱动(Runtime Drivers)。在RT阶段OS Loader获得控制权后这类驱动依然有效,常驻内存。

2.从驱动模型的角度,UEFI Drivers可分为两大类:UEFI Driver Model Driver和UEFI Non Driver Model Driver

EDK2设备驱动模型_第3张图片

  1. Service Drivers

    这种Driver会在一个或多个Service Handle上加载一个或多个Protocol,并在它的Entry Point里会返回EFI_SUCCESS。这类驱动不管理任何设备,一般用来产生Protocol。如常见的BootService、RuntimeService等等服务驱动

  2. Initializing Drivers

    这种Driver不会产生任何Handle,也不会增加任何Protocol到Handle Database,它只会进行一些初始化操作,并且返回错误代码。所以这种Driver执行完就会从系统内存中卸载。初始化服务、CPU 芯片 设备等的初始化驱动 FchDxe / PchDxe / CpuDxe。

  3. Root Bridge Drivers

    这种Driver会产生一个或多个Control Handle,而且包含一个Device Path Protocol和一个对芯片跟总线提供的I/O资源软件方式抽象出来的Protocol。最常见的是为PCI Root Bridge产生handles的一个Driver,并且产生的Handle上面支持Device Path Protocol和PCI Root Bridge I/O Protocol.

    PCIE的根桥设备等 根桥驱动 (x86架构使用的是PCI Bus) PciHostBridge。

  4. UEFI Driver Model Drivers

    • Bus Drivers

      这种Driver会在Handle Database中产生一个或多个Driver Handle或Driver Image handle,并在Handle上安装一个或多个Driver Binding Protocol的实例。这种Driver在调用Driver Binding Protocol的Start()函数时会产生新的Child Handles,而且会在新的Child Handles上增加另外的I/O Protocol。PciBus / UsbBus / AtaBus / SMBus /…

  • Device Drivers

    与Bus Drivers的区别是不会产生新的Child Handles,只会在现有的Child Handle上增加另外的I/O Protocol。SATA / NVME / GOP/ Keyboard / Mouse /…

  • Hybrid Drivers

    同时具有Bus Drivers和Device Drivers的特征,既会在现有的Handle上增加I/O Protocol,也会产生新的Child Handles。(不太常用)。

​ 注:对应的Handle Type可参见:http://blog.csdn.net/celiaqianhj/article/details/6764433

3.UEFI Driver Model出现的目的

随着硬件总线结构的发展,Bus的种类和数量都在增加,在Preboot的环境下,就需要有一种简单的方式来描述和管理平台上的Bus和Device。UEFI Driver Model就提供了一种简单的方式,具体形式则是Protocols Services和Boot Services。目前为止支持的Bus类型有PCI、USB等等。

  • 兼容性

    遵循 2.3.1 Spec的UEFI Driver Model Drivers必须可以与1.1/2.0 Spec保持兼容性。

  • 简单性

    遵循 2.3.1 Spec的UEFI Driver Model Drivers必须易于实现,易于维护。

  • 易伸缩性

    The UEFI Driver Model必须可以适应不同类型的平台,包括嵌入式系统,笔记本,台式机以及工作站和服务器。

  • 灵活性

    The UEFI Driver Model必须支持可以枚举平台上所有的设备,或者只枚举启动到特定系统所要求的设备。

  • 可扩展性

    The UEFI Driver Model必须可以扩展以支持未来的Bus类型。

  • 可移植性

    UEFI Driver Model Drivers必须可以移植到不同平台或者不同的处理器架构上。

  • 共存性

    Drivers必须可以与其他的Driver或System Firmware共存,且没有资源冲突。

  • 可以描述复杂的总线拓扑结构

  • 可执行文件保持最小

  • 解决了Legacy Option Rom的约束和局限性(Limitation)。

4.Driver的初始化

一个Driver Image文件可以从不同的媒介上被加载,这些媒介包括:ROM,Flash,硬盘,软盘,CD-ROM或者是从网络连接上。

当一个Driver Image被发现之后,它可以通过Boot Service LoadImage()被加载到系统内存(LoadImage()加载一个PE/COFF格式的Image到系统内存)。同时会为这个Driver创建一个Handle,并且在这个Handle上创建一个Loaded Image Protocol 的实例(EFI_Loaded_Image_Protocol)。包含Loaded Image Protocol 的实例的Handle被称为Image Handle。此时,这个Driver还没有被启动,它只是存在于内存中等待被启动。

我们可以通过Boot Service StartImage()来启动这个Driver。

UEFI Driver Model Drivers必须符合以下几个严格的规则:

  1. 不允许接触任何的硬件,相反的,只允许这个Driver在它自己的Image Handle上安装Protocol实例。
  2. UEFI Driver Model Drivers必须安装一个Driver Binding Protocol到它的Image Handle上,可选择的安装Driver Configuration Protocol, Driver Diagnostics Protocol, Component Name Protocol。另外,如果一个Driver希望可以被卸载,它可以选择更新Load Image Protocol来提供自己的Unloaded功能。
  3. 如果在执行Boot Service ExitBootService()时,一个Driver希望执行一些特定操作,那么它可以创建一个在调用Boot Service ExitBootService()时被触发的Event(with a notification function)。】

包含Driver Binding Protocol实例的Image Handle被称为Driver Image Handle。

5.UEFI Driver与DXE Driver的区别

符合UEFI驱动模型的驱动相对DXE驱动有如下特点:

  1. 不需要依赖Dependency来决定执行的顺序
  2. 必须可以被重复执行
  3. 不需要即时启动
  4. 支持硬体的热插拔(Hot-Plug)
  5. 支持软体的热插拔(Unload)
  6. 所有的Function都是Device(Handle)结合Driver(Protocol)所达成

UEFI驱动模型主要实现了驱动绑定协议(Driver Binding Protocol)

1.supported()

这个接口用于检测 Driver 是否支持相应的硬件,检查与 Driver相关的控制器句柄是否是这个硬件设备相符合的句柄。检查通过就表明有某个设备Driver 能处理控制器句柄所代表的硬件设备。检查成功时则返回 EFI_SUCCESS 状态。否则表明找改硬件设备不到对应的 Driver,返回 EFI_UNSUPPORTED 状态,。

Check DeviceID 和Vendor ID 来辨别某个Device是否由这个Driver管理,如果是则会启动UEFI的Start(),轮询执行很多次,会影响资源占用和启动时间, 所以除了check之外别做其他事。

2.start()

由Supported()确定后被启动。在硬件的controller上install一些子的handle,然后在handle上安装一些所提供的服务(Protocol)和Device path,以供给其他人使用。

3.stop()

这个接口用来卸载驱动程序,它其实就是 start( )的反过程,它将 start( )接口加载的驱动卸载并释放其创建的各种内容资源,把 start()打开的协议关掉。关闭协议的顺序应该和 start( )中打开协议的顺序相反。

通常情况下不会去调用,因为同样花费时间和空间,但从UEFI架构考虑到在某种情况下,设备不需要再使用时,想要释放Driver,调用Stop()后会将所有install的Protocol进行uninstall,这个设备就不会再被管理同时allocate的资源也会被free。但是,Driver以及安装好的Driver Binding Protocol还在,下次仍然可以重新管理使用。

要注意,编写 UEFI 驱动的要点是:

  • 接口点的时候不要去处理硬件(驱动模型的要求)

  • 复杂的 I/O 操作放到 Start()和 Stop()中实现

  • Start()和 Stop()的操作互相对应,如

    InstallProtocolInterface()           UninstallProtocolInterface() 
    OpenProtocol()                       CloseProtocol 
    AllocatePages()                      FreePages() 
    AllocatePool()                       FreePool() 
    
  • Entry()和 Unload()互相对应。

6 .UEFI 驱动执行过程

驱动程序模型采用 UEFI 驱动载入、连接的形式来进行硬件的辨识、控制及系统资源掌控。

6.1 注册阶段

注册发生在DXE阶段,通过LoadImage();函数驱动程序加载到内存,生成Image Handle,然后通过调用StartImage();调用驱动程序入口函数(xx.inf文件中定义的EntryPoint函数)。

注意:遵循模型规范的设备驱动在入口函数的初始化中不涉及任何硬件操作,仅仅实现驱动绑定协议(Driver Binding Protocol)。即,将驱动程序(Support()、Start()、Stop()函数)注册到Image Handle上,仅是注册,驱动程序不会执行。详细的代码实现过程为:Dxemain()

1.LoadImage()

LoadImage()为Boot Service,通过调用LoadImage()将driver加载进内存,同时该driver会生成自己的Image Handle,并且在Image Handle下安装EFI_LOADED_IMAGE_PROTOCOL,注意此时driver只是当做Image处理的,还不能明确它就是driver。
EDK2设备驱动模型_第4张图片

2.StartImage()

通过StartImage()执行driver image的入口函数,入口函数会在Image Handle上安装其他的Protocol,如果为EFI Driver Model的driver,那么会安装EFI_DRIVER_BINDING_PROTOCOL。也可以选择性安装CONFIGURATION和COMPONET_NAME协议。

EDK2设备驱动模型_第5张图片

主要实现在DxeMain()函数,此函数启动driver 派遣器,查询所有的驱动程序。实现流程:

  • 执行DXE入口及启动DXE dispatcher
  • 调用LoadImage函数,将驱动拷贝到内存
  • 执行driver的EntryPoint函数。

6.2 执行阶段

发生在BDS阶段。

LoadImage()和StartImage()之后,driver会等待被ConnectController()调用去连接到某个Controller。其实这时在内存中已经Load了很多的Driver都在等待被connect到controller,这时ConnectController()会以轮询方式去调用每个Driver Handle中的EFI_DRIVER_BINDING_PROTOCOL.Supported(),如果返回EFI_SUCCESS,那么该driver就会被connect到controller上了。

具体代码实现过程在BdsEntry()函数:

  • BdsEntry()函数进入BDS。
  • ConnectAll()函数——>gBS_ConnectController(),寻找适配的驱动
  • gBS->connectcontroller()里调用support检查,如果支持,调用start函数激活驱动

EDK2设备驱动模型_第6张图片

对于总线驱动和设备驱动,在Connect到Controller的时候所做的动作是不一样的。例如PCI总线驱动连接到PCI主桥之后,它会为每个PCI Device创建Child Device Handle,而PCI设备驱动不能创建新的Handle,它只是在Device Handle上安装Protocol Interface。

  1. 总线驱动

    总线驱动会为在该总线上发现的子设备创建新的Device Handle,并且会在Child Device Handle上安装一些Protocol Interface。该操作是通过EFI_DRIVER_BINDING_PROTOCOL.Start()来完成的。

EDK2设备驱动模型_第7张图片

  1. 设备驱动

    设备驱动不允许创造任何的新的Child Device Handle,它只是在现存的设备Handle上安装其他的协议接口。大多数常见设备驱动负责把I/O抽象挂到由总线驱动所创造的设备Device Handle上,该I/O抽象可以被用于启动EFI兼容的操作系统。

EDK2设备驱动模型_第8张图片

7. DXE driver的实现

7.1 Driver 的 Inf 文件设计

INF 文件全名为 EDK II Module Information File, 它描述了这个 Driver 的属性及依赖的其他文件等信息。在 Build 的时候,一定需要这个文件,它帮助自动生成makefile 的配置文件,在 inf 文件中主要包括以下部分:

(1)Driver 的基本信息和入口函数信息,[Defines]部分
(2)库目录的路径,[Packages]部分
(3)源文件的列表,[sources]部分
(4)库目录的路径,[libraryClasses]部分
(5)Protocol 的列表,[Protocols]部分

以下是各部分的介绍:

(1)[Defines]部分是必须要存在的。这个部分定义了 Build 阶段会用到的变量。

[Defines] 
INF_VERSION           = 0x00010005 
BASE_NAME            = DxeDebugTestLibJumpBuffer 
FILE_GUID              = 21BBF956-56B4-4318-B037-45F1AE4C4726 
MODULE_TYPE          =  DXE_DRIVER   //这里的定义表明这是个 DXE 阶
段执行的 Driver 
VERSION_STRING        = 1.0 
EDK_RELEASE_VERSION   = 0x00090000 
EFI_SPECIFICATION_VERSION       = 0x00020000 
//ENTRY_POINT 定义了入口函数是 DebugTestLibJumpBufferDriverEntry(). 
ENTRY_POINT                     = DebugTestLibJumpBufferDriverEntry 
UNLOAD_IMAGE                    = DebugTestLibJumpBufferDriverUnload 

(2)[sources]部分包括了 UEFI 目录中的.c 和.h 文件的文件名。这个部分中的文件将是 Driver 功能的实现文件。

[Sources.common] 
DebugTestLibJumpBuffer.c 
CommonHeader.h 

(3)[Packages]部分列出了 Driver 会用到的所有的包声明文件。当这个部分中列出了任意文件时,那 MdePkg/MdePkg.dec 也必须被包括。因为 MdePkg 包含 EDK II构建系统所需的编译信息。

[Packages] 
MdePkg/MdePkg.dec 
ShellTestPkg/ShellTestPkg.dec 

(4)[libraries]部分包括 UEFI 驱动需要链接的库列表。

[LibraryClasses] 
UefiBootServicesTableLib 
UefiDriverEntryPoint 
MemoryAllocationLib 
DebugLib 
BaseMemoryLib 
BaseLib 

(5)[Protocols]部分列出了模块开发中会用到的 Protocol,每个 Protocol 都有一个全球唯一的 12 位 guid 值标识,都在 DEC 文件中进行了定义。

[Protocols] 
gEfiTslInitInterfaceGuid                       #  PROTOCOL ALWAYS_CONSUMED 
gEfiDebugTestLibJumpBufferProtocolGuid         #  PROTOCOL ALWAYS_CONSUMED

(6) [Depex] , 该driver所依赖的其他驱动模块。该段将决定driver的加载顺序。注意:如果只写TRUE,表示不依赖任何其他模块;如果有多个依赖,用AND连接。

[Depex]
Amodule AND
Bmodule

7.2 模块load的优先级

当EFI 刚出现的时候,模块没有固定的加载顺序是作为优点介绍的,但是运行过程确实需要加载顺序,这一切是通过源代码中每个模块 INF 中[Depex] Section 决定的。但是如果fdf文件规定了 APRIORI ,那它指定的模块优先级更高。

总结:

  1. fdf 文件中 APRIORI DXE 指定的优先级最高;
  2. 模块中 INF 文件 Depex Section 可以指定依赖关系;
  3. 同等优先级的由fdf中的先后顺序决定。

例子,

APRIORI DXE {
INF  MdeModulePkg/Universal/PCD/DXE/Pcd.inf
}

8. DevicePath

8.1 UEFI为什么要提出Device Path的设计?

这里援引UEFI SPEC中关于Device Path用途说明的几个关键句子:

The primary purpose of a Device Path is to allow an application, such as an OS loader, to determine the physical device that the interfaces are abstracting.
A collection of device paths is usually referred to as a name space.
However, the ACPI name space was designed for usage at operating system runtime and does not fit well in platform firmware or OS loaders. Given this, EFI defines its own name space, called a Device Path.
A Device Path is designed to make maximum leverage of the ACPI name space.

对此,我个人的理解和认识是:

  • Device Path用来在UEFI环境中标识系统中的设备(物理设备或者逻辑设备),类似于在ACPI中用Name Space来标识各个Object。或者类似于在dtb中的某个节点。

    这就意味:1)Device Path也具有层级结构;2)众多Device Path集合起来构成了1个UEFI环境下的设备命名空间。

  • OS或者UEFI应用根据Device Path来知道它代表的设备。

    举个例子:UEFI SPEC中定义了与Console相关的3个variables:ConIn, ConOut, and ErrOut。每个variable的内容都包含1个Device Path,这个Device Path表示每次开机默认使用的用于该种用途(ConIn,ConOut, ErrOut)的Console Device,UEFI应用可以通过这个Device Path来确定对应的Console Device。

  • Device Path在设计上充分利用了已有的ACPI Name Space的命名设计。关于这一点参考下面“Device Path与ACPI的联系”一节。

8.2 Device Path的表示

系统中每个设备都有唯一的路径。例如,每次进入shell时,会打印出系统中的硬盘设备以及设备路径。例如Qemu的UEFI shell中包含的一块空硬盘,设备路径:

EDK2设备驱动模型_第9张图片

设备路径以节点为基本单位,一个设备路径由多个节点串联而成,以“End of Hardware Device Path”节点结束。

设备路径表示形式:
在这里插入图片描述

不同的设备类型有不同的设备节点定义,但是所有的设备节点都包含EFI_DEVICE_PATH_PROTOCOL结构,并作为节点的第一域。

EFI_DEVICE_PATH_PROTOCOL的定义如下:

43  typedef struct {
44    UINT8 Type;       ///< 0x01 Hardware Device Path.
45                      ///< 0x02 ACPI Device Path.
46                      ///< 0x03 Messaging Device Path.
47                      ///< 0x04 Media Device Path.
48                      ///< 0x05 BIOS Boot Specification Device Path.
49                      ///< 0x7F End of Hardware Device Path.
50  
51    UINT8 SubType;    ///< Varies by Type
52                      ///< 0xFF End Entire Device Path, or
53                      ///< 0x01 End This Instance of a Device Path and start a new
54                      ///< Device Path.
55  
56    UINT8 Length[2];  ///< Specific Device Path data. Type and Sub-Type define
57                      ///< type of data. Size of data is included in Length.
58  
59  } EFI_DEVICE_PATH_PROTOCOL;

可见,每个节点都以EFI_DEVICE_PATH_PROTOCOL作为header, 描述当前节点的类型、次类型,节点所占字节数(每种类型路径长度各有不同)。

不同类型(Type、SubType)的节点,除了header外,其余的域也各不相同。不太类型的Device Path节点定义在MdePkg/Include/Protocol/DevicePath.h

例如,PCI总线的设备路径节点定义:

76  ///
77  /// PCI Device Path.
78  ///
79  typedef struct {
80    EFI_DEVICE_PATH_PROTOCOL        Header;
81    ///
82    /// PCI Function Number.
83    ///
84    UINT8                           Function;
85    ///
86    /// PCI Device Number.
87    ///
88    UINT8                           Device;
89  } PCI_DEVICE_PATH;
90  

再例如,PCCARD 设备路径节点的定义:

99  typedef struct {
100    EFI_DEVICE_PATH_PROTOCOL        Header;
101    ///
102    /// Function Number (0 = First Function).
103    ///
104    UINT8                           FunctionNumber;
105  } PCCARD_DEVICE_PATH;

因此,我们对设备路径节点做一个总结。

  1. 设备路径以节点为基本单位,一个设备路径由多个节点串联而成,以“End of Hardware Device Path”节点结束。

在这里插入图片描述

  1. 不同类型的设备节点,有不同的结构体表示。但是都以EFI_DEVICE_PATH_PROTOCOL 作为第一个域,表示节点的header。

    43  typedef struct {
    44    UINT8 Type;       //设备类型
    51    UINT8 SubType;    //次设备类型
    56    UINT8 Length[2];  //节点长度
    59  } EFI_DEVICE_PATH_PROTOCOL;
    
  2. 节点中的设备类型Type和次设备类型SubType共同决定了设备节点的类型。UEFI支持的设备类型和次设备类型有:

    EDK2设备驱动模型_第10张图片

EDK2设备驱动模型_第11张图片

  1. 节点header中的Length 表示节点的长度。

8.3 Device Path的组织结构

Device Path以Device Path Node为基本单位来依次串接而成,其结束标志是End of Hardware Device Path类型的Device Path Node。

下图展示了Device Path Node的基本结构:
EDK2设备驱动模型_第12张图片

每个Device Path Node代表某一层级的域/设备或者设备属性(比如MAC地址/IPv4地址/IPv6地址等)。通常来说,它们会按照各自所处的Subsystem的拓扑结构至顶向下来排列形成最终Device Path,从而代表了设备的拓扑路径。下面,以开篇的UEFI Shell下的Device Path为例来说明。

Note: Device Path Node可以代表域、硬件设备、虚拟设备、设备属性(比如MAC地址/IPv4地址/IPv6地址等)。

Example 1:Removable USB HDD with MBR partition

PciRoot(0x0)/Pci(0x14,0x0)/USB(0x0,0x0)/HD(1,MBR,0x01EDBE02,0x800,0x1004800)

  • PciRoot(0x0) => 代表Pci Root Bridge扩展出来的Pci总线域,这个是PCI设备的最顶层域,在台式机和笔记本上通常只有1个,其编号为0。

  • Pci(0x14,0x0) => 代表USB XHCI Controller,其PCI Device=0x14,PCI Function=0x00。PCI Bus因为是会变动的(比如有PCI设备Hot-Plug或者下次启动有PCI设备添加/移除),UEFI SPEC规定其值不纳入Device Path Node,通常结合平台信息可以推断出来PCI Bus的值。

  • USB(0x0,0x0) => 代表Removable USB HDD, 其接在USB Port 0上。

  • HD(1,MBR,0x01EDBE02,0x800,0x1004800) => 代表Removable USB HDD上的第1个硬盘分区,为MBR格式。

Example 2:NVMe SSD with GPT partition

PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x0)/NVMe(0x1,C3-70-00-00-02-0D-08-00)/HD(1,GPT,425EAA1B-B401-4960-BEFC-5248CA7BCF28,0x800,0x82000)

PciRoot(0x0) => 意义同上。

  • Pci(0x1C,0x4) => 代表NVMe SSD所在的PCIe Root Port,其PCI Device=0x1C,PCI Function=0x04。
  • Pci(0x0,0x0) => 代表NVMe SSD Controller, 其PCI Device=0x0,PCI Function=0x0。
  • NVMe(0x1,C3-70-00-00-02-0D-08-00) => 代表NVMe的Name Space, 用该Name Space来标识NVMe SSD设备。
  • HD(1,GPT,425EAA1B-B401-4960-BEFC-5248CA7BCF28,0x800,0x82000) => 代表NVMe SSD上的第1个硬盘分区,为GPT格式。

Example 3: 硬盘设备(HARDWARE_DEVICE_PATH)的节点

例如我们的eMMC设备就属于硬盘设备,其设备路径节点使用HARDDRIVE_DEVICE_PATH结构体表示。严格意义上来说,应该命名为分区设备节点。

节点HARDDRIVE_DEVICE_PATH的定义如下:

997  ///
998  /// The Hard Drive Media Device Path is used to represent a partition on a hard drive.
999  ///
1000  typedef struct {
1001    EFI_DEVICE_PATH_PROTOCOL        Header;
1008    UINT32                          PartitionNumber;
1012    UINT64                          PartitionStart;
1016    UINT64                          PartitionSize;
1024    UINT8                           Signature[16];
1030    UINT8                           MBRType;
1037    UINT8                           SignatureType;
1038  } HARDDRIVE_DEVICE_PATH;

下表列出了硬盘设备节点HARDDRIVE_DEVICE_PATH各个域的含义:

8.4 Device Path的类别

UEFI SPEC一共定义了6个大类(Type)的Device Path Node,每个大类下面又分不同的子类(Sub-Type)。大类划分参考下面表格。

大类 可表示的典型设备/对象
Hardware Device Path PCI设备,Vendor自定义设备,BMC
ACPI Device Path PCI总线域(如上面的PciRoot(0x0)) 显示输出设备,ACPI设备(比如鼠标/键盘等)
Messaging Device Path IDE设备,USB设备,SATA设备,SAS设备,SCSI设备,UART设备,Vendor自定义对象,MAC地址,IPv4/IPv6地址,其他新兴设备
Media Device Path 硬盘分区,CDROM分区,Vendor自定义Media,文件路径,UEFI固件分区,UEFI固件文件,RAM Disk
BIOS Boot Specification Device Path 传统的BIOS启动设备
End of Hardware Device Path Device Path终结符

需要说明下:

  1. 一个Device Path可能说由多个Device Path组成的,则各个组成部分被称为Device Path Node;
  2. 每个Device Path都由End of Hardware Device Path结尾;
  3. End of Hardware Device Path由两种类型,一种是End of This Instance of a Device Path(Sub-Type是0x01),另一种是End Entire Device Path(Sub-Type是0xFF),前者用在Device Path Node的最后,后者用在整个Device Path的最后:

在UEFI中有指定的函数来判断这两种End of Hardware Device Path:

BOOLEAN
EFIAPI
IsDevicePathEnd (
  IN CONST VOID  *Node
  )
{
  ASSERT (Node != NULL);
  return (BOOLEAN) (IsDevicePathEndType (Node) && DevicePathSubType(Node) == END_ENTIRE_DEVICE_PATH_SUBTYPE);
}
 
BOOLEAN
EFIAPI
IsDevicePathEndInstance (
  IN CONST VOID  *Node
  )
{
  ASSERT (Node != NULL);
  return (BOOLEAN) (IsDevicePathEndType (Node) && DevicePathSubType(Node) == END_INSTANCE_DEVICE_PATH_SUBTYPE);
}

1.Hardware Device Path

Hardware Device Path描述了与一个设备关联的系统资源,比如共享内存,MMIO,IO等。

Hardware Device Path有多种子类型,比如PCI Device Path:

其它的类型还有:

Hardware Device Path
PCCARD Device Path
Memory Mapped Device Path
Vendor Device Path
Controller Device Path
BMC Device Path

这里不多做介绍,可以去看UEFI SPEC。

2.ACPI Device Path

ACPI Device Path包含了ACPI设备ID和其它的一些内容,有_HID、_CID、_UID等ID和_ADR等其它信息。

根据包含的ACPI信息的多少,也有不同的子类型,如:

ACPI Device Path
Expanded ACPI Device Path
ACPI _ADR Device Path

关于Messaging Device Path,UEFI SPEC的定义如下:

This Device Path is used to describe the connection of devices outside the resource domain of the
system. This Device Path can describe physical messaging information like SCSI ID, or abstract
information like networking protocol IP addresses.

并不是很理解,不过看起来像是一些总线和协议对应的Device Path,它有如下的子类型:

Messaging Device Path
ATAPI Device Path
SCSI Device Path
Fibre Channel Device Path
1394 Device Path
USB Device Path
SATA Device Path
USB Device Paths (WWID)
Device Logical Unit
USB Device Path (Class)
I2O Device Path
MAC Address Device Path
IPv4 Device Path
IPv6 Device Path
VLAN device path node
InfiniBand Device Path
UART Device Path
Vendor-Defined Messaging Device Path
UART Flow Control Messaging Path
Serial Attached SCSI (SAS) Device Path
Serial Attached SCSI (SAS) Ex Device Path
iSCSI Device Path
NVM Express namespace messaging device path node
Uniform Resource Identifiers (URI) Device Path
UFS (Universal Flash Storage) device messaging device path node
SD (Secure Digital) Device Path
EFI Bluetooth Device Path
Wireless Device Path
  1. Media Device Path

    Media Device Path表示了能够作为启动项的设备的Device Path。

    下面是所有的子类型:

    Media Device Path
    Hard Drive
    CD-ROM Media Device Path
    Vendor-Defined Media Device Path
    File Path Media Device Path
    Media Protocol Device Path
    PIWG Firmware File
    PIWG Firmware Volume
    Relative Offset Range
    Relative Offset Range
    RAM Disk
  2. BIOS Boot Specification Device Path

    BIOS Boot Specification Device Path与前一种Media Device Path类型,不同的是它表示的是Legacy BIOS启动项设备。

8.5 Device Path的文本表示形式

UEFI定义了相关的Protocol来将Device Path的数据结构转换为可视化的文本形式。下面给出几种常见Devie Path Node类型的文本形式。

EDK2给出了将各种Device Path Node转换成文本形式的实现,具体参考:mUefiDevicePathLibToTextTable[] defined in edk2\MdePkg\Library\UefiDevicePathLib\DevicePathToText.c.

Device Path Node类型 文本格式 示例
PCI设备 Pci(Dev,Func) Dev-PCI Device NumberFunc-PCI Function Number Pci(0x1C,0x4)
Vendor自定义设备 VenHw(GUID[,Data]) GUID为Vendor GUID,Data为对应的数据(如存在) VenHw(480B3204-C23C-4AE1-BB16-A73FADDA475F)
ACPI设备 - PCI总线域 PciRoot(UID) UID为PCI总线域编号 PciRoot(0)
ACPI设备 - 通用形式 Acpi(HID,UID) HID,UID定义参考ACPI SPEC Acpi(PNP0303,0x0)
IDE设备 Ata([PriSec,SlaveMaster,]LUN) 相关参数参考UEFI SPEC说明 Ata(Primary,Master,0)
USB设备 USB(ParentPort,InterfaceNumber) 相关参数参考UEFI SPEC说明 USB(0x5,0x0)
SATA设备 Sata(HBAPort,PortMultiplierPort,LUN) 相关参数参考UEFI SPEC说明 Sata(0x1,0x0,0x0)
NVMe设备 NVMe(NSID,EUI-64) 相关参数参考UEFI SPEC说明 NVMe(0x1,C3-70-00-00-02-0D-08-00)
硬盘分区 HD(PartitionNumber,PartitionFormat,PartitionSig, PartitionStartLBA, PartitionSize)相关参数参考UEFI SPEC说明 HD(1,MBR,0x01EDBE02,0x800,0x1004800) HD(1,GPT,425EAA1B-B401-4960-BEFC-5248CA7BCF28,0x800,0x82000)
CD-ROM分区 CDROM(BootEntry[,PartitionStartRBA,PartitionSize]) 相关参数参考UEFI SPEC说明
MAC地址 MAC(MACAddr,NICInterface) 相关参数参考UEFI SPEC说明 MAC(54E1AD76ACEB,0x0)
IPv4地址 IPv4(RemoteIPAddr[,Protocol,Static/DHCP,LocalIPAddr,GatewayIPAddr,SubnetMask]) 相关参数参考UEFI SPEC说明 IPv4(0.0.0.0)
IPv6地址 IPv6(RemoteIPAddr[,Protocol,IpAddressOrigin,LocalIPAddr,GatewayIPAddr,SubnetMask]) 相关参数参考UEFI SPEC说明 IPv6(0000:0000:0000:0000:0000:0000:0000:0000)
UEFI固件分区 Fv(FvGUID) 相关参数参考UEFI SPEC说明 Fv(A881D567-6CB0-4EEE-8435-2E72D33E45B5)

8.6 如何解读UEFI Shell下Mapping Table中的Device Path?

具体实现可以参考UEFI Shell在EDK2中的源码:ShellCommandCreateInitialMappingsAndPaths() defined in edk2\ShellPkg\Library\UefiShellCommandLib\UefiShellCommandLib.c.

详细的代码实现逻辑非常复杂,这里只挑几个要点陈述如下:

FSx的映射

1.先找到Support EFI_SIMPLE_FILE_SYSTEM_PROTOCOL的所有Device Handle,每个Device Handle代表1个File System,会被分配1个FSx:。

2.对上述每个Device Handle Locate到其Device Path,转换为Device Path Text,按Text String由小到大的顺序进行排序,对应的Device Handle的FSx从0开始依次编号。

BLKx的映射

1.先找到Support EFI_BLOCK_IO_PROTOCOL的所有Device Handle,每个Device Handle代表1个Block设备,会被分配1个BLKx:。

2.对上述每个Device Handle Locate到其Device Path,转换为Device Path Text, 按Text String由小到大的顺序进行排序,对应的Device Handle的BLKx从0开始依次编号。

3.如上述Device Handle与FSx映射中的Device Handle是同1个,则在对应的FSx:右侧标注对应的BLKx:。

8.7 Device Path操作接口

UEFI SPEC提供了一个工具接口来操作Device Path:

///
/// This protocol is used to creates and manipulates device paths and device nodes.
/// 
typedef struct {
  EFI_DEVICE_PATH_UTILS_GET_DEVICE_PATH_SIZE GetDevicePathSize;
  EFI_DEVICE_PATH_UTILS_DUP_DEVICE_PATH      DuplicateDevicePath;
  EFI_DEVICE_PATH_UTILS_APPEND_PATH          AppendDevicePath;
  EFI_DEVICE_PATH_UTILS_APPEND_NODE          AppendDeviceNode;
  EFI_DEVICE_PATH_UTILS_APPEND_INSTANCE      AppendDevicePathInstance;
  EFI_DEVICE_PATH_UTILS_GET_NEXT_INSTANCE    GetNextDevicePathInstance;
  EFI_DEVICE_PATH_UTILS_IS_MULTI_INSTANCE    IsDevicePathMultiInstance;
  EFI_DEVICE_PATH_UTILS_CREATE_NODE          CreateDeviceNode;
} EFI_DEVICE_PATH_UTILITIES_PROTOCOL;
具体各个接口的作用不多做介绍。

另外在EDKII代码中还提供了UefiDevicePathLib,里面也有不少有用的接口。

8.8 Device Path的产生

UEFI SPEC只要求为系统中的Physical Device创建Device Path,而Virtual Device是无需创建Device Path的。每个Physical Device都会对应1个Controller Handle,在该Handle下会安装其对应的Device Path。Controller Handle与Device Path的创建都与UEFI Driver紧密相关,因此有必须先了解UEFI Driver及其行为。

1.UEFI Driver与Controller的关系

UEFI SPEC定义了UEFI Driver Model, 在这个模型下UEFI Driver可以分成3类:

  • Bus Driver
  • Device Driver
  • Hybrid Driver (兼具Bus Driver和Device Driver的特点)

这里区分出了Bus Driver和Device Driver,其实是遵循物理设备在系统内部连接的拓扑层次结构来进行设计。PC中很多Industry总线标准是符合”Host Bus Controller - Child Device Controller“的层级结构的,比如下面示例的PCIe、SATA、USB总线都是如此。

Bus Type Host Bus Controller Child Device Controller
PCIe PCI Root Bridge,PCI-to-PCI Bridge (in Root Complex or Switch) PCIe Device
SATA SATA Host Controller SATA Drive
USB USB Host Controller,USB Hub USB Device

Example 1: PCIe总线拓扑层次结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IAMKz8eV-1677397607411)(D:\工作目录\组内svn\Bootloader\UEFI\笔记\驱动框架\picture\Example PCIe总线拓扑层次结构.png)]

Example 2: SATA总线拓扑层次结构

EDK2设备驱动模型_第13张图片

Example 3: USB总线拓扑层次结构

EDK2设备驱动模型_第14张图片

可以简单地这样来理解UEFI Driver和Controller的关系:

  1. Bus Driver负责初始化Host Bus Controller,侦测并枚举Host Bus Controller以下的所有连接到该总线的设备,这些设备统称为Child Device Controller。

  2. Device Driver负责初始化上述1个或者多个Child Device Controller。

  3. UEFI Bus Driver Connect时为Child Device Controller产生Device Path。

EFI_BOOT_SERVICES.ConnectController()

UEFI Bus Driver只有在Connect到对应的Host Bus Controller时才会被执行,这个过程通过调用EFI_BOOT_SERVICES.ConnectController()来发生。这个API的参数接口简单解释如下:

  • ControllerHandle – 目标Host Bus Controller的Handle。
  • DriverImageHandle – 代表UEFI Bus Driver的Image Handle List,可以包含多个UEFI Bus Driver。通常为NULL,表示用所有的UEFI Bus Driver来对该Host Bus Controller执行Connect操作。
  • RemainingDevicePath – 指向Host Bus Controller下面的Child Device的Device Path。通常为NULL不指定
  • Recursive – 如果为TRUE,则会沿着Host Bus Controller往下穷尽对所有层次的Child Device的枚举遍历。通常为TRUE。

2. UEFI Bus Driver Connection过程

上述UEFI Bus Driver Connect到Host Bus Controller的过程简化如下:

  1. 执行UEFI Bus Driver的服务EFI_DRIVER_BINDING_PROTOCOL.Supported(),该服务会判断目标Host Bus Controller是否为该UEFI Bus Driver适用的设备,如果不适用则直接结束。
  2. .如果判断UEFI Bus Driver和目标Host Bus Controller匹配,接着执行UEFI Bus Driver的服务EFI_DRIVER_BINDING_PROTOCOL.Start()。在这个服务中,完成上述说的初始化Host Bus Controller和枚举Child Device Controller的过程。
  3. 每当枚举到1个新的Child Device Controller,都要:
    • 为该Device创建1个Controller Handle。
    • 在该Controller Handle下安装1个Device Path Protocol,也就是创建1个Device Path。

创建Device Path的方法:

先为该Child Device创建对应的Device Path Node,然后将该Node追加到其Parent Device的Device Path尾部形成1个新的Device Path。

比如:Child Device (PCI Device=0x0, PCI Function=0x0) 是接在某个PCIe Root Port (PCI Device=0x1C, PCI Function=0x4)下的PCIe Device,其Parent Device的Device Path=PciRoot(0x0)/Pci(0x1C,0x4),该Child Device的Device Path Node为Pci(0x0,0x0),所以它最终的Device Path=PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x0)。

Host Bus Controller的Device Path产生

以上是UEFI SPEC定义的Child Device Controller的Device Path的产生过程,那么Host Bus Controller呢?它也属于Physical Device,也需要提供Device Path。针对这一点,UEFI SPEC并没有定义产生的标准流程。不过,系统的UEFI固件对平台上的Host Bus Controller有完全的了解,它会实现为所有Host Bus Controller创建Controller Handle并安装对应的Device Path。

3.其他情况下产生Device Path

某些UEFI Driver并不初始化Host Bus Controller并枚举Child Device,但是仍然产生Child Device Handle并安装Device Path,它们也被广义上看作UEFI Bus Driver。典型的例子包括Graphics Driver、Serial Driver、LAN Driver、Console Driver等,下面简单说明。

Graphics Driver产生Device Path

Graphics Driver用来初始化系统集成的或者外接显卡上的Graphics Controller,这些Graphics Controller往往不止有1个Graphics Output接口 (比如笔记本上有集成的LCD显示和HDMI接口等)。这些不同的Graphics Output在物理或者逻辑上被Graphics Driver视为不同的Child Device,Graphics Driver在初始化时会侦测并枚举它们,因此会给这些Child Device创建Handle并产生Device Path。

值得一提的是,这种Device Path的最后1个Device Path Node是ACPI _ADR Device子类型的(如下面的例子),其值代表了1个表示display output device的UID,具体定义参考ACPI SPEC 6.3 B.5.1 _ADR。PciRoot(0x0)/Pci(0x2,0x0)/AcpiAdr(0x80011400)

LAN Driver产生Device Path

该Driver会针对对应的网卡创建1个Child Device Handle并产生如下Device Path:

  1. 代表网卡MAC地址的Device Path,比如PciRoot(0x0)/Pci(0x1F,0x6)/MAC(54E1AD76ACEB,0x0)。
  2. 代表网卡IPv4和IPv4地址的Device Path,比如:
    • PciRoot(0x0)/Pci(0x1F,0x6)/MAC(54E1AD76ACEB,0x0)/IPv4(0.0.0.0)
    • PciRoot(0x0)/Pci(0x1F,0x6)/MAC(54E1AD76ACEB,0x0)/IPv6(0000:0000:0000:0000:0000:0000:0000:0000)

其Parent Device Path是对应网卡设备的PCI Device Path=PciRoot(0x0)/Pci(0x1F,0x6)。

Console Device Path

Serial Port和Keyboard/Mouse都是常见的Console In Device,相应的Driver会为它们创建Child Handle并产生Device Path。

以下是几个例子:

  • PciRoot(0x0)/Pci(0x1F,0x0)/Acpi(PNP0303,0x0) => PNP0303代表Keyboard
  • PciRoot(0x0)/Pci(0x1F,0x0)/Acpi(PNP0303,0x0) => PNP0303代表Keyboard
  • PciRoot(0x0)/Pci(0x1F,0x0)/Acpi(PNP0501,0x0) or PciRoot(0x0)/Pci(0x1F,0x0)/Serial(0x0)=> PNP0501代表Serial Port

9. ImageHandle和ControllerHandle

9.1 ImageHandle

每个uefi module都是一个image,而每个image对应都有一个ImageHandle,其实ImageHandle的类型就是EFI_HANDLE

typedef VOID                      *EFI_HANDLE;

因此实际上每个ImageHandle是一个void*指针,那么也就是说任何结构体都可以传给他,其实通过代码可知在uefi中,传入ImageHandle中的数据结构只会是IHANDLE:

///
/// IHANDLE - contains a list of protocol handles
///
typedef struct {
  UINTN               Signature;
  /// All handles list of IHANDLE
  LIST_ENTRY          AllHandles;
  /// List of PROTOCOL_INTERFACE's for this handle
  LIST_ENTRY          Protocols;      
  UINTN               LocateRequest;
  /// The Handle Database Key value when this handle was last created or modified
  UINT64              Key;
} IHANDLE;

由此可知,每个ImageHandle其实就对应一个IHANDLE结构,这个结构就是用于描述此image的,我们可以在ImageHandle上安装一系列的接口(就是Protocol),比如在DXE和UEFI驱动中,这一系列接口通过Protocols这个list连接在一起,它还有AllHandles链表,也就是说系统中所有的IHANDLE也都是连接在一起的,这就会在整个UEFI运行过程中组成一个EFI_HANDLE数据库。

因此在某个模块中实现的接口(Protocol),可以通过一系列的函数,先找到ImageHandle,再通过它找到对应的Protocol,然后在其它的地方调用这些Protocol。UEFI中所有的IHANDLE是通过AllHandles连接在一起的,所有的protocol是通过PROTOCOL_ENTRY结构连接在一起的。

关于ImageHandle和Procotol之间的关系可以参考下图:

EDK2设备驱动模型_第15张图片

9.2.ControllerHandle

看了一些书,也在网上搜索了一些Controller的概念,总结了一下我的理解。其实ControllerHandle和上文中介绍的ImageHandle本质上并没有区别,两者都是EFI_HANDLE对象,也就是IHANDLE数据结构。Controller控制器更多的含义应该是逻辑意义上的,它代表着当前IHANDLE对象的父对象,这样对我来说可能更好理解一些,子对象的实现是依赖于父对象之上的,这就更像是linux kernel中,bus总线和device设备之间的关系。

比如说我们系统中要实现一个AC97音频驱动,那么可能会有两个UEFI Driver,一个是PCI驱动(Bus Driver),一个是AC97音频驱动(Device Driver)提供音频操作的函数,那么我们可以认为PCI驱动(Bus Driver)是AC97音频驱动(Device Driver)的Controller控制器,而AC97音频驱动的实现依赖PCI驱动。

Controller在UEFI中的体现就是函数和参数的命名,比如我们常用到的操作:

gBS->ConnectController()

gBS->DisconnectController()

  • gBS->ConnectController()这个函数就是用来把我们的驱动按照到父对象上,也就是Controller上

  • gBS->DisconnectController()是把我们驱动从对应的Controller上断开。

    实际进一步跟下去,他们的实现是这样的:

    EFI_STATUS
     EFIAPI
     CoreConnectController (
       IN  EFI_HANDLE                ControllerHandle,
       IN  EFI_HANDLE                *DriverImageHandle    OPTIONAL,
       IN  EFI_DEVICE_PATH_PROTOCOL  *RemainingDevicePath  OPTIONAL,
       IN  BOOLEAN                   Recursive
       )
    
    EFI_STATUS
    EFIAPI
    CoreDisconnectController (
      IN  EFI_HANDLE  ControllerHandle,
      IN  EFI_HANDLE  DriverImageHandle  OPTIONAL,
      IN  EFI_HANDLE  ChildHandle        OPTIONAL
      )
    

举例说明这个函数如何使用,我们可以这样使用:

EFI_STATUS
EFIAPI
BdsLibConnectAllEfi (
  VOID
  )
{
  EFI_STATUS  Status;
  UINTN       HandleCount;
  EFI_HANDLE  *HandleBuffer;
  UINTN       Index;
  //找出所有load后的 handle,包括Bus driver handle和 device driver handle
  Status = gBS->LocateHandleBuffer (
                  AllHandles,
                  NULL,
                  NULL,
                  &HandleCount,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
 // 如果HandleBuffer[Index]是device driver的handle,connect没有作用;
 // 如果HandleBuffer[Index]是Bus driver的handle,将其child device handle遍历出来,安装在此controller handle。
// RemainingDevicePath – 指向 Bus Controller下面的Child Device的Device Path。这里为NULL不指定。
// Recursive – 为TRUE,则会沿着Bus Controller往下穷尽对所有层次的Child Device的枚举遍历。
  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->ConnectController (HandleBuffer[Index], NULL, NULL, TRUE);
  }

  if (HandleBuffer != NULL) {
    FreePool (HandleBuffer);
  }

  return EFI_SUCCESS;
}

ControllerHandle和ChildHandle是有依赖的,并且我们知道驱动之前的联系都是通过Procotol来进行了,所以这种依赖也是通过Procotol来进行的。那么我们在Connect和Disconnect的时候其实只需要查找Controller中的Procotol对应的OpenData和attribute私有数据区就可以找到对应的ChildHandle了。

绑定操作的基本原理是利用Driver Binding Protocol,这是UEFI driver中的一个概念了,在ConnectController中,可以不用指定DriverImageHandle和ChildHandle,因为它内部都可以通过Driver Binding Protocol来查找到对应的绑定信息。在开发UEFI driver时,在driver entry加载时都会利用Driver Binding Protocol进行一些操作。

1. 父设备如何找到子设备

一个Bus 设备(父设备),即Controller,如何知道自己有哪些Child device?

无法直接知道,只能将所有的handle拿过来逐个试,看是不是自己的子设备。即,获取handle list即Allhandle,然后逐个和父设备自己的handle匹配尝试。

具体实现可参考:MdeModulePkg/Core/Dxe/Hand/DriverSupport.c中CoreConnectSingleController函数

389  EFI_STATUS
390  CoreConnectSingleController (
391    IN  EFI_HANDLE                ControllerHandle,
392    IN  EFI_HANDLE                *ContextDriverImageHandles OPTIONAL,
393    IN  EFI_DEVICE_PATH_PROTOCOL  *RemainingDevicePath       OPTIONAL
394    )
395  {
       .....
619    // Loop until no more drivers can be started on ControllerHandle
620    //
621    OneStarted = FALSE;
622    do {
623  
624      //
625      // Loop through the sorted Driver Binding Protocol Instances in order, and see if
626      // any of the Driver Binding Protocols support the controller specified by
627      // ControllerHandle.
628      //
629      DriverBinding = NULL;
630      DriverFound = FALSE;
631      for (Index = 0; (Index < NumberOfSortedDriverBindingProtocols) && !DriverFound; Index++) {
632        if (SortedDriverBindingProtocols[Index] != NULL) {
633          DriverBinding = SortedDriverBindingProtocols[Index];
634          PERF_DRIVER_BINDING_SUPPORT_BEGIN (DriverBinding->DriverBindingHandle, ControllerHandle);
635          Status = DriverBinding->Supported(
636                                    DriverBinding,
637                                    ControllerHandle,
638                                    RemainingDevicePath
639                                    );
640          PERF_DRIVER_BINDING_SUPPORT_END (DriverBinding->DriverBindingHandle, ControllerHandle);
641          if (!EFI_ERROR (Status)) {
642            SortedDriverBindingProtocols[Index] = NULL;
643            DriverFound = TRUE;
644  
645            //
646            // A driver was found that supports ControllerHandle, so attempt to start the driver
647            // on ControllerHandle.
648            //
649            PERF_DRIVER_BINDING_START_BEGIN (DriverBinding->DriverBindingHandle, ControllerHandle);
650            Status = DriverBinding->Start (
651                                      DriverBinding,
652                                      ControllerHandle,
653                                      RemainingDevicePath
654                                      );
655            PERF_DRIVER_BINDING_START_END (DriverBinding->DriverBindingHandle, ControllerHandle);
656  
657            if (!EFI_ERROR (Status)) {
658              //
659              // The driver was successfully started on ControllerHandle, so set a flag
660              //
661              OneStarted = TRUE;
662            }
663          }
664        }
665      }
666    } while (DriverFound);
      .......
}
  • 将当前设备A的handle(父设备)作为Controller handle。
  • 从handle list中逐个找出安装了Driver Binding Protocols 的handle,我们记为D1。打开D1 handle的instance,进而调用其Support函数。
  • 将A的handle作为Controlller handle传入D1的Support函数,执行该Support函数。
  • 如果执行D1的Support函数返回SUCCESS,则表示D1就是A的子设备。
  • 进一步,调用子设备D1的Start函数。

总结,父设备A判断一个设备B是不是其子设备:A将自己的handle作为B的Controller handle,通过B的handle打开B的实例,调用B的Support函数来判断。

2.子设备如何判断一个device是其父设备

子设备在其Driver Binding Protocols 的Support函数中判断某个Controller 是不是自己的父设备。

// Controller是判断者传进来的某个父设备handle
183  DriverBindingSupported (
184    IN EFI_DRIVER_BINDING_PROTOCOL    *This,
185    IN EFI_HANDLE                     Controller,
186    IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
187    )c

Controller是判断者(一般是Connect函数)传进来的某个父设备handle,该函数的实现者只需要:判断当前Driver是否要将该任意Controller当作自己的父设备。其实就是:认爹。

那当前设备Driver如何判断这个Controller是不是自己的爹?

  • 通过DevicePath
  • 通过Protocol
  • 通过Controller实例的其他private date。

既然子设备知道父设备的handle(即Controller handle),就可以通过Controller handle打开父设备instance。通过父设备instance中的私有数据、协议等判断,是不是和当前子设备匹配。

例如,Mmc Drtiver的Support函数如下:

 EFI_STATUS
182  EFIAPI
183  MmcDriverBindingSupported (
184    IN EFI_DRIVER_BINDING_PROTOCOL    *This,
185    IN EFI_HANDLE                     Controller,
186    IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
187    )
188  {
189    EFI_STATUS                      Status;
190    //EFI_DEVICE_PATH_PROTOCOL        *ParentDevicePath;
191    EFI_MMC_HOST_PROTOCOL           *MmcHost;
192    EFI_DEV_PATH_PTR                Node;
193  
194    //
195    // Check RemainingDevicePath validation
196    //  通过父设备的DevicePath来判断,DevicePath有设备类型和子类型、NodeLength。
       //  通过这些信息判断是不是适配的父设备?
197    if (RemainingDevicePath != NULL) {
198      //
199      // Check if RemainingDevicePath is the End of Device Path Node,
200      // if yes, go on checking other conditions
201      //
202      if (!IsDevicePathEnd (RemainingDevicePath)) {
203        //
204        // If RemainingDevicePath isn't the End of Device Path Node,
205        // check its validation
206        //
207        Node.DevPath = RemainingDevicePath;
208        if (Node.DevPath->Type != HARDWARE_DEVICE_PATH ||
209            Node.DevPath->SubType != HW_VENDOR_DP ||
210            DevicePathNodeLength (Node.DevPath) != sizeof (VENDOR_DEVICE_PATH)) {
211          return EFI_UNSUPPORTED;
212        }
213      }
214    }
215  
216    //
217    // Check if Mmc Host protocol is installed by platform
218    // 通过协议判断,即看父设备handle上是不是安装了某个protocol。
       // 如果安装了该Protocol,可打开Instance,进一步检查。
219    Status = gBS->OpenProtocol (
220                    Controller,
221                    &gRaspberryPiMmcHostProtocolGuid,
222                    (VOID**)&MmcHost,
223                    This->DriverBindingHandle,
224                    Controller,
225                    EFI_OPEN_PROTOCOL_BY_DRIVER
226                  );
227    if (Status == EFI_ALREADY_STARTED) {
228      return EFI_SUCCESS;
229    }
      ......
}

9.3 谁来初始化父设备

对于父设备Driver来说,其Dxe本身只是在入口函数中安装一些协议,并不操作硬件。那么谁来对父设备硬件Controller初始化呢?

父设备的Driver只是一些Protocol的集合和实现。谁来构造出intance,并且调用其中的成员函数初始化父设备本身呢?

都是子设备Driver来实现的,也就是驱动的使用者来完成的。

如MMC Driver:

249  **/
250  EFI_STATUS
251  EFIAPI
252  MmcDriverBindingStart (
253    IN EFI_DRIVER_BINDING_PROTOCOL  *This,
254    IN EFI_HANDLE                   Controller,
255    IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
256    )
257  {
258    EFI_STATUS              Status;
259    MMC_HOST_INSTANCE       *MmcHostInstance;
260    EFI_MMC_HOST_PROTOCOL   *MmcHost;
261  
262    //
263    // Check RemainingDevicePath validation
264    //
265    if (RemainingDevicePath != NULL) {
266      //
267      // Check if RemainingDevicePath is the End of Device Path Node,
268      // if yes, return EFI_SUCCESS
269      //
270      if (IsDevicePathEnd (RemainingDevicePath)) {
271        return EFI_SUCCESS;
272      }
273    }
274  
275    //
276    // Get the Mmc Host protocol
277    //
278    Status = gBS->OpenProtocol (
279                    Controller,
280                    &gRaspberryPiMmcHostProtocolGuid,
281                    (VOID**)&MmcHost,
282                    This->DriverBindingHandle,
283                    Controller,
284                    EFI_OPEN_PROTOCOL_BY_DRIVER
285                  );
286    if (EFI_ERROR (Status)) {
287      if (Status == EFI_ALREADY_STARTED) {
288        return EFI_SUCCESS;
289      }
290      return Status;
291    }
292  
293    MmcHostInstance = CreateMmcHostInstance (MmcHost);
294    if (MmcHostInstance != NULL) {
295      // Add the handle to the pool
296      InsertMmcHost (MmcHostInstance);
297  
298      MmcHostInstance->Initialized = FALSE;
299  
300      // Detect card presence now
301      CheckCardsCallback (NULL, NULL);
302    }
303  
304    return EFI_SUCCESS;
305  }

需要搞明白的是,谁来调用这个device driver中的Start()函数?

是connect函数,扫描总线时,完成Controller和device的匹配。并调用device driver的Start()函数:

  • 创建host controller实例,并初始化controller硬件;
  • 创建devicepath
  • 安装一些device driver自身实现的Protocol到Controller。

可见Bus Driver只是提供了一些列接口,其他什么都没做。在Device driver中才会处理Controller相关的逻辑。

你可能感兴趣的:(UEFI,linux,arm开发)