NVMe-eMMC Bridge 芯片固件开发(八):UEFI 环境下固件升级 FMP 实现

概述

本文档是以 BH799 NVMe-eMMC Bridge 芯片 的 UEFI 固件升级为实例,系统讲解:

  1. UEFI 基础概念:DXE 驱动、Application、Protocol 的关系与分层框架
  2. EDK2 构建系统:INF/DEC/DSC 文件结构、库依赖、编译流程
  3. FMP Protocol 深度解析:接口定义、数据结构、设备库实现
  4. BH799 固件升级完整实现:从协议注册到 NVMe 命令交互

1. UEFI 基础概念与驱动分层框架

1.1 UEFI 启动阶段与 DXE

UEFI 系统启动顺序为:SEC (Security Phase) → PEI (Pre-EFI Initialization) → DXE (Driver Execution Environment) → BDS (Boot Device Selection) → TSL (Transient System Load)

DXE 阶段是 UEFI 驱动的核心执行阶段,分为两类:

类型 缩写 加载时机 示例
DXE Driver DXE 系统启动早期,多个驱动按依赖顺序加载 FmpDxe.efi
DXE Runtime Driver RUNTIME 贯穿启动和运行时,可持久化 RuntimeDxe
DXE SMM Driver SMM 运行于 SMM 隔离区 SmmDriver

对于固件升级场景,使用 标准 DXE Driver 即可。

1.2 UEFI Driver 与 Application 的区别

特性 UEFI Driver (.efi) UEFI Application (.efi)
入口函数 DriverEntry() UefiMain()
启动方式 由 UEFI Boot Manager 自动加载,或 Shell 中 load Shell 中直接执行
生命周期 常驻内存,可被多次调用 执行完即退出
Protocol 发布 ✅ 可发布 Protocol ❌ 通常不发布
依赖管理 通过 .inf 声明 DEPEX 无依赖声明

在 BH799 方案中:

  • FmpDxe.efi 是 DXE Driver,执行 DriverEntry,发布 EFI_FIRMWARE_MANAGEMENT_PROTOCOL
  • BH799App.efi 是 Application,执行 UefiMain,不发布协议,仅使用已发布的 FMP

1.3 Protocol 机制深度解析

Protocol 是 UEFI 中实现驱动与应用解耦的核心机制。

1.3.1 Protocol 本质

1
2
3
4
5
6
7
8
9
10
// UEFI 规范定义的核心数据结构
typedef struct {
UINT64 Revision; // 协议版本号
VOID *Interface; // 指向具体函数实现的指针
} EFI_GUID;

// Protocol GUID(全局唯一标识)
// 例如 FMP 的 GUID:
// {0x86570D99, 0x6E70, 0x464D, {0xA0, 0x8F, 0x7F, 0x51, 0x83, 0x51, 0xA6, 0x7E}}
extern EFI_GUID gEfiFirmwareManagementProtocolGuid;

1.3.2 Protocol 工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
+---------------------------+        +---------------------------+
| Driver Entry | | Application |
+---------------------------+ +---------------------------+
| |
v v
+---------------------------+ +---------------------------+
| InstallMultiple | | LocateHandleBuffer |
| ProtocolInterfaces | | (ByProtocol) |
| | +--->| |
| - FMP GUID | | +------------+------------+
| - FMP 接口函数指针 | | |
+----------+---------------+ | v
| | +---------------------------+
| Handle 有 FMP | | HandleProtocol |
v | | (获取 FMP 协议指针) |
+---------------------------+ | +------------+------------+
| Controller Handle | | |
| 附加 FMP 协议到 Handle |---+ |
+---------------------------+ |
v
+---------------------------+
| Fmp->GetImageInfo() |
| Fmp->CheckImage() |
| Fmp->SetImage() |
+---------------------------+

1.3.3 常见 Protocol 查找方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 方式1: LocateHandleBuffer - 查找所有发布过 FMP 的 Handle
EFI_STATUS status;
EFI_HANDLE *HandleBuffer;
UINTN NumberOfHandles;

status = LocateHandleBuffer(
ByProtocol,
&gEfiFirmwareManagementProtocolGuid,
NULL,
&NumberOfHandles,
&HandleBuffer
);

// 方式2: LocateProtocol - 直接获取(适用于单实例)
EFI_FIRMWARE_MANAGEMENT_PROTOCOL *Fmp;
status = LocateProtocol(
&gEfiFirmwareManagementProtocolGuid,
NULL,
(VOID**)&Fmp
);

2. EDK2 构建系统详解

2.1 EDK2 项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
edk2/
├── BaseTools/ # 构建工具链(C, Python)
├── MdePkg/ # 核心包(定义 UEFI 类型)
│ ├── Include/Protocol/ # Protocol 头文件
│ └── Library/ # 基础库(UefiLib, BaseLib)
├── MdeModulePkg/ # 模块包(Application, Driver)
├── FmpDevicePkg/ # BH799 定制包(本方案核心)
│ ├── FmpDxe/ # DXE Driver
│ │ └── FmpDxe.inf
│ └── Library/
│ └── FmpDeviceLibNull/ # 设备库实现
└── Conf/
└── target.txt # 编译目标配置

2.2 INF 文件结构(模块定义)

