1. 1. 1. Firmware 整体架构
    1. 1.1. 1.1 软件分层架构
    2. 1.2. 1.2 Firmware 核心职责
  2. 2. 2. Main 函数主循环
    1. 2.1. 2.1 Firmware 初始化流程
    2. 2.2. 2.2 主循环处理流程
    3. 2.3. 2.3 关键代码片段
  3. 3. 3. NVMe 命令处理流程
    1. 3.1. 3.1 NVMe 规范:Host ↔ Controller 命令处理全过程(Doorbell 机制)
    2. 3.2. 3.2 SQ / CQ:环形队列、队空 / 队满与生产者-消费者模型
      1. 3.2.1. 3.2.1 生产者与消费者角色
      2. 3.2.2. 3.2.2 队空(Empty)
      3. 3.2.3. 3.2.3 队满(Full)
      4. 3.2.4. 3.2.4 获取与提交流程(与 3.1 对应)
    3. 3.3. 3.3 PRP Entry 与 PRP List 布局(数据缓冲区描述)
      1. 3.3.1. 3.3.1 单个 PRP Entry(64 bit)位域概念图
      2. 3.3.2. 3.3.2 命令中的 PRP1 / PRP2 与 PRP List 关系(示意)
      3. 3.3.3. 3.3.3 与本固件代码的对应
    4. 3.4. 3.4 固件侧命令获取流程(NVMe 1.4 Figure 90 与 BayBridge CMD Buffer)
    5. 3.5. 3.5 Admin 命令列表(NVMe 1.4 Spec Figure 139)
    6. 3.6. 3.6 IO 命令列表(NVMe 1.4 Spec Figure 186)
    7. 3.7. 3.7 命令解析代码
  4. 4. 4. Read/Write 命令处理
    1. 4.1. 4.1 NVMe 到 eMMC 的地址映射
    2. 4.2. 4.2 Read/Write 处理流程
    3. 4.3. 4.3 任务信息结构
  5. 5. 5. eMMC Command Queuing 模式
    1. 5.1. 5.1 CQ 模式流程(eMMC 5.1 Spec Section 6.6.40-44)
    2. 5.2. 5.2 任务状态机
    3. 5.3. 5.3 CQ 模式核心代码
  6. 6. 6. Completion 发送机制
    1. 6.1. 6.1 NVMe Completion 流程(NVMe 1.4 Spec Figure 91)
    2. 6.2. 6.2 中断聚合策略
  7. 7. 7. 数据传输流程
    1. 7.1. 7.1 Host ↔ Controller ↔ eMMC 数据流
    2. 7.2. 7.2 PRP 处理(NVMe 1.4 Spec Section 4.4)
  8. 8. 8. 总结
    1. 8.1. 8.1 设计要点
    2. 8.2. 8.2 关键性能参数
  9. 9. 参考资料

NVMe-eMMC Bridge 芯片固件开发(三):Firmware 主流程与 NVMe 协议栈

1. Firmware 整体架构

1.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
┌─────────────────────────────────────────────────────────────────────┐
│ Host (NVMe Driver) │
├─────────────────────────────────────────────────────────────────────┤
│ PCIe Interface │
╔═════════════════════════════════════════════════════════════════════╗
║ BayBridge Firmware ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ NVMe Protocol Layer (NVMe 1.4) │ ║
║ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ ║
║ │ │ Admin Cmds │ │ IO Cmds │ │ Completion Engine │ │ ║
║ │ │ Identify │ │ Read/Write │ │ MSI/MSI-X/INTx │ │ ║
║ │ │ Get/Set │ │ Flush │ │ Interrupt Coal. │ │ ║
║ │ │ FW Update │ │ TRIM │ │ │ │ ║
║ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ Task Scheduling Layer │ ║
║ │ ┌──────────────────────┐ ┌────────────────────────────┐ │ ║
║ │ │ Task Queue Manager │ │ Power Management (APST) │ │ ║
║ │ │ PRE_CANDIDATE → │ │ L0/L1.2 State Machine │ │ ║
║ │ │ CANDIDATE → READY → │ │ LTR Configuration │ │ ║
║ │ │ EXECUTING → DONE │ │ │ │ ║
║ │ └──────────────────────┘ └────────────────────────────┘ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ eMMC Protocol Layer (eMMC 5.1) │ ║
║ │ ┌─────────────────────┐ ┌───────────────────────────────┐ │ ║
║ │ │ Command Queuing │ │ Non-CQ Mode (Legacy) │ │ ║
║ │ │ CMD44/45: Add Task │ │ CMD17/18: Single/Multi Rd │ │ ║
║ │ │ CMD46/47: Execute │ │ CMD24/25: Single/Multi Wr │ │ ║
║ │ │ CMD13: Query QSR │ │ │ │ ║
║ │ └─────────────────────┘ └───────────────────────────────┘ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
╚═════════════════════════════════════════════════════════════════════╝
├─────────────────────────────────────────────────────────────────────┤
│ eMMC Interface (HS400) │
├──────────────────────────────┬──────────────────────────────────────┤
│ eMMC #0 │ eMMC #1 │
└──────────────────────────────┴──────────────────────────────────────┘

