《UEFI原理与编程》读书笔记
wjy又双叒叕开始走支线了……经典学新东西一本书起步
UEFI概述
UEFI系统组成
- TSL阶段BS提供的服务:
- 事件服务
 - 内存管理
 - Protocol管理
 - Protocol使用类服务
 - 驱动管理
 - Image管理
 - ExitBootService
 
 - RT提供的服务:
- 时间服务
 - 读写UEFI系统变量
 - 虚拟内存服务
 - 其他服务,如重启系统的ResetSystem
 
 
UEFI启动过程
SEC
SEC阶段功能
- 接受并处理系统启动和重启信号
 - 初始化临时存储区域
 - 作为可信系统的根
 - 传递系统参数给下一阶段PEI
- 系统当前状态
 - BFM的地址和大小
 - 临时RAM的地址和大小
 - 栈的地址和大小
 
 
SEC执行流程
以初始化临时RAM为界,SEC分为两部分:
- Reset Vector
 - SEC功能区
 
Reset Vector执行流程:
- 进入固件入口
 - 从实模式转32位平坦模式
 - 定位固件中的BFV
 - 定位固件中的SEC映像
 - 若是64位系统,从32位模式转换到64位模式
 - 调用SEC入口函数
 
PEI
主要功能是为DXE准备执行环境,将需要传递到DXE的信息组成HOB列表
从功能上讲PEI分为两部分:
- PEI内核:负责PEI基础服务和执行流程
 - PEIM派遣器:找出系统中所有PEIM,根据依赖关系顺序执行
 
PEIM之间通过PPI通信,可通过GUID定位
DXE
从功能上讲DXE分为两部分:
- DXE内核:负责DXE基础服务和执行流程
 - DXE派遣器:负责调度执行DXE驱动,初始化系统设备
 
DXE之间通过Protocol通信,可通过GUID定位
BDS
主要功能是执行启动策略,包括:
- 初始化控制台设备
 - 加载必要的设备驱动
 - 根据系统设置加载和执行启动项
 
加载启动项失败,系统将重新执行DXE dispatcher以加载更多的驱动,然后重新尝试加载启动项
TSL
OS Loader执行的第一个阶段
- OS Loader作为一个UEFI应用程序运行,系统资源仍然由UEFI内核控制
 - ExitBootServices服务被调用后,系统进入RT阶段
 
TSL是一个临时系统,UEFI Shell是这个临时系统的人机交互界面
RT
- 系统控制权和资源由UEFI内核转交到OS Loader手中
 - 运行时服务保留给OS和OS Loader使用
 - 最终OS取得系统的控制权
 
AL
RT阶段错误处理,未定义
UEFI工程模块文件
各UEFI工程文件之间的关系
*Pkg:包
- .dsc:平台描述文件
 - .dec:包申明文件
 - 模块(编译完就是.efi)
- .inf:元数据文件
 - 源文件
 
 
| 模块类型 | 说明 | 
|---|---|
| 标准应用程序工程模块 | 在DXE阶段运行的应用程序(Shell环境下也可以运行) | 
| ShellAppMain应用程序工程模块 | Shell环境下运行的应用程序 | 
| UEFI驱动模块 | 符合UEFI驱动模型的驱动,仅在BS期间有效 | 
| 库模块 | 作为静态库被其他模块调用 | 
| DXE驱动模块 | DXE环境下运行的驱动,此类驱动不遵循UEFI驱动模型 | 
| DXE运行时驱动模型 | 进入RT依然有效的驱动 | 
| DXE SAL驱动模块 | 仅对安腾CPU有效的一种驱动 | 
| DXE SMM驱动模块 | 系统管理模式驱动,模块被加载到系统管理内存区,系统进入RT仍然有效 | 
| PEIM模块 | PEI阶段的模块 | 
| SEC模块 | 固件的SEC阶段 | 
| PEI_CORE模块 | 固件的PEI阶段 | 
| DXE_CORE模块 | 固件的DXE模块 | 
工程文件
edk2文件与edk2工具链命令之间的关系:
.inf
- 作用相当于Makefile
 - 分很多个块,以”[块名]\n”开头
 
| 必需块 | 块描述 | 
|---|---|
| [Defines] | 定义本模块的属性变量及其他变量,这些变量可在工程文件其他块中使用 | 
| [Sources] | 列出本模块所有源文件及资源文件 | 
| [Packages] | 列出本模块引用到的所有包的包声明文件,可能引用到的资源包括头文件、GUID、Protocol等,这些资源都声明在包的包声明文件.dec中 | 
| [LibraryClasses] | 列出本模块要链接的库模块 | 
| 非必需块 | 块描述 | 
|---|---|
| [Protocols] | 列出本模块用到的Protocol | 
| [Guids] | 列出本模块用到的GUID | 
| [BuildOptions] | 指定编译和链接选项 | 
| [Pcd] | 平台配置数据库,用于列出本模块用到的Pcd变量,这些Pcd变量可被整个UEFI系统访问 | 
| [PcdEx] | 用于列出本模块用到的Pcd变量,这些Pcd变量可被整个UEFI系统访问 | 
| [FixedPcd] | 用于列出本模块用到的Pcd编译期常量 | 
| [FeaturePcd] | 用于列出本模块用到的Pcd常量 | 
| [PatchPcd] | 列出的Pcd变量仅本模块可用 | 
.dsc
用于编译一个Package,分为好几个块:
[Define]
用于设置build相关的全局宏变量,可被.dsc其他块使用
通过DEFINE和EDK_GLOBAL定义的宏可以在.dsc和.fdf中通过$+宏变量名使用
必须定义的宏变量
可选宏变量

