ZYNQ學習之路11.AXI DMA

一. AXI DMA簡介

AXI DMA IP核提供了AXI4內存之間或AXI4-Stream IP之間的內存直接訪問,可選爲分散收集工作模式,初始化,狀態和管理寄存器等通過AXI4-Lite 從機幾口訪問,結構如圖1所示,AXI DMA主要包括Memory Map和Stream兩部分接口,前者連接PS段,後者連接帶有流接口的PL IP核。

圖1 AXI DMA結構框圖

 

AXI DMA的特性如下:

1. AXI4協議

2. 支持 Scatter/Gather DMA

  • 不需要CPU的控制
  • 獨立於數據總線獲取或更新傳輸描述符
  • 運行描述符放在任何內存映射的位置,如:描述符可以放在block RAM中
  • 支持循環工作模式

3. 直接寄存器模式

只需很少的FPGA資源就可以使用Scatter Gather引擎,在這種模式下,設置源地址(如MM2S)和目的地址(如S2MM),然後設置數據長度的寄存器。

4. AXI4支持多種數據位寬:32,64,128,256,512和1024位;

5. AXI4-Stream數據位寬支持:8,16,32,64,128,256,512和1024位;

6. 支持超過512字節重對齊。

1.1 開發環境

  • Windows 10 64位
  • Vivado 2018.2
  • XC7Z010-1-CLG400

1.2 例程簡介

首先構建AXI DMA例程使用的硬件環境,在這個設計中,我們用DMA將內存中的數據傳輸到IP模塊中,然後傳輸會內存,原則上這個IP模塊可以是任意類型的數據產生模塊,如ADC/DMA,在本例程中,我們使用FIFO來作爲環路進行測試。如圖2所示。

圖2 本例程結構示意圖

如圖2所示,我們在PL中使用AXI DMA和AXI Data FIFO模塊,AXI Lite總線用來配置AXI DMA,AXI_S2MM和AXI_MM2S用於內存和DMA控制器之間的通信。

2. 工程創建

2.1 添加AXI DMA

1. 打開Vivado模板工程,在Block Design中點擊"Add IP",搜索AXI Direct Memory Access模塊,雙擊添加到工程中。

2. 連接AXI總線。點擊"Run Connection Automation",點擊"OK",vivado會自動將AXI DMA連接到ZYNQ PS端,連接後如下圖所示。

3. 現在,我們要連接AXI DMA控制器的M_AXI_SG, M_AXI_MM2S和M_AXI_S2MM到一個PS端的高性能AXI從機接口。模板工程中並沒有這樣的從機接口,所以,雙擊ZYNQ IP,配置該模塊,選擇PS-PL Configuration,勾選HP Slave AXI Interface > S AXI HP0 Interface,如下圖所示。

4. 高性能AXI從機接口在模塊原理圖中顯示如圖,點擊"Run Connection Automation",選擇"processing_system7_0/S_AXI_HP0".

 5. 此時,根據輔助設計提示,點擊"Run Connection Automation",全選All Automation,默認即可。DMA連接完成後如下圖所示。

 6. 取消SG模式。雙擊axi_dma模塊,取消"Enable Scatter Gather Engine"。配置如下

 2.2 添加FIFO

1. 點擊"Add IP",搜索"AXI-Stream Data FIFO".

 

 2. 這裏只能手動連接AXI總線。連接data FIFO的"S_AXIS"到AXI DMA的M_AXIS_MM2S。

3. 連接data FIFO的“M_AXIS”到 AXI DMA的"S_AXIS_MM2S"。

4. data FIFO的s_axis_aresetn和s_axis_aclk到AXI DMA的axi_resetn和s_axi_lite_aclk。

5. 連接DMA中斷到PS。 連接AXI DMA的mm2s_introut到xlconcat_0的In0,連接s2mm_introut到xlconcat_0的In1.

6. 點擊Tools -> Validate Design,確認無誤後最終原理圖如下。

 編譯綜合,生成bitstream,導出到SDK中進行軟件設計。

3. SDK軟件測試

1.1 創建SDK工程

