Coherent4.2.10/conf/haict/src/haict.c

Compare this file to the similar file:
Show the results in this format:

/* (-lgl
 *	Coherent 386 release 4.2
 *	Copyright (c) 1982, 1994 by Mark Williams Company.
 *	All rights reserved. May not be copied without permission.
 *	For copying permission and licensing info, write licensing@mwc.com
 -lgl) */
/***********************************************************************
 * Module: haict.c
 *
 * Unix device driver functions for accessing SCSI tape drives as
 * character devices.	Conforms to Mark Williams Coherent definition
 * of the Unix Device Driver interface for Coherent v4.2.
 *
 * The philosophy of this driver is to support basic functions on
 * the tape drive (read, write, retension, rewind, skip, etc). There
 * are more features out there for all the SCSI tape drives out there
 * than I know what to do with.  I leave custom support for these
 * drives to the people who have them.  To this end this drive will
 * blindly follow whatever information it can get using Mode Sense
 * and Read Block Limits CDB's.  These tests are done at open time.
 * An application can change the operation of the driver by applying
 * the mode select command through I/O Control mechanism.
 *
 * Copyright (c) 1993, Christopher Sean Hilton, All Rights Reserved.
 *
 * Last Modified: Fri Jun 17 12:05:35 1994 by [kroot]
 *
 * $Id: haict.c,v 2.4 93/08/19 04:02:38 nigel Exp Locker: nigel $
 *
 * $Log:		haict.c,v $
 * Revision 2.4  93/08/19  04:02:38  nigel
 * Nigel's R83
 */

#include <errno.h>
#include <stddef.h>
#include <sys/coherent.h>
#include <sys/cmn_err.h>
#include <sys/buf.h>
#include <sys/inode.h>
#include <sys/stat.h>
#include <sys/sched.h>
#include <sys/mtioctl.h>
#include <sys/tape.h>
#include <sys/file.h>

#include <sys/haiscsi.h>
#include <sys/haiioctl.h>

#define LOCAL	static
#if __GNUC__
#define Register
#else
#define Register	register
#endif

#define REWINDTAPE	0x01
#define	  IMMEDIATE	0x0010
#define REQSENSE	0x03
#define READBLKLMT	0x05
#define READ		0x08
#define WRITE		0x0a
#define WRITEFM 	0x10
#define SPACE		0x11
#define   SKIP_BLK	0x00
#define   SKIP_FM 	0x01
#define   SKIP_SEQFM	0x02
#define   SKIP_EOT	0x03
#define MODESELECT	0x15
#define ERASE		0x19
#define   ERASE_BLOCK	0x0000
#define   ERASE_TAPE	0x0001
#define MODESENSE	0x1a
#define LOAD		0x1b
#define   RETENSION	0x0020

#define CTDIRTY 	0x0001
#define CTCLOSING	0x0002

#define CTILI		0x0020		/* Sensekey's Illegal Length Indicator */
#define CTEOM		0x0040		/* Sensekey's End OF Media bit */
#define CTFILEMARK	0x0080		/* Sensekey's Filemark bit */
#define	CTSKMASK	(CTILI | CTEOM | CTFILEMARK)
#define CTRDMD		0x0100		/* we are reading from the tape */
#define CTWRTMD 	0x0200		/* we are writing to the tape */

/*
 * There wasn't much of a difference in speed between 32 and 40 block
 * in my experiance so save as much kalloc memory as possible.
 */

#define STDCACHESZ	(8 * BSIZE)	/* 32 Block Cache for each device */
#define TDCCACHESZ      (10 * BSIZE)    /* Little bigger on the tandberg */

#ifndef HAICTVERBOSE
#define HAICTVERBOSE	0x0001		/* Switch console messages on/off */
#endif

typedef enum {
	CTIDLE = 0,
	CTINIT,
	CTFBRD,
	CTVBRD,
	CTFBWRT,
	CTVBWRT,
	CTLASTWRT,
	CTSENSE,
	CTWRITEFM,
	CTSPACE,
	CTREWIND,
	CTERASE,
	CTLOADRETEN,
	CTIOCTL
} ctstate_t;

/* Block Descriptors in the mode sense command. */

typedef struct blkdscr_s *blkdscr_p;

typedef struct blkdscr_s {
	union {
		unsigned char	mediatype;
		unsigned long	totalblocks;
	} mt;
	union {
		unsigned char reserved;
		unsigned long blocksize;
	} rb;
} blkdscr_t;

typedef struct blklim_s *blklim_p;

typedef struct blklim_s {
	unsigned		blmax;		/* Maximum size for Reads/Writes */
	unsigned short	blmin;		/* Minimum size for Reads/Writes */
} blklim_t;

typedef struct ctctrl_s *ctctrl_p;

typedef struct ctctrl_s {
	char		*cache, 	/* Transfer Cache */
			*start; 	/* Start of data in cache */
	haft_t *	haft;		/* Host adapter functions */
	size_t		cachesize,	/* Size of cache */
			avail;		/* bytes availaible in cache */
	ctstate_t	state;
	unsigned	block,		/* Block size of device */
			blmax;		/* Block limits maximum */
	unsigned short	blmin,		/* Block Limits minimum */
			flags,		/* Flags from device */
			inuse;		/* In Use flag */
	srb_t		srb;		/* SCSI Request block for transfers */
} ctctrl_t;

