Linux内核设备驱动程序从设备到用户空间内存的DMA [英] Linux kernel device driver to DMA from a device into user-space memory

查看:24
本文介绍了Linux内核设备驱动程序从设备到用户空间内存的DMA的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想尽快将数据从启用 DMA 的 PCIe 硬件设备获取到用户空间.

I want to get data from a DMA enabled, PCIe hardware device into user-space as quickly as possible.

问:我如何将直接 I/O 与/和/通过 DMA 传输结合到用户空间"

Q: How do I combine "direct I/O to user-space with/and/via a DMA transfer"

  1. 通读LDD3,好像需要进行几种不同类型的IO操作!?

  1. Reading through LDD3, it seems that I need to perform a few different types of IO operations!?

dma_alloc_coherent 为我提供了可以传递给硬件设备的物理地址.但是需要设置 get_user_pages 并在传输完成时执行 copy_to_user 类型的调用.这似乎是一种浪费,要求设备 DMA 进入内核内存(充当缓冲区),然后再次将其传输到用户空间.LDD3 p453: /* 现在访问缓冲区、复制给用户等是安全的 */

dma_alloc_coherent gives me the physical address that I can pass to the hardware device. But would need to have setup get_user_pages and perform a copy_to_user type call when the transfer completes. This seems a waste, asking the Device to DMA into kernel memory (acting as buffer) then transferring it again to user-space. LDD3 p453: /* Only now is it safe to access the buffer, copy to user, etc. */

我最想要的是一些记忆:

What I ideally want is some memory that:

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

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

Do I need single-page streaming mappings, setup mapping and user-space buffers mapped with get_user_pages dma_map_page?

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

My code so far sets up get_user_pages at the given address from user-space (I call this the Direct I/O part). Then, dma_map_page with a page from get_user_pages. I give the device the return value from dma_map_page as the DMA physical transfer address.

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

I am using some kernel modules as reference: drivers_scsi_st.c and drivers-net-sh_eth.c. I would look at infiniband code, but cant find which one is the most basic!

非常感谢.

推荐答案

我现在实际上正在做完全相同的事情,我将采用 ioctl() 路线.一般的想法是让用户空间分配用于 DMA 传输的缓冲区,并且使用 ioctl() 将这个缓冲区的大小和地址传递给设备驱动程序.然后,驱动程序将使用 scatter-gather 列表和流式 DMA API 将数据直接传入和传出设备和用户空间缓冲区.

I'm actually working on exactly the same thing right now and I'm going the ioctl() route. The general idea is for user space to allocate the buffer which will be used for the DMA transfer and an ioctl() will be used to pass the size and address of this buffer to the device driver. The driver will then use scatter-gather lists along with the streaming DMA API to transfer data directly to and from the device and user-space buffer.

我使用的实现策略是驱动程序中的 ioctl() 进入一个循环,该循环以 256k 的块为 DMA 的用户空间缓冲区(这是硬件对多少分散/收集它可以处理的条目).这被隔离在一个函数中,该函数在每次传输完成之前都会阻塞(见下文).当所有字节传输完毕或增量传输函数返回错误时,ioctl() 退出并返回用户空间

The implementation strategy I'm using is that the ioctl() in the driver enters a loop that DMA's the userspace buffer in chunks of 256k (which is the hardware imposed limit for how many scatter/gather entries it can handle). This is isolated inside a function that blocks until each transfer is complete (see below). When all bytes are transfered or the incremental transfer function returns an error the ioctl() exits and returns to userspace

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);

增量传递函数的伪代码:

Pseudo code for incremental transfer function:

/*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;
}

中断处理程序异常简短:

The interrupt handler is exceptionally brief:

/* 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);

请注意,这只是一种通用方法,过去几周我一直在研究此驱动程序,但尚未对其进行实际测试...所以请不要将此伪代码视为福音一定要仔细检查所有的逻辑和参数;-)

Please note that this is just a general approach, I've been working on this driver for the last few weeks and have yet to actually test it... So please, don't treat this pseudo code as gospel and be sure to double check all logic and parameters ;-).

这篇关于Linux内核设备驱动程序从设备到用户空间内存的DMA的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