我实际上正在处理完全相同的事情,现在我要去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(¤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;
}
中断处理程序是非常简单的:
/* 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);
请注意,这只是一个一般的方法,我一直工作在这个驱动程序在过去几个星期了,还没有实际测试它......所以,请不要把这个伪代码为福音,是一定要仔细检查所有的逻辑和参数;-)。
以来,它一直在Linux下我最后的驱动程序开发的年龄,但我一直保留DMA可用内存,然后被映射到用户页面。在过去,并非所有的地址都可以用于DMA传输。现在可能并非如此。另一个提示可能是查看Linux驱动程序的视频,尤其是涉及BT848/BT878芯片的视频。我希望你能在那里找到有用的东西。 – jdehaan 2011-04-04 13:51:37
我知道这个问题是类似的:http://stackoverflow.com/q/3333959/119790 – 2011-04-04 14:08:45
我正在处理类似的问题。我很想知道你最终走了哪条路。 – mksuth 2011-06-30 14:47:25