一尘不染

Linux内核设备驱动程序将DMA从设备插入用户空间内存

linux

我想将数据从启用DMA的PCIe硬件设备尽快移入用户空间。

问:如何将“直接I / O通过DMA传输和/或通过DMA传输结合到用户空间”

  1. 通读LDD3,看来我需要执行几种不同类型的IO操作!

dma_alloc_coherent给我可以传递给硬件设备的物理地址。但是将需要设置get_user_pagescopy_to_user在传输完成后执行类型调用。这似乎很浪费,要求设备将DMA
DMA到内核内存(充当缓冲区),然后将其再次传输到用户空间。LDD3 p453:/* Only now is it safe to access the buffer, copy to user, etc. */

  1. 我理想地想要的是一些内存,该内存可以:

    • 我可以在用户空间中使用(也许通过ioctl调用请求驱动程序来创建DMA’able内存/缓冲区?)
    • 我可以从中获取物理地址以传递给设备,以便所有用户空间要做的就是在驱动程序上执行读取
    • read方法将激活DMA传输,阻塞等待DMA完成中断,然后释放用户空间读取的内容(用户空间现在可以安全地使用/读取内存)。

我是否需要使用来映射单页流映射,设置映射和用户空间缓冲区get_user_pages dma_map_page

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

我使用一些内核模块作为参考:drivers_scsi_st.cdrivers-net- sh_eth.c。我会看infiniband代码,但是找不到最基本的代码!

提前谢谢了。


阅读 426

收藏
2020-06-03

共1个答案

一尘不染

我现在实际上正在做完全相同的事情,并且正在走ioctl()路线。一般的想法是让用户空间分配缓冲区,该缓冲区将用于DMA传输,并且ioctl()将用于将缓冲区的大小和地址传递给设备驱动程序。然后,驱动程序将使用分散收集列表以及流DMA
API来直接与设备和用户空间缓冲区之间来回传输数据。

我正在使用的实现策略是,ioctl()驱动程序中的进入一个循环,该循环以256k的块(这是硬件对其可处理的分散/聚集条目的硬性限制)的形式进入DMA的用户空间缓冲区。这是隔离在一个函数内部的,该函数在每次传输完成之前都会阻塞(请参阅下文)。当所有字节传输完毕或增量传输函数返回错误时,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,
                     0,
                     &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);

请注意,这只是一种通用方法,最近几周我一直在研究此驱动程序,但尚未进行实际测试。因此,请不要将此伪代码视为福音,并且一定要加倍检查所有逻辑和参数;-)。

2020-06-03