title: TriCore 与 RT-Thread(TC264 移植)
date: 2021-02-10 12:43
tags: TriCore
本文记录了我对 TriCore 上下文切换运行机制的理解以及 TriCore 内核移植 API 的注释分析。由于水平有限,再加上阅读的手册都是英文的,所以理解上如果存在偏差是难免的,还请公众号留言指正!(事实上,写完这篇文章后,发现 TriCore 架构还有很多可以发掘的点,等有时间再去验证心中所想吧。。。)

 

简介

TriCore 提供了一种硬件的上下文机制,这种机制是专为嵌入式实时操作系统设计的,他的目的就是为了能提高线程切换的效率。

 

编译环境

我使用的是 windows 平台下的 AURIX-Studio IDE , AURIX-Studio IDE 是英飞凌公司开发的基于 eclipse 的集成开发环境。使用的工具链是 altium 的 TASKING 工具链。在整个移植过程中几乎没有写汇编代码,用的一些函数,都是工具链提供的。

比如:

  1. 写一个寄存器

  •  
__mtcr(CPU_PSW, 0x00000980);    /* 对应的汇编指令就是:mtcr */
  1. 读一个寄存器

  •  
icr.U = __mfcr(CPU_ICR);    /* 对应的汇编指令就是:mfcr */

 

RT-Thread CPU 架构级别的移植 API 列表

需要移植的 API 接口说明,在 RT-Thread 文档中心《内核移植》章节里已经讲的很详细了,需要了解详情的可以去以下链接查看![链接]https://www.rt-thread.org/document/site/programming-manual/porting/porting/ 

以下是需要实现的函数和变量的列表:(向????滑动查看全部)

函数和变量 描述
rt_base_t rt_hw_interrupt_disable(void); 关闭全局中断
void rt_hw_interrupt_enable(rt_base_t level); 打开全局中断
rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit); 线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数
void rt_hw_context_switch_to(rt_uint32 to); 没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用
void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); 从 from 线程切换到 to 线程,用于线程和线程之间的切换
void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to); 从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用
rt_uint32_t rt_thread_switch_interrupt_flag; 表示需要在中断里进行切换的标志
rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; 在线程进行上下文切换时候,用来保存 from 和 to 线程

TriCore 移植代码注释分析

一. 关闭全局中断 rt_hw_interrupt_disable()

  •  
IFX_INLINE boolean IfxCpu_disableInterrupts(void){   boolean enabled;   enabled = IfxCpu_areInterruptsEnabled();   __disable();   __nop();   return enabled;}/** rt_base_t rt_hw_interrupt_disable(void);*/rt_base_t rt_hw_interrupt_disable(void){   rt_base_t level;   level = IfxCpu_disableInterrupts();   return level;}

__disable(); 是工具链直接提供的函数。这个函数的作用就是关闭芯片全局中断。

单步调试可以看到这条函数究竟做了什么:

TriCore 与 RT-Thread(TC264 移植)_RT-Thread

TriCore 与 RT-Thread(TC264 移植)_RT-Thread_02

由上图可以看到,当执行完 diable 指令后,TriCore 的 ICR 寄存器的 IE 位由 1 变为 0 了。IE 位的功能说明如下图所示:

TriCore 与 RT-Thread(TC264 移植)_RT-Thread_03

IE 位是一个全局的中断使能位,当进入中断时,IE 位会自动的置为 0,当中断服务函数执行 rfe 指令后,会自动的恢复进中断前的值。另外, IE 位的值还可以被  enable; disable; mtcr; bisr; 等指令更新。

二. 打开全局中断 rt_hw_interrupt_enable()

  •  
IFX_INLINE void IfxCpu_restoreInterrupts(boolean enabled){   if (enabled != FALSE)  {       __enable();  }}IFX_INLINE void restoreInterrupts(boolean enabled){   IfxCpu_restoreInterrupts(enabled);}/** void rt_hw_interrupt_enable(rt_base_t level);*/void rt_hw_interrupt_enable(rt_base_t level){   restoreInterrupts((boolean)level);}
有了前面关闭全局中断的分析,这里打开全局中断的就不再赘述了。rt_hw_interrupt_enable 最终执行的有效函数就是 __enable(); ,这个函数对应的 CPU 指令就是 enable ,这条指令的作用就是将 TriCore 的 ICR 寄存器的 IE 位置为 1 。

三. 实现线程栈初始化 rt_hw_stack_init()

在理解 rt_hw_stack_init 这个函数前,我需要先向大家介绍下 TriCore 的硬件上下文机制。

1. 上下文类型区分

TriCore 与 RT-Thread(TC264 移植)_RT-Thread_04

如上图所示 TriCore 内核的上下文分为两组:
  1. 上层上下文:外部中断;陷阱;函数调用;会自动保存恢复上下文。
  2. 下层上下文:如果要改变内容,需要显式的使用代码指令去读写寄存器。

下层上下文寄存器类似于全局寄存器。在 外部中断;陷阱;函数调用;中对这些寄存器做修改后,事件返回后值仍然会存在。这一特性意味着:

  1. 函数可以通过下层上下文寄存器传递参数,传递返回值。
  2. 中断与陷阱处理函数在使用下层上下文寄存器之前必须保存下层上下文寄存器的值,结束处理后,需要恢复下层上下文寄存器。

什么时候会保存恢复上下文:

TriCore 与 RT-Thread(TC264 移植)_RT-Thread_05

2. 上下文保存空间 CSA ( Context Save Area )

TriCore 架构使用固定大小的上下文保存区域的链表。一个 CSA 存储 16 个 word ,按 16 个 word 的边界对齐,每个 CSA 正好可以完整的存储一个上层上下文或者下层上下文。CSAs 通过一个 LinkWord 链接在一起。LinkWord 的内容通过如下图所示的关系可以转换成一个有效的 CSA 地址。

TriCore 与 RT-Thread(TC264 移植)_RT-Thread_06

3. 如何访问 CSA

TriCore 内核有三个寄存器用来操作访问 CSA 的信息:
  1. FCX:这个寄存器的值用来指向全局的空闲的 CSA 列表头指针地址信息。(总是指向可用的 CSA 列表头指针)
  2. PCX:这个寄存器的值用来保存前一个任务的 CSA 列表头指针地址信息。PCX 同时也是 PCXI 寄存器的一部分。
  3. LCX:如果 LCX 寄存器的值与 PCX 的值一致,那么就会触发一个  FCD 陷阱。(FCD:空闲上下文空间消耗殆尽的异常)

4. rt_hw_stack_init() 的实现

rt_hw_stack_init 的实现如下:

  •  
rt_uint8_t *rt_hw_stack_init(void       *tentry,                            void       *parameter,                            rt_uint8_t *stack_addr,                            void       *texit){   unsigned long *lower_csa = NULL;   unsigned long *upper_csa = NULL;
rt_hw_interrupt_disable(); { /* DSync to ensure that buffering is not a problem. */ __dsync(); /* 通过 FCX 寄存器获取一个空闲的 CSA 作为下层上下文。*/ lower_csa = LINKWORD_TO_ADDRESS( __mfcr( CPU_FCX ) ); if( NULL != lower_csa ) { /* 通过下层上下文的保存的 LinkWord 获取到上层上下文。*/ upper_csa = LINKWORD_TO_ADDRESS( lower_csa[ 0 ] ); } /* Check that we have successfully reserved two CSAs. */ if( ( NULL != lower_csa ) && ( NULL != upper_csa ) ) { __disable(); __dsync(); /* 由于已经使用掉了两个 CSA ,这里需要手动更新 FCX 寄存器的值为下一个空闲的 CSA 。*/ __mtcr( CPU_FCX, upper_csa[ 0 ] ); __isync(); __enable(); } else { /* Simply trigger a context list depletion trap. */ __svlcx(); } } rt_hw_interrupt_enable((rt_base_t)RT_NULL); /* 将获取到的上层 CSA 空间全部清零。*/ memset( upper_csa, 0, TRICORE_NUM_WORDS_IN_CSA * sizeof( unsigned long ) ); /* Upper Context. */ upper_csa[ 2 ] = ( unsigned long )stack_addr; /* 对应 A10 寄存器; 这个寄存器用于保存栈指针 */ upper_csa[ 1 ] = TRICORE_SYSTEM_PROGRAM_STATUS_WORD; /* 对应 PSW 寄存器;这个寄存器用于保存当前线程的初始状态*/
/* 将获取到的下层 CSA 空间全部清零。*/ memset( lower_csa, 0, TRICORE_NUM_WORDS_IN_CSA * sizeof( unsigned long ) ); /* Lower Context. */ lower_csa[ 8 ] = ( unsigned long ) parameter; /* 对应 A4 寄存器; 用于保存函数的入参。*/ lower_csa[ 1 ] = ( unsigned long ) tentry; /* 对应 A11 寄存器; 用于保存 PC 指针。*/ /* 将上层上下文的地址保存到下层上下文的 LinkWord 中。*/ lower_csa[ 0 ] = ( TRICORE_INITIAL_PCXI_UPPER_CONTEXT_WORD | ( unsigned long ) ADDRESS_TO_LINKWORD( upper_csa ) ); /* Save the link to the CSA in the top of stack. */ stack_addr = (unsigned long * ) ADDRESS_TO_LINKWORD( lower_csa ); /* DSync to ensure that buffering is not a problem. */ __dsync();
/* !!!注意:这里返回的其实已经不是栈地址了,而是下层 CSA 的 LinkWord 值。*/ return stack_addr;}

四. 实现上下文切换

RT-Thread 的 libcpu 抽象层需要实现以下三个线程切换相关的函数:1) rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。2) rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。3) rt_hw_context_switch_interrupt ():在中断环境下,从当前线程切换到目标线程。

实现 rt_hw_context_switch_to()

  •  
void rt_hw_context_switch_to(rt_ubase_t to){   unsigned long ulMFCR = 0UL;   unsigned long *lower_csa = NULL;   unsigned long *upper_csa = NULL;
/* Set-up the timer interrupt. */ rt_hw_systick_init();
/* 初始化通用目的服务请求寄存器 */ IfxSrc_init(GPSR[TRICORE_CPU_ID], (IfxSrc_Tos)TRICORE_CPU_ID, 1); IfxSrc_enable(GPSR[TRICORE_CPU_ID]);
__disable();
/* 初始化系统配置寄存器为初值 */ __mtcr( CPU_SYSCON, TRICORE_INITIAL_SYSCON ); __isync();
/* 确保 PSW 寄存器的值是正确的 */ ulMFCR = __mfcr( CPU_PSW ); ulMFCR &= TRICORE_RESTORE_PSW_MASK; __dsync(); __mtcr( CPU_PSW, ulMFCR ); __isync();
__dsync(); /* 将 to 线程的 LinkWord 赋值给 PCXI */ __mtcr(CPU_PCXI, (unsigned long)( *( (unsigned long *)to ) ) ); __isync(); __nop(); __rslcx(); __nop();
/* 当执行 fre 指令后,CPU 的线程会恢复成 PCXI 寄存器指向的 CSA 内容 */ __asm volatile( "rfe" );
/* Will not get here. */}

实现 rt_hw_context_switch()

在 TriCore 的移植实现中,这个函数 rt_hw_context_switch 并不会直接切换线程而是将当前线程的 CSA LinkWord 也就是 from 先保存到一个全局变量当中;要切换的线程的 CSA LinkWord 也就是 to 同样保存到一个全局变量中。
  •  
void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to){   rt_base_t level;
level = rt_hw_interrupt_disable(); current_thread = from; to_thread = to; rt_hw_interrupt_enable(level); /* 在保存完线程 CSA LinkWord 后,判断这次线程切换是否是 systick 触发的线程切换。*/ if(systick_flag == 0) { /* 如果不是 systick 引发的线程切换,那么就触发一个线程切换的异常函数,这个异常函数里会真正做线程切换的事情。*/ __syscall( 0 ); }}

这个函数不会直接切换线程,真正切换线程的函数是在 trigger_scheduling() 中。

实现 rt_hw_context_switch_interrupt()

同样,在 TriCore 的移植实现中,这个函数 rt_hw_context_switch_interrupt 并不会直接切换线程而是将当前线程的 CSA LinkWord 也就是 from 先保存到一个全局变量当中;要切换的线程的 CSA LinkWord 也就是 to 同样保存到一个全局变量中。
  •  
void rt_hw_context_switch_interrupt(rt_ubase_t from, rt_ubase_t to){   rt_base_t level;
level = rt_hw_interrupt_disable(); current_thread = from; to_thread = to; rt_hw_interrupt_enable(level);
/* 在保存完线程 CSA LinkWord 后,会给 SETR 位置 1,这样会触发一个异常陷阱函数。在这个陷阱函数里才会去真正的切换线程。*/ GPSR[TRICORE_CPU_ID]->B.SETR = 1; __isync();}

统一的触发线程切换的函数 trigger_scheduling()

  •  
inline void trigger_scheduling(void){    rt_ubase_t from, to;    rt_base_t level;
unsigned long *current_upper_csa = NULL;
/* 获取 from 和 to 线程的 CSA 信息。*/ level = rt_hw_interrupt_disable(); from = current_thread; to = to_thread; to_thread = RT_NULL; rt_hw_interrupt_enable(level);
/* 判断是否需要切换到 to 线程 */ if( to ) { __disable(); __dsync();
/* 获取当前线程的 CSA LinkWord */ current_upper_csa = LINKWORD_TO_ADDRESS( __mfcr( CPU_PCXI ) ); /* 保存当前线程的 CSA 上下文 */ *( (unsigned long *)from ) = current_upper_csa[ 0 ]; /* 将 to 线程的 CSA 地址赋值给当前线程的上层上下文的 LinkWord ,用于 TriCore 自动切换线程。*/ current_upper_csa[ 0 ] = *( (unsigned long *)to ); GPSR[TRICORE_CPU_ID]->B.SETR = 0; __isync(); }}

trigger_scheduling()  分别在三个地方使用,而且必须使用内联的方式!

  1. 首先系统滴答时钟:

  •  
__attribute__((noinline)) static void tricore_systick_handler( void ){    rt_base_t level;    IfxStm_Timer_acknowledgeTimerIrq(&tricore_timers[TRICORE_CPU_ID]);
level = rt_hw_interrupt_disable(); systick_flag = 1; rt_tick_increase(); systick_flag = 0; rt_hw_interrupt_enable(level);
trigger_scheduling();}
  1. 其次是 __syscall( 0 ); 触发的陷阱异常函数里:

  •  
void tricore_trap_yield_for_task( int iTrapIdentification ){  switch( iTrapIdentification )  {    case 0:        trigger_scheduling();      break;
default: /* Unimplemented trap called. */ break; }}
  1. 最后是 GPSR[TRICORE_CPU_ID]->B.SETR = 1; 触发的中断环境下的线程切换:

  •  
__attribute__((noinline)) static void tricore_yield_for_interrupt( void ){    trigger_scheduling();}
IFX_INTERRUPT(KERNEL_YIELD, 0, 2){ tricore_yield_for_interrupt();}

五. 最后就是实现系统时钟节拍

  •  
void rt_hw_systick_init( void ){  IfxStm_Timer_Config timer_config;  IfxStm_Timer_initConfig(&timer_config, STMs[TRICORE_CPU_ID]);
timer_config.base.frequency = RT_TICK_PER_SECOND; timer_config.base.isrPriority = 1; IfxStm_Timer_init(&tricore_timers[TRICORE_CPU_ID], &timer_config); IfxStm_Timer_run(&tricore_timers[TRICORE_CPU_ID]);}
系统时钟节拍没啥好说的,就不在赘述了。

写在最后:

我的实现并不是最优解,这里还有很多值得探讨的。但是业余对 TriCore 的研究学习就暂时告一段落了,后面我还是会把业余精力放回 RISC-V 的学习上。毕竟我觉得 RISC-V 架构才是国产物联网芯片的未来。TriCore 架构的生态有点封闭,目前只适合汽车电子芯片上。

TriCore 与 RT-Thread(TC264 移植)_RT-Thread_07