930 lines
18 KiB
C
930 lines
18 KiB
C
#include "sc.h"
|
|
#if NSC > 0
|
|
#ifndef lint
|
|
static char sccsid[] = "@(#)sc.c 1.1 94/10/31 SMI";
|
|
#endif
|
|
|
|
/*
|
|
* Copyright (C) 1989, Sun Microsystems, Inc.
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* TO DO: 1. reimplment PARITY support
|
|
* 2. handle linked commands
|
|
*
|
|
*/
|
|
|
|
#include <scsi/scsi.h>
|
|
#include <scsi/adapters/screg.h>
|
|
#include <sys/mman.h>
|
|
#include <machine/pte.h>
|
|
#include <machine/cpu.h>
|
|
#include <vm/seg.h>
|
|
|
|
/*
|
|
* Definitions
|
|
*/
|
|
|
|
/*#define SC_PARITY /* Compile in (cough) Parity support */
|
|
/*#define SC_LINKED /* Compile in linked command support */
|
|
/*#define SCDEBUG /* Compile in debug code */
|
|
|
|
#ifdef SC_PARITY
|
|
---- BLETCH ----- PARITY NOT SUPPORTED YET
|
|
#endif
|
|
|
|
|
|
#ifdef SCDEBUG
|
|
#define DEBUGGING (scdebug || (scsi_options & SCSI_DEBUG_HA))
|
|
#define DPRINTF(str) if (DEBUGGING) sc_printf(sc, str)
|
|
#define DPRINTF1(str, x) if (DEBUGGING) sc_printf(sc, str, x)
|
|
#define DPRINTF2(str, x, y) if (DEBUGGING) sc_printf(sc, str, x, y)
|
|
#define DPRINTF3(str, x, y, z) if (DEBUGGING) sc_printf(sc, str, x, y, z)
|
|
#else
|
|
#define DPRINTF
|
|
#define DPRINTF1
|
|
#define DPRINTF2
|
|
#define DPRINTF3
|
|
#endif
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
#define UNDEFINED -1
|
|
|
|
#define Tgt(sp) ((sp)->cmd_pkt.pkt_address.a_target)
|
|
#define Lun(sp) ((sp)->cmd_pkt.pkt_address.a_lun)
|
|
#define Nextcmd(sp) ((struct scsi_cmd *)((sp)->cmd_pkt.pkt_ha_private))
|
|
|
|
#define CNAME scdriver.mdr_cname
|
|
#define CNUM sc-sc_softc
|
|
|
|
#define INTPENDING(reg) ((reg)->icr & (ICR_INTERRUPT_REQUEST | ICR_BUS_ERROR))
|
|
|
|
|
|
/*
|
|
* SCSI Architecture Interface functions
|
|
*/
|
|
|
|
static int sc_start(), sc_abort(), sc_reset(), sc_getcap(), sc_setcap();
|
|
|
|
/*
|
|
* Local && h/w related functions
|
|
*/
|
|
|
|
|
|
static void screset(), sc_watch(), sc_ustart(), sc_finish(), sc_abort_cmd();
|
|
static void sc_printf(), sc_flush();
|
|
|
|
/*
|
|
* Local static data
|
|
*/
|
|
|
|
static struct scsitwo sc_softc[NSC];
|
|
static char *scbits =
|
|
"\20\20Per\17Berr\16Odd\15IRQ\14REQ\13MSG\12CD\11IO\03Wrd\02Dma\01Ena";
|
|
static long sc_kment;
|
|
static int sc_mapins;
|
|
|
|
#ifdef SCDEBUG
|
|
static int scdebug;
|
|
#endif SCDEBUG
|
|
|
|
/*
|
|
* External references
|
|
*/
|
|
|
|
extern struct seg kseg;
|
|
|
|
/*
|
|
* Config dependencies
|
|
*/
|
|
|
|
static int scprobe(), scslave(), scattach(), scpoll();
|
|
int scintr();
|
|
struct mb_driver scdriver = {
|
|
scprobe, scslave, scattach, 0, 0, scpoll,
|
|
sizeof (struct screg), "scsibus", 0, "sc", 0, MDR_BIODMA,
|
|
};
|
|
|
|
static int
|
|
scprobe(reg, ctlr)
|
|
caddr_t reg;
|
|
int ctlr;
|
|
{
|
|
static char scwstart = 0;
|
|
register struct scsitwo *sc;
|
|
register struct screg *btmp = (struct screg *) reg;
|
|
|
|
/* probe for different scsi host adaptor interfaces */
|
|
if (peek((short *)&btmp->dma_count) == -1) {
|
|
return (0);
|
|
}
|
|
/* validate ctlr by write/read/cmp with a data pattern */
|
|
btmp->dma_count = 0x6789;
|
|
if (btmp->dma_count != 0x6789) {
|
|
return (0);
|
|
}
|
|
|
|
if (ctlr >= NSC) {
|
|
printf("%s%d: illegal controller number", CNAME, ctlr);
|
|
return (0);
|
|
}
|
|
|
|
sc_kment = rmalloc(kernelmap, mmu_btopr(MMU_PAGESIZE));
|
|
if (sc_kment == 0)
|
|
return (0);
|
|
sc = &sc_softc[ctlr];
|
|
sc->sc_reg = btmp;
|
|
sc->sc_tran.tran_start = sc_start;
|
|
sc->sc_tran.tran_abort = sc_abort;
|
|
sc->sc_tran.tran_reset = sc_reset;
|
|
sc->sc_tran.tran_getcap = sc_getcap;
|
|
sc->sc_tran.tran_setcap = sc_setcap;
|
|
sc->sc_tran.tran_pktalloc = scsi_std_pktalloc;
|
|
sc->sc_tran.tran_dmaget = scsi_std_dmaget;
|
|
sc->sc_tran.tran_pktfree = scsi_std_pktfree;
|
|
sc->sc_tran.tran_dmafree = scsi_std_dmafree;
|
|
screset(sc, 0);
|
|
if (!scwstart) {
|
|
scwstart++;
|
|
timeout (sc_watch, (caddr_t) 0, hz);
|
|
}
|
|
return (sizeof (struct screg));
|
|
}
|
|
|
|
/*ARGSUSED1*/
|
|
static int
|
|
scslave(md, reg)
|
|
struct mb_device *md;
|
|
caddr_t reg;
|
|
{
|
|
struct scsitwo *sc = &sc_softc[md->md_unit];
|
|
|
|
md->md_slave = HOST_ID << 3;
|
|
#ifndef SC_LINKED
|
|
/*
|
|
* Disable linked commands for the moment...
|
|
*/
|
|
if (scsi_options & SCSI_OPTIONS_LINK) {
|
|
scsi_options ^= SCSI_OPTIONS_LINK;
|
|
sc_printf(sc, "disabling linked command support\n");
|
|
}
|
|
#endif
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
scattach(md)
|
|
struct mb_device *md;
|
|
{
|
|
int i;
|
|
static int sc_spl = 0;
|
|
struct scsitwo *sc = &sc_softc[md->md_unit];
|
|
|
|
/*
|
|
* Calculate max spl value so far seen
|
|
*/
|
|
sc_spl = MAX(sc_spl, pritospl(md->md_intpri));
|
|
|
|
/*
|
|
* Make sure that everyone, including us, is locking at
|
|
* the same priority.
|
|
*/
|
|
|
|
for (i = 0; i <= md->md_unit; i++) {
|
|
sc_softc[i].sc_tran.tran_spl = sc_spl;
|
|
}
|
|
|
|
/*
|
|
* Initialize interrupt register
|
|
*/
|
|
if (md->md_mc->mc_intr) {
|
|
/* set up for vectored interrupts */
|
|
sc->sc_reg->intvec = md->md_mc->mc_intr->v_vec;
|
|
*(md->md_mc->mc_intr->v_vptr) = (int) sc;
|
|
} else {
|
|
/* use auto-vectoring */
|
|
sc->sc_reg->intvec = AUTOBASE + md->md_mc->mc_intpri;
|
|
}
|
|
/*
|
|
* Now make ourselves known to the rest of the SCSI world
|
|
*/
|
|
scsi_config(&sc->sc_tran, md);
|
|
}
|
|
|
|
|
|
/*
|
|
*
|
|
* Begin of external interface routines
|
|
*
|
|
*/
|
|
|
|
static int
|
|
sc_start(sp)
|
|
register struct scsi_cmd *sp;
|
|
{
|
|
auto int s;
|
|
register struct scsitwo *sc;
|
|
register struct scsi_cmd *xp;
|
|
|
|
sc = (struct scsitwo *) sp->cmd_pkt.pkt_address.a_cookie;
|
|
s = splr(sc->sc_tran.tran_spl);
|
|
if (xp = sc->sc_que) {
|
|
for (;;) {
|
|
if (Tgt(xp) == Tgt(sp) && Lun(xp) == Lun(sp)) {
|
|
/* Queueing error */
|
|
(void) splx(s);
|
|
return (FALSE);
|
|
}
|
|
if (Nextcmd(xp) == 0)
|
|
break;
|
|
else
|
|
xp = Nextcmd(xp);
|
|
}
|
|
Nextcmd(xp) = sp;
|
|
} else {
|
|
sc->sc_que = sp;
|
|
}
|
|
Nextcmd(sp) = 0;
|
|
|
|
/*
|
|
* reinitialize some fields that need it...
|
|
*/
|
|
|
|
sp->cmd_pkt.pkt_resid = 0;
|
|
sp->cmd_pkt.pkt_reason = sp->cmd_pkt.pkt_state =
|
|
sp->cmd_pkt.pkt_statistics = 0;
|
|
sp->cmd_cdbp = (caddr_t) sp->cmd_pkt.pkt_cdbp;
|
|
sp->cmd_scbp = sp->cmd_pkt.pkt_scbp; *sp->cmd_scbp = 0;
|
|
if (sp->cmd_timeout = sp->cmd_pkt.pkt_time)
|
|
sp->cmd_flags |= CFLAG_WATCH;
|
|
else
|
|
sp->cmd_flags &= ~CFLAG_WATCH;
|
|
sp->cmd_data = sp->cmd_saved_data = sp->cmd_mapping;
|
|
sp->cmd_cursubseg = &sp->cmd_subseg;
|
|
sp->cmd_subseg.d_base = sp->cmd_mapping;
|
|
sp->cmd_subseg.d_count = 0;
|
|
sp->cmd_subseg.d_next = (struct dataseg *) 0;
|
|
sp->cmd_flags &= ~(CFLAG_NEEDSEG|CFLAG_CMDDISC);
|
|
|
|
|
|
|
|
/*
|
|
* Okay- if this command is a polling command,
|
|
* and we're not the head of the queue, we have to
|
|
* wait for the commands ahead of us to finish.
|
|
*/
|
|
if (sp->cmd_pkt.pkt_flags & FLAG_NOINTR) {
|
|
int id;
|
|
while (sc->sc_que != sp) {
|
|
DPRINTF ("polled command waiting\n");
|
|
sc_dopoll(sc, sc->sc_cmdid);
|
|
}
|
|
id = sc->sc_cmdid;
|
|
DPRINTF1 ("starting polling cmd #%d\n", id);
|
|
sc_ustart(sc);
|
|
DPRINTF ("waiting polled cmd completion\n");
|
|
sc_dopoll(sc, id);
|
|
DPRINTF1 ("polled cmd #%d complete\n", id);
|
|
if (sc->sc_que)
|
|
sc_ustart(sc);
|
|
} else if (sc->sc_que == sp) {
|
|
sc_ustart(sc);
|
|
}
|
|
(void) splx(s);
|
|
return (TRUE);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
sc_abort(ap, pkt)
|
|
struct scsi_address *ap;
|
|
struct scsi_pkt *pkt;
|
|
{
|
|
if (pkt != (struct scsi_pkt *) 0)
|
|
return (FALSE);
|
|
else
|
|
return (FALSE);
|
|
}
|
|
|
|
static int
|
|
sc_reset(ap, level)
|
|
struct scsi_address *ap;
|
|
int level;
|
|
{
|
|
struct scsitwo *sc = (struct scsitwo *) ap->a_cookie;
|
|
struct scsi_cmd *sp = sc->sc_que;
|
|
|
|
screset(sc, 0);
|
|
|
|
if (sp) {
|
|
/*
|
|
* If we're resetting everything, remove
|
|
* the current wait queue from reconsideration
|
|
* after we reset and blow away the current
|
|
* command.
|
|
*/
|
|
|
|
if (level == RESET_ALL) {
|
|
sp = Nextcmd(sc->sc_que);
|
|
Nextcmd(sc->sc_que) = 0;
|
|
}
|
|
|
|
sp->cmd_pkt.pkt_reason = CMD_RESET;
|
|
sc_finish(sc);
|
|
|
|
/*
|
|
* walk down the unrooted wait queue, 'finishing' off
|
|
* all the commands therein...
|
|
*/
|
|
if (level == RESET_ALL) {
|
|
while (sp) {
|
|
struct scsi_cmd *xp;
|
|
xp = Nextcmd(sp);
|
|
sp->cmd_pkt.pkt_reason = CMD_RESET;
|
|
(*sp->cmd_pkt.pkt_comp)(sp);
|
|
sp = xp;
|
|
}
|
|
}
|
|
return (TRUE);
|
|
} else {
|
|
return (FALSE);
|
|
}
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
sc_getcap(ap, cap)
|
|
struct scsi_address *ap;
|
|
char *cap;
|
|
{
|
|
if (cap == (char *) 0)
|
|
return (UNDEFINED);
|
|
else if (strncmp("dma_max", cap, 7) == 0)
|
|
return (1<<16);
|
|
else if (strncmp("msg_out", cap, 7) == 0)
|
|
return (0);
|
|
else if (strncmp("disconnect", cap, 10) == 0)
|
|
return (0);
|
|
else if (strncmp("synchronous", cap, 11) == 0)
|
|
return (0);
|
|
else if (strncmp("parity", cap, 6) == 0)
|
|
return (0);
|
|
else if (strncmp("initiator-id", cap, 12) == 0)
|
|
return (HOST_ID);
|
|
else
|
|
return (UNDEFINED);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
sc_setcap(ap, cap, value)
|
|
struct scsi_address *ap;
|
|
char *cap;
|
|
int value;
|
|
{
|
|
/*
|
|
* Cannot set any values yet
|
|
*/
|
|
return (UNDEFINED);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
*
|
|
* Internal start routine
|
|
*
|
|
*/
|
|
|
|
static void
|
|
sc_ustart(sc)
|
|
register struct scsitwo *sc;
|
|
{
|
|
register i;
|
|
register struct scsi_cmd *sp = sc->sc_que;
|
|
char dkn;
|
|
int r;
|
|
|
|
if (!sp)
|
|
return;
|
|
else if ((dkn = sp->cmd_pkt.pkt_pmon) >= 0) {
|
|
dk_busy |= (1<<dkn);
|
|
dk_xfer[dkn]++;
|
|
if ((sp->cmd_flags & CFLAG_DMASEND) == 0)
|
|
dk_read[dkn]++;
|
|
dk_wds[dkn] += sp->cmd_dmacount >> 6;
|
|
}
|
|
|
|
/*
|
|
* sc_select will wait for the scsi bus to be free, attempt
|
|
* to select the target, will set up the dma engine for any
|
|
* possible dma transfer, and will set the icr bits for having
|
|
* interrupts enabled if this is an interrupting command.
|
|
*
|
|
*/
|
|
|
|
sc->sc_busy = 1;
|
|
if (sc_select(sc) == FALSE) {
|
|
sc_finish(sc);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* At this point, we should either ge into STATUS phase or into
|
|
* COMMAND phase.
|
|
*/
|
|
#ifdef SCDEBUG
|
|
if (DEBUGGING) {
|
|
sc_printf(sc, "%d.%d cmd=", Tgt(sp), Lun(sp));
|
|
for (i = 0; i < sp->cmd_cdblen; i++) {
|
|
printf(" 0x%x", sp->cmd_cdbp[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif SCDEBUG
|
|
for (r = i = 0; i < sp->cmd_cdblen; i++) {
|
|
if ((r = sc_putbyte(sc, ICR_COMMAND, sp->cmd_cdbp[i])) <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (r < 0) {
|
|
sc_printf(sc, "couldn't send command\n");
|
|
sc_abort_cmd(sc);
|
|
} else if (i > 0) {
|
|
sp->cmd_pkt.pkt_state |= STATE_SENT_CMD;
|
|
sp->cmd_cdbp = (caddr_t) (((u_long)sp->cmd_cdbp) + i);
|
|
}
|
|
/*
|
|
* We will either let interrupts drive us the rest of the
|
|
* way, or call a polling routine to do that for us
|
|
*/
|
|
}
|
|
|
|
static int
|
|
sc_select(sc)
|
|
register struct scsitwo *sc;
|
|
{
|
|
register struct scsi_cmd *sp = sc->sc_que;
|
|
register int i;
|
|
u_short icr_mode;
|
|
|
|
DPRINTF2 ("select %d.%d\n", Tgt(sp), Lun(sp));
|
|
|
|
/*
|
|
* make sure scsi bus is not continuously busy
|
|
*/
|
|
|
|
for (i = SC_WAIT_COUNT; i > 0; i--) {
|
|
if ((sc->sc_reg->icr & ICR_BUSY) == 0)
|
|
break;
|
|
DELAY(10);
|
|
}
|
|
|
|
if (i == 0) {
|
|
sc_printf(sc, "scsi bus continuously busy: ICR=0x%b\n",
|
|
sc->sc_reg->icr, scbits);
|
|
sp->cmd_pkt.pkt_reason = CMD_INCOMPLETE;
|
|
screset(sc, 1); /* XXX? */
|
|
return (FALSE);
|
|
}
|
|
|
|
/*
|
|
* Got the bus...
|
|
*/
|
|
|
|
sp->cmd_pkt.pkt_state |= STATE_GOT_BUS;
|
|
|
|
/*
|
|
* select target and wait for response
|
|
*/
|
|
|
|
sc->sc_reg->icr = 0; /* Make sure SECONDBYTE flag is clear */
|
|
|
|
/*
|
|
* Since we aren't an arbitrating initiator, we only
|
|
* put in the target address and *not* our own as well.
|
|
*/
|
|
|
|
sc->sc_reg->data = 1 << Tgt(sp);
|
|
sc->sc_reg->icr = ICR_SELECT;
|
|
|
|
/*
|
|
* See if target responds to selection
|
|
*/
|
|
|
|
if (sc_wait((u_short *)&sc->sc_reg->icr, SC_SHORT_WAIT, ICR_BUSY)) {
|
|
DPRINTF2 ("%d.%d didn't select\n", Tgt(sp), Lun(sp));
|
|
sc->sc_reg->data = 0;
|
|
sc->sc_reg->icr = 0;
|
|
sp->cmd_pkt.pkt_reason = CMD_INCOMPLETE;
|
|
return (FALSE);
|
|
}
|
|
|
|
/*
|
|
* Got the target...
|
|
*/
|
|
|
|
sp->cmd_pkt.pkt_state |= STATE_GOT_TARGET;
|
|
|
|
icr_mode = 0;
|
|
if ((sp->cmd_pkt.pkt_flags & FLAG_NOINTR) == 0) {
|
|
icr_mode |= ICR_INTERRUPT_ENABLE;
|
|
}
|
|
/*
|
|
* If a data transfer is expected, set it up here...
|
|
*/
|
|
|
|
if (sp->cmd_dmacount && (sp->cmd_flags&CFLAG_DMAVALID)) {
|
|
SET_DMA_ADDR (sc->sc_reg, ((int)sp->cmd_data));
|
|
sc->sc_reg->dma_count = ~sp->cmd_dmacount;
|
|
if (((int)sp->cmd_data)&0x1) {
|
|
DPRINTF ("odd byte DMA address\n");
|
|
} else {
|
|
icr_mode |= ICR_WORD_MODE;
|
|
}
|
|
icr_mode |= ICR_DMA_ENABLE;
|
|
}
|
|
|
|
/*
|
|
* set the interrupt register
|
|
*/
|
|
|
|
sc->sc_reg->icr = icr_mode;
|
|
|
|
return (TRUE);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* finish routine
|
|
*
|
|
*/
|
|
|
|
|
|
static void
|
|
sc_finish(sc)
|
|
register struct scsitwo *sc;
|
|
{
|
|
register struct scsi_cmd *sp = sc->sc_que;
|
|
char dkn, wasintr;
|
|
|
|
sc->sc_reg->icr = 0; /* clear any pending interrupts */
|
|
sc->sc_busy = 0;
|
|
|
|
if ((dkn = sp->cmd_pkt.pkt_pmon) >= 0) {
|
|
dk_busy &= ~(1<<dkn);
|
|
}
|
|
DPRINTF1 ("finishing command %d\n", sc->sc_cmdid);
|
|
sc->sc_cmdid++;
|
|
sc->sc_que = Nextcmd(sp);
|
|
if (sp->cmd_pkt.pkt_state & STATE_XFERRED_DATA) {
|
|
/*
|
|
* Since we cannot disconnect, we cannot have had more
|
|
* than one data segment
|
|
*/
|
|
if (sp->cmd_subseg.d_next != (struct dataseg *) 0) {
|
|
panic("sc_finish: more than one segment with data");
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
sp->cmd_pkt.pkt_resid =
|
|
sp->cmd_dmacount - sp->cmd_subseg.d_count;
|
|
|
|
}
|
|
|
|
wasintr = (sp->cmd_pkt.pkt_flags & FLAG_NOINTR)? 0: 1;
|
|
|
|
(*sp->cmd_pkt.pkt_comp)(sp);
|
|
|
|
if (wasintr && sc->sc_que && sc->sc_busy == 0) {
|
|
sc_ustart(sc);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Interrupt service section
|
|
*/
|
|
|
|
static int
|
|
sc_dopoll(sc, id)
|
|
register struct scsitwo *sc;
|
|
register id;
|
|
{
|
|
register i;
|
|
struct scsi_cmd *sp = sc->sc_que;
|
|
|
|
for (i = 0; i < 300000 && id == sc->sc_cmdid; i++) {
|
|
if (INTPENDING(sc->sc_reg)) {
|
|
scintr(sc);
|
|
i = 0;
|
|
} else {
|
|
DELAY(500);
|
|
}
|
|
}
|
|
if (i >= 300000) {
|
|
sc_printf(sc, "polled command timeout\n");
|
|
sp->cmd_pkt.pkt_reason = CMD_TIMEOUT;
|
|
screset(sc, 0);
|
|
sc_finish(sc);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Handle a polling (autovectored) SCSI bus interrupt.
|
|
*/
|
|
|
|
static int
|
|
scpoll()
|
|
{
|
|
register struct scsitwo *sc;
|
|
register int serviced = 0;
|
|
|
|
for (sc = &sc_softc[0]; sc < &sc_softc[NSC]; sc++) {
|
|
if (sc->sc_reg && INTPENDING(sc->sc_reg)) {
|
|
serviced += scintr(sc);
|
|
}
|
|
}
|
|
return (serviced);
|
|
}
|
|
|
|
|
|
/*
|
|
* Handle a scsi bus interrupt.
|
|
*/
|
|
|
|
int
|
|
scintr(sc)
|
|
register struct scsitwo *sc;
|
|
{
|
|
register struct scsi_cmd *sp = sc->sc_que;
|
|
|
|
if (sp == 0 || !sc->sc_reg || !(INTPENDING(sc->sc_reg))) {
|
|
sc_printf(sc, "spurious interrupt\n");
|
|
return (0);
|
|
}
|
|
|
|
DPRINTF2 ("scintr: ICR=0x%b\n", sc->sc_reg->icr, scbits);
|
|
|
|
/*
|
|
* End of Data phase? Check for bus error first...
|
|
*/
|
|
if (sc->sc_reg->icr & ICR_BUS_ERROR) {
|
|
sc_printf(sc, "Dma BUS Error on address 0x%x\n", sp->cmd_data);
|
|
sc_abort_cmd(sc);
|
|
return (1);
|
|
} else if (sp->cmd_dmacount && (sp->cmd_flags & CFLAG_DMAVALID)) {
|
|
int resid, reqamt, xfer_amt;
|
|
|
|
resid = (u_short) (~sc->sc_reg->dma_count);
|
|
reqamt = sp->cmd_dmacount;
|
|
|
|
if (sc->sc_reg->icr & ICR_ODD_LENGTH) {
|
|
if (sp->cmd_flags & CFLAG_DMASEND) {
|
|
resid++;
|
|
} else if (reqamt) {
|
|
sc_flush(sc, (u_long)
|
|
(sp->cmd_data + reqamt - resid));
|
|
resid--;
|
|
}
|
|
}
|
|
if (xfer_amt = reqamt - resid) {
|
|
DPRINTF1 ("xferred %d\n", xfer_amt);
|
|
sp->cmd_data += xfer_amt;
|
|
sp->cmd_subseg.d_count += xfer_amt;
|
|
sp->cmd_pkt.pkt_state |= STATE_XFERRED_DATA;
|
|
}
|
|
}
|
|
/*
|
|
* Now, get status and/or message...
|
|
*/
|
|
while (sc->sc_reg->icr & (ICR_BUSY|ICR_REQUEST)) {
|
|
register u_short pbits;
|
|
|
|
while (((pbits = sc->sc_reg->icr) & ICR_REQUEST) == 0) {
|
|
if ((pbits & ICR_BUSY) == 0) {
|
|
sc_printf(sc, "Target %d dropped BUSY\n",
|
|
Tgt(sp));
|
|
sc_abort_cmd(sc);
|
|
return (1);
|
|
}
|
|
}
|
|
|
|
switch (pbits & ICR_BITS) {
|
|
case ICR_STATUS:
|
|
*sp->cmd_scbp = sc->sc_reg->cmd_stat;
|
|
sp->cmd_pkt.pkt_state |= STATE_GOT_STATUS;
|
|
break;
|
|
case ICR_MESSAGE_IN:
|
|
{
|
|
static char *bad =
|
|
"Bad Message '0x%x' from Target %d, Lun %d\n";
|
|
|
|
switch (sc->sc_msgin = sc->sc_reg->cmd_stat) {
|
|
case MSG_COMMAND_COMPLETE:
|
|
case MSG_LINK_CMPLT:
|
|
case MSG_LINK_CMPLT_FLAG:
|
|
sp->cmd_pkt.pkt_reason = CMD_CMPLT;
|
|
sc_finish(sc);
|
|
break;
|
|
default:
|
|
sc_printf(sc, bad, sc->sc_msgin,
|
|
Tgt(sp), Lun(sp));
|
|
sc_abort_cmd(sc);
|
|
break;
|
|
}
|
|
return (1);
|
|
break;
|
|
}
|
|
default:
|
|
sc_printf(sc, "Bad phase: ICR=0x%b\n", pbits, scbits);
|
|
sc_abort_cmd(sc);
|
|
return (1);
|
|
break;
|
|
}
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Flush the last byte of an odd length transfer
|
|
*/
|
|
|
|
static void
|
|
sc_flush(sc, offset)
|
|
register struct scsitwo *sc;
|
|
register u_long offset;
|
|
{
|
|
register u_int pv;
|
|
u_char *mapaddr;
|
|
|
|
if (MBI_MR(offset) < dvmasize) {
|
|
DVMA[offset] = sc->sc_reg->data;
|
|
return;
|
|
}
|
|
|
|
#ifdef sun3x
|
|
pv = btop (VME24D16_BASE + (offset & VME24D16_MASK));
|
|
#else sun3x
|
|
pv = PGT_VME_D16 | VME24_BASE | btop(offset & VME24_MASK);
|
|
#endif sun3x
|
|
|
|
mapaddr = (u_char *) ((u_long) kmxtob(sc_kment) |
|
|
(u_long) MBI_OFFSET(offset));
|
|
|
|
segkmem_mapin(&kseg, (addr_t) (((int)mapaddr) & PAGEMASK),
|
|
(u_int) mmu_ptob(1), PROT_READ | PROT_WRITE, pv, 0);
|
|
|
|
*mapaddr = sc->sc_reg->data;
|
|
|
|
segkmem_mapout(&kseg,
|
|
(addr_t) (((int)mapaddr) & PAGEMASK), (u_int) mmu_ptob(1));
|
|
|
|
segkmem_mapout(&kseg, (addr_t) mapaddr, (u_int) mmu_btop(1));
|
|
}
|
|
|
|
/*
|
|
* Miscellaneous functions
|
|
*/
|
|
|
|
|
|
static void
|
|
sc_abort_cmd(sc)
|
|
struct scsitwo *sc;
|
|
{
|
|
sc->sc_que->cmd_pkt.pkt_reason = CMD_RESET;
|
|
screset(sc, 1);
|
|
sc_finish(sc);
|
|
}
|
|
|
|
/*
|
|
* Wait for a condition to be (de)asserted on the scsi bus.
|
|
* Returns 0, if successful, else -1;
|
|
*/
|
|
|
|
static int
|
|
sc_wait(icrp, wait_count, cond)
|
|
register u_short *icrp;
|
|
register int wait_count;
|
|
{
|
|
register int i;
|
|
register u_short icr;
|
|
|
|
for (i = 0; i < wait_count; i++) {
|
|
icr = *icrp;
|
|
if ((icr & cond) == cond) {
|
|
return (0);
|
|
} else if (icr & ICR_BUS_ERROR) {
|
|
break;
|
|
} else
|
|
DELAY(10);
|
|
}
|
|
return (-1);
|
|
}
|
|
|
|
/*
|
|
* Put data byte onto the scsi bus if phase match.
|
|
* Returns: 1 if successful (phase matched)
|
|
* 0 if incorrect phase, but REQ is present
|
|
* -1 if no REQ present
|
|
*/
|
|
|
|
static
|
|
sc_putbyte(sc, bits, data)
|
|
register struct scsitwo *sc;
|
|
register u_short bits;
|
|
register u_char data;
|
|
{
|
|
register u_short icr;
|
|
|
|
if (sc_wait((u_short *)&sc->sc_reg->icr, SC_WAIT_COUNT, ICR_REQUEST)) {
|
|
DPRINTF ("sc_putbyte: no REQ\n");
|
|
return (-1);
|
|
}
|
|
icr = sc->sc_reg->icr;
|
|
if ((icr & ICR_BITS) != bits) {
|
|
DPRINTF2 ("sc_putbyte: ICR=0x%b\n", icr, scbits);
|
|
return (0);
|
|
}
|
|
sc->sc_reg->cmd_stat = data;
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Reset SCSI control logic and bus.
|
|
*/
|
|
static void
|
|
screset(sc, msg_enable)
|
|
struct scsitwo *sc;
|
|
int msg_enable;
|
|
{
|
|
|
|
if (msg_enable) {
|
|
sc_printf(sc, "Resetting SCSI bus\n");
|
|
}
|
|
|
|
sc->sc_reg->icr = ICR_RESET;
|
|
DELAY(50);
|
|
sc->sc_reg->icr = 0;
|
|
|
|
/* give reset scsi devices time to recover (> 2 Sec) */
|
|
DELAY(SC_RESET_DELAY);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
sc_watch(arg)
|
|
caddr_t arg;
|
|
{
|
|
register s;
|
|
register struct scsitwo *sc;
|
|
register struct scsi_cmd *sp;
|
|
|
|
for (sc = &sc_softc[0]; sc < &sc_softc[NSC]; sc++) {
|
|
if (sc->sc_reg) {
|
|
s = splr(sc->sc_tran.tran_spl);
|
|
sp = sc->sc_que;
|
|
if (sp && (sp->cmd_flags & CFLAG_WATCH)) {
|
|
if (sp->cmd_timeout == 0) {
|
|
/*
|
|
* A pending interrupt
|
|
* defers the sentence
|
|
* of death.
|
|
*/
|
|
if (INTPENDING(sc->sc_reg)) {
|
|
sp->cmd_timeout++;
|
|
(void) splx(s);
|
|
continue;
|
|
}
|
|
sc_printf(sc, "command timeout\n");
|
|
sp->cmd_pkt.pkt_reason = CMD_TIMEOUT;
|
|
sc_reset(sc, 0);
|
|
sc_finish(sc);
|
|
(void) splx(s);
|
|
continue;
|
|
} else {
|
|
sp->cmd_timeout -= 1;
|
|
}
|
|
}
|
|
(void) splx(s);
|
|
}
|
|
}
|
|
timeout(sc_watch, (caddr_t) 0, hz);
|
|
}
|
|
|
|
/*VARARGS*/
|
|
static void
|
|
sc_printf(sc, fmt, a, b, c, d, e, f, g, h)
|
|
struct scsitwo *sc;
|
|
char *fmt;
|
|
int a, b, c, d, e, f, g, h;
|
|
{
|
|
printf("%s%d: ", CNAME, CNUM);
|
|
printf(fmt, a, b, c, d, e, f, g, h);
|
|
}
|
|
|
|
#endif (NSC > 0)
|