[BACK]Return to ts.c CVS log [TXT][DIR] Up to [local] / sys / arch / vax / uba

File: [local] / sys / arch / vax / uba / ts.c (download)

Revision 1.1.1.1 (vendor branch), Tue Mar 4 16:08:45 2008 UTC (16 years, 3 months ago) by nbrk
Branch: OPENBSD_4_2_BASE, MAIN
CVS Tags: jornada-partial-support-wip, HEAD
Changes since 1.1: +0 -0 lines

Import of OpenBSD 4.2 release kernel tree with initial code to support 
Jornada 720/728, StrongARM 1110-based handheld PC.
At this point kernel roots on NFS and boots into vfs_mountroot() and traps.
What is supported:
- glass console, Jornada framebuffer (jfb) works in 16bpp direct color mode
(needs some palette tweaks for non black/white/blue colors, i think)
- saic, SA11x0 interrupt controller (needs cleanup)
- sacom, SA11x0 UART (supported only as boot console for now)
- SA11x0 GPIO controller fully supported (but can't handle multiple interrupt
handlers on one gpio pin)
- sassp, SSP port on SA11x0 that attaches spibus
- Jornada microcontroller (jmcu) to control kbd, battery, etc throught
the SPI bus (wskbd attaches on jmcu, but not tested)
- tod functions seem work
- initial code for SA-1111 (chip companion) : this is TODO

Next important steps, i think:
- gpio and intc on sa1111
- pcmcia support for sa11x0 (and sa1111 help logic)
- REAL root on nfs when we have PCMCIA support (we may use any of supported pccard NICs)
- root on wd0! (using already supported PCMCIA-ATA)

/*	$OpenBSD: ts.c,v 1.17 2007/06/06 17:15:13 deraadt Exp $ */
/*	$NetBSD: ts.c,v 1.11 1997/01/11 11:34:43 ragge Exp $ */

/*-
 * Copyright (c) 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)tmscp.c	7.16 (Berkeley) 5/9/91
 */

/*
 * sccsid = "@(#)tmscp.c	1.24	(ULTRIX)	1/21/86";
 */

/************************************************************************
 *									*
 *	  Licensed from Digital Equipment Corporation			*
 *			 Copyright (c)					*
 *		 Digital Equipment Corporation				*
 *		     Maynard, Massachusetts				*
 *			   1985, 1986					*
 *		      All rights reserved.				*
 *									*
 *	  The Information in this software is subject to change		*
 *   without notice and should not be construed as a commitment		*
 *   by	 Digital  Equipment  Corporation.   Digital   makes  no		*
 *   representations about the suitability of this software for		*
 *   any purpose.  It is supplied "As Is" without expressed  or		*
 *   implied  warranty.							*
 *									*
 *	  If the Regents of the University of California or its		*
 *   licensees modify the software in a manner creating			*
 *   diriviative copyright rights, appropriate copyright		*
 *   legends may be placed on  the drivative work in addition		*
 *   to that set forth above.						*
 *									*
 ************************************************************************/

/*
 * TSV05/TS05 device driver, written by Bertram Barth.
 *
 * should be TS11 compatible (untested)
 */

#define TS11_COMPAT	/* don't use extended features provided by TS05 */

#ifdef	NEED_18BIT
#define TS_UBAFLAGS	UBA_NEED16
#else
#define TS_UBAFLAGS	0
#endif

#define ENABLE_ESS
#define ENABLE_END

#define ENABLE_EAI	/* enable Attention-Interrupts */
#undef	ENABLE_EAI

#define ENABLE_ERI	/* Enable Release Buffer Interrupts */
#undef	ENABLE_ERI

#ifdef DEBUG
int tsdebug = 1;
# define debug(x)	if (tsdebug > 0) {DELAY(2000); printf x; DELAY(3000);}
# define debug10(x)	if (tsdebug > 9) printf x
#else
# define debug(x)	/* just ignore it */
# define debug10(x)	/* just ignore it */
#endif

#ifdef TRACE
int tstrace = 1;
# define trace(x)	if (tstrace > 0) {DELAY(2000); printf x; DELAY(3000);}
#else
# define trace(x)	/* just ignore it */
#endif

/*
 * TODO: most :-)
 *
 * include uba-mapping into tsinit();
 * merge tsinit(), tsreset() and tsprobe();
 * complete tsintr();
 * add proper error/status messages
 * make messages appear where they are intended to.
 * check for termination-classes and appropriate actions.
 * check if flags like CVC and ATTN should be used.
 * look for correct handling of attentions.
 * check for correct usage of retry-commands.
 * ...
 */


#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/syslog.h>
#include <sys/ioctl.h>
#include <sys/mtio.h>
#include <sys/uio.h>
#include <sys/proc.h>

#include <machine/pte.h>
#include <machine/sid.h>
#include <machine/cpu.h>
#include <machine/mtpr.h>

#include <vax/uba/ubareg.h>
#include <vax/uba/ubavar.h>

#include <vax/uba/tsreg.h>

#include "ts.h"

/*
 * ts command packets and communication area (per controller)
 */
struct	ts {
	struct	tsdevice *reg;		/* address of i/o-registers */
	struct	tscmd	  cmd;		/* command packet(s) */
	struct	tsmsg	  msg;		/* message packet(s) */
} ts[NTS];

/*
 * Software status, per controller.
 * also status per tape-unit, since only one unit per controller
 * (thus we have no struct ts_info)
 */
struct	ts_softc {
	struct	device sc_dev;		/* Autoconf ... */
	struct	uba_unit sc_unit;	/* Struct common for UBA to talk */
	struct	ts *sc_ts;		/* Unibus address of uda struct */
	short	sc_mapped;		/* Unibus map allocated ? */
	int	sc_ubainfo;		/* Unibus mapping info */
	short	sc_state;		/* see below: ST_xxx */
	short	sc_flags;		/* see below: FL_xxx */
	short	sc_lcmd;		/* last command word */
	short	sc_rtc;			/* retry count for lcmd */
	short	sc_lssr;		/* last status register */
	short	sc_lmsgh;		/* last message header */
	short	sc_lxst0;		/* last status word */
	short	sc_cmdf;		/* command flags (ack,cvc,ie) */
	short	sc_openf;		/* lock against multiple opens */
	short	sc_liowf;		/* last operation was write */
	int	sc_micro;		/* microcode revision */
	int	sc_ivec;		/* interrupt vector address */
	short	sc_ipl;			/* interrupt priority, Q-bus */
};

