項目背景
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上進行。
前期準備
開發環境: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(inti=0;i32;i++){
2isr_table[i]=undefined_handler;
3handler_count[i]=0;
4}
5isr_table[0x7]=timer_irq_handler;
6isr_table[0xb]=(void(*)(uint32_t))fc_soc_event_handlzer1;
在system_init函數中我們可以找到上述代碼,瀏覽該文件我們可以知道,timer_irq_handler即系統定時器的中斷入口函數,
在原有FreeRTOS工程中該函數內容如下:
1voidtimer_irq_handler(uint32_tmcause)
2{
3#warningrequirescriticalsectionifinterruptnestingisused.
4if(xTaskIncrementTick()!=0){
5vTaskSwitchContext();
6}
7}
該函數實現系統節拍的產生,所以將這里的內容修改為RT-Thread的節拍產生的方式,修改如下:
1voidtimer_irq_handler(uint32_tmcause)
2{
3#warningrequirescriticalsectionifinterruptnestingisused.
4rt_interrupt_enter();
5rt_tick_increase();
6rt_interrupt_leaves();
7}
涉及到中斷,我們就得考慮系統的中斷的實現方式,中斷一般會分為向量中斷與非向量中斷。RISC-V常在啟動后文件中進行中斷模式的配置。從crt0.S文件可以找到如下代碼:
1/*setvectortableaddress*/
2laa0,__vector_start
3ora0,a0,1/*enablevectoredmode(hardcodedanywayforCV32E40P)*/
4csrwmtvec,a0
RISC-V規范定義中斷、異常的入口地址,以及模式使用mtvec寄存器配置,上述代碼將系統的中斷模式配置為了向量模式,向量模式下,每個函數均包含一個獨立的入口函數用來處理中斷。所以我們切換至寫中斷向量表的文件vector.S,我們可以很明顯的看到一段代碼,從名字就可以看出下面是一張向量表 :
1vector_table:
2jfreertos_risc_v_trap_handler//irq0
3jfreertos_risc_v_trap_handler
4jfreertos_risc_v_trap_handler
5jfreertos_risc_v_trap_handler//irq3
6jfreertos_risc_v_trap_handler
7jfreertos_risc_v_trap_handler
8jfreertos_risc_v_trap_handler
9jfreertos_risc_v_trap_handler//ctxt_handler//irq7mtimeortimer
10jfreertos_risc_v_trap_handler
11jfreertos_risc_v_trap_handler
12jh7//freertos_risc_v_trap_handler
13jfreertos_risc_v_trap_handler//irq11Machine(eventFifo)
14jfreertos_risc_v_trap_handler
15jfreertos_risc_v_trap_handler
16jfreertos_risc_v_trap_handler
17jfreertos_risc_v_trap_handler
18jfreertos_risc_v_trap_handler//IRQ16
19jfreertos_risc_v_trap_handler//IRQ17
20jfreertos_risc_v_trap_handler//IRQ18
21jfreertos_risc_v_trap_handler//IRQ19
22jfreertos_risc_v_trap_handler//IRQ20
23jfreertos_risc_v_trap_handler//IRQ21
24jfreertos_risc_v_trap_handler//IRQ22
25jfreertos_risc_v_trap_handler//IRQ23
26jfreertos_risc_v_trap_handler//IRQ24
27jfreertos_risc_v_trap_handler//IRQ25
28jfreertos_risc_v_trap_handler//IRQ26
29jfreertos_risc_v_trap_handler//IRQ27
30jfreertos_risc_v_trap_handler//IRQ28
31jfreertos_risc_v_trap_handler//IRQ29
32jfreertos_risc_v_trap_handler//IRQ30
33jfreertos_risc_v_trap_handler//IRQ30
PS:上述向量表看的我懵了好久啊,相信對于剛接觸底層不久的小伙伴也會有同樣的感受吧,向量表不是一張多姿多彩的表嗎,怎么感覺都一樣啊???不急,我們在看一下core-v-mcu.c這個文件,又會發現一段非常顯眼的代碼:
1voidvSystemIrqHandler(uint32_tmcause)
2{
3uint32_tval=0;
4//externvoid(*isr_table[32])(uint32_t);
5isr_table[mcause&0x1f](mcause&0x1f);
6}
結合上文,思考一下大致可以明白這個函數的作用了:分發中斷。即所有的異常與中斷觸發后均會執行這個函數,那我們全局搜索一下這個函數,
找一下是哪里調用了.全局搜索可以找到如下代碼:
1CPPFLAGS+=-DportasmHANDLE_INTERRUPT=vSystemIrqHandler
繼續搜索DportasmHANDLE_INTERRUPT可從portASM.S找到如下代碼;
1load_xsp,xISRStackTop/*SwitchtoISRstackbeforefunctioncall.*/
2jalportasmHANDLE_INTERRUPT
3jprocessed_source
瀏覽代碼可知,系統觸發中斷或異常后最終均會執行vSystemIrqHandler函數,該函數是在freertos_risc_v_trap_handler函數中被調用,至此我們大致可以明白整個過程了,當中斷或者異常出發后,會查詢向量表,執行freertos_risc_v_trap_handler函數,該函數會調用vSystemIrqHandler,經其分發后最終執行到系統初始化時綁定的中斷入口函數。
在RT-Thread 中由interrupt_gcc.S中的函數實現vSystemIrqHandler函數的調用,所以我們修改中斷向量表的內容如下:
1vector_table:
2jIRQ_Handler//irq0
3jIRQ_Handler
4jIRQ_Handler
5jIRQ_Handler//irq3
6jIRQ_Handler
7jIRQ_Handler
8jIRQ_Handler
9jIRQ_Handler//ctxt_handler//irq7mtimeortimer
10jIRQ_Handler
11jIRQ_Handler
12jIRQ_Handler//IRQ_Handler
13jIRQ_Handler//irq11Machine(eventFifo)
14jIRQ_Handler
15jIRQ_Handler
16jIRQ_Handler
17jIRQ_Handler
18jIRQ_Handler//IRQ16
19jIRQ_Handler//IRQ17
20jIRQ_Handler//IRQ18
21jIRQ_Handler//IRQ19
22jIRQ_Handler//IRQ20
23jIRQ_Handler//IRQ21
24jIRQ_Handler//IRQ22
25jIRQ_Handler//IRQ23
26jIRQ_Handler//IRQ24
27jIRQ_Handler//IRQ25
28jIRQ_Handler//IRQ26
29jIRQ_Handler//IRQ27
30jIRQ_Handler//IRQ28
31jIRQ_Handler//IRQ29
32jIRQ_Handler//IzRQ30
33jIRQ_Handler//IRQ30
瀏覽IRQ_Handler可知,觸發中斷或異常均會執行該代碼,該函數的主要的功能就是實現了軟件保存上下文。根據RISC-V規范可知,RISC-V架構定義不支持硬件壓棧,所以需要軟件實現這部分,這樣做的出發點大概是為了簡化RISC-V架構的內核的設計吧!!!
libcpu/riscv/rv32m1中的IRQ_Handler函數存在如下內容
1/*switchtointerruptstack*/
2lasp,__stack//移植時需修改
3/*interrupthandle*/
4callrt_interrupt_enter
5csrra0,mcause
6csrra1,mepc
7mva2,sp
8callSystemIrqHandler//移植時需修改
9callrt_interrupt_leave
這部分的作用是,加載中斷棧的棧頂地址與執行保存上文之后的工作,這里是調用SystemIrqHandler函數進行中斷分發,修改如下:
1/*switchtointerruptstack*/
2lasp,__freertos_irq_stack_top//棧頂地址位于鏈接腳本中
3/*interrupthandle*/
4callrt_interrupt_enter
5csrra0,mcause
6csrra1,mepc
7mva2,sp
8callvSystemIrqHandler//調用vSystemIrqHandler函數
9callrt_interrupt_leave
在board.c的rt_hw_board_init函數中,添加如下代碼:
1vPortSetupTimerInterrupt();//初始化定時器
2volatileuint32_tmtvec=0;
3__asmvolatile("csrr%0,mtvec":"=r"(mtvec));//聲明僅有一張向量表
4__asmvolatile("csrsmie,%0"::"r"(0x880));//使能定時器中斷與外部中斷
至此,基本的移植工作已經完成,可以采用靜態創建任務的方式,實現多任務的創建與調度。
動態內存
在board.c添加下述代碼:
1#ifdefined(RT_USING_USER_MAIN)&&defined(RT_USING_HEAP)
2#defineRT_HEAP_SIZE(64*1024)
3staticrt_uint8_trt_heap[RT_HEAP_SIZE];
4void*rt_heap_begin_get(void)
5{
6returnrt_heap;
7}
8void*rt_heap_end_get(void)
9{
10returnrt_heap+RT_HEAP_SIZE;
11}
12#endif
在rt_hw_board_init添加下述代碼:
1#ifdefined(RT_USING_USER_MAIN)&&defined(RT_USING_HEAP)
2rt_system_heap_init(rt_heap_begin_get(),rt_heap_end_get());
3#endif
完成上述工作便可使用RT-Thread的動態內存相關接口,同樣可以種動態創建線程的函數。
shell
在鏈接腳本的.text段添加如下內容
1/*sectioninformationforfinshshell*/
2.=ALIGN(4);
3__fsymtab_start=.;
4KEEP(*(FSymTab))
5__fsymtab_end=.;
6.=ALIGN(4);
7__vsymtab_start=.;
8KEEP(*(VSymTab))
9__vsymtab_end=.;
10.=ALIGN(4);
實現以下函數:
1charrt_hw_console_getchar(void)
2{
3returnudma_uart_getchar(0);
4}
1voidrt_hw_console_output(constchar*str)
2{
3writeraw(0,strlen(str),(uint8_t*)str);
4}
注:writeraw函數來自udma_uart_writeraw,去掉了其中涉及FreeRTOS的API.
完成上述部分便可以使用RT-Thread的shell。
自動初始化
在鏈接腳本的.text段添加如下內容:
1/*sectioninformationforinitial.*/
2.=ALIGN(4);
3__rt_init_start=.;
4KEEP(*(SORT(.rti_fn*)))
5__rt_init_end=.;
6.=ALIGN(4);
添加上述代碼后便可使用RT-Thread的自動初始化接口。
結果驗證
在生成的目標文件的目錄下,輸入運行命令,示例命令:
1/home/wangshun/bin/qemu-riscv/bin/qemu-system-riscv32-Mcore_v_mcu-biosnone-kernelrtthread.elf-nographic-monitornone-serialstdio
使用時,/home/wangshun/bin/qemu-riscv/bin/修改為用戶的qemu的路徑。
運行結果如下:
1|/
2-RT-ThreadOperatingSystem
3/|5.0.0buildDec16202211:25:07
42006-2022CopyrightbyRT-Threadteam
5HelloRT-Thread
6msh>help
7RT-Threadshellcommands:
8finshToCLI-SwitchtoCLI:CLIcomponentofCore-V-MCU
9pin-pin[option]
10clear-cleartheterminalscreen
11version-showRT-Threadversioninformation
12list-listobjects
13help-RT-Threadshellhelp.
14ps-Listthreadsinthesystem.
15free-Showthememoryusageinthesystem.
16msh>ps
17threadpristatusspstacksizemaxusedlefttickerror
18---------------------------------------------------------
19tshell20running0x000001600x0000100022%0x00000009OK
20tidle031ready0x000000f00x0000010098%0x1c05e62dOK
21timer4suspend0x000000e00x0000020043%0x00000008OK
22msh>
至此,移植RT-Thread至OPENHW開源的基于CV32E40P內核的CORE-V-MCU便移植完成。
CORE-V-MCU bsp
core-v-mcu的bsp已經合并至RT-Thread的主倉庫,并配有詳細的使用說明。
小結
在為RISC-V移植RTOS時,筆者認為要具備RISC-V架構規范與編程規范的基本的了解,磨刀不誤砍柴工嘛,雖然在寫本文時洋洋灑灑寫的很自在,但是在移植的過程中每遇到一個坑就會卡好久,不過也不要妄自菲薄,經過這么一個過程就好很多了,此時不禁讓人感慨“兩岸猿聲啼不住,輕舟已過萬重山”。
審核編輯 :李倩
-
開源
+關注
關注
3文章
3421瀏覽量
42773 -
RT-Thread
+關注
關注
31文章
1308瀏覽量
40492 -
RISC-V
+關注
關注
45文章
2334瀏覽量
46735
原文標題:OPENHW開源CORE-V-MCU移植RT-Thread
文章出處:【微信號:RTThread,微信公眾號:RTThread物聯網操作系統】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
i.MX RT1170:VGLite移植RT-Thread Nano過程講解(上)
![i.MX <b class='flag-5'>RT</b>1170:VGLite<b class='flag-5'>移植</b><b class='flag-5'>RT-Thread</b> Nano過程講解(上)](https://file1.elecfans.com/web2/M00/AD/2F/wKgaomVMUFCAD-6dAABXFv2-2a8881.png)
OPENHW開源CORE-V-MCU移植RT-Thread的流程
移植RT-Thread的原理與方法
【國產MCU移植】HC32F460基于Keil MDK 移植 RT-Thread Nano
![【國產<b class='flag-5'>MCU</b><b class='flag-5'>移植</b>】HC32F460基于Keil MDK <b class='flag-5'>移植</b> <b class='flag-5'>RT-Thread</b> Nano](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
【國產MCU系列】在 HK32F030 上移植 RT-Thread Nano
![【國產<b class='flag-5'>MCU</b>系列】在 HK32F030 上<b class='flag-5'>移植</b> <b class='flag-5'>RT-Thread</b> Nano](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
基于 Keil MDK 移植 RT-Thread Nano
![基于 Keil MDK <b class='flag-5'>移植</b> <b class='flag-5'>RT-Thread</b> Nano](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
RT-Thread操作系統的FreeRTOS兼容層
機智云設備移植RT-Thread
![機智云設備<b class='flag-5'>移植</b><b class='flag-5'>RT-Thread</b>](https://file.elecfans.com/web2/M00/3E/11/pYYBAGJekXmABRUKAAYONSRKcWg428.png)
評論