本篇筆記中的代碼來自:米聯科技的教程“第三季第一篇的DMA_LOOP環路測試”
硬件的連接如下圖所示:
圖:DMA Loop Block Design
橘色的線就是DMA加FIFO組成的一個LOOP循環,紅色圈圈是AXI_LITE的控制和兩個讀寫完成的中斷。
米聯科技教程提供的該測試代碼文件是以下四個,我刪除了其中關於OLED的部分。
圖:DMA Loop 測試源碼結構
1.重要的結構體
1.1.中斷設備:static XScuGic Intc; //GIC
在main.c文件中,sataic用來修飾全局變量,形成靜態全局變量,static修飾的函數/全局變量屬於內鏈接,Intc可以在當前main.c文件內部范圍內進行鏈接。
/** * The XScuGic driver instance data. The user is required to allocate a * variable of this type for every intc device in the system. A pointer * to a variable of this type is then passed to the driver API functions. */ typedef struct { XScuGic_Config *Config; /**< Configuration table entry */ u32 IsReady; /**< Device is initialized and ready */ u32 UnhandledInterrupts; /**< Intc Statistics */ } XScuGic;
XScuGic結構體中包含了XScuGic_Config 結構體類型指針Config。XScuGic_Config 結構體如下:
typedef struct { u16 DeviceId; /**< Unique ID of device */ u32 CpuBaseAddress; /**< CPU Interface Register base address */ u32 DistBaseAddress; /**< Distributor Register base address */ XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];/**< Vector table of interrupt handlers */ } XScuGic_Config;
1.2.DMA設備:static XAxiDma AxiDma;
/** * The XAxiDma driver instance data. An instance must be allocated for each DMA * engine in use. */ typedef struct XAxiDma { UINTPTR RegBase; /* Virtual base address of DMA engine */ int HasMm2S; /* Has transmit channel */ int HasS2Mm; /* Has receive channel */ int Initialized; /* Driver has been initialized */ int HasSg; XAxiDma_BdRing TxBdRing; /* BD container management for TX channel */ XAxiDma_BdRing RxBdRing[16]; /* BD container management for RX channel */ int TxNumChannels; int RxNumChannels; int MicroDmaMode; int AddrWidth; /**< Address Width */ } XAxiDma;
1.3.中斷向量表
typedef struct { Xil_ExceptionHandler Handler; void *Data; } XExc_VectorTableEntry;
2.代碼結構
main()
|---- init_intr_sys();
|---- DMA_Intr_Init(); // 初始化DMA
|---- XAxiDma_LookupConfig(); // 查找DMA設備
|---- XAxiDma_CfgInitialize(); // 初始化DMA設備
|---- Init_Intr_System(); //初始化中斷控制器
|---- XScuGic_LookupConfig(); // 查找中斷控制器設備;帶的參數為設備ID,查看中斷向量是否存在
|---- XScuGic_CfgInitialize(); // 初始化中斷控制器設備
|---- Setup_Intr_Exception();
|---- Xil_ExceptionInit(); // 使能硬件中斷
|---- Xil_ExceptionRegisterHandler();
|---- Xil_ExceptionEnable();
|---- DMA_Setup_Intr_System(); // 設置DMA中斷
|---- XScuGic_SetPriorityTriggerType();
|---- XScuGic_Connect(); // 連接中斷源
|---- XScuGic_Enable();
|---- DMA_Intr_Enable();
|---- XAxiDma_IntrDisable();
|---- XAxiDma_IntrEnable();
|----axi_dma_test();
2.1.比較重要的函數
2.1.1.中斷注冊函數 Xil_ExceptionRegisterHandler:
/*****************************************************************************/ /** * @brief Register a handler for a specific exception. This handler is being * called when the processor encounters the specified exception. * * @param exception_id contains the ID of the exception source and should * be in the range of 0 to XIL_EXCEPTION_ID_LAST. * See xil_exception.h for further information. * @param Handler to the Handler for that exception. * @param Data is a reference to Data that will be passed to the * Handler when it gets called. * * @return None. * * @note None. * ****************************************************************************/ void Xil_ExceptionRegisterHandler(u32 Exception_id, Xil_ExceptionHandler Handler, void *Data) { XExc_VectorTable[Exception_id].Handler = Handler; XExc_VectorTable[Exception_id].Data = Data; }
從上面可以看到Xil_ExceptionRegisterHandler()這個函數是把中斷的句柄(第二個行參“Handler”)和中斷的參數(第三個行參“Data”)放到了兩個結構體XExc_VectorTableEntry類型的數組XExc_VectorTable當中 ,XExc_VectorTableEntry結構體類型如下:
XExc_VectorTableEntry XExc_VectorTable[XIL_EXCEPTION_ID_LAST + 1] = { {Xil_ExceptionNullHandler, NULL}, {Xil_UndefinedExceptionHandler, NULL}, {Xil_ExceptionNullHandler, NULL}, {Xil_PrefetchAbortHandler, NULL}, {Xil_DataAbortHandler, NULL}, {Xil_ExceptionNullHandler, NULL}, {Xil_ExceptionNullHandler, NULL}, };
Xil_ExceptionRegisterHandler()函數的第二傳參XScuGic_InterruptHandler,是一個函數指針,強制轉化成了Xil_ExceptionHandler類型,XScuGic_InterruptHandler()函數如下。
void XScuGic_InterruptHandler(XScuGic *InstancePtr) { u32 InterruptID; u32 IntIDFull; XScuGic_VectorTableEntry *TablePtr; /* Assert that the pointer to the instance is valid */ Xil_AssertVoid(InstancePtr != NULL); /* * Read the int_ack register to identify the highest priority interrupt ID * and make sure it is valid. Reading Int_Ack will clear the interrupt in the GIC. * 讀取 int_ack 寄存器以識別最高優先級的中斷 ID, 並確保其有效。讀取 Int_Ack 將清除 GIC 中的中斷。
* 然后看看讀出來的中斷 ID 是否大於最大的中斷值。 */ IntIDFull = XScuGic_CPUReadReg(InstancePtr, XSCUGIC_INT_ACK_OFFSET); InterruptID = IntIDFull & XSCUGIC_ACK_INTID_MASK; if(XSCUGIC_MAX_NUM_INTR_INPUTS < InterruptID){ goto IntrExit; } /* * Execute the ISR. Jump into the Interrupt service routine based on the * IRQSource. A software trigger is cleared by the ACK. */ TablePtr = &(InstancePtr->Config->HandlerTable[InterruptID]); if(TablePtr != NULL) { TablePtr->Handler(TablePtr->CallBackRef); } IntrExit: /* * Write to the EOI register, we are all done here. * Let this function return, the boot code will restore the stack. */ XScuGic_CPUWriteReg(InstancePtr, XSCUGIC_EOI_OFFSET, IntIDFull); }
通過程序開頭 xilinx 給出的這個XScuGic_InterruptHandler()程序的注釋可以知道: 這個函數是基本的中斷驅動函數。 它必須 連接到中斷源, 以便在中斷控制器的中斷激活時被調用。 它將解決哪些中斷是活動的和啟用的, 並調用適當的中斷處理程序。 它使用中斷類型信息來確定何時確認中斷。 首先處理最高優先級的中斷。 此函數假定中斷向量表已預先初始化。 它不會在調用中斷處理程序之前驗證表中的條目是否有效。 當中斷發生時,調用的就是上面的代碼中的語句:TablePtr->Handler(TablePtr->CallBackRef)。那么這個Handler和CallBackRef到底是什么呢?也就是Handler和CallBackRef到底是和哪段要被執行的代碼綁定在一起呢?
2.1.2.中斷連接函數
我們在DMA_Setup_Intr_System()函數中調用了中斷連接函數XScuGic_Connect();
int DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId) { <...> /* * 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)DMA_TxIntrHandler, AxiDmaPtr); if (Status != XST_SUCCESS) { return Status; } Status = XScuGic_Connect(IntcInstancePtr, RxIntrId, (Xil_InterruptHandler)DMA_RxIntrHandler, AxiDmaPtr); if (Status != XST_SUCCESS) { return Status; } <...> }
可以看到XScuGic_Connect()函數的第三個傳參是一個Xil_InterruptHandler類型的函數指針DMA_TxIntrHandler。XScuGic_Connect()內容如下。
s32 XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id, Xil_InterruptHandler Handler, void *CallBackRef) { /* * Assert the arguments */ Xil_AssertNonvoid(InstancePtr != NULL); Xil_AssertNonvoid(Int_Id < XSCUGIC_MAX_NUM_INTR_INPUTS); Xil_AssertNonvoid(Handler != NULL); Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); /* * The Int_Id is used as an index into the table to select the proper * handler */ InstancePtr->Config->HandlerTable[Int_Id].Handler = Handler; InstancePtr->Config->HandlerTable[Int_Id].CallBackRef = CallBackRef; return XST_SUCCESS; }
可以看到XScuGic_Connect()函數將傳進來的第三個參數,Xil_InterruptHandler類型的“Handler”,綁定到InstancePtr->Config->HandlerTable[Int_Id].Handler中,第四個參數同理綁定。這里的InstancePtr是函數XScuGic_Connect()傳進來的XScuGic結構體類型的指針變量,前面講過,XScuGic結構體中還包含XScuGic_Config結構體類型的指針Config,進一步來說,XScuGic_Connect()函數將傳進來的第三個參數Handler就是綁定到XScuGic_Config結構體類型的指針Config中的HandlerTable變量。這個HandlerTable變量是一個XScuGic_VectorTableEntry類型的結構體變量。至此,中斷的Handler就綁定到main.c文件開頭定義的設備static XScuGic Intc當中,同時設備 XScuGic Intc也因為函數Setup_Intr_Exception()跟硬件的異常向量表綁定到一起了。前文提到“Handler和CallBackRef到底是和哪段要被執行的代碼綁定在一起呢?”,那么答案就在這里了,要執行的代碼就在這里被綁定到一起來。
所以接下來看看這個形參Handler(對應的是調用XScuGic_Connect()函數傳進來的實參DMA_TxIntrHandler)指向了什么東西?
函數指針DMA_TxIntrHandler指向的內容如下。下面代碼是DMA Tx的,Rx的也差不多。
static void DMA_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; } }
代碼中,在dma_intr.h中聲明了三個全局變量:TxDone,RxDone和Error。
dam_intr.h :
extern volatile int TxDone; extern volatile int RxDone; extern volatile int Error;
在dma_intr.c文件的開頭,定義了這三個全局變量。
#include "dma_intr.h" volatile int TxDone; volatile int RxDone; volatile int Error;
可以看到用關鍵字volatile修飾,在此回顧一下朱有鵬老師在講解C語言的時候,總結的volatile的用法:
(1)volatile的字面意思:可變的、易變的。C語言中volatile用來修飾一個變量,表示這個變量可以被編譯器之外的東西改變。編譯器之內的意思是變量的值的改變是代碼的作用,編譯器之外的改變就是這個改變不是代碼造成的,或者不是當前代碼造成的,編譯器在編譯當前代碼時無法預知。譬如在中斷處理程序isr中更改了這個變量的值,譬如多線程中在別的線程更改了這個變量的值,譬如硬件自動更改了這個變量的值(一般這個變量是一個寄存器的值)
(2)以上說的三種情況(中斷isr中引用的變量,多線程中共用的變量,硬件會更改的變量)都是編譯器在編譯時無法預知的更改,此時應用使用volatile告訴編譯器這個變量屬於這種(可變的、易變的)情況。編譯器在遇到volatile修飾的變量時就不會對改變量的訪問進行優化,就不會出現錯誤。
(3)編譯器的優化在一般情況下非常好,可以幫助提升程序效率。但是在特殊情況(volatile)下,變量會被編譯器想象之外的力量所改變,此時如果編譯器沒有意識到而去優化則就會造成優化錯誤,優化錯誤就會帶來執行時錯誤。而且這種錯誤很難被發現。
(4)volatile是程序員意識到需要volatile然后在定義變量時加上volatile,如果你遇到了應該加volatile的情況而沒有加程序可能會被錯誤的優化。如果在不應該加volatile而加了的情況程序不會出錯只是會降低效率。所以我們對於volatile的態度應該是:正確區分,該加的時候加不該加的時候不加,如果不能確定該不該加為了保險起見就加上。
計划在xilinx DMA IP loop測試(二)中結合DMA的AXI4總線時序,來記錄一下DMA的數據收發。