FmpDxe.inf 是 EDK2 中每个模块(驱动/应用)的描述文件,声明模块信息、依赖库、入口函数等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[Defines]
INF_VERSION = 0x00010005
BASE_NAME = FmpDxe
FILE_GUID = 226034C4-8B67-4536-8653-D6EE7CE5A316
MODULE_TYPE = DXE_DRIVER
VERSION_STRING = 1.0
ENTRY_POINT = FmpDxeEntry
UNLOAD_IMAGE = UnloadFmpDxe

# 依赖库:驱动框架 + FMP 协议定义 + 加密库
[LibraryClasses]
UefiDriverEntryLib
UefiBootServicesTableLib
UefiDriverModelLib
BaseLib
BaseMemoryLib
DebugLib
PrintLib
BaseCryptLib

# 依赖的 Protocols:驱动加载时需要这些协议已存在
[Protocols]
gEfiFirmwareManagementProtocolGuid
gEfiDevicePathProtocolGuid
gEfiPciIoProtocolGuid

# 依赖的 GUID(用于 DEPEX)
[Guids]

[Sources]
FmpDxe.c
FmpDxe.h

关键字段解析

字段 说明 BH799 案例值
FILE_GUID 模块唯一标识,编译产出 FmpDxe_<GUID>.efi 226034C4-8B67-4536-8653-D6EE7CE5A316
MODULE_TYPE 模块类型 DXE_DRIVER
ENTRY_POINT 入口函数名 FmpDxeEntry
LibraryClasses 依赖的库 UefiDriverEntryLib, BaseCryptLib
Protocols 依赖的外部协议 FMP, PciIo

2.3 DSC 文件(平台配置)

DSC(Platform DSC)定义平台的编译配置、模块启用、库依赖等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# FmpDevicePkg.dsc (BH799 平台配置)

[Defines]
PLATFORM_NAME = FmpDevicePkg
DSC_SPECIFICATION = 0x00010005
OUTPUT_DIRECTORY = Build/FmpDevicePkg
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS = DEBUG|RELEASE

# 包含的库和模块
[LibraryClasses]
# 基础库
BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
DebugLib|MdePkg/Library/DebugLib/DebugLib.inf

# 驱动框架库
UefiDriverEntryLib|MdePkg/Library/UefiDriverEntryLib/UefiDriverEntryLib.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiDriverModelLib|MdePkg/Library/UefiDriverModelLib/UefiDriverModelLib.inf

# 加密库(SHA256)
BaseCryptLib|MdePkg/Library/BaseCryptLib/BaseCryptLib.inf

[LibraryClasses.common.DXE_DRIVER]
# BH799 设备库实现
FmpDeviceLib|FmpDevicePkg/Library/FmpDeviceLibNull/FmpDeviceLibNull.inf

[Components]
# 编译 FMP DXE 驱动
FmpDevicePkg/FmpDxe/FmpDxe.inf

# 编译 BH799 应用
MdeModulePkg/Application/BH799App/BH799App.inf

2.4 编译命令与构建流程

1
2
3
4
5
6
7
8
9
10
11
12
13
:: Windows 环境 EDK2 编译
@echo off
set PYTHON_HOME=C:\python38
set EDK2_PATH=C:\edk2

cd /d %EDK2_PATH%
call edksetup.bat Rebuild

:: 指定平台编译
build -p FmpDevicePkg.dsc -m FmpDevicePkg/FmpDxe/FmpDxe.inf -a X64 -t VS2019

:: 或者全量编译(包含 BH799App)
build -p FmpDevicePkg.dsc -a X64 -t VS2019

构建产物位置

1
2
3
Build/FmpDevicePkg/DEBUG_VS2019/X64/
├── FmpDxe/Driver/FmpDxe_226034C4-8B67-4536-8653-D6EE7CE5A316.efi
└── BH799App.efi

3. FMP Protocol 深度解析

3.1 FMP 协议定义(UEFI 规范 Chapter 23)

根据 UEFI 2.8 规范,EFI_FIRMWARE_MANAGEMENT_PROTOCOL 定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// MdePkg/Include/Protocol/FirmwareManagement.h

#define EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID \
{ 0x86570D99, 0x6E70, 0x464D, { 0xA0, 0x8F, 0x7F, 0x51, 0x83, 0x51, 0xA6, 0x7E } }

typedef struct _EFI_FIRMWARE_MANAGEMENT_PROTOCOL {
UINT32 Version;
EFI_FIRMWARE_MANAGEMENT_GET_IMAGE_INFO GetImageInfo;
EFI_FIRMWARE_MANAGEMENT_GET_IMAGE GetImage;
EFI_FIRMWARE_MANAGEMENT_SET_IMAGE SetImage;
EFI_FIRMWARE_MANAGEMENT_CHECK_IMAGE CheckImage;
EFI_FIRMWARE_MANAGEMENT_GET_PACKAGE_INFO GetPackageInfo;
EFI_FIRMWARE_MANAGEMENT_SET_PACKAGE_INFO SetPackageInfo;
} EFI_FIRMWARE_MANAGEMENT_PROTOCOL;

// 省略函数指针类型定义...

3.2 必选接口详解

3.2.1 GetImageInfo - 获取固件信息

