2010-07-26 89 views
11

我正在linux-2.6.26上编写设备驱动程序。我想有一个映射到用户空间的dma缓冲区,用于将数据从驱动程序发送到用户空间应用程序。请建议一些很好的教程。将DMA缓冲区映射到用户空间

感谢

+0

购买LDD http://lwn.net/Kernel/LDD3/并在设备驱动程序中查找mmap()实现。 – Dummy00001 2010-07-26 22:15:09

+0

我有LDD ...可以告诉我一些设备驱动程序的名称,以查找? – 2010-07-27 04:53:21

+0

@ Dummy00001物理/虚拟寻址怎么样? – 2011-04-04 14:02:42

回答

5

好吧,如果你有LDD,你可以看看第15章,更确切地说页435,在那里直接I/O动作。

内核调用将帮助你实现这个目标是get_user_pages。在你的情况下,因为你想从内核发送数据到用户空间,你应该设置写标志为1.

请注意,异步I/O可能允许你实现相同的结果,但用户空间应用程序不是不得不等待阅读完成,这可能会更好。

+2

'get_user_pages'映射到用户空间缓冲区到内核空间(对吧?),但如何一个链接到DMA操作?即触发DMA设备写入用户空间可以访问的内存? (请参阅http://stackoverflow.com/q/5539375/119790) – 2011-04-04 14:06:55

4

看看Infiniband驱动程序。他们非常努力地将零拷贝DMA和RDMA用于用户空间的工作。

我忘了保存前补充一点:

做DMA直接到用户空间内存映射是满的问题,所以,除非你有一个像的Infiniband或10 Gb以太网性能要求非常高,不这样做。相反,将DMA的数据复制到用户空间缓冲区中。它会为你节省很多的痛苦。

仅举一个例子,如果用户的程序在DMA完成之前退出会怎么样?如果用户内存在退出后重新分配给另一个进程但硬件仍设置为DMA进入该页面会怎么样?灾害!

+0

你能推荐一个最简单的Infiniband文件吗?他们都很复杂! – 2011-04-04 14:01:59

+0

额外的副本在我心中浪费时间。如果我们想冒这个问题的风险,那么解决方案到底是什么? – 2011-04-04 14:04:00

+3

'get_user_pages'固定页面。因此,在DMA完成之前程序是否退出并不重要......在执行相应的'put_page'来释放它们之前,这些页面不能重用。 – Nemo 2011-08-11 05:39:39

7

好吧,这就是我所做的。

声明:我是纯粹意义上的黑客,我的代码不是最漂亮的。

我读LDD3和infiniband源代码和其他前任的东西,并决定get_user_pages并钉住他们和所有其他rigmarole太痛苦,想不到,而宿醉。另外,我正在与PCIe总线上的其他人员一起工作,同时我还负责“设计”用户空间应用程序。

我写了驱动程序,以便在加载时,通过调用函数myAddr[i] = pci_alloc_consistent(blah,size,&pci_addr[i])直到它失败,它预分配尽可能多的缓冲区,因为它可以使用最大的大小。 (失败 - >myAddr[i]NULL我想,我忘记了)。我能够分配大约2.5GB的缓冲区,每个4MiB的大小在我只有4GiB内存的微薄机器中。缓冲区的总数取决于加载内核模块的时间。在引导时加载驱动程序并分配大多数缓冲区。在我的系统中,每个单独的缓冲区大小最大值为4MiB。不知道为什么。我cat泰德/proc/buddyinfo,以确保我没有做任何愚蠢的事情,这当然是我通常的开始模式。

然后驱动程序继续向PCIe设备提供pci_addr的数组及其大小。司机然后坐在那里等待中断风暴开始。同时在用户空间中,应用程序打开驱动程序,查询分配的缓冲区数量(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平在用户空间/内核空间分但该PCI设备的两侧上也必须支持比32位DMA寻址更高可能另一种类型的欺骗的。我还没有尝试过,但如果我最终会被逼,我不会感到惊讶。

9

这是我用过,在短暂的...

get_user_pages引脚用户页面(S),给你的struct page *指针数组。

dma_map_page每个struct page *获得页面的DMA地址(又名“I/O地址”)。这也会创建IOMMU映射(如果您的平台需要)。

现在告诉您的设备使用这些DMA地址将DMA执行到内存中。显然它们可以是非连续的;内存只能保证以页面大小的倍数连续。

dma_sync_single_for_cpu做任何必要的缓存刷新或反弹缓冲区blitting或任何。这个调用保证CPU可以真正看到DMA的结果,因为在很多系统上,修改CPU背后的物理RAM会导致过时的高速缓存。

dma_unmap_page释放IOMMU映射(如果它在您的平台上需要)。

put_page解锁用户页面。

请注意,您需要必须一路查看错误,因为整个地方的资源有限。 get_user_pages为直接错误(-errno)返回一个负数,但它可以返回一个正数来告诉您它实际设置了多少个页面(物理内存不是无限的)。如果这比你要求的少,你仍然必须遍历它所有的页面做了引脚,以便在它们上面调用put_page。 (否则你正在泄漏内核内存;非常糟糕。)

dma_map_page也可以返回一个错误(-errno),因为IOMMU映射是另一个有限的资源。

dma_unmap_pageput_page返回void,像往常一样用于Linux“释放”功能。 (Linux内核资源管理例程只会返回错误,因为实际上出错了,并不是因为你搞砸了,并且传递了一个坏指针或其他东西。基本假设是你是从来没有搞砸了,因为这是内核代码。虽然get_user_pages确实检查以确保用户地址的有效性,并且如果用户向您提交了错误的指针,则会返回错误。)

如果您想要友好的界面进行分散/收集,也可以考虑使用_sg函数。然后,你会打电话dma_map_sg代替dma_map_pagedma_sync_sg_for_cpu代替dma_sync_single_for_cpu

另外请注意,许多这些功能可能会更多或更少的平台上无操作,这样你就可以经常与懒散逃脱。 (特别是,dma_sync _...和dma_unmap _...在我的x86_64系统上什么都不做)。但是在这些平台上,这些调用本身被编译成什么都没有,所以没有任何理由怠慢。

+1

我最近被聘请为一个嵌入式项目实现一个iMX处理器的SSI端口的驱动程序,并且DMA没有使用这些功能。飞思卡尔的BSP(板级支持包)在iMX51芯片上为其SDMA内核提供了许多功能(API调用)。我认为OP是在x86 Intel硬件上......当你走出困境时,Linux是如此的混杂。 – Eric 2011-12-25 03:41:15

2

remap_pfn_range函数(用于驱动程序中的mmap调用)可用于将内核内存映射到用户空间。

一个真实的例子可以在mem字符驱动程序drivers/char/mem.c中找到。