好记性不如烂笔头,记录点滴移植经历,一方面便于总结提炼,二是分享,让别人少走有些弯路。
首先谈一下对FreeRTOS的印象,FreeRTOS还是非常简单的, 要比RT-Thread,Nuttx,SlixOS等偏重型的系统轻量不少,所以FreeRTOS一般不会用在带MMU的重型方案上,而是比较多的应用在如工业控制或者家电等嵌入式场合,在支撑中大规模方案的能力上,FreeRTOS相对比较弱。
下面开始技术流水账:
整体的移植框架如下:
下面说下具体的移植步骤:
-
搭建构建环境,准备利用melis上已经建好的环境,借”鸡“生”蛋".目标是移植FreeRTOS 系统,所以这部分不重点记录,总之,环境已建好。
-
用不用sbi? 暂时先用,后续优化掉,因为与Melis相比,FreeRTOS构建的系统相对简单很多,不用支持MMU,整个FreeRTOS系统加上应用都可以运行在M模式,sbi也就失去的存在的必要,但是还是决定先保留着,移植完成后再优化掉。主要考虑三点,1.环境和sbi之间有依赖,包括链接脚本,一些汇编等等,不想这个时候动它,2.SBI本身提供了一些vendor的扩展接口,这些东西暂时还不确定将来会不会用到,如果省去,万一需要还得以某种形式加回来,麻烦。所以暂时先用SBI启动,后续逐渐做减法。3.利用sbi中已经封装好的串口驱动,最小系统启动。用SBI启动,需要都SBI固件做一些小小的修改,改动如下:
diff --git a/firmware/fw_jump.S b/firmware/fw_jump.S
index afbcec0..79b3de8 100644
--- a/firmware/fw_jump.S
+++ b/firmware/fw_jump.S
@@ -84,7 +84,7 @@ fw_next_addr:
* The next address should be returned in 'a0'
*/
fw_next_mode:
- li a0, PRV_S
+ li a0, PRV_M
ret
修改fw_next_addr 的实现,使用返回的next状态为PRV_M,也就是下阶段是M-Mode,非Linux and Melis运行的S-Mode.,验证如下:0x40010600是OS _star地址,可以正确从SBI跳出,进入这个地址,表示可以进行接下来的CPU引导了.
-
下图代表当前处理器功能组件的打开状态,可以看到I/D cache以及PMU相关机制已经开启。
mxstatus bit 30 bit 31为 0b11,表示系统运行在machine 模式。
-
开发链接脚本,确定最终链接后ELF文件的Layout分布,不启用MMU,所以整体上VA=PA,如下图:
-
关于启动流程的考量
- 列表所有ISA定义的寄存器都在确定的状态,这里面包括GPRS和FPU.
- 初始化TLB,但M模式不访问页表,这里是为了在S模式中访问内存高地址,因为通过MMU映射后,物理内存可以访问40位,而不仅仅是VPA定义的39位.
- 由于页表建立在TLB中,而非内存中,所以这里是直接初始化TLB,TLB中的页表不能被刷出,因为物理内存没有页表项.(这很容易满足,映射和访问范围小于TLB能映射的容量,TLB条目不会被换出,当然,如果存在TLB lock选项,就更有保证了.)
- 不同于Linux内核对FPU的访问畏首畏尾,为了让FreeRTOS全速奔跑,这里准备全面支持内核FPU单元加速,采用SR_SD来表示FPU寄存器组的状态实现lazy save/restore.(使用-mabi=lp64d or -mabi=lb64v编译,linux内核只能使用-mabi=lp64编译,用户态才有的选择),
初始化bringup 堆栈,填满"ebreak"指令,以防不测. - 传递给内核参数包括启动时间,misa以及sbi的位置,SBI后面会优化掉。
- 工欲善其事,必先利其器,关于打印,进到c之后,最想看到的当然是打印,每个问题都调试汇编效率还是比较低的。这里准备使用newlibc中的printf打印函数,底层对接_write_r到sbi中已经调试好的串口驱动。这中间遇到一个小插曲,在调用printf进行输出的时候,竟然进入了异常 handle_exception里面。 分析发现,原来_write_r是使用的S模式下的SBI封装,调用了ecall指令,这种方式在S模式下当然是可以的,但是问题是,我们准备在M模式下跑FreeRTOS, 所以程序本身就和SBI一样运行于M模式,而M模式的mtvec寄存器已经在上面的流程里面被修改为了新的handle_exception,这里面当然不会处理SBI请求,解决的办法是,将基于"ecall"指令的SBI调用修改为直接对sbi函数的调用。分析过程见下图:
1.强制在handle_exceptions入口停住,打印 mepc
2.发现mepc指向0x4001b65a,反编译elf文件找到对应位置,发现是"ecall"指令:
- 移植SBI中的打印驱动进系统,得到:
- 开发arch timer中断功能,使用clic中的compare and value寄存器来作为时钟心跳源,value被24M驱动,当value和compare 相等时,中断被触发,这个时候需要在中断处理中设置下一个触发点的compare值,增量计算方法是24000000/CONFIG_HZ,即每两次中断之间的周期数。首先参考linux做法,在trap_init中开启Machine Mode的Timer中断和External 中断 的capability.
5.开启clic arch timer,注意Mtime和Compare寄存器类似于MIPS中的tick产生机制,这是SIFive的设计,很多vendor Follow.
6.完整流程为下图,最后一步触发调度器
7:需要定制的接口包括 xPortStartScheduler, pxPortInitialiseStack, vPortSetupTimerInterrupt, xPortStartFirstTask四个接口,注意arch timer中断的挂接。
- 任务调度现场的Context布局
12.实现portasmHANDLE_INTERRUPT, vPortSetupTimerInterrupt定义,用于启用定时器中断和外设中断的处理接口, configCPU_CLOCK_HZ, configTICK_RATE_HZ设置tick中断周期
13:实现异常和中断处理,将上述的定时器中断接口和外设中断接口挂接到异常处理流程中。
14:注意,中断现场的堆栈组织和任务调度现场的堆栈组织是一样的,也和任务初始化的时候堆栈排布一致,由于抢占调度和主动调度都是通过异常路径进入的,这样在freeRTOS里面的处理非常一致,不用区分是中断调度现场还是任务主动出让的调度现场,如下图:
主调度器调度逻辑:
15:移植结果:
总结:FreeRTOS作为一个老牌的RTOS,它的可移植性是非常友好的,在移植之前,梳理好几个重要的执行流,比如启动流,主调度器流以及中断流,然后按照FreeRTOS的运行机理将主要的这几个执行流挂接到平台上,基本就实现了整个移植过程。
关于 ICE, 之前用过不少ICE, 感觉有一个共同点,就是很多高成本一点的ICE仿真器都会内置一个XILINX的FPGA里面,ICE的主要作用是将PC调试指令转换为JTAG时序信号,有很多现成的转换芯片比如FT232,XILINX这里也是起到协议转换的作用,但是FPGA的逻辑断电侯会消失,ICE是如何保证调试逻辑不消失的呢?经过了解,ICE里面一般内置了一个Flash或者EEPROM,用于存放固化的逻辑,类似于FPGA物理验证阶段使用的bitfile,每次上电FPGA都会从EEPROM里面load bitfile设计逻辑。不过有点扯淡的是,虽然CKLink Pro用的XINLINX FPGA作主控,但CKLink Lite却出奇的省,仅仅用了STM32做主控。从价格也可以看出两者的明显不同,前者淘宝1200, 后者只有200,主控一换,1000块出去了。
Jlink 不能用,原因还是因为THEAD用了自家的调试IP,但是应该有一种可行的破解方法,就是抓出cklink协议然后修改OpenOCD,使在同样的GDB命令下,OpenOCD驱动JLink产生同样的输出,因该是个可行的办法.