BIOS BDS 阶段详解

目录

  1. BDS 阶段概述
  2. BDS 在 UEFI 启动流程中的位置
  3. DXE 到 BDS 的移交
  4. 控制台初始化
  5. 引导策略与 Boot Manager
  6. UEFI 引导选项
  7. 固件启动菜单(Firmware Boot Menu)
  8. 引导设备枚举与排序
  9. 顺序引导(Boot Order)处理流程
  10. 网络引导(PXE/HTTP Boot)
  11. 安全启动在 BDS 阶段
  12. UEFI 驱动程序加载与管理
  13. 固件配置界面(Setup)
  14. Boot Manager 内部数据结构
  15. Boot Manager 协议与编程接口
  16. 退出启动服务(ExitBootServices)
  17. BDS 与操作系统引导加载程序交接
  18. EDK2 BDS 实现分析
  19. 常见 BDS 问题与调试
  20. 参考资料

1. BDS 阶段概述

1.1 什么是 BDS

BDS(Boot Device Selection)是 UEFI 固件启动流程中的第三个主要阶段,紧随 DXE(Driver Execution Environment)之后。BDS 的职责是:

  • 初始化控制台设备(键盘、鼠标、显示输出)
  • 加载并执行平台特定的策略(固件菜单、启动策略)
  • 遍历引导选项列表(Boot Order),尝试引导操作系统
  • 管理 UEFI 驱动程序(可选驱动加载)
  • 实现安全启动策略验证
UEFI 固件阶段:

  SEC ──→ PEI ──→ DXE ──→ BDS ──→ TSL ──→ RT
   │        │        │       │       │       │
  安全    预初始   驱动执   引导设   短暂    运行时
  阶段    化阶段   行环境   备选择   系统
                         │
                         ├── DXE: 初始化所有驱动程序
                         │     建立 UEFI 系统表
                         │     提供所有 Boot Services
                         │
                         └── BDS: 控制台初始化
                                Boot Manager 执行
                                引导操作系统

1.2 BDS 的设计目标

目标 说明
用户交互 提供固件设置界面和启动菜单
自动引导 按照配置的引导顺序尝试每个设备
策略驱动 引导选择策略由 NVRAM 中的配置决定,而非硬编码
设备发现 发现所有可引导设备(本地磁盘、网络、USB)
驱动程序加载 按需加载可选 UEFI 驱动程序
安全策略执行 在引导前验证加载项的签名

1.3 BDS 阶段的关键特征

  • 完整内存可用:DRAM 已初始化完毕,可随意分配
  • 完整 UEFI 系统表BootServicesRuntimeServicesSystem Table 全部就绪
  • UEFI 协议可用:文件系统、图形输出、块 IO 等协议
  • 全彩色图形输出:UEFI GOP(Graphics Output Protocol)
  • 用户输入:键盘、鼠标、触摸输入
  • NVRAM 可读写:引导配置持久化存储

2. BDS 在 UEFI 启动流程中的位置

2.1 完整的 UEFI 启动阶段时序

安全(Security Phase)
  │ [~0-50ms]
  ├── 处理 Reset Vector
  ├── 建立 Cache-as-RAM (CAR)
  └── 验证 PEI Core 签名
       │
       ▼
预初始化(Pre-EFI Initialization)
  │ [~50-300ms]
  ├── 初始化系统内存 (DRAM)
  ├── 创建 HOB 列表
  └── 移交控制权给 DXE
       │
       ▼
驱动执行环境(Driver Execution Environment)
  │ [~100-1000ms]
  ├── DXE Core 初始化
  ├── 分派所有 DXE 驱动程序
  │   ├── 芯片组驱动
  │   ├── PCI 总线枚举
  │   ├── ACPI 表生成
  │   ├── SMBIOS 表生成
  │   ├── 控制台驱动 (Serial, GOP)
  │   └── 存储驱动 (SATA, NVMe, USB)
  ├── 创建 UEFI 系统表
  └── 安装 DXE 就绪通知 → 触发 BDS
       │
       ▼
引导设备选择(Boot Device Selection)  ← 本文重点
  │ [~100ms - 30s+]
  ├── 控制台初始化 (ConIn, ConOut, StdErr)
  ├── 执行 Boot Manager
  ├── 显示固件菜单(如用户按键)
  ├── 枚举引导设备
  ├── 按 BootOrder 遍历引导选项
  └── 启动引导加载程序
       │
       ▼
短暂系统加载(Transient System Load)
  │
  ├── 引导加载程序执行
  ├── ExitBootServices() 调用
  └── OS 接管
       │
       ▼
运行时(Runtime)

     [时间轴: 上电 → OS 加载, 总计 1-30 秒]

2.2 DXE → BDS 移交机制

DXE 阶段完成后,通过 EFI_EVENT_GROUP_READY_TO_BOOT 事件触发 BDS 入口:

// DXE Core 在完成所有驱动分派后触发 BDS 的机制

VOID
DxeCoreComplete(
    VOID
)
{
    //
    // 1. 所有 DXE 驱动程序已分派完毕
    //

    //
    // 2. 触发 ReadyToBoot 事件组
    //
    //    任何注册了这个事件的回调都会被执行
    //    BDS 入口通常会监听此事件
    //
    gBS->SignalEvent(gEfiEventReadyToBoot);

    //
    // 3. 调用 BDS 入口 (由平台 BDS 库实现)
    //
    //    BDS 从这个时候开始接管控制权
    //
    PlatformBds();

    //
    // BDS 不应该返回 (如果 BDS 返回了, 进入死循环)
    //
    CpuDeadLoop();
}

2.3 UEFI 系统表 —— BDS 的运行时环境

BDS 阶段运行时,以下 UEFI 基础设施已完全就绪:

UEFI 系统表 (gST):                        UEFI 启动服务 (gBS):
  ┌──────────────────────┐                ┌──────────────────────┐
  │ Hdr                  │                │ Hdr                  │
  │ FirmwareVendor       │                │ RaiseTPL             │
  │ FirmwareRevision     │                │ RestoreTPL           │
  │ ConsoleInHandle      │◄── ConIn       │ AllocatePages        │
  │ ConIn                │◄── ────┘       │ FreePages            │
  │ ConsoleOutHandle     │◄── ConOut      │ AllocatePool         │
  │ ConOut               │◄── ────┘       │ FreePool             │
  │ StandardErrorHandle  │◄── StdErr      │ CreateEvent          │
  │ StdErr               │◄── ────┘       │ SetTimer             │
  │ RuntimeServices      │                │ WaitForEvent         │
  │ BootServices         │                │ SignalEvent          │
  │ NumberOfTableEntries │                │ CloseEvent           │
  │ ConfigurationTable[] │                │ InstallProtocolInterface │
  └──────────────────────┘                │ ConnectController    │
                                          │ DisconnectController │
  UEFI 运行时服务 (gRT):                   │ LoadImage            │
  ┌──────────────────────┐                │ StartImage           │
  │ GetTime              │                │ Exit                 │
  │ SetTime              │                │ ExitBootServices     │
  │ GetVariable          │                │ ...                  │
  │ SetVariable          │                └──────────────────────┘
  │ GetNextVariableName  │
  │ SetVirtualAddressMap │                NVRAM 变量:
  │ ...                  │                  BootOrder, Boot####,
  └──────────────────────┘                  DriverOrder, Driver####,
                                            Timeout, PlatformLang,
                                            ConIn, ConOut, StdErr...

3. DXE 到 BDS 的移交

3.1 BDS 入口函数

BDS 阶段的入口通常由 PlatformBds()BdsEntry() 函数开始:

// EDK2 中的 BDS 入口 (MdeModulePkg/Universal/BdsDxe/BdsEntry.c)

EFI_STATUS
EFIAPI
BdsEntry(
    IN EFI_BOOT_SERVICES  *BootServices
)
{
    EFI_STATUS  Status;

    DEBUG((EFI_D_INFO, "[BDS] BDS Entry Point\n"));

    // ═══════════════════════════════════════════
    // 阶段 1: BDS 核心初始化
    // ═══════════════════════════════════════════

    // 1.1 初始化 BDS 内部数据结构
    InitializeBdsData();

    // 1.2 初始化控制台
    //
    //   ConIn  = 输入控制台 (键盘等)
    //   ConOut = 输出控制台 (显示设备)
    //   StdErr = 标准错误 (串口/显示)
    //
    Status = InitializeConsole();
    if (EFI_ERROR(Status)) {
        DEBUG((EFI_D_ERROR, "[BDS] Console init failed: %r\n", Status));
    }

    // ═══════════════════════════════════════════
    // 阶段 2: 设置默认引导选项
    // ═══════════════════════════════════════════

    // 确保至少有一个引导选项存在
    // 如果 NVRAM 为空,创建默认引导选项
    Status = SetDefaultBootCfgIfNeeded();

    // ═══════════════════════════════════════════
    // 阶段 3: 处理按键和超时
    // ═══════════════════════════════════════════

    // 3.1 检查是否用户要求进入设置
    if (CheckUserRequestedSetup()) {
        EnterSetupMenu();           // 进入固件设置界面
    }

    // 3.2 检查是否用户要求进入启动菜单
    if (CheckUserRequestedBootMenu()) {
        EnterBootMenu();            // 显示引导设备菜单
    }

    // 3.3 等待超时或用户按键
    //
    //   显示 "Press F2 for Setup, F12 for Boot Menu..."
    //   在 Timeout 秒内检查按键
    //
    WaitForBootTimeout();

    // ═══════════════════════════════════════════
    // 阶段 4: 执行引导
    // ═══════════════════════════════════════════

    // 4.1 调用 Boot Manager 引导
    Status = BdsBootManager();

    // 4.2 如果所有引导选项都失败
    if (EFI_ERROR(Status)) {
        // 显示 "No bootable device" 错误
        DisplayNoBootableDevice();
    }

    // ═══════════════════════════════════════════
    // 阶段 5: 等待用户干预
    // ═══════════════════════════════════════════

    // 如果引导失败,进入等待循环
    // 用户可以选择进入设置或重试
    while (TRUE) {
        // 等待用户按键
        // F2 → 进入设置
        // Enter → 重试引导
        // ...
    }
}

3.2 BDS 核心初始化

// BDS 内部数据结构初始化

typedef struct {
    //
    // 控制台句柄
    //
    EFI_HANDLE              ConsoleInHandle;
    EFI_HANDLE              ConsoleOutHandle;
    EFI_HANDLE              StandardErrorHandle;

    //
    // 控制台设备路径
    //
    EFI_DEVICE_PATH_PROTOCOL  *ConInDevicePath;
    EFI_DEVICE_PATH_PROTOCOL  *ConOutDevicePath;
    EFI_DEVICE_PATH_PROTOCOL  *StdErrDevicePath;

    //
    // 引导选项列表
    //
    LIST_ENTRY              BootOptionList;     // 所有 Boot#### 选项
    LIST_ENTRY              DriverOptionList;   // 所有 Driver#### 选项

    //
    // 当前引导状态
    //
    BOOLEAN                 BootAttempted;
    BOOLEAN                 SetupEntered;
    BOOLEAN                 BootMenuEntered;

    //
    // 时间配置
    //
    UINT16                  BootTimeOut;        // 超时秒数 (从 NVRAM 读取)
    BOOLEAN                 BootMenuPresent;

} BDS_PRIVATE_DATA;

3.3 超时处理

// BDS 超时处理逻辑

#define DEFAULT_BOOT_TIMEOUT    5   // 默认 5 秒

VOID
WaitForBootTimeout(
    VOID
)
{
    EFI_STATUS  Status;
    UINT16      Timeout;
    UINTN       Index;
    EFI_EVENT   TimerEvent;
    EFI_INPUT_KEY Key;

    // 1. 从 NVRAM 读取 Timeout 变量
    UINTN  DataSize = sizeof(Timeout);
    Status = gRT->GetVariable(
        L"Timeout",
        &gEfiGlobalVariableGuid,
        NULL,
        &DataSize,
        &Timeout
    );
    if (EFI_ERROR(Status)) {
        Timeout = DEFAULT_BOOT_TIMEOUT;
    }

    // 2. 如果 Timeout = 0, 立即引导 (无等待)
    if (Timeout == 0) {
        return;
    }

    // 3. 如果 Timeout = 0xFFFF, 无限等待用户
    if (Timeout == 0xFFFF) {
        WaitForeverForUserInput();
        return;
    }

    // 4. 创建定时器事件
    Status = gBS->CreateEvent(
        EVT_TIMER,
        TPL_CALLBACK,
        NULL,
        NULL,
        &TimerEvent
    );

    // 设置定时器
    gBS->SetTimer(TimerEvent, TimerRelative, Timeout * ONE_SECOND);

    // 显示倒计时信息
    Print(L"Press F2 for Setup, F12 for Boot Menu in %d seconds...", Timeout);

    // 5. 等待定时器或按键
    while (TRUE) {
        UINTN  WaitCount = 1;
        EFI_EVENT WaitList[] = { TimerEvent, gST->ConIn->WaitForKey };

        Status = gBS->WaitForEvent(2, WaitList, &Index);

        if (Index == 0) {
            // 定时器超时 → 自动引导
            break;
        } else if (Index == 1) {
            // 用户按键
            gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);

            if (Key.ScanCode == SCAN_F2) {
                EnterSetupMenu();
            } else if (Key.ScanCode == SCAN_F12) {
                EnterBootMenu();
            } else {
                // 任何其他按键 → 立即引导
                break;
            }
        }
    }

    gBS->CloseEvent(TimerEvent);
}

