AXI DIRECT DMA ip 核的Linux驱动代码

一、 背景介绍

当使用ZYNQ芯片的PL端向PS端传递数据时,我选择的是通过PL的Stream FIFO通过AXIDMA的IP核将数据传输到PS端的DDR中,当时采用官方的驱动例程,发现只能PS端进行自回环但不能完成上述功能(可能是我没有正确使用),后来查了AXIDMA这个IP核的相关资料,发现控制起来不是很困难,就自己编写驱动代码进行驱动(PS端搭载Linux系统)。

二、原理介绍(基于Xilinx官方的pg021_axi_dma_PG021文档)

AXI DMA此IP核有两种DMA的模式,一种是分散/收集模式,另一种是直接寄存器模式(即AXI DIRECT DMA),我的项目只需要使用到简单的直接寄存器模式就行, 所以没有对分散/收集模式进行研究,这里只讲解直接寄存器模式。下图是对该模式下寄存器的映射,基地址查看vivado工程

MM2S表示从DDR到FIFO,MM2S表示从FIFO到DDR,依次配置控制控制器、地址寄存器(物理地址)、和传输的长度,当传输 长度寄存器配置完成,该DMA传输即开始,通过查看状态寄存器来判断DMA传输是否完成。

 三、驱动源代码

该驱动代码需配合PL端进行工作,PL端需给出一个寄存器端口表示FIFO中有多少数据,增加poll接口函数来降低轮询带来过度占用CPU的问题。

1、设备树配置

axi_direct_dma {
		compatible = "yang,axi-direct-dma";
		reg = <0x40400000 0x30>,<0x40400030 0x30>;
		reg-names = "s2mm","mm2s";
		status = "okay";
};

2、驱动代码 (基于字符设备)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/gfp.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>

#define DEVICE_NAME         "axidma"
#define DMA_CAPACITY        1048576//1MB

#define DMA_MM2S_CR         0x0
#define DMA_MM2S_SR         0x4   
#define DMA_MM2S_SADDR      0x18
#define DMA_MM2S_LENGTH     0x28   

#define DMA_S2MM_CR         0x0
#define DMA_S2MM_SR         0x4   
#define DMA_S2MM_SADDR      0x18
#define DMA_S2MM_LENGTH     0x28 

/*********定义IO_CTRL命令********/
#define DMA_RESET_BASE          'b'
#define DMA_READ_RESET          (_IOW(DMA_RESET_BASE,0,int))
#define DMA_WRITE_RESET         (_IOW(DMA_RESET_BASE,1,int))
#define DMA_WRITE_READ_RESET    (_IOW(DMA_RESET_BASE,2,int))

//dma的寄存器访问顺序需固定
#define dma_readreg(addr)           readl(addr)
#define dma_writereg(addr,val)      writel(val,addr)

struct axidma_dev{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct platform_device *pdev;
    void *s2mm_virt_addr;
    void *mm2s_virt_addr;
    dma_addr_t s2mm_phy_addr;
    dma_addr_t mm2s_phy_addr;
    void __iomem *s2mm_base;
    void __iomem *mm2s_base;
};

static int axidma_open(struct inode *file, struct file *flip){
    flip->private_data = container_of(file->i_cdev,struct axidma_dev,cdev);
    pr_info("Axi direct dma is prepared\r\n");
    return 0;
}

static int axidma_release(struct inode *file, struct file *flip){
    pr_info("Axi direct dma is exited\r\n");
    return 0;
}

