UEFI系统的启动遵循UEFI平台初始化标准。UEFI系统从上电到关机可以分为以下7个阶段:
SEC(安全验证)→PEI(EFI前期初始化)→DXE(驱动执行环境)→BDS(启动设备选择)→TSL(操作系统加载前期)→RT(Run Time)→AL(系统灾难恢复期)
下图展示了UEFI系统从加电到关机的7个阶段(以图中竖线为界)。前三个阶段是UEFI初始化阶段,DXE阶段结束后UEFI环境已经准备完毕。BDS和TSL是操作系统加载器作为UEFI应用程序运行的阶段。操作系统加载器调用ExitBootServices()服务后进入RT阶段,RT阶段包括操作系统加载器后期和操作系统运行期。当系统硬件或操作系统出现严重错误不能继续正常运行时,固件会尝试修复错误,这时系统进入AL期。但PI规范和UEFI规范都没有规定AL期的行为。“?”表示其行为由系统供应商自行定义。
一、SEC(Security Phase)安全验证阶段
SEC作为整个系统的起点,可能会遇到各种异常,就需要设置IDT,有了中断描述符表接收异常,就能让系统遭遇意外的情况时不会崩溃,同时它还为PEI阶段的代码设置临时内存的基地址和长度,并传给PEI,最后找到PEI代码入口点,移交控制权,并且处理临时内存。SEC是平台初始化的第一个阶段,计算机加电后首先进入这个阶段。它主要做以下4件事情:
- 接收和处理系统的启动、重启、异常信号。
- SEC phase特色功能”Cache As RAM(CAR)”,在Cache上开辟一片空间作为内存使用。(原因:因为此时内存还没有被初始化,C语言运行时需要内存和栈空间)
- SEC阶段作为可信系统的根。
- 传递系统参数给PEI phase。
(一)SEC阶段的功能
- 接收并处理系统启动和重启的信号,系统加电信号、重启信号、运行过程的异常信号。
- 在SEC阶段时,仅有CPU和CPU内部资源被初始化,而各种外部设备和内存都没有被初始化。因此系统需要一部分临时的内存用于代码和数据的存储,一般称为临时RAM,此时它只能位于CPU内部。最常用的临时RAM是Cache,将它作为初始化临时存储区域。
- 只有SEC phase被系统信任,下面的各个阶段才有被信任的基础。
- SEC phase移交控制权给PEI phase,SEC phase给PEI phase传递的参数如下:
- 栈的地址和大小
- 系统当前的状态
- 可启动固件的地址和大小
- 临时RAM区域的地址和大小
(二)SEC阶段的执行流程
根据临时RAM是否初始化为界限,SEC阶段分为两部分:临时RAM初始化前称为Reset Vector阶段;临时RAM初始化后调用SEC入口函数进入SEC功能区。
1、Reset Vector的执行流程如下:
A)进入固件入口
B)从实模式转换为32位平坦模式
C)定位固件中的BFV
D)定位BFV中的SEC映像
E)若是64位系统,要从32位模式转换为64位模式
F)调用SEC入口函数
2、进入SEC功能区
进入功能区后,首先利用CAR技术初始栈,初始化IDT,初始化EFI_SEC_PEI_HAND_OFF,将控制权交给PEI,并将EFI_SEC_PEI_HAND_OFF传递给PEI。
EFI_SEC_PEI_HAND_OFF结构体很重要,具体如下:
///
/// EFI_SEC_PEI_HAND_OFF structure holds information about
/// PEI core's operating environment, such as the size of location of
/// temporary RAM, the stack location and BFV location.
///
typedef struct _EFI_SEC_PEI_HAND_OFF {
///
/// Size of the data structure.
///
UINT16 DataSize;
///
/// Points to the first byte of the boot firmware volume,
/// which the PEI Dispatcher should search for
/// PEI modules.
///
VOID *BootFirmwareVolumeBase;
///
/// Size of the boot firmware volume, in bytes.
///
UINTN BootFirmwareVolumeSize;
///
/// Points to the first byte of the temporary RAM.
///
VOID *TemporaryRamBase;
///
/// Size of the temporary RAM, in bytes.
///
UINTN TemporaryRamSize;
///
/// Points to the first byte of the temporary RAM
/// available for use by the PEI Foundation. The area
/// described by PeiTemporaryRamBase and PeiTemporaryRamSize
/// must not extend outside beyond the area described by
/// TemporaryRamBase & TemporaryRamSize. This area should not
/// overlap with the area reported by StackBase and
/// StackSize.
///
VOID *PeiTemporaryRamBase;
///
/// The size of the available temporary RAM available for
/// use by the PEI Foundation, in bytes.
///
UINTN PeiTemporaryRamSize;
///
/// Points to the first byte of the stack.
/// This are may be part of the memory described by
/// TemporaryRamBase and TemporaryRamSize
/// or may be an entirely separate area.
///
VOID *StackBase;
///
/// Size of the stack, in bytes.
///
UINTN StackSize;
} EFI_SEC_PEI_HAND_OFF;
EFI_SEC_PEI_HAND_OFF结构体中保存了PEI core的运行环境信息,如临时RAM的位置大小、堆栈位置和BFV位置。
1)数据结构的大小。
///
/// Size of the data structure.
///
UINT16 DataSize;
2)指向BFV的第一个字节,PEI Dispatcher应该搜索PEI模块。
///
/// Points to the first byte of the boot firmware volume,
/// which the PEI Dispatcher should search for
/// PEI modules.
///
VOID *BootFirmwareVolumeBase;
3)BFV的大小,单位是字节。
///
/// Size of the boot firmware volume, in bytes.
///
UINTN BootFirmwareVolumeSize;
4)指向临时RAM的第一个字节。
///
/// Points to the first byte of the temporary RAM.
///
VOID *TemporaryRamBase;
5)临时RAM的大小,以字节为单位。
///
/// Size of the temporary RAM, in bytes.
///
UINTN TemporaryRamSize;
6)指向PEI可以使用的临时RAM的第一个字节。PeiTemporaryRamBase和PeiTemporaryRamSize所描述的区域不能超出TemporaryRamBase和TemporaryRamSize所描述的区域。这个区域不应该与StackBase和StackSize返回的区域重叠。
///
/// Points to the first byte of the temporary RAM
/// available for use by the PEI Foundation. The area
/// described by PeiTemporaryRamBase and PeiTemporaryRamSize
/// must not extend outside beyond the area described by
/// TemporaryRamBase & TemporaryRamSize. This area should not
/// overlap with the area reported by StackBase and
/// StackSize.
///
VOID *PeiTemporaryRamBase;
7)PEI使用的可用临时RAM的大小,以字节为单位。
///
/// The size of the available temporary RAM available for
/// use by the PEI Foundation, in bytes.
///
UINTN PeiTemporaryRamSize;
8)指向堆栈的第一个字节。这可能是由TemporaryRamBase和TemporaryRamSize描述的内存的一部分,也可能是一个完全独立的区域。
///
/// Points to the first byte of the stack.
/// This are may be part of the memory described by
/// TemporaryRamBase and TemporaryRamSize
/// or may be an entirely separate area.
///
VOID *StackBase;
9)堆栈的大小,以字节为单位。
///
/// Size of the stack, in bytes.
///
UINTN StackSize;
3、IA32下的SEC入口函数,截取了一部分,详细代码在UefiCpuPkg\SecCore\SecMain.c。SEC的C语言阶段的入口点。SEC汇编代码初始化一些临时内存并建立堆栈后,控制被转移到这个函数。
二、PEI(Pre-EFI Initialization)前期初始化阶段
虽然SEC阶段对CPU和CPU内部的资源进行了初始化。但PEI阶段可用的资源依旧十分有限,该阶段是对内存进行初始化,主要功能是为DXE阶段准备执行环境,将所需要传递给DXE的信息组成HOB(Hand Off Block)列表,最终将控制权转交给DXE。UEFI具有模块化设计的特点,PEI就是一个模块。PEI Image的入口函数调用PEI模块的入口函数PEICore。
(一)PEI阶段的执行流程
1、SEC模块找到PEI Image的入口函数_ModuleEntryPoint,函数位于 MdePkg/Library/PeiCoreEntryPoint/PeiCoreEntryPoint.c中。
VOID
EFIAPI
_ModuleEntryPoint(
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreData,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList
)
{
ProcessModuleEntryPointList (SecCoreData, PpiList, NULL);
//
// Should never return
//
ASSERT(FALSE);
CpuDeadLoop ();
}
2、_ModuleEntryPoint函数最终调用PEI模块的入口函数PEICore,进入PEI入口。
VOID
EFIAPI
PeiCore (
IN CONST EFI_SEC_PEI_HAND_OFF *SecCoreDataPtr,
IN CONST EFI_PEI_PPI_DESCRIPTOR *PpiList,
IN VOID *Data
)
以OVMF为例,从SEC阶段分析得知,PEI入口函数是PeiCore,位置:
edk2\MdeModulePkg\Core\Pei\PeiMain\PeiMain.c,以上代码只是PeiCore函数的一部分。
3、根据SEC阶段传入的信息初始化PS(PEICore Service)。
//
// Initialize PEI Core Services
//
InitializeMemoryServices (&PrivateData, SecCoreData, OldCoreData);
4、调度系统中的PEIM(PEI Module),准备HOB列表。
//
// Call PEIM dispatcher
//
PeiDispatcher (SecCoreData, &PrivateData);
5、调用PEIServices得到DXE IPL PPI的Entry服务(即DXELoadCore)。
//
// Lookup DXE IPL PPI
//
Status = PeiServicesLocatePpi (
&gEfiDxeIplPpiGuid,
0,
NULL,
(VOID **)&TempPtr.DxeIpl
);
ASSERT_EFI_ERROR (Status);
注意:PPI与DXE阶段的Protocol类似,每个PPI都是一个结构体,包含有函数指针和变量。每个PPI都有一个GUID。通过PEIServices的LocatePPI服务可以找到GUID对应的PPI实例。
6、 DXELoadCore服务找出并运行DXEImage的入口函数,将HOB列表传递给DXE。
// DXE IPL
Status = TempPtr.DxeIpl->Entry (
TempPtr.DxeIpl,
&PrivateData.Ps,
PrivateData.HobList
);
PEI阶段执行流程图如下:
PEI阶段执行流程完整描述:SEC模块找到PEI Image的入口函数 _ModuleEntryPoint, _ModuleEntryPoint函数最终调用PEI模块的入口函数PEICore,进入PEICore后,首先根据从SEC阶段出入的信息设置PEI Core Services,然后调用PEIDispatcher执行系统总的PEIM,在内存初始化完成后,系统切换栈并重新进入PEICore。重新进入PEICore后使用的不再是临时RAM 而是真正的内存。在所有PEIM执行完成后,调用PEIServices的LocatePPI服务得到DXE IPL PPI,并调用DXE IPL PPI的Entry服务(即DEXLoadCore),找出DEX Image的入口函数,执行DXE Image函数并将HOB列表传递给DXE。
(二)具体调用的系统中的PEIM如下
- CPU PEIM(提供CPU相关功能,如进行Cache设置、主频设置等等)。
- 平台相关的PEIM(初始化内存控制器、I/O控制器等等)。
- 内存初始化PEIM(对内存进行初始化,此时内存才可以被使用,之前使用的是CPU模拟的临时内存)。
(三)PEI阶段的功能
- 初始化内存
- 为DXE阶段准备执行环境
具体为:
基本的Chipset初始化
Memory Sizing
BIOS Recovery
S3 Resume
切换Stack到Memory(Disable CAR, Enable Cache)
启动DXEIPL(DXE Initial Program Loader)
(四)PEI划分
PEI内核(PEI Foundation):负责PEI基础服务和流程。
PEIM(PEI Module)派遣器:找出系统中所有的PEIM,并根据PEIM之间的依赖关系按顺序执行PEIM。PEI阶段对系统的初始化主要由PEIM完成。
每个PEIM都是一个独立的模块。通过PEIMServices,PEIM可以调用PEI阶段提供的系统服务。通过调用这些服务,PEIM可以访问PEI内核。PEIM之间的通信通过PPI(PEIM-to-PEIM Interfaces)完成。
(五)为什么要有PEI Phase?
1. ROM空间的问题,所有的Code都没有压缩。
2. Memory还没有初始化。
3. Chipset没有初始化。
三、DXE(Driver Execution Environment)驱动执行环境阶段
DXE阶段执行大部分系统初始化工作,进入此阶段时,已经有足够的内存可以使用,因此可以完成大量的驱动加载和初始化工作。遍历固件中所有的Driver,当Driver所依赖的资源都满足要求时,调度Driver到执行队列执行,直到所有的Driver都被加载和执行完毕,系统完成初始化。
(一)DXE阶段的执行流程
1、DXE Core入口。
VOID
EFIAPI
DxeMain (
IN VOID *HobStart
)
2、根据HOB列表初始化系统服务。
//
VectorInfoList = NULL;
GuidHob = GetNextGuidHob (&gEfiVectorHandoffInfoPpiGuid, HobStart);
if (GuidHob != NULL) {
VectorInfoList = (EFI_VECTOR_HANDOFF_INFO *) (GET_GUID_HOB_DATA(GuidHob));
}
Status = InitializeCpuExceptionHandlersEx (VectorInfoList, NULL);
ASSERT_EFI_ERROR (Status);
3、初始化DXE调度程序。
//
// Initialize the DXE Dispatcher
//
CoreInitializeDispatcher ();
4、负责调用Dispatcher,所有的DXE Driver在这个函数中被检测并执行。
CoreDispatcher ();
5、 传输控制到BDS架构协议。
DXE阶段的执行流程如下:
(二)DXE阶段的功能
- 几乎所有的硬件初始化都在这里完成。
- 产生EFI System Table,来提供各种Service供所有阶段使用。
- 把控制权交给BDS来BOOT OS。
(三)涉及到的元件及功能
- DXE Core:可视为DXE的核心,用来Dispatch DXE Driver和产生EFI System Table,以提供BOOT Service,RunTime Service,DXE Service,负责DXE基础服务和执行流程。
- DXE Driver:被DXE Core所读取,用来做各种硬件初始化,产生protocol和其它Service。
- DXE Dispatcher:DXE Core的一部分,以正确的顺序来搜寻和执行DXE Driver,负责调度执行DXE驱动,初始化系统设备。DXE提供的基础服务包括系统表、启动服务、Run Time Services。
- DXE architecture protocol:由DXE Driver所产生,是DXE Core和HardWare沟通的唯一媒介,所以没有install完全不能开机。
- EFI System Table: 包含了许多pointer,如所有EFI System Table,Configuration Table,Handledatabase,Console Device。
(四)DXE Architecture Protocol种类及其功能
1、Security: 提供 DXE core 验证 firmware volume中的程序是否可用。
2、Cpu: 提供cpu的service,如管理cache,管理中断,取得处理器频率,查询处理器的timer。
3、下面是一个结构体,它用来提供一个微小的延时,单位为百万分之一秒。
typedef struct _EFI_METRONOME_ARCH_PROTOCOL {
EFI_METRONOME_WAIT_FOR_TICK WaitForTIck;
UINT32 TickPeriod;
}EFI_METRONOME_ARCH_PROTOCOL;
4、Timer: 提供固定时间的中断,使Dxe Core Dispatch 完成所有driver后,会将控制权交给BDS。
四、BDS (Boot Device Select)启动设备选择阶段
该阶段所做的任务:
在BDS阶段,主要是初始化控制台设备,加载执行必要的设备驱动,根据用户的选择,执行相应的启动项。
概述:
DXE阶段最终会调用BDS ARCH Protocol的接口EFI_BDS_ARCH_PROTOCOL.Entry()转入BDS阶段。BDS阶段负责加载额外的驱动,与用户交互,必要的硬件初始化,并转入操作系统。
这个阶段前面是DXE后面是OS Loader或shell,同时所有定制化的东西都在这个阶段完成,最主要的Setup page(UI界面)是不同的,不同的BIOS Vendor的UI是不一样的。造成直接影响的三个决策是:启动策略的管理、启动设备的选择、Setup界面。
从DXE拿到控制权后由用户去选择启动设备,开始转交控制权。在过程中可能失败,为了防止此类现象出现定义了Watch Dog来实时监控状态,如果失败则会返回启动界面,BDS再从下一个启动选项开始启动,直到启动成功,如上图所示。所以在BDS阶段要通过Boot Option这个Variable准备好所有可能的Boot选项,可以提供给用户选择。
在整个BDS phase还需要提供用户一个可配置的UI界面,该界面包含若干配置选项信息,这些信息完全是客户定制化的,客户想给用户暴露什么信息,就提供什么信息,正常时候报给客户的信息有:启动列表(哪些是设备是可以启动的)、基本的一些配置(Seurite boot如果这个选项是强制选项,那么根本不会暴露给用户)、显示的页面(宽屏或是窄屏)、安全相关的passwd、硬件相关的CPU芯片组的工作,硬盘工作模式等等。
(一)BSD阶段执行流程
BDS阶段是可以用于定制化的module,可以根据客户要求进行相应的设计。
1、Entry()的服务例程。连接好设备,初始化控制台,并尝试启动选项。BDS 的入口函数,负责安装EFI_BDS_ARCH_PROTOCOL 协议,以让DXE Foundation 调用。
VOID
EFIAPI
BdsEntry (
IN EFI_BDS_ARCH_PROTOCOL *This
)
2、Timeout用于在例如设备启动过程中时间的累计计算,如果在时间内一直未成功,则会进行下一个设备启动。
BootTimeOut = PcdGet16 (PcdPlatformBootTimeOut);
3、函数用于初始化一个加载选项。
Status = EfiBootManagerInitializeLoadOption (
&LoadOption,
LoadOptionNumberUnassigned,
LoadOptionTypePlatformRecovery,
LOAD_OPTION_ACTIVE,
L"Default PlatformRecovery",
FilePath,
NULL,
0
);
4、在调用平台代码之前初始化ConnectConIn事件。通过PcdGetbool来判定条件进而触发事件函数 (用于实现定制化要求)。
if (PcdGetBool (PcdConInConnectOnDemand)) {
Status = gBS->CreateEventEx (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
BdsDxeOnConnectConInCallBack,
NULL,
&gConnectConInEventGuid,
&gConnectConInEvent
);
if (EFI_ERROR (Status)) {
gConnectConInEvent = NULL;
}
}
5、执行平台初始化,可以通过OEM/IBV定制,可以在PlatformBootManagerBeforeConsole中完成的事情。
PERF_START (NULL, "PlatformBootManagerBeforeConsole", "BDS", 0);
6、基于控制台设备变量ConIn、ConOut和ErrOut连接所有控制台设备。
EfiBootManagerConnectAllDefaultConsoles ();
7、尝试启动EFI启动选项。这个例程设置L“BootCurent”,并通知EFI准备好启动事件。
EfiBootManagerBoot (&BootManagerMenu);
8、整个启动函数,包括枚举所有boot option ,然后判断UEFI还是Legacy启动,Legacy启动通过读MBI进行;UEFI启动通过读取特定路径下的BOOt信息,根据Boot Option优先级选择设备启动。
BdsBootDeviceSelect ();
(二)BDS Steps
- 初始化语言和字符串数据库
- 获得当前启动模式
- 基于启动模式建立设备清单
- 连接设备
- 检测input output设备
- 执行内存测试
- 过程引导选项
(三)执行策略
- 初始化控制台console设备,查看系统有多少可以启动的设备。
- 启动所有检测到的设备,加载Driver。
- 检测启动console设备,即输入输出设备。
- 根据收集到的所有信息,提供一个setup的UI界面,终端用户可以在此选择,当用户真正选择启动选项的时候,BDS就会加载启动选项里的OS loader,最后移交真正的控制权给OS Loader ,由OS Loader 转移控制权给OS。
(四)BDS三大任务
console初始化、Driver初始化、BootDeviceSelect
具体包括:
初始化快捷键服务、初始化SystemTable 中的FirmwareVendor 和FirmwareRevision 域、平台相关BDS 初始化、初始化HwErrRecSupport 系统变量、加载操作系统等。
当加载项其启动失败时,系统将重新执行DXE Dispatcher以加载更多的驱动,然后重新尝试加载驱动项。BDS策略通过全局NVRAM变量配置,这些变量可以被运行时服务的GetVariable()读取,通过SetVariable()设置(如BootOrder定义了启动顺序,Boot####对应不同的启动项,#为十六进制数)。当用户选中某个启动项(或进入系统默认启动项)后,OS Loader启动,系统进入TSL阶段。
五、TSL(Transient System Load)操作系统加载前期阶段
TSL阶段是OS Loader执行的第一个阶段,为OS Loader准备执行环境,OS Loader调用ExitBootService结束启动服务,进入RunTime阶段。
TSL阶段被称为临时系统的原因在于它为操作系统加载器准备执行环境。虽然是临时系统,但是已经具备操作系统的雏形,UEFI Shell是这个临时系统的人机交互界面。正常运行中,系统不会进入UEFI Shell,而是直接执行OS Loader,只有在用户干预或是操作系统加载器出现严重问题时才会进入UEFI Shell。
在TSL阶段,系统资源管理通过BS管理,BS提供的服务如下:
- 事件服务:事件是异步操作的基础。有了事件的支持,才可以在UEFI系统内执行并发操作。
- 内存管理:主要提供内存的分配与释放,管理系统的内存映射。
- Protocol管理:提供了安装Protocol与卸载Protocol的服务,以及注册Protocol通知函数(该函数会在Protocol安装时调用)的服务。
- Protocol的实用类服务:包括Protocol的打开与关闭,查找支持protocol的控制器。
- 驱动管理:包括用于将驱动安装到控制器的connect服务,及将驱动从控制器上卸载的disconnect服务。
- Image管理:此类服务包括加载,卸载,启动和推出UEFI应用程序或驱动。
- ExitBootServices:用于结束启动服务,注销BS。
六、 RT(Run Time)运行阶段
在RT阶段,OS Loader已经完全取得了系统的控制权,因此要清理和回收一些之前被UEFI占用的资源,runtime services随着操作系统的运行提供相应的运行时的服务,这个期间一旦出现错误和异常,将进入AL进行修复。
RT阶段提供的服务如下:
七、AL(After Life)灾难恢复阶段
在RT阶段如果系统(硬件或是软件)遇到灾难性错误,系统固件需要提供错误处理以及灾难恢复机制,这种机制运行在AL阶段。根据厂家自定义修复方案,UEFI和UEFI PI均未对AL阶段的行为和规范进行定义。