/*
 *  Copyright (c) 1993, 1994 Steve Gerakines
 *
 *  This is freely redistributable software.  You may do anything you
 *  wish with it, so long as the above notice stays intact.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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 AUTHOR(S) 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.
 *
 *  ft.c - QIC-40/80 floppy tape driver
 *  $Id: ft.c,v 1.38 1998/07/15 09:59:31 bde Exp $
 *
 *  01/19/95 ++sg
 *  Cleaned up recalibrate/seek code at attach time for FreeBSD 2.x.
 *
 *  06/07/94 v0.9 ++sg
 *  Tape stuck on segment problem should be gone.  Re-wrote buffering
 *  scheme.  Added support for drives that do not automatically perform
 *  seek load point.  Can handle more wakeup types now and should correctly
 *  report most manufacturer names.  Fixed places where unit 0 was being
 *  sent to the fdc instead of the actual unit number.  Added ioctl support
 *  for an in-core badmap.
 *
 *  01/26/94 v0.3b - Jim Babb
 *  Got rid of the hard coded device selection.  Moved (some of) the
 *  static variables into a structure for support of multiple devices.
 *  ( still has a way to go for 2 controllers - but closer )
 *  Changed the interface with fd.c so we no longer 'steal' its
 *  driver routine vectors.
 *
 *  10/30/93 v0.3
 *  Fixed a couple more bugs.  Reading was sometimes looping when an
 *  an error such as address-mark-missing was encountered.  Both
 *  reading and writing was having more backup-and-retries than was
 *  necessary.  Added support to get hardware info.  Updated for use
 *  with FreeBSD.
 *
 *  09/15/93 v0.2 pl01
 *  Fixed a bunch of bugs:  extra isa_dmadone() in async_write() (shouldn't
 *  matter), fixed double buffering in async_req(), changed tape_end() in
 *  set_fdcmode() to reduce unexpected interrupts, changed end of track
 *  processing in async_req(), protected more of ftreq_rw() with an
 *  splbio().  Changed some of the ftreq_*() functions so that they wait
 *  for inactivity and then go, instead of aborting immediately.
 *
 *  08/07/93 v0.2 release
 *  Shifted from ftstrat to ioctl support for I/O.  Streaming is now much
 *  more reliable.  Added internal support for error correction, QIC-40,
 *  and variable length tapes.  Random access of segments greatly
 *  improved.  Formatting and verification support is close but still
 *  incomplete.
 *
 *  06/03/93 v0.1 Alpha release
 *  Hopefully the last re-write.  Many bugs fixed, many remain.
 */

#include "ft.h"
#if NFT > 0

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/disklabel.h>	/* temp. for dkunit() in fdc.h */
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/ftape.h>
#include <sys/kernel.h>

#include <machine/clock.h>

#include <i386/isa/isa_device.h>
#ifdef PC98
#include <pc98/pc98/fdreg.h>
#else
#include <i386/isa/fdreg.h>
#endif
#include <i386/isa/fdc.h>
#include <i386/isa/ftreg.h>

extern int ftintr __P((ftu_t ftu));

/* Enable or disable debugging messages. */
#define FTDBGALL 0			/* 1 if you want everything */
/*#define DPRT(a) printf a		*/
#define DPRT(a)

/* Constants private to the driver */
#define FTPRI		(PRIBIO)	/* sleep priority */
#define	FTNBUFF		9		/* 8 for buffering, 1 for header */

/* The following items are needed from the fd driver. */
extern int in_fdc(int);			/* read fdc registers */
extern int out_fdc(int, int);		/* write fdc registers */

/* Flags in isadev struct */
#define	FT_PROBE	0x1		/* allow for "dangerous" tape probes */

/* Type of tape attached */
/* use numbers that don't interfere with the possible floppy types */
#define NO_TYPE 0		/* (same as NO_TYPE in fd.c) */

/* F_TAPE_TYPE must match value in fd.c */
#define F_TAPE_TYPE	0x020		/* bit for ft->types to indicate tape */
#define FT_NONE		(F_TAPE_TYPE | 0)	/* no method required */
#define	FT_MOUNTAIN	(F_TAPE_TYPE | 1)	/* mountain */
#define	FT_COLORADO	(F_TAPE_TYPE | 2)	/* colorado */
#define FT_INSIGHT	(F_TAPE_TYPE | 3)	/* insight */

/* Mode FDC is currently in: tape or disk */
enum { FDC_TAPE_MODE, FDC_DISK_MODE };

/* Command we are awaiting completion of */
enum { FTCMD_NONE, FTCMD_RESET, FTCMD_RECAL, FTCMD_SEEK, FTCMD_READID };

/* Tape interrupt status of current request */
enum { FTSTS_NONE, FTSTS_SNOOZE, FTSTS_INTERRUPT, FTSTS_TIMEOUT };

/* Tape I/O status */
enum {
	FTIO_READY,		/* No I/O activity */
	FTIO_READING,		/* Currently reading blocks */
	FTIO_RDAHEAD,		/* Currently reading ahead */
	FTIO_WRITING		/* Buffers are being written */
};

/* Current tape mode */
enum {
	FTM_PRIMARY,		/* Primary mode */
	FTM_VERIFY,		/* Verify mode */
	FTM_FORMAT,		/* Format mode */
	FTM_DIAG1,		/* Diagnostic mode 1 */
	FTM_DIAG2		/* Diagnostic mode 2 */
};

/* Tape geometries table */
static QIC_Geom ftgtbl[] = {
	{ 0, 0, "Unformatted", "Unknown", 0,  0,     0,   0,     0 }, /* XXX */
	{ 1, 1, "QIC-40",  "205/550",	20,  68,  2176, 128, 21760 },
	{ 1, 2, "QIC-40",  "307.5/550",	20, 102,  3264, 128, 32640 },
	{ 1, 3, "QIC-40",  "295/900",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 1, 4, "QIC-40",  "1100/550",	20, 365, 11680, 128, 32512 },
	{ 1, 5, "QIC-40",  "1100/900",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 2, 1, "QIC-80",  "205/550",	28, 100,  3200, 128, 19200 },
	{ 2, 2, "QIC-80",  "307.5/550",	28, 150,  4800, 128, 19200 },
	{ 2, 3, "QIC-80",  "295/900",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 2, 4, "QIC-80",  "1100/550",	28, 537, 17184, 128, 32512 },
	{ 2, 5, "QIC-80",  "1100/900",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 3, 1, "QIC-500", "205/550",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 3, 2, "QIC-500", "307.5/550",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 3, 3, "QIC-500", "295/900",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 3, 4, "QIC-500", "1100/550",	 0,   0,     0,   0,     0 }, /* ??? */
	{ 3, 5, "QIC-500", "1100/900",	 0,   0,     0,   0,     0 }  /* ??? */
};
#define NGEOM	(sizeof(ftgtbl) / sizeof(QIC_Geom))

static QIC_Geom *ftg = NULL;			/* Current tape's geometry */

/*
 *  things relating to asynchronous commands
 */
static int awr_state;		/* state of async write */
static int ard_state;		/* state of async read */
static int arq_state;		/* state of async request */
static int async_retries;	/* retries, one per invocation */
static int async_func;		/* function to perform */
static int async_state;		/* state current function is at */
static int async_arg0;		/* up to 3 arguments for async cmds */
static int async_arg1;		/**/
static int async_arg2;		/**/
static int async_ret;		/* return value */
static struct _astk {
	int over_func;
	int over_state;
	int over_retries;
	int over_arg0;
	int over_arg1;
	int over_arg2;
} astk[10];
static struct _astk *astk_ptr = &astk[0]; /* Pointer to stack position */

/* List of valid async (interrupt driven) tape support functions. */
enum {
	ACMD_NONE, 		/* no command */
	ACMD_SEEK,		/* command seek */
	ACMD_STATUS,		/* report status */
	ACMD_STATE,		/* wait for state bits to be true */
	ACMD_SEEKSTS,		/* perform command and wait for status */
	ACMD_READID,		/* read id */
	ACMD_RUNBLK		/* ready tape for I/O on the given block */
};

/* Call another asyncronous command from within async_cmd(). */
#define CALL_ACMD(r,f,a,b,c) \
			astk_ptr->over_retries = async_retries; \
			astk_ptr->over_func = async_func; \
			astk_ptr->over_state = (r); \
			astk_ptr->over_arg0 = async_arg0; \
			astk_ptr->over_arg1 = async_arg1; \
			astk_ptr->over_arg2 = async_arg2; \
			async_func = (f); async_state = 0; async_retries = 0; \
			async_arg0=(a); async_arg1=(b); async_arg2=(c); \
			astk_ptr++; \
			goto restate

/* Perform an asyncronous command from outside async_cmd(). */
#define ACMD_FUNC(r,f,a,b,c) over_async = (r); astk_ptr = &astk[0]; \
			async_func = (f); async_state = 0; async_retries = 0; \
			async_arg0=(a); async_arg1=(b); async_arg2=(c); \
			async_cmd(ftu); \
			return

/* Various wait channels */
static char *wc_buff_avail	= "bavail";
static char *wc_buff_done	= "bdone";
static char *wc_iosts_change	= "iochg";
static char *wc_long_delay	= "ldelay";
static char *wc_intr_wait	= "intrw";
#define ftsleep(wc,to)	tsleep((caddr_t)(wc),FTPRI,(wc),(to))

/***********************************************************************\
* Per controller structure.						*
\***********************************************************************/
extern struct fdc_data fdc_data[NFDC];

/***********************************************************************\
* Per tape drive structure.						*
\***********************************************************************/
static struct ft_data {
	struct	fdc_data *fdc;	/* pointer to controller structure */
	int	ftsu;		/* this units number on this controller */
	int	type;		/* Drive type (Mountain, Colorado) */
/*	QIC_Geom *ftg;	*/	/* pointer to Current tape's geometry */
	int	flags;
	int	cmd_wait;	/* Command we are awaiting completion of */
	int	sts_wait;	/* Tape interrupt status of current request */
	int	io_sts;		/* Tape I/O status */
	int	mode;
	int	pcn;		/* present cylinder number */
	int	attaching;	/* true when ft is attaching */
	unsigned char *xptr;	/* pointer to buffer blk to xfer  */
	int	xcnt;		/* transfer count                 */
	int	xblk;		/* block number to transfer       */
	int	xseg;		/* segment being transferred	  */
	SegReq *segh;		/* Current I/O request		  */
	SegReq *segt;		/* Tail of queued I/O requests	  */
	SegReq *doneh;		/* Completed I/O request queue    */
	SegReq *donet;		/* Completed I/O request tail	  */
	SegReq *segfree;	/* Free segments		  */
	SegReq *hdr;		/* Current tape header		  */
	int nsegq;		/* Segments on request queue	  */
	int ndoneq;		/* Segments on completed queue	  */
	int nfreelist;		/* Segments on free list	  */