void	tsintr(int);
int	tsinit(struct ts_softc *);
void	tscommand(dev_t, int, int);
int	tsstatus(int);
int	tsexec(int, int);
int	tsstart(struct ts_softc *, struct buf *);
int	tswchar(int);
void	tsreset(int);
void	tsxstatus(struct tsmsg *);
int	tsmatch(struct device *, void *, void *);
void	tsattach(struct device *, struct device *, void *);
void	tsstrategy(struct buf *);

int	tsopen(dev_t, int, int, struct proc *);
int	tsclose(dev_t, int, int, struct proc *);
int	tsioctl(dev_t, u_long, caddr_t, int, struct proc *);
int	tsread(dev_t, struct uio *);
int	tswrite(dev_t, struct uio *);
int	tsdump(dev_t, daddr64_t, caddr_t, size_t);

struct	cfdriver ts_cd = {
	NULL, "ts", DV_DULL
};

struct	cfattach ts_ca = {
	sizeof(struct ts_softc), tsmatch, tsattach
};


#define ST_INVALID	0	/* uninitialized, before probe */
#define ST_PROBE	1	/* during tsprobe(), not used */
#define ST_SLAVE	2	/* in tsslave(), init almost complete */
#define ST_ATTACH	3	/* during tsattach(), not used */
#define ST_INITIALIZED	4	/* init completed, set by tsintr() */
#define ST_RUNNING	5
#define ST_IDLE		6
#define ST_BUSY		7

/* Bits in minor device */ 
#define TS_UNIT(dev)	(minor(dev)&03)
#define TS_HIDENSITY	010

#define TS_PRI	LOG_INFO


/*
 * Since we don't have credits and thus only one operation per time,
 * we don't have and don't need queues like MSCP/TMSCP use them.
 * Per controller we only need one internal buffer for ioctls and 
 * two pointers to buffers to simulate similar behaviour ...
 */
struct buf	 ts_cbuf[NTS];		/* internal cmd buffer (for ioctls) */
struct buf	*ts_wtab[NTS];		/* dummy I/O wait queue */
#define b_ubinfo	b_resid		/* Unibus mapping info, per buffer */

/*----------------------------------------------------------------------*/

/*
 * Initialize a TS device. Set up UBA mapping registers,
 * initialize data structures, what else ???
 */
int 
tsinit (sc)
	struct ts_softc *sc;
{
	volatile struct tsdevice *tsregs;
	int unit = sc->sc_dev.dv_unit;
	struct uba_unit *uu;
	
	uu = &sc->sc_unit;
	tsregs = (struct tsdevice *)ts[unit].reg;
	if (sc->sc_mapped == 0) {
		/*
		 * Map the communications area and command and message
		 * buffer into Unibus address space.
		 */
		sc->sc_ubainfo = uballoc((struct uba_softc *)
		    sc->sc_dev.dv_parent,
		    (caddr_t)&ts[unit], sizeof (struct ts), TS_UBAFLAGS);
		sc->sc_ts = (struct ts *)(UBAI_ADDR(sc->sc_ubainfo));
		sc->sc_mapped = 1;
	}

	/*
	 * bertram: start hardware initialization ??????
	 */
	
	/* tsreset(unit); */

	return (1);
}

/*
 * send a command using the default command-buffer for this unit/controller.
 * If a command-word is given, then assemble a one-word command.
 * other words in command-buffer are unchanged and must thus be initialized
 * before calling this function.
 */
int 
tsexec (ctlr, cmd)
	int ctlr;
	int cmd;
{
	register struct ts_softc *sc = ts_cd.cd_devs[ctlr];
	register struct tscmd *tscmdp = &ts[ctlr].cmd;
	register long tscmdma = (long)&sc->sc_ts->cmd;	/* mapped address */
	volatile struct tsdevice *tsreg = ts[ctlr].reg;
	volatile char *dbx = ((char *)tsreg) + 3;
	volatile short sr;

	sc->sc_cmdf |= TS_CF_ACK | TS_CF_IE;
	tscmdp->cmdr = sc->sc_cmdf | cmd;
	tscmdp->cmdr = TS_CF_ACK | TS_CF_IE | sc->sc_cmdf | cmd;
	sc->sc_cmdf = 0;		/* XXX */

#ifdef DEBUG
	switch (tscmdp->cmdr & TS_CF_CMASK) {
	case TS_CMD_RNF:	cmdName = "Read Next (Forward)";	break;
	case TS_CMD_RPR:	cmdName = "Read Previous (Reverse)";	break;
	case TS_CMD_WCHAR:	cmdName = "Write Characteristics";	break;
	case TS_CMD_WD:		cmdName = "Write Data (Next)";		break;
	case TS_CMD_WDR:	cmdName = "Write Data (Retry)";		break;
	case TS_CMD_SRF:	cmdName = "Space Records Forward";	break;
	case TS_CMD_SRR:	cmdName = "Space Records Reverse";	break;
	case TS_CMD_STMF:	cmdName = "Skip Tape Marks Forward";	break;
	case TS_CMD_STMR:	cmdName = "Skip Tape Marks Reverse";	break;
	case TS_CMD_RWND:	cmdName = "Rewind";			break;
	case TS_CMD_WTM:	cmdName = "Write Tape Mark";		break;
	case TS_CMD_WTMR:	cmdName = "Write Tape Mark (Retry)";	break;
	case TS_CMD_STAT:	cmdName = "Get Status (END)";		break;
	default:		cmdName = "Unexpected Command";		break;
	}
#endif

	sr = tsreg->tssr;
	if ((sr & TS_SSR) == 0) {	/* subsystem NOT ready */
		printf ("%s%d: subsystem not ready [%x]\n", 
		    sc->sc_dev.dv_xname, sr);
		return (-1);
	}
	dbx = ((char *)tsreg) + 3;	/* dbx is located at the fourth byte */
	*dbx = (tscmdma >> 18) & 0x0F;	/* load bits 18-21 into dbx */ 

	/* possible race-condition with ATTN !!! */

	sr = tsreg->tssr;
	if ((sr & TS_RMR) != 0) {	/* Register modification Refused */
		printf ("ts: error writing TSDBX\n");
		return (-1);
	}
	/* now load bits 15-2 at pos 15-2 and bits 17,16 at pos 1,0 of TSDB */
	tsreg->tsdb = (tscmdma & 0xfffc) | ((tscmdma >> 16) & 0x03);

	/*
	 * wait for SSR or RMR to show up
	 */
	sr = tsreg->tssr;
	if ((sr & TS_SSR) != 0) {	/* something went wrong .. */
		if (sr & TS_RMR) {
			printf ("ts: error writing TSDB (RMR)\n");
			return (-1);
		}
		if (sr & TS_NXM) {
			printf ("ts: error writing TSDB (NXM)\n");
			return (-1);
		}
		printf ("ts: error 0x%x while writing TSDB\n", sr);
		tsstatus (sr);
		return (-1);
	}

	return (0);	 /* completed successfully */
}

