FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

QEMUに入門してみる(3. 独自コンフィグレーションを用意して必要なファイルを確認)

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を追加する。mhartidmstatusを追加する。

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);
}