311 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			311 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * SPDX-License-Identifer: GPL-2.0-or-later | ||
|  |  * | ||
|  |  * QEMU Vitual M68K Machine | ||
|  |  * | ||
|  |  * (c) 2020 Laurent Vivier <laurent@vivier.eu> | ||
|  |  * | ||
|  |  */ | ||
|  | 
 | ||
|  | #include "qemu/osdep.h"
 | ||
|  | #include "qemu/units.h"
 | ||
|  | #include "qemu-common.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 "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 */
 | ||
|  | 
 | ||
|  | static void main_cpu_reset(void *opaque) | ||
|  | { | ||
|  |     M68kCPU *cpu = opaque; | ||
|  |     CPUState *cs = CPU(cpu); | ||
|  | 
 | ||
|  |     cpu_reset(cs); | ||
|  |     cpu->env.aregs[7] = ldl_phys(cs->as, 0); | ||
|  |     cpu->env.pc = ldl_phys(cs->as, 4); | ||
|  | } | ||
|  | 
 | ||
|  | 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; | ||
|  | 
 | ||
|  |     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); | ||
|  |     } | ||
|  | 
 | ||
|  |     /* init CPUs */ | ||
|  |     cpu = M68K_CPU(cpu_create(machine->cpu_type)); | ||
|  |     qemu_register_reset(main_cpu_reset, cpu); | ||
|  | 
 | ||
|  |     /* RAM */ | ||
|  |     memory_region_add_subregion(get_system_memory(), 0, machine->ram); | ||
|  | 
 | ||
|  |     /* IRQ Controller */ | ||
|  | 
 | ||
|  |     irqc_dev = qdev_new(TYPE_M68K_IRQC); | ||
|  |     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); | ||
|  |         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 = qdev_new(TYPE_VIRT_CTRL); | ||
|  |     sysbus = SYS_BUS_DEVICE(dev); | ||
|  |     sysbus_realize_and_unref(sysbus, &error_fatal); | ||
|  |     sysbus_mmio_map(sysbus, 0, VIRT_CTRL_MMIO_BASE); | ||
|  |     sysbus_connect_irq(sysbus, 0, 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; | ||
|  | 
 | ||
|  |         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); | ||
|  |         } | ||
|  |         stl_phys(cs->as, 4, elf_entry); /* reset initial PC */ | ||
|  |         parameters_base = (high + 1) & ~1; | ||
|  | 
 | ||
|  |         BOOTINFO1(cs->as, parameters_base, BI_MACHTYPE, MACH_VIRT); | ||
|  |         BOOTINFO1(cs->as, parameters_base, BI_FPUTYPE, FPU_68040); | ||
|  |         BOOTINFO1(cs->as, parameters_base, BI_MMUTYPE, MMU_68040); | ||
|  |         BOOTINFO1(cs->as, parameters_base, BI_CPUTYPE, CPU_68040); | ||
|  |         BOOTINFO2(cs->as, parameters_base, BI_MEMCHUNK, 0, ram_size); | ||
|  | 
 | ||
|  |         BOOTINFO1(cs->as, parameters_base, BI_VIRT_QEMU_VERSION, | ||
|  |                   ((QEMU_VERSION_MAJOR << 24) | (QEMU_VERSION_MINOR << 16) | | ||
|  |                    (QEMU_VERSION_MICRO << 8))); | ||
|  |         BOOTINFO2(cs->as, parameters_base, BI_VIRT_GF_PIC_BASE, | ||
|  |                   VIRT_GF_PIC_MMIO_BASE, VIRT_GF_PIC_IRQ_BASE); | ||
|  |         BOOTINFO2(cs->as, parameters_base, BI_VIRT_GF_RTC_BASE, | ||
|  |                   VIRT_GF_RTC_MMIO_BASE, VIRT_GF_RTC_IRQ_BASE); | ||
|  |         BOOTINFO2(cs->as, parameters_base, BI_VIRT_GF_TTY_BASE, | ||
|  |                   VIRT_GF_TTY_MMIO_BASE, VIRT_GF_TTY_IRQ_BASE); | ||
|  |         BOOTINFO2(cs->as, parameters_base, BI_VIRT_CTRL_BASE, | ||
|  |                   VIRT_CTRL_MMIO_BASE, VIRT_CTRL_IRQ_BASE); | ||
|  |         BOOTINFO2(cs->as, parameters_base, BI_VIRT_VIRTIO_BASE, | ||
|  |                   VIRT_VIRTIO_MMIO_BASE, VIRT_VIRTIO_IRQ_BASE); | ||
|  | 
 | ||
|  |         if (kernel_cmdline) { | ||
|  |             BOOTINFOSTR(cs->as, parameters_base, BI_COMMAND_LINE, | ||
|  |                         kernel_cmdline); | ||
|  |         } | ||
|  | 
 | ||
|  |         /* 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(cs->as, parameters_base, BI_RAMDISK, initrd_base, | ||
|  |                       initrd_size); | ||
|  |         } else { | ||
|  |             initrd_base = 0; | ||
|  |             initrd_size = 0; | ||
|  |         } | ||
|  |         BOOTINFO0(cs->as, parameters_base, BI_LAST); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 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(major, minor, latest) \
 | ||
|  |     static void virt_##major##_##minor##_class_init(ObjectClass *oc, \ | ||
|  |                                                     void *data) \ | ||
|  |     { \ | ||
|  |         MachineClass *mc = MACHINE_CLASS(oc); \ | ||
|  |         virt_machine_##major##_##minor##_options(mc); \ | ||
|  |         mc->desc = "QEMU " # major "." # minor " M68K Virtual Machine"; \ | ||
|  |         if (latest) { \ | ||
|  |             mc->alias = "virt"; \ | ||
|  |         } \ | ||
|  |     } \ | ||
|  |     static const TypeInfo machvirt_##major##_##minor##_info = { \ | ||
|  |         .name = MACHINE_TYPE_NAME("virt-" # major "." # minor), \ | ||
|  |         .parent = MACHINE_TYPE_NAME("virt"), \ | ||
|  |         .class_init = virt_##major##_##minor##_class_init, \ | ||
|  |     }; \ | ||
|  |     static void machvirt_machine_##major##_##minor##_init(void) \ | ||
|  |     { \ | ||
|  |         type_register_static(&machvirt_##major##_##minor##_info); \ | ||
|  |     } \ | ||
|  |     type_init(machvirt_machine_##major##_##minor##_init); | ||
|  | 
 | ||
|  | static void virt_machine_6_0_options(MachineClass *mc) | ||
|  | { | ||
|  | } | ||
|  | DEFINE_VIRT_MACHINE(6, 0, true) |