1.2 Firmware 核心职责

模块 功能 协议依据
NVMe 协议处理 Admin/IO 命令解析与执行 NVMe 1.4 Spec
eMMC 控制 Command Queuing 读写操作 eMMC 5.1 Spec
数据流调度 Host ↔ Controller ↔ eMMC 流水线 -
电源管理 PCIe L1.2、APST 状态机 NVMe 1.4 Chapter 8
中断处理 MSI/MSI-X 中断派发 NVMe 1.4 Chapter 7

2. Main 函数主循环

2.1 Firmware 初始化流程

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
    ┌─────────────────────┐
│ Bootloader Exit │
│ Jump to main() │
└──────────┬──────────┘

┌─────────────────────┐
│ CAP Timeout 设置 │ NVMe CAP.TO = 60s
└──────────┬──────────┘

┌─────────────────────┐
│ FW Checksum 校验 │ 验证固件完整性
└──────────┬──────────┘

┌─────────────────────┐
│ 中断系统配置 │ 安装 ISR, 屏蔽中断
└──────────┬──────────┘

┌─────────────────────┐
│ 电源管理初始化 │ L1.2, LTR 配置
└──────────┬──────────┘

┌─────────────────────┐
│ NVMe 中断模式初始化 │ MSI/MSI-X/INTx
└──────────┬──────────┘

┌─────────────────────┐
│ eMMC 卡初始化 │ HS400, CQ Enable
└──────────┬──────────┘

┌─────────────────────┐
│ Namespace 初始化 │ 容量映射
└──────────┬──────────┘

┌─────────────────────┐
│ SMART 信息初始化 │ 读取 eMMC 保留区
└──────────┬──────────┘

┌─────────────────────────────┐
│ 进入 while(1) 主循环 │
└─────────────────────────────┘

2.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
┌─────────────────────────────────────────────────────────────────────┐
│ Main Loop (while 1) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ ① Timer │───►│ ② Interrupt │───►│ ③ NS Change Poll │ │
│ │ Tasks │ │ Process │ │ │ │
│ └─────────────┘ └─────────────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ │ ┌─────────────┴─────────────┐ │ │
│ │ │ CMD Buffer 就绪? │ │ │
│ │ │ → command_parse() │ │ │
│ │ └───────────────────────────┘ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ④ eMMC Read/Write Processing │ │
│ │ CQ Mode: add_task_to_cq() → execute_ready_task() │ │
│ │ Non-CQ: emmc_read_write() │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ⑤ send_completion_task() │ │
│ │ 遍历 COMPLETED 任务,发送 CQE + MSI │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ ⑥ Dataset │───►│ ⑦ Flush │───►│ ⑧ SMART Update │ │
│ │ Mgmt │ │ Commands │ │ │ │
│ └─────────────┘ └─────────────┘ └──────────┬──────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ⑨ Power Management │ │
│ │ FW Status → APST Timer → Shutdown 检测 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Loop 继续 │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘

2.3 关键代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
while (1) {
// ① 定时器任务:Enter OS Timer 超时后清除 KeepL0
if (T0_Counter[OS_READY].end_flag && async_event_received) {
pm.set_internalstate(&pm_dscp, ID_KEEP_L0, 0); // 允许进入 L1
}

// ② 中断事件处理(CMD Buffer 就绪时触发 command_parse)
interrupt_process();

// ④ eMMC 读写处理
if (global_admin_command_req_dma_channel_flag == 0) {
emmc_read_write_cq_mode(); // 或 non_cq_mode
}

// ⑤ Completion 发送
send_completion_task();

// ⑨ Shutdown 处理
if (shutdown_status == 1) {
smart_info_writeback(&smart_rw, &smart_data);
emmc_power_off_notification(3);
shutdown_status_set(SHUTDOWN_PROCESSING_COMPLETE);
}
}

