在riscv中异常分为exception和中断。其中exception一般就是指同步异常,指的是core内部触发的各种异常信号,会立即被core响应。中断一般就是指的是异步中断,信号来源基本为外部硬件。通常中断信号会先送给中断控制器(CLIC),再由clic发送给core内部。
本篇主要分析在freertos内核中的riscv框架是如何处理中断的。
中断模式
在RISC-V架构中,中断处理主要有两种模式:直接中断模式和向量中断模式。这两种模式通过CSR寄存器 mtvec(机器模式陷阱向量基地址寄存器)进行配置。
补充说明:
mtvec寄存器:机器模式陷阱向量基地址寄存器,是配置中断模式的核心。中断控制器:实际的优先级和中断使能/屏蔽通常由平台级中断控制器(如PLIC)或核心本地中断器(CLINT)管理,架构本身不定义硬件优先级。
默认模式:直接中断模式是RISC-V的默认和必需实现模式,而向量模式是可选的扩展。
处理流程
入口设置
把 mtvec 设成 vectored mode,并传入向量表地址,向量表一般定义在 vector.S中,里面按照中断号mapping了中断入口地址。每4字节一个入口,相当于一条普通指令长度,故一般软件都会在这里放j指令跳转到真正的中断处理程序里面。
中断触发
硬件产生
machine external interruptCPU 自动写
mcause/mepc/mstatus,清除MIE,跳到mtvec + 4 * cause入口地址,在freertos中一般会用内核提供好的中断处理函数freertos_risc_v_interrupt_handler
freertos通用内核中断处理函数freertos_risc_v_interrupt_handler
/*-----------------------------------------------------------*/
.section .text.freertos_risc_v_interrupt_handler
freertos_risc_v_interrupt_handler:
portcontextSAVE_INTERRUPT_CONTEXT
call freertos_risc_v_application_interrupt_handler
portcontextRESTORE_CONTEXT这里可以看出就三步:(1)保存上下文(2)处理中断(3)恢复上下文
上下文保存
上下文保存在portContext.h的 portcontextSAVE_CONTEXT_INTERNAL 宏
它做的事是:
在当前任务栈上为上下文开辟 portCONTEXT_SIZE
保存通用寄存器 x1, x5-x31
保存 xCriticalNesting
按需保存 FPU 上下文
只在 mstatus.FS == dirty 时保存
按需保存 VPU 上下文
保存 mstatus
保存芯片扩展寄存器
最后把当前 sp 写回 pxCurrentTCB->pxTopOfStack
这里有几个关键点:
sp 本身不作为普通寄存器存一份,而是“当前上下文帧的基址”,最后直接写入 TCB
gp/tp 没有保存,这是 FreeRTOS 这份 RISC-V port 的设计假设
对中断和异常,mepc 的处理不同
对“异步中断”路径,portcontextSAVE_INTERRUPT_CONTEXT 在保存完通用现场后会:
读 mcause 到 a0
读 mepc 到 a1
把“未修改的 mepc”存到任务栈
然后把 sp 切到 xISRStackTop
这意味着:
被打断任务的完整现场在“任务自己的栈”里
C 级 ISR 运行在独立 ISR 栈上
后面是否切换任务,只取决于 pxCurrentTCB 最终指向谁
.macro portcontextSAVE_CONTEXT_INTERNAL
addi sp, sp, -portCONTEXT_SIZE
store_x x1, 2 * portWORD_SIZE( sp )
store_x x5, 3 * portWORD_SIZE( sp )
store_x x6, 4 * portWORD_SIZE( sp )
store_x x7, 5 * portWORD_SIZE( sp )
store_x x8, 6 * portWORD_SIZE( sp )
store_x x9, 7 * portWORD_SIZE( sp )
store_x x10, 8 * portWORD_SIZE( sp )
store_x x11, 9 * portWORD_SIZE( sp )
store_x x12, 10 * portWORD_SIZE( sp )
store_x x13, 11 * portWORD_SIZE( sp )
store_x x14, 12 * portWORD_SIZE( sp )
store_x x15, 13 * portWORD_SIZE( sp )
#ifndef __riscv_32e
store_x x16, 14 * portWORD_SIZE( sp )
store_x x17, 15 * portWORD_SIZE( sp )
store_x x18, 16 * portWORD_SIZE( sp )
store_x x19, 17 * portWORD_SIZE( sp )
store_x x20, 18 * portWORD_SIZE( sp )
store_x x21, 19 * portWORD_SIZE( sp )
store_x x22, 20 * portWORD_SIZE( sp )
store_x x23, 21 * portWORD_SIZE( sp )
store_x x24, 22 * portWORD_SIZE( sp )
store_x x25, 23 * portWORD_SIZE( sp )
store_x x26, 24 * portWORD_SIZE( sp )
store_x x27, 25 * portWORD_SIZE( sp )
store_x x28, 26 * portWORD_SIZE( sp )
store_x x29, 27 * portWORD_SIZE( sp )
store_x x30, 28 * portWORD_SIZE( sp )
store_x x31, 29 * portWORD_SIZE( sp )
#endif /* ifndef __riscv_32e */
load_x t0, xCriticalNesting /* Load the value of xCriticalNesting into t0. */
store_x t0, portCRITICAL_NESTING_OFFSET * portWORD_SIZE( sp ) /* Store the critical nesting value to the stack. */
#if( configENABLE_FPU == 1 )
csrr t0, mstatus
srl t1, t0, MSTATUS_FS_OFFSET
andi t1, t1, 3
addi t2, x0, 3
bne t1, t2, 1f /* If FPU status is not dirty, do not save FPU registers. */
portcontexSAVE_FPU_CONTEXT
1:
#endif
#if( configENABLE_VPU == 1 )
csrr t0, mstatus
srl t1, t0, MSTATUS_VS_OFFSET
andi t1, t1, 3
addi t2, x0, 3
bne t1, t2, 2f /* If VPU status is not dirty, do not save VPU registers. */
portcontexSAVE_VPU_CONTEXT
2:
#endif
csrr t0, mstatus
store_x t0, 1 * portWORD_SIZE( sp )
portasmSAVE_ADDITIONAL_REGISTERS /* Defined in freertos_risc_v_chip_specific_extensions.h to save any registers unique to the RISC-V implementation. */
#if( configENABLE_FPU == 1 )
/* Mark the FPU as clean, if it was dirty and we saved FPU registers. */
srl t1, t0, MSTATUS_FS_OFFSET
andi t1, t1, 3
addi t2, x0, 3
bne t1, t2, 3f
li t1, ~MSTATUS_FS_MASK
and t0, t0, t1
li t1, MSTATUS_FS_CLEAN
or t0, t0, t1
csrw mstatus, t0
3:
#endif
#if( configENABLE_VPU == 1 )
/* Mark the VPU as clean, if it was dirty and we saved VPU registers. */
srl t1, t0, MSTATUS_VS_OFFSET
andi t1, t1, 3
addi t2, x0, 3
bne t1, t2, 4f
li t1, ~MSTATUS_VS_MASK
and t0, t0, t1
li t1, MSTATUS_VS_CLEAN
or t0, t0, t1
csrw mstatus, t0
4:
#endif
load_x t0, pxCurrentTCB /* Load pxCurrentTCB. */
store_x sp, 0 ( t0 ) /* Write sp to first TCB member. */
.endm
/*-----------------------------------------------------------*/ .macro portcontextSAVE_INTERRUPT_CONTEXT
portcontextSAVE_CONTEXT_INTERNAL
csrr a0, mcause
csrr a1, mepc
store_x a1, 0 ( sp ) /* Asynchronous interrupt so save unmodified exception return address. */
load_x sp, xISRStackTop /* Switch to ISR stack. */
.endm
/*-----------------------------------------------------------*/上下文恢复
上下文保存在portContext.h的 portcontextRESTORE_CONTEXT 宏
它的顺序是:
从 pxCurrentTCB 取出要运行任务的栈顶
从栈 [0] 恢复 mepc
恢复芯片扩展寄存器
恢复 mstatus
如果 FPU/VPU 状态是 dirty,就恢复其上下文
恢复 xCriticalNesting
恢复 x1, x5-x31
addi sp, sp, portCONTEXT_SIZE
mret
最核心的一句是:恢复时重新从 pxCurrentTCB 取 sp。
所以只要中断处理期间调用了 vTaskSwitchContext() 并修改了 pxCurrentTCB,mret 回去的就已经不是原任务,而是新任务。
.macro portcontextRESTORE_CONTEXT
load_x t1, pxCurrentTCB /* Load pxCurrentTCB. */
load_x sp, 0 ( t1 ) /* Read sp from first TCB member. */
/* Load mepc with the address of the instruction in the task to run next. */
load_x t0, 0 ( sp )
csrw mepc, t0
/* Defined in freertos_risc_v_chip_specific_extensions.h to restore any registers unique to the RISC-V implementation. */
portasmRESTORE_ADDITIONAL_REGISTERS
/* Restore mstatus register. It is important to use t3 (and not t0) here as t3
* is not clobbered by portcontextRESTORE_VPU_CONTEXT and
* portcontextRESTORE_FPU_CONTEXT. */
load_x t3, 1 * portWORD_SIZE( sp )
csrw mstatus, t3
#if( configENABLE_VPU == 1 )
srl t1, t3, MSTATUS_VS_OFFSET
andi t1, t1, 3
addi t2, x0, 3
bne t1, t2, 5f /* If VPU status is not dirty, do not restore VPU registers. */
portcontextRESTORE_VPU_CONTEXT
5:
#endif /* ifdef portasmSTORE_VPU_CONTEXT */
#if( configENABLE_FPU == 1 )
srl t1, t3, MSTATUS_FS_OFFSET
andi t1, t1, 3
addi t2, x0, 3
bne t1, t2, 6f /* If FPU status is not dirty, do not restore FPU registers. */
portcontextRESTORE_FPU_CONTEXT
6:
#endif /* ifdef portasmSTORE_FPU_CONTEXT */
load_x t0, portCRITICAL_NESTING_OFFSET * portWORD_SIZE( sp ) /* Obtain xCriticalNesting value for this task from task's stack. */
load_x t1, pxCriticalNesting /* Load the address of xCriticalNesting into t1. */
store_x t0, 0 ( t1 ) /* Restore the critical nesting value for this task. */
load_x x1, 2 * portWORD_SIZE( sp )
load_x x5, 3 * portWORD_SIZE( sp )
load_x x6, 4 * portWORD_SIZE( sp )
load_x x7, 5 * portWORD_SIZE( sp )
load_x x8, 6 * portWORD_SIZE( sp )
load_x x9, 7 * portWORD_SIZE( sp )
load_x x10, 8 * portWORD_SIZE( sp )
load_x x11, 9 * portWORD_SIZE( sp )
load_x x12, 10 * portWORD_SIZE( sp )
load_x x13, 11 * portWORD_SIZE( sp )
load_x x14, 12 * portWORD_SIZE( sp )
load_x x15, 13 * portWORD_SIZE( sp )
#ifndef __riscv_32e
load_x x16, 14 * portWORD_SIZE( sp )
load_x x17, 15 * portWORD_SIZE( sp )
load_x x18, 16 * portWORD_SIZE( sp )
load_x x19, 17 * portWORD_SIZE( sp )
load_x x20, 18 * portWORD_SIZE( sp )
load_x x21, 19 * portWORD_SIZE( sp )
load_x x22, 20 * portWORD_SIZE( sp )
load_x x23, 21 * portWORD_SIZE( sp )
load_x x24, 22 * portWORD_SIZE( sp )
load_x x25, 23 * portWORD_SIZE( sp )
load_x x26, 24 * portWORD_SIZE( sp )
load_x x27, 25 * portWORD_SIZE( sp )
load_x x28, 26 * portWORD_SIZE( sp )
load_x x29, 27 * portWORD_SIZE( sp )
load_x x30, 28 * portWORD_SIZE( sp )
load_x x31, 29 * portWORD_SIZE( sp )
#endif /* ifndef __riscv_32e */
addi sp, sp, portCONTEXT_SIZE
mret
.endm
/*-----------------------------------------------------------*/