static ssize_t axidma_read(struct file *flip, char __user *buf, size_t cnt, loff_t *offt){
    unsigned int s2mm_status=0;
    unsigned int s2mm_ctrl=0;
    unsigned int virtual_length=0;
    unsigned int i=0;
    struct axidma_dev *axidma_dev = (struct axidma_dev *)flip->private_data;
    
    if(cnt > DMA_CAPACITY)
    {
        pr_err("the number of data is not enough!\n");
        return -EINVAL;
    }
    /*******************给s2mm控制寄存器的中断位、启动位置1、关闭复位***************************/
    s2mm_ctrl = dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_CR);
    s2mm_ctrl |= (1<<0);//开启dma
    s2mm_ctrl &= ~(1<<12);//关闭中断
    s2mm_ctrl &= ~(1<<2);//关闭read dma reset
    dma_writereg(axidma_dev->s2mm_base+DMA_S2MM_CR,s2mm_ctrl);

    s2mm_ctrl = 0;
    /********************************写入目的地址**********************************/
    dma_writereg(axidma_dev->s2mm_base+DMA_S2MM_SADDR,axidma_dev->s2mm_phy_addr);
    /********************************写入传输长度**********************************/
    dma_writereg(axidma_dev->s2mm_base+DMA_S2MM_LENGTH,cnt);

    do {
        i++;
        s2mm_status=dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_SR);
        if(i == 0xffffff)
        {
            virtual_length = dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_LENGTH);
            pr_err("dma error s2mm_sr=0x%08x,s2mm_len=0x%08x\r\n",s2mm_status,virtual_length);
            
            /*******************复位读s2mm的dma*******************/
            s2mm_ctrl = dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_CR);
            s2mm_ctrl |= (1<<2);
            dma_writereg(axidma_dev->s2mm_base+DMA_S2MM_CR,s2mm_ctrl);
            s2mm_ctrl = 0;
            return -EINVAL;
        }
    }while(!(s2mm_status & (1 << 1)));

    virtual_length = dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_LENGTH);
    /********************************关闭dma**********************************/
    s2mm_ctrl = dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_CR); 
    s2mm_ctrl &= ~(1<<0);
    dma_writereg(axidma_dev->s2mm_base+DMA_S2MM_CR,s2mm_ctrl);

    /***************************将数据传递到用户空间****************************/
    if(copy_to_user(buf,axidma_dev->s2mm_virt_addr,cnt))
    {
        pr_err("dma read is error!\n");
        return -EINVAL;
    }

    return virtual_length;
}

static ssize_t axidma_write(struct file *flip, const char __user *buf, size_t cnt, loff_t *offt){
    unsigned int mm2s_status=0;
    unsigned int mm2s_ctrl = 0;
    unsigned int virtual_length = 0;
    unsigned int i = 0;
    struct axidma_dev *axidma_dev = (struct axidma_dev *)flip->private_data;

    if(cnt > DMA_CAPACITY)
    {
        pr_err("the number of data is over dma capacity!\n");
        return -EINVAL;
    }

    if(copy_from_user(axidma_dev->mm2s_virt_addr,buf,cnt))
    {
        pr_err("copy to kernel error\n");
        return -EINVAL;
    }
    /*******************给s2mm控制寄存器的中断位、启动位置1、关闭复位***************************/
    mm2s_ctrl = dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_CR);
    mm2s_ctrl |= (1<<0);//开启dma
    mm2s_ctrl &= ~(1<<12);//关闭中断
    mm2s_ctrl &= ~(1<<2);//关闭read dma reset
    dma_writereg(axidma_dev->mm2s_base+DMA_MM2S_CR,mm2s_ctrl);
    mm2s_ctrl = 0;
    /******************************配置传输地址******************************/
    dma_writereg(axidma_dev->mm2s_base+DMA_MM2S_SADDR,axidma_dev->mm2s_phy_addr);
    /******************************配置传输长度******************************/
    dma_writereg(axidma_dev->mm2s_base+DMA_MM2S_LENGTH,cnt);

    do {
        i++;
        mm2s_status=dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_SR);
        if(i == 0xfffff)
        {
            virtual_length = dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_LENGTH);
            pr_err("dma error mm2s_sr=0x%08x,mm2s_len=0x%08x\n",mm2s_status,virtual_length);
             /*******************复位读s2mm的dma*******************/
            mm2s_ctrl = dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_CR);
            mm2s_ctrl |= (1<<2);
            dma_writereg(axidma_dev->mm2s_base+DMA_MM2S_CR,mm2s_ctrl);
            mm2s_ctrl = 0;

            return -EINVAL;
        }
    }while(!(mm2s_status & (1 << 1)));

    virtual_length = dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_LENGTH);
    /********************************关闭dma**********************************/
    mm2s_ctrl = dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_CR);
    mm2s_ctrl &= ~(1<<0);
    dma_writereg(axidma_dev->mm2s_base+DMA_MM2S_CR,mm2s_ctrl);

    return virtual_length;
}

