NVMe-eMMC-Bridge芯片固件(十):PRP和PRP_List深度解析

NVMe-eMMC-Bridge芯片固件(十):PRP 和 PRP_List 深度解析

日期: 2026-03-21
系列: NVMe-eMMC-Bridge 芯片固件
适用: 固件工程师、存储系统开发者、NVMe 协议开发者


1. 概述

1.1 什么是 PRP?

PRP (Physical Region Page) 是 NVMe 协议中用于描述主机内存中数据区域的 64 位物理地址结构。在 NVMe 命令中,PRP 告诉控制器从哪里读取或写入数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────────────────────┐
│ PRP (Physical Region Page) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 64-bit Physical Address │ │
│ │ │ │
│ │ ┌───────────────────────┬───────────────────────┐ │ │
│ │ │ Bits [63:12] │ Bits [11:0] │ │ │
│ │ │ Page Frame Address │ Offset in Page │ │ │
│ │ │ (物理页基地址) │ (页内偏移) │ │ │
│ │ └───────────────────────┴───────────────────────┘ │ │
│ │ │ │
│ │ 页大小由 CAP.MPSMAX 决定,通常为 4KB │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

1.2 BayBridge 系统中的内存布局

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
┌─────────────────────────────────────────────────────────────────────┐
│ BayBridge 控制器内存布局 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 控制器寄存器空间 │ │
│ │ (PCIe BAR0) │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ CMD_BUFFER │ │ DATA_BUFFER │ │ │
│ │ │ (1KB) │ │ (4KB) │ │ │
│ │ │ │ │ │ │ │
│ │ │ NVMe 命令 │ │ DMA 数据缓冲 │ │ │
│ │ │ 存放地址 │ │ │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 外部 SPI Flash / eMMC │ │
│ │ 固件存储空间 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 主机系统内存 (Host Memory) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ PRP / PRP List │ │ │
│ │ │ ┌─────────────────────────────────────────────────┐ │ │ │
│ │ │ │ PRP Entry 0: 物理地址 + 偏移 │ │ │ │
│ │ │ │ PRP Entry 1: 物理地址 + 偏移 │ │ │ │
│ │ │ │ ... │ │ │ │
│ │ │ └─────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ 数据缓冲区 │ │ │
│ │ │ 实际的固件数据存放在这里 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

2. NVMe 规范中的 PRP 定义

2.1 NVMe Submission Queue 命令格式

根据 NVMe 1.4 规范,命令结构包含两个 PRP 字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// NVMe 命令结构 (Nvme.h)
typedef struct {
UINT8 Opc; // Opcode
UINT8 Fuse : 2; // Fused Operation
UINT8 Rsvd1 : 5;
UINT8 Psdt : 1; // PRP or SGL for Data Transfer
UINT16 Cid; // Command Identifier

UINT32 Nsid; // Namespace Identifier
UINT64 Rsvd2;
UINT64 Mptr; // Metadata Pointer

// ★ CDW 6-7: 第一个 PRP 条目 (PRP1)
UINT64 Prp[2]; // First and second PRP entries

UINT64 Payload; // Inline payload (如果 PSDT = 1)
} NVME_SQ;

2.2 PRP 字段说明

字段 说明
PRP1 第一个物理区域页,作为数据传输的首选位置
PRP2 第二个物理区域页,可以是:
• 另一个 PRP (指向数据)
• PRP List (指向多个 PRP)

2.3 PRP 数据结构

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
┌─────────────────────────────────────────────────────────────────────┐
│ PRP Entry 结构 (64-bit) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Byte 7 Byte 6 Byte 5 Byte 4 │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ [63:56] │ [55:48] │ [47:40] │ [39:32] │ │
│ └─────────┴─────────┴─────────┴─────────┘ │
│ │
│ Byte 3 Byte 2 Byte 1 Byte 0 │
│ ┌─────────┬─────────┬─────────┬─────────┐ │
│ │ [31:24] │ [23:16] │ [15:8] │ [7:0] │ │
│ └─────────┴─────────┴─────────┴─────────┘ │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PRP Entry 解析: │ │
│ │ │ │
│ │ Bits [63:12]: Page Frame Address (物理页基地址) │ │
│ │ 必须按 4KB (或 MPS 指定的页大小) 对齐 │ │
│ │ │ │
│ │ Bits [11:0]: Offset in Page (页内偏移) │ │
│ │ 指示数据在页内的起始位置 │ │
│ │ │ │
│ │ 注意: 如果偏移为 0,PRP 指向整个页面 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

