/***********************************************************************
 *  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.0.1.
 *
 *  Copyright (c) 1993, Christopher Sean Hilton, All Rights Reserved.
 *
 *  Last Modified: Thu Jul 15 19:59:42 1993 by [chris]
 *
 *  $Id: haictexp,v 1.1 93/07/20 11:21:24 bin Exp Locker: bin $
 */
/***********************************************************************
 *  READ THIS  *  READ THIS  *  READ THIS  *  READ THIS  *  READ THIS  *
 *
 *  This module is set up for my tape drive, a Tandberg TDC 3600 which
 *  is an oddball so here's why things are set up the way they are.
 *  This driver is set up for the possiblility that the tape drive
 *  is in an external cabinet from the PC.  This means that the tape
 *  drive may not be turned on when Coherent comes up and runs through
 *  the init routines.  Thus the init routine only prints a message
 *  to the effect that the driver is here.  The open routine handles
 *  the details of initializing the tape drive.  My TDC 3600 automatically
 *  retensions the tape cartridge after the tape is loaded and whenever
 *  the drive is reset.  I've timed this process at about 160 seconds
 *  from start to finish.  That is why the open routine will wait for
 *  180 seconds testing the drive through the load/unload command with
 *  immediate return.  This brings up the important fact that tape drives
 *  in general are slow devices and can take a long time to
 *  complete operations.  During the time when a tape drive is busy
 *  handling some action the SCSI driver could be doing something
 *  else so use the immediate return options whenever possible.
 */

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

#include <sys/haiscsi.h>

#define TDC3600 	/* Compensate for weirdness */
#define FIXED		/* Fixed block length reads/writes okay */
/* #define VARIABLE         Variable Block length reads/writes okay */
/* #define SHOWBLKSZ        Report blocksize on first open */

#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

/***********************************************************************
 *  The Tandberg TDC3600 requires special handling because it doesn't
 *  respond properly to a write when the tape is at the logical end
 *  of tape.  Tape should be organized as follows:
 *
 *  File 1 --------------| File 2-m
 *  DATA[1] | ...DATA[n] | FILEMARK | DATA[1-n] | FILEMARK | FILEMARK |
 *
 *  The double file marks signify the Logical End of Tape.  When a
 *  tape opened in write mode is written to and then closed on the
 *  no rewind device it is necessary to write a Logical End of tape
 *  and then skip backwards over one of the filemarks.  This leaves
 *  the tape positioned between the two filemarks. When this happens
 *  on my Tandberg the drive will then lock up (nice huh?) and fail
 *  to do anything until the tape is rewound or retensioned.
 */


#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 writeing to the tape */
int HAI_CACHESZ =	40 * BSIZE; /* 40 Block Cache for each device */

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

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 ctctrl_s *ctctrl_p;

typedef struct ctctrl_s {
	char		*cache, /* Transfer Cache */
			*start; /* Start of data in cache */
	size_t		avail;	/* bytes availaible in cache */
	ctstate_t	state;
	unsigned short	inuse,	/* In Use flag */
			flags,	/* Flags from device */
			block;	/* Block size of device */
	srb_t		srb;	/* SCSI Request block for transfers */
} ctctrl_t;

static int ctinit();		/* Initialize a SCSI device at (id) */
static void ctopen();		/* Open SCSI tape at (dev) */
static void ctclose();		/* Close a SCSI tape at (dev) */
static void ctread();		/* Read from SCSI tape at (dev) */
static void ctwrite();		/* Write SCSI tape at (dev) */
static void ctioctl();		/* I/O Control routine */

extern int nulldev();
extern int nonedev();

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

dca_t ctdca = {
	ctopen, 		/* Open */
	ctclose,		/* Close */
	nonedev,		/* Block No Block point here */
	ctread, 		/* Read */
	ctwrite,		/* Write */
	ctioctl,		/* Ioctl */
	ctinit, 		/* Load */
	nulldev,		/* Unload */
	nulldev 		/* Poll */
};

static ctctrl_p     ctdevs[MAXDEVS];

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

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

