FPGA開発日記

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

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

QEMUの続き。独自ターゲットでビルドしてみる。myriscvx64コンフィグレーションを用意した。

MYRISCVXCPUCPUMYRISCVXStateを包むラッパーのようなものらしい。envメンバ変数としてMYRISCVXStateを含んでおり、cfgメンバはRISC-Vのオプションを指定できるようになっている。

  • qemu/target/myriscvx/cpu.h
/**
 * MYRISCVXCPU:
 * @env: #CPUMYRISCVXState
 *
 * A MYRISCVX CPU.
 */
typedef struct MYRISCVXCPU {
    /*< private >*/
    CPUState parent_obj;
    /*< public >*/
    CPUNegativeOffsetState neg;
    CPUMYRISCVXState env;

    /* Configuration Settings */
    struct {
        bool ext_i;
        bool ext_e;
        bool ext_g;
        bool ext_m;
        bool ext_a;
        bool ext_f;
        bool ext_d;
        bool ext_c;
        bool ext_s;
        bool ext_u;
        bool ext_h;
        bool ext_counters;
        bool ext_ifencei;
        bool ext_icsr;

        char *priv_spec;
        char *user_spec;
        bool mmu;
        bool pmp;
    } cfg;
} MYRISCVXCPU;

次はCPU_RESOLVING_TYPEというマクロを定義しなければならないらしい。ここでは"myriscvx-cpu"という文字列を定義している。

  • target/myriscvx/cpu.h
#define TYPE_MYRISCVX_CPU "myriscvx-cpu"

#define MYRISCVX_CPU_TYPE_SUFFIX "-" TYPE_MYRISCVX_CPU
#define MYRISCVX_CPU_TYPE_NAME(name) (name MYRISCVX_CPU_TYPE_SUFFIX)
#define CPU_RESOLVING_TYPE TYPE_MYRISCVX_CPU

#define TYPE_MYRISCVX_CPU_ANY    MYRISCVX_CPU_TYPE_NAME("any")
#define TYPE_MYRISCVX_CPU_BASE64 MYRISCVX_CPU_TYPE_NAME("myriscvx64")

次はMYRISCVXのhelper.hを定義しなければならない。これはいったい何だろう?

riscv/helper.hでは以下のようなマクロが定義されている。

  • qemu/target/riscv/helper.h
/* Exceptions */
DEF_HELPER_2(raise_exception, noreturn, env, i32)

/* Floating Point - rounding mode */
DEF_HELPER_FLAGS_2(set_rounding_mode, TCG_CALL_NO_WG, void, env, i32)

/* Floating Point - fused */
DEF_HELPER_FLAGS_4(fmadd_s, TCG_CALL_NO_RWG, i64, env, i64, i64, i64)
DEF_HELPER_FLAGS_4(fmadd_d, TCG_CALL_NO_RWG, i64, env, i64, i64, i64)
DEF_HELPER_FLAGS_4(fmsub_s, TCG_CALL_NO_RWG, i64, env, i64, i64, i64)

これはいったいどういう定義になっているんだろう?

  • qemu/include/exec/helper-head.h
#define DEF_HELPER_2(name, ret, t1, t2) \
    DEF_HELPER_FLAGS_2(name, 0, ret, t1, t2)
  • qemu/include/exec/helper-gen.h
#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2)                    \
static inline void glue(gen_helper_, name)(dh_retvar_decl(ret)          \
    dh_arg_decl(t1, 1), dh_arg_decl(t2, 2))                             \
{                                                                       \
  TCGTemp *args[2] = { dh_arg(t1, 1), dh_arg(t2, 2) };                  \
  tcg_gen_callN(HELPER(name), dh_retvar(ret), 2, args);                 \
}

#define DEF_HELPER_FLAGS_4(name, flags, ret, t1, t2, t3, t4)            \
static inline void glue(gen_helper_, name)(dh_retvar_decl(ret)          \
    dh_arg_decl(t1, 1), dh_arg_decl(t2, 2),                             \
    dh_arg_decl(t3, 3), dh_arg_decl(t4, 4))                             \
{                                                                       \
  TCGTemp *args[4] = { dh_arg(t1, 1), dh_arg(t2, 2),                    \
                     dh_arg(t3, 3), dh_arg(t4, 4) };                    \
  tcg_gen_callN(HELPER(name), dh_retvar(ret), 4, args);                 \
}

例えばException定義であれば、以下のように変換されている。これはヘルプ関数を定義しているのだろうか?

DEF_HELPER_2(raise_exception, noreturn, env, i32)
// -->
DEF_HELPER_FLAGS_2(raise_exception, 0, noreturn, env, i32)
// -->
#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2)                    \
static inline void glue(gen_helper_, raise_exception)(dh_retvar_decl(noreturn)          \
    dh_arg_decl(env, 1), dh_arg_decl(i32, 2))                             \
{                                                                       \
  TCGTemp *args[2] = { dh_arg(env, 1), dh_arg(i32, 2) };                  \
  tcg_gen_callN(HELPER(raise_exception), dh_retvar(noreturn), 2, args);                 \
}

とりあえず空白のhelper.hを作成して次に進む。次はQEMU_ARCHの定義が必要だ。これはarch_init.cに定義されている。

diff --git a/arch_init.c b/arch_init.c
index d9eb0ec1dd..f4690ff1f0 100644
--- a/arch_init.c
+++ b/arch_init.c
@@ -91,6 +91,8 @@ int graphic_depth = 32;
 #define QEMU_ARCH QEMU_ARCH_UNICORE32
 #elif defined(TARGET_XTENSA)
 #define QEMU_ARCH QEMU_ARCH_XTENSA
+#elif defined(TARGET_MYRISCVX)
+#define QEMU_ARCH QEMU_ARCH_MYRISCVX
 #endif

 const uint32_t arch_type = QEMU_ARCH;

diff --git a/include/sysemu/arch_init.h b/include/sysemu/arch_init.h
index 71a7a285ee..316530e68a 100644
--- a/include/sysemu/arch_init.h
+++ b/include/sysemu/arch_init.h
@@ -25,6 +25,7 @@ enum {
     QEMU_ARCH_HPPA = (1 << 18),
     QEMU_ARCH_RISCV = (1 << 19),
     QEMU_ARCH_RX = (1 << 20),
+    QEMU_ARCH_MYRISCVX = (1 << 21),

     QEMU_ARCH_NONE = (1 << 31),
 };

さらにコンパイルを進めると今度はtranslate.cの定義が必要な関数群が無いと言われている。

/usr/bin/ld: accel/tcg/translate-all.o: in function `cpu_restore_state_from_tb':
/home/msyksphinz/work/riscv/qemu/accel/tcg/translate-all.c:372: undefined reference to `restore_state_to_opc'
/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'
/usr/bin/ld: accel/tcg/translate-all.o: in function `tb_check_watchpoint':
/home/msyksphinz/work/riscv/qemu/accel/tcg/translate-all.c:2144: undefined reference to `cpu_get_tb_cpu_state'
/usr/bin/ld: accel/tcg/cputlb.o: in function `get_page_addr_code_hostp':
/home/msyksphinz/work/riscv/qemu/accel/tcg/cputlb.c:1165: undefined reference to `cpu_mmu_index'
/usr/bin/ld: accel/tcg/cputlb.o: in function `cpu_ldub_data_ra':
void restore_state_to_opc(CPURXState *env, TranslationBlock *tb,
                          target_ulong *data)
{
    env->pc = data[0];
}

次はcpu_get_tb_cpu_state()これはおそらく例外発生時のCPUの情報を退避するためのものだと思う。

  • qemu/accel/tcg/translate-all.c
    } else {
        /* The exception probably happened in a helper.  The CPU state should
           have been saved before calling it. Fetch the PC from there.  */
        CPUArchState *env = cpu->env_ptr;
        target_ulong pc, cs_base;
        tb_page_addr_t addr;
        uint32_t flags;

        cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags);
...
  • qemu/target/riscv/cpu.h
static inline void cpu_get_tb_cpu_state(CPURISCVState *env, target_ulong *pc,
                                        target_ulong *cs_base, uint32_t *flags)
{
    *pc = env->pc;
    *cs_base = 0;
#ifdef CONFIG_USER_ONLY
    *flags = TB_FLAGS_MSTATUS_FS;
#else
    *flags = cpu_mmu_index(env, 0);
    if (riscv_cpu_fp_enabled(env)) {
        *flags |= env->mstatus & MSTATUS_FS;
    }
#endif
}

これをMYRISCVXに追加する。

static inline void cpu_get_tb_cpu_state(CPUMYRISCVXState *env, target_ulong *pc,
                                        target_ulong *cs_base, uint32_t *flags)
{
    *pc = env->pc;
    *cs_base = 0;
#ifdef CONFIG_USER_ONLY
    *flags = TB_FLAGS_MSTATUS_FS;
#else
    *flags = cpu_mmu_index(env, 0);
    if (riscv_cpu_fp_enabled(env)) {
        *flags |= env->mstatus & MSTATUS_FS;
    }
#endif
}


int myriscvx_cpu_mmu_index(CPUMYRISCVXState *env, bool ifetch)
{
#ifdef CONFIG_USER_ONLY
    return 0;
#else
    return env->priv;
#endif
}