1852 lines
48 KiB
C
1852 lines
48 KiB
C
/* $NetBSD: sii.c,v 1.8 1995/09/13 19:35:58 jonathan 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. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* @(#)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 "sii.h"
|
|
#if NSII > 0
|
|
/*
|
|
* SCSI interface driver
|
|
*/
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/dkstat.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/device.h>
|
|
|
|
#ifdef notyet
|
|
#include <scsi/scsi_all.h>
|
|
#include <scsi/scsiconf.h>
|
|
#endif
|
|
|
|
#include <machine/autoconf.h>
|
|
#include <machine/machConst.h>
|
|
#include <pmax/dev/device.h>
|
|
#include <pmax/dev/scsi.h>
|
|
#include <pmax/dev/siireg.h>
|
|
|
|
#include <pmax/pmax/kn01.h>
|
|
|
|
typedef struct scsi_state {
|
|
int statusByte; /* status byte returned during STATUS_PHASE */
|
|
int dmaDataPhase; /* which data phase to expect */
|
|
int dmaCurPhase; /* SCSI phase if DMA is in progress */
|
|
int dmaPrevPhase; /* SCSI phase of DMA suspended by disconnect */
|
|
u_short *dmaAddr[2]; /* DMA buffer memory address */
|
|
int dmaBufIndex; /* which of the above is currently in use */
|
|
int dmalen; /* amount to transfer in this chunk */
|
|
int cmdlen; /* total remaining amount of cmd to transfer */
|
|
u_char *cmd; /* current pointer within scsicmd->cmd */
|
|
int buflen; /* total remaining amount of data to transfer */
|
|
char *buf; /* current pointer within scsicmd->buf */
|
|
u_short flags; /* see below */
|
|
u_short prevComm; /* command reg before disconnect */
|
|
u_short dmaCtrl; /* DMA control register if disconnect */
|
|
u_short dmaAddrL; /* DMA address register if disconnect */
|
|
u_short dmaAddrH; /* DMA address register if disconnect */
|
|
u_short dmaCnt; /* DMA count if disconnect */
|
|
u_short dmaByte; /* DMA byte if disconnect on odd boundary */
|
|
u_short dmaReqAck; /* DMA synchronous xfer offset or 0 if async */
|
|
} State;
|
|
|
|
/* state flags */
|
|
#define FIRST_DMA 0x01 /* true if no data DMA started yet */
|
|
#define PARITY_ERR 0x02 /* true if parity error seen */
|
|
|
|
#define SII_NCMD 7
|
|
struct siisoftc {
|
|
struct device sc_dev; /* us as a device */
|
|
SIIRegs *sc_regs; /* HW address of SII controller chip */
|
|
int sc_flags;
|
|
int sc_target; /* target SCSI ID if connected */
|
|
ScsiCmd *sc_cmd[SII_NCMD]; /* active command indexed by ID */
|
|
State sc_st[SII_NCMD]; /* state info for each active command */
|
|
#ifdef NEW_SCSI
|
|
struct scsi_link sc_link; /* scsi lint struct */
|
|
#endif
|
|
};
|
|
|
|
/*
|
|
* Device definition for autoconfiguration.
|
|
*
|
|
*/
|
|
int siimatch __P((struct device * parent, void *cfdata, void *aux));
|
|
void siiattach __P((struct device *parent, struct device *self, void *aux));
|
|
int siiprint(void*, char*);
|
|
|
|
int sii_doprobe __P((void *addr, int unit, int flags, int pri,
|
|
struct device *self));
|
|
int siiintr __P((void *sc));
|
|
|
|
extern struct cfdriver siicd;
|
|
struct cfdriver siicd = {
|
|
NULL, "sii", siimatch, siiattach, DV_DULL, sizeof(struct siisoftc)
|
|
};
|
|
|
|
#ifdef USE_NEW_SCSI
|
|
/* Glue to the machine-independent scsi */
|
|
struct scsi_adapter asc_switch = {
|
|
NULL, /* XXX - asc_scsi_cmd */
|
|
#if 0
|
|
/*XXX*/ minphys, /* no max transfer size; DMA engine deals */
|
|
#else
|
|
SII_MAX_DMA_XFER_LENGTH,
|
|
#endif
|
|
NULL,
|
|
NULL,
|
|
};
|
|
|
|
struct scsi_device sii_dev = {
|
|
/*XXX*/ NULL, /* Use default error handler */
|
|
/*XXX*/ NULL, /* have a queue, served by this */
|
|
/*XXX*/ NULL, /* have no async handler */
|
|
/*XXX*/ NULL, /* Use default 'done' routine */
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* Definition of the controller for the old auto-configuration program.
|
|
*/
|
|
void siistart();
|
|
struct pmax_driver siidriver = {
|
|
"sii", NULL, siistart, 0,
|
|
};
|
|
|
|
/*
|
|
* 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) { \
|
|
register 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
|
|
|
|
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 */
|
|
#define SII_BUF_ADDR (MACH_PHYS_TO_UNCACHED(KN01_SYS_SII_B_START) \
|
|
+ SII_MAX_DMA_XFER_LENGTH * 14)
|
|
|
|
static void sii_Reset();
|
|
static void sii_StartCmd();
|
|
static void sii_CmdDone();
|
|
static void sii_DoIntr();
|
|
static void sii_StateChg();
|
|
static void sii_DoSync();
|
|
static void sii_StartDMA();
|
|
static int sii_GetByte();
|
|
|
|
|
|
/*
|
|
* Match driver based on name
|
|
*/
|
|
int
|
|
siimatch(parent, match, aux)
|
|
struct device *parent;
|
|
void *match;
|
|
void *aux;
|
|
{
|
|
struct cfdata *cf = match;
|
|
struct confargs *ca = aux;
|
|
|
|
if (!BUS_MATCHNAME(ca, "sii") && !BUS_MATCHNAME(ca, "PMAZ-AA "))
|
|
return (0);
|
|
|
|
/* XXX check for bad address */
|
|
/* XXX kn01s have exactly one SII. Does any other machine use them? */
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
siiattach(parent, self, aux)
|
|
struct device *parent;
|
|
struct device *self;
|
|
void *aux;
|
|
{
|
|
register struct confargs *ca = aux;
|
|
register struct siisoftc *sc = (struct siisoftc *) self;
|
|
register void *siiaddr;
|
|
register int i;
|
|
|
|
siiaddr = (void*)MACH_PHYS_TO_UNCACHED(BUS_CVTADDR(ca));
|
|
|
|
sc->sc_regs = (SIIRegs *)siiaddr;
|
|
sc->sc_flags = sc->sc_dev.dv_cfdata->cf_flags;
|
|
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 *)
|
|
MACH_PHYS_TO_UNCACHED(KN01_SYS_SII_B_START) +
|
|
2 * SII_MAX_DMA_XFER_LENGTH * i;
|
|
sc->sc_st[i].dmaAddr[1] = sc->sc_st[i].dmaAddr[0] +
|
|
SII_MAX_DMA_XFER_LENGTH;
|
|
}
|
|
|
|
/* Hack for old-sytle SCSI-device probe */
|
|
(void) pmax_add_scsi(&siidriver, sc->sc_dev.dv_unit);
|
|
|
|
sii_Reset(sc, RESET);
|
|
|
|
/*priority = ca->ca_slot;*/
|
|
/* tie pseudo-slot to device */
|
|
BUS_INTR_ESTABLISH(ca, siiintr, sc);
|
|
printf("\n");
|
|
}
|
|
|
|
/*
|
|
* Start activity on a SCSI device.
|
|
* We maintain information on each device separately since devices can
|
|
* connect/disconnect during an operation.
|
|
*/
|
|
void
|
|
siistart(scsicmd)
|
|
register ScsiCmd *scsicmd; /* command to start */
|
|
{
|
|
register struct pmax_scsi_device *sdp = scsicmd->sd;
|
|
register struct siisoftc *sc = siicd.cd_devs[sdp->sd_ctlr];
|
|
int s;
|
|
|
|
s = splbio();
|
|
/*
|
|
* Check if another command is already in progress.
|
|
* We may have to change this if we allow SCSI devices with
|
|
* separate LUNs.
|
|
*/
|
|
if (sc->sc_cmd[sdp->sd_drive]) {
|
|
printf("%s: device %s busy at start\n", sc->sc_dev.dv_xname,
|
|
sdp->sd_driver->d_name);
|
|
(*sdp->sd_driver->d_done)(scsicmd->unit, EBUSY,
|
|
scsicmd->buflen, 0);
|
|
splx(s);
|
|
}
|
|
sc->sc_cmd[sdp->sd_drive] = scsicmd;
|
|
sii_StartCmd(sc, sdp->sd_drive);
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Check to see if any SII chips have pending interrupts
|
|
* and process as appropriate.
|
|
*/
|
|
int
|
|
siiintr(xxxsc)
|
|
void *xxxsc;
|
|
{
|
|
register 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);
|
|
}
|
|
|
|
/*
|
|
* 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(sc, reset)
|
|
register struct siisoftc* sc;
|
|
int reset; /* TRUE => reset SCSI bus */
|
|
{
|
|
register 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;
|
|
/*
|
|
* SII is always ID 7.
|
|
*/
|
|
regs->id = SII_ID_IO | 7;
|
|
/*
|
|
* Enable SII to drive the SCSI bus.
|
|
*/
|
|
regs->dictrl = SII_PRE;
|
|
regs->dmctrl = 0;
|
|
|
|
if (reset) {
|
|
register 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;
|
|
MachEmptyWriteBuffer();
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
}
|
|
|
|
/*
|
|
* Start a SCSI command by sending the cmd data
|
|
* to a SCSI controller via the SII.
|
|
* Call the device done proceedure if it can't be started.
|
|
* NOTE: we should be called with interrupts disabled.
|
|
*/
|
|
static void
|
|
sii_StartCmd(sc, target)
|
|
register struct siisoftc *sc; /* which SII to use */
|
|
register int target; /* which command to start */
|
|
{
|
|
register SIIRegs *regs;
|
|
register ScsiCmd *scsicmd;
|
|
register State *state;
|
|
register 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->dmaDataPhase =
|
|
(scsicmd->flags & SCSICMD_DATA_TO_DEVICE) ?
|
|
SII_DATA_OUT_PHASE : SII_DATA_IN_PHASE;
|
|
state->buf = scsicmd->buf;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (sii_debug > 1) {
|
|
printf("sii_StartCmd: %s target %d cmd 0x%x addr %x size %d dma %d\n",
|
|
scsicmd->sd->sd_driver->d_name, target,
|
|
scsicmd->cmd[0], scsicmd->buf, scsicmd->buflen,
|
|
state->dmaDataPhase);
|
|
}
|
|
sii_debug_cmd = scsicmd->cmd[0];
|
|
if (scsicmd->cmd[0] == SCSI_READ_EXT ||
|
|
scsicmd->cmd[0] == SCSI_WRITE_EXT) {
|
|
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] = SCSI_DIS_REC_IDENTIFY;
|
|
sii_buf[1] = SCSI_EXTENDED_MSG;
|
|
sii_buf[2] = 3; /* message length */
|
|
sii_buf[3] = SCSI_SYNCHRONOUS_XFER;
|
|
sii_buf[4] = 0;
|
|
sii_buf[5] = 3; /* maximum SII chip supports */
|
|
|
|
state->dmaCurPhase = SII_MSG_OUT_PHASE,
|
|
state->dmalen = 6;
|
|
CopyToBuffer((u_short *)sii_buf,
|
|
(volatile u_short *)SII_BUF_ADDR, 6);
|
|
regs->slcsr = target;
|
|
regs->dmctrl = state->dmaReqAck;
|
|
regs->dmaddrl = (u_short)(SII_BUF_ADDR >> 1);
|
|
regs->dmaddrh = (u_short)(SII_BUF_ADDR >> 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 = SCSI_DIS_REC_IDENTIFY;
|
|
regs->slcsr = target;
|
|
regs->dmctrl = state->dmaReqAck;
|
|
regs->comm = SII_INXFER | SII_SELECT | SII_ATN | SII_CON |
|
|
SII_MSG_OUT_PHASE;
|
|
}
|
|
MachEmptyWriteBuffer();
|
|
|
|
/*
|
|
* 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;
|
|
MachEmptyWriteBuffer();
|
|
|
|
#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 */
|
|
CopyToBuffer((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;
|
|
MachEmptyWriteBuffer();
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
sii_CmdDone(sc, target, error);
|
|
}
|
|
|
|
/*
|
|
* Process interrupt conditions.
|
|
*/
|
|
static void
|
|
sii_DoIntr(sc, dstat)
|
|
register struct siisoftc *sc;
|
|
register u_int dstat;
|
|
{
|
|
register SIIRegs *regs = sc->sc_regs;
|
|
register State *state;
|
|
register 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 */
|
|
MachEmptyWriteBuffer();
|
|
|
|
if (dstat & SII_CI) {
|
|
/* deglitch cstat register */
|
|
msg = regs->cstat;
|
|
while (msg != (cstat = regs->cstat))
|
|
msg = cstat;
|
|
regs->cstat = cstat; /* acknowledge everything */
|
|
MachEmptyWriteBuffer();
|
|
#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", sc->sc_dev.dv_xname);
|
|
/* 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) {
|
|
}
|
|
#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",
|
|
sc->sc_dev.dv_xname, sc->sc_target,
|
|
regs->slcsr, regs->destat,
|
|
cstat); /* XXX */
|
|
if (cstat & SII_DST) {
|
|
sc->sc_target = regs->destat;
|
|
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", sc->sc_dev.dv_xname);
|
|
goto abort;
|
|
}
|
|
/* dmalen = amount left to transfer, i = amount transfered */
|
|
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",
|
|
sc->sc_dev.dv_xname, 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->dmalen = len);
|
|
dstat &= ~(SII_IBF | SII_TBE);
|
|
}
|
|
/* copy in the data */
|
|
CopyFromBuffer((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->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;
|
|
CopyToBuffer((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->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",
|
|
sc->sc_dev.dv_xname, sc->sc_target,
|
|
regs->slcsr, regs->destat,
|
|
cstat, dstat); /* XXX */
|
|
if (cstat & SII_DST) {
|
|
sc->sc_target = regs->destat;
|
|
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",
|
|
sc->sc_dev.dv_xname, 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;
|
|
MachEmptyWriteBuffer();
|
|
#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",
|
|
sc->sc_dev.dv_xname, sc->sc_target);
|
|
goto abort;
|
|
}
|
|
CopyToBuffer((u_short *)state->cmd,
|
|
(volatile u_short *)state->dmaAddr[0],
|
|
i);
|
|
sii_StartDMA(regs, state->dmaCurPhase =
|
|
SII_CMD_PHASE, state->dmaAddr[0],
|
|
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",
|
|
sc->sc_dev.dv_xname, 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",
|
|
sc->sc_dev.dv_xname, 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;
|
|
MachEmptyWriteBuffer();
|
|
#ifdef DEBUG
|
|
if (sii_debug > 4)
|
|
printf("Data %d dcnt %d dadr %x ",
|
|
state->dmaDataPhase,
|
|
state->dmaCnt,
|
|
(state->dmaAddrH << 16) |
|
|
state->dmaAddrL);
|
|
#endif
|
|
break;
|
|
}
|
|
if (state->dmaDataPhase != (dstat & SII_PHASE_MSK)) {
|
|
printf("%s: device %d: cmd %x: dma phase doesn't match\n",
|
|
sc->sc_dev.dv_xname, sc->sc_target,
|
|
sc->sc_cmd[sc->sc_target]->cmd[0]);
|
|
goto abort;
|
|
}
|
|
#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",
|
|
sc->sc_dev.dv_xname, 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->dmalen = i);
|
|
break;
|
|
}
|
|
/* start first chunk */
|
|
if (state->flags & FIRST_DMA) {
|
|
state->flags &= ~FIRST_DMA;
|
|
CopyToBuffer((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->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;
|
|
CopyToBuffer((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",
|
|
sc->sc_dev.dv_xname, sc->sc_target,
|
|
sc->sc_cmd[sc->sc_target]->cmd[0],
|
|
state->cmdlen);
|
|
state->cmdlen = 0;
|
|
#ifdef DEBUG
|
|
sii_DumpLog();
|
|
#endif
|
|
}
|
|
|
|
/* read amount transfered 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);
|
|
MachEmptyWriteBuffer();
|
|
regs->dstat = SII_DNE;
|
|
MachEmptyWriteBuffer();
|
|
#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 */
|
|
CopyFromBuffer((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;
|
|
state->dmaCnt = i = regs->dmlotc;
|
|
if (dstat & SII_OBB)
|
|
state->dmaByte = regs->dmabyte;
|
|
if (i == 0)
|
|
i = SII_MAX_DMA_XFER_LENGTH;
|
|
i = state->dmalen - i;
|
|
/* note: no carry from dmaddrl to dmaddrh */
|
|
state->dmaAddrL = regs->dmaddrl + i;
|
|
state->dmaAddrH = regs->dmaddrh;
|
|
regs->comm = comm &
|
|
(SII_STATE_MSK | SII_PHASE_MSK);
|
|
MachEmptyWriteBuffer();
|
|
regs->dstat = SII_DNE;
|
|
MachEmptyWriteBuffer();
|
|
#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 SCSI_COMMAND_COMPLETE:
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
/*
|
|
* 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 SCSI_EXTENDED_MSG:
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
/* 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 acknowlege 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 SCSI_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;
|
|
MachEmptyWriteBuffer();
|
|
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 SCSI_SYNCHRONOUS_XFER:
|
|
/*
|
|
* 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;
|
|
MachEmptyWriteBuffer();
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
|
|
/* wait for MSG_OUT phase */
|
|
SII_WAIT_UNTIL(dstat, regs->dstat,
|
|
dstat & SII_TBE,
|
|
SII_WAIT_COUNT, i);
|
|
|
|
/* send a reject message */
|
|
regs->data = SCSI_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;
|
|
MachEmptyWriteBuffer();
|
|
}
|
|
break;
|
|
|
|
case SCSI_SAVE_DATA_POINTER:
|
|
case SCSI_RESTORE_POINTERS:
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
/* 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 SCSI_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;
|
|
MachEmptyWriteBuffer();
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
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 SCSI_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;
|
|
MachEmptyWriteBuffer();
|
|
printf("%s: device %d: message reject.\n",
|
|
sc->sc_dev.dv_xname, sc->sc_target);
|
|
break;
|
|
|
|
default:
|
|
if (!(msg & SCSI_IDENTIFY)) {
|
|
printf("%s: device %d: couldn't handle message 0x%x... rejecting.\n",
|
|
sc->sc_dev.dv_xname, 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;
|
|
MachEmptyWriteBuffer();
|
|
/* 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 = SCSI_MESSAGE_PARITY_ERROR;
|
|
} else
|
|
regs->data = SCSI_NO_OP;
|
|
regs->comm = SII_INXFER | (comm & SII_STATE_MSK) |
|
|
SII_MSG_OUT_PHASE;
|
|
MachEmptyWriteBuffer();
|
|
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
printf("%s: Couldn't handle phase %d... ignoring.\n",
|
|
sc->sc_dev.dv_xname, 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",
|
|
sc->sc_dev.dv_xname, 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 = SCSI_ABORT;
|
|
regs->comm = SII_INXFER | SII_ATN | (cstat & SII_STATE_MSK) |
|
|
SII_MSG_OUT_PHASE;
|
|
MachEmptyWriteBuffer();
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
|
|
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(sc, cstat)
|
|
register struct siisoftc *sc;
|
|
register u_int cstat;
|
|
{
|
|
register SIIRegs *regs = sc->sc_regs;
|
|
register State *state;
|
|
register 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",
|
|
sc->sc_dev.dv_xname, 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",
|
|
sc->sc_dev.dv_xname, 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;
|
|
MachEmptyWriteBuffer();
|
|
if (!state->prevComm) {
|
|
printf("%s: device %d: spurious reselection\n",
|
|
sc->sc_dev.dv_xname, 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",
|
|
sc->sc_dev.dv_xname, regs->destat);
|
|
regs->comm = SII_DISCON;
|
|
MachEmptyWriteBuffer();
|
|
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",
|
|
sc->sc_dev.dv_xname, cstat);
|
|
#ifdef DEBUG
|
|
sii_DumpLog();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read one byte of data.
|
|
* If 'ack' is true, acknowledge the byte.
|
|
*/
|
|
static int
|
|
sii_GetByte(regs, phase, ack)
|
|
register SIIRegs *regs;
|
|
int phase, ack;
|
|
{
|
|
register u_int dstat;
|
|
register u_int state;
|
|
register int i;
|
|
register int data;
|
|
|
|
dstat = regs->dstat;
|
|
state = regs->cstat & SII_STATE_MSK;
|
|
if (!(dstat & SII_IBF) || (dstat & SII_MIS)) {
|
|
regs->comm = state | phase;
|
|
MachEmptyWriteBuffer();
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
}
|
|
}
|
|
|
|
return (data);
|
|
}
|
|
|
|
/*
|
|
* Exchange messages to initiate synchronous data transfers.
|
|
*/
|
|
static void
|
|
sii_DoSync(regs, state)
|
|
register SIIRegs *regs;
|
|
register State *state;
|
|
{
|
|
register u_int dstat, comm;
|
|
register 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] = SCSI_EXTENDED_MSG;
|
|
sii_buf[1] = 3; /* message length */
|
|
sii_buf[2] = SCSI_SYNCHRONOUS_XFER;
|
|
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;
|
|
MachEmptyWriteBuffer();
|
|
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
}
|
|
#else
|
|
CopyToBuffer((u_short *)sii_buf, (volatile u_short *)SII_BUF_ADDR, 5);
|
|
printf("sii_DoSync: %x %x %x ds %x\n",
|
|
((volatile u_short *)SII_BUF_ADDR)[0],
|
|
((volatile u_short *)SII_BUF_ADDR)[2],
|
|
((volatile u_short *)SII_BUF_ADDR)[4],
|
|
regs->dstat); /* XXX */
|
|
regs->dmaddrl = (u_short)(SII_BUF_ADDR >> 1);
|
|
regs->dmaddrh = (u_short)(SII_BUF_ADDR >> 17) & 03;
|
|
regs->dmlotc = 5;
|
|
regs->comm = SII_DMA | SII_INXFER | SII_ATN |
|
|
(regs->cstat & SII_STATE_MSK) | SII_MSG_OUT_PHASE;
|
|
MachEmptyWriteBuffer();
|
|
|
|
/* 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;
|
|
MachEmptyWriteBuffer();
|
|
#endif
|
|
|
|
#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(regs, phase, dmaAddr, size)
|
|
register SIIRegs *regs; /* which SII to use */
|
|
int phase; /* phase to send/receive data */
|
|
u_short *dmaAddr; /* DMA buffer address */
|
|
int 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;
|
|
MachEmptyWriteBuffer();
|
|
|
|
#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(sc, target, error)
|
|
register struct siisoftc *sc; /* which SII to use */
|
|
int target; /* which device is done */
|
|
int error; /* error code if any errors */
|
|
{
|
|
register ScsiCmd *scsicmd;
|
|
register int i;
|
|
|
|
scsicmd = sc->sc_cmd[target];
|
|
#ifdef DIAGNOSTIC
|
|
if (target < 0 || !scsicmd)
|
|
panic("sii_CmdDone");
|
|
#endif
|
|
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",
|
|
scsicmd->sd->sd_driver->d_name, 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;
|
|
}
|
|
|
|
(*scsicmd->sd->sd_driver->d_done)(scsicmd->unit, error,
|
|
sc->sc_st[target].buflen, sc->sc_st[target].statusByte);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
sii_DumpLog()
|
|
{
|
|
register 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
|
|
#endif
|
|
|