opensbi下的riscv64裸機(jī)系列編程1(串口輸出)
-
1.說(shuō)明
-
2.opensbi的編譯
-
3.基本環(huán)境的準(zhǔn)備
-
3.1 準(zhǔn)備qemu
-
3.2 準(zhǔn)備交叉編譯工具鏈
-
-
4.工程完善
-
5.封裝的sbi接口
-
6.程序運(yùn)行
-
7.printf函數(shù)的實(shí)現(xiàn)
-
8.小結(jié)
1.說(shuō)明
前面的文章中已經(jīng)提到了opensbi的作用不僅僅是一個(gè)引導(dǎo)作用,還提供了M模式轉(zhuǎn)換到S模式的實(shí)現(xiàn),同時(shí)在S-Mode下的內(nèi)核可以通過(guò)這一層訪問一些M-Mode的服務(wù)。
本文會(huì)從最小系統(tǒng)角度出發(fā),利用opensbi的M-Mode的服務(wù)在控制臺(tái)上輸出Hello
。
2.opensbi的編譯
opensbi提供了三種引導(dǎo)啟動(dòng)模式
- FW_PAYLOAD
- FW_JUMP
- FW_DYNAMIC
那么這三種模式有什么區(qū)別呢?
FW_PAYLOAD
這種模式會(huì)直接將Opensbi固件與uboot等綁定在一起。
可以說(shuō)這種模式是需要bootloader的。
FW_JUMP
這種模式會(huì)直接跳轉(zhuǎn)到bootloader去執(zhí)行。
這個(gè)對(duì)于qemu的啟動(dòng)模式來(lái)說(shuō)十分的有用。
FW_DYNAMIC
這種模式跳轉(zhuǎn)的時(shí)候會(huì)傳遞動(dòng)態(tài)的參數(shù)
這里是通過(guò)寄存器a2
傳遞了fw_dynamic_info
結(jié)構(gòu)體信息。
為了簡(jiǎn)化模型,目前只通過(guò)FW_JUMP
方式進(jìn)行跳轉(zhuǎn)。
下載opensbi的代碼
gitclonehttps://github.com/riscv/opensbi.git
進(jìn)行編譯
exportCROSS_COMPILE=riscv64-unknown-elf-
makePLATFORM=genericclean
makePLATFORM=genericFW_JUMP_ADDR=0x80200000
注意FW_JUMP_ADDR=0x80200000
是指定的跳轉(zhuǎn)地址。當(dāng)然可以指定固件跳轉(zhuǎn)到其他的地址。
生成fw_jump.elf
位于platform/generic/firmware/fw_jump.elf
。
3.基本環(huán)境的準(zhǔn)備
3.1 準(zhǔn)備qemu
可以到官網(wǎng)下載最新的qemu
https://www.qemu.org
解壓后進(jìn)行安裝與編譯。
tarxvfqemu-5.2.0.tar.xz
./configure--target-list=riscv64-softmmu
make
sudomakeinstall
3.2 準(zhǔn)備交叉編譯工具鏈
可以到官網(wǎng)上下載對(duì)應(yīng)的交叉編譯工具鏈
https://www.sifive.com/software
準(zhǔn)備交叉編譯工具鏈
exportPATH=$PATH:/opt/riscv64-unknown-elf-gcc-8.3.0-2020.04.0-x86_64-linux-ubuntu14/bin/
4.工程完善
相關(guān)的實(shí)驗(yàn)代碼已經(jīng)放到倉(cāng)庫(kù)
https://github.com/bigmagic123/riscv64_opensbi_baremetal/tree/master/01_startup
工程的目錄結(jié)構(gòu)如下:
.
├──build.sh##編譯腳本
├──entry.s##入口函數(shù)
├──fw_bin##可執(zhí)行的固件腳本
│├──fw_jump.elf##opensbi
│├──hello.elf##編譯完成的固件
│└──run.sh##直接運(yùn)行的腳本
├──link.ld##鏈接文件
├──main.c##主函數(shù)
├──readme.md
└──sbi.h##sbi調(diào)用api
首先是編譯腳本
build.sh
目前為了簡(jiǎn)化工程,暫時(shí)沒有使用makefile文件。
riscv64-unknown-elf-gcc-nostdlib-centry.s-oentry.o
riscv64-unknown-elf-gcc-nostdlib-cmain.c-omain.o
riscv64-unknown-elf-ld-ofw_bin/hello.elf-Tlink.ldentry.omain.o
編譯了entry.s
和main.c
文件,并通過(guò)link.ld
文件進(jìn)行鏈接。
link.ld
鏈接腳本規(guī)定了程序的布局
OUTPUT_ARCH("riscv")
OUTPUT_FORMAT("elf64-littleriscv")
ENTRY(_start)
SECTIONS
{
/*text:testcodesection*/
.=0x80200000;
start=.;
.text:{
stext=.;
*(.text.entry)
*(.text.text.*)
.=ALIGN(4K);
etext=.;
}
.data:{
sdata=.;
*(.data.data.*)
edata=.;
}
.bss:{
sbss=.;
*(.bss.bss.*)
ebss=.;
}
PROVIDE(end=.);
}
整體的鏈接腳本寫在SECTION{ }
包含的結(jié)構(gòu)中。
其中*
代表通配符,而.
則表示當(dāng)前的地址。當(dāng)鏈接腳本需要使用的時(shí)候,可將其通過(guò)-T
進(jìn)行參數(shù)的傳遞。
entry.s
該文件描述了執(zhí)行的入口函數(shù)。
.section.text.entry
.globl_start
_start:
/*setupstack*/
lasp,stack_top#setupstackpointer
callmain
halt:jhalt#entertheinfiniteloop
loop:
jloop
.section.bss.stack
.align12
.globalstack_top
stack_top:
.space4096*4
.globalstack_top
最關(guān)鍵的是兩點(diǎn):
- 設(shè)置函數(shù)堆地址
- 跳轉(zhuǎn)到main函數(shù)
stack_top:
.space4096*4
.globalstack_top
將棧頂設(shè)置,通過(guò)call
跳轉(zhuǎn)到c語(yǔ)言的main函數(shù)。
main.c
#include"sbi.h"
voidmain()
{
SBI_PUTCHAR('H');
SBI_PUTCHAR('e');
SBI_PUTCHAR('l');
SBI_PUTCHAR('l');
SBI_PUTCHAR('o');
SBI_PUTCHAR('
');
while(1){}
}
這個(gè)程序會(huì)調(diào)用opensbi的函數(shù),此時(shí)可以在S-Mode訪問M-Mode的串口輸出服務(wù)。
5.封裝的sbi接口
可以通過(guò)下面的官方文檔來(lái)了解其使用。
https://github.com/riscv/riscv-sbi-doc/blob/master/riscv-sbi.adoc
在進(jìn)行M-Mode服務(wù)訪問的時(shí)候,采用了ECALL進(jìn)行系統(tǒng)調(diào)用。
在系統(tǒng)調(diào)用過(guò)程中,ecall會(huì)使用a0與a7寄存器。其中a7寄存器保留的是系統(tǒng)的調(diào)用號(hào),而a0寄存器則保存系統(tǒng)的調(diào)用參數(shù)。返回值則會(huì)保存在a0寄存器中。
需要注意的是在RISCV的設(shè)計(jì)上,S模式不直接控制時(shí)鐘中斷和軟件中斷,而是使用ecall指令請(qǐng)求M模式設(shè)置定時(shí)器或在代理處理器中斷。
所以opensbi在提供M-Mode服務(wù)的時(shí)候,到目前為止,opensbi提供的sbi服務(wù)接口有如下的表示:
Function Name | FID | EID | Replacement EID |
---|---|---|---|
sbi_set_timer | 0 | 0x00 | 0x54494D45 |
sbi_console_putchar | 0 | 0x01 | N/A |
sbi_console_getchar | 0 | 0x02 | N/A |
sbi_clear_ipi | 0 | 0x03 | N/A |
sbi_send_ipi | 0 | 0x04 | 0x735049 |
sbi_remote_fence_i | 0 | 0x05 | 0x52464E43 |
sbi_remote_sfence_vma | 0 | 0x06 | 0x52464E43 |
sbi_remote_sfence_vma_asid | 0 | 0x07 | 0x52464E43 |
sbi_shutdown | 0 | 0x08 | 0x53525354 |
RESERVED | 0x09-0x0F |
這里只使用了sbi_console_putchar
接口。
接著看看具體的ecall的實(shí)現(xiàn):
#defineSBI_ECALL(__num,__a0,__a1,__a2)
({
registerunsignedlonga0asm("a0")=(unsignedlong)(__a0);
registerunsignedlonga1asm("a1")=(unsignedlong)(__a1);
registerunsignedlonga2asm("a2")=(unsignedlong)(__a2);
registerunsignedlonga7asm("a7")=(unsignedlong)(__num);
asmvolatile("ecall"
:"+r"(a0)
:"r"(a1),"r"(a2),"r"(a7)
:"memory");
a0;
})
根據(jù)上述的解釋,ecall采用的是內(nèi)嵌匯編函數(shù)。
ecall
iia0,101
lia1,0
lia2,0
lia7,1
這個(gè)內(nèi)嵌匯編的展開形式如上面所示,a0
、a1
、a2
表示傳遞的參數(shù),a7
表示系統(tǒng)調(diào)用號(hào)。
而根據(jù)內(nèi)嵌匯編的語(yǔ)法,有著如下的格式
asm(assemblertemplate
:/*outputoperands*/
:/*inputoperands*/
:/*clobberedregisterslist*/
);
對(duì)于C語(yǔ)言來(lái)說(shuō),其函數(shù)的調(diào)用規(guī)則是處理器規(guī)定的,而編譯器可以按照這種規(guī)則進(jìn)行翻譯代碼。riscv的函數(shù)調(diào)用規(guī)則可以按照下面的文檔進(jìn)行操作。
https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
而對(duì)于main函數(shù)中的SBI_PUTCHAR
其展開為
#defineSBI_CONSOLE_PUTCHAR1
#defineSBI_PUTCHAR(__a0)SBI_ECALL_1(SBI_CONSOLE_PUTCHAR,__a0)
#defineSBI_ECALL_1(__num,__a0)SBI_ECALL(__num,__a0,0,0)
可以看到通過(guò)ecall只傳遞一個(gè)參數(shù)。
6.程序運(yùn)行
在fw_bin
文件夾下輸入./run.sh
就可以運(yùn)行看到效果了。
而這條操作的代碼如下:
qemu-system-riscv64-Msifive_u-biosfw_jump.elf-kernelhello.elf-nographic
對(duì)應(yīng)的machine是sifive_u
。bios是fw_jump.elf
。
7.printf函數(shù)的實(shí)現(xiàn)
對(duì)于printf函數(shù)的使用很容易,但是深入了解其實(shí)現(xiàn)機(jī)制,發(fā)現(xiàn)并不簡(jiǎn)單,因?yàn)榭勺儏?shù)的特性使得其變得復(fù)雜起來(lái)。
實(shí)驗(yàn)代碼如下:
https://github.com/bigmagic123/riscv64_opensbi_baremetal/tree/master/02_printf
看一個(gè)glibc
中的prinf
的實(shí)現(xiàn)機(jī)制。
#include
#include
#include
/*WriteformattedoutputtostdoutfromtheformatstringFORMAT.*/
/*VARARGS1*/
intprintf(constchar*format,...)
{
va_listarg;
intdone;
va_start(arg,format);
done=vprintf(format,arg);
va_end(arg);
returndone;
}
對(duì)于上述的定義
intprintf(constchar*format,...)
format
表示固定的參數(shù),...
表示可變的參數(shù)。
主要的實(shí)現(xiàn)過(guò)程利用三個(gè)函數(shù)進(jìn)行
va_start(p,format)//將指針p移到第一個(gè)變量參數(shù)
var=va_arg(p,變量類型)//已知變量的情況下,移到下個(gè)參數(shù)變量
va_end(p)//結(jié)束參數(shù)使用等價(jià)于p=NULL
這里為了實(shí)現(xiàn)方便,我直接使用開源的tinyprintf
。
https://github.com/cjlano/tinyprintf
移植的過(guò)程也很容易,在main.c
文件中作如下的實(shí)現(xiàn):
#include"sbi.h"
#include"tinyprintf.h"
#defineUNUSED(x)(void)(x)
staticvoidstdout_putc(void*unused,char*ch)
{
SBI_PUTCHAR(ch);
}
voidmain()
{
init_printf(0,stdout_putc);
tfp_printf("helloworld
");
while(1){}
}
只需要移植init_printf
接口就可以使用tfp_printf
進(jìn)行串口輸出了。
結(jié)果如下:
8.小結(jié)
第一階段實(shí)現(xiàn)了opensbi的啟動(dòng)流程,同時(shí)通過(guò)系統(tǒng)調(diào)用訪問串口輸出。已經(jīng)實(shí)現(xiàn)了S-Mode下訪問M-Mode的初步計(jì)劃,并且通過(guò)串口進(jìn)行基本的輸出過(guò)程。隨著工程的不斷增加,后續(xù)會(huì)增加makefile工程組織,riscv下的中斷處理、以及定時(shí)器中斷的實(shí)現(xiàn),下篇文章主要介紹這些。
責(zé)任編輯:xj
原文標(biāo)題:opensbi下的riscv64裸機(jī)系列編程1(串口輸出)
文章出處:【微信公眾號(hào):嵌入式IoT】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
編程
+關(guān)注
關(guān)注
88文章
3565瀏覽量
93536 -
串口
+關(guān)注
關(guān)注
14文章
1540瀏覽量
76062 -
RISC
+關(guān)注
關(guān)注
6文章
461瀏覽量
83637
原文標(biāo)題:opensbi下的riscv64裸機(jī)系列編程1(串口輸出)
文章出處:【微信號(hào):Embeded_IoT,微信公眾號(hào):嵌入式IoT】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論