3. NVMe 命令处理流程

本章先按 NVM Express 规范说明 Host 与 Controller 之间的**控制面(SQ/CQ、Doorbell)数据面(PRP)**如何配合;再对照 BayBridge 固件中「CMD Buffer → command_parse()」的实现。

3.1 NVMe 规范:Host ↔ Controller 命令处理全过程(Doorbell 机制)

NVMe 将命令与完成状态放在 Host 内存中的环形队列里,通过 Doorbell 寄存器做生产者/消费者同步。规范中典型的 8 步流程(对应规范图示如 Command Processing / §7.2.1 Command Processing)如下。

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
┌──────────────────────────────────────────────────────────────────────────────┐
│ Host 内存 │ Controller(本芯片 = NVMe 目标端) │
├────────────────────────────────────┼──────────────────────────────────────────┤
│ │ │
│ ① Host 将一个或多个 SQ Entry │ │
│ 写入 SQ 的可用槽位 │ │
│ │ │ │
│ ▼ │ │
│ ② Host 写 SQ Tail Doorbell │◄── 通知「有新命令待取」 │
│ (更新尾指针) │ │
│ │ │ │
│ │ │ ③ Controller 通过 DMA 从 Host SQ │
│ │ │ 取走命令到内部(可多 SQ 仲裁) │
│ │ │ │
│ │ │ ④ Controller 执行命令 │
│ │ │ (IO 可乱序完成;数据按 PRP/SGL) │
│ │ │ │
│ ⑤ Controller 将 CQE 写入关联 CQ │ │
│ (含 SQ Head 推进信息) │──► 写 Host 内存 │
│ ▲ │ │
│ │ │ ⑥ 可选:MSI/MSI-X 等中断通知 Host │
│ │ │ │
│ ⑦ Host 读 CQ,按 Phase Tag 识别 │ │
│ 新完成项并处理 │ │
│ │ │ │
│ ⑧ Host 写 CQ Head Doorbell │◄── 告知已消费完成项,释放 CQ 语义 │
│ │ │
└────────────────────────────────────┴──────────────────────────────────────────┘

核心要点(与固件/驱动实现强相关):

要点 说明
控制路径 命令正文在 Host 的 SQ;完成记录在 CQ;双方只靠 DoorbellDMA 同步,无额外「边带」传命令。
SQ Host 生产(写 SQE、更新 Tail Doorbell);Controller 消费(DMA 取命令、内部维护/推进 SQ Head,并在 CQE 中反映)。
CQ Controller 生产(写 CQE);Host 消费(读 CQ、更新 CQ Head Doorbell)。
Phase Tag CQ 项中带 Phase,每轮绕队列反转一次,Host 用它区分「新 CQE」与「上一轮残留」,避免环形缓冲绕回歧义。
数据路径 IO 命令中的 PRP(或 SGL) 描述 Host 侧 DMA 缓冲区;执行阶段 Controller 按 SLBA/NLB 访问介质,按 PRP 在 Host 内存 ↔ 介质 间搬数据。本桥片中介质为 eMMC,见第 4、5 章。

与本项目固件的对应关系:规范中的步骤 ③④ 在 BayBridge 上体现为硬件将 SQE 拉入 CMD Buffer 并产生中断,固件在 command_parse() 中解析;步骤 ⑤⑧ 对应 send_complete() 写 CQE 与门铃/中断配合(见第 6 章)。


3.2 SQ / CQ:环形队列、队空 / 队满与生产者-消费者模型

NVMe 中 SQ、CQ 均为 Host 内存中的环形缓冲区,用 Head(消费者)Tail(生产者) 描述当前读写位置(具体寄存器名因实现而异,语义与规范一致)。

3.2.1 生产者与消费者角色

1
2
3
4
5
6
7
                 Submission Queue (SQ)              Completion Queue (CQ)
─────────────────── ─────────────────────

Producer ──► Host(写 SQE,动 Tail) Controller(写 CQE)
Consumer ◄── Controller(取 SQE,动 Head) Host(读 CQE,动 Head)

Doorbell ◄── SQ Tail Doorbell(Host 写) CQ Head Doorbell(Host 写)

3.2.2 队空(Empty)

规范定义:Head == Tail 时队列为空——没有待消费的条目。

1
2
3
4
5
6
7
8
9
10
11
12
Queue Base


