/*
* 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. 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
*/
/*
* Changes Copyright (c) 1996 Steve Woodford
* Original Copyright (c) 1994 Christian E. Hopps
*
* 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.
*/
/*
* 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 (struct sbic_softc *, void *, int, void *, int);
int sbicgo (struct sbic_softc *, struct scsipi_xfer *);
int sbicdmaok (struct sbic_softc *, struct scsipi_xfer *);
int sbicwait (sbic_regmap_p, u_char, int , int);
int sbiccheckdmap (void *, u_long, u_long);
u_char sbicselectbus (struct sbic_softc *);
int sbicxfout (sbic_regmap_p, int, void *);
int sbicxfin (sbic_regmap_p, int, void *);
int sbicfromscsiperiod (struct sbic_softc *, int);
int sbictoscsiperiod (struct sbic_softc *, int);
int sbicpoll (struct sbic_softc *);
int sbicnextstate (struct sbic_softc *, u_char, u_char);
int sbicmsgin (struct sbic_softc *);
int sbicabort (struct sbic_softc *, const char *);
void sbicxfdone (struct sbic_softc *);
void sbicerror (struct sbic_softc *, u_char);
void sbicreset (struct sbic_softc *);
void sbic_scsidone (struct sbic_acb *, int);
void sbic_sched (struct sbic_softc *);
void sbic_save_ptrs (struct sbic_softc *);
void sbic_load_ptrs (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 = 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(struct sbic_softc *dev);
#else
#define QPRINTF(a) /* */
#endif
/*
* default minphys routine for sbic based controllers
*/
void
sbic_minphys(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(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("%s: asr %02x canceled!\n", __func__, 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;
/*
* 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(struct scsipi_channel *chan, scsipi_adapter_req_t req,
void *arg)
{
struct scsipi_xfer *xs;
struct scsipi_periph *periph;
struct sbic_softc *dev = device_private(chan->chan_adapter->adapt_dev);
struct sbic_acb *acb;
int flags, s;
if (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(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;
/*
* 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", periph->periph_target,
periph->periph_lun);
#endif
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 ) {
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 != NULL;
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)
;
else if ( acb->chain.tqe_next ) {
TAILQ_REMOVE(&dev->ready_list, acb, chain);
} else {
printf("%s: can't find matching acb\n",
device_xname(dev->sc_dev));
#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[periph->periph_target].cmds++;
scsipi_done(xs);
if (dosched)
sbic_sched(dev);
}
int
sbicdmaok(struct sbic_softc *dev, struct scsipi_xfer *xs)
{
/*
* 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",
device_xname(dev->sc_dev), 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);
}
/*
* 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(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;
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 separate 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);
/*
* 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 definitely 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->xs_control & XS_CTL_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;
/*
* 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(sbic_regmap_p regs, int len, void *bp)
{
int wait = sbic_data_wait;
u_char asr, *buf = bp;
/*
* 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);
/*
* 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(struct sbic_softc *dev, void *cbuf, int clen, void *buf, int 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;
/*
* Make sure pointers are OK
*/
dev->sc_last = dev->sc_cur = &acb->sc_pa;
dev->sc_tcnt = acb->sc_tcnt = 0;
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;
/*
* 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);
/*
* 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(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);
/*
* 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(struct sbic_softc *dev)
{
sbic_regmap_p regs = dev->sc_sbicp;
u_char asr, csr;
int i;
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(struct sbic_softc *dev)
{
sbic_regmap_p regs = dev->sc_sbicp;
u_char asr, csr = SBIC_CSR_RESET; /* XXX: Quell un-init warning */
int i;
/*
* Wait for the next interrupt
*/
SBIC_WAIT(regs, SBIC_ASR_INT, sbic_cmd_wait);
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(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
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);
}
}
#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])) {
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;
/*
* 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
/*
* 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;
}
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);
/*
* 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->xs_control & XS_CTL_POLL ||
dev->sc_flags & SBICF_ICMD ||
acb->sc_dmacmd == 0 ) {
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);
/*
* 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...
*/
/* Dunno what I'm ACKing */
SET_SBIC_cmd(regs, SBIC_CMD_CLR_ACK);
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
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;
/*
* 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, something's up
*/
if ((asr & SBIC_ASR_INT) == 0) {
printf("%s: Reselect without identify?"
" asr %x\n", device_xname(dev->sc_dev), 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;
/*
* 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
* a 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",
device_xname(dev->sc_dev),
csr == SBIC_CSR_RSLT_NI ? "NI" : "IFY");
}
#endif
/*
* 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->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);
/*
* Check if DMA can not be used with specified buffer
*/
int
sbiccheckdmap(void *bp, u_long len, u_long mask)
{
u_char *buffer;
u_long phy_buf;
u_long phy_len;