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 请问您的这份代码如何编译呢?
-
Any update on the source code? I have downloaded d1_freertos_20211105_4.rar.
I have done an independent version, pretty much came to the same results as here. All tick timer stuff works fine using CSR time and MMIO MTIMECMP. The comments about 'stability' bother me, mine runs for hours and hours doing simple stuff.
While working on my drivers for UART, TMR, SPI, etc. I always start with POLLING, then do INTERRUPT, then do DMA (where needed). All my polling stuff works fine. I can't get ANY of my interrupt stuff to work. My main problem is that I can't get ANY of the Peripherals to trigger the PLIC_IP registers to even initiate an External Interrupt to the CLINT. I've checked enables, priorities etc. Think I have them all set correctly.
I downloaded this version because it looked like it had some interrupt stuff in it. After searching through all the source code with a fine tooth comb I can only find the 'potential' for the interrupt, never a call from driver or application code to enable/use it.
Has anyone done any external interrupts (TMR or UART) in a FREERTOS Bare Metal environment?
Is there some secret mux or clock enable to get the TMR peripheral to connect to the PLIC to connect to MEI of CLINT? OpenSBI does enable the RISCV_CFG_BGR registers and I had hopes that would be the missing link, but it didn't fix it. MXSTATUS for Supervisor Delegation etc. isn't needed since FREERTOS runs in Machine mode. Any other suggestions?
Of course Linux/Tina must use interrupts but I've searched for hours in source code and can't find anything. Could be buried deep in the DTS.
-Ed
Copyright © 2023 深圳全志在线有限公司 粤ICP备2021084185号 粤公网安备44030502007680号