3. UEFI 驱动模型基础

UEFI 驱动模型是 UEFI 固件中管理设备与驱动交互的核心架构。与传统的 BIOS 中断向量式驱动不同,UEFI 驱动模型采用对象化、协议化、动态绑定的设计思想,使得驱动可以在系统启动过程中按需加载、绑定和卸载。本章将深入剖析驱动模型的架构、核心协议、入口点实现以及驱动的安装与卸载机制。

3.1 驱动模型架构

UEFI 驱动模型围绕三个核心概念构建:设备(Device)、驱动(Driver)和协议(Protocol)。设备通过 I/O 抽象暴露其能力,驱动通过协议接口与设备通信,而驱动绑定协议(Driver Binding Protocol)则作为驱动与设备之间的“匹配器”。

架构核心思想:驱动不直接操作硬件,而是操作由设备句柄(Handle)上的 I/O 协议抽象出的逻辑设备。驱动通过 Driver Binding Protocol 的 Supported() 方法判断是否支持某设备,通过 Start() 方法绑定并安装驱动协议,通过 Stop() 方法解绑并卸载。
UEFI 驱动模型架构 物理设备 (PCI 设备 / USB 设备) 设备句柄(Handle) + I/O 协议(如 PciIo) UEFI 驱动 (Driver 映像) Driver Binding Protocol Supported / Start / Stop 驱动绑定过程 Supported() → Start() → 安装子协议 子协议安装完成 设备句柄上新增驱动协议 协议匹配

3.2 驱动绑定协议(Driver Binding Protocol)

驱动绑定协议是 UEFI 驱动模型的核心接口,每个 UEFI 驱动必须实现该协议。它定义了驱动与设备之间的匹配、绑定和解绑契约。该协议包含三个关键成员函数和一个版本号。

成员 类型 描述
Supported() EFI_DRIVER_BINDING_SUPPORTED 检查驱动是否支持指定的设备句柄。不修改硬件状态,只做探测。
Start() EFI_DRIVER_BINDING_START 将驱动绑定到设备上,分配资源,安装子协议。
Stop() EFI_DRIVER_BINDING_STOP 将驱动从设备上解绑,释放资源,卸载子协议。
Version UINT32 驱动版本号,用于驱动优先级比较。
ImageHandle EFI_HANDLE 驱动映像自身的句柄。
DriverBindingHandle EFI_HANDLE 驱动绑定协议安装所在的句柄。
设计要点:Supported() 必须是非侵入式的——它只能读取设备的 I/O 协议和硬件 ID,不能修改设备寄存器或分配内存。如果 Supported() 修改了设备状态,可能导致其他驱动无法正确识别该设备。

3.2.1 Supported() 实现模式

Supported() 的典型实现流程如下:

  1. 调用 OpenProtocol() 尝试打开设备句柄上的 I/O 协议(如 gEfiPciIoProtocolGuid)。
  2. 通过 I/O 协议读取设备的 Vendor ID 和 Device ID(或 UUID)。
  3. 与驱动内部支持的设备列表进行匹配。
  4. 如果匹配成功,返回 EFI_SUCCESS;否则返回 EFI_UNSUPPORTED。
  5. 无论成功与否,都必须 CloseProtocol() 释放协议引用。
EFI_STATUS
EFIAPI
AbcDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_HANDLE                   RemainingDevicePath  OPTIONAL
  )
{
  EFI_STATUS  Status;
  EFI_PCI_IO_PROTOCOL  *PciIo;

  Status = gBS->OpenProtocol (
                ControllerHandle,
                &gEfiPciIoProtocolGuid,
                (VOID **)&PciIo,
                This->DriverBindingHandle,
                ControllerHandle,
                EFI_OPEN_PROTOCOL_BY_DRIVER
                );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  // 读取 Vendor ID 和 Device ID
  Status = PciIo->Pci.Read (PciIo, EfiPciIoWidthUint16, 0, 2, &PciConfigSpace);
  if (EFI_ERROR (Status) ||
      (PciConfigSpace.Hdr.VendorId != ABC_VENDOR_ID) ||
      (PciConfigSpace.Hdr.DeviceId != ABC_DEVICE_ID)) {
    Status = EFI_UNSUPPORTED;
  }

  gBS->CloseProtocol (ControllerHandle, &gEfiPciIoProtocolGuid,
                      This->DriverBindingHandle, ControllerHandle);
  return Status;
}

