#define	TMSDEBUG	1

/*	@(#)tmscp.c	1.12 (2.11BSD) 1999/2/25 */

#if	!defined(lint) && defined(DOSCCS)
static	char	*sccsid = "@(#)tmscp.c	1.24	(ULTRIX)	1/21/86";
#endif

/************************************************************************
 *        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. 						*
 ************************************************************************
 * 
 * tmscp.c - TMSCP (TK50/TU81) tape device driver
 * 
 * Modification History:
 * 25-Feb-99 - sms
 *	Fix density selection to preserve the high byte of m_format.  This
 *	is required devices (such as the TK70) which have a nonzero value
 *	in this field.
 *
 * 22-Feb-99 - sms
 *	Add timeout logic to tmscpcommand to catch hardware going catatonic.
 *	When tmscpcommand() was first created the only use was to issue a
 *	nonblocking rewind upon close.  Tmscpcommand is now called for many
 *	other functions some of which can leave a process (and the drive) hung
 *	unless a timeout is done.
 *
 *	Remove special treatment of OFFLINE and AVLBL status codes in 
 *	tms_iodone().  The code is suspected of having been a bug even in
 *	its old location but is definitely causing problems now - if a read
 *	completes with a 'AVLBL' status the drive is hung because an iodone()
 *	is never performed.
 *
 *	Minor cleanup done thruout (reduce D space consumption ,etc).
 *
 * 24-Apr-98 - sms
 *	An incorrect pointer was being passed to tms_iodone() from tmscprsp()
 *	when dealing with the 'invalid endcode' case.  While that value should
 * 	never be generated by normal hardware the kernel should not panic.
 *
 * 07-Mar-98 - sms
 *	Fix a bug that caused EOM to be 'sticky'.  Once EndOfMedia was detected
 *	the only way to clear it was to unload and reload the tape.  Remove
 *	unused flag definitions.  Only use a single 'written' flag instead of
 *	two (one in tms_flags and another in Tflags).
 *
 * 01-Feb-98 - sms
 *	Initially the thought was the driver was broken sometime around June
 *	1996.  After adding additional logging and the 'sysctl' interface it
 *	was discovered (eventually after several long nights) that both my
 *	TK50 and another person's TU81+ had developed hardware problems.  The
 *	better logging and sysctl changes were retained for future use ;)
 *	
 *	Failure to 'online' a drive was changed to return EIO instead of ENXIO
 *	because 'device not configured' means the drive is not present.
 *
 *	A 'sysctl' interface was added so that 'tmscpprintf' could be changed
 *	without rebooting/recompiling the system.  Also sysctl can be used to
 *	set the cache on/off status even if a tape is not at the load point - 
 *	the setting takes effect on the next open().
 *
 * 14-May-96 - sms
 *	Missing parens caused mtflush,mtcache,mtnocache to be always skipped.
 *	The use cache bit was being cleared in tmscpopen(). The use cache bit
 *	is sticky in that once set via MTCACHE it stays set until cleared by
 *	MTNOCACHE.
 *
 * 14-Dec-95 - sms@wlv...
 *	Success!  But the size of the driver is even more of a problem now.
 *	If the crash dump option is defined the driver could exceed the max
 *	size permitted for an overlay.  The 'tmsdump' routine was moved to
 *	a separate file.  This driver almost always must be in an overlay 
 *	by itself now.
 *
 * 08-Dec-95 - sms@wlv...
 *	Begin serious surgery on this driver.  The goals are: 1) to correctly
 *	handle TU81/81+ drives (do not set density unless at BOT), 2) support
 *	use of the cache on drives which implement it (TU81+), 3) improve 
 *	error detection and reporting, 4) improve readability by replacing
 *	multipage 'switch' statements with calls to functions, 5) make it
 *	easier to add new capabilities and fix problems without wholescale
 *	changes again.
 *
 * 30-Nov-95 - sms@wlv...
 *	Experiment to see if M_MD_IMMED being set for the rewind on close
 *	obviates the need for skipping the iowait().  Success - only 2 ticks
 *	elapse doing the iowait!.
 *
 * 02-Jan-93 - sms@wlv. [2.11BSD]
 *	Remove unibus map ifdefs and rely on run time testing of 'ubmap' which
 * 	does the right thing and makes kernels easier to move between machines.
 *
 * 20-Nov-92 - sms@wlv.iipo.gtegsc.com [2.11BSD]
 *	Add tmsVec() for autoconfig to use in passing the vector from /etc/dtab
 *	to this driver.  The previous scheme of having a fixed vector with
 *	all TMSCP vectors contiguous has been removed.
 *
 * 23-May-91 - sms@wlv.iipo.gtegsc.com [2.11BSD]
 *	Minor typo in the multicontroller support fixed.  Major overhaul
 *	of the density selection, apparently some TMSCP controllers
 *	do not treat 0 in the mscp_format word as meaning the highest
 *	density, instead leaving the drive in the current/last-used
 *	density.
 *
 * 29-Mar-91 - sms@wlv.iipo.gtegsc.com [2.11BSD]
 *	Major changes to 1) support multiple drives per controller
 *	(the maximum number of drives per controller is 4 for now in order
 *	to maximize compatibility with existing minor device numbers - the
 *	norewind and density bit stay in the same place) and 2) more
 *	importantly reduce the bloat of this driver.  The command
 *	packet area is now allocated from an external heap setup at boot
 *	time (the MSCP, ra.c driver also does this).  Allocating from an
 * 	external arena save approximately 2kb of kernel D space PER CONTROLLER
 *	and costs little in speed because the amount of remapping involved is
 *	quite small.
 *
 *	The 'errinfo' routine was removed in the interest of space savings,
 *	the error code was already being printed out in hex and it's not
 *	worth eating up another 250 or so bytes of kernel D space to pretty
 *	print messages for which tmscp.h provides the cross reference (I space
 *	is also saved by not printing the messages).  Besides, the ra.c (MSCP)
 *	driver doesn't do it and it is worth a degree of non 4.3BSD verisimility
 *	to save a significant amount of space.
 *
 *	The tms_type field should have been a 'long' (mediatype).  Since the
 *	drives are not probed at autoconfigure time a GTUNT (TMS_SENSE) command
 *	is done at open() time to fetch the format/density menu and unit flags.
 *	iodone() proccessing was missing for the GTUNT function in tmscprsp()
 *	causing hangs in both open and ioctl functions.
 *
 *	Multiple controller support made to work, the top 2 bits of the minor
 *	device number are used to designate the controller, thus there is
 *	a maximum of 4 TMSCP controllers per system.
 *
 * 17-Jun-90,14Aug90 - sms@wlv.iipo.gtegsc.com
 *	Began porting to 2.10.1BSD/2.11BSD.  Multiple drives per controller
 *	NOT supported, although multiple controllers are (maybe).  Programmable
 *	vectors don't work very well with the autoconfigure scheme in use.
 *	the define TMSCP_VEC will have to be adjusted in tms.h (see
 *	conf/config and the sample config files).  For patching purposes
 *	the global "TMSvec" is declared and initialized with TMSCP_VEC.
 *
 * 06-Jan-86 - afd
 *	Changed the probe routine to use DELAY (not TODR).  This now
 *	works for MicroVAXen as well.  This eliminates the busy-wait
 *	for MicroVAXen so a dead TK50 controller will not hang autoconf.
 *
 * 06-Dec-85 - afd
 *	Fixed a bug in density selection.  The "set unit characteristics"
 *	command to select density, was clearing the "unit flags" field
 *	where the CACHE bit was for TU81-E.  Now the unit's "format" and
 *	"unitflgs" are saved in tms_info struct.  And are used on STUNT
 *	commands.
 *
 * 19-Oct-85 - afd
 *	Added support to the open routine to allow drives to be opened
 *	for low density (800 or 1600 bpi) use.  When the slave routine
 *	initiates a "get-unit-char" cmd, the format menu for the unit
 *	is saved in the tms_info structure. The format menu is used in the
 *	start routine to select the proper low density.
 *
 * 02-Oct-85 - afd
 *	When a tmscp-type controller is initializing, it is possible for
 *	the sa reg to become 0 between states.  Thus the init code in
 *	the interrupt routine had to be modified to reflect this.
 *
 * 21-Sep-85 - afd
 *	The TK50 declares a serious exception when a tape mark is encountered.
 *	This causes problems to dd (& other UN*X utilities).  So a flag
 *	is set in the rsp() routine when a tape mark is encountered.  If
 *	this flag is set, the start() routine appends the Clear Serious
 *	Exception modifier to the next command.
 *
 * 03-Sep-85 -- jaw
 *	messed up previous edit..
 *
 * 29-Aug-85 - jaw
 *	fixed bugs in 8200 and 750 buffered datapath handling.
 *
 * 06-Aug-85 - afd
 *   1. When repositioning records or files, the count of items skipped
 *	does NOT HAVE to be returned by controllers (& the TU81 doesn't).
 *	So tmscprsp() had to be modified to stop reporting
 *	residual count errors on reposition commands.
 *
 *   2. Fixed bug in the open routine which allowed multiple opens.
 *
 * 18-Jul-85 - afd
 *   1. Need to return status when mt status (or corresponding ioctl) is done.
 *	Save resid, flags, endcode & status in tmscprsp() routine (except on
 *	clear serious exception no-op).  Return these fields when status
 *	ioctl is done (in tmscpcommand()).  How they are returned:
 *		mt_resid = resid
 *		mt_dsreg = flags|endcode
 *		mt_erreg = status
 *
 *   2. Added latent support for enabling/disabling caching.  This is
 *	handled along with all other ioctl commands.
 *
 *   3. Need to issue a no-op on unrecognized ioctl in tmsstart(), since
 *	we have already commited to issuing a command at that point.
 *
 *   4. In tmscprsp() routine if encode is 0200 (invalid command issued);
 *	We need to: Unlink the buffer from the I/O wait queue,
 *	and signal iodone, so the higher level command can exit!
 *	Just as if it were a valid command.
 *
 * 11-jul-85 -- jaw
 *	fix bua/bda map registers.
 *
 * 19-Jun-85 -- jaw
 *	VAX8200 name change.
 *
 * 06-Jun-85 - jaw
 *	fixes for 8200.
 *
 * 9-Apr-85 - afd
 *	Added timeout code to the probe routine, so if the controller
 *	fails to init in 10 seconds we return failed status.
 *
 * 13-Mar-85 -jaw
 *	Changes for support of the VAX8200 were merged in.
 *
 * 27-Feb-85 -tresvik
 *	Changes for support of the VAX8600 were merged in.
 *
 */

