0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

opensbi下的riscv64裸機系列編程1(串口輸出)

嵌入式IoT ? 來源:嵌入式IoT ? 作者:嵌入式IoT ? 2020-12-31 10:56 ? 次閱讀

opensbi下的riscv64裸機系列編程1(串口輸出)

  • 1.說明

  • 2.opensbi的編譯

  • 3.基本環(huán)境的準備

    • 3.1 準備qemu

    • 3.2 準備交叉編譯工具鏈

  • 4.工程完善

  • 5.封裝的sbi接口

  • 6.程序運行

  • 7.printf函數(shù)的實現(xiàn)

  • 8.小結

1.說明

前面的文章中已經提到了opensbi的作用不僅僅是一個引導作用,還提供了M模式轉換到S模式的實現(xiàn),同時在S-Mode下的內核可以通過這一層訪問一些M-Mode的服務。

本文會從最小系統(tǒng)角度出發(fā),利用opensbi的M-Mode的服務在控制臺上輸出Hello。

2.opensbi的編譯

opensbi提供了三種引導啟動模式

  • FW_PAYLOAD
  • FW_JUMP
  • FW_DYNAMIC

那么這三種模式有什么區(qū)別呢?

FW_PAYLOAD

這種模式會直接將Opensbi固件與uboot等綁定在一起。

可以說這種模式是需要bootloader的。

FW_JUMP

這種模式會直接跳轉到bootloader去執(zhí)行。

這個對于qemu的啟動模式來說十分的有用。

FW_DYNAMIC

這種模式跳轉的時候會傳遞動態(tài)的參數(shù)

這里是通過寄存器a2傳遞了fw_dynamic_info結構體信息。

b4cb5694-4ad0-11eb-8b86-12bb97331649.png

為了簡化模型,目前只通過FW_JUMP方式進行跳轉。

下載opensbi的代碼

gitclonehttps://github.com/riscv/opensbi.git

進行編譯

exportCROSS_COMPILE=riscv64-unknown-elf-
makePLATFORM=genericclean
makePLATFORM=genericFW_JUMP_ADDR=0x80200000

注意FW_JUMP_ADDR=0x80200000是指定的跳轉地址。當然可以指定固件跳轉到其他的地址。

生成fw_jump.elf位于platform/generic/firmware/fw_jump.elf

3.基本環(huán)境的準備

3.1 準備qemu

可以到官網下載最新的qemu

https://www.qemu.org

解壓后進行安裝與編譯。

tarxvfqemu-5.2.0.tar.xz
./configure--target-list=riscv64-softmmu
make
sudomakeinstall

3.2 準備交叉編譯工具鏈

可以到官網上下載對應的交叉編譯工具鏈

https://www.sifive.com/software

準備交叉編譯工具鏈

exportPATH=$PATH:/opt/riscv64-unknown-elf-gcc-8.3.0-2020.04.0-x86_64-linux-ubuntu14/bin/

4.工程完善

相關的實驗代碼已經放到倉庫

https://github.com/bigmagic123/riscv64_opensbi_baremetal/tree/master/01_startup

工程的目錄結構如下:

.
├──build.sh##編譯腳本
├──entry.s##入口函數(shù)
├──fw_bin##可執(zhí)行的固件腳本
│├──fw_jump.elf##opensbi
│├──hello.elf##編譯完成的固件
│└──run.sh##直接運行的腳本
├──link.ld##鏈接文件
├──main.c##主函數(shù)
├──readme.md
└──sbi.h##sbi調用api

首先是編譯腳本

build.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.smain.c文件,并通過link.ld文件進行鏈接。

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{ }包含的結構中。

其中*代表通配符,而.則表示當前的地址。當鏈接腳本需要使用的時候,可將其通過-T進行參數(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

最關鍵的是兩點:

  • 設置函數(shù)堆地址
  • 跳轉到main函數(shù)
stack_top:
.space4096*4
.globalstack_top

將棧頂設置,通過call跳轉到c語言的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){}
}

這個程序會調用opensbi的函數(shù),此時可以在S-Mode訪問M-Mode的串口輸出服務。

5.封裝的sbi接口

可以通過下面的官方文檔來了解其使用。

https://github.com/riscv/riscv-sbi-doc/blob/master/riscv-sbi.adoc

在進行M-Mode服務訪問的時候,采用了ECALL進行系統(tǒng)調用。