┌───────┐
│ empty │
├───────┤
│ empty │ ◄── Head (Consumer)
├───────┤
│ empty │ ◄── Tail (Producer) Head 与 Tail 同槽 → 无新命令/无新完成
├───────┤
│ empty │
└───────┘

3.2.3 队满(Full)

为区分「满」与「空」,环形队列通常 保留一个空槽:当 Tail 的下一格将等于 Head(考虑绕回,即 (Tail+1) mod Size == Head)时视为 。等价表述常见为:最多可存放 (队列深度 − 1) 条有效项

1
2
3
4
5
6
7
8
9
┌─────────┐
│occupied │ ◄── Head:下一待处理
├─────────┤
│occupied │
├─────────┤
│occupied │
├─────────┤
│ empty │ ◄── Tail:下一写入位置(与 Head 相隔一格「哨兵」→ 满)
└─────────┘

规范侧要点摘录:

  • 队列深度多为 0’s based 编码:寄存器值 n 表示 n+1 个槽位。
  • 每个队列因 Head/Tail 定义会少用一个槽位(不可用满全部物理槽区分空/满)。
  • IO SQ/CQ 最大可达 64K 量级(仍受 CAP.MQES 等能力限制);Admin 队列上限更小(如 4K 级),以具体 Spec 为准。

3.2.4 获取与提交流程(与 3.1 对应)

  1. Host 提交命令:在 SQ 上从 Tail 起写入 SQE → 写 SQ Tail Doorbell
  2. Controller 取命令:DMA 读 SQ → 内部推进 SQ Head(Host 通过后续 CQE 中的 SQ Head 指针 得知消费进度)。
  3. Controller 完成命令:写 CQ 中下一空闲槽的 CQE(更新 Phase 等)→ 可选发中断。
  4. Host 回收完成:扫描 CQ 新 Phase 的项 → 处理完毕后写 CQ Head Doorbell 批量释放。

3.3 PRP Entry 与 PRP List 布局(数据缓冲区描述)

PRP(Physical Region Page) 是 NVMe 描述 Host 物理内存 中数据缓冲的主要方式之一(与 SGL 二选一或扩展)。Controller 根据命令中的 PRP1 / PRP2 以及可能的 PRP List 链,在 DMA 时逐页访问 Host 内存。

3.3.1 单个 PRP Entry(64 bit)位域概念图

页大小由配置 CC.MPS(Memory Page Size) 等决定,下文用 n 表示「页内偏移」所占最低位数(例如 4KiB 页时常有 n = 11,即 bit[11:0] 为页内字节偏移)。

1
2
3
4
5
6
7
8
PRP Entry(64 bit)
┌───────────────────────────────────────────┬
│ 63 (n+1) │ n 0 │
├──────────────────────────────┼────────────┤
│ Page Base Address(页基址) │ Offset │
│ (物理页号对齐的高位部分) │ 页内偏移 │
└──────────────────────────────┴────────────┘
PBAO = Page Base Address + Offset(概念上)

核心规则(实现与校验时常用):

  • 偏移Dword 对齐(低 2 bit 为 0)。
  • 命令中的第一个 PRP(PRP1) 可以带 非零页内偏移(数据可从页中某位置开始)。
  • PRP List 中的每一项以及 作为「指向 PRP List 的指针」的 PRP2(当 PRP2 指 list 时):偏移必须为 0页对齐),否则不符合规范要求。

3.3.2 命令中的 PRP1 / PRP2 与 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
小型传输(数据跨度小)
─────────────────────
NVMe Read/Write Command
┌─────────────────────────────────────┐
│ PRP1 ──► 第一页(可有 offset) │
│ PRP2 ──► 下一页起始 或 单个 PRP │
└─────────────────────────────────────┘

中大型传输(需多页)
─────────────────────
┌──────────┐ PRP2 指向一整页 PRP List
│ PRP1 │──► 第一段数据(起点可有 offset)
└──────────┘


┌──────────────────────────────────────┐
│ PRP List Page(一页内连续数组) │
│ ┌────────┐ │
│ │ PRP[0] │ 每项:仅页基址,offset=0 │
│ ├────────┤ │
│ │ PRP[1] │ │
│ ├────────┤ │
│ │ ... │ │
│ ├────────┤ │
│ │ PRP[k] │──► 可指向下一个 PRP List 页(链式扩展)│
│ └────────┘ │
└──────────────────────────────────────┘

