summaryrefslogtreecommitdiff
path: root/scsi/adapters/scsi_53C94_hdw.c
diff options
context:
space:
mode:
authorThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
committerThomas Bushnell <thomas@gnu.org>1997-02-25 21:28:37 +0000
commitf07a4c844da9f0ecae5bbee1ab94be56505f26f7 (patch)
tree12b07c7e578fc1a5f53dbfde2632408491ff2a70 /scsi/adapters/scsi_53C94_hdw.c
Initial source
Diffstat (limited to 'scsi/adapters/scsi_53C94_hdw.c')
-rw-r--r--scsi/adapters/scsi_53C94_hdw.c2840
1 files changed, 2840 insertions, 0 deletions
diff --git a/scsi/adapters/scsi_53C94_hdw.c b/scsi/adapters/scsi_53C94_hdw.c
new file mode 100644
index 0000000..dad9b22
--- /dev/null
+++ b/scsi/adapters/scsi_53C94_hdw.c
@@ -0,0 +1,2840 @@
+/*
+ * Mach Operating System
+ * Copyright (c) 1993,1992,1991,1990,1989 Carnegie Mellon University
+ * All Rights Reserved.
+ *
+ * Permission to use, copy, modify and distribute this software and its
+ * documentation is hereby granted, provided that both the copyright
+ * notice and this permission notice appear in all copies of the
+ * software, derivative works or modified versions, and any portions
+ * thereof, and that both notices appear in supporting documentation.
+ *
+ * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS AS-IS
+ * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
+ *
+ * Carnegie Mellon requests users of this software to return to
+ *
+ * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
+ * School of Computer Science
+ * Carnegie Mellon University
+ * Pittsburgh PA 15213-3890
+ *
+ * any improvements or extensions that they make and grant Carnegie Mellon
+ * the rights to redistribute these changes.
+ */
+/*
+ * File: scsi_53C94_hdw.h
+ * Author: Alessandro Forin, Carnegie Mellon University
+ * Date: 9/90
+ *
+ * Bottom layer of the SCSI driver: chip-dependent functions
+ *
+ * This file contains the code that is specific to the NCR 53C94
+ * SCSI chip (Host Bus Adapter in SCSI parlance): probing, start
+ * operation, and interrupt routine.
+ */
+
+/*
+ * This layer works based on small simple 'scripts' that are installed
+ * at the start of the command and drive the chip to completion.
+ * The idea comes from the specs of the NCR 53C700 'script' processor.
+ *
+ * There are various reasons for this, mainly
+ * - Performance: identify the common (successful) path, and follow it;
+ * at interrupt time no code is needed to find the current status
+ * - Code size: it should be easy to compact common operations
+ * - Adaptability: the code skeleton should adapt to different chips without
+ * terrible complications.
+ * - Error handling: and it is easy to modify the actions performed
+ * by the scripts to cope with strange but well identified sequences
+ *
+ */
+
+#include <asc.h>
+#if NASC > 0
+#include <platforms.h>
+
+#ifdef DECSTATION
+typedef unsigned char asc_register_t;
+#define PAD(n) char n[3];
+#define mb()
+#ifdef MACH_KERNEL
+#define HAS_MAPPED_SCSI
+#endif
+#define ASC_PROBE_DYNAMICALLY FALSE /* established custom */
+#define DEBUG 1
+#define TRACE 1
+#endif
+
+#ifdef FLAMINGO
+typedef unsigned int asc_register_t;
+#define PAD(n) int n; /* sparse ! */
+#define mb() wbflush() /* memory barrier */
+#define ASC_PROBE_DYNAMICALLY TRUE
+#define DEBUG 1
+#define TRACE 1
+#endif
+
+#include <mach/std_types.h>
+#include <sys/types.h>
+#include <chips/busses.h>
+#include <scsi/compat_30.h>
+#include <machine/machspl.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi2.h>
+
+#include <scsi/adapters/scsi_53C94.h>
+#include <scsi/scsi_defs.h>
+#include <scsi/adapters/scsi_dma.h>
+
+#define private static
+
+#ifdef PAD
+typedef struct {
+ volatile asc_register_t asc_tc_lsb; /* rw: Transfer Counter LSB */
+ PAD(pad0)
+ volatile asc_register_t asc_tc_msb; /* rw: Transfer Counter MSB */
+ PAD(pad1)
+ volatile asc_register_t asc_fifo; /* rw: FIFO top */
+ PAD(pad2)
+ volatile asc_register_t asc_cmd; /* rw: Command */
+ PAD(pad3)
+ volatile asc_register_t asc_csr; /* r: Status */
+/*#define asc_dbus_id asc_csr /* w: Destination Bus ID */
+ PAD(pad4)
+ volatile asc_register_t asc_intr; /* r: Interrupt */
+/*#define asc_sel_timo asc_intr /* w: (re)select timeout */
+ PAD(pad5)
+ volatile asc_register_t asc_ss; /* r: Sequence Step */
+/*#define asc_syn_p asc_ss /* w: synchronous period */
+ PAD(pad6)
+ volatile asc_register_t asc_flags; /* r: FIFO flags + seq step */
+/*#define asc_syn_o asc_flags /* w: synchronous offset */
+ PAD(pad7)
+ volatile asc_register_t asc_cnfg1; /* rw: Configuration 1 */
+ PAD(pad8)
+ volatile asc_register_t asc_ccf; /* w: Clock Conv. Factor */
+ PAD(pad9)
+ volatile asc_register_t asc_test; /* w: Test Mode */
+ PAD(pad10)
+ volatile asc_register_t asc_cnfg2; /* rw: Configuration 2 */
+ PAD(pad11)
+ volatile asc_register_t asc_cnfg3; /* rw: Configuration 3 */
+ PAD(pad12)
+ volatile asc_register_t asc_rfb; /* w: Reserve FIFO byte */
+ PAD(pad13)
+} asc_padded_regmap_t;
+
+#else /* !PAD */
+
+typedef asc_regmap_t asc_padded_regmap_t;
+
+#endif /* !PAD */
+
+#define get_reg(r,x) ((unsigned char)((r)->x))
+
+#define fifo_count(r) ((r)->asc_flags & ASC_FLAGS_FIFO_CNT)
+#define get_fifo(r) get_reg(r,asc_fifo)
+
+boolean_t asc_probe_dynamically = ASC_PROBE_DYNAMICALLY;
+
+/*
+ * We might need to use some fields usually
+ * handled by the DMA engine, if asked to.
+ * These are "dma_ptr" and "hba_dep".
+ */
+#define has_oddb hba_dep[0]
+#define the_oddb hba_dep[1]
+
+/*
+ * A script has a three parts: a pre-condition, an action, and
+ * an optional command to the chip. The first triggers error
+ * handling if not satisfied and in our case it is a match
+ * of the expected and actual scsi-bus phases.
+ * The action part is just a function pointer, and the
+ * command is what the 53c90 should be told to do at the end
+ * of the action processing. This command is only issued and the
+ * script proceeds if the action routine returns TRUE.
+ * See asc_intr() for how and where this is all done.
+ */
+
+typedef struct script {
+ unsigned char condition; /* expected state at interrupt */
+ unsigned char command; /* command to the chip */
+ unsigned short flags; /* unused padding */
+ boolean_t (*action)(); /* extra operations */
+} *script_t;
+
+/* Matching on the condition value */
+#define ANY 0xff
+#define SCRIPT_MATCH(csr,ir,value) ((SCSI_PHASE(csr)==(value)) || \
+ (((value)==ANY) && \
+ ((ir)&(ASC_INT_DISC|ASC_INT_FC))))
+
+/* When no command is needed */
+#define SCRIPT_END -1
+
+/* forward decls of script actions */
+boolean_t
+ asc_end(), /* all come to an end */
+ asc_clean_fifo(), /* .. in preparation for status byte */
+ asc_get_status(), /* get status from target */
+ asc_put_status(), /* send status to initiator */
+ asc_dma_in(), /* get data from target via dma */
+ asc_dma_in_r(), /* get data from target via dma (restartable)*/
+ asc_dma_out(), /* send data to target via dma */
+ asc_dma_out_r(), /* send data to target via dma (restartable) */
+ asc_dosynch(), /* negotiate synch xfer */
+ asc_msg_in(), /* receive the disconenct message */
+ asc_disconnected(), /* target has disconnected */
+ asc_reconnect(); /* target reconnected */
+
+/* forward decls of error handlers */
+boolean_t
+ asc_err_generic(), /* generic handler */
+ asc_err_disconn(), /* target disconnects amidst */
+ gimmeabreak(); /* drop into the debugger */
+
+int asc_reset_scsibus();
+boolean_t asc_probe_target();
+private asc_wait();
+
+/*
+ * State descriptor for this layer. There is one such structure
+ * per (enabled) SCSI-53c90 interface
+ */
+struct asc_softc {
+ watchdog_t wd;
+ asc_padded_regmap_t *regs; /* 53c90 registers */
+
+ scsi_dma_ops_t *dma_ops; /* DMA operations and state */
+ opaque_t dma_state;
+
+ script_t script; /* what should happen next */
+ boolean_t (*error_handler)();/* what if something is wrong */
+ int in_count; /* amnt we expect to receive */
+ int out_count; /* amnt we are going to ship */
+
+ volatile char state;
+#define ASC_STATE_BUSY 0x01 /* selecting or currently connected */
+#define ASC_STATE_TARGET 0x04 /* currently selected as target */
+#define ASC_STATE_COLLISION 0x08 /* lost selection attempt */
+#define ASC_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */
+#define ASC_STATE_SPEC_DMA 0x20 /* special, 8 byte threshold dma */
+#define ASC_STATE_DO_RFB 0x40 /* DMA engine cannot handle odd bytes */
+
+ unsigned char ntargets; /* how many alive on this scsibus */
+ unsigned char done;
+ unsigned char extra_count; /* sleazy trick to spare an interrupt */
+ int dmacnt_at_end;
+
+ scsi_softc_t *sc; /* HBA-indep info */
+ target_info_t *active_target; /* the current one */
+
+ target_info_t *next_target; /* trying to seize bus */
+ queue_head_t waiting_targets;/* other targets competing for bus */
+
+ unsigned char ss_was; /* districate powered on/off devices */
+ unsigned char cmd_was;
+
+ unsigned char timeout; /* cache a couple numbers */
+ unsigned char ccf;
+ unsigned char clk;
+
+} asc_softc_data[NASC];
+
+typedef struct asc_softc *asc_softc_t;
+
+asc_softc_t asc_softc[NASC];
+
+/*
+ * Synch xfer parameters, and timing conversions
+ */
+int asc_min_period = 5; /* in CLKS/BYTE, e.g. 1 CLK = 40nsecs @25 Mhz */
+int asc_max_offset = 15; /* pure number */
+
+int asc_to_scsi_period(a,clk)
+{
+ /* Note: the SCSI unit is 4ns, hence
+ A_P * 1,000,000,000
+ ------------------- = S_P
+ C_Mhz * 4
+ */
+ return a * (250 / clk);
+
+}
+
+int scsi_period_to_asc(p,clk)
+{
+ register int ret;
+
+ ret = (p * clk) / 250;
+ if (ret < asc_min_period)
+ return asc_min_period;
+ if ((asc_to_scsi_period(ret,clk)) < p)
+ return ret + 1;
+ return ret;
+}
+
+#define readback(a) {register int foo; foo = a; mb();}
+
+#define u_min(a,b) (((a) < (b)) ? (a) : (b))
+
+/*
+ * Definition of the controller for the auto-configuration program.
+ */
+
+int asc_probe(), scsi_slave(), asc_go(), asc_intr();
+void scsi_attach();
+
+vm_offset_t asc_std[NASC] = { 0 };
+struct bus_device *asc_dinfo[NASC*8];
+struct bus_ctlr *asc_minfo[NASC];
+struct bus_driver asc_driver =
+ { asc_probe, scsi_slave, scsi_attach, asc_go, asc_std, "rz", asc_dinfo,
+ "asc", asc_minfo, BUS_INTR_B4_PROBE};
+
+
+int asc_clock_speed_in_mhz[NASC] = {25,25,25,25}; /* original 3max */
+
+asc_set_dmaops(unit, dmaops)
+ unsigned int unit;
+ scsi_dma_ops_t *dmaops;
+{
+ if (unit < NASC)
+ asc_std[unit] = (vm_offset_t)dmaops;
+}
+
+/*
+ * Scripts
+ */
+struct script
+asc_script_data_in[] = { /* started with SEL & DMA */
+ {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in},
+ {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+
+asc_script_data_out[] = { /* started with SEL & DMA */
+ {SCSI_PHASE_DATAO, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_out},
+ {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+
+asc_script_try_synch[] = {
+ {SCSI_PHASE_MSG_OUT, ASC_CMD_I_COMPLETE,0, asc_dosynch},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+
+asc_script_simple_cmd[] = {
+ {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+
+asc_script_disconnect[] = {
+ {ANY, ASC_CMD_ENABLE_SEL, 0, asc_disconnected},
+/**/ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_reconnect}
+},
+
+asc_script_restart_data_in[] = { /* starts after disconnect */
+ {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in_r},
+ {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+
+asc_script_restart_data_out[] = { /* starts after disconnect */
+ {SCSI_PHASE_DATAO, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_out_r},
+ {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+
+#if documentation
+/*
+ * This is what might happen during a read
+ * that disconnects
+ */
+asc_script_data_in_wd[] = { /* started with SEL & DMA & allow disconnect */
+ {SCSI_PHASE_MSG_IN, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_msg_in},
+ {ANY, ASC_CMD_ENABLE_SEL, 0, asc_disconnected},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_reconnect},
+ {SCSI_PHASE_DATAI, ASC_CMD_XFER_INFO|ASC_CMD_DMA, 0, asc_dma_in},
+ {SCSI_PHASE_STATUS, ASC_CMD_I_COMPLETE, 0, asc_clean_fifo},
+ {SCSI_PHASE_MSG_IN, ASC_CMD_MSG_ACPT, 0, asc_get_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+#endif
+
+/*
+ * Target mode scripts
+ */
+asc_script_t_data_in[] = {
+ {SCSI_PHASE_CMD, ASC_CMD_RCV_DATA|ASC_CMD_DMA, 0, asc_dma_in_r},
+ {SCSI_PHASE_DATAO, ASC_CMD_TERM, 0, asc_put_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+},
+
+asc_script_t_data_out[] = {
+ {SCSI_PHASE_CMD, ASC_CMD_SND_DATA|ASC_CMD_DMA, 0, asc_dma_out_r},
+ {SCSI_PHASE_DATAI, ASC_CMD_TERM, 0, asc_put_status},
+ {ANY, SCRIPT_END, 0, asc_end}
+};
+
+
+#ifdef DEBUG
+
+#define PRINT(x) if (scsi_debug) printf x
+
+asc_state(regs)
+ asc_padded_regmap_t *regs;
+{
+ register unsigned char ff,csr,ir,d0,d1,cmd;
+
+ if (regs == 0) {
+ if (asc_softc[0])
+ regs = asc_softc[0]->regs;
+ else
+ regs = (asc_padded_regmap_t*)0xbf400000;
+ }
+ ff = get_reg(regs,asc_flags);
+ csr = get_reg(regs,asc_csr);
+/* ir = get_reg(regs,asc_intr); nope, clears interrupt */
+ d0 = get_reg(regs,asc_tc_lsb);
+ d1 = get_reg(regs,asc_tc_msb);
+ cmd = get_reg(regs,asc_cmd);
+ printf("dma %x ff %x csr %x cmd %x\n",
+ (d1 << 8) | d0, ff, csr, cmd);
+ return 0;
+}
+
+asc_target_state(tgt)
+ target_info_t *tgt;
+{
+ if (tgt == 0)
+ tgt = asc_softc[0]->active_target;
+ if (tgt == 0)
+ return 0;
+ db_printf("@x%x: fl %x dma %X+%x cmd %x@%X id %x per %x off %x ior %X ret %X\n",
+ tgt,
+ tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset, tgt->cur_cmd,
+ tgt->cmd_ptr, (long)tgt->target_id,
+ (long)tgt->sync_period, (long)tgt->sync_offset,
+ tgt->ior, (long)tgt->done);
+ if (tgt->flags & TGT_DISCONNECTED){
+ script_t spt;
+
+ spt = tgt->transient_state.script;
+ db_printf("disconnected at ");
+ db_printsym(spt,1);
+ db_printf(": %x %x ", spt->condition, spt->command);
+ db_printsym(spt->action,1);
+ db_printf(", ");
+ db_printsym(tgt->transient_state.handler, 1);
+ db_printf("\n");
+ }
+
+ return 0;
+}
+
+asc_all_targets(unit)
+{
+ int i;
+ target_info_t *tgt;
+ for (i = 0; i < 8; i++) {
+ tgt = asc_softc[unit]->sc->target[i];
+ if (tgt)
+ asc_target_state(tgt);
+ }
+}
+
+asc_script_state(unit)
+{
+ script_t spt = asc_softc[unit]->script;
+
+ if (spt == 0) return 0;
+ db_printsym(spt,1);
+ db_printf(": %x %x ", spt->condition, spt->command);
+ db_printsym(spt->action,1);
+ db_printf(", ");
+ db_printsym(asc_softc[unit]->error_handler, 1);
+ return 0;
+}
+
+#define TRMAX 200
+int tr[TRMAX+3];
+int trpt, trpthi;
+#define TR(x) tr[trpt++] = x
+#define TRWRAP trpthi = trpt; trpt = 0;
+#define TRCHECK if (trpt > TRMAX) {TRWRAP}
+
+
+#ifdef TRACE
+
+#define LOGSIZE 256
+int asc_logpt;
+char asc_log[LOGSIZE];
+
+#define MAXLOG_VALUE 0x42
+struct {
+ char *name;
+ unsigned int count;
+} logtbl[MAXLOG_VALUE];
+
+/* private */ LOG(e,f)
+ char *f;
+{
+ asc_log[asc_logpt++] = (e);
+ if (asc_logpt == LOGSIZE) asc_logpt = 0;
+ if ((e) < MAXLOG_VALUE) {
+ logtbl[(e)].name = (f);
+ logtbl[(e)].count++;
+ }
+}
+
+asc_print_log(skip)
+ int skip;
+{
+ register int i, j;
+ register unsigned char c;
+
+ for (i = 0, j = asc_logpt; i < LOGSIZE; i++) {
+ c = asc_log[j];
+ if (++j == LOGSIZE) j = 0;
+ if (skip-- > 0)
+ continue;
+ if (c < MAXLOG_VALUE)
+ db_printf(" %s", logtbl[c].name);
+ else
+ db_printf("-x%x", c & 0x7f);
+ }
+}
+
+asc_print_stat()
+{
+ register int i;
+ register char *p;
+ for (i = 0; i < MAXLOG_VALUE; i++) {
+ if (p = logtbl[i].name)
+ printf("%d %s\n", logtbl[i].count, p);
+ }
+}
+
+#else /*TRACE*/
+#define LOG(e,f)
+#define LOGSIZE
+#endif /*TRACE*/
+
+#else /*DEBUG*/
+#define PRINT(x)
+#define LOG(e,f)
+#define LOGSIZE
+#define TR(x)
+#define TRCHECK
+#define TRWRAP
+
+#endif /*DEBUG*/
+
+
+/*
+ * Probe/Slave/Attach functions
+ */
+
+/*
+ * Probe routine:
+ * Should find out (a) if the controller is
+ * present and (b) which/where slaves are present.
+ *
+ * Implementation:
+ * Send a test-unit-ready to each possible target on the bus
+ * except of course ourselves.
+ */
+asc_probe(reg, ui)
+ vm_offset_t reg;
+ struct bus_ctlr *ui;
+{
+ int unit = ui->unit;
+ asc_softc_t asc = &asc_softc_data[unit];
+ int target_id;
+ scsi_softc_t *sc;
+ register asc_padded_regmap_t *regs;
+ spl_t s;
+ boolean_t did_banner = FALSE;
+
+ /*
+ * We are only called if the right board is there,
+ * but make sure anyways..
+ */
+ if (check_memory(reg, 0))
+ return 0;
+
+#if defined(HAS_MAPPED_SCSI)
+ /* Mappable version side */
+ ASC_probe(reg, ui);
+#endif
+
+ /*
+ * Initialize hw descriptor, cache some pointers
+ */
+ asc_softc[unit] = asc;
+ asc->regs = (asc_padded_regmap_t *) (reg);
+
+ if ((asc->dma_ops = (scsi_dma_ops_t *)asc_std[unit]) == 0)
+ /* use same as unit 0 if undefined */
+ asc->dma_ops = (scsi_dma_ops_t *)asc_std[0];
+ {
+ int dma_bsize = 16; /* bits, preferred */
+ boolean_t do_rfb = FALSE;
+
+ asc->dma_state = (*asc->dma_ops->init)(unit, reg, &dma_bsize, &do_rfb);
+ if (dma_bsize > 16)
+ asc->state |= ASC_STATE_SPEC_DMA;
+ if (do_rfb)
+ asc->state |= ASC_STATE_DO_RFB;
+ }
+
+ queue_init(&asc->waiting_targets);
+
+ asc->clk = asc_clock_speed_in_mhz[unit];
+ asc->ccf = mhz_to_ccf(asc->clk); /* see .h file */
+ asc->timeout = asc_timeout_250(asc->clk,asc->ccf);
+
+ sc = scsi_master_alloc(unit, asc);
+ asc->sc = sc;
+
+ sc->go = asc_go;
+ sc->watchdog = scsi_watchdog;
+ sc->probe = asc_probe_target;
+ asc->wd.reset = asc_reset_scsibus;
+
+#ifdef MACH_KERNEL
+ sc->max_dma_data = -1;
+#else
+ sc->max_dma_data = scsi_per_target_virtual;
+#endif
+
+ regs = asc->regs;
+
+ /*
+ * Our SCSI id on the bus.
+ * The user can set this via the prom on 3maxen/pmaxen.
+ * If this changes it is easy to fix: make a default that
+ * can be changed as boot arg.
+ */
+ {
+ register unsigned char my_id;
+
+ my_id = scsi_initiator_id[unit] & 0x7;
+ if (my_id != 7)
+ regs->asc_cnfg1 = my_id; mb();
+ }
+
+ /*
+ * Reset chip, fully. Note that interrupts are already enabled.
+ */
+ s = splbio();
+ asc_reset(asc, TRUE, asc->state & ASC_STATE_SPEC_DMA);
+
+ sc->initiator_id = regs->asc_cnfg1 & ASC_CNFG1_MY_BUS_ID;
+ printf("%s%d: SCSI id %d", ui->name, unit, sc->initiator_id);
+
+ {
+ register target_info_t *tgt;
+
+ tgt = scsi_slave_alloc(sc->masterno, sc->initiator_id, asc);
+ (*asc->dma_ops->new_target)(asc->dma_state, tgt);
+ sccpu_new_initiator(tgt, tgt);
+ }
+
+ if (asc_probe_dynamically)
+ printf("%s", ", will probe targets on demand");
+ else {
+
+ /*
+ * For all possible targets, see if there is one and allocate
+ * a descriptor for it if it is there.
+ */
+ for (target_id = 0; target_id < 8; target_id++) {
+ register unsigned char csr, ss, ir, ff;
+ register scsi_status_byte_t status;
+
+ /* except of course ourselves */
+ if (target_id == sc->initiator_id)
+ continue;
+
+ regs->asc_cmd = ASC_CMD_FLUSH; /* empty fifo */
+ mb();
+ delay(2);
+
+ regs->asc_dbus_id = target_id; mb();
+ regs->asc_sel_timo = asc->timeout; mb();
+
+ /*
+ * See if the unit is ready.
+ * XXX SHOULD inquiry LUN 0 instead !!!
+ */
+ regs->asc_fifo = SCSI_CMD_TEST_UNIT_READY; mb();
+ regs->asc_fifo = 0; mb();
+ regs->asc_fifo = 0; mb();
+ regs->asc_fifo = 0; mb();
+ regs->asc_fifo = 0; mb();
+ regs->asc_fifo = 0; mb();
+
+ /* select and send it */
+ regs->asc_cmd = ASC_CMD_SEL; mb();
+
+ /* wait for the chip to complete, or timeout */
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+ ss = get_reg(regs,asc_ss);
+ ir = get_reg(regs,asc_intr);
+
+ /* empty fifo, there is garbage in it if timeout */
+ regs->asc_cmd = ASC_CMD_FLUSH; mb();
+ delay(2);
+
+ /*
+ * Check if the select timed out
+ */
+ if ((ASC_SS(ss) == 0) && (ir == ASC_INT_DISC))
+ /* noone out there */
+ continue;
+
+ if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS) {
+ printf( " %s%d%s", "ignoring target at ", target_id,
+ " cuz it acts weirdo");
+ continue;
+ }
+
+ printf(",%s%d", did_banner++ ? " " : " target(s) at ",
+ target_id);
+
+ regs->asc_cmd = ASC_CMD_I_COMPLETE;
+ wbflush();
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr); /* ack intr */
+ mb();
+
+ status.bits = get_fifo(regs); /* empty fifo */
+ mb();
+ ff = get_fifo(regs);
+
+ if (status.st.scsi_status_code != SCSI_ST_GOOD)
+ scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0);
+
+ regs->asc_cmd = ASC_CMD_MSG_ACPT; mb();
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr); /* ack intr */
+ mb();
+
+ /*
+ * Found a target
+ */
+ asc->ntargets++;
+ {
+ register target_info_t *tgt;
+ tgt = scsi_slave_alloc(sc->masterno, target_id, asc);
+
+ (*asc->dma_ops->new_target)(asc->dma_state, tgt);
+ }
+ }
+ } /* asc_probe_dynamically */
+
+ regs->asc_cmd = ASC_CMD_ENABLE_SEL; mb();
+
+ printf(".\n");
+
+ splx(s);
+ return 1;
+}
+
+boolean_t
+asc_probe_target(tgt, ior)
+ target_info_t *tgt;
+ io_req_t ior;
+{
+ asc_softc_t asc = asc_softc[tgt->masterno];
+ boolean_t newlywed;
+
+ newlywed = (tgt->cmd_ptr == 0);
+ if (newlywed) {
+ (*asc->dma_ops->new_target)(asc->dma_state, tgt);
+ }
+
+ if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN)
+ return FALSE;
+
+ asc->ntargets++;
+ tgt->flags = TGT_ALIVE;
+ return TRUE;
+}
+
+private asc_wait(regs, until, complain)
+ asc_padded_regmap_t *regs;
+{
+ int timeo = 1000000;
+ while ((regs->asc_csr & until) == 0) {
+ mb();
+ delay(1);
+ if (!timeo--) {
+ if (complain)
+ printf("asc_wait TIMEO with x%x\n", get_reg(regs,asc_csr));
+ break;
+ }
+ }
+ return get_reg(regs,asc_csr);
+}
+
+asc_reset(asc, quick, special_dma)
+ asc_softc_t asc;
+{
+ char my_id;
+ int ccf;
+ asc_padded_regmap_t *regs;
+
+ regs = asc->regs;
+
+ /* preserve our ID for now */
+ my_id = (regs->asc_cnfg1 & ASC_CNFG1_MY_BUS_ID);
+
+ /*
+ * Reset chip and wait till done
+ */
+ regs->asc_cmd = ASC_CMD_RESET;
+ wbflush(); delay(25);
+
+ /* spec says this is needed after reset */
+ regs->asc_cmd = ASC_CMD_NOP;
+ wbflush(); delay(25);
+
+ /*
+ * Set up various chip parameters
+ */
+ regs->asc_ccf = asc->ccf;
+ wbflush();
+ delay(25);
+ regs->asc_sel_timo = asc->timeout; mb();
+ /* restore our ID */
+ regs->asc_cnfg1 = my_id | ASC_CNFG1_P_CHECK; mb();
+ regs->asc_cnfg2 = ASC_CNFG2_SCSI2;
+ mb();
+ regs->asc_cnfg3 = special_dma ? (ASC_CNFG3_T8|ASC_CNFG3_ALT_DMA) : 0;
+ mb();
+ /* zero anything else */
+ ASC_TC_PUT(regs, 0); mb();
+ regs->asc_syn_p = asc_min_period; mb();
+ regs->asc_syn_o = 0; mb(); /* asynch for now */
+
+ regs->asc_cmd = ASC_CMD_ENABLE_SEL; mb();
+
+ if (quick) return;
+
+ /*
+ * reset the scsi bus, the interrupt routine does the rest
+ * or you can call asc_bus_reset().
+ */
+ regs->asc_cmd = ASC_CMD_BUS_RESET; mb();
+}
+
+
+/*
+ * Operational functions
+ */
+
+/*
+ * Start a SCSI command on a target
+ */
+asc_go(tgt, cmd_count, in_count, cmd_only)
+ target_info_t *tgt;
+ boolean_t cmd_only;
+{
+ asc_softc_t asc;
+ register spl_t s;
+ boolean_t disconn;
+ script_t scp;
+ boolean_t (*handler)();
+
+ LOG(1,"go");
+
+ asc = (asc_softc_t)tgt->hw_state;
+
+ tgt->transient_state.cmd_count = cmd_count; /* keep it here */
+ tgt->transient_state.out_count = 0; /* default */
+
+ (*asc->dma_ops->map)(asc->dma_state, tgt);
+
+ disconn = BGET(scsi_might_disconnect,tgt->masterno,tgt->target_id);
+ disconn = disconn && (asc->ntargets > 1);
+ disconn |= BGET(scsi_should_disconnect,tgt->masterno,tgt->target_id);
+
+ /*
+ * Setup target state
+ */
+ tgt->done = SCSI_RET_IN_PROGRESS;
+
+ handler = (disconn) ? asc_err_disconn : asc_err_generic;
+
+ switch (tgt->cur_cmd) {
+ case SCSI_CMD_READ:
+ case SCSI_CMD_LONG_READ:
+ LOG(2,"readop");
+ scp = asc_script_data_in;
+ break;
+ case SCSI_CMD_WRITE:
+ case SCSI_CMD_LONG_WRITE:
+ LOG(0x18,"writeop");
+ scp = asc_script_data_out;
+ break;
+ case SCSI_CMD_INQUIRY:
+ /* This is likely the first thing out:
+ do the synch neg if so */
+ if (!cmd_only && ((tgt->flags&TGT_DID_SYNCH)==0)) {
+ scp = asc_script_try_synch;
+ tgt->flags |= TGT_TRY_SYNCH;
+ disconn = FALSE;
+ break;
+ }
+ case SCSI_CMD_REQUEST_SENSE:
+ case SCSI_CMD_MODE_SENSE:
+ case SCSI_CMD_RECEIVE_DIAG_RESULTS:
+ case SCSI_CMD_READ_CAPACITY:
+ case SCSI_CMD_READ_BLOCK_LIMITS:
+ case SCSI_CMD_READ_TOC:
+ case SCSI_CMD_READ_SUBCH:
+ case SCSI_CMD_READ_HEADER:
+ case 0xc4: /* despised: SCSI_CMD_DEC_PLAYBACK_STATUS */
+ case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */
+ case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */
+ scp = asc_script_data_in;
+ LOG(0x1c,"cmdop");
+ LOG(0x80+tgt->cur_cmd,0);
+ break;
+ case SCSI_CMD_MODE_SELECT:
+ case SCSI_CMD_REASSIGN_BLOCKS:
+ case SCSI_CMD_FORMAT_UNIT:
+ case 0xc9: /* vendor-spec: SCSI_CMD_DEC_PLAYBACK_CONTROL */
+ tgt->transient_state.cmd_count = sizeof_scsi_command(tgt->cur_cmd);
+ tgt->transient_state.out_count =
+ cmd_count - tgt->transient_state.cmd_count;
+ scp = asc_script_data_out;
+ LOG(0x1c,"cmdop");
+ LOG(0x80+tgt->cur_cmd,0);
+ break;
+ case SCSI_CMD_TEST_UNIT_READY:
+ /*
+ * Do the synch negotiation here, unless prohibited
+ * or done already
+ */
+ if (tgt->flags & TGT_DID_SYNCH) {
+ scp = asc_script_simple_cmd;
+ } else {
+ scp = asc_script_try_synch;
+ tgt->flags |= TGT_TRY_SYNCH;
+ cmd_only = FALSE;
+ disconn = FALSE;
+ }
+ LOG(0x1c,"cmdop");
+ LOG(0x80+tgt->cur_cmd,0);
+ break;
+ default:
+ LOG(0x1c,"cmdop");
+ LOG(0x80+tgt->cur_cmd,0);
+ scp = asc_script_simple_cmd;
+ }
+
+ tgt->transient_state.script = scp;
+ tgt->transient_state.handler = handler;
+ tgt->transient_state.identify = (cmd_only) ? 0xff :
+ (disconn ? SCSI_IDENTIFY|SCSI_IFY_ENABLE_DISCONNECT :
+ SCSI_IDENTIFY);
+
+ if (in_count)
+ tgt->transient_state.in_count =
+ (in_count < tgt->block_size) ? tgt->block_size : in_count;
+ else
+ tgt->transient_state.in_count = 0;
+
+ /*
+ * See if another target is currently selected on
+ * this SCSI bus, e.g. lock the asc structure.
+ * Note that it is the strategy routine's job
+ * to serialize ops on the same target as appropriate.
+ * XXX here and everywhere, locks!
+ */
+ /*
+ * Protection viz reconnections makes it tricky.
+ */
+ s = splbio();
+
+ if (asc->wd.nactive++ == 0)
+ asc->wd.watchdog_state = SCSI_WD_ACTIVE;
+
+ if (asc->state & ASC_STATE_BUSY) {
+ /*
+ * Queue up this target, note that this takes care
+ * of proper FIFO scheduling of the scsi-bus.
+ */
+ LOG(3,"enqueue");
+ enqueue_tail(&asc->waiting_targets, (queue_entry_t) tgt);
+ } else {
+ /*
+ * It is down to at most two contenders now,
+ * we will treat reconnections same as selections
+ * and let the scsi-bus arbitration process decide.
+ */
+ asc->state |= ASC_STATE_BUSY;
+ asc->next_target = tgt;
+ asc_attempt_selection(asc);
+ /*
+ * Note that we might still lose arbitration..
+ */
+ }
+ splx(s);
+}
+
+asc_attempt_selection(asc)
+ asc_softc_t asc;
+{
+ target_info_t *tgt;
+ asc_padded_regmap_t *regs;
+ register int out_count;
+
+ regs = asc->regs;
+ tgt = asc->next_target;
+
+ LOG(4,"select");
+ LOG(0x80+tgt->target_id,0);
+
+ /*
+ * We own the bus now.. unless we lose arbitration
+ */
+ asc->active_target = tgt;
+
+ /* Try to avoid reselect collisions */
+ if ((regs->asc_csr & (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) ==
+ (ASC_CSR_INT|SCSI_PHASE_MSG_IN))
+ return;
+
+ /*
+ * Cleanup the FIFO
+ */
+ regs->asc_cmd = ASC_CMD_FLUSH;
+ readback(regs->asc_cmd);
+ /*
+ * This value is not from spec, I have seen it failing
+ * without this delay and with logging disabled. That had
+ * about 42 extra instructions @25Mhz.
+ */
+ delay(2);/* XXX time & move later */
+
+
+ /*
+ * Init bus state variables
+ */
+ asc->script = tgt->transient_state.script;
+ asc->error_handler = tgt->transient_state.handler;
+ asc->done = SCSI_RET_IN_PROGRESS;
+
+ asc->out_count = 0;
+ asc->in_count = 0;
+ asc->extra_count = 0;
+
+ /*
+ * Start the chip going
+ */
+ out_count = (*asc->dma_ops->start_cmd)(asc->dma_state, tgt);
+ if (tgt->transient_state.identify != 0xff) {
+ regs->asc_fifo = tgt->transient_state.identify | tgt->lun;
+ mb();
+ }
+ ASC_TC_PUT(regs, out_count);
+ readback(regs->asc_cmd);
+
+ regs->asc_dbus_id = tgt->target_id;
+ readback(regs->asc_dbus_id);
+
+ regs->asc_sel_timo = asc->timeout;
+ readback(regs->asc_sel_timo);
+
+ regs->asc_syn_p = tgt->sync_period;
+ readback(regs->asc_syn_p);
+
+ regs->asc_syn_o = tgt->sync_offset;
+ readback(regs->asc_syn_o);
+
+ /* ugly little help for compiler */
+#define command out_count
+ if (tgt->flags & TGT_DID_SYNCH) {
+ command = (tgt->transient_state.identify == 0xff) ?
+ ASC_CMD_SEL | ASC_CMD_DMA :
+ ASC_CMD_SEL_ATN | ASC_CMD_DMA; /*preferred*/
+ } else if (tgt->flags & TGT_TRY_SYNCH)
+ command = ASC_CMD_SEL_ATN_STOP;
+ else
+ command = ASC_CMD_SEL | ASC_CMD_DMA;
+
+ /* Try to avoid reselect collisions */
+ if ((regs->asc_csr & (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) !=
+ (ASC_CSR_INT|SCSI_PHASE_MSG_IN)) {
+ register int tmp;
+
+ regs->asc_cmd = command; mb();
+ /*
+ * Very nasty but infrequent problem here. We got/will get
+ * reconnected but the chip did not interrupt. The watchdog would
+ * fix it allright, but it stops the machine before it expires!
+ * Too bad we cannot just look at the interrupt register, sigh.
+ */
+ tmp = get_reg(regs,asc_cmd);
+ if ((tmp != command) && (tmp == (ASC_CMD_NOP|ASC_CMD_DMA))) {
+ if ((regs->asc_csr & ASC_CSR_INT) == 0) {
+ delay(250); /* increase if in trouble */
+
+ if (get_reg(regs,asc_csr) == SCSI_PHASE_MSG_IN) {
+ /* ok, take the risk of reading the ir */
+ tmp = get_reg(regs,asc_intr); mb();
+ if (tmp & ASC_INT_RESEL) {
+ (void) asc_reconnect(asc, get_reg(regs,asc_csr), tmp);
+ asc_wait(regs, ASC_CSR_INT, 1);
+ tmp = get_reg(regs,asc_intr); mb();
+ regs->asc_cmd = ASC_CMD_MSG_ACPT;
+ readback(regs->asc_cmd);
+ } else /* does not happen, but who knows.. */
+ asc_reset(asc,FALSE,asc->state & ASC_STATE_SPEC_DMA);
+ }
+ }
+ }
+ }
+#undef command
+}
+
+/*
+ * Interrupt routine
+ * Take interrupts from the chip
+ *
+ * Implementation:
+ * Move along the current command's script if
+ * all is well, invoke error handler if not.
+ */
+asc_intr(unit, spllevel)
+ spl_t spllevel;
+{
+ register asc_softc_t asc;
+ register script_t scp;
+ register int ir, csr;
+ register asc_padded_regmap_t *regs;
+#if defined(HAS_MAPPED_SCSI)
+ extern boolean_t rz_use_mapped_interface;
+
+ if (rz_use_mapped_interface)
+ return ASC_intr(unit,spllevel);
+#endif
+
+ asc = asc_softc[unit];
+
+ LOG(5,"\n\tintr");
+ /* collect ephemeral information */
+ regs = asc->regs;
+ csr = get_reg(regs,asc_csr); mb();
+ asc->ss_was = get_reg(regs,asc_ss); mb();
+ asc->cmd_was = get_reg(regs,asc_cmd); mb();
+
+ /* drop spurious interrupts */
+ if ((csr & ASC_CSR_INT) == 0)
+ return;
+
+ ir = get_reg(regs,asc_intr); /* this re-latches CSR (and SSTEP) */
+ mb();
+
+TR(csr);TR(ir);TR(get_reg(regs,asc_cmd));TRCHECK
+
+ /* this must be done within 250msec of disconnect */
+ if (ir & ASC_INT_DISC) {
+ regs->asc_cmd = ASC_CMD_ENABLE_SEL;
+ readback(regs->asc_cmd);
+ }
+
+ if (ir & ASC_INT_RESET)
+ return asc_bus_reset(asc);
+
+ /* we got an interrupt allright */
+ if (asc->active_target)
+ asc->wd.watchdog_state = SCSI_WD_ACTIVE;
+
+#if mips
+ splx(spllevel); /* drop priority */
+#endif
+
+ if ((asc->state & ASC_STATE_TARGET) ||
+ (ir & (ASC_INT_SEL|ASC_INT_SEL_ATN)))
+ return asc_target_intr(asc, csr, ir);
+
+ /*
+ * In attempt_selection() we could not check the asc_intr
+ * register to see if a reselection was in progress [else
+ * we would cancel the interrupt, and it would not be safe
+ * anyways]. So we gave the select command even if sometimes
+ * the chip might have been reconnected already. What
+ * happens then is that we get an illegal command interrupt,
+ * which is why the second clause. Sorry, I'd do it better
+ * if I knew of a better way.
+ */
+ if ((ir & ASC_INT_RESEL) ||
+ ((ir & ASC_INT_ILL) && (regs->asc_cmd & ASC_CMD_SEL_ATN)))
+ return asc_reconnect(asc, csr, ir);
+
+ /*
+ * Check for various errors
+ */
+ if ((csr & (ASC_CSR_GE|ASC_CSR_PE)) || (ir & ASC_INT_ILL)) {
+ char *msg;
+printf("{E%x,%x}", csr, ir);
+ if (csr & ASC_CSR_GE)
+ return;/* sit and prey? */
+
+ if (csr & ASC_CSR_PE)
+ msg = "SCSI bus parity error";
+ if (ir & ASC_INT_ILL)
+ msg = "Chip sez Illegal Command";
+ /* all we can do is to throw a reset on the bus */
+ printf( "asc%d: %s%s", asc - asc_softc_data, msg,
+ ", attempting recovery.\n");
+ asc_reset(asc, FALSE, asc->state & ASC_STATE_SPEC_DMA);
+ return;
+ }
+
+ if ((scp = asc->script) == 0) /* sanity */
+ return;
+
+ LOG(6,"match");
+ if (SCRIPT_MATCH(csr,ir,scp->condition)) {
+ /*
+ * Perform the appropriate operation,
+ * then proceed
+ */
+ if ((*scp->action)(asc, csr, ir)) {
+ asc->script = scp + 1;
+ regs->asc_cmd = scp->command; mb();
+ }
+ } else
+ (void) (*asc->error_handler)(asc, csr, ir);
+}
+
+asc_target_intr(asc, csr, ir)
+ register asc_softc_t asc;
+
+{
+ register script_t scp;
+
+ LOG(0x1e,"tmode");
+
+ if ((asc->state & ASC_STATE_TARGET) == 0) {
+
+ /*
+ * We just got selected
+ */
+ asc->state |= ASC_STATE_TARGET;
+
+ /*
+ * See if this selection collided with our selection attempt
+ */
+ if (asc->state & ASC_STATE_BUSY)
+ asc->state |= ASC_STATE_COLLISION;
+ asc->state |= ASC_STATE_BUSY;
+
+ return asc_selected(asc, csr, ir);
+
+ }
+ /* We must be executing a script here */
+ scp = asc->script;
+ assert(scp != 0);
+
+ LOG(6,"match");
+ if (SCRIPT_MATCH(csr,ir,scp->condition)) {
+ /*
+ * Perform the appropriate operation,
+ * then proceed
+ */
+ if ((*scp->action)(asc, csr, ir)) {
+ asc->script = scp + 1;
+ asc->regs->asc_cmd = scp->command; mb();
+ }
+ } else
+ (void) (*asc->error_handler)(asc, csr, ir);
+
+}
+
+/*
+ * All the many little things that the interrupt
+ * routine might switch to
+ */
+boolean_t
+asc_clean_fifo(asc, csr, ir)
+ register asc_softc_t asc;
+
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register char ff;
+
+ ASC_TC_GET(regs,asc->dmacnt_at_end);
+
+ ASC_TC_PUT(regs,0); /* stop dma engine */
+ readback(regs->asc_cmd);
+
+ LOG(7,"clean_fifo");
+
+ while (fifo_count(regs)) {
+ ff = get_fifo(regs);
+ mb();
+ }
+ return TRUE;
+}
+
+boolean_t
+asc_end(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ register target_info_t *tgt;
+ register io_req_t ior;
+
+ LOG(8,"end");
+
+ asc->state &= ~ASC_STATE_TARGET;
+ asc->regs->asc_syn_p = 0; mb();
+ asc->regs->asc_syn_o = 0; mb();
+
+ tgt = asc->active_target;
+ if ((tgt->done = asc->done) == SCSI_RET_IN_PROGRESS)
+ tgt->done = SCSI_RET_SUCCESS;
+
+ asc->script = 0;
+
+ if (asc->wd.nactive-- == 1)
+ asc->wd.watchdog_state = SCSI_WD_INACTIVE;
+
+ asc_release_bus(asc);
+
+ if (ior = tgt->ior) {
+ /*
+ * WARNING: the above might have scheduled the
+ * DMA engine off to someone else. Keep it in
+ * mind in the following code
+ */
+ (*asc->dma_ops->end_cmd)(asc->dma_state, tgt, ior);
+
+ LOG(0xA,"ops->restart");
+ (*tgt->dev_ops->restart)( tgt, TRUE);
+ }
+
+ return FALSE;
+}
+
+boolean_t
+asc_release_bus(asc)
+ register asc_softc_t asc;
+{
+ boolean_t ret = TRUE;
+
+ LOG(9,"release");
+ if (asc->state & ASC_STATE_COLLISION) {
+
+ LOG(0xB,"collided");
+ asc->state &= ~ASC_STATE_COLLISION;
+ asc_attempt_selection(asc);
+
+ } else if (queue_empty(&asc->waiting_targets)) {
+
+ asc->state &= ~ASC_STATE_BUSY;
+ asc->active_target = 0;
+ asc->script = 0;
+ ret = FALSE;
+
+ } else {
+
+ LOG(0xC,"dequeue");
+ asc->next_target = (target_info_t *)
+ dequeue_head(&asc->waiting_targets);
+ asc_attempt_selection(asc);
+ }
+ return ret;
+}
+
+boolean_t
+asc_get_status(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register scsi2_status_byte_t status;
+ int len;
+ boolean_t ret;
+ io_req_t ior;
+ register target_info_t *tgt = asc->active_target;
+
+ LOG(0xD,"get_status");
+TRWRAP
+
+ asc->state &= ~ASC_STATE_DMA_IN;
+
+ if (asc->state & ASC_STATE_DO_RFB) {
+ tgt->transient_state.has_oddb = FALSE;
+ regs->asc_cnfg2 = ASC_CNFG2_SCSI2;
+ }
+
+ /*
+ * Get the last two bytes in FIFO
+ */
+ while (fifo_count(regs) > 2) {
+ status.bits = get_fifo(regs); mb();
+ }
+
+ status.bits = get_fifo(regs); mb();
+
+ if (status.st.scsi_status_code != SCSI_ST_GOOD) {
+ scsi_error(asc->active_target, SCSI_ERR_STATUS, status.bits, 0);
+ asc->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ?
+ SCSI_RET_RETRY : SCSI_RET_NEED_SENSE;
+ } else
+ asc->done = SCSI_RET_SUCCESS;
+
+ status.bits = get_fifo(regs); /* just pop the command_complete */
+ mb();
+
+ /* if reading, move the last piece of data in main memory */
+ if (len = asc->in_count) {
+ register int count;
+
+ count = asc->dmacnt_at_end;
+ if (count) {
+#if 0
+ this is incorrect and besides..
+ tgt->ior->io_residual = count;
+#endif
+ len -= count;
+ }
+ regs->asc_cmd = asc->script->command;
+ readback(regs->asc_cmd);
+
+ ret = FALSE;
+ } else
+ ret = TRUE;
+
+ asc->dmacnt_at_end = 0;
+ (*asc->dma_ops->end_xfer)(asc->dma_state, tgt, len);
+ if (!ret)
+ asc->script++;
+ return ret;
+}
+
+boolean_t
+asc_put_status(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register scsi2_status_byte_t status;
+ register target_info_t *tgt = asc->active_target;
+ int len;
+
+ LOG(0x21,"put_status");
+
+ asc->state &= ~ASC_STATE_DMA_IN;
+
+ if (len = asc->in_count) {
+ register int count;
+
+ ASC_TC_GET(regs,count); mb();
+ if (count)
+ len -= count;
+ }
+ (*asc->dma_ops->end_xfer)(asc->dma_state, tgt, len);
+
+/* status.st.scsi_status_code = SCSI_ST_GOOD; */
+ regs->asc_fifo = 0; mb();
+ regs->asc_fifo = SCSI_COMMAND_COMPLETE; mb();
+
+ return TRUE;
+}
+
+
+boolean_t
+asc_dma_in(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ register target_info_t *tgt;
+ register asc_padded_regmap_t *regs = asc->regs;
+ register int count;
+ unsigned char ff = get_reg(regs,asc_flags); mb();
+
+ LOG(0xE,"dma_in");
+ tgt = asc->active_target;
+
+ /*
+ * This seems to be needed on certain rudimentary targets
+ * (such as the DEC TK50 tape) which apparently only pick
+ * up 6 initial bytes: when you add the initial IDENTIFY
+ * you are left with 1 pending byte, which is left in the
+ * FIFO and would otherwise show up atop the data we are
+ * really requesting.
+ *
+ * This is only speculation, though, based on the fact the
+ * sequence step value of 3 out of select means the target
+ * changed phase too quick and some bytes have not been
+ * xferred (see NCR manuals). Counter to this theory goes
+ * the fact that the extra byte that shows up is not alwyas
+ * zero, and appears to be pretty random.
+ * Note that asc_flags say there is one byte in the FIFO
+ * even in the ok case, but the sstep value is the right one.
+ * Note finally that this might all be a sync/async issue:
+ * I have only checked the ok case on synch disks so far.
+ *
+ * Indeed it seems to be an asynch issue: exabytes do it too.
+ */
+ if ((tgt->sync_offset == 0) && ((ff & ASC_FLAGS_SEQ_STEP) != 0x80)) {
+ regs->asc_cmd = ASC_CMD_NOP;
+ wbflush();
+ PRINT(("[tgt %d: %x while %d]", tgt->target_id, ff, tgt->cur_cmd));
+ while ((ff & ASC_FLAGS_FIFO_CNT) != 0) {
+ ff = get_fifo(regs); mb();
+ ff = get_reg(regs,asc_flags); mb();
+ }
+ }
+
+ asc->state |= ASC_STATE_DMA_IN;
+
+ count = (*asc->dma_ops->start_datain)(asc->dma_state, tgt);
+ ASC_TC_PUT(regs, count);
+ readback(regs->asc_cmd);
+
+ if ((asc->in_count = count) == tgt->transient_state.in_count)
+ return TRUE;
+ regs->asc_cmd = asc->script->command; mb();
+ asc->script = asc_script_restart_data_in;
+ return FALSE;
+}
+
+asc_dma_in_r(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ register target_info_t *tgt;
+ register asc_padded_regmap_t *regs = asc->regs;
+ register int count;
+ boolean_t advance_script = TRUE;
+
+
+ LOG(0x1f,"dma_in_r");
+ tgt = asc->active_target;
+
+ asc->state |= ASC_STATE_DMA_IN;
+
+ if (asc->in_count == 0) {
+ /*
+ * Got nothing yet, we just reconnected.
+ */
+ register int avail;
+
+ /*
+ * NOTE: if we have to handle the RFB (obb),
+ * the odd byte has been installed at reconnect
+ * time, before switching to data-in phase. Now
+ * we are already in data-in phase.
+ * It is up to the DMA engine to trim the dma_ptr
+ * down one byte.
+ */
+
+ count = (*asc->dma_ops->restart_datain_1)
+ (asc->dma_state, tgt);
+
+ /* common case of 8k-or-less read ? */
+ advance_script = (tgt->transient_state.in_count == count);
+
+ } else {
+
+ /*
+ * We received some data.
+ */
+ register int offset, xferred;
+
+ /*
+ * Problem: sometimes we get a 'spurious' interrupt
+ * right after a reconnect. It goes like disconnect,
+ * reconnect, dma_in_r, here but DMA is still rolling.
+ * Since there is no good reason we got here to begin with
+ * we just check for the case and dismiss it: we should
+ * get another interrupt when the TC goes to zero or the
+ * target disconnects.
+ */
+ ASC_TC_GET(regs,xferred); mb();
+ if (xferred != 0)
+ return FALSE;
+
+ xferred = asc->in_count - xferred;
+ assert(xferred > 0);
+
+ tgt->transient_state.in_count -= xferred;
+ assert(tgt->transient_state.in_count > 0);
+
+ /*
+ * There should NOT be any obb issues here,
+ * we would have no control anyways.
+ */
+ count = (*asc->dma_ops->restart_datain_2)
+ (asc->dma_state, tgt, xferred);
+
+ asc->in_count = count;
+ ASC_TC_PUT(regs, count);
+ readback(regs->asc_cmd);
+ regs->asc_cmd = asc->script->command; mb();
+
+ (*asc->dma_ops->restart_datain_3)
+ (asc->dma_state, tgt);
+
+ /* last chunk ? */
+ if (count == tgt->transient_state.in_count)
+ asc->script++;
+
+ return FALSE;
+ }
+
+ asc->in_count = count;
+ ASC_TC_PUT(regs, count);
+ readback(regs->asc_cmd);
+
+ if (!advance_script) {
+ regs->asc_cmd = asc->script->command;
+ readback(regs->asc_cmd);
+ }
+ return advance_script;
+}
+
+
+/* send data to target. Only called to start the xfer */
+
+boolean_t
+asc_dma_out(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register int reload_count;
+ register target_info_t *tgt;
+ int command;
+
+ LOG(0xF,"dma_out");
+
+ ASC_TC_GET(regs, reload_count); mb();
+ asc->extra_count = fifo_count(regs);
+ reload_count += asc->extra_count;
+ ASC_TC_PUT(regs, reload_count);
+ asc->state &= ~ASC_STATE_DMA_IN;
+ readback(regs->asc_cmd);
+
+ tgt = asc->active_target;
+
+ command = asc->script->command;
+
+ if (reload_count == 0) reload_count = ASC_TC_MAX;
+ asc->out_count = reload_count;
+
+ if (reload_count >= tgt->transient_state.out_count)
+ asc->script++;
+ else
+ asc->script = asc_script_restart_data_out;
+
+ if ((*asc->dma_ops->start_dataout)
+ (asc->dma_state, tgt, (volatile unsigned *)&regs->asc_cmd,
+ command, &asc->extra_count)) {
+ regs->asc_cmd = command;
+ readback(regs->asc_cmd);
+ }
+
+ return FALSE;
+}
+
+/* send data to target. Called in two different ways:
+ (a) to restart a big transfer and
+ (b) after reconnection
+ */
+boolean_t
+asc_dma_out_r(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register target_info_t *tgt;
+ boolean_t advance_script = TRUE;
+ int count;
+
+
+ LOG(0x20,"dma_out_r");
+
+ tgt = asc->active_target;
+ asc->state &= ~ASC_STATE_DMA_IN;
+
+ if (asc->out_count == 0) {
+ /*
+ * Nothing committed: we just got reconnected
+ */
+ count = (*asc->dma_ops->restart_dataout_1)
+ (asc->dma_state, tgt);
+
+ /* is this the last chunk ? */
+ advance_script = (tgt->transient_state.out_count == count);
+ } else {
+ /*
+ * We sent some data.
+ */
+ register int offset, xferred;
+
+ ASC_TC_GET(regs,count); mb();
+
+ /* see comment above */
+ if (count) {
+ return FALSE;
+ }
+
+ count += fifo_count(regs);
+ count -= asc->extra_count;
+ xferred = asc->out_count - count;
+ assert(xferred > 0);
+
+ tgt->transient_state.out_count -= xferred;
+ assert(tgt->transient_state.out_count > 0);
+
+ count = (*asc->dma_ops->restart_dataout_2)
+ (asc->dma_state, tgt, xferred);
+
+ /* last chunk ? */
+ if (tgt->transient_state.out_count == count)
+ goto quickie;
+
+ asc->out_count = count;
+
+ asc->extra_count = (*asc->dma_ops->restart_dataout_3)
+ (asc->dma_state, tgt,
+ (volatile unsigned *)&regs->asc_fifo);
+ ASC_TC_PUT(regs, count);
+ readback(regs->asc_cmd);
+ regs->asc_cmd = asc->script->command; mb();
+
+ (*asc->dma_ops->restart_dataout_4)(asc->dma_state, tgt);
+
+ return FALSE;
+ }
+
+quickie:
+ asc->extra_count = (*asc->dma_ops->restart_dataout_3)
+ (asc->dma_state, tgt,
+ (volatile unsigned *)&regs->asc_fifo);
+
+ asc->out_count = count;
+
+ ASC_TC_PUT(regs, count);
+ readback(regs->asc_cmd);
+
+ if (!advance_script) {
+ regs->asc_cmd = asc->script->command;
+ }
+ return advance_script;
+}
+
+boolean_t
+asc_dosynch(asc, csr, ir)
+ register asc_softc_t asc;
+ register unsigned char csr, ir;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register unsigned char c;
+ int i, per, offs;
+ register target_info_t *tgt;
+
+ /*
+ * Phase is MSG_OUT here.
+ * Try to go synchronous, unless prohibited
+ */
+ tgt = asc->active_target;
+ regs->asc_cmd = ASC_CMD_FLUSH;
+ readback(regs->asc_cmd);
+ delay(1);
+
+ per = asc_min_period;
+ if (BGET(scsi_no_synchronous_xfer,asc->sc->masterno,tgt->target_id))
+ offs = 0;
+ else
+ offs = asc_max_offset;
+
+ tgt->flags |= TGT_DID_SYNCH; /* only one chance */
+ tgt->flags &= ~TGT_TRY_SYNCH;
+
+ regs->asc_fifo = SCSI_EXTENDED_MESSAGE; mb();
+ regs->asc_fifo = 3; mb();
+ regs->asc_fifo = SCSI_SYNC_XFER_REQUEST; mb();
+ regs->asc_fifo = asc_to_scsi_period(asc_min_period,asc->clk); mb();
+ regs->asc_fifo = offs; mb();
+ regs->asc_cmd = ASC_CMD_XFER_INFO;
+ readback(regs->asc_cmd);
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr); mb();
+
+ /* some targets might be slow to move to msg-in */
+
+ if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN) {
+
+ /* wait for direction bit to flip */
+ csr = asc_wait(regs, SCSI_IO, 0);
+ ir = get_reg(regs,asc_intr); mb();
+ /* Some ugly targets go stright to command phase.
+ "You could at least say goodbye" */
+ if (SCSI_PHASE(csr) == SCSI_PHASE_CMD)
+ goto did_not;
+ if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN)
+ gimmeabreak();
+ }
+
+ regs->asc_cmd = ASC_CMD_XFER_INFO; mb();
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr); mb();
+
+ /* some targets do not even take all the bytes out */
+ while (fifo_count(regs) > 0) {
+ c = get_fifo(regs); /* see what it says */
+ mb();
+ }
+
+ if (c == SCSI_MESSAGE_REJECT) {
+did_not:
+ printf(" did not like SYNCH xfer ");
+
+ /* Tk50s get in trouble with ATN, sigh. */
+ regs->asc_cmd = ASC_CMD_CLR_ATN;
+ readback(regs->asc_cmd);
+
+ goto cmd;
+ }
+
+ /*
+ * Receive the rest of the message
+ */
+ regs->asc_cmd = ASC_CMD_MSG_ACPT; mb();
+ asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr); mb();
+
+ if (c != SCSI_EXTENDED_MESSAGE)
+ gimmeabreak();
+
+ regs->asc_cmd = ASC_CMD_XFER_INFO; mb();
+ asc_wait(regs, ASC_CSR_INT, 1);
+ c = get_reg(regs,asc_intr); mb();
+ if (get_fifo(regs) != 3)
+ panic("asc_dosynch");
+
+ for (i = 0; i < 3; i++) {
+ regs->asc_cmd = ASC_CMD_MSG_ACPT; mb();
+ asc_wait(regs, ASC_CSR_INT, 1);
+ c = get_reg(regs,asc_intr); mb();
+
+ regs->asc_cmd = ASC_CMD_XFER_INFO; mb();
+ asc_wait(regs, ASC_CSR_INT, 1);
+ c = get_reg(regs,asc_intr);/*ack*/ mb();
+ c = get_fifo(regs); mb();
+
+ if (i == 1) tgt->sync_period = scsi_period_to_asc(c,asc->clk);
+ if (i == 2) tgt->sync_offset = c;
+ }
+
+cmd:
+ regs->asc_cmd = ASC_CMD_MSG_ACPT; mb();
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+ c = get_reg(regs,asc_intr); mb();
+
+ /* Might have to wait a bit longer for slow targets */
+ for (c = 0; SCSI_PHASE(get_reg(regs,asc_csr)) == SCSI_PHASE_MSG_IN; c++) {
+ mb();
+ delay(2);
+ if (c & 0x80) break; /* waited too long */
+ }
+ csr = get_reg(regs,asc_csr); mb();
+
+ /* phase should normally be command here */
+ if (SCSI_PHASE(csr) == SCSI_PHASE_CMD) {
+ register char *cmd = tgt->cmd_ptr;
+
+ /* test unit ready or inquiry */
+ for (i = 0; i < sizeof(scsi_command_group_0); i++) {
+ regs->asc_fifo = *cmd++; mb();
+ }
+ ASC_TC_PUT(regs,0xff); mb();
+ regs->asc_cmd = ASC_CMD_XFER_PAD; /*0x18*/ mb();
+
+ if (tgt->cur_cmd == SCSI_CMD_INQUIRY) {
+ tgt->transient_state.script = asc_script_data_in;
+ asc->script = tgt->transient_state.script;
+ regs->asc_syn_p = tgt->sync_period;
+ regs->asc_syn_o = tgt->sync_offset; mb();
+ return FALSE;
+ }
+
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr);/*ack*/ mb();
+ }
+
+ if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS)
+ csr = asc_wait(regs, SCSI_IO, 1); /* direction flip */
+
+status:
+ if (SCSI_PHASE(csr) != SCSI_PHASE_STATUS)
+ gimmeabreak();
+
+ return TRUE;
+}
+
+/* The other side of the coin.. */
+asc_odsynch(asc, initiator)
+ register asc_softc_t asc;
+ target_info_t *initiator;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register unsigned char c;
+ int len, per, offs;
+
+ /*
+ * Phase is MSG_OUT, we are the target and we have control.
+ * Any IDENTIFY messages have been handled already.
+ */
+ initiator->flags |= TGT_DID_SYNCH;
+ initiator->flags &= ~TGT_TRY_SYNCH;
+
+ /*
+ * We only understand synch negotiations
+ */
+ c = get_fifo(regs); mb();
+ if (c != SCSI_EXTENDED_MESSAGE) goto bad;
+
+ /*
+ * This is not in the specs, but apparently the chip knows
+ * enough about scsi to receive the length automatically.
+ * So there were two bytes in the fifo at function call.
+ */
+ len = get_fifo(regs); mb();
+ if (len != 3) goto bad;
+ while (len) {
+ if (fifo_count(regs) == 0) {
+ regs->asc_cmd = ASC_CMD_RCV_MSG;
+ readback(regs->asc_cmd);
+ asc_wait(regs, ASC_CSR_INT, 1);
+ c = get_reg(regs,asc_intr); mb();
+ }
+ c = get_fifo(regs); mb();
+ if (len == 1) offs = c;
+ if (len == 2) per = c;
+ len--;
+ }
+
+ /*
+ * Adjust the proposed parameters
+ */
+ c = scsi_period_to_asc(per,asc->clk);
+ initiator->sync_period = c;
+ per = asc_to_scsi_period(c,asc->clk);
+
+ if (offs > asc_max_offset) offs = asc_max_offset;
+ initiator->sync_offset = offs;
+
+ /*
+ * Tell him what the deal is
+ */
+ regs->asc_fifo = SCSI_EXTENDED_MESSAGE; mb();
+ regs->asc_fifo = 3; mb();
+ regs->asc_fifo = SCSI_SYNC_XFER_REQUEST; mb();
+ regs->asc_fifo = per; mb();
+ regs->asc_fifo = offs; mb();
+ regs->asc_cmd = ASC_CMD_SND_MSG;
+ readback(regs->asc_cmd);
+ asc_wait(regs, ASC_CSR_INT, 1);
+ c = get_reg(regs,asc_intr); mb();
+
+ /*
+ * Exit conditions: fifo empty, phase undefined but non-command
+ */
+ return;
+
+ /*
+ * Something wrong, reject the message
+ */
+bad:
+ while (fifo_count(regs)) {
+ c = get_fifo(regs); mb();
+ }
+ regs->asc_fifo = SCSI_MESSAGE_REJECT; mb();
+ regs->asc_cmd = ASC_CMD_SND_MSG;
+ readback(regs->asc_cmd);
+ asc_wait(regs, ASC_CSR_INT, 1);
+ c = get_reg(regs,asc_intr); mb();
+}
+
+/*
+ * The bus was reset
+ */
+asc_bus_reset(asc)
+ register asc_softc_t asc;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+
+ LOG(0x1d,"bus_reset");
+
+ /*
+ * Clear bus descriptor
+ */
+ asc->script = 0;
+ asc->error_handler = 0;
+ asc->active_target = 0;
+ asc->next_target = 0;
+ asc->state &= ASC_STATE_SPEC_DMA | ASC_STATE_DO_RFB;
+ queue_init(&asc->waiting_targets);
+ asc->wd.nactive = 0;
+ asc_reset(asc, TRUE, asc->state & ASC_STATE_SPEC_DMA);
+
+ printf("asc: (%d) bus reset ", ++asc->wd.reset_count);
+
+ /* some targets take long to reset */
+ delay( scsi_delay_after_reset +
+ asc->sc->initiator_id * 500000); /* if multiple initiators */
+
+ if (asc->sc == 0) /* sanity */
+ return;
+
+ scsi_bus_was_reset(asc->sc);
+}
+
+/*
+ * Disconnect/reconnect mode ops
+ */
+
+/* get the message in via dma */
+boolean_t
+asc_msg_in(asc, csr, ir)
+ register asc_softc_t asc;
+ register unsigned char csr, ir;
+{
+ register target_info_t *tgt;
+ register asc_padded_regmap_t *regs = asc->regs;
+ unsigned char ff;
+
+ LOG(0x10,"msg_in");
+ /* must clean FIFO as in asc_dma_in, sigh */
+ while (fifo_count(regs) != 0) {
+ ff = get_fifo(regs); mb();
+ }
+
+ (void) (*asc->dma_ops->start_msgin)(asc->dma_state, asc->active_target);
+ /* We only really expect two bytes, at tgt->cmd_ptr */
+ ASC_TC_PUT(regs, sizeof(scsi_command_group_0));
+ readback(regs->asc_cmd);
+
+ return TRUE;
+}
+
+/* check the message is indeed a DISCONNECT */
+boolean_t
+asc_disconnect(asc, csr, ir)
+ register asc_softc_t asc;
+ register unsigned char csr, ir;
+
+{
+ register char *msgs, ff;
+ register target_info_t *tgt;
+ asc_padded_regmap_t *regs;
+
+ tgt = asc->active_target;
+
+ (*asc->dma_ops->end_msgin)(asc->dma_state, tgt);
+
+ /*
+ * Do not do this. It is most likely a reconnection
+ * message that sits there already by the time we
+ * get here. The chip is smart enough to only dma
+ * the bytes that correctly came in as msg_in proper,
+ * the identify and selection bytes are not dma-ed.
+ * Yes, sometimes the hardware does the right thing.
+ */
+#if 0
+ /* First check message got out of the fifo */
+ regs = asc->regs;
+ while (fifo_count(regs) != 0) {
+ *msgs++ = get_fifo(regs);
+ }
+#endif
+ msgs = tgt->cmd_ptr;
+
+ /* A SDP message preceeds it in non-completed READs */
+ if ((msgs[0] == SCSI_DISCONNECT) || /* completed */
+ ((msgs[0] == SCSI_SAVE_DATA_POINTER) && /* non complete */
+ (msgs[1] == SCSI_DISCONNECT))) {
+ /* that's the ok case */
+ } else
+ printf("[tgt %d bad SDP: x%x]",
+ tgt->target_id, *((unsigned short *)msgs));
+
+ return TRUE;
+}
+
+/* save all relevant data, free the BUS */
+boolean_t
+asc_disconnected(asc, csr, ir)
+ register asc_softc_t asc;
+ register unsigned char csr, ir;
+
+{
+ register target_info_t *tgt;
+
+ LOG(0x11,"disconnected");
+ asc_disconnect(asc,csr,ir);
+
+ tgt = asc->active_target;
+ tgt->flags |= TGT_DISCONNECTED;
+ tgt->transient_state.handler = asc->error_handler;
+ /* anything else was saved in asc_err_disconn() */
+
+ PRINT(("{D%d}", tgt->target_id));
+
+ asc_release_bus(asc);
+
+ return FALSE;
+}
+
+int asc_restore_ptr = 1;
+
+/* get reconnect message out of fifo, restore BUS */
+boolean_t
+asc_reconnect(asc, csr, ir)
+ register asc_softc_t asc;
+ register unsigned char csr, ir;
+
+{
+ register target_info_t *tgt;
+ asc_padded_regmap_t *regs;
+ unsigned int id;
+
+ LOG(0x12,"reconnect");
+ /*
+ * See if this reconnection collided with a selection attempt
+ */
+ if (asc->state & ASC_STATE_BUSY)
+ asc->state |= ASC_STATE_COLLISION;
+
+ asc->state |= ASC_STATE_BUSY;
+
+ /* find tgt: first byte in fifo is (tgt_id|our_id) */
+ regs = asc->regs;
+ while (fifo_count(regs) > 2) /* sanity */ {
+ id = get_fifo(regs); mb();
+ }
+ if (fifo_count(regs) != 2)
+ gimmeabreak();
+
+ id = get_fifo(regs); /* must decode this now */
+ mb();
+ id &= ~(1 << asc->sc->initiator_id);
+ {
+ register int i;
+ for (i = 0; id > 1; i++)
+ id >>= 1;
+ id = i;
+ }
+
+ tgt = asc->sc->target[id];
+ if (tgt == 0) panic("asc_reconnect");
+
+ /* synch things*/
+ regs->asc_syn_p = tgt->sync_period;
+ regs->asc_syn_o = tgt->sync_offset;
+ readback(regs->asc_syn_o);
+
+ /* Get IDENTIFY message */
+ {
+ register int i = get_fifo(regs);
+ if ((i & SCSI_IDENTIFY) == 0)
+ gimmeabreak();
+ i &= 0x7;
+ /* If not LUN 0 see which target it is */
+ if (i) {
+ target_info_t *tgt1;
+ for (tgt1 = tgt->next_lun;
+ tgt1 && tgt1 != tgt;
+ tgt1 = tgt1->next_lun)
+ if (tgt1->lun == i) {
+ tgt = tgt1;
+ break;
+ }
+ }
+ }
+
+ if (asc->state & ASC_STATE_DO_RFB) {
+ if (tgt->transient_state.has_oddb) {
+ if (tgt->sync_period) {
+ regs->asc_cnfg2 = ASC_CNFG2_SCSI2 | ASC_CNFG2_RFB;
+ wbflush();
+ regs->asc_rfb = tgt->transient_state.the_oddb;
+ } else {
+ regs->asc_fifo = tgt->transient_state.the_oddb;
+ }
+ tgt->transient_state.has_oddb = FALSE;
+ } else {
+ regs->asc_cnfg2 = ASC_CNFG2_SCSI2;
+ }
+ wbflush();
+ }
+
+ PRINT(("{R%d}", id));
+ if (asc->state & ASC_STATE_COLLISION)
+ PRINT(("[B %d-%d]", asc->active_target->target_id, id));
+
+ LOG(0x80+id,0);
+
+ asc->active_target = tgt;
+ tgt->flags &= ~TGT_DISCONNECTED;
+
+ asc->script = tgt->transient_state.script;
+ asc->error_handler = tgt->transient_state.handler;
+ asc->in_count = 0;
+ asc->out_count = 0;
+
+ regs->asc_cmd = ASC_CMD_MSG_ACPT;
+ readback(regs->asc_cmd);
+
+ /* What if there is a RESTORE_PTR msgin ? */
+ if (asc_restore_ptr) {
+more_msgin:
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+
+ if (SCSI_PHASE(csr) == SCSI_PHASE_MSG_IN) {
+ /* ack intr */
+ id = get_reg(regs,asc_intr); mb();
+
+ /* Ok, get msg */
+ regs->asc_cmd = ASC_CMD_XFER_INFO;
+ readback(regs->asc_cmd);
+ /* wait for xfer done */
+ csr = asc_wait(regs, ASC_CSR_INT, 1);
+
+ /* look at what we got */
+ id = get_fifo(regs);
+
+ if (id != SCSI_RESTORE_POINTERS)
+ printf("asc%d: uhu msg %x\n", asc->sc->masterno, id);
+ /* ack intr */
+ id = get_reg(regs,asc_intr); mb();
+
+ /* move on */
+ regs->asc_cmd = ASC_CMD_MSG_ACPT;
+ readback(regs->asc_cmd);
+ goto more_msgin;
+ }
+ }
+
+ return FALSE;
+}
+
+
+/* We have been selected as target */
+
+boolean_t
+asc_selected(asc, csr, ir)
+ register asc_softc_t asc;
+ register unsigned csr, ir;
+{
+ register asc_padded_regmap_t *regs = asc->regs;
+ register unsigned char id;
+ target_info_t *self, *initiator;
+ unsigned int len;
+
+ /*
+ * Find initiator's id: the head of the fifo is (init_id|our_id)
+ */
+
+ id = get_fifo(regs); /* must decode this now */
+ mb();
+ id &= ~(1 << asc->sc->initiator_id);
+ {
+ register int i;
+ for (i = 0; id > 1; i++)
+ id >>= 1;
+ id = i;
+ }
+
+ /*
+ * See if we have seen him before
+ */
+ self = asc->sc->target[asc->sc->initiator_id];
+ initiator = asc->sc->target[id];
+ if (initiator == 0) {
+
+ initiator = scsi_slave_alloc(asc->sc->masterno, id, asc);
+ (*asc->dma_ops->new_target)(asc->dma_state, initiator);
+
+ }
+
+ if (! (initiator->flags & TGT_ONLINE) )
+ sccpu_new_initiator(self, initiator);
+
+ /*
+ * If selected with ATN the chip did the msg-out
+ * phase already, let us look at the message(s)
+ */
+ if (ir & ASC_INT_SEL_ATN) {
+ register unsigned char m;
+
+ m = get_fifo(regs); mb();
+ if ((m & SCSI_IDENTIFY) == 0)
+ gimmeabreak();
+
+ csr = get_reg(regs,asc_csr); mb();
+ if ((SCSI_PHASE(csr) == SCSI_PHASE_MSG_OUT) &&
+ fifo_count(regs))
+ asc_odsynch(asc, initiator);
+
+ /* Get the command now, unless we have it already */
+ mb();
+ if (fifo_count(regs) < sizeof(scsi_command_group_0)) {
+ regs->asc_cmd = ASC_CMD_RCV_CMD;
+ readback(regs->asc_cmd);
+ asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr); mb();
+ csr = get_reg(regs,asc_csr); mb();
+ }
+ } else {
+ /*
+ * Pop away the null byte that follows the id
+ */
+ if (get_fifo(regs) != 0)
+ gimmeabreak();
+
+ }
+
+ /*
+ * Take rest of command out of fifo
+ */
+ self->dev_info.cpu.req_pending = TRUE;
+ self->dev_info.cpu.req_id = id;
+ self->dev_info.cpu.req_cmd = get_fifo(regs);
+ self->dev_info.cpu.req_lun = get_fifo(regs);
+
+ LOG(0x80+self->dev_info.cpu.req_cmd, 0);
+
+ switch ((self->dev_info.cpu.req_cmd & SCSI_CODE_GROUP) >> 5) {
+ case 0:
+ len = get_fifo(regs) << 16; mb();
+ len |= get_fifo(regs) << 8; mb();
+ len |= get_fifo(regs); mb();
+ break;
+ case 1:
+ case 2:
+ len = get_fifo(regs); /* xxx lba1 */ mb();
+ len = get_fifo(regs); /* xxx lba2 */ mb();
+ len = get_fifo(regs); /* xxx lba3 */ mb();
+ len = get_fifo(regs); /* xxx lba4 */ mb();
+ len = get_fifo(regs); /* xxx xxx */ mb();
+ len = get_fifo(regs) << 8; mb();
+ len |= get_fifo(regs); mb();
+ break;
+ case 5:
+ len = get_fifo(regs); /* xxx lba1 */ mb();
+ len = get_fifo(regs); /* xxx lba2 */ mb();
+ len = get_fifo(regs); /* xxx lba3 */ mb();
+ len = get_fifo(regs); /* xxx lba4 */ mb();
+ len = get_fifo(regs) << 24; mb();
+ len |= get_fifo(regs) << 16; mb();
+ len |= get_fifo(regs) << 8; mb();
+ len |= get_fifo(regs); mb();
+ if (get_fifo(regs) != 0) ;
+ break;
+ default:
+ panic("asc_selected");
+ }
+ self->dev_info.cpu.req_len = len;
+/*if (scsi_debug) printf("[L x%x]", len);*/
+
+ /* xxx pop the cntrl byte away */
+ if (get_fifo(regs) != 0)
+ gimmeabreak();
+ mb();
+
+ /*
+ * Setup state
+ */
+ asc->active_target = self;
+ asc->done = SCSI_RET_IN_PROGRESS;
+ asc->out_count = 0;
+ asc->in_count = 0;
+ asc->extra_count = 0;
+
+ /*
+ * Sync params.
+ */
+ regs->asc_syn_p = initiator->sync_period;
+ regs->asc_syn_o = initiator->sync_offset;
+ readback(regs->asc_syn_o);
+
+ /*
+ * Do the up-call
+ */
+ LOG(0xB,"tgt-mode-restart");
+ (*self->dev_ops->restart)( self, FALSE);
+
+ /* The call above has either prepared the data,
+ placing an ior on self, or it handled it some
+ other way */
+ if (self->ior == 0)
+ return FALSE;
+
+ /* sanity */
+ if (fifo_count(regs)) {
+ regs->asc_cmd = ASC_CMD_FLUSH;
+ readback(regs->asc_cmd);
+ }
+
+ /* needed by some dma code to align things properly */
+ self->transient_state.cmd_count = sizeof(scsi_command_group_0);
+ self->transient_state.in_count = len; /* XXX */
+
+ (*asc->dma_ops->map)(asc->dma_state, self);
+
+ if (asc->wd.nactive++ == 0)
+ asc->wd.watchdog_state = SCSI_WD_ACTIVE;
+
+
+ {
+ register script_t scp;
+ unsigned char command;
+
+ switch (self->dev_info.cpu.req_cmd) {
+ case SCSI_CMD_TEST_UNIT_READY:
+ scp = asc_script_t_data_in+1; break;
+ case SCSI_CMD_SEND:
+ scp = asc_script_t_data_in; break;
+ default:
+ scp = asc_script_t_data_out; break;
+ }
+
+ asc->script = scp;
+ command = scp->command;
+ if (!(*scp->action)(asc, csr, ir))
+ return FALSE;
+
+/*if (scsi_debug) printf("[F%x]", fifo_count(regs));*/
+
+ asc->script++;
+ regs->asc_cmd = command; mb();
+
+ }
+
+ return FALSE;
+}
+
+
+/*
+ * Other utilities
+ */
+private void
+pick_up_oddb(
+ target_info_t *tgt)
+{
+ register char *lastp;
+
+ /* State should have been updated before we get here */
+ lastp = tgt->dma_ptr + tgt->transient_state.dma_offset;
+
+ if ((vm_offset_t) lastp & 1) {
+ tgt->transient_state.has_oddb = TRUE;
+ tgt->transient_state.the_oddb = lastp[-1];
+ } else
+ tgt->transient_state.has_oddb = FALSE;
+}
+
+
+/*
+ * Error handlers
+ */
+
+/*
+ * Fall-back error handler.
+ */
+asc_err_generic(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ LOG(0x13,"err_generic");
+
+ /* handle non-existant or powered off devices here */
+ if ((ir == ASC_INT_DISC) &&
+ (asc_isa_select(asc->cmd_was)) &&
+ (ASC_SS(asc->ss_was) == 0)) {
+ /* Powered off ? */
+ target_info_t *tgt = asc->active_target;
+
+ tgt->flags = 0;
+ tgt->sync_period = 0;
+ tgt->sync_offset = 0; mb();
+ asc->done = SCSI_RET_DEVICE_DOWN;
+ asc_end(asc, csr, ir);
+ return;
+ }
+
+ switch (SCSI_PHASE(csr)) {
+ case SCSI_PHASE_STATUS:
+ if (asc->script[-1].condition == SCSI_PHASE_STATUS) {
+ /* some are just slow to get out.. */
+ } else
+ asc_err_to_status(asc, csr, ir);
+ return;
+ break;
+ case SCSI_PHASE_DATAI:
+ if (asc->script->condition == SCSI_PHASE_STATUS) {
+ /*
+ * Here is what I know about it. We reconnect to
+ * the target (csr 87, ir 0C, cmd 44), start dma in
+ * (81 10 12) and then get here with (81 10 90).
+ * Dma is rolling and keeps on rolling happily,
+ * the value in the counter indicates the interrupt
+ * was signalled right away: by the time we get
+ * here only 80-90 bytes have been shipped to an
+ * rz56 running synchronous at 3.57 Mb/sec.
+ */
+/* printf("{P}");*/
+ return;
+ }
+ break;
+ case SCSI_PHASE_DATAO:
+ if (asc->script->condition == SCSI_PHASE_STATUS) {
+ /*
+ * See comment above. Actually seen on hitachis.
+ */
+/* printf("{P}");*/
+ return;
+ }
+ break;
+ case SCSI_PHASE_CMD:
+ /* This can be the case with drives that are not
+ even scsi-1 compliant and do not like to be
+ selected with ATN (to do the synch negot) and
+ go stright to command phase, regardless */
+
+ if (asc->script == asc_script_try_synch) {
+
+ target_info_t *tgt = asc->active_target;
+ register asc_padded_regmap_t *regs = asc->regs;
+
+ tgt->flags |= TGT_DID_SYNCH; /* only one chance */
+ tgt->flags &= ~TGT_TRY_SYNCH;
+
+ if (tgt->cur_cmd == SCSI_CMD_INQUIRY)
+ tgt->transient_state.script = asc_script_data_in;
+ else
+ tgt->transient_state.script = asc_script_simple_cmd;
+ asc->script = tgt->transient_state.script;
+ regs->asc_cmd = ASC_CMD_CLR_ATN;
+ readback(regs->asc_cmd);
+ asc->regs->asc_cmd = ASC_CMD_XFER_PAD|ASC_CMD_DMA; /*0x98*/ mb();
+ printf(" did not like SYNCH xfer ");
+ return;
+ }
+ /* fall through */
+ }
+ gimmeabreak();
+}
+
+/*
+ * Handle generic errors that are reported as
+ * an unexpected change to STATUS phase
+ */
+asc_err_to_status(asc, csr, ir)
+ register asc_softc_t asc;
+{
+ script_t scp = asc->script;
+
+ LOG(0x14,"err_tostatus");
+ while (scp->condition != SCSI_PHASE_STATUS)
+ scp++;
+ (*scp->action)(asc, csr, ir);
+ asc->script = scp + 1;
+ asc->regs->asc_cmd = scp->command; mb();
+#if 0
+ /*
+ * Normally, we would already be able to say the command
+ * is in error, e.g. the tape had a filemark or something.
+ * But in case we do disconnected mode WRITEs, it is quite
+ * common that the following happens:
+ * dma_out -> disconnect (xfer complete) -> reconnect
+ * and our script might expect at this point that the dma
+ * had to be restarted (it didn't notice it was completed).
+ * And in any event.. it is both correct and cleaner to
+ * declare error iff the STATUS byte says so.
+ */
+ asc->done = SCSI_RET_NEED_SENSE;
+#endif
+}
+
+/*
+ * Handle disconnections as exceptions
+ */
+asc_err_disconn(asc, csr, ir)
+ register asc_softc_t asc;
+ register unsigned char csr, ir;
+{
+ register asc_padded_regmap_t *regs;
+ register target_info_t *tgt;
+ int count;
+ boolean_t callback = FALSE;
+
+ LOG(0x16,"err_disconn");
+
+ /*
+ * We only do msg-in cases here
+ */
+ if (SCSI_PHASE(csr) != SCSI_PHASE_MSG_IN)
+ return asc_err_generic(asc, csr, ir);
+
+ regs = asc->regs;
+ tgt = asc->active_target;
+
+ /*
+ * What did we expect to happen, and what did happen.
+ */
+ switch (asc->script->condition) {
+ case SCSI_PHASE_DATAO:
+ /*
+ * A data out phase was either about to be started,
+ * or it was in progress but more had to go out later
+ * [e.g. a big xfer for instance, or more than the
+ * DMA engine can take in one shot].
+ */
+ LOG(0x1b,"+DATAO");
+ if (asc->out_count) {
+ register int xferred, offset;
+
+ /*
+ * Xfer in progress. See where we stopped.
+ */
+ ASC_TC_GET(regs,xferred); /* temporary misnomer */
+ /*
+ * Account for prefetching, in its various forms
+ */
+ xferred += fifo_count(regs);
+ xferred -= asc->extra_count;
+ /*
+ * See how much went out, how much to go.
+ */
+ xferred = asc->out_count - xferred; /* ok now */
+ tgt->transient_state.out_count -= xferred;
+ assert(tgt->transient_state.out_count > 0);
+
+ callback = (*asc->dma_ops->disconn_1)
+ (asc->dma_state, tgt, xferred);
+
+ } else {
+
+ /*
+ * A disconnection before DMA was (re)started.
+ */
+ callback = (*asc->dma_ops->disconn_2)
+ (asc->dma_state, tgt);
+
+ }
+ asc->extra_count = 0;
+ tgt->transient_state.script = asc_script_restart_data_out;
+ break;
+
+
+ case SCSI_PHASE_DATAI:
+ /*
+ * Same as above, the other way around
+ */
+ LOG(0x17,"+DATAI");
+ if (asc->in_count) {
+ register int offset, xferred;
+
+ /*
+ * How much did we expect, how much did we get
+ */
+ ASC_TC_GET(regs,count); mb();
+ xferred = asc->in_count - count;
+ assert(xferred > 0);
+
+if (regs->asc_flags & 0xf)
+printf("{Xf %x,%x,%x}", xferred, asc->in_count, fifo_count(regs));
+ tgt->transient_state.in_count -= xferred;
+ assert(tgt->transient_state.in_count > 0);
+
+ callback = (*asc->dma_ops->disconn_3)
+ (asc->dma_state, tgt, xferred);
+
+ /*
+ * Handle obb if we have to. DMA code has
+ * updated pointers and flushed buffers.
+ */
+ if (asc->state & ASC_STATE_DO_RFB)
+ pick_up_oddb(tgt);
+
+ tgt->transient_state.script = asc_script_restart_data_in;
+ /*
+ * Some silly targets disconnect after they
+ * have given us all the data.
+ */
+ if (tgt->transient_state.in_count == 0)
+ tgt->transient_state.script++;
+
+ } else
+ tgt->transient_state.script = asc_script_restart_data_in;
+ break;
+
+ case SCSI_PHASE_STATUS:
+ /*
+ * Either one of the above cases here. Only diff
+ * the DMA engine was setup to run to completion
+ * and (most likely) did not.
+ */
+ ASC_TC_GET(regs,count); mb();
+ if (asc->state & ASC_STATE_DMA_IN) {
+ register int offset, xferred;
+
+ LOG(0x1a,"+STATUS+R");
+
+
+ /*
+ * Handle brain-dead sequence:
+ * 1-xfer all data, disconnect
+ * 2-reconnect, disconnect immediately ??
+ * 3-rept 2
+ * 4-reconnect,complete
+ */
+ if (asc->in_count) {
+
+ xferred = asc->in_count - count;
+ assert(xferred > 0);
+if (regs->asc_flags & 0xf)
+printf("{Xf %x,%x,%x}", xferred, asc->in_count, fifo_count(regs));
+
+ tgt->transient_state.in_count -= xferred;
+
+ callback = (*asc->dma_ops->disconn_4)
+ (asc->dma_state, tgt, xferred);
+ }
+ /*
+ * Handle obb if we have to. DMA code has
+ * updated pointers and flushed buffers.
+ */
+ if (asc->state & ASC_STATE_DO_RFB)
+ pick_up_oddb(tgt);
+
+ tgt->transient_state.script = asc_script_restart_data_in;
+
+ /* see previous note */
+ if (tgt->transient_state.in_count == 0)
+ tgt->transient_state.script++;
+
+ } else {
+
+ /*
+ * Outgoing xfer, take care of prefetching.
+ */
+ /* add what's left in the fifo */
+ count += fifo_count(regs);
+ /* take back the extra we might have added */
+ count -= asc->extra_count;
+ /* ..and drop that idea */
+ asc->extra_count = 0;
+
+ LOG(0x19,"+STATUS+W");
+
+ /*
+ * All done ? This is less silly than with
+ * READs: some disks will only say "done" when
+ * the data is down on the platter, and some
+ * people like it much better that way.
+ */
+ if ((count == 0) && (tgt->transient_state.out_count == asc->out_count)) {
+ /* all done */
+ tgt->transient_state.script = asc->script;
+ tgt->transient_state.out_count = 0;
+ } else {
+ register int xferred, offset;
+
+ /* how much we xferred */
+ xferred = asc->out_count - count;
+
+ /* how much to go */
+ tgt->transient_state.out_count -= xferred;
+ assert(tgt->transient_state.out_count > 0);
+
+ callback = (*asc->dma_ops->disconn_5)
+ (asc->dma_state,tgt,xferred);
+
+ tgt->transient_state.script = asc_script_restart_data_out;
+ }
+ asc->out_count = 0;
+ }
+ break;
+ default:
+ gimmeabreak();
+ return;
+ }
+ asc_msg_in(asc,csr,ir);
+ asc->script = asc_script_disconnect;
+ regs->asc_cmd = ASC_CMD_XFER_INFO|ASC_CMD_DMA;
+ wbflush();
+ /*
+ * Prevent a race, now. If the reselection comes quickly
+ * the chip will prefetch and reload the transfer counter
+ * register. Make sure it will stop, by reloading a zero.
+ */
+ regs->asc_tc_lsb = 0;
+ regs->asc_tc_msb = 0;
+ if (callback)
+ (*asc->dma_ops->disconn_callback)(asc->dma_state, tgt);
+}
+
+/*
+ * Watchdog
+ *
+ * So far I have only seen the chip get stuck in a
+ * select/reselect conflict: the reselection did
+ * win and the interrupt register showed it but..
+ * no interrupt was generated.
+ * But we know that some (name withdrawn) disks get
+ * stuck in the middle of dma phases...
+ */
+asc_reset_scsibus(asc)
+ register asc_softc_t asc;
+{
+ register target_info_t *tgt = asc->active_target;
+ register asc_padded_regmap_t *regs = asc->regs;
+ register int ir;
+
+ if (scsi_debug && tgt) {
+ int dmalen;
+ ASC_TC_GET(asc->regs,dmalen); mb();
+ printf("Target %d was active, cmd x%x in x%x out x%x Sin x%x Sou x%x dmalen x%x\n",
+ tgt->target_id, tgt->cur_cmd,
+ tgt->transient_state.in_count, tgt->transient_state.out_count,
+ asc->in_count, asc->out_count,
+ dmalen);
+ }
+ ir = get_reg(regs,asc_intr); mb();
+ if ((ir & ASC_INT_RESEL) && (SCSI_PHASE(regs->asc_csr) == SCSI_PHASE_MSG_IN)) {
+ /* getting it out of the woods is a bit tricky */
+ spl_t s = splbio();
+
+ (void) asc_reconnect(asc, get_reg(regs,asc_csr), ir);
+ asc_wait(regs, ASC_CSR_INT, 1);
+ ir = get_reg(regs,asc_intr); mb();
+ regs->asc_cmd = ASC_CMD_MSG_ACPT;
+ readback(regs->asc_cmd);
+ splx(s);
+ } else {
+ regs->asc_cmd = ASC_CMD_BUS_RESET; mb();
+ delay(35);
+ }
+}
+
+#endif NASC > 0
+