/*  
 * Execute a (ioctl) command on the tape drive a specified number of times.
 * This routine sets up a buffer and calls the strategy routine which
 * issues the command to the controller.
 */
void
tscommand (dev, cmd, count)
	register dev_t dev;
	int cmd;
	int count;
{
	register struct buf *bp;
	register int s;	 

	trace (("tscommand (%d, %x, %d)\n", TS_UNIT(dev), cmd, count));

	s = splbio();
	bp = &ts_cbuf[TS_UNIT(dev)];

	while (bp->b_flags & B_BUSY) {
		/*
		 * This special check is because B_BUSY never
		 * gets cleared in the non-waiting rewind case. ???
		 */
		if (bp->b_bcount == 0 && (bp->b_flags & B_DONE))
			break;
		bp->b_flags |= B_WANTED;
		tsleep ((caddr_t)bp, PRIBIO, "tscommand", 0);
		/* check MOT-flag !!! */
	}
	bp->b_flags = B_BUSY | B_READ;

	splx(s);

	/*
	 * Load the buffer.  The b_count field gets used to hold the command
	 * count.  the b_resid field gets used to hold the command mneumonic.
	 * These 2 fields are "known" to be "safe" to use for this purpose.
	 * (Most other drivers also use these fields in this way.)
	 */
	bp->b_dev = dev;
	bp->b_bcount = count;
	bp->b_resid = cmd;
	bp->b_blkno = 0;
	tsstrategy (bp);
	/*
	 * In case of rewind from close, don't wait.
	 * This is the only case where count can be 0.
	 */
	if (count == 0) {
		debug (("tscommand: direct return, no biowait.\n"));
		return;
	}
	debug (("tscommand: calling biowait ...\n"));
	biowait (bp);
	if (bp->b_flags & B_WANTED)
		wakeup ((caddr_t)bp);
	bp->b_flags &= B_ERROR;
}

/*
 * Start an I/O operation on TS05 controller
 */
int
tsstart (sc, bp)
	register struct ts_softc *sc;
	register struct buf *bp;
{
	int ctlr = sc->sc_dev.dv_unit;
	volatile struct tsdevice *tsreg = ts[ctlr].reg;
	register struct tscmd *tscmdp = &ts[ctlr].cmd;
	register struct buf *dp;
	volatile int i, itmp;
	int ioctl;
	int cmd;
	int s;

	if ((dp = ts_wtab[ctlr]) != NULL) {
		/*
		 * There's already a command pending ...
		 * Either we are called by tsintr or we have missed
		 * something important (race condition).
		 */

		/* bertram: ubarelse ??? */
		ts_wtab[ctlr] = NULL;
		dp->b_flags |= B_ERROR;
		s = splbio();
		biodone (dp);
		splx(s);

		if (tsreg->tssr & TS_SC) {	/* Special Condition; Error */
			log (TS_PRI, "%s: tssr 0x%x, state %d\n",
				sc->sc_dev.dv_xname, tsreg->tssr, sc->sc_state);
			tsinit (sc);
			return (-1);
		}
		/* XXX */
	}

	/*
	 * Check if command is an ioctl or not (ie. read or write).
	 * If it's an ioctl then just set the flags for later use;
	 * For other commands attempt to setup a buffer pointer.
	 */
	if (bp == &ts_cbuf[ctlr]) {
		ioctl = 1;
	} else {
		ioctl = 0;

		/*
		 * now we try to map the buffer into uba map space (???)
		 */
		i = TS_UBAFLAGS;
		switch (vax_cputype) {
		case VAX_8600:
		case VAX_780:
			i |= UBA_CANTWAIT;
			break;
		case VAX_750:
			i |= sc->sc_unit.uu_ubinfo | UBA_CANTWAIT;
			break;
		case VAX_730:
		case VAX_78032:
			i |= UBA_CANTWAIT;
			break;
		default:
			printf ("unsupported cpu %d in tsstart.\n", vax_cputype);
		} /* end switch (vax_cputype) */

		if ((i = ubasetup(sc->sc_dev.dv_parent->dv_unit, bp, i)) == 0) {
			/*
			 * For some reasons which I don't (yet? :) understand,
			 * tmscp.c initiates in this situation a GET-UNIT
			 * command. (Because no data-buffers are necessary?)
			 */
			cmd = TS_CMD_STAT;
			goto do_cmd;
			return (-1);	/* ??? */
		}
#if defined(VAX750)
		if (vax_cputype == VAX_750)
			itmp = i & 0xfffffff;		/* mask off bdp */
		else
#endif
			itmp = i;

		/* XXX */
	}

	/*
	 * If it's an ioctl command, then assemble the command.
	 * The "b_resid" field holds the command-number as defined
	 * in <sys/mtio.h>
	 */
	if (ioctl) {
		switch ((int)bp->b_resid) {
		case MTWEOF:
			cmd = TS_CMD_WTM;
			break;
		case MTFSF:
			cmd = TS_CMD_STMF;
			tscmdp->cw1 = bp->b_bcount;
			break;
		case MTBSF:
			cmd = TS_CMD_STMR; 
			tscmdp->cw1 = bp->b_bcount;
			break;
		case MTFSR:
			cmd = TS_CMD_SRF;
			tscmdp->cw1 = bp->b_bcount;
			break;
		case MTBSR:
			cmd = TS_CMD_SRR;
			tscmdp->cw1 = bp->b_bcount;
			break;
		case MTREW:
			cmd = TS_CMD_RWND;
#ifndef TS11_COMPAT
			if (bp->b_bcount == 0) {
				cmd = TS_CMD_RWII;
			}
#endif
			break;
		case MTOFFL:
			cmd = TS_CMD_RWUL;
			break;
		case MTNOP:
			cmd = TS_CMD_STAT;
			break;
		default:
			printf ("%s: bad ioctl %d\n", sc->sc_dev.dv_xname,
				(int)bp->b_resid);
			/* Need a no-op. get status */
			cmd = TS_CMD_STAT;
		} /* end switch (bp->b_resid) */
	} else {	/* Its a read/write command (not an ioctl) */ 
		tscmdp->cw1 = UBAI_ADDR(i) & 0xffff;
		tscmdp->cw2 = (UBAI_ADDR(i) >> 16) & 0x3f;
		tscmdp->cw3 = bp->b_bcount;

		if (bp->b_flags & B_READ) {
			cmd = TS_CMD_RNF;
		}
		else {
			cmd = TS_CMD_WD;
		}
		bp->b_ubinfo = itmp;			/* save mapping info */
	}

	/*
	 * Move buffer to I/O wait pseudo-queue
	 */
	if (ts_wtab[ctlr]) {
		/*
		 * we are already waiting for something ...
		 * this should not happen, so we have a problem now.
		 * bertram: set error-flag and call biodone() ???
		 */
	}
	ts_wtab[ctlr] = bp;

	/*
	 * Now that the command-buffer is setup, give it to the controller
	 */
do_cmd:
	return (tsexec(ctlr, cmd));
}

