UEFI设备驱动模型
1. UEFI Drivers
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进行管理。此时就可以提供相应的输入输出设备,启动设备。
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
-
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
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必须符合以下几个严格的规则:
- 不允许接触任何的硬件,相反的,只允许这个Driver在它自己的Image Handle上安装Protocol实例。
- UEFI Driver Model Drivers必须安装一个Driver Binding Protocol到它的Image Handle上,可选择的安装Driver Configuration Protocol, Driver Diagnostics Protocol, Component Name Protocol。另外,如果一个Driver希望可以被卸载,它可以选择更新Load Image Protocol来提供自己的Unloaded功能。
- 如果在执行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驱动有如下特点:
- 不需要依赖Dependency来决定执行的顺序
- 必须可以被重复执行
- 不需要即时启动
- 支持硬体的热插拔(Hot-Plug)
- 支持软体的热插拔(Unload)
- 所有的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。
2.StartImage()
通过StartImage()执行driver image的入口函数,入口函数会在Image Handle上安装其他的Protocol,如果为EFI Driver Model的driver,那么会安装EFI_DRIVER_BINDING_PROTOCOL。也可以选择性安装CONFIGURATION和COMPONET_NAME协议。
主要实现在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函数激活驱动
对于总线驱动和设备驱动,在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兼容的操作系统。
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 ,那它指定的模块优先级更高。
总结:
- fdf 文件中 APRIORI DXE 指定的优先级最高;
- 模块中 INF 文件 Depex Section 可以指定依赖关系;
- 同等优先级的由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。
<