PCI & PCIe Option ROM —— 从 BIOS 到 UEFI 的技术演进 | 加载机制 / 结构特点 / 开发

注:本文为 “PCI / PCIe Option ROM” 相关文章合辑。

图片清晰度受引文原图所限。
略作重排,未整理去重。
如有内容异常,请看原文。


PCI & PCIe Expansion Option ROM

pwl999 于 2017-10-11 20:36:50 发布

1. 什么是 Expansion Option ROM?

Expansion ROM 是 PCI/PCIe 设备可选的一个外接 EPROM 芯片,用于存储相应 PCI 设备的初始化代码或系统启动代码(例如 PXE 或 PCI Boot)。在 BIOS 的 POST(Power-on Self Test)阶段,会扫描 PCI 设备是否具有 Expansion ROM,若有,则将其拷贝到 RAM 中执行。在 PCI 规范中称为 Expansion ROM,在 BIOS 术语中称为 Option ROM。

例如,在系统启动阶段,我们可以看到网卡和 LSI SAS 芯片的 Option ROM 执行过程。显卡也有相应的 Option ROM,但在 KVM 上难以捕获。

这里写图片描述

2. PCI 配置空间中关于 Expansion ROM 的定义:

在 PCI/PCIe 设备的配置空间中,Expansion ROM 的定义位于 30h 寄存器:

这里写图片描述

关于“Expansion ROM Base Address”寄存器的具体定义如下:

这里写图片描述

  • Bit 11 - Bit 31 定义 Expansion ROM 映射到内存空间的高位地址。
  • Bit 0 表示是否使能 Expansion ROM,1 表示使能。需要注意的是,Expansion ROM 与其他 PCI BAR(Base Address Register)空间共享地址解码,因此一旦使能 Expansion ROM,就不能对其他 BAR 空间进行操作。
  • Expansion ROM 空间大小的计算方法与其他“Base Address Register”相同:向基址寄存器写入全 1,然后回读,回读为 1 的位置即为空间大小。

软件将“Expansion ROM Base Address”寄存器的基地址配置为相应的内存空间地址并使能 Expansion ROM 后,即可对设备的 Expansion ROM 进行读访问。软件将 Expansion ROM 中包含的可执行代码拷贝到 RAM 中执行,而不关心这些代码的具体功能。

3. Expansion ROM 的组织结构:

Expansion ROM 的组织结构遵循相应规范。一个 Expansion ROM 可能包含多个 ROM Image,支持多种不同类型的 PCI 设备,每种设备也可支持不同架构 CPU 的可执行代码。多个 Image 在一个 Expansion ROM 芯片中的组织如下图所示,其中每份 Image 的起始地址均以 512 字节对齐:

这里写图片描述

一份标准的 Image 由两部分组成:PCI Expansion ROM Header Format 和 PCI Data Structure Format。具体格式定义如下图:

这里写图片描述

4. Expansion ROM 的初始化过程:

BIOS 在 POST 阶段扫描并执行 PCI 设备的 Expansion ROM 的过程大致分为以下几步:

  1. 首先判断 PCI 设备是否实现“Expansion ROM Base Address”寄存器,若实现,则进入下一步;
  2. 若设备实现了 Expansion ROM 的基址寄存器,则配置并使能 Expansion ROM,然后查找 Expansion ROM 是否存在“AA55”标识字符。若存在,则说明设备有真实的 Expansion ROM 芯片;
  3. 若 Expansion ROM 存在,则扫描是否存在适合本设备和本 CPU 架构的 Image 代码;
  4. 若存在适合本环境的 Image 代码,则将相应代码拷贝到 RAM 的合适位置,并跳转到 Header Format 中指定的初始化入口执行;
  5. 最后关闭 Expansion ROM 的使能。

关于 Image 代码的长度,有三个概念:

  • Image Size:包括整个 Image 的长度;
  • Initialization Size:将 Expansion ROM 拷贝到 RAM 中执行时,需要拷贝的长度;
  • Runtime Size:这部分程序可以驻留在内存中,供运行时系统软件和 OS 调用。

为了节省常驻代码对 RAM 的占用,常用的做法是:在初始化功能执行完成后,代码会将自身长度修改为常驻程序所需的最小 Runtime Size。若不需要常驻程序,则直接将 Runtime Size 修改为 0。三者的关系为:Initialization Size ≥ Image Size ≥ Runtime Size。相应的组织架构如下图:

这里写图片描述


谈一谈 PCI Option ROM

一只小菜鸟-BIOS 已于 2024-02-06 17:27:46 修改

在计算机启动过程中,BIOS 会在 POST(Power-on Self Test)阶段扫描 PCI 设备,检查是否存在 expansion ROM。如果存在,则将其拷贝到 RAM 中执行。在 PCI 规范中,这被称为 expansion ROM,而在 BIOS 术语中则称为 Option ROM。

Expansion ROM 基地址

扩展空间桥

扩展空间桥

PCI_EXPANSION_ROM_BASE 为 0x30。

PCI_EXPANSION_ROM_BASE

PCI 设备的枚举与 Option ROM 检测

  1. 在 PCIE 枚举过程中,检测到该 PCIE 设备。
  2. 检查该 PCIE 设备是否包含合法的 Option ROM,即是否符合业界 Option ROM 标准规范。合法的关键字为 0xAA55。如果存在合法的 Option ROM,则执行步骤 3;否则执行步骤 6。
  3. 从步骤 2 确认设备包含合法的 Option ROM 后,进一步分析该 Option ROM。找到关键字“PCIR”处的地址,并读取该地址处有关 Option ROM 类型的字段 type。如果类型字段 type 为 0x03,则表明为 UEFI Option ROM,执行步骤 4;否则执行步骤 6。
  4. 将 UEFI Option ROM 拷贝到内存中。
  5. 运行拷贝到内存中的 UEFI Option ROM。

在 PCI 配置空间中,向 Expansion ROM 基地址写入 0xFFFFFFFE,然后返回基本的 ROM 大小信息,保存在 RomSize 变量中。如果返回的地址不是 0 或者 0xFFFFFFFFE,则认为该 PCI 的 Option ROM 寄存器已配置过,返回的大小即为有效值。

Option ROM 大小检测

PCI 扫描过程与 Option ROM 确定机制

在 PCI 扫描过程中,系统通过以下函数确定 PCI 设备是否支持 Option ROM:

  • InitializePciDevices
  • EnumerateBus
  • QueryPciDevice
  • GetOptRomRequirements

Option ROM 驱动的处理过程如下:

  • InstallPciDevice

Option ROM 驱动处理

Option ROM 驱动处理细节

UEFI Option ROM 结构

UEFI Option ROM 的结构与 Legacy Option ROM 相同,均基于 PCI/PCIE 规范。UEFI Option ROM 利用了之前保留的字节(偏移 0x04 处),用于表明其身份。

UEFI Option ROM 加载过程

Option ROM 的执行文件并非直接在 Flash 上运行,而是会被拷贝到内存中执行。Legacy BIOS 的 Option ROM 通常加载到 0xC0000~0xE0000(即 0xC000 段至 0xE000 段),而 UEFI Option ROM 并无此类约定。

通过分析 ProcessOpRomImage() 的代码,可以了解 Option ROM 的处理过程。该函数的入口参数为 PCI_IO_DEVICE *PciDevice,这是一个指向 PCI 设备的指针,包含了设备的所有属性以及其兄弟设备和父设备的信息。数据结构 PCI_IO_DEVICE 定义在 PciBus.h 中,可结合 PCI 协议进行理解。

函数中使用 do-while 循环对设备进行循环处理,寻找 Option ROM 的 ROM 签名(即 0xAA55)。随后对 ROM 结构进行分析,包括是否为 EFI 镜像、机器类型是否支持等,并创建其设备路径(DevicePath)。

在 UEFI 中,使用 Device Path 描述设备的位置信息,总线、启动项等也常用它来描述。进入 UEFI shell 时,各设备的字符串描述即为 Device Path:

Device Path 示例

Device Path 共有六种类型,详细描述见 UEFI Spec。

Device Path 类型

Device Path 类型细节

Device Path 类型细节

typedef struct {
  UINT32  Signature;    ///< "PCIR"
  UINT16  VendorId;
  UINT16  DeviceId;
  UINT16  Reservd0;
  UINT16  Length;
  UINT8   Revision;
  UINT8   ClassCode[3];
  UINT16  ImageLength;
  UINT16  CodeRevision;
  UINT8   CodeType;
  UINT8   Indicator;
  UINT16  Reserved1;
} PCI_DATA_STRUCTURE;

PCI_DATA_STRUCTURE

函数中创建的 Option ROM device path 类型为 MEDIA_DEVICE_PATH,这是一种可作为启动项设备的 Device Path。创建完成后,调用 LoadImage()StartImage() 执行 Option ROM 代码。

Option ROM 相关函数

  1. GetOpRomInfo(IN OUT PCI_IO_DEVICE *PciIoDevice):获取 Option ROM 的信息,判断 PCI 设备是否支持 Option ROM,并写入相关信息。

    • 返回值为 0xFFFFFFFE,表示 Option ROM 的 RomSize
  2. ContainEfiImage(IN VOID *RomImage, IN UINT64 RomSize):检查 ROM 镜像是否包含 EFI 镜像。

    #define PCI_DATA_STRUCTURE_SIGNATURE SIGNATURE_32('P', 'C', 'I', 'R')
    #define PCI_CODE_TYPE_EFI_IMAGE 0x03    // EFI image
    RomPcir = (PCI_DATA_STRUCTURE *)((UINT8 *)RomHeader + RomHeader->PcirOffset);
    if (RomPcir->Signature != PCI_DATA_STRUCTURE_SIGNATURE) {
      break;
    }
    if (RomPcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
      return TRUE;
    }
    
  3. InitializePciLoadFile2()->LocalLoadFile2:初始化 PCI 加载文件。

    LocalLoadFile2

    LocalLoadFile2 细节

    LocalLoadFile2 细节

    参数 RomBase:ROM 驱动加载的基地址。

  4. LoadOpRomImage(IN PCI_IO_DEVICE *PciDevice, IN UINT64 RomBase):加载 Option ROM 镜像。

    LoadOpRomImage

  5. ProcessOpRomImage(IN PCI_IO_DEVICE *PciDevice):处理 Option ROM 镜像。

    ProcessOpRomImage

