3. UEFI 固件基础:UEFI 规范简介、启动服务、运行时服务、配置表

UEFI(统一可扩展固件接口)规范定义了操作系统与平台固件之间的标准接口。在 EFI Stub 引导 ARM64 设备树的场景中,UEFI 固件负责初始化硬件、加载引导程序(或直接加载 Linux 内核),并通过配置表将设备树(DTB)传递给内核。理解 UEFI 的核心服务与数据结构,是分析设备树传递路径的前提。

3.1 UEFI 规范简介

UEFI 规范取代了传统的 BIOS,提供了更丰富的运行时环境与模块化架构。其核心组件包括:

  • UEFI 系统表(System Table):所有 UEFI 应用的入口点,包含指向启动服务、运行时服务、配置表的指针。
  • 启动服务(Boot Services):仅在操作系统启动前可用,提供内存管理、事件、协议接口等功能。
  • 运行时服务(Runtime Services):在操作系统启动后仍然可用,提供时间、变量、复位等基础服务。
  • 配置表(Configuration Table):系统表中的一个数组,用于传递标准数据结构(如 ACPI、SMBIOS、设备树)的 GUID 与指针。
关键点: ARM64 平台上,UEFI 固件通过配置表传递设备树(DTB)的物理地址。内核在 EFI Stub 中通过查找特定 GUID(EFI_DTB_TABLE_GUID)来获取 DTB 指针。
UEFI 系统表与配置表结构 UEFI 系统表与配置表结构 EFI_SYSTEM_TABLE - Firmware Vendor - Revision - Boot Services 指针 - Runtime Services 指针 - Configuration Table 数组 - ... 指针 Boot Services - AllocatePages / FreePages - LocateProtocol / HandleProtocol Runtime Services - GetVariable / SetVariable - GetTime / SetTime / ResetSystem Configuration Table (数组) ACPI 表 GUID → 指针 SMBIOS GUID → 指针 DTB GUID → DTB 地址

3.2 启动服务(Boot Services)

启动服务是 UEFI 固件在引导阶段提供的核心功能集合。操作系统加载器(如 EFI Stub)在调用 ExitBootServices() 之前,可以自由使用这些服务。关键服务包括:

服务类别 典型函数 说明
内存管理 AllocatePages(), FreePages(), GetMemoryMap() 分配/释放物理页,获取内存映射表
协议管理 LocateProtocol(), OpenProtocol(), InstallProtocolInterface() 查找和安装 UEFI 协议接口
事件与定时器 CreateEvent(), SetTimer(), WaitForEvent() 创建同步/异步事件,设置定时器
映像加载 LoadImage(), StartImage(), Exit() 加载并启动 UEFI 应用程序或驱动程序
退出引导服务 ExitBootServices() 移交硬件控制权给操作系统,之后启动服务不可用
EFI Stub 中的关键调用: Linux 内核的 EFI Stub 在引导早期会调用 GetMemoryMap() 获取物理内存布局,然后调用 ExitBootServices() 关闭启动服务。在此之后,内核只能使用运行时服务。

3.3 运行时服务(Runtime Services)

运行时服务在操作系统启动后仍然可用,通过系统表中的 RuntimeServices 指针访问。这些服务为操作系统提供了与固件交互的持久通道。

  • 变量服务:GetVariable() / SetVariable() — 读写 UEFI 非易失性变量,用于存储启动配置、引导选项等。
  • 时间服务:GetTime() / SetTime() — 获取/设置硬件实时时钟。
  • 复位服务:ResetSystem() — 冷复位、热复位或关机。
  • 虚拟地址映射:SetVirtualAddressMap() — 操作系统调用此函数将运行时服务代码从物理地址映射到虚拟地址空间。
注意事项: 在 ARM64 平台上,运行时服务通常以 虚拟地址模式 运行。操作系统在调用 SetVirtualAddressMap() 后,所有运行时服务指针将转换为虚拟地址。EFI Stub 在传递 DTB 时,必须确保 DTB 地址在 ExitBootServices() 后仍然有效(通常位于运行时保留内存区域)。

3.4 配置表(Configuration Table)

配置表是 UEFI 系统表中的一个数组,每个条目包含一个 GUID 和一个指针。操作系统通过 GUID 查找对应的数据结构。在 ARM64 设备树引导中,配置表是 DTB 传递的核心机制。

配置表条目的数据结构定义如下:

typedef struct {
    EFI_GUID  VendorGuid;      // 数据结构的唯一标识符
    VOID      *VendorTable;    // 指向数据结构的指针
} EFI_CONFIGURATION_TABLE;

常见的配置表 GUID 包括:

GUID 名称 GUID 值 对应数据结构
ACPI 表 GUID 8868E871-E4F1-11D3-BC22-0080C73C8881 RSDP(Root System Description Pointer)
SMBIOS 表 GUID EB9D2D31-2D88-11D3-9A16-0090273FC14D SMBIOS Entry Point
设备树表 GUID B1B621D5-F19C-41A5-830B-D9152C69AABE Flattened Device Tree (DTB)
UEFI 内存属性表 GUID 6C9D7E8B-7B1A-4F2A-93A4-6B9B2C3D1E0F EFI_MEMORY_ATTRIBUTES_TABLE
EFI Stub 通过配置表获取 DTB 流程 EFI Stub 通过配置表获取 DTB 流程 1. 获取 SystemTable 入口参数传递 2. 遍历配置表数组 匹配 DTB GUID 3. 获取 DTB 指针 VendorTable → DTB 4. 解析 DTB 获取硬件拓扑 关键代码路径 (Linux 内核 EFI Stub) efi_system_table->ConfigurationTable[] 数组遍历 匹配 guid == EFI_DTB_TABLE_GUID → 获取 dtb_addr → 调用 early_init_dt_scan()

3.5 设备树配置表 GUID 与内核匹配

Linux 内核在 drivers/firmware/efi/efi.c 中定义了设备树配置表的 GUID:

#define EFI_DTB_TABLE_GUID \
    EFI_GUID(0xb1b621d5, 0xf19c, 0x41a5, \
             0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xbe)

在 EFI Stub 引导过程中,内核通过以下步骤获取 DTB:

  1. 从入口参数获取 efi_system_table 指针。
  2. 访问 efi_system_table->NumberOfTableEntries 和 efi_system_table->ConfigurationTable。
  3. 遍历配置表数组,比较每个条目的 VendorGuid 是否等于 EFI_DTB_TABLE_GUID。
  4. 匹配成功后,将 VendorTable 指针转换为 DTB 物理地址,传递给内核早期启动代码。
调试技巧: 如果设备树未正确传递,可以在 UEFI Shell 中使用 dmpstore 命令查看配置表内容,或在内核启动参数中添加 efi=debug 来打印配置表 GUID 列表。

3.6 本章小结

  • UEFI 系统表是固件与操作系统之间的核心接口,包含启动服务、运行时服务和配置表。
  • 启动服务仅在 ExitBootServices() 之前可用,EFI Stub 利用它完成内存映射和协议查找。
  • 运行时服务在操作系统运行期间持续可用,用于变量、时间和复位操作。
  • 配置表是传递标准数据结构(包括设备树 DTB)的机制,通过 GUID 唯一标识。
  • ARM64 设备树传递路径:UEFI 固件 → 配置表(DTB GUID)→ EFI Stub 遍历匹配 → 内核 DTB 解析器。