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(流控制器)的时候,需要将该字段设置为true
	bool device_fc;
	//外部设备通过slave_id告诉dma controller自己是谁,很多dma controller不区分slave,只要给它src、dst、len等信息,它就可以进行传输,因此slave_id可以忽略,有些dma controller必须清晰地知道此次传输的对象是哪个外设,就必须要提供slave_id
	unsigned 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);
	
	//分配源内存,mm2s
	memset(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;
	}
	//分配目的内存,s2mm
	memset(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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/934986.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入解析下oracle的number底层存储格式

oracle数据库中&#xff0c;number数据类型用来存储数值数据&#xff0c;它既可以存储负数数值&#xff0c;也可以存储正数数值。相对于其他类型数据&#xff0c;number格式的数据底层存储格式要复杂得多。今天我们就详细探究下oracle的number底层存储格式。 一、环境搭建 1.…

多速率信号处理

介绍 简单来说&#xff0c; 多速率信号处理&#xff0c;是指对同时存在两个以上数据速率的系统进行信号处理。多速率信号处理原理就是通过改变信号速率&#xff0c;以适应系统内部各个节点对信号速率的要求。这里的速率其实就是指采样率。 多速率技术已广泛应用于数字音频处理…

golang实现简单的redis服务

golang 手搓redis服务器仓库地址:实现思路: golang 手搓redis服务器 仓库地址: 仓库: https://github.com/dengjiayue/my-redis.git 实现思路: ● 协议: tcp通信 ● 数据包: 长度(4byte)方法(1byte)数据json ● 数据处理: 单线程map读写 ○ 依次处理待处理队列的请求(chan)…

ssd202d-badblock-坏块检测

这边文章讲述的是坏快检测功能 思路: 1.第一次烧录固件会实现跳坏块,但是后续使用会导致坏块的产生; 于是我在uboot环境变量添加了两个变量来控制坏快 lb_badnum = //坏块个数 lb_badoff = //坏块所在位置 2.第一次开机会根据lb_badnum是否存在判断,如果不存在则保存上…

Y3编辑器文档2:场景编辑

文章目录 一、操作区二、地图设置2.1 地图大小2.2 其它选项三、地形编辑3.1 地势3.2 地形3.3 通行(碰撞、通行和视野规则)3.4 植被四、物件放置4.1 单位4.1.1 单位的摆放与调整4.1.2 状态栏属性编辑4.2 装饰物摆放4.3 物品4.4 镜头4.4.1 镜头的基本参数4.4.2 镜头时间轴动画4…

windows将文件推给Android真机/实机

记录一下 因为以前只试过从真机实机中将文件推给windows 但是从windows只简单复制粘贴的话会一直报错。 1.电脑安装adb 2.手机开启开发者模式 usb调试 3.usb连接选择文件传输 4.推送命令adb push 文件路径 /sdcard/download 步骤1和2和3不作赘述&#xff0c;可以搜相关配置教程…

[机器学习] 监督学习之线性回归与逻辑回归

这里写目录标题 一、监督学习概述二、线性回归&#xff08;一&#xff09;模型表示&#xff08;二&#xff09;损失函数&#xff08;三&#xff09;梯度下降算法导入所需库生成模拟数据&#xff08;可替换为真实数据&#xff09;初始化参数并进行训练可视化损失函数随迭代次数的…

精准预测美国失业率和贫困率,谷歌人口动态基础模型PDFM已开源,可增强现有地理空间模型

疾病、经济危机、失业、灾害……人类世界长期以来被各种各样的问题「侵扰」&#xff0c;了解人口动态对于解决这类复杂的社会问题至关重要。 政府相关人员可以通过人口动态数据来模拟疾病的传播&#xff0c;预测房价和失业率&#xff0c;甚至预测经济危机。然而&#xff0c;在过…

E172 ASP.NET+SQL+C#+LW+图书管理系统的设计与实现 配置 源码 文档 全套资料

图书管理系统 1.项目摘要2. 系统的概述3.项目功能4.界面展示5.源码获取 1.项目摘要 摘 要 书籍是供人们获取并增长知识的主要途径&#xff0c;由于图书的种类较多&#xff0c;阅读者也较多&#xff0c;借阅量较大&#xff0c;且易出错&#xff0c;传统的图书借阅若还停留在手工…

aippt:AI 智能生成 PPT 的开源项目

aippt&#xff1a;AI 智能生成 PPT 的开源项目 在现代办公和学习中&#xff0c;PPT&#xff08;PowerPoint Presentation&#xff09;是一种非常重要的展示工具。然而&#xff0c;制作一份高质量的PPT往往需要花费大量的时间和精力。为了解决这一问题&#xff0c;aippt项目应运…

轮转数组

轮转数组 1、题目描述2、解答思路2.1、辅助数组2.2、原地反转 1、题目描述 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 2、解答思路 2.1、辅助数组 如果我们在原数组上通过覆盖元素会导致部分元素的丢失&#xff0c…

selenium学习:等待方式

隐式等待 1.针对查找元素设置最大的超时时间 2.可以全局性的设置 3.不满足时&#xff0c;提示no such element driver.implicitly_wait(5) #对查找元素最大的超时时间&#xff0c;如果超过最大等待时间后&#xff0c;没有找到元素&#xff0c;则会报错&#xff1a;no such #e…

一文说清flink从编码到部署上线

引言&#xff1a;目前flink的文章比较多&#xff0c;但一般都关注某一特定方面&#xff0c;很少有一个文章&#xff0c;从一个简单的例子入手&#xff0c;说清楚从编码、构建、部署全流程是怎么样的。所以编写本文&#xff0c;自己做个记录备查同时跟大家分享一下。本文以简单的…

ZUC256 Go Go Go!!!

文章目录 背景运行效果代码 背景 因业务需要使用ZUC算法&#xff0c;GitHub上又没有对ZUC256相对应的Go语言的实现。 吃水不忘挖井人&#xff0c;在这里感谢GmSSL及BouncyCastle两个强大的密码学库&#xff01; 本ZUC256的编写&#xff0c;参考了这两个库及中科院软件院发布的…

图论【Lecode_HOT100】

文章目录 1.岛屿数量No.2002.腐烂的橘子No.9943.课程表No.2074.实现Trie&#xff08;前缀树&#xff09;No.208 1.岛屿数量No.200 class Solution {public int numIslands(char[][] grid) {if (grid null || grid.length 0) {return 0;}int numIslands 0;int rows grid.len…

快速将请求头构建成json结构

1.背景 有时候我们要爬虫(组包)请求一个资源数据,需要构建与原始请求一样的请求头,从浏览器复制过来的请求头,有很多,如果一个一个的配置成json有点慢,那么如何快速构建呢? 今天就使用正则表达式的方式实现 正则表达式实现快速将请求头构建成json结构 将冒号后边的换行符去掉…

数据结构6.3--交换排序

目录 交换排序基本思想 1.冒泡排序 2.快速排序 2.1hoare版本 2.2挖坑法 2.3前后指针版本 交换排序基本思想 所谓交换&#xff0c;就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置&#xff0c;交换排序的特点是&#xff1a;将键值较大的记录向序列的尾…

电脑怎么设置通电自动开机(工控机)

操作系统&#xff1a;win10 第一步&#xff0c;电脑开机时按del键进入bios页面。 第二步&#xff0c;选择advanced下的IT8712 Super IO Configuration 第三步&#xff0c;找到Auto Power On&#xff0c;将其从Power off设置为Power On 第四步&#xff0c;F10保存&#xff0c;大…

LearnOpenGL学习(高级OpenGL -> 高级GLSL,几何着色器,实例化)

高级GLSL 内建变量 顶点着色器 gl_PointSoze : float 输出变量&#xff0c;用于控制渲染 GL_POINTS 型图元时&#xff0c;点的大小。可用于粒子系统。将其设置为 gl_Position.z 时&#xff0c;可以使点的距离越远&#xff0c;大小越大。创建出类似近视眼看远处灯光的效果 gl…

SQL语句错误号:Incorrect integer value: ‘‘ for column ‘poi_id‘ at

SQL语句错误号&#xff1a;Incorrect integer value: for column poi_id at通用解决方案 在MySQL 5.7中&#xff0c;如果你遇到 Incorrect integer value: for column poi_id at row 1 错误&#xff0c;这通常意味着你尝试将一个空字符串插入到需要整数值的字段中。以下是几…