简介:
https://github.com/andeyqi/nuclei-sdk/tree/master/OS/RTThread/libcpu/risc-v/nuclei 该链接的nuclei_sdk 已经适配了rt_thread 系统的支持。
如何在RT-thread 系统上添加CPU 架构的适配,官方的文档说明有详细的介绍,在嵌入式领域有多种不同 CPU 架构,例如 Cortex-M、ARM920T、MIPS32、RISC-V 等等。为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。
适配CPU 按照文档的要求实现如下CPU架构相关的接口即可。
关闭全局中断
在RISC-V 架构下,全局中断的开关是通过CSR寄存器 MSTATUS.MIE 来控制,MIE = 0 关闭中断。
rt_base_t rt_hw_interrupt_disable(void) { return __RV_CSR_READ_CLEAR(CSR_MSTATUS, MSTATUS_MIE); }
打开全局中断
在 rt_hw_interrupt_enable(rt_base_t level) 里,将变量 level 作为需要恢复的状态,覆盖芯片的全局中断状态,将上述关闭中断前的CSR寄存器保存至level 变量。
void rt_hw_interrupt_enable(rt_base_t level) { __RV_CSR_WRITE(CSR_MSTATUS, level); }
实现线程栈初始化
在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init() 函数会调用栈初始化函数 rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。上下文在栈里的排布如下图所示:
/** * This function will initialize thread stack * * @param tentry the entry of thread * @param parameter the parameter of entry * @param stack_addr the beginning stack address * @param texit the function will be called when thread exit * * @return stack address */ rt_uint8_t* rt_hw_stack_init(void* tentry, void* parameter, rt_uint8_t* stack_addr, void* texit) { struct rt_hw_stack_frame* frame; rt_uint8_t* stk; int i; stk = stack_addr + sizeof(rt_ubase_t); stk = (rt_uint8_t*)RT_ALIGN_DOWN((rt_ubase_t)stk, REGBYTES); stk -= sizeof(struct rt_hw_stack_frame); frame = (struct rt_hw_stack_frame*)stk; for (i = 0; i < sizeof(struct rt_hw_stack_frame) / sizeof(rt_ubase_t); i++) { ((rt_ubase_t*)frame)[i] = 0xdeadbeef; } frame->ra = (rt_ubase_t)texit; frame->a0 = (rt_ubase_t)parameter; frame->epc = (rt_ubase_t)tentry; frame->mstatus = portINITIAL_MSTATUS; return stk; }
实现上下文切换
在不同的 CPU 架构里,线程之间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能是有差异的,也可能是一样的。在 Cortex-M 里面上下文切换都是统一使用 PendSV 异常来完成,切换部分并没有差异。但是为了能适应不同的 CPU 架构,RT-Thread 的 libcpu 抽象层还是需要实现三个线程切换相关的函数:
1) rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。
/* * void rt_hw_context_switch_to(rt_ubase_t to); * a0 --> to_thread */ .globl rt_hw_context_switch_to /* Start the first task. This also clears the bit that indicates the FPU is in use in case the FPU was used before the scheduler was started - which would otherwise result in the unnecessary leaving of space in the stack for lazy saving of FPU registers. */ .type rt_hw_context_switch_to, @function .align 3 rt_hw_context_switch_to: /* Setup Interrupt Stack using The stack that was used by main() before the scheduler is started is no longer required after the scheduler is started. Interrupt stack pointer is stored in CSR_MSCRATCH */ la t0, _sp csrw CSR_MSCRATCH, t0 LOAD sp, 0x0(a0) /* Read sp from first TCB member(a0) */ /* Pop PC from stack and set MEPC */ LOAD t0, 0 * REGBYTES(sp) csrw CSR_MEPC, t0 /* Pop mstatus from stack and set it */ LOAD t0, (portRegNum - 1) * REGBYTES(sp) csrw CSR_MSTATUS, t0 /* Interrupt still disable here */ /* Restore Registers from Stack */ LOAD x1, 1 * REGBYTES(sp) /* RA */ LOAD x5, 2 * REGBYTES(sp) LOAD x6, 3 * REGBYTES(sp) LOAD x7, 4 * REGBYTES(sp) LOAD x8, 5 * REGBYTES(sp) LOAD x9, 6 * REGBYTES(sp) LOAD x10, 7 * REGBYTES(sp) LOAD x11, 8 * REGBYTES(sp) LOAD x12, 9 * REGBYTES(sp) LOAD x13, 10 * REGBYTES(sp) LOAD x14, 11 * REGBYTES(sp) LOAD x15, 12 * REGBYTES(sp) #ifndef __riscv_32e LOAD x16, 13 * REGBYTES(sp) LOAD x17, 14 * REGBYTES(sp) LOAD x18, 15 * REGBYTES(sp) LOAD x19, 16 * REGBYTES(sp) LOAD x20, 17 * REGBYTES(sp) LOAD x21, 18 * REGBYTES(sp) LOAD x22, 19 * REGBYTES(sp) LOAD x23, 20 * REGBYTES(sp) LOAD x24, 21 * REGBYTES(sp) LOAD x25, 22 * REGBYTES(sp) LOAD x26, 23 * REGBYTES(sp) LOAD x27, 24 * REGBYTES(sp) LOAD x28, 25 * REGBYTES(sp) LOAD x29, 26 * REGBYTES(sp) LOAD x30, 27 * REGBYTES(sp) LOAD x31, 28 * REGBYTES(sp) #endif addi sp, sp, portCONTEXT_SIZE mret .size rt_hw_context_switch_to, . - rt_hw_context_switch_to
该函数的实现主要实现了如下功能
将_sp 的值加载到 mscratch 寄存器
sp 指令了RAM 地址的最高端地址,在link 文件中,根据上述的描述该特性事为了不同特权模式下的数据访问隔离特性。
实现 rt_hw_context_switch()/ rt_hw_context_switch_interrupt()
函数 rt_hw_context_switch() 和函数 rt_hw_context_switch_interrupt() 都有两个参数,分别是 from 线程和 to 线程。它们实现从 from 线程切换到 to 线程的功能。这两个函数的实现是一致的,通过软中断触发任务切换
对应软中断代码如下:
.align 2 .global eclic_msip_handler .type eclic_msip_handler, @function eclic_msip_handler: addi sp, sp, -portCONTEXT_SIZE STORE x1, 1 * REGBYTES(sp) /* RA */ STORE x5, 2 * REGBYTES(sp) STORE x6, 3 * REGBYTES(sp) STORE x7, 4 * REGBYTES(sp) STORE x8, 5 * REGBYTES(sp) STORE x9, 6 * REGBYTES(sp) STORE x10, 7 * REGBYTES(sp) STORE x11, 8 * REGBYTES(sp) STORE x12, 9 * REGBYTES(sp) STORE x13, 10 * REGBYTES(sp) STORE x14, 11 * REGBYTES(sp) STORE x15, 12 * REGBYTES(sp) #ifndef __riscv_32e STORE x16, 13 * REGBYTES(sp) STORE x17, 14 * REGBYTES(sp) STORE x18, 15 * REGBYTES(sp) STORE x19, 16 * REGBYTES(sp) STORE x20, 17 * REGBYTES(sp) STORE x21, 18 * REGBYTES(sp) STORE x22, 19 * REGBYTES(sp) STORE x23, 20 * REGBYTES(sp) STORE x24, 21 * REGBYTES(sp) STORE x25, 22 * REGBYTES(sp) STORE x26, 23 * REGBYTES(sp) STORE x27, 24 * REGBYTES(sp) STORE x28, 25 * REGBYTES(sp) STORE x29, 26 * REGBYTES(sp) STORE x30, 27 * REGBYTES(sp) STORE x31, 28 * REGBYTES(sp) #endif /* Push mstatus to stack */ csrr t0, CSR_MSTATUS STORE t0, (portRegNum - 1) * REGBYTES(sp) /* Push additional registers */ /* Store sp to task stack */ LOAD t0, rt_interrupt_from_thread STORE sp, 0(t0) csrr t0, CSR_MEPC STORE t0, 0(sp) jal xPortTaskSwitch /* Switch task context */ LOAD t0, rt_interrupt_to_thread LOAD sp, 0x0(t0) /* Pop PC from stack and set MEPC */ LOAD t0, 0 * REGBYTES(sp) csrw CSR_MEPC, t0 /* Pop additional registers */ /* Pop mstatus from stack and set it */ LOAD t0, (portRegNum - 1) * REGBYTES(sp) csrw CSR_MSTATUS, t0 /* Interrupt still disable here */ /* Restore Registers from Stack */ LOAD x1, 1 * REGBYTES(sp) /* RA */ LOAD x5, 2 * REGBYTES(sp) LOAD x6, 3 * REGBYTES(sp) LOAD x7, 4 * REGBYTES(sp) LOAD x8, 5 * REGBYTES(sp) LOAD x9, 6 * REGBYTES(sp) LOAD x10, 7 * REGBYTES(sp) LOAD x11, 8 * REGBYTES(sp) LOAD x12, 9 * REGBYTES(sp) LOAD x13, 10 * REGBYTES(sp) LOAD x14, 11 * REGBYTES(sp) LOAD x15, 12 * REGBYTES(sp) #ifndef __riscv_32e LOAD x16, 13 * REGBYTES(sp) LOAD x17, 14 * REGBYTES(sp) LOAD x18, 15 * REGBYTES(sp) LOAD x19, 16 * REGBYTES(sp) LOAD x20, 17 * REGBYTES(sp) LOAD x21, 18 * REGBYTES(sp) LOAD x22, 19 * REGBYTES(sp) LOAD x23, 20 * REGBYTES(sp) LOAD x24, 21 * REGBYTES(sp) LOAD x25, 22 * REGBYTES(sp) LOAD x26, 23 * REGBYTES(sp) LOAD x27, 24 * REGBYTES(sp) LOAD x28, 25 * REGBYTES(sp) LOAD x29, 26 * REGBYTES(sp) LOAD x30, 27 * REGBYTES(sp) LOAD x31, 28 * REGBYTES(sp) #endif addi sp, sp, portCONTEXT_SIZE mret .size eclic_msip_handler, . - eclic_msip_handler
该函数或根据from 和 to 任务的信息完成任务切换。