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() 方法解绑并卸载。
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() 的典型实现流程如下:
- 调用
OpenProtocol()尝试打开设备句柄上的 I/O 协议(如gEfiPciIoProtocolGuid)。 - 通过 I/O 协议读取设备的 Vendor ID 和 Device ID(或 UUID)。
- 与驱动内部支持的设备列表进行匹配。
- 如果匹配成功,返回
EFI_SUCCESS;否则返回EFI_UNSUPPORTED。 - 无论成功与否,都必须
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() 负责真正的驱动绑定工作:
- 再次打开 I/O 协议(这次使用
EFI_OPEN_PROTOCOL_BY_DRIVER或EFI_OPEN_PROTOCOL_GET_PROTOCOL)。 - 分配驱动私有数据结构(Private Context),保存 I/O 协议指针和控制器句柄。
- 初始化硬件(如设置 PCI 总线主控、中断使能等)。
- 安装子协议(如
gEfiSimpleTextOutProtocolGuid)到控制器句柄上。 - 返回
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)是驱动映像被加载后第一个被调用的函数。它的主要职责是安装驱动绑定协议到驱动映像句柄上,并注册驱动的卸载函数。
入口点的标准流程:
- 初始化驱动全局数据(如设备列表、缓存等)。
- 调用
gBS->InstallMultipleProtocolInterfaces()安装EFI_DRIVER_BINDING_PROTOCOL和可选的EFI_DRIVER_CONFIGURATION_PROTOCOL、EFI_DRIVER_DIAGNOSTICS_PROTOCOL。 - 返回
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 函数:
- 遍历所有已绑定的设备句柄,对每个句柄调用
Stop()解绑。 - 卸载 Driver Binding Protocol 等所有已安装的协议。
- 释放驱动全局数据占用的内存。
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 固件不会自动回收驱动泄漏的资源,泄漏会导致系统在后续启动阶段出现不可预测的行为。
3.5 驱动模型最佳实践
- 单一职责:一个驱动只管理一类设备,不要在一个驱动中混合多种设备类型的支持。
- 延迟初始化:硬件资源在
Start()中分配,在Stop()中释放,不要在入口点中操作硬件。 - 协议引用计数:使用
OpenProtocol()时注意属性参数,EFI_OPEN_PROTOCOL_BY_DRIVER会占用引用计数,确保在Stop()中关闭。 - 版本管理:驱动版本号用于解决驱动冲突,高版本驱动优先绑定设备。版本号应随驱动更新递增。
- 错误回滚:在
Start()中如果中途失败,必须回滚所有已分配的资源,确保设备状态与调用前一致。
调试建议:在
Supported() 和 Start() 中添加 DEBUG() 宏输出,打印 Vendor ID、Device ID 和返回状态。使用 UEFI Shell 的 drivers 和 devices 命令可以查看当前系统中已安装的驱动和设备的绑定关系。