All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] [RFC] scsi: New ncr53c710_v2 driver
@ 2026-11-06 22:18 Helge Deller
  2026-11-06 22:18 ` [PATCH 1/4] target/hppa: drop dependency on ncr53c710.h Helge Deller
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Helge Deller @ 2026-11-06 22:18 UTC (permalink / raw)
  To: Peter Maydell, Keith Monahan, BALATON Zoltan, Mark Cave-Ayland,
	qemu-devel, Paolo Bonzini
  Cc: Helge Deller

From: Helge Deller <deller@gmx.de>

This patch series contains a replacement driver for the current ncr53c710
driver.  The existing driver was written from scratch during the Google Summer
of Code 2025 program.  It works okay with Linux, but fails badly when trying to
run any version of HP-UX or BSD.

Keith Monahan tried to fix the existing driver. But after some hacking it
became clear that starting fresh and using the lsi53c895a driver as base seems
more promising to get a correctly functional driver.

His patches to replace the existing driver [1] in-place were rejected because
the patches were too big (and changed too many not-related things inside one
patch) and thus a potential review seemed impossible (which I agree on).

Since the new driver is not fixing the old driver, but is actually a completely
new driver, trying to put together patches which replace the old driver step by
step is impossible. I gave it a shot, but failed badly.

As the maintainer of the PA-RISC emulation (which is actually the only user of
the ncr53c710 driver), I'm of course very much interested in having the new
functional driver instead of the old broken driver.  The new driver works
reliable with Linux, HP-UX 9.05, HP-UX 10.20 and NetBSD, which is a big step
forward.

My proposed solution to this issue in this patch series is:
- in patch #2: add the new driver (v2) in a new source file: ncr53c710_v2.c
- in patch #3: wire up the new driver instead of the old driver
- in patch #4: remove the code of the old driver (ncr53c710.c)

Usually I'd prefer an evolutionary way to the new driver as well, but if you
check the code yourself you will see this will not be possible.

That said, I would be happy if my proposal here would be accepted.

Thanks!
Helge

[1] https://lists.nongnu.org/archive/html/qemu-devel/2026-07/msg00263.html

Helge Deller (4):
  target/hppa: drop dependency on ncr53c710.h
  ncr53c710_v2: add new NCR53C710 chipset driver
  lasi_ncr710: Wire up the new ncr53c710_v2 driver in LASI
  hw/scsi: Drop old ncr53c710 v1 driver

 hw/hppa/machine.c      |    1 -
 hw/scsi/lasi_ncr710.c  |  119 +-
 hw/scsi/lasi_ncr710.h  |   30 +-
 hw/scsi/meson.build    |    2 +-
 hw/scsi/ncr53c710.c    | 2473 ----------------------------------------
 hw/scsi/ncr53c710.h    |  246 ----
 hw/scsi/ncr53c710_v2.c | 1969 ++++++++++++++++++++++++++++++++
 hw/scsi/ncr53c710_v2.h |  183 +++
 hw/scsi/trace-events   |   36 +
 9 files changed, 2240 insertions(+), 2819 deletions(-)
 delete mode 100644 hw/scsi/ncr53c710.c
 delete mode 100644 hw/scsi/ncr53c710.h
 create mode 100644 hw/scsi/ncr53c710_v2.c
 create mode 100644 hw/scsi/ncr53c710_v2.h

-- 
2.54.0



^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 1/4] target/hppa: drop dependency on ncr53c710.h
  2026-11-06 22:18 [PATCH 0/4] [RFC] scsi: New ncr53c710_v2 driver Helge Deller
@ 2026-11-06 22:18 ` Helge Deller
  2026-11-06 22:18 ` [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver Helge Deller
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 10+ messages in thread
From: Helge Deller @ 2026-11-06 22:18 UTC (permalink / raw)
  To: Peter Maydell, Keith Monahan, BALATON Zoltan, Mark Cave-Ayland,
	qemu-devel, Paolo Bonzini
  Cc: Helge Deller

From: Helge Deller <deller@gmx.de>

The HP machine uses the LASI layer and not the SCSI controller directly,
so the reference to the header file can go.

Signed-off-by: Helge Deller <deller@gmx.de>
---
 hw/hppa/machine.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/hw/hppa/machine.c b/hw/hppa/machine.c
index 8a3f673daf..1f6be1e34b 100644
--- a/hw/hppa/machine.c
+++ b/hw/hppa/machine.c
@@ -30,7 +30,6 @@
 #include "hw/pci-host/astro.h"
 #include "hw/pci-host/dino.h"
 #include "hw/misc/lasi.h"
-#include "hw/scsi/ncr53c710.h"
 #include "hw/scsi/lasi_ncr710.h"
 #include "hppa_hardware.h"
 #include "qemu/units.h"
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver
  2026-11-06 22:18 [PATCH 0/4] [RFC] scsi: New ncr53c710_v2 driver Helge Deller
  2026-11-06 22:18 ` [PATCH 1/4] target/hppa: drop dependency on ncr53c710.h Helge Deller
@ 2026-11-06 22:18 ` Helge Deller
  2026-11-08 11:43   ` BALATON Zoltan
  2026-11-06 22:18 ` [PATCH 3/4] lasi_ncr710: Wire up the new ncr53c710_v2 driver in LASI Helge Deller
  2026-11-06 22:18 ` [PATCH 4/4] hw/scsi: Drop old ncr53c710 v1 driver Helge Deller
  3 siblings, 1 reply; 10+ messages in thread
From: Helge Deller @ 2026-11-06 22:18 UTC (permalink / raw)
  To: Peter Maydell, Keith Monahan, BALATON Zoltan, Mark Cave-Ayland,
	qemu-devel, Paolo Bonzini
  Cc: Helge Deller

From: Helge Deller <deller@gmx.de>

This adds a new driver for the NC53C710 chipset. This drover is based on the
lsi53c895a driver, and thus avoids many issues of the existing ncr53c710
driver, which was written from scratch.
This driver has been successfully used to install and run Linux, HP-UX 9.05,
HP-UX 10.20 and NetBSD, while the old driver failed on all operating systems
other than Linux.

Signed-off-by: Keith Monahan <keith@techtravels.org>
Signed-off-by: Helge Deller <deller@gmx.de>
---
 hw/scsi/ncr53c710_v2.c | 1969 ++++++++++++++++++++++++++++++++++++++++
 hw/scsi/ncr53c710_v2.h |  183 ++++
 hw/scsi/trace-events   |   36 +
 3 files changed, 2188 insertions(+)
 create mode 100644 hw/scsi/ncr53c710_v2.c
 create mode 100644 hw/scsi/ncr53c710_v2.h

