とりあえずQEMUのブートコードだけ動かしたいので、次はCSR命令を実装しなければならない。RISC-VのCSR命令がQEMUでどのように実行されているかを解析しよう。
まず、CSR命令をinsn32.decode
に追加する。これでCSR命令のデコーダが自動的に追加され、trans_xxx
関数を追加する必要性が発生する。
qemu/target/myriscvx/insn32.decode
@csr ............ ..... ... ..... ....... %csr %rs1 %rd csrrw ............ ..... 001 ..... 1110011 @csr csrrs ............ ..... 010 ..... 1110011 @csr csrrc ............ ..... 011 ..... 1110011 @csr csrrwi ............ ..... 101 ..... 1110011 @csr csrrsi ............ ..... 110 ..... 1110011 @csr csrrci ............ ..... 111 ..... 1110011 @csr
qemu/target/myriscvx/insn_trans/trans_rvi.inc.c
static bool trans_csrrw(DisasContext *ctx, arg_csrrw *a) { TCGv source1, csr_store, dest, rs1_pass; MYRISCVX_OP_CSR_PRE; gen_helper_csrrw(dest, cpu_env, source1, csr_store); MYRISCVX_OP_CSR_POST; return true; }
で、このtrans_csrrw()
の中身だが、TCGvというのはTiny Code Generatorの変数であるとして、MYRISCVX_OP_CSR_PRE
とMYRISCVX_OP_CSR_POST
はマクロである。すぐ上にその定義があるが、正直良く意味が分からない。
#define MYRISCVX_OP_CSR_PRE do { \ source1 = tcg_temp_new(); \ csr_store = tcg_temp_new(); \ dest = tcg_temp_new(); \ rs1_pass = tcg_temp_new(); \ gen_get_gpr(source1, a->rs1); \ tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next); \ tcg_gen_movi_tl(rs1_pass, a->rs1); \ tcg_gen_movi_tl(csr_store, a->csr); \ gen_io_start(); \ } while (0) #define MYRISCVX_OP_CSR_POST do { \ gen_set_gpr(a->rd, dest); \ tcg_gen_movi_tl(cpu_pc, ctx->pc_succ_insn); \ exit_tb(ctx); \ ctx->base.is_jmp = DISAS_NORETURN; \ tcg_temp_free(source1); \ tcg_temp_free(csr_store); \ tcg_temp_free(dest); \ tcg_temp_free(rs1_pass); \ } while (0)
gen_helper_csrrw()
だが、これは一応関数定義上どこにも存在しないので最初は少しびっくりする。実はマクロで定義されており、
qemu/target/myriscvx/helper.h
/* Special functions */
DEF_HELPER_3(csrrw, tl, env, tl, tl)
DEF_HELPER_4(csrrs, tl, env, tl, tl, tl)
DEF_HELPER_4(csrrc, tl, env, tl, tl, tl)
このDEF_HELPER
マクロがgen_helper_csrrw()
を定義している。このマクロは最終的にtcg_gen_callN(HELPER(csrrw))
を呼び出している。このHELPE()
マクロは、
#define HELPER(name) glue(helper_, name)
なので、結局helper_csrrw()
を呼び出していることになる。これはqemu/target/myriscvx/op_helper.c
に定義した。
qemu/target/myriscvx/op_helper.c
target_ulong helper_csrrw(CPUMYRISCVXState *env, target_ulong src, target_ulong csr) { target_ulong val = 0; if (myriscvx_csrrw(env, csr, &val, src, -1) < 0) { myriscvx_raise_exception(env, MYRISCVX_EXCP_ILLEGAL_INST, GETPC()); } return val; }
C言語で記述してあるmyriscvx_csrrw()
を呼び出す。これはqemu/target/myriscvx/csr.c
に記述した。
/* * myriscvx_csrrw - read and/or update control and status register * * csrr <-> myriscvx_csrrw(env, csrno, ret_value, 0, 0); * csrrw <-> myriscvx_csrrw(env, csrno, ret_value, value, -1); * csrrs <-> myriscvx_csrrw(env, csrno, ret_value, -1, value); * csrrc <-> myriscvx_csrrw(env, csrno, ret_value, 0, value); */ int myriscvx_csrrw(CPUMYRISCVXState *env, int csrno, target_ulong *ret_value, target_ulong new_value, target_ulong write_mask) { int ret; target_ulong old_value; ...
で、ここで命令実行に失敗していたのでGDBを当てて観察していたのだが、CSR拡張のフラグチェックの部分をまじめに実装していなかったので命令実行に失敗していたらしい。とりあえず省略。
/* ensure the CSR extension is enabled. */ // if (!cpu->cfg.ext_icsr) { // return -1; // }
QEMUにおいてRISC-VのCSRがどのように実装してあるかという話だが、CSRのオペレーションを各CSRで定義し、これをリスト化してある。
/* CSR function table */ static myriscvx_csr_operations csr_ops[]; /* CSR function table constants */ enum { CSR_TABLE_SIZE = 0x1000 };
CSRのオペレーションはとりあえず最小限必要なmhartid
だけ用意した。
/* Control and Status Register function table */ static myriscvx_csr_operations csr_ops[CSR_TABLE_SIZE] = { [CSR_MHARTID] = { any, read_mhartid }, };
read_mhartid()
という関数を定義する。これがMHARTID
レジスタを読み出す動作になる。
static int read_mhartid(CPUMYRISCVXState *env, int csrno, target_ulong *val) { *val = env->mhartid; return 0; }
これで一応CSR命令は通過することが確認できた。でもまだ命令トレースが出てこないなあ。どのように出力されているのか確認したい。
QEMU 5.0.0 monitor - type 'help' for more information (qemu) ---------------- IN: Priv: 3; Virt: 0 0x0000000000001000: OBJD-T: 9702000093850202732540f1 ---------------- IN: Priv: 3; Virt: 0 0x000000000000100c: OBJD-T: 83b2820167800200 ---------------- IN: Priv: 3; Virt: 0 0x0000000080000000: OBJD-T: 6f00c004 ---------------- ...