/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident		"$Header: tg.c 1.20 90/10/22 $"

/*
 * tg.c
 *	Driver for HP88780A tape drive (SSM-based SCSI)
 */

/* $Log:	tg.c,v $
 */

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/param.h"			/* Sizes */
#include "sys/mutex.h"			/* Gates, semaphores, and such */
#include "sys/sysmacros.h"
#include "sys/debug.h"
#include "sys/systm.h"
#include "sys/signal.h"
#include "sys/user.h"			/* User info and errno.h */
#include "sys/file.h"			/* Flag defines */
#include "sys/pte.h" 			/* Page tables info */
#include "sys/vmmac.h"			/* VM related conversion functions */
#include "sys/buf.h"			/* struct buf, header and such */
#include "sys/uio.h"			/* struct uio */
#include "sys/ioctl.h"			/* I/O copin's... */
#include "sys/errno.h"
#include "sys/cmn_err.h"
#include "sys/mtio.h"			/* Ioctl cmds from mt/others.. */
#include "sys/intctl.h"			/* Spl declarations */
#include "sys/autoconf.h"
#include "sys/cfg.h"
#include "sys/stats_tape.h"
#include "sys/scsi.h"
#include "sys/iovec.h"
#include "sys/scsi_adapter.h"
#include "sys/tg_device.h"
#include "sys/tg.h"			/* Driver local structures */

extern int strncmp();
extern char *scsi_errors[];
extern char *scsi_commands[];
extern struct devops tgdevops;		/* device operations for tg */
extern int tg_minor[];			/* permutations for minor # */
extern int tg_num_minor;		/* number of minor # */
extern int tg_c_major;			/* tg major device number */
static struct tg_info *tg_info;

/*
 * Forward references.
 */
int tgminphys();		
int tgstrat();
void tg_intr();
static void tg_raw_io();
static int tg_space_cmd();
static void tg_check_term();

#ifdef DEBUG

int tg_debug=0;				  /* 0=off */
#define TGPRINTF1	if (tg_debug & 1) CPRINTF
#define TGPRINTF2	if (tg_debug & 2) CPRINTF
#define TGPRINTF4	if (tg_debug & 4) CPRINTF

#endif /* DEBUG */
 
/*
 * tg_probe()
 *	Probe for an HP 88780A tape drive on an SSM SCSI bus.
 *	If the device is found return SPROBE_ONELUN if the
 *	device has an embedded SCSI target adapter and
 *	SPROBE_FOUND if it does not. 
 *	If not found return SPROBE_NOTARGET if the target
 *	adapter does not respond, and SPROBE_NOTFOUND otherwise.
 */
int
tg_probe(dev)
	tgdev_t *dev;
{
	scac_t inq_cmd, test_cmd;
	int retries, len;
	register struct tg_drive_info *dinfo;
	unchar lun = dev->config->dev_addr;
	struct scrsense *sensebuf;
	struct scinq_dev *inquiry;

	ASSERT(dev, "tg_probe: Unexpected NULL dev descriptor.");
	/*
	 *+ The probe function of the tg driver received a false
	 *+ descriptor address from the configuration code.  The
	 *+ configuration code might be broken.
	 */

	/*
	 * Allocate reusable SCSI inquiry and request-sense 
	 * buffers.
	 */
	sensebuf = (struct scrsense *)
		(*dev->alloc)(sizeof(struct scrsense));
	inquiry = (struct scinq_dev *) 
		(*dev->alloc)(sizeof(struct scinq_dev));
	/* 
	 * Issue a SCSI inquiry command to the device,
	 * verifying that the returned data indicates its a
	 * sequential access device with removable media and
	 * its vendor and product i.d. are valid for this driver.
	 * 
	 * If a check condition occurs, send a few SCSI 
	 * test-unit-ready commands to clear it had the device 
	 * been reporting powerup, then retry the inquiry.
	 */
	if (!scsi_inquiry_cmd(&inq_cmd.scmd, lun, sizeof(struct scinq_dev))) {
		cmn_err(CE_WARN, "tg_probe: command build failed\n");
		/*
		 *+ An attempt to probe the specified unit for
		 *+ the tg driver failed when the kernel could not build
		 *+ a SCSI command.  Likely cause of failure is 
		 *+ that the kernel has an invalid logical unit number for this
		 *+ device in its config table dev_addr field.
		 */
		return(SPROBE_NOTFOUND);
	}
	inq_cmd.dlen = sizeof(struct scinq_dev);
	inq_cmd.data = (paddr_t)inquiry;
	inq_cmd.iov = NULL;
	inq_cmd.sense = (paddr_t)sensebuf;
	inq_cmd.slen = sizeof(struct scrsense);

	switch ((*dev->polled_cmd)(dev, &inq_cmd)) {
	case SSTAT_NOTARGET:
		return (SPROBE_NOTARGET);
	case SSTAT_CCHECK:
	case SSTAT_DCHECK:
		(void) scsi_test_unit_ready_cmd(&test_cmd.scmd, lun);
		test_cmd.dlen = 0;
		test_cmd.sense = (paddr_t)sensebuf;
		test_cmd.slen = sizeof(struct scrsense);
		for (retries = 4; retries-- && 
			(*dev->polled_cmd)(dev, &test_cmd) != SSTAT_OK; ) 
			;
		if (((*dev->polled_cmd)(dev, &inq_cmd)) != SSTAT_OK) 
			return(SPROBE_NOTFOUND);
	case SSTAT_OK:
		break;
	default:
		return (SPROBE_NOTFOUND);
	}

	if (inquiry->sc_hdr.ih_devtype != INQ_SEQ 
	||  inquiry->sc_hdr.ih_qualif != INQ_REMOVABLE) 
		return (SPROBE_NOTFOUND); /* Incorrect media/device type */

	for (dinfo = tg_drives; dinfo < tg_drives + tg_drives_len; dinfo++) 
		if ((len = strlen(dinfo->vendor)) == 0) {
			if (inquiry->sc_hdr.ih_vlength == 0) {
				dev->drive_id = dinfo - tg_drives;
				return (SPROBE_FOUND); 
			}
		} else if (strncmp(dinfo->vendor, inquiry->sc_vendor, len) == 0
		&&  	   strncmp(dinfo->product, inquiry->sc_product,
				strlen(dinfo->product)) == 0) {
			dev->drive_id = dinfo - tg_drives;
			return ((dinfo->embedded == TGD_EMBED) ? 
				SPROBE_ONELUN : SPROBE_FOUND);
		}

	return (SPROBE_NOTFOUND);	/* No match in the table */
}

