目 录CONTENT

文章目录

AUTOSAR知识点 之 多核启动 (一):英飞凌单片机的多核启动详细解析

moke
2024-07-27 / 0 评论 / 0 点赞 / 74 阅读 / 0 字

1、概述

        英飞凌芯片的多核启动牵涉初始化核寄存器、初始化堆栈与上下文信息、初始化RAM、同步核等步骤,思维导图如下:

2、英飞凌单片机多核启动步骤

2.1、第一步:链接文件定义入口BMI

Core0 main 之前MCU干了什么?

BMI指定,英飞凌的BMI是软件运行开始的地方,没有BMI无法启动软件运行的,BMI定义如下:

/* BMI Header to be placed at location 0xA0000000 */
typedef struct 
{
  const uint32 stadabm;     
  const uint16 bmi;        
  const uint16 bmhdid;       
  const uint32 chkstart;    
  const uint32 chkend;       
  const uint32 crcrange;     
  const uint32 crcrangeinv;  
  const uint32 crchead;      
  const uint32 crcheadinv;   
}BMD_HDR;

通过链接文件指定到如下四个位置中的任意一个即可。

BMI的指定结构如下

具体代码如下

BMI      (rx)   : org = 0x80000000, len = 0x20
 
   . BMI_HDR : ALIGN(4)
   {
      *(.BMI_HDR *);
} > BMI
#define BMD_HDR_START
#include "MemMap.h"
const BMD_HDR BMIHEADER =
{
 
};
#define BMD_HDR_STOP
#include "MemMap.h"
 
#elif defined (BMD_HDR_START)
   #undef      BMD_HDR_START   #pragma section ". BMI_HDR " a   #define BMD_HDR_START
#elif defined (BMD_HDR_STOP)
   #undef      BMD_HDR_STOP
   #pragma section

2.2、第二步:定义入口ENTRY(symbol)

        链接文件指定入口函数,mcu 启动的地址有很多方式,这里介绍链接文件指定启动位置。使用ENTRY 指定一个symbol

        ENTRY(cstart)

        程序中要执行的第一条指令称为入口点。您可以使用ENTRY链接器脚本命令来设置入口点。参数是一个符号名:

        有几种方法可以设置入口点。链接器将依次尝试以下方法来设置入口点,当其中一个方法成功时停止:

这里面我们就可以在代码里定义一个函数。函数名字叫做

void cstart(void)

这里面 函数的链接位置,也是通过链接文件来指定。这里不赘述,一般都是”从头开始“。

例如如下定义

1、定义map文件

#elif defined CSTART_START_SEC_CODE
   #undef CSTART_START_SEC_CODE
   #pragma section ".text.cstart" ax 4
#elif defined CSTART_STOP_SEC_CODE
   #undef CSTART_STOP_SEC_CODE
   #pragma section

2、匹配cstart的函数到map

#define CSTART_START_SEC_CODE
#include "MemMap.h"
void cstart(void) ;
#define CSTART_STOP_SEC_CODE
#include "MemMap.h"

3、定义cstart的地址

PFLASH0_CPU0_STARTUP     (rx!p)      : org = 0x8000 0100, len = 0x100

4、定义链接文件

   .CPU0_CSTART_CODE : ALIGN(4)
   {
      *(.text.cstart);
   } > PFLASH0_CPU0_STARTUP

这样就能将函数放置到固定地址,链接文件有描述文章。

以英飞凌Tc27x举例如下:

BMI结构体放置在地址:0x8000 0000 ,BMI结构体的第一个元素为用户代码开始地址,例如实际项目里面的实现

ENTRY(_RESET)其中的symbol为_RESET函数,函数入口地址为0x8000 0020,也就是BMI结构体的第一个元素,

复位函数如下,其实不是复位,只是名字这样子而已,随便命名吧

void _RESET (void)
{
  __asm (".global _START");
  /* we must make a jump to cached segment, why trap_tab follow */
#ifdef _GNU_C_TRICORE_
 
  __asm ("_START: ja cstart");
}

下面跳入cstart函数,此处就是用户代码开始的地方,用链接文件进行指定即可。

2.3、第三步:CSTART函数

cstart 是上电后,MCU 指定的第一个函数入口,第一步执行的语句就是

