summaryrefslogtreecommitdiff
path: root/scsi/adapters/scsi_89352_hdw.c
diff options
context:
space:
mode:
Diffstat (limited to 'scsi/adapters/scsi_89352_hdw.c')
-rw-r--r--scsi/adapters/scsi_89352_hdw.c2192
1 files changed, 2192 insertions, 0 deletions
diff --git a/scsi/adapters/scsi_89352_hdw.c b/scsi/adapters/scsi_89352_hdw.c
new file mode 100644
index 0000000..5672cb6
--- /dev/null
+++ b/scsi/adapters/scsi_89352_hdw.c
@@ -0,0 +1,2192 @@
+/*
+ * Mach Operating System
+ * Copyright (c) 1992,1991 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_89352_hdw.c
+ * Author: Daniel Stodolsky, Carnegie Mellon University
+ * Date: 06/91
+ *
+ * Bottom layer of the SCSI driver: chip-dependent functions
+ *
+ * This file contains the code that is specific to the Fujitsu MB89352
+ * 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
+ *
+ */
+
+/*
+ *
+ *
+ * Known Headaches/Features with this chip.
+ *
+ * (1) After the interrupt raised by select, the phase sense (psns)
+ * and SPC status (ssts) registers do not display the correct values
+ * until the REQ line (via psns) is high. (danner@cs.cmu.edu 6/11/91)
+ *
+ * (2) After a data in phase, the command complete interrupt may be raised
+ * before the psns, ssts, and transfer counter registers settle. The reset
+ * acknowledge or request command should not be issued until they settle.
+ * (danner@cs.cmu.edu 6/14/91)
+ *
+ * (3) In general, an interrupt can be raised before the psns and ssts have
+ * meaningful values. One should wait for the psns to show the REQ bit (0x80)
+ * set before expecting meaningful values, with the exception of (2) above.
+ * Currently this is handled by spc_err_generic ("Late REQ"). (This problem
+ * is really a refinement of (1)). (danner@cs.cmu.edu 6/14/91)
+ *
+ * (4) When issuing a multibyte command after a select with attention,
+ * The chip will automatically drop ATN before sending the last byte of the
+ * message, in accordance with the ANSI SCSI standard. This requires, of course,
+ * the transfer counter be an accurate representation of the amount of data to be
+ * transfered. (danner@cs.cmu.edu 6/14/91)
+ *
+ */
+
+#if 0
+
+#include <platforms.h>
+
+#include <scsi.h>
+
+#if NSCSI > 0
+
+#include <mach/std_types.h>
+#include <sys/types.h>
+#include <chips/busses.h>
+#include <scsi/compat_30.h>
+#include <sys/syslog.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi2.h>
+#include <scsi/scsi_defs.h>
+#include <scsi/adapters/scsi_89352.h>
+
+#include <machine/db_machdep.h> /*4proto*/
+#include <ddb/db_sym.h> /*4proto*/
+
+#ifdef LUNA88K
+#include <luna88k/board.h>
+#define SPC_DEFAULT_ADDRESS (caddr_t) SCSI_ADDR
+#endif
+
+#ifndef SPC_DEFAULT_ADDRESS /* cross compile check */
+#define SPC_DEFAULT_ADDRESS (caddr_t) 0
+#endif
+
+
+/* external/forward declarations */
+int spc_probe(), spc_slave(), spc_attach(), scsi_go();
+void spc_reset(), spc_attempt_selection(), spc_target_intr(), spc_bus_reset();
+/*
+ * Statically allocated command & temp buffers
+ * This way we can attach/detach drives on-fly
+ */
+#define PER_TGT_BUFF_DATA 256
+
+static char spc_buffer[NSCSI * 8 * PER_TGT_BUFF_DATA];
+
+/*
+ * Macros to make certain things a little more readable
+ */
+
+/*
+ wait for the desired phase to appear, but make sure the REQ bit set in the psns
+ (otherwise the values tend to float/be garbage.
+*/
+
+#define SPC_WAIT_PHASE(p) while(((regs->spc_psns & (SPC_BUS_REQ|SCSI_PHASE_MASK))) \
+ != (SPC_BUS_REQ|(p)))
+
+/*
+ wait until a phase different than p appears in the psns. Since it is only valid
+ when the REQ bit is set, don't test unless REQ bit is set. So spin until
+ REQ is high or the phase is not p.
+*/
+
+#define SPC_WAIT_PHASE_VANISH(p) while(1) { int _psns_ = regs->spc_psns; \
+ if ((_psns_ & SPC_BUS_REQ) && (_psns_ & SCSI_PHASE_MASK)!=p) break; }
+
+
+
+/* ?? */
+/* #define SPC_ACK(ptr,phase) (ptr)->spc_pctl = (phase) */
+
+/*
+ * A script has a two parts: a pre-condition and an action.
+ * The first triggers error handling if not satisfied and in
+ * our case it is formed by the current bus phase and connected
+ * condition as per bus status bits. The action part is just a
+ * function pointer, invoked in a standard way. The script
+ * pointer is advanced only if the action routine returns TRUE.
+ * See spc_intr() for how and where this is all done.
+ */
+
+typedef struct script {
+ char condition; /* expected state at interrupt */
+ int (*action)(); /* action routine */
+} *script_t;
+
+#define SCRIPT_MATCH(psns) (SPC_CUR_PHASE((psns))|((psns) & SPC_BUS_BSY))
+
+/* ?? */
+#define SPC_PHASE_DISC 0x0 /* sort of .. */
+
+/* The active script is in the state expected right after the issue of a select */
+
+#define SCRIPT_SELECT(scp) (scp->action == spc_issue_command || \
+ scp->action == spc_issue_ident_and_command)
+
+/* forward decls of script actions */
+boolean_t
+ spc_dosynch(), /* negotiate synch xfer */
+ spc_xfer_in(), /* get data from target via dma */
+ spc_xfer_out(), /* send data to target via dma */
+ spc_get_status(), /* get status from target */
+ spc_end_transaction(), /* all come to an end */
+ spc_msg_in(), /* get disconnect message(s) */
+ spc_issue_command(), /* spit on the bus */
+ spc_issue_ident_and_command(), /* spit on the bus (with ATN) */
+ spc_disconnected(); /* current target disconnected */
+/* forward decls of error handlers */
+boolean_t
+ spc_err_generic(), /* generic error handler */
+ spc_err_disconn(); /* when a target disconnects */
+void gimmeabreak(); /* drop into the debugger */
+
+void spc_reset_scsibus();
+boolean_t spc_probe_target();
+
+scsi_ret_t spc_select_target();
+
+/*
+ * State descriptor for this layer. There is one such structure
+ * per (enabled) 89352 chip
+ */
+struct spc_softc {
+ watchdog_t wd;
+ spc_regmap_t *regs; /* 5380 registers */
+ char *buff; /* scratch buffer memory */
+ char *data_ptr; /* orig/dest memory */
+ script_t script;
+ int (*error_handler)();
+ int in_count; /* amnt we expect to receive */
+ int out_count; /* amnt we are going to ship */
+
+ volatile char state;
+#define SPC_STATE_BUSY 0x01 /* selecting or currently connected */
+#define SPC_STATE_TARGET 0x04 /* currently selected as target */
+#define SPC_STATE_COLLISION 0x08 /* lost selection attempt */
+#define SPC_STATE_DMA_IN 0x10 /* tgt --> initiator xfer */
+
+ unsigned char ntargets; /* how many alive on this scsibus */
+ unsigned char done;
+ unsigned char xxxx;
+
+ scsi_softc_t *sc;
+ target_info_t *active_target;
+
+ target_info_t *next_target; /* trying to seize bus */
+ queue_head_t waiting_targets;/* other targets competing for bus */
+ decl_simple_lock_data(,chiplock) /* Interlock */
+} spc_softc_data[NSCSI];
+
+typedef struct spc_softc *spc_softc_t;
+
+spc_softc_t spc_softc[NSCSI];
+
+/*
+ * Definition of the controller for the auto-configuration program.
+ */
+
+int spc_probe(), scsi_slave(), spc_go();
+void spc_intr();
+void scsi_attach();
+
+vm_offset_t spc_std[NSCSI] = { SPC_DEFAULT_ADDRESS };
+
+struct bus_device *spc_dinfo[NSCSI*8];
+struct bus_ctlr *spc_minfo[NSCSI];
+struct bus_driver spc_driver =
+ { spc_probe, scsi_slave, scsi_attach, spc_go, spc_std, "rz", spc_dinfo,
+ "spc", spc_minfo, BUS_INTR_B4_PROBE};
+
+/*
+ * Scripts
+ */
+
+struct script
+spc_script_data_in[] = {
+ { SCSI_PHASE_CMD|SPC_BUS_BSY, spc_issue_command},
+ { SCSI_PHASE_DATAI|SPC_BUS_BSY, spc_xfer_in},
+ { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status},
+ { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction}
+},
+
+spc_script_late_data_in[] = {
+ { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_issue_ident_and_command},
+ { SCSI_PHASE_DATAI|SPC_BUS_BSY, spc_xfer_in},
+ { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status},
+ { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction}
+},
+
+spc_script_data_out[] = {
+ { SCSI_PHASE_CMD|SPC_BUS_BSY, spc_issue_command},
+ { SCSI_PHASE_DATAO|SPC_BUS_BSY, spc_xfer_out},
+ { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status},
+ { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction}
+},
+
+
+spc_script_late_data_out[] = {
+ { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_issue_ident_and_command},
+ { SCSI_PHASE_DATAO|SPC_BUS_BSY, spc_xfer_out},
+ { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status},
+ { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction}
+},
+
+
+spc_script_cmd[] = {
+ { SCSI_PHASE_CMD|SPC_BUS_BSY, spc_issue_command},
+ { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status},
+ { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction}
+},
+
+spc_script_late_cmd[] = {
+ { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_issue_ident_and_command},
+ { SCSI_PHASE_STATUS|SPC_BUS_BSY, spc_get_status},
+ { SCSI_PHASE_MSG_IN|SPC_BUS_BSY, spc_end_transaction}
+},
+
+/* Synchronous transfer neg(oti)ation */
+
+spc_script_try_synch[] = {
+ { SCSI_PHASE_MSG_OUT|SPC_BUS_BSY, spc_dosynch}
+},
+
+/* Disconnect sequence */
+
+spc_script_disconnect[] = {
+ { SPC_PHASE_DISC, spc_disconnected}
+};
+
+
+
+#define u_min(a,b) (((a) < (b)) ? (a) : (b))
+
+
+#define DEBUG
+#ifdef DEBUG
+
+int spc_state(base)
+ vm_offset_t base;
+{
+ register spc_regmap_t *regs;
+
+ if (base == 0)
+ base = (vm_offset_t) SPC_DEFAULT_ADDRESS;
+
+ regs = (spc_regmap_t*) (base);
+
+ db_printf("spc_bdid (bus device #): %x\n",regs->spc_bdid);
+ db_printf("spc_sctl (spc internal control): %x\n",regs->spc_sctl);
+ db_printf("spc_scmd (scp command): %x\n",regs->spc_scmd);
+ db_printf("spc_ints (spc interrupt): %x\n",regs->spc_ints);
+ db_printf("spc_psns (scsi bus phase): %x\n",regs->spc_psns);
+ db_printf("spc_ssts (spc internal status): %x\n",regs->spc_ssts);
+ db_printf("spc_serr (spc internal err stat): %x\n",regs->spc_serr);
+ db_printf("spc_pctl (scsi transfer phase): %x\n",regs->spc_pctl);
+ db_printf("spc_mbc (spc transfer data ct): %x\n",regs->spc_mbc);
+/* db_printf("spc_dreg (spc transfer data r/w): %x\n",regs->spc_dreg);*/
+ db_printf("spc_temp (scsi data bus control): %x\n",regs->spc_temp);
+ db_printf("spc_tch (transfer byte ct (MSB): %x\n",regs->spc_tch);
+ db_printf("spc_tcm (transfer byte ct (2nd): %x\n",regs->spc_tcm);
+ db_printf("spc_tcl (transfer byte ct (LSB): %x\n",regs->spc_tcl);
+
+ return 0;
+}
+
+int spc_target_state(tgt)
+ target_info_t *tgt;
+{
+ if (tgt == 0)
+ tgt = spc_softc[0]->active_target;
+ if (tgt == 0)
+ return 0;
+ db_printf("fl %x dma %x+%x cmd %x id %x per %x off %x ior %x ret %x\n",
+ tgt->flags, tgt->dma_ptr, tgt->transient_state.dma_offset,
+ tgt->cmd_ptr, tgt->target_id, tgt->sync_period, tgt->sync_offset,
+ tgt->ior, tgt->done);
+ if (tgt->flags & TGT_DISCONNECTED){
+ script_t spt;
+
+ spt = tgt->transient_state.script;
+ db_printf("disconnected at ");
+ db_printsym((db_expr_t)spt,1);
+ db_printf(": %x ", spt->condition);
+ db_printsym((db_expr_t)spt->action,1);
+ db_printf(", ");
+ db_printsym((db_expr_t)tgt->transient_state.handler, 1);
+ db_printf("\n");
+ }
+
+ return 0;
+}
+
+void spc_all_targets(unit)
+int unit;
+{
+ int i;
+ target_info_t *tgt;
+ for (i = 0; i < 8; i++) {
+ tgt = spc_softc[unit]->sc->target[i];
+ if (tgt)
+ spc_target_state(tgt);
+ }
+}
+
+int spc_script_state(unit)
+int unit;
+{
+ script_t spt = spc_softc[unit]->script;
+
+ if (spt == 0) return 0;
+ db_printsym((db_expr_t)spt,1);
+ db_printf(": %x ", spt->condition);
+ db_printsym((db_expr_t)spt->action,1);
+ db_printf(", ");
+ db_printsym((db_expr_t)spc_softc[unit]->error_handler, 1);
+ return 0;
+}
+
+#define PRINT(x) if (scsi_debug) printf x
+
+#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}
+
+#define TRACE
+
+#ifdef TRACE
+
+#define LOGSIZE 256
+int spc_logpt;
+int spc_log[LOGSIZE];
+
+#define MAXLOG_VALUE 0x30
+struct {
+ char *name;
+ unsigned int count;
+} logtbl[MAXLOG_VALUE];
+
+static void LOG(e,f)
+ int e;
+ char *f;
+{
+ spc_log[spc_logpt++] = (e);
+ if (spc_logpt == LOGSIZE) spc_logpt = 0;
+ if ((e) < MAXLOG_VALUE) {
+ logtbl[(e)].name = (f);
+ logtbl[(e)].count++;
+ }
+}
+
+int spc_print_log(skip)
+ int skip;
+{
+ register int i, j;
+ register unsigned int c;
+
+ for (i = 0, j = spc_logpt; i < LOGSIZE; i++) {
+ c = spc_log[j];
+ if (++j == LOGSIZE) j = 0;
+ if (skip-- > 0)
+ continue;
+ if (c < MAXLOG_VALUE)
+ db_printf(" %s", logtbl[c].name);
+ else
+ db_printf("-0x%x", c - 0x80);
+ }
+ db_printf("\n");
+ return 0;
+}
+
+void spc_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)
+#endif /* TRACE */
+
+#else /* DEBUG */
+#define PRINT(x)
+#define LOG(e,f)
+#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 an identify msg to each possible target on the bus
+ * except of course ourselves.
+ */
+int spc_probe(reg, ui)
+ char *reg;
+ struct bus_ctlr *ui;
+{
+ int tmp;
+ int unit = ui->unit;
+ spc_softc_t spc = &spc_softc_data[unit];
+ int target_id, i;
+ scsi_softc_t *sc;
+ register spc_regmap_t *regs;
+ int s;
+ boolean_t did_banner = FALSE;
+ char *cmd_ptr;
+
+ /*
+ * We are only called if the chip is there,
+ * but make sure anyways..
+ */
+ regs = (spc_regmap_t *) (reg);
+ if (check_memory((unsigned)regs, 0))
+ return 0;
+
+#if notyet
+ /* Mappable version side */
+ SPC_probe(reg, ui);
+#endif
+
+ /*
+ * Initialize hw descriptor
+ */
+ spc_softc[unit] = spc;
+ spc->regs = regs;
+ spc->buff = spc_buffer;
+
+ queue_init(&spc->waiting_targets);
+
+ simple_lock_init(&spc->chiplock);
+
+ sc = scsi_master_alloc(unit, (char*)spc);
+ spc->sc = sc;
+
+ sc->go = spc_go;
+ sc->probe = spc_probe_target;
+ sc->watchdog = scsi_watchdog;
+ spc->wd.reset = spc_reset_scsibus;
+
+#ifdef MACH_KERNEL
+ sc->max_dma_data = -1; /* unlimited */
+#else
+ sc->max_dma_data = scsi_per_target_virtual;
+#endif
+
+ scsi_might_disconnect[unit] = 0; /* XXX for now */
+
+ /*
+ * Reset chip
+ */
+ s = splbio();
+ spc_reset(regs, TRUE);
+ tmp = regs->spc_ints = regs->spc_ints;
+
+ /*
+ * Our SCSI id on the bus.
+ */
+
+ sc->initiator_id = bdid_to_id(regs->spc_bdid);
+ printf("%s%d: my SCSI id is %d", ui->name, unit, sc->initiator_id);
+
+ /*
+ * For all possible targets, see if there is one and allocate
+ * a descriptor for it if it is there.
+ */
+ cmd_ptr = spc_buffer;
+ for (target_id = 0; target_id < 8; target_id++, cmd_ptr += PER_TGT_BUFF_DATA) {
+
+ register unsigned csr, ints;
+ scsi_status_byte_t status;
+
+ /* except of course ourselves */
+ if (target_id == sc->initiator_id)
+ continue;
+
+ if (spc_select_target( regs, sc->initiator_id, target_id, FALSE)
+ == SCSI_RET_DEVICE_DOWN) {
+ tmp = regs->spc_ints = regs->spc_ints;
+ continue;
+ }
+
+ printf(",%s%d", did_banner++ ? " " : " target(s) at ",
+ target_id);
+
+ /* should be command phase here: we selected wo ATN! */
+ SPC_WAIT_PHASE(SCSI_PHASE_CMD);
+
+ SPC_ACK(regs,SCSI_PHASE_CMD);
+
+ /* build command in buffer */
+ {
+ unsigned char *p = (unsigned char*) cmd_ptr;
+
+ p[0] = SCSI_CMD_TEST_UNIT_READY;
+ p[1] =
+ p[2] =
+ p[3] =
+ p[4] =
+ p[5] = 0;
+ }
+
+ spc_data_out(regs, SCSI_PHASE_CMD, 6, cmd_ptr);
+
+ SPC_WAIT_PHASE(SCSI_PHASE_STATUS);
+
+ /* should have recieved a Command Complete Interrupt */
+ while (!(regs->spc_ints))
+ delay(1);
+ ints = regs->spc_ints;
+ if (ints != (SPC_INTS_DONE))
+ gimmeabreak();
+ regs->spc_ints = ints;
+
+ SPC_ACK(regs,SCSI_PHASE_STATUS);
+
+ csr = spc_data_in(regs, SCSI_PHASE_STATUS, 1, &status.bits);
+ LOG(0x25,"din_count");
+ LOG(0x80+csr,0);
+
+ if (status.st.scsi_status_code != SCSI_ST_GOOD)
+ scsi_error( 0, SCSI_ERR_STATUS, status.bits, 0);
+
+ /* expect command complete interupt */
+ while (!(regs->spc_ints & SPC_INTS_DONE))
+ delay(1);
+
+ /* clear all intr bits */
+ tmp = regs->spc_ints;
+ LOG(0x26,"ints");
+ LOG(0x80+tmp,0);
+ regs->spc_ints = SPC_INTS_DONE;
+
+ /* get cmd_complete message */
+ SPC_WAIT_PHASE(SCSI_PHASE_MSG_IN);
+
+ SPC_ACK(regs,SCSI_PHASE_MSG_IN);
+
+ csr = spc_data_in(regs,SCSI_PHASE_MSG_IN, 1,(unsigned char*)&i);
+ LOG(0x25,"din_count");
+ LOG(0x80+csr,0);
+
+ while (!(regs->spc_ints & SPC_INTS_DONE))
+ delay(1);
+
+ /* clear all done intr */
+ tmp = regs->spc_ints;
+ LOG(0x26,"ints");
+ LOG(0x80+tmp,0);
+ regs->spc_ints = SPC_INTS_DONE;
+
+ SPC_ACK(regs,SPC_PHASE_DISC);
+
+ /* release the bus */
+ regs->spc_pctl = ~SPC_PCTL_BFREE_IE & SPC_PHASE_DISC;
+ /* regs->spc_scmd = 0; only in TARGET mode */
+
+ /* wait for disconnected interrupt */
+ while (!(regs->spc_ints & SPC_INTS_DISC))
+ delay(1);
+
+ tmp = regs->spc_ints;
+ LOG(0x26,"ints");
+ LOG(0x80+tmp,0);
+ regs->spc_ints = tmp;
+ LOG(0x29,"Probed\n");
+
+ /*
+ * Found a target
+ */
+ spc->ntargets++;
+ {
+ register target_info_t *tgt;
+
+ tgt = scsi_slave_alloc(unit, target_id, (char*)spc);
+
+ /* "virtual" address for our use */
+ tgt->cmd_ptr = cmd_ptr;
+ /* "physical" address for dma engine (??) */
+ tgt->dma_ptr = 0;
+#ifdef MACH_KERNEL
+#else /*MACH_KERNEL*/
+ fdma_init(&tgt->fdma, scsi_per_target_virtual);
+#endif /*MACH_KERNEL*/
+ }
+ }
+ printf(".\n");
+
+ splx(s);
+ return 1;
+}
+
+boolean_t
+spc_probe_target(tgt, ior)
+ target_info_t *tgt;
+ io_req_t ior;
+{
+ boolean_t newlywed;
+
+ newlywed = (tgt->cmd_ptr == 0);
+ if (newlywed) {
+ /* desc was allocated afresh */
+
+ /* "virtual" address for our use */
+ tgt->cmd_ptr = &spc_buffer[PER_TGT_BUFF_DATA*tgt->target_id +
+ (tgt->masterno*8*PER_TGT_BUFF_DATA) ];
+ /* "physical" address for dma engine */
+ tgt->dma_ptr = 0;
+#ifdef MACH_KERNEL
+#else /*MACH_KERNEL*/
+ fdma_init(&tgt->fdma, scsi_per_target_virtual);
+#endif /*MACH_KERNEL*/
+
+ }
+
+ if (scsi_inquiry(tgt, SCSI_INQ_STD_DATA) == SCSI_RET_DEVICE_DOWN)
+ return FALSE;
+
+ tgt->flags = TGT_ALIVE;
+ return TRUE;
+}
+
+int bdid_to_id(bdid)
+ register int bdid;
+{
+ register int i;
+ for (i = 0; i < 8; i++)
+ if (bdid == (1 << i)) break;
+ return i;
+}
+
+scsi_ret_t
+spc_select_target(regs, myid, id, with_atn)
+ register spc_regmap_t *regs;
+ unsigned myid, id;
+ boolean_t with_atn;
+{
+ scsi_ret_t ret = SCSI_RET_RETRY;
+ int mask;
+
+ if ((regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL))
+#ifdef MIPS
+ && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL))
+ && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL))
+#endif
+ )
+ return ret;
+
+ /* setup for for select:
+
+#if 0
+ (1) Toggle the Enable transfer bit (turning on the chips
+ SCSI bus drivers).
+#endif
+ (2) Enable arbitration, parity, reselect display, but
+ disable interrupt generation to the CPU (we are polling).
+ (3) Disable the bus free interrupt and set I/O direction
+ (4) If doing a select with attention, write the Set attention command.
+ Then delay 1 microsecond to avoid command races.
+
+ (5) Temp register gets 1<<target | 1<<initiator ids
+ (6) Timeout clocked into transfer registers
+ (7) Drive select (and optionally attention) onto the bus
+ (8) Wait 1/4 second for timeout.
+ */
+
+#if 0
+ regs->spc_psns = SPC_DIAG_ENBL_XFER; /* (1) */
+#endif
+
+ regs->spc_sctl = SPC_SCTL_ARB_EBL|
+ SPC_SCTL_PAR_EBL|
+ SPC_SCTL_RSEL_EBL; /* (2) */
+
+
+
+ mask = ~SPC_PCTL_BFREE_IE & regs->spc_pctl;
+ mask &= ~1; /* set I/O direction to be out */
+
+ regs->spc_pctl = mask; /* (3) */
+
+ if (with_atn)
+ {
+ regs->spc_scmd = SPC_SCMD_C_ATN_S; /* (4) */
+ delay(1);
+ }
+
+ regs->spc_temp = (1<<myid) | (1<<id); /* (5) */
+
+ SPC_TC_PUT(regs,0xfa004); /* (6) */
+
+ regs->spc_scmd = (SPC_SCMD_C_SELECT | SPC_SCMD_PROGRAMMED_X); /* (7) */
+
+ {
+ int count = 2500;
+
+ /* wait for an interrupt */
+ while ((regs->spc_ints)==0)
+ {
+ if (--count > 0)
+ delay(100);
+ else
+ {
+ goto nodev;
+ }
+ }
+
+ count = regs->spc_ints;
+ if (count & SPC_INTS_TIMEOUT)
+ {
+ /* sanity check. The ssts should have the busy bit set */
+ if (regs->spc_ssts & SPC_SSTS_BUSY)
+ goto nodev;
+ else
+ panic("spc_select_target: timeout");
+ }
+
+ /* otherwise, we should have received a
+ command complete interrupt */
+
+ if (count & ~SPC_INTS_DONE)
+ panic("spc_select_target");
+
+ } /* (8) */
+
+ /* we got a response - now connected; bus is in COMMAND phase */
+
+ regs->spc_ints = regs->spc_ints;
+ /* regs->spc_scmd = 0; target only */
+ return SCSI_RET_SUCCESS;
+nodev:
+ SPC_TC_PUT(regs,0); /* play it safe */
+ regs->spc_ints = regs->spc_ints;
+ /* regs->spc_scmd = 0; target only */
+ ret = SCSI_RET_DEVICE_DOWN;
+ return ret;
+}
+
+int spc_data_out(regs, phase, count, data)
+ int phase, count;
+ register spc_regmap_t *regs;
+ unsigned char *data;
+{
+ /* This is the one that sends data out. returns how many
+ bytes it did NOT xfer: */
+
+ if (SPC_CUR_PHASE(regs->spc_phase) != phase)
+ return count;
+
+ /* check that the fifo is empty. If not, cry */
+ if (!(regs->spc_ssts & SPC_SSTS_FIFO_EMPTY))
+ panic("spc_data_out: junk in fifo\n");
+
+ SPC_TC_PUT(regs,count);
+ regs->spc_scmd = SPC_SCMD_C_XFER | SPC_SCMD_PROGRAMMED_X;
+
+ /* wait for the SPC to start processing the command */
+ while ((regs->spc_ssts & (SPC_SSTS_INI_CON|SPC_SSTS_TGT_CON|SPC_SSTS_BUSY|SPC_SSTS_XIP))
+ != (SPC_SSTS_INI_CON|SPC_SSTS_BUSY|SPC_SSTS_XIP))
+ delay(1);
+
+ /* shovel out the data */
+
+ while (count)
+ {
+ /* check if interrupt is pending */
+ int ints = regs->spc_ints;
+ int ssts;
+
+ if (ints) /* something has gone wrong */
+ break;
+
+ ssts = regs->spc_ssts;
+ if (ssts & SPC_SSTS_FIFO_FULL) /* full fifo - can't write */
+ delay(1);
+ else
+ { /* spit out a byte */
+ regs->spc_dreg = *data;
+ data++;
+ count--;
+ }
+ }
+
+
+ if (count != 0)
+ {
+ /* need some sort of fifo cleanup if failed */
+ gimmeabreak(); /* Bytes stranded in the fifo */
+ }
+
+ return count;
+}
+
+int spc_data_in(regs, phase, count, data)
+ int phase, count;
+ register spc_regmap_t *regs;
+ unsigned char *data;
+{
+ if (SPC_CUR_PHASE(regs->spc_phase) != phase)
+ return count;
+
+ SPC_TC_PUT(regs,count);
+ regs->spc_scmd = SPC_SCMD_C_XFER | SPC_SCMD_PROGRAMMED_X;
+
+ /* The Fujistu code sample suggests waiting for the top nibble of the SSTS to
+ become 0xb (ssts & 0xf0) = 0xb. This state, however is transient. If the
+ message is short (say , 1 byte), it can get sucked into the fifo before
+ we ever get to look at the state. So instead, we are going to wait for
+ the fifo to become nonempty.
+ */
+
+ while ((regs->spc_ssts & SPC_SSTS_FIFO_EMPTY))
+ delay(1);
+
+ while (count)
+ {
+ int ints = regs->spc_ints;
+ int ssts;
+
+ /* If there is an interrupt pending besides command complete or
+ phase mismatch, give up */
+
+ if (ints & ~(SPC_INTS_DONE|SPC_INTS_BUSREQ))
+ break;
+
+ /* see if there is any data in the fifo */
+ ssts = regs->spc_ssts;
+ if ((ssts & SPC_SSTS_FIFO_EMPTY) == 0)
+ {
+ *data = regs->spc_dreg;
+ data++;
+ count--;
+ continue;
+ }
+
+ /* if empty, check if phase has changed */
+ if (SPC_CUR_PHASE(regs->spc_phase) != phase)
+ break;
+
+ }
+
+ if ((count==0) && (phase == SCSI_PHASE_MSG_IN))
+ {
+ while (!(regs->spc_ints & SPC_INTS_DONE))
+ delay(1);
+
+ /*
+ So the command complete interrupt has arrived. Now check that the
+ other two conditions we expect - The psns to be in ack|busy|message_in phase
+ and ssts to indicate connected|xfer in progress|busy|xfer counter 0|empty fifo
+ are true.
+ */
+ while (1)
+ {
+ register int psns = regs->spc_psns;
+ register int ssts = regs->spc_ssts;
+ register int sscon = ssts & (SPC_SSTS_INI_CON | SPC_SSTS_TGT_CON);
+ register int ssncon = ssts & ~(SPC_SSTS_INI_CON | SPC_SSTS_TGT_CON);
+
+ if (psns == (SPC_BUS_ACK | SPC_BUS_BSY | SCSI_PHASE_MSG_IN) &&
+ ssncon == (SPC_SSTS_BUSY | SPC_SSTS_XIP | SPC_SSTS_TC0 | SPC_SSTS_FIFO_EMPTY) &&
+ sscon)
+ break;
+ }
+
+ regs->spc_scmd = SPC_SCMD_C_ACKREQ_C;
+ }
+
+ return count;
+}
+
+void spc_reset(regs, quickly)
+ register spc_regmap_t *regs;
+ boolean_t quickly;
+{
+ register char myid;
+
+ /* save our id across reset */
+ myid = bdid_to_id(regs->spc_bdid);
+
+ /* wait for Reset In signal to go low */
+ while (regs->spc_ssts & SPC_SSTS_RST)
+ delay(1);
+
+ /* reset chip */
+ regs->spc_sctl = SPC_SCTL_RESET;
+ delay(25);
+
+ regs->spc_myid = myid;
+ regs->spc_sctl = SPC_SCTL_ARB_EBL|SPC_SCTL_PAR_EBL|SPC_SCTL_SEL_EBL|
+ SPC_SCTL_RSEL_EBL|SPC_SCTL_IE;
+ regs->spc_scmd = SPC_SCMD_C_BUS_RLSE;
+ /* regs->spc_tmod = 0; - SANDRO ? */
+ regs->spc_ints = 0xff;/* clear off any pending */
+#if 0
+ regs->spc_pctl = SPC_PCTL_LST_IE; /* useful only on 87033 */
+#else
+ regs->spc_pctl = 0;
+#endif
+ regs->spc_mbc = 0;
+ SPC_TC_PUT(regs,0);
+
+ if (quickly)
+ return;
+
+ /*
+ * reset the scsi bus, the interrupt routine does the rest
+ * or you can call spc_bus_reset().
+ */
+ regs->spc_scmd = SPC_SCMD_BUSRST|SPC_SCMD_C_STOP_X;/*?*/
+}
+
+/*
+ * Operational functions
+ */
+
+/*
+ * Start a SCSI command on a target
+ */
+spc_go(tgt, cmd_count, in_count, cmd_only)
+ int cmd_count, in_count;
+ target_info_t *tgt;
+ boolean_t cmd_only;
+{
+ spc_softc_t spc;
+ register int s;
+ boolean_t disconn;
+ script_t scp;
+ boolean_t (*handler)();
+ int late;
+
+ LOG(1,"\n\tgo");
+
+ spc = (spc_softc_t)tgt->hw_state;
+
+ /*
+ * We cannot do real DMA.
+ */
+#ifdef MACH_KERNEL
+#else /*MACH_KERNEL*/
+ if (tgt->ior)
+ fdma_map(&tgt->fdma, tgt->ior);
+#endif /*MACH_KERNEL*/
+
+ if ((tgt->cur_cmd == SCSI_CMD_WRITE) ||
+ (tgt->cur_cmd == SCSI_CMD_LONG_WRITE)){
+ io_req_t ior = tgt->ior;
+ register int len = ior->io_count;
+
+ tgt->transient_state.out_count = len;
+ tgt->transient_state.copy_count = 0;
+
+ if (len < tgt->block_size) {
+ gimmeabreak();
+
+ /* avoid leaks */
+#if 0
+you`ll have to special case this
+#endif
+ tgt->transient_state.out_count = tgt->block_size;
+ }
+ } else {
+ tgt->transient_state.out_count = 0;
+ tgt->transient_state.copy_count = 0;
+ }
+
+ tgt->transient_state.cmd_count = cmd_count;
+
+ disconn =
+ BGET(scsi_might_disconnect,(unsigned)tgt->masterno, tgt->target_id);
+ disconn = disconn && (spc->ntargets > 1);
+ disconn |=
+ BGET(scsi_should_disconnect,(unsigned)tgt->masterno, tgt->target_id);
+
+ /*
+ * Setup target state
+ */
+ tgt->done = SCSI_RET_IN_PROGRESS;
+
+ handler = (disconn) ? spc_err_disconn : spc_err_generic;
+
+ /* determine wether or not to use the late forms of the scripts */
+ late = cmd_only ? FALSE : (tgt->flags & TGT_DID_SYNCH);
+
+ switch (tgt->cur_cmd) {
+ case SCSI_CMD_READ:
+ case SCSI_CMD_LONG_READ:
+ LOG(0x13,"readop");
+ scp = late ? spc_script_late_data_in : spc_script_data_in;
+ break;
+ case SCSI_CMD_WRITE:
+ case SCSI_CMD_LONG_WRITE:
+ LOG(0x14,"writeop");
+ scp = late ? spc_script_late_data_out : spc_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 = spc_script_try_synch;
+ tgt->flags |= TGT_TRY_SYNCH;
+ 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 0xc6: /* despised: SCSI_CMD_TOSHIBA_READ_SUBCH_Q */
+ case 0xc7: /* despised: SCSI_CMD_TOSHIBA_READ_TOC_ENTRY */
+ case 0xdd: /* despised: SCSI_CMD_NEC_READ_SUBCH_Q */
+ case 0xde: /* despised: SCSI_CMD_NEC_READ_TOC */
+ scp = late ? spc_script_late_data_in : spc_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 = late ? spc_script_late_data_out : spc_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 = late ? spc_script_late_cmd : spc_script_cmd;
+ } else {
+ scp = spc_script_try_synch;
+ tgt->flags |= TGT_TRY_SYNCH;
+ cmd_only = FALSE;
+ }
+ LOG(0x1c,"cmdop");
+ LOG(0x80+tgt->cur_cmd,0);
+ break;
+ default:
+ LOG(0x1c,"cmdop");
+ LOG(0x80+tgt->cur_cmd,0);
+ scp = late ? spc_script_late_cmd : spc_script_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;
+ tgt->transient_state.dma_offset = 0;
+
+ /*
+ * See if another target is currently selected on
+ * this SCSI bus, e.g. lock the spc structure.
+ * Note that it is the strategy routine's job
+ * to serialize ops on the same target as appropriate.
+ */
+#if 0
+locking code here
+#endif
+ s = splbio();
+
+ if (spc->wd.nactive++ == 0)
+ spc->wd.watchdog_state = SCSI_WD_ACTIVE;
+
+ if (spc->state & SPC_STATE_BUSY) {
+ /*
+ * Queue up this target, note that this takes care
+ * of proper FIFO scheduling of the scsi-bus.
+ */
+ LOG(3,"enqueue");
+ enqueue_tail(&spc->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.
+ */
+ spc->state |= SPC_STATE_BUSY;
+ spc->next_target = tgt;
+ spc_attempt_selection(spc);
+ /*
+ * Note that we might still lose arbitration..
+ */
+ }
+ splx(s);
+}
+
+void spc_attempt_selection(spc)
+ spc_softc_t spc;
+{
+ target_info_t *tgt;
+ spc_regmap_t *regs;
+ register int cmd;
+ int atn=0;
+
+ /* This is about your select code */
+
+ regs = spc->regs;
+ tgt = spc->next_target;
+
+ LOG(4,"select");
+ LOG(0x80+tgt->target_id,0);
+
+ /*
+ * Init bus state variables and set registers.
+ */
+ spc->active_target = tgt;
+
+ /* reselection pending ? */
+ if ((regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL))
+#ifdef MIPS
+ && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL))
+ && (regs->spc_phase & (SPC_BUS_BSY|SPC_BUS_SEL))
+#endif
+ )
+ return;
+
+ spc->script = tgt->transient_state.script;
+ spc->error_handler = tgt->transient_state.handler;
+ spc->done = SCSI_RET_IN_PROGRESS;
+
+ spc->in_count = 0;
+ spc->out_count = 0;
+
+ cmd = SPC_SCMD_C_SELECT | SPC_SCMD_PROGRAMMED_X;
+ if (tgt->flags & TGT_DID_SYNCH)
+ {
+ if (tgt->transient_state.identify != 0xff)
+ atn = 1;
+ }
+ else
+ if (tgt->flags & TGT_TRY_SYNCH)
+ atn = 1;
+
+#if 0
+ regs->spc_psns = SPC_DIAG_ENBL_XFER;
+#endif
+
+ regs->spc_sctl = SPC_SCTL_ARB_EBL | SPC_SCTL_PAR_EBL |
+ SPC_SCTL_RSEL_EBL | SPC_SCTL_IE;
+
+
+ { int mask;
+ mask = ~SPC_PCTL_BFREE_IE & regs->spc_pctl;
+ regs->spc_pctl = mask;
+ }
+
+ regs->spc_temp = (1<<(spc->sc->initiator_id)) | (1<<(tgt->target_id));
+
+ SPC_TC_PUT(regs,0xfa004);
+
+ if (atn)
+ {
+ regs->spc_scmd = SPC_SCMD_C_ATN_S;
+ /* delay 1us to avoid races */
+ delay(1);
+ }
+
+ regs->spc_scmd = cmd;
+ return;
+}
+
+/*
+ * Interrupt routine
+ * Take interrupts from the chip
+ *
+ * Implementation:
+ * Move along the current command's script if
+ * all is well, invoke error handler if not.
+ */
+void spc_intr(unit)
+int unit;
+{
+ register spc_softc_t spc;
+ register script_t scp;
+ register unsigned ints, psns, ssts;
+ register spc_regmap_t *regs;
+ boolean_t try_match;
+#if notyet
+ extern boolean_t rz_use_mapped_interface;
+
+ if (rz_use_mapped_interface)
+ {
+ SPC_intr(unit);
+ return;
+ }
+#endif
+
+ spc = spc_softc[unit];
+ regs = spc->regs;
+
+ /* read the interrupt status register */
+ ints = regs->spc_ints;
+
+ LOG(5,"\n\tintr");
+ LOG(0x80+ints,0);
+
+TR(ints);
+TRCHECK;
+
+ if (ints & SPC_INTS_RESET)
+ {
+ /* does its own interrupt reset when ready */
+ spc_bus_reset(spc);
+ return;
+ }
+
+ /* we got an interrupt allright */
+ if (spc->active_target)
+ spc->wd.watchdog_state = SCSI_WD_ACTIVE;
+
+
+ if (ints == 0)
+ { /* no obvious cause */
+ LOG(2,"SPURIOUS");
+ gimmeabreak();
+ return;
+ }
+
+
+ /* reset the interrupt */
+ regs->spc_ints = ints;
+
+ /* go get the phase, and status. We can't trust the
+ phase until REQ is asserted in the psns. Only do
+ this is we received a command complete or service
+ required interrupt. Otherwise, just read them once
+ and trust. */
+
+
+
+ if (ints & (SPC_INTS_DONE|SPC_INTS_BUSREQ))
+ while(1)
+ {
+ psns = regs->spc_psns;
+ if (psns & SPC_BUS_REQ)
+ break;
+ delay(1); /* don't hog the bus */
+ }
+ else
+ psns = regs->spc_psns;
+
+ ssts = regs->spc_psns;
+
+TR(psns);
+TR(ssts);
+TRCHECK;
+
+ if ((spc->state & SPC_STATE_TARGET) ||
+ (ints & SPC_INTS_SELECTED))
+ spc_target_intr(spc /**, ints, psns, ssts **/);
+
+ scp = spc->script;
+
+ if ((scp == 0) || (ints & SPC_INTS_RESELECTED))
+ {
+ gimmeabreak();
+ spc_reconnect(spc, ints, psns, ssts);
+ return;
+ }
+
+ if (SCRIPT_MATCH(psns) != scp->condition) {
+ if (try_match = (*spc->error_handler)(spc, ints, psns, ssts)) {
+ psns = regs->spc_psns;
+ ssts = regs->spc_ssts;
+ }
+ } else
+ try_match = TRUE;
+
+
+ /* might have been side effected */
+ scp = spc->script;
+
+ if (try_match && (SCRIPT_MATCH(psns) == scp->condition)) {
+ /*
+ * Perform the appropriate operation,
+ * then proceed
+ */
+ if ((*scp->action)(spc, ints, psns, ssts)) {
+ /* might have been side effected */
+ scp = spc->script;
+ spc->script = scp + 1;
+ }
+ }
+}
+
+void spc_target_intr(spc)
+ register spc_softc_t spc;
+{
+ panic("SPC: TARGET MODE !!!\n");
+}
+
+/*
+ * All the many little things that the interrupt
+ * routine might switch to
+ */
+boolean_t
+spc_issue_command(spc, ints, psns, ssts)
+ spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register spc_regmap_t *regs = spc->regs;
+
+ LOG(0x12, "cmd_issue");
+ /* we have just done a select;
+ Bus is in CMD phase;
+ need to phase match */
+ SPC_ACK(regs, SCSI_PHASE_CMD);
+
+ return spc_data_out(regs, SCSI_PHASE_CMD,
+ spc->active_target->transient_state.cmd_count,
+ spc->active_target->cmd_ptr) ? FALSE : TRUE;
+}
+
+boolean_t
+spc_issue_ident_and_command(spc, ints, psns, ssts)
+ spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register spc_regmap_t *regs = spc->regs;
+
+ LOG(0x22, "ident_and_cmd");
+ /* we have just done a select with atn Bus is in MSG_OUT phase;
+ need to phase match */
+ SPC_ACK(regs, SCSI_PHASE_MSG_OUT);
+
+ spc_data_out(regs, SCSI_PHASE_MSG_OUT, 1,
+ &spc->active_target->transient_state.identify);
+
+ /* wait to go to command phase */
+ SPC_WAIT_PHASE(SCSI_PHASE_CMD);
+
+ /* ack */
+ SPC_ACK(regs, SCSI_PHASE_CMD);
+
+ /* should be a command complete intr pending. Eat it */
+ if (regs->spc_ints != SPC_INTS_DONE)
+ gimmeabreak();
+ regs->spc_ints = SPC_INTS_DONE;
+
+ /* spit */
+ return spc_data_out(regs, SCSI_PHASE_CMD,
+ spc->active_target->transient_state.cmd_count,
+ spc->active_target->cmd_ptr) ? FALSE : TRUE;
+}
+
+
+boolean_t
+spc_end_transaction( spc, ints, psns, serr)
+ register spc_softc_t spc;
+ int ints, psns, serr;
+{
+ register spc_regmap_t *regs = spc->regs;
+ char cmc;
+ int tmp;
+
+ LOG(0x1f,"end_t");
+
+ SPC_ACK(regs,SCSI_PHASE_MSG_IN /*,1*/);
+
+ spc_data_in(regs, SCSI_PHASE_MSG_IN, 1, &cmc);
+
+ if (cmc != SCSI_COMMAND_COMPLETE)
+ printf("{T%x}", cmc);
+
+ while (regs->spc_ints != (SPC_INTS_DONE|SPC_INTS_DISC));
+
+ SPC_ACK(regs,SPC_PHASE_DISC);
+
+ /* going to disconnect */
+ regs->spc_pctl = ~SPC_PCTL_BFREE_IE & SPC_PHASE_DISC;
+ /* regs->spc_scmd = 0; */
+
+ /* clear all intr bits? */
+ tmp = regs->spc_ints;
+ regs->spc_ints = tmp;
+
+
+ if (!spc_end(spc, ints, psns, serr))
+ (void) spc_reconnect(spc, ints, psns, serr);
+ return FALSE;
+}
+
+boolean_t
+spc_end( spc, ints, psns, serr)
+ register spc_softc_t spc;
+ int ints, psns, serr;
+{
+ register target_info_t *tgt;
+ register io_req_t ior;
+ register spc_regmap_t *regs = spc->regs;
+ int csr;
+
+ LOG(6,"end");
+
+ tgt = spc->active_target;
+
+ if ((tgt->done = spc->done) == SCSI_RET_IN_PROGRESS)
+ tgt->done = SCSI_RET_SUCCESS;
+
+ spc->script = 0;
+
+ if (spc->wd.nactive-- == 1)
+ spc->wd.watchdog_state = SCSI_WD_INACTIVE;
+
+ /* check reconnection not pending */
+ csr = SPC_INTS_RESELECTED & regs->spc_ints;
+ if (!csr)
+ spc_release_bus(spc);
+ else
+ {
+ spc->active_target = 0;
+ /* spc->state &= ~SPC_STATE_BUSY; later */
+ }
+ if (ior = tgt->ior) {
+#ifdef MACH_KERNEL
+#else /*MACH_KERNEL*/
+ fdma_unmap(&tgt->fdma, ior);
+#endif /*MACH_KERNEL*/
+ LOG(0xA,"ops->restart");
+ (*tgt->dev_ops->restart)( tgt, TRUE);
+ if (csr)
+ spc->state &= ~SPC_STATE_BUSY;
+ }
+
+ /* return not reselected */
+ return (csr & SPC_INTS_RESELECTED) ? 0 : 1;
+}
+
+boolean_t
+spc_release_bus(spc)
+ register spc_softc_t spc;
+{
+ boolean_t ret = FALSE;
+
+ LOG(9,"release");
+
+ spc->script = 0;
+
+ if (spc->state & SPC_STATE_COLLISION) {
+
+ LOG(0xB,"collided");
+ spc->state &= ~SPC_STATE_COLLISION;
+ spc_attempt_selection(spc);
+
+ } else if (queue_empty(&spc->waiting_targets)) {
+
+ spc->state &= ~SPC_STATE_BUSY;
+ spc->active_target = 0;
+ ret = TRUE;
+
+ } else {
+
+ LOG(0xC,"dequeue");
+ spc->next_target = (target_info_t *)
+ dequeue_head(&spc->waiting_targets);
+ spc_attempt_selection(spc);
+ }
+ return ret;
+}
+
+boolean_t
+spc_get_status( spc, ints, psns, serr)
+ register spc_softc_t spc;
+ int ints, psns, serr;
+{
+ register spc_regmap_t *regs = spc->regs;
+ scsi2_status_byte_t status;
+ register target_info_t *tgt;
+
+ LOG(0xD,"get_status");
+TRWRAP;
+
+ spc->state &= ~SPC_STATE_DMA_IN;
+
+ tgt = spc->active_target;
+
+ SPC_ACK(regs,SCSI_PHASE_STATUS /*,1*/);
+
+ spc_data_in(regs, SCSI_PHASE_STATUS, 1, &status.bits);
+
+ if (status.st.scsi_status_code != SCSI_ST_GOOD) {
+ scsi_error(spc->active_target, SCSI_ERR_STATUS, status.bits, 0);
+ spc->done = (status.st.scsi_status_code == SCSI_ST_BUSY) ?
+ SCSI_RET_RETRY : SCSI_RET_NEED_SENSE;
+ } else
+ spc->done = SCSI_RET_SUCCESS;
+
+ return TRUE;
+}
+
+boolean_t
+spc_xfer_in( spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register target_info_t *tgt;
+ register spc_regmap_t *regs = spc->regs;
+ register int count;
+ boolean_t advance_script = TRUE;
+
+ LOG(0xE,"xfer_in");
+
+ tgt = spc->active_target;
+ spc->state |= SPC_STATE_DMA_IN;
+
+ count = tgt->transient_state.in_count;
+
+ SPC_ACK(regs, SCSI_PHASE_DATAI);
+
+ if ((tgt->cur_cmd != SCSI_CMD_READ) &&
+ (tgt->cur_cmd != SCSI_CMD_LONG_READ))
+ spc_data_in(regs, SCSI_PHASE_DATAI, count, tgt->cmd_ptr);
+ else
+ {
+ spc_data_in(regs, SCSI_PHASE_DATAI, count, tgt->ior->io_data);
+ }
+
+ return advance_script;
+}
+
+boolean_t
+spc_xfer_out( spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register spc_regmap_t *regs = spc->regs;
+ register target_info_t *tgt;
+ boolean_t advance_script = TRUE;
+ int count = spc->out_count;
+
+ LOG(0xF,"xfer_out");
+
+ tgt = spc->active_target;
+ spc->state &= ~SPC_STATE_DMA_IN;
+
+ count = tgt->transient_state.out_count;
+
+ SPC_ACK(regs, SCSI_PHASE_DATAO);
+
+ if ((tgt->cur_cmd != SCSI_CMD_WRITE) &&
+ (tgt->cur_cmd != SCSI_CMD_LONG_WRITE))
+ spc_data_out(regs, SCSI_PHASE_DATAO, count,
+ tgt->cmd_ptr + tgt->transient_state.cmd_count);
+ else
+ spc_data_out(regs, SCSI_PHASE_DATAO, count, tgt->ior->io_data);
+
+ return advance_script;
+}
+
+/* disconnect-reconnect ops */
+
+/* get the message in via dma ?? */
+boolean_t
+spc_msg_in(spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register target_info_t *tgt;
+
+ LOG(0x15,"msg_in");
+ gimmeabreak();
+
+ tgt = spc->active_target;
+
+#if 0
+You can do this by hand, just leave an interrupt pending at the end
+#endif
+
+ /* We only really expect two bytes */
+#if 0
+ SPC_PUT(dmar,sizeof(scsi_command_group_0));
+ ....
+#endif
+ return TRUE;
+}
+
+/* check the message is indeed a DISCONNECT */
+boolean_t
+spc_disconnect(spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register int len = 0;
+ boolean_t ok = FALSE;
+ register char *msgs = 0;
+
+
+/* SPC_TC_GET(dmar,len); */
+ len = sizeof(scsi_command_group_0) - len;
+
+/* msgs = tgt->cmd_ptr; */ /* I think */
+
+ if ((len == 0) || (len > 2) || msgs == 0)
+ ok = FALSE;
+ else {
+ /* A SDP message preceeds it in non-completed READs */
+ ok = ((msgs[0] == SCSI_DISCONNECT) || /* completed op */
+ ((msgs[0] == SCSI_SAVE_DATA_POINTER) && /* incomplete */
+ (msgs[1] == SCSI_DISCONNECT)));
+ }
+ if (!ok)
+ printf("[tgt %d bad msg (%d): %x]",
+ spc->active_target->target_id, len, *msgs);
+
+ return TRUE;
+}
+
+/* save all relevant data, free the BUS */
+boolean_t
+spc_disconnected(spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register target_info_t *tgt;
+
+/* make sure reselects will work */
+
+ LOG(0x16,"disconnected");
+
+ spc_disconnect(spc,ints, psns, ssts);
+
+ tgt = spc->active_target;
+ tgt->flags |= TGT_DISCONNECTED;
+ tgt->transient_state.handler = spc->error_handler;
+ /* the rest has been saved in spc_err_disconn() */
+
+ PRINT(("{D%d}", tgt->target_id));
+
+ spc_release_bus(spc);
+
+ return FALSE;
+}
+
+/* get reconnect message, restore BUS */
+boolean_t
+spc_reconnect(spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+
+ LOG(0x17,"reconnect");
+
+ if (spc->wd.nactive == 0) {
+ LOG(2,"SPURIOUS");
+ return FALSE;
+ }
+
+#if 0
+This is the 5380 code, for reference:
+ spc_regmap_t *regs = spc->regs;
+ register target_info_t *tgt;
+ register int id;
+ int msg;
+
+
+ id = regs->spc_data;/*parity?*/
+ /* xxx check our id is in there */
+
+ id &= ~(1 << spc->sc->initiator_id);
+ {
+ register int i;
+ for (i = 0; i < 8; i++)
+ if (id & (1 << i)) break;
+if (i == 8) {printf("{P%x}", id);return;}
+ id = i;
+ }
+ regs->spc_icmd = SPC_ICMD_BSY;
+ while (regs->spc_bus_csr & SPC_BUS_SEL)
+ ;
+ regs->spc_icmd = 0;
+ delay_1p2_us();
+ while ( ((regs->spc_bus_csr & SPC_BUS_BSY) == 0) &&
+ ((regs->spc_bus_csr & SPC_BUS_BSY) == 0) &&
+ ((regs->spc_bus_csr & SPC_BUS_BSY) == 0))
+ ;
+
+ /* Now should wait for correct phase: REQ signals it */
+ while ( ((regs->spc_bus_csr & SPC_BUS_REQ) == 0) &&
+ ((regs->spc_bus_csr & SPC_BUS_REQ) == 0) &&
+ ((regs->spc_bus_csr & SPC_BUS_REQ) == 0))
+ ;
+
+ regs->spc_mode |= SPC_MODE_MONBSY;
+
+ /*
+ * See if this reconnection collided with a selection attempt
+ */
+ if (spc->state & SPC_STATE_BUSY)
+ spc->state |= SPC_STATE_COLLISION;
+
+ spc->state |= SPC_STATE_BUSY;
+
+ /* Get identify msg */
+ bs = regs->spc_phase;
+if (SPC_CUR_PHASE(bs) != SCSI_PHASE_MSG_IN) gimmeabreak();
+ SPC_ACK(regs,SCSI_PHASE_MSG_IN /*,1*/);
+ msg = 0;
+ spc_data_in(regs, SCSI_PHASE_MSG_IN, 1, &msg);
+ regs->spc_mode = SPC_MODE_PAR_CHK|SPC_MODE_DMA|SPC_MODE_MONBSY;
+
+ if (msg != SCSI_IDENTIFY)
+ printf("{I%x %x}", id, msg);
+
+ tgt = spc->sc->target[id];
+ if (id > 7 || tgt == 0) panic("spc_reconnect");
+
+ PRINT(("{R%d}", id));
+ if (spc->state & SPC_STATE_COLLISION)
+ PRINT(("[B %d-%d]", spc->active_target->target_id, id));
+
+ LOG(0x80+id,0);
+
+ spc->active_target = tgt;
+ tgt->flags &= ~TGT_DISCONNECTED;
+
+ spc->script = tgt->transient_state.script;
+ spc->error_handler = tgt->transient_state.handler;
+ spc->in_count = 0;
+ spc->out_count = 0;
+
+ /* Should get a phase mismatch when tgt changes phase */
+#endif
+ return TRUE;
+}
+
+
+
+/* do the synch negotiation */
+boolean_t
+spc_dosynch( spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ /*
+ * Phase is MSG_OUT here, cmd has not been xferred
+ */
+ int len;
+ register target_info_t *tgt;
+ register spc_regmap_t *regs = spc->regs;
+ unsigned char off;
+ unsigned char p[6];
+
+ LOG(0x11,"dosync");
+
+ /* ATN still asserted */
+ SPC_ACK(regs,SCSI_PHASE_MSG_OUT);
+
+ tgt = spc->active_target;
+
+ tgt->flags |= TGT_DID_SYNCH; /* only one chance */
+ tgt->flags &= ~TGT_TRY_SYNCH;
+
+ /*p = some scratch buffer, on the stack */
+
+ p[0] = SCSI_IDENTIFY;
+ p[1] = SCSI_EXTENDED_MESSAGE;
+ p[2] = 3;
+ p[3] = SCSI_SYNC_XFER_REQUEST;
+ /* We cannot run synchronous */
+#define spc_to_scsi_period(x) 0x7
+#define scsi_period_to_spc(x) (x)
+ off = 0;
+ p[4] = spc_to_scsi_period(spc_min_period);
+ p[5] = off;
+
+ /* The transfer is started with ATN still set. The
+ chip will automagically drop ATN before it transfers the
+ last byte. Pretty neat. */
+ spc_data_out(regs, SCSI_PHASE_MSG_OUT,
+ sizeof(scsi_synch_xfer_req_t)+1, p);
+
+ /* wait for phase change to status phase */
+ SPC_WAIT_PHASE_VANISH(SCSI_PHASE_MSG_OUT);
+
+
+ psns = regs->spc_phase;
+
+ /* The standard sez there nothing else the target can do but.. */
+ if (SPC_CUR_PHASE(psns) != SCSI_PHASE_MSG_IN)
+ panic("spc_dosync");/* XXX put offline */
+
+ /*
+ msgin:
+ */
+ /* ack */
+ SPC_ACK(regs,SCSI_PHASE_MSG_IN);
+
+ /* clear any pending interrupts */
+ regs->spc_ints = regs->spc_ints;
+
+ /* get answer */
+ len = sizeof(scsi_synch_xfer_req_t);
+ len = spc_data_in(regs, SCSI_PHASE_MSG_IN, len, p);
+
+ /* do not cancel the phase mismatch interrupt ! */
+
+ /* look at the answer and see if we like it */
+ if (len || (p[0] != SCSI_EXTENDED_MESSAGE)) {
+ /* did not like it at all */
+ printf(" did not like SYNCH xfer ");
+ } else {
+ /* will NOT do synch */
+ printf(" but we cannot do SYNCH xfer ");
+ tgt->sync_period = scsi_period_to_spc(p[3]);
+ tgt->sync_offset = p[4];
+ /* sanity */
+ if (tgt->sync_offset != 0)
+ printf(" ?OFFSET %x? ", tgt->sync_offset);
+ }
+
+ /* wait for phase change */
+ SPC_WAIT_PHASE_VANISH(SCSI_PHASE_MSG_IN);
+
+ psns = regs->spc_phase;
+
+ /* phase should be command now */
+ /* continue with simple command script */
+ spc->error_handler = spc_err_generic;
+ spc->script = spc_script_cmd;
+
+/* Make sure you get out right here, esp the script pointer and/or pending intr */
+
+ if (SPC_CUR_PHASE(psns) == SCSI_PHASE_CMD )
+ return FALSE;
+
+ if (SPC_CUR_PHASE(psns) == SCSI_PHASE_STATUS ) /* jump to get_status */
+ return TRUE; /* intr is pending */
+
+ spc->script++;
+ if (SPC_CUR_PHASE(psns) == SCSI_PHASE_MSG_IN )
+ return TRUE;
+
+ if ((psns & SPC_BUS_BSY) == 0) /* uhu? disconnected */
+ return TRUE;
+
+ gimmeabreak();
+ return FALSE;
+}
+
+/*
+ * The bus was reset
+ */
+void spc_bus_reset(spc)
+ register spc_softc_t spc;
+{
+ register spc_regmap_t *regs = spc->regs;
+
+ LOG(0x21,"bus_reset");
+
+ /*
+ * Clear bus descriptor
+ */
+ spc->script = 0;
+ spc->error_handler = 0;
+ spc->active_target = 0;
+ spc->next_target = 0;
+ spc->state = 0;
+ queue_init(&spc->waiting_targets);
+ spc->wd.nactive = 0;
+ spc_reset(regs, TRUE);
+
+ printf("spc%d: (%d) bus reset ", spc->sc->masterno, ++spc->wd.reset_count);
+ delay(scsi_delay_after_reset); /* some targets take long to reset */
+
+ if (spc->sc == 0) /* sanity */
+ return;
+
+ scsi_bus_was_reset(spc->sc);
+}
+
+/*
+ * Error handlers
+ */
+
+/*
+ * Generic, default handler
+ */
+boolean_t
+spc_err_generic(spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ register spc_regmap_t *regs = spc->regs;
+ LOG(0x10,"err_generic");
+
+ if (ints & SPC_INTS_TIMEOUT) /* we timed out */
+ if ((regs->spc_scmd & SPC_SCMD_CMDMASK) == SPC_SCMD_C_SELECT)
+ {
+ /* Powered off ? */
+ if (spc->active_target->flags & TGT_FULLY_PROBED)
+ {
+ spc->active_target->flags = 0;
+ LOG(0x1e,"Device Down");
+ }
+ spc->done = SCSI_RET_DEVICE_DOWN;
+ spc_end(spc, ints, psns, ssts);
+ return FALSE; /* don't retry - just report missing device */
+ }
+ else
+ { /* timed out - but not on a select. What is going on? */
+ gimmeabreak();
+ }
+
+ if (SPC_CUR_PHASE(psns) == SCSI_PHASE_STATUS)
+ return spc_err_to_status(spc, ints, psns, ssts);
+ gimmeabreak();
+ return FALSE;
+}
+
+/*
+ * Handle generic errors that are reported as
+ * an unexpected change to STATUS phase
+ */
+boolean_t
+spc_err_to_status(spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+ script_t scp = spc->script;
+
+ LOG(0x20,"err_tostatus");
+ while (SCSI_PHASE(scp->condition) != SCSI_PHASE_STATUS)
+ scp++;
+ spc->script = scp;
+#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 -> reconnect
+ * and our script might expect at this point that the dma
+ * had to be restarted (it didn't know it was completed
+ * because the tape record is shorter than we asked for).
+ * And in any event.. it is both correct and cleaner to
+ * declare error iff the STATUS byte says so.
+ */
+ spc->done = SCSI_RET_NEED_SENSE;
+#endif
+ return TRUE;
+}
+
+/*
+ * Watch for a disconnection
+ */
+boolean_t
+spc_err_disconn(spc, ints, psns, ssts)
+ register spc_softc_t spc;
+ int ints, psns, ssts;
+{
+#if 1
+/*
+ * THIS ROUTINE CAN'T POSSIBLY WORK...
+ * FOR EXAMPLE, THE VARIABLE 'xferred' IS NEVER INITIALIZED.
+ */
+ return FALSE;
+#else
+ register spc_regmap_t *regs;
+ register target_info_t *tgt;
+ int xferred;
+
+ LOG(0x18,"err_disconn");
+
+ if (SPC_CUR_PHASE(ints) != SCSI_PHASE_MSG_IN)
+ return spc_err_generic(spc, ints, psns, ssts);
+
+ regs = spc->regs;
+
+ tgt = spc->active_target;
+
+ switch (SCSI_PHASE(spc->script->condition)) {
+ case SCSI_PHASE_DATAO:
+ LOG(0x1b,"+DATAO");
+/*updatecounters:*/
+ tgt->transient_state.out_count -= xferred;
+ assert(tgt->transient_state.out_count > 0);
+ tgt->transient_state.dma_offset += xferred;
+
+ tgt->transient_state.script = spc_script_data_out;
+ break;
+
+ case SCSI_PHASE_DATAI:
+ LOG(0x19,"+DATAI");
+
+/*update counters: */
+ assert(xferred > 0);
+ tgt->transient_state.in_count -= xferred;
+ assert(tgt->transient_state.in_count > 0);
+ tgt->transient_state.dma_offset += xferred;
+
+ tgt->transient_state.script = spc_script_data_in;
+ break;
+
+ case SCSI_PHASE_STATUS:
+
+ if (spc->state & SPC_STATE_DMA_IN) {
+
+ LOG(0x1a,"+STATUS+R");
+
+/*same as above.. */
+ assert(xferred > 0);
+ tgt->transient_state.in_count -= xferred;
+/* assert(tgt->transient_state.in_count > 0);*/
+ tgt->transient_state.dma_offset += xferred;
+
+ tgt->transient_state.script = spc_script_data_in;
+ if (tgt->transient_state.in_count == 0)
+ tgt->transient_state.script++;
+
+ } else {
+
+ LOG(0x1d,"+STATUS+W");
+
+ if ((tgt->transient_state.out_count == spc->out_count)) {
+ /* all done */
+ tgt->transient_state.script = &spc_script_data_out[1];
+ tgt->transient_state.out_count = 0;
+ } else {
+
+/*.. */
+ tgt->transient_state.out_count -= xferred;
+ assert(tgt->transient_state.out_count > 0);
+ tgt->transient_state.dma_offset += xferred;
+
+ tgt->transient_state.script = spc_script_data_out;
+ }
+ spc->out_count = 0;
+ }
+ break;
+ default:
+ gimmeabreak();
+ }
+ /* spc->xxx = 0; */
+
+/* SPC_ACK(regs,SCSI_PHASE_MSG_IN); later */
+ (void) spc_msg_in(spc, ints, psns, ssts);
+
+ spc->script = spc_script_disconnect;
+
+ return FALSE;
+#endif
+}
+
+/*
+ * Watchdog
+ *
+ */
+void spc_reset_scsibus(spc)
+ register spc_softc_t spc;
+{
+ register target_info_t *tgt = spc->active_target;
+ if (tgt) {
+ int cnt = 0;
+ /* SPC_TC_GET(spc->dmar,cnt); */
+ log( LOG_KERN,
+ "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,
+ spc->in_count, spc->out_count, cnt);
+ }
+#if 0
+ spc->regs->.....
+#endif
+ delay(25);
+}
+
+int SPC_ACK(regs, phase)
+register spc_regmap_t *regs;
+unsigned phase;
+{
+ /* we want to switch into the specified phase -
+
+ The calling routine should already dismissed
+ any pending interrupts (spc_ints)
+ */
+
+ regs->spc_psns = 0;
+ regs->spc_pctl = phase | SPC_PCTL_BFREE_IE;
+ return 0;
+}
+#endif /*NSCSI > 0*/
+
+#endif 0