3. PRP_List 机制

3.1 为什么需要 PRP_List?

当传输的数据量超过一个内存页时,PRP1 无法容纳所有数据。此时:

  • PRP1: 指向第一个页的数据
  • PRP2: 指向一个 PRP_List (PRP 页)
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
┌─────────────────────────────────────────────────────────────────────┐
│ PRP_List 工作原理 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 场景: 传输 12KB 数据 │
│ ──────────────────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ NVMe 命令中的 PRP 字段 │ │
│ │ │ │
│ │ PRP1 ──► ┌─────────────────────────────────────────────┐ │ │
│ │ │ Host Memory Page 0 │ │ │
│ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ │ │ 数据区域 (4KB) │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ PRP2 ──► ┌─────────────────────────────────────────────┐ │ │
│ │ │ PRP_List (也是一页,4KB) │ │ │
│ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ │ │ PRP Entry 0 ──► Page 1 │ │ │ │
│ │ │ │ PRP Entry 1 ──► Page 2 │ │ │ │
│ │ │ │ PRP Entry 2 ──► Page 3 │ │ │ │
│ │ │ │ (每个 4KB,共 8KB) │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 总传输: 4KB (PRP1) + 4KB (PRP_List[0]) + 4KB (PRP_List[1]) = 12KB │
│ │
└─────────────────────────────────────────────────────────────────────┘

3.2 PRP_List 条目规则

根据 NVMe 规范,PRP_List 中的条目必须满足:

规则 说明
页对齐 每个 PRP Entry 的地址必须按页大小 (4KB) 对齐
偏移为 0 PRP_List 中的条目偏移必须为 0
页大小 除最后一个条目外,每个条目代表一个完整的页

3.3 BayBridge 中的 PRP_List 实现

1
2
3
4
5
6
// controller.h - BayBridge PRP 配置
#define FW_HEAD_LEN_BYTE 1024 // 固件头 1KB
#define DATA_BUFFER_SIZE (4*1024) // 4KB 数据缓冲区
#define PRP_SIZE (4*1024) // 4KB PRP 页大小
#define PRP_LIST_SIZE (4*512) // 2KB PRP List 大小
#define DATA_BLOCK_SIZE 512 // eMMC 块大小
1
2
3
4
5
6
7
8
9
10
// firmware_update_data_t 结构
typedef struct {
u32 *buffer; // 数据缓冲区 (4KB FIFO)
u32 *prp_list; // PRP List 缓冲区 (512 DWORDs = 2KB)

u8 prp_list_flag; // 标志: 是否使用 PRP_List
u16 offset_in_page; // PRP1 的页内偏移
u32 size; // 总下载数据大小
u32 offset; // 累计偏移
} firmware_update_data_t;

4. DMA 数据传输核心实现

4.1 dma_data_transfer 函数架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// controller.c:566 - DMA 数据传输核心函数
int dma_data_transfer(
u64 prp_entry1, // PRP1 (64位物理地址)
u64 prp_entry2, // PRP2 (可以是 PRP 或 PRP_List)
u32 size, // 数据大小
u8 clear_data_buffer_flag,
u8 is_write_to_system_memory // 传输方向
)
{
u32 offset_in_page = prp_entry1 & 0xfff; // 提取页内偏移

// 根据数据大小和跨页情况分三种处理:
//
// 情况 1: 数据跨越多个页面 → PRP2 包含 PRP_List
// 情况 2: 数据跨越 1 个页面 → PRP2 是 PRP 基地址
// 情况 3: 数据不跨页 → 直接 DMA
}

4.2 三种 DMA 场景详解

场景 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
┌─────────────────────────────────────────────────────────────────────┐
│ 场景 1: 单页数据传输 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 数据大小 ≤ 4KB 且不跨页 │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ NVMe 命令: │ │
│ │ │ │
│ │ PRP1 = 0x1000_0000 (含 offset) │ │
│ │ PRP2 = 0 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ DMA 操作: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 主机内存 ──DMA──► BayBridge DATA_BUFFER (4KB) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 代码: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ret = start_DMA1(prp_entry1, size, offset, ...); │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