static int loadtape(c, opt)
register ctctrl_p c;
int opt;
{
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);

	c->state = CTLOADRETEN;
	r->buf. space = r->buf. addr. vaddr = r->buf. size = 0;
	r->xferdir = 0;
	r->timeout = 300;
	memset(g0, 0, sizeof(cdb_t));
	g0->opcode = LOAD;
	g0->xfr_len = 1;
	if (opt & IMMEDIATE)
		g0->lun_lba |= 1;
	if (opt & RETENSION)
		g0->xfr_len |= 2;
	doscsi(r, 4, CVBLKIO, IVBLKIO, SVBLKIO, "loadtape");
	if (r->status != ST_GOOD && printerror(r, "Load failed"))
		u. u_error = EIO;

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

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

static void writefm(c, count)
register ctctrl_p c;
int count;
{
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);

	c->state = CTWRITEFM;
	r->buf. space = r->buf. addr. vaddr = 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(r, 4, CVBLKIO, IVBLKIO, SVBLKIO, "writefm");
	if (r->status != ST_GOOD && printerror(r, "Write filemarks failed"))
		u. u_error = EIO;
	c->state = CTIDLE;
}   /* writefm() */

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

static void space(c, count, object)
register ctctrl_p c;
int count;
int object;
{
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);

	c->state = CTSPACE;
	r->buf. space = r->buf. addr. vaddr = 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(r, 2, CVBLKIO, IVBLKIO, SVBLKIO, "space");
	if (r->status != ST_GOOD && printerror(r, "Space failed"))
		u. u_error = EIO;
	c->state = CTIDLE;
}   /* space() */

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

static void rewind(c, wait)
register ctctrl_p c;
int wait;
{
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);

	c->state = CTREWIND;
	r->buf. space = r->buf. addr. vaddr = 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(r, 2, CVBLKIO, IVBLKIO, SVBLKIO, "rewind");
	if (r->status != ST_GOOD && printerror(r, "Rewind failed"))
		u. u_error = EIO;
	c->flags = 0;
	c->state = CTIDLE;
}   /* rewind() */

static void erase(c, to_eot)
register ctctrl_p c;
int to_eot;
{
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);

	c->state = CTERASE;
	r->buf. space = r->buf. addr. vaddr = 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(r, 2, CVBLKIO, IVBLKIO, SVBLKIO, "erase");
	if (r->status != ST_GOOD && printerror(r, "Erase failed"))
		u. u_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.
 */

static int ctinit(id)
register int id;
{
	register ctctrl_p c = kalloc(sizeof(ctctrl_t) /* + HAI_CACHESZ */);

	if (!c) {
		printf("\tCould not allocate control structure.\n");
		return 0;
	}

	printf("\tCoherent SCSI Tape driver v1.1\n");
	memset(c, 0, sizeof(ctctrl_t));
	c->inuse = 0;
	c->cache = NULL; /* ((char *) c) + sizeof(ctctrl_t); */
	c->srb. target = id;
	c->srb. lun = 0;
	c->state = CTIDLE;
	ctdevs[id] = c;
	return 1;
}

static void ctopen(dev, mode)
dev_t	dev;
int mode;
{
	register ctctrl_p	c = ctdevs[tid(dev)];
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);
	int s;
	unsigned long blim;
	char buf[64];
	blkdscr_p	bd = (blkdscr_p) (buf + 4);

	if (!c) {
		u. u_error = ENXIO;
		return;
	}
	if ((mode != IPR) && (mode != IPW)) {
		u. u_error = EINVAL;
		return;
	}

	s = sphi();

	if (c->inuse) {
		u. u_error = EDBUSY;
		goto done;
	}

	c->inuse = 1;
	c->state = CTINIT;
	r->dev = dev;	   /* Save the rewind bit for close. */

