從我們的直觀感受來說,DMA并不是一個復(fù)雜的東西,要做的事情也很單純直白。因此Linux kernel對它的抽象和實現(xiàn),也應(yīng)該簡潔、易懂才是。不過現(xiàn)實卻不甚樂觀(個人感覺),Linux kernel dmaengine framework的實現(xiàn),真有點晦澀的感覺。為什么會這樣呢?
1.前言
如果一個軟件模塊比較復(fù)雜、晦澀,要么是設(shè)計者的功力不夠,要么是需求使然。當然,我們不敢對Linux kernel的那些大神們有絲毫懷疑和不敬,只能從需求上下功夫了:難道Linux kernel中的driver對DMA的使用上,有一些超出了我們?nèi)粘5恼J知范圍?
要回答這些問題并不難,將dmaengine framework為consumers提供的功能和API梳理一遍就可以了,這就是本文的目的。當然,也可以借助這個過程,加深對DMA的理解,以便在編寫那些需要DMA傳輸?shù)膁river的時候,可以更游刃有余。
2. Slave-DMA API和Async TX API
從方向上來說,DMA傳輸可以分為4類:memory到memory、memory到device、device到memory以及device到device。Linux kernel作為CPU的代理人,從它的視角看,外設(shè)都是slave,因此稱這些有device參與的傳輸(MEM2DEV、DEV2MEM、DEV2DEV)為Slave-DMA傳輸。而另一種memory到memory的傳輸,被稱為Async TX。
為什么強調(diào)這種差別呢?因為Linux為了方便基于DMA的memcpy、memset等操作,在dma engine之上,封裝了一層更為簡潔的API(如下面圖片1所示),這種API就是Async TX API(以async_開頭,例如async_memcpy、async_memset、async_xor等)。
圖片1 DMA Engine API示意圖
最后,因為memory到memory的DMA傳輸有了比較簡潔的API,沒必要直接使用dma engine提供的API,最后就導致dma engine所提供的API就特指為Slave-DMA API(把mem2mem剔除了)。
本文主要介紹dma engine為consumers提供的功能和API,因此就不再涉及Async TX API了(具體可參考本站后續(xù)的文章。
注1:Slave-DMA中的“slave”,指的是參與DMA傳輸?shù)脑O(shè)備。而對應(yīng)的,“master”就是指DMA controller自身。一定要明白“slave”的概念,才能更好的理解kernel dma engine中有關(guān)的術(shù)語和邏輯。
3. dma engine的使用步驟
注2:本文大部分內(nèi)容翻譯自kernel document[1],喜歡讀英語的讀者可以自行參考。
對設(shè)備驅(qū)動的編寫者來說,要基于dma engine提供的Slave-DMA API進行DMA傳輸?shù)脑挘枰缦碌牟僮鞑襟E:
1)申請一個DMA channel。
2)根據(jù)設(shè)備(slave)的特性,配置DMA channel的參數(shù)。
3)要進行DMA傳輸?shù)臅r候,獲取一個用于識別本次傳輸(transaction)的描述符(descriptor)。
4)將本次傳輸(transacTIon)提交給dma engine并啟動傳輸。
5)等待傳輸(transacTIon)結(jié)束。
然后,重復(fù)3~5即可。
上面5個步驟,除了3有點不好理解外,其它的都比較直觀易懂,具體可參考后面的介紹。
3.1 申請DMA channel
任何consumer(文檔[1]中稱作client,也可稱作slave driver,意思都差不多,不再特意區(qū)分)在開始DMA傳輸之前,都要申請一個DMA channel(有關(guān)DMA channel的概念,請參考[2]中的介紹)。
DMA channel(在kernel中由“struct dma_chan”數(shù)據(jù)結(jié)構(gòu)表示)由provider(或者是DMA controller)提供,被consumer(或者client)使用。對consumer來說,不需要關(guān)心該數(shù)據(jù)結(jié)構(gòu)的具體內(nèi)容(我們會在dmaengine provider的介紹中在詳細介紹)。
consumer可以通過如下的API申請DMA channel:
struct dma_chan *dma_request_chan(struct device *dev, const char *name);
該接口會返回綁定在指定設(shè)備(dev)上名稱為name的dma channel。dma engine的provider和consumer可以使用device tree、ACPI或者struct dma_slave_map類型的match table提供這種綁定關(guān)系,具體可參考XXXX章節(jié)的介紹。
最后,申請得到的dma channel可以在不需要使用的時候通過下面的API釋放掉:
void dma_release_channel(struct dma_chan *chan);
3.2 配置DMA channel的參數(shù)
driver申請到一個為自己使用的DMA channel之后,需要根據(jù)自身的實際情況,以及DMA controller的能力,對該channel進行一些配置??膳渲玫膬?nèi)容由struct dma_slave_config數(shù)據(jù)結(jié)構(gòu)表示(具體可參考4.1小節(jié)的介紹)。driver將它們填充到一個struct dma_slave_config變量中后,可以調(diào)用如下API將這些信息告訴給DMA controller:
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
3.3 獲取傳輸描述(tx descriptor)
DMA傳輸屬于異步傳輸,在啟動傳輸之前,slave driver需要將此次傳輸?shù)囊恍┬畔?例如src/dst的buffer、傳輸?shù)姆较虻?提交給dma engine(本質(zhì)上是dma controller driver),dma engine確認okay后,返回一個描述符(由struct dma_async_tx_descriptor抽象)。此后,slave driver就可以以該描述符為單位,控制并跟蹤此次傳輸。
struct dma_async_tx_descriptor數(shù)據(jù)結(jié)構(gòu)可參考4.2小節(jié)的介紹。根據(jù)傳輸模式的不同,slave driver可以使用下面三個API獲取傳輸描述符(具體可參考DocumentaTIon/dmaengine/client.txt[1]中的說明):
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direcTIon direction,
unsigned long flags);
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_data_direction direction);
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);
dmaengine_prep_slave_sg用于在“scatter gather buffers”列表和總線設(shè)備之間進行DMA傳輸,參數(shù)如下:
注3:有關(guān)scatterlist 我們在[3][2]中有提及,后續(xù)會有專門的文章介紹它,這里暫且按下不表。
chan,本次傳輸所使用的dma channel。
sgl,要傳輸?shù)摹皊catter gather buffers”數(shù)組的地址;
sg_len,“scatter gather buffers”數(shù)組的長度。
direction,數(shù)據(jù)傳輸?shù)姆较颍唧w可參考enum dma_data_direction (include/linux/dma-direction.h)的定義。
flags,可用于向dma controller driver傳遞一些額外的信息,包括(具體可參考enum dma_ctrl_flags中以DMA_PREP_開頭的定義):
DMA_PREP_INTERRUPT,告訴DMA controller driver,本次傳輸完成后,產(chǎn)生一個中斷,并調(diào)用client提供的回調(diào)函數(shù)(可在該函數(shù)返回后,通過設(shè)置struct dma_async_tx_descriptor指針中的相關(guān)字段,提供回調(diào)函數(shù),具體可參考4.2小節(jié)的介紹);
DMA_PREP_FENCE,告訴DMA controller driver,后續(xù)的傳輸,依賴本次傳輸?shù)慕Y(jié)果(這樣controller driver就會小心的組織多個dma傳輸之間的順序);
DMA_PREP_PQ_DISABLE_P、DMA_PREP_PQ_DISABLE_Q、DMA_PREP_CONTINUE,PQ有關(guān)的操作,TODO。
dmaengine_prep_dma_cyclic常用于音頻等場景中,在進行一定長度的dma傳輸(buf_addr&buf_len)的過程中,每傳輸一定的byte(period_len),就會調(diào)用一次傳輸完成的回調(diào)函數(shù),參數(shù)包括:
chan,本次傳輸所使用的dma channel。
buf_addr、buf_len,傳輸?shù)腷uffer地址和長度。
period_len,每隔多久(單位為byte)調(diào)用一次回調(diào)函數(shù)。需要注意的是,buf_len應(yīng)該是period_len的整數(shù)倍。
direction,數(shù)據(jù)傳輸?shù)姆较颉?
dmaengine_prep_interleaved_dma可進行不連續(xù)的、交叉的DMA傳輸,通常用在圖像處理、顯示等場景中,具體可參考struct dma_interleaved_template結(jié)構(gòu)的定義和解釋(這里不再詳細介紹,需要用到的時候,再去學習也okay)。
3.4 啟動傳輸
通過3.3章節(jié)介紹的API獲取傳輸描述符之后,client driver可以通過dmaengine_submit接口將該描述符放到傳輸隊列上,然后調(diào)用dma_async_issue_pending接口,啟動傳輸。
dmaengine_submit的原型如下:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
參數(shù)為傳輸描述符指針,返回一個唯一識別該描述符的cookie,用于后續(xù)的跟蹤、監(jiān)控。
dma_async_issue_pending的原型如下:
void dma_async_issue_pending(struct dma_chan *chan);
參數(shù)為dma channel,無返回值。
注4:由上面兩個API的特征可知,kernel dma engine鼓勵client driver一次提交多個傳輸,然后由kernel(或者dma controller driver)統(tǒng)一完成這些傳輸。
3.5 等待傳輸結(jié)束
傳輸請求被提交之后,client driver可以通過回調(diào)函數(shù)獲取傳輸完成的消息,當然,也可以通過dma_async_is_tx_complete等API,測試傳輸是否完成。不再詳細說明了。
最后,如果等不及了,也可以使用dmaengine_pause、dmaengine_resume、dmaengine_terminate_xxx等API,暫停、終止傳輸,具體請參考kernel document[1]以及source code。
4. 重要數(shù)據(jù)結(jié)構(gòu)說明
4.1 struct dma_slave_config
中包含了完成一次DMA傳輸所需要的所有可能的參數(shù),其定義如下:
/* include/linux/dmaengine.h */
struct dma_slave_config {
enum dma_transfer_direction direction;
phys_addr_t src_addr;
phys_addr_t dst_addr;
enum dma_slave_buswidth src_addr_width;
enum dma_slave_buswidth dst_addr_width;
u32 src_maxburst;
u32 dst_maxburst;
bool device_fc;
unsigned int slave_id;
};
direction,指明傳輸?shù)姆较?,包?具體可參考enum dma_transfer_direction的定義和注釋):
DMA_MEM_TO_MEM,memory到memory的傳輸;
DMA_MEM_TO_DEV,memory到設(shè)備的傳輸;
DMA_DEV_TO_MEM,設(shè)備到memory的傳輸;
DMA_DEV_TO_DEV,設(shè)備到設(shè)備的傳輸。
注5:controller不一定支持所有的DMA傳輸方向,具體要看provider的實現(xiàn)。
注6:參考第2章的介紹,MEM to MEM的傳輸,一般不會直接使用dma engine提供的API。
src_addr,傳輸方向是dev2mem或者dev2dev時,讀取數(shù)據(jù)的位置(通常是固定的FIFO地址)。對mem2dev類型的channel,不需配置該參數(shù)(每次傳輸?shù)臅r候會指定);
dst_addr,傳輸方向是mem2dev或者dev2dev時,寫入數(shù)據(jù)的位置(通常是固定的FIFO地址)。對dev2mem類型的channel,不需配置該參數(shù)(每次傳輸?shù)臅r候會指定);
src_addr_width、dst_addr_width,src/dst地址的寬度,包括1、2、3、4、8、16、32、64(bytes)等(具體可參考enum dma_slave_buswidth 的定義)。
src_maxburst、dst_maxburst,src/dst最大可傳輸?shù)腷urst size(可參考[2]中有關(guān)burst size的介紹),單位是src_addr_width/dst_addr_width(注意,不是byte)。
device_fc,當外設(shè)是Flow Controller(流控制器)的時候,需要將該字段設(shè)置為true。CPU中有關(guān)DMA和外部設(shè)備之間連接方式的設(shè)計中,決定DMA傳輸是否結(jié)束的模塊,稱作flow controller,DMA controller或者外部設(shè)備,都可以作為flow controller,具體要看外設(shè)和DMA controller的設(shè)計原理、信號連接方式等,不在詳細說明(感興趣的同學可參考[4]中的介紹)。
slave_id,外部設(shè)備通過slave_id告訴dma controller自己是誰(一般和某個request line對應(yīng))。很多dma controller并不區(qū)分slave,只要給它src、dst、len等信息,它就可以進行傳輸,因此slave_id可以忽略。而有些controller,必須清晰地知道此次傳輸?shù)膶ο笫悄膫€外設(shè),就必須要提供slave_id了(至于怎么提供,可dma controller的硬件以及驅(qū)動有關(guān),要具體場景具體對待)。
4.2 struct dma_async_tx_descriptor
傳輸描述符用于描述一次DMA傳輸(類似于一個文件句柄)。client driver將自己的傳輸請求通過3.3中介紹的API提交給dma controller driver后,controller driver會返回給client driver一個描述符。
client driver獲取描述符后,可以以它為單位,進行后續(xù)的操作(啟動傳輸、等待傳輸完成、等等)。也可以將自己的回調(diào)函數(shù)通過描述符提供給controller driver。
傳輸描述符的定義如下:
struct dma_async_tx_descriptor {
dma_cookie_t cookie;
enum dma_ctrl_flags flags; /* not a ‘long’ to pack with cookie */
dma_addr_t phys;
struct dma_chan *chan;
dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
int (*desc_free)(struct dma_async_tx_descriptor *tx);
dma_async_tx_callback callback;
void *callback_param;
struct dmaengine_unmap_data *unmap;
#ifdef CONFIG_ASYNC_TX_ENABLE_CHANNEL_SWITCH
struct dma_async_tx_descriptor *next;
struct dma_async_tx_descriptor *parent;
spinlock_t lock;
#endif
};
cookie,一個整型數(shù),用于追蹤本次傳輸。一般情況下,dma controller driver會在內(nèi)部維護一個遞增的number,每當client獲取傳輸描述的時候(參考3.3中的介紹),都會將該number賦予cookie,然后加一。
注7:有關(guān)cookie的使用場景,我們會在后續(xù)的文章中再詳細介紹。
flags, DMA_CTRL_開頭的標記,包括:
DMA_CTRL_REUSE,表明這個描述符可以被重復(fù)使用,直到它被清除或者釋放;
DMA_CTRL_ACK,如果該flag為0,表明暫時不能被重復(fù)使用。
phys,該描述符的物理地址??不太懂!
chan,對應(yīng)的dma channel。
tx_submit,controller driver提供的回調(diào)函數(shù),用于把改描述符提交到待傳輸列表。通常由dma engine調(diào)用,client driver不會直接和該接口打交道。
desc_free,用于釋放該描述符的回調(diào)函數(shù),由controller driver提供,dma engine調(diào)用,client driver不會直接和該接口打交道。
callback、callback_param,傳輸完成的回調(diào)函數(shù)(及其參數(shù)),由client driver提供。
后面其它參數(shù),client driver不需要關(guān)心,暫不描述了。
5. 參考文檔
[1] Documentation/dmaengine/client.txt
[2] Linux DMA Engine framework(1)_概述
[3] Linux MMC framework(2)_host controller driver
[4] https://forums.xilinx.com/xlnx/attachments/xlnx/ELINUX/10658/1/drivers-session4-dma-4public.pdf
以上就是今天的介紹啦,你了解了嗎?