#include "stc.h"
#if NSTC > 0

/* $Id: stc.c,v 1.39 1992/07/28 18:14:35 sean Exp $ */
/*****************************************************************************
*		   (c) Copyright  1990 Bill Nesheim			     *
*		 Thinking Machines Corporation, Inc.,			     *
*	       Cambridge, Mass.   All rights reserved.			     *
*									     *
*  This notice is intended as a precaution against inadvertent publication   *
*  and does not constitute an admission or acknowledgement that publication  *
*  has occurred or constitute a waiver of confidentiality.		     *
*									     *
*  Connection Machine software is the proprietary and confidential property  *
*  of Thinking Machines Corporation.					     *
*****************************************************************************/

#ifndef lint
static	char rcsid[] = "$Id: stc.c,v 1.39 1992/07/28 18:14:35 sean Exp $";
#endif  lint

/*
 * Driver for the StorageTek 4980 cart tape system.
 * Bill Nesheim 
 * Thinking Machines Corp.
 */


#define B_VME_ADDR 0x02000000	/* !!!!! MUST MATCH DEFINE IN CIP.C !!!! */

#define STC_TIMEOUT		/* Enable command timeouts */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/user.h>
#include <sys/file.h>
#include <sys/map.h>
#include <sys/vm.h>
#include <sys/mtio.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/types.h>

#include <machine/psl.h>
#include <machine/mmu.h>
#include <machine/cpu.h>
#include <machine/pte.h>

#include <sun/dklabel.h>
#include <sun/dkio.h>

#include <sundev/mbvar.h>
#include <sundev/screg.h>
#include <sundev/sireg.h>
#include <sundev/scsi.h>
#include <vm/faultcode.h>
#include <vm/hat.h>
#include <vm/seg.h>
#include <vm/as.h>
#include "stcreg.h"

/*
 * NOTES: Command timeouts don't work 100% of the time.  Si raises it's
 * interrupt priority so high that timeouts are blocked. Unfortunately, this
 * is usually triggered by spurious phase changes and it doesn't appear to
 * ever lower it.  Under these conditions, the whole system will hang.
 * 
 * Generic device operation assumes the device is capable of fixed-length I/O.
 * Be careful when modifying the following variables, as these are used
 * throughout this driver: dstc->un_openf, dstc->un_ctype,
 */

extern struct scsi_unit stcunits[];
extern struct scsi_unit_subr scsi_unit_subr[];
struct scsi_stc sstc[NSTC];
int stc_timer_started = 0;
int use_buffered_mode = 1;

int nSTC = NSTC;

#define MAXSTCBUF		3	/* Max outstanding buffers per unit */
#define MAX_BUSY		10	/* Arbitrary Constant */
#define MAX_RETRIES             5	/* Arbitrary Constant */
#define STCUNIT(dev)		(minor(dev) & 0x03)
#define STCNUM(un)              (un - stcunits)
#define SECSIZE                 512	/* Bytes/sector */
#define SECDIV                  9	/* log2 (SECSIZE) */
#define STC_MAX_XFR		((16 * 1024 * 1024) - 4)

int stc_max_xfr;

/*
 * Error message control.
 */
#define EL_MOREDEBUG    	6
#define EL_DEBUG        	5
#define EL_INFO			4
#define EL_RETRY        	3
#define EL_RESTORE      	2
#define EL_FAIL         	1

int      stc_err_level = EL_INFO;

#define EPRINTF(level)  if (level <= stc_err_level) printf
#define IFLEVEL(level)  if (level <= stc_err_level)

/* States in first 4 bits, flags above */
#define STATE_MASK		0xf
#define CLOSED                  0	
#define CLOSING                 1
#define OPEN_FAILED_BUSY        2
#define OPEN_FAILED_CHECK       3
#define OPEN_FAILED             4
#define OPENING_DELAY           6
#define OPENING                 7
#define RETRYING		8
#define REWINDING		9
#define OPEN                   	10

/* Flags defs */
#define SENSING			0x10

/* un_misc defs */
#define FIXED_BLOCK_MODE	0x1	/* Drive has been put in
					   fixed block mode by user */
#define NO_DVMA			0x2	/* VME <-> VME xfer; no DVMA */

/*
 * stcunitptr() -- Return a pointer to this unit's unit structure.
 */
stcunitptr(md)
	register struct mb_device *md;
{
    EPRINTF(EL_DEBUG) ("stcunitptr:\n");
    return ((md->md_unit < NSTC) ? (int) &stcunits[md->md_unit] : 0);
}

/*
 * stctimer() -- Kick device when necessary.
 */
static
stctimer(foo)
{
    register struct scsi_stc *dstc;
    register struct scsi_unit *un;
    register struct scsi_ctlr *c;
    register u_int  unit;
    int s;

    for (unit = 0; unit < nSTC; unit++) {
	un = &stcunits[unit];
	dstc = &sstc[unit];
	c = un->un_c;

	if (c && ((dstc->un_openf & STATE_MASK) > CLOSED)) {
	    s = splr(pritospl(un->un_mc->mc_intpri));
	    (*un->un_c->c_ss->scs_start) (un);

	    if ((dstc->un_timeout > 0) &&
		(--dstc->un_timeout == 0)) {
		/* Process command timeout for I/O operations */
		printf("stc%d:  stctimer: I/O request timeout: cmd=0x%x\n", 	
		       unit, un->un_cmd);
		if ((*c->c_ss->scs_deque) (c, un)) {
		    /* Can't Find CDB I/O request to kill, help! */
		    printf("stc%d:  stctimer: can't abort request\n",
			   unit);
		    (*un->un_c->c_ss->scs_reset) (un->un_c, 0);
		    dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | CLOSED;
		}
	    } 
	    (void) splx(s);
	}
    }
    /* Restart ourselves */
    timeout(stctimer, NULL, hz);
}

/*
 * stcattach() -- Attach device (boot time).  Initialize data structures and
 * allocate inquiry/mode_select/mode_sense/sense buffer.
 * The device isn't actually initialized at attach time, we wait until
 * we do an open.
 */
stcattach(md)
    register struct mb_device *md;
{
    register struct scsi_unit *un;
    register struct scsi_stc *dstc;
    int             stcsimple();
    register u_long ul;
    register int    unit;

    EPRINTF(EL_DEBUG) ("stcattach:\n");
    un = &stcunits[md->md_unit];
    dstc = &sstc[md->md_unit];
    unit = STCNUM(un);
    /*
     * link in, fill in unit struct.
     */
    
    un->un_md = md;
    un->un_mc = md->md_mc;
    un->un_unit = md->md_unit;
    un->un_target = TARGET(md->md_slave);
    un->un_lun = LUN(md->md_slave);
    un->un_ss = &scsi_unit_subr[TYPE(md->md_flags)];
    un->un_blkno = 0;
    un->un_count = 0;
    un->un_present = 0;
    un->un_flags = SC_UNF_NO_DISCON; /* disable disconnects */

    dstc->un_bufcnt = 0;	/* No I/O request in que */
    dstc->un_timeout = NO_TIMEOUT; /* Disable timeouts */
    dstc->un_err_blkno = 0;
    dstc->un_err_resid = 0;
    dstc->un_status = 0;
    dstc->un_retries = 0;
    dstc->un_openf = CLOSED;
    dstc->un_flags = SC_UNF_NO_DISCON; /* Turned off later if we can */
    dstc->un_ctype = STC_TYPE_INVALID; /* Set drive defaults */
    dstc->un_last_cmd = 0;
    dstc->un_eof = 0;
    dstc->un_eot = 0;
    dstc->un_misc = 0;
    dstc->stc_wsel = (struct proc *) 0;
    dstc->stc_state = 0;
    dstc->p_pgrp = 0;
    dstc->un_has_icrc = 0;
    dstc->un_icrc_on = 0;
    bzero(dstc->un_err_sense, SAVED_SENSE_LEN);

    /*
     * Allocate space for mode_select/inquiry/request_sense data in data
     * in Multibus memory.  Be sure to allocate sufficient space to force
     * alignment to a longword boundary, in case it's not there already.
     */

    ul = (u_long) rmalloc(iopbmap, (long) (DEV_BSIZE + 4));
    if (ul == NULL) {
	printf("stcd%d: stcattach: no scratch memory!\n",
	       STCUNIT(un->un_unit));
	return;
    }
    while ((u_int) ul & 0x03)
	((u_int) ul)++;
    dstc->un_sense = (int) ul;
    dstc->un_senselen = DEV_BSIZE;

    /*
     * Each stc unit will have its own DVMA buffer.  The buffer is used
     * to hold inquiry data, mode select/sense, and request sense info.
     * Needless to say we keep this buffer forever.
     */
    EPRINTF(EL_DEBUG) ("stc%d: stcattach: buffer= 0x%x dstc= 0x%x un= 0x%x\n",
		       unit, (int) ul, dstc, un);
    if (! stc_timer_started) {
	stc_timer_started++;
	timeout(stctimer, NULL, hz);
    }
    if (un->un_c->c_flags & SCSI_BIG_DMA_OK)
      stc_max_xfr = STC_MAX_XFR;
    else
      stc_max_xfr = 63 * 1024;

    printf("stc%d: CMIOP StorageTek driver rev $Revision: 1.39 $\n",unit);
}

/*
 * stcinit() -- Initialize stc device (called by stcopen and stcattach).
 * Check if device on-line and identify who it is.
 */