3.2.2 Start() 实现模式

Start() 负责真正的驱动绑定工作:

  1. 再次打开 I/O 协议(这次使用 EFI_OPEN_PROTOCOL_BY_DRIVER 或 EFI_OPEN_PROTOCOL_GET_PROTOCOL)。
  2. 分配驱动私有数据结构(Private Context),保存 I/O 协议指针和控制器句柄。
  3. 初始化硬件(如设置 PCI 总线主控、中断使能等)。
  4. 安装子协议(如 gEfiSimpleTextOutProtocolGuid)到控制器句柄上。
  5. 返回 EFI_SUCCESS。
EFI_STATUS
EFIAPI
AbcDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   ControllerHandle,
  IN EFI_HANDLE                   RemainingDevicePath  OPTIONAL
  )
{
  EFI_STATUS           Status;
  ABC_DRIVER_PRIVATE   *Private;

  Private = AllocateZeroPool (sizeof (ABC_DRIVER_PRIVATE));
  if (Private == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = gBS->OpenProtocol (
                ControllerHandle,
                &gEfiPciIoProtocolGuid,
                (VOID **)&Private->PciIo,
                This->DriverBindingHandle,
                ControllerHandle,
                EFI_OPEN_PROTOCOL_BY_DRIVER
                );
  if (EFI_ERROR (Status)) {
    FreePool (Private);
    return Status;
  }

  // 初始化硬件并安装子协议
  Private->ControllerHandle = ControllerHandle;
  Private->Signature        = ABC_DRIVER_PRIVATE_SIGNATURE;

  Status = gBS->InstallMultipleProtocolInterfaces (
                &ControllerHandle,
                &gEfiAbcProtocolGuid,
                &Private->AbcProtocol,
                NULL
                );
  if (EFI_ERROR (Status)) {
    gBS->CloseProtocol (ControllerHandle, &gEfiPciIoProtocolGuid,
                        This->DriverBindingHandle, ControllerHandle);
    FreePool (Private);
  }
  return Status;
}

3.3 驱动入口点(Entry Point)

UEFI 驱动的入口点函数(DriverEntryPoint)是驱动映像被加载后第一个被调用的函数。它的主要职责是安装驱动绑定协议到驱动映像句柄上,并注册驱动的卸载函数。

入口点的标准流程:

  1. 初始化驱动全局数据(如设备列表、缓存等)。
  2. 调用 gBS->InstallMultipleProtocolInterfaces() 安装 EFI_DRIVER_BINDING_PROTOCOL 和可选的 EFI_DRIVER_CONFIGURATION_PROTOCOL、EFI_DRIVER_DIAGNOSTICS_PROTOCOL。
  3. 返回 EFI_SUCCESS。
EFI_STATUS
EFIAPI
AbcDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;

  // 1. 初始化全局数据
  // ...

  // 2. 安装 Driver Binding Protocol
  Status = gBS->InstallMultipleProtocolInterfaces (
                &ImageHandle,
                &gEfiDriverBindingProtocolGuid,
                &gAbcDriverBinding,
                &gEfiDriverConfigurationProtocolGuid,
                &gAbcDriverConfiguration,
                &gEfiDriverDiagnosticsProtocolGuid,
                &gAbcDriverDiagnostics,
                NULL
                );
  return Status;
}
常见错误:入口点函数中不要做硬件初始化或设备枚举。硬件初始化应推迟到 Start() 中执行。入口点只负责安装协议和初始化驱动自身的全局状态。如果在入口点中访问硬件,当驱动被加载但设备不存在时,会导致系统崩溃。

3.4 驱动的安装与卸载

驱动的安装与卸载涉及两个层面:驱动映像的加载/卸载和驱动与设备的绑定/解绑。