新建AXIDMA_bsp工程,在system.mss的Peripheral Drivers中,點擊Import Examples,導入Xilinx官方例程。

 

 選擇xaxidma_example_simple_intr例程。

 1.2 編輯代碼

dma_intr.h文件

#ifndef SRC_DMA_INTR_H_
#define SRC_DMA_INTR_H_

#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xdebug.h"
#include "xscugic.h"

/************************** Constant Definitions *****************************/
#define DMA_DEV_ID			XPAR_AXIDMA_0_DEVICE_ID
#define MEM_BASE_ADDR		0x01000000
#define RX_INTR_ID			XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define TX_INTR_ID			XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID

#define TX_BUFFER_BASE		(MEM_BASE_ADDR + 0x00100000)
#define RX_BUFFER_BASE		(MEM_BASE_ADDR + 0x00300000)
#define RX_BUFFER_HIGH		(MEM_BASE_ADDR + 0x004FFFFF)
#define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID

#define INTC				XScuGic
#define INTC_HANDLER		XScuGic_InterruptHandler

/* Timeout loop counter for reset
 */
#define RESET_TIMEOUT_COUNTER	10000

#define TEST_START_VALUE	0xC
/*
 * Buffer and Buffer Descriptor related constant definition
 */
#define MAX_PKT_LEN			0x100

#define NUMBER_OF_TRANSFERS	10

/*
 * Flags interrupt handlers use to notify the application context the events.
 */
extern volatile int TxDone;
extern volatile int RxDone;
extern volatile int Error;

int SetupIntrSystem(INTC * IntcInstancePtr,
			   XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId);
void DisableIntrSystem(INTC * IntcInstancePtr,
					u16 TxIntrId, u16 RxIntrId);

/************************** Function Prototypes ******************************/
int CheckData(int Length, u8 StartValue);
void TxIntrHandler(void *Callback);
void RxIntrHandler(void *Callback);

#endif /* SRC_DMA_INTR_H_ */

 dma_intr.c

#include "dma_intr.h"

/*
 * Flags interrupt handlers use to notify the application context the events.
 */
volatile int TxDone;
volatile int RxDone;
volatile int Error;

/*****************************************************************************/
/*
*
* This function checks data buffer after the DMA transfer is finished.
*
* We use the static tx/rx buffers.
*
* @param	Length is the length to check
* @param	StartValue is the starting value of the first byte
*
* @return
*		- XST_SUCCESS if validation is successful
*		- XST_FAILURE if validation is failure.
*
* @note		None.
*
******************************************************************************/
int CheckData(int Length, u8 StartValue)
{
	u8 *RxPacket;
	int Index = 0;
	u8 Value;

	RxPacket = (u8 *) RX_BUFFER_BASE;
	Value = StartValue;

	/* Invalidate the DestBuffer before receiving the data, in case the
	 * Data Cache is enabled
	 */
#ifndef __aarch64__
	Xil_DCacheInvalidateRange((UINTPTR)RxPacket, Length);
#endif

	for(Index = 0; Index < Length; Index++) {
		if (RxPacket[Index] != Value) {
			xil_printf("Data error %d: %x/%x\r\n",
			    Index, RxPacket[Index], Value);

			return XST_FAILURE;
		}
		Value = (Value + 1) & 0xFF;
	}

	return XST_SUCCESS;
}

/*****************************************************************************/
/*
*
* This is the DMA TX Interrupt handler function.
*
* It gets the interrupt status from the hardware, acknowledges it, and if any
* error happens, it resets the hardware. Otherwise, if a completion interrupt
* is present, then sets the TxDone.flag
*
* @param	Callback is a pointer to TX channel of the DMA engine.
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void TxIntrHandler(void *Callback)
{

	u32 IrqStatus;
	int TimeOut;
	XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

	/* Read pending interrupts */
	IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);

	/* Acknowledge pending interrupts */


	XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);

	/*
	 * If no interrupt is asserted, we do not do anything
	 */
	if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

		return;
	}

	/*
	 * If error interrupt is asserted, raise error flag, reset the
	 * hardware to recover from the error, and return with no further
	 * processing.
	 */
	if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

		Error = 1;

		/*
		 * Reset should never fail for transmit channel
		 */
		XAxiDma_Reset(AxiDmaInst);

		TimeOut = RESET_TIMEOUT_COUNTER;

		while (TimeOut) {
			if (XAxiDma_ResetIsDone(AxiDmaInst)) {
				break;
			}

			TimeOut -= 1;
		}

		return;
	}

	/*
	 * If Completion interrupt is asserted, then set the TxDone flag
	 */
	if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

		TxDone = 1;
	}
}