static
stcinit(un, dev)
struct scsi_unit *un;
dev_t           dev;
{
    register struct scsi_stc *dstc;
    register struct mb_device *md;
    register struct scsi_inquiry_data *sid;
    register int    unit;
    int err;

    unit = STCNUM(un);
    md = un->un_md;
    dstc = &sstc[md->md_unit];

    EPRINTF(EL_MOREDEBUG) ("stc%d: stcinit: un= 0x%x, un->un_c= 0x%x\n",
			   unit, un, un->un_c);

    /*
     * If first time for drive do a full initialization pass to check it
     * out. If we already know who it is, use the short check.
     */
    if ((dstc->un_ctype == STC_TYPE_INVALID) ||	un->un_present == 0)
	dstc->un_ctype = STC_TYPE_DEFAULT; /* Set drive defaults */

    /*
     * Do an inquiry command and determine type of controller.
     * We only do this as long as we don't know what sort of device it is.
     */
    dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | OPENING;

    if (dstc->un_ctype == STC_TYPE_DEFAULT) {
      /*
       * If this is the first time that this device has been opened, we'll
       * need to figure out what type of stc drive it is.  After that we'll
       * always remember it.
       */
      if (err = stccmd(dev, SC_INQUIRY, (int) sizeof(struct scsi_inquiry_data), 0,
		       (char *) dstc->un_sense, un)) {
	
	/* 
	 * Inquiry didn't work, try again, due to Ciprico brain damage
	 */
	if (err = stccmd(dev, SC_INQUIRY, (int) sizeof(struct scsi_inquiry_data), 0,
			 (char *) dstc->un_sense, un)) {
	  
	  dstc->un_ctype = STC_TYPE_INVALID;
	  dstc->un_openf = dstc->un_openf & ~STATE_MASK | CLOSED;
	  return (err);
	  
	}
      }
      
      /*
       * After a reboot, the ciprico frequently needs to be
       * frobbed twice before it responds appropriately
       */
      err = stccmd(dev, SC_INQUIRY, (int) sizeof(struct scsi_inquiry_data), 0,
		   (char *) dstc->un_sense, un);
      
      /* 
       * Store drive characteristics. 
       */
      sid = (struct scsi_inquiry_data *) dstc->un_sense;
      
      EPRINTF(EL_DEBUG) ("stc%d: stcinit: checking inquiry data\n", unit);
      IFLEVEL(EL_MOREDEBUG)
	stc_print_buffer(sid, sizeof(struct scsi_inquiry_data));
      
      /*
       * First make sure that it is a tape unit
       */
      if ((sid->dtype != SCSI_TAPE) && (sid->dtype != SCSI_TAPELOADER)) {
	printf("\nstc%d: non tape device! (type 0x%x)\n", unit, sid->dtype);
	dstc->un_ctype = STC_TYPE_INVALID;
	dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | CLOSED;
	return (ENXIO);
      }
      /*
       * Some sort of tape drive 
       */
      if (bcmp(sid->vid, "STK", 3) == 0) {
	/* 
	 * Storage Tek 
	 */
	if (bcmp(sid->pid, "4980", 4) == 0) {
	  
	  if (sid->dtype == SCSI_TAPE)
	    dstc->un_ctype = STC_TYPE_4980;
	  else
	    dstc->un_ctype = STC_TYPE_4980_SK;
	  
	  if (sid->ucode_rev[0] & ~0x2)
	    dstc->un_has_icrc = 1;
	  
	  printf("\nstc%d: StorageTek 4980%s%s\n", 
		 unit,
		 (sid->dtype == SCSI_TAPELOADER) ?
		 ", stacker installed" : "",
		 (dstc->un_has_icrc) ?
		 ", ICRC installed" : "");
	}
	else if (bcmp(sid->pid, "2980", 4) == 0) {
	  if (sid->dtype == SCSI_TAPE) {
	    dstc->un_ctype = STC_TYPE_2980;
	    printf("\nstc%d: StorageTek 2980\n", unit);
	  }
	  
	  
	}else{
	  printf("stc%d: Unknown StorageTek device\n", unit);
	  stc_print_buffer(sid, sizeof(struct scsi_inquiry_data));
	}
	dstc->un_flags &= ~SC_UNF_NO_DISCON ; /* enable disconnect/reconnect */
      } else {
	printf("stc%d: Unknown tape device\n", unit);
	stc_print_buffer(sid, sizeof(struct scsi_inquiry_data));
      }
    }
    
    /*
     * Set up the drive.
     * Do one test unit ready first, to clear any 
     * RESET condition the drive may have saved, possibly
     * messing up the following "mode select".  Then 
     * enable buffered, variable block mode, and
     * do a TEST_UNIT_READY to get the sense key updated.
     */
    (void) stccmd(dev, STC_TEST_UNIT_READY,
		  1, 0, (caddr_t) NULL, un);

    (void) stc_set_bsize(dstc, dev, un, 0);   /* Select variable block mode */
    dstc->un_misc &= ~FIXED_BLOCK_MODE;

    (void) stccmd(dev, STC_TEST_UNIT_READY,
		  1, 0, (caddr_t) NULL, un);

    /*
     * Drive is ready to go.  Set mode to OPEN to signify the fact.
     * Return successful status to stcopen.
     */
    un->un_present = 1;
    dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | OPEN;

    return (0);
}

/*
 * Set the drive up for a particular block size
 */
static
stc_set_bsize(dstc, dev, un, size)
struct scsi_stc *dstc;
dev_t dev;
struct scsi_unit *un;
int size;
{
    struct stc_mode_params *sm;
    struct stc_mode_blk_desc *sb;
    int err;

    bzero(dstc->un_sense, 13); 
    sm = (struct stc_mode_params *) dstc->un_sense;
    sb = (struct stc_mode_blk_desc *) (dstc->un_sense + 4);

    sm->mode_buffered = use_buffered_mode;

    if( dstc->un_has_icrc )
      sm->mode_desc_len = 9;
    else
      sm->mode_desc_len = 8;
    sb->mode_icrc = dstc->un_icrc_on;
    sb->blk_len_high = (size >> 16) & 0xff;
    sb->blk_len_mid = (size >> 8) & 0xff;
    sb->blk_len_low = size & 0xff;

    if( dstc->un_has_icrc )
      err = stccmd(dev, STC_MODE_SELECT, 13,
		   0, (char *)dstc->un_sense, un);
    else
      err = stccmd(dev, STC_MODE_SELECT, 12,
		   0, (char *)dstc->un_sense, un);

    if (!err)
      {
	if (size) {
	    EPRINTF(EL_DEBUG) ("stc%d: fixed block (%d bytes) mode.\n",
			       STCUNIT(dev), size);
	} else {
	    EPRINTF(EL_DEBUG) ("stc%d: variable block mode.\n",
			       STCUNIT(dev));
	}

	dstc->un_bsize = size;
      }
    else
      EPRINTF(EL_DEBUG) ("stc%d: set fixed block mode to %d bytes failed.\n",
			 STCUNIT(dev), size);

    return (err);
}

/*
 * stcopen() -- This is an exclusive open device.
 */
stcopen(dev, flag)
	register dev_t  dev;
	int             flag;
{
    struct scsi_unit *un;
    register u_int  unit;
    register int    s;
    register int    status;
    struct scsi_stc *dstc;
    int             stccmd();

    EPRINTF(EL_DEBUG) ("stcopen:\n");
    unit = STCUNIT(dev);
    if (unit >= NSTC) {
	EPRINTF(EL_DEBUG) ("stc%d: stcopen: invalid unit\n", unit);
	return (ENXIO);
    }
    un = &stcunits[unit];
    dstc = &sstc[unit];
    EPRINTF(EL_DEBUG) ("stc%d: stcopen: un= 0x%x, un->un_mc= 0x%x, un->un_c= 0x%x\n",
		       unit, un, un->un_mc, un->un_c);

    if (un->un_mc == 0 || un->un_c == 0) {
	return (ENXIO);
    }
    /*
     * Can't open a drive that's already open.  If closed, go
     * ahead with open.  If open, return busy.  Note, raise interrupt
     * priority high enough to prevent I/O requestor from being swapped
     * while we're checking.
     */
    s = splr(pritospl(un->un_mc->mc_intpri));
    if ((dstc->un_openf & STATE_MASK) > CLOSING) {
	(void) splx(s);
	EPRINTF(EL_DEBUG) ("stc%d: stcopen BUSY: open state= %d\n",
			   unit, dstc->un_openf);
	return (EBUSY);
    } 

    /*
     * If closing, sleep until closed.
     */
    while ((dstc->un_openf & STATE_MASK) == CLOSING) {
      EPRINTF(EL_DEBUG) ("stcopen: waiting for device closure; sleeping on 0x%x\n",&dstc->un_openf);
      sleep((caddr_t) &dstc->un_openf, PRIBIO);
    }
    EPRINTF(EL_DEBUG) ("stcopen: opening device: state was %x\n", dstc->un_openf);
    dstc->un_openf = dstc->un_openf & ~STATE_MASK | OPENING;


    /*
     * Open stc drive. Also, make sure it's online. Note, this will also
     * set stcintr state machine to open state.
     */
    if ((status = stcinit(un, dev)) != 0) {
      (void) splx(s);
      return (status);
    }

    (void) splx(s);
    dstc->p_pgrp = (u.u_procp)->p_pid;

    return (0);
}

/*
 * stcclose() --
 */