#include "tms.h"
#if NTMSCP > 0

#include "param.h"
#include "systm.h"
#include "buf.h"
#include "conf.h"
#include "user.h"
#include "file.h"
#include "map.h"
#include "ioctl.h"
#include "syslog.h"
#include "mtio.h"
#include "uio.h"
#include "tty.h"
#include "uba.h"
#include "kernel.h"

#include "tmscpreg.h"
#include "../pdp/tmscp.h"
#include "../machine/seg.h"

/*
 * The density array is indexed by the density bits (bits 3 and 4) of the
 * minor device number AND the format menu returned for the drive.  Since
 * only 3 densities are apparently supported by TMSCP (no DPE/3200bpi), the 
 * second MEDium table is a copy of the first MEDium entry.
*/

	char	Dmatrix[4][8] =
		{
/* LOW */   {0,M_TF_800,M_TF_PE,M_TF_800,M_TF_GCR,M_TF_800,M_TF_PE,M_TF_800},
/* MED1 */  {0,M_TF_800,M_TF_PE,M_TF_800,M_TF_GCR,M_TF_800,M_TF_PE,M_TF_PE },
/* MED2 */  {0,M_TF_800,M_TF_PE,M_TF_800,M_TF_GCR,M_TF_800,M_TF_PE,M_TF_PE },
/* HI */    {0,M_TF_800,M_TF_PE,M_TF_PE,M_TF_GCR,M_TF_GCR,M_TF_GCR,M_TF_GCR}
		};

struct	tmscp_softc tmscp_softc[NTMSCP];	/* Controller info */
struct	tms_info tms_info[NTMS];		/* Drive info */

	memaddr	tmscp[NTMSCP];		/* click addresses of ctrl comm area */

/*
 * Tflags definitions.  These take the place of several
 * individual structure members in tms_info.
*/

#define	_SEREX		0x0001		/* Serious Exception exists */
#define	_CLSEREX	0x0002		/* Do Clear Serious Exception */
#define	_LOST		0x0004		/* Position lost error happened */
#define	_BUFMARK	0x0008		/* Encountered a tape mark */
#define	_HASCACHE	0x0010		/* Drive has cache capability */
#define	_CACHE_ON	0x0020		/* Cache enabled */
#define	_CACHE_LOST	0x0040		/* Cache data loss has happened */
#define	_CACHE_WRITTEN	0x0080		/* Cache has been written */
#define	_INUSE		0x0100		/* Drive is in use */
#define	_ONLINE		0x0200		/* Drive is online */

/*
 * Internal (ioctl) command codes (these must also be declared in the
 * tmscpioctl routine).  These correspond to ioctls in mtio.h
 */
#define TMS_WRITM	0		/* write tape mark */
#define TMS_FSF		1		/* forward space file */
#define TMS_BSF		2		/* backward space file */
#define TMS_FSR		3		/* forward space record */
#define TMS_BSR		4		/* backward space record */
#define TMS_REW		5		/* rewind tape */
#define TMS_OFFL	6		/* rewind tape & mark unit offline */
#define TMS_SENSE	7		/* noop - do a get unit status */
#define TMS_CACHE	8		/* enable cache */
#define TMS_NOCACHE	9		/* disable cache */
#define	TMS_FLUSH	10		/* flush cache */
/* These go last: after all real mt cmds, just bump the numbers up */
#define TMS_CSE		11		/* clear serious exception */
#define TMS_SETDENSITY	12		/* set unit density */

/*
 * Controller states
 */
#define S_IDLE  0               /* hasn't been initialized */
#define S_STEP1 1               /* doing step 1 init */
#define S_STEP2 2               /* doing step 2 init */
#define S_STEP3 3               /* doing step 3 init */
#define S_SCHAR 4               /* doing "set controller characteristics" */
#define S_RUN   5               /* running */

static	char *tmscpstepfailed = "step%d init failed: sa %x\n";
	char *tmscpfatalerr = "tms%d,%d: fatal error %x\n";

int	tmscp_cp_wait = 0;	/* Something to wait on for command */
				/* packets and or credits. */
int	wakeup();
	int	tmswatchdog();
extern	int	hz;		/* Should find the right include */
extern	long	_iomap();
extern	u_int	tmscp_cache;	/* See pdp/kern_pdp.c */

/*
 * Bit 0 = print all non-successful response packets _except_ hitting a
 *	   tapemark (which really isn't an error).
 * Bit 1 = print datagram arrival message.
 * Bit 2 = print status of all response packets except datagrams.
 * Bit 3 = enable debugging print and log statements not covered above
*/
int	tmscpprintf = 1;

/*
 * This is settable via "sysctl -w machdep.tmscp.cache=0xXXXX".  There is one
 * bit per drive.  Bit 0 is the first drive on the first controller, bit 4 is
 * the first drive on the second controller, and so on.
 *
*/

int	tmscpcache = 0;

struct  mscp *tmscpgetcp();

#define b_qsize         b_resid         /* queue size per drive, in tmsutab */

tmsVec(ctlr, vector)
	int	ctlr, vector;
	{
	register struct tmscp_softc *sc = &tmscp_softc[ctlr];

	if	(ctlr >= NTMSCP || sc->sc_ivec)
		return(-1);
	sc->sc_ivec = vector;
	return(0);
	}

/* 
 * Open routine will issue the online command, later.
 *
 * This routine attaches controllers, not drives - sc_unit and 'unit' are 
 * the controller number not a drive unit number.  sc_com is initialized
 * to SEG5 because all communication areas are mapped to the same virtual
 * address now.
 */
tmsattach(addr, unit)
	struct tmscpdevice *addr;
	register int unit;
{
	register struct tmscp_softc *sc = &tmscp_softc[unit];

	if (unit >= NTMSCP)
		return(0);
	if (sc->sc_addr == NULL && addr != NULL) {
		tmscp[unit] = (memaddr)_ioget(sizeof (struct tmscp));
		if (tmscp[unit] == NULL)
			return(0);
		sc->sc_addr = addr;
		sc->sc_unit = unit;
		sc->sc_com = (struct tmscp *)SEG5;
		return (1);
	}
	return(0);
}

struct tms_info *
getdd()
	{
	register struct tms_info *p;

	for	(p = tms_info; p < &tms_info[NTMS]; p++)
		{
		if	(p->tms_type == 0)
			{
/*
 * Set the type with a placeholder value - we may have to sleep waiting for
 * the drive to come on line and we don't want this slot to be grabbed again.
 * The online response will load the real drive type.
*/
			p->tms_type = 1;	/* XXX */
			return(p);
			}
		}
	log(LOG_INFO, "tms: !drives\n");
	return(NULL);
	}

static int
wait_step(mask, good, csr, sc)
	u_short mask, good;
	register struct tmscpdevice *csr;
	register struct	tmscp_softc *sc;
	{
	register int i;

	for	(i = 0; i < 150; i++)
		{
		if	((csr->tmscpsa & mask) == good)
			return(0);
		delay(10000L);		/* still in step - wait 1/100 sec */
		}
	sc->sc_state = S_IDLE;
	sc->sc_ctab.b_active = 0;
	log(LOG_INFO, tmscpstepfailed, sc->sc_state, csr->tmscpsa);
	wakeup((caddr_t)&sc->sc_ctab);
	return(-1);
	}

/*
 * TMSCP interrupt routine.
 */