/*
 * initialize the controller by sending WRITE CHARACTERISTICS command.
 * contents of command- and message-buffer are assembled during this
 * function. 
 */
int 
tswchar (ctlr)
	int ctlr;
{
	struct ts_softc *sc = ts_cd.cd_devs[ctlr];
	volatile struct tsdevice *tsregs = ts[ctlr].reg;
	volatile struct tscmd *tscmdp = &ts[ctlr].cmd;
	volatile struct tsmsg *tsmsgp = &ts[ctlr].msg;
	volatile unsigned int sr, ma, timeout;

	/*
	 * assemble and send "WRITE CHARACTERISTICS" command
	 */
	ma = (long)tsmsgp;
	if (ma & 0x7FC00001) {	/* address must be even and 22-bit */
		printf ("invalid address 0x%0x for msg-buffer.\n", ma);
		return (-1);
	}

	tsmsgp->hdr = ma & 0xFFFF;		/* low order addr. bits */
	tsmsgp->dfl = (ma >> 16) & 0x003F;	/* high order addr. bits */
	tsmsgp->rbpcr = 16;			/* size of message-buffer */
	tsmsgp->xst0 = 0;			/* chacacteristics mode word */
	tsmsgp->xst1 = 0;			/* control word (ext.feat.) */

#ifdef TS11_COMPAT
	tsmsgp->rbpcr = 14;		/* size of message-buffer */
	tsmsgp->xst0  = 0;		/* chacacteristics mode word */
	tsmsgp->xst1  = 0;		/* control word (ext.feat.) */
#else
	tsmsgp->rbpcr = 16;		/* size of extended message buffer */
	tsmsgp->xst0  = 0;		/* chacacteristics mode word */
	tsmsgp->xst1  = 0;		/* unit-select */
	tsmsgp->xst1 |= TS_WC_HSP;	/* high speed */
#endif

#ifdef ENABLE_ESS
	tsmsgp->xst0 |= TS_WC_ESS;
#ifdef ENABLE_ENB
	tsmsgp->xst0 |= TS_WC_ENB;
#endif
#endif

#ifdef ENABLE_EAI
	tsmsgp->xst0 |= TS_WC_EAI;
#ifdef ENABLE_ERI
	tsmsgp->xst0 |= TS_WC_ERI;
#endif
#endif

	tscmdp->cmdr = TS_CF_ACK | TS_CF_IE | TS_CMD_WCHAR;	/* obsolete */
	tscmdp->cw1  = ma & 0xFFFF;
	tscmdp->cw2  = (ma >> 16) & 0x003F;
	tscmdp->cw3  = 10;		   /* size of charact.-data */

	if (tsexec (ctlr, TS_CMD_WCHAR) < 0) {
		printf ("%s: write characteristics command failed [%x]\n",
		    sc->sc_dev.dv_xname, tsregs->tssr);
		return (-1);
	}

	timeout = 1000;		       /* timeout in 10 seconds */
	do {
		DELAY(10000);
		sr = tsregs->tssr;
		debug10 (("\ttssr: 0x%x\n", sr));
		if (timeout-- > 0) {
			printf ("timeout during initialize.");
			tsstatus (sr);
			return (-1);
		}
	} while ((sr & TS_SSR) == 0);
	tsstatus (sr);

	return (0);
}

/*
 *
 */
void
tsreset(ctlr) 
	int ctlr;
{
	struct ts_softc *sc = ts_cd.cd_devs[ctlr];
	volatile struct tsdevice *tsreg = ts[ctlr].reg;
	volatile unsigned int sr, timeout;

	/*
	 * reset ctlr by writing into TSSR, then write characteristics
	 */
	timeout = 1000;		/* timeout in 10 seconds */	
	tsreg->tssr = 0;			/* start initialization */
	do {
		DELAY(10000);
		sr = tsreg->tssr;
		debug10 (("\ttssr: 0x%x\n", sr));
		if (timeout-- > 0) {
			if (sr != 0)
				printf ("%s: timeout waiting for TS_SSR\n",
					sc->sc_dev.dv_xname);
			tsstatus (sr);
			return;
		}
	} while ((sr & TS_SSR) == 0);	/* wait until subsystem ready */
	tsstatus (sr);

	return;
}

/*
 * probe for device. If found, try to raise an interrupt.
 * XXX - most of this should be done in the attach routine.
 */