/*
 * tg_init()
 *	Initialize a group of SCSI tape drives.  
 * 	Allocate and initialize the driver information 
 *	structures, descriptors for the i/o buffers, and 
 *	dma mapping entries.  Perform any device specific 
 *	initialization to get things started.
 *
 *	Note that any device that is not fully initialized
 *	and/or is deconfigured will have infop->dev == NULL.
 */
void
tg_init(driver)
	struct driver_module *driver;
{
	register struct tg_info *infop;
	register tgdev_t *dev = (tgdev_t *) driver->devices;
	struct tg_drive_info *dinfo;
	struct dev *dp;
	int i, num_maps;
	int alive_count = 0;
	int unit = 0;

#ifdef DEBUG
	ASSERT(driver->numdev > 0, 
		"tg_init: # devices to boot is less than one");
	ASSERT(tg_bconf && tg_drives, 
		"tg_init: NULL binary configuration data address");

	TGPRINTF1( "tg_init: %d drives\n", driver->numdev);
#endif /* DEBUG */
	/*
 	 * Ignore device descriptions for which there 
	 * is not a binary configuration table entry
 	 * or which are in excess of the driver maximum.
	 */
	if (driver->numdev > tg_max_ndevs) {
		cmn_err(CE_WARN, "tg_init: %d device binary configuration ",
			 tg_max_ndevs);
		/*
		 *+ The number of tg devices configured in the
		 *+ system is greater than the number of binary configuration
		 *+ entries in tg_space.c.
		 */
		cmn_err(CE_CONT, "limit exceeded (from tg_space.c).\n");
		cmn_err(CE_CONT, "\tadditional configured tape ");
		cmn_err(CE_CONT, "drives ignored.\n");
	} else 
		tg_max_ndevs = driver->numdev;  /* Save actual # configured. */

	if (tg_max_ndevs > TG_UNITMAX + 1) {
		cmn_err(CE_WARN, 
	 	"tg_init: %d minor device limit exceeded - extras ignored.\n",
 			tg_max_ndevs = TG_UNITMAX + 1);
		/*
		 *+ The number of tg devices in the system configuration 
		 *+ exceeds the system maximum.  Extraneous entries
		 *+ will not be booted.  The system configuration should 
		 *+ be corrected.
		 */
	}

	/*
	 * Allocate an array of info structure, one per
	 * possible device, and initialize them.
	 */
	tg_info = infop = (struct tg_info *) 
		calloc(tg_max_ndevs * sizeof(struct tg_info));
#ifdef DEBUG
	TGPRINTF1( "tg_init: tg_info @@ 0x%x\n", tg_info);
#endif /* DEBUG */

	ASSERT(tg_info, "tg_init: Info structure allocation failed");
	/* 
	 *+ There is not enough memory for the tg driver to 
	 *+ allocate its per device information structures.
	 *+ Either another device has taken more than its
	 *+ share or more memory needs to be added to your
	 *+ machine.
	 */

	/* 
	 * For each unit locate its configuration
	 * information attempt to boot it.  If
	 * this fails, deconfigure the unit.
	 */
	for (; alive_count < driver->nalive; dev++, infop++, unit++) {
		if (!dev->driver) 
			continue;
#ifdef DEBUG
		TGPRINTF1("tg_init: tg%d is alive.\n", unit);
#endif /* DEBUG */
		alive_count++;

		num_maps = roundup(dev->segsize, 
			dev->map_extent) / dev->map_extent;

		if (dev->n_maps < num_maps) {
			cmn_err(CE_WARN, "tg%d: not enough maps available ", 
				unit);
			/*
			 *+ The tg driver doesn't have enough DMA maps 
			 *+ for this drive.  Recheck your system 
			 *+ configuration to verify that it has been 
			 *+ configured properly. 
			 */
			cmn_err(CE_CONT, 
				"(%d available, %d needed) ... deconfigured\n",
					dev->n_maps, num_maps);
			dev->driver = (struct driver_module *)NULL;
			continue;
		}

		/*
		 * For each of the unit's i/o descriptors allocate 
		 * a SCSI sense data buffer for its SCSI operations.  
		 * One of these buffers will also be used for mode 
		 * selection each time the unit is opened.
		 * Prior to allocation, verify that the configured
		 * sizes for this product is sufficiently large. 
		 */
		dinfo = tg_drives + dev->drive_id;

		if (dinfo->slen < sizeof(struct scrsense)
		|| dinfo->mlen < sizeof(struct scsi_mode_tape)) {
			cmn_err(CE_WARN, 
			     "tg%d: tg_drives[%d] slen and/or mlen ", unit);
			/*
			 *+ The configured buffer sizes (slen and mlen)
			 *+ in the tg_drives table entry for this 
			 *+ drive are insufficient.  
			 *+ Check these values in tg_space.c.  They
			 *+ should have been correct as shipped.
			 */
			cmn_err(CE_CONT, 
				"entries are too small ... deconfigured.\n");
			cmn_err(CE_CONT, 
				"\t(currently: %d, %d ... minimum: %d, %d)\n",
				dinfo->slen, dinfo->mlen, 
				sizeof(struct scrsense),
				sizeof(struct scsi_mode_tape));
			dev->driver = (struct driver_module *)NULL;
			continue;
		}

		/*
		 * Allocate the sense buffer so that it will accomodate
		 * the sense and mode sense buffers.
		 */
		if (dinfo->slen > dinfo->mlen)
			infop->cmd.slen = dinfo->slen;
		else
			infop->cmd.slen = dinfo->mlen;

		infop->cmd.sense = (paddr_t)(*dev->alloc)(infop->cmd.slen);

		if (!infop->cmd.sense) {
			cmn_err(CE_WARN, 
				"tg%d: buffer allocation failed", unit);
			/*
			 *+ Memory allocation of the request-sense
			 *+ mode select buffer for this tg driver
			 *+ unit failed.  Something else in system
			 *+ initialization is out of control or
			 *+ your system lacks sufficient physical
			 *+ memory.
		 	 */
			cmn_err(CE_CONT, " ... deconfigured.\n");
			dev->driver = (struct driver_module *)NULL;
		}

		/* 
		 * Perform interrupt mapping from the SCSI
		 * interface to the host for this device.
		 */
		if (!((*dev->bus->intr_setup)(dev, tg_bin))) {
			cmn_err(CE_WARN, 
			     "tg%d: interrupt mapping failed ... deconfigured",
			      unit);
			/*
			 *+ The interrupt mapping from this device's 
			 *+ controller to the tg driver failed.
			 *+ Too many device interrupts might
			 *+ be mapped to the same interrupt bin.
			 *+ Corrective action: recheck your binary configuration.
			 */
			dev->driver = (struct driver_module *)NULL;
			continue;
		}

		/* 
		 * Initialize the device's synchonization
		 * mechanisms, including its locks and semaphores.  
		 */
		init_lock(&infop->lock);
		init_sema(&infop->usrsync, 1);
		init_sema(&infop->gensync, 0);

		/*
		 * Add dev structures to devsw for this unit;
		 * one for each possible minor device number.
		 */
		 dp = (struct dev *)calloc(sizeof(struct dev) * tg_num_minor);
		 for (i = 0; i < tg_num_minor; i++, dp++) {
			dp->d_dev = makedev(tg_c_major, 
				MT_MINOR(unit, tg_minor[i], 0)); 
			dp->d_flags = D_TAPE;
			dp->d_ops = &tgdevops;
			if (devsw_add(dp) < 0)
			       CPRINTF("tg%d: problem adding to devsw\n", unit);
			else
				dev_deref(dp);
		}

		/*
		 * Initialize statistics gathering.
		 */
		infop->stats = tp_alloc_stats("tg", unit);

		/*
		 * Complete the initialization, indicating 
		 * to the driver that this device is alive.
		 */
		infop->access = TGA_CLOSED;
		infop->dev = dev;
		dev->info_id = unit;
	}
#ifdef DEBUG
	TGPRINTF1( "tg_init: DONE. \n");
#endif /* DEBUG */
}