Option ROM 镜像需要 512 字节对齐,循环寻找所有镜像。

镜像对齐

镜像对齐细节

镜像对齐细节

FV 拓展:EFI OPTIONROM

FV 拓展

如何生成 UEFI Option ROM

UEFI Option ROM 实际上是 UEFI driver 的一种。EDKII 提供了相应的工具,可将生成文件转换为 Option ROM。目前开发的 Option ROM 主要是 PCI Option ROM,相关内容可参考《EDKII Driver Writer’s Guide for UEFI 2.3.1》。

生成 PCI Option ROM 镜像有两种方法:使用工具 EfiRom 转换,或直接使用 EDKII 的 INF/FDF 文件编译生成。

EfiRom 提供了源代码,允许用户在任何支持 EDKII 的操作系统上编译。源代码位于 \BaseTools\Source\C\EfiRom,在开发用的机器上(Win10),编译后的执行文件位于 \BaseTools\Bin\Win32

ENTRY_POINT = FatEntryPoint
UNLOAD_IMAGE = FatUnload
PCI_VENDOR_ID = 0x1d94
PCI_DEVICE_ID = 0x8086
PCI_CLASS_CODE = 0x03
PCI_COMPRESS = TRUE

使用 Loadpcirom xxx.rom 判断是否为 Option ROM 的 header AA55。

((Pcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE)
EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE = 0x0EF1

EfiRom 示例

EfiRom 示例

UEFI Option ROM 是通过 UEFI Driver 转换而来的。关于如何编写 UEFI driver,此处不展开讨论。EfiRom 会对传入的 efi 文件(UEFI Driver)进行验证,例如检查 ROM 头是否为 0xAA55、PCI 数据结构标识是否为“PCIR”等。任何一项检查未通过,EfiRom 将退出,创建 Option ROM 的过程将终止。

生成命令如下:

EfiRom -f 0x9999 -i 0x8000 -e pcidriver.efi

其中,-f 后指定 Vendor ID,-i 后指定 Device ID,-e 后指定需要转换的文件。更多转换方法,包括如何与 Legacy Option ROM 一起打包转换、如何压缩等,可参考《EDKII Driver Writer’s Guide for UEFI 2.3.1》第 18 章第 7 节。

另一种转换方法是使用 INF/FDF 文件。在执行 build 命令时,会自动调用 efirom 将其转换为指定的 Option ROM。这是常用的方法,编译的同时完成了转换过程。一个典型的 INF 示例如下:

Vendor ID 和 Device ID 可在 Inf 文件中指定,其他包括 PCI 类码、PCI 版本、是否对 Option ROM 进行压缩(PCI_COMPRESS)等也可指定。

无论是采用 EfiRom 工具直接转换,还是使用 Inf 文件,都只能对一个 UEFI Driver 进行处理。如果需要同时管理多个 UEFI Driver,以及生成多种类型的 Option ROM(如 IA32、X64 等),可以使用 FDF 文件进行处理。具体内容可参考上述编程手册。

FDF 示例

FDF 示例

在 EFI shell 下使用 Loadpcirom file 成功加载 Option ROM 文件,但发现 start image 并未执行到 UEFI driver 的 Entry point,而是执行了 DriverBingProtocol。原因可能是 fat.inf 中包含的 Lib 无法使用,导致 Entry point 无法执行。使用 hellowworld.rom 后,成功执行了 Entry point 代码。

UefiMain.dll 文件使用了 /dll/entry:_ModuleEntryPoint.efi 文件遵循 PE32 格式,_ModuleEntryPoint 是该二进制文件的入口。

在 Shell 中执行 UefiMain.efi 时,Shell 首先调用 gBS->LoadImage()UefiMain.efi 文件加载到内存生成 Image 对象,然后调用 gBS->StartImage(Image) 启动该 Image。

LoadImage

StartImage

StartImage 细节

开发的 Option ROM 遵循 UEFI driver module 规范,实际上也是 PCI Driver,因此必须实现 EFI_DRIVER_BINDING_PROTOCOL,并实例化 Supported()Start()Stop() 这三个服务。

Option ROM 的开发与执行

Option ROM 的驱动程序需要满足 PCI 规范,不能是任意的 UEFI Driver。加载 Option ROM 时,需要判断是否为目标 PCIE 设备。

Driver image 需要从某些媒介加载,如 ROM、FLASH、硬盘、软驱、CD-ROM 甚至网络控制器。当发现 driver image 后,会通过 LoadImage() 加载到系统内存中,并创建一个 handle。Handle 上挂载了 Loaded Image Protocol 实例,此时该 handle 称为 Image Handle。此时 driver 尚未启动,需等待 StartImage() 调用。

LoadImage()
  1. UefiMain.efi 文件加载到内存,生成 Image 对象,NewHandle 为其句柄。
  2. 句柄:
    • 特殊的智能指针(当一个应用程序需要引用其他系统管理的内存块或对象时)。
    • Windows 编程的基础。句柄是指使用一个唯一的 4 字节型整数值来标识应用程序中的不同对象和同类中的不同实例。应用程序可以通过句柄访问相应对象的信息,但这种句柄不是指针,应用程序不能通过句柄直接读取文件中的信息。句柄是 Windows 系统用来标识应用程序中建立的或是使用的资源的唯一整数。

使用 gBS->LoadImage() 函数,将加载结果返回给 Status 参数,同时改变 NewHandle 的值。使用 EFI_ERROR() 函数判断是否加载成功。

Status = gBS->LoadImage(
  FALSE,
  *ParentImageHandle,
  (EFI_DEVICE_PATH_PROTOCOL *)DevicePath,
  NULL,
  0,
  &NewHandle
);
StartImage()

gBS->StartImage 中,SetJump/LongJump 为应用程序的执行提供了一种错误处理机制。其执行流程如下:

调用各个 driver image 的入口函数,入口函数必须符合 UEFI Driver Model。驱动程序不能直接接触硬件,只能在其自身的 Image Handle 上安装 protocol 实例,如 Driver Binding Protocol,ComponentName Protocol 可选安装。

StartImage 流程

StartImage 细节

构建 EFI 指令
DEBUG_VS2008x86_IA32_DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE : 4001 /OPT : REF /OPT : ICF = 10 /MAP /ALIGN : 32 /SECTION : .xdata,D /SECTION : .pdata,D /MACHINE : X86 /LTCG /DLL /ENTRY : $(IMAGE_ENTRY_POINT) /SUBSYSTEM : EFI_BOOT_SERVICE_DRIVER /SAFESEH : NO /BASE : 0 /DRIVER /DEBUG

RELEASE_VS2008x86_IA32_DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE : 4001 /IGNORE : 4254 /OPT : REF /OPT : ICF = 10 /MAP /ALIGN : 32 /SECTION : .xdata,D /SECTION : .pdata,D /MACHINE : X86 /LTCG /DLL /ENTRY : $(IMAGE_ENTRY_POINT) /SUBSYSTEM : EFI_BOOT_SERVICE_DRIVER /SAFESEH : NO /BASE : 0 /DRIVER /MERGE : .data = .text /MERGE : .rdata = .text

构建指令示例

连接器在生成 UefiMain.dll 文件时使用了 /dll/entry:_ModuleEntryPoint.efi 文件遵循 PE32 格式,_ModuleEntryPoint 是该二进制文件的入口。

  • img
  • 调用 CoreLoadImage,从 FV 中加载 DXE image 到内存中。这应是代码中定义的 FFS(DXE module),编译生成的 PE/COFF 格式镜像。加载成功后生成 ImageHandle。
  • img
  • Option ROM 内置于设备中。
  • Option ROM 在 UEFI 引导下自动加载。
  • EFI Option ROM 包括作为设备驱动程序的 EFI 镜像。
  • ROM 报头使用 0x0EF1 作为签名。
  • PCI 数据结构包括 DeviceId 和 VendorId。
  • Option ROM 镜像可以包含多个镜像,以支持不同的架构。

PCI Option ROM 加载过程

uefi_artisan 于 2016-06-29 19:39:00 发布

在 PCI 配置空间的 0x30 处有一个寄存器,称为 Expansion ROM Base Address。如下图所示:

Expansion ROM Base Address

Option ROM 就位于此位置。需要注意的是,并非每个设备都带有 Expansion ROM。通常,较高级的扩展卡会包含 Option ROM。从 30h 这个位置开始,定义了 Option ROM 的基本信息,例如起始位置和大小。其布局是固定的:

Option ROM 布局

从图中可以看出,其高 21 位记录了 Expansion ROM 的基地址。此外,PCI SPEC 指出,如果 BIOS 想确定一个设备需要多大的空间,只需向该寄存器写入一个全 0 的值,然后读取返回值即可。

为增加文章的专业性,以下是相关代码示例:

现在打开 PciOptionRomSupport.c 文件,查看第 269 行:

//
// The bit0 is 0 to prevent the enabling of the Rom address decoder
//
AllOnes = 0xfffffffe;
Address = EFI_PCI_ADDRESS(Bus, Device, Function, RomBarIndex);

Status = PciRootBridgeIo->Pci.Write(
  PciRootBridgeIo,
  EfiPciWidthUint32,
  Address,
  1,
  &AllOnes
);
if (EFI_ERROR(Status)) {
  return EFI_NOT_FOUND;
}

//
// Read back
//
Status = PciRootBridgeIo->Pci.Read(
  PciRootBridgeIo,
  EfiPciWidthUint32,
  Address,
  1,
  &AllOnes
);

顺便提一下,PCI bridge 设备也有类似的寄存器,但位于 0x38 位置,如下图所示:

PCI bridge 寄存器

因此,要获取 Option ROM 的相关信息,首先需要读取此类寄存器:

//
// Offset is 0x30 if it is not a PCI-to-PCI bridge
//
RomBarIndex = PCI_EXPANSION_ROM_BASE;

if (IS_PCI_BRIDGE(&PciIoDevice->Pci)) {
  //
  // If it is a PCI-to-PCI bridge, use 0x38
  //
  RomBarIndex = PCI_BRIDGE_ROMBAR;
}

根据不同的设备类型,需要设置 RomBarIndex。显然,这两个值分别是 0x30 和 0x38。

事实上,定义如下:

#define PCI_DEVICE_ROMBAR 0x30
#define PCI_BRIDGE_ROMBAR 0x38

如果读取返回值无效,则表明该设备没有 Option ROM。

//
// Bits [1, 10] are reserved
//
AllOnes &= 0xFFFFF800;
if ((AllOnes == 0) || (AllOnes == 0xFFFFF800)) {
  return EFI_NOT_FOUND;
}

如果一切顺利,最终将得到的值取反加 1,即为 Option ROM 的大小:

PciIoDevice->RomSize = (UINT64)((~AllOnes) + 1);

随着时代的发展,2006 年 1 月 31 日,EFI SPEC 正式发布。EFI SPEC 对 Option ROM 也提出了标准,此时不再称为 ROM,而是改称为 Image。一方面,这提升了其逼格,另一方面也与 UEFI 中其他函数名保持一致,例如 LoadImageStartImage 等。

UEFI SPEC 指出,所有 Option ROM 必须以 55AA 开头。细心的读者如果将 55AA 展开,会发现它是 01010101 10101010,0 和 1 交替排列。因此,55AA 的设计并非随意为之。如下图所示:

55AA 签名

早在上个世纪,著名数学家华罗庚就强调了数形结合的重要性,他说:“数缺形时少知觉,形少数时难入微。”因此,仅凭图示还不足以真正理解其运作机制,接下来通过代码进一步说明:

do {
  if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
    RomHeader = (PCI_EXPANSION_ROM_HEADER *)((UINT8 *)RomHeader + 512);
    continue;
  }

简单跳转后,可以看到 SIGNATURE 的定义,不出所料,它必须是 55AA。

#define PCI_EXPANSION_ROM_HEADER_SIGNATURE 0xaa55

随着剧情发展,此时已经确定某个 PCI 设备上有 Option ROM。接下来运行上述代码:

顺便提一下,编写 Option ROM 有其独特之处,与普通应用程序不同,它不能调用操作系统或标准 C 库提供的接口,仅依赖于内存,尽可能减少对其他模块的依赖。

此外,Option ROM 从不在 Flash 上运行,它必须先被拷贝到 RAM 中,然后才能执行。

//
// Copy Rom image into memory
//
PciDevice->PciRootBridgeIo->Mem.Read(
  PciDevice->PciRootBridgeIo,
  EfiPciWidthUint8,
  RomBar,
  (UINT32)RomImageSize,
  Image
);

将 Option ROM 加载到内存并执行的操作在一个函数中完成,该函数名为 ProcessOpRomImage。接下来逐行分析该函数:

该函数仅接收一个参数,即指向 PCI 设备实例的指针:

PCI_IO_DEVICE 描述了一个 PCI 设备的所有自身属性以及与其他设备(如兄弟设备、父设备)的关系。例如,typeBusNumberDeviceNumberFunctionNumberparent(挂载在哪个设备下)以及 ChildList(如果是桥设备)。

在讲解代码之前,先介绍一个名词:DevicePath。在 EFI 中,使用 DevicePath 描述设备的位置信息,可以是逻辑设备。

DevicePath 可分为 6 类:

  • Hardware Device Path:描述具体硬件属于哪个资源域,资源并非常见概念,例如内存、I/O、MMIO。
  • ACPI Device Path:此类地址空间只能用 ACPI 独有的 AML 语言描述。
  • Messaging Device Path:描述计算机本体之外的资源,例如 SCSI ID 或 IP 地址。
  • Media Device Path:即本文提到的类型。创建的 Option ROM Device Path 类型为 MEDIA_DEVICE_PATH。

根据创建的 DevicePath,加载并执行:


PCI Option ROM 在 UEFI 中的加载流程

china_seaman 于 2022-01-26 09:57:52 发布

一、UEFI Driver 是什么?

参考:

BIOS 知识枝桠——UEFI Driver

Hi, Hubery 于 2021-06-17 15:22:26 发布

UEFI Driver Model

UEFI Driver 主要用于管理设备,在 DXE 阶段被加载,在 BDS 阶段被启用。DXE 负责将当前系统中所有的 Driver(无论 DXE Driver、DXE RunTime Driver,还是 UEFI Driver)全部加载,并执行其入口点(entrypoint)。DXE Driver 不负责加载 Application,Application 是在 BDS 阶段加载的。在 BDS 阶段,需要将所有设备值初始化好,启动加载的 Driver,让相应的 Driver 对设备进行管理。此时可以提供相应的输入输出设备,启动设备。
 

为什么要引入 UEFI Driver?

 
UEFI Driver 的引入更好地实现了模块化。模块化可以理解为这些 UEFI Driver 用于管理设备。例如,如果 Driver 用于管理显卡,那么显卡厂商可以独立编写一个 UEFI Driver,该 Driver 可以运行在所有 UEFI 环境下,而无需根据不同的环境进行调整。
 
因此,UEFI Driver 的模块化可以以二进制形式发布,可以内置于 Option ROM 中,对外的入口非常清晰。这就是为什么说 UEFI Driver 扩展了整个固件的扩展性。以前的固件可能只能运行 Application,现在还可以加载许多 Driver,包括第三方编写的 Device Driver。这些 Driver 可能不需要内置到 Flash 上,可以在 Shell 阶段重新加载或在 BDS 阶段加载。
 
对于跨平台性,同一个 Driver 按照二进制形式发布后,可以在所有遵循 UEFI 规范的平台上运行。这种方式允许平台开发和设备开发分别进行,加快开发进程,最后协同到一起。
 
UEFI Driver 概念
 
上图明确展现了 UEFI Driver 的概念。PEIM 是 PEI 阶段的模块,APP 属于 OS 下的 Driver,而 UEFI Driver 可以泛指 DXE 和 BDS 之间的 Driver,也可以理解为在 PEI 之后、OS 之前的所有 Driver 的泛称。包括:

  • EFI Driver Model Driver(协议相关,无论平台是 Intel 还是其他架构,都需要按照这个标准)。
  • 非该类型的 Non-Driver Model Driver(与平台相关)。

此外,还有以下几种 Driver:

  • Service Driver:如常见的 BootService、RuntimeService 等服务驱动。
  • Initializing Driver:初始化服务、CPU 芯片设备等的初始化驱动(如 FchDxe、PchDxe、CpuDxe)。
  • Root Bridge Drivers:PCIE 的根桥设备等根桥驱动(x86 架构使用的是 PCI Bus,如 PciHostBridge)。
  • EFI 1.02 Drivers:符合 EFI 1.02 规范的非 Non-Driver Model Driver。
  • Bus Drivers:总线驱动,用于驱动总线上的设备(如 PciBus、UsbBus、AtaBus、SMBus 等)。
  • Device Drivers:设备驱动,用于驱动设备(如 SATA、NVME、GOP、Keyboard、Mouse 等)。
  • Hybrid Drivers:既有总线驱动又包括设备驱动(不太常用)。

如何定义 UEFI Driver?

UEFI Driver 与标准的 DXE Driver 有所不同。DXE Driver 主要用于硬件平台的初始化,例如对外设寄存器进行配置,或对 GPIO 进行修改。而 UEFI Driver 主要用于管理 PCI Device,有一个启动识别过程,确认目标是需要管理的设备后,再在设备上安装相关的 protocol 和 service,供上层进一步调用。对于 UEFI Driver 来说:

  1. 管理的是 PCI Device。
  2. 是分层的,有一个总线和架构。

UEFI Protocol:由 GUID 命名的函数和数据结构组成的接口,存储在 Handle 数据库中,是模块化 Driver 之间的通讯工具。Handle 类似于 VOID* 的指针(PEI 阶段的 PPI 没有 Handle 概念,DXE 阶段才出现),指平台系统中的所有内容(如句柄、驱动程序、设备、图像等)。在这个 Handle 上安装一个 Protocol 对接一个 GUID。GUID 是 UEFI 平台中唯一的身份标识,用于在 Handle 数据库中识别。

UEFI Driver 加载流程

UEFI Driver 加载流程
 
在 DXE 阶段加载时,会执行 UEFI 的入口点(entry point)。执行完成后,会进行 Driver 的初始化,安装该 Driver 的 Protocol。如果是 UEFI Driver,则会安装 Driver Binding Protocol 和 Component Name Protocol。完成所有 Driver 初始化后,入口点退出,并将控制权返回给 UEFI Loader。
 
对于 Application 来说,到达这个阶段就结束了,不会留下任何痕迹。但对于 UEFI Driver 来说,还没有结束。它的 Protocol 已经安装完成,这些 Protocol 已经在 Handle 数据库中,整个 UEFI 系统已经开始管理。虽然入口点已经执行完毕,但 Protocol 仍然存在,可以被其他模块使用。因此,Driver 会一直驻留在内存中,直到控制权从 BIOS 通过 ExitBootServices 移交给 OS Loader,才会被全部释放。或者在存在过程中人为卸载某个模块的 Driver。
 
Handle Database
 
Handle Database 用于管理 Protocol。假设某个 UEFI Driver 拥有两个 Protocol,分别有两个不同的名字,提供不同的服务,那么在入口点中会安装 Protocol,安装到 Handle 数据库中。这个 Protocol 可能依赖于 Handle 数据库中已加载的其他 Protocol,并不一定体现从头到尾完整的实现。将 BIOS 启动系统模块化的目的,是通过模块之间的配合完成启动。几乎 DXE 阶段的所有 Driver 都会调用 Protocol。上图表示的是,UEFI Driver 在加载时依赖 Handle 数据库中已加载的 Protocol,随后成功安装后又会被其他 Protocol 调用,最终管理设备。
 

UEFI Driver Binding Protocol

 
对于 UEFI Driver 来说,必须安装 Driver Binding Protocol,这是 UEFI Spec 定义好的 Protocol,包含三个 API:Supported()Start()Stop()

  • Supported():检查 DeviceID 和 Vendor ID,以确定某个设备是否由该 Driver 管理。如果是,则会启动 UEFI 的 Start()Supported() 会多次轮询执行,可能会影响资源占用和启动时间,因此除了检查外没有其他安排。
  • Start():由 Supported() 确定后启动,在硬件的 controller 上安装一些子的 handle,然后在 handle 上安装所提供的服务(Protocol)和 Device Path,供他人使用。
  • Stop():通常情况下不会调用,因为同样会花费时间和空间。但从 UEFI 架构考虑,在某些情况下,设备不再需要时,可以调用 Stop() 来释放 Driver。调用 Stop() 后,会将所有安装的 Protocol 进行 uninstall,该设备将不再被管理,同时分配的资源也会被释放。然而,Driver 以及安装好的 Driver Binding Protocol 仍然存在,下次仍然可以重新管理使用。
     
    Driver Binding Protocol

二、UEFI Driver 加载流程

1.EFI_DRIVER_BINDING_PROTOCOL

该协议由每个遵循 UEFI 驱动程序模型的驱动程序生成,是管理驱动程序和控制器的中心组件。它提供了以下服务:

  • 测试驱动程序是否支持特定控制器。
  • 启动管理控制器的服务。
  • 停止管理控制器的服务。

这些服务适用于总线控制器和设备控制器的驱动程序。具体细节可参考 UEFI Spec。

GUID
#define EFI_DRIVER_BINDING_PROTOCOL_GUID \
{0x18A031AB, 0xB443, 0x4D1A, {0xA5, 0xC0, 0x0C, 0x09, 0x26, 0x1E, 0x9F, 0x71}}
Protocol Interface Structure
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
  EFI_DRIVER_BINDING_PROTOCOL_SUPPORTED Supported;
  EFI_DRIVER_BINDING_PROTOCOL_START Start;
  EFI_DRIVER_BINDING_PROTOCOL_STOP Stop;
  UINT32 Version;
  EFI_HANDLE ImageHandle;
  EFI_HANDLE DriverBindingHandle;
} EFI_DRIVER_BINDING_PROTOCOL;