1
2
3
4
5
6
7
8
9
10
typedef EFI_STATUS (EFIAPI *EFI_FIRMWARE_MANAGEMENT_GET_IMAGE_INFO)(
IN EFI_FIRMWARE_MANAGEMENT_PROTOCOL *This,
OUT UINT32 *ImageInfoSize,
IN OUT EFI_FIRMWARE_IMAGE_DESCRIPTOR *ImageInfo,
OUT UINT32 *DescriptorVersion,
OUT UINT8 *DescriptorCount,
OUT UINTN *DescriptorSize,
OUT UINT32 *PackageVersion,
OUT CHAR16 **PackageVersionName
);

返回值 - EFI_FIRMWARE_IMAGE_DESCRIPTOR 结构体:

1
2
3
4
5
6
7
8
9
10
typedef struct {
UINT8 ImageIndex; // 固件索引(1-based)
UINT16 ImageTypeId; // 设备类型 GUID
CHAR16 *ImageId; // 固件 ID(字符串)
UINT32 ImageVersion; // 固件版本号
UINT32 LowestSupportedImageVersion; // 最低支持版本
UINT64 ImageSize; // 固件大小
UINT64 Attributes; // 属性位
// ...
} EFI_FIRMWARE_IMAGE_DESCRIPTOR;

3.2.2 CheckImage - 校验固件镜像

1
2
3
4
5
6
typedef EFI_STATUS (EFIAPI *EFI_FIRMWARE_MANAGEMENT_CHECK_IMAGE)(
IN EFI_FIRMWARE_MANAGEMENT_PROTOCOL *This,
IN UINT8 *Image,
IN UINTN ImageSize,
OUT UINT32 *ImageUpdatable
);

ImageUpdatable 返回值

含义
0x00 不可更新
0x01 可更新(版本相同或更高)
0x02 可降级更新
0x03 可强制更新(强制位设置)

3.2.3 SetImage - 写入固件镜像

1
2
3
4
5
6
7
8
9
typedef EFI_STATUS (EFIAPI *EFI_FIRMWARE_MANAGEMENT_SET_IMAGE)(
IN EFI_FIRMWARE_MANAGEMENT_PROTOCOL *This,
IN UINT8 ImageIndex,
IN UINT8 *Image,
IN UINTN ImageSize,
IN UINT64 VendorCode,
IN VOID *Progress,
OUT CHAR16 **AbortReason
);

3.3 FMP 设备库接口

EDK2 设计了 FMP Device Library 抽象层,将设备相关实现(NVMe 交互)与 FMP 协议层解耦:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// MdeModulePkg/Include/Library/FmpDeviceLib.h

// 获取固件版本
EFI_STATUS
EFIAPI
FmpDeviceGetVersion(
OUT UINT32 *Version
);

// 获取固件属性
EFI_STATUS
EFIAPI
FmpDeviceGetAttributes(
OUT UINT64 *Attributes
);

// 获取最低支持版本
EFI_STATUS
EFIAPI
FmpDeviceGetLowestSupportedVersion(
OUT UINT32 *LowestSupportedVersion
);

// 镜像校验(含状态)
EFI_STATUS
EFIAPI
FmpDeviceCheckImageWithStatus(
IN CONST VOID *Image,
IN UINTN ImageSize,
OUT UINT32 *ImageUpdatable,
OUT UINT32 *CheckStatus
);

// 镜像写入(含状态)
EFI_STATUS
EFIAPI
FmpDeviceSetImageWithStatus(
IN CONST VOID *Image,
IN UINTN ImageSize,
IN UINT64 VendorCode,
OUT UINT32 *SetImageStatus,
OUT CHAR16 **AbortReason
);

4. BH799 FMP DXE 驱动完整实现

4.1 驱动入口与 Protocol 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// FmpDevicePkg/FmpDxe/FmpDxe.c

#include <PiDxe.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiDriverEntryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/FirmwareManagement.h>
#include <Protocol/PciIo.h>
#include "FmpDxe.h"

// 全局 FMP 协议实例
EFI_FIRMWARE_MANAGEMENT_PROTOCOL mFmp = {
FMP_DRIVER_VERSION,
FmpGetImageInfo,
FmpGetImage,
FmpSetImage,
FmpCheckImage,
NULL, // GetPackageInfo - 未实现
NULL // SetPackageInfo - 未实现
};

// 驱动入口函数
EFI_STATUS
EFIAPI
FmpDxeEntry(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_HANDLE Handle = NULL;

DEBUG((DEBUG_INFO, "[BH799] FMP DXE Driver Entry\n"));

// 安装 FMP 协议到新的 Handle
Status = gBS->InstallMultipleProtocolInterfaces(
&Handle,
&gEfiFirmwareManagementProtocolGuid,
&mFmp,
NULL
);

if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "[BH799] Install FMP Protocol Failed: %r\n", Status));
return Status;
}

DEBUG((DEBUG_INFO, "[BH799] FMP Protocol Installed Successfully\n"));
return EFI_SUCCESS;
}

4.2 GetImageInfo 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// FmpDevicePkg/FmpDxe/FmpDxe.c

// 驱动内部版本号
#define FMP_DRIVER_VERSION 1

// 固件属性
#define FMP_IMAGE_ATTRIBUTE_IMAGE_UPDATABLE 0x0000000000000001
#define FMP_IMAGE_ATTRIBUTE_RESET_REQUIRED 0x0000000000000002
#define FMP_IMAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED 0x0000000000000010