diff --git a/hw/scsi/ncr53c710_v2.c b/hw/scsi/ncr53c710_v2.c
new file mode 100644
index 0000000000..b351cdbc62
--- /dev/null
+++ b/hw/scsi/ncr53c710_v2.c
@@ -0,0 +1,1969 @@
+/*
+ * QEMU NCR 53C710 SCSI I/O Processor emulation
+ *
+ * Copyright (c) 2026 Keith Monahan <keith@techtravels.org>
+ * Copyright (c) 2006 CodeSourcery (lsi53c895a.c, written by Paul Brook)
+ *
+ * A rewrite of the QEMU NCR 53C710 model.  The 53C710 is an early member of
+ * the NCR/LSI SCRIPTS processor family, so the SCRIPTS engine here is derived
+ * from QEMU's LSI53C895A model and adapted to the 53C710's register map,
+ * single byte interrupt model, big endian SCRIPTS fetch (PA-RISC) and 24 bit
+ * DMA, per the NCR 53C710 Data Manual (Jun 1992) and Programmer's Guide
+ * (Oct 1990).
+ *
+ * Derived from hw/scsi/lsi53c895a.c (Copyright (c) 2006 CodeSourcery, written
+ * by Paul Brook).
+ *
+ * Replaces the 53C710 model written for Google Summer of Code 2025 by
+ * Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com>, itself based on
+ * earlier NCR 53C710 work by Helge Deller and by Toni Wilen (for UAE).
+ *
+ * The only instantiated device is the LASI wrapper (lasi_ncr710.c), which
+ * embeds one NCR710State by value and drives it through this file.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "hw/core/irq.h"
+#include "hw/scsi/ncr53c710_v2.h"
+#include "system/block-backend.h"
+#include "trace.h"
+
+/* SCNTL0 (0x00) */
+#define NCR710_SCNTL0_TRG       0x01
+#define NCR710_SCNTL0_AAP       0x02
+#define NCR710_SCNTL0_EPG       0x04
+#define NCR710_SCNTL0_EPC       0x08
+#define NCR710_SCNTL0_WATN      0x10
+#define NCR710_SCNTL0_START     0x20
+/* ARB1|ARB0 = 0xc0 (full arbitration) at reset */
+
+/* SCNTL1 (0x01) */
+#define NCR710_SCNTL1_AESP      0x04
+#define NCR710_SCNTL1_RST       0x08
+#define NCR710_SCNTL1_CON       0x10
+#define NCR710_SCNTL1_ESR       0x20
+#define NCR710_SCNTL1_ADB       0x40
+#define NCR710_SCNTL1_EXC       0x80
+
+/* SSTAT0 (0x0d) and SIEN (0x03) share this bit layout. */
+#define NCR710_STAT0_PAR        0x01
+#define NCR710_STAT0_RST        0x02
+#define NCR710_STAT0_UDC        0x04
+#define NCR710_STAT0_SGE        0x08
+#define NCR710_STAT0_SEL        0x10
+#define NCR710_STAT0_STO        0x20
+#define NCR710_STAT0_FCMP       0x40
+#define NCR710_STAT0_MA         0x80
+
+/* DSTAT (0x0c) and DIEN (0x39) share this bit layout. */
+#define NCR710_DSTAT_IID        0x01
+#define NCR710_DSTAT_WTD        0x02
+#define NCR710_DSTAT_SIR        0x04
+#define NCR710_DSTAT_SSI        0x08
+#define NCR710_DSTAT_ABRT       0x10
+#define NCR710_DSTAT_BF         0x20
+#define NCR710_DSTAT_DFE        0x80   /* pure status; never raises an IRQ */
+
+/* ISTAT (0x21) */
+#define NCR710_ISTAT_DIP        0x01
+#define NCR710_ISTAT_SIP        0x02
+#define NCR710_ISTAT_CON        0x08
+#define NCR710_ISTAT_SIGP       0x20
+#define NCR710_ISTAT_RST        0x40
+#define NCR710_ISTAT_ABRT       0x80
+
+/* SBCL (0x0b): low 3 bits mirror the SCSI phase, upper bits are bus signals. */
+#define NCR710_SBCL_ATN         0x08
+#define NCR710_SBCL_SEL         0x10
+#define NCR710_SBCL_BSY         0x20
+#define NCR710_SBCL_ACK         0x40
+#define NCR710_SBCL_REQ         0x80
+
+/* SOCL (0x07) */
+#define NCR710_SOCL_ATN         0x08
+
+/* CTEST2 (0x16) */
+#define NCR710_CTEST2_DACK      0x01
+#define NCR710_CTEST2_SIGP      0x40
+
+/* DMODE (0x38) */
+#define NCR710_DMODE_MAN        0x01
+
+/* DCNTL (0x3b) */
+#define NCR710_DCNTL_COM        0x01
+#define NCR710_DCNTL_FA         0x02
+#define NCR710_DCNTL_STD        0x04
+#define NCR710_DCNTL_LLM        0x08
+#define NCR710_DCNTL_SSM        0x10
+#define NCR710_DCNTL_EA         0x20
+
+/* SCSI phase (MSG,C/D,I/O); matches SSTAT2[2:0]/SBCL[2:0]/DCMD[26:24]. */
+#define PHASE_DO        0
+#define PHASE_DI        1
+#define PHASE_CMD       2
+#define PHASE_ST        3
+#define PHASE_MO        6
+#define PHASE_MI        7
+#define PHASE_MASK      7
+
+/* Flag bit OR'd into select_tag/tag when a queue tag is valid. */
+#define NCR710_TAG_VALID    (1 << 16)
+
+/* Maximum SCRIPTS instructions to process before yielding to the CPU. */
+#define NCR710_MAX_INSN     500
+
+#define NCR710_BUF_SIZE     4096
+
+/*
+ * Safety bound on the synchronous completion drain loop in ncr710_reg_write().
+ * A real transfer needs only a handful of iterations; this cap prevents an
+ * unbounded spin if a drain ever fails to progress (it then falls back to
+ * asynchronous completion).
+ */
+#define NCR710_DRAIN_MAX_ITERS  100000
+
+/* waiting state machine (mirrors lsi53c895a). */
+enum {
+    NCR710_NOWAIT = 0,        /* SCRIPTS running or stopped */
+    NCR710_WAIT_RESELECT,     /* Wait Reselect instruction issued */
+    NCR710_DMA_SCRIPTS,       /* DMA invoked from within execute_script */
+    NCR710_DMA_IN_PROGRESS,   /* asynchronous DMA in progress */
+    NCR710_WAIT_SCRIPTS,      /* stopped on the instruction count limit */
+};
+
+enum {
+    NCR710_MSG_ACTION_COMMAND = 0,
+    NCR710_MSG_ACTION_DISCONNECT = 1,
+    NCR710_MSG_ACTION_DOUT = 2,
+    NCR710_MSG_ACTION_DIN = 3,
+};
+
+struct NCR710Request {
+    SCSIRequest *req;
+    uint32_t tag;
+    uint32_t dma_len;
+    uint8_t *dma_buf;
+    uint32_t pending;     /* bytes the SCSI layer has ready while queued */
+    int out;              /* nonzero if the queued transfer is DATA OUT */
+    bool orphan;
+    QTAILQ_ENTRY(NCR710Request) next;
+};
+
+static const char *const ncr710_reg_names[64] = {
+    "SCNTL0", "SCNTL1", "SDID", "SIEN", "SCID", "SXFER", "SODL", "SOCL",
+    "SFBR", "SIDL", "SBDL", "SBCL", "DSTAT", "SSTAT0", "SSTAT1", "SSTAT2",
+    "DSA0", "DSA1", "DSA2", "DSA3", "CTEST0", "CTEST1", "CTEST2", "CTEST3",
+    "CTEST4", "CTEST5", "CTEST6", "CTEST7", "TEMP0", "TEMP1", "TEMP2", "TEMP3",
+    "DFIFO", "ISTAT", "CTEST8", "LCRC", "DBC0", "DBC1", "DBC2", "DCMD",
+    "DNAD0", "DNAD1", "DNAD2", "DNAD3", "DSP0", "DSP1", "DSP2", "DSP3",
+    "DSPS0", "DSPS1", "DSPS2", "DSPS3", "SCRATCH0", "SCRATCH1", "SCRATCH2",
+    "SCRATCH3", "DMODE", "DIEN", "DWT", "DCNTL", "ADDER0", "ADDER1", "ADDER2",
+    "ADDER3",
+};
+
+static const char *ncr710_reg_name(int offset)
+{
+    return (offset >= 0 && offset < 64) ? ncr710_reg_names[offset] : "???";
+}
+
+/* Forward declarations. */
+static uint8_t ncr710_reg_readb(NCR710State *s, int offset);
+static void ncr710_reg_writeb(NCR710State *s, int offset, uint8_t val);
+static void ncr710_execute_script(NCR710State *s);
+static void ncr710_issue_bh(void *opaque);
+static void ncr710_set_phase(NCR710State *s, int phase);
+
+/*
+ * On the 53C700 family (including the 53C710), the Select/Reselect destination
+ * ID and the SDID register are a one hot BITMASK (one bit per SCSI ID), unlike
+ * the 53C8xx which use a binary encoded target number.  Convert to a number.
+ */
+static int ncr710_id_from_bits(unsigned bits)
+{
+    int i;
+
+    bits &= 0xff;
+    for (i = 0; i < 8; i++) {
+        if (bits & (1u << i)) {
+            return i;
+        }
+    }
+    qemu_log_mask(LOG_GUEST_ERROR,
+                  "ncr710: empty destination ID bitmask; defaulting to ID 0\n");
+    return 0;
+}
+
+void ncr710_soft_reset(NCR710State *s)
+{
+    NCR710Request *p, *p_next;
+
+    trace_ncr710_v2_reset();
+
+    /*
+     * A guest ISTAT.RST soft reset can land with commands still in flight: the
+     * connected request in s->current and disconnected tagged requests on
+     * s->queue.  Cancel them so their SCSIRequests are released and no stale
+     * pointer survives the reset.  Otherwise s->current dangles into the next
+     * ncr710_do_command (tripping its assert(s->current == NULL)) and the
+     * queued requests leak.  scsi_req_cancel drives each through
+     * ncr710_request_cancelled -> ncr710_request_orphan, which clears
+     * s->current and empties s->queue, mirroring the SCNTL1.RST bus-reset
+     * teardown via bus_cold_reset.  At device init or machine reset both are
+     * already empty, so this is a no-op (the queue is QTAILQ_INIT'd before the
+     * first reset).
+     */
+    if (s->current && s->current->req) {
+        scsi_req_cancel(s->current->req);
+    }
+    QTAILQ_FOREACH_SAFE(p, &s->queue, next, p_next) {
+        if (p->req) {
+            scsi_req_cancel(p->req);
+        }
+    }
+
+    s->carry = 0;
+    s->msg_action = NCR710_MSG_ACTION_COMMAND;
+    s->msg_len = 0;
+    s->waiting = NCR710_NOWAIT;
+    s->current_lun = 0;
+    s->select_tag = 0;
+    s->command_complete = 0;
+    s->script_running = false;
+    s->sigp_pending_resume = false;
+    /*
+     * Do not reset reentrancy_level here.  A SCRIPTS driven soft reset (a guest
+     * write of ISTAT.RST from a register move) can run while execute_script
+     * frames are still on the stack; those frames decrement the counter as they
+     * unwind, so zeroing it would underflow negative and defeat the reentrancy
+     * guard.  It is zero at device init and balances itself to zero.
+     */
+
+    s->scntl0 = 0xc0;       /* full arbitration */
+    s->scntl1 = 0;
+    s->sdid = 0;
+    s->sien = 0;
+    s->scid = 0;
+    s->sxfer = 0;
+    s->sodl = 0;
+    s->socl = 0;
+    s->sfbr = 0;
+    s->sidl = 0;
+    s->sbdl = 0;
+    s->sbcl = 0;
+    s->dstat = 0;           /* DFE is OR'd in on read */
+    s->sstat0 = 0;
+    s->sstat1 = 0;
+    s->sstat2 = 0;
+    s->dsa = 0;
+    s->ctest0 = 0;
+    s->ctest3 = 0;
+    s->ctest4 = 0;
+    s->ctest5 = 0;
+    s->ctest7 = 0;
+    s->temp = 0;
+    s->dfifo = 0;
+    s->istat = 0;
+    s->lcrc = 0;
+    s->dbc = 0;
+    s->dcmd = 0;
+    s->dnad = 0;
+    s->dsp = 0;
+    s->dsps = 0;
+    s->scratch = 0;
+    s->dmode = 0;
+    s->dien = 0;
+    s->dwt = 0;
+    s->dcntl = 0;
+    s->adder = 0;
+
+    if (s->scripts_timer) {
+        timer_del(s->scripts_timer);
+    }
+}
+
+static inline void ncr710_mem_read(NCR710State *s, uint32_t addr,
+                                   void *buf, uint32_t len)
+{
+    address_space_read(s->as, addr, MEMTXATTRS_UNSPECIFIED, buf, len);
+}
+
+static inline void ncr710_mem_write(NCR710State *s, uint32_t addr,
+                                    const void *buf, uint32_t len)
+{
+    address_space_write(s->as, addr, MEMTXATTRS_UNSPECIFIED, buf, len);
+}
+
+/*
+ * SCRIPTS and the table indirect data structures are built by the big endian
+ * PA-RISC CPU and fetched as longwords, so they are interpreted big endian.
+ */
+static inline uint32_t ncr710_read_dword(NCR710State *s, uint32_t addr)
+{
+    uint32_t buf;
+
+    address_space_read(s->as, addr, MEMTXATTRS_UNSPECIFIED, &buf, 4);
+    return be32_to_cpu(buf);
+}
+
+static void ncr710_stop_script(NCR710State *s)
+{
+    s->script_running = false;
+}
+
+static void ncr710_update_irq(NCR710State *s)
+{
+    int level = 0;
+
+    if (s->dstat) {
+        if (s->dstat & s->dien) {
+            level = 1;
+        }
+        s->istat |= NCR710_ISTAT_DIP;
+    } else {
+        s->istat &= ~NCR710_ISTAT_DIP;
+    }
+
+    if (s->sstat0) {
+        if (s->sstat0 & s->sien) {
+            level = 1;
+        }
+        s->istat |= NCR710_ISTAT_SIP;
+    } else {
+        s->istat &= ~NCR710_ISTAT_SIP;
+    }
+
+    trace_ncr710_v2_update_irq(level, s->istat, s->sstat0, s->dstat);
+    qemu_set_irq(s->irq, level);
+}
+
+/* Raise a SCSI interrupt and stop SCRIPTS on a fatal/unmasked condition. */
+static void ncr710_script_scsi_interrupt(NCR710State *s, int stat0)
+{
+    uint8_t mask;
+
+    trace_ncr710_v2_script_scsi_interrupt(stat0, s->sstat0);
+    s->sstat0 |= stat0;
+    /*
+     * FCMP and SEL are nonfatal (only stop when enabled in SIEN); STO never
+     * stops here (execution continues until the next SCSI bus instruction).
+     */
+    mask = s->sien | (uint8_t)~(NCR710_STAT0_FCMP | NCR710_STAT0_SEL);
+    mask &= ~NCR710_STAT0_STO;
+    if (s->sstat0 & mask) {
+        ncr710_stop_script(s);
+    }
+    ncr710_update_irq(s);
+}
+
+/* Raise a DMA interrupt and stop SCRIPTS (all DMA interrupts are fatal). */
+static void ncr710_script_dma_interrupt(NCR710State *s, int stat)
+{
+    trace_ncr710_v2_script_dma_interrupt(stat, s->dstat);
+    s->dstat |= stat;
+    ncr710_update_irq(s);
+    ncr710_stop_script(s);
+}
+
+static void ncr710_set_phase(NCR710State *s, int phase)
+{
+    trace_ncr710_v2_set_phase(phase);
+    s->sbcl = (s->sbcl & ~PHASE_MASK) | phase | NCR710_SBCL_REQ;
+    s->sstat2 = (s->sstat2 & ~PHASE_MASK) | phase;
+}
+
+/* 53C710 has no phase mismatch jump feature; a bad phase always interrupts. */
+static int ncr710_bad_phase(NCR710State *s, int new_phase)
+{
+    ncr710_script_scsi_interrupt(s, NCR710_STAT0_MA);
+    ncr710_stop_script(s);
+    ncr710_set_phase(s, new_phase);
+    return 1;
+}
+
+static void ncr710_resume_script(NCR710State *s)
+{
+    trace_ncr710_v2_resume_script(s->waiting);
+    if (s->reentrancy_level > 0) {
+        /*
+         * A completion fired synchronously while the SCRIPTS interpreter is
+         * already on the stack, e.g. inline GOOD status for a no data
+         * command (TEST UNIT READY, START STOP UNIT, ...) completing inside
+         * scsi_req_enqueue().  Reentering ncr710_execute_script() here would
+         * advance s->dsp underneath the outer interpreter frame and can
+         * double execute SCRIPTS (the reentrancy class behind CVE-2023-0330
+         * in lsi53c895a).  Do not recurse: release the wait so the outer
+         * loop continues to the next instruction, which the completion has
+         * already armed (PHASE_ST, via ncr710_command_complete()).
+         */
+        s->waiting = NCR710_NOWAIT;
+        return;
+    }
+    if (s->waiting != NCR710_DMA_SCRIPTS) {
+        s->waiting = NCR710_NOWAIT;
+        ncr710_execute_script(s);
+    } else {
+        s->waiting = NCR710_NOWAIT;
+    }
+}
+
+static void ncr710_disconnect(NCR710State *s)
+{
+    trace_ncr710_v2_disconnect(s->waiting);
+    s->scntl1 &= ~NCR710_SCNTL1_CON;
+    s->sstat2 &= ~PHASE_MASK;
+    s->sbcl = 0;
+}
+
+static void ncr710_bad_selection(NCR710State *s, uint32_t id)
+{
+    trace_ncr710_v2_bad_selection(id);
+    ncr710_script_scsi_interrupt(s, NCR710_STAT0_STO);
+    ncr710_disconnect(s);
+}
+
+/*
+ * Deferred half of ncr710_do_dma()'s SCSI continue: runs on a clean stack so a
+ * cached backend's completion arrives asynchronously (reentrancy_level 0)
+ * instead of reentrantly inside the SCRIPTS interpreter.  Guarded against an
+ * orphaned/cancelled request like ncr710_reselect_bh.
+ */
+static void ncr710_issue_bh(void *opaque)
+{
+    NCR710State *s = opaque;
+
+    if (!s->issue_pending) {
+        return;
+    }
+    s->issue_pending = false;
+    if (s->current == NULL || s->current->orphan) {
+        return;
+    }
+    scsi_req_continue(s->current->req);
+}
+
+static void ncr710_do_dma(NCR710State *s, int out)
+{
+    uint32_t count;
+    uint32_t addr;
+    SCSIRequest *req;
+    NCR710Request *p;
+
+    if (!s->current || !s->current->dma_len) {
+        /* Wait until data is available. */
+        trace_ncr710_v2_do_dma_unavailable();
+        return;
+    }
+
+    p = s->current;
+    req = scsi_req_ref(s->current->req);
+
+    count = s->dbc;
+    if (count > p->dma_len) {
+        count = p->dma_len;
+    }
+
+    addr = s->dnad;
+    trace_ncr710_v2_do_dma(addr, count);
+    s->dnad += count;
+    s->dbc -= count;
+    if (p->dma_buf == NULL) {
+        p->dma_buf = scsi_req_get_buf(req);
+    }
+    if (out) {
+        ncr710_mem_read(s, addr, p->dma_buf, count);
+    } else {
+        ncr710_mem_write(s, addr, p->dma_buf, count);
+    }
+    if (p->orphan) {
+        scsi_req_unref(req);
+        return;
+    }
+    scsi_req_unref(req);
+
+    p->dma_len -= count;
+    if (p->dma_len == 0) {
+        p->dma_buf = NULL;
+        /*
+         * Defer the completing continue (Block Move satisfied, dbc == 0) of a
+         * tagged disconnect/reselect transfer to a bottom half: its
+         * scsi_req_continue can trigger a cached backend (CD-ROM) completion
+         * reentrantly inside execute_script, collapsing the disconnect and
+         * completion INT into the guest MMIO write driving the engine.  That
+         * lets the HP-UX c720 driver's next-command SIGP kick race ahead of its
+         * completion ISR and wedge the install.  Deferring makes completion an
+         * autonomous async event, like the disk-backed path;
+         * the engine parks at DMA_IN_PROGRESS (set by the caller) meanwhile.
+         */
+        if ((p->tag & NCR710_TAG_VALID) && s->dbc == 0) {
+            s->issue_pending = true;
+            aio_bh_schedule_oneshot(qemu_get_aio_context(), ncr710_issue_bh, s);
+        } else {
+            /*
+             * Complete inline for untagged transfers (the connected, polled
+             * early-boot and PDC path: SeaBIOS, ISL, pre-driver probe) and
+             * mid-transfer continues (dbc > 0) that fetch more data without
+             * completing the command (deferring every chunk would stall large
+             * multi-chunk reads).
+             */
+            scsi_req_continue(req);
+        }
+    } else {
+        p->dma_buf += count;
+        ncr710_resume_script(s);
+    }
+}
+
+static void ncr710_add_msg_byte(NCR710State *s, uint8_t data)
+{
+    if (s->msg_len >= NCR710_MAX_MSGIN_LEN) {
+        qemu_log_mask(LOG_GUEST_ERROR, "ncr710: MSG IN overflow\n");
+    } else {
+        s->msg[s->msg_len++] = data;
+    }
+}
+
+/*
+ * Reselection-interrupt mode on the 53C700 family is enabled by the SCSI
+ * interrupt enable for (re)selection (SIEN.SEL).  Linux's 53c700 driver sets it
+ * and expects a (re)selection interrupt when a disconnected command reselects;
+ * HP-UX leaves it clear and instead parks the SCRIPTS engine on a Wait Reselect
+ * instruction (handled in ncr710_wait_reselect()).  Unlike the 53C8xx, the 710
+ * gates reselection response on SIEN.SEL alone, with no separate SCID.RRE bit.
+ */
+static inline int ncr710_irq_on_rsl(NCR710State *s)
+{
+    return (s->sien & NCR710_STAT0_SEL) != 0;
+}
+
+/* First disconnected request whose data the SCSI layer has made ready. */
+static NCR710Request *ncr710_get_pending_req(NCR710State *s)
+{
+    NCR710Request *p;
+
+    QTAILQ_FOREACH(p, &s->queue, next) {
+        if (p->pending) {
+            return p;
+        }
+    }
+    return NULL;
+}
+
+/* Move the in-flight command to the disconnected queue (target disconnect). */
+static void ncr710_queue_command(NCR710State *s)
+{
+    NCR710Request *p = s->current;
+
+    trace_ncr710_v2_queue_command(p->tag);
+    assert(s->current != NULL);
+    assert(s->current->dma_len == 0);
+    QTAILQ_INSERT_TAIL(&s->queue, s->current, next);
+    s->current = NULL;
+    p->pending = 0;
+    p->out = (s->sstat2 & PHASE_MASK) == PHASE_DO;
+}
+
+/* Reselect a disconnected command to resume (continue) its data transfer. */
+static void ncr710_reselect(NCR710State *s, NCR710Request *p)
+{
+    int id;
+
+    assert(s->current == NULL);
+    QTAILQ_REMOVE(&s->queue, p, next);
+    s->current = p;
+
+    id = (p->tag >> 8) & 0xf;
+    s->sdid = 1 << id;                   /* one-hot target id (53C700 family) */
+    /* 53C700 compatibility: SFBR holds the reselecting id bitmask. */
+    if (!(s->dcntl & NCR710_DCNTL_COM)) {
+        s->sfbr = 1 << (id & 0x7);
+    }
+    trace_ncr710_v2_reselect(id);
+    s->scntl1 |= NCR710_SCNTL1_CON;
+    ncr710_set_phase(s, PHASE_MI);
+    s->msg_action = p->out ? NCR710_MSG_ACTION_DOUT : NCR710_MSG_ACTION_DIN;
+    s->current->dma_len = p->pending;
+    ncr710_add_msg_byte(s, 0x80);        /* IDENTIFY (reselection) */
+    if (s->current->tag & NCR710_TAG_VALID) {
+        ncr710_add_msg_byte(s, 0x20);    /* SIMPLE QUEUE TAG */
+        ncr710_add_msg_byte(s, p->tag & 0xff);
+    }
+    if (ncr710_irq_on_rsl(s)) {
+        ncr710_script_scsi_interrupt(s, NCR710_STAT0_SEL);
+    }
+}
+
+/*
+ * Record that the SCSI layer has data ready for a queued command.  Returns 0 if
+ * the device was reselected (the engine can continue), nonzero if the transfer
+ * is deferred until the engine next parks at Wait Reselect.
+ */
+static int ncr710_queue_req(NCR710State *s, SCSIRequest *req, uint32_t len)
+{
+    NCR710Request *p = req->hba_private;
+
+    p->pending = len;
+    if (s->waiting == NCR710_WAIT_RESELECT ||
+        (ncr710_irq_on_rsl(s) && !(s->scntl1 & NCR710_SCNTL1_CON) &&
+         !(s->istat & (NCR710_ISTAT_SIP | NCR710_ISTAT_DIP)))) {
+        ncr710_reselect(s, p);
+        return 0;
+    }
+    trace_ncr710_v2_queue_req(p->tag);
+    return 1;
+}
+
+/*
+ * Deferred half of ncr710_wait_reselect(): reconnect a disconnected command
+ * whose data is ready and resume the engine, on a clean (bottom half) stack.
+ * Guarded so it does nothing if a guest SIGP wake or an inline transfer_data
+ * reselection moved the engine on while this was pending.
+ */
+static void ncr710_reselect_bh(void *opaque)
+{
+    NCR710State *s = opaque;
+    NCR710Request *p;
+
+    if (s->current != NULL || s->waiting != NCR710_WAIT_RESELECT) {
+        return;
+    }
+    p = ncr710_get_pending_req(s);
+    if (p == NULL) {
+        return;
+    }
+    ncr710_reselect(s, p);
+    s->waiting = NCR710_NOWAIT;
+    ncr710_execute_script(s);
+}
+
+/* SCRIPTS Wait Reselect: reconnect a ready queued command, else park. */
+static void ncr710_wait_reselect(NCR710State *s)
+{
+    if (s->current) {
+        return;
+    }
+    /*
+     * A disconnected command whose data is ready must reconnect to resume its
+     * transfer.  Reselecting synchronously here, inside the guest register
+     * write that drove the engine to this Wait Reselect, would deliver the
+     * command's completion interrupt reentrantly in the middle of driver flow,
+     * racing the HP-UX 10.20 c720 driver's completion bookkeeping and dropping
+     * a biodone/wakeup (the install then sleeps forever; the model idles
+     * with the queue empty).  Defer the reconnect to a bottom half so it
+     * lands on a clean stack after the register write returns, as a real target
+     * initiated reselection would arrive.  The engine parks meanwhile; if more
+     * data arrives first, transfer_data's own reselection (or a SIGP wake)
+     * handles it and the bottom half does nothing.
+     */
+    s->waiting = NCR710_WAIT_RESELECT;
+    if (ncr710_get_pending_req(s)) {
+        aio_bh_schedule_oneshot(qemu_get_aio_context(), ncr710_reselect_bh, s);
+    }
+}
+
+static void ncr710_request_orphan(NCR710State *s, NCR710Request *p)
+{
+    p->orphan = true;
+    if (p == s->current) {
+        s->current = NULL;
+    } else {
+        QTAILQ_REMOVE(&s->queue, p, next);
+    }
+    scsi_req_unref(p->req);
+}
+
+void ncr710_request_cancelled(SCSIRequest *req)
+{
+    NCR710State *s = ncr710_from_scsi_bus(req->bus);
+    NCR710Request *p = req->hba_private;
+
+    ncr710_request_orphan(s, p);
+}
+
+void ncr710_command_complete(SCSIRequest *req, size_t resid)
+{
+    NCR710State *s = ncr710_from_scsi_bus(req->bus);
+    int stop = 0;
+
+    trace_ncr710_v2_command_complete(req->tag, req->status);
+    s->status = req->status;
+    s->command_complete = 2;
+    if (s->waiting && s->dbc != 0) {
+        /* Raise phase mismatch for short transfers. */
+        stop = ncr710_bad_phase(s, PHASE_ST);
+        if (stop) {
+            s->waiting = NCR710_NOWAIT;
+        }
+    } else {
+        ncr710_set_phase(s, PHASE_ST);
+    }
+
+    if (req->hba_private == s->current) {
+        ncr710_request_orphan(s, s->current);
+    }
+    if (!stop) {
+        ncr710_resume_script(s);
+    }
+}
+
+void ncr710_transfer_data(SCSIRequest *req, uint32_t len)
+{
+    NCR710State *s = ncr710_from_scsi_bus(req->bus);
+    NCR710Request *p = req->hba_private;
+    int out;
+
+    assert(!p->orphan);
+
+    /*
+     * A disconnected (queued) command, or one whose data arrives while the
+     * engine is parked at Wait Reselect, must reselect before its data can be
+     * moved.  ncr710_queue_req() reselects if it can, else defers the transfer
+     * until the engine next parks at Wait Reselect.
+     */
+    if (s->waiting == NCR710_WAIT_RESELECT || p != s->current ||
+        (ncr710_irq_on_rsl(s) && !(s->scntl1 & NCR710_SCNTL1_CON))) {
+        if (ncr710_queue_req(s, req, len)) {
+            return;
+        }
+    }
+
+    out = (s->sstat2 & PHASE_MASK) == PHASE_DO;
+
+    trace_ncr710_v2_transfer_data(req->tag, len);
+    s->current->dma_len = len;
+    s->command_complete = 1;
+    if (s->waiting) {
+        if (s->waiting == NCR710_WAIT_RESELECT || s->dbc == 0) {
+            ncr710_resume_script(s);
+        } else {
+            ncr710_do_dma(s, out);
+        }
+    }
+}
+
+static void ncr710_do_command(NCR710State *s)
+{
+    SCSIDevice *dev;
+    uint8_t buf[16] = {0};
+    uint32_t id;
+    int n;
+
+    if (s->dbc > 16) {
+        s->dbc = 16;
+    }
+    ncr710_mem_read(s, s->dnad, buf, s->dbc);
+    s->sfbr = buf[0];
+    s->command_complete = 0;
+    trace_ncr710_v2_do_command(s->dbc, buf[0],
+                            (buf[2] << 24) | (buf[3] << 16) |
+                            (buf[4] << 8) | buf[5],
+                            (buf[7] << 8) | buf[8]);
+
+    id = (s->select_tag >> 8) & 0xf;
+    dev = scsi_device_find(&s->bus, 0, id, s->current_lun);
+    if (!dev) {
+        ncr710_bad_selection(s, id);
+        return;
+    }
+
+    assert(s->current == NULL);
+    s->current = g_new0(NCR710Request, 1);
+    s->current->tag = s->select_tag;
+    s->current->req = scsi_req_new(dev, s->current->tag, s->current_lun, buf,
+                                   s->dbc, s->current);
+
+    n = scsi_req_enqueue(s->current->req);
+    if (n) {
+        if (n > 0) {
+            ncr710_set_phase(s, PHASE_DI);
+        } else if (n < 0) {
+            ncr710_set_phase(s, PHASE_DO);
+        }
+        scsi_req_continue(s->current->req);
+    }
+    if (!s->command_complete) {
+        if (n) {
+            if ((s->current->tag & NCR710_TAG_VALID) &&
+                !ncr710_irq_on_rsl(s)) {
+                /*
+                 * Tagged command whose data is not yet ready (typically a read
+                 * whose media access has not completed), issued by a driver
+                 * that recovers a disconnected command by parking the SCRIPTS
+                 * engine on a Wait Reselect instruction (SIEN.SEL clear, e.g.
+                 * HP-UX). Disconnect and queue it so the bus is free for
+                 * further tagged commands; ncr710_wait_reselect() reselects it
+                 * once its data is ready.
+                 *
+                 * Do not disconnect for:
+                 *   - untagged transfers (SeaBIOS/PDC firmware, simple
+                 *     drivers), which have no Wait Reselect handler; and
+                 *   - reselection-interrupt drivers (SIEN.SEL set, e.g. Linux's
+                 *     53c700), which expect a target-reselect interrupt with
+                 *     the 700-family reselection handshake rather than a script
+                 *     park.
+                 * Both keep the connected behaviour and complete inline.
+                 */
+                ncr710_add_msg_byte(s, 2);   /* SAVE DATA POINTER */
+                ncr710_add_msg_byte(s, 4);   /* DISCONNECT */
+                ncr710_set_phase(s, PHASE_MI);
+                s->msg_action = NCR710_MSG_ACTION_DISCONNECT;
+                ncr710_queue_command(s);
+            }
+            /*
+             * Otherwise stay connected and let the block move wait for
+             * transfer_data; no disconnect is fabricated (see policy above).
+             */
+        } else {
+            /*
+             * Async no data command (e.g. SYNCHRONIZE CACHE from Linux
+             * 53c700, completing via a block layer bottom half).  Park with
+             * dbc = 0 so the engine stops in COMMAND instead of fabricating
+             * a data phase; ncr710_command_complete() later asserts STATUS
+             * and resumes on a quiescent stack.  dbc = 0 also keeps that
+             * completion on its clean PHASE_ST path.
+             */
+            s->dbc = 0;
+            s->waiting = NCR710_DMA_IN_PROGRESS;
+        }
+    }
+}
+
+static void ncr710_do_status(NCR710State *s)
+{
+    uint8_t status;
+
+    trace_ncr710_v2_do_status(s->dbc, s->status);
+    if (s->dbc != 1) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "ncr710: STATUS move requested %u bytes (expected 1)\n",
+                      s->dbc);
+    }
+    s->dbc = 1;
+    status = s->status;
+    s->sfbr = status;
+    ncr710_mem_write(s, s->dnad, &status, 1);
+    ncr710_set_phase(s, PHASE_MI);
+    s->msg_action = NCR710_MSG_ACTION_DISCONNECT;
+    ncr710_add_msg_byte(s, 0);            /* COMMAND COMPLETE */
+}
+
+static void ncr710_do_msgin(NCR710State *s)
+{
+    uint8_t len;
+
+    trace_ncr710_v2_do_msgin(s->dbc, s->msg_len);
+    if (s->msg_len == 0) {
+        /*
+         * MOVE WHEN MSG IN with no message queued: transfer nothing (SFBR
+         * keeps its last byte).  After a declined negotiation MESSAGE REJECT
+         * (msg_action COMMAND) the Linux 53c700 driver loops on MSG IN and
+         * needs the target driven switch to COMMAND, so advance the phase
+         * here or it spins.  (HP-UX issues its CDB directly and never
+         * reenters here.)
+         */
+        if (s->msg_action == NCR710_MSG_ACTION_COMMAND) {
+            ncr710_set_phase(s, PHASE_CMD);
+        }
+        return;
+    }
+    s->sfbr = s->msg[0];
+    len = s->msg_len;
+    assert(len <= NCR710_MAX_MSGIN_LEN);
+    if (len > s->dbc) {
+        len = s->dbc;
+    }
+
+    if (len) {
+        ncr710_mem_write(s, s->dnad, s->msg, len);
+        /* Drivers rely on the last byte being in SIDL. */
+        s->sidl = s->msg[len - 1];
+        s->msg_len -= len;
+        if (s->msg_len) {
+            memmove(s->msg, s->msg + len, s->msg_len);
+        }
+    }
+
+    if (!s->msg_len) {
+        switch (s->msg_action) {
+        case NCR710_MSG_ACTION_COMMAND:
+            /*
+             * A MESSAGE REJECT declining negotiation was just consumed.
+             * The HP-UX install template reads it with ATN deasserted and
+             * requires a trailing SAVE DATA POINTERS (0x02); a conformant
+             * initiator (NetBSD, Linux) reads it with ATN still asserted and
+             * wants no second byte.  See ncr710_do_msgout for the
+             * discriminator.
+             */
+            if (s->sfbr == 7 && !(s->socl & NCR710_SOCL_ATN)) {
+                ncr710_add_msg_byte(s, 2);   /* SAVE DATA POINTERS (HP-UX) */
+            }
+            /*
+             * Stay in MSG IN: SSTAT2 latches the last REQ phase, so the driver
+             * samples MSG IN at the script interrupt and the lazy MSG IN ->
+             * COMMAND switch happens when its next block move requests COMMAND.
+             */
+            break;
+        case NCR710_MSG_ACTION_DISCONNECT:
+            ncr710_disconnect(s);
+            break;
+        case NCR710_MSG_ACTION_DOUT:
+            ncr710_set_phase(s, PHASE_DO);
+            break;
+        case NCR710_MSG_ACTION_DIN:
+            ncr710_set_phase(s, PHASE_DI);
+            break;
+        default:
+            abort();
+        }
+    }
+}
+
+static uint8_t ncr710_get_msgbyte(NCR710State *s)
+{
+    uint8_t data;
+
+    ncr710_mem_read(s, s->dnad, &data, 1);
+    s->dnad++;
+    s->dbc--;
+    return data;
+}
+
+static void ncr710_skip_msgbytes(NCR710State *s, unsigned int n)
+{
+    s->dnad += n;
+    s->dbc -= n;
+}
+
+static void ncr710_do_msgout(NCR710State *s)
+{
+    uint8_t msg;
+
+    trace_ncr710_v2_do_msgout(s->dbc);
+    while (s->dbc) {
+        msg = ncr710_get_msgbyte(s);
+        s->sfbr = msg;
+
+        switch (msg) {
+        case 0x04:
+            ncr710_disconnect(s);
+            break;
+        case 0x08:                       /* NOP */
+            ncr710_set_phase(s, PHASE_CMD);
+            break;
+        case 0x01:                       /* Extended message */
+            ncr710_get_msgbyte(s);       /* skip the length byte */
+            msg = ncr710_get_msgbyte(s);
+            switch (msg) {
+            case 1:                      /* SDTR */
+                ncr710_skip_msgbytes(s, 2);
+                goto reject;
+            case 3:                      /* WDTR */
+                ncr710_skip_msgbytes(s, 1);
+                goto reject;
+            case 4:                      /* PPR */
+                ncr710_skip_msgbytes(s, 5);
+                goto reject;
+            default:
+                goto bad;
+            }
+            break;
+        case 0x20:                       /* SIMPLE queue tag */
+            s->select_tag &= ~0xff;
+            s->select_tag |= ncr710_get_msgbyte(s) | NCR710_TAG_VALID;
+            break;
+        case 0x21:                       /* HEAD of queue tag */
+        case 0x22:                       /* ORDERED queue tag */
+            s->select_tag &= ~0xff;
+            s->select_tag |= ncr710_get_msgbyte(s) | NCR710_TAG_VALID;
+            break;
+        case 0x0d:                       /* ABORT TAG */
+        case 0x06:                       /* ABORT */
+        case 0x0e:                       /* CLEAR QUEUE */
+        case 0x0c:                       /* BUS DEVICE RESET */
+            if (s->current && s->current->req) {
+                scsi_req_cancel(s->current->req);
+            }
+            ncr710_disconnect(s);
+            break;
+        default:
+            if ((msg & 0x80) == 0) {
+                goto bad;
+            }
+            s->current_lun = msg & 7;    /* IDENTIFY */
+            ncr710_set_phase(s, PHASE_CMD);
+            break;
+        }
+    }
+    return;
+reject:
+    /*
+     * Decline SDTR/WDTR/PPR negotiation with a single MESSAGE REJECT.  NetBSD
+     * osiop and Linux 53c700 accept a lone reject and move on; the HP-UX 10.20
+     * install template instead requires a trailing SAVE DATA POINTERS (0x02) or
+     * it spins in MSG IN until the I/O times out, while NetBSD osiop rejects a
+     * 0x02 there and resets the bus.  The two are told apart by ATN as the
+     * reject is read (HP-UX has deasserted it, conformant initiators have not),
+     * so queue only the reject here and append the 0x02 tail in ncr710_do_msgin
+     * when ATN is deasserted.  Phase stays latched at MSG IN until the CDB
+     * block move requests COMMAND.
+     */
+    ncr710_set_phase(s, PHASE_MI);
+    ncr710_add_msg_byte(s, 7);           /* MESSAGE REJECT */
+    s->msg_action = NCR710_MSG_ACTION_COMMAND;
+    return;
+bad:
+    qemu_log_mask(LOG_UNIMP, "ncr710: unimplemented message 0x%02x\n", msg);
+    ncr710_set_phase(s, PHASE_MI);
+    ncr710_add_msg_byte(s, 7);           /* MESSAGE REJECT */
+    s->msg_action = NCR710_MSG_ACTION_COMMAND;
+}
+
+static void ncr710_memcpy(NCR710State *s, uint32_t dest, uint32_t src,
+                          int count)
+{
+    QEMU_UNINITIALIZED uint8_t buf[NCR710_BUF_SIZE];
+    int n;
+
+    while (count) {
+        n = (count > NCR710_BUF_SIZE) ? NCR710_BUF_SIZE : count;
+        ncr710_mem_read(s, src, buf, n);
+        ncr710_mem_write(s, dest, buf, n);
+        src += n;
+        dest += n;
+        count -= n;
+    }
+}
+
+static void ncr710_scripts_timer_start(NCR710State *s)
+{
+    /* The wrapper allocates this timer on the nanosecond clock. */
+    timer_mod(s->scripts_timer,
+              qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500000);
+}
+
+void ncr710_scripts_timer_callback(void *opaque)
+{
+    NCR710State *s = opaque;
+
+    s->waiting = NCR710_NOWAIT;
+    ncr710_execute_script(s);
+}
+
+static void ncr710_execute_script(NCR710State *s)
+{
+    uint32_t insn;
+    uint32_t addr;
+    int opcode;
+    int insn_processed = 0;
+
+    if (s->waiting == NCR710_WAIT_SCRIPTS) {
+        timer_del(s->scripts_timer);
+        s->waiting = NCR710_NOWAIT;
+    }
+
+    s->reentrancy_level++;
+    s->script_running = true;
+again:
+    /*
+     * Yield to the CPU after NCR710_MAX_INSN instructions (so guests that spin
+     * waiting on a memory change make progress), and guard against a script
+     * that retriggers itself (CVE-2023-0330) via the reentrancy counter.
+     */
+    if (++insn_processed > NCR710_MAX_INSN || s->reentrancy_level > 8) {
+        trace_ncr710_v2_script_yield(insn_processed);
+        s->waiting = NCR710_WAIT_SCRIPTS;
+        ncr710_scripts_timer_start(s);
+        s->reentrancy_level--;
+        return;
+    }
+
+    insn = ncr710_read_dword(s, s->dsp);
+    if (!insn) {
+        /* Skip an empty (NULL) opcode 4 bytes at a time. */
+        s->dsp += 4;
+        goto again;
+    }
+    addr = ncr710_read_dword(s, s->dsp + 4);
+    trace_ncr710_v2_execute_script(s->dsp, insn, addr);
+    s->dsps = addr;
+    s->dcmd = insn >> 24;
+    s->dsp += 8;
+
+    switch (insn >> 30) {
+    case 0: /* Block move */
+        if (s->sstat0 & NCR710_STAT0_STO) {
+            ncr710_stop_script(s);
+            break;
+        }
+        s->dbc = insn & 0xffffff;
+        if (insn & (1 << 29)) {
+            /* Indirect addressing. */
+            addr = ncr710_read_dword(s, addr);
+        } else if (insn & (1 << 28)) {
+            /* Table indirect addressing (32 bit). */
+            uint32_t buf[2];
+            int32_t offset = sextract32(addr, 0, 24);
+
+            ncr710_mem_read(s, s->dsa + offset, buf, 8);
+            s->dbc = be32_to_cpu(buf[0]) & 0xffffff;
+            addr = be32_to_cpu(buf[1]);
+        }
+        if ((s->sstat2 & PHASE_MASK) != ((insn >> 24) & 7)) {
+            /*
+             * The target controls the phase; SSTAT2 latches the last REQ
+             * phase.  The one legitimate lazy transition is MSG IN -> COMMAND
+             * after a declined negotiation MESSAGE REJECT (msg_action
+             * COMMAND); applying it here rather than in do_msgin keeps the
+             * phase the driver samples at the script interrupt in sync.  Any
+             * other requested phase is a real mismatch and interrupts with
+             * M/A.
+             */
+            if ((s->sstat2 & PHASE_MASK) == PHASE_MI && s->msg_len == 0 &&
+                s->msg_action == NCR710_MSG_ACTION_COMMAND &&
+                ((insn >> 24) & 7) == PHASE_CMD) {
+                ncr710_set_phase(s, PHASE_CMD);
+            } else {
+                trace_ncr710_v2_block_move_badphase(s->sstat2 & PHASE_MASK,
+                                                 (insn >> 24) & 7);
+                ncr710_script_scsi_interrupt(s, NCR710_STAT0_MA);
+                break;
+            }
+        }
+        s->dnad = addr;
+        switch (s->sstat2 & PHASE_MASK) {
+        case PHASE_DO:
+            s->waiting = NCR710_DMA_SCRIPTS;
+            ncr710_do_dma(s, 1);
+            if (s->waiting) {
+                s->waiting = NCR710_DMA_IN_PROGRESS;
+            }
+            break;
+        case PHASE_DI:
+            s->waiting = NCR710_DMA_SCRIPTS;
+            ncr710_do_dma(s, 0);
+            if (s->waiting) {
+                s->waiting = NCR710_DMA_IN_PROGRESS;
+            }
+            break;
+        case PHASE_CMD:
+            ncr710_do_command(s);
+            break;
+        case PHASE_ST:
+            ncr710_do_status(s);
+            break;
+        case PHASE_MO:
+            ncr710_do_msgout(s);
+            break;
+        case PHASE_MI:
+            ncr710_do_msgin(s);
+            break;
+        default:
+            qemu_log_mask(LOG_UNIMP, "ncr710: unimplemented phase %d\n",
+                          s->sstat2 & PHASE_MASK);
+        }
+        s->dfifo = s->dbc & 0x7f;
+        break;
+
+    case 1: /* I/O or Read/Write register */
+        opcode = (insn >> 27) & 7;
+        if (opcode < 5) {
+            uint32_t id;
+            unsigned id_bits;
+
+            if (insn & (1 << 25)) {
+                id_bits = ncr710_read_dword(s,
+                                            s->dsa + sextract32(insn, 0, 24));
+            } else {
+                id_bits = insn;
+            }
+            /* Destination ID is a one hot bitmask in bits 23:16. */
+            id_bits = (id_bits >> 16) & 0xff;
+            id = ncr710_id_from_bits(id_bits);
+            if (insn & (1 << 26)) {
+                addr = s->dsp + sextract32(addr, 0, 24);
+            }
+            s->dnad = addr;
+            switch (opcode) {
+            case 0: /* Select */
+                trace_ncr710_v2_select(id, !!(insn & (1 << 24)));
+                s->sdid = id_bits;
+                if (s->scntl1 & NCR710_SCNTL1_CON) {
+                    s->dsp = s->dnad;
+                    break;
+                }
+                s->sstat1 |= 0x04;       /* Won arbitration */
+                if (!scsi_device_find(&s->bus, 0, id, 0)) {
+                    ncr710_bad_selection(s, id);
+                    break;
+                }
+                s->select_tag = id << 8;
+                s->scntl1 |= NCR710_SCNTL1_CON;
+                s->sbcl |= NCR710_SBCL_BSY;
+                if (insn & (1 << 24)) {
+                    /*
+                     * Select with ATN/: the initiator drives a message
+                     * first, so the target's first phase is MESSAGE OUT
+                     * (IDENTIFY).
+                     */
+                    s->socl |= NCR710_SOCL_ATN;
+                    s->sbcl |= NCR710_SBCL_ATN;
+                    ncr710_set_phase(s, PHASE_MO);
+                } else {
+                    /* No ATN/: target goes straight to COMMAND phase. */
+                    ncr710_set_phase(s, PHASE_CMD);
+                }
+                s->waiting = NCR710_NOWAIT;
+                break;
+            case 1: /* Wait Disconnect */
+                trace_ncr710_v2_wait_disconnect();
+                s->scntl1 &= ~NCR710_SCNTL1_CON;
+                break;
+            case 2: /* Wait Reselect */
+                /*
+                 * Two things release a Wait Reselect: the driver's SIGP "start
+                 * next command" kick (jump to the dispatch address latched in
+                 * DNAD), or a disconnected tagged command becoming ready to
+                 * reselect.  A SIGP that arrived while the engine was halted at
+                 * a completion INT is held in sigp_pending_resume and consumed
+                 * here.  Otherwise reconnect a ready queued command, or park.
+                 */
+                if ((s->istat & NCR710_ISTAT_SIGP) || s->sigp_pending_resume) {
+                    /*
+                     * The SIGP divert is a one-shot: clear the latch as it is
+                     * consumed so a subsequent Wait Reselect does not re-fire
+                     * on a stale SIGP, and so SIGP is never left set across a
+                     * real reselection (Jun92 p.53: an active SIGP at
+                     * reselection disables target-mode auto-switching).
+                     */
+                    s->istat &= ~NCR710_ISTAT_SIGP;
+                    s->sigp_pending_resume = false;
+                    s->dsp = s->dnad;
+                } else if (!ncr710_irq_on_rsl(s)) {
+                    ncr710_wait_reselect(s);
+                }
+                break;
+            case 3: /* Set */
+                if (insn & (1 << 3)) {
+                    s->socl |= NCR710_SOCL_ATN;
+                    s->sbcl |= NCR710_SBCL_ATN;
+                    ncr710_set_phase(s, PHASE_MO);
+                }
+                if (insn & (1 << 6)) {
+                    s->sbcl |= NCR710_SBCL_ACK;
+                }
+                if (insn & (1 << 10)) {
+                    s->carry = 1;
+                }
+                break;
+            case 4: /* Clear */
+                if (insn & (1 << 3)) {
+                    s->socl &= ~NCR710_SOCL_ATN;
+                    s->sbcl &= ~NCR710_SBCL_ATN;
+                }
+                if (insn & (1 << 6)) {
+                    s->sbcl &= ~NCR710_SBCL_ACK;
+                    /*
+                     * Releasing ACK on the last byte of a declined negotiation
+                     * MESSAGE REJECT lets the target move on to COMMAND.  HP-UX
+                     * 9 tests the phase with JUMP WHEN COMMAND after CLEAR ACK
+                     * instead of issuing the CDB block move directly.
+                     */
+                    if ((s->sstat2 & PHASE_MASK) == PHASE_MI && s->msg_len == 0 &&
+                        s->msg_action == NCR710_MSG_ACTION_COMMAND) {
+                        ncr710_set_phase(s, PHASE_CMD);
+                    }
+                }
+                if (insn & (1 << 10)) {
+                    s->carry = 0;
+                }
+                break;
+            }
+        } else {
+            /*
+             * Read/Write register.  The 895a ALU operator encoding is a
+             * backward compatible superset of the 710's (move/OR/AND/ADD plus
+             * carry enable map onto operators 0/2/4/6/7), so this decode is
+             * shared with lsi53c895a.
+             */
+            uint8_t op0 = 0;
+            uint8_t op1 = 0;
+            uint8_t data8 = (insn >> 8) & 0xff;
+            int reg = ((insn >> 16) & 0x7f) | (insn & 0x80);
+            int operator = (insn >> 24) & 7;
+
+            switch (opcode) {
+            case 5: /* From SFBR */
+                op0 = s->sfbr;
+                op1 = data8;
+                break;
+            case 6: /* To SFBR */
+                if (operator) {
+                    op0 = ncr710_reg_readb(s, reg);
+                }
+                op1 = data8;
+                break;
+            case 7: /* Read modify write */
+                if (operator) {
+                    op0 = ncr710_reg_readb(s, reg);
+                }
+                if (insn & (1 << 23)) {
+                    op1 = s->sfbr;
+                } else {
+                    op1 = data8;
+                }
+                break;
+            }
+
+            switch (operator) {
+            case 0: /* move */
+                op0 = op1;
+                break;
+            case 1: /* shift left */
+                op1 = op0 >> 7;
+                op0 = (op0 << 1) | s->carry;
+                s->carry = op1;
+                break;
+            case 2: /* OR */
+                op0 |= op1;
+                break;
+            case 3: /* XOR */
+                op0 ^= op1;
+                break;
+            case 4: /* AND */
+                op0 &= op1;
+                break;
+            case 5: /* shift right */
+                op1 = op0 & 1;
+                op0 = (op0 >> 1) | (s->carry << 7);
+                s->carry = op1;
+                break;
+            case 6: /* ADD */
+                op0 += op1;
+                s->carry = op0 < op1;
+                break;
+            case 7: /* ADC */
+                op0 += op1 + s->carry;
+                if (s->carry) {
+                    s->carry = op0 <= op1;
+                } else {
+                    s->carry = op0 < op1;
+                }
+                break;
+            }
+
+            switch (opcode) {
+            case 5: /* From SFBR */
+            case 7: /* Read modify write */
+                ncr710_reg_writeb(s, reg, op0);
+                break;
+            case 6: /* To SFBR */
+                s->sfbr = op0;
+                break;
+            }
+        }
+        break;
+
+    case 2: /* Transfer Control */
+        {
+            int cond;
+            int jmp;
+
+            if ((insn & 0x002e0000) == 0) {
+                /* NOP */
+                break;
+            }
+            if (s->sstat0 & NCR710_STAT0_STO) {
+                ncr710_stop_script(s);
+                break;
+            }
+            cond = jmp = (insn & (1 << 19)) != 0;
+            if (cond == jmp && (insn & (1 << 21))) {
+                cond = s->carry != 0;
+            }
+            if (cond == jmp && (insn & (1 << 17))) {
+                cond = (s->sstat2 & PHASE_MASK) == ((insn >> 24) & 7);
+            }
+            if (cond == jmp && (insn & (1 << 18))) {
+                uint8_t mask = (~insn >> 8) & 0xff;
+                cond = (s->sfbr & mask) == (insn & mask);
+            }
+            if (cond == jmp) {
+                if (insn & (1 << 23)) {
+                    /* Relative address. */
+                    addr = s->dsp + sextract32(addr, 0, 24);
+                }
+                switch ((insn >> 27) & 7) {
+                case 0: /* Jump */
+                    trace_ncr710_v2_tc_jump(addr);
+                    s->adder = addr;
+                    s->dsp = addr;
+                    break;
+                case 1: /* Call */
+                    trace_ncr710_v2_tc_call(addr);
+                    s->temp = s->dsp;
+                    s->dsp = addr;
+                    break;
+                case 2: /* Return */
+                    trace_ncr710_v2_tc_return(s->temp);
+                    s->dsp = s->temp;
+                    break;
+                case 3: /* Interrupt */
+                    trace_ncr710_v2_interrupt_insn(s->dsps);
+                    ncr710_script_dma_interrupt(s, NCR710_DSTAT_SIR);
+                    break;
+                default:
+                    trace_ncr710_v2_illegal_insn(insn);
+                    ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID);
+                    break;
+                }
+            }
+        }
+        break;
+
+    case 3: /* Memory Move (Load/Store is illegal on the 710) */
+        if ((insn & (1 << 29)) == 0) {
+            uint32_t dest;
+
+            dest = ncr710_read_dword(s, s->dsp);
+            s->dsp += 4;
+            trace_ncr710_v2_memmove(dest, addr, insn & 0xffffff);
+            ncr710_memcpy(s, dest, addr, insn & 0xffffff);
+        } else {
+            trace_ncr710_v2_illegal_insn(insn);
+            ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID);
+        }
+        break;
+    }
+
+    if (s->script_running && s->waiting == NCR710_NOWAIT) {
+        if (s->dcntl & NCR710_DCNTL_SSM) {
+            ncr710_script_dma_interrupt(s, NCR710_DSTAT_SSI);
+        } else {
+            goto again;
+        }
+    }
+
+    s->reentrancy_level--;
+}
+
+#define CASE_GET_REG24(name, addr) \
+    case addr: ret = s->name & 0xff; break; \
+    case addr + 1: ret = (s->name >> 8) & 0xff; break; \
+    case addr + 2: ret = (s->name >> 16) & 0xff; break;
+
+#define CASE_GET_REG32(name, addr) \
+    case addr: ret = s->name & 0xff; break; \
+    case addr + 1: ret = (s->name >> 8) & 0xff; break; \
+    case addr + 2: ret = (s->name >> 16) & 0xff; break; \
+    case addr + 3: ret = (s->name >> 24) & 0xff; break;
+
+static uint8_t ncr710_reg_readb(NCR710State *s, int offset)
+{
+    uint8_t ret;
+
+    switch (offset) {
+    case NCR710_SCNTL0:
+        ret = s->scntl0;
+        break;
+    case NCR710_SCNTL1:
+        ret = s->scntl1;
+        break;
+    case NCR710_SDID:
+        ret = s->sdid;
+        break;
+    case NCR710_SIEN:
+        ret = s->sien;
+        break;
+    case NCR710_SCID:
+        ret = s->scid;
+        break;
+    case NCR710_SXFER:
+        ret = s->sxfer;
+        break;
+    case NCR710_SODL:
+        ret = s->sodl;
+        break;
+    case NCR710_SOCL:
+        ret = s->socl;
+        break;
+    case NCR710_SFBR:
+        ret = s->sfbr;
+        break;
+    case NCR710_SIDL:
+        ret = s->sidl;
+        break;
+    case NCR710_SBDL:
+        /* Some drivers peek at the data bus during MSG IN. */
+        if ((s->sstat2 & PHASE_MASK) == PHASE_MI && s->msg_len > 0) {
+            ret = s->msg[0];
+        } else {
+            ret = 0;
+        }
+        break;
+    case NCR710_SBCL:
+        ret = s->sbcl;
+        break;
+    case NCR710_DSTAT:
+        ret = s->dstat | NCR710_DSTAT_DFE;
+        s->dstat = 0;
+        ncr710_update_irq(s);
+        break;
+    case NCR710_SSTAT0:
+        ret = s->sstat0;
+        s->sstat0 = 0;
+        ncr710_update_irq(s);
+        break;
+    case NCR710_SSTAT1:
+        ret = s->sstat1;
+        break;
+    case NCR710_SSTAT2:
+        ret = s->sstat2;
+        break;
+    CASE_GET_REG32(dsa, NCR710_DSA)
+    case NCR710_CTEST0:
+        ret = s->ctest0;
+        break;
+    case NCR710_CTEST1:
+        ret = 0xf0;              /* DMA FIFO empty */
+        break;
+    case NCR710_CTEST2:
+        ret = NCR710_CTEST2_DACK;
+        if (s->istat & NCR710_ISTAT_SIGP) {
+            s->istat &= ~NCR710_ISTAT_SIGP;
+            ret |= NCR710_CTEST2_SIGP;
+        }
+        break;
+    case NCR710_CTEST3:
+        ret = s->ctest3;
+        break;
+    case NCR710_CTEST4:
+        ret = s->ctest4;
+        break;
+    case NCR710_CTEST5:
+        ret = s->ctest5;
+        break;
+    case NCR710_CTEST6:
+        ret = 0;
+        break;
+    case NCR710_CTEST7:
+        ret = s->ctest7;
+        break;
+    CASE_GET_REG32(temp, NCR710_TEMP)
+    case NCR710_DFIFO:
+        ret = s->dfifo;
+        break;
+    case NCR710_ISTAT:
+        ret = s->istat & ~NCR710_ISTAT_CON;
+        if (s->scntl1 & NCR710_SCNTL1_CON) {
+            ret |= NCR710_ISTAT_CON;
+        }
+        break;
+    case NCR710_CTEST8:
+        ret = NCR710_CHIP_REVISION << 4;
+        break;
+    case NCR710_LCRC:
+        ret = s->lcrc;
+        break;
+    CASE_GET_REG24(dbc, NCR710_DBC)
+    case NCR710_DCMD:
+        ret = s->dcmd;
+        break;
+    CASE_GET_REG32(dnad, NCR710_DNAD)
+    CASE_GET_REG32(dsp, NCR710_DSP)
+    CASE_GET_REG32(dsps, NCR710_DSPS)
+    CASE_GET_REG32(scratch, NCR710_SCRATCH)
+    case NCR710_DMODE:
+        ret = s->dmode;
+        break;
+    case NCR710_DIEN:
+        ret = s->dien;
+        break;
+    case NCR710_DWT:
+        ret = s->dwt;
+        break;
+    case NCR710_DCNTL:
+        ret = s->dcntl;
+        break;
+    CASE_GET_REG32(adder, NCR710_ADDER)
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "ncr710: invalid read from reg %s 0x%x\n",
+                      ncr710_reg_name(offset), offset);
+        ret = 0xff;
+        break;
+    }
+
+    trace_ncr710_v2_reg_read(ncr710_reg_name(offset), offset, ret);
+    return ret;
+}
+
+#define CASE_SET_REG24(name, addr) \
+    case addr: s->name &= 0xffffff00; s->name |= val; break; \
+    case addr + 1: s->name &= 0xffff00ff; s->name |= val << 8; break; \
+    case addr + 2: s->name &= 0xff00ffff; s->name |= val << 16; break;
+
+#define CASE_SET_REG32(name, addr) \
+    case addr: s->name &= 0xffffff00; s->name |= val; break; \
+    case addr + 1: s->name &= 0xffff00ff; s->name |= val << 8; break; \
+    case addr + 2: s->name &= 0xff00ffff; s->name |= val << 16; break; \
+    case addr + 3: s->name &= 0x00ffffff; s->name |= val << 24; break;
+
+static void ncr710_reg_writeb(NCR710State *s, int offset, uint8_t val)
+{
+    trace_ncr710_v2_reg_write(ncr710_reg_name(offset), offset, val);
+
+    switch (offset) {
+    case NCR710_SCNTL0:
+        s->scntl0 = val;
+        if (val & NCR710_SCNTL0_START) {
+            qemu_log_mask(LOG_UNIMP, "ncr710: START not implemented\n");
+        }
+        break;
+    case NCR710_SCNTL1:
+        s->scntl1 = val;
+        if (val & NCR710_SCNTL1_RST) {
+            if (!(s->sstat0 & NCR710_STAT0_RST)) {
+                bus_cold_reset(BUS(&s->bus));
+                s->sstat0 |= NCR710_STAT0_RST;
+                ncr710_script_scsi_interrupt(s, NCR710_STAT0_RST);
+            }
+        } else {
+            s->sstat0 &= ~NCR710_STAT0_RST;
+        }
+        break;
+    case NCR710_SDID:
+        s->sdid = val;
+        break;
+    case NCR710_SIEN:
+        s->sien = val;
+        ncr710_update_irq(s);
+        break;
+    case NCR710_SCID:
+        s->scid = val;
+        break;
+    case NCR710_SXFER:
+        s->sxfer = val;
+        break;
+    case NCR710_SODL:
+        s->sodl = val;
+        break;
+    case NCR710_SOCL:
+        s->socl = val;
+        break;
+    case NCR710_SFBR:
+        /* CPU may not write SFBR, but SCRIPTS register moves do. */
+        s->sfbr = val;
+        break;
+    case NCR710_SIDL:
+    case NCR710_SBDL:
+        /* Read only. */
+        break;
+    case NCR710_SBCL:
+        s->sbcl = val;
+        break;
+    case NCR710_DSTAT:
+    case NCR710_SSTAT0:
+    case NCR710_SSTAT1:
+    case NCR710_SSTAT2:
+        /* Read only status registers. */
+        break;
+    CASE_SET_REG32(dsa, NCR710_DSA)
+    case NCR710_CTEST0:
+        s->ctest0 = val;
+        break;
+    case NCR710_CTEST1:
+    case NCR710_CTEST2:
+        /* Read only. */
+        break;
+    case NCR710_CTEST3:
+        s->ctest3 = val;
+        break;
+    case NCR710_CTEST4:
+        s->ctest4 = val;
+        break;
+    case NCR710_CTEST5:
+        s->ctest5 = val;
+        break;
+    case NCR710_CTEST6:
+        break;
+    case NCR710_CTEST7:
+        s->ctest7 = val;
+        break;
+    CASE_SET_REG32(temp, NCR710_TEMP)
+    case NCR710_DFIFO:
+        s->dfifo = val;
+        break;
+    case NCR710_ISTAT:
+        s->istat = (s->istat & ~(NCR710_ISTAT_ABRT | NCR710_ISTAT_RST |
+                                 NCR710_ISTAT_SIGP)) |
+                   (val & (NCR710_ISTAT_ABRT | NCR710_ISTAT_RST |
+                           NCR710_ISTAT_SIGP));
+        if (val & NCR710_ISTAT_RST) {
+            ncr710_soft_reset(s);
+            return;
+        }
+        if (val & NCR710_ISTAT_ABRT) {
+            ncr710_script_dma_interrupt(s, NCR710_DSTAT_ABRT);
+        }
+        if (s->waiting == NCR710_WAIT_RESELECT && (val & NCR710_ISTAT_SIGP)) {
+            /*
+             * SIGP "start next command" wake of a parked Wait Reselect.  Run
+             * the engine synchronously: the Wait Reselect opcode, on SIGP,
+             * jumps to the DNAD dispatch and SELECTs the next command (Jun92
+             * p.69 step 3).  This SELECT must take priority over any
+             * disconnected command pending reselection: on real silicon the
+             * SIGP jump wins and the pending target reselects later, when the
+             * engine is next parked.  Deferring this to a bottom half let
+             * ncr710_reselect_bh win the race and reconnect a pending command
+             * instead of selecting the new one, stranding the driver's
+             * start-next kick (it polls, sees no progress, and resets the bus).
+             * qemu_set_irq only raises the IRQ line here; the guest takes it at
+             * the next instruction boundary, after this store returns, so this
+             * is not a reentrant interrupt delivery.
+             */
+            trace_ncr710_v2_awoken();
+            s->waiting = NCR710_NOWAIT;
+            s->dsp = s->dnad;
+            ncr710_execute_script(s);
+        } else if ((val & NCR710_ISTAT_SIGP) && !s->script_running &&
+                   s->waiting == NCR710_NOWAIT) {
+            /*
+             * The driver's "start next command" SIGP kick raced ahead of the
+             * prior command's completion ISR: the engine is halted (script
+             * stopped, not parked at Wait Reselect) on a just completed
+             * command, either at its completion INT or in the brief window
+             * after the ISR has read cleared DSTAT but before it restarts the
+             * engine.  The wake above is skipped, so the kick only latches
+             * SIGP.  On real silicon the completion ISR runs before this lower
+             * priority kick, so the kick always lands on a parked Wait
+             * Reselect; under TCG it can land in the completion window.  The
+             * driver then polls the engine, sees no progress (SIGP still
+             * latched, DCMD/bus unchanged) and resets the SCSI bus, destroying
+             * the in flight command and hanging the install.
+             *
+             * Present "kick consumed" to that poll without disturbing any
+             * pending completion: clear the SIGP latch, exactly as a real Wait
+             * Reselect consumption would.  DSTAT/DSPS/DIP and the asserted IRQ
+             * are untouched, so the real completion ISR still services the
+             * command. Remember the kick so the next command is selected once
+             * the engine parks again at Wait Reselect (execute_script, case 2).
+             */
+            s->istat &= ~NCR710_ISTAT_SIGP;
+            s->sigp_pending_resume = true;
+        }
+        break;
+    case NCR710_CTEST8:
+        /*
+         * Revision is read only; FLF/CLF FIFO ops do nothing (the FIFO is
+         * modelled as always empty).
+         */
+        break;
+    case NCR710_LCRC:
+        /* Writing clears the longitudinal parity accumulator. */
+        s->lcrc = 0;
+        break;
+    CASE_SET_REG24(dbc, NCR710_DBC)
+    case NCR710_DCMD:
+        s->dcmd = val;
+        break;
+    CASE_SET_REG32(dnad, NCR710_DNAD)
+    case NCR710_DSP:
+        s->dsp = (s->dsp & 0xffffff00) | val;
+        break;
+    case NCR710_DSP + 1:
+        s->dsp = (s->dsp & 0xffff00ff) | (val << 8);
+        break;
+    case NCR710_DSP + 2:
+        s->dsp = (s->dsp & 0xff00ffff) | (val << 16);
+        break;
+    case NCR710_DSP + 3:
+        s->dsp = (s->dsp & 0x00ffffff) | (val << 24);
+        /* Writing the high byte starts SCRIPTS unless in manual start mode. */
+        if (!(s->dmode & NCR710_DMODE_MAN) && !s->script_running) {
+            ncr710_execute_script(s);
+        }
+        break;
+    CASE_SET_REG32(dsps, NCR710_DSPS)
+    CASE_SET_REG32(scratch, NCR710_SCRATCH)
+    case NCR710_DMODE:
+        s->dmode = val;
+        break;
+    case NCR710_DIEN:
+        s->dien = val;
+        ncr710_update_irq(s);
+        break;
+    case NCR710_DWT:
+        s->dwt = val;
+        break;
+    case NCR710_DCNTL:
+        s->dcntl = val & ~NCR710_DCNTL_STD;
+        if ((val & NCR710_DCNTL_STD) && !s->script_running) {
+            ncr710_execute_script(s);
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "ncr710: invalid write to reg %s 0x%x (0x%02x)\n",
+                      ncr710_reg_name(offset), offset, val);
+        break;
+    }
+}
+
+#undef CASE_GET_REG24
+#undef CASE_GET_REG32
+#undef CASE_SET_REG24
+#undef CASE_SET_REG32
+
+uint64_t ncr710_reg_read(NCR710State *s, hwaddr addr, unsigned size)
+{
+    /* The LASI wrapper decomposes multibyte accesses into single bytes. */
+    return ncr710_reg_readb(s, addr & 0xff);
+}
+
+void ncr710_reg_write(NCR710State *s, hwaddr addr, uint64_t val, unsigned size)
+{
+    unsigned drained = 0;
+
+    ncr710_reg_writeb(s, addr & 0xff, val & 0xff);
+
+    /*
+     * Drive any asynchronous SCSI transfer the register write kicked off to
+     * completion before returning to the guest, so its completion interrupt is
+     * raised first.  The HP-UX 10.20 c720 driver issues its "start next
+     * command" SIGP kick with CPU interrupts masked and then spins; a still
+     * pending asynchronous completion (its bottom half has not run) would land
+     * in that masked window unserviced, and the driver's watchdog would reset
+     * the bus.  Real hardware takes the higher priority completion interrupt
+     * before that poll.
+     *
+     * A deferred continue (ncr710_issue_bh, for a tagged reselect transfer) has
+     * not issued its backing I/O yet, so pump it inline first; otherwise
+     * blk_drain the backend.  The loop is bounded by NCR710_DRAIN_MAX_ITERS and
+     * falls back to asynchronous completion (trace_ncr710_v2_drain_exhausted) if it
+     * cannot make progress.
+     */
+    while (s->waiting == NCR710_DMA_IN_PROGRESS && s->current != NULL &&
+           s->current->req != NULL) {
+        if (drained++ >= NCR710_DRAIN_MAX_ITERS) {
+            trace_ncr710_v2_drain_exhausted(drained);
+            break;
+        }
+        if (s->issue_pending) {
+            ncr710_issue_bh(s);
+        } else if (s->current->req->dev->conf.blk != NULL) {
+            blk_drain(s->current->req->dev->conf.blk);
+        } else {
+            break;      /* nothing deferred and no backend to drain */
+        }
+    }
+}
+
+static int ncr710_pre_save(void *opaque)
+{
+    NCR710State *s = opaque;
+
+    if (s->current) {
+        assert(s->current->dma_buf == NULL);
+        assert(s->current->dma_len == 0);
+    }
+    return 0;
+}
+
+static int ncr710_post_load(void *opaque, int version_id)
+{
+    NCR710State *s = opaque;
+
+    if (s->msg_len < 0 || s->msg_len > NCR710_MAX_MSGIN_LEN) {
+        return -EINVAL;
+    }
+    if (s->msg_action < NCR710_MSG_ACTION_COMMAND ||
+        s->msg_action > NCR710_MSG_ACTION_DIN) {
+        return -EINVAL;
+    }
+    if (s->waiting < NCR710_NOWAIT || s->waiting > NCR710_WAIT_SCRIPTS) {
+        return -EINVAL;
+    }
+    if (s->current_lun < 0 || s->current_lun > 7) {
+        return -EINVAL;
+    }
+    if (s->waiting == NCR710_WAIT_SCRIPTS) {
+        ncr710_scripts_timer_start(s);
+    }
+    return 0;
+}
+
+const VMStateDescription vmstate_ncr710 = {
+    .name = "ncr710",
+    /*
+     * Version 2: the field layout was incompatibly reworked from the model this
+     * rewrite replaces (which also declared version 1).  minimum_version_id is
+     * raised in lockstep so a legacy stream is rejected rather than parsed
+     * positionally into the new layout.
+     */
+    .version_id = 3,
+    .minimum_version_id = 2,
+    .pre_save = ncr710_pre_save,
+    .post_load = ncr710_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_INT32(carry, NCR710State),
+        VMSTATE_INT32(status, NCR710State),
+        VMSTATE_INT32(msg_action, NCR710State),
+        VMSTATE_INT32(msg_len, NCR710State),
+        VMSTATE_BUFFER(msg, NCR710State),
+        VMSTATE_INT32(waiting, NCR710State),
+        VMSTATE_INT32(current_lun, NCR710State),
+        VMSTATE_UINT32(select_tag, NCR710State),
+        VMSTATE_INT32(command_complete, NCR710State),
+        VMSTATE_BOOL(script_running, NCR710State),
+        /*
+         * A deferred "start next command" SIGP kick (version 3+).  Unlike
+         * reentrancy_level it can be live across a migration point (set while
+         * the engine is halted at a completion INT awaiting its ISR), so it is
+         * migrated; absent from a v2 stream it defaults to false.
+         */
+        VMSTATE_BOOL_V(sigp_pending_resume, NCR710State, 3),
+
+        VMSTATE_UINT8(scntl0, NCR710State),
+        VMSTATE_UINT8(scntl1, NCR710State),
+        VMSTATE_UINT8(sdid, NCR710State),
+        VMSTATE_UINT8(sien, NCR710State),
+        VMSTATE_UINT8(scid, NCR710State),
+        VMSTATE_UINT8(sxfer, NCR710State),
+        VMSTATE_UINT8(sodl, NCR710State),
+        VMSTATE_UINT8(socl, NCR710State),
+        VMSTATE_UINT8(sfbr, NCR710State),
+        VMSTATE_UINT8(sidl, NCR710State),
+        VMSTATE_UINT8(sbdl, NCR710State),
+        VMSTATE_UINT8(sbcl, NCR710State),
+        VMSTATE_UINT8(dstat, NCR710State),
+        VMSTATE_UINT8(sstat0, NCR710State),
+        VMSTATE_UINT8(sstat1, NCR710State),
+        VMSTATE_UINT8(sstat2, NCR710State),
+        VMSTATE_UINT32(dsa, NCR710State),
+        VMSTATE_UINT8(ctest0, NCR710State),
+        VMSTATE_UINT8(ctest3, NCR710State),
+        VMSTATE_UINT8(ctest4, NCR710State),
+        VMSTATE_UINT8(ctest5, NCR710State),
+        VMSTATE_UINT8(ctest7, NCR710State),
+        VMSTATE_UINT32(temp, NCR710State),
+        VMSTATE_UINT8(dfifo, NCR710State),
+        VMSTATE_UINT8(istat, NCR710State),
+        VMSTATE_UINT8(lcrc, NCR710State),
+        VMSTATE_UINT32(dbc, NCR710State),
+        VMSTATE_UINT8(dcmd, NCR710State),
+        VMSTATE_UINT32(dnad, NCR710State),
+        VMSTATE_UINT32(dsp, NCR710State),
+        VMSTATE_UINT32(dsps, NCR710State),
+        VMSTATE_UINT32(scratch, NCR710State),
+        VMSTATE_UINT8(dmode, NCR710State),
+        VMSTATE_UINT8(dien, NCR710State),
+        VMSTATE_UINT8(dwt, NCR710State),
+        VMSTATE_UINT8(dcntl, NCR710State),
+        VMSTATE_UINT32(adder, NCR710State),
+        VMSTATE_END_OF_LIST()
+    }
+};
diff --git a/hw/scsi/ncr53c710_v2.h b/hw/scsi/ncr53c710_v2.h
new file mode 100644
index 0000000000..4e15c9d0c1
--- /dev/null
+++ b/hw/scsi/ncr53c710_v2.h
@@ -0,0 +1,183 @@
+/*
+ * QEMU NCR 53C710 SCSI I/O Processor emulation
+ *
+ * Copyright (c) 2026 Keith Monahan <keith@techtravels.org>
+ *
+ * Interface to the 53C710 model (ncr53c710.c), a rewrite whose SCRIPTS engine
+ * is derived from QEMU's LSI53C895A model and adapted to the 53C710 per its
+ * Data Manual (Jun 1992).  The only instantiated device is the LASI wrapper in
+ * lasi_ncr710.c, which embeds one NCR710State by value and drives it through
+ * the functions declared here.  This file is a function library, not a QOM
+ * type.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_NCR53C710_H
+#define HW_NCR53C710_H
+
+#include "qemu/queue.h"
+#include "hw/scsi/scsi.h"
+#include "system/memory.h"
+#include "qemu/timer.h"
+#include "migration/vmstate.h"
+
+/* Register offsets (little endian addressing; the chip is internally LE). */
+#define NCR710_SCNTL0   0x00
+#define NCR710_SCNTL1   0x01
+#define NCR710_SDID     0x02
+#define NCR710_SIEN     0x03
+#define NCR710_SCID     0x04
+#define NCR710_SXFER    0x05
+#define NCR710_SODL     0x06
+#define NCR710_SOCL     0x07
+#define NCR710_SFBR     0x08
+#define NCR710_SIDL     0x09
+#define NCR710_SBDL     0x0a
+#define NCR710_SBCL     0x0b
+#define NCR710_DSTAT    0x0c
+#define NCR710_SSTAT0   0x0d
+#define NCR710_SSTAT1   0x0e
+#define NCR710_SSTAT2   0x0f
+#define NCR710_DSA      0x10
+#define NCR710_CTEST0   0x14
+#define NCR710_CTEST1   0x15
+#define NCR710_CTEST2   0x16
+#define NCR710_CTEST3   0x17
+#define NCR710_CTEST4   0x18
+#define NCR710_CTEST5   0x19
+#define NCR710_CTEST6   0x1a
+#define NCR710_CTEST7   0x1b
+#define NCR710_TEMP     0x1c
+#define NCR710_DFIFO    0x20
+#define NCR710_ISTAT    0x21
+#define NCR710_CTEST8   0x22
+#define NCR710_LCRC     0x23
+#define NCR710_DBC      0x24
+#define NCR710_DCMD     0x27
+#define NCR710_DNAD     0x28
+#define NCR710_DSP      0x2c
+#define NCR710_DSPS     0x30
+#define NCR710_SCRATCH  0x34
+#define NCR710_DMODE    0x38
+#define NCR710_DIEN     0x39
+#define NCR710_DWT      0x3a
+#define NCR710_DCNTL    0x3b
+#define NCR710_ADDER    0x3c
+
+#define NCR710_MAX_MSGIN_LEN 8
+
+/* The chip revision reported in CTEST8[7:4]. */
+#define NCR710_CHIP_REVISION 0x2
+
+typedef struct NCR710Request NCR710Request;
+
+typedef struct NCR710State {
+    /* Wiring filled in by the LASI wrapper before ncr710_soft_reset(). */
+    SCSIBus bus;
+    AddressSpace *as;
+    qemu_irq irq;
+    /*
+     * SCRIPTS yield/resume timer: instruction budget backoff and post load
+     * restart, mirroring lsi53c895a's scripts_timer.  The wrapper creates it
+     * and points it at ncr710_scripts_timer_callback().
+     */
+    QEMUTimer *scripts_timer;
+
+    /* SCRIPTS engine working state (not directly register mapped). */
+    int carry;
+    int status;
+    int msg_action;
+    int msg_len;
+    uint8_t msg[NCR710_MAX_MSGIN_LEN];
+    int waiting;
+    int current_lun;
+    uint32_t select_tag;
+    int command_complete;
+    bool script_running;
+    /*
+     * The driver's "start next command" SIGP kick landed while the engine was
+     * halted at a completion INT (waiting==NOWAIT) instead of parked at Wait
+     * Reselect, so the normal SIGP wake is skipped.  The ISTAT write clears the
+     * SIGP latch (presenting the kick as "consumed" to the driver's poll) and
+     * sets this flag instead; the Wait Reselect handler (execute_script case 2)
+     * consumes it when the engine next parks, selecting the next command.
+     */
+    bool sigp_pending_resume;
+    /*
+     * A cached-backend SCSI continue was deferred to ncr710_issue_bh so its
+     * completion arrives asynchronously, not reentrantly inside the engine.
+     */
+    bool issue_pending;
+    /*
+     * SCRIPTS interpreter on-stack recursion depth; transient (always 0
+     * outside execute_script), so it is not migrated in vmstate.
+     */
+    int reentrancy_level;
+    NCR710Request *current;
+    /*
+     * Disconnected tagged commands awaiting target-initiated reselection
+     * (lsi53c895a-style).  Untagged 53C700-family transfers never disconnect,
+     * so for them this stays empty and the engine runs connected as before.
+     */
+    QTAILQ_HEAD(, NCR710Request) queue;
+
+    /*
+     * Registers (53C710 layout).  Several (e.g. sodl/sidl/sbdl/lcrc/ctest*) are
+     * readback/scratch only, with no modelled side effects.
+     */
+    uint8_t scntl0;
+    uint8_t scntl1;
+    uint8_t sdid;
+    uint8_t sien;
+    uint8_t scid;
+    uint8_t sxfer;
+    uint8_t sodl;
+    uint8_t socl;
+    uint8_t sfbr;
+    uint8_t sidl;
+    uint8_t sbdl;
+    uint8_t sbcl;
+    uint8_t dstat;
+    uint8_t sstat0;
+    uint8_t sstat1;
+    uint8_t sstat2;
+    uint32_t dsa;
+    uint8_t ctest0;
+    uint8_t ctest3;
+    uint8_t ctest4;
+    uint8_t ctest5;
+    uint8_t ctest7;
+    uint32_t temp;
+    uint8_t dfifo;
+    uint8_t istat;
+    uint8_t lcrc;
+    uint32_t dbc;
+    uint8_t dcmd;
+    uint32_t dnad;
+    uint32_t dsp;
+    uint32_t dsps;
+    uint32_t scratch;
+    uint8_t dmode;
+    uint8_t dien;
+    uint8_t dwt;
+    uint8_t dcntl;
+    uint32_t adder;
+} NCR710State;
+
+static inline NCR710State *ncr710_from_scsi_bus(SCSIBus *bus)
+{
+    return container_of(bus, NCR710State, bus);
+}
+
+/* Interface used by the LASI wrapper (lasi_ncr710.c). */
+uint64_t ncr710_reg_read(NCR710State *s, hwaddr addr, unsigned size);
+void ncr710_reg_write(NCR710State *s, hwaddr addr, uint64_t val, unsigned size);
+void ncr710_soft_reset(NCR710State *s);
+void ncr710_request_cancelled(SCSIRequest *req);
+void ncr710_command_complete(SCSIRequest *req, size_t resid);
+void ncr710_transfer_data(SCSIRequest *req, uint32_t len);
+void ncr710_scripts_timer_callback(void *opaque);
+extern const VMStateDescription vmstate_ncr710;
+
+#endif /* HW_NCR53C710_H */
diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events
index a8ac1e7f1d..0c73d303a9 100644
--- a/hw/scsi/trace-events
+++ b/hw/scsi/trace-events
@@ -318,6 +318,42 @@ ncr710_bad_selection(uint32_t target) "target=%d"
 ncr710_parity_sense_changed(const char *parity) "Parity sense changed to %s"
 ncr710_device_realize(void) "Device realized"
 
