1. 幾種地址類型
虛擬地址
Linux內核使用的地址是虛擬地址,數據類型為void *。例如,kmalloc()和vmalloc()函數返回值就是虛擬地址。
物理地址
處理器真實地址總線上的地址,數據類型為phys_addr_t。
對I/O設備寄存器和內存統一編址的處理器,如ARM/PowerPC,參考手冊一般會給出memory map,也就是各種I/O設備的寄存器在物理地址空間的分布。對I/O設備寄存器獨立編址的處理器。如X86,訪問I/O設備寄存器或內存時,向地址總線發送地址,並通過控制信號來實現對I/O設備寄存器和內存的不同尋址。這些I/O設備寄存器的地址可以在/proc/iomem中查看,必須使用ioremap()映射到虛擬地址空間才可以使用。
總線地址
從I/O設備的角度看,I/O設備使用的地址是總線地址。DMA使用地址也是總線地址,數據類型為dma_addr_t。對一些簡單的系統,設備通過DMA可以直接訪問物理地址,但大多數系統都有IOMMU將總線地址轉換為物理地址。
2. DMA尋址能力
默認情況下Linux認為設備DMA可以進行32位尋址。必須對DMA mask進行設置,將設備的DMA尋址能力通知內核。
int dma_set_mask_and_coherent(struct device *dev, u64 mask)
該函數也可以分為如下兩個函數,如果有需要,可以分別對流式映射設置DMA mask,對一致性分配設置DMA mask。
int dma_set_mask(struct device *dev, u64 mask); int dma_set_coherent_mask(struct device *dev, u64 mask);
3. DMA映射的類型
3.1 一致性DMA映射
一致性DMA映射通常在驅動初始化階段分配buffer,並且保持cache的一致性。分配和釋放一致性DMA buffer通常使用下面方法。
dma_addr_t dma_handle; cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);
dma_free_coherent(dev, size, cpu_addr, dma_handle)
當分配的buffer較小,小於一個頁的時候,通常使用dma_pool的APIs。
struct dma_pool *pool; pool = dma_pool_create(name, dev, size, align, boundary); cpu_addr = dma_pool_alloc(pool, flags, &dma_handle);
dma_pool_free(pool, cpu_addr, dma_handle);
dma_pool_destroy(pool)
3.2 流式DMA映射
流式DMA映射必須聲明DMA數據流的方向。
- DMA_BIDIRECTIONAL
- DMA_TO_DEVICE
- DMA_FROM_DEVICE
- DMA_NONE
對單個內存區域的映射和取消映射使用如下方法。
struct device *dev = &my_dev->dev; dma_addr_t dma_handle; void *addr = buffer->ptr; size_t size = buffer->len; dma_handle = dma_map_single(dev, addr, size, direction); dma_unmap_single(dev, dma_handle, size, direction);
該方法直接使用虛擬地址addr的缺點就是不能對HIGHMEM內存進行映射。下面的函數提供對page映射和取消映射的方法。
struct device *dev = &my_dev->dev; dma_addr_t dma_handle; struct page *page = buffer->page; unsigned long offset = buffer->offset; size_t size = buffer->len; dma_handle = dma_map_page(dev, page, offset, size, direction); dma_unmap_page(dev, dma_handle, size, direction);
對散列表的映射和取消映射如下,nents是sglist中entry的數目。通過散列表,將多個不連續的內存區域進行映射。返回的count的數值可能比nents小,因為有些scatterlist在內存區域連續可能進行了合並。
int i, count = dma_map_sg(dev, sglist, nents, direction); struct scatterlist *sg; for_each_sg(sglist, sg, count, i) { hw_address[i] = sg_dma_address(sg); hw_len[i] = sg_dma_len(sg); }
dma_unmap_sg(dev, sglist, nents, direction)
在流式DMA映射取消映射之前,CPU不應該訪問DMA buffer,如果需要訪問,則必須在DMA傳輸后相應地調用如下函數。
dma_sync_single_for_cpu(dev, dma_handle, size, direction);
dma_sync_sg_for_cpu(dev, sglist, nents, direction);
CPU訪問結束后,將buffer還給設備DMA使用時,需要相應調用如下函數。
dma_sync_single_for_device(dev, dma_handle, size, direction);
dma_sync_sg_for_device(dev, sglist, nents, direction);
參考
https://elixir.bootlin.com/linux/v5.4/source/Documentation/DMA-API-HOWTO.txt