diff options
author | gingold <gingold@cvs.openbsd.org> | 1997-10-14 07:25:35 +0000 |
---|---|---|
committer | gingold <gingold@cvs.openbsd.org> | 1997-10-14 07:25:35 +0000 |
commit | f6491d400ca651a8a1493d72c6a74c622aa231b1 (patch) | |
tree | 39f29255154f230f637c12e9214f12a9e64bd9d4 /sys/arch/kbus/dev/sbic.c.mvme | |
parent | 7a9e3739a66bd0fadfdc611c72e879fcc6f9ef01 (diff) |
Gingold's port for kbus Series5 machine. Not fully finished and not very stable
Diffstat (limited to 'sys/arch/kbus/dev/sbic.c.mvme')
-rw-r--r-- | sys/arch/kbus/dev/sbic.c.mvme | 2719 |
1 files changed, 2719 insertions, 0 deletions
diff --git a/sys/arch/kbus/dev/sbic.c.mvme b/sys/arch/kbus/dev/sbic.c.mvme new file mode 100644 index 00000000000..adf244611ac --- /dev/null +++ b/sys/arch/kbus/dev/sbic.c.mvme @@ -0,0 +1,2719 @@ +/* $OpenBSD: sbic.c.mvme,v 1.1 1997/10/14 07:25:29 gingold Exp $ */ +/* $NetBSD: sbic.c,v 1.2 1996/04/23 16:32:54 chuck Exp $ */ +/* + * Changes Copyright (c) 1996 Steve Woodford + * Original Copyright (c) 1994 Christian E. Hopps + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Van Jacobson of Lawrence Berkeley Laboratory. + * + * 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 the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)scsi.c 7.5 (Berkeley) 5/4/91 + */ + +/* + * Steve Woodford (SCW), Apr, 1996 + * MVME147S WD33C93 Scsi Bus Interface Controller driver, + * + * Basically a de-loused and tidied up version of the Amiga AMD 33C93 driver. + * + * The original driver used features which required at least a WD33C93A + * chip. The '147 has the original WD33C93 chip (no 'A' suffix). + * + * This version of the driver is pretty well generic, so should work with + * any flavour of WD33C93 chip. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kernel.h> /* For hz */ +#include <sys/disklabel.h> +#include <sys/dkstat.h> +#include <sys/buf.h> +#include <scsi/scsi_all.h> +#include <scsi/scsiconf.h> +#include <vm/vm.h> +#include <vm/vm_kern.h> +#include <vm/vm_page.h> +#include <vm/pmap.h> +#include <machine/pmap.h> +#include <dev/dmavar.h> +#include <dev/sbicreg.h> +#include <dev/sbicvar.h> +#include <machine/autoconf.h> + +/* + * Since I can't find this in any other header files + */ +#define SCSI_PHASE(reg) (reg&0x07) + +/* + * SCSI delays + * In u-seconds, primarily for state changes on the SPC. + */ +#define SBIC_CMD_WAIT 50000 /* wait per step of 'immediate' cmds */ +#define SBIC_DATA_WAIT 50000 /* wait per data in/out step */ +#define SBIC_INIT_WAIT 50000 /* wait per step (both) during init */ + +/* + * Convenience macro for waiting for a particular sbic event + */ +#define SBIC_WAIT(regs, until, timeo) sbicwait(regs, until, timeo, __LINE__) + +int sbicicmd __P((struct sbic_softc *, void *, int, void *, int)); +int sbicgo __P((struct sbic_softc *, struct scsi_xfer *)); +int sbicdmaok __P((struct sbic_softc *, struct scsi_xfer *)); +int sbicwait __P((sbic_regmap_p, u_char, int , int)); +int sbiccheckdmap __P((void *, u_long, u_long)); +u_char sbicselectbus __P((struct sbic_softc *)); +int sbicxfout __P((sbic_regmap_p, int, void *)); +int sbicxfin __P((sbic_regmap_p, int, void *)); +int sbicfromscsiperiod __P((struct sbic_softc *, int)); +int sbictoscsiperiod __P((struct sbic_softc *, int)); +int sbicintr __P((struct sbic_softc *)); +int sbicpoll __P((struct sbic_softc *)); +int sbicnextstate __P((struct sbic_softc *, u_char, u_char)); +int sbicmsgin __P((struct sbic_softc *)); +int sbicabort __P((struct sbic_softc *, char *)); +void sbicxfdone __P((struct sbic_softc *)); +void sbicerror __P((struct sbic_softc *,u_char)); +void sbicreset __P((struct sbic_softc *)); +void sbic_scsidone __P((struct sbic_acb *, int)); +void sbic_sched __P((struct sbic_softc *)); +void sbic_save_ptrs __P((struct sbic_softc *)); +void sbic_load_ptrs __P((struct sbic_softc *)); + +/* + * Synch xfer parameters, and timing conversions + */ +int sbic_min_period = SBIC_SYN_MIN_PERIOD; /* in cycles = f(ICLK,FSn) */ +int sbic_max_offset = SBIC_SYN_MAX_OFFSET; /* pure number */ +int sbic_cmd_wait = SBIC_CMD_WAIT; +int sbic_data_wait = SBIC_DATA_WAIT; +int sbic_init_wait = SBIC_INIT_WAIT; + +/* + * was broken before.. now if you want this you get it for all drives + * on sbic controllers. + */ +u_char sbic_inhibit_sync[8]; +int sbic_enable_reselect = 1; /* Allow Disconnect / Reselect */ +int sbic_no_dma = 0; /* Use PIO transfers instead of DMA */ +int sbic_parallel_operations = 1; /* Allow command queues */ + +/* + * Some useful stuff for debugging purposes + */ +#ifdef DEBUG +int sbicdma_ops = 0; /* total DMA operations */ +int sbicdma_hits = 0; /* number of DMA chains that were contiguous */ +int sbicdma_misses = 0; /* number of DMA chains that were not contiguous */ +int sbicdma_saves = 0; + +#define QPRINTF(a) if (sbic_debug > 1) printf a + +int sbic_debug = 2; /* Debug all chip related things */ +int sync_debug = 0; /* Debug all Synchronous Scsi related things */ +int reselect_debug = 0; /* Debug all reselection related things */ +int report_sense = 0; /* Always print Sense information */ +int data_pointer_debug = 2; /* Debug Data Pointer related things */ + +void sbictimeout __P((struct sbic_softc *dev)); + +#else +#define QPRINTF(a) /* */ +#endif + +extern int kvtop __P((caddr_t addr)); + +/* + * default minphys routine for sbic based controllers + */ +void +sbic_minphys(bp) + struct buf *bp; +{ + /* + * No max transfer at this level. + */ + minphys(bp); +} + + +/* + * Save DMA pointers. Take into account partial transfer. Shut down DMA. + */ +void +sbic_save_ptrs(dev) + struct sbic_softc *dev; +{ + sbic_regmap_p regs; + struct sbic_acb* acb; + int count, + asr, + s; + + /* + * Only need to save pointers if DMA was active... + */ + if ( dev->sc_cur == NULL || (dev->sc_flags & SBICF_INDMA) == 0 ) + return; + + regs = dev->sc_sbicp; + + s = splbio(); + + /* + * Wait until WD chip is idle + */ + do { + GET_SBIC_asr(regs, asr); + if( asr & SBIC_ASR_DBR ) { + printf("sbic_save_ptrs: asr %02x canceled!\n", asr); + splx(s); + return; + } + } while( asr & (SBIC_ASR_BSY|SBIC_ASR_CIP) ); + + + /* + * Save important state. + * must be done before dmastop + */ + acb = dev->sc_nexus; + acb->sc_dmacmd = dev->sc_dmacmd; + + /* + * Fetch the residual count + */ + SBIC_TC_GET(regs, count); + + /* + * Shut down DMA + */ + dev->sc_dmastop(dev); + + /* + * No longer in DMA + */ + dev->sc_flags &= ~SBICF_INDMA; + + /* + * Ensure the WD chip is back in polled I/O mode, with nothing to + * transfer. + */ + SBIC_TC_PUT(regs, 0); + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI); + + /* + * Update current count... + */ + acb->sc_tcnt = count; + + /* + * Work out how many bytes were actually transferred + */ + count = dev->sc_tcnt - count; + dev->sc_tcnt = acb->sc_tcnt; + + /* + * Fixup partial xfers + */ + acb->sc_kv.dc_addr += count; + acb->sc_kv.dc_count -= count; + acb->sc_pa.dc_addr += count; + acb->sc_pa.dc_count -= count >> 1; + +#ifdef DEBUG + if ( data_pointer_debug ) + printf("save at (%x,%x):%x\n", + dev->sc_cur->dc_addr, dev->sc_cur->dc_count,count); + sbicdma_saves++; +#endif + + splx(s); +} + + +/* + * DOES NOT RESTART DMA!!! + */ +void +sbic_load_ptrs(dev) + struct sbic_softc *dev; +{ + struct sbic_acb *acb = dev->sc_nexus; + int s; + + if ( acb->sc_kv.dc_count == 0 ) { + /* + * No data to xfer + */ + return; + } + + s = splbio(); + + /* + * Reset the Scatter-Gather chain + */ + dev->sc_last = dev->sc_cur = &acb->sc_pa; + + /* + * Restore the Transfer Count and DMA specific data + */ + dev->sc_tcnt = acb->sc_tcnt; + dev->sc_dmacmd = acb->sc_dmacmd; + +#ifdef DEBUG + sbicdma_ops++; +#endif + + /* + * Need to fixup new segment? + */ + if ( dev->sc_tcnt == 0 ) { + /* + * sc_tcnt == 0 implies end of segment + */ + char *vaddr, *paddr; + int count; + + /* + * do kvm to pa mappings + */ + vaddr = acb->sc_kv.dc_addr; + paddr = acb->sc_pa.dc_addr = (char *) kvtop(vaddr); + + for (count = (NBPG - ((int)vaddr & PGOFSET)); + count < acb->sc_kv.dc_count && + (char*)kvtop(vaddr + count + 4) == paddr + count + 4; + count += NBPG) + ; /* Do nothing */ + + /* + * If it's all contiguous... + */ + if ( count > acb->sc_kv.dc_count ) { + count = acb->sc_kv.dc_count; +#ifdef DEBUG + sbicdma_hits++; +#endif + } +#ifdef DEBUG + else + sbicdma_misses++; +#endif + + acb->sc_tcnt = count; + acb->sc_pa.dc_count = count >> 1; + +#ifdef DEBUG + if ( data_pointer_debug ) + printf("DMA recalc:kv(%x,%x)pa(%x,%x)\n", acb->sc_kv.dc_addr, + acb->sc_kv.dc_count, + acb->sc_pa.dc_addr, + acb->sc_tcnt); +#endif + + } + + splx(s); +} + +/* + * used by specific sbic controller + * + * it appears that the higher level code does nothing with LUN's + * so I will too. I could plug it in, however so could they + * in scsi_scsi_cmd(). + */ +int +sbic_scsicmd(xs) + struct scsi_xfer *xs; +{ + struct scsi_link *slp = xs->sc_link; + struct sbic_softc *dev = slp->adapter_softc; + struct sbic_acb *acb; + int flags = xs->flags, + s; + + if ( flags & SCSI_DATA_UIO ) + panic("sbic: scsi data uio requested"); + + if ( dev->sc_nexus && (flags & SCSI_POLL) ) + panic("sbic_scsicmd: busy"); + + if ( slp->target == slp->adapter_target ) + return ESCAPE_NOT_SUPPORTED; + + s = splbio(); + + if ( (acb = dev->free_list.tqh_first) != NULL ) + TAILQ_REMOVE(&dev->free_list, acb, chain); + + splx(s); + + if ( acb == NULL ) { +#ifdef DEBUG + printf("sbic_scsicmd: unable to queue request for target %d\n", + slp->target); +#ifdef DDB + Debugger(); +#endif +#endif + xs->error = XS_DRIVER_STUFFUP; + + return(TRY_AGAIN_LATER); + } + + if ( flags & SCSI_DATA_IN ) + acb->flags = ACB_ACTIVE | ACB_DATAIN; + else + acb->flags = ACB_ACTIVE; + + acb->xs = xs; + acb->clen = xs->cmdlen; + acb->sc_kv.dc_addr = xs->data; + acb->sc_kv.dc_count = xs->datalen; + acb->pa_addr = xs->data ? (char *)kvtop(xs->data) : 0; + bcopy(xs->cmd, &acb->cmd, xs->cmdlen); + + if ( flags & SCSI_POLL ) { + /* + * This has major side effects -- it locks up the machine + */ + int stat; + + s = splbio(); + + dev->sc_flags |= SBICF_ICMD; + + do { + /* + * If we already had a nexus, while away the time until idle... + * This is likely only to happen if a reselection occurs between + * here and our earlier check for ICMD && sc_nexus (which would + * have resulted in a panic() had it been true). + */ + while ( dev->sc_nexus ) + sbicpoll(dev); + + /* + * Fix up the new nexus + */ + dev->sc_nexus = acb; + dev->sc_xs = xs; + dev->target = slp->target; + dev->lun = slp->lun; + + stat = sbicicmd(dev, &acb->cmd, acb->clen, + acb->sc_kv.dc_addr, acb->sc_kv.dc_count); + + } while ( dev->sc_nexus != acb ); + + sbic_scsidone(acb, stat); + + splx(s); + + return(COMPLETE); + } + + s = splbio(); + TAILQ_INSERT_TAIL(&dev->ready_list, acb, chain); + + /* + * If nothing is active, try to start it now. + */ + if ( dev->sc_nexus == NULL ) + sbic_sched(dev); + + splx(s); + + return(SUCCESSFULLY_QUEUED); +} + +/* + * attempt to start the next available command + */ +void +sbic_sched(dev) + struct sbic_softc *dev; +{ + struct scsi_xfer *xs; + struct scsi_link *slp = NULL; /* Gag the compiler */ + struct sbic_acb *acb; + int flags, + stat; + + /* + * XXXSCW + * I'll keep this test here, even though I can't see any obvious way + * in which sbic_sched() could be called with sc_nexus non NULL + */ + if ( dev->sc_nexus ) + return; /* a command is current active */ + + /* + * Loop through the ready list looking for work to do... + */ + for (acb = dev->ready_list.tqh_first; acb; acb = acb->chain.tqe_next) { + int i, j; + + slp = acb->xs->sc_link; + i = slp->target; + j = 1 << slp->lun; + + /* + * We've found a potential command, but is the target/lun busy? + */ + if ( (dev->sc_tinfo[i].lubusy & j) == 0 ) { + /* + * Nope, it's not busy, so we can use it. + */ + dev->sc_tinfo[i].lubusy |= j; + TAILQ_REMOVE(&dev->ready_list, acb, chain); + dev->sc_nexus = acb; + acb->sc_pa.dc_addr = acb->pa_addr; /* XXXX check */ + break; + } + } + + if ( acb == NULL ) { + QPRINTF(("sbicsched: no work\n")); + return; /* did not find an available command */ + } + +#ifdef DEBUG + if ( data_pointer_debug > 1 ) + printf("sbic_sched(%d,%d)\n", slp->target, slp->lun); +#endif + + dev->sc_xs = xs = acb->xs; + flags = xs->flags; + + if ( flags & SCSI_RESET ) + sbicreset(dev); + + dev->sc_stat[0] = -1; + dev->target = slp->target; + dev->lun = slp->lun; + + if ( flags & SCSI_POLL || (!sbic_parallel_operations && + (sbicdmaok(dev, xs) == 0)) ) + stat = sbicicmd(dev, &acb->cmd, acb->clen, + acb->sc_kv.dc_addr, acb->sc_kv.dc_count); + else + if ( sbicgo(dev, xs) == 0 ) + return; + else + stat = dev->sc_stat[0]; + + sbic_scsidone(acb, stat); +} + +void +sbic_scsidone(acb, stat) + struct sbic_acb *acb; + int stat; +{ + struct scsi_xfer *xs = acb->xs; + struct scsi_link *slp = xs->sc_link; + struct sbic_softc *dev = slp->adapter_softc; + int dosched = 0; + +#ifdef DIAGNOSTIC + if ( acb == NULL || xs == NULL ) { + printf("sbic_scsidone -- (%d,%d) no scsi_xfer\n", dev->target, dev->lun); +#ifdef DDB + Debugger(); +#endif + return; + } +#endif + + if ( slp->device_softc && + ((struct device *)(slp->device_softc))->dv_unit < dk_ndrive) + ++dk_xfer[((struct device *)(slp->device_softc))->dv_unit]; + + /* + * is this right? + */ + xs->status = stat; + +#ifdef DEBUG + if ( data_pointer_debug > 1 ) + printf("scsidone: (%d,%d)->(%d,%d)%02x\n", slp->target, slp->lun, + dev->target, dev->lun, stat); + + if ( xs->sc_link->target == dev->sc_link.adapter_target ) + panic("target == hostid"); +#endif + + if ( xs->error == XS_NOERROR && (acb->flags & ACB_CHKSENSE) == 0 ) { + + if ( stat == SCSI_CHECK ) { + /* + * Schedule a REQUEST SENSE + */ + struct scsi_sense *ss = (void *)&acb->cmd; + +#ifdef DEBUG + if ( report_sense ) + printf("sbic_scsidone: autosense %02x targ %d lun %d", + acb->cmd.opcode, slp->target, slp->lun); +#endif + + bzero(ss, sizeof(*ss)); + + ss->opcode = REQUEST_SENSE; + ss->byte2 = slp->lun << 5; + ss->length = sizeof(struct scsi_sense_data); + + acb->clen = sizeof(*ss); + acb->sc_kv.dc_addr = (char *)&xs->sense; + acb->sc_kv.dc_count = sizeof(struct scsi_sense_data); + acb->pa_addr = (char *)kvtop((caddr_t)&xs->sense); /* XXX check */ + acb->flags = ACB_ACTIVE | ACB_CHKSENSE | ACB_DATAIN; + + TAILQ_INSERT_HEAD(&dev->ready_list, acb, chain); + + dev->sc_tinfo[slp->target].lubusy &= ~(1 << slp->lun); + dev->sc_tinfo[slp->target].senses++; + + if ( dev->sc_nexus == acb ) { + dev->sc_nexus = NULL; + dev->sc_xs = NULL; + sbic_sched(dev); + } + return; + } + } + + if ( xs->error == XS_NOERROR && (acb->flags & ACB_CHKSENSE) != 0 ) { + + xs->error = XS_SENSE; + +#ifdef DEBUG + if (report_sense) + printf(" => %02x %02x\n", xs->sense.flags, + xs->sense.extra_bytes[3]); +#endif + + } else { + xs->resid = 0; /* XXXX */ + } + + xs->flags |= ITSDONE; + + /* + * Remove the ACB from whatever queue it's on. We have to do a bit of + * a hack to figure out which queue it's on. Note that it is *not* + * necessary to cdr down the ready queue, but we must cdr down the + * nexus queue and see if it's there, so we can mark the unit as no + * longer busy. This code is sickening, but it works. + */ + if ( acb == dev->sc_nexus ) { + + dev->sc_nexus = NULL; + dev->sc_xs = NULL; + + dev->sc_tinfo[slp->target].lubusy &= ~(1 << slp->lun); + + if ( dev->ready_list.tqh_first ) + dosched = 1; /* start next command */ + + } else + if ( dev->ready_list.tqh_last == &acb->chain.tqe_next ) { + + TAILQ_REMOVE(&dev->ready_list, acb, chain); + + } else { + + register struct sbic_acb *a; + + for (a = dev->nexus_list.tqh_first; a; a = a->chain.tqe_next) { + if ( a == acb ) { + TAILQ_REMOVE(&dev->nexus_list, acb, chain); + dev->sc_tinfo[slp->target].lubusy &= ~(1 << slp->lun); + break; + } + } + + if ( a ) + ; + else if ( acb->chain.tqe_next ) { + TAILQ_REMOVE(&dev->ready_list, acb, chain); + } else { + printf("%s: can't find matching acb\n", dev->sc_dev.dv_xname); +#ifdef DDB + Debugger(); +#endif + } + } + + /* + * Put it on the free list. + */ + acb->flags = ACB_FREE; + TAILQ_INSERT_HEAD(&dev->free_list, acb, chain); + + dev->sc_tinfo[slp->target].cmds++; + + scsi_done(xs); + + if ( dosched ) + sbic_sched(dev); +} + +int +sbicdmaok(dev, xs) + struct sbic_softc *dev; + struct scsi_xfer *xs; +{ + if ( sbic_no_dma || xs->datalen & 0x03 || (int)xs->data & 0x03) + return(0); + + /* + * controller supports dma to any addresses? + */ + if ( (dev->sc_flags & SBICF_BADDMA) == 0 ) + return(1); + + /* + * this address is ok for dma? + */ + if ( sbiccheckdmap(xs->data, xs->datalen, dev->sc_dmamask) == 0 ) + return(1); + + return(0); +} + +int +sbicwait(regs, until, timeo, line) + sbic_regmap_p regs; + u_char until; + int timeo; + int line; +{ + u_char val; + + if ( timeo == 0 ) + timeo = 1000000; /* some large value.. */ + + GET_SBIC_asr(regs, val); + + while ( (val & until) == 0 ) { + + if ( timeo-- == 0 ) { + int csr; + GET_SBIC_csr(regs, csr); + printf("sbicwait TIMEO @%d with asr=x%x csr=x%x\n", line, val, csr); +#if defined(DDB) && defined(DEBUG) + Debugger(); +#endif + return(val); /* Maybe I should abort */ + break; + } + + DELAY(1); + GET_SBIC_asr(regs, val); + } + + return(val); +} + +int +sbicabort(dev, where) + struct sbic_softc *dev; + char *where; +{ + sbic_regmap_p regs = dev->sc_sbicp; + u_char csr, + asr; + + GET_SBIC_asr(regs, asr); + GET_SBIC_csr(regs, csr); + + /* + * Clean up chip itself + */ + if ( dev->sc_flags & SBICF_SELECTED ) { + + while ( asr & SBIC_ASR_DBR ) { + /* + * sbic is jammed w/data. need to clear it + * But we don't know what direction it needs to go + */ + GET_SBIC_data(regs, asr); + printf("%s: abort %s: clearing data buffer 0x%02x\n", + dev->sc_dev.dv_xname, where, asr); + GET_SBIC_asr(regs, asr); + if ( asr & SBIC_ASR_DBR ) /* Not the read direction, then */ + SET_SBIC_data(regs, asr); + GET_SBIC_asr(regs, asr); + } + + WAIT_CIP(regs); + + printf("%s: sbicabort - sending ABORT command\n", dev->sc_dev.dv_xname); + SET_SBIC_cmd(regs, SBIC_CMD_ABORT); + WAIT_CIP(regs); + + GET_SBIC_asr(regs, asr); + + if ( asr & (SBIC_ASR_BSY|SBIC_ASR_LCI) ) { + /* + * ok, get more drastic.. + */ + printf("%s: sbicabort - asr %x, trying to reset\n", + dev->sc_dev.dv_xname, asr); + sbicreset(dev); + dev->sc_flags &= ~SBICF_SELECTED; + return SBIC_STATE_ERROR; + } + + printf("%s: sbicabort - sending DISC command\n", dev->sc_dev.dv_xname); + SET_SBIC_cmd(regs, SBIC_CMD_DISC); + + do { + SBIC_WAIT (regs, SBIC_ASR_INT, 0); + GET_SBIC_asr(regs, asr); + GET_SBIC_csr (regs, csr); + QPRINTF(("csr: 0x%02x, asr: 0x%02x\n", csr, asr)); + } while ( (csr != SBIC_CSR_DISC) && (csr != SBIC_CSR_DISC_1) && + (csr != SBIC_CSR_CMD_INVALID) ); + + /* + * lets just hope it worked.. + */ + dev->sc_flags &= ~SBICF_SELECTED; + } + + return SBIC_STATE_ERROR; +} + + +/* + * Initialize driver-private structures + */ +void +sbicinit(dev) + struct sbic_softc *dev; +{ + u_int i; + + extern u_long scsi_nosync; + extern int shift_nosync; + + if ( (dev->sc_flags & SBICF_ALIVE) == 0 ) { + + struct sbic_acb *acb; + + TAILQ_INIT(&dev->ready_list); + TAILQ_INIT(&dev->nexus_list); + TAILQ_INIT(&dev->free_list); + + dev->sc_nexus = NULL; + dev->sc_xs = NULL; + + acb = dev->sc_acb; + bzero(acb, sizeof(dev->sc_acb)); + + for (i = 0; i < sizeof(dev->sc_acb) / sizeof(*acb); i++) { + TAILQ_INSERT_TAIL(&dev->free_list, acb, chain); + acb++; + } + + bzero(dev->sc_tinfo, sizeof(dev->sc_tinfo)); + +#ifdef DEBUG + /* + * make sure timeout is really not needed + */ + timeout((void *)sbictimeout, dev, 30 * hz); +#endif + + } else + panic("sbic: reinitializing driver!"); + + dev->sc_flags |= SBICF_ALIVE; + dev->sc_flags &= ~SBICF_SELECTED; + + /* + * initialize inhibit array + */ + if ( scsi_nosync ) { + + u_int inhibit_sync = (scsi_nosync >> shift_nosync) & 0xff; + + shift_nosync += 8; + +#ifdef DEBUG + if ( inhibit_sync ) + printf("%s: Inhibiting synchronous transfer %02x\n", + dev->sc_dev.dv_xname, inhibit_sync); +#endif + for (i = 0; i < 8; ++i) { + if ( inhibit_sync & (1 << i) ) + sbic_inhibit_sync[i] = 1; + } + } + + sbicreset(dev); +} + +void +sbicreset(dev) + struct sbic_softc *dev; +{ + sbic_regmap_p regs = dev->sc_sbicp; + u_int my_id, + s; + u_char csr; + u_char asr; + + GET_SBIC_asr (regs, asr); + s = splbio(); + + my_id = dev->sc_link.adapter_target & SBIC_ID_MASK; + + if (dev->sc_clkfreq < 110) + my_id |= SBIC_ID_FS_8_10; + else if (dev->sc_clkfreq < 160) + my_id |= SBIC_ID_FS_12_15; + else if (dev->sc_clkfreq < 210) + my_id |= SBIC_ID_FS_16_20; + + SET_SBIC_myid(regs, my_id); + + /* + * Reset the chip + */ + SET_SBIC_cmd(regs, SBIC_CMD_RESET); + DELAY(25); + + SBIC_WAIT(regs, SBIC_ASR_INT, 0); + GET_SBIC_csr(regs, csr); /* clears interrupt also */ + GET_SBIC_asr (regs, asr); + + /* + * Set up various chip parameters + */ + SET_SBIC_control(regs, + SBIC_CTL_EDI | SBIC_CTL_IDI | SBIC_MACHINE_DMA_MODE); + + /* + * don't allow Selection (SBIC_RID_ES) + * until we can handle target mode!! + */ + SET_SBIC_rselid(regs, SBIC_RID_ER); + + /* + * Asynchronous for now + */ + SET_SBIC_syn(regs, 0); + + /* + * Anything else was zeroed by reset + */ + splx(s); + + dev->sc_flags &= ~SBICF_SELECTED; + GET_SBIC_asr (regs, asr); +} + +void +sbicerror(dev, csr) + struct sbic_softc *dev; + u_char csr; +{ + struct scsi_xfer *xs = dev->sc_xs; + +#ifdef DIAGNOSTIC + if ( xs == NULL ) + panic("sbicerror: dev->sc_xs == NULL"); +#endif + + if ( xs->flags & SCSI_SILENT ) + return; + + printf("%s: csr == 0x%02x\n", dev->sc_dev.dv_xname, csr); +} + +/* + * select the bus, return when selected or error. + * + * Returns the current CSR following selection and optionally MSG out phase. + * i.e. the returned CSR *should* indicate CMD phase... + * If the return value is 0, some error happened. + */ +u_char +sbicselectbus(dev) + struct sbic_softc *dev; +{ + sbic_regmap_p regs = dev->sc_sbicp; + u_char target = dev->target, + lun = dev->lun, + asr, + csr, + id; + + /* + * if we're already selected, return (XXXX panic maybe?) + */ + if ( dev->sc_flags & SBICF_SELECTED ) + return(0); + + QPRINTF(("sbicselectbus %d: ", target)); + + /* + * issue select + */ + SET_SBIC_selid(regs, target); + SET_SBIC_timeo(regs, SBIC_TIMEOUT(250, dev->sc_clkfreq)); + + GET_SBIC_asr(regs, asr); + + if ( asr & (SBIC_ASR_INT|SBIC_ASR_BSY) ) { + /* + * This means we got ourselves reselected upon + */ + QPRINTF(("WD busy (reselect?)\n")); + return 0; + } + + SET_SBIC_cmd(regs, SBIC_CMD_SEL_ATN); + + /* + * wait for select (merged from seperate function may need + * cleanup) + */ + WAIT_CIP(regs); + + do { + + asr = SBIC_WAIT(regs, SBIC_ASR_INT | SBIC_ASR_LCI, 0); + + if ( asr & SBIC_ASR_LCI ) { + QPRINTF(("late LCI: asr %02x\n", asr)); + return 0; + } + + /* + * Clear interrupt + */ + GET_SBIC_csr (regs, csr); + + QPRINTF(("%02x ", csr)); + + /* + * Reselected from under our feet? + */ + if ( csr == SBIC_CSR_RSLT_NI || csr == SBIC_CSR_RSLT_IFY ) { + QPRINTF(("got reselected, asr %02x\n", asr)); + /* + * We need to handle this now so we don't lock up later + */ + sbicnextstate(dev, csr, asr); + + return 0; + } + + /* + * Whoops! + */ + if ( csr == SBIC_CSR_SLT || csr == SBIC_CSR_SLT_ATN ) { + panic("sbicselectbus: target issued select!"); + return 0; + } + + } while (csr != (SBIC_CSR_MIS_2 | MESG_OUT_PHASE) && + csr != (SBIC_CSR_MIS_2 | CMD_PHASE) && + csr != SBIC_CSR_SEL_TIMEO); + + /* + * Anyone at home? + */ + if ( csr == SBIC_CSR_SEL_TIMEO ) { + dev->sc_xs->error = XS_SELTIMEOUT; + QPRINTF(("Selection Timeout\n")); + return 0; + } + + QPRINTF(("Selection Complete\n")); + + /* + * Assume we're now selected + */ + GET_SBIC_selid(regs, id); + dev->target = id; + dev->lun = lun; + dev->sc_flags |= SBICF_SELECTED; + + /* + * Enable (or not) reselection + * XXXSCW This is probably not necessary since we don't use use the + * Select-and-Xfer-with-ATN command to initiate a selection... + */ + if ( !sbic_enable_reselect && dev->nexus_list.tqh_first == NULL) + SET_SBIC_rselid (regs, 0); + else + SET_SBIC_rselid (regs, SBIC_RID_ER); + + /* + * We only really need to do anything when the target goes to MSG out + * If the device ignored ATN, it's probably old and brain-dead, + * but we'll try to support it anyhow. + * If it doesn't support message out, it definately doesn't + * support synchronous transfers, so no point in even asking... + */ + if ( csr == (SBIC_CSR_MIS_2 | MESG_OUT_PHASE) ) { + /* + * Send identify message (SCSI-2 requires an identify msg) + */ + if ( sbic_inhibit_sync[id] && dev->sc_sync[id].state == SYNC_START ) { + /* + * Handle drives that don't want to be asked + * whether to go sync at all. + */ + dev->sc_sync[id].offset = 0; + dev->sc_sync[id].period = sbic_min_period; + dev->sc_sync[id].state = SYNC_DONE; + } + + /* + * Do we need to negotiate Synchronous Xfers for this target? + */ + if ( dev->sc_sync[id].state != SYNC_START ) { + /* + * Nope, we've already negotiated. + * Now see if we should allow the target to disconnect/reselect... + */ + if ( dev->sc_xs->flags & SCSI_POLL || dev->sc_flags & SBICF_ICMD || + !sbic_enable_reselect ) + SEND_BYTE (regs, MSG_IDENTIFY | lun); + else + SEND_BYTE (regs, MSG_IDENTIFY_DR | lun); + + } else { + /* + * try to initiate a sync transfer. + * So compose the sync message we're going + * to send to the target + */ +#ifdef DEBUG + if ( sync_debug ) + printf("\nSending sync request to target %d ... ", id); +#endif + /* + * setup scsi message sync message request + */ + dev->sc_msg[0] = MSG_IDENTIFY | lun; + dev->sc_msg[1] = MSG_EXT_MESSAGE; + dev->sc_msg[2] = 3; + dev->sc_msg[3] = MSG_SYNC_REQ; + dev->sc_msg[4] = sbictoscsiperiod(dev, sbic_min_period); + dev->sc_msg[5] = sbic_max_offset; + + sbicxfout(regs, 6, dev->sc_msg); + + dev->sc_sync[id].state = SYNC_SENT; +#ifdef DEBUG + if ( sync_debug ) + printf ("sent\n"); +#endif + } + + /* + * There's one interrupt still to come: the change to CMD phase... + */ + SBIC_WAIT(regs, SBIC_ASR_INT , 0); + GET_SBIC_csr(regs, csr); + } + + /* + * set sync or async + */ + if ( dev->sc_sync[target].state == SYNC_DONE ) { +#ifdef DEBUG + if ( sync_debug ) + printf("select(%d): sync reg = 0x%02x\n", target, + SBIC_SYN(dev->sc_sync[target].offset, + dev->sc_sync[target].period)); +#endif + SET_SBIC_syn(regs, SBIC_SYN(dev->sc_sync[target].offset, + dev->sc_sync[target].period)); + } else { +#ifdef DEBUG + if ( sync_debug ) + printf("select(%d): sync reg = 0x%02x\n", target, + SBIC_SYN(0,sbic_min_period)); +#endif + SET_SBIC_syn(regs, SBIC_SYN(0, sbic_min_period)); + } + + return csr; +} + +/* + * Information Transfer *to* a Scsi Target. + * + * Note: Don't expect there to be an interrupt immediately after all + * the data is transferred out. The WD spec sheet says that the Transfer- + * Info command for non-MSG_IN phases only completes when the target + * next asserts 'REQ'. That is, when the SCSI bus changes to a new state. + * + * This can have a nasty effect on commands which take a relatively long + * time to complete, for example a START/STOP unit command may remain in + * CMD phase until the disk has spun up. Only then will the target change + * to STATUS phase. This is really only a problem for immediate commands + * since we don't allow disconnection for them (yet). + */ +int +sbicxfout(regs, len, bp) + sbic_regmap_p regs; + int len; + void *bp; +{ + int wait = sbic_data_wait; + u_char asr, + *buf = bp; + + QPRINTF(("sbicxfout {%d} %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x\n", len, buf[0], buf[1], buf[2], + buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9])); + + /* + * sigh.. WD-PROTO strikes again.. sending the command in one go + * causes the chip to lock up if talking to certain (misbehaving?) + * targets. Anyway, this procedure should work for all targets, but + * it's slightly slower due to the overhead + */ + WAIT_CIP (regs); + + SBIC_TC_PUT (regs, 0); + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI); + SBIC_TC_PUT (regs, (unsigned)len); + SET_SBIC_cmd (regs, SBIC_CMD_XFER_INFO); + + /* + * Loop for each byte transferred + */ + do { + + GET_SBIC_asr (regs, asr); + + if ( asr & SBIC_ASR_DBR ) { + if ( len ) { + SET_SBIC_data (regs, *buf); + buf++; + len--; + } else { + SET_SBIC_data (regs, 0); + } + wait = sbic_data_wait; + } + + } while ( len && (asr & SBIC_ASR_INT) == 0 && wait-- > 0 ); + +#ifdef DEBUG + QPRINTF(("sbicxfout done: %d bytes remaining (wait:%d)\n", len, wait)); +#endif + + /* + * Normally, an interrupt will be pending when this routing returns. + */ + return(len); +} + +/* + * Information Transfer *from* a Scsi Target + * returns # bytes left to read + */ +int +sbicxfin(regs, len, bp) + sbic_regmap_p regs; + int len; + void *bp; +{ + int wait = sbic_data_wait; + u_char *buf = bp; + u_char asr; +#ifdef DEBUG + u_char *obp = bp; +#endif + + WAIT_CIP (regs); + + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI); + SBIC_TC_PUT (regs, (unsigned)len); + SET_SBIC_cmd (regs, SBIC_CMD_XFER_INFO); + + /* + * Loop for each byte transferred + */ + do { + + GET_SBIC_asr (regs, asr); + + if ( asr & SBIC_ASR_DBR ) { + if ( len ) { + GET_SBIC_data (regs, *buf); + buf++; + len--; + } else { + u_char foo; + GET_SBIC_data (regs, foo); + } + wait = sbic_data_wait; + } + + } while ( (asr & SBIC_ASR_INT) == 0 && wait-- > 0 ); + + QPRINTF(("sbicxfin {%d} %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x\n", len, obp[0], obp[1], obp[2], + obp[3], obp[4], obp[5], obp[6], obp[7], obp[8], obp[9])); + + SBIC_TC_PUT (regs, 0); + + /* + * this leaves with one csr to be read + */ + return len; +} + +/* + * SCSI 'immediate' command: issue a command to some SCSI device + * and get back an 'immediate' response (i.e., do programmed xfer + * to get the response data). 'cbuf' is a buffer containing a scsi + * command of length clen bytes. 'buf' is a buffer of length 'len' + * bytes for data. The transfer direction is determined by the device + * (i.e., by the scsi bus data xfer phase). If 'len' is zero, the + * command must supply no data. + * + * Note that although this routine looks like it can handle disconnect/ + * reselect, the fact is that it can't. There is still some work to be + * done to clean this lot up. + */ +int +sbicicmd(dev, cbuf, clen, buf, len) + struct sbic_softc *dev; + void *cbuf, + *buf; + int clen, + len; +{ + sbic_regmap_p regs = dev->sc_sbicp; + struct sbic_acb *acb = dev->sc_nexus; + u_char csr, + asr; + int still_busy = SBIC_STATE_RUNNING; +#ifdef DEBUG + int counter = 0; +#endif + + /* + * Make sure pointers are OK + */ + dev->sc_last = dev->sc_cur = &acb->sc_pa; + dev->sc_tcnt = acb->sc_tcnt = 0; + + acb->sc_dmacmd = 0; + acb->sc_pa.dc_count = 0; /* No DMA */ + acb->sc_kv.dc_addr = buf; + acb->sc_kv.dc_count = len; + +#ifdef DEBUG + if ( data_pointer_debug > 1 ) + printf("sbicicmd(%d,%d):%d\n", dev->target, dev->lun, acb->sc_kv.dc_count); +#endif + + /* + * set the sbic into non-DMA mode + */ + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI); + + dev->sc_stat[0] = 0xff; + dev->sc_msg[0] = 0xff; + + /* + * We're stealing the SCSI bus + */ + dev->sc_flags |= SBICF_ICMD; + + do { + GET_SBIC_asr (regs, asr); + + /* + * select the SCSI bus (it's an error if bus isn't free) + */ + if ( (dev->sc_flags & SBICF_SELECTED) == 0 && + still_busy != SBIC_STATE_DISCONNECT ) { + if ( (csr = sbicselectbus(dev)) == 0 ) { + dev->sc_flags &= ~SBICF_ICMD; + return(-1); + } + } else + if ( (asr & (SBIC_ASR_BSY | SBIC_ASR_INT)) == SBIC_ASR_INT ) + GET_SBIC_csr(regs, csr); + else + csr = 0; + + if ( csr ) { + + QPRINTF((">ASR:0x%02x CSR:0x%02x< ", asr, csr)); + + switch ( csr ) { + + case SBIC_CSR_S_XFERRED: + case SBIC_CSR_DISC: + case SBIC_CSR_DISC_1: + { + u_char phase; + + dev->sc_flags &= ~SBICF_SELECTED; + GET_SBIC_cmd_phase (regs, phase); + + if ( phase == 0x60 ) { + GET_SBIC_tlun (regs, dev->sc_stat[0]); + still_busy = SBIC_STATE_DONE; /* done */ + } else { +#ifdef DEBUG + if ( reselect_debug > 1 ) + printf("sbicicmd: handling disconnect\n"); +#endif + still_busy = SBIC_STATE_DISCONNECT; + } + } + break; + + case SBIC_CSR_XFERRED | CMD_PHASE: + case SBIC_CSR_MIS | CMD_PHASE: + case SBIC_CSR_MIS_1 | CMD_PHASE: + case SBIC_CSR_MIS_2 | CMD_PHASE: + { + if ( sbicxfout(regs, clen, cbuf) ) + still_busy = sbicabort(dev, "icmd sending cmd"); + } + break; + + case SBIC_CSR_XFERRED | STATUS_PHASE: + case SBIC_CSR_MIS | STATUS_PHASE: + case SBIC_CSR_MIS_1 | STATUS_PHASE: + case SBIC_CSR_MIS_2 | STATUS_PHASE: + { + /* + * The sbic does the status/cmd-complete reading ok, + * so do this with its hi-level commands. + */ +#ifdef DEBUG + if ( sbic_debug ) + printf("SBICICMD status phase (bsy=%d)\n", still_busy); +#endif + SET_SBIC_cmd_phase(regs, 0x46); + SET_SBIC_cmd(regs, SBIC_CMD_SEL_ATN_XFER); + } + break; + + default: + { + still_busy = sbicnextstate(dev, csr, asr); + } + break; + } + + /* + * make sure the last command was taken, + * ie. we're not hunting after an ignored command.. + */ + GET_SBIC_asr(regs, asr); + + /* + * tapes may take a loooong time.. + */ + while (asr & SBIC_ASR_BSY ) { + + if ( asr & SBIC_ASR_DBR ) { + int i; + + printf("sbicicmd: Waiting while sbic is jammed, CSR:%02x,ASR:%02x\n", csr,asr); +#ifdef DDB + Debugger(); +#endif + /* + * SBIC is jammed + * DUNNO which direction + * Try old direction + */ + GET_SBIC_data(regs, i); + GET_SBIC_asr(regs, asr); + + if ( asr & SBIC_ASR_DBR ) /* Wants us to write */ + SET_SBIC_data(regs, i); + } + + GET_SBIC_asr(regs, asr); + } + } + + /* + * wait for last command to complete + */ + if ( asr & SBIC_ASR_LCI ) { + printf("sbicicmd: last command ignored\n"); + } + else + if ( still_busy >= SBIC_STATE_RUNNING ) /* Bsy */ + SBIC_WAIT (regs, SBIC_ASR_INT, sbic_cmd_wait); + + /* + * do it again + */ + } while ( still_busy >= SBIC_STATE_RUNNING && dev->sc_stat[0] == 0xff ); + + /* + * Sometimes we need to do an extra read of the CSR + */ + GET_SBIC_csr(regs, csr); + +#ifdef DEBUG + if ( data_pointer_debug > 1 ) + printf("sbicicmd done(%d,%d):%d =%d=\n", dev->target, dev->lun, + acb->sc_kv.dc_count, + dev->sc_stat[0]); +#endif + + dev->sc_flags &= ~SBICF_ICMD; + + return(dev->sc_stat[0]); +} + +/* + * Finish SCSI xfer command: After the completion interrupt from + * a read/write operation, sequence through the final phases in + * programmed i/o. This routine is a lot like sbicicmd except we + * skip (and don't allow) the select, cmd out and data in/out phases. + */ +void +sbicxfdone(dev) + struct sbic_softc *dev; +{ + sbic_regmap_p regs = dev->sc_sbicp; + u_char phase, + csr; + int s; + + QPRINTF(("{")); + s = splbio(); + + /* + * have the sbic complete on its own + */ + SBIC_TC_PUT(regs, 0); + SET_SBIC_cmd_phase(regs, 0x46); + SET_SBIC_cmd(regs, SBIC_CMD_SEL_ATN_XFER); + + do { + + SBIC_WAIT (regs, SBIC_ASR_INT, 0); + GET_SBIC_csr (regs, csr); + QPRINTF(("%02x:", csr)); + + } while ( (csr != SBIC_CSR_DISC) && (csr != SBIC_CSR_DISC_1) && + (csr != SBIC_CSR_S_XFERRED)); + + dev->sc_flags &= ~SBICF_SELECTED; + + GET_SBIC_cmd_phase (regs, phase); + QPRINTF(("}%02x", phase)); + + if ( phase == 0x60 ) + GET_SBIC_tlun(regs, dev->sc_stat[0]); + else + sbicerror(dev, csr); + + QPRINTF(("=STS:%02x=\n", dev->sc_stat[0])); + + splx(s); +} + +/* + * No DMA chains + */ +int +sbicgo(dev, xs) + struct sbic_softc *dev; + struct scsi_xfer *xs; +{ + struct sbic_acb *acb = dev->sc_nexus; + sbic_regmap_p regs = dev->sc_sbicp; + int i, + dmaflags, + count, + usedma; + u_char csr, + asr, + *addr; + + dev->target = xs->sc_link->target; + dev->lun = xs->sc_link->lun; + + usedma = sbicdmaok(dev, xs); + +#ifdef DEBUG + if ( data_pointer_debug > 1 ) + printf("sbicgo(%d,%d): usedma=%d\n", dev->target, dev->lun, usedma); +#endif + + /* + * select the SCSI bus (it's an error if bus isn't free) + */ + if ( (csr = sbicselectbus(dev)) == 0 ) + return(0); /* Not done: needs to be rescheduled */ + + dev->sc_stat[0] = 0xff; + + /* + * Calculate DMA chains now + */ + if ( acb->flags & ACB_DATAIN ) + dmaflags = DMAGO_READ; + else + dmaflags = 0; + + addr = acb->sc_kv.dc_addr; + count = acb->sc_kv.dc_count; + + if ( count && ((char *)kvtop(addr) != acb->sc_pa.dc_addr) ) { + printf("sbic: DMA buffer mapping changed %x->%x\n", + acb->sc_pa.dc_addr, kvtop(addr)); +#ifdef DDB + Debugger(); +#endif + } + +#ifdef DEBUG + ++sbicdma_ops; /* count total DMA operations */ +#endif + + /* + * Allocate the DMA chain + * Mark end of segment... + */ + acb->sc_tcnt = dev->sc_tcnt = 0; + acb->sc_pa.dc_count = 0; + + sbic_load_ptrs(dev); + + /* + * Enable interrupts but don't do any DMA + * enintr() also enables interrupts for the sbic + */ + dev->sc_enintr(dev); + + if ( usedma ) { + dev->sc_tcnt = dev->sc_dmago(dev, acb->sc_pa.dc_addr, + acb->sc_pa.dc_count, dmaflags); +#ifdef DEBUG + dev->sc_dmatimo = dev->sc_tcnt ? 1 : 0; +#endif + } else + dev->sc_dmacmd = 0; /* Don't use DMA */ + + acb->sc_dmacmd = dev->sc_dmacmd; + +#ifdef DEBUG + if ( data_pointer_debug > 1 ) { + printf("sbicgo dmago:%d(%x:%x) dmacmd=0x%02x\n", dev->target, + dev->sc_cur->dc_addr, + dev->sc_tcnt, + dev->sc_dmacmd); + } +#endif + + /* + * Lets cycle a while then let the interrupt handler take over. + */ + GET_SBIC_asr(regs, asr); + + do { + + QPRINTF(("go ")); + + /* + * Handle the new phase + */ + i = sbicnextstate(dev, csr, asr); +#if 0 + WAIT_CIP(regs); +#endif + if ( i == SBIC_STATE_RUNNING ) { + GET_SBIC_asr(regs, asr); + + if ( asr & SBIC_ASR_LCI ) + printf("sbicgo: LCI asr:%02x csr:%02x\n", asr, csr); + + if ( asr & SBIC_ASR_INT ) + GET_SBIC_csr(regs, csr); + } + + } while ( i == SBIC_STATE_RUNNING && asr & (SBIC_ASR_INT|SBIC_ASR_LCI) ); + + if ( i == SBIC_STATE_DONE ) { + if ( dev->sc_stat[0] == 0xff ) +#if 0 + printf("sbicgo: done & stat = 0xff\n"); +#else + ; +#endif + else + return 1; /* Did we really finish that fast? */ + } + + return 0; +} + + +int +sbicintr(dev) + struct sbic_softc *dev; +{ + sbic_regmap_p regs = dev->sc_sbicp; + u_char asr, + csr; + int i; + + /* + * pending interrupt? + */ + GET_SBIC_asr (regs, asr); + if ( (asr & SBIC_ASR_INT) == 0 ) + return(0); + + GET_SBIC_csr(regs, csr); + + do { + + QPRINTF(("intr[0x%x]", csr)); + + i = sbicnextstate(dev, csr, asr); +#if 0 + WAIT_CIP(regs); +#endif + if ( i == SBIC_STATE_RUNNING ) { + GET_SBIC_asr(regs, asr); + + if ( asr & SBIC_ASR_LCI ) + printf("sbicgo: LCI asr:%02x csr:%02x\n", asr, csr); + + if ( asr & SBIC_ASR_INT ) + GET_SBIC_csr(regs, csr); + } + + } while ( i == SBIC_STATE_RUNNING && asr & (SBIC_ASR_INT|SBIC_ASR_LCI) ); + + QPRINTF(("intr done. state=%d, asr=0x%02x\n", i, asr)); + + return(1); +} + +/* + * Run commands and wait for disconnect. + * This is only ever called when a command is in progress, when we + * want to busy wait for it to finish. + */ +int +sbicpoll(dev) + struct sbic_softc *dev; +{ + sbic_regmap_p regs = dev->sc_sbicp; + u_char asr; + u_char csr; + int i; + + /* + * Wait for the next interrupt + */ + SBIC_WAIT(regs, SBIC_ASR_INT, sbic_cmd_wait); + + /* Just to avoid GCC warnings. */ + csr = 0; + + do { + GET_SBIC_asr (regs, asr); + + if (asr & SBIC_ASR_INT) + GET_SBIC_csr(regs, csr); + + QPRINTF(("poll[0x%x]", csr)); + + /* + * Handle it + */ + i = sbicnextstate(dev, csr, asr); + + WAIT_CIP(regs); + GET_SBIC_asr(regs, asr); + + /* + * tapes may take a loooong time.. + */ + while ( asr & SBIC_ASR_BSY ) { + u_char z = 0; + + if ( asr & SBIC_ASR_DBR ) { + printf("sbipoll: Waiting while sbic is jammed, CSR:%02x,ASR:%02x\n", csr,asr); +#ifdef DDB + Debugger(); +#endif + /* + * SBIC is jammed + * DUNNO which direction + * Try old direction + */ + GET_SBIC_data(regs, z); + GET_SBIC_asr(regs, asr); + + if ( asr & SBIC_ASR_DBR ) /* Wants us to write */ + SET_SBIC_data(regs, z); + } + + GET_SBIC_asr(regs, asr); + } + + if ( asr & SBIC_ASR_LCI ) + printf("sbicpoll: LCI asr:%02x csr:%02x\n", asr,csr); + else + if ( i == SBIC_STATE_RUNNING ) /* BSY */ + SBIC_WAIT(regs, SBIC_ASR_INT, sbic_cmd_wait); + + } while ( i == SBIC_STATE_RUNNING ); + + return(1); +} + +/* + * Handle a single msgin + */ +int +sbicmsgin(dev) + struct sbic_softc *dev; +{ + sbic_regmap_p regs = dev->sc_sbicp; + int recvlen = 1; + u_char asr, + csr, + *tmpaddr, + *msgaddr; + + tmpaddr = msgaddr = dev->sc_msg; + + tmpaddr[0] = 0xff; + tmpaddr[1] = 0xff; + + GET_SBIC_asr(regs, asr); + +#ifdef DEBUG + if ( reselect_debug > 1 ) + printf("sbicmsgin asr=%02x\n", asr); +#endif + + GET_SBIC_selid (regs, csr); + SET_SBIC_selid (regs, csr | SBIC_SID_FROM_SCSI); + + SBIC_TC_PUT(regs, 0); + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI); + + do { + while( recvlen-- ) { + + /* + * Fetch the next byte of the message + */ + RECV_BYTE(regs, *tmpaddr); + + /* + * get the command completion interrupt, or we + * can't send a new command (LCI) + */ + SBIC_WAIT(regs, SBIC_ASR_INT, 0); + GET_SBIC_csr(regs, csr); + +#ifdef DEBUG + if ( reselect_debug > 1 ) + printf("sbicmsgin: got %02x csr %02x\n", *tmpaddr, csr); +#endif + + tmpaddr++; + + if ( recvlen ) { + /* + * Clear ACK, and wait for the interrupt for the next byte + */ + SET_SBIC_cmd(regs, SBIC_CMD_CLR_ACK); + SBIC_WAIT(regs, SBIC_ASR_INT, 0); + GET_SBIC_csr(regs, csr); + } + } + + if ( msgaddr[0] == 0xff ) { + printf("sbicmsgin: sbic swallowed our message\n"); + break; + } + +#ifdef DEBUG + if ( sync_debug ) { + GET_SBIC_asr(regs, asr); + printf("msgin done csr 0x%x asr 0x%x msg 0x%x\n", csr, asr, msgaddr[0]); + } +#endif + /* + * test whether this is a reply to our sync + * request + */ + if ( MSG_ISIDENTIFY(msgaddr[0]) ) { + + /* + * Got IFFY msg -- ack it + */ + QPRINTF(("IFFY")); + + } else + if ( msgaddr[0] == MSG_REJECT && + dev->sc_sync[dev->target].state == SYNC_SENT) { + + /* + * Target probably rejected our Sync negotiation. + */ + QPRINTF(("REJECT of SYN")); + +#ifdef DEBUG + if ( sync_debug ) + printf("target %d rejected sync, going async\n", dev->target); +#endif + + dev->sc_sync[dev->target].period = sbic_min_period; + dev->sc_sync[dev->target].offset = 0; + dev->sc_sync[dev->target].state = SYNC_DONE; + SET_SBIC_syn(regs, SBIC_SYN(dev->sc_sync[dev->target].offset, + dev->sc_sync[dev->target].period)); + + } else + if ( msgaddr[0] == MSG_REJECT ) { + + /* + * we'll never REJECt a REJECT message.. + */ + QPRINTF(("REJECT")); + + } else + if ( msgaddr[0] == MSG_SAVE_DATA_PTR ) { + + /* + * don't reject this either. + */ + QPRINTF(("MSG_SAVE_DATA_PTR")); + + } else + if ( msgaddr[0] == MSG_RESTORE_PTR ) { + + /* + * don't reject this either. + */ + QPRINTF(("MSG_RESTORE_PTR")); + + } else + if ( msgaddr[0] == MSG_DISCONNECT ) { + + /* + * Target is disconnecting... + */ + QPRINTF(("DISCONNECT")); + +#ifdef DEBUG + if ( reselect_debug > 1 && msgaddr[0] == MSG_DISCONNECT ) + printf("sbicmsgin: got disconnect msg %s\n", + (dev->sc_flags & SBICF_ICMD) ? "rejecting" : ""); +#endif + + if ( dev->sc_flags & SBICF_ICMD ) { + /* + * We're in immediate mode. Prevent disconnects. + * prepare to reject the message, NACK + */ + SET_SBIC_cmd(regs, SBIC_CMD_SET_ATN); + WAIT_CIP(regs); + } + + } else + if ( msgaddr[0] == MSG_CMD_COMPLETE ) { + + /* + * !! KLUDGE ALERT !! quite a few drives don't seem to + * really like the current way of sending the + * sync-handshake together with the ident-message, and + * they react by sending command-complete and + * disconnecting right after returning the valid sync + * handshake. So, all I can do is reselect the drive, + * and hope it won't disconnect again. I don't think + * this is valid behavior, but I can't help fixing a + * problem that apparently exists. + * + * Note: we should not get here on `normal' command + * completion, as that condition is handled by the + * high-level sel&xfer resume command used to walk + * thru status/cc-phase. + */ + QPRINTF(("CMD_COMPLETE")); + +#ifdef DEBUG + if ( sync_debug ) + printf ("GOT MSG %d! target %d acting weird.." + " waiting for disconnect...\n", msgaddr[0], dev->target); +#endif + + /* + * Check to see if sbic is handling this + */ + GET_SBIC_asr(regs, asr); + + /* + * XXXSCW: I'm not convinced of this, we haven't negated ACK yet... + */ + if ( asr & SBIC_ASR_BSY ) + return SBIC_STATE_RUNNING; + + /* + * Let's try this: Assume it works and set status to 00 + */ + dev->sc_stat[0] = 0; + + } else + if ( msgaddr[0] == MSG_EXT_MESSAGE && tmpaddr == &(msgaddr[1]) ) { + + /* + * Target is sending us an extended message. We'll assume it's + * the response to our Sync. negotiation. + */ + QPRINTF(("ExtMSG\n")); + + /* + * Read in whole extended message. First, negate ACK to accept + * the MSG_EXT_MESSAGE byte... + */ + SET_SBIC_cmd(regs, SBIC_CMD_CLR_ACK); + + /* + * Wait for the interrupt for the next byte (length) + */ + SBIC_WAIT(regs, SBIC_ASR_INT, 0); + GET_SBIC_csr(regs, csr); + +#ifdef DEBUG + QPRINTF(("CLR ACK csr %02x\n", csr)); +#endif + + /* + * Read the length byte + */ + RECV_BYTE(regs, *tmpaddr); + + /* + * Wait for command completion IRQ + */ + SBIC_WAIT(regs, SBIC_ASR_INT, 0); + GET_SBIC_csr(regs, csr); + + /* + * Reload the loop counter + */ + recvlen = *tmpaddr++; + + QPRINTF(("Recving ext msg, csr %02x len %02x\n", csr, recvlen)); + + } else + if ( msgaddr[0] == MSG_EXT_MESSAGE && msgaddr[1] == 3 && + msgaddr[2] == MSG_SYNC_REQ ) { + + /* + * We've received the complete Extended Message Sync. Request... + */ + QPRINTF(("SYN")); + + /* + * Compute the required Transfer Period for the WD chip... + */ + dev->sc_sync[dev->target].period = sbicfromscsiperiod(dev, msgaddr[3]); + dev->sc_sync[dev->target].offset = msgaddr[4]; + dev->sc_sync[dev->target].state = SYNC_DONE; + + /* + * Put the WD chip in synchronous mode + */ + SET_SBIC_syn(regs, SBIC_SYN(dev->sc_sync[dev->target].offset, + dev->sc_sync[dev->target].period)); +#ifdef DEBUG + if ( sync_debug ) + printf("msgin(%d): sync reg = 0x%02x\n", dev->target, + SBIC_SYN(dev->sc_sync[dev->target].offset, + dev->sc_sync[dev->target].period)); +#endif + + printf("%s: target %d now synchronous, period=%dns, offset=%d.\n", + dev->sc_dev.dv_xname, dev->target, + msgaddr[3] * 4, msgaddr[4]); + + } else { + + /* + * We don't support whatever this message is... + */ +#ifdef DEBUG + if ( sbic_debug || sync_debug ) + printf ("sbicmsgin: Rejecting message 0x%02x\n", msgaddr[0]); +#endif + + /* + * prepare to reject the message, NACK + */ + SET_SBIC_cmd(regs, SBIC_CMD_SET_ATN); + WAIT_CIP(regs); + } + + /* + * Negate ACK to complete the transfer + */ + SET_SBIC_cmd(regs, SBIC_CMD_CLR_ACK); + + /* + * Wait for the interrupt for the next byte, or phase change. + * Only read the CSR if we have more data to transfer. + * XXXSCW: We should really verify that we're still in MSG IN phase + * before blindly going back around this loop, but that would mean + * we read the CSR... <sigh> + */ + SBIC_WAIT(regs, SBIC_ASR_INT, 0); + if ( recvlen > 0 ) + GET_SBIC_csr(regs, csr); + + } while ( recvlen > 0 ); + + /* + * Should still have one CSR to read + */ + return SBIC_STATE_RUNNING; +} + + +/* + * sbicnextstate() + * return: + * SBIC_STATE_DONE == done + * SBIC_STATE_RUNNING == working + * SBIC_STATE_DISCONNECT == disconnected + * SBIC_STATE_ERROR == error + */ +int +sbicnextstate(dev, csr, asr) + struct sbic_softc *dev; + u_char csr, + asr; +{ + sbic_regmap_p regs = dev->sc_sbicp; + struct sbic_acb *acb = dev->sc_nexus; + + QPRINTF(("next[%02x,%02x]: ",asr,csr)); + + switch (csr) { + + case SBIC_CSR_XFERRED | CMD_PHASE: + case SBIC_CSR_MIS | CMD_PHASE: + case SBIC_CSR_MIS_1 | CMD_PHASE: + case SBIC_CSR_MIS_2 | CMD_PHASE: + { + if ( sbicxfout(regs, acb->clen, &acb->cmd) ) + goto abort; + } + break; + + case SBIC_CSR_XFERRED | STATUS_PHASE: + case SBIC_CSR_MIS | STATUS_PHASE: + case SBIC_CSR_MIS_1 | STATUS_PHASE: + case SBIC_CSR_MIS_2 | STATUS_PHASE: + { + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI); + + /* + * this should be the normal i/o completion case. + * get the status & cmd complete msg then let the + * device driver look at what happened. + */ + sbicxfdone(dev); + +#ifdef DEBUG + dev->sc_dmatimo = 0; + if ( data_pointer_debug > 1 ) + printf("next dmastop: %d(%x:%x)\n", dev->target, + dev->sc_cur->dc_addr, + dev->sc_tcnt); +#endif + /* + * Stop the DMA chip + */ + dev->sc_dmastop(dev); + + dev->sc_flags &= ~(SBICF_INDMA | SBICF_DCFLUSH); + + /* + * Indicate to the upper layers that the command is done + */ + sbic_scsidone(acb, dev->sc_stat[0]); + + return SBIC_STATE_DONE; + } + + case SBIC_CSR_XFERRED | DATA_OUT_PHASE: + case SBIC_CSR_XFERRED | DATA_IN_PHASE: + case SBIC_CSR_MIS | DATA_OUT_PHASE: + case SBIC_CSR_MIS | DATA_IN_PHASE: + case SBIC_CSR_MIS_1 | DATA_OUT_PHASE: + case SBIC_CSR_MIS_1 | DATA_IN_PHASE: + case SBIC_CSR_MIS_2 | DATA_OUT_PHASE: + case SBIC_CSR_MIS_2 | DATA_IN_PHASE: + { + /* + * Verify that we expected to transfer data... + */ + if ( acb->sc_kv.dc_count <= 0 ) { + printf("next: DATA phase with xfer count == %d, asr:0x%02x csr:0x%02x\n", + acb->sc_kv.dc_count, asr, csr); + goto abort; + } + + /* + * Should we transfer using PIO or DMA ? + */ + if ( dev->sc_xs->flags & SCSI_POLL || dev->sc_flags & SBICF_ICMD || + acb->sc_dmacmd == 0 ) { + + /* + * Do PIO transfer + */ + int i; + +#ifdef DEBUG + if ( data_pointer_debug > 1 ) + printf("next PIO: %d(%x:%x)\n", dev->target, + acb->sc_kv.dc_addr, + acb->sc_kv.dc_count); +#endif + + if ( SBIC_PHASE(csr) == DATA_IN_PHASE ) + /* + * data in + */ + i = sbicxfin(regs, acb->sc_kv.dc_count, + acb->sc_kv.dc_addr); + else + /* + * data out + */ + i = sbicxfout(regs, acb->sc_kv.dc_count, + acb->sc_kv.dc_addr); + + acb->sc_kv.dc_addr += (acb->sc_kv.dc_count - i); + acb->sc_kv.dc_count = i; + + /* + * Update current count... + */ + acb->sc_tcnt = dev->sc_tcnt = i; + + dev->sc_flags &= ~SBICF_INDMA; + + } else { + + /* + * Do DMA transfer + * set next dma addr and dec count + */ + sbic_save_ptrs(dev); + sbic_load_ptrs(dev); + + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI | + SBIC_MACHINE_DMA_MODE); + +#ifdef DEBUG + dev->sc_dmatimo = 1; + if ( data_pointer_debug > 1 ) + printf("next DMA: %d(%x:%x)\n", dev->target, + dev->sc_cur->dc_addr, + dev->sc_tcnt); +#endif + /* + * Start the DMA chip going + */ + dev->sc_tcnt = dev->sc_dmanext(dev); + + /* + * Tell the WD chip how much to transfer this time around + */ + SBIC_TC_PUT(regs, (unsigned)dev->sc_tcnt); + + /* + * Start the transfer + */ + SET_SBIC_cmd(regs, SBIC_CMD_XFER_INFO); + + /* + * Indicate that we're in DMA mode + */ + dev->sc_flags |= SBICF_INDMA; + } + } + break; + + case SBIC_CSR_XFERRED | MESG_IN_PHASE: + case SBIC_CSR_MIS | MESG_IN_PHASE: + case SBIC_CSR_MIS_1 | MESG_IN_PHASE: + case SBIC_CSR_MIS_2 | MESG_IN_PHASE: + { + sbic_save_ptrs(dev); + + /* + * Handle a single message in... + */ + return sbicmsgin(dev); + } + + case SBIC_CSR_MSGIN_W_ACK: + { + /* + * We should never see this since it's handled in 'sbicmsgin()' + * but just for the sake of paranoia... + */ + SET_SBIC_cmd(regs, SBIC_CMD_CLR_ACK); /* Dunno what I'm ACKing */ + printf("Acking unknown msgin CSR:%02x",csr); + } + break; + + case SBIC_CSR_XFERRED | MESG_OUT_PHASE: + case SBIC_CSR_MIS | MESG_OUT_PHASE: + case SBIC_CSR_MIS_1 | MESG_OUT_PHASE: + case SBIC_CSR_MIS_2 | MESG_OUT_PHASE: + { + /* + * We only ever handle a message out phase here for sending a + * REJECT message. + */ + sbic_save_ptrs(dev); + +#ifdef DEBUG + if (sync_debug) + printf ("sending REJECT msg to last msg.\n"); +#endif + + SEND_BYTE(regs, MSG_REJECT); + WAIT_CIP(regs); + } + break; + + case SBIC_CSR_DISC: + case SBIC_CSR_DISC_1: + { + /* + * Try to schedule another target + */ + sbic_save_ptrs(dev); + + dev->sc_flags &= ~SBICF_SELECTED; + +#ifdef DEBUG + if ( reselect_debug > 1 ) + printf("sbicnext target %d disconnected\n", dev->target); +#endif + + TAILQ_INSERT_HEAD(&dev->nexus_list, acb, chain); + + ++dev->sc_tinfo[dev->target].dconns; + + dev->sc_nexus = NULL; + dev->sc_xs = NULL; + + if ( acb->xs->flags & SCSI_POLL || dev->sc_flags & SBICF_ICMD || + !sbic_parallel_operations ) + return SBIC_STATE_DISCONNECT; + + QPRINTF(("sbicnext: calling sbic_sched\n")); + + sbic_sched(dev); + + QPRINTF(("sbicnext: sbic_sched returned\n")); + + return SBIC_STATE_DISCONNECT; + } + + case SBIC_CSR_RSLT_NI: + case SBIC_CSR_RSLT_IFY: + { + /* + * A reselection. + * Note that since we don't enable Advanced Features (assuming + * the WD chip is at least the 'A' revision), we're only ever + * likely to see the 'SBIC_CSR_RSLT_NI' status. But for the + * hell of it, we'll handle it anyway, for all the extra code + * it needs... + */ + u_char newtarget, + newlun; + + GET_SBIC_rselid(regs, newtarget); + + /* + * check SBIC_RID_SIV? + */ + newtarget &= SBIC_RID_MASK; + + if ( csr == SBIC_CSR_RSLT_IFY ) { + + /* + * Read Identify msg to avoid lockup + */ + GET_SBIC_data(regs, newlun); + WAIT_CIP(regs); + newlun &= SBIC_TLUN_MASK; + + } else { + + /* + * Need to read Identify message the hard way, assuming + * the target even sends us one... + */ + for (newlun = 255; newlun; --newlun) { + GET_SBIC_asr(regs, asr); + if (asr & SBIC_ASR_INT) + break; + delay(10); + } + + /* + * If we didn't get an interrupt, somethink's up + */ + if ( (asr & SBIC_ASR_INT) == 0 ) { + printf("%s: Reselect without identify? asr %x\n", + dev->sc_dev.dv_xname, asr); + newlun = 0; /* XXXX */ + } else { + /* + * We got an interrupt, verify that it's a change to + * message in phase, and if so read the message. + */ + GET_SBIC_csr(regs,csr); + + if (csr == (SBIC_CSR_MIS | MESG_IN_PHASE) || + csr == (SBIC_CSR_MIS_1 | MESG_IN_PHASE) || + csr == (SBIC_CSR_MIS_2 | MESG_IN_PHASE) ) { + /* + * Yup, gone to message in. Fetch the target LUN + */ + sbicmsgin(dev); + newlun = dev->sc_msg[0] & 0x07; + + } else { + /* + * Whoops! Target didn't go to message in phase!! + */ + printf("RSLT_NI - not MESG_IN_PHASE %x\n", csr); + newlun = 0; /* XXXSCW */ + } + } + } + + /* + * Ok, we have the identity of the reselecting target. + */ +#ifdef DEBUG + if ( reselect_debug > 1 || + (reselect_debug && csr == SBIC_CSR_RSLT_NI) ) { + printf("sbicnext: reselect %s from targ %d lun %d\n", + csr == SBIC_CSR_RSLT_NI ? "NI" : "IFY", newtarget, newlun); + } +#endif + + if ( dev->sc_nexus ) { + /* + * Whoops! We've been reselected with an command in progress! + * The best we can do is to put the current command back on the + * ready list and hope for the best. + */ +#ifdef DEBUG + if ( reselect_debug > 1 ) { + printf("%s: reselect %s with active command\n", + dev->sc_dev.dv_xname, + csr == SBIC_CSR_RSLT_NI ? "NI" : "IFY"); + } +#endif + + TAILQ_INSERT_HEAD(&dev->ready_list, dev->sc_nexus, chain); + + dev->sc_tinfo[dev->target].lubusy &= ~(1 << dev->lun); + + dev->sc_nexus = NULL; + dev->sc_xs = NULL; + } + + /* + * Reload sync values for this target + */ + if ( dev->sc_sync[newtarget].state == SYNC_DONE ) + SET_SBIC_syn(regs, SBIC_SYN (dev->sc_sync[newtarget].offset, + dev->sc_sync[newtarget].period)); + else + SET_SBIC_syn(regs, SBIC_SYN (0, sbic_min_period)); + + /* + * Loop through the nexus list until we find the saved entry + * for the reselecting target... + */ + for (acb = dev->nexus_list.tqh_first; acb; + acb = acb->chain.tqe_next) { + + if ( acb->xs->sc_link->target == newtarget && + acb->xs->sc_link->lun == newlun) { + /* + * We've found the saved entry. Dequeue it, and + * make it current again. + */ + TAILQ_REMOVE(&dev->nexus_list, acb, chain); + + dev->sc_nexus = acb; + dev->sc_xs = acb->xs; + dev->sc_flags |= SBICF_SELECTED; + dev->target = newtarget; + dev->lun = newlun; + break; + } + } + + if ( acb == NULL ) { + printf("%s: reselect %s targ %d not in nexus_list %x\n", + dev->sc_dev.dv_xname, + csr == SBIC_CSR_RSLT_NI ? "NI" : "IFY", newtarget, + &dev->nexus_list.tqh_first); + panic("bad reselect in sbic"); + } + + if ( csr == SBIC_CSR_RSLT_IFY ) + SET_SBIC_cmd(regs, SBIC_CMD_CLR_ACK); + } + break; + + default: + abort: + { + /* + * Something unexpected happened -- deal with it. + */ + printf("next: aborting asr 0x%02x csr 0x%02x\n", asr, csr); + +#ifdef DDB + Debugger(); +#endif + +#ifdef DEBUG + dev->sc_dmatimo = 0; + if ( data_pointer_debug > 1 ) + printf("next dmastop: %d(%x:%x)\n", dev->target, + dev->sc_cur->dc_addr, + dev->sc_tcnt); +#endif + + dev->sc_dmastop(dev); + SET_SBIC_control(regs, SBIC_CTL_EDI | SBIC_CTL_IDI); + if ( dev->sc_xs ) sbicerror(dev, csr); + sbicabort(dev, "next"); + + if ( dev->sc_flags & SBICF_INDMA ) { + dev->sc_flags &= ~(SBICF_INDMA | SBICF_DCFLUSH); + +#ifdef DEBUG + dev->sc_dmatimo = 0; + if ( data_pointer_debug > 1 ) + printf("next dmastop: %d(%x:%x)\n", dev->target, + dev->sc_cur->dc_addr, + dev->sc_tcnt); +#endif + sbic_scsidone(acb, -1); + } + + return SBIC_STATE_ERROR; + } + } + + return(SBIC_STATE_RUNNING); +} + + +/* + * Check if DMA can not be used with specified buffer + */ +int +sbiccheckdmap(bp, len, mask) + void *bp; + u_long len, + mask; +{ + u_char *buffer; + u_long phy_buf; + u_long phy_len; + + buffer = bp; + + if ( len == 0 ) + return(1); + + while ( len ) { + + phy_buf = kvtop(buffer); + phy_len = NBPG - ((int) buffer & PGOFSET); + + if ( len < phy_len ) + phy_len = len; + + if ( phy_buf & mask ) + return(1); + + buffer += phy_len; + len -= phy_len; + } + + return(0); +} + +int +sbictoscsiperiod(dev, a) + struct sbic_softc *dev; + int a; +{ + unsigned int fs; + + /* + * cycle = DIV / (2 * CLK) + * DIV = FS + 2 + * best we can do is 200ns at 20Mhz, 2 cycles + */ + + GET_SBIC_myid(dev->sc_sbicp, fs); + + fs = (fs >> 6) + 2; /* DIV */ + + fs = (fs * 10000) / (dev->sc_clkfreq << 1); /* Cycle, in ns */ + + if ( a < 2 ) + a = 8; /* map to Cycles */ + + return ( (fs * a) >> 2 ); /* in 4 ns units */ +} + +int +sbicfromscsiperiod(dev, p) + struct sbic_softc *dev; + int p; +{ + unsigned fs, + ret; + + /* + * Just the inverse of the above + */ + GET_SBIC_myid(dev->sc_sbicp, fs); + + fs = (fs >> 6) + 2; /* DIV */ + + fs = (fs * 10000) / (dev->sc_clkfreq << 1); /* Cycle, in ns */ + + ret = p << 2; /* in ns units */ + ret = ret / fs; /* in Cycles */ + + if ( ret < sbic_min_period ) + return(sbic_min_period); + + /* + * verify rounding + */ + if ( sbictoscsiperiod(dev, ret) < p ) + ret++; + + return( (ret >= 8) ? 0 : ret ); +} + +#ifdef DEBUG +void +sbictimeout(dev) + struct sbic_softc *dev; +{ + int s, + asr; + + s = splbio(); + + if ( dev->sc_dmatimo ) { + + if ( dev->sc_dmatimo > 1 ) { + + printf("%s: dma timeout #%d\n", dev->sc_dev.dv_xname, + dev->sc_dmatimo - 1); + + GET_SBIC_asr(dev->sc_sbicp, asr); + + if ( asr & SBIC_ASR_INT ) { + /* + * We need to service a missed IRQ + */ + sbicintr(dev); + } else { + (void) sbicabort(dev, "timeout"); + splx(s); + return; + } + } + + dev->sc_dmatimo++; + } + + splx(s); + + timeout((void *)sbictimeout, dev, 30 * hz); +} +#endif |