From nobody Sun Jul 26 11:07:24 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) header.from=techtravels.org ARC-Seal: i=1; a=rsa-sha256; t=1782941699; cv=none; d=zohomail.com; s=zohoarc; b=efo74m9yhn+NvINkuavfoJPzJhh3gFNJyD41hAZGXhZZTLsaTUWD/CiI4gOfVCufZhfVYY/R+8WdtElBJTupBzM/ydmaTcsLEQtymYHgHr9MHW+bcutSWPNlnmN8ueB11R0hu8Kh1a8nhYbyPY0CB6e7LRBFAcgtw48iRzDVwik= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1782941699; 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=6TyIQGe5nKgG8efVYuK4A2vSVU/zotj9XIBU1Sl9kvk=; b=ezvOKVwPEl30bohWybZUG5btbu4UwTIwnw6B2SeZtmgOU+oYf9zlQYMfElkXvquM6z9ifdSt08eTWzU6i6VW3Pa9UBHXgSkc94Xuagp9JglcFvS3qYhfvyX3Rtp3kHM5RLvZ15LMWsauhCu+m4tysclNTm7cYtx7KQnIqmG9F1M= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1782941699327948.0253668122178; Wed, 1 Jul 2026 14:34:59 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wf2Za-0007tT-8b; Wed, 01 Jul 2026 17:34:22 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2ZY-0007ss-7H for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:34:20 -0400 Received: from a4i502.smtp2go.com ([158.120.81.246]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2ZW-00089C-NL for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:34:19 -0400 Received: from [10.91.249.253] (helo=antecquad..) by smtpcorp.com with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.99.4) (envelope-from ) id 1wf2ZF-FnQW0hPscrZ-GnN3; Wed, 01 Jul 2026 21:34:01 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=techtravels.org; i=@techtravels.org; q=dns/txt; s=s1175899; t=1782941644; h=from : subject : to : message-id : date; bh=6TyIQGe5nKgG8efVYuK4A2vSVU/zotj9XIBU1Sl9kvk=; b=URggQX4M92mSB1GNlX9d7bGDeymWnrNGKcqso2zXstKHjn4JzCpP3ULJGw8L2KfvwI9J0 nMfqu3ZO4rQuwquQHos8Km4mcnUWhY+HxDu6ZT89Vv/tQZJvXHo87wqDOm36LLRQn/dTV8R w+uj2rUV7w8w+8iNculzvrnAEMObX4pM0onAiQbDNDCy/1KdwZgiGFrYGJWYODvXm1VE9df OULdoAbJm4VOOFLubgFZBq7rF/rn+aqa/UD89SRnuTugsfpaPyWHaYtlMcoV9pWzEJCSRqt V+OSA25c21ieXhSnrRXLFKtoPVWrAlWxxmTotFwhnqcqa1ult3xJpPGWCYuw== From: Keith Monahan To: qemu-devel@nongnu.org Cc: Richard Henderson , Helge Deller , Paolo Bonzini , Fam Zheng , Peter Maydell , BALATON Zoltan , Keith Monahan Subject: [PATCH v2 1/4] hw/misc/lasi: derive IRR from pending and unmasked requests Date: Wed, 1 Jul 2026 17:33:56 -0400 Message-ID: <20260701213359.1855870-2-keith@techtravels.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260701213359.1855870-1-keith@techtravels.org> References: <20260701213359.1855870-1-keith@techtravels.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Report-Abuse: Please forward a copy of this message, including all headers, to Feedback-ID: 1175899m:1175899aL0uvZj:1175899sBq7-A2oFb X-smtpcorp-track: 4V0NyJAWXDYc.Tue5cxI5_PbL.hohf3QgT23G 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=lists1p.gnu.org; Received-SPF: pass client-ip=158.120.81.246; envelope-from=bounce.12z04cv3btw88n0=vpz22ppphfu9=9e2hb48lqb2g1v@em1175899.techtravels.org; helo=a4i502.smtp2go.com 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, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-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: qemu development 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 @techtravels.org) X-ZM-MESSAGEID: 1782941701429158500 Content-Type: text/plain; charset="utf-8" The LASI interrupt request register (IRR) was latched: set when a source asserted and then never cleared or re-evaluated against the mask, so a masked or dropped request stayed set forever. The parisc core I/O dispatcher reads IRR to find its interrupt source, so the stuck bit was returned on every later interrupt as an "unexpected core I/O interrupt". On an installed HP-UX system an unacknowledged i82596 LAN interrupt latched this way and the flood wedged the boot. Derive IRR as (pending & unmasked) with IPR tracking each source's level, matching the hardware and the in-tree parisc gsc/lasi driver. Signed-off-by: Keith Monahan Reviewed-by: Helge Deller --- hw/misc/lasi.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/hw/misc/lasi.c b/hw/misc/lasi.c index e8e1578b75..f0dfd78b4d 100644 --- a/hw/misc/lasi.c +++ b/hw/misc/lasi.c @@ -62,7 +62,15 @@ static MemTxResult lasi_chip_read_with_attrs(void *opaqu= e, hwaddr addr, =20 switch (addr) { case LASI_IRR: - val =3D s->irr; + /* + * The interrupt request register reports the interrupts that are = both + * pending and unmasked, derived live from IPR and IMR rather than + * latched, so masking or deasserting a source removes it immediat= ely. + * The parisc core I/O interrupt dispatcher reads IRR; a latched b= it + * that never cleared would be redelivered forever as a phantom + * "unexpected" interrupt. + */ + val =3D s->ipr & s->imr; break; case LASI_IMR: val =3D s->imr; @@ -234,13 +242,18 @@ static void lasi_set_irq(void *opaque, int irq, int l= evel) =20 if (level) { s->ipr |=3D bit; - if (bit & s->imr) { + if ((bit & s->imr) && (s->icr & ICR_BUS_ERROR_BIT) =3D=3D 0) { uint32_t iar =3D s->iar; - s->irr |=3D bit; - if ((s->icr & ICR_BUS_ERROR_BIT) =3D=3D 0) { - stl_be_phys(&address_space_memory, iar & -32, iar & 31); - } + stl_be_phys(&address_space_memory, iar & -32, iar & 31); } + } else { + /* + * The interrupt sources are level triggered, so a source that dro= ps + * its request must clear its pending bit. Otherwise the bit stay= s set + * in IPR (and hence IRR) and is redelivered as a phantom "unexpec= ted" + * core I/O interrupt on every later interrupt. + */ + s->ipr &=3D ~bit; } } =20 --=20 2.43.0 From nobody Sun Jul 26 11:07:24 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) header.from=techtravels.org ARC-Seal: i=1; a=rsa-sha256; t=1782941709; cv=none; d=zohomail.com; s=zohoarc; b=OwMiSb0h/7XtPu0h0V/n9aIfRkXMGHcSAECNtEM7fcRESaq6i6CL0xGNGjuTNEXueELyLBsMMpT12LADftE5ewJhGNsfu44zukXxDxegsmQ6oQP1G3UwYAyVTGo6n0jfXHjzNOSq8lJMqUO/QRJECjG4e5hf7YhvjLOHch88QOU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1782941709; 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=E1uDVsDHSyU28kuJ6GKslw1fWCMQ19ySF7S2/j1aX/8=; b=SZPlMO3aAx3Rf8ZFpRdDbSHpiwx398a8Mf1Xu463g1yOIsrr1golxEnqqJi31TCYpF+pzb1bwRVdYNlPpsHQIgEKYnIggaO1JvURgc2FJK5ffW/mU6fMzxyDJkTnklG/1XUfkWVZopVLUzDbCohZJOgn4BfOPcdxzf6VsrLCYcI= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1782941709505586.520025053451; Wed, 1 Jul 2026 14:35:09 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wf2ZZ-0007tQ-Ae; Wed, 01 Jul 2026 17:34:21 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2ZY-0007st-7C for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:34:20 -0400 Received: from a4i502.smtp2go.com ([158.120.81.246]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2ZW-00089i-Rp for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:34:19 -0400 Received: from [10.91.249.253] (helo=antecquad..) by smtpcorp.com with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.99.4) (envelope-from ) id 1wf2ZF-FnQW0hPscrZ-HRh9; Wed, 01 Jul 2026 21:34:01 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=techtravels.org; i=@techtravels.org; q=dns/txt; s=s1175899; t=1782941651; h=from : subject : to : message-id : date; bh=E1uDVsDHSyU28kuJ6GKslw1fWCMQ19ySF7S2/j1aX/8=; b=EmTtTOnmFJmvnqCN2t7jlS8F59DPaD1GuQi2JNlxNWqPZMq/j56VebZbTQWocLkXsvIkJ 73FYFfB99Thp18I8AZqFJGNdkiOmf8wv/jytd30W3kfjjYH6KyU68yH3yCnWuYd6iQwqVU0 Tavu9vtAgYR2ySlrry4KlYta4UAadM0Y15xW7bMohG1GFpj/C2KFRYzTRH2IJn5dV/0Sqo4 KwD5Ebzl8JId2Qr1j/dwFf+AleITIxip3Utuc2bV4wmE80RElTKeeZZSWBFfILbh1KoyrAR ZJ9s4yQyNyCDYn1EBZDV6QAVAptwzh42SWy+1u87fAf6nm24oEY2qynVqPsw== From: Keith Monahan To: qemu-devel@nongnu.org Cc: Richard Henderson , Helge Deller , Paolo Bonzini , Fam Zheng , Peter Maydell , BALATON Zoltan , Keith Monahan Subject: [PATCH v2 2/4] hw/scsi/lasi_ncr710: remove dead and shadowing defines Date: Wed, 1 Jul 2026 17:33:57 -0400 Message-ID: <20260701213359.1855870-3-keith@techtravels.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260701213359.1855870-1-keith@techtravels.org> References: <20260701213359.1855870-1-keith@techtravels.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Report-Abuse: Please forward a copy of this message, including all headers, to Feedback-ID: 1175899m:1175899aL0uvZj:1175899smeRLutvCC X-smtpcorp-track: HcToV7P5WeQB.KgATQeTdDhTp.6qrqcQrKYSI 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=lists1p.gnu.org; Received-SPF: pass client-ip=158.120.81.246; envelope-from=bounce.9yoaq2aauad66j2=d0e2tgnx3a05=42zs6ymhmgdkzt@em1175899.techtravels.org; helo=a4i502.smtp2go.com 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, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-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: qemu development 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 @techtravels.org) X-ZM-MESSAGEID: 1782941710857158500 Content-Type: text/plain; charset="utf-8" Drop unused register and phase defines that duplicate the authoritative definitions in ncr53c710.h (LASI_SCSI_RESET, PHASE_MASK, NCR710_SCNTL1_RST and friends), and the leftover SCNR debug constant. None are referenced. Signed-off-by: Keith Monahan --- hw/scsi/lasi_ncr710.c | 1 - hw/scsi/lasi_ncr710.h | 16 ---------------- 2 files changed, 17 deletions(-) diff --git a/hw/scsi/lasi_ncr710.c b/hw/scsi/lasi_ncr710.c index 4fde2265b5..3fccda9e4e 100644 --- a/hw/scsi/lasi_ncr710.c +++ b/hw/scsi/lasi_ncr710.c @@ -24,7 +24,6 @@ #include "system/dma.h" =20 #define LASI_710_SVERSION 0x00082 -#define SCNR 0xBEEFBABE #define LASI_710_HVERSION 0x3D #define HPHW_FIO 5 /* Fixed I/O module */ =20 diff --git a/hw/scsi/lasi_ncr710.h b/hw/scsi/lasi_ncr710.h index 450fb7e1c3..75d3e77d20 100644 --- a/hw/scsi/lasi_ncr710.h +++ b/hw/scsi/lasi_ncr710.h @@ -22,22 +22,6 @@ #define TYPE_LASI_NCR710 "lasi-ncr710" OBJECT_DECLARE_SIMPLE_TYPE(LasiNCR710State, LASI_NCR710) =20 -#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; --=20 2.43.0 From nobody Sun Jul 26 11:07:24 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) header.from=techtravels.org ARC-Seal: i=1; a=rsa-sha256; t=1782942648; cv=none; d=zohomail.com; s=zohoarc; b=BLlHoyVPNzpNdrpSgLOb5UF/3r1W8kJnKpg8iKN5WQR1TH6cPUr3beQ5tGjwul1IbPqKgp7J2uMIpHkIZeAkNquFgXXUh+m21BABkDDCwSORqPWz97gngRiGqMZ8kyLOFfbr56S0H2/TlyGG+vsqhS8xfQRI/NW/7CTbunxD8Q0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1782942648; 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=MW8yZmJqJ5pYqjFPIyO1oKcB84WtLBS73behSsiSJ+Y=; b=X12gNJfvHhKWH65PIQ9x6wbNi0DxLHKzwTRl7+yBpmPjCXufGwdI/c4sbrPm1qsCwQKh3Htte2F91d36M4mtEC4G7xF727ALlTxYEZ8X5+fb8JfbR0CLhSHKezmXl/Y7kYygNVhtfZ2yQ/qDi9759L/DulGjwd1V0F7S9PH31+0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1782942648075772.1896005479297; Wed, 1 Jul 2026 14:50:48 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wf2og-00021K-Mx; Wed, 01 Jul 2026 17:49:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2of-00021C-Fk for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:49:57 -0400 Received: from a4i502.smtp2go.com ([158.120.81.246]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2oa-0003qr-Kp for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:49:57 -0400 Received: from [10.34.231.120] (helo=SmtpCorp) by smtpcorp.com with esmtpsa (TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.99.4) (envelope-from ) id 1wf2ZF-3Quys43026b-s8SU; Wed, 01 Jul 2026 21:34:02 +0000 Received: from [10.91.249.253] (helo=antecquad..) by smtpcorp.com with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.99.4) (envelope-from ) id 1wf2ZF-FnQW0hPscrZ-IG9q; Wed, 01 Jul 2026 21:34:01 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=techtravels.org; i=@techtravels.org; q=dns/txt; s=s1175899; t=1782942586; h=from : subject : to : message-id : date; bh=MW8yZmJqJ5pYqjFPIyO1oKcB84WtLBS73behSsiSJ+Y=; b=iMBF1atWVM3+EANgvF85VyCcRxydAisvb1AbHRaDGCiBLFatQuz1m+OCsBJyhRZvqCwN1 VUhSyYt9PRzz1z7oyUqg4ET4c51HOrSB/nYh9qV35JhhECFOUDCtmei2m1wzvPDyYOnbmPZ 1CSHTk0djoaNlaxLzYlkYU/aNw3MO3uUlffNbyRG7IBomTwU+3VP/FWSugh3cEGA+kGjfFH 6kCcane+FmblEZxV9wz7qH2BmCLIdCvGNGKqg0dfZD+RfxEEUWCNwWdyAViRlkFk5SauYJZ /pQqoLFEDzxNutk7sU7L5Bz3LDrKrKPHv49/uTKG5neCfimkzGwAhj3AyIKg== From: Keith Monahan To: qemu-devel@nongnu.org Cc: Richard Henderson , Helge Deller , Paolo Bonzini , Fam Zheng , Peter Maydell , BALATON Zoltan , Keith Monahan Subject: [PATCH v2 3/4] hw/scsi/ncr53c710: rewrite the SCRIPTS engine Date: Wed, 1 Jul 2026 17:33:58 -0400 Message-ID: <20260701213359.1855870-4-keith@techtravels.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260701213359.1855870-1-keith@techtravels.org> References: <20260701213359.1855870-1-keith@techtravels.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Report-Abuse: Please forward a copy of this message, including all headers, to Feedback-ID: 1175899m:1175899aL0uvZj:1175899s6lrrQhD21 X-smtpcorp-track: -evCBtQB5Hku.9S3ZC5Lawpgx.wjy8Qgg8zc- 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=lists1p.gnu.org; Received-SPF: pass client-ip=158.120.81.246; envelope-from=bounce.29ayzrlrb9zog09=08qme0rmr6xv=h6s9gcnzkpt68q@em1175899.techtravels.org; helo=a4i502.smtp2go.com 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, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-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: qemu development 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 @techtravels.org) X-ZM-MESSAGEID: 1782942650936158500 Content-Type: text/plain; charset="utf-8" Replace the NCR 53C710 model with a SCRIPTS engine derived from QEMU's LSI53C895A model (hw/scsi/lsi53c895a.c), another device in the same NCR/LSI SCRIPTS family, adapted to the 53C710: its register map, the single-byte DSTAT/SSTAT0 interrupt model, big-endian SCRIPTS and table-indirect fetch (the part sits on the big-endian PA-RISC LASI bus), and 24-bit DMA byte counts. Behaviour follows the NCR 53C710 Data Manual (Jun 1992) and Programmer's Guide (Oct 1990). The base engine supports connected I/O and reselection-interrupt disconnect, enough to install and boot Debian off the 710 with the Linux lasi700/53c700 driver and to boot NetBSD/hppa 9.4 and 10.1 (osiop) with disk and CD-ROM enumeration. Reselection follows the 53C700 family: gated on the (re)selection interrupt enable (SIEN.SEL) alone, one-hot SDID, and an SFBR id bitmask in 700-compatibility mode. SDTR/WDTR/PPR negotiation is declined with MESSAGE REJECT, which the 700-family drivers require to proceed: unlike the 8xx parts they leave the target to drive the phase after the reject. The LASI glue (hw/scsi/lasi_ncr710.c) embeds one NCR710State by value. It gains the free_request SCSIBusInfo callback and request-queue init the core needs, renames the reselection timer to scripts_timer, and narrows the SCSI bus to the 8-bit inclusive maxima. The migration format is incompatible with the model this replaces, so the vmstate version and minimum version are both raised. Signed-off-by: Keith Monahan --- hw/scsi/lasi_ncr710.c | 117 +- hw/scsi/lasi_ncr710.h | 12 +- hw/scsi/ncr53c710.c | 2795 +++++++++++++++-------------------------- hw/scsi/ncr53c710.h | 276 ++-- hw/scsi/trace-events | 43 +- 5 files changed, 1191 insertions(+), 2052 deletions(-) diff --git a/hw/scsi/lasi_ncr710.c b/hw/scsi/lasi_ncr710.c index 3fccda9e4e..bca2695104 100644 --- a/hw/scsi/lasi_ncr710.c +++ b/hw/scsi/lasi_ncr710.c @@ -1,12 +1,12 @@ /* - * LASI Wrapper for NCR710 SCSI Controller + * LASI wrapper for the NCR 53C710 SCSI controller * * Copyright (c) 2025 Soumyajyotii Ssarkar - * This driver was developed during the Google Summer of Code 2025 program. - * Mentored by Helge Deller + * Developed during Google Summer of Code 2025, mentored by + * Helge Deller . * - * 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 */ @@ -33,8 +33,6 @@ static uint64_t lasi_ncr710_reg_read(void *opaque, hwaddr= addr, LasiNCR710State *s =3D LASI_NCR710(opaque); uint64_t val =3D 0; =20 - trace_lasi_ncr710_reg_read(addr, 0, size); - if (addr =3D=3D 0x00) { /* Device ID */ val =3D (HPHW_FIO << 24) | LASI_710_SVERSION; trace_lasi_ncr710_reg_read_id(HPHW_FIO, LASI_710_SVERSION, val); @@ -50,22 +48,22 @@ static uint64_t lasi_ncr710_reg_read(void *opaque, hwad= dr addr, if (addr >=3D 0x100) { hwaddr ncr_addr =3D addr - 0x100; if (size =3D=3D 1) { + /* + * Single byte access: flip the byte lane (PA-RISC big endian + * access -> the chip's little endian register order). + */ ncr_addr ^=3D 3; - NCR710_DPRINTF("Reading value to LASI WRAPPER =3D=3D 0x%lx%s, " - "val=3D0x%lx, size=3D%u\n", - addr - 0x100, size =3D=3D 1 ? " (XORed)" : "", - val, size); val =3D 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 =3D 0; for (unsigned i =3D 0; i < size; i++) { uint8_t byte_val =3D ncr710_reg_read(&s->ncr710, ncr_addr = + i, 1); val |=3D ((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); } =20 trace_lasi_ncr710_reg_forward_read(addr, val); @@ -83,7 +81,13 @@ static void lasi_ncr710_reg_write(void *opaque, hwaddr a= ddr, =20 trace_lasi_ncr710_reg_write(addr, val, size); =20 - if (addr <=3D 0x0F) { + /* + * 0x00..0x0f is the read only LASI module header (ID/SVERSION/HVERSIO= N) and + * 0x10..0xff is unmapped module space; writes to both are ignored. T= he + * SCSI bus reset is driven through the chip's SCNTL1.RST at forwarded + * offset 0x101 (see ncr53c710.c), not through this window. + */ + if (addr <=3D 0x0f) { return; } =20 @@ -92,16 +96,10 @@ static void lasi_ncr710_reg_write(void *opaque, hwaddr = addr, =20 if (size =3D=3D 1) { ncr_addr ^=3D 3; - NCR710_DPRINTF("Writing value to LASI WRAPPER =3D=3D 0x%lx%s, " - "val=3D0x%lx, size=3D%u\n", - addr - 0x100, size =3D=3D 1 ? " (XORed)" : "", - val, size); ncr710_reg_write(&s->ncr710, ncr_addr, val, size); } else { for (unsigned i =3D 0; i < size; i++) { uint8_t byte_val =3D (val >> (i * 8)) & 0xff; - NCR710_DPRINTF(" Writing byte %u to NCR addr 0x%lx: 0x%0= 2x\n", - i, ncr_addr + i, byte_val); ncr710_reg_write(&s->ncr710, ncr_addr + i, byte_val, 1); } } @@ -112,10 +110,7 @@ static void lasi_ncr710_reg_write(void *opaque, hwaddr= addr, } } =20 -/* - * 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); @@ -128,20 +123,27 @@ static void lasi_ncr710_command_complete(SCSIRequest = *req, size_t resid) ncr710_command_complete(req, resid); } =20 - 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); } =20 +static void lasi_ncr710_free_request(SCSIBus *bus, void *priv) +{ + g_free(priv); +} + static const struct SCSIBusInfo lasi_ncr710_scsi_info =3D { .tcq =3D true, - .max_target =3D 8, - .max_lun =3D 8, /* full LUN support */ + /* 8-bit bus: targets and LUNs 0-7 (scsi-bus.c maxima are inclusive). = */ + .max_target =3D 7, + .max_lun =3D 7, =20 .transfer_data =3D lasi_ncr710_transfer_data, .complete =3D lasi_ncr710_command_complete, .cancel =3D lasi_ncr710_request_cancelled, + .free_request =3D lasi_ncr710_free_request, }; =20 static const MemoryRegionOps lasi_ncr710_mmio_ops =3D { @@ -156,12 +158,9 @@ static const MemoryRegionOps lasi_ncr710_mmio_ops =3D { =20 static const VMStateDescription vmstate_lasi_ncr710 =3D { .name =3D "lasi-ncr710", - .version_id =3D 1, - .minimum_version_id =3D 1, + .version_id =3D 2, + .minimum_version_id =3D 2, .fields =3D (const VMStateField[]) { - VMSTATE_UINT32(hw_type, LasiNCR710State), - VMSTATE_UINT32(sversion, LasiNCR710State), - VMSTATE_UINT32(hversion, LasiNCR710State), VMSTATE_STRUCT(ncr710, LasiNCR710State, 1, vmstate_ncr710, NCR710S= tate), VMSTATE_END_OF_LIST() } @@ -178,49 +177,36 @@ static void lasi_ncr710_realize(DeviceState *dev, Err= or **errp) &lasi_ncr710_scsi_info); s->ncr710.as =3D &address_space_memory; s->ncr710.irq =3D s->lasi_irq; + QTAILQ_INIT(&s->ncr710.queue); =20 - s->ncr710.reselection_retry_timer =3D + s->ncr710.scripts_timer =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, - ncr710_reselection_retry_callback, + ncr710_scripts_timer_callback, &s->ncr710); =20 ncr710_soft_reset(&s->ncr710); =20 trace_lasi_ncr710_timers_initialized( - (uint64_t)s->ncr710.reselection_retry_timer); + (uint64_t)s->ncr710.scripts_timer); =20 - /* 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); } =20 -void lasi_ncr710_handle_legacy_cmdline(DeviceState *lasi_dev) +static void lasi_ncr710_unrealize(DeviceState *dev) { - LasiNCR710State *s =3D LASI_NCR710(lasi_dev); - SCSIBus *bus =3D &s->ncr710.bus; - int found_drives =3D 0; - - if (!bus) { - return; - } + LasiNCR710State *s =3D LASI_NCR710(dev); =20 - for (int unit =3D 0; unit <=3D 7; unit++) { - DriveInfo *dinfo =3D 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 =3D NULL; +} =20 - trace_lasi_ncr710_handle_legacy_cmdline(bus->busnr, found_drives); +void lasi_ncr710_handle_legacy_cmdline(DeviceState *lasi_dev) +{ + LasiNCR710State *s =3D LASI_NCR710(lasi_dev); =20 - 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); } =20 DeviceState *lasi_ncr710_init(MemoryRegion *addr_space, hwaddr hpa, @@ -247,20 +233,12 @@ static void lasi_ncr710_reset(DeviceState *dev) ncr710_soft_reset(&s->ncr710); } =20 -static void lasi_ncr710_instance_init(Object *obj) -{ - LasiNCR710State *s =3D LASI_NCR710(obj); - - s->hw_type =3D HPHW_FIO; - s->sversion =3D LASI_710_SVERSION; - s->hversion =3D LASI_710_HVERSION; -} - static void lasi_ncr710_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc =3D DEVICE_CLASS(klass); =20 dc->realize =3D lasi_ncr710_realize; + dc->unrealize =3D lasi_ncr710_unrealize; set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); dc->fw_name =3D "scsi"; dc->desc =3D "HP-PARISC LASI NCR710 SCSI adapter"; @@ -273,7 +251,6 @@ static const TypeInfo lasi_ncr710_info =3D { .name =3D TYPE_LASI_NCR710, .parent =3D TYPE_SYS_BUS_DEVICE, .instance_size =3D sizeof(LasiNCR710State), - .instance_init =3D lasi_ncr710_instance_init, .class_init =3D lasi_ncr710_class_init, }; =20 diff --git a/hw/scsi/lasi_ncr710.h b/hw/scsi/lasi_ncr710.h index 75d3e77d20..5168f61715 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 - * This driver was developed during the Google Summer of Code 2025 program. - * Mentored by Helge Deller - * - * 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 . * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -26,9 +23,6 @@ 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; =20 diff --git a/hw/scsi/ncr53c710.c b/hw/scsi/ncr53c710.c index 57967e7a84..57cd7d91a4 100644 --- a/hw/scsi/ncr53c710.c +++ b/hw/scsi/ncr53c710.c @@ -1,599 +1,308 @@ /* - * QEMU NCR710 SCSI Controller + * QEMU NCR 53C710 SCSI I/O Processor emulation * - * Copyright (c) 2025 Soumyajyotii Ssarkar - * This driver was developed during the Google Summer of Code 2025 program. + * Copyright (c) 2026 Keith Monahan + * Copyright (c) 2006 CodeSourcery (lsi53c895a.c, written by Paul Brook) * - * NCR710 SCSI Controller implementation - * Based on the NCR53C710 Technical Manual Version 3.2, December 2000 + * 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 der= ived + * 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). * - * Developed from an implementation of NCR53C710 by Helge Deller - * which was interim based on the implementation by Toni Wilen for UAE. + * Derived from hw/scsi/lsi53c895a.c (Copyright (c) 2006 CodeSourcery, wri= tten + * by Paul Brook). * - * SPDX-License-Identifier: GPL-2.0-or-later + * Replaces the 53C710 model written for Google Summer of Code 2025 by + * Soumyajyotii Ssarkar , itself based on + * earlier NCR 53C710 work by Helge Deller and by Toni Wilen (for UAE). * - * 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 + * 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 */ =20 #include "qemu/osdep.h" -#include "qapi/error.h" -#include "qemu/timer.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/main-loop.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 "system/block-backend.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); =20 +/* 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 =3D 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 signa= ls. */ +#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 + +/* waiting state machine (mirrors lsi53c895a). */ +enum { + NCR710_NOWAIT =3D 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 */ +}; =20 -static inline int ncr710_irq_on_rsl(NCR710State *s) +enum { + NCR710_MSG_ACTION_COMMAND =3D 0, + NCR710_MSG_ACTION_DISCONNECT =3D 1, + NCR710_MSG_ACTION_DOUT =3D 2, + NCR710_MSG_ACTION_DIN =3D 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] =3D { + "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", "TE= MP3", + "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", "ADDE= R2", + "ADDER3", +}; + +static const char *ncr710_reg_name(int offset) { - return (s->sien0 & NCR710_SSTAT0_SEL) !=3D 0; + return (offset >=3D 0 && offset < 64) ? ncr710_reg_names[offset] : "??= ?"; } =20 -static void ncr710_clear_pending_irq(NCR710State *s) +/* 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_set_phase(NCR710State *s, int phase); + +/* + * On the 53C700 family (including the 53C710), the Select/Reselect destin= ation + * ID and the SDID register are a one hot BITMASK (one bit per SCSI ID), u= nlike + * the 53C8xx which use a binary encoded target number. Convert to a numb= er. + */ +static int ncr710_id_from_bits(unsigned bits) { - if (s->current) { - if (s->current->req) { - s->current->req->hba_private =3D NULL; + int i; + + bits &=3D 0xff; + for (i =3D 0; i < 8; i++) { + if (bits & (1u << i)) { + return i; } - ncr710_request_free(s, s->current); - s->current =3D NULL; } + qemu_log_mask(LOG_GUEST_ERROR, + "ncr710: empty destination ID bitmask; defaulting to ID = 0\n"); + return 0; } =20 void ncr710_soft_reset(NCR710State *s) { + NCR710Request *p, *p_next; + trace_ncr710_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 st= ale + * pointer survives the reset. Otherwise s->current dangles into the = next + * ncr710_do_command (tripping its assert(s->current =3D=3D 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 befor= e 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 =3D 0; - s->msg_action =3D NCR710_MSG_ACTION_NONE; + s->msg_action =3D NCR710_MSG_ACTION_COMMAND; s->msg_len =3D 0; - s->waiting =3D NCR710_WAIT_NONE; - s->wait_reselect =3D false; - s->reselection_id =3D 0; + s->waiting =3D NCR710_NOWAIT; + s->current_lun =3D 0; + s->select_tag =3D 0; + s->command_complete =3D 0; + s->script_running =3D false; + + s->scntl0 =3D 0xc0; /* full arbitration */ + s->scntl1 =3D 0; + s->sdid =3D 0; + s->sien =3D 0; + s->scid =3D 0; + s->sxfer =3D 0; + s->sodl =3D 0; + s->socl =3D 0; + s->sfbr =3D 0; + s->sidl =3D 0; + s->sbdl =3D 0; + s->sbcl =3D 0; + s->dstat =3D 0; /* DFE is OR'd in on read */ + s->sstat0 =3D 0; + s->sstat1 =3D 0; + s->sstat2 =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->ctest0 =3D 0; s->ctest3 =3D 0; s->ctest4 =3D 0; s->ctest5 =3D 0; + s->ctest7 =3D 0; + s->temp =3D 0; + s->dfifo =3D 0; + s->istat =3D 0; + s->lcrc =3D 0; + s->dbc =3D 0; + s->dcmd =3D 0; + s->dnad =3D 0; s->dsp =3D 0; s->dsps =3D 0; + s->scratch =3D 0; s->dmode =3D 0; + s->dien =3D 0; + s->dwt =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"; - } -} + s->adder =3D 0; =20 -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; + if (s->scripts_timer) { + timer_del(s->scripts_timer); } - - return parity; } =20 -static bool ncr710_check_scsi_parity(NCR710State *s, uint8_t data, - uint8_t parity) +static inline void ncr710_mem_read(NCR710State *s, uint32_t addr, + void *buf, uint32_t len) { - 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; + address_space_read(s->as, addr, MEMTXATTRS_UNSPECIFIED, buf, len); } =20 -static void ncr710_handle_parity_error(NCR710State *s) +static inline void ncr710_mem_write(NCR710State *s, uint32_t addr, + const void *buf, uint32_t len) { - 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); + address_space_write(s->as, addr, MEMTXATTRS_UNSPECIFIED, buf, len); } =20 /* - * 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 + * SCRIPTS and the table indirect data structures are built by the big end= ian + * PA-RISC CPU and fetched as longwords, so they are interpreted big endia= n. */ - -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(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 =3D be32_to_cpu(buf); - NCR710_DPRINTF("Read dword %08x from %08x\n", buf, addr); - return buf; -} =20 -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 =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(s->as, addr, MEMTXATTRS_UNSPECIFIED, - buf, len); - NCR710_DPRINTF("Wrote %d bytes to %08x\n", len, addr); + address_space_read(s->as, addr, MEMTXATTRS_UNSPECIFIED, &buf, 4); + return be32_to_cpu(buf); } =20 static void ncr710_stop_script(NCR710State *s) { - s->script_active =3D 0; - s->scntl1 &=3D ~NCR710_SCNTL1_CON; - s->istat &=3D ~NCR710_ISTAT_CON; + s->script_running =3D false; } =20 static void ncr710_update_irq(NCR710State *s) { int level =3D 0; =20 - if (s->dstat & ~NCR710_DSTAT_DFE) { + if (s->dstat) { if (s->dstat & s->dien) { level =3D 1; } @@ -603,7 +312,7 @@ static void ncr710_update_irq(NCR710State *s) } =20 if (s->sstat0) { - if ((s->sstat0 & s->sien0)) { + if (s->sstat0 & s->sien) { level =3D 1; } s->istat |=3D NCR710_ISTAT_SIP; @@ -611,341 +320,342 @@ static void ncr710_update_irq(NCR710State *s) s->istat &=3D ~NCR710_ISTAT_SIP; } =20 + trace_ncr710_update_irq(level, s->istat, s->sstat0, s->dstat); qemu_set_irq(s->irq, level); } =20 +/* Raise a SCSI interrupt and stop SCRIPTS on a fatal/unmasked condition. = */ static void ncr710_script_scsi_interrupt(NCR710State *s, int stat0) { - uint32_t mask0; + uint8_t mask; =20 trace_ncr710_script_scsi_interrupt(stat0, s->sstat0); s->sstat0 |=3D stat0; - mask0 =3D stat0 & s->sien0; - if (mask0) { + /* + * FCMP and SEL are nonfatal (only stop when enabled in SIEN); STO nev= er + * stops here (execution continues until the next SCSI bus instruction= ). + */ + mask =3D s->sien | (uint8_t)~(NCR710_STAT0_FCMP | NCR710_STAT0_SEL); + mask &=3D ~NCR710_STAT0_STO; + if (s->sstat0 & mask) { ncr710_stop_script(s); - s->istat |=3D NCR710_ISTAT_SIP; - ncr710_update_irq(s); } + ncr710_update_irq(s); } =20 +/* Raise a DMA interrupt and stop SCRIPTS (all DMA interrupts are fatal). = */ 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); } =20 -inline void ncr710_set_phase(NCR710State *s, int phase) +static void ncr710_set_phase(NCR710State *s, int phase) { + trace_ncr710_set_phase(phase); + s->sbcl =3D (s->sbcl & ~PHASE_MASK) | phase | NCR710_SBCL_REQ; s->sstat2 =3D (s->sstat2 & ~PHASE_MASK) | phase; - s->ctest0 &=3D ~1; - if (phase =3D=3D PHASE_DI) { - s->ctest0 |=3D 1; +} + +/* 53C710 has no phase mismatch jump feature; a bad phase always interrupt= s. */ +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_resume_script(s->waiting); + if (s->waiting !=3D NCR710_DMA_SCRIPTS) { + s->waiting =3D NCR710_NOWAIT; + ncr710_execute_script(s); + } else { + s->waiting =3D NCR710_NOWAIT; } - s->sbcl &=3D ~NCR710_SBCL_REQ; } =20 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->scntl1 &=3D ~NCR710_SCNTL1_CON; s->sstat2 &=3D ~PHASE_MASK; + s->sbcl =3D 0; } =20 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_script_scsi_interrupt(s, NCR710_STAT0_STO); ncr710_disconnect(s); } =20 -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. */ + SCSIRequest *req; + NCR710Request *p; + + if (!s->current || !s->current->dma_len) { + /* Wait until data is available. */ + trace_ncr710_do_dma_unavailable(); return; } =20 - dev =3D s->current->req->dev; - assert(dev); + p =3D s->current; + req =3D scsi_req_ref(s->current->req); =20 count =3D s->dbc; - if (count > s->current->dma_len) { - count =3D s->current->dma_len; + if (count > p->dma_len) { + count =3D p->dma_len; } =20 addr =3D s->dnad; - + trace_ncr710_do_dma(addr, count); 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); + if (p->dma_buf =3D=3D NULL) { + p->dma_buf =3D scsi_req_get_buf(req); } - /* ??? Set SFBR to first data byte. */ if (out) { - ncr710_dma_read(s, addr, s->current->dma_buf, count); + ncr710_mem_read(s, addr, p->dma_buf, count); } else { - ncr710_dma_write(s, addr, s->current->dma_buf, count); + ncr710_mem_write(s, addr, p->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; - s->waiting =3D NCR710_WAIT_DMA; - scsi_req_continue(s->current->req); + if (p->orphan) { + scsi_req_unref(req); return; + } + scsi_req_unref(req); + + p->dma_len -=3D count; + if (p->dma_len =3D=3D 0) { + p->dma_buf =3D NULL; + scsi_req_continue(req); } else { - s->current->dma_buf +=3D count; - s->waiting =3D NCR710_WAIT_NONE; - ncr710_execute_script(s); + p->dma_buf +=3D count; + ncr710_resume_script(s); } } =20 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"); + qemu_log_mask(LOG_GUEST_ERROR, "ncr710: MSG IN overflow\n"); } else { s->msg[s->msg_len++] =3D data; } } =20 -static void ncr710_request_free(NCR710State *s, NCR710Request *p) +/* + * Reselection-interrupt mode on the 53C700 family is enabled by the SCSI + * interrupt enable for (re)selection (SIEN.SEL). Linux's 53c700 driver s= ets it + * and expects a (re)selection interrupt when a disconnected command resel= ects; + * HP-UX leaves it clear and instead parks the SCRIPTS engine on a Wait Re= select + * instruction (handled in ncr710_wait_reselect()). Unlike the 53C8xx, th= e 710 + * gates reselection response on SIEN.SEL alone, with no separate SCID.RRE= bit. + */ +static inline int ncr710_irq_on_rsl(NCR710State *s) { - if (!p) { - return; - } - if (p->req && p->req->hba_private =3D=3D p) { - p->req->hba_private =3D NULL; - } - if (p =3D=3D s->current) { - s->current =3D NULL; + return (s->sien & NCR710_STAT0_SEL) !=3D 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; + } } - g_free(p); + return NULL; } =20 -void ncr710_request_cancelled(SCSIRequest *req) +/* Reselect a disconnected command to resume (continue) its data transfer.= */ +static void ncr710_reselect(NCR710State *s, NCR710Request *p) { - NCR710State *s =3D ncr710_from_scsi_bus(req->bus); - NCR710Request *p =3D (NCR710Request *)req->hba_private; - if (p) { - req->hba_private =3D NULL; - p->req =3D NULL; - ncr710_request_free(s, p); + int id; + + assert(s->current =3D=3D NULL); + QTAILQ_REMOVE(&s->queue, p, next); + s->current =3D p; + + id =3D (p->tag >> 8) & 0xf; + s->sdid =3D 1 << id; /* one-hot target id (53C700 fa= mily) */ + /* 53C700 compatibility: SFBR holds the reselecting id bitmask. */ + if (!(s->dcntl & NCR710_DCNTL_COM)) { + s->sfbr =3D 1 << (id & 0x7); + } + trace_ncr710_reselect(id); + s->scntl1 |=3D NCR710_SCNTL1_CON; + ncr710_set_phase(s, PHASE_MI); + s->msg_action =3D p->out ? NCR710_MSG_ACTION_DOUT : NCR710_MSG_ACTION_= DIN; + s->current->dma_len =3D 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); } - scsi_req_unref(req); } =20 +/* + * Record that the SCSI layer has data ready for a queued command. Return= s 0 if + * the device was reselected (the engine can continue), nonzero if the tra= nsfer + * is deferred until the engine next parks at Wait Reselect. + */ static int ncr710_queue_req(NCR710State *s, SCSIRequest *req, uint32_t len) { - NCR710Request *p =3D (NCR710Request *)req->hba_private; + NCR710Request *p =3D req->hba_private; =20 - if (!p) { - return -1; - } p->pending =3D len; - if ((s->waiting =3D=3D NCR710_WAIT_RESELECT && - !(s->istat & (NCR710_ISTAT_SIP | NCR710_ISTAT_DIP))) || + if (s->waiting =3D=3D NCR710_WAIT_RESELECT || (ncr710_irq_on_rsl(s) && !(s->scntl1 & NCR710_SCNTL1_CON) && - !(s->istat & (NCR710_ISTAT_SIP | NCR710_ISTAT_DIP)))) { - s->current =3D p; + !(s->istat & (NCR710_ISTAT_SIP | NCR710_ISTAT_DIP)))) { + ncr710_reselect(s, p); return 0; - } else { - s->current =3D p; - return 1; } + trace_ncr710_queue_req(p->tag); + return 1; } =20 -void ncr710_command_complete(SCSIRequest *req, size_t resid) +/* SCRIPTS Wait Reselect: reconnect a ready queued command, else park. */ +static void ncr710_wait_reselect(NCR710State *s) { - NCR710State *s =3D ncr710_from_scsi_bus(req->bus); - NCR710Request *p =3D (NCR710Request *)req->hba_private; + NCR710Request *p; =20 - 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; + if (s->current) { + return; } - - ncr710_set_phase(s, PHASE_ST); - + p =3D ncr710_get_pending_req(s); if (p) { - req->hba_private =3D NULL; - if (p =3D=3D s->current) { - p->req =3D NULL; - } else { - ncr710_request_free(s, p); - } - scsi_req_unref(req); + ncr710_reselect(s, p); } + if (s->current =3D=3D NULL) { + s->waiting =3D NCR710_WAIT_RESELECT; + } +} =20 - 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); +static void ncr710_request_orphan(NCR710State *s, NCR710Request *p) +{ + p->orphan =3D true; + if (p =3D=3D s->current) { + s->current =3D NULL; + } else { + QTAILQ_REMOVE(&s->queue, p, next); } + scsi_req_unref(p->req); } =20 -void ncr710_transfer_data(SCSIRequest *req, uint32_t len) +void ncr710_request_cancelled(SCSIRequest *req) { NCR710State *s =3D ncr710_from_scsi_bus(req->bus); + NCR710Request *p =3D req->hba_private; =20 - assert(req->hba_private); + ncr710_request_orphan(s, p); +} =20 - if (s->waiting =3D=3D NCR710_WAIT_DMA) { - NCR710Request *p =3D (NCR710Request *)req->hba_private; - if (p) { - p->dma_len =3D len; +void ncr710_command_complete(SCSIRequest *req, size_t resid) +{ + NCR710State *s =3D ncr710_from_scsi_bus(req->bus); + int stop =3D 0; + + trace_ncr710_command_complete(req->tag, req->status); + s->status =3D req->status; + s->command_complete =3D 2; + if (s->waiting && s->dbc !=3D 0) { + /* Raise phase mismatch for short transfers. */ + stop =3D ncr710_bad_phase(s, PHASE_ST); + if (stop) { + s->waiting =3D NCR710_NOWAIT; } - s->dsp -=3D 8; - s->waiting =3D NCR710_WAIT_NONE; - ncr710_execute_script(s); - return; + } else { + ncr710_set_phase(s, PHASE_ST); } =20 - 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=3D s->current) { + ncr710_request_orphan(s, s->current); + } + if (!stop) { + ncr710_resume_script(s); } +} =20 - 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) { +void ncr710_transfer_data(SCSIRequest *req, uint32_t len) +{ + NCR710State *s =3D ncr710_from_scsi_bus(req->bus); + NCR710Request *p =3D 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 ca= n be + * moved. ncr710_queue_req() reselects if it can, else defers the tra= nsfer + * until the engine next parks at Wait Reselect. + */ + if (s->waiting =3D=3D NCR710_WAIT_RESELECT || p !=3D s->current || + (ncr710_irq_on_rsl(s) && !(s->scntl1 & NCR710_SCNTL1_CON))) { + if (ncr710_queue_req(s, req, len)) { return; } } =20 - /* Host adapter (re)connected */ - s->command_complete =3D NCR710_CMD_DATA_READY; - if (!s->current) { - return; - } - s->current->dma_len =3D len; + out =3D (s->sstat2 & PHASE_MASK) =3D=3D PHASE_DO; =20 + trace_ncr710_transfer_data(req->tag, len); + s->current->dma_len =3D len; + s->command_complete =3D 1; 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; + if (s->waiting =3D=3D NCR710_WAIT_RESELECT || s->dbc =3D=3D 0) { + ncr710_resume_script(s); } 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); - } + ncr710_do_dma(s, out); } - - 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); } } =20 -static int idbitstonum(uint8_t id) -{ - return 7 - clz8(id); -} - static void ncr710_do_command(NCR710State *s) { SCSIDevice *dev; - uint8_t buf[16]; + uint8_t buf[16] =3D {0}; 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; + ncr710_mem_read(s, s->dnad, buf, s->dbc); 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); - + s->command_complete =3D 0; + trace_ncr710_do_command(s->dbc, buf[0], + (buf[2] << 24) | (buf[3] << 16) | + (buf[4] << 8) | buf[5], + (buf[7] << 8) | buf[8]); + + id =3D (s->select_tag >> 8) & 0xf; + dev =3D scsi_device_find(&s->bus, 0, id, s->current_lun); if (!dev) { ncr710_bad_selection(s, id); return; } =20 - if (s->current) { - ncr710_request_free(s, s->current); - s->current =3D NULL; - } - + assert(s->current =3D=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); + s->dbc, s->current); + n =3D scsi_req_enqueue(s->current->req); if (n) { if (n > 0) { @@ -955,435 +665,335 @@ static void ncr710_do_command(NCR710State *s) } scsi_req_continue(s->current->req); } - if (!s->command_complete) { - if (!n) { - ncr710_set_phase(s, PHASE_SI); + if (n) { + /* Stay connected; the block move waits for transfer_data. */ } else { - NCR710_DPRINTF("Data transfer phase\n"); + /* + * Async no data command (e.g. SYNCHRONIZE CACHE from Linux + * 53c700, completing via a block layer bottom half). Park wi= th + * dbc =3D 0 so the engine stops in COMMAND instead of fabrica= ting + * a data phase; ncr710_command_complete() later asserts STATUS + * and resumes on a quiescent stack. dbc =3D 0 also keeps that + * completion on its clean PHASE_ST path. + */ + s->dbc =3D 0; + s->waiting =3D NCR710_DMA_IN_PROGRESS; } } } =20 static void ncr710_do_status(NCR710State *s) { - uint8_t status =3D s->status; - uint8_t parity =3D 0; + uint8_t status; =20 + trace_ncr710_do_status(s->dbc, s->status); if (s->dbc !=3D 1) { - BADF("Bad Status move\n"); + qemu_log_mask(LOG_GUEST_ERROR, + "ncr710: STATUS move requested %u bytes (expected 1)= \n", + s->dbc); } s->dbc =3D 1; + status =3D s->status; 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_mem_write(s, s->dnad, &status, 1); ncr710_set_phase(s, PHASE_MI); s->msg_action =3D NCR710_MSG_ACTION_DISCONNECT; - ncr710_add_msg_byte(s, 0); /* COMMAND COMPLETE */ - s->command_complete =3D NCR710_CMD_COMPLETE; + ncr710_add_msg_byte(s, 0); /* COMMAND COMPLETE */ } =20 static void ncr710_do_msgin(NCR710State *s) { - int len; + uint8_t len; + + trace_ncr710_do_msgin(s->dbc, s->msg_len); + if (s->msg_len =3D=3D 0) { + /* + * MOVE WHEN MSG IN with no message queued: transfer nothing (SFBR + * keeps its last byte). After a declined negotiation MESSAGE REJ= ECT + * (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 =3D=3D NCR710_MSG_ACTION_COMMAND) { + ncr710_set_phase(s, PHASE_CMD); + } + return; + } + s->sfbr =3D s->msg[0]; len =3D s->msg_len; + assert(len <=3D NCR710_MAX_MSGIN_LEN); if (len > s->dbc) { len =3D s->dbc; } - s->sfbr =3D s->msg[0]; =20 - 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]); + if (len) { + ncr710_mem_write(s, s->dnad, s->msg, len); + /* Drivers rely on the last byte being in SIDL. */ + 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); } - ncr710_scsi_fifo_enqueue(&s->scsi_fifo, s->msg[i], parity); } =20 - 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); - } + if (!s->msg_len) { + switch (s->msg_action) { + case NCR710_MSG_ACTION_COMMAND: + /* + * Stay in MSG IN: SSTAT2 latches the last REQ phase, so the d= river + * samples MSG IN at the script interrupt and the lazy MSG IN = -> + * COMMAND switch happens when its next block move requests CO= MMAND. + */ + 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(); } } - 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: - s->sstat2 &=3D ~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(); - } } =20 -static void ncr710_do_msgout(NCR710State *s) +static uint8_t ncr710_get_msgbyte(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 */ + uint8_t data; =20 - 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; - } + ncr710_mem_read(s, s->dnad, &data, 1); + s->dnad++; + s->dbc--; + return data; +} =20 - 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; +static void ncr710_skip_msgbytes(NCR710State *s, unsigned int n) +{ + s->dnad +=3D n; + s->dbc -=3D n; +} =20 - case 0x0d: /* ABORT TAG */ - if (current_req) { - scsi_req_cancel(current_req->req); - } - ncr710_disconnect(s); - break; +static void ncr710_do_msgout(NCR710State *s) +{ + uint8_t msg; =20 - 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; + trace_ncr710_do_msgout(s->dbc); + while (s->dbc) { + msg =3D ncr710_get_msgbyte(s); + s->sfbr =3D msg; =20 + 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 =3D 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: - 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; } + break; + case 0x20: /* SIMPLE queue tag */ + s->select_tag &=3D ~0xff; + s->select_tag |=3D ncr710_get_msgbyte(s) | NCR710_TAG_VALID; + break; + case 0x21: /* HEAD of queue tag */ + case 0x22: /* ORDERED queue tag */ + s->select_tag &=3D ~0xff; + s->select_tag |=3D 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) =3D=3D 0) { + goto bad; + } + s->current_lun =3D msg & 7; /* IDENTIFY */ + ncr710_set_phase(s, PHASE_CMD); + break; } - -out_chunk: - continue; } - return; - +reject: + /* + * Decline SDTR/WDTR/PPR negotiation with a single MESSAGE REJECT. Ne= tBSD + * 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 (0x= 02) or + * it spins in MSG IN until the I/O times out, while NetBSD osiop reje= cts 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 =3D NCR710_MSG_ACTION_COMMAND; + return; bad: - BADF("Unimplemented/Invalid message 0x%02x\n", s->sfbr); + qemu_log_mask(LOG_UNIMP, "ncr710: unimplemented message 0x%02x\n", msg= ); ncr710_set_phase(s, PHASE_MI); - ncr710_add_msg_byte(s, 7); - s->msg_action =3D NCR710_MSG_ACTION_NONE; + ncr710_add_msg_byte(s, 7); /* MESSAGE REJECT */ + s->msg_action =3D NCR710_MSG_ACTION_COMMAND; } =20 static void ncr710_memcpy(NCR710State *s, uint32_t dest, uint32_t src, int count) { - uint8_t buf[NCR710_BUF_SIZE]; + QEMU_UNINITIALIZED uint8_t buf[NCR710_BUF_SIZE]; + int n; =20 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; + n =3D (count > NCR710_BUF_SIZE) ? NCR710_BUF_SIZE : count; + ncr710_mem_read(s, src, buf, n); + ncr710_mem_write(s, dest, buf, n); + src +=3D n; + dest +=3D n; + count -=3D n; } } =20 -static void ncr710_wait_reselect(NCR710State *s) +static void ncr710_scripts_timer_start(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; + /* The wrapper allocates this timer on the nanosecond clock. */ + timer_mod(s->scripts_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + 500000); } =20 -void ncr710_reselection_retry_callback(void *opaque) +void ncr710_scripts_timer_callback(void *opaque) { NCR710State *s =3D opaque; =20 - 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; + s->waiting =3D NCR710_NOWAIT; + ncr710_execute_script(s); } =20 -void ncr710_execute_script(NCR710State *s) +static void ncr710_execute_script(NCR710State *s) { uint32_t insn; uint32_t addr; int opcode; - s->script_active =3D 1; + int insn_processed =3D 0; + static int reentrancy_level; =20 + if (s->waiting =3D=3D NCR710_WAIT_SCRIPTS) { + timer_del(s->scripts_timer); + s->waiting =3D NCR710_NOWAIT; + } + + reentrancy_level++; + s->script_running =3D 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 scri= pt + * that retriggers itself (CVE-2023-0330) via the reentrancy counter. + */ + if (++insn_processed > NCR710_MAX_INSN || reentrancy_level > 8) { + trace_ncr710_script_yield(insn_processed); + s->waiting =3D NCR710_WAIT_SCRIPTS; + ncr710_scripts_timer_start(s); + reentrancy_level--; + return; + } + 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 - */ + /* Skip an empty (NULL) opcode 4 bytes at a time. */ s->dsp +=3D 4; goto again; } addr =3D ncr710_read_dword(s, s->dsp + 4); + trace_ncr710_execute_script(s->dsp, insn, addr); 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"); + case 0: /* Block move */ + if (s->sstat0 & NCR710_STAT0_STO) { ncr710_stop_script(s); - ncr710_update_irq(s); break; } s->dbc =3D insn & 0xffffff; if (insn & (1 << 29)) { - /* Indirect addressing. */ + /* Indirect addressing. */ addr =3D ncr710_read_dword(s, addr); } else if (insn & (1 << 28)) { + /* Table indirect addressing (32 bit). */ uint32_t buf[2]; - int32_t offset; - /* Table indirect addressing. */ + int32_t offset =3D sextract32(addr, 0, 24); =20 - /* 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 */ + ncr710_mem_read(s, s->dsa + offset, buf, 8); s->dbc =3D be32_to_cpu(buf[0]) & 0xffffff; addr =3D be32_to_cpu(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; + /* + * The target controls the phase; SSTAT2 latches the last REQ + * phase. The one legitimate lazy transition is MSG IN -> COM= MAND + * 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) =3D=3D PHASE_MI && s->msg_len =3D= =3D 0 && + s->msg_action =3D=3D NCR710_MSG_ACTION_COMMAND && + ((insn >> 24) & 7) =3D=3D PHASE_CMD) { + ncr710_set_phase(s, PHASE_CMD); + } else { + trace_ncr710_block_move_badphase(s->sstat2 & PHASE_MASK, + (insn >> 24) & 7); + ncr710_script_scsi_interrupt(s, NCR710_STAT0_MA); + break; + } } - s->dnad =3D addr; - switch (s->sstat2 & 0x7) { + switch (s->sstat2 & PHASE_MASK) { case PHASE_DO: - s->waiting =3D NCR710_WAIT_DMA; + s->waiting =3D NCR710_DMA_SCRIPTS; ncr710_do_dma(s, 1); + if (s->waiting) { + s->waiting =3D NCR710_DMA_IN_PROGRESS; + } break; case PHASE_DI: - s->waiting =3D NCR710_WAIT_DMA; + s->waiting =3D NCR710_DMA_SCRIPTS; ncr710_do_dma(s, 0); + if (s->waiting) { + s->waiting =3D NCR710_DMA_IN_PROGRESS; + } break; - case PHASE_CO: + case PHASE_CMD: ncr710_do_command(s); break; - case PHASE_SI: + case PHASE_ST: ncr710_do_status(s); break; case PHASE_MO: @@ -1393,103 +1003,87 @@ again: ncr710_do_msgin(s); break; default: - BADF("Unimplemented phase %d\n", s->sstat2 & PHASE_MASK); + qemu_log_mask(LOG_UNIMP, "ncr710: unimplemented phase %d\n", + s->sstat2 & PHASE_MASK); } - s->ctest5 =3D (s->ctest5 & 0xfc) | ((s->dbc >> 8) & 3); - s->sbcl =3D s->dbc; + s->dfifo =3D s->dbc & 0x7f; break; =20 - case 1: /* IO or Read/Write instruction. */ + case 1: /* I/O or Read/Write register */ opcode =3D (insn >> 27) & 7; if (opcode < 5) { uint32_t id; + unsigned id_bits; =20 if (insn & (1 << 25)) { - id =3D ncr710_read_dword(s, s->dsa + sextract32(insn, 0, 2= 4)); + id_bits =3D ncr710_read_dword(s, + s->dsa + sextract32(insn, 0, 2= 4)); } else { - id =3D insn; + id_bits =3D insn; } - id =3D (id >> 16) & 0xff; + /* Destination ID is a one hot bitmask in bits 23:16. */ + id_bits =3D (id_bits >> 16) & 0xff; + id =3D ncr710_id_from_bits(id_bits); 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; + trace_ncr710_select(id, !!(insn & (1 << 24))); + s->sdid =3D id_bits; if (s->scntl1 & NCR710_SCNTL1_CON) { - if (!(insn & (1 << 24))) { - s->dsp =3D s->dnad; - break; - } - } - bool device_exists =3D false; - if (insn & (1 << 24)) { - /* ATN set - scan all LUNs for this target */ - for (int lun =3D 0; lun < 8; lun++) { - SCSIDevice *dev =3D scsi_device_find(&s->bus, 0, - idbitstonum(id), - lun); - if (dev) { - device_exists =3D true; - break; - } - } - } else { - /* No ATN - check only LUN 0 */ - SCSIDevice *dev =3D scsi_device_find(&s->bus, 0, - idbitstonum(id), 0); - device_exists =3D dev !=3D NULL; + s->dsp =3D s->dnad; + break; } - if (!device_exists) { + s->sstat1 |=3D 0x04; /* Won arbitration */ + if (!scsi_device_find(&s->bus, 0, id, 0)) { ncr710_bad_selection(s, id); - if (!(insn & (1 << 24)) && addr !=3D 0) { - s->dsp =3D addr; - } break; - } else { + } + s->select_tag =3D id << 8; + s->scntl1 |=3D NCR710_SCNTL1_CON; + s->sbcl |=3D NCR710_SBCL_BSY; + if (insn & (1 << 24)) { /* - * ??? 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= ); + * Select with ATN/: the initiator drives a message + * first, so the target's first phase is MESSAGE OUT + * (IDENTIFY). */ - 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; - s->waiting =3D NCR710_WAIT_NONE; + s->socl |=3D NCR710_SOCL_ATN; + s->sbcl |=3D NCR710_SBCL_ATN; + ncr710_set_phase(s, PHASE_MO); } 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"); + /* No ATN/: target goes straight to COMMAND phase. */ + ncr710_set_phase(s, PHASE_CMD); } + s->waiting =3D NCR710_NOWAIT; + break; + case 1: /* Wait Disconnect */ + trace_ncr710_wait_disconnect(); + s->scntl1 &=3D ~NCR710_SCNTL1_CON; break; case 2: /* Wait Reselect */ - if (!ncr710_irq_on_rsl(s)) { + /* + * A SIGP "start next command" kick jumps to the dispatch + * address latched in DNAD; otherwise reconnect a ready qu= eued + * command, or park. + */ + if (s->istat & NCR710_ISTAT_SIGP) { + s->dsp =3D s->dnad; + } else if (!ncr710_irq_on_rsl(s)) { ncr710_wait_reselect(s); } break; case 3: /* Set */ if (insn & (1 << 3)) { s->socl |=3D NCR710_SOCL_ATN; + s->sbcl |=3D NCR710_SBCL_ATN; ncr710_set_phase(s, PHASE_MO); } + if (insn & (1 << 6)) { + s->sbcl |=3D NCR710_SBCL_ACK; + } if (insn & (1 << 10)) { s->carry =3D 1; } @@ -1497,6 +1091,10 @@ again: case 4: /* Clear */ if (insn & (1 << 3)) { s->socl &=3D ~NCR710_SOCL_ATN; + s->sbcl &=3D ~NCR710_SBCL_ATN; + } + if (insn & (1 << 6)) { + s->sbcl &=3D ~NCR710_SBCL_ACK; } if (insn & (1 << 10)) { s->carry =3D 0; @@ -1504,30 +1102,31 @@ again: 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; + /* + * 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 =3D 0; + uint8_t op1 =3D 0; + uint8_t data8 =3D (insn >> 8) & 0xff; + int reg =3D ((insn >> 16) & 0x7f) | (insn & 0x80); + int operator =3D (insn >> 24) & 7; + switch (opcode) { case 5: /* From SFBR */ op0 =3D s->sfbr; op1 =3D data8; break; case 6: /* To SFBR */ - if (xoperator) { + if (operator) { op0 =3D ncr710_reg_readb(s, reg); } op1 =3D data8; break; - case 7: /* Read-modify-write */ - if (xoperator) { + case 7: /* Read modify write */ + if (operator) { op0 =3D ncr710_reg_readb(s, reg); } if (insn & (1 << 23)) { @@ -1538,11 +1137,11 @@ again: break; } =20 - switch (xoperator) { + switch (operator) { case 0: /* move */ op0 =3D op1; break; - case 1: /* Shift left */ + case 1: /* shift left */ op1 =3D op0 >> 7; op0 =3D (op0 << 1) | s->carry; s->carry =3D op1; @@ -1556,7 +1155,7 @@ again: case 4: /* AND */ op0 &=3D op1; break; - case 5: /* SHR */ + case 5: /* shift right */ op1 =3D op0 & 1; op0 =3D (op0 >> 1) | (s->carry << 7); s->carry =3D op1; @@ -1577,7 +1176,7 @@ again: =20 switch (opcode) { case 5: /* From SFBR */ - case 7: /* Read-modify-write */ + case 7: /* Read modify write */ ncr710_reg_writeb(s, reg, op0); break; case 6: /* To SFBR */ @@ -1587,13 +1186,17 @@ again: } break; =20 - case 2: /* Transfer Control. */ + case 2: /* Transfer Control */ { int cond; int jmp; =20 - - if (s->sstat0 & NCR710_SSTAT0_STO) { + if ((insn & 0x002e0000) =3D=3D 0) { + /* NOP */ + break; + } + if (s->sstat0 & NCR710_STAT0_STO) { + ncr710_stop_script(s); break; } cond =3D jmp =3D (insn & (1 << 19)) !=3D 0; @@ -1604,49 +1207,35 @@ again: 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; + uint8_t 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. */ + /* Relative address. */ addr =3D s->dsp + sextract32(addr, 0, 24); } switch ((insn >> 27) & 7) { case 0: /* Jump */ + trace_ncr710_tc_jump(addr); + s->adder =3D addr; s->dsp =3D addr; break; case 1: /* Call */ + trace_ncr710_tc_call(addr); 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; - } + trace_ncr710_tc_return(s->temp); 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); - } - } + trace_ncr710_interrupt_insn(s->dsps); + ncr710_script_dma_interrupt(s, NCR710_DSTAT_SIR); break; default: + trace_ncr710_illegal_insn(insn); ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID); break; } @@ -1654,658 +1243,427 @@ again: } break; =20 - case 3: + case 3: /* Memory Move (Load/Store is illegal on the 710) */ 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; + trace_ncr710_memmove(dest, addr, insn & 0xffffff); 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); - } + trace_ncr710_illegal_insn(insn); + ncr710_script_dma_interrupt(s, NCR710_DSTAT_IID); } + break; } =20 - if (s->script_active && s->waiting =3D=3D NCR710_WAIT_NONE) { + if (s->script_running && s->waiting =3D=3D NCR710_NOWAIT) { 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; } -} =20 -static uint8_t ncr710_reg_readb(NCR710State *s, int offset) -{ - uint8_t ret =3D 0; + reentrancy_level--; +} =20 #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; + 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; =20 #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; + 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; + +static uint8_t ncr710_reg_readb(NCR710State *s, int offset) +{ + uint8_t ret; =20 switch (offset) { - case NCR710_SCNTL0_REG: /* SCNTL0 */ + case NCR710_SCNTL0: ret =3D s->scntl0; break; - case NCR710_SCNTL1_REG: /* SCNTL1 */ + case NCR710_SCNTL1: ret =3D s->scntl1; break; - case NCR710_SDID_REG: /* SDID */ + case NCR710_SDID: ret =3D s->sdid; break; - case NCR710_SIEN_REG: /* SIEN */ - ret =3D s->sien0; + case NCR710_SIEN: + ret =3D s->sien; break; - case NCR710_SCID_REG: + case NCR710_SCID: 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 */ + case NCR710_SXFER: ret =3D s->sxfer; break; - case NCR710_SODL_REG: /* SODL */ + case NCR710_SODL: ret =3D s->sodl; break; - case NCR710_SOCL_REG: /* SOCL */ + case NCR710_SOCL: ret =3D s->socl; break; - case NCR710_SFBR_REG: /* SFBR */ + case NCR710_SFBR: ret =3D s->sfbr; break; - case NCR710_SIDL_REG: /* SIDL */ + case NCR710_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; - } + case NCR710_SBDL: + /* Some drivers peek at the data bus during MSG IN. */ + if ((s->sstat2 & PHASE_MASK) =3D=3D PHASE_MI && s->msg_len > 0) { + ret =3D s->msg[0]; + } else { + ret =3D 0; } 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; + case NCR710_SBCL: + ret =3D s->sbcl; + break; + case NCR710_DSTAT: + ret =3D s->dstat | NCR710_DSTAT_DFE; + s->dstat =3D 0; 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 =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 */ + case NCR710_SSTAT0: ret =3D s->sstat0; + s->sstat0 =3D 0; + ncr710_update_irq(s); break; - case NCR710_SSTAT2_REG: /* SSTAT2 */ - ret =3D s->sstat2; + case NCR710_SSTAT1: + ret =3D s->sstat1; break; - CASE_GET_REG32(dsa, NCR710_DSA_REG) + case NCR710_SSTAT2: + ret =3D s->sstat2; break; - case NCR710_CTEST0_REG: /* CTEST0 */ + CASE_GET_REG32(dsa, NCR710_DSA) + case NCR710_CTEST0: ret =3D s->ctest0; break; - case NCR710_CTEST1_REG: /* CTEST1 */ - ret =3D s->ctest1; + case NCR710_CTEST1: + ret =3D 0xf0; /* DMA FIFO empty */ break; - case NCR710_CTEST2_REG: /* CTEST2 */ - ret =3D s->ctest2; - s->ctest2 |=3D 0x04; + case NCR710_CTEST2: + ret =3D NCR710_CTEST2_DACK; + if (s->istat & NCR710_ISTAT_SIGP) { + s->istat &=3D ~NCR710_ISTAT_SIGP; + ret |=3D NCR710_CTEST2_SIGP; + } break; - case NCR710_CTEST3_REG: /* CTEST3 */ + case NCR710_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 */ + case NCR710_CTEST4: ret =3D s->ctest4; break; - case NCR710_CTEST5_REG: /* CTEST5 */ + case NCR710_CTEST5: ret =3D s->ctest5; break; - case NCR710_CTEST6_REG: /* CTEST6 */ - ret =3D s->ctest6; + case NCR710_CTEST6: + ret =3D 0; break; - case NCR710_CTEST7_REG: /* CTEST7 */ + case NCR710_CTEST7: ret =3D s->ctest7; break; - CASE_GET_REG32(temp, NCR710_TEMP_REG) - case NCR710_DFIFO_REG: /* DFIFO */ + CASE_GET_REG32(temp, NCR710_TEMP) + case NCR710_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; + case NCR710_ISTAT: + ret =3D s->istat & ~NCR710_ISTAT_CON; + if (s->scntl1 & NCR710_SCNTL1_CON) { + ret |=3D NCR710_ISTAT_CON; + } break; - case NCR710_CTEST8_REG: /* CTEST8 */ - ret =3D s->istat; + case NCR710_CTEST8: + ret =3D NCR710_CHIP_REVISION << 4; break; - case NCR710_LCRC_REG: /* LCRC */ + case NCR710_LCRC: ret =3D s->lcrc; break; - CASE_GET_REG24(dbc, NCR710_DBC_REG) - case NCR710_DCMD_REG: /* DCMD */ + CASE_GET_REG24(dbc, NCR710_DBC) + case NCR710_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; - 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 */ + 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 =3D s->dmode; break; - case NCR710_DIEN_REG: /* DIEN */ + case NCR710_DIEN: ret =3D s->dien; break; - case NCR710_DWT_REG: /* DWT */ + case NCR710_DWT: ret =3D s->dwt; break; - case NCR710_DCNTL_REG: /* DCNTL */ + case NCR710_DCNTL: ret =3D s->dcntl; - return ret; - CASE_GET_REG32(adder, NCR710_ADDER_REG) break; + CASE_GET_REG32(adder, NCR710_ADDER) default: - ret =3D 0; + qemu_log_mask(LOG_GUEST_ERROR, + "ncr710: invalid read from reg %s 0x%x\n", + ncr710_reg_name(offset), offset); + ret =3D 0xff; break; } =20 -#undef CASE_GET_REG24 -#undef CASE_GET_REG32 + trace_ncr710_reg_read(ncr710_reg_name(offset), offset, ret); return ret; } =20 -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; + 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; =20 #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; + 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; =20 +static void ncr710_reg_writeb(NCR710State *s, int offset, uint8_t val) +{ trace_ncr710_reg_write(ncr710_reg_name(offset), offset, val); =20 switch (offset) { - case NCR710_SCNTL0_REG: /* SCNTL0 */ - old_val =3D s->scntl0; + case NCR710_SCNTL0: s->scntl0 =3D val; + if (val & NCR710_SCNTL0_START) { + qemu_log_mask(LOG_UNIMP, "ncr710: START not implemented\n"); + } break; - - case NCR710_SCNTL1_REG: /* SCNTL1 */ - old_val =3D s->scntl1; + case NCR710_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); + if (!(s->sstat0 & NCR710_STAT0_RST)) { + bus_cold_reset(BUS(&s->bus)); + s->sstat0 |=3D NCR710_STAT0_RST; + ncr710_script_scsi_interrupt(s, NCR710_STAT0_RST); } } else { - s->sstat0 &=3D ~NCR710_SSTAT0_RST; + s->sstat0 &=3D ~NCR710_STAT0_RST; } break; - - case NCR710_SDID_REG: /* SDID */ - s->sdid =3D val & 0x0F; /* Only lower 4 bits are valid */ + case NCR710_SDID: + s->sdid =3D val; break; - - case NCR710_SIEN_REG: /* SIEN */ - s->sien0 =3D val; - NCR710_DPRINTF("SIEN: interrupt mask=3D0x%02x\n", val); + case NCR710_SIEN: + s->sien =3D val; ncr710_update_irq(s); break; - - case NCR710_SCID_REG: /* SCID */ + case NCR710_SCID: s->scid =3D val; break; - - case NCR710_SXFER_REG: /* SXFER */ + case NCR710_SXFER: s->sxfer =3D val; break; - - case NCR710_SODL_REG: /* SODL */ + case NCR710_SODL: s->sodl =3D val; - s->sstat1 |=3D NCR710_SSTAT1_ORF; /* SCSI Output Register Full */ break; - - case NCR710_SOCL_REG: /* SOCL */ + case NCR710_SOCL: s->socl =3D val; break; - - case NCR710_SFBR_REG: /* SFBR */ + case NCR710_SFBR: + /* CPU may not write SFBR, but SCRIPTS register moves do. */ s->sfbr =3D val; break; - - case NCR710_SIDL_REG: /* SIDL */ - case NCR710_SBDL_REG: /* SBDL */ + case NCR710_SIDL: + case NCR710_SBDL: + /* Read only. */ break; - - case NCR710_SBCL_REG: /* SBCL */ + case NCR710_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 NCR710_DSA_REG: - s->dsa &=3D 0xffffff00; - s->dsa |=3D val; - break; - case NCR710_DSA_REG + 1: - s->dsa &=3D 0xffff00ff; - s->dsa |=3D val << 8; break; - case NCR710_DSA_REG + 2: - s->dsa &=3D 0xff00ffff; - s->dsa |=3D val << 16; + case NCR710_DSTAT: + case NCR710_SSTAT0: + case NCR710_SSTAT1: + case NCR710_SSTAT2: + /* Read only status registers. */ break; - case NCR710_DSA_REG + 3: - s->dsa &=3D 0x00ffffff; - s->dsa |=3D val << 24; - break; - - case NCR710_CTEST0_REG: /* CTEST0 */ + CASE_SET_REG32(dsa, NCR710_DSA) + case NCR710_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; + case NCR710_CTEST1: + case NCR710_CTEST2: + /* Read only. */ break; - - case NCR710_CTEST3_REG: /* CTEST3 */ + case NCR710_CTEST3: s->ctest3 =3D val; break; - - case NCR710_CTEST4_REG: /* CTEST4 */ + case NCR710_CTEST4: s->ctest4 =3D val; break; - - case NCR710_CTEST5_REG: /* CTEST5 */ + case NCR710_CTEST5: s->ctest5 =3D val; break; - - case NCR710_CTEST6_REG: /* CTEST6 */ - s->ctest6 =3D val; + case NCR710_CTEST6: break; - - case NCR710_CTEST7_REG: /* CTEST7 */ + case NCR710_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; + CASE_SET_REG32(temp, NCR710_TEMP) + case NCR710_DFIFO: + s->dfifo =3D val; + break; + case NCR710_ISTAT: + s->istat =3D (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; } - - 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; - s->ctest1 =3D 0xFF; - } else if (s->ctest8 & 0x04) { - s->ctest1 =3D 0x00; - } - s->ctest8 =3D val; + case NCR710_CTEST8: + /* + * Revision is read only; FLF/CLF FIFO ops do nothing (the FIFO is + * modelled as always empty). + */ break; - case NCR710_LCRC_REG: /* LCRC */ - s->lcrc =3D val; + case NCR710_LCRC: + /* Writing clears the longitudinal parity accumulator. */ + s->lcrc =3D 0; break; - - CASE_SET_REG24(dbc, NCR710_DBC_REG) - - case NCR710_DCMD_REG: /* DCMD */ + CASE_SET_REG24(dbc, NCR710_DBC) + case NCR710_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; + CASE_SET_REG32(dnad, NCR710_DNAD) + case NCR710_DSP: + s->dsp =3D (s->dsp & 0xffffff00) | val; break; - case 0x2e: /* DSP[16:23] */ - s->dsp &=3D 0xff00ffff; - s->dsp |=3D val << 16; + case NCR710_DSP + 1: + s->dsp =3D (s->dsp & 0xffff00ff) | (val << 8); 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); + case NCR710_DSP + 2: + s->dsp =3D (s->dsp & 0xff00ffff) | (val << 16); break; - CASE_SET_REG32(dsps, NCR710_DSPS_REG) - CASE_SET_REG32(scratch, NCR710_SCRATCH_REG) + case NCR710_DSP + 3: + s->dsp =3D (s->dsp & 0x00ffffff) | (val << 24); + /* Writing the high byte starts SCRIPTS unless in manual start mod= e. */ + if (!(s->dmode & NCR710_DMODE_MAN) && !s->script_running) { + ncr710_execute_script(s); + } break; - - case NCR710_DMODE_REG: /* DMODE */ + CASE_SET_REG32(dsps, NCR710_DSPS) + CASE_SET_REG32(scratch, NCR710_SCRATCH) + case NCR710_DMODE: s->dmode =3D val; break; - - case NCR710_DIEN_REG: /* DIEN */ + case NCR710_DIEN: s->dien =3D val; - NCR710_DPRINTF("DIEN: interrupt enable=3D0x%02x\n", val); ncr710_update_irq(s); break; - - case NCR710_DWT_REG: /* DWT */ + case NCR710_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; + case NCR710_DCNTL: + s->dcntl =3D val & ~NCR710_DCNTL_STD; + if ((val & NCR710_DCNTL_STD) && !s->script_running) { ncr710_execute_script(s); - s->dcntl &=3D ~NCR710_DCNTL_STD; } break; - - CASE_SET_REG32(adder, NCR710_ADDER_REG) - 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; } +} =20 +#undef CASE_GET_REG24 +#undef CASE_GET_REG32 #undef CASE_SET_REG24 #undef CASE_SET_REG32 -} =20 -/* Memory region wrapper for NCR710 registers */ -uint64_t ncr710_reg_read(void *opaque, hwaddr addr, unsigned size) +uint64_t ncr710_reg_read(NCR710State *s, 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; + /* The LASI wrapper decomposes multibyte accesses into single bytes. */ + return ncr710_reg_readb(s, addr & 0xff); } =20 -void ncr710_reg_write(void *opaque, hwaddr addr, uint64_t val, unsigned si= ze) +void ncr710_reg_write(NCR710State *s, hwaddr addr, uint64_t val, unsigned = size) { - 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); + ncr710_reg_writeb(s, addr & 0xff, val & 0xff); } =20 -/* Device reset */ -static void ncr710_device_reset(DeviceState *dev) +static int ncr710_pre_save(void *opaque) { - SysBusNCR710State *sysbus_dev =3D SYSBUS_NCR710_SCSI(dev); - NCR710State *s =3D &sysbus_dev->ncr710; + NCR710State *s =3D opaque; =20 - ncr710_soft_reset(s); + if (s->current) { + assert(s->current->dma_buf =3D=3D NULL); + assert(s->current->dma_len =3D=3D 0); + } + return 0; } =20 -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 int ncr710_post_load(void *opaque, int version_id) +{ + NCR710State *s =3D opaque; =20 -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() + 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 =3D=3D NCR710_WAIT_SCRIPTS) { + ncr710_scripts_timer_start(s); + } + return 0; +} =20 const VMStateDescription vmstate_ncr710 =3D { .name =3D "ncr710", - .version_id =3D 1, - .minimum_version_id =3D 1, - .fields =3D (VMStateField[]) { + /* + * Version 2: the field layout was incompatibly reworked from the mode= l 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 =3D 2, + .minimum_version_id =3D 2, + .pre_save =3D ncr710_pre_save, + .post_load =3D ncr710_post_load, + .fields =3D (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), + VMSTATE_UINT8(scntl0, NCR710State), VMSTATE_UINT8(scntl1, NCR710State), VMSTATE_UINT8(sdid, NCR710State), - VMSTATE_UINT8(sien0, NCR710State), + VMSTATE_UINT8(sien, NCR710State), VMSTATE_UINT8(scid, NCR710State), VMSTATE_UINT8(sxfer, NCR710State), VMSTATE_UINT8(sodl, NCR710State), @@ -2318,156 +1676,27 @@ const VMStateDescription vmstate_ncr710 =3D { VMSTATE_UINT8(sstat0, NCR710State), VMSTATE_UINT8(sstat1, NCR710State), VMSTATE_UINT8(sstat2, NCR710State), + VMSTATE_UINT32(dsa, 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_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_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 index 00b6a01577..7bcefae902 100644 --- a/hw/scsi/ncr53c710.h +++ b/hw/scsi/ncr53c710.h @@ -1,13 +1,14 @@ /* - * QEMU NCR710 SCSI Controller + * QEMU NCR 53C710 SCSI I/O Processor emulation * - * Copyright (c) 2025 Soumyajyotii Ssarkar + * Copyright (c) 2026 Keith Monahan * - * 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 + * Interface to the 53C710 model (ncr53c710.c), a rewrite whose SCRIPTS en= gine + * is derived from QEMU's LSI53C895A model and adapted to the 53C710 per i= ts + * Data Manual (Jun 1992). The only instantiated device is the LASI wrapp= er in + * lasi_ncr710.c, which embeds one NCR710State by value and drives it thro= ugh + * the functions declared here. This file is a function library, not a QOM + * type. * * SPDX-License-Identifier: GPL-2.0-or-later */ @@ -15,143 +16,101 @@ #ifndef HW_NCR53C710_H #define HW_NCR53C710_H =20 -#include "hw/core/sysbus.h" +#include "qemu/queue.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" +#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 =20 -#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; =20 -/* - * 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; - +typedef struct NCR710State { + /* Wiring filled in by the LASI wrapper before ncr710_soft_reset(). */ SCSIBus bus; AddressSpace *as; - - /* Registers */ + 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; + NCR710Request *current; + /* + * Disconnected tagged commands awaiting target-initiated reselection + * (lsi53c895a-style). Untagged 53C700-family transfers never disconn= ect, + * so for them this stays empty and the engine runs connected as befor= e. + */ + 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 sien0; + uint8_t sien; uint8_t scid; uint8_t sxfer; uint8_t sodl; @@ -166,14 +125,10 @@ struct NCR710State { 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; @@ -189,58 +144,21 @@ struct NCR710State { 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; +} NCR710State; =20 static inline NCR710State *ncr710_from_scsi_bus(SCSIBus *bus) { return container_of(bus, NCR710State, bus); } =20 -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); +/* 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_execute_script(NCR710State *s); -void ncr710_set_phase(NCR710State *s, int phase); -void ncr710_reselection_retry_callback(void *opaque); +void ncr710_scripts_timer_callback(void *opaque); extern const VMStateDescription vmstate_ncr710; =20 #endif /* HW_NCR53C710_H */ diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events index a8ac1e7f1d..843d8f1a1e 100644 --- a/hw/scsi/trace-events +++ b/hw/scsi/trace-events @@ -314,9 +314,33 @@ ncr710_script_scsi_interrupt(uint8_t stat0, uint8_t ss= tat0) "SCSI interrupt stat 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_reselect(int id) "Reselect id=3D%d" +ncr710_queue_command(uint32_t tag) "queue command tag=3D0x%x" +ncr710_queue_req(uint32_t tag) "defer queued req tag=3D0x%x" 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" +ncr710_execute_script(uint32_t dsp, uint32_t insn, uint32_t addr) "dsp=3D0= x%08x insn=3D0x%08x addr=3D0x%08x" +ncr710_do_dma(uint32_t addr, uint32_t len) "addr=3D0x%08x len=3D%d" +ncr710_do_command(uint32_t dbc, uint8_t op, uint32_t lba, uint32_t xfer) "= len=3D%d op=3D0x%02x lba=3D%u xfer=3D%u" +ncr710_do_status(uint32_t dbc, uint8_t status) "len=3D%d status=3D0x%02x" +ncr710_do_msgin(uint32_t dbc, int msg_len) "len=3D%d msg_len=3D%d" +ncr710_do_msgout(uint32_t dbc) "len=3D%d" +ncr710_transfer_data(uint32_t tag, uint32_t len) "tag=3D0x%x len=3D%d" +ncr710_awoken(void) "SIGP woke Wait Reselect" +ncr710_set_phase(int phase) "phase=3D%d" +ncr710_update_irq(int level, uint8_t istat, uint8_t sstat0, uint8_t dstat)= "level=3D%d istat=3D0x%02x sstat0=3D0x%02x dstat=3D0x%02x" +ncr710_select(int id, int atn) "Select id=3D%d atn=3D%d" +ncr710_block_move_badphase(int have, int want) "phase mismatch have=3D%d w= ant=3D%d" +ncr710_resume_script(int waiting) "resume waiting=3D%d" +ncr710_script_yield(int insns) "yield after %d insns" +ncr710_tc_jump(uint32_t addr) "JUMP 0x%08x" +ncr710_tc_call(uint32_t addr) "CALL 0x%08x" +ncr710_tc_return(uint32_t addr) "RETURN 0x%08x" +ncr710_interrupt_insn(uint32_t dsps) "INT vector=3D0x%08x" +ncr710_memmove(uint32_t dest, uint32_t src, int count) "MOVE MEMORY dest= =3D0x%08x src=3D0x%08x count=3D%d" +ncr710_do_dma_unavailable(void) "DMA: data not yet available" +ncr710_wait_disconnect(void) "Wait Disconnect" +ncr710_illegal_insn(uint32_t insn) "ILLEGAL instruction 0x%08x" +ncr710_drain_exhausted(unsigned iters) "synchronous completion drain gave = up after %u iters" =20 # lasi_ncr710.c lasi_ncr710_device_realize(void) "Device realized" @@ -324,16 +348,13 @@ lasi_ncr710_device_reset(void) "Device reset" lasi_ncr710_reg_read(uint32_t addr, uint32_t val, unsigned size) "addr=3D0= x%03x val=3D0x%08x size=3D%u" lasi_ncr710_reg_write(uint32_t addr, uint32_t val, unsigned size) "addr=3D= 0x%03x val=3D0x%08x size=3D%u" lasi_ncr710_reg_read_id(uint32_t hw_type, uint32_t sversion, uint32_t val)= "hw_type=3D%u sversion=3D0x%04x val=3D0x%08x" -lasi_ncr710_reg_read_hversion(uint32_t hversion) "LASI NCR710: HVersion re= ad -> 0x%02x" -lasi_ncr710_reg_forward_read(uint32_t addr, uint32_t val) "LASI NCR710: Fo= rward read to NCR710 core addr=3D0x%03x val=3D0x%08x" -lasi_ncr710_reg_forward_write(uint32_t addr, uint32_t val) "LASI NCR710: F= orward write to NCR710 core addr=3D0x%03x val=3D0x%08x" -lasi_ncr710_command_complete(uint32_t status, size_t resid) "LASI NCR710: = Command complete status=3D0x%02x resid=3D%zu" -lasi_ncr710_transfer_data(uint32_t len) "LASI NCR710: Transfer data len=3D= %u" -lasi_ncr710_request_cancelled(void *req) "LASI NCR710: Request cancelled r= eq=3D%p" +lasi_ncr710_reg_read_hversion(uint32_t hversion) "HVersion read -> 0x%02x" +lasi_ncr710_reg_forward_read(uint32_t addr, uint32_t val) "Forward read to= NCR710 core addr=3D0x%03x val=3D0x%08x" +lasi_ncr710_reg_forward_write(uint32_t addr, uint32_t val) "Forward write = to NCR710 core addr=3D0x%03x val=3D0x%08x" +lasi_ncr710_command_complete(uint32_t status, size_t resid) "Command compl= ete status=3D0x%02x resid=3D%zu" +lasi_ncr710_transfer_data(uint32_t len) "Transfer data len=3D%u" +lasi_ncr710_request_cancelled(void *req) "Request cancelled req=3D%p" lasi_ncr710_timers_initialized(uint64_t reselection) "Timers: reselection= =3D0x%" PRIx64 -lasi_ncr710_handle_legacy_cmdline(int busnr, int found_drives) "LASI NCR71= 0: Handle legacy cmdline busnr=3D%d found_drives=3D%d" -lasi_ncr710_legacy_drive_found(int busnr, int unit) "LASI NCR710: Found le= gacy drive at bus=3D%d unit=3D%d" -lasi_ncr710_scsi_device_created(const char *type) "LASI NCR710: SCSI devic= e created: %s" =20 # virtio-scsi.c virtio_scsi_cmd_req(int lun, uint32_t tag, uint8_t cmd) "virtio_scsi_cmd_r= eq lun=3D%u tag=3D0x%x cmd=3D0x%x" --=20 2.43.0 From nobody Sun Jul 26 11:07:24 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) header.from=techtravels.org ARC-Seal: i=1; a=rsa-sha256; t=1782942648; cv=none; d=zohomail.com; s=zohoarc; b=OoaNy6aMKZPzOihm7LYqAxIN1oC7cl6Kea9VMIXLj4Ds6rqr6FvCm0ikqTlRF0dZFo9EqsWIQPJ5gMXdQaAtQ6b2ACJVeZhf03BJQxrmU4ahCgxC80uVLaGMuUiC2OD+mgBZcImh3x2+k6xu/vyYua5H1ISej5XH1c3FtuZc0zA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1782942648; 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=HrnERIchmfr98hxVHqmX5VPYqWMD94gizr69iCT6pG0=; b=QK31quyg5vFbUT1G4wFJ4dZfP8pmFAF6oP6T8YZVdAyEgLfCbkL8l8evGhKIfRzkTKuXjrOkLJ9TDFT9OjpPsOEUAouujhWSRCUP/H2ItMJER7z2qk7BBO1M4Dr2em4m2ByJVHOTTthrzV108yWce3QVExcoVtw88XMk70I8GoQ= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=@techtravels.org; 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=reject dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1782942648212752.802104408114; Wed, 1 Jul 2026 14:50:48 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wf2oa-00020o-Fz; Wed, 01 Jul 2026 17:49:52 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2oZ-00020f-KU for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:49:51 -0400 Received: from a4i502.smtp2go.com ([158.120.81.246]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wf2oW-0003qr-QF for qemu-devel@nongnu.org; Wed, 01 Jul 2026 17:49:51 -0400 Received: from [10.34.231.120] (helo=SmtpCorp) by smtpcorp.com with esmtpsa (TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.99.4) (envelope-from ) id 1wf2ZG-3Quys430272-ovsJ; Wed, 01 Jul 2026 21:34:02 +0000 Received: from [10.91.249.253] (helo=antecquad..) by smtpcorp.com with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.99.4) (envelope-from ) id 1wf2ZF-FnQW0hPscrZ-JVFq; Wed, 01 Jul 2026 21:34:01 +0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=techtravels.org; i=@techtravels.org; q=dns/txt; s=s1175899; t=1782942585; h=from : subject : to : message-id : date; bh=HrnERIchmfr98hxVHqmX5VPYqWMD94gizr69iCT6pG0=; b=EABINQUOM2fXqRb284pfBzyV913bDdLkBtmEZTfzxnEjywEzwTqMEpzXzmSPQu102fThu hsvdrOEgWxecfON9wZbMrHoq3x4R0Dzh1MpMbTo9MPdtrd/GPcJyENeGPd77AmV/gxqc+ZS sC8tdHpXnmTGadHR2uXBcqX+6EV3C/qlhQyWMmjDcJL6rVdj329Oja6oPSLF9pgV9m/VB4A PFJDL4SmVDpvmv32T8yj87U4XHuCokUQm5d2osPTfWON2Oo3n/nq6X2PeDU4dnCZlL6cWJm dWTYwIdGzg28UQoYIPgCHgYxmQlhc3elDNWBexO/vpIehD23LLvK4qB78Kdw== From: Keith Monahan To: qemu-devel@nongnu.org Cc: Richard Henderson , Helge Deller , Paolo Bonzini , Fam Zheng , Peter Maydell , BALATON Zoltan , Keith Monahan Subject: [PATCH v2 4/4] hw/scsi/ncr53c710: support HP-UX overlapped tagged commands Date: Wed, 1 Jul 2026 17:33:59 -0400 Message-ID: <20260701213359.1855870-5-keith@techtravels.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260701213359.1855870-1-keith@techtravels.org> References: <20260701213359.1855870-1-keith@techtravels.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Report-Abuse: Please forward a copy of this message, including all headers, to Feedback-ID: 1175899m:1175899aL0uvZj:1175899svwHxevZJG X-smtpcorp-track: LOMAs7nh8Zoa.Avp51DCavJgT.utI04wir3up 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=lists1p.gnu.org; Received-SPF: pass client-ip=158.120.81.246; envelope-from=bounce.i3sbe3ri2szxq23=i4g2cd7udsv6=3qoiy9vdttodoh@em1175899.techtravels.org; helo=a4i502.smtp2go.com 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, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-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: qemu development 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 @techtravels.org) X-ZM-MESSAGEID: 1782942650984158500 Content-Type: text/plain; charset="utf-8" The HP-UX 10.20 c720 driver overlaps tagged commands and recovers a disconnected command by parking the SCRIPTS engine on a Wait Reselect instruction with the reselection interrupt disabled (SIEN.SEL clear), which the base engine cannot service. Add the target-initiated disconnect/reselect and asynchronous completion path it needs. A tagged command whose data is not yet ready is disconnected and queued (only under a Wait Reselect driver; untagged transfers and reselection-interrupt drivers keep the connected path), and reselected once its data is ready and the engine is parked at Wait Reselect. Completions are delivered asynchronously through a bottom half rather than reentrantly inside the interpreter, and the interpreter guards the reentrancy class behind CVE-2023-0330. The c720 driver also issues its "start next command" SIGP kick with CPU interrupts masked and then spins, so after a register write starts an asynchronous transfer the model drives it to completion before returning to the guest (a bounded synchronous drain under the BQL, as IDE and MegaSAS do, falling back to asynchronous completion if it cannot advance), and a SIGP kick that lands on a stopped engine is held and replayed once it parks. The HP-UX 10.20 install kernel is also nonconformant in negotiation: its self-modifying SCRIPTS reads a second message byte after a MESSAGE REJECT and hangs unless it is SAVE DATA POINTERS, while NetBSD's osiop resets the bus if it sees that byte. The two are told apart by ATN state as the reject is consumed (HP-UX has deasserted ATN, a conformant initiator has not), so the extra byte is appended only for HP-UX. With this, HP-UX 10.20 installs and reboots into the installed system on both the Ignite (16700A) media and a software install that overlaps tagged commands. Signed-off-by: Keith Monahan --- hw/scsi/ncr53c710.c | 295 +++++++++++++++++++++++++++++++++++++++++--- hw/scsi/ncr53c710.h | 19 +++ 2 files changed, 295 insertions(+), 19 deletions(-) diff --git a/hw/scsi/ncr53c710.c b/hw/scsi/ncr53c710.c index 57cd7d91a4..1dcc111cc9 100644 --- a/hw/scsi/ncr53c710.c +++ b/hw/scsi/ncr53c710.c @@ -119,6 +119,14 @@ =20 #define NCR710_BUF_SIZE 4096 =20 +/* + * Safety bound on the synchronous completion drain loop in ncr710_reg_wri= te(). + * 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 =3D 0, /* SCRIPTS running or stopped */ @@ -167,6 +175,7 @@ static const char *ncr710_reg_name(int offset) 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); =20 /* @@ -225,6 +234,14 @@ void ncr710_soft_reset(NCR710State *s) s->select_tag =3D 0; s->command_complete =3D 0; s->script_running =3D false; + s->sigp_pending_resume =3D 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_scri= pt + * frames are still on the stack; those frames decrement the counter a= s they + * unwind, so zeroing it would underflow negative and defeat the reent= rancy + * guard. It is zero at device init and balances itself to zero. + */ =20 s->scntl0 =3D 0xc0; /* full arbitration */ s->scntl1 =3D 0; @@ -371,6 +388,21 @@ static int ncr710_bad_phase(NCR710State *s, int new_ph= ase) static void ncr710_resume_script(NCR710State *s) { trace_ncr710_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 insi= de + * scsi_req_enqueue(). Reentering ncr710_execute_script() here wo= uld + * advance s->dsp underneath the outer interpreter frame and can + * double execute SCRIPTS (the reentrancy class behind CVE-2023-03= 30 + * 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 =3D NCR710_NOWAIT; + return; + } if (s->waiting !=3D NCR710_DMA_SCRIPTS) { s->waiting =3D NCR710_NOWAIT; ncr710_execute_script(s); @@ -394,6 +426,26 @@ static void ncr710_bad_selection(NCR710State *s, uint3= 2_t id) ncr710_disconnect(s); } =20 +/* + * 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 =3D opaque; + + if (!s->issue_pending) { + return; + } + s->issue_pending =3D false; + if (s->current =3D=3D NULL || s->current->orphan) { + return; + } + scsi_req_continue(s->current->req); +} + static void ncr710_do_dma(NCR710State *s, int out) { uint32_t count; @@ -436,7 +488,30 @@ static void ncr710_do_dma(NCR710State *s, int out) p->dma_len -=3D count; if (p->dma_len =3D=3D 0) { p->dma_buf =3D NULL; - scsi_req_continue(req); + /* + * Defer the completing continue (Block Move satisfied, dbc =3D=3D= 0) of a + * tagged disconnect/reselect transfer to a bottom half: its + * scsi_req_continue can trigger a cached backend (CD-ROM) complet= ion + * reentrantly inside execute_script, collapsing the disconnect and + * completion INT into the guest MMIO write driving the engine. T= hat + * lets the HP-UX c720 driver's next-command SIGP kick race ahead = of its + * completion ISR and wedge the install. Deferring makes completi= on an + * autonomous async event, like the disk-backed path; + * the engine parks at DMA_IN_PROGRESS (set by the caller) meanwhi= le. + */ + if ((p->tag & NCR710_TAG_VALID) && s->dbc =3D=3D 0) { + s->issue_pending =3D true; + aio_bh_schedule_oneshot(qemu_get_aio_context(), ncr710_issue_b= h, s); + } else { + /* + * Complete inline for untagged transfers (the connected, poll= ed + * early-boot and PDC path: SeaBIOS, ISL, pre-driver probe) and + * mid-transfer continues (dbc > 0) that fetch more data witho= ut + * completing the command (deferring every chunk would stall l= arge + * multi-chunk reads). + */ + scsi_req_continue(req); + } } else { p->dma_buf +=3D count; ncr710_resume_script(s); @@ -478,6 +553,20 @@ static NCR710Request *ncr710_get_pending_req(NCR710Sta= te *s) return NULL; } =20 +/* Move the in-flight command to the disconnected queue (target disconnect= ). */ +static void ncr710_queue_command(NCR710State *s) +{ + NCR710Request *p =3D s->current; + + trace_ncr710_queue_command(p->tag); + assert(s->current !=3D NULL); + assert(s->current->dma_len =3D=3D 0); + QTAILQ_INSERT_TAIL(&s->queue, s->current, next); + s->current =3D NULL; + p->pending =3D 0; + p->out =3D (s->sstat2 & PHASE_MASK) =3D=3D PHASE_DO; +} + /* Reselect a disconnected command to resume (continue) its data transfer.= */ static void ncr710_reselect(NCR710State *s, NCR710Request *p) { @@ -528,20 +617,51 @@ static int ncr710_queue_req(NCR710State *s, SCSIReque= st *req, uint32_t len) return 1; } =20 -/* SCRIPTS Wait Reselect: reconnect a ready queued command, else park. */ -static void ncr710_wait_reselect(NCR710State *s) +/* + * Deferred half of ncr710_wait_reselect(): reconnect a disconnected comma= nd + * whose data is ready and resume the engine, on a clean (bottom half) sta= ck. + * Guarded so it does nothing if a guest SIGP wake or an inline transfer_d= ata + * reselection moved the engine on while this was pending. + */ +static void ncr710_reselect_bh(void *opaque) { + NCR710State *s =3D opaque; NCR710Request *p; =20 - if (s->current) { + if (s->current !=3D NULL || s->waiting !=3D NCR710_WAIT_RESELECT) { return; } p =3D ncr710_get_pending_req(s); - if (p) { - ncr710_reselect(s, p); + if (p =3D=3D NULL) { + return; } - if (s->current =3D=3D NULL) { - s->waiting =3D NCR710_WAIT_RESELECT; + ncr710_reselect(s, p); + s->waiting =3D 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 dro= pping + * 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 =3D NCR710_WAIT_RESELECT; + if (ncr710_get_pending_req(s)) { + aio_bh_schedule_oneshot(qemu_get_aio_context(), ncr710_reselect_bh= , s); } } =20 @@ -667,7 +787,36 @@ static void ncr710_do_command(NCR710State *s) } if (!s->command_complete) { if (n) { - /* Stay connected; the block move waits for transfer_data. */ + 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 driv= er + * that recovers a disconnected command by parking the SCR= IPTS + * 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() reselec= ts 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. L= inux's + * 53c700), which expect a target-reselect interrupt w= ith + * 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 =3D 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 abov= e). + */ } else { /* * Async no data command (e.g. SYNCHRONIZE CACHE from Linux @@ -741,6 +890,17 @@ static void ncr710_do_msgin(NCR710State *s) 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 =3D=3D 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 d= river * samples MSG IN at the script interrupt and the lazy MSG IN = -> @@ -899,14 +1059,13 @@ static void ncr710_execute_script(NCR710State *s) uint32_t addr; int opcode; int insn_processed =3D 0; - static int reentrancy_level; =20 if (s->waiting =3D=3D NCR710_WAIT_SCRIPTS) { timer_del(s->scripts_timer); s->waiting =3D NCR710_NOWAIT; } =20 - reentrancy_level++; + s->reentrancy_level++; s->script_running =3D true; again: /* @@ -914,11 +1073,11 @@ again: * waiting on a memory change make progress), and guard against a scri= pt * that retriggers itself (CVE-2023-0330) via the reentrancy counter. */ - if (++insn_processed > NCR710_MAX_INSN || reentrancy_level > 8) { + if (++insn_processed > NCR710_MAX_INSN || s->reentrancy_level > 8) { trace_ncr710_script_yield(insn_processed); s->waiting =3D NCR710_WAIT_SCRIPTS; ncr710_scripts_timer_start(s); - reentrancy_level--; + s->reentrancy_level--; return; } =20 @@ -1065,11 +1224,23 @@ again: break; case 2: /* Wait Reselect */ /* - * A SIGP "start next command" kick jumps to the dispatch - * address latched in DNAD; otherwise reconnect a ready qu= eued - * command, or park. + * Two things release a Wait Reselect: the driver's SIGP "= start + * next command" kick (jump to the dispatch address latche= d in + * DNAD), or a disconnected tagged command becoming ready = to + * reselect. A SIGP that arrived while the engine was hal= ted at + * a completion INT is held in sigp_pending_resume and con= sumed + * here. Otherwise reconnect a ready queued command, or p= ark. */ - if (s->istat & NCR710_ISTAT_SIGP) { + if ((s->istat & NCR710_ISTAT_SIGP) || s->sigp_pending_resu= me) { + /* + * The SIGP divert is a one-shot: clear the latch as i= t is + * consumed so a subsequent Wait Reselect does not re-= fire + * on a stale SIGP, and so SIGP is never left set acro= ss a + * real reselection (Jun92 p.53: an active SIGP at + * reselection disables target-mode auto-switching). + */ + s->istat &=3D ~NCR710_ISTAT_SIGP; + s->sigp_pending_resume =3D false; s->dsp =3D s->dnad; } else if (!ncr710_irq_on_rsl(s)) { ncr710_wait_reselect(s); @@ -1266,7 +1437,7 @@ again: } } =20 - reentrancy_level--; + s->reentrancy_level--; } =20 #define CASE_GET_REG24(name, addr) \ @@ -1528,6 +1699,52 @@ static void ncr710_reg_writeb(NCR710State *s, int of= fset, uint8_t val) if (val & NCR710_ISTAT_ABRT) { ncr710_script_dma_interrupt(s, NCR710_DSTAT_ABRT); } + if (s->waiting =3D=3D 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 (Ju= n92 + * p.69 step 3). This SELECT must take priority over any + * disconnected command pending reselection: on real silicon t= he + * 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 com= mand + * 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_awoken(); + s->waiting =3D NCR710_NOWAIT; + s->dsp =3D s->dnad; + ncr710_execute_script(s); + } else if ((val & NCR710_ISTAT_SIGP) && !s->script_running && + s->waiting =3D=3D 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, destr= oying + * 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 asserte= d 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, ca= se 2). + */ + s->istat &=3D ~NCR710_ISTAT_SIGP; + s->sigp_pending_resume =3D true; + } break; case NCR710_CTEST8: /* @@ -1599,7 +1816,40 @@ uint64_t ncr710_reg_read(NCR710State *s, hwaddr addr= , unsigned size) =20 void ncr710_reg_write(NCR710State *s, hwaddr addr, uint64_t val, unsigned = size) { + unsigned drained =3D 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 interru= pt 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 sti= ll + * pending asynchronous completion (its bottom half has not run) would= land + * in that masked window unserviced, and the driver's watchdog would r= eset + * the bus. Real hardware takes the higher priority completion interr= upt + * before that poll. + * + * A deferred continue (ncr710_issue_bh, for a tagged reselect transfe= r) 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_ITE= RS and + * falls back to asynchronous completion (trace_ncr710_drain_exhausted= ) if it + * cannot make progress. + */ + while (s->waiting =3D=3D NCR710_DMA_IN_PROGRESS && s->current !=3D NUL= L && + s->current->req !=3D NULL) { + if (drained++ >=3D NCR710_DRAIN_MAX_ITERS) { + trace_ncr710_drain_exhausted(drained); + break; + } + if (s->issue_pending) { + ncr710_issue_bh(s); + } else if (s->current->req->dev->conf.blk !=3D NULL) { + blk_drain(s->current->req->dev->conf.blk); + } else { + break; /* nothing deferred and no backend to drain */ + } + } } =20 static int ncr710_pre_save(void *opaque) @@ -1644,7 +1894,7 @@ const VMStateDescription vmstate_ncr710 =3D { * raised in lockstep so a legacy stream is rejected rather than parsed * positionally into the new layout. */ - .version_id =3D 2, + .version_id =3D 3, .minimum_version_id =3D 2, .pre_save =3D ncr710_pre_save, .post_load =3D ncr710_post_load, @@ -1659,6 +1909,13 @@ const VMStateDescription vmstate_ncr710 =3D { 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 w= hile + * 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), =20 VMSTATE_UINT8(scntl0, NCR710State), VMSTATE_UINT8(scntl1, NCR710State), diff --git a/hw/scsi/ncr53c710.h b/hw/scsi/ncr53c710.h index 7bcefae902..4e15c9d0c1 100644 --- a/hw/scsi/ncr53c710.h +++ b/hw/scsi/ncr53c710.h @@ -95,6 +95,25 @@ typedef struct NCR710State { 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=3D=3DNOWAIT) instead of parked = at Wait + * Reselect, so the normal SIGP wake is skipped. The ISTAT write clea= rs the + * SIGP latch (presenting the kick as "consumed" to the driver's poll)= and + * sets this flag instead; the Wait Reselect handler (execute_script c= ase 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 i= ts + * completion arrives asynchronously, not reentrantly inside the engin= e. + */ + 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 --=20 2.43.0