EFI_STATUS
EFIAPI
FmpGetImageInfo(
IN EFI_FIRMWARE_MANAGEMENT_PROTOCOL *This,
OUT UINT32 *ImageInfoSize,
IN OUT EFI_FIRMWARE_IMAGE_DESCRIPTOR *ImageInfo,
OUT UINT32 *DescriptorVersion,
OUT UINT8 *DescriptorCount,
OUT UINTN *DescriptorSize,
OUT UINT32 *PackageVersion,
OUT CHAR16 **PackageVersionName
)
{
UINT64 Attributes;
UINT32 Version;
UINT32 LowestVersion;
UINT64 FwSize;
STATIC EFI_FIRMWARE_IMAGE_DESCRIPTOR mDescriptor;

// 获取设备信息(通过设备库)
FmpDeviceGetAttributes(&Attributes);
FmpDeviceGetVersion(&Version);
FmpDeviceGetLowestSupportedVersion(&LowestVersion);
FmpDeviceGetSize(&FwSize);

// 构建镜像描述符
mDescriptor.ImageIndex = 1;
mDescriptor.ImageTypeId = gEfiFileSystemGuid; // 或自定义 GUID
mDescriptor.ImageId = (CHAR16*)L"BH799-Firmware";
mDescriptor.ImageVersion = Version;
mDescriptor.LowestSupportedImageVersion = LowestVersion;
mDescriptor.ImageSize = FwSize;
mDescriptor.Attributes = Attributes;

// 计算返回缓冲区大小
*DescriptorSize = sizeof(EFI_FIRMWARE_IMAGE_DESCRIPTOR);
*DescriptorVersion = 1;
*DescriptorCount = 1;

// 检查缓冲区大小
if (*ImageInfoSize < *DescriptorSize) {
*ImageInfoSize = *DescriptorSize;
return EFI_BUFFER_TOO_SMALL;
}

// 复制描述符
if (ImageInfo != NULL) {
CopyMem(ImageInfo, &mDescriptor, *DescriptorSize);
}

*ImageInfoSize = *DescriptorSize;
*PackageVersion = 0xFFFFFFFF; // 无包版本

return EFI_SUCCESS;
}

4.3 CheckImage 实现(安全校验)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
EFI_STATUS
EFIAPI
FmpCheckImage(
IN EFI_FIRMWARE_MANAGEMENT_PROTOCOL *This,
IN UINT8 *Image,
IN UINTN ImageSize,
OUT UINT32 *ImageUpdatable
)
{
EFI_STATUS Status;
UINT32 CheckStatus;
UINT32 CurrentVersion;

DEBUG((DEBUG_INFO, "[BH799] FmpCheckImage called, Size=%lu\n", ImageSize));

// 委托给设备库实现校验逻辑
Status = FmpDeviceCheckImageWithStatus(
Image,
ImageSize,
ImageUpdatable,
&CheckStatus
);

if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "[BH799] CheckImage Failed: %r, Status=0x%x\n",
Status, CheckStatus));
return Status;
}

// 解析当前设备版本用于日志
FmpDeviceGetVersion(&CurrentVersion);
DEBUG((DEBUG_INFO, "[BH799] Current Version: 0x%x, Updatable: 0x%x\n",
CurrentVersion, *ImageUpdatable));

return EFI_SUCCESS;
}

4.4 SetImage 实现(固件烧录)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
EFI_STATUS
EFIAPI
FmpSetImage(
IN EFI_FIRMWARE_MANAGEMENT_PROTOCOL *This,
IN UINT8 ImageIndex,
IN UINT8 *Image,
IN UINTN ImageSize,
IN UINT64 VendorCode,
IN VOID *Progress,
OUT CHAR16 **AbortReason
)
{
EFI_STATUS Status;
UINT32 SetImageStatus;

DEBUG((DEBUG_INFO, "[BH799] FmpSetImage called, Size=%lu\n", ImageSize));

if (Progress != NULL) {
*(VOID**)Progress = NULL; // 可实现进度回调
}

// 委托给设备库执行实际写入
Status = FmpDeviceSetImageWithStatus(
Image,
ImageSize,
VendorCode,
&SetImageStatus,
AbortReason
);

if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "[BH799] SetImage Failed: %r, Status=0x%x\n",
Status, SetImageStatus));
if (AbortReason != NULL && *AbortReason != NULL) {
DEBUG((DEBUG_ERROR, "[BH799] Abort Reason: %s\n", *AbortReason));
}
return Status;
}

DEBUG((DEBUG_INFO, "[BH799] Firmware Update Completed Successfully\n"));
return EFI_SUCCESS;
}

5. BH799 FmpDeviceLib 设备库实现

5.1 NVMe 控制器定位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// FmpDevicePkg/Library/FmpDeviceLibNull/FmpDeviceLib.c

#include <Library/UefiBootServicesTableLib.h>
#include <Library/PciIoLib.h>
#include <Protocol/PciIo.h>
#include <Protocol/NvmExpressPassThru.h>

// BH799 PCI 配置
#define BH799_VENDOR_ID 0x1217
#define BH799_DEVICE_ID 0x8760

STATIC EFI_HANDLE mNvmeControllerHandle = NULL;