LOCAL int ctinit	__PROTO((haft_t *, int));
LOCAL void ctopen	__PROTO((dev_t, int));
LOCAL void ctclose	__PROTO((dev_t));
LOCAL void ctread	__PROTO((dev_t, IO *));
LOCAL void ctwrite	__PROTO((dev_t, IO *));
LOCAL void ctioctl	__PROTO((dev_t, int, char *));
LOCAL int fillcache	__PROTO((ctctrl_p));
LOCAL int flushcache	__PROTO((ctctrl_p));

#define min(a, b)			((a) < (b) ? (a) : (b))

static dca_t dca = {
	ctopen, 		/* Open */
	ctclose,		/* Close */
	hainonblk,		/* No Block point here but don't just drop Buffers */
	ctread, 		/* Read */
	ctwrite,		/* Write */
	ctioctl,		/* Ioctl */
	ctinit, 		/* Load */
	NULL,			/* Unload */
	NULL			/* Poll */
};

dca_p		ctdca = &dca;

static ctctrl_p ctdevs[MAXDEVS];

extern int	haict_tdcbug;
extern size_t	haict_cache;

/***********************************************************************
 * Utility functions.		
 */

#define ctvmsg(l, cmd)		{ if (HAICTVERBOSE & (l)) { (cmd); } }
#define ctsleepPri(ctrl, value)	\
	(((ctrl)->flags & CTCLOSING) ? slpriNoSig : (value))
#define tandberg(id)	((bit(id) & haict_tdcbug) != 0)

/***********************************************************************
 * ctbusywait()
 *
 * Wait for the tape drive state to return to idle. This is easy
 * for two reasons: 1) With no block entry point its safe to sleep
 * at any time. 2) We shouldn't really need this anyhow. This is
 * unneccessary because without a block routine and with only one
 * process able to open the tape drive at a time the state of the
 * tape drive driver is well defined. So, why is it here you ask?
 * because one day some user might fork a process that owns the tape
 * drive. This would cause 40 days and nights worth of rain etc.
 * Now all that will happen is both processes will be able to write/read
 * from the tape drive and the data that they get will be complete
 * garbage. However, the kernel will not break.
 */

#if __USE_PROTO__
LOCAL int ctbusywait(Register ctctrl_p c, Register ctstate_t newstate)
#else
LOCAL int
ctbusywait(c, newstate)
Register ctctrl_p	c;
Register ctstate_t	newstate;
#endif
{
	Register int	s;
	int 			retval;

	s = sphi();
	retval = 1;
	while (c->state != CTIDLE)
		if (x_sleep(& c->srb.status, pritape, slpriSigCatch, "ctbsywt")) {
			set_user_error (EINTR);
			retval = 0;
			break;
		}
	c->state = newstate;
	spl(s);
	return retval;
}	/* ctbusywait() */

/***********************************************************************
 * loadtape()
 *
 * Move the tape to the load point.
 */

#if __USE_PROTO__
LOCAL int loadtape(ctctrl_p c, int opt)
#else
LOCAL int
loadtape(c, opt)
ctctrl_p	c;
int		opt;
#endif
{
	Register srb_p		r = & c->srb;
	Register g0cmd_p	g0 = & r->cdb.g0;

	if (!ctbusywait(c, CTLOADRETEN))
		return 0;

	r->buf.space = PHYS_ADDR | WATCH_REQACK;
	r->buf.ba_phys = NULL;
	r->buf.size = 0;
	r->xferdir = 0;
	r->timeout = 300;
	memset(g0, 0, sizeof(cdb_t));
	g0->opcode = LOAD;
	g0->xfr_len = 1;		/* Move tape to load point.*/
	if (opt & IMMEDIATE)
		g0->lun_lba |= 1;
	if (opt & RETENSION)
		g0->xfr_len |= 2;
	doscsi(c->haft, r, 4, pritape, slpriSigCatch, "loadtape");
	if (r->status != ST_GOOD && printerror(r, "Load failed"))
		set_user_error (EIO);

	c->state = CTIDLE;
	c->flags &= ~(CTFILEMARK | CTEOM);
	return (r->status == ST_GOOD);
}	/* loadtape() */

/***********************************************************************
 * writefm()
 *
 * Write Filemarks on the tape.
 */

#if __USE_PROTO__
LOCAL void writefm(ctctrl_p c, int count)
#else
LOCAL void
writefm(c, count)
ctctrl_p	c;
int		count;
#endif
{
	Register srb_p		r = &(c->srb);
	Register g0cmd_p	g0 = &(r->cdb.g0);

	if (!ctbusywait(c, CTWRITEFM))
		return;

	r->buf.space = PHYS_ADDR | WATCH_REQACK;
	r->buf.ba_phys = NULL;
	r->buf.size = 0;
	r->xferdir = 0;
	r->timeout = 40;
	g0->opcode = WRITEFM;
	g0->lun_lba = (r->lun << 5);
	g0->lba_mid = ((unsigned char *) &count)[2];
	g0->lba_low = ((unsigned char *) &count)[1];
	g0->xfr_len = ((unsigned char *) &count)[0];
	g0->control = 0;
	doscsi(c->haft, r, 4, pritape, ctsleepPri(c, slpriSigCatch), "writefm");
	if (r->status != ST_GOOD && printerror(r, "Write filemarks failed"))
		set_user_error (EIO);
	c->state = CTIDLE;
}	/* writefm() */

