/* $NetBSD: sii.c,v 1.16 2021/08/07 16:19:02 thorpej Exp $ */
/*-
* Copyright (c) 1992, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Ralph Campbell and Rick Macklem.
*
* 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.
*
* @(#)sii.c 8.2 (Berkeley) 11/30/93
*
* from: Header: /sprite/src/kernel/dev/ds3100.md/RCS/devSII.c,
* v 9.2 89/09/14 13:37:41 jhh Exp $ SPRITE (DECWRL)";
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sii.c,v 1.16 2021/08/07 16:19:02 thorpej Exp $");
#include "sii.h"
/*
* SCSI interface driver
*/
#include <sys/param.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <mips/locore.h>
#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsi_message.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsipi_disk.h>
#include <dev/scsipi/scsiconf.h>
/* old 4.4BSD/pmax scsi drivers */
#include <pmax/ibus/siireg.h> /* device registers */
#include <pmax/ibus/siivar.h> /* softc and prototypes */
#include <pmax/pmax/machdep.h> /* prom_scsiid prototype */
/* XXX not in dev/scsipi/scsi_message.h */
#define MSG_EXT_MODIFY_DATA_PTR 0x00
extern struct cfdriver sii_cd;
/*
* MACROS for timing out spin loops.
*
* Wait until expression is true.
*
* Control register bits can change at any time so when the CPU
* reads a register, the bits might change and
* invalidate the setup and hold times for the CPU.
* This macro reads the register twice to be sure the value is stable.
*
* args: var - variable to save control register contents
* reg - control register to read
* expr - expression to spin on
* spincount - maximum number of times through the loop
* cntr - variable for number of tries
*/
#define SII_WAIT_UNTIL(var, reg, expr, spincount, cntr) { \
u_int tmp = reg; \
for (cntr = 0; cntr < spincount; cntr++) { \
while (tmp != (var = reg)) \
tmp = var; \
if (expr) \
break; \
if (cntr >= 100) \
DELAY(100); \
} \
}
#ifdef DEBUG
int sii_debug = 1;
int sii_debug_cmd;
int sii_debug_bn;
int sii_debug_sz;
#define NLOG 16
struct sii_log {
u_short cstat;
u_short dstat;
u_short comm;
u_short msg;
int rlen;
int dlen;
int target;
} sii_log[NLOG], *sii_logp = sii_log;
#endif
static u_char sii_buf[256]; /* used for extended messages */
#define NORESET 0
#define RESET 1
#define NOWAIT 0
#define WAIT 1
/*
* Define a safe address in the SCSI buffer for doing status & message DMA
* XXX why not add another field to softc?
*/
#define SII_BUF_ADDR(sc) ((sc)->sc_buf + SII_MAX_DMA_XFER_LENGTH * 14)
/*
* Forward references
*/
static void sii_Reset(struct siisoftc *sc, int resetbus);
static void sii_StartCmd(struct siisoftc *sc, int target);
static void sii_CmdDone(struct siisoftc *sc, int target, int error);
static void sii_DoIntr(struct siisoftc *sc, u_int dstat);
static void sii_StateChg(struct siisoftc *sc, u_int cstat);
static int sii_GetByte(SIIRegs *regs, int phase, int ack);
static void sii_DoSync(SIIRegs *regs, State *state);
static void sii_StartDMA(SIIRegs *regs, int phase, u_short *dmaAddr,
int size);
#ifdef DEBUG
static void sii_DumpLog(void);
#endif
/*
* Match driver based on name
*/
void
siiattach(struct siisoftc *sc)
{
int i;
sc->sc_target = -1; /* no command active */
/*
* Give each target its own DMA buffer region.
* Make it big enough for 2 max transfers so we can ping pong buffers
* while we copy the data.
*/
for (i = 0; i < SII_NCMD; i++) {
sc->sc_st[i].dmaAddr[0] = (u_short *)
sc->sc_buf + 2 * SII_MAX_DMA_XFER_LENGTH * i;
sc->sc_st[i].dmaAddr[1] = sc->sc_st[i].dmaAddr[0] +
SII_MAX_DMA_XFER_LENGTH;
}
sii_Reset(sc, RESET);
printf(": target %d\n", sc->sc_regs->id & SII_IDMSK);
sc->sc_adapter.adapt_dev = sc->sc_dev;
sc->sc_adapter.adapt_nchannels = 1;
sc->sc_adapter.adapt_openings = 7;
sc->sc_adapter.adapt_max_periph = 1;
sc->sc_adapter.adapt_ioctl = NULL;
sc->sc_adapter.adapt_minphys = minphys;
sc->sc_adapter.adapt_request = sii_scsi_request;
sc->sc_channel.chan_adapter = &sc->sc_adapter;
sc->sc_channel.chan_bustype = &scsi_bustype;
sc->sc_channel.chan_channel = 0;
sc->sc_channel.chan_ntargets = 8;
sc->sc_channel.chan_nluns = 8;
sc->sc_channel.chan_id = sc->sc_regs->id & SII_IDMSK;
/*
* Now try to attach all the sub-devices
*/
config_found(sc->sc_dev, &sc->sc_channel, scsiprint, CFARGS_NONE);
}
/*
* Start activity on a SCSI device.
* We maintain information on each device separately since devices can
* connect/disconnect during an operation.
*/
void
sii_scsi_request(struct scsipi_channel *chan, scsipi_adapter_req_t req, void *arg)
{
struct scsipi_xfer *xs;
struct scsipi_periph *periph;
struct siisoftc *sc;
int target;
int s;
int count;
sc = device_private(chan->chan_adapter->adapt_dev);
switch (req) {
case ADAPTER_REQ_RUN_XFER:
xs = arg;
periph = xs->xs_periph;
target = periph->periph_target;
s = splbio();
if (sc->sc_cmd[target]) {
splx(s);
xs->error = XS_RESOURCE_SHORTAGE;
scsipi_done(xs);
printf("[busy at start]\n");
return;
}
/*
* Build a ScsiCmd for this command and start it.
*/
sc->sc_xs[target] = xs;
sc->sc_cmd[target] = &sc->sc_cmd_fake[target]; /* XXX */
sc->sc_cmd[target]->unit = 0;
sc->sc_cmd[target]->flags = 0;
sc->sc_cmd[target]->buflen = xs->datalen;
sc->sc_cmd[target]->buf = xs->data;
sc->sc_cmd[target]->cmdlen = xs->cmdlen;
sc->sc_cmd[target]->cmd = (u_char *)xs->cmd;
sc->sc_cmd[target]->lun = xs->xs_periph->periph_lun;
sii_StartCmd(sc, target);
splx(s);
if ((xs->xs_control & XS_CTL_POLL) == 0)
return;
count = xs->timeout;
while (count) {
if ((xs->xs_status & XS_STS_DONE) != 0)
return;
siiintr(sc);
/* XXX schedule another command? */
DELAY(1000);
--count;
}
xs->error = XS_TIMEOUT;
scsipi_done(xs);
return;
case ADAPTER_REQ_GROW_RESOURCES:
/* XXX Not supported. */
return;
case ADAPTER_REQ_SET_XFER_MODE:
/* XXX Not supported. */
return;
}
}
/*
* Check to see if any SII chips have pending interrupts
* and process as appropriate.
*/
int
siiintr(void *xxxsc)
{
struct siisoftc *sc = xxxsc;
u_int dstat;
/*
* Find which controller caused the interrupt.
*/
dstat = sc->sc_regs->dstat;
if (dstat & (SII_CI | SII_DI)) {
sii_DoIntr(sc, dstat);
return (0); /* XXX */
}
return (1); /* XXX spurious interrupt? */
}
/*
* Reset the SII chip and do a SCSI reset if 'reset' is true.
* NOTE: if !cold && reset, should probably probe for devices
* since a SCSI bus reset will set UNIT_ATTENTION.
*/
static void
sii_Reset(struct siisoftc* sc, int reset)
/* reset: TRUE => reset SCSI bus */
{
SIIRegs *regs = sc->sc_regs;
#ifdef DEBUG
if (sii_debug > 1)
printf("sii: RESET\n");
#endif
/*
* Reset the SII chip.
*/
regs->comm = SII_CHRESET;
/*
* Set arbitrated bus mode.
*/
regs->csr = SII_HPM;
/*
* Set host adapter ID (from PROM sciiidN variable).
*/
/* XXX device_unit() abuse */
regs->id = SII_ID_IO | prom_scsiid(device_unit(sc->sc_dev));
/*
* Enable SII to drive the SCSI bus.
*/
regs->dictrl = SII_PRE;
regs->dmctrl = 0;
if (reset) {
int i;
/*
* Assert SCSI bus reset for at least 25 Usec to clear the
* world. SII_DO_RST is self clearing.
* Delay 250 ms before doing any commands.
*/
regs->comm = SII_DO_RST;
wbflush();
DELAY(250000);
/* rearbitrate synchronous offset */
for (i = 0; i < SII_NCMD; i++)
sc->sc_st[i].dmaReqAck = 0;
}
/*
* Clear any pending interrupts from the reset.
*/
regs->cstat = regs->cstat;
regs->dstat = regs->dstat;
/*
* Set up SII for arbitrated bus mode, SCSI parity checking,
* Reselect Enable, and Interrupt Enable.
*/
regs->csr = SII_HPM | SII_RSE | SII_PCE | SII_IE;
wbflush();
}
/*
* Start a SCSI command by sending the cmd data
* to a SCSI controller via the SII.
* Call the device done procedure if it can't be started.
* NOTE: we should be called with interrupts disabled.
*/
static void
sii_StartCmd(struct siisoftc *sc, int target)
/* sc: which SII to use */
/* target: which command to start */
{
SIIRegs *regs;
ScsiCmd *scsicmd;
State *state;
u_int status;
int error, retval;
/* if another command is currently in progress, just wait */
if (sc->sc_target >= 0)
return;
/* initialize state information for this command */
scsicmd = sc->sc_cmd[target];
state = &sc->sc_st[target];
state->flags = FIRST_DMA;
state->prevComm = 0;
state->dmalen = 0;
state->dmaCurPhase = -1;
state->dmaPrevPhase = -1;
state->dmaBufIndex = 0;
state->cmd = scsicmd->cmd;
state->cmdlen = scsicmd->cmdlen;
if ((state->buflen = scsicmd->buflen) == 0) {
state->dmaDataPhase = -1; /* illegal phase. shouldn't happen */
state->buf = (char *)0;
} else {
state->buf = scsicmd->buf;
}
#ifdef DEBUG
if (sii_debug > 1) {
printf("sii_StartCmd: %s target %d cmd 0x%x addr %p size %d DMA %d\n",
device_xname(sc->sc_dev),
target, scsicmd->cmd[0], scsicmd->buf, scsicmd->buflen,
state->dmaDataPhase);
}
sii_debug_cmd = scsicmd->cmd[0];
if (scsicmd->cmd[0] == READ_10 ||
scsicmd->cmd[0] == WRITE_10) {
sii_debug_bn = (scsicmd->cmd[2] << 24) |
(scsicmd->cmd[3] << 16) |
(scsicmd->cmd[4] << 8) |
scsicmd->cmd[5];
sii_debug_sz = (scsicmd->cmd[7] << 8) | scsicmd->cmd[8];
} else {
sii_debug_bn = 0;
sii_debug_sz = 0;
}
#endif
/* try to select the target */
regs = sc->sc_regs;
/*
* Another device may have selected us; in which case,
* this command will be restarted later.
*/
if ((status = regs->dstat) & (SII_CI | SII_DI)) {
sii_DoIntr(sc, status);
return;
}
sc->sc_target = target;
#if 0
/* seem to have problems with synchronous transfers */
if (scsicmd->flags & SCSICMD_USE_SYNC) {
printf("sii_StartCmd: doing extended msg\n"); /* XXX */
/*
* Setup to send both the identify message and the synchronous
* data transfer request.
*/
sii_buf[0] = MSG_IDENTIFYFLAG | MSG_IDENTIFY_DISCFLAG;
sii_buf[1] = MSG_EXTENDED;
sii_buf[2] = MSG_EXT_SDTR_LEN;
sii_buf[3] = MSG_EXT_SDTR;
sii_buf[4] = 0;
sii_buf[5] = 3; /* maximum SII chip supports */
state->dmaCurPhase = SII_MSG_OUT_PHASE,
state->dmalen = 6;
sc->sii_copytobuf((u_short *)sii_buf,
(volatile u_short *)SII_BUF_ADDR(sc), 6);
regs->slcsr = target;
regs->dmctrl = state->dmaReqAck;
regs->dmaddrl = (u_short)(SII_BUF_ADDR(sc) >> 1);
regs->dmaddrh = (u_short)(SII_BUF_ADDR(sc) >> 17) & 03;
regs->dmlotc = 6;
regs->comm = SII_DMA | SII_INXFER | SII_SELECT | SII_ATN |
SII_CON | SII_MSG_OUT_PHASE;
} else
#endif
{
/* do a chained, select with ATN and programmed I/O command */
regs->data = MSG_IDENTIFYFLAG | MSG_IDENTIFY_DISCFLAG |
scsicmd->lun;
regs->slcsr = target;
regs->dmctrl = state->dmaReqAck;
regs->comm = SII_INXFER | SII_SELECT | SII_ATN | SII_CON |
SII_MSG_OUT_PHASE;
}
wbflush();
/*
* Wait for something to happen
* (should happen soon or we would use interrupts).
*/
SII_WAIT_UNTIL(status, regs->cstat, status & (SII_CI | SII_DI),
SII_WAIT_COUNT/4, retval);
/* check to see if we are connected OK */
if ((status & (SII_RST | SII_SCH | SII_STATE_MSK)) ==
(SII_SCH | SII_CON)) {
regs->cstat = status;
wbflush();
#ifdef DEBUG
sii_logp->target = target;
sii_logp->cstat = status;
sii_logp->dstat = 0;
sii_logp->comm = regs->comm;
sii_logp->msg = -1;
sii_logp->rlen = state->buflen;
sii_logp->dlen = state->dmalen;
if (++sii_logp >= &sii_log[NLOG])
sii_logp = sii_log;
#endif
/* wait a short time for command phase */
SII_WAIT_UNTIL(status, regs->dstat, status & SII_MIS,
SII_WAIT_COUNT, retval);
#ifdef DEBUG
if (sii_debug > 2)
printf("sii_StartCmd: ds %x cnt %d\n", status, retval);
#endif
if ((status & (SII_CI | SII_MIS | SII_PHASE_MSK)) !=
(SII_MIS | SII_CMD_PHASE)) {
printf("sii_StartCmd: timeout cs %x ds %x cnt %d\n",
regs->cstat, status, retval); /* XXX */
/* process interrupt or continue until it happens */
if (status & (SII_CI | SII_DI))
sii_DoIntr(sc, status);
return;
}
regs->dstat = SII_DNE; /* clear Msg Out DMA done */
/* send command data */
sc->sii_copytobuf((u_short *)state->cmd,
(volatile u_short *)state->dmaAddr[0], state->cmdlen);
sii_StartDMA(regs, state->dmaCurPhase = SII_CMD_PHASE,
state->dmaAddr[0], state->dmalen = scsicmd->cmdlen);
/* wait a little while for DMA to finish */
SII_WAIT_UNTIL(status, regs->dstat, status & (SII_CI | SII_DI),
SII_WAIT_COUNT, retval);
#ifdef DEBUG
if (sii_debug > 2)
printf("sii_StartCmd: ds %x, cnt %d\n", status, retval);
#endif
if (status & (SII_CI | SII_DI))
sii_DoIntr(sc, status);
#ifdef DEBUG
if (sii_debug > 2)
printf("sii_StartCmd: DONE ds %x\n", regs->dstat);
#endif
return;
}
/*
* Another device may have selected us; in which case,
* this command will be restarted later.
*/
if (status & (SII_CI | SII_DI)) {
sii_DoIntr(sc, regs->dstat);
return;
}
/*
* Disconnect if selection command still in progress.
*/
if (status & SII_SIP) {
error = ENXIO; /* device didn't respond */
regs->comm = SII_DISCON;
wbflush();
SII_WAIT_UNTIL(status, regs->cstat,
!(status & (SII_CON | SII_SIP)),
SII_WAIT_COUNT, retval);
} else
error = EBUSY; /* couldn't get the bus */
#ifdef DEBUG
if (sii_debug > 1)
printf("sii_StartCmd: Couldn't select target %d error %d\n",
target, error);
#endif
sc->sc_target = -1;
regs->cstat = 0xffff;
regs->dstat = 0xffff;
regs->comm = 0;
wbflush();
sii_CmdDone(sc, target, error);
}
/*
* Process interrupt conditions.
*/
static void
sii_DoIntr(struct siisoftc *sc, u_int dstat)
{
SIIRegs *regs = sc->sc_regs;
State *state;
u_int cstat;
int i, msg;
u_int comm;
again:
comm = regs->comm;
#ifdef DEBUG
if (sii_debug > 3)
printf("sii_DoIntr: cs %x, ds %x cm %x ",
regs->cstat, dstat, comm);
sii_logp->target = sc->sc_target;
sii_logp->cstat = regs->cstat;
sii_logp->dstat = dstat;
sii_logp->comm = comm;
sii_logp->msg = -1;
if (sc->sc_target >= 0) {
sii_logp->rlen = sc->sc_st[sc->sc_target].buflen;
sii_logp->dlen = sc->sc_st[sc->sc_target].dmalen;
} else {
sii_logp->rlen = 0;
sii_logp->dlen = 0;
}
if (++sii_logp >= &sii_log[NLOG])
sii_logp = sii_log;
#endif
regs->dstat = dstat; /* acknowledge everything */
wbflush();
if (dstat & SII_CI) {
/* deglitch cstat register */
msg = regs->cstat;
while (msg != (cstat = regs->cstat))
msg = cstat;
regs->cstat = cstat; /* acknowledge everything */
wbflush();
#ifdef DEBUG
if (sii_logp > sii_log)
sii_logp[-1].cstat = cstat;
else
sii_log[NLOG - 1].cstat = cstat;
#endif
/* check for a BUS RESET */
if (cstat & SII_RST) {
printf("%s: SCSI bus reset!!\n",
device_xname(sc->sc_dev));
/* need to flush disconnected commands */
for (i = 0; i < SII_NCMD; i++) {
if (!sc->sc_cmd[i])
continue;
sii_CmdDone(sc, i, EIO);
}
/* rearbitrate synchronous offset */
for (i = 0; i < SII_NCMD; i++)
sc->sc_st[i].dmaReqAck = 0;
sc->sc_target = -1;
return;
}
#ifdef notdef
/*
* Check for a BUS ERROR.
* According to DEC, this feature doesn't really work
* and to just clear the bit if it's set.
*/
if (cstat & SII_BER) {
regs->cstat = SII_BER;
wbflush();
}
#endif
/* check for state change */
if (cstat & SII_SCH) {
sii_StateChg(sc, cstat);
comm = regs->comm;
}
}
/* check for DMA completion */
if (dstat & SII_DNE) {
u_short *dma;
char *buf;
/*
* There is a race condition with SII_SCH. There is a short
* window between the time a SII_SCH is seen after a disconnect
* and when the SII_SCH is cleared. A reselect can happen
* in this window and we will clear the SII_SCH without
* processing the reconnect.
*/
if (sc->sc_target < 0) {
cstat = regs->cstat;
printf("%s: target %d DNE?? dev %d,%d cs %x\n",
device_xname(sc->sc_dev), sc->sc_target,
regs->slcsr, regs->destat,
cstat); /* XXX */
if (cstat & SII_DST) {
sc->sc_target = regs->destat;
state = &sc->sc_st[sc->sc_target];
state->prevComm = 0;
} else
panic("sc_target 1");
}
state = &sc->sc_st[sc->sc_target];
/* check for a PARITY ERROR */
if (dstat & SII_IPE) {
state->flags |= PARITY_ERR;
printf("%s: Parity error!!\n",
device_xname(sc->sc_dev));
goto abort;
}
/* dmalen = amount left to transfer, i = amount transferred */
i = state->dmalen;
state->dmalen = 0;
state->dmaCurPhase = -1;
#ifdef DEBUG
if (sii_debug > 4) {
printf("DNE: amt %d ", i);
if (!(dstat & SII_TCZ))
printf("no TCZ?? (%d) ", regs->dmlotc);
} else if (!(dstat & SII_TCZ)) {
printf("%s: device %d: no TCZ?? (%d)\n",
device_xname(sc->sc_dev),
sc->sc_target, regs->dmlotc);
sii_DumpLog(); /* XXX */
}
#endif
switch (comm & SII_PHASE_MSK) {
case SII_CMD_PHASE:
state->cmdlen -= i;
break;
case SII_DATA_IN_PHASE:
/* check for more data for the same phase */
dma = state->dmaAddr[state->dmaBufIndex];
buf = state->buf;
state->buf += i;
state->buflen -= i;
if (state->buflen > 0 && !(dstat & SII_MIS)) {
int len;
/* start reading next chunk */
len = state->buflen;
if (len > SII_MAX_DMA_XFER_LENGTH)
len = SII_MAX_DMA_XFER_LENGTH;
state->dmaBufIndex = !state->dmaBufIndex;
sii_StartDMA(regs,
state->dmaCurPhase = SII_DATA_IN_PHASE,
state->dmaAddr[state->dmaBufIndex],
state->dmaCnt = state->dmalen = len);
dstat &= ~(SII_IBF | SII_TBE);
}
/* copy in the data */
sc->sii_copyfrombuf((volatile u_short *)dma, buf, i);
break;
case SII_DATA_OUT_PHASE:
state->dmaBufIndex = !state->dmaBufIndex;
state->buf += i;
state->buflen -= i;
/* check for more data for the same phase */
if (state->buflen <= 0 || (dstat & SII_MIS))
break;
/* start next chunk */
i = state->buflen;
if (i > SII_MAX_DMA_XFER_LENGTH) {
sii_StartDMA(regs, state->dmaCurPhase =
SII_DATA_OUT_PHASE,
state->dmaAddr[state->dmaBufIndex],
state->dmaCnt = state->dmalen =
SII_MAX_DMA_XFER_LENGTH);
/* prepare for next chunk */
i -= SII_MAX_DMA_XFER_LENGTH;
if (i > SII_MAX_DMA_XFER_LENGTH)
i = SII_MAX_DMA_XFER_LENGTH;
sc->sii_copytobuf((u_short *)(state->buf +
SII_MAX_DMA_XFER_LENGTH),
(volatile u_short *)
state->dmaAddr[!state->dmaBufIndex], i);
} else {
sii_StartDMA(regs, state->dmaCurPhase =
SII_DATA_OUT_PHASE,
state->dmaAddr[state->dmaBufIndex],
state->dmaCnt = state->dmalen = i);
}
dstat &= ~(SII_IBF | SII_TBE);
}
}
/* check for phase change or another MsgIn/Out */
if (dstat & (SII_MIS | SII_IBF | SII_TBE)) {
/*
* There is a race condition with SII_SCH. There is a short
* window between the time a SII_SCH is seen after a disconnect
* and when the SII_SCH is cleared. A reselect can happen
* in this window and we will clear the SII_SCH without
* processing the reconnect.
*/
if (sc->sc_target < 0) {
cstat = regs->cstat;
printf("%s: target %d MIS?? dev %d,%d cs %x ds %x\n",
device_xname(sc->sc_dev), sc->sc_target,
regs->slcsr, regs->destat,
cstat, dstat); /* XXX */
if (cstat & SII_DST) {
sc->sc_target = regs->destat;
state = &sc->sc_st[sc->sc_target];
state->prevComm = 0;
} else {
#ifdef DEBUG
sii_DumpLog();
#endif
panic("sc_target 2");
}
}
state = &sc->sc_st[sc->sc_target];
switch (dstat & SII_PHASE_MSK) {
case SII_CMD_PHASE:
if (state->dmaPrevPhase >= 0) {
/* restart DMA after disconnect/reconnect */
if (state->dmaPrevPhase != SII_CMD_PHASE) {
printf("%s: device %d: DMA reselect phase doesn't match\n",
device_xname(sc->sc_dev),
sc->sc_target);
goto abort;
}
state->dmaCurPhase = SII_CMD_PHASE;
state->dmaPrevPhase = -1;
regs->dmaddrl = state->dmaAddrL;
regs->dmaddrh = state->dmaAddrH;
regs->dmlotc = state->dmaCnt;
if (state->dmaCnt & 1)
regs->dmabyte = state->dmaByte;
regs->comm = SII_DMA | SII_INXFER |
(comm & SII_STATE_MSK) | SII_CMD_PHASE;
wbflush();
#ifdef DEBUG
if (sii_debug > 4)
printf("Cmd dcnt %d dadr %x ",
state->dmaCnt,
(state->dmaAddrH << 16) |
state->dmaAddrL);
#endif
} else {
/* send command data */
i = state->cmdlen;
if (i == 0) {
printf("%s: device %d: cmd count exceeded\n",
device_xname(sc->sc_dev),
sc->sc_target);
goto abort;
}
sc->sii_copytobuf((u_short *)state->cmd,
(volatile u_short *)state->dmaAddr[0],
i);
sii_StartDMA(regs, state->dmaCurPhase =
SII_CMD_PHASE, state->dmaAddr[0],
state->dmaCnt = state->dmalen = i);
}
/* wait a short time for XFER complete */
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & (SII_CI | SII_DI), SII_WAIT_COUNT, i);
if (dstat & (SII_CI | SII_DI)) {
#ifdef DEBUG
if (sii_debug > 4)
printf("cnt %d\n", i);
else if (sii_debug > 0)
printf("sii_DoIntr: cmd wait ds %x cnt %d\n",
dstat, i);
#endif
goto again;
}
break;
case SII_DATA_IN_PHASE:
case SII_DATA_OUT_PHASE:
if (state->cmdlen > 0) {
printf("%s: device %d: cmd %x: command data not all sent (%d) 1\n",
device_xname(sc->sc_dev), sc->sc_target,
sc->sc_cmd[sc->sc_target]->cmd[0],
state->cmdlen);
state->cmdlen = 0;
#ifdef DEBUG
sii_DumpLog();
#endif
}
if (state->dmaPrevPhase >= 0) {
/* restart DMA after disconnect/reconnect */
if (state->dmaPrevPhase !=
(dstat & SII_PHASE_MSK)) {
printf("%s: device %d: DMA reselect phase doesn't match\n",
device_xname(sc->sc_dev),
sc->sc_target);
goto abort;
}
state->dmaCurPhase = state->dmaPrevPhase;
state->dmaPrevPhase = -1;
regs->dmaddrl = state->dmaAddrL;
regs->dmaddrh = state->dmaAddrH;
regs->dmlotc = state->dmaCnt;
if (state->dmaCnt & 1)
regs->dmabyte = state->dmaByte;
regs->comm = SII_DMA | SII_INXFER |
(comm & SII_STATE_MSK) |
state->dmaCurPhase;
wbflush();
#ifdef DEBUG
if (sii_debug > 4)
printf("Data %d dcnt %d dadr %x ",
state->dmaDataPhase,
state->dmaCnt,
(state->dmaAddrH << 16) |
state->dmaAddrL);
#endif
break;
}
#ifdef DEBUG
if (sii_debug > 4) {
printf("Data %d ", state->dmaDataPhase);
if (sii_debug > 5)
printf("\n");
}
#endif
i = state->buflen;
if (i == 0) {
printf("%s: device %d: data count exceeded\n",
device_xname(sc->sc_dev), sc->sc_target);
goto abort;
}
if (i > SII_MAX_DMA_XFER_LENGTH)
i = SII_MAX_DMA_XFER_LENGTH;
if ((dstat & SII_PHASE_MSK) == SII_DATA_IN_PHASE) {
sii_StartDMA(regs,
state->dmaCurPhase = SII_DATA_IN_PHASE,
state->dmaAddr[state->dmaBufIndex],
state->dmaCnt = state->dmalen = i);
break;
}
/* start first chunk */
if (state->flags & FIRST_DMA) {
state->flags &= ~FIRST_DMA;
sc->sii_copytobuf((u_short *)state->buf,
(volatile u_short *)
state->dmaAddr[state->dmaBufIndex], i);
}
sii_StartDMA(regs,
state->dmaCurPhase = SII_DATA_OUT_PHASE,
state->dmaAddr[state->dmaBufIndex],
state->dmaCnt = state->dmalen = i);
i = state->buflen - SII_MAX_DMA_XFER_LENGTH;
if (i > 0) {
/* prepare for next chunk */
if (i > SII_MAX_DMA_XFER_LENGTH)
i = SII_MAX_DMA_XFER_LENGTH;
sc->sii_copytobuf((u_short *)(state->buf +
SII_MAX_DMA_XFER_LENGTH),
(volatile u_short *)
state->dmaAddr[!state->dmaBufIndex], i);
}
break;
case SII_STATUS_PHASE:
if (state->cmdlen > 0) {
printf("%s: device %d: cmd %x: command data not all sent (%d) 2\n",
device_xname(sc->sc_dev), sc->sc_target,
sc->sc_cmd[sc->sc_target]->cmd[0],
state->cmdlen);
state->cmdlen = 0;
#ifdef DEBUG
sii_DumpLog();
#endif
}
/* read amount transferred if DMA didn't finish */
if (state->dmalen > 0) {
i = state->dmalen - regs->dmlotc;
state->dmalen = 0;
state->dmaCurPhase = -1;
regs->dmlotc = 0;
regs->comm = comm &
(SII_STATE_MSK | SII_PHASE_MSK);
wbflush();
regs->dstat = SII_DNE;
wbflush();
#ifdef DEBUG
if (sii_debug > 4)
printf("DMA amt %d ", i);
#endif
switch (comm & SII_PHASE_MSK) {
case SII_DATA_IN_PHASE:
/* copy in the data */
sc->sii_copyfrombuf((volatile u_short*)
state->dmaAddr[state->dmaBufIndex],
state->buf, i);
case SII_CMD_PHASE:
case SII_DATA_OUT_PHASE:
state->buflen -= i;
}
}
/* read a one byte status message */
state->statusByte = msg =
sii_GetByte(regs, SII_STATUS_PHASE, 1);
if (msg < 0) {
dstat = regs->dstat;
goto again;
}
#ifdef DEBUG
if (sii_debug > 4)
printf("Status %x ", msg);
if (sii_logp > sii_log)
sii_logp[-1].msg = msg;
else
sii_log[NLOG - 1].msg = msg;
#endif
/* do a quick wait for COMMAND_COMPLETE */
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & (SII_CI | SII_DI), SII_WAIT_COUNT, i);
if (dstat & (SII_CI | SII_DI)) {
#ifdef DEBUG
if (sii_debug > 4)
printf("cnt2 %d\n", i);
#endif
goto again;
}
break;
case SII_MSG_IN_PHASE:
/*
* Save DMA state if DMA didn't finish.
* Be careful not to save state again after reconnect
* and see RESTORE_POINTER message.
* Note that the SII DMA address is not incremented
* as DMA proceeds.
*/
if (state->dmaCurPhase >= 0) {
/* save DMA registers */
state->dmaPrevPhase = state->dmaCurPhase;
state->dmaCurPhase = -1;
if (dstat & SII_OBB)
state->dmaByte = regs->dmabyte;
i = regs->dmlotc;
if (i != 0)
i = state->dmaCnt - i;
/* note: no carry from dmaddrl to dmaddrh */
state->dmaAddrL = regs->dmaddrl + i;
state->dmaAddrH = regs->dmaddrh;
state->dmaCnt = regs->dmlotc;
if (state->dmaCnt == 0)
state->dmaCnt = SII_MAX_DMA_XFER_LENGTH;
regs->comm = comm &
(SII_STATE_MSK | SII_PHASE_MSK);
wbflush();
regs->dstat = SII_DNE;
wbflush();
#ifdef DEBUG
if (sii_debug > 4) {
printf("SavP dcnt %d dadr %x ",
state->dmaCnt,
(state->dmaAddrH << 16) |
state->dmaAddrL);
if (((dstat & SII_OBB) != 0) ^
(state->dmaCnt & 1))
printf("OBB??? ");
} else if (sii_debug > 0) {
if (((dstat & SII_OBB) != 0) ^
(state->dmaCnt & 1)) {
printf("sii_DoIntr: OBB??? ds %x cnt %d\n",
dstat, state->dmaCnt);
sii_DumpLog();
}
}
#endif
}
/* read a one byte message */
msg = sii_GetByte(regs, SII_MSG_IN_PHASE, 0);
if (msg < 0) {
dstat = regs->dstat;
goto again;
}
#ifdef DEBUG
if (sii_debug > 4)
printf("MsgIn %x ", msg);
if (sii_logp > sii_log)
sii_logp[-1].msg = msg;
else
sii_log[NLOG - 1].msg = msg;
#endif
/* process message */
switch (msg) {
case MSG_CMDCOMPLETE:
/* acknowledge last byte */
regs->comm = SII_INXFER | SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE, SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
msg = sc->sc_target;
sc->sc_target = -1;
/*
* Wait a short time for disconnect.
* Don't be fooled if SII_BER happens first.
* Note: a reselect may happen here.
*/
SII_WAIT_UNTIL(cstat, regs->cstat,
cstat & (SII_RST | SII_SCH),
SII_WAIT_COUNT, i);
if ((cstat & (SII_RST | SII_SCH |
SII_STATE_MSK)) == SII_SCH) {
regs->cstat = SII_SCH | SII_BER;
regs->comm = 0;
wbflush();
/*
* Double check that we didn't miss a
* state change between seeing it and
* clearing the SII_SCH bit.
*/
i = regs->cstat;
if (!(i & SII_SCH) &&
(i & SII_STATE_MSK) !=
(cstat & SII_STATE_MSK))
sii_StateChg(sc, i);
}
#ifdef DEBUG
if (sii_debug > 4)
printf("cs %x\n", cstat);
#endif
sii_CmdDone(sc, msg, 0);
break;
case MSG_EXTENDED:
/* acknowledge last byte */
regs->comm = SII_INXFER | SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE, SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
/* read the message length */
msg = sii_GetByte(regs, SII_MSG_IN_PHASE, 1);
if (msg < 0) {
dstat = regs->dstat;
goto again;
}
sii_buf[1] = msg; /* message length */
if (msg == 0)
msg = 256;
/*
* We read and acknowledge all the bytes
* except the last so we can assert ATN
* if needed before acknowledging the last.
*/
for (i = 0; i < msg; i++) {
dstat = sii_GetByte(regs,
SII_MSG_IN_PHASE, i < msg - 1);
if ((int)dstat < 0) {
dstat = regs->dstat;
goto again;
}
sii_buf[i + 2] = dstat;
}
switch (sii_buf[2]) {
case MSG_EXT_MODIFY_DATA_PTR:
/* acknowledge last byte */
regs->comm = SII_INXFER |
SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE,
SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
i = (sii_buf[3] << 24) |
(sii_buf[4] << 16) |
(sii_buf[5] << 8) |
sii_buf[6];
if (state->dmaPrevPhase >= 0) {
state->dmaAddrL += i;
state->dmaCnt -= i;
}
break;
case MSG_EXT_SDTR_LEN:
/*
* Acknowledge last byte and
* signal a request for MSG_OUT.
*/
regs->comm = SII_INXFER | SII_ATN |
SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE,
SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
sii_DoSync(regs, state);
break;
default:
reject:
/*
* Acknowledge last byte and
* signal a request for MSG_OUT.
*/
regs->comm = SII_INXFER | SII_ATN |
SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE,
SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
/* wait for MSG_OUT phase */
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_TBE,
SII_WAIT_COUNT, i);
/* send a reject message */
regs->data = MSG_MESSAGE_REJECT;
regs->comm = SII_INXFER |
(regs->cstat & SII_STATE_MSK) |
SII_MSG_OUT_PHASE;
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE,
SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
}
break;
case MSG_SAVEDATAPOINTER:
case MSG_RESTOREPOINTERS:
/* acknowledge last byte */
regs->comm = SII_INXFER | SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE, SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
/* wait a short time for another msg */
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & (SII_CI | SII_DI),
SII_WAIT_COUNT, i);
if (dstat & (SII_CI | SII_DI)) {
#ifdef DEBUG
if (sii_debug > 4)
printf("cnt %d\n", i);
#endif
goto again;
}
break;
case MSG_DISCONNECT:
/* acknowledge last byte */
regs->comm = SII_INXFER | SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE, SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
state->prevComm = comm;
#ifdef DEBUG
if (sii_debug > 4)
printf("disconn %d ", sc->sc_target);
#endif
/*
* Wait a short time for disconnect.
* Don't be fooled if SII_BER happens first.
* Note: a reselect may happen here.
*/
SII_WAIT_UNTIL(cstat, regs->cstat,
cstat & (SII_RST | SII_SCH),
SII_WAIT_COUNT, i);
if ((cstat & (SII_RST | SII_SCH |
SII_STATE_MSK)) != SII_SCH) {
#ifdef DEBUG
if (sii_debug > 4)
printf("cnt %d\n", i);
#endif
dstat = regs->dstat;
goto again;
}
regs->cstat = SII_SCH | SII_BER;
regs->comm = 0;
wbflush();
sc->sc_target = -1;
/*
* Double check that we didn't miss a state
* change between seeing it and clearing
* the SII_SCH bit.
*/
i = regs->cstat;
if (!(i & SII_SCH) && (i & SII_STATE_MSK) !=
(cstat & SII_STATE_MSK))
sii_StateChg(sc, i);
break;
case MSG_MESSAGE_REJECT:
/* acknowledge last byte */
regs->comm = SII_INXFER | SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE, SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
printf("%s: device %d: message reject.\n",
device_xname(sc->sc_dev), sc->sc_target);
break;
default:
if (!(msg & MSG_IDENTIFYFLAG)) {
printf("%s: device %d: couldn't handle "
"message 0x%x... rejecting.\n",
device_xname(sc->sc_dev),
sc->sc_target,
msg);
#ifdef DEBUG
sii_DumpLog();
#endif
goto reject;
}
/* acknowledge last byte */
regs->comm = SII_INXFER | SII_MSG_IN_PHASE |
(comm & SII_STATE_MSK);
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & SII_DNE, SII_WAIT_COUNT, i);
regs->dstat = SII_DNE;
wbflush();
/* may want to check LUN some day */
/* wait a short time for another msg */
SII_WAIT_UNTIL(dstat, regs->dstat,
dstat & (SII_CI | SII_DI),
SII_WAIT_COUNT, i);
if (dstat & (SII_CI | SII_DI)) {
#ifdef DEBUG
if (sii_debug > 4)
printf("cnt %d\n", i);
#endif
goto again;
}
}
break;
case SII_MSG_OUT_PHASE:
#ifdef DEBUG
if (sii_debug > 4)
printf("MsgOut\n");
#endif
printf("MsgOut %x\n", state->flags); /* XXX */
/*
* Check for parity error.
* Hardware will automatically set ATN
* to request the device for a MSG_OUT phase.
*/
if (state->flags & PARITY_ERR) {
state->flags &= ~PARITY_ERR;
regs->data = MSG_PARITY_ERROR;
} else
regs->data = MSG_NOOP;
regs->comm = SII_INXFER | (comm & SII_STATE_MSK) |
SII_MSG_OUT_PHASE;
wbflush();
/* wait a short time for XFER complete */
SII_WAIT_UNTIL(dstat, regs->dstat, dstat & SII_DNE,
SII_WAIT_COUNT, i);
#ifdef DEBUG
if (sii_debug > 4)
printf("ds %x i %d\n", dstat, i);
#endif
/* just clear the DNE bit and check errors later */
if (dstat & SII_DNE) {
regs->dstat = SII_DNE;
wbflush();
}
break;
default:
printf("%s: Couldn't handle phase %d... ignoring.\n",
device_xname(sc->sc_dev), dstat & SII_PHASE_MSK);
}
}
#ifdef DEBUG
if (sii_debug > 3)
printf("\n");
#endif
/*
* Check to make sure we won't be interrupted again.
* Deglitch dstat register.
*/
msg = regs->dstat;
while (msg != (dstat = regs->dstat))
msg = dstat;
if (dstat & (SII_CI | SII_DI))
goto again;
if (sc->sc_target < 0) {
/* look for another device that is ready */
for (i = 0; i < SII_NCMD; i++) {
/* don't restart a disconnected command */
if (!sc->sc_cmd[i] || sc->sc_st[i].prevComm)
continue;
sii_StartCmd(sc, i);
break;
}
}
return;
abort:
/* jump here to abort the current command */
printf("%s: device %d: current command terminated\n",
device_xname(sc->sc_dev), sc->sc_target);
#ifdef DEBUG
sii_DumpLog();
#endif
if ((cstat = regs->cstat) & SII_CON) {
/* try to send an abort msg for awhile */
regs->dstat = SII_DNE;
regs->data = MSG_ABORT;
regs->comm = SII_INXFER | SII_ATN | (cstat & SII_STATE_MSK) |
SII_MSG_OUT_PHASE;
wbflush();
SII_WAIT_UNTIL(dstat, regs->dstat,
(dstat & (SII_DNE | SII_PHASE_MSK)) ==
(SII_DNE | SII_MSG_OUT_PHASE),
2 * SII_WAIT_COUNT, i);
#ifdef DEBUG
if (sii_debug > 0)
printf("Abort: cs %x ds %x i %d\n", cstat, dstat, i);
#endif
if ((dstat & (SII_DNE | SII_PHASE_MSK)) ==
(SII_DNE | SII_MSG_OUT_PHASE)) {
/* disconnect if command in progress */
regs->comm = SII_DISCON;
wbflush();
SII_WAIT_UNTIL(cstat, regs->cstat,
!(cstat & SII_CON), SII_WAIT_COUNT, i);
}
} else {
#ifdef DEBUG
if (sii_debug > 0)
printf("Abort: cs %x\n", cstat);
#endif
}
regs->cstat = 0xffff;
regs->dstat = 0xffff;
regs->comm = 0;
wbflush();
i = sc->sc_target;
sc->sc_target = -1;
sii_CmdDone(sc, i, EIO);
#ifdef DEBUG
if (sii_debug > 4)
printf("sii_DoIntr: after CmdDone target %d\n", sc->sc_target);
#endif
}
static void
sii_StateChg(struct siisoftc *sc, u_int cstat)
{
SIIRegs *regs = sc->sc_regs;
State *state;
int i;
#ifdef DEBUG
if (sii_debug > 4)
printf("SCH: ");
#endif
switch (cstat & SII_STATE_MSK) {
case 0:
/* disconnect */
i = sc->sc_target;
sc->sc_target = -1;
#ifdef DEBUG
if (sii_debug > 4)
printf("disconn %d ", i);
#endif
if (i >= 0 && !sc->sc_st[i].prevComm) {
printf("%s: device %d: spurrious disconnect (%d)\n",
device_xname(sc->sc_dev), i, regs->slcsr);
sc->sc_st[i].prevComm = 0;
}
break;
case SII_CON:
/* connected as initiator */
i = regs->slcsr;
if (sc->sc_target == i)
break;
printf("%s: device %d: connect to device %d??\n",
device_xname(sc->sc_dev), sc->sc_target, i);
sc->sc_target = i;
break;
case SII_DST:
/*
* Wait for CON to become valid,
* chip is slow sometimes.
*/
SII_WAIT_UNTIL(cstat, regs->cstat,
cstat & SII_CON, SII_WAIT_COUNT, i);
if (!(cstat & SII_CON))
panic("sii resel");
/* FALLTHROUGH */
case SII_CON | SII_DST:
/*
* Its a reselection. Save the ID and wait for
* interrupts to tell us what to do next
* (should be MSG_IN of IDENTIFY).
* NOTE: sc_target may be >= 0 if we were in
* the process of trying to start a command
* and were reselected before the select
* command finished.
*/
sc->sc_target = i = regs->destat;
state = &sc->sc_st[i];
regs->comm = SII_CON | SII_DST | SII_MSG_IN_PHASE;
regs->dmctrl = state->dmaReqAck;
wbflush();
if (!state->prevComm) {
printf("%s: device %d: spurious reselection\n",
device_xname(sc->sc_dev), i);
break;
}
state->prevComm = 0;
#ifdef DEBUG
if (sii_debug > 4)
printf("resel %d ", sc->sc_target);
#endif
break;
#ifdef notyet
case SII_DST | SII_TGT:
case SII_CON | SII_DST | SII_TGT:
/* connected as target */
printf("%s: Selected by device %d as target!!\n",
device_xname(sc->sc_dev), regs->destat);
regs->comm = SII_DISCON;
wbflush();
SII_WAIT_UNTIL(!(regs->cstat & SII_CON),
SII_WAIT_COUNT, i);
regs->cstat = 0xffff;
regs->dstat = 0xffff;
regs->comm = 0;
break;
#endif
default:
printf("%s: Unknown state change (cs %x)!!\n",
device_xname(sc->sc_dev), cstat);
#ifdef DEBUG
sii_DumpLog();
#endif
}
}
/*
* Read one byte of data.
* If 'ack' is true, acknowledge the byte.
*/
static int
sii_GetByte(SIIRegs *regs, int phase, int ack)
{
u_int dstat;
u_int state;
int i;
int data;
dstat = regs->dstat;
state = regs->cstat & SII_STATE_MSK;
i = -1;
if (!(dstat & SII_IBF) || (dstat & SII_MIS)) {
regs->comm = state | phase;
wbflush();
/* wait a short time for IBF */
SII_WAIT_UNTIL(dstat, regs->dstat, dstat & SII_IBF,
SII_WAIT_COUNT, i);
#ifdef DEBUG
if (!(dstat & SII_IBF))
printf("status no IBF\n");
#endif
}
if (dstat & SII_DNE) { /* XXX */
printf("sii_GetByte: DNE set 5\n");
#ifdef DEBUG
sii_DumpLog();
#endif
regs->dstat = SII_DNE;
}
data = regs->data;
/* check for parity error */
if (dstat & SII_IPE) {
#ifdef DEBUG
if (sii_debug > 4)
printf("cnt0 %d\n", i);
#endif
printf("sii_GetByte: data %x ?? ds %x cm %x i %d\n",
data, dstat, regs->comm, i); /* XXX */
data = -1;
ack = 1;
}
if (ack) {
regs->comm = SII_INXFER | state | phase;
wbflush();
/* wait a short time for XFER complete */
SII_WAIT_UNTIL(dstat, regs->dstat, dstat & SII_DNE,
SII_WAIT_COUNT, i);
/* clear the DNE */
if (dstat & SII_DNE) {
regs->dstat = SII_DNE;
wbflush();
}
}
return (data);
}
/*
* Exchange messages to initiate synchronous data transfers.
*/
static void
sii_DoSync(SIIRegs *regs, State *state)
{
u_int dstat, comm;
int i, j;
u_int len;
#ifdef DEBUG
if (sii_debug)
printf("sii_DoSync: len %d per %d req/ack %d\n",
sii_buf[1], sii_buf[3], sii_buf[4]);
#endif
/* SII chip can only handle a minimum transfer period of ??? */
if (sii_buf[3] < 64)
sii_buf[3] = 64;
/* SII chip can only handle a maximum REQ/ACK offset of 3 */
len = sii_buf[4];
if (len > 3)
len = 3;
sii_buf[0] = MSG_EXTENDED;
sii_buf[1] = MSG_EXT_SDTR_LEN;
sii_buf[2] = MSG_EXT_SDTR;
sii_buf[4] = len;
#if 1
comm = SII_INXFER | SII_ATN | SII_MSG_OUT_PHASE |
(regs->cstat & SII_STATE_MSK);
regs->comm = comm & ~SII_INXFER;
for (j = 0; j < 5; j++) {
/* wait for target to request the next byte */
SII_WAIT_UNTIL(dstat, regs->dstat, dstat & SII_TBE,
SII_WAIT_COUNT, i);
if (!(dstat & SII_TBE) ||
(dstat & SII_PHASE_MSK) != SII_MSG_OUT_PHASE) {
printf("sii_DoSync: TBE? ds %x cm %x i %d\n",
dstat, comm, i); /* XXX */
return;
}
/* the last message byte should have ATN off */
if (j == 4)
comm &= ~SII_ATN;
regs->data = sii_buf[j];
regs->comm = comm;
wbflush();
/* wait a short time for XFER complete */
SII_WAIT_UNTIL(dstat, regs->dstat, dstat & SII_DNE,
SII_WAIT_COUNT, i);
if (!(dstat & SII_DNE)) {
printf("sii_DoSync: DNE? ds %x cm %x i %d\n",
dstat, comm, i); /* XXX */
return;
}
/* clear the DNE, other errors handled later */
regs->dstat = SII_DNE;
wbflush();
}
#else /* 0 */
sc->sii_copytobuf((u_short *)sii_buf,
(volatile u_short *)SII_BUF_ADDR(sc), 5);
printf("sii_DoSync: %x %x %x ds %x\n",
((volatile u_short *)SII_BUF_ADDR(sc))[0],
((volatile u_short *)SII_BUF_ADDR(sc))[2],
((volatile u_short *)SII_BUF_ADDR(sc))[4],
regs->dstat); /* XXX */
regs->dmaddrl = (u_short)(SII_BUF_ADDR(sc) >> 1);
regs->dmaddrh = (u_short)(SII_BUF_ADDR(sc) >> 17) & 03;
regs->dmlotc = 5;
regs->comm = SII_DMA | SII_INXFER | SII_ATN |
(regs->cstat & SII_STATE_MSK) | SII_MSG_OUT_PHASE;
wbflush();
/* wait a short time for XFER complete */
SII_WAIT_UNTIL(dstat, regs->dstat,
(dstat & (SII_DNE | SII_TCZ)) == (SII_DNE | SII_TCZ),
SII_WAIT_COUNT, i);
if ((dstat & (SII_DNE | SII_TCZ)) != (SII_DNE | SII_TCZ)) {
printf("sii_DoSync: ds %x cm %x i %d lotc %d\n",
dstat, regs->comm, i, regs->dmlotc); /* XXX */
sii_DumpLog(); /* XXX */
return;
}
/* clear the DNE, other errors handled later */
regs->dstat = SII_DNE;
wbflush();
#endif /* 0 */
#if 0
SII_WAIT_UNTIL(dstat, regs->dstat, dstat & (SII_CI | SII_DI),
SII_WAIT_COUNT, i);
printf("sii_DoSync: ds %x cm %x i %d lotc %d\n",
dstat, regs->comm, i, regs->dmlotc); /* XXX */
#endif
state->dmaReqAck = len;
}
/*
* Issue the sequence of commands to the controller to start DMA.
* NOTE: the data buffer should be word-aligned for DMA out.
*/
static void
sii_StartDMA(SIIRegs *regs, int phase, u_short *dmaAddr, int size)
/* regs: which SII to use */
/* phase: phase to send/receive data */
/* dmaAddr: DMA buffer address */
/* size: # of bytes to transfer */
{
if (regs->dstat & SII_DNE) { /* XXX */
regs->dstat = SII_DNE;
printf("sii_StartDMA: DNE set\n");
#ifdef DEBUG
sii_DumpLog();
#endif
}
regs->dmaddrl = ((u_long)dmaAddr >> 1);
regs->dmaddrh = ((u_long)dmaAddr >> 17) & 03;
regs->dmlotc = size;
regs->comm = SII_DMA | SII_INXFER | (regs->cstat & SII_STATE_MSK) |
phase;
wbflush();
#ifdef DEBUG
if (sii_debug > 5) {
printf("sii_StartDMA: cs 0x%x, ds 0x%x, cm 0x%x, size %d\n",
regs->cstat, regs->dstat, regs->comm, size);
}
#endif
}
/*
* Call the device driver's 'done' routine to let it know the command is done.
* The 'done' routine may try to start another command.
* To be fair, we should start pending commands for other devices
* before allowing the same device to start another command.
*/
static void
sii_CmdDone(struct siisoftc *sc, int target, int error)
/* sc: which SII to use */
/* target: which device is done */
/* error: error code if any errors */
{
int i;
ScsiCmd *scsicmd __unused = sc->sc_cmd[target];
KASSERTMSG(target >= 0 && scsicmd, "sii_CmdDone");
sc->sc_cmd[target] = (ScsiCmd *)0;
#ifdef DEBUG
if (sii_debug > 1) {
printf("sii_CmdDone: %s target %d cmd %x err %d resid %d\n",
device_xname(sc->sc_dev),
target, scsicmd->cmd[0], error, sc->sc_st[target].buflen);
}
#endif
/* look for another device that is ready */
for (i = 0; i < SII_NCMD; i++) {
/* don't restart a disconnected command */
if (!sc->sc_cmd[i] || sc->sc_st[i].prevComm)
continue;
sii_StartCmd(sc, i);
break;
}
sc->sc_xs[target]->status = sc->sc_st[target].statusByte;
/*
* Convert SII driver error code to MI SCSI XS_*.
*/
switch (error) {
case 0:
sc->sc_xs[target]->error = XS_NOERROR;
break;
case ENXIO:
sc->sc_xs[target]->error = XS_SELTIMEOUT;
break;
case EBUSY:
sc->sc_xs[target]->error = XS_BUSY;
break;
case EIO:
sc->sc_xs[target]->error = XS_DRIVER_STUFFUP;
break;
default:
sc->sc_xs[target]->error = XS_DRIVER_STUFFUP;
}
sc->sc_xs[target]->resid = sc->sc_st[target].buflen;
scsipi_done(sc->sc_xs[target]);
}
#ifdef DEBUG
static void
sii_DumpLog(void)
{
struct sii_log *lp;
printf("sii: cmd %x bn %d cnt %d\n", sii_debug_cmd, sii_debug_bn,
sii_debug_sz);
lp = sii_logp;
do {
printf("target %d cs %x ds %x cm %x msg %x rlen %x dlen %x\n",
lp->target, lp->cstat, lp->dstat, lp->comm, lp->msg,
lp->rlen, lp->dlen);
if (++lp >= &sii_log[NLOG])
lp = sii_log;
} while (lp != sii_logp);
}
#endif