2、UEFI基础概念:UEFI规范体系、固件启动流程(SEC、PEI、DXE、BDS、RT)、UEFI驱动模型、UEFI运行时服务与启动服务
UEFI(Unified Extensible Firmware Interface,统一可扩展固件接口)取代传统Legacy BIOS,是连接操作系统与硬件固件之间的关键桥梁。理解UEFI规范体系、启动流程、驱动模型以及服务接口,是从事EDK2固件开发的基础。本章将从规范分层、启动阶段、驱动架构和服务接口四个维度,系统梳理UEFI的核心概念,并结合EDK2开源框架说明其工程落地方式。
上图展示了UEFI固件从SEC到RT的完整启动链路,以及驱动模型和服务接口在整个体系中的位置。下面将逐一展开每个核心概念。
2.1 UEFI规范体系
核心原理
UEFI规范由UEFI论坛维护,当前主流版本为2.10。规范定义了固件与操作系统之间的接口标准,包括平台初始化、启动管理、运行时服务、驱动模型、协议定义等。与Legacy BIOS的16位实模式中断调用不同,UEFI运行在32位或64位保护模式下,使用C语言风格的接口定义,支持网络启动、大容量磁盘(分区)、安全启动(Secure Boot)等现代特性。
UEFI规范体系从逻辑上分为三个层次:平台初始化层(负责从复位向量到DXE阶段的硬件初始化)、固件接口层(提供Boot Services和Runtime Services)、协议层(定义驱动与设备之间的标准接口)。EDK2是Intel主导的开源UEFI实现,其包结构(MdePkg、MdeModulePkg等)严格对应规范层次。
| 规范层次 | 对应EDK2包 | 核心内容 | 典型模块 |
|---|---|---|---|
| 平台初始化 | PcAtChipsetPkg, IntelSiliconPkg | SEC/PEI/DXE阶段硬件初始化 | SecCore, PeiCore, DxeCore |
| 固件接口 | MdePkg | Boot Services, Runtime Services | gBS, gRT, gST |
| 协议层 | MdeModulePkg | 设备驱动协议、I/O抽象 | DevicePath, BlockIo, SimpleFileSystem |
| 平台应用 | OemPkg(定制) | BDS策略、平台专属功能 | PlatformBds, SetupBrowser |
判断方法
在EDK2开发中,判断一个功能应放在哪个层次,可遵循以下原则:硬件寄存器操作放在平台初始化层;通用固件服务(如内存分配、事件处理)使用Boot Services;设备访问抽象通过Protocol实现;启动策略(如启动顺序、超时时间)放在BDS阶段。例如,要新增一个I2C控制器驱动,应实现EFI_DRIVER_BINDING_PROTOCOL,并注册到Handle Database,而不是直接修改DxeCore。
MdePkg/Include/Ppi目录,Protocol定义在MdePkg/Include/Protocol目录。案例拆解
某项目需要在固件中支持板载TPM2.0芯片。按照规范体系,正确做法是:在PEI阶段通过PPI检测TPM是否存在(使用gPeiTpm2PpiGuid);在DXE阶段实现TPM驱动,注册EFI_TPM2_PROTOCOL;在BDS阶段根据TPM状态决定是否启用Secure Boot。如果直接在SEC阶段操作TPM寄存器,会因内存未初始化而失败。
常见误区
- 误区1:认为UEFI规范只包含启动流程,忽略了协议定义和驱动模型的重要性。
- 误区2:在PEI阶段使用Protocol,导致编译错误或运行时崩溃。
- 误区3:自定义Protocol时未遵循UEFI命名规范(GUID格式),导致与其他模块冲突。
2.2 固件启动流程(SEC、PEI、DXE、BDS、RT)
核心原理
UEFI固件启动分为五个阶段,每个阶段有明确的目标和边界:
- SEC(Security Phase):CPU复位后的第一个阶段,负责安全验证、临时RAM(Cache-as-RAM)设置、传递系统参数。代码量最小,通常用汇编编写。
- PEI(Pre-EFI Initialization):初始化内存控制器,发现内存,将临时RAM切换到真实内存。PEIM(PEI Module)通过PPI通信,执行顺序由依赖关系决定。
- DXE(Driver Execution Environment):内存可用后,执行大部分驱动初始化。DXE Dispatcher按依赖关系加载驱动,注册Protocol,建立Handle Database。
- BDS(Boot Device Selection):根据启动策略选择启动设备(硬盘、U盘、网络等),加载启动项。BDS阶段是平台定制的重点。
- RT(Runtime):固件将控制权交给OS,但保留Runtime Services(如变量服务、时间服务)供OS调用。RT阶段代码必须常驻内存,且不能使用Boot Services。
操作流程
在EDK2中,各阶段的入口函数和构建方式如下:
// SEC阶段入口(汇编)
SecEntryPoint:
; 设置临时RAM
; 跳转到C代码
call SecStartup
// PEI阶段入口(C语言)
EFI_STATUS
EFIAPI
PeiCoreEntryPoint (
IN EFI_FFS_FILE_HEADER *FfsHeader,
IN EFI_PEI_SERVICES **PeiServices
);
// DXE阶段入口
EFI_STATUS
EFIAPI
DxeCoreEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
// BDS阶段入口
EFI_STATUS
EFIAPI
BdsEntry (
IN EFI_BDS_ARCH_PROTOCOL *This
);
案例拆解
某服务器平台在启动时出现内存初始化失败,错误代码显示在PEI阶段。排查过程:首先在PEI阶段添加调试打印(DEBUG ((DEBUG_INFO, "MemoryInit...\n"))),确认内存控制器寄存器配置是否正确;然后检查内存条是否插满,使用MemTestPEIM进行快速测试;最终发现是内存参考代码(MRC)参数配置错误,导致DDR5训练失败。修复后,在PEI阶段增加内存参数校验逻辑,避免类似问题。
常见误区
- 误区1:在SEC阶段使用全局变量,但此时栈尚未建立,导致数据损坏。
- 误区2:PEI阶段分配内存使用
AllocatePages,但未考虑内存类型(EfiReservedMemoryType),导致后续阶段无法访问。 - 误区3:BDS阶段直接调用Boot Services的
ExitBootServices,但未正确处理内存映射,导致OS启动失败。
EfiRuntimeServicesCode和EfiRuntimeServicesData内存类型,否则OS无法访问。在EDK2中,可通过EFI_RUNTIME_SERVICES结构体中的ConvertPointer处理虚拟地址转换。2.3 UEFI驱动模型
核心原理
UEFI驱动模型基于Driver Binding Protocol,实现驱动与设备的动态绑定。核心思想是:驱动不直接操作硬件,而是通过Protocol抽象设备功能。驱动模型包含三个关键组件:
- EFI_DRIVER_BINDING_PROTOCOL:包含Supported、Start、Stop三个函数,分别用于检测设备兼容性、启动驱动、停止驱动。
- Handle Database:系统维护的句柄列表,每个句柄关联多个Protocol。
- Device Path Protocol:描述设备在系统中的位置,用于设备识别和启动项管理。
操作流程
驱动加载的典型流程:
- DXE Dispatcher加载驱动镜像,调用驱动入口函数。
- 驱动入口函数安装
EFI_DRIVER_BINDING_PROTOCOL到新的Handle。 - 系统调用驱动的
Supported()函数,检查设备是否支持。 - 若支持,调用
Start()函数,驱动创建子Handle并安装I/O Protocol。 - 设备移除时,调用
Stop()函数释放资源。
案例拆解
以NVMe驱动为例:NvmExpressDxe驱动安装Driver Binding Protocol后,系统遍历PCIe总线上的设备,调用Supported()检查设备的Class Code是否为NVMe(Class=01, Subclass=08, ProgIf=02)。匹配成功后,Start()函数创建Block I/O Protocol,供文件系统驱动使用。如果NVMe设备固件异常,Supported()可能返回EFI_UNSUPPORTED,此时需要检查设备是否被正确枚举。
常见误区
- 误区1:驱动入口函数中直接操作硬件,而不是通过Driver Binding Protocol的Start函数。
- 误区2:多个驱动竞争同一设备时,未正确处理优先级(Driver Binding Protocol的Version字段)。
- 误区3:Stop函数未释放所有资源,导致内存泄漏或句柄残留。
2.4 UEFI运行时服务与启动服务
核心原理
UEFI服务分为两类:Boot Services(BS)和Runtime Services(RT)。BS在OS加载前可用,包括内存分配、事件处理、Protocol管理、镜像加载等;RT在OS运行期间仍可用,包括变量服务、时间服务、复位服务等。系统表(EFI_SYSTEM_TABLE)中的BootServices和RuntimeServices指针分别指向这两组服务。
| 服务类型 | 典型接口 | 使用场景 | 生命周期 |
|---|---|---|---|
| Boot Services | AllocatePages, CreateEvent, InstallProtocolInterface | 驱动初始化、内存分配 | 固件启动到ExitBootServices |
| Runtime Services | GetVariable, SetVariable, GetTime, ResetSystem | OS运行时访问固件数据 | 固件启动到OS关机 |
操作流程
在EDK2中,使用Boot Services的典型代码:
// 分配内存
EFI_PHYSICAL_ADDRESS Address;
Status = gBS->AllocatePages (
AllocateAnyPages,
EfiBootServicesData,
Pages,
&Address
);
// 创建事件
EFI_EVENT TimerEvent;
Status = gBS->CreateEvent (
EVT_TIMER | EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
TimerCallback,
NULL,
&TimerEvent
);
Status = gBS->SetTimer (TimerEvent, TimerPeriodic, 10000000); // 1秒
案例拆解
某项目需要在OS运行时读取固件配置。正确做法是使用Runtime Services的GetVariable接口。在EDK2中,变量存储在Flash的Firmware Volume中,通过SetVariable写入。注意:变量写入有大小限制(通常8KB),且需要遵循UEFI变量命名规范(GUID+名称)。如果OS无法读取变量,检查是否在ExitBootServices后仍使用Boot Services,或者变量属性设置错误(如未设置EFI_VARIABLE_NON_VOLATILE)。
常见误区
- 误区1:在Runtime阶段调用Boot Services,导致系统崩溃。
- 误区2:变量服务使用未加锁的全局缓冲区,导致并发访问冲突。
- 误区3:时间服务未处理时区问题,导致OS显示时间错误。
UefiSpec.h开始阅读,理解每个Protocol和Service的定义,再结合平台代码实践。