/* ARGSUSED */
stcclose(dev, flag)
    register dev_t  dev;
    int flag;
{
    register struct scsi_unit *un;
    register struct scsi_stc *dstc;
    int             unit, err = 0;
    int s;

    unit = STCUNIT(dev);
    un = &stcunits[STCUNIT(dev)];
    dstc = &sstc[STCUNIT(dev)];

    /*
     * Wait until request_sense interrrupt is
     * taken, if one is pending.
     */
    while (dstc->un_openf & SENSING)
      ;

    EPRINTF(EL_DEBUG) ("stcclose:\n");
    /*
     * If we're closing or closed already, something majorly went wrong.
     */
    if ((dstc->un_openf & STATE_MASK) <= CLOSING) {
	u.u_error = EIO;
	return(EIO);
    }
    /*
     * If the last operation was a write,
     * write a tape mark
     */
    if (dstc->un_last_cmd == STC_WRITE)
	err = stccmd(dev, STC_WRITE_FILEMARKS, 1, 0, (caddr_t)NULL, un);

    /*
     * If rewind tape, set closing driver state, rewind on close,  and
     * allow stcintr to close the driver down.  Otherwise, now close the
     * driver down.
     */
    if ((minor(dev) & T_NOREWIND) == 0) {
	EPRINTF(EL_DEBUG) ("stc%d: stcclose:  rewinding...\n", unit);
	s = splr(pritospl(un->un_mc->mc_intpri));
	if ((dstc->un_openf & STATE_MASK) != REWINDING)
	  {
	    (void)splx(s);
	    err = stccmd(dev, SC_REWIND, -1, 0, 0, un);
	  }
	dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | CLOSING;
	dstc->un_eof = 0;
	dstc->un_eot = 0;
	(void)splx(s);
    } else {
      s = splr(pritospl(un->un_mc->mc_intpri));
      if ((dstc->un_openf & STATE_MASK) == REWINDING)
	dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | CLOSING;
      else
	dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | CLOSED;
      (void)splx(s);
    }

    dstc->p_pgrp = 0;
    dstc->stc_state = 0;

    EPRINTF(EL_DEBUG) ("stc%d: stcclose: retries= %d\n",
		       unit, dstc->un_retries);
    return (err);
}

/*
 * stccmd() --
 * "addr" MUST be in DVMA space.
 */
stccmd(dev, cmd, count, block, addr, un)
    dev_t           dev;
    u_int           cmd, count, block, addr;
    struct scsi_unit *un;
{
    register struct buf *bp;
    register int    s, error;
    register u_int  unit;
    register struct scsi_stc *dstc;

    EPRINTF(EL_DEBUG) ("stccmd:\n");
    unit = STCUNIT(dev);
    dstc = &sstc[unit];
    bp = &un->un_sbuf;

    s = splr(pritospl(un->un_mc->mc_intpri));

    /*
     * Wait for un_sbuf to be available
     */
    while (bp->b_flags & B_BUSY) {
	EPRINTF(EL_DEBUG) ("stccmd: waiting for buf\n");
	bp->b_flags |= B_WANTED;
	(void) sleep((caddr_t) bp, PRIBIO);
    }

    bp->b_flags = B_BUSY | B_READ | B_WANTED;
    (void) splx(s);

    un->un_scmd = cmd;
    bp->b_dev = dev;
    bp->b_bcount = count;
    bp->b_blkno = block;
    if (addr)
	bp->b_un.b_addr = (caddr_t)((int) addr - (int) DVMA);
    else
	bp->b_un.b_addr = NULL;
    stcstrategy(bp);

    /*
     * In case of rewind or unload, don't wait.
     */
    if (cmd == SC_REWIND || cmd == STC_UNLOAD) {
	return (NO_ERROR);
    }
    s = splr(pritospl(un->un_mc->mc_intpri));
    while ((bp->b_flags & B_DONE) == 0) {
	EPRINTF(EL_DEBUG) ("stccmd: waiting for completion\n");
	(void) sleep((caddr_t) bp, PRIBIO);
    }
    error = geterror(bp);
    /*
     * In case of unload, wait an additional
     * 11 seconds until we're sure the tape
     * is actually unloaded, unless we're asynchronous.
     */
    if (cmd == STC_UNLOAD && error == 0 &&
	!(dstc->stc_state & STC_ASYNC))
      {
	int i = 0;
	while (i<12)
	  {
	    sleep(&lbolt,PZERO-1);
	    i++;
	  }
      }
    bp->b_flags &= ~B_BUSY;
    wakeup((caddr_t) bp);
    (void) splx(s);

    return (error);
}

/*
 * stcstrategy() -- This routine is the high level interface to the device.
 * It performs reads and writes on the device using the buf as the method of
 * communication. It is called from the device switch for block operations
 * and via physio() for raw operations.  It is called at normal priority.
 */
stcstrategy(bp)
    register struct buf *bp;
{
    register struct scsi_unit *un;
    register struct mb_device *md;
    register struct scsi_stc *dstc;
    register struct buf *ap;
    register u_int  unit;

    unit = STCUNIT(bp->b_dev);
    if (unit >= NSTC) {
	EPRINTF(EL_DEBUG) ("stc%d: stcstrategy: invalid unit\n", unit);
	bp->b_error = EINVAL;
	bp->b_flags |= B_ERROR;
	iodone(bp);
	return;
    }

    un = &stcunits[unit];
    md = un->un_md;
    dstc = &sstc[unit];
    EPRINTF(EL_DEBUG) ("stc%d: stcstrategy: bp = 0x%x, un= 0x%x, un->un_c= 0x%x\n",
		       unit, bp, un, un->un_c);

    if ((!(bp->b_flags & B_READ)) &&
	dstc->un_eot) {
	/* Write at previously detected EOT, don't do it and return 0 */
        EPRINTF(EL_DEBUG) ("stc%d: at EOT; write command not executed\n",unit);
	bp->b_resid = bp->b_bcount;
	bp->b_error = 0;
	iodone(bp);
	return;
    }

    if (dstc->un_bsize &&
	(bp != &un->un_sbuf) && 
	(bp->b_bcount % dstc->un_bsize)) {
	printf("stc%d: invalid count %d in fixed block mode, blocksize %d\n",
	       unit, bp->b_bcount, dstc->un_bsize);
	bp->b_error = EINVAL;
	bp->b_flags |= B_ERROR;
	iodone(bp);
	return;
    }

    if ((dstc->un_openf & STATE_MASK) != OPEN && bp != &un->un_sbuf) {
      if ((dstc->un_openf & STATE_MASK) == REWINDING &&
	  !(dstc->stc_state & STC_ASYNC))
	{
	  while ((dstc->un_openf & STATE_MASK) == REWINDING)
	    {
	      EPRINTF(EL_DEBUG) ("stcstrategy: waiting for rewind completion; sleeping on 0x%x\n",&dstc->un_openf);
	      sleep((caddr_t) &dstc->un_openf, PRIBIO);
	    }
	}
      else
	{
	  EPRINTF(EL_DEBUG) ("stc%d: stcstrategy: device not open\n", unit);
	  bp->b_flags |= B_ERROR;
	  bp->b_flags |= ENXIO;
	  if( bp->b_flags & B_DONE )
	    EPRINTF(EL_INFO) ("stc%d: stcstrategy: dup biodone during rew\n",unit);
	  else
	    iodone(bp);
	  return;
	}
    }
    while (dstc->un_bufcnt >= MAXSTCBUF) {
	EPRINTF(EL_DEBUG) ("stc%d: stcstrategy: too busy, sleeping on bp= 0x%x\n",
			   unit, bp);
	(void) sleep((caddr_t) & dstc->un_bufcnt, PRIBIO);
    }
    dstc->un_bufcnt++;

    /*
     * Put the block at the end of the queue. Should probably have a
     * pointer to the end of the queue, but the queue can't get too long,
     * so the added code complexity probably isn't worth it.
     */
    EPRINTF(EL_DEBUG) ("stc%d: stcstrategy: queing bp= 0x%x\n", unit, bp);
    ap = (struct buf *) (&md->md_utab);
    while (ap->b_actf != NULL) {
	ap = ap->b_actf;
    }
    ap->b_actf = bp;
    bp->b_actf = NULL;

    /*
     * Call unit start routine to queue up device, if it currently isn't
     * queued.
     */
    (*un->un_c->c_ss->scs_ustart) (un);

    /* Call start routine to run the next SCSI command */
    (*un->un_c->c_ss->scs_start) (un);
}

/*
 * stcstart() -- Set up a transfer for the controller
 */
/* ARGSUSED */
stcstart(bp, un)
    register struct buf *bp;
    register struct scsi_unit *un;
{
    register struct scsi_stc *dstc;
    register int    unit;

    EPRINTF(EL_DEBUG) ("stcstart:\n");
    unit = STCUNIT(bp->b_dev);
    dstc = &sstc[STCUNIT(unit)];

    /*
     * Process internal I/O requests
     */
    if (bp == &un->un_sbuf) {
	EPRINTF(EL_DEBUG) ("stc%d: stcstart: internal I/O\n", unit);
	un->un_cmd = un->un_scmd;
	un->un_count = bp->b_bcount;
	un->un_blkno = bp->b_blkno;
	un->un_flags = 0;
	bp->b_resid = 0;
	return (1);
    }
    /*
     * Process file system I/O requests
     */
    if (bp == &un->un_rbuf)
	EPRINTF(EL_DEBUG) ("stc%d: stcstart: raw I/O\n", unit);
    else
	EPRINTF(EL_DEBUG) ("stc%d: stcstart: block mode I/O\n", unit);
    /* Check for read operation */
    if (bp->b_flags & B_READ) {
	un->un_cmd = SC_READ;
    } else {
	un->un_cmd = SC_WRITE;
    }
    /*
     * Finish processing read/write request
     */
    un->un_count = bp->b_bcount;
    bp->b_resid = 0;
    un->un_blkno = bp->b_blkno;
    un->un_flags = SC_UNF_DVMA;
    return (1);
}

/*
 * stcmkcdb() -- Make a command description block.
 */
