OpenSBI v0.9
git clone https://github.com/riscv-software-src/opensbi.git --branch v0.9
U-Boot v2021.07
https://source.denx.de/u-boot/u-boot.git --branch v2021.07
Linux 5.10.70 (直接在 www.kernel.org 上下载即可)
安装 RISCV 编译工具链 gcc-riscv64-linux-gnu
当使用 QEMU -biso 参数启动 OpenSBI + U-Boot 镜像时,一直出现找不到扁平设备树 (FDT)的情况,目前认为问题出在 U-Boot 函数 boot_get_fdt 中,这个函数位于文件 common/image-fdt.c 中 ,由4个分支进行 FDT 查找:
if (select || genimg_has_config(images)) ...
else if (images->legacy_hdr_valid &&
image_check_type(&images->legacy_hdr_os_copy,
IH_TYPE_MULTI)) ...
else if (genimg_get_format(buf) == IMAGE_FORMAT_ANDROID)
else ...
在最后一个 else 中直接就返回了而没有设置 images 中的 ft_addr 和 ft_len (即 FDT 的地址和长度)。目前没想过度分析,所以直接在最后一个else中补救,原代码如下:
else {
debug("## No Flattened Device Tree\n");
goto no_fdt;
}
通过 U-Boot 环境变量来查找 FDT,修改如下:
else {
fdt_addr = env_get_hex("fdtaddr", 0);
if(!fdt_addr) goto no_fdt;
fdt_blob = map_sysmem(fdt_addr, 0);
if(fdt_check_header(fdt_blob)) goto no_fdt;
}
然后修改 include/configs/qemu-riscv.h 中的默认环境变量,修改 kernel_addr_r 和 fdt_addr_r
"fdt_addr_r=0x82200000"
"kernel_addr_r=0x80200000"
这两个地址是 QEMU 6.1.0 中的默认加载地址,应该是与之前的版本有所变化,U-Boot 2021.07版本还没有更新。因为打算使用 virtio 虚拟块设备的方式挂载文件系统,所以再添加两个启动命令
#define BOOTENV_DEV_ACE(devtypeu, devtypel, instance) \
"bootcmd_ace=" \
"if env exists kernel_addr_r; then " \
"load virtio 0:1 ${kernel_addr_r} /Image; " \
"booti; " \
"fi;\0"
#define BOOTENV_DEV_NAME_ACE(devtypeu, devtypel, instance) \
"ace "
// 将 BOOTENV ACE 添加到目标设备列表中
#define BOOT_TARGET_DEVICES(func) \
func(ACE, ace, na) \
func(QEMU, qemu, na) \
func(VIRTIO, virtio, 0) \
func(SCSI, scsi, 0) \
func(DHCP, dhcp, na)
这样一来 U-Boot 应该就没有任何问题了,开始编译
export CROSS_COMPILE=riscv64-linux-gnu-
make qemu-riscv64_smode_defconfig
make -j16
编译完成后可以得到原材料 u-boot-nodtb.bin
其实在 arch/riscv/lib/image.c 文件中,U-Boot 2021.07 对 RISCV Linux 的头部魔数定义也已经过时,其中 res3 字段目前已经改为魔数 “RISCV”,但是这里并不会对头部检测造成影响(但还是要注意一下),所以目前不去修改。
特别简单
make PLATFORM=generic CROSS_COMPILE=riscv64-linux-gnu- FW_PATLOAD_PATH=/path/to/u-boot-nodtb.bin
最后得到一个 build/platform/generic/firmware/fw_payload.bin 就是最后的启动镜像。在使用 QEMU 引导镜像时使用 -bios 参数。 在引导过程中 OpenSBI + U-Boot 其实就是发挥着 U-Boot 本身的功能,具体可以查看 OpenSBI 项目的定位。
特别简单
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- defconfig
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j16
然后从 arch/riscv/boot/ 目录下拿到 Image 作为 Linux 内核镜像。
来一个空的文件(大小根据实际情况而定)
dd if=/dev/zero of=test.raw
挂载到环回设备上
mknod /dev/loop200 b 7 200
losetup /dev/loop200 ./test.raw
使用 parted 给 test.raw 分区 (分多少个视情况而定)
mklabel gpt
mkpart linux ext4 0% 100%
映射分区
kpartx -av /dev/loop200
最后各个分区可以在 /dev/mapper 中找到,给分区创建个文件系统
mkfs.ext4 /dev/mapper/loop200p1
随便挂一个目录到上边
mount /dev/mapper/loop200p1 ./door
然后将 Image 复制进去吧,然后用 test.raw 做一个 qcow2 格式的文件给 QEMU 启动用
qemu-img convert -f raw -O qcow2 test.raw test.qcow2
qemu-system-riscv64 -machine virt \
-smp 8 \
-m 1024 \
-bios fw_payload.bin \
-drive file=./test.qcow2,if=virtio \
-nographic
观察:
OpenSBI (我在里边打印了点无关紧要的东西,见谅)
OpenSBI v0.9
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
-- XDS(Ace)
[ACE] <sbi_boot_print_general()> scratch->next_addr: 0x0000000080200000. </qemu/opensbi-0.9/lib/sbi/sbi_init.c><61>
[ACE] <sbi_boot_print_general()> scratch->next_arg1: 0x0000000082200000. </qemu/opensbi-0.9/lib/sbi/sbi_init.c><62>
Platform Name : riscv-virtio,qemu
Platform Features : timer,mfdeleg
Platform HART Count : 8
Firmware Base : 0x80000000
Firmware Size : 156 KB
Runtime SBI Version : 0.2
Domain0 Name : root
Domain0 Boot HART : 7
Domain0 HARTs : 0*,1*,2*,3*,4*,5*,6*,7*
Domain0 Region00 : 0x0000000080000000-0x000000008003ffff ()
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 : 7
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
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
U-Boot
U-Boot 2021.07-dirty (Oct 09 2021 - 10:15:26 +0800)
CPU: rv64imafdcsu
Model: riscv-virtio,qemu
[ACE] <announce_dram_init()> ACE NOTICE <common/board_f.c><213>
DRAM: 1 GiB
Loading Environment from nowhere... OK
In: uart@10000000
Out: uart@10000000
Err: uart@10000000
Net: No ethernet found.
Hit any key to stop autoboot: 0
Couldn't find partition virtio 0:1
Can't set block device
image start address : 0x0000000080200000
magic : 0xb2830007c29769b0
magic2 : 0x4305db62
Bad Linux RISCV Image magic!
Device 0: 1af4 VirtIO Block Device
Type: Hard Disk
Capacity: 321.1 MB = 0.3 GB (657779 x 512)
... is now current device
Scanning virtio 0:1...
Scanning disk virtio-blk#8...
Found 2 disks
No EFI system partition
BootOrder not defined
EFI boot manager: Cannot load any image
scanning bus for devices...
Device 0: unknown device
No ethernet found.
No ethernet found.
此时在 U-Boot 输入 boot 命令,内核便会启动 (我在内核中也打印了些无关紧要的,[ACE] 开头的就是我打印的)
Starting kernel ...
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
[ACE] <sbi_hart_switch_mode()> next_mode is PRV_S. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><486>
[ACE] <sbi_hart_switch_mode()> __riscv_xlen is 64. </qemu/opensbi-0.9/lib/sbi/sbi_hart.c><519>
[ 0.000000] [ACE] init_task pid 0. <init/main.c><872>
[ 0.000000] Linux version 5.10.70 (root@4b4eec683270) (riscv64-linux-gnu-gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, GNU ld (GNU Binutils for Ubuntu) 2.34) #1 SMP Fri Oct 8 16:45:57 CST 2021
[ 0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[ 0.000000] efi: UEFI not found.
[ 0.000000] Zone ranges:
[ 0.000000] DMA32 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Normal empty
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] software IO TLB: mapped [mem 0x00000000bb1f7000-0x00000000bf1f7000] (64MB)
[ 0.000000] SBI specification v0.2 detected
[ 0.000000] SBI implementation ID=0x1 Version=0x9
[ 0.000000] SBI v0.2 TIME extension detected
[ 0.000000] SBI v0.2 IPI extension detected
[ 0.000000] SBI v0.2 RFENCE extension detected
[ 0.000000] SBI v0.2 HSM extension detected
[ 0.000000] riscv: ISA extensions acdfimsu
[ 0.000000] riscv: ELF capabilities acdfim
[ 0.000000] percpu: Embedded 17 pages/cpu s32552 r8192 d28888 u69632
[ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 258055
[ 0.000000] Kernel command line: noinitrd root=/dev/vda1
[ 0.000000] Dentry cache hash table entries: 131072 (order: 8, 1048576 bytes, linear)
[ 0.000000] Inode-cache hash table entries: 65536 (order: 7, 524288 bytes, linear)
[ 0.000000] Sorting __ex_table...
[ 0.000000] mem auto-init: stack:off, heap alloc:off, heap free:off
[ 0.000000] Memory: 946508K/1046528K available (6969K kernel code, 4109K rwdata, 4096K rodata, 223K init, 340K bss, 100020K reserved, 0K cma-reserved)
[ 0.000000] Virtual kernel memory layout:
[ 0.000000] fixmap : 0xffffffcefee00000 - 0xffffffceff000000 (2048 kB)
[ 0.000000] pci io : 0xffffffceff000000 - 0xffffffcf00000000 ( 16 MB)
[ 0.000000] vmemmap : 0xffffffcf00000000 - 0xffffffcfffffffff (4095 MB)
[ 0.000000] vmalloc : 0xffffffd000000000 - 0xffffffdfffffffff (65535 MB)
[ 0.000000] lowmem : 0xffffffe000000000 - 0xffffffe03fe00000 (1022 MB)
[ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1
[ 0.000000] rcu: Hierarchical RCU implementation.
[ 0.000000] rcu: RCU debug extended QS entry/exit.
[ 0.000000] Tracing variant of Tasks RCU enabled.
[ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies.
[ 0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[ 0.000000] riscv-intc: 64 local interrupts mapped
[ 0.000000] plic: plic@c000000: mapped 53 interrupts with 8 handlers for 16 contexts.
[ 0.000000] [ACE] <start_kernel()> Call init_timers(). <init/main.c><971>
[ 0.000000] [ACE] init_timers() Call init_timer_cpus(). <kernel/time/timer.c><2019>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:0. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:0. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:1. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:1. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:2. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:2. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:3. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:3. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:4. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:4. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:5. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:5. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:6. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:6. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timer_cpus() Call init_timer_cpu for cpu:7. <kernel/time/timer.c><2011>
[ 0.000000] [ACE] init_timer_cpu() for cpu:7. <kernel/time/timer.c><1994>
[ 0.000000] [ACE] init_timers() Call open_softirq(TIMER_SOFTIRQ, run_timer_softirq). <kernel/time/timer.c><2022>
[ 0.000000] [ACE] <start_kernel()> Call softirq_init(). <init/main.c><974>
[ 0.000000] random: get_random_bytes called from start_kernel+0x372/0x4e4 with crng_init=0
[ 0.000000] riscv_timer_init_dt: Registering clocksource cpuid [0] hartid [7]
[ 0.000000] clocksource: riscv_clocksource: mask: 0xffffffffffffffff max_cycles: 0x24e6a1710, max_idle_ns: 440795202120 ns
[ 0.000248] sched_clock: 64 bits at 10MHz, resolution 100ns, wraps every 4398046511100ns
[ 0.010517] Console: colour dummy device 80x25
[ 0.014473] printk: console [tty0] enabled
[ 0.018937] Calibrating delay loop (skipped), value calculated using timer frequency.. 20.00 BogoMIPS (lpj=40000)
[ 0.019356] pid_max: default: 32768 minimum: 301
[ 0.021925] Mount-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[ 0.022056] Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[ 0.056848] [ACE] rest_init() create kernel_init kernel thread with pid 1. <init/main.c><691>
[ 0.057045] [ACE] rest_init() create kernel_init kernel thread on cpu 0. <init/main.c><699>
[ 0.057809] [ACE] rest_init() create kthreadd kernel thread with pid 2. <init/main.c><706>
[ 0.057922] [ACE] rest_init() create kthreadd kernel thread on cpu 0. <init/main.c><709>
[ 0.058094] [ACE] rest_init() call schedule_preempt_disabled() <init/main.c><733>
需要总结的只有 U-Boot 引导 Linux 的过程(这里我的做法很不讲究,只是为了临时看看引导效果才这么干的),使用到distro_bootcmd的方式启动,在修改 U-Boot 时我加入了,自己定义的命令:
bootcmd_ace=if env exists kernel_addr_r; then load virtio 0:1 ${kernel_addr_r} /Image; booti; fi;
其实这里主要就只有两个有用的命令
load virtio 0:1 0x80200000 /Image
这条命令将 virtio 的 0 号设备的第一个分区中的 /Image 文件加载到内存 0x80200000 的位置。这个做法是非常不讲究的,只是为了临时完成一下 Linux 的引导
booti
这个命令用来引导 Image 类型的内核镜像(区别于 zImage,uImage)。booti 会根据 kernel_addr_r 的地址从 U-Boot 跳转到 Linux 内核,