/* * SPDX-License-Identifier: GPL-2.0-or-later * * QEMU Virtual M68K Machine * * (c) 2020 Laurent Vivier * */ #include "qemu/osdep.h" #include "qemu/units.h" #include "qemu/guest-random.h" #include "sysemu/sysemu.h" #include "cpu.h" #include "hw/boards.h" #include "hw/qdev-properties.h" #include "elf.h" #include "hw/loader.h" #include "ui/console.h" #include "hw/sysbus.h" #include "standard-headers/asm-m68k/bootinfo.h" #include "standard-headers/asm-m68k/bootinfo-virt.h" #include "bootinfo.h" #include "net/net.h" #include "qapi/error.h" #include "qemu/error-report.h" #include "sysemu/qtest.h" #include "sysemu/runstate.h" #include "sysemu/reset.h" #include "hw/intc/m68k_irqc.h" #include "hw/misc/virt_ctrl.h" #include "hw/char/goldfish_tty.h" #include "hw/rtc/goldfish_rtc.h" #include "hw/intc/goldfish_pic.h" #include "hw/virtio/virtio-mmio.h" #include "hw/virtio/virtio-blk.h" /* * 6 goldfish-pic for CPU IRQ #1 to IRQ #6 * CPU IRQ #1 -> PIC #1 * IRQ #1 to IRQ #31 -> unused * IRQ #32 -> goldfish-tty * CPU IRQ #2 -> PIC #2 * IRQ #1 to IRQ #32 -> virtio-mmio from 1 to 32 * CPU IRQ #3 -> PIC #3 * IRQ #1 to IRQ #32 -> virtio-mmio from 33 to 64 * CPU IRQ #4 -> PIC #4 * IRQ #1 to IRQ #32 -> virtio-mmio from 65 to 96 * CPU IRQ #5 -> PIC #5 * IRQ #1 to IRQ #32 -> virtio-mmio from 97 to 128 * CPU IRQ #6 -> PIC #6 * IRQ #1 -> goldfish-rtc * IRQ #2 to IRQ #32 -> unused * CPU IRQ #7 -> NMI */ #define PIC_IRQ_BASE(num) (8 + (num - 1) * 32) #define PIC_IRQ(num, irq) (PIC_IRQ_BASE(num) + irq - 1) #define PIC_GPIO(pic_irq) (qdev_get_gpio_in(pic_dev[(pic_irq - 8) / 32], \ (pic_irq - 8) % 32)) #define VIRT_GF_PIC_MMIO_BASE 0xff000000 /* MMIO: 0xff000000 - 0xff005fff */ #define VIRT_GF_PIC_IRQ_BASE 1 /* IRQ: #1 -> #6 */ #define VIRT_GF_PIC_NB 6 /* 2 goldfish-rtc (and timer) */ #define VIRT_GF_RTC_MMIO_BASE 0xff006000 /* MMIO: 0xff006000 - 0xff007fff */ #define VIRT_GF_RTC_IRQ_BASE PIC_IRQ(6, 1) /* PIC: #6, IRQ: #1 */ #define VIRT_GF_RTC_NB 2 /* 1 goldfish-tty */ #define VIRT_GF_TTY_MMIO_BASE 0xff008000 /* MMIO: 0xff008000 - 0xff008fff */ #define VIRT_GF_TTY_IRQ_BASE PIC_IRQ(1, 32) /* PIC: #1, IRQ: #32 */ /* 1 virt-ctrl */ #define VIRT_CTRL_MMIO_BASE 0xff009000 /* MMIO: 0xff009000 - 0xff009fff */ #define VIRT_CTRL_IRQ_BASE PIC_IRQ(1, 1) /* PIC: #1, IRQ: #1 */ /* * virtio-mmio size is 0x200 bytes * we use 4 goldfish-pic to attach them, * we can attach 32 virtio devices / goldfish-pic * -> we can manage 32 * 4 = 128 virtio devices */ #define VIRT_VIRTIO_MMIO_BASE 0xff010000 /* MMIO: 0xff010000 - 0xff01ffff */ #define VIRT_VIRTIO_IRQ_BASE PIC_IRQ(2, 1) /* PIC: 2, 3, 4, 5, IRQ: ALL */ typedef struct { M68kCPU *cpu; hwaddr initial_pc; hwaddr initial_stack; } ResetInfo; static void main_cpu_reset(void *opaque) { ResetInfo *reset_info = opaque; M68kCPU *cpu = reset_info->cpu; CPUState *cs = CPU(cpu); cpu_reset(cs); cpu->env.aregs[7] = reset_info->initial_stack; cpu->env.pc = reset_info->initial_pc; } static void rerandomize_rng_seed(void *opaque) { struct bi_record *rng_seed = opaque; qemu_guest_getrandom_nofail((void *)rng_seed->data + 2, be16_to_cpu(*(uint16_t *)rng_seed->data)); } static void virt_init(MachineState *machine) { M68kCPU *cpu = NULL; int32_t kernel_size; uint64_t elf_entry; ram_addr_t initrd_base; int32_t initrd_size; ram_addr_t ram_size = machine->ram_size; const char *kernel_filename = machine->kernel_filename; const char *initrd_filename = machine->initrd_filename; const char *kernel_cmdline = machine->kernel_cmdline; hwaddr parameters_base; DeviceState *dev; DeviceState *irqc_dev; DeviceState *pic_dev[VIRT_GF_PIC_NB]; SysBusDevice *sysbus; hwaddr io_base; int i; ResetInfo *reset_info; uint8_t rng_seed[32]; if (ram_size > 3399672 * KiB) { /* * The physical memory can be up to 4 GiB - 16 MiB, but linux * kernel crashes after this limit (~ 3.2 GiB) */ error_report("Too much memory for this machine: %" PRId64 " KiB, " "maximum 3399672 KiB", ram_size / KiB); exit(1); } reset_info = g_new0(ResetInfo, 1); /* init CPUs */ cpu = M68K_CPU(cpu_create(machine->cpu_type)); reset_info->cpu = cpu; qemu_register_reset(main_cpu_reset, reset_info); /* RAM */ memory_region_add_subregion(get_system_memory(), 0, machine->ram); /* IRQ Controller */ irqc_dev = qdev_new(TYPE_M68K_IRQC); object_property_set_link(OBJECT(irqc_dev), "m68k-cpu", OBJECT(cpu), &error_abort); sysbus_realize_and_unref(SYS_BUS_DEVICE(irqc_dev), &error_fatal); /* * 6 goldfish-pic * * map: 0xff000000 - 0xff006fff = 28 KiB * IRQ: #1 (lower priority) -> #6 (higher priority) * */ io_base = VIRT_GF_PIC_MMIO_BASE; for (i = 0; i < VIRT_GF_PIC_NB; i++) { pic_dev[i] = qdev_new(TYPE_GOLDFISH_PIC); sysbus = SYS_BUS_DEVICE(pic_dev[i]); qdev_prop_set_uint8(pic_dev[i], "index", i); sysbus_realize_and_unref(sysbus, &error_fatal); sysbus_mmio_map(sysbus, 0, io_base); sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(irqc_dev, i)); io_base += 0x1000; } /* goldfish-rtc */ io_base = VIRT_GF_RTC_MMIO_BASE; for (i = 0; i < VIRT_GF_RTC_NB; i++) { dev = qdev_new(TYPE_GOLDFISH_RTC); qdev_prop_set_bit(dev, "big-endian", true); sysbus = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(sysbus, &error_fatal); sysbus_mmio_map(sysbus, 0, io_base); sysbus_connect_irq(sysbus, 0, PIC_GPIO(VIRT_GF_RTC_IRQ_BASE + i)); io_base += 0x1000; } /* goldfish-tty */ dev = qdev_new(TYPE_GOLDFISH_TTY); sysbus = SYS_BUS_DEVICE(dev); qdev_prop_set_chr(dev, "chardev", serial_hd(0)); sysbus_realize_and_unref(sysbus, &error_fatal); sysbus_mmio_map(sysbus, 0, VIRT_GF_TTY_MMIO_BASE); sysbus_connect_irq(sysbus, 0, PIC_GPIO(VIRT_GF_TTY_IRQ_BASE)); /* virt controller */ dev = sysbus_create_simple(TYPE_VIRT_CTRL, VIRT_CTRL_MMIO_BASE, PIC_GPIO(VIRT_CTRL_IRQ_BASE)); /* virtio-mmio */ io_base = VIRT_VIRTIO_MMIO_BASE; for (i = 0; i < 128; i++) { dev = qdev_new(TYPE_VIRTIO_MMIO); qdev_prop_set_bit(dev, "force-legacy", false); sysbus = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(sysbus, &error_fatal); sysbus_connect_irq(sysbus, 0, PIC_GPIO(VIRT_VIRTIO_IRQ_BASE + i)); sysbus_mmio_map(sysbus, 0, io_base); io_base += 0x200; } if (kernel_filename) { CPUState *cs = CPU(cpu); uint64_t high; void *param_blob, *param_ptr, *param_rng_seed; if (kernel_cmdline) { param_blob = g_malloc(strlen(kernel_cmdline) + 1024); } else { param_blob = g_malloc(1024); } kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, &elf_entry, NULL, &high, NULL, 1, EM_68K, 0, 0); if (kernel_size < 0) { error_report("could not load kernel '%s'", kernel_filename); exit(1); } reset_info->initial_pc = elf_entry; parameters_base = (high + 1) & ~1; param_ptr = param_blob; BOOTINFO1(param_ptr, BI_MACHTYPE, MACH_VIRT); if (m68k_feature(&cpu->env, M68K_FEATURE_M68020)) { BOOTINFO1(param_ptr, BI_CPUTYPE, CPU_68020); } else if (m68k_feature(&cpu->env, M68K_FEATURE_M68030)) { BOOTINFO1(param_ptr, BI_MMUTYPE, MMU_68030); BOOTINFO1(param_ptr, BI_CPUTYPE, CPU_68030); } else if (m68k_feature(&cpu->env, M68K_FEATURE_M68040)) { BOOTINFO1(param_ptr, BI_FPUTYPE, FPU_68040); BOOTINFO1(param_ptr, BI_MMUTYPE, MMU_68040); BOOTINFO1(param_ptr, BI_CPUTYPE, CPU_68040); } else if (m68k_feature(&cpu->env, M68K_FEATURE_M68060)) { BOOTINFO1(param_ptr, BI_FPUTYPE, FPU_68060); BOOTINFO1(param_ptr, BI_MMUTYPE, MMU_68060); BOOTINFO1(param_ptr, BI_CPUTYPE, CPU_68060); } BOOTINFO2(param_ptr, BI_MEMCHUNK, 0, ram_size); BOOTINFO1(param_ptr, BI_VIRT_QEMU_VERSION, ((QEMU_VERSION_MAJOR << 24) | (QEMU_VERSION_MINOR << 16) | (QEMU_VERSION_MICRO << 8))); BOOTINFO2(param_ptr, BI_VIRT_GF_PIC_BASE, VIRT_GF_PIC_MMIO_BASE, VIRT_GF_PIC_IRQ_BASE); BOOTINFO2(param_ptr, BI_VIRT_GF_RTC_BASE, VIRT_GF_RTC_MMIO_BASE, VIRT_GF_RTC_IRQ_BASE); BOOTINFO2(param_ptr, BI_VIRT_GF_TTY_BASE, VIRT_GF_TTY_MMIO_BASE, VIRT_GF_TTY_IRQ_BASE); BOOTINFO2(param_ptr, BI_VIRT_CTRL_BASE, VIRT_CTRL_MMIO_BASE, VIRT_CTRL_IRQ_BASE); BOOTINFO2(param_ptr, BI_VIRT_VIRTIO_BASE, VIRT_VIRTIO_MMIO_BASE, VIRT_VIRTIO_IRQ_BASE); if (kernel_cmdline) { BOOTINFOSTR(param_ptr, BI_COMMAND_LINE, kernel_cmdline); } /* Pass seed to RNG. */ param_rng_seed = param_ptr; qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed)); BOOTINFODATA(param_ptr, BI_RNG_SEED, rng_seed, sizeof(rng_seed)); /* load initrd */ if (initrd_filename) { initrd_size = get_image_size(initrd_filename); if (initrd_size < 0) { error_report("could not load initial ram disk '%s'", initrd_filename); exit(1); } initrd_base = (ram_size - initrd_size) & TARGET_PAGE_MASK; load_image_targphys(initrd_filename, initrd_base, ram_size - initrd_base); BOOTINFO2(param_ptr, BI_RAMDISK, initrd_base, initrd_size); } else { initrd_base = 0; initrd_size = 0; } BOOTINFO0(param_ptr, BI_LAST); rom_add_blob_fixed_as("bootinfo", param_blob, param_ptr - param_blob, parameters_base, cs->as); qemu_register_reset_nosnapshotload(rerandomize_rng_seed, rom_ptr_for_as(cs->as, parameters_base, param_ptr - param_blob) + (param_rng_seed - param_blob)); g_free(param_blob); } } static void virt_machine_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "QEMU M68K Virtual Machine"; mc->init = virt_init; mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040"); mc->max_cpus = 1; mc->no_floppy = 1; mc->no_parallel = 1; mc->default_ram_id = "m68k_virt.ram"; } static const TypeInfo virt_machine_info = { .name = MACHINE_TYPE_NAME("virt"), .parent = TYPE_MACHINE, .abstract = true, .class_init = virt_machine_class_init, }; static void virt_machine_register_types(void) { type_register_static(&virt_machine_info); } type_init(virt_machine_register_types) #define DEFINE_VIRT_MACHINE_IMPL(latest, ...) \ static void MACHINE_VER_SYM(class_init, virt, __VA_ARGS__)( \ ObjectClass *oc, \ void *data) \ { \ MachineClass *mc = MACHINE_CLASS(oc); \ MACHINE_VER_SYM(options, virt, __VA_ARGS__)(mc); \ mc->desc = "QEMU " MACHINE_VER_STR(__VA_ARGS__) " M68K Virtual Machine"; \ MACHINE_VER_DEPRECATION(__VA_ARGS__); \ if (latest) { \ mc->alias = "virt"; \ } \ } \ static const TypeInfo MACHINE_VER_SYM(info, virt, __VA_ARGS__) = \ { \ .name = MACHINE_VER_TYPE_NAME("virt", __VA_ARGS__), \ .parent = MACHINE_TYPE_NAME("virt"), \ .class_init = MACHINE_VER_SYM(class_init, virt, __VA_ARGS__), \ }; \ static void MACHINE_VER_SYM(register, virt, __VA_ARGS__)(void) \ { \ MACHINE_VER_DELETION(__VA_ARGS__); \ type_register_static(&MACHINE_VER_SYM(info, virt, __VA_ARGS__)); \ } \ type_init(MACHINE_VER_SYM(register, virt, __VA_ARGS__)); #define DEFINE_VIRT_MACHINE_AS_LATEST(major, minor) \ DEFINE_VIRT_MACHINE_IMPL(true, major, minor) #define DEFINE_VIRT_MACHINE(major, minor) \ DEFINE_VIRT_MACHINE_IMPL(false, major, minor) static void virt_machine_9_2_options(MachineClass *mc) { } DEFINE_VIRT_MACHINE_AS_LATEST(9, 2) static void virt_machine_9_1_options(MachineClass *mc) { virt_machine_9_2_options(mc); compat_props_add(mc->compat_props, hw_compat_9_1, hw_compat_9_1_len); } DEFINE_VIRT_MACHINE(9, 1) static void virt_machine_9_0_options(MachineClass *mc) { virt_machine_9_1_options(mc); compat_props_add(mc->compat_props, hw_compat_9_0, hw_compat_9_0_len); } DEFINE_VIRT_MACHINE(9, 0) static void virt_machine_8_2_options(MachineClass *mc) { virt_machine_9_0_options(mc); compat_props_add(mc->compat_props, hw_compat_8_2, hw_compat_8_2_len); } DEFINE_VIRT_MACHINE(8, 2) static void virt_machine_8_1_options(MachineClass *mc) { virt_machine_8_2_options(mc); compat_props_add(mc->compat_props, hw_compat_8_1, hw_compat_8_1_len); } DEFINE_VIRT_MACHINE(8, 1) static void virt_machine_8_0_options(MachineClass *mc) { virt_machine_8_1_options(mc); compat_props_add(mc->compat_props, hw_compat_8_0, hw_compat_8_0_len); } DEFINE_VIRT_MACHINE(8, 0) static void virt_machine_7_2_options(MachineClass *mc) { virt_machine_8_0_options(mc); compat_props_add(mc->compat_props, hw_compat_7_2, hw_compat_7_2_len); } DEFINE_VIRT_MACHINE(7, 2) static void virt_machine_7_1_options(MachineClass *mc) { virt_machine_7_2_options(mc); compat_props_add(mc->compat_props, hw_compat_7_1, hw_compat_7_1_len); } DEFINE_VIRT_MACHINE(7, 1) static void virt_machine_7_0_options(MachineClass *mc) { virt_machine_7_1_options(mc); compat_props_add(mc->compat_props, hw_compat_7_0, hw_compat_7_0_len); } DEFINE_VIRT_MACHINE(7, 0) static void virt_machine_6_2_options(MachineClass *mc) { virt_machine_7_0_options(mc); compat_props_add(mc->compat_props, hw_compat_6_2, hw_compat_6_2_len); } DEFINE_VIRT_MACHINE(6, 2) static void virt_machine_6_1_options(MachineClass *mc) { virt_machine_6_2_options(mc); compat_props_add(mc->compat_props, hw_compat_6_1, hw_compat_6_1_len); } DEFINE_VIRT_MACHINE(6, 1) static void virt_machine_6_0_options(MachineClass *mc) { virt_machine_6_1_options(mc); compat_props_add(mc->compat_props, hw_compat_6_0, hw_compat_6_0_len); } DEFINE_VIRT_MACHINE(6, 0)