Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
risc-v_RISCV
移植问题
RISC-V 移植那些事
5.00
发布于 2022-04-10 20:30:14 浏览:4932
订阅该版
[tocm] 最近弄完了CH32V103R-EVT的BSP和软件包: - [沁恒微 CH32V103R-EVT BSP提交记录](https://club.rt-thread.org/ask/article/3641.html) - [CH32V103R-EVT RT-Thread Studio开发板支持包制作](https://club.rt-thread.org/ask/article/3660.html) 期间对比了GD32VF103V-EVAL BSP的port, 现分享RISC-V移植的一些心得,也做个总结,如有不正确的地方,欢迎留言交流讨论。 # 1 准备 ## 1.1 hardware - GD32VF103V-EVAL+ mini jlink ![image-20220408101627942.png](https://oss-club.rt-thread.org/uploads/20220410/01aef6c80c9bc8b722fa4ce4b0ba7a7d.png.webp) - CH32V103R ![image-20220410195629080.png](https://oss-club.rt-thread.org/uploads/20220410/36122873cdaf0d3fda7cf89b95e4c412.png.webp) ## 1.2 Software - NucleiStudio_IDE_202204-win64(for GD32VF103V) - MounRiver Studio(for CH32V103R) > 现在RT-Thread Stuido 对 RISC-V debug 做的不是很好,先使用官方IDE # 2 porting 先看一下最新rtthread源码libcpu里GD32VF103V-EVAL和CH32V103R-EVT使用的porting文件 ![image-20220409092916675.png](https://oss-club.rt-thread.org/uploads/20220410/dd9166cdf18ef2b8bb914b9995cd82f0.png.webp) | Board | Porting folder(libcpu/riscv/) | Note | | --------------- | ----------------------------- | ---------------------------------- | | GD32VF103V-EVAL | bumblebee + common | refer to `libcpu/riscv/Sconscript` | | CH32V103R-EVT | ch32v1 | | 整理一下,新建beyondcompare 文件夹对比 ![image-20220409093849202.png](https://oss-club.rt-thread.org/uploads/20220410/3ceecfbddd1ae7fd298361879d30ddca.png.webp) > Note: 为了方便对比,我把bumblebee的interrupt_gcc.S移到了common文件里 下面通过差异之处的对比,我们来看下两份BSP porting 孰优孰劣 ## 2.1 cpuport.c ### 2.1.1 rt_hw_stack_frame 很重要的任务栈存储格式,这部分GD32VF103V-EVAL和CH32V103R-EVT是完全一样的。 ```C struct rt_hw_stack_frame { rt_ubase_t epc; /* epc - epc - program counter */ rt_ubase_t ra; /* x1 - ra - return address for jumps */ rt_ubase_t mstatus; /* - machine status register */ rt_ubase_t gp; /* x3 - gp - global pointer */ rt_ubase_t tp; /* x4 - tp - thread pointer */ rt_ubase_t t0; /* x5 - t0 - temporary register 0 */ rt_ubase_t t1; /* x6 - t1 - temporary register 1 */ rt_ubase_t t2; /* x7 - t2 - temporary register 2 */ rt_ubase_t s0_fp; /* x8 - s0/fp - saved register 0 or frame pointer */ rt_ubase_t s1; /* x9 - s1 - saved register 1 */ rt_ubase_t a0; /* x10 - a0 - return value or function argument 0 */ rt_ubase_t a1; /* x11 - a1 - return value or function argument 1 */ rt_ubase_t a2; /* x12 - a2 - function argument 2 */ rt_ubase_t a3; /* x13 - a3 - function argument 3 */ rt_ubase_t a4; /* x14 - a4 - function argument 4 */ rt_ubase_t a5; /* x15 - a5 - function argument 5 */ rt_ubase_t a6; /* x16 - a6 - function argument 6 */ rt_ubase_t a7; /* x17 - s7 - function argument 7 */ rt_ubase_t s2; /* x18 - s2 - saved register 2 */ rt_ubase_t s3; /* x19 - s3 - saved register 3 */ rt_ubase_t s4; /* x20 - s4 - saved register 4 */ rt_ubase_t s5; /* x21 - s5 - saved register 5 */ rt_ubase_t s6; /* x22 - s6 - saved register 6 */ rt_ubase_t s7; /* x23 - s7 - saved register 7 */ rt_ubase_t s8; /* x24 - s8 - saved register 8 */ rt_ubase_t s9; /* x25 - s9 - saved register 9 */ rt_ubase_t s10; /* x26 - s10 - saved register 10 */ rt_ubase_t s11; /* x27 - s11 - saved register 11 */ rt_ubase_t t3; /* x28 - t3 - temporary register 3 */ rt_ubase_t t4; /* x29 - t4 - temporary register 4 */ rt_ubase_t t5; /* x30 - t5 - temporary register 5 */ rt_ubase_t t6; /* x31 - t6 - temporary register 6 */ #ifdef ARCH_RISCV_FPU rv_floatreg_t f0; /* f0 */ rv_floatreg_t f1; /* f1 */ rv_floatreg_t f2; /* f2 */ rv_floatreg_t f3; /* f3 */ rv_floatreg_t f4; /* f4 */ rv_floatreg_t f5; /* f5 */ rv_floatreg_t f6; /* f6 */ rv_floatreg_t f7; /* f7 */ rv_floatreg_t f8; /* f8 */ rv_floatreg_t f9; /* f9 */ rv_floatreg_t f10; /* f10 */ rv_floatreg_t f11; /* f11 */ rv_floatreg_t f12; /* f12 */ rv_floatreg_t f13; /* f13 */ rv_floatreg_t f14; /* f14 */ rv_floatreg_t f15; /* f15 */ rv_floatreg_t f16; /* f16 */ rv_floatreg_t f17; /* f17 */ rv_floatreg_t f18; /* f18 */ rv_floatreg_t f19; /* f19 */ rv_floatreg_t f20; /* f20 */ rv_floatreg_t f21; /* f21 */ rv_floatreg_t f22; /* f22 */ rv_floatreg_t f23; /* f23 */ rv_floatreg_t f24; /* f24 */ rv_floatreg_t f25; /* f25 */ rv_floatreg_t f26; /* f26 */ rv_floatreg_t f27; /* f27 */ rv_floatreg_t f28; /* f28 */ rv_floatreg_t f29; /* f29 */ rv_floatreg_t f30; /* f30 */ rv_floatreg_t f31; /* f31 */ #endif }; ``` ### 2.1.2 rt_hw_stack_init ![image-20220409095324830.png](https://oss-club.rt-thread.org/uploads/20220410/bbece2cdceccca7d6792811808fdb8f6.png.webp) 这部分不是很复杂,但是GD32VF103V-EVAL初始化mstatus寄存器为0x7880, CH32V103R-EVT则为0x1880,看下《riscv-privileged-20211203.pdf》里mstatus定义: ![image-20220409100115946.png](https://oss-club.rt-thread.org/uploads/20220410/d934e5b785339262f40f12afe29c2633.png.webp) 其他域请自行查看文档,区别所在的FS域 ![image-20220409104454568.png](https://oss-club.rt-thread.org/uploads/20220410/76228bf59859f535dd0a5463fa72e776.png.webp) > FS域其实很像Cortex-M4 CONTROL寄存器的FPCA域,后面我们还会再讨论 首先GD32VF103V和CH32V103使用的均是RV32IMAC指令集,不支持F,D浮点指令集。那么: - FS应该设置为默认0,关闭浮点单元 - 本身不存在浮点单元,还写成0x11(Dirty),有点过了 - 参考Nuclei_SDK rt-thread 里的做法 ``` #define portINITIAL_MSTATUS ( MSTATUS_MPP | MSTATUS_MPIE | MSTATUS_FS_INITIAL) ``` 宏MSTATUS_MPP , MSTATUS_MPIE , MSTATUS_FS_INITIAL分别为0x00001800,0x00000080,0x00002000,即 ``` #define portINITIAL_MSTATUS 0x00002880 ``` FS初始化为 Initial ,这种写法倒可以理解,为了兼容以后支持浮点单元的芯片。 **所以第一局, GD32VF103V-EVAL明显错误,输了。** ### 2.1.3 软件中断 ![image-20220409142743357.png](https://oss-club.rt-thread.org/uploads/20220410/8b61167954a68e9cfbf59583c30f9e68.png.webp) 这个部分相当于ARM里常见的PendSV中断, CH32V103R实现了一个Software_IRQ来扮演这个角色 GD32VF103中断系统本身比 CH32V103R复杂很,支持向量中断和非向量中断模式( CH32V103R仅支持向量中断) 在Nuclei_SDK rt-thread Demo里使用了两个中断: - SysTimer as Non-Vector Interrupt - SysTimerSW as Vector Interrupt ``` void vPortSetupTimerInterrupt(void) { uint64_t ticks = SYSTICK_TICK_CONST; /* Make SWI and SysTick the lowest priority interrupts. */ /* Stop and clear the SysTimer. SysTimer as Non-Vector Interrupt */ SysTick_Config(ticks); ECLIC_DisableIRQ(SysTimer_IRQn); ECLIC_SetLevelIRQ(SysTimer_IRQn, configKERNEL_INTERRUPT_PRIORITY); ECLIC_SetShvIRQ(SysTimer_IRQn, ECLIC_NON_VECTOR_INTERRUPT); ECLIC_EnableIRQ(SysTimer_IRQn); /* Set SWI interrupt level to lowest level/priority, SysTimerSW as Vector Interrupt */ ECLIC_SetShvIRQ(SysTimerSW_IRQn, ECLIC_VECTOR_INTERRUPT); ECLIC_SetLevelIRQ(SysTimerSW_IRQn, configKERNEL_INTERRUPT_PRIORITY); ECLIC_EnableIRQ(SysTimerSW_IRQn); } ``` 同时也基于SysTimerSW实现了 `portYIELD` ``` /* Scheduler utilities. */ #define portYIELD() \ { \ /* Set a software interrupt(SWI) request to request a context switch. */ \ SysTimer_SetSWIRQ(); \ /* Barriers are normally not required but do ensure the code is completely \ within the specified behaviour for the architecture. */ \ __RWMB(); \ } ``` 但是GD32VF103V-EVAL并没有参考Nuclei SDK的做法,只有SysTimer as Non-Vector Interrupt 而没有SysTimerSW as Vector Interrupt,就像ARM里只使用了Systick ,而未实现PendSV中断。 | Implementation | 系统中断 | 切换中断 | | ------------------------- | ------------ | -------------- | | ARM | Systick_IRQ | PendSV_IRQ | | Nuclei_SDK rt-thread Demo | SysTimer_IRQ | SysTimerSW_IRQ | | GD32VF103V-EVAL libcpu | SysTimer_IRQ | 无 | | CH32V103R-EVT libcpu | SysTicK_IRQ | Software_IRQ | 当然,这种做法确实可以让系统跑起来,先不做优劣评论,后面章节我们再讲。 ### 2.1.4 开关中断 这部分本质差不多,不过GD32VF103V-EVAL把这部分放到了context_gcc.s ![image-20220409151624479.png](https://oss-club.rt-thread.org/uploads/20220410/aee222c179d3e8799e8dad2b78714589.png.webp) 都是通过操作CSR mstatus寄存器 的MIE 域完成 ![image-20220410190003535.png](https://oss-club.rt-thread.org/uploads/20220410/e4f81f9bec8423f93ae5d6b4c910df71.png.webp) ## 2.2 Context_gcc.S 这部分具体的一些汇编我就不详细讲了,不懂的可以看下参考资料部分,或者论坛上一份小伙伴写的注释 https://github.com/chenyingchun0312/rt-thread/tree/riscv/libcpu/risc-v/common ### 2.2.1 rt_hw_context_swtich_to 该函数用于启动第一个任务 ![image-20220409152047989.png](https://oss-club.rt-thread.org/uploads/20220410/ab62353902d5d6113e6dd0e48a989c46.png.webp) 唯一不同的是右边CH32V103R-EVT先把栈顶复制给了mscratch寄存器。_eusrstack在链接文件中定义 ![image-20220409153118810.png](https://oss-club.rt-thread.org/uploads/20220410/dd65b0a228e7227b44058e895a8a6177.png.webp) 习惯性看下Nuclei_SDK rt-thread Demo的做法,也把_sp栈顶赋值给了CSR寄存器 MSCRATCH ``` 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) ... addi sp, sp, portCONTEXT_SIZE mret ``` 这种做法其实就是把主栈的栈顶先备份在mscratch里,然后在中断里使用,而mscratch本身就是为了在机器模式下快速保存和恢复某个通用寄存器(这里是SP)。 这种做法充分体现了mscratch价值。 ![image-20220409155453407.png](https://oss-club.rt-thread.org/uploads/20220410/db23dbb808f0a0d4985d00e4daa225f1.png.webp) 在具体的中断处理里,可以看出GD32VF103V-EVAL没有使用mscratch这个特殊寄存器,每次都需要先把当前sp保存到临时寄存器s0里。 > - CH32V103R-EVT每次中断使用的是默认栈顶-512,作为中断SP > > - Nuclei_SDK rt-thread Demo每次中断使用的就是默认栈顶作为中断SP > > 两者都是粗暴,因为OS跑起来后,主栈也就是在中断里使用一下,每次都可以设置成同一个地址,size也可以不设那么大。 **这一局,还是GD32VF103V-EVAL输** ### 2.2.2 rt_hw_context_switch_exit ![image-20220409155925752.png](https://oss-club.rt-thread.org/uploads/20220410/7f7edcbc744ea7fd8257f945acf23b46.png.webp) 1. 恢复mepc 2. 恢复x1(LR) 3. 设置mstatus,这里还是0x1800比较合适 4. 下面就是正常寄存器的恢复 ![image-20220409165604984.png](https://oss-club.rt-thread.org/uploads/20220410/e69034378e5bc187e9eaa1554e9336e1.png.webp) > 使用mre指令推出异常的机制如下: > > - 跳转到mpec寄存器指定的PC地址 > - mstatus域被更新: > - 机器模式 MPP永远为11 > - MPIE -> MIE 这里是关闭全局中断的 **这一局除了 mstatus老问题,算是平手** 这里顺便说一个问题,论坛的那位小伙伴在注释rt_hw_context_switch_exit时,疑问在rt_hw_context_switch里把x1(ra)同时保存到了任务栈的 mepc和 ra的位置, 执行mret,pc值会被赋值位新栈中的mepc,那么为什么还要恢复x1(ra)? ![image-20220410175719079.png](https://oss-club.rt-thread.org/uploads/20220410/3e25499d3b1e56633f82ec299428e8be.png.webp) 1. 首先把x1(ra)同时保存到了任务栈的 mepc和lr的位置( mepc = ra),这个只在主动调度时才成立(软中断触发模式下也不成立)。 主动调度时,下一次要恢复的mepc 其实就等于刚进入rt_hw_context_switch函数时LR的值。 2. OS调度可以发生在一个任务执行的任意时刻,一般情况下mepc != ra,这个时候执行rt_hw_context_switch_exit,就需要分别恢复mepc 和 ra 以保证上下文环境的一致。 > 关于 主动调度 和 OS调度请参考 2.3.3 rtthread任务切换情景 ### 2.2.3 rt_hw_context_switch #### 2.2.3.1 浮点寄存器组的保存 ![image-20220409172157241.png](https://oss-club.rt-thread.org/uploads/20220410/c35b0cf433224e0fce23559770d37dbe.png.webp) 如果支持浮点单元,上来就先保存32个浮点寄存器。关于这点我一直有些疑问,为什么不能像ARM那样根据当前程序状态来决定是否保存浮点寄存器组,如下面PendSV: ![image-20220409172852155.png](https://oss-club.rt-thread.org/uploads/20220410/c4bfe7f3ad2efba7e4ffaf4ebac8c123.png.webp) 在2.1.2节我们讲过mstatus FS域 ![image-20220409174652019.png](https://oss-club.rt-thread.org/uploads/20220410/76228bf59859f535dd0a5463fa72e776.png.webp) 感觉这个域上就可以作为保存上下文的依据,仔细看一下《riscv-spec-20191213.pdf》文档,果然有描述。 按照下表的描述,我们完全可以根据 FS是否是Dirty状态,来决定是否保存寄存器组。这样只在需要的时候,保存额外的32的浮点寄存器,平时可以省下很多时间,提高系统切换效率。 ![image-20220409175002098.png](https://oss-club.rt-thread.org/uploads/20220410/cc86bf8eab8fa44a665b6def5976e232.png.webp) 我查看了主流的Freertos,Threadx,TencentOS-tiny,AliOS-Things均没有这方面的实现,在写这篇文章的时候突然想起来还有一个Zephyr,github翻了一下,惊喜来临https://github.com/zephyrproject-rtos/zephyr/blob/main/arch/riscv/core/switch.S save: ``` #if defined(CONFIG_FPU) && defined(CONFIG_FPU_SHARING) /* Assess whether floating-point registers need to be saved. */ lb t0, _thread_offset_to_user_options(a1) andi t0, t0, K_FP_REGS beqz t0, skip_store_fp_callee_saved frcsr t0 sw t0, _thread_offset_to_fcsr(a1) DO_FP_CALLEE_SAVED(fsr, a1) skip_store_fp_callee_saved: #endif /* CONFIG_FPU && CONFIG_FPU_SHARING */ ``` load: ``` #if defined(CONFIG_FPU) && defined(CONFIG_FPU_SHARING) /* Determine if we need to restore floating-point registers. */ lb t0, _thread_offset_to_user_options(a0) li t1, MSTATUS_FS_INIT andi t0, t0, K_FP_REGS beqz t0, no_fp /* Enable floating point access */ csrs mstatus, t1 /* Restore FP regs */ lw t1, _thread_offset_to_fcsr(a0) fscsr t1 DO_FP_CALLEE_SAVED(flr, a0) j 1f no_fp: /* Disable floating point access */ csrc mstatus, t1 1: #endif /* CONFIG_FPU && CONFIG_FPU_SHARING */ ``` 大致看了一下,还没完全搞懂,感觉很有参考意义, 手头上没有带FPU的板子,后面准备入手一个CH32V307试一下。 #### 2.2.3.2 上文保存 ![image-20220409192554135.png](https://oss-club.rt-thread.org/uploads/20220410/a8bd7b362cc14f157c501b73988152c9.png.webp) #### 2.2.3.3 下文切换 ![image-20220409214745426.png](https://oss-club.rt-thread.org/uploads/20220410/1f33a5d7dc51897e957f1976e1f1bbd2.png.webp) #### 2.2.3.4 对比优化 如果看过ARM的rt_hw_context_switch函数,你会发现这部分很别扭。正常的切换不应该在PendSV或者软件中断里完成吗? 怎么变成在一个汇编函数直接切换了?看下Nuclei_SDK rt-thread Demo的做法: ![image-20220409215937781.png](https://oss-club.rt-thread.org/uploads/20220410/1e11cb28685460a3a9759c37c95a2f29.png.webp) 熟悉的味道。最后调用了portYIELD,在2.1.3中可知这个函数最后触发一个软件中断 ![image-20220409220403782.png](https://oss-club.rt-thread.org/uploads/20220410/a406faabf90b044e51d070a250c3d914.png.webp) 这样做的好处,我们在下一章基于rt_scheduler函数再说明。 ## 2.3 Interrrupt.s 这一章,会解开我们很多疑惑。 ### 2.3.1 大差异 ![image-20220409221437744.png](https://oss-club.rt-thread.org/uploads/20220410/b1d1bd8c90cf28e49238cbbad3f73b95.png.webp) - 左边GD32VF103V-EVAL只有一个非向量中断,并且在统一的非向量中断里做上下文切换 - 右边CH32V103R-EVT本身没有非向量中断,只有一个软件向量中断,也在里面做了上下文切换 ### 2.3.2 向量中断和非向量中断 > Note: 下面插图和说明均来自于《深入理解RISC-V 程序开发》林金龙,何小庆编一书配套的培训课件 向量表,这个和ARM处理器很像,存放不同中断源的服务程序入口地址。 ![image-20220410070338936.png](https://oss-club.rt-thread.org/uploads/20220410/07846553e3a0a9e01fb8c75a313dfbc7.png.webp) **向量中断**: - 中断响应时直接通过向量表跳转到中断服务程序,没有保存上下文 - 如果ISR中调用了其他函数,需要使用__attribute__((interrupt)),编译器自动插入保存和恢复寄存器的代码,但会增加额外的开销 - 进入中断模式后mstatus.MIE=0,关闭了全局中断,默认不支持中断嵌套 ,可以手动使能 mstatus.MIE,支持中断嵌套 ![image-20220410064707257.png](https://oss-club.rt-thread.org/uploads/20220410/60be89c320a7921c667e36b3a554d79a.png.webp) **非向量中断(GD32VF103 Bumblebee 内核 特有):** Nuclei 实现一个特殊的指令 ``` /* 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 ``` - 判断是否有中断在等待,有则跳转到中断服务程序,无则等同nop操作 - 开启中断全局使能(mstatus.MIE) ,支持中断嵌套 - 将链接寄存器的值更新为该指令的PC,从ISR返回时再次执行该指令 ![image-20220410064932666.png](https://oss-club.rt-thread.org/uploads/20220410/5d37a18d7299604c01162326f6232859.png.webp) 标准的RISC-V 只规定了向量中断,Nuclei 自己新增了非向量中断 所以GD32V103才有两中不同的中断处理模式,另外 - CH32V103内置快速可编程中断控制器 PFIC (Programmable Fast Interrupt Controller )中断控制器,**需要关闭硬件自动压栈** - GD32VF103内置改进型内核中断控制器 ECLIC(Enhanced Core Local Interrupt Controller,ECLIC )中断控制器 **补充一下之前的一个表格** | implementation | 系统中断 | 切换中断 | | ------------------------- | ---------------------------------------- | ---------------------------------- | | ARM | Systick_IRQ(向量,自动压栈) | PendSV_IRQ(向量,自动压栈) | | Nuclei_SDK rt-thread Demo | SysTimer_IRQ(非向量,公共入口自动压栈) | SysTimerSW_IRQ(向量,手动压栈) | | GD32VF103V-EVAL libcpu | SysTimer_IRQ(非向量,公共入口自动压栈) | 无 | | CH32V103R-EVT libcpu | SysTicK_IRQ(向量,ISR自动压栈) | Software_IRQ(向量,ISR自动压栈) | ### 2.3.3 rtthread任务切换情景 首先要知道rtthread任务在什么时候调度 - 主动调度(正常函数中): 任务自身delay让出CPU;或者在获取某些资源对象时,设置了等待时间让出CPU; 资源对象就绪后唤起等待它的任务,这些都会尝试一次调度 - OS调度(中断函数中):在systick 中断里发现延时到了; 等待对象超时;或者时间片到了,更新任务状态后,OS会主动切尝试一次任务调度 看下rt_schedule的切换代码, 在rt_schedule里,上来就关了全局中断,所以rt_hw_context_switch(主动调度)期间任何中断也不能响应 ``` void rt_schedule(void) { rt_base_t level; struct rt_thread *to_thread; struct rt_thread *from_thread; /* disable interrupt */ level = rt_hw_interrupt_disable(); .... if (rt_interrupt_nest == 0) { extern void rt_thread_handle_sig(rt_bool_t clean_state); RT_OBJECT_HOOK_CALL(rt_scheduler_switch_hook, (from_thread)); rt_hw_context_switch((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp); /* enable interrupt */ rt_hw_interrupt_enable(level); #ifdef RT_USING_SIGNALS /* check stat of thread for signal */ level = rt_hw_interrupt_disable(); if (rt_current_thread->stat & RT_THREAD_STAT_SIGNAL_PENDING) { extern void rt_thread_handle_sig(rt_bool_t clean_state); rt_current_thread->stat &= ~RT_THREAD_STAT_SIGNAL_PENDING; rt_hw_interrupt_enable(level); /* check signal status */ rt_thread_handle_sig(RT_TRUE); } else { rt_hw_interrupt_enable(level); } #endif /* RT_USING_SIGNALS */ goto __exit; } else { RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n")); rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp); } .... ``` 主要是rt_hw_context_switch和rt_hw_context_switch_interrupt两个函数,为方便说明,再上一张对照表 | Scheduler | rt_hw_context_switch(主动调度) | rt_hw_context_switch_interrupt(OS调度) | | ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | ARM | 触发PenSV中断,延迟切换 | 触发PenSV中断,延迟切换(rt_hw_context_switch的别名) | | Nuclei_SDK rt-thread Demo | 触发SysTimerSW_IRQ,延迟切换(调用rt_hw_context_switch_interrupt) | 触发SysTimerSW_IRQ,延迟切换 | | GD32VF103V-EVAL libcpu | 汇编函数,直接内部切切换 | SysTimer_IRQ设置 thread_to, thread_from 然后公共非向量irq_entry里切换 | | CH32V103R-EVT libcpu | 汇编函数,直接内部切切换 | 触发Software_IRQ,延迟切换 | ARM 和 Nuclei_SDK为了系统的实时性并没有直接在代码或者systick 中断里切换任务,均采用软件触发一个最低优先级的中断来执行。 我们看的这两个risc-v BSP: - rt_hw_context_switch(主动调度): GD32VF103V-EVAL libcpu 和 CH32V103R-EVT libcpu 现在的做法都不算太好,本可以使用软中断延迟处理简化的。 - rt_hw_context_switch_interrupt(OS 调度) : CH32V103R-EVT libcpu没啥问题 ,但是GD32VF103V-EVAL libcpu的做法就很显得很乱。 ### 2.3.4 rt_hw_context_switch_interrupt > Note: 下面主要针对GD32VF103V-EVAL BSP #### 2.3.4.1 OS 调度流程 rt_hw_context_switch_interrupt 这个函数初衷应该是为了在中断中使用的. 在ARM和Nuclei_SDK rt-thread Demo中使用了软中断的情况下,这个函数和rt_hw_context_switch基本没有区别,有时就是同一个函数,或者是调用关系。现在GD32VF103V-EVAL libcpu又让他恢复了原来的使命。 ``` void rt_hw_context_switch_interrupt(rt_ubase_t from, rt_ubase_t to) { if (rt_thread_switch_interrupt_flag == 0) rt_interrupt_from_thread = from; rt_interrupt_to_thread = to; rt_thread_switch_interrupt_flag = 1; return ; } ``` 一般中断不会触发任务调度(资源对象的获取都是非阻塞的方式,也不推荐中断里使用rt_thread_mdelay)但是Systick_ISR(GD32VF103使用eclic_mtip_handler) 是个例外 ```c /* System Tick Configuration */ static void systick_config(rt_uint32_t ticks) { /* set value */ *(rt_uint64_t *) (TMR_CTRL_ADDR + TMR_MTIMECMP) = ticks; /* enable interrupt */ eclic_irq_enable(CLIC_INT_TMR, 0, 0); /* clear value */ *(rt_uint64_t *) (TMR_CTRL_ADDR + TMR_MTIME) = 0; } /* This is the timer interrupt service routine. */ void eclic_mtip_handler(void) { /* clear value */ *(rt_uint64_t *) (TMR_CTRL_ADDR + TMR_MTIME) = 0; /* enter interrupt */ rt_interrupt_enter(); /* tick increase */ rt_tick_increase(); /* leave interrupt */ rt_interrupt_leave(); } ``` 在rt_tick_increase里,做了三件事 1. 更新rt_tick 2. 检查时间片,用完跟新状态和列表,然后调用rt_schedule 3. 检查是否超时,超时更新状态和列表,然后调用rt_schedule ``` void rt_tick_increase(void) { struct rt_thread *thread; rt_base_t level; RT_OBJECT_HOOK_CALL(rt_tick_hook, ()); level = rt_hw_interrupt_disable(); /* increase the global tick */ #ifdef RT_USING_SMP rt_cpu_self()->tick ++; #else ++ rt_tick; #endif /* RT_USING_SMP */ /* check time slice */ thread = rt_thread_self(); -- thread->remaining_tick; if (thread->remaining_tick == 0) { /* change to initialized tick */ thread->remaining_tick = thread->init_tick; thread->stat |= RT_THREAD_STAT_YIELD; rt_hw_interrupt_enable(level); rt_schedule(); } else { rt_hw_interrupt_enable(level); } /* check timer */ rt_timer_check(); } ``` 这就是OS维护的任务调度,且发生在中断里。 现在GD32VF103V-EVAL OS调度流程如下: 1. mtip_irq 发生 2. 进入公共的非向量入口地址irq_entry 3. 保存上文,保存必要的mstatus 4. csrrw ra, CSR_JALMNXTI(0x07ED), ra 特殊指令进入具体的中断函数eclic_mtip_handler 5. 如果需要调度,在eclic_mtip_handler通过rt_hw_context_switch_interrupt里更新全局变量rt_interrupt_from_thread,rt_interrupt_to_thread 并置位rt_thread_switch_interrupt_flag 6. 根据rt_thread_switch_interrupt_flag决定是备份mepc,sp,并更新mpec,sp 7. 恢复 下文和必要的mstatus 8. mret 退出irq_entry中断 ![image-20220410171019922.png](https://oss-club.rt-thread.org/uploads/20220410/b176ed58aba61c6b61368ef1df2c8f71.png.webp) #### 2.3.4.2 问题 - **公平性** rq_entry是大家共同的非向量入口地址, 按照正常的中断流程(参考ARM 和 Nuclei_SDK),进来仅需保存 调用者calller寄存器组和必要的CSR寄存器组,退出时恢复即可。现在为了任务切换上下文环境的完整,必须保存额外的被调用者callee寄存器组。这对于其它的非向量中断是很不公平的,徒增很多不必要的入栈和出栈操作。 ``` /* This label will be set to MTVT2 register */ irq_entry: /* 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 ``` - **效率性** OS心跳一般1ms一次,无论有无任务切换都要入栈和出栈完整的寄存器组和一些CSR寄存器,这是不能接受的,会大大降低系统效率。 # 3 总结 关于现在GD32VF103V-EVAL 和 CH32V103R-EVT BSP中libcpu的问题和可优化项如下 | Scheduler | init mstatus | mscratch | FS | rt_hw_context_switch | rt_hw_context_switch_interrupt | | ---------------------- | ---------------- | -------------------- | -------------------- | -------------------------------------------------- | ------------------------------------------------------------ | | GD32VF103V-EVAL libcpu | 0x7880(**错误**) | 未使用(**可优化**) | 未判别(**可优化**) | 汇编函数, 直接内部切切换(**可改成软中断优化**) | SysTimer_IRQ简单设置 thread_to, thread_from 然后公共非向量irq_entry里切换(**有问题,需要改成软中断里调度**) | | CH32V103R-EVT libcpu | 0x1800 | 使用 | 未判别(**可优化**) | 汇编函数,直接内部切切换(**可改成软中断优化**) | 触发Software_IRQ,延迟切换 | > 虽然都有问题和可优化项, 但CH32V103R-EVT BSP的porting好些。其实 GD32VF103V芯片本身比CH32V103R更具优越性,只是BSP没做好,这点可以参考Nuclei_SDK rt-thread Demo **关于问题和改进,后面会慢慢提交,有兴趣的小伙伴可以一起啊!** # 参考资料: - 《RISC-V CPU上处理器设计》胡振波主编 - 《RISC-V CPU下工程与实践》胡振波主编 - 《深入理解RISC-V 程序开发》林金龙,何小庆编 - 《RISC-V架构嵌入式原理与应用》裴晓芳主编 - 《The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors 》Third edition - 《riscv-spec-20191213.pdf》 - 《riscv-privileged-20211203.pdf》 - 《CH32xRM.PDF》v1.23 - 《Bumblebee处理器内核指令架构手册.pdf》
4
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
blta
这家伙很懒,什么也没写!
文章
12
回答
9
被采纳
2
关注TA
发私信
相关文章
1
RT-Thread 在ARM926 EJSA 内核的移植
2
裸机工程移植 RT-Thread
3
Keil MDK 移植 RT-Thread Nano
4
移植 Nano,rt_thread_mdelay()延迟时间不对
5
裸机工程移植 RT-Thread内核
6
跳转不进去main函数
7
我从KEIL 移植过来的 time外部中断定时器是失败的
8
Ambiq Apollo3 移植链接脚本问题
9
可以把 RT-Thread Studio 创建的项目移植到 Keil上面吗?
10
RT_USING_COMPONENTS_INIT 这部分相关的有没有更详细的介绍
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
编译报错
Debug
SFUD
msh
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
出出啊
1517
个答案
342
次被采纳
小小李sunny
1443
个答案
289
次被采纳
张世争
805
个答案
174
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
4
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
1
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部