/**
定位 BH799 NVMe 控制器 Handle

@retval EFI_SUCCESS 找到控制器
@retval EFI_NOT_FOUND 未找到
**/
EFI_STATUS
LocateBh799Controller(
OUT EFI_HANDLE *ControllerHandle
)
{
EFI_STATUS Status;
EFI_HANDLE *HandleBuffer;
UINTN NumberOfHandles;
UINTN Index;
EFI_PCI_IO_PROTOCOL *PciIo;
PCI_TYPE00 PciData;

// 查找所有支持 PciIo 的 Handle
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiPciIoProtocolGuid,
NULL,
&NumberOfHandles,
&HandleBuffer
);
if (EFI_ERROR(Status)) {
return Status;
}

// 遍历查找 BH799
for (Index = 0; Index < NumberOfHandles; Index++) {
Status = gBS->HandleProtocol(
HandleBuffer[Index],
&gEfiPciIoProtocolGuid,
(VOID**)&PciIo
);
if (EFI_ERROR(Status)) {
continue;
}

// 读取 PCI 配置空间
Status = PciIo->Pci.Read(
PciIo,
EfiPciIoWidthUint8,
0,
sizeof(PciData),
&PciData
);
if (EFI_ERROR(Status)) {
continue;
}

// 匹配 BH799 VendorId/DeviceId
if (PciData.Hdr.VendorId == BH799_VENDOR_ID &&
PciData.Hdr.DeviceId == BH799_DEVICE_ID) {
*ControllerHandle = HandleBuffer[Index];
mNvmeControllerHandle = HandleBuffer[Index];
FreePool(HandleBuffer);
return EFI_SUCCESS;
}
}

FreePool(HandleBuffer);
return EFI_NOT_FOUND;
}

5.2 版本获取(NVMe Identify)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 获取当前固件版本(从 NVMe Identify Controller 获取)
EFI_STATUS
EFIAPI
FmpDeviceGetVersion(
OUT UINT32 *Version
)
{
EFI_STATUS Status;
EFI_HANDLE ControllerHandle;
EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL *NvmePassThru;
NVME_CONTROLLER_DATA *ControllerData;
UINT8 *Buffer;
UINT32 Result;

if (Version == NULL) {
return EFI_INVALID_PARAMETER;
}

// 定位控制器
Status = LocateBh799Controller(&ControllerHandle);
if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "[BH799] Controller not found\n"));
return Status;
}

// 获取 NVMe PassThru 协议
Status = gBS->HandleProtocol(
ControllerHandle,
&gEfiNvmExpressPassThruProtocolGuid,
(VOID**)&NvmePassThru
);
if (EFI_ERROR(Status)) {
return Status;
}

// 分配 Identify 缓冲区
Buffer = AllocateZeroPool(4096);
if (Buffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}

// 发送 Identify Controller 命令 (CNS = 0x01)
Status = NvmePassThru->PassThru(
NvmePassThru,
0, // NamespaceId
NULL,
Buffer,
&Result
);

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

// 解析 FR (Firmware Revision) 字段
// NVMe Identify Controller 数据结构中,FR 位于 offset 0x98
// 格式:8 字节 ASCII
ControllerData = (NVME_CONTROLLER_DATA*)Buffer;

// 解析版本号(例如 "10100070" -> 0x10100070)
CHAR8 Fr[8];
CopyMem(Fr, ControllerData->Fr, 8);

// 简单解析:将 ASCII 转换为数字
*Version = AsciiStrDecimalToUintn(Fr); // 简化处理

FreePool(Buffer);
return EFI_SUCCESS;
}

5.3 镜像校验(签名 + 版本 + SHA256)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// FmpDeviceLib.c - CheckImageWithStatus

// 固件头部偏移定义
#define FW_SIGNATURE_OFFSET 0x3E0 // 8 bytes
#define FW_VERSION_OFFSET 0x3E8 // 8 bytes
#define FW_SHA256_OFFSET 0x3C0 // 32 bytes
#define FW_SIGNATURE_MAGIC "BH799"

