diff options
author | Stefan Hajnoczi <stefanha@redhat.com> | 2023-10-09 10:10:47 -0400 |
---|---|---|
committer | Stefan Hajnoczi <stefanha@redhat.com> | 2023-10-09 10:10:48 -0400 |
commit | f7294103560260ff09ffdf316cf6ba6d4e368d85 (patch) | |
tree | 953ad5eb29eb33975aaa9732c29715ef08b36308 /hw | |
parent | 17a319b175a57ee6fde4dabcaa2e11d47c39301e (diff) | |
parent | b4d3a83b89fba814613f7ead6b6ea92e0b2c3cd8 (diff) | |
download | qemu-f7294103560260ff09ffdf316cf6ba6d4e368d85.zip qemu-f7294103560260ff09ffdf316cf6ba6d4e368d85.tar.gz qemu-f7294103560260ff09ffdf316cf6ba6d4e368d85.tar.bz2 |
Merge tag 'q800-for-8.2-pull-request' of https://github.com/vivier/qemu-m68k into staging
Pull request q800 20231008
add support for booting:
- MacOS 7.1 - 8.1, with or without virtual memory enabled
- A/UX 3.0.1
- NetBSD 9.3
- Linux (via EMILE)
# -----BEGIN PGP SIGNATURE-----
#
# iQJGBAABCAAwFiEEzS913cjjpNwuT1Fz8ww4vT8vvjwFAmUiSrISHGxhdXJlbnRA
# dml2aWVyLmV1AAoJEPMMOL0/L748oSUQAKAm3TPYQUDDVFTi2uhzv6IgNSgOVUhK
# 3I3xoNb0UR9AT3Wfg1fah5La3p0kL9Y25gvhCl6veUg39WVicv3fbqUevbJ1Nwgl
# ovwS3MRRcvYhU+omcXImFfoIPyOxfSf3vZ6SedIkB24hQyXN9eFBZMfgCODU6lfo
# rAd/Hm50N2jRI8aKjvN+uHFRz75wqq6rNk/4QLWihRqhtWrjUDPHOTMI9sQxWy9z
# LcXxVKbWCY8/WOAandsGL94l2jfu94HM6CfwHaumdxvPBZT6WUyCv3T1rJsVJU29
# b8oTLcwKAmZ7lGLbjl6GdB8q5KAJFCAGLWuEbNIMj0orB37OpUd0Wx2SD9+aA53H
# yoKGbk6N1UappTtcnZCfwzWRzNaXrRno+w+/xYjlKsXBdHV9ZXHMGD5ERxoC6MY7
# ISsCa4bafeUDes6SCetgq87ho69E8l+gAlNYPgidHaTP226BjrYWQRJIa0leczfO
# aE6dAG7MQFOnOjeOHEJMDB2XpKHiVe1lyVGQH485cLW1J6LHJFWUfUUH2Zjs1v1z
# eXZHBTclPO2wbuQzXG6pAz2jdF/9w4ft/aA0PQhQcFxa9RB6AoNFG/juHJN5eUiw
# NXJetR2g1juNPqmMFWDNMJ7Zzce5Chjoj69XJBFYSXhgbOtwpUpoEPZUeIMcW1eJ
# Va2HvyDQPp1B
# =RUHg
# -----END PGP SIGNATURE-----
# gpg: Signature made Sun 08 Oct 2023 02:22:42 EDT
# gpg: using RSA key CD2F75DDC8E3A4DC2E4F5173F30C38BD3F2FBE3C
# gpg: issuer "laurent@vivier.eu"
# gpg: Good signature from "Laurent Vivier <lvivier@redhat.com>" [full]
# gpg: aka "Laurent Vivier <laurent@vivier.eu>" [full]
# gpg: aka "Laurent Vivier (Red Hat) <lvivier@redhat.com>" [full]
# Primary key fingerprint: CD2F 75DD C8E3 A4DC 2E4F 5173 F30C 38BD 3F2F BE3C
* tag 'q800-for-8.2-pull-request' of https://github.com/vivier/qemu-m68k:
mac_via: extend timer calibration hack to work with A/UX
q800: add alias for MacOS toolbox ROM at 0x40000000
q800: add ESCC alias at 0xc000
mac_via: always clear ADB interrupt when switching to A/UX mode
mac_via: implement ADB_STATE_IDLE state if shift register in input mode
mac_via: workaround NetBSD ADB bus enumeration issue
mac_via: work around underflow in TimeDBRA timing loop in SETUPTIMEK
swim: update IWM/ISM register block decoding
swim: split into separate IWM and ISM register blocks
swim: add trace events for IWM and ISM registers
q800: add easc bool machine class property to switch between ASC and EASC
q800: add Apple Sound Chip (ASC) audio to machine
asc: generate silence if FIFO empty but engine still running
audio: add Apple Sound Chip (ASC) emulation
q800: allow accesses to RAM area even if less memory is available
q800: add IOSB subsystem
q800: implement additional machine id bits on VIA1 port A
q800: add machine id register
q800: add djMEMC memory controller
q800-glue.c: convert to Resettable interface
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/audio/Kconfig | 3 | ||||
-rw-r--r-- | hw/audio/asc.c | 727 | ||||
-rw-r--r-- | hw/audio/meson.build | 1 | ||||
-rw-r--r-- | hw/audio/trace-events | 10 | ||||
-rw-r--r-- | hw/block/swim.c | 261 | ||||
-rw-r--r-- | hw/block/trace-events | 8 | ||||
-rw-r--r-- | hw/m68k/Kconfig | 3 | ||||
-rw-r--r-- | hw/m68k/q800-glue.c | 18 | ||||
-rw-r--r-- | hw/m68k/q800.c | 138 | ||||
-rw-r--r-- | hw/misc/Kconfig | 6 | ||||
-rw-r--r-- | hw/misc/djmemc.c | 135 | ||||
-rw-r--r-- | hw/misc/iosb.c | 133 | ||||
-rw-r--r-- | hw/misc/mac_via.c | 234 | ||||
-rw-r--r-- | hw/misc/meson.build | 2 | ||||
-rw-r--r-- | hw/misc/trace-events | 10 |
15 files changed, 1596 insertions, 93 deletions
diff --git a/hw/audio/Kconfig b/hw/audio/Kconfig index e76c69c..d099351 100644 --- a/hw/audio/Kconfig +++ b/hw/audio/Kconfig @@ -47,3 +47,6 @@ config PL041 config CS4231 bool + +config ASC + bool diff --git a/hw/audio/asc.c b/hw/audio/asc.c new file mode 100644 index 0000000..0f36b4c --- /dev/null +++ b/hw/audio/asc.c @@ -0,0 +1,727 @@ +/* + * QEMU Apple Sound Chip emulation + * + * Apple Sound Chip (ASC) 344S0063 + * Enhanced Apple Sound Chip (EASC) 343S1063 + * + * Copyright (c) 2012-2018 Laurent Vivier <laurent@vivier.eu> + * Copyright (c) 2022 Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "audio/audio.h" +#include "hw/audio/asc.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "trace.h" + +/* + * Linux doesn't provide information about ASC, see arch/m68k/mac/macboing.c + * and arch/m68k/include/asm/mac_asc.h + * + * best information is coming from MAME: + * https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.h + * https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.cpp + * Emulation by R. Belmont + * or MESS: + * http://mess.redump.net/mess/driver_info/easc + * + * 0x800: VERSION + * 0x801: MODE + * 1=FIFO mode, + * 2=wavetable mode + * 0x802: CONTROL + * bit 0=analog or PWM output, + * 1=stereo/mono, + * 7=processing time exceeded + * 0x803: FIFO MODE + * bit 7=clear FIFO, + * bit 1="non-ROM companding", + * bit 0="ROM companding") + * 0x804: FIFO IRQ STATUS + * bit 0=ch A 1/2 full, + * 1=ch A full, + * 2=ch B 1/2 full, + * 3=ch B full) + * 0x805: WAVETABLE CONTROL + * bits 0-3 wavetables 0-3 start + * 0x806: VOLUME + * bits 2-4 = 3 bit internal ASC volume, + * bits 5-7 = volume control sent to Sony sound chip + * 0x807: CLOCK RATE + * 0 = Mac 22257 Hz, + * 1 = undefined, + * 2 = 22050 Hz, + * 3 = 44100 Hz + * 0x80a: PLAY REC A + * 0x80f: TEST + * bits 6-7 = digital test, + * bits 4-5 = analog test + * 0x810: WAVETABLE 0 PHASE + * big-endian 9.15 fixed-point, only 24 bits valid + * 0x814: WAVETABLE 0 INCREMENT + * big-endian 9.15 fixed-point, only 24 bits valid + * 0x818: WAVETABLE 1 PHASE + * 0x81C: WAVETABLE 1 INCREMENT + * 0x820: WAVETABLE 2 PHASE + * 0x824: WAVETABLE 2 INCREMENT + * 0x828: WAVETABLE 3 PHASE + * 0x82C: WAVETABLE 3 INCREMENT + * 0x830: UNKNOWN START + * NetBSD writes Wavetable data here (are there more + * wavetables/channels than we know about?) + * 0x857: UNKNOWN END + */ + +#define ASC_SIZE 0x2000 + +enum { + ASC_VERSION = 0x00, + ASC_MODE = 0x01, + ASC_CONTROL = 0x02, + ASC_FIFOMODE = 0x03, + ASC_FIFOIRQ = 0x04, + ASC_WAVECTRL = 0x05, + ASC_VOLUME = 0x06, + ASC_CLOCK = 0x07, + ASC_PLAYRECA = 0x0a, + ASC_TEST = 0x0f, + ASC_WAVETABLE = 0x10 +}; + +#define ASC_FIFO_STATUS_HALF_FULL 1 +#define ASC_FIFO_STATUS_FULL_EMPTY 2 + +#define ASC_EXTREGS_FIFOCTRL 0x8 +#define ASC_EXTREGS_INTCTRL 0x9 +#define ASC_EXTREGS_CDXA_DECOMP_FILT 0x10 + +#define ASC_FIFO_CYCLE_TIME ((NANOSECONDS_PER_SECOND / ASC_FREQ) * \ + 0x400) + +static void asc_raise_irq(ASCState *s) +{ + qemu_set_irq(s->irq, 1); +} + +static void asc_lower_irq(ASCState *s) +{ + qemu_set_irq(s->irq, 0); +} + +static uint8_t asc_fifo_get(ASCFIFOState *fs) +{ + ASCState *s = container_of(fs, ASCState, fifos[fs->index]); + bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1; + uint8_t val; + + assert(fs->cnt); + + val = fs->fifo[fs->rptr]; + trace_asc_fifo_get('A' + fs->index, fs->rptr, fs->cnt, val); + + fs->rptr++; + fs->rptr &= 0x3ff; + fs->cnt--; + + if (fs->cnt <= 0x1ff) { + /* FIFO less than half full */ + fs->int_status |= ASC_FIFO_STATUS_HALF_FULL; + } else { + /* FIFO more than half full */ + fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL; + } + + if (fs->cnt == 0x1ff && fifo_half_irq_enabled) { + /* Raise FIFO half full IRQ */ + asc_raise_irq(s); + } + + if (fs->cnt == 0) { + /* Raise FIFO empty IRQ */ + fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY; + asc_raise_irq(s); + } + + return val; +} + +static int generate_fifo(ASCState *s, int maxsamples) +{ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + uint8_t *buf = s->mixbuf; + int i, wcount = 0; + + while (wcount < maxsamples) { + uint8_t val; + int16_t d, f0, f1; + int32_t t; + int shift, filter; + bool hasdata = false; + + for (i = 0; i < 2; i++) { + ASCFIFOState *fs = &s->fifos[i]; + + switch (fs->extregs[ASC_EXTREGS_FIFOCTRL] & 0x83) { + case 0x82: + /* + * CD-XA BRR mode: decompress 15 bytes into 28 16-bit + * samples + */ + if (!fs->cnt) { + val = 0x80; + break; + } + + if (fs->xa_cnt == -1) { + /* Start of packet, get flags */ + fs->xa_flags = asc_fifo_get(fs); + fs->xa_cnt = 0; + } + + shift = fs->xa_flags & 0xf; + filter = fs->xa_flags >> 4; + f0 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT + + (filter << 1) + 1]; + f1 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT + + (filter << 1)]; + + if ((fs->xa_cnt & 1) == 0) { + if (!fs->cnt) { + val = 0x80; + break; + } + + fs->xa_val = asc_fifo_get(fs); + d = (fs->xa_val & 0xf) << 12; + } else { + d = (fs->xa_val & 0xf0) << 8; + } + t = (d >> shift) + (((fs->xa_last[0] * f0) + + (fs->xa_last[1] * f1) + 32) >> 6); + if (t < -32768) { + t = -32768; + } else if (t > 32767) { + t = 32767; + } + + /* + * CD-XA BRR generates 16-bit signed output, so convert to + * 8-bit before writing to buffer. Does real hardware do the + * same? + */ + val = (uint8_t)(t / 256) ^ 0x80; + hasdata = true; + fs->xa_cnt++; + + fs->xa_last[1] = fs->xa_last[0]; + fs->xa_last[0] = (int16_t)t; + + if (fs->xa_cnt == 28) { + /* End of packet */ + fs->xa_cnt = -1; + } + break; + + default: + /* fallthrough */ + case 0x80: + /* Raw mode */ + if (fs->cnt) { + val = asc_fifo_get(fs); + hasdata = true; + } else { + val = 0x80; + } + break; + } + + buf[wcount * 2 + i] = val; + } + + if (!hasdata) { + break; + } + + wcount++; + } + + /* + * MacOS (un)helpfully leaves the FIFO engine running even when it has + * finished writing out samples, but still expects the FIFO empty + * interrupts to be generated for each FIFO cycle (without these interrupts + * MacOS will freeze) + */ + if (s->fifos[0].cnt == 0 && s->fifos[1].cnt == 0) { + if (!s->fifo_empty_ns) { + /* FIFO has completed first empty cycle */ + s->fifo_empty_ns = now; + } else if (now > (s->fifo_empty_ns + ASC_FIFO_CYCLE_TIME)) { + /* FIFO has completed entire cycle with no data */ + s->fifos[0].int_status |= ASC_FIFO_STATUS_HALF_FULL | + ASC_FIFO_STATUS_FULL_EMPTY; + s->fifos[1].int_status |= ASC_FIFO_STATUS_HALF_FULL | + ASC_FIFO_STATUS_FULL_EMPTY; + s->fifo_empty_ns = now; + asc_raise_irq(s); + } + } else { + /* FIFO contains data, reset empty time */ + s->fifo_empty_ns = 0; + } + + return wcount; +} + +static int generate_wavetable(ASCState *s, int maxsamples) +{ + uint8_t *buf = s->mixbuf; + int channel, count = 0; + + while (count < maxsamples) { + uint32_t left = 0, right = 0; + uint8_t sample; + + for (channel = 0; channel < 4; channel++) { + ASCFIFOState *fs = &s->fifos[channel >> 1]; + int chanreg = ASC_WAVETABLE + (channel << 3); + uint32_t phase, incr, offset; + + phase = ldl_be_p(&s->regs[chanreg]); + incr = ldl_be_p(&s->regs[chanreg + sizeof(uint32_t)]); + + phase += incr; + offset = (phase >> 15) & 0x1ff; + sample = fs->fifo[0x200 * (channel >> 1) + offset]; + + stl_be_p(&s->regs[chanreg], phase); + + left += sample; + right += sample; + } + + buf[count * 2] = left >> 2; + buf[count * 2 + 1] = right >> 2; + + count++; + } + + return count; +} + +static void asc_out_cb(void *opaque, int free_b) +{ + ASCState *s = opaque; + int samples, generated; + + if (free_b == 0) { + return; + } + + samples = MIN(s->samples, free_b >> s->shift); + + switch (s->regs[ASC_MODE] & 3) { + default: + /* Off */ + generated = 0; + break; + case 1: + /* FIFO mode */ + generated = generate_fifo(s, samples); + break; + case 2: + /* Wave table mode */ + generated = generate_wavetable(s, samples); + break; + } + + if (!generated) { + /* Workaround for audio underflow bug on Windows dsound backend */ + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + int silent_samples = muldiv64(now - s->fifo_empty_ns, + NANOSECONDS_PER_SECOND, ASC_FREQ); + + if (silent_samples > ASC_FIFO_CYCLE_TIME / 2) { + /* + * No new FIFO data within half a cycle time (~23ms) so fill the + * entire available buffer with silence. This prevents an issue + * with the Windows dsound backend whereby the sound appears to + * loop because the FIFO has run out of data, and the driver + * reuses the stale content in its circular audio buffer. + */ + AUD_write(s->voice, s->silentbuf, samples << s->shift); + } + return; + } + + AUD_write(s->voice, s->mixbuf, generated << s->shift); +} + +static uint64_t asc_fifo_read(void *opaque, hwaddr addr, + unsigned size) +{ + ASCFIFOState *fs = opaque; + + trace_asc_read_fifo('A' + fs->index, addr, size, fs->fifo[addr]); + return fs->fifo[addr]; +} + +static void asc_fifo_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + ASCFIFOState *fs = opaque; + ASCState *s = container_of(fs, ASCState, fifos[fs->index]); + bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1; + + trace_asc_write_fifo('A' + fs->index, addr, size, fs->wptr, fs->cnt, value); + + if (s->regs[ASC_MODE] == 1) { + fs->fifo[fs->wptr++] = value; + fs->wptr &= 0x3ff; + fs->cnt++; + + if (fs->cnt <= 0x1ff) { + /* FIFO less than half full */ + fs->int_status |= ASC_FIFO_STATUS_HALF_FULL; + } else { + /* FIFO at least half full */ + fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL; + } + + if (fs->cnt == 0x200 && fifo_half_irq_enabled) { + /* Raise FIFO half full interrupt */ + asc_raise_irq(s); + } + + if (fs->cnt == 0x3ff) { + /* Raise FIFO full interrupt */ + fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY; + asc_raise_irq(s); + } + } else { + fs->fifo[addr] = value; + } + return; +} + +static const MemoryRegionOps asc_fifo_ops = { + .read = asc_fifo_read, + .write = asc_fifo_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_BIG_ENDIAN, +}; + +static void asc_fifo_reset(ASCFIFOState *fs); + +static uint64_t asc_read(void *opaque, hwaddr addr, + unsigned size) +{ + ASCState *s = opaque; + uint64_t prev, value; + + switch (addr) { + case ASC_VERSION: + switch (s->type) { + default: + case ASC_TYPE_ASC: + value = 0; + break; + case ASC_TYPE_EASC: + value = 0xb0; + break; + } + break; + case ASC_FIFOIRQ: + prev = (s->fifos[0].int_status & 0x3) | + (s->fifos[1].int_status & 0x3) << 2; + + s->fifos[0].int_status = 0; + s->fifos[1].int_status = 0; + asc_lower_irq(s); + value = prev; + break; + default: + value = s->regs[addr]; + break; + } + + trace_asc_read_reg(addr, size, value); + return value; +} + +static void asc_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + ASCState *s = opaque; + + switch (addr) { + case ASC_MODE: + value &= 3; + if (value != s->regs[ASC_MODE]) { + asc_fifo_reset(&s->fifos[0]); + asc_fifo_reset(&s->fifos[1]); + asc_lower_irq(s); + if (value != 0) { + AUD_set_active_out(s->voice, 1); + } else { + AUD_set_active_out(s->voice, 0); + } + } + break; + case ASC_FIFOMODE: + if (value & 0x80) { + asc_fifo_reset(&s->fifos[0]); + asc_fifo_reset(&s->fifos[1]); + asc_lower_irq(s); + } + break; + case ASC_WAVECTRL: + break; + case ASC_VOLUME: + { + int vol = (value & 0xe0); + + AUD_set_volume_out(s->voice, 0, vol, vol); + break; + } + } + + trace_asc_write_reg(addr, size, value); + s->regs[addr] = value; +} + +static const MemoryRegionOps asc_regs_ops = { + .read = asc_read, + .write = asc_write, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + } +}; + +static uint64_t asc_ext_read(void *opaque, hwaddr addr, + unsigned size) +{ + ASCFIFOState *fs = opaque; + uint64_t value; + + value = fs->extregs[addr]; + + trace_asc_read_extreg('A' + fs->index, addr, size, value); + return value; +} + +static void asc_ext_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + ASCFIFOState *fs = opaque; + + trace_asc_write_extreg('A' + fs->index, addr, size, value); + + fs->extregs[addr] = value; +} + +static const MemoryRegionOps asc_extregs_ops = { + .read = asc_ext_read, + .write = asc_ext_write, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_BIG_ENDIAN, +}; + +static int asc_post_load(void *opaque, int version) +{ + ASCState *s = ASC(opaque); + + if (s->regs[ASC_MODE] != 0) { + AUD_set_active_out(s->voice, 1); + } + + return 0; +} + +static const VMStateDescription vmstate_asc_fifo = { + .name = "apple-sound-chip.fifo", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY(fifo, ASCFIFOState, ASC_FIFO_SIZE), + VMSTATE_UINT8(int_status, ASCFIFOState), + VMSTATE_INT32(cnt, ASCFIFOState), + VMSTATE_INT32(wptr, ASCFIFOState), + VMSTATE_INT32(rptr, ASCFIFOState), + VMSTATE_UINT8_ARRAY(extregs, ASCFIFOState, ASC_EXTREG_SIZE), + VMSTATE_INT32(xa_cnt, ASCFIFOState), + VMSTATE_UINT8(xa_val, ASCFIFOState), + VMSTATE_UINT8(xa_flags, ASCFIFOState), + VMSTATE_INT16_ARRAY(xa_last, ASCFIFOState, 2), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_asc = { + .name = "apple-sound-chip", + .version_id = 0, + .minimum_version_id = 0, + .post_load = asc_post_load, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(fifos, ASCState, 2, 0, vmstate_asc_fifo, + ASCFIFOState), + VMSTATE_UINT8_ARRAY(regs, ASCState, ASC_REG_SIZE), + VMSTATE_INT64(fifo_empty_ns, ASCState), + VMSTATE_END_OF_LIST() + } +}; + +static void asc_fifo_reset(ASCFIFOState *fs) +{ + fs->wptr = 0; + fs->rptr = 0; + fs->cnt = 0; + fs->xa_cnt = -1; + fs->int_status = 0; +} + +static void asc_fifo_init(ASCFIFOState *fs, int index) +{ + ASCState *s = container_of(fs, ASCState, fifos[index]); + char *name; + + fs->index = index; + name = g_strdup_printf("asc.fifo%c", 'A' + index); + memory_region_init_io(&fs->mem_fifo, OBJECT(s), &asc_fifo_ops, fs, + name, ASC_FIFO_SIZE); + g_free(name); + + name = g_strdup_printf("asc.extregs%c", 'A' + index); + memory_region_init_io(&fs->mem_extregs, OBJECT(s), &asc_extregs_ops, + fs, name, ASC_EXTREG_SIZE); + g_free(name); +} + +static void asc_reset_hold(Object *obj) +{ + ASCState *s = ASC(obj); + + AUD_set_active_out(s->voice, 0); + + memset(s->regs, 0, sizeof(s->regs)); + asc_fifo_reset(&s->fifos[0]); + asc_fifo_reset(&s->fifos[1]); + s->fifo_empty_ns = 0; + + if (s->type == ASC_TYPE_ASC) { + /* FIFO half full IRQs enabled by default */ + s->fifos[0].extregs[ASC_EXTREGS_INTCTRL] = 1; + s->fifos[1].extregs[ASC_EXTREGS_INTCTRL] = 1; + } +} + +static void asc_unrealize(DeviceState *dev) +{ + ASCState *s = ASC(dev); + + g_free(s->mixbuf); + g_free(s->silentbuf); + + AUD_remove_card(&s->card); +} + +static void asc_realize(DeviceState *dev, Error **errp) +{ + ASCState *s = ASC(dev); + struct audsettings as; + + if (!AUD_register_card("Apple Sound Chip", &s->card, errp)) { + return; + } + + as.freq = ASC_FREQ; + as.nchannels = 2; + as.fmt = AUDIO_FORMAT_U8; + as.endianness = AUDIO_HOST_ENDIANNESS; + + s->voice = AUD_open_out(&s->card, s->voice, "asc.out", s, asc_out_cb, + &as); + s->shift = 1; + s->samples = AUD_get_buffer_size_out(s->voice) >> s->shift; + s->mixbuf = g_malloc0(s->samples << s->shift); + + s->silentbuf = g_malloc0(s->samples << s->shift); + memset(s->silentbuf, 0x80, s->samples << s->shift); + + /* Add easc registers if required */ + if (s->type == ASC_TYPE_EASC) { + memory_region_add_subregion(&s->asc, ASC_EXTREG_OFFSET, + &s->fifos[0].mem_extregs); + memory_region_add_subregion(&s->asc, + ASC_EXTREG_OFFSET + ASC_EXTREG_SIZE, + &s->fifos[1].mem_extregs); + } +} + +static void asc_init(Object *obj) +{ + ASCState *s = ASC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init(&s->asc, OBJECT(obj), "asc", ASC_SIZE); + + asc_fifo_init(&s->fifos[0], 0); + asc_fifo_init(&s->fifos[1], 1); + + memory_region_add_subregion(&s->asc, ASC_FIFO_OFFSET, + &s->fifos[0].mem_fifo); + memory_region_add_subregion(&s->asc, + ASC_FIFO_OFFSET + ASC_FIFO_SIZE, + &s->fifos[1].mem_fifo); + + memory_region_init_io(&s->mem_regs, OBJECT(obj), &asc_regs_ops, s, + "asc.regs", ASC_REG_SIZE); + memory_region_add_subregion(&s->asc, ASC_REG_OFFSET, &s->mem_regs); + + sysbus_init_irq(sbd, &s->irq); + sysbus_init_mmio(sbd, &s->asc); +} + +static Property asc_properties[] = { + DEFINE_AUDIO_PROPERTIES(ASCState, card), + DEFINE_PROP_UINT8("asctype", ASCState, type, ASC_TYPE_ASC), + DEFINE_PROP_END_OF_LIST(), +}; + +static void asc_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->realize = asc_realize; + dc->unrealize = asc_unrealize; + set_bit(DEVICE_CATEGORY_SOUND, dc->categories); + dc->vmsd = &vmstate_asc; + device_class_set_props(dc, asc_properties); + rc->phases.hold = asc_reset_hold; +} + +static const TypeInfo asc_info_types[] = { + { + .name = TYPE_ASC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ASCState), + .instance_init = asc_init, + .class_init = asc_class_init, + }, +}; + +DEFINE_TYPES(asc_info_types) diff --git a/hw/audio/meson.build b/hw/audio/meson.build index d0fda10..8805322 100644 --- a/hw/audio/meson.build +++ b/hw/audio/meson.build @@ -1,6 +1,7 @@ system_ss.add(files('soundhw.c')) system_ss.add(when: 'CONFIG_AC97', if_true: files('ac97.c')) system_ss.add(when: 'CONFIG_ADLIB', if_true: files('fmopl.c', 'adlib.c')) +system_ss.add(when: 'CONFIG_ASC', if_true: files('asc.c')) system_ss.add(when: 'CONFIG_CS4231', if_true: files('cs4231.c')) system_ss.add(when: 'CONFIG_CS4231A', if_true: files('cs4231a.c')) system_ss.add(when: 'CONFIG_ES1370', if_true: files('es1370.c')) diff --git a/hw/audio/trace-events b/hw/audio/trace-events index 4dec48a..89ef299 100644 --- a/hw/audio/trace-events +++ b/hw/audio/trace-events @@ -17,3 +17,13 @@ via_ac97_codec_write(uint8_t addr, uint16_t val) "0x%x <- 0x%x" via_ac97_sgd_fetch(uint32_t curr, uint32_t addr, char stop, char eol, char flag, uint32_t len) "curr=0x%x addr=0x%x %c%c%c len=%d" via_ac97_sgd_read(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d -> 0x%"PRIx64 via_ac97_sgd_write(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d <- 0x%"PRIx64 + +# asc.c +asc_read_fifo(const char fifo, int reg, unsigned size, uint64_t value) "fifo %c reg=0x%03x size=%u value=0x%"PRIx64 +asc_read_reg(int reg, unsigned size, uint64_t value) "reg=0x%03x size=%u value=0x%"PRIx64 +asc_read_extreg(const char fifo, int reg, unsigned size, uint64_t value) "fifo %c reg=0x%03x size=%u value=0x%"PRIx64 +asc_fifo_get(const char fifo, int rptr, int cnt, uint64_t value) "fifo %c rptr=0x%x cnt=0x%x value=0x%"PRIx64 +asc_write_fifo(const char fifo, int reg, unsigned size, int wrptr, int cnt, uint64_t value) "fifo %c reg=0x%03x size=%u wptr=0x%x cnt=0x%x value=0x%"PRIx64 +asc_write_reg(int reg, unsigned size, uint64_t value) "reg=0x%03x size=%u value=0x%"PRIx64 +asc_write_extreg(const char fifo, int reg, unsigned size, uint64_t value) "fifo %c reg=0x%03x size=%u value=0x%"PRIx64 +asc_update_irq(int irq, int a, int b) "set IRQ to %d (A: 0x%x B: 0x%x)" diff --git a/hw/block/swim.c b/hw/block/swim.c index 333da08..fd65c59 100644 --- a/hw/block/swim.c +++ b/hw/block/swim.c @@ -19,25 +19,30 @@ #include "hw/block/block.h" #include "hw/block/swim.h" #include "hw/qdev-properties.h" +#include "trace.h" + + +/* IWM latch bits */ + +#define IWMLB_PHASE0 0 +#define IWMLB_PHASE1 1 +#define IWMLB_PHASE2 2 +#define IWMLB_PHASE3 3 +#define IWMLB_MOTORON 4 +#define IWMLB_DRIVESEL 5 +#define IWMLB_L6 6 +#define IWMLB_L7 7 /* IWM registers */ -#define IWM_PH0L 0 -#define IWM_PH0H 1 -#define IWM_PH1L 2 -#define IWM_PH1H 3 -#define IWM_PH2L 4 -#define IWM_PH2H 5 -#define IWM_PH3L 6 -#define IWM_PH3H 7 -#define IWM_MTROFF 8 -#define IWM_MTRON 9 -#define IWM_INTDRIVE 10 -#define IWM_EXTDRIVE 11 -#define IWM_Q6L 12 -#define IWM_Q6H 13 -#define IWM_Q7L 14 -#define IWM_Q7H 15 +#define IWM_READALLONES 0 +#define IWM_READDATA 1 +#define IWM_READSTATUS0 2 +#define IWM_READSTATUS1 3 +#define IWM_READWHANDSHAKE0 4 +#define IWM_READWHANDSHAKE1 5 +#define IWM_WRITESETMODE 6 +#define IWM_WRITEDATA 7 /* SWIM registers */ @@ -61,8 +66,9 @@ #define REG_SHIFT 9 -#define SWIM_MODE_IWM 0 -#define SWIM_MODE_SWIM 1 +#define SWIM_MODE_STATUS_BIT 6 +#define SWIM_MODE_IWM 0 +#define SWIM_MODE_ISM 1 /* bits in phase register */ @@ -125,6 +131,18 @@ #define SWIM_HEDSEL 0x20 #define SWIM_MOTON 0x80 +static const char *iwm_reg_names[] = { + "READALLONES", "READDATA", "READSTATUS0", "READSTATUS1", + "READWHANDSHAKE0", "READWHANDSHAKE1", "WRITESETMODE", "WRITEDATA" +}; + +static const char *ism_reg_names[] = { + "WRITE_DATA", "WRITE_MARK", "WRITE_CRC", "WRITE_PARAMETER", + "WRITE_PHASE", "WRITE_SETUP", "WRITE_MODE0", "WRITE_MODE1", + "READ_DATA", "READ_MARK", "READ_ERROR", "READ_PARAMETER", + "READ_PHASE", "READ_SETUP", "READ_STATUS", "READ_HANDSHAKE" +}; + static void fd_recalibrate(FDrive *drive) { } @@ -259,102 +277,158 @@ static const TypeInfo swim_bus_info = { .instance_size = sizeof(SWIMBus), }; -static void iwmctrl_write(void *opaque, hwaddr reg, uint64_t value, +static void iwmctrl_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { SWIMCtrl *swimctrl = opaque; + uint8_t latch, reg, ism_bit; - reg >>= REG_SHIFT; + addr >>= REG_SHIFT; - swimctrl->regs[reg >> 1] = reg & 1; - - if (swimctrl->regs[IWM_Q6] && - swimctrl->regs[IWM_Q7]) { - if (swimctrl->regs[IWM_MTR]) { - /* data register */ - swimctrl->iwm_data = value; - } else { - /* mode register */ - swimctrl->iwm_mode = value; - /* detect sequence to switch from IWM mode to SWIM mode */ - switch (swimctrl->iwm_switch) { - case 0: - if (value == 0x57) { - swimctrl->iwm_switch++; - } - break; - case 1: - if (value == 0x17) { - swimctrl->iwm_switch++; - } - break; - case 2: - if (value == 0x57) { - swimctrl->iwm_switch++; - } - break; - case 3: - if (value == 0x57) { - swimctrl->mode = SWIM_MODE_SWIM; - swimctrl->iwm_switch = 0; - } - break; + /* A3-A1 select a latch, A0 specifies the value */ + latch = (addr >> 1) & 7; + if (addr & 1) { + swimctrl->iwm_latches |= (1 << latch); + } else { + swimctrl->iwm_latches &= ~(1 << latch); + } + + reg = (swimctrl->iwm_latches & 0xc0) >> 5 | + (swimctrl->iwm_latches & 0x10) >> 4; + + swimctrl->iwmregs[reg] = value; + trace_swim_iwmctrl_write(reg, iwm_reg_names[reg], size, value); + + switch (reg) { + case IWM_WRITESETMODE: + /* detect sequence to switch from IWM mode to SWIM mode */ + ism_bit = (value & (1 << SWIM_MODE_STATUS_BIT)); + + switch (swimctrl->iwm_switch) { + case 0: + if (ism_bit) { /* 1 */ + swimctrl->iwm_switch++; + } + break; + case 1: + if (!ism_bit) { /* 0 */ + swimctrl->iwm_switch++; + } + break; + case 2: + if (ism_bit) { /* 1 */ + swimctrl->iwm_switch++; + } + break; + case 3: + if (ism_bit) { /* 1 */ + swimctrl->iwm_switch++; + + swimctrl->mode = SWIM_MODE_ISM; + swimctrl->swim_mode |= (1 << SWIM_MODE_STATUS_BIT); + swimctrl->iwm_switch = 0; + trace_swim_switch_to_ism(); + + /* Switch to ISM registers */ + memory_region_del_subregion(&swimctrl->swim, &swimctrl->iwm); + memory_region_add_subregion(&swimctrl->swim, 0x0, + &swimctrl->ism); } + break; } + break; + default: + break; } } -static uint64_t iwmctrl_read(void *opaque, hwaddr reg, unsigned size) +static uint64_t iwmctrl_read(void *opaque, hwaddr addr, unsigned size) { SWIMCtrl *swimctrl = opaque; + uint8_t latch, reg, value; - reg >>= REG_SHIFT; + addr >>= REG_SHIFT; + + /* A3-A1 select a latch, A0 specifies the value */ + latch = (addr >> 1) & 7; + if (addr & 1) { + swimctrl->iwm_latches |= (1 << latch); + } else { + swimctrl->iwm_latches &= ~(1 << latch); + } + + reg = (swimctrl->iwm_latches & 0xc0) >> 5 | + (swimctrl->iwm_latches & 0x10) >> 4; - swimctrl->regs[reg >> 1] = reg & 1; + switch (reg) { + case IWM_READALLONES: + value = 0xff; + break; + default: + value = 0; + break; + } - return 0; + trace_swim_iwmctrl_read(reg, iwm_reg_names[reg], size, value); + return value; } -static void swimctrl_write(void *opaque, hwaddr reg, uint64_t value, - unsigned size) +static const MemoryRegionOps swimctrl_iwm_ops = { + .write = iwmctrl_write, + .read = iwmctrl_read, + .endianness = DEVICE_BIG_ENDIAN, +}; + +static void ismctrl_write(void *opaque, hwaddr reg, uint64_t value, + unsigned size) { SWIMCtrl *swimctrl = opaque; - if (swimctrl->mode == SWIM_MODE_IWM) { - iwmctrl_write(opaque, reg, value, size); - return; - } - reg >>= REG_SHIFT; + trace_swim_ismctrl_write(reg, ism_reg_names[reg], size, value); + switch (reg) { case SWIM_WRITE_PHASE: swimctrl->swim_phase = value; break; case SWIM_WRITE_MODE0: swimctrl->swim_mode &= ~value; + /* Any access to MODE0 register resets PRAM index */ + swimctrl->pram_idx = 0; + + if (!(swimctrl->swim_mode & (1 << SWIM_MODE_STATUS_BIT))) { + /* Clearing the mode bit switches to IWM mode */ + swimctrl->mode = SWIM_MODE_IWM; + swimctrl->iwm_latches = 0; + trace_swim_switch_to_iwm(); + + /* Switch to IWM registers */ + memory_region_del_subregion(&swimctrl->swim, &swimctrl->ism); + memory_region_add_subregion(&swimctrl->swim, 0x0, + &swimctrl->iwm); + } break; case SWIM_WRITE_MODE1: swimctrl->swim_mode |= value; break; + case SWIM_WRITE_PARAMETER: + swimctrl->pram[swimctrl->pram_idx++] = value; + swimctrl->pram_idx &= 0xf; + break; case SWIM_WRITE_DATA: case SWIM_WRITE_MARK: case SWIM_WRITE_CRC: - case SWIM_WRITE_PARAMETER: case SWIM_WRITE_SETUP: break; } } -static uint64_t swimctrl_read(void *opaque, hwaddr reg, unsigned size) +static uint64_t ismctrl_read(void *opaque, hwaddr reg, unsigned size) { SWIMCtrl *swimctrl = opaque; uint32_t value = 0; - if (swimctrl->mode == SWIM_MODE_IWM) { - return iwmctrl_read(opaque, reg, size); - } - reg >>= REG_SHIFT; switch (reg) { @@ -367,22 +441,31 @@ static uint64_t swimctrl_read(void *opaque, hwaddr reg, unsigned size) value = SWIM_SENSE; } break; + case SWIM_READ_PARAMETER: + value = swimctrl->pram[swimctrl->pram_idx++]; + swimctrl->pram_idx &= 0xf; + break; + case SWIM_READ_STATUS: + value = swimctrl->swim_status & ~(1 << SWIM_MODE_STATUS_BIT); + if (swimctrl->swim_mode == SWIM_MODE_ISM) { + value |= (1 << SWIM_MODE_STATUS_BIT); + } + break; case SWIM_READ_DATA: case SWIM_READ_MARK: case SWIM_READ_ERROR: - case SWIM_READ_PARAMETER: case SWIM_READ_SETUP: - case SWIM_READ_STATUS: break; } + trace_swim_ismctrl_read(reg, ism_reg_names[reg], size, value); return value; } -static const MemoryRegionOps swimctrl_mem_ops = { - .write = swimctrl_write, - .read = swimctrl_read, - .endianness = DEVICE_NATIVE_ENDIAN, +static const MemoryRegionOps swimctrl_ism_ops = { + .write = ismctrl_write, + .read = ismctrl_read, + .endianness = DEVICE_BIG_ENDIAN, }; static void sysbus_swim_reset(DeviceState *d) @@ -393,13 +476,11 @@ static void sysbus_swim_reset(DeviceState *d) ctrl->mode = 0; ctrl->iwm_switch = 0; - for (i = 0; i < 8; i++) { - ctrl->regs[i] = 0; - } - ctrl->iwm_data = 0; - ctrl->iwm_mode = 0; + memset(ctrl->iwmregs, 0, sizeof(ctrl->iwmregs)); + ctrl->swim_phase = 0; ctrl->swim_mode = 0; + memset(ctrl->ismregs, 0, sizeof(ctrl->ismregs)); for (i = 0; i < SWIM_MAX_FD; i++) { fd_recalibrate(&ctrl->drives[i]); } @@ -411,9 +492,12 @@ static void sysbus_swim_init(Object *obj) Swim *sbs = SWIM(obj); SWIMCtrl *swimctrl = &sbs->ctrl; - memory_region_init_io(&swimctrl->iomem, obj, &swimctrl_mem_ops, swimctrl, - "swim", 0x2000); - sysbus_init_mmio(sbd, &swimctrl->iomem); + memory_region_init(&swimctrl->swim, obj, "swim", 0x2000); + memory_region_init_io(&swimctrl->iwm, obj, &swimctrl_iwm_ops, swimctrl, + "iwm", 0x2000); + memory_region_init_io(&swimctrl->ism, obj, &swimctrl_ism_ops, swimctrl, + "ism", 0x2000); + sysbus_init_mmio(sbd, &swimctrl->swim); } static void sysbus_swim_realize(DeviceState *dev, Error **errp) @@ -423,6 +507,9 @@ static void sysbus_swim_realize(DeviceState *dev, Error **errp) qbus_init(&swimctrl->bus, sizeof(SWIMBus), TYPE_SWIM_BUS, dev, NULL); swimctrl->bus.ctrl = swimctrl; + + /* Default register set is IWM */ + memory_region_add_subregion(&swimctrl->swim, 0x0, &swimctrl->iwm); } static const VMStateDescription vmstate_fdrive = { @@ -442,10 +529,10 @@ static const VMStateDescription vmstate_swim = { VMSTATE_INT32(mode, SWIMCtrl), /* IWM mode */ VMSTATE_INT32(iwm_switch, SWIMCtrl), - VMSTATE_UINT16_ARRAY(regs, SWIMCtrl, 8), - VMSTATE_UINT8(iwm_data, SWIMCtrl), - VMSTATE_UINT8(iwm_mode, SWIMCtrl), + VMSTATE_UINT8(iwm_latches, SWIMCtrl), + VMSTATE_UINT8_ARRAY(iwmregs, SWIMCtrl, 8), /* SWIM mode */ + VMSTATE_UINT8_ARRAY(ismregs, SWIMCtrl, 16), VMSTATE_UINT8(swim_phase, SWIMCtrl), VMSTATE_UINT8(swim_mode, SWIMCtrl), /* Drives */ diff --git a/hw/block/trace-events b/hw/block/trace-events index 34be8b9..bab21d3 100644 --- a/hw/block/trace-events +++ b/hw/block/trace-events @@ -90,3 +90,11 @@ m25p80_read_data(void *s, uint32_t pos, uint8_t v) "[%p] Read data 0x%"PRIx32"=0 m25p80_read_sfdp(void *s, uint32_t addr, uint8_t v) "[%p] Read SFDP 0x%"PRIx32"=0x%"PRIx8 m25p80_binding(void *s) "[%p] Binding to IF_MTD drive" m25p80_binding_no_bdrv(void *s) "[%p] No BDRV - binding to RAM" + +# swim.c +swim_ismctrl_read(int reg, const char *name, unsigned size, uint64_t value) "reg=%d [%s] size=%u value=0x%"PRIx64 +swim_ismctrl_write(int reg, const char *name, unsigned size, uint64_t value) "reg=%d [%s] size=%u value=0x%"PRIx64 +swim_iwmctrl_read(int reg, const char *name, unsigned size, uint64_t value) "reg=%d [%s] size=%u value=0x%"PRIx64 +swim_iwmctrl_write(int reg, const char *name, unsigned size, uint64_t value) "reg=%d [%s] size=%u value=0x%"PRIx64 +swim_switch_to_ism(void) "switch from IWM to ISM mode" +swim_switch_to_iwm(void) "switch from ISM to IWM mode" diff --git a/hw/m68k/Kconfig b/hw/m68k/Kconfig index f839f8a..d88741e 100644 --- a/hw/m68k/Kconfig +++ b/hw/m68k/Kconfig @@ -23,6 +23,9 @@ config Q800 select ESP select DP8393X select OR_IRQ + select DJMEMC + select IOSB + select ASC config M68K_VIRT bool diff --git a/hw/m68k/q800-glue.c b/hw/m68k/q800-glue.c index 34c4f0e..f413b15 100644 --- a/hw/m68k/q800-glue.c +++ b/hw/m68k/q800-glue.c @@ -97,6 +97,11 @@ static void GLUE_set_irq(void *opaque, int irq, int level) irq = 6; break; + case GLUE_IRQ_IN_ASC: + /* Route to VIA2 instead, negative edge-triggered */ + qemu_set_irq(s->irqs[GLUE_IRQ_ASC], !level); + return; + default: g_assert_not_reached(); } @@ -123,6 +128,10 @@ static void GLUE_set_irq(void *opaque, int irq, int level) irq = 6; break; + case GLUE_IRQ_IN_ASC: + irq = 4; + break; + default: g_assert_not_reached(); } @@ -166,9 +175,9 @@ static void glue_nmi_release(void *opaque) GLUE_set_irq(s, GLUE_IRQ_IN_NMI, 0); } -static void glue_reset(DeviceState *dev) +static void glue_reset_hold(Object *obj) { - GLUEState *s = GLUE(dev); + GLUEState *s = GLUE(obj); s->ipr = 0; s->auxmode = 0; @@ -214,7 +223,7 @@ static void glue_init(Object *obj) 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); + qdev_init_gpio_out(dev, s->irqs, 2); /* NMI release timer */ s->nmi_release = timer_new_ms(QEMU_CLOCK_VIRTUAL, glue_nmi_release, s); @@ -223,11 +232,12 @@ static void glue_init(Object *obj) static void glue_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); NMIClass *nc = NMI_CLASS(klass); dc->vmsd = &vmstate_glue; - dc->reset = glue_reset; device_class_set_props(dc, glue_properties); + rc->phases.hold = glue_reset_hold; nc->nmi_monitor_handler = glue_nmi; } diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c index b770b71..1d7cd5f 100644 --- a/hw/m68k/q800.c +++ b/hw/m68k/q800.c @@ -40,7 +40,10 @@ #include "hw/m68k/q800.h" #include "hw/m68k/q800-glue.h" #include "hw/misc/mac_via.h" +#include "hw/misc/djmemc.h" +#include "hw/misc/iosb.h" #include "hw/input/adb.h" +#include "hw/audio/asc.h" #include "hw/nubus/mac-nubus-bridge.h" #include "hw/display/macfb.h" #include "hw/block/swim.h" @@ -66,9 +69,11 @@ #define SONIC_PROM_BASE (IO_BASE + 0x08000) #define SONIC_BASE (IO_BASE + 0x0a000) #define SCC_BASE (IO_BASE + 0x0c020) +#define DJMEMC_BASE (IO_BASE + 0x0e000) #define ESP_BASE (IO_BASE + 0x10000) #define ESP_PDMA (IO_BASE + 0x10100) #define ASC_BASE (IO_BASE + 0x14000) +#define IOSB_BASE (IO_BASE + 0x18000) #define SWIM_BASE (IO_BASE + 0x1E000) #define SONIC_PROM_SIZE 0x1000 @@ -82,6 +87,9 @@ #define MAC_CLOCK 3686418 +/* Size of whole RAM area */ +#define RAM_SIZE 0x40000000 + /* * Slot 0x9 is reserved for use by the in-built framebuffer whilst only * slots 0xc, 0xd and 0xe physically exist on the Quadra 800 @@ -89,6 +97,9 @@ #define Q800_NUBUS_SLOTS_AVAILABLE (BIT(0x9) | BIT(0xc) | BIT(0xd) | \ BIT(0xe)) +/* Quadra 800 machine ID */ +#define Q800_MACHINE_ID 0xa55a2bad + static void main_cpu_reset(void *opaque) { @@ -190,6 +201,48 @@ static const MemoryRegionOps macio_alias_ops = { }, }; +static uint64_t machine_id_read(void *opaque, hwaddr addr, unsigned size) +{ + return Q800_MACHINE_ID; +} + +static void machine_id_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + return; +} + +static const MemoryRegionOps machine_id_ops = { + .read = machine_id_read, + .write = machine_id_write, + .endianness = DEVICE_BIG_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static uint64_t ramio_read(void *opaque, hwaddr addr, unsigned size) +{ + return 0x0; +} + +static void ramio_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + return; +} + +static const MemoryRegionOps ramio_ops = { + .read = ramio_read, + .write = ramio_write, + .endianness = DEVICE_BIG_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + static void q800_machine_init(MachineState *machine) { Q800MachineState *m = Q800_MACHINE(machine); @@ -234,7 +287,11 @@ static void q800_machine_init(MachineState *machine) qemu_register_reset(main_cpu_reset, &m->cpu); /* RAM */ - memory_region_add_subregion(get_system_memory(), 0, machine->ram); + memory_region_init_io(&m->ramio, OBJECT(machine), &ramio_ops, &m->ramio, + "ram", RAM_SIZE); + memory_region_add_subregion(get_system_memory(), 0x0, &m->ramio); + + memory_region_add_subregion(&m->ramio, 0, machine->ram); /* * Create container for all IO devices @@ -251,12 +308,32 @@ static void q800_machine_init(MachineState *machine) memory_region_add_subregion(get_system_memory(), IO_BASE + IO_SLICE, &m->macio_alias); + memory_region_init_io(&m->machine_id, NULL, &machine_id_ops, NULL, + "Machine ID", 4); + memory_region_add_subregion(get_system_memory(), 0x5ffffffc, + &m->machine_id); + /* IRQ Glue */ object_initialize_child(OBJECT(machine), "glue", &m->glue, TYPE_GLUE); object_property_set_link(OBJECT(&m->glue), "cpu", OBJECT(&m->cpu), &error_abort); sysbus_realize(SYS_BUS_DEVICE(&m->glue), &error_fatal); + /* djMEMC memory controller */ + object_initialize_child(OBJECT(machine), "djmemc", &m->djmemc, + TYPE_DJMEMC); + sysbus = SYS_BUS_DEVICE(&m->djmemc); + sysbus_realize_and_unref(sysbus, &error_fatal); + memory_region_add_subregion(&m->macio, DJMEMC_BASE - IO_BASE, + sysbus_mmio_get_region(sysbus, 0)); + + /* IOSB subsystem */ + object_initialize_child(OBJECT(machine), "iosb", &m->iosb, TYPE_IOSB); + sysbus = SYS_BUS_DEVICE(&m->iosb); + sysbus_realize_and_unref(sysbus, &error_fatal); + memory_region_add_subregion(&m->macio, IOSB_BASE - IO_BASE, + sysbus_mmio_get_region(sysbus, 0)); + /* VIA 1 */ object_initialize_child(OBJECT(machine), "via1", &m->via1, TYPE_MOS6522_Q800_VIA1); @@ -374,6 +451,12 @@ static void q800_machine_init(MachineState *machine) memory_region_add_subregion(&m->macio, SCC_BASE - IO_BASE, sysbus_mmio_get_region(sysbus, 0)); + /* Create alias for NetBSD */ + memory_region_init_alias(&m->escc_alias, OBJECT(machine), "escc-alias", + sysbus_mmio_get_region(sysbus, 0), 0, 0x8); + memory_region_add_subregion(&m->macio, SCC_BASE - IO_BASE - 0x20, + &m->escc_alias); + /* SCSI */ object_initialize_child(OBJECT(machine), "esp", &m->esp, @@ -404,6 +487,26 @@ static void q800_machine_init(MachineState *machine) scsi_bus_legacy_handle_cmdline(&esp->bus); + /* Apple Sound Chip */ + + object_initialize_child(OBJECT(machine), "asc", &m->asc, TYPE_ASC); + qdev_prop_set_uint8(DEVICE(&m->asc), "asctype", m->easc ? ASC_TYPE_EASC + : ASC_TYPE_ASC); + if (machine->audiodev) { + qdev_prop_set_string(DEVICE(&m->asc), "audiodev", machine->audiodev); + } + sysbus = SYS_BUS_DEVICE(&m->asc); + sysbus_realize_and_unref(sysbus, &error_fatal); + memory_region_add_subregion(&m->macio, ASC_BASE - IO_BASE, + sysbus_mmio_get_region(sysbus, 0)); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(DEVICE(&m->glue), + GLUE_IRQ_IN_ASC)); + + /* Wire ASC IRQ via GLUE for use in classic mode */ + qdev_connect_gpio_out(DEVICE(&m->glue), GLUE_IRQ_ASC, + qdev_get_gpio_in(DEVICE(&m->via2), + VIA2_IRQ_ASC_BIT)); + /* SWIM floppy controller */ object_initialize_child(OBJECT(machine), "swim", &m->swim, @@ -557,6 +660,11 @@ static void q800_machine_init(MachineState *machine) filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); memory_region_add_subregion(get_system_memory(), MACROM_ADDR, &m->rom); + memory_region_init_alias(&m->rom_alias, NULL, "m68k_mac.rom-alias", + &m->rom, 0, MACROM_SIZE); + memory_region_add_subregion(get_system_memory(), 0x40000000, + &m->rom_alias); + /* Load MacROM binary */ if (filename) { bios_size = load_image_targphys(filename, MACROM_ADDR, MACROM_SIZE); @@ -581,6 +689,28 @@ static void q800_machine_init(MachineState *machine) } } +static bool q800_get_easc(Object *obj, Error **errp) +{ + Q800MachineState *ms = Q800_MACHINE(obj); + + return ms->easc; +} + +static void q800_set_easc(Object *obj, bool value, Error **errp) +{ + Q800MachineState *ms = Q800_MACHINE(obj); + + ms->easc = value; +} + +static void q800_init(Object *obj) +{ + Q800MachineState *ms = Q800_MACHINE(obj); + + /* Default to EASC */ + ms->easc = true; +} + static GlobalProperty hw_compat_q800[] = { { "scsi-hd", "quirk_mode_page_vendor_specific_apple", "on" }, { "scsi-hd", "vendor", " SEAGATE" }, @@ -612,12 +742,18 @@ static void q800_machine_class_init(ObjectClass *oc, void *data) mc->max_cpus = 1; mc->block_default_type = IF_SCSI; mc->default_ram_id = "m68k_mac.ram"; + machine_add_audiodev_property(mc); compat_props_add(mc->compat_props, hw_compat_q800, hw_compat_q800_len); + + object_class_property_add_bool(oc, "easc", q800_get_easc, q800_set_easc); + object_class_property_set_description(oc, "easc", + "Set to off to use ASC rather than EASC"); } static const TypeInfo q800_machine_typeinfo = { .name = MACHINE_TYPE_NAME("q800"), .parent = TYPE_MACHINE, + .instance_init = q800_init, .instance_size = sizeof(Q800MachineState), .class_init = q800_machine_class_init, }; diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 6996d26..858277b 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -186,4 +186,10 @@ config AXP2XX_PMU bool depends on I2C +config DJMEMC + bool + +config IOSB + bool + source macio/Kconfig diff --git a/hw/misc/djmemc.c b/hw/misc/djmemc.c new file mode 100644 index 0000000..fd02640 --- /dev/null +++ b/hw/misc/djmemc.c @@ -0,0 +1,135 @@ +/* + * djMEMC, macintosh memory and interrupt controller + * (Quadra 610/650/800 & Centris 610/650) + * + * https://mac68k.info/wiki/display/mac68k/djMEMC+Information + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "migration/vmstate.h" +#include "hw/misc/djmemc.h" +#include "hw/qdev-properties.h" +#include "trace.h" + + +#define DJMEMC_INTERLEAVECONF 0x0 +#define DJMEMC_BANK0CONF 0x4 +#define DJMEMC_BANK1CONF 0x8 +#define DJMEMC_BANK2CONF 0xc +#define DJMEMC_BANK3CONF 0x10 +#define DJMEMC_BANK4CONF 0x14 +#define DJMEMC_BANK5CONF 0x18 +#define DJMEMC_BANK6CONF 0x1c +#define DJMEMC_BANK7CONF 0x20 +#define DJMEMC_BANK8CONF 0x24 +#define DJMEMC_BANK9CONF 0x28 +#define DJMEMC_MEMTOP 0x2c +#define DJMEMC_CONFIG 0x30 +#define DJMEMC_REFRESH 0x34 + + +static uint64_t djmemc_read(void *opaque, hwaddr addr, unsigned size) +{ + DJMEMCState *s = opaque; + uint64_t val = 0; + + switch (addr) { + case DJMEMC_INTERLEAVECONF: + case DJMEMC_BANK0CONF ... DJMEMC_BANK9CONF: + case DJMEMC_MEMTOP: + case DJMEMC_CONFIG: + case DJMEMC_REFRESH: + val = s->regs[addr >> 2]; + break; + default: + qemu_log_mask(LOG_UNIMP, "djMEMC: unimplemented read addr=0x%"PRIx64 + " val=0x%"PRIx64 " size=%d\n", + addr, val, size); + } + + trace_djmemc_read(addr, val, size); + return val; +} + +static void djmemc_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + DJMEMCState *s = opaque; + + trace_djmemc_write(addr, val, size); + + switch (addr) { + case DJMEMC_INTERLEAVECONF: + case DJMEMC_BANK0CONF ... DJMEMC_BANK9CONF: + case DJMEMC_MEMTOP: + case DJMEMC_CONFIG: + case DJMEMC_REFRESH: + s->regs[addr >> 2] = val; + break; + default: + qemu_log_mask(LOG_UNIMP, "djMEMC: unimplemented write addr=0x%"PRIx64 + " val=0x%"PRIx64 " size=%d\n", + addr, val, size); + } +} + +static const MemoryRegionOps djmemc_mmio_ops = { + .read = djmemc_read, + .write = djmemc_write, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .endianness = DEVICE_BIG_ENDIAN, +}; + +static void djmemc_init(Object *obj) +{ + DJMEMCState *s = DJMEMC(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->mem_regs, obj, &djmemc_mmio_ops, s, "djMEMC", + DJMEMC_SIZE); + sysbus_init_mmio(sbd, &s->mem_regs); +} + +static void djmemc_reset_hold(Object *obj) +{ + DJMEMCState *s = DJMEMC(obj); + + memset(s->regs, 0, sizeof(s->regs)); +} + +static const VMStateDescription vmstate_djmemc = { + .name = "djMEMC", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, DJMEMCState, DJMEMC_NUM_REGS), + VMSTATE_END_OF_LIST() + } +}; + +static void djmemc_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->vmsd = &vmstate_djmemc; + rc->phases.hold = djmemc_reset_hold; +} + +static const TypeInfo djmemc_info_types[] = { + { + .name = TYPE_DJMEMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DJMEMCState), + .instance_init = djmemc_init, + .class_init = djmemc_class_init, + }, +}; + +DEFINE_TYPES(djmemc_info_types) diff --git a/hw/misc/iosb.c b/hw/misc/iosb.c new file mode 100644 index 0000000..e7e9dcc --- /dev/null +++ b/hw/misc/iosb.c @@ -0,0 +1,133 @@ +/* + * QEMU IOSB emulation + * + * Copyright (c) 2019 Laurent Vivier + * Copyright (c) 2022 Mark Cave-Ayland + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "migration/vmstate.h" +#include "hw/sysbus.h" +#include "hw/misc/iosb.h" +#include "trace.h" + +#define IOSB_SIZE 0x2000 + +#define IOSB_CONFIG 0x0 +#define IOSB_CONFIG2 0x100 +#define IOSB_SONIC_SCSI 0x200 +#define IOSB_REVISION 0x300 +#define IOSB_SCSI_RESID 0x400 +#define IOSB_BRIGHTNESS 0x500 +#define IOSB_TIMEOUT 0x600 + + +static uint64_t iosb_read(void *opaque, hwaddr addr, + unsigned size) +{ + IOSBState *s = IOSB(opaque); + uint64_t val = 0; + + switch (addr) { + case IOSB_CONFIG: + case IOSB_CONFIG2: + case IOSB_SONIC_SCSI: + case IOSB_REVISION: + case IOSB_SCSI_RESID: + case IOSB_BRIGHTNESS: + case IOSB_TIMEOUT: + val = s->regs[addr >> 8]; + break; + default: + qemu_log_mask(LOG_UNIMP, "IOSB: unimplemented read addr=0x%"PRIx64 + " val=0x%"PRIx64 " size=%d\n", + addr, val, size); + } + + trace_iosb_read(addr, val, size); + return val; +} + +static void iosb_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + IOSBState *s = IOSB(opaque); + + switch (addr) { + case IOSB_CONFIG: + case IOSB_CONFIG2: + case IOSB_SONIC_SCSI: + case IOSB_REVISION: + case IOSB_SCSI_RESID: + case IOSB_BRIGHTNESS: + case IOSB_TIMEOUT: + s->regs[addr >> 8] = val; + break; + default: + qemu_log_mask(LOG_UNIMP, "IOSB: unimplemented write addr=0x%"PRIx64 + " val=0x%"PRIx64 " size=%d\n", + addr, val, size); + } + + trace_iosb_write(addr, val, size); +} + +static const MemoryRegionOps iosb_mmio_ops = { + .read = iosb_read, + .write = iosb_write, + .endianness = DEVICE_BIG_ENDIAN, +}; + +static void iosb_reset_hold(Object *obj) +{ + IOSBState *s = IOSB(obj); + + memset(s->regs, 0, sizeof(s->regs)); + + /* BCLK 33 MHz */ + s->regs[IOSB_CONFIG >> 8] = 1; +} + +static void iosb_init(Object *obj) +{ + IOSBState *s = IOSB(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->mem_regs, obj, &iosb_mmio_ops, s, "IOSB", + IOSB_SIZE); + sysbus_init_mmio(sbd, &s->mem_regs); +} + +static const VMStateDescription vmstate_iosb = { + .name = "IOSB", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, IOSBState, IOSB_REGS), + VMSTATE_END_OF_LIST() + } +}; + +static void iosb_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + dc->vmsd = &vmstate_iosb; + rc->phases.hold = iosb_reset_hold; +} + +static const TypeInfo iosb_info_types[] = { + { + .name = TYPE_IOSB, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IOSBState), + .instance_init = iosb_init, + .class_init = iosb_class_init, + }, +}; + +DEFINE_TYPES(iosb_info_types) diff --git a/hw/misc/mac_via.c b/hw/misc/mac_via.c index f84cc68..b6206ef 100644 --- a/hw/misc/mac_via.c +++ b/hw/misc/mac_via.c @@ -16,6 +16,7 @@ */ #include "qemu/osdep.h" +#include "exec/address-spaces.h" #include "migration/vmstate.h" #include "hw/sysbus.h" #include "hw/irq.h" @@ -114,6 +115,9 @@ #define VIA1A_CPUID1 0x04 /* CPU id bit 0 on RBV, others */ #define VIA1A_CPUID2 0x10 /* CPU id bit 0 on RBV, others */ #define VIA1A_CPUID3 0x40 /* CPU id bit 0 on RBV, others */ +#define VIA1A_CPUID_MASK (VIA1A_CPUID0 | VIA1A_CPUID1 | \ + VIA1A_CPUID2 | VIA1A_CPUID3) +#define VIA1A_CPUID_Q800 (VIA1A_CPUID0 | VIA1A_CPUID2) /* * Info on VIA1B is from Macintosh Family Hardware & MkLinux. @@ -698,6 +702,12 @@ static void adb_via_send(MOS6522Q800VIA1State *v1s, int state, uint8_t data) break; case ADB_STATE_IDLE: + ms->b |= VIA1B_vADBInt; + adb_autopoll_unblock(adb_bus); + + trace_via1_adb_send("IDLE", data, + (ms->b & VIA1B_vADBInt) ? "+" : "-"); + return; } @@ -865,6 +875,159 @@ static void via1_auxmode_update(MOS6522Q800VIA1State *v1s) if (irq != oldirq) { trace_via1_auxmode(irq); qemu_set_irq(v1s->auxmode_irq, irq); + + /* + * Clear the ADB interrupt. MacOS can leave VIA1B_vADBInt asserted + * (low) if a poll sequence doesn't complete before NetBSD disables + * interrupts upon boot. Fortunately NetBSD switches to the so-called + * "A/UX" interrupt mode after it initialises, so we can use this as + * a convenient place to clear the ADB interrupt for now. + */ + s->b |= VIA1B_vADBInt; + } +} + +/* + * Addresses and real values for TimeDBRA/TimeSCCB to allow timer calibration + * to succeed (NOTE: both values have been multiplied by 3 to cope with the + * speed of QEMU execution on a modern host + */ +#define MACOS_TIMEDBRA 0xd00 +#define MACOS_TIMESCCB 0xd02 + +#define MACOS_TIMEDBRA_VALUE (0x2a00 * 3) +#define MACOS_TIMESCCB_VALUE (0x079d * 3) + +static bool via1_is_toolbox_timer_calibrated(void) +{ + /* + * Indicate whether the MacOS toolbox has been calibrated by checking + * for the value of our magic constants + */ + uint16_t timedbra = lduw_be_phys(&address_space_memory, MACOS_TIMEDBRA); + uint16_t timesccdb = lduw_be_phys(&address_space_memory, MACOS_TIMESCCB); + + return (timedbra == MACOS_TIMEDBRA_VALUE && + timesccdb == MACOS_TIMESCCB_VALUE); +} + +static void via1_timer_calibration_hack(MOS6522Q800VIA1State *v1s, int addr, + uint64_t val, int size) +{ + /* + * Work around timer calibration to ensure we that we have non-zero and + * known good values for TIMEDRBA and TIMESCCDB. + * + * This works by attempting to detect the reset and calibration sequence + * of writes to VIA1 + */ + int old_timer_hack_state = v1s->timer_hack_state; + + switch (v1s->timer_hack_state) { + case 0: + if (addr == VIA_REG_PCR && val == 0x22) { + /* VIA_REG_PCR: configure VIA1 edge triggering */ + v1s->timer_hack_state = 1; + } + break; + case 1: + if (addr == VIA_REG_T2CL && val == 0xc) { + /* VIA_REG_T2CL: low byte of 1ms counter */ + if (!via1_is_toolbox_timer_calibrated()) { + v1s->timer_hack_state = 2; + } else { + v1s->timer_hack_state = 0; + } + } + break; + case 2: + if (addr == VIA_REG_T2CH && val == 0x3) { + /* + * VIA_REG_T2CH: high byte of 1ms counter (very likely at the + * start of SETUPTIMEK) + */ + if (!via1_is_toolbox_timer_calibrated()) { + v1s->timer_hack_state = 3; + } else { + v1s->timer_hack_state = 0; + } + } + break; + case 3: + if (addr == VIA_REG_IER && val == 0x20) { + /* + * VIA_REG_IER: update at end of SETUPTIMEK + * + * Timer calibration has finished: unfortunately the values in + * TIMEDBRA (0xd00) and TIMESCCDB (0xd02) are so far out they + * cause divide by zero errors. + * + * Update them with values obtained from a real Q800 but with + * a x3 scaling factor which seems to work well + */ + stw_be_phys(&address_space_memory, MACOS_TIMEDBRA, + MACOS_TIMEDBRA_VALUE); + stw_be_phys(&address_space_memory, MACOS_TIMESCCB, + MACOS_TIMESCCB_VALUE); + + v1s->timer_hack_state = 4; + } + break; + case 4: + /* + * This is the normal post-calibration timer state: we should + * generally remain here unless we detect the A/UX calibration + * loop, or a write to VIA_REG_PCR suggesting a reset + */ + if (addr == VIA_REG_PCR && val == 0x22) { + /* Looks like there has been a reset? */ + v1s->timer_hack_state = 1; + } + + if (addr == VIA_REG_T2CL && val == 0xf0) { + /* VIA_REG_T2CL: low byte of counter (A/UX) */ + v1s->timer_hack_state = 5; + } + break; + case 5: + if (addr == VIA_REG_T2CH && val == 0x3c) { + /* + * VIA_REG_T2CH: high byte of counter (A/UX). We are now extremely + * likely to be in the A/UX timer calibration routine, so move to + * the next state where we enable the calibration hack. + */ + v1s->timer_hack_state = 6; + } else if ((addr == VIA_REG_IER && val == 0x20) || + addr == VIA_REG_T2CH) { + /* We're doing something else with the timer, not calibration */ + v1s->timer_hack_state = 0; + } + break; + case 6: + if ((addr == VIA_REG_IER && val == 0x20) || addr == VIA_REG_T2CH) { + /* End of A/UX timer calibration routine, or another write */ + v1s->timer_hack_state = 7; + } else { + v1s->timer_hack_state = 0; + } + break; + case 7: + /* + * This is the normal post-calibration timer state once both the + * MacOS toolbox and A/UX have been calibrated, until we see a write + * to VIA_REG_PCR to suggest a reset + */ + if (addr == VIA_REG_PCR && val == 0x22) { + /* Looks like there has been a reset? */ + v1s->timer_hack_state = 1; + } + break; + default: + g_assert_not_reached(); + } + + if (old_timer_hack_state != v1s->timer_hack_state) { + trace_via1_timer_hack_state(v1s->timer_hack_state); } } @@ -872,9 +1035,36 @@ static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned size) { MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque); MOS6522State *ms = MOS6522(s); + uint64_t ret; + int64_t now; addr = (addr >> 9) & 0xf; - return mos6522_read(ms, addr, size); + ret = mos6522_read(ms, addr, size); + switch (addr) { + case VIA_REG_A: + case VIA_REG_ANH: + /* Quadra 800 Id */ + ret = (ret & ~VIA1A_CPUID_MASK) | VIA1A_CPUID_Q800; + break; + case VIA_REG_T2CH: + if (s->timer_hack_state == 6) { + /* + * The A/UX timer calibration loop runs continuously until 2 + * consecutive iterations differ by at least 0x492 timer ticks. + * Modern hosts execute the timer calibration loop so fast that + * this situation never occurs causing a hang on boot. Use a + * similar method to Shoebill which is to randomly add 0x500 to + * the T2 counter value during calibration to enable it to + * eventually succeed. + */ + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + if (now & 1) { + ret += 0x5; + } + } + break; + } + return ret; } static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val, @@ -882,8 +1072,13 @@ static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val, { MOS6522Q800VIA1State *v1s = MOS6522_Q800_VIA1(opaque); MOS6522State *ms = MOS6522(v1s); + int oldstate, state; + int oldsr = ms->sr; addr = (addr >> 9) & 0xf; + + via1_timer_calibration_hack(v1s, addr, val, size); + mos6522_write(ms, addr, val, size); switch (addr) { @@ -894,6 +1089,38 @@ static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val, v1s->last_b = ms->b; break; + + case VIA_REG_SR: + { + /* + * NetBSD assumes it can send its first ADB command after sending + * the ADB_BUSRESET command in ADB_STATE_NEW without changing the + * state back to ADB_STATE_IDLE first as detailed in the ADB + * protocol. + * + * Add a workaround to detect this condition at the start of ADB + * enumeration and send the next command written to SR after a + * ADB_BUSRESET onto the bus regardless, even if we don't detect a + * state transition to ADB_STATE_NEW. + * + * Note that in my tests the NetBSD state machine takes one ADB + * operation to recover which means the probe for an ADB device at + * address 1 always fails. However since the first device is at + * address 2 then this will work fine, without having to come up + * with a more complicated and invasive solution. + */ + oldstate = (v1s->last_b & VIA1B_vADB_StateMask) >> + VIA1B_vADB_StateShift; + state = (ms->b & VIA1B_vADB_StateMask) >> VIA1B_vADB_StateShift; + + if (oldstate == ADB_STATE_NEW && state == ADB_STATE_NEW && + (ms->acr & VIA1ACR_vShiftOut) && + oldsr == 0 /* ADB_BUSRESET */) { + trace_via1_adb_netbsd_enum_hack(); + adb_via_send(v1s, state, ms->sr); + } + } + break; } } @@ -996,6 +1223,9 @@ static void mos6522_q800_via1_reset_hold(Object *obj) adb_set_autopoll_enabled(adb_bus, true); v1s->cmd = REG_EMPTY; v1s->alt = REG_EMPTY; + + /* Timer calibration hack */ + v1s->timer_hack_state = 0; } static void mos6522_q800_via1_realize(DeviceState *dev, Error **errp) @@ -1088,6 +1318,8 @@ static const VMStateDescription vmstate_q800_via1 = { VMSTATE_INT64(next_second, MOS6522Q800VIA1State), VMSTATE_TIMER_PTR(sixty_hz_timer, MOS6522Q800VIA1State), VMSTATE_INT64(next_sixty_hz, MOS6522Q800VIA1State), + /* Timer hack */ + VMSTATE_INT32(timer_hack_state, MOS6522Q800VIA1State), VMSTATE_END_OF_LIST() } }; diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 88ecab8..3365931 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -20,6 +20,8 @@ system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c')) # Mac devices system_ss.add(when: 'CONFIG_MOS6522', if_true: files('mos6522.c')) +system_ss.add(when: 'CONFIG_DJMEMC', if_true: files('djmemc.c')) +system_ss.add(when: 'CONFIG_IOSB', if_true: files('iosb.c')) # virt devices system_ss.add(when: 'CONFIG_VIRT_CTRL', if_true: files('virt_ctrl.c')) diff --git a/hw/misc/trace-events b/hw/misc/trace-events index bc87cd3..24ba7cc 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -271,7 +271,9 @@ via1_rtc_cmd_pram_sect_write(int sector, int offset, int addr, int value) "secto via1_adb_send(const char *state, uint8_t data, const char *vadbint) "state %s data=0x%02x vADBInt=%s" via1_adb_receive(const char *state, uint8_t data, const char *vadbint, int status, int index, int size) "state %s data=0x%02x vADBInt=%s status=0x%x index=%d size=%d" via1_adb_poll(uint8_t data, const char *vadbint, int status, int index, int size) "data=0x%02x vADBInt=%s status=0x%x index=%d size=%d" +via1_adb_netbsd_enum_hack(void) "using NetBSD enum hack" via1_auxmode(int mode) "setting auxmode to %d" +via1_timer_hack_state(int state) "setting timer_hack_state to %d" # grlib_ahb_apb_pnp.c grlib_ahb_pnp_read(uint64_t addr, unsigned size, uint32_t value) "AHB PnP read addr:0x%03"PRIx64" size:%u data:0x%08x" @@ -301,3 +303,11 @@ virt_ctrl_instance_init(void *dev) "ctrl: %p" lasi_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d" lasi_chip_read(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x" lasi_chip_write(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x" + +# djmemc.c +djmemc_read(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u" +djmemc_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u" + +# iosb.c +iosb_read(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u" +iosb_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u" |