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

linuxarmsummary發表於2018-12-03

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

羅賓老師

羅賓老師

嵌入式教師、碼峰社QQ群541931432

15 人讚了該文章

前面兩篇文章中我分享了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                    0xF8F01F00
void 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\n\r");
    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\n\r");
		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\n\r");
	 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_ID
XScuGic 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\n\r");
		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\n\r");
	 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 interfaces
specified in the CPUTargetList field
0b01: send the interrupt to all CPU interfaces
except the CPU interface that requested the
interrupt
0b10: send the interrupt on only to the CPU
interface that requested the interrupt
0b11: reserved*/
#define TRIGGER_SELECTED           0x00000000
#define TRIGGER_SELF               0x02000000
#define TRIGGER_OTHER              0x01000000
/*CPUTargetList bits[23:16]
 When TargetListFilter is 0b00, defines the CPU
interfaces the Distributor must send the interrupt
to.
Each bit refers to the corresponding CPU
interface.*/
#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);
}

這個工程比較簡單隻使用了串列埠輸出資訊在任何開發板上應該都能使用。

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

 

 

 

 

 

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

編輯於 2017-11-15

ARM

嵌入式開發

現場可編輯邏輯閘陣列(FPGA)

相關文章