FPGA開発日記

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

QEMUに入門してみる(11. QEMUでのコールバック関数追加方法の調査)

QEMUの続き。QEMUのRISC-V対応においてバイナリのシミュレーション方法とログの出し方が分かったわけだが、これをMYRISCVXに移植したい。つまり同じコマンドをqemu-system-myriscvx64に適用することを考える。

./qemu-system-myriscvx64 --machine none --d in_asm --nographic \
    --kernel ./riscv-tests/isa/rv64ui-p-simple
qemu-system-myriscvx64: The -kernel parameter is not supported (use the generic 'loader' device instead).

あれ、何か必要なのか。loaderデバイスとは何だろう。

--machine noneだとおそらく問題で、システム用のQEMUなので--machineで何かしらのマシン構成を追加しなければならないのか。RISC-Vの実装を真似てvirt.cを実装している。

  • qemu/hw/myriscvx/virt.c
static void myriscvx_virt_machine_init_register_types(void)
{
  type_register_static(&myriscvx_virt_machine_typeinfo);
}

type_init(myriscvx_virt_machine_init_register_types)

myriscvx_virt_machine_init_register_types構造体を定義しなければならないらしい。

static const TypeInfo myriscvx_virt_machine_typeinfo = {
  .name          = MACHINE_TYPE_NAME("virt"),
  .parent        = TYPE_MACHINE,
  .class_init    = myriscvx_virt_machine_class_init,
  .instance_init = myriscvx_virt_machine_instance_init,
  .instance_size = sizeof(MYRISCVXVirtState),
};

myriscvx_virt_machine_class_init()の実装は以下のようになっており、myriscvx_virt_board_init()を呼び出す。

