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篇啊!顿时有了高山仰止的膜拜感,赶紧查查搜藏一下:
结果前面的搜索结果让我有点失望,米联的博客只是列出了工程项目的基本结构和步骤。我感兴趣的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
得到的结果我感兴趣的函数有几个:
读完这几个函数自己心里大概知道应该怎么做了:
1.初始化GIC中断控制器;
2.绑定中断服务函数;
3设置中断优先级和触发方式;(这条在我最初的设想中是不存在的)
4.绑定触发中断的CPU
5.允许中断
6.触发软件中断;(我在这里犯了第二个错误)
由于程序比较简单,三下五除二整吧整吧就开始debug了,结果不对,没有任何一个中断产生。这下有点懵逼了。逼得我回过头仔细看了第七章,发现了第一个错误,也就是我的第三步设置中断的触发方式,因为在UG585上是这么说的,所有的SGI均为边沿触发:
所以我加上了第三步设置中断优先级和触发方式的函数,但还是没有效果。这个坑有点深啊!
没办法继续找错误。看了一下第四步的函数操作的寄存器ICDIPTR[0:3],ICDIPTR寄存器一共有24个,每个寄存器管理4个中断一共96个中断用24个寄存器管理,用来标定每一个中断源用来中断哪个CPU。我用到的软件中断0和1,所以看了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));
<span class="n">Offset</span> <span class="o">=</span> <span class="p">(</span><span class="n">Int_Id</span> <span class="o">&</span> <span class="mh">0x3U</span><span class="p">);</span>
<span class="n">Cpu_Id</span> <span class="o">=</span> <span class="p">(</span><span class="mh">0x1U</span> <span class="o"><<</span> <span class="n">Cpu_Id</span><span class="p">);</span><span class="c1">//根据寄存器说明设置触发的CPU号
RegValue = (RegValue & (~(0xFFU << (Offset8U))) );
RegValue |= ((Cpu_Id) << (Offset8U));
<span class="n">XScuGic_DistWriteReg</span><span class="p">(</span><span class="n">InstancePtr</span><span class="p">,</span>
<span class="n">XSCUGIC_SPI_TARGET_OFFSET_CALC</span><span class="p">(</span><span class="n">Int_Id</span><span class="p">),</span> <span class="n">RegValue</span><span class="p">);</span>
//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;
<span class="cm">/*
* 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) ;
<span class="cm">/*
* 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);
<span class="cm">/*
* Write to the Software interrupt trigger register. Use the appropriate
* CPU Int_Id.
*/
XScuGic_DistWriteReg(InstancePtr, XSCUGIC_SFI_TRIG_OFFSET, Mask);
<span class="cm">/* Indicate the interrupt was successfully simulated */</span>
<span class="k">return</span> <span class="n">XST_SUCCESS</span><span class="p">;</span>
}
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;
}
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
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();
<span class="cm">/*
* 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);
}
这个工程比较简单只使用了串口输出信息在任何开发板上应该都能使用。
下面照例是我实际测试的视频: