RISC-V 与 RT-Thread(K210 内核移植)
本文将会对 RT-Thread 系统底层内核的移植以及 RT-Thread 如何与 RISC-V 相结合进行分析。
K210 基本情况
这部分的内容主要来自对 《kendryte_datasheet_20180919020633.pdf》《riscv-spec-20191213.pdf》的理解。
K210 的 CPU 包含两个 64 位的 RISC-V 架构核心。这两个 RISC-V 核心是对称双核,且各自具备独立的 FPU(浮点运算单元)。支持的 RISC-V 指令集有 I;M;A;F;D;C,也就是 RV64IMAFDC(或者叫 RV64GC),采用的这些指令集都是 RISC-V 《riscv-spec-20191213.pdf》 里明确说明已经被批准,未来不再发生改变的指令集。(也就是说是比较稳定的指令集模块)
(对 RISC-V 指令集如有疑问可以查阅我的上一篇文章。)
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 线程 |
K210 CPU 移植代码注释分析
关闭全局中断
1 | /* |
csrrci : (Control and Status Register Read and Clear Immediate) 立即数读后清除控制状态寄存器 。
mstatus:(Machine Status) 机器模式下,用于保存全局中断使能,以及许多其他状态的寄存器。
csrrci a0, mstatus, 8
这句话的意思就是:首先将 mstatus 寄存器的值保存在 a0 寄存器中,然后将 mstatus 寄存器的第3位(8的二进制就是第3位为1)清零。
mstatus 寄存器的第3位就是 MIE,MIE 是机器模式下的全局中断使能位,清零即为关闭全局中断。
打开全局中断
1 | /* |
csrw: (Control and Status Register Set) (伪指令) 写控制状态寄存器 。
mstatus:(Machine Status) 机器模式下,用于保存全局中断使能,以及许多其他状态的寄存器。
csrw mstatus, a0
这句话的意思就是:把 a0 里的值中每一个为1的位写入对应的 mstatus 状态控制寄存器中。
实现线程栈初始化
在理解 rt_hw_stack_init
这个函数前,大家需要查看下 struct rt_hw_stack_frame
这个结构体,这个结构体描述了系统切换时要保存的上下文(也就是需要保存的 CPU 寄存器现场)。
1 | struct rt_hw_stack_frame |
对于 RV32I 或者 RV64I 来说,一共有 32 个通用寄存器 X0-X31,其中 X0 被预留为常数 0 (至于什么原因我也不知道),所以不需要保存。如果 CPU 还支持浮点的话,那么还需要再加上 32 个通用浮点寄存器 f0-f31。
对于 RV32E (嵌入式架构)来说,则一共有 16 个通用寄存器 X0-X15,X0 同样被预留为常数 0 。
1 | /** |
这个函数的前半部分和 Cortex-M 的代码基本一直,主要区别是后半部分。
ra 寄存器:就是通用寄存器的 X1,用于保存函数的返回跳转地址,也就是退出函数地址。
a0 寄存器:就是通用寄存器的 X10,用于存放线程的入口函数的入参。
epc 寄存器:是程序计数器 ,用于存放线程入口函数的地址。
FS[14-13]: mstatus (机器模式状态寄存器)的 FS 域,由两位组成。用于维护或反映浮点单元的状态。(用于操作系统在进行上下文切换是的指引信息)
MPP[12-11]: mstatus (机器模式状态寄存器)的 MPP 域,由两位组成。发生异常之前的权限模式保留在 mstatus 的 MPP 域中 。[0,0]:表示用户模式;[0,1]:表示监管者模式;[1,1]:表示机器模式;
MPIE[7]: mstatus (机器模式状态寄存器)的 MPIE 域,由一位组成。用于保存发生异常之前的 MIE 域的值。
实现上下文切换
RT-Thread 的 libcpu 抽象层需要实现以下三个线程切换相关的函数:
1) rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。
2) rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。
3) rt_hw_context_switch_interrupt ():在中断环境下,从当前线程切换到目标线程。
阅读下面的内容需要大家再回过头来看下 struct rt_hw_stack_frame
这个结构体。这个结构体描述了系统切换时要保存的上下文(也就是需要保存的 CPU 寄存器现场)
实现 rt_hw_context_switch_to()
1 | /* |
实现 rt_hw_context_switch
1 | /* |
以上函数的作用就是把当前 from 线程的 CPU 寄存器现场贮存起来。(也就是把 from 线程的 struct rt_hw_stack_frame
结构体里的内容贮存起来。)
实现 rt_hw_context_switch_interrupt()
这个函数的实现其实有两种情况,这里我们只看在没有开启的 RT_USING_SMP(对称多核)情况下的 rt_hw_context_switch_interrupt
的实现。
1 | /* |
这个函数只是做了一件很简单的事情,那就是给 rt_thread_switch_interrupt_flag 这个标志置1,用来表示需要切换线程,且根据情况将 from 线程赋值给 rt_interrupt_from_thread ,to 线程赋值给 rt_interrupt_to_thread 。
真正在保存线程现场的地方不在这里,而是在以下这个汇编函数里:
1 | .section .text.entry |
RISC-V 架构规定,在处理器的程序执行过程中,一旦发生异常,则终止当前的程序流,处理器强行跳转到一个新的 PC 地址。这个过程在 RISC-V 的架构中定义为 “陷阱(trap)”。RISC-V 处理器在 trap 后跳转到一个指定的 PC 地址,这个指定的 PC 地址由一个机器模式下异常入口 mtvec 寄存器来指定。
mtvec:(Machine Trap Vector) 它保存发生异常时处理器需要跳转到的地址。
由于 K210 BSP 是基于勘智提供的 kendryte-standalone-sdk
库开发的,这个库在 kendryte-standalone-sdk/lib/bsp/crt.S
中有如下操作:
1 | la t0, trap_entry |
所以一旦发生中断异常,K210 将会率先进入 trap_entry
函数。
实现 rt_hw_context_switch_exit()
1 | .global rt_hw_context_switch_exit |
以上函数的作用就是把当前 to 线程的 CPU 寄存器现场恢复到 CPU 里。(也就是把 to 线程的 struct rt_hw_stack_frame
结构体里的内容恢复起来。)
最后就是实现时钟节拍
1 | static volatile unsigned long tick_cycles = 0; |
在 interrupt_gcc.S
中, K210 的每个中断都会调 uintptr_t handle_trap(uintptr_t mcause, uintptr_t epc, uintptr_t * sp)
这个函数,然后这个函数里会判断中断类型,相应的执行 tick_isr
这个函数。具体的系统时钟节拍初始化这里就不在赘述了。