/*
 * tgopen()
 *	Attempt to open the specified device if 
 *	it corresponds to a unit which is alive 
 *	and available for exclusive use.
 */
tgopen(dp, flag)
	struct dev *dp;
	int flag;
{
	register struct tg_info *infop = tg_info + MT_UNIT(dp->d_dev);
	tgdev_t *dev;
	unchar mode, lun;
	struct scsi_mode_tape *scmode;

	/* 
	 * Validate the minor device number.
	 */
	if (minor(dp->d_dev) > TG_MAXMINOR 
	||  MT_UNIT(dp->d_dev) >= tg_max_ndevs
	||  !(infop->dev))
		return (ENXIO);		/* Device is not available */

	switch (MT_DENSITY(dp->d_dev)) {
	case MTD_NONE:
		mode = TG_DENS_NOOP;
		break;
	case MTD_MED:
		mode = MODE_DEN_X339;
		break;
	case MTD_HIGH:
		mode = MODE_DEN_X354;
		break;
	default:
		cmn_err(CE_TO_USER, "tg%d: Illegal or unsupported density requested", MT_UNIT(dp->d_dev));
		/*
		 *+ This device was opened with a density-select code that is
		 *+ not supported by this tape drive.
		 */
		return (ENXIO);
	}

	dev = infop->dev;
	lun = dev->config->dev_addr;
	(void)p_sema(&infop->usrsync, PRIBIO);
	infop->spl = p_lock(&infop->lock, TG_SPL);

	/* 
	 * This device can only be opened for exclusive 
	 * access.  Verify that it is closed. 
	 */
	switch (infop->access) { 
	case TGA_RWNDCLS:
		/*
 		 * The previous close started a rewind
		 * operation.  Wait for its termination.
	 	 */
		tg_check_term(infop, TGC_PRSENSE);
		infop->access = TGA_CLOSED;
		break;
	case TGA_CLOSED:
		break;
	default:
		v_lock(&infop->lock, infop->spl);
		v_sema(&infop->usrsync);
		return (EBUSY);
	}

	/*
	 * Verify that the the unit is online.
	 */ 
	infop->sflags &= ~TGF_FAIL;
	(void) scsi_test_unit_ready_cmd(&infop->cmd.scmd, lun);
	infop->cmd.dlen = 0;
	infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
	tg_check_term(infop, TGC_PRSENSE);
	if (infop->sflags & TGF_FAIL) goto openfail;

	/* 
	 * Select the specified drive mode.
	 */
	infop->cmd.data = infop->cmd.sense;
	infop->cmd.dlen = tg_drives[dev->drive_id].mlen;
	bzero((char *)infop->cmd.data, (unsigned) infop->cmd.dlen);
	infop->cmd.iov = NULL;
	(void) scsi_mode_select_cmd(&infop->cmd.scmd, lun, infop->cmd.dlen);

	/* Set up drive-dependant mode select data  */
	if (tg_drives[dev->drive_id].mode_select)
		(*tg_drives[dev->drive_id].mode_select) (infop->cmd.data);
	scmode = (struct scsi_mode_tape *) infop->cmd.sense;

	/*
	 * Set the density-select field of the mode-select command based on
	 * the bits set in the minor device number of this device.
	 */
	scmode->t_bd.mb_density = mode;
	scmode->t_hdr.mh_dlen = sizeof(struct scmode_blkd);
	scmode->t_hdr.mh_mode = MSEL_BFM_ASYNC;
	infop->term_id = (*dev->start_cmd) (dev, &infop->cmd);
	tg_check_term(infop, TGC_PRSENSE);
	if (infop->sflags & TGF_FAIL) goto openfail;

	/* 
	 * Sense the drive's mode and
	 * determine if it is accessible.
	 */
	infop->cmd.data = infop->cmd.sense;
	infop->cmd.dlen = tg_drives[dev->drive_id].mlen;
	bzero((char *)infop->cmd.data, (unsigned) infop->cmd.dlen);
	infop->cmd.iov = NULL;
	(void) scsi_mode_sense_cmd(&infop->cmd.scmd, lun, infop->cmd.dlen);
	scmode->t_hdr.mh_dlen = sizeof(struct scmode_blkd);
	infop->term_id = (*dev->start_cmd) (dev, &infop->cmd);
	tg_check_term(infop, TGC_PRSENSE);
	if (infop->sflags & TGF_FAIL) goto openfail;

	if ((flag & FWRITE) && (scmode->t_hdr.mh_mode & MSENSE_WP)) {
		cmn_err(CE_TO_USER,"tg%d: no write ring\n", MT_UNIT(dp->d_dev));
		/*
		 *+ The requested tape drive is loaded with a tape that has
		 *+ no write ring.  Corrective action:  unload the tape and
		 *+ attach a write ring to the reel.
		 */
		goto openfail;
	}

	infop->access = TGA_OPEN;
	infop->sflags &= ~(TGF_LASTPOS | TGF_LASTIOW | TGF_EOF | TGF_EOM | 
		TGF_FAIL | TGF_UNLOAD);
	infop->blkno = (daddr_t)0;
	infop->nxrec = INF;
	v_lock(&infop->lock, infop->spl);
	v_sema(&infop->usrsync);
	return (0);

openfail:
	v_lock(&infop->lock, infop->spl);
	v_sema(&infop->usrsync);
	return (EIO);
}