/***********************************************************************
 * space()
 *
 * Space over blocks/filemarks/etc.
 */

#if __USE_PROTO__
LOCAL void space(ctctrl_p c, int count, int object)
#else
LOCAL void
space(c, count, object)
ctctrl_p	c;
int		count;
int		object;
#endif
{
	Register srb_p		r = &(c->srb);
	Register g0cmd_p	g0 = &(r->cdb.g0);

	if (!ctbusywait(c, CTSPACE))
		return;

	r->buf.space = PHYS_ADDR | WATCH_REQACK;
	r->buf.ba_phys = NULL;
	r->buf.size = 0;
	r->xferdir = 0;
	r->timeout = 300;
	g0->opcode = SPACE;
	g0->lun_lba = (r->lun << 5) | (object & 3);
	g0->lba_mid = ((unsigned char *) &count)[2];
	g0->lba_low = ((unsigned char *) &count)[1];
	g0->xfr_len = ((unsigned char *) &count)[0];
	g0->control = 0;
	doscsi(c->haft, r, 2, pritape, slpriSigCatch, "space");
	if (r->status != ST_GOOD && printerror(r, "Space failed"))
		set_user_error (EIO);
	c->state = CTIDLE;
}	/* space() */

/***********************************************************************
 * rewind()
 *
 * Rewind the tape drive back to the load point.
 */

#if __USE_PROTO__
LOCAL void rewind(ctctrl_p c, int wait)
#else
LOCAL void
rewind(c, wait)
ctctrl_p	c;
int		wait;
#endif
{
	Register srb_p		r = &(c->srb);
	Register g0cmd_p	g0 = &(r->cdb.g0);

	if (!ctbusywait(c, CTREWIND))
		return;

	r->buf.space = PHYS_ADDR | WATCH_REQACK;
	r->buf.ba_phys = NULL;
	r->buf.size = 0;
	r->timeout = 300;
	r->xferdir = 0;
	memset(g0, 0, sizeof(cdb_t));
	g0->opcode = REWINDTAPE;
	if (!wait)
		g0->lun_lba = (r->lun << 5) | 1;
	doscsi(c->haft, r, 2, pritape, ctsleepPri(c, slpriSigCatch), "rewind");
	if (r->status != ST_GOOD && printerror(r, "Rewind failed"))
		set_user_error (EIO);
	c->flags = 0;
	c->state = CTIDLE;
}	/* rewind() */

#if __USE_PROTO__
LOCAL void erase(ctctrl_p c, int to_eot)
#else
LOCAL void
erase(c, to_eot)
ctctrl_p	c;
int		to_eot;
#endif
{
	Register srb_p		r = &(c->srb);
	Register g0cmd_p	g0 = &(r->cdb.g0);

	if (!ctbusywait(c, CTERASE))
		return;

	r->buf.space = PHYS_ADDR | WATCH_REQACK;
	r->buf.ba_phys = NULL;
	r->buf.size = 0;
	r->timeout = 300;
	r->xferdir = 0;
	memset(g0, 0, sizeof(cdb_t));
	g0->opcode = ERASE;
	g0->lun_lba = (r->lun << 5);
	if (to_eot)
		g0->lun_lba |= 1;
	doscsi(c->haft, r, 2, pritape, slpriSigCatch, "erase");
	if (r->status != ST_GOOD && printerror(r, "Erase failed"))
		set_user_error (EIO);
	if (to_eot)
		c->flags &= ~(CTFILEMARK | CTEOM | CTILI | CTDIRTY);
	c->state = CTIDLE;
}	/* erase() */

/***********************************************************************
 * Device Driver Entry Point routines.		                       *
 ***********************************************************************/

/***********************************************************************
 * ctinit()
 *
 * Initialize the tape device at (id). This doesn't do anything,
 * not even verify that the drive is there because it could be powered
 * off.
 */

#if __USE_PROTO__
LOCAL int ctinit(haft_t * haft, Register int id)
#else
LOCAL int
ctinit(haft, id)
haft_t * 	haft;
Register int	id;
#endif
{
    Register ctctrl_p c = kalloc(sizeof(ctctrl_t));

    if (!haft) {
	cmn_err(CE_WARN, "haict: No host adapter function table.");
	return 0;
    }
    
    if (!c) {
	cmn_err(CE_WARN, "haict: Could not allocate control structure.");
	return 0;
    }
    
    cmn_err(CE_CONT, "%d: HAI SCSI Tape Module v1.9\n", id);
    memset(c, 0, sizeof(ctctrl_t));
    c->haft = haft;
    c->inuse = 0;

    /*
     * Now set up the cache. One of two types either a really big one
     * in physical memory. Or a smaller one in kalloc memory allocated 
     * at open time. Assume the latter.
     */

    c->cache = NULL;
    if (haict_cache) {		/* Configured for physical memory cache */
	c->cachesize = haict_cache;
	c->cache = (char *) getPhysMem(haict_cache);
    }

    if (!c->cache) {
	if (haict_cache)
	    haict_cache = 0;

	c->cachesize = (tandberg(id)) ? TDCCACHESZ : STDCACHESZ;
    }
    c->start = NULL;
    c->srb.target = id;
    c->srb.lun = 0;
    c->state = CTIDLE;
    ctdevs[id] = c;
    return 1;
}

