微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Linux内核设备驱动程序将DMA从设备转移到用户空间内存中

我希望尽快将支持DMA的PCIe硬件设备的数据传输到用户空间。

问:如何将“直接I / O与用户空间与/和/经由DMA传输”

通过LDD3读取,似乎我需要执行一些不同types的IO操作!?

dma_alloc_coherent给了我可以传递给硬件设备的物理地址。 但是,当传输完成时,需要设置get_user_pages并执行copy_to_usertypes的调用。 这似乎是一个浪费,要求设备DMA进入内核内存(作为缓冲区),然后再次传输到用户空间。 LDD3 p453: /* Only Now is it safe to access the buffer,copy to user,etc. */

内核模式驱动程序写入文件

使用用户空间进程来协助内核模块

REPEAT_BYTE(x)macros

编译内核时编译/ lib / modules / $(uname -r)/ build

Linux的重启()系统调用:为什么在kernel_halt()之后调用do_exit(0)?

我最想要的是一些记忆:

我可以在用户空间中使用(也许请求驱动程序通过ioctl调用来创buildDMA'able内存/缓冲区?)

我可以得到一个物理地址传递给设备,这样所有用户空间所要做的就是在驱动程序上执行一个读操作

读取方法将激活DMA传输,阻塞等待DMA完成中断,并释放用户空间后读取(用户空间现在是安全的使用/读取内存)。

我是否需要使用get_user_pages dma_map_page映射的单页stream式映射,设置映射和用户空间缓冲区?

我的代码到目前为止设置get_user_pages在给定的地址从用户空间(我称之为直接I / O部分)。 然后,带有来自get_user_pages页面的dma_map_page 。 我给设备返回来自dma_map_page的返回值作为DMA物理传输地址。

我使用一些内核模块作为参考: drivers_scsi_st.c和drivers-net-sh_eth.c 。 我会看infiniband代码,但不能find哪一个是最基本的!

提前谢谢了。

预计的杀戮持续时间和waitpid收割能力

从内核获取当前的驱动器path

在C中重新启动程序

简单的内存malloc,并在linux内核的ip_output.c中释放

在没有完全信任的情况下devise基于Linux的系统来实现所有权/pipe理权的可转移性

实际上我现在正在处理完全相同的事情,而我正在使用ioctl()路由。 总体思路是为用户空间分配将用于DMA传输的缓冲区,并使用ioctl()将此缓冲区的大小和地址传递给设备驱动程序。 然后,驱动程序将使用分散 – 收集列表以及流式DMA API来直接传输数据到设备和用户空间缓冲区。

我使用的实现策略是,驱动程序中的ioctl()进入一个循环,DMA以256k块的形式(这是硬件限制它可以处理多少个分散/收集条目)的用户空间缓冲区。 这是隔离在一个功能,阻止,直到每个传输完成(见下文)。 当所有字节传输或者增量传输函数返回错误时, ioctl()退出并返回到用户空间

代码为ioctl()

/*serialize all DMA transfers to/from the device*/ if (mutex_lock_interruptible( &device_ptr->mtx ) ) return -EINTR; chunk_data = (unsigned long) user_space_addr; while( *transferred < total_bytes && !ret ) { chunk_bytes = total_bytes - *transferred; if (chunk_bytes > HW_DMA_MAX) chunk_bytes = HW_DMA_MAX; /* 256kb limit imposed by my device */ ret = transfer_chunk(device_ptr,chunk_data,chunk_bytes,transferred); chunk_data += chunk_bytes; chunk_offset += chunk_bytes; } mutex_unlock(&device_ptr->mtx);

用于增量传递函数的伪代码

/*Assuming the userspace pointer is passed as an unsigned long,*/ /*calculate the first,last,and number of pages being transferred via*/ first_page = (udata & PAGE_MASK) >> PAGE_SHIFT; last_page = ((udata+nbytes-1) & PAGE_MASK) >> PAGE_SHIFT; first_page_offset = udata & PAGE_MASK; npages = last_page - first_page + 1; /* Ensure that all userspace pages are locked in memory for the */ /* duration of the DMA transfer */ down_read(&current->mm->mmap_sem); ret = get_user_pages(current,current->mm,udata,npages,is_writing_to_userspace,&pages_array,NULL); up_read(&current->mm->mmap_sem); /* Map a scatter-gather list to point at the userspace pages */ /*first*/ sg_set_page(&sglist[0],pages_array[0],PAGE_SIZE - fp_offset,fp_offset); /*middle*/ for(i=1; i < npages-1; i++) sg_set_page(&sglist[i],pages_array[i],PAGE_SIZE,0); /*last*/ if (npages > 1) { sg_set_page(&sglist[npages-1],pages_array[npages-1],nbytes - (PAGE_SIZE - fp_offset) - ((npages-2)*PAGE_SIZE),0); } /* Do the hardware specific thing to give it the scatter-gather list and tell it to start the DMA transfer */ /* Wait for the DMA transfer to complete */ ret = wait_event_interruptible_timeout( &device_ptr->dma_wait,&device_ptr->flag_dma_done,HZ*2 ); if (ret == 0) /* DMA operation timed out */ else if (ret == -ERESTARTSYS ) /* DMA operation interrupted by signal */ else { /* DMA success */ *transferred += nbytes; return 0; }

中断处理程序非常简短:

/* Do hardware specific thing to make the device happy */ /* Wake the thread waiting for this DMA operation to complete */ device_ptr->flag_dma_done = 1; wake_up_interruptible(device_ptr->dma_wait);

请注意,这只是一个普遍的方法,过去几个星期我一直在研究这个驱动程序,还没有真正的测试它…所以,请不要把这个伪代码当作福音书,而且一定要加倍检查所有的逻辑和参数;-)。