2. PCI 扫描过程中如何确定 PCI 设备支持 Option ROM?

函数调用关系如下:

PciBusDriverBindingStart->PciEnumerator->PciHostBridgeEnumerator->PciRootBridgeEnumerator->PciScanBus->PciSearchDevice->GetOpRomInfo

/**
  获取 PCI 设备的 Option ROM 信息。

  @param PciIoDevice    输入 PCI 设备实例。
                         输出已更新 Option ROM 大小的 PCI 设备实例。

  @retval EFI_NOT_FOUND PCI 设备没有 Option ROM。
  @retval EFI_SUCCESS   PCI 设备有 Option ROM。

**/
EFI_STATUS
GetOpRomInfo (
  IN OUT PCI_IO_DEVICE *PciIoDevice
  )
{
  UINT8 RomBarIndex;
  UINT32 AllOnes;
  UINT64 Address;
  EFI_STATUS Status;
  UINT8 Bus;
  UINT8 Device;
  UINT8 Function;
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo;

  Bus = PciIoDevice->BusNumber;
  Device = PciIoDevice->DeviceNumber;
  Function = PciIoDevice->FunctionNumber;

  PciRootBridgeIo = PciIoDevice->PciRootBridgeIo;

  //
  // 如果不是 PCI-to-PCI 桥,则偏移为 0x30
  //
  RomBarIndex = PCI_EXPANSION_ROM_BASE;

  if (IS_PCI_BRIDGE(&PciIoDevice->Pci)) {
    //
    // 如果是 PCI-to-PCI 桥,则偏移为 0x38
    //
    RomBarIndex = PCI_BRIDGE_ROMBAR;
  }

  //
  // 将位 0 设置为 0,以防止启用 ROM 地址解码器
  //
  AllOnes = 0xfffffffe;
  Address = EFI_PCI_ADDRESS(Bus, Device, Function, RomBarIndex);

  Status = PciRootBridgeIo->Pci.Write (
    PciRootBridgeIo,
    EfiPciWidthUint32,
    Address,
    1,
    &AllOnes
    );
  if (EFI_ERROR(Status)) {
    return EFI_NOT_FOUND;
  }

  //
  // 读取返回值
  //
  Status = PciRootBridgeIo->Pci.Read (
    PciRootBridgeIo,
    EfiPciWidthUint32,
    Address,
    1,
    &AllOnes
    );
  if (EFI_ERROR(Status)) {
    return EFI_NOT_FOUND;
  }

  //
  // 保留位 [1, 10]
  //
  AllOnes &= 0xFFFFF800;
  if ((AllOnes == 0) || (AllOnes == 0xFFFFF800)) {
    return EFI_NOT_FOUND;
  }

  PciIoDevice->RomSize = (UINT64)((~AllOnes) + 1);
  return EFI_SUCCESS;
}

GetOpRomInfo 中,仅探测设备是否有 ROM,如果有,则读取 ROM 大小并存储到 PciIoDevice 中。此时已探测到该 PCI 设备包含的 ROM 大小。

随后,在函数 PciHostBridgeResourceAllocator 中调用 ProcessOptionRom 对 Option ROM 进行处理,函数调用关系如下:

PciBusDriverBindingStart->PciEnumerator->PciHostBridgeResourceAllocator->ProcessOptionRom

/**
  该例程用于处理特定根桥下所有 PCI 设备的 Option ROM。

  @param Bridge     父级根桥。
  @param RomBase    从 ROM 驱动程序加载的基地址。
  @param MaxLength  最大 ROM 大小。

**/
VOID
ProcessOptionRom (
  IN PCI_IO_DEVICE *Bridge,
  IN UINT64 RomBase,
  IN UINT64 MaxLength
  )
{
  LIST_ENTRY *CurrentLink;
  PCI_IO_DEVICE *Temp;

  //
  // 遍历桥设备以访问所有设备
  //
  CurrentLink = Bridge->ChildList.ForwardLink;
  while (CurrentLink != NULL && CurrentLink != &Bridge->ChildList) {
    Temp = PCI_IO_DEVICE_FROM_LINK(CurrentLink);
    if (!IsListEmpty(&Temp->ChildList)) {
      //
      // 继续处理此桥下的 Option ROM
      //
      ProcessOptionRom(Temp, RomBase, MaxLength);
    }

    if (Temp->RomSize != 0 && Temp->RomSize <= MaxLength) {
      //
      // 加载并处理 Option ROM
      //
      LoadOpRomImage(Temp, RomBase);
    }

    CurrentLink = CurrentLink->ForwardLink;
  }
}

从上述代码可知,函数的输入参数包括 PCI 设备结构指针、RomBase 和 ROM 大小。需要注意的是,RomBase 是 PCI MEM 空间的起始地址,是从前面分配空间过程中获取的。根据代码逻辑,如果该设备是桥设备,则递归其下游子设备,并调用 ProcessOptionRom 处理桥下的设备 Option ROM;如果不是桥设备,则调用 LoadOpRomImage,将 ROM 中的数据读取到内存中。

以下是 LoadOpRomImage 的代码实现:

/**
  为指定的 PCI 设备加载 Option ROM 镜像。

  @param PciDevice PCI 设备实例。
  @param RomBase   Option ROM 的基地址。

  @retval EFI_OUT_OF_RESOURCES 内存不足,无法容纳镜像。
  @retval EFI_SUCCESS          成功加载 Option ROM。

**/
EFI_STATUS
LoadOpRomImage (
  IN PCI_IO_DEVICE *PciDevice,
  IN UINT64 RomBase
  )
{
  UINT8 RomBarIndex;
  UINT8 Indicator;
  UINT16 OffsetPcir;
  UINT32 RomBarOffset;
  UINT32 RomBar;
  EFI_STATUS RetStatus;
  BOOLEAN FirstCheck;
  UINT8 *Image;
  PCI_EXPANSION_ROM_HEADER *RomHeader;
  PCI_DATA_STRUCTURE *RomPcir;
  UINT64 RomSize;
  UINT64 RomImageSize;
  UINT32 LegacyImageLength;
  UINT8 *RomInMemory;
  UINT8 CodeType;
  BOOLEAN HasEfiOpRom;

  RomSize = PciDevice->RomSize;

  Indicator = 0;
  RomImageSize = 0;
  RomInMemory = NULL;
  CodeType = 0xFF;

  //
  // 获取 RomBarIndex
  //
  RomBarIndex = PCI_EXPANSION_ROM_BASE;
  if (IS_PCI_BRIDGE(&(PciDevice->Pci))) {
    //
    // 如果是 PCI-to-PCI 桥
    //
    RomBarIndex = PCI_BRIDGE_ROMBAR;
  }

  //
  // 为 RomHeader 和 PCIR 分配内存
  //
  RomHeader = AllocatePool(sizeof(PCI_EXPANSION_ROM_HEADER));
  if (RomHeader == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  RomPcir = AllocatePool(sizeof(PCI_DATA_STRUCTURE));
  if (RomPcir == NULL) {
    FreePool(RomHeader);
    return EFI_OUT_OF_RESOURCES;
  }

  RomBar = (UINT32)RomBase;

  //
  // 启用 RomBar
  //
  RomDecode(PciDevice, RomBarIndex, RomBar, TRUE);

  RomBarOffset = RomBar;
  RetStatus = EFI_NOT_FOUND;
  FirstCheck = TRUE;
  LegacyImageLength = 0;
  HasEfiOpRom = FALSE;

  do {
    PciDevice->PciRootBridgeIo->Mem.Read(
      PciDevice->PciRootBridgeIo,
      EfiPciWidthUint8,
      RomBarOffset,
      sizeof(PCI_EXPANSION_ROM_HEADER),
      (UINT8 *)RomHeader
      );

    if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
      RomBarOffset = RomBarOffset + 512;
      if (FirstCheck) {
        break;
      } else {
        RomImageSize = RomImageSize + 512;
        continue;
      }
    }

    FirstCheck = FALSE;
    OffsetPcir = RomHeader->PcirOffset;
    //
    // 如果 PCI 数据结构的指针无效,则无法定位更多镜像。
    // PCI 数据结构必须是 DWORD 对齐的。
    //
    if (OffsetPcir == 0 || (OffsetPcir & 3) != 0 || RomImageSize + OffsetPcir + sizeof(PCI_DATA_STRUCTURE) > RomSize) {
      break;
    }
    PciDevice->PciRootBridgeIo->Mem.Read(
      PciDevice->PciRootBridgeIo,
      EfiPciWidthUint8,
      RomBarOffset + OffsetPcir,
      sizeof(PCI_DATA_STRUCTURE),
      (UINT8 *)RomPcir
      );
    //
    // 如果 PCI 数据结构中没有有效的签名,则无法定位更多镜像。
    //
    if (RomPcir->Signature != PCI_DATA_STRUCTURE_SIGNATURE) {
      break;
    }
    if (RomImageSize + RomPcir->ImageLength * 512 > RomSize) {
      break;
    }
    if (RomPcir->CodeType == PCI_CODE_TYPE_PCAT_IMAGE) {
      CodeType = PCI_CODE_TYPE_PCAT_IMAGE;
      LegacyImageLength = ((UINT32)((EFI_LEGACY_EXPANSION_ROM_HEADER *)RomHeader)->Size512) * 512;
    } else if (RomPcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
      HasEfiOpRom = TRUE;
    }
    Indicator = RomPcir->Indicator;
    RomImageSize = RomImageSize + RomPcir->ImageLength * 512;
    RomBarOffset = RomBarOffset + RomPcir->ImageLength * 512;
  } while (((Indicator & 0x80) == 0x00) && ((RomBarOffset - RomBar) < RomSize));

  //
  // 某些 Legacy 卡未报告正确的 ImageLength,因此使用 Legacy 长度和 PCIR 镜像长度的最大值。
  //
  if (CodeType == PCI_CODE_TYPE_PCAT_IMAGE) {
    RomImageSize = MAX(RomImageSize, LegacyImageLength);
  }

  if (RomImageSize > 0) {
    RetStatus = EFI_SUCCESS;
    Image = AllocatePool((UINT32)RomImageSize);
    if (Image == NULL) {
      RomDecode(PciDevice, RomBarIndex, RomBar, FALSE);
      FreePool(RomHeader);
      FreePool(RomPcir);
      return EFI_OUT_OF_RESOURCES;
    }

    //
    // 将 ROM 镜像拷贝到内存
    //
    PciDevice->PciRootBridgeIo->Mem.Read(
      PciDevice->PciRootBridgeIo,
      EfiPciWidthUint8,
      RomBar,
      (UINT32)RomImageSize,
      Image
      );
    RomInMemory = Image;
  }

  RomDecode(PciDevice, RomBarIndex, RomBar, FALSE);

  PciDevice->HasEfiOpRom = HasEfiOpRom;
  PciDevice->EmbeddedRom = TRUE;
  PciDevice->PciIo.RomSize = RomImageSize;
  PciDevice->PciIo.RomImage = RomInMemory;

  //
  // 对于从 PCI 设备读取的 OpROM:
  //   将 ROM 镜像添加到内部数据库,以便后续 PCI 轻量级枚举使用
  //
  PciRomAddImageMapping(
    NULL,
    PciDevice->PciRootBridgeIo->SegmentNumber,
    PciDevice->BusNumber,
    PciDevice->DeviceNumber,
    PciDevice->FunctionNumber,
    (UINT64)(UINTN)PciDevice->PciIo.RomImage,
    PciDevice->PciIo.RomSize
    );

  //
  // 释放分配的内存
  //
  FreePool(RomHeader);
  FreePool(RomPcir);

  return RetStatus;
}

PciRomAddImageMapping 的作用是将设备信息(包括 Seg 号、Bus 号、Dev 号、Fun 号、ImageHandle、RomImage(加载到内存后的地址)、RomSize(ROM 大小))保存到全局变量 mRomImageTable 中。这是一个全局指针,每添加一个变量,指针位置向后移动。在扫描过程中,所有支持 Option ROM 的 PCI 设备信息都被保存在 mRomImageTable 中。

根据上述代码,首先读取 PCI_EXPANSION_ROM_HEADER 数据,其结构如下:

typedef struct {
  UINT16 Signature;    // 0xaa55
  UINT8 Reserved[0x16];
  UINT16 PcirOffset;
} PCI_EXPANSION_ROM_HEADER;

读取该结构数据的主要目的是获取 PcirOffset 的值。通过该值的偏移,可以获取 PCI 数据 PCI_DATA_STRUCTURE 的内容,其结构定义如下:

typedef struct {
  UINT32 Signature;    // "PCIR"
  UINT16 VendorId;
  UINT16 DeviceId;
  UINT16 Reserved0;
  UINT16 Length;
  UINT8 Revision;
  UINT8 ClassCode[3];
  UINT16 ImageLength;
  UINT16 CodeRevision;
  UINT8 CodeType;
  UINT8 Indicator;
  UINT16 Reserved1;
} PCI_DATA_STRUCTURE;

EFI_PCI_EXPANSION_ROM_HEADER 的定义如下:

typedef struct {
  UINT16 Signature;    // 0xaa55
  UINT16 InitializationSize;
  UINT32 EfiSignature; // 0x0EF1
  UINT16 EfiSubsystem;
  UINT16 EfiMachineType;
  UINT16 CompressionType;
  UINT8 Reserved[8];
  UINT16 EfiImageHeaderOffset;
  UINT16 PcirOffset;
} EFI_PCI_EXPANSION_ROM_HEADER;

如果校验未失败,则调用 PciRomAddImageMapping 函数将其添加到数据库中。以下是 PciRomAddImageMapping 的代码:

VOID
PciRomAddImageMapping (
  IN EFI_HANDLE ImageHandle,
  IN UINTN Seg,
  IN UINT8 Bus,
  IN UINT8 Dev,
  IN UINT8 Func,
  IN VOID *RomImage,
  IN UINT64 RomSize
  )
{
  UINTN Index;
  PCI_ROM_IMAGE *NewTable;

  for (Index = 0; Index < mNumberOfPciRomImages; Index++) {
    if (mRomImageTable[Index].Seg == Seg &&
        mRomImageTable[Index].Bus == Bus &&
        mRomImageTable[Index].Dev == Dev &&
        mRomImageTable[Index].Func == Func) {
      //
      // 预期 RomImage 和 RomSize 已记录,后续将传递它们。
      //
      ASSERT((mRomImageTable[Index].RomImage == NULL) || (RomImage == mRomImageTable[Index].RomImage));
      ASSERT((mRomImageTable[Index].RomSize == 0) || (RomSize == mRomImageTable[Index].RomSize));
      break;
    }
  }

  if (Index == mNumberOfPciRomImages) {
    //
    // ROM 镜像表缓冲区需要扩容。
    //
    if (mNumberOfPciRomImages == mMaxNumberOfPciRomImages) {
      NewTable = ReallocatePool(
        mMaxNumberOfPciRomImages * sizeof(PCI_ROM_IMAGE),
        (mMaxNumberOfPciRomImages + 0x20) * sizeof(PCI_ROM_IMAGE),
        mRomImageTable
        );
      if (NewTable == NULL) {
        return;
      }
      mRomImageTable = NewTable;
      mMaxNumberOfPciRomImages += 0x20;
    }
    //
    // 记录新的 PCI 设备
    //
    mRomImageTable[Index].Seg = Seg;
    mRomImageTable[Index].Bus = Bus;
    mRomImageTable[Index].Dev = Dev;
    mRomImageTable[Index].Func = Func;
    mNumberOfPciRomImages++;
  }

  mRomImageTable[Index].ImageHandle = ImageHandle;
  mRomImageTable[Index].RomImage = RomImage;
  mRomImageTable[Index].RomSize = RomSize;
}

3. Option ROM 驱动如何被处理?

函数调用关系如下:

PciBusDriverBindingStart->PciEnumerator->PciEnumeratorLight

/**
  处理指定父 PCI 设备的所有子设备的 Option ROM。
  仅在首次完整处理 Option ROM 之后使用。

  @param PciIoDevice PCI 设备实例。

**/
VOID
ProcessOptionRomLight (
  IN PCI_IO_DEVICE *PciIoDevice
  )
{
  PCI_IO_DEVICE *Temp;
  LIST_ENTRY *CurrentLink;

  //
  // 对于根桥、PCI-to-PCI 桥、PCI-to-CardBus 桥,递归遍历所有子设备
  //
  CurrentLink = PciIoDevice->ChildList.ForwardLink;
  while (CurrentLink != NULL && CurrentLink != &PciIoDevice->ChildList) {
    Temp = PCI_IO_DEVICE_FROM_LINK(CurrentLink);

    if (!IsListEmpty(&Temp->ChildList)) {
      ProcessOptionRomLight(Temp);
    }

    PciRomGetImageMapping(Temp);

    //
    // Option ROM 已在第一轮中处理
    //
    Temp->AllOpRomProcessed = TRUE;

    CurrentLink = CurrentLink->ForwardLink;
  }
}

根据上述代码,枚举每个 PCIe 设备。如果是桥设备,则递归调用 ProcessOptionRomLight 函数;如果不是桥设备,则调用 PciRomGetImageMapping 函数。以下是 PciRomGetImageMapping 的代码:

/**
  获取 PCI 设备的 Option ROM 驱动映射。

  @param PciIoDevice 设备实例。

  @retval TRUE   找到镜像映射。
  @retval FALSE  未找到镜像映射。

**/
BOOLEAN
PciRomGetImageMapping (
  IN PCI_IO_DEVICE *PciIoDevice
  )
{
  EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL *PciRootBridgeIo;
  UINTN Index;
  BOOLEAN Found;

  PciRootBridgeIo = PciIoDevice->PciRootBridgeIo;
  Found = FALSE;

  for (Index = 0; Index < mNumberOfPciRomImages; Index++) {
    if (mRomImageTable[Index].Seg == PciRootBridgeIo->SegmentNumber &&
        mRomImageTable[Index].Bus == PciIoDevice->BusNumber &&
        mRomImageTable[Index].Dev == PciIoDevice->DeviceNumber &&
        mRomImageTable[Index].Func == PciIoDevice->FunctionNumber) {
      Found = TRUE;

      if (mRomImageTable[Index].ImageHandle != NULL) {
        AddDriver(PciIoDevice, mRomImageTable[Index].ImageHandle);
      } else {
        PciIoDevice->PciIo.RomImage = (VOID *)(UINTN)mRomImageTable[Index].RomAddress;
        PciIoDevice->PciIo.RomSize = (UINTN)mRomImageTable[Index].RomLength;
      }
    }
  }

  return Found;
}