/* tgclose()
 *	Perform required cleanup and mark the 
 *	device as no longer open, notifing 
 *	user level of any errors. 
 */
tgclose(dp, flags)
	struct dev *dp;
	int flags;
{
	register struct tg_info *infop = tg_info + MT_UNIT(dp->d_dev);
	tgdev_t *dev = infop->dev;
	unchar lun = dev->config->dev_addr;
	bool_t write_fm = 0;

	/*
	 * Since the usrsync semaphore controls access to the drive,
	 * all i/o will be completed when the p_sema returns.
	 */
	(void)p_sema(&infop->usrsync, PRIBIO);
	infop->access = TGA_CLOSED;

	if (infop->sflags & TGF_UNLOAD) { 
		/* Just mark it closed and return */
		v_sema(&infop->usrsync);
		return;
	}
	infop->spl = p_lock(&infop->lock, TG_SPL);

	if (flags == FWRITE || infop->sflags & TGF_LASTIOW) {
		/*
		 * The file was open for writing or was written 
		 * to on its last operation, write two filemarks to 
		 * mark the end-of-file and logical-end-of-data.
		 * Then clear the state modifier flags so we
		 * don't accidently write another filemark
		 */
		infop->sflags &= ~(TGF_FAIL | TGF_LASTIOW);
		(void) scsi_SA_write_filemarks_cmd(&infop->cmd.scmd, lun, 2);
		infop->cmd.dlen = 0;
		infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
		tg_check_term(infop, TGC_PRSENSE | TGC_WSENSE);

		if ((infop->sflags & (TGF_FAIL | TGF_EOM)) == TGF_FAIL) {
			u.u_error = EIO;
			cmn_err(CE_TO_USER, 
				"tg%d: error writing file mark on close\n",
				MT_UNIT(dp->d_dev));
			/*
			 *+ The tg driver's SCSI write file mark command
			 *+ failed.  This probably occurred, along with
			 *+ an error, while the system was attempting to write to the 
			 *+ medium. The medium might have been protected against 
			 *+ writes or against an attempt to write while not at the 
			 *+ beginning of tape or logical end of data.
			 */
			v_lock(&infop->lock, infop->spl);
			v_sema(&infop->usrsync);
			return;
		} else
			write_fm = 1;
	}

	if (MT_REWIND(dp->d_dev)) {
		/* 
	 	 * This is a rewind-on-close device; 
	 	 * mark it no longer open and start
		 * an asynchronous rewind.
		 */
		infop->access = TGA_RWNDCLS;
		(void) scsi_SA_rewind_cmd(&infop->cmd.scmd, lun, 0);
		infop->cmd.dlen = 0;
		infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);

	} else if (write_fm || infop->blkno <= infop->nxrec
	       && (infop->sflags & TGF_LASTPOS) == 0) {
		/* 
		 * The media isn't going to be rewound. 
	 	 * If filemarks were just written position
		 * the media between them.  Otherwise, position
		 * to the end of the current file, unless a
		 * positioning ioctl just occurred.
		 */
		(void) scsi_SA_space_cmd(&infop->cmd.scmd, lun, 
			(ulong)(write_fm ? -1 : 1), SCSI_SPACE_FILEMARKS);
		infop->cmd.dlen = 0;
		infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
		tg_check_term(infop, TGC_PRSENSE | TGC_SSENSE);

		if ((infop->sflags & (TGF_FAIL|TGF_EOM)) == TGF_FAIL) {
			u.u_error = EIO;
			cmn_err(CE_TO_USER, 
				"tg%d: error spacing over file mark on close\n",
				MT_UNIT(dp->d_dev));
			/*
			 *+ The tg driver's SCSI space command
			 *+ failed while spacing over a tape mark
			 *+ on close.  This probably resulted 
			 *+ from an attempt to skip past the logical
			 *+ end of data, such as opening and closing 
			 *+ an unwritten tape.
			 */
		}
	}
	v_lock(&infop->lock, infop->spl);
	v_sema(&infop->usrsync);
}

/* 
 * tgwrite()
 *	Invoke physio() to lock down user buffer 
 *	in memory and invoke the driver strategy 
 *	routine to perform the output.
 */
tgwrite(dp, uio)
	struct dev *dp;
	struct uio *uio;
{
	register dev_t dev = dp->d_dev;
	int status;
	/*
	 * Call tgiocheck to make sure the i/o size is in range.
	 * If we simply allow a minphys interface to truncate the
	 * i/o operation, odd results may occur on the tape.
	 */
	if (status = tgiocheck(dev, uio))
		return(status);
	return (physio(dp, (struct buf *)NULL, B_WRITE, uio));
}


/* 
 * tgread()
 *	Invoke physio() to lock down user buffer 
 *	in memory and invoke the driver strategy 
 *	routine to perform the output.
 */
tgread(dp, uio)
	struct dev *dp;
	struct uio *uio;
{
	register dev_t dev = dp->d_dev;
	int status;
	/*
	 * Call tgiocheck to make sure the i/o size is in range.
	 * If we simply allow a minphys interface to truncate the
	 * i/o operation, odd results may occur on the tape.
	 */
	if (status = tgiocheck(dev, uio))
		return(status);
	return (physio(dp, (struct buf *)NULL, B_READ, uio));
}
 
 
/* 
 * tgiocheck()
 *	Make sure that the i/o request can be mapped by the allocated
 *	number of maps.
 */
tgiocheck(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register daddr_t blk;
	register struct tg_info *infop = tg_info + MT_UNIT(dev);
	long offset;
	
	offset = (long) uio->uio_iov->iov_base & (NBPG -1);
	if ((offset + uio->uio_resid) > infop->dev->segsize)
		return(ENXIO);
	blk = bdbtofsb(uio->uio_offset >> DEV_BSHIFT);
	infop->blkno = blk;
	infop->nxrec = blk + 1;
	return(0);
}



/* 
 * tgminphys()
 *	Since the transfer size has been checked by tgiocheck
 *	from tgread or tgwrite, just return.
 */
/*ARGSUSED*/
tgminphys(dp, bp)
	struct dev *dp;
	register struct buf *bp;
{
	return(bp->b_bcount);
}
 

/*
 * tgstrategy()
 * 	READ or WRITE to the device via the raw 
 *	device interface using a buf-structure.
 * 
 *	If the standard block interface is being 
 *	used and the requested block is not within
 * 	the current file, reject the attempt to 
 *	perform I/O and generate user level error.
 *
 *	Note that the raw interface always sets
 *	infop->nxrec to be one greater than the 
 *	requested block number.
 */