你基本上有正确的想法:在2.1,你可以让用户空间分配任何旧的内存。 你确实希望页面对齐,所以posix_memalign()是一个方便的API使用。

然后让用户空间以某种方式传递该缓冲区的用户空间虚拟地址和大小; ioctl()是一个很好的快速和肮脏的方法来做到这一点。 在内核中,分配适当大小的struct page* – user_buf_size/PAGE_SIZE条目的缓冲区数组,并使用get_user_pages()获取用户空间缓冲区的struct page *列表。

一旦你有了,你可以分配一个与你的页面数组大小相同的struct scatterlist sg_set_page()列表的数组,并循环遍历sg_set_page()的页面列表。 在sg列表设置之后,你可以在dma_map_sg()列表数组上做dma_map_sg() ,然后你可以得到sg_dma_address sg_dma_len中每个条目的sg_dma_address和sg_dma_len (注意你必须使用dma_map_sg()的返回值,因为你可能会结束由于事情可能被DMA映射代码合并,因此映射条目较少)。

这样就可以将所有的总线地址传递给你的设备,然后你可以触发DMA并等待它。 基于read()的方案你可能没问题。

您可以参考drivers / infiniband / core / umem.c,特别是ib_umem_get() ,以获得构建此映射的某些代码,但该代码需要处理的一般性可能会使其有点混淆。

或者,如果您的设备不能很好地处理分散/收集列表,并且想要连续的内存,则可以使用get_free_pages()分配物理连续的缓冲区,并使用dma_map_page() 。 为了给用户空间访问内存,你的驱动程序只需要实现一个mmap方法,而不是上面描述的ioctl。

在某些时候,我想允许用户空间应用程序分配DMA缓冲区,并将其映射到用户空间,并获得物理地址以便能够控制我的设备,并完全从用户空间完成DMA事务(总线主控),完全绕过Linux内核。 尽管我已经使用了一些不同的方法。 首先,我开始使用一个最小化的内核模块来初始化/探测PCIe设备并创建一个字符设备。 该驱动程序然后允许用户空间应用程序做两件事情:

使用remap_pfn_range()函数将PCIe设备的I / O栏映射到用户空间。

分配和释放DMA缓冲区,将它们映射到用户空间,并将物理总线地址传递给用户空间应用程序。

基本上,它归结为mmap()调用(尽管file_operations )的自定义实现。 一个I / O栏很容易:

struct vm_operations_struct a2gx_bar_vma_ops = { }; static int a2gx_cdev_mmap_bar2(struct file *filp,struct vm_area_struct *vma) { struct a2gx_dev *dev; size_t size; size = vma->vm_end - vma->vm_start; if (size != 134217728) return -EIO; dev = filp->private_data; vma->vm_ops = &a2gx_bar_vma_ops; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); vma->vm_private_data = dev; if (remap_pfn_range(vma,vma->vm_start,vmalloc_to_pfn(dev->bar2),size,vma->vm_page_prot)) { return -EAGAIN; } return 0; }

