在Linux 4.0下進行dmaengine的編程主要分爲兩部分,DMA Engine控制器編程和DMA Engine API編程。
DMA Engine API編程
slave DMA用法包括以下的步驟:
1. 分配一個DMA slave通道;
2. 設置slave和controller特定的參數;
3. 獲取一個傳輸描述符;
4. 提交傳輸描述符;
5. 發起等待的請求並等待回調通知。
下面是以上每一步的詳細說明。
1. 分配一個DMA slave通道
在slave DMA上下文通道的分配略有不同,客戶端驅動通常需要一個通道,這個通道源自特定的DMA控制器,在某些情況甚至需要一個特定的通道。請求通道的API是channel dma_request_channel()。
其接口如下:
struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
dma_filter_fn filter_fn,
void *filter_param);
1
2
3
其中dma_filter_fn接口定義如下:
typedef bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param);
1
filter_fn是可選的,但是對於slave和cyclic通道我們強烈推薦使用,因爲它們需要獲取一個特定的DMA通道。
當filter_fn參數爲空,dma_request_channel()函數簡單地返回第一個滿足mask參數的通道。
否則,filter_fn函數將會對每個空閒的通道調用一次,同樣這些通道也是要滿足mask參數。當filter_fn返回true的時候說明期望的通道已經找到。
通過這個API分配的通道在dma_release_channel()函數調用前對於其他調用者是互斥的。
2. 設置slave和controller特定的參數
這一步通常是傳遞一些特定的信息到DMA驅動。大多數slave DMA使用到的通用信息都在結構體dma_slave_config中。它允許客戶端對外設指定DMA的方向、DMA地址、總線寬度、DMA突發長度等等。
如果一些DMA控制器有更多要發送的參數,那它們應該試圖把結構體dma_slave_config內嵌到控制器特定的結構體中。對於客戶端來說將有更多的靈活性來傳遞更多需要的參數。
接口如下:
int dmaengine_slave_config(struct dma_chan *chan,
struct dma_slave_config *config)
1
2
對於dma_slave_config結構體成員的詳解可在dmaengine.h中查看。
3. 獲取一個傳輸描述符
DMA-engine支持多種slave傳輸模式:
- slave_sg:DMA一列聚散buffers from/to外設;
- dma_cyclic:實現一個循環的DMA操作 from/to外設,直到操作被停止;
- interleaved_dma:對於Slave客戶端和M2M客戶端都很常見。這種情況下驅動已知Slave設備FIFO的地址。可對dma_interleaved_template結構體的成員設置適當的值來表示多種類型的操作;
這個傳輸API的返回值就是一個傳輸描述符。
其接口如下:
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);
1
2
3
4
5
6
7
8
9
10
11
12
在調用dmaengine_prep_slave_sg()函數前外設驅動必須已經映射scatterlist,該映射必須保持直到DMA操作完成。
一般的步驟如下:
nr_sg = dma_map_sg(chan->device->dev, sgl, sg_len);
if (nr_sg == 0)
/* error */
desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);
1
2
3
4
5
一旦傳輸描述符獲取成功,回調信息加入後描述符被提交。
注意:
對於Slave DMA,在回調函數被調用前接下來的傳輸描述符可能不能提交,因此slave DMA回調函數允許準備並提交一個新的傳輸描述符;
對於cyclic
DMA,回調函數可被用來停止DMA操作,通過在回調函數內部調用dmaengine_terminate_all()函數完成;
因此,在調用回調函數前DMA engine驅動解鎖是非常重要的,因爲它會導致死鎖。
回調函數通常通過DMA engines的底半部tasklet方式調用,而不是直接通過中斷頂半部調用。
4. 提交傳輸描述符
一旦傳輸描述符準備好並且回調函數也加入後,下一步就是把傳輸描述符加入到DMA engine驅動的等待隊列。
其接口如下:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
1
返回值是一個cookie,主要用來檢查DMA engine活動的狀態過程,可通過其他的DMA engine API來實現。
dmaengine_submit()函數僅僅提交描述符到DMA engine的等待隊列,它不會啓動DMA操作。
5. 發起等待的請求並等待回調通知
等待隊列裏面的傳輸描述符可通過調用issue_pending API激活。此時如果通道是空閒的,等待隊列中的第一個傳輸描述符將會啓動DMA操作。
每次DMA操作完成後,等待隊列中的下一個描述符將會啓動DMA操作並且一個tasklet將會被處罰。如果我們在前面設定了客戶端驅動的回調通知函數,那麼tasklet將會調用這個函數。
其接口如下:
void dma_async_issue_pending(struct dma_chan *chan);
1
6. 其他API接口
dma engine API 說明
int dmaengine_terminate_all(struct dma_chan *chan) 指定DMA通道的所有活動都會被停止,DMA FIFO裏面尚未傳輸完成的數據可能會丟失。未完成的DMA傳輸不會調用任何回調函數
int dmaengine_pause(struct dma_chan *chan) 指定DMA通道的活動將會被暫停,不會造成數據的丟失
int dmaengine_resume(struct dma_chan *chan) 指定DMA通道的活動將會被恢復
enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used) 這個函數用來檢查指定DMA通道的狀態。可在linux/dmaengine.h查看這個API更詳細的用法,這個函數可與dma_async_is_complete()函數一併使用,從dmaengine_submit()函數返回的cookie可用來檢查指定DMA傳輸是否完成。注意:不是所有的DMA engine驅動能在一個正在運行的DMA通道返回準確的信息。因此我們推薦用戶在使用這個API前先暫停或者停止指定DMA通道。
DMA Engine controller編程
和其他的內核框架類似,dmaengine的註冊依賴於填充一個結構並把它註冊到框架中。對於dmaengine,這個結構是dma_device。
1. 分配dma_device結構
2. 初始化dma_device結構
dma_device結構的成員說明如下:
dma_device結構體成員 說明
channel 使用INIT_LIST_HEAD宏初始化
src_addr_widths 包含支持源傳輸寬度的bitmask
dst_addr_widths 包含支持目的傳輸寬度的bitmask
directions 包含一個支持從設備方向的bitmask
residue_granularity 使用dma_set_residue設置
Descriptor 你的設備不支持任何種類的residue報告。框架僅僅知道特定傳輸描述符完成
Segment 你的設備能報告哪個數據塊完成傳輸
Burst 你的設備能報告哪一次突發傳輸已經完成
dev 保存指向device的指針,這個指針和你當前的驅動實例相關
3. 設置設備支持的傳輸類型
dma_device結構有一個域cap_mask,這個域保存支持的傳輸類型,可使用dma_cap_set()函數改變,支持的傳輸類型定義在dma_transaction_type中,它位於include/linux/dmaengine.h頭文件中:
DMA傳輸類型 說明
DMA_MEMCPY 內存到內存的拷貝
DMA_SG 設備支持內存到內存的分散/聚合傳輸
DMA_INTERLEAVE 內存到內存的交錯傳輸;交錯傳輸的定義:傳輸數據從一個非連續的buffer到一個非連續的buffer,和DMA_SLAVE相反。通常用在2D內容的傳輸,在那種場景下你想直接傳輸一部分未經壓縮的數據到顯示部分
DMA_XOR 設備能在內存區域執行XOR操作,用來加速對XOR敏感的任務,例如RAID5
DMA_XOR_VAL 使用XOR進行內存Buffer的奇偶校驗
DMA_PQ 內存到內存的P+Q 計算
DMA_PQ_VAL 設備能在內存buffer執行奇偶校驗使用RAID6 P+Q算法
DMA_ INTERRUPT 設備能觸發一個虛擬的傳輸,它將會產生一箇中斷
DMA_SLAVE 設備能處理設備到內存的傳輸,包括分散/聚合傳輸。在mem2mem情況下我們有兩種傳輸類型:一種是單數據塊拷貝,一種是數據塊的集合拷貝。這裏我們僅僅使用單傳輸類型來處理以上兩種情況。如果你想傳輸一個單獨的連續內存buffer,只需要簡單建立一個scatter鏈表,這個鏈表僅有一個項
DMA_CYCLIC 設備能處理循環傳輸;循環傳輸的定義:數據塊集合能循環遍歷它自己,並且最後的項指向第一項。通常用在音頻傳輸,操作一個環形buffer,你需要做的僅僅是往這個Buffer填充音頻數據
DMA_PRIVATE 不通過dma_request_channel函數請求的通道進行異步發送,使用隨機的信道進行傳輸
DMA_ASYNC_TX 不必由設備設置,如何需要將會由框架設置
以上的類型都將會影響源地址和目的地址如何隨着時間如何改變。
4. 設備的操作
爲了完成實際的邏輯,dma_device結構需要有一些函數指針,現在我們開始討論有哪些操作我們可以實現的。我們必須填充的一些函數基於我們選擇的傳輸類型。
DMA設備操作函數 說明
device_alloc_chan_resources 當client驅動調用dma_request_channel的時候將會調用device_alloc_chan_resources,負責分配通道需要的資源
device_free_chan_resources 當client驅動調用dma_release_channel的時候將會調用device_free_chan_resources,負責釋放通道需要的資源
device_prep_dma_* 爲DMA傳輸準備傳輸描述符
device_issue_pending 從pending queue中取走第一個傳輸描述符並啓動傳輸。當傳輸完成後將會移到列表中的下一個傳輸描述符。這個函數可以在中斷上下文中使用
device_tx_status 報告一個通道還有多少字節數據要傳輸
device_config 使用給定的參數重新配置通道
device_pause 暫停通道的傳輸
device_resume 恢復通道的傳輸
device_terminate_all 停止通道中所有的(包括pending的和正在進行的傳輸)傳輸
5. 註冊DMA設備
int dma_async_device_register(struct dma_device *device)
利用這個函數把填充好的dma_device結構實體註冊到內核中。
註冊DMA控制器到DT DMA helpers
int of_dma_controller_register(struct device_node *np,
struct dma_chan *(*of_dma_xlate)
(struct of_phandle_args *, struct of_dma *),
void *data);
- @np: device node of DMA controller
- @of_dma_xlate: translation function which converts a phandle
- arguments list into a dma_chan structure
- @data pointer to controller specific data to be used by
- translation function
————————————————
版權聲明:本文爲CSDN博主「were0415」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/were0415/article/details/54095899