#if __USE_PROTO__
LOCAL void ctopen(dev_t dev, int mode)
#else
LOCAL void
ctopen(dev, mode)
dev_t		dev;
int		mode;
#endif
{
    Register ctctrl_p	c = ctdevs[tid(dev)];
    Register srb_p	r = &(c->srb);
    Register g0cmd_p	g0 = &(r->cdb.g0);
    int 		rblerf;	/* read block limits error flag */
    int 		s;
    char		buf[64];
    blkdscr_p		bd = (blkdscr_p) (buf + 4);
    blklim_p		bl = (blklim_p) (buf);
    
    if (!c) {
	set_user_error (ENXIO);
	return;
    }
    if ((mode != IPR) && (mode != IPW)) {
	set_user_error (EINVAL);
	return;
    }
    
    s = sphi();
    if (c->inuse) {
	set_user_error (EBUSY);
	goto done;
    }
    
    c->inuse = 1;
    c->state = CTINIT;
    r->dev = dev;	   /* Save the rewind bit for close.*/
    
    /***************************************************************
     *  Media_check: Make sure that there is a tape in the drive.
     *  The test unit ready command returns whether or not the
     *  tape drive is has a tape and is ready. We have to retry
     *  this command several times because a bus_device_resets
     *  or tape change is reported as a failed test_unit_ready
     *  followed by a successful one.
     */
    
    r->buf.space = PHYS_ADDR | WATCH_REQACK;
    r->buf.ba_phys = NULL;
    r->buf.size = 0;
    r->timeout = 2;
    r->xferdir = 0;
    memset(g0, 0, sizeof(cdb_t));		/* Test Unit Ready */
    memset(r->sensebuf, 0, sizeof(r->sensebuf));
    doscsi(c->haft, r, 4, pritape, slpriSigCatch, "ctopen");
    
    /***************************************************************
     * If the command fails there probably wasn't a tape in the
     * drive.
     */
    
    if (r->status != ST_GOOD) { 	/* Is there a tape in the drive? */
	if (r->status != ST_USRABRT) {
	    /* Otherwise assume no tape.*/
	    set_user_error (ENXIO);
	    devmsg (r->dev, "Tape drive not ready.");
	}
	goto openfailed;
    }
    else {
	/*******************************************************
	 *  Do a load command on the tandberg tape drives when
	 *  the drive is busy retensioning. This blocks the
	 *  applications from sending too many commands to
	 *  the drive on startup.
	 */
	
	if (tandberg(tid(dev)) && r->sensebuf[0] == 0x70 && r->sensebuf[2] == 0x06) {
            r->buf.space = PHYS_ADDR | WATCH_REQACK;
            r->buf.ba_phys = NULL;
            r->buf.size = 0;
	    r->xferdir = 0;
	    r->timeout = 300;
	    memset(g0, 0, sizeof(cdb_t));
	    g0->opcode = LOAD;
	    g0->xfr_len = 1;		/* Move tape to load point.*/
	    doscsi(c->haft, r, 4, pritape, slpriSigCatch, "ctopen");
	    if (r->status != ST_GOOD) {
		if (printerror(r, "Load failed - TDC3600 not ready"))
		  set_user_error (ENXIO);
		goto openfailed;
	    }
	}
    }
    
    ctvmsg(0x0100, (devmsg(r->dev, "Read block limits.")));
    
    /***************************************************************
     *  Tandberg's Read block limits command is broken.
     */
    
    if (tandberg(tid(dev))) {
	c->blmin = c->blmax = 512;
	rblerf = 0;
    }
    else {
	/*******************************************************
	 *  Do a read block limits to find out what the drive
	 *  is capable of. We need either Read block limits
	 *  or Mode Sense to work here. If we cannot get either
	 *  then we have problems.
	 */
	
	r->buf.space = KRNL_ADDR | WATCH_REQACK;
	r->buf.addr.caddr = (caddr_t) bl;
	r->buf.size = sizeof(blklim_t);
	r->xferdir = DMAREAD;
	r->timeout = 2;
	memset(g0, 0, sizeof(cdb_t));
	g0->opcode = READBLKLMT;
	g0->xfr_len = 6;
	doscsi(c->haft, r, 3, pritape, slpriSigCatch, "ctopen");
        
	if (rblerf = (r->status != ST_GOOD)) {
	    ctvmsg(0x0010, (printerror(r, "Read Block LImits")));
	    c->blmax = c->blmin = 0;
	}
	else {
	    flip(bl->blmax);        /* SCSI to INTEL order */
	    flip(bl->blmin);        /* Ditto */
	    c->blmax = (bl->blmax & 0x00ffffff);
	    c->blmin = bl->blmin;
	}
    }
    
    /***************************************************************
     *  Now for the mode sense. This should return at least one
     *  block descriptor which we can use to figure out the buffer
     *  size that the tape drive is using. Most of the streaming
     *  tape drives don't support variable mode operation. I don't
     *  know about the DATs
     */
    
    r->buf.space = KRNL_ADDR | WATCH_REQACK;
    r->buf.addr.caddr = (caddr_t) buf;
    r->buf.size = sizeof(buf);
    r->xferdir = DMAREAD;
    r->timeout = 2;
    memset(g0, 0, sizeof(cdb_t));
    g0->opcode = MODESENSE;
    g0->xfr_len = sizeof(buf);
    doscsi(c->haft, r, 3, pritape, slpriSigCatch, "ctopen");
    if (r->status != ST_GOOD) {
	if (printerror(r, "Mode sense failed"))
	    set_user_error (EIO);
	goto openfailed;
    }
    
    /***********************************************************************
     * If tape drive opened in write mode make sure the tape is not write
     * protected now.
     */
    
    if (mode == IPW && (buf[2] & 0x80) != 0) {
	devmsg(dev, "Tape is write protected");
	set_user_error (ENXIO);
	goto openfailed;
    }
 
    /***************************************************************
     *  According to SCSI-1 the first media descriptor is the default
     *  we will use this one. SCSI-2 is much clearer on this.
     */
    
    if (buf[3]) {	/* If mode sense returned any media descriptors */
	bd->rb.blocksize &= 0xffffff00;
	flip(bd->rb.blocksize);
	c->block = bd->rb.blocksize;
	if (c->block) {
	    if (haict_cache)
		c->cachesize = haict_cache;
	    else
		c->cachesize = (tandberg(tid(dev))) ? TDCCACHESZ : STDCACHESZ;

	    if (c->cachesize % c->block)
		c->cachesize -= (c->cachesize % c->block);
	}
    }
    else {
	devmsg(r->dev, "No media descriptors: Contact Mark Williams Tech support");
	set_user_error (ENXIO);
	goto openfailed;
    }
    ctvmsg(0x0010, devmsg(dev, "Blocksize: %d bytes.", c->block));
    
    /***********************************************************************
     * One last check:  If we aren't using block mode (!c->block)
     * and we didn't get any block limits then we cannot support this
     * drive.
     */
    
    if (!c->block && rblerf) {
	devmsg(r->dev, "<No block limits on variable mode tape drive>");
	devmsg(r->dev, "<Contact Mark Williams Tech Support>");
	set_user_error (ENXIO);
	goto openfailed;
    }
    
    c->flags = (c->flags & (CTDIRTY | CTEOM)) |
	       ((mode == IPR) ? CTRDMD : CTWRTMD);
    if (c->block) {
	if (!haict_cache) {
	    c->cache = kalloc(c->cachesize);
	    if (!c->cache) {
		devmsg(dev, "Could not allocate tape cache");
		set_user_error (ENOMEM);
		goto openfailed;
	    }
	}
	c->avail = (c->flags & CTRDMD) ? 0 : c->cachesize;
	c->start = c->cache;
    }
    c->state = CTIDLE;
    goto done;
    
 openfailed:
    c->state = CTIDLE;
    c->inuse = 0;
    
 done:
    spl(s);
}	/* ctopen() */

