diff options
Diffstat (limited to 'hw/m68k')
-rw-r--r-- | hw/m68k/Kconfig | 34 | ||||
-rw-r--r-- | hw/m68k/an5206.c | 102 | ||||
-rw-r--r-- | hw/m68k/bootinfo.h | 59 | ||||
-rw-r--r-- | hw/m68k/mcf5206.c | 629 | ||||
-rw-r--r-- | hw/m68k/mcf5208.c | 363 | ||||
-rw-r--r-- | hw/m68k/mcf_intc.c | 217 | ||||
-rw-r--r-- | hw/m68k/meson.build | 8 | ||||
-rw-r--r-- | hw/m68k/next-cube.c | 1056 | ||||
-rw-r--r-- | hw/m68k/next-kbd.c | 289 | ||||
-rw-r--r-- | hw/m68k/q800.c | 711 | ||||
-rw-r--r-- | hw/m68k/virt.c | 324 |
11 files changed, 3792 insertions, 0 deletions
diff --git a/hw/m68k/Kconfig b/hw/m68k/Kconfig new file mode 100644 index 000000000..f839f8a03 --- /dev/null +++ b/hw/m68k/Kconfig @@ -0,0 +1,34 @@ +config AN5206 + bool + select COLDFIRE + select PTIMER + +config MCF5208 + bool + select COLDFIRE + select PTIMER + +config NEXTCUBE + bool + select FRAMEBUFFER + select ESCC + +config Q800 + bool + select MAC_VIA + select NUBUS + select MACFB + select SWIM + select ESCC + select ESP + select DP8393X + select OR_IRQ + +config M68K_VIRT + bool + select M68K_IRQC + select VIRT_CTRL + select GOLDFISH_PIC + select GOLDFISH_TTY + select GOLDFISH_RTC + select VIRTIO_MMIO diff --git a/hw/m68k/an5206.c b/hw/m68k/an5206.c new file mode 100644 index 000000000..11ae4c979 --- /dev/null +++ b/hw/m68k/an5206.c @@ -0,0 +1,102 @@ +/* + * Arnewsh 5206 ColdFire system emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licensed under the GPL + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "cpu.h" +#include "hw/m68k/mcf.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "elf.h" +#include "qemu/error-report.h" +#include "sysemu/qtest.h" + +#define KERNEL_LOAD_ADDR 0x10000 +#define AN5206_MBAR_ADDR 0x10000000 +#define AN5206_RAMBAR_ADDR 0x20000000 + +static void mcf5206_init(MemoryRegion *sysmem, uint32_t base) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new(TYPE_MCF5206_MBAR); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + + memory_region_add_subregion(sysmem, base, sysbus_mmio_get_region(s, 0)); +} + +static void an5206_init(MachineState *machine) +{ + ram_addr_t ram_size = machine->ram_size; + const char *kernel_filename = machine->kernel_filename; + M68kCPU *cpu; + CPUM68KState *env; + int kernel_size; + uint64_t elf_entry; + hwaddr entry; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *sram = g_new(MemoryRegion, 1); + + cpu = M68K_CPU(cpu_create(machine->cpu_type)); + env = &cpu->env; + + /* Initialize CPU registers. */ + env->vbr = 0; + /* TODO: allow changing MBAR and RAMBAR. */ + env->mbar = AN5206_MBAR_ADDR | 1; + env->rambar0 = AN5206_RAMBAR_ADDR | 1; + + /* DRAM at address zero */ + memory_region_add_subregion(address_space_mem, 0, machine->ram); + + /* Internal SRAM. */ + memory_region_init_ram(sram, NULL, "an5206.sram", 512, &error_fatal); + memory_region_add_subregion(address_space_mem, AN5206_RAMBAR_ADDR, sram); + + mcf5206_init(address_space_mem, AN5206_MBAR_ADDR); + + /* Load kernel. */ + if (!kernel_filename) { + if (qtest_enabled()) { + return; + } + error_report("Kernel image must be specified"); + exit(1); + } + + kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, &elf_entry, + NULL, NULL, NULL, 1, EM_68K, 0, 0); + entry = elf_entry; + if (kernel_size < 0) { + kernel_size = load_uimage(kernel_filename, &entry, NULL, NULL, + NULL, NULL); + } + if (kernel_size < 0) { + kernel_size = load_image_targphys(kernel_filename, KERNEL_LOAD_ADDR, + ram_size - KERNEL_LOAD_ADDR); + entry = KERNEL_LOAD_ADDR; + } + if (kernel_size < 0) { + error_report("Could not load kernel '%s'", kernel_filename); + exit(1); + } + + env->pc = entry; +} + +static void an5206_machine_init(MachineClass *mc) +{ + mc->desc = "Arnewsh 5206"; + mc->init = an5206_init; + mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5206"); + mc->default_ram_id = "an5206.ram"; +} + +DEFINE_MACHINE("an5206", an5206_machine_init) diff --git a/hw/m68k/bootinfo.h b/hw/m68k/bootinfo.h new file mode 100644 index 000000000..adbf0c552 --- /dev/null +++ b/hw/m68k/bootinfo.h @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note + * + * Bootinfo tags from linux bootinfo.h and bootinfo-mac.h: + * This is an easily parsable and extendable structure containing all + * information to be passed from the bootstrap to the kernel + * + * This structure is copied right after the kernel by the bootstrap + * routine. + */ + +#ifndef HW_M68K_BOOTINFO_H +#define HW_M68K_BOOTINFO_H + +#define BOOTINFO0(as, base, id) \ + do { \ + stw_phys(as, base, id); \ + base += 2; \ + stw_phys(as, base, sizeof(struct bi_record)); \ + base += 2; \ + } while (0) + +#define BOOTINFO1(as, base, id, value) \ + do { \ + stw_phys(as, base, id); \ + base += 2; \ + stw_phys(as, base, sizeof(struct bi_record) + 4); \ + base += 2; \ + stl_phys(as, base, value); \ + base += 4; \ + } while (0) + +#define BOOTINFO2(as, base, id, value1, value2) \ + do { \ + stw_phys(as, base, id); \ + base += 2; \ + stw_phys(as, base, sizeof(struct bi_record) + 8); \ + base += 2; \ + stl_phys(as, base, value1); \ + base += 4; \ + stl_phys(as, base, value2); \ + base += 4; \ + } while (0) + +#define BOOTINFOSTR(as, base, id, string) \ + do { \ + int i; \ + stw_phys(as, base, id); \ + base += 2; \ + stw_phys(as, base, \ + (sizeof(struct bi_record) + strlen(string) + 2) & ~1); \ + base += 2; \ + for (i = 0; string[i]; i++) { \ + stb_phys(as, base++, string[i]); \ + } \ + stb_phys(as, base++, 0); \ + base = (parameters_base + 1) & ~1; \ + } while (0) +#endif diff --git a/hw/m68k/mcf5206.c b/hw/m68k/mcf5206.c new file mode 100644 index 000000000..6d93d761a --- /dev/null +++ b/hw/m68k/mcf5206.c @@ -0,0 +1,629 @@ +/* + * Motorola ColdFire MCF5206 SoC embedded peripheral emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licensed under the GPL + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "cpu.h" +#include "hw/boards.h" +#include "hw/irq.h" +#include "hw/m68k/mcf.h" +#include "qemu/timer.h" +#include "hw/ptimer.h" +#include "sysemu/sysemu.h" +#include "hw/sysbus.h" + +/* General purpose timer module. */ +typedef struct { + uint16_t tmr; + uint16_t trr; + uint16_t tcr; + uint16_t ter; + ptimer_state *timer; + qemu_irq irq; + int irq_state; +} m5206_timer_state; + +#define TMR_RST 0x01 +#define TMR_CLK 0x06 +#define TMR_FRR 0x08 +#define TMR_ORI 0x10 +#define TMR_OM 0x20 +#define TMR_CE 0xc0 + +#define TER_CAP 0x01 +#define TER_REF 0x02 + +static void m5206_timer_update(m5206_timer_state *s) +{ + if ((s->tmr & TMR_ORI) != 0 && (s->ter & TER_REF)) + qemu_irq_raise(s->irq); + else + qemu_irq_lower(s->irq); +} + +static void m5206_timer_reset(m5206_timer_state *s) +{ + s->tmr = 0; + s->trr = 0; +} + +static void m5206_timer_recalibrate(m5206_timer_state *s) +{ + int prescale; + int mode; + + ptimer_transaction_begin(s->timer); + ptimer_stop(s->timer); + + if ((s->tmr & TMR_RST) == 0) { + goto exit; + } + + prescale = (s->tmr >> 8) + 1; + mode = (s->tmr >> 1) & 3; + if (mode == 2) + prescale *= 16; + + if (mode == 3 || mode == 0) { + qemu_log_mask(LOG_UNIMP, "m5206_timer: mode %d not implemented\n", + mode); + goto exit; + } + if ((s->tmr & TMR_FRR) == 0) { + qemu_log_mask(LOG_UNIMP, + "m5206_timer: free running mode not implemented\n"); + goto exit; + } + + /* Assume 66MHz system clock. */ + ptimer_set_freq(s->timer, 66000000 / prescale); + + ptimer_set_limit(s->timer, s->trr, 0); + + ptimer_run(s->timer, 0); +exit: + ptimer_transaction_commit(s->timer); +} + +static void m5206_timer_trigger(void *opaque) +{ + m5206_timer_state *s = (m5206_timer_state *)opaque; + s->ter |= TER_REF; + m5206_timer_update(s); +} + +static uint32_t m5206_timer_read(m5206_timer_state *s, uint32_t addr) +{ + switch (addr) { + case 0: + return s->tmr; + case 4: + return s->trr; + case 8: + return s->tcr; + case 0xc: + return s->trr - ptimer_get_count(s->timer); + case 0x11: + return s->ter; + default: + return 0; + } +} + +static void m5206_timer_write(m5206_timer_state *s, uint32_t addr, uint32_t val) +{ + switch (addr) { + case 0: + if ((s->tmr & TMR_RST) != 0 && (val & TMR_RST) == 0) { + m5206_timer_reset(s); + } + s->tmr = val; + m5206_timer_recalibrate(s); + break; + case 4: + s->trr = val; + m5206_timer_recalibrate(s); + break; + case 8: + s->tcr = val; + break; + case 0xc: + ptimer_transaction_begin(s->timer); + ptimer_set_count(s->timer, val); + ptimer_transaction_commit(s->timer); + break; + case 0x11: + s->ter &= ~val; + break; + default: + break; + } + m5206_timer_update(s); +} + +static m5206_timer_state *m5206_timer_init(qemu_irq irq) +{ + m5206_timer_state *s; + + s = g_new0(m5206_timer_state, 1); + s->timer = ptimer_init(m5206_timer_trigger, s, PTIMER_POLICY_DEFAULT); + s->irq = irq; + m5206_timer_reset(s); + return s; +} + +/* System Integration Module. */ + +typedef struct { + SysBusDevice parent_obj; + + M68kCPU *cpu; + MemoryRegion iomem; + qemu_irq *pic; + m5206_timer_state *timer[2]; + void *uart[2]; + uint8_t scr; + uint8_t icr[14]; + uint16_t imr; /* 1 == interrupt is masked. */ + uint16_t ipr; + uint8_t rsr; + uint8_t swivr; + uint8_t par; + /* Include the UART vector registers here. */ + uint8_t uivr[2]; +} m5206_mbar_state; + +#define MCF5206_MBAR(obj) OBJECT_CHECK(m5206_mbar_state, (obj), TYPE_MCF5206_MBAR) + +/* Interrupt controller. */ + +static int m5206_find_pending_irq(m5206_mbar_state *s) +{ + int level; + int vector; + uint16_t active; + int i; + + level = 0; + vector = 0; + active = s->ipr & ~s->imr; + if (!active) + return 0; + + for (i = 1; i < 14; i++) { + if (active & (1 << i)) { + if ((s->icr[i] & 0x1f) > level) { + level = s->icr[i] & 0x1f; + vector = i; + } + } + } + + if (level < 4) + vector = 0; + + return vector; +} + +static void m5206_mbar_update(m5206_mbar_state *s) +{ + int irq; + int vector; + int level; + + irq = m5206_find_pending_irq(s); + if (irq) { + int tmp; + tmp = s->icr[irq]; + level = (tmp >> 2) & 7; + if (tmp & 0x80) { + /* Autovector. */ + vector = 24 + level; + } else { + switch (irq) { + case 8: /* SWT */ + vector = s->swivr; + break; + case 12: /* UART1 */ + vector = s->uivr[0]; + break; + case 13: /* UART2 */ + vector = s->uivr[1]; + break; + default: + /* Unknown vector. */ + qemu_log_mask(LOG_UNIMP, "%s: Unhandled vector for IRQ %d\n", + __func__, irq); + vector = 0xf; + break; + } + } + } else { + level = 0; + vector = 0; + } + m68k_set_irq_level(s->cpu, level, vector); +} + +static void m5206_mbar_set_irq(void *opaque, int irq, int level) +{ + m5206_mbar_state *s = (m5206_mbar_state *)opaque; + if (level) { + s->ipr |= 1 << irq; + } else { + s->ipr &= ~(1 << irq); + } + m5206_mbar_update(s); +} + +/* System Integration Module. */ + +static void m5206_mbar_reset(DeviceState *dev) +{ + m5206_mbar_state *s = MCF5206_MBAR(dev); + + s->scr = 0xc0; + s->icr[1] = 0x04; + s->icr[2] = 0x08; + s->icr[3] = 0x0c; + s->icr[4] = 0x10; + s->icr[5] = 0x14; + s->icr[6] = 0x18; + s->icr[7] = 0x1c; + s->icr[8] = 0x1c; + s->icr[9] = 0x80; + s->icr[10] = 0x80; + s->icr[11] = 0x80; + s->icr[12] = 0x00; + s->icr[13] = 0x00; + s->imr = 0x3ffe; + s->rsr = 0x80; + s->swivr = 0x0f; + s->par = 0; +} + +static uint64_t m5206_mbar_read(m5206_mbar_state *s, + uint16_t offset, unsigned size) +{ + if (offset >= 0x100 && offset < 0x120) { + return m5206_timer_read(s->timer[0], offset - 0x100); + } else if (offset >= 0x120 && offset < 0x140) { + return m5206_timer_read(s->timer[1], offset - 0x120); + } else if (offset >= 0x140 && offset < 0x160) { + return mcf_uart_read(s->uart[0], offset - 0x140, size); + } else if (offset >= 0x180 && offset < 0x1a0) { + return mcf_uart_read(s->uart[1], offset - 0x180, size); + } + switch (offset) { + case 0x03: return s->scr; + case 0x14 ... 0x20: return s->icr[offset - 0x13]; + case 0x36: return s->imr; + case 0x3a: return s->ipr; + case 0x40: return s->rsr; + case 0x41: return 0; + case 0x42: return s->swivr; + case 0x50: + /* DRAM mask register. */ + /* FIXME: currently hardcoded to 128Mb. */ + { + uint32_t mask = ~0; + while (mask > current_machine->ram_size) { + mask >>= 1; + } + return mask & 0x0ffe0000; + } + case 0x5c: return 1; /* DRAM bank 1 empty. */ + case 0xcb: return s->par; + case 0x170: return s->uivr[0]; + case 0x1b0: return s->uivr[1]; + } + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad MBAR offset 0x%"PRIx16"\n", + __func__, offset); + return 0; +} + +static void m5206_mbar_write(m5206_mbar_state *s, uint16_t offset, + uint64_t value, unsigned size) +{ + if (offset >= 0x100 && offset < 0x120) { + m5206_timer_write(s->timer[0], offset - 0x100, value); + return; + } else if (offset >= 0x120 && offset < 0x140) { + m5206_timer_write(s->timer[1], offset - 0x120, value); + return; + } else if (offset >= 0x140 && offset < 0x160) { + mcf_uart_write(s->uart[0], offset - 0x140, value, size); + return; + } else if (offset >= 0x180 && offset < 0x1a0) { + mcf_uart_write(s->uart[1], offset - 0x180, value, size); + return; + } + switch (offset) { + case 0x03: + s->scr = value; + break; + case 0x14 ... 0x20: + s->icr[offset - 0x13] = value; + m5206_mbar_update(s); + break; + case 0x36: + s->imr = value; + m5206_mbar_update(s); + break; + case 0x40: + s->rsr &= ~value; + break; + case 0x41: + /* TODO: implement watchdog. */ + break; + case 0x42: + s->swivr = value; + break; + case 0xcb: + s->par = value; + break; + case 0x170: + s->uivr[0] = value; + break; + case 0x178: case 0x17c: case 0x1c8: case 0x1bc: + /* Not implemented: UART Output port bits. */ + break; + case 0x1b0: + s->uivr[1] = value; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad MBAR offset 0x%"PRIx16"\n", + __func__, offset); + break; + } +} + +/* Internal peripherals use a variety of register widths. + This lookup table allows a single routine to handle all of them. */ +static const uint8_t m5206_mbar_width[] = +{ + /* 000-040 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + /* 040-080 */ 1, 2, 2, 2, 4, 1, 2, 4, 1, 2, 4, 2, 2, 4, 2, 2, + /* 080-0c0 */ 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, 2, 2, 4, + /* 0c0-100 */ 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* 100-140 */ 2, 2, 2, 2, 1, 0, 0, 0, 2, 2, 2, 2, 1, 0, 0, 0, + /* 140-180 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 180-1c0 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + /* 1c0-200 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +}; + +static uint32_t m5206_mbar_readw(void *opaque, hwaddr offset); +static uint32_t m5206_mbar_readl(void *opaque, hwaddr offset); + +static uint32_t m5206_mbar_readb(void *opaque, hwaddr offset) +{ + m5206_mbar_state *s = (m5206_mbar_state *)opaque; + offset &= 0x3ff; + if (offset >= 0x200) { + qemu_log_mask(LOG_GUEST_ERROR, "Bad MBAR read offset 0x%" HWADDR_PRIX, + offset); + return 0; + } + if (m5206_mbar_width[offset >> 2] > 1) { + uint16_t val; + val = m5206_mbar_readw(opaque, offset & ~1); + if ((offset & 1) == 0) { + val >>= 8; + } + return val & 0xff; + } + return m5206_mbar_read(s, offset, 1); +} + +static uint32_t m5206_mbar_readw(void *opaque, hwaddr offset) +{ + m5206_mbar_state *s = (m5206_mbar_state *)opaque; + int width; + offset &= 0x3ff; + if (offset >= 0x200) { + qemu_log_mask(LOG_GUEST_ERROR, "Bad MBAR read offset 0x%" HWADDR_PRIX, + offset); + return 0; + } + width = m5206_mbar_width[offset >> 2]; + if (width > 2) { + uint32_t val; + val = m5206_mbar_readl(opaque, offset & ~3); + if ((offset & 3) == 0) + val >>= 16; + return val & 0xffff; + } else if (width < 2) { + uint16_t val; + val = m5206_mbar_readb(opaque, offset) << 8; + val |= m5206_mbar_readb(opaque, offset + 1); + return val; + } + return m5206_mbar_read(s, offset, 2); +} + +static uint32_t m5206_mbar_readl(void *opaque, hwaddr offset) +{ + m5206_mbar_state *s = (m5206_mbar_state *)opaque; + int width; + offset &= 0x3ff; + if (offset >= 0x200) { + qemu_log_mask(LOG_GUEST_ERROR, "Bad MBAR read offset 0x%" HWADDR_PRIX, + offset); + return 0; + } + width = m5206_mbar_width[offset >> 2]; + if (width < 4) { + uint32_t val; + val = m5206_mbar_readw(opaque, offset) << 16; + val |= m5206_mbar_readw(opaque, offset + 2); + return val; + } + return m5206_mbar_read(s, offset, 4); +} + +static void m5206_mbar_writew(void *opaque, hwaddr offset, + uint32_t value); +static void m5206_mbar_writel(void *opaque, hwaddr offset, + uint32_t value); + +static void m5206_mbar_writeb(void *opaque, hwaddr offset, + uint32_t value) +{ + m5206_mbar_state *s = (m5206_mbar_state *)opaque; + int width; + offset &= 0x3ff; + if (offset >= 0x200) { + qemu_log_mask(LOG_GUEST_ERROR, "Bad MBAR write offset 0x%" HWADDR_PRIX, + offset); + return; + } + width = m5206_mbar_width[offset >> 2]; + if (width > 1) { + uint32_t tmp; + tmp = m5206_mbar_readw(opaque, offset & ~1); + if (offset & 1) { + tmp = (tmp & 0xff00) | value; + } else { + tmp = (tmp & 0x00ff) | (value << 8); + } + m5206_mbar_writew(opaque, offset & ~1, tmp); + return; + } + m5206_mbar_write(s, offset, value, 1); +} + +static void m5206_mbar_writew(void *opaque, hwaddr offset, + uint32_t value) +{ + m5206_mbar_state *s = (m5206_mbar_state *)opaque; + int width; + offset &= 0x3ff; + if (offset >= 0x200) { + qemu_log_mask(LOG_GUEST_ERROR, "Bad MBAR write offset 0x%" HWADDR_PRIX, + offset); + return; + } + width = m5206_mbar_width[offset >> 2]; + if (width > 2) { + uint32_t tmp; + tmp = m5206_mbar_readl(opaque, offset & ~3); + if (offset & 3) { + tmp = (tmp & 0xffff0000) | value; + } else { + tmp = (tmp & 0x0000ffff) | (value << 16); + } + m5206_mbar_writel(opaque, offset & ~3, tmp); + return; + } else if (width < 2) { + m5206_mbar_writeb(opaque, offset, value >> 8); + m5206_mbar_writeb(opaque, offset + 1, value & 0xff); + return; + } + m5206_mbar_write(s, offset, value, 2); +} + +static void m5206_mbar_writel(void *opaque, hwaddr offset, + uint32_t value) +{ + m5206_mbar_state *s = (m5206_mbar_state *)opaque; + int width; + offset &= 0x3ff; + if (offset >= 0x200) { + qemu_log_mask(LOG_GUEST_ERROR, "Bad MBAR write offset 0x%" HWADDR_PRIX, + offset); + return; + } + width = m5206_mbar_width[offset >> 2]; + if (width < 4) { + m5206_mbar_writew(opaque, offset, value >> 16); + m5206_mbar_writew(opaque, offset + 2, value & 0xffff); + return; + } + m5206_mbar_write(s, offset, value, 4); +} + +static uint64_t m5206_mbar_readfn(void *opaque, hwaddr addr, unsigned size) +{ + switch (size) { + case 1: + return m5206_mbar_readb(opaque, addr); + case 2: + return m5206_mbar_readw(opaque, addr); + case 4: + return m5206_mbar_readl(opaque, addr); + default: + g_assert_not_reached(); + } +} + +static void m5206_mbar_writefn(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + switch (size) { + case 1: + m5206_mbar_writeb(opaque, addr, value); + break; + case 2: + m5206_mbar_writew(opaque, addr, value); + break; + case 4: + m5206_mbar_writel(opaque, addr, value); + break; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps m5206_mbar_ops = { + .read = m5206_mbar_readfn, + .write = m5206_mbar_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void mcf5206_mbar_realize(DeviceState *dev, Error **errp) +{ + m5206_mbar_state *s = MCF5206_MBAR(dev); + + memory_region_init_io(&s->iomem, NULL, &m5206_mbar_ops, s, + "mbar", 0x00001000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + + s->pic = qemu_allocate_irqs(m5206_mbar_set_irq, s, 14); + s->timer[0] = m5206_timer_init(s->pic[9]); + s->timer[1] = m5206_timer_init(s->pic[10]); + s->uart[0] = mcf_uart_init(s->pic[12], serial_hd(0)); + s->uart[1] = mcf_uart_init(s->pic[13], serial_hd(1)); + s->cpu = M68K_CPU(qemu_get_cpu(0)); +} + +static void mcf5206_mbar_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->desc = "MCF5206 system integration module"; + dc->realize = mcf5206_mbar_realize; + dc->reset = m5206_mbar_reset; +} + +static const TypeInfo mcf5206_mbar_info = { + .name = TYPE_MCF5206_MBAR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(m5206_mbar_state), + .class_init = mcf5206_mbar_class_init, +}; + +static void mcf5206_mbar_register_types(void) +{ + type_register_static(&mcf5206_mbar_info); +} + +type_init(mcf5206_mbar_register_types) diff --git a/hw/m68k/mcf5208.c b/hw/m68k/mcf5208.c new file mode 100644 index 000000000..93812ee20 --- /dev/null +++ b/hw/m68k/mcf5208.c @@ -0,0 +1,363 @@ +/* + * Motorola ColdFire MCF5208 SoC emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licensed under the GPL + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "qemu-common.h" +#include "qemu/datadir.h" +#include "cpu.h" +#include "hw/irq.h" +#include "hw/m68k/mcf.h" +#include "hw/m68k/mcf_fec.h" +#include "qemu/timer.h" +#include "hw/ptimer.h" +#include "sysemu/sysemu.h" +#include "sysemu/qtest.h" +#include "net/net.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "hw/sysbus.h" +#include "elf.h" + +#define SYS_FREQ 166666666 + +#define ROM_SIZE 0x200000 + +#define PCSR_EN 0x0001 +#define PCSR_RLD 0x0002 +#define PCSR_PIF 0x0004 +#define PCSR_PIE 0x0008 +#define PCSR_OVW 0x0010 +#define PCSR_DBG 0x0020 +#define PCSR_DOZE 0x0040 +#define PCSR_PRE_SHIFT 8 +#define PCSR_PRE_MASK 0x0f00 + +typedef struct { + MemoryRegion iomem; + qemu_irq irq; + ptimer_state *timer; + uint16_t pcsr; + uint16_t pmr; + uint16_t pcntr; +} m5208_timer_state; + +static void m5208_timer_update(m5208_timer_state *s) +{ + if ((s->pcsr & (PCSR_PIE | PCSR_PIF)) == (PCSR_PIE | PCSR_PIF)) + qemu_irq_raise(s->irq); + else + qemu_irq_lower(s->irq); +} + +static void m5208_timer_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + m5208_timer_state *s = (m5208_timer_state *)opaque; + int prescale; + int limit; + switch (offset) { + case 0: + /* The PIF bit is set-to-clear. */ + if (value & PCSR_PIF) { + s->pcsr &= ~PCSR_PIF; + value &= ~PCSR_PIF; + } + /* Avoid frobbing the timer if we're just twiddling IRQ bits. */ + if (((s->pcsr ^ value) & ~PCSR_PIE) == 0) { + s->pcsr = value; + m5208_timer_update(s); + return; + } + + ptimer_transaction_begin(s->timer); + if (s->pcsr & PCSR_EN) + ptimer_stop(s->timer); + + s->pcsr = value; + + prescale = 1 << ((s->pcsr & PCSR_PRE_MASK) >> PCSR_PRE_SHIFT); + ptimer_set_freq(s->timer, (SYS_FREQ / 2) / prescale); + if (s->pcsr & PCSR_RLD) + limit = s->pmr; + else + limit = 0xffff; + ptimer_set_limit(s->timer, limit, 0); + + if (s->pcsr & PCSR_EN) + ptimer_run(s->timer, 0); + ptimer_transaction_commit(s->timer); + break; + case 2: + ptimer_transaction_begin(s->timer); + s->pmr = value; + s->pcsr &= ~PCSR_PIF; + if ((s->pcsr & PCSR_RLD) == 0) { + if (s->pcsr & PCSR_OVW) + ptimer_set_count(s->timer, value); + } else { + ptimer_set_limit(s->timer, value, s->pcsr & PCSR_OVW); + } + ptimer_transaction_commit(s->timer); + break; + case 4: + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n", + __func__, offset); + return; + } + m5208_timer_update(s); +} + +static void m5208_timer_trigger(void *opaque) +{ + m5208_timer_state *s = (m5208_timer_state *)opaque; + s->pcsr |= PCSR_PIF; + m5208_timer_update(s); +} + +static uint64_t m5208_timer_read(void *opaque, hwaddr addr, + unsigned size) +{ + m5208_timer_state *s = (m5208_timer_state *)opaque; + switch (addr) { + case 0: + return s->pcsr; + case 2: + return s->pmr; + case 4: + return ptimer_get_count(s->timer); + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n", + __func__, addr); + return 0; + } +} + +static const MemoryRegionOps m5208_timer_ops = { + .read = m5208_timer_read, + .write = m5208_timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint64_t m5208_sys_read(void *opaque, hwaddr addr, + unsigned size) +{ + switch (addr) { + case 0x110: /* SDCS0 */ + { + int n; + for (n = 0; n < 32; n++) { + if (current_machine->ram_size < (2u << n)) { + break; + } + } + return (n - 1) | 0x40000000; + } + case 0x114: /* SDCS1 */ + return 0; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n", + __func__, addr); + return 0; + } +} + +static void m5208_sys_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIX "\n", + __func__, addr); +} + +static const MemoryRegionOps m5208_sys_ops = { + .read = m5208_sys_read, + .write = m5208_sys_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void mcf5208_sys_init(MemoryRegion *address_space, qemu_irq *pic) +{ + MemoryRegion *iomem = g_new(MemoryRegion, 1); + m5208_timer_state *s; + int i; + + /* SDRAMC. */ + memory_region_init_io(iomem, NULL, &m5208_sys_ops, NULL, "m5208-sys", 0x00004000); + memory_region_add_subregion(address_space, 0xfc0a8000, iomem); + /* Timers. */ + for (i = 0; i < 2; i++) { + s = g_new0(m5208_timer_state, 1); + s->timer = ptimer_init(m5208_timer_trigger, s, PTIMER_POLICY_DEFAULT); + memory_region_init_io(&s->iomem, NULL, &m5208_timer_ops, s, + "m5208-timer", 0x00004000); + memory_region_add_subregion(address_space, 0xfc080000 + 0x4000 * i, + &s->iomem); + s->irq = pic[4 + i]; + } +} + +static void mcf_fec_init(MemoryRegion *sysmem, NICInfo *nd, hwaddr base, + qemu_irq *irqs) +{ + DeviceState *dev; + SysBusDevice *s; + int i; + + qemu_check_nic_model(nd, TYPE_MCF_FEC_NET); + dev = qdev_new(TYPE_MCF_FEC_NET); + qdev_set_nic_properties(dev, nd); + + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + for (i = 0; i < FEC_NUM_IRQ; i++) { + sysbus_connect_irq(s, i, irqs[i]); + } + + memory_region_add_subregion(sysmem, base, sysbus_mmio_get_region(s, 0)); +} + +static void mcf5208evb_init(MachineState *machine) +{ + ram_addr_t ram_size = machine->ram_size; + const char *kernel_filename = machine->kernel_filename; + M68kCPU *cpu; + CPUM68KState *env; + int kernel_size; + uint64_t elf_entry; + hwaddr entry; + qemu_irq *pic; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *rom = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + + cpu = M68K_CPU(cpu_create(machine->cpu_type)); + env = &cpu->env; + + /* Initialize CPU registers. */ + env->vbr = 0; + /* TODO: Configure BARs. */ + + /* ROM at 0x00000000 */ + memory_region_init_rom(rom, NULL, "mcf5208.rom", ROM_SIZE, &error_fatal); + memory_region_add_subregion(address_space_mem, 0x00000000, rom); + + /* DRAM at 0x40000000 */ + memory_region_add_subregion(address_space_mem, 0x40000000, machine->ram); + + /* Internal SRAM. */ + memory_region_init_ram(sram, NULL, "mcf5208.sram", 16 * KiB, &error_fatal); + memory_region_add_subregion(address_space_mem, 0x80000000, sram); + + /* Internal peripherals. */ + pic = mcf_intc_init(address_space_mem, 0xfc048000, cpu); + + mcf_uart_mm_init(0xfc060000, pic[26], serial_hd(0)); + mcf_uart_mm_init(0xfc064000, pic[27], serial_hd(1)); + mcf_uart_mm_init(0xfc068000, pic[28], serial_hd(2)); + + mcf5208_sys_init(address_space_mem, pic); + + if (nb_nics > 1) { + error_report("Too many NICs"); + exit(1); + } + if (nd_table[0].used) { + mcf_fec_init(address_space_mem, &nd_table[0], + 0xfc030000, pic + 36); + } + + g_free(pic); + + /* 0xfc000000 SCM. */ + /* 0xfc004000 XBS. */ + /* 0xfc008000 FlexBus CS. */ + /* 0xfc030000 FEC. */ + /* 0xfc040000 SCM + Power management. */ + /* 0xfc044000 eDMA. */ + /* 0xfc048000 INTC. */ + /* 0xfc058000 I2C. */ + /* 0xfc05c000 QSPI. */ + /* 0xfc060000 UART0. */ + /* 0xfc064000 UART0. */ + /* 0xfc068000 UART0. */ + /* 0xfc070000 DMA timers. */ + /* 0xfc080000 PIT0. */ + /* 0xfc084000 PIT1. */ + /* 0xfc088000 EPORT. */ + /* 0xfc08c000 Watchdog. */ + /* 0xfc090000 clock module. */ + /* 0xfc0a0000 CCM + reset. */ + /* 0xfc0a4000 GPIO. */ + /* 0xfc0a8000 SDRAM controller. */ + + /* Load firmware */ + if (machine->firmware) { + char *fn; + uint8_t *ptr; + + fn = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware); + if (!fn) { + error_report("Could not find ROM image '%s'", machine->firmware); + exit(1); + } + if (load_image_targphys(fn, 0x0, ROM_SIZE) < 8) { + error_report("Could not load ROM image '%s'", machine->firmware); + exit(1); + } + g_free(fn); + /* Initial PC is always at offset 4 in firmware binaries */ + ptr = rom_ptr(0x4, 4); + assert(ptr != NULL); + env->pc = ldl_p(ptr); + } + + /* Load kernel. */ + if (!kernel_filename) { + if (qtest_enabled() || machine->firmware) { + return; + } + error_report("Kernel image must be specified"); + exit(1); + } + + kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, &elf_entry, + NULL, NULL, NULL, 1, EM_68K, 0, 0); + entry = elf_entry; + if (kernel_size < 0) { + kernel_size = load_uimage(kernel_filename, &entry, NULL, NULL, + NULL, NULL); + } + if (kernel_size < 0) { + kernel_size = load_image_targphys(kernel_filename, 0x40000000, + ram_size); + entry = 0x40000000; + } + if (kernel_size < 0) { + error_report("Could not load kernel '%s'", kernel_filename); + exit(1); + } + + env->pc = entry; +} + +static void mcf5208evb_machine_init(MachineClass *mc) +{ + mc->desc = "MCF5208EVB"; + mc->init = mcf5208evb_init; + mc->is_default = true; + mc->default_cpu_type = M68K_CPU_TYPE_NAME("m5208"); + mc->default_ram_id = "mcf5208.ram"; +} + +DEFINE_MACHINE("mcf5208evb", mcf5208evb_machine_init) diff --git a/hw/m68k/mcf_intc.c b/hw/m68k/mcf_intc.c new file mode 100644 index 000000000..4cd30188c --- /dev/null +++ b/hw/m68k/mcf_intc.c @@ -0,0 +1,217 @@ +/* + * ColdFire Interrupt Controller emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licensed under the GPL + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "qemu/log.h" +#include "cpu.h" +#include "hw/irq.h" +#include "hw/sysbus.h" +#include "hw/m68k/mcf.h" +#include "qom/object.h" + +#define TYPE_MCF_INTC "mcf-intc" +OBJECT_DECLARE_SIMPLE_TYPE(mcf_intc_state, MCF_INTC) + +struct mcf_intc_state { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint64_t ipr; + uint64_t imr; + uint64_t ifr; + uint64_t enabled; + uint8_t icr[64]; + M68kCPU *cpu; + int active_vector; +}; + +static void mcf_intc_update(mcf_intc_state *s) +{ + uint64_t active; + int i; + int best; + int best_level; + + active = (s->ipr | s->ifr) & s->enabled & ~s->imr; + best_level = 0; + best = 64; + if (active) { + for (i = 0; i < 64; i++) { + if ((active & 1) != 0 && s->icr[i] >= best_level) { + best_level = s->icr[i]; + best = i; + } + active >>= 1; + } + } + s->active_vector = ((best == 64) ? 24 : (best + 64)); + m68k_set_irq_level(s->cpu, best_level, s->active_vector); +} + +static uint64_t mcf_intc_read(void *opaque, hwaddr addr, + unsigned size) +{ + int offset; + mcf_intc_state *s = (mcf_intc_state *)opaque; + offset = addr & 0xff; + if (offset >= 0x40 && offset < 0x80) { + return s->icr[offset - 0x40]; + } + switch (offset) { + case 0x00: + return (uint32_t)(s->ipr >> 32); + case 0x04: + return (uint32_t)s->ipr; + case 0x08: + return (uint32_t)(s->imr >> 32); + case 0x0c: + return (uint32_t)s->imr; + case 0x10: + return (uint32_t)(s->ifr >> 32); + case 0x14: + return (uint32_t)s->ifr; + case 0xe0: /* SWIACK. */ + return s->active_vector; + case 0xe1: case 0xe2: case 0xe3: case 0xe4: + case 0xe5: case 0xe6: case 0xe7: + /* LnIACK */ + qemu_log_mask(LOG_UNIMP, "%s: LnIACK not implemented (offset 0x%02x)\n", + __func__, offset); + /* fallthru */ + default: + return 0; + } +} + +static void mcf_intc_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + int offset; + mcf_intc_state *s = (mcf_intc_state *)opaque; + offset = addr & 0xff; + if (offset >= 0x40 && offset < 0x80) { + int n = offset - 0x40; + s->icr[n] = val; + if (val == 0) + s->enabled &= ~(1ull << n); + else + s->enabled |= (1ull << n); + mcf_intc_update(s); + return; + } + switch (offset) { + case 0x00: case 0x04: + /* Ignore IPR writes. */ + return; + case 0x08: + s->imr = (s->imr & 0xffffffff) | ((uint64_t)val << 32); + break; + case 0x0c: + s->imr = (s->imr & 0xffffffff00000000ull) | (uint32_t)val; + break; + case 0x1c: + if (val & 0x40) { + s->imr = ~0ull; + } else { + s->imr |= (0x1ull << (val & 0x3f)); + } + break; + case 0x1d: + if (val & 0x40) { + s->imr = 0ull; + } else { + s->imr &= ~(0x1ull << (val & 0x3f)); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%02x\n", + __func__, offset); + return; + } + mcf_intc_update(s); +} + +static void mcf_intc_set_irq(void *opaque, int irq, int level) +{ + mcf_intc_state *s = (mcf_intc_state *)opaque; + if (irq >= 64) + return; + if (level) + s->ipr |= 1ull << irq; + else + s->ipr &= ~(1ull << irq); + mcf_intc_update(s); +} + +static void mcf_intc_reset(DeviceState *dev) +{ + mcf_intc_state *s = MCF_INTC(dev); + + s->imr = ~0ull; + s->ipr = 0; + s->ifr = 0; + s->enabled = 0; + memset(s->icr, 0, 64); + s->active_vector = 24; +} + +static const MemoryRegionOps mcf_intc_ops = { + .read = mcf_intc_read, + .write = mcf_intc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void mcf_intc_instance_init(Object *obj) +{ + mcf_intc_state *s = MCF_INTC(obj); + + memory_region_init_io(&s->iomem, obj, &mcf_intc_ops, s, "mcf", 0x100); +} + +static void mcf_intc_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + dc->reset = mcf_intc_reset; +} + +static const TypeInfo mcf_intc_gate_info = { + .name = TYPE_MCF_INTC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(mcf_intc_state), + .instance_init = mcf_intc_instance_init, + .class_init = mcf_intc_class_init, +}; + +static void mcf_intc_register_types(void) +{ + type_register_static(&mcf_intc_gate_info); +} + +type_init(mcf_intc_register_types) + +qemu_irq *mcf_intc_init(MemoryRegion *sysmem, + hwaddr base, + M68kCPU *cpu) +{ + DeviceState *dev; + mcf_intc_state *s; + + dev = qdev_new(TYPE_MCF_INTC); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + s = MCF_INTC(dev); + s->cpu = cpu; + + memory_region_add_subregion(sysmem, base, &s->iomem); + + return qemu_allocate_irqs(mcf_intc_set_irq, s, 64); +} diff --git a/hw/m68k/meson.build b/hw/m68k/meson.build new file mode 100644 index 000000000..31248641d --- /dev/null +++ b/hw/m68k/meson.build @@ -0,0 +1,8 @@ +m68k_ss = ss.source_set() +m68k_ss.add(when: 'CONFIG_AN5206', if_true: files('an5206.c', 'mcf5206.c')) +m68k_ss.add(when: 'CONFIG_MCF5208', if_true: files('mcf5208.c', 'mcf_intc.c')) +m68k_ss.add(when: 'CONFIG_NEXTCUBE', if_true: files('next-kbd.c', 'next-cube.c')) +m68k_ss.add(when: 'CONFIG_Q800', if_true: files('q800.c')) +m68k_ss.add(when: 'CONFIG_M68K_VIRT', if_true: files('virt.c')) + +hw_arch += {'m68k': m68k_ss} diff --git a/hw/m68k/next-cube.c b/hw/m68k/next-cube.c new file mode 100644 index 000000000..e0d4a94f9 --- /dev/null +++ b/hw/m68k/next-cube.c @@ -0,0 +1,1056 @@ +/* + * NeXT Cube System Driver + * + * Copyright (c) 2011 Bryce Lanham + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "exec/hwaddr.h" +#include "sysemu/sysemu.h" +#include "sysemu/qtest.h" +#include "hw/irq.h" +#include "hw/m68k/next-cube.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "hw/scsi/esp.h" +#include "hw/sysbus.h" +#include "qom/object.h" +#include "hw/char/escc.h" /* ZILOG 8530 Serial Emulation */ +#include "hw/block/fdc.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "ui/console.h" +#include "target/m68k/cpu.h" +#include "migration/vmstate.h" + +/* #define DEBUG_NEXT */ +#ifdef DEBUG_NEXT +#define DPRINTF(fmt, ...) \ + do { printf("NeXT: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + +#define TYPE_NEXT_MACHINE MACHINE_TYPE_NAME("next-cube") +OBJECT_DECLARE_SIMPLE_TYPE(NeXTState, NEXT_MACHINE) + +#define ENTRY 0x0100001e +#define RAM_SIZE 0x4000000 +#define ROM_FILE "Rev_2.5_v66.bin" + +typedef struct next_dma { + uint32_t csr; + + uint32_t saved_next; + uint32_t saved_limit; + uint32_t saved_start; + uint32_t saved_stop; + + uint32_t next; + uint32_t limit; + uint32_t start; + uint32_t stop; + + uint32_t next_initbuf; + uint32_t size; +} next_dma; + +typedef struct NextRtc { + uint8_t ram[32]; + uint8_t command; + uint8_t value; + uint8_t status; + uint8_t control; + uint8_t retval; +} NextRtc; + +struct NeXTState { + MachineState parent; + + next_dma dma[10]; +}; + +#define TYPE_NEXT_PC "next-pc" +OBJECT_DECLARE_SIMPLE_TYPE(NeXTPC, NEXT_PC) + +/* NeXT Peripheral Controller */ +struct NeXTPC { + SysBusDevice parent_obj; + + M68kCPU *cpu; + + MemoryRegion mmiomem; + MemoryRegion scrmem; + + uint32_t scr1; + uint32_t scr2; + uint8_t scsi_csr_1; + uint8_t scsi_csr_2; + uint32_t int_mask; + uint32_t int_status; + + NextRtc rtc; +}; + +/* Thanks to NeXT forums for this */ +/* +static const uint8_t rtc_ram3[32] = { + 0x94, 0x0f, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfb, 0x6d, 0x00, 0x00, 0x7B, 0x00, + 0x00, 0x00, 0x65, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x13 +}; +*/ +static const uint8_t rtc_ram2[32] = { + 0x94, 0x0f, 0x40, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfb, 0x6d, 0x00, 0x00, 0x4b, 0x00, + 0x41, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x7e, +}; + +#define SCR2_RTCLK 0x2 +#define SCR2_RTDATA 0x4 +#define SCR2_TOBCD(x) (((x / 10) << 4) + (x % 10)) + +static void nextscr2_write(NeXTPC *s, uint32_t val, int size) +{ + static int led; + static int phase; + static uint8_t old_scr2; + uint8_t scr2_2; + NextRtc *rtc = &s->rtc; + + if (size == 4) { + scr2_2 = (val >> 8) & 0xFF; + } else { + scr2_2 = val & 0xFF; + } + + if (val & 0x1) { + DPRINTF("fault!\n"); + led++; + if (led == 10) { + DPRINTF("LED flashing, possible fault!\n"); + led = 0; + } + } + + if (scr2_2 & 0x1) { + /* DPRINTF("RTC %x phase %i\n", scr2_2, phase); */ + if (phase == -1) { + phase = 0; + } + /* If we are in going down clock... do something */ + if (((old_scr2 & SCR2_RTCLK) != (scr2_2 & SCR2_RTCLK)) && + ((scr2_2 & SCR2_RTCLK) == 0)) { + if (phase < 8) { + rtc->command = (rtc->command << 1) | + ((scr2_2 & SCR2_RTDATA) ? 1 : 0); + } + if (phase >= 8 && phase < 16) { + rtc->value = (rtc->value << 1) | + ((scr2_2 & SCR2_RTDATA) ? 1 : 0); + + /* if we read RAM register, output RT_DATA bit */ + if (rtc->command <= 0x1F) { + scr2_2 = scr2_2 & (~SCR2_RTDATA); + if (rtc->ram[rtc->command] & (0x80 >> (phase - 8))) { + scr2_2 |= SCR2_RTDATA; + } + + rtc->retval = (rtc->retval << 1) | + ((scr2_2 & SCR2_RTDATA) ? 1 : 0); + } + /* read the status 0x30 */ + if (rtc->command == 0x30) { + scr2_2 = scr2_2 & (~SCR2_RTDATA); + /* for now status = 0x98 (new rtc + FTU) */ + if (rtc->status & (0x80 >> (phase - 8))) { + scr2_2 |= SCR2_RTDATA; + } + + rtc->retval = (rtc->retval << 1) | + ((scr2_2 & SCR2_RTDATA) ? 1 : 0); + } + /* read the status 0x31 */ + if (rtc->command == 0x31) { + scr2_2 = scr2_2 & (~SCR2_RTDATA); + if (rtc->control & (0x80 >> (phase - 8))) { + scr2_2 |= SCR2_RTDATA; + } + rtc->retval = (rtc->retval << 1) | + ((scr2_2 & SCR2_RTDATA) ? 1 : 0); + } + + if ((rtc->command >= 0x20) && (rtc->command <= 0x2F)) { + scr2_2 = scr2_2 & (~SCR2_RTDATA); + /* for now 0x00 */ + time_t time_h = time(NULL); + struct tm *info = localtime(&time_h); + int ret = 0; + + switch (rtc->command) { + case 0x20: + ret = SCR2_TOBCD(info->tm_sec); + break; + case 0x21: + ret = SCR2_TOBCD(info->tm_min); + break; + case 0x22: + ret = SCR2_TOBCD(info->tm_hour); + break; + case 0x24: + ret = SCR2_TOBCD(info->tm_mday); + break; + case 0x25: + ret = SCR2_TOBCD((info->tm_mon + 1)); + break; + case 0x26: + ret = SCR2_TOBCD((info->tm_year - 100)); + break; + + } + + if (ret & (0x80 >> (phase - 8))) { + scr2_2 |= SCR2_RTDATA; + } + rtc->retval = (rtc->retval << 1) | + ((scr2_2 & SCR2_RTDATA) ? 1 : 0); + } + + } + + phase++; + if (phase == 16) { + if (rtc->command >= 0x80 && rtc->command <= 0x9F) { + rtc->ram[rtc->command - 0x80] = rtc->value; + } + /* write to x30 register */ + if (rtc->command == 0xB1) { + /* clear FTU */ + if (rtc->value & 0x04) { + rtc->status = rtc->status & (~0x18); + s->int_status = s->int_status & (~0x04); + } + } + } + } + } else { + /* else end or abort */ + phase = -1; + rtc->command = 0; + rtc->value = 0; + } + s->scr2 = val & 0xFFFF00FF; + s->scr2 |= scr2_2 << 8; + old_scr2 = scr2_2; +} + +static uint32_t mmio_readb(NeXTPC *s, hwaddr addr) +{ + switch (addr) { + case 0xc000: + return (s->scr1 >> 24) & 0xFF; + case 0xc001: + return (s->scr1 >> 16) & 0xFF; + case 0xc002: + return (s->scr1 >> 8) & 0xFF; + case 0xc003: + return (s->scr1 >> 0) & 0xFF; + + case 0xd000: + return (s->scr2 >> 24) & 0xFF; + case 0xd001: + return (s->scr2 >> 16) & 0xFF; + case 0xd002: + return (s->scr2 >> 8) & 0xFF; + case 0xd003: + return (s->scr2 >> 0) & 0xFF; + case 0x14020: + DPRINTF("MMIO Read 0x4020\n"); + return 0x7f; + + default: + DPRINTF("MMIO Read B @ %"HWADDR_PRIx"\n", addr); + return 0x0; + } +} + +static uint32_t mmio_readw(NeXTPC *s, hwaddr addr) +{ + switch (addr) { + default: + DPRINTF("MMIO Read W @ %"HWADDR_PRIx"\n", addr); + return 0x0; + } +} + +static uint32_t mmio_readl(NeXTPC *s, hwaddr addr) +{ + switch (addr) { + case 0x7000: + /* DPRINTF("Read INT status: %x\n", s->int_status); */ + return s->int_status; + + case 0x7800: + DPRINTF("MMIO Read INT mask: %x\n", s->int_mask); + return s->int_mask; + + case 0xc000: + return s->scr1; + + case 0xd000: + return s->scr2; + + default: + DPRINTF("MMIO Read L @ %"HWADDR_PRIx"\n", addr); + return 0x0; + } +} + +static void mmio_writeb(NeXTPC *s, hwaddr addr, uint32_t val) +{ + switch (addr) { + case 0xd003: + nextscr2_write(s, val, 1); + break; + default: + DPRINTF("MMIO Write B @ %x with %x\n", (unsigned int)addr, val); + } + +} + +static void mmio_writew(NeXTPC *s, hwaddr addr, uint32_t val) +{ + DPRINTF("MMIO Write W\n"); +} + +static void mmio_writel(NeXTPC *s, hwaddr addr, uint32_t val) +{ + switch (addr) { + case 0x7000: + DPRINTF("INT Status old: %x new: %x\n", s->int_status, val); + s->int_status = val; + break; + case 0x7800: + DPRINTF("INT Mask old: %x new: %x\n", s->int_mask, val); + s->int_mask = val; + break; + case 0xc000: + DPRINTF("SCR1 Write: %x\n", val); + break; + case 0xd000: + nextscr2_write(s, val, 4); + break; + + default: + DPRINTF("MMIO Write l @ %x with %x\n", (unsigned int)addr, val); + } +} + +static uint64_t mmio_readfn(void *opaque, hwaddr addr, unsigned size) +{ + NeXTPC *s = NEXT_PC(opaque); + + switch (size) { + case 1: + return mmio_readb(s, addr); + case 2: + return mmio_readw(s, addr); + case 4: + return mmio_readl(s, addr); + default: + g_assert_not_reached(); + } +} + +static void mmio_writefn(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + NeXTPC *s = NEXT_PC(opaque); + + switch (size) { + case 1: + mmio_writeb(s, addr, value); + break; + case 2: + mmio_writew(s, addr, value); + break; + case 4: + mmio_writel(s, addr, value); + break; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps mmio_ops = { + .read = mmio_readfn, + .write = mmio_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint32_t scr_readb(NeXTPC *s, hwaddr addr) +{ + switch (addr) { + case 0x14108: + DPRINTF("FD read @ %x\n", (unsigned int)addr); + return 0x40 | 0x04 | 0x2 | 0x1; + case 0x14020: + DPRINTF("SCSI 4020 STATUS READ %X\n", s->scsi_csr_1); + return s->scsi_csr_1; + + case 0x14021: + DPRINTF("SCSI 4021 STATUS READ %X\n", s->scsi_csr_2); + return 0x40; + + /* + * These 4 registers are the hardware timer, not sure which register + * is the latch instead of data, but no problems so far + */ + case 0x1a000: + return 0xff & (clock() >> 24); + case 0x1a001: + return 0xff & (clock() >> 16); + case 0x1a002: + return 0xff & (clock() >> 8); + case 0x1a003: + /* Hack: We need to have this change consistently to make it work */ + return 0xFF & clock(); + + default: + DPRINTF("BMAP Read B @ %x\n", (unsigned int)addr); + return 0; + } +} + +static uint32_t scr_readw(NeXTPC *s, hwaddr addr) +{ + DPRINTF("BMAP Read W @ %x\n", (unsigned int)addr); + return 0; +} + +static uint32_t scr_readl(NeXTPC *s, hwaddr addr) +{ + DPRINTF("BMAP Read L @ %x\n", (unsigned int)addr); + return 0; +} + +#define SCSICSR_ENABLE 0x01 +#define SCSICSR_RESET 0x02 /* reset scsi dma */ +#define SCSICSR_FIFOFL 0x04 +#define SCSICSR_DMADIR 0x08 /* if set, scsi to mem */ +#define SCSICSR_CPUDMA 0x10 /* if set, dma enabled */ +#define SCSICSR_INTMASK 0x20 /* if set, interrupt enabled */ + +static void scr_writeb(NeXTPC *s, hwaddr addr, uint32_t value) +{ + switch (addr) { + case 0x14108: + DPRINTF("FDCSR Write: %x\n", value); + + if (value == 0x0) { + /* qemu_irq_raise(s->fd_irq[0]); */ + } + break; + case 0x14020: /* SCSI Control Register */ + if (value & SCSICSR_FIFOFL) { + DPRINTF("SCSICSR FIFO Flush\n"); + /* will have to add another irq to the esp if this is needed */ + /* esp_puflush_fifo(esp_g); */ + /* qemu_irq_pulse(s->scsi_dma); */ + } + + if (value & SCSICSR_ENABLE) { + DPRINTF("SCSICSR Enable\n"); + /* + * qemu_irq_raise(s->scsi_dma); + * s->scsi_csr_1 = 0xc0; + * s->scsi_csr_1 |= 0x1; + * qemu_irq_pulse(s->scsi_dma); + */ + } + /* + * else + * s->scsi_csr_1 &= ~SCSICSR_ENABLE; + */ + + if (value & SCSICSR_RESET) { + DPRINTF("SCSICSR Reset\n"); + /* I think this should set DMADIR. CPUDMA and INTMASK to 0 */ + /* qemu_irq_raise(s->scsi_reset); */ + /* s->scsi_csr_1 &= ~(SCSICSR_INTMASK |0x80|0x1); */ + + } + if (value & SCSICSR_DMADIR) { + DPRINTF("SCSICSR DMAdir\n"); + } + if (value & SCSICSR_CPUDMA) { + DPRINTF("SCSICSR CPUDMA\n"); + /* qemu_irq_raise(s->scsi_dma); */ + + s->int_status |= 0x4000000; + } else { + s->int_status &= ~(0x4000000); + } + if (value & SCSICSR_INTMASK) { + DPRINTF("SCSICSR INTMASK\n"); + /* + * int_mask &= ~0x1000; + * s->scsi_csr_1 |= value; + * s->scsi_csr_1 &= ~SCSICSR_INTMASK; + * if (s->scsi_queued) { + * s->scsi_queued = 0; + * next_irq(s, NEXT_SCSI_I, level); + * } + */ + } else { + /* int_mask |= 0x1000; */ + } + if (value & 0x80) { + /* int_mask |= 0x1000; */ + /* s->scsi_csr_1 |= 0x80; */ + } + DPRINTF("SCSICSR Write: %x\n", value); + /* s->scsi_csr_1 = value; */ + return; + /* Hardware timer latch - not implemented yet */ + case 0x1a000: + default: + DPRINTF("BMAP Write B @ %x with %x\n", (unsigned int)addr, value); + } +} + +static void scr_writew(NeXTPC *s, hwaddr addr, uint32_t value) +{ + DPRINTF("BMAP Write W @ %x with %x\n", (unsigned int)addr, value); +} + +static void scr_writel(NeXTPC *s, hwaddr addr, uint32_t value) +{ + DPRINTF("BMAP Write L @ %x with %x\n", (unsigned int)addr, value); +} + +static uint64_t scr_readfn(void *opaque, hwaddr addr, unsigned size) +{ + NeXTPC *s = NEXT_PC(opaque); + + switch (size) { + case 1: + return scr_readb(s, addr); + case 2: + return scr_readw(s, addr); + case 4: + return scr_readl(s, addr); + default: + g_assert_not_reached(); + } +} + +static void scr_writefn(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + NeXTPC *s = NEXT_PC(opaque); + + switch (size) { + case 1: + scr_writeb(s, addr, value); + break; + case 2: + scr_writew(s, addr, value); + break; + case 4: + scr_writel(s, addr, value); + break; + default: + g_assert_not_reached(); + } +} + +static const MemoryRegionOps scr_ops = { + .read = scr_readfn, + .write = scr_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +#define NEXTDMA_SCSI(x) (0x10 + x) +#define NEXTDMA_FD(x) (0x10 + x) +#define NEXTDMA_ENTX(x) (0x110 + x) +#define NEXTDMA_ENRX(x) (0x150 + x) +#define NEXTDMA_CSR 0x0 +#define NEXTDMA_NEXT 0x4000 +#define NEXTDMA_LIMIT 0x4004 +#define NEXTDMA_START 0x4008 +#define NEXTDMA_STOP 0x400c +#define NEXTDMA_NEXT_INIT 0x4200 +#define NEXTDMA_SIZE 0x4204 + +static void dma_writel(void *opaque, hwaddr addr, uint64_t value, + unsigned int size) +{ + NeXTState *next_state = NEXT_MACHINE(opaque); + + switch (addr) { + case NEXTDMA_ENRX(NEXTDMA_CSR): + if (value & DMA_DEV2M) { + next_state->dma[NEXTDMA_ENRX].csr |= DMA_DEV2M; + } + + if (value & DMA_SETENABLE) { + /* DPRINTF("SCSI DMA ENABLE\n"); */ + next_state->dma[NEXTDMA_ENRX].csr |= DMA_ENABLE; + } + if (value & DMA_SETSUPDATE) { + next_state->dma[NEXTDMA_ENRX].csr |= DMA_SUPDATE; + } + if (value & DMA_CLRCOMPLETE) { + next_state->dma[NEXTDMA_ENRX].csr &= ~DMA_COMPLETE; + } + + if (value & DMA_RESET) { + next_state->dma[NEXTDMA_ENRX].csr &= ~(DMA_COMPLETE | DMA_SUPDATE | + DMA_ENABLE | DMA_DEV2M); + } + /* DPRINTF("RXCSR \tWrite: %x\n",value); */ + break; + case NEXTDMA_ENRX(NEXTDMA_NEXT_INIT): + next_state->dma[NEXTDMA_ENRX].next_initbuf = value; + break; + case NEXTDMA_ENRX(NEXTDMA_NEXT): + next_state->dma[NEXTDMA_ENRX].next = value; + break; + case NEXTDMA_ENRX(NEXTDMA_LIMIT): + next_state->dma[NEXTDMA_ENRX].limit = value; + break; + case NEXTDMA_SCSI(NEXTDMA_CSR): + if (value & DMA_DEV2M) { + next_state->dma[NEXTDMA_SCSI].csr |= DMA_DEV2M; + } + if (value & DMA_SETENABLE) { + /* DPRINTF("SCSI DMA ENABLE\n"); */ + next_state->dma[NEXTDMA_SCSI].csr |= DMA_ENABLE; + } + if (value & DMA_SETSUPDATE) { + next_state->dma[NEXTDMA_SCSI].csr |= DMA_SUPDATE; + } + if (value & DMA_CLRCOMPLETE) { + next_state->dma[NEXTDMA_SCSI].csr &= ~DMA_COMPLETE; + } + + if (value & DMA_RESET) { + next_state->dma[NEXTDMA_SCSI].csr &= ~(DMA_COMPLETE | DMA_SUPDATE | + DMA_ENABLE | DMA_DEV2M); + /* DPRINTF("SCSI DMA RESET\n"); */ + } + /* DPRINTF("RXCSR \tWrite: %x\n",value); */ + break; + + case NEXTDMA_SCSI(NEXTDMA_NEXT): + next_state->dma[NEXTDMA_SCSI].next = value; + break; + + case NEXTDMA_SCSI(NEXTDMA_LIMIT): + next_state->dma[NEXTDMA_SCSI].limit = value; + break; + + case NEXTDMA_SCSI(NEXTDMA_START): + next_state->dma[NEXTDMA_SCSI].start = value; + break; + + case NEXTDMA_SCSI(NEXTDMA_STOP): + next_state->dma[NEXTDMA_SCSI].stop = value; + break; + + case NEXTDMA_SCSI(NEXTDMA_NEXT_INIT): + next_state->dma[NEXTDMA_SCSI].next_initbuf = value; + break; + + default: + DPRINTF("DMA write @ %x w/ %x\n", (unsigned)addr, (unsigned)value); + } +} + +static uint64_t dma_readl(void *opaque, hwaddr addr, unsigned int size) +{ + NeXTState *next_state = NEXT_MACHINE(opaque); + + switch (addr) { + case NEXTDMA_SCSI(NEXTDMA_CSR): + DPRINTF("SCSI DMA CSR READ\n"); + return next_state->dma[NEXTDMA_SCSI].csr; + case NEXTDMA_ENRX(NEXTDMA_CSR): + return next_state->dma[NEXTDMA_ENRX].csr; + case NEXTDMA_ENRX(NEXTDMA_NEXT_INIT): + return next_state->dma[NEXTDMA_ENRX].next_initbuf; + case NEXTDMA_ENRX(NEXTDMA_NEXT): + return next_state->dma[NEXTDMA_ENRX].next; + case NEXTDMA_ENRX(NEXTDMA_LIMIT): + return next_state->dma[NEXTDMA_ENRX].limit; + + case NEXTDMA_SCSI(NEXTDMA_NEXT): + return next_state->dma[NEXTDMA_SCSI].next; + case NEXTDMA_SCSI(NEXTDMA_NEXT_INIT): + return next_state->dma[NEXTDMA_SCSI].next_initbuf; + case NEXTDMA_SCSI(NEXTDMA_LIMIT): + return next_state->dma[NEXTDMA_SCSI].limit; + case NEXTDMA_SCSI(NEXTDMA_START): + return next_state->dma[NEXTDMA_SCSI].start; + case NEXTDMA_SCSI(NEXTDMA_STOP): + return next_state->dma[NEXTDMA_SCSI].stop; + + default: + DPRINTF("DMA read @ %x\n", (unsigned int)addr); + return 0; + } + + /* + * once the csr's are done, subtract 0x3FEC from the addr, and that will + * normalize the upper registers + */ +} + +static const MemoryRegionOps dma_ops = { + .read = dma_readl, + .write = dma_writel, + .impl.min_access_size = 4, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void next_irq(void *opaque, int number, int level) +{ + NeXTPC *s = NEXT_PC(opaque); + M68kCPU *cpu = s->cpu; + int shift = 0; + + /* first switch sets interupt status */ + /* DPRINTF("IRQ %i\n",number); */ + switch (number) { + /* level 3 - floppy, kbd/mouse, power, ether rx/tx, scsi, clock */ + case NEXT_FD_I: + shift = 7; + break; + case NEXT_KBD_I: + shift = 3; + break; + case NEXT_PWR_I: + shift = 2; + break; + case NEXT_ENRX_I: + shift = 9; + break; + case NEXT_ENTX_I: + shift = 10; + break; + case NEXT_SCSI_I: + shift = 12; + break; + case NEXT_CLK_I: + shift = 5; + break; + + /* level 5 - scc (serial) */ + case NEXT_SCC_I: + shift = 17; + break; + + /* level 6 - audio etherrx/tx dma */ + case NEXT_ENTX_DMA_I: + shift = 28; + break; + case NEXT_ENRX_DMA_I: + shift = 27; + break; + case NEXT_SCSI_DMA_I: + shift = 26; + break; + case NEXT_SND_I: + shift = 23; + break; + case NEXT_SCC_DMA_I: + shift = 21; + break; + + } + /* + * this HAS to be wrong, the interrupt handlers in mach and together + * int_status and int_mask and return if there is a hit + */ + if (s->int_mask & (1 << shift)) { + DPRINTF("%x interrupt masked @ %x\n", 1 << shift, cpu->env.pc); + /* return; */ + } + + /* second switch triggers the correct interrupt */ + if (level) { + s->int_status |= 1 << shift; + + switch (number) { + /* level 3 - floppy, kbd/mouse, power, ether rx/tx, scsi, clock */ + case NEXT_FD_I: + case NEXT_KBD_I: + case NEXT_PWR_I: + case NEXT_ENRX_I: + case NEXT_ENTX_I: + case NEXT_SCSI_I: + case NEXT_CLK_I: + m68k_set_irq_level(cpu, 3, 27); + break; + + /* level 5 - scc (serial) */ + case NEXT_SCC_I: + m68k_set_irq_level(cpu, 5, 29); + break; + + /* level 6 - audio etherrx/tx dma */ + case NEXT_ENTX_DMA_I: + case NEXT_ENRX_DMA_I: + case NEXT_SCSI_DMA_I: + case NEXT_SND_I: + case NEXT_SCC_DMA_I: + m68k_set_irq_level(cpu, 6, 30); + break; + } + } else { + s->int_status &= ~(1 << shift); + cpu_reset_interrupt(CPU(cpu), CPU_INTERRUPT_HARD); + } +} + +static void next_escc_init(DeviceState *pcdev) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new(TYPE_ESCC); + qdev_prop_set_uint32(dev, "disabled", 0); + qdev_prop_set_uint32(dev, "frequency", 9600 * 384); + qdev_prop_set_uint32(dev, "it_shift", 0); + qdev_prop_set_bit(dev, "bit_swap", true); + qdev_prop_set_chr(dev, "chrB", serial_hd(1)); + qdev_prop_set_chr(dev, "chrA", serial_hd(0)); + qdev_prop_set_uint32(dev, "chnBtype", escc_serial); + qdev_prop_set_uint32(dev, "chnAtype", escc_serial); + + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(pcdev, NEXT_SCC_I)); + sysbus_connect_irq(s, 1, qdev_get_gpio_in(pcdev, NEXT_SCC_DMA_I)); + sysbus_mmio_map(s, 0, 0x2118000); +} + +static void next_pc_reset(DeviceState *dev) +{ + NeXTPC *s = NEXT_PC(dev); + + /* Set internal registers to initial values */ + /* 0x0000XX00 << vital bits */ + s->scr1 = 0x00011102; + s->scr2 = 0x00ff0c80; + + s->rtc.status = 0x90; + + /* Load RTC RAM - TODO: provide possibility to load contents from file */ + memcpy(s->rtc.ram, rtc_ram2, 32); +} + +static void next_pc_realize(DeviceState *dev, Error **errp) +{ + NeXTPC *s = NEXT_PC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + qdev_init_gpio_in(dev, next_irq, NEXT_NUM_IRQS); + + memory_region_init_io(&s->mmiomem, OBJECT(s), &mmio_ops, s, + "next.mmio", 0xD0000); + memory_region_init_io(&s->scrmem, OBJECT(s), &scr_ops, s, + "next.scr", 0x20000); + sysbus_init_mmio(sbd, &s->mmiomem); + sysbus_init_mmio(sbd, &s->scrmem); +} + +/* + * If the m68k CPU implemented its inbound irq lines as GPIO lines + * rather than via the m68k_set_irq_level() function we would not need + * this cpu link property and could instead provide outbound IRQ lines + * that the board could wire up to the CPU. + */ +static Property next_pc_properties[] = { + DEFINE_PROP_LINK("cpu", NeXTPC, cpu, TYPE_M68K_CPU, M68kCPU *), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription next_rtc_vmstate = { + .name = "next-rtc", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY(ram, NextRtc, 32), + VMSTATE_UINT8(command, NextRtc), + VMSTATE_UINT8(value, NextRtc), + VMSTATE_UINT8(status, NextRtc), + VMSTATE_UINT8(control, NextRtc), + VMSTATE_UINT8(retval, NextRtc), + VMSTATE_END_OF_LIST() + }, +}; + +static const VMStateDescription next_pc_vmstate = { + .name = "next-pc", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(scr1, NeXTPC), + VMSTATE_UINT32(scr2, NeXTPC), + VMSTATE_UINT32(int_mask, NeXTPC), + VMSTATE_UINT32(int_status, NeXTPC), + VMSTATE_UINT8(scsi_csr_1, NeXTPC), + VMSTATE_UINT8(scsi_csr_2, NeXTPC), + VMSTATE_STRUCT(rtc, NeXTPC, 0, next_rtc_vmstate, NextRtc), + VMSTATE_END_OF_LIST() + }, +}; + +static void next_pc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "NeXT Peripheral Controller"; + dc->realize = next_pc_realize; + dc->reset = next_pc_reset; + device_class_set_props(dc, next_pc_properties); + dc->vmsd = &next_pc_vmstate; +} + +static const TypeInfo next_pc_info = { + .name = TYPE_NEXT_PC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NeXTPC), + .class_init = next_pc_class_init, +}; + +static void next_cube_init(MachineState *machine) +{ + M68kCPU *cpu; + CPUM68KState *env; + MemoryRegion *rom = g_new(MemoryRegion, 1); + MemoryRegion *dmamem = g_new(MemoryRegion, 1); + MemoryRegion *bmapm1 = g_new(MemoryRegion, 1); + MemoryRegion *bmapm2 = g_new(MemoryRegion, 1); + MemoryRegion *sysmem = get_system_memory(); + const char *bios_name = machine->firmware ?: ROM_FILE; + DeviceState *dev; + DeviceState *pcdev; + + /* Initialize the cpu core */ + cpu = M68K_CPU(cpu_create(machine->cpu_type)); + if (!cpu) { + error_report("Unable to find m68k CPU definition"); + exit(1); + } + env = &cpu->env; + + /* Initialize CPU registers. */ + env->vbr = 0; + env->sr = 0x2700; + + /* Peripheral Controller */ + pcdev = qdev_new(TYPE_NEXT_PC); + object_property_set_link(OBJECT(pcdev), "cpu", OBJECT(cpu), &error_abort); + sysbus_realize_and_unref(SYS_BUS_DEVICE(pcdev), &error_fatal); + + /* 64MB RAM starting at 0x04000000 */ + memory_region_add_subregion(sysmem, 0x04000000, machine->ram); + + /* Framebuffer */ + dev = qdev_new(TYPE_NEXTFB); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x0B000000); + + /* MMIO */ + sysbus_mmio_map(SYS_BUS_DEVICE(pcdev), 0, 0x02000000); + + /* BMAP IO - acts as a catch-all for now */ + sysbus_mmio_map(SYS_BUS_DEVICE(pcdev), 1, 0x02100000); + + /* BMAP memory */ + memory_region_init_ram_flags_nomigrate(bmapm1, NULL, "next.bmapmem", 64, + RAM_SHARED, &error_fatal); + memory_region_add_subregion(sysmem, 0x020c0000, bmapm1); + /* The Rev_2.5_v66.bin firmware accesses it at 0x820c0020, too */ + memory_region_init_alias(bmapm2, NULL, "next.bmapmem2", bmapm1, 0x0, 64); + memory_region_add_subregion(sysmem, 0x820c0000, bmapm2); + + /* KBD */ + dev = qdev_new(TYPE_NEXTKBD); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x0200e000); + + /* Load ROM here */ + /* still not sure if the rom should also be mapped at 0x0*/ + memory_region_init_rom(rom, NULL, "next.rom", 0x20000, &error_fatal); + memory_region_add_subregion(sysmem, 0x01000000, rom); + if (load_image_targphys(bios_name, 0x01000000, 0x20000) < 8) { + if (!qtest_enabled()) { + error_report("Failed to load firmware '%s'.", bios_name); + } + } else { + uint8_t *ptr; + /* Initial PC is always at offset 4 in firmware binaries */ + ptr = rom_ptr(0x01000004, 4); + g_assert(ptr != NULL); + env->pc = ldl_p(ptr); + if (env->pc >= 0x01020000) { + error_report("'%s' does not seem to be a valid firmware image.", + bios_name); + exit(1); + } + } + + /* Serial */ + next_escc_init(pcdev); + + /* TODO: */ + /* Network */ + /* SCSI */ + + /* DMA */ + memory_region_init_io(dmamem, NULL, &dma_ops, machine, "next.dma", 0x5000); + memory_region_add_subregion(sysmem, 0x02000000, dmamem); +} + +static void next_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->desc = "NeXT Cube"; + mc->init = next_cube_init; + mc->default_ram_size = RAM_SIZE; + mc->default_ram_id = "next.ram"; + mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040"); +} + +static const TypeInfo next_typeinfo = { + .name = TYPE_NEXT_MACHINE, + .parent = TYPE_MACHINE, + .class_init = next_machine_class_init, + .instance_size = sizeof(NeXTState), +}; + +static void next_register_type(void) +{ + type_register_static(&next_typeinfo); + type_register_static(&next_pc_info); +} + +type_init(next_register_type) diff --git a/hw/m68k/next-kbd.c b/hw/m68k/next-kbd.c new file mode 100644 index 000000000..0544160e9 --- /dev/null +++ b/hw/m68k/next-kbd.c @@ -0,0 +1,289 @@ +/* + * QEMU NeXT Keyboard/Mouse emulation + * + * Copyright (c) 2011 Bryce Lanham + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * This is admittedly hackish, but works well enough for basic input. Mouse + * support will be added once we can boot something that needs the mouse. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/sysbus.h" +#include "hw/m68k/next-cube.h" +#include "ui/console.h" +#include "migration/vmstate.h" +#include "qom/object.h" + +OBJECT_DECLARE_SIMPLE_TYPE(NextKBDState, NEXTKBD) + +/* following defintions from next68k netbsd */ +#define CSR_INT 0x00800000 +#define CSR_DATA 0x00400000 + +#define KD_KEYMASK 0x007f +#define KD_DIRECTION 0x0080 /* pressed or released */ +#define KD_CNTL 0x0100 +#define KD_LSHIFT 0x0200 +#define KD_RSHIFT 0x0400 +#define KD_LCOMM 0x0800 +#define KD_RCOMM 0x1000 +#define KD_LALT 0x2000 +#define KD_RALT 0x4000 +#define KD_VALID 0x8000 /* only set for scancode keys ? */ +#define KD_MODS 0x4f00 + +#define KBD_QUEUE_SIZE 256 + +typedef struct { + uint8_t data[KBD_QUEUE_SIZE]; + int rptr, wptr, count; +} KBDQueue; + + +struct NextKBDState { + SysBusDevice sbd; + MemoryRegion mr; + KBDQueue queue; + uint16_t shift; +}; + +static void queue_code(void *opaque, int code); + +/* lots of magic numbers here */ +static uint32_t kbd_read_byte(void *opaque, hwaddr addr) +{ + switch (addr & 0x3) { + case 0x0: /* 0xe000 */ + return 0x80 | 0x20; + + case 0x1: /* 0xe001 */ + return 0x80 | 0x40 | 0x20 | 0x10; + + case 0x2: /* 0xe002 */ + /* returning 0x40 caused mach to hang */ + return 0x10 | 0x2 | 0x1; + + default: + qemu_log_mask(LOG_UNIMP, "NeXT kbd read byte %"HWADDR_PRIx"\n", addr); + } + + return 0; +} + +static uint32_t kbd_read_word(void *opaque, hwaddr addr) +{ + qemu_log_mask(LOG_UNIMP, "NeXT kbd read word %"HWADDR_PRIx"\n", addr); + return 0; +} + +/* even more magic numbers */ +static uint32_t kbd_read_long(void *opaque, hwaddr addr) +{ + int key = 0; + NextKBDState *s = NEXTKBD(opaque); + KBDQueue *q = &s->queue; + + switch (addr & 0xf) { + case 0x0: /* 0xe000 */ + return 0xA0F09300; + + case 0x8: /* 0xe008 */ + /* get keycode from buffer */ + if (q->count > 0) { + key = q->data[q->rptr]; + if (++q->rptr == KBD_QUEUE_SIZE) { + q->rptr = 0; + } + + q->count--; + + if (s->shift) { + key |= s->shift; + } + + if (key & 0x80) { + return 0; + } else { + return 0x10000000 | KD_VALID | key; + } + } else { + return 0; + } + + default: + qemu_log_mask(LOG_UNIMP, "NeXT kbd read long %"HWADDR_PRIx"\n", addr); + return 0; + } +} + +static uint64_t kbd_readfn(void *opaque, hwaddr addr, unsigned size) +{ + switch (size) { + case 1: + return kbd_read_byte(opaque, addr); + case 2: + return kbd_read_word(opaque, addr); + case 4: + return kbd_read_long(opaque, addr); + default: + g_assert_not_reached(); + } +} + +static void kbd_writefn(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + qemu_log_mask(LOG_UNIMP, "NeXT kbd write: size=%u addr=0x%"HWADDR_PRIx + "val=0x%"PRIx64"\n", size, addr, value); +} + +static const MemoryRegionOps kbd_ops = { + .read = kbd_readfn, + .write = kbd_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void nextkbd_event(void *opaque, int ch) +{ + /* + * Will want to set vars for caps/num lock + * if (ch & 0x80) -> key release + * there's also e0 escaped scancodes that might need to be handled + */ + queue_code(opaque, ch); +} + +static const unsigned char next_keycodes[128] = { + 0x00, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x50, 0x4F, + 0x4E, 0x1E, 0x1F, 0x20, 0x1D, 0x1C, 0x1B, 0x00, + 0x42, 0x43, 0x44, 0x45, 0x48, 0x47, 0x46, 0x06, + 0x07, 0x08, 0x00, 0x00, 0x2A, 0x00, 0x39, 0x3A, + 0x3B, 0x3C, 0x3D, 0x40, 0x3F, 0x3E, 0x2D, 0x2C, + 0x2B, 0x26, 0x00, 0x00, 0x31, 0x32, 0x33, 0x34, + 0x35, 0x37, 0x36, 0x2e, 0x2f, 0x30, 0x00, 0x00, + 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static void queue_code(void *opaque, int code) +{ + NextKBDState *s = NEXTKBD(opaque); + KBDQueue *q = &s->queue; + int key = code & KD_KEYMASK; + int release = code & 0x80; + static int ext; + + if (code == 0xE0) { + ext = 1; + } + + if (code == 0x2A || code == 0x1D || code == 0x36) { + if (code == 0x2A) { + s->shift = KD_LSHIFT; + } else if (code == 0x36) { + s->shift = KD_RSHIFT; + ext = 0; + } else if (code == 0x1D && !ext) { + s->shift = KD_LCOMM; + } else if (code == 0x1D && ext) { + ext = 0; + s->shift = KD_RCOMM; + } + return; + } else if (code == (0x2A | 0x80) || code == (0x1D | 0x80) || + code == (0x36 | 0x80)) { + s->shift = 0; + return; + } + + if (q->count >= KBD_QUEUE_SIZE) { + return; + } + + q->data[q->wptr] = next_keycodes[key] | release; + + if (++q->wptr == KBD_QUEUE_SIZE) { + q->wptr = 0; + } + + q->count++; + + /* + * might need to actually trigger the NeXT irq, but as the keyboard works + * at the moment, I'll worry about it later + */ + /* s->update_irq(s->update_arg, 1); */ +} + +static void nextkbd_reset(DeviceState *dev) +{ + NextKBDState *nks = NEXTKBD(dev); + + memset(&nks->queue, 0, sizeof(KBDQueue)); + nks->shift = 0; +} + +static void nextkbd_realize(DeviceState *dev, Error **errp) +{ + NextKBDState *s = NEXTKBD(dev); + + memory_region_init_io(&s->mr, OBJECT(dev), &kbd_ops, s, "next.kbd", 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mr); + + qemu_add_kbd_event_handler(nextkbd_event, s); +} + +static const VMStateDescription nextkbd_vmstate = { + .name = TYPE_NEXTKBD, + .unmigratable = 1, /* TODO: Implement this when m68k CPU is migratable */ +}; + +static void nextkbd_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + dc->vmsd = &nextkbd_vmstate; + dc->realize = nextkbd_realize; + dc->reset = nextkbd_reset; +} + +static const TypeInfo nextkbd_info = { + .name = TYPE_NEXTKBD, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(NextKBDState), + .class_init = nextkbd_class_init, +}; + +static void nextkbd_register_types(void) +{ + type_register_static(&nextkbd_info); +} + +type_init(nextkbd_register_types) diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c new file mode 100644 index 000000000..e4c7c9b88 --- /dev/null +++ b/hw/m68k/q800.c @@ -0,0 +1,711 @@ +/* + * QEMU Motorla 680x0 Macintosh hardware System Emulator + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu-common.h" +#include "qemu/datadir.h" +#include "sysemu/sysemu.h" +#include "cpu.h" +#include "hw/boards.h" +#include "hw/or-irq.h" +#include "hw/nmi.h" +#include "elf.h" +#include "hw/loader.h" +#include "ui/console.h" +#include "hw/char/escc.h" +#include "hw/sysbus.h" +#include "hw/scsi/esp.h" +#include "standard-headers/asm-m68k/bootinfo.h" +#include "standard-headers/asm-m68k/bootinfo-mac.h" +#include "bootinfo.h" +#include "hw/misc/mac_via.h" +#include "hw/input/adb.h" +#include "hw/nubus/mac-nubus-bridge.h" +#include "hw/display/macfb.h" +#include "hw/block/swim.h" +#include "net/net.h" +#include "qapi/error.h" +#include "sysemu/qtest.h" +#include "sysemu/runstate.h" +#include "sysemu/reset.h" +#include "migration/vmstate.h" + +#define MACROM_ADDR 0x40800000 +#define MACROM_SIZE 0x00100000 + +#define MACROM_FILENAME "MacROM.bin" + +#define IO_BASE 0x50000000 +#define IO_SLICE 0x00040000 +#define IO_SIZE 0x04000000 + +#define VIA_BASE (IO_BASE + 0x00000) +#define SONIC_PROM_BASE (IO_BASE + 0x08000) +#define SONIC_BASE (IO_BASE + 0x0a000) +#define SCC_BASE (IO_BASE + 0x0c020) +#define ESP_BASE (IO_BASE + 0x10000) +#define ESP_PDMA (IO_BASE + 0x10100) +#define ASC_BASE (IO_BASE + 0x14000) +#define SWIM_BASE (IO_BASE + 0x1E000) + +#define SONIC_PROM_SIZE 0x1000 + +/* + * the video base, whereas it a Nubus address, + * is needed by the kernel to have early display and + * thus provided by the bootloader + */ +#define VIDEO_BASE 0xf9000000 + +#define MAC_CLOCK 3686418 + +/* + * Slot 0x9 is reserved for use by the in-built framebuffer whilst only + * slots 0xc, 0xd and 0xe physically exist on the Quadra 800 + */ +#define Q800_NUBUS_SLOTS_AVAILABLE (BIT(0x9) | BIT(0xc) | BIT(0xd) | \ + BIT(0xe)) + +/* + * The GLUE (General Logic Unit) is an Apple custom integrated circuit chip + * that performs a variety of functions (RAM management, clock generation, ...). + * The GLUE chip receives interrupt requests from various devices, + * assign priority to each, and asserts one or more interrupt line to the + * CPU. + */ + +#define TYPE_GLUE "q800-glue" +OBJECT_DECLARE_SIMPLE_TYPE(GLUEState, GLUE) + +struct GLUEState { + SysBusDevice parent_obj; + M68kCPU *cpu; + uint8_t ipr; + uint8_t auxmode; + qemu_irq irqs[1]; + QEMUTimer *nmi_release; +}; + +#define GLUE_IRQ_IN_VIA1 0 +#define GLUE_IRQ_IN_VIA2 1 +#define GLUE_IRQ_IN_SONIC 2 +#define GLUE_IRQ_IN_ESCC 3 +#define GLUE_IRQ_IN_NMI 4 + +#define GLUE_IRQ_NUBUS_9 0 + +/* + * The GLUE logic on the Quadra 800 supports 2 different IRQ routing modes + * controlled from the VIA1 auxmode GPIO (port B bit 6) which are documented + * in NetBSD as follows: + * + * A/UX mode (Linux, NetBSD, auxmode GPIO low) + * + * Level 0: Spurious: ignored + * Level 1: Software + * Level 2: VIA2 (except ethernet, sound) + * Level 3: Ethernet + * Level 4: Serial (SCC) + * Level 5: Sound + * Level 6: VIA1 + * Level 7: NMIs: parity errors, RESET button, YANCC error + * + * Classic mode (default: used by MacOS, A/UX 3.0.1, auxmode GPIO high) + * + * Level 0: Spurious: ignored + * Level 1: VIA1 (clock, ADB) + * Level 2: VIA2 (NuBus, SCSI) + * Level 3: + * Level 4: Serial (SCC) + * Level 5: + * Level 6: + * Level 7: Non-maskable: parity errors, RESET button + * + * Note that despite references to A/UX mode in Linux and NetBSD, at least + * A/UX 3.0.1 still uses Classic mode. + */ + +static void GLUE_set_irq(void *opaque, int irq, int level) +{ + GLUEState *s = opaque; + int i; + + if (s->auxmode) { + /* Classic mode */ + switch (irq) { + case GLUE_IRQ_IN_VIA1: + irq = 0; + break; + + case GLUE_IRQ_IN_VIA2: + irq = 1; + break; + + case GLUE_IRQ_IN_SONIC: + /* Route to VIA2 instead */ + qemu_set_irq(s->irqs[GLUE_IRQ_NUBUS_9], level); + return; + + case GLUE_IRQ_IN_ESCC: + irq = 3; + break; + + case GLUE_IRQ_IN_NMI: + irq = 6; + break; + + default: + g_assert_not_reached(); + } + } else { + /* A/UX mode */ + switch (irq) { + case GLUE_IRQ_IN_VIA1: + irq = 5; + break; + + case GLUE_IRQ_IN_VIA2: + irq = 1; + break; + + case GLUE_IRQ_IN_SONIC: + irq = 2; + break; + + case GLUE_IRQ_IN_ESCC: + irq = 3; + break; + + case GLUE_IRQ_IN_NMI: + irq = 6; + break; + + default: + g_assert_not_reached(); + } + } + + if (level) { + s->ipr |= 1 << irq; + } else { + s->ipr &= ~(1 << irq); + } + + for (i = 7; i >= 0; i--) { + if ((s->ipr >> i) & 1) { + m68k_set_irq_level(s->cpu, i + 1, i + 25); + return; + } + } + m68k_set_irq_level(s->cpu, 0, 0); +} + +static void glue_auxmode_set_irq(void *opaque, int irq, int level) +{ + GLUEState *s = GLUE(opaque); + + s->auxmode = level; +} + +static void glue_nmi(NMIState *n, int cpu_index, Error **errp) +{ + GLUEState *s = GLUE(n); + + /* Hold NMI active for 100ms */ + GLUE_set_irq(s, GLUE_IRQ_IN_NMI, 1); + timer_mod(s->nmi_release, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 100); +} + +static void glue_nmi_release(void *opaque) +{ + GLUEState *s = GLUE(opaque); + + GLUE_set_irq(s, GLUE_IRQ_IN_NMI, 0); +} + +static void glue_reset(DeviceState *dev) +{ + GLUEState *s = GLUE(dev); + + s->ipr = 0; + s->auxmode = 0; + + timer_del(s->nmi_release); +} + +static const VMStateDescription vmstate_glue = { + .name = "q800-glue", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(ipr, GLUEState), + VMSTATE_UINT8(auxmode, GLUEState), + VMSTATE_TIMER_PTR(nmi_release, GLUEState), + VMSTATE_END_OF_LIST(), + }, +}; + +/* + * If the m68k CPU implemented its inbound irq lines as GPIO lines + * rather than via the m68k_set_irq_level() function we would not need + * this cpu link property and could instead provide outbound IRQ lines + * that the board could wire up to the CPU. + */ +static Property glue_properties[] = { + DEFINE_PROP_LINK("cpu", GLUEState, cpu, TYPE_M68K_CPU, M68kCPU *), + DEFINE_PROP_END_OF_LIST(), +}; + +static void glue_finalize(Object *obj) +{ + GLUEState *s = GLUE(obj); + + timer_free(s->nmi_release); +} + +static void glue_init(Object *obj) +{ + DeviceState *dev = DEVICE(obj); + GLUEState *s = GLUE(dev); + + qdev_init_gpio_in(dev, GLUE_set_irq, 8); + qdev_init_gpio_in_named(dev, glue_auxmode_set_irq, "auxmode", 1); + + qdev_init_gpio_out(dev, s->irqs, 1); + + /* NMI release timer */ + s->nmi_release = timer_new_ms(QEMU_CLOCK_VIRTUAL, glue_nmi_release, s); +} + +static void glue_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + NMIClass *nc = NMI_CLASS(klass); + + dc->vmsd = &vmstate_glue; + dc->reset = glue_reset; + device_class_set_props(dc, glue_properties); + nc->nmi_monitor_handler = glue_nmi; +} + +static const TypeInfo glue_info = { + .name = TYPE_GLUE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(GLUEState), + .instance_init = glue_init, + .instance_finalize = glue_finalize, + .class_init = glue_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_NMI }, + { } + }, +}; + +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 uint8_t fake_mac_rom[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + /* offset: 0xa - mac_reset */ + + /* via2[vDirB] |= VIA2B_vPower */ + 0x20, 0x7C, 0x50, 0xF0, 0x24, 0x00, /* moveal VIA2_BASE+vDirB,%a0 */ + 0x10, 0x10, /* moveb %a0@,%d0 */ + 0x00, 0x00, 0x00, 0x04, /* orib #4,%d0 */ + 0x10, 0x80, /* moveb %d0,%a0@ */ + + /* via2[vBufB] &= ~VIA2B_vPower */ + 0x20, 0x7C, 0x50, 0xF0, 0x20, 0x00, /* moveal VIA2_BASE+vBufB,%a0 */ + 0x10, 0x10, /* moveb %a0@,%d0 */ + 0x02, 0x00, 0xFF, 0xFB, /* andib #-5,%d0 */ + 0x10, 0x80, /* moveb %d0,%a0@ */ + + /* while (true) ; */ + 0x60, 0xFE /* bras [self] */ +}; + +static void q800_init(MachineState *machine) +{ + M68kCPU *cpu = NULL; + int linux_boot; + int32_t kernel_size; + uint64_t elf_entry; + char *filename; + int bios_size; + ram_addr_t initrd_base; + int32_t initrd_size; + MemoryRegion *rom; + MemoryRegion *io; + MemoryRegion *dp8393x_prom = g_new(MemoryRegion, 1); + uint8_t *prom; + const int io_slice_nb = (IO_SIZE / IO_SLICE) - 1; + int i, checksum; + MacFbMode *macfb_mode; + 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; + const char *bios_name = machine->firmware ?: MACROM_FILENAME; + hwaddr parameters_base; + CPUState *cs; + DeviceState *dev; + DeviceState *via1_dev, *via2_dev; + DeviceState *escc_orgate; + SysBusESPState *sysbus_esp; + ESPState *esp; + SysBusDevice *sysbus; + BusState *adb_bus; + NubusBus *nubus; + DeviceState *glue; + DriveInfo *dinfo; + + linux_boot = (kernel_filename != NULL); + + if (ram_size > 1 * GiB) { + error_report("Too much memory for this machine: %" PRId64 " MiB, " + "maximum 1024 MiB", ram_size / MiB); + 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); + + /* + * Memory from IO_BASE to IO_BASE + IO_SLICE is repeated + * from IO_BASE + IO_SLICE to IO_BASE + IO_SIZE + */ + io = g_new(MemoryRegion, io_slice_nb); + for (i = 0; i < io_slice_nb; i++) { + char *name = g_strdup_printf("mac_m68k.io[%d]", i + 1); + + memory_region_init_alias(&io[i], NULL, name, get_system_memory(), + IO_BASE, IO_SLICE); + memory_region_add_subregion(get_system_memory(), + IO_BASE + (i + 1) * IO_SLICE, &io[i]); + g_free(name); + } + + /* IRQ Glue */ + glue = qdev_new(TYPE_GLUE); + object_property_set_link(OBJECT(glue), "cpu", OBJECT(cpu), &error_abort); + sysbus_realize_and_unref(SYS_BUS_DEVICE(glue), &error_fatal); + + /* VIA 1 */ + via1_dev = qdev_new(TYPE_MOS6522_Q800_VIA1); + dinfo = drive_get(IF_MTD, 0, 0); + if (dinfo) { + qdev_prop_set_drive(via1_dev, "drive", blk_by_legacy_dinfo(dinfo)); + } + sysbus = SYS_BUS_DEVICE(via1_dev); + sysbus_realize_and_unref(sysbus, &error_fatal); + sysbus_mmio_map(sysbus, 1, VIA_BASE); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, GLUE_IRQ_IN_VIA1)); + /* A/UX mode */ + qdev_connect_gpio_out(via1_dev, 0, + qdev_get_gpio_in_named(glue, "auxmode", 0)); + + adb_bus = qdev_get_child_bus(via1_dev, "adb.0"); + dev = qdev_new(TYPE_ADB_KEYBOARD); + qdev_realize_and_unref(dev, adb_bus, &error_fatal); + dev = qdev_new(TYPE_ADB_MOUSE); + qdev_realize_and_unref(dev, adb_bus, &error_fatal); + + /* VIA 2 */ + via2_dev = qdev_new(TYPE_MOS6522_Q800_VIA2); + sysbus = SYS_BUS_DEVICE(via2_dev); + sysbus_realize_and_unref(sysbus, &error_fatal); + sysbus_mmio_map(sysbus, 1, VIA_BASE + VIA_SIZE); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, GLUE_IRQ_IN_VIA2)); + + /* MACSONIC */ + + if (nb_nics > 1) { + error_report("q800 can only have one ethernet interface"); + exit(1); + } + + qemu_check_nic_model(&nd_table[0], "dp83932"); + + /* + * MacSonic driver needs an Apple MAC address + * Valid prefix are: + * 00:05:02 Apple + * 00:80:19 Dayna Communications, Inc. + * 00:A0:40 Apple + * 08:00:07 Apple + * (Q800 use the last one) + */ + nd_table[0].macaddr.a[0] = 0x08; + nd_table[0].macaddr.a[1] = 0x00; + nd_table[0].macaddr.a[2] = 0x07; + + dev = qdev_new("dp8393x"); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_prop_set_uint8(dev, "it_shift", 2); + qdev_prop_set_bit(dev, "big_endian", true); + object_property_set_link(OBJECT(dev), "dma_mr", + OBJECT(get_system_memory()), &error_abort); + sysbus = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(sysbus, &error_fatal); + sysbus_mmio_map(sysbus, 0, SONIC_BASE); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, GLUE_IRQ_IN_SONIC)); + + memory_region_init_rom(dp8393x_prom, NULL, "dp8393x-q800.prom", + SONIC_PROM_SIZE, &error_fatal); + memory_region_add_subregion(get_system_memory(), SONIC_PROM_BASE, + dp8393x_prom); + + /* Add MAC address with valid checksum to PROM */ + prom = memory_region_get_ram_ptr(dp8393x_prom); + checksum = 0; + for (i = 0; i < 6; i++) { + prom[i] = revbit8(nd_table[0].macaddr.a[i]); + checksum ^= prom[i]; + } + prom[7] = 0xff - checksum; + + /* SCC */ + + dev = qdev_new(TYPE_ESCC); + qdev_prop_set_uint32(dev, "disabled", 0); + qdev_prop_set_uint32(dev, "frequency", MAC_CLOCK); + qdev_prop_set_uint32(dev, "it_shift", 1); + qdev_prop_set_bit(dev, "bit_swap", true); + qdev_prop_set_chr(dev, "chrA", serial_hd(0)); + qdev_prop_set_chr(dev, "chrB", serial_hd(1)); + qdev_prop_set_uint32(dev, "chnBtype", 0); + qdev_prop_set_uint32(dev, "chnAtype", 0); + sysbus = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(sysbus, &error_fatal); + + /* Logically OR both its IRQs together */ + escc_orgate = DEVICE(object_new(TYPE_OR_IRQ)); + object_property_set_int(OBJECT(escc_orgate), "num-lines", 2, &error_fatal); + qdev_realize_and_unref(escc_orgate, NULL, &error_fatal); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(escc_orgate, 0)); + sysbus_connect_irq(sysbus, 1, qdev_get_gpio_in(escc_orgate, 1)); + qdev_connect_gpio_out(DEVICE(escc_orgate), 0, + qdev_get_gpio_in(glue, GLUE_IRQ_IN_ESCC)); + sysbus_mmio_map(sysbus, 0, SCC_BASE); + + /* SCSI */ + + dev = qdev_new(TYPE_SYSBUS_ESP); + sysbus_esp = SYSBUS_ESP(dev); + esp = &sysbus_esp->esp; + esp->dma_memory_read = NULL; + esp->dma_memory_write = NULL; + esp->dma_opaque = NULL; + sysbus_esp->it_shift = 4; + esp->dma_enabled = 1; + + sysbus = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(sysbus, &error_fatal); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(via2_dev, + VIA2_IRQ_SCSI_BIT)); + sysbus_connect_irq(sysbus, 1, qdev_get_gpio_in(via2_dev, + VIA2_IRQ_SCSI_DATA_BIT)); + sysbus_mmio_map(sysbus, 0, ESP_BASE); + sysbus_mmio_map(sysbus, 1, ESP_PDMA); + + scsi_bus_legacy_handle_cmdline(&esp->bus); + + /* SWIM floppy controller */ + + dev = qdev_new(TYPE_SWIM); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, SWIM_BASE); + + /* NuBus */ + + dev = qdev_new(TYPE_MAC_NUBUS_BRIDGE); + qdev_prop_set_uint32(dev, "slot-available-mask", + Q800_NUBUS_SLOTS_AVAILABLE); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, + MAC_NUBUS_FIRST_SLOT * NUBUS_SUPER_SLOT_SIZE); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 1, NUBUS_SLOT_BASE + + MAC_NUBUS_FIRST_SLOT * NUBUS_SLOT_SIZE); + qdev_connect_gpio_out(dev, 9, + qdev_get_gpio_in_named(via2_dev, "nubus-irq", + VIA2_NUBUS_IRQ_INTVIDEO)); + for (i = 1; i < VIA2_NUBUS_IRQ_NB; i++) { + qdev_connect_gpio_out(dev, 9 + i, + qdev_get_gpio_in_named(via2_dev, "nubus-irq", + VIA2_NUBUS_IRQ_9 + i)); + } + + /* + * Since the framebuffer in slot 0x9 uses a separate IRQ, wire the unused + * IRQ via GLUE for use by SONIC Ethernet in classic mode + */ + qdev_connect_gpio_out(glue, GLUE_IRQ_NUBUS_9, + qdev_get_gpio_in_named(via2_dev, "nubus-irq", + VIA2_NUBUS_IRQ_9)); + + nubus = &NUBUS_BRIDGE(dev)->bus; + + /* framebuffer in nubus slot #9 */ + + dev = qdev_new(TYPE_NUBUS_MACFB); + qdev_prop_set_uint32(dev, "slot", 9); + qdev_prop_set_uint32(dev, "width", graphic_width); + qdev_prop_set_uint32(dev, "height", graphic_height); + qdev_prop_set_uint8(dev, "depth", graphic_depth); + if (graphic_width == 1152 && graphic_height == 870) { + qdev_prop_set_uint8(dev, "display", MACFB_DISPLAY_APPLE_21_COLOR); + } else { + qdev_prop_set_uint8(dev, "display", MACFB_DISPLAY_VGA); + } + qdev_realize_and_unref(dev, BUS(nubus), &error_fatal); + + macfb_mode = (NUBUS_MACFB(dev)->macfb).mode; + + cs = CPU(cpu); + if (linux_boot) { + 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_MAC); + 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); + BOOTINFO1(cs->as, parameters_base, BI_MAC_CPUID, CPUB_68040); + BOOTINFO1(cs->as, parameters_base, BI_MAC_MODEL, MAC_MODEL_Q800); + BOOTINFO1(cs->as, parameters_base, + BI_MAC_MEMSIZE, ram_size >> 20); /* in MB */ + BOOTINFO2(cs->as, parameters_base, BI_MEMCHUNK, 0, ram_size); + BOOTINFO1(cs->as, parameters_base, BI_MAC_VADDR, + VIDEO_BASE + macfb_mode->offset); + BOOTINFO1(cs->as, parameters_base, BI_MAC_VDEPTH, graphic_depth); + BOOTINFO1(cs->as, parameters_base, BI_MAC_VDIM, + (graphic_height << 16) | graphic_width); + BOOTINFO1(cs->as, parameters_base, BI_MAC_VROW, macfb_mode->stride); + BOOTINFO1(cs->as, parameters_base, BI_MAC_SCCBASE, SCC_BASE); + + rom = g_malloc(sizeof(*rom)); + memory_region_init_ram_ptr(rom, NULL, "m68k_fake_mac.rom", + sizeof(fake_mac_rom), fake_mac_rom); + memory_region_set_readonly(rom, true); + memory_region_add_subregion(get_system_memory(), MACROM_ADDR, rom); + + 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); + } else { + uint8_t *ptr; + /* allocate and load BIOS */ + rom = g_malloc(sizeof(*rom)); + memory_region_init_rom(rom, NULL, "m68k_mac.rom", MACROM_SIZE, + &error_abort); + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + memory_region_add_subregion(get_system_memory(), MACROM_ADDR, rom); + + /* Load MacROM binary */ + if (filename) { + bios_size = load_image_targphys(filename, MACROM_ADDR, MACROM_SIZE); + g_free(filename); + } else { + bios_size = -1; + } + + /* Remove qtest_enabled() check once firmware files are in the tree */ + if (!qtest_enabled()) { + if (bios_size < 0 || bios_size > MACROM_SIZE) { + error_report("could not load MacROM '%s'", bios_name); + exit(1); + } + + ptr = rom_ptr(MACROM_ADDR, MACROM_SIZE); + stl_phys(cs->as, 0, ldl_p(ptr)); /* reset initial SP */ + stl_phys(cs->as, 4, + MACROM_ADDR + ldl_p(ptr + 4)); /* reset initial PC */ + } + } +} + +static void q800_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + mc->desc = "Macintosh Quadra 800"; + mc->init = q800_init; + mc->default_cpu_type = M68K_CPU_TYPE_NAME("m68040"); + mc->max_cpus = 1; + mc->block_default_type = IF_SCSI; + mc->default_ram_id = "m68k_mac.ram"; +} + +static const TypeInfo q800_machine_typeinfo = { + .name = MACHINE_TYPE_NAME("q800"), + .parent = TYPE_MACHINE, + .class_init = q800_machine_class_init, +}; + +static void q800_machine_register_types(void) +{ + type_register_static(&q800_machine_typeinfo); + type_register_static(&glue_info); +} + +type_init(q800_machine_register_types) diff --git a/hw/m68k/virt.c b/hw/m68k/virt.c new file mode 100644 index 000000000..0efa4a45c --- /dev/null +++ b/hw/m68k/virt.c @@ -0,0 +1,324 @@ +/* + * 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-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_2_options(MachineClass *mc) +{ +} +DEFINE_VIRT_MACHINE(6, 2, true) + +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) |