NVMe
跳到导航
跳到搜索
NVMe规范 可以在这里找到。
关于这个页面的论坛帖子在这里。
概述
- NVMe控制器可以作为具有类代码1和子类代码8的PCI设备找到。
- 它的寄存器可以通过BAR 0进行访问(应该是64位内存IO)。
- 控制器处理从“submission queues(提交队列)”提交给它的命令(commands)。 驱动程序在内存中的队列的循环缓冲区中准备命令,然后更新队列的尾部指针寄存器。
- 控制器可以按照自己喜欢的任何顺序处理命令。
- 当控制器完成命令处理后,它会将一个条目附加到“完成队列(completion queue)”。 创建submission队列时指定要使用的completion队列。 当completion队列有可用命令时,控制器发送一个中断。 驱动程序处理队列的循环缓冲区中的所有新条目,然后更新队列的头指针寄存器。
- 重置时,存在仅一个submission队列和仅一个completion队列。 其它一些是管理队列(admin queues)。 驱动器在ASQ和ACQ寄存器中设置它们的基址。
- admin队列可以处理管理命令,例如创建IO队列 (用于提交IO命令,如读/写扇区),以及查询与之相连的控制器和驱动器 (称为 “命名空间(namespaces)”) 的信息。
- admin队列的标识符为0。
BAR0 寄存器
偏移 | 名称 | 说明 |
---|---|---|
0x00-0x07 | CAP | 控制器功能。(Controller capabilities) |
0x08-0x0B | VS | Version. |
0x0C-0x0F | INTMS | Interrupt mask set. |
0x10-0x13 | INTMC | Interrupt mask clear. |
0x14-0x17 | CC | Controller configuration. |
0x1C-0x1F | CSTS | Controller status. |
0x24-0x27 | AQA | Admin queue attributes. |
0x28-0x2F | ASQ | Admin submission queue. |
0x30-0x37 | ACQ | Admin completion queue. |
0x1000+(2X)*Y | SQxTDBL | Submission queue X tail doorbell. |
0x1000+(2X+1)*Y | CQxHDBL | Completion queue X head doorbell. |
Y是doorbell stride,在控制器功能寄存器(controller capabilities register)中指定。
数据结构
Submission队列条目
Submission队列条目 - 命令- 是64字节,按16个DWORD排列。
DWORD | Contents |
---|---|
0 | Command DWORD 0 (see below) |
1 | NSID (namespace identifier). If n/a, set to 0. |
2-3 | 保留。 |
4-5 | Metadata pointer. |
6-9 | Data pointer. 2 PRPs (see next section). |
10-15 | Command specific. |
命令DWORD 0的格式:
Bits | Contents |
---|---|
0-7 | Opcode. |
8-9 | Fused operation. 0 indicates normal operation. |
10-13 | Reserved. |
14-15 | PRP or SGL selection. 0 indicates PRPs. |
16-31 | Command identifier. This is put in the completion queue entry. |
PRP
一个PRP (物理区域页-physical region page) 是一个64位的物理内存地址。 它必须与DWORD对齐。 PRP列表用于将数据传送到存储器中的特定位置,其中数据从存储器传送到存储器中/从存储器传送到存储器中。 PRP列表受以下规则约束:
- 给定PRP指定的区域大小至少为: 可以在不越过页边界的情况下传输的数据量;以及剩余要传输的数据量。
- 只有PRP列表中的第一个条目可以是页面未对齐的。
- 如果PRP列表的长度不足以覆盖整个传输,则最后一个条目链接到包含更多PRP条目的页面。
完成队列条目(Completion queue entry)
completion队列条目为16字节。
Bits | Contents |
---|---|
0-31 | Command specific. |
32-63 | Reserved. |
64-79 | Submission queue head pointer. |
80-95 | Submission queue identifier. |
96-111 | Command identifier. |
112 | Phase bit. Toggled when entry written. |
113-127 | Status field. 0 on success. |
可以通过检查阶段(phase)位来确定completion队列中新条目结束的位置。
命令(Commands)
管理命令(Admin commands)
创建IO submission队列
- 操作码是0x01。
- 队列的基地址应放在命令的DWORD6和7中。
- 命令DWORD 10包含低字中的队列标识符,以及高字(high word)中的队列大小。 队列大小应小于实际值。
- 命令DWORD11在低位字中包含标志,在高位字中包含完成队列标识符(将发布此submission队列的完成条目)。 标志 (1 << 0) 表示队列在物理上是连续的 (推荐; 非连续不被所有控制器支持)。
创建IO completion队列
- 操作码为0x05。
- 队列的基地址应放在命令的DWORDs 6和7中。
- 命令DWORD 10在低位字中包含队列标识符,在高位字中包含队列大小。 队列大小应该比实际值小1。
- 命令DWORD 11在低字中包含标志,在高字中包含中断向量。 标志(1<<0)表示队列在物理上是连续的(建议使用;并非所有控制器都支持非连续),标志(1<<1)启用中断。
标识(Identify)
- 操作码为0x06。
- 输出的基址(单页)应该放在命令的DWORD6和7中。
- 命令DWORD 10的低位字节指示要标识的内容:0-命名空间,1-控制器,2-命名空间列表。
- 如果标识名称空间,请将DWORD 1设置为名称空间ID。
输入输出命令(IO commands)
读取
- 操作码为0x02。
- DWORD 1包含NSID。
- DWORD 6-9包含数据传输的PRP列表。
- DWORD 10-11包含起始LBA。
- DWORD 12的低字包含要传输的块数。这应该比实际值少一个。
写入
- 操作码为0x01。
- DWORD 1包含NSID。
- DWORD6-9包含数据传输的PRP列表。
- DWORD 10-11包含起始LBA。
- DWORD 12的低字包含要传输的块数。这应该比实际值少一个。
检查表
初始化
- 找到具有类代码0x01和子类代码0x08的PCI函数。
- 为函数在PCI配置空间中启用中断、总线主控DMA和内存空间访问。
- 映射BAR0。
- 检查控制器版本是否支持。
- 检查功能寄存器是否支持NVMe命令集。
- 检查能力寄存器以获取对主机页面大小的支持。
- 重置控制器。
- 设置控制器配置和管理队列基本地址。
- 启动控制器。
- 启用中断并注册处理程序。
- 向控制器发送标识命令(identify command)。检查它是一个IO控制器。记录最大传输大小。
- 如果已实现,重置软件进度标记(software progress marker)。
- 创建第一个IO completion队列和第一个IO Submission队列。
- 识别活动名称空间ID,然后识别各个名称空间。记录它们的块大小、容量以及它们是否为只读。
关机
- 删除IO队列。
- 通知控制器关机。
- 等待CSTS.SHST更新。
提交命令
- 建立PRP列表。
- 在submission队列等候空间控制器在completion队列条目中指示其内部头指针。
- 设置命令。
- 更新队列tail doorbell寄存器。
IRQ处理程序
- 对于每个completion队列,读取已切换相位位的所有条目。
- 检查命令的状态。
- 使用submission队列ID和命令ID计算出该补全条目对应的是哪个提交的命令。
- 更新completion队列head doorbell寄存器。