4. 控制台初始化

4.1 控制台协议

UEFI 定义了三种标准控制台:

控制台 NVRAM 变量 协议 用途
ConIn (控制台输入) ConIn EFI_SIMPLE_TEXT_INPUT_PROTOCOL 键盘、鼠标输入
ConOut (控制台输出) ConOut EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 文本屏幕输出
StdErr (标准错误) ErrOut EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 错误信息输出

4.2 控制台初始化流程

// BDS 控制台初始化

EFI_STATUS
InitializeConsole(
    VOID
)
{
    EFI_STATUS  Status;
    EFI_DEVICE_PATH_PROTOCOL  *ConInDevicePath;
    EFI_DEVICE_PATH_PROTOCOL  *ConOutDevicePath;
    EFI_DEVICE_PATH_PROTOCOL  *ErrOutDevicePath;

    // ═══════════════════════════════════════════
    // 1. 从 NVRAM 读取控制台设备路径配置
    // ═══════════════════════════════════════════

    //
    // ConIn 变量存储了输入控制台的设备路径列表
    // 例如:
    //   "UsbKeyboard(0x1234,0x5678)/UsbKeyboard(...)"
    //   或 "Serial(9600,8,N,1)"
    //
    ConInDevicePath = GetConsoleDevicePath(L"ConIn");
    ConOutDevicePath = GetConsoleDevicePath(L"ConOut");
    ErrOutDevicePath = GetConsoleDevicePath(L"ErrOut");

    // ═══════════════════════════════════════════
    // 2. 连接控制台设备
    // ═══════════════════════════════════════════

    //
    // 让 UEFI 驱动框架连接这些设备路径
    // 这会导致对应的驱动程序绑定到设备上
    //

    // 连接输入控制台 (键盘等)
    if (ConInDevicePath != NULL) {
        Status = gBS->ConnectController(ConInDevicePath, NULL, NULL, TRUE);
        if (EFI_ERROR(Status)) {
            // 尝试使用默认控制台 (PS/2 键盘或串口)
            Status = ConnectDefaultConsoleIn();
        }
    }

    // 连接输出控制台 (GOP 显示设备)
    if (ConOutDevicePath != NULL) {
        Status = gBS->ConnectController(ConOutDevicePath, NULL, NULL, TRUE);
        if (EFI_ERROR(Status)) {
            Status = ConnectDefaultConsoleOut();
        }
    }

    // 连接标准错误控制台
    if (ErrOutDevicePath != NULL) {
        gBS->ConnectController(ErrOutDevicePath, NULL, NULL, TRUE);
    }

    // ═══════════════════════════════════════════
    // 3. 如果 NVRAM 没有配置, 自动检测控制台
    // ═══════════════════════════════════════════

    if (ConInDevicePath == NULL || ConOutDevicePath == NULL) {
        Status = AutoDetectConsole();
    }

    // ═══════════════════════════════════════════
    // 4. 更新 UEFI 系统表中的控制台指针
    // ═══════════════════════════════════════════

    //
    // gST->ConIn, gST->ConOut, gST->StdErr
    // 现在指向实际的控制台设备协议
    //

    DEBUG((
        EFI_D_INFO,
        "[BDS] Console initialized: ConIn=%p, ConOut=%p, StdErr=%p\n",
        gST->ConIn, gST->ConOut, gST->StdErr
    ));

    // ═══════════════════════════════════════════
    // 5. 显示启动徽标 (Splash Screen / OEM Logo)
    // ═══════════════════════════════════════════

    ShowStartupLogo();

    return EFI_SUCCESS;
}

4.3 图形输出初始化

现代 UEFI 固件使用 GOP(Graphics Output Protocol)进行图形显示:

// GOP 初始化与启动徽标显示

EFI_STATUS
InitializeGraphicsConsole(
    VOID
)
{
    EFI_STATUS                    Status;
    EFI_HANDLE                    *HandleBuffer;
    UINTN                         HandleCount;
    EFI_GRAPHICS_OUTPUT_PROTOCOL  *Gop;

    // 1. 查找所有支持 GOP 的设备
    Status = gBS->LocateHandleBuffer(
        ByProtocol,
        &gEfiGraphicsOutputProtocolGuid,
        NULL,
        &HandleCount,
        &HandleBuffer
    );

    if (EFI_ERROR(Status) || HandleCount == 0) {
        // 没有 GOP 设备, 使用文本模式 (UGA 或串口)
        return EFI_UNSUPPORTED;
    }

    // 2. 使用第一个 GOP 设备
    Status = gBS->HandleProtocol(
        HandleBuffer[0],
        &gEfiGraphicsOutputProtocolGuid,
        (VOID **)&Gop
    );

    if (EFI_ERROR(Status)) {
        return Status;
    }

    // 3. 查询当前图形模式信息
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION  *Info;
    UINTN                                 SizeOfInfo;

    Status = Gop->QueryMode(Gop, Gop->Mode->Mode, &SizeOfInfo, &Info);
    if (EFI_ERROR(Status)) {
        // 查询失败, 使用当前模式
        Info = Gop->Mode->Info;
    }

    DEBUG((
        EFI_D_INFO,
        "[BDS] GOP Mode: %dx%d, Format=%d\n",
        Info->HorizontalResolution,
        Info->VerticalResolution,
        Info->PixelFormat
    ));

    // 4. 设置文本输出模式对应到 GOP
    //    UEFI Simple Text Output 在 GOP 上使用固定字体
    //    通常使用 80x25 或 100x31 字符模式

    // 5. 显示启动画面 Logo
    DisplayOemLogo(Gop, Info);

    gBS->FreePool(HandleBuffer);

    return EFI_SUCCESS;
}

// 显示 OEM 启动徽标 (BMP 或 JPEG)
EFI_STATUS
DisplayOemLogo(
    IN EFI_GRAPHICS_OUTPUT_PROTOCOL  *Gop,
    IN EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *ModeInfo
)
{
    VOID      *LogoData;
    UINTN     LogoSize;
    EFI_STATUS Status;

    // 1. 从 FV 或 NVRAM 中读取徽标图像
    Status = GetSectionFromFv(
        &gOemLogoFileGuid,
        EFI_SECTION_RAW,
        0,
        &LogoData,
        &LogoSize
    );

    if (EFI_ERROR(Status)) {
        return Status;
    }

    // 2. 解码并渲染徽标 (居中显示)
    //
    // 处理: BMP → 转换为 GOP 像素格式
    //        JPEG/PNG → 使用图像解码协议
    //
    Status = RenderImageToGop(
        Gop,
        LogoData, LogoSize,
        (ModeInfo->HorizontalResolution - LogoWidth) / 2,
        (ModeInfo->VerticalResolution - LogoHeight) / 2
    );

    FreePool(LogoData);

    return Status;
}

5. 引导策略与 Boot Manager

5.1 Boot Manager 的角色

UEFI Boot Manager 是 BDS 阶段的核心组件,负责执行引导策略:

