DMA原本不屬於CPU體系架構部分的內容,只由於在開發中常常要用到其相關的知識,因此這裏就其基本概念、工做原理、常見問題作一個總結。markdown
DMA概述
DMA的英文拼寫是「Direct Memory Access」,漢語的意思就是直接內存訪問。DMA既能夠指內存和外設直接存取數據這種內存訪問的計算機技術,又能夠指實現該技術的硬件模塊(對於通用計算機PC而言,DMA控制邏輯由CPU和DMA控制接口邏輯芯片共同組成,嵌入式系統的DMA控制器內建在處理器芯片內部,通常稱爲DMA控制器,DMAC)。網絡
DMA內存訪問技術
使用DMA的好處就是它不須要CPU的干預而直接服務外設,這樣CPU就能夠去處理別的事務,從而提升系統的效率,對於慢速設備,如UART,其做用只是下降CPU的使用率,但對於高速設備,如硬盤,它不僅是下降CPU的使用率,並且能大大提升硬件設備的吞吐量。由於對於這種設備,CPU直接供應數據的速度過低。 因CPU只能一個總線週期最多存取一次總線,並且對於ARM,它不能把內存中A地址的值直接搬到B地址。它只能先把A地址的值搬到一個寄存器,而後再從這個寄存器搬到B地址。也就是說,對於ARM,要花費兩個總線週期才能將A地址的值送到B地址。而DMA就不一樣了,通常系統中的DMA都有突發(Burst)傳輸的能力,在這種模式下,DMA能一次傳輸幾個甚至幾十個字節的數據,因此使用DMA能使設備的吞吐能力大爲加強。架構
使用DMA時咱們必需要注意以下事實:app
- DMA使用物理地址,程序是使用虛擬地址的,因此配置DMA時必須將虛擬地址轉化成物理地址。
- 由於程序使用虛擬地址,並且通常使用cache地址,因此Cache中的內容與其物理地址(內存)的內容不必定一致,因此在啓動DMA傳輸前必定要將該地址的cache刷新,即寫入內存。
- OS並不能保證每次分配到的內存空間在物理上是連續的。尤爲是在系統使用過一段時間而又分配了一塊比較大的內存時。因此每次都須要判斷地址是否是連續的,若是不連續就須要把這段內存分紅幾段讓DMA完成傳輸
DMAC的基本配置
DMA用於無需CPU的介入而直接由專用控制器(DMA控制器)創建源與目的傳輸的應用,所以,在大量數據傳輸中解放了CPU。PIC32微控制器中的DMA可用於映射到內存空間中的不一樣外設,如從存儲區到SPI,UART或I2C等設備。DMA特性詳見器件參考手冊,這裏僅對一些基本原理與功能作一個簡析。異步
地址寄存器 |
存放DMA傳輸時存儲單元地址 |
字節計數器 |
存放DMA傳輸的字節數 |
控制寄存器 |
存放由CPU設定的DMA傳輸方式,控制命令等 |
狀態寄存器 |
存放DMAC當前的狀態,包括有無DMA請求,是否結束等 |
獨立DMA控制芯片
在課程《微機原理》中,會講到X86下一片獨立的DMA控制芯片8237A。8237A控制芯片各通道在PC機內的任務:
- CH0:用做動態存儲器的刷新控制
- CH1:爲用戶預留
- CH2:軟盤驅動器數據傳輸用的DMA控制
- CH3:硬盤驅動器數據傳輸用的DMA控制
嵌入式設備中的DMA
直接存儲器存取(DMA)控制器是一種在系統內部轉移數據的獨特外設,能夠將其視爲一種可以經過一組專用總線將內部和外部存儲器與每一個具備DMA能力的外設鏈接起來的控制器。它之因此屬於外設,是由於它是在處理器的編程控制下來執行傳輸的。值得注意的是,一般只有數據流量較大(kBps或者更高)的外設才須要支持DMA能力,這些應用方面典型的例子包括視頻、音頻和網絡接口。
通常而言,DMA控制器將包括一條地址總線、一條數據總線和控制寄存器。高效率的DMA控制器將具備訪問其所須要的任意資源的能力,而無須處理器自己的介入,它必須能產生中斷。最後,它必須能在控制器內部計算出地址。
一個處理器能夠包含多個DMA控制器。每一個控制器有多個DMA通道,以及多條直接與存儲器站(memory bank)和外設鏈接的總線,如圖1所示。在不少高性能處理器中集成了兩種類型的DMA控制器。第一類一般稱爲「系統DMA控制器」,能夠實現對任何資源(外設和存儲器)的訪問,對於這種類型的控制器來講,信號週期數是以系統時鐘(SCLK)來計數的,以ADI的Blackfin處理器爲例,頻率最高可達133MHz。第二類稱爲內部存儲器DMA控制器(IMDMA),專門用於內部存儲器所處位置之間的相互存取操做。由於存取都發生在內部(L1-L一、L1-L2,或者L2-L2),週期數的計數則之內核時鐘(CCLK)爲基準來進行,該時鐘的速度能夠超過600MHz。
每一個DMA控制器有一組FIFO,起到DMA子系統和外設或存儲器之間的緩衝器的做用。對於MemDMA(Memory DMA)來講,傳輸的源端和目標端都有一組FIFO存在。當資源緊張而不能完成數據傳輸的話,則FIFO能夠提供數據的暫存區,從而提升性能。
由於一般會在代碼初始化過程當中對DMA控制器進行配置,內核就只須要在數據傳輸完成後對中斷作出響應便可。你能夠對DMA控制進行編程,讓其與內核並行地移動數據,而同時讓內核執行其基本的處理任務―那些應該讓它專一完成的工做。