static long axidma_ioctl(struct file *flip,unsigned int cmd,unsigned long arg){
    unsigned long read_reset_state = 0;
    unsigned long write_reset_state = 0;
    struct axidma_dev *axidma_dev = (struct axidma_dev *)flip->private_data;
    switch (cmd)
    {
        case DMA_READ_RESET:
            /*******************复位读s2mm的dma*******************/
            read_reset_state = dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_CR);
            read_reset_state |= (1<<2);
            dma_writereg(axidma_dev->s2mm_base+DMA_S2MM_CR,read_reset_state);
            pr_info("read_reset_state = 0x%08x\r\n",dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_CR));
            break;
        case DMA_WRITE_RESET:
            /*******************复位写mm2s的dma*******************/
            write_reset_state = dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_CR);
            write_reset_state |= (1<<2);
            dma_writereg(axidma_dev->mm2s_base+DMA_MM2S_CR,write_reset_state);
            pr_info("write_reset_state = 0x%08x\r\n",dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_CR));
            break;
        case DMA_WRITE_READ_RESET:
            /*******************复位读s2mm的dma*******************/
            read_reset_state = dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_CR);
            read_reset_state |= (1<<2);
            dma_writereg(axidma_dev->s2mm_base+DMA_S2MM_CR,read_reset_state);
            
            /*******************复位读mm2s的dma*******************/
            write_reset_state = dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_CR);
            write_reset_state |= (1<<2);
            dma_writereg(axidma_dev->mm2s_base+DMA_MM2S_CR,write_reset_state);

            pr_info("read_reset_state = 0x%08x ; write_reset_state = 0x%08x\r\n",
                        dma_readreg(axidma_dev->s2mm_base+DMA_S2MM_CR),dma_readreg(axidma_dev->mm2s_base+DMA_MM2S_CR));
            break;
        default:break;
    }
    return 0;
}

static int axidma_mmap(struct file *flip,struct vm_area_struct *vma){
    pr_debug("AXI_MMAP open,virt = 0x%lx,phys = 0x%lx,len  = 0x%lx\r\n",vma->vm_start,(vma->vm_pgoff),vma->vm_end-vma->vm_start);
    //由用户空间决定映射的物理页地址
    if(remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,vma->vm_end-vma->vm_start,vma->vm_page_prot)){
        return -EAGAIN;
    }
    
    return 0;
}

static struct file_operations axidma_ops = {
    .owner = THIS_MODULE,
    .open = axidma_open,
    .write = axidma_write,
    .read = axidma_read,
    .unlocked_ioctl = axidma_ioctl,
    .release = axidma_release,
    .mmap = axidma_mmap,
};

static int axidma_probe(struct platform_device *pdev){
    int ret = 0;
    struct  resource *s2mm_res = NULL,*mm2s_res = NULL;
    struct axidma_dev *axidma_dev = NULL;

    axidma_dev = devm_kzalloc(&pdev->dev,sizeof(struct axidma_dev),GFP_KERNEL);
    if(!axidma_dev){
        ret = PTR_ERR(axidma_dev);
        goto malloc_err;
    }

    s2mm_res = platform_get_resource_byname(pdev,IORESOURCE_MEM,"s2mm");
    axidma_dev->s2mm_base = devm_ioremap_resource(&pdev->dev,s2mm_res);
    if(IS_ERR(axidma_dev->s2mm_base)){
        ret = PTR_ERR(axidma_dev->s2mm_base);
        goto malloc_err;
    }

    mm2s_res = platform_get_resource_byname(pdev,IORESOURCE_MEM,"mm2s");
    axidma_dev->mm2s_base = devm_ioremap_resource(&pdev->dev,mm2s_res);
    if(IS_ERR(axidma_dev->s2mm_base)){
        ret = PTR_ERR(axidma_dev->s2mm_base);
        goto malloc_err;
    }

    axidma_dev->s2mm_virt_addr = dma_alloc_coherent(&pdev->dev,DMA_CAPACITY,&axidma_dev->s2mm_phy_addr,GFP_KERNEL);
    if(!axidma_dev->s2mm_virt_addr){
        ret = PTR_ERR(axidma_dev->s2mm_virt_addr);
        goto malloc_err;
    }

    axidma_dev->mm2s_virt_addr = dma_alloc_coherent(&pdev->dev,DMA_CAPACITY,&axidma_dev->mm2s_phy_addr,GFP_KERNEL);
    if(!axidma_dev->mm2s_virt_addr){
        ret = PTR_ERR(axidma_dev->mm2s_virt_addr);
        goto s2mm_dma_alloc_err;
    }

    
    ret = alloc_chrdev_region(&axidma_dev->devid,0,1,DEVICE_NAME);
    if(ret < 0){
        pr_err("alloc chrdev error\r\n");
        goto mm2s_dma_alloc_err;
    }

    cdev_init(&axidma_dev->cdev,&axidma_ops);
    ret = cdev_add(&axidma_dev->cdev,axidma_dev->devid,1);
    if(ret < 0){
        pr_err("cdev add error\r\n");
        goto alloc_chrdev_err;
    }

    axidma_dev->class = class_create(THIS_MODULE,"axidma_chrdevs");
    if(IS_ERR(axidma_dev->class)){
        ret = PTR_ERR(axidma_dev->class);
        pr_err("class create error\r\n");
        goto cdev_add_error;
    }

    axidma_dev->device = device_create(axidma_dev->class,NULL,axidma_dev->devid,NULL,DEVICE_NAME);
    if(IS_ERR(axidma_dev->device)){
        ret = PTR_ERR(axidma_dev->device);
        pr_err("device create error\r\n");
        goto class_create_err;
    }

    platform_set_drvdata(pdev,axidma_dev);

    pr_info("axi direct dma init success!\r\n");

    return 0;

class_create_err:
    class_destroy(axidma_dev->class);

cdev_add_error:
    cdev_del(&axidma_dev->cdev);

alloc_chrdev_err:
    unregister_chrdev_region(axidma_dev->devid,1);
    
mm2s_dma_alloc_err:
    dma_free_coherent(&pdev->dev,DMA_CAPACITY,axidma_dev->mm2s_virt_addr,axidma_dev->mm2s_phy_addr);

s2mm_dma_alloc_err:
    dma_free_coherent(&pdev->dev,DMA_CAPACITY,axidma_dev->s2mm_virt_addr,axidma_dev->s2mm_phy_addr);

malloc_err:
    mm2s_res = NULL;
    s2mm_res = NULL;
    axidma_dev = NULL;

    return ret;
}

