QEMU OpenSBI 裸机开发之字符打印
1. 编译OpenSBI镜像
前面的章节我们是直接从m模式开发的裸机程序,从本章节开始我们来学习在已经有OpenSBI的基础下进行裸机开发。首先需要下载OpenSBI源码进行编译,官方仓库地址如下。
git clone https://github.com/riscv/opensbi.git
下载完成后进入目录输入如下命令进行编译:
export CROSS_COMPILE=riscv64-unknown-elf- make PLATFORM=generic clean make PLATFORM=generic FW_JUMP_ADDR=0x80200000
其中0x80200000是指后面我们编写的裸机程序的入口地址,这个是OpenSBI源码中默认的跳转地址,当然也可以修改成其他合法地址,在后面的裸机程序编译时,我们也需要将链接地址进行修改。
编译完成后,生成的fw_jump.elf文件在build/platform/generic/firmware/目录下,这个文件就是我们要用的OpenSBI镜像。
2. 工程改动
2.1 总览
在OpenSBI环境下开发的裸机程序工程和原来相比有些地方要做修改,先来看下整体的文件分布,如下所示。
- entry.S:入口文件,主要设置了栈。
- fw_jump.elf:OpenSBI镜像。
- kernel.ld:编译用的链接脚本。
- Makefile:makefile文件,控制源码的编译过程。
- sbi.h:对OpenSBI调用做了一些接口封装,方便使用。
- start.c:是C语言实现的入口,在entry.S中会调转到C函数中。
2.2 Makefile
Makefile和之前的相比,主要是qemu启动命令修改了,因为现在要先启动OpenSBI镜像,然后再加载我们的程序启动运行,所以需要将“bios”选项修改为“-bios fw_jump.elf”,如下所示。
QEMUOPTS = -machine virt -bios fw_jump.elf -kernel $(KERNEL_IMAGE_NAME) -m 128M -smp $(CPUS) -nographic
2.3 连接脚本
链接脚本相比于原来的主要是修改了链接地址为0x80200000,如下所示。
OUTPUT_ARCH( "riscv" ) ENTRY( _entry ) SECTIONS { /* * ensure that entry.S / _entry is at 0x80200000, * where qemu's -kernel jumps. */ . = 0x80200000; .text : { *(.text .text.*) . = ALIGN(0x1000); PROVIDE(etext = .); }
2.4 入口处理
在entry.S中,原来是先读取当前cpu核id然后再设置栈地址,但是现在OpenSBI跳转到我们的裸机程序时,裸机程序已经处于s模式,就不然再运行只能在m模式下执行的指令了,所以直接将栈地址加上4KB即可,如下所示。
.section .text .global _entry _entry: la sp, __stack_start li a0, 4096 add sp, sp, a0 call start loop: j loop
2.5 SBI接口
我们将SBI调用封装成一些接口方便使用,定义在sbi.h中,这个文件也就是对之前的ecall.h进行完善而成,如下所示。
#ifndef _ASM_RISCV_SBI_H #define _ASM_RISCV_SBI_H #define SBI_SET_TIMER 0 #define SBI_CONSOLE_PUTCHAR 1 #define SBI_CONSOLE_GETCHAR 2 #define SBI_CLEAR_IPI 3 #define SBI_SEND_IPI 4 #define SBI_REMOTE_FENCE_I 5 #define SBI_REMOTE_SFENCE_VMA 6 #define SBI_REMOTE_SFENCE_VMA_ASID 7 #define SBI_SHUTDOWN 8 #define SBI_CALL(which, arg0, arg1, arg2) ({ \ register unsigned long a0 asm ("a0") = (unsigned long)(arg0); \ register unsigned long a1 asm ("a1") = (unsigned long)(arg1); \ register unsigned long a2 asm ("a2") = (unsigned long)(arg2); \ register unsigned long a7 asm ("a7") = (unsigned long)(which); \ asm volatile ("ecall" \ : "+r" (a0) \ : "r" (a1), "r" (a2), "r" (a7) \ : "memory"); \ a0; \ }) /* Lazy implementations until SBI is finalized */ #define SBI_CALL_0(which) SBI_CALL(which, 0, 0, 0) #define SBI_CALL_1(which, arg0) SBI_CALL(which, arg0, 0, 0) #define SBI_CALL_2(which, arg0, arg1) SBI_CALL(which, arg0, arg1, 0) static inline void sbi_console_putchar(int ch) { SBI_CALL_1(SBI_CONSOLE_PUTCHAR, ch); } static inline int sbi_console_getchar(void) { return SBI_CALL_0(SBI_CONSOLE_GETCHAR); } static inline void sbi_set_timer(unsigned long stime_value) { SBI_CALL_1(SBI_SET_TIMER, stime_value); } static inline void sbi_shutdown(void) { SBI_CALL_0(SBI_SHUTDOWN); } static inline void sbi_clear_ipi(void) { SBI_CALL_0(SBI_CLEAR_IPI); } static inline void sbi_send_ipi(const unsigned long *hart_mask) { SBI_CALL_1(SBI_SEND_IPI, hart_mask); } static inline void sbi_remote_fence_i(const unsigned long *hart_mask) { SBI_CALL_1(SBI_REMOTE_FENCE_I, hart_mask); } static inline void sbi_remote_sfence_vma(const unsigned long *hart_mask, unsigned long start, unsigned long size) { SBI_CALL_1(SBI_REMOTE_SFENCE_VMA, hart_mask); } static inline void sbi_remote_sfence_vma_asid(const unsigned long *hart_mask, unsigned long start, unsigned long size, unsigned long asid) { SBI_CALL_1(SBI_REMOTE_SFENCE_VMA_ASID, hart_mask); } #endif
在SBI规范中,已经提供了输出字符的调用,我们直接使用即可在终端上打印出字符。
2.6 C函数处理
C函数start通过SBI接口来打印字符即可,如下所示。
#include "sbi.h" void start(void) { sbi_console_putchar('r'); sbi_console_putchar('u'); sbi_console_putchar('n'); }
3. 测试
在命令行输入“make qemu”,然后在最后就可以看到字符信息输出,如下所示。
ewenbin@gewenbin-virtual-machine:~/Desktop/qemu_test/lesson12$ make qemu riscv64-unknown-elf-gcc -c -o entry.o entry.S riscv64-unknown-elf-gcc -Wall -Werror -O -fno-omit-frame-pointer -ggdb -mcmodel=medany -ffreestanding -fno-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -c -o start.o start.c riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel.ld -o kernelimage entry.o start.o riscv64-unknown-elf-objdump -S kernelimage > kernel.asm riscv64-unknown-elf-objdump -t kernelimage | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel.sym qemu-system-riscv64 -machine virt -bios fw_jump.elf -kernel kernelimage -m 128M -smp 1 -nographic OpenSBI v0.8-81-g7dcb1e1 ____ _____ ____ _____ / __ \ / ____| _ \_ _| | | | |_ __ ___ _ __ | (___ | |_) || | | | | | '_ \ / _ \ '_ \ \___ \| _ < | | | |__| | |_) | __/ | | |____) | |_) || |_ \____/| .__/ \___|_| |_|_____/|____/_____| | | |_| Platform Name : riscv-virtio,qemu Platform Features : timer,mfdeleg Platform HART Count : 1 Firmware Base : 0x80000000 Firmware Size : 112 KB Runtime SBI Version : 0.2 Domain0 Name : root Domain0 Boot HART : 0 Domain0 HARTs : 0* Domain0 Region00 : 0x0000000080000000-0x000000008001ffff () Domain0 Region01 : 0x0000000000000000-0xffffffffffffffff (R,W,X) Domain0 Next Address : 0x0000000080200000 Domain0 Next Arg1 : 0x0000000082200000 Domain0 Next Mode : S-mode Domain0 SysReset : yes Boot HART ID : 0 Boot HART Domain : root Boot HART ISA : rv64imafdcsu Boot HART Features : scounteren,mcounteren,time Boot HART PMP Count : 16 Boot HART PMP Granularity : 4 Boot HART PMP Address Bits: 54 Boot HART MHPM Count : 0 Boot HART MHPM Count : 0 Boot HART MIDELEG : 0x0000000000000222 Boot HART MEDELEG : 0x000000000000b109 run
4. 工程源码
链接:https://pan.baidu.com/s/1TnTYr7mywdKj5bxpdmWnyA,提取码:q772,见lesson12。
评论