一句话归纳PRP1 描述起点;PRP2 要么是「第二段直接地址」,要么是「PRP List 页指针」;List 内为 页对齐 的物理页指针链,必要时 最后一项 再指向下一个 List 页,以覆盖任意长度的散列缓冲区。

3.3.3 与本固件代码的对应

read_write.cset_cmd_info() 将 NVMe 命令里的 prp_entry1 / prp_entry2 原样记入 task_info_struct.prp1/prp2,后续由 BayBridge 数据通路寄存器(如 BAYBRIDGE_DATA_TRANSFER_CTRL_1~4 配置 DMA,从而在 CMD46/47(CQ 模式)或 CMD18/25 执行阶段完成 Host 内存与 eMMC 之间的数据传输(与第 4、5、7 章一致)。


3.4 固件侧命令获取流程(NVMe 1.4 Figure 90 与 BayBridge CMD Buffer)

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
┌────────────────────────────────────────────────────────────────────────┐
│ NVMe Command Processing Flow │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Host Driver Controller Firmware │
│ ─────────── ─────────────────── │
│ │ │ │
│ │ ① Write SQE to SQ Memory │ │
│ │─────────────────────────────► │ │
│ │ │ │
│ │ ② Write SQ Tail Doorbell │ │
│ │─────────────────────────────► │ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ HW Fetch SQE to │ │
│ │ │ CMD Buffer 0/1 │ │
│ │ └──────────┬──────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ CMD Buffer 就绪中断 │ │
│ │ │ → interrupt_process │ │
│ │ └──────────┬──────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ ③ command_parse() │ │
│ │ │ 解析 Opcode/NSID │ │
│ │ └──────────┬──────────┘ │
│ │ │ │
│ │ ┌────────────────┴────────────────┐ │
│ │ ▼ ▼ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │
│ │ │ Admin Queue │ │ IO Queue │ │
│ │ │ (QID = 0) │ │ (QID > 0) │ │
│ │ │ │ │ │ │
│ │ │ Identify │ │ Read/Write │ │
│ │ │ Get/Set Feature│ │ Flush │ │
│ │ │ FW Download │ │ Dataset Mgmt │ │
│ │ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌─────────────────────────────────────────────────┐ │
│ │ │ ④ 执行命令 / 插入任务队列 │ │
│ │ └─────────────────────┬───────────────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────┐ │
│ │ │ ⑤ send_complete() │ │
│ │ │ 写 CQE + 发 MSI 中断 │ │
│ │ ◄───────────┤ │ │
│ │ └─────────────────────────┘ │
│ │ │
└───────┴────────────────────────────────────────────────────────────────┘

3.5 Admin 命令列表(NVMe 1.4 Spec Figure 139)

Opcode 命令名称 功能说明
0x00 Delete I/O SQ 删除 IO 提交队列
0x01 Create I/O SQ 创建 IO 提交队列
0x02 Get Log Page 获取日志页(Error, SMART, FW Slot)
0x04 Delete I/O CQ 删除 IO 完成队列
0x05 Create I/O CQ 创建 IO 完成队列
0x06 Identify 获取 Controller/Namespace 信息
0x09 Set Features 设置功能参数(Power Mgmt, LBA Range)
0x0A Get Features 获取功能参数
0x0C Async Event Req 异步事件请求(标记进入 OS 阶段)
0x10 FW Commit 固件激活
0x11 FW Download 固件下载
0x80 Format NVM 格式化 Namespace

3.6 IO 命令列表(NVMe 1.4 Spec Figure 186)

Opcode 命令名称 功能说明
0x00 Flush 刷新易失性写缓存到非易失介质
0x01 Write 写数据到 LBA 地址
0x02 Read 从 LBA 地址读数据
0x09 Dataset Mgmt TRIM/Deallocate 命令

3.7 命令解析代码

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
int command_parse(void)
{
command_t command = {0};
byte queue_identifier = start_command_process_action();

// 从 Ping-Pong Buffer 获取命令
fetch_command(global_buffer_identifier, &command);

if (queue_identifier == 0) {
admin_command_parse(&command); // Admin 队列
} else {
nvme_command_parse(queue_identifier, &command); // IO 队列
}
return 0;
}

static void admin_command_parse(command_p command)
{
switch (command->opcode) {
case 0x06: identify_process(command); break;
case 0x09: set_features_process(command); break;
case 0x11: firmware_image_download_process(command); break;
// ... 其他 Admin 命令
default:
send_complete(command->command_identifier, 0, 0,
SC_INVALID_COMMAND_OPCODE);
}
}

static int nvme_command_parse(byte queue_id, command_p command)
{
switch (command->opcode) {
case 0x01: return read_write_process(queue_id, command, 1); // Write
case 0x02: return read_write_process(queue_id, command, 0); // Read
case 0x00: flush_process(queue_id, command); break; // Flush
default:
send_complete(command->command_identifier, queue_id, 0,
SC_INVALID_COMMAND_OPCODE);
}
return 0;
}

4. Read/Write 命令处理

4.1 NVMe 到 eMMC 的地址映射

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
┌─────────────────────────────────────────────────────────────────────────┐
│ Namespace 到 eMMC 映射模式 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Case 1: DOUBLE_EMMC_TOTAL_NS (双 eMMC 组成单一 Namespace) │
│ ═══════════════════════════════════════════════════════════ │
│ │
│ NVMe Namespace 1: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ LBA 0 │ LBA boundary │ LBA MAX │ │
│ └──────────────┴──────────────┴──────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ eMMC #0 │ │ Cross Boundary │ │ eMMC #1 │ │
│ │ LBA 0~MAX0 │ │ Fused Command │ │ LBA 0~MAX1 │ │
│ └──────────────┘ └─────────────────┘ └──────────────┘ │
│ │
│ Case 2: DOUBLE_EMMC_DOUBLE_NS (双 eMMC 双 Namespace) │
│ ═══════════════════════════════════════════════════════ │
│ │
│ NVMe NS 1: NVMe NS 2: │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ LBA 0~MAX │ │ LBA 0~MAX │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ eMMC #0 │ │ eMMC #1 │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

4.2 Read/Write 处理流程

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
┌─────────────────────────────────────────────────────────────────────────┐
│ read_write_process() 流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ NVMe Command (SQE) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Opcode: 0x01(Write) / 0x02(Read) │ │
│ │ NSID: Namespace Identifier │ │
│ │ PRP1: Physical Region Page Entry 1 (64-bit) │ │
│ │ PRP2: PRP Entry 2 or PRP List Pointer │ │
│ │ SLBA: Starting LBA (DW10-11) │ │
│ │ NLB: Number of Logical Blocks - 1 (DW12) │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 1. 地址范围检查 │ │
│ │ SLBA + NLB ≤ MAX? │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────────────┐ ┌────────────┐ │
│ │ 在 eMMC#0 │ │ 跨边界任务 │ │ 在 eMMC#1 │ │
│ │ 范围内 │ │ (Fused Cmd) │ │ 范围内 │ │
│ └─────┬──────┘ └───────┬────────┘ └─────┬──────┘ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 2. 插入到对应 eMMC Task Queue │ │
│ │ - PRP1/PRP2: DMA 地址 │ │
│ │ - Address: eMMC LBA │ │
│ │ - Block_len: 块数量 │ │
│ │ - Direction: Read(0) / Write(1) │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ 3. 状态 = PRE_CAND │ │
│ │ 等待下发到 eMMC CQ │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

4.3 任务信息结构

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
u64 prp1; // PRP Entry 1 (Host Memory Address)
u64 prp2; // PRP Entry 2 / PRP List
u64 address; // eMMC LBA Address
u16 block_len; // Number of 512B Blocks
u16 nvme_sq_id; // NVMe Submission Queue ID
u16 nvme_cmd_id; // NVMe Command Identifier
u8 direction; // 0=Read, 1=Write
u8 fused_flag; // FUSED_EMPTY / FUSED_NEW / FUSED_READY
u8 next_pointer; // 链接到另一个 eMMC 的任务 (Fused)
u8 start_card; // 起始 eMMC 卡号
} task_info_struct;

