UEFI支持通过加载UEFI驱动和UEFI应用程序映象来扩展平台固件。当UEFI驱动程序和UEFI应用程序加载时,它们可以访问所有UEFI定义的运行时和引导服务。
UEFI允许将OS加载程序和平台固件的引导菜单整合到一个单一的平台固件菜单中。这些平台固件菜单允许从UEFI引导服务支持的任何引导介质的任何分区选择任何UEFI OS加载器。UEFI OS加载器可以支持多个选项,这些选项可以出现在用户界面上。也可以包括旧的启动选项,比如在平台固件启动菜单中从A:或C:驱动器启动。
UEFI支持从包含UEFI OS加载器或UEFI定义的系统分区的媒体启动。UEFI需要一个UEFI定义的系统分区从块设备启动。UEFI不需要对分区的第一个扇区进行任何更改,因此可以构建在传统架构和UEFI平台上启动的媒体。
2.1启动管理器
UEFI包含一个引导管理器,允许从UEFI定义的文件系统上的任何文件或使用UEFI定义的映像加载服务加载写入该规范的应用程序(包括OS第一阶段加载器)或UEFI驱动程序。UEFI定义了用于指向要加载的文件的NVRAM变量。这些变量还包含直接传递给UEFI应用程序的特定于应用程序的数据。这些变量还包含一个人类可读的字符串,可以在菜单中显示给用户。
UEFI定义的变量允许系统固件包含一个引导菜单,该菜单可以指向所有操作系统,甚至是同一操作系统的多个版本。UEFI的设计目标是在平台固件中拥有一组引导菜单。UEFI仅指定在选择启动选项时使用的NVRAM变量。UEFI将菜单系统的实现作为增值实现空间。
UEFI极大地扩展了系统的引导灵活性,超越了pc - at类系统的当前状态。今天的pc - at类系统被限制从第一张软盘、硬盘驱动器、CD-ROM、USB密钥或网卡连接到系统上启动。从普通硬盘启动可能导致操作系统之间的互操作性问题,以及来自同一供应商的不同版本的操作系统。
2.1.1 UEFI映象
UEFI Images是UEFI定义的一类文件,包含可执行代码。UEFI Images最显著的特性是,UEFI Image文件中的第一组字节包含一个定义可执行映象编码的映象头。
UEFI使用PE32+映象格式的子集,头部签名经过修改。修改PE32+映象中的特征值是为了区分UEFI映象和正常的PE32可执行文件。PE32附加的“+”提供了标准PE32格式的64位重定位固定扩展。
对于具有UEFI映象签名的映象,PE映象头中的子系统值定义如下。映象类型之间的主要区别是固件将加载映象的内存类型,以及当映象的入口点退出或返回时所采取的操作。当从映象入口点返回控制时,UEFI应用程序映象总是被卸载。UEFI驱动映象只有在控制带UEFI错误代码返回时才会卸载。
// PE32+ Subsystem type for EFI images
#define EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION 10
#define EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER 11
#define EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 12
// PE32+ Machine type for EFI images
#define EFI_IMAGE_MACHINE_IA32 0x014c
#define EFI_IMAGE_MACHINE_IA64 0x0200
#define EFI_IMAGE_MACHINE_EBC 0x0EBC
#define EFI_IMAGE_MACHINE_x64 0x8664
#define EFI_IMAGE_MACHINE_ARMTHUMB_MIXED 0x01C2
#define EFI_IMAGE_MACHINE_AARCH64 0xAA64
#define EFI_IMAGE_MACHINE_RISCV32 0x5032
#define EFI_IMAGE_MACHINE_RISCV64 0x5064
#define EFI_IMAGE_MACHINE_RISCV128 0x5128
注意:选择这种映象类型是为了让UEFI映象包含Thumb和Thumb2指令,同时将EFI接口本身定义为ARM模式。
Subsystem Type | Code Memory Type | Data Memory Type |
EFI_IMAGE_SUSBSYTEM_EFI_APPLICATION | EfiLoaderCode | EfiLoaderData |
EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER | EfiBootServicesCode | EfiBootServicesData |
EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER | EfiRuntimeServicesCode | EfiRuntimeServicesData |
在PE映象文件头中找到的Machine值用于指示映象的机器代码类型。下面定义了具有UEFI映象签名的映象的机器码类型。给定的平台必须实现该平台的本地映象类型和EFI字节码(EBC)的映象类型。对其他机器码类型的支持对于平台来说是可选的。
UEFI映象通过EFI_BOOT_SERVICES.LoadImage()引导服务加载到内存中。
该服务将PE32+格式的映象加载到内存中。需要使用这个PE32+加载器将PE32+映象的所有部分加载到内存中。一旦将映像加载到内存中,并执行了适当的修复,根据应用程序基于支持的32位、64位或128位处理器的常规间接调用约定,就会在AddressOfEntryPoint引用中将控制转移到加载的映像。与UEFI映像的所有其他链接都是通过编程完成的。
2.1.2 UEFI应用程序
写入该规范的应用程序由引导管理器或其他UEFI应用程序加载。为了加载UEFI应用程序,固件会分配足够的内存来存放映像,将UEFI应用程序映像中的部分复制到分配的内存中,并应用所需的重新定位修复。一旦完成,分配的内存将被设置为适合用于映象的代码和数据的类型。然后将控制转移到UEFI应用程序的入口点。当应用程序从其入口点返回时,或者当它调用Boot Service EFI_BOOT_SERVICES.Exit()时,UEFI应用程序将从内存中卸载,并将控制返回到加载UEFI应用程序的UEFI组件。
当引导管理器加载UEFI应用程序时,可以使用映像句柄来定位UEFI应用程序的“加载选项”。加载选项存储在非易失性存储中,并与引导管理器加载和执行的UEFI应用程序相关联。
2.1.3 UEFI OS loader
UEFI OS loader是一种特殊类型的UEFI应用程序,通常从符合该规范的固件接管系统控制。当加载时,UEFI OS加载器的行为与任何其他UEFI应用程序一样,它必须只使用从固件分配的内存,并且只能使用UEFI服务和协议访问固件公开的设备。如果UEFI OS加载程序包含任何引导服务样式驱动程序功能,那么它必须使用适当的UEFI接口来获得对总线特定资源的访问。也就是说,I/O和内存映射设备寄存器必须通过适当的总线特定的I/O调用来访问,就像UEFI驱动程序将执行的调用一样。
如果UEFI OS加载器遇到问题,无法正确加载其操作系统,它可以释放所有分配的资源,并通过Boot Service Exit()调用将控制权返回给固件。Exit()调用允许返回错误代码和ExitData。ExitData包含要返回的字符串和特定于OS加载器的数据。
如果UEFI OS加载器成功加载了它的操作系统,它可以使用引导服务EFI_BOOT_SERVICES.ExitBootServices()来控制系统。成功调用后ExitBootServices()时,系统中的所有引导服务都被终止,包括内存管理,UEFI OS加载器负责系统的继续运行。
2.1.4 UEFI驱动
UEFI驱动程序由引导管理器、符合此规范的固件或其他UEFI应用程序加载。为了加载UEFI驱动,固件会分配足够的内存来存放映像,将UEFI驱动映像中的部分复制到分配的内存中,并应用所需的重新定位修复。一旦完成,分配的内存将被设置为适合用于映象的代码和数据的类型。然后将控制转移到UEFI驱动的入口点。当UEFI驱动从其入口点返回时,或者当它调用Boot Service EFI_BOOT_SERVICES.Exit()时,可选地将UEFI驱动从内存中卸载,并将控制返回到加载UEFI驱动的组件。如果返回EFI_SUCCESS状态码,UEFI驱动不会从内存中卸载。如果UEFI驱动的返回码是错误状态码,则驱动从内存中卸载。
UEFI驱动有两种类型:引导服务驱动和运行时驱动。这两种驱动类型之间的唯一区别是,UEFI运行时驱动在UEFI OS加载程序通过引导服务EFI_BOOT_SERVICES.ExitBootServices()控制平台后可用。
当调用ExitBootServices()时,UEFI引导服务驱动程序将被终止,UEFI引导服务驱动程序所消耗的所有内存资源将被释放,供操作系统环境使用。
当操作系统调用SetVirtualAddressMap()时,类型为EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER的运行时驱动程序将被虚拟映射固定。
2.2固件核心
本节概述UEFI定义的服务。这些服务包括引导服务和运行时服务。
2.2.1 UEFI服务
UEFI接口的目的是定义一个通用的引导环境抽象,供加载的UEFI映像使用,包括UEFI驱动程序、UEFI应用程序和UEFI OS加载器。这些调用是用一个完整的64位接口定义的,因此未来还有增长的空间。这组抽象平台调用的目标是允许平台和操作系统彼此独立地发展和创新。此外,操作系统还可以使用一组标准的基本运行时服务。
本节中定义的平台接口允许使用标准的即插即用选项rom作为引导服务的底层实现方法。接口的设计方式是映射回遗留接口。这些接口没有任何传统Option rom固有的限制。
UEFI平台接口旨在提供平台和操作系统之间的抽象,以便在平台上引导。UEFI规范还提供诊断或实用程序与平台之间的抽象;但是,它并不尝试实现一个完整的诊断操作系统环境。预计可以在UEFI系统上轻松构建一个小型诊断类os环境。本规范没有描述这样的诊断环境。
由本规范添加的接口分为以下几类,稍后将在本文档中详细介绍:
•运行时服务
•引导服务接口,有以下子类:
—全局启动服务接口
—基于设备句柄的引导服务接口
—设备协议
—协议服务
2.2.2运行时服务
介绍UEFI运行时业务功能。运行时服务的主要目的是从操作系统中抽象出平台硬件实现的一小部分。运行时服务功能在引导过程中可用,并且在运行时也可用,只要操作系统切换到平面物理寻址模式,就可以进行运行时调用。然而,如果操作系统加载器或操作系统使用运行时服务SetVirtualAddressMap()服务,操作系统将只能在虚拟寻址模式下调用运行时服务。所有运行时接口都是非阻塞接口,如果需要,可以使用禁用中断调用。为了确保与现有平台的最大兼容性,建议所有包含运行时服务的UEFI模块都在MemoryMap中表示为类型为EfiRuntimeServicesCode的单个EFI_MEMORY_DESCRIPTOR。
在所有情况下,运行时服务使用的内存都必须保留,不能被操作系统使用。运行时服务内存对UEFI功能总是可用的,不会被操作系统或其组件直接操作。UEFI负责定义运行时服务使用的硬件资源,因此当运行时服务调用时,操作系统可以与这些资源同步,或者保证操作系统永远不会使用这些资源。
Table 2-2 UEFI Runtime Services
名称 | 描述 |
GetTime() | 返回当前时间、时间上下文和时间保持功能。 |
SetTime() | 设置当前时间和时间上下文。 |
GetWakeupTime() | 返回当前的唤醒警报设置。 |
SetWakeupTime() | 设置当前的唤醒闹钟设置。 |
GetVariable() | 返回指定变量的值。 |
GetNextVariableName() | 枚举变量名。 |
SetVariable() | 设置,并在需要时创建一个变量。 |
SetVirtualAddressMap() | 将所有运行时函数从物理寻址切换到虚拟寻址。 |
ConvertPointer() | 用于将指针从物理寻址转换为虚拟寻址。 |
GetNextHighMonotonicCount() | 包含平台单调的计数器功能。 |
ResetSystem() | 重置所有处理器和设备并重新启动系统。 |
UpdateCapsule() | 将胶囊传递到具有虚拟和物理映射的固件。 |
QueryCapsuleCapabilities() | 返回如果胶囊可以支持通过UpdateCapsule()。 |
QueryVariableInfo() | 返回关于EFI变量store的信息。 |
2.3调用约定
除非另有说明,UEFI规范中定义的所有函数都是通过C编译器中通用的、架构定义的调用约定指针调用的。指向各种全局UEFI函数的指针可以在通过系统表定位的EFI_RUNTIME_SERVICES和EFI_BOOT_SERVICES表中找到。指向本规范中定义的其他函数的指针是通过设备句柄动态定位的。在所有情况下,所有指向UEFI函数的指针都使用单词EFIAPI进行转换。这允许每个体系结构的编译器提供适当的编译器关键字来实现所需的调用约定。当将指针参数传递给引导服务、运行时服务和协议接口时,调用者有以下职责:
•传递引用物理内存位置的指针参数是调用者的责任。如果传递的指针不指向物理内存位置(例如,内存映射I/O区域),结果是不可预测的,系统可能会停止。
•调用者有责任通过正确的对齐方式传递指针参数。如果一个未对齐的指针被传递给一个函数,结果是不可预测的,系统可能会停止。
•调用方有责任不向函数传递NULL参数,除非明确允许。如果一个NULL指针被传递给一个函数,结果是不可预测的,系统可能挂起。
•除非另有说明,如果函数返回错误,调用者不应该对指针形参的状态做任何假设。
•调用者不能通过值传递大于本机大小的结构,这些结构必须通过引用(通过指针)传递。传递大于本机宽度的结构(在受支持的32位处理器上为4个字节;8个字节(在支持的64位处理器指令上))的堆栈将产生未定义的结果。
下面将更详细地描述受支持的32位和64位应用程序的调用约定。任何函数或协议都可以返回任何有效的返回码。
UEFI模块的所有公共接口都必须遵循UEFI调用约定。公共接口包括映象入口点、UEFI事件处理程序和协议成员函数。类型EFIAPI用于指示是否符合本节定义的调用约定。非公共接口,如私有函数和静态库调用,不需要遵循UEFI调用约定,可以由编译器进行优化。
2.3.1数据类型
接口定义中常用的数据类型如表2-3所示,其修改项如表2-4所示。除非另行指定,否则所有数据类型都是自然对齐的。结构对齐的边界等于结构的最大内部数据,内部数据隐式填充以实现自然对齐。
传递到UEFI接口或由UEFI接口返回的指针的值必须为底层类型提供自然对齐。
Table 2-3 常用的UEFI数据类型
项目 | 描述 |
BOOLEAN | 逻辑布尔值。一个字节的值,包含0表示FALSE, 1表示TRUE。其他值未定义。 |
INTN | 本机宽度的带符号值。(支持32位处理器指令为4字节,支持64位处理器指令为8字节,支持128位处理器指令为16字节) |
UINTN | 原生宽度的无符号值。(支持32位处理器指令为4字节,支持64位处理器指令为8字节,支持128位处理器指令为16字节) |
INT8 | 1字节有符号值。 |
UINT8 | 1字节无符号值。 |
INT16 | 2字节有符号值。 |
UINT16 | 2字节无符号值。 |
INT32 | 4字节有符号值。 |
UINT32 | 4字节无符号值。 |
INT64 | 8字节有符号值。 |
UINT64 | 8字节无符号值。 |
INT128 | 16字节有符号值。 |
UINT128 | 16字节无符号值。 |
CHAR8 | 字节字符。除非另有说明,所有1字节或ASCII字符和字符串都以8位ASCII编码格式存储,使用ISO-Latin-1字符集。 |
CHAR16 | 2字节字符。除非另有指定,否则所有字符和字符串都以Unicode 2.1和ISO/IEC 10646标准定义的UCS-2编码格式存储。 |
VOID | 未申报的类型。 |
EFI_GUID | 包含唯一标识符值的128位缓冲区。除非另有说明,否则按64位边界对齐。 |
EFI_STATUS | 状态码。UINTN类型。 |
EFI_HANDLE | 相关接口的集合。类型VOID *。 |
EFI_EVENT | 事件结构的句柄。类型VOID *。 |
EFI_LBA | 逻辑块地址。UINT64类型。 |
EFI_TPL | 任务优先级。UINTN类型。 |
EFI_MAC_ADDRES S | 包含网络媒体访问控制地址的32字节缓冲区。 |
EFI_IPv4_ADDRE SS | 4字节缓冲区。IPv4网络协议地址。 |
EFI_IPv6_ADDRE SS | 16字节缓冲区。IPv6互联网协议地址。 |
EFI_IP_ADDRESS | 按4字节边界对齐的16字节缓冲区。IPv4或IPv6互联网协议地址。 |
<Enumerated Type> | 标准ANSI C enum类型声明的元素。INT32类型。或UINT32。ANSI C没有定义枚举符号的大小,所以它们不应该在结构中使用。当作为参数传递给函数时,ANSI C的整数提升规则使INT32或UINT32可以互换。 |
sizeof (VOID *) | 4字节支持的32位处理器指令。8字节支持的64位处理器指令。16字节,支持128位处理器。 |
Bitfields | 位域的顺序是0位是最低有效位。 |
Table 2-4 常用UEFI数据类型的描述器
项目 | 描述 |
IN | 将数据传递给函数。 |
OUT | Datum从函数中返回。 |
OPTIONAL | 将数据传递给函数是可选的,如果没有提供该值,可能会传递NULL。 |
CONST | 数据是只读的。 |
EFIAPI | 定义UEFI接口的调用约定。 |
2.3.2 IA-32平台
所有函数都按照C语言调用约定调用。在函数调用中易失的通用寄存器是eax、ecx和edx。所有其他通用寄存器都是非易失性的,并由目标函数保存。此外,除非函数定义另有规定,否则将保留所有其他寄存器。
在操作系统调用ExitBootServices()之前,固件引导服务和运行时服务以以下处理器执行模式运行:
•单处理器,如8.4章所述:
- intel 64 and IA-32架构软件开发人员手册
-第3卷,系统编程指南,第1部分
-订单号:253668-033US, 2009年12月
-请参阅“英特尔处理器手册”标题下的“uefi相关文档链接”(http://uefi.org/uefi)。
•保护模式
•可能开启了分页模式。如果启用了分页模式,建议使用PAE(物理地址扩展)模式。如果启用了分页模式,则UEFI内存映射定义的任何内存空间都是标识映射(虚拟地址等于物理地址)。到其他区域的映射是未定义的,并且可能因实现而异。
•选择器被设置为平面,否则不使用
•中断是启用的——尽管除了UEFI引导服务定时器功能之外不支持中断服务(所有加载的设备驱动程序都是通过“轮询”同步服务的)。
•EFFLAG中的方向标志清晰
•未定义其他通用标志寄存器
•128 KiB或更多可用堆栈空间
•堆栈必须是16字节对齐的。在标识映射页表中,堆栈可能被标记为不可执行。
•浮点控制字必须初始化为0x027F(所有异常都被屏蔽,双精度,舍至最近)
•多媒体扩展控制字(如果支持)必须初始化为0x1F80(所有异常屏蔽,整数到最近,为屏蔽底流刷新为零)。
•CR0.EM必须为零
•CR0.TS必须为零
编写到该规范的应用程序可能会改变处理器执行模式,但UEFI映像必须确保固件引导服务和运行时服务在规定的执行环境下执行。
操作系统调用ExitBootServices()后,固件引导服务不再可用,调用任何引导服务都是非法的。在ExitBootServices之后,固件运行时服务仍然可用,并且可以通过启用分页和虚拟地址指针调用已经调用SetVirtualAddressMap()来描述固件运行时服务使用的所有虚拟地址范围。
对于使用任何UEFI运行时服务的操作系统,必须:
•保存内存映射中标记为运行时代码和运行时数据的所有内存
•调用运行时服务函数,条件如下:
-保护模式
-分页可能启用或不启用,但如果分页启用和没有调用SetVirtualAddressMap(),任何由UEFI内存映射定义的内存空间都是标识映射(虚拟地址等于物理地址),尽管某些区域的属性可能没有所有的读、写和执行属性,或者出于平台保护的目的没有标记。到其他区域的映射是未定义的,并且可能因实现而异。参见SetVirtualAddressMap()的描述,了解调用该函数后的内存映射的细节。
-清除esflags中的方向标志
-4kib或更多可用堆栈空间
-堆叠必须是16字节对齐的
-浮点控制字必须初始化为0x027F(所有异常都被屏蔽,双精度,舍入到最近)多媒体扩展控制字(如果支持)必须被初始化为0x1F80(所有的异常都被屏蔽,从整数到最近,为屏蔽底流刷新为零)
-CR0.EM必须为零
-CR0.TS必须为零
-中断是否被禁用或启用由调用方决定
•在启动时加载的acpi表可以包含在内存类型中EfiACPIReclaimMemory(推荐)或EfiACPIMemoryNVS。ACPI FACS必须包含在EfiACPIMemoryNVS类型的内存中。
•系统固件不能请求任何类型为EfiACPIReclaimMemory或EfiACPIMemoryNVS的内存描述符的虚拟映射。
EfiACPIReclaimMemory和EfiACPIMemoryNVS类型的efi内存描述符必须在4kib边界上对齐,并且大小必须是4kib的倍数。
任何通过请求虚拟映射的UEFI内存描述符设置了EFI_MEMORY_RUNTIME位的EFI_MEMORY_DESCRIPTOR必须在4kib边界上对齐,并且大小必须是4kib的倍数。
•ACPI内存操作区必须继承UEFI内存映射的缓存属性。如果系统内存映射不包含缓存属性,则ACPI内存操作区必须从ACPI名称空间继承其缓存属性。如果在系统内存映射或ACPI名称空间中不存在可缓存属性,则必须假定该区域是不可缓存的。
•在运行时加载的acpi表必须包含在EfiACPIMemoryNVS类型的内存中。运行时加载的ACPI表的缓存属性应该在UEFI内存映射中定义。如果在UEFI内存映射中没有关于表位置的信息,则可以从ACPI内存描述符获取缓存属性。如果在UEFI内存映射或ACPI内存描述符中没有关于表位置的信息,则假定该表是非缓存的。
•一般情况下,启动时加载的UEFI配置表(例如SMBIOS表)可以包含在内存类型为EfiRuntimeServicesData(推荐),EfiBootServicesData, EfiACPIReclaimMemory或EfiACPIMemoryNVS的内存中。运行时加载的表必须包含在EfiRuntimeServicesData(推荐)或EfiACPIMemoryNVS类型的内存中。
注意:以前的EFI规范允许在运行时加载ACPI表EfiReservedMemoryType,没有为其他EFI配置表提供指导。EfiReservedMemoryType不打算用于存储任何EFI配置表。此外,只有符合UEFI规范的操作系统才能保证在内存中处理类型为EfiBootServicesData的SMBIOS表。
当加载32位UEFI OS时,系统固件将在32位平面模式下将控制权移交给OS。所有的描述符都设置为它们的4GiB限制,这样所有的内存都可以从所有段访问。
图2-2显示了在支持的32位系统上调用映像的PE32+头中的AddressOfEntryPoint后的堆栈。所有UEFI映象入口点都有两个参数。这些是UEFI映像句柄,以及指向EFI系统表的指针。
2.3.4 x64的平台
所有函数都按照C语言调用约定调用。更多细节请参见2.3.4.2节。
在启动服务期间,处理器处于以下执行模式:
•单处理器,如8.4章所述:
英特尔64和IA-32架构软件开发人员手册第3卷,系统编程指南,第1部分
订单号:253668-033US, 2009年12月
请参阅“英特尔处理器手册”标题下的“uefi相关文档链接”(http://uefi.org/uefi)
•长模式,64位模式
•启用分页模式,UEFI内存映射定义的任何内存空间都是标识映射(虚拟地址等于物理地址),尽管某些区域的属性可能没有所有的读、写和执行属性,或者出于平台保护的目的没有标记。到其他区域(如未接受内存的区域)的映射是未定义的,并且可能因实现而异。
•选择器被设置为平面,否则不使用。
•中断是启用的——尽管除了UEFI引导服务定时器功能之外不支持中断服务(所有加载的设备驱动程序都是通过“轮询”同步服务的)。
•EFFLAG中的方向标志清晰
•未定义其他通用标志寄存器
•128 KiB或更多可用堆栈空间
•堆栈必须是16字节对齐的。在标识映射页表中,堆栈可能被标记为不可执行。
•浮点控制字必须初始化为0x037F(所有异常都被屏蔽,双扩展精度,舍入到最接近)
•多媒体扩展控制字(如果支持)必须初始化为0x1F80(所有异常屏蔽,整数到最近,为屏蔽底流刷新为零)。
•CR0.EM必须为零
•CR0.TS必须为零
对于使用任何UEFI运行时服务的操作系统,必须:
•保存内存映射中标记为运行时代码和运行时数据的所有内存
•调用运行时服务函数,条件如下:
•长模式,64位模式
•启用分页
•所有选择器设置为平面的virtual = physical address。如果UEFI OS加载器或OS使用SetVirtualAddressMap()在虚拟地址空间中重新定位运行时服务,则不需要满足此条件。参见SetVirtualAddressMap()的描述,了解调用该函数后的内存映射的细节。
•EFFLAG中的方向标志清除
•4 KiB或更多可用堆栈空间
•堆栈必须是16字节对齐的
•浮点控制字必须初始化为0x037F(所有异常都被屏蔽,双扩展精度,舍入到最接近)
•多媒体扩展控制字(如果支持)必须初始化为0x1F80(所有异常都被屏蔽,整数到最近,为屏蔽底流刷新为零)
•CR0.EM必须为零
•CR0.TS必须为零
•根据调用方的决定,中断可以被禁用或启用。
•在启动时加载的acpi表可以包含在内存类型中EfiACPIReclaimMemory(推荐)或EfiACPIMemoryNVS。ACPI FACS必须包含在EfiACPIMemoryNVS类型的内存中。
•系统固件不能请求任何类型为EfiACPIReclaimMemory或EfiACPIMemoryNVS的内存描述符的虚拟映射。
EfiACPIReclaimMemory和EfiACPIMemoryNVS类型的efi内存描述符必须在4kib边界上对齐,并且大小必须是4kib的倍数。
任何通过请求虚拟映射的UEFI内存描述符
设置了EFI_MEMORY_RUNTIME位的EFI_MEMORY_DESCRIPTOR必须在4kib边界上对齐,并且大小必须是4kib的倍数。
•ACPI内存操作区必须继承UEFI内存映射的缓存属性。如果系统内存映射不包含缓存属性,则ACPI内存操作区必须从ACPI名称空间继承其缓存属性。如果在系统内存映射或ACPI名称空间中不存在可缓存属性,则必须假定该区域是不可缓存的。
•在运行时加载的acpi表必须包含在EfiACPIMemoryNVS类型的内存中。运行时加载的ACPI表的缓存属性应该在UEFI内存映射中定义。如果在UEFI内存映射中没有关于表位置的信息,则可以从ACPI内存描述符获取缓存属性。如果在UEFI内存映射或ACPI内存描述符中没有关于表位置的信息,则假定该表是非缓存的。
•一般情况下,启动时加载的UEFI配置表(例如SMBIOS表)可以包含在内存类型为EfiRuntimeServicesData(推荐),EfiBootServicesData, EfiACPIReclaimMemory或EfiACPIMemoryNVS的内存中。运行时加载的表必须包含在EfiRuntimeServicesData(推荐)或EfiACPIMemoryNVS类型的内存中。
注意:以前的EFI规范允许在运行时加载ACPI表EfiReservedMemoryType,没有为其他EFI配置表提供指导。EfiReservedMemoryType不打算被固件使用。此外,只有符合UEFI规范的操作系统才能保证在内存中处理类型为EfiBootServicesData的SMBIOS表。
Rcx, EFI_HANDLE
Rdx - EFI_SYSTEM_TABLE * RSP - <返回地址>
调用者传递寄存器中的前四个整型参数。整数值在Rcx、Rdx、R8和R9寄存器中从左到右传递。调用者将参数5及以上传递给堆栈。所有参数必须在传递它们的寄存器中右对齐。这确保了被调用方只能处理寄存器中需要的位。
调用方通过指向调用方分配的内存的指针传递数组和字符串。调用者传递大小为8、16、32或64位的结构和联合,就好像它们是相同大小的整数一样。调用者不允许传递这些大小以外的结构和联合,必须通过指针传递这些联合和结构。
如果需要,被调用方必须将寄存器参数转储到它们的阴影空间中。最常见的要求是采取一个论点的地址。
如果参数是通过可变参数传递的,那么实际上应用的是典型的参数传递,包括将第五个参数和随后的参数传递到堆栈上。被调用方必须转储其地址被占用的参数。
在Rax寄存器中返回固定为64位的返回值。如果返回值不符合64位,则调用者必须分配并传递一个指针作为第一个参数Rcx。随后的参数会向右移动一个参数,例如参数1会在Rdx中传递。返回的用户定义类型长度必须为1、2、4、8、16、32或64位。
寄存器Rax、Rcx Rdx R8、R9、R10、R11和XMM0-XMM5是volatile的,因此在函数调用时被销毁。
寄存器RBX、RBP、RDI、RSI、R12、R13、R14、R15和XMM6-XMM15被认为是非易失性的,必须由使用它们的函数保存和恢复。
函数指针是指向各自函数标签的指针,不需要特殊处理。调用方必须始终在堆栈对齐16字节的情况下调用。
对于MMX、XMM和浮点值,可以容纳64位的返回值通过RAX返回(包括MMX类型)。但是,XMM 128位类型、浮点数和双精度数以XMM0的形式返回。浮点状态寄存器不被目标函数保存。浮点和双精度参数在XMM0 - XMM3(最多4)中传递,带有通常用于基数槽的整数槽(RCX、RDX、R8和R9)被忽略(参见示例),反之亦然。XMM类型不会通过直接的值传递,而是将一个指针传递给调用者分配的内存。MMX类型将作为相同大小的整数传递。在没有提供正确的异常处理程序的情况下,调用者不能解除异常屏蔽。
此外,除非函数定义中另有规定,否则将保留所有其他CPU寄存器(包括MMX和XMM)。
引导服务定义了一个执行环境,其中没有启用分页(支持32位),或者启用了翻译,但映射为虚拟相等的物理(x64),本节将描述如何编写启用了分页或替代翻译的应用程序。一些操作系统要求OS Loader能够在引导服务时启用OS所需的翻译。
如果一个UEFI应用程序使用它自己的页表、GDT或IDT,应用程序必须确保固件使用每个替代的数据结构执行。当应用程序启用了分页功能时,有两种方法可以执行符合此规范的固件。
•显式固件调用
•通过定时器事件抢占应用程序的固件
启用翻译的应用程序可以在每次调用UEFI之前恢复所需的固件映射。但是,抢占的可能性可能要求启用翻译的应用程序在启用备选翻译时禁用中断。对于支持翻译的应用程序来说,如果应用程序在调用UEFI中断ISR之前捕获中断并恢复EFI固件环境,则启用中断是合法的。在UEFI ISR上下文执行之后,它将返回到启用翻译的应用程序上下文,并恢复应用程序所需的任何映射。
2.4协议
属性发现设备句柄支持的协议EFI_BOOT_SERVICES.HandleProtocol()引导服务或
EFI_BOOT_SERVICES.OpenProtocol()启动服务。每个协议都有一个规范,包括以下内容:
•协议的全局唯一ID (GUID)
•协议接口结构
•协议服务
除非另有规定,否则协议的接口结构不会从运行时内存中分配,协议成员函数也不应该在运行时被调用。如果未显式指定,则可以在小于或等于TPL_NOTIFY的TPL级别上调用协议成员函数(参见7.1节)。除非另有规定,协议的成员函数是不可重入的或MP安全的。
任何状态码协议定义的成员函数定义是需要实现的,额外的就会返回一个错误代码,但他们不会被测试通过标准遵从性测试,和任何软件使用过程不能依靠任何扩展的错误代码,可以提供一个实现。
要确定句柄是否支持任何给定的协议,将协议的GUID传递给
HandleProtocol()或OpenProtocol()。如果设备支持请求的协议,则返回一个指向已定义协议接口结构的指针。协议接口结构将调用者链接到用于此设备的特定于协议的服务。
协议的结构如图2-4所示。UEFI驱动程序包含特定于一个或多个协议实现的函数,并将它们注册到Boot Service EFI_BOOT_SERVICES.InstallProtocolInterface()。固件返回协议的协议接口,然后用于调用特定于协议的服务。UEFI驱动通过协议接口保持私有的、设备特定的上下文。
下面的C代码片段演示了协议的使用:
// There is a global “EffectsDevice” structure. This
// structure contains information pertinent to the device.
// Connect to the ILLUSTRATION_PROTOCOL on the EffectsDevice,
// by calling HandleProtocol with the device’s EFI device handle
// and the ILLUSTRATION_PROTOCOL GUID.
EffectsDevice.Handle = DeviceHandle;
Status = HandleProtocol (
EffectsDevice.EFIHandle,
&IllustrationProtocolGuid,
&EffectsDevice.IllustrationProtocol
);
// Use the EffectsDevice illustration protocol’s “MakeEffects”
// service to make flashy and noisy effects.
Status = EffectsDevice.IllustrationProtocol->MakeEffects (
EffectsDevice.IllustrationProtocol,
TheFlashyAndNoisyEffect
);
Table 2-10 下列表格列出了本协议规范定义的UEFI协议
Table 2-10 UEFI 协议
协议 | 描述 |
EFI_LOADED_IMAGE_PROTOCOL | 提供有关图像的信息。 |
EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL | 指定通过EFI引导服务LoadImage()加载PE/COFF映像时使用的设备路径。 |
EFI_DEVICE_PATH_PROTOCOL | 提供设备所在位置信息。 |
EFI_DRIVER_BINDING_PROTOCOL | 提供服务来决定一个UEFI驱动程序是否支持一个给定的控制器,和服务启动和停止一个给定的控制器。 |
EFI_DRIVER_FAMILY_OVERRIDE_PROTOCOL | 提供一个Driver Family Override机制,用于为给定控制器选择最佳的驱动程序。 |
EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCO L | 为给定控制器选择最佳驱动程序提供一个平台特定的覆盖机制。 |
EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PRO TOCOL | 为为给定控制器选择最佳驱动器提供总线特定的覆盖机制。 |
EFI_DRIVER_DIAGNOSTICS2_PROTOCOL | 为UEFI驱动管理的控制器提供诊断服务。 |
EFI_COMPONENT_NAME2_PROTOCOL | 为UEFI驱动程序和驱动程序管理的控制器提供人类可读的名称。 |
EFI_SIMPLE_TEXT_INPUT_PROTOCOL | 支持简单控制台样式文本输入的设备的协议接口。 |
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL | 支持控制台样式文本显示的设备的协议接口。 |
EFI_SIMPLE_POINTER_PROTOCOL | 鼠标和轨迹球等设备的协议接口。 |
EFI_SERIAL_IO_PROTOCOL | 支持串行字符传输的设备的协议接口。 |
EFI_LOAD_FILE_PROTOCOL | 用于从任意设备读取文件的协议接口。 |
EFI_LOAD_FILE2_PROTOCOL | 用于从任意设备读取非启动选项文件的协议接口 |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL | 打开包含UEFI文件系统的磁盘卷的协议接口。 |
EFI_FILE_PROTOCOL | 提供对支持的文件系统的访问。 |
EFI_DISK_IO_PROTOCOL | 在任何BLOCK_IO或BLOCK_IO_EX接口上分层的协议接口。 |
EFI_BLOCK_IO_PROTOCOL | 支持块I/O风格访问的设备的协议接口。 |
EFI_BLOCK_IO2_PROTOCOL | 支持块I/O风格访问的设备的协议接口。这个接口能够实现非阻塞事务。 |
EFI_UNICODE_COLLATION_PROTOCOL | 用于字符串比较操作的协议接口。 |
EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL | 协议接口抽象内存,I/O, PCI配置和DMA访问PCI根桥控制器。 |
EFI_PCI_IO_PROTOCOL | 协议接口抽象内存、I/O、PCI配置和对PCI总线上的PCI控制器的DMA访问。 |
EFI_USB_IO_PROTOCOL | 协议接口抽象访问USB控制器。 |
EFI_SIMPLE_NETWORK_PROTOCOL | 为支持报文传输的设备提供接口。 |
EFI_PXE_BASE_CODE_PROTOCOL | 支持网络引导的设备的协议接口。 |
EFI_BIS_PROTOCOL | 在加载和调用引导映像之前验证它们的协议接口。 |
EFI_DEBUG_SUPPORT_PROTOCOL | 协议接口用于保存和恢复处理器上下文和钩子处理器异常。 |
EFI_DEBUGPORT_PROTOCOL | 抽象调试主机和调试目标系统之间的字节流连接的协议接口。 |
EFI_DECOMPRESS_PROTOCOL | 协议接口来解压使用EFI压缩算法压缩的图像。 |
EFI_EBC_PROTOCOL | 支持EFI字节码解释器所需的协议接口。 |
EFI_GRAPHICS_OUTPUT_PROTOCOL | 支持图形输出的设备的协议接口。 |
EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL | 允许NVM Express命令下发到NVM Express控制器的协议接口。 |
EFI_EXT_SCSI_PASS_THRU_PROTOCOL | SCSI通道的协议接口,允许将SCSI请求包发送到SCSI设备。 |
EFI_USB2_HC_PROTOCOL | 协议接口抽象访问一个USB主机控制器。 |
EFI_AUTHENTICATION_INFO_PROTOCOL | 提供对与特定设备路径关联的通用身份验证信息的访问 |
EFI_DEVICE_PATH_UTILITIES_PROTOCOL | 帮助创建和操作设备路径。 |
EFI_DEVICE_PATH_TO_TEXT_PROTOCOL | 将设备节点和路径转换为文本。 |
EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL | 将文本转换为设备路径和设备节点。 |
EFI_EDID_DISCOVERED_PROTOCOL | 包含从视频输出设备检索到的EDID信息。 |
EFI_EDID_ACTIVE_PROTOCOL | 包含活动视频输出设备的EDID信息。 |
EFI_EDID_OVERRIDE_PROTOCOL | 由平台制作,允许平台向生产者提供EDID信息的图形输出协议 |
EFI_ISCSI_INITIATOR_NAME_PROTOCOL | 设置和获取iSCSI启动器名称。 |
EFI_TAPE_IO_PROTOCOL | 提供控制和访问磁带机的服务。 |
EFI_MANAGED_NETWORK_PROTOCOL | 用于定位MNP驱动程序支持的通信设备,并创建和销毁可以使用底层通信设备的MNP子协议驱动程序实例。 |
EFI_ARP_SERVICE_BINDING_PROTOCOL | 用于定位ARP驱动支持的通信设备,创建和销毁ARP子协议驱动的实例。 |
EFI_ARP_PROTOCOL | 用于将本地网络协议地址解析为网络硬件地址。 |
EFI_DHCP4_SERVICE_BINDING_PROTOCOL | 用于定位EFI DHCPv4协议驱动程序支持的通信设备,并创建和销毁能够使用底层通信设备的EFI DHCPv4协议子驱动程序实例。 |
EFI_DHCP4_PROTOCOL | 收集EFI IPv4协议驱动的配置信息,提供DHCPv4服务器和PXE引导服务器发现服务。 |
EFI_TCP4_SERVICE_BINDING_PROTOCOL | 用于定位EFI TCPv4Protocol驱动程序,创建和销毁该驱动程序的子程序,以便与其他主机使用TCP协议通信。 |
EFI_TCP4_PROTOCOL | 提供发送和接收数据流的服务。 |
EFI_IP4_SERVICE_BINDING_PROTOCOL | 用于定位EFI IPv4协议驱动支持的通信设备,并创建和销毁能够使用底层通信设备的EFI IPv4协议子协议驱动实例。 |
EFI_IP4_PROTOCOL | 提供基本的网络IPv4报文I/O业务。 |
EFI_IP4_CONFIG_PROTOCOL | EFI IPv4 Config Protocol驱动执行基于平台和策略的EFI IPv4 Protocol驱动配置。 |
EFI_IP4_CONFIG2_PROTOCOL | EFI IPv4 Configuration II Protocol驱动执行基于平台和策略的EFI IPv4 Protocol驱动配置。 |
EFI_UDP4_SERVICE_BINDING_PROTOCOL | 用于定位由EFI UDPv4 Protocol驱动程序支持的通信设备,并创建和销毁可以使用底层通信设备的EFI UDPv4 Protocol子协议驱动程序的实例。 |
EFI_UDP4_PROTOCOL | 提供简单的面向报文的服务,实现UDP报文的收发。 |
EFI_MTFTP4_SERVICE_BINDING_PROTOCOL | 用于定位EFI MTFTPv4协议驱动程序支持的通信设备,并创建和销毁可以使用底层通信设备的EFI MTFTPv4协议子协议驱动程序的实例。 |
EFI_MTFTP4_PROTOCOL | 为客户端单播或组播TFTP操作提供基本服务。 |
EFI_HASH_PROTOCOL | 允许使用一个或多个哈希算法创建任意消息摘要的哈希。 |
EFI_HASH_SERVICE_BINDING_PROTOCOL | 用于定位驱动程序提供的哈希服务支持,并创建和销毁EFI哈希协议的实例,以便多个驱动程序可以使用底层的哈希服务。 |
EFI_SD_MMC_PASS_THRU_PROTOCOL | 协议接口,允许将SD/eMMC命令发送到SD/eMMC控制器。 |
2.5 UEFI驱动型号
UEFI驱动模型旨在简化设备驱动程序的设计和实现,并生成较小的可执行映像。因此,一些复杂性已经转移到总线驱动程序中,并在更大程度上转移到公共固件服务中。
设备驱动程序需要在加载驱动程序的同一个映象句柄上生成驱动程序绑定协议。然后等待系统固件将驱动程序连接到控制器。当这种情况发生时,设备驱动程序负责在控制器的设备句柄上生成一个协议,该协议抽象了控制器支持的I/O操作。总线驱动程序执行完全相同的任务。此外,总线驱动程序还负责发现总线上的任何子控制器,并为发现的每个子控制器创建设备句柄。
一个假设是,一个系统的架构可以被看作是一个或多个处理器连接到一个或多个核心芯片组的集合。核心芯片组负责生产一个或多个I/O总线。UEFI驱动模型没有试图描述处理器或核心芯片组。相反,UEFI驱动模型描述了由核心芯片组产生的一组I/O总线,以及这些I/O总线的任何子总线。这些子节点可以是设备或额外的I/O总线。这可以看作是总线和设备的树,核心芯片组位于树的根。
这个树结构中的叶节点是执行某种类型的I/O的外围设备。这可能包括键盘、显示器、磁盘、网络等。非叶节点是在设备和总线之间或不同总线类型之间移动数据的总线。图2-5显示了一个带有4条总线和6个设备的桌面系统示例。
图2-6是一个更复杂的服务器系统示例。这个想法是为了让UEFI驱动模型更简单和可扩展,这样就可以在预引导环境中描述和管理更复杂的系统,比如下面的系统。这个系统包含6条总线和8个设备。
在任何给定的平台中,固件服务、总线驱动程序和设备驱动程序的组合都可能由各种各样的供应商生产,包括oem、ibv和ihv。这些来自不同供应商的不同组件需要协同工作,为I/O设备生成一种协议,该协议可用于引导符合UEFI的操作系统。因此,为了提高这些组件的互操作性,UEFI驱动模型进行了非常详细的描述。
本节的剩余部分是对UEFI驱动模型的简要概述。它描述了UEFI驱动模型要解决的遗留选项ROM问题,驱动的入口点,主机总线控制器,设备驱动的属性,总线驱动的属性,以及UEFI驱动模型如何容纳热插拔事件。
2.5.1遗留选项ROM问题
遗留选项rom有许多约束和限制,限制了平台设计人员和适配器供应商的创新。在编写时,ISA和PCI适配器都使用遗留选项rom。出于本讨论的目的,只考虑PCI选项rom;遗留ISA选项rom不支持作为UEFI规范的一部分。
下面是遗留选项rom的主要约束和限制。对于每个问题,还列出了UEFI驱动模型设计中的设计考虑。因此,UEFI驱动模型的设计直接解决了解决方案的需求,以克服pc - at风格的遗留选项rom所隐含的限制。
2.5.1.1 32位/16位实模式二进制文件
传统选项rom通常包含用于IA-32处理器的16位实模式代码。这意味着PCI卡上的旧选项ROM不能用于不支持IA-32实模式二进制文件执行的平台。16位实模式只允许驱动直接访问系统内存的低1mib。为了访问1mib以上的资源,驱动程序可以将处理器切换到其他模式而不是实模式,但这需要大量额外的代码,并导致与其他选项rom和系统BIOS的互操作性问题。另外,将处理器切换到可选执行模式的可选rom与安腾处理器不兼容。
UEFI驱动模型设计注意事项:
驱动程序需要完全访问系统组件的平面内存模式。
驱动程序需要用C语言编写,这样它们就可以在处理器架构之间移植。
驱动程序可以被编译成虚拟机可执行文件,允许单个二进制驱动程序在使用不同处理器架构的机器上工作。
2.5.1.2使用Option rom的固定资源
由于遗留选项ROM只能直接寻址系统内存的较低的1mib,这意味着遗留选项ROM的代码必须存在于1mib以下。在PC-AT平台中,0x00000-0x9FFFF的内存是系统内存。0xA0000-0xBFFFF的内存是VGA内存,0xF0000-0xFFFFF的内存预留给系统BIOS。此外,由于多年来系统BIOS变得越来越复杂,许多平台也使用0xE0000-0xEFFFF作为系统BIOS。这为遗留选项rom留下了从0xC0000-0xDFFFF的128kib内存。这限制了在BIOS POST期间可以运行的遗留选项rom的数量。
另外,遗留选项rom分配系统内存也不容易。他们的选择是从扩展BIOS数据区(EBDA)分配内存,通过Post memory Manager (PMM)分配内存,或者基于启发式搜索空闲内存。其中,只有EBDA是标准的,其他的在适配器之间或BIOS供应商之间使用不一致,这增加了复杂性并可能产生冲突。
UEFI驱动模型设计注意事项:
•驱动程序需要完全访问系统组件的平面内存模式。
•驱动程序需要能够被重新定位,以便它们可以加载在内存中的任何地方(PE/COFF Images)
•驱动程序应该通过引导服务分配内存。这些都是经过良好指定的接口,可以保证在各种平台实现中按预期的方式运行。
2.5.1.3选项rom与设备匹配
尚不清楚哪个控制器可能由一个特定的遗留选项ROM管理。一些遗留选项ROM搜索整个系统来管理控制器。这可能是一个漫长的过程,取决于平台的大小和复杂性。此外,由于BIOS设计的限制,必须执行所有遗留选项rom,并且在引导操作系统之前必须扫描所有外围设备。这也可能是一个漫长的过程,特别是在必须扫描SCSI总线以寻找SCSI设备的情况下。这意味着遗留选项rom正在制定关于平台如何初始化以及哪个控制器由哪个遗留选项rom管理的策略决策。这使得系统设计人员很难预测遗留选项rom将如何相互交互。这也会导致机载控制器出现问题,因为遗留选项ROM可能会错误地选择管理机载控制器。
UEFI驱动模型设计注意事项:
•驱动器与控制器的匹配必须是确定性的
•通过平台驱动覆盖协议和驱动配置协议,为oem提供更多的控制
•必须能够只启动启动操作系统所需的驱动程序和控制器。
2.5.1.4与PC-AT系统设计的关联
传统选项rom采用类似pc - at的系统架构。其中许多代码直接涉及硬件寄存器。这使得它们在无遗留和无头平台上不兼容。传统的选项rom也可能包含安装程序,这些程序采用类似pc - at的系统架构来与键盘或视频显示器交互。这使得设置应用程序在无遗留和无头平台上不兼容。
UEFI驱动模型设计注意事项:
驱动程序应该使用定义良好的协议与系统硬件、系统输入设备和系统输出设备进行交互。
2.5.1.5规范中的歧义和经验产生的变通方法
许多遗留选项rom和BIOS代码包含变通方法,因为遗留选项rom和系统BIOS之间不兼容。存在这些不兼容性的部分原因是,对于如何编写遗留选项ROM或编写系统BIOS没有明确的规范。
此外,中断链接和引导设备选择在传统选项rom中是非常复杂的。对于操作系统来说,哪个设备将是引导设备并不总是很清楚。
UEFI驱动模型设计注意事项:
•驱动程序和固件是按照这个规范编写的。由于两个组件都有一个明确定义的规范,可以开发遵从性测试来证明驱动程序和系统固件是遵从性的。这应该消除在驱动程序或系统固件中构建工作区的需要(除了那些可能需要解决特定硬件问题的工作)。
•通过平台驱动覆盖协议和驱动配置协议以及其他OEM增值组件,为OEM提供更多的控制,以管理启动设备选择过程。
2.5.2驱动程序初始化
驱动程序映像的文件必须从某种类型的媒体加载。这可能包括ROM、FLASH、硬盘驱动器、软盘驱动器、CD-ROM,甚至是网络连接。一旦找到了驱动程序映像,就可以使用引导服务EFI_BOOT_SERVICES.LoadImage()将它加载到系统内存中。LoadImage()将PE/COFF格式的映象加载到系统内存中。为驱动程序创建一个句柄,并将加载的映象协议实例放置在该句柄上。包含已加载映象协议实例的句柄称为映象句柄。此时,驱动程序尚未启动。它只是坐在内存中等待被启动。图2-7显示了调用LoadImage()后驱动程序的映象句柄的状态。
在使用引导服务loadadimage()加载驱动程序后,必须使用引导服务EFI_BOOT_SERVICES.StartImage()启动驱动程序。这适用于所有类型的UEFI应用程序和UEFI驱动程序,它们可以在兼容UEFI的系统上加载和启动。遵循UEFI驱动模型的驱动程序的入口点必须遵循一些严格的规则。首先,它不允许接触任何硬件。相反,驱动程序只允许将协议实例安装到它自己的映像句柄上。需要遵循UEFI驱动模型的驱动程序将驱动绑定协议的实例安装到自己的映象句柄上。它可以选择安装驱动程序配置协议、驱动程序诊断协议或组件名称协议。此外,如果一个驱动程序希望是不可加载的,它可以选择更新加载的映象协议(见第9节),以提供自己的Unload()函数。最后,驱动程序在启动服务时是否需要执行任何特殊操作
当调用EFI_BOOT_SERVICES.ExitBootServices()时,它可以选择创建一个带有通知函数的事件,该通知函数在调用引导服务ExitBootServices()时被触发。包含驱动绑定协议实例的映象句柄称为驱动映象句柄。图2-8显示了在启动服务StartImage()被调用后,图2-7中的Image Handle的可能配置。
2.5.3主总线控制器
驱动程序不允许接触驱动程序入口点中的任何硬件。因此,驱动程序将被加载并启动,但它们都将等待被告知管理系统中的一个或多个控制器。平台组件,如Boot Manager,负责管理驱动程序到控制器的连接。然而,在建立第一个连接之前,必须有一些初始的控制器集合供驱动程序管理。这个初始的控制器集合被称为主机总线控制器。主机总线控制器提供的I/O抽象是由固件组件产生的,这些固件组件不在UEFI驱动模型范围之内。主机总线控制器的设备句柄和每个控制器的I/O抽象必须由平台上的核心固件产生,或者驱动程序可能不遵循UEFI驱动模型。有关PCI总线的I/O抽象的示例,请参阅PCI根桥I/O协议规范。
平台可以被看作是一组处理器和一组核心芯片组组件,它们可以产生一个或多个主机总线。图2-9展示了一个平台,其中有n个处理器(cpu)和一组核心芯片组组件,生成m个主桥。
在UEFI中,每个主机桥都表示为一个设备句柄,其中包含一个device Path Protocol实例,以及一个协议实例,该协议实例抽象了主机总线可以执行的I/O操作。例如,一个PCI主机总线控制器支持一个或多个由PCI根桥I/O协议抽象的PCI根桥。PCI根桥的设备句柄示例如图2-10所示。
PCI总线驱动程序可以连接到这个PCI根桥,并为系统中的每个PCI设备创建子句柄。PCI设备驱动程序应该连接到这些子句柄,并生成可用于引导符合UEFI的操作系统的I/ O抽象。以下部分描述了在UEFI驱动模型中可以实现的不同类型的驱动程序。UEFI驱动模型非常灵活,所以这里不会讨论所有可能的驱动类型。相反,我们将介绍主要的驱动类型,它们可以作为设计和实现其他驱动类型的起点。
2.5.4设备驱动程序
设备驱动程序不允许创建任何新的设备句柄。相反,它在现有的设备句柄上安装额外的协议接口。最常见的设备驱动程序类型是将I/O抽象附加到总线驱动程序创建的设备句柄上。这种I/O抽象可以用于引导符合UEFI的操作系统。一些示例I/O抽象包括简单文本输出、简单输入、块I/O和简单网络协议。如图2-11所示为连接设备驱动前后的设备句柄。在本例中,设备句柄是XYZ总线的子句柄,因此它包含XYZ总线支持的I/O服务的XYZ I/O协议。它还包含由XYZ总线驱动程序放置在那里的设备路径协议。不是所有的设备句柄都需要设备路径协议。它只需要在系统中表示物理设备的设备句柄。虚拟设备句柄将不包含设备路径协议。
连接到图2-11中的设备句柄的设备驱动必须已经在自己的映象句柄上安装了驱动绑定协议。驱动绑定协议(参见11.1节)包含三个函数Supported(), Start()和Stop()。Supported()函数测试驱动程序是否支持给定的控制器。在这个例子中,驱动程序将检查设备句柄是否支持设备路径协议和XYZ I/O协议。如果一个驱动程序的Supported()函数通过,那么该驱动程序可以通过调用该驱动程序的Start()函数连接到控制器。Start()函数实际上是向设备句柄添加额外的I/O协议。在本例中,Block I/
O正在安装协议。为了提供对称性,驱动绑定协议也有一个Stop()函数,该函数强制驱动停止管理设备句柄。这将导致设备驱动程序卸载在Start()中安装的任何协议接口。
EFI驱动绑定协议的Supported()、Start()和Stop()函数需要使用引导服务EFI_BOOT_SERVICES.OpenProtocol()来获取协议接口,使用引导服务EFI_BOOT_SERVICES.CloseProtocol()来释放协议接口。OpenProtocol()和CloseProtocol()更新由系统固件维护的句柄数据库,以跟踪哪些驱动程序正在使用协议接口。句柄数据库中的信息可用于检索有关驱动程序和控制器的信息。新的引导服务EFI_BOOT_SERVICES.OpenProtocolInformation()可用于获取当前正在使用特定协议接口的组件列表。
2.5.5总线驱动
从UEFI驱动模型的角度来看,总线驱动和设备驱动几乎是相同的。唯一的区别是总线驱动程序为总线驱动程序在其总线上发现的子控制器创建新的设备句柄。因此,总线驱动程序比设备驱动程序稍微复杂一些,但这反过来简化了设备驱动程序的设计和实现。公共汽车司机主要有两种类型。第一个函数在第一次调用Start()时为所有子控制器创建句柄。另一种类型允许通过对Start()的多个调用创建子控制器的句柄。这第二种总线驱动程序在支持快速启动功能时非常有用。它允许创建几个子句柄,甚至一个子句柄。在需要花费很长时间来枚举所有子节点(例如SCSI)的总线上,这可以在引导平台时节省非常大的时间。图2-12显示了调用Start()前后总线控制器的树形结构。进入总线控制器节点的虚线表示到总线控制器的父控制器的链接。如果总线控制器是一个主机总线控制器,那么它将没有父控制器。节点A、B、C、D和E表示总线控制器的子控制器。
一个公交车司机,支持在每次调用Start()创建一个孩子可能会选择先创建子C,然后孩子E,然后剩下的孩子,B和d所支持的(),()开始,停止()函数驱动程序绑定协议的足够灵活,允许这种类型的行为。
总线驱动程序必须在创建的每个子句柄上安装协议接口。至少,它必须安装一个协议接口,为子控制器提供总线服务的I/O抽象。如果总线驱动程序创建了一个代表物理设备的子句柄,那么总线驱动程序也必须将设备路径协议实例安装到子句柄上。总线驱动程序可以选择在每个子句柄上安装总线特定驱动程序覆盖协议。当驱动程序连接到子控制器时使用此协议。启动服务EFI_BOOT_SERVICES.ConnectController()使用体系结构定义的优先规则为给定的控制器选择最好的驱动程序集。总线特定驱动程序覆盖协议的优先级高于一般的驱动程序搜索算法,低于平台覆盖的优先级。一个特定于总线的驱动程序选择的例子发生在PCI。PCI总线驱动程序使存储在PCI控制器的选项ROM中的驱动程序具有比存储在平台其他地方的驱动程序更高的优先级。图2-13显示了一个示例子设备句柄,它是由XYZ总线驱动创建的,它支持总线特定的驱动重写机制。
2.5.6平台组件
在UEFI驱动模型下,连接和断开驱动与平台控制器之间的连接是由平台固件控制的。这通常会作为UEFI的一部分实现
引导管理器,但其他实现也是可能的。启动服务
EFI_BOOT_SERVICES.ConnectController()和
平台固件可以使用EFI_BOOT_SERVICES.DisconnectController()来确定哪些控制器启动,哪些控制器不启动。如果平台希望执行系统诊断或安装操作系统,那么它可以选择将驱动程序连接到所有可能的引导设备。如果平台希望引导预安装的操作系统,它可以选择只将驱动程序连接到引导所选操作系统所需的设备。UEFI驱动模型通过启动服务ConnectController()和DisconnectController()支持这两种操作模式。此外,由于负责引导平台的平台组件必须使用控制台设备的设备路径和引导选项,所以UEFI驱动模型中涉及的所有服务和协议都根据设备路径进行了优化。
由于平台固件可能选择只连接生产控制台和访问启动设备所需的设备,所以OS当前的设备驱动程序不能假设已经执行了设备的UEFI驱动程序。系统固件或选项ROM中UEFI驱动的存在并不保证UEFI驱动将被加载、执行或允许管理平台上的任何设备。所有OS自带的设备驱动必须能够处理已被UEFI驱动管理的设备和未被UEFI驱动管理的设备。
平台也可以选择生成名为平台驱动程序覆盖协议的协议。这类似于总线特定驱动程序覆盖协议,但它有更高的优先级。这使得平台固件在决定哪个驱动程序连接到哪个控制器时具有最高的优先级。平台驱动程序覆盖协议附加到系统中的句柄。如果该协议存在于系统中,引导服务ConnectController()将使用该协议。
2.5.7热插拔事件
在过去,系统固件不需要在预引导环境中处理热插拔事件。然而,随着USB等总线的出现,终端用户可以随时添加和删除设备,确保在UEFI驱动模型中能够描述这些类型的总线非常重要。由支持设备热添加和删除的总线驱动程序来提供对此类事件的支持。对于这些类型的公交车,一些平台管理将不得不转移到公交车司机身上。例如,当键盘被热添加到一个平台上的USB总线时,最终用户会期望键盘是活动的。USB总线驱动程序可以检测热添加事件,并为键盘设备创建子句柄。但是,除非调用EFI_BOOT_SERVICES.ConnectController(),否则驱动程序不会连接到控制器,因此键盘不会成为一个活动的输入设备。激活键盘驱动程序需要调用USB总线驱动程序
当热添加事件发生时,使用ConnectController()。此外,当热移除事件发生时,USB总线驱动程序必须调用EFI_BOOT_SERVICES.DisconnectController()。如果EFI_BOOT_SERVICES.DisconnectController()返回一个错误,USB总线驱动程序需要从一个计时器事件重试EFI_BOOT_SERVICES.DisconnectController(),直到它成功。
设备驱动程序也受到这些热插拔事件的影响。在USB的情况下,可以在没有任何通知的情况下移除设备。这意味着USB设备驱动程序的Stop()函数将不得不处理为不再存在于系统中的设备关闭驱动程序的问题。因此,任何未完成的I/ O请求将不得不被刷新,而实际上无法接触设备硬件。
通常,添加对热插拔事件的支持大大增加了总线驱动程序和设备驱动程序的复杂性。添加这种支持取决于驱动程序编写人员,因此需要在预引导环境中权衡驱动程序的额外复杂性和大小。
2.5.8 EFI服务绑定
UEFI驱动模型可以很好地映射到硬件设备、硬件总线控制器以及硬件设备之上的软件服务的简单组合。然而,UEFI驱动模型不能很好地映射到复杂的软件服务组合。因此,对于更复杂的软件服务组合,还需要一套补充协议。
图2-14包含三个示例,展示了软件服务相互关联的不同方式。在前两种情况下,每个服务使用一个或多个其他服务,最多有一个其他服务使用所有服务。EFI_DRIVER_BINDING_PROTOCOL可以用来建模案例#1和案例#2,但它不能用来建模案例#3,因为UEFI引导服务OpenProtocol()的行为方式。当与BY_DRIVER开放模式一起使用时,OpenProtocol()允许每个协议最多只有一个消费者。这个特性非常有用,可以防止多个驱动程序试图管理同一个控制器。然而,它使得生成类似情况#3的软件服务集变得困难。
EFI_SERVICE_BINDING_PROTOCOL提供了允许协议拥有多个使用者的机制。EFI_SERVICE_BINDING_PROTOCOL与EFI_DRIVER_BINDING_PROTOCOL。如果一个UEFI驱动生成的协议需要同时对多个消费者可用,那么它将同时生成EFI_DRIVER_BINDING_PROTOCOL和EFI_SERVICE_BINDING_PROTOCOL。这种类型的驱动是一种混合驱动,它将在其驱动入口点生成EFI_DRIVER_BINDING_PROTOCOL。
当驱动程序收到开始管理控制器的请求时,它将生成正在启动的控制器句柄上的EFI_SERVICE_BINDING_PROTOCOL。的EFI_SERVICE_BINDING_PROTOCOL与UEFI规范中定义的其他协议略有不同。它没有与之关联的GUID。相反,这个协议实例结构实际上代表了一组协议。每个软件服务驱动需要
EFI_SERVICE_BINDING_PROTOCOL实例需要为它自己的EFI_SERVICE_BINDING_PROTOCOL类型生成一个新的GUID。这个要求就是为什么本规范中的各种网络协议包含两个guid。一个是该网络协议的EFI_SERVICE_BINDING_PROTOCOL GUID,另一个GUID用于包含由网络驱动程序生成的特定成员服务的协议。这里定义的机制并不局限于网络协议驱动程序。它可以应用于任何一组EFI_DRIVER_BINDING_PROTOCOL不能直接映射的协议,因为这些协议包含一个或多个关系,如图2-14中的案例#3。
无论是EFI_DRIVER_BINDING_PROTOCOL还是EFI_DRIVER_BINDING_PROTOCOL和EFI_SERVICE_BINDING_PROTOCOL可以处理循环依赖。有些方法允许循环引用,但它们要求循环链接在短时间内存在。当使用跨循环链接的协议时,这些方法还要求必须以EXCLUSIVE的开放模式打开协议,以便任何试图通过调用DisconnectController()来解析协议集的尝试都将失败。一旦驱动程序在循环链接中完成了协议,协议就应该关闭。
2.6需求
该文档是一个架构规范。因此,在以允许实现最大灵活性的方式指定体系结构时要小心谨慎。然而,为了确保操作系统加载器和其他设计用于与UEFI引导服务一起运行的代码能够依赖于一致的环境,必须实现该规范的某些元素。
为了描述这些需求,规范被分为必需元素和可选元素。通常,可选元素完全定义在匹配元素名的部分中。然而,对于必需的元素,在少数情况下,定义可能不是完全自包含在以特定元素命名的部分中。在实现所需元素时,应该注意涵盖本规范中定义的与特定元素相关的所有语义。
2.6.1所需元素
配置项如表2-11所示。任何设计以符合此规范的系统都必须提供所有这些元素的完整实现。这意味着必须提供所有必需的服务功能和协议,并且实现必须交付规范中为所有调用和参数组合定义的完整语义。应用程序、驱动程序或操作系统加载程序的实现者被设计为在符合UEFI规范的广泛系统上运行,他们可能会假设所有这样的系统都实现了所有需要的元素。
系统供应商可能选择不实现所需的所有元素,例如,在不支持所需元素所暗示的所有服务和功能的专门系统配置上。然而,由于编写大多数应用程序、驱动程序和操作系统加载器时,都假定实现UEFI规范的系统上存在所有必需的元素;任何这样的代码都可能需要显式定制,以在本规范中所需元素的不完全实现上运行。
Table 2-11 所需的UEFI实现元素
元素 | 描述 |
EFI_SYSTEM_TABLE | 提供访问UEFI Boot Services, UEFI Runtime Services,控制台,固件厂商信息,和系统配置表。 |
EFI_BOOT_SERVICES | 所有功能定义为引导服务。 |
EFI_RUNTIME_SERVICES | 所有函数定义为运行时服务。 |
EFI_LOADED_IMAGE_PROTOCOL | 提供有关图像的信息。 |
EFI_LOADED_IMAGE_DEVICE_PA TH_PROTOCOL | 指定通过EFI引导服务LoadImage()加载PE/COFF映像时使用的设备路径。 |
EFI_DEVICE_PATH_PROTOCOL | 提供设备所在位置信息。 |
EFI_DECOMPRESS_PROTOCOL | 协议接口来解压使用EFI压缩算法压缩的图像。 |
EFI_DEVICE_PATH_UTILITIES_ PROTOCOL | 协议接口创建和操作UEFI设备路径和UEFI设备路径节点。 |
2.6.4 在其他地方发布的本规范的扩展
随着时间的推移,该规范已经扩展到包括对新设备和技术的支持。正如该规范的名称所暗示的那样,其定义中的最初意图是为可扩展的固件接口创建基线,而不需要在该规范的主体中包含扩展。
本规范的读者可能会发现,该规范未处理设备的某个特性或类型。这并不一定意味着在要求符合本规范的实现中没有一致的“标准”方式来支持特性或设备。有时,其他标准组织发布它们自己的扩展可能更合适,这些扩展的设计目的是与这里给出的定义一致使用。例如,与等待规范的修订或由在主题领域具有特定专业知识的小组定义这种支持相比,这可能允许更及时地支持新特性。因此,如果读者想要访问本文档中没有提到的特性或设备,建议他们在创建自己的扩展之前咨询适当的标准小组,以确定是否已经存在适当的扩展出版物。
举例来说,在编写UEFI论坛时,已经知道有许多扩展出版物与该规范兼容并为使用而设计。这些扩展包括:
基于Itanium®架构服务器的开发者接口指南:由DIG64小组发布和托管(参见“基于Itanium®架构服务器的开发者接口指南”标题下的“uefi相关文档链接”(http://uefi.org/ uefi))。本文档是一组技术指南,定义基于Itanium™服务器的硬件、固件和操作系统兼容性;
TCG EFI平台规范:由可信计算组发布和托管(参见“TCG EFI平台规范”标题下的“eufi相关文档链接”(http://uefi.org/uefi))。本文档是关于启动一个EFI平台和在该平台上启动一个操作系统的进程。具体来说,该规范包含将引导事件测量到TPM pcr和将引导事件条目添加到事件日志的要求。
TCG EFI协议规范:由可信计算发布和托管
组(见“uefi相关文件链接”(http://uefi.org/uefi))
标题“TCG EFI协议规范”)。本文档定义了EFI平台上TPM的标准接口。
其他扩展文档可能存在于UEFI论坛视图之外,也可能是自本文档的上一个修订版以来创建的。