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.mzph.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-安装教程 如…

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 …

机器学习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与…

微信小程序横屏页面跳转后,自定义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…

《探索形象克隆:科技与未来的奇妙融合》

目录 一、什么是形象克隆 二、形象克隆的技术原理 三、形象克隆的发展现状 四、形象克隆的未来趋势 五、形象克隆的应用场景 六、形象克隆简单代码案例 Python 实现数字人形象克隆 Scratch 实现角色克隆效果&#xff08;以猫为例&#xff09; JavaScript 实现 Scratc…

MATLAB深度学习(七)——ResNet残差网络

一、ResNet网络 ResNet是深度残差网络的简称。其核心思想就是在&#xff0c;每两个网络层之间加入一个残差连接&#xff0c;缓解深层网络中的梯度消失问题 二、残差结构 在多层神经网络模型里&#xff0c;设想一个包含诺干层自网络&#xff0c;子网络的函数用H(x)来表示&#x…

前端入门之VUE--vue组件化编程

前言 VUE是前端用的最多的框架&#xff1b;这篇文章是本人大一上学习前端的笔记&#xff1b;欢迎点赞 收藏 关注&#xff0c;本人将会持续更新。 文章目录 2、Vue组件化编程2.1、组件2.2、基本使用2.2.1、VueComponent 2、Vue组件化编程 2.1、组件 组件&#xff1a;用来实现…

设计模式-装饰器模式(结构型)与责任链模式(行为型)对比,以及链式设计

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言1.装饰器模式1.1概念1.2作用1.3应用场景1.4特点1.5类与对象关系1.6实现 2责任链模式2.1概念2.2作用2.3应用场景2.4特点2.5类与对象关系2.6实现 3.对比总结 前言…

操作系统:死锁与饥饿

目录 死锁概念 饥饿与饿死概念 饥饿和死锁对比 死锁类型 死锁条件&#xff08;Coffman条件&#xff09; 死锁恢复方法 死锁避免 安全状态与安全进程序列&#xff1a; 银行家算法&#xff1a; 死锁检测时机&#xff08;了解&#xff09;&#xff1a; 死锁检测 死锁案…

RPO: Read-only Prompt Optimization for Vision-Language Few-shot Learning

文章汇总 想解决的问题对CoOp的改进CoCoOp尽管提升了性能,但却增加了方差(模型的准确率波动性较大)。 模型的框架一眼看去,跟maple很像(maple跟这篇文章都是2023年发表的),但maple的视觉提示是由文本提示经过全连接转换而来的,而这里是文本提示和视觉提示是独立的。另外m…

『MySQL 实战 45 讲』24 - MySQL是怎么保证主备一致的?

MySQL是怎么保证主备一致的&#xff1f; MySQL 主备的基本原理 基本的主备切换流程 状态 1&#xff1a;客户端的读写都直接访问节点 A&#xff0c;而节点 B 是 A 的备库状态 2&#xff1a;切换时&#xff0c;读写访问的都是节点 B&#xff0c;而节点 A 是 B 的备库注意&…