tmsintr(dev)
	dev_t dev;
{
	register struct tmscpdevice *tmscpaddr;
	struct buf *bp;
	int	i;
	register struct tmscp_softc *sc = &tmscp_softc[dev];
	register struct tmscp *tm = sc->sc_com;
	struct mscp *mp;
	segm seg5;

	tmscpaddr = sc->sc_addr;

	/*
	 * How the interrupt is handled depends on the state of the controller.
	 */
	switch (sc->sc_state) {

	case S_IDLE:
		log(LOG_INFO, "tms%d: rand intr\n", dev);
		return;

	/* Controller was in step 1 last, see if its gone to step 2 */
	case S_STEP1:
#		define STEP1MASK 0174377
#		define STEP1GOOD (TMSCP_STEP2|TMSCP_IE|(NCMDL2<<3)|NRSPL2)

		if	(wait_step(STEP1MASK, STEP1GOOD, tmscpaddr, sc) < 0)
			return;
		tmscpaddr->tmscpsa = (short)sc->sc_ctab.b_un.b_addr;
		sc->sc_state = S_STEP2;
		return;

	/* Controller was in step 2 last, see if its gone to step 3 */
	case S_STEP2:
#		define STEP2MASK 0174377
#		define STEP2GOOD (TMSCP_STEP3|TMSCP_IE|(sc->sc_ivec/4))

		if	(wait_step(STEP2MASK, STEP2GOOD, tmscpaddr, sc) < 0)
			return;
		tmscpaddr->tmscpsa = sc->sc_ctab.b_xmem;
		sc->sc_state = S_STEP3;
		return;

	/* Controller was in step 3 last, see if its gone to step 4 */
	case S_STEP3:
#		define STEP3MASK 0174000
#		define STEP3GOOD TMSCP_STEP4

		if	(wait_step(STEP3MASK, STEP3GOOD, tmscpaddr, sc) < 0)
			return;
		/*
		 * Get microcode version and model number of controller;
		 * Signal initialization complete (_GO) (to the controller);
		 * Set state to "set controller characteristics".
		 */
		i = tmscpaddr->tmscpsa;
		tmscpaddr->tmscpsa = TMSCP_GO | TMSCP_LF;
		sc->sc_state = S_SCHAR;

		log(LOG_INFO, "tms%d Ver %d Mod %d\n", dev, i & 0xf,
			(i >> 4) & 0xf);

	    /*
	     * Initialize the data structures (response and command queues).
	     */
	    saveseg5(seg5);
	    mapseg5(tmscp[sc->sc_unit], MAPBUFDESC);
	    tmsginit(sc, sc->sc_com->tmscp_ca.ca_rspdsc, sc->sc_com->tmscp_rsp,
			0, NRSP, TMSCP_OWN|TMSCP_INT);
	    tmsginit(sc, sc->sc_com->tmscp_ca.ca_cmddsc, sc->sc_com->tmscp_cmd,
			NRSP, NCMD, TMSCP_INT);
	    bp = &sc->sc_wtab;
	    bp->av_forw = bp->av_back = bp;
	    sc->sc_lastcmd = 1;
	    sc->sc_lastrsp = 0;
	    mp = sc->sc_com->tmscp_cmd;
	    mp->mscp_unit = mp->mscp_modifier = 0;
	    mp->mscp_flags = 0;
	    mp->mscp_version = 0;
	    mp->mscp_cntflgs = M_CF_ATTN|M_CF_MISC|M_CF_THIS;
	    /*
	     * A host time out value of 0 means that the controller will not
	     * time out.  This is ok for the TK50.
	     */
	    mp->mscp_hsttmo = 0;
	    bzero(&mp->mscp_time, sizeof (mp->mscp_time));
	    mp->mscp_cntdep = 0;
	    mp->mscp_opcode = M_OP_STCON;
	    ((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN|TMSCP_INT);
	    i = tmscpaddr->tmscpip;      /* initiate polling */
	    restorseg5(seg5);
	    return;

	case S_SCHAR:
	case S_RUN:
		break;

	default:
	    log(LOG_INFO, "tms%d: ST %d\n", dev, sc->sc_state);
	    return;
	}	/* end switch */

	/*
	 * The controller state is S_SCHAR or S_RUN
	 */

	/*
	 * If the error bit is set in the SA register then print an error
	 * message and reinitialize the controller.
	 */
	if (tmscpaddr->tmscpsa&TMSCP_ERR)
		{
		log(LOG_INFO, tmscpfatalerr, dev, sc->sc_unit, tmscpaddr->tmscpsa);
		tmscpaddr->tmscpip = 0;
		sc->sc_state = S_IDLE;
		sc->sc_ctab.b_active = 0;
		wakeup((caddr_t)&sc->sc_ctab);
		}
	/*
	 * Check for a buffer purge request. (Won't happen w/ TK50 on Q22 bus)
	 */
	saveseg5(seg5);
	mapseg5(tmscp[sc->sc_unit], MAPBUFDESC);
	if (tm->tmscp_ca.ca_bdp)
		{
		tm->tmscp_ca.ca_bdp = 0;
		tmscpaddr->tmscpsa = 0;      /* signal purge complete */
		}

	/*
	 * Check for response ring transition.
	 */
	if (tm->tmscp_ca.ca_rspint)
		{
		tm->tmscp_ca.ca_rspint = 0;
		for (i = sc->sc_lastrsp;; i++)
			{
			i %= NRSP;
			if (tm->tmscp_ca.ca_rspdsc[i].hsh&TMSCP_OWN)
			    break;
			tmscprsp(sc, i);
			tm->tmscp_ca.ca_rspdsc[i].hsh |= TMSCP_OWN;
			}
		sc->sc_lastrsp = i;
		}

	/*
	 * Check for command ring transition. (Should never happen!)
	 */
	if	(tm->tmscp_ca.ca_cmdint)
		tm->tmscp_ca.ca_cmdint = 0;
	restorseg5(seg5);
    	if (tmscp_cp_wait)
		wakeup((caddr_t)&tmscp_cp_wait);
    	(void) tmsstart(sc);
}


/*
 * Open a tmscp device and set the unit online.  If the controller is not 
 * in the run state, call init to initialize the tmscp controller first.
 */

/* ARGSUSED */
tmscpopen(dev, flag)
	dev_t dev;
	int flag;
{
	register int unit = TMSUNIT(dev);
	int ctlr = TMSCTLR(dev);
	register struct tmscp_softc *sc;
	register struct tms_info *tms;
	register struct mscp *mp;
	struct tmscpdevice *tmscpaddr;
	u_short	cmdref;
	int s,i;
	
	if (ctlr >= NTMSCP)
		return (ENXIO);
	sc = &tmscp_softc[ctlr];
	if (sc->sc_addr == NULL)
		return (ENXIO);
	if ((tms = sc->sc_drives[unit]) == NULL) {
		tms = getdd();
		if (!tms)
			return(ENXIO);
		tms->Tflags = 0;
		sc->sc_drives[unit] = tms;
	}
	else if	(tms->Tflags & _INUSE)
		return(EBUSY);
	tms->Tflags |= _INUSE;

	s = spl5();
	if (sc->sc_state != S_RUN)
		{
		if (sc->sc_state == S_IDLE)
			if	(!tkini(sc))
				{
				log(LOG_INFO, "tms%d init\n", ctlr);
				sc->sc_drives[unit] = NULL;
				tms->Tflags = 0;
				tms->tms_type = 0;
				(void) splx(s);
				return(ENXIO);
				}
		/* 
		 * Wait for initialization to complete
		 */
		timeout(wakeup,(caddr_t)&sc->sc_ctab,11*hz);	/* to be sure*/
		sleep((caddr_t)&sc->sc_ctab, PSWP+1);
		if	(sc->sc_state != S_RUN)
			{
			sc->sc_drives[unit] = NULL;
			tms->Tflags = 0;
			tms->tms_type = 0;
			(void) splx(s);
			return(EIO);
			}
		}
	(void) splx(s);

	/*
	 * If drive is not online get a command packet, sleeping if no
	 * controller credits are available at the moment.  Then start
	 * the ONLINE process.
	 */
	tmscpaddr = (struct tmscpdevice *) sc->sc_addr;
	if	(!(tms->Tflags & _ONLINE))
		{
		s = spl5();
		while	((mp = tmscpgetcp(sc)) == 0)
			{
			tmscp_cp_wait++;
			sleep((caddr_t)&tmscp_cp_wait,PSWP+1);
			tmscp_cp_wait--;
			}
		(void) splx(s);
		mapseg5(tmscp[sc->sc_unit], MAPBUFDESC);
		mp->mscp_opcode = M_OP_ONLIN;
		mp->mscp_unit = unit;		/* unit? */
		tms_clrerr(tms, mp);
	/* calculate this once instead of 4 times */
		cmdref = (u_short)&tms->tms_type;	
		mp->mscp_cmdref = cmdref; 	/* need to sleep on something */
		((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN | TMSCP_INT);
		normalseg5();
		i = tmscpaddr->tmscpip;
		/* 
		 * To make sure we wake up, timeout in 240 seconds.
		 * Wakeup in tmscprsp routine.
		 * 240 seconds (4 minutes) is necessary since a rewind
		 * can take a few minutes.
		 */
		timeout(wakeup,(caddr_t) cmdref,240 * hz);
		sleep((caddr_t)cmdref,PSWP+1);
		untimeout(wakeup, cmdref);
		}
	if	(!(tms->Tflags & _ONLINE))
		{
oops:		tms->Tflags = 0;
		tms->tms_type = 0;
		sc->sc_drives[unit] = NULL;
		return(EIO);  /* Didn't go online */
		}
/*
 * Get the unit characteristics (GTUNT).  This 1) Verifies the drive
 * is really still online and 2) Retrieves information about the drive
 * such as density choices, cache presence, etc.
*/
	tms->tms_flags = 0;
	if	(tmscpcache & ((1 << unit) << (4 * ctlr)))
		tms->Tflags |= _CACHE_ON;
	i = tms->Tflags & _CACHE_ON;
	tms->Tflags = _ONLINE | _INUSE | i;	/* Clear all other flags */
	tmscpcommand(dev, TMS_SENSE, 1);
	if	(!(tms->Tflags & _ONLINE))
		goto oops;

/*
 * Next issue a reposition NOP by spacing forward 0 records.  This returns
 * the current position (so we can tell if we're at BOT or not) and also 
 * clears any serious exceptions outstanding.
*/
	tmscpcommand(dev, TMS_CSE, 1);

/*
 * Now go set the density - the lower level routines set the density if 
 * the drive is at BOT.
*/
	tmscpcommand(dev, TMS_SETDENSITY, 1);
	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.
*/
tmscpclose(dev, flag)
	register dev_t dev;
	register flag;
	{
	struct tmscp_softc *sc;
	register struct tms_info *tms;
	int unit = TMSUNIT(dev);

	sc = &tmscp_softc[TMSCTLR(dev)];
	tms = sc->sc_drives[unit];

	if	((tms->Tflags & _CACHE_ON) && (tms->Tflags & _CACHE_WRITTEN))
		{
/*
 * A misbehaving application has closed the device without flushing 
 * the enabled cache by issuing a MTFLUSH - we'll do it for it.
*/
		tmscpcommand(dev, TMS_FLUSH, 1);
		if	(tms->tms_status != M_ST_SUCC)
			{
/*
 * This is BAD news - the flush didn't work.  The drive is in a serious
 * exception state, leave that alone for the non-rewind case so that further
 * operations fail.  About all that can be done now is log the error.
*/
			log(LOG_INFO, "tms%d,%d flsh\n", TMSCTLR(dev), unit);
			tms->Tflags &= ~_CACHE_WRITTEN;
			}
		}
	if	(tms->tms_flags & MTF_WRITTEN)
		tms_wrteof(dev, tms);
	tms->Tflags &= ~(_BUFMARK | _INUSE);
	if	((dev & T_NOREWIND) == 0)
		{
		tmscpcommand(dev, TMS_REW, 0);
		tms->Tflags &= ~(_LOST | _CLSEREX | _SEREX);
		}
	return(0);
	}

/*
 * Execute a command on the tape drive a specified number of times.
 * This routine sets up a buffer and calls the strategy routine which
 * links the buffer onto the drive's buffer queue.
 * The start routine will take care of creating a tmscp command packet
 * with the command.  The start routine is called by the strategy or the
 * interrupt routine.
 */

tmscpcommand(dev, com, count)
	register dev_t dev;
	int com, count;
{
	register struct buf *bp;
	register int s;
	int unit = TMSUNIT(dev);

	bp = &tmscp_softc[TMSCTLR(dev)].sc_cmdbuf;

	s = spl5();
	while	(bp->b_flags&B_BUSY)
		{
		bp->b_flags |= B_WANTED;
		sleep((caddr_t)bp, PRIBIO);
		}
	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 = com;
	bp->b_blkno = 0;
/*
 * Start the timer before entering the strategy routine.  If it declares
 * an immediate error it will also perform an iodone which will cause us
 * to fall thru and cancel the timer.
*/
	timeout(tmswatchdog, bp, 240 * hz);
	tmscpstrategy(bp);
	iowait(bp);
	untimeout(tmswatchdog, bp);
	if	(bp->b_flags & B_WANTED)	/* Anyone waiting above? */
		wakeup(bp);
	bp->b_flags &= B_ERROR;		/* Clears B_BUSY */
}

/*
 * If this routine is called then  (after 4 minutes) something is hung.
 * Set the I/O done flag, set error to be ETIMEDOUT, and issue the wakeup.
*/
tmswatchdog(bp)
	struct	buf *bp;
	{

	bp->b_error = ETIMEDOUT;
	biodone(bp);
	}

/*
 * Init mscp communications area
 */
tmsginit(sc, com, msgs, offset, length, flags)
	register struct tmscp_softc	*sc;
	register	Trl		*com;
	register struct mscp		*msgs;
	int				offset;
	int				length;
	int				flags;
{
	long				vaddr;

	/* 
	 * Figure out virtual address of message 
	 * skip comm area and mscp messages header and previous messages
	 *
	 * N.B. Assumes SEG5 has been remapped to the comm area for this
	 * controller.
	 */
	vaddr = _iomap(tmscp[sc->sc_unit]);
	vaddr += sizeof(struct tmscpca)			/* skip comm area */
		+sizeof(struct mscp_header);		/* m_cmdref disp */
	vaddr += offset * sizeof(struct mscp);		/* skip previous */
	while (length--) {
		com->lsh = loint(vaddr);
		com->hsh = flags | hiint(vaddr);
		msgs->mscp_dscptr = (long *)com;
		msgs->mscp_header.mscp_msglen = sizeof(struct mscp);
		msgs->mscp_header.mscp_vcid = 1; /* tape VCID = 1 */
		++com; ++msgs; vaddr += sizeof(struct mscp);
	}
}

/*
 * Find an unused command packet
 */
struct mscp *
tmscpgetcp(sc)
	register struct tmscp_softc *sc;
{
	register struct mscp *mp = NULL;
	register struct tmscpca *cp;
	int	i;
	int	s;
	segm seg5;

	s = spl5();
	saveseg5(seg5);
	mapseg5(tmscp[sc->sc_unit], MAPBUFDESC);
	cp = &sc->sc_com->tmscp_ca;
	/*
	 * If no credits, can't issue any commands
	 * until some outstanding commands complete.
	 */
	i = sc->sc_lastcmd;
	if	(((cp->ca_cmddsc[i].hsh&(TMSCP_OWN|TMSCP_INT))==TMSCP_INT) &&
		  (sc->sc_credits >= 2))
		{
		sc->sc_credits--;       /* This commits to issuing a command */
		cp->ca_cmddsc[i].hsh &= ~TMSCP_INT;
		mp = &sc->sc_com->tmscp_cmd[i];
		mp->mscp_cmdref = 0;
		mp->mscp_modifier = 0;
		mp->mscp_flags = 0;
		bzero(&mp->un, sizeof (mp->un));
		sc->sc_lastcmd = (i + 1) % NCMD;
		}
	restorseg5(seg5);
	(void) splx(s);
	return(mp);
}

/*
 * Initialize a TMSCP device.  Set up UBA mapping registers,
 * initialize data structures, and start hardware
 * initialization sequence.
 */
tkini(sc)
	register struct tmscp_softc *sc;
{
	register struct tmscpdevice *tmscpaddr;
	long adr;

	sc->sc_ctab.b_active++;
	adr = _iomap(tmscp[sc->sc_unit]) + (u_int)RINGBASE;
	sc->sc_ctab.b_un.b_addr = (caddr_t)loint(adr);
	sc->sc_ctab.b_xmem = hiint(adr);
	tmscpaddr = sc->sc_addr;

	/*
	 * Start the hardware initialization sequence.
	 */
	tmscpaddr->tmscpip = 0;              /* start initialization */

	while((tmscpaddr->tmscpsa & TMSCP_STEP1) == 0)
		{
		if	(tmscpaddr->tmscpsa & TMSCP_ERR)
			return(0);	/* CHECK */
		}
	tmscpaddr->tmscpsa=TMSCP_ERR|(NCMDL2<<11)|(NRSPL2<<8)|TMSCP_IE|(sc->sc_ivec/4);
	/*
	 * Initialization continues in the interrupt routine.
	 */
	sc->sc_state = S_STEP1;
	sc->sc_credits = 0;
	return(1);
}

/*
 * Start I/O operation
 * This code is convoluted.  The majority of it was copied from the uda driver.
 */

tmsstart(sc)
	register struct tmscp_softc *sc;
{
	register struct mscp *mp;
	register struct buf *bp, *dp;
	register struct tms_info *tms;
	struct   tmscpdevice *tmscpaddr;
	int i, unit;
	segm seg5;

     saveseg5(seg5);		/* save just once at top */
     for(;;)
	{
	if	((dp = sc->sc_ctab.b_actf) == NULL)
		{
		/* (drive was inactive) */
		sc->sc_ctab.b_active = 0;
		break;
		}
	if	((bp = dp->b_actf) == NULL)
		{
		/*
		 * No more requests for this drive, remove
		 * from controller queue and look at next drive.
		 * We know we're at the head of the controller queue.
		 */
		dp->b_active = 0;
		sc->sc_ctab.b_actf = dp->b_forw;
		continue;
		}
	sc->sc_ctab.b_active++;
	unit = TMSUNIT(bp->b_dev);
	tmscpaddr = (struct tmscpdevice *)sc->sc_addr;
	tms = sc->sc_drives[unit];
	if	((tmscpaddr->tmscpsa&TMSCP_ERR) || sc->sc_state != S_RUN)
		{
		log(LOG_INFO, "tms%d,%d: sa %x st %d\n", sc->sc_unit,
			unit, tmscpaddr->tmscpsa, sc->sc_state);
		(void)tkini(sc);
		/* SHOULD REQUEUE OUTSTANDING REQUESTS, LIKE TMSCPRESET */
		break;
		}

	if	(!(tms->Tflags & _ONLINE))
		{
		if	((mp = tmscpgetcp(sc)) == NULL)
			break;
		mapseg5(tmscp[sc->sc_unit], MAPBUFDESC);
		mp->mscp_opcode = M_OP_ONLIN;
		mp->mscp_unit = unit;
		tms_clrerr(tms, mp);
		dp->b_active = 2;
		sc->sc_ctab.b_actf = dp->b_forw; /* remove from controller q */
		((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN|TMSCP_INT);
		if	(tmscpaddr->tmscpsa&TMSCP_ERR)
			log(LOG_INFO, tmscpfatalerr, sc->sc_unit,
					TMSUNIT(bp->b_dev), tmscpaddr->tmscpsa);
		restorseg5(seg5);
		i = tmscpaddr->tmscpip;
		continue;
		}
	if	((mp = tmscpgetcp(sc)) == NULL)
		break;
	mapseg5(tmscp[sc->sc_unit], MAPBUFDESC);
	mp->mscp_cmdref = (u_short)bp;		/* pointer to get back */
	mp->mscp_unit = unit;
	/*
	 * If its an ioctl-type command then set up the appropriate
	 * tmscp command;  by doing a switch on the "b_resid" field where
	 * the command mneumonic is stored.
	 */
	if	(bp == &sc->sc_cmdbuf)
		{
		/*
		 * The reccnt and tmkcnt fields are set to zero by the getcp
		 * routine (as bytecnt and buffer fields).  Thus reccnt and
		 * tmkcnt are only modified here if they need to be set to
		 * a non-zero value.
		 */
		switch ((int)bp->b_resid) {

		case TMS_WRITM:
			tms_wtm_st(mp, sc, 0);
			break;
		case TMS_FSF:
			tms_repos_st(mp, sc, 0);
			break;
		case TMS_BSF:
			tms_repos_st(mp, sc, M_MD_REVRS);
			break;
		case TMS_FSR:
			tms_reposrec_st(mp, sc, M_MD_OBJCT);
			break;
		case TMS_BSR:
			tms_reposrec_st(mp, sc, M_MD_OBJCT | M_MD_REVRS);
			break;
		case TMS_REW:
			if	(bp->b_bcount)
				i = M_MD_REWND;
			else
				i = M_MD_REWND | M_MD_IMMED;
			bp->b_bcount = 0;	/* XXX */
			tms_repos_st(mp, sc, i);
			break;
		case TMS_OFFL:
			tms_avail_st(mp, sc, M_MD_UNLOD);
			break;
		case TMS_SENSE:
			mp->mscp_opcode = M_OP_GTUNT;
			break;
		case TMS_FLUSH:
			mp->mscp_opcode = M_OP_FLUSH;
			break;
		case TMS_CACHE:
			tms->tms_unitflgs |= M_UF_WBKNV;
			tms->tms_unitflgs &= ~M_UF_SCCHH;
			tms->Tflags |= _CACHE_ON;
			tms_stunt_st(mp, sc, 0);
			break;
		case TMS_NOCACHE:
			tms->tms_unitflgs &= ~M_UF_WBKNV;
			tms->tms_unitflgs |= M_UF_SCCHH;
			tms->Tflags &= ~_CACHE_ON;
			tms_stunt_st(mp, sc, 0);
			break;
		case TMS_CSE:
			bp->b_bcount = 0;	/* 0 objects to skip */
			tms_repos_st(mp, sc, M_MD_OBJCT);
			break;
		case TMS_SETDENSITY:
			tms->tms_format &= ~M_TF_MASK;
			tms->tms_format |= Dmatrix[TMSDENS(bp->b_dev)][tms->tms_fmtmenu & FMTMASK];
			tms_stunt_st(mp, sc, 0);
			break;
		default:
			log(LOG_INFO, "ioctl %x\n", bp->b_resid);
			/* Need a no-op. Reposition no amount */
			mp->mscp_opcode = M_OP_REPOS;
			break;
		}   /* end switch (bp->b_resid) */
		}
	else    /* Its a read/write command (not an ioctl) */
		{
		mp->mscp_opcode = bp->b_flags&B_READ ? M_OP_READ : M_OP_WRITE;
		mp->mscp_bytecnt = bp->b_bcount;
		mp->mscp_buffer_l = (u_short) bp->b_un.b_addr;
		mp->mscp_buffer_h = bp->b_xmem;
		}

	if	((tms->Tflags & _CACHE_ON) && (mp->mscp_opcode == M_OP_WRITE))
		tms->Tflags |= _CACHE_WRITTEN;
	if	((tms->Tflags & _BUFMARK) && (mp->mscp_opcode == M_OP_READ) &&
		 (tms->Tflags & _CLSEREX))
		{
		tms->Tflags &= ~(_BUFMARK | _CLSEREX);
		mp->mscp_modifier |= M_MD_CLSEX;
		}

	if	(tmscpprintf & 0x8)
		{
		log(LOG_INFO, "tms%d,%d -> op %x fl %x mod %x\n",
			sc->sc_unit, mp->mscp_unit,
			mp->mscp_opcode, mp->mscp_flags, mp->mscp_modifier);
		}

	((Trl *)mp->mscp_dscptr)->hsh |= (TMSCP_OWN|TMSCP_INT);
	i = tmscpaddr->tmscpip;              /* initiate polling */
	dp->b_qsize++;
	/*
	 * Move drive to the end of the controller queue
	 */
	if (dp->b_forw != NULL)
		{
		sc->sc_ctab.b_actf = dp->b_forw;
		sc->sc_ctab.b_actl->b_forw = dp;
		sc->sc_ctab.b_actl = dp;
		dp->b_forw = NULL;
		}
	/*
	 * Move buffer to I/O wait queue
	 */
	dp->b_actf = bp->av_forw;
	dp = &sc->sc_wtab;
	bp->av_forw = dp;
	bp->av_back = dp->av_back;
	dp->av_back->av_forw = bp;
	dp->av_back = bp;
	if (tmscpaddr->tmscpsa&TMSCP_ERR)
		{
		log(LOG_INFO, tmscpfatalerr,sc->sc_unit, 
			mp->mscp_unit, tmscpaddr->tmscpsa);
		(void)tkini(sc);
		break;
		}
    }   /* end for */
    /*
     * Check for response ring transitions lost in the
     * Race condition.  Map SEG5 in case we escaped early from the for().
     */
    mapseg5(tmscp[sc->sc_unit], MAPBUFDESC);
    for (i = sc->sc_lastrsp;; i++)
	    {
	    i %= NRSP;
	    if (sc->sc_com->tmscp_ca.ca_rspdsc[i].hsh&TMSCP_OWN)
		    break;
	    tmscprsp(sc, i);
	    sc->sc_com->tmscp_ca.ca_rspdsc[i].hsh |= TMSCP_OWN;
	    }
    sc->sc_lastrsp = i;
    restorseg5(seg5);
}


/*
 * Process a response packet.  N.B.  Assumes SEG5 maps comm area for controller
 */
tmscprsp(sc, i)
	register struct tmscp_softc *sc;
	int i;
{
	register struct mscp *mp;
	register struct tms_info *tms;
	struct buf *dp, *bp;
	int	em_status, em_endcode;

	mp = &sc->sc_com->tmscp_rsp[i];
	mp->mscp_header.mscp_msglen = sizeof (struct mscp);
	sc->sc_credits += mp->mscp_header.mscp_credits & 0xf;  /* low 4 bits */
	if	((mp->mscp_header.mscp_credits & 0xf0) > 0x10)	/* Check */
		return;

	/*
	 * If it's an error log message (datagram),
	 * pass it on for more extensive processing.
	 */
	if	((mp->mscp_header.mscp_credits & 0xf0) == 0x10)
		{
		tmserror(sc->sc_unit, (struct mslg *)mp);
		return;
		}

	if	(tmscpprintf & 0x4)
		log(LOG_INFO, "tms%d,%d: op %x st %x\n", sc->sc_unit,
			mp->mscp_unit,mp->mscp_opcode,
			mp->mscp_status & M_ST_MASK);

	em_status = mp->mscp_status&M_ST_MASK;
	em_endcode = mp->mscp_endcode & ~M_OP_END;
	/*
	 * The controller interrupts as any drive.
	 * This means that you must check for controller interrupts
	 * before drive responses.
	 */
	if	(em_endcode == M_OP_STCON)
		{
		if	(em_status == M_ST_SUCC)
			sc->sc_state = S_RUN;
		else
			sc->sc_state = S_IDLE;
		sc->sc_ctab.b_active = 0;
		wakeup((caddr_t)&sc->sc_ctab);
		return;
		}
	if	(mp->mscp_unit >= 4)
		return;
	tms = sc->sc_drives[mp->mscp_unit];
	if	(!tms)		/* unopened unit coming online - ignore it */
		return;
	dp = &tms->tms_dtab;

	switch (em_endcode) {
	case	M_OP_ONLIN:
		if	(em_status == M_ST_SUCC || em_status == M_ST_WRTPR)
			{
			tms->Tflags |= _ONLINE;
			tms->Tflags &= ~_CACHE_WRITTEN;
/*
 * Normally this is done in the 'tms_check_ret' routine.  The ONLIN case is
 * special (well, ok - weird) in that it was never put on the I/O wait queue.
 * Thus we need to do this here.
*/
			tms->tms_endcode = em_endcode;
			tms->tms_status = em_status;
			tms->tms_type = mp->mscp_mediaid;
			tms->tms_unitflgs = mp->mscp_unitflgs;
			tms->tms_format = mp->mscp_format;
			/*
			 * Link the drive onto the controller queue
			 */
			dp->b_forw = NULL;
			if	(sc->sc_ctab.b_actf == NULL)
				sc->sc_ctab.b_actf = dp;
			else
				sc->sc_ctab.b_actl->b_forw = dp;
			sc->sc_ctab.b_actl = dp;
			dp->b_active = 1;
			}
		else 
			{
			while	(bp = dp->b_actf)
				{
				dp->b_actf = bp->av_forw;
				bp->b_flags |= B_ERROR;
				iodone(bp);
				}
			}
		if	(mp->mscp_cmdref)
			wakeup((caddr_t)mp->mscp_cmdref);
		return;
	/*
	 * The AVAILABLE ATTENTION message occurs when the
	 * unit becomes available after loading.
	 * Marking the unit offline will force an
	 * online command prior to using the unit.
	 */
	case	M_OP_AVATN:
		tms->Tflags &= ~_ONLINE;
		return;
/*
 * An endcode with no opcode (0x80) is an invalid command.  This is supposed
 * to indicate a protocol error (illegal opcode, parameter error, etc) but
 * without the real opcode we don't know which command (reposition, write,
 * read, ...) failed.  So, just declare I/O done and hope for the best.
*/
	case	0:
		if	(tmscpprintf & 0x8)
			log(LOG_INFO, "tms%d,%d: inv end=%x st=%x\n",
				sc->sc_unit,mp->mscp_unit,em_endcode,em_status);
		tms_iodone(mp, sc);
		return;
	case	M_OP_WRITE:
	case	M_OP_READ:
		tms_rw_em(mp, sc);
		return;
	case	M_OP_WRITM:
		tms_wtm_em(mp, sc);
		return;
	case	M_OP_REPOS:
		tms_repos_em(mp, sc);
		return;
	case	M_OP_STUNT:
		tms_stunt_em(mp, sc);
		return;
	case	M_OP_AVAIL:
		tms_avail_em(mp, sc);
		return;
	case	M_OP_GTUNT:
		tms_gtunt_em(mp, sc);
		return;
	case	M_OP_FLUSH:
		tms_flush_em(mp, sc);
		return;
	default:
		if	(tmscpprintf & 0x8)
			log(LOG_INFO, "tms%d,%d rsp %x\n", sc->sc_unit,
				mp->mscp_unit, em_endcode);
		return;
	}	/* end switch mp->mscp_opcode */
}

/*
 * Manage buffers and perform block mode read and write operations.
 */

tmscpstrategy (bp)
	register struct buf *bp;
	{
	register struct buf *dp;
	register struct tmscp_softc *sc;
	struct	tms_info *tms;
	int ctlr = TMSCTLR(bp->b_dev);
	int s;

	sc = &tmscp_softc[ctlr];
	tms = sc->sc_drives[TMSUNIT(bp->b_dev)];
	if	(!tms || !(tms->Tflags & _ONLINE))
		{
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
		}
/*
 * If we're at the end of the tape and this is not an 'ioctl' command
 * then return an error rather than reading or writing off the end of the tape.
 * Ioctl commands are allowed to proceed so that tapemarks can be written and
 * repositioning (rewind, etc) can be done.
*/
	if	((tms->tms_flags & MTF_EOM) && bp != &sc->sc_cmdbuf)
		{
		bp->b_resid = bp->b_bcount;
		bp->b_error = ENOSPC;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
		}
	mapalloc(bp);
	s = spl5();
	/*
	 * Link the buffer onto the drive queue
	 */
	dp = &tms->tms_dtab;
	if (dp->b_actf == 0)
		dp->b_actf = bp;
	else
		dp->b_actl->av_forw = bp;
	dp->b_actl = bp;
	bp->av_forw = 0;
	/*
	 * Link the drive onto the controller queue
	 */
	if (dp->b_active == 0)
		{
		dp->b_forw = NULL;
		if (sc->sc_ctab.b_actf == NULL)
			sc->sc_ctab.b_actf = dp;
		else
			sc->sc_ctab.b_actl->b_forw = dp;
		sc->sc_ctab.b_actl = dp;
		dp->b_active = 1;
		}
	/*
	 * If the controller is not active, start it.
	 */
	if	(sc->sc_ctab.b_active == 0)
		(void) tmsstart(sc);
	splx(s);
	return;
	}

/* ARGSUSED */
tmscpioctl(dev, cmd, data, flag)
	dev_t dev;
	int cmd;
	caddr_t data;
	int flag;
{
	struct tmscp_softc *sc = &tmscp_softc[TMSCTLR(dev)];
	struct buf *bp = &sc->sc_cmdbuf;
	register struct tms_info *tms = sc->sc_drives[TMSUNIT(dev)];
	int fcount;		/* number of files (or records) to space */
	register struct mtop *mtop;	/* mag tape cmd op to perform */
	register struct mtget *mtget;	/* mag tape struct to get info in */

	/* we depend of the values and order of the TMS ioctl codes here */
	static char tmsops[] =
	 {TMS_WRITM,TMS_FSF,TMS_BSF,TMS_FSR,TMS_BSR,TMS_REW,TMS_OFFL,TMS_SENSE,
	  TMS_CACHE,TMS_NOCACHE,TMS_FLUSH};

	if	(!tms)
		return(ENXIO);
	if	(cmd != MTIOCGET)
		tms->tms_status = ~M_ST_SUCC;
	if	(tms->tms_position == 0)
		tms->tms_flags |= MTF_BOM;
	else
		tms->tms_flags &= ~MTF_BOM;

	switch (cmd) {
	case	MTIOCTOP:
		mtop = (struct mtop *)data;
		switch (mtop->mt_op) {

		case	MTWEOF:
			return(tms_wrteof(dev, tms));
		case	MTFSF:
		case	MTBSF:
		case	MTFSR:
		case	MTBSR:
			fcount = mtop->mt_count;
			break;
		case	MTFLUSH:
		case	MTCACHE:
		case	MTNOCACHE:
			if	((tms->Tflags & (_HASCACHE|_ONLINE)) !=
				 (_HASCACHE|_ONLINE))
				return(0);
		case	MTREW:
		case	MTOFFL:
		case	MTNOP:
			fcount = 1;
			break;
		default:
			return(ENXIO);
		}	/* end switch mtop->mt_op */

		if	(fcount <= 0)
			return(EINVAL);
		tmscpcommand(dev, tmsops[mtop->mt_op], fcount);
		if	(tms->tms_status != M_ST_SUCC)
			{
			if	(tms->Tflags & _CLSEREX)
				tmscpcommand(dev, TMS_CSE, 1);
			return(EIO);
			}
		return(geterror(bp));

	case	MTIOCGET:
		/*
		 * Return status info associated with the particular UNIT.
		 */
		mtget = (struct mtget *)data;
		mtget->mt_type = MT_ISTMSCP;
		mtget->mt_dsreg = tms->tms_flags << 8;
		mtget->mt_dsreg |= tms->tms_endcode;
		mtget->mt_erreg = tms->tms_status;
		mtget->mt_resid = tms->tms_resid;
		break;
	default:
		return(ENXIO);
	}
	return (0);
}

tms_wrteof(dev, tms)
	dev_t	dev;
	register struct tms_info *tms;
	{

	tmscpcommand(dev, TMS_WRITM, 1);
	if	(tms->tms_status != M_ST_SUCC)
		return(EIO);
	tms->tms_status = ~M_ST_SUCC;
	tmscpcommand(dev, TMS_WRITM, 1);
	if	(tms->tms_status != M_ST_SUCC)
		return(EIO);
	tms->Tflags &= ~_CACHE_WRITTEN;
	tms->tms_status = ~M_ST_SUCC;
	tmscpcommand(dev, TMS_BSR, 1);
	if	(tms->tms_status != M_ST_SUCC)
		return(EIO);
	return(0);
	}

/*
 * Initialize the mscp packet for a STUNT command.  If the drive is in a
 * 'cache lost' but 'written' state then clear it and log the error.
*/

tms_stunt_st(mp, sc, flgs)
	register struct	mscp	*mp;
	register struct	tmscp_softc *sc;
	int	flgs;
	{
	register struct	tms_info *tms = sc->sc_drives[mp->mscp_unit];

	mp->mscp_opcode = M_OP_STUNT;
	mp->mscp_modifier = flgs | M_MD_EXCAC;
	tms_cache_cmn(sc, tms, mp);

/*
 * Set the unit flags - this includes the cache enable/disable flags.
*/

	mp->mscp_unitflgs = tms->tms_unitflgs;

/*
 * Only initialize the density if the position is not at BOT.  On some
 * drives (notably the TU81) selecting a density other than the 'current'
 * (0) causes an illegal command error.
*/
	if	(tms->tms_position != 0)
		mp->mscp_format = 0;
	else
		mp->mscp_format = tms->tms_format;
	}

/*
 * Initialize the mscp packet for a REPOS command.  The 'flgs' parameter
 * specifies if the movement if forwards or backwards and if records or
 * tape marks (objects) are to be skipped.
*/

tms_repos_st(mp, sc, flgs)
	register struct	mscp	*mp;
	register struct	tmscp_softc *sc;
	int	flgs;
	{
	struct	buf	*bp = (struct buf *)mp->mscp_cmdref;
	register struct	tms_info *tms = sc->sc_drives[mp->mscp_unit];

	mp->mscp_opcode = M_OP_REPOS;
	mp->mscp_modifier = flgs | M_MD_CLSEX;
	tms->Tflags &= ~_CLSEREX;
	tms_cache_cmn(sc, tms, mp);	/* shared code with tms_reposrec_st */
	if	(mp->mscp_modifier & M_MD_OBJCT)
		mp->mscp_reccnt = bp->b_bcount;
	else
		mp->mscp_tmkcnt = bp->b_bcount;
	}

/*
 * Initialize the mscp packet for a REPOS 'record' command.  Tape records do
 * not include tapemarks (which is why this is a separate routine from
 * tms_repos_st above).
*/

tms_reposrec_st(mp, sc, flgs)
	register struct	mscp	*mp;
	register struct	tmscp_softc *sc;
	int	flgs;
	{
	struct	buf	*bp = (struct buf *)mp->mscp_cmdref;
	register struct	tms_info *tms = sc->sc_drives[mp->mscp_unit];

	mp->mscp_opcode = M_OP_REPOS;
	mp->mscp_modifier = flgs;
	tms_cache_cmn(sc, tms, mp);
	mp->mscp_reccnt = bp->b_bcount;
	}

/*
 * Initialize the mscp packet for an AVAIL command.  The only time this is
 * done is on a 'rewoffl' command.
*/

tms_avail_st(mp, sc, flgs)
	register struct	mscp	*mp;
	struct	tmscp_softc *sc;
	int	flgs;
	{
	register struct	tms_info *tms = sc->sc_drives[mp->mscp_unit];

	mp->mscp_opcode = M_OP_AVAIL;
	mp->mscp_modifier = flgs;
	tms_clrerr(tms, mp);
	}

tms_clrerr(tms, mp)
	register struct tms_info *tms;
	register struct mscp *mp;
	{

	mp->mscp_modifier |= M_MD_CLSEX;
	tms->Tflags &= ~_CLSEREX;
	if	(tms->Tflags & _CACHE_LOST)
		{
		tms->Tflags &= ~(_CACHE_LOST | _CACHE_WRITTEN);
		mp->mscp_modifier |= M_MD_CDATL;
		}
	}

/*
 * Initialize the mscp packet for a WTM (write tapemark) command.
*/

tms_wtm_st(mp, sc, flgs)
	register struct	mscp	*mp;
	register struct	tmscp_softc *sc;
	int	flgs;
	{
	register struct	tms_info *tms = sc->sc_drives[mp->mscp_unit];

	mp->mscp_opcode = M_OP_WRITM;
	mp->mscp_modifier = flgs | M_MD_CLSEX;
	tms->Tflags &= ~_CLSEREX;
	tms_cache_cmn(sc, tms, mp);
	}

tms_cache_cmn(sc, tms, mp)
	register struct	tmscp_softc *sc;
	register struct	tms_info *tms;
	register struct	mscp *mp;
	{

	if	(tms->Tflags & _CACHE_LOST)
		{
		if	(!(tms->Tflags & _CACHE_WRITTEN))
			{
			tms->Tflags &= ~(_CACHE_LOST | _CACHE_WRITTEN);
			mp->mscp_modifier |= M_MD_CDATL;
			}
		else
			log(LOG_INFO, "tms%d,%d clost\n",
				sc->sc_unit, mp->mscp_unit);
		}
	}

/*
 * Process the end message from a REPOS command.  Called from tmscprsp().
*/

tms_repos_em(mp, sc)
	register struct mscp *mp;
	register struct tmscp_softc *sc;
	{
	register struct tms_info *tms = sc->sc_drives[mp->mscp_unit];
	u_short	em_status = mp->mscp_status & M_ST_MASK;

	(void)tms_check_ret(mp, sc);
	if	(em_status == M_ST_SUCC)
		{
		if	(tms->tms_position != mp->mscp_position)
			tms->tms_flags &= ~MTF_WRITTEN;
		if	(tms->tms_position = mp->mscp_position)
			tms->tms_flags &= ~MTF_BOM;
		else
			{
			tms->tms_flags |= MTF_BOM;
			tms->Tflags &= ~_LOST;
			}
		tms->Tflags &= ~_CACHE_WRITTEN;
		}
	tms->tms_resid = 0;
	tms_iodone(mp, sc);
	}

/*
 * Process the end message from a STUNT command.
*/

tms_stunt_em(mp, sc)
	register struct mscp *mp;
	register struct tmscp_softc *sc;
	{
	register struct tms_info *tms = sc->sc_drives[mp->mscp_unit];
	u_short	em_status = mp->mscp_status & M_ST_MASK;

	(void)tms_check_ret(mp, sc);
	tms->Tflags &= ~_CACHE_WRITTEN;
	if	(em_status == M_ST_SUCC)
		{
		tms->tms_unitflgs = mp->mscp_unitflgs;
		tms->tms_format = mp->mscp_format;
		tms->tms_type = mp->mscp_mediaid;
		tms->Tflags |= _ONLINE;
		}
	tms->tms_resid = 0;
	tms_iodone(mp, sc);
	}

/*
 * Process the end message from a WRITM (Write Tape Mark) command.
*/

tms_wtm_em(mp, sc)
	register struct mscp *mp;
	register struct tmscp_softc *sc;
	{
	register struct tms_info *tms = sc->sc_drives[mp->mscp_unit];
	u_short	em_status = mp->mscp_status & M_ST_MASK;

	if	(em_status == M_ST_SUCC)
		{
		tms->tms_flags &= ~MTF_WRITTEN;
		tms->tms_position = mp->mscp_position;
		}
	(void)tms_check_ret(mp, sc);
	tms->tms_resid = 0;
	tms_iodone(mp, sc);
	}

tms_avail_em(mp, sc)
	register struct mscp *mp;
	register struct tmscp_softc *sc;
	{
	register struct tms_info *tms = sc->sc_drives[mp->mscp_unit];

	(void)tms_check_ret(mp, sc);
	tms->Tflags &= ~(_INUSE | _ONLINE);
	tms->tms_position = 0;
	tms->tms_flags |= MTF_BOM;
	if	(tms->tms_status == M_ST_SUCC)
		tms->Tflags &= ~_CACHE_WRITTEN;
	tms->tms_resid = 0;
	tms_iodone(mp, sc);
	}

tms_flush_em(mp, sc)
	register struct mscp *mp;
	register struct tmscp_softc *sc;
	{
	register struct tms_info *tms = sc->sc_drives[mp->mscp_unit];
	u_short	em_status = mp->mscp_status & M_ST_MASK;

	if	(em_status == M_ST_SUCC)
		tms->Tflags &= ~_CACHE_WRITTEN;
	else
		log(LOG_INFO, "tms%d,%d Flush\n",sc->sc_unit,mp->mscp_unit);
	tms->tms_position = mp->mscp_position;
	(void)tms_check_ret(mp, sc);
	tms->tms_resid = 0;
	tms_iodone(mp, sc);
	}

tms_gtunt_em(mp, sc)
	register struct mscp *mp;
	register struct tmscp_softc *sc;
	{
	register struct tms_info *tms = sc->sc_drives[mp->mscp_unit];
	u_short	em_status = mp->mscp_status & M_ST_MASK;
	u_short	em_flags = mp->mscp_unitflgs;

	(void)tms_check_ret(mp, sc);
	if	(em_status == M_ST_SUCC)
		{
		tms->tms_format = mp->mscp_format;
		tms->tms_type = mp->mscp_mediaid;
		tms->tms_fmtmenu = mp->mscp_fmtmenu;
		tms->tms_unitflgs = mp->mscp_unitflgs;
		tms->Tflags |= _ONLINE;
		if	(em_flags & (M_UF_WRTPH | M_UF_WRTPS | M_UF_WRTPD))
			tms->tms_flags |= MTF_WRTLCK;
		else
			tms->tms_flags &= ~MTF_WRTLCK;
		if	(em_flags & M_UF_CACH)
			{
			tms->Tflags |= _HASCACHE;
			if	(tms->Tflags & _CACHE_ON)
				{
				tms->tms_unitflgs |= M_UF_WBKNV;
				tms->tms_unitflgs &= ~M_UF_SCCHH;
				}
			else
				{
				tms->tms_unitflgs &= ~M_UF_WBKNV;
				tms->tms_unitflgs |= M_UF_SCCHH;
				}
			}
		if	(tms->tms_position == 0)
			tms->tms_flags |= MTF_BOM;
		else
			tms->tms_flags &= ~MTF_BOM;
		}
	tms->tms_resid = 0;
	tms_iodone(mp, sc);
	}

tms_rw_em(mp, sc)
	register struct mscp *mp;
	register struct tmscp_softc *sc;
	{
	struct	buf	*bp = (struct buf *)mp->mscp_cmdref;
	register struct tms_info *tms = sc->sc_drives[mp->mscp_unit];
	u_short	em_status = mp->mscp_status & M_ST_MASK;

	tms->tms_flags &= ~MTF_WRITTEN;
	(void)tms_check_ret(mp, sc);
	if	(em_status == M_ST_SUCC)
		{
		if	((tms->tms_endcode & ~M_OP_END) == M_OP_WRITE)
			tms->tms_flags |= MTF_WRITTEN;
		else
			tms->Tflags &= ~_CACHE_WRITTEN;
		}
	tms->tms_resid = bp->b_bcount - mp->mscp_bytecnt;
	if	(tms->tms_position = mp->mscp_position)
		tms->tms_flags &= ~MTF_BOM;
	else
		tms->tms_flags |= MTF_BOM;
	tms_iodone(mp, sc);
	}

tms_iodone(mp, sc)
	struct	mscp	*mp;
	struct	tmscp_softc *sc;
	{
	struct	tms_info *tms = sc->sc_drives[mp->mscp_unit];
	register struct	buf	*bp = (struct buf *)mp->mscp_cmdref;
	register struct	buf	*dp = &tms->tms_dtab;

/*
 * Remove the buffer from the I/O wait queue.  Set the residual count and
 * declare the I/O done.
*/
	bp->av_back->av_forw = bp->av_forw;
	bp->av_forw->av_back = bp->av_back;
	dp->b_qsize--;
	bp->b_resid = tms->tms_resid;
	iodone(bp);
	}

/*
 * Check the return status of an mscp packet and adjust the drive's flags
 * appropriately.  On error conditions set the error flag and code in the 
 * buffer header.
 *
 * Many of the 'case' statements below are finer grained than strictly
 * necessary - this is done to provide easy access for logging of specific 
 * conditions during debugging.
*/

tms_check_ret(mp, sc)
	struct	mscp	*mp;
	struct	tmscp_softc *sc;
	{
	register struct	tms_info *tms = sc->sc_drives[mp->mscp_unit];
	register struct	buf	*bp = (struct buf *)mp->mscp_cmdref;
	u_short	em_status = mp->mscp_status & M_ST_MASK;
	u_short	em_subcode = mp->mscp_status >> M_ST_SBBIT;
	u_short	em_flags = mp->mscp_flags;
	u_short	em_endcode = mp->mscp_endcode & ~M_OP_END;
	char	berr, unkerr;

	if	(em_status != M_ST_SUCC && em_status != M_ST_TAPEM &&
			(tmscpprintf & 0x1))
		log(LOG_INFO, "tms%d,%d st=%x sb=%x fl=%x en=%x\n",
			sc->sc_unit, mp->mscp_unit,
			em_status, em_subcode, em_flags, em_endcode);
	tms->tms_endcode = mp->mscp_endcode;
	if	(em_flags & M_EF_EOT)
		tms->tms_flags |= MTF_EOM;
	else
		tms->tms_flags &= ~MTF_EOM;
	if	(em_flags & M_EF_DLS)
		tms->Tflags |= _CACHE_LOST;
	if	(em_flags & M_EF_PLS)
		tms->Tflags |= _LOST;
	tms->tms_status = mp->mscp_status;

	berr = 0;
	unkerr = 0;

	switch	(em_status)
		{
		case	M_ST_SUCC:
			{
			switch	(em_subcode)
				{
				case	M_SC_DUPUN:	/* Duplicate unit */
					berr = ENXIO;
					break;
				case	M_SC_ALONL:	/* Already online */
				case	M_SC_STONL:	/* Still online */
				case	M_SC_UNIGN:	/* Unload ignored */
				case	M_SC_ROVOL:	/* Readonly unit */
					tms->Tflags |= _ONLINE;
					break;
				case	M_SC_EOT:	/* End of Tape */
					tms->tms_flags |= MTF_EOM;
					break;
				}
			}
			break;
		case	M_ST_OFFLN:
			tms->Tflags &= ~_ONLINE;
			if	((tms->Tflags & _CACHE_ON) && 
				 (tms->Tflags & _CACHE_WRITTEN))
				log(LOG_INFO, "tms%d,%d closs2\n",
					sc->sc_unit, mp->mscp_unit);
			berr = ENXIO;
			break;
		case	M_ST_WRTPR:			/* Write Protected */
			tms->tms_flags |= MTF_WRTLCK;
			berr = EACCES;			/* Really an error ? */
			break;
		case	M_ST_ICMD:			/* Byte count too big */
		case	M_ST_ABRTD:			/* Command aborted */
		case	M_ST_COMP:			/* Data Compare error */
		case	M_ST_DATA:			/* Data error */
		case	M_ST_HSTBF:			/* Host buffer error */
		case	M_ST_CNTLR:			/* Controller error */
		case	M_ST_FMTER:			/* Formatter error */
			berr = EIO;
			break;
		case	M_ST_AVLBL:			/* Unit available */
			tms->Tflags &= ~(_LOST | _CLSEREX | _SEREX | _ONLINE);
			break;
		case	M_ST_BOT:			/* Beginning of tape */
			tms->tms_flags |= MTF_BOM;
			tms->tms_position = 0;
			break;
		case	M_ST_TAPEM:			/* Tape Mark */
			tms->Tflags |= (_BUFMARK | _CLSEREX);
			break;
		case	M_ST_SEX:			/* Serious Exception */
			if	(em_flags & M_EF_EOT)
				{
				tms->tms_flags |= MTF_EOM;
				berr = ENOSPC;
				}
			else
				{
				tms->Tflags &= ~_CLSEREX;
				log(LOG_INFO, "tms%d,%d serex sb %x\n",
					sc->sc_unit, mp->mscp_unit, em_subcode);
				berr = EIO;
				}
			break;
		case	M_ST_PLOST:			/* Position Lost */
			log(LOG_INFO, "tms%d,%d plost\n", sc->sc_unit,
					mp->mscp_unit);
			tms->Tflags |= (_LOST | _SEREX);
			tms->Tflags &= ~_CLSEREX;
			berr = EIO;
			break;
		case	M_ST_LED:			/* Logical end of dev */
			tms->Tflags |= _CLSEREX;
			break;
		case	M_ST_MFMTE:			/* Media format err */
		case	M_ST_DRIVE:			/* Drive error */
		case	M_ST_RDTRN:			/* Record truncated */
			tms->Tflags |= _SEREX;
			tms->Tflags &= ~_CLSEREX;
		case	M_ST_LOADR:
		case	M_ST_DIAG:			/* Intern diag err */
		case	M_ST_IPARM:			/* Invalid parameter */
			berr = EIO;
			break;
		default:
			unkerr = 1;
			berr = EIO;
			break;
		}
	if	(unkerr)
		log(LOG_INFO, "tms%d,%d: st/sb =%x/%x\n",
			sc->sc_unit, mp->mscp_unit, em_status, em_subcode);
	if	(berr && bp)
		{
		bp->b_flags |= B_ERROR;
		bp->b_error = berr;
		}
	return;
	}

/*
 * Process an error log datagram. 
 *
 * No decoding is done - it wasn't worth the D space.  If bit 1 is set
 * in the print flags then a simple message is generated out of (hopefully) 
 * useful fields.
 *
 * Eventually a error log daemon will be written and the datagram sent to
 * it for capture and detailed analysis.  Until then the logging in 
 * 'tms_check_ret' and a few other strategic spots will have to suffice.
 */

	u_short	tms_datagrams[NTMSCP];

tmserror(ctlr, mp)
	int ctlr;
	register struct mslg *mp;
	{

	tms_datagrams[ctlr]++;
	if	(tmscpprintf & 0x2)
		log(LOG_INFO,
		"tms%d,%d dgram fmt=%x evt=%x grp=%x flg=%x pos=%D\n",
			ctlr, mp->mslg_unit, mp->mslg_format,
			mp->mslg_event, mp->mslg_group, mp->mslg_flags, 
			mp->mslg_position);
	}
#endif NTMSCP
