为什么要实现自定义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;
//设备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