场景 2: 两页数据传输 (跨一页)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────────┐
│ 场景 2: 两页数据传输 (跨一页) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 4KB < 数据大小 ≤ 8KB │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ NVMe 命令: │ │
│ │ │ │
│ │ PRP1 = 0x1000_0000 (Page 0) │ │
│ │ PRP2 = 0x2000_0000 (Page 1 基地址) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ DMA 操作: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Page 0 ──DMA0──► DATA_BUFFER ──DMA1──► Page 1 │ │
│ │ │ │
│ │ 1. start_DMA0(prp_entry1, ...) DMA 读 │ │
│ │ 2. start_DMA1(prp_entry2, ...) DMA 写 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

场景 3: 多页数据传输 (使用 PRP_List)

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
┌─────────────────────────────────────────────────────────────────────┐
│ 场景 3: 多页数据传输 (使用 PRP_List) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 数据大小 > 8KB │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ NVMe 命令: │ │
│ │ │ │
│ │ PRP1 = 0x1000_0000 (数据页 0) │ │
│ │ PRP2 = 0x3000_0000 (PRP_List 页面) │ │
│ │ │ │
│ │ PRP_List 内容: │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ Entry 0: 0x4000_0000 (数据页 1) │ │ │
│ │ │ Entry 1: 0x5000_0000 (数据页 2) │ │ │
│ │ │ ... │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ DMA 操作: │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. DMA PRP1 ──► DATA_BUFFER │ │
│ │ 2. DMA PRP_List[0] ──► DATA_BUFFER │ │
│ │ 3. DMA PRP_List[1] ──► DATA_BUFFER │ │
│ │ ... │ │
│ │ │ │
│ │ 每页数据都需要单独的 DMA 操作 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

4.3 start_DMA1 函数实现

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
// controller.c:514 - DMA1 传输函数
int start_DMA1(
u64 prp, // 物理地址
u32 size, // 数据大小
u16 data_buf_addr, // DATA_BUFFER 内偏移
u8 is_write_to_system_memory, // 方向
u32 timeout
)
{
// 1. 设置 DMA 源/目的地址
BAYBRIDGE_DMA1_CTRL_4 = prp >> 32; // 高 32 位
BAYBRIDGE_DMA1_CTRL_3 = prp; // 低 32 位

// 2. 设置 DATA_BUFFER 内偏移
BAYBRIDGE_DMA1_CTRL_2 = data_buf_addr & 0xfff;

// 3. 配置 DMA 控制寄存器
if(is_write_to_system_memory) {
// 写: DATA_BUFFER ──► 主机内存
BAYBRIDGE_DMA1_CTRL_1 = START_DMA_BIT |
(DMA_DIRECTION_WRITE_TO << DMA_DIRECTION_SHIFT) |
(size & DMA_TRANSFER_SIZE_IN_BYTE_MASK_BIT);
} else {
// 读: 主机内存 ──► DATA_BUFFER
BAYBRIDGE_DMA1_CTRL_1 = START_DMA_BIT |
(DMA_DIRECTION_READ_FROM << DMA_DIRECTION_SHIFT) |
(size & DMA_TRANSFER_SIZE_IN_BYTE_MASK_BIT);
}

// 4. 等待 DMA 完成
while(!(BAYBRIDGE_INTERNAL_INTERRUPT_STATUS & DMA1_INTERRUPT_STATUS_BIT)
&& timeout > 0) {
timeout--;
}

// 5. 清除中断标志
BAYBRIDGE_INTERNAL_INTERRUPT_STATUS = DMA1_INTERRUPT_STATUS_BIT;

return 0;
}

5. PRP_List 解析实现

