Autosar Os MCU 多核 启动_core_sec-程序员宅基地

技术标签: infineon  单片机  

1.1 core0 main 之前MCU 干了什么

1.1.2 链接文件指定入口函数

mcu 启动的地址有很多方式,这里介绍链接文件指定启动位置。使用ENTRY 指定一个symbol (不知道咋翻译)

ENTRY(cstart)

ENTRY 是 编译器给的link文件命令。

The first instruction to execute in a program is called the entry point. You can use the ENTRY
linker script command to set the entry point. The argument is a symbol name:
ENTRY(<symbol>)
There are several ways to set the entry point. The linker will set the entry point by trying each
of the following methods in order, and stopping when one of them succeeds:
 the ’-e’ <entry> command-line option;
 the ENTRY(<symbol>) command in a linker script;
 the value of the symbol start , if defined;
 the address of the first byte of the . text section, if present;
 The address 0.

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

void cstart(void)

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

这里我们要注意一点。cstart 每个core 都可以来调用。我们来看一下cstart 的实现。

void cstart(void)
{
    
   unsigned int coreID;
   coreID = __MFCR(0xFE1C);

   if(coreID == 0u)
  {
    
       Ifx_Ssw_jumpToFunction(__StartUpSoftware);
  }
   if(coreID == 1u)
  {
    
       Ifx_Ssw_jumpToFunction(__Core1_start);
  }
   if(coreID == 2u)
  {
    
       Ifx_Ssw_jumpToFunction(__Core2_start);
  }
   if(coreID == 3u)
  {
    
       Ifx_Ssw_jumpToFunction(__Core3_start);
  }
   if(coreID == 4u)
  {
    
       Ifx_Ssw_jumpToFunction(__Core4_start);
  }
   if(coreID == 5u)
  {
    
       Ifx_Ssw_jumpToFunction(__Core5_start);
  }
}

1.1.3 __StartUpSoftware

由前面可以知道cstart 是上电后,MCU 指定的第一个函数入口。

第一句有个指令 __MFCR

Move from core register

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

这个寄存器是什么呢?

在这里插入图片描述
所以当不同的core 来执行 该指令的时候,返回值就是该core的coreID.

到了这里我们开始启动core0。

void __StartUpSoftware(void)

首先这里对A1 寄存器进行了初始化。

Ifx_Ssw_setAddressReg(a1, __SDATA2(0));

实际上就是把SDATA2(0) 数值 set 给 a1 寄存器。那么 SDATA2(0) 是什么呢?

#define __SDATA1(cpu)     __A0_MEM
#define __SDATA2(cpu)     __A1_MEM
#define __SDATA3(cpu)     __A8_MEM
#define __SDATA4(cpu)     __A9_MEM

那么问题来了 __A1_MEM 又是哪里来的呢?

这个又涉及到了链接文件。

 CORE_SEC(.sdata2) : FLAGS(arsl)
  {
    
      *(.srodata)
      *(.srodata.*)
  } > default_rom
  _SMALL_DATA2_ = SIZEOF(CORE_SEC(.sdata2)) ? ADDR(CORE_SEC(.sdata2)) : (ADDR(CORE_SEC(.sdata2)) & 0xF0000000) + 32k ;
  __A1_MEM = _SMALL_DATA2_;  

这个是什么意思呢?

这段代码是用于计算SMALL_DATA2_的值。首先,它使用SIZEOF(CORE_SEC(.sdata2))来获取.sdata2段的大小,然后判断该大小是否为0。如果不为0,则将ADDR(CORE_SEC(.sdata2))赋值给SMALL_DATA2_;否则,将ADDR(CORE_SEC(.sdata2))与0xF0000000进行按位与运算,再加上32k,最后将结果赋值给SMALL_DATA2_。
所以也就是看有没有内容链接到 这个sec,如果没有,那么就只需要知道 CORE_SEC 的地址就可以了。

这里 default_rom 是 0x8000xxxx, 所以最终计算下来是

0x80008000

也就是说A1 寄存器现在的数值是0x80008000。
下面看一下这些寄存器分别的介绍。
在这里插入图片描述
设置完A1 寄存器的global register 地址之后。set 另外一个寄存器。
在这里插入图片描述
这里设置固定值

