2

根据 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) 和控制器内存缓冲区 (CMB)。

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

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

4

1 回答 1

3

抱歉,我是从内存中执行此操作的,但我已经实现了一个 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 驱动器都要求您发送至少 2dword(8 字节)的写入,即使您只打算发送 1dword 的数据,请注意。

  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您想查看识别名称空间以告诉您 LBA(逻辑块地址)大小,这将引导您进入兔子洞以确定实际大小,但没关系,信息就在那里。

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

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

  6. 此时驱动器将运行。啊哈,主持人告诉我有新的命令要获取。因此控制器将进入队列基地址 + commandsize*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 节来找到使事情发生的操作码,但我相信您可以从我给您的内容中弄清楚。如果您还有任何问题,请发表评论,我会看看我能做什么。

于 2020-03-01T04:57:31.567 回答