根据上述代码,如果传入设备的信息与之前枚举设备时添加到 mRomImageTable 中的设备信息匹配,则调用 AddDriver,使用该设备的 ImageHandlePciIoDevice 信息创建驱动程序,并将其添加到 PCI 设备的 OptionRomDriverList 中。以下是 AddDriver 的代码:

/**
  添加一个覆盖驱动程序镜像。

  @param PciIoDevice          PCI IO 设备实例。
  @param DriverImageHandle    新添加的驱动程序镜像。

  @retval EFI_SUCCESS          成功添加驱动程序。
  @retval EFI_OUT_OF_RESOURCES 内存不足,无法为新驱动程序实例分配内存。
  @retval other                在定位 gEfiLoadedImageProtocolGuid 时发生错误。

**/
EFI_STATUS
AddDriver (
  IN PCI_IO_DEVICE *PciIoDevice,
  IN EFI_HANDLE DriverImageHandle
  )
{
  EFI_STATUS Status;
  EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
  PE_COFF_LOADER_IMAGE_CONTEXT ImageContext;
  PCI_DRIVER_OVERRIDE_LIST *Node;

  Status = gBS->HandleProtocol(DriverImageHandle, &gEfiLoadedImageProtocolGuid, (VOID **)&LoadedImage);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  Node = AllocatePool(sizeof(PCI_DRIVER_OVERRIDE_LIST));
  if (Node == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Node->Signature = DRIVER_OVERRIDE_SIGNATURE;
  Node->DriverImageHandle = DriverImageHandle;

  InsertTailList(&PciIoDevice->OptionRomDriverList, &(Node->Link));

  PciIoDevice->BusOverride = TRUE;

  ImageContext.Handle = LoadedImage->ImageBase;
  ImageContext.ImageRead = PeCoffLoaderImageReadFromMemory;

  //
  // 获取镜像信息
  //
  PeCoffLoaderGetImageInfo(&ImageContext);

  return EFI_SUCCESS;
}

AddDriver 的主要作用是为该设备驱动程序创建 Device Path 路径,并将驱动程序及其节点添加到 OptionRomDriverList 链表中。驱动程序节点的类型为 PCI_DRIVER_OVERRIDE_LIST,并更新 PciIoDevice->BusOverride = TRUE。后续将使用该标志。

4. Option ROM 驱动如何被加载及执行

函数调用关系如下:

PciBusDriverBindingStart->StartPciDevices->StartPciDevicesOnBridge->RegisterPciDevice->ContainEfiImage

以下主要介绍 ContainEfiImage 函数,该函数用于判断 Option ROM 是否包含 UEFI 镜像。代码如下:

/**
  检查 ROM 镜像是否包含 EFI 镜像。

  @param RomImage ROM 镜像的地址。
  @param RomSize  ROM 镜像的大小。

  @retval TRUE  ROM 包含 EFI 镜像。
  @retval FALSE ROM 不包含 EFI 镜像。

**/
BOOLEAN
ContainEfiImage (
  IN VOID *RomImage,
  IN UINT64 RomSize
  )
{
  PCI_EXPANSION_ROM_HEADER *RomHeader;
  PCI_DATA_STRUCTURE *RomPcir;
  BOOLEAN FirstCheck;

  FirstCheck = TRUE;
  RomHeader = RomImage;

  while ((UINT8 *)RomHeader < (UINT8 *)RomImage + RomSize) {
    if (RomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
      if (FirstCheck) {
        return FALSE;
      } else {
        RomHeader = (PCI_EXPANSION_ROM_HEADER *)((UINT8 *)RomHeader + 512);
        continue;
      }
    }

    FirstCheck = FALSE;
    RomPcir = (PCI_DATA_STRUCTURE *)((UINT8 *)RomHeader + RomHeader->PcirOffset);

    if (RomPcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) {
      return TRUE;
    }

    RomHeader = (PCI_EXPANSION_ROM_HEADER *)((UINT8 *)RomHeader + RomPcir->Length * 512);
  }

  return FALSE;
}

根据上述代码,如果函数返回 TRUE,则表示 Option ROM 包含一个有效的 UEFI 镜像,可以被加载和执行。如果返回 TRUE,则在 RegisterPciDevice 函数中安装 gEfiLoadFile2ProtocolGuid 协议,等待后续真正加载镜像时调用注册的接口 PciIoDevice->LoadFile2。代码如下:

if (HasEfiImage) {
  Status = gBS->InstallMultipleProtocolInterfaces(
    &PciIoDevice->Handle,
    &gEfiLoadFile2ProtocolGuid,
    &PciIoDevice->LoadFile2,
    NULL
    );
  if (EFI_ERROR(Status)) {
    gBS->UninstallMultipleProtocolInterfaces(
      &PciIoDevice->Handle,
      &gEfiDevicePathProtocolGuid,
      PciIoDevice->DevicePath,
      &gEfiPciIoProtocolGuid,
      &PciIoDevice->PciIo,
      NULL
      );
    return Status;
  }
}

以下是 PciIoDevice->LoadFile2 函数的代码:

/**
  由驱动程序加载指定文件。

  @param This        调用上下文的指针。
  @param FilePath    要加载的文件的设备特定路径。
  @param BootPolicy  始终为 FALSE。
  @param BufferSize  输入时为缓冲区大小(以字节为单位)。如果返回值为 EFI_SUCCESS,则为传输到缓冲区的数据量;如果返回值为 EFI_BUFFER_TOO_SMALL,则为读取请求文件所需的缓冲区大小。
  @param Buffer      要传输文件的内存缓冲区。如果 Buffer 为 NULL,则返回请求文件的大小。

  @retval EFI_SUCCESS           文件已加载。
  @retval EFI_UNSUPPORTED       BootPolicy 为 TRUE。
  @retval EFI_INVALID_PARAMETER FilePath 不是有效的设备路径,或 BufferSize 为 NULL。
  @retval EFI_NOT_FOUND         未在 PCI 设备上找到 PCI Option ROM。
  @retval EFI_DEVICE_ERROR      解压 PCI Option ROM 镜像失败。
  @retval EFI_BUFFER_TOO_SMALL  缓冲区大小不足以读取当前目录条目。已更新 BufferSize,以完成请求所需大小。

**/
EFI_STATUS
EFIAPI
LoadFile2 (
  IN EFI_LOAD_FILE2_PROTOCOL *This,
  IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
  IN BOOLEAN BootPolicy,
  IN OUT UINTN *BufferSize,
  IN VOID *Buffer OPTIONAL
  )
{
  PCI_IO_DEVICE *PciIoDevice;

  if (BootPolicy) {
    return EFI_UNSUPPORTED;
  }
  PciIoDevice = PCI_IO_DEVICE_FROM_LOAD_FILE2_THIS(This);

  return LocalLoadFile2(
    PciIoDevice,
    FilePath,
    BufferSize,
    Buffer
    );
}

以下是 LocalLoadFile2 的代码:

/**
  从 Option ROM 加载 EFI 镜像。

  @param PciIoDevice   PCI IO 设备实例。
  @param FilePath      EFI 镜像的文件路径。
  @param BufferSize    输入时为缓冲区大小(以字节为单位)。如果返回值为 EFI_SUCCESS,则为传输到缓冲区的数据量;如果返回值为 EFI_BUFFER_TOO_SMALL,则为读取请求文件所需的缓冲区大小。
  @param Buffer        要传输文件的内存缓冲区。如果 Buffer 为 NULL,则返回请求文件的大小。

  @retval EFI_SUCCESS           文件已加载。
  @retval EFI_INVALID_PARAMETER FilePath 不是有效的设备路径,或 BufferSize 为 NULL。
  @retval EFI_NOT_FOUND         未在 PCI 设备上找到 PCI Option ROM。
  @retval EFI_DEVICE_ERROR      解压 PCI Option ROM 镜像失败。
  @retval EFI_BUFFER_TOO_SMALL  缓冲区大小不足以读取当前目录条目。已更新 BufferSize,以完成请求所需大小。

**/
EFI_STATUS
LocalLoadFile2 (
  IN PCI_IO_DEVICE *PciIoDevice,
  IN EFI_DEVICE_PATH_PROTOCOL *FilePath,
  IN OUT UINTN *BufferSize,
  IN VOID *Buffer OPTIONAL
  )
{
  EFI_STATUS Status;
  MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH *EfiOpRomImageNode;
  EFI_PCI_EXPANSION_ROM_HEADER *EfiRomHeader;
  PCI_DATA_STRUCTURE *Pcir;
  UINT32 ImageSize;
  UINT8 *ImageBuffer;
  UINT32 ImageLength;
  UINT32 DestinationSize;
  UINT32 ScratchSize;
  VOID *Scratch;
  EFI_DECOMPRESS_PROTOCOL *Decompress;

  EfiOpRomImageNode = (MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH *)FilePath;
  if ((EfiOpRomImageNode == NULL) ||
      (DevicePathType(FilePath) != MEDIA_DEVICE_PATH) ||
      (DevicePathSubType(FilePath) != MEDIA_RELATIVE_OFFSET_RANGE_DP) ||
      (DevicePathNodeLength(FilePath) != sizeof(MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH)) ||
      (!IsDevicePathEnd(NextDevicePathNode(FilePath))) ||
      (EfiOpRomImageNode->StartingOffset > EfiOpRomImageNode->EndingOffset) ||
      (EfiOpRomImageNode->EndingOffset >= PciIoDevice->RomSize) ||
      (BufferSize == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  EfiRomHeader = (EFI_PCI_EXPANSION_ROM_HEADER *)(
    (UINT8 *)PciIoDevice->PciIo.RomImage + EfiOpRomImageNode->StartingOffset
    );
  if (EfiRomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
    return EFI_NOT_FOUND;
  }

  Pcir = (PCI_DATA_STRUCTURE *)((UINT8 *)EfiRomHeader + EfiRomHeader->PcirOffset);

  if ((Pcir->CodeType == PCI_CODE_TYPE_EFI_IMAGE) &&
      (EfiRomHeader->EfiSignature == EFI_PCI_EXPANSION_ROM_HEADER_EFISIGNATURE) &&
      ((EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER) ||
       (EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER)) &&
      (EfiRomHeader->CompressionType <= EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED)) {

    ImageSize = (UINT32)EfiRomHeader->InitializationSize * 512;
    ImageBuffer = (UINT8 *)EfiRomHeader + EfiRomHeader->EfiImageHeaderOffset;
    ImageLength = ImageSize - EfiRomHeader->EfiImageHeaderOffset;

    if (EfiRomHeader->CompressionType != EFI_PCI_EXPANSION_ROM_HEADER_COMPRESSED) {
      //
      // 未压缩:直接将 EFI 镜像复制到用户缓冲区
      //
      if (Buffer == NULL || *BufferSize < ImageLength) {
        *BufferSize = ImageLength;
        return EFI_BUFFER_TOO_SMALL;
      }

      *BufferSize = ImageLength;
      CopyMem(Buffer, ImageBuffer, ImageLength);
      return EFI_SUCCESS;

    } else {
      //
      // 已压缩:解压后再复制
      //
      Status = gBS->LocateProtocol(&gEfiDecompressProtocolGuid, NULL, (VOID **)&Decompress);
      if (EFI_ERROR(Status)) {
        return EFI_DEVICE_ERROR;
      }
      Status = Decompress->GetInfo(
        Decompress,
        ImageBuffer,
        ImageLength,
        &DestinationSize,
        &ScratchSize
        );
      if (EFI_ERROR(Status)) {
        return EFI_DEVICE_ERROR;
      }

      if (Buffer == NULL || *BufferSize < DestinationSize) {
        *BufferSize = DestinationSize;
        return EFI_BUFFER_TOO_SMALL;
      }

      *BufferSize = DestinationSize;
      Scratch = AllocatePool(ScratchSize);
      if (Scratch == NULL) {
        return EFI_DEVICE_ERROR;
      }

      Status = Decompress->Decompress(
        Decompress,
        ImageBuffer,
        ImageLength,
        Buffer,
        DestinationSize,
        Scratch,
        ScratchSize
        );
      FreePool(Scratch);

      if (EFI_ERROR(Status)) {
        return EFI_DEVICE_ERROR;
      }
      return EFI_SUCCESS;
    }
  }

  return EFI_NOT_FOUND;
}

LocalLoadFile2 是加载的回调函数,代码细节在此不再详细展开。许多镜像格式的要求都在代码中体现。如果有一项不满足,则加载失败,进而导致执行失败。

接下来回到 RegisterPciDevice 函数。在安装 gEfiLoadFile2ProtocolGuid 后,接下来调用 ProcessOpRomImage 函数,该函数真正执行加载操作。加载成功后,将执行驱动程序的 EntryPoint 函数。以下是详细的代码实现:

/**
  加载并启动 Option ROM 镜像。

  @param PciDevice PCI 设备实例。

  @retval EFI_SUCCESS    成功加载并启动 PCI Option ROM 镜像。
  @retval EFI_NOT_FOUND  无法处理 PCI Option ROM 镜像。

**/
EFI_STATUS
ProcessOpRomImage (
  IN PCI_IO_DEVICE *PciDevice
  )
{
  UINT8 Indicator;
  UINT32 ImageSize;
  VOID *RomBar;
  UINT8 *RomBarOffset;
  EFI_HANDLE ImageHandle;
  EFI_STATUS Status;
  EFI_STATUS RetStatus;
  BOOLEAN FirstCheck;
  EFI_PCI_EXPANSION_ROM_HEADER *EfiRomHeader;
  PCI_DATA_STRUCTURE *Pcir;
  EFI_DEVICE_PATH_PROTOCOL *PciOptionRomImageDevicePath;
  MEDIA_RELATIVE_OFFSET_RANGE_DEVICE_PATH EfiOpRomImageNode;
  VOID *Buffer;
  UINTN BufferSize;
  EFI_LEGACY_BIOS_PROTOCOL *LegacyBios;
  KERNEL_CONFIGURATION KernelConfig;

  Status = GetKernelConfiguration(&KernelConfig);

  if (Status == EFI_SUCCESS) {
    //
    // Legacy 引导模式,跳过加载原生驱动程序。
    //
    if (KernelConfig.BootType == LEGACY_BOOT_TYPE) {
      return EFI_SUCCESS;
    }
    //
    // 在双引导模式下,VGA 设备将使用 Legacy ROM,跳过原生 GOP 驱动程序。
    //
    if (KernelConfig.BootType == DUAL_BOOT_TYPE) {
      if (PciDevice->Pci.Hdr.ClassCode[2] == PCI_CLASS_DISPLAY) {
        Status = gBS->LocateProtocol(&gEfiLegacyBiosProtocolGuid, NULL, (VOID **)&LegacyBios);
        if (!EFI_ERROR(Status)) {

          Indicator = 0;

          //
          // 获取 ROM 镜像的地址
          //
          RomBar = PciDevice->PciIo.RomImage;
          RomBarOffset = (UINT8 *)RomBar;
          RetStatus = EFI_NOT_FOUND;
          FirstCheck = TRUE;

          do {
            EfiRomHeader = (EFI_PCI_EXPANSION_ROM_HEADER *)RomBarOffset;
            if (EfiRomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
              RomBarOffset = RomBarOffset + 512;
              if (FirstCheck) {
                break;
              } else {
                continue;
              }
            }

            FirstCheck = FALSE;
            Pcir = (PCI_DATA_STRUCTURE *)(RomBarOffset + EfiRomHeader->PcirOffset);
            ImageSize = (UINT32)(Pcir->ImageLength * 512);
            Indicator = Pcir->Indicator;
            RomBarOffset = RomBarOffset + ImageSize;
            if (Pcir->CodeType == PCI_CODE_TYPE_PCAT_IMAGE) {
              //
              // 如果 BIOS 支持 Legacy,且 PCI 设备具有 Legacy oprom,则系统将选择 Legacy VGA ROM 进行显示。
              //
              return EFI_SUCCESS;
            }
          } while (((Indicator & 0x80) == 0x00) && ((UINTN)(RomBarOffset - (UINT8 *)RomBar) < PciDevice->RomSize));
        }
      }
    }
  }

  Indicator = 0;

  //
  // 获取 Option ROM 镜像的地址
  //
  RomBar = PciDevice->PciIo.RomImage;
  RomBarOffset = (UINT8 *)RomBar;
  RetStatus = EFI_NOT_FOUND;
  FirstCheck = TRUE;

  do {
    EfiRomHeader = (EFI_PCI_EXPANSION_ROM_HEADER *)RomBarOffset;
    if (EfiRomHeader->Signature != PCI_EXPANSION_ROM_HEADER_SIGNATURE) {
      RomBarOffset += 512;
      if (FirstCheck) {
        break;
      } else {
        continue;
      }
    }

    FirstCheck = FALSE;
    Pcir = (PCI_DATA_STRUCTURE *)(RomBarOffset + EfiRomHeader->PcirOffset);
    ImageSize = (UINT32)(Pcir->ImageLength * 512);
    Indicator = Pcir->Indicator;

    //
    // 跳过非 EFI PCI Option ROM 镜像
    //
    if (Pcir->CodeType != PCI_CODE_TYPE_EFI_IMAGE) {
      goto NextImage;
    }

    //
    // 跳过不支持的机器类型的 EFI PCI Option ROM 镜像
    //
    if (!EFI_IMAGE_MACHINE_TYPE_SUPPORTED(EfiRomHeader->EfiMachineType)) {
      goto NextImage;
    }

    //
    // 忽略 EFI 应用程序的 EFI PCI Option ROM 镜像
    //
    if (EfiRomHeader->EfiSubsystem == EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION) {
      goto NextImage;
    }

    //
    // 创建 PCI Option ROM 镜像设备路径头
    //
    EfiOpRomImageNode.Header.Type = MEDIA_DEVICE_PATH;
    EfiOpRomImageNode.Header.SubType = MEDIA_RELATIVE_OFFSET_RANGE_DP;
    SetDevicePathNodeLength(&EfiOpRomImageNode.Header, sizeof(EfiOpRomImageNode));
    EfiOpRomImageNode.StartingOffset = (UINTN)RomBarOffset - (UINTN)RomBar;
    EfiOpRomImageNode.EndingOffset = (UINTN)RomBarOffset + ImageSize - 1 - (UINTN)RomBar;

    PciOptionRomImageDevicePath = AppendDevicePathNode(PciDevice->DevicePath, &EfiOpRomImageNode.Header);
    ASSERT(PciOptionRomImageDevicePath != NULL);

    //
    // 加载镜像并启动
    //
    BufferSize = 0;
    Buffer = NULL;
    ImageHandle = NULL;

    Status = gBS->LoadImage(
      FALSE,
      gPciBusDriverBinding.DriverBindingHandle,
      PciOptionRomImageDevicePath,
      Buffer,
      BufferSize,
      &ImageHandle
      );

    FreePool(PciOptionRomImageDevicePath);

    if (!EFI_ERROR(Status)) {
      Status = gBS->StartImage(ImageHandle, NULL, NULL);
      if (!EFI_ERROR(Status)) {
        AddDriver(PciDevice, ImageHandle);
        PciRomAddImageMapping(
          ImageHandle,
          PciDevice->PciRootBridgeIo->SegmentNumber,
          PciDevice->BusNumber,
          PciDevice->DeviceNumber,
          PciDevice->FunctionNumber,
          (UINT64)(UINTN)PciDevice->PciIo.RomImage,
          PciDevice->PciIo.RomSize
          );
        RetStatus = EFI_SUCCESS;
      }
    }

NextImage:
    RomBarOffset += ImageSize;

  } while (((Indicator & 0x80) == 0x00) && ((UINTN)(RomBarOffset - (UINT8 *)RomBar) < PciDevice->RomSize));

  return RetStatus;
}

根据上述代码,加载成功后将执行启动操作。需要注意的是,加载函数内部会调用之前注册的函数。执行完启动函数(即运行驱动程序的 EntryPoint)后,将调用 AddDriver

在后续的 Dxe 结尾阶段,在 connectcontroller 函数中会调用 GetDriver 函数。以下是 GetDriver 函数的代码:

/**
  使用总线特定算法为控制器检索驱动程序镜像句柄。

  @param This                  EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL 实例的指针。
  @param DriverImageHandle     输入时为上一次调用 GetDriver() 返回的驱动程序镜像句柄。输出时为下一个驱动程序镜像句柄。如果输入为 NULL,则返回第一个驱动程序镜像句柄。

  @retval EFI_SUCCESS           返回总线特定的覆盖驱动程序。
  @retval EFI_NOT_FOUND         已到达覆盖驱动程序列表的末尾。未返回总线特定的覆盖驱动程序。
  @retval EFI_INVALID_PARAMETER DriverImageHandle 不是之前调用 GetDriver() 返回的句柄。

**/
EFI_STATUS
EFIAPI
GetDriver (
  IN EFI_BUS_SPECIFIC_DRIVER_OVERRIDE_PROTOCOL *This,
  IN OUT EFI_HANDLE *DriverImageHandle
  )
{
  PCI_IO_DEVICE *PciIoDevice;
  LIST_ENTRY *CurrentLink;
  PCI_DRIVER_OVERRIDE_LIST *Node;

  PciIoDevice = PCI_IO_DEVICE_FROM_PCI_DRIVER_OVERRIDE_THIS(This);

  CurrentLink = PciIoDevice->OptionRomDriverList.ForwardLink;

  while (CurrentLink != NULL && CurrentLink != &PciIoDevice->OptionRomDriverList) {

    Node = DRIVER_OVERRIDE_FROM_LINK(CurrentLink);

    if (*DriverImageHandle == NULL) {

      *DriverImageHandle = Node->DriverImageHandle;
      return EFI_SUCCESS;
    }

    if (*DriverImageHandle == Node->DriverImageHandle) {

      if (CurrentLink->ForwardLink == &PciIoDevice->OptionRomDriverList ||
          CurrentLink->ForwardLink == NULL) {
        return EFI_NOT_FOUND;
      }

      //
      // 获取下一个节点
      //
      Node = DRIVER_OVERRIDE_FROM_LINK(CurrentLink->ForwardLink);
      *DriverImageHandle = Node->DriverImageHandle;
      return EFI_SUCCESS;
    }

    CurrentLink = CurrentLink->ForwardLink;
  }

  return EFI_INVALID_PARAMETER;
}

CoreConnectController 中会调用 CoreConnectSingleController 函数,然后在该函数中调用 GetDriver 函数。以下是代码片段:

//
// 在控制器句柄上获取总线特定驱动程序覆盖协议实例
//
Status = CoreHandleProtocol(
  ControllerHandle,
  &gEfiBusSpecificDriverOverrideProtocolGuid,
  (VOID **)&BusSpecificDriverOverride
  );
if (!EFI_ERROR(Status) && (BusSpecificDriverOverride != NULL)) {
  DriverImageHandle = NULL;
  do {
    Status = BusSpecificDriverOverride->GetDriver(
      BusSpecificDriverOverride,
      &DriverImageHandle
      );
    if (!EFI_ERROR(Status)) {
      AddSortedDriverBindingProtocol(
        DriverImageHandle,
        &NumberOfSortedDriverBindingProtocols,
        SortedDriverBindingProtocols,
        DriverBindingHandleCount,
        DriverBindingHandleBuffer,
        TRUE
        );
    }
  } while (!EFI_ERROR(Status));
}

执行完成后,在后续执行驱动程序中的 Support 函数时,将调用该驱动程序的 Support 函数,进而执行驱动程序的 Start 函数。代码逻辑如下:

// 然后添加所有剩余的驱动程序绑定协议
SortIndex = NumberOfSortedDriverBindingProtocols;
for (Index = 0; Index < DriverBindingHandleCount; Index++) {
  AddSortedDriverBindingProtocol(
    DriverBindingHandleBuffer[Index],
    &NumberOfSortedDriverBindingProtocols,
    SortedDriverBindingProtocols,
    DriverBindingHandleCount,
    DriverBindingHandleBuffer,
    FALSE
    );
}

//
// 释放通过 AllocatePool() 分配的所有缓冲区
//
CoreFreePool(DriverBindingHandleBuffer);

//
// 如果自函数开始以来驱动程序绑定协议的数量增加,则返回 EFI_NOT_READY,以便重新启动
//
Status = CoreLocateHandleBuffer(
  ByProtocol,
  &gEfiDriverBindingProtocolGuid,
  NULL,
  &NewDriverBindingHandleCount,
  &NewDriverBindingHandleBuffer
  );
CoreFreePool(NewDriverBindingHandleBuffer);
if (NewDriverBindingHandleCount > DriverBindingHandleCount) {
  //
  // 释放通过 AllocatePool() 分配的所有缓冲区
  //
  CoreFreePool(SortedDriverBindingProtocols);

  return EFI_NOT_READY;
}

//
// 根据 Version 字段对剩余的驱动程序绑定协议进行排序,从高到低。
//
for ( ; SortIndex < NumberOfSortedDriverBindingProtocols; SortIndex++) {
  HighestVersion = SortedDriverBindingProtocols[SortIndex]->Version;
  HighestIndex = SortIndex;
  for (Index = SortIndex + 1; Index < NumberOfSortedDriverBindingProtocols; Index++) {
    if (SortedDriverBindingProtocols[Index]->Version > HighestVersion) {
      HighestVersion = SortedDriverBindingProtocols[Index]->Version;
      HighestIndex = Index;
    }
  }
  if (SortIndex != HighestIndex) {
    DriverBinding = SortedDriverBindingProtocols[SortIndex];
    SortedDriverBindingProtocols[SortIndex] = SortedDriverBindingProtocols[HighestIndex];
    SortedDriverBindingProtocols[HighestIndex] = DriverBinding;
  }
}

//
// 循环,直到无法在 ControllerHandle 上启动更多驱动程序
//
OneStarted = FALSE;
do {

  //
  // 遍历排序后的驱动程序绑定协议实例,查看是否有驱动程序支持 ControllerHandle 指定的控制器。
  //
  DriverBinding = NULL;
  DriverFound = FALSE;
  for (Index = 0; (Index < NumberOfSortedDriverBindingProtocols) && !DriverFound; Index++) {
    if (SortedDriverBindingProtocols[Index] != NULL) {
      DriverBinding = SortedDriverBindingProtocols[Index];
      PERF_START(DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
      Status = DriverBinding->Supported(
        DriverBinding,
        ControllerHandle,
        RemainingDevicePath
        );
      PERF_END(DriverBinding->DriverBindingHandle, "DB:Support:", NULL, 0);
      if (!EFI_ERROR(Status)) {
        SortedDriverBindingProtocols[Index] = NULL;
        DriverFound = TRUE;

        //
        // 找到支持 ControllerHandle 的驱动程序,尝试在 ControllerHandle 上启动该驱动程序。
        //
        PERF_START(DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);
        Status = DriverBinding->Start(
          DriverBinding,
          ControllerHandle,
          RemainingDevicePath
          );
        PERF_END(DriverBinding->DriverBindingHandle, "DB:Start:", NULL, 0);

        if (!EFI_ERROR(Status)) {
          //
          // 驱动程序成功启动在 ControllerHandle 上,设置标志
          //
          OneStarted = TRUE;
        }
      }
    }
  }
} while (DriverFound);

总结

以上内容仅对 PCI Option ROM 的加载和执行过程进行了简单介绍。UEFI 提供了大量工具和协议,用于高效地管理和启动硬件设备。


via:

### 构建任务失败解决方案 当遇到 `Execution failed for task &#39;:app:shrinkReleaseRes&#39;` 错误时,这通常意味着资源压缩过程中出现了问题。此错误可能由多种原因引起,包括但不限于配置不正确、依赖冲突或特定于项目的其他因素。 #### 可能的原因分析 1. **ProGuard 或 R8 配置不当** ProGuard 和 R8 是用于优化和混淆代码以及减少 APK 大小的工具。如果这些工具的配置存在问题,可能会导致资源无法正常处理[^1]。 2. **重复资源** 如果项目中有多个模块定义了相同的资源名称,可能导致冲突并引发该错误。检查是否存在重名的 drawable、string 等资源文件[^2]。 3. **第三方库兼容性** 某些第三方库可能与当前使用的 Gradle 插件版本或其他库存在兼容性问题,从而影响到资源打包过程中的行为[^3]。 4. **Gradle 缓存问题** 有时旧缓存数据会干扰新编译的结果,尝试清理本地仓库和重新同步项目可以帮助排除此类潜在障碍[^4]。 #### 推荐的操作方法 为了有效解决问题,建议按照以下步骤逐一排查: ```bash # 清理项目构建目录 ./gradlew clean # 删除 .gradle 文件夹下的所有内容以清除缓存 rm -rf ~/.gradle/caches/ ``` 调整 `build.gradle` 中的相关设置也是一个重要环节: ```groovy android { ... buildTypes { release { minifyEnabled true // 是否启用代码缩减 shrinkResources true // 是否开启资源压缩 proguardFiles getDefaultProguardFile(&#39;proguard-android-optimize.txt&#39;), &#39;proguard-rules.pro&#39; // 尝试禁用 shrinkResources 来测试是否为资源压缩引起的错误 // shrinkResources false } } } ``` 此外,在 `proguard-rules.pro` 文件内添加必要的保留规则,防止关键类被意外移除: ```text -keep class com.example.yourpackage.** { *; } # 替换为你自己的包路径 -dontwarn androidx.**,com.google.** # 忽略警告信息 ``` 最后,确保所使用的 Android Studio 版本是最新的稳定版,并且已经应用了所有的补丁更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值