static int axidma_remove(struct platform_device *pdev){
    struct axidma_dev *axidma_dev = platform_get_drvdata(pdev);

    dma_free_coherent(&pdev->dev,DMA_CAPACITY,axidma_dev->s2mm_virt_addr,axidma_dev->s2mm_phy_addr);
    dma_free_coherent(&pdev->dev,DMA_CAPACITY,axidma_dev->mm2s_virt_addr,axidma_dev->mm2s_phy_addr);

    device_destroy(axidma_dev->class,axidma_dev->devid);
    class_destroy(axidma_dev->class);
    cdev_del(&axidma_dev->cdev);
    unregister_chrdev_region(axidma_dev->devid,1);
    
    return 0;
}

static const struct of_device_id axidma_id[] = {
    {.compatible = "yang,axi-direct-dma"},
    {}
};
MODULE_DEVICE_TABLE(of,axidma_id);

static struct platform_driver axidma_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "axi-direct-dma driver",
        .of_match_table = axidma_id
    },
    .probe = axidma_probe,
    .remove = axidma_remove
}; 

module_platform_driver(axidma_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yangsen<yangsen2123@163.com>");
MODULE_DESCRIPTION("XILINX ZYNQ AXI DIRECT DMA DRIVER");

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 中,AXI DMA IP 驱动程序由 Xilinx 提供,并包含在 Xilinx 的 Linux中。以下是在 Linux 中使用 AXI DMA 驱动程序的基本步骤: 1. 安装 Linux:首先,你需要安装 Xilinx 提供的适用于你的 Zynq SoC 的 Linux。这个内版本应该包含 AXI DMA 驱动程序。 2. 配置设备树(Device Tree):设备树是一种描述硬件配置的数据结构,在 Linux 中使用设备树来配置 AXI DMA IP 。你需要编辑设备树文件(.dts 或 .dtsi),添加 AXI DMA IP 的节点,并设置相应的属性,如基地址、中断号等。 3. 编译设备树:将设备树文件编译成二进制格式(.dtb),并将其放置在适当的位置,以使 Linux能够加载它。 4. 加载驱动程序:在启动 Linux时,你需要加载 AXI DMA 驱动程序模块。可以通过修改启动脚本或使用 `modprobe` 命令加载驱动程序模块。 5. 使用 AXI DMA 驱动程序:一旦驱动程序加载成功,你可以通过使用相应的设备节点(例如 `/dev/xdevcfg`)来控制和配置 AXI DMA IP 。你可以使用标准的文件操作系统调用(如 `open`、`read`、`write` 等)来与驱动程序进行交互。 需要注意的是,AXI DMA 驱动程序的具体使用方法可能会因不同的平台和内版本而有所差异。你可以查阅 Xilinx 的文档和示例代码,以便更详细地了解在 Linux 中使用 AXI DMA 驱动程序的具体步骤和配置方法。希望对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值