RISC-Vマシン(QEMU・Virt)でのリセット後の処理の流れ
QEMU上のRISC-Vマシン(Virt)ではマシンの起動・リセット時にプログラムカウンタが0x80000000番地へセットされ、そこからプログラムの実行が始まるようになっている。
リセットが行われてから0x80000000に飛んでくるまでの具体的な処理の流れが分かってなくてモヤモヤしてたので、QEMUのソースを読むなどして確認した。
リセットから0x80000000へジャンプするしくみ
a. マシンのリセット時に 0x1000(リセットベクタアドレス) へ飛ぶようにハード的に構成されている
b. リセットベクタアドレスにはROMが組み込まれており、ROM上で以下のようにプログラムされている
プログラムの内容はざっくりこんな感じ
t0に現在のPCの値(0x1000)をセット
t0へstart_addrの値(= 0x80000000)をロード
t0のアドレス(= 0x80000000)へジャンプ
code:sh
$ qemu-system-riscv64 -machine virt -bios none -nographic
(Ctrl+a c を押してモニタを起動)
QEMU 6.2.0 monitor - type 'help' for more information
(リセットベクタの中の命令を眺める)
(qemu) x/10i 0x1000
0x0000000000001000: 00000297 auipc t0,0 # 0x1000
0x0000000000001004: 02828613 addi a2,t0,40
0x0000000000001008: f1402573 csrrs a0,mhartid,zero
0x000000000000100c: 0202b583 ld a1,32(t0)
0x0000000000001010: 0182b283 ld t0,24(t0)
0x0000000000001014: 00028067 jr t0
0x0000000000001018: 0000 illegal (start_addrの下位アドレス = 0x0000)
0x000000000000101a: 8000 illegal (start_addrの上位アドレス = 0x8000)
0x000000000000101c: 0000 illegal (fdtの下位アドレス)
0x000000000000101e: 0000 illegal (fdtのの上位アドレス)
c.マシンリセット後、リセットベクタのある0x1000へジャンプし、リセットベクタのプログラムが実行されて 0x80000000 へジャンプする
メモリ上の命令を確認する方法
QEMUモニタの「x/(表示したい命令数?)i (アドレス)」でメモリ上の命令を確認できるみたい。便利〜
code:sh
$ qemu-system-riscv64 -machine virt -bios none -nographic
(Ctrl+a c を押してモニタを起動)
QEMU 6.2.0 monitor - type 'help' for more information
(qemu) x/10i 0x1000
0x0000000000001000: 00000297 auipc t0,0 # 0x1000
0x0000000000001004: 02828613 addi a2,t0,40
0x0000000000001008: f1402573 csrrs a0,mhartid,zero
0x000000000000100c: 0202b583 ld a1,32(t0)
0x0000000000001010: 0182b283 ld t0,24(t0)
0x0000000000001014: 00028067 jr t0
0x0000000000001018: 0000 illegal
0x000000000000101a: 8000 illegal
0x000000000000101c: 0000 illegal
0x000000000000101e: 0000 illegal
参考
調査まえのメモ
あやしそうなところ
info roms
0x00001000 〜 0x00001027 の "mrom.reset" があやしい
code:info roms
(qemu) info roms
addr=0000000000001000 size=0x000028 mem=rom name="mrom.reset"
addr=0000000000001028 size=0x000030 mem=rom name="mrom.finfo"
addr=0000000080000000 size=0x026000 mem=ram name="kernel/kernel ELF program header segment 0"
addr=0000000087000000 size=0x100000 mem=ram name="fdt"
info mtree
0x00001000 の riscv_virt_board.mrom があやしい
code:info mtree
(qemu) info mtree
address-space: memory
0000000000000000-ffffffffffffffff (prio 0, i/o): system
0000000000001000-000000000000ffff (prio 0, rom): riscv_virt_board.mrom
virt_memmap
code:c
static const MemMapEntry virt_memmap[] = {
VIRT_PLIC = { 0xc000000, VIRT_PLIC_SIZE(VIRT_CPUS_MAX * 2) }, };
調査中のメモ
hw/riscv/virt.c の virt_machine_done 関数
start_addr = memmap[VIRT_DRAM].base;
start_addr は 0x80000000
code:virt.c
/* load the reset vector */
riscv_setup_rom_reset_vec(machine, &s->soc0, start_addr, fdt_load_addr, machine->fdt);
start_addr (0x80000000) をリセットベクタにセット
code:hw/riscv/boot.c
/* reset vector */
0x00000297, /* 1: auipc t0, %pcrel_hi(fw_dyn) */
0x02828613, /* addi a2, t0, %pcrel_lo(1b) */
0xf1402573, /* csrr a0, mhartid */
0,
0,
0x00028067, /* jr t0 */
start_addr, /* start: .dword */
start_addr_hi32,
fdt_load_addr, /* fdt_laddr: .dword */
fdt_load_addr_hi32,
/* fw_dyn: */
};
code:hw/riscv/boot.c
rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),
rom_base, &address_space_memory);
メモリ上のリセットベクタを眺める
code:sh
$ qemu-system-riscv64 -machine virt -bios none -nographic
(Ctrl+a c を押してモニタを起動)
QEMU 6.2.0 monitor - type 'help' for more information
(リセットベクタの中の命令を眺める)
(qemu) x/10i 0x1000
0x0000000000001000: 00000297 auipc t0,0 # 0x1000
0x0000000000001004: 02828613 addi a2,t0,40
0x0000000000001008: f1402573 csrrs a0,mhartid,zero
0x000000000000100c: 0202b583 ld a1,32(t0)
0x0000000000001010: 0182b283 ld t0,24(t0)
0x0000000000001014: 00028067 jr t0
0x0000000000001018: 0000 illegal
0x000000000000101a: 8000 illegal
0x000000000000101c: 0000 illegal
0x000000000000101e: 0000 illegal
code:target/riscv/cpu_bits.h
/* Default Reset Vector adress */
code:target/riscv/cpu.c
static void rv32_imafcu_nommu_cpu_init(Object *obj)
{
CPURISCVState *env = &RISCV_CPU(obj)->env;
set_misa(env, MXL_RV32, RVI | RVM | RVA | RVF | RVC | RVU);
set_priv_version(env, PRIV_VERSION_1_10_0);
set_resetvec(env, DEFAULT_RSTVEC);
qdev_prop_set_bit(DEVICE(obj), "mmu", false);
}
code:target/riscv/cpu.c
static void set_resetvec(CPURISCVState *env, target_ulong resetvec)
{
env->resetvec = resetvec;
}
code:target/riscv/cpu.c
static void riscv_cpu_reset(DeviceState *dev)
....
env->pc = env->resetvec;