5. eMMC Command Queuing 模式

5.1 CQ 模式流程(eMMC 5.1 Spec Section 6.6.40-44)

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
┌─────────────────────────────────────────────────────────────────────────┐
│ eMMC Command Queuing Protocol Flow │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Controller Firmware eMMC Device │
│ ─────────────────── ─────────── │
│ │ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ Phase 1: 任务下发 (Queuing) │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ │ │
│ │ CMD44: Set Task Parameters │ │
│ │ Arg = [Block Count, Task ID, Dir] │ │
│ │─────────────────────────────────────────► │ │
│ │ R1 ◄───│ │
│ │ │ │
│ │ CMD45: Set Task Address │ │
│ │ Arg = [LBA Address] │ │
│ │─────────────────────────────────────────► │ │
│ │ R1 ◄───│ │
│ │ │ │
│ │ ... 重复 CMD44+CMD45 添加更多任务 │ │
│ │ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ Phase 2: 状态查询 (Query) │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ │ │
│ │ CMD13: Query Queue Status Register │ │
│ │ Arg = 0x00008000 │ │
│ │─────────────────────────────────────────► │ │
│ │ QSR ◄───│ │
│ │ QSR = 32-bit 位图,每位表示任务就绪 │ │
│ │ │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ Phase 3: 任务执行 (Execute) │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ │ │
│ │ CMD46: Execute Read Task │ │
│ │ Arg = [Task ID << 16] │ │
│ │─────────────────────────────────────────► │ │
│ │ │ │
│ │◄─────────────────── Data ─────────────────│ ← 数据传输 │
│ │ │ │
│ │ CMD47: Execute Write Task │ │
│ │ Arg = [Task ID << 16] │ │
│ │─────────────────────────────────────────► │ │
│ │ │ │
│ │─────────────────── Data ─────────────────►│ → 数据传输 │
│ │ │ │
└────────┴───────────────────────────────────────────┴────────────────────┘

