Ian*_*han 30 linux linux-device-driver linux-kernel dma
我希望尽快将支持DMA的PCIe硬件设备中的数据导入用户空间.
问:如何将"使用/和/通过DMA传输直接I/O连接到用户空间"
通过LDD3读取,似乎我需要执行一些不同类型的IO操作!?
dma_alloc_coherent给我一个可以传递给硬件设备的物理地址.但是在传输完成时需要设置get_user_pages并执行copy_to_user类型调用.这似乎是浪费,要求设备DMA进入内核内存(充当缓冲区),然后再将其传输到用户空间.LDD3 p453:/* Only now is it safe to access the buffer, copy to user, etc. */
我理想的是一些记忆:
我是否需要单页流映射,设置映射和用户空间缓冲区映射get_user_pages dma_map_page?
到目前为止,我的代码get_user_pages在用户空间的给定地址设置(我称之为直接I/O部分).然后,dma_map_page用一个页面get_user_pages.我将设备的返回值dma_map_page作为DMA物理传输地址.
我使用一些内核模块作为参考:drivers_scsi_st.c和drivers-net-sh_eth.c.我会看看infiniband代码,但无法找到哪一个是最基本的!
提前谢谢了.
Rak*_*kis 16
我现在正在做同样的事情,我正在走这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);
Run Code Online (Sandbox Code Playgroud)
增量传递函数的伪代码:
/*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(¤t->mm->mmap_sem);
ret = get_user_pages(current,
current->mm,
udata,
npages,
is_writing_to_userspace,
0,
&pages_array,
NULL);
up_read(¤t->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;
}
Run Code Online (Sandbox Code Playgroud)
中断处理程序非常简短:
/* 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);
Run Code Online (Sandbox Code Playgroud)
请注意,这只是一种通用的方法,我在过去的几周里一直在研究这个驱动程序并且尚未对它进行实际测试...所以请不要将这个伪代码视为福音并确保加倍检查所有逻辑和参数;-).
Rol*_*and 14
你基本上有正确的想法:在2.1中,你可以让用户空间分配任何旧的内存.您确实希望页面对齐,因此posix_memalign()使用它是一个方便的API.
然后让用户空间以某种方式传递用户空间虚拟地址和此缓冲区的大小; ioctl()是一种快速而又脏的方法.在内核中,分配一个适当大小的struct page*- user_buf_size/PAGE_SIZEentries条目 - 并用于get_user_pages()获取用户空间缓冲区的struct page*列表.
一旦你有了这个,你可以分配一个struct scatterlist与你的页面数组大小相同的数组,并循环遍历页面列表sg_set_page().神光列表设置后,你做dma_map_sg()的散布的阵列上,然后就可以得到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.
小智 6
在某些时候,我想允许用户空间应用程序分配DMA缓冲区并将其映射到用户空间并获取物理地址以便能够控制我的设备并完全从用户空间进行DMA事务(总线主控),完全绕过Linux内核.我虽然使用了一些不同的方法.首先,我开始使用初始化/探测PCIe设备并创建字符设备的最小内核模块.然后该驱动程序允许用户空间应用程序执行以下两项操作:
remap_pfn_range()功能将PCIe设备的I/O栏映射到用户空间.基本上,它归结为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;
}
Run Code Online (Sandbox Code Playgroud)
另一个使用DMA缓冲区分配pci_alloc_consistent()的方法有点复杂:
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, size, &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, vma->vm_start,
vmalloc_to_pfn(buf->cpu_addr),
size, vma->vm_page_prot))
{
/* Out of luck, rollback... */
pci_free_consistent(dev->pci_dev, buf->size, buf->cpu_addr,
buf->dma_addr);
buf->cpu_addr = NULL;
return -EAGAIN;
}
return 0; /* All good! */
}
Run Code Online (Sandbox Code Playgroud)
一旦到位,用户空间应用程序几乎可以完成所有工作 - 通过读/写I/O寄存器来控制设备,分配和释放任意大小的DMA缓冲区,并让设备执行DMA事务.唯一缺少的部分是中断处理.我正在用户空间进行轮询,刻录我的CPU,并禁用中断.
希望能帮助到你.祝好运!
| 归档时间: |
|
| 查看次数: |
35642 次 |
| 最近记录: |