DMA即Direct Memory Access,是一種允許外設直接存取記憶體數據而沒有CPU參與的技術,當外設對於該塊記憶體的讀寫完成之後,DMAC通過中斷通知CPU,這種技術多用於對數據量和數據傳輸速度都有很高要求的外設控制,比如顯示設備等。 DMA和Cache一致性 我們知道,為了提高系統運行效率 ...
DMA即Direct Memory Access,是一種允許外設直接存取記憶體數據而沒有CPU參與的技術,當外設對於該塊記憶體的讀寫完成之後,DMAC通過中斷通知CPU,這種技術多用於對數據量和數據傳輸速度都有很高要求的外設控制,比如顯示設備等。
DMA和Cache一致性
我們知道,為了提高系統運行效率,現代的CPU都採用多級緩存結構,其中就包括使用多級Cache技術來緩存記憶體中的數據來緩解CPU和記憶體速度差異問題。在這種前提下,顯而易見,如果DMA記憶體的數據已經被Cache緩存了,而外設又修改了其中的數據,這就會造成Cache數據和記憶體數據不匹配的問題,即DMA與Cache的一致性問題。為瞭解決這個問題,最簡單的辦法就是禁掉對DMA記憶體的Cache功能,顯然,這會導致性能的降低
虛擬地址 VS 物理地址 VS 匯流排地址
在有MMU的電腦中,CPU看到的是虛擬地址,發給MMU後轉換成物理地址,虛擬地址再經過相應的電路轉換成匯流排地址,就是外設看到的地址。所以,DMA外設看到的地址其實是匯流排地址。Linux內核提供了相應的API來實現三種地址間的轉換:
//虛擬->物理
virt_to_phys()
//物理->虛擬
ioremap()
//虛擬->匯流排
virt_to_bus()
//匯流排->虛擬
bus_to_virt()
DMA地址掩碼
DMA外設並不一定能在所有的記憶體地址上執行DMA操作,此時應該使用DMA地址掩碼
int dma_set_mask(struct device *dev,u64 mask);
比如一個只能訪問24位地址的DMA外設,就使用dma_set_mask(dev,0xffffff)
編程流程
下麵是在內核程式中使用DMA記憶體的流程:
一致性DMA
如果在驅動中使用DMA緩衝區,可以使用內核提供的已經考慮到一致性的API:
/**
* request_dma - 申請DMA通道
* On certain platforms, we have to allocate an interrupt as well...
*/
int request_dma(unsigned int chan, const char *device_id);
/**
* dma_alloc_coherent - allocate consistent memory for DMA
* @dev: valid struct device pointer, or NULL for ISA and EISA-like devices
* @size: required memory size
* @handle: bus-specific DMA address *
* Allocate some memory for a device for performing DMA. This function
* allocates pages, and will return the CPU-viewed address, and sets @handle
* to be the device-viewed address.
*/
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)
//申請PCI設備的DMA緩衝區
void *pci_alloc_consistent(struct pci_dev *hwdev, size_t size, dma_addr_t *dma_handle)
//釋放DMA緩衝區
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle )
//釋放PCI設備的DMA緩衝區
void pci_free_consistent()
/**
* free_dma - 釋放DMA通道
* On certain platforms, we have to free interrupt as well...
*/
void free_dma(unsigned int chan);
流式DMA
如果使用應用層的緩衝區建立的DMA申請而不是驅動中的緩衝區,可能僅僅使用kmalloc等函數進行申請,那麼就需要使用流式DMA緩衝區,此外,還要解決Cache一致性的問題。
/**
* request_dma - 申請DMA通道
* On certain platforms, we have to allocate an interrupt as well...
*/
int request_dma(unsigned int chan, const char *device_id);
//映射流式DMA
dma_addr_t dma_map_single(struct device *dev,void *buf, size_t size, enum dma_datadirection direction);
//驅動獲得DMA擁有權,通常驅動不該這麼做
void dma_sync_single_for_cpu(struct device *dev,dma_addr_t dma_handle_t bus_addr,size_t size, enum dma_data_direction direction);
//將DMA擁有權還給設備
void dma_sync_single_for_device(struct device *dev,dma_addr_t dma_handle_t bus_addr,size_t size, enum dma_data_direction direction);
//去映射流式DMA
dma_addr_t dma_unmap_single(struct device *dev,void *buf, size_t size, enum dma_datadirection direction);
/**
* free_dma - 釋放DMA通道
* On certain platforms, we have to free interrupt as well...
*/
void free_dma(unsigned int chan);