高速大容量数据采集方案是一个可实现是连续采集数据率超过10MB/s的应用方案,方案的硬件由支持PCIE高速接口的Linux工控主板(ESM7100、ESM8100等)和基于FPGA的扩展模块组成,软件则包括符合Linux DMA Engine架构的DMA Controller驱动、DMA Client驱动及应用程序三个部分。方案的总体介绍请参考《基于PCIE接口的高速大容量数据采集—总体方案》一文。本文将重点介绍针对Xilinx的< DMA/Bridge Subsystem for PCI Express v4.1> FPGA IP核的特性,实现Linux DMA Engine架构驱动程序,并为应用程序提供简洁方便的API函数,来控制数据采集过程并适时处理已在系统内存中的实时采集数据流。
目前Xilinx公司为其IP核DMA/Bridge Subsystem for PCI Express v4.1,仅提供基于x86体系的驱动,而没有在Linux DMA Engine架构上做工作。而事实上,DMA Engine架构已成为ARM嵌入式Linux平台的DMA应用的事实标准(de facto),因此本方案针对高速大容量数据采集的需求,构建了DMA Engine架构的驱动程序,包括通用DMA Controller驱动和面向应用的DMA Client驱动,应用程序通过标准的字符型设备节点,操作DMA Client驱动,从而实现所需的数据采集。图1是从软件开发角度来看的总体功能框图。
图1 方案总体功能框图
Linux DMA Engine架构的基本思路是把不同的DMA控制器封装为统一的API接口,供面向应用的DMA Client调用。在本方案中,DMA Client驱动一方面通过DMA Engine的API函数操作PCIE端点的DMA功能,另一方面还要通过PCIE接口控制前端的数据采集逻辑。DMA Client驱动对上层的用户应用程序,开放了一组标准的字符设备(char dev)API,供User App操作,在本方案中字符设备为“/dev/pcie0”。为了减少数据的搬动,采集数据缓冲区通常以ping-pong buffer的结构配置在Linux的保留存储器(reserved memory)中,且用户应用程序可通过mmap获得ping-pong buffer指针,直接处理DMA传上来的数据。Ping-pong buffer让应用程序和DMA交替操作数据buffer,如应用程序处理buffer 0#的数据时,DMA把新数据传送到buffer 1#,然后按固定时间周期ping-pong切换buffer,从而实现连续的实时数据采集和处理。字符设备“/dev/pcie0”仅传递采集硬件的控制与状态信息。
DMA Engine API
DMA Engine架构为不同的DMA模式提供不同的API函数,其中最主要的是单次DMA和周期DMA两种,其API函数分别为:
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( struct dma_chan *chan, struct scatterlist *sgl, unsigned int sg_len, enum dma_data_direction direction, unsigned long flags); struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic( struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len, size_t period_len, enum dma_data_direction direction);
DMA Controller驱动要求DMA支持Scatter-gather结构的非连续数据Buffer,但在本方案的应用中,对单次DMA情形,采用单个Buffer是最常见的应用方式,这时可采用DMAEngine的简化函数:
struct dma_async_tx_descriptor *dmaengine_prep_slave_singl( struct dma_chan *chan, dma_addr_t buf, size_t len, enum dma_data_direction direction, unsigned long flags);
Cyclic DMA模式,是把多个DMA Buffer通过其描述符(dma descriptor)表连接成环状,当一个buffer的DMA传送结束后,驱动程序的中断线程将自动启动面向下一个描述符的DMA Buffer。由DMA descriptor表描述的逻辑流程如图2所示:
图2 Cyclic DMA逻辑流程
本方案的DMA Controller驱动实现了上述两种DMA传输方式,即单次DMA传输和周期DMA传输。DMA Controller驱动本质上讲,是一种通用的DMA服务器,如何使用DMA的传输功能,实现具体的数据传输任务,则是由DMA Client来决定的。Linux把DMA服务与具体应用分成两个部分,有利于DMA Controller驱动面向不同的应用场景。
DMA Client驱动
DMA Client驱动是一个面向应用的驱动,如图1所示,它需要与User Space的上层应用程序配合运行,来完成所需的数据采集与处理。
单次DMA的操作如下所示。
/* prepare a single buffer dma */ desc = dmaengine_prep_slave_single(dchan, edev->dma_phys, edev->total_len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc) { dev_err(edev->dev, "dmaengine_prep_slave_single(..) failed\n"); ret = -ENODEV; goto error_out; } /* setup dtaker hardware */ eta750_dtaker_setup(edev); /* put callback, and submit dma */ desc->callback = dma_callback; desc->callback_param = edev; edev->cookie = dmaengine_submit(desc); ret = dma_submit_error(edev->cookie); if (ret) { dev_err(edev->dev, "DMA submit failed %d\n", ret); goto error_submit; } /* init complete, and fire */ reinit_completion(&edev->xdma_chan_complete); dma_async_issue_pending(dchan); /* simulate input data */ eta750_dtaker_run(edev); /* wait dma complete */ count = wait_for_completion_timeout(&edev->xdma_chan_complete, msecs_to_jiffies(DMA_TIMEOUT)); if (count == 0) { dev_err(edev->dev, "wait_for_completion_timeout timeout\n"); ret = -ETIMEDOUT; eta750_dtaker_end(edev); goto error_submit; } /* error processing */ eta750_dtaker_error_pro(edev); /* stop front-end daq unit */ count = eta750_dtaker_end(edev); /* dump data */ eta750_dtaker_dump_data(edev); return edev->total_len; error_submit: dmaengine_terminate_all(dchan); error_out: return ret;
只有周期DMA方式才能实现连续数据采集,在DMA Client中采用双DMA Buffer的乒乓结构来实现连续采集,应用程序处理0# Buffer数据时,DMA传输数据至1# Buffer,传输结束时,进行切换,应用程序处理1# Buffer数据,DMA传输新数据至0# Buffer。周期DMA需要指定每个buffer的长度period_len,同时需指定由2个buffer构成的ping-pong buffer的总长度total_len。其DMA流程如下所示。
/* prepare cyclic buffer dma */ desc = dmaengine_prep_dma_cyclic(dchan, edev->dma_phys, edev->total_len, edev->period_len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); if (!desc) { dev_err(edev->dev, "%s: prep dma cyclic failed!\n", __func__); ret = -EINVAL; goto error_out; } /* in cyclic mode */ edev->cyclic = true; /* setup dtaker hardware */ eta750_dtaker_setup(edev); /* put callback, and submit dma */ desc->callback = dma_callback; desc->callback_param = edev; edev->cookie = dmaengine_submit(desc); ret = dma_submit_error(edev->cookie); if (ret) { dev_err(edev->dev, "cyclic dma submit failed %d\n", ret); goto error_submit; } /* init complete, and fire */ reinit_completion(&edev->xdma_chan_complete); dma_async_issue_pending(dchan); edev->running = true; /* simulate input data */ eta750_dtaker_run(edev); edev->data_seed += ((edev->period_len / sizeof(u16)) * edev->data_incr); while(!kthread_should_stop()) { /* wait dma complete */ count = wait_for_completion_timeout(&edev->xdma_chan_complete, msecs_to_jiffies(DMA_TIMEOUT)); if (count == 0) { dev_err(edev->dev, "wait_for_completion timeout, transfer %d\n", edev->transfer_count); ret = -ETIMEDOUT; break; } /* data processing */ eta750_dtaker_error_pro(edev); edev->transfer_count++; reinit_completion(&edev->xdma_chan_complete); /* fill more data */ eta750_dtaker_run(edev); edev->data_seed += ((edev->period_len / sizeof(u16)) * edev->data_incr); } /* stop front-end daq unit */ count = eta750_dtaker_end(edev); edev->running = false; error_submit: dmaengine_terminate_all(dchan); edev->cyclic = false; dev_info(edev->dev, "%s: dma stopped, cyclic %d, running %d\n", __func__, edev->cyclic, edev->running); error_out: return ret;
从上面代码可见,传送过程是一个无限循环,DMA Controller驱动会自动进行ping-pong buffer的切换。并通过回调函数通知上层应用程序,新数据已准备就绪。应用程序可通过命令来终止采集传输过程。
DTaker前端采集逻辑
前端数据采集单元DTaker,包括在FPGA芯片XC7A50T-2CSG325中,主要是实现对AD芯片AD7606C的操作,并把数据写入FPGA中的FIFO缓冲区中。从图3可看到作为User Logic的DTaker与FIFO的相互关系。
图3 DTaker功能框图
从programming角度看,DTaker就是一组寄存器。代码通过操作实现:(1)启动AD转换;(2)读取AD数据,并按照一定格式把packed的数据写入FIFO。具体说,DTaker是由8个32-bit寄存器及相关数字逻辑构成的可编程控制单元,寄存器的定义如下。
Offset | 寄存器名称 | 功能简述 |
0x00 | CONTROL | 控制寄存器 |
0x04 | STATUS | 状态寄存器 |
0x08 | PKG_SIZE | 采集数据长度寄存器 |
0x0C | DAQ_CFG | 前端采集控制寄存器 |
0x10 | EVENT_CFG | 前端采集触发事件配置寄存器 |
0x14 | - | 未定义 |
0x18 | CPU_DAT2 | CPU仿真测试数据 |
0x1C | CPU_DAT3 | CPU仿真测试数据 |
各个寄存器功能定义如下:
DTaker控制寄存器 CONTROL各bit定义如下:
D[3 : 0] = LEDOUT[3 : 0]
D4 = EVENT_IRQEN,置1使能DTAKER触发中断,事件=某种触发条件
D5 = OVERFLOW_IRQEN,置1使能FIFO写溢出中断
D6 = TIMEOUT_IRQEN,置1使能C2H接口超时中断,暂不用
D7 = DMAEN,置1使能AXIS transfer
D8 = DAQEN,= 0:选择CPU仿真数据;= 1: 选择前端AD数据
D9 = FIFO_RESET,置1清FIFO,软件清零控制位
D[31 : 10] = 暂时未用
状态寄存器STATUS,各bit状态标志RO/W1C
D[1 : 0] = KEY_IN[1:0]
D2 = EVENT_FLAG,事件标志,W1C
D3 = OVERFLOW_ERR,FIFO写溢出错误标志,W1C
D4 = EVENT_IRQ,事件中断标志,W1C
D5 = OVERFLOW_IRQ,FIFO写溢出错误中断标志,W1C
D6 = TIMEOUT_IRQ,C2H接口超时中断标志,W1C
D7 = DEBUG_BIT,调试用,根据情况临时定义具体内容
D[17 : 8] = 10’b0000000000,暂时未用
D18 = FIFO_FULL,=1: FIFO 已满
D19 = FIFO_EMPTY,=1: FIFO 已空
D[22 : 20] = EVENT_CODE[2 : 0],事件编码信息
D23 = 1’b0,暂时未用
D[31 : 24] = Version Info, RO
DMA传送长度寄存器PKG_SIZE,字节为单位,需16字节整倍数。每完成一次transfer,PKG_SIZE -= 8字节,直至PKG_SIZE = 0;PKG_SIZE = 8时,表示后续是最后一次transfer,TLAST = 1。
数据采集配置寄存器DAQ_CFG,用于定义采集的通道、采样率等参数。
DAQ_CFG.D0 = RUN,置1启动前端硬件数据采集。
触发事件配置寄存器EVENT_CFG,用于定义采集单元的触发事件条件,如触发通道、电平、触发沿,触发采集长度等参数。
仿真数据寄存器CPU_DAT2。
仿真数据寄存器CPU_DAT3,与CPU_DAT2构成一次64-bit的raw_data。由fifo_writer状态机写入XPM_FIFO_ASYNC。
应用程序
从应用程序的角度看,程序的基本架构与《精简ISA总线实现高速大容量数据采集》一文的描述是一样的,其要点包括:
1、使用预先划定的Linux保留存储区域作为采集数据buffer,应用程序通过mmap获得buffer指针。
2、打开设备文件(节点名称:“/dev/pcie0”),通过write函数设置启动数据采集过程,在接收线程通过poll函数等待驱动通知数据就绪信息。
3、Write函数写入pcie_dma_info数据结构,定义如下:
struct pcie_dma_info { dma_addr_t phys; /* dma buffer start addr, */ /* = 0: internal buffer for testing */ unsigned long size; /* total buffer size in byte */ int period; /* = 1: single dma, > 1: cyclic dma */ int count; /* only for cyclic dma, = 0: free run, */ /* > 0: number of period buffer after */ /* event happened */ u32 daq_config; /* data acquisition config register in */ /* dtaker IP */ u32 event_config; /* event config register in dtaker IP */ };
4、由数据就绪信息触发对采集数据buffer的相应处理。
对本方案有实际应用需求的客户,可与英创公司技术支持联系,以了解进一步的技术细节,进行实际的评估应用。
成都英创信息技术有限公司 028-8618 0660