NVMe命令位于PCIe BAR内的何处



根据NVMe规范,BAR为每个队列都有尾字段和头字段。例如:

  • 提交队列y尾门铃(SQyTDBL):
    • 开始:1000h + (2y * (4 << CAP.DSTRD))
    • 结束:1003h + (2y * (4 << CAP.DSTRD))
  • 提交队列y头门铃(SQyHDBL):
    • 开始:1000h + ((2y + 1) * (4 << CAP.DSTRD))
    • 结束:1003h + ((2y + 1) * (4 << CAP.DSTRD))

是队列本身还是仅仅是指针?这是正确的吗?如果是队列,我假设DSTRD指示所有队列的最大长度。

此外,本规范还讨论了两个可选的区域:主机内存缓冲区(HMB)和控制器内存缓冲区。

  • HMB:主机DRAM(PCIe根)内的区域
  • CMB:NVMe控制器的DRAM内的区域(SSD内)

如果两者都是可选的,那么它位于哪里由于端点PCIe仅适用于BAR和PCI标头,因此除了BAR之外,我看不到它们可能位于的任何其他位置。

对不起,我是用内存做这件事的,但我已经实现了FPGA NVMe主机,所以希望我的内存足以回答你的问题,如果我做错了什么,至少你知道为什么。我将提供规范中的参考部分,您可以在这里找到。https://nvmexpress.org/wp-content/uploads/NVM-Express-1_4-2019.06.10-Ratified.pdf另外,在我真正回答你的问题之前,我想澄清一些困惑,理解规范需要一些时间。我真诚地建议你从下到上阅读最后几节,这有助于为前几节提供听起来很奇怪的背景。

  1. 这些是提交队列和完成队列,特别是分别是子队列尾部和完成队列头部(第3.1节)。稍后我只想纠正错误的概念,即您作为主机访问提交队列头部,而不仅仅是控制器(传统上是驱动器)访问。一个简单的提醒提交是你要求驱动器做某事,完成是驱动器告诉你它是如何进行的。阅读第7.2节了解更多信息。

  2. 在向这些队列发送任何内容之前,必须先设置所述队列。系统中的基线-这些队列不存在,必须使用管理队列进行设置。

28h 2Fh ASQ管理员提交队列基本地址

30h 37h ACQ管理员完成队列基本地址

  1. 您关于DSTRD的陈述是一个巨大的误解。该字段来自功能寄存器(0x0)图3.1.1。该字段是控制器(驱动器),告诉您"门铃步幅",即每个门铃之间有多少字节,我从未见过驱动器报告该值为0,因为为什么要在门铃寄存器之间留有空白。

  2. 请注意写入的大小,根据我的经验,大多数NVMe驱动器要求您发送至少2字(8字节)的写入,即使您只打算发送1字的数据,只是一条注释。

  3. 为了真正帮助您将此东西用作主机,请参阅第7.6.1节以查找初始化序列。请注意您必须如何设置多个寄存器、读取某些参数和其他类似的东西。

  4. 假设您或其他人已经完成了初始化,现在让我回答您问题的核心,如何使用这些队列。问题是,这个答案涵盖了规范的许多部分,是它的核心。因此,我将尽我所能为一个简单的写命令将其分解。请注意,在您第一次使用管理队列创建队列之前,您无法编写,管理队列利用规范不同部分的不同操作码,很抱歉,我无法编写所有这些。

将数据写入NVMe驱动器的步骤。

  1. 在创建提交队列时,您将指定此特定队列的大小。这是一次可以放入队列中进行处理的命令数。同时,您将指定队列基地址。因此,在本例中,假设您将基址设置为0x1000_0000,大小为16(0x10)图105让我们知道,每个提交队列条目的大小都是64字节(0x40),因此队列条目0位于0x1000_0000,条目1位于0x1000-0040 2 0x1000_0080,依此类推,对于我们的16个条目,它将循环返回。

  2. 您将首先存储要写入的数据,假设您得到了512字节(0x200)的数据来写入。因此,为了简单起见,您将数据放置在0x2000_0000-0x2000_0200处。

  3. 您可以创建提交队列命令。这不是一个简单的过程。我不打算为您记录所有这些,但我知道您应该参考图104、图346和第6.15节。然而,这还不够。你还需要了解PRP与SGL以及你正在使用的内容(PRP更容易上手)。决定写入大小的NLB(逻辑块数),对于NVMe,您不以字节为单位指定写入,但就大小由控制器(驱动器)指定的NLB而言,它可以实现多个NLB大小,但这取决于驱动器,而不是您作为主机,您只需从它支持的内容中选择第5.15.2.1节,图245您想要查看identify名称空间来告诉您LBA(逻辑块地址)大小,这将导致您陷入一个兔子洞来确定实际大小,但没关系,信息就在那里。

  4. 好的,所以你完成了这个混乱,并创建了提交命令。让我们假设主机已经在这个队列上完成了2个命令(在开始时,这将是0,我选择2只是为了在我的示例中更清楚)。现在需要做的是将此命令放置在0x1000_0080处。

  5. 现在让我们假设这是队列1(根据您发布的等式,队列号是y值。请注意,队列0是管理队列)。你需要做的是戳控制器提交队列的尾门铃,告诉它现在加载了多少命令(这样你就可以一次排队多个,只告诉驱动器你什么时候准备好了)。在这种情况下,数字是2。因此,您需要将值2写入寄存器0x1008。

  6. 此时驱动器将关闭。啊哈,主机告诉我有新的命令要获取。因此,控制器将转到队列基地址+命令大小*2,并获取64字节的数据,即1个命令(地址0x1000_0080)。控制器会将此命令解码为写入,这意味着控制器(驱动器)必须从某个地址读取数据,并将其放入指定的内存中。这意味着您的写入命令应该告诉驱动器转到地址0x2000_0000并读取512字节的数据,如果您确定PCIe总线的范围,则会这样做。此时,驱动器将填写一个完成队列条目(在第4.6节中指定的16字节),并将其放置在创建队列时指定的完成队列地址中(加上0x20,因为这是第二次完成)。然后控制器将产生MSI-X中断。

  7. 此时,您必须转到放置完成队列的任何位置,读取响应以检查状态。此外,如果您对多个提交进行排队,请检查SQID以查看完成了什么,因为作业可能会无序完成。然后,您必须写入完成队列头(0x100C),以指示您已检索到完成队列(成功或失败)。请注意,在这里,您永远不会与提交队列头交互(这取决于控制器,因为只有他知道提交队列条目何时被处理),只有控制器将东西放在完成队列尾部,因为只有控制器可以创建新条目。

很抱歉,这太长了,格式不好,但希望你现在对NVMe有了更好的理解,一开始有点混乱,但一旦你明白了,一切都有意义。请记住我的例子,假设您创建了一个基线不存在的队列。首先,您需要设置队列ID为0的管理员提交和完成队列(0x28和0x30),因此其尾部/头部门铃的地址分别为0x1000,0x1004。然后,您必须参考第5节来找到使事情发生的操作码,但我相信您可以从我给您的信息中找到它。如果你还有任何问题,请写下评论,我会看看我能做些什么。

最新更新