				/* the next 3 should be defines in 'flags' */
	int active;		/* TRUE if transfer is active	  */
	int rdonly;		/* TRUE if tape is read-only	  */
	int newcart;		/* TRUE if new cartridge detected */
	int laststs;		/* last reported status code      */
	int lastcfg;		/* last reported QIC config	  */
	int lasterr;		/* last QIC error code 		  */
	int lastpos;		/* last known segment number	  */
	int moving;		/* TRUE if tape is moving	  */
	int rid[7];		/* read_id return values	  */
	struct callout_handle tohandle;
} *ft_data[NFT];

/***********************************************************************\
* Throughout this file the following conventions will be used:		*
* ft is a pointer to the ft_data struct for the drive in question	*
* fdc is a pointer to the fdc_data struct for the controller		*
* ftu is the tape drive unit number					*
* fdcu is the floppy controller unit number				*
* ftsu is the tape drive unit number on that controller. (sub-unit)	*
\***********************************************************************/



#define	id_physid id_scsiid	/* this biotab field doubles as a field */
				/* for the physical unit number on the controller */

int ftopen(dev_t, int);
int ftclose(dev_t, int);
int ftioctl(dev_t, unsigned long, caddr_t, int, struct proc *);
int ftattach(struct isa_device *, struct isa_device *, int);
static timeout_t ft_timeout;
static void async_cmd(ftu_t);
static void async_req(ftu_t, int);
static void async_read(ftu_t, int);
static void async_write(ftu_t, int);
static void tape_start(ftu_t, int);
static void tape_end(ftu_t);
static void tape_inactive(ftu_t);
static int tape_cmd(ftu_t, int);
static int tape_status(ftu_t);
static int qic_status(ftu_t, int, int);
static int ftreq_rewind(ftu_t);
static int ftreq_hwinfo(ftu_t, QIC_HWInfo *);

/*****************************************************************************/


/*
 *  Allocate a segment I/O buffer from the free list.
 */