在系統(tǒng)調用過程中,ecall會使用a0與a7寄存器。其中a7寄存器保留的是系統(tǒng)的調用號,而a0寄存器則保存系統(tǒng)的調用參數(shù)。返回值則會保存在a0寄存器中。

需要注意的是在RISCV的設計上,S模式不直接控制時鐘中斷和軟件中斷,而是使用ecall指令請求M模式設置定時器或在代理處理器中斷。

所以opensbi在提供M-Mode服務的時候,到目前為止,opensbi提供的sbi服務接口有如下的表示:

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的實現(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;
})

根據上述的解釋,ecall采用的是內嵌匯編函數(shù)。

ecall
iia0,101
lia1,0
lia2,0
lia7,1

這個內嵌匯編的展開形式如上面所示,a0、a1、a2表示傳遞的參數(shù),a7表示系統(tǒng)調用號。

而根據內嵌匯編的語法,有著如下的格式

asm(assemblertemplate
:/*outputoperands*/
:/*inputoperands*/
:/*clobberedregisterslist*/
);

對于C語言來說,其函數(shù)的調用規(guī)則是處理器規(guī)定的,而編譯器可以按照這種規(guī)則進行翻譯代碼。riscv的函數(shù)調用規(guī)則可以按照下面的文檔進行操作。

https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf

而對于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)

可以看到通過ecall只傳遞一個參數(shù)。

6.程序運行

fw_bin文件夾下輸入./run.sh就可以運行看到效果了。

b4f5e012-4ad0-11eb-8b86-12bb97331649.png

而這條操作的代碼如下:

qemu-system-riscv64-Msifive_u-biosfw_jump.elf-kernelhello.elf-nographic

對應的machine是sifive_u。bios是fw_jump.elf。

7.printf函數(shù)的實現(xiàn)

對于printf函數(shù)的使用很容易,但是深入了解其實現(xiàn)機制,發(fā)現(xiàn)并不簡單,因為可變參數(shù)的特性使得其變得復雜起來。

實驗代碼如下:

https://github.com/bigmagic123/riscv64_opensbi_baremetal/tree/master/02_printf

看一個glibc中的prinf的實現(xiàn)機制。

#include
#include
#include

/*WriteformattedoutputtostdoutfromtheformatstringFORMAT.*/
/*VARARGS1*/
intprintf(constchar*format,...)
{
va_listarg;
intdone;

va_start(arg,format);
done=vprintf(format,arg);
va_end(arg);

returndone;
}

對于上述的定義

intprintf(constchar*format,...)

format表示固定的參數(shù),...表示可變的參數(shù)。

主要的實現(xiàn)過程利用三個函數(shù)進行

va_start(p,format)//將指針p移到第一個變量參數(shù)
var=va_arg(p,變量類型)//已知變量的情況下,移到下個參數(shù)變量
va_end(p)//結束參數(shù)使用等價于p=NULL

這里為了實現(xiàn)方便,我直接使用開源的tinyprintf。

https://github.com/cjlano/tinyprintf