/***********************************************************************
 * ctclose()
 *
 * Close the SCSI Device at (dev).
 */


#if __USE_PROTO__
LOCAL void ctclose(Register dev_t dev)
#else
LOCAL void
ctclose(dev)
Register dev_t	dev;
#endif
{
    Register ctctrl_p	c = ctdevs[tid(dev)];
    Register srb_p	r = &(c->srb);
    int 		s;
    
    if (!c) {
	set_user_error (ENXIO);
	return;
    }
    
    s = sphi();
    c->flags |= CTCLOSING;
    if (c->block && (c->flags & CTDIRTY)) {
	if (ctbusywait(c, CTLASTWRT))
	    flushcache(c);
	c->state = CTIDLE;
    }
    spl(s);
    if (!haict_cache && c->cache) {
	kfree(c->cache);
	c->cache = c->start = NULL;
	c->avail = 0;
    }
    
    if (c->flags & CTDIRTY)
	writefm(c, 1);
    
    if (r->dev & REWIND) {
	if (c->flags & CTDIRTY)
	    writefm(c, 1);
	rewind(c, 0);
    }
    
    c->inuse = 0;
    return;
}	/* ctclose() */

/***********************************************************************
 * fillcache() --	Read from the tape into the cache (really?)
 *
 * return 0 and set u.u_error on any errors.
 */

#if __USE_LOCAL__
LOCAL int fillcache(Register ctctrl_p c)
#else
LOCAL int
fillcache(c)
Register ctctrl_p	c;
#endif
{
	Register srb_p		r = (&c->srb);
	Register g0cmd_p	g0 = &(r->cdb.g0);
	size_t				blocks;
	extsense_p			e;
	int 				info;
	int 				retval = 0;

	r->buf.space = KRNL_ADDR;
	r->buf.addr.caddr = (caddr_t) c->cache;
	r->buf.size = c->cachesize;
	r->xferdir = DMAREAD;
	r->timeout = 30;
	r->tries = 0;
	g0->opcode = READ;
	g0->lun_lba = (r->lun << 5) | 1;
	blocks = c->cachesize / c->block;
	g0->lba_mid = ((unsigned char *) &blocks)[2];
	g0->lba_low = ((unsigned char *) &blocks)[1];
	g0->xfr_len = ((unsigned char *) &blocks)[0];
	g0->control = 0;
	doscsi(c->haft, r, 1, pritape, slpriSigCatch, "ctblkrd");
	switch (r->status) {
	case ST_GOOD:
		c->start = c->cache;
		c->avail = r->buf.size;
		retval = 1;
		break;
	case ST_CHKCOND:
		e = (extsense_p) r->sensebuf;
		if ((e->errorcode & 0x70) == 0x70) {
			info = 0;
			if (e->errorcode & 0x80) {
				info = e->info;
				flip(info);
			}
			if (e->sensekey & (CTFILEMARK | CTEOM)) {
				c->flags |= (e->sensekey & (CTFILEMARK | CTEOM));
				c->start = c->cache;
				c->avail = c->cachesize - (info * c->block);
				retval = 1;
				break;
			}
		}
		printsense(r->dev, "Read failed", (extsense_p) r->sensebuf);
		set_user_error (EIO);
		retval = 0;
		break;
	case ST_USRABRT:
		set_user_error (EINTR);
		c->start = c->cache;
		c->avail = 0;
		retval = 0;
		break;
	default:
		devmsg(r->dev, "Read failed: status (0x%x)", r->status);
		set_user_error (EIO);
		retval = 0;
		break;
	}
	return retval;
}	/* fillcache() */

