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配置
设备树修改
- 在顶层设备树根节点中加入如下节点
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最大传输字节数 */};
- 在顶层设备树中引用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;//设备IDdev_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 skbuffstruct 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 bufferuint8_t *mm2s_buffer;dma_addr_t mm2s_phy;//s2mm bufferuint8_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 + XAXIDMA_BUFFLEN_OFFSET);//传输结果,成功为传输长度,失败为0xFFFFFFFFuser_axi_dma->mm2s_result = (mm2s_bufflen > 0) ? mm2s_bufflen : 0xFFFFFFFF;}//通过等待队列唤醒正在进行写操作的线程wake_up_interruptible(&user_axi_dma->mm2s_wq);return IRQ_HANDLED;
}static irqreturn_t s2mm_interrupt(int irq, void *d)
{int count;uint32_t irq_status;uint32_t mm2s_cr;uint32_t s2mm_cr;uint32_t s2mm_bufflen;struct sk_buff *skb;struct user_axi_dma *user_axi_dma = d;//读取待处理的中断irq_status = ioread32(user_axi_dma->base_addr + 0x00000030 + XAXIDMA_SR_OFFSET) & XAXIDMA_IRQ_ALL_MASK;//确认待处理的中断iowrite32(irq_status & XAXIDMA_IRQ_ALL_MASK, user_axi_dma->base_addr + 0x00000030 + XAXIDMA_SR_OFFSET);//s2mm出错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;}}else{//获取DMA实际接收长度s2mm_bufflen = ioread32(user_axi_dma->base_addr + 0x00000030 + XAXIDMA_BUFFLEN_OFFSET);if(s2mm_bufflen == 0)return IRQ_HANDLED;//确保skbuff中最大包含8个skb,避免内存消耗过大while(skb_queue_len(&user_axi_dma->s2mm_skbuff) > 8){skb = skb_dequeue(&user_axi_dma->s2mm_skbuff);kfree_skb(skb);}//分配skbskb = alloc_skb(s2mm_bufflen, GFP_ATOMIC);if(!skb)return IRQ_HANDLED;//将数据拷贝到skpmemcpy(skb_put(skb, s2mm_bufflen), user_axi_dma->s2mm_buffer, s2mm_bufflen);//将skp添加到skp队列skb_queue_tail(&user_axi_dma->s2mm_skbuff, skb);//通过等待队列唤醒正在进行读操作的线程wake_up_interruptible(&user_axi_dma->s2mm_wq);}//重新启动DMA接收s2mm_transfer(user_axi_dma, user_axi_dma->s2mm_phy, user_axi_dma->buffer_max);return IRQ_HANDLED;
}static int user_axi_dma_open(struct inode *inode, struct file *p_file)
{int result;int count;uint32_t mm2s_cr;uint32_t s2mm_cr;struct sk_buff *skb;struct user_axi_dma *user_axi_dma = container_of(inode->i_cdev, struct user_axi_dma, cdev);unsigned long irqflags = IRQF_SHARED | IRQF_ONESHOT;//若已经被打开则返回busyif(!atomic_dec_and_test(&user_axi_dma->open_flag)){atomic_inc(&user_axi_dma->open_flag);return -EBUSY;}//设置文件私有数据p_file->private_data = user_axi_dma;//复位skbuffwhile(!skb_queue_empty(&user_axi_dma->s2mm_skbuff)){skb = skb_dequeue(&user_axi_dma->s2mm_skbuff);kfree_skb(skb);}//复位DMA控制器iowrite32(XAXIDMA_CR_RESET_MASK, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);//等待复位完成for(count = 0; count < 200; 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;msleep_interruptible(1);}if(count >= 200){atomic_inc(&user_axi_dma->open_flag);printk("reset dma failed\r\n");return result;}//注册中断snprintf(user_axi_dma->mm2s_irq_name, sizeof(user_axi_dma->mm2s_irq_name), "axi-dma%ld-mm2s", user_axi_dma->minor);result = request_threaded_irq(user_axi_dma->mm2s_irq, NULL, mm2s_interrupt, irqflags, user_axi_dma->mm2s_irq_name, user_axi_dma);if(result < 0){atomic_inc(&user_axi_dma->open_flag);printk("request mm2s_irq failed\r\n");return result;}snprintf(user_axi_dma->s2mm_irq_name, sizeof(user_axi_dma->s2mm_irq_name), "axi-dma%ld-s2mm", user_axi_dma->minor);result = request_threaded_irq(user_axi_dma->s2mm_irq, NULL, s2mm_interrupt, irqflags, user_axi_dma->s2mm_irq_name, user_axi_dma);if(result < 0){free_irq(user_axi_dma->mm2s_irq, user_axi_dma);atomic_inc(&user_axi_dma->open_flag);printk("request s2mm_irq failed\r\n");return result;}//使能DMA mm2s中断mm2s_cr = ioread32(user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);iowrite32(mm2s_cr | XAXIDMA_IRQ_ALL_MASK, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);//使能DMA s2mm中断s2mm_cr = ioread32(user_axi_dma->base_addr + 0x00000030 + XAXIDMA_CR_OFFSET);iowrite32(s2mm_cr | XAXIDMA_IRQ_ALL_MASK, user_axi_dma->base_addr + 0x00000030 + XAXIDMA_CR_OFFSET);//启动DMA接收s2mm_transfer(user_axi_dma, user_axi_dma->s2mm_phy, user_axi_dma->buffer_max);return 0;
}static int user_axi_dma_release(struct inode *inode, struct file *p_file)
{int count;uint32_t mm2s_cr;uint32_t s2mm_cr;struct user_axi_dma *user_axi_dma = p_file->private_data;//复位DMA控制器iowrite32(XAXIDMA_CR_RESET_MASK, user_axi_dma->base_addr + 0x00000000 + XAXIDMA_CR_OFFSET);//等待复位完成for(count = 0; count < 200; 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;msleep_interruptible(1);}//注销中断free_irq(user_axi_dma->s2mm_irq, user_axi_dma);free_irq(user_axi_dma->mm2s_irq, user_axi_dma);//恢复原子变量的值atomic_set(&user_axi_dma->open_flag, 1);return 0;
}static ssize_t user_axi_dma_read(struct file *p_file, char __user *buf, size_t len, loff_t *pos)
{struct sk_buff *skb;struct user_axi_dma *user_axi_dma = p_file->private_data;if(skb_queue_empty(&user_axi_dma->s2mm_skbuff)){if(p_file->f_flags & O_NONBLOCK)return -EAGAIN;if(wait_event_interruptible(user_axi_dma->s2mm_wq, !skb_queue_empty(&user_axi_dma->s2mm_skbuff)))return -ERESTARTSYS;}skb = skb_dequeue(&user_axi_dma->s2mm_skbuff);if(!skb) {printk("read failed, s2mm_skbuff is empty.\n");return -EAGAIN;}len = (len > skb->len) ? skb->len : len;if(copy_to_user(buf, skb->data, len)){printk("Failed to copy data to user.\n");kfree_skb(skb);return -EFAULT;}kfree_skb(skb);return len;
}static ssize_t user_axi_dma_write(struct file *p_file, const char __user *buf, size_t len, loff_t *pos)
{int result;struct user_axi_dma *user_axi_dma = p_file->private_data;//单次传输不能超过FIFO大小len = (len > user_axi_dma->buffer_max) ? user_axi_dma->buffer_max : len;//将数据拷贝到发送DMA缓冲区if(copy_from_user(user_axi_dma->mm2s_buffer, buf, len) > 0){printk("copy to mm2s_buffer failed\r\n");return -EFAULT;}//复位传输结果user_axi_dma->mm2s_result = 0;//启动DMA接收mm2s_transfer(user_axi_dma, user_axi_dma->mm2s_phy, user_axi_dma->buffer_max);//等待传输完成result = wait_event_interruptible_timeout(user_axi_dma->mm2s_wq, user_axi_dma->mm2s_result, 100);if(result == 0){printk("mm2s_transfer timeout\r\n");return -ETIMEDOUT;}else if(result < 0){printk("mm2s_transfer break\r\n");return result;}return len;
}static unsigned int user_axi_dma_poll(struct file *p_file, poll_table *wait)
{unsigned int mask = 0;struct user_axi_dma *user_axi_dma = p_file->private_data;poll_wait(p_file, &user_axi_dma->s2mm_wq, wait);if (!skb_queue_empty(&user_axi_dma->s2mm_skbuff))mask |= POLLIN | POLLRDNORM;return mask;
}static long user_axi_dma_ioctl(struct file *p_file, unsigned int cmd, unsigned long arg)
{struct user_axi_dma *user_axi_dma = p_file->private_data;return 0;
}static const struct file_operations user_axi_dma_fops = {.owner = THIS_MODULE,.open = user_axi_dma_open,.release = user_axi_dma_release,.read = user_axi_dma_read,.write = user_axi_dma_write,.poll = user_axi_dma_poll,.unlocked_ioctl = user_axi_dma_ioctl,
};//设备和驱动匹配成功执行
static int user_axi_dma_probe(struct platform_device *pdev)
{int result;struct device *device;struct user_axi_dma *user_axi_dma;char device_driver_name[128];printk("%s\r\n", __FUNCTION__);//设置SPI设备的DMA寻址范围,不然dma_alloc会执行失败pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);//分配设备句柄,采用devm_xxx函数申请资源,后续可自动释放user_axi_dma = devm_kzalloc(&pdev->dev, sizeof(struct user_axi_dma), GFP_KERNEL);if (!user_axi_dma){printk("alloc memory failed\r\n");return -ENOMEM;}memset(user_axi_dma, 0, sizeof(struct user_axi_dma));//设置原子变量为1,表示未执行open操作atomic_set(&user_axi_dma->open_flag, 1);//初始化等待队列user_axi_dma->mm2s_result = 0;init_waitqueue_head(&user_axi_dma->mm2s_wq);init_waitqueue_head(&user_axi_dma->s2mm_wq);//初始化skbuffskb_queue_head_init(&user_axi_dma->s2mm_skbuff);//获取缓冲区最大大小result = of_property_read_u32_index(pdev->dev.of_node, "buffer-max", 0, &user_axi_dma->buffer_max);if(result < 0){printk("get \"buffer-max\" parameter failed\r\n");return result;}//分配mm2s_buffer缓冲帧user_axi_dma->mm2s_buffer = dma_alloc_wc(&pdev->dev, user_axi_dma->buffer_max, &user_axi_dma->mm2s_phy, GFP_KERNEL);if(!user_axi_dma->mm2s_buffer){printk("alloc mm2s_buffer failed\r\n");return -ENOMEM;}memset(user_axi_dma->mm2s_buffer, 0x00, user_axi_dma->buffer_max);//分配s2mm_buffer缓冲帧user_axi_dma->s2mm_buffer = dma_alloc_wc(&pdev->dev, user_axi_dma->buffer_max, &user_axi_dma->s2mm_phy, GFP_KERNEL);if(!user_axi_dma->s2mm_buffer){dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);printk("alloc s2mm_buffer failed\r\n");return -ENOMEM;}memset(user_axi_dma->s2mm_buffer, 0x00, user_axi_dma->buffer_max);//映射寄存器资源user_axi_dma->base_addr = devm_platform_ioremap_resource(pdev, 0);if (IS_ERR(user_axi_dma->base_addr)){dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->s2mm_buffer, user_axi_dma->s2mm_phy);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);printk("ioremap reg failed.\n");return PTR_ERR(user_axi_dma->base_addr);}//获取中断号user_axi_dma->mm2s_irq = of_irq_get(pdev->dev.of_node, 0);if(user_axi_dma->mm2s_irq <= 0){dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->s2mm_buffer, user_axi_dma->s2mm_phy);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);printk("irq mm2s_irq failed");return user_axi_dma->mm2s_irq;}printk("mm2s_irq:%d\r\n", user_axi_dma->mm2s_irq);user_axi_dma->s2mm_irq = of_irq_get(pdev->dev.of_node, 1);if(user_axi_dma->s2mm_irq <= 0){dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->s2mm_buffer, user_axi_dma->s2mm_phy);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);printk("irq s2mm_irq failed");return user_axi_dma->s2mm_irq;}printk("s2mm_irq:%d\r\n", user_axi_dma->s2mm_irq);//分配设备IDresult = ida_simple_get(&user_axi_dma_ida, 0, USER_AXI_DMA_MAX_MINORS, GFP_KERNEL);if(result < 0){dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->s2mm_buffer, user_axi_dma->s2mm_phy);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);printk("Not able to get minor id for device.\n");return result;}//合成设备号user_axi_dma->minor = result;user_axi_dma->devt = MKDEV(MAJOR(user_axi_dma_major), user_axi_dma->minor);//初始化CDEV对象cdev_init(&user_axi_dma->cdev, &user_axi_dma_fops);user_axi_dma->cdev.owner = THIS_MODULE;//向系统添加CDEV对象result = cdev_add(&user_axi_dma->cdev, user_axi_dma->devt, 1);if(result < 0){ida_simple_remove(&user_axi_dma_ida, user_axi_dma->minor);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->s2mm_buffer, user_axi_dma->s2mm_phy);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);printk("chardev registration failed.\n");return result;}printk("device major %d, device minor %d, device file name = user_axi_dma%ld\r\n",MAJOR(user_axi_dma->devt), MINOR(user_axi_dma->devt), user_axi_dma->minor);//创建设备文件snprintf(device_driver_name, sizeof(device_driver_name), "user_axi_dma%ld", user_axi_dma->minor);device = device_create(user_axi_dma_class, NULL, user_axi_dma->devt, NULL, device_driver_name);if(IS_ERR(device)){cdev_del(&user_axi_dma->cdev);ida_simple_remove(&user_axi_dma_ida, user_axi_dma->minor);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->s2mm_buffer, user_axi_dma->s2mm_phy);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);printk("device create failed");return PTR_ERR(device);}//设置平台设备的驱动私有数据dev_set_drvdata(&pdev->dev, user_axi_dma);return 0;
}//设备或驱动卸载时执行
static int user_axi_dma_remove(struct platform_device *pdev)
{struct sk_buff *skb;struct user_axi_dma *user_axi_dma = dev_get_drvdata(&pdev->dev);printk("%s\r\n", __FUNCTION__);if(!user_axi_dma)return 0;//删除设备文件device_destroy(user_axi_dma_class, user_axi_dma->devt);//从内核删除cdev对象cdev_del(&user_axi_dma->cdev);//释放设备IDida_simple_remove(&user_axi_dma_ida, user_axi_dma->minor);//释放DMA缓冲区dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->s2mm_buffer, user_axi_dma->s2mm_phy);dma_free_wc(&pdev->dev, user_axi_dma->buffer_max, user_axi_dma->mm2s_buffer, user_axi_dma->mm2s_phy);//复位skbuffwhile(!skb_queue_empty(&user_axi_dma->s2mm_skbuff)){skb = skb_dequeue(&user_axi_dma->s2mm_skbuff);kfree_skb(skb);}return 0;
}//匹配列表,用于设备树和平台驱动匹配
static const struct of_device_id user_axi_dma_of_match[] = {{ .compatible = "user_axi_dma" },{ /* Sentinel */ }
};
//平台驱动
struct platform_driver user_axi_dma_drv = {.driver = {.name = "user_axi_dma", //平台驱动名称.owner = THIS_MODULE,.pm = NULL,.of_match_table = user_axi_dma_of_match,},.probe = user_axi_dma_probe, //设备和驱动匹配成功执行.remove = user_axi_dma_remove, //设备或驱动卸载时执行
};static int __init plt_drv_init(void)
{int result;printk("%s\r\n", __FUNCTION__);//根据次设备号起始值动态注册字符设备号result = alloc_chrdev_region(&user_axi_dma_major, 0, USER_AXI_DMA_MAX_MINORS, "char-driver-template");if(result < 0){printk("alloc chrdev failed\r\n");return result;}printk("first device major %d, minor %d\r\n", MAJOR(user_axi_dma_major), MINOR(user_axi_dma_major));//创建class对象user_axi_dma_class = class_create(THIS_MODULE, "led,class");if(IS_ERR(user_axi_dma_class)){unregister_chrdev_region(user_axi_dma_major, USER_AXI_DMA_MAX_MINORS);printk("class create failed");return PTR_ERR(user_axi_dma_class);}//注册平台驱动result = platform_driver_register(&user_axi_dma_drv);if(result < 0){class_destroy(user_axi_dma_class);unregister_chrdev_region(user_axi_dma_major, USER_AXI_DMA_MAX_MINORS);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(&user_axi_dma_drv);//销毁class对象class_destroy(user_axi_dma_class);//注销字符设备号unregister_chrdev_region(user_axi_dma_major, USER_AXI_DMA_MAX_MINORS);
}module_init(plt_drv_init);
module_exit(plt_drv_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("axi_dma_driver");
AXI DMA驱动测试程序
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <pthread.h>static int amp_fd;static uint8_t wbuffer[512];
static uint8_t rbuffer[512*2];
static volatile ssize_t read_total = 0, write_total = 0;
static volatile int cnt = 0;void *echo_thread(void *arg)
{int32_t result;ssize_t write_size, read_size;int32_t magic = 0;while(1){//填充数据magic++;for(int i=0; i<sizeof(wbuffer); i++)wbuffer[i] = (uint8_t)(magic + i);//发送数据result = write(amp_fd, wbuffer, sizeof(wbuffer));if(result != sizeof(wbuffer)){printf("write failed\r\n");continue;}write_size = result;write_total += write_size;//读取数据memset(rbuffer, 0, sizeof(rbuffer));result = read(amp_fd, rbuffer, sizeof(rbuffer));if(result != write_size){printf("read failed\r\n");continue;}read_size = result;read_total += read_size;//校验数据是否一致for(int i=0; i<sizeof(wbuffer); i++){if(wbuffer[i] != rbuffer[i]){printf("check out failed\r\n");break;}}cnt++;}
}int main(int argc, char *argv[])
{int err;pthread_t thread;char *rpmsg_dev="/dev/rpmsg0";if(argc > 1)rpmsg_dev = argv[1];printf("amp test\r\n");amp_fd = open(rpmsg_dev, O_RDWR);if(amp_fd < 0){perror("error");return -1;}err = pthread_create(&thread, NULL, echo_thread, NULL);if(err != 0){printf("create thread failed, error code %d\r\n", err);return -1;}err = pthread_detach(thread);if(err != 0){printf("thread detach failed, error code %d\r\n", err);return -1;}while(1){sleep(1);printf("read speed = %fMB/s\r\n", read_total/1.0f/1024.0f/1024.0f);read_total = 0;printf("write speed = %fMB/s\r\n", write_total/1.0f/1024.0f/1024.0f);write_total = 0;printf("echo count = %d\r\n", cnt);cnt = 0;}
}
相关文章:

06 实现自定义AXI DMA驱动
为什么要实现自定义AXI DMA驱动 ZYNQ 的 AXI DMA 在 Direct Register DMA (即 Simple DMA)模式下可以通过 AXIS 总线的 tlast 提前结束传输,同时还可以在 BUFFLEN 寄存器中读取到实际传输的字节数,但是通过 Linux 的 DMA 驱动框架…...

SpringBoot集成ENC对配置文件进行加密
在线MD5生成工具 配置文件加密,集成ENC 引入POM依赖 <!-- ENC配置文件加密 --><dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.2</ver…...

初学stm32 ——— 串口通信
目录 STM32的串口通信接口 UART异步通信方式特点: 串口通信过程 STM32串口异步通信需要定义的参数: USART框图: 常用的串口相关寄存器 串口操作相关库函数 编辑 串口配置的一般步骤 STM32的串口通信接口 UART:通用异步收发器USART&am…...

qwt 多Y轴 项目效果
项目场景: 在做一个半导体上位机软件项目实践中,需要做一个曲线展示和分析界面,上位机主题是用qt框架来开发,考虑到目前qt框架的两种图标库,一个是qcustomplot 一个是 qwt。之所以采用qwt ,根本原因是因为…...

Java中通过ArrayList扩展数组
在Java中,ArrayList 是一个动态数组实现,能够根据需要自动调整其大小。与传统的数组不同,ArrayList 不需要预先指定大小,并且提供了许多方便的方法来操作集合中的元素。下面将详细介绍如何使用 ArrayList 进行数组的扩展ÿ…...

Java:链接redis报错:NoSuchElementException: Unable to validate object
目录 前言报错信息排查1、确认redis密码设置是否有效2、确认程序配置文件,是否配置了正确的redis登录密码3、检测是否是redis持久化的问题4、确认程序读取到的redis密码没有乱码 原因解决 前言 一个已经上线的项目,生产环境的redis居然没有设置密码&…...

datasets库之load_dataset
目录 问题解决方案 问题 使用peft用lora微调blip2时用到了一个足球数据集,如下: 原始代码如下 dataset load_dataset("ybelkada/football-dataset", split"train")然而这需要梯子才能下载,服务器较难用VPN所以使用au…...

React Router常见面试题目
1. React Router 支持哪几种模式? React Router 支持以下两种主要模式: BrowserRouter (基于 HTML5 History API 的模式) 原理: 利用 history.pushState 和 history.replaceState 操作浏览器历史栈,无需重新加载页面。URL 看起来像传统 URL…...

sequelize-cli 封装登录接口
node ORM (sequelize)使用、查询、验证及express 基础框架的搭建及实例的使用 一、思路 第一步:肯定是用户要向接口传递邮箱、账号和密码了。 第二步:接口这边,先要验证。因为这里不是往数据库里存储数据,…...

使用 Elasticsearch 查询和数据同步的实现方法
在开发过程中,将数据从数据库同步到 Elasticsearch (ES) 是常见的需求之一。本文将重点介绍如何通过 Python 脚本将数据库中的数据插入或更新到 Elasticsearch,并基于多字段的唯一性来判断是否执行插入或更新操作。此外,我们还将深入探讨如何…...

QTday1作业设置简易登录界面
代码 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//创建一个标签QLabel *lab1 new QLabel(this);//重新设置大小lab1->resize(1925,1080);//用动图类QMovie实例化一个动图QMovie *mv new QMovie("C:\\Users\\MR\\Deskto…...

RC低通滤波器和LR低通滤波器,LC低通滤波器该怎么选择
这是RC低通滤波器利用电容两端的电压不能突变可以滤除高频噪声 这是LR低通滤波器利用流过电感的电流不能突变也可以滤除高频噪声 那么问题来了两个低通滤波器,该怎么选择呢还是随便选一个就好? RC电路:因为电流电阻会发热耗能,所…...

芯旺微KF32A156芯片ADC配置
使用芯旺微KF32A156的芯片做预研项目,使用了ADC0外设,根据芯片规格书中的描述进行了配置: /*** brief: Configure ADC.** param[in] none.* param[in] none.* param[out] None* retval: None*/ static void adc_peripheral_init(void) …...

【Token】校验、会话技术、登录请求、拦截器【期末实训】实战项目学生和班级管理系统\Day15-后端Web实战(登录认证)\讲义
登录认证 在前面的课程中,我们已经实现了部门管理、员工管理的基本功能,但是大家会发现,我们并没有登录,就直接访问到了Tlias智能学习辅助系统的后台。 这是不安全的,所以我们今天的主题就是登录认证。 最终我们要实现…...

[Unity Shader] 【游戏开发】【图形渲染】Shader数学基础3:矢量与标量的乘法与除法详解
在计算机图形学和Shader编程中,矢量(Vector)运算是非常基础的数学知识。其中,矢量与标量(Scalar)的乘法与除法是常见的操作。本篇文章将通过详细分析,以及实例讲解矢量和标量的乘除法运算,帮助大家更好地理解并应用到实际开发中。 什么是矢量与标量? 在开始具体运算之…...

javalock(四)AQS派生类之Semphore逐行注释
简单概括: Semphore是一把共享锁(即读锁),即实现了AQS的tryAcquireShared&&tryReleaseShared函数Semphore的逻辑是这样: 创建semphore的时候会初始化一个锁容量即permits,即最多同时允许permits个…...

【C语言】头文件”“和<>的详解
前言 作者在刚开始学C语言的时候,都是用的< >去引用头文件,但在学习STM32的时候发现,程序中大量使用" "去引用双引号。 那么二者有什么区别呢? 无论使用哪种方式,头文件的目的都是为了引用你需要的文件供你编程使…...

Elasticsearch:什么是信息检索?
信息检索定义 信息检索 (IR) 是一种有助于从大量非结构化或半结构化数据中有效、高效地检索相关信息的过程。信息(IR)检索系统有助于搜索、定位和呈现与用户的搜索查询或信息需求相匹配的信息。 作为信息访问的主要形式,信息检索是每天使用…...

Spark-Streaming容错语义
一、背景 为了理解Spark Streaming提供的语义,我们先回顾西Spark RDD的基本容错语义学。 RDD是一个不可变的、确定性可重新计算的分布式数据集。每个RDD都记住在容错输入数据集上用于创建它的确定性操作的沿袭。如果RDD的任何分区由于工作节点故障而丢失ÿ…...

2024年12月陪玩系统-仿东郊到家约玩系统是一种新兴的线上预约线下社交、陪伴系统分享-优雅草央千澈-附带搭建教程
2024年12月陪玩系统-仿东郊到家约玩系统是一种新兴的线上预约线下社交、陪伴系统分享-优雅草央千澈-附带搭建教程 产品介绍 仿东郊到家约玩系统是一种新兴的线上预约,线下社交、陪伴、助娱、助攻、分享、解答、指导等服务模式,范围涉及电竞、运动、音乐…...

GUI07-学工具栏,懂MVC
MVC模式,是天底下编写GUI程序最为经典、实效的一种软件架构模式。当一个人学完菜单栏、开始学习工具栏时,就是他的一生中,最适合开始认识 MVC 模式的好时机之一。这节将安排您学习: Model-View-Controller 模式如何创建工具栏以及…...

【进程篇】04.进程的状态与优先级
一、进程的状态 1.1 进程的状态 1.1.1 并行与并发 • 并行: 多个进程在多个CPU下分别,同时进行运行 • 并发: 多个进程在一个CPU下采用进程切换的方式,在一个时间片内,让多个进程都得以推进 1.1.2 时间片的概念 LInux/windows这些民用级别…...

ElasticSearch 数据聚合与运算
1、数据聚合 聚合(aggregations)可以让我们极其方便的实现数据的统计、分析和运算。实现这些统计功能的比数据库的 SQL 要方便的多,而且查询速度非常快,可以实现近实时搜索效果。 注意: 参加聚合的字段必须是 keywor…...

科研学习|论文解读——智能体最新研究进展
从2024-12-13到2024-12-18的45篇文章中精选出5篇优秀的工作分享 Can Modern LLMs Act as Agent Cores in Radiology~Environments? Achieving Collective Welfare in Multi-Agent Reinforcement Learning via Suggestion Sharing A systematic review of norm emergence in …...

面试小札:Java后端闪电五连鞭_8
1. Kafka消息模型及其组成部分 - 消息(Message):是Kafka中最基本的数据单元。消息包含一个键(key)、一个值(value)和一个时间戳(timestamp)。键可以用于对消息进行分区等…...

java error(2)保存时间带时分秒,回显时分秒变成00:00:00
超简单,顺带记录一下 1.入参实体类上使用注释:JsonFormat(pattern “yyyy-MM-dd”) 导致舍弃了 时分秒的部分。 2.数据库字段对应的类型是 date。date就是日期,日期就不带时分秒。 3.返参实体类使用了JsonFormat(pattern “yyyy-MM-dd”) 导…...

计算机毕业设计python+spark+hive动漫推荐系统 漫画推荐系统 漫画分析可视化大屏 漫画爬虫 漫画推荐系统 漫画爬虫 知识图谱 大数据毕设
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

数字IC后端设计实现篇之TSMC 12nm TCD cell(Dummy TCD Cell)应该怎么加?
TSMC 12nm A72项目我们需要按照foundary的要求提前在floorplan阶段加好TCD Cell。这个cell是用来做工艺校准的。这个dummy TCD Cell也可以等后续Calibre 插dummy自动插。但咱们项目要求提前在floorplan阶段就先预先规划好位置。 TSCM12nm 1P9M的metal stack结构图如下图所示。…...

(8)YOLOv6算法基本原理
一、YOLOv6 模型原理 发布日期:2022年6月 作者:美团技术团队 骨干网络:参考了 RepVGG 的设计,将重参数化能力进行补强,增强了模型结构的重参数化能力。使用了深度可分离卷积和跨阶段连接等技术,旨在提升…...

LNMP+discuz论坛
0.准备 文章目录 0.准备1.nginx2.mysql2.1 mysql82.2 mysql5.7 3.php4.测试php访问mysql5.部署 Discuz6.其他 yum源: # 没有wget,用这个 # curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo[rootlocalhost ~]#…...