EFI_STATUS
EFIAPI
FmpDeviceCheckImageWithStatus(
IN CONST VOID *Image,
IN UINTN ImageSize,
OUT UINT32 *ImageUpdatable,
OUT UINT32 *CheckStatus
)
{
UINT8 *ImageBuffer = (UINT8*)Image;
UINT32 ImageVersion;
UINT32 DeviceVersion;
UINT8 Digest1[32], Digest2[32];
SHA256_CONTEXT *HashContext;
UINTN i;

*CheckStatus = 0;
*ImageUpdatable = 0;

// ========== 1. 签名校验 ==========
if (CompareMem(ImageBuffer + FW_SIGNATURE_OFFSET,
FW_SIGNATURE_MAGIC, 5) != 0) {
DEBUG((DEBUG_ERROR, "[BH799] Signature mismatch!\n"));
*CheckStatus = 0x01; // 签名失败
return EFI_INVALID_PARAMETER;
}
*CheckStatus |= 0x02; // 签名通过

// ========== 2. 版本校验 ==========
ImageVersion = AsciiStrDecimalToUintn((CHAR8*)(ImageBuffer + FW_VERSION_OFFSET));
FmpDeviceGetVersion(&DeviceVersion);

DEBUG((DEBUG_INFO, "[BH799] Image Version: 0x%x, Device Version: 0x%x\n",
ImageVersion, DeviceVersion));

if (ImageVersion < DeviceVersion) {
DEBUG((DEBUG_ERROR, "[BH799] Downgrade not allowed in -S mode!\n"));
*CheckStatus = 0x04; // 版本过低
return EFI_ACCESS_DENIED;
}
*CheckStatus |= 0x08; // 版本校验通过

// ========== 3. SHA256 完整性校验 ==========
// 保存固件头中的 SHA256
CopyMem(Digest1, ImageBuffer + FW_SHA256_OFFSET, 32);

// 将 SHA256 区域清零后计算
ZeroMem(ImageBuffer + FW_SHA256_OFFSET, 32);

// 计算整镜像 SHA256
HashContext = AllocatePool(Sha256GetContextSize());
if (HashContext == NULL) {
return EFI_OUT_OF_RESOURCES;
}

Sha256Init(HashContext);
Sha256Update(HashContext, ImageBuffer, ImageSize);
Sha256Final(HashContext, Digest2);

// 比较
if (CompareMem(Digest1, Digest2, 32) != 0) {
DEBUG((DEBUG_ERROR, "[BH799] SHA256 mismatch!\n"));
*CheckStatus = 0x10; // 校验失败
FreePool(HashContext);
return EFI_VOLUME_CORRUPTED;
}
*CheckStatus |= 0x20; // 完整性校验通过

// 所有校验通过
*ImageUpdatable = (ImageVersion >= DeviceVersion) ? 1 : 2;
FreePool(HashContext);

DEBUG((DEBUG_INFO, "[BH799] CheckImage: All Passed!\n"));
return EFI_SUCCESS;
}

5.4 固件写入(NVMe Firmware Download + Commit)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
EFI_STATUS
EFIAPI
FmpDeviceSetImageWithStatus(
IN CONST VOID *Image,
IN UINTN ImageSize,
IN UINT64 VendorCode,
OUT UINT32 *SetImageStatus,
OUT CHAR16 **AbortReason
)
{
EFI_STATUS Status;
EFI_HANDLE ControllerHandle;
EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL *NvmePassThru;
UINT8 *Buffer;
UINTN Offset;
UINTN ChunkSize;
NVME_PASS_THRU_COMMAND_PACKET Packet;
NVME_ADMIN_COMMAND AdminCmd;
UINT32 Result;

*SetImageStatus = 0;

// 定位控制器和获取 PassThru 协议
Status = LocateBh799Controller(&ControllerHandle);
if (EFI_ERROR(Status)) {
return Status;
}

Status = gBS->HandleProtocol(
ControllerHandle,
&gEfiNvmExpressPassThruProtocolGuid,
(VOID**)&NvmePassThru
);
if (EFI_ERROR(Status)) {
return Status;
}

Buffer = (UINT8*)Image;
Offset = 0;

// ========== 1. Firmware Image Download (分块传输) ==========
// 每次最多 4KB
while (Offset < ImageSize) {
ChunkSize = (ImageSize - Offset > 4096) ? 4096 : (ImageSize - Offset);

// 构造 Firmware Image Download 命令
ZeroMem(&AdminCmd, sizeof(AdminCmd));
AdminCmd.Cdw0.Opcode = 0x19; // Firmware Image Download
AdminCmd.Cdw10 = (UINT32)(ChunkSize - 1); // Numd
AdminCmd.Cdw11 = (UINT32)Offset; // Ofst
AdminCmd.Cdw12 = 0; // 保留

// 构造 PassThru 包
ZeroMem(&Packet, sizeof(Packet));
Packet.CommandTimeout = 5000000; // 5 秒
Packet.NvmeCmd = &AdminCmd;
Packet.DataDir = NVME_DATA_DIR_IN;
Packet.PayloadSize = ChunkSize;
Packet.Buffer = Buffer + Offset;

Status = NvmePassThru->PassThru(
NvmePassThru,
0,
NULL,
Buffer + Offset,
&Result
);

if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "[BH799] Firmware Download Failed at offset 0x%x\n",
Offset));
*SetImageStatus = 0x01;
return Status;
}

Offset += ChunkSize;
DEBUG((DEBUG_INFO, "[BH799] Downloaded %lu / %lu bytes\n",
Offset, ImageSize));
}

// ========== 2. Firmware Commit ==========
// 提交到 Slot 3 (用户固件槽)
ZeroMem(&AdminCmd, sizeof(AdminCmd));
AdminCmd.Cdw0.Opcode = 0x10; // Firmware Commit
AdminCmd.Cdw10 = (3 << 24) | 0x01; // FwAction=01 (Downloaded image replace),
// FwSlot=3

ZeroMem(&Packet, sizeof(Packet));
Packet.CommandTimeout = 10000000; // 10 秒
Packet.NvmeCmd = &AdminCmd;
Packet.DataDir = NVME_DATA_DIR_IN;

Status = NvmePassThru->PassThru(
NvmePassThru,
0,
NULL,
NULL,
&Result
);

if (EFI_ERROR(Status)) {
DEBUG((DEBUG_ERROR, "[BH799] Firmware Commit Failed\n"));
*SetImageStatus = 0x02;
return Status;
}

*SetImageStatus = 0; // 成功
DEBUG((DEBUG_INFO, "[BH799] Firmware Update Completed, Reboot Required!\n"));

return EFI_SUCCESS;
}

6. BH799App 应用实现