stcmkcdb(un)
    register struct scsi_unit *un;
{
    register struct scsi_cdb *cdb;
    register struct scsi_stc *dstc;
    register int    unit;
    register struct buf *bp;
    char *cmdname;

    unit = STCUNIT(un->un_unit);
    dstc = &sstc[un->un_unit];
    bp = un->un_md->md_utab.b_forw;
    cdb = (struct scsi_cdb *) & un->un_cdb;

    bzero((caddr_t) cdb, sizeof(struct scsi_cdb));
    cdb->cmd = un->un_cmd;
    cdb->lun = un->un_lun;

    /*
     * Decode command name
     */
    if (un->un_cmd < MAX_STC_CMDS)
      cmdname = stc_cmds[un->un_cmd];
    else if ((un->un_cmd & 0x7f) < MAX_STC_INT_CMDS)
      cmdname = stc_int_cmds[un->un_cmd & 0x7f];
    else
      cmdname = "unknown cmd";

    EPRINTF(EL_DEBUG) ("stc%d stcmkcdb: command (0x%x): %s\n", 
		       unit, un->un_cmd, cmdname);

    switch (un->un_cmd) {
    case STC_REQUEST_SENSE:
	/*
	 * Internal only, uses sense buffer 
	 */
	dstc->un_timeout = REQUEST_SENSE_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = (int) dstc->un_sense - (int) DVMA;
    	if (un->un_flags & SC_UNF_BIG_DMA) {
	    *(int *)&un->un_dma_count = sizeof (struct stc_ext_sense); 
	} else
	    un->un_dma_count = sizeof (struct stc_ext_sense);
	bzero((caddr_t) (dstc->un_sense), dstc->un_senselen);
	FORMG0COUNT(cdb, sizeof (struct stc_ext_sense));
	return;
	/* break; */

    case STC_TEST_UNIT_READY:
	dstc->un_timeout = TEST_UNIT_READY_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
	    dstc->un_flags;
	un->un_dma_addr = un->un_dma_count = 0;
	break;

    case STC_REWIND:
	dstc->un_timeout = REWIND_TIMEOUT;
	cdb->t_code = 0; /* Wait for completion */
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
	    dstc->un_flags;
	un->un_dma_addr = un->un_dma_count = 0;
	break;

    case STC_READ_BLOCK_LIMITS:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = (int)bp->b_un.b_addr;
	un->un_dma_count = sizeof (struct stc_rbl_result);
	/* No allocation length! */
	break;
	
    case STC_LOAD_DISPLAY:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags ;
	un->un_dma_addr = (int)bp->b_un.b_addr;
	un->un_dma_count =	/*sizeof(struct stc_display_params)*/ 17;
	FORMG0COUNT(cdb,	/*sizeof(struct stc_display_params)*/ 17);
	break;

    case STC_READ:
	dstc->un_timeout = READ_WRITE_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = un->un_baddr;
    	if (un->un_c->c_flags & SCSI_BIG_DMA_OK) {
	    *(int *)&un->un_dma_count = un->un_count;
	    un->un_flags |= SC_UNF_BIG_DMA;
	} else
	    un->un_dma_count = un->un_count;
	if (dstc->un_bsize) {
	    register int count = un->un_count / dstc->un_bsize;
	    /* Use fixed block mode */
            cdb->tag = 1;    /* Put in fixed block mode */
            cdb->high_count = (count >> 16) & 0xff;
            cdb->mid_count = (count >> 8) & 0xff;
            cdb->low_count = count & 0xff;
	} else {
	    register int count = un->un_count;
	    /* Variable block mode */
            cdb->tag = 0;    /* Put in variable block mode */
            cdb->high_count = (count >> 16) & 0xff;
            cdb->mid_count = (count >> 8) & 0xff;
            cdb->low_count = count & 0xff;
	}
	break;

    case STC_WRITE:
	dstc->un_timeout = READ_WRITE_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
	    dstc->un_flags;
	un->un_dma_addr = un->un_baddr;
    	if (un->un_c->c_flags & SCSI_BIG_DMA_OK) {
	    *(int *)&un->un_dma_count = un->un_count;
	    un->un_flags |= SC_UNF_BIG_DMA;
	} else
	    un->un_dma_count = un->un_count;
	if (dstc->un_bsize) {
	    register int count = un->un_count / dstc->un_bsize;
	    /* Use fixed block mode */
/*	printf("building a write command, count=%d mode = F\n",count); */
            cdb->tag = 1;    /* Put in fixed block mode */
            cdb->high_count = (count >> 16) & 0xff;
            cdb->mid_count = (count >> 8) & 0xff;
            cdb->low_count = count & 0xff;
	} else {
	    register int count = un->un_count;
/*	printf("building a write command, count=%d mode = V\n",count); */
	    /* Variable block mode */
            cdb->tag = 0;    /* Put in variable block mode */
            cdb->high_count = (count >> 16) & 0xff;
            cdb->mid_count = (count >> 8) & 0xff;
            cdb->low_count = count & 0xff;
	}
	break;

    case STC_READ_REVERSE:
	dstc->un_timeout = READ_WRITE_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = un->un_baddr;
	un->un_dma_count = un->un_count;
	if (dstc->un_bsize) {
	    register int count = un->un_count / dstc->un_bsize;
	    /* Use fixed block mode */
	    cdb->tag = 1;
            cdb->high_count = (count >> 16) & 0xff;
            cdb->mid_count = (count >> 8) & 0xff;
            cdb->low_count = count & 0xff;
	} else {
	    register int count = un->un_count;
	    /* Variable block mode */
	    cdb->tag = 0;
            cdb->high_count = (count >> 16) & 0xff;
            cdb->mid_count = (count >> 8) & 0xff;
            cdb->low_count = count & 0xff;
	}
	break;

    case STC_WRITE_FILEMARKS:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags;
	cdb->tag = 1;	/* Immediate, flush buffer */
	cdb->high_count = (un->un_count >> 16) & 0xff;
	cdb->mid_count = (un->un_count >> 8) & 0xff;
	cdb->low_count = un->un_count & 0xff;
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	break;

    case STC_SPACE_RECORD:
	dstc->un_timeout = READ_WRITE_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags;
	cdb->high_count = ((un->un_count >> 16) & 0xFF);
	cdb->mid_count = ((un->un_count >> 8) & 0xFF);
	cdb->low_count = (un->un_count & 0xFF);
	cdb->cmd = STC_SPACE;
	cdb->t_code = SPACE_BLOCKS;
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	break;

    case STC_SPACE_FILE:
	dstc->un_timeout = READ_WRITE_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags;
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	cdb->cmd = STC_SPACE;
	cdb->t_code = SPACE_FILEMARKS;
	cdb->high_count = ((un->un_count >> 16) & 0xFF);
	cdb->mid_count = ((un->un_count >> 8) & 0xFF);
	cdb->low_count = (un->un_count & 0xFF);
	break;

    case STC_INQUIRY:
	dstc->un_timeout = INQUIRY_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = (int)bp->b_un.b_addr;
	un->un_dma_count = sizeof (struct scsi_inquiry_data);
        FORMG0COUNT(cdb, sizeof (struct scsi_inquiry_data));
	break;

    case STC_RECOVER_BUFFERED_DATA:
	dstc->un_timeout = READ_WRITE_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = un->un_baddr;
	un->un_dma_count = un->un_count;
	cdb->high_count = (un->un_count >> 16) & 0xff;
	cdb->mid_count = (un->un_count >> 8) & 0xff;
	cdb->low_count = un->un_count & 0xff;
	break;

    case STC_MODE_SELECT:
	dstc->un_timeout = MODE_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
	    dstc->un_flags;
	un->un_dma_addr = (int)bp->b_un.b_addr;

	/*
	 * If we have the ICRC hardware installed, mode select
	 * commands are one byte longer...
	 */
	if( dstc->un_has_icrc ) {
	  un->un_dma_count = 13;
	  FORMG0COUNT(cdb, 13);
	} else {
	  un->un_dma_count = 12;
	  FORMG0COUNT(cdb, 12);
	}
	break;

    case STC_MODE_SENSE:
	dstc->un_timeout = MODE_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = (int)bp->b_un.b_addr;
	un->un_dma_count = sizeof (struct stc_mode_sense_data);
	FORMG0COUNT(cdb, sizeof (struct stc_mode_sense_data));
	break;

    case STC_ERASE:
	dstc->un_timeout = REWIND_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags;
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	cdb->tag = 1;		/* "Long" erase, do whole tape */
	break;

    case STC_LOAD:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags;
	cdb->g0_count0 = 1;     /* Set load bit */
	cdb->tag = 1; /* STC barfs if I tell it to wait.  Grumble */
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	break;

    case STC_UNLOAD:
	dstc->un_timeout = REWIND_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags;
	cdb->cmd = STC_LOAD;
	/* load bit */
	cdb->g0_count0 = 0;
	cdb->tag = 0; 		/* Don't return good status immediately */
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	break;

    case STC_RECIEVE_DIAG_RESULTS:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	FORMG0COUNT(cdb, dstc->un_senselen);
	un->un_dma_addr = (int)bp->b_un.b_addr;
	un->un_dma_count = un->un_count;
	break;

    case STC_SEND_DIAGNOSTIC:
	dstc->un_timeout = SELFTEST_TIMEOUT;
	cdb->g0_addr2 = SELF_TEST; /* set self test bit */
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
	    dstc->un_flags;
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	break;

    case STC_LOCATE:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_dma_addr = 0;
	un->un_dma_count = 0;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) | dstc->un_flags;
	cdb->tag = 5;
	/* 
	 * Use un_blkno for position:
	 * The following uses the Storage Tek doc as a guide - it contradicts the
	 * SUN macro definitions and naming scheme
	 */
	cdb->g1_addr3  = 0;				
	cdb->g1_addr2 = un->un_blkno >> 24;		/* MSB of address */
	cdb->g1_addr1  = (un->un_blkno >> 16) & 0xFF;	/* 2nd MSB of address */
	cdb->g1_addr0  = (un->un_blkno >> 8) & 0xFF; 	/* 3rd MSB of address */
	cdb->sg.g1.rsvd1  = un->un_blkno & 0xFF;	/* LSB of address */
	break;
	
    case STC_READ_POSITION:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_flags |= (SC_UNF_RECV_DATA | dstc->un_flags);
	un->un_dma_addr = (int)bp->b_un.b_addr;
	un->un_dma_count = sizeof(struct stc_position);
	/* No allocation length! */
	break;

    default:
	dstc->un_timeout = DEFAULT_TIMEOUT;
	un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
	    dstc->un_flags;
	printf("stc%d:  stcmkcdb: invalid command %x\n",
	       STCUNIT(un->un_unit), un->un_cmd);
	break;

    }
    /*
     * Save last command for stcintr error recovery
     */
    dstc->un_last_cmd = un->un_cmd;
}