/*****************************************************************************/
/*
*
* This is the DMA RX interrupt handler function
*
* It gets the interrupt status from the hardware, acknowledges it, and if any
* error happens, it resets the hardware. Otherwise, if a completion interrupt
* is present, then it sets the RxDone flag.
*
* @param	Callback is a pointer to RX channel of the DMA engine.
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void RxIntrHandler(void *Callback)
{
	u32 IrqStatus;
	int TimeOut;
	XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

	/* Read pending interrupts */
	IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);

	/* Acknowledge pending interrupts */
	XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

	/*
	 * If no interrupt is asserted, we do not do anything
	 */
	if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
		return;
	}

	/*
	 * If error interrupt is asserted, raise error flag, reset the
	 * hardware to recover from the error, and return with no further
	 * processing.
	 */
	if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

		Error = 1;

		/* Reset could fail and hang
		 * NEED a way to handle this or do not call it??
		 */
		XAxiDma_Reset(AxiDmaInst);

		TimeOut = RESET_TIMEOUT_COUNTER;

		while (TimeOut) {
			if(XAxiDma_ResetIsDone(AxiDmaInst)) {
				break;
			}

			TimeOut -= 1;
		}

		return;
	}

	/*
	 * If completion interrupt is asserted, then set RxDone flag
	 */
	if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

		RxDone = 1;
	}
}

/*****************************************************************************/
/*
*
* This function setups the interrupt system so interrupts can occur for the
* DMA, it assumes INTC component exists in the hardware system.
*
* @param	IntcInstancePtr is a pointer to the instance of the INTC.
* @param	AxiDmaPtr is a pointer to the instance of the DMA engine
* @param	TxIntrId is the TX channel Interrupt ID.
* @param	RxIntrId is the RX channel Interrupt ID.
*
* @return
*		- XST_SUCCESS if successful,
*		- XST_FAILURE.if not succesful
*
* @note		None.
*
******************************************************************************/
int SetupIntrSystem(INTC * IntcInstancePtr,
			   XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId)
{
	int Status;

	XScuGic_Config *IntcConfig;


	/*
	 * Initialize the interrupt controller driver so that it is ready to
	 * use.
	 */
	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	if (NULL == IntcConfig) {
		return XST_FAILURE;
	}

	Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
					IntcConfig->CpuBaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}


	XScuGic_SetPriorityTriggerType(IntcInstancePtr, TxIntrId, 0xA0, 0x3);

	XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3);
	/*
	 * Connect the device driver handler that will be called when an
	 * interrupt for the device occurs, the handler defined above performs
	 * the specific interrupt processing for the device.
	 */
	Status = XScuGic_Connect(IntcInstancePtr, TxIntrId,
				(Xil_InterruptHandler)TxIntrHandler,
				AxiDmaPtr);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	Status = XScuGic_Connect(IntcInstancePtr, RxIntrId,
				(Xil_InterruptHandler)RxIntrHandler,
				AxiDmaPtr);
	if (Status != XST_SUCCESS) {
		return Status;
	}

	XScuGic_Enable(IntcInstancePtr, TxIntrId);
	XScuGic_Enable(IntcInstancePtr, RxIntrId);

	/* Enable interrupts from the hardware */

	Xil_ExceptionInit();
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
			(Xil_ExceptionHandler)INTC_HANDLER,
			(void *)IntcInstancePtr);

	Xil_ExceptionEnable();

	return XST_SUCCESS;
}

