OPENHW开源CORE-V-MCU移植RT-Thread
Posted RT-Thread物联网操作系统
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OPENHW开源CORE-V-MCU移植RT-Thread相关的知识,希望对你有一定的参考价值。
项目背景
OpenHW Group 是一个以协作方式开发开源硬件和相关软件的非营利组织,致力于开发、验证和提供开源处理器内核。OpenHW Group的开源项目致力于开发和验证基于免费和开放的RISC-V指令集架构 (ISA) 系列内核,称为 CORE-V系列。CV32E40P 开源处理器 IP 内核,这是 OpenHW CORE-V 系列中第一个经过全面验证的内核。本文的对象就是基于CV32E40P实现的开源CORE-V-MCU移植RT-Thread。
CORE-V-MCU目前是以软核的形式在FPGA上进行了实现,中科院 PLCT 实验室针对CV32E40P内核的CORE-V-MCU适配了QEMU,本文最终的验证在QEMU上进行。
相关链接
点击阅读原文获取
https://club.rt-thread.org/ask/article/24d7317f5125b054.html
前期准备
开发环境:ubuntu18.04
验证示例工程
本次实验验证的平台是PLCT提供的QEMU,在Linux下的QEMU可以使用上述的笔者编译好的,也可以使用自己尝试编译PLCT提供的源码。
OPENHW提供了基于FreeRTOS的示例工程,由于使用的是PLCT提供的QEMU,所以IDE中自带的工程并不能直接使用,为了避免不必要的麻烦,本文采用PLCT提供的示例工程。
下载好示例工程,进入到app目录,执行以下命令配置编译工程:
source ../env/core-v-mcu.sh
make RISCV=xxx //xxx为工具链的路径
编译完成后将生成的cli_test可执行文件拷到qemu的安装目录,运行下述命令验证工程;
./qemu-system-riscv32 -M core_v_mcu -bios none -kernel cli_test -nographic -monitor none -serial stdio
运行结果:
出现上述结果,则表示示例工程运行正常,这样我们就有一个可移植的模板工程,后续移植基于该工程开展。
移植RT-Thread
CV32E40P内核是一个RISC-V架构的内核,移植RTOS不可避免的会涉及到汇编部分的修改,所以在移植前期学习一下RISC-V架构与汇编会产生事半功倍的效果,同样在一之前需要熟悉CV32E40P的内核资源。
(https://docs.openhwgroup.org/projects/core-v-mcu/doc-src/overview.html)
cv32e40p继承自pulp开源的RI5CY内核,而RI5CY内核的对接代码在RT-Thread的仓库libcpu/riscv/rv32m1已经实现,所以以RI5CY中的代码为基础,移植cv32e40p的对接代码。
成功移植RT-Thread有如下几个关键的阶段:
节拍定时器正常工作
线程可以正常创建,调度(这步最难,需要前面好多工作来保证)
shell可以正常使用(从这开始就顺利多了)
RTOS的最小时间单位是系统节拍,系统正常工作需要节拍定时器的支持,因而以节拍定时器的移植为切入点展开移植。
core-v-mcu.c中放置的是系统初始化,中断处理相关代码。其中system_init函数完成了时钟初始化,串口,I2C等外设的初始化,以及中断函数的绑定。在文件的开头可以找到如下一个指针数组:
1void (*isr_table[32])(uint32_t);
该数组用于绑定中断入口函数。
1for (int i = 0 ; i < 32 ; i ++)
2 isr_table[i] = undefined_handler;
3 handler_count[i] = 0;
4
5 isr_table[0x7] = timer_irq_handler;
6 isr_table[0xb] = (void(*)(uint32_t))fc_soc_event_handlzer1;
在system_init函数中我们可以找到上述代码,浏览该文件我们可以知道,timer_irq_handler即系统定时器的中断入口函数,
在原有FreeRTOS工程中该函数内容如下:
1void timer_irq_handler(uint32_t mcause)
2
3#warning requires critical section if interrupt nesting is used.
4 if (xTaskIncrementTick() != 0)
5 vTaskSwitchContext();
6
7
该函数实现系统节拍的产生,所以将这里的内容修改为RT-Thread的节拍产生的方式,修改如下:
1void timer_irq_handler(uint32_t mcause)
2
3#warning requires critical section if interrupt nesting is used.
4 rt_interrupt_enter();
5 rt_tick_increase();
6 rt_interrupt_leaves();
7
涉及到中断,我们就得考虑系统的中断的实现方式,中断一般会分为向量中断与非向量中断。RISC-V常在启动后文件中进行中断模式的配置。从crt0.S文件可以找到如下代码:
1/* set vector table address */
2 la a0, __vector_start
3 or a0, a0, 1 /* enable vectored mode (hardcoded anyway for CV32E40P) */
4 csrw mtvec, a0
RISC-V规范定义中断、异常的入口地址,以及模式使用mtvec寄存器配置,上述代码将系统的中断模式配置为了向量模式,向量模式下,每个函数均包含一个独立的入口函数用来处理中断。所以我们切换至写中断向量表的文件vector.S,我们可以很明显的看到一段代码,从名字就可以看出下面是一张向量表 :
1vector_table:
2 j freertos_risc_v_trap_handler // irq0
3 j freertos_risc_v_trap_handler
4 j freertos_risc_v_trap_handler
5 j freertos_risc_v_trap_handler // irq3
6 j freertos_risc_v_trap_handler
7 j freertos_risc_v_trap_handler
8 j freertos_risc_v_trap_handler
9 j freertos_risc_v_trap_handler //ctxt_handler // irq 7 mtime or timer
10 j freertos_risc_v_trap_handler
11 j freertos_risc_v_trap_handler
12 j h7// freertos_risc_v_trap_handler
13 j freertos_risc_v_trap_handler // irq 11 Machine (event Fifo)
14 j freertos_risc_v_trap_handler
15 j freertos_risc_v_trap_handler
16 j freertos_risc_v_trap_handler
17 j freertos_risc_v_trap_handler
18 j freertos_risc_v_trap_handler // IRQ16
19 j freertos_risc_v_trap_handler // IRQ17
20 j freertos_risc_v_trap_handler // IRQ18
21 j freertos_risc_v_trap_handler // IRQ19
22 j freertos_risc_v_trap_handler // IRQ20
23 j freertos_risc_v_trap_handler // IRQ21
24 j freertos_risc_v_trap_handler // IRQ22
25 j freertos_risc_v_trap_handler // IRQ23
26 j freertos_risc_v_trap_handler // IRQ24
27 j freertos_risc_v_trap_handler // IRQ25
28 j freertos_risc_v_trap_handler // IRQ26
29 j freertos_risc_v_trap_handler // IRQ27
30 j freertos_risc_v_trap_handler // IRQ28
31 j freertos_risc_v_trap_handler // IRQ29
32 j freertos_risc_v_trap_handler // IRQ30
33 j freertos_risc_v_trap_handler // IRQ30
PS:上述向量表看的我懵了好久啊,相信对于刚接触底层不久的小伙伴也会有同样的感受吧,向量表不是一张多姿多彩的表吗,怎么感觉都一样啊???不急,我们在看一下core-v-mcu.c这个文件,又会发现一段非常显眼的代码:
1void vSystemIrqHandler(uint32_t mcause)
2
3 uint32_t val = 0;
4// extern void (*isr_table[32])(uint32_t);
5 isr_table[mcause & 0x1f](mcause & 0x1f);
6
结合上文,思考一下大致可以明白这个函数的作用了:分发中断。即所有的异常与中断触发后均会执行这个函数,那我们全局搜索一下这个函数,
找一下是哪里调用了.全局搜索可以找到如下代码:
1CPPFLAGS += -DportasmHANDLE_INTERRUPT=vSystemIrqHandler
继续搜索DportasmHANDLE_INTERRUPT可从portASM.S找到如下代码;
1load_x sp, xISRStackTop /* Switch to ISR stack before function call. */
2jal portasmHANDLE_INTERRUPT
3j processed_source
浏览代码可知,系统触发中断或异常后最终均会执行vSystemIrqHandler函数,该函数是在freertos_risc_v_trap_handler函数中被调用,至此我们大致可以明白整个过程了,当中断或者异常出发后,会查询向量表,执行freertos_risc_v_trap_handler函数,该函数会调用vSystemIrqHandler,经其分发后最终执行到系统初始化时绑定的中断入口函数。
在RT-Thread 中由interrupt_gcc.S中的函数实现vSystemIrqHandler函数的调用,所以我们修改中断向量表的内容如下:
1vector_table:
2 j IRQ_Handler // irq0
3 j IRQ_Handler
4 j IRQ_Handler
5 j IRQ_Handler // irq3
6 j IRQ_Handler
7 j IRQ_Handler
8 j IRQ_Handler
9 j IRQ_Handler //ctxt_handler // irq 7 mtime or timer
10 j IRQ_Handler
11 j IRQ_Handler
12 j IRQ_Handler // IRQ_Handler
13 j IRQ_Handler // irq 11 Machine (event Fifo)
14 j IRQ_Handler
15 j IRQ_Handler
16 j IRQ_Handler
17 j IRQ_Handler
18 j IRQ_Handler // IRQ16
19 j IRQ_Handler // IRQ17
20 j IRQ_Handler // IRQ18
21 j IRQ_Handler // IRQ19
22 j IRQ_Handler // IRQ20
23 j IRQ_Handler // IRQ21
24 j IRQ_Handler // IRQ22
25 j IRQ_Handler // IRQ23
26 j IRQ_Handler // IRQ24
27 j IRQ_Handler // IRQ25
28 j IRQ_Handler // IRQ26
29 j IRQ_Handler // IRQ27
30 j IRQ_Handler // IRQ28
31 j IRQ_Handler // IRQ29
32 j IRQ_Handler // IzRQ30
33 j IRQ_Handler // IRQ30
浏览IRQ_Handler可知,触发中断或异常均会执行该代码,该函数的主要的功能就是实现了软件保存上下文。根据RISC-V规范可知,RISC-V架构定义不支持硬件压栈,所以需要软件实现这部分,这样做的出发点大概是为了简化RISC-V架构的内核的设计吧!!!
libcpu/riscv/rv32m1中的IRQ_Handler函数存在如下内容
1 /* switch to interrupt stack */
2 la sp, __stack // 移植时需修改
3 /* interrupt handle */
4 call rt_interrupt_enter
5 csrr a0, mcause
6 csrr a1, mepc
7 mv a2, sp
8 call SystemIrqHandler // 移植时需修改
9 call rt_interrupt_leave
这部分的作用是,加载中断栈的栈顶地址与执行保存上文之后的工作,这里是调用SystemIrqHandler函数进行中断分发,修改如下:
1 /* switch to interrupt stack */
2 la sp, __freertos_irq_stack_top //栈顶地址 位于链接脚本中
3 /* interrupt handle */
4 call rt_interrupt_enter
5 csrr a0, mcause
6 csrr a1, mepc
7 mv a2, sp
8 call vSystemIrqHandler // 调用vSystemIrqHandler函数
9 call rt_interrupt_leave
在board.c的rt_hw_board_init函数中,添加如下代码:
1vPortSetupTimerInterrupt();//初始化定时器
2 volatile uint32_t mtvec = 0;
3 __asm volatile( "csrr %0, mtvec" : "=r"( mtvec ) );//声明仅有一张向量表
4 __asm volatile( "csrs mie, %0" :: "r"(0x880) );//使能定时器中断与外部中断
至此,基本的移植工作已经完成,可以采用静态创建任务的方式,实现多任务的创建与调度。
动态内存
在board.c添加下述代码:
1#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
2#define RT_HEAP_SIZE (64*1024)
3static rt_uint8_t rt_heap[RT_HEAP_SIZE];
4void *rt_heap_begin_get(void)
5
6 return rt_heap;
7
8void *rt_heap_end_get(void)
9
10 return rt_heap + RT_HEAP_SIZE;
11
12#endif
在rt_hw_board_init添加下述代码:
1#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
2 rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
3#endif
完成上述工作便可使用RT-Thread的动态内存相关接口,同样可以种动态创建线程的函数。
shell
在链接脚本的.text段添加如下内容
1 /* section information for finsh shell */
2 . = ALIGN(4);
3 __fsymtab_start = .;
4 KEEP(*(FSymTab))
5 __fsymtab_end = .;
6 . = ALIGN(4);
7 __vsymtab_start = .;
8 KEEP(*(VSymTab))
9 __vsymtab_end = .;
10 . = ALIGN(4);
实现以下函数:
1char rt_hw_console_getchar(void)
2
3 return udma_uart_getchar(0);
4
1void rt_hw_console_output(const char *str)
2
3 writeraw(0, strlen(str), (uint8_t*)str);
4
注:writeraw函数来自udma_uart_writeraw,去掉了其中涉及FreeRTOS的API.
完成上述部分便可以使用RT-Thread的shell。
自动初始化
在链接脚本的.text段添加如下内容:
1 /* section information for initial. */
2 . = ALIGN(4);
3 __rt_init_start = .;
4 KEEP(*(SORT(.rti_fn*)))
5 __rt_init_end = .;
6 . = ALIGN(4);
添加上述代码后便可使用RT-Thread的自动初始化接口。
结果验证
在生成的目标文件的目录下,输入运行命令,示例命令:
1/home/wangshun/bin/qemu-riscv/bin/qemu-system-riscv32 -M core_v_mcu -bios none -kernel rtthread.elf -nographic -monitor none -serial stdio
使用时,/home/wangshun/bin/qemu-riscv/bin/修改为用户的qemu的路径。
运行结果如下:
1\\ | /
2- RT - Thread Operating System
3 / | \\ 5.0.0 build Dec 16 2022 11:25:07
4 2006 - 2022 Copyright by RT-Thread team
5Hello RT-Thread
6msh >help
7RT-Thread shell commands:
8finshToCLI - Switch to CLI: CLI component of Core-V-MCU
9pin - pin [option]
10clear - clear the terminal screen
11version - show RT-Thread version information
12list - list objects
13help - RT-Thread shell help.
14ps - List threads in the system.
15free - Show the memory usage in the system.
16msh >ps
17thread pri status sp stack size max used left tick error
18-------- --- ------- ---------- ---------- ------ ---------- ---
19tshell 20 running 0x00000160 0x00001000 22% 0x00000009 OK
20tidle0 31 ready 0x000000f0 0x00000100 98% 0x1c05e62d OK
21timer 4 suspend 0x000000e0 0x00000200 43% 0x00000008 OK
22msh >
至此,移植RT-Thread至OPENHW开源的基于CV32E40P内核的CORE-V-MCU便移植完成。
CORE-V-MCU bsp
core-v-mcu的bsp已经合并至RT-Thread的主仓库,并配有详细的使用说明,链接。
小结
在为RISC-V移植RTOS时,笔者认为要具备RISC-V架构规范与编程规范的基本的了解,磨刀不误砍柴工嘛,虽然在写本文时洋洋洒洒写的很自在,但是在移植的过程中每遇到一个坑就会卡好久,不过也不要妄自菲薄,经过这么一个过程就好很多了,此时不禁让人感慨“两岸猿声啼不住,轻舟已过万重山”。
————————————————
版权声明:
本文为RT-Thread论坛用户「牧尘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://club.rt-thread.org/ask/article/24d7317f5125b054.html
———————End———————
你可以添加微信:rtthread2020 为好友,注明:公司+姓名,拉进RT-Thread官方微信交流群!
爱我就给我点在看
👇点击阅读原文
以上是关于OPENHW开源CORE-V-MCU移植RT-Thread的主要内容,如果未能解决你的问题,请参考以下文章