/*ARGSUSED*/
tgstrat(dp, bp)
	struct dev *dp;
	struct buf *bp;
{
	register struct tg_info *infop = tg_info + MT_UNIT(bp->b_dev);
	int opcount;

	ASSERT(bp, "tgstrat: Unexpected NULL buf-struct pointer");
	/*
	 *+ The physio function passed an invalid address
	 *+ describing a transfer request
	 *+ to the tg driver's strategy function.
	 */
	bp->b_resid = bp->b_bcount;	/* Initially nothing transferred */
	bp->b_status = B_STATUS_OK;	/* Default status */
	(void)p_sema(&infop->usrsync, PRIBIO);
	infop->spl = p_lock(&infop->lock, TG_SPL);
#ifdef DEBUG
	TGPRINTF2("tgstrat: b_blkno %d, blkno %d, nxrec %d, sflags %d\n",
		bp->b_blkno, infop->blkno, infop->nxrec, infop->sflags);
#endif /* DEBUG */

	/*
	 * If we have crossed the End-of-tape sticker while writing
	 * or had a reported failure, and have not successfully 
	 * repositioned the tape since, reject this I/O as an error.
	 * Successful tape positioning or other such operations will
	 * clear the TGF_FAIL flag.
	 */
	if (infop->sflags & TGF_FAIL) {
		if ((bp->b_flags & B_READ) == B_WRITE 
		&&  infop->sflags & TGF_EOM) {
#ifdef DEBUG
			TGPRINTF2("tgstrat: EOM failure not cleared.\n");
#endif /* DEBUG */
			bp->b_error = ENOSPC;
		} else {
#ifdef DEBUG
			TGPRINTF2("tgstrat: previous failures not cleared.\n");
#endif /* DEBUG */
			bp->b_error = EIO;
		}
		bp->b_status |= B_S_XFER_ERR;
		bp->b_flags |= B_ERROR;
		goto stratdone;
	}
		
	infop->sflags &= ~TGF_LASTPOS;

	/*
	 * The following checks handle boundary cases for operation
	 * on non-raw tapes.  On raw tapes the initialization of
	 * infop->nxrec by tgminphys causes them to be skipped normally.
	 */
	if (bdbtofsb(bp->b_blkno) > infop->nxrec) {
		/*
		 * Can't read past known end-of-file.
		 */
		bp->b_flags |= B_ERROR;
		bp->b_error = ENXIO;
#ifdef DEBUG
		TGPRINTF2("tgstrat: past eof,nxrec is %d\n",infop->nxrec);
#endif /* DEBUG */
		goto stratdone;
	}
	if (bdbtofsb(bp->b_blkno) == infop->nxrec &&
	    	(bp->b_flags & B_READ) == B_READ) {
		/*
		 * Reading at end of file returns 0 bytes.
		 */
		clrbuf(bp);
#ifdef DEBUG
		TGPRINTF2("tgstrat: reading at EOF, returning zero bytes\n");
#endif /* DEBUG */
		goto stratdone;
	}

	if (infop->blkno != bdbtofsb(bp->b_blkno)) {
		/*
	 	 * Tape positioned incorrectly; position 
		 * forwards or backwards to the correct spot.
	 	 */
		opcount = bdbtofsb(bp->b_blkno) - infop->blkno;

		if (infop->blkno > infop->nxrec) {
			/*
			 * We must backspace over the filemark 
			 * just read (count it as a record).
			 */
			if (!tg_space_cmd(infop, -1, SCSI_SPACE_FILEMARKS)
			&&  (infop->sflags & (TGF_EOM | TGF_EOF)) == 0) {
				bp->b_flags |= B_ERROR;
				bp->b_error = ENXIO;
#ifdef DEBUG
				TGPRINTF2("tgstrat: backspace of FM failed\n");
#endif /* DEBUG */
				goto stratdone;
			}
			infop->blkno--;
			opcount++;
		}

		if (!tg_space_cmd(infop, opcount, SCSI_SPACE_BLOCKS)) {
			if (opcount > 0) {
				infop->blkno += opcount - infop->resid;
				if (infop->sflags & TGF_EOF)
					infop->nxrec = infop->blkno - 1;
				bp->b_flags |= B_ERROR;
				bp->b_error = ENXIO;
#ifdef DEBUG
				TGPRINTF2("tgstrat: space %d blocks failed\n",
					opcount);
#endif /* DEBUG */
				goto stratdone;
			} else if (infop->sflags & (TGF_EOM | TGF_EOF)) {
				infop->blkno = 0;	/* Beginning of file */
			} else {
				infop->blkno += opcount + infop->resid;
				bp->b_flags |= B_ERROR;
				bp->b_error = ENXIO;
#ifdef DEBUG
				TGPRINTF2("tgstrat: bkspace %d blocks failed\n",
					opcount);
#endif /* DEBUG */
				goto stratdone;
			}
		} else
			infop->blkno += opcount;
	}

 	tg_raw_io(infop, bp);
 	tp_completion_stats(infop->stats,
 			   (int) ((bp->b_bcount - bp->b_resid) >> DEV_BSHIFT),
			   bp->b_queued, (int) bp->b_flags);
stratdone:
#ifdef DEBUG
	TGPRINTF1("tgstrat: done, blkno %d, nxrec %d, sflags %d\n",
		infop->blkno, infop->nxrec, infop->sflags);
#endif /* DEBUG */
	v_lock(&infop->lock, infop->spl);
	v_sema(&infop->usrsync);
	biodone(bp);			/* Indicate I/O termination.  */
}

/*
 * tg_intr()
 *	Interrupt service routine for the SCSI tape 
 *	driver.  Verify its a valid interrupt, then
 *	save termination status, where it can be 
 * 	checked by the process waiting upon it.
 */
void
tg_intr(dev, term_id, status)
	register tgdev_t *dev;
	ulong  term_id;
	int status;
{
	register struct tg_info *infop;

	/*
	 * Verify that the interrupt is not bogus
	 * and that the device is alive.
	 */
	if (!dev) {
		cmn_err(CE_WARN, 
			"tg_intr: bogus interrupt ... invalid device @@ 0x%x.\n",
			(ulong)dev);
		/*
		 *+ The tg driver received an interrupt for a 
		 *+ non-existent or non-configured unit.  This may 
		 *+ indicate that the system's interrupt dispatch 
		 *+ table has been corrupted.
		 */
		return;
	}
	infop = tg_info + dev->info_id;
	infop->spl = p_lock(&infop->lock, TG_SPL);

	if (term_id != infop->term_id) {
		cmn_err(CE_WARN, 
			"tg_intr: tg%d: bad interrupt received- ignored",
				dev->info_id);
		/*
		 *+ The tg driver received an interrupt for a
		 *+ unit that appears to be inactive.  This 
		 *+ may indicate a driver internal problem, a problem
		 *+ with the SCSI adapter in the host, or a 
		 *+ malfunctioning drive unit.
		 */
		v_lock(&infop->lock, infop->spl);
		return;
	}	

	/*
	 * Save the termination status and notify the
 	 * process awaiting the command's termination.
	 */
	infop->status = status;
	v_sema(&infop->gensync);
	v_lock(&infop->lock, infop->spl);
}
 