int
tsmatch(parent, match, aux)
	struct	device *parent;
	void	*match, *aux;
{
	struct ts_softc *sc = match;
	struct uba_softc *uh = (void *)parent;
	struct uba_attach_args *ua = aux;
	struct tsdevice *tsregs = (struct tsdevice*)ua->ua_addr;
	volatile unsigned int sr, timeout, count;
	int ctlr = sc->sc_dev.dv_unit;

	ts_wtab[ctlr] = NULL;
	sc->sc_ts = &ts[ctlr];
	sc->sc_state = ST_PROBE;
	sc->sc_flags = 0;
	ts[ctlr].reg = (struct tsdevice*)ua->ua_addr;

	/*
	 * Set host-settable interrupt vector.
	 * Assign 0 to the TSSR register to start the ts-device initialization.
	 * The device is not really initialized at this point, this is just to
	 * find out if the device exists. 
	 */
	sc->sc_ivec = (uh->uh_lastiv -= 4);

	count = 0;
again:
	timeout = 1000;		/* timeout in 10 seconds */	
	tsregs->tssr = 0;			/* start initialization */
	do {
		DELAY(10000);
		sr = tsregs->tssr;
		debug10 (("\ttssr-1: 0x%x\n", sr));
		if (timeout-- > 0) {
			if (sr != 0)	/* the device exists !!! */
				printf ("%s: timeout waiting for TS_SSR\n",
					sc->sc_dev.dv_xname);
			tsstatus (sr);
			goto bad;
		}
	} while ((sr & TS_SSR) == 0);	/* wait until subsystem ready */
	tsstatus (sr);

	tswchar (ctlr);		/* write charact. to enable interrupts */
				/* completion of this will raise the intr. */

#ifdef notyet
	sc->sc_ipl = br = qbgetpri();
#else
	sc->sc_ipl = 0x15;
#endif
	return (sizeof (struct tsdevice));

bad:	if (++count < 3)
		goto again;

#ifdef notyet
	splx(s);
#endif
	return (0);
}


/*
 * Try to find a slave (a drive) on the controller.
 * Since there's only one drive per controller there's nothing to do.
 * (we could check the status of the drive (online/offline/...)
 */
void
tsattach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct ts_softc *sc = (void *)self;
	int ctlr = sc->sc_dev.dv_unit;
	struct tsmsg *tsmsgp = &ts[ctlr].msg;

	trace (("tsslave (%x, %x)\n", ui, reg));
	
	/*
	 * write the characteristics (again)
	 * This will raise an interrupt during ST_SLAVE which indicates
	 * completion of initialization ...
	 */
	sc->sc_state = ST_SLAVE;	/* tsintr() checks this ... */
	if (tswchar (ctlr) < 0) {
		printf ("%s: cannot initialize", sc->sc_dev.dv_xname);
	}
	sc->sc_micro = (tsmsgp->xst2 & TS_SF_MCRL) >> 2;
	printf ("%s: rev %d, extended features %s, transport %s\n",
		sc->sc_dev.dv_xname, sc->sc_micro,
		(tsmsgp->xst2 & TS_SF_EFES ? "enabled" : "disabled"),
		(ts[ctlr].reg->tssr & TS_OFL ? "offline" : "online"));

	tsinit (sc);		/* must be called once, why not here ? */
}


/*
 * TSV05/TS05 interrupt routine
 */
