4. UEFI 应用与驱动模型

在 UEFI 固件体系中,模块并不是“可执行文件”这一种单一形态。按照运行阶段、服务依赖和是否参与设备管理,UEFI 模块主要分为 UEFI Application、DXE Driver 以及遵循 UEFI Driver Model 的驱动。理解它们的入口点、模块类型与加载流程,是进行协议实现和平台服务开发的基础。

一、核心原理:三类模块的定位差异

UEFI Application 是运行在 UEFI 环境下的应用程序,通常由 Shell 或 Boot Manager 加载,运行在 Boot Services 仍然可用的阶段。它可以直接调用 Boot Services、Runtime Services 和已安装的协议,适合做诊断工具、固件更新程序、配置工具或操作系统加载器。

DXE Driver 是 DXE 阶段被 DXE Dispatcher 调度的驱动模块。它的核心任务是初始化硬件、安装协议、产生 Handle,并在需要时消费其他驱动提供的协议。DXE Driver 不一定要绑定具体设备,它可以是一个“服务提供者”,例如安装一个自定义协议供后续模块使用。

UEFI Driver Model 则是在 DXE Driver 基础上进一步规范化的驱动模型。它要求驱动实现 DriverBinding 协议,通过 Supported()、Start()、Stop() 三个入口管理设备。驱动不再直接“写死”某个设备,而是由总线驱动枚举设备后,通过 Driver Binding 动态匹配并启动。这种模型让驱动具备可插拔、可组合、可热插拔管理的能力。

关键区别:UEFI Application 面向“执行任务”;DXE Driver 面向“安装协议、初始化服务”;UEFI Driver Model 面向“管理设备并绑定驱动”。三者入口点、生命周期和依赖服务均不同。

二、入口点与模块类型

UEFI 模块的入口点由模块类型决定。Application 和 DXE Driver 通常使用 UefiMain 或 ProcessModuleEntryPointList 作为入口;而 UEFI Driver Model 驱动通常使用 InitializeDriver 作为入口,并在其中安装 EFI_DRIVER_BINDING_PROTOCOL。

在 EDK II 中,模块类型通过 INF 文件的 MODULE_TYPE 声明。常见类型包括 UEFI_APPLICATION、DXE_DRIVER、UEFI_DRIVER、DXE_RUNTIME_DRIVER、DXE_SMM_DRIVER 等。模块类型决定了构建系统如何链接、如何生成入口点、以及模块被哪个调度器加载。

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = MyUefiApp
  FILE_GUID                      = 12345678-1234-1234-1234-1234567890AB
  MODULE_TYPE                    = UEFI_APPLICATION
  ENTRY_POINT                    = UefiMain

[Sources]
  MyUefiApp.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib

对于 UEFI Driver Model 驱动,INF 中通常声明 MODULE_TYPE = UEFI_DRIVER,并在源文件中定义 EFI_DRIVER_BINDING_PROTOCOL 实例。入口函数 InitializeDriver 负责安装该协议,之后由 DXE Core 或总线驱动触发 Supported() 和 Start()。

三、加载流程:从镜像到运行

UEFI 模块的加载流程可以概括为“发现镜像 → 加载到内存 → 重定位 → 解析入口 → 调用入口 → 安装协议/执行任务”。对于 Application,通常由 Shell 通过 LoadImage() 和 StartImage() 完成;对于 DXE Driver,则由 DXE Dispatcher 根据依赖关系调度;对于 UEFI Driver Model,则在 Driver Binding 被安装后,由总线驱动或平台策略触发绑定。

UEFI 模块加载与驱动绑定流程 UEFI Application Shell / Boot Manager LoadImage() StartImage() UefiMain() 执行 调用协议 / 执行任务 DXE Driver DXE Dispatcher 依赖解析 / 调度 入口点调用 安装 Protocol 产生 Handle / 服务 UEFI Driver Model 安装 DriverBinding 总线枚举设备 Supported() 匹配 Start() 绑定设备 设备可用 / Stop() 卸载 Application 由 Shell 启动;DXE Driver 由 Dispatcher 调度;UEFI Driver Model 由 DriverBinding 动态绑定设备

四、案例与场景:自定义协议驱动的加载

假设我们要开发一个平台服务,向后续模块提供“平台信息查询协议”。可以创建一个 DXE Driver,在入口点中安装 EFI_PLATFORM_INFO_PROTOCOL。该驱动不绑定具体设备,因此不需要实现 Driver Binding,只需在入口点调用 InstallProtocolInterface() 即可。

EFI_STATUS
EFIAPI
PlatformInfoInit (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  EFI_HANDLE  Handle = NULL;

  Status = gBS->InstallProtocolInterface (
                  &Handle,
                  &gEfiPlatformInfoProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &gPlatformInfo
                  );
  return Status;
}

如果该服务需要管理一个 PCI 设备,则应改为 UEFI Driver Model:实现 Supported() 判断设备是否匹配,Start() 初始化设备并安装协议,Stop() 释放资源。这样,当设备被热插拔或重新枚举时,驱动可以自动响应。

五、常见误区与复盘

  • 误区一:把 UEFI Application 当作 DXE Driver 使用。Application 运行在 Boot Services 可用阶段,不能假设自己会在 DXE 调度时被自动加载。
  • 误区二:认为所有 DXE Driver 都必须实现 Driver Binding。只有需要管理设备的驱动才需要;纯服务型驱动只需安装协议。
  • 误区三:忽略入口点与模块类型的匹配。INF 中 MODULE_TYPE 与源文件入口点不一致,会导致构建失败或运行异常。
  • 误区四:在 Supported() 中做耗时初始化。Supported() 应快速判断,真正初始化应放在 Start() 中。
模块类型 典型入口点 加载者 核心职责
UEFI Application UefiMain Shell / Boot Manager 执行任务、调用协议
DXE Driver 入口点函数 DXE Dispatcher 安装协议、初始化服务
UEFI Driver Model InitializeDriver 总线驱动 / DXE Core Supported/Start/Stop 管理设备
DXE Runtime Driver 入口点函数 DXE Dispatcher 提供运行时服务

实践建议:开发前先明确模块类型:是“一次性任务”还是“长期服务”?是否绑定设备?是否需要运行时可用?答案会直接决定 INF 的 MODULE_TYPE、入口点形式和是否实现 Driver Binding。

掌握 UEFI Application、DXE Driver 与 UEFI Driver Model 的差异,本质上是在掌握“模块生命周期”与“服务提供方式”的设计选择。入口点决定模块如何被调用,模块类型决定构建与调度方式,加载流程决定模块何时、由谁、以何种依赖顺序进入系统。只有把这三者统一起来,才能写出结构清晰、可维护、可扩展的 UEFI 模块。