/***********************************************************************
 *  Repeat the test unit ready command to the tape drive.  This should
 *  return one of three ways:
 *
 *      Either there isn't a tape in the drive and the test unit ready
 *      will fail all the time: Sense Key 70 00 02 (Not Ready),
 *
 *      Or there is a tape in the drive and the user just put it in
 *      there: Sense Key 70 00 06 (Unit Attention - tape changed)
 *
 *      Or there is a tape in the drive and it's been in use for a
 *      used (No Sense Key data at all).
 *
 *      If there is a new tape in the drive then do the load command
 *      to make sure that it is ready to go.
 */

	r->timeout = 2;
	r->buf. space = r->buf. addr. vaddr = r->buf. size = 0;
	r->xferdir = 0;
	memset(g0, 0, sizeof(cdb_t));		/* Test Unit Ready */
	memset(r->sensebuf, 0, sizeof(r->sensebuf));
	doscsi(r, 4, CVBLKIO, IVBLKIO, SVBLKIO, "ctopen");

/***********************************************************************
 *  If the command fails there probably wasn't a tape in the drive.
 */

	if (r->status != ST_GOOD) {
		if (r->status != ST_USRABRT)
			u. u_error = ENXIO;
		goto openfailed;
	}

/***********************************************************************
 *  Command Passed: check for sense data which would say that the tape
 *  was just loaded.  If it was then block here because it is very
 *  inconvienent to do so in the block routine.  (loadtape needs to
 *  use v_sleep).
 */

	else if (r->sensebuf[0] == 0x70 && r->sensebuf[2] == 0x06) {
		if (!loadtape(c, 0))
			goto openfailed;
	}

/***********************************************************************
 *  Do a mode sense right now to check if the tape is write protected
 *  and the user asked for write access.  Also need to figure out block
 *  size for reads and writes.      This will end this fixed/variable
 *  nonsense.  If you need to use variable block mode I will provide
 *  an IO control to swap it.  Also this way my Tandberg will be supported
 *  just like any other tape drive.
 */

	r->buf. space = KRNL_ADDR;
	r->buf. addr. vaddr = (vaddr_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(r, 3, CVBLKIO, IVBLKIO, SVBLKIO, "ctopen");
	if (r->status != ST_GOOD) {
		if (printerror(r, "Mode sense failed"))
			u. u_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");
		u. u_error = ENXIO;
		goto openfailed;
	}

/***********************************************************************
 *  If mode sense returned any media descriptors take the first one
 *  and use it.  This should be the default media type.
 */

	if (buf[3]) {	/* If mode sense returned any media descriptors */
		blim = (bd->rb. blocksize & 0xffffff00);
		flip(blim);
		c->block = blim;
		if (c->block && HAI_CACHESZ % c->block) {
			HAI_CACHESZ -= (HAI_CACHESZ % c->block);
			devmsg(r->dev, "HAI_CACHESZ adjusted to %d bytes", HAI_CACHESZ);
		}
	}
	else {
		u. u_error = ENXIO;
		goto openfailed;
	}
#ifdef SHOWBLKSZ
	devmsg(dev, "Using blocksize %d bytes", c->block);
#endif

	c->flags = (mode == IPR) ? CTRDMD : CTWRTMD;
	if (c->block) {
		c->cache = kalloc(HAI_CACHESZ);
		if (!c->cache) {
			devmsg(dev, "Could not allocate tape cache");
			u. u_error = ENOMEM;
			goto openfailed;
		}
		c->avail = (c->flags & CTRDMD) ? 0 : HAI_CACHESZ;
		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).
 */

static void ctclose(dev)
register dev_t	dev;
{
	register ctctrl_p	c = ctdevs[tid(dev)];
	register srb_p		r = &(c->srb);
	int 			s;

	if (!c) {
		u. u_error = EINVAL;
		return;
	}

	s = sphi();
	c->flags |= CTCLOSING;
	flushcache(c);
	while (c->state != CTIDLE) {
		/* x_sleep(&(c->flags), ...); */
		v_sleep(&(c->flags), CVBLKIO, IVBLKIO, SVBLKIO, "ctclose");
		if (nondsig()) {
			u. u_error = EINTR;
			break;
		}
	}
	spl(s);
	if (c->cache) {
		kfree(c->cache);
		c->cache = c->start = NULL;
		c->avail = 0;
	}

#ifndef TDC3600
/***********************************************************************
 *  Write two filemarks (Logical End of Tape) and then skip backwards
 *  over one of them to ready for the another write operation on the
 *  no rewind device.  This code guarantees properly formed tapes according
 *  to the wisened old nine-track hacker that I work with.  The problem
 *  is that...
 */
	if (c->flags & CTDIRTY) {
		writefm(c, 1);		   /* Write a file mark */
		writefm(c, 1);		   /* Write another filemark */
		space(c, -1, SKIP_FM); /* Go back to the first filemark */
	}
#else
/*
 *  ... problem is that my Tandberg considers it an error to do anything
 *  after it has skipped past a filemark. So all commands except load and
 *  rewind fail after the the previous code.  The following will work in
 *  all situations but there is a risk that a user's tapes will only
 *  have one filemark at Logical End-of-Tape if the user isn't careful
 *  to use the rewind device the last time he uses tape drive.      This is
 *  only a problem with drives which insist upon doing a Retension each
 *  time the tape is changed or the drive gets reset.
 */
	if (c->flags & CTDIRTY) {
		writefm(c, 1);
		if (r->dev & REWIND)
			writefm(c, 1);
	}
#endif

	if (r->dev & REWIND)
		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.
 */

static int fillcache(c)
register ctctrl_p   c;
{
	register srb_p		r = (&c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);
	size_t				xfrsize;
	extsense_p			e;
	int 				info;
	int 				retval = 0;

	r->buf. space = KRNL_ADDR;
	r->buf. addr. vaddr = (vaddr_t) c->cache;
	r->buf. size = xfrsize = HAI_CACHESZ;
	r->xferdir = DMAREAD;
	r->timeout = 30;
	r->tries = 0;
	g0->opcode = READ;
	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(r, 1, CVBLKIO, IVBLKIO, SVBLKIO, "ctblkrd");
	switch (r->status) {
	case ST_GOOD:
		c->start = c->cache;
		c->avail = r->buf. size;
		retval = 1;
		break;
	case ST_CHKCOND:
		e = 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 = HAI_CACHESZ - (info * c->block);
				retval = 1;
				break;
			}
		}
		printsense(r->dev, "Read failed", r->sensebuf);
		u. u_error = EIO;
		retval = 0;
		break;
	case ST_USRABRT:
		retval = 0;
		break;
	default:
		devmsg(r->dev, "Read failed: status (0x%x)", r->status);
		u. u_error = EIO;
		retval = 0;
		break;
	}
	c->state = CTIDLE;
	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.
 */

static void ctfbrd(c, iop)
register ctctrl_p   c;
register IO	    *iop;
{
	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 */

	reqcount = 0;
	while (iop->io_ioc) {
		xfrsize = min(c->avail, iop->io_ioc);
		if (xfrsize) {
			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,
					       SEG_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) {
				u. u_error = EIO;
				break;
			}

			if (!fillcache(c))
				break;
		}
	}	/* while */
}   /* ctfbrd() */

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