6.1 应用入口与命令行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// MdeModulePkg/Application/BH799App/BH799App.c

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellLib.h>
#include <Protocol/FirmwareManagement.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>

EFI_GUID gEfiFirmwareManagementProtocolGuid =
{ 0x86570D99, 0x6E70, 0x464D, { 0xA0, 0x8F, 0x7F, 0x51, 0x83, 0x51, 0xA6, 0x7E } };

// 打印固件信息
VOID
PrintFirmwareInfo(
IN EFI_FIRMWARE_IMAGE_DESCRIPTOR *Descriptor
)
{
Print(L"========================================\n");
Print(L" BH799 Firmware Information\n");
Print(L"========================================\n");
Print(L" Image Index: %d\n", Descriptor->ImageIndex);
Print(L" Image ID: %s\n", Descriptor->ImageId);
Print(L" Version: 0x%x (%d.%d.%d.%d)\n",
Descriptor->ImageVersion,
(Descriptor->ImageVersion >> 24) & 0xFF,
(Descriptor->ImageVersion >> 16) & 0xFF,
(Descriptor->ImageVersion >> 8) & 0xFF,
Descriptor->ImageVersion & 0xFF);
Print(L" Lowest Version: 0x%x\n", Descriptor->LowestSupportedImageVersion);
Print(L" Image Size: %lu bytes\n", Descriptor->ImageSize);
Print(L" Attributes: 0x%lx\n", Descriptor->Attributes);
Print(L"========================================\n");
}

// 主函数
INTN
EFIAPI
UefiMain(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_FIRMWARE_MANAGEMENT_PROTOCOL *Fmp;
UINT32 ImageInfoSize;
EFI_FIRMWARE_IMAGE_DESCRIPTOR *ImageInfo;
CHAR16 **Argv;
UINTN Argc;
CHAR16 *ImagePath;
UINT8 *FileBuffer;
UINTN FileSize;
UINT32 ImageUpdatable;

// 获取命令行参数
Status = ShellGetArgcArgv((SHELL_ARGUMENT_PACKAGE**)&Argc, (CHAR16***)&Argv);
if (EFI_ERROR(Status)) {
Print(L"Error: Failed to get command line args\n");
return Status;
}

// 定位 FMP 协议
Status = gBS->LocateProtocol(
&gEfiFirmwareManagementProtocolGuid,
NULL,
(VOID**)&Fmp
);
if (EFI_ERROR(Status)) {
Print(L"Error: FMP Protocol not found. Please load FmpDxe first!\n");
return Status;
}

// 解析命令
if (Argc < 2) {
Print(L"Usage:\n");
Print(L" BH799App -I : Query firmware info\n");
Print(L" BH799App -S <firmware.bin> : Update with version check\n");
Print(L" BH799App -U <firmware.bin> : Update without version check\n");
return EFI_INVALID_PARAMETER;
}

if (StrCmp(Argv[1], L"-I") == 0) {
// ===== 查询模式 =====
ImageInfoSize = 0;
Status = Fmp->GetImageInfo(Fmp, &ImageInfoSize, NULL, NULL, NULL, NULL, NULL, NULL);
if (Status != EFI_BUFFER_TOO_SMALL) {
Print(L"Error: GetImageInfo failed\n");
return Status;
}

ImageInfo = AllocatePool(ImageInfoSize);
Status = Fmp->GetImageInfo(Fmp, &ImageInfoSize, ImageInfo,
NULL, NULL, NULL, NULL, NULL);
if (EFI_ERROR(Status)) {
Print(L"Error: GetImageInfo failed\n");
FreePool(ImageInfo);
return Status;
}

PrintFirmwareInfo(ImageInfo);
FreePool(ImageInfo);
}
else if (StrCmp(Argv[1], L"-S") == 0 || StrCmp(Argv[1], L"-U") == 0) {
// ===== 升级模式 =====
if (Argc < 3) {
Print(L"Error: Missing firmware file path\n");
return EFI_INVALID_PARAMETER;
}

ImagePath = Argv[2];

// 读取固件文件
Status = ReadFileToBuffer(ImagePath, &FileBuffer, &FileSize);
if (EFI_ERROR(Status)) {
Print(L"Error: Cannot read firmware file: %s\n", ImagePath);
return Status;
}

// -S 模式:先 CheckImage
if (StrCmp(Argv[1], L"-S") == 0) {
Status = Fmp->CheckImage(Fmp, FileBuffer, FileSize, &ImageUpdatable);
if (EFI_ERROR(Status) || ImageUpdatable == 0) {
Print(L"Error: CheckImage failed or image not updatable\n");
FreePool(FileBuffer);
return EFI_ACCESS_DENIED;
}
Print(L"CheckImage passed, proceed to set image...\n");
}

// 执行 SetImage
Print(L"Starting firmware update...\n");
Status = Fmp->SetImage(Fmp, 1, FileBuffer, FileSize, 0, NULL, NULL);

FreePool(FileBuffer);

if (EFI_ERROR(Status)) {
Print(L"Error: Firmware update failed: %r\n", Status);
return Status;
}

Print(L"========================================\n");
Print(L" Firmware update completed!\n");
Print(L" Please reboot to activate new firmware.\n");
Print(L"========================================\n");
}
else {
Print(L"Error: Unknown command: %s\n", Argv[1]);
return EFI_INVALID_PARAMETER;
}

return EFI_SUCCESS;
}

