QEMUの続き。独自ターゲットでビルドしてみる。
次に追加したのはmyriscvx_cpu_fp_enable()
である。これはどうも浮動小数点を使用したかを確認するらしい。浮動小数点命令が有効になっていても、mstatus.fs
が0になっていればfalseになる。つまりこれはコンテキストスイッチの時に使用するということだろうか。
qemu/target/myriscvx/cpu_helper.c
/* Return true is floating point support is currently enabled */ bool myriscvx_cpu_fp_enabled(CPUMYRISCVXState *env) { if (env->mstatus & MSTATUS_FS) { if (myriscvx_cpu_virt_enabled(env) && !(env->mstatus_hs & MSTATUS_FS)) { return false; } return true; } return false; } bool myriscvx_cpu_virt_enabled(CPUMYRISCVXState *env) { return get_field(env->virt, VIRT_ONOFF); }
さらにCPUMYRISCVXState
に各種CSRを追加する。mhartid
とmstatus
を追加する。
typedef struct CPUMYRISCVXState { target_ulong gpr[32]; target_ulong pc; target_ulong mhartid; target_ulong mstatus; ...
MSTATUS_FS
等の定数はriscv/cpu_bits.h
から拝借した。
/* MYRISCVX ISA constants */ #ifndef TARGET_MYRISCVX_CPU_BITS_H #define TARGET_MYRISCVX_CPU_BITS_H /* mstatus CSR bits */ #define MSTATUS_UIE 0x00000001 #define MSTATUS_SIE 0x00000002 #define MSTATUS_MIE 0x00000008 #define MSTATUS_UPIE 0x00000010 #define MSTATUS_SPIE 0x00000020 #define MSTATUS_MPIE 0x00000080 #define MSTATUS_SPP 0x00000100 #define MSTATUS_MPP 0x00001800 #define MSTATUS_FS 0x00006000 #define MSTATUS_XS 0x00018000 #define MSTATUS_MPRV 0x00020000 #define MSTATUS_PUM 0x00040000 /* until: priv-1.9.1 */ #define MSTATUS_SUM 0x00040000 /* since: priv-1.10 */ #define MSTATUS_MXR 0x00080000 #define MSTATUS_VM 0x1F000000 /* until: priv-1.9.1 */ #define MSTATUS_TVM 0x00100000 /* since: priv-1.10 */ #define MSTATUS_TW 0x20000000 /* since: priv-1.10 */ #define MSTATUS_TSR 0x40000000 /* since: priv-1.10 */ #define MSTATUS_MTL 0x4000000000ULL #define MSTATUS_MPV 0x8000000000ULL #endif // TARGET_MYRISCVX_CPU_BITS_H
さて、このあたりからtranslate
系の関数が足りないと言われてくる。
/usr/bin/ld: accel/tcg/translate-all.o: in function `tb_gen_code': /home/msyksphinz/work/riscv/qemu/accel/tcg/translate-all.c:1718: undefined reference to `gen_intermediate_code' collect2: error: ld returned 1 exit status make[1]: *** [Makefile:208: qemu-system-myriscvx64] Error 1 make[1]: Target 'all' not remade because of errors. make: *** [Makefile:527: myriscvx64-softmmu/all] Error 2
gen_intermediate_code()
はtranslate-all.c
で使用されているようだ。
qemu/accel/tcg/translate-all.c
/* Called with mmap_lock held for user mode emulation. */ TranslationBlock *tb_gen_code(CPUState *cpu, target_ulong pc, target_ulong cs_base, uint32_t flags, int cflags) { ... tcg_ctx->cpu = env_cpu(env); gen_intermediate_code(cpu, tb, max_insns); tcg_ctx->cpu = NULL; ...
RISC-V用の実装では、以下のように定義されていた。
qemu/target/riscv/translate.c
static const TranslatorOps riscv_tr_ops = { .init_disas_context = riscv_tr_init_disas_context, .tb_start = riscv_tr_tb_start, .insn_start = riscv_tr_insn_start, .breakpoint_check = riscv_tr_breakpoint_check, .translate_insn = riscv_tr_translate_insn, .tb_stop = riscv_tr_tb_stop, .disas_log = riscv_tr_disas_log, }; void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int max_insns) { DisasContext ctx; translator_loop(&riscv_tr_ops, &ctx.base, cs, tb, max_insns); }
なるほど、ここではtranslator
のための関数群を実装しなければならないということか。
/** * TranslatorOps: * @init_disas_context: * Initialize the target-specific portions of DisasContext struct. * The generic DisasContextBase has already been initialized. * * @tb_start: * Emit any code required before the start of the main loop, * after the generic gen_tb_start(). * * @insn_start: * Emit the tcg_gen_insn_start opcode. * * @breakpoint_check: * When called, the breakpoint has already been checked to match the PC, * but the target may decide the breakpoint missed the address * (e.g., due to conditions encoded in their flags). Return true to * indicate that the breakpoint did hit, in which case no more breakpoints * are checked. If the breakpoint did hit, emit any code required to * signal the exception, and set db->is_jmp as necessary to terminate * the main loop. * * @translate_insn: * Disassemble one instruction and set db->pc_next for the start * of the following instruction. Set db->is_jmp as necessary to * terminate the main loop. * * @tb_stop: * Emit any opcodes required to exit the TB, based on db->is_jmp. * * @disas_log: * Print instruction disassembly to log. */
init_disas_context()
:DisasContext
構造体のターゲット固有の実装について初期化を行う。一般的なDisasContextBase
の部分は既に初期化されている。
static void riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs) { DisasContext *ctx = container_of(dcbase, DisasContext, base); CPURISCVState *env = cs->env_ptr; RISCVCPU *cpu = RISCV_CPU(cs); ctx->pc_succ_insn = ctx->base.pc_first; ctx->mem_idx = ctx->base.tb->flags & TB_FLAGS_MMU_MASK; ctx->mstatus_fs = ctx->base.tb->flags & TB_FLAGS_MSTATUS_FS; ctx->priv_ver = env->priv_ver; #if !defined(CONFIG_USER_ONLY) if (riscv_has_ext(env, RVH)) { ctx->virt_enabled = riscv_cpu_virt_enabled(env); if (env->priv_ver == PRV_M && get_field(env->mstatus, MSTATUS_MPRV) && MSTATUS_MPV_ISSET(env)) { ctx->virt_enabled = true; } else if (env->priv == PRV_S && !riscv_cpu_virt_enabled(env) && get_field(env->hstatus, HSTATUS_SPRV) && get_field(env->hstatus, HSTATUS_SPV)) { ctx->virt_enabled = true; } } else { ctx->virt_enabled = false; } #else ctx->virt_enabled = false; #endif ctx->misa = env->misa; ctx->frm = -1; /* unknown rounding mode */ ctx->ext_ifencei = cpu->cfg.ext_ifencei; }
tb_start()
:メインループを出力する前に必要な任意のコードを出力する。gen_tb_start()
の後に呼び出される。
static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu) { }
insn_start()
:tcg_gen_insn_start
オペコードを出力する。
static void riscv_tr_insn_start(DisasContextBase *dcbase, CPUState *cpu) { DisasContext *ctx = container_of(dcbase, DisasContext, base); tcg_gen_insn_start(ctx->base.pc_next); }
breakpoint_check()
:この関数が呼ばれると、ブレークポイントが現在のPCとマッチしているかがチェックされるが、ターゲットはアドレスがマッチしないと判定することがある(例えば、フラグによりエンコードされる条件などによる)。ブレークポイントにヒットするとtrueが返され、それ以上のブレークポイントのチェックは行われない。ブレークポイントにヒットすると、例外が出力されれメインループから抜けるためにdb->is_jmp
を設定する必要がある。
static bool riscv_tr_breakpoint_check(DisasContextBase *dcbase, CPUState *cpu, const CPUBreakpoint *bp) { DisasContext *ctx = container_of(dcbase, DisasContext, base); tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next); ctx->base.is_jmp = DISAS_NORETURN; gen_exception_debug(); /* The address covered by the breakpoint must be included in [tb->pc, tb->pc + tb->size) in order to for it to be properly cleared -- thus we increment the PC here so that the logic setting tb->size below does the right thing. */ ctx->base.pc_next += 4; return true; }
translate_insn()
:命令をディスアセンブルしdb->pc_next
を設定し次の命令の実行を許可する。メインループを抜けるためにはdb->is_jmp
を設定する必要がある。
static void riscv_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu) { DisasContext *ctx = container_of(dcbase, DisasContext, base); CPURISCVState *env = cpu->env_ptr; uint16_t opcode16 = translator_lduw(env, ctx->base.pc_next); decode_opc(env, ctx, opcode16); ctx->base.pc_next = ctx->pc_succ_insn; if (ctx->base.is_jmp == DISAS_NEXT) { target_ulong page_start; page_start = ctx->base.pc_first & TARGET_PAGE_MASK; if (ctx->base.pc_next - page_start >= TARGET_PAGE_SIZE) { ctx->base.is_jmp = DISAS_TOO_MANY; } } }
tb_stop()
:TBを終了させるためのオペコードを出力する。
static void riscv_tr_tb_stop(DisasContextBase *dcbase, CPUState *cpu) { DisasContext *ctx = container_of(dcbase, DisasContext, base); switch (ctx->base.is_jmp) { case DISAS_TOO_MANY: gen_goto_tb(ctx, 0, ctx->base.pc_next); break; case DISAS_NORETURN: break; default: g_assert_not_reached(); } }
disas_log()
:ログに命令のディスアセンブリの記録を出力する。
static void riscv_tr_disas_log(const DisasContextBase *dcbase, CPUState *cpu) { #ifndef CONFIG_USER_ONLY RISCVCPU *rvcpu = RISCV_CPU(cpu); CPURISCVState *env = &rvcpu->env; #endif qemu_log("IN: %s\n", lookup_symbol(dcbase->pc_first)); #ifndef CONFIG_USER_ONLY qemu_log("Priv: "TARGET_FMT_ld"; Virt: "TARGET_FMT_ld"\n", env->priv, env->virt); #endif log_target_disas(cpu, dcbase->pc_first, dcbase->tb->size); }