| /* |
| * 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 "qemu/error-report.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 interrupt 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) |