void
tsintr(ctlr)
	int ctlr;
{
	register struct ts_softc *sc = ts_cd.cd_devs[ctlr];
	register struct tsmsg *tsmsgp = &ts[ctlr].msg;
	register struct tscmd *tscmdp = &ts[ctlr].cmd;
	volatile struct tsdevice *tsreg = ts[ctlr].reg;
	struct uba_unit *um = &sc->sc_unit;
	register struct buf *bp;

	unsigned short sr = tsreg->tssr;	/* save TSSR */
	unsigned short mh = tsmsgp->hdr;	/* and msg-header */
		/* clear the message header ??? */

	short ccode = tscmdp->cmdr & TS_CF_CCODE;
	short cmask = tscmdp->cmdr & TS_CF_CMASK;

#ifdef DEBUG
	{
		char bits[64];
		printf ("TSSR: %s, MSG: %x ", bitmask_snprintf(sr,
		    TS_TSSR_BITS, bits, sizeof(bits)), mh);
	}
	switch (tsmsgp->hdr & 0x001F) {
	case 16:	printf ("(End)");	break;
	case 17:	printf ("(Fail)");	break;
	case 18:	printf ("(Error)");	break;
	case 19:	printf ("(Attention)"); break;
	}
#endif

	trace (("  tsintr (%d, %d, %d, %x)\n", uba, vector, level, ctlr));

	if (tsmsgp->xst0 & TS_SF_VCK)
		sc->sc_cmdf |= TS_CF_CVC;

#ifdef QBA				/* copied from uda.c */
	if(vax_cputype == VAX_78032)
		splx(sc->sc_ipl);	/* Qbus interrupt protocol is odd */
#endif

	/*
	 * There are two different things which can (and should) be checked:
	 * the actual (internal) state and the device's result (tssr/msg.hdr)
	 * 
	 * For each state there's only one "normal" interrupt. Anything else
	 * has to be checked more intensively. Thus in a first run according
	 * to the internal state the expected interrupt is checked/handled.
	 *
	 * In a second run the remaining (not yet handled) interrupts are
	 * checked according to the drive's result.
	 */
	switch (sc->sc_state) {

	case ST_INVALID:
		/*
		 * Ignore unsolicited interrupts.
		 */
		log (LOG_WARNING, "%s: stray intr [%x,%x]\n", 
			sc->sc_dev.dv_xname, sr, mh);
		return;

	case ST_SLAVE:
		/*
		 * this interrupt was caused by write-charact. command
		 * issued by tsslave() and indicates the end of the
		 * initialization phase. Just ignore it ...
		 */
		if ((sr & TS_SC) != 0 || (sr & TS_TC) != TS_TC_NORM) {
			printf("%s: problem during init [%x,%x]\n",
				sc->sc_dev.dv_xname, sr, mh);
			/* return here ??? */
			/* break and check the error outside switch ??? */
			break;
		}
		sc->sc_state = ST_RUNNING;
		return;


	case ST_RUNNING:
		/*
		 * Here we expect interrupts indicating the end of
		 * commands or indicating problems.
		 */
		/*
		 * Anything else is handled outside this switch ...
		 */
		break;

	case ST_IDLE:
		break;


	default:
		printf ("%s: unexpected interrupt during state %d [%x,%x]\n", 
			sc->sc_dev.dv_xname, sc->sc_state, sr, mh);
		return;
	}

	/*
	 * now we check the termination class.
	 */
	switch (sr & TS_TC) {

	case TS_TC_NORM:
		/*
		 * Normal termination -- The operation is completed
		 * witout incident.
		 */
		sc->sc_state = ST_IDLE;		/* XXX ??? */
		sc->sc_state = ST_RUNNING;
		sc->sc_liowf = (ccode == TS_CC_WRITE);
		sc->sc_rtc = 0;
		if ((bp = ts_wtab[ctlr]) != NULL) {
			ts_wtab[ctlr] = NULL;	/* pseudo-unlink */

			if (bp != &ts_cbuf[ctlr]) {	/* no ioctl */
				ubarelse((struct uba_softc *)
				    sc->sc_dev.dv_parent,
				    (int *)&bp->b_ubinfo);
#if defined(VAX750)
				if (vax_cputype == VAX_750 &&
				    sc->sc_unit.uu_ubinfo != 0)
					ubarelse((struct uba_softc *)
					    sc->sc_dev.dv_parent,
					    &sc->sc_unit.uu_ubinfo);
					/* XXX */
#endif
			}
			bp->b_resid = tsmsgp->rbpcr;
			debug (("tsintr: biodone(NORM) [%d,%d,%d]\n",
				bp->b_resid, bp->b_bcount, tsmsgp->rbpcr));
			biodone (bp); /* bertram: ioctl ??? */
		}
		return;

	case TS_TC_ATTN:
		/*
		 * Attention condition -- this code indicates that the
		 * drive has undergone a status change, such as going
		 * off-line or coming on-line.
		 * (Without EAI enabled, no Attention interrupts occur.
		 * drive status changes are signaled by the VCK flag.)
		 */
		return;

	case TS_TC_TSA:
		/* 
		 * Tape Status Alert -- A status condition is encountered
		 * that may have significance to the program. Bits of
		 * interest in the extended status registers include
		 * TMK, EOT and RLL.
		 */
		debug (("Tape Status Alert\n"));
		tsxstatus (tsmsgp);
		if (tsmsgp->xst0 & TS_SF_TMK) {
			debug (("Tape Mark detected"));
		}
		if (tsmsgp->xst0 & TS_SF_EOT) {
			debug (("End of Tape"));
		}
		break;

	case TS_TC_FR: 
		/*
		 * Function Reject -- The specified function was not
		 * initiated. Bits of interest include OFL, VCK, BOT,
		 * WLE, ILC and ILA.
		 */
		debug (("Function reject\n")); 
		tsxstatus (tsmsgp);
		if (sr & TS_OFL) {
			printf ("tape is off-line.\n");
			break;
		}
		if (tsmsgp->xst0 & TS_SF_VCK) {
			printf ("Volume check: repeating command ...\n");
			tsexec (ctlr, tscmdp->cmdr);
			return;
		}
		if (tsmsgp->xst0 & TS_SF_BOT) {
			printf ("bottom of tape.\n");
		}
		if (tsmsgp->xst0 & TS_SF_WLE) {
			printf ("Write Lock Error\n");
		}
		break;

	case TS_TC_TPD:
		/*
		 * Recoverable Error -- Tape position is a record beyond
		 * what its position was when the function was initiated.
		 * Suggested recovery procedure is to log the error and
		 * issue the appropriate retry command.
		 */
		debug (("Tape position down\n")); 
		switch (cmask) {
		case TS_CMD_RNF:	/* Read Next (forward) */
			debug (("retry read forward ...\n"));
			sc->sc_rtc = 1;
			tsexec (ctlr, TS_CMD_RPF);
			return;
		case TS_CMD_RPR:	/* Read Previous (Reverse) */
			debug (("retry read reverse ...\n"));
			sc->sc_rtc = 1;
			tsexec (ctlr, TS_CMD_RNR);
			return;
		case TS_CMD_WD:		/* Write Data (Next) */
			debug (("retry write data ...\n"));
			sc->sc_rtc = 1;		
			tsexec (ctlr, TS_CMD_WDR);
			return;
		case TS_CMD_WTM:
			debug (("retry write tape mark ...\n"));
			sc->sc_rtc = 1;
			tsexec (ctlr, TS_CMD_WTMR);
			return;
		default:
			debug (("TPD in command %x\n", cmask));
		}
		break;

	case TS_TC_TNM:
		/*
		 * Recoverable Error -- Tape position has not changed.
		 * Suggested recovery procedure is to log the error and
		 * reissue the original command.
		 */
		printf ("Tape not moved\n"); 
		if (sc->sc_rtc < 3) {
			sc->sc_rtc++;
			/* bertram: log the error !!! */
			printf ("retrying command %x (%d)\n", 
				tscmdp->cmdr, sc->sc_rtc);
			tsexec (ctlr, tscmdp->cmdr);
			return;
		}
		break;

	case TS_TC_TPL:
		/*
		 * Unrecoverable Error -- Tape position has been lost.
		 * No valid recovery procedures exist unless the tape
		 * has labels or sequence numbers.
		 */
		printf ("Tape position lost\n"); 
		break;

	case TS_TC_FCE:
		/*
		 * Fatal subsytem Error -- The subsytem is incapable
		 * of properly performing commands, or at least its
		 * integrity is seriously questionable. Refer to the 
		 * fatal class code field in the TSSR register for
		 * additional information on the type of fatal error.
		 */
		printf ("Fatal Controller Error\n"); 

	default:
		printf ("%s: error 0x%x, resetting controller\n", 
			sc->sc_dev.dv_xname, sr & TS_TC);
		tsreset (ctlr);
	}

	/*
	 * reset controller ?? call tsinit() ???
	 */
	if ((bp = ts_wtab[ctlr]) != NULL) {
		ts_wtab[ctlr] = NULL;		/* pseudo unlink */

		if (bp != &ts_cbuf[ctlr]) 	/* no ioctl */
			ubarelse((struct uba_softc *)sc->sc_dev.dv_parent,
			    (int *)&bp->b_ubinfo);

		if ((sr & TS_TC) != TS_TC_NORM)
			bp->b_flags |= B_ERROR;

		debug (("resid:%d, count:%d, rbpcr:%d\n",
			bp->b_resid, bp->b_bcount, tsmsgp->rbpcr));
		bp->b_resid = tsmsgp->rbpcr; /* XXX */
		debug (("tsintr: biodone(%x)\n", bp->b_flags));
		biodone (bp);
	}
	if ((sr & TS_TC) > TS_TC_FR)
		tsreset (ctlr);

	return;
}


/*
 * Open a ts device and set the unit online.  If the controller is not
 * in the run state, call init to initialize the ts controller first.
 */
int
tsopen (dev, flag, type, p)
	dev_t dev;
	int flag, type;
	struct proc *p;
{
	register struct uba_device *ui;
	register struct uba_ctlr *um;
	register struct ts_softc *sc;
	register int unit = TS_UNIT(dev);
	int s;

	trace (("tsopen (%x, %x)\n", dev, flag));
	
	if (unit >= ts_cd.cd_ndevs)
		return ENXIO;

	sc = ts_cd.cd_devs[unit];
	if (sc == 0)
		return ENXIO;

