ZYNQ有兩個CPU?(三)——SGI非同步通信

前面兩篇文章中我分享了ZYNQ上在Standalone環境下搭建AMP和用OCM共享內存傳遞數據的方法。而到目前為止實現的功能是在兩個CPU上跑了多線程,線程之間可以通過共享內存進行同步通信,而我們知道同步通信需要耗費大量的CPU時間,為了節約CPU時間必須要採用非同步通信的方式也就是中斷方式。

這篇文章的SGI測試方案是這樣的:由CPU0綁定軟體中斷0的中斷服務函數讓CPU1來觸發該中斷,CPU0的main函數中會一直等待軟體中斷0被觸發,觸發後CPU0將觸發軟體中斷1;而軟體中斷1的中斷服務函數由CPU1綁定,CPU1在觸發軟體中斷0後會一直等待自己的軟體中斷1被觸發才會再次觸發軟體中斷0。兩個CPU在各自的main函數里採用乒乓的方式相互觸發對方的軟體中斷並在串口輸出調試信息。

在UG585第七章中斷章節的閱讀中,自然想到符合我要求的中斷源就是SGI(軟體生成中斷),ZYNQ中的三大類中斷SGI、SPI、PPI請查閱UG585。而SGI的中斷號一共16個佔據了ZYNQ本身96個中斷號的0~15,中斷產生的方式是往ICDSGIR(軟體中斷觸發寄存器)寫中斷號和觸發目標CPU來產生。可以中斷自己,或其他CPU。ICDSGIR寄存器在UG585的附錄B寄存器詳情中有詳細說明,而SGI測試程序的編寫過程中會在第七章和附錄B中來回查找相關信息。其中需要注意ICDSGIR寄存器的地址(絕對地址)軟體名稱(GIC_SFI_TRIG)。

根據我查詢到的信息應該不需要重新建立Vivado下的HW_Platform,所以就在SDK中開始寫代碼了。按照流程實例化中斷控制器了,寫到Connect中斷服務函數時就有點犯難了,因為最後一個參數是回調函數的設備實例,而SGI本身不是設備產生的中斷,思索很久以後猜測是0,但是為了證實自己的想法在網上做了查詢。明確講到SGI應用的有三篇:

米聯科技的SGI應用實例

S03_CH13_ZYNQ A9 TCP UART雙核AMP常式

另外就是Xilinx官網上搜出來的大神寫的《亞當.泰勒玩轉Microzed》系列中有SGI的應用及對應的中文翻譯博客,人家寫了188篇啊!頓時有了高山仰止的膜拜感,趕緊查查搜藏一下:

Search - Community Forums

結果前面的搜索結果讓我有點失望,米聯的博客只是列出了工程項目的基本結構和步驟。我感興趣的SGI如何設置和觸發的並沒有詳細寫出只交代了用哪幾個自定義函數來實現這個功能。而大神的博客變成了《九陰真經》殘本,關於AMP的三篇博客偏偏SGI的沒有了,你們合夥坑我是不是?

回過頭只能利用現有的資料進行探索了。首先根據UG585 查詢到的根據關鍵字查到了和SGI相關的寄存器在xparameters.h中的定義,比如GIC定義的信息如下:

#define XPAR_SCUGIC_0_DIST_BASEADDR 0xF8F01000//查自xparameters_ps.h#define XSCUGIC_SFI_TRIG_OFFSET 0x00000F00U /**< Software Triggered Interrupt Register *///查自xscugic_hw.h

對應的API函數也查詢了一下System.mss中的documentation

得到的結果我感興趣的函數有幾個:

綁定中斷服務函數

設置中斷優先順序和觸發方式

允許中斷

指定中斷觸發的CPU

觸發軟體中斷

讀完這幾個函數自己心裡大概知道應該怎麼做了:

1.初始化GIC中斷控制器;

2.綁定中斷服務函數;

3設置中斷優先順序和觸發方式;(這條在我最初的設想中是不存在的)

4.綁定觸發中斷的CPU

5.允許中斷

6.觸發軟體中斷;(我在這裡犯了第二個錯誤)

由於程序比較簡單,三下五除二整吧整吧就開始debug了,結果不對,沒有任何一個中斷產生。這下有點懵逼了。逼得我回過頭仔細看了第七章,發現了第一個錯誤,也就是我的第三步設置中斷的觸發方式,因為在UG585上是這麼說的,所有的SGI均為邊沿觸發:

所以我加上了第三步設置中斷優先順序和觸發方式的函數,但還是沒有效果。這個坑有點深啊!