static SegReq *
segio_alloc(ft_p ft)
{
  SegReq *r;

  /* Grab first item from free list */
  if ((r = ft->segfree) != NULL) {
	ft->segfree = ft->segfree->next;
	ft->nfreelist--;
  }
  DPRT(("segio_alloc: nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq));
  return(r);
}


/*
 *  Queue a segment I/O request.
 */
static void
segio_queue(ft_p ft, SegReq *sp)
{
  /* Put request on in process queue. */
  if (ft->segt == NULL)
	ft->segh = sp;
  else
	ft->segt->next = sp;
  sp->next = NULL;
  ft->segt = sp;
  ft->nsegq++;
  DPRT(("segio_queue: nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq));
}


/*
 *  Segment I/O completed, place on correct queue.
 */
static void
segio_done(ft_p ft, SegReq *sp)
{
  /* First remove from current I/O queue */
  ft->segh = sp->next;
  if (ft->segh == NULL) ft->segt = NULL;
  ft->nsegq--;

  if (sp->reqtype == FTIO_WRITING) {
	/* Place on free list */
	sp->next = ft->segfree;
	ft->segfree = sp;
	ft->nfreelist++;
	wakeup((caddr_t)wc_buff_avail);
	DPRT(("segio_done: (w) nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq));
  } else {
	/* Put on completed I/O queue */
	if (ft->donet == NULL)
		ft->doneh = sp;
	else
		ft->donet->next = sp;
	sp->next = NULL;
	ft->donet = sp;
	ft->ndoneq++;
	wakeup((caddr_t)wc_buff_done);
	DPRT(("segio_done: (r) nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq));
  }
}


/*
 *  Take I/O request from finished queue to free queue.
 */
static void
segio_free(ft_p ft, SegReq *sp)
{
  /* First remove from done queue */
  ft->doneh = sp->next;
  if (ft->doneh == NULL) ft->donet = NULL;
  ft->ndoneq--;

  /* Place on free list */
  sp->next = ft->segfree;
  ft->segfree = sp;
  ft->nfreelist++;
  wakeup((caddr_t)wc_buff_avail);
  DPRT(("segio_free: nfree=%d ndone=%d nreq=%d\n", ft->nfreelist, ft->ndoneq, ft->nsegq));
}

/*
 *  Probe/attach floppy tapes.
 */
int
ftattach(isadev, fdup, unithasfd)
	struct isa_device *isadev, *fdup;
	int unithasfd;
{
  fdcu_t fdcu = isadev->id_unit;		/* fdc active unit */
  fdc_p fdc = fdc_data + fdcu;	/* pointer to controller structure */
  ftu_t ftu = fdup->id_unit;
  ft_p ft;
  ftsu_t ftsu = fdup->id_physid;
  QIC_HWInfo hw;
  char *manu;

  if (ftu >= NFT) return 0;
  ft = ft_data[ftu] = malloc(sizeof *ft, M_DEVBUF, M_NOWAIT);
  bzero(ft, sizeof *ft);
  callout_handle_init(&ft->tohandle);

  /* Probe for tape */
  ft->attaching = 1;
  ft->type = NO_TYPE;
  ft->fdc = fdc;
  ft->ftsu = ftsu;

  /*
   *  FT_NONE - no method, just do it
   */
  tape_start(ftu, 0);
  if (tape_status(ftu) >= 0) {
	ft->type = FT_NONE;
	ftreq_hwinfo(ftu, &hw);
	goto out;
  }

  /*
   *  FT_COLORADO - colorado style
   */
  tape_start(ftu, 0);
  tape_cmd(ftu, QC_COL_ENABLE1);
  tape_cmd(ftu, QC_COL_ENABLE2 + ftu);
  if (tape_status(ftu) >= 0) {
	ft->type = FT_COLORADO;
	ftreq_hwinfo(ftu, &hw);
	tape_cmd(ftu, QC_COL_DISABLE);
	goto out;
  }

  /*
   *  FT_MOUNTAIN - mountain style
   */
  tape_start(ftu, 0);
  tape_cmd(ftu, QC_MTN_ENABLE1);
  tape_cmd(ftu, QC_MTN_ENABLE2);
  if (tape_status(ftu) >= 0) {
	ft->type = FT_MOUNTAIN;
	ftreq_hwinfo(ftu, &hw);
	tape_cmd(ftu, QC_MTN_DISABLE);
	goto out;
  }

  if(isadev->id_flags & FT_PROBE) {
    /*
     * Insight probe is dangerous, since it requires the motor being
     * enabled and therefore risks attached floppy disk drives to jam.
     * Probe only if explicitly requested by a flag 0x1 from config
     */

    /*
     *  FT_INSIGHT - insight style
     *
     *  Since insight requires turning the drive motor on, we will not
     *  perform this probe if a floppy drive was already found with the
     *  the given unit and controller.
     */
    if (unithasfd) goto out;
    tape_start(ftu, 1);
    if (tape_status(ftu) >= 0) {
	ft->type = FT_INSIGHT;
	ftreq_hwinfo(ftu, &hw);
	goto out;
    }
  }

out:
  tape_end(ftu);
  if (ft->type != NO_TYPE) {
	fdc->flags |= FDC_HASFTAPE;
	switch(hw.hw_make) {
	    case 0x0000:
		if (ft->type == FT_COLORADO) {
			manu = "Colorado";
		} else if (ft->type == FT_INSIGHT) {
			manu = "Insight";
		} else if (ft->type == FT_MOUNTAIN && hw.hw_model == 0x05) {
			manu = "Archive";
		} else if (ft->type == FT_MOUNTAIN) {
			manu = "Mountain";
		} else {
			manu = "Unknown";
		}
		break;
	    case 0x0001:
		manu = "Colorado";
		break;
	    case 0x0005:
		if (hw.hw_model >= 0x09) {
			manu = "Conner";
		} else {
			manu = "Archive";
		}
		break;
	    case 0x0006:
		manu = "Mountain";
		break;
	    case 0x0007:
		manu = "Wangtek";
		break;
	    case 0x0222:
		manu = "IOMega";
		break;
	    default:
		manu = "Unknown";
		break;
	}
	printf("ft%d: %s tape\n", fdup->id_unit, manu);
  }
  ft->attaching = 0;
  return(ft->type);
}


/*
 *  Perform common commands asynchronously.
 */
static void
async_cmd(ftu_t ftu) {
	ft_p	ft = ft_data[ftu];
	fdcu_t	fdcu = ft->fdc->fdcu;
	int cmd, i, st0, st3, pcn;
	static int bitn, retval, retpos, nbits, newcn;
	static int wanttrk, wantblk, wantdir;
	static int curtrk, curblk, curdir, curdiff;
	static int errcnt = 0;

restate:
#if FTDBGALL
  DPRT(("async_cmd state: func: %d  state: %d\n", async_func, async_state));
#endif
  switch(async_func) {
     case ACMD_SEEK:
	/*
	 *  Arguments:
	 *     0 - command to perform
	 */
	switch (async_state) {
	    case 0:
		cmd = async_arg0;
#if FTDBGALL
		DPRT(("===>async_seek cmd = %d\n", cmd));
#endif
		newcn = (cmd <= ft->pcn) ? ft->pcn - cmd : ft->pcn + cmd;
		async_state = 1;
		i = 0;
		if (out_fdc(fdcu, NE7CMD_SEEK) < 0) i = 1;
		if (!i && out_fdc(fdcu, ftu) < 0) i = 1;
		if (!i && out_fdc(fdcu, newcn) < 0) i = 1;
		if (i) {
			if (++async_retries >= 10) {
				DPRT(("ft%d: async_cmd command seek failed!!\n", ftu));
				goto complete;
			}
			DPRT(("ft%d: async_cmd command seek retry...\n",ftu));
			async_state = 0;
			goto restate;
		}
		break;
	    case 1:
		out_fdc(fdcu, NE7CMD_SENSEI);
		st0 = in_fdc(fdcu);
		pcn = in_fdc(fdcu);
		if (st0 < 0 || pcn < 0 || newcn != pcn) {
			if (++async_retries >= 10) {
				DPRT(("ft%d: async_cmd seek retries exceeded\n",ftu));
				goto complete;
			}
			DPRT(("ft%d: async_cmd command bad st0=$%02x pcn=$%02x\n",
							ftu, st0, pcn));
			async_state = 0;
			ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10);
			break;
		}
		if (st0 & 0x20)	{ 	/* seek done */
			ft->pcn = pcn;
		}
#if FTDBGALL
		 else
			DPRT(("ft%d: async_seek error st0 = $%02x pcn = %d\n",
							ftu, st0, pcn));
#endif
		if (async_arg1) goto complete;
		async_state = 2;
		ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/50);
		break;
	    case 2:
		goto complete;
		/* NOTREACHED */
	}
	break;

     case ACMD_STATUS:
	/*
	 *  Arguments:
	 *     0 - command to issue report from
	 *     1 - number of bits
	 *  modifies: bitn, retval, st3
	 */
	switch (async_state) {
	    case 0:
		bitn = 0;
		retval = 0;
		cmd = async_arg0;
		nbits = async_arg1;
		DPRT(("async_status got cmd = %d nbits = %d\n", cmd,nbits));
		CALL_ACMD(5, ACMD_SEEK, QC_NEXTBIT, 0, 0);
		/* NOTREACHED */
	    case 1:
		out_fdc(fdcu, NE7CMD_SENSED);
		out_fdc(fdcu, ftu);
		st3 = in_fdc(fdcu);
		if (st3 < 0) {
		DPRT(("ft%d: async_status timed out on bit %d r=$%02x\n",
					ftu,bitn,retval));
			async_ret = -1;
			goto complete;
		}
		if ((st3 & 0x10) != 0) retval |= (1 << bitn);
		bitn++;
		if (bitn >= (nbits+2)) {
			if ((retval & 1) && (retval & (1 << (nbits+1)))) {
				async_ret = (retval & ~(1<<(nbits+1))) >> 1;
				if (async_arg0 == QC_STATUS && async_arg2 == 0 &&
				   (async_ret & (QS_ERROR|QS_NEWCART))) {
					async_state = 2;
					goto restate;
				}
			DPRT(("async status got $%04x ($%04x)\n", async_ret,retval));
			} else {
				DPRT(("ft%d: async_status failed: retval=$%04x nbits=%d\n",
						ftu, retval,nbits));
				async_ret = -2;
			}
			goto complete;
		}
		CALL_ACMD(1, ACMD_SEEK, QC_NEXTBIT, 0, 0);
		/* NOTREACHED */
	    case 2:
		if (async_ret & QS_NEWCART) ft->newcart = 1;
		CALL_ACMD(3, ACMD_STATUS, QC_ERRCODE, 16, 1);
	    case 3:
		ft->lasterr = async_ret;
		if ((ft->lasterr & QS_NEWCART) == 0 && ft->lasterr) {
			DPRT(("ft%d: QIC error %d occurred on cmd %d\n",
				ftu, ft->lasterr & 0xff, ft->lasterr >> 8));
		}
	        cmd = async_arg0;
		nbits = async_arg1;
		CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 1);
	    case 4:
		goto complete;
	    case 5:
		CALL_ACMD(6, ACMD_SEEK, QC_NEXTBIT, 0, 0);
	    case 6:
		CALL_ACMD(7, ACMD_SEEK, QC_NEXTBIT, 0, 0);
	    case 7:
		CALL_ACMD(8, ACMD_SEEK, QC_NEXTBIT, 0, 0);
	    case 8:
		cmd = async_arg0;
		CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0);
	}
	break;

     case ACMD_STATE:
	/*
	 *  Arguments:
	 *     0 - status bits to check
	 */
	switch(async_state) {
	    case 0:
		CALL_ACMD(1, ACMD_STATUS, QC_STATUS, 8, 0);
	    case 1:
		if ((async_ret & async_arg0) != 0) goto complete;
		async_state = 0;
		if (++async_retries == 360) {	/* 90 secs. */
			DPRT(("ft%d: acmd_state exceeded retry count\n", ftu));
			goto complete;
		}
		ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/4);
		break;
	}
	break;

     case ACMD_SEEKSTS:
	/*
	 *  Arguments:
	 *     0 - command to perform
	 *     1 - status bits to check
	 *     2 - (optional) seconds to wait until completion
	 */
	switch(async_state) {
	    case 0:
		cmd = async_arg0;
		async_retries = (async_arg2) ? (async_arg2 * 4) : 10;
		CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0);
	    case 1:
		CALL_ACMD(2, ACMD_STATUS, QC_STATUS, 8, 0);
	    case 2:
		if ((async_ret & async_arg1) != 0) goto complete;
		if (--async_retries == 0) {
			DPRT(("ft%d: acmd_seeksts retries exceeded\n", ftu));
			goto complete;
		}
		async_state = 1;
		ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/4);
		break;
	}
	break;

     case ACMD_READID:
	/*
	 *  Arguments: (none)
	 */
	switch(async_state) {
	    case 0:
		if (!ft->moving) {
			CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
			/* NOTREACHED */
		}
		async_state = 1;
		out_fdc(fdcu, 0x4a);		/* READ_ID */
		out_fdc(fdcu, ftu);
		break;
	    case 1:
		for (i = 0; i < 7; i++) ft->rid[i] = in_fdc(fdcu);
		async_ret = (ft->rid[3]*ftg->g_fdtrk) +
			    (ft->rid[4]*ftg->g_fdside) + ft->rid[5] - 1;
		DPRT(("readid st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d\n",
			ft->rid[0], ft->rid[1], ft->rid[2], ft->rid[3],
			ft->rid[4], ft->rid[5], async_ret));
		if ((ft->rid[0] & 0xc0) != 0 || async_ret < 0) {
			/*
			 *  Method for retry:
			 *    errcnt == 1 regular retry
			 *		2 microstep head 1
			 * 		3 microstep head 2
			 *		4 microstep head back to 0
			 *		5 fail
			 */
			if (++errcnt >= 5) {
				DPRT(("ft%d: acmd_readid errcnt exceeded\n", fdcu));
				async_ret = -2;
				errcnt = 0;
				goto complete;
			}
			if (errcnt == 1) {
				ft->moving = 0;
				CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
			} else {
				ft->moving = 0;
				CALL_ACMD(4, ACMD_SEEKSTS, QC_STPAUSE, QS_READY, 0);
			}
			DPRT(("readid retry %d...\n", errcnt));
			async_state = 0;
			goto restate;
		}
		if ((async_ret % ftg->g_blktrk) == (ftg->g_blktrk-1)) {
			DPRT(("acmd_readid detected last block on track\n"));
			retpos = async_ret;
			CALL_ACMD(2, ACMD_STATE, QS_BOT|QS_EOT, 0, 0);
			/* NOTREACHED */
		}
		ft->lastpos = async_ret;
		errcnt = 0;
		goto complete;
		/* NOTREACHED */
	    case 2:
		CALL_ACMD(3, ACMD_STATE, QS_READY, 0, 0);
	    case 3:
		ft->moving = 0;
		async_ret = retpos+1;
		goto complete;
	    case 4:
		CALL_ACMD(5, ACMD_SEEK, QC_FORWARD, 0, 0);
	    case 5:
		ft->moving = 1;
		async_state = 0;
		ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */
		break;
	}
	break;

     case ACMD_RUNBLK:
	/*
	 *  Arguments:
	 *     0 - block number I/O will be performed on
	 *
	 *  modifies: curpos
	 */
	switch (async_state) {
	    case 0:
		wanttrk = async_arg0 / ftg->g_blktrk;
		wantblk = async_arg0 % ftg->g_blktrk;
		wantdir = wanttrk & 1;
		ft->moving = 0;
		CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
	    case 1:
		curtrk = wanttrk;
		curdir = curtrk & 1;
		DPRT(("Changing to track %d\n", wanttrk));
		CALL_ACMD(2, ACMD_SEEK, QC_SEEKTRACK, 0, 0);
	    case 2:
		cmd = wanttrk+2;
		CALL_ACMD(3, ACMD_SEEKSTS, cmd, QS_READY, 0);
	    case 3:
		CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 0);
	    case 4:
		ft->laststs = async_ret;
		if (wantblk == 0) {
			curblk = 0;
			cmd = (wantdir) ? QC_SEEKEND : QC_SEEKSTART;
			CALL_ACMD(6, ACMD_SEEKSTS, cmd, QS_READY, 90);
		}
		if (ft->laststs & QS_BOT) {
			DPRT(("Tape is at BOT\n"));
			curblk = (wantdir) ? 4800 : 0;
			async_state = 6;
			goto restate;
		}
		if (ft->laststs & QS_EOT) {
			DPRT(("Tape is at EOT\n"));
			curblk = (wantdir) ? 0 : 4800;
			async_state = 6;
			goto restate;
		}
		CALL_ACMD(5, ACMD_READID, 0, 0, 0);
	    case 5:
		if (async_ret < 0) {
			ft->moving = 0;
			ft->lastpos = -2;
			if (async_ret == -2) {
				CALL_ACMD(9, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
			}
			CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
		}
		curtrk = (async_ret+1) / ftg->g_blktrk;
		curblk = (async_ret+1) % ftg->g_blktrk;
		DPRT(("gotid: curtrk=%d wanttrk=%d curblk=%d wantblk=%d\n",
			curtrk, wanttrk, curblk, wantblk));
		if (curtrk != wanttrk) {	/* oops! */
			DPRT(("oops!! wrong track!\n"));
			CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
		}
		async_state = 6;
		goto restate;
	    case 6:
		DPRT(("curtrk = %d nextblk = %d\n", curtrk, curblk));
		if (curblk == wantblk) {
			ft->lastpos = curblk - 1;
			async_ret = ft->lastpos;
			if (ft->moving) goto complete;
			CALL_ACMD(7, ACMD_STATE, QS_READY, 0, 0);
		}
		if (curblk > wantblk) {		/* passed it */
			ft->moving = 0;
			CALL_ACMD(10, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
		}
		if ((wantblk - curblk) <= 256) {	/* approaching it */
			CALL_ACMD(5, ACMD_READID, 0, 0, 0);
		}
		/* way up ahead */
		ft->moving = 0;
		CALL_ACMD(14, ACMD_SEEKSTS, QC_STOP, QS_READY, 0);
		break;
	    case 7:
		ft->moving = 1;
		CALL_ACMD(8, ACMD_SEEK, QC_FORWARD, 0, 0);
		break;
	    case 8:
		async_state = 9;
		ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10);  /* XXX */
		break;
	    case 9:
		goto complete;
	    case 10:
		curdiff = ((curblk - wantblk) / QCV_BLKSEG) + 2;
		if (curdiff >= ftg->g_segtrk) curdiff = ftg->g_segtrk - 1;
		DPRT(("pos %d past %d, reverse %d\n", curblk, wantblk, curdiff));
		CALL_ACMD(11, ACMD_SEEK, QC_SEEKREV, 0, 0);
	    case 11:
		DPRT(("reverse 1 done\n"));
		CALL_ACMD(12, ACMD_SEEK, (curdiff & 0xf)+2, 0, 0);
	    case 12:
		DPRT(("reverse 2 done\n"));
		CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff>>4)&0xf)+2, QS_READY, 90);
	    case 13:
		CALL_ACMD(5, ACMD_READID, 0, 0, 0);
	    case 14:
		curdiff = ((wantblk - curblk) / QCV_BLKSEG) - 2;
		if (curdiff < 0) curdiff = 0;
		DPRT(("pos %d before %d, forward %d\n", curblk, wantblk, curdiff));
		CALL_ACMD(15, ACMD_SEEK, QC_SEEKFWD, 0, 0);
	    case 15:
		DPRT(("forward 1 done\n"));
		CALL_ACMD(16, ACMD_SEEK, (curdiff & 0xf)+2, 0, 0);
	    case 16:
		DPRT(("forward 2 done\n"));
		CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff>>4)&0xf)+2, QS_READY, 90);
	}
	break;
  }

  return;

complete:
  if (astk_ptr != &astk[0]) {
	astk_ptr--;
	async_retries = astk_ptr->over_retries;
	async_func = astk_ptr->over_func;
	async_state = astk_ptr->over_state;
	async_arg0 = astk_ptr->over_arg0;
	async_arg1 = astk_ptr->over_arg1;
	async_arg2 = astk_ptr->over_arg2;
	goto restate;
  }
  async_func = ACMD_NONE;
  async_state = 0;
  switch (ft->io_sts) {
     case FTIO_READY:
	async_req(ftu, 2);
	break;
     case FTIO_READING:
     case FTIO_RDAHEAD:
	async_read(ftu, 2);
	break;
     case FTIO_WRITING:
	async_write(ftu, 2);
	break;
     default:
	DPRT(("ft%d: bad async_cmd ending I/O state!\n", ftu));
	break;
  }
}


/*
 *  Entry point for the async request processor.
 */
static void
async_req(ftu_t ftu, int from)
{
  ft_p	ft = ft_data[ftu];
  SegReq *sp;
  static int over_async, lastreq;
  int cmd;

  if (from == 2) arq_state = over_async;

restate:
  switch (arq_state) {
     case 0:	/* Process segment */
	sp = ft->segh;
	ft->io_sts = (sp == NULL) ? FTIO_READY : sp->reqtype;

	if (ft->io_sts == FTIO_WRITING)
		async_write(ftu, from);
	else
		async_read(ftu, from);
	if (ft->io_sts != FTIO_READY) return;

	/* Pull buffer from current I/O queue */
	if (sp != NULL) {
		lastreq = sp->reqtype;
		segio_done(ft, sp);

		/* If I/O cancelled, clear finished queue. */
		if (sp->reqcan) {
			while (ft->doneh != NULL)
				segio_free(ft, ft->doneh);
			lastreq = FTIO_READY;
		}
	} else
		lastreq = FTIO_READY;

	/* Detect end of track */
	if (((ft->xblk / QCV_BLKSEG) % ftg->g_segtrk) == 0) {
		ACMD_FUNC(2, ACMD_STATE, QS_BOT|QS_EOT, 0, 0);
	}
	arq_state = 1;
	goto restate;

     case 1:	/* Next request */
	/* If we have another request queued, start it running. */
	if (ft->segh != NULL) {
		sp = ft->segh;
		sp->reqcrc = 0;
		arq_state = ard_state = awr_state = 0;
		ft->xblk = sp->reqblk;
		ft->xseg = sp->reqseg;
		ft->xcnt = 0;
		ft->xptr = sp->buff;
		DPRT(("I/O reqblk = %d\n", ft->xblk));
		goto restate;
	}

	/* If the last request was reading, do read ahead. */
	if ((lastreq == FTIO_READING || lastreq == FTIO_RDAHEAD) &&
					(sp = segio_alloc(ft)) != NULL) {
		sp->reqtype = FTIO_RDAHEAD;
		sp->reqblk = ft->xblk;
		sp->reqseg = ft->xseg+1;
		sp->reqcrc = 0;
		sp->reqcan = 0;
		segio_queue(ft, sp);
		bzero(sp->buff, QCV_SEGSIZE);
		arq_state = ard_state = awr_state = 0;
		ft->xblk = sp->reqblk;
		ft->xseg = sp->reqseg;
		ft->xcnt = 0;
		ft->xptr = sp->buff;
		DPRT(("Processing readahead reqblk = %d\n", ft->xblk));
		goto restate;
	}

	if (ft->moving) {
		DPRT(("No more I/O.. Stopping.\n"));
		ft->moving = 0;
		ACMD_FUNC(7, ACMD_SEEKSTS, QC_PAUSE, QS_READY, 0);
		break;
	}
	arq_state = 7;
	goto restate;

     case 2:	/* End of track */
	ft->moving = 0;
	ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0);
	break;

     case 3:
	DPRT(("async_req seek head to track %d\n", ft->xblk / ftg->g_blktrk));
	ACMD_FUNC(4, ACMD_SEEK, QC_SEEKTRACK, 0, 0);
	break;

     case 4:
	cmd = (ft->xblk / ftg->g_blktrk) + 2;
	if (ft->segh != NULL) {
		ACMD_FUNC(5, ACMD_SEEKSTS, cmd, QS_READY, 0);
	} else {
		ACMD_FUNC(7, ACMD_SEEKSTS, cmd, QS_READY, 0);
	}
	break;

     case 5:
	ft->moving = 1;
	ACMD_FUNC(6, ACMD_SEEK, QC_FORWARD, 0, 0);
	break;

     case 6:
	arq_state = 1;
	ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */
	break;

     case 7:
	/* Time to rest. */
	ft->active = 0;
	ft->lastpos = -2;

	/* wakeup those who want an i/o chg */
	wakeup((caddr_t)wc_iosts_change);
	break;
  }
}


/*
 *  Entry for async read.
 */
static void
async_read(ftu_t ftu, int from)
{
  ft_p ft = ft_data[ftu];
  fdcu_t fdcu = ft->fdc->fdcu;		/* fdc active unit */
  int i, rddta[7];
  int where;
  static int over_async;
  static int retries = 0;

  if (from == 2) ard_state = over_async;

restate:
#if FTDBGALL
  DPRT(("async_read: state: %d  from = %d\n", ard_state, from));
#endif
  switch (ard_state) {
     case 0:	/* Start off */
	/* If tape is not at desired position, stop and locate */
	if (ft->lastpos != (ft->xblk-1)) {
		DPRT(("ft%d: position unknown: lastpos:%d ft->xblk:%d\n",
			ftu, ft->lastpos, ft->xblk));
		ACMD_FUNC(1, ACMD_RUNBLK, ft->xblk, 0, 0);
	}

	/* Tape is in position but stopped. */
	if (!ft->moving) {
		DPRT(("async_read ******STARTING TAPE\n"));
		ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0);
	}
	ard_state = 1;
	goto restate;

     case 1:	/* Start DMA */
	/* Tape is now moving and in position-- start DMA now! */
	isa_dmastart(B_READ, ft->xptr, QCV_BLKSIZE, 2);
	out_fdc(fdcu, 0x66);				/* read */
	out_fdc(fdcu, ftu);				/* unit */
	out_fdc(fdcu, (ft->xblk % ftg->g_fdside) / ftg->g_fdtrk); 	/* cylinder */
	out_fdc(fdcu, ft->xblk / ftg->g_fdside);		/* head */
	out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1);	/* sector */
	out_fdc(fdcu, 0x03);				/* 1K sectors */
	out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1);	/* count */
	out_fdc(fdcu, 0x74);				/* gap length */
	out_fdc(fdcu, 0xff);				/* transfer size */
	ard_state = 2;
	break;

     case 2:	/* DMA completed */
	/* Transfer complete, get status */
	for (i = 0; i < 7; i++) rddta[i] = in_fdc(fdcu);
	isa_dmadone(B_READ, ft->xptr, QCV_BLKSIZE, 2);

#if FTDBGALL
	/* Compute where the controller thinks we are */
	where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside)
			+ rddta[5]-1;
	DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
	    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
	    where, ft->xblk));
#endif

	/* Check for errors */
	if ((rddta[0] & 0xc0) != 0x00) {
#if !FTDBGALL
		where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside)
			+ rddta[5]-1;
		DPRT(("xd: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
		    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
		    where, ft->xblk));
#endif
		if ((rddta[1] & 0x04) == 0x04 && retries < 2) {
			/* Probably wrong position */
			DPRT(("async_read: doing retry %d\n", retries));
			ft->lastpos = ft->xblk;
			ard_state = 0;
			retries++;
			goto restate;
		} else {
			/* CRC/Address-mark/Data-mark, et. al. */
			DPRT(("ft%d: CRC error on block %d\n", fdcu, ft->xblk));
			ft->segh->reqcrc |= (1 << ft->xcnt);
		}
	}

	/* Otherwise, transfer completed okay. */
	retries = 0;
	ft->lastpos = ft->xblk;
	ft->xblk++;
	ft->xcnt++;
	ft->xptr += QCV_BLKSIZE;
	if (ft->xcnt < QCV_BLKSEG && ft->segh->reqcan == 0) {
		ard_state = 0;
		goto restate;
	}
	DPRT(("Read done..  Cancel = %d\n", ft->segh->reqcan));
	ft->io_sts = FTIO_READY;
	break;

     case 3:
	ft->moving = 1;
	ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0);
	break;

     case 4:
	ard_state = 1;
	ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10);  /* XXX */
	break;

     default:
	DPRT(("ft%d: bad async_read state %d!!\n", ftu, ard_state));
	break;
  }
}


/*
 *  Entry for async write.  If from is 0, this came from the interrupt
 *  routine, if it's 1 then it was a timeout, if it's 2, then an
 *  async_cmd completed.
 */
static void
async_write(ftu_t ftu, int from)
{
  ft_p ft = ft_data[ftu];
  fdcu_t fdcu = ft->fdc->fdcu;		/* fdc active unit */
  int i, rddta[7];
  int where;
  static int over_async;
  static int retries = 0;

  if (from == 2) awr_state = over_async;

restate:
#if FTDBGALL
  DPRT(("async_write: state: %d  from = %d\n", awr_state, from));
#endif
  switch (awr_state) {
     case 0:	/* Start off */
	/* If tape is not at desired position, stop and locate */
	if (ft->lastpos != (ft->xblk-1)) {
		DPRT(("ft%d: position unknown: lastpos:%d ft->xblk:%d\n",
			ftu, ft->lastpos, ft->xblk));
		ACMD_FUNC(1, ACMD_RUNBLK, ft->xblk, 0, 0);
	}

	/* Tape is in position but stopped. */
	if (!ft->moving) {
		DPRT(("async_write ******STARTING TAPE\n"));
		ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0);
	}
	awr_state = 1;
	goto restate;

     case 1:	/* Start DMA */
	/* Tape is now moving and in position-- start DMA now! */
	isa_dmastart(B_WRITE, ft->xptr, QCV_BLKSIZE, 2);
	out_fdc(fdcu, 0x45);				/* write */
	out_fdc(fdcu, ftu);				/* unit */
	out_fdc(fdcu, (ft->xblk % ftg->g_fdside) / ftg->g_fdtrk); /* cyl */
	out_fdc(fdcu, ft->xblk / ftg->g_fdside);		/* head */
	out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1);	/* sector */
	out_fdc(fdcu, 0x03);				/* 1K sectors */
	out_fdc(fdcu, (ft->xblk % ftg->g_fdtrk) + 1);	/* count */
	out_fdc(fdcu, 0x74);				/* gap length */
	out_fdc(fdcu, 0xff);				/* transfer size */
	awr_state = 2;
	break;

     case 2:	/* DMA completed */
	/* Transfer complete, get status */
	for (i = 0; i < 7; i++) rddta[i] = in_fdc(fdcu);
	isa_dmadone(B_WRITE, ft->xptr, QCV_BLKSIZE, 2);

