a391eeb129
This calls the MACHINE_VER_DELETION() macro in the machine type registration method, so that when a versioned machine type reaches the end of its life, it is no longer registered with QOM and thus cannot be used. The actual definition of the machine type should be deleted at this point, but experience shows that can easily be forgotten. By skipping registration the manual code deletion task can be done at any later date. Reviewed-by: Thomas Huth <thuth@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Message-ID: <20240620165742.1711389-12-berrange@redhat.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
443 lines
14 KiB
C
443 lines
14 KiB
C
/*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* QEMU Virtual M68K Machine
|
|
*
|
|
* (c) 2020 Laurent Vivier <laurent@vivier.eu>
|
|
*
|
|
*/
|
|
|
|
#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_1_options(MachineClass *mc)
|
|
{
|
|
}
|
|
DEFINE_VIRT_MACHINE_AS_LATEST(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)
|