/*
 * stcintr() --
 */
stcintr(c, resid, error)
    register struct scsi_ctlr *c;
    register u_int  error;
    int             resid;
{
    register struct scsi_unit *un;
    register struct buf *bp;
    register struct scsi_stc *dstc;
    register int    unit;

    un = c->c_un;
    bp = un->un_md->md_utab.b_forw;
    unit = STCUNIT(bp->b_dev);
    dstc = &sstc[unit];

    if (bp == NULL) {
	printf("stcintr stc%d: bp = NULL\n", unit);
	return;
    }
    dstc->un_timeout = NO_TIMEOUT; /* Disable time-outs */

    IFLEVEL(EL_DEBUG) {
	u_char *p = (u_char *)&un->un_scb;
	char *cmdname;

	if (un->un_cmd < MAX_STC_CMDS)
	  cmdname = stc_cmds[un->un_cmd];
	else if ((un->un_cmd & 0x7f) < MAX_STC_INT_CMDS)
	  cmdname = stc_int_cmds[un->un_cmd & 0x7f];
	else
	  cmdname = "unknown cmd";

	printf("stcintr stc%d: c= 0x%x, resid = 0x%x, error = 0x%x\n", 
	       unit, c, resid, error);
	printf("                un= 0x%x, bp= 0x%x\n", un, bp);
	printf("                cdb = \"%s\": ", cmdname);
	stc_print_buffer((u_char *) &(un->un_cdb), 10);
	printf("                dma count= %d, blk count= %d, ", 
	       (un->un_flags & SC_UNF_BIG_DMA) ?
	       *(int *)&un->un_dma_count : un->un_dma_count,
	       un->un_count);
	printf("dma addr=0x%x, blk addr = 0x%x\n",
	       un->un_dma_addr, un->un_baddr);
	printf("                scb = 0x%x, 0x%x, 0x%x\n", p[0], p[1], p[2]);
    }

    if (error || (dstc->un_openf & SENSING) || resid) {
	/*
	 * Special processing for driver command timeout errors.
	 * Also, log location stc died at.
	 */
	if (error == SE_TIMEOUT) {
	    char *cmdname;
	    if (un->un_cmd < MAX_STC_CMDS)
	      cmdname = stc_cmds[un->un_cmd];
	    else if ((un->un_cmd & 0x7f) < MAX_STC_INT_CMDS)
	      cmdname = stc_int_cmds[un->un_cmd & 0x7f];
	    else
	      cmdname = "unknown cmd";

	    EPRINTF(EL_RETRY) ("stc%d: timed out, cmd=0x%x <%s>\n", 
			       unit, un->un_cmd, cmdname);
	    dstc->un_status = SC_TIMEOUT;
	    dstc->un_err_resid = resid;
	    dstc->un_err_blkno = un->un_blkno;
	    bp->b_flags |= B_ERROR;
	    bp->b_error = EIO;
	    goto STCINTR_WRAPUP;
	}
	/*
	 * Special processing for scsi bus failures. Note, this error
	 * is implies a SCSI bus handshake failure.  SCSI may now be
	 * dead too.
	 */
	if (error == SE_FATAL) {
	    char *cmdname;
	    if (un->un_cmd < MAX_STC_CMDS)
	      cmdname = stc_cmds[un->un_cmd];
	    else if ((un->un_cmd & 0x7f) < MAX_STC_INT_CMDS)
	      cmdname = stc_int_cmds[un->un_cmd & 0x7f];
	    else
	      cmdname = "unknown cmd";

	    EPRINTF(EL_RETRY) ("stc%d: stcintr: scsi bus failure, cmd=0x%x <%s>\n",
			       unit, un->un_cmd, cmdname);
	    dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | CLOSED;
	    dstc->un_status = SC_FATAL;
	    dstc->un_err_resid = resid;
	    dstc->un_err_blkno = un->un_blkno;
	    bp->b_error = EIO;
	    bp->b_flags |= B_ERROR;
	    goto STCINTR_WRAPUP;
	}

	/*
	 * Processing for failed command -- do a request sense.
	 */
	if (un->un_scb.chk) {
	    stcintr_run_sense(dstc, un, resid);
	    return;
	}

	/*
	 * Are we completing a request sense?
	 * If so, clean up.
	 */
	if (dstc->un_openf & SENSING) {
	    stcintr_ran_sense(dstc, un, &resid);
	    /* ... roll on along ... */
	}

	/*
	 * Interpret error based on sense information.
	 */
	switch (stcintr_error(c, un, bp, dstc, &resid, error)) {
	case REAL_ERROR:	/* This error is FATAL ! */
	    EPRINTF(EL_DEBUG) ("stc%d: stcintr: real error handling\n", unit);
	    goto STCINTR_WRAPUP;

	case PSEUDO_ERROR:	/* This error really isn't an error. */
	    EPRINTF(EL_DEBUG) ("stc%d: stcintr: psuedo error handling\n", unit);
	    goto STCINTR_SUCCESS;

	case MORE_PROCESSING:
	    EPRINTF(EL_DEBUG) ("stc%d: stcintr: more processing\n", unit);
	    return;

	default:
	    /*
	     * If you get here, there's a bug in the driver
	     */
	    printf("stc%d: stcintr: invalid error\n", unit);
	    goto STCINTR_WRAPUP;
	}
	/* NOTREACHED */
    } 

 STCINTR_SUCCESS:
    /*
     * Final processing for successful commands
     */

    switch (un->un_cmd) {
	/*
	 * For internal stuff don't mess with un_status and un_retries
	 */
    case STC_REQUEST_SENSE:
    case STC_INQUIRY:
    case STC_MODE_SENSE:
    case STC_MODE_SELECT:
	break;
    default:
	/*
	 * Command worked.  Reset status
	 */
	dstc->un_retries = 0;
	dstc->un_status = 0;
	break;
    }

 STCINTR_WRAPUP:
    /*
     * Wrap up command processing for a command that has run to
     * completion (successfully or otherwise).
     */

    if ((bp == &un->un_sbuf &&
	 ((un->un_flags & SC_UNF_DVMA) == 0)) ||
	(dstc->un_misc & NO_DVMA))
      (*c->c_ss->scs_done) (un->un_md);
    else
      mbudone(un->un_md);

    un->un_flags &= ~SC_UNF_DVMA;

    if (dstc->un_bufcnt-- >= MAXSTCBUF) {
	wakeup((caddr_t) & dstc->un_bufcnt);
    }

    if (un->un_cmd == SC_REWIND || un->un_cmd == STC_UNLOAD) {
	/* a non-waited for rewind has finished */
	dstc->un_eof = 0;
	dstc->un_eot = 0;
    	bp->b_flags &= ~B_BUSY;
	wakeup((caddr_t) bp);
	if ((dstc->un_openf & STATE_MASK) == CLOSING) {
	  dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | CLOSED;
	  wakeup((caddr_t) &dstc->un_openf);
	}
	if ((dstc->un_openf & STATE_MASK) == REWINDING) {
	  dstc->un_openf = (dstc->un_openf & ~STATE_MASK) | OPEN;
	  wakeup((caddr_t) &dstc->un_openf);
	}
    }

    /*
     * If the user requested asynchronous notification, send their
     * process a signal, and turn on a bit in the tape descriptor
     * to remind us later, if the user asks, that we did indeed
     * wake them up with a signal...
     */
    if ((dstc->stc_state & STC_ASYNC) && dstc->p_pgrp)
      {
	gsignal(dstc->p_pgrp, SIGIO);
	dstc->stc_state &= ~STC_ASYNC;
	dstc->stc_state |= STC_DEVDONE;
      }
}
/*
 * stc_print_key() -- Return the text string associated with the sense key
 * value.
 */
static char    *
stc_print_key(key_code)
	register u_char key_code;
{
	static char    *unknown_key = "unknown key";
	if ((key_code > MAX_KEY_ERROR_STR - 1) ||
	    stc_key_error_str[key_code] == NULL) {

		return (unknown_key);
	}
	return (stc_key_error_str[key_code]);
}


/*
 * stcintr_error() -- Error decoder/handler.
 */
static
stcintr_error(c, un, bp, dstc, resid, error)
    register struct scsi_ctlr *c;
    register struct scsi_unit *un;
    register struct buf *bp;
    register struct scsi_stc *dstc;
    int            *resid;
    u_int           error;
{
    register struct scsi_ext_sense *ssd;
    register int    unit;

    EPRINTF(EL_DEBUG) ("stcintr_error:\n");
    unit = STCUNIT(bp->b_dev);
    ssd = (struct scsi_ext_sense *) dstc->un_sense;

    /*
     * Log error info for error reporting
     */
    dstc->un_err_resid = *resid;
    dstc->un_err_blkno = un->un_blkno;
    dstc->un_status = ssd->key;

    if (un->un_scb.chk) {

	if (STC_FILE_MARK(dstc, ssd)) {
	    EPRINTF(EL_DEBUG) ("stc%d: file mark\n", unit);
	    bp->b_error = 0;
            if (dstc->un_bsize)  /* Resid number of records */
	      {
		bp->b_resid = ((ssd->info_1 << 24) | (ssd->info_2 << 16) |
			       (ssd->info_3 << 8) | (ssd->info_4)) * dstc->un_bsize;
		if (bp->b_resid < bp->b_bcount)
		  dstc->un_eof = 1; /* so subsequent read will return zero */
	      }
            else
              bp->b_resid = (ssd->info_1 << 24) | (ssd->info_2 << 16) |
                            (ssd->info_3 << 8) | (ssd->info_4);
	    return (PSEUDO_ERROR);

	} else if (STC_END_OF_TAPE(dstc, ssd)) {
            if (dstc->un_bsize)  /* Resid number of records */
	      {
		bp->b_resid = ((ssd->info_1 << 24) | (ssd->info_2 << 16) |
			       (ssd->info_3 << 8) | (ssd->info_4)) * dstc->un_bsize;
		if (bp->b_resid < bp->b_bcount)
		  dstc->un_eof = 1;
	      }
            else
              bp->b_resid = (ssd->info_1 << 24) | (ssd->info_2 << 16) |
                            (ssd->info_3 << 8) | (ssd->info_4);
	    bp->b_error = 0;
	    dstc->un_eot = 1;
	    EPRINTF(EL_DEBUG) ("stc%d: logical end of tape on %s of 0x%x bytes\n",
		   unit, (bp->b_flags & B_READ) ? "read" : "write",
		   bp->b_bcount);
	    return (PSEUDO_ERROR);

	} else if (STC_LENGTH(dstc, ssd)) {
	  int real_length;
	  
	  if (ssd->adr_val) {
	    real_length = 
	      (ssd->info_1 << 24) |
		(ssd->info_2 << 16) |
		  (ssd->info_3 << 8) |
		    ssd->info_4;
	    if (dstc->un_bsize)
	      real_length = real_length * dstc->un_bsize;
            if (real_length >= 0) {
	      bp->b_resid = real_length;
	      EPRINTF(EL_DEBUG) ("stc%d: length error, resid = 0x%x, info=0x%x\n",
	                         unit, *resid, real_length );
	      bp->b_error = 0;
	      return (PSEUDO_ERROR);
	    }
	  } 
	  EPRINTF(EL_RETRY) ("stc%d: length error\n", unit);
	  bp->b_error = EIO;
	  bp->b_flags |= B_ERROR;
	  return (REAL_ERROR);

	    /*
	     * Have an sense key error we aren't decoding.
	     */
	} else if (STC_CORRECTABLE(dstc, ssd)) {
	    /*
	     * A block had to be read/written more than once but was
	     * successfully read/written. Just bump stats and consider
	     * the operation a success.
	     */
	    EPRINTF(EL_RETRY) ("stc%d: soft error\n", unit);
	    dstc->un_retries++;
	    return (PSEUDO_ERROR);

	} else if (STC_NOT_READY(dstc, ssd)) {
	    IFLEVEL(EL_DEBUG) {
		stc_error_message(un, dstc, "not ready");
	    }
	    bp->b_error = EIO;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	} else if (STC_MEDIA_ERROR(dstc, ssd)) {
	    stc_error_message(un, dstc, "media error");
	    bp->b_error = EIO;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	} else if (STC_ILLEGAL(dstc, ssd)) {
	    stc_error_message(un, dstc, "illegal command");
	    bp->b_error = EINVAL;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	} else if (STC_HARDWARE(dstc, ssd)) {
	    stc_error_message(un, dstc, "hardware error");
	    bp->b_error = EIO;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	} else if (STC_RESET(dstc, ssd)) {
	    EPRINTF(EL_DEBUG) ("stc%d: reset\n", unit);
	    dstc->un_retries++;
	    dstc->un_eof = 0;
	    dstc->un_eot = 0;
	    return (PSEUDO_ERROR);

	} else if (STC_WRITE_PROT(dstc, ssd) &&
		   (dstc->un_last_cmd == SC_WRITE)) {
	    EPRINTF(EL_RETRY) ("stc%d: write protected\n", unit);
	    bp->b_error = EROFS;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	} else if (STC_VOLUME_OVERFLOW(dstc, ssd)) {
	    EPRINTF(EL_RETRY) ("stc%d: physical end of tape\n", unit);
	    bp->b_error = EIO;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	} else if (STC_BLANK_CHECK(dstc, ssd)) {
	    EPRINTF(EL_RETRY) ("stc%d: blank check\n", unit);
	    bp->b_error = EIO;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	} else if (ssd->key != SC_NO_SENSE) {
	    stc_error_message(un, dstc, "undecoded sense key");
	    bp->b_error = EIO;
	    bp->b_flags |= B_ERROR;
	    return (REAL_ERROR);

	    /*
	     * Have a check condition error which we aren't
	     * decoding.
	     */
	} else {
	    stc_error_message(un, dstc, "Unknown check condition");
	    bp->b_flags |= B_ERROR;
	    bp->b_error = EIO;
	    return (REAL_ERROR);
	}

	/*
	 * Process reservation error.  Abort operation
	 */
    } else if (un->un_scb.busy && un->un_scb.is) {
	stc_error_message(un, dstc, "reservation conflict");
	bp->b_flags |= B_ERROR;
	bp->b_error = EIO;
	return (REAL_ERROR);

	/*
	 * Process busy error. Try retries and hope that it'll be
	 * ready soon.  Note, this test must follow the reservation
	 * conflict error test.
	 */
    } else if (un->un_scb.busy) {
	stc_error_message(un, dstc, "busy");
	goto STCINTR_ERROR_RETRY;

	/*
	 * Check for host adapter error.
	 */
    } else if (error != NO_ERROR) {
	EPRINTF(EL_RETRY) ("stc%d: host adapter error, error= %d\n", unit, error);
	bp->b_flags |= B_ERROR;
	bp->b_error = EIO;
	return (REAL_ERROR);

	/*
	 * Have left over residue data from last command. 
	 */
    } else if (*resid != NO_ERROR) {
	EPRINTF(EL_RETRY) ("stc%d: residue error, residue= 0x%x\n", unit, *resid);
	/*bp->b_resid = *resid;*/
	return (PSEUDO_ERROR);

	/*
	 * Have an unknown error.  Don't know what went wrong. If not
	 * a tape, do retries and hope this fixes it...
	 */
    } else {
	EPRINTF(EL_RETRY) ("stc%d: unknown error\n", unit);
	stc_error_message(un, dstc, "unknown error");
	bp->b_flags |= B_ERROR;
	bp->b_error = EIO;
	return (REAL_ERROR);
    }

 STCINTR_ERROR_RETRY:
    /*
     * Command failed, retry it.
     */
    if (dstc->un_retries++ < MAX_RETRIES) {
	EPRINTF(EL_DEBUG) ("stc%d: stcintr_error: un_retries=%d, un_cmd= 0x%x\n",
			   unit, dstc->un_retries, un->un_cmd);
	dstc->un_openf = dstc->un_openf & ~STATE_MASK | RETRYING;
	EPRINTF(EL_DEBUG) ("stc%d: stcintr_error: calling stcmkcdb\n", unit);
	stcmkcdb(un);
	if ((*c->c_ss->scs_cmd) (c, un, 1) != 0) {
	    printf("stc%d:  stcintr_error: retry failed\n", unit);
	    return (REAL_ERROR);
	}
	return (MORE_PROCESSING);

	/*
	 * Retries exhausted, die!
	 */
    } else {
	/*
	 * complete failure
	 */
	dstc->un_openf = dstc->un_openf & ~STATE_MASK | OPEN;
	dstc->un_retries = 0;
	stc_error_message(un, dstc, "complete failure");
	bp->b_flags |= B_ERROR;
	bp->b_error = EIO;
	return (REAL_ERROR);
    }
}

/*
 * stcintr_run_sense() -- Command failed, need to run a request sense command
 * to determine why.
 */
static
stcintr_run_sense(dstc, un, resid)
    register struct scsi_stc *dstc;
    register struct scsi_unit *un;
    register int    resid;
{

    EPRINTF(EL_DEBUG) ("stcintr_run_sense:\n");
    /*
     * Save away old command state.
     */
    un->un_saved_cmd.saved_cmd = un->un_cmd;
    un->un_saved_cmd.saved_scb = un->un_scb;
    un->un_saved_cmd.saved_cdb = un->un_cdb;
    un->un_saved_cmd.saved_resid = resid;
    un->un_saved_cmd.saved_dma_addr = un->un_dma_addr;
    un->un_saved_cmd.saved_dma_count = un->un_dma_count;

    /*
     * Note that stcstart will call stcmkcdb, which will notice that the
     * flag is set and not do the copy of the cdb, doing a request sense
     * rather than the normal command.
     */
    un->un_flags |= SC_UNF_GET_SENSE;
    dstc->un_openf |= SENSING;
    un->un_cmd = STC_REQUEST_SENSE;
    (*un->un_c->c_ss->scs_go) (un->un_md);
}

/*
 * stcintr_ran_sense() -- Cleanup after running request sense command to see
 * why the real command failed.
 */
static
stcintr_ran_sense(dstc, un, resid)
    register struct scsi_stc *dstc;
    register struct scsi_unit *un;
    int *resid;
{
    un->un_flags &= ~SC_UNF_GET_SENSE;
    dstc->un_openf &= ~SENSING;

    /*
     * Save some of the sense info.
     */
    dstc->un_err_sense_len = un->un_dma_count - *resid;
    dstc->un_err_resid = un->un_saved_cmd.saved_resid;
    dstc->un_err_blkno = un->un_blkno;
    bcopy((char *) dstc->un_sense,  dstc->un_err_sense, 
	  dstc->un_err_sense_len);

    /*
     * Restore failed command which caused request sense to be run.
     */
    un->un_cmd = un->un_saved_cmd.saved_cmd;
    un->un_scb = un->un_saved_cmd.saved_scb;
    un->un_cdb = un->un_saved_cmd.saved_cdb;
    un->un_dma_addr = un->un_saved_cmd.saved_dma_addr;
    un->un_dma_count = un->un_saved_cmd.saved_dma_count;
    *resid = un->un_saved_cmd.saved_resid;
}

/*
 * stc_print_buffer() --
 */
static
stc_print_buffer(y, count)
	register u_char *y;
	register int    count;
{
	register int    x;

	for (x = 0; x < count; x++)
		printf("%x  ", *y++);
	printf("\n");
}

/*
 * stc_error_message() -- Print the sense key and secondary error codes and
 * dump out the sense bytes.
 */