/*****************************************************************************/
/**
*
* This function disables the interrupts for DMA engine.
*
* @param	IntcInstancePtr is the pointer to the INTC component instance
* @param	TxIntrId is interrupt ID associated w/ DMA TX channel
* @param	RxIntrId is interrupt ID associated w/ DMA RX channel
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void DisableIntrSystem(INTC * IntcInstancePtr,
					u16 TxIntrId, u16 RxIntrId)
{
	XScuGic_Disconnect(IntcInstancePtr, TxIntrId);
	XScuGic_Disconnect(IntcInstancePtr, RxIntrId);
}

 main.c文件

#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xdebug.h"
#include "xscugic.h"

#include "dma_intr.h"

static XAxiDma AxiDma;		/* Instance of the XAxiDma */
static INTC Intc;			/* Instance of the Interrupt Controller */

/*****************************************************************************/
/**
*
* Main function
*
* This function is the main entry of the interrupt test. It does the following:
*	Initialize the DMA engine
*	Set up Tx and Rx channels
*	Set up the interrupt system for the Tx and Rx interrupts
*	Submit a transfer
*	Wait for the transfer to finish
*	Check transfer status
*	Disable Tx and Rx interrupts
*	Print test status and exit
*
* @param	None
*
* @return
*		- XST_SUCCESS if example finishes successfully
*		- XST_FAILURE if example fails.
*
* @note		None.
*
******************************************************************************/
int axi_dma_test()
{
	int Status;
	XAxiDma_Config *Config;
	int Tries = NUMBER_OF_TRANSFERS;
	int Index;
	u8 *TxBufferPtr;
	u8 *RxBufferPtr;
	u8 Value;

	TxBufferPtr = (u8 *)TX_BUFFER_BASE ;
	RxBufferPtr = (u8 *)RX_BUFFER_BASE;

	xil_printf("\r\n--- Entering main() --- \r\n");

	Config = XAxiDma_LookupConfig(DMA_DEV_ID);
	if (!Config) {
		xil_printf("No config found for %d\r\n", DMA_DEV_ID);
		return XST_FAILURE;
	}

	/* Initialize DMA engine */
	Status = XAxiDma_CfgInitialize(&AxiDma, Config);

	if (Status != XST_SUCCESS) {
		xil_printf("Initialization failed %d\r\n", Status);
		return XST_FAILURE;
	}

	if(XAxiDma_HasSg(&AxiDma)){
		xil_printf("Device configured as SG mode \r\n");
		return XST_FAILURE;
	}

	/* Set up Interrupt system  */
	Status = SetupIntrSystem(&Intc, &AxiDma, TX_INTR_ID, RX_INTR_ID);
	if (Status != XST_SUCCESS) {

		xil_printf("Failed intr setup\r\n");
		return XST_FAILURE;
	}

	/* Disable all interrupts before setup */

	XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
						XAXIDMA_DMA_TO_DEVICE);

	XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
				XAXIDMA_DEVICE_TO_DMA);

	/* Enable all interrupts */
	XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
							XAXIDMA_DMA_TO_DEVICE);


	XAxiDma_IntrEnable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
							XAXIDMA_DEVICE_TO_DMA);

	/* Initialize flags before start transfer test  */
	TxDone = 0;
	RxDone = 0;
	Error = 0;

	Value = TEST_START_VALUE;

	for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
			TxBufferPtr[Index] = Value;

			Value = (Value + 1) & 0xFF;
	}

	/* Flush the SrcBuffer before the DMA transfer, in case the Data Cache
	 * is enabled
	 */
	Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, MAX_PKT_LEN);
#ifdef __aarch64__
	Xil_DCacheFlushRange((UINTPTR)RxBufferPtr, MAX_PKT_LEN);
#endif

	/* Send a packet */
	for(Index = 0; Index < Tries; Index ++) {

		Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
					MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);

		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}

		Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
					MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);

		if (Status != XST_SUCCESS) {
			return XST_FAILURE;
		}


		/*
		 * Wait TX done and RX done
		 */
		while (!TxDone && !RxDone && !Error) {
				/* NOP */
		}

		if (Error) {
			xil_printf("Failed test transmit%s done, "
			"receive%s done\r\n", TxDone? "":" not",
							RxDone? "":" not");

			goto Done;

		}

		/*
		 * Test finished, check data
		 */
		Status = CheckData(MAX_PKT_LEN, 0xC);
		if (Status != XST_SUCCESS) {
			xil_printf("Data check failed\r\n");
			goto Done;
		}
	}


	xil_printf("Successfully ran AXI DMA interrupt Example\r\n");


	/* Disable TX and RX Ring interrupts and return success */

	DisableIntrSystem(&Intc, TX_INTR_ID, RX_INTR_ID);

