我正在 linux-2.6.26 上编写设备驱动程序。我想将一个 dma 缓冲区映射到用户空间,以便将数据从驱动程序发送到用户空间应用程序。请推荐一些关于它的好教程。
谢谢
我正在 linux-2.6.26 上编写设备驱动程序。我想将一个 dma 缓冲区映射到用户空间,以便将数据从驱动程序发送到用户空间应用程序。请推荐一些关于它的好教程。
谢谢
这是我用过的,简而言之......
get_user_pages
固定用户页面并给你一个指针数组struct page *
。
dma_map_page
在每个struct page *
上获取页面的 DMA 地址(又名“I/O 地址”)。这也会创建一个 IOMMU 映射(如果您的平台需要)。
现在告诉您的设备使用这些 DMA 地址对内存执行 DMA。显然它们可以是不连续的;内存只保证在页面大小的倍数中是连续的。
dma_sync_single_for_cpu
做任何必要的缓存刷新或反弹缓冲区块或其他任何事情。这个调用保证了 CPU 可以实际看到 DMA 的结果,因为在许多系统上,修改 CPU 背后的物理 RAM 会导致缓存失效。
dma_unmap_page
释放 IOMMU 映射(如果您的平台需要它)。
put_page
取消固定用户页面。
请注意,您必须从头到尾检查错误,因为到处都是有限的资源。 get_user_pages
对于彻底的错误 (-errno) 返回一个负数,但它可以返回一个正数来告诉您它实际管理了多少页(物理内存不是无限的)。如果这比您要求的要少,您仍然必须遍历它固定的所有页面才能调用put_page
它们。(否则你会泄漏内核内存;非常糟糕。)
dma_map_page
也可以返回错误 (-errno),因为 IOMMU 映射是另一个有限资源。
dma_unmap_page
和put_page
return void
,像往常一样用于 Linux“释放”功能。(Linux 内核资源管理例程只返回错误,因为实际上出了问题,而不是因为你搞砸并传递了一个错误的指针或其他东西。基本假设是你永远不会搞砸,因为这是内核代码。虽然get_user_pages
确实检查以确保用户地址的有效性,如果用户给你一个错误的指针,将返回一个错误。)
如果你想要一个友好的界面来分散/聚集,你也可以考虑使用 _sg 函数。然后你会调用dma_map_sg
而不是dma_map_page
,dma_sync_sg_for_cpu
而不是dma_sync_single_for_cpu
,等等。
另请注意,其中许多功能在您的平台上可能或多或少是无操作的,因此您通常可以摆脱马虎。(特别是 dma_sync_... 和 dma_unmap_... 在我的 x86_64 系统上什么都不做。)但是在那些平台上,调用本身被编译成空,所以没有理由草率。
好的,这就是我所做的。
免责声明:我是一个纯粹意义上的黑客,我的代码不是最漂亮的。
我阅读了 LDD3 和 infiniband 源代码以及其他前辈的东西,并决定get_user_pages
将它们和所有其他冗长的东西固定在宿醉时太痛苦了,无法思考。此外,我正在与其他人一起通过 PCIe 总线工作,我还负责“设计”用户空间应用程序。
我编写了驱动程序,以便在加载时,它通过调用函数以最大大小预分配尽可能多的缓冲区,myAddr[i] = pci_alloc_consistent(blah,size,&pci_addr[i])
直到它失败。(失败->我认为,我忘记了)myAddr[i]
。NULL
我能够在我只有 4GiB 内存的微薄机器中分配大约 2.5GB 的缓冲区,每个缓冲区大小为 4MiB。当然,缓冲区的总数取决于内核模块的加载时间。在引导时加载驱动程序并分配最多的缓冲区。在我的系统中,每个单独缓冲区的大小最大为 4MiB。不知道为什么。我cat
要/proc/buddyinfo
确保我没有做任何愚蠢的事情,这当然是我通常的开始模式。
然后驱动程序继续将数组pci_addr
及其大小提供给 PCIe 设备。然后司机就坐在那里等待中断风暴开始。同时在用户空间中,应用程序打开驱动程序,查询分配的缓冲区数量(n)及其大小(使用ioctl
s 或read
s 等),然后继续调用系统调用mmap()
多次(n)次。当然mmap()
必须在驱动程序和LDD3 422-423页中正确实现才得心应手。
用户空间现在有 n 个指向驱动程序内存的 n 个区域的指针。由于驱动程序被 PCIe 设备中断,它被告知哪些缓冲区“已满”或“可用”被吸干。反过来,应用程序正在等待read()
或被ioctl()
告知哪些缓冲区充满了有用的数据。
棘手的部分是管理用户空间到内核空间的同步,这样通过 PCIe 进入 DMA 的缓冲区也不会被用户空间修改,但这就是我们得到的报酬。我希望这是有道理的,我很高兴被告知我是个白痴,但请告诉我原因。
顺便说一下,我也推荐这本书: http: //www.amazon.com/Linux-Programming-Interface-System-Handbook/dp/1593272200。我希望七年前我写第一个 Linux 驱动程序时有这本书。
通过添加更多内存而不让内核使用它并mmap
在用户空间/内核空间划分的两侧执行 ping 操作,可能还有另一种类型的诡计,但 PCI 设备还必须支持高于 32 位的 DMA 寻址。我没有尝试过,但如果我最终被迫这样做,我不会感到惊讶。
好吧,如果你有 LDD,你可以看看第 15 章,更准确地说是第 435 页,其中描述了直接 I/O 操作。
可以帮助您实现这一目标的内核调用是get_user_pages
. 在您的情况下,由于您想将数据从内核发送到用户空间,您应该将写入标志设置为 1。
另请注意,异步 I/O 可能允许您获得相同的结果,但您的用户空间应用程序不必等待读取完成,这可能会更好。
好好看看 Infiniband 驱动程序。他们付出了很多努力来使用户空间的零拷贝 DMA 和 RDMA 工作。
我忘了在保存之前添加这个:
直接对用户空间内存映射进行 DMA 会带来很多问题,因此除非您有非常高的性能要求,例如 Infiniband 或 10 Gb 以太网,否则不要这样做。相反,将 DMA 数据复制到用户空间缓冲区。它会为你省去很多悲伤。
仅举一个例子,如果用户的程序在 DMA 完成之前退出怎么办?如果用户内存在退出后重新分配给另一个进程但硬件仍设置为 DMA 进入该页面怎么办?灾难!
remap_pfn_range 函数(用于驱动程序中的 mmap 调用)可用于将内核内存映射到用户空间。
一个真实的例子可以在 mem character driver drivers/char/mem.c中找到。