移植的過程也很容易,在main.c文件中作如下的實現(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進行串口輸出了。

結果如下:

b554dde2-4ad0-11eb-8b86-12bb97331649.png

8.小結

第一階段實現(xiàn)了opensbi的啟動流程,同時通過系統(tǒng)調用訪問串口輸出。已經實現(xiàn)了S-Mode下訪問M-Mode的初步計劃,并且通過串口進行基本的輸出過程。隨著工程的不斷增加,后續(xù)會增加makefile工程組織,riscv下的中斷處理、以及定時器中斷的實現(xiàn),下篇文章主要介紹這些。

責任編輯:xj

原文標題:opensbi下的riscv64裸機系列編程1(串口輸出)

文章出處:【微信公眾號:嵌入式IoT】歡迎添加關注!文章轉載請注明出處。


聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 編程
    +關注

    關注

    88

    文章

    3546

    瀏覽量

    93502
  • 串口
    +關注

    關注

    14

    文章

    1539

    瀏覽量

    75944
  • RISC
    +關注

    關注

    6

    文章

    461

    瀏覽量

    83621

原文標題:opensbi下的riscv64裸機系列編程1(串口輸出)

文章出處:【微信號:Embeded_IoT,微信公眾號:嵌入式IoT】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    如何一鍵生成支持riscv64的Debian rootfs?

    如何一鍵生成支持riscv64的Debian rootfs?
    發(fā)表于 12-28 07:21

    請問哪里可以下載可以在d1上運行的debian/riscv64的img鏡像

    請問哪里可以下載可以在d1上運行的debian/riscv64的img鏡像?非常感謝!
    發(fā)表于 12-28 07:47

    d1哪吒開發(fā)板的啟動流程分析

    志D1芯片的啟動流程最底層的分析來看,和其他全志產品線的芯片的啟動流程基本類似,主要需要理解的是fel模式對SRAM,DDR等操作,這樣在做裸機開發(fā)的時候,才能將程序下載進去。有了這些理解,在做
    發(fā)表于 08-16 08:16

    全志D1開發(fā)板RISCV64開箱評測

    的生態(tài)建設遠遠沒有arm強大,但是也在開源思想的引領,開始逐步走向大眾的視野。 這塊哪吒 RISCV64的板子,從主要的核的特性上來看,與目前市面上可見的riscv開發(fā)板相比,特性主要有以下幾點:
    發(fā)表于 08-16 07:05

    TOP系列編程器軟件

    TOP系列編程器軟件適用于TOP全系列編程器。(TOP853?TOP2003?TOP2004?TOP2005?TOP2048)
    發(fā)表于 12-27 16:27 ?1633次下載

    UNICON觸摸屏HU系列編程軟件0723

    支持UNICON觸摸屏HU系列編程軟件 安裝步驟:
    發(fā)表于 09-24 11:18 ?86次下載
    UNICON觸摸屏HU<b class='flag-5'>系列編程</b>軟件0723

    FX1SFX1NFX2NFX2NC系列編程手冊

    三菱FX1SFX1NFX2NFX2NC系列編程手冊
    發(fā)表于 12-13 22:43 ?4次下載

    riscv64裸機編程實踐與分析

    riscv64 裸機編程實踐與分析 1.概述 2.最小工程的構成 3. 鏈接腳本 4.可執(zhí)行的程序源代碼分析 5.編譯與運行 5.1 編譯 5.2 運行 5.3 調試 6.總結
    的頭像 發(fā)表于 12-31 10:54 ?4397次閱讀
    <b class='flag-5'>riscv64</b><b class='flag-5'>裸機</b><b class='flag-5'>編程</b>實踐與分析

    opensbiriscv64裸機編程:中斷與異常

    opensbiriscv64裸機編程2(中斷與異常) 1.本文說明 2.
    的頭像 發(fā)表于 01-07 10:30 ?2617次閱讀

    riscv64上運行完整Linux的流程

    搭建qemu RISC-V運行Linux環(huán)境 1.本文概述 2.工具介紹 2.1 riscv-gnu-toolchain 2.2 spike 2.3 RISC-V Porxy Kernel 2.4
    的頭像 發(fā)表于 05-23 15:01 ?7229次閱讀
    <b class='flag-5'>riscv64</b>上運行完整Linux的流程

    全志D1哪吒 RISCV64開發(fā)板上手評測

    全志D1開發(fā)板(哪吒 RISCV64)開箱評測 1.概述 2.開箱體驗 3.資料情況 3.1 上手情況 3.2 芯片文檔 4.總體感受 1.概述作為主打RISC-V架構芯片的國產開發(fā)板
    的頭像 發(fā)表于 05-27 17:56 ?9637次閱讀
    全志D<b class='flag-5'>1</b>哪吒 <b class='flag-5'>RISCV64</b>開發(fā)板上手評測

    ARM Cortex-R系列編程手冊資源下載

    ARM Cortex-R系列編程手冊資源下載
    發(fā)表于 08-23 16:16 ?13次下載

    支持串口并口和USB(FT245B)PIC-Pgm 1.9.3.1全系列編程軟件

    支持串口并口和USB(FT245B)PIC-Pgm 1.9.3.1全系列編程軟件
    發(fā)表于 12-20 16:34 ?9次下載

    RT-Smart riscv64匯編注釋

    以rt-smart在全志D1上的代碼為例,主要注釋了rt-smart在riscv64上的系統(tǒng)初始化和異常處理的代碼倉庫地址https://gitee.com/rtthread/rt-thread
    的頭像 發(fā)表于 02-08 21:40 ?1076次閱讀

    RT-Smart riscv64匯編注釋

    以rt-smart在全志D1上的代碼為例,主要注釋了rt-smart在riscv64上的系統(tǒng)初始化和異常處理的代碼
    的頭像 發(fā)表于 10-12 17:26 ?558次閱讀
    RT-Smart <b class='flag-5'>riscv64</b>匯編注釋