Linux DMA Engine驱动解析:FPGA PCIE接口

摘要

英创嵌入式主板,如ESM7000系列、ESM8000系列等,均可配置标准的PCIE×1高速接口。连接NVMe模块作高速大容量数据存储、连接多通道高速网络接口模块都是PCIE接口的典型应用。此外,对于工控领域中的高速数据采集,还可采用FPGA的PCIE IP核实现PCIE EP端点,与英创嵌入式主板构成高效低成本的应用方案。本文简要介绍方案硬件配置,以及PCIE在Linux平台上的驱动程序实现。

硬件设计要点

Xilinx公司为它的FPGA设计有多种PCIE EP端点的IP核,针对本文的应用需求,选择DMA/Bridge Subsystem for PCI Express v4.1(简称PCIE/XDMA)。PCIE/XDMA在硬件上把PCIE接口转换为AXI-Stream高速并行接口(简称AXIS),工控前端逻辑只需把采集数据转换成AXI-Stream格式提供给AXIS通道。IP核会采用PCIE总线的DMA机制,把AXIS通道数据按数据块的形式直接传送至Linux的内存中,这样在Linux的应用程序就可直接处理采集数据了。Xilinx Artix 7系的低成本芯片XC7A35T、XC7A50T均可容纳PCIE/XDMA IP核,这样可保证应用方案的成本处于合理的范围。

图1 ETA750 Block Design Diagram

 

图1中的实例xdma_0是Xilinx公司的PCIE/DMA IP模块,作为PCIE端点设备(PCIE Endpoint Device)。Dtaker1_5_0是应用相关的前端逻辑。对PCIE的主要配置如下图所示:

图2 PCIE接口配置

 

上述配置定义的AXIS总线为64-bit数据宽度、总线时钟62.5MHz(ACLK)。

AXIS总线典型的握手时序如图3所示,一个数据传输周期最快需要3个ACLK,T3上升沿为数据锁存时刻:

图3  AXI同步握手时序

 

若前端逻辑每4个ACLK产生一个dword数据,则对应的数据速率就是125MB/s。

图4 PCIE中断配置

 

基于XC7A50T的PCIE/DMA IP可支持最多4路DMA通道,分别为2路发送(H2C通道)和2路接收(C2H通道),加上用户前端逻辑中断,共有至少5个中断源。采用PCIE的MSI中断机制是解决多中断源的最好方式,所以配置8个中断矢量,实际使用5个。

DMA Engine驱动

目前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驱动,从而实现所需的数据采集。图5是从软件开发角度来看的总体功能框图。

图5 方案总体功能框图

 

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所示:

图6 Cyclic DMA逻辑流程

 

本方案的DMA Controller驱动实现了上述两种DMA传输方式,即单次DMA传输和周期DMA传输。DMA Controller驱动本质上讲,是一种通用的DMA服务器,如何使用DMA的传输功能,实现具体的数据传输任务,则是由DMA Client来决定的。Linux把DMA服务与具体应用分成两个部分,有利于DMA Controller驱动面向不同的应用场景。

DMA Client驱动

DMA Client驱动是一个面向应用的驱动,如图5所示,它需要与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的切换。并通过回调函数通知上层应用程序,新数据已准备就绪。应用程序可通过命令来终止采集传输过程。

物联沃分享整理
物联沃-IOTWORD物联网 » Linux DMA Engine驱动解析:FPGA PCIE接口

发表评论