7. 固件镜像格式详解

7.1 镜像头部结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌────────────────────────────────────────────────────────────────────────┐
│ BH799 Firmware Binary Layout │
├────────────────────────────────────────────────────────────────────────┤
│ Offset(h) │ Size │ Field │ Description │
├───────────┼─────────┼───────────────────┼────────────────────────────┤
│ 0x000 │ 0x3C0 │ Image Payload │ 固件实际代码/数据 │
├───────────┼─────────┼───────────────────┼────────────────────────────┤
│ 0x3C0 │ 0x20 │ SHA256 Digest │ 0x3C0-0x3DF (32 bytes) │
│ │ │ │ 校验时该区域置 0 后计算 │
├───────────┼─────────┼───────────────────┼────────────────────────────┤
│ 0x3E0 │ 0x08 │ Signature │ "BH799...." (魔数) │
├───────────┼─────────┼───────────────────┼────────────────────────────┤
│ 0x3E8 │ 0x08 │ FW Revision │ "10100080" (版本字符串) │
├───────────┼─────────┼───────────────────┼────────────────────────────┤
│ 0x3F0 │ 0x10 │ Reserved │ 保留 (原 MD5 区域) │
└────────────────────────────────────────────────────────────────────────┘

总大小:0x400 (1KB) = 固件数据区(0x3C0) + 头部(0x40)

7.2 固件生成脚本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# gen_firmware_header.py - 生成固件头部
import hashlib
import struct

def generate_firmware(input_bin, output_bin, version_str):
with open(input_bin, 'rb') as f:
image_data = f.read()

# 头部预留区
header = bytearray(0x40) # 64 bytes

# 写入签名
signature = b'BH799' + b'\x00' * 3
header[0x00:0x08] = signature

# 写入版本号 (8 bytes, 左对齐)
version_bytes = version_str.encode('ascii')[:8].ljust(8, b'\x00')
header[0x08:0x10] = version_bytes

# SHA256 区域预留 (0x3C0-0x3DF 在完整镜像中)
# 先跳过,不计算

# 合并镜像
full_image = image_data + header

# 计算 SHA256 (头部校验区置零)
sha256_area = bytearray(32)
temp_image = full_image[:0x3C0] + sha256_area + full_image[0x3E0:]

digest = hashlib.sha256(temp_image).digest()

# 填入 SHA256
full_image = full_image[:0x3C0] + digest + full_image[0x3E0:]

with open(output_bin, 'wb') as f:
f.write(full_image)

print(f"Firmware generated: Version={version_str}, SHA256={digest.hex()}")

if __name__ == '__main__':
import sys
generate_firmware('bh799_fw.bin', 'bh799_fw.bin', '10100080')

8. 完整使用流程

8.1 编译与部署

1
2
3
4
5
6
7
8
9
10
:: 1. 编译 EDK2
cd C:\edk2
call edksetup.bat Rebuild
build -p FmpDevicePkg.dsc -a X64 -t VS2019

:: 2. 部署文件到 UEFI 可启动介质
:: 假设 U 盘 ESP 分区为 X:
copy Build\FmpDevicePkg\DEBUG_VS2019\X64\FmpDxe\FmpDxe_226034C4-8B67-4536-8653-D6EE7CE5A316.efi X:\EFI\BOOT\
copy Build\FmpDevicePkg\DEBUG_VS2019\X64\BH799App.efi X:\EFI\BOOT\
copy bh799_fw.bin X:\EFI\BOOT\

8.2 Shell 脚本

BH799FirmwareUpgrade.nsh(带版本策略):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@echo -off
echo Loading BH799 FMP Driver...
load FmpDxe_226034C4-8B67-4536-8653-D6EE7CE5A316.efi

echo Querying current firmware...
BH799App.efi -I

echo Starting firmware upgrade...
BH799App.efi -S bh799_fw.bin

echo.
echo ========================================
echo Firmware upgrade completed!
echo Please restart the system.
echo ========================================

8.3 验证步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Shell> load FmpDxe_226034C4-8B67-4536-8653-D6EE7CE5A316.efi
Shell> BH799App.efi -I
========================================
BH799 Firmware Information
========================================
Image Index: 1
Image ID: BH799-Firmware
Version: 0x10100070 (16.1.0.112)
Lowest Version: 0x10100000
Image Size: 524288 bytes
========================================

Shell> BH799App.efi -S bh799_fw.bin
CheckImage passed, proceed to set image...
Starting firmware update...
========================================
Firmware update completed!
Please reboot to activate new firmware.
========================================

Shell> reset

9. 总结

本文档从 UEFI 开发基础BH799 固件升级完整实现,系统覆盖了:

  1. UEFI 框架:DXE Driver 与 Application 的区别、Protocol 机制
  2. EDK2 构建:INF/DSC 文件结构、库依赖、编译流程
  3. FMP Protocol:规范定义、三大必选接口详解
  4. 设备库实现:NVMe 控制器定位、固件校验(签名+版本+SHA256)、固件烧录
  5. 应用层:命令行解析、FMP 调用、文件读取

上述实现遵循 UEFI 规范 Chapter 23,完全兼容标准 EDK2 框架,可作为其他 UEFI 固件升级项目的参考模板。