/***********************************************************************
 * ctfbrd()	--	Fixed block read handler. Reads from the tape
 * 				drive through the cache when the tape drive is
 * 				in fixed block mode.
 */

#if __USE_PROTO__
LOCAL void ctfbrd(Register ctctrl_p c, Register IO *iop)
#else
LOCAL void
ctfbrd(c, iop)
Register ctctrl_p	c;
Register IO 		*iop;
#endif
{
    Register size_t reqcount,	/* Total bytes transfered toward request */
                    xfrsize;	/* Current transfer size */
    size_t          total,	/* System global memory total transfer size */
    		    size;	/* System global memory current transfer size */
    
    if (!ctbusywait(c, CTFBRD))
	return;
    reqcount = 0;
    while (iop->io_ioc) {
	xfrsize = min(c->avail, iop->io_ioc);
	if (xfrsize > 0) {
	    switch (iop->io_seg) {
	    case IOSYS:
		memcpy(iop->io.vbase + reqcount, c->start, xfrsize);
		break;
	    case IOUSR:
		kucopy(c->start, iop->io.vbase + reqcount, xfrsize);
		break;
	    case IOPHY:
		total = 0;
		while (total < xfrsize) {
		    size = min(xfrsize - total, NBPC);
		    xpcopy(c->start + total,
			   iop->io.pbase + reqcount + total,
			   size, SEL_386_KD | SEG_VIRT);
		    total += size;
		}
		break;
	    }
	    c->start += xfrsize;
	    c->avail -= xfrsize;
	    reqcount += xfrsize;
	    iop->io_ioc -= xfrsize;
	}
	if (iop->io_ioc) {
	    if (c->flags & CTFILEMARK) {
		c->flags &= ~CTFILEMARK;
		break;
	    }
	    
	    if (c->flags & CTEOM) {
		set_user_error (EIO);
		break;
	    }
	    
	    if (!fillcache(c))
		break;
	}
    }	/* while */
    c->state = CTIDLE;
}	/* ctfbrd() */

/***********************************************************************
 * ctvbrd()	--	Variable block read entry point.
 */

#if __USE_PROTO__
LOCAL void ctvbrd(Register ctctrl_p c, IO *iop)
#else
LOCAL void
ctvbrd(c, iop)
Register ctctrl_p c;
IO *iop;
#endif
{
    Register srb_p r = &(c->srb);
    Register g0cmd_p g0 = &(r->cdb.g0);
    size_t xfrsize;
    extsense_p e;
    int info;
    
    if (!ctbusywait(c, CTVBRD))
	return;
    
    if (c->flags & CTEOM) {
	set_user_error (EIO);
	return;
    }
    if (c->flags & CTFILEMARK) {
	c->flags &= ~CTFILEMARK;
	return;
    }
    switch (iop->io_seg) {
    case IOSYS:
	r->buf.space = KRNL_ADDR;
	r->buf.addr.caddr = iop->io.vbase;
	break;
    case IOUSR:
	r->buf.space = USER_ADDR;
	r->buf.addr.caddr = iop->io.vbase;
	break;
    case IOPHY:
	r->buf.space = PHYS_ADDR;
	r->buf.addr.paddr = iop->io.pbase;
	break;
    }
    r->buf.size = xfrsize = iop->io_ioc;
    r->xferdir = DMAREAD;
    r->timeout = 30;
    g0->opcode = READ;
    g0->lun_lba = (r->lun << 5);
    g0->lba_mid = ((unsigned char *) &xfrsize)[2];
    g0->lba_low = ((unsigned char *) &xfrsize)[1];
    g0->xfr_len = ((unsigned char *) &xfrsize)[0];
    g0->control = 0;
    doscsi(c->haft, r, 1, pritape, slpriSigCatch, "ctvbrd");
    switch (r->status) {
    case ST_GOOD:
	iop->io_ioc -= r->buf.size;
	break;
    case ST_CHKCOND:
	e = (extsense_p) r->sensebuf;
	if ((e->errorcode & 0x70) == 0x70) {
	    info = 0;
	    if (e->errorcode & 0x80) {
		info = (long) e->info;
		flip(info);
	    }
	    if (e->sensekey & (CTFILEMARK | CTEOM)) {
		c->flags |= (e->sensekey & (CTFILEMARK | CTEOM));
		break;
	    }
	    else if (e->sensekey & CTILI) {
		devmsg(r->dev,
		       "Read failed buffer size %d blocksize %d",
		       xfrsize,
		       xfrsize - info);
		if (info > 0)
		    iop->io_ioc -= (xfrsize - info);
		else
		    set_user_error (EIO);
		break;
	    }
	}
	printsense(r->dev, "Read failed", (extsense_p) r->sensebuf);
	set_user_error (EIO);
	break;
    case ST_USRABRT:
	break;
    default:
	devmsg(r->dev, "Read failed: status (0x%x)", r->status);
	set_user_error (EIO);
	break;
    }
    c->state = CTIDLE;
}	/* ctvbrd() */