5.1 PRP_List 解析核心代码

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
// firmware_image_download.c - PRP_List 解析
for(i = 0; i < prp_total_cnt; ++i) {
// ★ 关键: 从 prp_list 缓冲区读取 64 位 PRP Entry
// 由于 PCIe 传输是大端序,需要字节序转换
prp_entry = ((u64)leon2_cpu_32bits_swap(global_fw_update_data.prp_list[2*i+1])) << 32 |
leon2_cpu_32bits_swap(global_fw_update_data.prp_list[2*i]);

LOG_TEST(TEST_FW_DOWNLOAD, "prp_entry[%d]: 0x%llx\n", i, prp_entry);

// 检查 PRP Entry 偏移
if((prp_entry & 0xfff) == 0) {
// 偏移为 0: 正常 PRP,指向一个页

if(i != prp_total_cnt - 1) {
// 非最后一个 PRP: 大小 = 页大小 (4KB)
prp_data_size = PRP_SIZE;
} else {
// 最后一个 PRP: 大小可能 < 4KB
data_size = (size + global_fw_update_data.offset_in_page) % PRP_SIZE;
prp_data_size = data_size ? data_size : PRP_SIZE;
}

// 执行 DMA 传输
dma_data_transfer(prp_entry, 0, prp_data_size, 0, 0);

} else {
// 偏移非 0: 指向另一个 PRP_List (多级 PRP)
LOG_TEST(TEST_FW_DOWNLOAD, "PRP list's entry offset non zero!\n");
// 当前 BayBridge 固件最大支持 160KB,暂不需要多级 PRP
}
}

5.2 PRP 条目数计算

1
2
3
4
5
6
7
8
9
10
11
// 计算需要的 PRP 条目数
u32 data_size = total_size + offset_in_page;
u32 prp_total_cnt = data_size / PRP_SIZE;

// 如果有余数,需要额外一个 PRP
if(data_size % PRP_SIZE != 0) {
prp_total_cnt++;
}

// 注意: PRP1 已经传输了一个页,需要从总数中减 1
prp_total_cnt--;

5.3 大小端转换问题

1
2
3
4
5
6
7
8
9
10
11
12
// 关键: PCIe 传输使用大端序,但处理器是小端序
// 需要进行 64 位字节序转换

// PCIe DMA 读取的原始数据 (大端):
// prp_list[2*i+1] = 高 32 位: 0x1234_5678
// prp_list[2*i] = 低 32 位: 0xABCD_EFGH

// 转换为小端 64 位地址:
prp_entry = ((u64)leon2_cpu_32bits_swap(prp_list[2*i+1])) << 32 |
leon2_cpu_32bits_swap(prp_list[2*i]);

// 相当于: 0x89ABCDEF_12345678

6. 固件下载场景分析

6.1 BayBridge 支持的固件下载场景

场景 说明 PRP 处理
SPI Flash 下载 下载到外部 SPI Flash DMA → RAM Buffer → SPI
eMMC Boot 分区 下载到 eMMC 启动分区 DMA → RAM Buffer → eMMC
eMMC FFU 在线升级 eMMC 固件 DMA → eMMC (直接)
PowerShell FFU Windows PowerShell FFU PRP List → 多 DMA

6.2 SPI Flash 下载流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────┐
│ SPI Flash 下载流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. NVMe 命令解析 │
│ └─► PRP1/PRP2 提取 │
│ │
│ 2. DMA 传输 (主机内存 → DATA_BUFFER) │
│ └─► start_DMA1(prp_entry, ...) │
│ │
│ 3. 拷贝到固件缓冲区 │
│ └─► fn_memcpy_4bytes(buffer, DATA_BUFFER, 4KB) │
│ │
│ 4. 写入 SPI Flash │
│ └─► spi_flash_block_safe_write(spi_addr, buffer, size) │
│ │
│ 5. 重复步骤 2-4 直到所有 PRP_List 处理完成 │
│ │
└─────────────────────────────────────────────────────────────────────┘
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
// SPI Flash 下载核心代码
static void do_download_spi_flash(u32 size, u32 offset)
{
u32 *spi_addr = (u32 *)BAYBRIDGE_EEPROM_BASE;
u32 prp_total_cnt;
u64 prp_entry;

// 设置 SPI 模式
set_pinshare_mode(MODE_SPI);

// PRP_List 解析
if(prp_list_flag == 1) {
// 计算 PRP 条目数
data_size = size + offset_in_page;
prp_total_cnt = data_size / PRP_SIZE +
(data_size % PRP_SIZE == 0 ? 0 : 1);
prp_total_cnt--; // 减去 PRP1

// 遍历每个 PRP
for(i = 0; i < prp_total_cnt; i++) {
// 读取 PRP Entry
prp_entry = ((u64)swap(prp_list[2*i+1])) << 32 |
swap(prp_list[2*i]);

// DMA 传输
dma_data_transfer(prp_entry, 0, prp_data_size, 0, 0);

// 拷贝到缓冲区
fn_memcpy_4bytes(buffer, DATA_BUFFER, DATA_BUFFER_SIZE);

// 写入 SPI Flash
spi_flash_block_safe_write(spi_addr, buffer, prp_data_size);
spi_addr += (prp_data_size >> 2);
}
}
}