#if FTDBGALL
	/* Compute where the controller thinks we are */
	where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside) + rddta[5]-1;
	DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
	    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
	    where, ft->xblk));
#endif

	/* Check for errors */
	if ((rddta[0] & 0xc0) != 0x00) {
#if !FTDBGALL
		where = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside)
			 + rddta[5]-1;
		DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
		    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
		    where, ft->xblk));
#endif
		if (retries < 3) {
			/* Something happened -- try again */
			DPRT(("async_write: doing retry %d\n", retries));
			ft->lastpos = ft->xblk;
			awr_state = 0;
			retries++;
			goto restate;
		} else {
			/*
			 *  Retries failed.  Note the unrecoverable error.
			 *  Marking the block as bad is useless right now.
			 */
			printf("ft%d: unrecoverable write error on block %d\n",
					ftu, ft->xblk);
			ft->segh->reqcrc |= (1 << ft->xcnt);
		}
	}

	/* Otherwise, transfer completed okay. */
	retries = 0;
	ft->lastpos = ft->xblk;
	ft->xblk++;
	ft->xcnt++;
	ft->xptr += QCV_BLKSIZE;
	if (ft->xcnt < QCV_BLKSEG) {
		awr_state = 0;	/* next block */
		goto restate;
	}
#if FTDBGALL
	DPRT(("Write done.\n"));
#endif
	ft->io_sts = FTIO_READY;
	break;

     case 3:
	ft->moving = 1;
	ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0);
	break;

     case 4:
	awr_state = 1;
	ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, hz/10); /* XXX */
	break;

     default:
	DPRT(("ft%d: bad async_write state %d!!\n", ftu, awr_state));
	break;
  }
}


/*
 *  Interrupt handler for active tape.  Bounced off of fdintr().
 */
int
ftintr(ftu_t ftu)
{
  int st0, pcn, i;
  ft_p	ft = ft_data[ftu];
  fdcu_t fdcu = ft->fdc->fdcu;		/* fdc active unit */
  int s = splbio();

  st0 = 0;
  pcn = 0;

  /* I/O segment transfer completed */
  if (ft->active) {
	if (async_func != ACMD_NONE) {
		async_cmd(ftu);
		splx(s);
		return(1);
	}
#if FTDBGALL
	DPRT(("Got request interrupt\n"));
#endif
	async_req(ftu, 0);
	splx(s);
	return(1);
  }

  /* Get interrupt status */
  if (ft->cmd_wait != FTCMD_READID) {
	out_fdc(fdcu, NE7CMD_SENSEI);
	st0 = in_fdc(fdcu);
	pcn = in_fdc(fdcu);
  }

  if (ft->cmd_wait == FTCMD_NONE || ft->sts_wait != FTSTS_SNOOZE) {
huh_what:
	printf("ft%d: unexpected interrupt; st0 = $%02x pcn = %d\n",
				ftu, st0, pcn);
	splx(s);
	return(1);
  }

  switch (ft->cmd_wait) {
     case FTCMD_RESET:
	ft->sts_wait = FTSTS_INTERRUPT;
	wakeup((caddr_t)wc_intr_wait);
	break;
     case FTCMD_RECAL:
     case FTCMD_SEEK:
	if (st0 & 0x20)	{ 	/* seek done */
		ft->sts_wait = FTSTS_INTERRUPT;
		ft->pcn = pcn;
		wakeup((caddr_t)wc_intr_wait);
	}
#if FTDBGALL
	else
		DPRT(("ft%d: seek error st0 = $%02x pcn = %d\n",
						ftu, st0, pcn));
#endif
	break;
     case FTCMD_READID:
	for (i = 0; i < 7; i++) ft->rid[i] = in_fdc(fdcu);
	ft->sts_wait = FTSTS_INTERRUPT;
	wakeup((caddr_t)wc_intr_wait);
	break;

     default:
	goto huh_what;
  }

  splx(s);
  return(1);
}


/*
 *  Interrupt timeout routine.
 */
static void
ft_timeout(void *arg1)
{
  int s;
  ftu_t ftu = (ftu_t)arg1;
  ft_p	ft = ft_data[ftu];

  s = splbio();
  if (ft->active) {
	if (async_func != ACMD_NONE) {
		async_cmd(ftu);
		splx(s);
		return;
	}
	async_req(ftu, 1);
  } else {
	  ft->sts_wait = FTSTS_TIMEOUT;
	  wakeup((caddr_t)wc_intr_wait);
  }
  splx(s);
}


/*
 *  Wait for a particular interrupt to occur.  ftintr() will wake us up
 *  if it sees what we want.  Otherwise, time out and return error.
 *  Should always disable ints before trigger is sent and calling here.
 */
static int
ftintr_wait(ftu_t ftu, int cmd, int ticks)
{
  int retries, st0, pcn;
  ft_p	ft = ft_data[ftu];
  fdcu_t fdcu = ft->fdc->fdcu;		/* fdc active unit */

  ft->cmd_wait = cmd;
  ft->sts_wait = FTSTS_SNOOZE;

  /* At attach time, we can't rely on having interrupts serviced */
  if (ft->attaching) {
	switch (cmd) {
	    case FTCMD_RESET:
		DELAY(100);
		ft->sts_wait = FTSTS_INTERRUPT;
		goto intrdone;
	    case FTCMD_RECAL:
	    case FTCMD_SEEK:
		for (retries = 0; retries < 10000; retries++) {
			DELAY(150);
			out_fdc(fdcu, NE7CMD_SENSEI);
			st0 = in_fdc(fdcu);
			if ((st0 & 0xc0) == 0x80) continue;
			pcn = in_fdc(fdcu);
			if (st0 & 0x20) {
				ft->sts_wait = FTSTS_INTERRUPT;
				ft->pcn = pcn;
				goto intrdone;
			}
		}
		break;
	}
	ft->sts_wait = FTSTS_TIMEOUT;
	goto intrdone;
  }

  ftsleep(wc_intr_wait, ticks);

intrdone:
  if (ft->sts_wait == FTSTS_TIMEOUT) {	/* timeout */
#if FTDBGALL
	if (ft->cmd_wait != FTCMD_RESET)
		DPRT(("ft%d: timeout on command %d\n", ftu, ft->cmd_wait));
#endif
	ft->cmd_wait = FTCMD_NONE;
	ft->sts_wait = FTSTS_NONE;
	return(1);
  }

  /* got interrupt */
  if (ft->attaching == 0 && ticks)
	untimeout(ft_timeout, (caddr_t)ftu, ft->tohandle);
  ft->cmd_wait = FTCMD_NONE;
  ft->sts_wait = FTSTS_NONE;
  return(0);
}


/*
 *  Recalibrate tape drive.  Parameter totape is true, if we should
 *  recalibrate to tape drive settings.
 */
static int
tape_recal(ftu_t ftu, int totape)
{
  int s;
  ft_p	ft = ft_data[ftu];
  fdcu_t fdcu = ft->fdc->fdcu;		/* fdc active unit */

  DPRT(("tape_recal start\n"));

#ifdef PC98
  outb(0xbe, FDP_FDDEXC | FDP_PORTEXC);
#endif
  out_fdc(fdcu, NE7CMD_SPECIFY);
#ifdef PC98
  out_fdc(fdcu, (totape) ? 0xEF : 0xCF);
  out_fdc(fdcu, 0x02);
#else
  out_fdc(fdcu, (totape) ? 0xAD : 0xDF);
  out_fdc(fdcu, 0x02);
#endif

  s = splbio();
  out_fdc(fdcu, NE7CMD_RECAL);
  out_fdc(fdcu, ftu);

  if (ftintr_wait(ftu, FTCMD_RECAL, hz)) {
	splx(s);
	DPRT(("ft%d: recalibrate timeout\n", ftu));
	return(1);
  }
  splx(s);

  out_fdc(fdcu, NE7CMD_SPECIFY);
#ifdef PC98
  out_fdc(fdcu, (totape) ? 0xEF : 0xCF);
  out_fdc(fdcu, 0x02);
#else
  out_fdc(fdcu, (totape) ? 0xFD : 0xDF);
  out_fdc(fdcu, 0x02);
#endif

  DPRT(("tape_recal end\n"));
  return(0);
}

/*
 *  Wait for a particular tape status to be met.  If all is TRUE, then
 *  all states must be met, otherwise any state can be met.
 */
static int
tape_state(ftu_t ftu, int all, int mask, int seconds)
{
  int r, tries, maxtries;

  maxtries = (seconds) ? (4 * seconds) : 1;
  for (tries = 0; tries < maxtries; tries++) {
	r = tape_status(ftu);
	if (r >= 0) {
		if (all && (r & mask) == mask) return(r);
		if ((r & mask) != 0) return(r);
	}
	if (seconds) ftsleep(wc_long_delay, hz/4);
  }
  DPRT(("ft%d: tape_state failed on mask=$%02x maxtries=%d\n",
				ftu, mask, maxtries));
  return(-1);
}


/*
 *  Send a QIC command to tape drive, wait for completion.
 */
static int
tape_cmd(ftu_t ftu, int cmd)
{
  int newcn;
  int retries = 0;
  int s;
  ft_p	ft = ft_data[ftu];
  fdcu_t fdcu = ft->fdc->fdcu;		/* fdc active unit */

  DPRT(("===> tape_cmd: %d\n",cmd));
  newcn = (cmd <= ft->pcn) ? ft->pcn - cmd : ft->pcn + cmd;

retry:

  /* Perform seek */
  s = splbio();
  out_fdc(fdcu, NE7CMD_SEEK);
  out_fdc(fdcu, ftu);
  out_fdc(fdcu, newcn);

  if (ftintr_wait(ftu, FTCMD_SEEK, hz)) {
	DPRT(("ft%d: tape_cmd seek timeout\n", ftu));
redo:
	splx(s);
	if (++retries < 5) goto retry;
	DPRT(("ft%d: tape_cmd seek failed!\n", ftu));
	return(1);
  }
  splx(s);

  if (ft->pcn != newcn) {
	DPRT(("ft%d: bad seek in tape_cmd; pcn = %d  newcn = %d\n",
			ftu, ft->pcn, newcn));
	goto redo;
  }
  DELAY(2500);
  return(0);
}