static void ctvbrd(c, iop)
register ctctrl_p   c;
register IO	    *iop;
{
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);
	size_t			xfrsize;
	extsense_p		e;
	int 			info;

	if (c->flags & CTEOM) {
		u. u_error = EIO;
		return;
	}
	if (c->flags & CTFILEMARK) {
		c->flags &= ~CTFILEMARK;
		return;
	}
	c->state = CTVBRD;
	switch (iop->io_seg) {
	case IOSYS:
		r->buf. space = KRNL_ADDR;
		r->buf. addr. vaddr = iop->io. vbase;
		break;
	case IOUSR:
		r->buf. space = USER_ADDR;
		r->buf. addr. vaddr = 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(r, 1, CVBLKIO, IVBLKIO, SVBLKIO, "ctvbrd");
	switch (r->status) {
	case ST_GOOD:
		iop->io_ioc -= r->buf. size;
		break;
	case ST_CHKCOND:
		e = 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
					u. u_error = EIO;
				break;
			}
		}
		printsense(r->dev, "Read failed", r->sensebuf);
		u. u_error = EIO;
		break;
	case ST_USRABRT:
		break;
	default:
		devmsg(r->dev, "Read failed: status (0x%x)", r->status);
		u. u_error = EIO;
		break;
	}
	c->state = CTIDLE;
}   /* ctvbrd() */

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

