FreeRTOS 10.4.3在RISCV(T-HEAD C906)平台上移植过程
-
好记性不如烂笔头,记录点滴移植经历,一方面便于总结提炼,二是分享,让别人少走有些弯路。
首先谈一下对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产生同样的输出,因该是个可行的办法.
-
-
跟紧大佬的脚步,晚点我也试一试。
-
This post is deleted! -
@caozilong 你好,在FreeRTOSConfig.h中,需要配置configMTIME_BASE_ADDRESS和configMTIMECMP_BASE_ADDRESS,但是C906没有mtime寄存器啊,该怎么设置呢?
-
@marleo 在 FreeRTOS 10.4.3在RISCV(T-HEAD C906)平台上移植过程 中说:
mtime
你好,mtime是m-mode arch register,是属于RISCV 架构SPEC定义的内容,C906一定是有的。
只不过,mtime寄存器在实现的时候,vendor可以选择两种方式去实现,第一种是访问寄存器的方式隐藏在指令中,不暴露寄存器出来,比如通过rdtime,rdcycle指令等等,第二种是所谓的MMIO方式,将mtime映射为一篇存储区,直接给地址访问。
据我了解,C906是第二种方式实现的,第一种指令也支持,只不过支持方式是陷入SBI模拟去模拟实现的,本质上也是直接读取的MMIO内存。
这篇移植版本由于整个FREERTOS系统运行在M模式下,有权限直接读取MMIO地址获取timer,上面的流程图也有画这部分。
想要看细节,要看SBI的代码了,可以grep 关键字 rdtime,rdcycle或者ilegal systemcall等等,就会找到线索。 -
@caozilong 小白刚学习移植FreeRTOS,具体应该怎么得知Mtime基地址呢,可以方便直接说一下吗.
-
@theone
C906手册里面有喔 -
@dreamer
只有mtime比较寄存器地址,没有mtime寄存器啊
-
@marleo 偏移BFFF8位置处就是,你看的这份文档应该是没有更新,可以和源码交叉对照一起看。
-
@caozilong 大哥你这水平,月薪多少啊
-
@caozilong
我看的是这个版本的,去平头哥官网看了下,目前还是这个版本的,请问您能分享下更新后的版本吗?
-
原因是这样的:
首先说结论,MTIME 一定是有的,只是vendor在设计的时候,微架构上的实现不同。
业内在RISCV架构的中断部分实现中有两个标准,一个叫CLIC,另一个是PLIC,CLIC访问MTIME的方式就是前面说的,通过 ”clinc+0xbff8"寻址mtime,也就是”基地址+偏移“的形式,这种形式比较普遍。
而C906不是完全按照CLIC的标准定义这一块的,它使用了CLIC+PLIC,并没有把MTIME的地址给映射出来,而是通过rdtime 伪指令,这条指令编译的时候会扩展为csr指令读取 C906的扩展寄存器,而这个扩展寄存器就是和mtime绑定的。
所以C906上的做法可能不是完全符合CLIC的标准设计,如果你使用TH的E906,可能就是另外一个样子,E906上,中断控制器是完全按照CLIC设计的,可以用读地址的方式读取MTIME的值。
这里不会对你的有太多妨碍的,时钟配对了,上电MTME就会起来。 -
@caozilong 您好,对于第一种方式,我通过rdtime指令是可以读取mtime寄存器的值,对于第二种MMIO方式,直接去访问其地址,在freertos的vPortSetupTimerInterrupt()函数中,有对mtime和cmp进行操作,这里我设置的地址是mtime=0x1400BFF8,cmp=0x14004000,但是当芯片上电运行后,会陷入“Load access fault”异常,通过读取mtval寄存器里的值,发现其等于0x1400BFF8,所以,mtime这里该怎么设置呢?还有您的移植过程的第五点,CSR_PLIC_BASE指的是PLIC的基地址吧?谢谢。
-
@marleo MMIO方式是行不通的,根据向Vendor的了解,C906并没有将Mtime映射出来,你这样直接访问这个地址肯定是会出异常的。
C906只支持1种方式,rdtime获取时钟数. -
@caozilong 那vPortSetupTimerInterrupt()函数是需要改写吧?
-
@march 是的,要定制,它属于架构相关的结构,所以才叫"vPortxxxxx"
-
@caozilong xPortStartScheduler, pxPortInitialiseStack, vPortSetupTimerInterrupt, xPortStartFirstTask,这四个函数都需要定制,是吧?还有其他的嘛?
-
@march 有些忘记了,图中画的是当初遇到的, 同样的事情,你遇到的情况和我遇到的情况可能是不同的。建议看完后把它忘掉,自己淌一遍,把每个问题当成新问题去思考解决,博客只是参考,不要依赖。
-
@caozilong 您好,我只修改了vPortSetupTimerInterrupt函数,创建了三个简单的线程,刚开始时,线程能正常调度运行,但是运行177s后,线程便卡死,且不管创建几个线程,都是177s后卡死,请问可能的原因是什么呢?另外能分享下需要定制的函数源码吗?
-
@marleo 在 FreeRTOS 10.4.3在RISCV(T-HEAD C906)平台上移植过程 中说:
听起来像是某个变量固定在某个时间溢出了,和计时器相关的逻辑要注意下。而且你的调度器都已经正常工作了,适配的大方向应该都是对的了,可能细节方面要留意一些。
后面可能需要对mtval, mcause,以及RISCV的异常处理流程多多了解一下,类似的问题绝对不会只有这一条,裸机开发最困扰人的就是类似的死机问题,需要对处理器架构相关的细节多多了解。
刚刚找了一下,由于没有在产品中使用freeRTOS(用的rt-thread), 之前移植的成果找不到了。我的代码没有,你可以放码上来,我们可以针对具体的问题进行讨论。 -
@caozilong 您好,具体的移植过程以及源码: D1_FreeRTOS.rar
https://whycan.com/t_7299.html#p69778
里面用了很多printf打桩调试,请忽略。谢谢。 -
-
@caozilong 请问有找到了嘛
非常感谢
-
@marleo ft.tar.bz2
仅供参考。 -
@caozilong 您好,参考了您的代码,在system_tick_init()函数中调用了get_cycles_hi()和get_cycles()函数来获取mtime的值,我在移植到哪吒开发板上,会出现”illegal instruction“异常,于是分别改写了system_tick_init和riscv_timer_interrupt函数,其余和arch以及freertos配置相关的基本和您的代码保持一致,但是现象还是运行一定时间就会卡死,求大佬hlep。D1_FreeRTOS_20211105_4.rar
void system_tick_init(void) { csr_clear(mie, MIE_MTIE | MIE_MSIE); write32(CLINT + 0x4000, counter() + delta); write32(CLINT + 0x4004, 0); csr_set(mie, MIE_MTIE); } void riscv_timer_interrupt(void) { csr_clear(mie, MIE_MTIE); *(uint64_t*)CLINT_MTIMECMPL(0) = counter() + delta; csr_set(mie, MIE_MTIE); } static inline uint64_t counter(void) { uint64_t cnt; __asm__ __volatile__("csrr %0, time\n" : "=r"(cnt) :: "memory"); return cnt; }
-
-
@marleo 请问解决死机问题了吗
-
@caozilong 请问您的这份代码如何编译呢?
Copyright © 2021 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号