/*
 * tgioctl() 
 *	Driver asynchronous i/o control requests.
 *	Verify that the command is a tape operation 
 *	and verify that it is compatible with the 
 * 	current mode of operation before executing 
 *	the request and reporting its termination 
 *	status.  
 */
tgioctl(dp, cmd, data)
	struct dev *dp;
	int	cmd;
	caddr_t data;
{
	register struct tg_info *infop = tg_info + MT_UNIT(dp->d_dev);
	struct mtop mtopdata, *mtop;
	tgdev_t *dev;
	unchar lun;
	int success = 1;
	int opcount;
	struct mtget tgget;

	dev = infop->dev;
	lun = dev->config->dev_addr;

	(void)p_sema(&infop->usrsync, PRIBIO);
	infop->spl = p_lock(&infop->lock, TG_SPL);
	switch (cmd) {
	case MTIOCTOP:		/* tape operation */
		if (copyin((caddr_t)data, (caddr_t)&mtopdata, sizeof(struct mtop)))
			return(EFAULT);
		mtop = &mtopdata;
		opcount = mtop->mt_count;

		switch (mtop->mt_op) {
		case MTNOP:
			break;			/* Boy, that was easy... ;-) */
		case MTFSF: 
			/*
		       	 * Space 'opcount' filemarks.
		 	 */
			success = tg_space_cmd(infop, opcount, SCSI_SPACE_FILEMARKS);
			infop->sflags |= TGF_LASTPOS;
			break;
		case MTBSF: 
			/*
		       	 * Space 'opcount' filemarks backwards.
		 	 */
			success = tg_space_cmd(infop, -opcount, SCSI_SPACE_FILEMARKS);
			infop->sflags |= TGF_LASTPOS;
			break;
		case MTFSR: 
			/*
		       	 * Space 'opcount' records.
		 	 */
			if (success = tg_space_cmd(infop, opcount, SCSI_SPACE_BLOCKS))
				infop->blkno += opcount;
			infop->sflags |= TGF_LASTPOS;
			break;
		case MTBSR: 
			/*
		       	 * Space 'opcount' records backwards.
		 	 */
			if (success = tg_space_cmd(infop, -opcount, SCSI_SPACE_BLOCKS))
				infop->blkno -= opcount;
			infop->sflags |= TGF_LASTPOS;
			break;
		case MTSEOD: 
			/*
		       	 * Space to the logical end-of-data.
		 	 */
			success = tg_space_cmd(infop, 0, SCSI_SPACE_ENDOFDATA);
			infop->sflags |= TGF_LASTPOS;
			break;
		case MTWEOF:
			/*
		 	 * Write 'opcount' filemarks on the media.
		 	 */
			infop->sflags &= ~(TGF_FAIL | TGF_LASTIOW);
			infop->sflags |= TGF_LASTPOS;

			/* 
		 	 * Initiate the SCSI write-filemarks
		 	 * operation, then await its termination.
		 	 */
			(void)scsi_SA_write_filemarks_cmd(&infop->cmd.scmd, lun, (ulong) opcount);
			infop->cmd.dlen = 0;
			infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
			tg_check_term(infop, TGC_PRSENSE | TGC_WSENSE);
			/* 
			 * Handle termination status and 
			 * mode/state transitions.  
			 */
			if ((infop->sflags & (TGF_FAIL | TGF_EOM)) == TGF_FAIL)
				success = 0;
			break;
		case MTERASE:
			/*
		 	 * Erase the media from its current
			 * position to the end-of-tape.
		 	 */
			infop->sflags &= ~(TGF_FAIL | TGF_EOF | 
				TGF_EOM | TGF_LASTIOW);
			infop->sflags |= TGF_LASTPOS;

			/* 
		 	 * Initiate a long-mode SCSI erase operation 
		 	 * and await its termination.
		 	 */
			(void) scsi_SA_erase_cmd(&infop->cmd.scmd, lun, 1);
			infop->cmd.dlen = 0;
			infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
			tg_check_term(infop, TGC_PRSENSE);

			/*
		 	 * Handle termination status and
		 	 * mode/state transitions.
		  	 */
			if (infop->sflags & TGF_FAIL) 
				success = 0;
			break;
		case MTOFFL:
			/*
		 	 * Unload the tape.
		 	 * 
		 	 * Clear the state modifier flags 
		 	 * so we don't write another filemark
		 	 * if the device is closed next.
		 	 */
			infop->sflags &= ~(TGF_FAIL | TGF_LASTIOW);

			/* 
		 	 * Initiate the SCSI unload tape 
		 	 * operation, then await its termination.
		 	 */
			(void)scsi_SA_load_unload_cmd(&infop->cmd.scmd, lun, 0);
			infop->cmd.dlen = 0;
			infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
			tg_check_term(infop, TGC_PRSENSE);
			/* 
			 * Handle termination status and 
			 * mode/state transitions.  
			 */
			if (infop->sflags & TGF_FAIL)
				success = 0;
			else 
				infop->sflags |= TGF_UNLOAD;
			break;
		case MTREW:
			/*
		 	 * Rewind the media to its load point.
		 	 */ 
			infop->sflags &= ~(TGF_FAIL | TGF_EOF | 
				TGF_EOM | TGF_LASTIOW);
			infop->sflags |= TGF_LASTPOS;

			/* 
		 	 * Initiate a SCSI rewind operation 
		 	 * and await its termination.
		 	 */
			(void) scsi_SA_rewind_cmd(&infop->cmd.scmd, lun, 0);
			infop->cmd.dlen = 0;
			infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
			tg_check_term(infop, TGC_PRSENSE);

			/*
		 	 * Handle termination status and
		 	 * mode/state transitions.
		  	 */
			if (infop->sflags & TGF_FAIL)
				success = 0;
			else 
				infop->blkno = 0;
			break;
		default:
			/*
		 	 * Unrecognizable or unsupported
		 	 * operation requested.  Return
		 	 * an error.
		 	 */
			v_lock(&infop->lock, infop->spl);
		 	v_sema(&infop->usrsync);
			return (EINVAL);
		}
		break;
	case MTIOCGET:
		tgget.mt_type = MT_ISTB;
		tgget.mt_dsreg = (short)infop->access << 8 | 
				 (short)infop->sflags;
		tgget.mt_erreg = 0;
		tgget.mt_resid = 0;	/* always zero */
		tgget.mt_fileno = 0;	/* not yet implemented */
		tgget.mt_blkno = (daddr_t) infop->blkno;
		if (copyout((caddr_t)&tgget, (caddr_t)data, sizeof(tgget))) {
			v_lock(&infop->lock, infop->spl);
		 	v_sema(&infop->usrsync);
			return(EFAULT);
		}
		break;
	default:
		/*
	 	 * Unrecognizable or unsupported
	 	 * operation requested.  Return
	 	 * an error.
	 	 */
		v_lock(&infop->lock, infop->spl);
	 	v_sema(&infop->usrsync);
		return (EINVAL);
	}	
	v_lock(&infop->lock, infop->spl);
 	v_sema(&infop->usrsync);
	return ((success) ? 0 : EIO);
}
 
/*
 * tg_raw_io()
 *	Start a read or write operation.
 *	Assumes that infop is locked.
 */
static void
tg_raw_io(infop, bp)
	register struct tg_info *infop;
	struct buf *bp;
{
	register tgdev_t *dev = infop->dev;
	struct pte *pte;
	off_t offset;
	int length;
	ulong mapped;
	unchar smask, lun = dev->config->dev_addr;

	ASSERT(bp->b_resid > 0, 
		"tg_raw_io: bp->b_resid < 0");
	/*
	 *+ The tg driver detected a buf structure from 
	 *+ physio that has an invalid, negative residual 
	 *+ count in it or that the driver over-decremented. 
	 */

	/*
	 * Build a SCSI command for the
	 * specified buffer.
	 */
	bp_iovec_info(bp, &pte, &offset, &length);
	infop->cmd.dlen = length;
	mapped = (*dev->bus->dma_setup) 
		 (dev, pte, offset, length, infop);
	if (mapped < length) {
		cmn_err(CE_WARN, "tg%d: I/O map fail, size %d\n",
			dev->info_id, length);
		/*
		  *+ An I/O request was not mapped by the SCSI
		  *+ map function.  Since the transfer was previously
		  *+ checked for the correct size, this should not
		  *+ happen.
	 	 */
		bp->b_flags |= B_ERROR;
		bp->b_status |= B_S_XFER_ERR | B_S_UNIT_ERR;
		return;
	}
	infop->sflags &= ~(TGF_FAIL | TGF_EOF | TGF_EOM | TGF_LASTIOW);
	infop->cmd.iov = (paddr_t)dev->bus_map_addr;
	infop->cmd.data = offset;

	if (bp->b_flags & B_READ) {
#ifdef DEBUG
	TGPRINTF1("tg_raw_io: READ, resid %d, blkno %d, nxrec %d, sflags %d\n", 
		bp->b_resid, infop->blkno, infop->nxrec, infop->sflags);
#endif /* DEBUG */
		(void) scsi_SA_read_cmd(&infop->cmd.scmd, lun, 
			TG_VARIABLE, (ulong)length);
		smask = TGF_FAIL;
	} else {
#ifdef DEBUG
	TGPRINTF1("tg_raw_io: WRITE, resid %d, blkno %d, nxrec %d, sflags %d\n",
		bp->b_resid, infop->blkno, infop->nxrec, infop->sflags);
#endif /* DEBUG */
		(void) scsi_SA_write_cmd(&infop->cmd.scmd, lun, 
			TG_VARIABLE, (ulong)length);
		infop->sflags |= TGF_LASTIOW;
		smask = TGF_FAIL | TGF_EOM;
		infop->nxrec = bdbtofsb(bp->b_blkno) + 1;
	}

	/*
	 * Fire off the command and statistics as well.
	 */
	infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
	TP_IOSTART_TIME(infop->stats);
	tg_check_term(infop, TGC_PRSENSE | TGC_RSENSE);
	TP_IODONE_TIME(infop->stats);

	if (bp->b_flags & B_READ
	&&  infop->sflags & TGF_EOF) {
		/* 
		 * Reading EOF is not an error; Note EOF
		 * block number and clear the failure flag.
		 * Treat EOM like EOF so that blank tapes
		 * can be used on the block interface, which
		 * attempts to read files prior to writting.
		 */
		infop->nxrec = bdbtofsb(bp->b_blkno);
		infop->sflags &= ~TGF_FAIL;
#ifdef DEBUG
	TGPRINTF1("tg_raw_io: EOF read\n");
#endif /* DEBUG */
	} else if ((infop->sflags & smask) == TGF_FAIL) {
		/* 
		 * Report the occurance of a failure
		 * during the operation.  Note that
		 * smask is used here so that write
		 * can detect early warning (EOM set)
		 * and not call it an error unless the
		 * user attempts yet another write.
		 */
		bp->b_flags |= B_ERROR;
		bp->b_status |= B_S_XFER_ERR | B_S_UNIT_ERR;
#ifdef DEBUG
	TGPRINTF1("tg_raw_io: I/O failure occurred\n");
#endif /* DEBUG */
	}

	bp->b_resid = infop->resid;
	infop->blkno++;
	(*dev->bus->dma_done) (dev, infop);
#ifdef DEBUG
	TGPRINTF1("tg_raw_io: done, resid %d, blkno %d, nxrec %d, sflags %d\n", 
		bp->b_resid, infop->blkno, infop->nxrec, infop->sflags);
#endif /* DEBUG */
}

/*
 * tg_space_cmd()
 *	Build and execute a SCSI space command.
 *	The 'fcode' argument specifies what to
 *	space over (filemarks or to end-of-data).
 *	The 'opcount' is the number of items to
 *	spacing over; forward when greater than
 *	zero, backwards when less than zero.
 *
 *	Returns: 1=successful, 0=failed.
 *
 *	Assumes that the info structure is locked 
 *	upon entry.
 */		
