基于ZYNQ7000的AD9226采集卡实现(3、PS LINUX DMA驱动实现)
本章节实现第三个小目标
实现PS侧DMA驱动,提供mmap和sysfs给应用程序提供接口
DMA初始化
此驱动为platform设备驱动,设备树和驱动匹配后执行probe函数,probe函数中进行DMA初始化等操作
/** @brief : fsdma2_rx_init dma初始化* @param : p_fdma fdma指针* @return: ...*/
int fsdma_rx_init(struct fan_sdma_rx *p_fsdma)
{struct device *p_dev;p_dev = p_fsdma->fdev.p_dev;/* 创建信号量 */sema_init(&p_fsdma->sem_rx, 0);p_fsdma->frm_len = p_fsdma->config[RX_SDMA_FRAME_LEN];p_fsdma->frm_num = p_fsdma->config[RX_SDMA_FRAME_NUM];p_fsdma->buf_size = p_fsdma->frm_len * p_fsdma->frm_num;/* 简易DMA,分配报文buffer空间 */p_fsdma->p_buffer = dma_alloc_coherent(p_dev, p_fsdma->buf_size, &p_fsdma->phy_buffer, GFP_KERNEL);if (p_fsdma->p_buffer == NULL) {dev_info(p_dev, "sdma buffer alloc failed, size=(0x%x x 0x%x)\n", p_fsdma->frm_len, p_fsdma->frm_num);return -EBUSY; // 不释放pDma->pInfo, 由fdmaDelete()处理}memset(p_fsdma->p_buffer, 0, p_fsdma->buf_size); //初始化值为0dev_info(p_dev, "sdma buffer addr=%px, buf_phy=0x%zx, bufSize=0x%08x\n", \p_fsdma->p_buffer, (size_t)p_fsdma->phy_buffer, p_fsdma->buf_size);/* 配置简易DMA */p_fsdma->p_reg->sdma_frm_addr_reg = p_fsdma->phy_buffer;p_fsdma->p_reg->sdma_frm_len_reg = p_fsdma->frm_len;p_fsdma->p_reg->sdma_frm_num_reg = p_fsdma->frm_num;return 0;
}
提供给应用的接口
read接口获取环形缓冲区写指针的位置,产生接受报文中断后fpga_idx会更新,并再中断处理函数中释放sem_rx信号量。
static irqreturn_t fsdma_rx_isr(int irq, void *dev_id)
{struct fan_sdma_rx *p_fsdma = (struct fan_sdma_rx *)dev_id;p_fsdma->fpga_idx = p_fsdma->p_reg->sdma_frm_w_point_reg;p_fsdma->irq_cnt++;up(&p_fsdma->sem_rx);return IRQ_HANDLED;
}ssize_t fsdma_rx_dev_read(struct file *file, \char __user *buf, size_t cnt, loff_t *ppos)
{struct fan_sdma_rx *p_fsdma;u32 temp_buf[1];p_fsdma = (struct fan_sdma_rx *)file->private_data;down_timeout(&p_fsdma->sem_rx, msecs_to_jiffies(1000));if (cnt >= sizeof(u32)) {temp_buf[0] = p_fsdma->fpga_idx;return copy_to_user(buf, (void *)temp_buf, sizeof(u32)) ? -EFAULT : sizeof(u32);} elsereturn 0;
}
mmap用于映射DMA缓存到应用程序虚拟地址,实现高效数据访问
int fsdma_rx_dev_mmap(struct file *file, struct vm_area_struct *vma)
{struct fan_sdma_rx *p_fsdma;p_fsdma = (struct fan_sdma_rx *)file->private_data;vma->vm_flags |= VM_IO | VM_SHARED;/* !!!物理内存和用户空间的虚拟内存之间存在cache,必须加nocache标记,否则应用空间数据修改会刷新不及时 */vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); //赋nocache标志if (remap_pfn_range(vma, //虚拟内存区域,即设备地址将要映射到这vma->vm_start, //虚拟空间的起始地址p_fsdma->phy_buffer>>PAGE_SHIFT, //与物理内存对应的页帧号,物理地址右移12使vma->vm_end - vma->vm_start, //映射区域大小,一般是页大小的整数倿vma->vm_page_prot) //保护属性) {dev_info(p_fsdma->fdev.p_dev, "remap_pfn_range failed\n");return -EAGAIN;}return 0;
}
sysfs文件导出DMA属性到用户空间,应用程序直接读写属性文件来控制DMA,通过sysfs提供DMA使能接口如下:
static ssize_t sysfs_dma_enable_show(struct device *dev, struct device_attribute *attr, char *m)
{struct fan_dev *p_fdev = dev_get_drvdata(dev);return sprintf(m, "%d", FDEV_CSR_READ(p_fdev, FREG_DMA_RX, SDMA_EN_REG));
}static ssize_t sysfs_dma_enable_set(struct device *dev, struct device_attribute *attr,const char *m, size_t siz)
{long intv;struct fan_dev *p_fdev = dev_get_drvdata(dev);if (!siz || !sscanf(m, "%ld", &intv))return -EINVAL;FDEV_CSR_WRITE(p_fdev, FREG_DMA_RX, SDMA_EN_REG, intv ? ENABLE : DISABLE);return siz;
}static DEVICE_ATTR(dma_enable, S_IWUSR | S_IRUGO, sysfs_dma_enable_show, sysfs_dma_enable_set);
提供debugfs接口查看调试信息,调试信息包括DMA帧长,帧数量,写指针位置等
/** @brief : fsdma_rx_show 打印dma rx信息* @param : m ...* @param : p_fdma fdma指针* @return: ...*/
void fsdma_rx_show(struct fan_sdma_rx *p_fsdma, struct seq_file *m)
{seq_printf(m, "\nRX SDMA INFORMATION\n");seq_printf(m, "\tp_buffer=%px, fpga_idx=%zx\n", p_fsdma->p_buffer, p_fsdma->phy_buffer);seq_printf(m, "\topened=%d, fpga_idx=%u, irq_cnt=%u\n", p_fsdma->fdev.opened, p_fsdma->fpga_idx, p_fsdma->irq_cnt);seq_printf(m, "\tframe_number=%u, frame_length=%u, buffer_size=%u(byte)\n", p_fsdma->config[RX_SDMA_FRAME_NUM], \p_fsdma->config[RX_SDMA_FRAME_LEN], p_fsdma->buf_size);seq_printf(m, "\titem_num=%u, timeout=%u(us), period=%u(us)\n", p_fsdma->config[RX_SDMA_ITEM_NUM], \p_fsdma->config[RX_SDMA_TIME_OUT], p_fsdma->config[RX_SDMA_PERIOD]);seq_printf(m, "\n\tstate=%u, frm_w_point=%u, frm_total_cnt=%u, frm_err_cnt=%u\n", p_fsdma->p_reg->sdma_state_reg, \p_fsdma->p_reg->sdma_frm_w_point_reg, \p_fsdma->p_reg->sdma_frm_total_count_reg, \p_fsdma->p_reg->sdma_frm_err_count_reg);seq_printf(m, "\tcmd_w_point=%u, cmd_total_count=%u\n", p_fsdma->p_reg->sdma_cmd_w_point_reg, \p_fsdma->p_reg->sdma_cmd_total_count_reg);
}