沒辦法繼續找錯誤。看了一下第四步的函數操作的寄存器ICDIPTR[0:3],ICDIPTR寄存器一共有24個,每個寄存器管理4個中斷一共96個中斷用24個寄存器管理,用來標定每一個中斷源用來中斷哪個CPU。我用到的軟體中斷0和1,所以看了ICDIPTR[0]:

ICDIPTR[0]寄存器說明

XScuGic_InterruptMaptoCpu函數的底層操作是這樣的:

void XScuGic_InterruptMaptoCpu(XScuGic *InstancePtr, u8 Cpu_Id, u32 Int_Id){ u32 RegValue, Offset; RegValue = XScuGic_DistReadReg(InstancePtr, XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id)); Offset = (Int_Id & 0x3U); Cpu_Id = (0x1U << Cpu_Id);//根據寄存器說明設置觸發的CPU號 RegValue = (RegValue & (~(0xFFU << (Offset*8U))) ); RegValue |= ((Cpu_Id) << (Offset*8U)); XScuGic_DistWriteReg(InstancePtr, XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id), RegValue);//XSCUGIC_SPI_TARGET_OFFSET_CALC(Int_Id)是通過中斷號計算操作哪個ICDIPTR寄存器的//公式宏定義}

檢查之後我確定在這裡沒有犯錯誤但效果依然是沒有觸發中斷,而看完這個函數以後我覺得完全可以甩開BSP的API函數自己操作這些寄存器,簡單粗暴原始的操作才是王道,因此我也就沒檢查第五和第六步了。直接寫了自己的軟體中斷觸發函數,根據是UG585上ICDSGIR寄存器的說明:

翻譯一下:

第26到第31位保留也就是沒用。

第24、25位叫做目標列表過濾設置該兩位為00觸發中斷的目標CPU會從目標CPU列表裡面選擇;為01則觸發除了自身之外的所有CPU;為10則觸發自身;11保留不用。

第16到23位共8位為目標CPU列表,對應的每一位代表一個CPU因此對應位設置為1則中斷會觸發對應的CPU(目標列表過濾設置必須設置為00才能讓本列表有效)。

第15位安全設置默認為0。

第4到第14位必須設置為0。

第0到第3位是中斷ID正好對應0~15的軟體中斷號。

根據這個說明我自己寫了個暴力觸發軟體中斷的函數:

//函數中相關的宏定義如下:#define TRIGGER_SELECTED 0x00000000#define TRIGGER_SELF 0x02000000#define TRIGGER_OTHER 0x01000000#define CPU_NO0 0#define CPU_NO1 1#define CPU_ID_LIST 0x00010000/*ICDSGIR SGI觸發寄存器地址*/#define ICDSGIR 0xF8F01F00void SGITriggerTargetCPU(u32 CPUNO,u32 INTR,u32 TargetSelect){ Xil_Out32(ICDSGIR, TargetSelect+(CPU_ID_LIST<<CPUNO)+INTR);}

霸王硬上弓的結果就是成功,不管是0觸發1還是1觸發0還是自己觸發自己全部OK,可為啥用SDK裡頭的API就不行呢?為了找到這個原因我又查了XScuGic_SoftwareIntr函數的底層操作:

s32 XScuGic_SoftwareIntr(XScuGic *InstancePtr, u32 Int_Id, u32 Cpu_Id){ u32 Mask; /* * Assert the arguments */ Xil_AssertNonvoid(InstancePtr != NULL); Xil_AssertNonvoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY); Xil_AssertNonvoid(Int_Id <= 15U) ; Xil_AssertNonvoid(Cpu_Id <= 255U) ; /* * The Int_Id is used to create the appropriate mask for the * desired interrupt. Int_Id currently limited to 0 - 15 * Use the target list for the Cpu ID. */ Mask = ((Cpu_Id << 16U) | Int_Id) &//天殺的問題就出在這裡 (XSCUGIC_SFI_TRIG_CPU_MASK | XSCUGIC_SFI_TRIG_INTID_MASK); /* * Write to the Software interrupt trigger register. Use the appropriate * CPU Int_Id. */ XScuGic_DistWriteReg(InstancePtr, XSCUGIC_SFI_TRIG_OFFSET, Mask); /* Indicate the interrupt was successfully simulated */ return XST_SUCCESS;}

SDK的文檔里並沒有說明Cpu_Id的參數格式是怎麼樣的,而看了這裡才知道我按照自己的理解填0或者1作為CPU的編號肯定不能得到正確的結果了。根據這個函數的操作,ICDSGIR寄存器的目標列表過濾被設置為00,需要從目標CPU列表中選擇,而函數直接把我填入的Cpu_Id左移16位到第16到23位上,也就是說SDK希望我直接填入8位的目標CPU列表而不是從0開始編號的Cpu_Id,但在文檔中根本沒有說明,讓我在坑裡頭轉悠了好久。下面把我測試項目的所有代碼貼出來:

