File:  [The Machine Emulator] / tme / ic / ncr53c9x.c
Revision 1.1.1.2 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 16:43:39 2018 UTC (3 years, 9 months ago) by root
Branches: heeltoe, fredette, MAIN
CVS tags: tme-0_8heeltoe, tme-0_8, HEAD
tme-0.8

/* $Id: ncr53c9x.c,v 1.1.1.2 2018-04-24 16:43:39 root Exp $ */

/* ic/ncr53c9x.c - implementation of NCR 53c9x emulation: */

/*
 * Copyright (c) 2005, 2006 Matt Fredette
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt Fredette.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <tme/common.h>
_TME_RCSID("$Id: ncr53c9x.c,v 1.1.1.2 2018-04-24 16:43:39 root Exp $");

/* includes: */
#include <tme/generic/bus-device.h>
#include <tme/generic/scsi.h>
#include <sys/time.h>

/* TODO: */

/* _tme_ncr53c9x_reset() needs to know how to reset the device entirely,
   needs a set of flags or values to distinguish different reset types */

/* XXX FIXME - we need to call _tme_ncr53c9x_disconnect(ncr53c9x)
   when all commands that may disconnect from the bus do so, since normal
   command completion does not output a zero scsi_control.  for example,
   more error cases during selection probably need this */

/* macros: */

/* NCR 53c9x variants: */
#define TME_NCR53C9X_VARIANT_NULL	(0)
#define TME_NCR53C9X_VARIANT_ESP100	(1)
#define TME_NCR53C9X_VARIANT_ESP100A	(2)

/* most registers are not read-write: */
#define TME_NCR53C9X_REG_RO(x)		(x)
#define TME_NCR53C9X_REG_WO(x)		((x) + TME_NCR53C9X_SIZ_REGS)
#define TME_NCR53C9X_REG_RW(x)		TME_NCR53C9X_REG_RO(x)
#define TME_NCR53C9X_REG_INDEX(x)	((x) % TME_NCR53C9X_SIZ_REGS)

/* register offsets: */
#define TME_NCR53C9X_REG_CTC_LSB	TME_NCR53C9X_REG_RO(0x0)
#define TME_NCR53C9X_REG_CTC_MSB	TME_NCR53C9X_REG_RO(0x1)
#define TME_NCR53C9X_REG_STC_LSB	TME_NCR53C9X_REG_WO(0x0)
#define TME_NCR53C9X_REG_STC_MSB	TME_NCR53C9X_REG_WO(0x1)
#define TME_NCR53C9X_REG_FIFO		TME_NCR53C9X_REG_RW(0x2)
#define TME_NCR53C9X_REG_CMD		TME_NCR53C9X_REG_RW(0x3)
#define TME_NCR53C9X_REG_STAT		TME_NCR53C9X_REG_RO(0x4)
#define TME_NCR53C9X_REG_SDID		TME_NCR53C9X_REG_WO(0x4)
#define TME_NCR53C9X_REG_INST		TME_NCR53C9X_REG_RO(0x5)
#define TME_NCR53C9X_REG_TIMEOUT	TME_NCR53C9X_REG_WO(0x5)
#define TME_NCR53C9X_REG_IS		TME_NCR53C9X_REG_RO(0x6)
#define TME_NCR53C9X_REG_SYNCH_PERIOD	TME_NCR53C9X_REG_WO(0x6)
#define TME_NCR53C9X_REG_CFIS		TME_NCR53C9X_REG_RO(0x7)
#define TME_NCR53C9X_REG_SYNCH_OFFSET	TME_NCR53C9X_REG_WO(0x7)
#define TME_NCR53C9X_REG_CONTROL1	TME_NCR53C9X_REG_RW(0x8)
#define TME_NCR53C9X_REG_CLOCK_FACTOR	TME_NCR53C9X_REG_WO(0x9)
#define TME_NCR53C9X_REG_TEST		TME_NCR53C9X_REG_WO(0xa)
#define TME_NCR53C9X_REG_CONTROL2	TME_NCR53C9X_REG_RW(0xb)
#define TME_NCR53C9X_REG_CONTROL3	TME_NCR53C9X_REG_RW(0xc)
#define TME_NCR53C9X_REG_ALIGN		TME_NCR53C9X_REG_WO(0xf)
#define TME_NCR53C9X_SIZ_REGS		(0x10)

/* a few registers are read/write: */
#define TME_NCR53C9X_REGS_RW					\
  (TME_BIT(TME_NCR53C9X_REG_INDEX(TME_NCR53C9X_REG_FIFO))	\
   | TME_BIT(TME_NCR53C9X_REG_INDEX(TME_NCR53C9X_REG_CMD))	\
   | TME_BIT(TME_NCR53C9X_REG_INDEX(TME_NCR53C9X_REG_CONTROL1))	\
   | TME_BIT(TME_NCR53C9X_REG_INDEX(TME_NCR53C9X_REG_CONTROL2))	\
   | TME_BIT(TME_NCR53C9X_REG_INDEX(TME_NCR53C9X_REG_CONTROL3)))

/* fields in the Command Register: */
#define TME_NCR53C9X_CMD_MASK		(0x7f)
#define  TME_NCR53C9X_CMD_NOP		 (0x00)
#define  TME_NCR53C9X_CMD_CLEAR_FIFO	 (0x01)
#define  TME_NCR53C9X_CMD_RESET		 (0x02)
#define  TME_NCR53C9X_CMD_RESET_BUS	 (0x03)
#define  TME_NCR53C9X_CMD_DMA_STOP	 (0x04)
#define  TME_NCR53C9X_CMD_TRANSFER	 (0x10)
#define  TME_NCR53C9X_CMD_ICCS		 (0x11)
#define  TME_NCR53C9X_CMD_MSG_ACCEPTED	 (0x12)
#define  TME_NCR53C9X_CMD_TRANSFER_PAD	 (0x18)
#define  TME_NCR53C9X_CMD_ATN_SET	 (0x1a)
#define  TME_NCR53C9X_CMD_ATN_RESET	 (0x1b)
#define  TME_NCR53C9X_CMD_SEND_MSG	 (0x20)
#define  TME_NCR53C9X_CMD_SEND_STATUS	 (0x21)
#define  TME_NCR53C9X_CMD_SEND_DATA	 (0x22)
#define  TME_NCR53C9X_CMD_DISCONNECT_ST	 (0x23)
#define  TME_NCR53C9X_CMD_TERMINATE	 (0x24)
#define  TME_NCR53C9X_CMD_TCCS		 (0x25)
#define  TME_NCR53C9X_CMD_DISCONNECT	 (0x27)
#define  TME_NCR53C9X_CMD_RECV_MSG	 (0x28)
#define  TME_NCR53C9X_CMD_RECV_CMD	 (0x29)
#define  TME_NCR53C9X_CMD_RECV_DATA	 (0x2a)
#define  TME_NCR53C9X_CMD_RCS		 (0x2b)
#define  TME_NCR53C9X_CMD_RESELECT	 (0x40)
#define  TME_NCR53C9X_CMD_SELECT	 (0x41)
#define  TME_NCR53C9X_CMD_SELECT_ATN	 (0x42)
#define  TME_NCR53C9X_CMD_SELECT_ATN_STOP (0x43)
#define  TME_NCR53C9X_CMD_SELECT_ENABLE	 (0x44)
#define  TME_NCR53C9X_CMD_SELECT_DISABLE (0x45)
#define  TME_NCR53C9X_CMD_SELECT_ATN3	 (0x46)
#define  TME_NCR53C9X_CMD_RESELECT3	 (0x47)
#define TME_NCR53C9X_CMD_DMA		TME_BIT(7)

/* bits in the Status register: */
#define TME_NCR53C9X_STAT_I_O		TME_BIT(0)
#define TME_NCR53C9X_STAT_C_D		TME_BIT(1)
#define TME_NCR53C9X_STAT_MSG		TME_BIT(2)
#define TME_NCR53C9X_STAT_GCV		TME_BIT(3)
#define TME_NCR53C9X_STAT_CTZ		TME_BIT(4)
#define TME_NCR53C9X_STAT_PE		TME_BIT(5)
#define TME_NCR53C9X_STAT_IOE		TME_BIT(6)
#define TME_NCR53C9X_STAT_INT		TME_BIT(7)

/* bits in the SCSI Destination ID register: */
#define TME_NCR53C9X_SDID_DID		(0x07)

/* bits in the Interrupt Status register: */
#define TME_NCR53C9X_INST_SEL		TME_BIT(0)
#define TME_NCR53C9X_INST_SELA		TME_BIT(1)
#define TME_NCR53C9X_INST_RESEL		TME_BIT(2)
#define TME_NCR53C9X_INST_SO		TME_BIT(3)
#define TME_NCR53C9X_INST_SR		TME_BIT(4)
#define TME_NCR53C9X_INST_DIS		TME_BIT(5)
#define TME_NCR53C9X_INST_ICMD		TME_BIT(6)
#define TME_NCR53C9X_INST_SRST		TME_BIT(7)

/* bits in the Internal State register: */
#define TME_NCR53C9X_IS_SOF		TME_BIT(3)

/* bits in the Current FIFO/Internal State register: */
#define TME_NCR53C9X_CFIS_IS		(0xe0)
#define TME_NCR53C9X_CFIS_CF		(0x1f)

/* bits in Control register one: */
#define TME_NCR53C9X_CONTROL1_ID	(0x07)
#define TME_NCR53C9X_CONTROL1_STE	TME_BIT(3)
#define TME_NCR53C9X_CONTROL1_PERE	TME_BIT(4)
#define TME_NCR53C9X_CONTROL1_PTE	TME_BIT(5)
#define TME_NCR53C9X_CONTROL1_DISR	TME_BIT(6)
#define TME_NCR53C9X_CONTROL1_ETM	TME_BIT(7)

/* bits in the Test register: */
#define TME_NCR53C9X_TEST_FTM		TME_BIT(0)
#define TME_NCR53C9X_TEST_FIM		TME_BIT(1)
#define TME_NCR53C9X_TEST_FHI		TME_BIT(2)

/* bits in Control register two: */
#define TME_NCR53C9X_CONTROL2_PGDP	TME_BIT(0)
#define TME_NCR53C9X_CONTROL2_PGRP	TME_BIT(1)
#define TME_NCR53C9X_CONTROL2_ACDPE	TME_BIT(2)
#define TME_NCR53C9X_CONTROL2_S2FE	TME_BIT(3)
#define TME_NCR53C9X_CONTROL2_TSDR	TME_BIT(4)
#define TME_NCR53C9X_CONTROL2_SBO	TME_BIT(5)
#define TME_NCR53C9X_CONTROL2_LSP	TME_BIT(6)
#define TME_NCR53C9X_CONTROL2_DAE	TME_BIT(7)

/* bits in Control register three: */
#define TME_NCR53C9X_CONTROL3_BS8	TME_BIT(0)
#define TME_NCR53C9X_CONTROL3_MDM	TME_BIT(1)
#define TME_NCR53C9X_CONTROL3_LBTM	TME_BIT(2)

/* predicates: */
#define TME_NCR53C9X_HAS_CONTROL2_LSP(ncr53c9x) \
  (FALSE)
#define TME_NCR53C9X_HAS_CONTROL2_DAE(ncr53c9x) \
  (FALSE)

/* major modes: */
#define TME_NCR53C9X_MODE_IDLE		(0)
#define TME_NCR53C9X_MODE_INITIATOR	(1)
#define TME_NCR53C9X_MODE_TARGET	(2)

/* special command sequence numbers: */
#define TME_NCR53C9X_CMD_SEQUENCE_DONE	(0x100)
#define TME_NCR53C9X_CMD_SEQUENCE_UNDEF	(0x101)

/* reset types: */
#define TME_NCR53C9X_RESET_FLAG_CMD	TME_BIT(0)
#define TME_NCR53C9X_RESET_WHICH	TME_BIT(1)
#define TME_NCR53C9X_RESET_DEVICE	(0 * TME_NCR53C9X_RESET_WHICH)
#define TME_NCR53C9X_RESET_BUS		(1 * TME_NCR53C9X_RESET_WHICH)

/* this value must match none of the SCSI bus phases: */
#define _TME_SCSI_PHASE_UNDEF			(TME_SCSI_PHASE_MESSAGE_IN * 2)

/* the callout flags: */
#define TME_NCR53C9X_CALLOUTS_RUNNING		TME_BIT(0)
#define TME_NCR53C9X_CALLOUTS_MASK		(-2)
#define TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA	(0x3 << 2)
#define TME_NCR53C9X_CALLOUT_TERMINAL_DMA	(0x3 << 4)
#define TME_NCR53C9X_CALLOUT_INT		(0x3 << 6)
#define TME_NCR53C9X_CALLOUT_SCSI_CYCLE		(0x3 << 8)
#define TME_NCR53C9X_CALLOUT_RUNNING(x)		((x) & ((x) << 1))

#if 1
#define TME_NCR53C9X_DEBUG
#endif

/* structures: */

/* the IC: */
struct tme_ncr53c9x {

  /* our simple bus device header: */
  struct tme_bus_device tme_ncr53c9x_device;
#define tme_ncr53c9x_element tme_ncr53c9x_device.tme_bus_device_element

  /* the mutex protecting the card: */
  tme_mutex_t tme_ncr53c9x_mutex;

  /* the SCSI bus connection: */
  struct tme_scsi_connection *tme_ncr53c9x_scsi_connection;

  /* the callout flags: */
  int tme_ncr53c9x_callout_flags;

  /* the variant we are emulating: */
  unsigned int tme_ncr53c9x_variant;

  /* it's easiest to just model the registers as a chunk of memory.
     we have twice as much memory as address space, because most
     addresses read one register, and write another: */
  tme_uint8_t tme_ncr53c9x_regs[TME_NCR53C9X_SIZ_REGS * 2];

  /* the current major mode: */
  unsigned int tme_ncr53c9x_mode;

  /* the desired output SCSI cycle: */
  tme_scsi_control_t tme_ncr53c9x_out_scsi_control;
  tme_scsi_data_t tme_ncr53c9x_out_scsi_data;
  tme_uint32_t tme_ncr53c9x_out_scsi_events;
  tme_uint32_t tme_ncr53c9x_out_scsi_actions;

  /* the active output SCSI cycle: */
  tme_scsi_control_t tme_ncr53c9x_active_scsi_control;
  tme_scsi_data_t tme_ncr53c9x_active_scsi_data;
  tme_uint32_t tme_ncr53c9x_active_scsi_events;
  tme_uint32_t tme_ncr53c9x_active_scsi_actions;
  tme_uint32_t tme_ncr53c9x_active_scsi_cycle_marker;
  unsigned long tme_ncr53c9x_active_scsi_dma_resid;

  /* the last input SCSI cycle: */
  tme_scsi_control_t tme_ncr53c9x_in_scsi_control;
  tme_scsi_data_t tme_ncr53c9x_in_scsi_data;
  tme_uint32_t tme_ncr53c9x_in_scsi_events;
  tme_uint32_t tme_ncr53c9x_in_scsi_actions;

  /* if our interrupt line is currently asserted: */
  int tme_ncr53c9x_last_int_asserted;

  /* the command FIFO: */
  unsigned int tme_ncr53c9x_fifo_cmd_head;
  unsigned int tme_ncr53c9x_fifo_cmd_tail;
  tme_uint8_t tme_ncr53c9x_fifo_cmd[3];

  /* the data FIFO: */
  unsigned int tme_ncr53c9x_fifo_data_head;
  unsigned int tme_ncr53c9x_fifo_data_tail;
  tme_uint8_t tme_ncr53c9x_fifo_data[16];

  /* the status FIFO: */
  unsigned int tme_ncr53c9x_fifo_status_head;
  unsigned int tme_ncr53c9x_fifo_status_tail;
  struct {
    tme_uint8_t tme_ncr53c9x_status_stat;
    tme_uint8_t tme_ncr53c9x_status_is;
    tme_uint8_t tme_ncr53c9x_status_inst;
  } tme_ncr53c9x_fifo_status[3];

  /* the sequence number of the current command: */
  unsigned int tme_ncr53c9x_cmd_sequence;

  /* this is nonzero if DMA is running: */
  int tme_ncr53c9x_dma_running;

  /* our DMA TLB set: */
  struct tme_bus_tlb tme_ncr53c9x_dma_tlb;
  int tme_ncr53c9x_dma_tlb_added;

  /* our DMA pseudoaddress: */
  tme_bus_addr32_t tme_ncr53c9x_dma_address;

  /* if this is nonzero, a SCSI reset is detected: */
  int tme_ncr53c9x_detected_scsi_reset;

  /* the command sequence label for a SCSI BSY signal lost handler: */
  unsigned int tme_ncr53c9x_cmd_sequence_bsy_lost;

  /* the command sequence label for a SCSI bus phase mismatch handler,
     and the latched SCSI bus phase: */
  unsigned int tme_ncr53c9x_cmd_sequence_phase_mismatch;
  tme_scsi_control_t tme_ncr53c9x_latched_phase;

  /* the command sequence label for a timeout handler, the timeout
     length, and the timeout absolute time: */
  unsigned int tme_ncr53c9x_cmd_sequence_timeout;
  struct timeval tme_ncr53c9x_timeout_length;
  struct timeval tme_ncr53c9x_timeout_time;

  /* the command sequence SCSI bus transfer residual: */
  unsigned long tme_ncr53c9x_transfer_resid;

  /* if this is nonzero, this is the state in a machine that detects
     the actual SCSI bus transfer residual, based on the SCSI bus
     phase and the data being transferred: */
  tme_uint32_t tme_ncr53c9x_transfer_resid_detect_state;

  /* the timeout thread condition: */
  tme_cond_t tme_ncr53c9x_timeout_cond;
};

/* this locks the mutex: */
static void
_tme_ncr53c9x_lock(void *_ncr53c9x,
		   unsigned int locks)
{
  struct tme_ncr53c9x *ncr53c9x;

  /* recover our data structure: */
  ncr53c9x = (struct tme_ncr53c9x *) _ncr53c9x;

  /* lock the mutex: */
  tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);
}

/* this unlocks the mutex: */
static void
_tme_ncr53c9x_unlock(void *_ncr53c9x,
		     unsigned int locks)
{
  struct tme_ncr53c9x *ncr53c9x;

  /* recover our data structure: */
  ncr53c9x = (struct tme_ncr53c9x *) _ncr53c9x;

  /* unlock the mutex: */
  tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);
}

/* this hashes an address into a TLB entry: */
static struct tme_bus_tlb *
_tme_ncr53c9x_tlb_hash(void *_ncr53c9x,
		     tme_bus_addr_t linear_address,
		     unsigned int cycles)
{
  struct tme_ncr53c9x *ncr53c9x;

  /* recover our data structure: */
  ncr53c9x = (struct tme_ncr53c9x *) _ncr53c9x;

  /* return the TLB entry: */
  return (&ncr53c9x->tme_ncr53c9x_dma_tlb);
}

#define TME_NCR53C9X_DEBUG_REG_READ	(0)
#define TME_NCR53C9X_DEBUG_REG_WRITE	(1)
#define TME_NCR53C9X_DEBUG_REG_PUT	(2)
#ifdef TME_NCR53C9X_DEBUG
static void
_tme_ncr53c9x_debug_reg(struct tme_ncr53c9x *ncr53c9x,
			unsigned int reg,
			unsigned int why,
			tme_uint8_t val_new)
{
  const char *why_name;
  const char *reg_name;

  switch (why) {
  case TME_NCR53C9X_DEBUG_REG_READ: 
    why_name = "rd"; 
    break;
  case TME_NCR53C9X_DEBUG_REG_WRITE: 
    why_name = "wr"; 
    break;
  default: assert (FALSE);
  case TME_NCR53C9X_DEBUG_REG_PUT:
    if (ncr53c9x->tme_ncr53c9x_regs[reg] == val_new) {
      return;
    }
    why_name = "<-";
    break;
  }

  switch (reg) {
  case TME_NCR53C9X_REG_CTC_LSB: reg_name = "CTC.LSB"; break;
  case TME_NCR53C9X_REG_CTC_MSB: reg_name = "CTC.MSB"; break;
  case TME_NCR53C9X_REG_STC_LSB: reg_name = "STC.LSB"; break;
  case TME_NCR53C9X_REG_STC_MSB: reg_name = "STC.MSB"; break;
  case TME_NCR53C9X_REG_FIFO: reg_name = "FIFO"; break;
  case TME_NCR53C9X_REG_CMD: reg_name = "CMD"; break;
  case TME_NCR53C9X_REG_STAT: reg_name = "STAT"; break;
  case TME_NCR53C9X_REG_SDID: reg_name = "SDID"; break;
  case TME_NCR53C9X_REG_INST: reg_name = "INST"; break;
  case TME_NCR53C9X_REG_TIMEOUT: reg_name = "TIMEOUT"; break;
  case TME_NCR53C9X_REG_IS: reg_name = "IS"; break;
  case TME_NCR53C9X_REG_SYNCH_PERIOD: reg_name = "SYNCH_PERIOD"; break;
  case TME_NCR53C9X_REG_CFIS: reg_name = "CFIS"; break;
  case TME_NCR53C9X_REG_SYNCH_OFFSET: reg_name = "SYNCH_OFFSET"; break;
  case TME_NCR53C9X_REG_CONTROL1: reg_name = "CONTROL1"; break;
  case TME_NCR53C9X_REG_CLOCK_FACTOR: reg_name = "CLOCK_FACTOR"; break;
  case TME_NCR53C9X_REG_TEST: reg_name = "TEST"; break;
  case TME_NCR53C9X_REG_CONTROL2: reg_name = "CONTROL2"; break;
  case TME_NCR53C9X_REG_CONTROL3: reg_name = "CONTROL3"; break;
  case TME_NCR53C9X_REG_ALIGN: reg_name = "ALIGN"; break;
  default: reg_name = "???"; break;
  }
  tme_log(&ncr53c9x->tme_ncr53c9x_element->tme_element_log_handle,
	  100, TME_OK,
	  (&ncr53c9x->tme_ncr53c9x_element->tme_element_log_handle,
	   "%s (0x%02x) %s 0x%02x",
	   reg_name,
	   (reg % TME_NCR53C9X_SIZ_REGS),
	   why_name,
	   val_new));
}
#define TME_NCR53C9X_DEBUG_BP(x) _TME_CONCAT(_tme_ncr53c9x_debug_bp_,x)()
#define _TME_NCR53C9X_DEBUG_BP(x) static void _TME_CONCAT(_tme_ncr53c9x_debug_bp_,x)(void) { }
_TME_NCR53C9X_DEBUG_BP(read_inst)
_TME_NCR53C9X_DEBUG_BP(read_fifo)
_TME_NCR53C9X_DEBUG_BP(write_fifo)
_TME_NCR53C9X_DEBUG_BP(write_cmd)
_TME_NCR53C9X_DEBUG_BP(dma_terminal)
_TME_NCR53C9X_DEBUG_BP(dma_scsi)
#else  /* !TME_NCR53C9X_DEBUG */
#define _tme_ncr53c9x_debug_reg(n, r, w, v) do { } while (/* CONSTCOND */ 0 && (n) && (r) && (w) && (v))
#define TME_NCR53C9X_DEBUG_BP(x) do { } while (/* CONSTCOND */ 0)
#endif /* !TME_NCR53C9X_DEBUG */
#define TME_NCR53C9X_REG_PUT(n, r, v)	\
  do {					\
    _tme_ncr53c9x_debug_reg((n), (r), TME_NCR53C9X_DEBUG_REG_PUT, (v));\
    (n)->tme_ncr53c9x_regs[(r)] = (v);	\
  } while (/* CONSTCOND */ 0)

/* this puts the current SCSI bus phase into a STAT register value: */
static inline tme_uint8_t
_tme_ncr53c9x_scsi_phase_stat(struct tme_ncr53c9x *ncr53c9x,
			      tme_uint8_t reg_stat)
{
  tme_scsi_control_t scsi_control;

  /* get the current SCSI bus control signals: */
  scsi_control = ncr53c9x->tme_ncr53c9x_in_scsi_control;

  /* clear the SCSI bus phase bits in the STAT register value: */
  reg_stat
    &= ~(TME_NCR53C9X_STAT_I_O
	 | TME_NCR53C9X_STAT_C_D
	 | TME_NCR53C9X_STAT_MSG);

  /* copy the SCSI bus control signals for the bus phase into the STAT
     register value: */
#define _TME_NCR53C9X_X_CONTROL(reg, _reg, _control)\
  do {						\
    if (scsi_control & (_control)) {		\
      reg |= (_reg);				\
    }						\
  } while (/* CONSTCOND */ 0)
  _TME_NCR53C9X_X_CONTROL(reg_stat, TME_NCR53C9X_STAT_MSG, TME_SCSI_SIGNAL_MSG);
  _TME_NCR53C9X_X_CONTROL(reg_stat, TME_NCR53C9X_STAT_C_D, TME_SCSI_SIGNAL_C_D);
  _TME_NCR53C9X_X_CONTROL(reg_stat, TME_NCR53C9X_STAT_I_O, TME_SCSI_SIGNAL_I_O);
#undef _TME_NCR53C9X_X_CONTROL

  return (reg_stat);
}

/* this clears the command FIFO: */
static inline void
_tme_ncr53c9x_fifo_cmd_clear(struct tme_ncr53c9x *ncr53c9x)
{
  unsigned int fifo_head;

  /* write a NOP command (which must be a zero) at the head of the
     command FIFO: */
#if TME_NCR53C9X_CMD_NOP != 0
#error "TME_NCR53C9X_CMD_NOP must be zero"
#endif
  fifo_head = ncr53c9x->tme_ncr53c9x_fifo_cmd_head;
  ncr53c9x->tme_ncr53c9x_fifo_cmd[fifo_head] = TME_NCR53C9X_CMD_NOP;

  /* set the tail of the command FIFO equal to the head: */
  ncr53c9x->tme_ncr53c9x_fifo_cmd_tail = fifo_head;

  /* start the NOP command at the beginning of its sequence: */
  ncr53c9x->tme_ncr53c9x_cmd_sequence = 0;
}

/* this updates the data FIFO: */
static void
_tme_ncr53c9x_fifo_data_update(struct tme_ncr53c9x *ncr53c9x)
{
  unsigned int fifo_head;
  unsigned int fifo_tail;
  unsigned int fifo_count;

  /* get the number of bytes in the FIFO: */
  fifo_head = ncr53c9x->tme_ncr53c9x_fifo_data_head;
  fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_data_tail;
  if (fifo_head >= fifo_tail) {
    fifo_count = fifo_head - fifo_tail;
  }
  else {
    fifo_count = TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data) - (fifo_tail - fifo_head);
  }
    
  /* update the Current FIFO register: */
  assert (fifo_count <= TME_FIELD_MASK_EXTRACTU(TME_NCR53C9X_CFIS_CF, TME_NCR53C9X_CFIS_CF));
  TME_FIELD_MASK_DEPOSITU(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CFIS],
			  TME_NCR53C9X_CFIS_CF,
			  fifo_count);
}

/* this clears the data FIFO: */
static inline void
_tme_ncr53c9x_fifo_data_clear(struct tme_ncr53c9x *ncr53c9x)
{
  unsigned int fifo_tail;

  /* set the head of the data FIFO equal to the tail: */
  fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_data_tail;
  ncr53c9x->tme_ncr53c9x_fifo_data_head = fifo_tail;

  /* put a zero at the tail of the data FIFO: */
  ncr53c9x->tme_ncr53c9x_fifo_data[fifo_tail] = 0;

  /* update the Current FIFO register: */
  _tme_ncr53c9x_fifo_data_update(ncr53c9x);
}

/* this adds the given INST register value, along with the values
   currently in the STAT and IS register images, to the status FIFO,
   and calls out an interrupt: */
static void
_tme_ncr53c9x_fifo_status_add(struct tme_ncr53c9x *ncr53c9x,
			      tme_uint8_t reg_inst)
{
  tme_uint8_t reg_stat;
  unsigned int fifo_head;

  /* get the head of the status FIFO: */
  fifo_head = ncr53c9x->tme_ncr53c9x_fifo_status_head;

  /* get the accumulated STAT image at the head of the status FIFO and
     set the INT bit: */
  /* NB: the accumulated IOE, PE, and GCV bits may have been set by
     the active command (or even by some earlier command, especially
     in the IOE case).  the CTZ bit is always live.  the SCSI bus
     phase bits are either live, or latched here: */
  reg_stat
    = (ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_stat
       | TME_NCR53C9X_STAT_INT);

  /* if the SCSI bus phase bits are latched in the STAT register: */
  if (TME_NCR53C9X_HAS_CONTROL2_LSP(ncr53c9x)
      && (ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL2] & TME_NCR53C9X_CONTROL2_LSP)) {

    /* latch the current SCSI bus phase: */
    reg_stat = _tme_ncr53c9x_scsi_phase_stat(ncr53c9x, reg_stat);
  }

  /* finish the STAT and INST values at the head of the status FIFO.
     the IS value may have already been set by the active command: */
  /* NB: we also accumulate values in the INST register: */
  ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_stat = reg_stat;
  ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_inst |= reg_inst;

  /* if the status FIFO is not full: */
  fifo_head++;
  if (fifo_head == TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_status)) {
    fifo_head = 0;
  }
  if (fifo_head != ncr53c9x->tme_ncr53c9x_fifo_status_tail) {

    /* zero STAT, IS, and INST for the next status: */
    ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_stat = 0;
    ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_is = 0;
    ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_inst = 0;

    /* advance the head of the status FIFO: */
    ncr53c9x->tme_ncr53c9x_fifo_status_head = fifo_head;
  }

  /* call out an interrupt change: */
  ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_INT;
}

/* this returns the current timeout in the SCSI Timeout register, in
   milliseconds: */
static unsigned int
_tme_ncr53c9x_stimreg_msec(struct tme_ncr53c9x *ncr53c9x)
{
  /* XXX WRITEME: */
  ncr53c9x = 0;
  return (25);
}

/* this returns nonzero if the current SCSI data transfer is
   transferring in from the SCSI bus: */
static inline tme_scsi_control_t
_tme_ncr53c9x_transfer_input(const struct tme_ncr53c9x *ncr53c9x)
{
  tme_scsi_control_t scsi_control;

  /* if we're in initiator mode: */
  if (ncr53c9x->tme_ncr53c9x_mode == TME_NCR53C9X_MODE_INITIATOR) {

    /* the bus cycle type is derived from the latched SCSI bus phase: */
    scsi_control = ncr53c9x->tme_ncr53c9x_latched_phase;
    assert (scsi_control != _TME_SCSI_PHASE_UNDEF);
  }

  /* otherwise, we must be in target mode: */
  else {
    assert (ncr53c9x->tme_ncr53c9x_mode == TME_NCR53C9X_MODE_TARGET);

    /* the bus cycle type is derived from the output SCSI bus phase,
       and we flip the SCSI I/O control signal: */
    scsi_control = (ncr53c9x->tme_ncr53c9x_out_scsi_control ^ TME_SCSI_SIGNAL_I_O);
  }

  /* the current SCSI data transfer is transferring in from the SCSI
     bus if the SCSI I/O control signal is asserted: */
  return (scsi_control & TME_SCSI_SIGNAL_I_O);
}

/* this returns the CTC value: */
static inline tme_uint32_t
_tme_ncr53c9x_ctc_read(const struct tme_ncr53c9x *ncr53c9x)
{
  tme_uint32_t reg_ctc;

  /* get the raw value of the CTC register: */
  reg_ctc = ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CTC_MSB];
  reg_ctc = (reg_ctc << 8) + ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CTC_LSB];

  /* if the CTC register is zero, and it didn't count down to zero,
     the actual CTC value is 65536: */
  if (reg_ctc == 0
      && !(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_STAT]
	   & TME_NCR53C9X_STAT_CTZ)) {
    reg_ctc = 0x10000;
  }

  return (reg_ctc);
}

/* this writes the CTC value: */
static inline void
_tme_ncr53c9x_ctc_write(struct tme_ncr53c9x *ncr53c9x, tme_uint32_t reg_ctc)
{

  /* the CTC register must fit into 16 bits: */
  assert (reg_ctc < 0x10000);
  
  /* if the CTC register counted down to zero, set CTZ: */
  if (reg_ctc == 0) {
    TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_STAT,
			 (ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_STAT]
			  | TME_NCR53C9X_STAT_CTZ));
  }

  /* put the raw value of the CTC register: */
  TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_CTC_LSB, reg_ctc);
  TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_CTC_MSB, (reg_ctc >> 8));
}

/* this returns the SCSI bus transfer count: */
static tme_uint32_t
_tme_ncr53c9x_transfer_count(const struct tme_ncr53c9x *ncr53c9x)
{
  tme_uint32_t reg_ctc;
  tme_uint32_t fifo_count;
  tme_uint32_t count;

  /* get the count of bytes already in the data FIFO: */
  fifo_count = TME_FIELD_MASK_EXTRACTU(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CFIS],
				       TME_NCR53C9X_CFIS_CF);

  /* if DMA is running: */
  if (ncr53c9x->tme_ncr53c9x_dma_running) {

    /* if there are bytes already in the data FIFO: */
    if (fifo_count != 0) {
      /* XXX FIXME - should we log a warning here? */
    }

    /* get the value of the CTC register: */
    reg_ctc = _tme_ncr53c9x_ctc_read(ncr53c9x);

    /* if the current SCSI data transfer is transferring in from the
       SCSI bus: */
    if (_tme_ncr53c9x_transfer_input(ncr53c9x)) {

      /* subtract the number of bytes already in the data FIFO from
	 the CTC value to get the SCSI bus transfer count: */
      if (reg_ctc < fifo_count) {
	count = 0;
      }
      else {
	count = reg_ctc - fifo_count;
      }
    }

    /* otherwise, the current SCSI data transfer is transferring out to
       the SCSI bus: */
    else {

      /* add the number of bytes already in the data FIFO to the CTC
	 value to get the maximum SCSI bus transfer count: */
      count = reg_ctc + fifo_count;
    }
  }

  /* otherwise, DMA is not running: */
  else {

    /* if the current SCSI data transfer is transferring in from the
       SCSI bus: */
    if (_tme_ncr53c9x_transfer_input(ncr53c9x)) {

      /* XXX FIXME - the AMD 53c(F?)9x documentation is all I can
	 find, and it's not well-written.  part of the 53cF94
	 documentation for the Information Transfer command reads:

	 "Upon receipt of the last byte during Msg In phase, ACK will
	 remain asserted to prevent the Target from issuing any
	 additional bytes, while the Initiator decides to accept/
	 reject the message.  If non-DMA commands are used, the last
	 byte signals the FIFO is empty."

	 this last sentence is very confusing, but it could be
	 interpreted to mean that a non-DMA transfer in from the SCSI
	 bus will transfer the number of bytes already in the data
	 FIFO (*overwriting* those bytes in the FIFO), plus one
	 additional byte, and then the command is complete.

	 this sounds very strange, so it's probably wrong.  for now,
	 we assume that a non-DMA transfer in from the SCSI bus simply
	 transfers one byte, and hope that all users only do non-DMA
	 input transfers when the FIFO is empty: */
      /* XXX FIXME - what "the last byte signals the FIFO
	 is empty" probably means is: after you read out what you know
	 to be the last byte of a message (because you know how SCSI
	 messages are structured), you can assume that the FIFO is 
	 empty. */
      
      /* transfer a single byte: */
      count = 1;
    }

    /* otherwise, the current SCSI data transfer is transferring out to
       the SCSI bus: */
    else {
      
      /* transfer all of the bytes in the data FIFO: */
      count = fifo_count;
    }
  }

  return (count);
}

/* this finishes a command: */
static void
_tme_ncr53c9x_cmd_done(struct tme_ncr53c9x *ncr53c9x)
{

  /* stop driving the SCSI data lines, and just wait for the SCSI bus
     to change without taking any actions: */
  ncr53c9x->tme_ncr53c9x_out_scsi_data = 0;
  ncr53c9x->tme_ncr53c9x_out_scsi_events = TME_SCSI_EVENT_BUS_CHANGE;
  ncr53c9x->tme_ncr53c9x_out_scsi_actions = TME_SCSI_ACTION_NONE;

  /* call out a SCSI bus cycle: */
  ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE;

  /* if DMA is running: */
  if (ncr53c9x->tme_ncr53c9x_dma_running) {

    /* call out the terminal DMA address: */
    ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_TERMINAL_DMA;
  }
}

/* this disconnects from the the SCSI bus: */
static void
_tme_ncr53c9x_disconnect(struct tme_ncr53c9x *ncr53c9x)
{

  /* stop driving the SCSI control lines: */
  ncr53c9x->tme_ncr53c9x_out_scsi_control = 0;

  /* finish any current command.  this will also call out a SCSI bus
     cycle: */
  _tme_ncr53c9x_cmd_done(ncr53c9x);

  /* return to the idle mode: */
  ncr53c9x->tme_ncr53c9x_mode = TME_NCR53C9X_MODE_IDLE;
}

/* this resets the NCR 53c9x: */
static void
_tme_ncr53c9x_reset(struct tme_ncr53c9x *ncr53c9x,
		    unsigned int reset_type)
{
  unsigned int fifo_tail;
  tme_uint8_t value;

  /* if this is a device reset: */
  if ((reset_type & ~TME_NCR53C9X_RESET_FLAG_CMD) == TME_NCR53C9X_RESET_DEVICE) {

    /* "The Reset Device Command immediately stops any device operation
       and resets all the functions of the device.  It returns the device
       to the disconnected state and it also generates a hard reset."

       NB: I think the above is in error, and the Reset Device command
       actually generates a soft reset.  if the above is not in error,
       then the documentation describes no way to cause a soft reset: */

    /* "[STCREG] retains its programmed value until it is overwritten
       and is not affected by hardware or software reset." */

    /* "[FFREG] is reset to zero by hardware or software reset." */
    _tme_ncr53c9x_fifo_data_clear(ncr53c9x);
    
    /* "[PE in STATREG] will be cleared by reading the Interrupt
       Status Register or by a hard or soft reset."

       "[INT in STATREG] will be cleared by a hardware or software
       reset."

       "[IOE in STATREG] will be cleared by reading the Interrupt
       Status Register or by a hard or soft reset."

       "The GCV bit [in STATREG] is cleared by reading the Interrupt
       Status Register (INSTREG) or by a hard or soft reset." */

    /* get the tail of the status FIFO: */
    fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_status_tail;

    /* set the head of the status FIFO equal to the tail: */
    ncr53c9x->tme_ncr53c9x_fifo_status_head = fifo_tail;

    /* clear STAT, IS, and INST: */
    ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_stat = 0;
    ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_is = 0;
    ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_inst = 0;

    /* call out an interrupt change: */
    ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_INT;

    /* "The DID 2:0 bits are not affected by reset." */

    /* "The STPREG defaults to five after a hard or soft reset." */
    TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_SYNCH_PERIOD, 5);

    /* "The SOFREG is set to zero after a hard or soft reset." */
    TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_SYNCH_OFFSET, 0);

    /* "The ETM bit [in CONTROL1] is reset to zero by a hard or soft
       reset."

       "The DISR bit [in CONTROL1] is reset to zero by a hard or soft
       reset."

       "[The CID bits in CONTROL1] are not affected by hard or soft
       reset."

       "The PTE bit [in CONTROL1] is reset to zero by a hard or soft
       reset."
       
       "To reset [the STE bit in CONTROL1] and to resume normal
       operation the device must be issued a hard or soft reset." */
    TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_CONTROL1, 
			 (ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL1]
			  & ~(TME_NCR53C9X_CONTROL1_ETM
			      | TME_NCR53C9X_CONTROL1_DISR
			      | TME_NCR53C9X_CONTROL1_PTE
			      | TME_NCR53C9X_CONTROL1_STE)));

    /* "The CLKF 2:0 bits will default to a value of 2 by a hard or soft reset." */
    TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_CLOCK_FACTOR, 2);

    /* "The LSP bit [in CONTROL2] is reset by a hard or soft reset."

       "The DAE bit [in CONTROL2] is reset to zero by a hard or soft
       reset." */
    value = ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL2];
    if (TME_NCR53C9X_HAS_CONTROL2_LSP(ncr53c9x)) {
      value &= ~TME_NCR53C9X_CONTROL2_LSP;
    }
    if (TME_NCR53C9X_HAS_CONTROL2_DAE(ncr53c9x)) {
      value &= ~TME_NCR53C9X_CONTROL2_DAE;
    }
    TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_CONTROL2, value);

    /* "The LBTM bit [in CONTROL3] is reset by hard or soft reset." */
    TME_NCR53C9X_REG_PUT(ncr53c9x, TME_NCR53C9X_REG_CONTROL3, 
			 (ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL3]
			  & ~(TME_NCR53C9X_CONTROL3_LBTM)));

    /* make the Reset Device command the finished active command, and
       drop all other commands in the command queue: */
    ncr53c9x->tme_ncr53c9x_fifo_cmd[ncr53c9x->tme_ncr53c9x_fifo_cmd_tail] = TME_NCR53C9X_CMD_RESET;
    ncr53c9x->tme_ncr53c9x_cmd_sequence = TME_NCR53C9X_CMD_SEQUENCE_DONE;
    ncr53c9x->tme_ncr53c9x_fifo_cmd_head = ncr53c9x->tme_ncr53c9x_fifo_cmd_tail;
  }

  /* otherwise, this is a SCSI bus reset: */
  else {

    /* "A SCSI bus reset during any target command will cause the device
       to abort the command sequence, flag a SCSI bus reset interrupt
       (if the interrupt is enabled) and disconnect from the SCSI bus."  */

    /* we assume that the command FIFO should be cleared: */
    _tme_ncr53c9x_fifo_cmd_clear(ncr53c9x);
  }

  /* disconnect from the SCSI bus: */
  _tme_ncr53c9x_disconnect(ncr53c9x);
}

/* command sequence macros and functions: */

/* this labels an unknown default point in the sequence: */
#define _TME_NCR53C9X_CS_DEFAULT					\
  default: assert(FALSE)

/* this labels a point in the sequence: */
#define _TME_NCR53C9X_CS(x)						\
  cmd_sequence = (x);							\
  /* FALLTHROUGH */							\
  case (x)

/* this simply waits in the sequence: */
#define _TME_NCR53C9X_CS_WAIT						\
  if (TRUE)								\
    break

/* this does a goto in the sequence: */
#define _TME_NCR53C9X_CS_GOTO(x)					\
  cmd_sequence = (x);							\
  _TME_NCR53C9X_CS_WAIT

/* this finishes the sequence: */
#define _TME_NCR53C9X_CS_DONE						\
  _TME_NCR53C9X_CS_GOTO(TME_NCR53C9X_CMD_SEQUENCE_DONE)

/* this sets the IS register: */
#define _TME_NCR53C9X_CS_IS(reg_is)					\
  ncr53c9x->tme_ncr53c9x_fifo_status[ncr53c9x->tme_ncr53c9x_fifo_status_head].tme_ncr53c9x_status_is = (reg_is)

/* this generates an interrupt: */
#define _TME_NCR53C9X_CS_INT(reg_inst)					\
  _tme_ncr53c9x_fifo_status_add(ncr53c9x, (reg_inst))

/* this clears the command FIFO: */
#define _TME_NCR53C9X_CS_FIFO_CMD_CLEAR					\
  _tme_ncr53c9x_fifo_cmd_clear(ncr53c9x)

/* this checks the current major mode: */
/* "If the device is not in the initiator mode and an initiator
   command is received the device will ignore the command, generate an
   illegal command interrupt and clear the Command Register (CMDREG)
   03H." */
#define _TME_NCR53C9X_CS_MODE(mode)					\
  if (ncr53c9x->tme_ncr53c9x_mode != (mode)) {				\
    _TME_NCR53C9X_CS_FIFO_CMD_CLEAR;					\
    _TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_ICMD);			\
    _TME_NCR53C9X_CS_DONE;						\
  }									\
  do { } while (/* CONSTCOND */ 0)

/* this drives the SCSI bus: */
#define _TME_NCR53C9X_CS_SCSI_OUT(control)				\
  ncr53c9x->tme_ncr53c9x_out_scsi_control = (control);			\
  ncr53c9x->tme_ncr53c9x_out_scsi_data = 0;				\
  ncr53c9x->tme_ncr53c9x_out_scsi_events = TME_SCSI_EVENT_BUS_CHANGE;	\
  ncr53c9x->tme_ncr53c9x_out_scsi_actions = TME_SCSI_ACTION_NONE;	\
  ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE

/* this waits for us to drive the SCSI bus, and then waits for a value
   on the SCSI bus: */
#define _TME_NCR53C9X_CS_WAIT_SCSI(control_mask, control)		\
  if ((((ncr53c9x)->tme_ncr53c9x_callout_flags				\
	& TME_NCR53C9X_CALLOUT_SCSI_CYCLE) != 0)			\
      || ((ncr53c9x->tme_ncr53c9x_in_scsi_control & (control_mask))	\
	    != (control)))						\
    break
#define _TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(control)			\
  _TME_NCR53C9X_CS_WAIT_SCSI(control, control)
#define _TME_NCR53C9X_CS_WAIT_SCSI_NEGATED(control)			\
  _TME_NCR53C9X_CS_WAIT_SCSI(control, 0)

/* this selects on the SCSI bus: */
#define _TME_NCR53C9X_CS_SELECT(action)					\
  ncr53c9x->tme_ncr53c9x_out_scsi_data = 0;				\
  ncr53c9x->tme_ncr53c9x_out_scsi_events = TME_SCSI_EVENT_BUS_FREE;	\
  ncr53c9x->tme_ncr53c9x_out_scsi_actions				\
    = ((action)								\
       | TME_SCSI_ACTION_ID_SELF(TME_FIELD_MASK_EXTRACTU(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL1], \
							 TME_NCR53C9X_CONTROL1_ID)) \
       | TME_SCSI_ACTION_ID_OTHER(TME_FIELD_MASK_EXTRACTU(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_SDID], \
							  TME_NCR53C9X_SDID_DID))); \
  ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE

/* this waits for us to select on the SCSI bus: */
#define _TME_NCR53C9X_CS_WAIT_SELECT					\
  if ((((ncr53c9x)->tme_ncr53c9x_callout_flags				\
	& TME_NCR53C9X_CALLOUT_SCSI_CYCLE) != 0)			\
      || (ncr53c9x->tme_ncr53c9x_in_scsi_actions 			\
	  & (TME_SCSI_ACTION_SELECT					\
	     | TME_SCSI_ACTION_SELECT_WITH_ATN				\
	     | TME_SCSI_ACTION_RESELECT)) == 0)				\
    break

/* this latches the current time, plus a timeout in milliseconds: */
static void inline
_tme_ncr53c9x_cs_timeout(struct tme_ncr53c9x *ncr53c9x, unsigned int msec, unsigned int label)
{

  /* save the timeout length: */
  ncr53c9x->tme_ncr53c9x_timeout_length.tv_sec = (msec / 1000);
  ncr53c9x->tme_ncr53c9x_timeout_length.tv_usec = (msec % 1000) * 1000;

  /* get the current time: */
  gettimeofday(&ncr53c9x->tme_ncr53c9x_timeout_time, NULL);

  /* add the timeout length to get the timeout time: */
  ncr53c9x->tme_ncr53c9x_timeout_time.tv_sec += ncr53c9x->tme_ncr53c9x_timeout_length.tv_sec;
  ncr53c9x->tme_ncr53c9x_timeout_time.tv_usec += ncr53c9x->tme_ncr53c9x_timeout_length.tv_usec;
  if (ncr53c9x->tme_ncr53c9x_timeout_time.tv_usec >= 1000000) {
    ncr53c9x->tme_ncr53c9x_timeout_time.tv_usec -= 1000000;
    ncr53c9x->tme_ncr53c9x_timeout_time.tv_sec++;
  }

  /* set the timeout label: */
  ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout = label;

  /* signal the timeout thread: */
  tme_cond_notify(&ncr53c9x->tme_ncr53c9x_timeout_cond, FALSE);
}
#define _TME_NCR53C9X_CS_TIMEOUT(msec, label) _tme_ncr53c9x_cs_timeout(ncr53c9x, (msec), (label))
#define _TME_NCR53C9X_CS_TIMEOUT_CANCEL					\
  assert (ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout != TME_NCR53C9X_CMD_SEQUENCE_UNDEF); \
  ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout = TME_NCR53C9X_CMD_SEQUENCE_UNDEF

/* this monitors the SCSI BSY signal: */
static unsigned int
_tme_ncr53c9x_cs_monitor_bsy(struct tme_ncr53c9x *ncr53c9x)
{
  tme_scsi_control_t scsi_control;
  unsigned int cmd_sequence_goto;

  /* get the current SCSI bus state: */
  scsi_control = ncr53c9x->tme_ncr53c9x_in_scsi_control;

  /* if BSY is negated: */
  if ((scsi_control & TME_SCSI_SIGNAL_BSY) == 0) {

    /* if an initiator DMA SCSI cycle is running: */
    if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_SCSI_CYCLE) != 0
	&& (ncr53c9x->tme_ncr53c9x_out_scsi_actions
	    & (TME_SCSI_ACTION_DMA_INITIATOR
	       | TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)) != 0) {

      /* cancel the SCSI cycle: */
      ncr53c9x->tme_ncr53c9x_callout_flags &= ~TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
    }

    /* if the BSY lost label for this command sequence is the
       done sequence number: */
    if (ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost == TME_NCR53C9X_CMD_SEQUENCE_DONE) {

      /* issue a Disconnected interrupt: */
      _TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_DIS);

      /* force a disconnect: */
      _tme_ncr53c9x_disconnect(ncr53c9x);
    }

    /* goto the BSY lost label for the command sequence, and
       cancel the BSY lost label, and any phase mismatch label: */
    cmd_sequence_goto = ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost;
    ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost = TME_NCR53C9X_CMD_SEQUENCE_UNDEF;
    ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch = TME_NCR53C9X_CMD_SEQUENCE_UNDEF;
    return (cmd_sequence_goto);
  }

  return (TME_NCR53C9X_CMD_SEQUENCE_UNDEF);
}
#define _TME_NCR53C9X_CS_MONITOR_BSY(label)				\
  assert (ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost == TME_NCR53C9X_CMD_SEQUENCE_UNDEF);\
  assert (label != TME_NCR53C9X_CMD_SEQUENCE_UNDEF);			\
  ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost = (label);		\
  cmd_sequence_goto = _tme_ncr53c9x_cs_monitor_bsy(ncr53c9x);		\
  if (cmd_sequence_goto != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {		\
    _TME_NCR53C9X_CS_GOTO(cmd_sequence_goto);				\
  }									\
  do { } while (/* CONSTCOND */ 0)

/* this monitors the SCSI bus phase: */
static unsigned int
_tme_ncr53c9x_cs_monitor_phase(struct tme_ncr53c9x *ncr53c9x, int force_monitor)
{
  tme_scsi_control_t scsi_control;
  unsigned int cmd_sequence_goto;

  /* get the current SCSI bus state: */
  scsi_control = ncr53c9x->tme_ncr53c9x_in_scsi_control;

  /* BSY must be asserted: */
  assert ((scsi_control & TME_SCSI_SIGNAL_BSY) != 0);

  /* if the command sequence SCSI bus transfer residual is nonzero,
     or if we're forcing monitoring even if the residual is zero,
     REQ is asserted, and the phase is mismatched: */
  if ((ncr53c9x->tme_ncr53c9x_transfer_resid != 0
       || force_monitor)
      && (scsi_control & TME_SCSI_SIGNAL_REQ) != 0
      && TME_SCSI_PHASE(scsi_control) != ncr53c9x->tme_ncr53c9x_latched_phase) {

    /* if an initiator DMA SCSI cycle is running: */
    if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_SCSI_CYCLE) != 0
	&& (ncr53c9x->tme_ncr53c9x_out_scsi_actions
	    & (TME_SCSI_ACTION_DMA_INITIATOR
	       | TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)) != 0) {

      /* cancel the SCSI cycle: */
      ncr53c9x->tme_ncr53c9x_callout_flags &= ~TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
    }

    /* goto the phase mismatch label for the command sequence,
       and cancel the phase latch: */
    cmd_sequence_goto = ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch;
    ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch = TME_NCR53C9X_CMD_SEQUENCE_UNDEF;
    return (cmd_sequence_goto);
  }

  return (TME_NCR53C9X_CMD_SEQUENCE_UNDEF);
}
#define _TME_NCR53C9X_CS_LATCH_PHASE(phase)				\
  assert (ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch == TME_NCR53C9X_CMD_SEQUENCE_UNDEF);\
  ncr53c9x->tme_ncr53c9x_latched_phase = (phase)
#define _TME_NCR53C9X_CS_MONITOR_PHASE(label)				\
  assert (ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch == TME_NCR53C9X_CMD_SEQUENCE_UNDEF);\
  assert (ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost != TME_NCR53C9X_CMD_SEQUENCE_UNDEF);\
  assert (label != TME_NCR53C9X_CMD_SEQUENCE_UNDEF);			\
  ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch = (label);		\
  cmd_sequence_goto = _tme_ncr53c9x_cs_monitor_phase(ncr53c9x, TRUE);	\
  if (cmd_sequence_goto != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {		\
    _TME_NCR53C9X_CS_GOTO(cmd_sequence_goto);				\
  }									\
  do { } while (/* CONSTCOND */ 0)
#define _TME_NCR53C9X_CS_UNMONITOR_PHASE				\
  ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch = TME_NCR53C9X_CMD_SEQUENCE_UNDEF

/* this starts a data transfer: */
#define _TME_NCR53C9X_CS_TRANSFER(action, count)			\
  assert (ncr53c9x->tme_ncr53c9x_transfer_resid == 0);			\
  ncr53c9x->tme_ncr53c9x_out_scsi_events = TME_SCSI_EVENT_NONE;		\
  ncr53c9x->tme_ncr53c9x_out_scsi_actions = (action);			\
  ncr53c9x->tme_ncr53c9x_transfer_resid = (count);			\
  assert (ncr53c9x->tme_ncr53c9x_transfer_resid > 0);			\
  ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state = 0;		\
  ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE

/* this waits for a data transfer to finish: */
#define _TME_NCR53C9X_CS_WAIT_TRANSFER					\
  if (ncr53c9x->tme_ncr53c9x_transfer_resid != 0) 			\
    break

/* if this is a DMA command, this copies the Start Transfer Count
   register into the Current Transfer Count register: */
static void
_tme_ncr53c9x_cs_dma_setup(struct tme_ncr53c9x *ncr53c9x,
			   unsigned int cmd)
{
 
  /* if this command is a DMA command: */
  if ((cmd & TME_NCR53C9X_CMD_DMA) != 0) {

    /* copy the Start Transfer Count register into the Current
       Transfer Count register: */
    TME_NCR53C9X_REG_PUT(ncr53c9x, 
			 TME_NCR53C9X_REG_CTC_LSB,
			 ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_STC_LSB]);
    TME_NCR53C9X_REG_PUT(ncr53c9x, 
			 TME_NCR53C9X_REG_CTC_MSB,
			 ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_STC_MSB]);

    /* clear CTZ: */
    TME_NCR53C9X_REG_PUT(ncr53c9x, 
			 TME_NCR53C9X_REG_STAT,
			 (ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_STAT]
			  & ~TME_NCR53C9X_STAT_CTZ));

    /* zero the DMA address: */
    ncr53c9x->tme_ncr53c9x_dma_address = 0;

    /* DMA is running: */
    ncr53c9x->tme_ncr53c9x_dma_running = TRUE;
  }
}
#define _TME_NCR53C9X_CS_DMA_SETUP _tme_ncr53c9x_cs_dma_setup(ncr53c9x, cmd)

/* this is the NCR 53c9x update function: */
static void
_tme_ncr53c9x_update(struct tme_ncr53c9x *ncr53c9x)
{
  unsigned int fifo_tail;
  unsigned int cmd;
  unsigned int cmd_sequence;
  unsigned int cmd_sequence_goto;
  tme_scsi_control_t scsi_control;
  tme_scsi_data_t scsi_data;
  tme_scsi_control_t phase;
  tme_scsi_data_t ids;
  struct timeval now;
  tme_uint32_t count;

  /* loop forever: */
  for (;;) {

    /* get the command at the tail of the command FIFO: */
    fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_cmd_tail;
    cmd = ncr53c9x->tme_ncr53c9x_fifo_cmd[fifo_tail];
    cmd_sequence = ncr53c9x->tme_ncr53c9x_cmd_sequence;

    /* if the command sequence number is zero: */
    if (cmd_sequence == 0) {

      /* the SCSI data bus must not be driven, and we must be waiting
	 for any SCSI bus change, without taking any actions: */
      assert (ncr53c9x->tme_ncr53c9x_out_scsi_data == 0);
      assert (ncr53c9x->tme_ncr53c9x_out_scsi_events == TME_SCSI_EVENT_BUS_CHANGE);
      assert (ncr53c9x->tme_ncr53c9x_out_scsi_actions == TME_SCSI_ACTION_NONE);

      /* DMA must not be running yet: */
      assert (!ncr53c9x->tme_ncr53c9x_dma_running);

      /* clear the input SCSI events and actions: */
      ncr53c9x->tme_ncr53c9x_in_scsi_events = TME_SCSI_EVENT_NONE;
      ncr53c9x->tme_ncr53c9x_in_scsi_actions = TME_SCSI_ACTION_NONE;

      /* no data is being transferred over the SCSI bus: */
      ncr53c9x->tme_ncr53c9x_transfer_resid = 0;

      /* no timeout is set: */
      ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout = TME_NCR53C9X_CMD_SEQUENCE_UNDEF;

      /* no SCSI bus phase is latched: */
      ncr53c9x->tme_ncr53c9x_latched_phase = _TME_SCSI_PHASE_UNDEF;
      
      /* the SCSI bus phase is not being monitored: */
      ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch = TME_NCR53C9X_CMD_SEQUENCE_UNDEF;

      /* if we're in initiator mode: */
      if (ncr53c9x->tme_ncr53c9x_mode == TME_NCR53C9X_MODE_INITIATOR) {
	
	/* monitor the SCSI BSY signal: */
	ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost = TME_NCR53C9X_CMD_SEQUENCE_DONE;
      }

      /* otherwise, we're not in initiator mode: */
      else {

	/* don't monitor the SCSI BSY signal: */
	ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost = TME_NCR53C9X_CMD_SEQUENCE_UNDEF;
      }
    }

    /* get the current SCSI bus state: */
    scsi_control = ncr53c9x->tme_ncr53c9x_in_scsi_control;
    scsi_data = ncr53c9x->tme_ncr53c9x_in_scsi_data;

    /* if RST is being asserted: */
    if ((scsi_control & TME_SCSI_SIGNAL_RST) != 0) {

      /* if this SCSI reset hasn't already been detected: */
      if (!ncr53c9x->tme_ncr53c9x_detected_scsi_reset) {

	/* this SCSI reset has been detected: */
	ncr53c9x->tme_ncr53c9x_detected_scsi_reset = TRUE;

	/* if SCSI reset reporting hasn't been disabled: */
	if (!(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL1]
	      & TME_NCR53C9X_CONTROL1_DISR)) {

	  /* issue a SCSI reset interrupt: */
	  _TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SRST);
	}

	/* if the active command isn't a bus reset: */
	if ((cmd & TME_NCR53C9X_CMD_MASK) != TME_NCR53C9X_CMD_RESET_BUS
	    || cmd_sequence == TME_NCR53C9X_CMD_SEQUENCE_DONE) {

	  /* do an external SCSI reset: */
	  _tme_ncr53c9x_reset(ncr53c9x, TME_NCR53C9X_RESET_BUS);

	  /* stop now: */
	  break;
	}
      }
    }

    /* otherwise, RST is not being asserted: */
    else {

      /* no SCSI reset is detected: */
      ncr53c9x->tme_ncr53c9x_detected_scsi_reset = FALSE;
    }

    /* if we are idle: */
    if (ncr53c9x->tme_ncr53c9x_mode == TME_NCR53C9X_MODE_IDLE) {

      /* if we're not selecting or reselecting, and we are being
         selected or reselected: */
      ids = (1 << TME_FIELD_MASK_EXTRACTU(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL1],
					  TME_NCR53C9X_CONTROL1_ID));
      if ((ncr53c9x->tme_ncr53c9x_in_scsi_actions
	   & (TME_SCSI_ACTION_SELECT
	      | TME_SCSI_ACTION_SELECT_WITH_ATN
	      | TME_SCSI_ACTION_RESELECT)) == 0
	  && (TME_SCSI_ID_SELECTED(ids, scsi_control, scsi_data)
	      || TME_SCSI_ID_RESELECTED(ids, scsi_control, scsi_data))) {

	/* if selection and reselection are enabled: */
	/* XXX WRITEME: */
	abort();
      }
    }

    /* if the command at the tail of the command FIFO is not done: */
    if (cmd_sequence != TME_NCR53C9X_CMD_SEQUENCE_DONE) {

      /* if the SCSI BSY signal is monitored: */
      if (ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {

	/* we must be in initiator mode: */
	assert (ncr53c9x->tme_ncr53c9x_mode == TME_NCR53C9X_MODE_INITIATOR);

	/* monitor the SCSI BSY signal: */
	cmd_sequence_goto = _tme_ncr53c9x_cs_monitor_bsy(ncr53c9x);
	if (cmd_sequence_goto != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {
	  cmd_sequence = cmd_sequence_goto;
	}
      }

      /* if the SCSI bus phase is monitored: */
      if (ncr53c9x->tme_ncr53c9x_cmd_sequence_phase_mismatch != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {

	/* we must be in initiator mode: */
	assert (ncr53c9x->tme_ncr53c9x_mode == TME_NCR53C9X_MODE_INITIATOR);

	/* the SCSI BSY signal must be monitored: */
	assert (ncr53c9x->tme_ncr53c9x_cmd_sequence_bsy_lost != TME_NCR53C9X_CMD_SEQUENCE_UNDEF);

	/* monitor the SCSI bus phase: */
	cmd_sequence_goto = _tme_ncr53c9x_cs_monitor_phase(ncr53c9x, FALSE);
	if (cmd_sequence_goto != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {
	  cmd_sequence = cmd_sequence_goto;
	}
      }

      /* if a timeout is set: */
      if (ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {

	/* get the current time: */
	gettimeofday(&now, NULL);

	/* if the timeout has expired: */
	if (now.tv_sec > ncr53c9x->tme_ncr53c9x_timeout_time.tv_sec
	    || (now.tv_sec == ncr53c9x->tme_ncr53c9x_timeout_time.tv_sec
		&& now.tv_usec >= ncr53c9x->tme_ncr53c9x_timeout_time.tv_usec)) {

	  /* goto the timeout label for the command sequence, and
             cancel the timeout: */
	  cmd_sequence = ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout;
	  ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout = TME_NCR53C9X_CMD_SEQUENCE_UNDEF;
	}
      }
    }

    /* if the command at the tail of the command FIFO is still not done: */
    if (cmd_sequence != TME_NCR53C9X_CMD_SEQUENCE_DONE) {

      /* dispatch on the current command: */
      switch (cmd & TME_NCR53C9X_CMD_MASK) {

	/* "The No Operation Command is used to perform no operation
	   and no interrupt is generated at the end of this command.
	   This command is issued after the Reset Device Command to
	   enable the Command Register.  A No Operation Command in the
	   DMA mode may be used to verify the contents of the Start
	   Transfer Count Register (STCREG) 00H 01H.  After the STCREG
	   is loaded with the transfer count and a No Operation
	   Command is issued, reading the Current Transfer Count
	   Register (CTCREG) 00H 01H will give the transfer count
	   value." */
      case TME_NCR53C9X_CMD_NOP:
	assert (cmd_sequence == 0);
	_TME_NCR53C9X_CS_DMA_SETUP;
	_TME_NCR53C9X_CS_DONE;

	/* "The Clear FIFO Command is used to initialize the FIFO to
	   the empty condition. The Current FIFO Register (CFISREG)
	   07H reflects the empty FIFO status and the bottom of the
	   FIFO is set to zero. No interrupt is generated at the end
	   of this command." */
      case TME_NCR53C9X_CMD_CLEAR_FIFO:
	assert (cmd_sequence == 0);
	_tme_ncr53c9x_fifo_data_clear(ncr53c9x);
	_TME_NCR53C9X_CS_DONE;

	/* these commands are handled elsewhere: */
      case TME_NCR53C9X_CMD_RESET: 
      case TME_NCR53C9X_CMD_DMA_STOP:
	assert (FALSE);
	/* FALLTHROUGH */

	/* an unknown command: */
      default:
	abort();

	/* "The Reset SCSI Bus Command is used to assert the RSTC
	   signal for approximately 25 ms. This command causes the
	   device to go to the disconnected state.  No interrupt is
	   generated upon command completion.  A SCSI reset interrupt
	   is however generated upon command completion if the
	   interrupt is not disabled in the Control Register One
	   (CNTLREG1) 08H." */
      case TME_NCR53C9X_CMD_RESET_BUS:
	switch (cmd_sequence) {
	  _TME_NCR53C9X_CS_DEFAULT;
	  _TME_NCR53C9X_CS(0):	_TME_NCR53C9X_CS_SCSI_OUT(TME_SCSI_SIGNAL_RST);
	  _TME_NCR53C9X_CS(1):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_RST);
				_TME_NCR53C9X_CS_TIMEOUT(25, 3);
	  _TME_NCR53C9X_CS(2):	_TME_NCR53C9X_CS_WAIT;
	  _TME_NCR53C9X_CS(3):	_TME_NCR53C9X_CS_SCSI_OUT(0);
				_tme_ncr53c9x_disconnect(ncr53c9x);
				_TME_NCR53C9X_CS_DONE;
	}
	break;

	/* "The Information Transfer command is used to transfer
	   information bytes over the SCSI bus. This command may be
	   issued during any SCSI Information Transfer phase.
	   Information transfer for synchronous data must use the
	   DMA mode." */
      case TME_NCR53C9X_CMD_TRANSFER:
	switch (cmd_sequence) {
	  _TME_NCR53C9X_CS_DEFAULT;
	  _TME_NCR53C9X_CS(0):	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_INITIATOR);
				_TME_NCR53C9X_CS_DMA_SETUP;
	  _TME_NCR53C9X_CS(1):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				_TME_NCR53C9X_CS_LATCH_PHASE(TME_SCSI_PHASE(scsi_control));
				_TME_NCR53C9X_CS_MONITOR_PHASE(10);
				count = _tme_ncr53c9x_transfer_count(ncr53c9x);
				if (count == 0) {
				  _TME_NCR53C9X_CS_GOTO(6);
				}
				phase = ncr53c9x->tme_ncr53c9x_latched_phase;
				if (phase == TME_SCSI_PHASE_MESSAGE_OUT) {
				  count--;
				  if (count == 0) {
				    _TME_NCR53C9X_CS_GOTO(3);
				  }
				}
				_TME_NCR53C9X_CS_TRANSFER((phase == TME_SCSI_PHASE_MESSAGE_IN
							   ? TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK
							   : TME_SCSI_ACTION_DMA_INITIATOR),
							  count);
	  _TME_NCR53C9X_CS(2):	_TME_NCR53C9X_CS_WAIT_TRANSFER;
				phase = ncr53c9x->tme_ncr53c9x_latched_phase;
				if (phase == TME_SCSI_PHASE_MESSAGE_IN) {
				  _TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SO);
				  _TME_NCR53C9X_CS_DONE;
				}
				_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				if (phase != TME_SCSI_PHASE_MESSAGE_OUT) {
				  _TME_NCR53C9X_CS_GOTO(6);
				}
	  _TME_NCR53C9X_CS(3):	_TME_NCR53C9X_CS_SCSI_OUT(ncr53c9x->tme_ncr53c9x_out_scsi_control & ~TME_SCSI_SIGNAL_ATN);
	  _TME_NCR53C9X_CS(4):	_TME_NCR53C9X_CS_WAIT_SCSI_NEGATED(TME_SCSI_SIGNAL_ATN);
				_TME_NCR53C9X_CS_TRANSFER(TME_SCSI_ACTION_DMA_INITIATOR, 1);
	  _TME_NCR53C9X_CS(5):	_TME_NCR53C9X_CS_WAIT_TRANSFER;
				_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
	  _TME_NCR53C9X_CS(6):	_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SR);
				_TME_NCR53C9X_CS_DONE;

	  /* "The target changes the SCSI bus phase before the
	     expected number of bytes are transferred. The device
	     clears the Command Register (CMDREG) 03H, and generates a
	     service interrupt when the target asserts REQ." */
	  /* NB: we have to clear the command FIFO last; once we do,
	     we can't CS_WAIT on anything or CS_GOTO, since our own
	     command will have been cleared: */
	  _TME_NCR53C9X_CS(10):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SR);
				_TME_NCR53C9X_CS_FIFO_CMD_CLEAR;
				_TME_NCR53C9X_CS_DONE;	  
	}
	break;

	/* "The Initiator Command Complete Steps command is normally
	   issued when the SCSI bus is in the Status In phase.  One
	   Status byte followed by one Message byte is transferred if
	   this command completes normally.  After receiving the
	   message byte the device will keep the ACK signal asserted
	   to allow the initiator to examine the message and assert
	   the ATN signal if it is unacceptable." */
      case TME_NCR53C9X_CMD_ICCS:
	switch (cmd_sequence) {
	  _TME_NCR53C9X_CS_DEFAULT;
	  _TME_NCR53C9X_CS(0):	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_INITIATOR);
				_TME_NCR53C9X_CS_DMA_SETUP;
          _TME_NCR53C9X_CS(1):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				_TME_NCR53C9X_CS_LATCH_PHASE(TME_SCSI_PHASE(scsi_control));
				_TME_NCR53C9X_CS_MONITOR_PHASE(10);
				_TME_NCR53C9X_CS_TRANSFER(TME_SCSI_ACTION_DMA_INITIATOR, 1);
          _TME_NCR53C9X_CS(2):	_TME_NCR53C9X_CS_WAIT_TRANSFER;
				_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				_TME_NCR53C9X_CS_UNMONITOR_PHASE;
				_TME_NCR53C9X_CS_LATCH_PHASE(TME_SCSI_PHASE_MESSAGE_IN);
          _TME_NCR53C9X_CS(3):	_TME_NCR53C9X_CS_MONITOR_PHASE(5);
				_TME_NCR53C9X_CS_TRANSFER(TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK, 1);
          _TME_NCR53C9X_CS(4):	_TME_NCR53C9X_CS_WAIT_TRANSFER;
          _TME_NCR53C9X_CS(5):	_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SO);
				_TME_NCR53C9X_CS_DONE;

	  /* a phase mismatch: */
          _TME_NCR53C9X_CS(10):	_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SR);
				_TME_NCR53C9X_CS_DONE;
	}
	break;

	/* "The Message Accepted Command is used to release the ACK
	   signal. This command is normally used to complete a Message
	   In handshake.  Upon execution of this command the device
	   generates a service request interrupt after REQ is asserted
	   by the target." */
	/* NB: the target usually disconnects instead of asserting REQ
	   again, because most Message In phases are at the end of
	   completed commands.  in this case, the default initiator
	   mode BSY monitoring will issue a disconnected interrupt: */
      case TME_NCR53C9X_CMD_MSG_ACCEPTED:
	switch (cmd_sequence) {
	  _TME_NCR53C9X_CS_DEFAULT;
          _TME_NCR53C9X_CS(0):	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_INITIATOR);
          _TME_NCR53C9X_CS(1):	_TME_NCR53C9X_CS_WAIT_SCSI_NEGATED(TME_SCSI_SIGNAL_REQ);
				_TME_NCR53C9X_CS_SCSI_OUT(ncr53c9x->tme_ncr53c9x_out_scsi_control & ~TME_SCSI_SIGNAL_ACK);
          _TME_NCR53C9X_CS(2):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
	  			_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SR);
				_TME_NCR53C9X_CS_DONE;
	}
	break;

	/* XXX FIXME - we don't support the Transfer Pad command yet: */
      case TME_NCR53C9X_CMD_TRANSFER_PAD:
	abort();

	/* "The Set ATN Command is used to drive the ATN signal active
	   on the SCSI bus.  An interrupt is not generated at the end
	   of this command." */
      case TME_NCR53C9X_CMD_ATN_SET:
	assert (cmd_sequence == 0);
	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_INITIATOR);
	_TME_NCR53C9X_CS_SCSI_OUT(ncr53c9x->tme_ncr53c9x_out_scsi_control | TME_SCSI_SIGNAL_ATN);
	_TME_NCR53C9X_CS_DONE;

	/* "The Reset ATN Command is used to deassert the ATN signal
	   on the SCSI bus. An interrupt is not generated at the end
	   of this command." */
      case TME_NCR53C9X_CMD_ATN_RESET:
	assert (cmd_sequence == 0);
	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_INITIATOR);
	_TME_NCR53C9X_CS_SCSI_OUT(ncr53c9x->tme_ncr53c9x_out_scsi_control & ~TME_SCSI_SIGNAL_ATN);
	_TME_NCR53C9X_CS_DONE;

	/* "The Select without ATN Steps Command is used by the
	   initiator to select a target.  When this command is issued
	   the device arbitrates for the control of the SCSI bus.
	   When the device wins arbitration, it selects the target
	   device and transfers the Command Descriptor Block (CDB).
	   Before issuing this command the SCSI Timeout Register
	   (STIMREG) 05H, the Control Register One (CNTLREG1) 08H and
	   the SCSI Destination ID Register (SDIDREG) 04H must be set
	   to the proper values.  If DMA is enabled, the Start
	   Transfer Count Register (STCREG) 00H 01H must be set to the
	   total length of the command.  If DMA is not enabled, the
	   data must be loaded into the FIFO before issuing this
	   command.  This command will be terminated early if the SCSI
	   Timeout Register times out or if the target does not go to
	   the Command Phase following the Selection Phase or if the
	   target exits the Command Phase early." */
      case TME_NCR53C9X_CMD_SELECT:
	/* FALLTHROUGH */

	/* "The Select with ATN Steps Command is used by the initiator
	   to select a target.  When this command is issued the device
	   arbitrates for the control of the SCSI bus.  When the
	   device wins arbitration, it selects the target device with
	   the ATN signal asserted and transfers the Command
	   Descriptor Block (CDB) and a one byte message.  Before
	   issuing this command the SCSI Timeout Register (STIMREG)
	   05H, the Control Register One (CNTLREG1) 08H and the SCSI
	   Destination ID Register (SDIDREG) 04H must be set to the
	   proper values.  If DMA is enabled, the Start Transfer Count
	   Register (STCREG) 00H 01H must be set to the total length
	   of the command.  If DMA is not enabled, the data must be
	   loaded into the FIFO before issuing this command." */
      case TME_NCR53C9X_CMD_SELECT_ATN:
	/* FALLTHROUGH */

	/* "The Select with ATN3 Steps Command is used by the
	   initiator to select a target.  This command is similar to
	   the Select with ATN Steps Command, except that it sends
	   exactly three message bytes.  When this command is issued
	   the device arbitrates for the control of the SCSI bus.
	   When the device wins arbitration, it selects the target
	   device with the ATN signal asserted and transfers the
	   Command Descriptor Block (CDB) and three message bytes.
	   Before issuing this command the SCSI Timeout Register
	   (STIMREG) 05H, the Control Register One (CNTLREG1) 08H and
	   the SCSI Destination ID Register (SDIDREG) 04H must be set
	   to the proper values.  If DMA is enabled, the Start
	   Transfer Count Register (STCREG) 00H 01H must be set to the
	   total length of the command.  If DMA is not enabled, the
	   data must be loaded into the FIFO before issuing this
	   command." */
      case TME_NCR53C9X_CMD_SELECT_ATN3:
	/* FALLTHROUGH */

	/* "The Select with ATN and Stop Steps Command is used by the
	   initiator to select a target.  When this command is issued
	   the device arbitrates for the control of the SCSI bus. When
	   the device wins arbitration, it selects the target device
	   with the ATN signal asserted and transfers the Command
	   Descriptor Block (CDB) and stops after one message byte is
	   sent, but the ATN signal is not deasserted at the end of
	   the command which allows the initiator to send other
	   messages after the ID message is sent out. Before issuing
	   this command the SCSI Timeout Register (STIMREG) 05H, the
	   Control Register One (CNTLREG1) 08H and the SCSI
	   Destination ID Register (SDIDREG) 04H must be set to the
	   proper values." */
      case TME_NCR53C9X_CMD_SELECT_ATN_STOP:
	/* FALLTHROUGH */

	switch (cmd_sequence) {
	  _TME_NCR53C9X_CS_DEFAULT;
          _TME_NCR53C9X_CS(0):	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_IDLE);
				_TME_NCR53C9X_CS_DMA_SETUP;
				_TME_NCR53C9X_CS_SELECT((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT
							? TME_SCSI_ACTION_SELECT
							: TME_SCSI_ACTION_SELECT_WITH_ATN);
          _TME_NCR53C9X_CS(1):	_TME_NCR53C9X_CS_WAIT_SELECT;
				_TME_NCR53C9X_CS_TIMEOUT(_tme_ncr53c9x_stimreg_msec(ncr53c9x), 30);
          _TME_NCR53C9X_CS(2):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_BSY);
				_TME_NCR53C9X_CS_TIMEOUT_CANCEL;
				_TME_NCR53C9X_CS_SCSI_OUT((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT
							  ? 0
							  : TME_SCSI_SIGNAL_ATN);
          _TME_NCR53C9X_CS(3):	_TME_NCR53C9X_CS_WAIT_SCSI_NEGATED(TME_SCSI_SIGNAL_SEL);
				ncr53c9x->tme_ncr53c9x_mode = TME_NCR53C9X_MODE_INITIATOR;
				_TME_NCR53C9X_CS_MONITOR_BSY(21);
				if ((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT) {
				  _TME_NCR53C9X_CS_GOTO(10);
				}
          _TME_NCR53C9X_CS(4):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				if (TME_SCSI_PHASE(scsi_control) != TME_SCSI_PHASE_MESSAGE_OUT) {
				  _TME_NCR53C9X_CS_IS(0);
				  _TME_NCR53C9X_CS_GOTO(16);
				}
				_TME_NCR53C9X_CS_LATCH_PHASE(TME_SCSI_PHASE_MESSAGE_OUT);
				_TME_NCR53C9X_CS_MONITOR_PHASE(20);
				count = _tme_ncr53c9x_transfer_count(ncr53c9x);
				if (count == 0) {
				  abort();
				}
				if ((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT_ATN) {
				  _TME_NCR53C9X_CS_GOTO(6);
				}
				else if ((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT_ATN3) {
				  if (count < 3) {
				    abort();
				  }
				  count = 2;
				}
				else {
				  assert ((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT_ATN_STOP);
				  count = 1;
				}
				_TME_NCR53C9X_CS_TRANSFER(TME_SCSI_ACTION_DMA_INITIATOR, count);
          _TME_NCR53C9X_CS(5):	_TME_NCR53C9X_CS_WAIT_TRANSFER;
          _TME_NCR53C9X_CS(6):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				if ((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT_ATN_STOP) {
				  _TME_NCR53C9X_CS_IS(1);
				  _TME_NCR53C9X_CS_GOTO(16);
				}
				_TME_NCR53C9X_CS_SCSI_OUT(ncr53c9x->tme_ncr53c9x_out_scsi_control & ~TME_SCSI_SIGNAL_ATN);
          _TME_NCR53C9X_CS(7):	_TME_NCR53C9X_CS_WAIT_SCSI_NEGATED(TME_SCSI_SIGNAL_ATN);
				_TME_NCR53C9X_CS_TRANSFER(TME_SCSI_ACTION_DMA_INITIATOR, 1);
          _TME_NCR53C9X_CS(8):	_TME_NCR53C9X_CS_WAIT_TRANSFER;
	  _TME_NCR53C9X_CS(10):	_TME_NCR53C9X_CS_WAIT_SCSI_ASSERTED(TME_SCSI_SIGNAL_REQ);
				_TME_NCR53C9X_CS_UNMONITOR_PHASE;
				_TME_NCR53C9X_CS_LATCH_PHASE(TME_SCSI_PHASE_COMMAND);
				_TME_NCR53C9X_CS_MONITOR_PHASE(21);
				count = _tme_ncr53c9x_transfer_count(ncr53c9x);
				if (count == 0) {
				  abort();
				}
				_TME_NCR53C9X_CS_TRANSFER(((cmd & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_SELECT_ATN_STOP
							   ? TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK
							   : TME_SCSI_ACTION_DMA_INITIATOR),
							  count);
				ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state = 1;
	  _TME_NCR53C9X_CS(11):	_TME_NCR53C9X_CS_WAIT_TRANSFER;
				_TME_NCR53C9X_CS_IS(4);
	  _TME_NCR53C9X_CS(16):	_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SR | TME_NCR53C9X_INST_SO);
				_TME_NCR53C9X_CS_DONE;

	  /* "This command will be terminated early in the following situations:" */

	  /* "The target exits the Message Phase early" */
          _TME_NCR53C9X_CS(20):	_TME_NCR53C9X_CS_IS(2);
				_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SR | TME_NCR53C9X_INST_SO);
				_TME_NCR53C9X_CS_DONE;

	  /* "The target does not go to the Command Phase following the
	     Selection Phase"

	     "The target exits the Command Phase early." */
          _TME_NCR53C9X_CS(21):	_TME_NCR53C9X_CS_IS(ncr53c9x->tme_ncr53c9x_transfer_resid == 0 ? 2 : 3);
				_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SR | TME_NCR53C9X_INST_SO);
				_TME_NCR53C9X_CS_DONE;

	  /* "The SCSI Timeout Register times out" */
          _TME_NCR53C9X_CS(30):	_tme_ncr53c9x_disconnect(ncr53c9x);
				_TME_NCR53C9X_CS_IS(0);
				_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_DIS);
				_TME_NCR53C9X_CS_DONE;
	}
	break;

	/* XXX FIXME - we don't support any of the Target mode commands yet: */
      case TME_NCR53C9X_CMD_SEND_MSG:
      case TME_NCR53C9X_CMD_SEND_STATUS:
      case TME_NCR53C9X_CMD_SEND_DATA:
      case TME_NCR53C9X_CMD_DISCONNECT_ST:
      case TME_NCR53C9X_CMD_TERMINATE:
      case TME_NCR53C9X_CMD_TCCS:
      case TME_NCR53C9X_CMD_DISCONNECT:
      case TME_NCR53C9X_CMD_RECV_MSG:
      case TME_NCR53C9X_CMD_RECV_CMD:
      case TME_NCR53C9X_CMD_RECV_DATA:
      case TME_NCR53C9X_CMD_RCS:
      case TME_NCR53C9X_CMD_RESELECT:
      case TME_NCR53C9X_CMD_RESELECT3:
	abort();

	/* XXX FIXME - since we don't support any of the Target mode
	   commands yet, there isn't much to do here: */
      case TME_NCR53C9X_CMD_SELECT_ENABLE:
	assert (cmd_sequence == 0);
	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_IDLE);
	_TME_NCR53C9X_CS_DONE;

	/* XXX FIXME - since we don't support any of the Target mode
	   commands yet, there isn't much to do here: */
      case TME_NCR53C9X_CMD_SELECT_DISABLE:
	assert (cmd_sequence == 0);
	_TME_NCR53C9X_CS_MODE(TME_NCR53C9X_MODE_IDLE);
	_TME_NCR53C9X_CS_INT(TME_NCR53C9X_INST_SO);
	_TME_NCR53C9X_CS_DONE;
      }
    }

    /* if the command at the tail of the command FIFO has finished its
       sequence: */
    if (cmd_sequence == TME_NCR53C9X_CMD_SEQUENCE_DONE) {

      /* if the command sequence was just now finished: */
      if (ncr53c9x->tme_ncr53c9x_cmd_sequence != TME_NCR53C9X_CMD_SEQUENCE_DONE) {

	/* finish the command: */
	_tme_ncr53c9x_cmd_done(ncr53c9x);

	/* mark the command sequence as finished: */
	ncr53c9x->tme_ncr53c9x_cmd_sequence = cmd_sequence;
      }

      /* if we still have to flush the FIFO to DMA, or call out our
	 terminal DMA address, we can't start running another command,
	 because it may need to use DMA too: */
      if (ncr53c9x->tme_ncr53c9x_callout_flags
	  & (TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA
	     | TME_NCR53C9X_CALLOUT_TERMINAL_DMA)) {

	/* stop now: */
	break;
      }
	
      /* if the command FIFO is empty: */
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_cmd_tail;
      if (fifo_tail == ncr53c9x->tme_ncr53c9x_fifo_cmd_head) {

	/* stop now: */
	break;
      }

      /* advance the tail of the command FIFO and reset the command
	 sequence: */
      fifo_tail++;
      if (fifo_tail == TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_cmd)) {
	fifo_tail = 0;
      }
      ncr53c9x->tme_ncr53c9x_fifo_cmd_tail = fifo_tail;
      cmd_sequence = 0;
    }

    /* otherwise, the command at the tail of the command FIFO has not
       finished its sequence: */
    else {
      
      /* if the command sequence number has not changed: */
      if (cmd_sequence == ncr53c9x->tme_ncr53c9x_cmd_sequence) {

	/* stop now: */
	break;
      }
    }

    /* update the command sequence number: */
    ncr53c9x->tme_ncr53c9x_cmd_sequence = cmd_sequence;
  }
}

/* the NCR 53c9x callout function.  it must be called with the mutex locked: */
static void
_tme_ncr53c9x_callout(struct tme_ncr53c9x *ncr53c9x)
{
  struct tme_scsi_connection *conn_scsi;
  struct tme_bus_connection *conn_bus;
  struct tme_bus_tlb *tlb;
  struct tme_bus_tlb tlb_local;
  tme_scsi_control_t scsi_control;
  tme_scsi_data_t scsi_data;
  tme_uint32_t scsi_events;
  tme_uint32_t scsi_actions;
  tme_scsi_control_t last_active_scsi_control;
  tme_scsi_data_t last_active_scsi_data;
  tme_uint32_t last_active_scsi_events;
  tme_uint32_t last_active_scsi_actions;
  struct tme_scsi_dma scsi_dma_buffer;
  struct tme_scsi_dma *scsi_dma;
  unsigned int fifo_head;
  unsigned int fifo_tail;
  int callouts_blocked;
  tme_bus_addr32_t address;
  tme_uint32_t reg_ctc;
  tme_uint32_t resid;
  unsigned int cycle_type;
  int rc;
  int int_asserted;

  /* if this function is already running in another thread, simply
     return now.  the other thread will do our work: */
  if (ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUTS_RUNNING) {
    return;
  }

  /* callouts are now running: */
  ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUTS_RUNNING;

  /* initially, no callouts are blocked: */
  callouts_blocked = 0;

  /* loop forever: */
  for (;;) {

    /* any callout, successful or not, will clear this bit.  if we get
       to the bottom of the loop and this bit is still set, there are
       no more (unblocked) callouts to make, so we can stop: */
    callouts_blocked |= TME_NCR53C9X_CALLOUTS_RUNNING;

    /* if the data FIFO needs to be DMA flushed: */
    if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA) != 0) {

      /* we are running the data FIFO DMA flush: */
      ncr53c9x->tme_ncr53c9x_callout_flags
	&= ((~TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA)
	    | TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA));

      /* get the head and the tail of the data FIFO: */
      fifo_head = ncr53c9x->tme_ncr53c9x_fifo_data_head;
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_data_tail;

      /* DMA must be running: */
      assert (ncr53c9x->tme_ncr53c9x_dma_running);

      /* get the DMA address and the CTC register: */
      address = ncr53c9x->tme_ncr53c9x_dma_address;
      reg_ctc = _tme_ncr53c9x_ctc_read(ncr53c9x);

      /* transfer out of the data FIFO starting at the tail, until the
	 head, or the right end of the data FIFO, or after CTC bytes,
	 whichever comes first: */
      resid = ((fifo_head < fifo_tail
		? TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data)
		: fifo_head)
	       - fifo_tail);
      resid = TME_MIN(resid, reg_ctc);

      /* we must have data to transfer: */
      assert (resid > 0);

      /* call out the transfer: */
      /* NB that we don't care about bus errors here - there's no way
	 to signal them to the chip.  it's possible that other DMA
	 hardware feeding the chip may note the bus error, though (and
	 may possibly intercept our callout of the terminal DMA
	 address, to substitute the actual address where the bus error
	 occurred, so software can recover it): */
      (void) tme_bus_device_dma_write_16(&ncr53c9x->tme_ncr53c9x_device,
					 address,
					 resid,
					 &ncr53c9x->tme_ncr53c9x_fifo_data[fifo_tail],
					 0);

      /* unblock all callouts: */
      callouts_blocked = 0;

      /* if this callout was aborted or changed: */
      if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA)
	  != TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA)) {

	/* XXX FIXME - should we log an error here?  something
	   (probably software) aborted a DMA, but we can't abort a DMA
	   once it's been called out, so we likely transferred data
	   (changing the state of the system) after we reported the
	   abort complete to software: */
	continue;
      }

      /* the FIFO tail, DMA address, the CTC register must not have changed: */
      assert (fifo_tail == ncr53c9x->tme_ncr53c9x_fifo_data_tail);
      assert (address == ncr53c9x->tme_ncr53c9x_dma_address);
      assert (reg_ctc == _tme_ncr53c9x_ctc_read(ncr53c9x));

      /* update the FIFO tail, DMA address and CTC register: */
      ncr53c9x->tme_ncr53c9x_fifo_data_tail
	= ((ncr53c9x->tme_ncr53c9x_fifo_data_tail
	    + resid)
	   % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data));
      _tme_ncr53c9x_fifo_data_update(ncr53c9x);
      ncr53c9x->tme_ncr53c9x_dma_address += resid;
      reg_ctc -= resid;
      _tme_ncr53c9x_ctc_write(ncr53c9x, reg_ctc);

      /* if the FIFO can't be flushed any more: */
      if (ncr53c9x->tme_ncr53c9x_fifo_data_tail == ncr53c9x->tme_ncr53c9x_fifo_data_head
	  || reg_ctc == 0) {

	/* we don't need to make this callout any more: */
	ncr53c9x->tme_ncr53c9x_callout_flags &= ~TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA;
      }
    }

    /* if we need to call out the terminal DMA address, and this
       callout isn't blocked: */
    if ((ncr53c9x->tme_ncr53c9x_callout_flags
	 & TME_NCR53C9X_CALLOUT_TERMINAL_DMA
	 & ~callouts_blocked) != 0) {
      TME_NCR53C9X_DEBUG_BP(dma_terminal);

      /* if the data FIFO needs to be DMA flushed, we have to do that
	 first, so this callout is blocked: */
      if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA) != 0) {
	callouts_blocked |= TME_NCR53C9X_CALLOUT_TERMINAL_DMA;
	continue;
      }

      /* we are running the terminal DMA address callout: */
      ncr53c9x->tme_ncr53c9x_callout_flags
	&= ((~TME_NCR53C9X_CALLOUT_TERMINAL_DMA)
	    | TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_TERMINAL_DMA));

      /* DMA must be running: */
      assert (ncr53c9x->tme_ncr53c9x_dma_running);

      /* get the terminal DMA address: */
      address = ncr53c9x->tme_ncr53c9x_dma_address;

      /* get our bus connection: */
      conn_bus = tme_memory_atomic_pointer_read(struct tme_bus_connection *,
						ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection,
						&ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection_rwlock);						  

      /* unlock the mutex: */
      tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);
      
      /* do the callout: */
      rc = (conn_bus != NULL
	    ? ((*conn_bus->tme_bus_tlb_fill)
	       (conn_bus,
		NULL,
		address,
		TME_BUS_CYCLE_UNDEF))
	    : TME_OK);
	
      /* lock the mutex: */
      tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

      /* unblock all callouts: */
      callouts_blocked = 0;

      /* if this callout was aborted or changed: */
      if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_TERMINAL_DMA)
	  != TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_TERMINAL_DMA)) {

	/* XXX FIXME - should we log an error here?  something
	   (probably software) aborted a terminal DMA callout, but we
	   can't abort one once it's been called out, so we likely
	   effected the callout (changing the state of the system)
	   after we reported the abort complete to software: */
	continue;
      }

      /* DMA must still be running, and the terminal DMA address must
         not have changed: */
      assert (ncr53c9x->tme_ncr53c9x_dma_running);
      assert (ncr53c9x->tme_ncr53c9x_dma_address == address);

      /* if this callout failed: */
      if (rc != TME_OK) {

	/* block this callout until some other callout succeeds: */
	callouts_blocked |= TME_NCR53C9X_CALLOUT_TERMINAL_DMA;
	continue;
      }

      /* DMA is no longer running: */
      ncr53c9x->tme_ncr53c9x_dma_running = FALSE;

      /* we don't need to make this callout any more: */
      ncr53c9x->tme_ncr53c9x_callout_flags &= ~TME_NCR53C9X_CALLOUT_TERMINAL_DMA;
    }

    /* if our interrupt signal has changed, and this callout isn't
       blocked: */
    fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_status_tail;
    int_asserted = ((ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_stat
		     & TME_NCR53C9X_STAT_INT) != 0);
    if (!int_asserted != !ncr53c9x->tme_ncr53c9x_last_int_asserted
	&& (callouts_blocked & TME_NCR53C9X_CALLOUT_INT) == 0) {

      /* if we need to assert the interrupt signal, and either the
	 data FIFO needs to be data flushed, or we need to call out
	 the terminal DMA address, we need to do those first, so this
	 callout is blocked: */
      if (int_asserted
	  && (ncr53c9x->tme_ncr53c9x_callout_flags
	      & (TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA
		 | TME_NCR53C9X_CALLOUT_TERMINAL_DMA)) != 0) {
	callouts_blocked |= TME_NCR53C9X_CALLOUT_INT;
	continue;
      }

      /* get our bus connection: */
      conn_bus = tme_memory_atomic_pointer_read(struct tme_bus_connection *,
						ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection,
						&ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection_rwlock);

      /* unlock our mutex: */
      tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);
	
      /* call out the bus interrupt signal edge: */
      rc = (*conn_bus->tme_bus_signal)
	(conn_bus,
	 TME_BUS_SIGNAL_INT_UNSPEC
	 | (int_asserted
	    ? TME_BUS_SIGNAL_LEVEL_ASSERTED
	    : TME_BUS_SIGNAL_LEVEL_NEGATED));

      /* lock our mutex: */
      tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

      /* unblock all callouts: */
      callouts_blocked = 0;

      /* if this callout failed: */
      if (rc != TME_OK) {

	/* block this callout until some other callout succeeds: */
	callouts_blocked |= TME_NCR53C9X_CALLOUT_INT;
	continue;
      }
	  
      /* note the new state of the interrupt signal: */
      ncr53c9x->tme_ncr53c9x_last_int_asserted = int_asserted;
    }

    /* assume that we won't make a SCSI cycle callout: */
    scsi_control = 0;
    scsi_data = 0;
    scsi_events = TME_SCSI_EVENT_NONE;
    scsi_actions = TME_SCSI_ACTION_NONE;
    scsi_dma = NULL;

    /* if we need to call out a SCSI cycle, and this callout isn't
       blocked: */
    if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_SCSI_CYCLE
	 & TME_NCR53C9X_CALLOUT_SCSI_CYCLE
	 & ~callouts_blocked) != 0) {

      /* we are running the SCSI cycle callout: */
      ncr53c9x->tme_ncr53c9x_callout_flags
	&= ((~TME_NCR53C9X_CALLOUT_SCSI_CYCLE)
	    | TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_SCSI_CYCLE));
      
      /* get our desired output SCSI cycle: */
      scsi_control = ncr53c9x->tme_ncr53c9x_out_scsi_control;
      scsi_data = ncr53c9x->tme_ncr53c9x_out_scsi_data;
      scsi_events = ncr53c9x->tme_ncr53c9x_out_scsi_events;
      scsi_actions = ncr53c9x->tme_ncr53c9x_out_scsi_actions;

      /* the desired output SCSI cycle must have some events or
         actions: */
      assert (scsi_events != TME_SCSI_EVENT_NONE
	      || scsi_actions != TME_SCSI_ACTION_NONE);

      /* if this is a SCSI DMA callout: */
      if (scsi_actions
	  & (TME_SCSI_ACTION_DMA_TARGET
	     | TME_SCSI_ACTION_DMA_INITIATOR
	     | TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)) {
	TME_NCR53C9X_DEBUG_BP(dma_scsi);

	/* we must have no SCSI events: */
	assert (scsi_events == TME_SCSI_EVENT_NONE);

	/* the command sequence SCSI bus transfer residual must be
	   nonzero: */
	assert (ncr53c9x->tme_ncr53c9x_transfer_resid > 0);

	/* set the bus cycle type to TME_BUS_CYCLE_WRITE if the SCSI
	   DMA cycle will transfer in from the SCSI bus, else set it
	   to TME_BUS_CYCLE_READ: */
	cycle_type = (_tme_ncr53c9x_transfer_input(ncr53c9x)
		      ? TME_BUS_CYCLE_WRITE
		      : TME_BUS_CYCLE_READ);

	/* assume that DMA is not running: */
	tlb = NULL;

	/* if DMA is running: */
	if (ncr53c9x->tme_ncr53c9x_dma_running) {

	  /* get the DMA address: */
	  address = ncr53c9x->tme_ncr53c9x_dma_address;

	  /* the CTC register must be nonzero: */
	  assert (_tme_ncr53c9x_ctc_read(ncr53c9x) > 0);

	  /* get our TLB entry: */
	  tlb = &ncr53c9x->tme_ncr53c9x_dma_tlb;

	  /* busy this TLB entry: */
	  tme_bus_tlb_busy(tlb);

	  /* if the TLB entry is valid, covers this address and allows
	     the needed access: */
	  if (tme_bus_tlb_is_valid(tlb)
	      && address >= (tme_bus_addr32_t) tlb->tme_bus_tlb_addr_first
	      && address <= (tme_bus_addr32_t) tlb->tme_bus_tlb_addr_last
	      && (((cycle_type == TME_BUS_CYCLE_READ
		    ? tlb->tme_bus_tlb_emulator_off_read
		    : tlb->tme_bus_tlb_emulator_off_write) != TME_EMULATOR_OFF_UNDEF)
		  || (tlb->tme_bus_tlb_cycles_ok & cycle_type))) {

	    /* unbusy this TLB entry: */
	    tme_bus_tlb_unbusy(tlb);
	  }

	  /* otherwise, this TLB entry is invalid, or doesn't cover this
	     address, or doesn't allow the needed access: */
	  else {

	    /* unbusy this TLB entry for filling: */
	    tme_bus_tlb_unbusy_fill(tlb);

	    /* pass this TLB's token: */
	    tlb_local.tme_bus_tlb_token = tlb->tme_bus_tlb_token;

	    /* get our bus connection: */
	    conn_bus = tme_memory_atomic_pointer_read(struct tme_bus_connection *,
						      ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection,
						      &ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection_rwlock);

	    /* unlock the mutex: */
	    tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);
      
	    /* do the callout: */
	    rc = (conn_bus != NULL
		  ? ((*conn_bus->tme_bus_tlb_fill)
		     (conn_bus,
		      &tlb_local,
		      address,
		      cycle_type))
		  : EAGAIN);
	
	    /* lock the mutex: */
	    tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

	    /* unblock all callouts: */
	    callouts_blocked = 0;

	    /* if the SCSI cycle callout was aborted or changed, restart: */
	    if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_SCSI_CYCLE)
		!= TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_SCSI_CYCLE)) {
	      continue;
	    }

	    /* DMA must still be running, and the DMA address must
	       not have changed: */
	    assert (ncr53c9x->tme_ncr53c9x_dma_running);
	    assert (ncr53c9x->tme_ncr53c9x_dma_address == address);

	    /* if this callout failed: */
	    if (rc != TME_OK) {

	      /* block this callout until some other callout succeeds.
		 if there isn't an active SCSI cycle called out, we
		 may actually call out this SCSI cycle's control and
		 data lines, but instead of running DMA we will just
		 wait for a bus change: */
	      callouts_blocked |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
	      continue;
	    }
	  
	    /* copy the local TLB entry into the global TLB entry: */
	    *tlb = tlb_local;
	  }
	}

	/* start the SCSI DMA structure: */
	/* XXX parity? */
	scsi_dma = &scsi_dma_buffer;
	scsi_dma_buffer.tme_scsi_dma_flags = TME_SCSI_DMA_8BIT;
	scsi_dma_buffer.tme_scsi_dma_sync_offset = 0;
	scsi_dma_buffer.tme_scsi_dma_sync_period = 0;

	/* get the head and the tail of the data FIFO: */
	fifo_head = ncr53c9x->tme_ncr53c9x_fifo_data_head;
	fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_data_tail;

	/* assume that new data will be transferred into the data
	   FIFO, either from the SCSI bus or from the data bus.  we
	   will transfer data into the data FIFO starting at the head,
	   until the tail, or until the right end of the data FIFO,
	   whichever comes first: */
	scsi_dma_buffer.tme_scsi_dma_resid
	  = (((fifo_head < fifo_tail)
	      ? fifo_tail
	      : TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data))
	     - fifo_head);

	/* note that we can't fill all positions of the data FIFO,
	   because we identify an empty data FIFO when the head and
	   the tail are equal: */
	if (((fifo_head + scsi_dma_buffer.tme_scsi_dma_resid)
	     % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data))
	    == fifo_tail) {
	  scsi_dma_buffer.tme_scsi_dma_resid--;
	}

	/* if the SCSI DMA cycle will transfer in from the SCSI bus: */
	if (cycle_type == TME_BUS_CYCLE_WRITE) {

	  /* assume that we're transferring into the data FIFO: */
	  scsi_dma_buffer.tme_scsi_dma_in = &ncr53c9x->tme_ncr53c9x_fifo_data[fifo_head];
#ifndef NDEBUG
	  scsi_dma_buffer.tme_scsi_dma_out = NULL;
#endif /* NDEBUG */

	  /* if DMA is running: */
	  if (ncr53c9x->tme_ncr53c9x_dma_running) {

	    /* if the data FIFO isn't empty: */
	    if (fifo_head != fifo_tail) {

	      /* don't transfer any data from the SCSI bus now, and
		 request that the data FIFO be flushed: */
	      /* NB: this isn't really necessary - we could just go
		 ahead and transfer more data from the SCSI bus into
		 the data FIFO, and flush it all out later.  but
		 instead we assume that if we let the data in the FIFO
		 flush to DMA now, after we advance the DMA address we
		 may be able to start doing truly direct DMAs: */
	      scsi_dma_buffer.tme_scsi_dma_resid = 0;
	      ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA;
	    }

	    /* otherwise, the data FIFO is empty: */
	    else {

	      /* if this TLB entry supports fast writing: */
	      if (tlb->tme_bus_tlb_emulator_off_write != TME_EMULATOR_OFF_UNDEF) {

		/* get the DMA address: */
		address = ncr53c9x->tme_ncr53c9x_dma_address;

		/* transfer starting from the fast writing address,
		   stopping at least at the end of this TLB entry: */
		/* XXX FIXME - this breaks volatile: */
		scsi_dma_buffer.tme_scsi_dma_in = (tme_uint8_t *) (tlb->tme_bus_tlb_emulator_off_write + address);
		scsi_dma_buffer.tme_scsi_dma_resid = (((tme_bus_addr32_t) tlb->tme_bus_tlb_addr_last) - address) + 1;
	      }

	      /* otherwise, this TLB entry doesn't support fast writing: */
	      else {

		/* we're already set up to transfer in to the data FIFO: */
	      }

	      /* don't transfer more than CTC bytes: */
	      reg_ctc = _tme_ncr53c9x_ctc_read(ncr53c9x);
	      if (reg_ctc < scsi_dma_buffer.tme_scsi_dma_resid) {
		scsi_dma_buffer.tme_scsi_dma_resid = reg_ctc;
	      }
	    }
	  }

	  /* otherwise, DMA is not running: */
	  else {

	    /* if we're not transferring any data now, the data FIFO
               is full: */
	    if (scsi_dma_buffer.tme_scsi_dma_resid == 0) {

	      /* XXX FIXME - what happens here?  for now, we assume
		 that we do nothing, and just wait for software to
		 read data out of the FIFO, to make room for more
		 data: */
	    }
	  }
	}

	/* otherwise, the SCSI DMA cycle will transfer out to the SCSI bus: */
	else {

#ifndef NDEBUG
	  /* we're not transferring in from the SCSI bus: */
	  scsi_dma_buffer.tme_scsi_dma_in = NULL;
#endif /* NDEBUG */

	  /* if DMA is not running: */
	  if (!ncr53c9x->tme_ncr53c9x_dma_running) {

	    /* unless the FIFO is not empty, which we check below, we
	       don't have any data to transfer now: */
	    scsi_dma_buffer.tme_scsi_dma_out = NULL;
	    scsi_dma_buffer.tme_scsi_dma_resid = 0;
	  }

	  /* otherwise, DMA is running.  if there isn't any data in
	     the FIFO that needs to be transferred first: */
	  else if (fifo_tail == fifo_head) {

	    /* get the DMA address and the CTC register: */
	    address = ncr53c9x->tme_ncr53c9x_dma_address;
	    reg_ctc = _tme_ncr53c9x_ctc_read(ncr53c9x);

	    /* if this TLB entry supports fast reading: */
	    if (tlb->tme_bus_tlb_emulator_off_read != TME_EMULATOR_OFF_UNDEF) {

	      /* transfer starting from the fast reading address, stopping
		 at least at the end of this TLB entry: */
	      /* XXX FIXME - this breaks volatile: */
	      scsi_dma_buffer.tme_scsi_dma_out = (const tme_uint8_t *) (tlb->tme_bus_tlb_emulator_off_read + address);
	      scsi_dma_buffer.tme_scsi_dma_resid = (((tme_bus_addr32_t) tlb->tme_bus_tlb_addr_last) - address) + 1;

	      /* don't transfer more than CTC bytes: */
	      if (reg_ctc < scsi_dma_buffer.tme_scsi_dma_resid) {
		scsi_dma_buffer.tme_scsi_dma_resid = reg_ctc;
	      }
	    }

	    /* otherwise, this TLB entry doesn't support fast reading: */
	    else {

	      /* transfer into the data FIFO starting at the head,
		 until the tail, or the right end of the data FIFO, or
		 after CTC bytes, or after the SCSI bus transfer
		 residual, whichever comes first: */
	      resid = TME_MIN(scsi_dma_buffer.tme_scsi_dma_resid, reg_ctc);
	      resid = TME_MIN(resid, ncr53c9x->tme_ncr53c9x_transfer_resid);

	      /* if we need to detect the actual SCSI bus transfer
		 residual based on the data transferred: */
	      if (ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state != 0) {

		/* we can't DMA more than one byte at a time before
		   we've detected the actual SCSI bus residual: */
		assert (resid > 0);
		resid = 1;
	      }

	      /* call out the transfer: */
	      /* NB that we don't care about bus errors here - there's
		 no way to signal them to the chip.  it's possible
		 that other DMA hardware feeding the chip may note the
		 bus error, though (and may possibly intercept our
		 callout of the terminal DMA address, to substitute
		 the actual address where the bus error occurred, so
		 software can recover it): */
	      (void) tme_bus_device_dma_read_16(&ncr53c9x->tme_ncr53c9x_device,
						address,
						resid,
						&ncr53c9x->tme_ncr53c9x_fifo_data[fifo_head],
						0);

	      /* unblock all callouts: */
	      callouts_blocked = 0;

	      /* if this callout was aborted or changed: */
	      if ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_SCSI_CYCLE)
		  != TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_SCSI_CYCLE)) {

		/* XXX FIXME - should we log an error here?  something
		   (probably software) aborted a DMA, but we can't
		   abort a DMA once it's been called out, so we likely
		   transferred data (changing the state of the system)
		   after we reported the abort complete to software: */
		continue;
	      }

	      /* the FIFO head, DMA address, the CTC register must not have changed: */
	      assert (fifo_head == ncr53c9x->tme_ncr53c9x_fifo_data_head);
	      assert (address == ncr53c9x->tme_ncr53c9x_dma_address);
	      assert (reg_ctc == _tme_ncr53c9x_ctc_read(ncr53c9x));

	      /* update the FIFO head, DMA address and CTC register: */
	      fifo_head = (fifo_head + resid) % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data);
	      ncr53c9x->tme_ncr53c9x_fifo_data_head = fifo_head;
	      _tme_ncr53c9x_fifo_data_update(ncr53c9x);
	      ncr53c9x->tme_ncr53c9x_dma_address += resid;
	      reg_ctc -= resid;
	      _tme_ncr53c9x_ctc_write(ncr53c9x, reg_ctc);

	      /* reload the data FIFO tail: */
	      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_data_tail;

	      /* the data FIFO must not be empty: */
	      assert (fifo_head != fifo_tail);
	    }
	  }

	  /* if the data FIFO isn't empty: */
	  if (fifo_head != fifo_tail) {

	    /* transfer out of the data FIFO starting at the tail,
	       until the head, or the right end of the data FIFO,
	       whichever comes first: */
	    scsi_dma_buffer.tme_scsi_dma_out = &ncr53c9x->tme_ncr53c9x_fifo_data[fifo_tail];
	    scsi_dma_buffer.tme_scsi_dma_resid = ((fifo_head < fifo_tail
						   ? TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data)
						   : fifo_head)
						  - fifo_tail);
	  }

	  /* if we need to detect the SCSI bus transfer residual based
	     on the data being transferred: */
	  if (ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state != 0) {

	    /* try to detect the SCSI bus transfer residual: */
	    resid
	      = tme_scsi_phase_resid(((ncr53c9x->tme_ncr53c9x_mode
				       == TME_NCR53C9X_MODE_INITIATOR)
				      ? ncr53c9x->tme_ncr53c9x_latched_phase
				      : ncr53c9x->tme_ncr53c9x_out_scsi_control),
				     &ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state,
				     scsi_dma_buffer.tme_scsi_dma_out,
				     scsi_dma_buffer.tme_scsi_dma_resid);
	    if (resid != 0) {
	      ncr53c9x->tme_ncr53c9x_transfer_resid
		= TME_MIN(ncr53c9x->tme_ncr53c9x_transfer_resid, resid);
	    }
	  }
	}

	/* don't transfer more bytes over the SCSI bus than requested
           by the command sequence: */
	scsi_dma_buffer.tme_scsi_dma_resid
	  = TME_MIN(scsi_dma_buffer.tme_scsi_dma_resid,
		    ncr53c9x->tme_ncr53c9x_transfer_resid);

	/* if we're the initiator and we want to hold ACK on the last
	   byte in the transfer, but either we haven't detected which
	   is the last byte yet or it's not in this DMA, we don't want
	   to hold ACK on the last byte in this DMA: */
	if (((scsi_actions & TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)
	     == TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)
	    && (ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state != 0
		|| scsi_dma_buffer.tme_scsi_dma_resid < ncr53c9x->tme_ncr53c9x_transfer_resid)) {
	  scsi_actions
	    = ((scsi_actions & ~TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)
	       | TME_SCSI_ACTION_DMA_INITIATOR);
	}

	/* XXX FIXME - in initiator mode, ACK may have been left set
	   by a previous command.  clear it before running this DMA
	   cycle: */

	/* if we don't have a SCSI DMA buffer: */
	if (scsi_dma_buffer.tme_scsi_dma_resid == 0) {

	  /* call out a SCSI cycle that just waits for a bus change: */
	  scsi_events = TME_SCSI_EVENT_BUS_CHANGE;
	  scsi_actions = TME_SCSI_ACTION_NONE;
	  scsi_data = 0;
	  scsi_dma = NULL;
	}
      }
    }

    /* otherwise, we either don't need to call out a SCSI cycle, or
       this callout is blocked.  if the callout isn't blocked, but no
       SCSI cycle is active: */
    else if ((callouts_blocked & TME_NCR53C9X_CALLOUT_SCSI_CYCLE) == 0
	     && ncr53c9x->tme_ncr53c9x_active_scsi_events == TME_SCSI_EVENT_NONE
	     && ncr53c9x->tme_ncr53c9x_active_scsi_actions == TME_SCSI_ACTION_NONE) {

      /* call out the control and data signals from the last active
	 SCSI cycle, and just wait for a SCSI bus change: */
      scsi_control = ncr53c9x->tme_ncr53c9x_active_scsi_control;
      scsi_data = ncr53c9x->tme_ncr53c9x_active_scsi_data;
      scsi_events = TME_SCSI_EVENT_BUS_CHANGE;
      scsi_actions = TME_SCSI_ACTION_NONE;
    }

    /* if we need to call out a SCSI cycle: */
    if (scsi_events != TME_SCSI_EVENT_NONE
	|| scsi_actions != TME_SCSI_ACTION_NONE) {

      /* get this card's SCSI connection: */
      conn_scsi = ncr53c9x->tme_ncr53c9x_scsi_connection;

      /* remember the SCSI cycle that is active before the callout: */
      last_active_scsi_control = ncr53c9x->tme_ncr53c9x_active_scsi_control;
      last_active_scsi_data = ncr53c9x->tme_ncr53c9x_active_scsi_data;
      last_active_scsi_events = ncr53c9x->tme_ncr53c9x_active_scsi_events;
      last_active_scsi_actions = ncr53c9x->tme_ncr53c9x_active_scsi_actions;

      /* assume that the callout will succeed, and set this new SCSI
	 cycle as active: */
      /* NB: we *cannot* do this after the callout returns, because
	 while we are calling this cycle out, the finished cycle may
	 get called out back to us: */
      ncr53c9x->tme_ncr53c9x_active_scsi_control = scsi_control;
      ncr53c9x->tme_ncr53c9x_active_scsi_data = scsi_data;
      ncr53c9x->tme_ncr53c9x_active_scsi_events = scsi_events;
      ncr53c9x->tme_ncr53c9x_active_scsi_actions = scsi_actions;
      ncr53c9x->tme_ncr53c9x_active_scsi_dma_resid
	= (scsi_dma != NULL
	   ? scsi_dma->tme_scsi_dma_resid
	   : 0);

      /* flip the SCSI cycle marker, and add it to the actions: */
      ncr53c9x->tme_ncr53c9x_active_scsi_cycle_marker
	^= TME_SCSI_ACTION_CYCLE_MARKER;
      scsi_actions |= ncr53c9x->tme_ncr53c9x_active_scsi_cycle_marker;

      /* unlock the mutex: */
      tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);
      
      /* do the callout: */
      rc = (conn_scsi != NULL
	    ? ((*conn_scsi->tme_scsi_connection_cycle)
	       (conn_scsi,
		scsi_control,
		scsi_data,
		scsi_events,
		scsi_actions,
		scsi_dma))
	    : TME_OK);

      /* lock the mutex: */
      tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

      /* unblock all callouts: */
      callouts_blocked = 0;

      /* if the SCSI cycle callout failed: */
      if (rc != TME_OK) {

	/* assume that the SCSI cycle that was active before we tried
	   to call out, is still active after the callout failure: */	   
	ncr53c9x->tme_ncr53c9x_active_scsi_control = last_active_scsi_control;
	ncr53c9x->tme_ncr53c9x_active_scsi_data = last_active_scsi_data;
	ncr53c9x->tme_ncr53c9x_active_scsi_events = last_active_scsi_events;
	ncr53c9x->tme_ncr53c9x_active_scsi_actions = last_active_scsi_actions;
	ncr53c9x->tme_ncr53c9x_active_scsi_cycle_marker
	  ^= TME_SCSI_ACTION_CYCLE_MARKER;

	/* block this callout until some other callout succeeds: */
	callouts_blocked |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
	continue;
      }

      /* if we called out the desired output SCSI cycle, and it isn't
	 a DMA cycle: */
      if (scsi_control == ncr53c9x->tme_ncr53c9x_out_scsi_control
	  && scsi_data == ncr53c9x->tme_ncr53c9x_out_scsi_data
	  && scsi_events == ncr53c9x->tme_ncr53c9x_out_scsi_events
	  && ((scsi_actions & ~TME_SCSI_ACTION_CYCLE_MARKER)
	      == ncr53c9x->tme_ncr53c9x_out_scsi_actions)
	  && scsi_dma == NULL) {

	/* we don't need to make this callout any more: */
	ncr53c9x->tme_ncr53c9x_callout_flags &= ~TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
      }
    }

    /* if no more (unblocked) callouts can run, we can stop: */
    if (callouts_blocked & TME_NCR53C9X_CALLOUTS_RUNNING) {
      break;
    }
  }
  
  /* clear that callouts are running: */
  ncr53c9x->tme_ncr53c9x_callout_flags &= ~TME_NCR53C9X_CALLOUTS_RUNNING;
}

/* this is called for an event on the SCSI bus: */
static int
_tme_ncr53c9x_scsi_cycle(struct tme_scsi_connection *conn_scsi,
			 tme_scsi_control_t scsi_control,
			 tme_scsi_data_t scsi_data,
			 tme_uint32_t scsi_events_triggered,
			 tme_uint32_t scsi_actions_taken,
			 const struct tme_scsi_dma *scsi_dma)
{
  struct tme_ncr53c9x *ncr53c9x;
  unsigned long transfer_count;
  unsigned long resid;
  const tme_uint8_t *transfer_buffer;
  unsigned int fifo_head;
  unsigned int fifo_tail;
  tme_uint32_t reg_ctc;
  unsigned int cycle_type;
  int callout_clear;
  
  /* recover our data structure: */
  ncr53c9x = conn_scsi->tme_scsi_connection.tme_connection_element->tme_element_private;

  /* assume that we won't clear the SCSI cycle callout flags: */
  callout_clear = FALSE;

  /* lock the mutex: */
  tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

  /* if this input SCSI cycle follows our active output SCSI cycle: */
  if (((scsi_actions_taken 
	^ ncr53c9x->tme_ncr53c9x_active_scsi_cycle_marker)
       & TME_SCSI_ACTION_CYCLE_MARKER) == 0) {

    /* the active output SCSI cycle now has the same control and data
       lines, but is waiting on no events and will take no actions: */
    ncr53c9x->tme_ncr53c9x_active_scsi_events = TME_SCSI_EVENT_NONE;
    ncr53c9x->tme_ncr53c9x_active_scsi_actions = TME_SCSI_ACTION_NONE;

    /* assume that, if the SCSI cycle callout is still marked running,
       we can clear the callout flags: */
    callout_clear = TRUE;
  }
  scsi_actions_taken &= ~TME_SCSI_ACTION_CYCLE_MARKER;

  /* set the last input SCSI cycle.  we accumulate the events
     triggered and actions taken, filtering out bus change and bus
     free events: */
  ncr53c9x->tme_ncr53c9x_in_scsi_control = scsi_control;
  ncr53c9x->tme_ncr53c9x_in_scsi_data = scsi_data;
  ncr53c9x->tme_ncr53c9x_in_scsi_events
    |= (scsi_events_triggered
	& ~(TME_SCSI_EVENT_BUS_CHANGE
	    | TME_SCSI_EVENT_BUS_FREE));
  ncr53c9x->tme_ncr53c9x_in_scsi_actions |= scsi_actions_taken;

  /* if a SCSI selection with ATN cycle just finished: */
  if ((scsi_actions_taken
       & TME_SCSI_ACTION_SELECT_WITH_ATN)
      == TME_SCSI_ACTION_SELECT_WITH_ATN) {

    /* ATN has already been asserted on our behalf, and we have to
       make sure that we continue to assert it: */
    ncr53c9x->tme_ncr53c9x_out_scsi_control |= TME_SCSI_SIGNAL_ATN;
    ncr53c9x->tme_ncr53c9x_active_scsi_control |= TME_SCSI_SIGNAL_ATN;
  }

  /* if a SCSI DMA cycle just finished: */
  if (scsi_actions_taken
      & (TME_SCSI_ACTION_DMA_TARGET
	 | TME_SCSI_ACTION_DMA_INITIATOR
	 | TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)) {

    /* this input cycle must follow our active output SCSI cycle: */
    assert (ncr53c9x->tme_ncr53c9x_active_scsi_events == TME_SCSI_EVENT_NONE
	    && ncr53c9x->tme_ncr53c9x_active_scsi_actions == TME_SCSI_ACTION_NONE);

    /* get the count of bytes transferred: */
    transfer_count = ncr53c9x->tme_ncr53c9x_active_scsi_dma_resid - scsi_dma->tme_scsi_dma_resid;

    /* update the number of expected bytes in this transfer: */
    resid = ncr53c9x->tme_ncr53c9x_transfer_resid;
    assert (transfer_count <= resid);
    resid -= transfer_count;
    ncr53c9x->tme_ncr53c9x_transfer_resid = resid;

    /* if this SCSI DMA cycle has no more bytes to transfer: */
    if (resid == 0) {

      /* if a SCSI initiator DMA with hold ACK cycle just finished: */
      if ((scsi_actions_taken
	   & TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK)
	  == TME_SCSI_ACTION_DMA_INITIATOR_HOLD_ACK) {

	/* ACK has already been asserted on our behalf, and we have to
	   make sure that we continue to assert it: */
	ncr53c9x->tme_ncr53c9x_out_scsi_control |= TME_SCSI_SIGNAL_ACK;
	ncr53c9x->tme_ncr53c9x_active_scsi_control |= TME_SCSI_SIGNAL_ACK;
      }
    }

    /* otherwise, this SCSI DMA cycle has more bytes to transfer: */
    else {

      /* we can't clear the callout flags for this DMA SCSI cycle yet.
	 we probably just reached the end of our TLB entry, and we
	 need to advance in DMA and call out the DMA SCSI cycle again.
	 if something else has happened (like we're the initiator, and
	 the target has suddenly changed phases), we'll pick that up
	 in _tme_ncr53c9x_update(): */
      callout_clear = FALSE;
    }

    /* if the SCSI DMA cycle transferred in from the SCSI bus: */
    if (_tme_ncr53c9x_transfer_input(ncr53c9x)) {

      /* set the bus cycle type to TME_BUS_CYCLE_WRITE and get
	 a pointer to the bytes transferred: */
      cycle_type = TME_BUS_CYCLE_WRITE;
      transfer_buffer = scsi_dma->tme_scsi_dma_in - transfer_count;

      /* if we need to detect the SCSI bus transfer residual based
	 on the data being transferred: */
      if (ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state != 0) {

	/* try to detect the SCSI bus transfer residual: */
	resid
	  = tme_scsi_phase_resid(((ncr53c9x->tme_ncr53c9x_mode
				   == TME_NCR53C9X_MODE_INITIATOR)
				  ? ncr53c9x->tme_ncr53c9x_latched_phase
				  : ncr53c9x->tme_ncr53c9x_out_scsi_control),
				 &ncr53c9x->tme_ncr53c9x_transfer_resid_detect_state,
				 transfer_buffer,
				 transfer_count);
	if (resid != 0) {
	  ncr53c9x->tme_ncr53c9x_transfer_resid
	    = TME_MIN(ncr53c9x->tme_ncr53c9x_transfer_resid, resid);
	}
      }
    }

    /* otherwise, the SCSI DMA cycle transferred out to the SCSI bus: */
    else {

      /* set the bus cycle type to TME_BUS_CYCLE_READ and get
	 a pointer to the bytes transferred: */
      cycle_type = TME_BUS_CYCLE_READ;
      transfer_buffer = scsi_dma->tme_scsi_dma_out - transfer_count;
    }

    /* if this SCSI DMA cycle didn't transfer to or from the data
       FIFO: */
    if ((transfer_buffer
	 < &ncr53c9x->tme_ncr53c9x_fifo_data[0])
	|| (transfer_buffer
	    > &ncr53c9x->tme_ncr53c9x_fifo_data[TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data) - 1])) {

      /* the data FIFO must be empty: */
      assert (ncr53c9x->tme_ncr53c9x_fifo_data_head == ncr53c9x->tme_ncr53c9x_fifo_data_tail);

      /* DMA must be running: */
      assert (ncr53c9x->tme_ncr53c9x_dma_running);

      /* the transfer count can't be greater than the CTC register: */
      reg_ctc = _tme_ncr53c9x_ctc_read(ncr53c9x);
      assert (transfer_count <= reg_ctc);
      
      /* update the DMA address and CTC register: */
      ncr53c9x->tme_ncr53c9x_dma_address += transfer_count;
      reg_ctc -= transfer_count;
      _tme_ncr53c9x_ctc_write(ncr53c9x, reg_ctc);
    }

    /* otherwise, this SCSI DMA cycle transferred to or from the data
       FIFO: */
    else {

      /* if the SCSI DMA cycle transferred in from the SCSI bus: */
      if (cycle_type == TME_BUS_CYCLE_WRITE) {

	/* if the SCSI DMA cycle transferred into the head of the data
           FIFO: */
	fifo_head = ncr53c9x->tme_ncr53c9x_fifo_data_head;
	if (transfer_buffer == &ncr53c9x->tme_ncr53c9x_fifo_data[fifo_head]) {

	  /* update the data FIFO head: */
	  ncr53c9x->tme_ncr53c9x_fifo_data_head
	    = ((fifo_head + transfer_count)
	       % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data));
	  _tme_ncr53c9x_fifo_data_update(ncr53c9x);

	  /* if DMA is running: */
	  if (ncr53c9x->tme_ncr53c9x_dma_running) {

	    /* we need to flush the data FIFO to DMA: */
	    ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_FLUSH_FIFO_DMA;
	  }
	}
      }

      /* otherwise, the SCSI DMA cycle transferred out to the SCSI bus: */
      else {

	/* if the SCSI DMA cycle transferred out of the tail of the data FIFO: */
	fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_data_tail;
	if (transfer_buffer == &ncr53c9x->tme_ncr53c9x_fifo_data[fifo_tail]) {

	  /* update the data FIFO tail: */
	  ncr53c9x->tme_ncr53c9x_fifo_data_tail
	    = ((fifo_tail + transfer_count)
	       % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data));
	  _tme_ncr53c9x_fifo_data_update(ncr53c9x);
	}
      }
    }
  }

  /* if we can clear a SCSI cycle callout marked as running, do it: */
  if (callout_clear
      && ((ncr53c9x->tme_ncr53c9x_callout_flags & TME_NCR53C9X_CALLOUT_SCSI_CYCLE)
	  == TME_NCR53C9X_CALLOUT_RUNNING(TME_NCR53C9X_CALLOUT_SCSI_CYCLE))) {
    ncr53c9x->tme_ncr53c9x_callout_flags &= ~TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
  }

  /* run the update function: */
  _tme_ncr53c9x_update(ncr53c9x);

  /* make any callouts: */
  _tme_ncr53c9x_callout(ncr53c9x);

  /* unlock the mutex: */
  tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);

  return (TME_OK);
}

/* the ncr53c9x timeout thread: */
static void
_tme_ncr53c9x_timeout_th(struct tme_ncr53c9x *ncr53c9x)
{

  /* lock our mutex: */
  tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

  /* loop forever: */
  for (;;) {

    /* update: */
    _tme_ncr53c9x_update(ncr53c9x);

    /* make any callouts: */
    _tme_ncr53c9x_callout(ncr53c9x);

    /* if there is a timeout pending: */
    if (ncr53c9x->tme_ncr53c9x_cmd_sequence != TME_NCR53C9X_CMD_SEQUENCE_DONE
	&& ncr53c9x->tme_ncr53c9x_cmd_sequence_timeout != TME_NCR53C9X_CMD_SEQUENCE_UNDEF) {

      /* sleep, but wake up if we get another timeout: */
      tme_cond_sleep_yield(&ncr53c9x->tme_ncr53c9x_timeout_cond,
			   &ncr53c9x->tme_ncr53c9x_mutex,
			   &ncr53c9x->tme_ncr53c9x_timeout_length);
    }
    
    /* otherwise, there is no timeout pending: */
    else {

      /* wait on the condition: */
      tme_cond_wait_yield(&ncr53c9x->tme_ncr53c9x_timeout_cond,
			  &ncr53c9x->tme_ncr53c9x_mutex);
    }
  }
  /* NOTREACHED */
}

/* the ncr53c9x bus signal handler: */
static int
_tme_ncr53c9x_signal(void *_ncr53c9x, 
		     unsigned int signal)
{
  struct tme_ncr53c9x *ncr53c9x;
  unsigned int level;

  /* recover our data structure: */
  ncr53c9x = (struct tme_ncr53c9x *) _ncr53c9x;

  /* take out the signal level: */
  level = signal & TME_BUS_SIGNAL_LEVEL_MASK;
  signal = TME_BUS_SIGNAL_WHICH(signal);

  /* lock the mutex: */
  tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

  /* dispatch on the generic bus signals: */
  switch (signal) {

  case TME_BUS_SIGNAL_RESET:
    if (level == TME_BUS_SIGNAL_LEVEL_ASSERTED) {
      _tme_ncr53c9x_reset(ncr53c9x, TME_NCR53C9X_RESET_DEVICE);
    }
    break;

  case TME_BUS_SIGNAL_DACK:
    if (level == TME_BUS_SIGNAL_LEVEL_ASSERTED) {
      ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
    }
    break;

  default:
    signal = TME_BUS_SIGNAL_IGNORE;
    break;
  }

  /* if we didn't ignore this bus signal: */
  if (signal != TME_BUS_SIGNAL_IGNORE) {

    /* run the update function: */
    _tme_ncr53c9x_update(ncr53c9x);

    /* make any callouts: */
    _tme_ncr53c9x_callout(ncr53c9x);
  }

  /* unlock the mutex: */
  tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);

  /* no faults: */
  return (TME_OK);
}

/* the NCR 53c9x bus cycle handler: */
static int
_tme_ncr53c9x_bus_cycle(void *_ncr53c9x,
			struct tme_bus_cycle *cycle_init)
{
  struct tme_ncr53c9x *ncr53c9x;
  unsigned int address;
  tme_uint8_t value;
  unsigned int fifo_head;
  unsigned int fifo_tail;

  /* recover our data structure: */
  ncr53c9x = (struct tme_ncr53c9x *) _ncr53c9x;

  /* get the address: */
  address = cycle_init->tme_bus_cycle_address;

  /* lock the mutex: */
  tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

  /* if this is a read: */
  if ((cycle_init->tme_bus_cycle_type & TME_BUS_CYCLE_READ) != 0) {

    /* dispatch on the register: */
    switch (address) {
      
      /* reads of these registers are simple: */
    case TME_NCR53C9X_REG_CTC_LSB:
    case TME_NCR53C9X_REG_CTC_MSB:
    case TME_NCR53C9X_REG_CFIS:
    case TME_NCR53C9X_REG_CONTROL1:
    case TME_NCR53C9X_REG_CONTROL3:
      value = ncr53c9x->tme_ncr53c9x_regs[address];
      break;

      /* "The data is latched until the Interrupt Status Register is
         read.  The phase bits will be latched only if latching is
         enabled in the Control Register 2, otherwise, it will
         indicate the current SCSI phase.  If command stacking is used,
         two interrupts might occur.  Reading this register will clear
         the status information for the first interrupt and update the
         Status Register for the second interrupt." */
    case TME_NCR53C9X_REG_STAT:

      /* get the STAT register value at the tail of the status FIFO: */
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_status_tail;
      value = ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_stat;

      /* if the SCSI bus phase bits are not latched: */
      if (!TME_NCR53C9X_HAS_CONTROL2_LSP(ncr53c9x)
	  || !(ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_CONTROL2] & TME_NCR53C9X_CONTROL2_LSP)) {

	/* put in the current SCSI bus phase: */
	value = _tme_ncr53c9x_scsi_phase_stat(ncr53c9x, value);
      }
      
      /* put in the CTZ bit: */
      value
	= ((value & ~TME_NCR53C9X_STAT_CTZ)
	   | (ncr53c9x->tme_ncr53c9x_regs[TME_NCR53C9X_REG_STAT]
	      & TME_NCR53C9X_STAT_CTZ));
      break;

      /* "The Internal State Register (ISREG) tracks the progress of a
         sequence-type command.  It is updated after each successful
         completion of an intermediate operation.  If an error occurs,
         the host can read this register to determine at where the
         command failed and take the necessary procedure for
         recovery.  Reading the Interrupt Status Register will clear
         this register.  ISREG Bit 3 SOF Synchronous Offset Flag The
         SOF is reset when the Synchronous Offset Register (SOFREG)
         has reached its maximum value.  Note: The SOF bit is active
         Low." */
    case TME_NCR53C9X_REG_IS:

      /* get the IS register value at the tail of the status FIFO: */
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_status_tail;
      value = ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_is;
      break;

      /* "The Interrupt Status Register (INSTREG) will indicate the
         reason for the interrupt.  This register is used with the
         Status Register (STATREG) and Internal Status Register
         (ISREG) to determine the reason for the interrupt.  Reading
         the INSTREG will clear all three registers." */
    case TME_NCR53C9X_REG_INST:
      TME_NCR53C9X_DEBUG_BP(read_inst);

      /* get the INST register value at the tail of the status FIFO: */
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_status_tail;
      value = ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_inst;

      /* if the status FIFO has another element in it: */
      if (fifo_tail != ncr53c9x->tme_ncr53c9x_fifo_status_head) {

	/* advance the tail of the status FIFO: */
	fifo_tail++;
	if (fifo_tail == TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_status)) {
	  fifo_tail = 0;
	}
	ncr53c9x->tme_ncr53c9x_fifo_status_tail = fifo_tail;
      }

