diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1996-04-24 11:09:01 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1996-04-24 11:09:01 +0000 |
commit | 6bbb90fd25d2dcd7a4acdc8969a442fd71b98a06 (patch) | |
tree | da1eb150bc647bb89c88e694d8714e5193738055 /sys/arch/arm32/podulebus/ncr5380sbc.c | |
parent | 00287be7e84692788588bfeb60508f1022602abb (diff) |
Initial import of arm32 port
Diffstat (limited to 'sys/arch/arm32/podulebus/ncr5380sbc.c')
-rw-r--r-- | sys/arch/arm32/podulebus/ncr5380sbc.c | 2591 |
1 files changed, 2591 insertions, 0 deletions
diff --git a/sys/arch/arm32/podulebus/ncr5380sbc.c b/sys/arch/arm32/podulebus/ncr5380sbc.c new file mode 100644 index 00000000000..9758419156e --- /dev/null +++ b/sys/arch/arm32/podulebus/ncr5380sbc.c @@ -0,0 +1,2591 @@ +/* $NetBSD: ncr5380sbc.c,v 1.2 1996/03/27 22:05:19 mark Exp $ */ + +/* + * Copyright (c) 1996 Melvin Tang-Richardson (Modified for weird regs) + * Copyright (c) 1995 David Jones, Gordon W. Ross + * Copyright (c) 1994 Jarle Greipsland + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the authors may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * 4. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by + * David Jones and Gordon Ross + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#undef DIAGNOSTIC + +/* + * This is a machine-independent driver for the NCR5380 + * SCSI Bus Controller (SBC), also known as the Am5380. + * + * This code should work with any memory-mapped 5380, + * and can be shared by multiple adapters that address + * the 5380 with different register offset spacings. + * (This can happen on the atari, for example.) + * For porting/design info. see: ncr5380.doc + * + * Credits, history: + * + * David Jones is the author of most of the code that now + * appears in this file, and was the architect of the + * current overall structure (MI/MD code separation, etc.) + * + * Gordon Ross integrated the message phase code, added lots of + * comments about what happens when and why (re. SCSI spec.), + * debugged some reentrance problems, and added several new + * "hooks" needed for the Sun3 "si" adapters. + * + * The message in/out code was taken nearly verbatim from + * the aic6360 driver by Jarle Greipsland. + * + * Several other NCR5380 drivers were used for reference + * while developing this driver, including work by: + * The Alice Group (mac68k port) namely: + * Allen K. Briggs, Chris P. Caputo, Michael L. Finch, + * Bradley A. Grantham, and Lawrence A. Kesteloot + * Michael L. Hitch (amiga drivers: sci.c) + * Leo Weppelman (atari driver: ncr5380.c) + * There are others too. Thanks, everyone. + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/device.h> +#include <sys/buf.h> +#include <sys/proc.h> +#include <sys/user.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsi_debug.h> +#include <scsi/scsi_message.h> +#include <scsi/scsiconf.h> + +#define DEBUG XXX + +#define SetReg(l,d) *((volatile unsigned int *)((void *)l))=((d)|(((d)<<16))) + +#if 0 /* XXX - not yet... */ +#include <dev/ic/ncr5380reg.h> +#include <dev/ic/ncr5380var.h> +#else +#include "ncr5380reg.h" +#include "ncr5380var.h" +#endif + +static int ncr5380_wait_req __P((struct ncr5380_softc *)); +static int ncr5380_wait_not_req __P((struct ncr5380_softc *)); + +static void ncr5380_sched __P((struct ncr5380_softc *)); +static void ncr5380_done __P((struct ncr5380_softc *)); + +static int ncr5380_select + __P((struct ncr5380_softc *, struct sci_req *)); +static void ncr5380_reselect __P((struct ncr5380_softc *)); + +static int ncr5380_msg_in __P((struct ncr5380_softc *)); +static int ncr5380_msg_out __P((struct ncr5380_softc *)); +static int ncr5380_data_xfer __P((struct ncr5380_softc *, int)); +static int ncr5380_command __P((struct ncr5380_softc *)); +static int ncr5380_status __P((struct ncr5380_softc *)); +static void ncr5380_machine __P((struct ncr5380_softc *)); + +/* + * Action flags returned by the info_tranfer functions: + * (These determine what happens next.) + */ +#define ACT_CONTINUE 0x00 /* No flags: expect another phase */ +#define ACT_DISCONNECT 0x01 /* Target is disconnecting */ +#define ACT_CMD_DONE 0x02 /* Need to call scsi_done() */ +#define ACT_RESET_BUS 0x04 /* Need bus reset (cmd timeout) */ +#define ACT_WAIT_DMA 0x10 /* Wait for DMA to complete */ + +/***************************************************************** + * Debugging stuff + *****************************************************************/ + +#ifndef DDB +/* This is used only in recoverable places. */ +#define Debugger() printf("Debug: ncr5380.c:%d\n", __LINE__) +#endif + +#ifdef DEBUG + +#define NCR_DBG_BREAK 1 +#define NCR_DBG_CMDS 2 +int ncr5380_debug = NCR_DBG_BREAK; +#define NCR_BREAK() \ + do { if (ncr5380_debug & NCR_DBG_BREAK) Debugger(); } while (0) +static void ncr5380_show_scsi_cmd __P((struct scsi_xfer *)); +static void ncr5380_show_sense __P((struct scsi_xfer *)); +#else /* DEBUG */ +#define NCR_BREAK() /* nada */ +#define ncr5380_show_scsi_cmd(xs) /* nada */ +#define ncr5380_show_sense(xs) /* nada */ +#endif /* DEBUG */ + +static char * +phase_names[8] = { + "DATA_OUT", + "DATA_IN", + "COMMAND", + "STATUS", + "UNSPEC1", + "UNSPEC2", + "MSG_OUT", + "MSG_IN", +}; + +/***************************************************************** + * Actual chip control + *****************************************************************/ + +/* + * XXX: These timeouts might need to be tuned... + */ + +/* This one is used when waiting for a phase change. (X100uS.) */ +int ncr5380_wait_phase_timo = 1000 * 10 * 300; /* 5 min. */ + +/* These are used in the following inline functions. */ +int ncr5380_wait_req_timo = 1000 * 50; /* X2 = 100 mS. */ +int ncr5380_wait_nrq_timo = 1000 * 25; /* X2 = 50 mS. */ + +/* Return zero on success. */ +static __inline__ int ncr5380_wait_req(sc) + struct ncr5380_softc *sc; +{ + register int timo = ncr5380_wait_req_timo; + for (;;) { + if (*sc->sci_bus_csr & SCI_BUS_REQ) { + timo = 0; /* return 0 */ + break; + } + if (--timo < 0) + break; /* return -1 */ + delay(2); + } + return (timo); +} + +/* Return zero on success. */ +static __inline__ int ncr5380_wait_not_req(sc) + struct ncr5380_softc *sc; +{ + register int timo = ncr5380_wait_nrq_timo; + for (;;) { + if ((*sc->sci_bus_csr & SCI_BUS_REQ) == 0) { + timo = 0; /* return 0 */ + break; + } + if (--timo < 0) + break; /* return -1 */ + delay(2); + } + return (timo); +} + +/* Ask the target for a MSG_OUT phase. */ +static __inline__ void +ncr_sched_msgout(sc, msg_code) + struct ncr5380_softc *sc; + int msg_code; +{ + /* First time, raise ATN line. */ + if (sc->sc_msgpriq == 0) { + register u_char icmd; + icmd = *sc->sci_icmd & SCI_ICMD_RMASK; + SetReg ( sc->sci_icmd, icmd | SCI_ICMD_ATN ); +/* *sc->sci_icmd = icmd | SCI_ICMD_ATN; */ + delay(2); + } + sc->sc_msgpriq |= msg_code; +} + + +int +ncr5380_pio_out(sc, phase, count, data) + struct ncr5380_softc *sc; + int phase, count; + unsigned char *data; +{ + register u_char icmd; + register int resid; + register int error; + + icmd = *(sc->sci_icmd) & SCI_ICMD_RMASK; + + icmd |= SCI_ICMD_DATA; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + resid = count; + while (resid > 0) { + if (!SCI_BUSY(sc)) { + NCR_TRACE("pio_out: lost BSY, resid=%d\n", resid); + break; + } + if (ncr5380_wait_req(sc)) { + NCR_TRACE("pio_out: no REQ, resid=%d\n", resid); + break; + } + if (SCI_BUS_PHASE(*sc->sci_bus_csr) != phase) + break; + + /* Put the data on the bus. */ + SetReg ( sc->sci_odata, *data ); data++; +/* *sc->sci_odata = *data++; */ + + /* Tell the target it's there. */ + icmd |= SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + /* Wait for target to get it. */ + error = ncr5380_wait_not_req(sc); + + /* OK, it's got it (or we gave up waiting). */ + icmd &= ~SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + if (error) { + NCR_TRACE("pio_out: stuck REQ, resid=%d\n", resid); + break; + } + + --resid; + } + + /* Stop driving the data bus. */ + icmd &= ~SCI_ICMD_DATA; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + return (count - resid); +} + + +int +ncr5380_pio_in(sc, phase, count, data) + struct ncr5380_softc *sc; + int phase, count; + unsigned char *data; +{ + register u_char icmd; + register int resid; + register int error; + + icmd = *(sc->sci_icmd) & SCI_ICMD_RMASK; + + resid = count; + while (resid > 0) { + if (!SCI_BUSY(sc)) { + NCR_TRACE("pio_in: lost BSY, resid=%d\n", resid); + break; + } + if (ncr5380_wait_req(sc)) { + NCR_TRACE("pio_in: no REQ, resid=%d\n", resid); + break; + } + /* A phase change is not valid until AFTER REQ rises! */ + if (SCI_BUS_PHASE(*sc->sci_bus_csr) != phase) + break; + + /* Read the data bus. */ + *data++ = *sc->sci_data; + + /* Tell target we got it. */ + icmd |= SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + /* Wait for target to drop REQ... */ + error = ncr5380_wait_not_req(sc); + + /* OK, we can drop ACK. */ + icmd &= ~SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + if (error) { + NCR_TRACE("pio_in: stuck REQ, resid=%d\n", resid); + break; + } + + --resid; + } + + return (count - resid); +} + + +void +ncr5380_init(sc) + struct ncr5380_softc *sc; +{ + int i, j; + +#ifdef DEBUG + ncr5380_debug_sc = sc; +#endif + + for (i = 0; i < SCI_OPENINGS; i++) + sc->sc_ring[i].sr_xs = NULL; + for (i = 0; i < 8; i++) + for (j = 0; j < 8; j++) + sc->sc_matrix[i][j] = NULL; + + sc->sc_link.openings = 2; /* XXX - Not SCI_OPENINGS */ + sc->sc_prevphase = PHASE_INVALID; + sc->sc_state = NCR_IDLE; + + SetReg ( sc->sci_tcmd, PHASE_INVALID ); + SetReg ( sc->sci_icmd, 0 ); + SetReg ( sc->sci_mode, 0 ); + SetReg ( sc->sci_sel_enb, 0 ); + +/* *sc->sci_tcmd = PHASE_INVALID; + *sc->sci_icmd = 0; + *sc->sci_mode = 0; + *sc->sci_sel_enb = 0; */ + SCI_CLR_INTR(sc); + + /* XXX: Enable reselect interrupts... */ + SetReg ( sc->sci_sel_enb, 0x80 ); +/* *sc->sci_sel_enb = 0x80; */ + + /* Another hack (Er.. hook!) for the sun3 si: */ + if (sc->sc_intr_on) { + NCR_TRACE("init: intr ON\n", 0); + sc->sc_intr_on(sc); + } +} + + +void +ncr5380_reset_scsibus(sc) + struct ncr5380_softc *sc; +{ + + NCR_TRACE("reset_scsibus, cur=0x%x\n", + (long) sc->sc_current); + + SetReg ( sc->sci_icmd, SCI_ICMD_RST ); +/* *sc->sci_icmd = SCI_ICMD_RST; */ + delay(500); + SetReg ( sc->sci_icmd, 0 ); +/* *sc->sci_icmd = 0; */ + + SetReg ( sc->sci_mode, 0 ); + SetReg ( sc->sci_tcmd, PHASE_INVALID ); + +/* *sc->sci_mode = 0; + *sc->sci_tcmd = PHASE_INVALID; */ + + SCI_CLR_INTR(sc); + /* XXX - Need long delay here! */ + delay(100000); + + /* XXX - Need to cancel disconnected requests. */ +} + + +/* + * Interrupt handler for the SCSI Bus Controller (SBC) + * This may also called for a DMA timeout (at splbio). + */ +int +ncr5380_intr(sc) + struct ncr5380_softc *sc; +{ + int claimed = 0; + + /* + * Do not touch SBC regs here unless sc_current == NULL + * or it will complain about "register conflict" errors. + * Instead, just let ncr5380_machine() deal with it. + */ + NCR_TRACE("intr: top, state=%d\n", sc->sc_state); + + if (sc->sc_state == NCR_IDLE) { + /* + * Might be reselect. ncr5380_reselect() will check, + * and set up the connection if so. This will verify + * that sc_current == NULL at the beginning... + */ + + /* Another hack (Er.. hook!) for the sun3 si: */ + if (sc->sc_intr_off) { + NCR_TRACE("intr: for reselect, intr off\n", 0); + sc->sc_intr_off(sc); + } + + ncr5380_reselect(sc); + } + + /* + * The remaining documented interrupt causes are phase mismatch and + * disconnect. In addition, the sunsi controller may produce a state + * where SCI_CSR_DONE is false, yet DMA is complete. + * + * The procedure in all these cases is to let ncr5380_machine() + * figure out what to do next. + */ + if (sc->sc_state & NCR_WORKING) { + NCR_TRACE("intr: call machine, cur=0x%x\n", + (long) sc->sc_current); + /* This will usually free-up the nexus. */ + ncr5380_machine(sc); + NCR_TRACE("intr: machine done, cur=0x%x\n", + (long) sc->sc_current); + claimed = 1; + } + + /* Maybe we can run some commands now... */ + if (sc->sc_state == NCR_IDLE) { + NCR_TRACE("intr: call sched, cur=0x%x\n", + (long) sc->sc_current); + ncr5380_sched(sc); + NCR_TRACE("intr: sched done, cur=0x%x\n", + (long) sc->sc_current); + } + + return claimed; +} + + +/* + * Abort the current command (i.e. due to timeout) + */ +void +ncr5380_abort(sc) + struct ncr5380_softc *sc; +{ + + /* + * Finish it now. If DMA is in progress, we + * can not call ncr_sched_msgout() because + * that hits the SBC (avoid DMA conflict). + */ + + /* Another hack (Er.. hook!) for the sun3 si: */ + if (sc->sc_intr_off) { + NCR_TRACE("abort: intr off\n", 0); + sc->sc_intr_off(sc); + } + + sc->sc_state |= NCR_ABORTING; + if ((sc->sc_state & NCR_DOINGDMA) == 0) { + ncr_sched_msgout(sc, SEND_ABORT); + } + NCR_TRACE("abort: call machine, cur=0x%x\n", + (long) sc->sc_current); + ncr5380_machine(sc); + NCR_TRACE("abort: machine done, cur=0x%x\n", + (long) sc->sc_current); + + /* Another hack (Er.. hook!) for the sun3 si: */ + if (sc->sc_intr_on) { + NCR_TRACE("abort: intr ON\n", 0); + sc->sc_intr_on(sc); + } +} + +/* + * Timeout handler, scheduled for each SCSI command. + */ +void +ncr5380_cmd_timeout(arg) + void *arg; +{ + struct sci_req *sr = arg; + struct scsi_xfer *xs; + struct scsi_link *sc_link; + struct ncr5380_softc *sc; + int s; + + s = splbio(); + + /* Get all our variables... */ + xs = sr->sr_xs; + if (xs == NULL) { + printf("ncr5380_cmd_timeout: no scsi_xfer\n"); + goto out; + } + sc_link = xs->sc_link; + sc = sc_link->adapter_softc; + + printf("%s: cmd timeout, targ=%d, lun=%d\n", + sc->sc_dev.dv_xname, + sr->sr_target, sr->sr_lun); + + /* + * Mark the overdue job as failed, and arrange for + * ncr5380_machine to terminate it. If the victim + * is the current job, call ncr5380_machine() now. + * Otherwise arrange for ncr5380_sched() to do it. + */ + sr->sr_flags |= SR_OVERDUE; + if (sc->sc_current == sr) { + NCR_TRACE("cmd_tmo: call abort, sr=0x%x\n", (long) sr); + ncr5380_abort(sc); + } else { + /* + * The driver may be idle, or busy with another job. + * Arrange for ncr5380_sched() to do the deed. + */ + NCR_TRACE("cmd_tmo: clear matrix, t/l=0x%02x\n", + (sr->sr_target << 4) | sr->sr_lun); + sc->sc_matrix[sr->sr_target][sr->sr_lun] = NULL; + } + + /* + * We may have aborted the current job, or may have + * already been idle. In either case, we should now + * be idle, so try to start another job. + */ + if (sc->sc_state == NCR_IDLE) { + NCR_TRACE("cmd_tmo: call sched, cur=0x%x\n", + (long) sc->sc_current); + ncr5380_sched(sc); + NCR_TRACE("cmd_tmo: sched done, cur=0x%x\n", + (long) sc->sc_current); + } + +out: + splx(s); +} + + +/***************************************************************** + * Interface to higher level + *****************************************************************/ + + +/* + * Enter a new SCSI command into the "issue" queue, and + * if there is work to do, start it going. + * + * WARNING: This can be called recursively! + * (see comment in ncr5380_done) + */ +int +ncr5380_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct ncr5380_softc *sc; + struct sci_req *sr; + int s, rv, i, flags; + extern int cold; /* XXX */ + + sc = xs->sc_link->adapter_softc; + + flags = xs->flags; + /* + * XXX: Hack: During autoconfig, force polling mode. + * Needed as long as sdsize() can be called while cold, + * otherwise timeouts will never call back (grumble). + */ + if (cold) + flags |= SCSI_POLL; + + if (sc->sc_flags & NCR5380_FORCE_POLLING) + flags |= SCSI_POLL; + + if (flags & SCSI_DATA_UIO) + panic("ncr5380: scsi data uio requested"); + + s = splbio(); + + if (flags & SCSI_POLL) { + /* Terminate any current command. */ + sr = sc->sc_current; + if (sr) { + printf("%s: polled request aborting %d/%d\n", + sc->sc_dev.dv_xname, + sr->sr_target, sr->sr_lun); + ncr5380_abort(sc); + } + if (sc->sc_state != NCR_IDLE) { + panic("ncr5380_scsi_cmd: polled request, abort failed"); + } + } + + /* + * Find lowest empty slot in ring buffer. + * XXX: What about "fairness" and cmd order? + */ + for (i = 0; i < SCI_OPENINGS; i++) + if (sc->sc_ring[i].sr_xs == NULL) + goto new; + + rv = TRY_AGAIN_LATER; + NCR_TRACE("scsi_cmd: no openings, rv=%d\n", rv); + goto out; + +new: + /* Create queue entry */ + sr = &sc->sc_ring[i]; + sr->sr_xs = xs; + sr->sr_target = xs->sc_link->target; + sr->sr_lun = xs->sc_link->lun; + sr->sr_dma_hand = NULL; + sr->sr_dataptr = xs->data; + sr->sr_datalen = xs->datalen; + sr->sr_flags = (flags & SCSI_POLL) ? SR_IMMED : 0; + sr->sr_status = -1; /* no value */ + sc->sc_ncmds++; + rv = SUCCESSFULLY_QUEUED; + + NCR_TRACE("scsi_cmd: new sr=0x%x\n", (long)sr); + + if (flags & SCSI_POLL) { + /* Force this new command to be next. */ + sc->sc_rr = i; + } + + /* + * If we were idle, run some commands... + */ + if (sc->sc_state == NCR_IDLE) { + NCR_TRACE("scsi_cmd: call sched, cur=0x%x\n", + (long) sc->sc_current); + ncr5380_sched(sc); + NCR_TRACE("scsi_cmd: sched done, cur=0x%x\n", + (long) sc->sc_current); + } + + if (flags & SCSI_POLL) { + /* Make sure ncr5380_sched() finished it. */ + if ((xs->flags & ITSDONE) == 0) + panic("ncr5380_scsi_cmd: poll didn't finish"); + rv = COMPLETE; + } + +out: + splx(s); + return (rv); +} + + +/* + * POST PROCESSING OF SCSI_CMD (usually current) + * Called by ncr5380_sched(), ncr5380_machine() + */ +static void +ncr5380_done(sc) + struct ncr5380_softc *sc; +{ + struct sci_req *sr; + struct scsi_xfer *xs; + +#ifdef DIAGNOSTIC + if ((getsr() & PSL_IPL) < PSL_IPL2) + panic("ncr5380_done: bad spl"); + if (sc->sc_state == NCR_IDLE) + panic("ncr5380_done: state=idle"); + if (sc->sc_current == NULL) + panic("ncr5380_done: current=0"); +#endif + + sr = sc->sc_current; + xs = sr->sr_xs; + + NCR_TRACE("done: top, cur=0x%x\n", (long) sc->sc_current); + + /* + * Clean up DMA resources for this command. + */ + if (sr->sr_dma_hand) { + NCR_TRACE("done: dma_free, dh=0x%x\n", + (long) sr->sr_dma_hand); + (*sc->sc_dma_free)(sc); + } +#ifdef DIAGNOSTIC + if (sr->sr_dma_hand) + panic("ncr5380_done: dma free did not"); +#endif + + if (sc->sc_state & NCR_ABORTING) { + NCR_TRACE("done: aborting, error=%d\n", xs->error); + if (xs->error == XS_NOERROR) + xs->error = XS_TIMEOUT; + } + + NCR_TRACE("done: check error=%d\n", (long) xs->error); + + /* If error is already set, ignore sr_status value. */ + if (xs->error != XS_NOERROR) + goto finish; + + NCR_TRACE("done: check status=%d\n", sr->sr_status); + + switch (sr->sr_status) { + case SCSI_OK: /* 0 */ + if (sr->sr_flags & SR_SENSE) { + if (ncr5380_debug & NCR_DBG_CMDS) { + ncr5380_show_sense(xs); + } + xs->error = XS_SENSE; + } + break; + + case SCSI_CHECK: + if (sr->sr_flags & SR_SENSE) { + /* Sense command also asked for sense? */ + printf("ncr5380_done: sense asked for sense\n"); + NCR_BREAK(); + xs->error = XS_DRIVER_STUFFUP; + break; + } + sr->sr_flags |= SR_SENSE; + NCR_TRACE("done: get sense, sr=0x%x\n", (long) sr); + /* + * Leave queued, but clear sc_current so we start over + * with selection. Guaranteed to get the same request. + */ + sc->sc_state = NCR_IDLE; + sc->sc_current = NULL; + sc->sc_matrix[sr->sr_target][sr->sr_lun] = NULL; + return; /* XXX */ + + case SCSI_BUSY: + xs->error = XS_BUSY; + break; + + case -1: + /* This is our "impossible" initial value. */ + /* fallthrough */ + default: + printf("%s: target %d, bad status=%d\n", + sc->sc_dev.dv_xname, sr->sr_target, sr->sr_status); + xs->error = XS_DRIVER_STUFFUP; + break; + } + +finish: + + NCR_TRACE("done: finish, error=%d\n", xs->error); + + /* + * Dequeue the finished command, but don't clear sc_state until + * after the call to scsi_done(), because that may call back to + * ncr5380_scsi_cmd() - unwanted recursion! + * + * Keeping sc->sc_state != idle terminates the recursion. + */ +#ifdef DIAGNOSTIC + if ((sc->sc_state & NCR_WORKING) == 0) + panic("ncr5380_done: bad state"); +#endif + + /* Clear our pointers to the request. */ + sc->sc_current = NULL; + sc->sc_matrix[sr->sr_target][sr->sr_lun] = NULL; + untimeout(ncr5380_cmd_timeout, sr); + + /* Make the request free. */ + sr->sr_xs = NULL; + sc->sc_ncmds--; + + /* Tell common SCSI code it is done. */ + xs->flags |= ITSDONE; + scsi_done(xs); + + sc->sc_state = NCR_IDLE; + /* Now ncr5380_sched() may be called again. */ +} + + +/* + * Schedule a SCSI operation. This routine should return + * only after it achieves one of the following conditions: + * Busy (sc->sc_state != NCR_IDLE) + * No more work can be started. + */ +static void +ncr5380_sched(sc) + struct ncr5380_softc *sc; +{ + struct sci_req *sr; + struct scsi_xfer *xs; + int target, lun; + int error, i; + +#ifdef DIAGNOSTIC + if ((getsr() & PSL_IPL) < PSL_IPL2) + panic("ncr5380_sched: bad spl"); +#endif + + /* Another hack (Er.. hook!) for the sun3 si: */ + if (sc->sc_intr_off) { + NCR_TRACE("sched: top, intr off\n", 0); + sc->sc_intr_off(sc); + } + +next_job: + /* + * Grab the next job from queue. Must be idle. + */ +#ifdef DIAGNOSTIC + if (sc->sc_state != NCR_IDLE) + panic("ncr5380_sched: not idle"); + if (sc->sc_current) + panic("ncr5380_sched: current set"); +#endif + + /* + * Always start the search where we last looked. + * The REQUEST_SENSE logic depends on this to + * choose the same job as was last picked, so it + * can just clear sc_current and reschedule. + * (Avoids loss of "contingent allegiance".) + */ + i = sc->sc_rr; + sr = NULL; + do { + if (sc->sc_ring[i].sr_xs) { + target = sc->sc_ring[i].sr_target; + lun = sc->sc_ring[i].sr_lun; + if (sc->sc_matrix[target][lun] == NULL) { + sc->sc_matrix[target][lun] = + sr = &sc->sc_ring[i]; + sc->sc_rr = i; + break; + } + } + i++; + if (i == SCI_OPENINGS) + i = 0; + } while (i != sc->sc_rr); + + if (sr == NULL) { + NCR_TRACE("sched: no work, cur=0x%x\n", + (long) sc->sc_current); + + /* Another hack (Er.. hook!) for the sun3 si: */ + if (sc->sc_intr_on) { + NCR_TRACE("sched: ret, intr ON\n", 0); + sc->sc_intr_on(sc); + } + + return; /* No more work to do. */ + } + + NCR_TRACE("sched: select for t/l=0x%02x\n", + (sr->sr_target << 4) | sr->sr_lun); + + sc->sc_state = NCR_WORKING; + error = ncr5380_select(sc, sr); + if (sc->sc_current) { + /* Lost the race! reselected out from under us! */ + /* Work with the reselected job. */ + if (sr->sr_flags & SR_IMMED) { + printf("%s: reselected while polling (abort)\n", + sc->sc_dev.dv_xname); + /* Abort the reselected job. */ + sc->sc_state |= NCR_ABORTING; + sc->sc_msgpriq |= SEND_ABORT; + } + sr = sc->sc_current; + xs = sr->sr_xs; + NCR_TRACE("sched: reselect, new sr=0x%x\n", (long)sr); + goto have_nexus; + } + + /* Normal selection result */ + sc->sc_current = sr; /* connected */ + xs = sr->sr_xs; + + /* + * Initialize pointers, etc. for this job + */ + sc->sc_dataptr = sr->sr_dataptr; + sc->sc_datalen = sr->sr_datalen; + sc->sc_prevphase = PHASE_INVALID; + sc->sc_msgpriq = SEND_IDENTIFY; + sc->sc_msgoutq = 0; + sc->sc_msgout = 0; + + NCR_TRACE("sched: select rv=%d\n", error); + + switch (error) { + case XS_NOERROR: + break; + + case XS_BUSY: + /* XXX - Reset and try again. */ + printf("%s: SCSI bus busy, resetting...\n", + sc->sc_dev.dv_xname); + ncr5380_reset_scsibus(sc); + /* fallthrough */ + case XS_SELTIMEOUT: + default: + xs->error = error; /* from select */ + NCR_TRACE("sched: call done, sr=0x%x\n", (long)sr); + ncr5380_done(sc); + + /* Paranoia: clear everything. */ + sc->sc_dataptr = NULL; + sc->sc_datalen = 0; + sc->sc_prevphase = PHASE_INVALID; + sc->sc_msgpriq = 0; + sc->sc_msgoutq = 0; + sc->sc_msgout = 0; + + goto next_job; + } + + /* + * Selection was successful. Normally, this means + * we are starting a new command. However, this + * might be the termination of an overdue job. + */ + if (sr->sr_flags & SR_OVERDUE) { + NCR_TRACE("sched: overdue, sr=0x%x\n", (long)sr); + sc->sc_state |= NCR_ABORTING; + sc->sc_msgpriq |= SEND_ABORT; + goto have_nexus; + } + + /* + * This may be the continuation of some job that + * completed with a "check condition" code. + */ + if (sr->sr_flags & SR_SENSE) { + NCR_TRACE("sched: get sense, sr=0x%x\n", (long)sr); + /* Do not allocate DMA, nor set timeout. */ + goto have_nexus; + } + + /* + * OK, we are starting a new command. + * Initialize and allocate resources for the new command. + * Device reset is special (only uses MSG_OUT phase). + * Normal commands start in MSG_OUT phase where we will + * send and IDENDIFY message, and then expect CMD phase. + */ + if (ncr5380_debug & NCR_DBG_CMDS) { + printf("ncr5380_sched: begin, target=%d, LUN=%d\n", + xs->sc_link->target, xs->sc_link->lun); + ncr5380_show_scsi_cmd(xs); + } + if (xs->flags & SCSI_RESET) { + NCR_TRACE("sched: cmd=reset, sr=0x%x\n", (long)sr); + /* Not an error, so do not set NCR_ABORTING */ + sc->sc_msgpriq |= SEND_DEV_RESET; + goto have_nexus; + } + +#ifdef DIAGNOSTIC + if ((xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) == 0) { + if (sc->sc_dataptr) { + printf("%s: ptr but no data in/out flags?\n"); + NCR_BREAK(); + sc->sc_dataptr = NULL; + } + } +#endif + + /* Allocate DMA space (maybe) */ + if (sc->sc_dataptr && sc->sc_dma_alloc && + (sc->sc_datalen >= sc->sc_min_dma_len)) + { + NCR_TRACE("sched: dma_alloc, len=%d\n", sc->sc_datalen); + (*sc->sc_dma_alloc)(sc); + } + + /* + * Initialization hook called just after select, + * at the beginning of COMMAND phase. + * (but AFTER the DMA allocation is done) + * + * The evil Sun "si" adapter (OBIO variant) needs some + * setup done to the DMA engine BEFORE the target puts + * the SCSI bus into any DATA phase. + */ + if (sr->sr_dma_hand && sc->sc_dma_setup) { + NCR_TRACE("sched: dma_setup, dh=0x%x\n", + (long) sr->sr_dma_hand); + sc->sc_dma_setup(sc); + } + + /* + * Schedule a timeout for the job we are starting. + */ + if ((sr->sr_flags & SR_IMMED) == 0) { + i = (xs->timeout * hz) / 1000; + NCR_TRACE("sched: set timeout=%d\n", i); + timeout(ncr5380_cmd_timeout, sr, i); + } + +have_nexus: + NCR_TRACE("sched: call machine, cur=0x%x\n", + (long) sc->sc_current); + ncr5380_machine(sc); + NCR_TRACE("sched: machine done, cur=0x%x\n", + (long) sc->sc_current); + + /* + * What state did ncr5380_machine() leave us in? + * Hopefully it sometimes completes a job... + */ + if (sc->sc_state == NCR_IDLE) + goto next_job; + + return; /* Have work in progress. */ +} + + +/* + * Reselect handler: checks for reselection, and if we are being + * reselected, it sets up sc->sc_current. + * + * We are reselected when: + * SEL is TRUE + * IO is TRUE + * BSY is FALSE + */ +void +ncr5380_reselect(sc) + struct ncr5380_softc *sc; +{ + struct sci_req *sr; + int target, lun, phase, timo; + u_char bus, data, icmd, msg; + +#ifdef DIAGNOSTIC + /* + * Note: sc_state will be "idle" when ncr5380_intr() + * calls, or "working" when ncr5380_select() calls. + * (So don't test that in this DIAGNOSTIC) + */ + if (sc->sc_current) + panic("ncr5380_reselect: current set"); +#endif + + /* + * First, check the select line. + * (That has to be set first.) + */ + bus = *(sc->sci_bus_csr); + if ((bus & SCI_BUS_SEL) == 0) { + /* Not a selection or reselection. */ + return; + } + + /* + * The target will assert BSY first (for bus arbitration), + * then raise SEL, and finally drop BSY. Only then is the + * data bus required to have valid selection ID bits set. + * Wait for: SEL==1, BSY==0 before reading the data bus. + */ + timo = ncr5380_wait_nrq_timo; + for (;;) { + if ((bus & SCI_BUS_BSY) == 0) + break; + /* Probably never get here... */ + if (--timo <= 0) { + printf("%s: reselect, BSY stuck, bus=0x%x\n", + sc->sc_dev.dv_xname, bus); + /* Not much we can do. Reset the bus. */ + ncr5380_reset_scsibus(sc); + return; + } + delay(10); + bus = *(sc->sci_bus_csr); + /* If SEL went away, forget it. */ + if ((bus & SCI_BUS_SEL) == 0) + return; + /* Still have SEL, check BSY. */ + } + NCR_TRACE("reselect, valid data after %d loops\n", + ncr5380_wait_nrq_timo - timo); + + /* + * Good. We have SEL=1 and BSY=0. Now wait for a + * "bus settle delay" before we sample the data bus + */ + delay(2); + data = *(sc->sci_data) & 0xFF; + /* XXX - Should check parity... */ + + /* + * Is this a reselect (I/O == 1) or have we been + * selected as a target? (I/O == 0) + */ + if ((bus & SCI_BUS_IO) == 0) { + printf("%s: selected as target, data=0x%x\n", + sc->sc_dev.dv_xname, data); + /* Not much we can do. Reset the bus. */ + ncr5380_reset_scsibus(sc); + return; + } + + /* + * OK, this is a reselection. + */ + for (target = 0; target < 7; target++) + if (data & (1 << target)) + break; + + if ((data & 0x7F) != (1 << target)) { + /* No selecting ID? or >2 IDs on bus? */ + printf("%s: bad reselect, data=0x%x\n", + sc->sc_dev.dv_xname, data); + return; + } + + NCR_TRACE("reselect: target=0x%x\n", target); + + /* Raise BSY to acknowledge target reselection. */ + SetReg ( sc->sci_icmd, SCI_ICMD_BSY ); +/* *(sc->sci_icmd) = SCI_ICMD_BSY; */ + + /* Wait for target to drop SEL. */ + timo = ncr5380_wait_nrq_timo; + for (;;) { + bus = *(sc->sci_bus_csr); + if ((bus & SCI_BUS_SEL) == 0) + break; /* success */ + if (--timo <= 0) { + printf("%s: reselect, SEL stuck, bus=0x%x\n", + sc->sc_dev.dv_xname, bus); + NCR_BREAK(); + /* assume connected (fail later if not) */ + break; + } + delay(2); + } + + /* Now we drop BSY, and we are connected. */ + SetReg ( sc->sci_icmd, 0 ); + SetReg ( sc->sci_sel_enb, 0 ); +/* *(sc->sci_icmd) = 0; + *sc->sci_sel_enb = 0; */ + SCI_CLR_INTR(sc); + + /* + * At this point the target should send an IDENTIFY message, + * which will permit us to determine the reselecting LUN. + * If not, we assume LUN 0. + */ + lun = 0; + /* Wait for REQ before reading bus phase. */ + if (ncr5380_wait_req(sc)) { + printf("%s: reselect, no REQ\n", + sc->sc_dev.dv_xname); + /* Try to send an ABORT message. */ + goto abort; + } + phase = SCI_BUS_PHASE(*sc->sci_bus_csr); + if (phase != PHASE_MSG_IN) { + printf("%s: reselect, phase=%d\n", + sc->sc_dev.dv_xname, phase); + goto abort; + } + + /* Ack. the change to PHASE_MSG_IN */ + SetReg ( sc->sci_tcmd, PHASE_MSG_IN ); +/* *(sc->sci_tcmd) = PHASE_MSG_IN; */ + + /* Peek at the message byte without consuming it! */ + msg = *(sc->sci_data); + if ((msg & 0x80) == 0) { + printf("%s: reselect, not identify, msg=%d\n", + sc->sc_dev.dv_xname, msg); + goto abort; + } + lun = msg & 7; + + /* We now know target/LUN. Do we have the request? */ + sr = sc->sc_matrix[target][lun]; + if (sr) { + /* We now have a nexus. */ + sc->sc_state |= NCR_WORKING; + sc->sc_current = sr; + NCR_TRACE("reselect: resume sr=0x%x\n", (long)sr); + + /* Implicit restore pointers message */ + sc->sc_dataptr = sr->sr_dataptr; + sc->sc_datalen = sr->sr_datalen; + + sc->sc_prevphase = PHASE_INVALID; + sc->sc_msgpriq = 0; + sc->sc_msgoutq = 0; + sc->sc_msgout = 0; + + /* + * Another hack for the Sun3 "si", which needs + * some setup done to its DMA engine before the + * target puts the SCSI bus into any DATA phase. + */ + if (sr->sr_dma_hand && sc->sc_dma_setup) { + NCR_TRACE("reselect: call DMA setup, dh=0x%x\n", + (long) sr->sr_dma_hand); + sc->sc_dma_setup(sc); + } + + /* Now consume the IDENTIFY message. */ + ncr5380_pio_in(sc, PHASE_MSG_IN, 1, &msg); + return; + } + + printf("%s: phantom reselect: target=%d, LUN=%d\n", + sc->sc_dev.dv_xname, target, lun); +abort: + /* + * Try to send an ABORT message. This makes us + * temporarily busy, but no current command... + */ + sc->sc_state |= NCR_ABORTING; + + /* Raise ATN, delay, raise ACK... */ + icmd = SCI_ICMD_ATN; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + delay(2); + + /* Now consume the IDENTIFY message. */ + ncr5380_pio_in(sc, PHASE_MSG_IN, 1, &msg); + + /* Finally try to send the ABORT. */ + sc->sc_prevphase = PHASE_INVALID; + sc->sc_msgpriq = SEND_ABORT; + ncr5380_msg_out(sc); + + SetReg ( sc->sci_tcmd, PHASE_INVALID ); + SetReg ( sc->sci_sel_enb, 0 ); +/* *(sc->sci_tcmd) = PHASE_INVALID; + *sc->sci_sel_enb = 0; */ + SCI_CLR_INTR(sc); + SetReg ( sc->sci_sel_enb, 0x80 ); +/* *sc->sci_sel_enb = 0x80; */ + + sc->sc_state &= ~NCR_ABORTING; +} + + +/* + * Select target: xs is the transfer that we are selecting for. + * sc->sc_current should be NULL. + * + * Returns: + * sc->sc_current != NULL ==> we were reselected (race!) + * XS_NOERROR ==> selection worked + * XS_BUSY ==> lost arbitration + * XS_SELTIMEOUT ==> no response to selection + */ +static int +ncr5380_select(sc, sr) + struct ncr5380_softc *sc; + struct sci_req *sr; +{ + int timo; + u_char bus, data, icmd; + + /* Check for reselect */ + ncr5380_reselect(sc); + if (sc->sc_current) { + NCR_TRACE("select: reselect, cur=0x%x\n", + (long) sc->sc_current); + return XS_BUSY; /* reselected */ + } + + /* + * Set phase bits to 0, otherwise the 5380 won't drive the bus during + * selection. + */ + + SetReg ( sc->sci_tcmd, PHASE_DATA_OUT ); + SetReg ( sc->sci_icmd, 0 ); + SetReg ( sc->sci_mode, 0 ); + +/* *sc->sci_tcmd = PHASE_DATA_OUT; + *sc->sci_icmd = icmd = 0; + *sc->sci_mode = 0; */ + + /* + * Arbitrate for the bus. The 5380 takes care of the + * time-critical bus interactions. We set our ID bit + * in the output data register and set MODE_ARB. The + * 5380 watches for the required "bus free" period. + * If and when the "bus free" period is detected, the + * 5380 then drives BSY, drives the data bus, and sets + * the "arbitration in progress" (AIP) bit to let us + * know arbitration has started. We then wait for one + * arbitration delay (2.2uS) and check the ICMD_LST bit, + * which will be set if someone else drives SEL. + */ + + SetReg ( sc->sci_odata, 0x80 ); + SetReg ( sc->sci_mode, SCI_MODE_ARB ); + +/* *(sc->sci_odata) = 0x80; */ /* OUR_ID */ +/* *(sc->sci_mode) = SCI_MODE_ARB; */ + + /* Wait for ICMD_AIP. */ + timo = ncr5380_wait_req_timo; + for (;;) { + if (*(sc->sci_icmd) & SCI_ICMD_AIP) + break; + if (--timo <= 0) { + /* Did not see any "bus free" period. */ + SetReg ( sc->sci_mode, 0 ); +/* *sc->sci_mode = 0; */ + NCR_TRACE("select: bus busy, rc=%d\n", XS_BUSY); + return XS_BUSY; + } + delay(2); + } + NCR_TRACE("select: have AIP after %d loops\n", + ncr5380_wait_req_timo - timo); + + /* Got AIP. Wait one arbitration delay (2.2 uS.) */ + delay(3); + + /* Check for ICMD_LST */ + if (*(sc->sci_icmd) & SCI_ICMD_LST) { + /* Some other target asserted SEL. */ + SetReg ( sc->sci_mode, 0 ); +/* *sc->sci_mode = 0; */ + NCR_TRACE("select: lost one, rc=%d\n", XS_BUSY); + ncr5380_reselect(sc); /* XXX */ + return XS_BUSY; + } + + /* + * No other device has declared itself the winner. + * The spec. says to check for higher IDs, but we + * are always the highest (ID=7) so don't bother. + * We can now declare victory by asserting SEL. + * + * Note that the 5380 is asserting BSY because we + * asked it to do arbitration. We will now hold + * BSY directly so we can turn off ARB mode. + */ + icmd = (SCI_ICMD_BSY | SCI_ICMD_SEL); + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + /* + * "The SCSI device that wins arbitration shall wait + * at least a bus clear delay plus a bus settle delay + * after asserting the SEL signal before changing + * any [other] signal." (1.2uS. total) + */ + delay(2); + +#if 1 + /* + * XXX: Check one last time to see if we really + * XXX: did win arbitration. (too paranoid?) + */ + if (*(sc->sci_icmd) & SCI_ICMD_LST) { + SetReg ( sc->sci_icmd, 0 ); + SetReg ( sc->sci_mode, 0 ); +/* *sc->sci_icmd = 0; + *sc->sci_mode = 0; */ + NCR_TRACE("select: lost two, rc=%d\n", XS_BUSY); + return XS_BUSY; + } +#endif + /* Leave ARB mode Now that we drive BSY+SEL */ + SetReg ( sc->sci_mode, 0 ); + SetReg ( sc->sci_sel_enb, 0 ); +/* *sc->sci_mode = 0; + *sc->sci_sel_enb = 0; */ + + /* + * Arbitration is complete. Now do selection: + * Drive the data bus with the ID bits for both + * the host and target. Also set ATN now, to + * ask the target for a messgae out phase. + */ + data = 0x80 | (1 << sr->sr_target); + SetReg ( sc->sci_odata, data ); +/* *(sc->sci_odata) = data; */ + icmd |= (SCI_ICMD_DATA | SCI_ICMD_ATN); + SetReg ( sc->sci_icmd, icmd ); + *(sc->sci_icmd) = icmd; + delay(2); /* two deskew delays. */ + + /* De-assert BSY (targets sample the data now). */ + icmd &= ~SCI_ICMD_BSY; + SetReg ( sc->sci_icmd, icmd ); +/* *(sc->sci_icmd) = icmd; */ + delay(3); /* Bus settle delay. */ + + /* + * Wait for the target to assert BSY. + * SCSI spec. says wait for 250 mS. + */ + for (timo = 25000;;) { + if (*sc->sci_bus_csr & SCI_BUS_BSY) + goto success; + if (--timo <= 0) + break; + delay(10); + } + + /* + * There is no reaction from the target. Start the selection + * timeout procedure. We release the databus but keep SEL+ATN + * asserted. After that we wait a 'selection abort time' (200 + * usecs) and 2 deskew delays (90 ns) and check BSY again. + * When BSY is asserted, we assume the selection succeeded, + * otherwise we release the bus. + */ + icmd &= ~SCI_ICMD_DATA; + SetReg ( sc->sci_icmd, icmd ); +/* *(sc->sci_icmd) = icmd; */ + delay(201); + if ((*sc->sci_bus_csr & SCI_BUS_BSY) == 0) { + /* Really no device on bus */ + SetReg ( sc->sci_tcmd, PHASE_INVALID ); + SetReg ( sc->sci_icmd, 0 ); + SetReg ( sc->sci_mode, 0 ); + SetReg ( sc->sci_sel_enb, 0 ); +/* *sc->sci_tcmd = PHASE_INVALID; + *sc->sci_icmd = 0; + *sc->sci_mode = 0; + *sc->sci_sel_enb = 0; */ + SCI_CLR_INTR(sc); + SetReg ( sc->sci_sel_enb, 0x80 ); + *sc->sci_sel_enb = 0x80; + NCR_TRACE("select: device down, rc=%d\n", XS_SELTIMEOUT); + return XS_SELTIMEOUT; + } + +success: + /* + * The target is now driving BSY, so we can stop + * driving SEL and the data bus (keep ATN true). + * Configure the ncr5380 to monitor BSY, parity. + */ + icmd &= ~(SCI_ICMD_DATA | SCI_ICMD_SEL); + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + /* XXX - Make parity checking optional? */ + SetReg ( sc->sci_mode, (SCI_MODE_MONBSY) ); +/* SetReg ( sc->sci_mode, (SCI_MODE_MONBSY | SCI_MODE_PAR_CHK) ); */ +/* *sc->sci_mode = (SCI_MODE_MONBSY | SCI_MODE_PAR_CHK); */ + + return XS_NOERROR; +} + + +/***************************************************************** + * Functions to handle each info. transfer phase: + *****************************************************************/ + +/* + * The message system: + * + * This is a revamped message system that now should easier accomodate + * new messages, if necessary. + * + * Currently we accept these messages: + * IDENTIFY (when reselecting) + * COMMAND COMPLETE # (expect bus free after messages marked #) + * NOOP + * MESSAGE REJECT + * SYNCHRONOUS DATA TRANSFER REQUEST + * SAVE DATA POINTER + * RESTORE POINTERS + * DISCONNECT # + * + * We may send these messages in prioritized order: + * BUS DEVICE RESET # if SCSI_RESET & xs->flags (or in weird sits.) + * MESSAGE PARITY ERROR par. err. during MSGI + * MESSAGE REJECT If we get a message we don't know how to handle + * ABORT # send on errors + * INITIATOR DETECTED ERROR also on errors (SCSI2) (during info xfer) + * IDENTIFY At the start of each transfer + * SYNCHRONOUS DATA TRANSFER REQUEST if appropriate + * NOOP if nothing else fits the bill ... + */ + +#define IS1BYTEMSG(m) (((m) != 0x01 && (m) < 0x20) || (m) >= 0x80) +#define IS2BYTEMSG(m) (((m) & 0xf0) == 0x20) +#define ISEXTMSG(m) ((m) == 0x01) + +/* + * Precondition: + * The SCSI bus is already in the MSGI phase and there is a message byte + * on the bus, along with an asserted REQ signal. + * + * Our return value determines whether our caller, ncr5380_machine() + * will expect to see another REQ (and possibly phase change). + */ +static int +ncr5380_msg_in(sc) + register struct ncr5380_softc *sc; +{ + struct sci_req *sr = sc->sc_current; + int n, phase, timo; + int act_flags; + register u_char icmd; + + /* acknowledge phase change */ + SetReg ( sc->sci_tcmd, PHASE_MSG_IN ); +/* *sc->sci_tcmd = PHASE_MSG_IN; */ + + act_flags = ACT_CONTINUE; + icmd = *sc->sci_icmd & SCI_ICMD_RMASK; + + if (sc->sc_prevphase == PHASE_MSG_IN) { + /* This is a continuation of the previous message. */ + n = sc->sc_imp - sc->sc_imess; + NCR_TRACE("msg_in: continuation, n=%d\n", n); + goto nextbyte; + } + + /* This is a new MESSAGE IN phase. Clean up our state. */ + sc->sc_state &= ~NCR_DROP_MSGIN; + +nextmsg: + n = 0; + sc->sc_imp = &sc->sc_imess[n]; + +nextbyte: + /* + * Read a whole message, but don't ack the last byte. If we reject the + * message, we have to assert ATN during the message transfer phase + * itself. + */ + for (;;) { + /* + * Read a message byte. + * First, check BSY, REQ, phase... + */ + if (!SCI_BUSY(sc)) { + NCR_TRACE("msg_in: lost BSY, n=%d\n", n); + /* XXX - Assume the command completed? */ + act_flags |= (ACT_DISCONNECT | ACT_CMD_DONE); + return (act_flags); + } + if (ncr5380_wait_req(sc)) { + NCR_TRACE("msg_in: BSY but no REQ, n=%d\n", n); + /* Just let ncr5380_machine() handle it... */ + return (act_flags); + } + phase = SCI_BUS_PHASE(*sc->sci_bus_csr); + if (phase != PHASE_MSG_IN) { + /* + * Target left MESSAGE IN, probably because it + * a) noticed our ATN signal, or + * b) ran out of messages. + */ + return (act_flags); + } + /* Still in MESSAGE IN phase, and REQ is asserted. */ + if (*sc->sci_csr & SCI_CSR_PERR) { + ncr_sched_msgout(sc, SEND_PARITY_ERROR); + sc->sc_state |= NCR_DROP_MSGIN; + } + + /* Gather incoming message bytes if needed. */ + if ((sc->sc_state & NCR_DROP_MSGIN) == 0) { + if (n >= NCR_MAX_MSG_LEN) { + ncr_sched_msgout(sc, SEND_REJECT); + sc->sc_state |= NCR_DROP_MSGIN; + } else { + *sc->sc_imp++ = *sc->sci_data; + n++; + /* + * This testing is suboptimal, but most + * messages will be of the one byte variety, so + * it should not affect performance + * significantly. + */ + if (n == 1 && IS1BYTEMSG(sc->sc_imess[0])) + goto have_msg; + if (n == 2 && IS2BYTEMSG(sc->sc_imess[0])) + goto have_msg; + if (n >= 3 && ISEXTMSG(sc->sc_imess[0]) && + n == sc->sc_imess[1] + 2) + goto have_msg; + } + } + + /* + * If we reach this spot we're either: + * a) in the middle of a multi-byte message, or + * b) dropping bytes. + */ + + /* Ack the last byte read. */ + icmd |= SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + if (ncr5380_wait_not_req(sc)) { + NCR_TRACE("msg_in: drop, stuck REQ, n=%d\n", n); + act_flags |= ACT_RESET_BUS; + } + + icmd &= ~SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + if (act_flags != ACT_CONTINUE) + return (act_flags); + + /* back to nextbyte */ + } + +have_msg: + /* We now have a complete message. Parse it. */ + + switch (sc->sc_imess[0]) { + case MSG_CMDCOMPLETE: + NCR_TRACE("msg_in: CMDCOMPLETE\n", 0); + /* Target is about to disconnect. */ + act_flags |= (ACT_DISCONNECT | ACT_CMD_DONE); + break; + + case MSG_DISCONNECT: + NCR_TRACE("msg_in: DISCONNECT\n", 0); + /* Target is about to disconnect. */ + act_flags |= ACT_DISCONNECT; + break; + + case MSG_PARITY_ERROR: + NCR_TRACE("msg_in: PARITY_ERROR\n", 0); + /* Resend the last message. */ + ncr_sched_msgout(sc, sc->sc_msgout); + break; + + case MSG_MESSAGE_REJECT: + /* The target rejects the last message we sent. */ + NCR_TRACE("msg_in: got reject for 0x%x\n", sc->sc_msgout); + switch (sc->sc_msgout) { + case SEND_IDENTIFY: + /* Really old target controller? */ + /* XXX ... */ + break; + case SEND_INIT_DET_ERR: + goto abort; + } + break; + + case MSG_NOOP: + NCR_TRACE("msg_in: NOOP\n", 0); + break; + + case MSG_SAVEDATAPOINTER: + NCR_TRACE("msg_in: SAVE_PTRS\n", 0); + sr->sr_dataptr = sc->sc_dataptr; + sr->sr_datalen = sc->sc_datalen; + break; + + case MSG_RESTOREPOINTERS: + NCR_TRACE("msg_in: RESTORE_PTRS\n", 0); + sc->sc_dataptr = sr->sr_dataptr; + sc->sc_datalen = sr->sr_datalen; + break; + + case MSG_EXTENDED: + switch (sc->sc_imess[2]) { + case MSG_EXT_SDTR: + case MSG_EXT_WDTR: + /* The ncr5380 can not do synchronous mode. */ + goto reject; + default: + printf("%s: unrecognized MESSAGE EXTENDED; sending REJECT\n", + sc->sc_dev.dv_xname); + NCR_BREAK(); + goto reject; + } + break; + + default: + NCR_TRACE("msg_in: eh? imsg=0x%x\n", sc->sc_imess[0]); + printf("%s: unrecognized MESSAGE; sending REJECT\n", + sc->sc_dev.dv_xname); + NCR_BREAK(); + /* fallthrough */ + reject: + ncr_sched_msgout(sc, SEND_REJECT); + break; + + abort: + sc->sc_state |= NCR_ABORTING; + ncr_sched_msgout(sc, SEND_ABORT); + break; + } + + /* Ack the last byte read. */ + icmd |= SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + if (ncr5380_wait_not_req(sc)) { + NCR_TRACE("msg_in: last, stuck REQ, n=%d\n", n); + act_flags |= ACT_RESET_BUS; + } + + icmd &= ~SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + /* Go get the next message, if any. */ + if (act_flags == ACT_CONTINUE) + goto nextmsg; + + return (act_flags); +} + + +/* + * The message out (and in) stuff is a bit complicated: + * If the target requests another message (sequence) without + * having changed phase in between it really asks for a + * retransmit, probably due to parity error(s). + * The following messages can be sent: + * IDENTIFY @ These 4 stem from SCSI command activity + * SDTR @ + * WDTR @ + * DEV_RESET @ + * REJECT if MSGI doesn't make sense + * PARITY_ERROR if parity error while in MSGI + * INIT_DET_ERR if parity error while not in MSGI + * ABORT if INIT_DET_ERR rejected + * NOOP if asked for a message and there's nothing to send + * + * Note that we call this one with (sc_current == NULL) + * when sending ABORT for unwanted reselections. + */ +static int +ncr5380_msg_out(sc) + register struct ncr5380_softc *sc; +{ + struct sci_req *sr = sc->sc_current; + int n, phase, resel; + int progress, act_flags; + register u_char icmd; + + /* acknowledge phase change */ + SetReg ( sc->sci_tcmd, PHASE_MSG_OUT ); +/* *sc->sci_tcmd = PHASE_MSG_OUT; */ + + progress = 0; /* did we send any messages? */ + act_flags = ACT_CONTINUE; + + /* + * Set ATN. If we're just sending a trivial 1-byte message, + * we'll clear ATN later on anyway. Also drive the data bus. + */ + icmd = *sc->sci_icmd & SCI_ICMD_RMASK; + icmd |= (SCI_ICMD_ATN | SCI_ICMD_DATA); + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + if (sc->sc_prevphase == PHASE_MSG_OUT) { + if (sc->sc_omp == sc->sc_omess) { + /* + * This is a retransmission. + * + * We get here if the target stayed in MESSAGE OUT + * phase. Section 5.1.9.2 of the SCSI 2 spec indicates + * that all of the previously transmitted messages must + * be sent again, in the same order. Therefore, we + * requeue all the previously transmitted messages, and + * start again from the top. Our simple priority + * scheme keeps the messages in the right order. + */ + sc->sc_msgpriq |= sc->sc_msgoutq; + NCR_TRACE("msg_out: retrans priq=0x%x\n", sc->sc_msgpriq); + } else { + /* This is a continuation of the previous message. */ + n = sc->sc_omp - sc->sc_omess; + NCR_TRACE("msg_out: continuation, n=%d\n", n); + goto nextbyte; + } + } + + /* No messages transmitted so far. */ + sc->sc_msgoutq = 0; + +nextmsg: + /* Pick up highest priority message. */ + sc->sc_msgout = sc->sc_msgpriq & -sc->sc_msgpriq; + sc->sc_msgpriq &= ~sc->sc_msgout; + sc->sc_msgoutq |= sc->sc_msgout; + + /* Build the outgoing message data. */ + switch (sc->sc_msgout) { + case SEND_IDENTIFY: + NCR_TRACE("msg_out: SEND_IDENTIFY\n", 0); + if (sr == NULL) { + printf("%s: SEND_IDENTIFY while not connected; sending NOOP\n", + sc->sc_dev.dv_xname); + NCR_BREAK(); + goto noop; + } + resel = (sc->sc_flags & NCR5380_PERMIT_RESELECT) ? 1 : 0; + resel &= (sr->sr_flags & (SR_IMMED | SR_SENSE)) ? 0 : 1; + sc->sc_omess[0] = MSG_IDENTIFY(sr->sr_lun, resel); + n = 1; + break; + + case SEND_DEV_RESET: + NCR_TRACE("msg_out: SEND_DEV_RESET\n", 0); + /* Expect disconnect after this! */ + /* XXX: Kill jobs for this target? */ + act_flags |= (ACT_DISCONNECT | ACT_CMD_DONE); + sc->sc_omess[0] = MSG_BUS_DEV_RESET; + n = 1; + break; + + case SEND_REJECT: + NCR_TRACE("msg_out: SEND_REJECT\n", 0); + sc->sc_omess[0] = MSG_MESSAGE_REJECT; + n = 1; + break; + + case SEND_PARITY_ERROR: + NCR_TRACE("msg_out: SEND_PARITY_ERROR\n", 0); + sc->sc_omess[0] = MSG_PARITY_ERROR; + n = 1; + break; + + case SEND_INIT_DET_ERR: + NCR_TRACE("msg_out: SEND_INIT_DET_ERR\n", 0); + sc->sc_omess[0] = MSG_INITIATOR_DET_ERR; + n = 1; + break; + + case SEND_ABORT: + NCR_TRACE("msg_out: SEND_ABORT\n", 0); + /* Expect disconnect after this! */ + /* XXX: Set error flag? */ + act_flags |= (ACT_DISCONNECT | ACT_CMD_DONE); + sc->sc_omess[0] = MSG_ABORT; + n = 1; + break; + + case 0: + printf("%s: unexpected MESSAGE OUT; sending NOOP\n", + sc->sc_dev.dv_xname); + NCR_BREAK(); + noop: + NCR_TRACE("msg_out: send NOOP\n", 0); + sc->sc_omess[0] = MSG_NOOP; + n = 1; + break; + + default: + printf("%s: weird MESSAGE OUT; sending NOOP\n", + sc->sc_dev.dv_xname); + NCR_BREAK(); + goto noop; + } + sc->sc_omp = &sc->sc_omess[n]; + +nextbyte: + /* Send message bytes. */ + while (n > 0) { + /* + * Send a message byte. + * First check BSY, REQ, phase... + */ + if (!SCI_BUSY(sc)) { + NCR_TRACE("msg_out: lost BSY, n=%d\n", n); + goto out; + } + if (ncr5380_wait_req(sc)) { + NCR_TRACE("msg_out: no REQ, n=%d\n", n); + goto out; + } + phase = SCI_BUS_PHASE(*sc->sci_bus_csr); + if (phase != PHASE_MSG_OUT) { + /* + * Target left MESSAGE OUT, possibly to reject + * our message. + */ + NCR_TRACE("msg_out: new phase=%d\n", phase); + goto out; + } + + /* Yes, we can send this message byte. */ + --n; + + /* Clear ATN before last byte if this is the last message. */ + if (n == 0 && sc->sc_msgpriq == 0) { + icmd &= ~SCI_ICMD_ATN; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + /* 2 deskew delays */ + delay(2); /* XXX */ + } + + /* Put data on the bus. */ + sc->sc_omp--; + SetReg ( sc->sci_odata, *sc->sc_omp ); +/* *sc->sci_odata = *--sc->sc_omp; */ + + /* Raise ACK to tell target data is on the bus. */ + icmd |= SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + /* Wait for REQ to be negated. */ + if (ncr5380_wait_not_req(sc)) { + NCR_TRACE("msg_out: stuck REQ, n=%d\n", n); + act_flags |= ACT_RESET_BUS; + } + + /* Finally, drop ACK. */ + icmd &= ~SCI_ICMD_ACK; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + /* Stuck bus or something... */ + if (act_flags & ACT_RESET_BUS) + goto out; + + } + progress++; + + /* We get here only if the entire message has been transmitted. */ + if (sc->sc_msgpriq != 0) { + /* There are more outgoing messages. */ + goto nextmsg; + } + + /* + * The last message has been transmitted. We need to remember the last + * message transmitted (in case the target switches to MESSAGE IN phase + * and sends a MESSAGE REJECT), and the list of messages transmitted + * this time around (in case the target stays in MESSAGE OUT phase to + * request a retransmit). + */ + +out: + /* Stop driving the data bus. */ + icmd &= ~SCI_ICMD_DATA; + SetReg ( sc->sci_icmd, icmd ); +/* *sc->sci_icmd = icmd; */ + + if (!progress) + act_flags |= ACT_RESET_BUS; + + return (act_flags); +} + + +/* + * Handle command phase. + */ +static int +ncr5380_command(sc) + struct ncr5380_softc *sc; +{ + struct sci_req *sr = sc->sc_current; + struct scsi_xfer *xs = sr->sr_xs; + struct scsi_sense rqs; + int len; + + /* acknowledge phase change */ + SetReg ( sc->sci_tcmd, PHASE_COMMAND ); +/* *sc->sci_tcmd = PHASE_COMMAND; */ + + if (sr->sr_flags & SR_SENSE) { + rqs.opcode = REQUEST_SENSE; + rqs.byte2 = xs->sc_link->lun << 5; + rqs.length = sizeof(xs->sense); + + rqs.unused[0] = rqs.unused[1] = rqs.control = 0; + len = ncr5380_pio_out(sc, PHASE_COMMAND, sizeof(rqs), + (u_char *)&rqs); + } + else { + /* Assume command can be sent in one go. */ + /* XXX: Do this using DMA, and get a phase change intr? */ + len = ncr5380_pio_out(sc, PHASE_COMMAND, xs->cmdlen, + (u_char *)xs->cmd); + } + + if (len != xs->cmdlen) { +#ifdef DEBUG + printf("ncr5380_command: short transfer: wanted %d got %d.\n", + xs->cmdlen, len); + ncr5380_show_scsi_cmd(xs); + NCR_BREAK(); +#endif + if (len < 6) { + xs->error = XS_DRIVER_STUFFUP; + sc->sc_state |= NCR_ABORTING; + ncr_sched_msgout(sc, SEND_ABORT); + } + + } + + return ACT_CONTINUE; +} + + +/* + * Handle either data_in or data_out + */ +static int +ncr5380_data_xfer(sc, phase) + struct ncr5380_softc *sc; + int phase; +{ + struct sci_req *sr = sc->sc_current; + struct scsi_xfer *xs = sr->sr_xs; + int expected_phase; + int i, len; + + if (sr->sr_flags & SR_SENSE) { + NCR_TRACE("data_xfer: get sense, sr=0x%x\n", (long)sr); + if (phase != PHASE_DATA_IN) { + printf("%s: sense phase error\n", sc->sc_dev.dv_xname); + goto abort; + } + /* acknowledge phase change */ + SetReg ( sc->sci_tcmd, PHASE_DATA_IN ); +/* *sc->sci_tcmd = PHASE_DATA_IN; */ + len = ncr5380_pio_in(sc, phase, sizeof(xs->sense), + (u_char *)&xs->sense); + return ACT_CONTINUE; + } + + /* + * When aborting a command, disallow any data phase. + */ + if (sc->sc_state & NCR_ABORTING) { + printf("%s: aborting, but phase=%s (reset)\n", + sc->sc_dev.dv_xname, + phase_names[phase & 7]); + return ACT_RESET_BUS; /* XXX */ + } + + /* Validate expected phase (data_in or data_out) */ + expected_phase = (xs->flags & SCSI_DATA_OUT) ? + PHASE_DATA_OUT : PHASE_DATA_IN; + if (phase != expected_phase) { + printf("%s: data phase error\n", + sc->sc_dev.dv_xname); + goto abort; + } + + /* Make sure we have some data to move. */ + if (sc->sc_datalen <= 0) { + printf("%s: can not transfer more data\n", + sc->sc_dev.dv_xname); + goto abort; + } + + /* + * Attempt DMA only if dma_alloc gave us a DMA handle AND + * there is enough left to transfer so DMA is worth while. + */ + if (sr->sr_dma_hand && + (sc->sc_datalen >= sc->sc_min_dma_len)) + { + /* + * OK, really start DMA. Note, the MI start function + * is responsible for setting the TCMD register, etc. + * (Acknowledge the phase change there, not here.) + */ + NCR_TRACE("data_xfer: dma_start, dh=0x%x\n", + (long) sr->sr_dma_hand); + (*sc->sc_dma_start)(sc); + return ACT_WAIT_DMA; + } + + NCR_TRACE("data_xfer: doing PIO, len=%d\n", sc->sc_datalen); + + /* acknowledge phase change */ + SetReg ( sc->sci_tcmd, phase ); +/* *sc->sci_tcmd = phase; */ + if (phase == PHASE_DATA_OUT) { + len = ncr5380_pio_out(sc, phase, sc->sc_datalen, sc->sc_dataptr); + } else { + len = ncr5380_pio_in (sc, phase, sc->sc_datalen, sc->sc_dataptr); + } + sc->sc_dataptr += len; + sc->sc_datalen -= len; + + NCR_TRACE("data_xfer: did PIO, resid=%d\n", sc->sc_datalen); + return (ACT_CONTINUE); + +abort: + sc->sc_state |= NCR_ABORTING; + ncr_sched_msgout(sc, SEND_ABORT); + return (ACT_CONTINUE); +} + + +static int +ncr5380_status(sc) + struct ncr5380_softc *sc; +{ + int len; + u_char status; + struct sci_req *sr = sc->sc_current; + struct scsi_xfer *xs = sr->sr_xs; + + /* acknowledge phase change */ + SetReg ( sc->sci_tcmd, PHASE_STATUS ); +/* *sc->sci_tcmd = PHASE_STATUS; */ + + len = ncr5380_pio_in(sc, PHASE_STATUS, 1, &status); + if (len) { + sr->sr_status = status; + } else { + printf("ncr5380_status: none?\n"); + } + + return ACT_CONTINUE; +} + + +/* + * This is the big state machine that follows SCSI phase changes. + * This is somewhat like a co-routine. It will do a SCSI command, + * and exit if the command is complete, or if it must wait, i.e. + * for DMA to complete or for reselect to resume the job. + * + * The bus must be selected, and we need to know which command is + * being undertaken. + */ +static void +ncr5380_machine(sc) + struct ncr5380_softc *sc; +{ + struct sci_req *sr; + struct scsi_xfer *xs; + int act_flags, phase, timo; + +#ifdef DIAGNOSTIC + if ((getsr() & PSL_IPL) < PSL_IPL2) + panic("ncr5380_machine: bad spl"); + if (sc->sc_state == NCR_IDLE) + panic("ncr5380_machine: state=idle"); + if (sc->sc_current == NULL) + panic("ncr5380_machine: no current cmd"); +#endif + + sr = sc->sc_current; + xs = sr->sr_xs; + act_flags = ACT_CONTINUE; + + /* + * This will be called by ncr5380_intr() when DMA is + * complete. Must stop DMA before touching the 5380 or + * there will be "register conflict" errors. + */ + if (sc->sc_state & NCR_DOINGDMA) { + /* Pick-up where where we left off... */ + goto dma_done; + } + +next_phase: + + if (!SCI_BUSY(sc)) { + /* Unexpected disconnect */ + printf("ncr5380_machine: unexpected disconnect.\n"); + xs->error = XS_DRIVER_STUFFUP; + act_flags |= (ACT_DISCONNECT | ACT_CMD_DONE); + goto do_actions; + } + + /* + * Wait for REQ before reading the phase. + * Need to wait longer than usual here, because + * some devices are just plain slow... + */ + timo = ncr5380_wait_phase_timo; + for (;;) { + if (*sc->sci_bus_csr & SCI_BUS_REQ) + break; + if (--timo <= 0) { + if (sc->sc_state & NCR_ABORTING) { + printf("%s: no REQ while aborting, reset\n", + sc->sc_dev.dv_xname); + act_flags |= ACT_RESET_BUS; + goto do_actions; + } + printf("%s: no REQ for next phase, abort\n", + sc->sc_dev.dv_xname); + sc->sc_state |= NCR_ABORTING; + ncr_sched_msgout(sc, SEND_ABORT); + goto next_phase; + } + delay(100); + } + + phase = SCI_BUS_PHASE(*sc->sci_bus_csr); + NCR_TRACE("machine: phase=%s\n", + (long) phase_names[phase & 7]); + + /* + * We assume that the device knows what it's doing, + * so any phase is good. + */ + +#if 0 + /* + * XXX: Do not ACK the phase yet! do it later... + * XXX: ... each phase routine does that itself. + * In particular, DMA needs it done LATER. + */ + SetReg ( sc->sci_tcmd, phase ); +/* *sc->sci_tcmd = phase; */ /* acknowledge phase change */ +#endif + + switch (phase) { + + case PHASE_DATA_OUT: + case PHASE_DATA_IN: + act_flags = ncr5380_data_xfer(sc, phase); + break; + + case PHASE_COMMAND: + act_flags = ncr5380_command(sc); + break; + + case PHASE_STATUS: + act_flags = ncr5380_status(sc); + break; + + case PHASE_MSG_OUT: + act_flags = ncr5380_msg_out(sc); + break; + + case PHASE_MSG_IN: + act_flags = ncr5380_msg_in(sc); + break; + + default: + printf("ncr5380_machine: Unexpected phase 0x%x\n", phase); + sc->sc_state |= NCR_ABORTING; + ncr_sched_msgout(sc, SEND_ABORT); + goto next_phase; + + } /* switch */ + sc->sc_prevphase = phase; + +do_actions: + __asm("_ncr5380_actions:"); + + if (act_flags & ACT_WAIT_DMA) { + act_flags &= ~ACT_WAIT_DMA; + /* Wait for DMA to complete (polling, or interrupt). */ + if ((sr->sr_flags & SR_IMMED) == 0) { + NCR_TRACE("machine: wait for DMA intr.\n", 0); + return; /* will resume at dma_done */ + } + /* Busy-wait for it to finish. */ + NCR_TRACE("machine: dma_poll, dh=0x%x\n", + (long) sr->sr_dma_hand); + (*sc->sc_dma_poll)(sc); + dma_done: + /* Return here after interrupt. */ + if (sr->sr_flags & SR_OVERDUE) + sc->sc_state |= NCR_ABORTING; + NCR_TRACE("machine: dma_stop, dh=0x%x\n", + (long) sr->sr_dma_hand); + (*sc->sc_dma_stop)(sc); + SCI_CLR_INTR(sc); /* XXX */ + /* + * While DMA is running we can not touch the SBC, + * so various places just set NCR_ABORTING and + * expect us the "kick it" when DMA is done. + */ + if (sc->sc_state & NCR_ABORTING) { + ncr_sched_msgout(sc, SEND_ABORT); + } + } + + /* + * Check for parity error. + * XXX - better place to check? + */ + if (*(sc->sci_csr) & SCI_CSR_PERR) { + printf("%s: parity error csr = %02x!\n", + sc->sc_dev.dv_xname, *(sc->sci_csr) ); + /* XXX: sc->sc_state |= NCR_ABORTING; */ + ncr_sched_msgout(sc, SEND_PARITY_ERROR); + } + + if (act_flags == ACT_CONTINUE) + goto next_phase; + /* All other actions "break" from the loop. */ + + NCR_TRACE("machine: act_flags=0x%x\n", act_flags); + + if (act_flags & ACT_RESET_BUS) { + act_flags |= ACT_CMD_DONE; + /* + * Reset the SCSI bus, usually due to a timeout. + * The error code XS_TIMEOUT allows retries. + */ + sc->sc_state |= NCR_ABORTING; + printf("%s: reset SCSI bus for TID=%d LUN=%d\n", + sc->sc_dev.dv_xname, + sr->sr_target, sr->sr_lun); + ncr5380_reset_scsibus(sc); + } + + if (act_flags & ACT_CMD_DONE) { + act_flags |= ACT_DISCONNECT; + /* Need to call scsi_done() */ + /* XXX: from the aic6360 driver, but why? */ + if (sc->sc_datalen < 0) { + printf("%s: %d extra bytes from %d:%d\n", + sc->sc_dev.dv_xname, -sc->sc_datalen, + sr->sr_target, sr->sr_lun); + sc->sc_datalen = 0; + } + xs->resid = sc->sc_datalen; + /* Note: this will clear sc_current */ + NCR_TRACE("machine: call done, cur=0x%x\n", (long)sr); + ncr5380_done(sc); + } + + if (act_flags & ACT_DISCONNECT) { + /* + * The device has dropped BSY (or will soon). + * Return and let ncr5380_sched() do its thing. + */ + SetReg ( sc->sci_icmd, 0 ); + SetReg ( sc->sci_mode, 0 ); + SetReg ( sc->sci_tcmd, PHASE_INVALID ); + SetReg ( sc->sci_sel_enb, 0 ); + +/* *sc->sci_icmd = 0; + *sc->sci_mode = 0; + *sc->sci_tcmd = PHASE_INVALID; + *sc->sci_sel_enb = 0; */ + + SCI_CLR_INTR(sc); + SetReg ( sc->sci_sel_enb, 0x80 ); + *sc->sci_sel_enb = 0x80; + + if ((act_flags & ACT_CMD_DONE) == 0) { + __asm("_ncr5380_disconnected:"); + NCR_TRACE("machine: discon, cur=0x%x\n", (long)sr); + } + + /* + * We may be here due to a disconnect message, + * in which case we did NOT call ncr5380_done, + * and we need to clear sc_current. + */ + sc->sc_state = NCR_IDLE; + sc->sc_current = NULL; + + /* Paranoia: clear everything. */ + sc->sc_dataptr = NULL; + sc->sc_datalen = 0; + sc->sc_prevphase = PHASE_INVALID; + sc->sc_msgpriq = 0; + sc->sc_msgoutq = 0; + sc->sc_msgout = 0; + + /* Our caller will re-enable interrupts. */ + } +} + + +#ifdef DEBUG + +static void +ncr5380_show_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + u_char *b = (u_char *) xs->cmd; + int i = 0; + + if ( ! ( xs->flags & SCSI_RESET ) ) { + printf("si(%d:%d:%d)-", + xs->sc_link->scsibus, + xs->sc_link->target, + xs->sc_link->lun); + while (i < xs->cmdlen) { + if (i) printf(","); + printf("%x",b[i++]); + } + printf("-\n"); + } else { + printf("si(%d:%d:%d)-RESET-\n", + xs->sc_link->scsibus, + xs->sc_link->target, + xs->sc_link->lun); + } +} + + +static void +ncr5380_show_sense(xs) + struct scsi_xfer *xs; +{ + u_char *b = (u_char *)&xs->sense; + int i; + + printf("sense:"); + for (i = 0; i < sizeof(xs->sense); i++) + printf(" %02x", b[i]); + printf("\n"); +} + +int ncr5380_traceidx = 0; + +#define TRACE_MAX 1024 +struct trace_ent { + char *msg; + long val; +} ncr5380_tracebuf[TRACE_MAX]; + +void +ncr5380_trace(msg, val) + char *msg; + long val; +{ + register struct trace_ent *tr; + register int s; + + s = splhigh(); + + tr = &ncr5380_tracebuf[ncr5380_traceidx]; + + ncr5380_traceidx++; + if (ncr5380_traceidx >= TRACE_MAX) + ncr5380_traceidx = 0; + + tr->msg = msg; + tr->val = val; + + splx(s); +} + +#ifdef DDB +void +ncr5380_clear_trace() +{ + ncr5380_traceidx = 0; + bzero((char*) ncr5380_tracebuf, sizeof(ncr5380_tracebuf)); +} + +void +ncr5380_show_trace() +{ + struct trace_ent *tr; + int idx; + + idx = ncr5380_traceidx; + do { + tr = &ncr5380_tracebuf[idx]; + idx++; + if (idx >= TRACE_MAX) + idx = 0; + if (tr->msg) + db_printf(tr->msg, tr->val); + } while (idx != ncr5380_traceidx); +} + +void +ncr5380_show_req(sr) + struct sci_req *sr; +{ + struct scsi_xfer *xs = sr->sr_xs; + + db_printf("TID=%d ", sr->sr_target); + db_printf("LUN=%d ", sr->sr_lun); + db_printf("dh=0x%x ", sr->sr_dma_hand); + db_printf("dptr=0x%x ", sr->sr_dataptr); + db_printf("dlen=0x%x ", sr->sr_datalen); + db_printf("flags=%d ", sr->sr_flags); + db_printf("stat=%d ", sr->sr_status); + + if (xs == NULL) { + db_printf("(xs=NULL)\n"); + return; + } + db_printf("\n"); +#ifdef SCSIDEBUG + show_scsi_xs(xs); +#else + db_printf("xs=0x%x\n", xs); +#endif +} + +void +ncr5380_show_state() +{ + struct ncr5380_softc *sc; + struct sci_req *sr; + int i, j, k; + + sc = ncr5380_debug_sc; + + if (sc == NULL) { + db_printf("ncr5380_debug_sc == NULL\n"); + return; + } + + db_printf("sc_ncmds=%d\n", sc->sc_ncmds); + k = -1; /* which is current? */ + for (i = 0; i < SCI_OPENINGS; i++) { + sr = &sc->sc_ring[i]; + if (sr->sr_xs) { + if (sr == sc->sc_current) + k = i; + db_printf("req %d: (sr=0x%x)", i, (long)sr); + ncr5380_show_req(sr); + } + } + db_printf("sc_rr=%d, current=%d\n", sc->sc_rr, k); + + db_printf("Active request matrix:\n"); + for(i = 0; i < 8; i++) { /* targets */ + for (j = 0; j < 8; j++) { /* LUN */ + sr = sc->sc_matrix[i][j]; + if (sr) { + db_printf("TID=%d LUN=%d sr=0x%x\n", i, j, (long)sr); + } + } + } + + db_printf("sc_state=0x%x\n", sc->sc_state); + db_printf("sc_current=0x%x\n", sc->sc_current); + db_printf("sc_dataptr=0x%x\n", sc->sc_dataptr); + db_printf("sc_datalen=0x%x\n", sc->sc_datalen); + + db_printf("sc_prevphase=%d\n", sc->sc_prevphase); + db_printf("sc_msgpriq=0x%x\n", sc->sc_msgpriq); +} + +#endif /* DDB */ +#endif /* DEBUG */ |