Boot Manager 逻辑:

  BDS Entry
    │
    ├── 1. 处理平台特定初始化
    │     ├── 芯片组/平台后初始化
    │     └── 连接平台设备
    │
    ├── 2. 加载驱动程序选项 (Driver####)
    │     ├── 从 NVRAM 读取 DriverOrder
    │     ├── 按顺序加载每个 Driver#### 选项
    │     └── 连接驱动对应的设备
    │
    ├── 3. 处理固件设置入口
    │     ├── 检查 "F2" 按键
    │     ├── 检查 "Del" 按键
    │     ├── 检查 Setup 标志变量
    │     └── 如果触发 → 进入固件设置界面
    │
    ├── 4. 执行引导 (Boot Manager)
    │     ├── 从 NVRAM 读取 BootOrder
    │     ├── 对每个 Boot####:
    │     │   ├── 连接设备路径
    │     │   ├── 验证安全启动签名
    │     │   ├── 尝试加载 EFI 应用程序
    │     │   └── 如果成功 → 启动
    │     └── 如果全部失败 → 错误处理
    │
    └── 5. 引导失败恢复
          └── 等待用户干预

5.2 UEFI Boot Manager 规范

根据 UEFI 规范,Boot Manager 的定义如下:

The UEFI Boot Manager is a firmware policy engine that is
active after the DXE phase is complete. The Boot Manager
determines which UEFI drivers and UEFI applications should
be loaded and executed. The Boot Manager’s behavior is
controlled by a set of NVRAM variables.

— UEFI Specification, Chapter 3: Boot Manager

5.3 Boot Manager 执行循环

// Boot Manager 主循环 (简化版)

EFI_STATUS
EFIAPI
BdsBootManager(
    VOID
)
{
    EFI_STATUS            Status;
    UINT16                *BootOrder;
    UINTN                 BootOrderSize;
    UINTN                 BootOrderCount;
    BOOLEAN               BootSucceeded;

    BootSucceeded = FALSE;

    // ═══════════════════════════════════════════
    // 1. 从 NVRAM 读取 BootOrder 变量
    // ═══════════════════════════════════════════

    //
    // BootOrder 是一个 UINT16 数组, 例如:
    //   { 0x0001, 0x0003, 0x0000, 0x0002 }
    //   表示引导顺序为 Boot0001 → Boot0003 → Boot0000 → Boot0002
    //
    BootOrder = BdsReadBootOrder(&BootOrderSize);
    BootOrderCount = BootOrderSize / sizeof(UINT16);

    if (BootOrderCount == 0) {
        DEBUG((EFI_D_ERROR, "[BDS] BootOrder is empty!\n"));
        return EFI_NOT_FOUND;
    }

    // ═══════════════════════════════════════════
    // 2. 按顺序遍历所有引导选项
    // ═══════════════════════════════════════════

    for (UINTN Index = 0; Index < BootOrderCount; Index++) {
        UINT16        BootCurrent = BootOrder[Index];
        EFI_HANDLE    ImageHandle;
        BDS_BOOT_OPTION *BootOption;

        DEBUG((EFI_D_INFO, "[BDS] Trying Boot%04x...\n", BootCurrent));

        // 2.1 读取 Boot#### 变量
        Status = BdsReadBootOption(BootCurrent, &BootOption);
        if (EFI_ERROR(Status)) {
            continue;
        }

        // 2.2 连接设备路径
        //
        //     Boot#### 包含了设备路径, 例如:
        //       "PciRoot(0)/Pci(0x1F,0x2)/Sata(0x0,0x0)/HD(1,GPT,...)/\EFI\BOOT\BOOTX64.EFI"
        //
        //     需要连接设备路径上的所有控制器
        //
        Status = gBS->ConnectController(BootOption->DeviceHandle, NULL, NULL, TRUE);
        if (EFI_ERROR(Status)) {
            // 尝试枚举所有设备
            BdsConnectAll();
        }

        // 2.3 安全启动验证
        //
        //     如果安全启动已启用, 验证 BootOption 的签名
        //
        if (IsSecureBootEnabled()) {
            Status = SecureBootVerify(BootOption);
            if (EFI_ERROR(Status)) {
                DEBUG((EFI_D_ERROR, "[BDS] Secure Boot rejects Boot%04x\n", BootCurrent));
                continue;
            }
        }

        // 2.4 加载并启动 EFI 映像
        //
        //     LoadImage: 从设备读取 EFI 映像到内存
        //     StartImage: 执行 EFI 映像
        //
        Status = gBS->LoadImage(
            FALSE,              // BootPolicy = FALSE (使用设备路径)
            gImageHandle,       // 调用者映像句柄
            BootOption->DevicePath,  // EFI 映像路径
            NULL,               // 源缓冲区 (NULL = 从设备读)
            0,                  // 源大小 (0 = 自动)
            &ImageHandle        // [输出] 加载的映像句柄
        );

        if (EFI_ERROR(Status)) {
            DEBUG((EFI_D_WARN, "[BDS] LoadImage failed: %r\n", Status));
            continue;
        }

        // 2.5 启动映像
        Status = gBS->StartImage(
            ImageHandle,
            &BootOption->ExitDataSize,
            &BootOption->ExitData
        );

        if (Status == EFI_SUCCESS) {
            BootSucceeded = TRUE;
            break;
        }

        // 如果映像返回错误, 尝试下一个
        DEBUG((EFI_D_WARN, "[BDS] StartImage returned: %r\n", Status));
    }

    if (!BootSucceeded) {
        DEBUG((EFI_D_ERROR, "[BDS] No bootable device found!\n"));
        return EFI_NOT_FOUND;
    }

    return EFI_SUCCESS;
}

6. UEFI 引导选项

6.1 引导选项结构

UEFI 引导选项存储在 NVRAM 变量中,变量名为 Boot####(十六进制数字):

// Boot#### 变量中存储的数据结构
//
// 变量名: "Boot0001", "Boot0002", ..., "Boot####"
// GUID:    EFI_GLOBAL_VARIABLE (8BE4DF61-93CA-11d2-AA0D-00E098032B8C)

typedef struct {
    //
    // 引导选项属性标志
    //
    UINT32                Attributes;
    // Bits:
    //   0x01 = LOAD_OPTION_ACTIVE      (选项已激活)
    //   0x02 = LOAD_OPTION_FORCE_RECONNECT (强制重新连接设备)
    //   0x04 = LOAD_OPTION_HIDDEN       (在启动菜单中隐藏)
    //   0x08 = LOAD_OPTION_CATEGORY     (类别: 0=引导, 1=应用程序)

    //
    // 设备路径列表长度
    //
    UINT16                FilePathListLength;

    //
    // 描述 (以 NUL 结尾的 UCS-2 字符串)
    //
    // CHAR16                Description[];
    //
    // 设备路径列表
    //
    // EFI_DEVICE_PATH_PROTOCOL  FilePathList[];
    //
    // 可选数据
    //
    // UINT8                 OptionalData[];
    //
} EFI_LOAD_OPTION;

6.2 从 NVRAM 读取引导选项

// 读取 Boot#### 引导选项

EFI_STATUS
BdsReadBootOption(
    IN  UINT16            BootCurrent,    // 如 0x0001
    OUT BDS_BOOT_OPTION   **BootOption
)
{
    EFI_STATUS  Status;
    CHAR16      VariableName[20];
    UINT8       *VariableData;
    UINTN       DataSize;
    EFI_LOAD_OPTION       *LoadOption;
    BDS_BOOT_OPTION       *Option;

    // 1. 构造变量名: "Boot0001"
    UnicodeSPrint(VariableName, sizeof(VariableName), L"Boot%04x", BootCurrent);

    // 2. 读取 NVRAM 变量
    DataSize = 0;
    Status = gRT->GetVariable(
        VariableName,
        &gEfiGlobalVariableGuid,
        NULL,
        &DataSize,
        NULL
    );
    if (Status != EFI_BUFFER_TOO_SMALL) {
        return EFI_NOT_FOUND;
    }

    VariableData = (UINT8 *)AllocatePool(DataSize);
    Status = gRT->GetVariable(
        VariableName,
        &gEfiGlobalVariableGuid,
        NULL,
        &DataSize,
        VariableData
    );
    if (EFI_ERROR(Status)) {
        FreePool(VariableData);
        return Status;
    }

    // 3. 解析 EFI_LOAD_OPTION
    LoadOption = (EFI_LOAD_OPTION *)VariableData;

    Option = (BDS_BOOT_OPTION *)AllocateZeroPool(sizeof(BDS_BOOT_OPTION));
    Option->OptionNumber = BootCurrent;
    Option->Attributes   = LoadOption->Attributes;
    Option->Description  = AllocateCopyPool(
        StrSize((CHAR16 *)(LoadOption + 1)),
        (CHAR16 *)(LoadOption + 1)
    );

    // 设备路径在描述字符串之后
    Option->DevicePath = (EFI_DEVICE_PATH_PROTOCOL *)
        ((UINT8 *)(LoadOption + 1) + StrSize(Option->Description));

    // 可选数据在设备路径之后
    Option->OptionalData = NULL;
    UINTN FilePathLen = LoadOption->FilePathListLength;
    UINTN TotalLen = sizeof(EFI_LOAD_OPTION) + StrSize(Option->Description)
                     + FilePathLen;
    if (DataSize > TotalLen) {
        Option->OptionalData = VariableData + TotalLen;
        Option->OptionalDataSize = DataSize - TotalLen;
    }

    // 4. 检查活动状态
    Option->IsActive = (LoadOption->Attributes & LOAD_OPTION_ACTIVE) != 0;

    *BootOption = Option;

    return EFI_SUCCESS;
}

6.3 引导选项示例

引导选项在 NVRAM 中的典型布局:

  BootOrder = { 0001, 0003, 0000, 0002 }
                          ▲
                          └── 引导顺序: Boot0001 → Boot0003 → Boot0000 → Boot0002

  ─────────────────────────────────────────────────────────────────────

  Variable: Boot0001
    Attributes:       0x01 (ACTIVE)
    Description:      "Windows Boot Manager"
    DevicePath:       HD(1,GPT,1234-5678,...)/\EFI\Microsoft\Boot\bootmgfw.efi
    OptionalData:     (无)

  Variable: Boot0003
    Attributes:       0x01 (ACTIVE)
    Description:      "ubuntu"
    DevicePath:       HD(2,GPT,ABCD-EF01,...)/\EFI\ubuntu\shimx64.efi
    OptionalData:     (无)

  Variable: Boot0000
    Attributes:       0x01 (ACTIVE)
    Description:      "UEFI: SanDisk Extreme Pro"
    DevicePath:       USB(0x0781,0x5583)/HD(1,MBR,...)/\EFI\BOOT\BOOTX64.EFI

  Variable: Boot0002
    Attributes:       0x01 (ACTIVE)
    Description:      "UEFI PXE IPv4 Intel(R) Ethernet"
    DevicePath:       MAC(001122334455,0x1)/IPv4(0.0.0.0)

6.4 默认引导选项

系统首次启动或 NVRAM 被清空时,固件会自动创建默认引导选项:

// 创建默认引导选项

EFI_STATUS
SetDefaultBootCfgIfNeeded(
    VOID
)
{
    EFI_STATUS  Status;
    UINT16      *BootOrder;
    UINTN       DataSize;

    // 1. 检查 BootOrder 是否已存在
    DataSize = 0;
    Status = gRT->GetVariable(
        L"BootOrder",
        &gEfiGlobalVariableGuid,
        NULL,
        &DataSize,
        NULL
    );
    if (Status != EFI_BUFFER_TOO_SMALL) {
        // BootOrder 不存在 → 创建默认配置

        // 2. 按照标准 UEFI 规范创建默认引导选项
        //
        // 顺序:
        //   1. 可移动媒体引导 (Removable Media)
        //   2. UEFI 壳 (UEFI Shell)
        //   3. 网络引导 (PXE / HTTP)
        //
        // 具体实现: 枚举所有支持 Simple File System 的
        //           设备和网络设备, 创建对应的 Boot####
        //
        Status = EnumerateAndCreateDefaultBootOptions();
        if (EFI_ERROR(Status)) {
            DEBUG((EFI_D_ERROR, "[BDS] Failed to create default boot options\n"));
        }
    }

    return EFI_SUCCESS;
}

7. 固件启动菜单(Firmware Boot Menu)

7.1 启动菜单调用方式

用户进入固件菜单的常见方式:

  1. 按键进入
     开机时按特定键:
       F2        → 固件设置 (Setup)
       Del       → 固件设置 (Setup)
       F12       → 引导设备菜单 (Boot Menu)
       F10       → 引导设备菜单 (某些平台)
       Esc       → 引导菜单 (某些平台)
       F9        → 系统诊断

  2. 从 OS 重启进入
     Windows:     "高级启动" → "UEFI 固件设置"
     Linux:       systemctl reboot --firmware-setup
     macOS:       Option 键开机

  3. 启动失败自动进入
     多次引导失败 → 自动显示启动菜单

7.2 引导设备菜单实现

// 引导设备菜单 (Boot Menu)

EFI_STATUS
EnterBootMenu(
    VOID
)
{
    EFI_STATUS        Status;
    UINTN             SelectedIndex;
    BDS_BOOT_OPTION   **MenuOptions;
    UINTN             OptionCount;

    DEBUG((EFI_D_INFO, "[BDS] Entering Boot Menu\n"));

    // 1. 清除屏幕
    gST->ConOut->ClearScreen(gST->ConOut);

    // 2. 收集所有可引导的选项
    Status = CollectBootMenuOptions(&MenuOptions, &OptionCount);
    if (EFI_ERROR(Status)) {
        return Status;
    }

    // 3. 显示菜单界面
    while (TRUE) {
        // 3.1 显示标题
        Print(L"\n  Please select boot device:\n\n");

        // 3.2 列出所有选项
        for (UINTN i = 0; i < OptionCount; i++) {
            Print(L"    %d. %s\n", i + 1, MenuOptions[i]->Description);
        }
        Print(L"    \n");
        Print(L"    Q. Quit and continue boot\n");

        // 3.3 等待用户选择
        EFI_INPUT_KEY  Key;
        Status = gST->ConIn->ReadKeyStroke(gST->ConIn, &Key);

        if (Key.UnicodeChar >= '1' && Key.UnicodeChar <= '9') {
            SelectedIndex = Key.UnicodeChar - '1';
            if (SelectedIndex < OptionCount) {
                // 启动选中的引导选项
                Status = BootFromOption(MenuOptions[SelectedIndex]);
                break;
            }
        } else if (Key.UnicodeChar == 'q' || Key.UnicodeChar == 'Q') {
            // 退出菜单, 继续正常引导
            break;
        }
    }

    // 4. 释放资源
    for (UINTN i = 0; i < OptionCount; i++) {
        FreePool(MenuOptions[i]);
    }
    FreePool(MenuOptions);

    return EFI_SUCCESS;
}

// 收集所有可引导选项
EFI_STATUS
CollectBootMenuOptions(
    OUT BDS_BOOT_OPTION  ***Options,
    OUT UINTN            *Count
)
{
    UINT16          *BootOrder;
    UINTN           BootOrderSize;
    UINTN           BootOrderCount;
    BDS_BOOT_OPTION **BootOptions;
    UINTN           ValidCount = 0;

    // 读取 BootOrder
    BootOrder = BdsReadBootOrder(&BootOrderSize);
    BootOrderCount = BootOrderSize / sizeof(UINT16);

    BootOptions = (BDS_BOOT_OPTION **)
        AllocateZeroPool(BootOrderCount * sizeof(BDS_BOOT_OPTION *));

    for (UINTN i = 0; i < BootOrderCount; i++) {
        BDS_BOOT_OPTION *Option;

        Status = BdsReadBootOption(BootOrder[i], &Option);
        if (EFI_ERROR(Status)) continue;

        // 只包含激活的、非隐藏的引导选项
        if (Option->IsActive &&
            !(Option->Attributes & LOAD_OPTION_HIDDEN)) {
            BootOptions[ValidCount++] = Option;
        }
    }

    // 最后添加"UEFI Shell"选项 (如果存在)
    AddShellBootOption(&BootOptions, &ValidCount);

    *Options = BootOptions;
    *Count   = ValidCount;

    return EFI_SUCCESS;
}

7.3 固件设置界面(Setup Menu)

// 进入固件设置界面

VOID
EnterSetupMenu(
    VOID
)
{
    EFI_STATUS  Status;
    EFI_HANDLE  *HandleBuffer;
    UINTN       HandleCount;

    DEBUG((EFI_D_INFO, "[BDS] Entering Setup Menu\n"));

    // 1. 查找所有固件设置表单 (HII 数据库)
    //
    //    UEFI 使用 HII (Human Interface Infrastructure)
    //    来定义和管理固件配置界面
    //
    Status = gBS->LocateHandleBuffer(
        ByProtocol,
        &gEfiHiiDatabaseProtocolGuid,
        NULL,
        &HandleCount,
        &HandleBuffer
    );

    // 2. 查找 Setup 浏览器协议
    EFI_FORM_BROWSER2_PROTOCOL  *FormBrowser;
    Status = gBS->LocateProtocol(
        &gEfiFormBrowser2ProtocolGuid,
        NULL,
        (VOID **)&FormBrowser
    );

    if (EFI_ERROR(Status)) {
        DEBUG((EFI_D_ERROR, "[BDS] FormBrowser not found!\n"));
        return;
    }

    // 3. 设置标志: 正在设置中
    //    这样后续引导不会干扰
    SetSetupMode(TRUE);

    // 4. 调用 HII 浏览器
    //
    //    这会在图形/文本模式下显示完整的固件配置界面
    //    包括:
    //      - Main (系统信息, 日期时间)
    //      - Advanced (高级设置)
    //      - Security (安全设置, 密码)
    //      - Boot (引导配置)
    //      - Save & Exit (保存退出)
    //
    FormBrowser->SendForm(
        FormBrowser,
        HandleBuffer,           // HII 句柄列表
        HandleCount,            // 句柄数量
        NULL,                   // 默认 FormSet GUID (NULL = 所有)
        0,                      // 默认 Form ID
        NULL,                   // 默认 Question ID
        NULL                    // 回调函数
    );

    // 5. 退出设置, 继续 BDS 流程
    SetSetupMode(FALSE);

    DEBUG((EFI_D_INFO, "[BDS] Exiting Setup Menu\n"));

    FreePool(HandleBuffer);
}

8. 引导设备枚举与排序

8.1 设备枚举

BDS 阶段需要发现所有可能的引导设备:

// 枚举所有可引导设备

EFI_STATUS
EnumerateBootableDevices(
    VOID
)
{
    EFI_STATUS  Status;
    UINTN       HandleCount;
    EFI_HANDLE  *HandleBuffer;

    //
    // ═══════════════════════════════════════════
    // 1. 连接所有设备到驱动框架
    //
    //    调用 gBS->ConnectController 遍历所有未连接的
    //    设备句柄, 让驱动绑定到设备上
    //
    //    这会导致:
    //      - PCI 枚举已完成
    //      - USB 控制器初始化并枚举 USB 设备
    //      - NVMe/SATA 控制器初始化
    //      - 网络适配器初始化
    // ═══════════════════════════════════════════
    //

    BdsConnectAll();

    //
    // ═══════════════════════════════════════════
    // 2. 寻找支持 Simple File System Protocol 的设备
    //
    //    这是 UEFI 常用的引导接口:
    //    设备支持文件系统 → 可以存放 .efi 文件
    // ═══════════════════════════════════════════
    //

    Status = gBS->LocateHandleBuffer(
        ByProtocol,
        &gEfiSimpleFileSystemProtocolGuid,
        NULL,
        &HandleCount,
        &HandleBuffer
    );

    if (!EFI_ERROR(Status)) {
        for (UINTN i = 0; i < HandleCount; i++) {
            // 检查该设备是否有 EFI/BOOT/BOOTX64.EFI
            if (HasEfiBootFile(HandleBuffer[i])) {
                RegisterBootDevice(HandleBuffer[i],
                                   "UEFI OS", LoadOptionTypeBoot);
            }
        }
        FreePool(HandleBuffer);
    }

    //
    // ═══════════════════════════════════════════
    // 3. 寻找支持 Load File Protocol 的设备
    //
    //    这通常是网络引导 (PXE) 或远程引导
    // ═══════════════════════════════════════════
    //

    Status = gBS->LocateHandleBuffer(
        ByProtocol,
        &gEfiLoadFileProtocolGuid,
        NULL,
        &HandleCount,
        &HandleBuffer
    );

    if (!EFI_ERROR(Status)) {
        for (UINTN i = 0; i < HandleCount; i++) {
            RegisterBootDevice(HandleBuffer[i],
                               "UEFI PXE Network", LoadOptionTypeBoot);
        }
        FreePool(HandleBuffer);
    }

    //
    // ═══════════════════════════════════════════
    // 4. 寻找 UEFI Shell 设备
    // ═══════════════════════════════════════════
    //

    if (IsShellSupported()) {
        RegisterBootDevice(NULL, "UEFI Shell", LoadOptionTypeBoot);
    }

    return EFI_SUCCESS;
}

8.2 引导选项排序

// 按 BootOrder 对引导选项排序

INTN
EFIAPI
CompareBootOptions(
    IN CONST VOID  *Option1,
    IN CONST VOID  *Option2
)
{
    BDS_BOOT_OPTION  *BdsOption1 = *(BDS_BOOT_OPTION **)Option1;
    BDS_BOOT_OPTION  *BdsOption2 = *(BDS_BOOT_OPTION **)Option2;
    UINT16           *BootOrder;
    UINTN            BootOrderSize;
    UINTN            Index1, Index2;
    UINTN            i;

    // 读取 BootOrder 数组
    BootOrder = BdsReadBootOrder(&BootOrderSize);

    // 查找每个选项在 BootOrder 中的位置
    Index1 = BootOrderSize / sizeof(UINT16);  // 默认: 排到最后
    Index2 = BootOrderSize / sizeof(UINT16);

    for (i = 0; i < BootOrderSize / sizeof(UINT16); i++) {
        if (BootOrder[i] == BdsOption1->OptionNumber) {
            Index1 = i;
        }
        if (BootOrder[i] == BdsOption2->OptionNumber) {
            Index2 = i;
        }
    }

    FreePool(BootOrder);

    if (Index1 < Index2) return -1;
    if (Index1 > Index2) return 1;
    return 0;
}

9. 顺序引导(Boot Order)处理流程

9.1 完整引导循环

// 完整引导循环

EFI_STATUS
BdsBootManager(
    VOID
)
{
    EFI_STATUS    Status;
    BOOT_LOOP     LoopControl;
    EFI_BOOT_MODE BootMode;

    LoopControl = BootLoopContinue;

    while (LoopControl == BootLoopContinue) {
        BOOLEAN  BootAttempted = FALSE;
        UINT16   *BootOrder;
        UINTN    BootOrderCount;
        UINTN    Index = 0;

        // ═══════════════════════════════════════════
        // 重新读取 BootOrder (可能被设置程序修改)
        // ═══════════════════════════════════════════

        BootOrder = BdsReadBootOrder(&BootOrderCount);

        while (Index < BootOrderCount) {
            UINT16         BootCurrent;
            BDS_BOOT_OPTION *BootOption;
            EFI_HANDLE     ImageHandle;
            CHAR16         LoadOptionName[20];

            BootCurrent = BootOrder[Index++];

            // 读取引导选项
            Status = BdsReadBootOption(BootCurrent, &BootOption);
            if (EFI_ERROR(Status)) continue;

            // 检查选项是否已激活
            if (!BootOption->IsActive) {
                FreePool(BootOption);
                continue;
            }

            // 跳过隐藏的选项 (除非是从 OS 发起的 OS 恢复)
            if (BootOption->Attributes & LOAD_OPTION_HIDDEN) continue;

            // ═══════════════════════════════════════════
            // 连接引导设备
            // ═══════════════════════════════════════════

            //
            // 连接设备路径上的所有节点
            // 例如: PciRoot → PciBridge → SATA → HD
            // 确保设备可用
            //
            Status = gBS->ConnectController(
                BootOption->DeviceHandle,
                NULL,
                NULL,
                TRUE
            );

            // ═══════════════════════════════════════════
            // 安全启动验证
            // ═══════════════════════════════════════════

            if (IsSecureBootEnabled()) {
                Status = VerifyBootOptionSignature(BootOption);
                if (EFI_ERROR(Status)) {
                    DEBUG((
                        EFI_D_ERROR,
                        "[BDS] Secure Boot blocked Boot%04x\n",
                        BootCurrent
                    ));
                    FreePool(BootOption);
                    continue;
                }
            }

            // ═══════════════════════════════════════════
            // 加载映像
            // ═══════════════════════════════════════════

            Status = gBS->LoadImage(
                FALSE,                  // BootPolicy
                gImageHandle,
                BootOption->DevicePath,
                NULL,
                0,
                &ImageHandle
            );

            if (EFI_ERROR(Status)) {
                // 加载失败, 尝试下一个
                DEBUG((
                    EFI_D_WARN,
                    "[BDS] Boot%04x LoadImage: %r\n",
                    BootCurrent, Status
                ));
                FreePool(BootOption);
                continue;
            }

            // ═══════════════════════════════════════════
            // 启动映像
            // ═══════════════════════════════════════════

            BootAttempted = TRUE;

            //
            // 设置 BootCurrent 变量
            // 引导加载程序可以读取此变量知道自己是哪个选项启动的
            //
            UnicodeSPrint(LoadOptionName, sizeof(LoadOptionName),
                          L"Boot%04x", BootCurrent);
            gRT->SetVariable(
                L"BootCurrent",
                &gEfiGlobalVariableGuid,
                EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                sizeof(UINT16),
                &BootCurrent
            );

            //
            // 调用 StartImage 执行引导加载程序
            //
            Status = gBS->StartImage(
                ImageHandle,
                &BootOption->ExitDataSize,
                &BootOption->ExitData
            );

            if (Status == EFI_SUCCESS) {
                // 引导加载程序成功返回意味着 ExitBootServices 已调用
                // BDS 不应该到这里
                LoopControl = BootLoopExit;
                break;
            }

            //
            // 引导加载程序返回了错误
            // 可能是 "BootNext" 一次性引导, 需要清理
            //
            if (BootCurrent == GetBootNext()) {
                // 清除 BootNext 变量
                gRT->SetVariable(
                    L"BootNext",
                    &gEfiGlobalVariableGuid,
                    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                    0,
                    NULL
                );
            }

            FreePool(BootOption);
        }

        FreePool(BootOrder);

        //
        // 如果所有引导选项都失败
        //
        if (!BootAttempted) {
            LoopControl = BootLoopContinue;
            // 显示错误并等待用户输入
            DisplayBootError();
            // 如果有按键进入 Setup, 重置 LoopControl = BootLoopContinue
            // 如果用户重启, 退出
        }
    }

    return EFI_SUCCESS;
}

9.2 BootNext — 一次性引导

UEFI 支持 BootNext 变量,用于实现一次性引导:

// BootNext 处理

EFI_STATUS
HandleBootNext(
    VOID
)
{
    EFI_STATUS  Status;
    UINT16      BootNext;
    UINTN       DataSize;

    // 1. 读取 BootNext 变量
    DataSize = sizeof(BootNext);
    Status = gRT->GetVariable(
        L"BootNext",
        &gEfiGlobalVariableGuid,
        NULL,
        &DataSize,
        &BootNext
    );

    if (EFI_ERROR(Status)) {
        return EFI_NOT_FOUND;  // 没有一次性引导请求
    }

    DEBUG((EFI_D_INFO, "[BDS] BootNext = Boot%04x\n", BootNext));

    // 2. 清除 BootNext (防止下次启动再次执行)
    gRT->SetVariable(
        L"BootNext",
        &gEfiGlobalVariableGuid,
        EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
        0,
        NULL
    );

    // 3. 将 BootNext 插入到 BootOrder 的第一个位置
    //
    //    但不修改 NVRAM 中的 BootOrder
    //    只在当前启动中有效
    //
    Status = InsertBootNextToFront(BootNext);

    return Status;
}

9.3 引导选项的 Boot Manager 菜单显示

典型的 Boot Order 在固件设置中的显示:

  Boot Configuration
  ┌─────────────────────────────────────────────┐
  │ Boot Option Priorities                      │
  │                                             │
  │  Boot Option #1:  [Windows Boot Manager]    │
  │  Boot Option #2:  [ubuntu]                  │
  │  Boot Option #3:  [UEFI: SanDisk SSD]       │
  │  Boot Option #4:  [UEFI PXE IPv4]           │
  │  Boot Option #5:  [UEFI Shell]              │
  │                                             │
  │  [  Add New Boot Option  ]                  │
  │  [  Delete Boot Option    ]                  │
  │                                             │
  │  Boot Option #1 → Boot0001 → Windows        │
  │  Boot Option #2 → Boot0003 → Ubuntu         │
  │  Boot Option #3 → Boot0000 → SanDisk        │
  │  Boot Option #4 → Boot0002 → PXE            │
  │  Boot Option #5 → Boot0004 → Shell          │
  │                                             │
  │  Boot Order:  1  3  0  2  4                 │
  └─────────────────────────────────────────────┘

10. 网络引导(PXE/HTTP Boot)

10.1 PXE 引导过程

UEFI 支持通过网络引导操作系统:

UEFI PXE 引导流程:

  1. DHCP/DISCOVER (广播)
     │  Client: "我是 XX:XX:XX:XX:XX:XX, 需要 IP 地址和引导文件"
     │
     ├── DHCP Server 回应:
     │   + IP 地址
     │   + 下一跳服务器 (next-server)
     │   + 引导文件名 (bootfile)
     │
  2. TFTP/HTTP 下载引导文件
     │  Client → Server: GET  bootfile.efi
     │  Server → Client: 传输文件内容
     │
  3. UEFI LoadImage + StartImage
     │  gBS->LoadImage(BootPolicy=TRUE, DevicePath=)
     │  gBS->StartImage(ImageHandle)
     │
  4. GRUB / iPXE / Windows Boot Manager 执行

10.2 UEFI HTTP Boot

现代 UEFI 支持 HTTP Boot(取代传统 TFTP):

// HTTP Boot 流程

EFI_STATUS
HttpBootStart(
    VOID
)
{
    EFI_STATUS                    Status;
    EFI_HANDLE                    *HandleBuffer;
    UINTN                         HandleCount;
    EFI_HTTP_BOOT_PROTOCOL        *HttpBoot;

    // 1. 查找 HTTP Boot 协议实例
    Status = gBS->LocateHandleBuffer(
        ByProtocol,
        &gEfiHttpBootProtocolGuid,
        NULL,
        &HandleCount,
        &HandleBuffer
    );

    if (EFI_ERROR(Status)) {
        DEBUG((EFI_D_ERROR, "[BDS] HTTP Boot not available\n"));
        return Status;
    }

    // 2. 对每个网络设备尝试 HTTP Boot
    for (UINTN i = 0; i < HandleCount; i++) {
        Status = gBS->HandleProtocol(
            HandleBuffer[i],
            &gEfiHttpBootProtocolGuid,
            (VOID **)&HttpBoot
        );

        if (EFI_ERROR(Status)) continue;

        // 3. 启动 HTTP Boot
        //
        //    HTTP Boot Protocol 内部处理:
        //    - DHCP 获取网络配置
        //    - HTTP 下载引导文件
        //    - 调用 LoadImage/StartImage
        //
        Status = HttpBoot->Start(HttpBoot);

        if (Status == EFI_SUCCESS) {
            break;
        }
    }

    FreePool(HandleBuffer);

    return Status;
}

10.3 UEFI 网络引导协议栈

UEFI 网络引导协议栈:

  ┌──────────────────────────────────────┐
  │  网络引导应用                         │
  │  (iPXE, GRUB, 或其他 HTTP Boot 应用) │
  ├──────────────────────────────────────┤
  │  UEFI 网络应用层协议                  │
  │  ┌─────────────────────────────────┐ │
  │  │  HTTP Boot Protocol             │ │ ← HTTP 引导
  │  │  Load File Protocol             │ │ ← PXE/TFTP 引导
  │  └─────────────────────────────────┘ │
  ├──────────────────────────────────────┤
  │  UEFI 网络传输层协议                  │
  │  ┌───────────┐ ┌───────────────────┐ │
  │  │ TCP       │ │ UDP               │ │
  │  └───────────┘ └───────────────────┘ │
  ├──────────────────────────────────────┤
  │  UEFI 网络层协议                     │
  │  ┌─────────────────────────────────┐ │
  │  │ IPv4 / IPv6                     │ │
  │  └─────────────────────────────────┘ │
  ├──────────────────────────────────────┤
  │  UEFI 网络底层协议                   │
  │  ┌──────────┐ ┌───────────────────┐ │
  │  │ ARP      │ │ DHCP              │ │
  │  ├──────────┤ ├───────────────────┤ │
  │  │ ICMP     │ │ DNS               │ │
  │  └──────────┘ └───────────────────┘ │
  ├──────────────────────────────────────┤
  │  UEFI 网络数据链路层                  │
  │  ┌─────────────────────────────────┐ │
  │  │ SNP (Simple Network Protocol)   │ │
  │  │ MNP (Managed Network Protocol)  │ │
  │  └─────────────────────────────────┘ │
  ├──────────────────────────────────────┤
  │  网络适配器硬件                       │
  │  (NIC: Intel I219, Realtek, ...)  │
  └──────────────────────────────────────┘

11. 安全启动在 BDS 阶段

11.1 安全启动流程

// BDS 阶段的安全启动验证

EFI_STATUS
SecureBootVerify(
    IN BDS_BOOT_OPTION  *BootOption
)
{
    EFI_STATUS  Status;
    BOOLEAN     SecureBootEnabled;
    UINT8       *ImageBuffer;
    UINTN       ImageSize;
    EFI_HANDLE  ImageHandle;

    // 1. 检查安全启动是否开启
    SecureBootEnabled = IsSecureBootEnabled();
    if (!SecureBootEnabled) {
        return EFI_SUCCESS;  // 安全启动已禁用, 跳过验证
    }

    DEBUG((EFI_D_INFO, "[BDS] SecureBoot: verifying Boot%04x\n",
           BootOption->OptionNumber));

    // 2. 从设备路径加载映像到内存 (但不执行)
    Status = gBS->LoadImage(
        FALSE,
        gImageHandle,
        BootOption->DevicePath,
        NULL,
        0,
        &ImageHandle
    );

    if (EFI_ERROR(Status)) {
        return Status;
    }

    // 3. 获取映像的数据库指针
    Status = gBS->OpenProtocol(
        ImageHandle,
        &gEfiLoadedImageProtocolGuid,
        (VOID **)&LoadedImage,
        gImageHandle,
        NULL,
        EFI_OPEN_PROTOCOL_GET_PROTOCOL
    );

    // 4. 执行映像签名验证
    //
    //    验证流程:
    //    a. 提取映像的签名 (嵌入在 PE/COFF 头部)
    //    b. 检查 db (允许签名数据库)
    //       └── 如果签名在 db 中 → 通过
    //    c. 检查 dbx (禁止签名数据库)
    //       └── 如果签名在 dbx 中 → 拒绝
    //    d. 如果既不在 db 也不在 dbx:
    //       ├── 如果 SetupMode == 1 (设置模式) → 通过
    //       └── 如果 SecureBoot == 1 (用户模式) → 拒绝
    //
    Status = VerifyImageSignature(
        LoadedImage->ImageBase,
        LoadedImage->ImageSize
    );

    if (EFI_ERROR(Status)) {
        //
        // 安全启动验证失败
        // 可以选择记录审计日志并拒绝启动
        //
        DEBUG((
            EFI_D_ERROR,
            "[BDS] Secure Boot: Image rejected! Status=%r\n",
            Status
        ));
        return Status;
    }

    return EFI_SUCCESS;
}

11.2 安全启动密钥数据库

// UEFI 安全启动密钥结构

//
// 安全启动使用以下 NVRAM 变量:
//
//   PK     = Platform Key (平台密钥, 1 个)
//   KEK    = Key Exchange Key (密钥交换密钥, 多个)
//   db     = Allowed Signatures Database (允许签名数据库)
//   dbx    = Forbidden Signatures Database (禁止签名数据库)
//   dbt    = Allowed Timestamp Database (时间戳数据库, 可选)
//   dbr    = Allowed Recovery Database (恢复数据库, 可选)
//

//
// 签名列表结构 (db, dbx, dbt, dbr 使用)
//
typedef struct {
    EFI_GUID             SignatureType;    // 签名类型
    UINT32               SignatureListSize; // 本结构总大小
    UINT32               SignatureHeaderSize; // 签名头部大小 (通常为 0)
    UINT32               SignatureSize;    // 每个签名条目的大小
    // 签名头部 (SignatureHeaderSize 字节)
    // 签名条目列表 (不定长)
} EFI_SIGNATURE_LIST;

typedef struct {
    EFI_GUID             Owner;     // 所有者 GUID
    // 签名数据 (如 RSA-2048 的 256 字节哈希):
    //   UINT8             SignatureData[];
} EFI_SIGNATURE_DATA;

//
// 签名类型 GUID:
//   EFI_CERT_SHA256_GUID        → SHA-256 哈希
//   EFI_CERT_RSA2048_GUID       → RSA-2048 公钥
//   EFI_CERT_RSA2048_SHA256_GUID → RSA-2048 + SHA-256 签名
//   EFI_CERT_X509_GUID          → X.509 证书
//   EFI_CERT_X509_SHA256_GUID   → X.509 证书的 SHA-256 哈希
//   EFI_CERT_X509_SHA384_GUID   → X.509 证书的 SHA-384 哈希
//   EFI_CERT_X509_SHA512_GUID   → X.509 证书的 SHA-512 哈希
//

11.3 安全启动验证流程

Secure Boot 验证流程图:

  用户加载映像 LoadImage()
    │
    ├── SecureBoot 是否开启?
    │   ├── 否 → 直接通过 (EFI_SUCCESS)
    │   └── 是 → 继续验证
    │
    ├── 映像是否在 dbx (禁止列表) 中?
    │   ├── 是 → 拒绝加载 (EFI_SECURITY_VIOLATION)
    │   └── 否 → 继续验证
    │
    ├── 映像是否在 db (允许列表) 中?
    │   ├── 是 → 允许加载 (EFI_SUCCESS)
    │   └── 否 → 继续验证
    │
    ├── SetupMode 是否为 1 (设置模式)?
    │   ├── 是 → 允许加载
    │   └── 否 → 继续验证
    │
    ├── 映像是否签名?
    │   ├── 未签名 → 拒绝 (在用户模式下)
    │   └── 已签名 → 验证签名链
    │
    ├── 签名是否可链到 KEK 和 PK?
    │   ├── 否 → 拒绝
    │   └── 是 → 允许加载
    │
    └── 最终决定:
          EFI_SUCCESS           = 允许引导
          EFI_SECURITY_VIOLATION = 阻止引导

11.4 AUdIT MODE

安全启动的几种模式:

  ┌──────────────┬────────────────┬─────────────────┐
  │ 模式          │  SetupMode     │  SecureBoot      │
  ├──────────────┼────────────────┼─────────────────┤
  │ 设置模式      │ 1              │ 0                │
  │ (Setup Mode) │ PK 未设置       │ 可以安装任意映像  │
  │              │                │ 可更改密钥        │
  ├──────────────┼────────────────┼─────────────────┤
  │ 用户模式      │ 0              │ 1                │
  │ (User Mode)  │ PK 已安装       │ 只运行签名的映像  │
  │              │                │ 需要 PK 才能改密钥 │
  ├──────────────┼────────────────┼─────────────────┤
  │ 审计模式      │ 0              │ 0                │
  │ (Audit Mode) │ PK 已安装       │ 记录日志但不阻止  │
  │              │                │ (用于调试和迁移)   │
  ├──────────────┼────────────────┼─────────────────┤
  │ 部署模式      │ 0              │ 1                │
  │ (Deployed    │ PK 已安装       │ 最严格的验证      │
  │  Mode)       │                │ 不能禁用安全启动   │
  └──────────────┴────────────────┴─────────────────┘

12. UEFI 驱动程序加载与管理

12.1 驱动程序选项

BDS 阶段可以加载可选的 UEFI 驱动程序:

Driver#### 选项 (与 Boot#### 类似):

  DriverOrder = { 0000, 0002, 0001 }
    │
    ├── Driver0000 → "\EFI\DRIVERS\NVMe.efi"
    ├── Driver0002 → "\EFI\DRIVERS\USBXHCI.efi"
    └── Driver0001 → "\EFI\DRIVERS\AHCI.efi"

  在 Boot Manager 执行之前,
  BDS 先加载所有 Driver#### 选项中的驱动

12.2 驱动程序加载

// BDS 驱动程序加载流程

EFI_STATUS
BdsLoadDrivers(
    VOID
)
{
    EFI_STATUS  Status;
    UINT16      *DriverOrder;
    UINTN       DriverOrderCount;
    UINTN       Index;

    // 1. 读取 DriverOrder
    DriverOrder = BdsReadDriverOrder(&DriverOrderCount);

    // 2. 按顺序加载每个驱动
    for (Index = 0; Index < DriverOrderCount; Index++) {
        UINT16  DriverCurrent = DriverOrder[Index];
        BDS_DRIVER_OPTION *DriverOption;

        Status = BdsReadDriverOption(DriverCurrent, &DriverOption);
        if (EFI_ERROR(Status)) continue;

        if (!DriverOption->IsActive) {
            FreePool(DriverOption);
            continue;
        }

        DEBUG((
            EFI_D_INFO,
            "[BDS] Loading Driver%04x: %s\n",
            DriverCurrent, DriverOption->Description
        ));

        // 连接驱动所属设备
        gBS->ConnectController(DriverOption->DeviceHandle, NULL, NULL, TRUE);

        // 加载并启动驱动
        EFI_HANDLE  ImageHandle;
        Status = gBS->LoadImage(
            FALSE,
            gImageHandle,
            DriverOption->DevicePath,
            NULL,
            0,
            &ImageHandle
        );

        if (EFI_ERROR(Status)) {
            DEBUG((EFI_D_WARN, "[BDS] Driver%04x LoadImage: %r\n",
                   DriverCurrent, Status));
            continue;
        }

        // 启动驱动
        Status = gBS->StartImage(ImageHandle, NULL, NULL);

        if (EFI_ERROR(Status)) {
            DEBUG((EFI_D_WARN, "[BDS] Driver%04x StartImage: %r\n",
                   DriverCurrent, Status));
        }

        FreePool(DriverOption);
    }

    // 3. 连接所有驱动对应的设备
    BdsConnectAll();

    return EFI_SUCCESS;
}

12.3 UEFI 驱动模型

UEFI 驱动模型在 BDS 阶段的作用:

  ┌──────────────────────────────────────────────┐
  │  UEFI 驱动模型                                 │
  │                                                │
  │  ┌──────────┐    ┌──────────┐    ┌──────────┐  │
  │  │ 总线驱动  │───▶│ 设备驱动  │───▶│ 文件系统  │  │
  │  │ (PCI Bus)│    │ (SATA)   │    │ (NTFS)   │  │
  │  └──────────┘    └──────────┘    └──────────┘  │
  │       │               │               │        │
  │       ▼               ▼               ▼        │
  │  ┌──────────┐    ┌──────────┐    ┌──────────┐  │
  │  │ 枚举设备  │    │ 绑定到   │    │ 提供     │  │
  │  │ 创建句柄  │    │ 设备句柄  │    │ GOP/FS   │  │
  │  └──────────┘    └──────────┘    └──────────┘  │
  │                                                │
  │  BDS 通过 ConnectController 触发驱动绑定:       │
  │    gBS->ConnectController(Handle, ...)          │
  │      → 寻找匹配的 Driver Binding Protocol       │
  │      → 调用 DriverBinding->Supported()          │
  │      → 调用 DriverBinding->Start()              │
  │      → 驱动初始化完成                           │
  └──────────────────────────────────────────────┘

13. 固件配置界面(Setup)

13.1 HII 架构

UEFI 使用 HII(Human Interface Infrastructure)构建固件配置界面:

HII 配置界面架构:

  ┌──────────────────────────────────────────────┐
  │  固件设置界面 (Setup Browser)                  │
  │  ┌────────────────────────────────────────┐   │
  │  │  Main  │ Advanced │ Security │ Boot │Exit│   │
  │  ├────────────────────────────────────────┤   │
  │  │  [System Information]                  │   │
  │  │  BIOS Version:  X.X.X                  │   │
  │  │  Processor:     Intel Core i7-xxxx     │   │
  │  │  Memory:        32 GB DDR4             │   │
  │  │  [Boot Configuration]                  │   │
  │  │  Boot Mode:     [UEFI]                 │   │
  │  │  Fast Boot:     [Enabled]              │   │
  │  └────────────────────────────────────────┘   │
  └──────────────────────────────────────────────┘
        │
        ▼
  ┌──────────────────────────────────────────────┐
  │  Form Browser (EFI_FORM_BROWSER2_PROTOCOL)    │
  │  - 渲染表单 (Form)                            │
  │  - 处理用户输入                                │
  │  - 调用配置回调                                │
  └──────────────────────────────────────────────┘
        │
        ▼
  ┌──────────────────────────────────────────────┐
  │  HII 数据库 (EFI_HII_DATABASE_PROTOCOL)       │
  │  - 存储表单定义 (IFR 字节码)                   │
  │  - 存储字符串 (多语言支持)                     │
  │  - 存储图像 (Logo, 图标)                      │
  │  - 存储字体                                   │
  └──────────────────────────────────────────────┘
        │
        ▼
  ┌──────────────────────────────────────────────┐
  │  IFR (Internal Forms Representation)          │
  │  ┌────────────────────────────────────────┐   │
  │  │  opcode: FORM_SET                      │   │
  │  │  opcode: FORM                          │   │
  │  │  opcode: TEXT (处理器)                  │   │
  │  │  opcode: ONE_OF (引导模式)              │   │
  │  │  opcode: CHECKBOX (快速启动)            │   │
  │  │  opcode: NUMERIC (超时)                 │   │
  │  │  opcode: STRING (设备路径)               │   │
  │  └────────────────────────────────────────┘   │
  └──────────────────────────────────────────────┘

13.2 IFR 示例

// IFR (Internal Forms Representation) 字节码示例

// 使用 VFR (Visual Forms Representation) 语言定义表单
// VFR 被编译为 IFR 字节码

// 一个典型的固件设置页面定义
formset
    guid     = FORMSET_GUID,
    title    = STRING_TOKEN(STR_MAIN_TITLE),
    help     = STRING_TOKEN(STR_MAIN_HELP),

    // ── 系统信息页面 ──
    form formid = 1,
        title = STRING_TOKEN(STR_SYSTEM_INFO);

        text
            text = STRING_TOKEN(STR_BIOS_VERSION),
            text = STRING_TOKEN(STR_BIOS_VERSION_VALUE);
        endtext;

        text
            text = STRING_TOKEN(STR_PROCESSOR),
            text = STRING_TOKEN(STR_PROCESSOR_VALUE);
        endtext;

        text
            text = STRING_TOKEN(STR_SYSTEM_MEMORY),
            text = STRING_TOKEN(STR_MEMORY_VALUE);
        endtext;
    endform;

    // ── 引导配置页面 ──
    form formid = 2,
        title = STRING_TOKEN(STR_BOOT_CONFIG);

        // 引导模式选择
        oneof varid = BootMode,
            prompt = STRING_TOKEN(STR_BOOT_MODE),
            help   = STRING_TOKEN(STR_BOOT_MODE_HELP),
            option text = STRING_TOKEN(STR_UEFI), value = 0, flags = DEFAULT;
            option text = STRING_TOKEN(STR_LEGACY), value = 1;
        endoneof;

        // 快速启动开关
        checkbox varid = FastBoot,
            prompt = STRING_TOKEN(STR_FAST_BOOT),
            help   = STRING_TOKEN(STR_FAST_BOOT_HELP),
            flags  = CHECKBOX_DEFAULT;
        endcheckbox;

        // 超时设置
        numeric varid = BootTimeout,
            prompt = STRING_TOKEN(STR_BOOT_TIMEOUT),
            help   = STRING_TOKEN(STR_BOOT_TIMEOUT_HELP),
            minimum = 0,
            maximum = 30,
            step    = 1,
            default = 5;
        endnumeric;
    endform;
endformset;

14. Boot Manager 内部数据结构

14.1 内部选项结构

// BDS 内部使用的完整引导选项结构

typedef struct {
    //
    // 选项编号 (如 0x0001 对应 Boot0001)
    //
    UINT16                OptionNumber;

    //
    // 选项类型
    //
    EFI_BOOT_MANAGER_LOAD_OPTION_TYPE OptionType; // Boot 或 Driver

    //
    // 选项属性
    //
    UINT32                Attributes;
    #define LOAD_OPTION_ACTIVE           0x00000001
    #define LOAD_OPTION_FORCE_RECONNECT  0x00000002
    #define LOAD_OPTION_HIDDEN           0x00000008
    #define LOAD_OPTION_CATEGORY         0x00001F00
    #define LOAD_OPTION_CATEGORY_BOOT    0x00000000
    #define LOAD_OPTION_CATEGORY_APP     0x00000100

    //
    // 描述字符串 (Unicode)
    //
    CHAR16                *Description;

    //
    // 设备路径 (指向 .efi 文件或设备)
    //
    EFI_DEVICE_PATH_PROTOCOL  *DevicePath;

    //
    // 可选的额外数据
    //
    UINT8                 *OptionalData;
    UINT16                OptionalDataSize;

    //
    // 设备句柄 (连接后的设备)
    //
    EFI_HANDLE            DeviceHandle;

    //
    // 文件路径 (从设备路径中提取的文件部分)
    //
    EFI_DEVICE_PATH_PROTOCOL  *FilePath;

    //
    // 状态
    //
    BOOLEAN               IsActive;       // 选项是否激活
    BOOLEAN               IsBootable;     // 设备是否可引导

    //
    // 退出数据 (引导加载程序返回的数据)
    //
    UINTN                 ExitDataSize;
    VOID                  *ExitData;

    //
    // 加载的映像句柄 (可选, 调试用)
    //
    EFI_HANDLE            ImageHandle;

} BDS_BOOT_OPTION;

14.2 Boot Manager 全局数据

// Boot Manager 全局数据

typedef struct {
    //
    // 所有引导选项的链表
    //
    LIST_ENTRY            BootOptionList;
    UINTN                 BootOptionCount;

    //
    // 所有驱动选项的链表
    //
    LIST_ENTRY            DriverOptionList;
    UINTN                 DriverOptionCount;

    //
    // 控制台信息
    //
    EFI_DEVICE_PATH_PROTOCOL *ConInDevicePath;
    EFI_DEVICE_PATH_PROTOCOL *ConOutDevicePath;
    EFI_DEVICE_PATH_PROTOCOL *StdErrDevicePath;

    //
    // 当前引导状态
    //
    UINT16                BootCurrent;       // 当前引导的 Boot####
    UINT16                BootNext;          // 一次性引导选项

    //
    // 超时设置
    //
    UINT16                Timeout;           // 引导倒计时秒数
    BOOLEAN               TimeoutElapsed;    // 超时是否已到

    //
    // 安全启动状态
    //
    BOOLEAN               SecureBootEnabled;
    UINT8                 SetupMode;         // 0=User, 1=Setup

    //
    // 重启/关闭标记
    //
    BOOLEAN               ShutdownRequested;
    BOOLEAN               RebootRequested;

    //
    // 平台特定标志
    //
    BOOLEAN               SetupMenuRequested;
    BOOLEAN               BootMenuRequested;

    //
    // 调试
    //
    UINTN                 BootAttemptCount;  // 已尝试的引导次数

} BDS_GLOBAL_DATA;

15. Boot Manager 协议与编程接口

15.1 EFI_BOOT_MANAGER_PROTOCOL

// Boot Manager Protocol (由 UEFI 规范定义)

typedef struct _EFI_BOOT_MANAGER_PROTOCOL {
    //
    // 加载引导选项
    // 从 NVRAM 读取并解析一个 Boot#### 选项
    //
    EFI_BOOT_MANAGER_LOAD_OPTION   LoadOption;

    //
    // 释放引导选项占用的资源
    //
    EFI_BOOT_MANAGER_FREE_OPTION   FreeOption;

    //
    // 启动引导选项
    // 执行 LoadImage + StartImage
    //
    EFI_BOOT_MANAGER_START_OPTION  StartOption;

    //
    // 写入引导选项到 NVRAM
    //
    EFI_BOOT_MANAGER_WRITE_OPTION  WriteOption;

    //
    // 枚举所有引导选项
    //
    EFI_BOOT_MANAGER_ENUMERATE_BOOT_OPTIONS EnumerateBootOptions;

    //
    // 获取引导顺序
    //
    EFI_BOOT_MANAGER_GET_BOOT_ORDER GetBootOrder;

    //
    // 设置引导顺序
    //
    EFI_BOOT_MANAGER_SET_BOOT_ORDER SetBootOrder;

} EFI_BOOT_MANAGER_PROTOCOL;

15.2 编程接口示例

// 通过编程接口添加引导选项

EFI_STATUS
AddNewBootOption(
    IN CHAR16                   *Description,
    IN EFI_DEVICE_PATH_PROTOCOL *DevicePath,
    IN UINT32                   Attributes
)
{
    EFI_STATUS  Status;
    UINT16      BootIndex;
    UINT16      *BootOrder;
    UINTN       BootOrderSize;
    UINTN       NewOrderSize;
    UINT16      *NewBootOrder;

    // 1. 查找下一个可用的 Boot#### 编号
    BootIndex = FindNextFreeBootOption();

    // 2. 构造 EFI_LOAD_OPTION 数据
    //
    //    结构:
    //      UINT32                Attributes
    //      UINT16                FilePathListLength
    //      CHAR16[]              Description (以 NUL 结尾)
    //      EFI_DEVICE_PATH_PROTOCOL[] FilePathList
    //      UINT8[]               OptionalData (可选)
    //

    UINTN  DescSize = StrSize(Description);
    UINTN  FilePathSize = GetDevicePathSize(DevicePath);
    UINTN  TotalSize = sizeof(EFI_LOAD_OPTION) + DescSize + FilePathSize;
    UINT8  *OptionData = AllocatePool(TotalSize);

    EFI_LOAD_OPTION *LoadOption = (EFI_LOAD_OPTION *)OptionData;
    LoadOption->Attributes = Attributes;
    LoadOption->FilePathListLength = (UINT16)FilePathSize;

    // 复制描述字符串
    CopyMem(OptionData + sizeof(EFI_LOAD_OPTION),
            Description, DescSize);

    // 复制设备路径
    CopyMem(OptionData + sizeof(EFI_LOAD_OPTION) + DescSize,
            DevicePath, FilePathSize);

    // 3. 写入 NVRAM: Boot####
    CHAR16  VariableName[20];
    UnicodeSPrint(VariableName, sizeof(VariableName),
                  L"Boot%04x", BootIndex);

    Status = gRT->SetVariable(
        VariableName,
        &gEfiGlobalVariableGuid,
        EFI_VARIABLE_NON_VOLATILE |
        EFI_VARIABLE_BOOTSERVICE_ACCESS |
        EFI_VARIABLE_RUNTIME_ACCESS,
        TotalSize,
        OptionData
    );

    FreePool(OptionData);

    // 4. 更新 BootOrder
    BootOrder = BdsReadBootOrder(&BootOrderSize);
    NewOrderSize = BootOrderSize + sizeof(UINT16);
    NewBootOrder = AllocatePool(NewOrderSize);

    // 将新选项放在第一个位置
    NewBootOrder[0] = BootIndex;
    if (BootOrder != NULL) {
        CopyMem(&NewBootOrder[1], BootOrder, BootOrderSize);
        FreePool(BootOrder);
    }

    Status = gRT->SetVariable(
        L"BootOrder",
        &gEfiGlobalVariableGuid,
        EFI_VARIABLE_NON_VOLATILE |
        EFI_VARIABLE_BOOTSERVICE_ACCESS |
        EFI_VARIABLE_RUNTIME_ACCESS,
        NewOrderSize,
        NewBootOrder
    );

    FreePool(NewBootOrder);

    return Status;
}

16. 退出启动服务(ExitBootServices)

16.1 ExitBootServices 的含义

当引导加载程序准备接管系统时,调用 ExitBootServices()

ExitBootServices 的作用:

  ┌─────────────────────────────────────────────┐
  │  ExitBootServices() 调用                     │
  │                                              │
  │  ┌───────────────────────────────────────┐   │
  │  │ 调用前 (BDS/TSL):                      │   │
  │  │  - Boot Services 可用                   │   │
  │  │  - 固件可控制所有硬件                    │   │
  │  │  - UEFI 驱动和协议仍然可用               │   │
  │  │  - 固件中断处理程序就绪                   │   │
  │  │  - 固件管理页表和 GDT                   │   │
  │  └───────────────────────────────────────┘   │
  │                      │                        │
  │                      ▼                        │
  │  ┌───────────────────────────────────────┐   │
  │  │ ExitBootServices() 执行操作:            │   │
  │  │  1. 终止所有 UEFI 事件                  │   │
  │  │  2. 释放所有 Boot Services 内存          │   │
  │  │  3. 关闭所有 Boot Services 协议          │   │
  │  │  4. 将控制台交由 OS 管理                 │   │
  │  │  5. 禁用 UEFI 中断处理                   │   │
  │  └───────────────────────────────────────┘   │
  │                      │                        │
  │                      ▼                        │
  │  ┌───────────────────────────────────────┐   │
  │  │ 调用后 (RT):                            │   │
  │  │  - 只有 Runtime Services 可用           │   │
  │  │  - 操作系统完全控制硬件                  │   │
  │  │  - UEFI 驱动停止工作                     │   │
  │  │  - 只有 gRT 中的功能可用                 │   │
  │  │  - SetVirtualAddressMap 可被调用         │   │
  │  └───────────────────────────────────────┘   │
  └─────────────────────────────────────────────┘

16.2 ExitBootServices 调用过程

// 引导加载程序调用 ExitBootServices 的过程
//
// 这段代码通常由引导加载程序 (如 GRUB, Windows Boot Manager) 执行
// 不是由 BDS 执行

EFI_STATUS
CallExitBootServices(
    IN EFI_HANDLE     ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS  Status;
    UINTN       MapKey;
    UINTN       MemoryMapSize;
    EFI_MEMORY_DESCRIPTOR *MemoryMap;
    UINTN       MapKey2;
    UINTN       DescriptorSize;
    UINT32      DescriptorVersion;

    // 1. 获取当前内存映射的 MapKey
    MemoryMapSize = 0;
    MemoryMap = NULL;

    Status = gBS->GetMemoryMap(
        &MemoryMapSize,
        MemoryMap,
        &MapKey,
        &DescriptorSize,
        &DescriptorVersion
    );

    // 2. 分配内存映射缓冲区
    MemoryMap = AllocatePool(MemoryMapSize);
    Status = gBS->GetMemoryMap(
        &MemoryMapSize,
        MemoryMap,
        &MapKey,
        &DescriptorSize,
        &DescriptorVersion
    );

    // 3. 调用 ExitBootServices
    //
    //    这个函数:
    //    - 接收 ImageHandle (调用者的映像句柄)
    //    - 接收 MapKey (内存映射的密钥, 用于检测竞态条件)
    //
    //    如果另一个事件在 GetMemoryMap 和 ExitBootServices
    //    之间修改了内存映射, ExitBootServices 会返回错误
    //    此时需要重新获取 MapKey 并再次调用
    //
    Status = gBS->ExitBootServices(ImageHandle, MapKey);

    if (Status == EFI_INVALID_PARAMETER) {
        //
        // MapKey 不匹配, 重新获取并重试
        //
        MemoryMapSize = 0;
        Status = gBS->GetMemoryMap(
            &MemoryMapSize,
            NULL,
            &MapKey,
            &DescriptorSize,
            &DescriptorVersion
        );
        MemoryMap = AllocatePool(MemoryMapSize);
        Status = gBS->GetMemoryMap(
            &MemoryMapSize,
            MemoryMap,
            &MapKey,
            &DescriptorSize,
            &DescriptorVersion
        );
        Status = gBS->ExitBootServices(ImageHandle, MapKey);
    }

    if (EFI_ERROR(Status)) {
        // ExitBootServices 失败 (!)
        // 系统仍然在固件控制下
        return Status;
    }

    //
    // 从此处开始:
    //   - gBS (Boot Services) 不可用
    //   - gRT (Runtime Services) 仍然可用
    //   - 操作系统已经控制了所有硬件
    //

    // 4. 可选: 调用 SetVirtualAddressMap
    //    将 Runtime Services 转换为虚拟地址模式
    //    (依赖于操作系统策略)

    return EFI_SUCCESS;
}

16.3 ExitBootServices 之后的系统状态

ExitBootServices 之后可用的 UEFI 运行时服务:

  gRT->GetTime()             // 读取 RTC 时间
  gRT->SetTime()             // 设置 RTC 时间
  gRT->GetWakeupTime()       // 读取唤醒时间
  gRT->SetWakeupTime()       // 设置唤醒时间

  gRT->GetVariable()         // 读取 NVRAM 变量
  gRT->SetVariable()         // 写入 NVRAM 变量
  gRT->GetNextVariableName() // 枚举 NVRAM 变量

  gRT->GetNextHighMonotonicCount() // 单调计数器

  gRT->SetVirtualAddressMap()      // 转换地址空间 (可选)
  gRT->ConvertPointer()            // 指针转换

  gRT->GetCapabilities()           // 重置能力查询
  gRT->ResetSystem()               // 系统重启 (Cold/Warm/Shutdown)

  不可用的启动服务:
    gBS->LoadImage()          // ✗
    gBS->StartImage()         // ✗
    gBS->AllocatePages()      // ✗
    gBS->OpenProtocol()       // ✗
    gBS->ConnectController()  // ✗
    gBS->CreateEvent()        // ✗
    ...                       // ✗ 所有 Boot Services

17. BDS 与操作系统引导加载程序交接

17.1 引导加载程序执行环境

BDS 调用 gBS->StartImage() 后,引导加载程序获得控制权:

BDS 传递给引导加载程序的执行环境:

  ┌──────────────────────────────────────────────┐
  │ 引导加载程序启动时的状态                       │
  │                                                │
  │  内存:                                         │
  │    - 所有物理内存已映射                          │
  │    - UEFI 保留了一些内存区域                     │
  │    - 没有虚拟地址映射 (OS 需要自己建立页表)        │
  │                                                │
  │  处理器:                                        │
  │    - 64 位长模式 (x64)                           │
  │    - 中断关闭 (CLI)                              │
  │    - 无浮点上下文                                 │
  │    - TLB/缓存不确定状态                           │
  │                                                │
  │  UEFI 协议:                                     │
  │    - 全套 Boot Services 可用                     │
  │    - Runtime Services 可用                       │
  │    - 所有 UEFI 协议可用                           │
  │    - 控制台就绪                                   │
  │                                                │
  │  参数:                                          │
  │    - ImageHandle     → 引导加载程序自身的句柄      │
  │    - SystemTable     → UEFI 系统表指针            │
  │                        (包含 BootServices,        │
  │                         RuntimeServices, 配置表)   │
  │    - LoadedImage     → 映像加载信息                │
  │                        (设备路径, 加载地址, 大小)   │
  │                                                │
  └──────────────────────────────────────────────┘

17.2 引导加载程序接口

// UEFI 引导加载程序的标准入口点

//
// 每个 UEFI 应用程序 (包括引导加载程序) 的入口点:
//
typedef
EFI_STATUS
(EFIAPI *EFI_IMAGE_ENTRY_POINT)(
    IN EFI_HANDLE        ImageHandle,   // 映像实例句柄
    IN EFI_SYSTEM_TABLE  *SystemTable   // UEFI 系统表
);

//
// 引导加载程序入口函数 (如 GRUB, bootmgfw.efi, systemd-boot)
//
EFI_STATUS
EFIAPI
BootLoaderEntry(
    IN EFI_HANDLE        ImageHandle,
    IN EFI_SYSTEM_TABLE  *SystemTable
)
{
    //
    // 引导加载程序执行流程:
    //

    // 1. 保存系统表指针 (全局变量)
    gST = SystemTable;
    gBS = SystemTable->BootServices;
    gRT = SystemTable->RuntimeServices;

    // 2. 初始化引导加载程序自身
    //    └── 内存分配
    //    └── 字符输出设置
    //    └── 加载配置文件

    // 3. 显示引导菜单 (如 GRUB 菜单)
    //    └── 列出可用的操作系统内核
    //    └── 等待用户选择

    // 4. 加载操作系统内核
    //    └── 读取内核文件到内存
    //    └── 收集硬件信息 (内存映射, ACPI 表, SMBIOS)
    //    └── 设置内核启动参数

    // 5. 调用 ExitBootServices()
    //    └── 终止 UEFI 启动服务
    //    └── 固件交出控制权

    // 6. 跳转到内核入口
    //    └── 设置页表 (如果 OS 需要)
    //    └── 设置内核栈
    //    └── jmp kernel_entry

    return EFI_SUCCESS;
}

17.3 典型引导加载程序的 BDS 交互

GRUB (Linux) 与 BDS 的交互:

  BDS → LoadImage(HD0, \EFI\GRUB\grubx64.efi)
      → StartImage(ImageHandle)
          │
          ▼
  grubx64.efi 入口:
    ├── 初始化 GRUB 控制台
    ├── 加载 GRUB 模块 (通过 UEFI 文件系统协议)
    ├── 读取 grub.cfg 配置文件
    ├── 显示 GRUB 菜单
    ├── 用户选择 "Boot Linux"
    │
    ├── 加载 vmlinuz-linux (通过 UEFI 文件系统)
    ├── 加载 initramfs (通过 UEFI 文件系统)
    ├── 收集 UEFI 配置表 (ACPI, SMBIOS)
    ├── 收集内存映射 (GetMemoryMap)
    │
    ├── ExitBootServices()
    │   └── UEFI 固件不再控制硬件
    │
    └── 跳转到 Linux 内核入口 (startup_64)

  ───────────────────────────────────────────

  Windows Boot Manager 与 BDS 的交互:

  BDS → LoadImage(HD0, \EFI\Microsoft\Boot\bootmgfw.efi)
      → StartImage(ImageHandle)
          │
          ▼
  bootmgfw.efi 入口:
    ├── 初始化 Windows 引导环境
    ├── 读取 BCD (Boot Configuration Data)
    ├── 显示 Windows 引导菜单 (如多系统)
    │
    ├── 加载 winload.efi
    ├── 加载 HAL (硬件抽象层)
    ├── 收集 UEFI 配置表
    │
    ├── ExitBootServices()
    │
    └── 跳转到 Windows 内核入口

18. EDK2 BDS 实现分析

18.1 EDK2 BDS 文件结构

edk2/MdeModulePkg/Universal/BdsDxe/
  │
  ├── BdsEntry.c              ← BDS 入口 (BdsEntry)
  ├── BdsEntry.h
  ├── BdsDxe.inf              ← 模块描述文件
  │
  ├── BdsMisc.c               ← BDS 辅助函数
  │     ├── BdsReadBootOrder()
  │     ├── BdsReadBootOption()
  │     ├── BdsSetBootOrder()
  │     └── BdsConnectAll()
  │
  ├── Language.c              ← 语言选择
  ├── Device.c                ← 设备连接管理
  ├── Device.h
  │
  ├── HwErrRecSupport.c       ← 硬件错误记录支持
  │
  └── BootManager.c            ← Boot Manager 核心
        ├── BootManagerMenu ()
        └── ...

edk2/MdeModulePkg/Library/
  ├── UefiBootManagerLib/      ← Boot Manager 库
  │     ├── BmLoadOption.c     ← 引导选项加载
  │     ├── BmBoot.c           ← 引导执行
  │     ├── BmConnect.c        ← 设备连接
  │     ├── BmConsole.c        ← 控制台初始化
  │     ├── BmDriver.c         ← 驱动加载
  │     ├── BmMisc.c           ← 杂项
  │     └── BmVariable.c       ← NVRAM 变量操作
  │
  └── PlatformBootManagerLib/  ← 平台 BDS 策略 (各平台不同)
        ├── PlatformBootManager.c
        └── ...

18.2 PlatformBootManagerLib

平台 BDS 策略是 BDS 阶段与平台相关的部分,由 PlatformBootManagerLib 定义:

// 平台 BDS 策略 (每个平台有自己实现)
// edk2-platforms/Platform/<Vendor>/<Platform>/Library/PlatformBootManagerLib/

/**
  平台 BDS 初始化

  这个函数由 BDS Entry 调用,完成平台特定的 BDS 初始化。
  不同平台可以有不同的实现:
    - Intel 参考平台: 初始化串口、显示启动Logo
    - 服务器平台: 初始化 BMC、显示 POST 卡
    - 嵌入式平台: 可能没有显示设备
**/
VOID
EFIAPI
PlatformBootManagerBeforeConsole(
    VOID
)
{
    //
    // 1. 平台特定早期初始化 (在控制台初始化之前)
    //

    // 初始化调试输出 (串口)
    SerialPortInitialize();
    DEBUG((EFI_D_INFO, "[PlatformBDS] Platform BDS start\n"));

    //
    // 2. 连接必要的设备
    //
    //    某些设备需要在控制台初始化之前连接
    //
    gBS->ConnectController(PciRootBridgeHandle, NULL, NULL, FALSE);

    //
    // 3. 注册平台特定的控制台设备
    //
    //    Platform specific console path:
    //    - Serial console for servers
    //    - GOP for client platforms
    //
    PlatformRegisterDefaultConIn();
    PlatformRegisterDefaultConOut();

    //
    // 4. 安装平台特定 PPI/Protocol (如果有)
    //
    //    ...
}

VOID
EFIAPI
PlatformBootManagerAfterConsole(
    VOID
)
{
    //
    // 1. 控制台已初始化
    //

    // 显示 OEM 启动徽标
    ShowPlatformLogo();

    //
    // 2. 注册平台特定引导选项
    //

    // 添加 UEFI Shell 引导选项
    PlatformRegisterBootOptionShell();

    // 添加操作系统引导选项 (如果未配置)
    PlatformRegisterDefaultBootOption();

    //
    // 3. 注册 "F2/F12" 热键
    //
    //    F2 → Setup
    //    Delete → Setup
    //    F12 → Boot Menu
    //    F10 → BIOS Flash Update (某些平台)
    //

    EfiBootManagerRegisterBootOptionHotkey(
        &mSetupOption,       // Setup 选项
        L"F2",               // 按键
        FALSE,               // Shift
        FALSE,               // Ctrl
        FALSE                // Alt
    );

    EfiBootManagerRegisterBootOptionHotkey(
        &mBootMenuOption,    // Boot Menu 选项
        L"F12",              // 按键
        FALSE, FALSE, FALSE
    );
}

/**
  无法引导时调用
**/
EFI_STATUS
EFIAPI
PlatformBootManagerUnableToBoot(
    VOID
)
{
    //
    // 所有引导选项都失败时
    //
    // 显示用户的错误信息
    //

    Print(L"\n  No bootable device found.\n");
    Print(L"  Press any key to enter Setup...\n");

    // 等待按键进入 Setup
    WaitForSingleEvent(gST->ConIn->WaitForKey, 0);

    // 进入固件设置
    EnterSetupMenu();

    // 从设置返回后, 重启
    gRT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL);

    return EFI_SUCCESS;
}

