The Quadra 800 contains a SWIM2 floppy controller, which is a
simplified revision of the original SWIM controller.  Implement it
based on publicly available specs; a future commit will connect it
to q800.

Signed-off-by: Matt Jacobson <[email protected]>
---
 hw/block/meson.build     |   2 +-
 hw/block/swim2.c         | 711 +++++++++++++++++++++++++++++++++++++++
 hw/block/trace-events    |   9 +
 include/hw/block/swim2.h |  58 ++++
 4 files changed, 779 insertions(+), 1 deletion(-)
 create mode 100644 hw/block/swim2.c
 create mode 100644 include/hw/block/swim2.h

diff --git a/hw/block/meson.build b/hw/block/meson.build
index c4b873a906..733e9ffb47 100644
--- a/hw/block/meson.build
+++ b/hw/block/meson.build
@@ -10,7 +10,7 @@ system_ss.add(when: 'CONFIG_PFLASH_CFI01', if_true: 
files('pflash_cfi01.c'))
 system_ss.add(when: 'CONFIG_PFLASH_CFI02', if_true: files('pflash_cfi02.c'))
 system_ss.add(when: 'CONFIG_SSI_M25P80', if_true: files('m25p80.c'))
 system_ss.add(when: 'CONFIG_SSI_M25P80', if_true: files('m25p80_sfdp.c'))
-system_ss.add(when: 'CONFIG_SWIM', if_true: files('swim.c', 
'sony_superdrive.c'))
+system_ss.add(when: 'CONFIG_SWIM', if_true: files('swim.c', 'swim2.c', 
'sony_superdrive.c'))
 system_ss.add(when: 'CONFIG_XEN_BUS', if_true: files('xen-block.c'))
 
 system_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio-blk.c'))
