From nobody Mon Oct 27 13:50:41 2025 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=kernel.org ARC-Seal: i=1; a=rsa-sha256; t=1761409232; cv=none; d=zohomail.com; s=zohoarc; b=DizEZCxpNiQHxbM/876YRmAHZv1RE7EEw2maUm1HHKIg4/qr5+C5j/+bTlfU9TLugSGntGnPT0bvSxQqcmxQ06sPcmojwUWKnuPGiYLxXH/RrMtmL4Nxp3iPtLT02NpuReEbiv07NQNc81lAKfKwjWVlDzhAym9e6ySpdqxGyso= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1761409232; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=0Leosgi1NxPUBBQafxSJMpxJjHWRvJPMEE4Bz6aUjpk=; b=HYL7QWOySymOiWdK4ctGhqJxKIImKfrn6tMaFD/J3VhEXuSOow5fogqphel6CsDUEgWkM1VOeY7rkYHCJCOkB+sS8hQkocToTWosWrh4/tHYqu99JDzeUkd+XeWS0H13QVnX/HMLJ6izEJO5ipVYV5qGVtk2vyvd4YV3V0AY7NM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1761409232462481.93586356382525; Sat, 25 Oct 2025 09:20:32 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vCgzB-0004nV-OP; Sat, 25 Oct 2025 12:19:21 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vCgz9-0004mp-N6 for qemu-devel@nongnu.org; Sat, 25 Oct 2025 12:19:19 -0400 Received: from tor.source.kernel.org ([2600:3c04:e001:324:0:1991:8:25]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vCgz5-0004AK-AM for qemu-devel@nongnu.org; Sat, 25 Oct 2025 12:19:19 -0400 Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by tor.source.kernel.org (Postfix) with ESMTP id 0808260618; Sat, 25 Oct 2025 16:19:13 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0E578C4CEFB; Sat, 25 Oct 2025 16:19:10 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1761409152; bh=S3la8qt3ZfXL2sQ23lhxe/Njo2i/c+q2L6tnWMuse98=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CUZ5mbOTPUy7lEQGa0L3N3LlATPhgfzWSe0P3uKQ9RGfjPLLtcDqxVQK0PjL/Ld15 oat6HUHg8YvUzydLH8gU6a4FVdIqNCStpXDcJqBkbxmo4nRp1pvxxzbnOvmFIVpRFC ZCc7ikHNVp2Z6XFTNOHOPMSpKbULafquAKSZz5v4kMah/Xr2U0RfOT087vzr+3XvOp GqQJ+JDdSyoA/NnenaiNzUTbVjAjjE/MaYLWfyn56IKJXlc36iclszI7F+ZCNbHzaV b0mFrAFvUeciH0LPG1/rLK480TK7VJgwXiG7ZsgkNJgNmgaxohiaAdpTFfiiTW08sL k2BVvSGS5Sleg== From: deller@kernel.org To: qemu-devel@nongnu.org, Richard Henderson Cc: Fam Zheng , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Soumyajyotii Ssarkar , Helge Deller , Paolo Bonzini Subject: [PATCH v2 03/11] ncr710: Add driver for the NCR 53c710 SCSI chip Date: Sat, 25 Oct 2025 18:18:53 +0200 Message-ID: <20251025161901.32710-4-deller@kernel.org> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20251025161901.32710-1-deller@kernel.org> References: <20251025161901.32710-1-deller@kernel.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2600:3c04:e001:324:0:1991:8:25; envelope-from=deller@kernel.org; helo=tor.source.kernel.org X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @kernel.org) X-ZM-MESSAGEID: 1761409233143154100 Content-Type: text/plain; charset="utf-8" From: Soumyajyotii Ssarkar Add an emulation for the NCR 53c710 SCSI chip. This SCSI chip was used widely in historic machines, e.g. as SCSI core in the LASI controller in HP PA-RISC machines. This driver was developed as part of the Google Summer of Code 2025 program. Signed-off-by: Soumyajyotii Ssarkar Reviewed-by: Helge Deller Signed-off-by: Helge Deller --- MAINTAINERS | 1 + hw/scsi/ncr53c710.c | 2432 ++++++++++++++++++++++++++++++++++++++++++ hw/scsi/ncr53c710.h | 246 +++++ hw/scsi/trace-events | 12 + 4 files changed, 2691 insertions(+) create mode 100644 hw/scsi/ncr53c710.c create mode 100644 hw/scsi/ncr53c710.h diff --git a/MAINTAINERS b/MAINTAINERS index a204c38754..2fee936fb0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1284,6 +1284,7 @@ F: hw/misc/lasi.c F: hw/pci-host/astro.c F: hw/pci-host/dino.c F: hw/scsi/lasi_ncr710.* +F: hw/scsi/ncr53c710.* F: include/hw/input/lasips2.h F: include/hw/misc/lasi.h F: include/hw/net/lasi_82596.h diff --git a/hw/scsi/ncr53c710.c b/hw/scsi/ncr53c710.c new file mode 100644 index 0000000000..ade951b1d1 --- /dev/null +++ b/hw/scsi/ncr53c710.c @@ -0,0 +1,2432 @@ +/* + * QEMU NCR710 SCSI Controller + * + * Copyright (c) 2025 Soumyajyotii Ssarkar + * 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/irq.h" +#include "hw/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) !=3D 0; +} + +static void ncr710_clear_pending_irq(NCR710State *s) +{ + if (s->current) { + if (s->current->req) { + s->current->req->hba_private =3D NULL; + } + ncr710_request_free(s, s->current); + s->current =3D NULL; + } +} + +void ncr710_soft_reset(NCR710State *s) +{ + trace_ncr710_reset(); + s->carry =3D 0; + s->msg_action =3D NCR710_MSG_ACTION_NONE; + s->msg_len =3D 0; + s->waiting =3D NCR710_WAIT_NONE; + s->wait_reselect =3D false; + s->reselection_id =3D 0; + s->dsa =3D 0; + s->dnad =3D 0; + s->dbc =3D 0; + s->temp =3D 0; + s->scratch =3D 0; + s->istat &=3D 0x40; + s->dcmd =3D 0x40; + s->dstat =3D NCR710_DSTAT_DFE; + s->dien =3D 0x04; + s->sien0 =3D 0; + s->ctest2 =3D NCR710_CTEST2_DACK; + s->ctest3 =3D 0; + s->ctest4 =3D 0; + s->ctest5 =3D 0; + s->dsp =3D 0; + s->dsps =3D 0; + s->dmode =3D 0; + s->dcntl =3D 0; + s->scntl0 =3D 0xc0; + s->scntl1 =3D 0; + s->sstat0 =3D 0; + s->sstat1 =3D 0; + s->sstat2 =3D 0; + s->scid =3D 0x80; + s->sxfer =3D 0; + s->socl =3D 0; + s->sdid =3D 0; + s->sbcl =3D 0; + s->sidl =3D 0; + s->sfbr =3D 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 =3D parity8(data); + + if (s->scntl1 & NCR710_SCNTL1_AESP) { + parity =3D !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 =3D ncr710_generate_scsi_parity(s, data); + return parity =3D=3D expected_parity; +} + +static void ncr710_handle_parity_error(NCR710State *s) +{ + s->sstat0 |=3D NCR710_SSTAT0_PAR; + + /* If parity error ATN is enabled, assert ATN */ + if (s->scntl0 & NCR710_SCNTL0_AAP) { + s->socl |=3D 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=3D0), Full (count=3D8) + * + * 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 =3D 0; + fifo->count =3D 0; +} + +static inline bool ncr710_scsi_fifo_empty(NCR710_SCSI_FIFO *fifo) +{ + return fifo->count =3D=3D 0; +} + +static inline bool ncr710_scsi_fifo_full(NCR710_SCSI_FIFO *fifo) +{ + return fifo->count =3D=3D 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 =3D (fifo->head + fifo->count) % NCR710_SCSI_FIFO_SIZE; + fifo->data[tail_pos] =3D data; + fifo->parity[tail_pos] =3D 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 =3D 0; + return 0; /* FIFO empty */ + } + + /* Taking data from the head position */ + data =3D fifo->data[fifo->head]; + *parity =3D fifo->parity[fifo->head]; + fifo->head =3D (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(&address_space_memory, 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 =3D 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(&address_space_memory, addr, MEMTXATTRS_UNSPECIFIED, + buf, len); + NCR710_DPRINTF("Read %d bytes from %08x: ", len, addr); + for (int i =3D 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(&address_space_memory, addr, MEMTXATTRS_UNSPECIFIE= D, + buf, len); + NCR710_DPRINTF("Wrote %d bytes to %08x\n", len, addr); +} + +static void ncr710_stop_script(NCR710State *s) +{ + s->script_active =3D 0; + s->scntl1 &=3D ~NCR710_SCNTL1_CON; + s->istat &=3D ~NCR710_ISTAT_CON; +} + +static void ncr710_update_irq(NCR710State *s) +{ + int level =3D 0; + + if (s->dstat) { + if (s->dstat & s->dien) { + level =3D 1; + } + s->istat |=3D NCR710_ISTAT_DIP; + } else { + s->istat &=3D ~NCR710_ISTAT_DIP; + } + + if (s->sstat0) { + if ((s->sstat0 & s->sien0)) { + level =3D 1; + } + s->istat |=3D NCR710_ISTAT_SIP; + } else { + s->istat &=3D ~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 |=3D stat0; + mask0 =3D stat0 & s->sien0; + if (mask0) { + ncr710_stop_script(s); + s->istat |=3D 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 =3D=3D NCR710_DSTAT_SIR && (s->dstat & NCR710_DSTAT_DFE)) { + s->dstat &=3D ~NCR710_DSTAT_DFE; + } + + s->dstat |=3D stat; + s->istat |=3D NCR710_ISTAT_DIP; + ncr710_update_irq(s); + ncr710_stop_script(s); +} + +inline void ncr710_set_phase(NCR710State *s, int phase) +{ + s->sstat2 =3D (s->sstat2 & ~PHASE_MASK) | phase; + s->ctest0 &=3D ~1; + if (phase =3D=3D PHASE_DI) { + s->ctest0 |=3D 1; + } + s->sbcl &=3D ~NCR710_SBCL_REQ; +} + +static void ncr710_disconnect(NCR710State *s) +{ + trace_ncr710_disconnect(s->waiting); + if (s->waiting =3D=3D NCR710_WAIT_NONE) { + s->scntl1 &=3D ~NCR710_SCNTL1_CON; + s->istat &=3D ~NCR710_ISTAT_CON; + } + s->sstat2 &=3D ~PHASE_MASK; +} + +static void ncr710_bad_selection(NCR710State *s, uint32_t id) +{ + trace_ncr710_bad_selection(id); + s->dstat =3D 0; + s->dsps =3D 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 &=3D ~NCR710_SSTAT0_STO; + ncr710_clear_pending_irq(s); + if (s->sstat0 =3D=3D 0) { + s->istat &=3D ~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 =3D s->current->req->dev; + assert(dev); + + count =3D s->dbc; + if (count > s->current->dma_len) { + count =3D s->current->dma_len; + } + + addr =3D s->dnad; + + s->dnad +=3D count; + s->dbc -=3D count; + if (s->current->dma_buf =3D=3D NULL) { + s->current->dma_buf =3D 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 -=3D count; + if (s->current->dma_len =3D=3D 0) { + s->current->dma_buf =3D NULL; + s->current->pending =3D 0; + scsi_req_continue(s->current->req); + } else { + s->current->dma_buf +=3D count; + s->waiting =3D NCR710_WAIT_NONE; + ncr710_execute_script(s); + } +} + +static void ncr710_add_msg_byte(NCR710State *s, uint8_t data) +{ + if (s->msg_len >=3D NCR710_MAX_MSGIN_LEN) { + BADF("MSG IN data too long\n"); + } else { + s->msg[s->msg_len++] =3D data; + } +} + +static void ncr710_request_free(NCR710State *s, NCR710Request *p) +{ + if (p =3D=3D s->current) { + s->current =3D NULL; + } + g_free(p); +} + +void ncr710_request_cancelled(SCSIRequest *req) +{ + NCR710State *s =3D ncr710_from_scsi_bus(req->bus); + NCR710Request *p =3D (NCR710Request *)req->hba_private; + req->hba_private =3D NULL; + ncr710_request_free(s, p); + scsi_req_unref(req); +} + +static int ncr710_queue_req(NCR710State *s, SCSIRequest *req, uint32_t len) +{ + NCR710Request *p =3D (NCR710Request *)req->hba_private; + + if (!p) { + return -1; + } + p->pending =3D len; + if ((s->waiting =3D=3D 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 =3D p; + return 0; + } else { + s->current =3D p; + return 1; + } +} + +void ncr710_command_complete(SCSIRequest *req, size_t resid) +{ + NCR710State *s =3D ncr710_from_scsi_bus(req->bus); + NCR710Request *p =3D (NCR710Request *)req->hba_private; + + trace_ncr710_command_complete(req->tag, req->status); + + s->lcrc =3D 0; + s->status =3D req->status; + s->command_complete =3D NCR710_CMD_COMPLETE; + + if (p) { + p->pending =3D 0; + } + + ncr710_set_phase(s, PHASE_ST); + + if (req->hba_private =3D=3D s->current) { + scsi_req_unref(req); + } + + if (s->waiting =3D=3D NCR710_WAIT_RESELECT || s->waiting =3D=3D NCR710= _WAIT_DMA) { + s->waiting =3D NCR710_WAIT_NONE; + ncr710_execute_script(s); + } +} + +void ncr710_transfer_data(SCSIRequest *req, uint32_t len) +{ + NCR710State *s =3D ncr710_from_scsi_bus(req->bus); + + assert(req->hba_private); + + if (s->waiting =3D=3D NCR710_WAIT_DMA) { + NCR710Request *p =3D (NCR710Request *)req->hba_private; + if (p) { + p->dma_len =3D len; + } + s->dsp -=3D 8; + s->waiting =3D NCR710_WAIT_NONE; + ncr710_execute_script(s); + return; + } + + if (s->wait_reselect) { + s->current =3D (NCR710Request *)req->hba_private; + s->current->dma_len =3D len; + s->waiting =3D NCR710_WAIT_RESELECT; + } + + if (req->hba_private !=3D s->current || + (ncr710_irq_on_rsl(s) && !(s->scntl1 & NCR710_SCNTL1_CON)) || + s->waiting =3D=3D NCR710_WAIT_RESELECT) { + int queue_result =3D ncr710_queue_req(s, req, len); + if (queue_result !=3D 0) { + return; + } + } + + /* Host adapter (re)connected */ + s->current->dma_len =3D len; + s->command_complete =3D NCR710_CMD_DATA_READY; + + if (!s->current) { + return; + } + + if (s->waiting) { + s->scntl1 |=3D NCR710_SCNTL1_CON; + s->istat |=3D NCR710_ISTAT_CON; + s->sbcl =3D NCR710_SBCL_IO | NCR710_SBCL_CD | NCR710_SBCL_MSG | + NCR710_SBCL_BSY | NCR710_SBCL_SEL | NCR710_SBCL_REQ; + uint8_t host_id =3D (s->scid & 0x07); + + /* Special case: both target and host are ID 0 */ + if (req->dev->id =3D=3D 0 && host_id =3D=3D 0) { + s->sfbr =3D 0x00; + } else { + s->sfbr =3D (req->dev->id =3D=3D 0 ? 0 : (1 << req->dev->id)) | + (host_id =3D=3D 0 ? 0 : (1 << host_id)); + } + + ncr710_set_phase(s, PHASE_MI); + + if (s->current) { + uint8_t identify_msg =3D 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 |=3D NCR710_SSTAT0_SEL; + s->istat |=3D NCR710_ISTAT_SIP; + s->dsps =3D RESELECTED_DURING_SELECTION; + s->waiting =3D 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 =3D 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 =3D MIN(s->dbc, sizeof(buf)); + ncr710_dma_read(s, s->dnad, buf, bytes_read); + + s->dnad +=3D bytes_read; + s->dbc -=3D bytes_read; + s->sfbr =3D buf[0]; + + s->command_complete =3D NCR710_CMD_PENDING; + id =3D (s->select_tag >> 8) & 0xff; + s->lcrc =3D id; + + dev =3D 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 =3D NULL; + } + + s->current =3D g_new0(NCR710Request, 1); + s->current->tag =3D s->select_tag; + s->current->resume_offset =3D 0; + + s->current->req =3D scsi_req_new(dev, s->current->tag, s->current_lun,= buf, + bytes_read, s->current); + n =3D 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 =3D s->status; + uint8_t parity =3D 0; + + if (s->dbc !=3D 1) { + BADF("Bad Status move\n"); + } + s->dbc =3D 1; + s->sfbr =3D status; + + /* Generate parity if enabled and enqueue status byte */ + if (s->scntl0 & NCR710_SCNTL0_EPG) { + parity =3D ncr710_generate_scsi_parity(s, status); + } + ncr710_scsi_fifo_enqueue(&s->scsi_fifo, status, parity); + + /* Dequeue status byte and write to memory */ + status =3D 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 +=3D 1; + s->dbc -=3D 1; + + ncr710_set_phase(s, PHASE_MI); + s->msg_action =3D NCR710_MSG_ACTION_DISCONNECT; + ncr710_add_msg_byte(s, 0); /* COMMAND COMPLETE */ +} + +static void ncr710_do_msgin(NCR710State *s) +{ + int len; + len =3D s->msg_len; + if (len > s->dbc) { + len =3D s->dbc; + } + s->sfbr =3D s->msg[0]; + + for (int i =3D 0; i < len; i++) { + uint8_t parity =3D 0; + if (s->scntl0 & NCR710_SCNTL0_EPG) { + parity =3D 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 =3D 0; i < len; i++) { + uint8_t parity; + buf[i] =3D 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 +=3D len; + s->dbc -=3D len; + s->sidl =3D s->msg[len - 1]; + s->msg_len -=3D 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: + ncr710_disconnect(s); + 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 =3D s->current; + + while (s->dbc > 0) { + int to_move =3D 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 =3D 0; + for (int j =3D 0; j < to_move && + !ncr710_scsi_fifo_full(&s->scsi_fifo); j++) { + uint8_t parity =3D 0; + if (s->scntl0 & NCR710_SCNTL0_EPG) { + parity =3D ncr710_generate_scsi_parity(s, temp_buf[j]); + } + if (ncr710_scsi_fifo_enqueue(&s->scsi_fifo, temp_buf[j], + parity) =3D=3D 0) { + filled++; + } else { + break; + } + } + + if (filled <=3D 0) { + break; + } + uint8_t buf[NCR710_SCSI_FIFO_SIZE]; + int bytes =3D 0; + for (int j =3D 0; j < filled && + !ncr710_scsi_fifo_empty(&s->scsi_fifo); j++) { + uint8_t parity; + buf[bytes] =3D 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 +=3D bytes; + s->dbc -=3D bytes; + int i =3D 0; + while (i < bytes) { + uint8_t msg =3D buf[i++]; + s->sfbr =3D 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 >=3D bytes) { + /* Not enough data; let next chunk continue parsing */ + i--; /* rewind one to reparse later */ + goto out_chunk; + } + i++; /* skip ext_len */ + + if (i >=3D bytes) { + i -=3D 2; /* rewind msg + ext_len for next chunk */ + goto out_chunk; + } + uint8_t ext_code =3D buf[i++]; + + switch (ext_code) { + case 1: /* SDTR (ignore body) */ + /* Body has 2 bytes, may span chunks: skip what we hav= e */ + { + int skip =3D MIN(2, bytes - i); + i +=3D 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 =3D buf[i++]; + s->select_tag =3D (s->select_tag & 0xFF00) | tag | + NCR710_TAG_VALID; + NCR710_DPRINTF("Tagged command: tag=3D0x%02x, " + "type=3D0x%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 =3D msg & 0x07; + s->current_lun =3D 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 =3D 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 =3D 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 +=3D chunk; + dest +=3D chunk; + count -=3D chunk; + } +} + +static void ncr710_wait_reselect(NCR710State *s) +{ + s->wait_reselect =3D true; + s->waiting =3D NCR710_WAIT_RESELECT; + s->script_active =3D false; + + s->scntl1 &=3D ~NCR710_SCNTL1_CON; + s->istat &=3D ~NCR710_ISTAT_CON; +} + +void ncr710_reselection_retry_callback(void *opaque) +{ + NCR710State *s =3D opaque; + + if (!s->current || s->current->pending =3D=3D 0) { + return; + } + + if (s->waiting !=3D 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 =3D s->current; + uint32_t len =3D p->pending; + p->pending =3D 0; + + SCSIRequest *req =3D p->req; + s->command_complete =3D NCR710_CMD_PENDING; + p->dma_len =3D len; + + s->scntl1 |=3D NCR710_SCNTL1_CON; + s->istat |=3D NCR710_ISTAT_CON; + s->sbcl =3D NCR710_SBCL_IO | NCR710_SBCL_CD | NCR710_SBCL_MSG | + NCR710_SBCL_BSY | NCR710_SBCL_SEL | NCR710_SBCL_REQ; + + uint8_t host_id =3D (s->scid & 0x07); + if (req->dev->id =3D=3D 0 && host_id =3D=3D 0) { + s->sfbr =3D 0x00; + } else { + s->sfbr =3D (req->dev->id =3D=3D 0 ? 0 : (1 << req->dev->id)) | + (host_id =3D=3D 0 ? 0 : (1 << host_id)); + } + + ncr710_set_phase(s, PHASE_MI); + + uint8_t identify_msg =3D 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 =3D p->resume_offset - 8; + + s->dsps =3D RESELECTED_DURING_SELECTION; + s->sstat0 |=3D NCR710_SSTAT0_SEL; + s->istat |=3D NCR710_ISTAT_SIP; + ncr710_update_irq(s); + s->waiting =3D NCR710_WAIT_NONE; +} + +void ncr710_execute_script(NCR710State *s) +{ + uint32_t insn; + uint32_t addr; + int opcode; + s->script_active =3D 1; + +again: + insn =3D 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 +=3D 4; + goto again; + } + addr =3D ncr710_read_dword(s, s->dsp + 4); + s->dsps =3D addr; + s->dcmd =3D insn >> 24; + s->dsp +=3D 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 =3D insn & 0xffffff; + if (insn & (1 << 29)) { + /* Indirect addressing. */ + addr =3D 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 =3D sextract32(addr, 0, 24); + ncr710_dma_read(s, s->dsa + offset, buf, 8); + /* byte count is stored in bits 0:23 only */ + s->dbc =3D cpu_to_le32(buf[0]) & 0xffffff; + addr =3D cpu_to_le32(buf[1]); + } + /* Check phase match for block move instructions */ + if ((s->sstat2 & PHASE_MASK) !=3D ((insn >> 24) & 7)) { + uint8_t current_phase =3D s->sstat2 & PHASE_MASK; + + ncr710_set_phase(s, current_phase); + s->sbcl |=3D NCR710_SBCL_REQ; + ncr710_script_scsi_interrupt(s, NCR710_SSTAT0_MA); + ncr710_stop_script(s); + break; + } + + s->dnad =3D addr; + switch (s->sstat2 & 0x7) { + case PHASE_DO: + s->waiting =3D NCR710_WAIT_DMA; + ncr710_do_dma(s, 1); + break; + case PHASE_DI: + s->waiting =3D NCR710_WAIT_DMA; + ncr710_do_dma(s, 0); + if (s->waiting !=3D NCR710_WAIT_NONE) { + /* Async - stop and wait */ + break; + } + /* Sync - continue execution */ + 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 =3D (s->ctest5 & 0xfc) | ((s->dbc >> 8) & 3); + s->sbcl =3D s->dbc; + break; + + case 1: /* IO or Read/Write instruction. */ + opcode =3D (insn >> 27) & 7; + if (opcode < 5) { + uint32_t id; + + if (insn & (1 << 25)) { + id =3D ncr710_read_dword(s, s->dsa + sextract32(insn, 0, 2= 4)); + } else { + id =3D insn; + } + id =3D (id >> 16) & 0xff; + if (insn & (1 << 26)) { + addr =3D s->dsp + sextract32(addr, 0, 24); + } + s->dnad =3D addr; + switch (opcode) { + case 0: /* Select */ + s->sdid =3D id; + if (s->scntl1 & NCR710_SCNTL1_CON) { + if (!(insn & (1 << 24))) { + s->dsp =3D s->dnad; + break; + } + } else if (!scsi_device_find(&s->bus, 0, idbitstonum(id), = 0)) { + ncr710_bad_selection(s, id); + 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 =3D id << 8; + s->scntl1 |=3D NCR710_SCNTL1_CON; + + if (insn & (1 << 24)) { + s->socl |=3D NCR710_SOCL_ATN; + ncr710_set_phase(s, PHASE_MO); + } else { + ncr710_set_phase(s, PHASE_CO); + } + } + break; + case 1: /* Disconnect */ + + if (s->command_complete !=3D NCR710_CMD_PENDING) { + s->scntl1 &=3D ~NCR710_SCNTL1_CON; + s->istat &=3D ~NCR710_ISTAT_CON; + if (s->waiting =3D=3D NCR710_WAIT_RESELECT) { + s->waiting =3D NCR710_WAIT_NONE; + } + } else { + if (s->current) { + s->current->resume_offset =3D s->dsp; + } + + s->waiting =3D 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 |=3D NCR710_SOCL_ATN; + ncr710_set_phase(s, PHASE_MO); + } + if (insn & (1 << 10)) { + s->carry =3D 1; + } + break; + case 4: /* Clear */ + if (insn & (1 << 3)) { + s->socl &=3D ~NCR710_SOCL_ATN; + } + if (insn & (1 << 10)) { + s->carry =3D 0; + } + break; + } + } else { + uint8_t op0; + uint8_t op1; + uint8_t data8; + int reg; + int xoperator; + + reg =3D ((insn >> 16) & 0x7f) | (insn & 0x80); + data8 =3D (insn >> 8) & 0xff; + opcode =3D (insn >> 27) & 7; + xoperator =3D (insn >> 24) & 7; + op0 =3D op1 =3D 0; + switch (opcode) { + case 5: /* From SFBR */ + op0 =3D s->sfbr; + op1 =3D data8; + break; + case 6: /* To SFBR */ + if (xoperator) { + op0 =3D ncr710_reg_readb(s, reg); + } + op1 =3D data8; + break; + case 7: /* Read-modify-write */ + if (xoperator) { + op0 =3D ncr710_reg_readb(s, reg); + } + if (insn & (1 << 23)) { + op1 =3D s->sfbr; + } else { + op1 =3D data8; + } + break; + } + + switch (xoperator) { + case 0: /* move */ + op0 =3D op1; + break; + case 1: /* Shift left */ + op1 =3D op0 >> 7; + op0 =3D (op0 << 1) | s->carry; + s->carry =3D op1; + break; + case 2: /* OR */ + op0 |=3D op1; + break; + case 3: /* XOR */ + op0 ^=3D op1; + break; + case 4: /* AND */ + op0 &=3D op1; + break; + case 5: /* SHR */ + op1 =3D op0 & 1; + op0 =3D (op0 >> 1) | (s->carry << 7); + s->carry =3D op1; + break; + case 6: /* ADD */ + op0 +=3D op1; + s->carry =3D op0 < op1; + break; + case 7: /* ADC */ + op0 +=3D op1 + s->carry; + if (s->carry) { + s->carry =3D op0 <=3D op1; + } else { + s->carry =3D 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 =3D op0; + break; + } + } + break; + + case 2: /* Transfer Control. */ + { + int cond; + int jmp; + + + if (s->sstat0 & NCR710_SSTAT0_STO) { + break; + } + cond =3D jmp =3D (insn & (1 << 19)) !=3D 0; + if (cond =3D=3D jmp && (insn & (1 << 21))) { + cond =3D s->carry !=3D 0; + } + if (cond =3D=3D jmp && (insn & (1 << 17))) { + cond =3D (s->sstat2 & PHASE_MASK) =3D=3D ((insn >> 24) & 7= ); + } + if (cond =3D=3D jmp && (insn & (1 << 18))) { + uint8_t mask; + + mask =3D (~insn >> 8) & 0xff; + cond =3D (s->sfbr & mask) =3D=3D (insn & mask); + } + if (cond =3D=3D jmp) { + if (insn & (1 << 23)) { + /* Relative address. */ + addr =3D s->dsp + sextract32(addr, 0, 24); + } + switch ((insn >> 27) & 7) { + case 0: /* Jump */ + s->dsp =3D addr; + break; + case 1: /* Call */ + s->temp =3D s->dsp; + s->dsp =3D addr; + break; + case 2: /* Return */ + if (s->temp =3D=3D 0) { + ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID); + break; + } + s->dsp =3D s->temp; + break; + case 3: /* Interrupt */ + if ((insn & (1 << 20)) !=3D 0) { + ncr710_update_irq(s); + } else { + if (s->dsps =3D=3D GOOD_STATUS_AFTER_STATUS) { + NCR710_DPRINTF("Script completion: Processing " + "GOOD_STATUS_AFTER_STATUS\n"); + NCR710_DPRINTF("Script completion: Command sta= te " + "preserved for driver processin= g\n"); + ncr710_script_dma_interrupt(s, + NCR710_DSTAT_SIR); + s->command_complete =3D NCR710_CMD_PENDING; + } else { + ncr710_script_dma_interrupt(s, NCR710_DSTAT_SI= R); + } + } + break; + default: + ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID); + break; + } + } + } + break; + + case 3: + if ((insn & (1 << 29)) =3D=3D 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 =3D ncr710_read_dword(s, s->dsp); + s->dsp +=3D 4; + ncr710_memcpy(s, dest, addr, insn & 0xffffff); + } else { + uint8_t data[8]; + int reg; + int n; + int i; + bool dsa_relative =3D (insn & (1 << 28)) !=3D 0; + bool is_load =3D (insn & (1 << 24)) !=3D 0; + + if (dsa_relative) { + addr =3D s->dsa + sextract32(addr, 0, 24); + } + + n =3D (insn & 7); + if (n =3D=3D 0) { + n =3D 8; /* 0 means 8 bytes */ + } + + reg =3D (insn >> 16) & 0xff; + + if (is_load) { + ncr710_dma_read(s, addr, data, n); + for (i =3D 0; i < n; i++) { + ncr710_reg_writeb(s, reg + i, data[i]); + } + } else { + for (i =3D 0; i < n; i++) { + data[i] =3D ncr710_reg_readb(s, reg + i); + } + ncr710_dma_write(s, addr, data, n); + } + } + } + + if (s->script_active && s->waiting =3D=3D 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 =3D=3D NCR710_WAIT_RESELECT) { + return; + } else if (s->waiting =3D=3D NCR710_WAIT_DMA || + s->waiting =3D=3D NCR710_WAIT_RESERVED) { + if (s->command_complete =3D=3D NCR710_CMD_COMPLETE) { + s->waiting =3D NCR710_WAIT_NONE; + goto again; + } + return; + } +} + +static uint8_t ncr710_reg_readb(NCR710State *s, int offset) +{ + uint8_t ret =3D 0; + +#define CASE_GET_REG24(name, addr) \ + case addr: \ + ret =3D s->name & 0xff; \ + break; \ + case addr + 1: \ + ret =3D (s->name >> 8) & 0xff; \ + break; \ + case addr + 2: \ + ret =3D (s->name >> 16) & 0xff; \ + break; + +#define CASE_GET_REG32(name, addr) \ + case addr: \ + ret =3D s->name & 0xff; \ + break; \ + case addr + 1: \ + ret =3D (s->name >> 8) & 0xff; \ + break; \ + case addr + 2: \ + ret =3D (s->name >> 16) & 0xff; \ + break; \ + case addr + 3: \ + ret =3D (s->name >> 24) & 0xff; \ + break; + + switch (offset) { + case NCR710_SCNTL0_REG: /* SCNTL0 */ + ret =3D s->scntl0; + break; + case NCR710_SCNTL1_REG: /* SCNTL1 */ + ret =3D s->scntl1; + break; + case NCR710_SDID_REG: /* SDID */ + ret =3D s->sdid; + break; + case NCR710_SIEN_REG: /* SIEN */ + ret =3D s->sien0; + break; + case NCR710_SCID_REG: + ret =3D s->scid; + if ((ret & 0x7F) =3D=3D 0) { + ret =3D 0x80 | NCR710_HOST_ID; + } else { + ret |=3D 0x80; + } + break; + case NCR710_SXFER_REG: /* SXFER */ + ret =3D s->sxfer; + break; + case NCR710_SODL_REG: /* SODL */ + ret =3D s->sodl; + break; + case NCR710_SOCL_REG: /* SOCL */ + ret =3D s->socl; + break; + case NCR710_SFBR_REG: /* SFBR */ + ret =3D s->sfbr; + break; + case NCR710_SIDL_REG: /* SIDL */ + ret =3D s->sidl; + break; + case NCR710_SBDL_REG: /* SBDL */ + ret =3D s->sbdl; + break; + case NCR710_SBCL_REG: /* SBCL */ + ret =3D 0; + if (s->scntl1 & NCR710_SCNTL1_CON) { + ret =3D s->sstat2 & PHASE_MASK; + ret |=3D s->sbcl; + if (s->socl & NCR710_SOCL_ATN) { + ret |=3D NCR710_SBCL_ATN; + } + } + break; + case NCR710_DSTAT_REG: /* DSTAT */ + ret =3D 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 =3D 0; /* Clear all DMA interrupt status bits */ + s->dstat |=3D NCR710_DSTAT_DFE; + s->istat &=3D ~NCR710_ISTAT_DIP; + ncr710_update_irq(s); + + if (s->waiting =3D=3D 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 =3D=3D NCR710_CMD_COMPLETE) { + s->current->pending =3D 0; + s->waiting =3D NCR710_WAIT_NONE; + ncr710_execute_script(s); + } + + if (s->waiting && s->current && s->current->pending > 0 && + s->command_complete =3D=3D NCR710_CMD_COMPLETE) { + s->current->pending =3D 0; + s->waiting =3D NCR710_WAIT_NONE; + ncr710_execute_script(s); + } + + return ret; + case NCR710_SSTAT0_REG: /* SSTAT0 */ + ret =3D s->sstat0; + if (s->sstat0 !=3D 0 && !(s->sstat0 & NCR710_SSTAT0_STO)) { + s->sstat0 =3D 0; + s->istat &=3D ~NCR710_ISTAT_SIP; + ncr710_update_irq(s); + if (s->sbcl !=3D 0) { + s->sbcl =3D 0; + } + } + break; + case NCR710_SSTAT1_REG: /* SSTAT1 */ + ret =3D s->sstat0; + break; + case NCR710_SSTAT2_REG: /* SSTAT2 */ + ret =3D s->dstat; + + if (s->dstat & NCR710_DSTAT_SIR) { + /* SIR bit processing */ + } + s->dstat =3D 0; + s->istat &=3D ~NCR710_ISTAT_DIP; + ncr710_update_irq(s); + break; + CASE_GET_REG32(dsa, NCR710_DSA_REG) + break; + case NCR710_CTEST0_REG: /* CTEST0 */ + ret =3D s->ctest0; + break; + case NCR710_CTEST1_REG: /* CTEST1 */ + ret =3D s->ctest1; + break; + case NCR710_CTEST2_REG: /* CTEST2 */ + ret =3D s->ctest2; + s->ctest2 |=3D 0x04; + break; + case NCR710_CTEST3_REG: /* CTEST3 */ + ret =3D s->ctest3; + if (!ncr710_scsi_fifo_empty(&s->scsi_fifo)) { + uint8_t parity; + ret =3D ncr710_scsi_fifo_dequeue(&s->scsi_fifo, &parity); + if (parity) { + s->ctest2 |=3D 0x10; + } else { + s->ctest2 &=3D ~0x10; + } + } + break; + case NCR710_CTEST4_REG: /* CTEST4 */ + ret =3D s->ctest4; + break; + case NCR710_CTEST5_REG: /* CTEST5 */ + ret =3D s->ctest5; + break; + case NCR710_CTEST6_REG: /* CTEST6 */ + ret =3D s->ctest6; + break; + case NCR710_CTEST7_REG: /* CTEST7 */ + ret =3D s->ctest7; + break; + CASE_GET_REG32(temp, NCR710_TEMP_REG) + case NCR710_DFIFO_REG: /* DFIFO */ + ret =3D s->dfifo; + s->dfifo =3D 0; /* DMA FIFO count is always 0 */ + break; + case NCR710_ISTAT_REG: /* ISTAT */ + ret =3D s->istat; + break; + case NCR710_CTEST8_REG: /* CTEST8 */ + ret =3D s->istat; + break; + case NCR710_LCRC_REG: /* LCRC */ + ret =3D s->lcrc; + break; + CASE_GET_REG24(dbc, NCR710_DBC_REG) + case NCR710_DCMD_REG: /* DCMD */ + ret =3D s->dcmd; + break; + CASE_GET_REG32(dnad, NCR710_DNAD_REG) + case NCR710_DSP_REG: + ret =3D s->dsp & 0xff; + break; + case NCR710_DSP_REG + 1: + ret =3D (s->dsp >> 8) & 0xff; + break; + case NCR710_DSP_REG + 2: + ret =3D (s->dsp >> 16) & 0xff; + break; + case NCR710_DSP_REG + 3: + ret =3D (s->dsp >> 24) & 0xff; + if (s->dsps =3D=3D GOOD_STATUS_AFTER_STATUS && + (s->dstat & NCR710_DSTAT_SIR)) { + s->dstat &=3D ~NCR710_DSTAT_SIR; + s->istat &=3D ~NCR710_ISTAT_DIP; + ncr710_update_irq(s); + } + break; + case NCR710_DSPS_REG: + ret =3D s->dsps & 0xff; + break; + case NCR710_DSPS_REG + 1: + ret =3D (s->dsps >> 8) & 0xff; + break; + case NCR710_DSPS_REG + 2: + ret =3D (s->dsps >> 16) & 0xff; + break; + case NCR710_DSPS_REG + 3: + ret =3D (s->dsps >> 24) & 0xff; + if (!(s->dstat & NCR710_DSTAT_SIR) && s->dsps !=3D 0) { + s->dsps =3D 0; + } + break; + CASE_GET_REG32(scratch, NCR710_SCRATCH_REG) + break; + case NCR710_DMODE_REG: /* DMODE */ + ret =3D s->dmode; + break; + case NCR710_DIEN_REG: /* DIEN */ + ret =3D s->dien; + break; + case NCR710_DWT_REG: /* DWT */ + ret =3D s->dwt; + break; + case NCR710_DCNTL_REG: /* DCNTL */ + ret =3D s->dcntl; + return ret; + CASE_GET_REG32(adder, NCR710_ADDER_REG) + break; + default: + ret =3D 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 &=3D 0xffffff00; \ + s->name |=3D val; \ + break; \ + case addr + 1: \ + s->name &=3D 0xffff00ff; \ + s->name |=3D val << 8; \ + break; \ + case addr + 2: \ + s->name &=3D 0xff00ffff; \ + s->name |=3D val << 16; \ + break; + +#define CASE_SET_REG32(name, addr) \ + case addr: \ + s->name &=3D 0xffffff00; \ + s->name |=3D val; \ + break; \ + case addr + 1: \ + s->name &=3D 0xffff00ff; \ + s->name |=3D val << 8; \ + break; \ + case addr + 2: \ + s->name &=3D 0xff00ffff; \ + s->name |=3D val << 16; \ + break; \ + case addr + 3: \ + s->name &=3D 0x00ffffff; \ + s->name |=3D val << 24; \ + break; + + trace_ncr710_reg_write(ncr710_reg_name(offset), offset, val); + + switch (offset) { + case NCR710_SCNTL0_REG: /* SCNTL0 */ + old_val =3D s->scntl0; + s->scntl0 =3D val; + break; + + case NCR710_SCNTL1_REG: /* SCNTL1 */ + old_val =3D s->scntl1; + s->scntl1 =3D val; + + + /* Handle Assert Even SCSI Parity (AESP) bit changes */ + if ((val & NCR710_SCNTL1_AESP) !=3D (old_val & NCR710_SCNTL1_AESP)= ) { + trace_ncr710_parity_sense_changed((val & NCR710_SCNTL1_AESP) + !=3D 0 ? "even" : "odd"); + } + + if (val & NCR710_SCNTL1_RST) { + if (!(s->sstat0 & NCR710_SSTAT0_RST)) { + s->sstat0 |=3D 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 &=3D ~NCR710_SSTAT0_RST; + } + break; + + case NCR710_SDID_REG: /* SDID */ + s->sdid =3D val & 0x0F; /* Only lower 4 bits are valid */ + break; + + case NCR710_SIEN_REG: /* SIEN */ + s->sien0 =3D val; + NCR710_DPRINTF("SIEN: interrupt mask=3D0x%02x\n", val); + ncr710_update_irq(s); + break; + + case NCR710_SCID_REG: /* SCID */ + s->scid =3D val; + break; + + case NCR710_SXFER_REG: /* SXFER */ + s->sxfer =3D val; + break; + + case NCR710_SODL_REG: /* SODL */ + s->sodl =3D val; + s->sstat1 |=3D NCR710_SSTAT1_ORF; /* SCSI Output Register Full */ + break; + + case NCR710_SOCL_REG: /* SOCL */ + s->socl =3D val; + break; + + case NCR710_SFBR_REG: /* SFBR */ + s->sfbr =3D val; + break; + + case NCR710_SIDL_REG: /* SIDL */ + case NCR710_SBDL_REG: /* SBDL */ + break; + + case NCR710_SBCL_REG: /* SBCL */ + s->sbcl =3D 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_SET_REG32(dsa, NCR710_DSA_REG) + break; + + case NCR710_CTEST0_REG: /* CTEST0 */ + s->ctest0 =3D val; + break; + + case NCR710_CTEST1_REG: /* CTEST1, read-only */ + s->ctest1 =3D val; + break; + + case NCR710_CTEST2_REG: /* CTEST2, read-only */ + s->ctest2 =3D val; + break; + + case NCR710_CTEST3_REG: /* CTEST3 */ + s->ctest3 =3D val; + break; + + case NCR710_CTEST4_REG: /* CTEST4 */ + s->ctest4 =3D val; + break; + + case NCR710_CTEST5_REG: /* CTEST5 */ + s->ctest5 =3D val; + break; + + case NCR710_CTEST6_REG: /* CTEST6 */ + s->ctest6 =3D val; + break; + + case NCR710_CTEST7_REG: /* CTEST7 */ + s->ctest7 =3D val; + break; + + CASE_SET_REG32(temp, NCR710_TEMP_REG) + + case NCR710_DFIFO_REG: /* DFIFO, read-only */ + break; + + case NCR710_ISTAT_REG: /* ISTAT */ + old_val =3D s->istat; + + if ((old_val & NCR710_ISTAT_DIP) && !(val & NCR710_ISTAT_DIP)) { + /* Clear script interrupt data after Linux processes it */ + s->dstat =3D 0; + s->dsps =3D 0; + } + + if ((old_val & NCR710_ISTAT_SIP) && !(val & NCR710_ISTAT_SIP)) { + s->sstat0 =3D 0; + } + + s->istat =3D (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 |=3D NCR710_DSTAT_DFE; + } + if (val & 0x04) { + ncr710_scsi_fifo_init(&s->scsi_fifo); + s->dstat |=3D NCR710_DSTAT_DFE; + } + break; + case NCR710_LCRC_REG: /* LCRC */ + s->lcrc =3D val; + break; + + CASE_SET_REG24(dbc, NCR710_DBC_REG) + + case NCR710_DCMD_REG: /* DCMD */ + s->dcmd =3D val; + break; + + CASE_SET_REG32(dnad, NCR710_DNAD_REG) + case 0x2c: /* DSP[0:7] */ + s->dsp &=3D 0xffffff00; + s->dsp |=3D val; + break; + case 0x2d: /* DSP[8:15] */ + s->dsp &=3D 0xffff00ff; + s->dsp |=3D val << 8; + break; + case 0x2e: /* DSP[16:23] */ + s->dsp &=3D 0xff00ffff; + s->dsp |=3D val << 16; + break; + case 0x2f: /* DSP[24:31] */ + s->dsp &=3D 0x00ffffff; + s->dsp |=3D val << 24; + s->waiting =3D NCR710_WAIT_NONE; + s->script_active =3D 1; + s->istat |=3D 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 =3D val; + break; + + case NCR710_DIEN_REG: /* DIEN */ + s->dien =3D val; + NCR710_DPRINTF("DIEN: interrupt enable=3D0x%02x\n", val); + ncr710_update_irq(s); + break; + + case NCR710_DWT_REG: /* DWT */ + s->dwt =3D val; + break; + + case NCR710_DCNTL_REG: /* DCNTL */ + s->dcntl =3D val & ~(NCR710_DCNTL_PFF); + if (val & NCR710_DCNTL_STD) { + s->waiting =3D NCR710_WAIT_NONE; + ncr710_execute_script(s); + s->dcntl &=3D ~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 =3D opaque; + uint8_t offset =3D addr & 0xff; + uint8_t val =3D 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 si= ze) +{ + NCR710State *s =3D opaque; + uint8_t offset =3D addr & 0xff; + uint8_t val8 =3D 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 =3D SYSBUS_NCR710_SCSI(dev); + NCR710State *s =3D &sysbus_dev->ncr710; + + ncr710_soft_reset(s); +} + +static const struct SCSIBusInfo ncr710_scsi_info =3D { + .tcq =3D true, + .max_target =3D 8, + .max_lun =3D 8, /* Full LUN support */ + + .transfer_data =3D ncr710_transfer_data, + .complete =3D ncr710_command_complete, + .cancel =3D ncr710_request_cancelled, +}; + +static const MemoryRegionOps ncr710_mmio_ops =3D { + .read =3D ncr710_reg_read, + .write =3D ncr710_reg_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, +}; + +static const VMStateDescription vmstate_ncr710_scsi_fifo =3D { + .name =3D "ncr710_scsi_fifo", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_UINT8_ARRAY(data, NCR710_SCSI_FIFO, NCR710_SCSI_FIFO_SIZE), + VMSTATE_UINT8_ARRAY(parity, NCR710_SCSI_FIFO, NCR710_SCSI_FIFO_SIZ= E), + VMSTATE_INT32(count, NCR710_SCSI_FIFO), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_ncr710 =3D { + .name =3D "ncr710", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D { + .name =3D "sysbus_ncr710", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D qdev_new(TYPE_SYSBUS_NCR710_SCSI); + sysbus =3D 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 =3D qdev_new(TYPE_SYSBUS_NCR710_SCSI); + sysbus =3D 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 =3D SYSBUS_NCR710_SCSI(dev); + if (!s->ncr710.as) { + s->ncr710.as =3D &address_space_memory; + } + + return dev; +} + +static void sysbus_ncr710_realize(DeviceState *dev, Error **errp) +{ + SysBusNCR710State *s =3D 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 =3D &address_space_memory; + + ncr710_scsi_fifo_init(&s->ncr710.scsi_fifo); + s->ncr710.dcntl &=3D ~NCR710_DCNTL_COM; + s->ncr710.scid =3D 0x80 | NCR710_HOST_ID; + + s->ncr710.reselection_retry_timer =3D + 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->ncr7= 10, + "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 =3D SYSBUS_NCR710_SCSI(obj); + memset(&s->ncr710, 0, sizeof(NCR710State)); + s->ncr710.ctest0 =3D 0x01; + s->ncr710.scid =3D 0x80 | NCR710_HOST_ID; + s->ncr710.dstat =3D NCR710_DSTAT_DFE; +} + +static void sysbus_ncr710_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(oc); + + dc->realize =3D sysbus_ncr710_realize; + device_class_set_legacy_reset(dc, ncr710_device_reset); + dc->bus_type =3D NULL; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); + dc->desc =3D "NCR53C710 SCSI I/O Processor (SysBus)"; + dc->vmsd =3D &vmstate_sysbus_ncr710; +} + +static const TypeInfo sysbus_ncr710_info =3D { + .name =3D TYPE_SYSBUS_NCR710_SCSI, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(SysBusNCR710State), + .instance_init =3D sysbus_ncr710_init, + .class_init =3D 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 new file mode 100644 index 0000000000..380e3959b3 --- /dev/null +++ b/hw/scsi/ncr53c710.h @@ -0,0 +1,246 @@ +/* + * QEMU NCR710 SCSI Controller + * + * Copyright (c) 2025 Soumyajyotii Ssarkar + * + * 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 "qemu/osdep.h" +#include "hw/sysbus.h" +#include "hw/scsi/scsi.h" +#include "qemu/fifo8.h" +#include "qom/object.h" +#include "system/memory.h" +#include "hw/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 =3D 0, + NCR710_WAIT_RESELECT =3D 1, + NCR710_WAIT_DMA =3D 2, + NCR710_WAIT_RESERVED =3D 3 +} NCR710WaitState; + +typedef enum { + NCR710_CMD_PENDING =3D 0, + NCR710_CMD_DATA_READY =3D 1, + NCR710_CMD_COMPLETE =3D 2 +} NCR710CommandState; + +typedef enum { + NCR710_MSG_ACTION_NONE =3D 0, + NCR710_MSG_ACTION_DISCONNECT =3D 1, + NCR710_MSG_ACTION_DATA_OUT =3D 2, + NCR710_MSG_ACTION_DATA_IN =3D 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 si= ze); +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); + +#endif /* HW_NCR53C710_H */ diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events index 0604050a67..3e81f44dad 100644 --- a/hw/scsi/trace-events +++ b/hw/scsi/trace-events @@ -306,6 +306,18 @@ lsi_reg_write(const char *name, int offset, uint8_t va= l) "Write reg %s 0x%x =3D 0x lsi_scripts_timer_triggered(void) "SCRIPTS timer triggered" lsi_scripts_timer_start(void) "SCRIPTS timer started" =20 +# ncr53c710.c +ncr710_reset(void) "Reset" +ncr710_reg_read(const char *name, int offset, uint8_t ret) "Read %s [0x%02= x] =3D 0x%02x" +ncr710_reg_write(const char *name, int offset, uint8_t val) "Write %s [0x%= 02x] =3D 0x%02x" +ncr710_script_scsi_interrupt(uint8_t stat0, uint8_t sstat0) "SCSI interrup= t stat=3D0x%02x sstat=3D0x%02x" +ncr710_script_dma_interrupt(uint8_t stat, uint8_t dstat) "DMA interrupt st= at=3D0x%02x dstat=3D0x%02x" +ncr710_command_complete(uint32_t tag, uint8_t status) "tag=3D0x%x status= =3D0x%02x" +ncr710_disconnect(uint8_t waiting) "waiting=3D%d" +ncr710_bad_selection(uint32_t target) "target=3D%d" +ncr710_parity_sense_changed(const char *parity) "Parity sense changed to %= s" +ncr710_device_realize(void) "Device realized" + # lasi_ncr710.c lasi_ncr710_device_realize(void) "Device realized" lasi_ncr710_device_reset(void) "Device reset" --=20 2.51.0