0x00000980u == 1001 1000 0000b

看一下具体含义。

  • Call depth counting is enabled

  • Write permission to global registers A[0], A[1], A[8], A[9] is enabled.

  • Supervisor ModeEnables access to all peripheral devices. It enables read/write access to coreregisters and protected peripheral devices. Tasks at this level may disableinterrupts.

什么意思呢?简言之,开启栈深度counter, 开启牛逼权限,开启一些寄存器的读写权限。

有了这些权限之后。判断一下,本次启动是因为软件重启还是硬件复位。不同的reset 方式 对应的启动流程是不一样的。

这里是通过寄存器来判断,具体不同bit 含义如下。不做赘述。

typedef struct _Ifx_SCU_RSTCON_Bits
{
    
   Ifx_UReg_32Bit ESR0:2;            /**< \brief [1:0] ESR0 Reset Request Trigger Reset Configuration - ESR0 (rw) */
   Ifx_UReg_32Bit ESR1:2;            /**< \brief [3:2] ESR1 Reset Request Trigger Reset Configuration - ESR1 (rw) */
   Ifx_UReg_32Bit reserved_4:2;      /**< \brief [5:4] \internal Reserved */
   Ifx_UReg_32Bit SMU:2;             /**< \brief [7:6] SMU Reset Request Trigger Reset Configuration - SMU (rw) */
   Ifx_UReg_32Bit SW:2;              /**< \brief [9:8] SW Reset Request Trigger Reset Configuration - SW (rw) */
   Ifx_UReg_32Bit STM0:2;            /**< \brief [11:10] STM0 Reset Request Trigger Reset Configuration - STM0 (rw) */
   Ifx_UReg_32Bit STM1:2;            /**< \brief [13:12] STM1 Reset Request Trigger Reset Configuration (If Product has STM1) - STM1 (rw) */
   Ifx_UReg_32Bit STM2:2;            /**< \brief [15:14] STM2 Reset Request Trigger Reset Configuration (If Product has STM2) - STM2 (rw) */
   Ifx_UReg_32Bit STM3:2;            /**< \brief [17:16] STM3 Reset Request Trigger Reset Configuration (If Product has STM3) - STM3 (rw) */
   Ifx_UReg_32Bit STM4:2;            /**< \brief [19:18] STM4 Reset Request Trigger Reset Configuration (If Product has STM4) - STM4 (rw) */
   Ifx_UReg_32Bit STM5:2;            /**< \brief [21:20] STM5 Reset Request Trigger Reset Configuration (If Product has STM5) - STM5 (rw) */
   Ifx_UReg_32Bit reserved_22:10;    /**< \brief [31:22] \internal Reserved */
} Ifx_SCU_RSTCON_Bits;

我们这里说 硬件从零上电的过程。

总结一下:在phase0 主要是初始化core 寄存器,以及赋予一些操作权限。

1.1.4 __StartUpSoftware_Phase2

这里主要对mcu 电源,内部内存进行一些自检。还是要看用户有没有进行配置,进行写自检代码,否则没有实际代码执行。

   /* Power and EVRC configurations */
   IFX_CFG_SSW_CALLOUT_PMS_INIT();

   /* LBIST Tests and evaluation */
   IFX_CFG_SSW_CALLOUT_LBIST();

   /* MONBIST Tests and evaluation */
   IFX_CFG_SSW_CALLOUT_MONBIST();

总结一下:mcu 自检操作。

1.1.5 __StartUpSoftware_Phase3PowerOnResetPath

这里主要是进行上下文初始化。包含 stack 与 CSA。也就是 上下文切换,占空间的 ram 地址。

我们先来分析一下链接文件对stack,CSA 的指定地址。

CORE_ID = CPU0;
SECTIONS
{
    
   CORE_SEC(.ustack) (LCF_DSPR0_START + LCF_USTACK0_OFFSET):
  {
     PROVIDE(__USTACK0_END = .);   . = . + LCF_USTACK0_SIZE;    PROVIDE(__USTACK0 = .); }
   
   CORE_SEC(.istack) (LCF_DSPR0_START + LCF_ISTACK0_OFFSET):
  {
     PROVIDE(__ISTACK0_END = .);   . = . + LCF_ISTACK0_SIZE;    PROVIDE(__ISTACK0 = .); }
   
   CORE_SEC(.csa) (LCF_DSPR0_START + LCF_CSA0_OFFSET):
  {
     PROVIDE(__CSA0_END = .);   . = . + LCF_CSA0_SIZE;    PROVIDE(__CSA0 = .); }
}

看这个之前先了解一下PROVIDE 是干什么的。简单来说就是通过链接文件来定义了一下symbol,可以给程序使用。程序如果没有定义,就用这里面的定义。具体如下(翻译麻烦,不翻译了。)。

In some cases, it is desirable for a linker script to define a symbol only if it is referenced and
is not defined by any object included in the link. For example, traditional linkers defined the
symbol etext. However, ANSI C requires the user to be able to use etext as a function name
without encountering an error. The PROVIDE keyword may be used to define a symbol, such
as etext, only if it is referenced but not defined. The syntax is PROVIDE( = <
expression>).
Here is an example of using PROVIDE to define etext:
SECTIONS
{
.text :
{
∗(.text)
etext = .;
PROVIDE(etext = .);
}
}
In this example, if the program defines _etext (with a leading underscore), the linker will give
a multiple definition error. If, on the other hand, the program defines etext (with no leading
underscore), the linker will silently use the definition in the program. If the program references
etext but does not define it, the linker will use the definition in the linker script.

在set好A10 寄存器 (也就是SP 指针)后,这里加了一行汇编。

IFX_SSW_INLINE void Ifx_Ssw_DSYNC(void)
{
    
   __asm__ volatile ("dsync" : : : "memory");
}

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

To ensure memory coherency, a prior to any access to an active CSA memory location.
DSYNC instruction must be executed

随之而来初始化CSA。

具体CSA 是什么,这里直接copy一下之前写过的文章。

上下文查看

上下文保存了一些数据寄存器,地址寄存器以及程序状态字和链接字。上下文分为高级context和低级context, 高级context自动保存,低级context需要用户手动保存。这里以高级context为例。看一下怎么查看context使用情况。

连接好调试器,打开CPU 寄存器,这里会有高级context, 低级context以及一起其他的系统寄存器。

从芯片系统架构手册可知上下文的数据结构和内容如下图。
在这里插入图片描述
我们在调试器打开CPU 寄存器能找到PCXI,这个是链接字。这里以链接字为0x00370B70 为例。
在这里插入图片描述
通过取中间可用的信息来计算链接字对应RAM 空间位置。具体计算方式来自英飞凌系统架构手册,比较详细的介绍。实际上就是数据段加上偏移量。填零。
在这里插入图片描述
微软自带计算器 计算出 ram位置这里计算出来的是0x7002DC00 是上文我们提到的core0 的 ram空间。文档搬运工。。。
在这里插入图片描述
这时候可以使用调试器我这里使用的是Lauterbach可以查看内存信息。dump… 工具搬运工。。。

在这里插入图片描述
通过英飞凌系统架构手册里面对context的解析,可以看到上下文使用情况,以及具体数据。需要对每一个寄存器进行调查 可以查看手册。

上下文在任务切换过程中总是保存或者读取。新任务抢占老任务,老任务的上下文会被抢占,新任务结束,继续执行老任务,老任务的上下文会被重读恢复读取使用。后进先出的过程。那么下面看一下在哪些情况下上下文会被操作。
在这里插入图片描述
和上面说的一样,高级context保存是系统自动保存。在中断发生,进入trap,函数调用,换句话说 当系统发生需要使用另一个context的时候。高级context就会被自动保存。然而lower context则需要相应的指令去保存。这里提一下,lower 和 upper 对应的地址,对应的链接字,大小格式,都是一样的。比如配置了64个上下文位置,那么这是lower + upper 一共可以用的空间。所以可以通过程序状态字去查看这个context具体指的是lower or upper.

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

举个梨子

当前在用的context时CSA2, 那么FCX 指向下一个可用的context就是CSA3, 在CSA3 里面指向的链接字就是CSA4,以此类推 直到LCX和最后一个。手册搬运工。。。

往前的话,上一个使用了的context的链接字就是CSA1.
在这里插入图片描述
新建一个context后,前一个用过了的 和 下一个可用的 context都会被更新,但是下一个可用后面的链表还是原来的样子。当CSA3被new出来之后就变成了这样。
在这里插入图片描述
自此 SP 指针 与 CSA 都已经被初始化完毕。

执行一次

isync 来确保 指令的操作完整性。

All preceding instructions are executed by the CPU. Then the pipeline is flushed before the next instruction is executed.

1.1.5 __StartUpSoftware_Phase4

开启内部看门狗,没什么好说的。

&MODULE_SCU.WDTCPU[0]

1.1.6 __StartUpSoftware_Phase5

如果有配置smu, 这里就可以开启SMU 模块,来监控芯片状态。

1.1.7 __StartUpSoftware_Phase6.

这里是和用户main 最接近的一次。
这里面执行两个事情。

  • 启动下一个core
void Ifx_Ssw_startCore(Ifx_CPU *cpu, unsigned int programCounter)
{
    
   /* Set the PC */
   cpu->PC.B.PC = (unsigned int)programCounter >> 1U;

   /* release boot halt mode if required */
   Ifx_CPU_SYSCON syscon;
   syscon = cpu->SYSCON;

   if (syscon.B.BHALT)
  {
    
       syscon.B.BHALT = 0U;
       cpu->SYSCON    = syscon;
  }

}
  • 运行至本core的main 函数
void __Core0_start(void)

所以这里之后真正的多核开始了并行。一步一步来。

1.2 多核启动

1.2.1 core0 start

  • 重新加载关闭看门狗

  • 通过写寄存器来决定是否开启P/D cache

/** \brief 920C, CPUx Program Control 0 */
#define CPU_PCON0 0x920C
/** \brief 9040, CPUx Data Memory Control Register */
#define CPU_DCON0 0x9040

然后对其他几个通用寄存器进行初始化。初始化方式和上面类似。这里不一一赘述。

/* Set A0 Pointer to access global variables with small data addressing */
   Ifx_Ssw_setAddressReg(a0, __SDATA1(0));

   /* These to be un commented if A8 and A9 are required to be initialized */
   Ifx_Ssw_setAddressReg(a8, __SDATA3(0));
   Ifx_Ssw_setAddressReg(a9, __SDATA4(0));

随之而来的就是 初始化中断向量表。注意这里中断向量表可以说是每个core都可以存在一个。互不干扰。

Os_InitializeVectorTable();

这个中断向量表是配置在os 里面的中断。里面包含中断服务函数,与优先级。在os 的实现代码里面是汇编。部分如下。详细可查看文件:Os_vectors.c

__asm__("\n\
 .file \"Os_vectors.s\"\n\
 .section \".os_interrupt_code.osinterrupts\", \"ax\", @progbits\n\
 #==========================================\n\
 # Os_InterruptVectorTable0\n\
 #==========================================\n\
 .align 13\n\
 .global Os_InterruptVectorTable0 ;# Start of the table\n\
Os_InterruptVectorTable0:\n\

初始化了中断向量表,后面就需要立即给中断进行栈分配。

Ifx_Ssw_MTCR(CPU_ISP, (unsigned int)__ISTACK(0));

注意这里中断有自己独立的栈空间。和前面任务栈一个意思。

由于看门狗是自动起的。下面需要进行ram 初始化,这里先关闭看门狗,如下操作。

   Ifx_Ssw_setCpuEndinitInline(&MODULE_SCU.WDTCPU[0], cpuWdtPassword);

   /* CPU and safety watchdogs are enabled by default,
    * C initialization functions are not servicing the watchdogs.
    */
   Ifx_Ssw_disableCpuWatchdog(&MODULE_SCU.WDTCPU[0], cpuWdtPassword);
   Ifx_Ssw_disableSafetyWatchdog(safetyWdtPassword);

前面已经对中断,对栈空间,对通用寄存器进行了初始化与配置。

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

一个是初始化直接是0. 那么怎么,哪里,如何从flash 里拿到对应的ram呢?CPU 怎么知道的。

前面有个文章已经进行说明。也是通过链接文件的方式。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;
      }
  }
}

ram已经初始化完毕。可以开启看门狗。

Ifx_Ssw_enableCpuWatchdog(&MODULE_SCU.WDTCPU[0], cpuWdtPassword);

终于到了core0 的main 函数。

void core0_main (void)

好这里我们等一下,前面说到,这个函数做了两个事情,一个是运行至core0的main. 还有一个是 __Core1_start。

对了现在两个核独立的跑了,我们需要同步分析一下另外一个core.

1.2.2 __Core1_start

首先我们看一下start core 的接口函数什么样的。

void Ifx_Ssw_startCore(Ifx_CPU *cpu, unsigned int programCounter)
{
    
   /* Set the PC */
   cpu->PC.B.PC = (unsigned int)programCounter >> 1U;

   /* release boot halt mode if required */
   Ifx_CPU_SYSCON syscon;
   syscon = cpu->SYSCON;

   if (syscon.B.BHALT)
  {
    
       syscon.B.BHALT = 0U;
       cpu->SYSCON    = syscon;
  }

}

其实就是把对应的core的 寄存器 置为,进而让核运行起来。然后第二个参数,就是即将运行的函数。

Ifx_Strict_32Bit BHALT:1;         /**< \brief [24:24] Boot Halt - BHALT (rw) */

分析一下

void __Core1_start(void)

这里与前面分析的Core0 一致。只是不需要对全局ram进行初始化。因为前面已经初始化了。

还有不同的点就是这里可能会拉起后面的core. 如core2.

然后运行至自己的main函数。即:

void core1_main (void)
{
    
 volatile unsigned short LoopFlag = 1U;
 unsigned short cpuWdtPassword;
 #if ((defined IFX_CFG_SSW_ENABLE_TRICORE0) && (IFX_CFG_SSW_ENABLE_TRICORE0 == 0))
 unsigned short safetyWdtPassword;
 #endif
 ENABLE();
 /*
  * !!WATCHDOG1 IS DISABLED HERE!!
  * Enable the watchdog in the demo if it is required and also service the watchdog periodically
  * */

 #if ((defined IFX_CFG_SSW_ENABLE_TRICORE0) && (IFX_CFG_SSW_ENABLE_TRICORE0 == 0))
 safetyWdtPassword = Ifx_Ssw_getSafetyWatchdogPassword();
 Ifx_Ssw_disableSafetyWatchdog(safetyWdtPassword);
 #endif

 cpuWdtPassword = Ifx_Ssw_getCpuWatchdogPassword(&MODULE_SCU.WDTCPU[1]);
 Ifx_Ssw_disableCpuWatchdog(&MODULE_SCU.WDTCPU[1], cpuWdtPassword);

 main();

 while(LoopFlag == 1U)
{
    

}
}

所以从上面也能看出来,多核的启动是一个拉着一个的,

不是说core0 直接把所有都拉起来的。

后面的每一个core 怎么拉起来, 这里就不介绍了,和core1被拉起来的方式一致。

那么也就是说,我们说到了 两个main 函数。

core0 和 core1 都已经运行到了自己的main 函数。这里就涉及到同步的概念了,先期的core 要等一会 暂时还没有启动的core. 是什么意思呢?我们继续说。

1.3 各自main函数

1.3.1 core0 的main 函数

extern int main(void);
void core0_main (void)
{
    
 volatile unsigned short LoopFlag = 1U;
 unsigned short cpuWdtPassword;
 unsigned short safetyWdtPassword;


 ENABLE();
 /*
  * !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
  * Enable the watchdog in the demo if it is required and also service the watchdog periodically
  * */
 cpuWdtPassword = Ifx_Ssw_getCpuWatchdogPassword(&MODULE_SCU.WDTCPU[0]);
 safetyWdtPassword = Ifx_Ssw_getSafetyWatchdogPassword();
 Ifx_Ssw_disableCpuWatchdog(&MODULE_SCU.WDTCPU[0], cpuWdtPassword);
 Ifx_Ssw_disableSafetyWatchdog(safetyWdtPassword);

 main();

 while (LoopFlag == 1U)
{
    

}
}

不难看出基操后面跟了个main() 然后就是while 死循环了。由此猜想。走完这个main. 可能就被os 接管了。换句话说,分析到这里,基本和autosar 没有一毛钱关系,除了,前面的中断向量表。不过那个中断其实mcal也可以实现。只是为了配合os 所以才有的。好 现在我们来分析这个

int main(void)

这实际是个宏展开。我来给大家展开一下就一目了然了。

extern int main(void);

int main(void)
{
    
(
  {
    
     unsigned newval = (0x200 | (({
     unsigned res; __asm__ volatile ("mfcr %0," "0xfe04" : "=d" (res) : : "memory"); res; })));
      __asm__ volatile ("mtcr " "0xfe04" ",%0" : : "d" (newval) : "memory");
  }
);
 
 __asm__ volatile ("isync" : : : "memory");
     
   Os_StartCoreGate();
   
   inner_main();
   
   return 0;
     
}
     
void inner_main(void)
{
    
   Dem_SetOperationCycleState(0u, 0);
   /*Invoking the ECUM Init for ECU Initialization, never return */
   EcuM_Init();
}

这里也看出在EcuM_Init() 的时候,才真正运行到Autosar 协议栈。

好,那我们看看前面是什么。

有一个函数:

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();}
 while ((core == 3U) && (Os_StartBlock[2] != 0xa55a5aa5U)) {
    OS_NOP();}
 while ((core == 4U) && (Os_StartBlock[3] != 0xa55a5aa5U)) {
    OS_NOP();}
 while ((core == 5U) && (Os_StartBlock[4] != 0xa55a5aa5U)) {
    OS_NOP();}
}

这是在干啥呢。哦。这里貌似在同步每一个core. 好不着急,我们继续往下看。

void inner_main(void)
{
    
   Dem_SetOperationCycleState(0u, 0);
   /*Invoking the ECUM Init for ECU Initialization, never return */
   EcuM_Init();
}

暂不介绍Dem 模块,这里进行了EcuM_Init().

当然这里会对mcu外设,等寄存器进行初始化。这里我们先不说,后面有专门的文章来介绍EcuM的相关说明。我们只说EcuM 是怎么拉起来Os的。

在EcuM 初始化list0,list1 之后,会调用

EcuM_Prv_StartSlaveCores();
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);

          }
      }

}

也就是说会对后面的每一个core进行

StartCore( cntrLoopCtr_u16, &dataStatus_chr);

这里就是Autosar Os对应的start core.

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 */

这里可以发现,在起core的时候有callback函数。那这个对应的是什么呢。

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
   ret = E_OK;

} else if (CoreID == 3U) {
    
   Os_StartBlock[2] = 0xa55a5aa5U;
#ifndef _lint /* Skip lint checks on compiler-specific ornamentations */
   CPU3_PC.U = (uint32)cstart;
   CPU3_SYSCON.B.BHALT = 0U;
#endif
   ret = E_OK;

} else if (CoreID == 4U) {
    
   Os_StartBlock[3] = 0xa55a5aa5U;
#ifndef _lint /* Skip lint checks on compiler-specific ornamentations */
   CPU4_PC.U = (uint32)cstart;
   CPU4_SYSCON.B.BHALT = 0U;
#endif
   ret = E_OK;

} else if (CoreID == 5U) {
    
   Os_StartBlock[4] = 0xa55a5aa5U;
#ifndef _lint /* Skip lint checks on compiler-specific ornamentations */
   CPU5_PC.U = (uint32)cstart;
   CPU5_SYSCON.B.BHALT = 0U;
#endif
   ret = E_OK;
} else {
    
   /* Not an expected core! */
}
 return ret;
}

这里是不是豁然开朗,其实core本身已经启动了。这里os 同步一下。给每一个Os_StartBlock[] 进行赋值。

也就是说当core0 走了EcuM_Init 到这里之后,才可以说 来识别每一个core 同步。core0 来给每一个core进行赋值。

这样各自core 就不会卡在 下面函数里了。

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();}
 while ((core == 3U) && (Os_StartBlock[2] != 0xa55a5aa5U)) {
    OS_NOP();}
 while ((core == 4U) && (Os_StartBlock[3] != 0xa55a5aa5U)) {
    OS_NOP();}
 while ((core == 5U) && (Os_StartBlock[4] != 0xa55a5aa5U)) {
    OS_NOP();}
}

当每一个core同步之后。除了core0 其他的core都跑到各自的

inner_main();

因为那些core暂时没有配置EcuM 所以就直接到了while(1)

也就是说,等着os 来进行调度。

core0 这时候也进入了

EcuM_Prv_StartOS();

即:

StartOS(x)
{
    Os_StackBase[OS_MFCR(0xfe1c)] = Os_GetSP();
if (Os_StartOS(x)) {
    while(Os_Cbk_Idle()) {
    } /* [$UKS 161] */;
for(;;)
{
    } /* [$UKS 16] */
}
}

到这里为止 多核系统启动完毕。从裸机让Autosar Os 进行接管。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_48120109/article/details/134044616

智能推荐

基于广播星历的北斗定位解算原理(基于C语言和MATLAB实现)_卫星位置解算-程序员宅基地

文章浏览阅读2.3k次,点赞32次,收藏53次。本文先用C语言解算卫星位置,再用MATLAB绘出卫星三维坐标图。本篇博客所使用的资料和文件都是网络上公开发表且可以找到的资料文件。_卫星位置解算

Vue面试题-程序员宅基地

文章浏览阅读158次。ViewModel提供双向数据绑定把View和Model连接起来,他们之间的同步是自动的,不需要人为的干涉,所以我们只需要关注业务逻辑,不需要操作DOM,同时也不需要关注数据的状态问题,因为他是MVVM统一管理。当我们在组件中访问 Vuex 中的状态时,Vue.js 的响应式系统会建立依赖关系,并将组件与状态属性之间的关联记录下来。代码分割和异步加载:将页面按需拆分成多个模块,通过使用路由懒加载或动态导入组件的方式,使得页面初始化时只加载必要的代码,延迟加载其他非必要的模块,从而加快首屏渲染速度。

【新手科研指南5】深度学习代码怎么读-小白阶段性思路(以手写数字识别应用为例)_深度学习程序怎么读-程序员宅基地

文章浏览阅读6.2k次,点赞6次,收藏26次。我是一个深度学习代码小白,请你用中文写上注释,能让我能轻松理解下面这段代码。注意包含所有函数、调用和参数的注释。以同样的python代码块样式返回你写的代码给我。代码看累了,就看《动手学深度学习》文档:基于PyTorch框架,从底层函数实现基础功能,再到框架的高级功能。努力上路的小白一枚,麻烦路过的大佬指导一二,同时希望能和大家交流学习~争取更新学习这个文档的专栏,记录学习过程。量身定做了一套话术hhh,亲身测试还不错。这个感觉更浅一点儿,之后复习看吧。20天吃掉那只Pytorch。_深度学习程序怎么读

Java学习路线图,看这一篇就够了!-程序员宅基地

文章浏览阅读2.7w次,点赞126次,收藏1.2k次。耗废1024根秀发,Java学习路线图来了,整合了自己所学的所有技术整理出来的2022最新版Java学习路线图,适合于初、中级别的Java程序员。_java学习路线

PCL_Tutorial2-1.7-点云保存PNG_pcl::io:savepng-程序员宅基地

文章浏览阅读4.4k次。1.7-savingPNG介绍代码详情函数详解savePNGFile()源码savePNGFile()源码提示savePNGFile()推荐用法处理结果代码链接介绍PCL提供了将点云的值保存到PNG图像文件的可能性。这只能用有有序的云来完成,因为结果图像的行和列将与云中的行和列完全对应。例如,如果您从类似Kinect或Xtion的传感器中获取了点云,则可以使用它来检索与该云匹配的640x480 RGB图像。代码详情#include <pcl / io / pcd_io.h>#incl_pcl::io:savepng

知乎问答:程序员在咖啡店编程,喝什么咖啡容易吸引妹纸?-程序员宅基地

文章浏览阅读936次。吸引妹子的关键点不在于喝什么咖啡,主要在于竖立哪种男性人设。能把人设在几分钟内快速固定下来,也就不愁吸引对口的妹子了。我有几个备选方案,仅供参考。1. 运动型男生左手单手俯卧撑,右手在键盘上敲代码。你雄壮的腰腹肌肉群活灵活现,简直就是移动的春药。2.幽默男生花 20 块找一个托(最好是老同学 or 同事)坐你对面。每当你侃侃而谈,他便满面涨红、放声大笑、不能自已。他笑的越弱_咖啡厅写代码

随便推点

静态表的查找操作实验(数据结构C语言版)_用顺序查找方法味例设计一个有关静态查找表的简历,查找等基本操作的演示程序,-程序员宅基地

静态表的查找操作实现了顺序查找、二分查找和索引查找。实验结果显示元素的位置或未找到元素。

C#生成CSV文件_c# 生成csv-程序员宅基地

文章浏览阅读1.2w次,点赞5次,收藏27次。编程中,通常需要将数据进行保存,保存为CSV文件,代码如下://写CSV文件 /// &lt;summary&gt; /// Write CSV File /// &lt;/summary&gt; /// &lt;param name="fileName"&gt;&lt;/param&gt; /// &lt;pa..._c# 生成csv

Flutter ListView ListView.build ListView.separated_flutter listview.separated和listview.builder-程序员宅基地

文章浏览阅读1.7k次。理解为ListView 的三种形式吧ListView 默认构造但是这种方式创建的列表存在一个问题:对于那些长列表或者需要较昂贵渲染开销的子组件,即使还没有出现在屏幕中但仍然会被ListView所创建,这将是一项较大的开销,使用不当可能引起性能问题甚至卡顿直接返回的是每一行的Widget,相当于ios的row。行高按Widget(cell)高设置ListView.build 就和io..._flutter listview.separated和listview.builder

2021 最新前端面试题及答案-程序员宅基地

文章浏览阅读1.4k次,点赞4次,收藏14次。废话不多说直接上干货1.js运行机制JavaScript单线程,任务需要排队执行同步任务进入主线程排队,异步任务进入事件队列排队等待被推入主线程执行定时器的延迟时间为0并不是立刻执行,只是代表相比于其他定时器更早的被执行以宏任务和微任务进一步理解js执行机制整段代码作为宏任务开始执行,执行过程中宏任务和微任务进入相应的队列中整段代码执行结束,看微任务队列中是否有任务等待执行,如果有则执行所有的微任务,直到微任务队列中的任务执行完毕,如果没有则继续执行新的宏任务执行新的宏任务,凡是在..._前端面试

linux基本概述-程序员宅基地

文章浏览阅读1k次。(3)若没有查到,则将请求发给根域DNS服务器,并依序从根域查找顶级域,由顶级查找二级域,二级域查找三级,直至找到要解析的地址或名字,即向客户机所在网络的DNS服务器发出应答信息,DNS服务器收到应答后现在缓存中存储,然后,将解析结果发给客户机。(3)若没有查到,则将请求发给根域DNS服务器,并依序从根域查找顶级域,由顶级查找二级域,二级域查找三级,直至找到要解析的地址或名字,即向客户机所在网络的DNS服务器发出应答信息,DNS服务器收到应答后现在缓存中存储,然后,将解析结果发给客户机。_linux

JavaScript学习手册十三:HTML DOM——文档元素的操作(一)_javascript学习手册十三:html dom——文档元素的操作(一)-程序员宅基地

文章浏览阅读7.9k次,点赞26次,收藏66次。HTML DOM——文档元素的操作1、通过id获取文档元素任务描述相关知识什么是DOM文档元素节点树通过id获取文档元素代码文件2、通过类名获取文档元素任务描述相关知识通过类名获取文档元素代码文件3、通过标签名获取文档元素任务描述相关知识通过标签名获取文档元素获取标签内部的子元素代码文件4、html5中获取元素的方法一任务描述相关知识css选择器querySelector的用法代码文件5、html5中获取元素的方法二任务描述相关知识querySelectorAll的用法代码文件6、节点树上的操作任务描述相关_javascript学习手册十三:html dom——文档元素的操作(一)

推荐文章

热门文章

相关标签