CPU0的主函數ZYNQ_AMP_CPU0_SGI.c

#include <stdio.h>#include <sleep.h>#include "xparameters.h"#include "xstatus.h"#include "xscugic.h"#include "sleep.h"#include "xil_exception.h"#include "xuartps.h"#include "ZYNQ_SGI.h"#define DELAY 50000000#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID#define sev() __asm__("sev")XScuGic InterruptController; /* Instance of the Interrupt Controller */u32 SGI_INTR;int SGI_trigered;void SGI0_INTR_ID_ISR (void);int main(void){ int Status; SGI_INTR=SGI0_INTR_ID; SGI_trigered=0; Xil_Out32(0xFFFFFFF0, 0x20000000); //Xil_Out32(CPU1STARTADR, 0x00200000); dmb(); //waits until write has finished print("CPU0: sending the SEV to wake up CPU1

"); sev(); dmb(); //waits until write has finished Status = SetupSGIIntrSystem(&InterruptController,(Xil_ExceptionHandler)SGI0_INTR_ID_ISR, INTC_DEVICE_ID,SGI_INTR,CPU_NO0); if (Status != XST_SUCCESS) { return XST_FAILURE; } ExceptionSetup(&InterruptController); while(1) { while(SGI_trigered==0); SGI_trigered=0; for(int i=0;i<DELAY;i++); xil_printf("CPU0: Trigger interrupt1 to CPU1

"); for(int i=0;i<DELAY;i++); Status = XScuGic_SoftwareIntr(&InterruptController,SGI1_INTR_ID,0x1<<CPU_NO1); if (Status != XST_SUCCESS) { return XST_FAILURE; } } return 0;}void SGI0_INTR_ID_ISR (void){ xil_printf("CPU0: The software interrupt0 has been triggered

"); SGI_trigered=1;}

CPU1的主函數ZYNQ_AMP_CPU1_SGI.c

#include <stdio.h>#include <sleep.h>#include "xparameters.h"#include "xstatus.h"#include "xscugic.h"#include "sleep.h"#include "xil_exception.h"#include "xuartps.h"#include "ZYNQ_SGI.h"#define DELAY 50000000#define INTC_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_IDXScuGic InterruptController; /* Instance of the Interrupt Controller */u32 SGI_INTR;int SGI_trigered;void SGI1_INTR_ID_ISR (void);int main(void){ int Status; SGI_INTR=SGI1_INTR_ID; SGI_trigered=0; Status = SetupSGIIntrSystem(&InterruptController,(Xil_ExceptionHandler)SGI1_INTR_ID_ISR, INTC_DEVICE_ID,SGI_INTR,CPU_NO1); if (Status != XST_SUCCESS) { return XST_FAILURE; } ExceptionSetup(&InterruptController); while(1) { xil_printf("CPU1: Trigger interrupt0 to CPU0

"); for(int i=0;i<DELAY;i++); Status = XScuGic_SoftwareIntr(&InterruptController,SGI0_INTR_ID,0x1<<CPU_NO0); if (Status != XST_SUCCESS) { return XST_FAILURE; } while(SGI_trigered==0); SGI_trigered=0; for(int i=0;i<DELAY;i++); } return 0;}void SGI1_INTR_ID_ISR (void){ xil_printf("CPU1: The software interrupt1 has been triggered

"); SGI_trigered=1;}

兩個CPU共用的關於SGI的頭文件ZYNQ_SGI.h