      /* otherwise, the status FIFO is empty: */
      else {

	/* clear STAT, IS, and INST: */
	ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_stat = 0;
	ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_is = 0;
	ncr53c9x->tme_ncr53c9x_fifo_status[fifo_tail].tme_ncr53c9x_status_inst = 0;
      }

      /* call out an interrupt signal change: */
      ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_INT;
      break;
      
      /* a data FIFO read: */
    case TME_NCR53C9X_REG_FIFO:
      TME_NCR53C9X_DEBUG_BP(read_fifo);

      /* get the FIFO register value at the tail of the data FIFO: */
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_data_tail;
      value = ncr53c9x->tme_ncr53c9x_fifo_data[fifo_tail];

      /* if the data FIFO has another element in it: */
      if (fifo_tail != ncr53c9x->tme_ncr53c9x_fifo_data_head) {

	/* advance the tail of the data FIFO: */
	ncr53c9x->tme_ncr53c9x_fifo_data_tail = (fifo_tail + 1) % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data);
	_tme_ncr53c9x_fifo_data_update(ncr53c9x);
      }
      break;

      /* "Reading this register will return the command currently
         being executed (or the last command executed if there are no
         pending commands)." */
    case TME_NCR53C9X_REG_CMD:

      /* get the CMD register value at the tail of the command FIFO: */
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_cmd_tail;
      value = ncr53c9x->tme_ncr53c9x_fifo_cmd[fifo_tail];
      break;

      /* a read of Control register two depends on the variant: */
    case TME_NCR53C9X_REG_CONTROL2:
      switch (ncr53c9x->tme_ncr53c9x_variant) {
      default: assert(FALSE);

	/* the ESP100 doesn't have Control register two: */
      case TME_NCR53C9X_VARIANT_ESP100: value = 0xff; break;
      }
      break;

      /* an unknown register: */
    default: abort();
    }

    /* log the read: */
    _tme_ncr53c9x_debug_reg(ncr53c9x, address, TME_NCR53C9X_DEBUG_REG_READ, value);

    /* run the bus cycle: */
    tme_bus_cycle_xfer_reg(cycle_init,
			   &value,
			   TME_BUS8_LOG2);
  }

  /* otherwise, this is a write: */
  else {
    assert ((cycle_init->tme_bus_cycle_type & TME_BUS_CYCLE_WRITE) != 0);

    /* run the bus cycle: */
    tme_bus_cycle_xfer_reg(cycle_init,
			   &value,
			   TME_BUS8_LOG2);
    
    /* turn the address into a proper register number: */
    if ((TME_NCR53C9X_REGS_RW & TME_BIT(address)) != 0) {
      address = TME_NCR53C9X_REG_RW(address);
    }
    else {
      address = TME_NCR53C9X_REG_WO(address);
    }

    /* log the write: */
    _tme_ncr53c9x_debug_reg(ncr53c9x, address, TME_NCR53C9X_DEBUG_REG_WRITE, value);

    /* dispatch on the register: */
    switch (address) {

      /* writes of these registers are simple: */
    case TME_NCR53C9X_REG_STC_LSB:
    case TME_NCR53C9X_REG_STC_MSB:
    case TME_NCR53C9X_REG_SDID:
    case TME_NCR53C9X_REG_TIMEOUT:
    case TME_NCR53C9X_REG_SYNCH_PERIOD:
    case TME_NCR53C9X_REG_SYNCH_OFFSET:
    case TME_NCR53C9X_REG_CONTROL1:
    case TME_NCR53C9X_REG_CLOCK_FACTOR:
    case TME_NCR53C9X_REG_CONTROL3:
    case TME_NCR53C9X_REG_ALIGN:
      ncr53c9x->tme_ncr53c9x_regs[address] = value;
      break;

      /* a data FIFO write: */
    case TME_NCR53C9X_REG_FIFO:
      TME_NCR53C9X_DEBUG_BP(write_fifo);

      /* if the data FIFO is full: */
      fifo_head = ncr53c9x->tme_ncr53c9x_fifo_data_head;
      if (((fifo_head + 1) % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data))
	  == ncr53c9x->tme_ncr53c9x_fifo_data_tail) {

	/* set Illegal Operation in the STAT register value at the
           head of the status FIFO: */
	fifo_head = ncr53c9x->tme_ncr53c9x_fifo_status_head;
	ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_stat |= TME_NCR53C9X_STAT_IOE;
      }

      /* otherwise, the data FIFO is not full: */
      else {

	/* add this data to the data FIFO: */
	ncr53c9x->tme_ncr53c9x_fifo_data[fifo_head] = value;
	ncr53c9x->tme_ncr53c9x_fifo_data_head = (fifo_head + 1) % TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_data);
	_tme_ncr53c9x_fifo_data_update(ncr53c9x);

	/* since we may be running a non-DMA command that is waiting
	   for data to transfer, call out a SCSI cycle, because we now
	   have some data: */
	ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
      }
      break;

      /* "Commands to the device are issued by writing to this
         register.  This register is two deep which allows for command
         queuing.  The second command can be issued before the first
         one is completed.  The Reset command and the Stop DMA command
         are not queued and are executed immediately." */
    case TME_NCR53C9X_REG_CMD:

      /* if this is the Reset Device command: */
      if ((value & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_RESET) {

	/* "The Reset Device Command immediately stops any device
	   operation and resets all the functions of the device.  It
	   returns the device to the disconnected state and it also
	   generates a hard reset.  The Reset Device Command remains
	   on the top of the Command Register FIFO holding the device
	   in the reset state until the No Operation Command is
	   loaded. The No Operation command serves to enable the
	   Command Register."  */
	_tme_ncr53c9x_reset(ncr53c9x, TME_NCR53C9X_RESET_DEVICE | TME_NCR53C9X_RESET_FLAG_CMD);
      }

      /* if the active command is the Reset Device command, and this
	 is not a No Operation command, ignore it: */
      fifo_tail = ncr53c9x->tme_ncr53c9x_fifo_cmd_tail;
      if (((ncr53c9x->tme_ncr53c9x_fifo_cmd[fifo_tail] & TME_NCR53C9X_CMD_MASK)
	   == TME_NCR53C9X_CMD_RESET)
	  && ((value & TME_NCR53C9X_CMD_MASK) != TME_NCR53C9X_CMD_NOP)) {
	break;
      }

      /* if this is the DMA Stop command: */
      if ((value & TME_NCR53C9X_CMD_MASK) == TME_NCR53C9X_CMD_DMA_STOP) {

	/* XXX FIXME - we don't support any of the Target mode commands yet: */
	abort();
      }

      /* if the command FIFO is already full: */
      fifo_head = (ncr53c9x->tme_ncr53c9x_fifo_cmd_head + 1);
      if (fifo_head == TME_ARRAY_ELS(ncr53c9x->tme_ncr53c9x_fifo_cmd)) {
	fifo_head = 0;
      }
      if (fifo_head == fifo_tail) {

	/* set Illegal Operation in the STAT register value at the
           head of the status FIFO: */
	fifo_head = ncr53c9x->tme_ncr53c9x_fifo_status_head;
	ncr53c9x->tme_ncr53c9x_fifo_status[fifo_head].tme_ncr53c9x_status_stat |= TME_NCR53C9X_STAT_IOE;
      }

      /* otherwise, make this command pending: */
      else {
	TME_NCR53C9X_DEBUG_BP(write_cmd);

	/* add this command to the command FIFO: */
	ncr53c9x->tme_ncr53c9x_fifo_cmd[fifo_head] = value;
	ncr53c9x->tme_ncr53c9x_fifo_cmd_head = fifo_head;
      }
      break;

      /* a write of Control register two depends on the variant: */
    case TME_NCR53C9X_REG_CONTROL2:
      switch (ncr53c9x->tme_ncr53c9x_variant) {
      default: assert(FALSE);

	/* the ESP100 doesn't have Control register two: */
      case TME_NCR53C9X_VARIANT_ESP100: break;
      }
      break;

      /* an unknown register: */
    default: abort();
    }
  }
  
  /* run the update function: */
  _tme_ncr53c9x_update(ncr53c9x);

  /* make any callouts: */
  _tme_ncr53c9x_callout(ncr53c9x);

  /* unlock the mutex: */
  tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);

  /* no faults: */
  return (TME_OK);
}

/* the NCR 53c9x TLB filler: */
static int
_tme_ncr53c9x_tlb_fill(void *_ncr53c9x, 
		       struct tme_bus_tlb *tlb, 
		       tme_bus_addr_t address,
		       unsigned int cycles)
{
  struct tme_ncr53c9x *ncr53c9x;

  /* recover our data structure: */
  ncr53c9x = (struct tme_ncr53c9x *) _ncr53c9x;

  /* the address must be within range: */
  assert(address < TME_NCR53C9X_SIZ_REGS);

  /* initialize the TLB entry: */
  tme_bus_tlb_initialize(tlb);

  /* this TLB entry covers all registers: */
  tlb->tme_bus_tlb_addr_first = 0;
  tlb->tme_bus_tlb_addr_last = TME_NCR53C9X_SIZ_REGS - 1;

  /* allow reading and writing: */
  tlb->tme_bus_tlb_cycles_ok = TME_BUS_CYCLE_READ | TME_BUS_CYCLE_WRITE;

  /* our bus cycle handler: */
  tlb->tme_bus_tlb_cycle = _tme_ncr53c9x_bus_cycle;

  /* our bus cycle handler private data: */
  tlb->tme_bus_tlb_cycle_private = ncr53c9x;

  return (TME_OK);
}

/* this makes a new bus connection: */
static int
_tme_ncr53c9x_connection_make_bus(struct tme_connection *conn,
				  unsigned int state)
{
  struct tme_ncr53c9x *ncr53c9x;
  struct tme_bus_connection *conn_bus;
  int rc;

  /* recover our data structure: */
  ncr53c9x = conn->tme_connection_element->tme_element_private;

  /* call the bus device connection maker: */
  rc = tme_bus_device_connection_make(conn, state);

  /* if the full connection was successful, and we don't have a TLB
     set yet, allocate it: */
  if (rc == TME_OK
      && state == TME_CONNECTION_FULL
      && !ncr53c9x->tme_ncr53c9x_dma_tlb_added) {

    /* get our bus connection: */
    conn_bus
      = tme_memory_atomic_pointer_read(struct tme_bus_connection *,
				       ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection,
				       &ncr53c9x->tme_ncr53c9x_device.tme_bus_device_connection_rwlock);

    /* allocate the TLB set: */
    rc = tme_bus_device_tlb_set_add(&ncr53c9x->tme_ncr53c9x_device,
				    1, 
				    &ncr53c9x->tme_ncr53c9x_dma_tlb);
    assert (rc == TME_OK);
    ncr53c9x->tme_ncr53c9x_dma_tlb_added = TRUE;
  }

  return (rc);
}

/* this makes a new SCSI connection: */
static int
_tme_ncr53c9x_connection_make_scsi(struct tme_connection *conn,
				   unsigned int state)
{
  struct tme_ncr53c9x *ncr53c9x;
  struct tme_scsi_connection *conn_scsi;
  struct tme_scsi_connection *conn_scsi_other;

  /* recover our data structures: */
  ncr53c9x = conn->tme_connection_element->tme_element_private;
  conn_scsi = (struct tme_scsi_connection *) conn;
  conn_scsi_other = (struct tme_scsi_connection *) conn->tme_connection_other;

  /* both sides must be SCSI connections: */
  assert(conn->tme_connection_type == TME_CONNECTION_SCSI);
  assert(conn->tme_connection_other->tme_connection_type == TME_CONNECTION_SCSI);

  /* we're always set up to answer calls across the connection, so we
     only have to do work when the connection has gone full, namely
     taking the other side of the connection: */
  if (state == TME_CONNECTION_FULL) {

    /* lock our mutex: */
    tme_mutex_lock(&ncr53c9x->tme_ncr53c9x_mutex);

    /* save our connection: */
    ncr53c9x->tme_ncr53c9x_scsi_connection = conn_scsi_other;

    /* call out a cycle that asserts no signals and runs the
       wait-change action: */
    ncr53c9x->tme_ncr53c9x_active_scsi_events = TME_SCSI_EVENT_NONE;
    ncr53c9x->tme_ncr53c9x_out_scsi_events = TME_SCSI_EVENT_BUS_CHANGE;
    ncr53c9x->tme_ncr53c9x_out_scsi_actions = TME_SCSI_ACTION_NONE;
    ncr53c9x->tme_ncr53c9x_out_scsi_control = 0;
    ncr53c9x->tme_ncr53c9x_out_scsi_data = 0;
    ncr53c9x->tme_ncr53c9x_callout_flags |= TME_NCR53C9X_CALLOUT_SCSI_CYCLE;
    _tme_ncr53c9x_callout(ncr53c9x);

    /* unlock our mutex: */
    tme_mutex_unlock(&ncr53c9x->tme_ncr53c9x_mutex);
  }

  return (TME_OK);
}

/* this breaks a connection: */
static int
_tme_ncr53c9x_connection_break(struct tme_connection *conn, unsigned int state)
{
  abort();
}

/* this makes a new connection side for a NCR 53c9x: */
static int
_tme_ncr53c9x_connections_new(struct tme_element *element,
			      const char * const *args,
			      struct tme_connection **_conns,
			      char **_output)
{
  struct tme_ncr53c9x *ncr53c9x;
  struct tme_scsi_connection *conn_scsi;
  struct tme_connection *conn;
  int rc;

  /* recover our data structure: */
  ncr53c9x = (struct tme_ncr53c9x *) element->tme_element_private;

  /* make the generic bus device connection side: */
  rc = tme_bus_device_connections_new(element, args, _conns, _output);
  if (rc != TME_OK) {
    return (rc);
  }

  /* since we need to allocate a TLB set when we make our bus
     connection, make sure any generic bus device connection sides use
     our connection maker: */
  for (conn = *_conns;
       conn != NULL;
       conn = conn->tme_connection_next) {
    if ((conn->tme_connection_type
	 == TME_CONNECTION_BUS_GENERIC)
	&& (conn->tme_connection_make
	    == tme_bus_device_connection_make)) {
      conn->tme_connection_make
	= _tme_ncr53c9x_connection_make_bus;
    }
  }

  /* if we don't have a SCSI connection, make one: */
  if (ncr53c9x->tme_ncr53c9x_scsi_connection == NULL) {

    /* allocate the new SCSI connection: */
    conn_scsi = tme_new0(struct tme_scsi_connection, 1);
    conn = &conn_scsi->tme_scsi_connection;
    
    /* fill in the generic connection: */
    conn->tme_connection_next = *_conns;
    conn->tme_connection_type = TME_CONNECTION_SCSI;
    conn->tme_connection_score = tme_scsi_connection_score;
    conn->tme_connection_make = _tme_ncr53c9x_connection_make_scsi;
    conn->tme_connection_break = _tme_ncr53c9x_connection_break;

    /* fill in the SCSI connection: */
    conn_scsi->tme_scsi_connection_cycle = _tme_ncr53c9x_scsi_cycle;

    /* return the connection side possibility: */
    *_conns = conn;
  }

  /* done: */
  return (TME_OK);
}

/* the new NCR 53c9x function: */
TME_ELEMENT_NEW_DECL(tme_ic_ncr53c9x) {
  struct tme_ncr53c9x *ncr53c9x;
  unsigned int variant;
  int arg_i;
  int usage;

  /* check our arguments: */
  usage = 0;
  arg_i = 1;
  variant = TME_NCR53C9X_VARIANT_NULL;
  for (;;) {

    if (TME_ARG_IS(args[arg_i + 0], "variant")) {
      if (args[arg_i + 1] == NULL) {
	tme_output_append_error(_output,
				"%s, ",
				_("missing variant"));
	usage = TRUE;
	break;
      }
      else if (TME_ARG_IS(args[arg_i + 1], "esp100")) {
	variant = TME_NCR53C9X_VARIANT_ESP100;
      }
      else if (TME_ARG_IS(args[arg_i + 1], "esp100a")) {
	variant = TME_NCR53C9X_VARIANT_ESP100A;
      }
      else {
	tme_output_append_error(_output,
				"%s %s, ",
				_("bad variant"),
				args[arg_i + 1]);
	usage = TRUE;
	break;
      }
      arg_i += 2;
    }

    /* if we ran out of arguments: */
    else if (args[arg_i] == NULL) {

      break;
    }

    /* otherwise this is a bad argument: */
    else {
      tme_output_append_error(_output,
			      "%s %s, ",
			      args[arg_i],
			      _("unexpected"));
      usage = TRUE;
      break;
    }
  }

  if (variant == TME_NCR53C9X_VARIANT_NULL) {
    tme_output_append_error(_output,
			    "%s, ",
			    _("missing variant"));
    usage = TRUE;
  }

  if (usage) {
    tme_output_append_error(_output, 
			    "%s %s variant { esp100 | esp100a }",
			    _("usage:"),
			    args[0]);
    return (EINVAL);
  }

  /* start the NCR 53c9x structure: */
  ncr53c9x = tme_new0(struct tme_ncr53c9x, 1);
  ncr53c9x->tme_ncr53c9x_variant = variant;
  ncr53c9x->tme_ncr53c9x_element = element;
  tme_mutex_init(&ncr53c9x->tme_ncr53c9x_mutex);

  /* initialize our simple bus device descriptor: */
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_element = element;
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_tlb_fill = _tme_ncr53c9x_tlb_fill;
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_address_last = TME_NCR53C9X_SIZ_REGS - 1;
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_signal = _tme_ncr53c9x_signal;
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_lock = _tme_ncr53c9x_lock;
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_unlock = _tme_ncr53c9x_unlock;
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_tlb_hash = _tme_ncr53c9x_tlb_hash;
  ncr53c9x->tme_ncr53c9x_device.tme_bus_device_router = tme_bus_device_router_16eb;

  /* fill the element: */
  element->tme_element_private = ncr53c9x;
  element->tme_element_connections_new = _tme_ncr53c9x_connections_new;

  /* reset the device: */
  _tme_ncr53c9x_reset(ncr53c9x, TME_NCR53C9X_RESET_DEVICE);

  /* initialize the timeout thread condition: */
  tme_cond_init(&ncr53c9x->tme_ncr53c9x_timeout_cond);

  /* start the timeout thread: */
  tme_thread_create((tme_thread_t) _tme_ncr53c9x_timeout_th, ncr53c9x);

  return (TME_OK);
}

unix.superglobalmegacorp.com