blob: f37fbcafbcf25f54953ec815b77f23fa1c63ba1c [file] [log] [blame]
/* $Id: ncr53c9x.c,v 1.5 2010/06/05 15:50:40 fredette 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.5 2010/06/05 15:50:40 fredette 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);
}