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