static
stc_error_message(un, dstc, s)
    register struct scsi_unit *un;
    register struct scsi_stc *dstc;
    char *s;
{
    register struct scsi_ext_sense *ssd;
    register char  *cmdname;
    register int    i, unit;
    register int    error_code;

    unit = STCUNIT(un->un_unit);

    IFLEVEL(EL_MOREDEBUG) {
	printf ("stc_error_message:\n");
	printf("stc%d: failed cmd =  ", STCUNIT(un->un_unit));
	stc_print_buffer((u_char *) & (un->un_cdb), sizeof(struct scsi_cdb));
	printf("\n");
    }
    /*
     * Decode command name
     */
    if (un->un_cmd < MAX_STC_CMDS)
      cmdname = stc_cmds[un->un_cmd];
    else if ((un->un_cmd & 0x7f) < MAX_STC_INT_CMDS)
      cmdname = stc_int_cmds[un->un_cmd & 0x7f];
    else
      cmdname = "unknown cmd";

    ssd = (struct scsi_ext_sense *) dstc->un_err_sense;
    error_code = ssd->error_code;

    printf("stc%d %s: command (0x%x): %s\n", unit, s, un->un_cmd, cmdname);
    printf("\tkey (0x%x): %s, ", ssd->key, stc_print_key(ssd->key));
    printf("<%s %s %s>\n",
	   ssd->fil_mk ? "FILE_MARK" : "", 
	   ssd->eom ? "EOM" : "", 
	   ssd->ili ? "ILLEGAL_LENGTH" : "");
    printf("\tinformation bytes %x %x %x %x\n",
	   ssd->info_1, ssd->info_2, ssd->info_3, ssd->info_4);
	
    /* Device specific stuff */
    switch (dstc->un_ctype) {
    case STC_TYPE_4980:
    case STC_TYPE_4980_SK:
	{
	    struct stc_ext_sense *es = (struct stc_ext_sense *)ssd;

	    printf("\tsecondary sense = 0x%x, 0x%x\n",
		   es->sec_sense_code, es->sec_sense_qual);
	    printf("\tfsc = 0x%x, 0x%x, 0x%x\n",
		   (es->fsc_1_high<<8)|es->fsc_1_low,
		   (es->fsc_2_high<<8)|es->fsc_2_low,
		   (es->fsc_last_high<<8)|es->fsc_last_low);
	}
	break;
    default:
	{
	    register u_char *cp = (u_char *) ssd;

	    printf("\terror code(0x%x)\n", ssd->error_code);
	    printf("\n\tsense = ");
	    for (i = 0; i < sizeof(struct scsi_ext_sense); i++)
		printf("%x  ", *cp++);
	    printf("\n");
	    break;
	}
    }
}

/*
 * stcread() --
 */
stcread(dev, uio)
	register dev_t  dev;
	register struct uio *uio;
{
  struct scsi_stc *dstc;

  dstc = &sstc[STCUNIT(dev)];

  EPRINTF(EL_DEBUG) ("stcread:\n");
  if (dstc->un_eof)
    {
      dstc->un_eof = 0;
      return(0);
    }
  return (stcrw(dev, uio, B_READ));
}

/*
 * stcwrite() --
 */
stcwrite(dev, uio)
	register dev_t  dev;
	register struct uio *uio;
{
	int ret;
	EPRINTF(EL_DEBUG) ("stcwrite:\n");
	ret = stcrw(dev, uio, B_WRITE);
	return (ret);
}


#define MAXRECSIZE (64*1024)

/*
 * Figure out largest allowed transfer
 */
stcminphys(bp)
    register struct buf *bp;
{
  struct scsi_stc *dstc;
  struct scsi_unit *un;
  int max_transfer_size;
  
  dstc = &sstc[STCUNIT(bp->b_dev)];
  un = &stcunits[STCUNIT(bp->b_dev)];
  
  /*
   * Figure out the biggest chunk we can DMA.
   */
  if (dstc->un_misc & NO_DVMA)
    max_transfer_size = stc_max_xfr; /* HUGE DMA */
  else if (un->un_c->c_flags & SCSI_BIG_DMA_OK)
    max_transfer_size = 128*1024;
  else
    max_transfer_size = 63*1024;
  
  if (bp->b_bcount > max_transfer_size) {
    if (dstc->un_bsize)
      /* Fixed block mode */
      bp->b_bcount = (max_transfer_size/dstc->un_bsize) * dstc->un_bsize;
    else
      /* Variable block mode */
      bp->b_bcount = max_transfer_size;
  }
  
  /*
   * Now if this is a big transfer; put the drive in fixed block mode
   * at MAXRECSIZE byte records.  Arrange to switch back to variable
   * block mode for last transfer.
   */
  if (! (bp->b_flags & B_READ))
    {
      /* We're writing */
      
      if (dstc->un_bsize == 0 && bp->b_bcount > MAXRECSIZE)
	/* Big write, set fixed block mode */
	(void) stc_set_bsize(dstc, bp->b_dev, un, MAXRECSIZE);
      
      if (((dstc->un_misc & FIXED_BLOCK_MODE) == 0)
	  && dstc->un_bsize)
	{
	  /*
	   * Fixed block mode was automatically entered
	   * by the code above.  Handle count correctly.
	   */
	  if (bp->b_bcount < dstc->un_bsize)
	    {
	      /* Reset to variable mode for small transfer */
	      (void) stc_set_bsize(dstc, bp->b_dev, un, 0);
	    }
	  else
	    {
	      /* write an integral number of blocks */
	      bp->b_bcount = (bp->b_bcount / dstc->un_bsize) * dstc->un_bsize;
	    }
	}
    }
}

/*
 * stcrw() --
 */
stcrw(dev, uio, direction)
	register dev_t  dev;
	register struct uio *uio;
	int             direction;
{
  register struct scsi_unit *un;
  register struct scsi_stc *dstc;
  register u_int  unit;
  register struct iovec *iov;
  int pfnum;
  int ret = 0;
  
  EPRINTF(EL_DEBUG) ("stcrw:\n");
  unit = STCUNIT(dev);
  if (unit >= NSTC) {
    EPRINTF(EL_DEBUG) ("stc%d: stcrw: invalid unit %d\n", unit, unit);
    return (ENXIO);
  }
  un = &stcunits[unit];
  dstc = &sstc[unit];

  if (uio->uio_resid == 0)
    return (0);

  uio->uio_offset = 0;
  /* 
   * If this is a VME only transfer,
   * don't call physio to do it.
   * Just split it up into stc_max_xfr chunks
   * and call the strategy routine directly.
   */
  for (iov = uio->uio_iov; iov < &uio->uio_iov[uio->uio_iovcnt]; iov++) {
    register struct buf *bp;
    
    if (iov->iov_len == 0)
      continue;
    
    bp = &un->un_rbuf;
    /*
     * We need to at least fault the PTE for addr in
     * so we can see if it is in VME space.  But I can't figure
     * out how to fault just the PTE.  So we fault a whole page.
     * (But only one!) Sigh.
     */
    if(as_fault(u.u_procp->p_as, iov->iov_base, (u_int) 4,
		F_SOFTLOCK, direction == B_READ ? S_WRITE : S_READ))
      return (EFAULT);
    
    pfnum = hat_getkpfnum(iov->iov_base);
    (void) as_fault(u.u_procp->p_as, iov->iov_base, (u_int)4,
		    F_SOFTUNLOCK, direction == B_READ ? S_WRITE : S_READ);
    
    if ((pfnum & PG_TYPE) == PGT_VME_D32) {
      /*
       * VME only transfer, don't have to worry
       * about locking down pages, etc.
       * By avoiding a call to physio() we
       * can do bigger transfers.
       */
      dstc->un_misc |= NO_DVMA;
      EPRINTF(EL_DEBUG) ("stcrw: VME<->VME xfr\n");

      while (iov->iov_len > 0)
	{
	  int c, s;
	  
	  if(as_fault(u.u_procp->p_as, iov->iov_base, (u_int) 4,
		      F_SOFTLOCK, direction == B_READ ? S_WRITE : S_READ))
	      return (EFAULT);
    
	  (void) as_fault(u.u_procp->p_as, iov->iov_base, (u_int)4,
			  F_SOFTUNLOCK, direction == B_READ ? S_WRITE : S_READ);
    
	  s = splr(pritospl(un->un_mc->mc_intpri));
	  bzero(bp, sizeof (struct buf));
	  bp->b_error = 0;
	  bp->b_proc = u.u_procp;
	  bp->b_un.b_addr = iov->iov_base;
	  bp->b_flags = B_BUSY | B_PHYS | B_VME_ADDR | direction;
	  bp->b_dev = dev;
	  bp->b_blkno = 0;
	  bp->b_bcount = iov->iov_len;
	  ret = 0;
	  /*
	   * split up into stc_max_xfr
	   * units, and put STC in fixed block mode,
	   * if need be.
	   */
	  stcminphys(bp);
#if 0
	  if (bp->b_bcount > stc_max_xfr)
	    bp->b_bcount = stc_max_xfr;
#endif
	  c = bp->b_bcount;
	  
	  /* Do the I/O */
	  u.u_procp->p_flag |= SPHYSIO;
	  vac_flush(bp->b_un.b_addr, c);
	  stcstrategy(bp);
	  iowait(bp);
	  vac_flush(bp->b_un.b_addr, c);
	  u.u_procp->p_flag &= ~SPHYSIO;
	  c -= bp->b_resid;
	  splx(s);

	  iov->iov_base += c;
	  iov->iov_len -=c;
	  uio->uio_resid -= c;
	  uio->uio_offset += c;

	  if ((bp->b_flags & B_ERROR) && (ret = bp->b_error) == 0)
	    ret = EIO;

	  if (ret || bp->b_resid)
	    break;
	} /* end while(iov->iov_len) */
      if (ret || bp->b_resid)
	break;
    } else {
      /* Let physio deal with it */
      struct uio nuio;
      int s;
      
      EPRINTF(EL_DEBUG) ("stcrw: non-VME xfr\n");

      /* Important stuff */
      nuio.uio_iov = iov;
      nuio.uio_iovcnt = 1;
      nuio.uio_resid = iov->iov_len;
      
      /* Junk, may not be needed */
      nuio.uio_offset = 0;
      nuio.uio_segflg = uio->uio_segflg;
      nuio.uio_fmode = uio->uio_fmode;

      s = splr(pritospl(un->un_mc->mc_intpri));
      bzero(bp, sizeof (struct buf));
      ret = physio(stcstrategy, bp,
		   dev, direction, stcminphys, &nuio);
      splx(s);

      uio->uio_resid = nuio.uio_resid;
      if (ret)
	break;
    }
  }
  dstc->un_misc &= ~NO_DVMA;

  /* If this write was done in variable mode,
   * and happened to force the tape to fixed block mode
   * and left it there, make sure we put it back
   */
  if (((dstc->un_misc & FIXED_BLOCK_MODE) == 0)
      && dstc->un_bsize)
    (void) stc_set_bsize(dstc, dev, un, 0);

  return(ret);
}

