以rt-smart在全志D1上的代码为例,主要注释了rt-smart在riscv64上的系统初始化和异常处理的代码
启动
代码路径
libcpu\risc-v\t-head\c906\startup_gcc.S
/*
Copyright (c) 2006-2018, RT-Thread Development Team
SPDX-License-Identifier: Apache-2.0
Change Logs:
Date Author Notes
2018/10/01 Bernard The first version
2018/12/27 Jesven Add SMP support
2020/6/12 Xim Port to QEMU and remove SMP support
/
#define ASSEMBLY
#define SSTATUS_FS 0x00006000U / initial state of FPU, clear to disable /
#include <cpuport.h>
.global _start
.section ".start", "ax"
_start:
j 1f
.word 0xdeadbeef
.align 3
.global g_wake_up
g_wake_up:
.dword 1
.dword 0
1:
csrw sie, 0 /超级用户模式中断使能关闭/
csrw sip, 0 /超级用户模式中断等待关闭/
la t0, trap_entry /将trap_entry的地址放入t0寄存器/
csrw stvec, t0 /配置异常服务程序的入口地址/
li x1, 0
/.........../ /初始化通用寄存器/
li x31,0
/ set to disable FPU */
li t0, SSTATUS_FS /将FS的bit位写入t0寄存器/
csrc sstatus, t0 /清除sstatus中的FS bit,关闭浮点单元/
li t0, 0x40000 /当 SUM=1 时,超级用户模式下,加载、存储和取指令请求可以访问标记为用户态的虚拟内存空间/
csrs sstatus, t0 /置位sstatus中的SUM位/
.option push
.option norelax
la gp, __global_pointer$
.option pop
// removed SMP support here
la sp, stack_start /栈指针的值来自于链接脚本中的__stack_start/
li t0, STACKSIZE
add sp, sp, t0 /栈自上到下增长/
csrw sscratch, sp /*sscratch存储栈顶的地址 */
j primary_cpu_entry /跳转到board中的C程序入口/
//BSP的C入口
void primary_cpu_entry(void)
{
extern void entry(void);
//初始化BSS
init_bss();
//关中断
rt_hw_interrupt_disable();
rt_assert_set_hook(__rt_assert_handler);
//启动RT-Thread Smart内核
entry();
}
异常处理
异常处理流程图
异常处理上半部分
/libcpu\risc-v\t-head\c906\interrupt_gcc.S/
#define ASSEMBLY
#include "cpuport.h"
#include "encoding.h"
#include "stackframe.h"
.section .text.entry
.align 2
.global trap_entry
.extern __stack_cpu0
.extern get_current_thread_kernel_stack_top
trap_entry: /异常处理函数的入口/
//backup sp
csrrw sp, sscratch, sp /将当前栈与sscratch做交换/
//load interrupt stack
la sp, __stack_cpu0 /sp指向cpu0的中断栈的栈顶/
//backup context
SAVE_ALL /CPU寄存器入栈,使能浮点的情况下浮点相关的寄存器也要入栈 并且要保存sstatus中浮点的运算状态/
RESTORE_SYS_GP /gp操作不用了解/
//check syscall
csrr t0, scause /读取scaue到t0/
li t1, 8 //environment call from u-mode /用户模式环境调用异常/
beq t0, t1, syscall_entry /如果是系统调用则跳转到系统调用处理函数,这个函数最终会调用sret/
csrr a0, scause /读取scause到a0,机器模式异常事件向量寄存器(MCAUSE)用于保存触发异常的异常事件向量号,用于在异常服务程序中处理对应事件/
csrrc a1, stval, zero /读取stval到a1,发生异常或者中断,且在机器模式响应时,处理器会更新 pc 到 MEPC,并根据异常类型更新 MTVAL/
csrr a2, sepc /读取sepc到a2, 超级用户模式异常保留程序计数器(SEPC)用于存储程序从异常服务程序退出时的程序计数器值(即
PC 值)/
mv a3, sp /读取sp的值到a3/
/* scause, stval, sepc, sp /
call handle_trap /进行中断处理/
中断处理
/libcpu\risc-v\t-head\c906\trap.c/
/ Trap entry /
void handle_trap(rt_size_t scause,rt_size_t stval,rt_size_t sepc,struct rt_hw_stack_frame sp)
{
/
SCAUSE
bit63 Interrupt-中断标记位
当 Interrupt 位为 0 时,表示触发异常的来源不是中断, Exception Code 按照异常解析。当 Interrupt 位为 1 时,表示触发异常的来源是中断, Exception Code 按照中断解析。该位会被 reset 置为 1’ b0。
bit04 Exception Code-异常向量号位
在处理器响应异常或中断时,该域会被更新为对应异常号,具体请参考 表 3.9 异常和中断向量分
配。该位会被 reset 置为 5’ b0。
*/
/我理解这里是想获取Exception Code,但是Exception Code是bit0 ~ bit4,这里用__MASK(5UL)更合适吧/
rt_size_t id = __MASKVALUE(scause,__MASK(63UL));
const char msg;
/ supervisor external interrupt */
/*如果scause的bit63是1,scause的bit04是9超级用户模式外部中断/
if ((SCAUSE_INTERRUPT & scause) && SCAUSE_S_EXTERNAL_INTR == (scause & 0xff))
{
rt_interrupt_enter();
plic_handle_irq();
rt_interrupt_leave();
return;
} /如果scause的bit63是1,scause的bit0~4是超级用户模式计时器中断/
else if ((SCAUSE_INTERRUPT | SCAUSE_S_TIMER_INTR) == scause)
{
/* supervisor timer /
rt_interrupt_enter();
tick_isr();
rt_interrupt_leave();
return;
} /其他中断/
else if (SCAUSE_INTERRUPT & scause)
{
if(id < sizeof(Interrupt_Name) / sizeof(const char ))
{
msg = Interrupt_Name[id];
}
else
{
msg = "Unknown Interrupt";
}
LOG_E("Unhandled Interrupt %ld:%s\n",id,msg);
}
else /异常处理/
{
#ifdef RT_USING_USERSPACE
/ page fault 缺页异常处理/
if (id == EP_LOAD_PAGE_FAULT ||
id == EP_STORE_PAGE_FAULT)
{
arch_expand_user_stack((void *)stval);
return;
}
#endif /其他异常处理,走到这里后打印一些必要信息,最终会走到while(1),进入死循环/
if(id < sizeof(Exception_Name) / sizeof(const char *))
{
msg = Exception_Name[id];
}
else
{
msg = "Unknown Exception";
}
rt_kprintf("Unhandled Exception %ld:%s\n",id,msg);
}
rt_kprintf("scause:0x%p,stval:0x%p,sepc:0x%p\n",scause,stval,sepc);
dump_regs(sp);
while(1);
}
在rt-smart中任务切换有三个相关的线程函数
rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候 被调用
rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程
rt_hw_context_switch_interrupt ():在中断环境下,从当前线程切换到目标线程。
rt_hw_context_switch_interrupt ()会将rt_thread_switch_interrupt_flag置为1,真正的线程切换动作在异常处理函数中完成。
异常处理下半部分
/* need to switch new thread 查询线程切换的flag是否被置位为1*/
la s0, rt_thread_switch_interrupt_flag /读取rt_thread_switch_interrupt_flag/
lw s2, 0(s0)
beqz s2, spurious_interrupt /rt_thread_switch_interrupt_flag如果为0那么直接跳转到spurious_interrupt进行寄存器恢复,并调用sret回到异常之前的状态/
sw zero, 0(s0) /rt_thread_switch_interrupt_flag = 0/
.global rt_hw_context_switch_interrupt_do
rt_hw_context_switch_interrupt_do:
//swap to thread kernel stack
csrr t0, sstatus /读取sstatus到t0/
andi t0, t0, 0x100 /bit8 超级用户模式保留特权状态位/
/*
该位用于保存处理器在降级到超级用户模式进入异常服务程序前的特权状态。
• 当 SPP 为 2’ b00 时,表示处理器进入异常服务程序前处于用户模式;
• 当 SPP 为 2’ b01 时,表示处理器进入异常服务程序前处于超级用户模式;
该位会被 reset 置 2’ b01。
/
beqz t0, __restore_sp_from_tcb_interrupt /如果是内核态发生异常/
__restore_sp_from_sscratch_interrupt:
csrr t0, sscratch /获取发生异常时的上下文数据/
j __move_stack_context_interrupt /如果是用户态发生异常/
/获取当前线程的栈顶位置存到t0中/
__restore_sp_from_tcb_interrupt:
la s0, rt_interrupt_from_thread
LOAD a0, 0(s0)
jal rt_thread_sp_to_thread
jal get_thread_kernel_stack_top
mv t0, a0
__move_stack_context_interrupt:
mv t1, sp//src /当前栈,当前栈存储的是发生异常时的通用寄存器信息/
mv sp, t0//switch stack / 将发生异常时的栈的值写回到sp寄存器 */
addi sp, sp, -CTX_REG_NR * REGBYTES /栈指针向下移动CTX_REG_NR * REGBYTES/
//copy context
li s0, CTX_REG_NR//cnt /需要恢复的寄存器的个数加载到s0/
mv t2, sp//dst /栈指针加载到t2/
/总结就是,当前CPU的中断栈存储了当前线程的通用寄存器的信息,如果发生任务切换,需要把这些信息拷贝到线程的栈里/
copy_context_loop_interrupt:
LOAD t0, 0(t1) /t1的值放到t0/
STORE t0, 0(t2) /t0的值放到t2/
addi s0, s0, -1 /要恢复的寄存器个数-1/
addi t1, t1, 8 /t1的地址加8/
addi t2, t2, 8 /t2的地址加8/
bnez s0, copy_context_loop_interrupt /如果s0不为0就重复拷贝/
la s0, rt_interrupt_from_thread
LOAD s1, 0(s0)
STORE sp, 0(s1) /更新from线程的sp指针/
la s0, rt_interrupt_to_thread
LOAD s1, 0(s0)
LOAD sp, 0(s1) /恢复to线程的sp/
#ifdef RT_USING_USERSPACE
mv a0, s1
jal rt_thread_sp_to_thread
jal lwp_mmu_switch /切换mmu,函数内部会判断from线程和to线程是不是在同一个lwp中,不是的话就会切换MMU/
#endif
spurious_interrupt:
RESTORE_ALL /恢复寄存器/
sret /超级用户模式异常返回指令/
原作者:RT-Thread小师弟