18.3 Boot Manager 库关键函数

// EDK2 Boot Manager Library 关键函数
// edk2/MdeModulePkg/Library/UefiBootManagerLib/BmBoot.c

/**
  加载并启动一个引导选项

  @param[in]  BootOption     引导选项指针
  @param[out] ExitDataSize   退出数据大小
  @param[out] ExitData       退出数据

  @retval EFI_SUCCESS         引导选项正常启动并返回
  @retval others              引导选项启动失败
**/
EFI_STATUS
EFIAPI
EfiBootManagerBoot(
    IN  BDS_BOOT_OPTION  *BootOption,
    OUT UINTN            *ExitDataSize  OPTIONAL,
    OUT VOID             **ExitData     OPTIONAL
)
{
    EFI_STATUS                Status;
    EFI_HANDLE                ImageHandle;
    EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
    EFI_DEVICE_PATH_PROTOCOL  *FilePath;
    EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
    CHAR16                    BootOptionName[20];

    //
    // 1. 如果 BootOption 设置了 LOAD_OPTION_FORCE_RECONNECT,
    //    强制重新连接设备
    //
    if (BootOption->Attributes & LOAD_OPTION_FORCE_RECONNECT) {
        DEBUG((EFI_D_INFO, "[Bm] Force reconnect for Boot%04x\n",
               BootOption->OptionNumber));
        gBS->DisconnectController(BootOption->DeviceHandle, NULL, NULL);
        gBS->ConnectController(BootOption->DeviceHandle, NULL, NULL, TRUE);
    }

    //
    // 2. 将设备路径分解为设备部分和文件部分
    //
    //    例: "PciRoot(0)/Pci(0x1F,2)/Sata(0,0)/HD(1,GPT,...)/\EFI\BOOT\BOOTX64.EFI"
    //        设备部分: "PciRoot(0)/Pci(0x1F,2)/Sata(0,0)/HD(1,GPT,...)"
    //        文件部分: "\EFI\BOOT\BOOTX64.EFI"
    //
    EfiBootManagerExpandLoadOption(BootOption);
    DevicePath = BootOption->DevicePath;
    FilePath   = BootOption->FilePath;

    //
    // 3. 连接设备路径上的所有节点
    //
    EfiBootManagerConnectDevicePath(DevicePath, &BootOption->DeviceHandle);

    //
    // 4. 检查安全启动
    //
    if (IsSecureBootEnabled()) {
        Status = EfiBootManagerVerifyBootOption(BootOption);
        if (EFI_ERROR(Status)) {
            DEBUG((EFI_D_ERROR, "[Bm] Secure boot rejected\n"));
            return Status;
        }
    }

    //
    // 5. 设置 BootCurrent 变量
    //
    UnicodeSPrint(BootOptionName, sizeof(BootOptionName),
                  L"Boot%04x", BootOption->OptionNumber);
    gRT->SetVariable(
        L"BootCurrent",
        &gEfiGlobalVariableGuid,
        EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
        sizeof(UINT16),
        &BootOption->OptionNumber
    );

    //
    // 6. 设置 LoadOptions
    //
    //    将引导选项的可选数据传递给引导加载程序
    //
    if (BootOption->OptionalData != NULL && BootOption->OptionalDataSize > 0) {
        // LoadedImage->LoadOptions 和 LoadOptionsSize
        // 引导加载程序可以读取这些值
    }

    //
    // 7. 加载映像 (LoadImage)
    //
    Status = gBS->LoadImage(
        FALSE,              // BootPolicy = FALSE
        gImageHandle,       // 调用者
        BootOption->DevicePath,
        NULL,
        0,
        &ImageHandle
    );

    if (EFI_ERROR(Status)) {
        DEBUG((EFI_D_ERROR, "[Bm] LoadImage error: %r\n", Status));
        return Status;
    }

    //
    // 8. 设置引导描述
    //
    Status = gBS->HandleProtocol(
        ImageHandle,
        &gEfiLoadedImageProtocolGuid,
        (VOID **)&LoadedImage
    );
    if (!EFI_ERROR(Status)) {
        LoadedImage->LoadOptionsSize = BootOption->OptionalDataSize;
        LoadedImage->LoadOptions = BootOption->OptionalData;
    }

    //
    // 9. 启动映像 (StartImage)
    //
    //    这个调用通常不返回——引导加载程序会
    //    调用 ExitBootServices 并接管系统
    //
    Status = gBS->StartImage(
        ImageHandle,
        ExitDataSize,
        ExitData
    );

    //
    // 10. 如果引导加载程序返回...
    //
    //     这可能是因为:
    //       - BootNext 一次性引导完成
    //       - 引导加载程序出错
    //       - 用户选择了其他引导选项
    //

    // 清理 BootNext
    BootNext = 0;
    gRT->GetVariable(L"BootNext", &gEfiGlobalVariableGuid,
                     NULL, &DataSize, &BootNext);
    if (BootOption->OptionNumber == BootNext) {
        gRT->SetVariable(L"BootNext", &gEfiGlobalVariableGuid,
                         EFI_VARIABLE_BOOTSERVICE_ACCESS |
                         EFI_VARIABLE_RUNTIME_ACCESS,
                         0, NULL);
    }

    return Status;
}

