Linux PCIe DMA 驱动程序(Xilinx XDMA)
Linux PCIe DMA Driver (Xilinx XDMA)
我目前正在使用 Xilinx XDMA 驱动程序(查看此处的源代码:XDMA Source),并试图将其升级到 运行(在你提问之前:我已经联系了我的技术人员支持联系点和 Xilinx 论坛上到处都是遇到同样问题的人)。但是,我可能在 Xilinx 的代码中发现了一个障碍,这对我来说可能是一个交易破坏者。我希望有一些我没有考虑的事情。
首先,驱动程序有两种主要模式,AXI-Memory Mapped (AXI-MM) 和 AXI-Streaming (AXI-ST)。对于我的特定应用,我需要 AXI-ST,因为数据将不断从设备中流出。
编写驱动程序是为了利用分散-聚集列表。在 AXI-MM 模式下,这是有效的,因为读取是相当随机的事件(即,没有数据流出设备,用户空间应用程序只是在需要时请求数据)。因此,建立 DMA 传输,传输数据,然后拆除传输。这是 get_user_pages()
、pci_map_sg()
和 pci_unmap_sg()
的组合。
对于AXI-ST,事情变得很奇怪,源代码也很不正统。驱动程序分配一个循环缓冲区,数据将源源不断地流入其中。这个缓冲区的大小通常有点大(我的设置为 32MB 左右),因为您希望能够处理用户空间应用程序忘记驱动程序的瞬态事件,然后可以稍后处理传入数据。
这就是事情变得不稳定的地方......循环缓冲区是使用 vmalloc32()
分配的,并且来自该分配的页面以与用户空间缓冲区在 AXI-MM 模式下相同的方式映射(即,使用pci_map_sg()
界面)。结果,因为循环缓冲区在设备和 CPU 之间共享,每个 read()
调用都需要我调用 pci_dma_sync_sg_for_cpu()
和 pci_dma_sync_sg_for_device()
,这绝对会破坏我的性能(我跟不上设备!),因为这适用于整个缓冲区。有趣的是,Xilinx 从未在他们的代码中包含这些同步调用,所以当我编辑他们的测试脚本以在退出前尝试多次 DMA 传输并且结果数据缓冲区已损坏时,我第一次知道我遇到了问题。
因此,我想知道如何解决这个问题。我考虑过重写代码以构建我自己的使用 pci_alloc_consistent()/dma_alloc_coherent()
分配的缓冲区,但这说起来容易做起来难。也就是说,代码被设计为假设在任何地方都使用分散-收集列表(在分散-收集列表和 FPGA 理解的内存描述符之间似乎有一个奇怪的专有映射)。
还有其他 API 电话我应该注意吗?我可以通过某种翻译机制使用 "single" 变体(即 pci dma_sync_single_for_cpu()
)来不同步整个缓冲区吗?或者,是否有一些函数可以使分配给 vmalloc()
的循环缓冲区连贯?
我认为 XDMA 最初是为 x86 编写的,在这种情况下同步函数什么都不做。
除非您修改循环缓冲区,否则您似乎不太可能使用单同步变体。用要发送的缓冲区列表替换循环缓冲区对我来说似乎是个好主意。您 pre-allocate 许多这样的缓冲区,并有一个要发送的缓冲区列表和一个供您的应用重用的空闲列表。
如果您使用的是 Zynq FPGA,则可以将 DMA 引擎连接到 ACP 端口,以便 FPGA 内存访问保持一致。或者,您可以将内存区域映射为 uncached/buffered 而不是缓存。
最后,在我的 FPGA 应用程序中,我将控制寄存器和缓冲区映射到应用程序进程中,并且只在驱动程序中实现 mmap() 和 poll(),以便让应用程序更灵活地执行 DMA。我通常实现自己的 DMA 引擎。
好的,我明白了。
基本上,我的假设 and/or 对有关同步的内核文档的理解 API 是完全错误的。也就是说,我在两个关键假设上错了:
- 如果 CPU 从未写入缓冲区,则不需要为设备同步。删除此调用使我的
read()
吞吐量翻了一番。
- 您不需要同步整个散点列表。相反,现在在我的
read()
调用中,我弄清楚哪些页面将受到 copy_to_user()
调用的影响(即,什么将被复制出循环缓冲区)并且只同步这些页面我关心的。基本上,我可以调用 pci_dma_sync_sg_for_cpu(lro->pci_dev, &transfer->sgm->sgl[sgl_index], pages_to_sync, DMA_FROM_DEVICE)
之类的东西,其中 sgl_index
是我认为副本将开始的位置,pages_to_sync
是页数中数据的大小。
通过以上两项更改,我的代码现在满足了我的吞吐量要求。
Pete,我是驱动程序代码的原始开发人员(在 XMDA 的 X 出现之前)。
ringbuffer 一直是一个非正统的东西,确实适用于 cache-coherent 系统,默认情况下禁用。它的最初目的是摆脱 DMA(重新)启动延迟;即使有完全异步 I/O 支持(即使在某些情况下使用 zero-latency 描述符链接),我们也有无法保证这一点的用例,并且需要真正的硬件 ringbuffer/cyclic/loop 模式。
在Linux中没有相当于ringbuffer API的东西,所以有点open-coded。
我很高兴re-think IP/driver 设计。
你能分享你的修复方法吗?
我目前正在使用 Xilinx XDMA 驱动程序(查看此处的源代码:XDMA Source),并试图将其升级到 运行(在你提问之前:我已经联系了我的技术人员支持联系点和 Xilinx 论坛上到处都是遇到同样问题的人)。但是,我可能在 Xilinx 的代码中发现了一个障碍,这对我来说可能是一个交易破坏者。我希望有一些我没有考虑的事情。
首先,驱动程序有两种主要模式,AXI-Memory Mapped (AXI-MM) 和 AXI-Streaming (AXI-ST)。对于我的特定应用,我需要 AXI-ST,因为数据将不断从设备中流出。
编写驱动程序是为了利用分散-聚集列表。在 AXI-MM 模式下,这是有效的,因为读取是相当随机的事件(即,没有数据流出设备,用户空间应用程序只是在需要时请求数据)。因此,建立 DMA 传输,传输数据,然后拆除传输。这是 get_user_pages()
、pci_map_sg()
和 pci_unmap_sg()
的组合。
对于AXI-ST,事情变得很奇怪,源代码也很不正统。驱动程序分配一个循环缓冲区,数据将源源不断地流入其中。这个缓冲区的大小通常有点大(我的设置为 32MB 左右),因为您希望能够处理用户空间应用程序忘记驱动程序的瞬态事件,然后可以稍后处理传入数据。
这就是事情变得不稳定的地方......循环缓冲区是使用 vmalloc32()
分配的,并且来自该分配的页面以与用户空间缓冲区在 AXI-MM 模式下相同的方式映射(即,使用pci_map_sg()
界面)。结果,因为循环缓冲区在设备和 CPU 之间共享,每个 read()
调用都需要我调用 pci_dma_sync_sg_for_cpu()
和 pci_dma_sync_sg_for_device()
,这绝对会破坏我的性能(我跟不上设备!),因为这适用于整个缓冲区。有趣的是,Xilinx 从未在他们的代码中包含这些同步调用,所以当我编辑他们的测试脚本以在退出前尝试多次 DMA 传输并且结果数据缓冲区已损坏时,我第一次知道我遇到了问题。
因此,我想知道如何解决这个问题。我考虑过重写代码以构建我自己的使用 pci_alloc_consistent()/dma_alloc_coherent()
分配的缓冲区,但这说起来容易做起来难。也就是说,代码被设计为假设在任何地方都使用分散-收集列表(在分散-收集列表和 FPGA 理解的内存描述符之间似乎有一个奇怪的专有映射)。
还有其他 API 电话我应该注意吗?我可以通过某种翻译机制使用 "single" 变体(即 pci dma_sync_single_for_cpu()
)来不同步整个缓冲区吗?或者,是否有一些函数可以使分配给 vmalloc()
的循环缓冲区连贯?
我认为 XDMA 最初是为 x86 编写的,在这种情况下同步函数什么都不做。
除非您修改循环缓冲区,否则您似乎不太可能使用单同步变体。用要发送的缓冲区列表替换循环缓冲区对我来说似乎是个好主意。您 pre-allocate 许多这样的缓冲区,并有一个要发送的缓冲区列表和一个供您的应用重用的空闲列表。
如果您使用的是 Zynq FPGA,则可以将 DMA 引擎连接到 ACP 端口,以便 FPGA 内存访问保持一致。或者,您可以将内存区域映射为 uncached/buffered 而不是缓存。
最后,在我的 FPGA 应用程序中,我将控制寄存器和缓冲区映射到应用程序进程中,并且只在驱动程序中实现 mmap() 和 poll(),以便让应用程序更灵活地执行 DMA。我通常实现自己的 DMA 引擎。
好的,我明白了。
基本上,我的假设 and/or 对有关同步的内核文档的理解 API 是完全错误的。也就是说,我在两个关键假设上错了:
- 如果 CPU 从未写入缓冲区,则不需要为设备同步。删除此调用使我的
read()
吞吐量翻了一番。 - 您不需要同步整个散点列表。相反,现在在我的
read()
调用中,我弄清楚哪些页面将受到copy_to_user()
调用的影响(即,什么将被复制出循环缓冲区)并且只同步这些页面我关心的。基本上,我可以调用pci_dma_sync_sg_for_cpu(lro->pci_dev, &transfer->sgm->sgl[sgl_index], pages_to_sync, DMA_FROM_DEVICE)
之类的东西,其中sgl_index
是我认为副本将开始的位置,pages_to_sync
是页数中数据的大小。
通过以上两项更改,我的代码现在满足了我的吞吐量要求。
Pete,我是驱动程序代码的原始开发人员(在 XMDA 的 X 出现之前)。
ringbuffer 一直是一个非正统的东西,确实适用于 cache-coherent 系统,默认情况下禁用。它的最初目的是摆脱 DMA(重新)启动延迟;即使有完全异步 I/O 支持(即使在某些情况下使用 zero-latency 描述符链接),我们也有无法保证这一点的用例,并且需要真正的硬件 ringbuffer/cyclic/loop 模式。
在Linux中没有相当于ringbuffer API的东西,所以有点open-coded。
我很高兴re-think IP/driver 设计。
你能分享你的修复方法吗?