[LibraryClasses]
- 定义了模块的名字和模块.inf文件的路径
 
[Components]
- 该区域内定义的模块都会被build工具编译生成.efi文件
 
[BuildOptions]
- 编译和链接选项
 
[PCD]
- 定义平台配置数据
 
.dec
定义了公开的数据和的接口,供其他模块使用
- [Defines]
- 用于提供package的名称,GUID,版本号等信息
 
 - [Includes]
- 列出本package提供的头文件所在目录
 
 - [LibraryClasses]
- 对外提供库,每个库都必须包含一个头文件
 
 - [Guids]
- 各个.inf文件中的GUID的常量定义
 
 - [Protocols]
- Protocol的GUID值的定义
 
 - [Ppis]
- 源文件中用到的PPI的GUID值的定义
 
 - [PCD]
- .dsc文件中[PCD]块的补充
 
 
标准应用程序工程模块
入口函数
1  |  | 
- 返回类型EFI_STATUS
- 本质无符号长整数
 - 最高位1错误值,最高位0非错误值
 - EFI_SUCCESS=0,表示没有错误的返回值
 
 - 参数
- ImageHandle:
- .efi加载到内存后生成的对象为Image
 - ImageHandle是Image的句柄
 
 - SystemTable:
- 获取UEFI的各种服务
 - 全局结构体
 
 
 - ImageHandle:
 
Shell应用程序工程模块
入口函数
1  |  | 
没有SystemTable参数,通过全局变量gST使用系统表
UEFI驱动模块
- 驱动常驻内存
 - 应用程序执行完毕后会从内存清楚
 
入口函数
1  |  | 
UEFI中的Protocol
UEFI中的Protocol引入了面向对象的思想:
- 用struct模拟class
 - 用函数指针模拟成员函数
- 这种函数的第一参数必须是指向Protocol的指针
 - 用来模拟this指针
 
 
一个Protocol实例例子:
1  |  | 
1  |  | 
1  |  | 
Protocol在UEFI内核中的表示
EFI_HANDLE:UEFI用于表示指向一个对象的指针
1
typedef VOID *EFI_HANDLE;- Controller:一个EFI_HANDLE对象
- 用于控制设备
 - UEFI扫描总线后会为所有设备建立一个Controller
 
 - Image:也是一个EFI_HANDLE对象
- .efi文件被加载进内存后建立
 
 
- Controller:一个EFI_HANDLE对象
 IHANDLE:EFI_HANDLE指向的对象表示结构体
1
2
3
4
5
6
7
8
9
10typedef struct {
UINTN Signature;
/// All handles list of IHANDLE
LIST_ENTRY AllHandles;
/// List of PROTOCOL_INTERFACE's for this handle
LIST_ENTRY Protocols;
UINTN LocateRequest;
/// The Handle Database Key value when this handle was last created or modified
UINT64 Key;
} IHANDLE;PROTOCOL_INTERFACE:表示handle和Protocol的接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16typedef struct {
UINTN Signature;
/// Link on IHANDLE.Protocols
LIST_ENTRY Link;
/// Back pointer
IHANDLE *Handle;
/// Link on PROTOCOL_ENTRY.Protocols
LIST_ENTRY ByProtocol;
/// The protocol ID
PROTOCOL_ENTRY *Protocol;
/// The interface value
VOID *Interface;
/// OPEN_PROTOCOL_DATA list
LIST_ENTRY OpenList;
UINTN OpenListCount;
} PROTOCOL_INTERFACE;PROTOCOL_ENTRY:表示Protocol的GUID
1
2
3
4
5
6
7
8
9
10
11typedef struct {
UINTN Signature;
/// Link Entry inserted to mProtocolDatabase
LIST_ENTRY AllEntries;
/// ID of the protocol
EFI_GUID ProtocolID;
/// All protocol interfaces
LIST_ENTRY Protocols;
/// Registerd notification handlers
LIST_ENTRY Notify;
} PROTOCOL_ENTRY;
几个结构体之间的关系:
如何使用Protocol
使用Protocol的一般步骤:
找出Protocol对象
1
gBS->OpenProtocol / HandleProtocol / LocateProtocol使用这个Protocol提供的服务
关闭打开的Protocol
1
gBs->CloseProtocol
OpenProtocol
1  |  | 
HandleProtocol
OpenProtocol的简化
1  |  | 
LocateProtocol
从UEFI内核中找出指定Protocol的第一个实例
1  |  | 
CloseProtocol
1  |  | 
UEFI基础服务
系统表
- 用户空间通向系统空间的通道
 - DXE阶段初始化
 - 全局结构体
 
应用程序和驱动如何访问
系统表指针作为Image入口函数的参数传递到用户空间
1  |  | 
系统表构成
1  |  | 
启动服务
启动服务分为以下几类:
UEFI事件服务
内存管理服务
Protocol管理服务
Protocol使用类服务
驱动管理服务
Image管理服务
ExitBootService服务
其他

运行时服务
- 时间服务
- 读取 / 设定系统时间
 - 读取 / 设定系统从睡眠中唤醒的时间
 
 - 读写系统变量服务
- 读取 / 设置系统变量
 
 - 虚拟内存服务
- 将物理内存转化为虚拟内存
 
 - 其他服务