#include "xscugic.h"#include "Xil_io.h"#ifndef SRC_ZYNQ_SGI_H_#define SRC_ZYNQ_SGI_H_/*Register ICDSGIR setting*/#define SGI0_INTR_ID 0#define SGI1_INTR_ID 1#define SGI2_INTR_ID 2#define SGI3_INTR_ID 3#define SGI4_INTR_ID 4#define SGI5_INTR_ID 5#define SGI6_INTR_ID 6#define SGI7_INTR_ID 7#define SGI8_INTR_ID 8#define SGI9_INTR_ID 9#define SGI10_INTR_ID 10#define SGI11_INTR_ID 11#define SGI12_INTR_ID 12#define SGI13_INTR_ID 13#define SGI14_INTR_ID 14#define SGI15_INTR_ID 15/*TargetListFilter bits[25:24] * 0b00: send the interrupt to the CPU interfacesspecified in the CPUTargetList field0b01: send the interrupt to all CPU interfacesexcept the CPU interface that requested theinterrupt0b10: send the interrupt on only to the CPUinterface that requested the interrupt0b11: reserved*/#define TRIGGER_SELECTED 0x00000000#define TRIGGER_SELF 0x02000000#define TRIGGER_OTHER 0x01000000/*CPUTargetList bits[23:16] When TargetListFilter is 0b00, defines the CPUinterfaces the Distributor must send the interruptto.Each bit refers to the corresponding CPUinterface.*/#define CPU_NO0 0#define CPU_NO1 1#define CPU_ID_LIST 0x00010000/*ICDSGIR SGI觸發寄存器地址*/#define ICDSGIR 0xF8F01F00#define ICDIPTR 0xF8F01800/* SGI中斷初始化函數 * 該函數初始化指定的SGI中斷到本CPU上並綁定對應的ISR * 參數說明: * XScuGic *IntcInstancePtr 中斷控制器實例 * Xil_InterruptHandler Handler ISR函數名 * u32 INTC_DEVICE_ID 中斷控制器設備號 * u32 SGI_INTR SGI中斷號 * CPU_NO 運行當前程序的CPU號 */u32 SetupSGIIntrSystem(XScuGic *IntcInstancePtr,Xil_InterruptHandler Handler, u32 INTC_DEVICE_ID,u32 SGI_INTR,u32 CPU_NO);/* Exception初始化函數 * 參數說明: * XScuGic *IntcInstancePtr 中斷控制器實例 */void ExceptionSetup(XScuGic *IntcInstancePtr);/* SGI觸發函數 * 該函數觸髮指定的SGI到指定的CPU上 * 參數說明: * u32 CPUNO 目標CPU號 * u32 INTR SGI中斷號 * u32 TargetSelect 選擇過濾,該參數決定了CPUNO是否有效 * TRIGGER_SELECTED 0x00000000 根據CPUNO選擇觸發的CPU * TRIGGER_SELF 0x02000000 觸發自身,此時CPUNO無效 * TRIGGER_OTHER 0x01000000 觸發除了自身的其他CPU,此時CPUNO無效 */void SGITriggerTargetCPU(u32 CPUNO,u32 INTR,u32 TargetSelect);/* 設定中斷觸發的目標CPU * 參數說明: * u32 INTR 中斷號 * u32 CPUID 目標CPU的ID號 */void SetINTRTargetCPU(u32 INTR,u32 CPUID);#endif /* SRC_ZYNQ_SGI_H_ */

兩個CPU共用的SGI的設置函數ZYNQ_SGI.c

#include "ZYNQ_SGI.h"void SetINTRTargetCPU(u32 INTR,u32 CPUID){ u8 RegValue=Xil_In8(ICDIPTR+INTR); RegValue=RegValue|(0x01<<CPUID); Xil_Out8(ICDIPTR+INTR,RegValue);}u32 SetupSGIIntrSystem(XScuGic *IntcInstancePtr,Xil_InterruptHandler Handler, u32 INTC_DEVICE_ID,u32 SGI_INTR,u32 CPU_NO){ int Status; XScuGic_Config *IntcConfig; 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, SGI_INTR,0xd0, 0x3); Status = XScuGic_Connect(IntcInstancePtr, SGI_INTR, (Xil_ExceptionHandler)Handler,0); if (Status != XST_SUCCESS) { return XST_FAILURE; } XScuGic_Enable(IntcInstancePtr, SGI_INTR); XScuGic_InterruptMaptoCpu(IntcInstancePtr,CPU_NO,SGI_INTR); return XST_SUCCESS;}void ExceptionSetup(XScuGic *IntcInstancePtr){ /* * Initialize the exception table */ Xil_ExceptionInit(); /* * Register the interrupt controller handler with the exception table */ Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, IntcInstancePtr); /* * Enable non-critical exceptions */ Xil_ExceptionEnable();}void SGITriggerTargetCPU(u32 CPUNO,u32 INTR,u32 TargetSelect){ Xil_Out32(ICDSGIR, TargetSelect+(CPU_ID_LIST<<CPUNO)+INTR);}

這個工程比較簡單只使用了串口輸出信息在任何開發板上應該都能使用。

下面照例是我實際測試的視頻:

https://www.zhihu.com/video/909209710991933440

如有問題歡迎評論留言或關注碼峰社嵌入式群541931432進行討論。


推薦閱讀:

Altera FPGA 中的 Nios II 實際中用得多不多?是否可以代替普通 MCU?
ARM伺服器進展小結
arm晶元用pcie匯流排是不是能提高數據傳輸性能和效率?

TAG:ARM | 嵌入式开发 | 现场可编辑逻辑门阵列FPGA |