6.3 eMMC FFU 下载流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────────────────────┐
│ eMMC FFU 下载流程 (PowerShell) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 与 SPI Flash 下载的主要区别: │
│ ───────────────────────────────────────────────────── │
│ • 不需要中间的固件缓冲区拷贝 │
│ • 每个 PRP DMA 后直接调用 FFU │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ PRP DMA ──► DATA_BUFFER ──► eMMC DMA (ffu_download) │ │
│ │ │ │ │
│ │ └── 不需要缓冲区拷贝,直接传输 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
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
// eMMC FFU 下载核心代码
static void do_ffu_process(u32 size, u32 offset)
{
u32 prp_total_cnt;
u64 prp_entry;

// PRP_List 解析
if(prp_list_flag == 1) {
// 计算 PRP 条目数
data_size = size + offset_in_page;
prp_total_cnt = data_size / PRP_SIZE +
(data_size % PRP_SIZE == 0 ? 0 : 1);
prp_total_cnt--;

// ★ 关键: 首次 FFU 配置
if(is_ffu_case == FFU_DEFAULT) {
// 跳过 Bayhub 额外头,提取纯 eMMC 固件
data_size = DATA_BUFFER_SIZE - offset_in_page - FW_HEAD_LEN_BYTE;
fn_memcpy_4bytes(DATA_BUFFER,
&buffer[FW_HEAD_LEN_BYTE >> 2],
data_size);

// 启动首次 FFU DMA
ffu_download(emmc_id, 1, data_size); // 1 = 首次配置
}

// ★ 关键: 每个 PRP 立即 FFU
for(i = 0; i < prp_total_cnt; i++) {
// 读取 PRP Entry
prp_entry = ((u64)swap(prp_list[2*i+1])) << 32 |
swap(prp_list[2*i]);

// DMA 传输
dma_data_transfer(prp_entry, 0, prp_data_size, 0, 0);

// ★ 直接 FFU,不需要缓冲区拷贝!
ffu_download(emmc_id, 0, prp_data_size); // 0 = 继续
}
}
}

6.4 关键区别对比

特性 SPI Flash 下载 eMMC FFU
中间缓冲 需要固件缓冲区 不需要
数据传输 DMA → Buffer → Flash DMA → eMMC
缓冲区拷贝 fn_memcpy_4bytes()
写入方式 spi_flash_block_safe_write() ffu_download()
性能 较慢 (多一步) 较快 (直接)

7. 双 DMA 通道架构

7.1 DMA 通道说明

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
┌─────────────────────────────────────────────────────────────────────┐
│ BayBridge 双 DMA 通道架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PCIe DMA 通道 │ │
│ │ │ │
│ │ DMA0: 主机内存 ──► DATA_BUFFER (读) │ │
│ │ DMA1: DATA_BUFFER ──► 主机内存 (写) │ │
│ │ │ │
│ │ 寄存器: │ │
│ │ • DMA0_CTRL_1~4: DMA0 控制 │ │
│ │ • DMA1_CTRL_1~4: DMA1 控制 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ DATA_BUFFER (4KB FIFO) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ DMA 读/写共享的缓冲区 │ │ │
│ │ │ • NVMe 命令数据 │ │ │
│ │ │ • eMMC/SPI 传输数据 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ ▼ ▼ │
│ ┌───────────────────────┐ ┌───────────────────────┐ │
│ │ eMMC DMA 通道 │ │ SPI Flash 控制器 │ │
│ │ │ │ │ │
│ │ • 固件升级 (FFU) │ │ • 固件存储 │ │
│ │ • 读/写数据 │ │ • 配置存储 │ │
│ └───────────────────────┘ └───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

7.2 双 DMA 协同工作

1
2
3
4
5
6
7
8
9
// 双 DMA 传输示例: NVMe → DATA_BUFFER → eMMC

