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) { if (T0_Counter[OS_READY].end_flag && async_event_received) { pm.set_internalstate(&pm_dscp, ID_KEEP_L0, 0); }
interrupt_process();
if (global_admin_command_req_dma_channel_flag == 0) { emmc_read_write_cq_mode(); }
send_completion_task();
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;双方只靠 Doorbell 与 DMA 同步,无额外「边带」传命令。 |
| 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 对应)
- Host 提交命令:在 SQ 上从 Tail 起写入 SQE → 写 SQ Tail Doorbell。
- Controller 取命令:DMA 读 SQ → 内部推进 SQ Head(Host 通过后续 CQE 中的 SQ Head 指针 得知消费进度)。
- Controller 完成命令:写 CQ 中下一空闲槽的 CQE(更新 Phase 等)→ 可选发中断。
- 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.c 中 set_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 章一致)。
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 中断 │ │ │ │ ◄───────────┤ │ │ │ │ └─────────────────────────┘ │ │ │ │ └───────┴────────────────────────────────────────────────────────────────┘
|
| 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 |
| 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(); fetch_command(global_buffer_identifier, &command);
if (queue_identifier == 0) { admin_command_parse(&command); } else { nvme_command_parse(queue_identifier, &command); } 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; 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); case 0x02: return read_write_process(queue_id, command, 0); case 0x00: flush_process(queue_id, command); break; 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; u64 prp2; u64 address; u16 block_len; u16 nvme_sq_id; u16 nvme_cmd_id; u8 direction; u8 fused_flag; u8 next_pointer; u8 start_card; } 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() { for (i = 0; i < task_status_map_index[card_slot]; i++) { task_id = task_status_map[card_slot][i]; add_task_to_cq(card_slot, task_id); task_status_flag[card_slot][task_id] = CANDIDATE; }
emmc_task_sequence_select_for_execute(card_slot);
if (emmc_task_completion) { execute_ready_task(); } }
int add_task_to_cq(u8 card_slot, u8 task_id) { cmd44_cq_set_task_params(card_slot, task_queue[task_id].block_len, task_id, 0, !task_queue[task_id].direction); cmd45_cq_set_task_address(card_slot, task_queue[task_id].address); }
|
6. Completion 发送机制
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) { 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;
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) { if (aggregation_threshold == 0) { INTERRUPT_REQUEST_SET = 1 << interrupt_vector[cq_id]; } else { msi_count[...]++; } } else { 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