static void ctread(dev, iop)
dev_t	    dev;
register IO *iop;
{
	register ctctrl_p	c = ctdevs[tid(dev)];

	if (!c) {
		u. u_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.
 */

static int flushcache(c)
register ctctrl_p   c;
{
	register srb_p		r = (&c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);
	size_t			xfrsize;
	extsense_p		e;
	int 			info;
	int 			retval = 0;

	r->buf. space = KRNL_ADDR;
	r->buf. addr. vaddr = (vaddr_t) c->cache;
	r->buf. size = xfrsize = HAI_CACHESZ - 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(r, 1, CVBLKIO, IVBLKIO, SVBLKIO, "ctblkwrt");
	switch (r->status) {
	case ST_GOOD:
		c->start = c->cache;
		c->avail = HAI_CACHESZ;
		retval = 1;
		break;
	case ST_CHKCOND:
		e = 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", r->sensebuf);
		u. u_error = EIO;
		retval = 0;
		break;
	case ST_USRABRT:
		retval = 0;
		break;
	default:
		devmsg(r->dev, "Read failed: status (0x%x)", r->status);
		u. u_error = EIO;
		retval = 0;
		break;
	}
	c->state = CTIDLE;
	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.
 */

static void ctfbwrt(c, iop)
register ctctrl_p   c;
register IO	    *iop;
{
	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 */

	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,
						   SEG_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 */
}   /* ctfbwrt() */

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


static void ctvbwrt(c, iop)
register ctctrl_p   c;
register IO	    *iop;
{
	register srb_p		r = &(c->srb);
	register g0cmd_p	g0 = &(r->cdb. g0);
	size_t			xfrsize;
	extsense_p		e;
	int 			info;

	c->state = CTVBWRT;
	switch (iop->io_seg) {
	case IOSYS:
		r->buf. space = KRNL_ADDR;
		r->buf. addr. vaddr = iop->io. vbase;
		break;
	case IOUSR:
		r->buf. space = USER_ADDR;
		r->buf. addr. vaddr = iop->io. vbase;
		break;
	case IOPHY:
		r->buf. space = PHYS_ADDR;
		r->buf. addr. paddr = iop->io. pbase;
		break;
	}
	xfrsize = iop->io_ioc;
	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(r, 1, CVBLKIO, IVBLKIO, SVBLKIO, "ctvbwrt");
	switch (r->status) {
	case ST_GOOD:
		iop->io_ioc -= r->buf. size;
		break;
	case ST_CHKCOND:
		e = 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", r->sensebuf);
		u. u_error = EIO;
		break;
	case ST_USRABRT:
		break;
	default:
		devmsg(r->dev, "Read failed: status (0x%x)", r->status);
		u. u_error = EIO;
		break;
	}
	c->state = CTIDLE;
}   /* ctvbwrt() */

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

static void ctwrite(dev, iop)
register dev_t	dev;
register IO	*iop;
{
	register ctctrl_p c = ctdevs[tid(dev)];

	if (!c) {
		u. u_error = EINVAL;
		return;
	}

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

/***********************************************************************
 *  ctioctl()
 *
 *  I/O Control Entry point for Cartridge tape devices.
 */

static void ctioctl(dev, cmd /*, vec */)
register dev_t	dev;
register int	cmd;
/* char *vec; */
{
	register ctctrl_p	c = ctdevs[tid(dev)];

	if (!c) {
		u. u_error = EINVAL;
		return;
	}

	switch (cmd) {
	case MTREWIND:		/* Rewind */
		rewind(c, 1);
		break;
	case MTWEOF:		/* Write end of file mark */
		writefm(c, 1);
		break;
	case MTRSKIP:		/* Record skip */
		space(c, 1, SKIP_BLK);
		break;
	case MTFSKIP:		/* File skip */
		space(c, 1, SKIP_FM);
		break;
	case MTTENSE:		/* Tension tape */
		loadtape(c, RETENSION);
		break;
	case MTERASE:		/* Erase tape */
		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:
		u. u_error = ENXIO;
		break;
	}
}   /* ctioctl() */

/* End of file */
