在HPM系列芯片上实现FreeRTOS对中断优先级的管理
FreeRTOS在RISCV MCU上运行时,大多数的临界段保护是通过开关MSTATUS.MIE来实现的。
这种实现方式的好处是简单、通用。所有的RISCV处理器都可以使用这种方式,因为这是RISCV的标准实现。
但是这种实现方式也存在一定的局限性,临界段的管理过于粗放,所有中断的响应都会被影响。在不支持中断嵌套/抢占的处理器上,这种实现没有问题,但是在支持中断嵌套/抢占和中断优先级的处理器上,这种处理方式就过于粗放了。
HPMicro的MCU的PLIC中断管理器支持中断优先级和中断抢占功能,那么有没有可能使实时性要求高的中断不受FreeRTOS的临界段保护影响呢?本文介绍一种FreeRTOS临界段保护的实现方式,可以实现此功能。
首先根据中断是否由FreeRTOS管理,将中断分为受RTOS管理的和不受RTOS管理的中断,此外由于中断和异常的入口函数相同,还需要考虑不可屏蔽的异常:
受RTOS管理的中断 | 不受RTOS管理的中断 | 异常 |
---|---|---|
低优先级PLIC中断 | 高优先级PLIC中断 | 系统调用ecall |
Machine Timer中断 | 其他异常 | |
软中断 |
如上图显示的,当中断的优先级高于RTOS管理的优先级时,其中断处理不会被内核的任何操作所延迟,但是其中不可以调用任何FreeRTOS的API接口。
低优先级的中断处理函数中可以调用FreeRTOS的中断版本的API。
中断屏蔽的实现
如下讨论的情况,均基于非向量模式下,开启中断嵌套/抢占的情形。
低优先级PLIC中断
屏蔽低优先级PLIC中断,可以通过修改PLIC的THRESHOLD寄存器完成。假设想要屏蔽优先级小于等于X的中断,那么将THRESHOLD寄存器配置为X即可。优先级>X的中断不会被屏蔽。
intc_m_set_threshold(uxSavedStatusValue);
Machine Timer中断
屏蔽Machine Timer中断,修改CSR寄存器MIE.MTIE即可。
__asm volatile( "csrc mie, %0" :: "r"(0x80) );
软中断
屏蔽软中断,修改CSR寄存器MIE.MSIE即可。
__asm volatile( "csrc mie, %0" :: "r"(0x08) );
对于设计目标(使高优先级PLIC中断不受RTOS临界段影响)来说,RTOS进入临界段时,需要将所有受RTOS管理的中断全部屏蔽掉。对于没有使用Machine Timer和软中断的情况来说,响应的步骤可以省略。
在中断中进入临界段时,还需要记录当前plic的threshold的值,在退出中断临界段时需要恢复
在退出临界段时,需要考虑如下的情况:
- 线程临界段退出
- 对于线程临界段来说,考虑以下两种情况:
- 未发生任务切换
- 临界段代码执行完毕,临界段退出的代码得到执行,只需在临界段嵌套的最外层退出的时候,将进入临界段时的操作逆向操作即可
- 发生任务切换
- 由于RISCV上使用ecall作为系统调用的指令,所以在线程临界段内部,如果需要任务切换,那么ecall指令作为不可屏蔽的异常,会使处理器在线程临界段内进入异常处理程序,由于发生了任务切换,当异常处理代码退出时,处理器已经切换到了另一个线程中。这种情况下,切换前线程中退出临界段的代码不会得到执行,所以此时受RTOS管理的中断仍然在被屏蔽的状态。
- 为了解决这个问题,需要在任务堆栈中保存之前的MIE和PLIC.THRESHOLD的值。
- 在一些非RISCV架构的处理器上,系统调用也是可以被屏蔽的,此时虽然产生了系统调用,但是其会在临界段退出代码执行完毕后,再得到执行,所以不需要考虑之前描述的问题。
- 或者可以考虑使用可以屏蔽的软中断代替ecall指令实现系统调用,不过这不在本文的讨论范围之内了
- 未发生任务切换
- 对于线程临界段来说,考虑以下两种情况:
我们解决了在线程中进入和退出临界段时的处理,现在我们来看一下在中断中退出临界段的实现:
- 中断临界段退出
- 在中断中如果需要进行任务切换,是通过程序主动调用portYIELD_FROM_ISR来实现,不需要执行ecall,所以中断临界段的进入和退出都是成对调用的,退出时只需要将状态恢复到进入临界段前即可
tickless模式修改
由于tickless模式会使mcu进入低功耗模式,低功耗模式下,中断可以将mcu从低功耗模式下唤醒。在进入低功耗模式前,有一些代码需要执行;同时在退出低功耗后,有一些操作也需要执行,然后才可以去处理中断。在之前的实现中,在进入低功耗前关闭MSTATUS.MIE,保持MIE中的各中断使能位,此时发生中断时,cpu会从wfi的下一句开始执行,直到打开MSTATUS.MIE后才会跳转到中断中去处理。在此期间我们可以做一些恢复正常模式的一些准备工作。
如果低功耗期间保持MSTATUS.MIE打开的话,发生中断后,mcu会直接跳转到中断处理函数中,此时退出低功耗时需要执行的代码得不到执行,可能会导致程序执行出错。
所以进入低功耗和退出低功耗的操作代码是严格意义上的临界段代码,此时无论受RTOS管理的中断还是不受RTOS管理的中断,都不应该去响应。所以在MCU低功耗模式时,是保持MSTATUS.MIE总中断响应关闭,MIE中保持正常运行时的状态,PLIC的threashold保持为默认状态。
使用方法
在HPM_SDKv1.8.0中,支持了上述的优先级管理功能,想要使用此功能,需要在工程的CMakeLists.txt中添加sdk_compile_definitions(-DUSE_SYSCALL_INTERRUPT_PRIORITY=1);然后需要在FreeRTOSConfig.h中定义configMAX_SYSCALL_INTERRUPT_PRIORITY为RTOS管理的最大优先级的值,即优先级大于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断不会被RTOS管理。