当前位置:   article > 正文

FPGA PCIE接口的Linux DMA Engine驱动_fpga数据采集卡pcie驱动

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函数分别为:

  1. struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
  2.            struct dma_chan *chan, struct scatterlist *sgl,
  3.            unsigned int sg_len, enum dma_data_direction direction,
  4.            unsigned long flags);
  5. struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
  6.            struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
  7.            size_t period_len, enum dma_data_direction direction);

DMA Controller驱动要求DMA支持Scatter-gather结构的非连续数据Buffer,但在本方案的应用中,对单次DMA情形,采用单个Buffer是最常见的应用方式,这时可采用DMAEngine的简化函数:

  1. struct dma_async_tx_descriptor *dmaengine_prep_slave_singl(
  2.            struct dma_chan *chan, dma_addr_t buf, size_t len,
  3.            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的操作如下所示。

  1. /* prepare a single buffer dma */
  2. desc = dmaengine_prep_slave_single(dchan, edev->dma_phys,
  3.               edev->total_len, DMA_DEV_TO_MEM,
  4.               DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
  5. if (!desc) {
  6.     dev_err(edev->dev, "dmaengine_prep_slave_single(..) failed\n");
  7.     ret = -ENODEV;
  8.     goto error_out;
  9. }
  10. /* setup dtaker hardware */
  11. eta750_dtaker_setup(edev);
  12. /* put callback, and submit dma */
  13. desc->callback = dma_callback;
  14. desc->callback_param = edev;
  15. edev->cookie = dmaengine_submit(desc);
  16. ret = dma_submit_error(edev->cookie);
  17. if (ret) {
  18. dev_err(edev->dev, "DMA submit failed %d\n", ret);
  19. goto error_submit;
  20. }
  21. /* init complete, and fire */
  22. reinit_completion(&edev->xdma_chan_complete);
  23. dma_async_issue_pending(dchan);
  24. /* simulate input data */
  25. eta750_dtaker_run(edev);
  26. /* wait dma complete */
  27. count = wait_for_completion_timeout(&edev->xdma_chan_complete, msecs_to_jiffies(DMA_TIMEOUT));
  28. if (count == 0) {
  29. dev_err(edev->dev, "wait_for_completion_timeout timeout\n");
  30. ret = -ETIMEDOUT;
  31. eta750_dtaker_end(edev);
  32. goto error_submit;
  33. }
  34. /* error processing */
  35. eta750_dtaker_error_pro(edev);
  36. /* stop front-end daq unit */
  37. count = eta750_dtaker_end(edev);
  38. /* dump data */
  39. eta750_dtaker_dump_data(edev);
  40. return edev->total_len;
  41. error_submit:
  42. dmaengine_terminate_all(dchan);
  43. error_out:
  44. 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流程如下所示。

  1. /* prepare cyclic buffer dma */
  2. desc = dmaengine_prep_dma_cyclic(dchan, edev->dma_phys, edev->total_len,
  3. edev->period_len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
  4. if (!desc) {
  5. dev_err(edev->dev, "%s: prep dma cyclic failed!\n", __func__);
  6. ret = -EINVAL;
  7. goto error_out;
  8. }
  9. /* in cyclic mode */
  10. edev->cyclic = true;
  11. /* setup dtaker hardware */
  12. eta750_dtaker_setup(edev);
  13. /* put callback, and submit dma */
  14. desc->callback = dma_callback;
  15. desc->callback_param = edev;
  16. edev->cookie = dmaengine_submit(desc);
  17. ret = dma_submit_error(edev->cookie);
  18. if (ret) {
  19. dev_err(edev->dev, "cyclic dma submit failed %d\n", ret);
  20. goto error_submit;
  21. }
  22. /* init complete, and fire */
  23. reinit_completion(&edev->xdma_chan_complete);
  24. dma_async_issue_pending(dchan);
  25. edev->running = true;
  26. /* simulate input data */
  27. eta750_dtaker_run(edev);
  28. edev->data_seed += ((edev->period_len / sizeof(u16)) * edev->data_incr);
  29. while(!kthread_should_stop()) {
  30. /* wait dma complete */
  31. count = wait_for_completion_timeout(&edev->xdma_chan_complete,
  32. msecs_to_jiffies(DMA_TIMEOUT));
  33. if (count == 0) {
  34. dev_err(edev->dev, "wait_for_completion timeout, transfer %d\n", edev->transfer_count);
  35. ret = -ETIMEDOUT;
  36. break;
  37. }
  38. /* data processing */
  39. eta750_dtaker_error_pro(edev);
  40. edev->transfer_count++;
  41. reinit_completion(&edev->xdma_chan_complete);
  42. /* fill more data */
  43. eta750_dtaker_run(edev);
  44. edev->data_seed += ((edev->period_len / sizeof(u16)) * edev->data_incr);
  45. }
  46. /* stop front-end daq unit */
  47. count = eta750_dtaker_end(edev);
  48. edev->running = false;
  49. error_submit:
  50. dmaengine_terminate_all(dchan);
  51. edev->cyclic = false;
  52. dev_info(edev->dev, "%s: dma stopped, cyclic %d, running %d\n", __func__, edev->cyclic, edev->running);
  53. error_out:
  54. return ret;

从上面代码可见,传送过程是一个无限循环,DMA Controller驱动会自动进行ping-pong buffer的切换。并通过回调函数通知上层应用程序,新数据已准备就绪。应用程序可通过命令来终止采集传输过程。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/640288
推荐阅读
相关标签
  

闽ICP备14008679号