/*
 *  Return status of tape drive
 */
static int
tape_status(ftu_t ftu)
{
  int r, err, tries;
  ft_p ft = ft_data[ftu];
  int max = (ft->attaching) ? 2 : 3;

  for (r = -1, tries = 0; r < 0 && tries < max; tries++)
	r = qic_status(ftu, QC_STATUS, 8);
  if (tries == max) return(-1);

recheck:
  DPRT(("tape_status got $%04x\n",r));
  ft->laststs = r;

  if (r & (QS_ERROR|QS_NEWCART)) {
	err = qic_status(ftu, QC_ERRCODE, 16);
	ft->lasterr = err;
	if (r & QS_NEWCART) {
		ft->newcart = 1;
		/* If tape not referenced, do a seek load point. */
		if ((r & QS_FMTOK) == 0 && !ft->attaching) {
			tape_cmd(ftu, QC_SEEKLP);
			do {
				ftsleep(wc_long_delay, hz);
			} while ((r = qic_status(ftu, QC_STATUS, 8)) < 0 ||
					(r & (QS_READY|QS_CART)) == QS_CART);
			goto recheck;
		}
	} else if (err && !ft->attaching) {
		DPRT(("ft%d: QIC error %d occurred on cmd %d\n",
					ftu, err & 0xff, err >> 8));
	}
	r = qic_status(ftu, QC_STATUS, 8);
	ft->laststs = r;
	DPRT(("tape_status got error code $%04x new sts = $%02x\n",err,r));
  }

  ft->rdonly = (r & QS_RDONLY);
  return(r);
}


/*
 *  Transfer control to tape drive.
 */
static void
tape_start(ftu_t ftu, int motor)
{
  ft_p	ft = ft_data[ftu];
  fdc_p	fdc = ft->fdc;
  int s, mbits;
#ifndef PC98
  static int mbmotor[] = { FDO_MOEN0, FDO_MOEN1, FDO_MOEN2, FDO_MOEN3 };
#endif

  s = splbio();
  DPRT(("tape_start start\n"));

  /* reset, dma disable */
#ifdef PC98
  outb(fdc->baseport+FDOUT, FDO_RST | FDO_FRY | FDO_AIE | FDO_MTON);
#else
  outb(fdc->baseport+FDOUT, 0x00);
#endif
  (void)ftintr_wait(ftu, FTCMD_RESET, hz/10);

  /* raise reset, enable DMA, motor on if needed */
#ifdef PC98
  outb(fdc->baseport+FDOUT, FDO_DMAE | FDO_MTON);
#else
  mbits = ftu & 3;
  if (motor && ftu < 4)
	mbits |= mbmotor[ftu];

  outb(fdc->baseport+FDOUT, FDO_FRST | FDO_FDMAEN | mbits);
#endif
  (void)ftintr_wait(ftu, FTCMD_RESET, hz/10);

  splx(s);

  tape_recal(ftu, 1);

  /* set transfer speed */
#ifndef PC98
  outb(fdc->baseport+FDCTL, FDC_500KBPS);
  DELAY(10);
#endif

  DPRT(("tape_start end\n"));
}


/*
 *  Transfer control back to floppy disks.
 */
static void
tape_end(ftu_t ftu)
{
  ft_p	ft = ft_data[ftu];
  fdc_p	fdc = ft->fdc;
  int s;

  DPRT(("tape_end start\n"));
  tape_recal(ftu, 0);

  s = splbio();

  /* reset, dma disable */
#ifdef PC98
  outb(fdc->baseport+FDOUT, FDO_RST | FDO_FRY | FDO_AIE | FDO_MTON);
#else
  outb(fdc->baseport+FDOUT, 0x00);
#endif
  (void)ftintr_wait(ftu, FTCMD_RESET, hz/10);

  /* raise reset, enable DMA */
#ifdef PC98
  outb(fdc->baseport+FDOUT, FDO_DMAE | FDO_MTON);
#else
  outb(fdc->baseport+FDOUT, FDO_FRST | FDO_FDMAEN);
#endif
  (void)ftintr_wait(ftu, FTCMD_RESET, hz/10);

  splx(s);

  /* set transfer speed */
#ifndef PC98
  outb(fdc->baseport+FDCTL, FDC_500KBPS);
  DELAY(10);
#endif
  fdc->flags &= ~FDC_TAPE_BUSY;

  DPRT(("tape_end end\n"));
}


/*
 *  Wait for the driver to go inactive, cancel readahead if necessary.
 */
static void
tape_inactive(ftu_t ftu)
{
  ft_p	ft = ft_data[ftu];
  int s = splbio();

  if (ft->segh != NULL) {
	if (ft->segh->reqtype == FTIO_RDAHEAD) {
		/* cancel read-ahead */
		ft->segh->reqcan = 1;
	} else if (ft->segh->reqtype == FTIO_WRITING && !ft->active) {
		/* flush out any remaining writes */
		DPRT(("Flushing write I/O chain\n"));
		arq_state = ard_state = awr_state = 0;
		ft->xblk = ft->segh->reqblk;
		ft->xseg = ft->segh->reqseg;
		ft->xcnt = 0;
		ft->xptr = ft->segh->buff;
		ft->active = 1;
		ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, 1);
	}
  }
  while (ft->active) ftsleep(wc_iosts_change, 0);
  splx(s);
}


/*
 *  Get the geometry of the tape currently in the drive.
 */
static int
ftgetgeom(ftu_t ftu)
{
  int r, i, tries;
  int cfg, qic80, ext;
  int sts, fmt, len;
  ft_p	ft = ft_data[ftu];

  r = tape_status(ftu);

  /* XXX fix me when format mode is finished */
  if (r < 0 || (r & QS_CART) == 0 || (r & QS_FMTOK) == 0) {
	DPRT(("ftgetgeom: no cart or not formatted 0x%04x\n",r));
	ftg = NULL;
	ft->newcart = 1;
	return(0);
  }

  /* Report drive configuration */
  for (cfg = -1, tries = 0; cfg < 0 && tries < 3; tries++)
	cfg = qic_status(ftu, QC_CONFIG, 8);
  if (tries == 3) {
	DPRT(("ftgetgeom report config failed\n"));
	ftg = NULL;
	return(-1);
  }
  DPRT(("ftgetgeom report config got $%04x\n", cfg));
  ft->lastcfg = cfg;

  qic80 = cfg & QCF_QIC80;
  ext = cfg & QCF_EXTRA;

/*
 *  XXX - This doesn't seem to work on my Colorado Jumbo 250...
 *  if it works on your drive, I'd sure like to hear about it.
 */
#if 0
  /* Report drive status */
  for (sts = -1, tries = 0; sts < 0 && tries < 3; tries++)
	sts = qic_status(ftu, QC_TSTATUS, 8);
  if (tries == 3) {
	DPRT(("ftgetgeom report tape status failed\n"));
	ftg = NULL;
	return(-1);
  }
  DPRT(("ftgetgeom report tape status got $%04x\n", sts));
#else
  /*
   *  XXX - Forge a fake tape status based upon the returned
   *  configuration, since the above command or code is broken
   *  for my drive and probably other older drives.
   */
  sts = 0;
  sts = (qic80) ? QTS_QIC80 : QTS_QIC40;
  sts |= (ext) ? QTS_LEN2 : QTS_LEN1;
#endif

  fmt = sts & QTS_FMMASK;
  len = (sts & QTS_LNMASK) >> 4;

  if (fmt > QCV_NFMT) {
	ftg = NULL;
	printf("ft%d: unsupported tape format\n", ftu);
	return(-1);
  }
  if (len > QCV_NLEN) {
	ftg = NULL;
	printf("ft%d: unsupported tape length\n", ftu);
	return(-1);
  }

  /* Look up geometry in the table */
  for (i = 1; i < NGEOM; i++)
	if (ftgtbl[i].g_fmtno == fmt && ftgtbl[i].g_lenno == len) break;
  if (i == NGEOM) {
	printf("ft%d: unknown tape geometry\n", ftu);
	ftg = NULL;
	return(-1);
  }
  ftg = &ftgtbl[i];
  if (!ftg->g_trktape) {
	printf("ft%d: unsupported format %s w/len %s\n",
				ftu, ftg->g_fmtdesc, ftg->g_lendesc);
	ftg = NULL;
	return(-1);
  }
  DPRT(("Tape format is %s, length is %s\n", ftg->g_fmtdesc, ftg->g_lendesc));
  ft->newcart = 0;
  return(0);
}


/*
 *  Switch between tape/floppy.  This will send the tape enable/disable
 *  codes for this drive's manufacturer.
 */