2.3.1、获取核ID

coreID = __MFCR(0xFE1C);

就是说把Core 寄存器 0xFE1C 的数 拿给CoreID这个 变量。

CoreID寄存器如下:

        在多处理器系统中,每个逻辑处理器核心被赋予一个唯一的标识号。核心标识寄存器保存这个号码。

Cstart的处理如下

2.3.2、概念:一般通用寄存器

Core Registers处理,首先解释一下Core Registers,如下图所示

一般通用寄存器分为两个部分16个数据寄存器DGPRs D[0]~D[15],16个地址寄存器 A[0]~A[15]。

初始化栈指针到A10

   .CPU0_PRIVATE_SDATA :
   {
      __CPU0_SMALL_DATA = . + 0x8000;
      KEEP(*(.sdata.CPU0.Private*));
      *(.sdata);
      *(.sdata*);
      *(.gnu.linkonce.s.*);
      __CPU0_SMALL_DATA_end = .;
   } > CPU0_DSPR  AT > PFLASH0_CPU0_PRIVATE

地址寄存器如下:

数据寄存器如下:

2.3.3、初始化SP,也就是A10

SetReg (SP, StackAddress);

2.3.4、数据同步

DSYNC()

{

   __asm__ volatile ("dsync" : : : "memory");

}

什么意思呢:就是保证所有的数据都搞定了,在开始访问下一个数据,也就是说保证做CSA前A10搞好了。

为了确保内存一致性,在任何访问活动CSA内存位置之前。必须执行DSYNC指令

2.3.5、设置PSW

下一步需要设置PSW寄存器,寄存器如下

        程序状态字寄存器(PSW)是一个32位寄存器,它包含在通用寄存器值中没有捕获的特定于任务的体系结构状态。下半部保存与保护系统相关的控制值和参数,包括:

此值的设置一般为

MTCR (CPU_PSW, 0x00000980);

开启栈深度count、

开启管理者模式、

开启寄存器的读写权限。

2.3.6、设置PCXI寄存器

        前一个上下文信息寄存器(PCXI)包含到前一个执行上下文的链接信息,支持中断和自动上下文切换。PCXI是任务状态信息的一部分。前一个上下文指针(PCX)保存前一个任务的CSA的地址。

其实PCXI也就是链接字。

2.3.7、概念:CSA-上下文

此处参考大佬文章介绍一下CSA。

上下文保存到内存时,占用16个字块,称为上下文保存区(csa)。

        我们在调试器打开CPU 寄存器能找到PCXI,这个是链接字。这里以链接字为0x003706BE 为例。

找到此时存储上下文的RAM空间,计算方式如下

计算结果如下

找到结果如下

相关事件和说明

        上下文在任务切换过程中总是保存或者读取。新任务抢占老任务,老任务的上下文会被抢占,新任务结束,继续执行老任务,老任务的上下文会被重读恢复读取使用。后进先出的过程。那么下面看一下在哪些情况下上下文会被操作。

        和上面说的一样,高级context保存是系统自动保存。在中断发生,进入trap,函数调用,换句话说 当系统发生需要使用另一个context的时候。高级context就会被自动保存。然而lower context则需要相应的指令去保存。这里提一下,lower 和 upper 对应的地址,对应的链接字,大小格式,都是一样的。比如配置了64个上下文位置,那么这是lower + upper 一共可以用的空间。所以可以通过程序状态字去查看这个context具体指的是lower or upper.PSW有寄存器标志位显示的。

        PCX, FCX 这两个分别的 free context List 和 Previous context List 对应的就是用过了的和可用的链接字的位置。那么问题来了,会不会存在用超了的现象,用超了会有措施吗。这里就到提到LCX 这个寄存器,指向的是最后的某个context 所以 当系统认为的 free context  和 LCX 相等时 就需要注意了。Context 马上就要超了。

好几个CSA需要存储后面文章会进行介绍。

做指令同步:ISYNC();以上所有指令均由CPU执行。然后在执行下一条指令之前刷新线程。

2.3.8、CSA初始化

第一步通过PCXI也就是链接字解析出来RAM地址,然后将最后的CSA存储在LCX里面。

MTCR (CPU_LCX, PCXI_Address_Last);