在一個優化的應用中,內核永遠不用參與任何數據的移動,而僅僅對L1存儲器中的數據進行讀寫。因而,內核不須要等待數據的到來,由於DMA引擎會在內核準備讀取數據以前將數據準備好。圖2給出了處理器和DMA控制器間的交互關係。由處理器完成的操做步驟包括:創建傳輸,啓用中斷,生成中斷時執行代碼。返回處處理器的中斷輸入能夠用來指示「數據已經準備好,可進行處理」。

數據除了往來外設以外,還須要從一個存儲器空間轉移到另外一個空間中。例如,視頻源能夠從一個 視頻端口直接流入L3存儲器,由於工做緩衝區規模太大,沒法放入到存儲器中。咱們並不但願讓處理器在每次須要執行計算時都從外部存儲讀取像素信息,所以爲 了提升存取的效率,能夠用一個存儲器到存儲器的DMA(MemDMA)來將像素轉移到L1或者L2存儲器中。
到目前爲之,咱們還僅專一於數據的移動,可是DMA的傳送能力並不老是用來移動數據。
在最簡單的MemDMA狀況中,咱們須要告訴DMA控制器源端地址、目標端地址和待傳送的字的個數。每次傳輸的字的大小能夠是八、16或者12位。 咱們只須要改變數據傳輸每次的數據大小,就能夠簡單地增長DMA的靈活性。例如,採用非單一大小的傳輸方式時,咱們以傳輸數據塊的大小的倍數來做爲地址增量。也就是說,若規定32位的傳輸和4個採樣的跨度,則每次傳輸結束後,地址的增量爲16字節(4個32位字)。
DMA的設置
目前有兩類主要的DMA傳輸結構:寄存器模式和描述符模式。不管屬於哪一類DMA,表1所描述的幾類信息都會在DMA控制器中出現。當DMA以寄存器模式工做時,DMA控制器只是簡單地利用寄存器中所存儲的參數值。在描述符模式中,DMA控制器在存儲器中查找本身的配置參數。
基於寄存器的DMA
在基於寄存器的DMA內部,處理器直接對DMA控制寄存器進行編程,來啓動傳輸。基於寄存器的DMA提供了最佳的DMA控制器性能,由於寄存器並不須要不斷地從存儲器中的描述符上載入數據,而內核也不須要保持描述符。
基於寄存器的DMA由兩種子模式組成:自動緩衝(Autobuffer)模式和中止模式。在自動緩衝DMA中,當一個傳輸塊傳輸完畢,控制寄存器就自動從新載入其最初的設定值,同一個DMA進程從新啓動,開銷爲零。
正如咱們在圖3中所看到的那樣,若是將一個自動緩衝DMA設定爲從外設傳輸必定數量的字到 L1數據存儲器的緩衝器上,則DMA控制器將會在最後一個字傳輸完成的時刻就迅速從新載入初始的參數。這構成了一個「循環緩衝器」,由於當一個量值被寫入 到緩衝器的最後一個位置上時,下一個值將被寫入到緩衝器的第一個位置上。
自動緩衝DMA特別適合於對性能敏感的、存在持續數據流的應用。DMA控制器能夠在獨立於處理器其餘活動的狀況下讀入數據流,而後在每次傳輸結束時,向內核發出中斷。
中止模式的工做方式與自動緩衝DMA相似,區別在於各寄存器在DMA結束後不會從新載入,因 此整個DMA傳輸只發生一次。中止模式對於基於某種事件的一次性傳輸來講十分有用。例如,非按期地將數據塊從一個位置轉移到另外一個位置。當你須要對事件進 行同步時,這種模式也很是有用。例如,若是一個任務必須在下一次傳輸前完成的話,則中止模式能夠確保各事件發生的前後順序。此外,中止模式對於緩衝器的初 始化來講很是有用。
描述符模型
基於描述符(descriptor)的DMA要求在存儲器中存入一組參數,以 啓動DMA的系列操做。該描述符所包含的參數與那些一般經過編程寫入DMA控制寄存器組的全部參數相同。不過,描述符還能夠允許多個DMA操做序列串在一 起。在基於描述符的DMA操做中,咱們能夠對一個DMA通道進行編程,在當前的操做序列完成後,自動設置並啓動另外一次DMA傳輸。基於描述符的方式爲管理 系統中的DMA傳輸提供了最大的靈活性。
ADI 的Blackfin處理器上有兩種主要的描述符方式―描述符陣列和描述符列表,這兩種操做方式所要實現的目標是在靈活性和性能之間實現一種折中平衡。
1. 什麼是DMA
直接內存訪問是一種硬件機制,它容許外圍設備和主內存之間直接傳輸它們的I/O數據,而不須要系統處理器的參與。使用這種機制能夠大大提升與設備通訊的吞吐量。
2. DMA數據傳輸
有兩種方式引起數據傳輸:
第一種狀況:軟件對數據的請求
1. 當進程調用read,驅動程序函數分配一個DMA緩衝區,並讓硬件將數據傳輸到這個緩衝區中。進程處於睡眠狀態。
2. 硬件將數據寫入到DMA緩衝區中,當寫入完畢,產生一箇中斷
3. 中斷處理程序獲取輸入的數據,應答中斷,並喚起進程,該進程如今便可讀取數據
第二種狀況發生在異步使用DMA時。
1. 硬件產生中斷,宣告新數據的到來
2. 中斷處理程序分配一個緩衝區,而且告訴硬件向哪裏傳輸數據
3. 外圍設備將數據寫入數據區,完成後,產生另一箇中斷
4.處理程序分發新數據,喚醒任何相關進程,而後執行清理工做
高效的DMA處理依賴於中斷報告。
3. 分配DMA緩衝區
使用DMA緩衝區的主要問題是:當大於一頁時,它們必須佔據連續的物理頁,由於設備使用ISA或PCI系統總線傳輸數據,而這兩種方式使用的都是物理地址。
使用get_free_pasges能夠分配多大幾M字節的內存(MAX_ORDER是11),可是對於較大數量(即便是遠小於128KB)的請求,一般會失敗,這是由於系統內存充滿了內存碎片。
解決方法之一就是在引導時分配內存,或者爲緩衝區保留頂部物理內存。
例子:在系統引導時,向內核傳遞參數「mem=value」的方法保留頂部的RAM。好比系統有256內存,參數「mem=255M」,使內核不能使用頂部的1M字節。隨後,模塊可使用下面代碼得到該內存的訪問權:
dmabuf=ioremap(0XFF00000/**255M/, 0X100000/*1M/*);
解決方法之二是使用GPF_NOFAIL分配標誌爲緩衝區分配內存,可是該方法爲內存管理子系統帶來了至關大的壓力。
解決方法之三十設備支持分散/彙集I/O,這能夠將緩衝區分配成多個小塊,設備會很好地處理它們。
4. 通用DMA層
DMA操做最終會分配緩衝區,並將總線地址傳遞給設備。內核提升了一個與總線——體系結構無關的DMA層。強烈建議在編寫驅動程序時,爲DMA操做使用該層。使用這些函數的頭文件是<linux/dmamapping.h>。
int dma_set_mask(struct device *dev, u64 mask);
該掩碼顯示該設備能尋址能力對應的位。好比說,設備受限於24位尋址,則mask應該是0x0FFFFFF。
5. DMA映射
IOMMU在設備可訪問的地址範圍內規劃了物理內存,使得物理上分散的緩衝區對設備來講成連續的。對IOMMU的運用須要使用到通用DMA層,而vir_to_bus函數不能完成這個任務。可是,x86平臺沒有對IOMMU的支持。
解決之道就是創建回彈緩衝區,而後,必要時會將數據寫入或者讀出回彈緩衝區。缺點是下降系統性能。
根據DMA緩衝區指望保留的時間長短,PCI代碼區分兩種類型的DMA映射:
一是一致性DMA映射,存在於驅動程序生命週期中,一致性映射的緩衝區必須可同時被CPU和外圍設備訪問。一致性映射必須保存在一致性緩存中。創建和使用一致性映射的開銷是很大的。
二是流式DMA映射,內核開發者建議儘可能使用流式映射,緣由:一是在支持映射寄存器的系統中,每一個DMA映射使用總線上的一個或多個映射寄存器,而一致性映射生命週期很長,長時間佔用這些這些寄存器,甚至在不使用他們的時候也不釋放全部權;二是在一些硬件中,流式映射能夠被優化,但優化的方法對一致性映射無效。
6. 創建一致性映射
驅動程序可調用pci_alloc_consistent函數創建一致性映射:
void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int falg);
該函數處理了緩衝區的分配和映射,前兩個參數是device結構和所需的緩衝區的大小。函數在兩處返回DMA映射的結果:函數的返回值是緩衝區的內核虛擬地址,能夠被驅動程序使用;而與其相關的總線地址保存在dma_handle中。
當再也不須要緩衝區時,調用下函數:
void dma_free_conherent(struct device *dev, size_t size, void *vaddr, dma_addr_t *dma_handle);
7. DMA池
DMA池是一個生成小型,一致性DMA映射的機制。調用dma_alloc_coherent函數得到的映射,可能其最小大小爲單個頁。若是設備須要的DMA區域比這還小,就是用DMA池。在<linux/dmapool.h>中定義了DMA池函數:
struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation);
void dma_pool_destroy(struct dma_pool *pool);
name是DMA池的名字,dev是device結構,size是從該池中分配的緩衝區的大小,align是該池分配操做所必須遵照的硬件對齊原則(用字節表示),若是allocation不爲零,表示內存邊界不能超越allocation。好比說傳入的allocation是4K,表示從該池分配的緩衝區不能跨越4KB的界限。
在銷燬以前必須向DMA池返回全部分配的內存。
void * dma_pool_alloc(sturct dma_pool *pool, int mem_flags, dma_addr_t *handle);
void dma_pool_free(struct dma_pool *pool, void *addr, dma_addr_t addr);
8. 創建流式DMA映射
在某些體系結構中,流式映射也可以擁有多個不連續的頁和多個「分散/彙集」緩衝區。創建流式映射時,必須告訴內核數據流動的方向。
DMA_TO_DEVICE
DEVICE_TO_DMA
若是數據被髮送到設備,使用DMA_TO_DEVICE;而若是數據被髮送到CPU,則使用DEVICE_TO_DMA。
DMA_BIDIRECTTONAL
若是數據可雙向移動,則使用該值
DMA_NONE
該符號只是出於調試目的。
當只有一個緩衝區要被傳輸的時候,使用下函數映射它:
dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);
返回值是總線地址,能夠把它傳遞給設備;若是執行錯誤,返回NULL。
當傳輸完畢後,使用下函數刪除映射:
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma-data_direction direction);
使用流式DMA的原則:
一是緩衝區只能用於這樣的傳送,即其傳送方向匹配與映射時給定的方向值;
二是一旦緩衝區被映射,它將屬於設備,不是處理器。直到緩衝區被撤銷映射前,驅動程序不能以任何方式訪問其中的內容。只用當dma_unmap_single函數被調用後,顯示刷新處理器緩存中的數據,驅動程序才能安全訪問其中的內容。
三是在DMA出於活動期間內,不能撤銷對緩衝區的映射,不然會嚴重破壞系統的穩定性。
若是要映射的緩衝區位於設備不能訪問的內存區段(高端內存),怎麼辦?一些體系結構只產生一個錯誤,可是其餘一些系統結構件建立一個回彈緩衝區。回彈緩衝區就是內存中的獨立區域,它可被設備訪問。若是使用DMA_TO_DEVICE標誌映射緩衝區,而且須要使用回彈緩衝區,則在最初緩衝區中的內容做爲映射操做的一部分被拷貝。很明顯,在拷貝後,最初緩衝區內容的改變對設備不可見。一樣DEVICE_TO_DMA回彈緩衝區被dma_unmap_single函數拷貝回最初的緩衝區中,也就是說,直到拷貝操做完成,來自設備的數據纔可用。
有時候,驅動程序須要不通過撤銷映射就訪問流式DMA緩衝區的內容,爲此內核提供了以下調用:
void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_directction direction);
應該在處理器訪問流式DMA緩衝區前調用該函數。一旦調用了該函數,處理器將「擁有」DMA緩衝區,並可根據須要對它進行訪問。而後在設備訪問緩衝區前,應該調用下面的函數將全部權交還給設備:
void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
再次強調,處理器在調用該函數後,不能再訪問DMA緩衝區了。