高速大容量數據采集方案是一個可實現是連續采集數據率超過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