AUTOSAR OS模块详解(五) 中断ISR
本文主要介绍AUTOSAR OS的中断ISR,并对基于英飞凌Aurix TC3XX系列芯片的Vector Microsar代码和配置进行部分讲解。
文章目录
1 简介
对于嵌入式系统来说,实时性是一个非常重要的特性,相比较于桌面端系统,嵌入式系统往往部署在各种硬件设备上,这些硬件设备的控制往往都要求毫秒甚至微秒级的反馈。而在车规级控制器中,有些硬件功能的实时性要求就更为严苛。
关于Aurix TC3XX系列芯片的中断系统,我们已经专门出过相关文章进行介绍,本文就从纯软件的角度,解析Autosar Os中的中断定义及处理逻辑。
2 功能介绍
2.1 中断分类
在Autosar Os中,中断分为两类,就叫做一类中断和二类中断。其中一类中断的执行不经过Os的调度管理,中断发生后直接到达中断业务逻辑,对于Os来说是无感的,而二类中断则是需要Os进行调度管理的,有专门的程序控制块。除此之外,二者还具有以下区别:
-
一类中断的优先级恒高于二类优先级(配置强限制);
-
临界区层级不同,例如SuspendOsInterrupt只关闭Os中断而不关闭一类中断,SuspendAllInterrupt则关闭所有中断;
-
因为一类中断不被Os管理,因此一类中断的处理过程中不需要切换线程和保存上下文;
一般情况下,对于外设等的中断,如DMA、SPI、PWM等与OS并非直接关联的,建议设置为一类中断,省去了Os调度的消耗;而对于CAN、Eth等与协议栈MainFunction等关联性较强的中断,甚至需要Os资源提供数据保护的,建议使用二类中断。
2.2 中断源
在Vector DaVinci工具中配置中断,要区别于在EB中配置中断,在EB中每个模块都详细列出来,而在DaVinci工具中,还需要配置一个中断源,顾名思义,该中断源用于指定该中断连接到哪个硬件中断信号上。中断源填写的是所使用中断的SRC寄存器偏移地址,具体需要查询芯片用户手册,下面我们以CAN中断为例介绍如何查询。
我们这里配置使用CAN0INT0,关于CAN中断及其连接,读者可以翻阅本公众号发布的CAN模块的介绍,此处不进行赘述。
我们来到所使用芯片的UserMannual中,注意是系列手册,不是family手册,这里我的芯片是TC387,使用的TC38x-UserManual。打开16.4 IR章节中的TC38x Specific Service Request Control (SRC) registers。
这里我们可以看到两个表,一个是Table 262 Register Address Space - SRC,里面说明了SRC寄存器地址范围,另一个是Table 263 Register Overview - SRC (ascending Offset Address),里面说明了所有该芯片各个中断的SRC寄存器偏移地址,也就是我们需要查阅的目标。
我们在第二个表中搜索CAN可以找到如下条目:
我们需要查找的是CAN0INT0,这里将x、y代入得出偏移地址为0x5B0,即1456,后续我们在中断源配置中需要填入该值。
另外值得一提的是Os Counter的中断源是不需要我们填的,我们在Counter配置中选定了硬件定时器后工具会自动进行配置。
2.3 中断配置
OS配置这里我们选择Os Counter作为示例进行演示,虽然该中断有些配置项是OS自动配置的,但是配置项和其他我们添加的配置也是一样的。
OsIsrShortName
中断的ShortName和其他元素的不太一样,我们自行添加的中断没有Special Function Name那项配置的话,Os会使用ISR这个宏对ShortName进行展开,进行中断入口函数的声明,如ISR(CanIsr_0)展开成Os_Isr_CanIsr_0。
OsIsrCategory
中断类型,一类还是二类。一类的话无法由OS控制,所以下面的中断嵌套和中断使能都无法勾选。另外如果我们要配置Trap向量表(Vector OS支持用户配置部分Trap,这个我们以后在Os异常处理中介绍),这里不能选为二类。
OsIsrEnableNesting
是否可以嵌套,如果可以嵌套,则该中断可以被高优先级中断打断。
OsIsrInitialEnableInterruptSource
是否由OS使能该中断,一般默认选择开启,OS会在初始化后使能该中断,无需用户手动操作。一类中断此处无法配置,所以一类的初始化和使能需要用户在初始化中实现。
OsIsrInterruptMapping
中断映射,我们知道TC3XX系列芯片可以将中断路由到DMA中,这里可以选择DMA进行路由。
OsIsrInterruptPriority
中断优先级,与硬件相关,这里即TC3XX的中断优先级,数字越大优先级越高,中断入口代码在向量表中按优先级排布。
OsIsrInterruptSource
中断源,即前文提到的硬件中断关联,Os Counter的中断源是根据用户选的定时器自动计算的。这里为了演示配置方法,我们查询手册,我选的定时器是STM0(默认Compare0中断),其中断源在手册中为0x300,即上述配置中的768。
OsIsrInterruptType
中断类型,这里配置为EXTERNAL即可,如果是配置Trap的话,则要配置为EXCEPTION。
OsIsrMemoryProtectionIdentifier
内存保护的识别号,Task和Isr的内存保护我们一般不单独配置,因为芯片没有那么多的Set足够每个Task或者Isr单独配置,一般是继承Application的识别号。
OsIsrSpecialFunctionName
指定的中断函数,因为Counter的中断函数是Vector内置的,所以这里强行配置了Os_TimerPfrtIsr,这个函数我们在Counter章节中也介绍过。
OsIsrStackSize
二类中断栈大小,一般情况下1024就够用了,甚至偏多,除非是用户手写代码。一类中断不由OS管理,这里配置了也没用。需要注意的是一类中断在进入时直接使用当前线程的栈,如果使用较多则需要注意。
OsIsrUsesFpu
是否使用浮点运算单元,在ARM等芯片中,FPU这类协处理器是具有独立的寄存器的,因此Task/Isr切换的过程中,是需要保存FPU上下文的。TriCore内核芯片FPU没有单独的上下文,这里不使能即可。
Cross-core channel receiver interrupt
多核通道接收中断,Vector中利用TC3XX的软中断,实现X-Signal跨核通信,对应的中断这里需要配置使能。
Timing Protection Enabled
是否使用时间保护,在SC2和SC4中,该配置可以用于中断时间监控。
2.4 中断向量表
在TC3XX中断文章中,我们介绍过,该系列芯片的中断向量表是一段指定的连续Flash地址,按照优先级进行中断入口函数的排布。
在Vector代码中,该向量表由OS维护,统一进行中断入口的管理,包括一类中断。
在Os_Hal_Entry_Lcfg.c中我们可以看到该向量表,是以Memmap包裹的特殊宏定义。
Memmap的功能是将该段代码以OS_INTVEC_CORE0_CODE的section装载到指定内存中,并且中断向量表基址寄存器的初始化也会使用该代码段的地址标签。
每个中断优先级都以32字节对齐分配了一定的空间,用来存放中断入口,进行中断函数跳转。未配置的中断则使用Os_Hal_UnhandledInterruptEntry展开,用于异常触发时的处理。
#define OS_START_SEC_INTVEC_CORE0_CODE
#include "Os_MemMap_OsSections.h"
Os_Hal_InterruptSectionDeclaration(0)
Os_Hal_UnhandledInterruptEntry(0, 0)
...
Os_Hal_Cat2InterruptEntry(0, 80, OS_CFG_HAL_ISR2_DISABLE_LEVEL, OsCfg_Isr_CounterIsr_SystemTimer)
...
Os_Hal_Cat2InterruptEntry(0, 85, OS_CFG_HAL_ISR2_DISABLE_LEVEL, OsCfg_Isr_CanIsr_0)
...
#define OS_STOP_SEC_INTVEC_CORE0_CODE
#include "Os_MemMap_OsSections.h"
在使用Tasking编译器时,一类中断宏按照如下形式展开,我们可以看到其仅仅声明了一个函数名,保存低上下文供中断使用,然后根据用户配置的中断函数进行跳转,并未做其他处理,所以我们说一类中断不经过OS管理。
# define Os_Hal_Cat1InterruptEntry(core, level, function) \
__asm (" .SECT \".text.OS_INTVEC_CORE"#core"_CODE\""); \
__asm (" .ALIGN 32"); \
__asm (" .EXTERN " #function); \
__asm (" .GLOBAL osIsrLevel_" #level "_Core" #core); \
__asm ("osIsrLevel_" #level "_Core" #core": svlcx"); \
__asm (" call " #function);\
__asm (" rslcx");\
__asm (" rfe");\
二类中断的宏展开内容多了一些,仍然声明一个向量表函数名,然后这里extern了一个全局变量,是中断的配置结构体,作为入参传递到Os的二类中断处理函数中,比如CAN中断这里传入的就是前面的OsCfg_Isr_CanIsr_0。
所有的二类中断都要进入Os_Hal_IsrRun函数,在该函数中进行上下文切换,线程处理,然后才能进入实际中断执行代码。所以说二类中断是由OS进行管理的。
# define Os_Hal_Cat2InterruptEntry(core, level, systemlevel, isrconfig) \
__asm (" .SECT \".text.OS_INTVEC_CORE"#core"_CODE\""); \
__asm (" .ALIGN 32"); \
__asm (" .EXTERN " #isrconfig); \
__asm (" .EXTERN Os_Hal_IsrRun"); \
__asm (" .GLOBAL osIsrLevel_" #level "_Core" #core); \
__asm ("osIsrLevel_" #level "_Core" #core": svlcx"); \
__asm (" movh.a a4, #@his("#isrconfig")"); \
__asm (" lea a4, [a4]@los("#isrconfig")"); \
__asm (" call Os_Hal_IsrRun"); \
__asm (" rslcx"); \
__asm (" rfe");
2.5 代码分析
2.5.1 主要数据结构
中断Isr的主要数据结构为Os_IsrConfigType,我们到Os_Isr_Lcfg.c文件中可以找到该数据,Isr对应的变量名为 OsCfg_Isr_前缀加上中断Isr的配置名称。
另外如果是Os Counter中断,会特殊一点,类型为Os_TimerIsrConfigType,其中包含一个Os_IsrConfigType和一个Os_CounterConfigType的指针。这里就以Timer中断为例。
CONST(Os_TimerIsrConfigType, OS_CONST) OsCfg_Isr_CounterIsr_SystemTimer =
{
/* .Isr = */
{
/* .Thread = */
{
/* .ContextConfig = */ &OsCfg_Hal_Context_CounterIsr_SystemTimer,
/* .Context = */ &OsCfg_Hal_Context_OsCore0_Isr_Level5_Dyn,
/* .Stack = */ &OsCfg_Stack_OsCore0_Isr_Core,
/* .Dyn = */ OS_ISR_CASTDYN_ISR_2_THREAD(OsCfg_Isr_CounterIsr_SystemTimer_Dyn),
/* .OwnerApplication = */ &OsCfg_App_SystemApplication_OsCore0,
/* .Core = */ &OsCfg_Core_OsCore0,
/* .IntApiState = */ &OsCfg_Core_OsCore0_Dyn.IntApiState,
/* .TimeProtConfig = */ NULL_PTR,
/* .MpAccessRightsInitial = */ &OsCfg_Mp_CounterIsr_SystemTimer,
/* .AccessRights = */ &OsCfg_AccessCheck_NoAccess,
/* .Trace = */ NULL_PTR,
/* .FpuContext = */ NULL_PTR,
/* .InitialCallContext = */ OS_CALLCONTEXT_ISR2,
/* .PreThreadHook = */ NULL_PTR,
/* .InitDuringStartUp = */ FALSE,
/* .UsesFpu = */ FALSE
},
/* .SourceConfig = */ &OsCfg_Isr_CounterIsr_SystemTimer_HwConfig,
/* .IsrId = */ CounterIsr_SystemTimer,
/* .IsEnabledOnInitialization = */ FALSE
}
,
/* .Counter = */ OS_COUNTER_CASTCONFIG_TIMERPFRT_2_COUNTER(OsCfg_Counter_SystemTimer)
};
该结构体是该Isr的句柄,相当于线程控制块。其中ContextConfig指向的结构体为OsCfg_Hal_Context_CounterIsr_SystemTimer,包含了栈信息等,其中Entry是该Isr的入口函数,ReturnAddress是中断调用栈的栈底Os_TrapIsrEpilogue,中断执行完毕之后在这里切回原有线程。
CONST(Os_Hal_ContextConfigType, OS_CONST) OsCfg_Hal_Context_CounterIsr_SystemTimer =
{
/* .StackEndAddr = */ (uint32)(OS_STACK_GETHIGHADDRESS(OsCfg_Stack_OsCore0_Isr_Core_Dyn)+1),
/* .StackStartAddr = */ (uint32)OS_STACK_GETLOWADDRESS(OsCfg_Stack_OsCore0_Isr_Core_Dyn),
/* .ProgramStatus = */ (uint32)OS_HAL_PSW_IS_MASK | OS_HAL_PSW_CDE_MASK | OS_HAL_PSW_IO_SUPERVISOR | OS_HAL_PSW_S_MASK | OS_HAL_PSW_PRS_PS0,
/* .Entry = */ (uint32)&Os_Isr_Os_TimerPfrtIsr,
/* .ReturnAddress = */ (uint32)&Os_TrapIsrEpilogue,
/* .IntStatus = */ ((uint32)130<<OS_HAL_PCXI_PCPN_BIT_POSITION) | OS_HAL_PCXI_PIE_ENABLED
};
Context所指向的OsCfg_Hal_Context_OsCore0_Isr_Level5_Dyn为该Isr的上下文信息,包括返回地址、CSA等信息,如下图所示。
Dyn所指向结构体指示了该Isr的各种动态状态量,包括运行状态,运行时优先级等。在实际调试过程中可用于观察该中断状态。
2.5.2 中断Isr初始化阶段
关于Os的初始化,在本系列Counter章节中已经介绍过,这里就不赘述了,与Counter、Task等相同,Isr的初始化也是在Os_AppInit中进行的,其调用栈如下图所示。
中断的初始化相对于Task要简单,只有一个线程的初始化。
2.5.3 中断使能
中断在完成初始化之后,仅仅是配置了中断状态相关的寄存器此时中断还不能够运行,还需要使能中断。
一类中断的使能是需要用户手动操作的,推荐在EcuM_AL_SetProgrammableInterrupts函数中执行。
对于Os Counter的中断,之前的文章已经介绍过了,会在Timer的初始化中启动Os Tick所使用的中断。
而对于普通的二类中断,则是在Default_Init_Task_Trusted任务中进行使能的(前提是勾选了OsIsrInitialEnableInterruptSource),该任务是Os的内置任务,用户不可配置其中的Rbl,优先级也是建议配置为最高,启动Os之后优先执行。
在该任务中调用Os_InitialEnableInterruptSources函数,随后一路调用到Os_Hal_IntEnableSource函数中,在其中写入中断的SRC寄存器中SRE位域,使能该中断。
2.5.4 中断执行
一类中断的执行不经过Os进行处理,因此一类中断在经过中断向量表之后,直接跳转到其中断执行函数,此处不进行赘述。
对于二类中断,这里以Counter中断CounterIsr_SystemTimer为例,我们将断点打到中断向量表中。
可以看到该段汇编首先保存了低上下文,以供中断函数使用,然后将0x800255f4作为指针入参传递该Os_Hal_IsrRun函数中,我们在Map里搜索该地址发现它就是前文提到的OsCfg_Isr_CounterIsr_SystemTimer,也就是该中断的句柄。
然后我们从Os_Hal_IsrRun将其调用时序图画出。
在进入Os_Hal_IsrRun之后,会直接调用Os_IsrRun,然后进入Os的中断处理函数。所有的二类中断都会经过这个函数,在这里进行线程的切换以及上下文的处理。
首先会通过Os_IntSuspend关闭中断(虽然硬件在进入中断时已经关闭,但对于Os需要保证自身完整性),如果使能了0类中断,接下来会重新打开,以保证0类中断能够进入。
然后使用Os_CoreInterruptedThreadsPush记录中断嵌套次数。
接下来就是通过Os_ThreadSuspendAndStart进行线程的上下文处理了。
{
Os_StackOverflowCheck();
/* #15 Store FPU context if needed */
Os_ThreadStoreFpuContext(Current);
/* #20 Resume memory protection access rights of Next. */
Os_MpSwitch(Current->Dyn->MpAccessRights, Next->Dyn->MpAccessRights);
/* #25 Switch current thread pointer. */
Os_CoreSetThread(Next);
/* #30 Call PreThreadHook. */
Os_ThreadCallPreThreadHook(Next, CallPreThreadHook);
/* #40 Set the current stack pointer. */
Os_CoreSetCurrentStack(Next->Stack, Next->Core);
/* #50 Prepare context of next thread. */
Os_Hal_ContextInit(Next->ContextConfig, Next->Context);
/* #60 Setup FPU for next thread. */
Os_ThreadInitFpu(Next);
/* #70 Set parameter for the entry function, currently only used for hooks (hook thread pointer). */
Os_Hal_ContextSetParameter(Next->Context, Next);
/* #80 Update the Msr of the next thread. */
Os_Hal_ContextSetUserMsrBits(Next->Context);
/* #90 Perform context switch. */
Os_Hal_ContextCallOnStack(Current->Context, Next->Context);
/* #100 Restore FPU context if needed */
Os_ThreadRestoreFpuContext(Current);
}
在这里首先进行当前线程的栈检查,然后保存FPU上下文(如果需要),切换内存保护集,设置线程全局指针等上下文操作,然后在Os_Hal_ContextCallOnStack中进行上下文切换,切换到目标中断线程中。
在Os_Hal_ContextCallOnStack中首先将当前线程的上下文信息进行保存,如PCXI、返回地址等,在Os_Hal_ContextIntRestore中加载中断线程的内容然后完成切换动作,然后我们就来到了实际要处理的中断函数Os_TimerPfrtIsr中。
到这里Os真正开始处理对应的二类中断函数,也就是用户定义的业务代码。
当中断函数执行完毕之后,返回线程入口地址Os_TrapIsrEpilogue,然后调用Os_IsrEpilogue函数,在其中返回被打断的Task,或者返回被打断的其他中断,这样就切出了该中断线程,至此,该中断处理完成。
3 小结
本文介绍了Autosar Os中的中断Isr定义及其实现,就其类型及工具配置展开了介绍。通过对Vector Os实现机制进行了代码解析,详细介绍了中断Isr的处理过程。
如您有任何问题,欢迎关注公众号【TechLink汽车软件】与我们联系!