TriCore 与 RT-Thread(TC264 移植)
本文记录了我对 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 文档中心《内核移植》章节里已经讲的很详细了,需要了解详情的可以去以下链接查看!
以下是需要实现的函数和变量的列表:
函数和变量 | 描述 |
---|---|
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()
1 | IFX_INLINE boolean IfxCpu_disableInterrupts(void) |
__disable();
是工具链直接提供的函数。这个函数的作用就是关闭芯片全局中断。
单步调试可以看到这条函数究竟做了什么:
由上图可以看到,当执行完 diable
指令后,TriCore 的 ICR 寄存器的 IE 位由 1 变为 0 了。IE 位的功能说明如下图所示:
IE 位是一个全局的中断使能位,当进入中断时,IE 位会自动的置为 0,当中断服务函数执行 rfe 指令后,会自动的恢复进中断前的值。另外, IE 位的值还可以被 enable
; disable
; mtcr
; bisr
; 等指令更新。
二. 打开全局中断 rt_hw_interrupt_enable()
1 | IFX_INLINE void IfxCpu_restoreInterrupts(boolean enabled) |
有了前面关闭全局中断的分析,这里打开全局中断的就不再赘述了。rt_hw_interrupt_enable
最终执行的有效函数就是 __enable();
,这个函数对应的 CPU 指令就是 enable
,这条指令的作用就是将 TriCore 的 ICR 寄存器的 IE 位置为 1 。
三. 实现线程栈初始化 rt_hw_stack_init()
在理解 rt_hw_stack_init
这个函数前,我需要先向大家介绍下 TriCore 的硬件上下文机制。
1. 上下文类型区分
如上图所示 TriCore 内核的上下文分为两组:
- 上层上下文:外部中断;陷阱;函数调用;会自动保存恢复上下文。
- 下层上下文:如果要改变内容,需要显式的使用代码指令去读写寄存器。
下层上下文寄存器类似于全局寄存器。在 外部中断;陷阱;函数调用;中对这些寄存器做修改后,事件返回后值仍然会存在。
这一特性意味着:
- 函数可以通过下层上下文寄存器传递参数,传递返回值。
- 中断与陷阱处理函数在使用下层上下文寄存器之前必须保存下层上下文寄存器的值,结束处理后,需要恢复下层上下文寄存器。
什么时候会保存恢复上下文:
2. 上下文保存空间 CSA ( Context Save Area )
TriCore 架构使用固定大小的上下文保存区域的链表。一个 CSA 存储 16 个 word ,按 16 个 word 的边界对齐,每个 CSA 正好可以完整的存储一个上层上下文或者下层上下文。CSAs 通过一个 LinkWord 链接在一起。
LinkWord 的内容通过如下图所示的关系可以转换成一个有效的 CSA 地址。
3. 如何访问 CSA
TriCore 内核有三个寄存器用来操作访问 CSA 的信息:
- FCX: 这个寄存器的值用来指向全局的空闲的 CSA 列表头指针地址信息。(总是指向可用的 CSA 列表头指针)
- PCX: 这个寄存器的值用来保存前一个任务的 CSA 列表头指针地址信息。PCX 同时也是 PCXI 寄存器的一部分。
- LCX: 如果 LCX 寄存器的值与 PCX 的值一致,那么就会触发一个 FCD 陷阱。(FCD:空闲上下文空间消耗殆尽的异常)
4. rt_hw_stack_init() 的实现
rt_hw_stack_init
的实现如下:
1 | rt_uint8_t *rt_hw_stack_init(void *tentry, |
四. 实现上下文切换
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()
1 | void rt_hw_context_switch_to(rt_ubase_t to) |
实现 rt_hw_context_switch()
在 TriCore 的移植实现中,这个函数 rt_hw_context_switch
并不会直接切换线程而是将当前线程的 CSA LinkWord 也就是 from 先保存到一个全局变量当中;要切换的线程的 CSA LinkWord 也就是 to 同样保存到一个全局变量中。
1 | void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to) |
这个函数不会直接切换线程,真正切换线程的函数是在 trigger_scheduling()
中。
实现 rt_hw_context_switch_interrupt()
同样,在 TriCore 的移植实现中,这个函数 rt_hw_context_switch_interrupt
并不会直接切换线程而是将当前线程的 CSA LinkWord 也就是 from 先保存到一个全局变量当中;要切换的线程的 CSA LinkWord 也就是 to 同样保存到一个全局变量中。
1 | void rt_hw_context_switch_interrupt(rt_ubase_t from, rt_ubase_t to) |
统一的触发线程切换的函数 trigger_scheduling()
1 | inline void trigger_scheduling(void) |
trigger_scheduling()
分别在三个地方使用,而且必须使用内联的方式!
- 首先系统滴答时钟:
1 | __attribute__((noinline)) static void tricore_systick_handler( void ) |
- 其次是
__syscall( 0 );
触发的陷阱异常函数里:
1 | void tricore_trap_yield_for_task( int iTrapIdentification ) |
- 最后是
GPSR[TRICORE_CPU_ID]->B.SETR = 1;
触发的中断环境下的线程切换:
1 | __attribute__((noinline)) static void tricore_yield_for_interrupt( void ) |
五. 最后就是实现系统时钟节拍
1 | void rt_hw_systick_init( void ) |
系统时钟节拍没啥好说的,就不在赘述了。
写在最后:
我的实现并不是最优解,一定是有更好的方式充分发挥 TriCore 的优势的。