19. 常见 BDS 问题与调试

19.1 常见问题

问题 可能原因 排查方法
无法引导到操作系统 BootOrder 为空或错误 检查 NVRAM 中 BootOrder 和 Boot#### 变量
“No bootable device” 设备路径无效或设备未连接 检查设备枚举和驱动绑定
安全启动阻止引导 引导加载程序未签名或签名过期 检查 db/dbx, 尝试禁用安全启动
引导顺序无效 BootOrder 指向不存在的 Boot#### 重新设置引导顺序
控制台无输出 GOP 驱动未加载或显示设备故障 检查 ConOut 设备路径
键盘无响应 ConIn 设备路径无效 检查 ConIn 设备路径
固件设置无法进入 按键映射错误或热键冲突 检查 PlatformBootManager 热键注册
PXE 引导超时 DHCP 服务器无响应或网络未连接 检查网络连接和 DHCP 配置
退出设置后重启 NVRAM 数据损坏或需清空 CMOS 重置 NVRAM 或清除 CMOS
引导加载程序崩溃 ExitBootServices 内存映射不一致 检查 GetMemoryMap 和 MapKey

19.2 BDS 调试方法

// BDS 调试技巧

// 1. 启用详细的 BDS 日志
//    在固件构建中设置:
//      DEBUG_PROPERTY_DEBUG_LEVEL = 0x80000000 | DEBUG_INFO
//
//    典型的 BDS DEBUG 输出:
//
//   [BDS] BDS Entry Point
//   [BDS] Console init: ConOut GOP, ConIn USB KBD
//   [BDS] BootOrder = { 0001, 0003, 0000 }
//   [BDS] Trying Boot0001: "Windows Boot Manager"
//   [BDS]   DevicePath: HD(1,GPT,...)/\EFI\Microsoft\Boot\bootmgfw.efi
//   [BDS]   Connecting device path...
//   [BDS]   LoadImage: Success
//   [BDS]   StartImage: bootmgfw.efi
//   [BDS]   (bootmgfw.efi running...)
//   [BDS]   StartImage returned: EFI_SUCCESS
//   [BDS] BootNext detected, cleaning up
//   [BDS] Trying Boot0003: "ubuntu"

