From: briggs Date: Thu, 27 Feb 1997 13:57:21 +0000 (+0000) Subject: M.I. NCR53c9x/ESP driver, derived from sparc/alpha esp.c. From Jason Thorpe X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=0ee2f123530eda0367522fad9dd2c8c626b9da4c;p=openbsd M.I. NCR53c9x/ESP driver, derived from sparc/alpha esp.c. From Jason Thorpe and NetBSD. --- diff --git a/sys/dev/ic/ncr53c9x.c b/sys/dev/ic/ncr53c9x.c new file mode 100644 index 00000000000..63d12633173 --- /dev/null +++ b/sys/dev/ic/ncr53c9x.c @@ -0,0 +1,1901 @@ +/* $OpenBSD: ncr53c9x.c,v 1.1 1997/02/27 13:57:21 briggs Exp $ */ +/* $NetBSD: ncr53c9x.c,v 1.1 1997/02/27 01:12:07 thorpej Exp $ */ + +/* + * Copyright (c) 1996 Charles M. Hannum. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Charles M. Hannum. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (c) 1994 Peter Galbavy + * Copyright (c) 1995 Paul Kranenburg + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Peter Galbavy + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Based on aic6360 by Jarle Greipsland + * + * Acknowledgements: Many of the algorithms used in this driver are + * inspired by the work of Julian Elischer (julian@tfs.com) and + * Charles Hannum (mycroft@duality.gnu.ai.mit.edu). Thanks a million! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +int ncr53c9x_debug = 0; /*NCR_SHOWPHASE|NCR_SHOWMISC|NCR_SHOWTRAC|NCR_SHOWCMDS;*/ + +/*static*/ void ncr53c9x_readregs __P((struct ncr53c9x_softc *)); +/*static*/ void ncr53c9x_select __P((struct ncr53c9x_softc *, + struct ncr53c9x_ecb *)); +/*static*/ int ncr53c9x_reselect __P((struct ncr53c9x_softc *, int)); +/*static*/ void ncr53c9x_scsi_reset __P((struct ncr53c9x_softc *)); +/*static*/ void ncr53c9x_init __P((struct ncr53c9x_softc *, int)); +/*static*/ int ncr53c9x_poll __P((struct ncr53c9x_softc *, + struct scsi_xfer *, int)); +/*static*/ void ncr53c9x_sched __P((struct ncr53c9x_softc *)); +/*static*/ void ncr53c9x_done __P((struct ncr53c9x_softc *, + struct ncr53c9x_ecb *)); +/*static*/ void ncr53c9x_msgin __P((struct ncr53c9x_softc *)); +/*static*/ void ncr53c9x_msgout __P((struct ncr53c9x_softc *)); +/*static*/ void ncr53c9x_timeout __P((void *arg)); +/*static*/ void ncr53c9x_abort __P((struct ncr53c9x_softc *, + struct ncr53c9x_ecb *)); +/*static*/ void ncr53c9x_dequeue __P((struct ncr53c9x_softc *, + struct ncr53c9x_ecb *)); + +void ncr53c9x_sense __P((struct ncr53c9x_softc *, + struct ncr53c9x_ecb *)); +void ncr53c9x_free_ecb __P((struct ncr53c9x_softc *, + struct ncr53c9x_ecb *, int)); +struct ncr53c9x_ecb *ncr53c9x_get_ecb __P((struct ncr53c9x_softc *, int)); + +static inline int ncr53c9x_stp2cpb __P((struct ncr53c9x_softc *, int)); +static inline void ncr53c9x_setsync __P((struct ncr53c9x_softc *, + struct ncr53c9x_tinfo *)); + +/* + * Names for the NCR53c9x variants, correspnding to the variant tags + * in ncr53c9xvar.h. + */ +const char *ncr53c9x_variant_names[] = { + "ESP100", + "ESP100A", + "ESP200", + "NCR53C94", + "NCR53C96", +}; + +/* + * Attach this instance, and then all the sub-devices + */ +void +ncr53c9x_attach(sc, adapter, dev) + struct ncr53c9x_softc *sc; + struct scsi_adapter *adapter; + struct scsi_device *dev; +{ + + /* + * Note, the front-end has set us up to print the chip variation. + */ + + if (sc->sc_rev >= NCR_VARIANT_MAX) { + printf("\n%s: unknown variant %d, devices not attached\n", + sc->sc_dev.dv_xname, sc->sc_rev); + return; + } + + printf(": %s, %dMHz, SCSI ID %d\n", + ncr53c9x_variant_names[sc->sc_rev], sc->sc_freq, sc->sc_id); + + sc->sc_ccf = FREQTOCCF(sc->sc_freq); + + /* The value *must not* be == 1. Make it 2 */ + if (sc->sc_ccf == 1) + sc->sc_ccf = 2; + + /* + * The recommended timeout is 250ms. This register is loaded + * with a value calculated as follows, from the docs: + * + * (timout period) x (CLK frequency) + * reg = ------------------------------------- + * 8192 x (Clock Conversion Factor) + * + * Since CCF has a linear relation to CLK, this generally computes + * to the constant of 153. + */ + sc->sc_timeout = ((250 * 1000) * sc->sc_freq) / (8192 * sc->sc_ccf); + + /* CCF register only has 3 bits; 0 is actually 8 */ + sc->sc_ccf &= 7; + + /* Reset state & bus */ + sc->sc_state = 0; + ncr53c9x_init(sc, 1); + + /* + * fill in the prototype scsi_link. + */ + sc->sc_link.adapter_softc = sc; + sc->sc_link.adapter_target = sc->sc_id; + sc->sc_link.adapter = adapter; + sc->sc_link.device = dev; + sc->sc_link.openings = 2; + + /* + * Now try to attach all the sub-devices + */ + config_found(&sc->sc_dev, &sc->sc_link, scsiprint); +} + +/* + * This is the generic esp reset function. It does not reset the SCSI bus, + * only this controllers, but kills any on-going commands, and also stops + * and resets the DMA. + * + * After reset, registers are loaded with the defaults from the attach + * routine above. + */ +void +ncr53c9x_reset(sc) + struct ncr53c9x_softc *sc; +{ + + /* reset DMA first */ + NCRDMA_RESET(sc); + + /* reset SCSI chip */ + NCRCMD(sc, NCRCMD_RSTCHIP); + NCRCMD(sc, NCRCMD_NOP); + DELAY(500); + + /* do these backwards, and fall through */ + switch (sc->sc_rev) { + case NCR_VARIANT_NCR53C94: + case NCR_VARIANT_NCR53C96: + case NCR_VARIANT_ESP200: + NCR_WRITE_REG(sc, NCR_CFG3, sc->sc_cfg3); + case NCR_VARIANT_ESP100A: + NCR_WRITE_REG(sc, NCR_CFG2, sc->sc_cfg2); + case NCR_VARIANT_ESP100: + NCR_WRITE_REG(sc, NCR_CFG1, sc->sc_cfg1); + NCR_WRITE_REG(sc, NCR_CCF, sc->sc_ccf); + NCR_WRITE_REG(sc, NCR_SYNCOFF, 0); + NCR_WRITE_REG(sc, NCR_TIMEOUT, sc->sc_timeout); + break; + default: + printf("%s: unknown revision code, assuming ESP100\n", + sc->sc_dev.dv_xname); + NCR_WRITE_REG(sc, NCR_CFG1, sc->sc_cfg1); + NCR_WRITE_REG(sc, NCR_CCF, sc->sc_ccf); + NCR_WRITE_REG(sc, NCR_SYNCOFF, 0); + NCR_WRITE_REG(sc, NCR_TIMEOUT, sc->sc_timeout); + } +} + +/* + * Reset the SCSI bus, but not the chip + */ +void +ncr53c9x_scsi_reset(sc) + struct ncr53c9x_softc *sc; +{ + + (*sc->sc_glue->gl_dma_stop)(sc); + + printf("%s: resetting SCSI bus\n", sc->sc_dev.dv_xname); + NCRCMD(sc, NCRCMD_RSTSCSI); +} + +/* + * Initialize esp state machine + */ +void +ncr53c9x_init(sc, doreset) + struct ncr53c9x_softc *sc; + int doreset; +{ + struct ncr53c9x_ecb *ecb; + int r; + + NCR_TRACE(("[NCR_INIT(%d)] ", doreset)); + + if (sc->sc_state == 0) { + /* First time through; initialize. */ + TAILQ_INIT(&sc->ready_list); + TAILQ_INIT(&sc->nexus_list); + TAILQ_INIT(&sc->free_list); + sc->sc_nexus = NULL; + ecb = sc->sc_ecb; + bzero(ecb, sizeof(sc->sc_ecb)); + for (r = 0; r < sizeof(sc->sc_ecb) / sizeof(*ecb); r++) { + TAILQ_INSERT_TAIL(&sc->free_list, ecb, chain); + ecb++; + } + bzero(sc->sc_tinfo, sizeof(sc->sc_tinfo)); + } else { + /* Cancel any active commands. */ + sc->sc_state = NCR_CLEANING; + if ((ecb = sc->sc_nexus) != NULL) { + ecb->xs->error = XS_DRIVER_STUFFUP; + untimeout(ncr53c9x_timeout, ecb); + ncr53c9x_done(sc, ecb); + } + while ((ecb = sc->nexus_list.tqh_first) != NULL) { + ecb->xs->error = XS_DRIVER_STUFFUP; + untimeout(ncr53c9x_timeout, ecb); + ncr53c9x_done(sc, ecb); + } + } + + /* + * reset the chip to a known state + */ + ncr53c9x_reset(sc); + + sc->sc_phase = sc->sc_prevphase = INVALID_PHASE; + for (r = 0; r < 8; r++) { + struct ncr53c9x_tinfo *ti = &sc->sc_tinfo[r]; +/* XXX - config flags per target: low bits: no reselect; high bits: no synch */ + int fl = sc->sc_dev.dv_cfdata->cf_flags; + + ti->flags = ((sc->sc_minsync && !(fl & (1<<(r+8)))) + ? T_NEGOTIATE : 0) | + ((fl & (1<period = sc->sc_minsync; + ti->offset = 0; + } + + if (doreset) { + sc->sc_state = NCR_SBR; + NCRCMD(sc, NCRCMD_RSTSCSI); + } else { + sc->sc_state = NCR_IDLE; + } +} + +/* + * Read the NCR registers, and save their contents for later use. + * NCR_STAT, NCR_STEP & NCR_INTR are mostly zeroed out when reading + * NCR_INTR - so make sure it is the last read. + * + * I think that (from reading the docs) most bits in these registers + * only make sense when he DMA CSR has an interrupt showing. Call only + * if an interrupt is pending. + */ +void +ncr53c9x_readregs(sc) + struct ncr53c9x_softc *sc; +{ + + sc->sc_espstat = NCR_READ_REG(sc, NCR_STAT); + /* Only the stepo bits are of interest */ + sc->sc_espstep = NCR_READ_REG(sc, NCR_STEP) & NCRSTEP_MASK; + sc->sc_espintr = NCR_READ_REG(sc, NCR_INTR); + + if (sc->sc_glue->gl_clear_latched_intr != NULL) + (*sc->sc_glue->gl_clear_latched_intr)(sc); + + /* + * Determine the SCSI bus phase, return either a real SCSI bus phase + * or some pseudo phase we use to detect certain exceptions. + */ + + sc->sc_phase = (sc->sc_espintr & NCRINTR_DIS) + ? /* Disconnected */ BUSFREE_PHASE + : sc->sc_espstat & NCRSTAT_PHASE; + + NCR_MISC(("regs[intr=%02x,stat=%02x,step=%02x] ", + sc->sc_espintr, sc->sc_espstat, sc->sc_espstep)); +} + +/* + * Convert Synchronous Transfer Period to chip register Clock Per Byte value. + */ +static inline int +ncr53c9x_stp2cpb(sc, period) + struct ncr53c9x_softc *sc; + int period; +{ + int v; + v = (sc->sc_freq * period) / 250; + if (ncr53c9x_cpb2stp(sc, v) < period) + /* Correct round-down error */ + v++; + return v; +} + +static inline void +ncr53c9x_setsync(sc, ti) + struct ncr53c9x_softc *sc; + struct ncr53c9x_tinfo *ti; +{ + + if (ti->flags & T_SYNCMODE) { + NCR_WRITE_REG(sc, NCR_SYNCOFF, ti->offset); + NCR_WRITE_REG(sc, NCR_SYNCTP, + ncr53c9x_stp2cpb(sc, ti->period)); + } else { + NCR_WRITE_REG(sc, NCR_SYNCOFF, 0); + NCR_WRITE_REG(sc, NCR_SYNCTP, 0); + } +} + +/* + * Send a command to a target, set the driver state to NCR_SELECTING + * and let the caller take care of the rest. + * + * Keeping this as a function allows me to say that this may be done + * by DMA instead of programmed I/O soon. + */ +void +ncr53c9x_select(sc, ecb) + struct ncr53c9x_softc *sc; + struct ncr53c9x_ecb *ecb; +{ + struct scsi_link *sc_link = ecb->xs->sc_link; + int target = sc_link->target; + struct ncr53c9x_tinfo *ti = &sc->sc_tinfo[target]; + u_char *cmd; + int clen; + + NCR_TRACE(("[ncr53c9x_select(t%d,l%d,cmd:%x)] ", + sc_link->target, sc_link->lun, ecb->cmd.opcode)); + + /* new state NCR_SELECTING */ + sc->sc_state = NCR_SELECTING; + + NCRCMD(sc, NCRCMD_FLUSH); + + /* + * The docs say the target register is never reset, and I + * can't think of a better place to set it + */ + NCR_WRITE_REG(sc, NCR_SELID, target); + ncr53c9x_setsync(sc, ti); + + /* + * Who am I. This is where we tell the target that we are + * happy for it to disconnect etc. + */ + NCR_WRITE_REG(sc, NCR_FIFO, + MSG_IDENTIFY(sc_link->lun, (ti->flags & T_RSELECTOFF)?0:1)); + + if (ti->flags & T_NEGOTIATE) { + /* Arbitrate, select and stop after IDENTIFY message */ + NCRCMD(sc, NCRCMD_SELATNS); + return; + } + + /* Now the command into the FIFO */ + cmd = (u_char *)&ecb->cmd; + clen = ecb->clen; + while (clen--) + NCR_WRITE_REG(sc, NCR_FIFO, *cmd++); + + /* And get the targets attention */ + NCRCMD(sc, NCRCMD_SELATN); +} + +void +ncr53c9x_free_ecb(sc, ecb, flags) + struct ncr53c9x_softc *sc; + struct ncr53c9x_ecb *ecb; + int flags; +{ + int s; + + s = splbio(); + + ecb->flags = 0; + TAILQ_INSERT_HEAD(&sc->free_list, ecb, chain); + + /* + * If there were none, wake anybody waiting for one to come free, + * starting with queued entries. + */ + if (ecb->chain.tqe_next == 0) + wakeup(&sc->free_list); + + splx(s); +} + +struct ncr53c9x_ecb * +ncr53c9x_get_ecb(sc, flags) + struct ncr53c9x_softc *sc; + int flags; +{ + struct ncr53c9x_ecb *ecb; + int s; + + s = splbio(); + + while ((ecb = sc->free_list.tqh_first) == NULL && + (flags & SCSI_NOSLEEP) == 0) + tsleep(&sc->free_list, PRIBIO, "especb", 0); + if (ecb) { + TAILQ_REMOVE(&sc->free_list, ecb, chain); + ecb->flags |= ECB_ALLOC; + } + + splx(s); + return ecb; +} + +/* + * DRIVER FUNCTIONS CALLABLE FROM HIGHER LEVEL DRIVERS + */ + +/* + * Start a SCSI-command + * This function is called by the higher level SCSI-driver to queue/run + * SCSI-commands. + */ +int +ncr53c9x_scsi_cmd(xs) + struct scsi_xfer *xs; +{ + struct scsi_link *sc_link = xs->sc_link; + struct ncr53c9x_softc *sc = sc_link->adapter_softc; + struct ncr53c9x_ecb *ecb; + int s, flags; + + NCR_TRACE(("[ncr53c9x_scsi_cmd] ")); + NCR_CMDS(("[0x%x, %d]->%d ", (int)xs->cmd->opcode, xs->cmdlen, + sc_link->target)); + + flags = xs->flags; + if ((ecb = ncr53c9x_get_ecb(sc, flags)) == NULL) { + xs->error = XS_DRIVER_STUFFUP; + return TRY_AGAIN_LATER; + } + + /* Initialize ecb */ + ecb->xs = xs; + ecb->timeout = xs->timeout; + + if (xs->flags & SCSI_RESET) { + ecb->flags |= ECB_RESET; + ecb->clen = 0; + ecb->dleft = 0; + } else { + bcopy(xs->cmd, &ecb->cmd, xs->cmdlen); + ecb->clen = xs->cmdlen; + ecb->daddr = xs->data; + ecb->dleft = xs->datalen; + } + ecb->stat = 0; + + s = splbio(); + + TAILQ_INSERT_TAIL(&sc->ready_list, ecb, chain); + if (sc->sc_state == NCR_IDLE) + ncr53c9x_sched(sc); + + splx(s); + + if ((flags & SCSI_POLL) == 0) + return SUCCESSFULLY_QUEUED; + + /* Not allowed to use interrupts, use polling instead */ + if (ncr53c9x_poll(sc, xs, ecb->timeout)) { + ncr53c9x_timeout(ecb); + if (ncr53c9x_poll(sc, xs, ecb->timeout)) + ncr53c9x_timeout(ecb); + } + return COMPLETE; +} + +/* + * Used when interrupt driven I/O isn't allowed, e.g. during boot. + */ +int +ncr53c9x_poll(sc, xs, count) + struct ncr53c9x_softc *sc; + struct scsi_xfer *xs; + int count; +{ + + NCR_TRACE(("[ncr53c9x_poll] ")); + while (count) { + if (NCRDMA_ISINTR(sc)) { + ncr53c9x_intr(sc); + } +#if alternatively + if (NCR_READ_REG(sc, NCR_STAT) & NCRSTAT_INT) + ncr53c9x_intr(sc); +#endif + if ((xs->flags & ITSDONE) != 0) + return 0; + if (sc->sc_state == NCR_IDLE) { + NCR_TRACE(("[ncr53c9x_poll: rescheduling] ")); + ncr53c9x_sched(sc); + } + DELAY(1000); + count--; + } + return 1; +} + + +/* + * LOW LEVEL SCSI UTILITIES + */ + +/* + * Schedule a scsi operation. This has now been pulled out of the interrupt + * handler so that we may call it from ncr53c9x_scsi_cmd and ncr53c9x_done. + * This may save us an unecessary interrupt just to get things going. + * Should only be called when state == NCR_IDLE and at bio pl. + */ +void +ncr53c9x_sched(sc) + struct ncr53c9x_softc *sc; +{ + struct ncr53c9x_ecb *ecb; + struct scsi_link *sc_link; + struct ncr53c9x_tinfo *ti; + + NCR_TRACE(("[ncr53c9x_sched] ")); + if (sc->sc_state != NCR_IDLE) + panic("ncr53c9x_sched: not IDLE (state=%d)", sc->sc_state); + + /* + * Find first ecb in ready queue that is for a target/lunit + * combinations that is not busy. + */ + for (ecb = sc->ready_list.tqh_first; ecb; ecb = ecb->chain.tqe_next) { + sc_link = ecb->xs->sc_link; + ti = &sc->sc_tinfo[sc_link->target]; + if ((ti->lubusy & (1 << sc_link->lun)) == 0) { + TAILQ_REMOVE(&sc->ready_list, ecb, chain); + sc->sc_nexus = ecb; + ncr53c9x_select(sc, ecb); + break; + } else + NCR_MISC(("%d:%d busy\n", + sc_link->target, sc_link->lun)); + } +} + +void +ncr53c9x_sense(sc, ecb) + struct ncr53c9x_softc *sc; + struct ncr53c9x_ecb *ecb; +{ + struct scsi_xfer *xs = ecb->xs; + struct scsi_link *sc_link = xs->sc_link; + struct ncr53c9x_tinfo *ti = &sc->sc_tinfo[sc_link->target]; + struct scsi_sense *ss = (void *)&ecb->cmd; + + NCR_MISC(("requesting sense ")); + /* Next, setup a request sense command block */ + bzero(ss, sizeof(*ss)); + ss->opcode = REQUEST_SENSE; + ss->byte2 = sc_link->lun << 5; + ss->length = sizeof(struct scsi_sense_data); + ecb->clen = sizeof(*ss); + ecb->daddr = (char *)&xs->sense; + ecb->dleft = sizeof(struct scsi_sense_data); + ecb->flags |= ECB_SENSE; + ti->senses++; + if (ecb->flags & ECB_NEXUS) + ti->lubusy &= ~(1 << sc_link->lun); + if (ecb == sc->sc_nexus) { + ncr53c9x_select(sc, ecb); + } else { + ncr53c9x_dequeue(sc, ecb); + TAILQ_INSERT_HEAD(&sc->ready_list, ecb, chain); + if (sc->sc_state == NCR_IDLE) + ncr53c9x_sched(sc); + } +} + +/* + * POST PROCESSING OF SCSI_CMD (usually current) + */ +void +ncr53c9x_done(sc, ecb) + struct ncr53c9x_softc *sc; + struct ncr53c9x_ecb *ecb; +{ + struct scsi_xfer *xs = ecb->xs; + struct scsi_link *sc_link = xs->sc_link; + struct ncr53c9x_tinfo *ti = &sc->sc_tinfo[sc_link->target]; + + NCR_TRACE(("[ncr53c9x_done(error:%x)] ", xs->error)); + + /* + * Now, if we've come here with no error code, i.e. we've kept the + * initial XS_NOERROR, and the status code signals that we should + * check sense, we'll need to set up a request sense cmd block and + * push the command back into the ready queue *before* any other + * commands for this target/lunit, else we lose the sense info. + * We don't support chk sense conditions for the request sense cmd. + */ + if (xs->error == XS_NOERROR) { + if ((ecb->flags & ECB_ABORT) != 0) { + xs->error = XS_DRIVER_STUFFUP; + } else if ((ecb->flags & ECB_SENSE) != 0) { + xs->error = XS_SENSE; + } else if ((ecb->stat & ST_MASK) == SCSI_CHECK) { + /* First, save the return values */ + xs->resid = ecb->dleft; + xs->status = ecb->stat; + ncr53c9x_sense(sc, ecb); + return; + } else { + xs->resid = ecb->dleft; + } + } + + xs->flags |= ITSDONE; + +#ifdef NCR53C9X_DEBUG + if (ncr53c9x_debug & NCR_SHOWMISC) { + if (xs->resid != 0) + printf("resid=%d ", xs->resid); + if (xs->error == XS_SENSE) + printf("sense=0x%02x\n", xs->sense.error_code); + else + printf("error=%d\n", xs->error); + } +#endif + + /* + * Remove the ECB from whatever queue it's on. + */ + if (ecb->flags & ECB_NEXUS) + ti->lubusy &= ~(1 << sc_link->lun); + if (ecb == sc->sc_nexus) { + sc->sc_nexus = NULL; + sc->sc_state = NCR_IDLE; + ncr53c9x_sched(sc); + } else + ncr53c9x_dequeue(sc, ecb); + + ncr53c9x_free_ecb(sc, ecb, xs->flags); + ti->cmds++; + scsi_done(xs); +} + +void +ncr53c9x_dequeue(sc, ecb) + struct ncr53c9x_softc *sc; + struct ncr53c9x_ecb *ecb; +{ + + if (ecb->flags & ECB_NEXUS) { + TAILQ_REMOVE(&sc->nexus_list, ecb, chain); + } else { + TAILQ_REMOVE(&sc->ready_list, ecb, chain); + } +} + +/* + * INTERRUPT/PROTOCOL ENGINE + */ + +/* + * Schedule an outgoing message by prioritizing it, and asserting + * attention on the bus. We can only do this when we are the initiator + * else there will be an illegal command interrupt. + */ +#define ncr53c9x_sched_msgout(m) \ + do { \ + NCR_MISC(("ncr53c9x_sched_msgout %d ", m)); \ + NCRCMD(sc, NCRCMD_SETATN); \ + sc->sc_flags |= NCR_ATN; \ + sc->sc_msgpriq |= (m); \ + } while (0) + +int +ncr53c9x_reselect(sc, message) + struct ncr53c9x_softc *sc; + int message; +{ + u_char selid, target, lun; + struct ncr53c9x_ecb *ecb; + struct scsi_link *sc_link; + struct ncr53c9x_tinfo *ti; + + /* + * The SCSI chip made a snapshot of the data bus while the reselection + * was being negotiated. This enables us to determine which target did + * the reselect. + */ + selid = sc->sc_selid & ~(1 << sc->sc_id); + if (selid & (selid - 1)) { + printf("%s: reselect with invalid selid %02x;" + " sending DEVICE RESET\n", sc->sc_dev.dv_xname, selid); + goto reset; + } + + /* + * Search wait queue for disconnected cmd + * The list should be short, so I haven't bothered with + * any more sophisticated structures than a simple + * singly linked list. + */ + target = ffs(selid) - 1; + lun = message & 0x07; + for (ecb = sc->nexus_list.tqh_first; ecb != NULL; + ecb = ecb->chain.tqe_next) { + sc_link = ecb->xs->sc_link; + if (sc_link->target == target && sc_link->lun == lun) + break; + } + if (ecb == NULL) { + printf("%s: reselect from target %d lun %d with no nexus;" + " sending ABORT\n", sc->sc_dev.dv_xname, target, lun); + goto abort; + } + + /* Make this nexus active again. */ + TAILQ_REMOVE(&sc->nexus_list, ecb, chain); + sc->sc_state = NCR_CONNECTED; + sc->sc_nexus = ecb; + ti = &sc->sc_tinfo[target]; + ti->lubusy |= (1 << lun); + ncr53c9x_setsync(sc, ti); + + if (ecb->flags & ECB_RESET) + ncr53c9x_sched_msgout(SEND_DEV_RESET); + else if (ecb->flags & ECB_ABORT) + ncr53c9x_sched_msgout(SEND_ABORT); + + /* Do an implicit RESTORE POINTERS. */ + sc->sc_dp = ecb->daddr; + sc->sc_dleft = ecb->dleft; + + return (0); + +reset: + ncr53c9x_sched_msgout(SEND_DEV_RESET); + return (1); + +abort: + ncr53c9x_sched_msgout(SEND_ABORT); + return (1); +} + +#define IS1BYTEMSG(m) (((m) != 1 && (m) < 0x20) || (m) & 0x80) +#define IS2BYTEMSG(m) (((m) & 0xf0) == 0x20) +#define ISEXTMSG(m) ((m) == 1) + +/* + * Get an incoming message as initiator. + * + * The SCSI bus must already be in MESSAGE_IN_PHASE and there is a + * byte in the FIFO + */ +void +ncr53c9x_msgin(sc) + register struct ncr53c9x_softc *sc; +{ + register int v; + + NCR_TRACE(("[ncr53c9x_msgin(curmsglen:%ld)] ", (long)sc->sc_imlen)); + + if ((NCR_READ_REG(sc, NCR_FFLAG) & NCRFIFO_FF) == 0) { + printf("%s: msgin: no msg byte available\n", + sc->sc_dev.dv_xname); + return; + } + + /* + * Prepare for a new message. A message should (according + * to the SCSI standard) be transmitted in one single + * MESSAGE_IN_PHASE. If we have been in some other phase, + * then this is a new message. + */ + if (sc->sc_prevphase != MESSAGE_IN_PHASE) { + sc->sc_flags &= ~NCR_DROP_MSGI; + sc->sc_imlen = 0; + } + + v = NCR_READ_REG(sc, NCR_FIFO); + NCR_MISC(("", v)); + +#if 0 + if (sc->sc_state == NCR_RESELECTED && sc->sc_imlen == 0) { + /* + * Which target is reselecting us? (The ID bit really) + */ + sc->sc_selid = v; + NCR_MISC(("selid=0x%2x ", sc->sc_selid)); + return; + } +#endif + + sc->sc_imess[sc->sc_imlen] = v; + + /* + * If we're going to reject the message, don't bother storing + * the incoming bytes. But still, we need to ACK them. + */ + + if ((sc->sc_flags & NCR_DROP_MSGI)) { + NCRCMD(sc, NCRCMD_MSGOK); + printf("", + sc->sc_imess[sc->sc_imlen]); + return; + } + + if (sc->sc_imlen >= NCR_MAX_MSG_LEN) { + ncr53c9x_sched_msgout(SEND_REJECT); + sc->sc_flags |= NCR_DROP_MSGI; + } else { + sc->sc_imlen++; + /* + * This testing is suboptimal, but most + * messages will be of the one byte variety, so + * it should not effect performance + * significantly. + */ + if (sc->sc_imlen == 1 && IS1BYTEMSG(sc->sc_imess[0])) + goto gotit; + if (sc->sc_imlen == 2 && IS2BYTEMSG(sc->sc_imess[0])) + goto gotit; + if (sc->sc_imlen >= 3 && ISEXTMSG(sc->sc_imess[0]) && + sc->sc_imlen == sc->sc_imess[1] + 2) + goto gotit; + } + /* Ack what we have so far */ + NCRCMD(sc, NCRCMD_MSGOK); + return; + +gotit: + NCR_MSGS(("gotmsg(%x)", sc->sc_imess[0])); + /* + * Now we should have a complete message (1 byte, 2 byte + * and moderately long extended messages). We only handle + * extended messages which total length is shorter than + * NCR_MAX_MSG_LEN. Longer messages will be amputated. + */ + switch (sc->sc_state) { + struct ncr53c9x_ecb *ecb; + struct ncr53c9x_tinfo *ti; + + case NCR_CONNECTED: + ecb = sc->sc_nexus; + ti = &sc->sc_tinfo[ecb->xs->sc_link->target]; + + switch (sc->sc_imess[0]) { + case MSG_CMDCOMPLETE: + NCR_MSGS(("cmdcomplete ")); + if (sc->sc_dleft < 0) { + struct scsi_link *sc_link = ecb->xs->sc_link; + printf("%s: %ld extra bytes from %d:%d\n", + sc->sc_dev.dv_xname, -(long)sc->sc_dleft, + sc_link->target, sc_link->lun); + sc->sc_dleft = 0; + } + ecb->xs->resid = ecb->dleft = sc->sc_dleft; + sc->sc_state = NCR_CMDCOMPLETE; + break; + + case MSG_MESSAGE_REJECT: + if (ncr53c9x_debug & NCR_SHOWMSGS) + printf("%s: our msg rejected by target\n", + sc->sc_dev.dv_xname); + switch (sc->sc_msgout) { + case SEND_SDTR: + sc->sc_flags &= ~NCR_SYNCHNEGO; + ti->flags &= ~(T_NEGOTIATE | T_SYNCMODE); + ncr53c9x_setsync(sc, ti); + break; + case SEND_INIT_DET_ERR: + goto abort; + } + break; + + case MSG_NOOP: + NCR_MSGS(("noop ")); + break; + + case MSG_DISCONNECT: + NCR_MSGS(("disconnect ")); + ti->dconns++; + sc->sc_state = NCR_DISCONNECT; + if ((ecb->xs->sc_link->quirks & SDEV_AUTOSAVE) == 0) + break; + /*FALLTHROUGH*/ + + case MSG_SAVEDATAPOINTER: + NCR_MSGS(("save datapointer ")); + ecb->daddr = sc->sc_dp; + ecb->dleft = sc->sc_dleft; + break; + + case MSG_RESTOREPOINTERS: + NCR_MSGS(("restore datapointer ")); + sc->sc_dp = ecb->daddr; + sc->sc_dleft = ecb->dleft; + break; + + case MSG_EXTENDED: + NCR_MSGS(("extended(%x) ", sc->sc_imess[2])); + switch (sc->sc_imess[2]) { + case MSG_EXT_SDTR: + NCR_MSGS(("SDTR period %d, offset %d ", + sc->sc_imess[3], sc->sc_imess[4])); + if (sc->sc_imess[1] != 3) + goto reject; + ti->period = sc->sc_imess[3]; + ti->offset = sc->sc_imess[4]; + ti->flags &= ~T_NEGOTIATE; + if (sc->sc_minsync == 0 || + ti->offset == 0 || + ti->period > 124) { + printf("%s:%d: async\n", "esp", + ecb->xs->sc_link->target); + if ((sc->sc_flags&NCR_SYNCHNEGO) + == 0) { + /* + * target initiated negotiation + */ + ti->offset = 0; + ti->flags &= ~T_SYNCMODE; + ncr53c9x_sched_msgout( + SEND_SDTR); + } else { + /* we are async */ + ti->flags &= ~T_SYNCMODE; + } + } else { + int r = 250/ti->period; + int s = (100*250)/ti->period - 100*r; + int p; + + p = ncr53c9x_stp2cpb(sc, ti->period); + ti->period = ncr53c9x_cpb2stp(sc, p); +#ifdef NCR53C9X_DEBUG + sc_print_addr(ecb->xs->sc_link); + printf("max sync rate %d.%02dMb/s\n", + r, s); +#endif + if ((sc->sc_flags&NCR_SYNCHNEGO) + == 0) { + /* + * target initiated negotiation + */ + if (ti->period < + sc->sc_minsync) + ti->period = + sc->sc_minsync; + if (ti->offset > 15) + ti->offset = 15; + ti->flags &= ~T_SYNCMODE; + ncr53c9x_sched_msgout( + SEND_SDTR); + } else { + /* we are sync */ + ti->flags |= T_SYNCMODE; + } + } + sc->sc_flags &= ~NCR_SYNCHNEGO; + ncr53c9x_setsync(sc, ti); + break; + + default: + printf("%s: unrecognized MESSAGE EXTENDED;" + " sending REJECT\n", sc->sc_dev.dv_xname); + goto reject; + } + break; + + default: + NCR_MSGS(("ident ")); + printf("%s: unrecognized MESSAGE; sending REJECT\n", + sc->sc_dev.dv_xname); + reject: + ncr53c9x_sched_msgout(SEND_REJECT); + break; + } + break; + + case NCR_RESELECTED: + if (!MSG_ISIDENTIFY(sc->sc_imess[0])) { + printf("%s: reselect without IDENTIFY;" + " sending DEVICE RESET\n", sc->sc_dev.dv_xname); + goto reset; + } + + (void) ncr53c9x_reselect(sc, sc->sc_imess[0]); + break; + + default: + printf("%s: unexpected MESSAGE IN; sending DEVICE RESET\n", + sc->sc_dev.dv_xname); + reset: + ncr53c9x_sched_msgout(SEND_DEV_RESET); + break; + + abort: + ncr53c9x_sched_msgout(SEND_ABORT); + break; + } + + /* Ack last message byte */ + NCRCMD(sc, NCRCMD_MSGOK); + + /* Done, reset message pointer. */ + sc->sc_flags &= ~NCR_DROP_MSGI; + sc->sc_imlen = 0; +} + + +/* + * Send the highest priority, scheduled message + */ +void +ncr53c9x_msgout(sc) + register struct ncr53c9x_softc *sc; +{ + struct ncr53c9x_tinfo *ti; + struct ncr53c9x_ecb *ecb; + size_t size; + + NCR_TRACE(("[ncr53c9x_msgout(priq:%x, prevphase:%x)]", + sc->sc_msgpriq, sc->sc_prevphase)); + + if (sc->sc_flags & NCR_ATN) { + if (sc->sc_prevphase != MESSAGE_OUT_PHASE) { + new: + NCRCMD(sc, NCRCMD_FLUSH); + DELAY(1); + sc->sc_msgoutq = 0; + sc->sc_omlen = 0; + } + } else { + if (sc->sc_prevphase == MESSAGE_OUT_PHASE) { + ncr53c9x_sched_msgout(sc->sc_msgoutq); + goto new; + } else { + printf("%s at line %d: unexpected MESSAGE OUT phase\n", + sc->sc_dev.dv_xname, __LINE__); + } + } + + if (sc->sc_omlen == 0) { + /* Pick up highest priority message */ + sc->sc_msgout = sc->sc_msgpriq & -sc->sc_msgpriq; + sc->sc_msgoutq |= sc->sc_msgout; + sc->sc_msgpriq &= ~sc->sc_msgout; + sc->sc_omlen = 1; /* "Default" message len */ + switch (sc->sc_msgout) { + case SEND_SDTR: + ecb = sc->sc_nexus; + ti = &sc->sc_tinfo[ecb->xs->sc_link->target]; + sc->sc_omess[0] = MSG_EXTENDED; + sc->sc_omess[1] = 3; + sc->sc_omess[2] = MSG_EXT_SDTR; + sc->sc_omess[3] = ti->period; + sc->sc_omess[4] = ti->offset; + sc->sc_omlen = 5; + if ((sc->sc_flags & NCR_SYNCHNEGO) == 0) { + ti->flags |= T_SYNCMODE; + ncr53c9x_setsync(sc, ti); + } + break; + case SEND_IDENTIFY: + if (sc->sc_state != NCR_CONNECTED) { + printf("%s at line %d: no nexus\n", + sc->sc_dev.dv_xname, __LINE__); + } + ecb = sc->sc_nexus; + sc->sc_omess[0] = + MSG_IDENTIFY(ecb->xs->sc_link->lun, 0); + break; + case SEND_DEV_RESET: + sc->sc_flags |= NCR_ABORTING; + sc->sc_omess[0] = MSG_BUS_DEV_RESET; + ecb = sc->sc_nexus; + ti = &sc->sc_tinfo[ecb->xs->sc_link->target]; + ti->flags &= ~T_SYNCMODE; + ti->flags |= T_NEGOTIATE; + break; + case SEND_PARITY_ERROR: + sc->sc_omess[0] = MSG_PARITY_ERROR; + break; + case SEND_ABORT: + sc->sc_flags |= NCR_ABORTING; + sc->sc_omess[0] = MSG_ABORT; + break; + case SEND_INIT_DET_ERR: + sc->sc_omess[0] = MSG_INITIATOR_DET_ERR; + break; + case SEND_REJECT: + sc->sc_omess[0] = MSG_MESSAGE_REJECT; + break; + default: + NCRCMD(sc, NCRCMD_RSTATN); + sc->sc_flags &= ~NCR_ATN; + sc->sc_omess[0] = MSG_NOOP; + break; + } + sc->sc_omp = sc->sc_omess; + } + +#if 1 + /* (re)send the message */ + size = min(sc->sc_omlen, sc->sc_maxxfer); + NCRDMA_SETUP(sc, &sc->sc_omp, &sc->sc_omlen, 0, &size); + /* Program the SCSI counter */ + NCR_WRITE_REG(sc, NCR_TCL, size); + NCR_WRITE_REG(sc, NCR_TCM, size >> 8); + if (sc->sc_cfg2 & NCRCFG2_FE) { + NCR_WRITE_REG(sc, NCR_TCH, size >> 16); + } + /* load the count in */ + NCRCMD(sc, NCRCMD_NOP|NCRCMD_DMA); + NCRCMD(sc, NCRCMD_TRANS|NCRCMD_DMA); + NCRDMA_GO(sc); +#else + { int i; + for (i = 0; i < sc->sc_omlen; i++) + NCR_WRITE_REG(sc, FIFO, sc->sc_omess[i]); + NCRCMD(sc, NCRCMD_TRANS); + sc->sc_omlen = 0; + } +#endif +} + +/* + * This is the most critical part of the driver, and has to know + * how to deal with *all* error conditions and phases from the SCSI + * bus. If there are no errors and the DMA was active, then call the + * DMA pseudo-interrupt handler. If this returns 1, then that was it + * and we can return from here without further processing. + * + * Most of this needs verifying. + */ +int +ncr53c9x_intr(sc) + register struct ncr53c9x_softc *sc; +{ + register struct ncr53c9x_ecb *ecb; + register struct scsi_link *sc_link; + struct ncr53c9x_tinfo *ti; + int loop; + size_t size; + + NCR_TRACE(("[ncr53c9x_intr]")); + + /* + * I have made some (maybe seriously flawed) assumptions here, + * but basic testing (uncomment the printf() below), show that + * certainly something happens when this loop is here. + * + * The idea is that many of the SCSI operations take very little + * time, and going away and getting interrupted is too high an + * overhead to pay. For example, selecting, sending a message + * and command and then doing some work can be done in one "pass". + * + * The DELAY is not variable because I do not understand that the + * DELAY loop should be fixed-time regardless of CPU speed, but + * I am *assuming* that the faster SCSI processors get things done + * quicker (sending a command byte etc), and so there is no + * need to be too slow. + * + * This is a heuristic. It is 2 when at 20Mhz, 2 at 25Mhz and 1 + * at 40Mhz. This needs testing. + */ + for (loop = 0; 1;loop++, DELAY(50/sc->sc_freq)) { + /* a feeling of deja-vu */ + if (!NCRDMA_ISINTR(sc)) + return (loop != 0); +#if 0 + if (loop) + printf("*"); +#endif + + /* and what do the registers say... */ + ncr53c9x_readregs(sc); + + sc->sc_intrcnt.ev_count++; + + /* + * At the moment, only a SCSI Bus Reset or Illegal + * Command are classed as errors. A disconnect is a + * valid condition, and we let the code check is the + * "NCR_BUSFREE_OK" flag was set before declaring it + * and error. + * + * Also, the status register tells us about "Gross + * Errors" and "Parity errors". Only the Gross Error + * is really bad, and the parity errors are dealt + * with later + * + * TODO + * If there are too many parity error, go to slow + * cable mode ? + */ + + /* SCSI Reset */ + if (sc->sc_espintr & NCRINTR_SBR) { + if (NCR_READ_REG(sc, NCR_FFLAG) & NCRFIFO_FF) { + NCRCMD(sc, NCRCMD_FLUSH); + DELAY(1); + } + if (sc->sc_state != NCR_SBR) { + printf("%s: SCSI bus reset\n", + sc->sc_dev.dv_xname); + ncr53c9x_init(sc, 0); /* Restart everything */ + return 1; + } +#if 0 + /*XXX*/ printf("\n", + sc->sc_espintr, sc->sc_espstat, + sc->sc_espstep); +#endif + if (sc->sc_nexus) + panic("%s: nexus in reset state", + sc->sc_dev.dv_xname); + goto sched; + } + + ecb = sc->sc_nexus; + +#define NCRINTR_ERR (NCRINTR_SBR|NCRINTR_ILL) + if (sc->sc_espintr & NCRINTR_ERR || + sc->sc_espstat & NCRSTAT_GE) { + + if (sc->sc_espstat & NCRSTAT_GE) { + /* no target ? */ + if (NCR_READ_REG(sc, NCR_FFLAG) & NCRFIFO_FF) { + NCRCMD(sc, NCRCMD_FLUSH); + DELAY(1); + } + if (sc->sc_state == NCR_CONNECTED || + sc->sc_state == NCR_SELECTING) { + ecb->xs->error = XS_DRIVER_STUFFUP; + ncr53c9x_done(sc, ecb); + } + return 1; + } + + if (sc->sc_espintr & NCRINTR_ILL) { + /* illegal command, out of sync ? */ + printf("%s: illegal command: 0x%x " + "(state %d, phase %x, prevphase %x)\n", + sc->sc_dev.dv_xname, sc->sc_lastcmd, + sc->sc_state, sc->sc_phase, + sc->sc_prevphase); + if (NCR_READ_REG(sc, NCR_FFLAG) & NCRFIFO_FF) { + NCRCMD(sc, NCRCMD_FLUSH); + DELAY(1); + } + ncr53c9x_init(sc, 0); /* Restart everything */ + return 1; + } + } + + /* + * Call if DMA is active. + * + * If DMA_INTR returns true, then maybe go 'round the loop + * again in case there is no more DMA queued, but a phase + * change is expected. + */ + if (NCRDMA_ISACTIVE(sc)) { + int r = NCRDMA_INTR(sc); + if (r == -1) { + printf("%s: DMA error; resetting\n", + sc->sc_dev.dv_xname); + ncr53c9x_init(sc, 1); + } + /* If DMA active here, then go back to work... */ + if (NCRDMA_ISACTIVE(sc)) + return 1; + + if (sc->sc_dleft == 0 && + (sc->sc_espstat & NCRSTAT_TC) == 0) + printf("%s: !TC [intr %x, stat %x, step %d]" + " prevphase %x, resid %x\n", + sc->sc_dev.dv_xname, + sc->sc_espintr, + sc->sc_espstat, + sc->sc_espstep, + sc->sc_prevphase, + ecb?ecb->dleft:-1); + } + +#if 0 /* Unreliable on some NCR revisions? */ + if ((sc->sc_espstat & NCRSTAT_INT) == 0) { + printf("%s: spurious interrupt\n", + sc->sc_dev.dv_xname); + return 1; + } +#endif + + /* + * check for less serious errors + */ + if (sc->sc_espstat & NCRSTAT_PE) { + printf("%s: SCSI bus parity error\n", + sc->sc_dev.dv_xname); + if (sc->sc_prevphase == MESSAGE_IN_PHASE) + ncr53c9x_sched_msgout(SEND_PARITY_ERROR); + else + ncr53c9x_sched_msgout(SEND_INIT_DET_ERR); + } + + if (sc->sc_espintr & NCRINTR_DIS) { + NCR_MISC(("", + sc->sc_espintr,sc->sc_espstat,sc->sc_espstep)); + if (NCR_READ_REG(sc, NCR_FFLAG) & NCRFIFO_FF) { + NCRCMD(sc, NCRCMD_FLUSH); + DELAY(1); + } + /* + * This command must (apparently) be issued within + * 250mS of a disconnect. So here you are... + */ + NCRCMD(sc, NCRCMD_ENSEL); + switch (sc->sc_state) { + case NCR_RESELECTED: + goto sched; + + case NCR_SELECTING: + ecb->xs->error = XS_SELTIMEOUT; + goto finish; + + case NCR_CONNECTED: + if ((sc->sc_flags & NCR_SYNCHNEGO)) { +#ifdef NCR53C9X_DEBUG + if (ecb) + sc_print_addr(ecb->xs->sc_link); + printf("sync nego not completed!\n"); +#endif + ti = &sc->sc_tinfo[ecb->xs->sc_link->target]; + sc->sc_flags &= ~NCR_SYNCHNEGO; + ti->flags &= + ~(T_NEGOTIATE | T_SYNCMODE); + } + + /* it may be OK to disconnect */ + if ((sc->sc_flags & NCR_ABORTING) == 0) { + /* + * Section 5.1.1 of the SCSI 2 spec + * suggests issuing a REQUEST SENSE + * following an unexpected disconnect. + * Some devices go into a contingent + * allegiance condition when + * disconnecting, and this is necessary + * to clean up their state. + */ + printf("%s: unexpected disconnect; ", + sc->sc_dev.dv_xname); + if (ecb->flags & ECB_SENSE) { + printf("resetting\n"); + goto reset; + } + printf("sending REQUEST SENSE\n"); + ncr53c9x_sense(sc, ecb); + goto out; + } + + ecb->xs->error = XS_DRIVER_STUFFUP; + goto finish; + + case NCR_DISCONNECT: + TAILQ_INSERT_HEAD(&sc->nexus_list, ecb, chain); + sc->sc_nexus = NULL; + goto sched; + + case NCR_CMDCOMPLETE: + goto finish; + } + } + + switch (sc->sc_state) { + + case NCR_SBR: + printf("%s: waiting for SCSI Bus Reset to happen\n", + sc->sc_dev.dv_xname); + return 1; + + case NCR_RESELECTED: + /* + * we must be continuing a message ? + */ + if (sc->sc_phase != MESSAGE_IN_PHASE) { + printf("%s: target didn't identify\n", + sc->sc_dev.dv_xname); + ncr53c9x_init(sc, 1); + return 1; + } +printf("<>"); +#if XXXX + ncr53c9x_msgin(sc); + if (sc->sc_state != NCR_CONNECTED) { + /* IDENTIFY fail?! */ + printf("%s: identify failed\n", + sc->sc_dev.dv_xname); + ncr53c9x_init(sc, 1); + return 1; + } +#endif + break; + + case NCR_IDLE: +if (sc->sc_flags & NCR_ICCS) printf("[[esp: BUMMER]]"); + case NCR_SELECTING: + sc->sc_msgpriq = sc->sc_msgout = sc->sc_msgoutq = 0; + sc->sc_flags = 0; + + if (sc->sc_espintr & NCRINTR_RESEL) { + /* + * If we're trying to select a + * target ourselves, push our command + * back into the ready list. + */ + if (sc->sc_state == NCR_SELECTING) { + NCR_MISC(("backoff selector ")); + sc_link = sc->sc_nexus->xs->sc_link; + ti = &sc->sc_tinfo[sc_link->target]; + TAILQ_INSERT_HEAD(&sc->ready_list, + sc->sc_nexus, chain); + ecb = sc->sc_nexus = NULL; + } + sc->sc_state = NCR_RESELECTED; + if (sc->sc_phase != MESSAGE_IN_PHASE) { + /* + * Things are seriously fucked up. + * Pull the brakes, i.e. reset + */ + printf("%s: target didn't identify\n", + sc->sc_dev.dv_xname); + ncr53c9x_init(sc, 1); + return 1; + } + if ((NCR_READ_REG(sc, NCR_FFLAG) & NCRFIFO_FF) + != 2) { + printf("%s: RESELECT: " + "%d bytes in FIFO!\n", + sc->sc_dev.dv_xname, + NCR_READ_REG(sc, NCR_FFLAG) & + NCRFIFO_FF); + ncr53c9x_init(sc, 1); + return 1; + } + sc->sc_selid = NCR_READ_REG(sc, NCR_FIFO); + NCR_MISC(("selid=0x%2x ", sc->sc_selid)); + + /* Handle identify message */ + ncr53c9x_msgin(sc); + + if (sc->sc_state != NCR_CONNECTED) { + /* IDENTIFY fail?! */ + printf("%s: identify failed\n", + sc->sc_dev.dv_xname); + ncr53c9x_init(sc, 1); + return 1; + } + continue; /* ie. next phase expected soon */ + } + +#define NCRINTR_DONE (NCRINTR_FC|NCRINTR_BS) + if ((sc->sc_espintr & NCRINTR_DONE) == NCRINTR_DONE) { + ecb = sc->sc_nexus; + if (!ecb) + panic("esp: not nexus at sc->sc_nexus"); + + sc_link = ecb->xs->sc_link; + ti = &sc->sc_tinfo[sc_link->target]; + + switch (sc->sc_espstep) { + case 0: + printf("%s: select timeout/no " + "disconnect\n", + sc->sc_dev.dv_xname); + ecb->xs->error = XS_SELTIMEOUT; + goto finish; + case 1: + if ((ti->flags & T_NEGOTIATE) == 0) { + printf("%s: step 1 & !NEG\n", + sc->sc_dev.dv_xname); + goto reset; + } + if (sc->sc_phase != MESSAGE_OUT_PHASE) { + printf("%s: !MSGOUT\n", + sc->sc_dev.dv_xname); + goto reset; + } + /* Start negotiating */ + ti->period = sc->sc_minsync; + ti->offset = 15; + sc->sc_flags |= NCR_SYNCHNEGO; + ncr53c9x_sched_msgout(SEND_SDTR); + break; + case 3: + /* + * Grr, this is supposed to mean + * "target left command phase + * prematurely". It seems to happen + * regularly when sync mode is on. + * Look at FIFO to see if command + * went out. + * (Timing problems?) + */ + if ((NCR_READ_REG(sc, NCR_FFLAG) + & NCRFIFO_FF) == 0) { + /* Hope for the best.. */ + break; + } + printf("(%s:%d:%d): selection failed;" + " %d left in FIFO " + "[intr %x, stat %x, step %d]\n", + sc->sc_dev.dv_xname, + sc_link->target, + sc_link->lun, + NCR_READ_REG(sc, NCR_FFLAG) + & NCRFIFO_FF, + sc->sc_espintr, sc->sc_espstat, + sc->sc_espstep); + NCRCMD(sc, NCRCMD_FLUSH); + ncr53c9x_sched_msgout(SEND_ABORT); + return 1; + case 2: + /* Select stuck at Command Phase */ + NCRCMD(sc, NCRCMD_FLUSH); + case 4: + /* So far, everything went fine */ + break; + } +#if 0 + if (ecb->xs->flags & SCSI_RESET) + ncr53c9x_sched_msgout(SEND_DEV_RESET); + else if (ti->flags & T_NEGOTIATE) + ncr53c9x_sched_msgout( + SEND_IDENTIFY | SEND_SDTR); + else + ncr53c9x_sched_msgout(SEND_IDENTIFY); +#endif + + ecb->flags |= ECB_NEXUS; + ti->lubusy |= (1 << sc_link->lun); + + sc->sc_prevphase = INVALID_PHASE; /* ?? */ + /* Do an implicit RESTORE POINTERS. */ + sc->sc_dp = ecb->daddr; + sc->sc_dleft = ecb->dleft; + + /* On our first connection, schedule a timeout. */ + if ((ecb->xs->flags & SCSI_POLL) == 0) + timeout(ncr53c9x_timeout, ecb, + (ecb->timeout * hz) / 1000); + + sc->sc_state = NCR_CONNECTED; + break; + } else { + printf("%s: unexpected status after select" + ": [intr %x, stat %x, step %x]\n", + sc->sc_dev.dv_xname, + sc->sc_espintr, sc->sc_espstat, + sc->sc_espstep); + NCRCMD(sc, NCRCMD_FLUSH); + DELAY(1); + goto reset; + } + if (sc->sc_state == NCR_IDLE) { + printf("%s: stray interrupt\n", + sc->sc_dev.dv_xname); + return 0; + } + break; + + case NCR_CONNECTED: + if (sc->sc_flags & NCR_ICCS) { + u_char msg; + + sc->sc_flags &= ~NCR_ICCS; + + if (!(sc->sc_espintr & NCRINTR_DONE)) { + printf("%s: ICCS: " + ": [intr %x, stat %x, step %x]\n", + sc->sc_dev.dv_xname, + sc->sc_espintr, sc->sc_espstat, + sc->sc_espstep); + } + if ((NCR_READ_REG(sc, NCR_FFLAG) + & NCRFIFO_FF) != 2) { + int i = (NCR_READ_REG(sc, NCR_FFLAG) + & NCRFIFO_FF) - 2; + while (i--) + (void) NCR_READ_REG(sc, + NCR_FIFO); + } + ecb->stat = NCR_READ_REG(sc, NCR_FIFO); + msg = NCR_READ_REG(sc, NCR_FIFO); + NCR_PHASE(("", ecb->stat, msg)); + if (msg == MSG_CMDCOMPLETE) { + ecb->xs->resid = ecb->dleft = + sc->sc_dleft; + sc->sc_state = NCR_CMDCOMPLETE; + } else + printf("%s: STATUS_PHASE: msg %d\n", + sc->sc_dev.dv_xname, msg); + NCRCMD(sc, NCRCMD_MSGOK); + continue; /* ie. wait for disconnect */ + } + break; + default: + panic("%s: invalid state: %d", + sc->sc_dev.dv_xname, + sc->sc_state); + } + + /* + * Driver is now in state NCR_CONNECTED, i.e. we + * have a current command working the SCSI bus. + */ + if (sc->sc_state != NCR_CONNECTED || ecb == NULL) { + panic("esp no nexus"); + } + + switch (sc->sc_phase) { + case MESSAGE_OUT_PHASE: + NCR_PHASE(("MESSAGE_OUT_PHASE ")); + ncr53c9x_msgout(sc); + sc->sc_prevphase = MESSAGE_OUT_PHASE; + break; + case MESSAGE_IN_PHASE: + NCR_PHASE(("MESSAGE_IN_PHASE ")); + if (sc->sc_espintr & NCRINTR_BS) { + NCRCMD(sc, NCRCMD_FLUSH); + sc->sc_flags |= NCR_WAITI; + NCRCMD(sc, NCRCMD_TRANS); + } else if (sc->sc_espintr & NCRINTR_FC) { + if ((sc->sc_flags & NCR_WAITI) == 0) { + printf("%s: MSGIN: unexpected FC bit: " + "[intr %x, stat %x, step %x]\n", + sc->sc_dev.dv_xname, + sc->sc_espintr, sc->sc_espstat, + sc->sc_espstep); + } + sc->sc_flags &= ~NCR_WAITI; + ncr53c9x_msgin(sc); + } else { + printf("%s: MSGIN: weird bits: " + "[intr %x, stat %x, step %x]\n", + sc->sc_dev.dv_xname, + sc->sc_espintr, sc->sc_espstat, + sc->sc_espstep); + } + sc->sc_prevphase = MESSAGE_IN_PHASE; + break; + case COMMAND_PHASE: { + /* well, this means send the command again */ + u_char *cmd = (u_char *)&ecb->cmd; + int i; + + NCR_PHASE(("COMMAND_PHASE 0x%02x (%d) ", + ecb->cmd.opcode, ecb->clen)); + if (NCR_READ_REG(sc, NCR_FFLAG) & NCRFIFO_FF) { + NCRCMD(sc, NCRCMD_FLUSH); + DELAY(1); + } + /* Now the command into the FIFO */ + for (i = 0; i < ecb->clen; i++) + NCR_WRITE_REG(sc, NCR_FIFO, *cmd++); + NCRCMD(sc, NCRCMD_TRANS); + sc->sc_prevphase = COMMAND_PHASE; + } + break; + case DATA_OUT_PHASE: + NCR_PHASE(("DATA_OUT_PHASE [%ld] ",(long)sc->sc_dleft)); + NCRCMD(sc, NCRCMD_FLUSH); + size = min(sc->sc_dleft, sc->sc_maxxfer); + NCRDMA_SETUP(sc, &sc->sc_dp, &sc->sc_dleft, + 0, &size); + sc->sc_prevphase = DATA_OUT_PHASE; + goto setup_xfer; + case DATA_IN_PHASE: + NCR_PHASE(("DATA_IN_PHASE ")); + if (sc->sc_rev == NCR_VARIANT_ESP100) + NCRCMD(sc, NCRCMD_FLUSH); + size = min(sc->sc_dleft, sc->sc_maxxfer); + NCRDMA_SETUP(sc, &sc->sc_dp, &sc->sc_dleft, + 1, &size); + sc->sc_prevphase = DATA_IN_PHASE; + setup_xfer: + /* Program the SCSI counter */ + NCR_WRITE_REG(sc, NCR_TCL, size); + NCR_WRITE_REG(sc, NCR_TCM, size >> 8); + if (sc->sc_cfg2 & NCRCFG2_FE) { + NCR_WRITE_REG(sc, NCR_TCH, size >> 16); + } + /* load the count in */ + NCRCMD(sc, NCRCMD_NOP|NCRCMD_DMA); + + /* + * Note that if `size' is 0, we've already transceived + * all the bytes we want but we're still in DATA PHASE. + * Apparently, the device needs padding. Also, a + * transfer size of 0 means "maximum" to the chip + * DMA logic. + */ + NCRCMD(sc, + (size==0?NCRCMD_TRPAD:NCRCMD_TRANS)|NCRCMD_DMA); + NCRDMA_GO(sc); + return 1; + case STATUS_PHASE: + NCR_PHASE(("STATUS_PHASE ")); + sc->sc_flags |= NCR_ICCS; + NCRCMD(sc, NCRCMD_ICCS); + sc->sc_prevphase = STATUS_PHASE; + break; + case INVALID_PHASE: + break; + default: + printf("%s: unexpected bus phase; resetting\n", + sc->sc_dev.dv_xname); + goto reset; + } + } + panic("esp: should not get here.."); + +reset: + ncr53c9x_init(sc, 1); + return 1; + +finish: + untimeout(ncr53c9x_timeout, ecb); + ncr53c9x_done(sc, ecb); + goto out; + +sched: + sc->sc_state = NCR_IDLE; + ncr53c9x_sched(sc); + goto out; + +out: + return 1; +} + +void +ncr53c9x_abort(sc, ecb) + struct ncr53c9x_softc *sc; + struct ncr53c9x_ecb *ecb; +{ + + /* 2 secs for the abort */ + ecb->timeout = NCR_ABORT_TIMEOUT; + ecb->flags |= ECB_ABORT; + + if (ecb == sc->sc_nexus) { + /* + * If we're still selecting, the message will be scheduled + * after selection is complete. + */ + if (sc->sc_state == NCR_CONNECTED) + ncr53c9x_sched_msgout(SEND_ABORT); + + /* + * Reschedule timeout. First, cancel a queued timeout (if any) + * in case someone decides to call ncr53c9x_abort() from + * elsewhere. + */ + untimeout(ncr53c9x_timeout, ecb); + timeout(ncr53c9x_timeout, ecb, (ecb->timeout * hz) / 1000); + } else { + ncr53c9x_dequeue(sc, ecb); + TAILQ_INSERT_HEAD(&sc->ready_list, ecb, chain); + if (sc->sc_state == NCR_IDLE) + ncr53c9x_sched(sc); + } +} + +void +ncr53c9x_timeout(arg) + void *arg; +{ + struct ncr53c9x_ecb *ecb = arg; + struct scsi_xfer *xs = ecb->xs; + struct scsi_link *sc_link = xs->sc_link; + struct ncr53c9x_softc *sc = sc_link->adapter_softc; + int s; + + sc_print_addr(sc_link); + printf("%s: timed out [ecb %p (flags 0x%x, dleft %x, stat %x)], " + "", + sc->sc_dev.dv_xname, + ecb, ecb->flags, ecb->dleft, ecb->stat, + sc->sc_state, sc->sc_nexus, sc->sc_phase, sc->sc_prevphase, + (long)sc->sc_dleft, sc->sc_msgpriq, sc->sc_msgout, + NCRDMA_ISACTIVE(sc) ? "DMA active" : ""); +#if NCR53C9X_DEBUG > 0 + printf("TRACE: %s.", ecb->trace); +#endif + + s = splbio(); + + if (ecb->flags & ECB_ABORT) { + /* abort timed out */ + printf(" AGAIN\n"); + ncr53c9x_init(sc, 1); + } else { + /* abort the operation that has timed out */ + printf("\n"); + xs->error = XS_TIMEOUT; + ncr53c9x_abort(sc, ecb); + } + + splx(s); +} diff --git a/sys/dev/ic/ncr53c9xreg.h b/sys/dev/ic/ncr53c9xreg.h new file mode 100644 index 00000000000..4b848ed8c2a --- /dev/null +++ b/sys/dev/ic/ncr53c9xreg.h @@ -0,0 +1,150 @@ +/* $OpenBSD: ncr53c9xreg.h,v 1.1 1997/02/27 13:57:23 briggs Exp $ */ +/* $NetBSD: ncr53c9xreg.h,v 1.1 1997/02/27 01:12:08 thorpej Exp $ */ + +/* + * Copyright (c) 1994 Peter Galbavy. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Peter Galbavy. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Register addresses, relative to some base address + */ + +#define NCR_TCL 0x00 /* RW - Transfer Count Low */ +#define NCR_TCM 0x01 /* RW - Transfer Count Mid */ +#define NCR_TCH 0x0e /* RW - Transfer Count High */ + /* NOT on 53C90 */ + +#define NCR_FIFO 0x02 /* RW - FIFO data */ + +#define NCR_CMD 0x03 /* RW - Command (2 deep) */ +#define NCRCMD_DMA 0x80 /* DMA Bit */ +#define NCRCMD_NOP 0x00 /* No Operation */ +#define NCRCMD_FLUSH 0x01 /* Flush FIFO */ +#define NCRCMD_RSTCHIP 0x02 /* Reset Chip */ +#define NCRCMD_RSTSCSI 0x03 /* Reset SCSI Bus */ +#define NCRCMD_RESEL 0x40 /* Reselect Sequence */ +#define NCRCMD_SELNATN 0x41 /* Select without ATN */ +#define NCRCMD_SELATN 0x42 /* Select with ATN */ +#define NCRCMD_SELATNS 0x43 /* Select with ATN & Stop */ +#define NCRCMD_ENSEL 0x44 /* Enable (Re)Selection */ +#define NCRCMD_DISSEL 0x45 /* Disable (Re)Selection */ +#define NCRCMD_SELATN3 0x46 /* Select with ATN3 */ +#define NCRCMD_RESEL3 0x47 /* Reselect3 Sequence */ +#define NCRCMD_SNDMSG 0x20 /* Send Message */ +#define NCRCMD_SNDSTAT 0x21 /* Send Status */ +#define NCRCMD_SNDDATA 0x22 /* Send Data */ +#define NCRCMD_DISCSEQ 0x23 /* Disconnect Sequence */ +#define NCRCMD_TERMSEQ 0x24 /* Terminate Sequence */ +#define NCRCMD_TCCS 0x25 /* Target Command Comp Seq */ +#define NCRCMD_DISC 0x27 /* Disconnect */ +#define NCRCMD_RECMSG 0x28 /* Receive Message */ +#define NCRCMD_RECCMD 0x29 /* Receive Command */ +#define NCRCMD_RECDATA 0x2a /* Receive Data */ +#define NCRCMD_RECCSEQ 0x2b /* Receive Command Sequence*/ +#define NCRCMD_ABORT 0x04 /* Target Abort DMA */ +#define NCRCMD_TRANS 0x10 /* Transfer Information */ +#define NCRCMD_ICCS 0x11 /* Initiator Cmd Comp Seq */ +#define NCRCMD_MSGOK 0x12 /* Message Accepted */ +#define NCRCMD_TRPAD 0x18 /* Transfer Pad */ +#define NCRCMD_SETATN 0x1a /* Set ATN */ +#define NCRCMD_RSTATN 0x1b /* Reset ATN */ + +#define NCR_STAT 0x04 /* RO - Status */ +#define NCRSTAT_INT 0x80 /* Interrupt */ +#define NCRSTAT_GE 0x40 /* Gross Error */ +#define NCRSTAT_PE 0x20 /* Parity Error */ +#define NCRSTAT_TC 0x10 /* Terminal Count */ +#define NCRSTAT_VGC 0x08 /* Valid Group Code */ +#define NCRSTAT_PHASE 0x07 /* Phase bits */ + +#define NCR_SELID 0x04 /* WO - Select/Reselect Bus ID */ + +#define NCR_INTR 0x05 /* RO - Interrupt */ +#define NCRINTR_SBR 0x80 /* SCSI Bus Reset */ +#define NCRINTR_ILL 0x40 /* Illegal Command */ +#define NCRINTR_DIS 0x20 /* Disconnect */ +#define NCRINTR_BS 0x10 /* Bus Service */ +#define NCRINTR_FC 0x08 /* Function Complete */ +#define NCRINTR_RESEL 0x04 /* Reselected */ +#define NCRINTR_SELATN 0x02 /* Select with ATN */ +#define NCRINTR_SEL 0x01 /* Selected */ + +#define NCR_TIMEOUT 0x05 /* WO - Select/Reselect Timeout */ + +#define NCR_STEP 0x06 /* RO - Sequence Step */ +#define NCRSTEP_MASK 0x07 /* the last 3 bits */ +#define NCRSTEP_DONE 0x04 /* command went out */ + +#define NCR_SYNCTP 0x06 /* WO - Synch Transfer Period */ + /* Default 5 (53C9X) */ + +#define NCR_FFLAG 0x07 /* RO - FIFO Flags */ +#define NCRFIFO_SS 0xe0 /* Sequence Step (Dup) */ +#define NCRFIFO_FF 0x1f /* Bytes in FIFO */ + +#define NCR_SYNCOFF 0x07 /* WO - Synch Offset */ + /* 0 = ASYNC */ + /* 1 - 15 = SYNC bytes */ + +#define NCR_CFG1 0x08 /* RW - Configuration #1 */ +#define NCRCFG1_SLOW 0x80 /* Slow Cable Mode */ +#define NCRCFG1_SRR 0x40 /* SCSI Reset Rep Int Dis */ +#define NCRCFG1_PTEST 0x20 /* Parity Test Mod */ +#define NCRCFG1_PARENB 0x10 /* Enable Parity Check */ +#define NCRCFG1_CTEST 0x08 /* Enable Chip Test */ +#define NCRCFG1_BUSID 0x07 /* Bus ID */ + +#define NCR_CCF 0x09 /* WO - Clock Conversion Factor */ + /* 0 = 35.01 - 40Mhz */ + /* NEVER SET TO 1 */ + /* 2 = 10Mhz */ + /* 3 = 10.01 - 15Mhz */ + /* 4 = 15.01 - 20Mhz */ + /* 5 = 20.01 - 25Mhz */ + /* 6 = 25.01 - 30Mhz */ + /* 7 = 30.01 - 35Mhz */ + +#define NCR_TEST 0x0a /* WO - Test (Chip Test Only) */ + +#define NCR_CFG2 0x0b /* RW - Configuration #2 */ +#define NCRCFG2_RSVD 0xa0 /* reserved */ +#define NCRCFG2_FE 0x40 /* Features Enable */ +#define NCRCFG2_DREQ 0x10 /* DREQ High Impedance */ +#define NCRCFG2_SCSI2 0x08 /* SCSI-2 Enable */ +#define NCRCFG2_BPA 0x04 /* Target Bad Parity Abort */ +#define NCRCFG2_RPE 0x02 /* Register Parity Error */ +#define NCRCFG2_DPE 0x01 /* DMA Parity Error */ + +/* Config #3 only on 53C9X */ +#define NCR_CFG3 0x0c /* RW - Configuration #3 */ +#define NCRCFG3_RSVD 0xe0 /* reserved */ +#define NCRCFG3_IDM 0x10 /* ID Message Res Check */ +#define NCRCFG3_QTE 0x08 /* Queue Tag Enable */ +#define NCRCFG3_CDB 0x04 /* CDB 10-bytes OK */ +#define NCRCFG3_FSCSI 0x02 /* Fast SCSI */ +#define NCRCFG3_FCLK 0x01 /* Fast Clock (>25Mhz) */ diff --git a/sys/dev/ic/ncr53c9xvar.h b/sys/dev/ic/ncr53c9xvar.h new file mode 100644 index 00000000000..e217cf77905 --- /dev/null +++ b/sys/dev/ic/ncr53c9xvar.h @@ -0,0 +1,368 @@ +/* $OpenBSD: ncr53c9xvar.h,v 1.1 1997/02/27 13:57:23 briggs Exp $ */ +/* $NetBSD: ncr53c9xvar.h,v 1.1 1997/02/27 01:12:09 thorpej Exp $ */ + +/* + * Copyright (c) 1997 Jason R. Thorpe. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the NetBSD Project + * by Jason R. Thorpe. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (c) 1994 Peter Galbavy. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Peter Galbavy. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define NCR53C9X_DEBUG 0 + +#define NCR_ABORT_TIMEOUT 2000 /* time to wait for abort */ + +#define FREQTOCCF(freq) (((freq + 4) / 5)) + +/* + * NCR 53c9x variants. Note, these values are used as indexes into + * a table; don't modify them unless you know what you're doing. + */ +#define NCR_VARIANT_ESP100 0 +#define NCR_VARIANT_ESP100A 1 +#define NCR_VARIANT_ESP200 2 +#define NCR_VARIANT_NCR53C94 3 +#define NCR_VARIANT_NCR53C96 4 +#define NCR_VARIANT_MAX 5 + +/* + * ECB. Holds additional information for each SCSI command Comments: We + * need a separate scsi command block because we may need to overwrite it + * with a request sense command. Basicly, we refrain from fiddling with + * the scsi_xfer struct (except do the expected updating of return values). + * We'll generally update: xs->{flags,resid,error,sense,status} and + * occasionally xs->retries. + */ +struct ncr53c9x_ecb { + TAILQ_ENTRY(ncr53c9x_ecb) chain; + struct scsi_xfer *xs; /* SCSI xfer ctrl block from above */ + int flags; +#define ECB_ALLOC 0x01 +#define ECB_NEXUS 0x02 +#define ECB_SENSE 0x04 +#define ECB_ABORT 0x40 +#define ECB_RESET 0x80 + int timeout; + + struct scsi_generic cmd; /* SCSI command block */ + int clen; + char *daddr; /* Saved data pointer */ + int dleft; /* Residue */ + u_char stat; /* SCSI status byte */ + +#if NCR53C9X_DEBUG > 0 + char trace[1000]; +#endif +}; +#if NCR53C9X_DEBUG > 0 +#define ECB_TRACE(ecb, msg, a, b) do { \ + const char *f = "[" msg "]"; \ + int n = strlen((ecb)->trace); \ + if (n < (sizeof((ecb)->trace)-100)) \ + sprintf((ecb)->trace + n, f, a, b); \ +} while(0) +#else +#define ECB_TRACE(ecb, msg, a, b) +#endif + +/* + * Some info about each (possible) target on the SCSI bus. This should + * probably have been a "per target+lunit" structure, but we'll leave it at + * this for now. Is there a way to reliably hook it up to sc->fordriver?? + */ +struct ncr53c9x_tinfo { + int cmds; /* #commands processed */ + int dconns; /* #disconnects */ + int touts; /* #timeouts */ + int perrs; /* #parity errors */ + int senses; /* #request sense commands sent */ + ushort lubusy; /* What local units/subr. are busy? */ + u_char flags; +#define T_NEED_TO_RESET 0x01 /* Should send a BUS_DEV_RESET */ +#define T_NEGOTIATE 0x02 /* (Re)Negotiate synchronous options */ +#define T_BUSY 0x04 /* Target is busy, i.e. cmd in progress */ +#define T_SYNCMODE 0x08 /* sync mode has been negotiated */ +#define T_SYNCHOFF 0x10 /* .. */ +#define T_RSELECTOFF 0x20 /* .. */ + u_char period; /* Period suggestion */ + u_char offset; /* Offset suggestion */ +} tinfo_t; + +/* Register a linenumber (for debugging) */ +#define LOGLINE(p) + +#define NCR_SHOWECBS 0x01 +#define NCR_SHOWINTS 0x02 +#define NCR_SHOWCMDS 0x04 +#define NCR_SHOWMISC 0x08 +#define NCR_SHOWTRAC 0x10 +#define NCR_SHOWSTART 0x20 +#define NCR_SHOWPHASE 0x40 +#define NCR_SHOWDMA 0x80 +#define NCR_SHOWCCMDS 0x100 +#define NCR_SHOWMSGS 0x200 + +#ifdef NCR53C9X_DEBUG +extern int ncr53c9x_debug; +#define NCR_ECBS(str) \ + do {if (ncr53c9x_debug & NCR_SHOWECBS) printf str;} while (0) +#define NCR_MISC(str) \ + do {if (ncr53c9x_debug & NCR_SHOWMISC) printf str;} while (0) +#define NCR_INTS(str) \ + do {if (ncr53c9x_debug & NCR_SHOWINTS) printf str;} while (0) +#define NCR_TRACE(str) \ + do {if (ncr53c9x_debug & NCR_SHOWTRAC) printf str;} while (0) +#define NCR_CMDS(str) \ + do {if (ncr53c9x_debug & NCR_SHOWCMDS) printf str;} while (0) +#define NCR_START(str) \ + do {if (ncr53c9x_debug & NCR_SHOWSTART) printf str;}while (0) +#define NCR_PHASE(str) \ + do {if (ncr53c9x_debug & NCR_SHOWPHASE) printf str;}while (0) +#define NCR_DMA(str) \ + do {if (ncr53c9x_debug & NCR_SHOWDMA) printf str;}while (0) +#define NCR_MSGS(str) \ + do {if (ncr53c9x_debug & NCR_SHOWMSGS) printf str;}while (0) +#else +#define NCR_ECBS(str) +#define NCR_MISC(str) +#define NCR_INTS(str) +#define NCR_TRACE(str) +#define NCR_CMDS(str) +#define NCR_START(str) +#define NCR_PHASE(str) +#define NCR_DMA(str) +#define NCR_MSGS(str) +#endif + +#define NCR_MAX_MSG_LEN 8 + +struct ncr53c9x_softc; + +/* + * Function switch used as glue to MD code. + */ +struct ncr53c9x_glue { + /* Mandatory entry points. */ + u_char (*gl_read_reg) __P((struct ncr53c9x_softc *, int)); + void (*gl_write_reg) __P((struct ncr53c9x_softc *, int, u_char)); + int (*gl_dma_isintr) __P((struct ncr53c9x_softc *)); + void (*gl_dma_reset) __P((struct ncr53c9x_softc *)); + int (*gl_dma_intr) __P((struct ncr53c9x_softc *)); + int (*gl_dma_setup) __P((struct ncr53c9x_softc *, + caddr_t *, size_t *, int, size_t *)); + void (*gl_dma_go) __P((struct ncr53c9x_softc *)); + void (*gl_dma_stop) __P((struct ncr53c9x_softc *)); + int (*gl_dma_isactive) __P((struct ncr53c9x_softc *)); + + /* Optional entry points. */ + void (*gl_clear_latched_intr) __P((struct ncr53c9x_softc *)); +}; + +struct ncr53c9x_softc { + struct device sc_dev; /* us as a device */ + + struct evcnt sc_intrcnt; /* intr count */ + struct scsi_link sc_link; /* scsi lint struct */ + + struct ncr53c9x_glue *sc_glue; /* glue to MD code */ + + /* register defaults */ + u_char sc_cfg1; /* Config 1 */ + u_char sc_cfg2; /* Config 2, not ESP100 */ + u_char sc_cfg3; /* Config 3, only ESP200 */ + u_char sc_ccf; /* Clock Conversion */ + u_char sc_timeout; + + /* register copies, see espreadregs() */ + u_char sc_espintr; + u_char sc_espstat; + u_char sc_espstep; + u_char sc_espfflags; + + /* Lists of command blocks */ + TAILQ_HEAD(ecb_list, ncr53c9x_ecb) free_list, + ready_list, + nexus_list; + + struct ncr53c9x_ecb *sc_nexus; /* current command */ + struct ncr53c9x_ecb sc_ecb[3*8]; /* three per target */ + struct ncr53c9x_tinfo sc_tinfo[8]; + + /* Data about the current nexus (updated for every cmd switch) */ + caddr_t sc_dp; /* Current data pointer */ + ssize_t sc_dleft; /* Data left to transfer */ + + /* Adapter state */ + int sc_phase; /* Copy of what bus phase we are in */ + int sc_prevphase; /* Copy of what bus phase we were in */ + u_char sc_state; /* State applicable to the adapter */ + u_char sc_flags; + u_char sc_selid; + u_char sc_lastcmd; + + /* Message stuff */ + u_char sc_msgpriq; /* One or more messages to send (encoded) */ + u_char sc_msgout; /* What message is on its way out? */ + u_char sc_msgoutq; /* What messages have been sent so far? */ + u_char sc_omess[NCR_MAX_MSG_LEN]; + caddr_t sc_omp; /* Message pointer (for multibyte messages) */ + size_t sc_omlen; + u_char sc_imess[NCR_MAX_MSG_LEN + 1]; + caddr_t sc_imp; /* Message pointer (for multibyte messages) */ + size_t sc_imlen; + + /* hardware/openprom stuff */ + int sc_freq; /* Freq in HZ */ + int sc_id; /* our scsi id */ + int sc_rev; /* esp revision */ + int sc_minsync; /* minimum sync period / 4 */ + int sc_maxxfer; /* maximum transfer size */ +}; + +/* values for sc_state */ +#define NCR_IDLE 1 /* waiting for something to do */ +#define NCR_SELECTING 2 /* SCSI command is arbiting */ +#define NCR_RESELECTED 3 /* Has been reselected */ +#define NCR_CONNECTED 4 /* Actively using the SCSI bus */ +#define NCR_DISCONNECT 5 /* MSG_DISCONNECT received */ +#define NCR_CMDCOMPLETE 6 /* MSG_CMDCOMPLETE received */ +#define NCR_CLEANING 7 +#define NCR_SBR 8 /* Expect a SCSI RST because we commanded it */ + +/* values for sc_flags */ +#define NCR_DROP_MSGI 0x01 /* Discard all msgs (parity err detected) */ +#define NCR_ABORTING 0x02 /* Bailing out */ +#define NCR_DOINGDMA 0x04 /* The FIFO data path is active! */ +#define NCR_SYNCHNEGO 0x08 /* Synch negotiation in progress. */ +#define NCR_ICCS 0x10 /* Expect status phase results */ +#define NCR_WAITI 0x20 /* Waiting for non-DMA data to arrive */ +#define NCR_ATN 0x40 /* ATN asserted */ + +/* values for sc_msgout */ +#define SEND_DEV_RESET 0x01 +#define SEND_PARITY_ERROR 0x02 +#define SEND_INIT_DET_ERR 0x04 +#define SEND_REJECT 0x08 +#define SEND_IDENTIFY 0x10 +#define SEND_ABORT 0x20 +#define SEND_SDTR 0x40 +#define SEND_WDTR 0x80 + +/* SCSI Status codes */ +#define ST_MASK 0x3e /* bit 0,6,7 is reserved */ + +/* phase bits */ +#define IOI 0x01 +#define CDI 0x02 +#define MSGI 0x04 + +/* Information transfer phases */ +#define DATA_OUT_PHASE (0) +#define DATA_IN_PHASE (IOI) +#define COMMAND_PHASE (CDI) +#define STATUS_PHASE (CDI|IOI) +#define MESSAGE_OUT_PHASE (MSGI|CDI) +#define MESSAGE_IN_PHASE (MSGI|CDI|IOI) + +#define PHASE_MASK (MSGI|CDI|IOI) + +/* Some pseudo phases for getphase()*/ +#define BUSFREE_PHASE 0x100 /* Re/Selection no longer valid */ +#define INVALID_PHASE 0x101 /* Re/Selection valid, but no REQ yet */ +#define PSEUDO_PHASE 0x100 /* "pseudo" bit */ + +/* + * Macros to read and write the chip's registers. + */ +#define NCR_READ_REG(sc, reg) \ + (*(sc)->sc_glue->gl_read_reg)((sc), (reg)) +#define NCR_WRITE_REG(sc, reg, val) \ + (*(sc)->sc_glue->gl_write_reg)((sc), (reg), (val)) + +#ifdef NCR53C9X_DEBUG +#define NCRCMD(sc, cmd) do { \ + if (ncr53c9x_debug & NCR_SHOWCCMDS) \ + printf("", (unsigned)cmd); \ + sc->sc_lastcmd = cmd; \ + NCR_WRITE_REG(sc, NCR_CMD, cmd); \ +} while (0) +#else +#define NCRCMD(sc, cmd) NCR_WRITE_REG(sc, NCR_CMD, cmd) +#endif + +/* + * DMA macros for NCR53c9x + */ +#define NCRDMA_ISINTR(sc) (*(sc)->sc_glue->gl_dma_isintr)((sc)) +#define NCRDMA_RESET(sc) (*(sc)->sc_glue->gl_dma_reset)((sc)) +#define NCRDMA_INTR(sc) (*(sc)->sc_glue->gl_dma_intr)((sc)) +#define NCRDMA_SETUP(sc, addr, len, datain, dmasize) \ + (*(sc)->sc_glue->gl_dma_setup)((sc), (addr), (len), (datain), (dmasize)) +#define NCRDMA_GO(sc) (*(sc)->sc_glue->gl_dma_go)((sc)) +#define NCRDMA_ISACTIVE(sc) (*(sc)->sc_glue->gl_dma_isactive)((sc)) + +/* + * Macro to convert the chip register Clock Per Byte value to + * Sunchronous Transfer Period. + */ +#define ncr53c9x_cpb2stp(sc, cpb) \ + ((250 * (cpb)) / (sc)->sc_freq) + +void ncr53c9x_attach __P((struct ncr53c9x_softc *, + struct scsi_adapter *, struct scsi_device *)); +int ncr53c9x_scsi_cmd __P((struct scsi_xfer *)); +void ncr53c9x_reset __P((struct ncr53c9x_softc *)); +int ncr53c9x_intr __P((struct ncr53c9x_softc *));