+# ncr53c710_v2.c
+ncr710_v2_reset(void) "Reset"
+ncr710_v2_reg_read(const char *name, int offset, uint8_t ret) "Read %s [0x%02x] = 0x%02x"
+ncr710_v2_reg_write(const char *name, int offset, uint8_t val) "Write %s [0x%02x] = 0x%02x"
+ncr710_v2_script_scsi_interrupt(uint8_t stat0, uint8_t sstat0) "SCSI interrupt stat=0x%02x sstat=0x%02x"
+ncr710_v2_script_dma_interrupt(uint8_t stat, uint8_t dstat) "DMA interrupt stat=0x%02x dstat=0x%02x"
+ncr710_v2_command_complete(uint32_t tag, uint8_t status) "tag=0x%x status=0x%02x"
+ncr710_v2_disconnect(uint8_t waiting) "waiting=%d"
+ncr710_v2_reselect(int id) "Reselect id=%d"
+ncr710_v2_queue_command(uint32_t tag) "queue command tag=0x%x"
+ncr710_v2_queue_req(uint32_t tag) "defer queued req tag=0x%x"
+ncr710_v2_bad_selection(uint32_t target) "target=%d"
+ncr710_v2_execute_script(uint32_t dsp, uint32_t insn, uint32_t addr) "dsp=0x%08x insn=0x%08x addr=0x%08x"
+ncr710_v2_do_dma(uint32_t addr, uint32_t len) "addr=0x%08x len=%d"
+ncr710_v2_do_command(uint32_t dbc, uint8_t op, uint32_t lba, uint32_t xfer) "len=%d op=0x%02x lba=%u xfer=%u"
+ncr710_v2_do_status(uint32_t dbc, uint8_t status) "len=%d status=0x%02x"
+ncr710_v2_do_msgin(uint32_t dbc, int msg_len) "len=%d msg_len=%d"
+ncr710_v2_do_msgout(uint32_t dbc) "len=%d"
+ncr710_v2_transfer_data(uint32_t tag, uint32_t len) "tag=0x%x len=%d"
+ncr710_v2_awoken(void) "SIGP woke Wait Reselect"
+ncr710_v2_set_phase(int phase) "phase=%d"
+ncr710_v2_update_irq(int level, uint8_t istat, uint8_t sstat0, uint8_t dstat) "level=%d istat=0x%02x sstat0=0x%02x dstat=0x%02x"
+ncr710_v2_select(int id, int atn) "Select id=%d atn=%d"
+ncr710_v2_block_move_badphase(int have, int want) "phase mismatch have=%d want=%d"
+ncr710_v2_resume_script(int waiting) "resume waiting=%d"
+ncr710_v2_script_yield(int insns) "yield after %d insns"
+ncr710_v2_tc_jump(uint32_t addr) "JUMP 0x%08x"
+ncr710_v2_tc_call(uint32_t addr) "CALL 0x%08x"
+ncr710_v2_tc_return(uint32_t addr) "RETURN 0x%08x"
+ncr710_v2_interrupt_insn(uint32_t dsps) "INT vector=0x%08x"
+ncr710_v2_memmove(uint32_t dest, uint32_t src, int count) "MOVE MEMORY dest=0x%08x src=0x%08x count=%d"
+ncr710_v2_do_dma_unavailable(void) "DMA: data not yet available"
+ncr710_v2_wait_disconnect(void) "Wait Disconnect"
+ncr710_v2_illegal_insn(uint32_t insn) "ILLEGAL instruction 0x%08x"
+ncr710_v2_drain_exhausted(unsigned iters) "synchronous completion drain gave up after %u iters"
+
 # lasi_ncr710.c
 lasi_ncr710_device_realize(void) "Device realized"
 lasi_ncr710_device_reset(void) "Device reset"
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 3/4] lasi_ncr710: Wire up the new ncr53c710_v2 driver in LASI
  2026-11-06 22:18 [PATCH 0/4] [RFC] scsi: New ncr53c710_v2 driver Helge Deller
  2026-11-06 22:18 ` [PATCH 1/4] target/hppa: drop dependency on ncr53c710.h Helge Deller
  2026-11-06 22:18 ` [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver Helge Deller
@ 2026-11-06 22:18 ` Helge Deller
  2026-11-06 22:18 ` [PATCH 4/4] hw/scsi: Drop old ncr53c710 v1 driver Helge Deller
  3 siblings, 0 replies; 10+ messages in thread
From: Helge Deller @ 2026-11-06 22:18 UTC (permalink / raw)
  To: Peter Maydell, Keith Monahan, BALATON Zoltan, Mark Cave-Ayland,
	qemu-devel, Paolo Bonzini
  Cc: Helge Deller

From: Helge Deller <deller@gmx.de>

Signed-off-by: Keith Monahan <keith@techtravels.org>
Signed-off-by: Helge Deller <deller@gmx.de>
---
 hw/scsi/lasi_ncr710.c | 119 +++++++++++++++++-------------------------
 hw/scsi/lasi_ncr710.h |  30 ++---------
 hw/scsi/meson.build   |   2 +-
 3 files changed, 52 insertions(+), 99 deletions(-)

diff --git a/hw/scsi/lasi_ncr710.c b/hw/scsi/lasi_ncr710.c
index 4fde2265b5..42b0dcb626 100644
--- a/hw/scsi/lasi_ncr710.c
+++ b/hw/scsi/lasi_ncr710.c
@@ -1,19 +1,18 @@
 /*
- * LASI Wrapper for NCR710 SCSI Controller
+ * LASI wrapper for the NCR 53C710 SCSI controller
  *
  * Copyright (c) 2025 Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com>
- * This driver was developed during the Google Summer of Code 2025 program.
- * Mentored by Helge Deller <deller@gmx.de>
+ * Developed during Google Summer of Code 2025, mentored by
+ * Helge Deller <deller@gmx.de>.
  *
- * NCR710 SCSI Controller implementation
- * Based on the NCR53C710 Technical Manual Version 3.2, December 2000
+ * LASI module glue around the 53C710 core in ncr53c710.c: it embeds one
+ * NCR710State by value and maps it onto the HP 715 (-M 715) LASI bus.
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
 
 #include "qemu/osdep.h"
 #include "hw/scsi/lasi_ncr710.h"
-#include "hw/scsi/ncr53c710.h"
 #include "hw/core/sysbus.h"
 #include "qemu/timer.h"
 #include "qemu/log.h"
@@ -24,7 +23,6 @@
 #include "system/dma.h"
 
 #define LASI_710_SVERSION    0x00082
-#define SCNR                 0xBEEFBABE
 #define LASI_710_HVERSION    0x3D
 #define HPHW_FIO             5        /* Fixed I/O module */
 
@@ -34,8 +32,6 @@ static uint64_t lasi_ncr710_reg_read(void *opaque, hwaddr addr,
     LasiNCR710State *s = LASI_NCR710(opaque);
     uint64_t val = 0;
 
-    trace_lasi_ncr710_reg_read(addr, 0, size);
-
     if (addr == 0x00) {  /* Device ID */
         val = (HPHW_FIO << 24) | LASI_710_SVERSION;
         trace_lasi_ncr710_reg_read_id(HPHW_FIO, LASI_710_SVERSION, val);
@@ -51,22 +47,22 @@ static uint64_t lasi_ncr710_reg_read(void *opaque, hwaddr addr,
     if (addr >= 0x100) {
         hwaddr ncr_addr = addr - 0x100;
         if (size == 1) {
+            /*
+             * Single byte access: flip the byte lane (PA-RISC big endian
+             * access -> the chip's little endian register order).
+             */
             ncr_addr ^= 3;
-            NCR710_DPRINTF("Reading value to LASI WRAPPER == 0x%lx%s, "
-                           "val=0x%lx, size=%u\n",
-                           addr - 0x100, size == 1 ? " (XORed)" : "",
-                           val, size);
             val = ncr710_reg_read(&s->ncr710, ncr_addr, size);
         } else {
+            /*
+             * Multibyte access: gather little endian, no lane flip, so
+             * the 24/32 bit registers assemble correctly.
+             */
             val = 0;
             for (unsigned i = 0; i < size; i++) {
                 uint8_t byte_val = ncr710_reg_read(&s->ncr710, ncr_addr + i, 1);
                 val |= ((uint64_t)byte_val) << (i * 8);
-                NCR710_DPRINTF("  Read byte %u from NCR addr 0x%lx: "
-                               "0x%02x\n", i, ncr_addr + i, byte_val);
             }
-            NCR710_DPRINTF("  Reconstructed %u-byte value: 0x%lx\n",
-                           size, val);
         }
 
         trace_lasi_ncr710_reg_forward_read(addr, val);
@@ -84,7 +80,13 @@ static void lasi_ncr710_reg_write(void *opaque, hwaddr addr,
 
     trace_lasi_ncr710_reg_write(addr, val, size);
 
-    if (addr <= 0x0F) {
+    /*
+     * 0x00..0x0f is the read only LASI module header (ID/SVERSION/HVERSION) and
+     * 0x10..0xff is unmapped module space; writes to both are ignored.  The
+     * SCSI bus reset is driven through the chip's SCNTL1.RST at forwarded
+     * offset 0x101 (see ncr53c710.c), not through this window.
+     */
+    if (addr <= 0x0f) {
         return;
     }
 
@@ -93,16 +95,10 @@ static void lasi_ncr710_reg_write(void *opaque, hwaddr addr,
 
         if (size == 1) {
             ncr_addr ^= 3;
-            NCR710_DPRINTF("Writing value to LASI WRAPPER == 0x%lx%s, "
-                           "val=0x%lx, size=%u\n",
-                           addr - 0x100, size == 1 ? " (XORed)" : "",
-                           val, size);
             ncr710_reg_write(&s->ncr710, ncr_addr, val, size);
         } else {
             for (unsigned i = 0; i < size; i++) {
                 uint8_t byte_val = (val >> (i * 8)) & 0xff;
-                 NCR710_DPRINTF("  Writing byte %u to NCR addr 0x%lx: 0x%02x\n",
-                       i, ncr_addr + i, byte_val);
                 ncr710_reg_write(&s->ncr710, ncr_addr + i, byte_val, 1);
             }
         }
@@ -113,10 +109,7 @@ static void lasi_ncr710_reg_write(void *opaque, hwaddr addr,
     }
 }
 
-/*
- * req_cancelled, command_complete, transfer_data forwards
- * commands to its core counterparts.
- */
+/* SCSIBusInfo callbacks: trace, then forward to the ncr53c710 core. */
 static void lasi_ncr710_request_cancelled(SCSIRequest *req)
 {
     trace_lasi_ncr710_request_cancelled(req);
@@ -129,20 +122,27 @@ static void lasi_ncr710_command_complete(SCSIRequest *req, size_t resid)
     ncr710_command_complete(req, resid);
 }
 
- static void lasi_ncr710_transfer_data(SCSIRequest *req, uint32_t len)
+static void lasi_ncr710_transfer_data(SCSIRequest *req, uint32_t len)
 {
     trace_lasi_ncr710_transfer_data(len);
     ncr710_transfer_data(req, len);
 }
 
+static void lasi_ncr710_free_request(SCSIBus *bus, void *priv)
+{
+    g_free(priv);
+}
+
 static const struct SCSIBusInfo lasi_ncr710_scsi_info = {
     .tcq = true,
-    .max_target = 8,
-    .max_lun = 8,  /* full LUN support */
+    /* 8-bit bus: targets and LUNs 0-7 (scsi-bus.c maxima are inclusive). */
+    .max_target = 7,
+    .max_lun = 7,
 
     .transfer_data = lasi_ncr710_transfer_data,
     .complete = lasi_ncr710_command_complete,
     .cancel = lasi_ncr710_request_cancelled,
+    .free_request = lasi_ncr710_free_request,
 };
 
 static const MemoryRegionOps lasi_ncr710_mmio_ops = {
@@ -157,12 +157,9 @@ static const MemoryRegionOps lasi_ncr710_mmio_ops = {
 
 static const VMStateDescription vmstate_lasi_ncr710 = {
     .name = "lasi-ncr710",
-    .version_id = 1,
-    .minimum_version_id = 1,
+    .version_id = 2,
+    .minimum_version_id = 2,
     .fields = (const VMStateField[]) {
-        VMSTATE_UINT32(hw_type, LasiNCR710State),
-        VMSTATE_UINT32(sversion, LasiNCR710State),
-        VMSTATE_UINT32(hversion, LasiNCR710State),
         VMSTATE_STRUCT(ncr710, LasiNCR710State, 1, vmstate_ncr710, NCR710State),
         VMSTATE_END_OF_LIST()
     }
@@ -179,49 +176,36 @@ static void lasi_ncr710_realize(DeviceState *dev, Error **errp)
                   &lasi_ncr710_scsi_info);
     s->ncr710.as = &address_space_memory;
     s->ncr710.irq = s->lasi_irq;
+    QTAILQ_INIT(&s->ncr710.queue);
 
-    s->ncr710.reselection_retry_timer =
+    s->ncr710.scripts_timer =
         timer_new_ns(QEMU_CLOCK_VIRTUAL,
-                     ncr710_reselection_retry_callback,
+                     ncr710_scripts_timer_callback,
                      &s->ncr710);
 
     ncr710_soft_reset(&s->ncr710);
 
     trace_lasi_ncr710_timers_initialized(
-        (uint64_t)s->ncr710.reselection_retry_timer);
+        (uint64_t)s->ncr710.scripts_timer);
 
-    /* Initialize memory region */
     memory_region_init_io(&s->mmio, OBJECT(dev), &lasi_ncr710_mmio_ops, s,
                           "lasi-ncr710", 0x200);
     sysbus_init_mmio(sbd, &s->mmio);
 }
 
-void lasi_ncr710_handle_legacy_cmdline(DeviceState *lasi_dev)
+static void lasi_ncr710_unrealize(DeviceState *dev)
 {
-    LasiNCR710State *s = LASI_NCR710(lasi_dev);
-    SCSIBus *bus = &s->ncr710.bus;
-    int found_drives = 0;
-
-    if (!bus) {
-        return;
-    }
+    LasiNCR710State *s = LASI_NCR710(dev);
 
-    for (int unit = 0; unit <= 7; unit++) {
-        DriveInfo *dinfo = drive_get(IF_SCSI, bus->busnr, unit);
-        if (dinfo) {
-            trace_lasi_ncr710_legacy_drive_found(bus->busnr, unit);
-            found_drives++;
-        }
-    }
+    timer_free(s->ncr710.scripts_timer);
+    s->ncr710.scripts_timer = NULL;
+}
 
-    trace_lasi_ncr710_handle_legacy_cmdline(bus->busnr, found_drives);
+void lasi_ncr710_handle_legacy_cmdline(DeviceState *lasi_dev)
+{
+    LasiNCR710State *s = LASI_NCR710(lasi_dev);
 
-    scsi_bus_legacy_handle_cmdline(bus);
-    BusChild *kid;
-    QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
-        trace_lasi_ncr710_scsi_device_created(
-            object_get_typename(OBJECT(kid->child)));
-    }
+    scsi_bus_legacy_handle_cmdline(&s->ncr710.bus);
 }
 
 DeviceState *lasi_ncr710_init(MemoryRegion *addr_space, hwaddr hpa,
@@ -248,20 +232,12 @@ static void lasi_ncr710_reset(DeviceState *dev)
     ncr710_soft_reset(&s->ncr710);
 }
 
-static void lasi_ncr710_instance_init(Object *obj)
-{
-    LasiNCR710State *s = LASI_NCR710(obj);
-
-    s->hw_type = HPHW_FIO;
-    s->sversion = LASI_710_SVERSION;
-    s->hversion = LASI_710_HVERSION;
-}
-
 static void lasi_ncr710_class_init(ObjectClass *klass, const void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
 
     dc->realize = lasi_ncr710_realize;
+    dc->unrealize = lasi_ncr710_unrealize;
     set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
     dc->fw_name = "scsi";
     dc->desc = "HP-PARISC LASI NCR710 SCSI adapter";
@@ -274,7 +250,6 @@ static const TypeInfo lasi_ncr710_info = {
     .name          = TYPE_LASI_NCR710,
     .parent        = TYPE_SYS_BUS_DEVICE,
     .instance_size = sizeof(LasiNCR710State),
-    .instance_init = lasi_ncr710_instance_init,
     .class_init    = lasi_ncr710_class_init,
 };
 
diff --git a/hw/scsi/lasi_ncr710.h b/hw/scsi/lasi_ncr710.h
index 450fb7e1c3..968604c2e7 100644
--- a/hw/scsi/lasi_ncr710.h
+++ b/hw/scsi/lasi_ncr710.h
@@ -1,12 +1,9 @@
 /*
- * LASI Wrapper for NCR710 SCSI Controller
+ * LASI wrapper for the NCR 53C710 SCSI controller
  *
  * Copyright (c) 2025 Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com>
- * This driver was developed during the Google Summer of Code 2025 program.
- * Mentored by Helge Deller <deller@gmx.de>
- *
- * NCR710 SCSI Controller implementation
- * Based on the NCR53C710 Technical Manual Version 3.2, December 2000
+ * Developed during Google Summer of Code 2025, mentored by
+ * Helge Deller <deller@gmx.de>.
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -17,34 +14,15 @@
 #include "hw/core/sysbus.h"
 #include "exec/memattrs.h"
 #include "hw/scsi/scsi.h"
-#include "hw/scsi/ncr53c710.h"
+#include "hw/scsi/ncr53c710_v2.h"
 
 #define TYPE_LASI_NCR710 "lasi-ncr710"
 OBJECT_DECLARE_SIMPLE_TYPE(LasiNCR710State, LASI_NCR710)
 
-#define LASI_SCSI_RESET         0x000   /* SCSI Reset Register */
-#define LASI_SCSI_NCR710_BASE   0x100   /* NCR710 Base Register Offset */
-
-#define PARISC_DEVICE_ID_OFF    0x00    /* HW type, HVERSION, SVERSION */
-#define PARISC_DEVICE_CONFIG_OFF 0x04   /* Configuration data */
-
-#define PHASE_MASK              7
-#define PHASE_DO                0
-
-#define NCR710_SCNTL1_RST       0x08    /* SCSI Reset */
-#define NCR710_ISTAT_RST        0x40    /* Device Reset */
-#define NCR710_ISTAT_ABRT       0x80    /* Script Abort */
-#define NCR710_ISTAT_CON        0x08    /* ISTAT_Connected */
-#define NCR710_DSTAT_DFE        0x80    /* DMA FIFO Empty */
-#define NCR710_CTEST2_DACK      0x01    /* DMA Acknowledge */
-
 typedef struct LasiNCR710State {
     SysBusDevice parent_obj;
     MemoryRegion mmio;
     qemu_irq lasi_irq;       /* IRQ line to LASI controller */
-    uint32_t hw_type;        /* Hardware type (HPHW_*) */
-    uint32_t sversion;       /* Software version */
-    uint32_t hversion;       /* Hardware version */
     NCR710State ncr710;
 } LasiNCR710State;
 
diff --git a/hw/scsi/meson.build b/hw/scsi/meson.build
index 69fde0cf84..066d95f464 100644
--- a/hw/scsi/meson.build
+++ b/hw/scsi/meson.build
@@ -11,7 +11,7 @@ scsi_ss.add(files(
 scsi_ss.add(when: 'CONFIG_ESP', if_true: files('esp.c'))
 scsi_ss.add(when: 'CONFIG_ESP_PCI', if_true: files('esp-pci.c'))
 scsi_ss.add(when: 'CONFIG_LSI_SCSI_PCI', if_true: files('lsi53c895a.c'))
-scsi_ss.add(when: 'CONFIG_NCR710_SCSI', if_true: files('ncr53c710.c', 'lasi_ncr710.c'))
+scsi_ss.add(when: 'CONFIG_NCR710_SCSI', if_true: files('ncr53c710_v2.c', 'lasi_ncr710.c'))
 scsi_ss.add(when: 'CONFIG_MEGASAS_SCSI_PCI', if_true: files('megasas.c'))
 scsi_ss.add(when: 'CONFIG_MPTSAS_SCSI_PCI', if_true: files('mptsas.c', 'mptconfig.c', 'mptendian.c'))
 scsi_ss.add(when: 'CONFIG_VMW_PVSCSI_SCSI_PCI', if_true: files('vmw_pvscsi.c'))
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH 4/4] hw/scsi: Drop old ncr53c710 v1 driver
  2026-11-06 22:18 [PATCH 0/4] [RFC] scsi: New ncr53c710_v2 driver Helge Deller
                   ` (2 preceding siblings ...)
  2026-11-06 22:18 ` [PATCH 3/4] lasi_ncr710: Wire up the new ncr53c710_v2 driver in LASI Helge Deller
@ 2026-11-06 22:18 ` Helge Deller
  3 siblings, 0 replies; 10+ messages in thread
From: Helge Deller @ 2026-11-06 22:18 UTC (permalink / raw)
  To: Peter Maydell, Keith Monahan, BALATON Zoltan, Mark Cave-Ayland,
	qemu-devel, Paolo Bonzini
  Cc: Helge Deller

From: Helge Deller <deller@gmx.de>

Drop this driver as it isn't used any longer.
The functionality is now provided in the new ncr53c710_v2 driver.

Signed-off-by: Helge Deller <deller@gmx.de>
---
 hw/scsi/ncr53c710.c | 2473 -------------------------------------------
 hw/scsi/ncr53c710.h |  246 -----
 2 files changed, 2719 deletions(-)
 delete mode 100644 hw/scsi/ncr53c710.c
 delete mode 100644 hw/scsi/ncr53c710.h

diff --git a/hw/scsi/ncr53c710.c b/hw/scsi/ncr53c710.c
deleted file mode 100644
index 57967e7a84..0000000000
--- a/hw/scsi/ncr53c710.c
+++ /dev/null
@@ -1,2473 +0,0 @@
-/*
- * QEMU NCR710 SCSI Controller
- *
- * Copyright (c) 2025 Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com>
- * This driver was developed during the Google Summer of Code 2025 program.
- *
- * NCR710 SCSI Controller implementation
- * Based on the NCR53C710 Technical Manual Version 3.2, December 2000
- *
- * Developed from an implementation of NCR53C710 by Helge Deller
- * which was interim based on the implementation by Toni Wilen for UAE.
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- *
- * Contents:
- * 1. Register Definitions
- * 2. Register name functions
- * 3. Parity functions
- * 4. SCSI FIFO Structures
- * 5. Scripts Misc functions
- * 6. DMA functions
- * 7. Scripts functions
- * 8. Read and Write functions
- * 9. QEMU Device model functions
- *
- */
-
-#include "qemu/osdep.h"
-#include "qapi/error.h"
-#include "qemu/timer.h"
-#include "hw/core/irq.h"
-#include "hw/core/sysbus.h"
-#include "hw/scsi/scsi.h"
-#include "hw/scsi/ncr53c710.h"
-#include "migration/vmstate.h"
-#include "system/dma.h"
-#include "qemu/log.h"
-#include "qemu/module.h"
-#include "trace.h"
-#include "qom/object.h"
-
-#define NCR710_MAX_DEVS 7
-
-/* SCNTL0 (0x00) - SCSI Control Register 0 */
-#define NCR710_SCNTL0_TRG    0x01
-#define NCR710_SCNTL0_AAP    0x02
-#define NCR710_SCNTL0_EPG    0x04
-#define NCR710_SCNTL0_EPC    0x08
-#define NCR710_SCNTL0_WATN   0x10
-#define NCR710_SCNTL0_START  0x20
-#define NCR710_SCNTL0_ARB0   0x40
-#define NCR710_SCNTL0_ARB1   0x80
-
-/* SCNTL1 (0x01) - SCSI Control Register 1 */
-#define NCR710_SCNTL1_RES0   0x01
-#define NCR710_SCNTL1_RES1   0x02
-#define NCR710_SCNTL1_AESP   0x04
-#define NCR710_SCNTL1_RST    0x08
-#define NCR710_SCNTL1_CON    0x10
-#define NCR710_SCNTL1_ESR    0x20
-#define NCR710_SCNTL1_ADB    0x40
-#define NCR710_SCNTL1_EXC    0x80
-
-/* ISTAT (0x21) - Interrupt Status Register */
-#define NCR710_ISTAT_DIP    0x01
-#define NCR710_ISTAT_SIP    0x02
-#define NCR710_ISTAT_CON    0x08
-#define NCR710_ISTAT_SIGP   0x20
-#define NCR710_ISTAT_RST    0x40
-#define NCR710_ISTAT_ABRT   0x80
-
-/* SSTAT0 (0x0D) - SCSI Status Register 0 */
-#define NCR710_SSTAT0_PAR    0x01
-#define NCR710_SSTAT0_RST    0x02
-#define NCR710_SSTAT0_UDC    0x04
-#define NCR710_SSTAT0_SGE    0x08
-#define NCR710_SSTAT0_SEL    0x10
-#define NCR710_SSTAT0_STO    0x20
-#define NCR710_SSTAT0_FCMP   0x40
-#define NCR710_SSTAT0_MA     0x80
-
-/* SSTAT1 (0x0E) - SCSI Status Register 1 */
-#define NCR710_SSTAT1_ORF    0x02
-#define NCR710_SSTAT1_ILF    0x04
-
-/* SSTAT2 (0x0F) - SCSI Status Register 2 */
-#define NCR710_SSTAT2_FF0    0x01
-#define NCR710_SSTAT2_FF1    0x02
-#define NCR710_SSTAT2_FF2    0x04
-#define NCR710_SSTAT2_FF3    0x08
-
-/* SOCL (0x07) / SBCL (0x0B) - SCSI Output/Bus Control Lines */
-#define NCR710_SOCL_IO       0x01
-#define NCR710_SOCL_CD       0x02
-#define NCR710_SOCL_MSG      0x04
-#define NCR710_SOCL_ATN      0x08
-#define NCR710_SOCL_SEL      0x10
-#define NCR710_SOCL_BSY      0x20
-#define NCR710_SOCL_ACK      0x40
-#define NCR710_SOCL_REQ      0x80
-
-/* SBCL bits same as SOCL */
-#define NCR710_SBCL_IO       0x01
-#define NCR710_SBCL_CD       0x02
-#define NCR710_SBCL_MSG      0x04
-#define NCR710_SBCL_ATN      0x08
-#define NCR710_SBCL_SEL      0x10
-#define NCR710_SBCL_BSY      0x20
-#define NCR710_SBCL_ACK      0x40
-#define NCR710_SBCL_REQ      0x80
-
-/* DSTAT (0x0C) - DMA Status Register */
-#define NCR710_DSTAT_IID     0x01
-#define NCR710_DSTAT_SIR     0x04
-#define NCR710_DSTAT_SSI     0x08
-#define NCR710_DSTAT_ABRT    0x10
-#define NCR710_DSTAT_BF      0x20
-#define NCR710_DSTAT_MDPE    0x40
-#define NCR710_DSTAT_DFE     0x80
-
-/* DCNTL (0x3B) - DMA Control Register */
-#define NCR710_DCNTL_COM     0x01
-#define NCR710_DCNTL_IRQD    0x02
-#define NCR710_DCNTL_STD     0x04
-#define NCR710_DCNTL_IRQM    0x08
-#define NCR710_DCNTL_SSM     0x10
-#define NCR710_DCNTL_PFEN    0x20
-#define NCR710_DCNTL_PFF     0x40
-
-/* DMODE (0x38) - DMA Mode Register */
-#define NCR710_DMODE_MAN     0x01
-#define NCR710_DMODE_BOF     0x02
-#define NCR710_DMODE_ERMP    0x04
-#define NCR710_DMODE_ERL     0x08
-#define NCR710_DMODE_DIOM    0x10
-#define NCR710_DMODE_SIOM    0x20
-#define NCR710_DMODE_BL_MASK 0xC0
-#define NCR710_DMODE_BL_1    0x00
-#define NCR710_DMODE_BL_2    0x40
-#define NCR710_DMODE_BL_4    0x80
-#define NCR710_DMODE_BL_8    0xC0
-
-/* CTEST2 (0x16) - Chip Test Register 2 */
-#define NCR710_CTEST2_DACK   0x01
-#define NCR710_CTEST2_DREQ   0x02
-#define NCR710_CTEST2_TEOP   0x04
-#define NCR710_CTEST2_PCICIE 0x08
-#define NCR710_CTEST2_CM     0x10
-#define NCR710_CTEST2_CIO    0x20
-#define NCR710_CTEST2_SIGP   0x40
-#define NCR710_CTEST2_DDIR   0x80
-
-/* CTEST5 (0x19) - Chip Test Register 5 */
-#define NCR710_CTEST5_BL2    0x04
-#define NCR710_CTEST5_DDIR   0x08
-#define NCR710_CTEST5_MASR   0x10
-#define NCR710_CTEST5_DFSN   0x20
-#define NCR710_CTEST5_BBCK   0x40
-#define NCR710_CTEST5_ADCK   0x80
-
-/* SCID (0x04) - SCSI Chip ID Register */
-#define NCR710_SCID_RRE      0x60
-#define NCR710_SCID_ID_MASK  0x07
-
-#define NCR710_HOST_ID       7
-
-/* NCR53C710 has 8-byte SCSI FIFO */
-#define NCR710_MAX_MSGIN_LEN 8
-#define NCR710_BUF_SIZE         4096
-
-/* Standard SCSI Message Byte Constants */
-#define SCSI_MSG_ABORT                      0x06
-#define SCSI_MSG_BUS_DEVICE_RESET           0x0c
-#define SCSI_MSG_COMMAND_COMPLETE           0x00
-#define SCSI_MSG_DISCONNECT                 0x04
-#define SCSI_MSG_EXTENDED_MESSAGE           0x01
-#define SCSI_MSG_IDENTIFY                   0x80
-#define SCSI_MSG_IGNORE_WIDE_RESIDUE        0x23
-#define SCSI_MSG_MESSAGE_PARITY_ERROR       0x09
-#define SCSI_MSG_MESSAGE_REJECT             0x07
-#define SCSI_MSG_NO_OPERATION               0x08
-#define SCSI_MSG_RELEASE_RECOVERY           0x10
-#define SCSI_MSG_RESTORE_POINTERS           0x03
-#define SCSI_MSG_SAVE_DATA_POINTER          0x02
-#define SCSI_MSG_SYNCHRONOUS_DATA_TRANSFER  0x01
-#define SCSI_MSG_WIDE_DATA_TRANSFER         0x03
-
-/* Script interrupt codes */
-#define A_GOOD_STATUS_AFTER_STATUS          0x401
-#define A_DISCONNECT_AFTER_CMD              0x380
-#define A_DISCONNECT_AFTER_DATA             0x580
-#define A_DISCONNECT_DURING_DATA            0x780
-#define A_RESELECTION_IDENTIFIED            0x1003
-#define A_UNEXPECTED_PHASE                  0x20
-#define A_FATAL                             0x2000
-#define A_DEBUG_INTERRUPT                   0x3000
-
-/* SCSI Script execution states */
-#define SCRIPT_STATE_IDLE                  0
-#define SCRIPT_STATE_SELECTING             1
-#define SCRIPT_STATE_COMMAND               2
-#define SCRIPT_STATE_DATA                  3
-#define SCRIPT_STATE_STATUS                4
-#define SCRIPT_STATE_MESSAGE               5
-#define SCRIPT_STATE_DISCONNECTED          6
-
-#define AFTER_SELECTION         0x100
-#define BEFORE_CMD              0x200
-#define AFTER_CMD               0x300
-#define AFTER_STATUS            0x400
-#define AFTER_DATA_IN           0x500
-#define AFTER_DATA_OUT          0x600
-#define DURING_DATA_IN          0x700
-
-#define NOT_MSG_OUT             0x10
-#define UNEXPECTED_PHASE        0x20
-#define NOT_MSG_IN              0x30
-#define UNEXPECTED_MSG          0x40
-#define MSG_IN                  0x50
-#define SDTR_MSG_R              0x60
-#define REJECT_MSG_R            0x70
-#define DISCONNECT              0x80
-#define MSG_OUT                 0x90
-#define WDTR_MSG_R              0xA0
-
-#define GOOD_STATUS             0x1
-
-#define NOT_MSG_OUT_AFTER_SELECTION         0x110
-#define UNEXPECTED_PHASE_BEFORE_CMD         0x220
-#define UNEXPECTED_PHASE_AFTER_CMD          0x320
-#define NOT_MSG_IN_AFTER_STATUS             0x430
-#define GOOD_STATUS_AFTER_STATUS            0x401
-#define UNEXPECTED_PHASE_AFTER_DATA_IN      0x520
-#define UNEXPECTED_PHASE_AFTER_DATA_OUT     0x620
-#define UNEXPECTED_MSG_BEFORE_CMD           0x240
-#define MSG_IN_BEFORE_CMD                   0x250
-#define MSG_IN_AFTER_CMD                    0x350
-#define SDTR_MSG_BEFORE_CMD                 0x260
-#define REJECT_MSG_BEFORE_CMD               0x270
-#define DISCONNECT_AFTER_CMD                0x380
-#define SDTR_MSG_AFTER_CMD                  0x360
-#define WDTR_MSG_AFTER_CMD                  0x3A0
-#define MSG_IN_AFTER_STATUS                 0x440
-#define DISCONNECT_AFTER_DATA               0x580
-#define MSG_IN_AFTER_DATA_IN                0x550
-#define MSG_IN_AFTER_DATA_OUT               0x650
-#define MSG_OUT_AFTER_DATA_IN               0x590
-#define DATA_IN_AFTER_DATA_IN               0x5a0
-#define MSG_IN_DURING_DATA_IN               0x750
-#define DISCONNECT_DURING_DATA              0x780
-
-#define RESELECTED_DURING_SELECTION      0x1000
-#define COMPLETED_SELECTION_AS_TARGET    0x1001
-#define RESELECTION_IDENTIFIED           0x1003
-
-#define FATAL                            0x2000
-#define FATAL_UNEXPECTED_RESELECTION_MSG 0x2000
-#define FATAL_SEND_MSG                   0x2001
-#define FATAL_NOT_MSG_IN_AFTER_SELECTION 0x2002
-#define FATAL_ILLEGAL_MSG_LENGTH         0x2003
-
-#define DEBUG_INTERRUPT     0x3000
-#define DEBUG_INTERRUPT1    0x3001
-#define DEBUG_INTERRUPT2    0x3002
-#define DEBUG_INTERRUPT3    0x3003
-#define DEBUG_INTERRUPT4    0x3004
-#define DEBUG_INTERRUPT5    0x3005
-#define DEBUG_INTERRUPT6    0x3006
-
-#define COMMAND_COMPLETE_MSG      0x00
-#define EXTENDED_MSG              0x01
-#define SDTR_MSG                  0x01
-#define SAVE_DATA_PTRS_MSG        0x02
-#define RESTORE_DATA_PTRS_MSG     0x03
-#define WDTR_MSG                  0x03
-#define DISCONNECT_MSG            0x04
-#define REJECT_MSG                0x07
-#define PARITY_ERROR_MSG          0x09
-#define SIMPLE_TAG_MSG            0x20
-#define IDENTIFY_MSG              0x80
-#define IDENTIFY_MSG_MASK         0x7F
-#define TWO_BYTE_MSG              0x20
-#define TWO_BYTE_MSG_MASK         0x0F
-
-/* SCSI phases */
-#define PHASE_DO   0  /* Data out phase */
-#define PHASE_DI   1  /* Data in phase */
-#define PHASE_CO   2  /* Command phase */
-#define PHASE_SI   3  /* Status phase */
-#define PHASE_ST   3  /* Status phase (alias) */
-#define PHASE_MO   6  /* Message out phase */
-#define PHASE_MI   7  /* Message in phase */
-#define PHASE_MASK 7  /* Mask for phase bits */
-
-#define NCR710_TAG_VALID     (1 << 16)
-
-static void ncr710_scsi_fifo_init(NCR710_SCSI_FIFO *fifo);
-static const char *ncr710_reg_name(int offset);
-static void ncr710_script_scsi_interrupt(NCR710State *s, int stat0);
-static void ncr710_update_irq(NCR710State *s);
-static void ncr710_script_dma_interrupt(NCR710State *s, int stat);
-static void ncr710_request_free(NCR710State *s, NCR710Request *p);
-static inline void ncr710_dma_read(NCR710State *s, uint32_t addr,
-                                   void *buf, uint32_t len);
-static inline void ncr710_dma_write(NCR710State *s, uint32_t addr,
-                                    const void *buf, uint32_t len);
-static uint8_t ncr710_reg_readb(NCR710State *s, int offset);
-static void ncr710_reg_writeb(NCR710State *s, int offset, uint8_t val);
-
-
-static inline int ncr710_irq_on_rsl(NCR710State *s)
-{
-    return (s->sien0 & NCR710_SSTAT0_SEL) != 0;
-}
-
-static void ncr710_clear_pending_irq(NCR710State *s)
-{
-    if (s->current) {
-        if (s->current->req) {
-            s->current->req->hba_private = NULL;
-        }
-        ncr710_request_free(s, s->current);
-        s->current = NULL;
-    }
-}
-
-void ncr710_soft_reset(NCR710State *s)
-{
-    trace_ncr710_reset();
-    s->carry = 0;
-    s->msg_action = NCR710_MSG_ACTION_NONE;
-    s->msg_len = 0;
-    s->waiting = NCR710_WAIT_NONE;
-    s->wait_reselect = false;
-    s->reselection_id = 0;
-    s->dsa = 0;
-    s->dnad = 0;
-    s->dbc = 0;
-    s->temp = 0;
-    s->scratch = 0;
-    s->istat &= 0x40;
-    s->dcmd = 0x40;
-    s->dstat = NCR710_DSTAT_DFE;
-    s->dien = 0x04;
-    s->sien0 = 0;
-    s->ctest2 = NCR710_CTEST2_DACK;
-    s->ctest3 = 0;
-    s->ctest4 = 0;
-    s->ctest5 = 0;
-    s->dsp = 0;
-    s->dsps = 0;
-    s->dmode = 0;
-    s->dcntl = 0;
-    s->scntl0 = 0xc0;
-    s->scntl1 = 0;
-    s->sstat0 = 0;
-    s->sstat1 = 0;
-    s->sstat2 = 0;
-    s->scid = 0x80;
-    s->sxfer = 0;
-    s->socl = 0;
-    s->sdid = 0;
-    s->sbcl = 0;
-    s->sidl = 0;
-    s->sfbr = 0;
-    qemu_set_irq(s->irq, 0);
-    ncr710_clear_pending_irq(s);
-    ncr710_scsi_fifo_init(&s->scsi_fifo);
-}
-
-static const char *ncr710_reg_name(int offset)
-{
-    switch (offset) {
-    case NCR710_SCNTL0_REG:      return "SCNTL0";
-    case NCR710_SCNTL1_REG:      return "SCNTL1";
-    case NCR710_SDID_REG:        return "SDID";
-    case NCR710_SIEN_REG:        return "SIEN";
-    case NCR710_SCID_REG:        return "SCID";
-    case NCR710_SXFER_REG:       return "SXFER";
-    case NCR710_SODL_REG:        return "SODL";
-    case NCR710_SOCL_REG:        return "SOCL";
-    case NCR710_SFBR_REG:        return "SFBR";
-    case NCR710_SIDL_REG:        return "SIDL";
-    case NCR710_SBDL_REG:        return "SBDL";
-    case NCR710_SBCL_REG:        return "SBCL";
-    case NCR710_DSTAT_REG:       return "DSTAT";
-    case NCR710_SSTAT0_REG:      return "SSTAT0";
-    case NCR710_SSTAT1_REG:      return "SSTAT1";
-    case NCR710_SSTAT2_REG:      return "SSTAT2";
-    case NCR710_DSA_REG:         return "DSA";
-    case NCR710_DSA_REG + 1:     return "DSA+1";
-    case NCR710_DSA_REG + 2:     return "DSA+2";
-    case NCR710_DSA_REG + 3:     return "DSA+3";
-    case NCR710_CTEST0_REG:      return "CTEST0";
-    case NCR710_CTEST1_REG:      return "CTEST1";
-    case NCR710_CTEST2_REG:      return "CTEST2";
-    case NCR710_CTEST3_REG:      return "CTEST3";
-    case NCR710_CTEST4_REG:      return "CTEST4";
-    case NCR710_CTEST5_REG:      return "CTEST5";
-    case NCR710_CTEST6_REG:      return "CTEST6";
-    case NCR710_CTEST7_REG:      return "CTEST7";
-    case NCR710_TEMP_REG:        return "TEMP";
-    case NCR710_TEMP_REG + 1:    return "TEMP+1";
-    case NCR710_TEMP_REG + 2:    return "TEMP+2";
-    case NCR710_TEMP_REG + 3:    return "TEMP+3";
-    case NCR710_DFIFO_REG:       return "DFIFO";
-    case NCR710_ISTAT_REG:       return "ISTAT";
-    case NCR710_CTEST8_REG:      return "CTEST8";
-    case NCR710_LCRC_REG:        return "LCRC";
-    case NCR710_DBC_REG:         return "DBC";
-    case NCR710_DBC_REG + 1:     return "DBC+1";
-    case NCR710_DBC_REG + 2:     return "DBC+2";
-    case NCR710_DCMD_REG:        return "DCMD";
-    case NCR710_DNAD_REG:        return "DNAD";
-    case NCR710_DNAD_REG + 1:    return "DNAD+1";
-    case NCR710_DNAD_REG + 2:    return "DNAD+2";
-    case NCR710_DNAD_REG + 3:    return "DNAD+3";
-    case NCR710_DSP_REG:         return "DSP";
-    case NCR710_DSP_REG + 1:     return "DSP+1";
-    case NCR710_DSP_REG + 2:     return "DSP+2";
-    case NCR710_DSP_REG + 3:     return "DSP+3";
-    case NCR710_DSPS_REG:        return "DSPS";
-    case NCR710_DSPS_REG + 1:    return "DSPS+1";
-    case NCR710_DSPS_REG + 2:    return "DSPS+2";
-    case NCR710_DSPS_REG + 3:    return "DSPS+3";
-    case NCR710_SCRATCH_REG:     return "SCRATCH";
-    case NCR710_SCRATCH_REG + 1: return "SCRATCH+1";
-    case NCR710_SCRATCH_REG + 2: return "SCRATCH+2";
-    case NCR710_SCRATCH_REG + 3: return "SCRATCH+3";
-    case NCR710_DMODE_REG:       return "DMODE";
-    case NCR710_DIEN_REG:        return "DIEN";
-    case NCR710_DWT_REG:         return "DWT";
-    case NCR710_DCNTL_REG:       return "DCNTL";
-    case NCR710_ADDER_REG:       return "ADDER";
-    case NCR710_ADDER_REG + 1:   return "ADDER+1";
-    case NCR710_ADDER_REG + 2:   return "ADDER+2";
-    case NCR710_ADDER_REG + 3:   return "ADDER+3";
-    default:                     return "UNKNOWN";
-    }
-}
-
-static uint8_t ncr710_generate_scsi_parity(NCR710State *s, uint8_t data)
-{
-    uint8_t parity = parity8(data);
-
-    if (s->scntl1 & NCR710_SCNTL1_AESP) {
-        parity = !parity;
-    }
-
-    return parity;
-}
-
-static bool ncr710_check_scsi_parity(NCR710State *s, uint8_t data,
-                                     uint8_t parity)
-{
-    if (!(s->scntl0 & NCR710_SCNTL0_EPC)) {
-        return true;
-    }
-
-    uint8_t expected_parity = ncr710_generate_scsi_parity(s, data);
-    return parity == expected_parity;
-}
-
-static void ncr710_handle_parity_error(NCR710State *s)
-{
-    s->sstat0 |= NCR710_SSTAT0_PAR;
-
-    /* If parity error ATN is enabled, assert ATN */
-    if (s->scntl0 & NCR710_SCNTL0_AAP) {
-        s->socl |= NCR710_SOCL_ATN;
-    }
-
-    ncr710_script_scsi_interrupt(s, NCR710_SSTAT0_PAR);
-}
-
-/*
- * NCR710 SCSI FIFO IMPLEMENTATION
- *
- * Hardware Specifications (NCR53C710 datasheet):
- * - Width: 9 bits (8 data bits + 1 parity bit)
- * - Depth: 8 bytes
- * - Type: Circular buffer
- *
- * Implementation:
- * - Enqueue: Add byte at tail position ((head + count) % 8)
- * - Dequeue: Remove byte from head position, advance head
- * - Status: Empty (count=0), Full (count=8)
- *
- * FIFO Operations:
- * - ncr710_scsi_fifo_init()    - Reset FIFO to empty state
- * - ncr710_scsi_fifo_enqueue() - Add byte with parity to tail
- * - ncr710_scsi_fifo_dequeue() - Remove byte with parity from head
- * - ncr710_scsi_fifo_empty()   - Check if FIFO is empty
- * - ncr710_scsi_fifo_full()    - Check if FIFO is full
- */
-
-static void ncr710_scsi_fifo_init(NCR710_SCSI_FIFO *fifo)
-{
-    memset(fifo->data, 0, NCR710_SCSI_FIFO_SIZE);
-    memset(fifo->parity, 0, NCR710_SCSI_FIFO_SIZE);
-    fifo->head = 0;
-    fifo->count = 0;
-}
-
-static inline bool ncr710_scsi_fifo_empty(NCR710_SCSI_FIFO *fifo)
-{
-    return fifo->count == 0;
-}
-
-static inline bool ncr710_scsi_fifo_full(NCR710_SCSI_FIFO *fifo)
-{
-    return fifo->count == NCR710_SCSI_FIFO_SIZE;
-}
-
-static inline int ncr710_scsi_fifo_enqueue(NCR710_SCSI_FIFO *fifo,
-                                           uint8_t data, uint8_t parity)
-{
-    if (ncr710_scsi_fifo_full(fifo)) {
-        return -1; /* FIFO full - 8 transfers deep */
-    }
-
-    /* Add data at the tail (head + count) */
-    int tail_pos = (fifo->head + fifo->count) % NCR710_SCSI_FIFO_SIZE;
-    fifo->data[tail_pos] = data;
-    fifo->parity[tail_pos] = parity;
-    fifo->count++;
-
-    return 0;
-}
-
-static inline uint8_t ncr710_scsi_fifo_dequeue(NCR710_SCSI_FIFO *fifo,
-                                                uint8_t *parity)
-{
-    uint8_t data;
-
-    if (ncr710_scsi_fifo_empty(fifo)) {
-        *parity = 0;
-        return 0; /* FIFO empty */
-    }
-
-    /* Taking data from the head position */
-    data = fifo->data[fifo->head];
-    *parity = fifo->parity[fifo->head];
-    fifo->head = (fifo->head + 1) % NCR710_SCSI_FIFO_SIZE;
-    fifo->count--;
-
-    return data;
-}
-
-static inline uint32_t ncr710_read_dword(NCR710State *s, uint32_t addr)
-{
-    uint32_t buf;
-    address_space_read(s->as, addr, MEMTXATTRS_UNSPECIFIED,
-                       (uint8_t *)&buf, 4);
-    /*
-     * The NCR710 datasheet saying "operates internally in LE mode"
-     * refers to its internal register organization,
-     * not how it reads SCRIPTS from host memory.
-     */
-    buf = be32_to_cpu(buf);
-    NCR710_DPRINTF("Read dword %08x from %08x\n", buf, addr);
-    return buf;
-}
-
-static inline void ncr710_dma_read(NCR710State *s, uint32_t addr,
-                                   void *buf, uint32_t len)
-{
-    address_space_read(s->as, addr, MEMTXATTRS_UNSPECIFIED,
-                       buf, len);
-    NCR710_DPRINTF("Read %d bytes from %08x: ", len, addr);
-    for (int i = 0; i < len && i < 16; i++) {
-        NCR710_DPRINTF("%02x ", ((uint8_t *)buf)[i]);
-    }
-    NCR710_DPRINTF("\n");
-}
-
-static inline void ncr710_dma_write(NCR710State *s, uint32_t addr,
-                                    const void *buf, uint32_t len)
-{
-    address_space_write(s->as, addr, MEMTXATTRS_UNSPECIFIED,
-                        buf, len);
-    NCR710_DPRINTF("Wrote %d bytes to %08x\n", len, addr);
-}
-
-static void ncr710_stop_script(NCR710State *s)
-{
-    s->script_active = 0;
-    s->scntl1 &= ~NCR710_SCNTL1_CON;
-    s->istat &= ~NCR710_ISTAT_CON;
-}
-
-static void ncr710_update_irq(NCR710State *s)
-{
-    int level = 0;
-
-    if (s->dstat & ~NCR710_DSTAT_DFE) {
-        if (s->dstat & s->dien) {
-            level = 1;
-        }
-        s->istat |= NCR710_ISTAT_DIP;
-    } else {
-        s->istat &= ~NCR710_ISTAT_DIP;
-    }
-
-    if (s->sstat0) {
-        if ((s->sstat0 & s->sien0)) {
-            level = 1;
-        }
-        s->istat |= NCR710_ISTAT_SIP;
-    } else {
-        s->istat &= ~NCR710_ISTAT_SIP;
-    }
-
-    qemu_set_irq(s->irq, level);
-}
-
-static void ncr710_script_scsi_interrupt(NCR710State *s, int stat0)
-{
-    uint32_t mask0;
-
-    trace_ncr710_script_scsi_interrupt(stat0, s->sstat0);
-    s->sstat0 |= stat0;
-    mask0 = stat0 & s->sien0;
-    if (mask0) {
-        ncr710_stop_script(s);
-        s->istat |= NCR710_ISTAT_SIP;
-        ncr710_update_irq(s);
-    }
-}
-
-static void ncr710_script_dma_interrupt(NCR710State *s, int stat)
-{
-    trace_ncr710_script_dma_interrupt(stat, s->dstat);
-    if (stat == NCR710_DSTAT_SIR && (s->dstat & NCR710_DSTAT_DFE)) {
-        s->dstat &= ~NCR710_DSTAT_DFE;
-    }
-
-    s->dstat |= stat;
-    s->istat |= NCR710_ISTAT_DIP;
-    ncr710_update_irq(s);
-    ncr710_stop_script(s);
-}
-
-inline void ncr710_set_phase(NCR710State *s, int phase)
-{
-    s->sstat2 = (s->sstat2 & ~PHASE_MASK) | phase;
-    s->ctest0 &= ~1;
-    if (phase == PHASE_DI) {
-        s->ctest0 |= 1;
-    }
-    s->sbcl &= ~NCR710_SBCL_REQ;
-}
-
-static void ncr710_disconnect(NCR710State *s)
-{
-    trace_ncr710_disconnect(s->waiting);
-    if (s->waiting == NCR710_WAIT_NONE) {
-        s->scntl1 &= ~NCR710_SCNTL1_CON;
-        s->istat &= ~NCR710_ISTAT_CON;
-    }
-    s->sstat2 &= ~PHASE_MASK;
-}
-
-static void ncr710_bad_selection(NCR710State *s, uint32_t id)
-{
-    trace_ncr710_bad_selection(id);
-    s->dstat = 0;
-    s->dsps = 0;
-    ncr710_script_scsi_interrupt(s, NCR710_SSTAT0_STO);
-    ncr710_disconnect(s);
-}
-
-static void ncr710_clear_selection_timeout(NCR710State *s)
-{
-    if (s->sstat0 & NCR710_SSTAT0_STO) {
-        s->sstat0 &= ~NCR710_SSTAT0_STO;
-        ncr710_clear_pending_irq(s);
-        if (s->sstat0 == 0) {
-            s->istat &= ~NCR710_ISTAT_SIP;
-        }
-        ncr710_update_irq(s);
-    }
-}
-
-static void ncr710_do_dma(NCR710State *s, int out)
-{
-    uint32_t count;
-    uint32_t addr;
-    SCSIDevice *dev;
-    assert(s->current);
-    if (!s->current->dma_len) {
-        /* We wait until data is available.  */
-        return;
-    }
-
-    dev = s->current->req->dev;
-    assert(dev);
-
-    count = s->dbc;
-    if (count > s->current->dma_len) {
-        count = s->current->dma_len;
-    }
-
-    addr = s->dnad;
-
-    s->dnad += count;
-    s->dbc -= count;
-    if (s->current->dma_buf == NULL) {
-        s->current->dma_buf = scsi_req_get_buf(s->current->req);
-    }
-    /* ??? Set SFBR to first data byte.  */
-    if (out) {
-        ncr710_dma_read(s, addr, s->current->dma_buf, count);
-    } else {
-        ncr710_dma_write(s, addr, s->current->dma_buf, count);
-    }
-    s->current->dma_len -= count;
-    if (s->current->dma_len == 0) {
-        s->current->dma_buf = NULL;
-        s->current->pending = 0;
-        s->waiting = NCR710_WAIT_DMA;
-        scsi_req_continue(s->current->req);
-        return;
-    } else {
-        s->current->dma_buf += count;
-        s->waiting = NCR710_WAIT_NONE;
-        ncr710_execute_script(s);
-    }
-}
-
-static void ncr710_add_msg_byte(NCR710State *s, uint8_t data)
-{
-    if (s->msg_len >= NCR710_MAX_MSGIN_LEN) {
-        BADF("MSG IN data too long\n");
-    } else {
-        s->msg[s->msg_len++] = data;
-    }
-}
-
-static void ncr710_request_free(NCR710State *s, NCR710Request *p)
-{
-    if (!p) {
-        return;
-    }
-    if (p->req && p->req->hba_private == p) {
-        p->req->hba_private = NULL;
-    }
-    if (p == s->current) {
-        s->current = NULL;
-    }
-    g_free(p);
-}
-
-void ncr710_request_cancelled(SCSIRequest *req)
-{
-    NCR710State *s = ncr710_from_scsi_bus(req->bus);
-    NCR710Request *p = (NCR710Request *)req->hba_private;
-    if (p) {
-        req->hba_private = NULL;
-        p->req = NULL;
-        ncr710_request_free(s, p);
-    }
-    scsi_req_unref(req);
-}
-
-static int ncr710_queue_req(NCR710State *s, SCSIRequest *req, uint32_t len)
-{
-    NCR710Request *p = (NCR710Request *)req->hba_private;
-
-    if (!p) {
-        return -1;
-    }
-    p->pending = len;
-    if ((s->waiting == NCR710_WAIT_RESELECT &&
-        !(s->istat & (NCR710_ISTAT_SIP | NCR710_ISTAT_DIP))) ||
-        (ncr710_irq_on_rsl(s) && !(s->scntl1 & NCR710_SCNTL1_CON) &&
-        !(s->istat & (NCR710_ISTAT_SIP | NCR710_ISTAT_DIP)))) {
-        s->current = p;
-        return 0;
-    } else {
-        s->current = p;
-        return 1;
-    }
-}
-
-void ncr710_command_complete(SCSIRequest *req, size_t resid)
-{
-    NCR710State *s = ncr710_from_scsi_bus(req->bus);
-    NCR710Request *p = (NCR710Request *)req->hba_private;
-
-    trace_ncr710_command_complete(req->tag, req->status);
-
-    s->lcrc = 0;
-    s->status = req->status;
-    s->command_complete = NCR710_CMD_COMPLETE;
-
-    if (p) {
-        p->pending = 0;
-    }
-
-    ncr710_set_phase(s, PHASE_ST);
-
-    if (p) {
-        req->hba_private = NULL;
-        if (p == s->current) {
-            p->req = NULL;
-        } else {
-            ncr710_request_free(s, p);
-        }
-        scsi_req_unref(req);
-    }
-
-    if (s->waiting == NCR710_WAIT_RESELECT || s->waiting == NCR710_WAIT_DMA) {
-        s->waiting = NCR710_WAIT_NONE;
-        ncr710_execute_script(s);
-    }
-}
-
-void ncr710_transfer_data(SCSIRequest *req, uint32_t len)
-{
-    NCR710State *s = ncr710_from_scsi_bus(req->bus);
-
-    assert(req->hba_private);
-
-    if (s->waiting == NCR710_WAIT_DMA) {
-        NCR710Request *p = (NCR710Request *)req->hba_private;
-        if (p) {
-            p->dma_len = len;
-        }
-        s->dsp -= 8;
-        s->waiting = NCR710_WAIT_NONE;
-        ncr710_execute_script(s);
-        return;
-    }
-
-    if (s->wait_reselect) {
-        s->current = (NCR710Request *)req->hba_private;
-        s->current->dma_len = len;
-        s->waiting = NCR710_WAIT_RESELECT;
-    }
-
-    if (req->hba_private != s->current ||
-        (ncr710_irq_on_rsl(s) && !(s->scntl1 & NCR710_SCNTL1_CON)) ||
-        s->waiting == NCR710_WAIT_RESELECT) {
-        int queue_result = ncr710_queue_req(s, req, len);
-        if (queue_result != 0) {
-            return;
-        }
-    }
-
-    /* Host adapter (re)connected */
-    s->command_complete = NCR710_CMD_DATA_READY;
-    if (!s->current) {
-        return;
-    }
-    s->current->dma_len = len;
-
-    if (s->waiting) {
-        s->scntl1 |= NCR710_SCNTL1_CON;
-        s->istat |= NCR710_ISTAT_CON;
-        s->sbcl = NCR710_SBCL_IO | NCR710_SBCL_CD | NCR710_SBCL_MSG |
-                  NCR710_SBCL_BSY | NCR710_SBCL_SEL | NCR710_SBCL_REQ;
-        uint8_t host_id = (s->scid & 0x07);
-
-        /* Special case: both target and host are ID 0 */
-        if (req->dev->id == 0 && host_id == 0) {
-            s->sfbr = 0x00;
-        } else {
-            s->sfbr = (req->dev->id == 0 ? 0 : (1 << req->dev->id)) |
-                      (host_id == 0 ? 0 : (1 << host_id));
-        }
-
-        ncr710_set_phase(s, PHASE_MI);
-
-        if (s->current) {
-            uint8_t identify_msg = 0x80 | (req->lun & 0x07);
-            ncr710_add_msg_byte(s, identify_msg);
-
-            if (s->current->tag) {
-                ncr710_add_msg_byte(s, 0x20);  /* SIMPLE_TAG_MSG */
-                ncr710_add_msg_byte(s, s->current->tag & 0xff);
-            }
-        }
-
-        s->sstat0 |= NCR710_SSTAT0_SEL;
-        s->istat |= NCR710_ISTAT_SIP;
-        s->dsps = RESELECTED_DURING_SELECTION;
-        s->waiting = NCR710_WAIT_NONE;
-        ncr710_update_irq(s);
-        return;
-    }
-    if (!s->script_active && !s->waiting) {
-        ncr710_execute_script(s);
-    }
-}
-
-static int idbitstonum(uint8_t id)
-{
-    return 7 - clz8(id);
-}
-
-static void ncr710_do_command(NCR710State *s)
-{
-    SCSIDevice *dev;
-    uint8_t buf[16];
-    uint32_t id;
-    int n;
-    int bytes_read;
-    if (s->dbc > 16) {
-        s->dbc = 16;
-    }
-
-    /*
-     * Reading command data directly from memory
-     * NOTE: SCSI commands can be up to 16 bytes
-     * (e.g., READ_CAPACITY_10 is 10 bytes) but the NCR710 SCSI FIFO is
-     * only 8 bytes deep. For command phase, we bypass the FIFO and read
-     * directly from memory since commands don't need FIFO buffering.
-     */
-    bytes_read = MIN(s->dbc, sizeof(buf));
-    ncr710_dma_read(s, s->dnad, buf, bytes_read);
-
-    s->dnad += bytes_read;
-    s->dbc -= bytes_read;
-    s->sfbr = buf[0];
-
-    s->command_complete = NCR710_CMD_PENDING;
-    id = (s->select_tag >> 8) & 0xff;
-    s->lcrc = id;
-
-    dev = scsi_device_find(&s->bus, 0, idbitstonum(id), s->current_lun);
-
-    if (!dev) {
-        ncr710_bad_selection(s, id);
-        return;
-    }
-
-    if (s->current) {
-        ncr710_request_free(s, s->current);
-        s->current = NULL;
-    }
-
-    s->current = g_new0(NCR710Request, 1);
-    s->current->tag = s->select_tag;
-    s->current->resume_offset = 0;
-
-    s->current->req = scsi_req_new(dev, s->current->tag, s->current_lun, buf,
-                                   bytes_read, s->current);
-    n = scsi_req_enqueue(s->current->req);
-    if (n) {
-        if (n > 0) {
-            ncr710_set_phase(s, PHASE_DI);
-        } else if (n < 0) {
-            ncr710_set_phase(s, PHASE_DO);
-        }
-        scsi_req_continue(s->current->req);
-    }
-
-    if (!s->command_complete) {
-        if (!n) {
-            ncr710_set_phase(s, PHASE_SI);
-        } else {
-            NCR710_DPRINTF("Data transfer phase\n");
-        }
-    }
-}
-
-static void ncr710_do_status(NCR710State *s)
-{
-    uint8_t status = s->status;
-    uint8_t parity = 0;
-
-    if (s->dbc != 1) {
-        BADF("Bad Status move\n");
-    }
-    s->dbc = 1;
-    s->sfbr = status;
-
-    /* Generate parity if enabled and enqueue status byte */
-    if (s->scntl0 & NCR710_SCNTL0_EPG) {
-        parity = ncr710_generate_scsi_parity(s, status);
-    }
-    ncr710_scsi_fifo_enqueue(&s->scsi_fifo, status, parity);
-
-    /* Dequeue status byte and write to memory */
-    status = ncr710_scsi_fifo_dequeue(&s->scsi_fifo, &parity);
-    if (s->scntl0 & NCR710_SCNTL0_EPC) {
-        if (!ncr710_check_scsi_parity(s, status, parity)) {
-            ncr710_handle_parity_error(s);
-        }
-    }
-    ncr710_dma_write(s, s->dnad, &status, 1);
-
-    s->dnad += 1;
-    s->dbc  -= 1;
-
-    ncr710_set_phase(s, PHASE_MI);
-    s->msg_action = NCR710_MSG_ACTION_DISCONNECT;
-    ncr710_add_msg_byte(s, 0); /* COMMAND COMPLETE */
-    s->command_complete = NCR710_CMD_COMPLETE;
-}
-
-static void ncr710_do_msgin(NCR710State *s)
-{
-    int len;
-    len = s->msg_len;
-    if (len > s->dbc) {
-        len = s->dbc;
-    }
-    s->sfbr = s->msg[0];
-
-    for (int i = 0; i < len; i++) {
-        uint8_t parity = 0;
-        if (s->scntl0 & NCR710_SCNTL0_EPG) {
-            parity = ncr710_generate_scsi_parity(s, s->msg[i]);
-        }
-        ncr710_scsi_fifo_enqueue(&s->scsi_fifo, s->msg[i], parity);
-    }
-
-    uint8_t buf[NCR710_MAX_MSGIN_LEN];
-    for (int i = 0; i < len; i++) {
-        uint8_t parity;
-        buf[i] = ncr710_scsi_fifo_dequeue(&s->scsi_fifo, &parity);
-        if (s->scntl0 & NCR710_SCNTL0_EPC) {
-            if (!ncr710_check_scsi_parity(s, buf[i], parity)) {
-                ncr710_handle_parity_error(s);
-            }
-        }
-    }
-    ncr710_dma_write(s, s->dnad, buf, len);
-
-    s->dnad += len;
-    s->dbc  -= len;
-    s->sidl = s->msg[len - 1];
-    s->msg_len -= len;
-    if (s->msg_len) {
-        memmove(s->msg, s->msg + len, s->msg_len);
-        return;
-    }
-    switch (s->msg_action) {
-    case NCR710_MSG_ACTION_NONE:
-        ncr710_set_phase(s, PHASE_CO);
-        break;
-    case NCR710_MSG_ACTION_DISCONNECT:
-        s->sstat2 &= ~PHASE_MASK;
-        break;
-    case NCR710_MSG_ACTION_DATA_OUT:
-        ncr710_set_phase(s, PHASE_DO);
-        break;
-    case NCR710_MSG_ACTION_DATA_IN:
-        ncr710_set_phase(s, PHASE_DI);
-        break;
-    default:
-        abort();
-    }
-}
-
-static void ncr710_do_msgout(NCR710State *s)
-{
-    NCR710Request *current_req = s->current;
-
-    while (s->dbc > 0) {
-        int to_move = MIN((int)s->dbc, NCR710_SCSI_FIFO_SIZE);
-        uint8_t temp_buf[NCR710_SCSI_FIFO_SIZE];
-        ncr710_dma_read(s, s->dnad, temp_buf, to_move);
-        int filled = 0;
-        for (int j = 0; j < to_move &&
-                 !ncr710_scsi_fifo_full(&s->scsi_fifo); j++) {
-            uint8_t parity = 0;
-            if (s->scntl0 & NCR710_SCNTL0_EPG) {
-                parity = ncr710_generate_scsi_parity(s, temp_buf[j]);
-            }
-            if (ncr710_scsi_fifo_enqueue(&s->scsi_fifo, temp_buf[j],
-                                         parity) == 0) {
-                filled++;
-            } else {
-                break;
-            }
-        }
-
-        if (filled <= 0) {
-            break;
-        }
-        uint8_t buf[NCR710_SCSI_FIFO_SIZE];
-        int bytes = 0;
-        for (int j = 0; j < filled &&
-                 !ncr710_scsi_fifo_empty(&s->scsi_fifo); j++) {
-            uint8_t parity;
-            buf[bytes] = ncr710_scsi_fifo_dequeue(&s->scsi_fifo, &parity);
-            if (s->scntl0 & NCR710_SCNTL0_EPC) {
-                if (!ncr710_check_scsi_parity(s, buf[bytes], parity)) {
-                    ncr710_handle_parity_error(s);
-                }
-            }
-            bytes++;
-        }
-
-        s->dnad += bytes;
-        s->dbc  -= bytes;
-        int i = 0;
-        while (i < bytes) {
-            uint8_t msg = buf[i++];
-            s->sfbr = msg;
-
-            switch (msg) {
-            case SCSI_MSG_COMMAND_COMPLETE:
-                /* 0x00 - NOP / padding byte / Command Complete */
-                /* Just gonna ignore padding bytes, continue processing */
-                break;
-
-            case SCSI_MSG_DISCONNECT: /* 0x04 - Disconnect */
-                ncr710_disconnect(s);
-                break;
-
-            case SCSI_MSG_MESSAGE_REJECT: /* 0x07 - Message Reject */
-                /* Target is rejecting our last message */
-                ncr710_set_phase(s, PHASE_CO);
-                break;
-
-            case SCSI_MSG_NO_OPERATION: /* 0x08 - NOP */
-                ncr710_set_phase(s, PHASE_CO);
-                break;
-
-            case SCSI_MSG_SAVE_DATA_POINTER: /* 0x02 - Save Data Pointer */
-                /* Save current data pointer for later restore */
-                break;
-
-            case SCSI_MSG_RESTORE_POINTERS: /* 0x03 - Restore Pointers */
-                /* Restore previously saved data pointer */
-                break;
-
-            case SCSI_MSG_EXTENDED_MESSAGE: { /* 0x01 - Extended message */
-                if (i >= bytes) {
-                    /* Not enough data; let next chunk continue parsing */
-                    i--; /* rewind one to reparse later */
-                    goto out_chunk;
-                }
-                i++; /* skip ext_len */
-
-                if (i >= bytes) {
-                    i -= 2; /* rewind msg + ext_len for next chunk */
-                    goto out_chunk;
-                }
-                uint8_t ext_code = buf[i++];
-
-                switch (ext_code) {
-                case 1: /* SDTR (ignore body) */
-                    /* Body has 2 bytes, may span chunks: skip what we have */
-                    {
-                        int skip = MIN(2, bytes - i);
-                        i += skip;
-                        /*
-                         * If not all skipped this chunk, rest will arrive
-                         * in next loop
-                         */
-                    }
-                    break;
-                case 3: /* WDTR (ignore body) */
-                    if (i < bytes) {
-                        i++; /* skip one param byte if present this chunk */
-                    }
-                    break;
-                default:
-                    goto bad;
-                }
-                break;
-            }
-
-            case 0x20: /* SIMPLE queue tag */
-            case 0x21: /* HEAD of queue tag */
-            case 0x22: /* ORDERED queue tag */
-                if (i < bytes) {
-                    uint8_t tag = buf[i++];
-                    s->select_tag = (s->select_tag & 0xFF00) | tag |
-                                    NCR710_TAG_VALID;
-                    NCR710_DPRINTF("Tagged command: tag=0x%02x, "
-                                   "type=0x%02x\n", tag, msg);
-                } else {
-                    /*
-                     * Tag byte not in this chunk; rewind and reparse
-                     * next loop
-                     */
-                    i--;
-                    goto out_chunk;
-                }
-                break;
-
-            case 0x0d: /* ABORT TAG */
-                if (current_req) {
-                    scsi_req_cancel(current_req->req);
-                }
-                ncr710_disconnect(s);
-                break;
-
-            case SCSI_MSG_ABORT: /* 0x06 - ABORT */
-            case 0x0e: /* CLEAR QUEUE */
-            case SCSI_MSG_BUS_DEVICE_RESET: /* 0x0c - BUS DEVICE RESET */
-                if (s->current) {
-                    scsi_req_cancel(s->current->req);
-                }
-                ncr710_disconnect(s);
-                break;
-
-            default:
-                if (msg & SCSI_MSG_IDENTIFY) {
-                    uint8_t lun = msg & 0x07;
-                    s->current_lun = lun;
-                    ncr710_set_phase(s, PHASE_CO);
-                    break;
-                }
-
-                /* Unknown message - reject it */
-                goto bad;
-            }
-        }
-
-out_chunk:
-        continue;
-    }
-
-    return;
-
-bad:
-    BADF("Unimplemented/Invalid message 0x%02x\n", s->sfbr);
-    ncr710_set_phase(s, PHASE_MI);
-    ncr710_add_msg_byte(s, 7);
-    s->msg_action = NCR710_MSG_ACTION_NONE;
-}
-
-static void ncr710_memcpy(NCR710State *s, uint32_t dest, uint32_t src,
-                          int count)
-{
-    uint8_t buf[NCR710_BUF_SIZE];
-
-    while (count) {
-        int chunk = MIN(count, NCR710_BUF_SIZE);
-        /* Read from source */
-        ncr710_dma_read(s, src, buf, chunk);
-
-        /* Write to destination */
-        ncr710_dma_write(s, dest, buf, chunk);
-
-        src += chunk;
-        dest += chunk;
-        count -= chunk;
-    }
-}
-
-static void ncr710_wait_reselect(NCR710State *s)
-{
-    s->wait_reselect = true;
-    s->waiting = NCR710_WAIT_RESELECT;
-    s->script_active = false;
-
-    s->scntl1 &= ~NCR710_SCNTL1_CON;
-    s->istat &= ~NCR710_ISTAT_CON;
-}
-
-void ncr710_reselection_retry_callback(void *opaque)
-{
-    NCR710State *s = opaque;
-
-    if (!s->current || s->current->pending == 0) {
-        return;
-    }
-
-    if (s->waiting != NCR710_WAIT_RESELECT) {
-        return;
-    }
-
-    if (s->istat & (NCR710_ISTAT_SIP | NCR710_ISTAT_DIP)) {
-        timer_mod(s->reselection_retry_timer,
-                  qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 1000);
-        return;
-    }
-
-    NCR710Request *p = s->current;
-    uint32_t len = p->pending;
-    p->pending = 0;
-
-    SCSIRequest *req = p->req;
-    s->command_complete = NCR710_CMD_PENDING;
-    p->dma_len = len;
-
-    s->scntl1 |= NCR710_SCNTL1_CON;
-    s->istat |= NCR710_ISTAT_CON;
-    s->sbcl = NCR710_SBCL_IO | NCR710_SBCL_CD | NCR710_SBCL_MSG |
-              NCR710_SBCL_BSY | NCR710_SBCL_SEL | NCR710_SBCL_REQ;
-
-    uint8_t host_id = (s->scid & 0x07);
-    if (req->dev->id == 0 && host_id == 0) {
-        s->sfbr = 0x00;
-    } else {
-        s->sfbr = (req->dev->id == 0 ? 0 : (1 << req->dev->id)) |
-                  (host_id == 0 ? 0 : (1 << host_id));
-    }
-
-    ncr710_set_phase(s, PHASE_MI);
-
-    uint8_t identify_msg = 0x80 | (req->lun & 0x07);
-    ncr710_add_msg_byte(s, identify_msg);
-
-    if (p->tag) {
-        ncr710_add_msg_byte(s, 0x20);  /* SIMPLE_TAG_MSG */
-        ncr710_add_msg_byte(s, p->tag & 0xff);
-    }
-
-    s->dsp = p->resume_offset - 8;
-
-    s->dsps = RESELECTED_DURING_SELECTION;
-    s->sstat0 |= NCR710_SSTAT0_SEL;
-    s->istat |= NCR710_ISTAT_SIP;
-    ncr710_update_irq(s);
-    s->waiting = NCR710_WAIT_NONE;
-}
-
-void ncr710_execute_script(NCR710State *s)
-{
-    uint32_t insn;
-    uint32_t addr;
-    int opcode;
-    s->script_active = 1;
-
-again:
-    insn = ncr710_read_dword(s, s->dsp);
-    if (!insn) {
-        /*
-         * If we receive an empty opcode increment the DSP by 4 bytes
-         * and execute the next opcode at that location
-         */
-        s->dsp += 4;
-        goto again;
-    }
-    addr = ncr710_read_dword(s, s->dsp + 4);
-    s->dsps = addr;
-    s->dcmd = insn >> 24;
-    s->dsp += 8;
-    switch (insn >> 30) {
-    case 0: /* Block move.  */
-        if (s->sstat0 & NCR710_SSTAT0_STO) {
-            NCR710_DPRINTF("Delayed select timeout\n");
-            ncr710_stop_script(s);
-            ncr710_update_irq(s);
-            break;
-        }
-        s->dbc = insn & 0xffffff;
-        if (insn & (1 << 29)) {
-            /* Indirect addressing.  */
-            addr = ncr710_read_dword(s, addr);
-        } else if (insn & (1 << 28)) {
-            uint32_t buf[2];
-            int32_t offset;
-            /* Table indirect addressing.  */
-
-            /* 32-bit Table indirect */
-            offset = sextract32(addr, 0, 24);
-            ncr710_dma_read(s, s->dsa + offset, buf, 8);
-            /* byte count is stored in bits 0:23 only */
-            s->dbc = be32_to_cpu(buf[0]) & 0xffffff;
-            addr = be32_to_cpu(buf[1]);
-        }
-        /* Check phase match for block move instructions */
-        if ((s->sstat2 & PHASE_MASK) != ((insn >> 24) & 7)) {
-            uint8_t current_phase = s->sstat2 & PHASE_MASK;
-
-            ncr710_set_phase(s, current_phase);
-            s->sbcl |= NCR710_SBCL_REQ;
-            ncr710_script_scsi_interrupt(s, NCR710_SSTAT0_MA);
-            ncr710_stop_script(s);
-            break;
-        }
-
-        s->dnad = addr;
-        switch (s->sstat2 & 0x7) {
-        case PHASE_DO:
-            s->waiting = NCR710_WAIT_DMA;
-            ncr710_do_dma(s, 1);
-            break;
-        case PHASE_DI:
-            s->waiting = NCR710_WAIT_DMA;
-            ncr710_do_dma(s, 0);
-            break;
-        case PHASE_CO:
-            ncr710_do_command(s);
-            break;
-        case PHASE_SI:
-            ncr710_do_status(s);
-            break;
-        case PHASE_MO:
-            ncr710_do_msgout(s);
-            break;
-        case PHASE_MI:
-            ncr710_do_msgin(s);
-            break;
-        default:
-            BADF("Unimplemented phase %d\n", s->sstat2 & PHASE_MASK);
-        }
-        s->ctest5 = (s->ctest5 & 0xfc) | ((s->dbc >> 8) & 3);
-        s->sbcl = s->dbc;
-        break;
-
-    case 1: /* IO or Read/Write instruction.  */
-        opcode = (insn >> 27) & 7;
-        if (opcode < 5) {
-            uint32_t id;
-
-            if (insn & (1 << 25)) {
-                id = ncr710_read_dword(s, s->dsa + sextract32(insn, 0, 24));
-            } else {
-                id = insn;
-            }
-            id = (id >> 16) & 0xff;
-            if (insn & (1 << 26)) {
-                addr = s->dsp + sextract32(addr, 0, 24);
-            }
-            s->dnad = addr;
-            switch (opcode) {
-            case 0: /* Select */
-                s->sdid = id;
-                if (s->scntl1 & NCR710_SCNTL1_CON) {
-                    if (!(insn & (1 << 24))) {
-                        s->dsp = s->dnad;
-                        break;
-                    }
-                }
-                bool device_exists = false;
-                if (insn & (1 << 24)) {
-                    /* ATN set - scan all LUNs for this target */
-                    for (int lun = 0; lun < 8; lun++) {
-                        SCSIDevice *dev = scsi_device_find(&s->bus, 0,
-                                                           idbitstonum(id),
-                                                           lun);
-                        if (dev) {
-                            device_exists = true;
-                            break;
-                        }
-                    }
-                } else {
-                    /* No ATN - check only LUN 0 */
-                    SCSIDevice *dev = scsi_device_find(&s->bus, 0,
-                                                       idbitstonum(id), 0);
-                    device_exists = dev != NULL;
-                }
-                if (!device_exists) {
-                    ncr710_bad_selection(s, id);
-                    if (!(insn & (1 << 24)) && addr != 0) {
-                        s->dsp = addr;
-                    }
-                    break;
-                } else {
-                    /*
-                     * ??? Linux drivers compain when this is set. Maybe
-                     * it only applies in low-level mode (unimplemented).
-                     * ncr710_script_scsi_interrupt(s, NCR710_SIST0_CMP, 0);
-                     */
-                    s->select_tag = id << 8;
-                    s->scntl1 |= NCR710_SCNTL1_CON;
-
-                    if (insn & (1 << 24)) {
-                        s->socl |= NCR710_SOCL_ATN;
-                        ncr710_set_phase(s, PHASE_MO);
-                    } else {
-                        ncr710_set_phase(s, PHASE_CO);
-                    }
-                }
-                break;
-            case 1: /* Disconnect */
-
-                if (s->command_complete != NCR710_CMD_PENDING) {
-                    s->scntl1 &= ~NCR710_SCNTL1_CON;
-                    s->istat &= ~NCR710_ISTAT_CON;
-                    s->waiting = NCR710_WAIT_NONE;
-                } else {
-                    if (s->current) {
-                        s->current->resume_offset = s->dsp;
-                    }
-
-                    s->waiting = NCR710_WAIT_RESELECT;
-                    ncr710_stop_script(s);
-                    NCR710_DPRINTF("SCRIPTS paused at WAIT DISCONNECT\n");
-                }
-                break;
-            case 2: /* Wait Reselect */
-                if (!ncr710_irq_on_rsl(s)) {
-                    ncr710_wait_reselect(s);
-                }
-                break;
-            case 3: /* Set */
-                if (insn & (1 << 3)) {
-                    s->socl |= NCR710_SOCL_ATN;
-                    ncr710_set_phase(s, PHASE_MO);
-                }
-                if (insn & (1 << 10)) {
-                    s->carry = 1;
-                }
-                break;
-            case 4: /* Clear */
-                if (insn & (1 << 3)) {
-                    s->socl &= ~NCR710_SOCL_ATN;
-                }
-                if (insn & (1 << 10)) {
-                    s->carry = 0;
-                }
-                break;
-            }
-        } else {
-            uint8_t op0;
-            uint8_t op1;
-            uint8_t data8;
-            int reg;
-            int xoperator;
-
-            reg = ((insn >> 16) & 0x7f) | (insn & 0x80);
-            data8 = (insn >> 8) & 0xff;
-            opcode = (insn >> 27) & 7;
-            xoperator = (insn >> 24) & 7;
-            op0 = op1 = 0;
-            switch (opcode) {
-            case 5: /* From SFBR */
-                op0 = s->sfbr;
-                op1 = data8;
-                break;
-            case 6: /* To SFBR */
-                if (xoperator) {
-                    op0 = ncr710_reg_readb(s, reg);
-                }
-                op1 = data8;
-                break;
-            case 7: /* Read-modify-write */
-                if (xoperator) {
-                    op0 = ncr710_reg_readb(s, reg);
-                }
-                if (insn & (1 << 23)) {
-                    op1 = s->sfbr;
-                } else {
-                    op1 = data8;
-                }
-                break;
-            }
-
-            switch (xoperator) {
-            case 0: /* move */
-                op0 = op1;
-                break;
-            case 1: /* Shift left */
-                op1 = op0 >> 7;
-                op0 = (op0 << 1) | s->carry;
-                s->carry = op1;
-                break;
-            case 2: /* OR */
-                op0 |= op1;
-                break;
-            case 3: /* XOR */
-                op0 ^= op1;
-                break;
-            case 4: /* AND */
-                op0 &= op1;
-                break;
-            case 5: /* SHR */
-                op1 = op0 & 1;
-                op0 = (op0 >> 1) | (s->carry << 7);
-                s->carry = op1;
-                break;
-            case 6: /* ADD */
-                op0 += op1;
-                s->carry = op0 < op1;
-                break;
-            case 7: /* ADC */
-                op0 += op1 + s->carry;
-                if (s->carry) {
-                    s->carry = op0 <= op1;
-                } else {
-                    s->carry = op0 < op1;
-                }
-                break;
-            }
-
-            switch (opcode) {
-            case 5: /* From SFBR */
-            case 7: /* Read-modify-write */
-                ncr710_reg_writeb(s, reg, op0);
-                break;
-            case 6: /* To SFBR */
-                s->sfbr = op0;
-                break;
-            }
-        }
-        break;
-
-    case 2: /* Transfer Control.  */
-        {
-            int cond;
-            int jmp;
-
-
-            if (s->sstat0 & NCR710_SSTAT0_STO) {
-                break;
-            }
-            cond = jmp = (insn & (1 << 19)) != 0;
-            if (cond == jmp && (insn & (1 << 21))) {
-                cond = s->carry != 0;
-            }
-            if (cond == jmp && (insn & (1 << 17))) {
-                cond = (s->sstat2 & PHASE_MASK) == ((insn >> 24) & 7);
-            }
-            if (cond == jmp && (insn & (1 << 18))) {
-                uint8_t mask;
-
-                mask = (~insn >> 8) & 0xff;
-                cond = (s->sfbr & mask) == (insn & mask);
-            }
-            if (cond == jmp) {
-                if (insn & (1 << 23)) {
-                    /* Relative address.  */
-                    addr = s->dsp + sextract32(addr, 0, 24);
-                }
-                switch ((insn >> 27) & 7) {
-                case 0: /* Jump */
-                    s->dsp = addr;
-                    break;
-                case 1: /* Call */
-                    s->temp = s->dsp;
-                    s->dsp = addr;
-                    break;
-                case 2: /* Return */
-                    if (s->temp == 0) {
-                        ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID);
-                        break;
-                    }
-                    s->dsp = s->temp;
-                    break;
-                case 3: /* Interrupt */
-                    if ((insn & (1 << 20)) != 0) {
-                        ncr710_update_irq(s);
-                    } else {
-                        if (s->dsps == GOOD_STATUS_AFTER_STATUS) {
-                            NCR710_DPRINTF("Script completion: Processing "
-                                           "GOOD_STATUS_AFTER_STATUS\n");
-                            NCR710_DPRINTF("Script completion: Command state "
-                                           "preserved for driver processing\n");
-                            ncr710_script_dma_interrupt(s,
-                                                        NCR710_DSTAT_SIR);
-                            s->command_complete = NCR710_CMD_PENDING;
-                        } else {
-                            ncr710_script_dma_interrupt(s, NCR710_DSTAT_SIR);
-                        }
-                    }
-                    break;
-                default:
-                    ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID);
-                    break;
-                }
-            }
-        }
-        break;
-
-    case 3:
-        if ((insn & (1 << 29)) == 0) {
-            /* Memory move.  */
-            uint32_t dest;
-            /*
-             * ??? The docs imply the destination address is loaded into
-             * the TEMP register.  However the Linux drivers rely on
-             * the value being preserved.
-             */
-            dest = ncr710_read_dword(s, s->dsp);
-            s->dsp += 4;
-            ncr710_memcpy(s, dest, addr, insn & 0xffffff);
-        } else {
-            uint8_t data[8];
-            int reg;
-            int n;
-            int i;
-            bool dsa_relative = (insn & (1 << 28)) != 0;
-            bool is_load = (insn & (1 << 24)) != 0;
-
-            if (dsa_relative) {
-                addr = s->dsa + sextract32(addr, 0, 24);
-            }
-
-            n = (insn & 7);
-            if (n == 0) {
-                n = 8;  /* 0 means 8 bytes */
-            }
-
-            reg = (insn >> 16) & 0xff;
-
-            if (is_load) {
-                ncr710_dma_read(s, addr, data, n);
-                for (i = 0; i < n; i++) {
-                    ncr710_reg_writeb(s, reg + i, data[i]);
-                }
-            } else {
-                for (i = 0; i < n; i++) {
-                    data[i] = ncr710_reg_readb(s, reg + i);
-                }
-                ncr710_dma_write(s, addr, data, n);
-            }
-        }
-    }
-
-    if (s->script_active && s->waiting == NCR710_WAIT_NONE) {
-        if (s->dcntl & NCR710_DCNTL_SSM) {
-            ncr710_script_dma_interrupt(s, NCR710_DSTAT_SSI);
-            return;
-        } else {
-            goto again;
-        }
-    } else if (s->waiting == NCR710_WAIT_RESELECT) {
-        return;
-    } else if (s->waiting == NCR710_WAIT_DMA ||
-               s->waiting == NCR710_WAIT_RESERVED) {
-        if (s->command_complete == NCR710_CMD_COMPLETE) {
-            s->waiting = NCR710_WAIT_NONE;
-            goto again;
-        }
-        return;
-    }
-}
-
-static uint8_t ncr710_reg_readb(NCR710State *s, int offset)
-{
-    uint8_t ret = 0;
-
-#define CASE_GET_REG24(name, addr) \
-    case addr: \
-        ret = s->name & 0xff; \
-        break; \
-    case addr + 1: \
-        ret = (s->name >> 8) & 0xff; \
-        break; \
-    case addr + 2: \
-        ret = (s->name >> 16) & 0xff; \
-        break;
-
-#define CASE_GET_REG32(name, addr) \
-    case addr: \
-        ret = s->name & 0xff; \
-        break; \
-    case addr + 1: \
-        ret = (s->name >> 8) & 0xff; \
-        break; \
-    case addr + 2: \
-        ret = (s->name >> 16) & 0xff; \
-        break; \
-    case addr + 3: \
-        ret = (s->name >> 24) & 0xff; \
-        break;
-
-    switch (offset) {
-    case NCR710_SCNTL0_REG: /* SCNTL0 */
-        ret = s->scntl0;
-        break;
-    case NCR710_SCNTL1_REG: /* SCNTL1 */
-        ret = s->scntl1;
-        break;
-    case NCR710_SDID_REG: /* SDID */
-        ret = s->sdid;
-        break;
-    case NCR710_SIEN_REG: /* SIEN */
-        ret = s->sien0;
-        break;
-    case NCR710_SCID_REG:
-        ret = s->scid;
-        if ((ret & 0x7F) == 0) {
-            ret = 0x80 | NCR710_HOST_ID;
-        } else {
-            ret |= 0x80;
-        }
-        break;
-    case NCR710_SXFER_REG: /* SXFER */
-        ret = s->sxfer;
-        break;
-    case NCR710_SODL_REG: /* SODL */
-        ret = s->sodl;
-        break;
-    case NCR710_SOCL_REG: /* SOCL */
-        ret = s->socl;
-        break;
-    case NCR710_SFBR_REG: /* SFBR */
-        ret = s->sfbr;
-        break;
-    case NCR710_SIDL_REG: /* SIDL */
-        ret = s->sidl;
-        break;
-    case NCR710_SBDL_REG: /* SBDL */
-        ret = s->sbdl;
-        break;
-    case NCR710_SBCL_REG: /* SBCL */
-        ret = 0;
-        if (s->scntl1 & NCR710_SCNTL1_CON) {
-            ret = s->sstat2 & PHASE_MASK;
-            ret |= s->sbcl;
-            if (s->socl & NCR710_SOCL_ATN) {
-                ret |= NCR710_SBCL_ATN;
-            }
-        }
-        break;
-    case NCR710_DSTAT_REG: /* DSTAT */
-        ret = s->dstat;
-
-        /*
-         * Not freeing s->current here:: driver needs it for completion
-         * processing. It will be freed when the next command starts.
-         */
-        if (s->dstat & NCR710_DSTAT_SIR) {
-            /* SIR bit set */
-        }
-        s->dstat = 0;  /* Clear all DMA interrupt status bits */
-        s->dstat |= NCR710_DSTAT_DFE;
-        ncr710_update_irq(s);
-
-        if (s->waiting == NCR710_WAIT_RESELECT && s->current &&
-            s->current->pending > 0) {
-            timer_mod(s->reselection_retry_timer,
-                      qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL));
-        }
-
-        if (!s->script_active && s->current && s->current->pending > 0 &&
-            s->command_complete == NCR710_CMD_COMPLETE) {
-            s->current->pending = 0;
-            s->waiting = NCR710_WAIT_NONE;
-            ncr710_execute_script(s);
-        }
-
-        if (s->waiting && s->current && s->current->pending > 0 &&
-            s->command_complete == NCR710_CMD_COMPLETE) {
-            s->current->pending = 0;
-            s->waiting = NCR710_WAIT_NONE;
-            ncr710_execute_script(s);
-        }
-
-        return ret;
-    case NCR710_SSTAT0_REG: /* SSTAT0 */
-        ret = s->sstat0;
-        if (s->sstat0 != 0) {
-            s->sstat0 = 0;
-            s->istat &= ~NCR710_ISTAT_SIP;
-            ncr710_update_irq(s);
-            if (s->sbcl != 0) {
-                s->sbcl = 0;
-            }
-        }
-        break;
-    case NCR710_SSTAT1_REG: /* SSTAT1 */
-        ret = s->sstat0;
-        break;
-    case NCR710_SSTAT2_REG: /* SSTAT2 */
-        ret = s->sstat2;
-        break;
-        CASE_GET_REG32(dsa, NCR710_DSA_REG)
-        break;
-    case NCR710_CTEST0_REG: /* CTEST0 */
-        ret = s->ctest0;
-        break;
-    case NCR710_CTEST1_REG: /* CTEST1 */
-        ret = s->ctest1;
-        break;
-    case NCR710_CTEST2_REG: /* CTEST2 */
-        ret = s->ctest2;
-        s->ctest2 |= 0x04;
-        break;
-    case NCR710_CTEST3_REG: /* CTEST3 */
-        ret = s->ctest3;
-        if (!ncr710_scsi_fifo_empty(&s->scsi_fifo)) {
-            uint8_t parity;
-            ret = ncr710_scsi_fifo_dequeue(&s->scsi_fifo, &parity);
-            if (parity) {
-                s->ctest2 |= 0x10;
-            } else {
-                s->ctest2 &= ~0x10;
-            }
-        }
-        break;
-    case NCR710_CTEST4_REG: /* CTEST4 */
-        ret = s->ctest4;
-        break;
-    case NCR710_CTEST5_REG: /* CTEST5 */
-        ret = s->ctest5;
-        break;
-    case NCR710_CTEST6_REG: /* CTEST6 */
-        ret = s->ctest6;
-        break;
-    case NCR710_CTEST7_REG: /* CTEST7 */
-        ret = s->ctest7;
-        break;
-        CASE_GET_REG32(temp, NCR710_TEMP_REG)
-    case NCR710_DFIFO_REG: /* DFIFO */
-        ret = s->dfifo;
-        s->dfifo = 0;  /* DMA FIFO count is always 0 */
-        break;
-    case NCR710_ISTAT_REG: /* ISTAT */
-        ret = s->istat;
-        break;
-    case NCR710_CTEST8_REG: /* CTEST8 */
-        ret = s->istat;
-        break;
-    case NCR710_LCRC_REG: /* LCRC */
-        ret = s->lcrc;
-        break;
-        CASE_GET_REG24(dbc, NCR710_DBC_REG)
-    case NCR710_DCMD_REG: /* DCMD */
-        ret = s->dcmd;
-        break;
-        CASE_GET_REG32(dnad, NCR710_DNAD_REG)
-    case NCR710_DSP_REG:
-        ret = s->dsp & 0xff;
-        break;
-    case NCR710_DSP_REG + 1:
-        ret = (s->dsp >> 8) & 0xff;
-        break;
-    case NCR710_DSP_REG + 2:
-        ret = (s->dsp >> 16) & 0xff;
-        break;
-    case NCR710_DSP_REG + 3:
-        ret = (s->dsp >> 24) & 0xff;
-        if (s->dsps == GOOD_STATUS_AFTER_STATUS &&
-            (s->dstat & NCR710_DSTAT_SIR)) {
-            s->dstat &= ~NCR710_DSTAT_SIR;
-            ncr710_update_irq(s);
-        }
-        break;
-    case NCR710_DSPS_REG:
-        ret = s->dsps & 0xff;
-        break;
-    case NCR710_DSPS_REG + 1:
-        ret = (s->dsps >> 8) & 0xff;
-        break;
-    case NCR710_DSPS_REG + 2:
-        ret = (s->dsps >> 16) & 0xff;
-        break;
-    case NCR710_DSPS_REG + 3:
-        ret = (s->dsps >> 24) & 0xff;
-        if (!(s->dstat & NCR710_DSTAT_SIR) && s->dsps != 0) {
-            s->dsps = 0;
-        }
-        break;
-        CASE_GET_REG32(scratch, NCR710_SCRATCH_REG)
-        break;
-    case NCR710_DMODE_REG: /* DMODE */
-        ret = s->dmode;
-        break;
-    case NCR710_DIEN_REG: /* DIEN */
-        ret = s->dien;
-        break;
-    case NCR710_DWT_REG: /* DWT */
-        ret = s->dwt;
-        break;
-    case NCR710_DCNTL_REG: /* DCNTL */
-        ret = s->dcntl;
-        return ret;
-        CASE_GET_REG32(adder, NCR710_ADDER_REG)
-        break;
-    default:
-        ret = 0;
-        break;
-    }
-
-#undef CASE_GET_REG24
-#undef CASE_GET_REG32
-    return ret;
-}
-
-static void ncr710_reg_writeb(NCR710State *s, int offset, uint8_t val)
-{
-    uint8_t old_val;
-
-#define CASE_SET_REG24(name, addr) \
-    case addr: \
-        s->name &= 0xffffff00; \
-        s->name |= val; \
-        break; \
-    case addr + 1: \
-        s->name &= 0xffff00ff; \
-        s->name |= val << 8; \
-        break; \
-    case addr + 2: \
-        s->name &= 0xff00ffff; \
-        s->name |= val << 16; \
-        break;
-
-#define CASE_SET_REG32(name, addr) \
-    case addr: \
-        s->name &= 0xffffff00; \
-        s->name |= val; \
-        break; \
-    case addr + 1: \
-        s->name &= 0xffff00ff; \
-        s->name |= val << 8; \
-        break; \
-    case addr + 2: \
-        s->name &= 0xff00ffff; \
-        s->name |= val << 16; \
-        break; \
-    case addr + 3: \
-        s->name &= 0x00ffffff; \
-        s->name |= val << 24; \
-        break;
-
-    trace_ncr710_reg_write(ncr710_reg_name(offset), offset, val);
-
-    switch (offset) {
-    case NCR710_SCNTL0_REG: /* SCNTL0 */
-        old_val = s->scntl0;
-        s->scntl0 = val;
-        break;
-
-    case NCR710_SCNTL1_REG: /* SCNTL1 */
-        old_val = s->scntl1;
-        s->scntl1 = val;
-
-
-        /* Handle Assert Even SCSI Parity (AESP) bit changes */
-        if ((val & NCR710_SCNTL1_AESP) != (old_val & NCR710_SCNTL1_AESP)) {
-            trace_ncr710_parity_sense_changed((val & NCR710_SCNTL1_AESP)
-                                               != 0 ? "even" : "odd");
-        }
-
-        if (val & NCR710_SCNTL1_RST) {
-            if (!(s->sstat0 & NCR710_SSTAT0_RST)) {
-                s->sstat0 |= NCR710_SSTAT0_RST;
-                ncr710_script_scsi_interrupt(s, NCR710_SSTAT0_RST);
-            }
-            if (!(old_val & NCR710_SCNTL1_RST)) {
-                NCR710_DPRINTF("NCR710: SCNTL1: SCSI bus reset "
-                               "initiated\n");
-                ncr710_soft_reset(s);
-            }
-        } else {
-            s->sstat0 &= ~NCR710_SSTAT0_RST;
-        }
-        break;
-
-    case NCR710_SDID_REG: /* SDID */
-        s->sdid = val & 0x0F; /* Only lower 4 bits are valid */
-        break;
-
-    case NCR710_SIEN_REG: /* SIEN */
-        s->sien0 = val;
-        NCR710_DPRINTF("SIEN: interrupt mask=0x%02x\n", val);
-        ncr710_update_irq(s);
-        break;
-
-    case NCR710_SCID_REG: /* SCID */
-        s->scid = val;
-        break;
-
-    case NCR710_SXFER_REG: /* SXFER */
-        s->sxfer = val;
-        break;
-
-    case NCR710_SODL_REG: /* SODL */
-        s->sodl = val;
-        s->sstat1 |= NCR710_SSTAT1_ORF; /* SCSI Output Register Full */
-        break;
-
-    case NCR710_SOCL_REG: /* SOCL */
-        s->socl = val;
-        break;
-
-    case NCR710_SFBR_REG: /* SFBR */
-        s->sfbr = val;
-        break;
-
-    case NCR710_SIDL_REG: /* SIDL */
-    case NCR710_SBDL_REG: /* SBDL */
-        break;
-
-    case NCR710_SBCL_REG: /* SBCL */
-        s->sbcl = val;
-        ncr710_set_phase(s, val & PHASE_MASK);
-        break;
-
-    case NCR710_DSTAT_REG:
-    case NCR710_SSTAT0_REG:
-    case NCR710_SSTAT1_REG:
-    case NCR710_SSTAT2_REG:
-        /* Linux writes to these readonly registers on startup */
-        return;
-
-    case NCR710_DSA_REG:
-        s->dsa &= 0xffffff00;
-        s->dsa |= val;
-        break;
-    case NCR710_DSA_REG + 1:
-        s->dsa &= 0xffff00ff;
-        s->dsa |= val << 8;
-        break;
-    case NCR710_DSA_REG + 2:
-        s->dsa &= 0xff00ffff;
-        s->dsa |= val << 16;
-        break;
-    case NCR710_DSA_REG + 3:
-        s->dsa &= 0x00ffffff;
-        s->dsa |= val << 24;
-        break;
-
-    case NCR710_CTEST0_REG: /* CTEST0 */
-        s->ctest0 = val;
-        break;
-
-    case NCR710_CTEST1_REG: /* CTEST1, read-only */
-        s->ctest1 = val;
-        break;
-
-    case NCR710_CTEST2_REG: /* CTEST2, read-only */
-        s->ctest2 = val;
-        break;
-
-    case NCR710_CTEST3_REG: /* CTEST3 */
-        s->ctest3 = val;
-        break;
-
-    case NCR710_CTEST4_REG: /* CTEST4 */
-        s->ctest4 = val;
-        break;
-
-    case NCR710_CTEST5_REG: /* CTEST5 */
-        s->ctest5 = val;
-        break;
-
-    case NCR710_CTEST6_REG: /* CTEST6 */
-        s->ctest6 = val;
-        break;
-
-    case NCR710_CTEST7_REG: /* CTEST7 */
-        s->ctest7 = val;
-        break;
-
-    CASE_SET_REG32(temp, NCR710_TEMP_REG)
-
-    case NCR710_DFIFO_REG: /* DFIFO, read-only */
-        break;
-
-    case NCR710_ISTAT_REG: /* ISTAT */
-        old_val = s->istat;
-
-        if ((old_val & NCR710_ISTAT_DIP) && !(val & NCR710_ISTAT_DIP)) {
-            /* Clear script interrupt data after Linux processes it */
-            s->dstat = 0;
-            s->dsps = 0;
-        }
-
-        if ((old_val & NCR710_ISTAT_SIP) && !(val & NCR710_ISTAT_SIP)) {
-            s->sstat0 = 0;
-        }
-
-        s->istat = (val & ~(NCR710_ISTAT_DIP | NCR710_ISTAT_SIP)) |
-                  (s->istat & (NCR710_ISTAT_DIP | NCR710_ISTAT_SIP));
-        ncr710_update_irq(s);
-
-        if (val & NCR710_ISTAT_ABRT) {
-            ncr710_script_dma_interrupt(s, NCR710_DSTAT_ABRT);
-        }
-        break;
-
-    case NCR710_CTEST8_REG: /* CTEST8 */
-        if (val & 0x08) {
-            s->dstat |= NCR710_DSTAT_DFE;
-        }
-        if (val & 0x04) {
-            ncr710_scsi_fifo_init(&s->scsi_fifo);
-            s->dstat |= NCR710_DSTAT_DFE;
-            s->ctest1 = 0xFF;
-        } else if (s->ctest8 & 0x04) {
-            s->ctest1 = 0x00;
-        }
-        s->ctest8 = val;
-        break;
-    case NCR710_LCRC_REG: /* LCRC */
-        s->lcrc = val;
-        break;
-
-    CASE_SET_REG24(dbc, NCR710_DBC_REG)
-
-    case NCR710_DCMD_REG: /* DCMD */
-        s->dcmd = val;
-        break;
-
-    CASE_SET_REG32(dnad, NCR710_DNAD_REG)
-    case 0x2c: /* DSP[0:7] */
-        s->dsp &= 0xffffff00;
-        s->dsp |= val;
-        break;
-    case 0x2d: /* DSP[8:15] */
-        s->dsp &= 0xffff00ff;
-        s->dsp |= val << 8;
-        break;
-    case 0x2e: /* DSP[16:23] */
-        s->dsp &= 0xff00ffff;
-        s->dsp |= val << 16;
-        break;
-    case 0x2f: /* DSP[24:31] */
-        s->dsp &= 0x00ffffff;
-        s->dsp |= val << 24;
-        s->waiting = NCR710_WAIT_NONE;
-        s->script_active = 1;
-        s->istat |= NCR710_ISTAT_CON;
-        ncr710_clear_selection_timeout(s);
-        ncr710_execute_script(s);
-        break;
-    CASE_SET_REG32(dsps, NCR710_DSPS_REG)
-    CASE_SET_REG32(scratch, NCR710_SCRATCH_REG)
-        break;
-
-    case NCR710_DMODE_REG: /* DMODE */
-        s->dmode = val;
-        break;
-
-    case NCR710_DIEN_REG: /* DIEN */
-        s->dien = val;
-        NCR710_DPRINTF("DIEN: interrupt enable=0x%02x\n", val);
-        ncr710_update_irq(s);
-        break;
-
-    case NCR710_DWT_REG: /* DWT */
-        s->dwt = val;
-        break;
-
-    case NCR710_DCNTL_REG: /* DCNTL */
-        s->dcntl = val & ~(NCR710_DCNTL_PFF);
-        if (val & NCR710_DCNTL_STD) {
-            s->waiting = NCR710_WAIT_NONE;
-            ncr710_execute_script(s);
-            s->dcntl &= ~NCR710_DCNTL_STD;
-        }
-        break;
-
-    CASE_SET_REG32(adder, NCR710_ADDER_REG)
-        break;
-
-    default:
-        break;
-    }
-
-#undef CASE_SET_REG24
-#undef CASE_SET_REG32
-}
-
-/* Memory region wrapper for NCR710 registers */
-uint64_t ncr710_reg_read(void *opaque, hwaddr addr, unsigned size)
-{
-    NCR710State *s = opaque;
-    uint8_t offset = addr & 0xff;
-    uint8_t val = ncr710_reg_readb(s, offset);
-    trace_ncr710_reg_read(ncr710_reg_name(offset), offset, val);
-    return val;
-}
-
-void ncr710_reg_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
-{
-    NCR710State *s = opaque;
-    uint8_t offset = addr & 0xff;
-    uint8_t val8 = val & 0xff;
-    trace_ncr710_reg_write(ncr710_reg_name(offset), offset, val8);
-    ncr710_reg_writeb(s, offset, val8);
-}
-
-/* Device reset */
-static void ncr710_device_reset(DeviceState *dev)
-{
-    SysBusNCR710State *sysbus_dev = SYSBUS_NCR710_SCSI(dev);
-    NCR710State *s = &sysbus_dev->ncr710;
-
-    ncr710_soft_reset(s);
-}
-
-static const struct SCSIBusInfo ncr710_scsi_info = {
-    .tcq = true,
-    .max_target = 8,
-    .max_lun = 8,  /* Full LUN support */
-
-    .transfer_data = ncr710_transfer_data,
-    .complete = ncr710_command_complete,
-    .cancel = ncr710_request_cancelled,
-};
-
-static const MemoryRegionOps ncr710_mmio_ops = {
-    .read = ncr710_reg_read,
-    .write = ncr710_reg_write,
-    .endianness = DEVICE_LITTLE_ENDIAN,
-    .valid = {
-        .min_access_size = 1,
-        .max_access_size = 4,
-    },
-};
-
-static const VMStateDescription vmstate_ncr710_scsi_fifo = {
-    .name = "ncr710_scsi_fifo",
-    .version_id = 1,
-    .minimum_version_id = 1,
-    .fields = (VMStateField[]) {
-        VMSTATE_UINT8_ARRAY(data, NCR710_SCSI_FIFO, NCR710_SCSI_FIFO_SIZE),
-        VMSTATE_UINT8_ARRAY(parity, NCR710_SCSI_FIFO, NCR710_SCSI_FIFO_SIZE),
-        VMSTATE_INT32(count, NCR710_SCSI_FIFO),
-        VMSTATE_END_OF_LIST()
-    }
-};
-
-const VMStateDescription vmstate_ncr710 = {
-    .name = "ncr710",
-    .version_id = 1,
-    .minimum_version_id = 1,
-    .fields = (VMStateField[]) {
-        VMSTATE_UINT8(scntl0, NCR710State),
-        VMSTATE_UINT8(scntl1, NCR710State),
-        VMSTATE_UINT8(sdid, NCR710State),
-        VMSTATE_UINT8(sien0, NCR710State),
-        VMSTATE_UINT8(scid, NCR710State),
-        VMSTATE_UINT8(sxfer, NCR710State),
-        VMSTATE_UINT8(sodl, NCR710State),
-        VMSTATE_UINT8(socl, NCR710State),
-        VMSTATE_UINT8(sfbr, NCR710State),
-        VMSTATE_UINT8(sidl, NCR710State),
-        VMSTATE_UINT8(sbdl, NCR710State),
-        VMSTATE_UINT8(sbcl, NCR710State),
-        VMSTATE_UINT8(dstat, NCR710State),
-        VMSTATE_UINT8(sstat0, NCR710State),
-        VMSTATE_UINT8(sstat1, NCR710State),
-        VMSTATE_UINT8(sstat2, NCR710State),
-        VMSTATE_UINT8(ctest0, NCR710State),
-        VMSTATE_UINT8(ctest1, NCR710State),
-        VMSTATE_UINT8(ctest2, NCR710State),
-        VMSTATE_UINT8(ctest3, NCR710State),
-        VMSTATE_UINT8(ctest4, NCR710State),
-        VMSTATE_UINT8(ctest5, NCR710State),
-        VMSTATE_UINT8(ctest6, NCR710State),
-        VMSTATE_UINT8(ctest7, NCR710State),
-        VMSTATE_UINT8(ctest8, NCR710State),
-        VMSTATE_UINT32(temp, NCR710State),
-        VMSTATE_UINT8(dfifo, NCR710State),
-        VMSTATE_UINT8(istat, NCR710State),
-        VMSTATE_UINT8(lcrc, NCR710State),
-        VMSTATE_UINT8(dcmd, NCR710State),
-        VMSTATE_UINT8(dmode, NCR710State),
-        VMSTATE_UINT8(dien, NCR710State),
-        VMSTATE_UINT8(dwt, NCR710State),
-        VMSTATE_UINT8(dcntl, NCR710State),
-        VMSTATE_UINT32(dsa, NCR710State),
-        VMSTATE_UINT32(dbc, NCR710State),
-        VMSTATE_UINT32(dnad, NCR710State),
-        VMSTATE_UINT32(dsp, NCR710State),
-        VMSTATE_UINT32(dsps, NCR710State),
-        VMSTATE_UINT32(scratch, NCR710State),
-        VMSTATE_UINT32(adder, NCR710State),
-        VMSTATE_STRUCT(scsi_fifo, NCR710State, 1,
-            vmstate_ncr710_scsi_fifo, NCR710_SCSI_FIFO),
-        VMSTATE_UINT8(status, NCR710State),
-        VMSTATE_UINT8_ARRAY(msg, NCR710State,
-            NCR710_MAX_MSGIN_LEN),
-        VMSTATE_UINT8(msg_len, NCR710State),
-        VMSTATE_UINT8(msg_action, NCR710State),
-        VMSTATE_INT32(carry, NCR710State),
-        VMSTATE_BOOL(script_active, NCR710State),
-        VMSTATE_INT32(waiting, NCR710State),
-        VMSTATE_UINT8(command_complete, NCR710State),
-        VMSTATE_UINT32(select_tag, NCR710State),
-        VMSTATE_UINT8(current_lun, NCR710State),
-        VMSTATE_END_OF_LIST()
-    }
-};
-
-static const VMStateDescription vmstate_sysbus_ncr710 = {
-    .name = "sysbus_ncr710",
-    .version_id = 1,
-    .minimum_version_id = 1,
-    .fields = (VMStateField[]) {
-        VMSTATE_STRUCT(ncr710, SysBusNCR710State, 1, vmstate_ncr710,
-                       NCR710State),
-        VMSTATE_END_OF_LIST()
-    }
-};
-
-DeviceState *ncr710_device_create_sysbus(hwaddr addr, qemu_irq irq)
-{
-    DeviceState *dev;
-    SysBusDevice *sysbus;
-
-    dev = qdev_new(TYPE_SYSBUS_NCR710_SCSI);
-    sysbus = SYS_BUS_DEVICE(dev);
-
-    qdev_realize_and_unref(dev, NULL, &error_abort);
-    sysbus_mmio_map(sysbus, 0, addr);
-    sysbus_connect_irq(sysbus, 0, irq);
-    return dev;
-}
-
-DeviceState *ncr53c710_init(MemoryRegion *address_space, hwaddr addr,
-                             qemu_irq irq)
-{
-    DeviceState *dev;
-    SysBusDevice *sysbus;
-    SysBusNCR710State *s;
-
-    /* trace_ncr710_device_init(addr); */
-
-    dev = qdev_new(TYPE_SYSBUS_NCR710_SCSI);
-    sysbus = SYS_BUS_DEVICE(dev);
-
-    qdev_realize_and_unref(dev, NULL, &error_abort);
-    sysbus_mmio_map(sysbus, 0, addr);
-    sysbus_connect_irq(sysbus, 0, irq);
-
-    s = SYSBUS_NCR710_SCSI(dev);
-    if (!s->ncr710.as) {
-        s->ncr710.as = &address_space_memory;
-    }
-
-    return dev;
-}
-
-static void sysbus_ncr710_realize(DeviceState *dev, Error **errp)
-{
-    SysBusNCR710State *s = SYSBUS_NCR710_SCSI(dev);
-
-    trace_ncr710_device_realize();
-    scsi_bus_init(&s->ncr710.bus, sizeof(s->ncr710.bus), dev,
-                  &ncr710_scsi_info);
-    s->ncr710.as = &address_space_memory;
-
-    ncr710_scsi_fifo_init(&s->ncr710.scsi_fifo);
-    s->ncr710.dcntl &= ~NCR710_DCNTL_COM;
-    s->ncr710.scid = 0x80 | NCR710_HOST_ID;
-
-    s->ncr710.reselection_retry_timer =
-        timer_new_ns(QEMU_CLOCK_VIRTUAL,
-                     ncr710_reselection_retry_callback,
-                     &s->ncr710);
-
-    memset(s->ncr710.msg, 0, sizeof(s->ncr710.msg));
-
-    memory_region_init_io(&s->iomem, OBJECT(s), &ncr710_mmio_ops, &s->ncr710,
-                          "ncr710", 0x100);
-    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
-    sysbus_init_irq(SYS_BUS_DEVICE(s), &s->ncr710.irq);
-
-}
-
-static void sysbus_ncr710_init(Object *obj)
-{
-    SysBusNCR710State *s = SYSBUS_NCR710_SCSI(obj);
-    memset(&s->ncr710, 0, sizeof(NCR710State));
-    s->ncr710.ctest0 = 0x01;
-    s->ncr710.scid = 0x80 | NCR710_HOST_ID;
-    s->ncr710.dstat = NCR710_DSTAT_DFE;
-}
-
-static void sysbus_ncr710_class_init(ObjectClass *oc, const void *data)
-{
-    DeviceClass *dc = DEVICE_CLASS(oc);
-
-    dc->realize = sysbus_ncr710_realize;
-    device_class_set_legacy_reset(dc, ncr710_device_reset);
-    dc->bus_type = NULL;
-    set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
-    dc->desc = "NCR53C710 SCSI I/O Processor (SysBus)";
-    dc->vmsd = &vmstate_sysbus_ncr710;
-}
-
-static const TypeInfo sysbus_ncr710_info = {
-    .name = TYPE_SYSBUS_NCR710_SCSI,
-    .parent = TYPE_SYS_BUS_DEVICE,
-    .instance_size = sizeof(SysBusNCR710State),
-    .instance_init = sysbus_ncr710_init,
-    .class_init = sysbus_ncr710_class_init,
-};
-
-static void ncr710_register_types(void)
-{
-    type_register_static(&sysbus_ncr710_info);
-}
-
-type_init(ncr710_register_types)
diff --git a/hw/scsi/ncr53c710.h b/hw/scsi/ncr53c710.h
deleted file mode 100644
index 00b6a01577..0000000000
--- a/hw/scsi/ncr53c710.h
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * QEMU NCR710 SCSI Controller
- *
- * Copyright (c) 2025 Soumyajyotii Ssarkar <soumyajyotisarkar23@gmail.com>
- *
- * NCR710 SCSI Controller implementation
- * Based on the NCR53C710 Technical Manual Version 3.2, December 2000
- *
- * Developed from the hackish implementation of NCR53C710 by Helge Deller
- * which was interim based on the hackish implementation by Toni Wilen for UAE
- *
- * SPDX-License-Identifier: GPL-2.0-or-later
- */
-
-#ifndef HW_NCR53C710_H
-#define HW_NCR53C710_H
-
-#include "hw/core/sysbus.h"
-#include "hw/scsi/scsi.h"
-#include "qemu/fifo8.h"
-#include "qom/object.h"
-#include "system/memory.h"
-#include "hw/core/irq.h"
-#include "qemu/timer.h"
-
-#define TYPE_NCR710_SCSI "ncr710-scsi"
-#define TYPE_SYSBUS_NCR710_SCSI "sysbus-ncr710-scsi"
-
-#define SYSBUS_NCR710_SCSI(obj) \
-    OBJECT_CHECK(SysBusNCR710State, (obj), TYPE_SYSBUS_NCR710_SCSI)
-
-#define ENABLE_DEBUG 0
-#if ENABLE_DEBUG
-#define DBG(x)          x
-#define NCR710_DPRINTF(fmt, ...) \
-    fprintf(stderr, "QEMU: " fmt, ## __VA_ARGS__)
-#define BADF(fmt, ...) \
-    fprintf(stderr, "QEMU: error: " fmt, ## __VA_ARGS__)
-#else
-#define DBG(x)         do { } while (0)
-#define NCR710_DPRINTF(fmt, ...) do { } while (0)
-#define BADF(fmt, ...) do { } while (0)
-#endif
-
-/* NCR710 - Little Endian register Ordering */
-#define NCR710_SCNTL0_REG       0x00    /* SCSI Control Zero */
-#define NCR710_SCNTL1_REG       0x01    /* SCSI Control One */
-#define NCR710_SDID_REG         0x02    /* SCSI Destination ID */
-#define NCR710_SIEN_REG         0x03    /* SCSI Interrupt Enable */
-#define NCR710_SCID_REG         0x04    /* SCSI Chip ID */
-#define NCR710_SXFER_REG        0x05    /* SCSI Transfer */
-#define NCR710_SODL_REG         0x06    /* SCSI Output Data Latch */
-#define NCR710_SOCL_REG         0x07    /* SCSI Output Control Latch */
-#define NCR710_SFBR_REG         0x08    /* SCSI First Byte Received */
-#define NCR710_SIDL_REG         0x09    /* SCSI Input Data Latch */
-#define NCR710_SBDL_REG         0x0A    /* SCSI Bus Data Lines */
-#define NCR710_SBCL_REG         0x0B    /* SCSI Bus Control Lines */
-#define NCR710_DSTAT_REG        0x0C    /* DMA Status */
-#define NCR710_SSTAT0_REG       0x0D    /* SCSI Status Zero */
-#define NCR710_SSTAT1_REG       0x0E    /* SCSI Status One */
-#define NCR710_SSTAT2_REG       0x0F    /* SCSI Status Two */
-#define NCR710_DSA_REG          0x10    /* Data Structure Address */
-#define NCR710_CTEST0_REG       0x14    /* Chip Test Zero */
-#define NCR710_CTEST1_REG       0x15    /* Chip Test One */
-#define NCR710_CTEST2_REG       0x16    /* Chip Test Two */
-#define NCR710_CTEST3_REG       0x17    /* Chip Test Three */
-#define NCR710_CTEST4_REG       0x18    /* Chip Test Four */
-#define NCR710_CTEST5_REG       0x19    /* Chip Test Five */
-#define NCR710_CTEST6_REG       0x1A    /* Chip Test Six */
-#define NCR710_CTEST7_REG       0x1B    /* Chip Test Seven */
-#define NCR710_TEMP_REG         0x1C    /* Temporary Stack */
-#define NCR710_DFIFO_REG        0x20    /* DMA FIFO */
-#define NCR710_ISTAT_REG        0x21    /* Interrupt Status */
-#define NCR710_CTEST8_REG       0x22    /* Chip Test Eight */
-#define NCR710_LCRC_REG         0x23    /* Longitudinal Parity */
-#define NCR710_DBC_REG          0x24    /* DMA Byte Counter (24-bit, LE) */
-#define NCR710_DCMD_REG         0x27    /* DMA Command */
-#define NCR710_DNAD_REG         0x28    /* DMA Next Data Address (32-bit, LE) */
-#define NCR710_DSP_REG          0x2C    /* DMA SCRIPTS Pointer (32-bit, LE) */
-#define NCR710_DSPS_REG         0x30    /* DMA SCRIPTS Pointer Save */
-#define NCR710_SCRATCH_REG      0x34    /* Scratch (32-bit, LE) */
-#define NCR710_DMODE_REG        0x38    /* DMA Mode */
-#define NCR710_DIEN_REG         0x39    /* DMA Interrupt Enable */
-#define NCR710_DWT_REG          0x3A    /* DMA Watchdog Timer */
-#define NCR710_DCNTL_REG        0x3B    /* DMA Control */
-#define NCR710_ADDER_REG        0x3C    /* Adder Sum Output (32-bit, LE) */
-
-#define NCR710_REG_SIZE         0x100
-
-#define NCR710_BUF_SIZE         4096
-#define NCR710_HOST_ID          7
-#define NCR710_MAX_MSGIN_LEN    8
-#define NCR710_SCSI_FIFO_SIZE   8
-
-typedef enum {
-    NCR710_WAIT_NONE = 0,
-    NCR710_WAIT_RESELECT = 1,
-    NCR710_WAIT_DMA = 2,
-    NCR710_WAIT_RESERVED = 3
-} NCR710WaitState;
-
-typedef enum {
-    NCR710_CMD_PENDING = 0,
-    NCR710_CMD_DATA_READY = 1,
-    NCR710_CMD_COMPLETE = 2
-} NCR710CommandState;
-
-typedef enum {
-    NCR710_MSG_ACTION_NONE = 0,
-    NCR710_MSG_ACTION_DISCONNECT = 1,
-    NCR710_MSG_ACTION_DATA_OUT = 2,
-    NCR710_MSG_ACTION_DATA_IN = 3
-} NCR710MessageAction;
-
-typedef struct NCR710State NCR710State;
-typedef struct NCR710Request NCR710Request;
-
-/*
- * SCSI FIFO structure - 8 transfers deep, 1 byte per transfer
- * (9-bit wide with parity)
- */
-typedef struct {
-    uint8_t data[NCR710_SCSI_FIFO_SIZE];
-    uint8_t parity[NCR710_SCSI_FIFO_SIZE];
-    int head;
-    int count;
-} NCR710_SCSI_FIFO;
-
-struct NCR710Request {
-    SCSIRequest *req;
-    uint32_t tag;
-    uint32_t dma_len;
-    uint32_t pending;
-    uint8_t status;
-    bool active;
-    uint8_t *dma_buf;
-    bool out;
-    uint32_t resume_offset;
-    uint32_t saved_dnad;
-};
-
-struct NCR710State {
-    SysBusDevice parent_obj;
-    MemoryRegion mmio;
-    qemu_irq irq;
-
-    SCSIBus bus;
-    AddressSpace *as;
-
-    /* Registers */
-    uint8_t scntl0;
-    uint8_t scntl1;
-    uint8_t sdid;
-    uint8_t sien0;
-    uint8_t scid;
-    uint8_t sxfer;
-    uint8_t sodl;
-    uint8_t socl;
-    uint8_t sfbr;
-    uint8_t sidl;
-    uint8_t sbdl;
-    uint8_t sbcl;
-    uint8_t dstat;
-    uint8_t sstat0;
-    uint8_t sstat1;
-    uint8_t sstat2;
-    uint32_t dsa;
-    uint8_t ctest0;
-    uint8_t ctest1;
-    uint8_t ctest2;
-    uint8_t ctest3;
-    uint8_t ctest4;
-    uint8_t ctest5;
-    uint8_t ctest6;
-    uint8_t ctest7;
-    uint8_t ctest8;
-    uint32_t temp;
-    uint8_t dfifo;
-    uint8_t istat;
-    uint8_t lcrc;
-    uint32_t dbc;
-    uint8_t dcmd;
-    uint32_t dnad;
-    uint32_t dsp;
-    uint32_t dsps;
-    uint32_t scratch;
-    uint8_t dmode;
-    uint8_t dien;
-    uint8_t dwt;
-    uint8_t dcntl;
-    uint32_t adder;
-
-    NCR710_SCSI_FIFO scsi_fifo;
-
-    NCR710Request *current;
-    uint8_t status;
-    uint8_t msg[NCR710_MAX_MSGIN_LEN];
-    uint8_t msg_len;
-    uint8_t msg_action;         /* NCR710MessageAction values */
-    int carry;
-    bool script_active;
-    int32_t waiting;            /* NCR710WaitState values */
-    uint8_t command_complete;   /* NCR710CommandState values */
-
-    QEMUTimer *reselection_retry_timer;
-    uint32_t saved_dsps;
-
-    uint32_t select_tag;
-    uint8_t current_lun;
-    uint8_t reselection_id;
-    bool wait_reselect;
-};
-
-typedef struct SysBusNCR710State {
-    SysBusDevice parent_obj;
-    MemoryRegion mmio;
-    MemoryRegion iomem;
-    qemu_irq irq;
-    NCR710State ncr710;
-} SysBusNCR710State;
-
-static inline NCR710State *ncr710_from_scsi_bus(SCSIBus *bus)
-{
-    return container_of(bus, NCR710State, bus);
-}
-
-static inline SysBusNCR710State *sysbus_from_ncr710(NCR710State *s)
-{
-    return container_of(s, SysBusNCR710State, ncr710);
-}
-
-DeviceState *ncr53c710_init(MemoryRegion *address_space, hwaddr addr,
-                             qemu_irq irq);
-DeviceState *ncr710_device_create_sysbus(hwaddr addr, qemu_irq irq);
-void ncr710_reg_write(void *opaque, hwaddr addr, uint64_t val, unsigned size);
-uint64_t ncr710_reg_read(void *opaque, hwaddr addr, unsigned size);
-void ncr710_soft_reset(NCR710State *s);
-void ncr710_request_cancelled(SCSIRequest *req);
-void ncr710_command_complete(SCSIRequest *req, size_t resid);
-void ncr710_transfer_data(SCSIRequest *req, uint32_t len);
-void ncr710_execute_script(NCR710State *s);
-void ncr710_set_phase(NCR710State *s, int phase);
-void ncr710_reselection_retry_callback(void *opaque);
-extern const VMStateDescription vmstate_ncr710;
-
-#endif /* HW_NCR53C710_H */
-- 
2.54.0



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver
  2026-11-06 22:18 ` [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver Helge Deller
@ 2026-11-08 11:43   ` BALATON Zoltan
  2026-11-08 12:49     ` Helge Deller
  0 siblings, 1 reply; 10+ messages in thread
From: BALATON Zoltan @ 2026-11-08 11:43 UTC (permalink / raw)
  To: Helge Deller
  Cc: Peter Maydell, Keith Monahan, Mark Cave-Ayland, qemu-devel,
	Paolo Bonzini, Helge Deller

On Fri, 6 Nov 2026, Helge Deller wrote:
> From: Helge Deller <deller@gmx.de>
>
> This adds a new driver for the NC53C710 chipset. This drover is based on the
> lsi53c895a driver, and thus avoids many issues of the existing ncr53c710
> driver, which was written from scratch.

There's a typo "drover" above.

I asked this before but would it be possible to modify lsi53c895a.c to 
also emulate the 53c710 too instead of duplicating code? This device model 
is almost the same as the 53c895a or at least a large part is shared but 
it's not easy to see that as it uses different names for the same 
structures and functions. It would be better to at least split the reused 
struct definitions from 53c895a into a shared header and use that here. 
Then compare what are the differences between the functions and see if the 
common parts could either be split into a common super class or if the 
differences are small enough to just add conditionals in lsi53c895a.c to 
also emulate 53c710 with most of the code shared instead of duplicated.

A somewhat complex example of such code sharing is hw/isa/vt82c686.c which 
uses a common super class or hw/display/ati.c which has conditionals to 
emulate simlar devices. I was thinking about such code sharing would be 
possible for these devices too to avoid code duplication that can diverge 
in the future.

Regards,
BALATON Zoltan


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver
  2026-11-08 11:43   ` BALATON Zoltan
@ 2026-11-08 12:49     ` Helge Deller
  2026-11-08 13:01       ` BALATON Zoltan
  0 siblings, 1 reply; 10+ messages in thread
From: Helge Deller @ 2026-11-08 12:49 UTC (permalink / raw)
  To: BALATON Zoltan, Helge Deller
  Cc: Peter Maydell, Keith Monahan, Mark Cave-Ayland, qemu-devel,
	Paolo Bonzini

Hello Balaton,

On 11/8/26 12:43, BALATON Zoltan wrote:
> On Fri, 6 Nov 2026, Helge Deller wrote:
>> From: Helge Deller <deller@gmx.de>
>>
>> This adds a new driver for the NC53C710 chipset. This drover is based on the
>> lsi53c895a driver, and thus avoids many issues of the existing ncr53c710
>> driver, which was written from scratch.
> 
> There's a typo "drover" above.

Thanks! I'll fix it the next round.
  
> I asked this before but would it be possible to modify lsi53c895a.c
> to also emulate the 53c710 too instead of duplicating code?

Maybe. At least in theory: Probably.

> This device model is almost the same as the 53c895a or at least a
> large part is shared but it's not easy to see that as it uses
> different names for the same structures and functions. It would be
> better to at least split the reused struct definitions from 53c895a
> into a shared header and use that here. Then compare what are the
> differences between the functions and see if the common parts could
> either be split into a common super class or if the differences are
> small enough to just add conditionals in lsi53c895a.c to also emulate 
> 53c710 with most of the code shared instead of duplicated.
Well, this is a lot of work.
And I'm not sure if you gain that much from that exercise.
If you are a person which really want to know all differences between
those two SCSI chips then this is most likely a very interesting project.
But I fail to see someone who volunteers for this, who has the time and
interest to really do this and who still guarantees he will not break the lsi53c895a.

Looking at the Linux kernel, there are lots of different drivers driving
all various variants of those SCSI chips. The 710 was one of the earlier
SCSI chips (e.g. ISA bus, no PCI,...) while the 53c895a is a newer chip
for PCI.
> A somewhat complex example of such code sharing is hw/isa/vt82c686.c
> which uses a common super class or hw/display/ati.c which has
> conditionals to emulate simlar devices. I was thinking about such
> code sharing would be possible for these devices too to avoid code
> duplication that can diverge in the future.

Yes, generally I prefer code sharing too.
But in this case the 710 is currently only used by the parisc emulation
(and might be used by one or two old historic non-x86 arches maybe too).

Helge


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver
  2026-11-08 12:49     ` Helge Deller
@ 2026-11-08 13:01       ` BALATON Zoltan
  2026-11-11  3:19         ` Keith Monahan
  0 siblings, 1 reply; 10+ messages in thread
From: BALATON Zoltan @ 2026-11-08 13:01 UTC (permalink / raw)
  To: Helge Deller
  Cc: Helge Deller, Peter Maydell, Keith Monahan, Mark Cave-Ayland,
	qemu-devel, Paolo Bonzini

On Sun, 8 Nov 2026, Helge Deller wrote:
> Hello Balaton,
>
> On 11/8/26 12:43, BALATON Zoltan wrote:
>> On Fri, 6 Nov 2026, Helge Deller wrote:
>>> From: Helge Deller <deller@gmx.de>
>>> 
>>> This adds a new driver for the NC53C710 chipset. This drover is based on 
>>> the
>>> lsi53c895a driver, and thus avoids many issues of the existing ncr53c710
>>> driver, which was written from scratch.
>> 
>> There's a typo "drover" above.
>
> Thanks! I'll fix it the next round.
> 
>> I asked this before but would it be possible to modify lsi53c895a.c
>> to also emulate the 53c710 too instead of duplicating code?
>
> Maybe. At least in theory: Probably.
>
>> This device model is almost the same as the 53c895a or at least a
>> large part is shared but it's not easy to see that as it uses
>> different names for the same structures and functions. It would be
>> better to at least split the reused struct definitions from 53c895a
>> into a shared header and use that here. Then compare what are the
>> differences between the functions and see if the common parts could
>> either be split into a common super class or if the differences are
>> small enough to just add conditionals in lsi53c895a.c to also emulate 
>> 53c710 with most of the code shared instead of duplicated.
> Well, this is a lot of work.
> And I'm not sure if you gain that much from that exercise.

OK but at least using a common header should not be that much work and 
would reduce the differences and make the two device models more easily 
comparable.

> If you are a person which really want to know all differences between
> those two SCSI chips then this is most likely a very interesting project.
> But I fail to see someone who volunteers for this, who has the time and
> interest to really do this and who still guarantees he will not break the 
> lsi53c895a.

I'm not sure what is the state of AI usage discussion but might also be a 
project to try an AI agent with.

> Looking at the Linux kernel, there are lots of different drivers driving
> all various variants of those SCSI chips. The 710 was one of the earlier
> SCSI chips (e.g. ISA bus, no PCI,...) while the 53c895a is a newer chip
> for PCI.

That's a diference in inteface but if the chip otherwise is the same or 
very similar it would be more easily modelled as a super class 
implementing the SCSI things and two subclasses embedding it one ISADevice 
and the other PCIDevice with the parts that's specific to the interface 
belonging to those child classes. That way there would be no code 
duplication.

In any case if there's code duplication then try not to make it worse by 
renaming everthing but keep the names and shared structs at least the same 
so it can be more easily compared and to what's different. That might also 
come in handy when there's a fix in one device that could be applied to 
the other too.

Regards,
BALATON Zoltan

>> A somewhat complex example of such code sharing is hw/isa/vt82c686.c
>> which uses a common super class or hw/display/ati.c which has
>> conditionals to emulate simlar devices. I was thinking about such
>> code sharing would be possible for these devices too to avoid code
>> duplication that can diverge in the future.
>
> Yes, generally I prefer code sharing too.
> But in this case the 710 is currently only used by the parisc emulation
> (and might be used by one or two old historic non-x86 arches maybe too).
>
> Helge
>
>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver
  2026-11-08 13:01       ` BALATON Zoltan
@ 2026-11-11  3:19         ` Keith Monahan
  2026-11-11 13:26           ` BALATON Zoltan
  0 siblings, 1 reply; 10+ messages in thread
From: Keith Monahan @ 2026-11-11  3:19 UTC (permalink / raw)
  To: BALATON Zoltan, Helge Deller
  Cc: Helge Deller, Peter Maydell, Mark Cave-Ayland, qemu-devel,
	Paolo Bonzini

Hi Zoltan,


On 11/8/2026 8:01 AM, BALATON Zoltan wrote:
> On Sun, 8 Nov 2026, Helge Deller wrote:
>> Hello Balaton,
>>
>> On 11/8/26 12:43, BALATON Zoltan wrote:
>>> On Fri, 6 Nov 2026, Helge Deller wrote:
>>>> From: Helge Deller <deller@gmx.de>
>>>>
>>>> This adds a new driver for the NC53C710 chipset. This drover is 
>>>> based on the
>>>> lsi53c895a driver, and thus avoids many issues of the existing 
>>>> ncr53c710
>>>> driver, which was written from scratch.
>>>
>>> There's a typo "drover" above.
>>
>> Thanks! I'll fix it the next round.
>>
>>> I asked this before but would it be possible to modify lsi53c895a.c
>>> to also emulate the 53c710 too instead of duplicating code?
>>
>> Maybe. At least in theory: Probably.
>>
>>> This device model is almost the same as the 53c895a or at least a
>>> large part is shared but it's not easy to see that as it uses
>>> different names for the same structures and functions. It would be
>>> better to at least split the reused struct definitions from 53c895a
>>> into a shared header and use that here. Then compare what are the
>>> differences between the functions and see if the common parts could
>>> either be split into a common super class or if the differences are
>>> small enough to just add conditionals in lsi53c895a.c to also emulate 
>>> 53c710 with most of the code shared instead of duplicated.
>> Well, this is a lot of work.
>> And I'm not sure if you gain that much from that exercise.
> 
> OK but at least using a common header should not be that much work and 
> would reduce the differences and make the two device models more easily 
> comparable.
> 
>> If you are a person which really want to know all differences between
>> those two SCSI chips then this is most likely a very interesting project.
>> But I fail to see someone who volunteers for this, who has the time and
>> interest to really do this and who still guarantees he will not break 
>> the lsi53c895a.
> 
> I'm not sure what is the state of AI usage discussion but might also be 
> a project to try an AI agent with.
> 
>> Looking at the Linux kernel, there are lots of different drivers driving
>> all various variants of those SCSI chips. The 710 was one of the earlier
>> SCSI chips (e.g. ISA bus, no PCI,...) while the 53c895a is a newer chip
>> for PCI.
> 
> That's a diference in inteface but if the chip otherwise is the same or 
> very similar it would be more easily modelled as a super class 
> implementing the SCSI things and two subclasses embedding it one 
> ISADevice and the other PCIDevice with the parts that's specific to the 
> interface belonging to those child classes. That way there would be no 
> code duplication.
> 
> In any case if there's code duplication then try not to make it worse by 
> renaming everthing but keep the names and shared structs at least the 
> same so it can be more easily compared and to what's different. That 
> might also come in handy when there's a fix in one device that could be 
> applied to the other too.


Right now, HP-UX (both 9.05 and 10.20) can't boot at all on the HP 715. 
The new model has been tested extensively, so we're keen to get this 
working for everyone.

You're right that it's hard to see how the two models relate when the 
shared pieces don't line up. Before I start, here is what I understand 
you'll accept for this patch series:

I'll move the shared definitions into a common header:

* request struct
* waiting and message-action enums
* SCSI phase constants
* register-bit defines that are common.

Both drivers will include that header. The 710 driver will use the same 
names as the 895 for the shared definitions. This enables the two to be 
compared side by side, and fixes can be carried between them.

The nice part about this approach is that the generated code on the 895 
would be identical, when compared properly, so there shouldn't be any 
behavior changes to test, beyond basic smoke tests.

And we'll make sure the MAINTAINERS entry is correct, so we get proper 
notification for changes.

This would be the scope of this series: the 710 stays its own device 
model. If the header shows the remaining differences are small, I’m open 
to revisiting a common base class later.

Does this address your concern?

Thanks,
Keith

> 
> Regards,
> BALATON Zoltan
> 
>>> A somewhat complex example of such code sharing is hw/isa/vt82c686.c
>>> which uses a common super class or hw/display/ati.c which has
>>> conditionals to emulate simlar devices. I was thinking about such
>>> code sharing would be possible for these devices too to avoid code
>>> duplication that can diverge in the future.
>>
>> Yes, generally I prefer code sharing too.
>> But in this case the 710 is currently only used by the parisc emulation
>> (and might be used by one or two old historic non-x86 arches maybe too).
>>
>> Helge
>>
>>



^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver
  2026-11-11  3:19         ` Keith Monahan
@ 2026-11-11 13:26           ` BALATON Zoltan
  0 siblings, 0 replies; 10+ messages in thread
From: BALATON Zoltan @ 2026-11-11 13:26 UTC (permalink / raw)
  To: Keith Monahan
  Cc: Helge Deller, Helge Deller, Peter Maydell, Mark Cave-Ayland,
	qemu-devel, Paolo Bonzini

[-- Attachment #1: Type: text/plain, Size: 5795 bytes --]

Hello,

On Tue, 10 Nov 2026, Keith Monahan wrote:
> Hi Zoltan,
>
>
> On 11/8/2026 8:01 AM, BALATON Zoltan wrote:
>> On Sun, 8 Nov 2026, Helge Deller wrote:
>>> Hello Balaton,
>>> 
>>> On 11/8/26 12:43, BALATON Zoltan wrote:
>>>> On Fri, 6 Nov 2026, Helge Deller wrote:
>>>>> From: Helge Deller <deller@gmx.de>
>>>>> 
>>>>> This adds a new driver for the NC53C710 chipset. This drover is based on 
>>>>> the
>>>>> lsi53c895a driver, and thus avoids many issues of the existing ncr53c710
>>>>> driver, which was written from scratch.
>>>> 
>>>> There's a typo "drover" above.
>>> 
>>> Thanks! I'll fix it the next round.
>>> 
>>>> I asked this before but would it be possible to modify lsi53c895a.c
>>>> to also emulate the 53c710 too instead of duplicating code?
>>> 
>>> Maybe. At least in theory: Probably.
>>> 
>>>> This device model is almost the same as the 53c895a or at least a
>>>> large part is shared but it's not easy to see that as it uses
>>>> different names for the same structures and functions. It would be
>>>> better to at least split the reused struct definitions from 53c895a
>>>> into a shared header and use that here. Then compare what are the
>>>> differences between the functions and see if the common parts could
>>>> either be split into a common super class or if the differences are
>>>> small enough to just add conditionals in lsi53c895a.c to also emulate 
>>>> 53c710 with most of the code shared instead of duplicated.
>>> Well, this is a lot of work.
>>> And I'm not sure if you gain that much from that exercise.
>> 
>> OK but at least using a common header should not be that much work and 
>> would reduce the differences and make the two device models more easily 
>> comparable.
>> 
>>> If you are a person which really want to know all differences between
>>> those two SCSI chips then this is most likely a very interesting project.
>>> But I fail to see someone who volunteers for this, who has the time and
>>> interest to really do this and who still guarantees he will not break the 
>>> lsi53c895a.
>> 
>> I'm not sure what is the state of AI usage discussion but might also be a 
>> project to try an AI agent with.
>> 
>>> Looking at the Linux kernel, there are lots of different drivers driving
>>> all various variants of those SCSI chips. The 710 was one of the earlier
>>> SCSI chips (e.g. ISA bus, no PCI,...) while the 53c895a is a newer chip
>>> for PCI.
>> 
>> That's a diference in inteface but if the chip otherwise is the same or 
>> very similar it would be more easily modelled as a super class implementing 
>> the SCSI things and two subclasses embedding it one ISADevice and the other 
>> PCIDevice with the parts that's specific to the interface belonging to 
>> those child classes. That way there would be no code duplication.
>> 
>> In any case if there's code duplication then try not to make it worse by 
>> renaming everthing but keep the names and shared structs at least the same 
>> so it can be more easily compared and to what's different. That might also 
>> come in handy when there's a fix in one device that could be applied to the 
>> other too.
>
>
> Right now, HP-UX (both 9.05 and 10.20) can't boot at all on the HP 715. The 
> new model has been tested extensively, so we're keen to get this working for 
> everyone.
>
> You're right that it's hard to see how the two models relate when the shared 
> pieces don't line up. Before I start, here is what I understand you'll accept 
> for this patch series:
>
> I'll move the shared definitions into a common header:
>
> * request struct
> * waiting and message-action enums
> * SCSI phase constants
> * register-bit defines that are common.
>
> Both drivers will include that header. The 710 driver will use the same names 
> as the 895 for the shared definitions. This enables the two to be compared

Yes, that's what I thought that it's not much additional work and would 
reduce code duplication so at least these shared parts could be split in a 
common header used by both models instead of duplicating everthing in the 
710 model.

You could even keep the same names for static functions and not prefix 
them with ncr710_ in the new model. As these are static they don't clash 
between different files but keeping the same name helps with comparing. 
Although the lsi53c895a.c uses lsi_ prefix and the 710 predates when NCR 
became LSI that probably does not matter much so I'd just keep everyting 
with lsi_ prefix so the common header could be called lsi_scsi.h or 
something like that and note in a comment that NCR chips use the same as 
that's the origin of LSI SCSI. The goal is to make the two models as much 
similar as possible to be easy to see the differences by comparing them.

> side by side, and fixes can be carried between them.
>
> The nice part about this approach is that the generated code on the 895 would 
> be identical, when compared properly, so there shouldn't be any behavior 
> changes to test, beyond basic smoke tests.

Yes, just splitting out the common parts to a header and reusing them 
should not break the 895 and it's easy to review so that should be easily 
doable.

> And we'll make sure the MAINTAINERS entry is correct, so we get proper 
> notification for changes.
>
> This would be the scope of this series: the 710 stays its own device model. 
> If the header shows the remaining differences are small, I’m open to 
> revisiting a common base class later.

Trying to unify the two models more can be done later in separate series 
if somebody wants to, the above common code reuse should make it easier to 
see what would be needed for that and if it's worth going further.

> Does this address your concern?

Yes, I think that's all I wanted to say in my review comments. Thank you.

Regards,
BALATON Zoltan

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-11-11 13:26 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-06 22:18 [PATCH 0/4] [RFC] scsi: New ncr53c710_v2 driver Helge Deller
2026-11-06 22:18 ` [PATCH 1/4] target/hppa: drop dependency on ncr53c710.h Helge Deller
2026-11-06 22:18 ` [PATCH 2/4] ncr53c710_v2: add new NCR53C710 chipset driver Helge Deller
2026-11-08 11:43   ` BALATON Zoltan
2026-11-08 12:49     ` Helge Deller
2026-11-08 13:01       ` BALATON Zoltan
2026-11-11  3:19         ` Keith Monahan
2026-11-11 13:26           ` BALATON Zoltan
2026-11-06 22:18 ` [PATCH 3/4] lasi_ncr710: Wire up the new ncr53c710_v2 driver in LASI Helge Deller
2026-11-06 22:18 ` [PATCH 4/4] hw/scsi: Drop old ncr53c710 v1 driver Helge Deller

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.