UEFI Drivers是UEFI Image的一种,UEFI Drivers与UEFI Applications的区别:
Objects managed by UEFI-based firmware:
对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进行管理。此时就可以提供相应的输入输出设备,启动设备。
1.从类型上看,UEFI驱动可分为:
2.从驱动模型的角度,UEFI Drivers可分为两大类:UEFI Driver Model Driver和UEFI Non Driver Model Driver
Service Drivers
这种Driver会在一个或多个Service Handle上加载一个或多个Protocol,并在它的Entry Point里会返回EFI_SUCCESS。这类驱动不管理任何设备,一般用来产生Protocol。如常见的BootService、RuntimeService等等服务驱动
Initializing Drivers
这种Driver不会产生任何Handle,也不会增加任何Protocol到Handle Database,它只会进行一些初始化操作,并且返回错误代码。所以这种Driver执行完就会从系统内存中卸载。初始化服务、CPU 芯片 设备等的初始化驱动 FchDxe / PchDxe / CpuDxe。
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。
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
随着硬件总线结构的发展,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)。
一个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必须符合以下几个严格的规则:
包含Driver Binding Protocol实例的Image Handle被称为Driver Image Handle。
符合UEFI驱动模型的驱动相对DXE驱动有如下特点:
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()互相对应。
驱动程序模型采用 UEFI 驱动载入、连接的形式来进行硬件的辨识、控制及系统资源掌控。
注册发生在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。
2.StartImage()
通过StartImage()执行driver image的入口函数,入口函数会在Image Handle上安装其他的Protocol,如果为EFI Driver Model的driver,那么会安装EFI_DRIVER_BINDING_PROTOCOL。也可以选择性安装CONFIGURATION和COMPONET_NAME协议。
主要实现在DxeMain()函数,此函数启动driver 派遣器,查询所有的驱动程序。实现流程:
发生在BDS阶段。
LoadImage()和StartImage()之后,driver会等待被ConnectController()调用去连接到某个Controller。其实这时在内存中已经Load了很多的Driver都在等待被connect到controller,这时ConnectController()会以轮询方式去调用每个Driver Handle中的EFI_DRIVER_BINDING_PROTOCOL.Supported(),如果返回EFI_SUCCESS,那么该driver就会被connect到controller上了。
具体代码实现过程在BdsEntry()函数:
对于总线驱动和设备驱动,在Connect到Controller的时候所做的动作是不一样的。例如PCI总线驱动连接到PCI主桥之后,它会为每个PCI Device创建Child Device Handle,而PCI设备驱动不能创建新的Handle,它只是在Device Handle上安装Protocol Interface。
总线驱动
总线驱动会为在该总线上发现的子设备创建新的Device Handle,并且会在Child Device Handle上安装一些Protocol Interface。该操作是通过EFI_DRIVER_BINDING_PROTOCOL.Start()来完成的。
设备驱动
设备驱动不允许创造任何的新的Child Device Handle,它只是在现存的设备Handle上安装其他的协议接口。大多数常见设备驱动负责把I/O抽象挂到由总线驱动所创造的设备Device Handle上,该I/O抽象可以被用于启动EFI兼容的操作系统。
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
当EFI 刚出现的时候,模块没有固定的加载顺序是作为优点介绍的,但是运行过程确实需要加载顺序,这一切是通过源代码中每个模块 INF 中[Depex] Section 决定的。但是如果fdf文件规定了 APRIORI ,那它指定的模块优先级更高。
总结:
例子,
APRIORI DXE {
INF MdeModulePkg/Universal/PCD/DXE/Pcd.inf
}“
这里援引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的联系”一节。
系统中每个设备都有唯一的路径。例如,每次进入shell时,会打印出系统中的硬盘设备以及设备路径。例如Qemu的UEFI shell中包含的一块空硬盘,设备路径:
设备路径以节点为基本单位,一个设备路径由多个节点串联而成,以“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;
因此,我们对设备路径节点做一个总结。
不同类型的设备节点,有不同的结构体表示。但是都以EFI_DEVICE_PATH_PROTOCOL 作为第一个域,表示节点的header。
43 typedef struct {
44 UINT8 Type; //设备类型
51 UINT8 SubType; //次设备类型
56 UINT8 Length[2]; //节点长度
59 } EFI_DEVICE_PATH_PROTOCOL;
节点中的设备类型Type和次设备类型SubType共同决定了设备节点的类型。UEFI支持的设备类型和次设备类型有:
Device Path以Device Path Node为基本单位来依次串接而成,其结束标志是End of Hardware Device Path类型的Device Path Node。
每个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) => 意义同上。
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各个域的含义:
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终结符 |
需要说明下:
在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 |
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 |
BIOS Boot Specification Device Path
BIOS Boot Specification Device Path与前一种Media Device Path类型,不同的是它表示的是Legacy BIOS启动项设备。
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) |
具体实现可以参考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:。
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,里面也有不少有用的接口。
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及其行为。
UEFI SPEC定义了UEFI Driver Model, 在这个模型下UEFI Driver可以分成3类:
这里区分出了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总线拓扑层次结构
Example 3: USB总线拓扑层次结构
可以简单地这样来理解UEFI Driver和Controller的关系:
Bus Driver负责初始化Host Bus Controller,侦测并枚举Host Bus Controller以下的所有连接到该总线的设备,这些设备统称为Child Device Controller。
Device Driver负责初始化上述1个或者多个Child Device Controller。
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的参数接口简单解释如下:
上述UEFI Bus Driver Connect到Host Bus Controller的过程简化如下:
创建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。
某些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:
其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。
以下是几个例子:
每个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之间的关系可以参考下图:
看了一些书,也在网上搜索了一些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进行一些操作。
一个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判断一个设备B是不是其子设备:A将自己的handle作为B的Controller handle,通过B的handle打开B的实例,调用B的Support函数来判断。
子设备在其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是不是自己的爹?
既然子设备知道父设备的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 }
......
}
对于父设备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()函数:
可见Bus Driver只是提供了一些列接口,其他什么都没做。在Device driver中才会处理Controller相关的逻辑。