static void myriscvx_virt_machine_class_init(ObjectClass *oc, void *data)
{
  MachineClass *mc = MACHINE_CLASS(oc);

  mc->desc = "RISC-V VirtIO board";
  mc->init = myriscvx_virt_board_init;
  mc->max_cpus = 8;
  mc->default_cpu_type = VIRT_CPU;
  mc->pci_allow_0_address = true;
}
static void myriscvx_virt_board_init(MachineState *machine)
{
  const struct MemmapEntry *memmap = virt_memmap;
  MYRISCVXVirtState *s = MYRISCVX_VIRT_MACHINE(machine);
  MemoryRegion *system_memory = get_system_memory();
  MemoryRegion *main_mem = g_new(MemoryRegion, 1);
  MemoryRegion *mask_rom = g_new(MemoryRegion, 1);
  char *plic_hart_config;
  size_t plic_hart_config_len;
  target_ulong start_addr = memmap[VIRT_DRAM].base;
  int i;
  unsigned int smp_cpus = machine->smp.cpus;
...
  • SoCの初期化を行うルーチン。何だか良く分からない。
  /* Initialize SOC */
  object_initialize_child(OBJECT(machine), "soc", &s->soc, sizeof(s->soc),
                          TYPE_MYRISCVX_HART_ARRAY, &error_abort, NULL);
  object_property_set_str(OBJECT(&s->soc), machine->cpu_type, "cpu-type",
                          &error_abort);
  object_property_set_int(OBJECT(&s->soc), smp_cpus, "num-harts",
                          &error_abort);
  object_property_set_bool(OBJECT(&s->soc), true, "realized",
                           &error_abort);
  • メインメモリの定義。
  /* register system main memory (actual RAM) */
  memory_region_init_ram(main_mem, NULL, "myriscvx_virt_board.ram",
                         machine->ram_size, &error_fatal);
  memory_region_add_subregion(system_memory, memmap[VIRT_DRAM].base,
                              main_mem);
  • デバイスツリーを作成する。
  /* create device tree */
  create_fdt(s, memmap, machine->ram_size, machine->kernel_cmdline);
  • BootROMを作成する。
  /* boot rom */
  memory_region_init_rom(mask_rom, NULL, "myriscvx_virt_board.mrom",
                         memmap[VIRT_MROM].size, &error_fatal);
  memory_region_add_subregion(system_memory, memmap[VIRT_MROM].base,
                              mask_rom);
  • リセットベクタの定義。
  /* reset vector */
  uint32_t reset_vec[8] = {
    0x00000297,                  /* 1:  auipc  t0, %pcrel_hi(dtb) */
    0x02028593,                  /*     addi   a1, t0, %pcrel_lo(1b) */
    0xf1402573,                  /*     csrr   a0, mhartid  */
#if defined(TARGET_MYRISCVX32)
    0x0182a283,                  /*     lw     t0, 24(t0) */
#elif defined(TARGET_MYRISCVX64)
    0x0182b283,                  /*     ld     t0, 24(t0) */
#endif
    0x00028067,                  /*     jr     t0 */
    0x00000000,
    start_addr,                  /* start: .dword */
    0x00000000,
    /* dtb: */
  };

  /* copy in the reset vector in little_endian byte order */
  for (i = 0; i < sizeof(reset_vec) >> 2; i++) {
    reset_vec[i] = cpu_to_le32(reset_vec[i]);
  }
  rom_add_blob_fixed_as("mrom.reset", reset_vec, sizeof(reset_vec),
                        memmap[VIRT_MROM].base, &address_space_memory);

で、KconfigVIRTを定義する。

  • qemu/hw/myriscvx/Kconfig
config HART
    bool

config VIRT
    bool
    select MSI_NONBROKEN
    select HART
  • qemu/hw/myriscvx/Makefile.objs
obj-y += boot.o

obj-$(CONFIG_VIRT) += virt.o
obj-$(CONFIG_HART) += myriscvx_hart.o

ここまで実装して、ビルドして実行してみる。

./qemu-system-myriscvx64 --machine virt --d in_asm --nographic --kernel ./rv64ui-p-simple
qemu-system-myriscvx64: missing object type 'myriscvx64-myriscvx-cpu'
$ ag myriscvx-cpu
target/myriscvx/cpu.h
26:#define TYPE_MYRISCVX_CPU "myriscvx-cpu"

うーん、これはどうやって実装したらいいんだろう?

色々見ていると、type_tableにCPUのTypeを登録するらしい。

  • qemu/qom/object.c
void object_initialize(void *data, size_t size, const char *typename)
{
    TypeImpl *type = type_get_by_name(typename);

    if (!type) {
        error_report("missing object type '%s'", typename);
        abort();
    }

    object_initialize_with_type(data, size, type);
}

static TypeImpl *type_get_by_name(const char *name)
{
    if (name == NULL) {
        return NULL;
    }

    return type_table_lookup(name);
}

static TypeImpl *type_table_lookup(const char *name)
{
    return g_hash_table_lookup(type_table_get(), name);
}

static GHashTable *type_table_get(void)
{
    static GHashTable *type_table;

    if (type_table == NULL) {
        type_table = g_hash_table_new(g_str_hash, g_str_equal);
    }

    return type_table;
}

これを追いかけていると、どうもcpu.cの中でCPUを定義して登録しなければならないらしい。

#define DEFINE_CPU(type_name, initfn)      \
    {                                      \
        .name = type_name,                 \
        .parent = TYPE_RISCV_CPU,          \
        .instance_init = initfn            \
    }

static const TypeInfo riscv_cpu_type_infos[] = {
    {
        .name = TYPE_RISCV_CPU,
        .parent = TYPE_CPU,
        .instance_size = sizeof(RISCVCPU),
        .instance_init = riscv_cpu_init,
        .abstract = true,
        .class_size = sizeof(RISCVCPUClass),
        .class_init = riscv_cpu_class_init,
    },
    DEFINE_CPU(TYPE_RISCV_CPU_ANY,              riscv_any_cpu_init),
#if defined(TARGET_RISCV32)
    DEFINE_CPU(TYPE_RISCV_CPU_BASE32,           riscv_base32_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_E31,       rv32imacu_nommu_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_U34,       rv32gcsu_priv1_10_0_cpu_init),
    /* Depreacted */
    DEFINE_CPU(TYPE_RISCV_CPU_RV32IMACU_NOMMU,  rv32imacu_nommu_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_RV32GCSU_V1_09_1, rv32gcsu_priv1_09_1_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_RV32GCSU_V1_10_0, rv32gcsu_priv1_10_0_cpu_init)
#elif defined(TARGET_RISCV64)
    DEFINE_CPU(TYPE_RISCV_CPU_BASE64,           riscv_base64_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_E51,       rv64imacu_nommu_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_SIFIVE_U54,       rv64gcsu_priv1_10_0_cpu_init),
    /* Deprecated */
    DEFINE_CPU(TYPE_RISCV_CPU_RV64IMACU_NOMMU,  rv64imacu_nommu_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_RV64GCSU_V1_09_1, rv64gcsu_priv1_09_1_cpu_init),
    DEFINE_CPU(TYPE_RISCV_CPU_RV64GCSU_V1_10_0, rv64gcsu_priv1_10_0_cpu_init)
#endif
};

DEFINE_TYPES(riscv_cpu_type_infos)

一番ベースになっているのはriscv_base64_cpu_initで、これの定義は、以下の関数をriscv-rv64で定義するらしい。

static void riscv_base64_cpu_init(Object *obj)
{
    CPURISCVState *env = &RISCV_CPU(obj)->env;
    /* We set this in the realise function */
    set_misa(env, 0);
}
#define TYPE_RISCV_CPU_BASE64           RISCV_CPU_TYPE_NAME("rv64")

同じように定義してみよう。

  • qemu/target/myriscvx/cpu.c
#define DEFINE_CPU(type_name, initfn)      \
    {                                      \
        .name = type_name,                 \
        .parent = TYPE_MYRISCVX_CPU,          \
        .instance_init = initfn            \
    }

static const TypeInfo myriscvx_cpu_type_infos[] = {
    {
        .name = TYPE_MYRISCVX_CPU,
        .parent = TYPE_CPU,
        .instance_size = sizeof(MYRISCVXCPU),
        .instance_init = myriscvx_cpu_init,
        .abstract = true,
        .class_size = sizeof(MYRISCVXCPUClass),
        .class_init = myriscvx_cpu_class_init,
    },
    DEFINE_CPU(TYPE_MYRISCVX_CPU_BASE64, myriscvx_base64_cpu_init),
};

DEFINE_TYPES(myriscvx_cpu_type_infos)

で、ここで問題になるのがmyriscvx_cpu_class_init()が非常に大きい。うーん、これを1つずつ理解しながら実装するのは大変だな...

static void myriscvx_cpu_class_init(ObjectClass *c, void *data)
{
  MYRISCVXCPUClass *mcc = MYRISCVX_CPU_CLASS(c);
  CPUClass *cc = CPU_CLASS(c);
  DeviceClass *dc = DEVICE_CLASS(c);

  device_class_set_parent_realize(dc, myriscvx_cpu_realize,
                                  &mcc->parent_realize);

  device_class_set_parent_reset(dc, myriscvx_cpu_reset, &mcc->parent_reset);

  cc->class_by_name = myriscvx_cpu_class_by_name;
  cc->has_work = myriscvx_cpu_has_work;
  // cc->do_interrupt = myriscvx_cpu_do_interrupt;
  // cc->cpu_exec_interrupt = myriscvx_cpu_exec_interrupt;
  cc->dump_state = myriscvx_cpu_dump_state;
  cc->set_pc = myriscvx_cpu_set_pc;
  cc->synchronize_from_tb = myriscvx_cpu_synchronize_from_tb;
  // cc->gdb_read_register = myriscvx_cpu_gdb_read_register;
...

このCPUClassに、コールバック関数をどんどん追加していかなければならない。大変だ。。。