05 在 Linux 使用 AXI DMA

DMA簡介

DMA 是一種采用硬件實現存儲器與存儲器之間或存儲器與外設之間直接進行高速數據傳輸的技術,傳輸過程無需 CPU 參與(但是CPU需要提前配置傳輸規則),可以大大減輕 CPU 的負擔。
DMA 存儲傳輸的過程如下:

  1. CPU 向 DMA 控制器配置傳輸規則,如源地址、目的地址、長度、地址是否自增等
  2. DMA 控制器根據配置進行數據搬移,此時 CPU 資源被釋放
  3. 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 模式使用的相對較少

  1. 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)代碼時必須設置緩沖區地址和長度字段以啟動相應通道中的傳輸。
  2. Scatter/Gather DMA 模式 :
    SGDMA(Scatter/Gather DMA)模式允許在單個 DMA 事務中將數據傳輸到多個存儲區域(相當于將多個 Simple DMA 請求通過事務列表鏈接在一起)。SGDMA 允許應用程序在內存中定義事務列表,硬件將在應用程序沒有進一步干預的情況下處理這些事務,在此期間應用程序可以繼續添加更多事務以保持硬件工作,還可以通過輪詢或中斷來檢查事務是否完成。
  3. 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 工程框圖如下:
在這里插入圖片描述

  1. 添加 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中斷配置如下:
    在這里插入圖片描述

  2. 添加 DMA IP 核
    在這里插入圖片描述

  3. 配置 DMA IP 核
    在這里插入圖片描述

  4. 添加 FIFO IP 核心
    在這里插入圖片描述

  5. 連接DMA IP和FIFO IP,將 DMA 的 MM2S 和 S2MM 接口通過一個 AXIS DATA FIFO 構成回環。
    在這里插入圖片描述

  6. 點擊“Run Block Automation”和“Run Connection Automation”進行自動連線(可能會重復操作多次),在連線過程中會自動添加一些 IP 核。
    在這里插入圖片描述

  7. 添加 concat IP 核和 const IP 核
    添加 concat IP:
    在這里插入圖片描述
    添加 const IP:
    在這里插入圖片描述

  8. 配置 concat IP 核和 const IP 核
    配置 concat IP:
    在這里插入圖片描述
    配置 const IP:
    在這里插入圖片描述

  9. 鏈接中斷信號
    在這里插入圖片描述

  10. 生成代碼,然后編譯并生成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 驅動編寫流程

  1. 申請DMA通道
  2. 配置DMA通道(部分專用DMA不需要進行配置)
  3. 分配DMA內存(對來自其他驅動模塊的內存則進行映射操作)
  4. 創建描述符,并綁定傳輸完成回調函數
  5. 提交描述符
  6. 啟動DMA傳輸
  7. 等待傳輸完成
  8. 檢查傳輸結果
  9. 釋放前面分配的DMA內存
  10. 釋放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");

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/62673.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/62673.shtml
英文地址,請注明出處:http://en.pswp.cn/web/62673.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux 安裝 vsftpd 服務以及配置全攻略,vsftpd 虛擬多用戶多目錄配置,為每個用戶配置不同的使用權限

linux 安裝 vsftpd 服務以及配置全攻略&#xff0c;vsftpd 虛擬多用戶多目錄配置&#xff0c;為每個用戶配置不同的使用權限。 linux 安裝 vsftpd 服務以及配置全攻略 FTP 是 File Transfer Protocol 的簡稱&#xff0c;用于 Internet 上的控制文件的雙向傳輸。同時&#xff0…

SQL語句在MySQL中如何執行

MySQL的基礎架構 首先就是客戶端&#xff0c;其次Server服務層&#xff0c;大多數MySQL的核心服務都在這一層&#xff0c;包括連接、分析、優化、緩存以及所有的內置函數&#xff08;時間、日期、加密函數&#xff09;&#xff0c;所有跨存儲引擎功能都在這一層實現&#xff1…

ragflow連不上ollama的解決方案

由于前期wsl默認裝在C盤&#xff0c;后期部署好RagFlow后C盤爆紅&#xff0c;在連接ollama的時候一直在轉圈圈&#xff0c;問其他人沒有遇到這種情況&#xff0c;猜測是因為內存不足無法加載模型導致&#xff0c;今天重新在E盤安裝wsl 使用wsl裝Ubuntu Win11 wsl-安裝教程 如…

力扣-漢明距離

1.兩個整數之間的 漢明距離 指的是這兩個數字對應二進制位不同的位置的數目。 給你兩個整數 x 和 y&#xff0c;計算并返回它們之間的漢明距離。 看到這題&#xff0c;當然想到了按位異或^,并且c內置了計算二進制數中1數量的函數__builtin_popcount() class Solution { publ…

關于成功插入 SQLite 但沒有數據的問題

背景 技術棧&#xff1a;SpringBoot Mybatis-flex SQLite 項目中集成了SQLite&#xff0c;配置如下&#xff1a; spring:datasource:url: jdbc:sqlite::resource:db/project.dbdriver-class-name: org.sqlite.JDBC在進行測試時&#xff0c;使用Mybatis-flex往表中插入數據&…

C#常見錯誤—空對象錯誤

System.NullReferenceException&#xff1a;未將對象引用設置到對象的實例 在C#編程中&#xff0c;System.NullReferenceException是一個常見的運行時異常&#xff0c;其錯誤信息“未將對象引用設置到對象的實例”意味著代碼試圖訪問一個未被初始化或已被設置為null的對象的成…

