*BSD News Article 20433


Return to BSD News archive

Path: sserve!newshost.anu.edu.au!munnari.oz.au!news.Hawaii.Edu!ames!agate!howland.reston.ans.net!noc.near.net!ceylon!genesis!steve2
From: steve2@genesis.nred.ma.us
Newsgroups: comp.os.386bsd.development
Subject: ft dist0.2 part 02/03
Message-ID: <CCtK4z.3AC@genesis.nred.ma.us>
Date: 4 Sep 93 07:43:47 GMT
Organization: Genesis Public Access Unix  +1 508 664 0149
Lines: 1986

# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#	driver
#	driver/ft.c
#
echo c - driver
mkdir driver > /dev/null 2>&1
echo x - driver/ft.c
sed 's/^X//' >driver/ft.c << 'END-of-driver/ft.c'
X/*
X *  Copyright (c) 1993 Steve Gerakines
X *
X *  This is freely redistributable software.  You may do anything you
X *  wish with it, so long as the above notice stays intact.
X *
X *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
X *  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
X *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
X *  DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
X *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
X *  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
X *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
X *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
X *  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
X *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
X *  POSSIBILITY OF SUCH DAMAGE.
X *
X *  ft.c - floppy tape driver
X *  08/07/93 v0.2 release
X *  Shifted from ftstrat to ioctl support for I/O.  Streaming is now much
X *  more reliable.  Added internal support for error correction, QIC-40,
X *  and variable length tapes.  Random access of segments greatly
X *  improved.  Formatting and verification support is close but still
X *  incomplete.
X *
X *  06/03/93 v0.1 Alpha release
X *  Hopefully the last re-write.  Many bugs fixed, many remain.
X */
X
X#include "ft.h"
X#if NFT > 0
X
X#include "param.h"
X#include "dkbad.h"
X#include "systm.h"
X#include "conf.h"
X#include "file.h"
X#include "ioctl.h"
X#include "malloc.h"
X#include "buf.h"
X#include "uio.h"
X#include "i386/isa/isa_device.h"
X#include "i386/isa/fdreg.h"
X#include "i386/isa/icu.h"
X#include "i386/isa/rtc.h"
X#include <sys/ftape.h>
X#include "ftreg.h"
X
X/* Enable or disable debugging messages. */
X/*#define DPRT(a) printf a		/**/
X#define DPRT(a) 			/**/
X
X/* Constants private to the driver */
X#define FTPRI		(PRIBIO)	/* sleep priority */
X
X/* The following items are needed from the fd driver. */
Xextern int Fdopen();			/* fd open function */
Xextern int in_fdc();			/* read fdc registers */
Xextern int out_fdc();			/* write fdc registers */
X#undef NFD
X#define NFD	2			/* Ugh. */
X
Xextern int rawread(dev_t, struct uio *, int);
Xextern int rawwrite(dev_t, struct uio *, int);
Xextern int nullop();
Xextern int enodev();
X
Xextern int hz;				/* system clock rate */
Xextern int nblkdev, nchrdev;		/* number of block/char devs */
Xextern struct bdevsw bdevsw[];		/* block device table */
Xextern struct cdevsw cdevsw[];		/* character device table */
X
X/* Type of tape attached */
Xint ft_type;
Xenum { FT_NONE, FT_MOUNTAIN, FT_COLORADO };
X
X/* Mode FDC is currently in: tape or disk */
Xenum { FDC_TAPE_MODE, FDC_DISK_MODE };
Xint fdc_mode = FDC_DISK_MODE;
X
X/* Command we are awaiting completion of */
Xstatic int ftcmd_wait;
Xenum { FTCMD_NONE, FTCMD_RESET, FTCMD_RECAL, FTCMD_SEEK, FTCMD_READID };
X
X/* Tape interrupt status of current request */
Xstatic int ftsts_wait;
Xenum { FTSTS_NONE, FTSTS_SNOOZE, FTSTS_INTERRUPT, FTSTS_TIMEOUT };
X
X/* Tape I/O status */
Xstatic int ftio_sts;
Xenum {
X	FTIO_READY,		/* No I/O activity */
X	FTIO_READING,		/* Currently reading blocks */
X	FTIO_RDAHEAD,		/* Currently reading ahead */
X	FTIO_WRITING		/* Buffers are being written */
X};
X
X/* Current tape mode */
Xenum {
X	FTM_PRIMARY,		/* Primary mode */
X	FTM_VERIFY,		/* Verify mode */
X	FTM_FORMAT,		/* Format mode */
X	FTM_DIAG1,		/* Diagnostic mode 1 */
X	FTM_DIAG2		/* Diagnostic mode 2 */
X};
Xstatic int ftmode = FTM_PRIMARY;
X
X/* Tape geometries table */
XQIC_Geom ftgtbl[] = {
X	{ 0, 0, "Unformatted", "Unknown", 0,  0,     0,   0,     0 }, /* XXX */
X	{ 1, 1, "QIC-40",  "205/550",	20,  68,  2176, 128, 21760 },
X	{ 1, 2, "QIC-40",  "307.5/550",	20, 102,  3264, 128, 32640 },
X	{ 1, 3, "QIC-40",  "295/900",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 1, 4, "QIC-40",  "1100/550",	20, 365, 11680, 128, 32512 },
X	{ 1, 5, "QIC-40",  "1100/900",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 2, 1, "QIC-80",  "205/550",	28, 100,  3200, 128, 19200 },
X	{ 2, 2, "QIC-80",  "307.5/550",	28, 150,  4800, 128, 19200 },
X	{ 2, 3, "QIC-80",  "295/900",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 2, 4, "QIC-80",  "1100/550",	28, 537, 17184, 128, 32512 },
X	{ 2, 5, "QIC-80",  "1100/900",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 3, 1, "QIC-500", "205/550",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 3, 2, "QIC-500", "307.5/550",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 3, 3, "QIC-500", "295/900",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 3, 4, "QIC-500", "1100/550",	 0,   0,     0,   0,     0 }, /* ??? */
X	{ 3, 5, "QIC-500", "1100/900",	 0,   0,     0,   0,     0 }  /* ??? */
X};
X#define NGEOM	(sizeof(ftgtbl) / sizeof(QIC_Geom))
X
XQIC_Geom *ftg = NULL;			/* Current tape's geometry */
X
X/*
X *  things relating to asynchronous commands
X */
Xstatic int astk_depth;		/* async_cmd stack depth */
Xstatic int awr_state;		/* state of async write */
Xstatic int ard_state;		/* state of async read */
Xstatic int arq_state;		/* state of async request */
Xstatic int async_retries;	/* retries, one per invocation */
Xstatic int async_func;		/* function to perform */
Xstatic int async_state;		/* state current function is at */
Xstatic int async_arg[5];	/* up to 5 arguments for async cmds */
Xstatic int async_ret;		/* return value */
X
X/* List of valid async (interrupt driven) tape support functions. */
Xenum {
X	ACMD_NONE, 		/* no command */
X	ACMD_SEEK,		/* command seek */
X	ACMD_STATUS,		/* report status */
X	ACMD_STATE,		/* wait for state bits to be true */
X	ACMD_SEEKSTS,		/* perform command and wait for status */
X	ACMD_READID,		/* read id */
X	ACMD_RUNBLK		/* ready tape for I/O on the given block */
X};
X
X/* Call another asyncronous command from within async_cmd(). */
X#define CALL_ACMD(r,f,a,b,c,d,e) \
X			astk[astk_depth].over_retries = async_retries; \
X			astk[astk_depth].over_func = async_func; \
X			astk[astk_depth].over_state = (r); \
X			for (i = 0; i < 5; i++) \
X			   astk[astk_depth].over_arg[i] = async_arg[i]; \
X			async_func = (f); async_state = 0; async_retries = 0; \
X			async_arg[0]=(a); async_arg[1]=(b); async_arg[2]=(c); \
X			async_arg[3]=(d); async_arg[4]=(e); \
X			astk_depth++; \
X			goto restate
X
X/* Perform an asyncronous command from outside async_cmd(). */
X#define ACMD_FUNC(r,f,a,b,c,d,e) over_async = (r); astk_depth = 0; \
X			async_func = (f); async_state = 0; async_retries = 0; \
X			async_arg[0]=(a); async_arg[1]=(b); async_arg[2]=(c); \
X			async_arg[3]=(d); async_arg[4]=(e); \
X			async_cmd(); \
X			return
X
X/* Various wait channels */
Xstatic struct {
X	int buff_avail;
X	int iosts_change;
X	int long_delay;
X	int wait_chan;
X} ftsem;
X
Xstatic int fdc;			/* fdc address - passed from fd */
Xstatic int ft_pcn;		/* present cylinder number */
Xstatic int ft_attaching;	/* true when ft is attaching */
Xstatic int bi, ci;		/* saved index into blk/chr tables */
Xstatic struct bdevsw fdbdevsw;	/* original block entry */
Xstatic struct cdevsw fdcdevsw;	/* original character entry */
X
Xint ftopen(dev_t, int, int, struct proc *);
Xint ftclose(dev_t, int, int, struct proc *);
Xint ftstrategy(struct buf *);
Xint ftioctl(dev_t, int, caddr_t, int, struct proc *);
Xint ftdump(dev_t);
Xint ftsize(dev_t);
Xint ft_timeout(int);
X
X/* Tape block and character device entries. */
Xstatic struct bdevsw ftbdevsw = {
X  ftopen, ftclose, ftstrategy, ftioctl, ftdump, ftsize, B_TAPE
X};
Xstatic struct cdevsw ftcdevsw = {
X  ftopen, ftclose, rawread, rawwrite, ftioctl,
X  (int (*)(struct tty *, int))enodev, (int (*)(int))nullop, NULL, seltrue,
X  (int (*)())enodev, ftstrategy
X};
X
Xstatic int ftfill;			/* buffer currently being filled  */
Xstatic int ftxfer;			/* buffer currently being xferred */
Xstatic unsigned char *ftxptr;		/* pointer to buffer blk to xfer  */
Xstatic int ftxcnt;			/* transfer count                 */
Xstatic int ftxblk;			/* block number to transfer       */
Xstatic int ftbbase[2];			/* blocks loaded in this buffer   */
Xstatic int ftbcnt[2];			/* count loaded so far		  */
X
Xstatic SegReq *curseg;			/* Current segment to do I/O on	  */
Xstatic SegReq *bufseg;			/* Buffered segment to r/w ahead  */
Xstatic int ftactive = 0;		/* TRUE if transfer is active	  */
Xstatic int ftrdonly = 0;		/* TRUE if tape is read-only	  */
Xstatic int ftnewcart = 0;		/* TRUE if new cartridge detected */
Xstatic int ftlaststs = 0;		/* last reported status code      */
Xstatic int ftlastcfg = 0;		/* last reported QIC config	  */
Xstatic int ftlasterr = 0;		/* last QIC error code 		  */
Xstatic int ftlastpos = -1;		/* last known segment number	  */
Xstatic int ftmoving = 0;		/* TRUE if tape is moving	  */
Xstatic int rid[7];			/* read_id return values	  */
X
X
X/*
X *  Probe/attach floppy tapes.
X */
Xint ftattach(int fd_fdc)
X{
X
X  /* Probe for tape */
X  fdc = fd_fdc;
X  ft_attaching = 1;
X  ft_type = FT_NONE;
X
X  tape_start();			/* ready controller for tape */
X  tape_cmd(QC_COL_ENABLE1);
X  tape_cmd(QC_COL_ENABLE2);
X  if (tape_status() >= 0) {
X	ft_type = FT_COLORADO;
X	tape_cmd(QC_COL_DISABLE);
X	goto out;
X  }
X
X  tape_start();			/* ready controller for tape */
X  tape_cmd(QC_MTN_ENABLE1);
X  tape_cmd(QC_MTN_ENABLE2);
X  if (tape_status() >= 0) {
X	ft_type = FT_MOUNTAIN;
X	tape_cmd(QC_MTN_DISABLE);
X	goto out;
X  }
X
Xout:
X  tape_end();
X  ft_attaching = 0;
X}
X
X
X/*
X *  Perform common commands asynchronously.
X */
Xasync_cmd()
X{
X  int cmd, i, st0, st3, pcn;
X  static int bitn, retval, retpos, nbits, newcn;
X  static struct {
X	int over_func;
X	int over_state;
X	int over_retries;
X	int over_arg[5];
X  } astk[15];
X  static int wanttrk, wantblk, wantdir;
X  static int curpos, curtrk, curblk, curdir, curdiff;
X  static int errcnt = 0;
X
Xrestate:
X#if 0
X  DPRT(("async_cmd state: func: %d  state: %d\n", async_func, async_state));
X#endif
X  switch(async_func) {
X     case ACMD_SEEK:
X	/*
X	 *  Arguments:
X	 *     0 - command to perform
X	 */
X	switch (async_state) {
X	    case 0:
X		cmd = async_arg[0];
X#if 0
X		DPRT(("===>async_seek cmd = %d\n", cmd));
X#endif
X		newcn = (cmd <= ft_pcn) ? ft_pcn - cmd : ft_pcn + cmd;
X		async_state = 1;
X		i = 0;
X		if (out_fdc(NE7CMD_SEEK) < 0) i = 1;
X		if (!i && out_fdc(0x00) < 0) i = 1;
X		if (!i && out_fdc(newcn) < 0) i = 1;
X		if (i) {
X			if (++async_retries >= 10) {
X				printf("ft0: async_cmd command seek failed!!\n");
X				goto complete;
X			}
X			DPRT(("ft0: async_cmd command seek retry...\n"));
X			async_state = 0;
X			goto restate;
X		}
X		break;
X	    case 1:
X		out_fdc(NE7CMD_SENSEI);
X		st0 = in_fdc();
X		pcn = in_fdc();
X		if (st0 < 0 || pcn < 0 || newcn != pcn) {
X			if (++async_retries >= 10) {
X				printf("ft0: async_cmd seek retries exceeded\n");
X				goto complete;
X			}
X			printf("ft0: async_cmd command bad st0=$%02x pcn=$%02x\n", st0, pcn);
X			async_state = 0;
X			timeout(ft_timeout, 0, hz/10);
X			break;
X		}
X		if (st0 & 0x20)	{ 	/* seek done */
X			ft_pcn = pcn;
X		} else
X			printf("ft0: async_seek error st0 = $%02x pcn = %d\n", st0, pcn);
X		if (async_arg[1]) goto complete;
X		async_state = 2;
X		timeout(ft_timeout, 0, hz/50);
X		break;
X	    case 2:
X		goto complete;
X		/* NOTREACHED */
X	}
X	break;
X
X     case ACMD_STATUS:
X	/*
X	 *  Arguments:
X	 *     0 - command to issue report from
X	 *     1 - number of bits
X	 *  modifies: bitn, retval, st3
X	 */
X	switch (async_state) {
X	    case 0:
X		bitn = 0;
X		retval = 0;
X		cmd = async_arg[0];
X		nbits = async_arg[1];
X		DPRT(("async_status got cmd = %d nbits = %d\n", cmd,nbits));
X		CALL_ACMD(5, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0);
X		/* NOTREACHED */
X	    case 1:
X		out_fdc(NE7CMD_SENSED);
X		out_fdc(0x00);
X		st3 = in_fdc();
X		if (st3 < 0) {
X			printf("ft0: async_status timed out on bit %d r=$%02x\n",bitn,retval);
X			async_ret = -1;
X			goto complete;
X		}
X		if ((st3 & 0x10) != 0) retval |= (1 << bitn);
X		bitn++;
X		if (bitn >= (nbits+2)) {
X			if ((retval & 1) && (retval & (1 << (nbits+1)))) {
X				async_ret = (retval & ~(1<<(nbits+1))) >> 1;
X				if (async_arg[0] == QC_STATUS && async_arg[2] == 0 &&
X				   (async_ret & (QS_ERROR|QS_NEWCART))) {
X					async_state = 2;
X					goto restate;
X				}
X				DPRT(("async status got $%04x ($%04x)\n", async_ret,retval));
X 			} else {
X				printf("ft0: async_status failed: retval=$%04x nbits=%d\n",retval,nbits);
X				async_ret = -2; 
X			}
X			goto complete;
X		}
X		CALL_ACMD(1, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0);
X		/* NOTREACHED */
X	    case 2:
X		if (async_ret & QS_NEWCART) ftnewcart = 1;
X		CALL_ACMD(3, ACMD_STATUS, QC_ERRCODE, 16, 1, 0, 0);
X	    case 3:
X		ftlasterr = async_ret;
X		if ((ftlasterr & QS_NEWCART) == 0 && ftlasterr) {
X			DPRT(("ft0: QIC error %d occurred on cmd %d\n",
X				ftlasterr & 0xff, ftlasterr >> 8));
X		}
X	        cmd = async_arg[0];
X		nbits = async_arg[1];
X		CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 1, 0, 0);
X	    case 4:
X		goto complete;
X	    case 5:
X		CALL_ACMD(6, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0);
X	    case 6:
X		CALL_ACMD(7, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0);
X	    case 7:
X		CALL_ACMD(8, ACMD_SEEK, QC_NEXTBIT, 0, 0, 0, 0);
X	    case 8:
X		cmd = async_arg[0];
X		CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0, 0, 0);
X	}
X	break;
X
X     case ACMD_STATE:
X	/*
X	 *  Arguments:
X	 *     0 - status bits to check
X	 */
X	switch(async_state) {
X	    case 0:
X		CALL_ACMD(1, ACMD_STATUS, QC_STATUS, 8, 0, 0, 0);
X	    case 1:
X		if ((async_ret & async_arg[0]) != 0) goto complete;
X		async_state = 0;
X		if (++async_retries == 10) {
X			printf("ft0: acmd_state exceeded retry count\n");
X			goto complete;
X		}
X		timeout(ft_timeout, 0, hz/4);
X		break;
X	}
X	break;
X
X     case ACMD_SEEKSTS:
X	/*
X	 *  Arguments:
X	 *     0 - command to perform
X	 *     1 - status bits to check
X	 *     2 - (optional) seconds to wait until completion
X	 */
X	switch(async_state) {
X	    case 0:
X		cmd = async_arg[0];
X		async_retries = (async_arg[2]) ? (async_arg[2]*4) : 10;
X		CALL_ACMD(1, ACMD_SEEK, cmd, 0, 0, 0, 0);
X	    case 1:
X		CALL_ACMD(2, ACMD_STATUS, QC_STATUS, 8, 0, 0, 0);
X	    case 2:
X		if ((async_ret & async_arg[1]) != 0) goto complete;
X		if (--async_retries == 0) {
X			printf("ft0: acmd_seeksts retries exceeded\n");
X			goto complete;
X		}
X		async_state = 1;
X		timeout(ft_timeout, 0, hz/4);
X		break;
X	}
X	break;
X
X     case ACMD_READID:
X	/*
X	 *  Arguments: (none)
X	 */
X	switch(async_state) {
X	    case 0:
X		if (!ftmoving) {
X			CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0);
X			/* NOTREACHED */
X		}
X		async_state = 1;
X		out_fdc(0x4a);		/* READ_ID */
X		out_fdc(0);
X		break;
X	    case 1:
X		for (i = 0; i < 7; i++) rid[i] = in_fdc();
X		async_ret = (rid[3]*ftg->g_fdtrk) + (rid[4]*ftg->g_fdside) + rid[5] - 1;
X		DPRT(("readid st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d\n",
X			rid[0], rid[1], rid[2], rid[3], rid[4], rid[5],	async_ret));
X		if ((rid[0] & 0xc0) == 0x40) {
X			if (++errcnt >= 10) {
X				DPRT(("ft0: acmd_readid errcnt exceeded\n"));
X				async_ret = ftlastpos;
X				errcnt = 0;
X				goto complete;
X			}
X			if (errcnt > 2) {
X				ftmoving = 0;
X				CALL_ACMD(4, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0);
X			}
X			DPRT(("readid retry...\n"));
X			async_state = 0;
X			goto restate;
X		}
X		if ((async_ret % ftg->g_blktrk) == (ftg->g_blktrk-1)) {
X			DPRT(("acmd_readid detected last block on track\n"));
X			retpos = async_ret;
X			CALL_ACMD(2, ACMD_STATE, QS_BOT|QS_EOT, 0, 0, 0, 0);
X			/* NOTREACHED */
X		}
X		ftlastpos = async_ret;
X		errcnt = 0;
X		goto complete;
X		/* NOTREACHED */
X	    case 2:
X		CALL_ACMD(3, ACMD_STATE, QS_READY, 0, 0, 0, 0);
X	    case 3:
X		ftmoving = 0;
X		async_ret = retpos+1;
X		goto complete;
X	    case 4:
X		CALL_ACMD(5, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0);
X	    case 5:
X		ftmoving = 1;
X		async_state = 0;
X		timeout(ft_timeout, 0, hz/4);
X		break;
X	}
X	break;
X
X     case ACMD_RUNBLK:
X	/*
X	 *  Arguments:
X	 *     0 - block number I/O will be performed on
X	 *
X	 *  modifies: curpos
X	 */
X	switch (async_state) {
X	    case 0:
X		wanttrk = async_arg[0] / ftg->g_blktrk;
X		wantblk = async_arg[0] % ftg->g_blktrk;
X		wantdir = wanttrk & 1;
X		ftmoving = 0;
X		CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0);
X	    case 1:
X		curtrk = wanttrk;
X		curdir = curtrk & 1;
X		DPRT(("Changing to track %d\n", wanttrk));
X		CALL_ACMD(2, ACMD_SEEK, QC_SEEKTRACK, 0, 0, 0, 0);
X	    case 2:
X		cmd = wanttrk+2;
X		CALL_ACMD(3, ACMD_SEEKSTS, cmd, QS_READY, 0, 0, 0);
X	    case 3:
X		CALL_ACMD(4, ACMD_STATUS, QC_STATUS, 8, 0, 0, 0);
X	    case 4:
X		ftlaststs = async_ret;
X		if (wantblk == 0) {
X			curblk = 0;
X			cmd = (wantdir) ? QC_SEEKEND : QC_SEEKSTART;
X			CALL_ACMD(6, ACMD_SEEKSTS, cmd, QS_READY, 90, 0, 0);
X		}
X		if (ftlaststs & QS_BOT) {
X			DPRT(("Tape is at BOT\n"));
X			curblk = (wantdir) ? 4800 : 0;
X			async_state = 6;
X			goto restate;
X		}
X		if (ftlaststs & QS_EOT) {
X			DPRT(("Tape is at EOT\n"));
X			curblk = (wantdir) ? 0 : 4800;
X			async_state = 6;
X			goto restate;
X		}
X		CALL_ACMD(5, ACMD_READID, 0, 0, 0, 0, 0);
X	    case 5:
X		curtrk = (async_ret+1) / ftg->g_blktrk;
X		curblk = (async_ret+1) % ftg->g_blktrk;
X		DPRT(("gotid: curtrk=%d wanttrk=%d curblk=%d wantblk=%d\n",
X			curtrk, wanttrk, curblk, wantblk));
X		if (curtrk != wanttrk) {	/* oops! */
X			DPRT(("oops!! wrong track!\n"));
X			CALL_ACMD(1, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0);
X		}
X		async_state = 6;
X		goto restate;
X	    case 6:
X		DPRT(("curtrk = %d nextblk = %d\n", curtrk, curblk));
X		if (curblk == wantblk) {
X			ftlastpos = curblk - 1;
X			async_ret = ftlastpos;
X			if (ftmoving) goto complete;
X			CALL_ACMD(7, ACMD_STATE, QS_READY, 0, 0, 0, 0);
X		}
X		if (curblk > wantblk) {		/* passed it */
X			ftmoving = 0;
X			CALL_ACMD(10, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0);
X		}
X		if ((wantblk - curblk) <= 96) {	/* approaching it */
X			CALL_ACMD(5, ACMD_READID, 0, 0, 0, 0, 0);
X		}
X		/* way up ahead */
X		ftmoving = 0;
X		CALL_ACMD(14, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0);
X		break;
X	    case 7:
X		ftmoving = 1;
X		CALL_ACMD(8, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0);
X		break;
X	    case 8:
X		async_state = 9;
X		timeout(ft_timeout, 0, hz/4);
X		break;
X	    case 9:
X		goto complete;
X	    case 10:
X		curdiff = ((curblk - wantblk) / QCV_BLKSEG) + 2;
X		if (curdiff >= ftg->g_segtrk) curdiff = ftg->g_segtrk - 1;
X		DPRT(("pos %d past %d, reverse %d\n", curblk, wantblk, curdiff));
X		CALL_ACMD(11, ACMD_SEEK, QC_SEEKREV, 0, 0, 0, 0);
X	    case 11:
X		DPRT(("reverse 1 done\n"));
X		CALL_ACMD(12, ACMD_SEEK, (curdiff & 0xf)+2, 0, 0, 0, 0);
X	    case 12:
X		DPRT(("reverse 2 done\n"));
X		CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff>>4)&0xf)+2, QS_READY, 90, 0, 0);
X	    case 13:
X		CALL_ACMD(5, ACMD_READID, 0, 0, 0, 0, 0);
X	    case 14:
X		curdiff = ((wantblk - curblk) / QCV_BLKSEG) - 2;
X		if (curdiff < 0) curdiff = 0;
X		DPRT(("pos %d before %d, forward %d\n", curblk, wantblk, curdiff));
X		CALL_ACMD(15, ACMD_SEEK, QC_SEEKFWD, 0, 0, 0, 0);
X	    case 15:
X		DPRT(("forward 1 done\n"));
X		CALL_ACMD(16, ACMD_SEEK, (curdiff & 0xf)+2, 0, 0, 0, 0);
X	    case 16:
X		DPRT(("forward 2 done\n"));
X		CALL_ACMD(13, ACMD_SEEKSTS, ((curdiff>>4)&0xf)+2, QS_READY, 90, 0, 0);
X	}
X	break;
X  }
X
X  return;
X
Xcomplete:
X  if (astk_depth) {
X	astk_depth--;
X	async_retries = astk[astk_depth].over_retries;
X	async_func = astk[astk_depth].over_func;
X	async_state = astk[astk_depth].over_state;
X	for(i = 0; i < 5; i++)
X		async_arg[i] = astk[astk_depth].over_arg[i];
X	goto restate;
X  }
X  async_func = ACMD_NONE;
X  async_state = 0;
X  switch (ftio_sts) {
X     case FTIO_READY:
X	async_req(2);
X	break;
X     case FTIO_READING:
X	async_read(2);
X	break;
X     case FTIO_WRITING:
X	async_write(2);
X	break;
X     default:
X	printf("ft0: bad async_cmd ending I/O state!\n");
X	break;
X  }
X}
X
X
X/*
X *  Entry point for the async request processor.
X */
Xasync_req(int from)
X{
X  SegReq *sp;
X  static int over_async;
X  static int endoftrk = 0;
X  int cmd;
X
X  if (from == 2) arq_state = over_async;
X
Xrestate:
X  switch (arq_state) {
X     case 0:	/* Process segment */
X	ftio_sts = curseg->reqtype;
X	if (ftio_sts == FTIO_WRITING)
X		async_write(from);
X	else
X		async_read(from);
X	if (ftio_sts != FTIO_READY) return;
X
X	/* Swap buffered and current segment */
X	sp = curseg;
X	curseg = bufseg;
X	bufseg = sp;
X
X	wakeup(&ftsem.buff_avail);
X
X	/* Detect end of track */
X	endoftrk = ((ftxblk / QCV_BLKSEG) % ftg->g_segtrk) == 0;
X
X	if (endoftrk) {
X		ACMD_FUNC(2, ACMD_STATE, QS_BOT|QS_EOT, 0, 0, 0, 0);
X	}
X	arq_state = 1;
X	goto restate;
X
X     case 1:	/* Next request */
X	if (curseg->reqtype != FTIO_READY) {
X		bufseg->reqtype = FTIO_READY;
X		curseg->reqcrc = 0;
X		arq_state = ard_state = awr_state = 0;
X		ftxblk = curseg->reqblk;
X		ftxcnt = 0;
X		ftxptr = curseg->buff;
X		DPRT(("Processing I/O reqblk = %d\n", curseg->reqblk));
X		goto restate;
X	}
X	if (bufseg->reqtype == FTIO_READING && endoftrk == 0) {
X		bufseg->reqtype = FTIO_READY;
X		curseg->reqtype = FTIO_RDAHEAD;
X		curseg->reqblk = ftxblk;
X		curseg->reqcrc = 0;
X		curseg->reqcan = 0;
X		bzero(curseg->buff, QCV_SEGSIZE);
X		arq_state = ard_state = awr_state = 0;
X		ftxblk = curseg->reqblk;
X		ftxcnt = 0;
X		ftxptr = curseg->buff;
X		DPRT(("Processing readahead reqblk = %d\n", curseg->reqblk));
X		goto restate;
X	}
X
X	bufseg->reqtype = FTIO_READY;
X
X	if (ftmoving) {
X		DPRT(("No more I/O.. Stopping.\n"));
X		ACMD_FUNC(7, ACMD_SEEKSTS, QC_STOP, QS_READY, 0, 0, 0);
X		break;
X	}
X	arq_state = 7;
X	goto restate;
X
X     case 2:	/* End of track */
X	ftmoving = 0;
X	ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0, 0, 0);
X	break;
X
X     case 3:
X	DPRT(("async_req seek head to track %d\n", ftxblk / ftg->g_blktrk));
X	ACMD_FUNC(4, ACMD_SEEK, QC_SEEKTRACK, 0, 0, 0, 0);
X	break;
X
X     case 4:
X	cmd = (ftxblk / ftg->g_blktrk) + 2;
X	ACMD_FUNC(5, ACMD_SEEKSTS, cmd, QS_READY, 0, 0, 0);
X	break;
X
X     case 5:
X	ftmoving = 1;
X	ACMD_FUNC(6, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0);
X	break;
X
X     case 6:
X	arq_state = 1;
X	timeout(ft_timeout, 0, hz/4);
X	break;
X
X     case 7:
X	ftmoving = 0;
X
X	/* Check one last time to see if a request came in. */
X	if (curseg->reqtype != FTIO_READY) {
X		DPRT(("async_req: Never say no!\n"));
X		arq_state = 1;
X		goto restate;
X	}
X
X	/* Time to rest. */
X	ftactive = 0;
X	wakeup(&ftsem.iosts_change);	/* wakeup those who want an i/o chg */
X	break;
X  }
X}
X
X/*
X *  Entry for async read.
X */
Xasync_read(int from)
X{
X  unsigned long paddr;
X  int i, cmd, newcn, rddta[7];
X  int st0, pcn;
X  static int over_async;
X
X  if (from == 2) ard_state = over_async;
X
Xrestate:
X#if 0
X  DPRT(("async_read: state: %d  from = %d\n", ard_state, from));
X#endif
X  switch (ard_state) {
X     case 0:	/* Start off */
X	/* If tape is not at desired position, stop and locate */
X	if (ftlastpos != (ftxblk-1)) {
X		DPRT(("ft0: position unknown: lastpos:%d ftxblk:%d\n",
X			ftlastpos, ftxblk));
X		ACMD_FUNC(1, ACMD_RUNBLK, ftxblk, 0, 0, 0, 0);
X	}
X
X	/* Tape is in position but stopped. */
X	if (!ftmoving) {
X		DPRT(("async_read ******STARTING TAPE\n"));
X		ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0, 0, 0);
X	}
X	ard_state = 1;
X	goto restate;
X
X     case 1:	/* Start DMA */
X	/* Tape is now moving and in position-- start DMA now! */
X	isa_dmastart(B_READ, ftxptr, QCV_BLKSIZE, 2);
X	out_fdc(0x66);					/* read */
X	out_fdc(0x00);					/* unit */
X	out_fdc((ftxblk % ftg->g_fdside) / ftg->g_fdtrk);	/* cylinder */
X	out_fdc(ftxblk / ftg->g_fdside);			/* head */
X	out_fdc((ftxblk % ftg->g_fdtrk) + 1);		/* sector */
X	out_fdc(0x03);					/* 1K sectors */
X	out_fdc((ftxblk % ftg->g_fdtrk) + 1);		/* count */
X	out_fdc(0x74);					/* gap length */
X	out_fdc(0xff);					/* transfer size */
X	ard_state = 2;
X	break;
X
X     case 2:	/* DMA completed */
X	/* Check for positional error. */
X	for (i = 0; i < 7; i++) rddta[i] = in_fdc();
X	isa_dmadone(B_READ, ftxptr, QCV_BLKSIZE, 2);
X
X	i = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside) + rddta[5] - 1;
X#if 0
X	if (rddta[0] || rddta[1] || rddta[2]) {
X		DPRT(("st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
X		    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
X		    i, ftxblk));
X	}
X#endif
X	if ((rddta[0] & 0xc0) == 0x40) {
X		if (rddta[1] & 0x20) {
X#if 0
X				DPRT(("ft0: CRC error on block %d\n", ftxblk));
X#endif
X				curseg->reqcrc |= (1 << ftxcnt);
X		} else {
X			/* Probably wrong position */
X			ftlastpos = ftxblk;
X			ard_state = 0;
X			goto restate;
X		}
X	}
X#if 0
X	DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
X	    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
X	    i, ftxblk));
X#endif
X
X	/* Otherwise, transfer completed okay. */
X	ftlastpos = ftxblk;
X	ftxblk++;
X	ftxcnt++;
X	ftxptr += QCV_BLKSIZE;
X	if (ftxcnt < QCV_BLKSEG && curseg->reqcan == 0) {
X		ard_state = 0;
X		goto restate;
X	}
X	DPRT(("Read done..  Cancel = %d\n", curseg->reqcan));
X	ftio_sts = FTIO_READY;
X	break;
X
X     case 3:
X	ftmoving = 1;
X	ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0);
X	break;
X
X     case 4:
X	ard_state = 1;
X	timeout(ft_timeout, 0, hz/4);
X	break;
X
X     default:
X	printf("ft0: bad async_read state %d!!\n", ard_state);
X	break;
X  }
X}
X
X
X/*
X *  Entry for async write.  If from is 0, this came from the interrupt
X *  routine, if it's 1 then it was a timeout, if it's 2, then an
X *  async_cmd completed.
X */
Xasync_write(int from)
X{
X  unsigned long paddr;
X  int i, cmd, newcn, rddta[7];
X  int st0, pcn;
X  static int over_async;
X
X  if (from == 2) awr_state = over_async;
X
Xrestate:
X#if 0
X  DPRT(("async_write: state: %d  from = %d\n", awr_state, from));
X#endif
X  switch (awr_state) {
X     case 0:	/* Start off */
X	/* If tape is not at desired position, stop and locate */
X	if (ftlastpos != (ftxblk-1)) {
X		DPRT(("ft0: position unknown: lastpos:%d ftxblk:%d\n",
X			ftlastpos, ftxblk));
X		ACMD_FUNC(1, ACMD_RUNBLK, ftxblk, 0, 0, 0, 0);
X	}
X
X	/* Tape is in position but stopped. */
X	if (!ftmoving) {
X		DPRT(("async_write ******STARTING TAPE\n"));
X		ACMD_FUNC(3, ACMD_STATE, QS_READY, 0, 0, 0, 0);
X	}
X	awr_state = 1;
X	goto restate;
X
X     case 1:	/* Start DMA */
X	/* Tape is now moving and in position-- start DMA now! */
X	isa_dmastart(B_WRITE, ftxptr, QCV_BLKSIZE, 2);
X	out_fdc(0x45);					/* write */
X	out_fdc(0x00);					/* unit */
X	out_fdc((ftxblk % ftg->g_fdside) / ftg->g_fdtrk);	/* cylinder */
X	out_fdc(ftxblk / ftg->g_fdside);			/* head */
X	out_fdc((ftxblk % ftg->g_fdtrk) + 1);		/* sector */
X	out_fdc(0x03);					/* 1K sectors */
X	out_fdc((ftxblk % ftg->g_fdtrk) + 1);		/* count */
X	out_fdc(0x74);					/* gap length */
X	out_fdc(0xff);					/* transfer size */
X	awr_state = 2;
X	break;
X
X     case 2:	/* DMA completed */
X	/* Check for positional error. */
X	for (i = 0; i < 7; i++) rddta[i] = in_fdc();
X	isa_dmadone(B_WRITE, ftxptr, QCV_BLKSIZE, 2);
X
X	i = (rddta[3]*ftg->g_fdtrk) + (rddta[4]*ftg->g_fdside) + rddta[5] - 1;
X	if (rddta[0] || rddta[1] || rddta[2]) {
X		DPRT(("st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
X		    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
X		    i, ftxblk));
X	}
X	if ((rddta[0] & 0xc0) == 0x40) {
X		if (rddta[1] & 0x20) {
X			DPRT(("ft0: CRC error on block %d\n", ftxblk));
X			curseg->reqcrc |= (1 << ftxcnt);
X		} else {
X			/* Probably wrong position */
X			ftlastpos = ftxblk;
X			awr_state = 0;
X			goto restate;
X		}
X	}
X#if 0
X	DPRT(("xfer done: st0:%02x st1:%02x st2:%02x c:%d h:%d s:%d pos:%d want:%d\n",
X	    rddta[0], rddta[1], rddta[2], rddta[3], rddta[4], rddta[5],
X	    i, ftxblk));
X#endif
X	isa_dmadone(B_WRITE, ftxptr, QCV_BLKSIZE, 2);
X
X	/* Otherwise, transfer completed okay. */
X	ftlastpos = ftxblk;
X	ftxblk++;
X	ftxcnt++;
X	ftxptr += QCV_BLKSIZE;
X	if (ftxcnt < QCV_BLKSEG) {
X		awr_state = 0;	/* next block */
X		goto restate;
X	}
X	DPRT(("Write done.\n"));
X	ftio_sts = FTIO_READY;
X	break;
X
X     case 3:
X	ftmoving = 1;
X	ACMD_FUNC(4, ACMD_SEEK, QC_FORWARD, 0, 0, 0, 0);
X	break;
X
X     case 4:
X	awr_state = 1;
X	timeout(ft_timeout, 0, hz/4);
X	break;
X
X     default:
X	printf("ft0: bad async_write state %d!!\n", awr_state);
X	break;
X  }
X}
X
X
X/*
X *  Interrupt handler for active tape.  Bounced off of fdintr().
X */
Xftintr(int unit)
X{
X  int st0, pcn, i;
X
X  /* I/O segment transfer completed */
X  if (ftactive) {
X	if (async_func != ACMD_NONE) {
X		async_cmd();
X		return(1);
X	}
X#if 0
X	DPRT(("Got request interrupt\n"));
X#endif
X	async_req(0);
X	return(1);
X  }
X
X  /* Get interrupt status */
X  if (ftcmd_wait != FTCMD_READID) {
X	out_fdc(NE7CMD_SENSEI);
X	st0 = in_fdc();
X	pcn = in_fdc();
X  }
X
X  if (ftcmd_wait == FTCMD_NONE || ftsts_wait != FTSTS_SNOOZE) {
Xhuh_what:
X	printf("ft0: unexpected interrupt; st0 = $%02x pcn = %d\n", st0, pcn);
X	return(1);
X  }
X
X  switch (ftcmd_wait) {
X     case FTCMD_RESET:
X	ftsts_wait = FTSTS_INTERRUPT;
X	wakeup(&ftsem.wait_chan);
X	break;
X     case FTCMD_RECAL:
X     case FTCMD_SEEK:
X	if (st0 & 0x20)	{ 	/* seek done */
X		ftsts_wait = FTSTS_INTERRUPT;
X		ft_pcn = pcn;
X		wakeup(&ftsem.wait_chan);
X	} else
X		printf("ft0: seek error st0 = $%02x pcn = %d\n", st0, pcn);
X	break;
X     case FTCMD_READID:
X	for (i = 0; i < 7; i++) rid[i] = in_fdc();
X	ftsts_wait = FTSTS_INTERRUPT;
X	wakeup(&ftsem.wait_chan);
X	break;
X
X     default:
X	goto huh_what;
X  }
X
X  return(1);
X}
X
X/*
X *  Interrupt timeout routine.
X */
Xstatic int ft_timeout(int unit)
X{
X  if (ftactive) {
X	if (async_func != ACMD_NONE) {
X		async_cmd();
X		return;
X	}
X	async_req(1);
X  } else {
X	  ftsts_wait = FTSTS_TIMEOUT;
X	  wakeup(&ftsem.wait_chan);
X  }
X}
X
X/*
X *  Wait for a particular interrupt to occur.  ftintr() will wake us up
X *  if it sees what we want.  Otherwise, time out and return error.
X *  Should always disable ints before trigger is sent and calling here.
X */
Xftintr_wait(int cmd, int ticks)
X{
X  int retries, st0, pcn;
X
X  ftcmd_wait = cmd;
X  ftsts_wait = FTSTS_SNOOZE;
X
X  /* At attach time, we can't rely on having interrupts serviced */
X  if (ft_attaching) {
X	switch (cmd) {
X	    case FTCMD_RESET:
X		DELAY(2000);
X		ftsts_wait = FTSTS_INTERRUPT;
X		goto intrdone;
X	    case FTCMD_RECAL:
X	    case FTCMD_SEEK:
X		for (retries = 0; retries < 10000; retries++) {
X			out_fdc(NE7CMD_SENSEI);
X			st0 = in_fdc();
X			pcn = in_fdc();
X			if (st0 & 0x20) {
X				ftsts_wait = FTSTS_INTERRUPT;
X				ft_pcn = pcn;
X				goto intrdone;
X			}
X			DELAY(2000);
X		}
X		break;
X	}
X	ftsts_wait = FTSTS_TIMEOUT;
X	goto intrdone;
X  }
X
X  if (ticks) timeout(ft_timeout, 0, ticks);
X  sleep(&ftsem.wait_chan, FTPRI);
X
Xintrdone:
X  if (ftsts_wait == FTSTS_TIMEOUT) {	/* timeout */
X	if (ftcmd_wait != FTCMD_RESET)
X		printf("ft0: timeout on command %d\n", ftcmd_wait);
X	ftcmd_wait = FTCMD_NONE;
X	ftsts_wait = FTSTS_NONE;
X	return(1);
X  }
X
X  /* got interrupt */
X  if (ft_attaching == 0 && ticks) untimeout(ft_timeout, 0);
X  ftcmd_wait = FTCMD_NONE;
X  ftsts_wait = FTSTS_NONE;
X  return(0);
X}
X
X/*
X *  Recalibrate tape drive.  Parameter totape is true, if we should
X *  recalibrate to tape drive settings.
X */
Xint tape_recal(int totape)
X{
X  int s;
X
X  DPRT(("tape_recal start\n"));
X
X  out_fdc(NE7CMD_SPECIFY);
X  out_fdc((totape) ? 0xAD : 0xDF);
X  out_fdc(0x02);
X
X  s = splbio();
X  out_fdc(NE7CMD_RECAL);
X  out_fdc(0x00);
X  if (ftintr_wait(FTCMD_RECAL, 100)) {
X	splx(s);
X	printf("ft0: recalibrate timeout\n");
X	return(1);
X  }
X  splx(s);
X
X  out_fdc(NE7CMD_SPECIFY);
X  out_fdc((totape) ? 0xFD : 0xDF);
X  out_fdc(0x02);
X
X  DPRT(("tape_recal end\n"));
X  return(0);
X}
X
Xint state_timeout()
X{
X  wakeup(&ftsem.long_delay);
X}
X
X/*
X *  Wait for a particular tape status to be met.  If all is TRUE, then
X *  all states must be met, otherwise any state can be met.
X */
Xint tape_state(int all, int mask, int seconds)
X{
X  int r, tries, maxtries;
X
X  maxtries = (seconds) ? (4 * seconds) : 1;
X  for (tries = 0; tries < maxtries; tries++) {
X	r = tape_status();
X	if (r >= 0) {
X		if (all && (r & mask) == mask) return(r);
X		if ((r & mask) != 0) return(r);
X	}
X	if (seconds) {
X		timeout(state_timeout, 0, hz/4);
X		sleep(&ftsem.long_delay, FTPRI);
X	}
X  }
X  DPRT(("ft0: tape_state failed on mask=$%02x maxtries=%d\n", mask, maxtries));
X  return(-1);
X}
X
X/*
X *  Send a QIC command to tape drive, wait for completion.
X */
Xint tape_cmd(int cmd)
X{
X  int newcn;
X  int retries = 0;
X  int s;
X
X  DPRT(("===> tape_cmd: %d\n",cmd));
X  newcn = (cmd <= ft_pcn) ? ft_pcn - cmd : ft_pcn + cmd;
X
Xretry:
X
X  /* Perform seek */
X  s = splbio();
X  out_fdc(NE7CMD_SEEK);
X  out_fdc(0x00);
X  out_fdc(newcn);
X
X  if (ftintr_wait(FTCMD_SEEK, 250)) {
X	DPRT(("ft0: tape_cmd seek timeout\n"));
Xredo:
X	splx(s);
X	if (++retries < 5) goto retry;
X	printf("ft0: tape_cmd seek failed!\n");
X	return(1);
X  }
X  splx(s);
X
X  if (ft_pcn != newcn) {
X	printf("ft0: bad seek in tape_cmd; pcn = %d  newcn = %d\n", ft_pcn, newcn);
X	goto redo;
X  }
X  DELAY(2500);
X  return(0);
X}
X
X/*
X *  Return status of tape drive
X */
Xtape_status(void)
X{
X  int r, err, tries;
X
X  for (r = -1, tries = 0; r < 0 && tries < 3; tries++)
X	r = qic_status(QC_STATUS, 8);
X  if (tries == 3) return(-1);
X  DPRT(("tape_status got $%04x\n",r));
X  ftlaststs = r;
X
X  if (r & (QS_ERROR|QS_NEWCART)) {
X	if (r & QS_NEWCART) ftnewcart = 1;
X	err = qic_status(QC_ERRCODE, 16);
X	ftlasterr = err;
X	if ((r & QS_NEWCART) == 0 && err && ft_attaching == 0) {
X		DPRT(("ft0: QIC error %d occurred on cmd %d\n", err & 0xff, err >> 8));
X	}
X	r = qic_status(QC_STATUS, 8);
X	ftlaststs = r;
X	DPRT(("tape_status got error code $%04x new sts = $%02x\n",err,r));
X  }
X  ftrdonly = (r & QS_RDONLY);
X  return(r);
X}
X
X/*
X *  Transfer control to tape drive.
X */
Xtape_start(void)
X{
X  int s;
X
X  DPRT(("tape_start start\n"));
X  fdc_mode = FDC_TAPE_MODE;
X
X  /* reset, dma disable */
X  s = splbio();
X  outb(fdc+fdout, 0x00);
X  DELAY(5000);
X
X  /* raise reset, enable DMA */
X  outb(fdc+fdout, FDO_FRST | FDO_FDMAEN);
X  DELAY(10);
X
X  /* set transfer speed */
X  outb (fdc+fdctl, FDC_500KBPS);
X  DELAY(10);
X
X  (void)ftintr_wait(FTCMD_RESET, 500);
X  splx(s);
X  tape_recal(1);
X  DPRT(("tape_start end\n"));
X}
X
X/*
X *  Transfer control back to floppy disks.
X */
Xtape_end(void)
X{
X  int s;
X
X  DPRT(("tape_end start\n"));
X  tape_recal(0);
X
X  s = splbio();
X  outb (fdc+fdctl, FDC_500KBPS);
X  DELAY(10);
X
X  outb(fdc+fdout, 0x00);
X  DELAY(5000);
X
X  outb(fdc+fdout, FDO_FRST | FDO_FDMAEN);
X  DELAY(10);
X
X  (void)ftintr_wait(FTCMD_RESET, 500);
X  splx(s);
X  fdc_mode = FDC_DISK_MODE;
X  DPRT(("tape_end end\n"));
X}
X
X/*
X *  Get the geometry of the tape currently in the drive.
X */
Xint ftgetgeom()
X{
X  int r, i, tries;
X  int cfg, qic80, ext;
X  int sts, fmt, len;
X
X  r = tape_status();
X
X  /* XXX fix me when format mode is finished */
X  if ((r & QS_CART) == 0 || (r & QS_FMTOK) == 0) {
X	DPRT(("ftgetgeom: no cart or not formatted 0x%04x\n",r));
X	ftg = NULL;
X	ftnewcart = 1;
X	return(0);
X  }
X
X  /* Report drive configuration */
X  for (cfg = -1, tries = 0; cfg < 0 && tries < 3; tries++)
X	cfg = qic_status(QC_CONFIG, 8);
X  if (tries == 3) {
X	DPRT(("ftgetgeom report config failed\n"));
X	ftg = NULL;
X	return(-1);
X  }
X  DPRT(("ftgetgeom report config got $%04x\n", cfg));
X  ftlastcfg = cfg;
X
X  qic80 = cfg & QCF_QIC80;
X  ext = cfg & QCF_EXTRA;
X
X/*
X *  XXX - This doesn't seem to work on my Colorado Jumbo 250...
X *  if it works on your drive, I'd sure like to hear about it.
X */
X#if 0
X  /* Report drive status */
X  for (sts = -1, tries = 0; sts < 0 && tries < 3; tries++)
X	sts = qic_status(QC_TSTATUS, 8);
X  if (tries == 3) {
X	DPRT(("ftgetgeom report tape status failed\n"));
X	ftg = NULL;
X	return(-1);
X  }
X  DPRT(("ftgetgeom report tape status got $%04x\n", sts));
X#else
X  /*
X   *  XXX - Forge a fake tape status based upon the returned
X   *  configuration, since the above command or code is broken
X   *  for my drive and probably other older drives.
X   */
X  sts = 0;
X  sts = (qic80) ? QTS_QIC80 : QTS_QIC40;
X  sts |= (ext) ? QTS_LEN2 : QTS_LEN1;
X#endif
X
X  fmt = sts & QTS_FMMASK;
X  len = (sts & QTS_LNMASK) >> 4;
X
X  if (fmt > QCV_NFMT) {
X	ftg = NULL;
X	printf("ft0: unsupported tape format\n");
X	return(-1);
X  }
X  if (len > QCV_NLEN) {
X	ftg = NULL;
X	printf("ft0: unsupported tape length\n");
X	return(-1);
X  }
X
X  /* Look up geometry in the table */
X  for (i = 1; i < NGEOM; i++)
X	if (ftgtbl[i].g_fmtno == fmt && ftgtbl[i].g_lenno == len) break;
X  if (i == NGEOM) {
X	printf("ft0: unknown tape geometry\n");
X	ftg = NULL;
X	return(-1);
X  }
X  ftg = &ftgtbl[i];
X  if (!ftg->g_trktape) {
X	printf("ft0: unsupported format %s w/len %s\n", ftg->g_fmtdesc, ftg->g_lendesc);
X	ftg = NULL;
X	return(-1);
X  }
X  DPRT(("Tape format is %s, length is %s\n", ftg->g_fmtdesc, ftg->g_lendesc));
X  ftnewcart = 0;
X  return(0);
X}
X
X/*
X *  Switch between tape/floppy.  This will send the tape enable/disable
X *  codes for this drive's manufacturer.  It will also install/de-install
X *  the tape's device driver functions.
X */
Xint set_fdcmode(int newmode)
X{
X  static int havebufs = 0;
X  void *buf;
X  int r, s, i;
X  SegReq *sp;
X
X  if (newmode == FDC_TAPE_MODE) {
X	if (fdc_mode == FDC_TAPE_MODE) return(EBUSY);
X
X	/* Wake up the tape drive */
X	switch (ft_type) {
X	    case FT_NONE:
X		return(ENXIO);
X	    case FT_COLORADO:
X		tape_start();
X		if (tape_cmd(QC_COL_ENABLE1)) {
X			tape_end();
X			return(EIO);
X		}
X		if (tape_cmd(QC_COL_ENABLE2)) {
X			tape_end();
X			return(EIO);
X		}
X		break;
X	    case FT_MOUNTAIN:
X		tape_start();
X		if (tape_cmd(QC_MTN_ENABLE1)) {
X			tape_end();
X			return(EIO);
X		}
X		if (tape_cmd(QC_MTN_ENABLE2)) {
X			tape_end();
X			return(EIO);
X		}
X		break;
X	    default:
X		printf("ft0: bad tape type\n");
X		return(ENXIO);
X	}
X	if (tape_status() < 0) {
X		tape_cmd((ft_type == FT_COLORADO) ? QC_COL_DISABLE : QC_MTN_DISABLE);
X		tape_end();
X		return(EIO);
X	}
X
X	/* Grab buffers from memory. */
X	if (!havebufs) {
X		curseg = malloc(sizeof(SegReq), M_DEVBUF, M_NOWAIT);
X		if (curseg == NULL) {
X			printf("ft0: not enough memory for buffers\n");
X			return(ENOMEM);
X		}
X		bufseg = malloc(sizeof(SegReq), M_DEVBUF, M_NOWAIT);
X		if (bufseg == NULL) {
X			free(curseg, M_DEVBUF);
X			printf("ft0: not enough memory for buffers\n");
X			return(ENOMEM);
X		}
X		havebufs = 1;
X	}
X
X	/* Tell drivers the fd major is now tape!  Can't fail now. */
X	s = splhigh();
X
X	/* Save old blk/chr device entries, install tape entries */
X	for (bi = 0; bi < nblkdev; bi++)
X		if ((void *)bdevsw[bi].d_open == (void *)Fdopen) break;
X	for (ci = 0; ci < nchrdev; ci++)
X		if ((void *)cdevsw[ci].d_open == (void *)Fdopen) break;
X
X	/* This shouldn't happen! */
X	if (bi == nblkdev || ci == nchrdev) {
X		splx(s);
X		tape_cmd((ft_type == FT_COLORADO) ? QC_COL_DISABLE : QC_MTN_DISABLE);
X		tape_end();
X		printf("ft0: floppy device entry not found!\n");
X		return(ENODEV);
X	}
X	bcopy(&bdevsw[bi], &fdbdevsw, sizeof(struct bdevsw));
X	bcopy(&cdevsw[ci], &fdcdevsw, sizeof(struct cdevsw));
X	bcopy(&ftbdevsw, &bdevsw[bi], sizeof(struct bdevsw));
X	bcopy(&ftcdevsw, &cdevsw[ci], sizeof(struct cdevsw));
X	splx(s);
X
X	curseg->reqtype = FTIO_READY;
X	bufseg->reqtype = FTIO_READY;
X	ftio_sts = FTIO_READY;		/* tape drive is ready */
X	ftactive = 0;			/* interrupt driver not active */
X	ftmoving = 0;			/* tape not moving */
X	ftrdonly = 0;			/* tape read only */
X	ftnewcart = 0;			/* a new cart was inserted */
X	ftlastpos = -1;			/* tape is rewound */
X	tape_state(0, QS_READY, 60);
X	tape_cmd(QC_RATE);
X	tape_cmd(QCF_RT500+2);		/* 500K bps */
X	tape_state(0, QS_READY, 60);
X	ftmode = FTM_PRIMARY;
X	tape_cmd(QC_PRIMARY);		/* Make sure we're in primary mode */
X	tape_state(0, QS_READY, 60);
X	ftg = NULL;			/* No geometry yet */
X	ftgetgeom();			/* Get tape geometry */
X	ftreq_rewind();			/* Make sure tape is rewound */
X  } else {
X	if (fdc_mode == FDC_DISK_MODE) return(0);
X	tape_cmd((ft_type == FT_COLORADO) ? QC_COL_DISABLE : QC_MTN_DISABLE);
X
X	/* Restore floppy device handlers */
X	s = splhigh();
X	bcopy(&fdbdevsw, &bdevsw[bi], sizeof(struct bdevsw));
X	bcopy(&fdcdevsw, &cdevsw[ci], sizeof(struct cdevsw));
X	splx(s);
X	ftnewcart = 0;			/* clear new cartridge */
X	tape_end();
X	havebufs = 0;
X	free(curseg, M_DEVBUF);
X	free(bufseg, M_DEVBUF);
X  }
X  return(0);
X}
X
X
X/*
X *  Perform a QIC status function.
X */
Xint qic_status(int cmd, int nbits)
X{
X  int st3, val, r, i;
X
X  if (tape_cmd(cmd)) {
X	printf("ft0: QIC status timeout\n");
X	return(-1);
X  }
X
X  /* Sense drive status */
X  out_fdc(NE7CMD_SENSED);
X  out_fdc(0x00);
X  st3 = in_fdc();
X
X  if ((st3 & 0x10) == 0) {	/* track 0 */
X	DPRT(("qic_status has dead drive...  st3 = $%02x\n", st3));
X	if (!ft_attaching)
X		printf("ft0: tape drive seems dead; st3 = $%02x\n", st3);
X	return(-1);
X  }
X
X  for (i = r = 0; i <= nbits; i++) {
X	if (tape_cmd(QC_NEXTBIT)) {
X		printf("ft0: QIC status bit timed out on %d\n", i);
X		return(-1);
X	}
X
X	out_fdc(NE7CMD_SENSED);
X	out_fdc(0x00);
X	st3 = in_fdc();
X	if (st3 < 0) {
X		printf("ft0: controller timed out on bit %d r=$%02x\n",i,r);
X		return(-1);
X	}
X
X	r >>= 1;
X	if (i < nbits)
X		r |= ((st3 & 0x10) ? 1 : 0) << nbits;
X	else if ((st3 & 0x10) == 0) {
X		printf("ft0: qic status stop bit missing at %d, st3=$%02x r=$%04x\n",i,st3,r);
X		return(-1);
X	}
X  }
X
X  DPRT(("qic_status returned $%02x\n", r));
X  return(r);
X}
X
X/*
X *  Open tape drive for use.  Bounced off of Fdopen if tape minor is
X *  detected.
X */
Xint ftopen(dev_t dev, int oflags, int devtype, struct proc *p)
X{
X  int unit = (minor(dev) >> 3) & 3;
X
X  if (unit < NFD) return(EBUSY);	/* tape open -- reject others */
X  return (set_fdcmode(FDC_TAPE_MODE));	/* try to switch to tape */
X}
X
X/*
X *  Close tape.  If a floppy disk close request comes in, pretend it's ok,
X *  otherwise we return floppy controller to disk mode.
X */
Xint ftclose(dev_t dev, int fflag, int devtype, struct proc *p)
X{
X  int unit = (minor(dev) >> 3) & 3;
X  int s;
X  SegReq *sp;
X
X  if (unit < NFD) return(0);		/* If not tape, close ok. */
X
X  /* Wait for any remaining I/O activity to complete. */
X  if (curseg->reqtype == FTIO_RDAHEAD) curseg->reqcan = 1;
X  while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X
X  ftmode = FTM_PRIMARY;
X  tape_cmd(QC_PRIMARY);
X  tape_state(0, QS_READY, 60);
X  ftreq_rewind();
X  return(set_fdcmode(FDC_DISK_MODE));	/* Otherwise, close tape */
X}
X
X/*
X *  Perform strategy on a given buffer (not!).
X */
Xint ftstrategy(struct buf *bp)
X{
X  return(EINVAL);
X}
X
X/* Read or write a segment. */
Xint ftreq_rw(int cmd, QIC_Segment *sr, struct proc *p)
X{
X  int r, i, j;
X  SegReq *sp;
X  int s;
X  long blk, bad;
X  unsigned char *cp, *cp2;
X
X  if (!ftactive) {
X	r = tape_status();
X	if ((r & QS_CART) == 0) return(ENXIO);	/* No cartridge */
X	if ((r & QS_FMTOK) == 0) return(ENXIO);	/* Not formatted */
X	tape_state(0, QS_READY, 90);
X  }
X
X  if (ftg == NULL || ftnewcart) {
X	while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X	if (ftgetgeom() < 0) return(ENXIO);
X  }
X
X  /* Write not allowed on a read-only tape. */
X  if (cmd == QIOWRITE && ftrdonly) return(EROFS);
X
X  /* Quick check of request and buffer. */
X  if (sr == NULL || sr->sg_data == NULL) return(EINVAL);
X  if (sr->sg_trk >= ftg->g_trktape ||
X	sr->sg_seg >= ftg->g_segtrk) return(EINVAL);
X  blk = sr->sg_trk * ftg->g_blktrk + sr->sg_seg * QCV_BLKSEG;
X
X  if (cmd == QIOREAD) {
X	s = splbio();
X	if (curseg->reqtype == FTIO_RDAHEAD) {
X		if (blk == curseg->reqblk) {
X			sp = curseg;
X			sp->reqtype = FTIO_READING;
X			sp->reqbad = sr->sg_badmap;
X			splx(s);
X			goto rdwait;
X		} else
X			curseg->reqcan = 1;	/* XXX cancel rdahead */
X	}
X	splx(s);
X
X	/* Wait until we're ready. */
X	while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X
X	/* Set up a new read request. */
X	sp = curseg;
X	sp->reqcrc = 0;
X	sp->reqbad = sr->sg_badmap;
X	sp->reqblk = blk;
X	sp->reqcan = 0;
X	sp->reqtype = FTIO_READING;
X
X	/* Start the read request off. */
X	DPRT(("Starting read I/O chain\n"));
X	arq_state = ard_state = awr_state = 0;
X	ftxblk = sp->reqblk;
X	ftxcnt = 0;
X	ftxptr = sp->buff;
X	ftactive = 1;
X	timeout(ft_timeout, 0, 1);
X
Xrdwait:
X	sleep(&ftsem.buff_avail, FTPRI);
X	bad = sp->reqbad;
X	sr->sg_crcmap = sp->reqcrc & ~bad;
X
X	/* Copy out segment and discard bad mapped blocks. */
X	cp = sp->buff; cp2 = sr->sg_data;
X	for (i = 0; i < QCV_BLKSEG; cp += QCV_BLKSIZE, i++) {
X		if (bad & (1 << i)) continue;
X		copyout(cp, cp2, QCV_BLKSIZE);
X		cp2 += QCV_BLKSIZE;
X	}
X  } else {
X	if (curseg->reqtype == FTIO_RDAHEAD) {
X		curseg->reqcan = 1;	/* XXX cancel rdahead */
X		while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X	}
X
X	/* Sleep until a buffer becomes available. */
X	while (bufseg->reqtype != FTIO_READY) sleep(&ftsem.buff_avail, FTPRI);
X	s = splbio();
X	sp = (curseg->reqtype == FTIO_READY) ? curseg : bufseg;
X	splx(s);
X
X	/* Copy in segment and expand bad blocks. */
X	bad = sr->sg_badmap;
X	cp = sr->sg_data; cp2 = sp->buff;
X	for (i = 0; i < QCV_BLKSEG; cp2 += QCV_BLKSIZE, i++) {
X		if (bad & (1 << i)) continue;
X		copyin(cp, cp2, QCV_BLKSIZE);
X		cp += QCV_BLKSIZE;
X	}
X
X	sp->reqblk = blk;
X	sp->reqcan = 0;
X	s = splbio();
X	sp->reqtype = FTIO_WRITING;
X
X	if (!ftactive) {
X		DPRT(("Starting write I/O chain\n"));
X		arq_state = ard_state = awr_state = 0;
X		ftxblk = sp->reqblk;
X		ftxcnt = 0;
X		ftxptr = sp->buff;
X		ftactive = 1;
X		timeout(ft_timeout, 0, 1);
X	}
X	splx(s);
X  }
X  return(0);
X}
X
X
X/* Rewind to beginning of tape */
Xint ftreq_rewind()
X{
X  if (curseg->reqtype == FTIO_RDAHEAD) {
X	curseg->reqcan = 1;	/* XXX cancel rdahead */
X	while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X  }
X  if (ftactive) return(ENXIO);
X
X  tape_cmd(QC_STOP);
X  tape_state(0, QS_READY, 90);
X  tape_cmd(QC_SEEKSTART);
X  tape_state(0, QS_READY, 90);
X  tape_cmd(QC_SEEKTRACK);
X  tape_cmd(2);
X  tape_state(0, QS_READY, 90);
X  ftlastpos = -1;
X  ftmoving = 0;
X  return(0);
X}
X
X/* Move to logical beginning or end of track */
Xint ftreq_trkpos(int req)
X{
X  int curtrk, r, cmd;
X
X  if (curseg->reqtype == FTIO_RDAHEAD) {
X	curseg->reqcan = 1;	/* XXX cancel rdahead */
X	while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X  }
X  if (ftactive) return(ENXIO);
X
X  tape_cmd(QC_STOP);
X  tape_state(0, QS_READY, 90);
X
X  r = tape_status();
X  if ((r & QS_CART) == 0) return(ENXIO);	/* No cartridge */
X  if ((r & QS_FMTOK) == 0) return(ENXIO);	/* Not formatted */
X
X  if (ftg == NULL || ftnewcart) {
X	if (ftgetgeom() < 0) return(ENXIO);
X  }
X
X  curtrk = (ftlastpos < 0) ? 0 : ftlastpos / ftg->g_blktrk;
X  if (req == QIOBOT)
X	cmd = (curtrk & 1) ? QC_SEEKEND : QC_SEEKSTART;
X  else
X	cmd = (curtrk & 1) ? QC_SEEKSTART : QC_SEEKEND;
X  tape_cmd(cmd);
X  tape_state(0, QS_READY, 90);
X  return(0);
X}
X
X/* Seek tape head to a particular track. */
Xint ftreq_trkset(int *trk)
X{
X  int curtrk, r, cmd;
X
X  if (curseg->reqtype == FTIO_RDAHEAD) {
X	curseg->reqcan = 1;	/* XXX cancel rdahead */
X	while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X  }
X  if (ftactive) return(ENXIO);
X
X  tape_cmd(QC_STOP);
X  tape_state(0, QS_READY, 90);
X
X  r = tape_status();
X  if ((r & QS_CART) == 0) return(ENXIO);	/* No cartridge */
X  if ((r & QS_FMTOK) == 0) return(ENXIO);	/* Not formatted */
X  if (ftg == NULL || ftnewcart) {
X	if (ftgetgeom() < 0) return(ENXIO);
X  }
X
X  tape_cmd(QC_SEEKTRACK);
X  tape_cmd(*trk + 2);
X  tape_state(0, QS_READY, 90);
X  return(0);
X}
X
X/* Start tape moving forward. */
Xint ftreq_lfwd()
X{
X  if (ftactive) return(ENXIO);
X  tape_cmd(QC_STOP);
X  tape_state(0, QS_READY, 90);
X  tape_cmd(QC_FORWARD);
X  return(0);
X}
X
X/* Stop the tape */
Xint ftreq_stop()
X{
X  if (ftactive) return(ENXIO);
X  tape_cmd(QC_STOP);
X  tape_state(0, QS_READY, 90);
X  return(0);
X}
X
X/* Set the particular mode the drive should be in. */
Xint ftreq_setmode(int cmd)
X{
X  int r;
X
X  if (curseg->reqtype == FTIO_RDAHEAD) {
X	curseg->reqcan = 1;	/* XXX cancel rdahead */
X	while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X  }
X  if (ftactive) return(ENXIO);
X  r = tape_status();
X
X  switch(cmd) {
X     case QIOPRIMARY:
X	ftmode = FTM_PRIMARY;
X	tape_cmd(QC_PRIMARY);
X	break;
X     case QIOFORMAT:
X	if (r & QS_RDONLY) return(ENXIO);
X	if ((r & QS_BOT) == 0) return(ENXIO);
X	tape_cmd(QC_FORMAT);
X	break;
X     case QIOVERIFY:
X	if ((r & QS_FMTOK) == 0) return(ENXIO);	/* Not formatted */
X	tape_cmd(QC_VERIFY);
X	break;
X  }
X  tape_state(0, QS_READY, 60);
X  return(0);
X}
X
X/* Return drive status bits */
Xint ftreq_status(int cmd, int *sts, struct proc *p)
X{
X  if (ftactive)
X	*sts = ftlaststs;
X  else
X	*sts = tape_status();
X  return(0);
X}
X
X/* Return drive configuration bits */
Xint ftreq_config(int cmd, int *cfg, struct proc *p)
X{
X  int r, tries;
X
X  if (ftactive)
X	r = ftlastcfg;
X  else {
X	for (r = -1, tries = 0; r < 0 && tries < 3; tries++)
X		r = qic_status(QC_CONFIG, 8);
X	if (tries == 3) return(ENXIO);
X  }
X  *cfg = r;
X  return(0);
X}
X
X/* Return current tape's geometry. */
Xint ftreq_geom(QIC_Geom *g)
X{
X  if (curseg->reqtype == FTIO_RDAHEAD) {
X	curseg->reqcan = 1;	/* XXX cancel rdahead */
X	while (ftactive) sleep(&ftsem.iosts_change, FTPRI);
X  }
X  if (ftactive) return(ENXIO);
X  if (ftg == NULL && ftgetgeom() < 0) return(ENXIO);
X  bcopy(ftg, g, sizeof(QIC_Geom));
X  return(0);
X}
X
X/*
X *  I/O functions.
X */
Xint ftioctl(dev_t dev, int cmd, caddr_t data, int flag, struct proc *p)
X{
X  switch(cmd) {
X     case QIOREAD:	/* Request reading a segment from tape.		*/
X     case QIOWRITE:	/* Request writing a segment to tape.		*/
X	return(ftreq_rw(cmd, (QIC_Segment *)data, p));
X
X     case QIOREWIND:	/* Rewind tape.					*/
X	return(ftreq_rewind());
X
X     case QIOBOT:	/* Seek to logical beginning of track.		*/
X     case QIOEOT:	/* Seek to logical end of track.		*/
X	return(ftreq_trkpos(cmd));
X
X     case QIOTRACK:	/* Seek tape head to specified track.		*/
X	return(ftreq_trkset((int *)data));
X
X     case QIOSEEKLP:	/* Seek load point.				*/
X	goto badreq;
X
X     case QIOFORWARD:	/* Move tape in logical forward direction.	*/
X	return(ftreq_lfwd());
X
X     case QIOSTOP:	/* Causes tape to stop.				*/
X	return(ftreq_stop());
X
X     case QIOPRIMARY:	/* Enter primary mode.				*/
X     case QIOFORMAT:	/* Enter format mode.				*/
X     case QIOVERIFY:	/* Enter verify mode.				*/
X	return(ftreq_setmode(cmd));
X
X     case QIOWRREF:	/* Write reference burst.			*/
X	goto badreq;
X
X     case QIOSTATUS:	/* Get drive status.				*/
X	return(ftreq_status(cmd, (int *)data, p));
X
X     case QIOCONFIG:	/* Get tape configuration.			*/
X	return(ftreq_config(cmd, (int *)data, p));
X
X     case QIOGEOM:
X	return(ftreq_geom((QIC_Geom *)data));
X
X  }
Xbadreq:
X  printf("ft0: unknown ioctl() request\n");
X  return(ENXIO);
X}
X
Xint ftdump(dev_t dev)
X{
X}
X
Xint ftsize(dev_t dev)
X{
X}
X#endif
END-of-driver/ft.c
exit