5.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
       ┌─────────────────────────────────────────────────────────┐
│ Task State Machine │
└─────────────────────────────────────────────────────────┘

┌──────────┐ NVMe Cmd ┌───────────────┐ CMD44+45 ┌───────────┐
│ EMPTY │ ────────────────► │ PRE_CANDIDATE │ ─────────────► │ CANDIDATE │
│ (0x00) │ Received │ (0x01) │ Success │ (0x02) │
└──────────┘ └───────────────┘ └─────┬─────┘
▲ │
│ │
│ │ CMD13
│ │ QSR 查询
│ │
│ ┌───────────────┐ CMD46/47 ┌───────────┐ ▼
│ │ COMPLETED │ ◄───────────── │ EXECUTING │ ◄── ┌───────┐
│ │ (0x04) │ Success │ (0x03) │ │ READY │
│ └───────┬───────┘ └───────────┘ │(0x05) │
│ │ │ └───────┘
│ │ send_complete() │
│ │ │ Error
│ ▼ ▼
│ ┌───────────────┐ ┌───────────────┐
└─────────│ 释放槽位 │ │ ERR_STATUS │
└───────────────┘ │ (0x7F) │
└───────────────┘

5.3 CQ 模式核心代码

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
int emmc_read_write_cq_mode()
{
// Phase 1: 将 PRE_CANDIDATE 任务下发到 eMMC CQ
for (i = 0; i < task_status_map_index[card_slot]; i++) {
task_id = task_status_map[card_slot][i];

// CMD44 + CMD45: 添加任务
add_task_to_cq(card_slot, task_id);

// 更新状态: PRE_CANDIDATE → CANDIDATE
task_status_flag[card_slot][task_id] = CANDIDATE;
}

// Phase 2: 查询任务就绪状态 (CMD13)
emmc_task_sequence_select_for_execute(card_slot);

// Phase 3: 执行就绪任务 (CMD46/47)
if (emmc_task_completion) {
execute_ready_task();
}
}

int add_task_to_cq(u8 card_slot, u8 task_id)
{
// CMD44: 设置任务参数
cmd44_cq_set_task_params(card_slot,
task_queue[task_id].block_len,
task_id,
0, // Priority
!task_queue[task_id].direction);

// CMD45: 设置任务地址
cmd45_cq_set_task_address(card_slot,
task_queue[task_id].address);
}

6. Completion 发送机制

6.1 NVMe Completion 流程(NVMe 1.4 Spec Figure 91)

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
┌─────────────────────────────────────────────────────────────────────────┐
│ Completion Entry Generation │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 命令执行完成 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Completion Queue Entry (CQE) │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ DW0: Command Specific │ │
│ │ DW1: Reserved │ │
│ │ DW2: SQ Head Pointer (15:0) | SQ Identifier (31:16) │ │
│ │ DW3: Status Field (31:17) | Phase Tag (16) | CID (15:0) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 写 CQE 到 │ DMA Write to Host Memory │
│ │ CQ Tail 位置 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 更新 Phase Tag │ 队列回绕时翻转 │
│ │ CQ Tail++ │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 发送 MSI 中断 │ ← Interrupt Coalescing 可选 │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ Host Driver 处理 CQE,写 CQ Head Doorbell │
│ │
└─────────────────────────────────────────────────────────────────────────┘

