2471 lines
66 KiB
C
2471 lines
66 KiB
C
/* $NetBSD: sbic.c,v 1.1 2001/08/19 03:16:22 wdk Exp $ */
|
|
|
|
/*
|
|
* Changes Copyright (c) 2001 Wayne Knowles
|
|
* 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
|
|
*/
|
|
|
|
/*
|
|
* This version of the driver is pretty well generic, so should work with
|
|
* any flavour of WD33C93 chip.
|
|
*/
|
|
#include "opt_ddb.h"
|
|
|
|
#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 <dev/scsipi/scsi_all.h>
|
|
#include <dev/scsipi/scsipi_all.h>
|
|
#include <dev/scsipi/scsiconf.h>
|
|
|
|
#include <uvm/uvm_extern.h>
|
|
|
|
#include <machine/bus.h>
|
|
|
|
#include <sgimips/hpc/sbicreg.h>
|
|
#include <sgimips/hpc/sbicvar.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 scsipi_xfer *));
|
|
int sbicdmaok __P((struct sbic_softc *, struct scsipi_xfer *));
|
|
int sbicwait __P((struct sbic_softc *, u_char, int , int));
|
|
u_char sbicselectbus __P((struct sbic_softc *));
|
|
int sbicxfout __P((struct sbic_softc *, int, void *));
|
|
int sbicxfin __P((struct sbic_softc *, int, void *));
|
|
int sbicfromscsiperiod __P((struct sbic_softc *, int));
|
|
int sbictoscsiperiod __P((struct sbic_softc *, int));
|
|
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_dma_stop __P((struct sbic_softc *));
|
|
void sbic_dma_setup __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
|
|
|
|
#define QPRINTF(a) if (sbic_debug) printf a
|
|
|
|
int sbic_debug = 0; /* 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 data_pointer_debug = 0; /* Debug Data Pointer related things */
|
|
|
|
void sbictimeout __P((struct sbic_softc *dev));
|
|
|
|
#else
|
|
#define QPRINTF(a) /* */
|
|
#endif
|
|
|
|
/*
|
|
* Save DMA pointers. Take into account partial transfer. Shut down DMA.
|
|
*/
|
|
void
|
|
sbic_dma_stop(dev)
|
|
struct sbic_softc *dev;
|
|
{
|
|
struct sbic_acb* acb;
|
|
int count,
|
|
asr,
|
|
s;
|
|
|
|
/*
|
|
* Only need to save pointers if DMA was active...
|
|
*/
|
|
if ((dev->sc_flags & SBICF_INDMA) == 0)
|
|
return;
|
|
|
|
s = splbio();
|
|
|
|
/*
|
|
* Wait until WD chip is idle
|
|
*/
|
|
do {
|
|
GET_SBIC_asr(dev, asr); /* XXX */
|
|
if (asr & SBIC_ASR_DBR) {
|
|
printf("sbic_dma_stop: 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_usedma = dev->sc_usedma;
|
|
|
|
/*
|
|
* Fetch the residual count
|
|
*/
|
|
SBIC_TC_GET(dev, 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(dev, 0);
|
|
SET_SBIC_control(dev, 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;
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug)
|
|
printf("dma_stop\n");
|
|
#endif
|
|
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Setup for dma transfer
|
|
*/
|
|
void
|
|
sbic_dma_setup(dev)
|
|
struct sbic_softc *dev;
|
|
{
|
|
struct sbic_acb *acb = dev->sc_nexus;
|
|
int s;
|
|
|
|
if (acb->sc_kv.dc_count) {
|
|
s = splbio();
|
|
dev->sc_tcnt = dev->sc_dmasetup(dev, dev->sc_dmamap,
|
|
acb->flags);
|
|
splx(s);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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_scsipi_cmd().
|
|
*/
|
|
void
|
|
sbic_scsi_request(chan, req, arg)
|
|
struct scsipi_channel *chan;
|
|
scsipi_adapter_req_t req;
|
|
void *arg;
|
|
{
|
|
struct scsipi_xfer *xs;
|
|
struct scsipi_periph *periph;
|
|
struct sbic_softc *dev = (void *)chan->chan_adapter->adapt_dev;
|
|
struct sbic_acb *acb;
|
|
int flags, s;
|
|
|
|
switch (req) {
|
|
case ADAPTER_REQ_RUN_XFER:
|
|
xs = arg;
|
|
periph = xs->xs_periph;
|
|
flags = xs->xs_control;
|
|
|
|
if (flags & XS_CTL_DATA_UIO)
|
|
panic("sbic: scsi data uio requested");
|
|
|
|
if (dev->sc_nexus && (flags & XS_CTL_POLL))
|
|
panic("sbic_scsicmd: busy");
|
|
|
|
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",
|
|
periph->periph_target);
|
|
#ifdef DDB
|
|
Debugger();
|
|
#endif
|
|
#endif
|
|
xs->error = XS_RESOURCE_SHORTAGE;
|
|
scsipi_done(xs);
|
|
return;
|
|
}
|
|
|
|
if (flags & XS_CTL_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;
|
|
memcpy(&acb->cmd, xs->cmd, xs->cmdlen);
|
|
|
|
if (sbic_no_dma || flags & XS_CTL_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 = periph->periph_target;
|
|
dev->lun = periph->periph_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;
|
|
}
|
|
|
|
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;
|
|
|
|
case ADAPTER_REQ_GROW_RESOURCES:
|
|
/* XXX Not supported. */
|
|
return;
|
|
|
|
case ADAPTER_REQ_SET_XFER_MODE:
|
|
/* XXX Not supported. */
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* attempt to start the next available command
|
|
*/
|
|
void
|
|
sbic_sched(dev)
|
|
struct sbic_softc *dev;
|
|
{
|
|
struct scsipi_xfer *xs;
|
|
struct scsipi_periph *periph = 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;
|
|
|
|
periph = acb->xs->xs_periph;
|
|
i = periph->periph_target;
|
|
j = 1 << periph->periph_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;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (acb == NULL) {
|
|
QPRINTF(("sbicsched: no work\n"));
|
|
return; /* did not find an available command */
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug)
|
|
printf("sbic_sched(%d,%d)\n", periph->periph_target,
|
|
periph->periph_lun);
|
|
#endif
|
|
|
|
dev->sc_xs = xs = acb->xs;
|
|
flags = xs->xs_control;
|
|
|
|
if (flags & XS_CTL_RESET)
|
|
sbicreset(dev);
|
|
|
|
dev->sc_stat[0] = -1;
|
|
dev->target = periph->periph_target;
|
|
dev->lun = periph->periph_lun;
|
|
|
|
if (flags & XS_CTL_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 && xs->error != XS_SELTIMEOUT)
|
|
return;
|
|
else
|
|
stat = dev->sc_stat[0];
|
|
|
|
sbic_scsidone(acb, stat);
|
|
}
|
|
|
|
void
|
|
sbic_scsidone(acb, stat)
|
|
struct sbic_acb *acb;
|
|
int stat;
|
|
{
|
|
struct scsipi_xfer *xs = acb->xs;
|
|
struct scsipi_periph *periph = xs->xs_periph;
|
|
struct sbic_softc *dev = (void *)periph->periph_channel->chan_adapter->adapt_dev;
|
|
int dosched = 0;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
if (acb == NULL || xs == NULL) {
|
|
panic("sbic_scsidone -- (%d,%d) no scsipi_xfer\n",
|
|
dev->target, dev->lun);
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug)
|
|
printf("scsidone: (%d,%d)->(%d,%d)%02x\n", periph->periph_target,
|
|
periph->periph_lun, dev->target, dev->lun, stat);
|
|
|
|
if (xs->xs_periph->periph_target == dev->sc_channel.chan_id)
|
|
panic("target == hostid");
|
|
#endif
|
|
|
|
xs->status = stat;
|
|
xs->resid = 0; /* XXXX */
|
|
if (xs->error == XS_NOERROR) {
|
|
if (stat == SCSI_CHECK || stat == SCSI_BUSY)
|
|
xs->error = XS_BUSY;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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[periph->periph_target].lubusy &=
|
|
~(1 << periph->periph_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 {
|
|
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[periph->periph_target].lubusy &=
|
|
~(1 << periph->periph_lun);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (a == NULL) {
|
|
if (acb->chain.tqe_next)
|
|
TAILQ_REMOVE(&dev->ready_list, acb, chain);
|
|
else
|
|
panic("%s: can't find matching acb\n",
|
|
dev->sc_dev.dv_xname);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put it on the free list.
|
|
*/
|
|
acb->flags = ACB_FREE;
|
|
TAILQ_INSERT_HEAD(&dev->free_list, acb, chain);
|
|
|
|
dev->sc_tinfo[periph->periph_target].cmds++;
|
|
|
|
scsipi_done(xs);
|
|
|
|
if (dosched)
|
|
sbic_sched(dev);
|
|
}
|
|
|
|
/*
|
|
* Check if current operation can be done using DMA
|
|
*
|
|
* returns 1 if DMA OK
|
|
*/
|
|
int
|
|
sbicdmaok(dev, xs)
|
|
struct sbic_softc *dev;
|
|
struct scsipi_xfer *xs;
|
|
{
|
|
if (sbic_no_dma || xs->datalen == 0)
|
|
return (0);
|
|
return(1);
|
|
|
|
if (sbic_no_dma || xs->datalen == 0 ||
|
|
xs->datalen & 0x03 || (int)xs->data & 0x03)
|
|
return(0);
|
|
|
|
return(1);
|
|
}
|
|
|
|
int
|
|
sbicwait(dev, until, timeo, line)
|
|
struct sbic_softc *dev;
|
|
u_char until;
|
|
int timeo;
|
|
int line;
|
|
{
|
|
u_char val;
|
|
|
|
if (timeo == 0)
|
|
timeo = 1000000; /* some large value.. */
|
|
|
|
GET_SBIC_asr(dev, val);
|
|
|
|
while ((val & until) == 0) {
|
|
|
|
if (timeo-- == 0) {
|
|
int csr;
|
|
GET_SBIC_csr(dev, 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(dev, val);
|
|
}
|
|
|
|
return(val);
|
|
}
|
|
|
|
int
|
|
sbicabort(dev, where)
|
|
struct sbic_softc *dev;
|
|
char *where;
|
|
{
|
|
u_char csr,
|
|
asr;
|
|
|
|
GET_SBIC_asr(dev, asr);
|
|
GET_SBIC_csr(dev, csr);
|
|
|
|
printf ("%s: abort %s: csr = 0x%02x, asr = 0x%02x\n",
|
|
dev->sc_dev.dv_xname, where, csr, asr);
|
|
|
|
/*
|
|
* 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(dev, asr);
|
|
printf("%s: abort %s: clearing data buffer 0x%02x\n",
|
|
dev->sc_dev.dv_xname, where, asr);
|
|
GET_SBIC_asr(dev, asr);
|
|
if (asr & SBIC_ASR_DBR) /* Not the read direction, then */
|
|
SET_SBIC_data(dev, asr);
|
|
GET_SBIC_asr(dev, asr);
|
|
}
|
|
|
|
WAIT_CIP(dev);
|
|
|
|
printf("%s: sbicabort - sending ABORT command\n", dev->sc_dev.dv_xname);
|
|
SET_SBIC_cmd(dev, SBIC_CMD_ABORT);
|
|
WAIT_CIP(dev);
|
|
|
|
GET_SBIC_asr(dev, 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(dev, SBIC_CMD_DISC);
|
|
|
|
do {
|
|
SBIC_WAIT (dev, SBIC_ASR_INT, 0);
|
|
GET_SBIC_asr(dev, asr);
|
|
GET_SBIC_csr(dev, 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;
|
|
|
|
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);
|
|
callout_init(&dev->sc_timo_ch);
|
|
|
|
dev->sc_nexus = NULL;
|
|
dev->sc_xs = NULL;
|
|
|
|
acb = dev->sc_acb;
|
|
memset(acb, 0, sizeof(dev->sc_acb));
|
|
|
|
for (i = 0; i < sizeof(dev->sc_acb) / sizeof(*acb); i++) {
|
|
TAILQ_INSERT_TAIL(&dev->free_list, acb, chain);
|
|
acb++;
|
|
}
|
|
|
|
memset(dev->sc_tinfo, 0, sizeof(dev->sc_tinfo));
|
|
|
|
#ifdef DEBUG
|
|
/*
|
|
* make sure timeout is really not needed
|
|
*/
|
|
callout_reset(&dev->sc_timo_ch, 30 * hz, (void *)sbictimeout, dev);
|
|
#endif
|
|
|
|
} else
|
|
panic("sbic: reinitializing driver!");
|
|
|
|
dev->sc_flags |= SBICF_ALIVE;
|
|
dev->sc_flags &= ~SBICF_SELECTED;
|
|
|
|
/*
|
|
* initialize inhibit array
|
|
* Never enable Sync, since it just doesn't work on mvme147 :(
|
|
*/
|
|
for (i = 0; i < 8; ++i)
|
|
sbic_inhibit_sync[i] = 1;
|
|
|
|
sbicreset(dev);
|
|
}
|
|
|
|
void
|
|
sbicreset(dev)
|
|
struct sbic_softc *dev;
|
|
{
|
|
u_int my_id,
|
|
s;
|
|
u_char csr;
|
|
|
|
s = splbio();
|
|
|
|
my_id = dev->sc_channel.chan_id & 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(dev, my_id);
|
|
|
|
/*
|
|
* Reset the chip
|
|
*/
|
|
SET_SBIC_cmd(dev, SBIC_CMD_RESET);
|
|
DELAY(25);
|
|
|
|
SBIC_WAIT(dev, SBIC_ASR_INT, 0);
|
|
GET_SBIC_csr(dev, csr); /* clears interrupt also */
|
|
|
|
/*
|
|
* Set up various chip parameters
|
|
*/
|
|
SET_SBIC_control(dev, SBIC_CTL_EDI | SBIC_CTL_IDI);
|
|
|
|
/*
|
|
* don't allow Selection (SBIC_RID_ES)
|
|
* until we can handle target mode!!
|
|
*/
|
|
SET_SBIC_rselid(dev, SBIC_RID_ER);
|
|
|
|
/*
|
|
* Asynchronous for now
|
|
*/
|
|
SET_SBIC_syn(dev, 0);
|
|
|
|
/*
|
|
* Anything else was zeroed by reset
|
|
*/
|
|
splx(s);
|
|
|
|
dev->sc_flags &= ~SBICF_SELECTED;
|
|
}
|
|
|
|
void
|
|
sbicerror(dev, csr)
|
|
struct sbic_softc *dev;
|
|
u_char csr;
|
|
{
|
|
struct scsipi_xfer *xs = dev->sc_xs;
|
|
|
|
#ifdef DIAGNOSTIC
|
|
if (xs == NULL)
|
|
panic("sbicerror: dev->sc_xs == NULL");
|
|
#endif
|
|
|
|
if (xs->xs_control & XS_CTL_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;
|
|
{
|
|
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(dev, target);
|
|
SET_SBIC_timeo(dev, SBIC_TIMEOUT(250, dev->sc_clkfreq));
|
|
|
|
GET_SBIC_asr(dev, asr);
|
|
|
|
if (asr & (SBIC_ASR_INT|SBIC_ASR_BSY)) {
|
|
/*
|
|
* This means we got ourselves reselected upon
|
|
*/
|
|
QPRINTF(("WD busy (reselect?) ASR=%02x\n", asr));
|
|
return 0;
|
|
}
|
|
|
|
SET_SBIC_cmd(dev, SBIC_CMD_SEL_ATN);
|
|
|
|
/*
|
|
* wait for select (merged from seperate function may need
|
|
* cleanup)
|
|
*/
|
|
WAIT_CIP(dev);
|
|
|
|
do {
|
|
|
|
asr = SBIC_WAIT(dev, 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 (dev, 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(dev, 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 (dev, 0);
|
|
else
|
|
SET_SBIC_rselid (dev, 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 (sbic_no_dma || dev->sc_xs->xs_control & XS_CTL_POLL ||
|
|
dev->sc_flags & SBICF_ICMD || !sbic_enable_reselect)
|
|
SEND_BYTE (dev, MSG_IDENTIFY | lun);
|
|
else
|
|
SEND_BYTE (dev, 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(dev, 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(dev, SBIC_ASR_INT , 0);
|
|
GET_SBIC_csr(dev, 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(dev, 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(dev, 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(dev, len, bp)
|
|
struct sbic_softc *dev;
|
|
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 (dev);
|
|
|
|
SBIC_TC_PUT (dev, 0);
|
|
SET_SBIC_control(dev, SBIC_CTL_EDI | SBIC_CTL_IDI);
|
|
SBIC_TC_PUT (dev, (unsigned)len);
|
|
SET_SBIC_cmd (dev, SBIC_CMD_XFER_INFO);
|
|
|
|
/*
|
|
* Loop for each byte transferred
|
|
*/
|
|
do {
|
|
|
|
GET_SBIC_asr (dev, asr);
|
|
|
|
if (asr & SBIC_ASR_DBR) {
|
|
if (len) {
|
|
SET_SBIC_data (dev, *buf);
|
|
buf++;
|
|
len--;
|
|
} else {
|
|
SET_SBIC_data (dev, 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(dev, len, bp)
|
|
struct sbic_softc *dev;
|
|
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 (dev);
|
|
|
|
SET_SBIC_control(dev, SBIC_CTL_EDI | SBIC_CTL_IDI);
|
|
SBIC_TC_PUT (dev, (unsigned)len);
|
|
SET_SBIC_cmd (dev, SBIC_CMD_XFER_INFO);
|
|
|
|
/*
|
|
* Loop for each byte transferred
|
|
*/
|
|
do {
|
|
|
|
GET_SBIC_asr (dev, asr);
|
|
|
|
if (asr & SBIC_ASR_DBR) {
|
|
if (len) {
|
|
GET_SBIC_data (dev, *buf);
|
|
buf++;
|
|
len--;
|
|
} else {
|
|
u_char foo;
|
|
GET_SBIC_data (dev, 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 (dev, 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;
|
|
{
|
|
struct sbic_acb *acb = dev->sc_nexus;
|
|
u_char csr,
|
|
asr;
|
|
int still_busy = SBIC_STATE_RUNNING;
|
|
|
|
/*
|
|
* Make sure pointers are OK
|
|
*/
|
|
dev->sc_tcnt = acb->sc_tcnt = 0;
|
|
|
|
acb->sc_usedma = 0;
|
|
acb->sc_kv.dc_addr = buf;
|
|
acb->sc_kv.dc_count = len;
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug)
|
|
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(dev, 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 (dev, 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(dev, 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 (dev, phase);
|
|
|
|
if (phase == 0x60) {
|
|
GET_SBIC_tlun (dev, dev->sc_stat[0]);
|
|
still_busy = SBIC_STATE_DONE; /* done */
|
|
} else {
|
|
#ifdef DEBUG
|
|
if (reselect_debug)
|
|
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(dev, 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(dev, 0x46);
|
|
SET_SBIC_cmd(dev, 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(dev, 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(dev, i);
|
|
GET_SBIC_asr(dev, asr);
|
|
|
|
if (asr & SBIC_ASR_DBR) /* Wants us to write */
|
|
SET_SBIC_data(dev, i);
|
|
}
|
|
|
|
GET_SBIC_asr(dev, 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 (dev, 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(dev, csr);
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug)
|
|
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;
|
|
{
|
|
u_char phase,
|
|
csr;
|
|
int s;
|
|
|
|
QPRINTF(("{"));
|
|
s = splbio();
|
|
|
|
/*
|
|
* have the sbic complete on its own
|
|
*/
|
|
SBIC_TC_PUT(dev, 0);
|
|
SET_SBIC_cmd_phase(dev, 0x46);
|
|
SET_SBIC_cmd(dev, SBIC_CMD_SEL_ATN_XFER);
|
|
|
|
do {
|
|
|
|
SBIC_WAIT (dev, SBIC_ASR_INT, 0);
|
|
GET_SBIC_csr (dev, 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 (dev, phase);
|
|
QPRINTF(("}%02x", phase));
|
|
|
|
if (phase == 0x60)
|
|
GET_SBIC_tlun(dev, 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 scsipi_xfer *xs;
|
|
{
|
|
struct sbic_acb *acb = dev->sc_nexus;
|
|
int i,
|
|
count,
|
|
usedma;
|
|
u_char csr,
|
|
asr,
|
|
*addr;
|
|
|
|
dev->target = xs->xs_periph->periph_target;
|
|
dev->lun = xs->xs_periph->periph_lun;
|
|
|
|
usedma = sbicdmaok(dev, xs);
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug)
|
|
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
|
|
*/
|
|
|
|
addr = acb->sc_kv.dc_addr;
|
|
count = acb->sc_kv.dc_count;
|
|
|
|
/*
|
|
* Allocate the DMA chain
|
|
* Mark end of segment...
|
|
*/
|
|
acb->sc_tcnt = dev->sc_tcnt = 0;
|
|
|
|
sbic_dma_setup(dev);
|
|
|
|
/*
|
|
* Enable interrupts but don't do any DMA
|
|
* enintr() also enables interrupts for the sbic
|
|
*/
|
|
dev->sc_enintr(dev);
|
|
|
|
dev->sc_tcnt = count;
|
|
dev->sc_usedma = usedma;
|
|
#ifdef DEBUG
|
|
dev->sc_dmatimo = dev->sc_tcnt ? 1 : 0;
|
|
#endif
|
|
|
|
acb->sc_usedma = dev->sc_usedma;
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug) {
|
|
printf("sbicgo dmago:%d(%lx) dmacmd=0x%02x\n", dev->target,
|
|
dev->sc_tcnt,
|
|
dev->sc_usedma);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Lets cycle a while then let the interrupt handler take over.
|
|
*/
|
|
GET_SBIC_asr(dev, asr);
|
|
|
|
do {
|
|
|
|
QPRINTF(("go "));
|
|
|
|
/*
|
|
* Handle the new phase
|
|
*/
|
|
i = sbicnextstate(dev, csr, asr);
|
|
#if 0
|
|
WAIT_CIP(dev);
|
|
#endif
|
|
if (i == SBIC_STATE_RUNNING) {
|
|
GET_SBIC_asr(dev, asr);
|
|
|
|
if (asr & SBIC_ASR_LCI)
|
|
printf("sbicgo: LCI asr:%02x csr:%02x\n", asr, csr);
|
|
|
|
if (asr & SBIC_ASR_INT)
|
|
GET_SBIC_csr(dev, 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;
|
|
{
|
|
u_char asr,
|
|
csr;
|
|
int i;
|
|
|
|
/*
|
|
* pending interrupt?
|
|
*/
|
|
GET_SBIC_asr (dev, asr);
|
|
if ((asr & SBIC_ASR_INT) == 0)
|
|
return(0);
|
|
|
|
GET_SBIC_csr(dev, csr);
|
|
|
|
do {
|
|
|
|
QPRINTF(("intr[0x%x]", csr));
|
|
|
|
i = sbicnextstate(dev, csr, asr);
|
|
#if 1
|
|
WAIT_CIP(dev);
|
|
#endif
|
|
if (i == SBIC_STATE_RUNNING) {
|
|
GET_SBIC_asr(dev, asr);
|
|
|
|
if (asr & SBIC_ASR_LCI)
|
|
printf("sbicgo: LCI asr:%02x csr:%02x\n", asr, csr);
|
|
|
|
if (asr & SBIC_ASR_INT)
|
|
GET_SBIC_csr(dev, 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;
|
|
{
|
|
u_char asr,
|
|
csr;
|
|
int i;
|
|
|
|
/*
|
|
* Wait for the next interrupt
|
|
*/
|
|
SBIC_WAIT(dev, SBIC_ASR_INT, sbic_cmd_wait);
|
|
|
|
do {
|
|
GET_SBIC_asr (dev, asr);
|
|
|
|
if (asr & SBIC_ASR_INT)
|
|
GET_SBIC_csr(dev, csr);
|
|
|
|
QPRINTF(("poll[0x%x]", csr));
|
|
|
|
/*
|
|
* Handle it
|
|
*/
|
|
i = sbicnextstate(dev, csr, asr);
|
|
|
|
WAIT_CIP(dev);
|
|
GET_SBIC_asr(dev, 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(dev, z);
|
|
GET_SBIC_asr(dev, asr);
|
|
|
|
if (asr & SBIC_ASR_DBR) /* Wants us to write */
|
|
SET_SBIC_data(dev, z);
|
|
}
|
|
|
|
GET_SBIC_asr(dev, 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(dev, SBIC_ASR_INT, sbic_cmd_wait);
|
|
|
|
} while (i == SBIC_STATE_RUNNING);
|
|
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* Handle a single msgin
|
|
*/
|
|
int
|
|
sbicmsgin(dev)
|
|
struct sbic_softc *dev;
|
|
{
|
|
int recvlen = 1;
|
|
u_char asr,
|
|
csr,
|
|
*tmpaddr,
|
|
*msgaddr;
|
|
|
|
tmpaddr = msgaddr = dev->sc_msg;
|
|
|
|
tmpaddr[0] = 0xff;
|
|
tmpaddr[1] = 0xff;
|
|
|
|
GET_SBIC_asr(dev, asr);
|
|
|
|
#ifdef DEBUG
|
|
if (reselect_debug)
|
|
printf("sbicmsgin asr=%02x\n", asr);
|
|
#endif
|
|
|
|
GET_SBIC_selid (dev, csr);
|
|
SET_SBIC_selid (dev, csr | SBIC_SID_FROM_SCSI);
|
|
|
|
SBIC_TC_PUT(dev, 0);
|
|
SET_SBIC_control(dev, SBIC_CTL_EDI | SBIC_CTL_IDI);
|
|
|
|
do {
|
|
while( recvlen-- ) {
|
|
|
|
/*
|
|
* Fetch the next byte of the message
|
|
*/
|
|
RECV_BYTE(dev, *tmpaddr);
|
|
|
|
/*
|
|
* get the command completion interrupt, or we
|
|
* can't send a new command (LCI)
|
|
*/
|
|
SBIC_WAIT(dev, SBIC_ASR_INT, 0);
|
|
GET_SBIC_csr(dev, csr);
|
|
|
|
#ifdef DEBUG
|
|
if (reselect_debug)
|
|
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(dev, SBIC_CMD_CLR_ACK);
|
|
SBIC_WAIT(dev, SBIC_ASR_INT, 0);
|
|
GET_SBIC_csr(dev, csr);
|
|
}
|
|
}
|
|
|
|
if (msgaddr[0] == 0xff) {
|
|
printf("sbicmsgin: sbic swallowed our message\n");
|
|
break;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (sync_debug) {
|
|
GET_SBIC_asr(dev, 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(dev, 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 && 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(dev, SBIC_CMD_SET_ATN);
|
|
WAIT_CIP(dev);
|
|
}
|
|
|
|
} 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(dev, 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(dev, SBIC_CMD_CLR_ACK);
|
|
|
|
/*
|
|
* Wait for the interrupt for the next byte (length)
|
|
*/
|
|
SBIC_WAIT(dev, SBIC_ASR_INT, 0);
|
|
GET_SBIC_csr(dev, csr);
|
|
|
|
#ifdef DEBUG
|
|
QPRINTF(("CLR ACK csr %02x\n", csr));
|
|
#endif
|
|
|
|
/*
|
|
* Read the length byte
|
|
*/
|
|
RECV_BYTE(dev, *tmpaddr);
|
|
|
|
/*
|
|
* Wait for command completion IRQ
|
|
*/
|
|
SBIC_WAIT(dev, SBIC_ASR_INT, 0);
|
|
GET_SBIC_csr(dev, 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(dev, 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(dev, SBIC_CMD_SET_ATN);
|
|
WAIT_CIP(dev);
|
|
}
|
|
|
|
/*
|
|
* Negate ACK to complete the transfer
|
|
*/
|
|
SET_SBIC_cmd(dev, 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(dev, SBIC_ASR_INT, 0);
|
|
if (recvlen > 0)
|
|
GET_SBIC_csr(dev, 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;
|
|
{
|
|
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(dev, 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(dev, 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)
|
|
printf("next dmastop: %d(%lx)\n", dev->target,
|
|
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 (sbic_no_dma || dev->sc_xs->xs_control & XS_CTL_POLL ||
|
|
dev->sc_flags & SBICF_ICMD || acb->sc_usedma == 0) {
|
|
|
|
/*
|
|
* Do PIO transfer
|
|
*/
|
|
int i;
|
|
|
|
#ifdef DEBUG
|
|
if (data_pointer_debug)
|
|
printf("next PIO: %d(%p:%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(dev, acb->sc_kv.dc_count,
|
|
acb->sc_kv.dc_addr);
|
|
else
|
|
/*
|
|
* data out
|
|
*/
|
|
i = sbicxfout(dev, 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_dma_stop(dev);
|
|
sbic_dma_setup(dev);
|
|
|
|
SET_SBIC_control(dev, SBIC_CTL_EDI | SBIC_CTL_IDI |
|
|
SBIC_CTL_DMA);
|
|
|
|
#ifdef DEBUG
|
|
dev->sc_dmatimo = 1;
|
|
if (data_pointer_debug)
|
|
printf("next DMA: %d(%lx)\n", dev->target,
|
|
dev->sc_tcnt);
|
|
#endif
|
|
/*
|
|
* Start the DMA chip going
|
|
*/
|
|
dev->sc_tcnt = dev->sc_dmago(dev);
|
|
|
|
/*
|
|
* Tell the WD chip how much to transfer this time around
|
|
*/
|
|
SBIC_TC_PUT(dev, (unsigned)dev->sc_tcnt);
|
|
|
|
/*
|
|
* Start the transfer
|
|
*/
|
|
SET_SBIC_cmd(dev, 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_dma_stop(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(dev, 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_dma_stop(dev);
|
|
|
|
#ifdef DEBUG
|
|
if (sync_debug)
|
|
printf ("sending REJECT msg to last msg.\n");
|
|
#endif
|
|
|
|
SEND_BYTE(dev, MSG_REJECT);
|
|
WAIT_CIP(dev);
|
|
}
|
|
break;
|
|
|
|
case SBIC_CSR_DISC:
|
|
case SBIC_CSR_DISC_1:
|
|
{
|
|
/*
|
|
* Try to schedule another target
|
|
*/
|
|
sbic_dma_stop(dev);
|
|
|
|
dev->sc_flags &= ~SBICF_SELECTED;
|
|
|
|
#ifdef DEBUG
|
|
if (reselect_debug)
|
|
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 (sbic_no_dma || acb->xs->xs_control & XS_CTL_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(dev, newtarget);
|
|
|
|
/*
|
|
* check SBIC_RID_SIV?
|
|
*/
|
|
newtarget &= SBIC_RID_MASK;
|
|
|
|
if (csr == SBIC_CSR_RSLT_IFY) {
|
|
|
|
/*
|
|
* Read Identify msg to avoid lockup
|
|
*/
|
|
GET_SBIC_data(dev, newlun);
|
|
WAIT_CIP(dev);
|
|
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(dev, 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(dev,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 ||
|
|
(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) {
|
|
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(dev, SBIC_SYN (dev->sc_sync[newtarget].offset,
|
|
dev->sc_sync[newtarget].period));
|
|
else
|
|
SET_SBIC_syn(dev, 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->xs_periph->periph_target == newtarget &&
|
|
acb->xs->xs_periph->periph_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 %p\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(dev, 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)
|
|
printf("next dmastop: %d(%lx)\n", dev->target,
|
|
dev->sc_tcnt);
|
|
#endif
|
|
|
|
dev->sc_dmastop(dev);
|
|
SET_SBIC_control(dev, 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)
|
|
printf("next dmastop: %d(%lx)\n", dev->target,
|
|
dev->sc_tcnt);
|
|
#endif
|
|
sbic_scsidone(acb, -1);
|
|
}
|
|
|
|
return SBIC_STATE_ERROR;
|
|
}
|
|
}
|
|
|
|
return(SBIC_STATE_RUNNING);
|
|
}
|
|
|
|
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, 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, 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, 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);
|
|
|
|
callout_reset(&dev->sc_timo_ch, 30 * hz, (void *)sbictimeout, dev);
|
|
}
|
|
#endif
|