沁恒CH32V208藍牙串口透傳例程:修改透傳的串口;UART-CH32V208-APP代碼分析;APP-CH32V208-UART代碼分析

從事嵌入式單片機的工作算是符合我個人興趣愛好的,當面對一個新的芯片我即想把芯片盡快搞懂完成項目賺錢,也想著能夠把自己遇到的坑和注意事項記錄下來,即方便自己后面查閱也可以分享給大家,這是一種沖動,但是這個或許并不是原廠希望的,盡管這樣有可能會犧牲一些時間也有哪天原…

Scala的隱式對象

Scala中&#xff0c;隱式對象&#xff08;implicit object&#xff09;是一種特殊的對象&#xff0c;它可以使得其成員&#xff08;如方法和值&#xff09;在特定的上下文中自動可用&#xff0c;而無需顯式地傳遞它們。隱式對象通常與隱式參數和隱式轉換一起使用&#xff0c;以…

矩陣的乘(包括乘方)和除

矩陣的乘分為兩種&#xff1a; 一種是高等代數中對矩陣的乘的定義&#xff1a;可以去這里看看包含矩陣的乘。總的來說&#xff0c;若矩陣 A s ? n A_{s*n} As?n?列數和矩陣 B n ? t B_{n*t} Bn?t?的行數相等&#xff0c;則 A A A和 B B B可相乘&#xff0c;得到一個矩陣 …

DVWA親測sql注入漏洞

LOW等級 我們先輸入1 我們加上一個單引號&#xff0c;頁面報錯 我們看一下源代碼&#xff1a; <?php if( isset( $_REQUEST[ Submit ] ) ) { // Get input $id $_REQUEST[ id ]; // Check database $query "SELECT first_name, last_name FROM users WHERE user_id …

C++,提供函數接口,函數如何做到接收外部變量隨時結束

在C中&#xff0c;如果你想要創建一個函數&#xff0c;該函數可以接收外部變量并在變量改變時作出響應&#xff0c;你可以使用回調函數或者將變量包裝在可以觀察其變化的設計模式中&#xff0c;例如觀察者模式。 以下是一個使用標準庫中的std::function和std::bind來創建響應外…

機器學習01-發展歷史

機器學習01-發展歷史 文章目錄 機器學習01-發展歷史1-傳統機器學習的發展進展1. 初始階段&#xff1a;統計學習和模式識別2. 集成方法和核方法的興起3. 特征工程和模型優化4. 大規模數據和分布式計算5. 自動化機器學習和特征選擇總結 2-隱馬爾科夫鏈為什么不能解決較長上下文問…

想了解操作系統,有什么書籍推薦?

推薦一本操作系統經典書&#xff1a; 操作系統導論 《操作系統導論》虛擬化(virtualization)、并發(concurrency)和持久性(persistence)。這是我們要學習的3個關鍵概念。通過學習這3個概念&#xff0c;我們將理解操作系統是如何工作的&#xff0c;包括它如何決定接下來哪個程序…

[Collection與數據結構] 位圖與布隆過濾器

&#x1f338;個人主頁:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;?熱門專欄: &#x1f9ca; Java基本語法(97平均質量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection與…

【大數據學習 | 面經】Spark的shuffle hash join的具體細節

1. 前言 shuffle hash join是Spark中一種常見的連接策略&#xff0c;尤其適用于兩個數據集都比較大且無法通過廣播來優化的情況。其核心思想是通過對連接鍵進行哈希分區&#xff0c;使得相同鍵值的數據被分配到相同的分區中&#xff0c;從而可以在每個分區獨立的執行連接操作。…

設計模式從入門到精通之(一)工廠模式

工廠模式&#xff1a;為每個工廠找到"生意經" 在現實生活中&#xff0c;我們隨處可見"工廠"的影子&#xff0c;比如汽車工廠生產汽車&#xff0c;食品工廠生產食品。但你有沒有想過&#xff0c;為什么我們需要工廠&#xff1f;如果沒有工廠&#xff0c;我們…

談談你對vue這種框架理解

發現寶藏 前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。【寶藏入口】。 Vue.js 是一個漸進式的前端 JavaScript 框架&#xff0c;旨在通過提供易于理解、上手簡單且功能強大的工具來構建現代化的 Web …

使用html2canvas實現前端截圖

一、主要功能 網頁截圖&#xff1a;html2canvas通過讀取DOM結構和元素的CSS樣式&#xff0c;在客戶端生成圖像&#xff0c;不依賴于服務端的渲染。它可以將指定的DOM元素渲染為畫布&#xff08;canvas&#xff09;&#xff0c;并生成圖像。多種輸出格式&#xff1a;生成的圖像…

微信小程序橫屏頁面跳轉后,自定義navbar樣式跑了?

文章目錄 問題原因&#xff1a;解決方案&#xff1a; 今天剛遇到的問題&#xff0c;橫屏的頁面完成操作后跳轉頁面后&#xff0c;自定義的tabbar樣式亂了&#xff0c;跑到最頂了&#xff0c;真機調試后發現navbar跑到手機狀態欄了&#xff0c;它正常應該跟右邊膠囊一行。 知道問…

Vivado ILA數據導出MATLAB分析

目錄 ILA數據導出 分析方式一 分析方式二 有時候在系統調試時&#xff0c;數據在VIVADO窗口獲取的信息有限&#xff0c;可結合MATLAB對已捕獲的數據進行分析處理 ILA數據導出 選擇信號&#xff0c;單擊右鍵后&#xff0c;會有export ILA DATA選項&#xff0c;將其保存成CS…