// 2. 使用 UEFI Shell 手动调试
//
//    Shell> bcfg boot dump          // 列出所有引导选项
//    Shell> bcfg boot add 0 fs0:\EFI\BOOT\BOOTX64.EFI "My Boot"
//    Shell> bcfg boot rm 3          // 删除引导选项 3
//    Shell> reset -s                // 进入固件设置
//
//    Shell> fs0:
//    FS0:\> \EFI\BOOT\BOOTX64.EFI   // 手动加载引导加载程序

// 3. 修改 NVRAM 变量
//
//    Shell> dmpstore BootOrder      // 查看 BootOrder
//    Shell> setvar BootOrder -guid 8BE4DF61-93CA-11d2-AA0D-00E098032B8C
//           -bs -rt -nv =010003000200  // 设置 BootOrder = {1, 3, 0, 2}
//
//    Shell> setvar Boot0001 -guid ... -bs -rt -nv =...  // 设置 Boot0001

// 4. NVRAM 变量操作代码
EFI_STATUS
DumpBootVariables(
    VOID
)
{
    CHAR16      VariableName[256];
    UINTN       VariableNameSize;
    EFI_GUID    VendorGuid;
    EFI_STATUS  Status;

    VariableNameSize = sizeof(VariableName);
    Status = gRT->GetNextVariableName(
        &VariableNameSize,
        VariableName,
        &VendorGuid
    );

    while (!EFI_ERROR(Status)) {
        // 只显示 UEFI 全局变量
        if (CompareGuid(&VendorGuid, &gEfiGlobalVariableGuid)) {
            // 检查是否 Boot#### 变量
            if (StrnCmp(VariableName, L"Boot", 4) == 0 ||
                StrnCmp(VariableName, L"Driver", 6) == 0 ||
                StrCmp(VariableName, L"BootOrder") == 0 ||
                StrCmp(VariableName, L"BootNext") == 0 ||
                StrCmp(VariableName, L"Timeout") == 0) {

                DEBUG((EFI_D_INFO, "  %s\n", VariableName));
            }
        }

        VariableNameSize = sizeof(VariableName);
        Status = gRT->GetNextVariableName(
            &VariableNameSize,
            VariableName,
            &VendorGuid
        );
    }

    return EFI_SUCCESS;
}