	if (sc->sc_openf)
		return EBUSY;

	sc->sc_openf = 1;

	s = splbio ();
	if (sc->sc_state < ST_RUNNING) {		/* XXX */
		printf ("%s not running.\n", sc->sc_dev.dv_xname);
		splx(s);
		sc->sc_openf = 0;
		return (ENXIO);
	}
	splx(s);

	/*
	 * check if transport is really online.
	 * (without attention-interrupts enabled, we really don't know
	 * the actual state of the transport. Thus we call get-status
	 * (ie. MTNOP) once and check the actual status.)
	 */
	tscommand (dev, MTNOP, 1);
	if (ts[unit].reg->tssr & TS_OFL) {
		printf ("%s: transport is offline.\n", sc->sc_dev.dv_xname);
		sc->sc_openf = 0;
		return EIO;		/* transport is offline */
	}

	sc->sc_liowf = 0;
	return 0;
}


/*
 * Close tape device.
 *
 * If tape was open for writing or last operation was
 * a write, then write two EOF's and backspace over the last one.
 * Unless this is a non-rewinding special file, rewind the tape.
 *
 * Make the tape available to others, by clearing openf flag.
 */
int
tsclose (dev, flag, type, p)
	dev_t dev;
	int flag, type;
	struct proc *p;
{
	register struct ts_softc *sc = ts_cd.cd_devs[TS_UNIT(dev)];

	if (flag == FWRITE || ((flag & FWRITE) && sc->sc_liowf)) {
		/* 
		 * We are writing two tape marks (EOT), but place the tape
		 * before the second one, so that another write operation
		 * will overwrite the second one and leave and EOF-mark.
		 */
		tscommand (dev, MTWEOF, 1);	/* Write Tape Mark */
		tscommand (dev, MTWEOF, 1);	/* Write Tape Mark */
		tscommand (dev, MTBSF,	1);	/* Skip Tape Marks Reverse */
	}

	if ((dev & T_NOREWIND) == 0)
		tscommand (dev, MTREW, 0);

	sc->sc_openf = 0;
	sc->sc_liowf = 0;
	return 0;
}


/*
 * Manage buffers and perform block mode read and write operations.
 */
void
tsstrategy (bp)
	register struct buf *bp;
{
	register int unit = TS_UNIT(bp->b_dev);
	struct ts_softc *sc = (void *)ts_cd.cd_devs[unit];
	int s;

	s = splbio ();
	/*
	 * we have only one command at one time, no credits.
	 * thus we don't need buffer management and controller queue
	 * just try to execute the command ...
	 */

	tsstart (sc, bp);
	splx(s);
	return;
}


/*
 * Catch ioctl commands, and call the "command" routine to do them.
 */
int
tsioctl (dev, cmd, data, flag, p)
	dev_t dev;
	u_long cmd;
	caddr_t data;
	int flag;
	struct proc *p;
{
	register struct buf *bp = &ts_cbuf[TS_UNIT(dev)];
	register struct ts_softc *sc;
	register struct mtop *mtop;	/* mag tape cmd op to perform */
	register struct mtget *mtget;	/* mag tape struct to get info in */
	register callcount;		/* number of times to call routine */
	int scount;			/* number of files/records to space */
	int spaceop = 0;		/* flag for skip/space operation */
	int error = 0;

	trace (("tsioctl (%x, %x, %x, %d)\n", dev, cmd, data, flag));

	switch (cmd) {
	case MTIOCTOP:			/* do a mag tape op */
		mtop = (struct mtop *)data;
		switch (mtop->mt_op) {
		case MTWEOF:		/* write an end-of-file record */
			callcount = mtop->mt_count;
			scount = 1;
			break;
		case MTFSF:		/* forward space file */
		case MTBSF:		/* backward space file */
		case MTFSR:		/* forward space record */
		case MTBSR:		/* backward space record */
			spaceop = 1;
			callcount = 1;
			scount = mtop->mt_count;
			break;
		case MTREW:		/* rewind */
		case MTOFFL:		/* rewind and put the drive offline */
		case MTNOP:		/* no operation, sets status only */
			callcount = 1;
			scount = 1;		/* wait for this rewind */
			break;
		case MTRETEN:		/* retension */
		case MTERASE:		/* erase entire tape */
		case MTEOM:		/* forward to end of media */
		case MTNBSF:		/* backward space to begin of file */
		case MTCACHE:		/* enable controller cache */
		case MTNOCACHE:		/* disable controller cache */
		case MTSETBSIZ:		/* set block size; 0 for variable */
		case MTSETDNSTY:	/* set density code for current mode */
			debug (("ioctl %d not implemented.\n", mtop->mt_op));
			return (ENXIO);
		default:
			debug (("invalid ioctl %d\n", mtop->mt_op));
			return (ENXIO);
		}	/* switch (mtop->mt_op) */

		if (callcount <= 0 || scount <= 0) {
			debug (("invalid values %d/%d\n", callcount, scount));
			return (EINVAL);
		}
		do {
			tscommand (dev, mtop->mt_op, scount);
			if (spaceop && bp->b_resid) {
				debug (("spaceop didn't complete\n"));
				return (EIO);
			}
			if (bp->b_flags & B_ERROR) {
				debug (("error in ioctl %d\n", mtop->mt_op));
				break;
			}
		} while (--callcount > 0);
		if (bp->b_flags & B_ERROR) 
			if ((error = bp->b_error) == 0)
				return (EIO);
		return (error);		

	case MTIOCGET:			/* get tape status */
		sc = ts_cd.cd_devs[TS_UNIT(dev)];
		mtget = (struct mtget *)data;
		mtget->mt_type = MT_ISTS;
		mtget->mt_dsreg = (unsigned)(ts[TS_UNIT(dev)].reg->tssr);
		mtget->mt_erreg = (unsigned)(ts[TS_UNIT(dev)].msg.hdr);
		mtget->mt_resid = 0;		/* ??? */
		mtget->mt_density = 0;		/* ??? */
		break;

	case MTIOCIEOT:			/* ignore EOT error */
		debug (("MTIOCIEOT not implemented.\n"));
		return (ENXIO); 

	case MTIOCEEOT:			/* enable EOT error */
		debug (("MTIOCEEOT not implemented.\n"));
		return (ENXIO);

	default:
		debug (("invalid ioctl cmd 0x%x\n", cmd));
		return (ENXIO);
	}

	return (0);
}


/*
 * 
 */
