xilinx DMA IP核(一) —— loop測試 代碼注釋


  本篇筆記中的代碼來自:米聯科技的教程“第三季第一篇的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)。那么這個HandlerCallBackRef到底是什么呢?也就是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的數據收發。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM