之前使用过xdma和qdma,但是未对其流程梳理,今天对其DMA操作流程梳理下,以备记录。
首先说一下流式DMA映射和一致性DMA映射:
流式DMA映射:缓冲区来自叫上层的比如,kmalloc,__get_free_pages 等申请的,一般使用流式DMA映射,流式DMA映射大多进行cache的使无效或清楚操作,以解决cache的一致性问题,
接口较为复杂。
xmda使用流式dma映射:
dma_map_single(dev, addr, size, direction);
dma_unmap_single(dev, dma_handle, size, direction);
如果设备要求较大的DMA缓冲区,在支持SG模式时,可申请不连续的DMA缓冲区进行映射。
一致性DMA映射申请的缓存区能够使用cache,并且保持cache一致性。一致性映射具有很长的生命周期,在这段时间内占用的映射寄存器,即使不使用也不会释放。生命周期为该驱动的生命周期。
主要用到的函数:
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle,
gfp_t gfp);
上述函数的返回值为申请到的DMA缓冲区的虚拟地址, 此外, 该函数还通过参数handle返回DMA缓
冲区的总线地址。 handle的类型为dma_addr_t, 代表的是总线地址。
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
dma_addr_t handle);
qdma使用一致性dma映射;
先梳理下qdma:
qdma 顾名思义,即queue dma,在实际使用时,会建立最多2048和字符设备进行数据传输。使用pcie接口,再使用dma传输数据。
pcie驱动的probe过程略去不表,比较通用。
梳理下用户空间的数据是如何通过dma传输搬移的,首先,用户空间传入一个地址指针ptr和长度len,通过系统调用write传到内核空间,ptr是个用户空间的虚拟地址,也不一定是按页对齐的,所以通过 offset_in_page 获取页内的偏移量,再通过 pages_nr = (len + pg_off + PAGE_SIZE - 1) >> PAGE_SHIFT 获取到实际需要的物理页个数。
使用get_user_pages_fast函数 传入用户空间的地址和页个数,锁定用户进程的数据,并将其映射到内核空间的一个地址,及获取物理页。保存好这些数据后,通过 qdma_request 保存这些值,尤其是iocb.sgl,传入到descq_st_c2h_read等函数操作时。
qdma_queue_start -> qdma_descq_alloc_resource -> desc_ring_alloc
调用dma_alloc_coherent
void *dma_alloc_coherent(struct device *dev,size_t size,
dma_addr_t *dma_handle,int flag);函数如下:
返回一个虚拟地址,dma_handle 为总线地址,descq->desc_cmpt 中保存了调用dma_alloc_coherent分配的虚拟地址。
xdma的处理过程:
前一部分处理和qdma大致相同,传入用户空间的地址buf和长度len。
unsigned int pages_nr = (((unsigned long)buf + len + PAGE_SIZE - 1) -
((unsigned long)buf & PAGE_MASK))
>> PAGE_SHIFT; 获取实际所需的页帧个数。
sg_alloc_table
sg的详细内容可参考http://www.wowotech.net/memory_management/scatterlist.html
再调用get_user_pages_fast
offset_in_page 获取页内偏移
flush_dcache_page
如果高速缓存包含几个虚拟地址不同项指向内存中的同一页,可能会发生所谓的alias问题,flush_dcache_page有助于防止该问题
sg_set_page
还有最后一点,使用spin_lock 和spin_lock_irqsave的区别:
在任何情况下使用spin_lock_irq都是安全的。因为它既禁止本地中断,又禁止内核抢占。
spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。在同一个CPU上的中断可能会导致死锁。