int
tsread (dev, uio)
	dev_t dev;
	struct uio *uio;
{
	return (physio (tsstrategy, NULL, dev, B_READ, minphys, uio));
}

/*
 *
 */
int
tswrite (dev, uio)
	dev_t dev;
	struct uio *uio;
{
	return (physio (tsstrategy, NULL, dev, B_WRITE, minphys, uio));
}

/*
 *
 */
int
tsdump(dev, blkno, va, size)
	dev_t dev;
	daddr64_t blkno;
	caddr_t va;
	size_t size;
{
	trace (("tsdump (%x)\n", dev));
	return 0;
}

/*----------------------------------------------------------------------*/

int
tsstatus (sr)
	int sr;
{
#ifdef DEBUG
	char bits[64];

	debug (("status: TSSR=%s\n", bitmask_snprintf(sr, TS_TSSR_BITS,
	    bits, sizeof(bits))));

	if (tsdebug < 5)
		return (0);

	if (sr & TS_SC)		printf ("special condition\n");
	if (sr & TS_UPE)	printf ("UPE\n");
	if (sr & TS_SCE)	printf ("Sanity Check Error\n");
	if (sr & TS_RMR)	printf ("Register Modification Refused\n");
	if (sr & TS_NXM)	printf ("Nonexistent Memory\n");
	if (sr & TS_NBA)	printf ("Need Buffer Address\n");
	if (sr & TS_A11)	printf ("Address Bits 17-16\n");
	if (sr & TS_SSR)	printf ("Subsystem Ready\n");
	if (sr & TS_OFL)	printf ("Off Line\n");
	if (sr & TS_FTC)	printf ("Fatal Termination Class Code\n");
	switch (sr & TS_TC) {
	case TS_TC_NORM:	printf ("Normal Termination\n"); break;
	case TS_TC_ATTN:	printf ("Attention Condition\n"); break;
	case TS_TC_TSA:		printf ("Tape status \n"); break;
	case TS_TC_FR:		printf ("Function reject\n"); break;
	case TS_TC_TPD:		printf ("Tape position down\n"); break;
	case TS_TC_TNM:		printf ("Tape not moved\n"); break;
	case TS_TC_TPL:		printf ("Tape position lost\n"); break;
	case TS_TC_FCE:		printf ("Fatal Controller Error\n"); break;
	}
#endif
	return (0);
}

void
tsxstatus (mp) 
	struct tsmsg *mp;
{
#ifdef DEBUG
	char bits[64];

	debug (("tsxstatus: xst0=%s, ", bitmask_snprintf(mp->xst0,
	    TS_XST0_BITS, bits, sizeof(bits))));
	debug (("xst1=%s, ", bitmask_snprintf(mp->xst1, TS_XST1_BITS,
	    bits, sizeof(bits))));
	debug (("xst2=%s, ", bitmask_snprintf(mp->xst2, TS_XST2_BITS, 
	    bits, sizeof(bits))));
	debug (("xst3=%s, ", bitmask_snprintf(mp->xst3, TS_XST3_BITS,
	    bits, sizeof(bits))));
	debug (("xst4=%s\n", bitmask_snprintf(mp->xst4, "\20",
	    bits, sizeof(bits))));

	if (tsdebug < 10)
		return (0);

	if (mp->xst0 & TS_SF_TMK)	printf ("Tape Mark Detected\n");
	if (mp->xst0 & TS_SF_RLS)	printf ("Record Length Short\n");
	if (mp->xst0 & TS_SF_LET)	printf ("Logical End of Tape\n");
	if (mp->xst0 & TS_SF_RLL)	printf ("Record Length Long\n");
	if (mp->xst0 & TS_SF_WLE)	printf ("Write Lock Error\n");
	if (mp->xst0 & TS_SF_NEF)	printf ("Nonexecutable Function\n");
	if (mp->xst0 & TS_SF_ILC)	printf ("Illegal Command\n");
	if (mp->xst0 & TS_SF_ILA)	printf ("Illegal Address\n");
	if (mp->xst0 & TS_SF_MOT)	printf ("Motion\n");
	if (mp->xst0 & TS_SF_ONL)	printf ("On-Line\n");
	if (mp->xst0 & TS_SF_IE)	printf ("Interrupt Enable\n");
	if (mp->xst0 & TS_SF_VCK)	printf ("Volume Check\n");
	if (mp->xst0 & TS_SF_PED)	printf ("Phase Encoded Drive\n");
	if (mp->xst0 & TS_SF_WLK)	printf ("Write Locked\n");
	if (mp->xst0 & TS_SF_BOT)	printf ("Beginning of Tape\n");
	if (mp->xst0 & TS_SF_EOT)	printf ("End of Tape\n");

	if (mp->xst1 & TS_SF_DLT)	printf ("Data Late\n");
	if (mp->xst1 & TS_SF_COR)	printf ("Correctable Data\n");
	if (mp->xst1 & TS_SF_RBP)	printf ("Read Bus Parity Error\n");
	if (mp->xst1 & TS_SF_UNC)	printf ("Uncorrectable Data or Hard Error\n");

	if (mp->xst2 & TS_SF_OPM)	printf ("Operation in Progress\n");
	if (mp->xst2 & TS_SF_RCE)	printf ("RAM Checksum Error\n");
	if (mp->xst2 & TS_SF_WCF)	printf ("Write Clock Failure\n");
	if (mp->xst2 & TS_SF_EFES)	printf ("extended features enabled\n");
	if (mp->xst2 & TS_SF_BES)	printf ("Buffering enabled\n");
	
	printf ("micro-code revision level: %d\n", (mp->xst2 & TS_SF_MCRL)>>2);
	printf ("unit number: %d\n", (mp->xst2 & TS_SF_UNIT));

	if (mp->xst3 & TS_SF_MDE)
		printf ("Micro-Diagnostics Error Code: 0x%x\n", mp->xst3 >> 8);
	if (mp->xst3 & TS_SF_OPI)	printf ("Operation Incomplete\n");
	if (mp->xst3 & TS_SF_REV)	printf ("Revers\n");
	if (mp->xst3 & TS_SF_DCK)	printf ("Density Check\n");
	if (mp->xst3 & TS_SF_RIB)	printf ("Reverse into BOT\n");

	if (mp->xst4 & TS_SF_HSP)	printf ("High Speed\n");
	if (mp->xst4 & TS_SF_RCX)	printf ("Retry Count Exceeded\n");
#endif
}