19.3 基于 POST 码的 BDS 调试

// BDS 阶段 POST 码分配

#define POST_BDS_START              0x40    // BDS 阶段开始
#define POST_BDS_CONSOLE_INIT       0x41    // 控制台初始化
#define POST_BDS_CONSOLE_DONE       0x42    // 控制台就绪
#define POST_BDS_DRIVERS_LOAD       0x43    // 驱动程序加载
#define POST_BDS_DRIVERS_DONE       0x44    // 驱动加载完成
#define POST_BDS_SETUP_ENTER        0x45    // 进入固件设置
#define POST_BDS_SETUP_EXIT         0x46    // 退出固件设置
#define POST_BDS_BOOT_MENU          0x47    // 引导菜单显示
#define POST_BDS_BOOT_START         0x48    // 开始尝试引导
#define POST_BDS_BOOT_ATTEMPT       0x49    // 尝试引导选项 N
#define POST_BDS_BOOT_SUCCESS       0x4A    // 引导成功
#define POST_BDS_BOOT_FAIL          0x4B    // 引导失败
#define POST_BDS_NO_BOOTABLE        0x4C    // 无可用引导设备
#define POST_BDS_EXIT               0x4F    // BDS 阶段结束

// 引导选项尝试时的细分 POST 码
#define POST_BDS_LOAD_IMAGE         0x50    // LoadImage 开始
#define POST_BDS_SECURE_VERIFY      0x51    // 安全启动验证
#define POST_BDS_START_IMAGE        0x52    // StartImage 开始

