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);
で、Kconfig
にVIRT
を定義する。
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
に、コールバック関数をどんどん追加していかなければならない。大変だ。。。