NVMe

来自osdev
跳到导航 跳到搜索

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寄存器。