20. 参考资料

规范

  • UEFI Specification 2.x — Chapter 3: Boot Manager
    • Section 3.1: Boot Manager Overview
    • Section 3.2: Boot Options
    • Section 3.3: Boot Order
    • Section 3.4: Boot Manager Programming
  • UEFI Specification 2.x — Chapter 4: Protocols (Boot Manager Protocol)
  • UEFI Specification 2.x — Chapter 7: Secure Boot
  • UEFI Platform Initialization (PI) Specification 1.7+ — Volume 5: Standards

EDK2 源码

  • edk2/MdeModulePkg/Universal/BdsDxe/ — BDS DXE 驱动
  • edk2/MdeModulePkg/Library/UefiBootManagerLib/ — Boot Manager 核心库
    • BmBoot.c — 引导执行
    • BmLoadOption.c — 引导选项管理
    • BmConnect.c — 设备连接
    • BmConsole.c — 控制台初始化
  • edk2/MdeModulePkg/Library/PlatformBootManagerLibNull/ — 默认平台 BDS(空实现)
  • edk2/SecurityPkg/Library/SecureBootLib/ — 安全启动库

平台 BDS 参考实现

  • edk2-platforms/Platform/Intel/MinPlatformPkg/ — Intel MinPlatform
    • Bds/ — Intel 参考 BDS
  • edk2-platforms/Platform/Intel/Vlv2TbltDevicePkg/ — Valley Island 平台
  • edk2-platforms/Silicon/Intel/CoffeelakeSiliconPkg/ — Coffee Lake 芯片组

调试和工具

  • UEFI Shell — Shell 命令: bcfg, dmpstore, setvar, reset
  • UEFI 调试器 — 基于 ITP/JTAG 的 UEFI 调试
  • OVMF (QEMU) — 使用 QEMU 模拟 UEFI,方便 BDS 调试

本文全面解析了 UEFI BDS 阶段的架构设计、实现机制和关键流程。BDS 是用户最直观感受到的 UEFI 阶段——它管理着固件设置界面、引导菜单、引导设备选择、安全启动策略等直接影响用户体验的核心功能。从 DXE 就绪后的控制台初始化,到 Boot Manager 遍历引导选项执行引导加载程序,再到最终 ExitBootServices() 移交系统给 OS,BDS 阶段完成了一次优雅的控制权转交。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