DMA簡介
DMA 是一種采用硬件實現存儲器與存儲器之間或存儲器與外設之間直接進行高速數據傳輸的技術,傳輸過程無需 CPU 參與(但是CPU需要提前配置傳輸規則),可以大大減輕 CPU 的負擔。
DMA 存儲傳輸的過程如下:
- CPU 向 DMA 控制器配置傳輸規則,如源地址、目的地址、長度、地址是否自增等
- DMA 控制器根據配置進行數據搬移,此時 CPU 資源被釋放
- DMA 數據搬移完成后向 CPU 發送中斷
AXI DMA簡介
AXI Direct Memory Access(AXI DMA)是 ZYNQ PL端的一個 IP 核,它提供了 CPU 內存(AXI4 內存映射)和 PL 端 AXI4-Stream 之間高速數據傳輸的功能,如下是 AXI DMA 的框圖:
- AXI4 Memory Map Read:用于從 DDR3 中讀取數據
- AXI4 Memory Map Write:用于向 DDR3 中寫入數據
- AXI4 Stream Master(MM2S):接口用于向外設寫入數據
- AXI4-Stream Slave(S2MM):接口用于從外設讀取數據
- AXI Control stream (MM2S):是控制流接口,該接口的主要作用是 AXI DMA 對目標設備寫入數據時進行節流
- AXI Stream (S2MM):是一個狀態流接口,主要作用是將目標設備的狀態傳輸到 AXI DMA 中的用戶應用程序數據字段中
- AXI Memory Map Write/Read:接口的主要作用是在 S/G 模式下 AXI DMA 與外設進行數據的讀寫
ADX DMA工作模式
AXI DMA 提供 3 種模式,分別是 Direct Register 模式、Scatter/Gather 模式和 Cyclic DMA(循環 DMA)模式,其中 Direct Register 模式最常用,Scatter/Gather 模式和 Cyclic DMA 模式使用的相對較少
- Direct Register DMA 模式 :
Direct Register DMA 模式也就是 Simple DMA(簡單 DMA)。Direct Register 模式提供了一種配置,用于在 MM2S 和 S2MM 通道上執行簡單的 DMA 傳輸,這種傳輸只需要少量的 FPGA 資源。Simple DMA(簡單 DMA)允許應用程序在 DMA 和 Device 之間定義單個事務。它有兩個通道:一個從 DMA 到 Device,另一個從 Device 到 DMA。這里有個地方需要大家注意下,在編寫 Simple DMA(簡單 DMA)代碼時必須設置緩沖區地址和長度字段以啟動相應通道中的傳輸。 - Scatter/Gather DMA 模式 :
SGDMA(Scatter/Gather DMA)模式允許在單個 DMA 事務中將數據傳輸到多個存儲區域(相當于將多個 Simple DMA 請求通過事務列表鏈接在一起)。SGDMA 允許應用程序在內存中定義事務列表,硬件將在應用程序沒有進一步干預的情況下處理這些事務,在此期間應用程序可以繼續添加更多事務以保持硬件工作,還可以通過輪詢或中斷來檢查事務是否完成。 - Cyclic DMA 模式:
Cyclic DMA(循環 DMA)模式是在 Scatter/Gather 模式下的一種獨特工作方式,在 Multichannel Mode 下不可用。正常情況下的 Scatter/Gather 模式在遇到 Tail BD(最后一個事務列表項)就應該結束當前的傳輸,但是如果使能了 Cyclic 模式的話,在遇到 Tail BD時會忽略 completed 位,并且回到 First BD(第一個事務列表項),這一過程會一直持續直到遇到錯誤或者人為中止。
VIVADO 工程搭建
使用 Direct Register DMA 模式搭建一個 DMA 回環測試的 VIVADO 工程,Vivado 工程框圖如下:
-
添加 ZYNQ IP 核,并進行配置,使能flash接口、AXI GP0、AXI HP0、ENET、SD、DDR、PL中斷、PL時鐘等,其中AXI GP0、AXI HP0、PL 中斷、PL 時鐘、PL 中斷是為了連接 PL 端的AXI DMA IP 核。
AXI GP0、AXI HP0配置如下:
PL 時鐘配置如下:
PL中斷配置如下:
-
添加 DMA IP 核
-
配置 DMA IP 核
-
添加 FIFO IP 核心
-
連接DMA IP和FIFO IP,將 DMA 的 MM2S 和 S2MM 接口通過一個 AXIS DATA FIFO 構成回環。
-
點擊“Run Block Automation”和“Run Connection Automation”進行自動連線(可能會重復操作多次),在連線過程中會自動添加一些 IP 核。
-
添加 concat IP 核和 const IP 核
添加 concat IP:
添加 const IP:
-
配置 concat IP 核和 const IP 核
配置 concat IP:
配置 const IP:
-
鏈接中斷信號
-
生成代碼,然后編譯并生成bit文件,最后導出 xsa 文件,導出時必須包含bit流
利用 Petalinux 構建根文件系統和 BOOT.BIN
此部分內容參考04 搭建linux驅動開發環境中的利用 Petalinux 構建根文件系統和 BOOT.BIN部分。
獲取 Linux 設備樹
此部分內容04 搭建linux驅動開發環境中的獲取Linux設備樹文件部分
可以發現在 pl.dtsi 文件中已經生成了AXI DMA IP核的設備樹節點
編譯 Linux 內核
此部分內容04 搭建linux驅動開發環境中的編譯Linux內核部分。
Linux 中使用 DMA的要點
DMA驅動框架
DMA驅動框架可分為三層:
DMA slave驅動:使用DMA設備的驅動程序,如SPI控制器驅動、UART控制器驅動等
DMA核心:提供統一的編程接口,管理DMA slave驅動和DMA控制器驅動
DMA控制器驅動:驅動DMA控制器
在Linux系統中DMA核心已經包含在系統中,DMA控制器驅動一般有芯片廠家提供,普通開發人員一般只要掌握DMA slave驅動開發即可
DMA 區域
部分SOC的DMA只能訪問特定的一段內存空間,而非整個內存空間,所以在使用kmalloc()、__get_free_pages()等類似函數申請可能用于DMA緩沖區的內存時需要在申請標志中增加GFP_DMA標志,此外系統也通過了一些快捷接口,如__get_dma_pages()、dma_mem_alloc()等。
提示
大多數嵌入式SOC其DMA都可以訪問整個常規內存空間。
虛擬地址、物理地址、總線地址
虛擬地址:CPU所使用的地址就是虛擬地址(站在CPU角度來看)。
物理地址:站在MMU角度來看,即DRAM空間向MMU呈現出來的地址,它可通過MMU轉換為虛擬地址。
總線地址:站在設備的角度來看,即DRAM空間向設備呈現出來的地址,部分SOC帶有IOMMU,還可以對總線地址進行映射后在呈現給設備。
DMA地址掩碼
部分SOC的DMA只能訪問特定的一段內存空間(如系統總線32bit地址,而DMA只能訪問低24bit地址,在這種情況下,外設在發起DMA操作的時候就只能訪問16M以下的系統物理內存),因此需要設置DMA mask,以指示DMA能訪問的地址空間,如DMA只能訪問低24位地址則需要執行dma_set_mask(dev, DMA_BIT_MASK(24))和dev.coherent_dma_mask = DMA_BIT_MASK(24)操作,dma_set_mask(dev, DMA_BIT_MASK(24))設置的是DMA流式映射的尋址范圍,dev.coherent_dma_mask = DMA_BIT_MASK(24)設置一致性 DMA 緩沖區的尋址范圍,或者使用int dma_set_mask_and_coherent(dev, DMA_BIT_MASK(24))一次完成這兩部操作。
一致性DMA緩沖區
一致性DMA緩沖區即DMA和CPU可以一致訪問的緩沖區,不需要考慮cache的影響,一般通過關閉cache實現(部分SOC的DMA也支持從通過cache訪問內存),一致性緩沖區分別使用如下函數申請或釋放:
//分配一致性緩沖區
void *dmam_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t gfp)
void *dma_alloc_wc(struct device *dev, size_t size, dma_addr_t *dma_addr, gfp_t gfp)
//釋放一致性緩沖區
void dmam_free_coherent(struct device *dev, size_t size, void *vaddr, dma_addr_t dma_handle)
void dma_free_wc(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_addr)
single類型流式映射
有時候需要通過DMA傳輸的內存并非由DMA分配,針對這種情況可以采用流式映射,若來自其他驅動的是一個連續的內存區域可以使用如下函數進行映射和取消映射:
//映射
dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size, enum dma_data_direction dir)
//取消映射
void dma_unmap_single((struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir)
被映射后的內存在取消映射前原則上CPU不能訪問,若CPU實在需要訪問需要先申請擁有權,訪問完后在釋放擁有權:
//申請CPU擁有權
void dma_sync_single_for_cpu(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir)
//釋放CPU擁有權
void dma_sync_single_for_device(struct device *dev, dma_addr_t addr, size_t size, enum dma_data_direction dir)
SG類型流式映射
若來自其他驅動的內存區域在物理上是不連續的則需要進行分段映射,可以使用如下函數進行映射和取消映射:
//映射
#define dma_map_sg(d, s, n, r) dma_map_sg_attrs(d, s, n, r, 0)
int dma_map_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, unsigned long attrs)
//取消映射
#define dma_unmap_sg(d, s, n, r) dma_unmap_sg_attrs(d, s, n, r, 0)
void dma_unmap_sg_attrs(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, unsigned long attrs)
同樣對于被映射后的內存在取消映射前原則上CPU不能訪問,若CPU實在需要訪問需要先申請擁有權,訪問完后在釋放擁有權:
//申請CPU擁有權
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction dir)
//釋放CPU擁有權
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nelems, enum dma_data_direction dir)
此外對于struct scatterlist還有如下常用宏和函數
//獲取總線地址成員,可用于設置或讀取總線地址
#define sg_dma_address(sg)
//獲取長度成員,可用于設置或讀取長度
#define sg_dma_len(sg)
//設置buf,它會獲取buf的頁地址和偏移,并將頁地址、偏移、長度設置到struct scatterlist中
void sg_set_buf(struct scatterlist *sg, const void *buf, unsigned int buflen)
//獲取buf的物理地址,這里的dma_addr_t返回的是物理地址,而非總線地址
dma_addr_t sg_phys(struct scatterlist *sg)
//獲取buf的虛擬地址
void *sg_virt(struct scatterlist *sg)
//初始化SG列表,此函數不會設置buf
void sg_init_table(struct scatterlist *, unsigned int);
//初始化單個sg,并設置buf
void sg_init_one(struct scatterlist *, const void *, unsigned int);
DMA統一操作接口
DMA驅動框架提供了一套標準的DMA操作接口,常用的如下:
//申請DMA通道
struct dma_chan *dma_request_slave_channel(struct device *dev, const char *name)
struct dma_chan *dma_request_chan(struct device *dev, const char *name)
//釋放DMA通道
void dma_release_channel(struct dma_chan *chan)
//配置DMA通道
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
//基于總線地址創建一個描述符
struct dma_async_tx_descriptor *dmaengine_prep_slave_single(struct dma_chan *chan, dma_addr_t buf, size_t len, enum dma_transfer_direction dir, unsigned long flags)
//基于struct scatterlist創建一個描述符
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_transfer_direction dir, 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_transfer_direction dir, unsigned long flags)
//創建一個用于設置內存值的描述符
struct dma_async_tx_descriptor *dmaengine_prep_dma_memset(struct dma_chan *chan, dma_addr_t dest, int value, size_t len, unsigned long flags)
//創建一個用于內存拷貝的描述符
struct dma_async_tx_descriptor *dmaengine_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src, size_t len, unsigned long flags)
//提交描述符,在提交描述符前一般還需要設置描述符的callback和callback_param參數,以便描述符傳輸完后進行通知和獲取處理結果,此外該函數的返回值還應采用dma_submit_error函數檢查是否出差
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
//啟動提交的描述符,開始進行傳輸
void dma_async_issue_pending(struct dma_chan *chan)
//獲取傳輸結果
enum dma_status dma_async_is_tx_complete(struct dma_chan *chan, dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)
//struct dma_slave_config結構體
struct dma_slave_config {//傳輸的方向,DMA_MEM_TO_MEM:memory到memory的傳輸, DMA_MEM_TO_DEV:memory到設備的傳輸,DMA_DEV_TO_MEM:設備到memory的傳輸,DMA_DEV_TO_DEV:設備到設備的傳輸enum dma_transfer_direction direction;//傳輸方向是DMA_DEV_TO_MEM或DMA_DEV_TO_DEV時,讀取數據的位置phys_addr_t src_addr;//傳輸方向是DMA_MEM_TO_DEV或DMA_DEV_TO_DEV時,寫入數據的位置phys_addr_t dst_addr;//src地址的寬度,包括1、2、3、4、8、16、32、64(bytes)等enum dma_slave_buswidth src_addr_width;//dst地址的寬度,包括1、2、3、4、8、16、32、64(bytes)等enum dma_slave_buswidth dst_addr_width;//src最大可傳輸的突發長度u32 src_maxburst;//dst最大可傳輸的突發長度u32 dst_maxburst;//當外設是Flow Controller(流控制器)的時候,需要將該字段設置為truebool device_fc;//外部設備通過slave_id告訴dma controller自己是誰,很多dma controller不區分slave,只要給它src、dst、len等信息,它就可以進行傳輸,因此slave_id可以忽略,有些dma controller必須清晰地知道此次傳輸的對象是哪個外設,就必須要提供slave_idunsigned int slave_id;
};
DMA 從設備驅動設備樹
使用DMA設備時只需要在設備樹節點中增加如下屬性即可:
//引用DMA節點,并傳入參數指定使用哪一個通道(參數的具體值和個數依據DMA驅動決定)
dmas = <&axi_dma_0 0&axi_dma_0 1>;
//dma名稱,與上面的dmas一一對應,申請dma通道時就是dma-names作為參數
dma-names ="axidma0", "axidma1";
編寫AXI DMA回環測試驅動
DMA 驅動編寫流程
- 申請DMA通道
- 配置DMA通道(部分專用DMA不需要進行配置)
- 分配DMA內存(對來自其他驅動模塊的內存則進行映射操作)
- 創建描述符,并綁定傳輸完成回調函數
- 提交描述符
- 啟動DMA傳輸
- 等待傳輸完成
- 檢查傳輸結果
- 釋放前面分配的DMA內存
- 釋放DMA通道
設備樹編寫
在system-user.dtsi增的根節點中增加如下節點:
axi_dma_test0: dma_test@0{compatible = "axi_dma_test"; /* 用于設備樹與驅動進行匹配 */status = "okay"; /* 狀態 */dmas = <&axi_dma_0 0 /* mm2s-channel*/&axi_dma_0 1>; /* s2mm-channel */dma-names ="axidma0", "axidma1"; /* DMA名稱,與dmas對應 */};
驅動代碼編寫
此驅動采用內核線程進行DMA回環測試,DMA相關操作均放在內核線程中實現(申請和釋放dma通道除外)。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/kthread.h>
#include <linux/idr.h>
#include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>#define DMA_TEST_TASK_MAX 8#define DMA_TEST_BUFFER_COUNT 1 //一次傳輸測試的SG數量,vavido中未使能描述符模式,所以只能是1
#define DMA_TEST_BUFFER_SIZE 512 //每個SG緩沖區大小,不能超過vivado中用于回環的AXI DATA FIFO的大小#define DMA_TEST_COUNT 10 //測試次數struct dma_test_handle {struct task_struct *kthread;uint32_t number;struct dma_chan *mm2s_chan;struct dma_chan *s2mm_chan;uint32_t thread_done;
};static DEFINE_IDA(task_number_ida);static void mm2s_chan_callback(void *completion)
{complete((struct completion*)completion);
}static void s2mm_chan_callback(void *completion)
{complete((struct completion*)completion);
}static int dma_test_thread(void *arg)
{int loop_count;int buffer_index;int test_count;enum dma_status status;uint8_t *ptr_mm2s[DMA_TEST_BUFFER_COUNT];uint8_t *ptr_s2mm[DMA_TEST_BUFFER_COUNT];struct scatterlist mm2s_sg[DMA_TEST_BUFFER_COUNT];struct scatterlist s2mm_sg[DMA_TEST_BUFFER_COUNT];struct dma_async_tx_descriptor *mm2s_des;struct dma_async_tx_descriptor *s2mm_des;struct completion mm2s_cmp;struct completion s2mm_cmp;dma_cookie_t mm2s_cookie;dma_cookie_t s2mm_cookie;struct dma_test_handle *dma_test_handle = (struct dma_test_handle*)arg;printk("start axi_dma_test, task number = %d\r\n", dma_test_handle->number);//分配源內存,mm2smemset(ptr_mm2s, 0, sizeof(ptr_mm2s));for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){ptr_mm2s[loop_count] = kmalloc(DMA_TEST_BUFFER_SIZE, GFP_KERNEL|GFP_DMA);if (!ptr_mm2s[loop_count])goto MM2S_ALLOC_ERROR;}//分配目的內存,s2mmmemset(ptr_s2mm, 0, sizeof(ptr_s2mm));for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){ptr_s2mm[loop_count] = kmalloc(DMA_TEST_BUFFER_SIZE, GFP_KERNEL|GFP_DMA);if (!ptr_s2mm[loop_count])goto S2MM_ALLOC_ERROR;}//初始化完成量init_completion(&mm2s_cmp);init_completion(&s2mm_cmp);test_count = 0;while(!kthread_should_stop() && (test_count < DMA_TEST_COUNT)){//填充待發送的數據for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){for(buffer_index = 0; buffer_index < DMA_TEST_BUFFER_SIZE; buffer_index++){ptr_mm2s[loop_count][buffer_index] = (uint8_t)(loop_count + buffer_index + test_count);}}//復位目的數據for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){memset(ptr_s2mm[loop_count], 0, DMA_TEST_BUFFER_SIZE);}//初始化分散聚集映射描述符sg_init_table(mm2s_sg, DMA_TEST_BUFFER_COUNT);sg_init_table(s2mm_sg, DMA_TEST_BUFFER_COUNT);for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){sg_set_buf(&mm2s_sg[loop_count], ptr_mm2s[loop_count], DMA_TEST_BUFFER_SIZE);sg_set_buf(&s2mm_sg[loop_count], ptr_s2mm[loop_count], DMA_TEST_BUFFER_SIZE);}//進行分散聚集映射if(dma_map_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE) == 0){printk("map mm2s_sg faled\r\n");break;}if(dma_map_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE) == 0){dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("map s2mm_sg faled\r\n");break;}//創建DMA傳輸描述符mm2s_des = dmaengine_prep_slave_sg(dma_test_handle->mm2s_chan, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_MEM_TO_DEV, DMA_CTRL_ACK | DMA_PREP_INTERRUPT);if(!mm2s_des){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("create mm2s_des faled\r\n");break;}s2mm_des = dmaengine_prep_slave_sg(dma_test_handle->s2mm_chan, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_DEV_TO_MEM, DMA_CTRL_ACK | DMA_PREP_INTERRUPT);if(!s2mm_des){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("create s2mm_des faled\r\n");break;}//綁定傳輸完成回調函數reinit_completion(&mm2s_cmp);mm2s_des->callback = mm2s_chan_callback;mm2s_des->callback_param = &mm2s_cmp;reinit_completion(&s2mm_cmp);s2mm_des->callback = s2mm_chan_callback;s2mm_des->callback_param = &s2mm_cmp;//提交描述符mm2s_cookie = dmaengine_submit(mm2s_des);if(dma_submit_error(mm2s_cookie)){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("submit mm2s_des faled\r\n");break;}s2mm_cookie = dmaengine_submit(s2mm_des);if(dma_submit_error(s2mm_cookie)){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("submit s2mm_des faled\r\n");break;}//啟動傳輸dma_async_issue_pending(dma_test_handle->mm2s_chan);dma_async_issue_pending(dma_test_handle->s2mm_chan);//等待傳輸完成if(wait_for_completion_timeout(&mm2s_cmp, msecs_to_jiffies(300000)) == 0){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("mm2s transfer timeout\r\n");break;}if(wait_for_completion_timeout(&s2mm_cmp, msecs_to_jiffies(300000)) == 0){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("s2mm transfer timeout\r\n");break;}//獲取傳輸結果status = dma_async_is_tx_complete(dma_test_handle->mm2s_chan, mm2s_cookie, NULL, NULL);if(status != DMA_COMPLETE){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("mm2s transfer error\r\n");break;}status = dma_async_is_tx_complete(dma_test_handle->s2mm_chan, s2mm_cookie, NULL, NULL);if(status != DMA_COMPLETE){dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);printk("s2mm transfer error\r\n");break;}//取消映射dma_unmap_sg(dma_test_handle->s2mm_chan->device->dev, s2mm_sg, DMA_TEST_BUFFER_COUNT, DMA_FROM_DEVICE);dma_unmap_sg(dma_test_handle->mm2s_chan->device->dev, mm2s_sg, DMA_TEST_BUFFER_COUNT, DMA_TO_DEVICE);//驗證傳輸結果for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){for(buffer_index = 0; buffer_index < DMA_TEST_BUFFER_SIZE; buffer_index++){if(ptr_mm2s[loop_count][buffer_index] != ptr_s2mm[loop_count][buffer_index]){printk("verifying failed!!!\r\n");printk("mm2s[%d][%d] = %d, s2mm[%d][%d] = %d\r\n", loop_count, buffer_index, ptr_mm2s[loop_count][buffer_index],loop_count, buffer_index, ptr_s2mm[loop_count][buffer_index]);break;}}if(buffer_index < DMA_TEST_BUFFER_SIZE)break;}if((loop_count < DMA_TEST_BUFFER_COUNT) || (buffer_index < DMA_TEST_BUFFER_SIZE))break;printk("%d.DMA test success\r\n", test_count);//測試技術遞增test_count++;}//停止通道上的所有傳輸事件dmaengine_terminate_all(dma_test_handle->mm2s_chan);dmaengine_terminate_all(dma_test_handle->s2mm_chan);S2MM_ALLOC_ERROR:for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){if(ptr_s2mm[loop_count])kfree(ptr_s2mm[loop_count]);}
MM2S_ALLOC_ERROR:for(loop_count = 0; loop_count < DMA_TEST_BUFFER_COUNT; loop_count++){if(ptr_mm2s[loop_count])kfree(ptr_mm2s[loop_count]);}dma_test_handle->thread_done = 1;return 0;
}//設備和驅動匹配成功執行
static int dma_test_probe(struct platform_device *pdev)
{int result;struct dma_test_handle *dma_test_handle;printk("%s probe\r\n", pdev->name);//分配設備句柄dma_test_handle = devm_kzalloc(&pdev->dev, sizeof(struct dma_test_handle), GFP_KERNEL);if(!dma_test_handle){printk("alloc memory failed\r\n");return -ENOMEM;}//復位LED設備句柄memset(dma_test_handle, 0, sizeof(struct dma_test_handle));//分配一個編號,用于標識測試任務dma_test_handle->number = ida_simple_get(&task_number_ida, 0, DMA_TEST_TASK_MAX, GFP_KERNEL);if(dma_test_handle->number < 0){printk("get number failed\r\n");return dma_test_handle->number;}//請求DMA通道dma_test_handle->mm2s_chan = dma_request_chan(&pdev->dev, "axidma0");if(IS_ERR(dma_test_handle->mm2s_chan)) {ida_simple_remove(&task_number_ida, dma_test_handle->number);result = IS_ERR(dma_test_handle->mm2s_chan);printk("xilinx_dmatest: No Tx channel\n");}dma_test_handle->s2mm_chan = dma_request_chan(&pdev->dev, "axidma1");if(IS_ERR(dma_test_handle->s2mm_chan)) {dma_release_channel(dma_test_handle->mm2s_chan);ida_simple_remove(&task_number_ida, dma_test_handle->number);result = IS_ERR(dma_test_handle->s2mm_chan);printk("xilinx_dmatest: No Rx channel\n");}//線程主動退出標志dma_test_handle->thread_done = 0;//創建內核線程dma_test_handle->kthread = kthread_create(dma_test_thread, dma_test_handle, "dma_test_thread%d", dma_test_handle->number);if(IS_ERR(dma_test_handle->kthread)){dma_release_channel(dma_test_handle->s2mm_chan);dma_release_channel(dma_test_handle->mm2s_chan);ida_simple_remove(&task_number_ida, dma_test_handle->number);printk("create dma_test_thread failed\r\n");return IS_ERR(dma_test_handle->kthread);}//啟動內核線程wake_up_process(dma_test_handle->kthread);//設置平臺設備的驅動私有數據pdev->dev.driver_data = (void*)dma_test_handle;return 0;
}//設備或驅動卸載時執行
static int dma_test_remove(struct platform_device *pdev)
{struct dma_test_handle *dma_test_handle;printk("%s remove\r\n", pdev->name);//提取平臺設備的驅動私有數據dma_test_handle = (struct dma_test_handle*)pdev->dev.driver_data;//停止內核線程if(dma_test_handle->thread_done == 0)kthread_stop(dma_test_handle->kthread);//釋放DMA通道dma_release_channel(dma_test_handle->s2mm_chan);dma_release_channel(dma_test_handle->mm2s_chan);//釋放測試任務編號ida_simple_remove(&task_number_ida, dma_test_handle->number);return 0;
}//系統關機前執行
static void dma_test_shutdown(struct platform_device *pdev)
{printk("%s shutdown\r\n", pdev->name);
}//系統進入睡眠狀態之前執行
static int dma_test_suspend(struct platform_device *pdev, pm_message_t state)
{printk("%s suspend\r\n", pdev->name);return 0;
}//系統從睡眠狀態中喚醒系統后執行
static int dma_test_resume(struct platform_device *pdev)
{printk("%s resume\r\n", pdev->name);return 0;
}//匹配列表,用于設備樹和平臺驅動匹配
static const struct of_device_id dma_test_of_match[] = {{ .compatible = "axi_dma_test" },{ /* Sentinel */ }
};
//平臺驅動
struct platform_driver dma_test_drv = {.driver = {.name = "axi_dma_test", //平臺驅動名稱.owner = THIS_MODULE,.pm = NULL,.of_match_table = dma_test_of_match,},.probe = dma_test_probe, //設備和驅動匹配成功執行.remove = dma_test_remove, //設備或驅動卸載時執行.shutdown = dma_test_shutdown, //系統關機前執行.suspend = dma_test_suspend, //系統休眠前執行.resume = dma_test_resume, //系統喚醒后執行
};static int __init plt_drv_init(void)
{int result;printk("%s\r\n", __FUNCTION__);//注冊平臺驅動result = platform_driver_register(&dma_test_drv);if(result < 0){printk("add cdev failed\r\n");return result;}return 0;
}static void __exit plt_drv_exit(void)
{printk("%s\r\n", __FUNCTION__);//注銷平臺驅動platform_driver_unregister(&dma_test_drv);
}module_init(plt_drv_init);
module_exit(plt_drv_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("dma_test_dev");