6.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
void send_complete(u16 cmd_id, byte queue_id, u32 spec, u16 status)
{
// 填充 CQE
cpl.dw0 = spec;
cpl.sq_head_pointer = get_sq_head(queue_id);
cpl.command_identifier = cmd_id;
cpl.phase_tag = complete_queue_phase[cq_id];
cpl.status_field = status;

// 发送 CQE 到 Host 内存
fn_memcpy_4bytes((void *)CPL_BUFFER_BASE, &cpl, sizeof(cpl_t));
CPL_SEND_CTRL = cq_id | START_SENDING_BIT;

// 中断发送策略
if (status == 0 && queue_id != 0) {
// IO 命令成功:考虑 Interrupt Coalescing
if (aggregation_threshold == 0) {
INTERRUPT_REQUEST_SET = 1 << interrupt_vector[cq_id]; // 立即
} else {
msi_count[...]++; // 累积后批量发送
}
} else {
// Admin 命令或错误:立即发送中断
INTERRUPT_REQUEST_SET = 1 << interrupt_vector[cq_id];
}
}

7. 数据传输流程

7.1 Host ↔ Controller ↔ eMMC 数据流

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
┌─────────────────────────────────────────────────────────────────────────┐
│ Data Transfer Pipeline │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Write │ │
│ │ Command │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Host Memory (PRP) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Page 0 │ │ Page 1 │ │ Page 2 │ │ Page N │ │ │
│ │ │ (PRP1) │ │ │ │ │ │ │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ └───────│────────────│────────────│────────────│───────────────────┘ │
│ │ │ │ │ │
│ │ PCIe DMA │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Controller Data Buffer (Streaming) │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ DMA Engine: 自动处理 PRP → FIFO → eMMC │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────┬────────────────────────────────┘ │
│ │ │
│ │ eMMC HS400 (200MB/s) │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ eMMC Device │ │
│ │ ┌──────────────────────────────────────────────────────────┐ │ │
│ │ │ Internal Write Buffer → NAND Flash │ │ │
│ │ └──────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ═══════════════════════════════════════════════════════════════════ │
│ │
│ ┌─────────────┐ │
│ │ Read │ │
│ │ Command │ (数据流反向) │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

7.2 PRP 处理(NVMe 1.4 Spec Section 4.4)

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
┌─────────────────────────────────────────────────────────────────────────┐
│ PRP Entry Interpretation │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Case 1: Data Size ≤ 4KB (单页) │
│ ─────────────────────────────── │
│ PRP1 → [Data Page] │
│ PRP2 → (unused) │
│ │
│ Case 2: 4KB < Data Size ≤ 8KB (双页) │
│ ──────────────────────────────────── │
│ PRP1 → [Data Page 0] │
│ PRP2 → [Data Page 1] │
│ │
│ Case 3: Data Size > 8KB (PRP List) │
│ ────────────────────────────────── │
│ PRP1 → [Data Page 0] │
│ PRP2 → [PRP List Page] │
│ │ │
│ ├──► PRP Entry 0 → [Data Page 1] │
│ ├──► PRP Entry 1 → [Data Page 2] │
│ ├──► ... │
│ └──► PRP Entry N → [Data Page N] │
│ │
└─────────────────────────────────────────────────────────────────────────┘

8. 总结

8.1 设计要点

特性 实现方式 优势
事件驱动 主循环轮询 + 中断配合 响应速度与功耗平衡
三级流水 NVMe Fetch → Task Queue → eMMC Execute 并行处理提升吞吐
双 Buffer CMD Buffer 0/1 Ping-Pong 避免 Fetch 阻塞
CQ 模式 eMMC Command Queuing 随机性能提升
错误恢复 CMD/Data 错误自动重试 系统可靠性

8.2 关键性能参数

参数 典型值 说明
Task Table 深度 32 每个 eMMC 通道
CQ 深度 32 硬件 Command Queue
CMD Buffer 2 x 1KB 双 Buffer 交替
Data Buffer 4KB Admin 命令数据
FW Status 切换 ~10ms 约 263200 Loop

参考资料

  • NVM Express Base Specification 1.4(对照阅读:§4.1 队列深度与空/满、§4.3 PRP Entry 与 PRP List、§7.2.1 Command Processing;1.3a 等旧版章节号可能略有差异)
  • JEDEC Standard JESD84-B51 (eMMC 5.1)
  • SPARC V8 Architecture Manual