diff --git a/hw/block/swim2.c b/hw/block/swim2.c
new file mode 100644
index 0000000000..4ac486904f
--- /dev/null
+++ b/hw/block/swim2.c
@@ -0,0 +1,711 @@
+/*
+ * QEMU Macintosh floppy disk controller emulator (SWIM2)
+ *
+ * Copyright (c) 2025 Matt Jacobson <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
+#include "hw/block/sony_superdrive.h"
+#include "hw/block/swim2.h"
+#include "system/block-backend.h"
+#include "system/blockdev.h"
+#include "trace.h"
+
+#define SWIM2_MMIO_SIZE            0x2000
+#define SWIM2_REG_SHIFT            9
+
+#define SWIM2_FIFO_FLAG_MARK       0x01
+
+#define SWIM2_SETUP_INVERT_WRDATA  BIT(0)
+#define SWIM2_SETUP_3_5_OUTPUT     BIT(1)
+#define SWIM2_SETUP_GCR_MODE       BIT(2)
+#define SWIM2_SETUP_CLOCK_DIV_2    BIT(3)
+#define SWIM2_SETUP_TEST_MODE      BIT(4)
+#define SWIM2_SETUP_IBM_DATA_MODE  BIT(5)
+#define SWIM2_SETUP_GCR_WRITES     BIT(6)
+#define SWIM2_SETUP_RESERVED       BIT(7)
+
+#define SWIM2_MODE_CLR_FIFO        BIT(0)
+#define SWIM2_MODE_ENBL1           BIT(1)
+#define SWIM2_MODE_ENBL2           BIT(2)
+#define SWIM2_MODE_ACTION          BIT(3)
+#define SWIM2_MODE_WRITE           BIT(4)
+#define SWIM2_MODE_SIDE            BIT(5)
+#define SWIM2_MODE_ALWAYS1         BIT(6)
+#define SWIM2_MODE_MOTORON         BIT(7)
+
+#define SWIM2_ERROR_UNDERRUN       BIT(0)
+#define SWIM2_ERROR_MARK_IN_DATA   BIT(1)
+#define SWIM2_ERROR_OVERRUN        BIT(2)
+#define SWIM2_ERROR_SHORT          BIT(3)
+#define SWIM2_ERROR_LONG           BIT(4)
+
+/*
+ * These are shifted versions of the "SWIM offsets" in HardwareEqu.a in the
+ * System 7.1 source.
+ */
+typedef enum {
+    SWIM2_REG_DATA = 0,
+    SWIM2_REG_MARK = 1,
+    SWIM2_REG_ERROR = 2,
+    SWIM2_REG_PARAMETER = 3,
+    SWIM2_REG_PHASE = 4,
+    SWIM2_REG_SETUP = 5,
+    SWIM2_REG_WRITE_ZEROES = 6,
+    SWIM2_REG_WRITE_ONES = 7,
+} SWIM2Register;
+
+#define SWIM2_REG_WRITE_CRC  SWIM2_REG_ERROR
+#define SWIM2_REG_STATUS     SWIM2_REG_WRITE_ZEROES
+#define SWIM2_REG_HANDSHAKE  SWIM2_REG_WRITE_ONES
+
+static const char *const swim2_read_reg_names[] = {
+    [SWIM2_REG_DATA] = "DATA",
+    [SWIM2_REG_MARK] = "MARK",
+    [SWIM2_REG_ERROR] = "ERROR",
+    [SWIM2_REG_PARAMETER] = "PARAM",
+    [SWIM2_REG_PHASE] = "PHASE",
+    [SWIM2_REG_SETUP] = "SETUP",
+    [SWIM2_REG_STATUS] = "STATUS",
+    [SWIM2_REG_HANDSHAKE] = "HANDSHAKE",
+};
+
+static const char *const swim2_write_reg_names[] = {
+    [SWIM2_REG_DATA] = "DATA",
+    [SWIM2_REG_MARK] = "MARK",
+    [SWIM2_REG_WRITE_CRC] = "WRITE_CRC",
+    [SWIM2_REG_PARAMETER] = "PARAM",
+    [SWIM2_REG_PHASE] = "PHASE",
+    [SWIM2_REG_SETUP] = "SETUP",
+    [SWIM2_REG_WRITE_ZEROES] = "WRITE0",
+    [SWIM2_REG_WRITE_ONES] = "WRITE1",
+};
+
+static inline const char *swim2_reg_name(const uint8_t reg,
+                                         const bool write)
+{
+    assert(reg < 8);
+    const char *const *table = write ? swim2_write_reg_names :
+                                       swim2_read_reg_names;
+    return table[reg];
+}
+
+static uint32_t swim2_fifo_pack(const uint8_t data, const bool is_mark)
+{
+    return data | (is_mark ? BIT(8) : 0);
+}
+
+static uint8_t swim2_fifo_data(const uint32_t entry)
+{
+    return entry & 0xff;
+}
+
+static bool swim2_fifo_is_mark(const uint32_t entry)
+{
+    return (entry & BIT(8)) != 0;
+}
+
+static void swim2_set_error(SWIM2State *const ctrl, const uint8_t err)
+{
+    const uint8_t prev = ctrl->error_reg;
+    ctrl->error_reg |= err;
+    trace_swim2_error_set(prev, ctrl->error_reg, err, ctrl->mode_reg,
+                          (ctrl->mode_reg & SWIM2_MODE_WRITE) != 0,
+                          fifo32_num_used(&ctrl->fifo));
+}
+
+static void swim2_fifo_clear(SWIM2State *const ctrl)
+{
+    fifo32_reset(&ctrl->fifo);
+    trace_swim2_fifo_clear();
+}
+
+static bool swim2_fifo_push(SWIM2State *const ctrl, const uint8_t data,
+                            const bool is_mark)
+{
+    if (fifo32_is_full(&ctrl->fifo)) {
+        return false;
+    } else {
+        fifo32_push(&ctrl->fifo, swim2_fifo_pack(data, is_mark));
+        trace_swim2_fifo_push(fifo32_num_used(&ctrl->fifo), data, is_mark);
+        return true;
+    }
+}
+
+static bool swim2_fifo_pop(SWIM2State *const ctrl, uint8_t *const data,
+                           bool *const is_mark)
+{
+    if (fifo32_is_empty(&ctrl->fifo)) {
+        trace_swim2_fifo_pop(fifo32_num_used(&ctrl->fifo), 0, false, false);
+        return false;
+    } else {
+        const uint32_t entry = fifo32_pop(&ctrl->fifo);
+        const uint8_t value = swim2_fifo_data(entry);
+        const bool entry_is_mark = swim2_fifo_is_mark(entry);
+
+        *data = value;
+        if (is_mark) {
+            *is_mark = entry_is_mark;
+        }
+
+        trace_swim2_fifo_pop(fifo32_num_used(&ctrl->fifo), value,
+                             entry_is_mark, true);
+        return true;
+    }
+}
+
+static SonyDrive *swim2_active_drive(SWIM2State *const ctrl)
+{
+    uint8_t selected;
+
+    if (ctrl->mode_reg & SWIM2_MODE_ENBL1) {
+        selected = 0;
+    } else if (ctrl->mode_reg & SWIM2_MODE_ENBL2) {
+        selected = 1;
+    } else {
+        return NULL;
+    }
+
+    if (ctrl->drives[selected]) {
+        return &ctrl->drives[selected]->sony;
+    } else {
+        return NULL;
+    }
+}
+
+static void swim2_apply_drive_lines(SWIM2State *const ctrl)
+{
+    SonyDrive *const active = swim2_active_drive(ctrl);
+
+    for (uint8_t i = 0; i < SWIM2_MAX_FD; i++) {
+        SWIM2Drive *const entry = ctrl->drives[i];
+
+        if (!entry) {
+            continue;
+        }
+
+        SonyDrive *const drive = &entry->sony;
+
+        const uint8_t phases = (ctrl->phase_reg & 0xF) &
+                                    (ctrl->phase_reg >> 4);
+        const bool head = (ctrl->mode_reg & SWIM2_MODE_SIDE);
+        const bool enabled = (drive == active) &&
+                             ((ctrl->mode_reg & SWIM2_MODE_MOTORON) != 0);
+
+        sony_drive_set_inputs(drive, phases, head, enabled);
+    }
+}
+
+static void swim2_fill_fifo_from_drive(SWIM2State *const ctrl)
+{
+    SonyDrive *const drive = swim2_active_drive(ctrl);
+
+    if (!drive) {
+        return;
+    }
+
+    /*
+     * Don't wait for a mark byte if the drive's transfer stream is GCR 
encoded.
+     * A/UX hits this codepath, because it tries to read in MFM mode before
+     * falling back to GCR.
+     */
+    if (ctrl->wait_for_mark && sony_drive_uses_gcr_encoding(drive)) {
+        return;
+    }
+
+    while (!fifo32_is_full(&ctrl->fifo)) {
+        uint8_t data;
+        bool is_mark;
+
+        if (!sony_drive_read_byte(drive, &data, &is_mark)) {
+            break;
+        } else if (!ctrl->wait_for_mark || is_mark) {
+            ctrl->wait_for_mark = false;
+            const bool did_push = swim2_fifo_push(ctrl, data, is_mark);
+            assert(did_push);
+        }
+    }
+}
+
+static void swim2_push_fifo_to_drive(SWIM2State *const ctrl)
+{
+    SonyDrive *const drive = swim2_active_drive(ctrl);
+
+    if (!drive) {
+        /*
+         * The Mac ROM does this to measure how quickly we can spit bytes out
+         * to a drive.  Just consume the FIFO without error.
+         */
+        swim2_fifo_clear(ctrl);
+    } else {
+        uint8_t data;
+        bool is_mark;
+
+        while (swim2_fifo_pop(ctrl, &data, &is_mark)) {
+            if (ctrl->wait_for_mark && is_mark) {
+                ctrl->wait_for_mark = false;
+            } else if (!ctrl->wait_for_mark && !is_mark) {
+                sony_drive_write_byte(drive, data);
+            }
+        }
+    }
+}
+
+static void swim2_update_mode(SWIM2State *const ctrl, const uint8_t mask,
+                              const bool set_bits)
+{
+    const uint8_t prev_mode = ctrl->mode_reg;
+
+    if (set_bits) {
+        ctrl->mode_reg |= mask;
+    } else {
+        ctrl->mode_reg &= ~mask;
+        ctrl->mode_reg |= SWIM2_MODE_ALWAYS1;
+    }
+
+    if (!set_bits) {
+        ctrl->parameter_index = 0;
+    }
+
+    if ((mask & SWIM2_MODE_CLR_FIFO) && set_bits) {
+        swim2_fifo_clear(ctrl);
+    }
+
+    swim2_apply_drive_lines(ctrl);
+
+    const bool prev_action = prev_mode & SWIM2_MODE_ACTION;
+    const bool action = ctrl->mode_reg & SWIM2_MODE_ACTION;
+
+    if (!prev_action && action) {
+        ctrl->did_handshake = false;
+        ctrl->wait_for_mark = !(ctrl->setup_reg & SWIM2_SETUP_GCR_MODE);
+
+        if (ctrl->mode_reg & SWIM2_MODE_WRITE) {
+            swim2_push_fifo_to_drive(ctrl);
+        } else {
+            swim2_fill_fifo_from_drive(ctrl);
+        }
+    }
+}
+
+static void swim2_handle_phase_write(SWIM2State *const ctrl,
+                                     const uint8_t value)
+{
+    ctrl->phase_reg = value;
+    swim2_apply_drive_lines(ctrl);
+}
+
+static void swim2_handle_setup_write(SWIM2State *const ctrl,
+                                     const uint8_t value)
+{
+    ctrl->setup_reg = value;
+}
+
+/*
+ * We don't actually do anything with these parameter values other than store
+ * them.
+ */
+static void swim2_handle_parameter_write(SWIM2State *const ctrl,
+                                         const uint8_t value)
+{
+    ctrl->parameter_data[ctrl->parameter_index] = value;
+    ctrl->parameter_index = (ctrl->parameter_index + 1) & 0x3;
+}
+
+static uint8_t swim2_handle_parameter_read(SWIM2State *const ctrl)
+{
+    uint8_t value;
+
+    /* Return 0 for parameters 1 and 3, as A/UX expects for a SWIM2. */
+    if (ctrl->parameter_index == 1 || ctrl->parameter_index == 3) {
+        value = 0;
+    } else {
+        value = ctrl->parameter_data[ctrl->parameter_index];
+    }
+
+    ctrl->parameter_index = (ctrl->parameter_index + 1) & 0x3;
+    return value;
+}
+
+static uint8_t swim2_handle_handshake_read(SWIM2State *const ctrl)
+{
+    SonyDrive *const drive = swim2_active_drive(ctrl);
+
+    ctrl->did_handshake = true;
+
+    const bool empty = fifo32_is_empty(&ctrl->fifo);
+    const bool full = fifo32_is_full(&ctrl->fifo);
+    const bool error = ctrl->error_reg != 0;
+    const bool sense = drive ? sony_drive_read_sense(drive) : true;
+    const bool mark_next = !empty &&
+                           swim2_fifo_is_mark(fifo32_peek(&ctrl->fifo));
+
+    uint8_t value = 0;
+    if (mark_next) {
+        value |= BIT(0);
+    }
+    /* bit 1: 1 when invalid CRC (i.e., never, for us) */
+    /* bit 2: rddata, not emulated */
+    if (sense) {
+        value |= BIT(3);
+    }
+    /* bit 4: unused */
+    if (error) {
+        value |= BIT(5);
+    }
+
+    if ((ctrl->mode_reg & SWIM2_MODE_WRITE) != 0) {
+        if (empty) {
+            value |= BIT(6);
+        }
+
+        if (!full || error) {
+            value |= BIT(7);
+        }
+    } else {
+        if (full) {
+            value |= BIT(6);
+        }
+
+        if (!empty) {
+            value |= BIT(7);
+        }
+    }
+
+    return value;
+}
+
+static uint64_t swim2_read(void *const opaque, const hwaddr addr,
+                           const unsigned int size)
+{
+    SWIM2State *const ctrl = SWIM2(opaque);
+    const uint8_t reg = (addr >> SWIM2_REG_SHIFT) & 0x7;
+    uint8_t value = 0xff;
+
+    switch (reg) {
+    case SWIM2_REG_DATA: {
+        bool is_mark;
+
+        if (!swim2_fifo_pop(ctrl, &value, &is_mark)) {
+            value = 0xff;
+            swim2_set_error(ctrl, SWIM2_ERROR_OVERRUN);
+        } else {
+            if (is_mark) {
+                swim2_set_error(ctrl, SWIM2_ERROR_MARK_IN_DATA);
+            }
+
+            swim2_fill_fifo_from_drive(ctrl);
+        }
+
+        break;
+    }
+    case SWIM2_REG_MARK: {
+        /*
+         * Allow reading data bytes from here; the specs seem unclear on 
whether
+         * this is allowed, but the Mac Sony driver does so.
+         */
+
+        if (!ctrl->did_handshake) {
+            /*
+             * The Mac ROM does something weird when reading an MFM disk.
+             * Immediately after setting ACTION, it pulls and discards two 
bytes
+             * from MARK.  To avoid needing to emulate this with timing,
+             * simply detect the reads that are not preceded by a HANDSHAKE
+             * and give back the garbage they seem to expect.
+             */
+            value = 0xff;
+        } else if (!swim2_fifo_pop(ctrl, &value, NULL)) {
+            value = 0xff;
+            swim2_set_error(ctrl, SWIM2_ERROR_OVERRUN);
+        } else {
+            swim2_fill_fifo_from_drive(ctrl);
+        }
+
+        break;
+    }
+    case SWIM2_REG_ERROR:
+        value = ctrl->error_reg;
+        ctrl->error_reg = 0;
+        break;
+    case SWIM2_REG_PARAMETER:
+        value = swim2_handle_parameter_read(ctrl);
+        break;
+    case SWIM2_REG_PHASE:
+        value = ctrl->phase_reg;
+        break;
+    case SWIM2_REG_SETUP:
+        value = ctrl->setup_reg;
+        break;
+    case SWIM2_REG_STATUS:
+        value = ctrl->mode_reg;
+        break;
+    case SWIM2_REG_HANDSHAKE:
+        value = swim2_handle_handshake_read(ctrl);
+        break;
+    default:
+        value = 0xff;
+        break;
+    }
+
+    trace_swim2_mmio_read(addr, size, reg, swim2_reg_name(reg, false), value,
+                          ctrl->mode_reg, ctrl->setup_reg, ctrl->phase_reg,
+                          fifo32_num_used(&ctrl->fifo));
+    return value;
+}
+
+static void swim2_write(void *const opaque, const hwaddr addr,
+                        const uint64_t data, const unsigned int size)
+{
+    SWIM2State *const ctrl = SWIM2(opaque);
+    const uint8_t reg = (addr >> SWIM2_REG_SHIFT) & 0x7;
+    const uint8_t value = data & 0xff;
+
+    switch (reg) {
+    case SWIM2_REG_DATA:
+        if (!swim2_fifo_push(ctrl, value, 0)) {
+            swim2_set_error(ctrl, SWIM2_ERROR_OVERRUN);
+        } else if ((ctrl->mode_reg & SWIM2_MODE_ACTION)) {
+            swim2_push_fifo_to_drive(ctrl);
+        }
+        break;
+    case SWIM2_REG_MARK:
+        if (!swim2_fifo_push(ctrl, value, SWIM2_FIFO_FLAG_MARK)) {
+            swim2_set_error(ctrl, SWIM2_ERROR_OVERRUN);
+        } else if ((ctrl->mode_reg & SWIM2_MODE_ACTION)) {
+            swim2_push_fifo_to_drive(ctrl);
+        }
+        break;
+    case SWIM2_REG_WRITE_CRC:
+        /* Nothing to do. */
+        break;
+    case SWIM2_REG_PARAMETER:
+        swim2_handle_parameter_write(ctrl, value);
+        break;
+    case SWIM2_REG_PHASE:
+        swim2_handle_phase_write(ctrl, value);
+        break;
+    case SWIM2_REG_SETUP:
+        swim2_handle_setup_write(ctrl, value);
+        break;
+    case SWIM2_REG_WRITE_ZEROES:
+        swim2_update_mode(ctrl, value, false);
+        break;
+    case SWIM2_REG_WRITE_ONES:
+        swim2_update_mode(ctrl, value, true);
+        break;
+    default:
+        break;
+    }
+
+    trace_swim2_mmio_write(addr, size, reg, swim2_reg_name(reg, true), value,
+                           ctrl->mode_reg, ctrl->setup_reg, ctrl->phase_reg,
+                           fifo32_num_used(&ctrl->fifo));
+}
+
+void swim2_set_sel(SWIM2State *ctrl, bool sel)
+{
+    trace_swim2_set_sel(sel);
+    if (sel) {
+        ctrl->mode_reg |= SWIM2_MODE_SIDE;
+    } else {
+        ctrl->mode_reg &= ~SWIM2_MODE_SIDE;
+    }
+    swim2_apply_drive_lines(ctrl);
+}
+
+static const MemoryRegionOps swim2_mmio_ops = {
+    .read = swim2_read,
+    .write = swim2_write,
+    .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static void swim2_change_cb(void *const opaque, const bool load,
+                            Error **const errp)
+{
+    SWIM2Drive *const drive = opaque;
+
+    if (!load) {
+        blk_set_perm(drive->conf.blk, 0, BLK_PERM_ALL, &error_abort);
+    } else if (!blkconf_apply_backend_options(&drive->conf,
+                 !blk_supports_write_perm(drive->conf.blk), false, errp)) {
+        return;
+    }
+
+    sony_drive_set_block_backend(&drive->sony, drive->conf.blk);
+}
+
+static void swim2_drive_realize(DeviceState *const dev, Error **const errp)
+{
+    SWIM2Drive *const drive = SWIM2_DRIVE(dev);
+    SWIM2State *const ctrl = SWIM2(dev->parent_bus->parent);
+
+    if (!drive->conf.blk) {
+        drive->conf.blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL);
+        const int ret = blk_attach_dev(drive->conf.blk, dev);
+        assert(ret == 0);
+    }
+
+    if (drive->unit < 0 || drive->unit >= SWIM2_MAX_FD) {
+        error_setg(errp, "unit %d out of range (0-%d)", drive->unit,
+                   SWIM2_MAX_FD - 1);
+        return;
+    }
+
+    if (ctrl->drives[drive->unit]) {
+        error_setg(errp, "floppy unit %d already in use", drive->unit);
+        return;
+    }
+
+    if (!blkconf_blocksizes(&drive->conf, errp)) {
+        /* errp set by blkconf_blocksizes(). */
+        return;
+    }
+
+    if (drive->conf.logical_block_size  != 512 ||
+        drive->conf.physical_block_size != 512) {
+        error_setg(errp, "physical and logical block size must be 512 for"
+                   " floppy");
+        return;
+    }
+
+    drive->conf.rerror = BLOCKDEV_ON_ERROR_AUTO;
+    drive->conf.werror = BLOCKDEV_ON_ERROR_AUTO;
+
+    if (!blkconf_apply_backend_options(&drive->conf,
+          !blk_supports_write_perm(drive->conf.blk), false, errp)) {
+        /* errp set by blkconf_apply_backend_options(). */
+        return;
+    }
+
+    if (blk_get_on_error(drive->conf.blk, 0) != BLOCKDEV_ON_ERROR_ENOSPC &&
+        blk_get_on_error(drive->conf.blk, 0) != BLOCKDEV_ON_ERROR_REPORT) {
+        error_setg(errp, "SWIM2 doesn't support drive option werror");
+        return;
+    }
+
+    if (blk_get_on_error(drive->conf.blk, 1) != BLOCKDEV_ON_ERROR_REPORT) {
+        error_setg(errp, "SWIM2 doesn't support drive option rerror");
+        return;
+    }
+
+    ctrl->drives[drive->unit] = drive;
+    sony_drive_set_block_backend(&drive->sony, drive->conf.blk);
+
+    static const BlockDevOps swim2_block_ops = {
+        .change_media_cb = swim2_change_cb,
+    };
+    blk_set_dev_ops(drive->conf.blk, &swim2_block_ops, drive);
+}
+
+static void swim2_init(Object *const obj)
+{
+    SWIM2State *const ctrl = SWIM2(obj);
+
+    memory_region_init_io(&ctrl->mmio, obj, &swim2_mmio_ops, ctrl,
+                          "swim2", SWIM2_MMIO_SIZE);
+    sysbus_init_mmio(&ctrl->parent_obj, &ctrl->mmio);
+
+    for (int i = 0; i < SWIM2_MAX_FD; i++) {
+        ctrl->drives[i] = NULL;
+    }
+
+    memset(ctrl->parameter_data, 0, sizeof ctrl->parameter_data);
+    ctrl->parameter_index = 0;
+    ctrl->phase_reg = 0;
+    ctrl->setup_reg = 0;
+    ctrl->mode_reg = SWIM2_MODE_ALWAYS1;
+    ctrl->error_reg = 0;
+    ctrl->wait_for_mark = false;
+    fifo32_create(&ctrl->fifo, 2);
+    swim2_fifo_clear(ctrl);
+}
+
+static void swim2_finalize(Object *const obj)
+{
+    SWIM2State *const ctrl = SWIM2(obj);
+
+    fifo32_destroy(&ctrl->fifo);
+}
+
+static void swim2_realize(DeviceState *const dev, Error **const errp)
+{
+    SWIM2State *const ctrl = SWIM2(dev);
+    ctrl->bus = qbus_new(TYPE_SWIM2_BUS, dev, "SWIM2 dummy bus");
+
+    for (int unit = 0; unit < SWIM2_MAX_FD; unit++) {
+        DriveInfo *const dinfo = drive_get(IF_FLOPPY, 0, unit);
+
+        if (dinfo) {
+            DeviceState *const drive = qdev_new(TYPE_SWIM2_DRIVE);
+            qdev_prop_set_int32(drive, "unit", unit);
+            qdev_prop_set_drive_err(drive, "drive",
+                                    blk_by_legacy_dinfo(dinfo), &error_abort);
+            qdev_realize_and_unref(drive, ctrl->bus, &error_abort);
+        }
+    }
+}
+
+/* TODO: support migration */
+static void swim2_class_init(ObjectClass *const oc, const void *const opaque)
+{
+    DeviceClass *const dc = DEVICE_CLASS(oc);
+    dc->realize = swim2_realize;
+    dc->desc = "Apple Macintosh SWIM2 floppy diskette drive controller";
+}
+
+static void swim2_bus_class_init(ObjectClass *const oc,
+                                 const void *const opaque)
+{
+    BusClass *const bc = BUS_CLASS(oc);
+    bc->max_dev = 2;
+}
+
+/* TODO: support migration */
+static void swim2_drive_class_init(ObjectClass *const oc,
+                                   const void *const opaque)
+{
+    DeviceClass *const dc = DEVICE_CLASS(oc);
+    dc->bus_type = TYPE_SWIM2_BUS;
+    dc->realize = swim2_drive_realize;
+    set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
+
+    static const Property swim2_drive_properties[] = {
+        DEFINE_BLOCK_PROPERTIES(SWIM2Drive, conf),
+        DEFINE_PROP_INT32("unit", SWIM2Drive, unit, -1),
+    };
+    device_class_set_props(dc, swim2_drive_properties);
+
+    dc->desc = "Apple Macintosh SuperDrive floppy diskette drive";
+}
+
+static const TypeInfo swim2_types[] = {
+    {
+        .name = TYPE_SWIM2,
+        .parent = TYPE_SYS_BUS_DEVICE,
+        .instance_size = sizeof(SWIM2State),
+        .instance_init = swim2_init,
+        .instance_finalize = swim2_finalize,
+        .class_init = swim2_class_init,
+    },
+    {
+        .name = TYPE_SWIM2_BUS,
+        .parent = TYPE_BUS,
+        .instance_size = 0,
+        .class_init = swim2_bus_class_init,
+    },
+    {
+        .name = TYPE_SWIM2_DRIVE,
+        .parent = TYPE_DEVICE,
+        .instance_size = sizeof(SWIM2Drive),
+        .class_init = swim2_drive_class_init,
+    },
+};
+
+DEFINE_TYPES(swim2_types)
diff --git a/hw/block/trace-events b/hw/block/trace-events
index c5e021738c..c5793c7da8 100644
--- a/hw/block/trace-events
+++ b/hw/block/trace-events
@@ -112,6 +112,15 @@ swim_iwmctrl_write(int reg, const char *name, unsigned 
size, uint64_t value) "re
 swim_switch_to_ism(void) "switch from IWM to ISM mode"
 swim_switch_to_iwm(void) "switch from ISM to IWM mode"
 