/*
 * stcioctl() --
 */
/* ARGSUSED */
stcioctl(dev, cmd, data, flag)
    dev_t           dev;
    register int    cmd;
    register caddr_t data;
    int             flag;
{
    register struct scsi_unit *un;
    register struct scsi_stc *dstc;
    register int    unit;
    int err = 0;
    struct scsi_ext_sense *ssd;
    struct scsi_inquiry_data *sid;
    int s;

    struct stc_mode_params *sm;
    struct stc_mode_blk_desc *sb;

    EPRINTF(EL_DEBUG) ("stcioctl:\n");
    unit = STCUNIT(dev);
    if (unit >= NSTC) {
	EPRINTF(EL_DEBUG) ("stc%d: stcioctl: invalid unit %d\n", unit, unit);
	return (ENXIO);
    }
    un = &stcunits[unit];
    dstc = &sstc[unit];

    switch (cmd) {
	/* Handle MTIOCTOP stuff first */
    case MTIOCTOP:
	{
	    struct mtop *mop;

	    mop = (struct mtop *) data;
	   
	    switch (mop->mt_op) {
	    case MTWEOF:
		err = stccmd(dev, STC_WRITE_FILEMARKS, 
			     mop->mt_count, 0, (caddr_t) NULL, un);
		dstc->un_eof = 0;
		dstc->un_eot = 0;
		break;
	    case MTFSF:	
		err = stccmd(dev, STC_SPACE_FILE, 
			     mop->mt_count, 0, (caddr_t) NULL, un);
		break;
	    case MTBSF:
		err = stccmd(dev, STC_SPACE_FILE, 
			     mop->mt_count * -1, 0, (caddr_t) NULL, un);
		dstc->un_eof = 0;
		dstc->un_eot = 0;
		break;
	    case MTFSR:
		err = stccmd(dev, STC_SPACE_RECORD, 
			     mop->mt_count, 0, (caddr_t) NULL, un);
		dstc->un_eof = 0;
		break;
	    case MTBSR:
		err = stccmd(dev, STC_SPACE_RECORD, 
			     mop->mt_count * -1, 0, (caddr_t) NULL, un);
		dstc->un_eof = 0;
		break;
	    case MTREW:
		err = stccmd(dev, STC_REWIND,
			     mop->mt_count, 0, (caddr_t) NULL, un);
		s = splr(pritospl(un->un_mc->mc_intpri));
		dstc->un_openf = dstc->un_openf & ~STATE_MASK | REWINDING;
		dstc->un_eof = 0;
		dstc->un_eot = 0;
		(void)splx(s);
		break;
	    case MTOFFL:
	        if (mop->mt_count >= 0)
		    err = stccmd(dev, STC_UNLOAD, 
			     1, 0, (caddr_t) NULL, un);
		else
		    err = stccmd(dev, STC_LOAD,
			     1, 0, (caddr_t) NULL, un);
		dstc->un_eof = 0;
		dstc->un_eot = 0;
		break;
	    case MTNOP:
		err = stccmd(dev, STC_TEST_UNIT_READY,
			     mop->mt_count, 0, (caddr_t) NULL, un);
		break;
	    case MTERASE:
		err = stccmd(dev, STC_ERASE, 
			     mop->mt_count, 0, (caddr_t) NULL, un);
		dstc->un_eof = 0;
		dstc->un_eot = 0;
		break;

	    case MTRETEN:
		break;		/* Do nothing, need to return OK status for diags */
	    case MTEOM:
	    default:
		return (EOPNOTSUPP);
	    }
	}
	break;

    case MTIOCGET:
	{
	    struct mtget *mg;
	    struct stc_mode_sense_data *ms;

	    mg = (struct mtget *)data;
	    ms = &dstc->un_mdata;

	    mg->mt_type = MT_ISCCS32; /* type of magtape device */
	    mg->mt_dsreg = dstc->un_retries; /* ``drive status'' register */
	    mg->mt_erreg = dstc->un_status; /* ``error'' register */
	    mg->mt_resid = dstc->un_err_resid; /* residual count */
	    mg->mt_fileno = dstc->un_fileno; /* file number of current position */
	    mg->mt_blkno = dstc->un_position; /* block number of current position */
	}
	break;

    case STCIOC_GETERRC:	/* Get detailed error code */
	ssd = (struct scsi_ext_sense *) dstc->un_err_sense;
	bcopy((char *) ssd, (char *) data,
	      sizeof(struct scsi_ext_sense));
	break;

    case STCIOC_SELFTEST:
	if (err = stccmd(dev, STC_SEND_DIAGNOSTIC, 0, 0, 0, un)) {
	    EPRINTF(EL_DEBUG) ("stc%d: stcioctl: Can't start self test.\n",
			       unit);
	    return (err);
	}
	break;


    case STCIOC_INQUIRY:
	sid = (struct scsi_inquiry_data *) dstc->un_sense;
	if (err = stccmd(dev, STC_INQUIRY, (int) sizeof(struct scsi_inquiry_data),
		   0, (char *) dstc->un_sense, un)) {
	    EPRINTF(EL_DEBUG) ("stc%d: stcioctl: Inquiry failed.\n",
			       unit);
	    return (err);
	}
	bcopy((char *) sid, (char *) data,
	      sizeof(struct scsi_inquiry_data));
	break;

    case STCIOC_LOAD_DISPLAY:
	bcopy((char *)data, (char *)dstc->un_sense, sizeof (struct stc_display_params));
	err = stccmd(dev, STC_LOAD_DISPLAY, sizeof(struct stc_display_params), 
		     0, (char *)dstc->un_sense, un);
	break;

    case STCIOC_SET_BLKSIZE:
	err = stc_set_bsize(dstc, dev, un, *(int *)data);
	if ((err == 0) && *(int *)data)
		dstc->un_misc |= FIXED_BLOCK_MODE;
	else
		dstc->un_misc &= ~FIXED_BLOCK_MODE;
	break;

    case STCIOC_GET_BLKSIZE:
	*(int *)data = dstc->un_bsize;
	break;

    case STCIOC_SET_ASYNC:
	dstc->stc_state |= STC_ASYNC;
	break;

    case STCIOC_CANCEL_ASYNC:
	dstc->stc_state &= ~STC_ASYNC;
	break;

    /*
     * User has asked if we just woke them up with a SIGIO.
     */
    case STCIOC_READ_DEVDONE:
	*data = (dstc->stc_state & STC_DEVDONE);
	dstc->stc_state &= ~STC_DEVDONE;

	return(*data);
	break;

    /*
     * Turn on ICRC (data compression)
     */
    case STCIOC_SET_ICRC:
	if( dstc->un_has_icrc ) {
	  bzero(dstc->un_sense, 13);
	  sm = (struct stc_mode_params *) dstc->un_sense;
	  sb = (struct stc_mode_blk_desc *) (dstc->un_sense + 4);
	  
	  sm->mode_buffered = use_buffered_mode;
	  sm->mode_desc_len = 9;
	  sb->mode_icrc = 1;
	  sb->blk_len_high = (dstc->un_bsize >> 16) & 0xff;
	  sb->blk_len_mid = (dstc->un_bsize >> 8) & 0xff;
	  sb->blk_len_low = dstc->un_bsize & 0xff;

	  dstc->un_icrc_on = 1;
	  
/*	  printf("Turning on ICRC mode...\n"); */
	  
	  err = stccmd(dev, STC_MODE_SELECT, 13, 0,
		       (char *)dstc->un_sense, un);
/*	  printf("ICRC mode should now be on\n"); */
	  return(err);
	}
	break;
	
    /*
     * Turn off ICRC (data compression)
     */
    case STCIOC_CLEAR_ICRC:
	if( dstc->un_has_icrc ) {
	  bzero(dstc->un_sense, 13);
	  sm = (struct stc_mode_params *) dstc->un_sense;
	  sb = (struct stc_mode_blk_desc *) (dstc->un_sense + 4);
	  
	  sm->mode_buffered = use_buffered_mode;
	  sm->mode_desc_len = 9;
	  sb->mode_icrc = 0;
	  sb->blk_len_high = (dstc->un_bsize >> 16) & 0xff;
	  sb->blk_len_mid = (dstc->un_bsize >> 8) & 0xff;
	  sb->blk_len_low = dstc->un_bsize & 0xff;
	  
	  dstc->un_icrc_on = 0;
	  
/*	  printf("Turning off ICRC mode...\n"); */
	  
	  err = stccmd(dev, STC_MODE_SELECT, 13, 0,
		       (char *) dstc->un_sense, un);
	  
/*	  printf("ICRC mode should now be off\n"); */
	  return(err);
	}
	break;

      /*
       * Inquire about ICRC mode
       */
    case STCIOC_GET_ICRC:
	return(dstc->un_has_icrc);
	break;

    case STCIOC_CLEAR_EOT:
	/*
	 * Allowing writing past logical EOT
	 */
	dstc->un_eof = 0;
	dstc->un_eot = 0;
	break;

    default:
	EPRINTF(EL_DEBUG) ("stc%d: stcioctl: unknown ioctl\n", unit);
	return (ENOTTY);
	break;
    }
    
    return (err);
}
#endif	NSTC > 0