3.4.1 驱动安装流程

步骤 动作 说明
1 加载驱动映像 UEFI 启动管理器或 Shell 调用 LoadImage() 将驱动 PE/COFF 映像加载到内存。
2 调用入口点 StartImage() 调用驱动的 DriverEntryPoint,安装 Driver Binding Protocol。
3 连接驱动与设备 UEFI 总线枚举器或 ConnectController() 遍历设备句柄,调用 Supported() 匹配。
4 绑定设备 匹配成功后调用 Start(),驱动安装子协议到设备句柄上。

3.4.2 驱动卸载流程

驱动的卸载通常由 UnloadImage() 触发,驱动需要实现 Unload 函数:

  1. 遍历所有已绑定的设备句柄,对每个句柄调用 Stop() 解绑。
  2. 卸载 Driver Binding Protocol 等所有已安装的协议。
  3. 释放驱动全局数据占用的内存。
EFI_STATUS
EFIAPI
AbcDriverUnload (
  IN EFI_HANDLE  ImageHandle
  )
{
  EFI_STATUS  Status;
  UINTN       HandleCount;
  EFI_HANDLE  *HandleBuffer;
  UINTN       Index;

  // 1. 获取所有绑定了本驱动的设备句柄
  Status = gBS->LocateHandleBuffer (
                ByProtocol,
                &gEfiAbcProtocolGuid,
                NULL,
                &HandleCount,
                &HandleBuffer
                );
  if (!EFI_ERROR (Status)) {
    for (Index = 0; Index < HandleCount; Index++) {
      gAbcDriverBinding.Stop (&gAbcDriverBinding,
                              HandleBuffer[Index],
                              NULL);
    }
    FreePool (HandleBuffer);
  }

  // 2. 卸载 Driver Binding Protocol
  Status = gBS->UninstallMultipleProtocolInterfaces (
                ImageHandle,
                &gEfiDriverBindingProtocolGuid,
                &gAbcDriverBinding,
                &gEfiDriverConfigurationProtocolGuid,
                &gAbcDriverConfiguration,
                &gEfiDriverDiagnosticsProtocolGuid,
                &gAbcDriverDiagnostics,
                NULL
                );
  return Status;
}

3.4.3 Stop() 实现要点

Stop() 是 Start() 的逆操作,必须彻底清理所有资源:

  • 卸载通过 InstallMultipleProtocolInterfaces() 安装的子协议。
  • 关闭通过 OpenProtocol() 打开的 I/O 协议。
  • 释放私有数据结构(Private Context)。
  • 恢复硬件到初始状态(如禁用 DMA、中断)。
资源泄漏防范:在 Stop() 中,必须确保每个 OpenProtocol() 都有对应的 CloseProtocol(),每个 AllocatePool() 都有对应的 FreePool()。UEFI 固件不会自动回收驱动泄漏的资源,泄漏会导致系统在后续启动阶段出现不可预测的行为。
驱动安装与卸载生命周期 LoadImage() StartImage() Entry Point ConnectController() Supported() Start() UnloadImage() Stop() Uninstall 协议

3.5 驱动模型最佳实践

  • 单一职责:一个驱动只管理一类设备,不要在一个驱动中混合多种设备类型的支持。
  • 延迟初始化:硬件资源在 Start() 中分配,在 Stop() 中释放,不要在入口点中操作硬件。
  • 协议引用计数:使用 OpenProtocol() 时注意属性参数,EFI_OPEN_PROTOCOL_BY_DRIVER 会占用引用计数,确保在 Stop() 中关闭。
  • 版本管理:驱动版本号用于解决驱动冲突,高版本驱动优先绑定设备。版本号应随驱动更新递增。
  • 错误回滚:在 Start() 中如果中途失败,必须回滚所有已分配的资源,确保设备状态与调用前一致。
调试建议:在 Supported() 和 Start() 中添加 DEBUG() 宏输出,打印 Vendor ID、Device ID 和返回状态。使用 UEFI Shell 的 drivers 和 devices 命令可以查看当前系统中已安装的驱动和设备的绑定关系。