+# swim2.c
+swim2_mmio_read(uint64_t addr, unsigned size, int reg, const char *name, 
uint64_t value, uint8_t mode, uint8_t setup, uint8_t phase, uint32_t 
fifo_count) "addr=0x%"PRIx64" size=%u reg=%d [%s] value=0x%"PRIx64" mode=0x%02x 
setup=0x%02x phase=0x%02x fifo=%u"
+swim2_mmio_write(uint64_t addr, unsigned size, int reg, const char *name, 
uint64_t value, uint8_t mode, uint8_t setup, uint8_t phase, uint32_t 
fifo_count) "addr=0x%"PRIx64" size=%u reg=%d [%s] value=0x%"PRIx64" mode=0x%02x 
setup=0x%02x phase=0x%02x fifo=%u"
+swim2_fifo_push(uint32_t count, uint8_t data, uint8_t flags) "fifo=%u 
data=0x%02x flags=0x%02x"
+swim2_fifo_pop(uint32_t count, uint8_t data, uint8_t flags, bool success) 
"fifo=%u data=0x%02x flags=0x%02x success=%d"
+swim2_fifo_clear(void) "clear FIFO"
+swim2_set_sel(bool sel) "set sel=%d"
+swim2_error_set(uint8_t prev, uint8_t curr, uint8_t new_err, uint8_t mode, 
bool write_mode, uint32_t fifo_count) "prev=0x%02x curr=0x%02x new=0x%02x 
mode=0x%02x write=%d fifo=%u"
+
 # sony_superdrive.c
 sony_drive_prepare(void *drive, uint8_t track, uint8_t head, uint8_t sector, 
bool gcr, uint16_t payload_len) "drive=%p track=%"PRIu8" head=%"PRIu8" 
sector=%"PRIu8" gcr=%d length=%"PRIu16
 sony_drive_persist(void *drive, uint64_t lba, bool success) "drive=%p 