/***********************************************************************
 * ctread()	--	OS Read entry point.
 */

#if __USE_PROTO__
LOCAL void ctread(dev_t dev, Register IO *iop)
#else
LOCAL void
ctread(dev, iop)
dev_t dev;
IO *iop;
#endif
{
	Register ctctrl_p	c = ctdevs[tid(dev)];

	if (!c) {
		set_user_error (EINVAL);
		return;
	}

	if (c->block)
		ctfbrd(c, iop);
	else
		ctvbrd(c, iop);
}	/* ctread() */

/***********************************************************************
 * flushcache()	--	flush the data in the cache to the tape.
 *
 * returns 0 and sets u.u_error on failure else returns 1.
 */

#if __USE_LOCAL__
LOCAL int flushcache(Register ctctrl_p c)
#else
LOCAL int
flushcache(c)
Register ctctrl_p	c;
#endif
{
	Register srb_p		r = (&c->srb);
	Register g0cmd_p	g0 = &(r->cdb.g0);
	size_t				xfrsize;
	extsense_p			e;
	int 				info;
	int 				retval = 0;

	if (c->avail >= c->cachesize)
		return 1;

	r->buf.space = KRNL_ADDR;
	r->buf.addr.caddr = (caddr_t) c->cache;
	r->buf.size = xfrsize = c->cachesize - c->avail;
	r->xferdir = DMAWRITE;
	r->timeout = 30;
	r->tries = 0;
	g0->opcode = WRITE;
	g0->lun_lba = (r->lun << 5);
	if (c->block) {
		g0->lun_lba |= 1;
		xfrsize = (xfrsize + c->block - 1) / c->block;
	}
	g0->lba_mid = ((unsigned char *) &xfrsize)[2];
	g0->lba_low = ((unsigned char *) &xfrsize)[1];
	g0->xfr_len = ((unsigned char *) &xfrsize)[0];
	g0->control = 0;
	doscsi(c->haft, r, 1, pritape, ctsleepPri(c, slpriSigCatch), "ctblkwrt");
	switch (r->status) {
	case ST_GOOD:
		c->start = c->cache;
		c->avail = c->cachesize;
		retval = 1;
		break;
	case ST_CHKCOND:
		e = (extsense_p) r->sensebuf;
		if ((e->errorcode & 0x70) == 0x70) {
			info = 0;
			if (e->errorcode & 0x80) {
				info = e->info;
				flip(info);
			}
			if (e->sensekey & CTEOM) {
				c->flags |= CTEOM;
				devmsg(r->dev, "End of tape on block write");
			}
		}
		printsense(r->dev, "Read failed", (extsense_p) r->sensebuf);
		set_user_error (EIO);
		retval = 0;
		break;
	case ST_USRABRT:
		retval = 0;
		break;
	default:
		devmsg(r->dev, "Read failed: status (0x%x)", r->status);
		set_user_error (EIO);
		retval = 0;
		break;
	}
	return retval;
}	/* flushcache() */

/***********************************************************************
 * ctfbwrt()	--	Fixed block write.	This should be fast because
 * 				it uses the tapes drives optimum setting and it
 * 				goes through a cache.
 */

#if __USE_PROTO__
LOCAL void ctfbwrt(Register ctctrl_p c, Register IO *iop)
#else
LOCAL void
ctfbwrt(c, iop)
Register ctctrl_p	c;
Register IO 		*iop;
#endif
{
    Register size_t reqcount,	/* Total bytes transfered */
                    xfrsize;	/* Current transfer size */
    size_t    	    total,	/* System global memory total transfer size */
                    size;	/* System global memory current transfer size */
  
    if (!ctbusywait(c, CTFBWRT))
	return;
    
    reqcount = 0;
    while (iop->io_ioc) {
	xfrsize = min(c->avail, iop->io_ioc);
	if (xfrsize) {
	    switch (iop->io_seg) {
	    case IOSYS:
		memcpy(c->start, iop->io.vbase + reqcount, xfrsize);
		break;
	    case IOUSR:
		ukcopy(iop->io.vbase + reqcount, c->start, xfrsize);
		break;
	    case IOPHY:
		total = 0;
		while (total < xfrsize) {
		    size = min(xfrsize - total, NBPC);
		    pxcopy(iop->io.pbase + reqcount + total,
			   c->start + total,
			   size, SEL_386_KD | SEG_VIRT);
		    total += size;
		}
		break;
	    }
	    c->start += xfrsize;
	    c->avail -= xfrsize;
	    reqcount += xfrsize;
	    iop->io_ioc -= xfrsize;
	}
	if (iop->io_ioc) {
	    if (!flushcache(c))
		break;
	}
    }	/* while */
    c->state = CTIDLE;
}	/* ctfbwrt() */

/***********************************************************************
 * ctvbwrt()	--	Variable block writes.
 */