static int
set_fdcmode(dev_t dev, int newmode)
{
  ftu_t ftu = FDUNIT(minor(dev));
  ft_p	ft = ft_data[ftu];
  fdc_p	fdc = ft->fdc;
  static int havebufs = 0;
  int i;
  SegReq *sp, *rsp;

  if (newmode == FDC_TAPE_MODE) {
	/* Wake up the tape drive */
	switch (ft->type) {
	    case NO_TYPE:
		fdc->flags &= ~FDC_TAPE_BUSY;
		return(ENXIO);
	    case FT_NONE:
		tape_start(ftu, 0);
		break;
	    case FT_COLORADO:
		tape_start(ftu, 0);
		if (tape_cmd(ftu, QC_COL_ENABLE1)) {
			tape_end(ftu);
			return(EIO);
		}
		if (tape_cmd(ftu, QC_COL_ENABLE2 + ftu)) {
			tape_end(ftu);
			return(EIO);
		}
		break;
	    case FT_MOUNTAIN:
		tape_start(ftu, 0);
		if (tape_cmd(ftu, QC_MTN_ENABLE1)) {
			tape_end(ftu);
			return(EIO);
		}
		if (tape_cmd(ftu, QC_MTN_ENABLE2)) {
			tape_end(ftu);
			return(EIO);
		}
		break;
	    case FT_INSIGHT:
		tape_start(ftu, 1);
		break;
	    default:
		DPRT(("ft%d: bad tape type\n", ftu));
		return(ENXIO);
	}
	if (tape_status(ftu) < 0) {
		if (ft->type == FT_COLORADO)
			tape_cmd(ftu, QC_COL_DISABLE);
		else if (ft->type == FT_MOUNTAIN)
			tape_cmd(ftu, QC_MTN_DISABLE);
		tape_end(ftu);
		return(EIO);
	}

	/* Grab buffers from memory. */
	if (!havebufs) {
		ft->segh = ft->segt = NULL;
		ft->doneh = ft->donet = NULL;
		ft->segfree = NULL;
		ft->hdr = NULL;
		ft->nsegq = ft->ndoneq = ft->nfreelist = 0;
		for (i = 0; i < FTNBUFF; i++) {
			sp = malloc(sizeof(SegReq), M_DEVBUF, M_WAITOK);
			if (sp == NULL) {
				printf("ft%d: not enough memory for buffers\n", ftu);
				for (sp=ft->segfree; sp != NULL; sp=sp->next)
					free(sp, M_DEVBUF);
				if (ft->type == FT_COLORADO)
					tape_cmd(ftu, QC_COL_DISABLE);
				else if (ft->type == FT_MOUNTAIN)
					tape_cmd(ftu, QC_MTN_DISABLE);
				tape_end(ftu);
				return(ENOMEM);
			}
			sp->reqtype = FTIO_READY;
			sp->next = ft->segfree;
			ft->segfree = sp;
			ft->nfreelist++;
		}
		/* take one buffer for header */
		ft->hdr = ft->segfree;
		ft->segfree = ft->segfree->next;
		ft->nfreelist--;
		havebufs = 1;
	}
	ft->io_sts = FTIO_READY;	/* tape drive is ready */
	ft->active = 0;			/* interrupt driver not active */
	ft->moving = 0;			/* tape not moving */
	ft->rdonly = 0;			/* tape read only */
	ft->newcart = 0;		/* new cartridge flag */
	ft->lastpos = -1;		/* tape is rewound */
	async_func = ACMD_NONE;		/* No async function */
	tape_state(ftu, 0, QS_READY, 60);
	tape_cmd(ftu, QC_RATE);
	tape_cmd(ftu, QCF_RT500+2);		/* 500K bps */
	tape_state(ftu, 0, QS_READY, 60);
	ft->mode = FTM_PRIMARY;
	tape_cmd(ftu, QC_PRIMARY);	/* Make sure we're in primary mode */
	tape_state(ftu, 0, QS_READY, 60);
	ftg = NULL;			/* No geometry yet */
	ftgetgeom(ftu);			/* Get tape geometry */
	ftreq_rewind(ftu);		/* Make sure tape is rewound */
  } else {
	if (ft->type == FT_COLORADO)
		tape_cmd(ftu, QC_COL_DISABLE);
	else if (ft->type == FT_MOUNTAIN)
		tape_cmd(ftu, QC_MTN_DISABLE);
	tape_end(ftu);
	ft->newcart = 0;		/* clear new cartridge */
	if (ft->hdr != NULL) free(ft->hdr, M_DEVBUF);
	if (havebufs) {
		for (sp = ft->segfree; sp != NULL;) {
			rsp = sp; sp = sp->next;
			free(rsp, M_DEVBUF);
		}
		for (sp = ft->segh; sp != NULL;) {
			rsp = sp; sp = sp->next;
			free(rsp, M_DEVBUF);
		}
		for (sp = ft->doneh; sp != NULL;) {
			rsp = sp; sp = sp->next;
			free(rsp, M_DEVBUF);
		}
	}
	havebufs = 0;
  }
  return(0);
}


/*
 *  Perform a QIC status function.
 */
static int
qic_status(ftu_t ftu, int cmd, int nbits)
{
  int st3, r, i;
  ft_p	ft = ft_data[ftu];
  fdcu_t fdcu = ft->fdc->fdcu;		/* fdc active unit */

  if (tape_cmd(ftu, cmd)) {
	DPRT(("ft%d: QIC status timeout\n", ftu));
	return(-1);
  }

  /* Sense drive status */
  out_fdc(fdcu, NE7CMD_SENSED);
  out_fdc(fdcu, ftu);
  st3 = in_fdc(fdcu);

  if ((st3 & 0x10) == 0) {	/* track 0 */
	DPRT(("qic_status has dead drive...  st3 = $%02x\n", st3));
	return(-1);
  }

  for (i = r = 0; i <= nbits; i++) {
	if (tape_cmd(ftu, QC_NEXTBIT)) {
		DPRT(("ft%d: QIC status bit timed out on %d\n", ftu, i));
		return(-1);
	}

	out_fdc(fdcu, NE7CMD_SENSED);
	out_fdc(fdcu, ftu);
	st3 = in_fdc(fdcu);
	if (st3 < 0) {
		DPRT(("ft%d: controller timed out on bit %d r=$%02x\n",
					ftu, i, r));
		return(-1);
	}

	r >>= 1;
	if (i < nbits)
		r |= ((st3 & 0x10) ? 1 : 0) << nbits;
	else if ((st3 & 0x10) == 0) {
		DPRT(("ft%d: qic status stop bit missing at %d, st3=$%02x r=$%04x\n",
			ftu,i,st3,r));
		return(-1);
	}
  }

  DPRT(("qic_status returned $%02x\n", r));
  return(r);
}


/*
 *  Open tape drive for use.  Bounced off of Fdopen if tape minor is
 *  detected.
 */
int
ftopen(dev_t dev, int arg2) {
  ftu_t ftu = FDUNIT(minor(dev));
  fdc_p fdc;

  /* check bounds */
  if (ftu >= NFT)
	return(ENXIO);
  if (!ft_data[ftu])
	return(ENXIO);
  fdc = ft_data[ftu]->fdc;
  if ((fdc == NULL) || (ft_data[ftu]->type == NO_TYPE))
	  return(ENXIO);
  /* check for controller already busy with tape */
  if (fdc->flags & FDC_TAPE_BUSY)
	return(EBUSY);
  /* make sure we found a tape when probed */
  if (!(fdc->flags & FDC_HASFTAPE))
	return(ENODEV);
  fdc->fdu = ftu;
  fdc->flags |= FDC_TAPE_BUSY;
  return(set_fdcmode(dev, FDC_TAPE_MODE)); /* try to switch to tape */
}


/*
 *  Close tape and return floppy controller to disk mode.
 */
int
ftclose(dev_t dev, int flags)
{
  ftu_t ftu = FDUNIT(minor(dev));
  ft_p	ft = ft_data[ftu];


  /* Wait for any remaining I/O activity to complete. */
  tape_inactive(ftu);

  ft->mode = FTM_PRIMARY;
  tape_cmd(ftu, QC_PRIMARY);
  tape_state(ftu, 0, QS_READY, 60);
  ftreq_rewind(ftu);
  return(set_fdcmode(dev, FDC_DISK_MODE));	/* Otherwise, close tape */
}

/*
 *  Read or write a segment.
 */
static int
ftreq_rw(ftu_t ftu, unsigned long cmd, QIC_Segment *sr, struct proc *p)
{
  int r, i;
  SegReq *sp;
  int s;
  long blk, bad, seg;
  unsigned char *cp, *cp2;
  ft_p	ft = ft_data[ftu];

  if (!ft->active && ft->segh == NULL) {
	r = tape_status(ftu);
	if ((r & QS_CART) == 0)
		return(ENXIO);	/* No cartridge */
	if ((r & QS_FMTOK) == 0)
		return(ENXIO);	/* Not formatted */
	tape_state(ftu, 0, QS_READY, 90);
  }

  if (ftg == NULL || ft->newcart) {
	tape_inactive(ftu);
	tape_state(ftu, 0, QS_READY, 90);
	if (ftgetgeom(ftu) < 0)
		return(ENXIO);
  }

  /* Write not allowed on a read-only tape. */
  if (cmd == QIOWRITE && ft->rdonly)
	return(EROFS);

  /* Quick check of request and buffer. */
  if (sr == NULL || sr->sg_data == NULL)
	return(EINVAL);

  /* Make sure requested track and segment is in range. */
  if (sr->sg_trk >= ftg->g_trktape || sr->sg_seg >= ftg->g_segtrk)
	return(EINVAL);

  blk = sr->sg_trk * ftg->g_blktrk + sr->sg_seg * QCV_BLKSEG;
  seg = sr->sg_trk * ftg->g_segtrk + sr->sg_seg;

  s = splbio();
  if (cmd == QIOREAD) {
	/*
	 *  See if the driver is reading ahead.
	 */
	if (ft->doneh != NULL ||
		(ft->segh != NULL && ft->segh->reqtype == FTIO_RDAHEAD)) {
		/*
		 *  Eat the completion queue and see if the request
		 *  is already there.
		 */
		while (ft->doneh != NULL) {
			if (blk == ft->doneh->reqblk) {
				sp = ft->doneh;
				sp->reqtype = FTIO_READING;
				sp->reqbad = sr->sg_badmap;
				goto rddone;
			}
			segio_free(ft, ft->doneh);
		}

		/*
		 *  Not on the completed queue, in progress maybe?
		 */
		if (ft->segh != NULL && ft->segh->reqtype == FTIO_RDAHEAD &&
				blk == ft->segh->reqblk) {
			sp = ft->segh;
			sp->reqtype = FTIO_READING;
			sp->reqbad = sr->sg_badmap;
			goto rdwait;
 		}
	}

	/* Wait until we're ready. */
	tape_inactive(ftu);

	/* Set up a new read request. */
	sp = segio_alloc(ft);
	sp->reqcrc = 0;
	sp->reqbad = sr->sg_badmap;
	sp->reqblk = blk;
	sp->reqseg = seg;
	sp->reqcan = 0;
	sp->reqtype = FTIO_READING;
	segio_queue(ft, sp);

	/* Start the read request off. */
	DPRT(("Starting read I/O chain\n"));
	arq_state = ard_state = awr_state = 0;
	ft->xblk = sp->reqblk;
	ft->xseg = sp->reqseg;
	ft->xcnt = 0;
	ft->xptr = sp->buff;
	ft->active = 1;
	ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, 1);

rdwait:
	ftsleep(wc_buff_done, 0);

rddone:
	bad = sp->reqbad;
	sr->sg_crcmap = sp->reqcrc & ~bad;

	/* Copy out segment and discard bad mapped blocks. */
	cp = sp->buff; cp2 = sr->sg_data;
	for (i = 0; i < QCV_BLKSEG; cp += QCV_BLKSIZE, i++) {
		if (bad & (1 << i)) continue;
		copyout(cp, cp2, QCV_BLKSIZE);
		cp2 += QCV_BLKSIZE;
	}
	segio_free(ft, sp);
  } else {
	if (ft->segh != NULL && ft->segh->reqtype != FTIO_WRITING)
		tape_inactive(ftu);

	/* Allocate a buffer and start tape if we're running low. */
	sp = segio_alloc(ft);
	if (!ft->active && (sp == NULL || ft->nfreelist <= 1)) {
		DPRT(("Starting write I/O chain\n"));
		arq_state = ard_state = awr_state = 0;
		ft->xblk = ft->segh->reqblk;
		ft->xseg = ft->segh->reqseg;
		ft->xcnt = 0;
		ft->xptr = ft->segh->buff;
		ft->active = 1;
		ft->tohandle = timeout(ft_timeout, (caddr_t)ftu, 1);
	}

	/* Sleep until a buffer becomes available. */
	while (sp == NULL) {
		ftsleep(wc_buff_avail, 0);
		sp = segio_alloc(ft);
	}

	/* Copy in segment and expand bad blocks. */
	bad = sr->sg_badmap;
	cp = sr->sg_data; cp2 = sp->buff;
	for (i = 0; i < QCV_BLKSEG; cp2 += QCV_BLKSIZE, i++) {
		if (bad & (1 << i)) continue;
		copyin(cp, cp2, QCV_BLKSIZE);
		cp += QCV_BLKSIZE;
	}
	sp->reqblk = blk;
	sp->reqseg = seg;
	sp->reqcan = 0;
	sp->reqtype = FTIO_WRITING;
	segio_queue(ft, sp);
  }
  splx(s);
  return(0);
}


