65f5144e17
Add a new property "big-endian" to allow configuring the RTC as either little or big endian, the default is little endian. Currently overriding the default to big endian is only used by the m68k virt platform. New platforms should prefer to use little endian and not set this. Cc: Laurent Vivier <lvivier@redhat.com> Reviewed-by: Anup Patel <anup@brainfault.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Stafford Horne <shorne@gmail.com>
365 lines
12 KiB
C
365 lines
12 KiB
C
/*
|
|
* SPDX-License-Identifier: 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/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 "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 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);
|
|
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 = 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);
|
|
}
|
|
reset_info->initial_pc = elf_entry;
|
|
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);
|
|
}
|
|
|
|
/* Pass seed to RNG. */
|
|
qemu_guest_getrandom_nofail(rng_seed, sizeof(rng_seed));
|
|
BOOTINFODATA(cs->as, parameters_base, BI_VIRT_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(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_7_2_options(MachineClass *mc)
|
|
{
|
|
}
|
|
DEFINE_VIRT_MACHINE(7, 2, true)
|
|
|
|
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, false)
|
|
|
|
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, false)
|
|
|
|
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, false)
|
|
|
|
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, false)
|
|
|
|
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, false)
|