06 实现自定义AXI DMA驱动

为什么要实现自定义AXI DMA驱动

ZYNQ 的 AXI DMA 在 Direct Register DMA (即 Simple DMA)模式下可以通过 AXIS 总线的 tlast 提前结束传输,同时还可以在 BUFFLEN 寄存器中读取到实际传输的字节数,但是通过 Linux 的 DMA 驱动框架控制 AXI DMA 时无法获取实际传输的字节数,因此这里便基于字符设备驱动框架实现一个 AXI DMA 的驱动(只支持 Simple 模式)。

AXI DMA IP配置

在这里插入图片描述

设备树修改

  1. 在顶层设备树根节点中加入如下节点
	axi_dma_test0: dma_test@0{
   
		compatible = "user_axi_dma";						/* 用于设备树与驱动进行匹配 */
		status = "okay";									/* 状态 */
		interrupt-names = "mm2s_introut", "s2mm_introut";	/* 中断名称 */
		interrupt-parent = <&intc>;							/* 中断控制器 */
		interrupts = <0 29 4 0 30 4>;						/* mm2s中断类型、中断号、触发方式,s2mm中断类型、中断号、触发方式,需要结合Vivado的配置 */
		reg = <0x40400000 0x10000>;							/* 控制寄存器 */
		buffer-max = <512>;									/* 收发缓冲区大小,不能超过DMA最大传输字节数 */
	};
  1. 在顶层设备树中引用axi_dma_0节点,并加入如下内容
&axi_dma_0 {
   
	compatible = "unused-xlnx-axi-dma-driver";				/* 修改compatible属性,不使用赛灵思的AXI DMA驱动 */
};

Linux驱动代码

#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/poll.h>
#include <linux/ioport.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/skbuff.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

#define USER_AXI_DMA_MAX_MINORS		10

#define XAXIDMA_CR_OFFSET			0x00000000		/* Channel control */
#define XAXIDMA_SR_OFFSET			0x00000004		/* Status */
#define XAXIDMA_SRCADDR_OFFSET		0x00000018		/* Simple mode source address pointer */
#define XAXIDMA_DESTADDR_OFFSET		0x00000018		/* Simple mode destination address pointer */
#define XAXIDMA_SRCADDR_MSB_OFFSET	0x0000001C		/* Simple mode source address pointer */
#define XAXIDMA_BUFFLEN_OFFSET		0x00000028		/* Tail descriptor pointer */

#define XAXIDMA_CR_RUNSTOP_MASK		0x00000001 		/* Start/stop DMA channel */
#define XAXIDMA_CR_RESET_MASK		0x00000004 		/* Reset DMA engine */
#define XAXIDMA_CR_KEYHOLE_MASK		0x00000008 		/* Keyhole feature */
#define XAXIDMA_CR_CYCLIC_MASK		0x00000010 		/* Cyclic Mode */

#define XAXIDMA_IRQ_IOC_MASK		0x00001000 		/* Completion intr */
#define XAXIDMA_IRQ_DELAY_MASK		0x00002000 		/* Delay interrupt */
#define XAXIDMA_IRQ_ERROR_MASK		0x00004000 		/* Error interrupt */
#define XAXIDMA_IRQ_ALL_MASK		0x00007000 		/* All interrupts */

struct user_axi_dma {
   
	//cdev对象
	struct cdev cdev;
	//次设备号
	unsigned long minor;
	//设备ID
	dev_t devt;
	//打开标志
	atomic_t open_flag;
	//mm2s等待队列
	uint32_t mm2s_result;
	struct wait_queue_head mm2s_wq;
	//s2mm等待队列
	struct wait_queue_head s2mm_wq;
	//s2mm skbuff
	struct sk_buff_head s2mm_skbuff;
	//MM2S的中断号
	int mm2s_irq;
	//S2MM的中断号
	int s2mm_irq;
	//MM2S中断名称
	char mm2s_irq_name[32];
	//S2MM中断名称
	char s2mm_irq_name[32];
	//IO内存虚拟地址
	void __iomem *base_addr;
	//mm2s buffer
	uint8_t *mm2s_buffer;
	dma_addr_t mm2s_phy;
	//s2mm buffer
	uint8_t *s2mm_buffer;
	dma_addr_t s2mm_phy;
	//DMA缓冲区最大大小
	uint32_t buffer_max;
};

//起始设备号
static dev_t user_axi_dma_major;
//class对象
static struct class *user_axi_dma_class;
//IAD,用于管理次设备号
static DEFINE_IDA(user_axi_dma_ida);

static void mm2s_transfer(struct user_axi_dma *user_axi_dma, dma_addr_t mm2s_buffer, uint32_t length)
{
   
	uint32_t cr_reg;

	iowrite32(mm2s_buffer, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_SRCADDR_OFFSET);

	cr_reg = ioread32(user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);
	iowrite32(cr_reg | XAXIDMA_CR_RUNSTOP_MASK, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);

	iowrite32(length, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_BUFFLEN_OFFSET);
}

static void s2mm_transfer(struct user_axi_dma *user_axi_dma, dma_addr_t s2mm_buffer, u32 length)
{
   
	uint32_t cr_reg;

	iowrite32(s2mm_buffer, user_axi_dma->base_addr + 0x00000030 + XAXIDMA_DESTADDR_OFFSET);

	cr_reg = ioread32(user_axi_dma->base_addr + 0x00000030 + XAXIDMA_CR_OFFSET);
	iowrite32(cr_reg | XAXIDMA_CR_RUNSTOP_MASK, user_axi_dma->base_addr + 0x00000030 + XAXIDMA_CR_OFFSET);

	iowrite32(length, user_axi_dma->base_addr + 0x00000030 + XAXIDMA_BUFFLEN_OFFSET);
}

static irqreturn_t mm2s_interrupt(int irq, void *d)
{
   
	int count;
    uint32_t irq_status;
	uint32_t mm2s_cr;
	uint32_t s2mm_cr;
	uint32_t mm2s_bufflen;
	struct user_axi_dma *user_axi_dma = d;
	
    //读取待处理的中断
	irq_status = ioread32(user_axi_dma->base_addr + 0x00000000 + XAXIDMA_SR_OFFSET) & XAXIDMA_IRQ_ALL_MASK;
    //确认待处理的中断
	iowrite32(irq_status & XAXIDMA_IRQ_ALL_MASK, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_SR_OFFSET);
	
    //mm2s出错
    if((irq_status & XAXIDMA_IRQ_ERROR_MASK)) 
	{
   
		//复位DMA控制器
		iowrite32(XAXIDMA_CR_RESET_MASK, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);
		//等待复位完成
		for(count = 0; count < 2000; count++)
		{
   
			mm2s_cr = ioread32(user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);
			s2mm_cr = ioread32(user_axi_dma->base_addr + 0x00000030 + XAXIDMA_CR_OFFSET);
			if(((mm2s_cr & XAXIDMA_CR_RESET_MASK) == 0) && ((s2mm_cr & XAXIDMA_CR_RESET_MASK) == 0))
				break;
		}
		
		//传输结果为0xFFFFFFFF,表示失败
		user_axi_dma->mm2s_result = 0xFFFFFFFF;
    }
	else
	{
   
		//获取DMA实际发送长度
		mm2s_bufflen = ioread32(user_axi_dma->base_addr + 0x00000000 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值