#if __USE_PROTO__
LOCAL void ctvbwrt(Register ctctrl_p c, Register IO *iop)
#else
LOCAL void
ctvbwrt(c, iop)
Register ctctrl_p	c;
Register IO 		*iop;
#endif
{
	Register srb_p		r = &(c->srb);
	Register g0cmd_p	g0 = &(r->cdb.g0);
	size_t				xfrsize;
	extsense_p			e;
	int 				info;

	if (!ctbusywait(c, CTVBWRT))
		return;

	if (c->blmax && iop->io_ioc > c->blmax) {
		devmsg(r->dev, "Tape Error: maximum read/write size is %d bytes.", c->blmax);
		set_user_error (EIO);
		return;
	}
	switch (iop->io_seg) {
	case IOSYS:
		r->buf.space = KRNL_ADDR;
		r->buf.addr.caddr = iop->io.vbase;
		break;
	case IOUSR:
		r->buf.space = USER_ADDR;
		r->buf.addr.caddr = iop->io.vbase;
		break;
	case IOPHY:
		r->buf.space = PHYS_ADDR;
		r->buf.addr.paddr = iop->io.pbase;
		break;
	}
	xfrsize = min(iop->io_ioc, c->blmin);
	r->buf.size = xfrsize;
	r->xferdir = DMAWRITE;
	r->timeout = 30;
	g0->opcode = WRITE;
	g0->lun_lba = (r->lun << 5);
	g0->lba_mid = ((unsigned char *) &xfrsize)[2];
	g0->lba_low = ((unsigned char *) &xfrsize)[1];
	g0->xfr_len = ((unsigned char *) &xfrsize)[0];
	g0->control = 0;
	doscsi(c->haft, r, 1, pritape, slpriSigCatch, "ctvbwrt");
	switch (r->status) {
	case ST_GOOD:
		iop->io_ioc -= r->buf.size;
		break;
	case ST_CHKCOND:
		e = (extsense_p) r->sensebuf;
		if ((e->errorcode & 0x70) == 0x70) {
			info = 0;
			if (e->errorcode & 0x80) {
				info = (long) e->info;
				flip(info);
			}
			if (e->sensekey & CTEOM) {
				c->flags |= CTEOM;
				devmsg(r->dev, "End of tape");
			}
		}
		printsense(r->dev, "Write failed", (extsense_p) r->sensebuf);
		set_user_error (EIO);
		break;
	case ST_USRABRT:
		break;
	default:
		devmsg(r->dev, "Read failed: status (0x%x)", r->status);
		set_user_error (EIO);
		break;
	}
	c->state = CTIDLE;
}	/* ctvbwrt() */

/***********************************************************************
 * ctwrite()	-- Write entry point for tape drive.
 */

#if __USE_PROTO__
LOCAL void ctwrite(Register dev_t dev, Register IO *iop)
#else
LOCAL void
ctwrite(dev, iop)
Register dev_t	dev;
Register IO 	*iop;
#endif
{
	Register ctctrl_p c = ctdevs[tid(dev)];

	if (!c) {
		set_user_error (ENXIO);
		return;
	}

	c->flags |= CTDIRTY;
	if (c->block)
		ctfbwrt(c, iop);
	else
		ctvbwrt(c, iop);
}	/* ctwrite() */

/***********************************************************************
 * ctioctl()
 *
 * I/O Control Entry point for Cartridge tape devices.
 *
 * This function had been modified to allow applications level programs
 * to select modes and features for the tape drive.As stated above,
 * the philosophy of this driver is to provide least common denominator
 * support for all tape drives. I know that you spend big bucks to
 * get that (insert your favorite drive brand/model).	If I decide
 * to support everything out there on the market then I won't be able
 * to write network drivers, serial drivers, etc. So if you need
 * to do something to the tape drive to make it work (mode sense/select)
 * you can do it through this ctioctl as an applications program.
 */

#if __USE_PROTO__
LOCAL void ctioctl(dev_t dev, Register int cmd, char *vec)
#else
LOCAL void
ctioctl(dev, cmd, vec)
dev_t		dev;
Register int	cmd;
char		*vec;
#endif
{
    Register ctctrl_p	c = ctdevs[tid(dev)];
    Register srb_p		r = &(c->srb);
    int 				s;
    
    if (!c) {
	set_user_error (EINVAL);
	return;
    }
    
    switch (cmd) {
    case T_RST:
	resetdevice(c->haft, tid(dev));
	break;
    case MTREWIND:		/* Rewind */
    case T_RWD:
	if (c->flags & CTDIRTY)
	    writefm(c, 1);

	rewind(c, 1);
	break;
    case MTWEOF:		/* Write end of file mark */
    case T_WRFILEM:
	writefm(c, 1);
	break;
    case MTRSKIP:		/* Record skip */
	space(c, 1, SKIP_BLK);
	break;
    case MTFSKIP:		/* File skip */
    case T_SFF:
	space(c, 1, SKIP_FM);
	break;
    case MTTENSE:		/* Tension tape */
    case T_RETENSION:
	if (c->flags & CTDIRTY)
	    writefm(c, 1);
	loadtape(c, RETENSION);
	break;
    case MTERASE:		/* Erase tape */
    case T_ERASE:
	erase(c, ERASE_TAPE);
	break;
    case MTDEC: 		/* DEC mode */
    case MTIBM: 		/* IBM mode */
    case MT800: 		/* 800 bpi */
    case MT1600:		/* 1600 bpi */
    case MT6250:		/* 6250 bpi */
	return;
    default:
	if (!ctbusywait(c, CTIOCTL))
	    return;
	s = sphi();
	haiioctl(r, cmd, vec);
	c->state = CTIDLE;
	spl(s);
	break;
    }
} /* ctioctl() */