Done:
	xil_printf("--- Exiting main() --- \r\n");
	return XST_SUCCESS;
}

int main(void)
{
	axi_dma_test();
	return XST_SUCCESS;
}

 1.3 編譯調試。

下載bitstream文件後,運行app程序,在終端中顯示如下。

 通過斷點調試觀察內存狀態。

 在數據發送前,先賦值發送數據包,此時發送數據爲0~255而接收數據位無效數據。

 在下面位置打斷點,觀察接收數據內存數據。

 可以看到接收的數據與發送的數據一致。

4 Linux驅動AXI DMA

4.1 安裝devicetree生成工具

在Vvivado安裝目錄下創建一個空文件夾,這裏命名爲Tools/devicetree,用git下載device_tree-generator。

git clone https://github.com/Xilinx/device-tree-xlnx.git device_tree-generator

 在Xilinx SDK軟件中,點擊Xilinx-> Repositories,在Local Repositories添加上面的路徑。

4.2 創建設備樹文件

創建BSP工程,點擊File -> New -> Board Support Package, 在Board Support Package框中選擇device_tree.

 在bootargs中輸入:console=ttyPS0,115200 root=/dev/mmcblk0p2 rw rootfstype=ext4 earlyprintk rootwait

此時,創建的工程中pl.dtsi是PL側的設備樹信息。

 打開pl.dtsi內容如下,可以看到axi_dma添加了兩個通道,一個讀和一個寫通道。

/ {
	amba_pl: amba_pl {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "simple-bus";
		ranges ;
		axi_dma_0: dma@40400000 {
			#dma-cells = <1>;
			clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
			clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>;
			compatible = "xlnx,axi-dma-7.1", "xlnx,axi-dma-1.00.a";
			interrupt-names = "mm2s_introut", "s2mm_introut";
			interrupt-parent = <&intc>;
			interrupts = <0 29 4 0 30 4>;
			reg = <0x40400000 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,sg-length-width = <0xe>;
			dma-channel@40400000 {
				compatible = "xlnx,axi-dma-mm2s-channel";
				dma-channels = <0x1>;
				interrupts = <0 29 4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x0>;
			};
			dma-channel@40400030 {
				compatible = "xlnx,axi-dma-s2mm-channel";
				dma-channels = <0x1>;
				interrupts = <0 30 4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x0>;
			};
		};
	};
};

製作BOOT.bin啓動鏡像。

4.3 編譯Linux系統文件

配置Linux內核使其支持AXI DMA。在linux kernel根目錄下執行:

# make menuconfig

選擇Device Drivers > DMA Engine support > Xilinx DMA Engines --->

勾選Xilinx AXI DMA Engine。

編輯設備樹文件:pl.dtsi文件中添加以下內容。

axidma_chrdev: axidma_chrdev@0 {
    compatible = "xlnx,axidma-chrdev";
    dmas = <&axi_dma_0 0 &axi_dma_0 1>;
    dma-names = "tx_channel", "rx_channel";
};

 將devicetree工程中的設備樹源文件複製到Ubuntu中。

 編譯設備樹:

# ./scripts/dtc/dtc -I dts -O dtb -o /home/biac/workspace/AXIDMA\ 
    devicetree/devicetree.dtb /home/biac/workspace/AXIDMA\ devicetree/system-top.dts 

其中./scripts/dtc/dtc爲Zturn board Linux內核目錄下的文件,終端在該目錄下打開。

將本文生成的以下文件複製到SD卡中,啓動Linux系統。

BOOT.bin, devicetree.dtb, 7z010.bit

參考資料

[1] http://www.fpgadeveloper.com/2014/08/using-the-axi-dma-in-vivado.html

[2] https://blog.csdn.net/shichaog/article/details/51771247

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章