实际的的CSA地址值存储在FCX里面

MTCR (CPU_FCX, PCXI_Address_Real);

2.3.9、指令同步

ISYNC(); isync 来确保 指令的操作完整性

以上所有指令均由CPU执行。然后在执行下一条指令之前刷新线程。

2.3.10、禁狗

2.3.11、设置SMU

设置SMU属于可以选择的操作,看软件应用情况进行设置。

2.3.12、使能ICACHE

步骤如下

1、将bit位PCBYP设置为1

MTCR(CPU_PCON0,2);

2、指令同步

ISYNC();

3、将PCON1的PCINV置1

然后读取此寄存器的bit位,直到变为0

#define  PCON1_PCINV  0x01

while((MFCR(CPU_PCON1) & PCON1_PCINV) == PCON1_PCINV);

以上步骤均需要解锁寄存器再操作,也就是调用

void Mcal_SetCpuENDINIT(const sint8 wdt)

4、将PCON0的PCBYP位置位为0

MTCR(CPU_PCON0,0);

2.3.13、使能DCACHE

使能DCACHE相对来说比较简单

直接使能即可

MTCR(CPU_DCON0,0);

注意一下使能的DCACHE与ICACHE之后一定需要调用ISYNC();与DSYNC();

2.3.14、给中断向量表分配栈区

在这里需要先给中断向量表分配栈区,然后在后面的任意时刻都可以初始化中断向量表。

MTCR(CPU_ISP, IStackAddress) ;

2.3.15、初始化A0\A1\A8\A9

   Ifx_Ssw_setAddressReg(a0, __SDATA1(0));

   Ifx_Ssw_setAddressReg(a1, __SDATA2(0));

   Ifx_Ssw_setAddressReg(a8, __SDATA3(0));

   Ifx_Ssw_setAddressReg(a9, __SDATA4(0));

2.3.16、对Flash的读权限等状态设置

2.3.17、RAM初始化

        ram初始化主要有两个方面。一个是从对应的falsh 拿数值放到ram 里面。

一个是初始化直接是0.

Copytabble一般是链接文件的方式,代码解析如下

IFX_SSW_INLINE void Ifx_Ssw_C_InitInline(void)
{
   Ifx_Ssw_CTablePtr pBlockDest, pBlockSrc;
   unsigned int      uiLength, uiCnt;
   unsigned int     *pTable;
   /* clear table */
   pTable = (unsigned int *)&__clear_table;
while (pTable)
  {
       pBlockDest.uiPtr = (unsigned int *)*pTable++;
       uiLength         = *pTable++;
 
       /* we are finished when length == -1 */
       if (uiLength == 0xFFFFFFFF)
      {
           break;
      }
 
       uiCnt = uiLength / 8;
 
       while (uiCnt--)
      {
           *pBlockDest.ullPtr++ = 0;
      }
 
       if (uiLength & 0x4)
      {
           *pBlockDest.uiPtr++ = 0;
      }
 
       if (uiLength & 0x2)
      {
           *pBlockDest.usPtr++ = 0;
      }
 
       if (uiLength & 0x1)
      {
           *pBlockDest.ucPtr = 0;
      }
  }
   /* copy table */
   pTable = (unsigned int *)&__copy_table;
 
   while (pTable)
  {
       pBlockSrc.uiPtr  = (unsigned int *)*pTable++;
       pBlockDest.uiPtr = (unsigned int *)*pTable++;
       uiLength         = *pTable++;
 
       /* we are finished when length == -1 */
       if (uiLength == 0xFFFFFFFF)
      {
           break;
      }
 
       uiCnt = uiLength / 8;
 
       while (uiCnt--)
      {
           *pBlockDest.ullPtr++ = *pBlockSrc.ullPtr++;
      }
 
       if (uiLength & 0x4)
      {
           *pBlockDest.uiPtr++ = *pBlockSrc.uiPtr++;
      }
 
       if (uiLength & 0x2)
      {
           *pBlockDest.usPtr++ = *pBlockSrc.usPtr++;
      }
 
       if (uiLength & 0x1)
      {
           *pBlockDest.ucPtr = *pBlockSrc.ucPtr;
      }
  }
}

2.4、进入Main

