《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
2
3
4
5
6
7
8
9
10
#include <Uefi.h>	// 包含基本数据类型和核心数据结构

EFI_STATUS
EFIAPI
UefiMain ( // UEFI标准应用程序通常的入口函数名,由.inf中的ENTRY_POINT定义
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
}
  • 返回类型EFI_STATUS
    • 本质无符号长整数
    • 最高位1错误值,最高位0非错误值
    • EFI_SUCCESS=0,表示没有错误的返回值
  • 参数
    • ImageHandle:
      • .efi加载到内存后生成的对象为Image
      • ImageHandle是Image的句柄
    • SystemTable:
      • 获取UEFI的各种服务
      • 全局结构体

Shell应用程序工程模块

入口函数

1
2
3
4
5
6
7
8
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
}

没有SystemTable参数,通过全局变量gST使用系统表

UEFI驱动模块

  • 驱动常驻内存
  • 应用程序执行完毕后会从内存清楚

入口函数

1
2
3
4
5
6
7
8
EFI_STATUS
EFIAPI
InitializePciSioSerial (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
}

UEFI中的Protocol

UEFI中的Protocol引入了面向对象的思想:

  • 用struct模拟class
  • 用函数指针模拟成员函数
    • 这种函数的第一参数必须是指向Protocol的指针
    • 用来模拟this指针

一个Protocol实例例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// MdePkg\Include\Protocol\BlockIo.h

struct _EFI_BLOCK_IO_PROTOCOL {
UINT64 Revision;
EFI_BLOCK_IO_MEDIA *Media;

EFI_BLOCK_RESET Reset;
EFI_BLOCK_READ ReadBlocks;
EFI_BLOCK_WRITE WriteBlocks;
EFI_BLOCK_FLUSH FlushBlocks;
};

extern EFI_GUID gEfiBlockIoProtocolGuid;
1
2
3
4
// MdePkg\MdePkg.dec

## Include/Protocol/BlockIo.h
gEfiBlockIoProtocolGuid = { 0x964E5B21, 0x6459, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}
1
2
3
4
5
6
7
8
9
10
11
// MdePkg\Include\Protocol\BlockIo.h

typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_READ)(
IN EFI_BLOCK_IO_PROTOCOL *This,
IN UINT32 MediaId,
IN EFI_LBA Lba,
IN UINTN BufferSize,
OUT VOID *Buffer
);

Protocol在UEFI内核中的表示

  • EFI_HANDLE:UEFI用于表示指向一个对象的指针

    1
    typedef VOID *EFI_HANDLE;
    • Controller:一个EFI_HANDLE对象
      • 用于控制设备
      • UEFI扫描总线后会为所有设备建立一个Controller
    • Image:也是一个EFI_HANDLE对象
      • .efi文件被加载进内存后建立
  • IHANDLE:EFI_HANDLE指向的对象表示结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef 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
    16
    typedef 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
    11
    typedef 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
2
3
4
5
6
7
8
9
10
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL)(
IN EFI_HANDLE Handle, // 指定的Handle,将查询并打开此Handle中安装的Protocol
IN EFI_GUID *Protocol, // 要打开的Protocol GUID
OUT VOID **Interface OPTIONAL, // 返回打开的Protocol对象
IN EFI_HANDLE AgentHandle, // 打开此Protocol的Image
IN EFI_HANDLE ControllerHandle, // 使用此Protocol的控制器
IN UINT32 Attributes // 打开Protocol的方式
);

HandleProtocol

OpenProtocol的简化

1
2
3
4
5
6
7
typedef
EFI_STATUS
(EFIAPI *EFI_HANDLE_PROTOCOL)(
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
OUT VOID **Interface
);

LocateProtocol

从UEFI内核中找出指定Protocol的第一个实例

1
2
3
4
5
6
7
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL)(
IN EFI_GUID *Protocol,
IN VOID *Registration OPTIONAL,
OUT VOID **Interface
);

CloseProtocol

1
2
3
4
5
6
7
8
typedef
EFI_STATUS
(EFIAPI *EFI_CLOSE_PROTOCOL)(
IN EFI_HANDLE Handle,
IN EFI_GUID *Protocol,
IN EFI_HANDLE AgentHandle,
IN EFI_HANDLE ControllerHandle
);

UEFI基础服务

系统表

  • 用户空间通向系统空间的通道
  • DXE阶段初始化
  • 全局结构体

应用程序和驱动如何访问

系统表指针作为Image入口函数的参数传递到用户空间

1
2
3
4
5
6
typedef
EFI_STATUS
(EFIAPI *EFI_IMAGE_ENTRY_POINT)(
IN EFI_HANDLE ImageHandle, // Image的句柄
IN EFI_SYSTEM_TABLE *SystemTable // 系统表指针
);

系统表构成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct {
EFI_TABLE_HEADER Hdr; // 标准UEFI表头
CHAR16 *FirmwareVendor; // 固件提供商
UINT32 FirmwareRevision; // 固件版本号
EFI_HANDLE ConsoleInHandle; // 输入控制台的句柄
EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;
EFI_HANDLE ConsoleOutHandle; // 输出控制台的句柄
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
EFI_HANDLE StandardErrorHandle; // 标准错误控制台的句柄
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
EFI_RUNTIME_SERVICES *RuntimeServices; // RT服务表
EFI_BOOT_SERVICES *BootServices; // BS服务表
UINTN NumberOfTableEntries; // ConfigurationTable数组大小
EFI_CONFIGURATION_TABLE *ConfigurationTable; // 系统配置表数组
} EFI_SYSTEM_TABLE;

启动服务

启动服务分为以下几类:

  • UEFI事件服务

  • 内存管理服务

  • Protocol管理服务

  • Protocol使用类服务

  • 驱动管理服务

  • Image管理服务

  • ExitBootService服务

  • 其他

运行时服务

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

《UEFI原理与编程》读书笔记
http://akaieurus.github.io/2024/03/27/UEFI原理与编程读书笔记/
作者
Eurus
发布于
2024年3月27日
许可协议