// 步骤 1: NVMe DMA (PCIe) - DMA0 读
start_DMA0(prp_entry, DATA_BUFFER_SIZE, 0, DMA_DIRECTION_READ_FROM, timeout);

// 步骤 2: eMMC DMA - 写 eMMC
ffu_download(emmc_id, 0, DATA_BUFFER_SIZE); // 直接从 DATA_BUFFER DMA

// 注意: 两个 DMA 不能同时执行,需要串行处理

8. 实际案例分析

8.1 PowerShell FFU 下载解析

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
┌─────────────────────────────────────────────────────────────────────┐
│ PowerShell FFU PRP_List 解析示例 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 场景: 下载 160KB eMMC 固件 │
│ │
│ 假设: │
│ • offset_in_page = 1024 (PRP1 偏移 1KB) │
│ • total_size = 160KB │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 数据大小计算: │
│ data_size = 160KB + 1KB = 161KB │
│ │
│ PRP 条目数计算: │
│ prp_total_cnt = 161KB / 4KB = 40.25 → 41 │
│ prp_total_cnt-- = 40 (减去 PRP1) │
│ │
│ ───────────────────────────────────────────────────────────────── │
│ │
│ 传输顺序: │
│ │
│ 1. PRP1 (已在之前 DMA) → eMMC (跳过 1KB 头) │
│ │
│ 2-41. PRP_List[0-39] → DATA_BUFFER → eMMC │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PRP_List[0]: 4KB ──► DATA_BUFFER ──► eMMC │ │
│ │ PRP_List[1]: 4KB ──► DATA_BUFFER ──► eMMC │ │
│ │ ... │ │
│ │ PRP_List[39]: 4KB ──► DATA_BUFFER ──► eMMC │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

8.2 关键代码注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* BayBridge FW 下载 PRP 处理注意事项:
*
* 1. PRP_List 条目偏移必须为 0
* 根据 NVMe 规范,PRP_List 中的条目偏移必须为 0。
* 如果偏移非 0,表示这是一个指向另一个 PRP_List 的指针。
*
* 2. 最后一个 PRP 的大小计算
* 最后一个 PRP 可能小于 4KB。
* 计算公式: data_size = (total_size + offset_in_page) % PRP_SIZE
* 注意: 如果余数为 0,实际大小是 4KB!
*
* 3. DMA 大小不能超过 PRP 指定
* DMA 传输的数据大小不能超过 PRP Entry 指定的大小,
* 否则会导致固件命令失败。
*
* 4. 大端序转换
* PCIe 传输使用大端序,需要进行字节序转换。
* 使用 leon2_cpu_32bits_swap() 进行转换。
*/

9. 总结

9.1 PRP 机制要点

要点 说明
PRP 结构 64 位物理地址 = 页基址 + 页内偏移
PRP1 第一个数据页 (或 PRP_List 指针)
PRP2 第二个 PRP 或 PRP_List
PRP_List 当数据 > 1 页时使用,存放多个 PRP 条目
页对齐 所有 PRP 地址按 4KB 对齐

9.2 BayBridge 实现要点

要点 说明
DATA_BUFFER 4KB 共享缓冲区,DMA 中转
双 DMA DMA0 (读) + DMA1 (写) 通道
大端转换 PCIe 传输使用大端,CPU 使用小端
FFU 优化 直接 DMA 到 eMMC,无需缓冲区拷贝

9.3 调试检查清单

  • PRP 地址对齐检查 (4KB 对齐)
  • PRP_List 条目偏移检查 (必须为 0)
  • 最后一个 PRP 大小计算正确性
  • 大端序转换正确性
  • DMA 超时处理
  • 传输方向正确性 (读/写)

参考资料

  1. NVMe Specification 1.4 - NVM Express, Inc.
  2. PCI Express Base Specification - PCI-SIG
  3. JEDEC eMMC Specification JESD84-B51 - JEDEC
  4. BayBridge 固件代码: C:\gitlab-bht\baybridge\src\nvme\firmware_image_download.c

相关文档

文档 说明
NVMe-eMMC-Bridge芯片固件_01_硬件和软件架构.md BayBridge 芯片整体架构
本文档 PRP/PRP_List 深度解析

文档生成日期: 2026-03-21
固件版本: BayBridge v1.0+