移植rvos - preemptive
gd32vf103中断模式
gd32vf103的中断有2种模式:向量处理模式和非向量处理模式
非向量处理模式
中断被处理器内核响应后,处理器会直接跳入到所有非向量中断共享的入口地址,该入口地址可以通过软件进行设置。
非向量中断入口地址的选择方式如下:
mtvec 中记录了异常入口地址
mtvt2 中记录了非向量中断入口地址
mtvt2中有个有个bit0,值为0的时候,非向量中断入口地址是mtvec中的地址,这个时候异常和中断使用一个入口地址。值为1的时候,非向量中断入口地址是mtvt2中记录的地址,这个时候,非向量中断的入口地址是mtvt2,而异常的入口地址是mtevc,异常和中断的入口地址是分开的。
启动代码中,设置了入口地址为mtvt2中的地址
/* 自定义
* mtvt2 用于指定 ECLIC 非向量模式的中断 common-code 入口地址
* bit2~bit31 用于存放common-code入口地址 所以改地址需要4bytes对齐
* bit0:
* 0: 入口地址有mtvec决定
* 1: 入口地址就是设置的值
*/
la t0, irq_entry
csrw CSR_MTVT2, t0
csrs CSR_MTVT2, 0x1
向量处理模式
如果被配置成为向量处理模式,则该中断被处理器内核响应后,处理器会直接跳入该中断的向
量入口(Vector Table Entry)存储的目标地址,即该中断源的中断服务程序。
/*
* Intialize ECLIC vector interrupt
* base address mtvt to vector_base
*/
/* mtvt ECLIC 中断向量表的基地址
* 填充基地址
*/
la t0, vector_base
csrw CSR_MTVT, t0
移植rvos的难点
首先rvos的timer中断函数处理是这样的
# 这里很明显的是用的非向量处理模式
# risc-v中异常和中断统称为trap
# 和上面介绍gd32vf103非向量模式中说的
# mtvt2中有个有个bit0,值为0的时候,非向量中断入口地址是mtvec中的地址,这个时候异常和中断使用一个入口地址
# 这里是一个意思
# 只是gd32vf103自己加了一个可以将异常和中断入口函数分开的功能
# interrupts and exceptions while in machine mode come here.
.globl trap_vector
# the trap vector base address must always be aligned on a 4-byte boundary
.align 4
trap_vector:
# 进入trap第一件事,保存任务上下文
# 难点在进入就保存上下文这里
# gd32vf103中使用的是nuclei sdk
# 即使是使用非向量模式中的,将中断和异常分开的方法,也是有上下文保护及csr寄存器保护的功能
# 这部分代码后面分析
# save context(registers).
csrr t6, mscratch # read t6 back from mscratch
reg_save t6
# save mepc to context of current task
csrr a0, mepc
sw a0, 124(t6)
# call the C trap handler in trap.c
csrr a0, mepc
csrr a1, mcause
# 调用处理程序
# trap_handler中会判断是不是timer中断,是的话则会调用timer中断处理函数进行schedule()
call trap_handler
# trap_handler will return the return address via a0.
csrw mepc, a0
# restore context(registers).
csrr t6, mscratch
reg_restore t6
# return to whatever we were doing before trap.
mret
OK,现在看一下gd32vf103 nuclei sdk的处理
.global irq_entry
.weak irq_entry
/* This label will be set to MTVT2 register */
irq_entry:
/* 这里可以看到进入的第一件事就是保存上下文和csr
*/
/* Save the caller saving registers (context) */
SAVE_CONTEXT
/* Save the necessary CSR registers */
SAVE_CSR_CONTEXT
/* This special CSR read/write operation, which is actually
* claim the CLIC to find its pending highest ID, if the ID
* is not 0, then automatically enable the mstatus.MIE, and
* jump to its vector-entry-label, and update the link register
*/
csrrw ra, CSR_JALMNXTI, ra
/* Critical section with interrupts disabled */
DISABLE_MIE
/* Restore the necessary CSR registers */
RESTORE_CSR_CONTEXT
/* Restore the caller saving registers (context) */
RESTORE_CONTEXT
/* Return to regular code */
mret
由于进入就保存上下文和csr,而且我们又不想修改这部分sdk代码,所以这里的解决方法是使用向量质量模式
使用向量处理模式
我们的调度是使用的timer中断,这里我们重写这个中断处理函数
.global eclic_mtip_handler
.align 4
eclic_mtip_handler:
# save context(registers).
csrr t6, CSR_MSCRATCH
reg_save t6
# save mepc to context of current task
csrr a0, mepc
sw a0, 124(t6)
call timer_handler
mret
如何将中断改成向量模式?
void mtimer_init(void)
{
uint64_t ticks = SYSTICK_TICK_CONST;
SysTick_Config(ticks);
ECLIC_DisableIRQ(SysTimer_IRQn);
ECLIC_SetLevelIRQ(SysTimer_IRQn, configKERNEL_INTERRUPT_PRIORITY);
/*
* mcu gd32vf103 Each interrupt can be configured with vectored interrupt or non-vectored interrupt.
* For non-vector interrupts, the interrupt function entry is irq_entry. When processing the interrupt,
* SAVE_CONTEXT and SAVE_CSR_CONTEXT will be executed first to save the context and csr registers.
* After the rvos timer interrupt is triggered, we want to save the task directly to the context information,
* so the non-vector interrupt mode cannot be used for the timer interrupt, but the vector interrupt is used.
*
* In the vector processing mode, because the processor does not save the context before jumping into the
* interrupt service routine, in theory, the interrupt service routine function itself cannot make sub-function
* calls (that is, it must be a Leaf Function).
* If the interrupt service routine function accidentally calls other sub-functions (not Leaf Function),
* it will cause a function error if it is not handled. In order to avoid this accidental error situation,
* as long as the special __attribute__ ((interrupt)) is used to modify the interrupt service routine function,
* then the compiler will automatically make a judgment. When function, it will automatically insert a piece of code to save the context.
*
* In general, for the vector processing mode, we need to write an interrupt processing function in C language,
* and it must be modified with __attribute__ ((interrupt)). But when this function is called, there will be a stack operation first.
* When we need it, the first thing to call this function is to save the task until morning and afternoon, which conflicts with our needs.
*
* So we use the vector processing mode here, but do not use the __attribute__ ((interrupt)) modification.
*/
ECLIC_SetShvIRQ(SysTimer_IRQn, ECLIC_VECTOR_INTERRUPT);
ECLIC_EnableIRQ(SysTimer_IRQn);
__enable_irq();
}
关键语句
ECLIC_SetShvIRQ(SysTimer_IRQn, ECLIC_VECTOR_INTERRUPT);
向量模式需要注意的地方
向量模式中断服务程序函数不小心调用了其他的子函数(不是 Leaf Function),如果不加处理则会造成功能的错误。为了规避这种不小心造成的错误情形,只要使用了特殊的 attribute ((interrupt)) 来修饰该中断服务程序函数,那么编译器会自动的进行判断,当编译器发现该函数调用了其他子函数时,便会自动的插入一段代码进行上下文的保存。
当前移植到时候,如果中断函数写成c函数,那么执行这个中断函数的时候,编译器自动会加上入栈操作,所以不能写成c函数。但是写成汇编的话,无法使用**attribute ((interrupt))** 修饰,
这里有些纠结。
但是为什么最后这里决定使用汇编呢?
因为测试发现调度完成后,switch_to调度完成后,通过mret返回要切换的任务中执行,此时寄存器数据是上次调度时候保存的值。