/*
 *  Rewind to beginning of tape
 */
static int
ftreq_rewind(ftu_t ftu)
{
  ft_p	ft = ft_data[ftu];

  tape_inactive(ftu);
  tape_cmd(ftu, QC_STOP);
  tape_state(ftu, 0, QS_READY, 90);
  tape_cmd(ftu, QC_SEEKSTART);
  tape_state(ftu, 0, QS_READY, 90);
  tape_cmd(ftu, QC_SEEKTRACK);
  tape_cmd(ftu, 2);
  tape_state(ftu, 0, QS_READY, 90);
  ft->lastpos = -1;
  ft->moving = 0;
  return(0);
}


/*
 *  Move to logical beginning or end of track
 */
static int
ftreq_trkpos(ftu_t ftu, int req)
{
  int curtrk, r, cmd;
  ft_p	ft = ft_data[ftu];

  tape_inactive(ftu);
  tape_cmd(ftu, QC_STOP);
  tape_state(ftu, 0, QS_READY, 90);

  r = tape_status(ftu);
  if ((r & QS_CART) == 0) return(ENXIO);	/* No cartridge */
  if ((r & QS_FMTOK) == 0) return(ENXIO);	/* Not formatted */

  if (ftg == NULL || ft->newcart) {
	if (ftgetgeom(ftu) < 0) return(ENXIO);
  }

  curtrk = (ft->lastpos < 0) ? 0 : ft->lastpos / ftg->g_blktrk;
  if (req == QIOBOT)
	cmd = (curtrk & 1) ? QC_SEEKEND : QC_SEEKSTART;
  else
	cmd = (curtrk & 1) ? QC_SEEKSTART : QC_SEEKEND;
  tape_cmd(ftu, cmd);
  tape_state(ftu, 0, QS_READY, 90);
  return(0);
}


/*
 *  Seek tape head to a particular track.
 */
static int
ftreq_trkset(ftu_t ftu, int *trk)
{
  int r;
  ft_p	ft = ft_data[ftu];

  tape_inactive(ftu);
  tape_cmd(ftu, QC_STOP);
  tape_state(ftu, 0, QS_READY, 90);

  r = tape_status(ftu);
  if ((r & QS_CART) == 0) return(ENXIO);	/* No cartridge */
  if ((r & QS_FMTOK) == 0) return(ENXIO);	/* Not formatted */
  if (ftg == NULL || ft->newcart) {
	if (ftgetgeom(ftu) < 0) return(ENXIO);
  }

  tape_cmd(ftu, QC_SEEKTRACK);
  tape_cmd(ftu, *trk + 2);
  tape_state(ftu, 0, QS_READY, 90);
  return(0);
}


/*
 *  Start tape moving forward.
 */
static int
ftreq_lfwd(ftu_t ftu)
{
  ft_p	ft = ft_data[ftu];

  tape_inactive(ftu);
  tape_cmd(ftu, QC_STOP);
  tape_state(ftu, 0, QS_READY, 90);
  tape_cmd(ftu, QC_FORWARD);
  ft->moving = 1;
  return(0);
}


/*
 *  Stop the tape
 */
static int
ftreq_stop(ftu_t ftu)
{
  ft_p	ft = ft_data[ftu];

  tape_inactive(ftu);
  tape_cmd(ftu, QC_STOP);
  tape_state(ftu, 0, QS_READY, 90);
  ft->moving = 0;
  return(0);
}


/*
 *  Set the particular mode the drive should be in.
 */
static int
ftreq_setmode(ftu_t ftu, int cmd)
{
  int r;
  ft_p	ft = ft_data[ftu];

  tape_inactive(ftu);
  r = tape_status(ftu);

  switch(cmd) {
     case QIOPRIMARY:
	ft->mode = FTM_PRIMARY;
	tape_cmd(ftu, QC_PRIMARY);
	break;
     case QIOFORMAT:
	if (r & QS_RDONLY) return(ENXIO);
	if ((r & QS_BOT) == 0) return(ENXIO);
	tape_cmd(ftu, QC_FORMAT);
	break;
     case QIOVERIFY:
	if ((r & QS_FMTOK) == 0) return(ENXIO);	/* Not formatted */
	tape_cmd(ftu, QC_VERIFY);
	break;
  }
  tape_state(ftu, 0, QS_READY, 60);
  return(0);
}


/*
 *  Return drive status bits
 */
static int
ftreq_status(ftu_t ftu, unsigned long cmd, int *sts, struct proc *p)
{
  ft_p	ft = ft_data[ftu];

  if (ft->active)
	*sts = ft->laststs & ~QS_READY;
  else
	*sts = tape_status(ftu);
  return(0);
}


/*
 *  Return drive configuration bits
 */
static int
ftreq_config(ftu_t ftu, unsigned long cmd, int *cfg, struct proc *p)
{
  int r, tries;
  ft_p	ft = ft_data[ftu];

  if (ft->active)
	r = ft->lastcfg;
  else {
	for (r = -1, tries = 0; r < 0 && tries < 3; tries++)
		r = qic_status(ftu, QC_CONFIG, 8);
	if (tries == 3) return(ENXIO);
  }
  *cfg = r;
  return(0);
}


/*
 *  Return current tape's geometry.
 */
static int
ftreq_geom(ftu_t ftu, QIC_Geom *g)
{
  tape_inactive(ftu);
  if (ftg == NULL && ftgetgeom(ftu) < 0) return(ENXIO);
  bcopy(ftg, g, sizeof(QIC_Geom));
  return(0);
}


/*
 *  Return drive hardware information
 */
static int
ftreq_hwinfo(ftu_t ftu, QIC_HWInfo *hwp)
{
  int tries;
  int rom, vend;

  tape_inactive(ftu);
  bzero(hwp, sizeof(QIC_HWInfo));

  for (rom = -1, tries = 0; rom < 0 && tries < 3; tries++)
	rom = qic_status(ftu, QC_VERSION, 8);
  if (rom > 0) {
	hwp->hw_rombeta = (rom >> 7) & 0x01;
	hwp->hw_romid = rom & 0x7f;
  }

  for (vend = -1, tries = 0; vend < 0 && tries < 3; tries++)
	vend = qic_status(ftu, QC_VENDORID, 16);
  if (vend > 0) {
	hwp->hw_make = (vend >> 6) & 0x3ff;
	hwp->hw_model = vend & 0x3f;
  }

  return(0);
}


/*
 *  Receive or Send the in-core header segment.
 */
static int
ftreq_hdr(ftu_t ftu, unsigned long cmd, QIC_Segment *sp)
{
  ft_p	ft = ft_data[ftu];
  QIC_Header *h = (QIC_Header *)ft->hdr->buff;

  if (sp == NULL || sp->sg_data == NULL) return(EINVAL);
  if (cmd == QIOSENDHDR) {
	copyin(sp->sg_data, ft->hdr->buff, QCV_SEGSIZE);
  } else {
	if (h->qh_sig != QCV_HDRMAGIC) return(EIO);
	copyout(ft->hdr->buff, sp->sg_data, QCV_SEGSIZE);
  }
  return(0);
}

/*
 *  I/O functions.
 */
int
ftioctl(dev_t dev, unsigned long cmd, caddr_t data, int flag, struct proc *p)
{
  ftu_t ftu = FDUNIT(minor(dev));

  switch(cmd) {
     case QIOREAD:	/* Request reading a segment from tape.		*/
     case QIOWRITE:	/* Request writing a segment to tape.		*/
	return(ftreq_rw(ftu, cmd, (QIC_Segment *)data, p));

     case QIOREWIND:	/* Rewind tape.					*/
	return(ftreq_rewind(ftu));

     case QIOBOT:	/* Seek to logical beginning of track.		*/
     case QIOEOT:	/* Seek to logical end of track.		*/
	return(ftreq_trkpos(ftu, cmd));

     case QIOTRACK:	/* Seek tape head to specified track.		*/
	return(ftreq_trkset(ftu, (int *)data));

     case QIOSEEKLP:	/* Seek load point.				*/
	goto badreq;

     case QIOFORWARD:	/* Move tape in logical forward direction.	*/
	return(ftreq_lfwd(ftu));

     case QIOSTOP:	/* Causes tape to stop.				*/
	return(ftreq_stop(ftu));

     case QIOPRIMARY:	/* Enter primary mode.				*/
     case QIOFORMAT:	/* Enter format mode.				*/
     case QIOVERIFY:	/* Enter verify mode.				*/
	return(ftreq_setmode(ftu, cmd));

     case QIOWRREF:	/* Write reference burst.			*/
	goto badreq;

     case QIOSTATUS:	/* Get drive status.				*/
	return(ftreq_status(ftu, cmd, (int *)data, p));

     case QIOCONFIG:	/* Get tape configuration.			*/
	return(ftreq_config(ftu, cmd, (int *)data, p));

     case QIOGEOM:
	return(ftreq_geom(ftu, (QIC_Geom *)data));

     case QIOHWINFO:
	return(ftreq_hwinfo(ftu, (QIC_HWInfo *)data));

     case QIOSENDHDR:
     case QIORECVHDR:
	return(ftreq_hdr(ftu, cmd, (QIC_Segment *)data));
  }
badreq:
  DPRT(("ft%d: unknown ioctl(%#lx) request\n", ftu, cmd));
  return(ENXIO);
}

#endif
