root/sys/arch/pmax/ibus/sii.c
/*      $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