static int
tg_space_cmd(infop, opcount, fcode)
	register struct tg_info *infop;
	int opcount;
	unchar fcode;
{
	tgdev_t *dev;
	unchar lun;

	if (opcount == 0) return (1);	/* Short curcuit the operation */
	dev = infop->dev;
	lun = dev->config->dev_addr;
	infop->sflags &= ~(TGF_FAIL | TGF_EOF | TGF_LASTIOW);
	(void) scsi_SA_space_cmd(&infop->cmd.scmd, lun, (ulong) opcount, fcode);
	infop->cmd.dlen = 0;
	infop->term_id = (*dev->start_cmd)(dev, &infop->cmd);
	tg_check_term(infop, TGC_PRSENSE | TGC_SSENSE);
	return ((infop->sflags & TGF_FAIL) ? 0 : 1);
}

/* 
 * tg_summary()
 * 	Display a summary message for a teminated
 *	command through the cmn_err interface.
 */
static void
tg_summary(level, unitno, error, command)
	unchar unitno;
	char *error, *command;
{
	cmn_err(level, "tg%d: %s on command %s.\n",
		unitno, error, command);
		/*
		 *+ A SCSI command issued by the tg driver
		 *+ had an error termination.  A summary has
		 *+ been displayed containing the reason for
		 *+ the SCSI termination and the SCSI command 
		 *+ being executed.  For more information,
		 *+ refer to the specific error and command.
		 */
}

/* 
 * tg_dump()
 * 	Display request sense information to the 
 * 	console about the command that just terminated.
 * 	It consists of a summary followed by a
 * 	recoverable error count, the SCSI command that
 * 	caused the check condition, and the sense data.
 */
static void
tg_dump(infop)
	register struct tg_info *infop;
{
	char msgbuf[200];
	int i;
	struct scrsense *sense = (struct scrsense *)infop->cmd.sense;

	(void) scsi_rsense_string(&infop->cmd.scmd, msgbuf, 200, sense);
	cmn_err(CE_WARN, "tg%d: %s", infop->dev->info_id, msgbuf);
	/*
	 *+ The tg driver is displaying detailed information 
	 *+ pertaining to a problem that occurred when the
	 *+ indicated SCSI command was executed.  The string
	 *+ containing the message will be of the form:
	  *+ <sensekey> on command <scsi_command>; FILEMARK; EOM; ILI;
	  *+	Info bytes=0xXXXXXXXX; Additional length is xx bytes.
	 */

	if (tg_drives[infop->dev->drive_id].recerrs 
	&&  (i = (*tg_drives[infop->dev->drive_id].recerrs)(sense)) > 0)
		cmn_err(CE_CONT, "\tRecoverable error count=%d", i);

	scsi_cmd_dump(&infop->cmd.scmd, infop->cmd.dlen, infop->cmd.data, 
		infop->cmd.iov, infop->cmd.slen, infop->cmd.sense);
}

/*
 * tg_check_term()
 *	Wait for the previously initiated SCSI command
 *	to terminate and then verify its termination status 
 *	saved by tg_intr() and make appropriate state transitions. 
 *	Also, notify the user console of relevent conditions or
 *	the system console if configured to do so for SCSI
 *	check conditions or upon critical errors. 
 */
static void
tg_check_term(infop, dumpflags)
	register struct tg_info *infop;
	unchar dumpflags;
{
	register tgdev_t *dev = infop->dev;
	register struct scrsense *sense = (struct scrsense *)infop->cmd.sense;
	int command = infop->cmd.scmd.cmd[0];
	long temp;
	ulong utemp;

	/*
	 * Wait for the command to terminate.
	 */
	infop->resid = 0;
	(void)p_sema_v_lock(&infop->gensync, PRIBIO, &infop->lock);
	infop->spl = p_lock(&infop->lock, TG_SPL);

	switch (infop->status) {
	case SSTAT_OK:
		break; 			/* Nothing to report */
	case SSTAT_NODEV:
	case SSTAT_BUSERR:
		infop->sflags |= TGF_FAIL;
		tg_summary(CE_WARN, dev->info_id, 
			scsi_status_msg[infop->status], scsi_commands[command]);
		break;
	case SSTAT_UCHECK:
		/*
		 * Vendor unique check condition format.
		 * Attempt to recover residue, then fall
		 * through, reporting it and setting failure.  
		 */
		if (scsi_rs_lba((unchar *)infop->cmd.sense, &temp))
			infop->resid = temp;
	case SSTAT_NOTARGET:
	case SSTAT_BUSYTARGET:
	case SSTAT_BUSYLUN:
		infop->sflags |= TGF_FAIL;
		tg_summary(CE_TO_USER, dev->info_id, 
			scsi_status_msg[infop->status], scsi_commands[command]);
		break;
	case SSTAT_CCHECK:
	case SSTAT_DCHECK:
		/*
		 * A normal check condition occurred. 
		 * Analyze sense data for failures,
		 * filemarks, or end-of-media, and 
		 * recover the residual count, if valid.
		 */
		if (SCSI_RS_EOM_SET(sense)) infop->sflags |= TGF_FAIL | TGF_EOM;
		if (SCSI_RS_FILEMARK_SET(sense)) 
			infop->sflags |= TGF_FAIL | TGF_EOF;
		if (scsi_rs_info_bytes(sense, &utemp)) {
			if ((int)utemp < 0) {
				infop->resid = 0;
			} else {
				infop->resid = utemp;
			}
		}

		/*
		 * Additional actions and mode transitions are 
		 * based on key codes for the check condition. 
		 */
		switch (SCSI_RS_SENSE_KEY(sense)) {
		case RS_BLANK:
			if (command == SCSI_READ) 
				infop->sflags |= TGF_EOF;  
		case RS_UNITATTN: 	/* Media change or unit reset. */
			if (command != SCSI_TEST) 
				infop->sflags |= TGF_FAIL;  
		case RS_NOSENSE:
		case RS_RECERR:
			/* Report to console if configured to do so */
			if (tg_bconf[dev->info_id].cflags & dumpflags)
				tg_dump(infop);
			break;

		case RS_NOTRDY:
		case RS_PROTECT:
		case RS_OVFLOW:
			if ((tg_bconf[dev->info_id].cflags & dumpflags) == 0) {
				/* Note failure and notify user console */
				infop->sflags |= TGF_FAIL;
				tg_summary(CE_TO_USER, dev->info_id, 
					scsi_status_msg[infop->status], 
					scsi_commands[command]);
				break;
			}	/* Otherwise fall into default actions... */
		default:
			/* Note these failures and report to system console */
			infop->sflags |= TGF_FAIL;
			tg_dump(infop);	
			break;
		}
		break;
	default:
		cmn_err(CE_PANIC,
			"tg_check_term - Unrecognized SCSI command block completion status");
		/*
		 *+ The tg driver received an invalid SCSI termination
		 *+ code for a SCSI command.  The SCSI adapter or
		 *+ operating system are corrupt.
		 */
		/*NOTREACHED*/
	}
}