一个使用pci_alloc_consistent()分配DMA缓冲区的方法稍微复杂一些:

static void a2gx_dma_vma_close(struct vm_area_struct *vma) { struct a2gx_dma_buf *buf; struct a2gx_dev *dev; buf = vma->vm_private_data; dev = buf->priv_data; pci_free_consistent(dev->pci_dev,buf->size,buf->cpu_addr,buf->dma_addr); buf->cpu_addr = NULL; /* Mark this buffer data structure as unused/free */ } struct vm_operations_struct a2gx_dma_vma_ops = { .close = a2gx_dma_vma_close }; static int a2gx_cdev_mmap_dma(struct file *filp,struct vm_area_struct *vma) { struct a2gx_dev *dev; struct a2gx_dma_buf *buf; size_t size; unsigned int i; /* Obtain a pointer to our device structure and calculate the size of the requested DMA buffer */ dev = filp->private_data; size = vma->vm_end - vma->vm_start; if (size < sizeof(unsigned long)) return -EINVAL; /* Something fishy is happening */ /* Find a structure where we can store extra information about this buffer to be able to release it later. */ for (i = 0; i < A2GX_DMA_BUF_MAX; ++i) { buf = &dev->dma_buf[i]; if (buf->cpu_addr == NULL) break; } if (buf->cpu_addr != NULL) return -ENOBUFS; /* Oops,hit the limit of allowed number of allocated buffers. Change A2GX_DMA_BUF_MAX and recompile? */ /* Allocate consistent memory that can be used for DMA transactions */ buf->cpu_addr = pci_alloc_consistent(dev->pci_dev,&buf->dma_addr); if (buf->cpu_addr == NULL) return -ENOMEM; /* Out of juice */ /* There is no way to pass extra information to the user. And I am too lazy to implement this mmap() call using ioctl(). So we simply tell the user the bus address of this buffer by copying it to the allocated buffer itself. Hacks,hacks everywhere. */ memcpy(buf->cpu_addr,&buf->dma_addr,sizeof(buf->dma_addr)); buf->size = size; buf->priv_data = dev; vma->vm_ops = &a2gx_dma_vma_ops; vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); vma->vm_private_data = buf; /* * Map this DMA buffer into user space. */ if (remap_pfn_range(vma,vmalloc_to_pfn(buf->cpu_addr),vma->vm_page_prot)) { /* Out of luck,rollback... */ pci_free_consistent(dev->pci_dev,buf->dma_addr); buf->cpu_addr = NULL; return -EAGAIN; } return 0; /* All good! */ }

一旦这些应用程序到位,用户空间应用程序几乎可以做任何事情 – 通过读/写I / O寄存器来控制设备,分配和释放任意大小的DMA缓冲区,并让设备执行DMA事务。 唯一缺少的部分是中断处理。 我在用户空间轮询,烧我的cpu,并禁用中断。

希望能帮助到你。 祝你好运!

我对实施的方向感到困惑。 我要…

考虑设计驱动程序时的应用程序。

数据移动的性质,频率,大小以及系统中可能发生的事情是什么?

传统的读写API是否足够? 将设备直接映射到用户空间好吗? 反射(半连贯)共享内存是否可取?

手动操作数据(读/写)是一个非常好的选择,如果数据适合于很好的理解。 内联复制使用通用VM和读/写就足够了。 直接映射不可访问的外设是方便的,但可能笨拙。 如果访问是大型块的相对罕见的移动,则使用正常的内存,具有驱动器引脚,翻译地址,DMA和释放页面可能是有意义的。 作为优化,页面(也许是巨大的)可以被预先固定和翻译; 驱动器可以识别准备好的内存,避免动态翻译的复杂性。 如果有很多很少的I / O操作,使驱动器异步运行是有道理的。 如果优雅很重要,VM脏页面标志可用于自动识别需要移动的内容,并且可以使用(Meta_sync())调用来刷新页面。 也许是上述作品的混合物…

在深入细节之前,人们往往不会去看待更大的问题。 通常最简单的解决方案就足够了。 构建行为模型的一些努力可以帮助指导什么API是优选的。

first_page_offset = udata & PAGE_MASK;

这似乎是错误的。 它应该是:

first_page_offset = udata & ~PAGE_MASK;

要么

first_page_offset = udata & (PAGE_SIZE - 1)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