思维导图如下

2.4.1、Main

Main函数原型如下

int main(void) 
{ 
(
MTCR(0xfe04, 0x200 | (MFCR(0xfe04)));); 
ISYNC();      
   Os_StartCoreGate(); 
   EcuM_Init();
   return 0; 
}

        有了ECUM算是与AUTOSAR接轨了,注意一下上述代码均是在Core0里面执行的,启动从核是在Ecum里面执行的。

2.4.2、API: Os_StartCoreGate

Os_StartCoreGate();函数原型如下

FUNC(void, OS_CODE) Os_StartCoreGate(void) {
 uint32 core = OS_MFCR(0xfe1c);
 /* Prevent secondary cores starting before given permission in Os_Cbk_StartCore() */
 while ((core == 1U) && (Os_StartBlock[0] != 0xa55a5aa5U)) {OS_NOP();}
 while ((core == 2U) && (Os_StartBlock[1] != 0xa55a5aa5U)) {OS_NOP();}
}

2.4.3、API: EcuM_Prv_StartSlaveCores

ECUM里面有个配置项

启动从核

函数原型如下

void EcuM_Prv_StartSlaveCores( void )
{
/*local variables*/
   StatusType dataStatus_chr = E_NOT_OK;
   uint16 cntrLoopCtr_u16;
/*Starting all the OS Cores in a loop*/
       for( cntrLoopCtr_u16=0; cntrLoopCtr_u16<ECUM_CFG_NUM_OS_CORES ; cntrLoopCtr_u16++ )
      {
           StartCore( cntrLoopCtr_u16, &dataStatus_chr);
           if(dataStatus_chr != E_OK)
          {
               /* StartCore Failed*/
               EcuM_ErrorHook(ECUM_E_START_CORE_FAILED);
          }
      }
}

2.4.4、API: StartCore

其中有个函数

StartCore( cntrLoopCtr_u16, &dataStatus_chr);

cntrLoopCtr_u16参数就是核号,函数原型如下

FUNC(void, OS_CODE) Os_StartCore(CoreIdType CoreID, Os_StatusRefType Status) {
 *Status = E_OK; /* [$UKS 1628] */
 if (CoreID >= 3U) {
   *Status = E_OS_ID; /* [$UKS 1629] */
} else if (*Os_const_coreconfiguration[CoreID].state > 1U) {
   *Status = E_OS_ACCESS; /* [$UKS 1631] */
} else if (*Os_const_coreconfiguration[CoreID].state != 0U) {
   *Status = E_OS_STATE; /* [$UKS 1632] [$UKS 1633] */
} else {
   /* OK */
}
 if (*Status == E_OK) {
   /* [$UKS 1634] */
  (*Os_const_coreconfiguration[CoreID].state) = 1U;  /* Started */
   if ((CoreIdType)OS_MFCR(0xfe1c) != CoreID) {
     *Status = Os_Cbk_StartCore(CoreID);
  }
}
 return;
} /* StartCore */

2.4.5、API:Os_Cbk_StartCore

Os_Cbk_StartCore函数是关键点

FUNC(StatusType, OS_CALLOUT_CODE) Os_Cbk_StartCore(uint16 CoreID) {
 StatusType ret = E_OS_ID;
 
 if (CoreID == 1U) {
   Os_StartBlock[0] = 0xa55a5aa5U;
#ifndef _lint /* Skip lint checks on compiler-specific ornamentations */
   CPU1_PC.U = (uint32)cstart;
   CPU1_SYSCON.B.BHALT = 0U;
#endif
   ret = E_OK;
 
} else if (CoreID == 2U) {
   Os_StartBlock[1] = 0xa55a5aa5U;
#ifndef _lint /* Skip lint checks on compiler-specific ornamentations */
   CPU2_PC.U = (uint32)cstart;
   CPU2_SYSCON.B.BHALT = 0U;
#endif
else {
   /* Not an expected core! */
}
return ret;
}

2.4.6、注意点

其实到最后也就是有几个核执行几次cstart函数而已,注意是在对应的核里面执行的。

另外注意一个点FUNC(void, OS_CODE) Os_StartCoreGate(void)里面不包含核0,一定要看清。

博主关闭了所有页面的评论