lba=0x%"PRIx64" success=%d"
diff --git a/include/hw/block/swim2.h b/include/hw/block/swim2.h
new file mode 100644
index 0000000000..b40af25a9f
--- /dev/null
+++ b/include/hw/block/swim2.h
@@ -0,0 +1,58 @@
+/*
+ * QEMU Macintosh floppy disk controller emulator (SWIM2)
+ *
+ * Copyright (c) 2025 Matt Jacobson <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_BLOCK_SWIM2_H
+#define HW_BLOCK_SWIM2_H
+
+#include "hw/core/qdev.h"
+#include "hw/core/sysbus.h"
+#include "hw/block/block.h"
+#include "hw/block/sony_superdrive.h"
+#include "qemu/fifo32.h"
+#include "qom/object.h"
+
+#define TYPE_SWIM2 "swim2"
+#define TYPE_SWIM2_BUS "swim2-bus"
+#define TYPE_SWIM2_DRIVE "swim2-drive"
+
+OBJECT_DECLARE_SIMPLE_TYPE(SWIM2State, SWIM2)
+OBJECT_DECLARE_SIMPLE_TYPE(SWIM2Drive, SWIM2_DRIVE)
+
+#define SWIM2_MAX_FD 2
+
+struct SWIM2Drive {
+    DeviceState parent_obj;
+
+    BlockConf conf;
+    SonyDrive sony;
+    int32_t unit;
+};
+
+struct SWIM2State {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    BusState *bus;
+
+    SWIM2Drive *drives[SWIM2_MAX_FD];
+
+    uint8_t parameter_data[4];
+    uint8_t parameter_index;
+    uint8_t phase_reg;
+    uint8_t setup_reg;
+    uint8_t mode_reg;
+    uint8_t error_reg;
+    bool did_handshake;
+    bool wait_for_mark;
+
+    Fifo32 fifo;
+};
+
+void swim2_set_sel(SWIM2State *ctrl, bool sel);
+
+#endif /* HW_BLOCK_SWIM2_H */
-- 
2.54.0


Reply via email to