view usr/src/uts/common/io/scsi/targets/st.c @ 13036:9c1800b4023e

6956564 st debug asserts at EOM 6726523 Amber Road NDMP: ndmp should not assume device links represent devices 6968119 Amber Road NDMP: ndmp status page should show correct WWN for tape devices
author Randall Ralphs <Randall.Ralphs@Sun.COM>
date Thu, 05 Aug 2010 15:20:03 -0600
parents 6be3b83e2860
children 6bec9720e054
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 *  Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
 */

/*
 * SCSI	 SCSA-compliant and not-so-DDI-compliant Tape Driver
 */

#if defined(lint) && !defined(DEBUG)
#define	DEBUG	1
#endif

#include <sys/modctl.h>
#include <sys/scsi/scsi.h>
#include <sys/mtio.h>
#include <sys/scsi/targets/stdef.h>
#include <sys/file.h>
#include <sys/kstat.h>
#include <sys/ddidmareq.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/byteorder.h>

#define	IOSP	KSTAT_IO_PTR(un->un_stats)
/*
 * stats maintained only for reads/writes as commands
 * like rewind etc skew the wait/busy times
 */
#define	IS_RW(bp) 	((bp)->b_bcount > 0)
#define	ST_DO_KSTATS(bp, kstat_function) \
	if ((bp != un->un_sbufp) && un->un_stats && IS_RW(bp)) { \
		kstat_function(IOSP); \
	}

#define	ST_DO_ERRSTATS(un, x)  \
	if (un->un_errstats) { \
		struct st_errstats *stp; \
		stp = (struct st_errstats *)un->un_errstats->ks_data; \
		stp->x.value.ul++; \
	}

#define	FILL_SCSI1_LUN(devp, pkt) 					\
	if ((devp)->sd_inq->inq_ansi == 0x1) {				\
		int _lun;						\
		_lun = ddi_prop_get_int(DDI_DEV_T_ANY, (devp)->sd_dev,	\
		    DDI_PROP_DONTPASS, SCSI_ADDR_PROP_LUN, 0);		\
		if (_lun > 0) {						\
			((union scsi_cdb *)(pkt)->pkt_cdbp)->scc_lun =	\
			    _lun;					\
		}							\
	}

/*
 * get an available contig mem header, cp.
 * when big_enough is true, we will return NULL, if no big enough
 * contig mem is found.
 * when big_enough is false, we will try to find cp containing big
 * enough contig mem. if not found, we will ruturn the last cp available.
 *
 * used by st_get_contig_mem()
 */
#define	ST_GET_CONTIG_MEM_HEAD(un, cp, len, big_enough) {		\
	struct contig_mem *tmp_cp = NULL;				\
	for ((cp) = (un)->un_contig_mem;				\
	    (cp) != NULL;						\
	    tmp_cp = (cp), (cp) = (cp)->cm_next) { 			\
		if (((cp)->cm_len >= (len)) || 				\
		    (!(big_enough) && ((cp)->cm_next == NULL))) { 	\
			if (tmp_cp == NULL) { 				\
				(un)->un_contig_mem = (cp)->cm_next; 	\
			} else { 					\
				tmp_cp->cm_next = (cp)->cm_next; 	\
			} 						\
			(cp)->cm_next = NULL; 				\
			(un)->un_contig_mem_available_num--; 		\
			break; 						\
		} 							\
	} 								\
}

#define	ST_NUM_MEMBERS(array)	(sizeof (array) / sizeof (array[0]))
#define	COPY_POS(dest, source) bcopy(source, dest, sizeof (tapepos_t))
#define	ISALNUM(byte) \
	(((byte) >= 'a' && (byte) <= 'z') || \
	((byte) >= 'A' && (byte) <= 'Z') || \
	((byte) >= '0' && (byte) <= '9'))

#define	ONE_K	1024

#define	MAX_SPACE_CNT(cnt) if (cnt >= 0) { \
		if (cnt > MIN(SP_CNT_MASK, INT32_MAX)) \
			return (EINVAL); \
	} else { \
		if (-(cnt) > MIN(SP_CNT_MASK, INT32_MAX)) \
			return (EINVAL); \
	} \

/*
 * Global External Data Definitions
 */
extern struct scsi_key_strings scsi_cmds[];
extern uchar_t	scsi_cdb_size[];

/*
 * Local Static Data
 */
static void *st_state;
static char *const st_label = "st";
static volatile int st_recov_sz = sizeof (recov_info);
static const char mp_misconf[] = {
	"St Tape is misconfigured, MPxIO enabled and "
	"tape-command-recovery-disable set in st.conf\n"
};

#ifdef	__x86
/*
 * We need to use below DMA attr to alloc physically contiguous
 * memory to do I/O in big block size
 */
static ddi_dma_attr_t st_contig_mem_dma_attr = {
	DMA_ATTR_V0,    /* version number */
	0x0,		/* lowest usable address */
	0xFFFFFFFFull,  /* high DMA address range */
	0xFFFFFFFFull,  /* DMA counter register */
	1,		/* DMA address alignment */
	1,		/* DMA burstsizes */
	1,		/* min effective DMA size */
	0xFFFFFFFFull,  /* max DMA xfer size */
	0xFFFFFFFFull,  /* segment boundary */
	1,		/* s/g list length */
	1,		/* granularity of device */
	0		/* DMA transfer flags */
};

static ddi_device_acc_attr_t st_acc_attr = {
	DDI_DEVICE_ATTR_V0,
	DDI_NEVERSWAP_ACC,
	DDI_STRICTORDER_ACC
};

/* set limitation for the number of contig_mem */
static int st_max_contig_mem_num = ST_MAX_CONTIG_MEM_NUM;
#endif

/*
 * Tunable parameters
 *
 * DISCLAIMER
 * ----------
 * These parameters are intended for use only in system testing; if you use
 * them in production systems, you do so at your own risk. Altering any
 * variable not listed below may cause unpredictable system behavior.
 *
 * st_check_media_time
 *
 *   Three second state check
 *
 * st_allow_large_xfer
 *
 *   Gated with ST_NO_RECSIZE_LIMIT
 *
 *   0 - Transfers larger than 64KB will not be allowed
 *       regardless of the setting of ST_NO_RECSIZE_LIMIT
 *   1 - Transfers larger than 64KB will be allowed
 *       if ST_NO_RECSIZE_LIMIT is TRUE for the drive
 *
 * st_report_soft_errors_on_close
 *
 *  Gated with ST_SOFT_ERROR_REPORTING
 *
 *  0 - Errors will not be reported on close regardless
 *      of the setting of ST_SOFT_ERROR_REPORTING
 *
 *  1 - Errors will be reported on close if
 *      ST_SOFT_ERROR_REPORTING is TRUE for the drive
 */
static int st_selection_retry_count = ST_SEL_RETRY_COUNT;
static int st_retry_count	= ST_RETRY_COUNT;

static int st_io_time		= ST_IO_TIME;
static int st_long_timeout_x	= ST_LONG_TIMEOUT_X;

static int st_space_time	= ST_SPACE_TIME;
static int st_long_space_time_x	= ST_LONG_SPACE_TIME_X;

static int st_error_level	= SCSI_ERR_RETRYABLE;
static int st_check_media_time	= 3000000;	/* 3 Second State Check */

static int st_max_throttle	= ST_MAX_THROTTLE;

static clock_t st_wait_cmds_complete = ST_WAIT_CMDS_COMPLETE;

static int st_allow_large_xfer = 1;
static int st_report_soft_errors_on_close = 1;

/*
 * End of tunable parameters list
 */



/*
 * Asynchronous I/O and persistent errors, refer to PSARC/1995/228
 *
 * Asynchronous I/O's main offering is that it is a non-blocking way to do
 * reads and writes.  The driver will queue up all the requests it gets and
 * have them ready to transport to the HBA.  Unfortunately, we cannot always
 * just ship the I/O requests to the HBA, as there errors and exceptions
 * that may happen when we don't want the HBA to continue.  Therein comes
 * the flush-on-errors capability.  If the HBA supports it, then st will
 * send in st_max_throttle I/O requests at the same time.
 *
 * Persistent errors : This was also reasonably simple.  In the interrupt
 * routines, if there was an error or exception (FM, LEOT, media error,
 * transport error), the persistent error bits are set and shuts everything
 * down, but setting the throttle to zero.  If we hit and exception in the
 * HBA, and flush-on-errors were set, we wait for all outstanding I/O's to
 * come back (with CMD_ABORTED), then flush all bp's in the wait queue with
 * the appropriate error, and this will preserve order. Of course, depending
 * on the exception we have to show a zero read or write before we show
 * errors back to the application.
 */

extern const int st_ndrivetypes;	/* defined in st_conf.c */
extern const struct st_drivetype st_drivetypes[];
extern const char st_conf_version[];

#ifdef STDEBUG
static int st_soft_error_report_debug = 0;
volatile int st_debug = 0;
static volatile dev_info_t *st_lastdev;
static kmutex_t st_debug_mutex;
#endif

#define	ST_MT02_NAME	"Emulex  MT02 QIC-11/24  "

static const struct vid_drivetype {
	char	*vid;
	char	type;
} st_vid_dt[] = {
	{"LTO-CVE ",	MT_LTO},
	{"QUANTUM ",    MT_ISDLT},
	{"SONY    ",    MT_ISAIT},
	{"STK     ",	MT_ISSTK9840}
};

static const struct driver_minor_data {
	char	*name;
	int	minor;
} st_minor_data[] = {
	/*
	 * The top 4 entries are for the default densities,
	 * don't alter their position.
	 */
	{"",	0},
	{"n",	MT_NOREWIND},
	{"b",	MT_BSD},
	{"bn",	MT_NOREWIND | MT_BSD},
	{"l",	MT_DENSITY1},
	{"m",	MT_DENSITY2},
	{"h",	MT_DENSITY3},
	{"c",	MT_DENSITY4},
	{"u",	MT_DENSITY4},
	{"ln",	MT_DENSITY1 | MT_NOREWIND},
	{"mn",	MT_DENSITY2 | MT_NOREWIND},
	{"hn",	MT_DENSITY3 | MT_NOREWIND},
	{"cn",	MT_DENSITY4 | MT_NOREWIND},
	{"un",	MT_DENSITY4 | MT_NOREWIND},
	{"lb",	MT_DENSITY1 | MT_BSD},
	{"mb",	MT_DENSITY2 | MT_BSD},
	{"hb",	MT_DENSITY3 | MT_BSD},
	{"cb",	MT_DENSITY4 | MT_BSD},
	{"ub",	MT_DENSITY4 | MT_BSD},
	{"lbn",	MT_DENSITY1 | MT_NOREWIND | MT_BSD},
	{"mbn",	MT_DENSITY2 | MT_NOREWIND | MT_BSD},
	{"hbn",	MT_DENSITY3 | MT_NOREWIND | MT_BSD},
	{"cbn",	MT_DENSITY4 | MT_NOREWIND | MT_BSD},
	{"ubn",	MT_DENSITY4 | MT_NOREWIND | MT_BSD}
};

/* strings used in many debug and warning messages */
static const char wr_str[]  = "write";
static const char rd_str[]  = "read";
static const char wrg_str[] = "writing";
static const char rdg_str[] = "reading";
static const char *space_strs[] = {
	"records",
	"filemarks",
	"sequential filemarks",
	"eod",
	"setmarks",
	"sequential setmarks",
	"Reserved",
	"Reserved"
};
static const char *load_strs[] = {
	"unload",		/* LD_UNLOAD		0 */
	"load",			/* LD_LOAD		1 */
	"retension",		/* LD_RETEN		2 */
	"load reten",		/* LD_LOAD | LD_RETEN	3 */
	"eod",			/* LD_EOT		4 */
	"load EOD",		/* LD_LOAD | LD_EOT	5 */
	"reten EOD",		/* LD_RETEN | LD_EOT	6 */
	"load reten EOD"	/* LD_LOAD|LD_RETEN|LD_EOT 7 */
	"hold",			/* LD_HOLD		8 */
	"load and hold"		/* LD_LOAD | LD_HOLD	9 */
};

static const char *errstatenames[] = {
	"COMMAND_DONE",
	"COMMAND_DONE_ERROR",
	"COMMAND_DONE_ERROR_RECOVERED",
	"QUE_COMMAND",
	"QUE_BUSY_COMMAND",
	"QUE_SENSE",
	"JUST_RETURN",
	"COMMAND_DONE_EACCES",
	"QUE_LAST_COMMAND",
	"COMMAND_TIMEOUT",
	"PATH_FAILED",
	"DEVICE_RESET",
	"DEVICE_TAMPER",
	"ATTEMPT_RETRY"
};

const char *bogusID = "Unknown Media ID";

/* default density offsets in the table above */
#define	DEF_BLANK	0
#define	DEF_NOREWIND	1
#define	DEF_BSD		2
#define	DEF_BSD_NR	3

/* Sense Key, ASC/ASCQ for which tape ejection is needed */

static struct tape_failure_code {
	uchar_t key;
	uchar_t add_code;
	uchar_t qual_code;
} st_tape_failure_code[] = {
	{ KEY_HARDWARE_ERROR, 0x15, 0x01},
	{ KEY_HARDWARE_ERROR, 0x44, 0x00},
	{ KEY_HARDWARE_ERROR, 0x53, 0x00},
	{ KEY_HARDWARE_ERROR, 0x53, 0x01},
	{ KEY_NOT_READY, 0x53, 0x00},
	{ 0xff}
};

/*  clean bit position and mask */

static struct cln_bit_position {
	ushort_t cln_bit_byte;
	uchar_t cln_bit_mask;
} st_cln_bit_position[] = {
	{ 21, 0x08},
	{ 70, 0xc0},
	{ 18, 0x81}  /* 80 bit indicates in bit mode, 1 bit clean light is on */
};

/*
 * architecture dependent allocation restrictions. For x86, we'll set
 * dma_attr_addr_hi to st_max_phys_addr and dma_attr_sgllen to
 * st_sgl_size during _init().
 */
#if defined(__sparc)
static ddi_dma_attr_t st_alloc_attr = {
	DMA_ATTR_V0,	/* version number */
	0x0,		/* lowest usable address */
	0xFFFFFFFFull,	/* high DMA address range */
	0xFFFFFFFFull,	/* DMA counter register */
	1,		/* DMA address alignment */
	1,		/* DMA burstsizes */
	1,		/* min effective DMA size */
	0xFFFFFFFFull,	/* max DMA xfer size */
	0xFFFFFFFFull,	/* segment boundary */
	1,		/* s/g list length */
	512,		/* granularity of device */
	0		/* DMA transfer flags */
};
#elif defined(__x86)
static ddi_dma_attr_t st_alloc_attr = {
	DMA_ATTR_V0,	/* version number */
	0x0,		/* lowest usable address */
	0x0,		/* high DMA address range [set in _init()] */
	0xFFFFull,	/* DMA counter register */
	512,		/* DMA address alignment */
	1,		/* DMA burstsizes */
	1,		/* min effective DMA size */
	0xFFFFFFFFull,	/* max DMA xfer size */
	0xFFFFFFFFull,  /* segment boundary */
	0,		/* s/g list length */
	512,		/* granularity of device [set in _init()] */
	0		/* DMA transfer flags */
};
uint64_t st_max_phys_addr = 0xFFFFFFFFull;
int st_sgl_size = 0xF;

#endif

/*
 * Configuration Data:
 *
 * Device driver ops vector
 */
static int st_aread(dev_t dev, struct aio_req *aio, cred_t *cred_p);
static int st_awrite(dev_t dev, struct aio_req *aio, cred_t *cred_p);
static int st_read(dev_t  dev,  struct   uio   *uio_p,   cred_t *cred_p);
static int st_write(dev_t  dev,  struct  uio   *uio_p,   cred_t *cred_p);
static int st_open(dev_t  *devp,  int  flag,  int  otyp,  cred_t *cred_p);
static int st_close(dev_t  dev,  int  flag,  int  otyp,  cred_t *cred_p);
static int st_strategy(struct buf *bp);
static int st_queued_strategy(buf_t *bp);
static int st_ioctl(dev_t dev, int cmd, intptr_t arg, int  flag,
	cred_t *cred_p, int *rval_p);
extern int nulldev(), nodev();

static struct cb_ops st_cb_ops = {
	st_open,		/* open */
	st_close,		/* close */
	st_queued_strategy,	/* strategy Not Block device but async checks */
	nodev,			/* print */
	nodev,			/* dump */
	st_read,		/* read */
	st_write,		/* write */
	st_ioctl,		/* ioctl */
	nodev,			/* devmap */
	nodev,			/* mmap */
	nodev,			/* segmap */
	nochpoll,		/* poll */
	ddi_prop_op,		/* cb_prop_op */
	0,			/* streamtab  */
	D_64BIT | D_MP | D_NEW | D_HOTPLUG |
	D_OPEN_RETURNS_EINTR,	/* cb_flag */
	CB_REV,			/* cb_rev */
	st_aread, 		/* async I/O read entry point */
	st_awrite		/* async I/O write entry point */

};

static int st_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
		void **result);
static int st_probe(dev_info_t *dev);
static int st_attach(dev_info_t *dev, ddi_attach_cmd_t cmd);
static int st_detach(dev_info_t *dev, ddi_detach_cmd_t cmd);

static struct dev_ops st_ops = {
	DEVO_REV,		/* devo_rev, */
	0,			/* refcnt  */
	st_info,		/* info */
	nulldev,		/* identify */
	st_probe,		/* probe */
	st_attach,		/* attach */
	st_detach,		/* detach */
	nodev,			/* reset */
	&st_cb_ops,		/* driver operations */
	(struct bus_ops *)0,	/* bus operations */
	nulldev,		/* power */
	ddi_quiesce_not_needed,	/* devo_quiesce */
};

/*
 * Local Function Declarations
 */
static char *st_print_scsi_cmd(char cmd);
static void st_print_cdb(dev_info_t *dip, char *label, uint_t level,
    char *title, char *cdb);
static void st_clean_print(dev_info_t *dev, char *label, uint_t level,
    char *title, char *data, int len);
static int st_doattach(struct scsi_device *devp, int (*canwait)());
static void st_known_tape_type(struct scsi_tape *un);
static int st_get_conf_from_st_dot_conf(struct scsi_tape *, char *,
    struct st_drivetype *);
static int st_get_conf_from_st_conf_dot_c(struct scsi_tape *, char *,
    struct st_drivetype *);
static int st_get_conf_from_tape_drive(struct scsi_tape *, char *,
    struct st_drivetype *);
static int st_get_densities_from_tape_drive(struct scsi_tape *,
    struct st_drivetype *);
static int st_get_timeout_values_from_tape_drive(struct scsi_tape *,
    struct st_drivetype *);
static int st_get_timeouts_value(struct scsi_tape *, uchar_t, ushort_t *,
    ushort_t);
static int st_get_default_conf(struct scsi_tape *, char *,
    struct st_drivetype *);
static int st_rw(dev_t dev, struct uio *uio, int flag);
static int st_arw(dev_t dev, struct aio_req *aio, int flag);
static int st_find_eod(struct scsi_tape *un);
static int st_check_density_or_wfm(dev_t dev, int wfm, int mode, int stepflag);
static int st_uscsi_cmd(struct scsi_tape *un, struct uscsi_cmd *, int flag);
static int st_mtioctop(struct scsi_tape *un, intptr_t arg, int flag);
static int st_mtiocltop(struct scsi_tape *un, intptr_t arg, int flag);
static int st_do_mtioctop(struct scsi_tape *un, struct mtlop *mtop);
static void st_start(struct scsi_tape *un);
static int st_handle_start_busy(struct scsi_tape *un, struct buf *bp,
    clock_t timeout_interval, int queued);
static int st_handle_intr_busy(struct scsi_tape *un, struct buf *bp,
    clock_t timeout_interval);
static int st_handle_intr_retry_lcmd(struct scsi_tape *un, struct buf *bp);
static void st_done_and_mutex_exit(struct scsi_tape *un, struct buf *bp);
static void st_init(struct scsi_tape *un);
static void st_make_cmd(struct scsi_tape *un, struct buf *bp,
    int (*func)(caddr_t));
static void st_make_uscsi_cmd(struct scsi_tape *, struct uscsi_cmd *,
    struct buf *bp, int (*func)(caddr_t));
static void st_intr(struct scsi_pkt *pkt);
static void st_set_state(struct scsi_tape *un, buf_t *bp);
static void st_test_append(struct buf *bp);
static int st_runout(caddr_t);
static int st_cmd(struct scsi_tape *un, int com, int64_t count, int wait);
static int st_setup_cmd(struct scsi_tape *un, buf_t *bp, int com,
    int64_t count);
static int st_set_compression(struct scsi_tape *un);
static int st_write_fm(dev_t dev, int wfm);
static int st_determine_generic(struct scsi_tape *un);
static int st_determine_density(struct scsi_tape *un, int rw);
static int st_get_density(struct scsi_tape *un);
static int st_set_density(struct scsi_tape *un);
static int st_loadtape(struct scsi_tape *un);
static int st_modesense(struct scsi_tape *un);
static int st_modeselect(struct scsi_tape *un);
static errstate st_handle_incomplete(struct scsi_tape *un, struct buf *bp);
static int st_wrongtapetype(struct scsi_tape *un);
static errstate st_check_error(struct scsi_tape *un, struct scsi_pkt *pkt);
static errstate st_handle_sense(struct scsi_tape *un, struct buf *bp,
    tapepos_t *);
static errstate st_handle_autosense(struct scsi_tape *un, struct buf *bp,
    tapepos_t *);
static int st_get_error_entry(struct scsi_tape *un, intptr_t arg, int flag);
static void st_update_error_stack(struct scsi_tape *un, struct scsi_pkt *pkt,
    struct scsi_arq_status *cmd);
static void st_empty_error_stack(struct scsi_tape *un);
static errstate st_decode_sense(struct scsi_tape *un, struct buf *bp, int amt,
    struct scsi_arq_status *, tapepos_t *);
static int st_report_soft_errors(dev_t dev, int flag);
static void st_delayed_cv_broadcast(void *arg);
static int st_check_media(dev_t dev, enum mtio_state state);
static int st_media_watch_cb(caddr_t arg, struct scsi_watch_result *resultp);
static void st_intr_restart(void *arg);
static void st_start_restart(void *arg);
static int st_gen_mode_sense(struct scsi_tape *un, ubufunc_t ubf, int page,
    struct seq_mode *page_data, int page_size);
static int st_change_block_size(struct scsi_tape *un, uint32_t nblksz);
static int st_gen_mode_select(struct scsi_tape *un, ubufunc_t ubf,
    struct seq_mode *page_data, int page_size);
static int st_read_block_limits(struct scsi_tape *un,
    struct read_blklim *read_blk);
static int st_report_density_support(struct scsi_tape *un,
    uchar_t *density_data, size_t buflen);
static int st_report_supported_operation(struct scsi_tape *un,
    uchar_t *oper_data, uchar_t option_code, ushort_t service_action);
static int st_tape_init(struct scsi_tape *un);
static void st_flush(struct scsi_tape *un);
static void st_set_pe_errno(struct scsi_tape *un);
static void st_hba_unflush(struct scsi_tape *un);
static void st_turn_pe_on(struct scsi_tape *un);
static void st_turn_pe_off(struct scsi_tape *un);
static void st_set_pe_flag(struct scsi_tape *un);
static void st_clear_pe(struct scsi_tape *un);
static void st_wait_for_io(struct scsi_tape *un);
static int st_set_devconfig_page(struct scsi_tape *un, int compression_on);
static int st_set_datacomp_page(struct scsi_tape *un, int compression_on);
static int st_reserve_release(struct scsi_tape *un, int command, ubufunc_t ubf);
static int st_check_cdb_for_need_to_reserve(struct scsi_tape *un, uchar_t *cdb);
static int st_check_cmd_for_need_to_reserve(struct scsi_tape *un, uchar_t cmd,
    int count);
static int st_take_ownership(struct scsi_tape *un, ubufunc_t ubf);
static int st_check_asc_ascq(struct scsi_tape *un);
static int st_check_clean_bit(struct scsi_tape *un);
static int st_check_alert_flags(struct scsi_tape *un);
static int st_check_sequential_clean_bit(struct scsi_tape *un);
static int st_check_sense_clean_bit(struct scsi_tape *un);
static int st_clear_unit_attentions(dev_t dev_instance, int max_trys);
static void st_calculate_timeouts(struct scsi_tape *un);
static writablity st_is_drive_worm(struct scsi_tape *un);
static int st_read_attributes(struct scsi_tape *un, uint16_t attribute,
    void *buf, size_t size, ubufunc_t bufunc);
static int st_get_special_inquiry(struct scsi_tape *un, uchar_t size,
    caddr_t dest, uchar_t page);
static int st_update_block_pos(struct scsi_tape *un, bufunc_t bf,
    int post_space);
static int st_interpret_read_pos(struct scsi_tape const *un, tapepos_t *dest,
    read_p_types type, size_t data_sz, const caddr_t responce, int post_space);
static int st_get_read_pos(struct scsi_tape *un, buf_t *bp);
static int st_logical_block_locate(struct scsi_tape *un, ubufunc_t ubf,
    tapepos_t *pos, uint64_t lblk, uchar_t partition);
static int st_mtfsf_ioctl(struct scsi_tape *un, int64_t files);
static int st_mtfsr_ioctl(struct scsi_tape *un, int64_t count);
static int st_mtbsf_ioctl(struct scsi_tape *un, int64_t files);
static int st_mtnbsf_ioctl(struct scsi_tape *un, int64_t count);
static int st_mtbsr_ioctl(struct scsi_tape *un, int64_t num);
static int st_mtfsfm_ioctl(struct scsi_tape *un, int64_t cnt);
static int st_mtbsfm_ioctl(struct scsi_tape *un, int64_t cnt);
static int st_backward_space_files(struct scsi_tape *un, int64_t count,
    int infront);
static int st_forward_space_files(struct scsi_tape *un, int64_t files);
static int st_scenic_route_to_begining_of_file(struct scsi_tape *un,
    int32_t fileno);
static int st_space_to_begining_of_file(struct scsi_tape *un);
static int st_space_records(struct scsi_tape *un, int64_t records);
static int st_get_media_identification(struct scsi_tape *un, ubufunc_t bufunc);
static errstate st_command_recovery(struct scsi_tape *un, struct scsi_pkt *pkt,
    errstate onentry);
static void st_recover(void *arg);
static void st_recov_cb(struct scsi_pkt *pkt);
static int st_rcmd(struct scsi_tape *un, int com, int64_t count, int wait);
static int st_uscsi_rcmd(struct scsi_tape *un, struct uscsi_cmd *ucmd,
    int flag);
static void st_add_recovery_info_to_pkt(struct scsi_tape *un, buf_t *bp,
    struct scsi_pkt *cmd);
static int st_check_mode_for_change(struct scsi_tape *un, ubufunc_t ubf);
static int st_test_path_to_device(struct scsi_tape *un);
static int st_recovery_read_pos(struct scsi_tape *un, read_p_types type,
    read_pos_data_t *raw);
static int st_recovery_get_position(struct scsi_tape *un, tapepos_t *read,
    read_pos_data_t *raw);
static int st_compare_expected_position(struct scsi_tape *un, st_err_info *ei,
    cmd_attribute const * cmd_att, tapepos_t *read);
static errstate st_recover_reissue_pkt(struct scsi_tape *us,
    struct scsi_pkt *pkt);
static int st_transport(struct scsi_tape *un, struct scsi_pkt *pkt);
static buf_t *st_remove_from_queue(buf_t **head, buf_t **tail, buf_t *bp);
static void st_add_to_queue(buf_t **head, buf_t **tail, buf_t *end, buf_t *bp);
static int st_reset(struct scsi_tape *un, int reset_type);
static void st_reset_notification(caddr_t arg);
static const cmd_attribute *st_lookup_cmd_attribute(unsigned char cmd);

static int st_set_target_TLR_mode(struct scsi_tape *un, ubufunc_t ubf);
static int st_make_sure_mode_data_is_correct(struct scsi_tape *un,
    ubufunc_t ubf);

#ifdef	__x86
/*
 * routines for I/O in big block size
 */
static void st_release_contig_mem(struct scsi_tape *un, struct contig_mem *cp);
static struct contig_mem *st_get_contig_mem(struct scsi_tape *un, size_t len,
    int alloc_flags);
static int st_bigblk_xfer_done(struct buf *bp);
static struct buf *st_get_bigblk_bp(struct buf *bp);
#endif
static void st_print_position(dev_info_t *dev, char *label, uint_t level,
    const char *comment, tapepos_t *pos);

/*
 * error statistics create/update functions
 */
static int st_create_errstats(struct scsi_tape *, int);
static int st_validate_tapemarks(struct scsi_tape *un, ubufunc_t ubf,
    tapepos_t *pos);

#ifdef STDEBUG
static void st_debug_cmds(struct scsi_tape *un, int com, int count, int wait);
#endif /* STDEBUG */
static char *st_dev_name(dev_t dev);

#if !defined(lint)
_NOTE(SCHEME_PROTECTS_DATA("unique per pkt",
    scsi_pkt buf uio scsi_cdb uscsi_cmd))
_NOTE(SCHEME_PROTECTS_DATA("unique per pkt", scsi_extended_sense scsi_status))
_NOTE(SCHEME_PROTECTS_DATA("unique per pkt", recov_info))
_NOTE(SCHEME_PROTECTS_DATA("stable data", scsi_device))
_NOTE(DATA_READABLE_WITHOUT_LOCK(st_drivetype scsi_address))
#endif

/*
 * autoconfiguration routines.
 */
char _depends_on[] = "misc/scsi";

static struct modldrv modldrv = {
	&mod_driverops,		/* Type of module. This one is a driver */
	"SCSI tape Driver", 	/* Name of the module. */
	&st_ops			/* driver ops */
};

static struct modlinkage modlinkage = {
	MODREV_1, &modldrv, NULL
};

/*
 * Notes on Post Reset Behavior in the tape driver:
 *
 * When the tape drive is opened, the driver  attempts  to make sure that
 * the tape head is positioned exactly where it was left when it was last
 * closed  provided  the  medium  is not  changed.  If the tape  drive is
 * opened in O_NDELAY mode, the repositioning  (if necessary for any loss
 * of position due to reset) will happen when the first tape operation or
 * I/O occurs.  The repositioning (if required) may not be possible under
 * certain situations such as when the device firmware not able to report
 * the medium  change in the REQUEST  SENSE data  because of a reset or a
 * misbehaving  bus  not  allowing  the  reposition  to  happen.  In such
 * extraordinary  situations, where the driver fails to position the head
 * at its  original  position,  it will fail the open the first  time, to
 * save the applications from overwriting the data.  All further attempts
 * to open the tape device will result in the driver  attempting  to load
 * the  tape at BOT  (beginning  of  tape).  Also a  warning  message  to
 * indicate  that further  attempts to open the tape device may result in
 * the tape being  loaded at BOT will be printed on the  console.  If the
 * tape  device is opened  in  O_NDELAY  mode,  failure  to  restore  the
 * original tape head  position,  will result in the failure of the first
 * tape  operation  or I/O,  Further,  the  driver  will  invalidate  its
 * internal tape position  which will  necessitate  the  applications  to
 * validate the position by using either a tape  positioning  ioctl (such
 * as MTREW) or closing and reopening the tape device.
 *
 */

int
_init(void)
{
	int e;

	if (((e = ddi_soft_state_init(&st_state,
	    sizeof (struct scsi_tape), ST_MAXUNIT)) != 0)) {
		return (e);
	}

	if ((e = mod_install(&modlinkage)) != 0) {
		ddi_soft_state_fini(&st_state);
	} else {
#ifdef STDEBUG
		mutex_init(&st_debug_mutex, NULL, MUTEX_DRIVER, NULL);
#endif

#if defined(__x86)
		/* set the max physical address for iob allocs on x86 */
		st_alloc_attr.dma_attr_addr_hi = st_max_phys_addr;

		/*
		 * set the sgllen for iob allocs on x86. If this is set less
		 * than the number of pages the buffer will take
		 * (taking into account alignment), it would force the
		 * allocator to try and allocate contiguous pages.
		 */
		st_alloc_attr.dma_attr_sgllen = st_sgl_size;
#endif
	}

	return (e);
}

int
_fini(void)
{
	int e;

	if ((e = mod_remove(&modlinkage)) != 0) {
		return (e);
	}

#ifdef STDEBUG
	mutex_destroy(&st_debug_mutex);
#endif

	ddi_soft_state_fini(&st_state);

	return (e);
}

int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}


static int
st_probe(dev_info_t *devi)
{
	int instance;
	struct scsi_device *devp;
	int rval;

#if !defined(__sparc)
	char    *tape_prop;
	int	tape_prop_len;
#endif

	ST_ENTR(devi, st_probe);

	/* If self identifying device */
	if (ddi_dev_is_sid(devi) == DDI_SUCCESS) {
		return (DDI_PROBE_DONTCARE);
	}

#if !defined(__sparc)
	/*
	 * Since some x86 HBAs have devnodes that look like SCSI as
	 * far as we can tell but aren't really SCSI (DADK, like mlx)
	 * we check for the presence of the "tape" property.
	 */
	if (ddi_prop_op(DDI_DEV_T_NONE, devi, PROP_LEN_AND_VAL_ALLOC,
	    DDI_PROP_CANSLEEP, "tape",
	    (caddr_t)&tape_prop, &tape_prop_len) != DDI_PROP_SUCCESS) {
		return (DDI_PROBE_FAILURE);
	}
	if (strncmp(tape_prop, "sctp", tape_prop_len) != 0) {
		kmem_free(tape_prop, tape_prop_len);
		return (DDI_PROBE_FAILURE);
	}
	kmem_free(tape_prop, tape_prop_len);
#endif

	devp = ddi_get_driver_private(devi);
	instance = ddi_get_instance(devi);

	if (ddi_get_soft_state(st_state, instance) != NULL) {
		return (DDI_PROBE_PARTIAL);
	}


	/*
	 * Turn around and call probe routine to see whether
	 * we actually have a tape at this SCSI nexus.
	 */
	if (scsi_probe(devp, NULL_FUNC) == SCSIPROBE_EXISTS) {

		/*
		 * In checking the whole inq_dtype byte we are looking at both
		 * the Peripheral Qualifier and the Peripheral Device Type.
		 * For this driver we are only interested in sequential devices
		 * that are connected or capable if connecting to this logical
		 * unit.
		 */
		if (devp->sd_inq->inq_dtype ==
		    (DTYPE_SEQUENTIAL | DPQ_POSSIBLE)) {
			ST_DEBUG6(devi, st_label, SCSI_DEBUG,
			    "probe exists\n");
			rval = DDI_PROBE_SUCCESS;
		} else {
			rval = DDI_PROBE_FAILURE;
		}
	} else {
		ST_DEBUG6(devi, st_label, SCSI_DEBUG,
		    "probe failure: nothing there\n");
		rval = DDI_PROBE_FAILURE;
	}
	scsi_unprobe(devp);
	return (rval);
}

static int
st_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	int 	instance;
	int	wide;
	int 	dev_instance;
	int	ret_status;
	struct	scsi_device *devp;
	int	node_ix;
	struct	scsi_tape *un;

	ST_ENTR(devi, st_attach);

	devp = ddi_get_driver_private(devi);
	instance = ddi_get_instance(devi);

	switch (cmd) {
		case DDI_ATTACH:
			if (ddi_getprop(DDI_DEV_T_ANY, devi, DDI_PROP_DONTPASS,
			    "tape-command-recovery-disable", 0) != 0) {
				st_recov_sz = sizeof (pkt_info);
			}
			if (st_doattach(devp, SLEEP_FUNC) == DDI_FAILURE) {
				return (DDI_FAILURE);
			}
			break;
		case DDI_RESUME:
			/*
			 * Suspend/Resume
			 *
			 * When the driver suspended, there might be
			 * outstanding cmds and therefore we need to
			 * reset the suspended flag and resume the scsi
			 * watch thread and restart commands and timeouts
			 */

			if (!(un = ddi_get_soft_state(st_state, instance))) {
				return (DDI_FAILURE);
			}
			dev_instance = ((un->un_dev == 0) ? MTMINOR(instance) :
			    un->un_dev);

			mutex_enter(ST_MUTEX);

			un->un_throttle = un->un_max_throttle;
			un->un_tids_at_suspend = 0;
			un->un_pwr_mgmt = ST_PWR_NORMAL;

			if (un->un_swr_token) {
				scsi_watch_resume(un->un_swr_token);
			}

			/*
			 * Restart timeouts
			 */
			if ((un->un_tids_at_suspend & ST_DELAY_TID) != 0) {
				mutex_exit(ST_MUTEX);
				un->un_delay_tid = timeout(
				    st_delayed_cv_broadcast, un,
				    drv_usectohz((clock_t)
				    MEDIA_ACCESS_DELAY));
				mutex_enter(ST_MUTEX);
			}

			if (un->un_tids_at_suspend & ST_HIB_TID) {
				mutex_exit(ST_MUTEX);
				un->un_hib_tid = timeout(st_intr_restart, un,
				    ST_STATUS_BUSY_TIMEOUT);
				mutex_enter(ST_MUTEX);
			}

			ret_status = st_clear_unit_attentions(dev_instance, 5);

			/*
			 * now check if we need to restore the tape position
			 */
			if ((un->un_suspend_pos.pmode != invalid) &&
			    ((un->un_suspend_pos.fileno > 0) ||
			    (un->un_suspend_pos.blkno > 0)) ||
			    (un->un_suspend_pos.lgclblkno > 0)) {
				if (ret_status != 0) {
					/*
					 * tape didn't get good TUR
					 * just print out error messages
					 */
					scsi_log(ST_DEVINFO, st_label, CE_WARN,
					    "st_attach-RESUME: tape failure "
					    " tape position will be lost");
				} else {
					/* this prints errors */
					(void) st_validate_tapemarks(un,
					    st_uscsi_cmd, &un->un_suspend_pos);
				}
				/*
				 * there are no retries, if there is an error
				 * we don't know if the tape has changed
				 */
				un->un_suspend_pos.pmode = invalid;
			}

			/* now we are ready to start up any queued I/Os */
			if (un->un_ncmds || un->un_quef) {
				st_start(un);
			}

			cv_broadcast(&un->un_suspend_cv);
			mutex_exit(ST_MUTEX);
			return (DDI_SUCCESS);

		default:
			return (DDI_FAILURE);
	}

	un = ddi_get_soft_state(st_state, instance);

	ST_DEBUG(devi, st_label, SCSI_DEBUG,
	    "st_attach: instance=%x\n", instance);

	/*
	 * Add a zero-length attribute to tell the world we support
	 * kernel ioctls (for layered drivers)
	 */
	(void) ddi_prop_create(DDI_DEV_T_NONE, devi, DDI_PROP_CANSLEEP,
	    DDI_KERNEL_IOCTL, NULL, 0);

	ddi_report_dev((dev_info_t *)devi);

	/*
	 * If it's a SCSI-2 tape drive which supports wide,
	 * tell the host adapter to use wide.
	 */
	wide = ((devp->sd_inq->inq_rdf == RDF_SCSI2) &&
	    (devp->sd_inq->inq_wbus16 || devp->sd_inq->inq_wbus32)) ?  1 : 0;

	if (scsi_ifsetcap(ROUTE, "wide-xfer", wide, 1) == 1) {
		ST_DEBUG(devi, st_label, SCSI_DEBUG,
		    "Wide Transfer %s\n", wide ? "enabled" : "disabled");
	}

	/*
	 * enable autorequest sense; keep the rq packet around in case
	 * the autorequest sense fails because of a busy condition
	 * do a getcap first in case the capability is not variable
	 */
	if (scsi_ifgetcap(ROUTE, "auto-rqsense", 1) == 1) {
		un->un_arq_enabled = 1;
	} else {
		un->un_arq_enabled =
		    ((scsi_ifsetcap(ROUTE, "auto-rqsense", 1, 1) == 1) ? 1 : 0);
	}

	ST_DEBUG(devi, st_label, SCSI_DEBUG, "auto request sense %s\n",
	    (un->un_arq_enabled ? "enabled" : "disabled"));

	un->un_untagged_qing =
	    (scsi_ifgetcap(ROUTE, "untagged-qing", 0) == 1);

	/*
	 * XXX - This is just for 2.6.  to tell users that write buffering
	 *	has gone away.
	 */
	if (un->un_arq_enabled && un->un_untagged_qing) {
		if (ddi_getprop(DDI_DEV_T_ANY, devi, DDI_PROP_DONTPASS,
		    "tape-driver-buffering", 0) != 0) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "Write Data Buffering has been depricated. Your "
			    "applications should continue to work normally.\n"
			    " But, they should  ported to use Asynchronous "
			    " I/O\n"
			    " For more information, read about "
			    " tape-driver-buffering "
			    "property in the st(7d) man page\n");
		}
	}

	un->un_max_throttle = un->un_throttle = un->un_last_throttle = 1;
	un->un_flush_on_errors = 0;
	un->un_mkr_pkt = (struct scsi_pkt *)NULL;

	ST_DEBUG(devi, st_label, SCSI_DEBUG,
	    "throttle=%x, max_throttle = %x\n",
	    un->un_throttle, un->un_max_throttle);

	/* initialize persistent errors to nil */
	un->un_persistence = 0;
	un->un_persist_errors = 0;

	/*
	 * Get dma-max from HBA driver. If it is not defined, use 64k
	 */
	un->un_maxdma	= scsi_ifgetcap(&devp->sd_address, "dma-max", 1);
	if (un->un_maxdma == -1) {
		ST_DEBUG(devi, st_label, SCSI_DEBUG,
		    "Received a value that looked like -1. Using 64k maxdma");
		un->un_maxdma = (64 * ONE_K);
	}

#ifdef	__x86
	/*
	 * for x86, the device may be able to DMA more than the system will
	 * allow under some circumstances. We need account for both the HBA's
	 * and system's contraints.
	 *
	 * Get the maximum DMA under worse case conditions. e.g. looking at the
	 * device constraints, the max copy buffer size, and the worse case
	 * fragmentation. NOTE: this may differ from dma-max since dma-max
	 * doesn't take the worse case framentation into account.
	 *
	 * e.g. a device may be able to DMA 16MBytes, but can only DMA 1MByte
	 * if none of the pages are contiguous. Keeping track of both of these
	 * values allows us to support larger tape block sizes on some devices.
	 */
	un->un_maxdma_arch = scsi_ifgetcap(&devp->sd_address, "dma-max-arch",
	    1);

	/*
	 * If the dma-max-arch capability is not implemented, or the value
	 * comes back higher than what was reported in dma-max, use dma-max.
	 */
	if ((un->un_maxdma_arch == -1) ||
	    ((uint_t)un->un_maxdma < (uint_t)un->un_maxdma_arch)) {
		un->un_maxdma_arch = un->un_maxdma;
	}
#endif

	/*
	 * Get the max allowable cdb size
	 */
	un->un_max_cdb_sz =
	    scsi_ifgetcap(&devp->sd_address, "max-cdb-length", 1);
	if (un->un_max_cdb_sz < CDB_GROUP0) {
		ST_DEBUG(devi, st_label, SCSI_DEBUG,
		    "HBA reported max-cdb-length as %d\n", un->un_max_cdb_sz);
		un->un_max_cdb_sz = CDB_GROUP4; /* optimistic default */
	}

	if (strcmp(ddi_driver_name(ddi_get_parent(ST_DEVINFO)), "scsi_vhci")) {
		un->un_multipath = 0;
	} else {
		un->un_multipath = 1;
	}

	un->un_maxbsize = MAXBSIZE_UNKNOWN;

	un->un_mediastate = MTIO_NONE;
	un->un_HeadClean  = TAPE_ALERT_SUPPORT_UNKNOWN;

	/*
	 * initialize kstats
	 */
	un->un_stats = kstat_create("st", instance, NULL, "tape",
	    KSTAT_TYPE_IO, 1, KSTAT_FLAG_PERSISTENT);
	if (un->un_stats) {
		un->un_stats->ks_lock = ST_MUTEX;
		kstat_install(un->un_stats);
	}
	(void) st_create_errstats(un, instance);

	/*
	 * find the drive type for this target
	 */
	mutex_enter(ST_MUTEX);
	un->un_dev = MTMINOR(instance);
	st_known_tape_type(un);
	un->un_dev = 0;
	mutex_exit(ST_MUTEX);

	for (node_ix = 0; node_ix < ST_NUM_MEMBERS(st_minor_data); node_ix++) {
		int minor;
		char *name;

		name  = st_minor_data[node_ix].name;
		minor = st_minor_data[node_ix].minor;

		/*
		 * For default devices set the density to the
		 * preferred default density for this device.
		 */
		if (node_ix <= DEF_BSD_NR) {
			minor |= un->un_dp->default_density;
		}
		minor |= MTMINOR(instance);

		if (ddi_create_minor_node(devi, name, S_IFCHR, minor,
		    DDI_NT_TAPE, NULL) == DDI_SUCCESS) {
			continue;
		}

		ddi_remove_minor_node(devi, NULL);

		(void) scsi_reset_notify(ROUTE, SCSI_RESET_CANCEL,
		    st_reset_notification, (caddr_t)un);
		cv_destroy(&un->un_clscv);
		cv_destroy(&un->un_sbuf_cv);
		cv_destroy(&un->un_queue_cv);
		cv_destroy(&un->un_state_cv);
#ifdef	__x86
		cv_destroy(&un->un_contig_mem_cv);
#endif
		cv_destroy(&un->un_suspend_cv);
		cv_destroy(&un->un_tape_busy_cv);
		cv_destroy(&un->un_recov_buf_cv);
		if (un->un_recov_taskq) {
			ddi_taskq_destroy(un->un_recov_taskq);
		}
		if (un->un_sbufp) {
			freerbuf(un->un_sbufp);
		}
		if (un->un_recov_buf) {
			freerbuf(un->un_recov_buf);
		}
		if (un->un_uscsi_rqs_buf) {
			kmem_free(un->un_uscsi_rqs_buf, SENSE_LENGTH);
		}
		if (un->un_mspl) {
			i_ddi_mem_free((caddr_t)un->un_mspl, NULL);
		}
		if (un->un_dp_size) {
			kmem_free(un->un_dp, un->un_dp_size);
		}
		if (un->un_state) {
			kstat_delete(un->un_stats);
		}
		if (un->un_errstats) {
			kstat_delete(un->un_errstats);
		}

		scsi_destroy_pkt(un->un_rqs);
		scsi_free_consistent_buf(un->un_rqs_bp);
		ddi_soft_state_free(st_state, instance);
		devp->sd_private = NULL;
		devp->sd_sense = NULL;

		ddi_prop_remove_all(devi);
		return (DDI_FAILURE);
	}

	return (DDI_SUCCESS);
}

/*
 * st_detach:
 *
 * we allow a detach if and only if:
 *	- no tape is currently inserted
 *	- tape position is at BOT or unknown
 *		(if it is not at BOT then a no rewind
 *		device was opened and we have to preserve state)
 *	- it must be in a closed state : no timeouts or scsi_watch requests
 *		will exist if it is closed, so we don't need to check for
 *		them here.
 */
/*ARGSUSED*/
static int
st_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	int instance;
	int result;
	struct scsi_device *devp;
	struct scsi_tape *un;
	clock_t wait_cmds_complete;

	ST_ENTR(devi, st_detach);

	instance = ddi_get_instance(devi);

	if (!(un = ddi_get_soft_state(st_state, instance))) {
		return (DDI_FAILURE);
	}

	mutex_enter(ST_MUTEX);

	/*
	 * Clear error entry stack
	 */
	st_empty_error_stack(un);

	mutex_exit(ST_MUTEX);

	switch (cmd) {

	case DDI_DETACH:
		/*
		 * Undo what we did in st_attach & st_doattach,
		 * freeing resources and removing things we installed.
		 * The system framework guarantees we are not active
		 * with this devinfo node in any other entry points at
		 * this time.
		 */

		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_detach: instance=%x, un=%p\n", instance,
		    (void *)un);

		if (((un->un_dp->options & ST_UNLOADABLE) == 0) ||
		    ((un->un_rsvd_status & ST_APPLICATION_RESERVATIONS) != 0) ||
		    (un->un_ncmds != 0) || (un->un_quef != NULL) ||
		    (un->un_state != ST_STATE_CLOSED)) {
			/*
			 * we cannot unload some targets because the
			 * inquiry returns junk unless immediately
			 * after a reset
			 */
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "cannot unload instance %x\n", instance);
			un->un_unit_attention_flags |= 4;
			return (DDI_FAILURE);
		}

		/*
		 * if the tape has been removed then we may unload;
		 * do a test unit ready and if it returns NOT READY
		 * then we assume that it is safe to unload.
		 * as a side effect, pmode may be set to invalid if the
		 * the test unit ready fails;
		 * also un_state may be set to non-closed, so reset it
		 */
		if ((un->un_dev) &&		/* Been opened since attach */
		    ((un->un_pos.pmode == legacy) &&
		    (un->un_pos.fileno > 0) ||	/* Known position not rewound */
		    (un->un_pos.blkno != 0)) ||	/* Or within first file */
		    ((un->un_pos.pmode == logical) &&
		    (un->un_pos.lgclblkno > 0))) {
			mutex_enter(ST_MUTEX);
			/*
			 * Send Test Unit Ready in the hopes that if
			 * the drive is not in the state we think it is.
			 * And the state will be changed so it can be detached.
			 * If the command fails to reach the device and
			 * the drive was not rewound or unloaded we want
			 * to fail the detach till a user command fails
			 * where after the detach will succead.
			 */
			result = st_cmd(un, SCMD_TEST_UNIT_READY, 0, SYNC_CMD);
			/*
			 * After TUR un_state may be set to non-closed,
			 * so reset it back.
			 */
			un->un_state = ST_STATE_CLOSED;
			mutex_exit(ST_MUTEX);
		}
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "un_status=%x, fileno=%x, blkno=%x\n",
		    un->un_status, un->un_pos.fileno, un->un_pos.blkno);

		/*
		 * check again:
		 * if we are not at BOT then it is not safe to unload
		 */
		if ((un->un_dev) &&		/* Been opened since attach */
		    (result != EACCES) &&	/* drive is use by somebody */
		    ((((un->un_pos.pmode == legacy) &&
		    (un->un_pos.fileno > 0) ||	/* Known position not rewound */
		    (un->un_pos.blkno != 0)) ||	/* Or within first file */
		    ((un->un_pos.pmode == logical) &&
		    (un->un_pos.lgclblkno > 0))) &&
		    ((un->un_state == ST_STATE_CLOSED) &&
		    (un->un_laststate == ST_STATE_CLOSING)))) {

			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "cannot detach: pmode=%d fileno=0x%x, blkno=0x%x"
			    " lgclblkno=0x%"PRIx64"\n", un->un_pos.pmode,
			    un->un_pos.fileno, un->un_pos.blkno,
			    un->un_pos.lgclblkno);
			un->un_unit_attention_flags |= 4;
			return (DDI_FAILURE);
		}

		/*
		 * Just To make sure that we have released the
		 * tape unit .
		 */
		if (un->un_dev && (un->un_rsvd_status & ST_RESERVE) &&
		    !DEVI_IS_DEVICE_REMOVED(devi)) {
			mutex_enter(ST_MUTEX);
			(void) st_reserve_release(un, ST_RELEASE, st_uscsi_cmd);
			mutex_exit(ST_MUTEX);
		}

		/*
		 * now remove other data structures allocated in st_doattach()
		 */
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "destroying/freeing\n");

		(void) scsi_reset_notify(ROUTE, SCSI_RESET_CANCEL,
		    st_reset_notification, (caddr_t)un);
		cv_destroy(&un->un_clscv);
		cv_destroy(&un->un_sbuf_cv);
		cv_destroy(&un->un_queue_cv);
		cv_destroy(&un->un_suspend_cv);
		cv_destroy(&un->un_tape_busy_cv);
		cv_destroy(&un->un_recov_buf_cv);

		if (un->un_recov_taskq) {
			ddi_taskq_destroy(un->un_recov_taskq);
		}

		if (un->un_hib_tid) {
			(void) untimeout(un->un_hib_tid);
			un->un_hib_tid = 0;
		}

		if (un->un_delay_tid) {
			(void) untimeout(un->un_delay_tid);
			un->un_delay_tid = 0;
		}
		cv_destroy(&un->un_state_cv);

#ifdef	__x86
		cv_destroy(&un->un_contig_mem_cv);

		if (un->un_contig_mem_hdl != NULL) {
			ddi_dma_free_handle(&un->un_contig_mem_hdl);
		}
#endif
		if (un->un_sbufp) {
			freerbuf(un->un_sbufp);
		}
		if (un->un_recov_buf) {
			freerbuf(un->un_recov_buf);
		}
		if (un->un_uscsi_rqs_buf) {
			kmem_free(un->un_uscsi_rqs_buf, SENSE_LENGTH);
		}
		if (un->un_mspl) {
			i_ddi_mem_free((caddr_t)un->un_mspl, NULL);
		}
		if (un->un_rqs) {
			scsi_destroy_pkt(un->un_rqs);
			scsi_free_consistent_buf(un->un_rqs_bp);
		}
		if (un->un_mkr_pkt) {
			scsi_destroy_pkt(un->un_mkr_pkt);
		}
		if (un->un_arq_enabled) {
			(void) scsi_ifsetcap(ROUTE, "auto-rqsense", 0, 1);
		}
		if (un->un_dp_size) {
			kmem_free(un->un_dp, un->un_dp_size);
		}
		if (un->un_stats) {
			kstat_delete(un->un_stats);
			un->un_stats = (kstat_t *)0;
		}
		if (un->un_errstats) {
			kstat_delete(un->un_errstats);
			un->un_errstats = (kstat_t *)0;
		}
		if (un->un_media_id_len) {
			kmem_free(un->un_media_id, un->un_media_id_len);
		}
		devp = ST_SCSI_DEVP;
		ddi_soft_state_free(st_state, instance);
		devp->sd_private = NULL;
		devp->sd_sense = NULL;
		scsi_unprobe(devp);
		ddi_prop_remove_all(devi);
		ddi_remove_minor_node(devi, NULL);
		ST_DEBUG(0, st_label, SCSI_DEBUG, "st_detach done\n");
		return (DDI_SUCCESS);

	case DDI_SUSPEND:

		/*
		 * Suspend/Resume
		 *
		 * To process DDI_SUSPEND, we must do the following:
		 *
		 *  - check ddi_removing_power to see if power will be turned
		 *    off. if so, return DDI_FAILURE
		 *  - check if we are already suspended,
		 *    if so, return DDI_FAILURE
		 *  - check if device state is CLOSED,
		 *    if not, return DDI_FAILURE.
		 *  - wait until outstanding operations complete
		 *  - save tape state
		 *  - block new operations
		 *  - cancel pending timeouts
		 *
		 */

		if (ddi_removing_power(devi)) {
			return (DDI_FAILURE);
		}

		if (un->un_dev == 0)
			un->un_dev = MTMINOR(instance);

		mutex_enter(ST_MUTEX);

		/*
		 * Shouldn't already be suspended, if so return failure
		 */
		if (un->un_pwr_mgmt == ST_PWR_SUSPENDED) {
			mutex_exit(ST_MUTEX);
			return (DDI_FAILURE);
		}
		if (un->un_state != ST_STATE_CLOSED) {
			mutex_exit(ST_MUTEX);
			return (DDI_FAILURE);
		}

		/*
		 * Wait for all outstanding I/O's to complete
		 *
		 * we wait on both ncmds and the wait queue for times
		 * when we are flushing after persistent errors are
		 * flagged, which is when ncmds can be 0, and the
		 * queue can still have I/O's.  This way we preserve
		 * order of biodone's.
		 */
		wait_cmds_complete = ddi_get_lbolt();
		wait_cmds_complete +=
		    st_wait_cmds_complete * drv_usectohz(1000000);
		while (un->un_ncmds || un->un_quef ||
		    (un->un_state == ST_STATE_RESOURCE_WAIT)) {

			if (cv_timedwait(&un->un_tape_busy_cv, ST_MUTEX,
			    wait_cmds_complete) == -1) {
				/*
				 * Time expired then cancel the command
				 */
				if (st_reset(un, RESET_LUN) == 0) {
					if (un->un_last_throttle) {
						un->un_throttle =
						    un->un_last_throttle;
					}
					mutex_exit(ST_MUTEX);
					return (DDI_FAILURE);
				} else {
					break;
				}
			}
		}

		/*
		 * DDI_SUSPEND says that the system "may" power down, we
		 * remember the file and block number before rewinding.
		 * we also need to save state before issuing
		 * any WRITE_FILE_MARK command.
		 */
		(void) st_update_block_pos(un, st_cmd, 0);
		COPY_POS(&un->un_suspend_pos, &un->un_pos);


		/*
		 * Issue a zero write file fmk command to tell the drive to
		 * flush any buffered tape marks
		 */
		(void) st_cmd(un, SCMD_WRITE_FILE_MARK, 0, SYNC_CMD);

		/*
		 * Because not all tape drives correctly implement buffer
		 * flushing with the zero write file fmk command, issue a
		 * synchronous rewind command to force data flushing.
		 * st_validate_tapemarks() will do a rewind during DDI_RESUME
		 * anyway.
		 */
		(void) st_cmd(un, SCMD_REWIND, 0, SYNC_CMD);

		/* stop any new operations */
		un->un_pwr_mgmt = ST_PWR_SUSPENDED;
		un->un_throttle = 0;

		/*
		 * cancel any outstanding timeouts
		 */
		if (un->un_delay_tid) {
			timeout_id_t temp_id = un->un_delay_tid;
			un->un_delay_tid = 0;
			un->un_tids_at_suspend |= ST_DELAY_TID;
			mutex_exit(ST_MUTEX);
			(void) untimeout(temp_id);
			mutex_enter(ST_MUTEX);
		}

		if (un->un_hib_tid) {
			timeout_id_t temp_id = un->un_hib_tid;
			un->un_hib_tid = 0;
			un->un_tids_at_suspend |= ST_HIB_TID;
			mutex_exit(ST_MUTEX);
			(void) untimeout(temp_id);
			mutex_enter(ST_MUTEX);
		}

		/*
		 * Suspend the scsi_watch_thread
		 */
		if (un->un_swr_token) {
			opaque_t temp_token = un->un_swr_token;
			mutex_exit(ST_MUTEX);
			scsi_watch_suspend(temp_token);
		} else {
			mutex_exit(ST_MUTEX);
		}

		return (DDI_SUCCESS);

	default:
		ST_DEBUG(0, st_label, SCSI_DEBUG, "st_detach failed\n");
		return (DDI_FAILURE);
	}
}


/* ARGSUSED */
static int
st_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
	dev_t dev;
	struct scsi_tape *un;
	int instance, error;

	ST_ENTR(dip, st_info);

	switch (infocmd) {
	case DDI_INFO_DEVT2DEVINFO:
		dev = (dev_t)arg;
		instance = MTUNIT(dev);
		if ((un = ddi_get_soft_state(st_state, instance)) == NULL)
			return (DDI_FAILURE);
		*result = (void *) ST_DEVINFO;
		error = DDI_SUCCESS;
		break;
	case DDI_INFO_DEVT2INSTANCE:
		dev = (dev_t)arg;
		instance = MTUNIT(dev);
		*result = (void *)(uintptr_t)instance;
		error = DDI_SUCCESS;
		break;
	default:
		error = DDI_FAILURE;
	}
	return (error);
}

static int
st_doattach(struct scsi_device *devp, int (*canwait)())
{
	struct scsi_tape *un = NULL;
	recov_info *ri;
	int km_flags = (canwait != NULL_FUNC) ? KM_SLEEP : KM_NOSLEEP;
	int instance;
	size_t rlen;

	ST_FUNC(devp->sd_dev, st_doattach);
	/*
	 * Call the routine scsi_probe to do some of the dirty work.
	 * If the INQUIRY command succeeds, the field sd_inq in the
	 * device structure will be filled in.
	 */
	ST_DEBUG(devp->sd_dev, st_label, SCSI_DEBUG,
	    "st_doattach(): probing\n");

	if (scsi_probe(devp, canwait) == SCSIPROBE_EXISTS) {

		/*
		 * In checking the whole inq_dtype byte we are looking at both
		 * the Peripheral Qualifier and the Peripheral Device Type.
		 * For this driver we are only interested in sequential devices
		 * that are connected or capable if connecting to this logical
		 * unit.
		 */
		if (devp->sd_inq->inq_dtype ==
		    (DTYPE_SEQUENTIAL | DPQ_POSSIBLE)) {
			ST_DEBUG(devp->sd_dev, st_label, SCSI_DEBUG,
			    "probe exists\n");
		} else {
			/* Something there but not a tape device */
			scsi_unprobe(devp);
			return (DDI_FAILURE);
		}
	} else {
		/* Nothing there */
		ST_DEBUG(devp->sd_dev, st_label, SCSI_DEBUG,
		    "probe failure: nothing there\n");
		scsi_unprobe(devp);
		return (DDI_FAILURE);
	}


	/*
	 * The actual unit is present.
	 * Now is the time to fill in the rest of our info..
	 */
	instance = ddi_get_instance(devp->sd_dev);

	if (ddi_soft_state_zalloc(st_state, instance) != DDI_SUCCESS) {
		goto error;
	}
	un = ddi_get_soft_state(st_state, instance);

	ASSERT(un != NULL);

	un->un_rqs_bp = scsi_alloc_consistent_buf(&devp->sd_address, NULL,
	    MAX_SENSE_LENGTH, B_READ, canwait, NULL);
	if (un->un_rqs_bp == NULL) {
		goto error;
	}
	un->un_rqs = scsi_init_pkt(&devp->sd_address, NULL, un->un_rqs_bp,
	    CDB_GROUP0, 1, st_recov_sz, PKT_CONSISTENT, canwait, NULL);
	if (!un->un_rqs) {
		goto error;
	}
	ASSERT(un->un_rqs->pkt_resid == 0);
	devp->sd_sense =
	    (struct scsi_extended_sense *)un->un_rqs_bp->b_un.b_addr;
	ASSERT(geterror(un->un_rqs_bp) == NULL);

	(void) scsi_setup_cdb((union scsi_cdb *)un->un_rqs->pkt_cdbp,
	    SCMD_REQUEST_SENSE, 0, MAX_SENSE_LENGTH, 0);
	FILL_SCSI1_LUN(devp, un->un_rqs);
	un->un_rqs->pkt_flags |= (FLAG_SENSING | FLAG_HEAD | FLAG_NODISCON);
	un->un_rqs->pkt_time = st_io_time;
	un->un_rqs->pkt_comp = st_intr;
	ri = (recov_info *)un->un_rqs->pkt_private;
	if (st_recov_sz == sizeof (recov_info)) {
		ri->privatelen = sizeof (recov_info);
	} else {
		ri->privatelen = sizeof (pkt_info);
	}

	un->un_sbufp = getrbuf(km_flags);
	un->un_recov_buf = getrbuf(km_flags);

	un->un_uscsi_rqs_buf = kmem_alloc(SENSE_LENGTH, KM_SLEEP);

	/*
	 * use i_ddi_mem_alloc() for now until we have an interface to allocate
	 * memory for DMA which doesn't require a DMA handle. ddi_iopb_alloc()
	 * is obsolete and we want more flexibility in controlling the DMA
	 * address constraints.
	 */
	(void) i_ddi_mem_alloc(devp->sd_dev, &st_alloc_attr,
	    sizeof (struct seq_mode), ((km_flags == KM_SLEEP) ? 1 : 0), 0,
	    NULL, (caddr_t *)&un->un_mspl, &rlen, NULL);

	(void) i_ddi_mem_alloc(devp->sd_dev, &st_alloc_attr,
	    sizeof (read_pos_data_t), ((km_flags == KM_SLEEP) ? 1 : 0), 0,
	    NULL, (caddr_t *)&un->un_read_pos_data, &rlen, NULL);

	if (!un->un_sbufp || !un->un_mspl || !un->un_read_pos_data) {
		ST_DEBUG6(devp->sd_dev, st_label, SCSI_DEBUG,
		    "probe partial failure: no space\n");
		goto error;
	}

	bzero(un->un_mspl, sizeof (struct seq_mode));

	cv_init(&un->un_sbuf_cv, NULL, CV_DRIVER, NULL);
	cv_init(&un->un_queue_cv, NULL, CV_DRIVER, NULL);
	cv_init(&un->un_clscv, NULL, CV_DRIVER, NULL);
	cv_init(&un->un_state_cv, NULL, CV_DRIVER, NULL);
#ifdef	__x86
	cv_init(&un->un_contig_mem_cv, NULL, CV_DRIVER, NULL);
#endif

	/* Initialize power managemnet condition variable */
	cv_init(&un->un_suspend_cv, NULL, CV_DRIVER, NULL);
	cv_init(&un->un_tape_busy_cv, NULL, CV_DRIVER, NULL);
	cv_init(&un->un_recov_buf_cv, NULL, CV_DRIVER, NULL);

	un->un_recov_taskq = ddi_taskq_create(devp->sd_dev,
	    "un_recov_taskq", 1, TASKQ_DEFAULTPRI, km_flags);

	ASSERT(un->un_recov_taskq != NULL);

	un->un_pos.pmode = invalid;
	un->un_sd	= devp;
	un->un_swr_token = (opaque_t)NULL;
	un->un_comp_page = ST_DEV_DATACOMP_PAGE | ST_DEV_CONFIG_PAGE;
	un->un_wormable = st_is_drive_worm;
	un->un_media_id_method = st_get_media_identification;
	/*
	 * setting long a initial as it contains logical file info.
	 * support for long format is mandatory but many drive don't do it.
	 */
	un->un_read_pos_type = LONG_POS;

	un->un_suspend_pos.pmode = invalid;

	st_add_recovery_info_to_pkt(un, un->un_rqs_bp, un->un_rqs);

#ifdef	__x86
	if (ddi_dma_alloc_handle(ST_DEVINFO, &st_contig_mem_dma_attr,
	    DDI_DMA_SLEEP, NULL, &un->un_contig_mem_hdl) != DDI_SUCCESS) {
		ST_DEBUG6(devp->sd_dev, st_label, SCSI_DEBUG,
		    "allocation of contiguous memory dma handle failed!");
		un->un_contig_mem_hdl = NULL;
		goto error;
	}
#endif

	/*
	 * Since this driver manages devices with "remote" hardware,
	 * i.e. the devices themselves have no "reg" properties,
	 * the SUSPEND/RESUME commands in detach/attach will not be
	 * called by the power management framework unless we request
	 * it by creating a "pm-hardware-state" property and setting it
	 * to value "needs-suspend-resume".
	 */
	if (ddi_prop_update_string(DDI_DEV_T_NONE, devp->sd_dev,
	    "pm-hardware-state", "needs-suspend-resume") !=
	    DDI_PROP_SUCCESS) {

		ST_DEBUG(devp->sd_dev, st_label, SCSI_DEBUG,
		    "ddi_prop_update(\"pm-hardware-state\") failed\n");
		goto error;
	}

	if (ddi_prop_create(DDI_DEV_T_NONE, devp->sd_dev, DDI_PROP_CANSLEEP,
	    "no-involuntary-power-cycles", NULL, 0) != DDI_PROP_SUCCESS) {

		ST_DEBUG(devp->sd_dev, st_label, SCSI_DEBUG,
		    "ddi_prop_create(\"no-involuntary-power-cycles\") "
		    "failed\n");
		goto error;
	}

	(void) scsi_reset_notify(ROUTE, SCSI_RESET_NOTIFY,
	    st_reset_notification, (caddr_t)un);

	ST_DEBUG6(devp->sd_dev, st_label, SCSI_DEBUG, "attach success\n");
	return (DDI_SUCCESS);

error:
	devp->sd_sense = NULL;

	ddi_remove_minor_node(devp->sd_dev, NULL);
	if (un) {
		if (un->un_mspl) {
			i_ddi_mem_free((caddr_t)un->un_mspl, NULL);
		}
		if (un->un_read_pos_data) {
			i_ddi_mem_free((caddr_t)un->un_read_pos_data, 0);
		}
		if (un->un_sbufp) {
			freerbuf(un->un_sbufp);
		}
		if (un->un_recov_buf) {
			freerbuf(un->un_recov_buf);
		}
		if (un->un_uscsi_rqs_buf) {
			kmem_free(un->un_uscsi_rqs_buf, SENSE_LENGTH);
		}
#ifdef	__x86
		if (un->un_contig_mem_hdl != NULL) {
			ddi_dma_free_handle(&un->un_contig_mem_hdl);
		}
#endif
		if (un->un_rqs) {
			scsi_destroy_pkt(un->un_rqs);
		}

		if (un->un_rqs_bp) {
			scsi_free_consistent_buf(un->un_rqs_bp);
		}

		ddi_soft_state_free(st_state, instance);
		devp->sd_private = NULL;
	}

	if (devp->sd_inq) {
		scsi_unprobe(devp);
	}
	return (DDI_FAILURE);
}

typedef int
(*cfg_functp)(struct scsi_tape *, char *vidpid, struct st_drivetype *);

static cfg_functp config_functs[] = {
	st_get_conf_from_st_dot_conf,
	st_get_conf_from_st_conf_dot_c,
	st_get_conf_from_tape_drive,
	st_get_default_conf
};


/*
 * determine tape type, using tape-config-list or built-in table or
 * use a generic tape config entry
 */
static void
st_known_tape_type(struct scsi_tape *un)
{
	struct st_drivetype *dp;
	cfg_functp *config_funct;
	uchar_t reserved;

	ST_FUNC(ST_DEVINFO, st_known_tape_type);

	reserved = (un->un_rsvd_status & ST_RESERVE) ? ST_RESERVE
	    : ST_RELEASE;

	/*
	 * XXX:  Emulex MT-02 (and emulators) predates SCSI-1 and has
	 *	 no vid & pid inquiry data.  So, we provide one.
	 */
	if (ST_INQUIRY->inq_len == 0 ||
	    (bcmp("\0\0\0\0\0\0\0\0", ST_INQUIRY->inq_vid, 8) == 0)) {
		(void) strcpy((char *)ST_INQUIRY->inq_vid, ST_MT02_NAME);
	}

	if (un->un_dp_size == 0) {
		un->un_dp_size = sizeof (struct st_drivetype);
		dp = kmem_zalloc((size_t)un->un_dp_size, KM_SLEEP);
		un->un_dp = dp;
	} else {
		dp = un->un_dp;
	}

	un->un_dp->non_motion_timeout = st_io_time;
	/*
	 * Loop through the configuration methods till one works.
	 */
	for (config_funct = &config_functs[0]; ; config_funct++) {
		if ((*config_funct)(un, ST_INQUIRY->inq_vid, dp)) {
			break;
		}
	}

	/*
	 * If we didn't just make up this configuration and
	 * all the density codes are the same..
	 * Set Auto Density over ride.
	 */
	if (*config_funct != st_get_default_conf) {
		/*
		 * If this device is one that is configured and all
		 * densities are the same, This saves doing gets and set
		 * that yield nothing.
		 */
		if ((dp->densities[0]) == (dp->densities[1]) &&
		    (dp->densities[0]) == (dp->densities[2]) &&
		    (dp->densities[0]) == (dp->densities[3])) {

			dp->options |= ST_AUTODEN_OVERRIDE;
		}
	}


	/*
	 * Store tape drive characteristics.
	 */
	un->un_status = 0;
	un->un_attached = 1;
	un->un_init_options = dp->options;

	/* setup operation time-outs based on options */
	st_calculate_timeouts(un);

	/* TLR support */
	if (un->un_dp->type != ST_TYPE_INVALID) {
		int result;

		/* try and enable TLR */
		un->un_tlr_flag = TLR_SAS_ONE_DEVICE;
		result = st_set_target_TLR_mode(un, st_uscsi_cmd);
		if (result == EACCES) {
			/*
			 * From attach command failed.
			 * Set dp type so is run again on open.
			 */
			un->un_dp->type = ST_TYPE_INVALID;
			un->un_tlr_flag = TLR_NOT_KNOWN;
		} else if (result == 0) {
			if (scsi_ifgetcap(&un->un_sd->sd_address,
			    "tran-layer-retries", 1) == -1) {
				un->un_tlr_flag = TLR_NOT_SUPPORTED;
				(void) st_set_target_TLR_mode(un, st_uscsi_cmd);
			} else {
				un->un_tlr_flag = TLR_SAS_ONE_DEVICE;
			}
		} else {
			un->un_tlr_flag = TLR_NOT_SUPPORTED;
		}
	}

	/* make sure if we are supposed to be variable, make it variable */
	if (dp->options & ST_VARIABLE) {
		dp->bsize = 0;
	}

	if (reserved != ((un->un_rsvd_status & ST_RESERVE) ? ST_RESERVE
	    : ST_RELEASE)) {
		(void) st_reserve_release(un, reserved, st_uscsi_cmd);
	}

	un->un_unit_attention_flags |= 1;

	scsi_log(ST_DEVINFO, st_label, CE_NOTE, "?<%s>\n", dp->name);

}


typedef struct {
	int mask;
	int bottom;
	int top;
	char *name;
} conf_limit;

static const conf_limit conf_limits[] = {

	-1,		1,		2,		"conf version",
	-1,		MT_ISTS,	ST_LAST_TYPE,	"drive type",
	-1,		0,		0xffffff,	"block size",
	ST_VALID_OPTS,	0,		ST_VALID_OPTS,	"options",
	-1,		0,		4,		"number of densities",
	-1,		0,		UINT8_MAX,	"density code",
	-1,		0,		3,		"default density",
	-1,		0,		UINT16_MAX,	"non motion timeout",
	-1,		0,		UINT16_MAX,	"I/O timeout",
	-1,		0,		UINT16_MAX,	"space timeout",
	-1,		0,		UINT16_MAX,	"load timeout",
	-1,		0,		UINT16_MAX,	"unload timeout",
	-1,		0,		UINT16_MAX,	"erase timeout",
	0,		0,		0,		NULL
};

static int
st_validate_conf_data(struct scsi_tape *un, int *list, int list_len,
    const char *conf_name)
{
	int dens;
	int ndens;
	int value;
	int type;
	int count;
	const conf_limit *limit = &conf_limits[0];

	ST_FUNC(ST_DEVINFO, st_validate_conf_data);

	ST_DEBUG3(ST_DEVINFO, st_label, CE_NOTE,
	    "Checking %d entrys total with %d densities\n", list_len, list[4]);

	count = list_len;
	type = *list;
	for (;  count && limit->name; count--, list++, limit++) {

		value = *list;
		if (value & ~limit->mask) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "%s %s value invalid bits set: 0x%X\n",
			    conf_name, limit->name, value & ~limit->mask);
			*list &= limit->mask;
		} else if (value < limit->bottom) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "%s %s value too low: value = %d limit %d\n",
			    conf_name, limit->name, value, limit->bottom);
		} else if (value > limit->top) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "%s %s value too high: value = %d limit %d\n",
			    conf_name, limit->name, value, limit->top);
		} else {
			ST_DEBUG3(ST_DEVINFO, st_label, CE_CONT,
			    "%s %s value = 0x%X\n",
			    conf_name, limit->name, value);
		}

		/* If not the number of densities continue */
		if (limit != &conf_limits[4]) {
			continue;
		}

		/* If number of densities is not in range can't use config */
		if (value < limit->bottom || value > limit->top) {
			return (-1);
		}

		ndens = min(value, NDENSITIES);
		if ((type == 1) && (list_len - ndens) != 6) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "%s conf version 1 with %d densities has %d items"
			    " should have %d",
			    conf_name, ndens, list_len, 6 + ndens);
		} else if ((type == 2) && (list_len - ndens) != 13) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "%s conf version 2 with %d densities has %d items"
			    " should have %d",
			    conf_name, ndens, list_len, 13 + ndens);
		}

		limit++;
		for (dens = 0; dens < ndens && count; dens++) {
			count--;
			list++;
			value = *list;
			if (value < limit->bottom) {
				scsi_log(ST_DEVINFO, st_label, CE_NOTE,
				    "%s density[%d] value too low: value ="
				    " 0x%X limit 0x%X\n",
				    conf_name, dens, value, limit->bottom);
			} else if (value > limit->top) {
				scsi_log(ST_DEVINFO, st_label, CE_NOTE,
				    "%s density[%d] value too high: value ="
				    " 0x%X limit 0x%X\n",
				    conf_name, dens, value, limit->top);
			} else {
				ST_DEBUG3(ST_DEVINFO, st_label, CE_CONT,
				    "%s density[%d] value = 0x%X\n",
				    conf_name, dens, value);
			}
		}
	}

	return (0);
}

static int
st_get_conf_from_st_dot_conf(struct scsi_tape *un, char *vidpid,
    struct st_drivetype *dp)
{
	caddr_t config_list = NULL;
	caddr_t data_list = NULL;
	int	*data_ptr;
	caddr_t vidptr, prettyptr, datanameptr;
	size_t	vidlen, prettylen, datanamelen, tripletlen = 0;
	int config_list_len, data_list_len, len, i;
	int version;
	int found = 0;

	ST_FUNC(ST_DEVINFO, st_get_conf_from_st_dot_conf);

	/*
	 * Determine type of tape controller. Type is determined by
	 * checking the vendor ids of the earlier inquiry command and
	 * comparing those with vids in tape-config-list defined in st.conf
	 */
	if (ddi_getlongprop(DDI_DEV_T_ANY, ST_DEVINFO, DDI_PROP_DONTPASS,
	    "tape-config-list", (caddr_t)&config_list, &config_list_len)
	    != DDI_PROP_SUCCESS) {
		return (found);
	}

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_get_conf_from_st_dot_conf(): st.conf has tape-config-list\n");

	/*
	 * Compare vids in each triplet - if it matches, get value for
	 * data_name and contruct a st_drivetype struct
	 * tripletlen is not set yet!
	 */
	for (len = config_list_len, vidptr = config_list;
	    len > 0;
	    vidptr += tripletlen, len -= tripletlen) {

		vidlen = strlen(vidptr);
		prettyptr = vidptr + vidlen + 1;
		prettylen = strlen(prettyptr);
		datanameptr = prettyptr + prettylen + 1;
		datanamelen = strlen(datanameptr);
		tripletlen = vidlen + prettylen + datanamelen + 3;

		if (vidlen == 0) {
			continue;
		}

		/*
		 * If inquiry vid dosen't match this triplets vid,
		 * try the next.
		 */
		if (strncasecmp(vidpid, vidptr, vidlen)) {
			continue;
		}

		/*
		 * if prettylen is zero then use the vid string
		 */
		if (prettylen == 0) {
			prettyptr = vidptr;
			prettylen = vidlen;
		}

		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "vid = %s, pretty=%s, dataname = %s\n",
		    vidptr, prettyptr, datanameptr);

		/*
		 * get the data list
		 */
		if (ddi_getlongprop(DDI_DEV_T_ANY, ST_DEVINFO, 0,
		    datanameptr, (caddr_t)&data_list,
		    &data_list_len) != DDI_PROP_SUCCESS) {
			/*
			 * Error in getting property value
			 * print warning!
			 */
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "data property (%s) has no value\n",
			    datanameptr);
			continue;
		}

		/*
		 * now initialize the st_drivetype struct
		 */
		(void) strncpy(dp->name, prettyptr, ST_NAMESIZE - 1);
		dp->length = (int)min(vidlen, (VIDPIDLEN - 1));
		(void) strncpy(dp->vid, vidptr, dp->length);
		data_ptr = (int *)data_list;
		/*
		 * check if data is enough for version, type,
		 * bsize, options, # of densities, density1,
		 * density2, ..., default_density
		 */
		if ((data_list_len < 5 * sizeof (int)) ||
		    (data_list_len < 6 * sizeof (int) +
		    *(data_ptr + 4) * sizeof (int))) {
			/*
			 * print warning and skip to next triplet.
			 */
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "data property (%s) incomplete\n",
			    datanameptr);
			kmem_free(data_list, data_list_len);
			continue;
		}

		if (st_validate_conf_data(un, data_ptr,
		    data_list_len / sizeof (int), datanameptr)) {
			kmem_free(data_list, data_list_len);
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "data property (%s) rejected\n",
			    datanameptr);
			continue;
		}

		/*
		 * check version
		 */
		version = *data_ptr++;
		if (version != 1 && version != 2) {
			/* print warning but accept it */
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "Version # for data property (%s) "
			    "not set to 1 or 2\n", datanameptr);
		}

		dp->type    = *data_ptr++;
		dp->bsize   = *data_ptr++;
		dp->options = *data_ptr++;
		dp->options |= ST_DYNAMIC;
		len = *data_ptr++;
		for (i = 0; i < NDENSITIES; i++) {
			if (i < len) {
				dp->densities[i] = *data_ptr++;
			}
		}
		dp->default_density = *data_ptr << 3;
		if (version == 2 &&
		    data_list_len >= (13 + len) * sizeof (int)) {
			data_ptr++;
			dp->non_motion_timeout	= *data_ptr++;
			dp->io_timeout		= *data_ptr++;
			dp->rewind_timeout	= *data_ptr++;
			dp->space_timeout	= *data_ptr++;
			dp->load_timeout	= *data_ptr++;
			dp->unload_timeout	= *data_ptr++;
			dp->erase_timeout	= *data_ptr++;
		}
		kmem_free(data_list, data_list_len);
		found = 1;
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "found in st.conf: vid = %s, pretty=%s\n",
		    dp->vid, dp->name);
		break;
	}

	/*
	 * free up the memory allocated by ddi_getlongprop
	 */
	if (config_list) {
		kmem_free(config_list, config_list_len);
	}
	return (found);
}

static int
st_get_conf_from_st_conf_dot_c(struct scsi_tape *un, char *vidpid,
    struct st_drivetype *dp)
{
	int i;

	ST_FUNC(ST_DEVINFO, st_get_conf_from_st_conf_dot_c);
	/*
	 * Determine type of tape controller.  Type is determined by
	 * checking the result of the earlier inquiry command and
	 * comparing vendor ids with strings in a table declared in st_conf.c.
	 */
	ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_get_conf_from_st_conf_dot_c(): looking at st_drivetypes\n");

	for (i = 0; i < st_ndrivetypes; i++) {
		if (st_drivetypes[i].length == 0) {
			continue;
		}
		if (strncasecmp(vidpid, st_drivetypes[i].vid,
		    st_drivetypes[i].length)) {
			continue;
		}
		bcopy(&st_drivetypes[i], dp, sizeof (st_drivetypes[i]));
		return (1);
	}
	return (0);
}

static int
st_get_conf_from_tape_drive(struct scsi_tape *un, char *vidpid,
    struct st_drivetype *dp)
{
	int bsize;
	ulong_t maxbsize;
	caddr_t buf;
	struct st_drivetype *tem_dp;
	struct read_blklim *blklim;
	int rval;
	int i;

	ST_FUNC(ST_DEVINFO, st_get_conf_from_tape_drive);

	/*
	 * Determine the type of tape controller. Type is determined by
	 * sending SCSI commands to tape drive and deriving the type from
	 * the returned data.
	 */
	ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_get_conf_from_tape_drive(): asking tape drive\n");

	tem_dp = kmem_zalloc(sizeof (struct st_drivetype), KM_SLEEP);

	/*
	 * Make up a name
	 */
	bcopy(vidpid, tem_dp->name, VIDPIDLEN);
	tem_dp->name[VIDPIDLEN] = '\0';
	tem_dp->length = min(strlen(ST_INQUIRY->inq_vid), (VIDPIDLEN - 1));
	(void) strncpy(tem_dp->vid, ST_INQUIRY->inq_vid, tem_dp->length);
	/*
	 * 'clean' vendor and product strings of non-printing chars
	 */
	for (i = 0; i < VIDPIDLEN - 1; i ++) {
		if (tem_dp->name[i] < ' ' || tem_dp->name[i] > '~') {
			tem_dp->name[i] = '.';
		}
	}

	/*
	 * MODE SENSE to determine block size.
	 */
	un->un_dp->options |= ST_MODE_SEL_COMP | ST_UNLOADABLE;
	rval = st_modesense(un);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			rval = 1;
		} else {
			un->un_dp->options &= ~ST_MODE_SEL_COMP;
			rval = 0;
		}
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_conf_from_tape_drive(): fail to mode sense\n");
		goto exit;
	}

	/* Can mode sense page 0x10 or 0xf */
	tem_dp->options |= ST_MODE_SEL_COMP;
	bsize = (un->un_mspl->high_bl << 16)	|
	    (un->un_mspl->mid_bl << 8)		|
	    (un->un_mspl->low_bl);

	if (bsize == 0) {
		tem_dp->options |= ST_VARIABLE;
		tem_dp->bsize = 0;
	} else if (bsize > ST_MAXRECSIZE_FIXED) {
		rval = st_change_block_size(un, 0);
		if (rval) {
			if (rval == EACCES) {
				un->un_dp->type = ST_TYPE_INVALID;
				rval = 1;
			} else {
				rval = 0;
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_get_conf_from_tape_drive(): "
				    "Fixed record size is too large and"
				    "cannot switch to variable record size");
			}
			goto exit;
		}
		tem_dp->options |= ST_VARIABLE;
	} else {
		rval = st_change_block_size(un, 0);
		if (rval == 0) {
			tem_dp->options |= ST_VARIABLE;
			tem_dp->bsize = 0;
		} else if (rval != EACCES) {
			tem_dp->bsize = bsize;
		} else {
			un->un_dp->type = ST_TYPE_INVALID;
			rval = 1;
			goto exit;
		}
	}

	/*
	 * If READ BLOCk LIMITS works and upper block size limit is
	 * more than 64K, ST_NO_RECSIZE_LIMIT is supported.
	 */
	blklim = kmem_zalloc(sizeof (struct read_blklim), KM_SLEEP);
	rval = st_read_block_limits(un, blklim);
	if (rval) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_conf_from_tape_drive(): "
		    "fail to read block limits.\n");
		rval = 0;
		kmem_free(blklim, sizeof (struct read_blklim));
		goto exit;
	}
	maxbsize = (blklim->max_hi << 16) +
	    (blklim->max_mid << 8) + blklim->max_lo;
	if (maxbsize > ST_MAXRECSIZE_VARIABLE) {
		tem_dp->options |= ST_NO_RECSIZE_LIMIT;
	}
	kmem_free(blklim, sizeof (struct read_blklim));

	/*
	 * Inquiry VPD page 0xb0 to see if the tape drive supports WORM
	 */
	buf = kmem_zalloc(6, KM_SLEEP);
	rval = st_get_special_inquiry(un, 6, buf, 0xb0);
	if (rval) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_conf_from_tape_drive(): "
		    "fail to read vitial inquiry.\n");
		rval = 0;
		kmem_free(buf, 6);
		goto exit;
	}
	if (buf[4] & 1) {
		tem_dp->options |= ST_WORMABLE;
	}
	kmem_free(buf, 6);

	/* Assume BSD BSR KNOWS_EOD */
	tem_dp->options |= ST_BSF | ST_BSR | ST_KNOWS_EOD | ST_UNLOADABLE;
	tem_dp->max_rretries = -1;
	tem_dp->max_wretries = -1;

	/*
	 * Decide the densities supported by tape drive by sending
	 * REPORT DENSITY SUPPORT command.
	 */
	if (st_get_densities_from_tape_drive(un, tem_dp) == 0) {
		goto exit;
	}

	/*
	 * Decide the timeout values for several commands by sending
	 * REPORT SUPPORTED OPERATION CODES command.
	 */
	rval = st_get_timeout_values_from_tape_drive(un, tem_dp);
	if (rval == 0 || ((rval == 1) && (tem_dp->type == ST_TYPE_INVALID))) {
		goto exit;
	}

	bcopy(tem_dp, dp, sizeof (struct st_drivetype));
	rval = 1;

exit:
	un->un_status = KEY_NO_SENSE;
	kmem_free(tem_dp, sizeof (struct st_drivetype));
	return (rval);
}

static int
st_get_densities_from_tape_drive(struct scsi_tape *un,
    struct st_drivetype *dp)
{
	int i, p;
	size_t buflen;
	ushort_t des_len;
	uchar_t *den_header;
	uchar_t num_den;
	uchar_t den[NDENSITIES];
	uchar_t deflt[NDENSITIES];
	struct report_density_desc *den_desc;

	ST_FUNC(ST_DEVINFO, st_get_densities_from_type_drive);

	/*
	 * Since we have no idea how many densitiy support entries
	 * will be returned, we send the command firstly assuming
	 * there is only one. Then we can decide the number of
	 * entries by available density support length. If multiple
	 * entries exist, we will resend the command with enough
	 * buffer size.
	 */
	buflen = sizeof (struct report_density_header) +
	    sizeof (struct report_density_desc);
	den_header = kmem_zalloc(buflen, KM_SLEEP);
	if (st_report_density_support(un, den_header, buflen) != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_conf_from_tape_drive(): fail to report density.\n");
		kmem_free(den_header, buflen);
		return (0);
	}
	des_len =
	    BE_16(((struct report_density_header *)den_header)->ava_dens_len);
	num_den = (des_len - 2) / sizeof (struct report_density_desc);

	if (num_den > 1) {
		kmem_free(den_header, buflen);
		buflen = sizeof (struct report_density_header) +
		    sizeof (struct report_density_desc) * num_den;
		den_header = kmem_zalloc(buflen, KM_SLEEP);
		if (st_report_density_support(un, den_header, buflen) != 0) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_get_conf_from_tape_drive(): "
			    "fail to report density.\n");
			kmem_free(den_header, buflen);
			return (0);
		}
	}

	den_desc = (struct report_density_desc *)(den_header
	    + sizeof (struct report_density_header));

	/*
	 * Decide the drive type by assigning organization
	 */
	for (i = 0; i < ST_NUM_MEMBERS(st_vid_dt); i ++) {
		if (strncmp(st_vid_dt[i].vid, (char *)(den_desc->ass_org),
		    8) == 0) {
			dp->type = st_vid_dt[i].type;
			break;
		}
	}
	if (i == ST_NUM_MEMBERS(st_vid_dt)) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_conf_from_tape_drive(): "
		    "can't find match of assigned ort.\n");
		kmem_free(den_header, buflen);
		return (0);
	}

	/*
	 * The tape drive may support many tape formats, but the st driver
	 * supports only the four highest densities. Since density code
	 * values are returned by ascending sequence, we start from the
	 * last entry of density support data block descriptor.
	 */
	p = 0;
	den_desc += num_den - 1;
	for (i = 0; i < num_den && p < NDENSITIES; i ++, den_desc --) {
		if ((den_desc->pri_den != 0) && (den_desc->wrtok)) {
			if (p != 0) {
				if (den_desc->pri_den >= den[p - 1]) {
					continue;
				}
			}
			den[p] = den_desc->pri_den;
			deflt[p] = den_desc->deflt;
			p ++;
		}
	}

	switch (p) {
	case 0:
		bzero(dp->densities, NDENSITIES);
		dp->options |= ST_AUTODEN_OVERRIDE;
		dp->default_density = MT_DENSITY4;
		break;

	case 1:
		(void) memset(dp->densities, den[0], NDENSITIES);
		dp->options |= ST_AUTODEN_OVERRIDE;
		dp->default_density = MT_DENSITY4;
		break;

	case 2:
		dp->densities[0] = den[1];
		dp->densities[1] = den[1];
		dp->densities[2] = den[0];
		dp->densities[3] = den[0];
		if (deflt[0]) {
			dp->default_density = MT_DENSITY4;
		} else {
			dp->default_density = MT_DENSITY2;
		}
		break;

	case 3:
		dp->densities[0] = den[2];
		dp->densities[1] = den[1];
		dp->densities[2] = den[0];
		dp->densities[3] = den[0];
		if (deflt[0]) {
			dp->default_density = MT_DENSITY4;
		} else if (deflt[1]) {
			dp->default_density = MT_DENSITY2;
		} else {
			dp->default_density = MT_DENSITY1;
		}
		break;

	default:
		for (i = p; i > p - NDENSITIES; i --) {
			dp->densities[i - 1] = den[p - i];
		}
		if (deflt[0]) {
			dp->default_density = MT_DENSITY4;
		} else if (deflt[1]) {
			dp->default_density = MT_DENSITY3;
		} else if (deflt[2]) {
			dp->default_density = MT_DENSITY2;
		} else {
			dp->default_density = MT_DENSITY1;
		}
		break;
	}

	bzero(dp->mediatype, NDENSITIES);

	kmem_free(den_header, buflen);
	return (1);
}

static int
st_get_timeout_values_from_tape_drive(struct scsi_tape *un,
    struct st_drivetype *dp)
{
	ushort_t timeout;
	int rval;

	ST_FUNC(ST_DEVINFO, st_get_timeout_values_from_type_drive);

	rval = st_get_timeouts_value(un, SCMD_ERASE, &timeout, 0);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			dp->type = ST_TYPE_INVALID;
			return (1);
		}
		return (0);
	}
	dp->erase_timeout = timeout;

	rval = st_get_timeouts_value(un, SCMD_READ, &timeout, 0);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			dp->type = ST_TYPE_INVALID;
			return (1);
		}
		return (0);
	}
	dp->io_timeout = timeout;

	rval = st_get_timeouts_value(un, SCMD_WRITE, &timeout, 0);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			dp->type = ST_TYPE_INVALID;
			return (1);
		}
		return (0);
	}
	dp->io_timeout = max(dp->io_timeout, timeout);

	rval = st_get_timeouts_value(un, SCMD_SPACE, &timeout, 0);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			dp->type = ST_TYPE_INVALID;
			return (1);
		}
		return (0);
	}
	dp->space_timeout = timeout;

	rval = st_get_timeouts_value(un, SCMD_LOAD, &timeout, 0);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			dp->type = ST_TYPE_INVALID;
			return (1);
		}
		return (0);
	}
	dp->load_timeout = timeout;
	dp->unload_timeout = timeout;

	rval = st_get_timeouts_value(un, SCMD_REWIND, &timeout, 0);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			dp->type = ST_TYPE_INVALID;
			return (1);
		}
		return (0);
	}
	dp->rewind_timeout = timeout;

	rval = st_get_timeouts_value(un, SCMD_INQUIRY, &timeout, 0);
	if (rval) {
		if (rval == EACCES) {
			un->un_dp->type = ST_TYPE_INVALID;
			dp->type = ST_TYPE_INVALID;
			return (1);
		}
		return (0);
	}
	dp->non_motion_timeout = timeout;

	return (1);
}

static int
st_get_timeouts_value(struct scsi_tape *un, uchar_t option_code,
    ushort_t *timeout_value, ushort_t service_action)
{
	uchar_t *timeouts;
	uchar_t *oper;
	uchar_t support;
	uchar_t cdbsize;
	uchar_t ctdp;
	size_t buflen;
	int rval;

	ST_FUNC(ST_DEVINFO, st_get_timeouts_value);

	buflen = sizeof (struct one_com_des) +
	    sizeof (struct com_timeout_des);
	oper = kmem_zalloc(buflen, KM_SLEEP);
	rval = st_report_supported_operation(un, oper, option_code,
	    service_action);

	if (rval) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_timeouts_value(): "
		    "fail to timeouts value for command %d.\n", option_code);
		kmem_free(oper, buflen);
		return (rval);
	}

	support = ((struct one_com_des *)oper)->support;
	if ((support != SUPPORT_VALUES_SUPPORT_SCSI) &&
	    (support != SUPPORT_VALUES_SUPPORT_VENDOR)) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_timeouts_value(): "
		    "command %d is not supported.\n", option_code);
		kmem_free(oper, buflen);
		return (ENOTSUP);
	}

	ctdp = ((struct one_com_des *)oper)->ctdp;
	if (!ctdp) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_timeouts_value(): "
		    "command timeout is not included.\n");
		kmem_free(oper, buflen);
		return (ENOTSUP);
	}

	cdbsize = BE_16(((struct one_com_des *)oper)->cdb_size);
	timeouts = (uchar_t *)(oper + cdbsize + 4);

	/*
	 * Timeout value in seconds is 4 bytes, but we only support the lower 2
	 * bytes. If the higher 2 bytes are not zero, the timeout value is set
	 * to 0xFFFF.
	 */
	if (*(timeouts + 8) != 0 || *(timeouts + 9) != 0) {
		*timeout_value = USHRT_MAX;
	} else {
		*timeout_value = ((*(timeouts + 10)) << 8) |
		    (*(timeouts + 11));
	}

	kmem_free(oper, buflen);
	return (0);
}

static int
st_get_default_conf(struct scsi_tape *un, char *vidpid, struct st_drivetype *dp)
{
	int i;

	ST_FUNC(ST_DEVINFO, st_get_default_conf);

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_get_default_conf(): making drivetype from INQ cmd\n");

	/*
	 * Make up a name
	 */
	bcopy("Vendor '", dp->name, 8);
	bcopy(vidpid, &dp->name[8], VIDLEN);
	bcopy("' Product '", &dp->name[16], 11);
	bcopy(&vidpid[8], &dp->name[27], PIDLEN);
	dp->name[ST_NAMESIZE - 2] = '\'';
	dp->name[ST_NAMESIZE - 1] = '\0';
	dp->length = min(strlen(ST_INQUIRY->inq_vid), (VIDPIDLEN - 1));
	(void) strncpy(dp->vid, ST_INQUIRY->inq_vid, dp->length);
	/*
	 * 'clean' vendor and product strings of non-printing chars
	 */
	for (i = 0; i < ST_NAMESIZE - 2; i++) {
		if (dp->name[i] < ' ' || dp->name[i] > '~') {
			dp->name[i] = '.';
		}
	}
	dp->type = ST_TYPE_INVALID;
	dp->options |= (ST_DYNAMIC | ST_UNLOADABLE | ST_MODE_SEL_COMP);

	return (1); /* Can Not Fail */
}

/*
 * Regular Unix Entry points
 */



/* ARGSUSED */
static int
st_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
{
	dev_t dev = *dev_p;
	int rval = 0;

	GET_SOFT_STATE(dev);

	ST_ENTR(ST_DEVINFO, st_open);

	/*
	 * validate that we are addressing a sensible unit
	 */
	mutex_enter(ST_MUTEX);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_open(node = %s dev = 0x%lx, flag = %d, otyp = %d)\n",
	    st_dev_name(dev), *dev_p, flag, otyp);

	/*
	 * All device accesss go thru st_strategy() where we check
	 * suspend status
	 */

	if (!un->un_attached) {
		st_known_tape_type(un);
		if (!un->un_attached) {
			rval = ENXIO;
			goto exit;
		}

	}

	/*
	 * Check for the case of the tape in the middle of closing.
	 * This isn't simply a check of the current state, because
	 * we could be in state of sensing with the previous state
	 * that of closing.
	 *
	 * And don't allow multiple opens.
	 */
	if (!(flag & (FNDELAY | FNONBLOCK)) && IS_CLOSING(un)) {
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_CLOSE_PENDING_OPEN;
		while (IS_CLOSING(un) ||
		    un->un_state == ST_STATE_CLOSE_PENDING_OPEN) {
			if (cv_wait_sig(&un->un_clscv, ST_MUTEX) == 0) {
				rval = EINTR;
				un->un_state = un->un_laststate;
				goto exit;
			}
		}
	} else if (un->un_state != ST_STATE_CLOSED) {
		rval = EBUSY;
		goto busy;
	}

	/*
	 * record current dev
	 */
	un->un_dev = dev;
	un->un_oflags = flag;	/* save for use in st_tape_init() */
	un->un_errno = 0;	/* no errors yet */
	un->un_restore_pos = 0;
	un->un_rqs_state = 0;

	/*
	 * If we are opening O_NDELAY, or O_NONBLOCK, we don't check for
	 * anything, leave internal states alone, if fileno >= 0
	 */
	if (flag & (FNDELAY | FNONBLOCK)) {
		switch (un->un_pos.pmode) {

		case invalid:
			un->un_state = ST_STATE_OFFLINE;
			break;

		case legacy:
			/*
			 * If position is anything other than rewound.
			 */
			if (un->un_pos.fileno != 0 || un->un_pos.blkno != 0) {
				/*
				 * set un_read_only/write-protect status.
				 *
				 * If the tape is not bot we can assume
				 * that mspl->wp_status is set properly.
				 * else
				 * we need to do a mode sense/Tur once
				 * again to get the actual tape status.(since
				 * user might have replaced the tape)
				 * Hence make the st state OFFLINE so that
				 * we re-intialize the tape once again.
				 */
				un->un_read_only =
				    (un->un_oflags & FWRITE) ? RDWR : RDONLY;
				un->un_state = ST_STATE_OPEN_PENDING_IO;
			} else {
				un->un_state = ST_STATE_OFFLINE;
			}
			break;
		case logical:
			if (un->un_pos.lgclblkno == 0) {
				un->un_state = ST_STATE_OFFLINE;
			} else {
				un->un_read_only =
				    (un->un_oflags & FWRITE) ? RDWR : RDONLY;
				un->un_state = ST_STATE_OPEN_PENDING_IO;
			}
			break;
		}
		rval = 0;
	} else {
		/*
		 * Not opening O_NDELAY.
		 */
		un->un_state = ST_STATE_OPENING;

		/*
		 * Clear error entry stack
		 */
		st_empty_error_stack(un);

		rval = st_tape_init(un);
		if ((rval == EACCES) && (un->un_read_only & WORM)) {
			un->un_state = ST_STATE_OPEN_PENDING_IO;
			rval = 0; /* so open doesn't fail */
		} else if (rval) {
			/*
			 * Release the tape unit, if reserved and not
			 * preserve reserve.
			 */
			if ((un->un_rsvd_status &
			    (ST_RESERVE | ST_PRESERVE_RESERVE)) == ST_RESERVE) {
				(void) st_reserve_release(un, ST_RELEASE,
				    st_uscsi_cmd);
			}
		} else {
			un->un_state = ST_STATE_OPEN_PENDING_IO;
		}
	}

exit:
	/*
	 * we don't want any uninvited guests scrogging our data when we're
	 * busy with something, so for successful opens or failed opens
	 * (except for EBUSY), reset these counters and state appropriately.
	 */
	if (rval != EBUSY) {
		if (rval) {
			un->un_state = ST_STATE_CLOSED;
		}
		un->un_err_resid = 0;
		un->un_retry_ct = 0;
	}
busy:
	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_open: return val = %x, state = %d\n", rval, un->un_state);
	mutex_exit(ST_MUTEX);
	return (rval);

}

static int
st_tape_init(struct scsi_tape *un)
{
	int err;
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_tape_init);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_tape_init(un = 0x%p, oflags = %d)\n", (void*)un, un->un_oflags);

	/*
	 * Clean up after any errors left by 'last' close.
	 * This also handles the case of the initial open.
	 */
	if (un->un_state != ST_STATE_INITIALIZING) {
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_OPENING;
	}

	un->un_kbytes_xferred = 0;

	/*
	 * do a throw away TUR to clear check condition
	 */
	err = st_cmd(un, SCMD_TEST_UNIT_READY, 0, SYNC_CMD);

	/*
	 * If test unit ready fails because the drive is reserved
	 * by another host fail the open for no access.
	 */
	if (err) {
		if (un->un_rsvd_status & ST_RESERVATION_CONFLICT) {
			un->un_state = ST_STATE_CLOSED;
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_tape_init: RESERVATION CONFLICT\n");
			rval = EACCES;
			goto exit;
		} else if ((un->un_rsvd_status &
		    ST_APPLICATION_RESERVATIONS) != 0) {
			if ((ST_RQSENSE != NULL) &&
			    (ST_RQSENSE->es_add_code == 0x2a &&
			    ST_RQSENSE->es_qual_code == 0x03)) {
				un->un_state = ST_STATE_CLOSED;
				rval = EACCES;
				goto exit;
			}
		}
	}

	/*
	 * Tape self identification could fail if the tape drive is used by
	 * another host during attach time. We try to get the tape type
	 * again. This is also applied to any posponed configuration methods.
	 */
	if (un->un_dp->type == ST_TYPE_INVALID) {
		un->un_comp_page = ST_DEV_DATACOMP_PAGE | ST_DEV_CONFIG_PAGE;
		st_known_tape_type(un);
	}

	/*
	 * If the tape type is still invalid, try to determine the generic
	 * configuration.
	 */
	if (un->un_dp->type == ST_TYPE_INVALID) {
		rval = st_determine_generic(un);
		if (rval) {
			if (rval != EACCES) {
				rval = EIO;
			}
			un->un_state = ST_STATE_CLOSED;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_tape_init: %s invalid type\n",
			    rval == EACCES ? "EACCES" : "EIO");
			goto exit;
		}
		/*
		 * If this is a Unknown Type drive,
		 * Use the READ BLOCK LIMITS to determine if
		 * allow large xfer is approprate if not globally
		 * disabled with st_allow_large_xfer.
		 */
		un->un_allow_large_xfer = (uchar_t)st_allow_large_xfer;
	} else {

		/*
		 * If we allow_large_xfer (ie >64k) and have not yet found out
		 * the max block size supported by the drive,
		 * find it by issueing a READ_BLKLIM command.
		 * if READ_BLKLIM cmd fails, assume drive doesn't
		 * allow_large_xfer and min/max block sizes as 1 byte and 63k.
		 */
		un->un_allow_large_xfer = st_allow_large_xfer &&
		    (un->un_dp->options & ST_NO_RECSIZE_LIMIT);
	}
	/*
	 * if maxbsize is unknown, set the maximum block size.
	 */
	if (un->un_maxbsize == MAXBSIZE_UNKNOWN) {

		/*
		 * Get the Block limits of the tape drive.
		 * if un->un_allow_large_xfer = 0 , then make sure
		 * that maxbsize is <= ST_MAXRECSIZE_FIXED.
		 */
		un->un_rbl = kmem_zalloc(RBLSIZE, KM_SLEEP);

		err = st_cmd(un, SCMD_READ_BLKLIM, RBLSIZE, SYNC_CMD);
		if (err) {
			/* Retry */
			err = st_cmd(un, SCMD_READ_BLKLIM, RBLSIZE, SYNC_CMD);
		}
		if (!err) {

			/*
			 * if cmd successful, use limit returned
			 */
			un->un_maxbsize = (un->un_rbl->max_hi << 16) +
			    (un->un_rbl->max_mid << 8) +
			    un->un_rbl->max_lo;
			un->un_minbsize = (un->un_rbl->min_hi << 8) +
			    un->un_rbl->min_lo;
			un->un_data_mod = 1 << un->un_rbl->granularity;
			if ((un->un_maxbsize == 0) ||
			    (un->un_allow_large_xfer == 0 &&
			    un->un_maxbsize > ST_MAXRECSIZE_FIXED)) {
				un->un_maxbsize = ST_MAXRECSIZE_FIXED;

			} else if (un->un_dp->type == ST_TYPE_DEFAULT) {
				/*
				 * Drive is not one that is configured, But the
				 * READ BLOCK LIMITS tells us it can do large
				 * xfers.
				 */
				if (un->un_maxbsize > ST_MAXRECSIZE_FIXED) {
					un->un_dp->options |=
					    ST_NO_RECSIZE_LIMIT;
				}
				/*
				 * If max and mimimum block limits are the
				 * same this is a fixed block size device.
				 */
				if (un->un_maxbsize == un->un_minbsize) {
					un->un_dp->options &= ~ST_VARIABLE;
				}
			}

			if (un->un_minbsize == 0) {
				un->un_minbsize = 1;
			}

		} else { /* error on read block limits */

			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "!st_tape_init: Error on READ BLOCK LIMITS,"
			    " errno = %d un_rsvd_status = 0x%X\n",
			    err, un->un_rsvd_status);

			/*
			 * since read block limits cmd failed,
			 * do not allow large xfers.
			 * use old values in st_minphys
			 */
			if (un->un_rsvd_status & ST_RESERVATION_CONFLICT) {
				rval = EACCES;
			} else {
				un->un_allow_large_xfer = 0;
				scsi_log(ST_DEVINFO, st_label, CE_NOTE,
				    "!Disabling large transfers\n");

				/*
				 * we guess maxbsize and minbsize
				 */
				if (un->un_bsize) {
					un->un_maxbsize = un->un_minbsize =
					    un->un_bsize;
				} else {
					un->un_maxbsize = ST_MAXRECSIZE_FIXED;
					un->un_minbsize = 1;
				}
				/*
				 * Data Mod must be set,
				 * Even if read block limits fails.
				 * Prevents Divide By Zero in st_rw().
				 */
				un->un_data_mod = 1;
			}
		}
		if (un->un_rbl) {
			kmem_free(un->un_rbl, RBLSIZE);
			un->un_rbl = NULL;
		}

		if (rval) {
			goto exit;
		}
	}

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "maxdma = %d, maxbsize = %d, minbsize = %d, %s large xfer\n",
	    un->un_maxdma, un->un_maxbsize, un->un_minbsize,
	    (un->un_allow_large_xfer ? "ALLOW": "DON'T ALLOW"));

	err = st_cmd(un, SCMD_TEST_UNIT_READY, 0, SYNC_CMD);

	if (err != 0) {
		if (err == EINTR) {
			un->un_laststate = un->un_state;
			un->un_state = ST_STATE_CLOSED;
			rval = EINTR;
			goto exit;
		}
		/*
		 * Make sure the tape is ready
		 */
		un->un_pos.pmode = invalid;
		if (un->un_status != KEY_UNIT_ATTENTION) {
			/*
			 * allow open no media.  Subsequent MTIOCSTATE
			 * with media present will complete the open
			 * logic.
			 */
			un->un_laststate = un->un_state;
			if (un->un_oflags & (FNONBLOCK|FNDELAY)) {
				un->un_mediastate = MTIO_EJECTED;
				un->un_state = ST_STATE_OFFLINE;
				rval = 0;
				goto exit;
			} else {
				un->un_state = ST_STATE_CLOSED;
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_tape_init EIO no media, not opened "
				    "O_NONBLOCK|O_EXCL\n");
				rval = EIO;
				goto exit;
			}
		}
	}

	/*
	 * On each open, initialize block size from drivetype struct,
	 * as it could have been changed by MTSRSZ ioctl.
	 * Now, ST_VARIABLE simply means drive is capable of variable
	 * mode. All drives are assumed to support fixed records.
	 * Hence, un_bsize tells what mode the drive is in.
	 *	un_bsize	= 0	- variable record length
	 *			= x	- fixed record length is x
	 */
	un->un_bsize = un->un_dp->bsize;

	/*
	 * If saved position is valid go there
	 */
	if (un->un_restore_pos) {
		un->un_restore_pos = 0;
		un->un_pos.fileno = un->un_save_fileno;
		un->un_pos.blkno = un->un_save_blkno;
		rval = st_validate_tapemarks(un, st_uscsi_cmd, &un->un_pos);
		if (rval != 0) {
			if (rval != EACCES) {
				rval = EIO;
			}
			un->un_laststate = un->un_state;
			un->un_state = ST_STATE_CLOSED;
			goto exit;
		}
	}

	if (un->un_pos.pmode == invalid) {
		rval = st_loadtape(un);
		if (rval) {
			if (rval != EACCES) {
				rval = EIO;
			}
			un->un_laststate = un->un_state;
			un->un_state = ST_STATE_CLOSED;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_tape_init: %s can't open tape\n",
			    rval == EACCES ? "EACCES" : "EIO");
			goto exit;
		}
	}

	/*
	 * do a mode sense to pick up state of current write-protect,
	 * Could cause reserve and fail due to conflict.
	 */
	if (un->un_unit_attention_flags) {
		rval = st_modesense(un);
		if (rval == EACCES) {
			goto exit;
		}
	}

	/*
	 * If we are opening the tape for writing, check
	 * to make sure that the tape can be written.
	 */
	if (un->un_oflags & FWRITE) {
		err = 0;
		if (un->un_mspl->wp) {
			un->un_status = KEY_WRITE_PROTECT;
			un->un_laststate = un->un_state;
			un->un_state = ST_STATE_CLOSED;
			rval = EACCES;
			/*
			 * STK sets the wp bit if volsafe tape is loaded.
			 */
			if ((un->un_dp->type == MT_ISSTK9840) &&
			    (un->un_dp->options & ST_WORMABLE)) {
				un->un_read_only = RDONLY;
			} else {
				goto exit;
			}
		} else {
			un->un_read_only = RDWR;
		}
	} else {
		un->un_read_only = RDONLY;
	}

	if (un->un_dp->options & ST_WORMABLE &&
	    un->un_unit_attention_flags) {
		un->un_read_only |= un->un_wormable(un);

		if (((un->un_read_only == WORM) ||
		    (un->un_read_only == RDWORM)) &&
		    ((un->un_oflags & FWRITE) == FWRITE)) {
			un->un_status = KEY_DATA_PROTECT;
			rval = EACCES;
			ST_DEBUG4(ST_DEVINFO, st_label, CE_NOTE,
			    "read_only = %d eof = %d oflag = %d\n",
			    un->un_read_only, un->un_pos.eof, un->un_oflags);
		}
	}

	/*
	 * If we're opening the tape write-only, we need to
	 * write 2 filemarks on the HP 1/2 inch drive, to
	 * create a null file.
	 */
	if ((un->un_read_only == RDWR) ||
	    (un->un_read_only == WORM) && (un->un_oflags & FWRITE)) {
		if (un->un_dp->options & ST_REEL) {
			un->un_fmneeded = 2;
		} else {
			un->un_fmneeded = 1;
		}
	} else {
		un->un_fmneeded = 0;
	}

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "fmneeded = %x\n", un->un_fmneeded);

	/*
	 * Make sure the density can be selected correctly.
	 * If WORM can only write at the append point which in most cases
	 * isn't BOP. st_determine_density() with a B_WRITE only attempts
	 * to set and try densities if a BOP.
	 */
	if (st_determine_density(un,
	    un->un_read_only == RDWR ? B_WRITE : B_READ)) {
		un->un_status = KEY_ILLEGAL_REQUEST;
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_CLOSED;
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_tape_init: EIO can't determine density\n");
		rval = EIO;
		goto exit;
	}

	/*
	 * Destroy the knowledge that we have 'determined'
	 * density so that a later read at BOT comes along
	 * does the right density determination.
	 */

	un->un_density_known = 0;


	/*
	 * Okay, the tape is loaded and either at BOT or somewhere past.
	 * Mark the state such that any I/O or tape space operations
	 * will get/set the right density, etc..
	 */
	un->un_laststate = un->un_state;
	un->un_lastop = ST_OP_NIL;
	un->un_mediastate = MTIO_INSERTED;
	cv_broadcast(&un->un_state_cv);

	/*
	 *  Set test append flag if writing.
	 *  First write must check that tape is positioned correctly.
	 */
	un->un_test_append = (un->un_oflags & FWRITE);

	/*
	 * if there are pending unit attention flags.
	 * Check that the media has not changed.
	 */
	if (un->un_unit_attention_flags) {
		rval = st_get_media_identification(un, st_uscsi_cmd);
		if (rval != 0 && rval != EACCES) {
			rval = EIO;
		}
		un->un_unit_attention_flags = 0;
	}

exit:
	un->un_err_resid = 0;
	un->un_last_resid = 0;
	un->un_last_count = 0;

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_tape_init: return val = %x\n", rval);
	return (rval);

}



/* ARGSUSED */
static int
st_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
	int err = 0;
	int count, last_state;
	minor_t minor = getminor(dev);
#ifdef	__x86
	struct contig_mem *cp, *cp_temp;
#endif

	GET_SOFT_STATE(dev);

	ST_ENTR(ST_DEVINFO, st_close);

	/*
	 * wait till all cmds in the pipeline have been completed
	 */
	mutex_enter(ST_MUTEX);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_close(dev = 0x%lx, flag = %d, otyp = %d)\n", dev, flag, otyp);

	st_wait_for_io(un);

	/* turn off persistent errors on close, as we want close to succeed */
	st_turn_pe_off(un);

	/*
	 * set state to indicate that we are in process of closing
	 */
	last_state = un->un_laststate = un->un_state;
	un->un_state = ST_STATE_CLOSING;

	ST_POS(ST_DEVINFO, "st_close1:", &un->un_pos);

	/*
	 * BSD behavior:
	 * a close always causes a silent span to the next file if we've hit
	 * an EOF (but not yet read across it).
	 */
	if ((minor & MT_BSD) && (un->un_pos.eof == ST_EOF)) {
		if (un->un_pos.pmode != invalid) {
			un->un_pos.fileno++;
			un->un_pos.blkno = 0;
		}
		un->un_pos.eof = ST_NO_EOF;
	}

	/*
	 * SVR4 behavior for skipping to next file:
	 *
	 * If we have not seen a filemark, space to the next file
	 *
	 * If we have already seen the filemark we are physically in the next
	 * file and we only increment the filenumber
	 */
	if (((minor & (MT_BSD | MT_NOREWIND)) == MT_NOREWIND) &&
	    (flag & FREAD) &&		/* reading or at least asked to */
	    (un->un_mediastate == MTIO_INSERTED) &&	/* tape loaded */
	    (un->un_pos.pmode != invalid) &&		/* XXX position known */
	    ((un->un_pos.blkno != 0) && 		/* inside a file */
	    (un->un_lastop != ST_OP_WRITE) &&		/* Didn't just write */
	    (un->un_lastop != ST_OP_WEOF))) {		/* or write filemarks */
		switch (un->un_pos.eof) {
		case ST_NO_EOF:
			/*
			 * if we were reading and did not read the complete file
			 * skip to the next file, leaving the tape correctly
			 * positioned to read the first record of the next file
			 * Check first for REEL if we are at EOT by trying to
			 * read a block
			 */
			if ((un->un_dp->options & ST_REEL) &&
			    (!(un->un_dp->options & ST_READ_IGNORE_EOFS)) &&
			    (un->un_pos.blkno == 0)) {
				if (st_cmd(un, SCMD_SPACE, Blk(1), SYNC_CMD)) {
					ST_DEBUG2(ST_DEVINFO, st_label,
					    SCSI_DEBUG,
					    "st_close : EIO can't space\n");
					err = EIO;
					goto error_out;
				}
				if (un->un_pos.eof >= ST_EOF_PENDING) {
					un->un_pos.eof = ST_EOT_PENDING;
					un->un_pos.fileno += 1;
					un->un_pos.blkno   = 0;
					break;
				}
			}
			if (st_cmd(un, SCMD_SPACE, Fmk(1), SYNC_CMD)) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_close: EIO can't space #2\n");
				err = EIO;
				goto error_out;
			} else {
				ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_close2: fileno=%x,blkno=%x,eof=%x\n",
				    un->un_pos.fileno, un->un_pos.blkno,
				    un->un_pos.eof);
				un->un_pos.eof = ST_NO_EOF;
			}
			break;

		case ST_EOF_PENDING:
		case ST_EOF:
			un->un_pos.fileno += 1;
			un->un_pos.lgclblkno += 1;
			un->un_pos.blkno   = 0;
			un->un_pos.eof = ST_NO_EOF;
			break;

		case ST_EOT:
		case ST_EOT_PENDING:
		case ST_EOM:
			/* nothing to do */
			break;
		default:
			ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
			    "Undefined state 0x%x", un->un_pos.eof);

		}
	}


	/*
	 * For performance reasons (HP 88780), the driver should
	 * postpone writing the second tape mark until just before a file
	 * positioning ioctl is issued (e.g., rewind).	This means that
	 * the user must not manually rewind the tape because the tape will
	 * be missing the second tape mark which marks EOM.
	 * However, this small performance improvement is not worth the risk.
	 */

	/*
	 * We need to back up over the filemark we inadvertently popped
	 * over doing a read in between the two filemarks that constitute
	 * logical eot for 1/2" tapes. Note that ST_EOT_PENDING is only
	 * set while reading.
	 *
	 * If we happen to be at physical eot (ST_EOM) (writing case),
	 * the writing of filemark(s) will clear the ST_EOM state, which
	 * we don't want, so we save this state and restore it later.
	 */

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "flag=%x, fmneeded=%x, lastop=%x, eof=%x\n",
	    flag, un->un_fmneeded, un->un_lastop, un->un_pos.eof);

	if (un->un_pos.eof == ST_EOT_PENDING) {
		if (minor & MT_NOREWIND) {
			if (st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD)) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_close: EIO can't space #3\n");
				err = EIO;
				goto error_out;
			} else {
				un->un_pos.blkno = 0;
				un->un_pos.eof = ST_EOT;
			}
		} else {
			un->un_pos.eof = ST_NO_EOF;
		}

	/*
	 * Do we need to write a file mark?
	 *
	 * only write filemarks if there are fmks to be written and
	 *   - open for write (possibly read/write)
	 *   - the last operation was a write
	 * or:
	 *   -	opened for wronly
	 *   -	no data was written
	 */
	} else if ((un->un_pos.pmode != invalid) &&
	    (un->un_fmneeded > 0) &&
	    (((flag & FWRITE) &&
	    ((un->un_lastop == ST_OP_WRITE)||(un->un_lastop == ST_OP_WEOF))) ||
	    ((flag == FWRITE) && (un->un_lastop == ST_OP_NIL)))) {

		/* save ST_EOM state */
		int was_at_eom = (un->un_pos.eof == ST_EOM) ? 1 : 0;

		/*
		 * Note that we will write a filemark if we had opened
		 * the tape write only and no data was written, thus
		 * creating a null file.
		 *
		 * If the user already wrote one, we only have to write 1 more.
		 * If they wrote two, we don't have to write any.
		 */

		count = un->un_fmneeded;
		if (count > 0) {
			if (st_cmd(un, SCMD_WRITE_FILE_MARK, count, SYNC_CMD)) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_close : EIO can't wfm\n");
				err = EIO;
				goto error_out;
			}
			if ((un->un_dp->options & ST_REEL) &&
			    (minor & MT_NOREWIND)) {
				if (st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD)) {
					ST_DEBUG2(ST_DEVINFO, st_label,
					    SCSI_DEBUG,
					    "st_close : EIO space fmk(-1)\n");
					err = EIO;
					goto error_out;
				}
				un->un_pos.eof = ST_NO_EOF;
				/* fix up block number */
				un->un_pos.blkno = 0;
			}
		}

		/*
		 * If we aren't going to be rewinding, and we were at
		 * physical eot, restore the state that indicates we
		 * are at physical eot. Once you have reached physical
		 * eot, and you close the tape, the only thing you can
		 * do on the next open is to rewind. Access to trailer
		 * records is only allowed without closing the device.
		 */
		if ((minor & MT_NOREWIND) == 0 && was_at_eom) {
			un->un_pos.eof = ST_EOM;
		}
	}

	/*
	 * report soft errors if enabled and available, if we never accessed
	 * the drive, don't get errors. This will prevent some DAT error
	 * messages upon LOG SENSE.
	 */
	if (st_report_soft_errors_on_close &&
	    (un->un_dp->options & ST_SOFT_ERROR_REPORTING) &&
	    (last_state != ST_STATE_OFFLINE)) {
		if (st_report_soft_errors(dev, flag)) {
			err = EIO;
			goto error_out;
		}
	}


	/*
	 * Do we need to rewind? Can we rewind?
	 */
	if ((minor & MT_NOREWIND) == 0 &&
	    un->un_pos.pmode != invalid && err == 0) {
		/*
		 * We'd like to rewind with the
		 * 'immediate' bit set, but this
		 * causes problems on some drives
		 * where subsequent opens get a
		 * 'NOT READY' error condition
		 * back while the tape is rewinding,
		 * which is impossible to distinguish
		 * from the condition of 'no tape loaded'.
		 *
		 * Also, for some targets, if you disconnect
		 * with the 'immediate' bit set, you don't
		 * actually return right away, i.e., the
		 * target ignores your request for immediate
		 * return.
		 *
		 * Instead, we'll fire off an async rewind
		 * command. We'll mark the device as closed,
		 * and any subsequent open will stall on
		 * the first TEST_UNIT_READY until the rewind
		 * completes.
		 */

		/*
		 * Used to be if reserve was not supported we'd send an
		 * asynchronious rewind. Comments above may be slightly invalid
		 * as the immediate bit was never set. Doing an immedate rewind
		 * makes sense, I think fixes to not ready status might handle
		 * the problems described above.
		 */
		if (un->un_sd->sd_inq->inq_ansi < 2) {
			if (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD)) {
				err = EIO;
			}
		} else {
			/* flush data for older drives per scsi spec. */
			if (st_cmd(un, SCMD_WRITE_FILE_MARK, 0, SYNC_CMD)) {
				err = EIO;
			} else {
				/* release the drive before rewind immediate */
				if ((un->un_rsvd_status &
				    (ST_RESERVE | ST_PRESERVE_RESERVE)) ==
				    ST_RESERVE) {
					if (st_reserve_release(un, ST_RELEASE,
					    st_uscsi_cmd)) {
						err = EIO;
					}
				}

				/* send rewind with immediate bit set */
				if (st_cmd(un, SCMD_REWIND, 1, ASYNC_CMD)) {
					err = EIO;
				}
			}
		}
		/*
		 * Setting positions invalid in case the rewind doesn't
		 * happen. Drives don't like to rewind if resets happen
		 * they will tend to move back to where the rewind was
		 * issued if a reset or something happens so that if a
		 * write happens the data doesn't get clobbered.
		 *
		 * Not a big deal if the position is invalid when the
		 * open occures it will do a read position.
		 */
		un->un_pos.pmode = invalid;
		un->un_running.pmode = invalid;

		if (err == EIO) {
			goto error_out;
		}
	}

	/*
	 * eject tape if necessary
	 */
	if (un->un_eject_tape_on_failure) {
		un->un_eject_tape_on_failure = 0;
		if (st_cmd(un, SCMD_LOAD, LD_UNLOAD, SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_close : can't unload tape\n");
			err = EIO;
			goto error_out;
		} else {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_close : tape unloaded \n");
			un->un_pos.eof = ST_NO_EOF;
			un->un_mediastate = MTIO_EJECTED;
		}
	}
	/*
	 * Release the tape unit, if default reserve/release
	 * behaviour.
	 */
	if ((un->un_rsvd_status &
	    (ST_RESERVE | ST_PRESERVE_RESERVE |
	    ST_APPLICATION_RESERVATIONS)) == ST_RESERVE) {
		(void) st_reserve_release(un, ST_RELEASE, st_uscsi_cmd);
	}
error_out:
	/*
	 * clear up state
	 */
	un->un_laststate = un->un_state;
	un->un_state = ST_STATE_CLOSED;
	un->un_lastop = ST_OP_NIL;
	un->un_throttle = 1;	/* assume one request at time, for now */
	un->un_retry_ct = 0;
	un->un_errno = 0;
	un->un_swr_token = (opaque_t)NULL;
	un->un_rsvd_status &= ~(ST_INIT_RESERVE);

	/* Restore the options to the init time settings */
	if (un->un_init_options & ST_READ_IGNORE_ILI) {
		un->un_dp->options |= ST_READ_IGNORE_ILI;
	} else {
		un->un_dp->options &= ~ST_READ_IGNORE_ILI;
	}

	if (un->un_init_options & ST_READ_IGNORE_EOFS) {
		un->un_dp->options |= ST_READ_IGNORE_EOFS;
	} else {
		un->un_dp->options &= ~ST_READ_IGNORE_EOFS;
	}

	if (un->un_init_options & ST_SHORT_FILEMARKS) {
		un->un_dp->options |= ST_SHORT_FILEMARKS;
	} else {
		un->un_dp->options &= ~ST_SHORT_FILEMARKS;
	}

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * Signal anyone awaiting a close operation to complete.
	 */
	cv_signal(&un->un_clscv);

	/*
	 * any kind of error on closing causes all state to be tossed
	 */
	if (err && un->un_status != KEY_ILLEGAL_REQUEST) {
		/*
		 * note that st_intr has already set
		 * un_pos.pmode to invalid.
		 */
		un->un_density_known = 0;
	}

#ifdef	__x86
	/*
	 * free any contiguous mem alloc'ed for big block I/O
	 */
	cp = un->un_contig_mem;
	while (cp) {
		if (cp->cm_addr) {
			ddi_dma_mem_free(&cp->cm_acc_hdl);
		}
		cp_temp = cp;
		cp = cp->cm_next;
		kmem_free(cp_temp,
		    sizeof (struct contig_mem) + biosize());
	}
	un->un_contig_mem_total_num = 0;
	un->un_contig_mem_available_num = 0;
	un->un_contig_mem = NULL;
	un->un_max_contig_mem_len = 0;
#endif

	ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_close3: return val = %x, fileno=%x, blkno=%x, eof=%x\n",
	    err, un->un_pos.fileno, un->un_pos.blkno, un->un_pos.eof);

	mutex_exit(ST_MUTEX);
	return (err);
}

/*
 * These routines perform raw i/o operations.
 */

/* ARGSUSED2 */
static int
st_aread(dev_t dev, struct aio_req *aio, cred_t *cred_p)
{
#ifdef STDEBUG
	GET_SOFT_STATE(dev);
	ST_ENTR(ST_DEVINFO, st_aread);
#endif
	return (st_arw(dev, aio, B_READ));
}


/* ARGSUSED2 */
static int
st_awrite(dev_t dev, struct aio_req *aio, cred_t *cred_p)
{
#ifdef STDEBUG
	GET_SOFT_STATE(dev);
	ST_ENTR(ST_DEVINFO, st_awrite);
#endif
	return (st_arw(dev, aio, B_WRITE));
}



/* ARGSUSED */
static int
st_read(dev_t dev, struct uio *uiop, cred_t *cred_p)
{
#ifdef STDEBUG
	GET_SOFT_STATE(dev);
	ST_ENTR(ST_DEVINFO, st_read);
#endif
	return (st_rw(dev, uiop, B_READ));
}

/* ARGSUSED */
static int
st_write(dev_t dev, struct uio *uiop, cred_t *cred_p)
{
#ifdef STDEBUG
	GET_SOFT_STATE(dev);
	ST_ENTR(ST_DEVINFO, st_write);
#endif
	return (st_rw(dev, uiop, B_WRITE));
}

/*
 * Due to historical reasons, old limits are: For variable-length devices:
 * if greater than 64KB - 1 (ST_MAXRECSIZE_VARIABLE), block into 64 KB - 2
 * ST_MAXRECSIZE_VARIABLE_LIMIT) requests; otherwise,
 * (let it through unmodified. For fixed-length record devices:
 * 63K (ST_MAXRECSIZE_FIXED) is max (default minphys).
 *
 * The new limits used are un_maxdma (retrieved using scsi_ifgetcap()
 * from the HBA) and un_maxbsize (retrieved by sending SCMD_READ_BLKLIM
 * command to the drive).
 *
 */
static void
st_minphys(struct buf *bp)
{
	struct scsi_tape *un;

	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));

	ST_FUNC(ST_DEVINFO, st_minphys);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_minphys(bp = 0x%p): b_bcount = 0x%lx\n", (void *)bp,
	    bp->b_bcount);

	if (un->un_allow_large_xfer) {

		/*
		 * check un_maxbsize for variable length devices only
		 */
		if (un->un_bsize == 0 && bp->b_bcount > un->un_maxbsize) {
			bp->b_bcount = un->un_maxbsize;
		}
		/*
		 * can't go more that HBA maxdma limit in either fixed-length
		 * or variable-length tape drives.
		 */
		if (bp->b_bcount > un->un_maxdma) {
			bp->b_bcount = un->un_maxdma;
		}
	} else {

		/*
		 *  use old fixed limits
		 */
		if (un->un_bsize == 0) {
			if (bp->b_bcount > ST_MAXRECSIZE_VARIABLE) {
				bp->b_bcount = ST_MAXRECSIZE_VARIABLE_LIMIT;
			}
		} else {
			if (bp->b_bcount > ST_MAXRECSIZE_FIXED) {
				bp->b_bcount = ST_MAXRECSIZE_FIXED;
			}
		}
	}

	/*
	 * For regular raw I/O and Fixed Block length devices, make sure
	 * the adjusted block count is a whole multiple of the device
	 * block size.
	 */
	if (bp != un->un_sbufp && un->un_bsize) {
		bp->b_bcount -= (bp->b_bcount % un->un_bsize);
	}
}

static int
st_rw(dev_t dev, struct uio *uio, int flag)
{
	int rval = 0;
	long len;

	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_rw);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_rw(dev = 0x%lx, flag = %s)\n", dev,
	    (flag == B_READ ? rd_str: wr_str));

	/* get local copy of transfer length */
	len = uio->uio_iov->iov_len;

	mutex_enter(ST_MUTEX);

	/*
	 * Clear error entry stack
	 */
	st_empty_error_stack(un);

	/*
	 * If in fixed block size mode and requested read or write
	 * is not an even multiple of that block size.
	 */
	if ((un->un_bsize != 0) && (len % un->un_bsize != 0)) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "%s: not modulo %d block size\n",
		    (flag == B_WRITE) ? wr_str : rd_str, un->un_bsize);
		rval = EINVAL;
	}

	/* If device has set granularity in the READ_BLKLIM we honor it. */
	if ((un->un_data_mod != 0) && (len % un->un_data_mod != 0)) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "%s: not modulo %d device granularity\n",
		    (flag == B_WRITE) ? wr_str : rd_str, un->un_data_mod);
		rval = EINVAL;
	}

	if (st_recov_sz != sizeof (recov_info) && un->un_multipath) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN, mp_misconf);
		rval = EFAULT;
	}

	if (rval != 0) {
		un->un_errno = rval;
		mutex_exit(ST_MUTEX);
		return (rval);
	}

	/*
	 * Reset this so it can be set if Berkeley and read over a filemark.
	 */
	un->un_silent_skip = 0;
	mutex_exit(ST_MUTEX);

	len = uio->uio_resid;

	rval = physio(st_queued_strategy, (struct buf *)NULL,
	    dev, flag, st_minphys, uio);
	/*
	 * if we have hit logical EOT during this xfer and there is not a
	 * full residue, then set eof back  to ST_EOM to make sure that
	 * the user will see at least one zero write
	 * after this short write
	 */
	mutex_enter(ST_MUTEX);
	if (un->un_pos.eof > ST_NO_EOF) {
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		"eof=%d resid=%lx\n", un->un_pos.eof, uio->uio_resid);
	}
	if (un->un_pos.eof >= ST_EOM && (flag == B_WRITE)) {
		if ((uio->uio_resid != len) && (uio->uio_resid != 0)) {
			un->un_pos.eof = ST_EOM;
		} else if (uio->uio_resid == len) {
			un->un_pos.eof = ST_NO_EOF;
		}
	}

	if (un->un_silent_skip && uio->uio_resid != len) {
		un->un_pos.eof = ST_EOF;
		un->un_pos.blkno = un->un_save_blkno;
		un->un_pos.fileno--;
	}

	un->un_errno = rval;

	mutex_exit(ST_MUTEX);

	return (rval);
}

static int
st_arw(dev_t dev, struct aio_req *aio, int flag)
{
	struct uio *uio = aio->aio_uio;
	int rval = 0;
	long len;

	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_arw);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_arw(dev = 0x%lx, flag = %s)\n", dev,
	    (flag == B_READ ? rd_str: wr_str));

	/* get local copy of transfer length */
	len = uio->uio_iov->iov_len;

	mutex_enter(ST_MUTEX);

	/*
	 * If in fixed block size mode and requested read or write
	 * is not an even multiple of that block size.
	 */
	if ((un->un_bsize != 0) && (len % un->un_bsize != 0)) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "%s: not modulo %d block size\n",
		    (flag == B_WRITE) ? wr_str : rd_str, un->un_bsize);
		rval = EINVAL;
	}

	/* If device has set granularity in the READ_BLKLIM we honor it. */
	if ((un->un_data_mod != 0) && (len % un->un_data_mod != 0)) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "%s: not modulo %d device granularity\n",
		    (flag == B_WRITE) ? wr_str : rd_str, un->un_data_mod);
		rval = EINVAL;
	}

	if (st_recov_sz != sizeof (recov_info) && un->un_multipath) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN, mp_misconf);
		rval = EFAULT;
	}

	if (rval != 0) {
		un->un_errno = rval;
		mutex_exit(ST_MUTEX);
		return (rval);
	}

	mutex_exit(ST_MUTEX);

	len = uio->uio_resid;

	rval =
	    aphysio(st_queued_strategy, anocancel, dev, flag, st_minphys, aio);

	/*
	 * if we have hit logical EOT during this xfer and there is not a
	 * full residue, then set eof back  to ST_EOM to make sure that
	 * the user will see at least one zero write
	 * after this short write
	 *
	 * we keep this here just in case the application is not using
	 * persistent errors
	 */
	mutex_enter(ST_MUTEX);
	if (un->un_pos.eof > ST_NO_EOF) {
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "eof=%d resid=%lx\n", un->un_pos.eof, uio->uio_resid);
	}
	if (un->un_pos.eof >= ST_EOM && (flag == B_WRITE)) {
		if ((uio->uio_resid != len) && (uio->uio_resid != 0)) {
			un->un_pos.eof = ST_EOM;
		} else if (uio->uio_resid == len &&
		    !(un->un_persistence && un->un_persist_errors)) {
			un->un_pos.eof = ST_NO_EOF;
		}
	}
	un->un_errno = rval;
	mutex_exit(ST_MUTEX);

	return (rval);
}



static int
st_queued_strategy(buf_t *bp)
{
	struct scsi_tape *un;
	char reading = bp->b_flags & B_READ;
	int wasopening = 0;

	/*
	 * validate arguments
	 */
	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));
	if (un == NULL) {
		bp->b_resid = bp->b_bcount;
		bioerror(bp, ENXIO);
		ST_DEBUG6(NULL, st_label, SCSI_DEBUG,
		    "st_queued_strategy: ENXIO error exit\n");
		biodone(bp);
		return (0);
	}

	ST_ENTR(ST_DEVINFO, st_queued_strategy);

	mutex_enter(ST_MUTEX);

	while (un->un_pwr_mgmt == ST_PWR_SUSPENDED) {
		cv_wait(&un->un_suspend_cv, ST_MUTEX);
	}

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_queued_strategy(): bcount=0x%lx, fileno=%d, blkno=%x, eof=%d\n",
	    bp->b_bcount, un->un_pos.fileno, un->un_pos.blkno, un->un_pos.eof);

	/*
	 * If persistent errors have been flagged, just nix this one. We wait
	 * for any outstanding I/O's below, so we will be in order.
	 */
	if (un->un_persistence && un->un_persist_errors) {
		goto exit;
	}

	/*
	 * If last command was non queued, wait till it finishes.
	 */
	while (un->un_sbuf_busy) {
		cv_wait(&un->un_sbuf_cv, ST_MUTEX);
		/* woke up because of an error */
		if (un->un_persistence && un->un_persist_errors) {
			goto exit;
		}
	}

	/*
	 * s_buf and recovery commands shouldn't come here.
	 */
	ASSERT(bp != un->un_recov_buf);
	ASSERT(bp != un->un_sbufp);

	/*
	 * If we haven't done/checked reservation on the tape unit
	 * do it now.
	 */
	if ((un->un_rsvd_status &
	    (ST_RESERVE | ST_APPLICATION_RESERVATIONS)) == 0) {
		if ((un->un_dp->options & ST_NO_RESERVE_RELEASE) == 0) {
			if (st_reserve_release(un, ST_RESERVE, st_uscsi_cmd)) {
				st_bioerror(bp, un->un_errno);
				goto exit;
			}
		} else if (un->un_state == ST_STATE_OPEN_PENDING_IO) {
			/*
			 * Enter here to restore position for possible
			 * resets when the device was closed and opened
			 * in O_NDELAY mode subsequently
			 */
			un->un_state = ST_STATE_INITIALIZING;
			(void) st_cmd(un, SCMD_TEST_UNIT_READY,
			    0, SYNC_CMD);
			un->un_state = ST_STATE_OPEN_PENDING_IO;
		}
		un->un_rsvd_status |= ST_INIT_RESERVE;
	}

	/*
	 * If we are offline, we have to initialize everything first.
	 * This is to handle either when opened with O_NDELAY, or
	 * we just got a new tape in the drive, after an offline.
	 * We don't observe O_NDELAY past the open,
	 * as it will not make sense for tapes.
	 */
	if (un->un_state == ST_STATE_OFFLINE || un->un_restore_pos) {
		/*
		 * reset state to avoid recursion
		 */
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_INITIALIZING;
		if (st_tape_init(un)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "stioctl : OFFLINE init failure ");
			un->un_state = ST_STATE_OFFLINE;
			un->un_pos.pmode = invalid;
			goto b_done_err;
		}
		/* un_restore_pos make invalid */
		un->un_state = ST_STATE_OPEN_PENDING_IO;
		un->un_restore_pos = 0;
	}
	/*
	 * Check for legal operations
	 */
	if (un->un_pos.pmode == invalid) {
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "strategy with un->un_pos.pmode invalid\n");
		goto b_done_err;
	}

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_queued_strategy(): regular io\n");

	/*
	 * Process this first. If we were reading, and we're pending
	 * logical eot, that means we've bumped one file mark too far.
	 */

	/*
	 * Recursion warning: st_cmd will route back through here.
	 * Not anymore st_cmd will go through st_strategy()!
	 */
	if (un->un_pos.eof == ST_EOT_PENDING) {
		if (st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD)) {
			un->un_pos.pmode = invalid;
			un->un_density_known = 0;
			goto b_done_err;
		}
		un->un_pos.blkno = 0; /* fix up block number.. */
		un->un_pos.eof = ST_EOT;
	}

	/*
	 * If we are in the process of opening, we may have to
	 * determine/set the correct density. We also may have
	 * to do a test_append (if QIC) to see whether we are
	 * in a position to append to the end of the tape.
	 *
	 * If we're already at logical eot, we transition
	 * to ST_NO_EOF. If we're at physical eot, we punt
	 * to the switch statement below to handle.
	 */
	if ((un->un_state == ST_STATE_OPEN_PENDING_IO) ||
	    (un->un_test_append && (un->un_dp->options & ST_QIC))) {

		if (un->un_state == ST_STATE_OPEN_PENDING_IO) {
			if (st_determine_density(un, (int)reading)) {
				goto b_done_err;
			}
		}

		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "pending_io@fileno %d rw %d qic %d eof %d\n",
		    un->un_pos.fileno, (int)reading,
		    (un->un_dp->options & ST_QIC) ? 1 : 0,
		    un->un_pos.eof);

		if (!reading && un->un_pos.eof != ST_EOM) {
			if (un->un_pos.eof == ST_EOT) {
				un->un_pos.eof = ST_NO_EOF;
			} else if (un->un_pos.pmode != invalid &&
			    (un->un_dp->options & ST_QIC)) {
				/*
				 * st_test_append() will do it all
				 */
				st_test_append(bp);
				mutex_exit(ST_MUTEX);
				return (0);
			}
		}
		if (un->un_state == ST_STATE_OPEN_PENDING_IO) {
			wasopening = 1;
		}
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_OPEN;
	}


	/*
	 * Process rest of END OF FILE and END OF TAPE conditions
	 */

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "eof=%x, wasopening=%x\n",
	    un->un_pos.eof, wasopening);

	switch (un->un_pos.eof) {
	case ST_EOM:
		/*
		 * This allows writes to proceed past physical
		 * eot. We'll *really* be in trouble if the
		 * user continues blindly writing data too
		 * much past this point (unwind the tape).
		 * Physical eot really means 'early warning
		 * eot' in this context.
		 *
		 * Every other write from now on will succeed
		 * (if sufficient  tape left).
		 * This write will return with resid == count
		 * but the next one should be successful
		 *
		 * Note that we only transition to logical EOT
		 * if the last state wasn't the OPENING state.
		 * We explicitly prohibit running up to physical
		 * eot, closing the device, and then re-opening
		 * to proceed. Trailer records may only be gotten
		 * at by keeping the tape open after hitting eot.
		 *
		 * Also note that ST_EOM cannot be set by reading-
		 * this can only be set during writing. Reading
		 * up to the end of the tape gets a blank check
		 * or a double-filemark indication (ST_EOT_PENDING),
		 * and we prohibit reading after that point.
		 *
		 */
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "EOM\n");
		if (wasopening == 0) {
			/*
			 * this allows st_rw() to reset it back to
			 * will see a zero write
			 */
			un->un_pos.eof = ST_WRITE_AFTER_EOM;
		}
		un->un_status = SUN_KEY_EOT;
		goto b_done;

	case ST_WRITE_AFTER_EOM:
	case ST_EOT:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "EOT\n");
		un->un_status = SUN_KEY_EOT;
		if (SVR4_BEHAVIOR && reading) {
			goto b_done_err;
		}

		if (reading) {
			goto b_done;
		}
		un->un_pos.eof = ST_NO_EOF;
		break;

	case ST_EOF_PENDING:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "EOF PENDING\n");
		un->un_status = SUN_KEY_EOF;
		if (SVR4_BEHAVIOR) {
			un->un_pos.eof = ST_EOF;
			goto b_done;
		}
		/* FALLTHROUGH */
	case ST_EOF:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "EOF\n");
		un->un_status = SUN_KEY_EOF;
		if (SVR4_BEHAVIOR) {
			goto b_done_err;
		}

		if (BSD_BEHAVIOR) {
			un->un_pos.eof = ST_NO_EOF;
			un->un_pos.fileno += 1;
			un->un_pos.blkno   = 0;
		}

		if (reading) {
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "now file %d (read)\n",
			    un->un_pos.fileno);
			goto b_done;
		}
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "now file %d (write)\n", un->un_pos.fileno);
		break;
	default:
		un->un_status = 0;
		break;
	}

	bp->b_flags &= ~(B_DONE);
	st_bioerror(bp, 0);
	bp->av_forw = NULL;
	bp->b_resid = 0;
	SET_BP_PKT(bp, 0);


	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_queued_strategy: cmd=0x%p  count=%ld  resid=%ld flags=0x%x"
	    " pkt=0x%p\n",
	    (void *)bp->b_forw, bp->b_bcount,
	    bp->b_resid, bp->b_flags, (void *)BP_PKT(bp));

#ifdef	__x86
	/*
	 * We will replace bp with a new bp that can do big blk xfer
	 * if the requested xfer size is bigger than un->un_maxdma_arch
	 *
	 * Also, we need to make sure that we're handling real I/O
	 * by checking group 0/1 SCSI I/O commands, if needed
	 */
	if (bp->b_bcount > un->un_maxdma_arch &&
	    ((uchar_t)(uintptr_t)bp->b_forw == SCMD_READ ||
	    (uchar_t)(uintptr_t)bp->b_forw == SCMD_READ_G4 ||
	    (uchar_t)(uintptr_t)bp->b_forw == SCMD_WRITE ||
	    (uchar_t)(uintptr_t)bp->b_forw == SCMD_WRITE_G4)) {
		mutex_exit(ST_MUTEX);
		bp = st_get_bigblk_bp(bp);
		mutex_enter(ST_MUTEX);
	}
#endif

	/* put on wait queue */
	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_queued_strategy: un->un_quef = 0x%p, bp = 0x%p\n",
	    (void *)un->un_quef, (void *)bp);

	st_add_to_queue(&un->un_quef, &un->un_quel, un->un_quel, bp);

	ST_DO_KSTATS(bp, kstat_waitq_enter);

	st_start(un);

	mutex_exit(ST_MUTEX);
	return (0);

b_done_err:
	st_bioerror(bp, EIO);
	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_queued_strategy : EIO b_done_err\n");

b_done:
	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_queued_strategy: b_done\n");

exit:
	/*
	 * make sure no commands are outstanding or waiting before closing,
	 * so we can guarantee order
	 */
	st_wait_for_io(un);
	un->un_err_resid = bp->b_resid = bp->b_bcount;

	/* override errno here, if persistent errors were flagged */
	if (un->un_persistence && un->un_persist_errors)
		bioerror(bp, un->un_errno);

	mutex_exit(ST_MUTEX);

	biodone(bp);
	ASSERT(mutex_owned(ST_MUTEX) == 0);
	return (0);
}


static int
st_strategy(struct buf *bp)
{
	struct scsi_tape *un;

	/*
	 * validate arguments
	 */
	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));
	if (un == NULL) {
		bp->b_resid = bp->b_bcount;
		bioerror(bp, ENXIO);
		ST_DEBUG6(NULL, st_label, SCSI_DEBUG,
		    "st_strategy: ENXIO error exit\n");

		biodone(bp);
		return (0);

	}

	ST_ENTR(ST_DEVINFO, st_strategy);

	mutex_enter(ST_MUTEX);

	while (un->un_pwr_mgmt == ST_PWR_SUSPENDED) {
		cv_wait(&un->un_suspend_cv, ST_MUTEX);
	}

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_strategy(): bcount=0x%lx, fileno=%d, blkno=%x, eof=%d\n",
	    bp->b_bcount, un->un_pos.fileno, un->un_pos.blkno, un->un_pos.eof);

	ASSERT((bp == un->un_recov_buf) || (bp == un->un_sbufp));

	bp->b_flags &= ~(B_DONE);
	st_bioerror(bp, 0);
	bp->av_forw = NULL;
	bp->b_resid = 0;
	SET_BP_PKT(bp, 0);


	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_strategy: cmd=0x%x  count=%ld  resid=%ld flags=0x%x"
	    " pkt=0x%p\n",
	    (unsigned char)(uintptr_t)bp->b_forw, bp->b_bcount,
	    bp->b_resid, bp->b_flags, (void *)BP_PKT(bp));
	ST_DO_KSTATS(bp, kstat_waitq_enter);

	st_start(un);

	mutex_exit(ST_MUTEX);
	return (0);
}

/*
 * this routine spaces forward over filemarks
 */
static int
st_space_fmks(struct scsi_tape *un, int64_t count)
{
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_space_fmks);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_space_fmks(dev = 0x%lx, count = %"PRIx64")\n",
	    un->un_dev, count);

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * the risk with doing only one space operation is that we
	 * may accidentily jump in old data
	 * the exabyte 8500 reading 8200 tapes cannot use KNOWS_EOD
	 * because the 8200 does not append a marker; in order not to
	 * sacrifice the fast file skip, we do a slow skip if the low
	 * density device has been opened
	 */

	if ((un->un_dp->options & ST_KNOWS_EOD) &&
	    !((un->un_dp->type == ST_TYPE_EXB8500 &&
	    MT_DENSITY(un->un_dev) == 0))) {
		if (st_cmd(un, SCMD_SPACE, Fmk(count), SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "space_fmks : EIO can't do space cmd #1\n");
			rval = EIO;
		}
	} else {
		while (count > 0) {
			if (st_cmd(un, SCMD_SPACE, Fmk(1), SYNC_CMD)) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "space_fmks : EIO can't do space cmd #2\n");
				rval = EIO;
				break;
			}
			count -= 1;
			/*
			 * read a block to see if we have reached
			 * end of medium (double filemark for reel or
			 * medium error for others)
			 */
			if (count > 0) {
				if (st_cmd(un, SCMD_SPACE, Blk(1), SYNC_CMD)) {
					ST_DEBUG2(ST_DEVINFO, st_label,
					    SCSI_DEBUG,
					    "space_fmks : EIO can't do "
					    "space cmd #3\n");
					rval = EIO;
					break;
				}
				if ((un->un_pos.eof >= ST_EOF_PENDING) &&
				    (un->un_dp->options & ST_REEL)) {
					un->un_status = SUN_KEY_EOT;
					ST_DEBUG2(ST_DEVINFO, st_label,
					    SCSI_DEBUG,
					    "space_fmks : EIO ST_REEL\n");
					rval = EIO;
					break;
				} else if (IN_EOF(un->un_pos)) {
					un->un_pos.eof = ST_NO_EOF;
					un->un_pos.fileno++;
					un->un_pos.blkno = 0;
					count--;
				} else if (un->un_pos.eof > ST_EOF) {
					ST_DEBUG2(ST_DEVINFO, st_label,
					    SCSI_DEBUG,
					    "space_fmks, EIO > ST_EOF\n");
					rval = EIO;
					break;
				}

			}
		}
		un->un_err_resid = count;
		COPY_POS(&un->un_pos, &un->un_err_pos);
	}
	ASSERT(mutex_owned(ST_MUTEX));
	return (rval);
}

/*
 * this routine spaces to EOD
 *
 * it keeps track of the current filenumber and returns the filenumber after
 * the last successful space operation, we keep the number high because as
 * tapes are getting larger, the possibility of more and more files exist,
 * 0x100000 (1 Meg of files) probably will never have to be changed any time
 * soon
 */
#define	MAX_SKIP	0x100000 /* somewhat arbitrary */

static int
st_find_eod(struct scsi_tape *un)
{
	tapepos_t savepos;
	int64_t sp_type;
	int result;

	if (un == NULL) {
		return (-1);
	}

	ST_FUNC(ST_DEVINFO, st_find_eod);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_find_eod(dev = 0x%lx): fileno = %d\n", un->un_dev,
	    un->un_pos.fileno);

	ASSERT(mutex_owned(ST_MUTEX));

	COPY_POS(&savepos, &un->un_pos);

	/*
	 * see if the drive is smart enough to do the skips in
	 * one operation; 1/2" use two filemarks
	 * the exabyte 8500 reading 8200 tapes cannot use KNOWS_EOD
	 * because the 8200 does not append a marker; in order not to
	 * sacrifice the fast file skip, we do a slow skip if the low
	 * density device has been opened
	 */
	if ((un->un_dp->options & ST_KNOWS_EOD) != 0) {
		if ((un->un_dp->type == ST_TYPE_EXB8500) &&
		    (MT_DENSITY(un->un_dev) == 0)) {
			sp_type = Fmk(1);
		} else if (un->un_pos.pmode == logical) {
			sp_type = SPACE(SP_EOD, 0);
		} else {
			sp_type = Fmk(MAX_SKIP);
		}
	} else {
		sp_type = Fmk(1);
	}

	for (;;) {
		result = st_cmd(un, SCMD_SPACE, sp_type, SYNC_CMD);

		if (result == 0) {
			COPY_POS(&savepos, &un->un_pos);
		}

		if (sp_type == SPACE(SP_EOD, 0)) {
			if (result != 0) {
				sp_type = Fmk(MAX_SKIP);
				continue;
			}

			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_find_eod: 0x%"PRIx64"\n",
			    savepos.lgclblkno);
			/*
			 * What we return will become the current file position.
			 * After completing the space command with the position
			 * mode that is not invalid a read position command will
			 * be automaticly issued. If the drive support the long
			 * read position format a valid file position can be
			 * returned.
			 */
			return (un->un_pos.fileno);
		}

		if (result != 0) {
			break;
		}

		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "count=%"PRIx64", eof=%x, status=%x\n",
		    SPACE_CNT(sp_type),  un->un_pos.eof, un->un_status);

		/*
		 * If we're not EOM smart,  space a record
		 * to see whether we're now in the slot between
		 * the two sequential filemarks that logical
		 * EOM consists of (REEL) or hit nowhere land
		 * (8mm).
		 */
		if (sp_type == Fmk(1)) {
			/*
			 * no fast skipping, check a record
			 */
			if (st_cmd(un, SCMD_SPACE, Blk((1)), SYNC_CMD)) {
				break;
			}
			if ((un->un_pos.eof >= ST_EOF_PENDING) &&
			    (un->un_dp->options & ST_REEL)) {
				un->un_status = KEY_BLANK_CHECK;
				un->un_pos.fileno++;
				un->un_pos.blkno = 0;
				break;
			}
			if (IN_EOF(un->un_pos)) {
				un->un_pos.eof = ST_NO_EOF;
				un->un_pos.fileno++;
				un->un_pos.blkno = 0;
			}
			if (un->un_pos.eof > ST_EOF) {
				break;
			}
		} else {
			if (un->un_pos.eof > ST_EOF) {
				break;
			}
		}
	}

	if (un->un_dp->options & ST_KNOWS_EOD) {
		COPY_POS(&savepos, &un->un_pos);
	}

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_find_eod: %x\n", savepos.fileno);
	return (savepos.fileno);
}


/*
 * this routine is frequently used in ioctls below;
 * it determines whether we know the density and if not will
 * determine it
 * if we have written the tape before, one or more filemarks are written
 *
 * depending on the stepflag, the head is repositioned to where it was before
 * the filemarks were written in order not to confuse step counts
 */
#define	STEPBACK    0
#define	NO_STEPBACK 1

static int
st_check_density_or_wfm(dev_t dev, int wfm, int mode, int stepflag)
{

	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_check_density_or_wfm);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_check_density_or_wfm(dev= 0x%lx, wfm= %d, mode= %d, stpflg= %d)"
	    "\n", dev, wfm, mode, stepflag);

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * If we don't yet know the density of the tape we have inserted,
	 * we have to either unconditionally set it (if we're 'writing'),
	 * or we have to determine it. As side effects, check for any
	 * write-protect errors, and for the need to put out any file-marks
	 * before positioning a tape.
	 *
	 * If we are going to be spacing forward, and we haven't determined
	 * the tape density yet, we have to do so now...
	 */
	if (un->un_state == ST_STATE_OPEN_PENDING_IO) {
		if (st_determine_density(un, mode)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "check_density_or_wfm : EIO can't determine "
			    "density\n");
			un->un_errno = EIO;
			return (EIO);
		}
		/*
		 * Presumably we are at BOT. If we attempt to write, it will
		 * either work okay, or bomb. We don't do a st_test_append
		 * unless we're past BOT.
		 */
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_OPEN;

	} else if (un->un_pos.pmode != invalid && un->un_fmneeded > 0 &&
	    ((un->un_lastop == ST_OP_WEOF && wfm) ||
	    (un->un_lastop == ST_OP_WRITE && wfm))) {

		tapepos_t spos;

		COPY_POS(&spos, &un->un_pos);

		/*
		 * We need to write one or two filemarks.
		 * In the case of the HP, we need to
		 * position the head between the two
		 * marks.
		 */
		if ((un->un_fmneeded > 0) || (un->un_lastop == ST_OP_WEOF)) {
			wfm = un->un_fmneeded;
			un->un_fmneeded = 0;
		}

		if (st_write_fm(dev, wfm)) {
			un->un_pos.pmode = invalid;
			un->un_density_known = 0;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "check_density_or_wfm : EIO can't write fm\n");
			un->un_errno = EIO;
			return (EIO);
		}

		if (stepflag == STEPBACK) {
			if (st_cmd(un, SCMD_SPACE, Fmk(-wfm), SYNC_CMD)) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "check_density_or_wfm : EIO can't space "
				    "(-wfm)\n");
				un->un_errno = EIO;
				return (EIO);
			}
			COPY_POS(&un->un_pos, &spos);
		}
	}

	/*
	 * Whatever we do at this point clears the state of the eof flag.
	 */

	un->un_pos.eof = ST_NO_EOF;

	/*
	 * If writing, let's check that we're positioned correctly
	 * at the end of tape before issuing the next write.
	 */
	if (un->un_read_only == RDWR) {
		un->un_test_append = 1;
	}

	ASSERT(mutex_owned(ST_MUTEX));
	return (0);
}


/*
 * Wait for all outstaning I/O's to complete
 *
 * we wait on both ncmds and the wait queue for times when we are flushing
 * after persistent errors are flagged, which is when ncmds can be 0, and the
 * queue can still have I/O's.  This way we preserve order of biodone's.
 */
static void
st_wait_for_io(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_wait_for_io);
	ASSERT(mutex_owned(ST_MUTEX));
	while ((un->un_ncmds) || (un->un_quef) || (un->un_runqf)) {
		cv_wait(&un->un_queue_cv, ST_MUTEX);
	}
}

/*
 * This routine implements the ioctl calls.  It is called
 * from the device switch at normal priority.
 */
/*ARGSUSED*/
static int
st_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred_p,
    int *rval_p)
{
	int tmp, rval = 0;

	GET_SOFT_STATE(dev);

	ST_ENTR(ST_DEVINFO, st_ioctl);

	mutex_enter(ST_MUTEX);

	ASSERT(un->un_recov_buf_busy == 0);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_ioctl(): fileno=%x, blkno=%x, eof=%x, state = %d, "
	    "pe_flag = %d\n",
	    un->un_pos.fileno, un->un_pos.blkno, un->un_pos.eof, un->un_state,
	    un->un_persistence && un->un_persist_errors);

	/*
	 * We don't want to block on these, so let them through
	 * and we don't care about setting driver states here.
	 */
	if ((cmd == MTIOCGETDRIVETYPE) ||
	    (cmd == MTIOCGUARANTEEDORDER) ||
	    (cmd == MTIOCPERSISTENTSTATUS)) {
		goto check_commands;
	}

	/*
	 * We clear error entry stack except command
	 * MTIOCGETERROR and MTIOCGET
	 */
	if ((cmd != MTIOCGETERROR) &&
	    (cmd != MTIOCGET)) {
		st_empty_error_stack(un);
	}

	/*
	 * wait for all outstanding commands to complete, or be dequeued.
	 * And because ioctl's are synchronous commands, any return value
	 * after this,  will be in order
	 */
	st_wait_for_io(un);

	/*
	 * allow only a through clear errors and persistent status, and
	 * status
	 */
	if (un->un_persistence && un->un_persist_errors) {
		if ((cmd == MTIOCLRERR) ||
		    (cmd == MTIOCPERSISTENT) ||
		    (cmd == MTIOCGET)) {
			goto check_commands;
		} else {
			rval = un->un_errno;
			goto exit;
		}
	}

	ASSERT(un->un_throttle != 0);
	un->un_throttle = 1;	/* > 1 will never happen here */
	un->un_errno = 0;	/* start clean from here */

	/*
	 * first and foremost, handle any ST_EOT_PENDING cases.
	 * That is, if a logical eot is pending notice, notice it.
	 */
	if (un->un_pos.eof == ST_EOT_PENDING) {
		int resid = un->un_err_resid;
		uchar_t status = un->un_status;
		uchar_t lastop = un->un_lastop;

		if (st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "stioctl : EIO can't space fmk(-1)\n");
			rval = EIO;
			goto exit;
		}
		un->un_lastop = lastop; /* restore last operation */
		if (status == SUN_KEY_EOF) {
			un->un_status = SUN_KEY_EOT;
		} else {
			un->un_status = status;
		}
		un->un_err_resid  = resid;
		/* fix up block number */
		un->un_err_pos.blkno = un->un_pos.blkno = 0;
		/* now we're at logical eot */
		un->un_pos.eof = ST_EOT;
	}

	/*
	 * now, handle the rest of the situations
	 */
check_commands:
	switch (cmd) {
	case MTIOCGET:
	{
#ifdef _MULTI_DATAMODEL
		/*
		 * For use when a 32 bit app makes a call into a
		 * 64 bit ioctl
		 */
		struct mtget32		mtg_local32;
		struct mtget32 		*mtget_32 = &mtg_local32;
#endif /* _MULTI_DATAMODEL */

			/* Get tape status */
		struct mtget mtg_local;
		struct mtget *mtget = &mtg_local;
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: MTIOCGET\n");

		bzero((caddr_t)mtget, sizeof (struct mtget));
		mtget->mt_erreg = un->un_status;
		mtget->mt_resid = un->un_err_resid;
		mtget->mt_dsreg = un->un_retry_ct;
		if (un->un_err_pos.pmode == legacy) {
			mtget->mt_fileno = un->un_err_pos.fileno;
		} else {
			mtget->mt_fileno = -1;
		}
		/*
		 * If the value is positive fine.
		 * If its negative we need to return a value based on the
		 * old way if counting backwards from INF (1,000,000,000).
		 */
		if (un->un_err_pos.blkno >= 0) {
			mtget->mt_blkno = un->un_err_pos.blkno;
		} else {
			mtget->mt_blkno = INF + 1 - (-un->un_err_pos.blkno);
		}
		mtget->mt_type = un->un_dp->type;
		mtget->mt_flags = MTF_SCSI | MTF_ASF;
		if (un->un_read_pos_type != NO_POS) {
			mtget->mt_flags |= MTF_LOGICAL_BLOCK;
		}
		if (un->un_dp->options & ST_REEL) {
			mtget->mt_flags |= MTF_REEL;
			mtget->mt_bf = 20;
		} else {		/* 1/4" cartridges */
			switch (mtget->mt_type) {
			/* Emulex cartridge tape */
			case MT_ISMT02:
				mtget->mt_bf = 40;
				break;
			default:
				mtget->mt_bf = 126;
				break;
			}
		}

		/*
		 * If large transfers are allowed and drive options
		 * has no record size limit set. Calculate blocking
		 * factor from the lesser of maxbsize and maxdma.
		 */
		if ((un->un_allow_large_xfer) &&
		    (un->un_dp->options & ST_NO_RECSIZE_LIMIT)) {
			mtget->mt_bf = min(un->un_maxbsize,
			    un->un_maxdma) / SECSIZE;
		}

		if (un->un_read_only == WORM ||
		    un->un_read_only == RDWORM) {
			mtget->mt_flags |= MTF_WORM_MEDIA;
		}

		/*
		 * In persistent error mode sending a non-queued can hang
		 * because this ioctl gets to be run without turning off
		 * persistense. Fake the answer based on previous info.
		 */
		if (un->un_persistence) {
			rval = 0;
		} else {
			rval = st_check_clean_bit(un);
		}
		if (rval == 0) {
			/*
			 * If zero is returned or in persistent mode,
			 * use the old data.
			 */
			if ((un->un_HeadClean & (TAPE_ALERT_SUPPORTED |
			    TAPE_SEQUENTIAL_SUPPORTED|TAPE_ALERT_NOT_SUPPORTED))
			    != TAPE_ALERT_NOT_SUPPORTED) {
				mtget->mt_flags |= MTF_TAPE_CLN_SUPPORTED;
			}
			if (un->un_HeadClean & (TAPE_PREVIOUSLY_DIRTY |
			    TAPE_ALERT_STILL_DIRTY)) {
				mtget->mt_flags |= MTF_TAPE_HEAD_DIRTY;
			}
		} else {
			mtget->mt_flags |= (ushort_t)rval;
			rval = 0;
		}

		un->un_status = 0;		/* Reset status */
		un->un_err_resid = 0;
		tmp = sizeof (struct mtget);

#ifdef _MULTI_DATAMODEL

		switch (ddi_model_convert_from(flag & FMODELS)) {
		case DDI_MODEL_ILP32:
			/*
			 * Convert 64 bit back to 32 bit before doing
			 * copyout. This is what the ILP32 app expects.
			 */
			mtget_32->mt_erreg = 	mtget->mt_erreg;
			mtget_32->mt_resid = 	mtget->mt_resid;
			mtget_32->mt_dsreg = 	mtget->mt_dsreg;
			mtget_32->mt_fileno = 	(daddr32_t)mtget->mt_fileno;
			mtget_32->mt_blkno = 	(daddr32_t)mtget->mt_blkno;
			mtget_32->mt_type =  	mtget->mt_type;
			mtget_32->mt_flags = 	mtget->mt_flags;
			mtget_32->mt_bf = 	mtget->mt_bf;

			if (ddi_copyout(mtget_32, (void *)arg,
			    sizeof (struct mtget32), flag)) {
				rval = EFAULT;
			}
			break;

		case DDI_MODEL_NONE:
			if (ddi_copyout(mtget, (void *)arg, tmp, flag)) {
				rval = EFAULT;
			}
			break;
		}
#else /* ! _MULTI_DATAMODE */
		if (ddi_copyout(mtget, (void *)arg, tmp, flag)) {
			rval = EFAULT;
		}
#endif /* _MULTI_DATAMODE */

		break;
	}
	case MTIOCGETERROR:
			/*
			 * get error entry from error stack
			 */
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: MTIOCGETERROR\n");

			rval = st_get_error_entry(un, arg, flag);

			break;

	case MTIOCSTATE:
		{
			/*
			 * return when media presence matches state
			 */
			enum mtio_state state;

			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: MTIOCSTATE\n");

			if (ddi_copyin((void *)arg, &state, sizeof (int), flag))
				rval = EFAULT;

			mutex_exit(ST_MUTEX);

			rval = st_check_media(dev, state);

			mutex_enter(ST_MUTEX);

			if (rval != 0) {
				break;
			}

			if (ddi_copyout(&un->un_mediastate, (void *)arg,
			    sizeof (int), flag))
				rval = EFAULT;
			break;

		}

	case MTIOCGETDRIVETYPE:
		{
#ifdef _MULTI_DATAMODEL
		/*
		 * For use when a 32 bit app makes a call into a
		 * 64 bit ioctl
		 */
		struct mtdrivetype_request32	mtdtrq32;
#endif /* _MULTI_DATAMODEL */

			/*
			 * return mtdrivetype
			 */
			struct mtdrivetype_request mtdtrq;
			struct mtdrivetype mtdrtyp;
			struct mtdrivetype *mtdt = &mtdrtyp;
			struct st_drivetype *stdt = un->un_dp;

			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: MTIOCGETDRIVETYPE\n");

#ifdef _MULTI_DATAMODEL
		switch (ddi_model_convert_from(flag & FMODELS)) {
		case DDI_MODEL_ILP32:
		{
			if (ddi_copyin((void *)arg, &mtdtrq32,
			    sizeof (struct mtdrivetype_request32), flag)) {
				rval = EFAULT;
				break;
			}
			mtdtrq.size = mtdtrq32.size;
			mtdtrq.mtdtp =
			    (struct  mtdrivetype *)(uintptr_t)mtdtrq32.mtdtp;
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: size 0x%x\n", mtdtrq.size);
			break;
		}
		case DDI_MODEL_NONE:
			if (ddi_copyin((void *)arg, &mtdtrq,
			    sizeof (struct mtdrivetype_request), flag)) {
				rval = EFAULT;
				break;
			}
			break;
		}

#else /* ! _MULTI_DATAMODEL */
		if (ddi_copyin((void *)arg, &mtdtrq,
		    sizeof (struct mtdrivetype_request), flag)) {
			rval = EFAULT;
			break;
		}
#endif /* _MULTI_DATAMODEL */

			/*
			 * if requested size is < 0 then return
			 * error.
			 */
			if (mtdtrq.size < 0) {
				rval = EINVAL;
				break;
			}
			bzero(mtdt, sizeof (struct mtdrivetype));
			(void) strncpy(mtdt->name, stdt->name, ST_NAMESIZE);
			(void) strncpy(mtdt->vid, stdt->vid, VIDPIDLEN - 1);
			mtdt->type = stdt->type;
			mtdt->bsize = stdt->bsize;
			mtdt->options = stdt->options;
			mtdt->max_rretries = stdt->max_rretries;
			mtdt->max_wretries = stdt->max_wretries;
			for (tmp = 0; tmp < NDENSITIES; tmp++) {
				mtdt->densities[tmp] = stdt->densities[tmp];
			}
			mtdt->default_density = stdt->default_density;
			/*
			 * Speed hasn't been used since the hayday of reel tape.
			 * For all drives not setting the option ST_KNOWS_MEDIA
			 * the speed member renamed to mediatype are zeros.
			 * Those drives that have ST_KNOWS_MEDIA set use the
			 * new mediatype member which is used to figure the
			 * type of media loaded.
			 *
			 * So as to not break applications speed in the
			 * mtdrivetype structure is not renamed.
			 */
			for (tmp = 0; tmp < NDENSITIES; tmp++) {
				mtdt->speeds[tmp] = stdt->mediatype[tmp];
			}
			mtdt->non_motion_timeout = stdt->non_motion_timeout;
			mtdt->io_timeout = stdt->io_timeout;
			mtdt->rewind_timeout = stdt->rewind_timeout;
			mtdt->space_timeout = stdt->space_timeout;
			mtdt->load_timeout = stdt->load_timeout;
			mtdt->unload_timeout = stdt->unload_timeout;
			mtdt->erase_timeout = stdt->erase_timeout;

			/*
			 * Limit the maximum length of the result to
			 * sizeof (struct mtdrivetype).
			 */
			tmp = sizeof (struct mtdrivetype);
			if (mtdtrq.size < tmp)
				tmp = mtdtrq.size;
			if (ddi_copyout(mtdt, mtdtrq.mtdtp, tmp, flag)) {
				rval = EFAULT;
			}
			break;
		}
	case MTIOCPERSISTENT:

		if (ddi_copyin((void *)arg, &tmp, sizeof (tmp), flag)) {
			rval = EFAULT;
			break;
		}

		if (tmp) {
			st_turn_pe_on(un);
		} else {
			st_turn_pe_off(un);
		}

		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: MTIOCPERSISTENT : persistence = %d\n",
		    un->un_persistence);

		break;

	case MTIOCPERSISTENTSTATUS:
		tmp = (int)un->un_persistence;

		if (ddi_copyout(&tmp, (void *)arg, sizeof (tmp), flag)) {
			rval = EFAULT;
		}
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: MTIOCPERSISTENTSTATUS:persistence = %d\n",
		    un->un_persistence);

		break;

	case MTIOCLRERR:
		{
			/* clear persistent errors */

			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: MTIOCLRERR\n");

			st_clear_pe(un);

			break;
		}

	case MTIOCGUARANTEEDORDER:
		{
			/*
			 * this is just a holder to make a valid ioctl and
			 * it won't be in any earlier release
			 */
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: MTIOCGUARANTEEDORDER\n");

			break;
		}

	case MTIOCRESERVE:
		{
			ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: MTIOCRESERVE\n");

			/*
			 * Check if Reserve/Release is supported.
			 */
			if (un->un_dp->options & ST_NO_RESERVE_RELEASE) {
				rval = ENOTTY;
				break;
			}

			rval = st_reserve_release(un, ST_RESERVE, st_uscsi_cmd);

			if (rval == 0) {
				un->un_rsvd_status |= ST_PRESERVE_RESERVE;
			}
			break;
		}

	case MTIOCRELEASE:
		{
			ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl: MTIOCRELEASE\n");

			/*
			 * Check if Reserve/Release is supported.
			 */
			if (un->un_dp->options & ST_NO_RESERVE_RELEASE) {
				rval = ENOTTY;
				break;
			}

			/*
			 * Used to just clear ST_PRESERVE_RESERVE which
			 * made the reservation release at next close.
			 * As the user may have opened and then done a
			 * persistant reservation we now need to drop
			 * the reservation without closing if the user
			 * attempts to do this.
			 */
			rval = st_reserve_release(un, ST_RELEASE, st_uscsi_cmd);

			un->un_rsvd_status &= ~ST_PRESERVE_RESERVE;

			break;
		}

	case MTIOCFORCERESERVE:
	{
		ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: MTIOCFORCERESERVE\n");

		/*
		 * Check if Reserve/Release is supported.
		 */
		if (un->un_dp->options & ST_NO_RESERVE_RELEASE) {
			rval = ENOTTY;
			break;
		}
		/*
		 * allow only super user to run this.
		 */
		if (drv_priv(cred_p) != 0) {
			rval = EPERM;
			break;
		}
		/*
		 * Throw away reserve,
		 * not using test-unit-ready
		 * since reserve can succeed without tape being
		 * present in the drive.
		 */
		(void) st_reserve_release(un, ST_RESERVE, st_uscsi_cmd);

		rval = st_take_ownership(un, st_uscsi_cmd);

		break;
	}

	case USCSICMD:
	{
		cred_t	*cr;

		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: USCSICMD\n");

		cr = ddi_get_cred();
		if ((drv_priv(cred_p) != 0) && (drv_priv(cr) != 0)) {
			rval = EPERM;
		} else {
			rval = st_uscsi_cmd(un, (struct uscsi_cmd *)arg, flag);
		}
		break;
	}
	case MTIOCTOP:
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: MTIOCTOP\n");
		rval = st_mtioctop(un, arg, flag);
		break;

	case MTIOCLTOP:
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: MTIOLCTOP\n");
		rval = st_mtiocltop(un, arg, flag);
		break;

	case MTIOCREADIGNOREILI:
		{
			int set_ili;

			if (ddi_copyin((void *)arg, &set_ili,
			    sizeof (set_ili), flag)) {
				rval = EFAULT;
				break;
			}

			if (un->un_bsize) {
				rval = ENOTTY;
				break;
			}

			switch (set_ili) {
			case 0:
				un->un_dp->options &= ~ST_READ_IGNORE_ILI;
				break;

			case 1:
				un->un_dp->options |= ST_READ_IGNORE_ILI;
				break;

			default:
				rval = EINVAL;
				break;
			}
			break;
		}

	case MTIOCREADIGNOREEOFS:
		{
			int ignore_eof;

			if (ddi_copyin((void *)arg, &ignore_eof,
			    sizeof (ignore_eof), flag)) {
				rval = EFAULT;
				break;
			}

			if (!(un->un_dp->options & ST_REEL)) {
				rval = ENOTTY;
				break;
			}

			switch (ignore_eof) {
			case 0:
				un->un_dp->options &= ~ST_READ_IGNORE_EOFS;
				break;

			case 1:
				un->un_dp->options |= ST_READ_IGNORE_EOFS;
				break;

			default:
				rval = EINVAL;
				break;
			}
			break;
		}

	case MTIOCSHORTFMK:
	{
		int short_fmk;

		if (ddi_copyin((void *)arg, &short_fmk,
		    sizeof (short_fmk), flag)) {
			rval = EFAULT;
			break;
		}

		switch (un->un_dp->type) {
		case ST_TYPE_EXB8500:
		case ST_TYPE_EXABYTE:
			if (!short_fmk) {
				un->un_dp->options &= ~ST_SHORT_FILEMARKS;
			} else if (short_fmk == 1) {
				un->un_dp->options |= ST_SHORT_FILEMARKS;
			} else {
				rval = EINVAL;
			}
			break;

		default:
			rval = ENOTTY;
			break;
		}
		break;
	}

	case MTIOCGETPOS:
		rval = st_update_block_pos(un, st_cmd, 0);
		if (rval == 0) {
			if (ddi_copyout((void *)&un->un_pos, (void *)arg,
			    sizeof (tapepos_t), flag)) {
				scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "MTIOCGETPOS copy out failed\n");
				rval = EFAULT;
			}
		}
		break;

	case MTIOCRESTPOS:
	{
		tapepos_t dest;

		if (ddi_copyin((void *)arg, &dest, sizeof (tapepos_t),
		    flag) != 0) {
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "MTIOCRESTPOS copy in failed\n");
			rval = EFAULT;
			break;
		}
		rval = st_validate_tapemarks(un, st_uscsi_cmd, &dest);
		if (rval != 0) {
			rval = EIO;
		}
		break;
	}
	default:
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl: unknown ioctl\n");
		rval = ENOTTY;
	}

exit:
	if (!(un->un_persistence && un->un_persist_errors)) {
		un->un_errno = rval;
	}

	mutex_exit(ST_MUTEX);

	return (rval);
}


/*
 * do some MTIOCTOP tape operations
 */
static int
st_mtioctop(struct scsi_tape *un, intptr_t arg, int flag)
{
#ifdef _MULTI_DATAMODEL
	/*
	 * For use when a 32 bit app makes a call into a
	 * 64 bit ioctl
	 */
	struct mtop32	mtop_32_for_64;
#endif /* _MULTI_DATAMODEL */
	struct mtop passed;
	struct mtlop local;
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_mtioctop);

	ASSERT(mutex_owned(ST_MUTEX));

#ifdef _MULTI_DATAMODEL
	switch (ddi_model_convert_from(flag & FMODELS)) {
	case DDI_MODEL_ILP32:
		if (ddi_copyin((void *)arg, &mtop_32_for_64,
		    sizeof (struct mtop32), flag)) {
			return (EFAULT);
		}
		local.mt_op = mtop_32_for_64.mt_op;
		local.mt_count =  (int64_t)mtop_32_for_64.mt_count;
		break;

	case DDI_MODEL_NONE:
		if (ddi_copyin((void *)arg, &passed, sizeof (passed), flag)) {
			return (EFAULT);
		}
		local.mt_op = passed.mt_op;
		/* prevent sign extention */
		local.mt_count = (UINT32_MAX & passed.mt_count);
		break;
	}

#else /* ! _MULTI_DATAMODEL */
	if (ddi_copyin((void *)arg, &passed, sizeof (passed), flag)) {
		return (EFAULT);
	}
	local.mt_op = passed.mt_op;
	/* prevent sign extention */
	local.mt_count = (UINT32_MAX & passed.mt_count);
#endif /* _MULTI_DATAMODEL */

	rval = st_do_mtioctop(un, &local);

#ifdef _MULTI_DATAMODEL
	switch (ddi_model_convert_from(flag & FMODELS)) {
	case DDI_MODEL_ILP32:
		if (((uint64_t)local.mt_count) > UINT32_MAX) {
			rval = ERANGE;
			break;
		}
		/*
		 * Convert 64 bit back to 32 bit before doing
		 * copyout. This is what the ILP32 app expects.
		 */
		mtop_32_for_64.mt_op = local.mt_op;
		mtop_32_for_64.mt_count = local.mt_count;

		if (ddi_copyout(&mtop_32_for_64, (void *)arg,
		    sizeof (struct mtop32), flag)) {
			rval = EFAULT;
		}
		break;

	case DDI_MODEL_NONE:
		passed.mt_count = local.mt_count;
		passed.mt_op = local.mt_op;
		if (ddi_copyout(&passed, (void *)arg, sizeof (passed), flag)) {
			rval = EFAULT;
		}
		break;
	}
#else /* ! _MULTI_DATAMODE */
	if (((uint64_t)local.mt_count) > UINT32_MAX) {
		rval = ERANGE;
	} else {
		passed.mt_op = local.mt_op;
		passed.mt_count = local.mt_count;
		if (ddi_copyout(&passed, (void *)arg, sizeof (passed), flag)) {
			rval = EFAULT;
		}
	}
#endif /* _MULTI_DATAMODE */


	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_ioctl: fileno=%x, blkno=%x, eof=%x\n", un->un_pos.fileno,
	    un->un_pos.blkno, un->un_pos.eof);

	if (un->un_pos.pmode == invalid) {
		un->un_density_known = 0;
	}

	ASSERT(mutex_owned(ST_MUTEX));
	return (rval);
}

static int
st_mtiocltop(struct scsi_tape *un, intptr_t arg, int flag)
{
	struct mtlop local;
	int rval;

	ST_FUNC(ST_DEVINFO, st_mtiocltop);
	if (ddi_copyin((void *)arg, &local, sizeof (local), flag)) {
		return (EFAULT);
	}

	rval = st_do_mtioctop(un, &local);

	if (ddi_copyout(&local, (void *)arg, sizeof (local), flag)) {
		rval = EFAULT;
	}
	return (rval);
}


static int
st_do_mtioctop(struct scsi_tape *un, struct mtlop *mtop)
{
	dev_t dev = un->un_dev;
	int savefile;
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_do_mtioctop);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_do_mtioctop(): mt_op=%x\n", mtop->mt_op);
	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "fileno=%x, blkno=%x, eof=%x\n",
	    un->un_pos.fileno, un->un_pos.blkno, un->un_pos.eof);

	un->un_status = 0;

	/*
	 * if we are going to mess with a tape, we have to make sure we have
	 * one and are not offline (i.e. no tape is initialized).  We let
	 * commands pass here that don't actually touch the tape, except for
	 * loading and initialization (rewinding).
	 */
	if (un->un_state == ST_STATE_OFFLINE) {
		switch (mtop->mt_op) {
		case MTLOAD:
		case MTNOP:
			/*
			 * We don't want strategy calling st_tape_init here,
			 * so, change state
			 */
			un->un_state = ST_STATE_INITIALIZING;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : OFFLINE state = %d\n",
			    un->un_state);
			break;
		default:
			/*
			 * reinitialize by normal means
			 */
			rval = st_tape_init(un);
			if (rval) {
				un->un_state = ST_STATE_INITIALIZING;
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_do_mtioctop : OFFLINE init failure ");
				un->un_state = ST_STATE_OFFLINE;
				un->un_pos.pmode = invalid;
				if (rval != EACCES) {
					rval = EIO;
				}
				return (rval);
			}
			un->un_state = ST_STATE_OPEN_PENDING_IO;
			break;
		}
	}

	/*
	 * If the file position is invalid, allow only those
	 * commands that properly position the tape and fail
	 * the rest with EIO
	 */
	if (un->un_pos.pmode == invalid) {
		switch (mtop->mt_op) {
		case MTWEOF:
		case MTRETEN:
		case MTERASE:
		case MTEOM:
		case MTFSF:
		case MTFSR:
		case MTBSF:
		case MTNBSF:
		case MTBSR:
		case MTSRSZ:
		case MTGRSZ:
		case MTSEEK:
		case MTBSSF:
		case MTFSSF:
			return (EIO);
			/* NOTREACHED */
		case MTREW:
		case MTLOAD:
		case MTOFFL:
		case MTNOP:
		case MTTELL:
		case MTLOCK:
		case MTUNLOCK:
			break;

		default:
			return (ENOTTY);
			/* NOTREACHED */
		}
	}

	switch (mtop->mt_op) {
	case MTERASE:
		/*
		 * MTERASE rewinds the tape, erase it completely, and returns
		 * to the beginning of the tape
		 */
		if (un->un_mspl->wp || un->un_read_only & WORM) {
			un->un_status = KEY_WRITE_PROTECT;
			un->un_err_resid = mtop->mt_count;
			COPY_POS(&un->un_err_pos, &un->un_pos);
			return (EACCES);
		}
		if (un->un_dp->options & ST_REEL) {
			un->un_fmneeded = 2;
		} else {
			un->un_fmneeded = 1;
		}
		mtop->mt_count = mtop->mt_count ? 1 : 0;
		if (st_check_density_or_wfm(dev, 1, B_WRITE, NO_STEPBACK) ||
		    st_cmd(un, SCMD_REWIND, 0, SYNC_CMD) ||
		    st_cmd(un, SCMD_ERASE, mtop->mt_count, SYNC_CMD)) {
			un->un_pos.pmode = invalid;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO space or erase or "
			    "check den)\n");
			rval = EIO;
		} else {
			/* QIC and helical scan rewind after erase */
			if (un->un_dp->options & ST_REEL) {
				(void) st_cmd(un, SCMD_REWIND, 0, ASYNC_CMD);
			}
		}
		break;

	case MTWEOF:
		/*
		 * write an end-of-file record
		 */
		if (un->un_mspl->wp || un->un_read_only & RDONLY) {
			un->un_status = KEY_WRITE_PROTECT;
			un->un_err_resid = mtop->mt_count;
			COPY_POS(&un->un_err_pos, &un->un_pos);
			return (EACCES);
		}

		/*
		 * zero count means just flush buffers
		 * negative count is not permitted
		 */
		if (mtop->mt_count < 0) {
			return (EINVAL);
		}

		/* Not on worm */
		if (un->un_read_only == RDWR) {
			un->un_test_append = 1;
		}

		if (un->un_state == ST_STATE_OPEN_PENDING_IO) {
			if (st_determine_density(un, B_WRITE)) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_do_mtioctop : EIO : MTWEOF can't "
				    "determine density");
				return (EIO);
			}
		}

		rval = st_write_fm(dev, (int)mtop->mt_count);
		if ((rval != 0) && (rval != EACCES)) {
			/*
			 * Failure due to something other than illegal
			 * request results in loss of state (st_intr).
			 */
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTWEOF can't write "
			    "file mark");
			rval = EIO;
		}
		break;

	case MTRETEN:
		/*
		 * retension the tape
		 */
		if (st_check_density_or_wfm(dev, 1, 0, NO_STEPBACK) ||
		    st_cmd(un, SCMD_LOAD, LD_LOAD | LD_RETEN, SYNC_CMD)) {
			un->un_pos.pmode = invalid;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTRETEN ");
			rval = EIO;
		}
		break;

	case MTREW:
		/*
		 * rewind  the tape
		 */
		if (st_check_density_or_wfm(dev, 1, 0, NO_STEPBACK)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO:MTREW check "
			    "density/wfm failed");
			return (EIO);
		}
		if (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTREW ");
			rval = EIO;
		}
		break;

	case MTOFFL:
		/*
		 * rewinds, and, if appropriate, takes the device offline by
		 * unloading the tape
		 */
		if (st_check_density_or_wfm(dev, 1, 0, NO_STEPBACK)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop :EIO:MTOFFL check "
			    "density/wfm failed");
			return (EIO);
		}
		(void) st_cmd(un, SCMD_REWIND, 0, SYNC_CMD);
		if (st_cmd(un, SCMD_LOAD, LD_UNLOAD, SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTOFFL");
			return (EIO);
		}
		un->un_pos.eof = ST_NO_EOF;
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_OFFLINE;
		un->un_mediastate = MTIO_EJECTED;
		break;

	case MTLOAD:
		/*
		 * This is to load a tape into the drive
		 * Note that if the tape is not loaded, the device will have
		 * to be opened via O_NDELAY or O_NONBLOCK.
		 */
		/*
		 * Let's try and clean things up, if we are not
		 * initializing, and then send in the load command, no
		 * matter what.
		 *
		 * load after a media change by the user.
		 */

		if (un->un_state > ST_STATE_INITIALIZING) {
			(void) st_check_density_or_wfm(dev, 1, 0, NO_STEPBACK);
		}
		rval = st_cmd(un, SCMD_LOAD, LD_LOAD, SYNC_CMD);
		/* Load command to a drive that doesn't support load */
		if ((rval == EIO) &&
		    ((un->un_status == KEY_NOT_READY) &&
			/* Medium not present */
		    (un->un_uscsi_rqs_buf->es_add_code == 0x3a) ||
		    ((un->un_status == KEY_ILLEGAL_REQUEST) &&
		    (un->un_dp->type == MT_ISSTK9840) &&
			/* CSL not present */
		    (un->un_uscsi_rqs_buf->es_add_code == 0x80)))) {
			rval = ENOTTY;
			break;
		} else if (rval != EACCES && rval != 0) {
			rval = EIO;
		}
		if (rval) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : %s : MTLOAD\n",
			    rval == EACCES ? "EACCES" : "EIO");
			/*
			 * If load tape fails, who knows what happened...
			 */
			un->un_pos.pmode = invalid;
			break;
		}

		/*
		 * reset all counters appropriately using rewind, as if LOAD
		 * succeeds, we are at BOT
		 */
		un->un_state = ST_STATE_INITIALIZING;

		rval = st_tape_init(un);
		if ((rval == EACCES) && (un->un_read_only & WORM)) {
			rval = 0;
			break;
		}

		if (rval != 0) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTLOAD calls "
			    "st_tape_init\n");
			rval = EIO;
			un->un_state = ST_STATE_OFFLINE;
		}

		break;

	case MTNOP:
		un->un_status = 0;		/* Reset status */
		un->un_err_resid = 0;
		mtop->mt_count = MTUNIT(dev);
		break;

	case MTEOM:
		/*
		 * positions the tape at a location just after the last file
		 * written on the tape. For cartridge and 8 mm, this after
		 * the last file mark; for reel, this is inbetween the two
		 * last 2 file marks
		 */
		if ((un->un_pos.pmode == legacy && un->un_pos.eof >= ST_EOT) ||
		    (un->un_lastop == ST_OP_WRITE) ||
		    (un->un_lastop == ST_OP_WEOF)) {
			/*
			 * If the command wants to move to logical end
			 * of media, and we're already there, we're done.
			 * If we were at logical eot, we reset the state
			 * to be *not* at logical eot.
			 *
			 * If we're at physical or logical eot, we prohibit
			 * forward space operations (unconditionally).
			 *
			 * Also if the last operation was a write of any
			 * kind the tape is at EOD.
			 */
			return (0);
		}
		/*
		 * physical tape position may not be what we've been
		 * telling the user; adjust the request accordingly
		 */
		if (IN_EOF(un->un_pos)) {
			un->un_pos.fileno++;
			un->un_pos.blkno = 0;
		}

		if (st_check_density_or_wfm(dev, 1, B_READ, NO_STEPBACK)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO:MTEOM check density/wfm "
			    " failed");
			return (EIO);
		}

		/*
		 * st_find_eod() returns the last fileno we knew about;
		 */
		savefile = st_find_eod(un);

		if ((un->un_status != KEY_BLANK_CHECK) &&
		    (un->un_status != SUN_KEY_EOT)) {
			un->un_pos.pmode = invalid;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTEOM status check failed");
			rval = EIO;
		} else {
			/*
			 * For 1/2" reel tapes assume logical EOT marked
			 * by two file marks or we don't care that we may
			 * be extending the last file on the tape.
			 */
			if (un->un_dp->options & ST_REEL) {
				if (st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD)) {
					un->un_pos.pmode = invalid;
					ST_DEBUG2(ST_DEVINFO, st_label,
					    SCSI_DEBUG,
					    "st_do_mtioctop : EIO : MTEOM space"
					    " cmd failed");
					rval = EIO;
					break;
				}
				/*
				 * Fix up the block number.
				 */
				un->un_pos.blkno = 0;
				un->un_err_pos.blkno = 0;
			}
			un->un_err_resid = 0;
			un->un_pos.fileno = savefile;
			un->un_pos.eof = ST_EOT;
		}
		un->un_status = 0;
		break;

	case MTFSF:
		MAX_SPACE_CNT(mtop->mt_count);
		rval = st_mtfsf_ioctl(un, mtop->mt_count);
		break;

	case MTFSR:
		MAX_SPACE_CNT(mtop->mt_count);
		rval = st_mtfsr_ioctl(un, mtop->mt_count);
		break;

	case MTBSF:
		MAX_SPACE_CNT(mtop->mt_count);
		rval = st_mtbsf_ioctl(un, mtop->mt_count);
		break;

	case MTNBSF:
		MAX_SPACE_CNT(mtop->mt_count);
		rval = st_mtnbsf_ioctl(un, mtop->mt_count);
		break;

	case MTBSR:
		MAX_SPACE_CNT(mtop->mt_count);
		rval = st_mtbsr_ioctl(un, mtop->mt_count);
		break;

	case MTBSSF:
		MAX_SPACE_CNT(mtop->mt_count);
		rval = st_mtbsfm_ioctl(un, mtop->mt_count);
		break;

	case MTFSSF:
		MAX_SPACE_CNT(mtop->mt_count);
		rval = st_mtfsfm_ioctl(un, mtop->mt_count);
		break;

	case MTSRSZ:

		/*
		 * Set record-size to that sent by user
		 * Check to see if there is reason that the requested
		 * block size should not be set.
		 */

		/* If requesting variable block size is it ok? */
		if ((mtop->mt_count == 0) &&
		    ((un->un_dp->options & ST_VARIABLE) == 0)) {
			return (ENOTTY);
		}

		/*
		 * If requested block size is not variable "0",
		 * is it less then minimum.
		 */
		if ((mtop->mt_count != 0) &&
		    (mtop->mt_count < un->un_minbsize)) {
			return (EINVAL);
		}

		/* Is the requested block size more then maximum */
		if ((mtop->mt_count > min(un->un_maxbsize, un->un_maxdma)) &&
		    (un->un_maxbsize != 0)) {
			return (EINVAL);
		}

		/* Is requested block size a modulus the device likes */
		if ((mtop->mt_count % un->un_data_mod) != 0) {
			return (EINVAL);
		}

		if (st_change_block_size(un, (uint32_t)mtop->mt_count) != 0) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl : MTSRSZ : EIO : cant set block size");
			return (EIO);
		}

		return (0);

	case MTGRSZ:
		/*
		 * Get record-size to the user
		 */
		mtop->mt_count = un->un_bsize;
		rval = 0;
		break;

	case MTTELL:
		rval = st_update_block_pos(un, st_cmd, 0);
		mtop->mt_count = un->un_pos.lgclblkno;
		break;

	case MTSEEK:
		rval = st_logical_block_locate(un, st_uscsi_cmd, &un->un_pos,
		    (uint64_t)mtop->mt_count, un->un_pos.partition);
		/*
		 * This bit of magic make mt print the actual position if
		 * the resulting position was not what was asked for.
		 */
		if (rval == ESPIPE) {
			rval = EIO;
			if ((uint64_t)mtop->mt_count != un->un_pos.lgclblkno) {
				mtop->mt_op = MTTELL;
				mtop->mt_count = un->un_pos.lgclblkno;
			}
		}
		break;

	case MTLOCK:
		if (st_cmd(un, SCMD_DOORLOCK, MR_LOCK, SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTLOCK");
			rval = EIO;
		}
		break;

	case MTUNLOCK:
		if (st_cmd(un, SCMD_DOORLOCK, MR_UNLOCK, SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_do_mtioctop : EIO : MTUNLOCK");
			rval = EIO;
		}
		break;

	default:
		rval = ENOTTY;
	}

	return (rval);
}


/*
 * Run a command for uscsi ioctl.
 */
static int
st_uscsi_cmd(struct scsi_tape *un, struct uscsi_cmd *ucmd, int flag)
{
	struct uscsi_cmd	*uscmd;
	struct buf	*bp;
	enum uio_seg	uioseg;
	int	offline_state = 0;
	int	err = 0;
	dev_t dev = un->un_dev;

	ST_FUNC(ST_DEVINFO, st_uscsi_cmd);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_uscsi_cmd(dev = 0x%lx)\n", un->un_dev);

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * We really don't know what commands are coming in here and
	 * we don't want to limit the commands coming in.
	 *
	 * If st_tape_init() gets called from st_strategy(), then we
	 * will hang the process waiting for un->un_sbuf_busy to be cleared,
	 * which it never will, as we set it below.  To prevent
	 * st_tape_init() from getting called, we have to set state to other
	 * than ST_STATE_OFFLINE, so we choose ST_STATE_INITIALIZING, which
	 * achieves this purpose already.
	 *
	 * We use offline_state to preserve the OFFLINE state, if it exists,
	 * so other entry points to the driver might have the chance to call
	 * st_tape_init().
	 */
	if (un->un_state == ST_STATE_OFFLINE) {
		un->un_laststate = ST_STATE_OFFLINE;
		un->un_state = ST_STATE_INITIALIZING;
		offline_state = 1;
	}

	mutex_exit(ST_MUTEX);
	err = scsi_uscsi_alloc_and_copyin((intptr_t)ucmd, flag, ROUTE, &uscmd);
	mutex_enter(ST_MUTEX);
	if (err != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_uscsi_cmd: scsi_uscsi_alloc_and_copyin failed\n");
		goto exit;
	}

	uioseg = (flag & FKIOCTL) ? UIO_SYSSPACE : UIO_USERSPACE;

	/* check to see if this command requires the drive to be reserved */
	if (uscmd->uscsi_cdb != NULL) {
		err = st_check_cdb_for_need_to_reserve(un,
		    (uchar_t *)uscmd->uscsi_cdb);
		if (err) {
			goto exit_free;
		}
		/*
		 * If this is a space command we need to save the starting
		 * point so we can retry from there if the command fails.
		 */
		if ((uscmd->uscsi_cdb[0] == SCMD_SPACE) ||
		    (uscmd->uscsi_cdb[0] == (char)SCMD_SPACE_G4)) {
			(void) st_update_block_pos(un, st_cmd, 0);
		}
	}

	/*
	 * Forground should not be doing anything while recovery is active.
	 */
	ASSERT(un->un_recov_buf_busy == 0);

	/*
	 * Get buffer resources...
	 */
	while (un->un_sbuf_busy)
		cv_wait(&un->un_sbuf_cv, ST_MUTEX);
	un->un_sbuf_busy = 1;

#ifdef STDEBUG
	if ((uscmd->uscsi_cdb != NULL) && (st_debug & 0x7) > 6) {
		int rw = (uscmd->uscsi_flags & USCSI_READ) ? B_READ : B_WRITE;
		st_print_cdb(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "uscsi cdb", uscmd->uscsi_cdb);
		if (uscmd->uscsi_buflen) {
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "uscsi %s of %ld bytes %s %s space\n",
			    (rw == B_READ) ? rd_str : wr_str,
			    uscmd->uscsi_buflen,
			    (rw == B_READ) ? "to" : "from",
			    (uioseg == UIO_SYSSPACE) ? "system" : "user");
		}
	}
#endif /* STDEBUG */

	/*
	 * Although st_uscsi_cmd() never makes use of these
	 * now, we are just being safe and consistent.
	 */
	uscmd->uscsi_flags &= ~(USCSI_NOINTR | USCSI_NOPARITY |
	    USCSI_OTAG | USCSI_HTAG | USCSI_HEAD);

	un->un_srqbufp = uscmd->uscsi_rqbuf;
	bp = un->un_sbufp;
	bzero(bp, sizeof (buf_t));
	if (uscmd->uscsi_cdb != NULL) {
		bp->b_forw = (struct buf *)(uintptr_t)uscmd->uscsi_cdb[0];
	}
	bp->b_back = (struct buf *)uscmd;

	mutex_exit(ST_MUTEX);
	err = scsi_uscsi_handle_cmd(dev, uioseg, uscmd, st_strategy, bp, NULL);
	mutex_enter(ST_MUTEX);

	/*
	 * If scsi reset successful, don't write any filemarks.
	 */
	if ((err == 0) && (uscmd->uscsi_flags &
	    (USCSI_RESET_LUN | USCSI_RESET_TARGET | USCSI_RESET_ALL))) {
		un->un_fmneeded = 0;
	}

exit_free:
	/*
	 * Free resources
	 */
	un->un_sbuf_busy = 0;
	un->un_srqbufp = NULL;

	/*
	 * If was a space command need to update logical block position.
	 * If the command failed such that positioning is invalid, Don't
	 * update the position as the user must do this to validate the
	 * position for data protection.
	 */
	if ((uscmd->uscsi_cdb != NULL) &&
	    ((uscmd->uscsi_cdb[0] == SCMD_SPACE) ||
	    (uscmd->uscsi_cdb[0] == (char)SCMD_SPACE_G4)) &&
	    (un->un_pos.pmode != invalid)) {
		un->un_running.pmode = invalid;
		(void) st_update_block_pos(un, st_cmd, 1);
		/*
		 * Set running position to invalid so it updates on the
		 * next command.
		 */
		un->un_running.pmode = invalid;
	}
	cv_signal(&un->un_sbuf_cv);
	mutex_exit(ST_MUTEX);
	(void) scsi_uscsi_copyout_and_free((intptr_t)ucmd, uscmd);
	mutex_enter(ST_MUTEX);
	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_uscsi_cmd returns 0x%x\n", err);

exit:
	/* don't lose offline state */
	if (offline_state) {
		un->un_state = ST_STATE_OFFLINE;
	}

	ASSERT(mutex_owned(ST_MUTEX));
	return (err);
}

static int
st_write_fm(dev_t dev, int wfm)
{
	int i;
	int rval;

	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_write_fm);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_write_fm(dev = 0x%lx, wfm = %d)\n", dev, wfm);

	/*
	 * write one filemark at the time after EOT
	 */
	if (un->un_pos.eof >= ST_EOT) {
		for (i = 0; i < wfm; i++) {
			rval = st_cmd(un, SCMD_WRITE_FILE_MARK, 1, SYNC_CMD);
			if (rval == EACCES) {
				return (rval);
			}
			if (rval != 0) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_write_fm : EIO : write EOT file mark");
				return (EIO);
			}
		}
	} else {
		rval = st_cmd(un, SCMD_WRITE_FILE_MARK, wfm, SYNC_CMD);
		if (rval == EACCES) {
			return (rval);
		}
		if (rval) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_write_fm : EIO : write file mark");
			return (EIO);
		}
	}

	ASSERT(mutex_owned(ST_MUTEX));
	return (0);
}

#ifdef STDEBUG
static void
st_start_dump(struct scsi_tape *un, struct buf *bp)
{
	struct scsi_pkt *pkt = BP_PKT(bp);
	uchar_t *cdbp = (uchar_t *)pkt->pkt_cdbp;

	ST_FUNC(ST_DEVINFO, st_start_dump);

	if ((st_debug & 0x7) < 6)
		return;
	scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_start: cmd=0x%p count=%ld resid=%ld flags=0x%x pkt=0x%p\n",
	    (void *)bp->b_forw, bp->b_bcount,
	    bp->b_resid, bp->b_flags, (void *)BP_PKT(bp));
	st_print_cdb(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_start: cdb",  (caddr_t)cdbp);
	scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_start: fileno=%d, blk=%d\n",
	    un->un_pos.fileno, un->un_pos.blkno);
}
#endif


/*
 * Command start && done functions
 */

/*
 * st_start()
 *
 * Called from:
 *  st_strategy() to start a command.
 *  st_runout() to retry when scsi_pkt allocation fails on previous attempt(s).
 *  st_attach() when resuming from power down state.
 *  st_start_restart() to retry transport when device was previously busy.
 *  st_done_and_mutex_exit() to start the next command when previous is done.
 *
 * On entry:
 *  scsi_pkt may or may not be allocated.
 *
 */
static void
st_start(struct scsi_tape *un)
{
	struct buf *bp;
	int status;
	int queued;

	ST_FUNC(ST_DEVINFO, st_start);
	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_start(): dev = 0x%lx\n", un->un_dev);

	if (un->un_recov_buf_busy) {
		/* recovery commands can happen anytime */
		bp = un->un_recov_buf;
		queued = 0;
	} else if (un->un_sbuf_busy) {
		/* sbuf commands should only happen with an empty queue. */
		ASSERT(un->un_quef == NULL);
		ASSERT(un->un_runqf == NULL);
		bp = un->un_sbufp;
		queued = 0;
	} else if (un->un_quef != NULL) {
		if (un->un_persistence && un->un_persist_errors) {
			return;
		}
		bp = un->un_quef;
		queued = 1;
	} else {
		scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_start() returning no buf found\n");
		return;
	}

	ASSERT((bp->b_flags & B_DONE) == 0);

	/*
	 * Don't send more than un_throttle commands to the HBA
	 */
	if ((un->un_throttle <= 0) || (un->un_ncmds >= un->un_throttle)) {
		/*
		 * if doing recovery we know there is outstanding commands.
		 */
		if (bp != un->un_recov_buf) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_start returning throttle = %d or ncmds = %d\n",
			    un->un_throttle, un->un_ncmds);
			if (un->un_ncmds == 0) {
				typedef void (*func)();
				func fnc = (func)st_runout;

				scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "Sending delayed start to st_runout()\n");
				mutex_exit(ST_MUTEX);
				(void) timeout(fnc, un, drv_usectohz(1000000));
				mutex_enter(ST_MUTEX);
			}
			return;
		}
	}

	/*
	 * If the buf has no scsi_pkt call st_make_cmd() to get one and
	 * build the command.
	 */
	if (BP_PKT(bp) == NULL) {
		ASSERT((bp->b_flags & B_DONE) == 0);
		st_make_cmd(un, bp, st_runout);
		ASSERT((bp->b_flags & B_DONE) == 0);
		status = geterror(bp);

		/*
		 * Some HBA's don't call bioerror() to set an error.
		 * And geterror() returns zero if B_ERROR is not set.
		 * So if we get zero we must check b_error.
		 */
		if (status == 0 && bp->b_error != 0) {
			status = bp->b_error;
			bioerror(bp, status);
		}

		/*
		 * Some HBA's convert DDI_DMA_NORESOURCES into ENOMEM.
		 * In tape ENOMEM has special meaning so we'll change it.
		 */
		if (status == ENOMEM) {
			status = 0;
			bioerror(bp, status);
		}

		/*
		 * Did it fail and is it retryable?
		 * If so return and wait for the callback through st_runout.
		 * Also looks like scsi_init_pkt() will setup a callback even
		 * if it isn't retryable.
		 */
		if (BP_PKT(bp) == NULL) {
			if (status == 0) {
				/*
				 * If first attempt save state.
				 */
				if (un->un_state != ST_STATE_RESOURCE_WAIT) {
					un->un_laststate = un->un_state;
					un->un_state = ST_STATE_RESOURCE_WAIT;
				}
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "temp no resources for pkt\n");
			} else if (status == EINVAL) {
				scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "scsi_init_pkt rejected pkt as too big\n");
				if (un->un_persistence) {
					st_set_pe_flag(un);
				}
			} else {
				/*
				 * Unlikely that it would be retryable then not.
				 */
				if (un->un_state == ST_STATE_RESOURCE_WAIT) {
					un->un_state = un->un_laststate;
				}
				scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "perm no resources for pkt errno = 0x%x\n",
				    status);
			}
			return;
		}
		/*
		 * Worked this time set the state back.
		 */
		if (un->un_state == ST_STATE_RESOURCE_WAIT) {
			un->un_state = un->un_laststate;
		}
	}

	if (queued) {
		/*
		 * move from waitq to runq
		 */
		(void) st_remove_from_queue(&un->un_quef, &un->un_quel, bp);
		st_add_to_queue(&un->un_runqf, &un->un_runql, un->un_runql, bp);
	}


#ifdef STDEBUG
	st_start_dump(un, bp);
#endif

	/* could not get here if throttle was zero */
	un->un_last_throttle = un->un_throttle;
	un->un_throttle = 0;	/* so nothing else will come in here */
	un->un_ncmds++;

	ST_DO_KSTATS(bp, kstat_waitq_to_runq);

	status = st_transport(un, BP_PKT(bp));

	if (un->un_last_throttle) {
		un->un_throttle = un->un_last_throttle;
	}

	if (status != TRAN_ACCEPT) {
		ST_DO_KSTATS(bp, kstat_runq_back_to_waitq);
		ST_DEBUG(ST_DEVINFO, st_label, CE_WARN,
		    "Unhappy transport packet status 0x%x\n", status);

		if (status == TRAN_BUSY) {
			pkt_info *pkti = BP_PKT(bp)->pkt_private;

			/*
			 * If command recovery is enabled and this isn't
			 * a recovery command try command recovery.
			 */
			if (pkti->privatelen == sizeof (recov_info) &&
			    bp != un->un_recov_buf) {
				ST_RECOV(ST_DEVINFO, st_label, CE_WARN,
				    "Command Recovery called on busy send\n");
				if (st_command_recovery(un, BP_PKT(bp),
				    ATTEMPT_RETRY) == JUST_RETURN) {
					return;
				}
			} else {
				mutex_exit(ST_MUTEX);
				if (st_handle_start_busy(un, bp,
				    ST_TRAN_BUSY_TIMEOUT, queued) == 0) {
					mutex_enter(ST_MUTEX);
					return;
				}
				/*
				 * if too many retries, fail the transport
				 */
				mutex_enter(ST_MUTEX);
			}
		}
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "transport rejected %d\n", status);
		bp->b_resid = bp->b_bcount;

		ST_DO_KSTATS(bp, kstat_waitq_exit);
		ST_DO_ERRSTATS(un, st_transerrs);
		if ((bp == un->un_recov_buf) && (status == TRAN_BUSY)) {
			st_bioerror(bp, EBUSY);
		} else {
			st_bioerror(bp, EIO);
			st_set_pe_flag(un);
		}
		st_done_and_mutex_exit(un, bp);
		mutex_enter(ST_MUTEX);
	}

	ASSERT(mutex_owned(ST_MUTEX));
}

/*
 * if the transport is busy, then put this bp back on the waitq
 */
static int
st_handle_start_busy(struct scsi_tape *un, struct buf *bp,
    clock_t timeout_interval, int queued)
{

	pkt_info *pktinfo = BP_PKT(bp)->pkt_private;

	ST_FUNC(ST_DEVINFO, st_handle_start_busy);

	mutex_enter(ST_MUTEX);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_handle_start_busy()\n");

	/*
	 * Check to see if we hit the retry timeout and one last check for
	 * making sure this is the last on the runq, if it is not, we have
	 * to fail
	 */
	if ((pktinfo->str_retry_cnt++ > st_retry_count) ||
	    ((queued) && (un->un_runql != bp))) {
		mutex_exit(ST_MUTEX);
		return (-1);
	}

	if (queued) {
		/* put the bp back on the waitq */
		st_add_to_queue(&un->un_quef, &un->un_quel, un->un_quef, bp);
	}

	/*
	 * Decrement un_ncmds so that this
	 * gets thru' st_start() again.
	 */
	un->un_ncmds--;

	if (queued) {
		/*
		 * since this is an error case, we won't have to do this list
		 * walking much. We've already made sure this bp was the
		 * last on the runq
		 */
		(void) st_remove_from_queue(&un->un_runqf, &un->un_runql, bp);

		/*
		 * send a marker pkt, if appropriate
		 */
		st_hba_unflush(un);

	}
	/*
	 * all queues are aligned, we are just waiting to
	 * transport, don't alloc any more buf p's, when
	 * st_start is reentered.
	 */
	(void) timeout(st_start_restart, un, timeout_interval);

	mutex_exit(ST_MUTEX);
	return (0);
}


/*
 * st_runout a callback that is called what a resource allocatation failed
 */
static int
st_runout(caddr_t arg)
{
	struct scsi_tape *un = (struct scsi_tape *)arg;
	struct buf *bp;
	int queued;

	ASSERT(un != NULL);

	ST_FUNC(ST_DEVINFO, st_runout);

	mutex_enter(ST_MUTEX);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_runout()\n");

	if (un->un_recov_buf_busy != 0) {
		bp = un->un_recov_buf;
		queued = 0;
	} else if (un->un_sbuf_busy != 0) {
		/* sbuf commands should only happen with an empty queue. */
		ASSERT(un->un_quef == NULL);
		ASSERT(un->un_runqf == NULL);
		bp = un->un_sbufp;
		queued = 0;
	} else if (un->un_quef != NULL) {
		bp = un->un_quef;
		if (un->un_persistence && un->un_persist_errors) {
			mutex_exit(ST_MUTEX);
			bp->b_resid = bp->b_bcount;
			biodone(bp);
			return (1);
		}
		queued = 1;
	} else {
		ASSERT(1 == 0);
		mutex_exit(ST_MUTEX);
		return (1);
	}

	/*
	 * failed scsi_init_pkt(). If errno is zero its retryable.
	 */
	if ((bp != NULL) && (geterror(bp) != 0)) {

		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "errors after pkt alloc (b_flags=0x%x, b_error=0x%x)\n",
		    bp->b_flags, geterror(bp));
		ASSERT((bp->b_flags & B_DONE) == 0);

		if (queued) {
			(void) st_remove_from_queue(&un->un_quef, &un->un_quel,
			    bp);
		}
		mutex_exit(ST_MUTEX);

		ASSERT((bp->b_flags & B_DONE) == 0);

		/*
		 * Set resid, Error already set, then unblock calling thread.
		 */
		bp->b_resid = bp->b_bcount;
		biodone(bp);
	} else {
		/*
		 * Try Again
		 */
		st_start(un);
		mutex_exit(ST_MUTEX);
	}

	/*
	 * Comments courtesy of sd.c
	 * The scsi_init_pkt routine allows for the callback function to
	 * return a 0 indicating the callback should be rescheduled or a 1
	 * indicating not to reschedule. This routine always returns 1
	 * because the driver always provides a callback function to
	 * scsi_init_pkt. This results in a callback always being scheduled
	 * (via the scsi_init_pkt callback implementation) if a resource
	 * failure occurs.
	 */

	return (1);
}

/*
 * st_done_and_mutex_exit()
 *	- remove bp from runq
 *	- start up the next request
 *	- if this was an asynch bp, clean up
 *	- exit with released mutex
 */
static void
st_done_and_mutex_exit(struct scsi_tape *un, struct buf *bp)
{
	int pe_flagged = 0;
	struct scsi_pkt *pkt = BP_PKT(bp);
	pkt_info *pktinfo = pkt->pkt_private;

	ASSERT(MUTEX_HELD(&un->un_sd->sd_mutex));
#if !defined(lint)
	_NOTE(LOCK_RELEASED_AS_SIDE_EFFECT(&un->un_sd->sd_mutex))
#endif

	ST_FUNC(ST_DEVINFO, st_done_and_mutex_exit);

	ASSERT(mutex_owned(ST_MUTEX));

	(void) st_remove_from_queue(&un->un_runqf, &un->un_runql, bp);

	un->un_ncmds--;
	cv_signal(&un->un_queue_cv);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_done_and_mutex_exit(): cmd=0x%x count=%ld resid=%ld  flags="
	    "0x%x\n", pkt->pkt_cdbp[0], bp->b_bcount,
	    bp->b_resid, bp->b_flags);


	/*
	 * update kstats with transfer count info
	 */
	if (un->un_stats && (bp != un->un_sbufp) && IS_RW(bp)) {
		uint32_t n_done =  bp->b_bcount - bp->b_resid;
		if (bp->b_flags & B_READ) {
			IOSP->reads++;
			IOSP->nread += n_done;
		} else {
			IOSP->writes++;
			IOSP->nwritten += n_done;
		}
	}

	/*
	 * Start the next one before releasing resources on this one, if
	 * there is something on the queue and persistent errors has not been
	 * flagged
	 */

	if ((pe_flagged = (un->un_persistence && un->un_persist_errors)) != 0) {
		un->un_last_resid = bp->b_resid;
		un->un_last_count = bp->b_bcount;
	}

	if (un->un_pwr_mgmt == ST_PWR_SUSPENDED) {
		cv_broadcast(&un->un_tape_busy_cv);
	} else if (un->un_quef && un->un_throttle && !pe_flagged &&
	    (bp != un->un_recov_buf)) {
		st_start(un);
	}

	un->un_retry_ct = max(pktinfo->pkt_retry_cnt, pktinfo->str_retry_cnt);

	if (bp == un->un_sbufp && (bp->b_flags & B_ASYNC)) {
		/*
		 * Since we marked this ourselves as ASYNC,
		 * there isn't anybody around waiting for
		 * completion any more.
		 */
		uchar_t *cmd = pkt->pkt_cdbp;
		if (*cmd == SCMD_READ || *cmd == SCMD_WRITE) {
			bp->b_un.b_addr = (caddr_t)0;
		}
		ST_DEBUG(ST_DEVINFO, st_label, CE_NOTE,
		    "st_done_and_mutex_exit(async): freeing pkt\n");
		st_print_cdb(ST_DEVINFO, st_label, CE_NOTE,
		    "CDB sent with B_ASYNC",  (caddr_t)cmd);
		if (pkt) {
			scsi_destroy_pkt(pkt);
		}
		un->un_sbuf_busy = 0;
		cv_signal(&un->un_sbuf_cv);
		mutex_exit(ST_MUTEX);
		return;
	}

	if (bp == un->un_sbufp && BP_UCMD(bp)) {
		/*
		 * Copy status from scsi_pkt to uscsi_cmd
		 * since st_uscsi_cmd needs it
		 */
		BP_UCMD(bp)->uscsi_status = SCBP_C(BP_PKT(bp));
	}


#ifdef STDEBUG
	if (((st_debug & 0x7) >= 4) &&
	    (((un->un_pos.blkno % 100) == 0) ||
	    (un->un_persistence && un->un_persist_errors))) {

		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_d_a_m_exit(): ncmds = %d, thr = %d, "
		    "un_errno = %d, un_pe = %d\n",
		    un->un_ncmds, un->un_throttle, un->un_errno,
		    un->un_persist_errors);
	}

#endif

	mutex_exit(ST_MUTEX);
	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_done_and_mutex_exit: freeing pkt\n");

	if (pkt) {
		scsi_destroy_pkt(pkt);
	}

	biodone(bp);

	/*
	 * now that we biodoned that command, if persistent errors have been
	 * flagged, flush the waitq
	 */
	if (pe_flagged)
		st_flush(un);
}


/*
 * Tape error, flush tape driver queue.
 */
static void
st_flush(struct scsi_tape *un)
{
	struct buf *bp;

	ST_FUNC(ST_DEVINFO, st_flush);

	mutex_enter(ST_MUTEX);

	ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_flush(), ncmds = %d, quef = 0x%p\n",
	    un->un_ncmds, (void *)un->un_quef);

	/*
	 * if we still have commands outstanding, wait for them to come in
	 * before flushing the queue, and make sure there is a queue
	 */
	if (un->un_ncmds || !un->un_quef)
		goto exit;

	/*
	 * we have no more commands outstanding, so let's deal with special
	 * cases in the queue for EOM and FM. If we are here, and un_errno
	 * is 0, then we know there was no error and we return a 0 read or
	 * write before showing errors
	 */

	/* Flush the wait queue. */
	while ((bp = un->un_quef) != NULL) {
		un->un_quef = bp->b_actf;

		bp->b_resid = bp->b_bcount;

		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_flush() : blkno=%d, err=%d, b_bcount=%ld\n",
		    un->un_pos.blkno, un->un_errno, bp->b_bcount);

		st_set_pe_errno(un);

		bioerror(bp, un->un_errno);

		mutex_exit(ST_MUTEX);
		/* it should have one, but check anyway */
		if (BP_PKT(bp)) {
			scsi_destroy_pkt(BP_PKT(bp));
		}
		biodone(bp);
		mutex_enter(ST_MUTEX);
	}

	/*
	 * It's not a bad practice to reset the
	 * waitq tail pointer to NULL.
	 */
	un->un_quel = NULL;

exit:
	/* we mucked with the queue, so let others know about it */
	cv_signal(&un->un_queue_cv);
	mutex_exit(ST_MUTEX);
}


/*
 * Utility functions
 */
static int
st_determine_generic(struct scsi_tape *un)
{
	int bsize;
	static char *cart = "0.25 inch cartridge";
	char *sizestr;

	ST_FUNC(ST_DEVINFO, st_determine_generic);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_determine_generic(un = 0x%p)\n", (void*)un);

	ASSERT(mutex_owned(ST_MUTEX));

	if (st_modesense(un)) {
		return (-1);
	}

	bsize = (un->un_mspl->high_bl << 16)	|
	    (un->un_mspl->mid_bl << 8)	|
	    (un->un_mspl->low_bl);

	if (bsize == 0) {
		un->un_dp->options |= ST_VARIABLE;
		un->un_dp->bsize = 0;
		un->un_bsize = 0;
	} else if (bsize > ST_MAXRECSIZE_FIXED) {
		/*
		 * record size of this device too big.
		 * try and convert it to variable record length.
		 *
		 */
		un->un_dp->options |= ST_VARIABLE;
		if (st_change_block_size(un, 0) != 0) {
			ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
			    "Fixed Record Size %d is too large\n", bsize);
			ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
			    "Cannot switch to variable record size\n");
			un->un_dp->options &= ~ST_VARIABLE;
			return (-1);
		}
	} else if (st_change_block_size(un, 0) == 0) {
		/*
		 * If the drive was set to a non zero block size,
		 * See if it can be set to a zero block size.
		 * If it works, ST_VARIABLE so user can set it as they want.
		 */
		un->un_dp->options |= ST_VARIABLE;
		un->un_dp->bsize = 0;
		un->un_bsize = 0;
	} else {
		un->un_dp->bsize = bsize;
		un->un_bsize = bsize;
	}


	switch (un->un_mspl->density) {
	default:
	case 0x0:
		/*
		 * default density, cannot determine any other
		 * information.
		 */
		sizestr = "Unknown type- assuming 0.25 inch cartridge";
		un->un_dp->type = ST_TYPE_DEFAULT;
		un->un_dp->options |= (ST_AUTODEN_OVERRIDE|ST_QIC);
		break;
	case 0x1:
	case 0x2:
	case 0x3:
	case 0x6:
		/*
		 * 1/2" reel
		 */
		sizestr = "0.50 inch reel";
		un->un_dp->type = ST_TYPE_REEL;
		un->un_dp->options |= ST_REEL;
		un->un_dp->densities[0] = 0x1;
		un->un_dp->densities[1] = 0x2;
		un->un_dp->densities[2] = 0x6;
		un->un_dp->densities[3] = 0x3;
		break;
	case 0x4:
	case 0x5:
	case 0x7:
	case 0x0b:

		/*
		 * Quarter inch.
		 */
		sizestr = cart;
		un->un_dp->type = ST_TYPE_DEFAULT;
		un->un_dp->options |= ST_QIC;

		un->un_dp->densities[1] = 0x4;
		un->un_dp->densities[2] = 0x5;
		un->un_dp->densities[3] = 0x7;
		un->un_dp->densities[0] = 0x0b;
		break;

	case 0x0f:
	case 0x10:
	case 0x11:
	case 0x12:
		/*
		 * QIC-120, QIC-150, QIC-320, QIC-600
		 */
		sizestr = cart;
		un->un_dp->type = ST_TYPE_DEFAULT;
		un->un_dp->options |= ST_QIC;
		un->un_dp->densities[0] = 0x0f;
		un->un_dp->densities[1] = 0x10;
		un->un_dp->densities[2] = 0x11;
		un->un_dp->densities[3] = 0x12;
		break;

	case 0x09:
	case 0x0a:
	case 0x0c:
	case 0x0d:
		/*
		 * 1/2" cartridge tapes. Include HI-TC.
		 */
		sizestr = cart;
		sizestr[2] = '5';
		sizestr[3] = '0';
		un->un_dp->type = ST_TYPE_HIC;
		un->un_dp->densities[0] = 0x09;
		un->un_dp->densities[1] = 0x0a;
		un->un_dp->densities[2] = 0x0c;
		un->un_dp->densities[3] = 0x0d;
		break;

	case 0x13:
			/* DDS-2/DDS-3 scsi spec densities */
	case 0x24:
	case 0x25:
	case 0x26:
		sizestr = "DAT Data Storage (DDS)";
		un->un_dp->type = ST_TYPE_DAT;
		un->un_dp->options |= ST_AUTODEN_OVERRIDE;
		break;

	case 0x14:
		/*
		 * Helical Scan (Exabyte) devices
		 */
		sizestr = "8mm helical scan cartridge";
		un->un_dp->type = ST_TYPE_EXABYTE;
		un->un_dp->options |= ST_AUTODEN_OVERRIDE;
		break;
	}

	/*
	 * Assume LONG ERASE, BSF and BSR
	 */

	un->un_dp->options |=
	    (ST_LONG_ERASE | ST_UNLOADABLE | ST_BSF | ST_BSR | ST_KNOWS_EOD);

	/*
	 * Only if mode sense data says no buffered write, set NOBUF
	 */
	if (un->un_mspl->bufm == 0)
		un->un_dp->options |= ST_NOBUF;

	/*
	 * set up large read and write retry counts
	 */

	un->un_dp->max_rretries = un->un_dp->max_wretries = 1000;

	/*
	 * If this is a 0.50 inch reel tape, and
	 * it is *not* variable mode, try and
	 * set it to variable record length
	 * mode.
	 */
	if ((un->un_dp->options & ST_REEL) && un->un_bsize != 0 &&
	    (un->un_dp->options & ST_VARIABLE)) {
		if (st_change_block_size(un, 0) == 0) {
			un->un_dp->bsize = 0;
			un->un_mspl->high_bl = un->un_mspl->mid_bl =
			    un->un_mspl->low_bl = 0;
		}
	}

	/*
	 * Write to console about type of device found
	 */
	ST_DEBUG6(ST_DEVINFO, st_label, CE_NOTE,
	    "Generic Drive, Vendor=%s\n\t%s", un->un_dp->name,
	    sizestr);
	if (un->un_dp->options & ST_VARIABLE) {
		scsi_log(ST_DEVINFO, st_label, CE_NOTE,
		    "!Variable record length I/O\n");
	} else {
		scsi_log(ST_DEVINFO, st_label, CE_NOTE,
		    "!Fixed record length (%d byte blocks) I/O\n",
		    un->un_dp->bsize);
	}
	ASSERT(mutex_owned(ST_MUTEX));
	return (0);
}

static int
st_determine_density(struct scsi_tape *un, int rw)
{
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_determine_density);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_determine_density(un = 0x%p, rw = %s)\n",
	    (void*)un, (rw == B_WRITE ? wr_str: rd_str));

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * If we're past BOT, density is determined already.
	 */
	if (un->un_pos.pmode == logical) {
		if (un->un_pos.lgclblkno != 0) {
			goto exit;
		}
	} else if (un->un_pos.pmode == legacy) {
		if ((un->un_pos.fileno != 0) || (un->un_pos.blkno != 0)) {
			/*
			 * XXX: put in a bitch message about attempting to
			 * XXX: change density past BOT.
			 */
			goto exit;
		}
	} else {
		goto exit;
	}
	if ((un->un_pos.pmode == logical) &&
	    (un->un_pos.lgclblkno != 0)) {
		goto exit;
	}


	/*
	 * If we're going to be writing, we set the density
	 */
	if (rw == 0 || rw == B_WRITE) {
		/* un_curdens is used as an index into densities table */
		un->un_curdens = MT_DENSITY(un->un_dev);
		if (st_set_density(un)) {
			rval = -1;
		}
		goto exit;
	}

	/*
	 * If density is known already,
	 * we don't have to get it again.(?)
	 */
	if (!un->un_density_known) {
		if (st_get_density(un)) {
			rval = -1;
		}
	}

exit:
	ASSERT(mutex_owned(ST_MUTEX));
	return (rval);
}


/*
 * Try to determine density. We do this by attempting to read the
 * first record off the tape, cycling through the available density
 * codes as we go.
 */

static int
st_get_density(struct scsi_tape *un)
{
	int succes = 0, rval = -1, i;
	uint_t size;
	uchar_t dens, olddens;

	ST_FUNC(ST_DEVINFO, st_get_density);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_get_density(un = 0x%p)\n", (void*)un);

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * If Auto Density override is enabled The drive has
	 * only one density and there is no point in attempting
	 * find the correct one.
	 *
	 * Since most modern drives auto detect the density
	 * and format of the recorded media before they come
	 * ready. What this function does is a legacy behavior
	 * and modern drives not only don't need it, The backup
	 * utilities that do positioning via uscsi find the un-
	 * expected rewinds problematic.
	 *
	 * The drives that need this are old reel to reel devices.
	 * I took a swag and said they must be scsi-1 or older.
	 * I don't beleave there will any of the newer devices
	 * that need this. There will be some scsi-1 devices that
	 * don't need this but I don't think they will be using the
	 * BIG aftermarket backup and restore utilitys.
	 */
	if ((un->un_dp->options & ST_AUTODEN_OVERRIDE) ||
	    (un->un_sd->sd_inq->inq_ansi > 1)) {
		un->un_density_known = 1;
		rval = 0;
		goto exit;
	}

	/*
	 * This will only work on variable record length tapes
	 * if and only if all variable record length tapes autodensity
	 * select.
	 */
	size = (unsigned)(un->un_dp->bsize ? un->un_dp->bsize : SECSIZE);
	un->un_tmpbuf = kmem_alloc(size, KM_SLEEP);

	/*
	 * Start at the specified density
	 */

	dens = olddens = un->un_curdens = MT_DENSITY(un->un_dev);

	for (i = 0; i < NDENSITIES; i++, ((un->un_curdens == NDENSITIES - 1) ?
	    (un->un_curdens = 0) : (un->un_curdens += 1))) {
		/*
		 * If we've done this density before,
		 * don't bother to do it again.
		 */
		dens = un->un_dp->densities[un->un_curdens];
		if (i > 0 && dens == olddens)
			continue;
		olddens = dens;
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "trying density 0x%x\n", dens);
		if (st_set_density(un)) {
			continue;
		}

		/*
		 * XXX - the creates lots of headaches and slowdowns - must
		 * fix.
		 */
		succes = (st_cmd(un, SCMD_READ, (int)size, SYNC_CMD) == 0);
		if (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD)) {
			break;
		}
		if (succes) {
			st_init(un);
			rval = 0;
			un->un_density_known = 1;
			break;
		}
	}
	kmem_free(un->un_tmpbuf, size);
	un->un_tmpbuf = 0;

exit:
	ASSERT(mutex_owned(ST_MUTEX));
	return (rval);
}

static int
st_set_density(struct scsi_tape *un)
{
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_set_density);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_set_density(un = 0x%p): density = 0x%x\n", (void*)un,
	    un->un_dp->densities[un->un_curdens]);

	ASSERT(mutex_owned(ST_MUTEX));

	un->un_mspl->density = un->un_dp->densities[un->un_curdens];

	if ((un->un_dp->options & ST_AUTODEN_OVERRIDE) == 0) {
		/*
		 * If auto density override is not set, Use mode select
		 * to set density and compression.
		 */
		if (st_modeselect(un)) {
			rval = -1;
		}
	} else if ((un->un_dp->options & ST_MODE_SEL_COMP) != 0) {
		/*
		 * If auto density and mode select compression are set,
		 * This is a drive with one density code but compression
		 * can be enabled or disabled.
		 * Set compression but no need to set density.
		 */
		rval = st_set_compression(un);
		if ((rval != 0) && (rval != EALREADY)) {
			rval = -1;
		} else {
			rval = 0;
		}
	}

	/* If sucessful set density and/or compression, mark density known */
	if (rval == 0) {
		un->un_density_known = 1;
	}

	ASSERT(mutex_owned(ST_MUTEX));
	return (rval);
}

static int
st_loadtape(struct scsi_tape *un)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_loadtape);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_loadtape(un = 0x%p)\n", (void*) un);

	ASSERT(mutex_owned(ST_MUTEX));

	rval = st_update_block_pos(un, st_cmd, 0);
	if (rval == EACCES) {
		return (rval);
	}

	/*
	 * 'LOAD' the tape to BOT by rewinding
	 */
	rval = st_cmd(un, SCMD_REWIND, 1, SYNC_CMD);
	if (rval == 0) {
		st_init(un);
		un->un_density_known = 0;
	}

	ASSERT(mutex_owned(ST_MUTEX));
	return (rval);
}


/*
 * Note: QIC devices aren't so smart.  If you try to append
 * after EOM, the write can fail because the device doesn't know
 * it's at EOM.	 In that case, issue a read.  The read should fail
 * because there's no data, but the device knows it's at EOM,
 * so a subsequent write should succeed.  To further confuse matters,
 * the target returns the same error if the tape is positioned
 * such that a write would overwrite existing data.  That's why
 * we have to do the append test.  A read in the middle of
 * recorded data would succeed, thus indicating we're attempting
 * something illegal.
 */


static void
st_test_append(struct buf *bp)
{
	dev_t dev = bp->b_edev;
	struct scsi_tape *un;
	uchar_t status;
	unsigned bcount;

	un = ddi_get_soft_state(st_state, MTUNIT(dev));

	ST_FUNC(ST_DEVINFO, st_test_append);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_test_append(): fileno %d\n", un->un_pos.fileno);

	un->un_laststate = un->un_state;
	un->un_state = ST_STATE_APPEND_TESTING;
	un->un_test_append = 0;

	/*
	 * first, map in the buffer, because we're doing a double write --
	 * first into the kernel, then onto the tape.
	 */
	bp_mapin(bp);

	/*
	 * get a copy of the data....
	 */
	un->un_tmpbuf = kmem_alloc((unsigned)bp->b_bcount, KM_SLEEP);
	bcopy(bp->b_un.b_addr, un->un_tmpbuf, (uint_t)bp->b_bcount);

	/*
	 * attempt the write..
	 */

	if (st_cmd(un, (int)SCMD_WRITE, (int)bp->b_bcount, SYNC_CMD) == 0) {
success:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "append write succeeded\n");
		bp->b_resid = un->un_sbufp->b_resid;
		mutex_exit(ST_MUTEX);
		bcount = (unsigned)bp->b_bcount;
		biodone(bp);
		mutex_enter(ST_MUTEX);
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_OPEN;
		kmem_free(un->un_tmpbuf, bcount);
		un->un_tmpbuf = NULL;
		return;
	}

	/*
	 * The append failed. Do a short read. If that fails,  we are at EOM
	 * so we can retry the write command. If that succeeds, than we're
	 * all screwed up (the controller reported a real error).
	 *
	 * XXX: should the dummy read be > SECSIZE? should it be the device's
	 * XXX: block size?
	 *
	 */
	status = un->un_status;
	un->un_status = 0;
	(void) st_cmd(un, SCMD_READ, SECSIZE, SYNC_CMD);
	if (un->un_status == KEY_BLANK_CHECK) {
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "append at EOM\n");
		/*
		 * Okay- the read failed. We should actually have confused
		 * the controller enough to allow writing. In any case, the
		 * i/o is on its own from here on out.
		 */
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_OPEN;
		bcopy(bp->b_un.b_addr, un->un_tmpbuf, (uint_t)bp->b_bcount);
		if (st_cmd(un, (int)SCMD_WRITE, (int)bp->b_bcount,
		    SYNC_CMD) == 0) {
			goto success;
		}
	}

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "append write failed- not at EOM\n");
	bp->b_resid = bp->b_bcount;
	st_bioerror(bp, EIO);

	ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_test_append : EIO : append write failed - not at EOM");

	/*
	 * backspace one record to get back to where we were
	 */
	if (st_cmd(un, SCMD_SPACE, Blk(-1), SYNC_CMD)) {
		un->un_pos.pmode = invalid;
	}

	un->un_err_resid = bp->b_resid;
	un->un_status = status;

	/*
	 * Note: biodone will do a bp_mapout()
	 */
	mutex_exit(ST_MUTEX);
	bcount = (unsigned)bp->b_bcount;
	biodone(bp);
	mutex_enter(ST_MUTEX);
	un->un_laststate = un->un_state;
	un->un_state = ST_STATE_OPEN_PENDING_IO;
	kmem_free(un->un_tmpbuf, bcount);
	un->un_tmpbuf = NULL;
}

/*
 * Special command handler
 */

/*
 * common st_cmd code. The fourth parameter states
 * whether the caller wishes to await the results
 * Note the release of the mutex during most of the function
 */
static int
st_cmd(struct scsi_tape *un, int com, int64_t count, int wait)
{
	struct buf *bp;
	int err;
	uint_t last_err_resid;

	ST_FUNC(ST_DEVINFO, st_cmd);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_cmd(dev = 0x%lx, com = 0x%x, count = %"PRIx64", wait = %d)\n",
	    un->un_dev, com, count, wait);

	ASSERT(MUTEX_HELD(&un->un_sd->sd_mutex));
	ASSERT(mutex_owned(ST_MUTEX));

#ifdef STDEBUG
	if ((st_debug & 0x7)) {
		st_debug_cmds(un, com, count, wait);
	}
#endif

	st_wait_for_io(un);

	/* check to see if this command requires the drive to be reserved */
	err = st_check_cmd_for_need_to_reserve(un, com, count);

	if (err) {
		return (err);
	}

	/*
	 * A space command is not recoverable if we don't know were we
	 * were when it was issued.
	 */
	if ((com == SCMD_SPACE) || (com == SCMD_SPACE_G4)) {
		(void) st_update_block_pos(un, st_cmd, 0);
	}

	/*
	 * Forground should not be doing anything while recovery is active.
	 */
	ASSERT(un->un_recov_buf_busy == 0);

	while (un->un_sbuf_busy)
		cv_wait(&un->un_sbuf_cv, ST_MUTEX);
	un->un_sbuf_busy = 1;

	bp = un->un_sbufp;
	bzero(bp, sizeof (buf_t));

	bp->b_flags = (wait) ? B_BUSY : B_BUSY|B_ASYNC;

	err = st_setup_cmd(un, bp, com, count);

	un->un_sbuf_busy = 0;

	/*
	 * If was a space command need to update logical block position.
	 * Only do this if the command was sucessful or it will mask the fact
	 * that the space command failed by promoting the pmode to logical.
	 */
	if (((com == SCMD_SPACE) || (com == SCMD_SPACE_G4)) &&
	    (un->un_pos.pmode != invalid)) {
		un->un_running.pmode = invalid;
		last_err_resid = un->un_err_resid;
		(void) st_update_block_pos(un, st_cmd, 1);
		/*
		 * Set running position to invalid so it updates on the
		 * next command.
		 */
		un->un_running.pmode = invalid;
		un->un_err_resid = last_err_resid;
	}

	cv_signal(&un->un_sbuf_cv);

	return (err);
}

static int
st_setup_cmd(struct scsi_tape *un, buf_t *bp, int com, int64_t count)
{
	int err;
	dev_t dev = un->un_dev;

	ST_FUNC(ST_DEVINFO, st_setup_cmd);
	/*
	 * Set count to the actual size of the data tranfer.
	 * For commands with no data transfer, set bp->b_bcount
	 * to the value to be used when constructing the
	 * cdb in st_make_cmd().
	 */
	switch (com) {
	case SCMD_READ:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "special read %"PRId64"\n", count);
		bp->b_flags |= B_READ;
		bp->b_un.b_addr = un->un_tmpbuf;
		break;

	case SCMD_WRITE:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "special write %"PRId64"\n", count);
		bp->b_un.b_addr = un->un_tmpbuf;
		break;

	case SCMD_WRITE_FILE_MARK:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "write %"PRId64" file marks\n", count);
		bp->b_bcount = count;
		count = 0;
		break;

	case SCMD_REWIND:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "rewind\n");
		bp->b_bcount = count;
		count = 0;
		break;

	case SCMD_SPACE:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "space\n");
		/*
		 * If the user could have entered a number that will
		 * not fit in the 12 bit count field of space(8),
		 * use space(16).
		 */
		if (((int64_t)SPACE_CNT(count) > 0x7fffff) ||
		    ((int64_t)SPACE_CNT(count) < -(0x7fffff))) {
			com = SCMD_SPACE_G4;
		}
		bp->b_bcount = count;
		count = 0;
		break;

	case SCMD_RESERVE:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "reserve");
		bp->b_bcount = 0;
		count = 0;
		break;

	case SCMD_RELEASE:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "release");
		bp->b_bcount = 0;
		count = 0;
		break;

	case SCMD_LOAD:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "%s tape\n", (count & LD_LOAD) ? "load" : "unload");
		bp->b_bcount = count;
		count = 0;
		break;

	case SCMD_ERASE:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "erase tape\n");
		bp->b_bcount = count;
		count = 0;
		break;

	case SCMD_MODE_SENSE:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "mode sense\n");
		bp->b_flags |= B_READ;
		bp->b_un.b_addr = (caddr_t)(un->un_mspl);
		break;

	case SCMD_MODE_SELECT:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "mode select\n");
		bp->b_un.b_addr = (caddr_t)(un->un_mspl);
		break;

	case SCMD_READ_BLKLIM:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "read block limits\n");
		bp->b_bcount = count;
		bp->b_flags |= B_READ;
		bp->b_un.b_addr = (caddr_t)(un->un_rbl);
		break;

	case SCMD_TEST_UNIT_READY:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "test unit ready\n");
		bp->b_bcount = 0;
		count = 0;
		break;

	case SCMD_DOORLOCK:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "%s tape\n", (count & MR_LOCK) ? "lock" : "unlock");
		bp->b_bcount = count = 0;
		break;

	case SCMD_READ_POSITION:
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "read position\n");
		switch (un->un_read_pos_type) {
		case LONG_POS:
			count = sizeof (tape_position_long_t);
			break;
		case EXT_POS:
			count = min(count, sizeof (tape_position_ext_t));
			break;
		case SHORT_POS:
			count = sizeof (tape_position_t);
			break;
		default:
			ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
			    "Unknown read position type 0x%x in "
			    "st_make_cmd()\n", un->un_read_pos_type);
		}
		bp->b_bcount = count;
		bp->b_flags |= B_READ;
		bp->b_un.b_addr = (caddr_t)un->un_read_pos_data;
		break;

	default:
		ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
		    "Unhandled scsi command 0x%x in st_setup_cmd()\n", com);
	}

	mutex_exit(ST_MUTEX);

	if (count > 0) {
		int flg = (bp->b_flags & B_READ) ? B_READ : B_WRITE;
		/*
		 * We're going to do actual I/O.
		 * Set things up for physio.
		 */
		struct iovec aiov;
		struct uio auio;
		struct uio *uio = &auio;

		bzero(&auio, sizeof (struct uio));
		bzero(&aiov, sizeof (struct iovec));
		aiov.iov_base = bp->b_un.b_addr;
		aiov.iov_len = count;

		uio->uio_iov = &aiov;
		uio->uio_iovcnt = 1;
		uio->uio_resid = aiov.iov_len;
		uio->uio_segflg = UIO_SYSSPACE;

		/*
		 * Let physio do the rest...
		 */
		bp->b_forw = (struct buf *)(uintptr_t)com;
		bp->b_back = NULL;
		err = physio(st_strategy, bp, dev, flg, st_minphys, uio);
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_setup_cmd: physio returns %d\n", err);
	} else {
		/*
		 * Mimic physio
		 */
		bp->b_forw = (struct buf *)(uintptr_t)com;
		bp->b_back = NULL;
		bp->b_edev = dev;
		bp->b_dev = cmpdev(dev);
		bp->b_blkno = 0;
		bp->b_resid = 0;
		(void) st_strategy(bp);
		if (bp->b_flags & B_ASYNC) {
			/*
			 * This is an async command- the caller won't wait
			 * and doesn't care about errors.
			 */
			mutex_enter(ST_MUTEX);
			return (0);
		}

		/*
		 * BugTraq #4260046
		 * ----------------
		 * Restore Solaris 2.5.1 behavior, namely call biowait
		 * unconditionally. The old comment said...
		 *
		 * "if strategy was flagged with  persistent errors, we would
		 *  have an error here, and the bp would never be sent, so we
		 *  don't want to wait on a bp that was never sent...or hang"
		 *
		 * The new rationale, courtesy of Chitrank...
		 *
		 * "we should unconditionally biowait() here because
		 *  st_strategy() will do a biodone() in the persistent error
		 *  case and the following biowait() will return immediately.
		 *  If not, in the case of "errors after pkt alloc" in
		 *  st_start(), we will not biowait here which will cause the
		 *  next biowait() to return immediately which will cause
		 *  us to send out the next command. In the case where both of
		 *  these use the sbuf, when the first command completes we'll
		 *  free the packet attached to sbuf and the same pkt will
		 *  get freed again when we complete the second command.
		 *  see esc 518987.  BTW, it is necessary to do biodone() in
		 *  st_start() for the pkt alloc failure case because physio()
		 *  does biowait() and will hang if we don't do biodone()"
		 */

		err = biowait(bp);
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_setup_cmd: biowait returns %d\n", err);
	}

	mutex_enter(ST_MUTEX);

	return (err);
}

static int
st_set_compression(struct scsi_tape *un)
{
	int rval;
	int turn_compression_on;
	minor_t minor;

	ST_FUNC(ST_DEVINFO, st_set_compression);

	/*
	 * Drive either dosn't have compression or it is controlled with
	 * special density codes. Return ENOTTY so caller
	 * knows nothing was done.
	 */
	if ((un->un_dp->options & ST_MODE_SEL_COMP) == 0) {
		un->un_comp_page = 0;
		return (ENOTTY);
	}

	/* set compression based on minor node opened */
	minor = MT_DENSITY(un->un_dev);

	/*
	 * If this the compression density or
	 * the drive has two densities and uses mode select for
	 * control of compression turn on compression for MT_DENSITY2
	 * as well.
	 */
	if ((minor == ST_COMPRESSION_DENSITY) ||
	    (minor == MT_DENSITY(MT_DENSITY2)) &&
	    (un->un_dp->densities[0] == un->un_dp->densities[1]) &&
	    (un->un_dp->densities[2] == un->un_dp->densities[3]) &&
	    (un->un_dp->densities[0] != un->un_dp->densities[2])) {

		turn_compression_on = 1;
	} else {
		turn_compression_on = 0;
	}

	un->un_mspl->high_bl = (uchar_t)(un->un_bsize >> 16);
	un->un_mspl->mid_bl  = (uchar_t)(un->un_bsize >> 8);
	un->un_mspl->low_bl  = (uchar_t)(un->un_bsize);

	/*
	 * Need to determine which page does the device use for compression.
	 * First try the data compression page. If this fails try the device
	 * configuration page
	 */

	if ((un->un_comp_page & ST_DEV_DATACOMP_PAGE) == ST_DEV_DATACOMP_PAGE) {
		rval = st_set_datacomp_page(un, turn_compression_on);
		if (rval == EALREADY) {
			return (rval);
		}
		if (rval != 0) {
			if (un->un_status == KEY_ILLEGAL_REQUEST) {
				/*
				 * This device does not support data
				 * compression page
				 */
				un->un_comp_page = ST_DEV_CONFIG_PAGE;
			} else if (un->un_state >= ST_STATE_OPEN) {
				un->un_pos.pmode = invalid;
				rval = EIO;
			} else {
				rval = -1;
			}
		} else {
			un->un_comp_page = ST_DEV_DATACOMP_PAGE;
		}
	}

	if ((un->un_comp_page & ST_DEV_CONFIG_PAGE) == ST_DEV_CONFIG_PAGE) {
		rval = st_set_devconfig_page(un, turn_compression_on);
		if (rval == EALREADY) {
			return (rval);
		}
		if (rval != 0) {
			if (un->un_status == KEY_ILLEGAL_REQUEST) {
				/*
				 * This device does not support
				 * compression at all advice the
				 * user and unset ST_MODE_SEL_COMP
				 */
				un->un_dp->options &= ~ST_MODE_SEL_COMP;
				un->un_comp_page = 0;
				scsi_log(ST_DEVINFO, st_label, CE_NOTE,
				    "Device Does Not Support Compression\n");
			} else if (un->un_state >= ST_STATE_OPEN) {
				un->un_pos.pmode = invalid;
				rval = EIO;
			} else {
				rval = -1;
			}
		}
	}

	return (rval);
}

/*
 * set or unset compression thru device configuration page.
 */
static int
st_set_devconfig_page(struct scsi_tape *un, int compression_on)
{
	unsigned char cflag;
	int rval = 0;


	ST_FUNC(ST_DEVINFO, st_set_devconfig_page);

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * if the mode sense page is not the correct one, load the correct one.
	 */
	if (un->un_mspl->page_code != ST_DEV_CONFIG_PAGE) {
		rval = st_gen_mode_sense(un, st_uscsi_cmd, ST_DEV_CONFIG_PAGE,
		    un->un_mspl, sizeof (struct seq_mode));
		if (rval)
			return (rval);
	}

	/*
	 * Figure what to set compression flag to.
	 */
	if (compression_on) {
		/* They have selected a compression node */
		if (un->un_dp->type == ST_TYPE_FUJI) {
			cflag = 0x84;   /* use EDRC */
		} else {
			cflag = ST_DEV_CONFIG_DEF_COMP;
		}
	} else {
		cflag = ST_DEV_CONFIG_NO_COMP;
	}

	/*
	 * If compression is already set the way it was requested.
	 * And if this not the first time we has tried.
	 */
	if ((cflag == un->un_mspl->page.dev.comp_alg) &&
	    (un->un_comp_page == ST_DEV_CONFIG_PAGE)) {
		return (EALREADY);
	}

	un->un_mspl->page.dev.comp_alg = cflag;
	/*
	 * need to send mode select even if correct compression is
	 * already set since need to set density code
	 */

#ifdef STDEBUG
	if ((st_debug & 0x7) >= 6) {
		st_clean_print(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_set_devconfig_page: sense data for mode select",
		    (char *)un->un_mspl, sizeof (struct seq_mode));
	}
#endif
	rval = st_gen_mode_select(un, st_uscsi_cmd, un->un_mspl,
	    sizeof (struct seq_mode));

	return (rval);
}

/*
 * set/reset compression bit thru data compression page
 */
static int
st_set_datacomp_page(struct scsi_tape *un, int compression_on)
{
	int compression_on_already;
	int rval = 0;


	ST_FUNC(ST_DEVINFO, st_set_datacomp_page);

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * if the mode sense page is not the correct one, load the correct one.
	 */
	if (un->un_mspl->page_code != ST_DEV_DATACOMP_PAGE) {
		rval = st_gen_mode_sense(un, st_uscsi_cmd, ST_DEV_DATACOMP_PAGE,
		    un->un_mspl, sizeof (struct seq_mode));
		if (rval)
			return (rval);
	}

	/*
	 * If drive is not capable of compression (at this time)
	 * return EALREADY so caller doesn't think that this page
	 * is not supported. This check is for drives that can
	 * disable compression from the front panel or configuration.
	 * I doubt that a drive that supports this page is not really
	 * capable of compression.
	 */
	if (un->un_mspl->page.comp.dcc == 0) {
		return (EALREADY);
	}

	/* See if compression currently turned on */
	if (un->un_mspl->page.comp.dce) {
		compression_on_already = 1;
	} else {
		compression_on_already = 0;
	}

	/*
	 * If compression is already set the way it was requested.
	 * And if this not the first time we has tried.
	 */
	if ((compression_on == compression_on_already) &&
	    (un->un_comp_page == ST_DEV_DATACOMP_PAGE)) {
		return (EALREADY);
	}

	/*
	 * if we are already set to the appropriate compression
	 * mode, don't set it again
	 */
	if (compression_on) {
		/* compression selected */
		un->un_mspl->page.comp.dce = 1;
	} else {
		un->un_mspl->page.comp.dce = 0;
	}


#ifdef STDEBUG
	if ((st_debug & 0x7) >= 6) {
		st_clean_print(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_set_datacomp_page: sense data for mode select",
		    (char *)un->un_mspl, sizeof (struct seq_mode));
	}
#endif
	rval = st_gen_mode_select(un, st_uscsi_cmd, un->un_mspl,
	    sizeof (struct seq_mode));

	return (rval);
}

static int
st_modesense(struct scsi_tape *un)
{
	int rval;
	uchar_t page;

	ST_FUNC(ST_DEVINFO, st_modesense);

	page = un->un_comp_page;

	switch (page) {
	case ST_DEV_DATACOMP_PAGE:
	case ST_DEV_CONFIG_PAGE: /* FALLTHROUGH */
		rval = st_gen_mode_sense(un, st_uscsi_cmd, page, un->un_mspl,
		    sizeof (struct seq_mode));
		break;

	case ST_DEV_DATACOMP_PAGE | ST_DEV_CONFIG_PAGE:
		if (un->un_dp->options & ST_MODE_SEL_COMP) {
			page = ST_DEV_DATACOMP_PAGE;
			rval = st_gen_mode_sense(un, st_uscsi_cmd, page,
			    un->un_mspl, sizeof (struct seq_mode));
			if (rval == 0 && un->un_mspl->page_code == page) {
				un->un_comp_page = page;
				break;
			}
			page = ST_DEV_CONFIG_PAGE;
			rval = st_gen_mode_sense(un, st_uscsi_cmd, page,
			    un->un_mspl, sizeof (struct seq_mode));
			if (rval == 0 && un->un_mspl->page_code == page) {
				un->un_comp_page = page;
				break;
			}
			un->un_dp->options &= ~ST_MODE_SEL_COMP;
			un->un_comp_page = 0;
		} else {
			un->un_comp_page = 0;
		}

	default:	/* FALLTHROUGH */
		rval = st_cmd(un, SCMD_MODE_SENSE, MSIZE, SYNC_CMD);
	}
	return (rval);
}

static int
st_modeselect(struct scsi_tape *un)
{
	int rval = 0;
	int ix;

	ST_FUNC(ST_DEVINFO, st_modeselect);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_modeselect(dev = 0x%lx): density = 0x%x\n",
	    un->un_dev, un->un_mspl->density);

	ASSERT(mutex_owned(ST_MUTEX));

	/*
	 * The parameter list should be the same for all of the
	 * cases that follow so set them here
	 *
	 * Try mode select first if if fails set fields manually
	 */
	rval = st_modesense(un);
	if (rval != 0) {
		ST_DEBUG3(ST_DEVINFO, st_label, CE_WARN,
		    "st_modeselect: First mode sense failed\n");
		un->un_mspl->bd_len  = 8;
		un->un_mspl->high_nb = 0;
		un->un_mspl->mid_nb  = 0;
		un->un_mspl->low_nb  = 0;
	}
	un->un_mspl->high_bl = (uchar_t)(un->un_bsize >> 16);
	un->un_mspl->mid_bl  = (uchar_t)(un->un_bsize >> 8);
	un->un_mspl->low_bl  = (uchar_t)(un->un_bsize);


	/*
	 * If configured to use a specific density code for a media type.
	 * curdens is previously set by the minor node opened.
	 * If the media type doesn't match the minor node we change it so it
	 * looks like the correct one was opened.
	 */
	if (un->un_dp->options & ST_KNOWS_MEDIA) {
		uchar_t best;

		for (best = 0xff, ix = 0; ix < NDENSITIES; ix++) {
			if (un->un_mspl->media_type ==
			    un->un_dp->mediatype[ix]) {
				best = ix;
				/*
				 * It matches but it might not be the only one.
				 * Use the highest matching media type but not
				 * to exceed the density selected by the open.
				 */
				if (ix < un->un_curdens) {
					continue;
				}
				un->un_curdens = ix;
				break;
			}
		}
		/* If a match was found best will not be 0xff any more */
		if (best < NDENSITIES) {
			ST_DEBUG3(ST_DEVINFO, st_label, CE_WARN,
			    "found media 0x%X using density 0x%X\n",
			    un->un_mspl->media_type,
			    un->un_dp->densities[best]);
			un->un_mspl->density = un->un_dp->densities[best];
		} else {
			/* Otherwise set density based on minor node opened */
			un->un_mspl->density =
			    un->un_dp->densities[un->un_curdens];
		}
	} else {
		un->un_mspl->density = un->un_dp->densities[un->un_curdens];
	}

	if (un->un_dp->options & ST_NOBUF) {
		un->un_mspl->bufm = 0;
	} else {
		un->un_mspl->bufm = 1;
	}

	rval = st_set_compression(un);

	/*
	 * If st_set_compression returned invalid or already it
	 * found no need to do the mode select.
	 * So do it here.
	 */
	if ((rval == ENOTTY) || (rval == EALREADY)) {

		/* Zero non-writeable fields */
		un->un_mspl->data_len = 0;
		un->un_mspl->media_type = 0;
		un->un_mspl->wp = 0;

		/* need to set the density code */
		rval = st_cmd(un, SCMD_MODE_SELECT, MSIZE, SYNC_CMD);
		if (rval != 0) {
			if (un->un_state >= ST_STATE_OPEN) {
				ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
				    "unable to set tape mode\n");
				un->un_pos.pmode = invalid;
				rval = EIO;
			} else {
				rval = -1;
			}
		}
	}

	/*
	 * The spec recommends to send a mode sense after a mode select
	 */
	(void) st_modesense(un);

	ASSERT(mutex_owned(ST_MUTEX));

	return (rval);
}

/*
 * st_gen_mode_sense
 *
 * generic mode sense.. it allows for any page
 */
static int
st_gen_mode_sense(struct scsi_tape *un, ubufunc_t ubf, int page,
    struct seq_mode *page_data, int page_size)
{

	int r;
	char	cdb[CDB_GROUP0];
	struct uscsi_cmd *com;
	struct scsi_arq_status status;

	ST_FUNC(ST_DEVINFO, st_gen_mode_sense);

	com = kmem_zalloc(sizeof (*com), KM_SLEEP);

	bzero(cdb, CDB_GROUP0);
	cdb[0] = SCMD_MODE_SENSE;
	cdb[2] = (char)page;
	cdb[4] = (char)page_size;

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP0;
	com->uscsi_bufaddr = (caddr_t)page_data;
	com->uscsi_buflen = page_size;
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;
	com->uscsi_flags = USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;

	r = ubf(un, com, FKIOCTL);
	kmem_free(com, sizeof (*com));
	return (r);
}

/*
 * st_gen_mode_select
 *
 * generic mode select.. it allows for any page
 */
static int
st_gen_mode_select(struct scsi_tape *un, ubufunc_t ubf,
    struct seq_mode *page_data, int page_size)
{

	int r;
	char cdb[CDB_GROUP0];
	struct uscsi_cmd *com;
	struct scsi_arq_status status;

	ST_FUNC(ST_DEVINFO, st_gen_mode_select);

	/* Zero non-writeable fields */
	page_data->data_len = 0;
	page_data->media_type = 0;
	page_data->wp = 0;

	/*
	 * If mode select has any page data, zero the ps (Page Savable) bit.
	 */
	if (page_size > MSIZE) {
		page_data->ps = 0;
	}


	com = kmem_zalloc(sizeof (*com), KM_SLEEP);

	/*
	 * then, do a mode select to set what ever info
	 */
	bzero(cdb, CDB_GROUP0);
	cdb[0] = SCMD_MODE_SELECT;
	cdb[1] = 0x10;		/* set PF bit for many third party drives */
	cdb[4] = (char)page_size;

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP0;
	com->uscsi_bufaddr = (caddr_t)page_data;
	com->uscsi_buflen = page_size;
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;
	com->uscsi_flags = USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_WRITE;

	r = ubf(un, com, FKIOCTL);

	kmem_free(com, sizeof (*com));
	return (r);
}

static int
st_read_block_limits(struct scsi_tape *un, struct read_blklim *read_blk)
{
	int rval;
	char cdb[CDB_GROUP0];
	struct uscsi_cmd *com;
	struct scsi_arq_status status;

	ST_FUNC(ST_DEVINFO, st_read_block_limits);

	com = kmem_zalloc(sizeof (*com), KM_SLEEP);

	bzero(cdb, CDB_GROUP0);
	cdb[0] = SCMD_READ_BLKLIM;

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP0;
	com->uscsi_bufaddr = (caddr_t)read_blk;
	com->uscsi_buflen = sizeof (struct read_blklim);
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;
	com->uscsi_flags = USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;

	rval = st_uscsi_cmd(un, com, FKIOCTL);
	if (com->uscsi_status || com->uscsi_resid) {
		rval = -1;
	}

	kmem_free(com, sizeof (*com));
	return (rval);
}

static int
st_report_density_support(struct scsi_tape *un, uchar_t *density_data,
    size_t buflen)
{
	int rval;
	char cdb[CDB_GROUP1];
	struct uscsi_cmd *com;
	struct scsi_arq_status status;

	ST_FUNC(ST_DEVINFO, st_report_density_support);

	com = kmem_zalloc(sizeof (*com), KM_SLEEP);

	bzero(cdb, CDB_GROUP1);
	cdb[0] = SCMD_REPORT_DENSITIES;
	cdb[7] = (buflen & 0xff00) >> 8;
	cdb[8] = buflen & 0xff;

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP1;
	com->uscsi_bufaddr = (caddr_t)density_data;
	com->uscsi_buflen = buflen;
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;
	com->uscsi_flags = USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;

	rval = st_uscsi_cmd(un, com, FKIOCTL);
	if (com->uscsi_status || com->uscsi_resid) {
		rval = -1;
	}

	kmem_free(com, sizeof (*com));
	return (rval);
}

static int
st_report_supported_operation(struct scsi_tape *un, uchar_t *oper_data,
    uchar_t option_code, ushort_t service_action)
{
	int rval;
	char cdb[CDB_GROUP5];
	struct uscsi_cmd *com;
	struct scsi_arq_status status;
	uint32_t allo_length;

	ST_FUNC(ST_DEVINFO, st_report_supported_operation);

	allo_length = sizeof (struct one_com_des) +
	    sizeof (struct com_timeout_des);
	com = kmem_zalloc(sizeof (*com), KM_SLEEP);

	bzero(cdb, CDB_GROUP5);
	cdb[0] = (char)SCMD_MAINTENANCE_IN;
	cdb[1] = SSVC_ACTION_GET_SUPPORTED_OPERATIONS;
	if (service_action) {
		cdb[2] = (char)(ONE_COMMAND_DATA_FORMAT | 0x80); /* RCTD */
		cdb[4] = (service_action & 0xff00) >> 8;
		cdb[5] = service_action & 0xff;
	} else {
		cdb[2] = (char)(ONE_COMMAND_NO_SERVICE_DATA_FORMAT |
		    0x80); /* RCTD */
	}
	cdb[3] = option_code;
	cdb[6] = (allo_length & 0xff000000) >> 24;
	cdb[7] = (allo_length & 0xff0000) >> 16;
	cdb[8] = (allo_length & 0xff00) >> 8;
	cdb[9] = allo_length & 0xff;

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP5;
	com->uscsi_bufaddr = (caddr_t)oper_data;
	com->uscsi_buflen = allo_length;
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;
	com->uscsi_flags = USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;

	rval = st_uscsi_cmd(un, com, FKIOCTL);
	if (com->uscsi_status) {
		rval = -1;
	}

	kmem_free(com, sizeof (*com));
	return (rval);
}

/*
 * Changes devices blocksize and bsize to requested blocksize nblksz.
 * Returns returned value from first failed call or zero on success.
 */
static int
st_change_block_size(struct scsi_tape *un, uint32_t nblksz)
{
	struct seq_mode *current;
	int rval;
	uint32_t oldblksz;

	ST_FUNC(ST_DEVINFO, st_change_block_size);

	current = kmem_zalloc(MSIZE, KM_SLEEP);

	/*
	 * If we haven't got the compression page yet, do that first.
	 */
	if (un->un_comp_page == (ST_DEV_DATACOMP_PAGE | ST_DEV_CONFIG_PAGE)) {
		(void) st_modesense(un);
	}

	/* Read current settings */
	rval = st_gen_mode_sense(un, st_uscsi_cmd, 0, current, MSIZE);
	if (rval != 0) {
		scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "mode sense for change block size failed: rval = %d", rval);
		goto finish;
	}

	/* Figure the current block size */
	oldblksz =
	    (current->high_bl << 16) |
	    (current->mid_bl << 8) |
	    (current->low_bl);

	/* If current block size is the same as requested were done */
	if (oldblksz == nblksz) {
		un->un_bsize = nblksz;
		rval = 0;
		goto finish;
	}

	/* Change to requested block size */
	current->high_bl = (uchar_t)(nblksz >> 16);
	current->mid_bl  = (uchar_t)(nblksz >> 8);
	current->low_bl  = (uchar_t)(nblksz);

	/* Attempt to change block size */
	rval = st_gen_mode_select(un, st_uscsi_cmd, current, MSIZE);
	if (rval != 0) {
		scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Set new block size failed: rval = %d", rval);
		goto finish;
	}

	/* Read back and verify setting */
	rval = st_modesense(un);
	if (rval == 0) {
		un->un_bsize =
		    (un->un_mspl->high_bl << 16) |
		    (un->un_mspl->mid_bl << 8) |
		    (un->un_mspl->low_bl);

		if (un->un_bsize != nblksz) {
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Blocksize set does not equal requested blocksize"
			    "(read: %u requested: %u)\n", nblksz, un->un_bsize);
			rval = EIO;
		}
	}
finish:
	kmem_free(current, MSIZE);
	return (rval);
}


static void
st_init(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_init);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_init(): dev = 0x%lx, will reset fileno, blkno, eof\n",
	    un->un_dev);

	un->un_pos.blkno = 0;
	un->un_pos.fileno = 0;
	un->un_lastop = ST_OP_NIL;
	un->un_pos.eof = ST_NO_EOF;
	un->un_pwr_mgmt = ST_PWR_NORMAL;
	if (st_error_level != SCSI_ERR_ALL) {
		if (DEBUGGING) {
			st_error_level = SCSI_ERR_ALL;
		} else {
			st_error_level = SCSI_ERR_RETRYABLE;
		}
	}
}


static void
st_make_cmd(struct scsi_tape *un, struct buf *bp, int (*func)(caddr_t))
{
	struct scsi_pkt *pkt;
	struct uscsi_cmd *ucmd;
	recov_info *ri;
	int tval = 0;
	int64_t count;
	uint32_t additional = 0;
	uint32_t address = 0;
	union scsi_cdb *ucdb;
	int flags = 0;
	int cdb_len = CDB_GROUP0; /* default */
	uchar_t com;
	char fixbit;
	char short_fm = 0;
	optype prev_op = un->un_lastop;
	int stat_size =
	    (un->un_arq_enabled ? sizeof (struct scsi_arq_status) : 1);

	ST_FUNC(ST_DEVINFO, st_make_cmd);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_make_cmd(): dev = 0x%lx\n", un->un_dev);


	/*
	 * fixbit is for setting the Fixed Mode and Suppress Incorrect
	 * Length Indicator bits on read/write commands, for setting
	 * the Long bit on erase commands, and for setting the Code
	 * Field bits on space commands.
	 */

	/* regular raw I/O */
	if ((bp != un->un_sbufp) && (bp != un->un_recov_buf)) {
		pkt = scsi_init_pkt(ROUTE, NULL, bp,
		    CDB_GROUP0, stat_size, st_recov_sz, 0, func,
		    (caddr_t)un);
		if (pkt == NULL) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "Read Write scsi_init_pkt() failure\n");
			goto exit;
		}
		ASSERT(pkt->pkt_resid == 0);
#ifdef STDEBUG
		bzero(pkt->pkt_private, st_recov_sz);
		bzero(pkt->pkt_scbp, stat_size);
#endif
		ri = (recov_info *)pkt->pkt_private;
		ri->privatelen = st_recov_sz;
		if (un->un_bsize == 0) {
			count = bp->b_bcount;
			fixbit = 0;
		} else {
			count = bp->b_bcount / un->un_bsize;
			fixbit = 1;
		}
		if (bp->b_flags & B_READ) {
			com = SCMD_READ;
			un->un_lastop = ST_OP_READ;
			if ((un->un_bsize == 0) && /* Not Fixed Block */
			    (un->un_dp->options & ST_READ_IGNORE_ILI)) {
				fixbit = 2;
			}
		} else {
			com = SCMD_WRITE;
			un->un_lastop = ST_OP_WRITE;
		}
		tval = un->un_dp->io_timeout;

		/*
		 * For really large xfers, increase timeout
		 */
		if (bp->b_bcount > (10 * ONE_MEG))
			tval *= bp->b_bcount/(10 * ONE_MEG);

		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "%s %d amt 0x%lx\n", (com == SCMD_WRITE) ?
		    wr_str: rd_str, un->un_pos.blkno, bp->b_bcount);

	} else if ((ucmd = BP_UCMD(bp)) != NULL) {
		/*
		 * uscsi - build command, allocate scsi resources
		 */
		st_make_uscsi_cmd(un, ucmd, bp, func);
		goto exit;

	} else {				/* special I/O */
		struct buf *allocbp = NULL;
		com = (uchar_t)(uintptr_t)bp->b_forw;
		count = bp->b_bcount;

		switch (com) {
		case SCMD_READ:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "special read %"PRId64"\n", count);
			if (un->un_bsize == 0) {
				fixbit = 2;	/* suppress SILI */
			} else {
				fixbit = 1;	/* Fixed Block Mode */
				count /= un->un_bsize;
			}
			allocbp = bp;
			un->un_lastop = ST_OP_READ;
			tval = un->un_dp->io_timeout;
			break;

		case SCMD_WRITE:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "special write %"PRId64"\n", count);
			if (un->un_bsize != 0) {
				fixbit = 1;	/* Fixed Block Mode */
				count /= un->un_bsize;
			} else {
				fixbit = 0;
			}
			allocbp = bp;
			un->un_lastop = ST_OP_WRITE;
			tval = un->un_dp->io_timeout;
			break;

		case SCMD_WRITE_FILE_MARK:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "write %"PRId64" file marks\n", count);
			un->un_lastop = ST_OP_WEOF;
			fixbit = 0;
			tval = un->un_dp->io_timeout;
			/*
			 * If ST_SHORT_FILEMARKS bit is ON for EXABYTE
			 * device, set the Vendor Unique bit to
			 * write Short File Mark.
			 */
			if ((un->un_dp->options & ST_SHORT_FILEMARKS) &&
			    ((un->un_dp->type == ST_TYPE_EXB8500) ||
			    (un->un_dp->type == ST_TYPE_EXABYTE))) {
				/*
				 * Now the Vendor Unique bit 7 in Byte 5 of CDB
				 * is set to to write Short File Mark
				 */
				short_fm = 1;
			}
			break;

		case SCMD_REWIND:
			/*
			 * In the case of rewind we're gona do the rewind with
			 * the immediate bit set so status will be retured when
			 * the command is accepted by the device. We clear the
			 * B_ASYNC flag so we wait for that acceptance.
			 */
			fixbit = 0;
			if (bp->b_flags & B_ASYNC) {
				allocbp = bp;
				if (count) {
					fixbit = 1;
					bp->b_flags &= ~B_ASYNC;
				}
			}
			count = 0;
			bp->b_bcount = 0;
			un->un_lastop = ST_OP_CTL;
			tval = un->un_dp->rewind_timeout;
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "rewind\n");
			break;

		case SCMD_SPACE_G4:
			cdb_len = CDB_GROUP4;
			fixbit = SPACE_TYPE(bp->b_bcount);
			count = SPACE_CNT(bp->b_bcount);
			ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
			    " %s space %s %"PRId64" from file %d blk %d\n",
			    bp->b_bcount & SP_BACKSP ? "backward" : "forward",
			    space_strs[fixbit & 7], count,
			    un->un_pos.fileno, un->un_pos.blkno);
			address = (count >> 48) & 0x1fff;
			additional = (count >> 16) & 0xffffffff;
			count &= 0xffff;
			count <<= 16;
			un->un_lastop = ST_OP_CTL;
			tval = un->un_dp->space_timeout;
			break;

		case SCMD_SPACE:
			fixbit = SPACE_TYPE(bp->b_bcount);
			count = SPACE_CNT(bp->b_bcount);
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    " %s space %s %"PRId64" from file %d blk %d\n",
			    bp->b_bcount & SP_BACKSP ? "backward" : "forward",
			    space_strs[fixbit & 7], count,
			    un->un_pos.fileno, un->un_pos.blkno);
			count &= 0xffffffff;
			un->un_lastop = ST_OP_CTL;
			tval = un->un_dp->space_timeout;
			break;

		case SCMD_LOAD:
			ASSERT(count < 10);
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "%s tape\n", load_strs[count]);
			fixbit = 0;

			/* Loading or Unloading */
			if (count & LD_LOAD) {
				tval = un->un_dp->load_timeout;
			} else {
				tval = un->un_dp->unload_timeout;
			}
			/* Is Retension requested */
			if (count & LD_RETEN) {
				tval += un->un_dp->rewind_timeout;
			}
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_ERASE:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "erase tape\n");
			ASSERT(count == 1); /* mt sets this */
			if (count == 1) {
				/*
				 * do long erase
				 */
				fixbit = 1; /* Long */

				/* Drive might not honor immidiate bit */
				tval = un->un_dp->erase_timeout;
			} else {
				/* Short Erase */
				tval = un->un_dp->erase_timeout;
				fixbit = 0;
			}
			un->un_lastop = ST_OP_CTL;
			count = 0;
			break;

		case SCMD_MODE_SENSE:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "mode sense\n");
			allocbp = bp;
			fixbit = 0;
			tval = un->un_dp->non_motion_timeout;
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_MODE_SELECT:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "mode select\n");
			allocbp = bp;
			fixbit = 0;
			tval = un->un_dp->non_motion_timeout;
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_RESERVE:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "reserve\n");
			fixbit = 0;
			tval = un->un_dp->non_motion_timeout;
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_RELEASE:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "release\n");
			fixbit = 0;
			tval = un->un_dp->non_motion_timeout;
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_READ_BLKLIM:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "read block limits\n");
			allocbp = bp;
			fixbit = count = 0;
			tval = un->un_dp->non_motion_timeout;
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_TEST_UNIT_READY:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "test unit ready\n");
			fixbit = 0;
			tval = un->un_dp->non_motion_timeout;
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_DOORLOCK:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "prevent/allow media removal\n");
			fixbit = 0;
			tval = un->un_dp->non_motion_timeout;
			un->un_lastop = ST_OP_CTL;
			break;

		case SCMD_READ_POSITION:
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "read position\n");
			fixbit = un->un_read_pos_type;
			cdb_len = CDB_GROUP1;
			tval = un->un_dp->non_motion_timeout;
			allocbp = bp;
			un->un_lastop = ST_OP_CTL;
			switch (un->un_read_pos_type) {
			case LONG_POS:
				count = 0;
				break;
			case EXT_POS:
				count = sizeof (tape_position_ext_t);
				break;
			case SHORT_POS:
				count = 0;
				break;
			default:
				ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
				    "Unknown read position type 0x%x in "
				    " st_make_cmd()\n", un->un_read_pos_type);
			}
			break;

		default:
			ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
			    "Unhandled scsi command 0x%x in st_make_cmd()\n",
			    com);
		}

		pkt = scsi_init_pkt(ROUTE, NULL, allocbp, cdb_len, stat_size,
		    st_recov_sz, 0, func, (caddr_t)un);
		if (pkt == NULL) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "generic command scsi_init_pkt() failure\n");
			goto exit;
		}

		ASSERT(pkt->pkt_resid == 0);
#ifdef STDEBUG
		bzero(pkt->pkt_private, st_recov_sz);
		bzero(pkt->pkt_scbp, stat_size);
#endif
		ri = (recov_info *)pkt->pkt_private;
		ri->privatelen = st_recov_sz;
		if (allocbp) {
			ASSERT(geterror(allocbp) == 0);
		}

	}

	ucdb = (union scsi_cdb *)pkt->pkt_cdbp;

	(void) scsi_setup_cdb(ucdb, com, address, (uint_t)count, additional);
	FILL_SCSI1_LUN(un->un_sd, pkt);
	/*
	 * Initialize the SILI/Fixed bits of the byte 1 of cdb.
	 */
	ucdb->t_code = fixbit;
	ucdb->g0_vu_1 = short_fm;
	pkt->pkt_flags = flags;

	ASSERT(tval);
	pkt->pkt_time = tval;
	if (bp == un->un_recov_buf) {
		pkt->pkt_comp = st_recov_cb;
	} else {
		pkt->pkt_comp = st_intr;
	}

	st_add_recovery_info_to_pkt(un, bp, pkt);

	/*
	 * If we just write data to tape and did a command that doesn't
	 * change position, we still need to write a filemark.
	 */
	if ((prev_op == ST_OP_WRITE) || (prev_op == ST_OP_WEOF)) {
		recov_info *rcvi = pkt->pkt_private;
		cmd_attribute const *atrib;

		if (rcvi->privatelen == sizeof (recov_info)) {
			atrib = rcvi->cmd_attrib;
		} else {
			atrib = st_lookup_cmd_attribute(com);
		}
		if (atrib->chg_tape_direction == DIR_NONE) {
			un->un_lastop = prev_op;
		}
	}

exit:
	ASSERT(mutex_owned(ST_MUTEX));
}


/*
 * Build a command based on a uscsi command;
 */
static void
st_make_uscsi_cmd(struct scsi_tape *un, struct uscsi_cmd *ucmd,
    struct buf *bp, int (*func)(caddr_t))
{
	struct scsi_pkt *pkt;
	recov_info *ri;
	caddr_t cdb;
	int	cdblen;
	int	stat_size = 1;
	int	flags = 0;

	ST_FUNC(ST_DEVINFO, st_make_uscsi_cmd);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_make_uscsi_cmd(): dev = 0x%lx\n", un->un_dev);

	if (ucmd->uscsi_flags & USCSI_RQENABLE) {
		if (un->un_arq_enabled) {
			if (ucmd->uscsi_rqlen > SENSE_LENGTH) {
				stat_size = (int)(ucmd->uscsi_rqlen) +
				    sizeof (struct scsi_arq_status) -
				    sizeof (struct scsi_extended_sense);
				flags = PKT_XARQ;
			} else {
				stat_size = sizeof (struct scsi_arq_status);
			}
		}
	}

	ASSERT(mutex_owned(ST_MUTEX));

	cdb = ucmd->uscsi_cdb;
	cdblen = ucmd->uscsi_cdblen;

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_make_uscsi_cmd: buflen=%ld bcount=%ld\n",
	    ucmd->uscsi_buflen, bp->b_bcount);
	pkt = scsi_init_pkt(ROUTE, NULL,
	    (bp->b_bcount > 0) ? bp : NULL,
	    cdblen, stat_size, st_recov_sz, flags, func, (caddr_t)un);
	if (pkt == NULL) {
		scsi_log(ST_DEVINFO, st_label, CE_NOTE,
		    "uscsi command scsi_init_pkt() failure\n");
		goto exit;
	}

	ASSERT(pkt->pkt_resid == 0);
#ifdef STDEBUG
	bzero(pkt->pkt_private, st_recov_sz);
	bzero(pkt->pkt_scbp, stat_size);
#endif
	ri = (recov_info *)pkt->pkt_private;
	ri->privatelen = st_recov_sz;

	bcopy(cdb, pkt->pkt_cdbp, (uint_t)cdblen);

#ifdef STDEBUG
	if ((st_debug & 0x7) >= 6) {
		st_clean_print(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "pkt_cdbp", (char *)cdb, cdblen);
	}
#endif

	if (ucmd->uscsi_flags & USCSI_SILENT) {
		pkt->pkt_flags |= FLAG_SILENT;
	}

	(void) scsi_uscsi_pktinit(ucmd, pkt);

	pkt->pkt_time = ucmd->uscsi_timeout;
	if (bp == un->un_recov_buf) {
		pkt->pkt_comp = st_recov_cb;
	} else {
		pkt->pkt_comp = st_intr;
	}

	st_add_recovery_info_to_pkt(un, bp, pkt);
exit:
	ASSERT(mutex_owned(ST_MUTEX));
}


/*
 * restart cmd currently at the head of the runq
 *
 * If scsi_transport() succeeds or the retries
 * count exhausted, restore the throttle that was
 * zeroed out in st_handle_intr_busy().
 *
 */
static void
st_intr_restart(void *arg)
{
	struct scsi_tape *un = arg;
	struct buf *bp;
	int queued;
	int status = TRAN_ACCEPT;

	mutex_enter(ST_MUTEX);

	ST_FUNC(ST_DEVINFO, st_intr_restart);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_intr_restart(), un = 0x%p\n", (void *)un);

	un->un_hib_tid = 0;

	if (un->un_recov_buf_busy != 0) {
		bp = un->un_recov_buf;
		queued = 0;
	} else if (un->un_sbuf_busy != 0) {
		bp = un->un_sbufp;
		queued = 0;
	} else if (un->un_quef != NULL) {
		bp = un->un_quef;
		queued = 1;
	} else {
		mutex_exit(ST_MUTEX);
		return;
	}

	/*
	 * Here we know :
	 *	throttle = 0, via st_handle_intr_busy
	 */

	if (queued) {
		/*
		 * move from waitq to runq, if there is anything on the waitq
		 */
		(void) st_remove_from_queue(&un->un_quef, &un->un_quef, bp);

		if (un->un_runqf) {
			/*
			 * not good, we don't want to requeue something after
			 * another.
			 */
			goto done_error;
		} else {
			un->un_runqf = bp;
			un->un_runql = bp;
		}
	}

	ST_CDB(ST_DEVINFO, "Interrupt restart CDB",
	    (char *)BP_PKT(bp)->pkt_cdbp);

	ST_DO_KSTATS(bp, kstat_waitq_to_runq);

	status = st_transport(un, BP_PKT(bp));

	if (status != TRAN_ACCEPT) {
		ST_DO_KSTATS(bp, kstat_runq_back_to_waitq);

		if (status == TRAN_BUSY) {
			pkt_info *pkti = BP_PKT(bp)->pkt_private;

			if (pkti->privatelen == sizeof (recov_info) &&
			    un->un_unit_attention_flags &&
			    bp != un->un_recov_buf) {
			un->un_unit_attention_flags = 0;
				ST_RECOV(ST_DEVINFO, st_label, CE_WARN,
				    "Command Recovery called on busy resend\n");
				if (st_command_recovery(un, BP_PKT(bp),
				    ATTEMPT_RETRY) == JUST_RETURN) {
					mutex_exit(ST_MUTEX);
					return;
				}
			}
			mutex_exit(ST_MUTEX);
			if (st_handle_intr_busy(un, bp,
			    ST_TRAN_BUSY_TIMEOUT) == 0)
				return;	/* timeout is setup again */
			mutex_enter(ST_MUTEX);
		}

done_error:
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "restart transport rejected\n");
		bp->b_resid = bp->b_bcount;

		if (un->un_last_throttle) {
			un->un_throttle = un->un_last_throttle;
		}
		if (status != TRAN_ACCEPT) {
			ST_DO_ERRSTATS(un, st_transerrs);
		}
		ST_DO_KSTATS(bp, kstat_waitq_exit);
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "busy restart aborted\n");
		st_set_pe_flag(un);
		st_bioerror(bp, EIO);
		st_done_and_mutex_exit(un, bp);
	} else {
		if (un->un_last_throttle) {
			un->un_throttle = un->un_last_throttle;
		}
		mutex_exit(ST_MUTEX);
	}
}

/*
 * st_check_media():
 * Periodically check the media state using scsi_watch service;
 * this service calls back after TUR and possibly request sense
 * the callback handler (st_media_watch_cb()) decodes the request sense
 * data (if any)
 */

static int
st_check_media(dev_t dev, enum mtio_state state)
{
	int rval = 0;
	enum mtio_state	prev_state;
	opaque_t token = NULL;

	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_check_media);

	mutex_enter(ST_MUTEX);

	ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_check_media:state=%x, mediastate=%x\n",
	    state, un->un_mediastate);

	prev_state = un->un_mediastate;

	/*
	 * is there anything to do?
	 */
retry:
	if (state == un->un_mediastate || un->un_mediastate == MTIO_NONE) {
		/*
		 * submit the request to the scsi_watch service;
		 * scsi_media_watch_cb() does the real work
		 */
		mutex_exit(ST_MUTEX);
		token = scsi_watch_request_submit(ST_SCSI_DEVP,
		    st_check_media_time, SENSE_LENGTH,
		    st_media_watch_cb, (caddr_t)dev);
		if (token == NULL) {
			rval = EAGAIN;
			goto done;
		}
		mutex_enter(ST_MUTEX);

		un->un_swr_token = token;
		un->un_specified_mediastate = state;

		/*
		 * now wait for media change
		 * we will not be signalled unless mediastate == state but it
		 * still better to test for this condition, since there
		 * is a 5 sec cv_broadcast delay when
		 *  mediastate == MTIO_INSERTED
		 */
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_check_media:waiting for media state change\n");
		while (un->un_mediastate == state) {
			if (cv_wait_sig(&un->un_state_cv, ST_MUTEX) == 0) {
				mutex_exit(ST_MUTEX);
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_check_media:waiting for media state "
				    "was interrupted\n");
				rval = EINTR;
				goto done;
			}
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_check_media:received signal, state=%x\n",
			    un->un_mediastate);
		}
	}

	/*
	 * if we transitioned to MTIO_INSERTED, media has really been
	 * inserted.  If TUR fails, it is probably a exabyte slow spin up.
	 * Reset and retry the state change.  If everything is ok, replay
	 * the open() logic.
	 */
	if ((un->un_mediastate == MTIO_INSERTED) &&
	    (un->un_state == ST_STATE_OFFLINE)) {
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_check_media: calling st_cmd to confirm inserted\n");

		/*
		 * set this early so that TUR will make it through strategy
		 * without triggering a st_tape_init().  We needed it set
		 * before calling st_tape_init() ourselves anyway.  If TUR
		 * fails, set it back
		 */
		un->un_state = ST_STATE_INITIALIZING;

		/*
		 * If not reserved fail as getting reservation conflict
		 * will make this hang forever.
		 */
		if ((un->un_rsvd_status &
		    (ST_RESERVE | ST_APPLICATION_RESERVATIONS)) == 0) {
			mutex_exit(ST_MUTEX);
			rval = EACCES;
			goto done;
		}
		rval = st_cmd(un, SCMD_TEST_UNIT_READY, 0, SYNC_CMD);
		if (rval == EACCES) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_check_media: TUR got Reservation Conflict\n");
			mutex_exit(ST_MUTEX);
			goto done;
		}
		if (rval) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_check_media: TUR failed, going to retry\n");
			un->un_mediastate = prev_state;
			un->un_state = ST_STATE_OFFLINE;
			goto retry;
		}
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_check_media: media inserted\n");

		/* this also rewinds the tape */
		rval = st_tape_init(un);
		if (rval != 0) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_check_media : OFFLINE init failure ");
			un->un_state = ST_STATE_OFFLINE;
			un->un_pos.pmode = invalid;
		} else {
			un->un_state = ST_STATE_OPEN_PENDING_IO;
		}
	} else if ((un->un_mediastate == MTIO_EJECTED) &&
	    (un->un_state != ST_STATE_OFFLINE)) {
		/*
		 * supported devices must be rewound before ejection
		 * rewind resets fileno & blkno
		 */
		un->un_laststate = un->un_state;
		un->un_state = ST_STATE_OFFLINE;
	}
	mutex_exit(ST_MUTEX);
done:
	if (token) {
		(void) scsi_watch_request_terminate(token,
		    SCSI_WATCH_TERMINATE_WAIT);
		mutex_enter(ST_MUTEX);
		un->un_swr_token = (opaque_t)NULL;
		mutex_exit(ST_MUTEX);
	}

	ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG, "st_check_media: done\n");

	return (rval);
}

/*
 * st_media_watch_cb() is called by scsi_watch_thread for
 * verifying the request sense data (if any)
 */
static int
st_media_watch_cb(caddr_t arg, struct scsi_watch_result *resultp)
{
	struct scsi_status *statusp = resultp->statusp;
	struct scsi_extended_sense *sensep = resultp->sensep;
	uchar_t actual_sense_length = resultp->actual_sense_length;
	struct scsi_tape *un;
	enum mtio_state state = MTIO_NONE;
	int instance;
	dev_t dev = (dev_t)arg;

	instance = MTUNIT(dev);
	if ((un = ddi_get_soft_state(st_state, instance)) == NULL) {
		return (-1);
	}

	mutex_enter(ST_MUTEX);
	ST_FUNC(ST_DEVINFO, st_media_watch_cb);
	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_media_watch_cb: status=%x, sensep=%p, len=%x\n",
	    *((char *)statusp), (void *)sensep,
	    actual_sense_length);


	/*
	 * if there was a check condition then sensep points to valid
	 * sense data
	 * if status was not a check condition but a reservation or busy
	 * status then the new state is MTIO_NONE
	 */
	if (sensep) {
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_media_watch_cb: KEY=%x, ASC=%x, ASCQ=%x\n",
		    sensep->es_key, sensep->es_add_code, sensep->es_qual_code);

		switch (un->un_dp->type) {
		default:
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_media_watch_cb: unknown drive type %d, "
			    "default to ST_TYPE_HP\n", un->un_dp->type);
		/* FALLTHROUGH */

		case ST_TYPE_STC3490:	/* STK 4220 1/2" cartridge */
		case ST_TYPE_FUJI:	/* 1/2" cartridge */
		case ST_TYPE_HP:	/* HP 88780 1/2" reel */
			if (un->un_dp->type == ST_TYPE_FUJI) {
				ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_media_watch_cb: ST_TYPE_FUJI\n");
			} else {
				ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_media_watch_cb: ST_TYPE_HP\n");
			}
			switch (sensep->es_key) {
			case KEY_UNIT_ATTENTION:
				/* not ready to ready transition */
				/* hp/es_qual_code == 80 on>off>on */
				/* hp/es_qual_code == 0 on>off>unld>ld>on */
				if (sensep->es_add_code == 0x28) {
					state = MTIO_INSERTED;
				}
				break;
			case KEY_NOT_READY:
				/* in process, rewinding or loading */
				if ((sensep->es_add_code == 0x04) &&
				    (sensep->es_qual_code == 0x00)) {
					state = MTIO_EJECTED;
				}
				break;
			}
			break;

		case ST_TYPE_EXB8500:	/* Exabyte 8500 */
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_media_watch_cb: ST_TYPE_EXB8500\n");
			switch (sensep->es_key) {
			case KEY_UNIT_ATTENTION:
				/* operator medium removal request */
				if ((sensep->es_add_code == 0x5a) &&
				    (sensep->es_qual_code == 0x01)) {
					state = MTIO_EJECTED;
				/* not ready to ready transition */
				} else if ((sensep->es_add_code == 0x28) &&
				    (sensep->es_qual_code == 0x00)) {
					state = MTIO_INSERTED;
				}
				break;
			case KEY_NOT_READY:
				/* medium not present */
				if (sensep->es_add_code == 0x3a) {
					state = MTIO_EJECTED;
				}
				break;
			}
			break;
		case ST_TYPE_EXABYTE:	/* Exabyte 8200 */
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_media_watch_cb: ST_TYPE_EXABYTE\n");
			switch (sensep->es_key) {
			case KEY_NOT_READY:
				if ((sensep->es_add_code == 0x04) &&
				    (sensep->es_qual_code == 0x00)) {
					/* volume not mounted? */
					state = MTIO_EJECTED;
				} else if (sensep->es_add_code == 0x3a) {
					state = MTIO_EJECTED;
				}
				break;
			case KEY_UNIT_ATTENTION:
				state = MTIO_EJECTED;
				break;
			}
			break;

		case ST_TYPE_DLT:		/* quantum DLT4xxx */
			switch (sensep->es_key) {
			case KEY_UNIT_ATTENTION:
				if (sensep->es_add_code == 0x28) {
					state = MTIO_INSERTED;
				}
				break;
			case KEY_NOT_READY:
				if (sensep->es_add_code == 0x04) {
					/* in transition but could be either */
					state = un->un_specified_mediastate;
				} else if ((sensep->es_add_code == 0x3a) &&
				    (sensep->es_qual_code == 0x00)) {
					state = MTIO_EJECTED;
				}
				break;
			}
			break;
		}
	} else if (*((char *)statusp) == STATUS_GOOD) {
		state = MTIO_INSERTED;
	}

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_media_watch_cb:state=%x, specified=%x\n",
	    state, un->un_specified_mediastate);

	/*
	 * now signal the waiting thread if this is *not* the specified state;
	 * delay the signal if the state is MTIO_INSERTED
	 * to allow the target to recover
	 */
	if (state != un->un_specified_mediastate) {
		un->un_mediastate = state;
		if (state == MTIO_INSERTED) {
			/*
			 * delay the signal to give the drive a chance
			 * to do what it apparently needs to do
			 */
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_media_watch_cb:delayed cv_broadcast\n");
			un->un_delay_tid = timeout(st_delayed_cv_broadcast,
			    un, drv_usectohz((clock_t)MEDIA_ACCESS_DELAY));
		} else {
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_media_watch_cb:immediate cv_broadcast\n");
			cv_broadcast(&un->un_state_cv);
		}
	}
	mutex_exit(ST_MUTEX);
	return (0);
}

/*
 * delayed cv_broadcast to allow for target to recover
 * from media insertion
 */
static void
st_delayed_cv_broadcast(void *arg)
{
	struct scsi_tape *un = arg;

	ST_FUNC(ST_DEVINFO, st_delayed_cv_broadcast);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_delayed_cv_broadcast:delayed cv_broadcast\n");

	mutex_enter(ST_MUTEX);
	cv_broadcast(&un->un_state_cv);
	mutex_exit(ST_MUTEX);
}

/*
 * restart cmd currently at the start of the waitq
 */
static void
st_start_restart(void *arg)
{
	struct scsi_tape *un = arg;

	ST_FUNC(ST_DEVINFO, st_start_restart);

	ASSERT(un != NULL);

	mutex_enter(ST_MUTEX);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_tran_restart()\n");

	st_start(un);

	mutex_exit(ST_MUTEX);
}


/*
 * Command completion processing
 *
 */
static void
st_intr(struct scsi_pkt *pkt)
{
	recov_info *rcv = pkt->pkt_private;
	struct buf *bp = rcv->cmd_bp;
	struct scsi_tape *un;
	errstate action = COMMAND_DONE;
	clock_t	timout;
	int	status;

	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));

	ST_FUNC(ST_DEVINFO, st_intr);

	ASSERT(un != NULL);

	mutex_enter(ST_MUTEX);

	ASSERT(bp != un->un_recov_buf);

	un->un_rqs_state &= ~(ST_RQS_ERROR);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_intr()\n");

	if (pkt->pkt_reason != CMD_CMPLT) {
		ST_DEBUG(ST_DEVINFO, st_label, CE_WARN,
		    "Unhappy packet status reason = %s statistics = 0x%x\n",
		    scsi_rname(pkt->pkt_reason), pkt->pkt_statistics);

		/* If device has gone away not much else to do */
		if (pkt->pkt_reason == CMD_DEV_GONE) {
			action = COMMAND_DONE_ERROR;
		} else if ((pkt == un->un_rqs) ||
		    (un->un_state == ST_STATE_SENSING)) {
			ASSERT(pkt == un->un_rqs);
			ASSERT(un->un_state == ST_STATE_SENSING);
			un->un_state = un->un_laststate;
			rcv->cmd_bp = un->un_rqs_bp;
			ST_DO_ERRSTATS(un, st_transerrs);
			action = COMMAND_DONE_ERROR;
		} else {
			action = st_handle_incomplete(un, bp);
		}
	/*
	 * At this point we know that the command was successfully
	 * completed. Now what?
	 */
	} else if ((pkt == un->un_rqs) || (un->un_state == ST_STATE_SENSING)) {
		/*
		 * okay. We were running a REQUEST SENSE. Find
		 * out what to do next.
		 */
		ASSERT(pkt == un->un_rqs);
		ASSERT(un->un_state == ST_STATE_SENSING);
		scsi_sync_pkt(pkt);
		action = st_handle_sense(un, bp, &un->un_pos);
		/*
		 * Make rqs isn't going to be retied.
		 */
		if (action != QUE_BUSY_COMMAND && action != QUE_COMMAND) {
			/*
			 * set pkt back to original packet in case we will have
			 * to requeue it
			 */
			pkt = BP_PKT(bp);
			rcv->cmd_bp = un->un_rqs_bp;
			/*
			 * some actions are based on un_state, hence
			 * restore the state st was in before ST_STATE_SENSING.
			 */
			un->un_state = un->un_laststate;
		}

	} else if (un->un_arq_enabled && (pkt->pkt_state & STATE_ARQ_DONE)) {
		/*
		 * the transport layer successfully completed an autorqsense
		 */
		action = st_handle_autosense(un, bp, &un->un_pos);

	} else  if ((SCBP(pkt)->sts_busy) ||
	    (SCBP(pkt)->sts_chk) ||
	    (SCBP(pkt)->sts_vu7)) {
		/*
		 * Okay, we weren't running a REQUEST SENSE. Call a routine
		 * to see if the status bits we're okay. If a request sense
		 * is to be run, that will happen.
		 */
		action = st_check_error(un, pkt);
	}

	if (un->un_pwr_mgmt == ST_PWR_SUSPENDED) {
		switch (action) {
			case QUE_COMMAND:
				/*
				 * return cmd to head to the queue
				 * since we are suspending so that
				 * it gets restarted during resume
				 */
				st_add_to_queue(&un->un_runqf, &un->un_runql,
				    un->un_runqf, bp);

				action = JUST_RETURN;
				break;

			case QUE_SENSE:
				action = COMMAND_DONE_ERROR;
				break;

			default:
				break;
		}
	}

	/*
	 * check for undetected path failover.
	 */
	if (un->un_multipath) {

		struct uscsi_cmd *ucmd = BP_UCMD(bp);
		int pkt_valid = 0;

		if (ucmd) {
			/*
			 * Also copies path instance to the uscsi structure.
			 */
			pkt_valid = scsi_uscsi_pktfini(pkt, ucmd);

			/*
			 * scsi_uscsi_pktfini() zeros pkt_path_instance.
			 */
			pkt->pkt_path_instance = ucmd->uscsi_path_instance;
		} else {
			pkt_valid = scsi_pkt_allocated_correctly(pkt);
		}

		/*
		 * If the scsi_pkt was not allocated correctly the
		 * pkt_path_instance is not even there.
		 */
		if ((pkt_valid != 0) &&
		    (un->un_last_path_instance != pkt->pkt_path_instance)) {
			/*
			 * Don't recover the path change if it was done
			 * intentionally or if the device has not completely
			 * opened yet.
			 */
			if (((pkt->pkt_flags & FLAG_PKT_PATH_INSTANCE) == 0) &&
			    (un->un_state > ST_STATE_OPENING)) {
				ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
				    "Failover detected, action is %s\n",
				    errstatenames[action]);
				if (action == COMMAND_DONE) {
					action = PATH_FAILED;
				}
			}
			un->un_last_path_instance = pkt->pkt_path_instance;
		}
	}

	/*
	 * Restore old state if we were sensing.
	 */
	if (un->un_state == ST_STATE_SENSING && action != QUE_SENSE) {
		un->un_state = un->un_laststate;
	}

	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_intr: pkt=%p, bp=%p, action=%s, status=%x\n",
	    (void *)pkt, (void *)bp, errstatenames[action], SCBP_C(pkt));

again:
	switch (action) {
	case COMMAND_DONE_EACCES:
		/* this is to report a reservation conflict */
		st_bioerror(bp, EACCES);
		ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Reservation Conflict \n");
		un->un_pos.pmode = invalid;

		/*FALLTHROUGH*/
	case COMMAND_DONE_ERROR:
		if (un->un_pos.eof < ST_EOT_PENDING &&
		    un->un_state >= ST_STATE_OPEN) {
			/*
			 * all errors set state of the tape to 'unknown'
			 * unless we're at EOT or are doing append testing.
			 * If sense key was illegal request, preserve state.
			 */
			if (un->un_status != KEY_ILLEGAL_REQUEST) {
				un->un_pos.pmode = invalid;
			}
		}

		un->un_err_resid = bp->b_resid = bp->b_bcount;
		/*
		 * since we have an error (COMMAND_DONE_ERROR), we want to
		 * make sure an error ocurrs, so make sure at least EIO is
		 * returned
		 */
		if (geterror(bp) == 0)
			st_bioerror(bp, EIO);

		st_set_pe_flag(un);
		if (!(un->un_rqs_state & ST_RQS_ERROR) &&
		    (un->un_errno == EIO)) {
			un->un_rqs_state &= ~(ST_RQS_VALID);
		}
		break;

	case COMMAND_DONE_ERROR_RECOVERED:
		un->un_err_resid = bp->b_resid = bp->b_bcount;
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_intr(): COMMAND_DONE_ERROR_RECOVERED");
		if (geterror(bp) == 0) {
			st_bioerror(bp, EIO);
		}
		st_set_pe_flag(un);
		if (!(un->un_rqs_state & ST_RQS_ERROR) &&
		    (un->un_errno == EIO)) {
			un->un_rqs_state &= ~(ST_RQS_VALID);
		}
		/*FALLTHROUGH*/
	case COMMAND_DONE:
		st_set_state(un, bp);
		break;

	case QUE_SENSE:
		if ((un->un_ncmds > 1) && !un->un_flush_on_errors)
			goto sense_error;

		if (un->un_state != ST_STATE_SENSING) {
			un->un_laststate = un->un_state;
			un->un_state = ST_STATE_SENSING;
		}

		/*
		 * zero the sense data.
		 */
		bzero(un->un_rqs->pkt_scbp, SENSE_LENGTH);

		/*
		 * If this is not a retry on QUE_SENSE point to the original
		 * bp of the command that got us here.
		 */
		if (pkt != un->un_rqs) {
			((recov_info *)un->un_rqs->pkt_private)->cmd_bp = bp;
		}

		if (un->un_throttle) {
			un->un_last_throttle = un->un_throttle;
			un->un_throttle = 0;
		}

		ST_CDB(ST_DEVINFO, "Queue sense CDB",
		    (char *)BP_PKT(bp)->pkt_cdbp);

		/*
		 * never retry this, some other command will have nuked the
		 * sense, anyway
		 */
		status = st_transport(un, un->un_rqs);

		if (un->un_last_throttle) {
			un->un_throttle = un->un_last_throttle;
		}

		if (status == TRAN_ACCEPT) {
			mutex_exit(ST_MUTEX);
			return;
		}
		if (status != TRAN_BUSY)
			ST_DO_ERRSTATS(un, st_transerrs);
sense_error:
		un->un_pos.pmode = invalid;
		st_bioerror(bp, EIO);
		st_set_pe_flag(un);
		break;

	case QUE_BUSY_COMMAND:
		/* longish timeout */
		timout = ST_STATUS_BUSY_TIMEOUT;
		goto que_it_up;

	case QUE_COMMAND:
		/* short timeout */
		timout = ST_TRAN_BUSY_TIMEOUT;
que_it_up:
		/*
		 * let st_handle_intr_busy put this bp back on waitq and make
		 * checks to see if it is ok to requeue the command.
		 */
		ST_DO_KSTATS(bp, kstat_runq_back_to_waitq);

		/*
		 * Save the throttle before setting up the timeout
		 */
		if (un->un_throttle) {
			un->un_last_throttle = un->un_throttle;
		}
		mutex_exit(ST_MUTEX);
		if (st_handle_intr_busy(un, bp, timout) == 0)
			return;		/* timeout is setup again */

		mutex_enter(ST_MUTEX);
		un->un_pos.pmode = invalid;
		un->un_err_resid = bp->b_resid = bp->b_bcount;
		st_bioerror(bp, EIO);
		st_set_pe_flag(un);
		break;

	case QUE_LAST_COMMAND:

		if ((un->un_ncmds > 1) && !un->un_flush_on_errors) {
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "un_ncmds: %d can't retry cmd \n", un->un_ncmds);
			goto last_command_error;
		}
		mutex_exit(ST_MUTEX);
		if (st_handle_intr_retry_lcmd(un, bp) == 0)
			return;
		mutex_enter(ST_MUTEX);
last_command_error:
		un->un_err_resid = bp->b_resid = bp->b_bcount;
		un->un_pos.pmode = invalid;
		st_bioerror(bp, EIO);
		st_set_pe_flag(un);
		break;

	case COMMAND_TIMEOUT:
	case DEVICE_RESET:
	case DEVICE_TAMPER:
	case ATTEMPT_RETRY:
	case PATH_FAILED:
		ST_RECOV(ST_DEVINFO, st_label, CE_WARN,
		    "Command Recovery called on %s status\n",
		    errstatenames[action]);
		action = st_command_recovery(un, pkt, action);
		goto again;

	default:
		ASSERT(0);
		/* FALLTHRU */
	case JUST_RETURN:
		ST_DO_KSTATS(bp, kstat_runq_back_to_waitq);
		mutex_exit(ST_MUTEX);
		return;
	}

	ST_DO_KSTATS(bp, kstat_runq_exit);
	st_done_and_mutex_exit(un, bp);
}

static errstate
st_handle_incomplete(struct scsi_tape *un, struct buf *bp)
{
	static char *fail = "SCSI transport failed: reason '%s': %s\n";
	recov_info *rinfo;
	errstate rval = COMMAND_DONE_ERROR;
	struct scsi_pkt *pkt = (un->un_state == ST_STATE_SENSING) ?
	    un->un_rqs : BP_PKT(bp);
	int result;

	ST_FUNC(ST_DEVINFO, st_handle_incomplete);

	rinfo = (recov_info *)pkt->pkt_private;

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_handle_incomplete(): dev = 0x%lx\n", un->un_dev);

	ASSERT(mutex_owned(ST_MUTEX));

	/* prevent infinite number of retries */
	if (rinfo->pkt_retry_cnt++ > st_retry_count) {
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Recovery stopped for incomplete %s command, "
		    "retries exhausted",
		    st_print_scsi_cmd(pkt->pkt_cdbp[0]));
		return (COMMAND_DONE_ERROR);
	}

	switch (pkt->pkt_reason) {
	case CMD_INCOMPLETE:	/* tran stopped with not normal state */
		/*
		 * this occurs when accessing a powered down drive, no
		 * need to complain; just fail the open
		 */
		ST_CDB(ST_DEVINFO, "Incomplete CDB", (char *)pkt->pkt_cdbp);

		/*
		 * if we have commands outstanding in HBA, and a command
		 * comes back incomplete, we're hosed, so reset target
		 * If we have the bus, but cmd_incomplete, we probably just
		 * have a failed selection, so don't reset the target, just
		 * requeue the command and try again
		 */
		if ((un->un_ncmds > 1) || (pkt->pkt_state != STATE_GOT_BUS)) {
			goto reset_target;
		}

		/*
		 * Retry selection a couple more times if we're
		 * open.  If opening, we only try just once to
		 * reduce probe time for nonexistant devices.
		 */
		if ((un->un_laststate > ST_STATE_OPENING) &&
		    (rinfo->pkt_retry_cnt < st_selection_retry_count)) {
			/* XXX check retriable? */
			rval = QUE_COMMAND;
		}
		ST_DO_ERRSTATS(un, st_transerrs);
		break;

	case CMD_ABORTED:
		/*
		 * most likely this is caused by flush-on-error support. If
		 * it was not there, the we're in trouble.
		 */
		if (!un->un_flush_on_errors) {
			un->un_status = SUN_KEY_FATAL;
			goto reset_target;
		}

		st_set_pe_errno(un);
		bioerror(bp, un->un_errno);
		if (un->un_errno)
			return (COMMAND_DONE_ERROR);
		else
			return (COMMAND_DONE);

	case CMD_TIMEOUT:	/* Command timed out */
		un->un_status = SUN_KEY_TIMEOUT;
		return (COMMAND_TIMEOUT);

	case CMD_TRAN_ERR:
	case CMD_RESET:
		if (pkt->pkt_statistics & (STAT_BUS_RESET | STAT_DEV_RESET)) {
			if ((un->un_rsvd_status &
			    (ST_RESERVE | ST_APPLICATION_RESERVATIONS)) ==
			    ST_RESERVE) {
				un->un_rsvd_status |= ST_LOST_RESERVE;
				ST_DEBUG3(ST_DEVINFO, st_label, CE_WARN,
				    "Lost Reservation\n");
			}
			rval = DEVICE_RESET;
			return (rval);
		}
		if (pkt->pkt_statistics & (STAT_ABORTED | STAT_TERMINATED)) {
			rval = DEVICE_RESET;
			return (rval);
		}
		/*FALLTHROUGH*/
	default:
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "Unhandled packet status reason = %s statistics = 0x%x\n",
		    scsi_rname(pkt->pkt_reason), pkt->pkt_statistics);
reset_target:

		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "transport completed with %s\n",
		    scsi_rname(pkt->pkt_reason));
		ST_DO_ERRSTATS(un, st_transerrs);
		if ((pkt->pkt_state & STATE_GOT_TARGET) &&
		    ((pkt->pkt_statistics & (STAT_BUS_RESET | STAT_DEV_RESET |
		    STAT_ABORTED)) == 0)) {

			/*
			 * If we haven't reserved the drive don't reset it.
			 */
			if ((un->un_rsvd_status &
			    (ST_RESERVE | ST_APPLICATION_RESERVATIONS)) == 0) {
				return (rval);
			}

			/*
			 * if we aren't lost yet we will be soon.
			 */
			un->un_pos.pmode = invalid;

			result = st_reset(un, RESET_LUN);

			if ((result == 0) && (un->un_state >= ST_STATE_OPEN)) {
				/* no hope left to recover */
				scsi_log(ST_DEVINFO, st_label, CE_WARN,
				    "recovery by resets failed\n");
				return (rval);
			}
		}
	}


	if (un->un_pwr_mgmt == ST_PWR_SUSPENDED) {
		rval = QUE_COMMAND;
	} else if (bp == un->un_sbufp) {
		if (rinfo->privatelen == sizeof (recov_info)) {
			if (rinfo->cmd_attrib->retriable) {
				/*
				 * These commands can be rerun
				 * with impunity
				 */
				rval = QUE_COMMAND;
			}
		} else {
			cmd_attribute const *attrib;
			attrib = st_lookup_cmd_attribute(pkt->pkt_cdbp[0]);
			if (attrib->retriable) {
				rval = QUE_COMMAND;
			}
		}
	}

	if (un->un_state >= ST_STATE_OPEN) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    fail, scsi_rname(pkt->pkt_reason),
		    (rval == COMMAND_DONE_ERROR)?
		    "giving up" : "retrying command");
	}
	return (rval);
}

/*
 * if the device is busy, then put this bp back on the waitq, on the
 * interrupt thread, where we want the head of the queue and not the
 * end
 *
 * The callers of this routine should take measures to save the
 * un_throttle in un_last_throttle which will be restored in
 * st_intr_restart(). The only exception should be st_intr_restart()
 * calling this routine for which the saving is already done.
 */
static int
st_handle_intr_busy(struct scsi_tape *un, struct buf *bp,
	clock_t timeout_interval)
{

	int queued;
	int rval = 0;
	pkt_info *pktinfo = BP_PKT(bp)->pkt_private;

	mutex_enter(ST_MUTEX);

	ST_FUNC(ST_DEVINFO, st_handle_intr_busy);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_handle_intr_busy(), un = 0x%p\n", (void *)un);

	if ((bp != un->un_sbufp) && (bp != un->un_recov_buf)) {
		queued = 1;
	} else {
		queued = 0;
	}

	/*
	 * Check to see if we hit the retry timeout. We check to make sure
	 * this is the first one on the runq and make sure we have not
	 * queued up any more, so this one has to be the last on the list
	 * also. If it is not, we have to fail.  If it is not the first, but
	 * is the last we are in trouble anyway, as we are in the interrupt
	 * context here.
	 */
	if ((pktinfo->str_retry_cnt++ > st_retry_count) ||
	    ((un->un_runqf != bp) && (un->un_runql != bp) && (queued))) {
		rval = -1;
		goto exit;
	}

	/* put the bp back on the waitq */
	if (queued) {
		(void) st_remove_from_queue(&un->un_runqf, &un->un_runql, bp);
		st_add_to_queue(&un->un_quef, &un->un_quel, un->un_quef, bp);
	}

	/*
	 * We don't want any other commands being started in the mean time.
	 * If start had just released mutex after putting something on the
	 * runq, we won't even get here.
	 */
	un->un_throttle = 0;

	/*
	 * send a marker pkt, if appropriate
	 */
	st_hba_unflush(un);

	/*
	 * all queues are aligned, we are just waiting to
	 * transport
	 */
	un->un_hib_tid = timeout(st_intr_restart, un, timeout_interval);

exit:
	mutex_exit(ST_MUTEX);
	return (rval);
}

/*
 * To get one error entry from error stack
 */
static int
st_get_error_entry(struct scsi_tape *un, intptr_t arg, int flag)
{
#ifdef _MULTI_DATAMODEL
	/*
	 * For use when a 32 bit app makes a call into a
	 * 64 bit ioctl
	 */
	struct mterror_entry32 err_entry32;
#endif /* _MULTI_DATAMODEL */

	int rval = 0;
	struct mterror_entry err_entry;
	struct mterror_entry_stack *err_link_entry_p;
	size_t arq_status_len_in, arq_status_len_kr;

	ST_FUNC(ST_DEVINFO, st_get_error_entry);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_get_error_entry()\n");

	/*
	 * if error record stack empty, return ENXIO
	 */
	if (un->un_error_entry_stk == NULL) {
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_error_entry: Error Entry Stack Empty!\n");
		rval = ENXIO;
		goto ret;
	}

	/*
	 * get the top entry from stack
	 */
	err_link_entry_p = un->un_error_entry_stk;
	arq_status_len_kr =
	    err_link_entry_p->mtees_entry.mtee_arq_status_len;

#ifdef _MULTI_DATAMODEL
	switch (ddi_model_convert_from(flag & FMODELS)) {
	case DDI_MODEL_ILP32:
		if (ddi_copyin((void *)arg, &err_entry32,
		    MTERROR_ENTRY_SIZE_32, flag)) {
			rval = EFAULT;
			goto ret;
		}

		arq_status_len_in =
		    (size_t)err_entry32.mtee_arq_status_len;

		err_entry32.mtee_cdb_len =
		    (size32_t)err_link_entry_p->mtees_entry.mtee_cdb_len;

		if (arq_status_len_in > arq_status_len_kr)
			err_entry32.mtee_arq_status_len =
			    (size32_t)arq_status_len_kr;

		if (ddi_copyout(
		    err_link_entry_p->mtees_entry.mtee_cdb_buf,
		    (void *)(uintptr_t)err_entry32.mtee_cdb_buf,
		    err_entry32.mtee_cdb_len, flag)) {
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_get_error_entry: Copy cdb buffer error!");
			rval = EFAULT;
		}

		if (ddi_copyout(
		    err_link_entry_p->mtees_entry.mtee_arq_status,
		    (void *)(uintptr_t)err_entry32.mtee_arq_status,
		    err_entry32.mtee_arq_status_len, flag)) {
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_get_error_entry: copy arq status error!");
			rval = EFAULT;
		}

		if (ddi_copyout(&err_entry32, (void *)arg,
		    MTERROR_ENTRY_SIZE_32, flag)) {
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_get_error_entry: copy arq status out error!");
			rval = EFAULT;
		}
		break;

	case DDI_MODEL_NONE:
		if (ddi_copyin((void *)arg, &err_entry,
		    MTERROR_ENTRY_SIZE_64, flag)) {
			rval = EFAULT;
			goto ret;
		}
		arq_status_len_in = err_entry.mtee_arq_status_len;

		err_entry.mtee_cdb_len =
		    err_link_entry_p->mtees_entry.mtee_cdb_len;

		if (arq_status_len_in > arq_status_len_kr)
			err_entry.mtee_arq_status_len =
			    arq_status_len_kr;

		if (ddi_copyout(
		    err_link_entry_p->mtees_entry.mtee_cdb_buf,
		    err_entry.mtee_cdb_buf,
		    err_entry.mtee_cdb_len, flag)) {
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_get_error_entry: Copy cdb buffer error!");
			rval = EFAULT;
		}

		if (ddi_copyout(
		    err_link_entry_p->mtees_entry.mtee_arq_status,
		    err_entry.mtee_arq_status,
		    err_entry.mtee_arq_status_len, flag)) {
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_get_error_entry: copy arq status error!");
			rval = EFAULT;
		}

		if (ddi_copyout(&err_entry, (void *)arg,
		    MTERROR_ENTRY_SIZE_64, flag)) {
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_get_error_entry: copy arq status out error!");
			rval = EFAULT;
		}
		break;
	}
#else /* _MULTI_DATAMODEL */
	if (ddi_copyin((void *)arg, &err_entry,
	    MTERROR_ENTRY_SIZE_64, flag)) {
		rval = EFAULT;
		goto ret;
	}
	arq_status_len_in = err_entry.mtee_arq_status_len;

	err_entry.mtee_cdb_len =
	    err_link_entry_p->mtees_entry.mtee_cdb_len;

	if (arq_status_len_in > arq_status_len_kr)
		err_entry.mtee_arq_status_len =
		    arq_status_len_kr;

	if (ddi_copyout(
	    err_link_entry_p->mtees_entry.mtee_cdb_buf,
	    err_entry.mtee_cdb_buf,
	    err_entry.mtee_cdb_len, flag)) {
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_error_entry: Copy cdb buffer error!");
		rval = EFAULT;
	}

	if (ddi_copyout(
	    err_link_entry_p->mtees_entry.mtee_arq_status,
	    err_entry.mtee_arq_status,
	    err_entry.mtee_arq_status_len, flag)) {
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_error_entry: copy arq status buffer error!");
		rval = EFAULT;
	}

	if (ddi_copyout(&err_entry, (void *)arg,
	    MTERROR_ENTRY_SIZE_64, flag)) {
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_error_entry: copy arq status out error!");
		rval = EFAULT;
	}
#endif /* _MULTI_DATAMODEL */

	/*
	 * update stack
	 */
	un->un_error_entry_stk = err_link_entry_p->mtees_nextp;

	kmem_free(err_link_entry_p->mtees_entry.mtee_cdb_buf,
	    err_link_entry_p->mtees_entry.mtee_cdb_len);
	err_link_entry_p->mtees_entry.mtee_cdb_buf = NULL;

	kmem_free(err_link_entry_p->mtees_entry.mtee_arq_status,
	    SECMDS_STATUS_SIZE);
	err_link_entry_p->mtees_entry.mtee_arq_status = NULL;

	kmem_free(err_link_entry_p, MTERROR_LINK_ENTRY_SIZE);
	err_link_entry_p = NULL;
ret:
	return (rval);
}

/*
 * MTIOCGETERROR ioctl needs to retrieve the current sense data along with
 * the scsi CDB command which causes the error and generates sense data and
 * the scsi status.
 *
 *      error-record stack
 *
 *
 *             TOP                                     BOTTOM
 *              ------------------------------------------
 *              |   0   |   1   |   2   |   ...  |   n   |
 *              ------------------------------------------
 *                  ^
 *                  |
 *       pointer to error entry
 *
 * when st driver generates one sense data record, it creates a error-entry
 * and pushes it onto the stack.
 *
 */

static void
st_update_error_stack(struct scsi_tape *un,
			struct scsi_pkt *pkt,
			struct scsi_arq_status *cmd)
{
	struct mterror_entry_stack *err_entry_tmp;
	uchar_t *cdbp = (uchar_t *)pkt->pkt_cdbp;
	size_t cdblen = scsi_cdb_size[CDB_GROUPID(cdbp[0])];

	ST_FUNC(ST_DEVINFO, st_update_error_stack);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_update_error_stack()\n");

	ASSERT(cmd);
	ASSERT(cdbp);
	if (cdblen == 0) {
		ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_update_error_stack: CDB length error!\n");
		return;
	}

	err_entry_tmp = kmem_alloc(MTERROR_LINK_ENTRY_SIZE, KM_SLEEP);
	ASSERT(err_entry_tmp != NULL);

	err_entry_tmp->mtees_entry.mtee_cdb_buf =
	    kmem_alloc(cdblen, KM_SLEEP);
	ASSERT(err_entry_tmp->mtees_entry.mtee_cdb_buf != NULL);

	err_entry_tmp->mtees_entry.mtee_arq_status =
	    kmem_alloc(SECMDS_STATUS_SIZE, KM_SLEEP);
	ASSERT(err_entry_tmp->mtees_entry.mtee_arq_status != NULL);

	/*
	 * copy cdb command & length to current error entry
	 */
	err_entry_tmp->mtees_entry.mtee_cdb_len = cdblen;
	bcopy(cdbp, err_entry_tmp->mtees_entry.mtee_cdb_buf, cdblen);

	/*
	 * copy scsi status length to current error entry
	 */
	err_entry_tmp->mtees_entry.mtee_arq_status_len =
	    SECMDS_STATUS_SIZE;

	/*
	 * copy sense data and scsi status to current error entry
	 */
	bcopy(cmd, err_entry_tmp->mtees_entry.mtee_arq_status,
	    SECMDS_STATUS_SIZE);

	err_entry_tmp->mtees_nextp = un->un_error_entry_stk;
	un->un_error_entry_stk = err_entry_tmp;

}

/*
 * Empty all the error entry in stack
 */
static void
st_empty_error_stack(struct scsi_tape *un)
{
	struct mterror_entry_stack *linkp;

	ST_FUNC(ST_DEVINFO, st_empty_error_stack);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_empty_entry_stack()\n");

	while (un->un_error_entry_stk != NULL) {
		linkp = un->un_error_entry_stk;
		un->un_error_entry_stk =
		    un->un_error_entry_stk->mtees_nextp;

		if (linkp->mtees_entry.mtee_cdb_buf != NULL)
			kmem_free(linkp->mtees_entry.mtee_cdb_buf,
			    linkp->mtees_entry.mtee_cdb_len);

		if (linkp->mtees_entry.mtee_arq_status != NULL)
			kmem_free(linkp->mtees_entry.mtee_arq_status,
			    linkp->mtees_entry.mtee_arq_status_len);

		kmem_free(linkp, MTERROR_LINK_ENTRY_SIZE);
		linkp = NULL;
	}
}

static errstate
st_handle_sense(struct scsi_tape *un, struct buf *bp, tapepos_t *pos)
{
	struct scsi_pkt *pkt = BP_PKT(bp);
	struct scsi_pkt *rqpkt = un->un_rqs;
	struct scsi_arq_status arqstat;
	recov_info *rcif = pkt->pkt_private;

	errstate rval = COMMAND_DONE_ERROR;
	int amt;

	ST_FUNC(ST_DEVINFO, st_handle_sense);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_handle_sense()\n");

	if (SCBP(rqpkt)->sts_busy) {
		if (rcif->privatelen == sizeof (recov_info)) {
			ST_RECOV(ST_DEVINFO, st_label, CE_WARN,
			    "Attempt recovery of busy unit on request sense\n");
			rval = ATTEMPT_RETRY;
		} else if (rcif->pkt_retry_cnt++ < st_retry_count) {
			ST_DEBUG4(ST_DEVINFO, st_label, CE_WARN,
			    "Retry busy unit on request sense\n");
			rval = QUE_BUSY_COMMAND;
		}
		return (rval);
	} else if (SCBP(rqpkt)->sts_chk) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "Check Condition on REQUEST SENSE\n");
		return (rval);
	}

	/*
	 * Make sure there is sense data to look at.
	 */
	if ((rqpkt->pkt_state & (STATE_GOT_BUS | STATE_GOT_TARGET |
	    STATE_SENT_CMD | STATE_GOT_STATUS)) != (STATE_GOT_BUS |
	    STATE_GOT_TARGET | STATE_SENT_CMD | STATE_GOT_STATUS)) {
		return (rval);
	}

	/* was there enough data? */
	amt = (int)MAX_SENSE_LENGTH - rqpkt->pkt_resid;
	if ((rqpkt->pkt_state & STATE_XFERRED_DATA) == 0 ||
	    (amt < SUN_MIN_SENSE_LENGTH)) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "REQUEST SENSE couldn't get sense data\n");
		return (rval);
	}

	bcopy(SCBP(pkt), &arqstat.sts_status,
	    sizeof (struct scsi_status));
	bcopy(SCBP(rqpkt), &arqstat.sts_rqpkt_status,
	    sizeof (struct scsi_status));
	arqstat.sts_rqpkt_reason = rqpkt->pkt_reason;
	arqstat.sts_rqpkt_resid = rqpkt->pkt_resid;
	arqstat.sts_rqpkt_state = rqpkt->pkt_state;
	arqstat.sts_rqpkt_statistics = rqpkt->pkt_statistics;
	bcopy(ST_RQSENSE, &arqstat.sts_sensedata, SENSE_LENGTH);

	/*
	 * copy one arqstat entry in the sense data buffer
	 */
	st_update_error_stack(un, pkt, &arqstat);
	return (st_decode_sense(un, bp, amt, &arqstat, pos));
}

static errstate
st_handle_autosense(struct scsi_tape *un, struct buf *bp, tapepos_t *pos)
{
	struct scsi_pkt *pkt = BP_PKT(bp);
	struct scsi_arq_status *arqstat =
	    (struct scsi_arq_status *)pkt->pkt_scbp;
	errstate rval = COMMAND_DONE_ERROR;
	int amt;

	ST_FUNC(ST_DEVINFO, st_handle_autosense);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_handle_autosense()\n");

	if (arqstat->sts_rqpkt_status.sts_busy) {
		ST_DEBUG4(ST_DEVINFO, st_label, CE_WARN,
		    "busy unit on request sense\n");
		/*
		 * we return QUE_SENSE so st_intr will setup the SENSE cmd.
		 * the disadvantage is that we do not have any delay for the
		 * second retry of rqsense and we have to keep a packet around
		 */
		return (QUE_SENSE);

	} else if (arqstat->sts_rqpkt_reason != CMD_CMPLT) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "transport error on REQUEST SENSE\n");
		if ((arqstat->sts_rqpkt_state & STATE_GOT_TARGET) &&
		    ((arqstat->sts_rqpkt_statistics &
		    (STAT_BUS_RESET | STAT_DEV_RESET | STAT_ABORTED)) == 0)) {
			if (st_reset(un, RESET_LUN) == 0) {
				ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
				    "recovery by resets failed\n");
			}
		}
		return (rval);

	} else if (arqstat->sts_rqpkt_status.sts_chk) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "Check Condition on REQUEST SENSE\n");
		return (rval);
	}


	/* was there enough data? */
	if (pkt->pkt_state & STATE_XARQ_DONE) {
		amt = (int)MAX_SENSE_LENGTH - arqstat->sts_rqpkt_resid;
	} else {
		if (arqstat->sts_rqpkt_resid > SENSE_LENGTH) {
			amt = (int)MAX_SENSE_LENGTH - arqstat->sts_rqpkt_resid;
		} else {
			amt = (int)SENSE_LENGTH - arqstat->sts_rqpkt_resid;
		}
	}
	if ((arqstat->sts_rqpkt_state & STATE_XFERRED_DATA) == 0 ||
	    (amt < SUN_MIN_SENSE_LENGTH)) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "REQUEST SENSE couldn't get sense data\n");
		return (rval);
	}

	if (pkt->pkt_state & STATE_XARQ_DONE) {
		bcopy(&arqstat->sts_sensedata, ST_RQSENSE, MAX_SENSE_LENGTH);
	} else {
		bcopy(&arqstat->sts_sensedata, ST_RQSENSE, SENSE_LENGTH);
	}

	/*
	 * copy one arqstat entry in the sense data buffer
	 */
	st_update_error_stack(un, pkt, arqstat);

	return (st_decode_sense(un, bp, amt, arqstat, pos));
}

static errstate
st_decode_sense(struct scsi_tape *un, struct buf *bp, int amt,
    struct scsi_arq_status *statusp, tapepos_t *pos)
{
	struct scsi_pkt *pkt = BP_PKT(bp);
	recov_info *ri = pkt->pkt_private;
	errstate rval = COMMAND_DONE_ERROR;
	cmd_attribute const *attrib;
	long resid;
	struct scsi_extended_sense *sensep = ST_RQSENSE;
	int severity;
	int get_error;

	ST_FUNC(ST_DEVINFO, st_decode_sense);

	ASSERT(mutex_owned(ST_MUTEX));
	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_decode_sense()\n");

	/*
	 * For uscsi commands, squirrel away a copy of the
	 * results of the Request Sense.
	 */
	if (USCSI_CMD(bp)) {
		struct uscsi_cmd *ucmd = BP_UCMD(bp);
		ucmd->uscsi_rqstatus = *(uchar_t *)statusp;
		if (ucmd->uscsi_rqlen && un->un_srqbufp) {
			uchar_t rqlen = min((uchar_t)amt, ucmd->uscsi_rqlen);
			ucmd->uscsi_rqresid = ucmd->uscsi_rqlen - rqlen;
			bcopy(ST_RQSENSE, un->un_srqbufp, rqlen);
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_decode_sense: stat=0x%x resid=0x%x\n",
			    ucmd->uscsi_rqstatus, ucmd->uscsi_rqresid);
		}
	}

	if (ri->privatelen == sizeof (recov_info)) {
		attrib = ri->cmd_attrib;
	} else {
		attrib = st_lookup_cmd_attribute(pkt->pkt_cdbp[0]);
	}

	/*
	 * If the drive is an MT-02, reposition the
	 * secondary error code into the proper place.
	 *
	 * XXX	MT-02 is non-CCS tape, so secondary error code
	 * is in byte 8.  However, in SCSI-2, tape has CCS definition
	 * so it's in byte 12.
	 */
	if (un->un_dp->type == ST_TYPE_EMULEX) {
		sensep->es_code = sensep->es_add_info[0];
	}

	ST_CDB(ST_DEVINFO, "st_decode_sense failed CDB",
	    (caddr_t)&CDBP(pkt)->scc_cmd);

	ST_SENSE(ST_DEVINFO, "st_decode_sense sense data", (caddr_t)statusp,
	    sizeof (*statusp));

	/* for normal I/O check extract the resid values. */
	if (bp != un->un_sbufp && bp != un->un_recov_buf) {
		if (sensep->es_valid) {
			resid =
			    (sensep->es_info_1 << 24) |
			    (sensep->es_info_2 << 16) |
			    (sensep->es_info_3 << 8)  |
			    (sensep->es_info_4);
			/* If fixed block */
			if (un->un_bsize) {
				resid *= un->un_bsize;
			}
		} else if (pkt->pkt_state & STATE_XFERRED_DATA) {
			resid = pkt->pkt_resid;
		} else {
			resid = bp->b_bcount;
		}
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_decode_sense (rw): xferred bit = %d, resid=%ld (%d), "
		    "pkt_resid=%ld\n", pkt->pkt_state & STATE_XFERRED_DATA,
		    resid,
		    (sensep->es_info_1 << 24) |
		    (sensep->es_info_2 << 16) |
		    (sensep->es_info_3 << 8)  |
		    (sensep->es_info_4),
		    pkt->pkt_resid);
		/*
		 * The problem is, what should we believe?
		 */
		if (resid && (pkt->pkt_resid == 0)) {
			pkt->pkt_resid = resid;
		}
	} else {
		/*
		 * If the command is SCMD_SPACE, we need to get the
		 * residual as returned in the sense data, to adjust
		 * our idea of current tape position correctly
		 */
		if ((sensep->es_valid) &&
		    (CDBP(pkt)->scc_cmd == SCMD_LOCATE) ||
		    (CDBP(pkt)->scc_cmd == SCMD_LOCATE_G4) ||
		    (CDBP(pkt)->scc_cmd == SCMD_SPACE) ||
		    (CDBP(pkt)->scc_cmd == SCMD_SPACE_G4) ||
		    (CDBP(pkt)->scc_cmd == SCMD_WRITE_FILE_MARK)) {
			resid =
			    (sensep->es_info_1 << 24) |
			    (sensep->es_info_2 << 16) |
			    (sensep->es_info_3 << 8)  |
			    (sensep->es_info_4);
			bp->b_resid = resid;
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_decode_sense(other):	resid=%ld\n", resid);
		} else {
			/*
			 * If the special command is SCMD_READ,
			 * the correct resid will be set later.
			 */
			if (attrib->get_cnt != NULL) {
				resid = attrib->get_cnt(pkt->pkt_cdbp);
			} else {
				resid = bp->b_bcount;
			}
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_decode_sense(special read):  resid=%ld\n",
			    resid);
		}
	}

	if ((un->un_state >= ST_STATE_OPEN) &&
	    (DEBUGGING || st_error_level == SCSI_ERR_ALL)) {
		st_print_cdb(ST_DEVINFO, st_label, CE_NOTE,
		    "Failed CDB", (char *)pkt->pkt_cdbp);
		st_clean_print(ST_DEVINFO, st_label, CE_CONT,
		    "sense data", (char *)sensep, amt);
		scsi_log(ST_DEVINFO, st_label, CE_CONT,
		    "count 0x%lx resid 0x%lx pktresid 0x%lx\n",
		    bp->b_bcount, resid, pkt->pkt_resid);
	}

	switch (un->un_status = sensep->es_key) {
	case KEY_NO_SENSE:
		severity = SCSI_ERR_INFO;

		/*
		 * Erase, locate or rewind operation in progress, retry
		 * ASC  ASCQ
		 *  00   18    Erase operation in progress
		 *  00   19    Locate operation in progress
		 *  00   1A    Rewind operation in progress
		 */
		if (sensep->es_add_code == 0 &&
		    ((sensep->es_qual_code == 0x18) ||
		    (sensep->es_qual_code == 0x19) ||
		    (sensep->es_qual_code == 0x1a))) {
			rval = QUE_BUSY_COMMAND;
			break;
		}

		goto common;

	case KEY_RECOVERABLE_ERROR:
		severity = SCSI_ERR_RECOVERED;
		if ((sensep->es_class == CLASS_EXTENDED_SENSE) &&
		    (sensep->es_code == ST_DEFERRED_ERROR)) {
			if (un->un_dp->options &
			    ST_RETRY_ON_RECOVERED_DEFERRED_ERROR) {
				rval = QUE_LAST_COMMAND;
				scsi_errmsg(ST_SCSI_DEVP, pkt, st_label,
				    severity, pos->lgclblkno,
				    un->un_err_pos.lgclblkno, scsi_cmds,
				    sensep);
				scsi_log(ST_DEVINFO, st_label, CE_CONT,
				    "Command will be retried\n");
			} else {
				severity = SCSI_ERR_FATAL;
				rval = COMMAND_DONE_ERROR_RECOVERED;
				ST_DO_ERRSTATS(un, st_softerrs);
				scsi_errmsg(ST_SCSI_DEVP, pkt, st_label,
				    severity, pos->lgclblkno,
				    un->un_err_pos.lgclblkno, scsi_cmds,
				    sensep);
			}
			break;
		}
common:
		/*
		 * XXX only want reads to be stopped by filemarks.
		 * Don't want them to be stopped by EOT.  EOT matters
		 * only on write.
		 */
		if (sensep->es_filmk && !sensep->es_eom) {
			rval = COMMAND_DONE;
		} else if (sensep->es_eom) {
			rval = COMMAND_DONE;
		} else if (sensep->es_ili) {
			/*
			 * Fun with variable length record devices:
			 * for specifying larger blocks sizes than the
			 * actual physical record size.
			 */
			if (un->un_bsize == 0 && resid > 0) {
				/*
				 * XXX! Ugly.
				 * The requested blocksize is > tape blocksize,
				 * so this is ok, so we just return the
				 * actual size xferred.
				 */
				pkt->pkt_resid = resid;
				rval = COMMAND_DONE;
			} else if (un->un_bsize == 0 && resid < 0) {
				/*
				 * The requested blocksize is < tape blocksize,
				 * so this is not ok, so we err with ENOMEM
				 */
				rval = COMMAND_DONE_ERROR_RECOVERED;
				st_bioerror(bp, ENOMEM);
			} else {
				ST_DO_ERRSTATS(un, st_softerrs);
				severity = SCSI_ERR_FATAL;
				rval = COMMAND_DONE_ERROR;
				st_bioerror(bp, EINVAL);
				un->un_running.pmode = invalid;
			}
		} else {
			/*
			 * we hope and pray for this just being
			 * something we can ignore (ie. a
			 * truly recoverable soft error)
			 */
			rval = COMMAND_DONE;
		}
		if (sensep->es_filmk) {
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "filemark\n");
			un->un_status = SUN_KEY_EOF;
			pos->eof = ST_EOF_PENDING;
			st_set_pe_flag(un);
		}

		/*
		 * ignore eom when reading, a fmk should terminate reading
		 */
		if ((sensep->es_eom) &&
		    (CDBP(pkt)->scc_cmd != SCMD_READ)) {
			if ((sensep->es_add_code == 0) &&
			    (sensep->es_qual_code == 4)) {
				ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "bot\n");
				un->un_status = SUN_KEY_BOT;
				pos->eof = ST_NO_EOF;
				pos->lgclblkno = 0;
				pos->fileno = 0;
				pos->blkno = 0;
				if (pos->pmode != legacy)
					pos->pmode = legacy;
			} else {
				ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "eom\n");
				un->un_status = SUN_KEY_EOT;
				pos->eof = ST_EOM;
			}
			st_set_pe_flag(un);
		}

		break;

	case KEY_ILLEGAL_REQUEST:

		if (un->un_laststate >= ST_STATE_OPEN) {
			ST_DO_ERRSTATS(un, st_softerrs);
			severity = SCSI_ERR_FATAL;
		} else {
			severity = SCSI_ERR_INFO;
		}
		break;

	case KEY_MEDIUM_ERROR:
		ST_DO_ERRSTATS(un, st_harderrs);
		severity = SCSI_ERR_FATAL;
		un->un_pos.pmode = invalid;
		un->un_running.pmode = invalid;
check_keys:
		/*
		 * attempt to process the keys in the presence of
		 * other errors
		 */
		if (sensep->es_ili && rval != COMMAND_DONE_ERROR) {
			/*
			 * Fun with variable length record devices:
			 * for specifying larger blocks sizes than the
			 * actual physical record size.
			 */
			if (un->un_bsize == 0 && resid > 0) {
				/*
				 * XXX! Ugly
				 */
				pkt->pkt_resid = resid;
			} else if (un->un_bsize == 0 && resid < 0) {
				st_bioerror(bp, EINVAL);
			} else {
				severity = SCSI_ERR_FATAL;
				rval = COMMAND_DONE_ERROR;
				st_bioerror(bp, EINVAL);
			}
		}
		if (sensep->es_filmk) {
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "filemark\n");
			un->un_status = SUN_KEY_EOF;
			pos->eof = ST_EOF_PENDING;
			st_set_pe_flag(un);
		}

		/*
		 * ignore eom when reading, a fmk should terminate reading
		 */
		if ((sensep->es_eom) &&
		    (CDBP(pkt)->scc_cmd != SCMD_READ)) {
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG, "eom\n");
			un->un_status = SUN_KEY_EOT;
			pos->eof = ST_EOM;
			st_set_pe_flag(un);
		}

		break;

	case KEY_VOLUME_OVERFLOW:
		ST_DO_ERRSTATS(un, st_softerrs);
		pos->eof = ST_EOM;
		severity = SCSI_ERR_FATAL;
		rval = COMMAND_DONE_ERROR;
		goto check_keys;

	case KEY_HARDWARE_ERROR:
		ST_DO_ERRSTATS(un, st_harderrs);
		severity = SCSI_ERR_FATAL;
		rval = COMMAND_DONE_ERROR;
		if (un->un_dp->options & ST_EJECT_ON_CHANGER_FAILURE)
			un->un_eject_tape_on_failure = st_check_asc_ascq(un);
		break;

	case KEY_BLANK_CHECK:
		ST_DO_ERRSTATS(un, st_softerrs);
		severity = SCSI_ERR_INFO;

		/*
		 * if not a special request and some data was xferred then it
		 * it is not an error yet
		 */
		if (bp != un->un_sbufp && (bp->b_flags & B_READ)) {
			/*
			 * no error for read with or without data xferred
			 */
			un->un_status = SUN_KEY_EOT;
			pos->eof = ST_EOT;
			rval = COMMAND_DONE_ERROR;
			un->un_running.pmode = invalid;
			st_set_pe_flag(un);
			goto check_keys;
		} else if (bp != un->un_sbufp &&
		    (pkt->pkt_state & STATE_XFERRED_DATA)) {
			rval = COMMAND_DONE;
		} else {
			rval = COMMAND_DONE_ERROR_RECOVERED;
		}

		if (un->un_laststate >= ST_STATE_OPEN) {
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "blank check\n");
			pos->eof = ST_EOM;
		}
		if ((CDBP(pkt)->scc_cmd == SCMD_LOCATE) ||
		    (CDBP(pkt)->scc_cmd == SCMD_LOCATE_G4) ||
		    (CDBP(pkt)->scc_cmd == SCMD_SPACE) &&
		    (un->un_dp->options & ST_KNOWS_EOD)) {
			/*
			 * we were doing a fast forward by skipping
			 * multiple fmk at the time
			 */
			st_bioerror(bp, EIO);
			severity = SCSI_ERR_RECOVERED;
			rval	 = COMMAND_DONE;
		}
		st_set_pe_flag(un);
		goto check_keys;

	case KEY_WRITE_PROTECT:
		if (st_wrongtapetype(un)) {
			un->un_status = SUN_KEY_WRONGMEDIA;
			ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
			    "wrong tape for writing- use DC6150 tape "
			    "(or equivalent)\n");
			severity = SCSI_ERR_UNKNOWN;
		} else {
			severity = SCSI_ERR_FATAL;
		}
		ST_DO_ERRSTATS(un, st_harderrs);
		rval = COMMAND_DONE_ERROR;
		st_bioerror(bp, EACCES);
		break;

	case KEY_UNIT_ATTENTION:
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "KEY_UNIT_ATTENTION : un_state = %d\n", un->un_state);

		un->un_unit_attention_flags |= 1;
		/*
		 * If we have detected a Bus Reset and the tape
		 * drive has been reserved.
		 */
		if (ST_RQSENSE->es_add_code == 0x29) {
			rval = DEVICE_RESET;
			if ((un->un_rsvd_status &
			    (ST_RESERVE | ST_APPLICATION_RESERVATIONS)) ==
			    ST_RESERVE) {
				un->un_rsvd_status |= ST_LOST_RESERVE;
				ST_DEBUG(ST_DEVINFO, st_label, CE_WARN,
				    "st_decode_sense: Lost Reservation\n");
			}
		}

		/*
		 * If this is a recovery command and retrable, retry.
		 */
		if (bp == un->un_recov_buf) {
			severity = SCSI_ERR_INFO;
			if (attrib->retriable &&
			    ri->pkt_retry_cnt++ < st_retry_count) {
				rval = QUE_COMMAND;
			} else {
				rval = COMMAND_DONE_ERROR;
			}
			break; /* Don't set position invalid */
		}

		/*
		 * If ST_APPLICATION_RESERVATIONS is set,
		 * If the asc/ascq indicates that the reservation
		 * has been cleared just allow the write to continue
		 * which would force a scsi 2 reserve.
		 * If preempted that persistent reservation
		 * the scsi 2 reserve would get a reservation conflict.
		 */
		if ((un->un_rsvd_status &
		    ST_APPLICATION_RESERVATIONS) != 0) {
			/*
			 * RESERVATIONS PREEMPTED
			 * With MPxIO this could be a fail over? XXX
			 */
			if (ST_RQSENSE->es_add_code == 0x2a &&
			    ST_RQSENSE->es_qual_code == 0x03) {
				severity = SCSI_ERR_INFO;
				rval = COMMAND_DONE_ERROR;
				pos->pmode = invalid;
				break;
			/*
			 * RESERVATIONS RELEASED
			 */
			} else if (ST_RQSENSE->es_add_code == 0x2a &&
			    ST_RQSENSE->es_qual_code == 0x04) {
				severity = SCSI_ERR_INFO;
				rval = COMMAND_DONE;
				break;
			}
		}

		if (un->un_state <= ST_STATE_OPENING) {
			/*
			 * Look, the tape isn't open yet, now determine
			 * if the cause is a BUS RESET, Save the file
			 * and Block positions for the callers to
			 * recover from the loss of position.
			 */
			severity = SCSI_ERR_INFO;
			if ((pos->pmode != invalid) &&
			    (rval == DEVICE_RESET) &&
			    (un->un_restore_pos != 1)) {
				un->un_save_fileno = pos->fileno;
				un->un_save_blkno = pos->blkno;
				un->un_restore_pos = 1;
			}

			if (attrib->retriable &&
			    ri->pkt_retry_cnt++ < st_retry_count) {
				rval = QUE_COMMAND;
			} else if (rval == DEVICE_RESET) {
				break;
			} else {
				rval = COMMAND_DONE_ERROR;
			}
		/*
		 * Means it thinks the mode parameters have changed.
		 * This is the result of a reset clearing settings or
		 * another initiator changing what we set.
		 */
		}
		if (ST_RQSENSE->es_add_code == 0x2a) {
			if (ST_RQSENSE->es_qual_code == 0x1) {
				/* Error recovery will modeselect and retry. */
				rval = DEVICE_TAMPER;
				severity = SCSI_ERR_INFO;
				break; /* don't set position invalid */
			}
			if (ST_RQSENSE->es_qual_code == 0x0 ||
			    ST_RQSENSE->es_qual_code == 0x2 ||
			    ST_RQSENSE->es_qual_code == 0x3 ||
			    ST_RQSENSE->es_qual_code == 0x4 ||
			    ST_RQSENSE->es_qual_code == 0x5 ||
			    ST_RQSENSE->es_qual_code == 0x6 ||
			    ST_RQSENSE->es_qual_code == 0x7) {
				rval = DEVICE_TAMPER;
				severity = SCSI_ERR_INFO;
			}
		} else if (ST_RQSENSE->es_add_code == 0x28 &&
		    ((ST_RQSENSE->es_qual_code == 0x0) ||
		    ST_RQSENSE->es_qual_code == 0x5)) {
			/*
			 * Not Ready to Ready change, Media may have changed.
			 */
			rval = DEVICE_TAMPER;
			severity = SCSI_ERR_RETRYABLE;
		} else {
			if (rval != DEVICE_RESET) {
				rval = COMMAND_DONE_ERROR;
			} else {
				/*
				 * Returning DEVICE_RESET will call
				 * error recovery.
				 */
				severity = SCSI_ERR_INFO;
				break; /* don't set position invalid */
			}
			/*
			 * Check if it is an Unexpected Unit Attention.
			 * If state is >= ST_STATE_OPEN, we have
			 * already done the initialization .
			 * In this case it is Fatal Error
			 * since no further reading/writing
			 * can be done with fileno set to < 0.
			 */
			if (un->un_state >= ST_STATE_OPEN) {
				ST_DO_ERRSTATS(un, st_harderrs);
				severity = SCSI_ERR_FATAL;
			} else {
				severity = SCSI_ERR_INFO;
			}
		}

		pos->pmode = invalid;

		break;

	case KEY_NOT_READY:
		/*
		 * If in process of getting ready retry.
		 */
		if (sensep->es_add_code == 0x04) {
			switch (sensep->es_qual_code) {
			case 0x07:
				/*
				 * We get here when the tape is rewinding.
				 * QUE_BUSY_COMMAND retries every 10 seconds.
				 */
				if (ri->pkt_retry_cnt++ <
				    (un->un_dp->rewind_timeout / 10)) {
					rval = QUE_BUSY_COMMAND;
					severity = SCSI_ERR_INFO;
				} else {
					/* give up */
					rval = COMMAND_DONE_ERROR;
					severity = SCSI_ERR_FATAL;
				}
				break;
			case 0x01:
				if (ri->pkt_retry_cnt++ < st_retry_count) {
					rval = QUE_COMMAND;
					severity = SCSI_ERR_INFO;
					break;
				}
			default: /* FALLTHRU */
				/* give up */
				rval = COMMAND_DONE_ERROR;
				severity = SCSI_ERR_FATAL;
			}
		} else {
			/* give up */
			rval = COMMAND_DONE_ERROR;
			severity = SCSI_ERR_FATAL;
		}

		/*
		 * If this was an error and after device opened
		 * do error stats.
		 */
		if (rval == COMMAND_DONE_ERROR &&
		    un->un_state > ST_STATE_OPENING) {
			ST_DO_ERRSTATS(un, st_harderrs);
		}

		if (ST_RQSENSE->es_add_code == 0x3a) {
			if (st_error_level >= SCSI_ERR_FATAL)
				scsi_log(ST_DEVINFO, st_label, CE_NOTE,
				    "Tape not inserted in drive\n");
			un->un_mediastate = MTIO_EJECTED;
			cv_broadcast(&un->un_state_cv);
		}
		if ((un->un_dp->options & ST_EJECT_ON_CHANGER_FAILURE) &&
		    (rval != QUE_COMMAND))
			un->un_eject_tape_on_failure = st_check_asc_ascq(un);
		break;

	case KEY_ABORTED_COMMAND:
		/* XXX Do drives return this when they see a lost light? */
		/* Testing would say yes */

		if (ri->pkt_retry_cnt++ < st_retry_count) {
			rval = ATTEMPT_RETRY;
			severity = SCSI_ERR_RETRYABLE;
			goto check_keys;
		}
		/*
		 * Probably a parity error...
		 * if we retry here then this may cause data to be
		 * written twice or data skipped during reading
		 */
		ST_DO_ERRSTATS(un, st_harderrs);
		severity = SCSI_ERR_FATAL;
		rval = COMMAND_DONE_ERROR;
		goto check_keys;

	default:
		/*
		 * Undecoded sense key.	 Try retries and hope
		 * that will fix the problem.  Otherwise, we're
		 * dead.
		 */
		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "Unhandled Sense Key '%s'\n",
		    sense_keys[un->un_status]);
		ST_DO_ERRSTATS(un, st_harderrs);
		severity = SCSI_ERR_FATAL;
		rval = COMMAND_DONE_ERROR;
		goto check_keys;
	}

	if ((!(pkt->pkt_flags & FLAG_SILENT) &&
	    un->un_state >= ST_STATE_OPEN) && (DEBUGGING ||
	    (un->un_laststate > ST_STATE_OPENING) &&
	    (severity >= st_error_level))) {

		scsi_errmsg(ST_SCSI_DEVP, pkt, st_label, severity,
		    pos->lgclblkno, un->un_err_pos.lgclblkno,
		    scsi_cmds, sensep);
		if (sensep->es_filmk) {
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "File Mark Detected\n");
		}
		if (sensep->es_eom) {
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "End-of-Media Detected\n");
		}
		if (sensep->es_ili) {
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "Incorrect Length Indicator Set\n");
		}
	}
	get_error = geterror(bp);
	if (((rval == COMMAND_DONE_ERROR) ||
	    (rval == COMMAND_DONE_ERROR_RECOVERED)) &&
	    ((get_error == EIO) || (get_error == 0))) {
		un->un_rqs_state |= (ST_RQS_ERROR | ST_RQS_VALID);
		bcopy(ST_RQSENSE, un->un_uscsi_rqs_buf, SENSE_LENGTH);
		if (un->un_rqs_state & ST_RQS_READ) {
			un->un_rqs_state &= ~(ST_RQS_READ);
		} else {
			un->un_rqs_state |= ST_RQS_OVR;
		}
	}

	return (rval);
}


static int
st_handle_intr_retry_lcmd(struct scsi_tape *un, struct buf *bp)
{
	int status = TRAN_ACCEPT;
	pkt_info *pktinfo = BP_PKT(bp)->pkt_private;

	mutex_enter(ST_MUTEX);

	ST_FUNC(ST_DEVINFO, st_handle_intr_retry_lcmd);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_handle_intr_rtr_lcmd(), un = 0x%p\n", (void *)un);

	/*
	 * Check to see if we hit the retry timeout. We check to make sure
	 * this is the first one on the runq and make sure we have not
	 * queued up any more, so this one has to be the last on the list
	 * also. If it is not, we have to fail.  If it is not the first, but
	 * is the last we are in trouble anyway, as we are in the interrupt
	 * context here.
	 */
	if ((pktinfo->pkt_retry_cnt > st_retry_count) ||
	    ((un->un_runqf != bp) && (un->un_runql != bp))) {
		goto exit;
	}

	if (un->un_throttle) {
		un->un_last_throttle = un->un_throttle;
		un->un_throttle = 0;
	}

	/*
	 * Here we know : bp is the first and last one on the runq
	 * it is not necessary to put it back on the head of the
	 * waitq and then move from waitq to runq. Save this queuing
	 * and call scsi_transport.
	 */
	ST_CDB(ST_DEVINFO, "Retry lcmd CDB", (char *)BP_PKT(bp)->pkt_cdbp);

	status = st_transport(un, BP_PKT(bp));

	if (status == TRAN_ACCEPT) {
		if (un->un_last_throttle) {
			un->un_throttle = un->un_last_throttle;
		}
		mutex_exit(ST_MUTEX);

		ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
		    "restart transport \n");
		return (0);
	}

	ST_DO_KSTATS(bp, kstat_runq_back_to_waitq);
	mutex_exit(ST_MUTEX);

	if (status == TRAN_BUSY) {
		if (st_handle_intr_busy(un, bp, ST_TRAN_BUSY_TIMEOUT) == 0) {
			return (0);
		}
	}
	ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
	    "restart transport rejected\n");
	mutex_enter(ST_MUTEX);
	ST_DO_ERRSTATS(un, st_transerrs);
	if (un->un_last_throttle) {
		un->un_throttle = un->un_last_throttle;
	}
exit:
	mutex_exit(ST_MUTEX);
	return (-1);
}

static int
st_wrongtapetype(struct scsi_tape *un)
{

	ST_FUNC(ST_DEVINFO, st_wrongtapetype);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_wrongtapetype()\n");

	/*
	 * Hack to handle  600A, 600XTD, 6150 && 660 vs. 300XL tapes...
	 */
	if (un->un_dp && (un->un_dp->options & ST_QIC) && un->un_mspl) {
		switch (un->un_dp->type) {
		case ST_TYPE_WANGTEK:
		case ST_TYPE_ARCHIVE:
			/*
			 * If this really worked, we could go off of
			 * the density codes set in the modesense
			 * page. For this drive, 0x10 == QIC-120,
			 * 0xf == QIC-150, and 0x5 should be for
			 * both QIC-24 and, maybe, QIC-11. However,
			 * the h/w doesn't do what the manual says
			 * that it should, so we'll key off of
			 * getting a WRITE PROTECT error AND wp *not*
			 * set in the mode sense information.
			 */
			/*
			 * XXX but we already know that status is
			 * write protect, so don't check it again.
			 */

			if (un->un_status == KEY_WRITE_PROTECT &&
			    un->un_mspl->wp == 0) {
				return (1);
			}
			break;
		default:
			break;
		}
	}
	return (0);
}

static errstate
st_check_error(struct scsi_tape *un, struct scsi_pkt *pkt)
{
	errstate action;
	recov_info *rcvi = pkt->pkt_private;
	buf_t *bp = rcvi->cmd_bp;
	struct scsi_arq_status *stat = (struct scsi_arq_status *)pkt->pkt_scbp;

	ST_FUNC(ST_DEVINFO, st_check_error);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_check_error()\n");

	switch (SCBP_C(pkt)) {
	case STATUS_RESERVATION_CONFLICT:
		/*
		 * Command recovery is enabled, not just opening,
		 * we had the drive reserved and we thing its ours.
		 * Call recovery to attempt to take it back.
		 */
		if ((rcvi->privatelen == sizeof (recov_info)) &&
		    (bp != un->un_recov_buf) &&
		    (un->un_state > ST_STATE_OPEN_PENDING_IO) &&
		    ((un->un_rsvd_status & (ST_RESERVE |
		    ST_APPLICATION_RESERVATIONS)) != 0)) {
			action = ATTEMPT_RETRY;
			un->un_rsvd_status |= ST_LOST_RESERVE;
		} else {
			action = COMMAND_DONE_EACCES;
			un->un_rsvd_status |= ST_RESERVATION_CONFLICT;
		}
		break;

	case STATUS_BUSY:
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG, "unit busy\n");
		if (rcvi->privatelen == sizeof (recov_info) &&
		    un->un_multipath && (pkt->pkt_state == (STATE_GOT_BUS |
		    STATE_GOT_TARGET | STATE_SENT_CMD | STATE_GOT_STATUS))) {
			/*
			 * Status returned by scsi_vhci indicating path
			 * has failed over.
			 */
			action = PATH_FAILED;
			break;
		}
		/* FALLTHRU */
	case STATUS_QFULL:
		if (rcvi->privatelen == sizeof (recov_info)) {
			/*
			 * If recovery is inabled use it instead of
			 * blind reties.
			 */
			action = ATTEMPT_RETRY;
		} else if (rcvi->pkt_retry_cnt++ < st_retry_count) {
			action = QUE_BUSY_COMMAND;
		} else if ((un->un_rsvd_status &
		    (ST_RESERVE | ST_APPLICATION_RESERVATIONS)) == 0) {
			/*
			 * If this is a command done before reserve is done
			 * don't reset.
			 */
			action = COMMAND_DONE_ERROR;
		} else {
			ST_DEBUG2(ST_DEVINFO, st_label, CE_WARN,
			    "unit busy too long\n");
			(void) st_reset(un, RESET_ALL);
			action = COMMAND_DONE_ERROR;
		}
		break;

	case STATUS_CHECK:
	case STATUS_TERMINATED:
		/*
		 * we should only get here if the auto rqsense failed
		 * thru a uscsi cmd without autorequest sense
		 * so we just try again
		 */
		if (un->un_arq_enabled &&
		    stat->sts_rqpkt_reason == CMD_CMPLT &&
		    (stat->sts_rqpkt_state & (STATE_GOT_BUS |
		    STATE_GOT_TARGET | STATE_SENT_CMD | STATE_GOT_STATUS)) ==
		    (STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD |
		    STATE_GOT_STATUS)) {

			ST_DEBUG2(ST_DEVINFO, st_label, CE_WARN,
			    "Really got sense data\n");
			action = st_decode_sense(un, bp, MAX_SENSE_LENGTH -
			    pkt->pkt_resid, stat, &un->un_pos);
		} else {
			ST_DEBUG2(ST_DEVINFO, st_label, CE_WARN,
			    "Trying to queue sense command\n");
			action = QUE_SENSE;
		}
		break;

	case STATUS_TASK_ABORT:
		/*
		 * This is an aborted task. This can be a reset on the other
		 * port of a multiport drive. Lets try and recover it.
		 */
		action = DEVICE_RESET;
		break;

	default:
		action = COMMAND_DONE;
		ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
		    "Unexpected scsi status byte 0x%x\n", SCBP_C(pkt));
	}
	return (action);
}

static void
st_calc_bnum(struct scsi_tape *un, struct buf *bp, struct scsi_pkt *pkt)
{
	int nblks;
	int nfiles;
	long count;
	recov_info *ri = pkt->pkt_private;
	cmd_attribute const *attrib;

	ST_FUNC(ST_DEVINFO, st_calc_bnum);

	ASSERT(mutex_owned(ST_MUTEX));

	if (ri->privatelen == sizeof (recov_info)) {
		attrib = ri->cmd_attrib;
		ASSERT(attrib->recov_pos_type == POS_EXPECTED);
		ASSERT(attrib->chg_tape_pos);
	} else {
		ri = NULL;
		attrib = st_lookup_cmd_attribute(pkt->pkt_cdbp[0]);
	}

	count = bp->b_bcount - bp->b_resid;

	/* Command reads or writes data */
	if (attrib->transfers_data != TRAN_NONE) {
		if (count == 0) {
			if (attrib->transfers_data == TRAN_WRTE) {
				ASSERT(un->un_pos.pmode == ST_EOM);
				nblks = 0;
				nfiles = 0;
			} else {
				ASSERT(un->un_pos.pmode == ST_EOF_PENDING);
				nblks = 0;
				nfiles = 1;
			}
		} else if (un->un_bsize == 0) {
			/*
			 * If variable block mode.
			 * Fixed bit in CBD should be zero.
			 */
			ASSERT((pkt->pkt_cdbp[1] & 1) == 0);
			nblks = 1;
			un->un_kbytes_xferred += (count / ONE_K);
			nfiles = 0;
		} else {
			/*
			 * If fixed block mode.
			 * Fixed bit in CBD should be one.
			 */
			ASSERT((pkt->pkt_cdbp[1] & 1) == 1);
			nblks = (count / un->un_bsize);
			un->un_kbytes_xferred += (nblks * un->un_bsize) / ONE_K;
			nfiles = 0;
		}
		/*
		 * So its possable to read some blocks and hit a filemark.
		 * Example reading in fixed block mode where more then one
		 * block at a time is requested. In this case because the
		 * filemark is hit something less then the requesed number
		 * of blocks is read.
		 */
		if (un->un_pos.eof == ST_EOF_PENDING && bp->b_resid) {
			nfiles = 1;
		}
	} else {
		nblks = 0;
		nfiles = count;
	}

	/*
	 * If some command failed after this one started and it seems
	 * to have finshed without error count the position.
	 */
	if (un->un_persistence && un->un_persist_errors) {
		ASSERT(un->un_pos.pmode != invalid);
	}

	if (attrib->chg_tape_direction == DIR_FORW) {
		un->un_pos.blkno += nblks;
		un->un_pos.lgclblkno += nblks;
		un->un_pos.lgclblkno += nfiles;
	} else if (attrib->chg_tape_direction == DIR_REVC) {
		un->un_pos.blkno -= nblks;
		un->un_pos.lgclblkno -= nblks;
		un->un_pos.lgclblkno -= nfiles;
	} else {
		ASSERT(0);
	}

	/* recovery disabled */
	if (ri == NULL) {
		un->un_running.pmode = invalid;
		return;
	}

	/*
	 * If we didn't just read a filemark.
	 */
	if (un->un_pos.eof != ST_EOF_PENDING) {
		ASSERT(nblks != 0 && nfiles == 0);
		/*
		 * If Previously calulated expected position does not match
		 * debug the expected position.
		 */
		if ((ri->pos.pmode != invalid) && nblks &&
		    ((un->un_pos.blkno != ri->pos.blkno) ||
		    (un->un_pos.lgclblkno != ri->pos.lgclblkno))) {
#ifdef STDEBUG
			st_print_position(ST_DEVINFO, st_label, CE_NOTE,
			    "Expected", &ri->pos);
			st_print_position(ST_DEVINFO, st_label, CE_NOTE,
			    "But Got", &un->un_pos);
#endif
			un->un_running.pmode = invalid;
		}
	} else {
		ASSERT(nfiles != 0);
		if (un->un_running.pmode != invalid) {
			/*
			 * blkno and lgclblkno already counted in
			 * st_add_recovery_info_to_pkt(). Since a block was not
			 * read and a filemark was.
			 */
			if (attrib->chg_tape_direction == DIR_FORW) {
				un->un_running.fileno++;
				un->un_running.blkno = 0;
			} else if (attrib->chg_tape_direction == DIR_REVC) {
				un->un_running.fileno--;
				un->un_running.blkno = LASTBLK;
			}
		}
	}
}

static void
st_set_state(struct scsi_tape *un, struct buf *bp)
{
	struct scsi_pkt *sp = BP_PKT(bp);
	struct uscsi_cmd *ucmd;

	ST_FUNC(ST_DEVINFO, st_set_state);

	ASSERT(mutex_owned(ST_MUTEX));
	ASSERT(bp != un->un_recov_buf);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_set_state(): eof=%x	fmneeded=%x  pkt_resid=0x%lx (%ld)\n",
	    un->un_pos.eof, un->un_fmneeded, sp->pkt_resid, sp->pkt_resid);

	if (bp != un->un_sbufp) {
#ifdef STDEBUG
		if (DEBUGGING && sp->pkt_resid) {
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "pkt_resid %ld bcount %ld\n",
			    sp->pkt_resid, bp->b_bcount);
		}
#endif
		bp->b_resid = sp->pkt_resid;
		if (geterror(bp) != EIO) {
			st_calc_bnum(un, bp, sp);
		}
		if (bp->b_flags & B_READ) {
			un->un_lastop = ST_OP_READ;
			un->un_fmneeded = 0;
		} else {
			un->un_lastop = ST_OP_WRITE;
			if (un->un_dp->options & ST_REEL) {
				un->un_fmneeded = 2;
			} else {
				un->un_fmneeded = 1;
			}
		}
		/*
		 * all is honky dory at this point, so let's
		 * readjust the throttle, to increase speed, if we
		 * have not throttled down.
		 */
		if (un->un_throttle) {
			un->un_throttle = un->un_max_throttle;
		}
	} else {
		optype new_lastop = ST_OP_NIL;
		uchar_t cmd = (uchar_t)(intptr_t)bp->b_forw;

		switch (cmd) {
		case SCMD_WRITE:
		case SCMD_WRITE_G4:
			bp->b_resid = sp->pkt_resid;
			new_lastop = ST_OP_WRITE;
			if (geterror(bp) == EIO) {
				break;
			}
			st_calc_bnum(un, bp, sp);
			if (un->un_dp->options & ST_REEL) {
				un->un_fmneeded = 2;
			} else {
				un->un_fmneeded = 1;
			}
			break;
		case SCMD_READ:
		case SCMD_READ_G4:
			bp->b_resid = sp->pkt_resid;
			new_lastop = ST_OP_READ;
			un->un_lastop = ST_OP_READ;
			if (geterror(bp) == EIO) {
				break;
			}
			st_calc_bnum(un, bp, sp);
			un->un_fmneeded = 0;
			break;
		case SCMD_WRITE_FILE_MARK_G4:
		case SCMD_WRITE_FILE_MARK:
		{
			int fmdone;

			if (un->un_pos.eof != ST_EOM) {
				un->un_pos.eof = ST_NO_EOF;
			}
			fmdone = (bp->b_bcount - bp->b_resid);
			if (fmdone > 0) {
				un->un_lastop = new_lastop = ST_OP_WEOF;
				un->un_pos.lgclblkno += fmdone;
				un->un_pos.fileno += fmdone;
				un->un_pos.blkno = 0;
			} else {
				new_lastop = ST_OP_CTL;
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "Flushed buffer\n");
			}
			if (fmdone > un->un_fmneeded) {
				un->un_fmneeded = 0;
			} else {
				un->un_fmneeded -= fmdone;
			}
			break;
		}
		case SCMD_REWIND:
			un->un_pos.eof = ST_NO_EOF;
			un->un_pos.fileno = 0;
			un->un_pos.blkno = 0;
			un->un_pos.lgclblkno = 0;
			if (un->un_pos.pmode != legacy)
				un->un_pos.pmode = legacy;
			new_lastop = ST_OP_CTL;
			un->un_restore_pos = 0;
			break;

		case SCMD_SPACE:
		case SCMD_SPACE_G4:
		{
			int64_t count;
			int64_t resid;
			int64_t done;
			cmd_attribute const *attrib;
			recov_info *ri = sp->pkt_private;

			if (ri->privatelen == sizeof (recov_info)) {
				attrib = ri->cmd_attrib;
			} else {
				attrib =
				    st_lookup_cmd_attribute(sp->pkt_cdbp[0]);
			}

			resid = (int64_t)SPACE_CNT(bp->b_resid);
			count = (int64_t)attrib->get_cnt(sp->pkt_cdbp);

			if (count >= 0) {
				done = (count - resid);
			} else {
				done = ((-count) - resid);
			}
			if (done > 0) {
				un->un_lastop = new_lastop = ST_OP_CTL;
			} else {
				new_lastop = ST_OP_CTL;
			}

			ST_SPAC(ST_DEVINFO, st_label, CE_WARN,
			    "space cmd: cdb[1] = %s\n"
			    "space data:       = 0x%lx\n"
			    "space count:      = %"PRId64"\n"
			    "space resid:      = %"PRId64"\n"
			    "spaces done:      = %"PRId64"\n"
			    "fileno before     = %d\n"
			    "blkno before      = %d\n",
			    space_strs[sp->pkt_cdbp[1] & 7],
			    bp->b_bcount,
			    count, resid, done,
			    un->un_pos.fileno, un->un_pos.blkno);

			switch (sp->pkt_cdbp[1]) {
			case SPACE_TYPE(SP_FLM):
				/* Space file forward */
				if (count >= 0) {
					if (un->un_pos.eof <= ST_EOF) {
						un->un_pos.eof = ST_NO_EOF;
					}
					un->un_pos.fileno += done;
					un->un_pos.blkno = 0;
					break;
				}
				/* Space file backward */
				if (done > un->un_pos.fileno) {
					un->un_pos.fileno = 0;
					un->un_pos.blkno = 0;
				} else {
					un->un_pos.fileno -= done;
					un->un_pos.blkno = LASTBLK;
					un->un_running.pmode = invalid;
				}
				break;
			case SPACE_TYPE(SP_BLK):
				/* Space block forward */
				if (count >= 0) {
					un->un_pos.blkno += done;
					break;
				}
				/* Space block backward */
				if (un->un_pos.eof >= ST_EOF_PENDING) {
				/*
				 * we stepped back into
				 * a previous file; we are not
				 * making an effort to pretend that
				 * we are still in the current file
				 * ie. logical == physical position
				 * and leave it to st_ioctl to correct
				 */
					if (done > un->un_pos.blkno) {
						un->un_pos.blkno = 0;
					} else {
						un->un_pos.fileno--;
						un->un_pos.blkno = LASTBLK;
						un->un_running.pmode = invalid;
					}
				} else {
					un->un_pos.blkno -= done;
				}
				break;
			case SPACE_TYPE(SP_SQFLM):
				un->un_pos.pmode = logical;
				un->un_pos.blkno = 0;
				un->un_lastop = new_lastop = ST_OP_CTL;
				break;
			case SPACE_TYPE(SP_EOD):
				un->un_pos.pmode = logical;
				un->un_pos.eof = ST_EOM;
				un->un_status = KEY_BLANK_CHECK;
				break;
			default:
				un->un_pos.pmode = invalid;
				scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "Unsupported space cmd: %s\n",
				    space_strs[sp->pkt_cdbp[1] & 7]);

				un->un_lastop = new_lastop = ST_OP_CTL;
			}

			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "after_space rs %"PRId64" fil %d blk %d\n",
			    resid, un->un_pos.fileno, un->un_pos.blkno);

			break;
		}
		case SCMD_LOAD:
			if ((bp->b_bcount & (LD_LOAD | LD_EOT)) == LD_LOAD) {
				un->un_pos.fileno = 0;
				if (un->un_pos.pmode != legacy)
					un->un_pos.pmode = legacy;
			} else {
				un->un_state = ST_STATE_OFFLINE;
				un->un_pos.pmode = invalid;

			}
			/*
			 * If we are loading or unloading we expect the media id
			 * to change. Lets make it unknown.
			 */
			if (un->un_media_id != bogusID && un->un_media_id_len) {
				kmem_free(un->un_media_id, un->un_media_id_len);
				un->un_media_id = NULL;
				un->un_media_id_len = 0;
			}
			un->un_density_known = 0;
			un->un_pos.eof = ST_NO_EOF;
			un->un_pos.blkno = 0;
			un->un_lastop = new_lastop = ST_OP_CTL;
			break;
		case SCMD_ERASE:
			un->un_pos.eof = ST_NO_EOF;
			un->un_pos.blkno = 0;
			un->un_pos.fileno = 0;
			un->un_pos.lgclblkno = 0;
			if (un->un_pos.pmode != legacy)
				un->un_pos.pmode = legacy;
			new_lastop = ST_OP_CTL;
			break;
		case SCMD_RESERVE:
			un->un_rsvd_status |= ST_RESERVE;
			un->un_rsvd_status &=
			    ~(ST_RELEASE | ST_LOST_RESERVE |
			    ST_RESERVATION_CONFLICT | ST_INITIATED_RESET);
			new_lastop = ST_OP_CTL;
			break;
		case SCMD_RELEASE:
			un->un_rsvd_status |= ST_RELEASE;
			un->un_rsvd_status &=
			    ~(ST_RESERVE | ST_LOST_RESERVE |
			    ST_RESERVATION_CONFLICT | ST_INITIATED_RESET);
			new_lastop = ST_OP_CTL;
			break;
		case SCMD_PERSISTENT_RESERVE_IN:
			ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
			    "PGR_IN command\n");
			new_lastop = ST_OP_CTL;
			break;
		case SCMD_PERSISTENT_RESERVE_OUT:
			switch (sp->pkt_cdbp[1] & ST_SA_MASK) {
			case ST_SA_SCSI3_RESERVE:
			case ST_SA_SCSI3_PREEMPT:
			case ST_SA_SCSI3_PREEMPTANDABORT:
				un->un_rsvd_status |=
				    (ST_APPLICATION_RESERVATIONS | ST_RESERVE);
				un->un_rsvd_status &= ~(ST_RELEASE |
				    ST_LOST_RESERVE | ST_RESERVATION_CONFLICT |
				    ST_INITIATED_RESET);
				ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
				    "PGR Reserve and set: entering"
				    " ST_APPLICATION_RESERVATIONS mode");
				break;
			case ST_SA_SCSI3_REGISTER:
				ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
				    "PGR Reserve register key");
				un->un_rsvd_status |= ST_INIT_RESERVE;
				break;
			case ST_SA_SCSI3_CLEAR:
				un->un_rsvd_status &= ~ST_INIT_RESERVE;
				/* FALLTHROUGH */
			case ST_SA_SCSI3_RELEASE:
				un->un_rsvd_status &=
				    ~(ST_APPLICATION_RESERVATIONS | ST_RESERVE |
				    ST_LOST_RESERVE | ST_RESERVATION_CONFLICT |
				    ST_INITIATED_RESET);
				un->un_rsvd_status |= ST_RELEASE;
				ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
				    "PGR Release and reset: exiting"
				    " ST_APPLICATION_RESERVATIONS mode");
				break;
			}
			new_lastop = ST_OP_CTL;
			break;
		case SCMD_TEST_UNIT_READY:
		case SCMD_READ_BLKLIM:
		case SCMD_REQUEST_SENSE:
		case SCMD_INQUIRY:
		case SCMD_RECOVER_BUF:
		case SCMD_MODE_SELECT:
		case SCMD_MODE_SENSE:
		case SCMD_DOORLOCK:
		case SCMD_READ_BUFFER:
		case SCMD_REPORT_DENSITIES:
		case SCMD_LOG_SELECT_G1:
		case SCMD_LOG_SENSE_G1:
		case SCMD_REPORT_LUNS:
		case SCMD_READ_ATTRIBUTE:
		case SCMD_WRITE_ATTRIBUTE:
		case SCMD_SVC_ACTION_IN_G5:
		case SCMD_SECURITY_PROTO_IN:
		case SCMD_SECURITY_PROTO_OUT:
			new_lastop = ST_OP_CTL;
			break;
		case SCMD_READ_POSITION:
			new_lastop = ST_OP_CTL;
			/*
			 * Only if the buf used was un_sbufp.
			 * Among other things the prevents read positions used
			 * as part of error recovery from messing up our
			 * current position as they will use un_recov_buf.
			 */
			if (USCSI_CMD(bp)) {
				(void) st_get_read_pos(un, bp);
			}
			break;
		case SCMD_LOCATE:
		case SCMD_LOCATE_G4:
			/* Locate makes position mode no longer legacy */
			un->un_lastop = new_lastop = ST_OP_CTL;
			break;
		case SCMD_MAINTENANCE_IN:
			switch (sp->pkt_cdbp[1]) {
			case SSVC_ACTION_GET_SUPPORTED_OPERATIONS:
			case SSVC_ACTION_SET_TARGET_PORT_GROUPS:
				new_lastop = ST_OP_CTL;
				break;
			}
			if (new_lastop != ST_OP_NIL) {
				break;
			}
		default:
			/*
			 * Unknown command, If was USCSI and USCSI_SILENT
			 * flag was not set, set position to unknown.
			 */
			if ((((ucmd = BP_UCMD(bp)) != NULL) &&
			    (ucmd->uscsi_flags & USCSI_SILENT) == 0)) {
				ST_DEBUG2(ST_DEVINFO, st_label, CE_WARN,
				    "unknown cmd 0x%X caused loss of state\n",
				    cmd);
			} else {
				/*
				 * keep the old agreement to allow unknown
				 * commands with the USCSI_SILENT set.
				 * This prevents ASSERT below.
				 */
				new_lastop = ST_OP_CTL;
				break;
			}
			/* FALLTHROUGH */
		case SCMD_WRITE_BUFFER: /* Writes new firmware to device */
			un->un_pos.pmode = invalid;
			un->un_lastop = new_lastop = ST_OP_CTL;
			break;
		}

		/* new_lastop should have been changed */
		ASSERT(new_lastop != ST_OP_NIL);

		/* If un_lastop should copy new_lastop  */
		if (((un->un_lastop == ST_OP_WRITE) ||
		    (un->un_lastop == ST_OP_WEOF)) &&
		    new_lastop != ST_OP_CTL) {
			un->un_lastop = new_lastop;
		}
	}

	/*
	 * In the st driver we have a logical and physical file position.
	 * Under BSD behavior, when you get a zero read, the logical position
	 * is before the filemark but after the last record of the file.
	 * The physical position is after the filemark. MTIOCGET should always
	 * return the logical file position.
	 *
	 * The next read gives a silent skip to the next file.
	 * Under SVR4, the logical file position remains before the filemark
	 * until the file is closed or a space operation is performed.
	 * Hence set err_resid and err_file before changing fileno if case
	 * BSD Behaviour.
	 */
	un->un_err_resid = bp->b_resid;
	COPY_POS(&un->un_err_pos, &un->un_pos);


	/*
	 * If we've seen a filemark via the last read operation
	 * advance the file counter, but mark things such that
	 * the next read operation gets a zero count. We have
	 * to put this here to handle the case of sitting right
	 * at the end of a tape file having seen the file mark,
	 * but the tape is closed and then re-opened without
	 * any further i/o. That is, the position information
	 * must be updated before a close.
	 */

	if (un->un_lastop == ST_OP_READ && un->un_pos.eof == ST_EOF_PENDING) {
		/*
		 * If we're a 1/2" tape, and we get a filemark
		 * right on block 0, *AND* we were not in the
		 * first file on the tape, and we've hit logical EOM.
		 * We'll mark the state so that later we do the
		 * right thing (in st_close(), st_strategy() or
		 * st_ioctl()).
		 *
		 */
		if ((un->un_dp->options & ST_REEL) &&
		    !(un->un_dp->options & ST_READ_IGNORE_EOFS) &&
		    un->un_pos.blkno == 0 && un->un_pos.fileno > 0) {
			un->un_pos.eof = ST_EOT_PENDING;
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "eot pending\n");
			un->un_pos.fileno++;
			un->un_pos.blkno = 0;
		} else if (BP_UCMD(bp)) {
			/*
			 * Uscsi reads have no concept of Berkley ver System IV.
			 * Counts here must match raw device.
			 * A non-full resid implies fix block mode where an
			 * attempt to read X blocks resulted in less then X.
			 */
			if (bp->b_resid != bp->b_bcount) {
				un->un_pos.eof = ST_EOF;
			} else {
				/* Read over a file mark */
				un->un_pos.fileno++;
				/* logical block is counted up elsewhere */
				/* we're before the first block in next file */
				un->un_pos.blkno = 0;
				/* EOF is no longer pending */
				un->un_pos.eof = ST_NO_EOF;
			}
		} else if (BSD_BEHAVIOR) {
			/*
			 * If the read of the filemark was a side effect
			 * of reading some blocks (i.e., data was actually
			 * read), then the EOF mark is pending and the
			 * bump into the next file awaits the next read
			 * operation (which will return a zero count), or
			 * a close or a space operation, else the bump
			 * into the next file occurs now.
			 */
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "resid=%lx, bcount=%lx\n",
			    bp->b_resid, bp->b_bcount);

			if (bp->b_resid != bp->b_bcount) {
				un->un_pos.eof = ST_EOF;
			} else {
				un->un_silent_skip = 1;
				un->un_pos.eof = ST_NO_EOF;
				un->un_pos.fileno++;
				un->un_pos.lgclblkno++;
				un->un_save_blkno = un->un_pos.blkno;
				un->un_pos.blkno = 0;
			}
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "eof of file %d, eof=%d\n",
			    un->un_pos.fileno, un->un_pos.eof);
		} else if (SVR4_BEHAVIOR) {
			/*
			 * If the read of the filemark was a side effect
			 * of reading some blocks (i.e., data was actually
			 * read), then the next read should return 0
			 */
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "resid=%lx, bcount=%lx\n",
			    bp->b_resid, bp->b_bcount);
			if (bp->b_resid == bp->b_bcount) {
				un->un_pos.eof = ST_EOF;
			}
			ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "eof of file=%d, eof=%d\n",
			    un->un_pos.fileno, un->un_pos.eof);
		}
	}
}

/*
 * set the correct un_errno, to take corner cases into consideration
 */
static void
st_set_pe_errno(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_set_pe_errno);

	ASSERT(mutex_owned(ST_MUTEX));

	/* if errno is already set, don't reset it */
	if (un->un_errno)
		return;

	/* here un_errno == 0 */
	/*
	 * if the last transfer before flushing all the
	 * waiting I/O's, was 0 (resid = count), then we
	 * want to give the user an error on all the rest,
	 * so here.  If there was a transfer, we set the
	 * resid and counts to 0, and let it drop through,
	 * giving a zero return.  the next I/O will then
	 * give an error.
	 */
	if (un->un_last_resid == un->un_last_count) {
		switch (un->un_pos.eof) {
		case ST_EOM:
			un->un_errno = ENOMEM;
			break;
		case ST_EOT:
		case ST_EOF:
			un->un_errno = EIO;
			break;
		}
	} else {
		/*
		 * we know they did not have a zero, so make
		 * sure they get one
		 */
		un->un_last_resid = un->un_last_count = 0;
	}
}


/*
 * send in a marker pkt to terminate flushing of commands by BBA (via
 * flush-on-errors) property.  The HBA will always return TRAN_ACCEPT
 */
static void
st_hba_unflush(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_hba_unflush);

	ASSERT(mutex_owned(ST_MUTEX));

	if (!un->un_flush_on_errors)
		return;

#ifdef FLUSH_ON_ERRORS

	if (!un->un_mkr_pkt) {
		un->un_mkr_pkt = scsi_init_pkt(ROUTE, NULL, (struct buf *)NULL,
		    NULL, 0, 0, 0, SLEEP_FUNC, NULL);

		/* we slept, so it must be there */
		pkt->pkt_flags |= FLAG_FLUSH_MARKER;
	}

	st_transport(un, un->un_mkr_pkt);
#endif
}

static char *
st_print_scsi_cmd(char cmd)
{
	char tmp[64];
	char *cpnt;

	cpnt = scsi_cmd_name(cmd, scsi_cmds, tmp);
	/* tmp goes out of scope on return and caller sees garbage */
	if (cpnt == tmp) {
		cpnt = "Unknown Command";
	}
	return (cpnt);
}

static void
st_print_cdb(dev_info_t *dip, char *label, uint_t level,
    char *title, char *cdb)
{
	int len = scsi_cdb_size[CDB_GROUPID(cdb[0])];
	char buf[256];
	struct scsi_tape *un;
	int instance = ddi_get_instance(dip);

	un = ddi_get_soft_state(st_state, instance);

	ST_FUNC(dip, st_print_cdb);

	/* force one line output so repeated commands are printed once */
	if ((st_debug & 0x180) == 0x100) {
		scsi_log(dip, label, level, "node %s cmd %s\n",
		    st_dev_name(un->un_dev), st_print_scsi_cmd(*cdb));
		return;
	}

	/* force one line output so repeated CDB's are printed once */
	if ((st_debug & 0x180) == 0x80) {
		st_clean_print(dip, label, level, NULL, cdb, len);
	} else {
		(void) sprintf(buf, "%s for cmd(%s)", title,
		    st_print_scsi_cmd(*cdb));
		st_clean_print(dip, label, level, buf, cdb, len);
	}
}

static void
st_clean_print(dev_info_t *dev, char *label, uint_t level,
    char *title, char *data, int len)
{
	int	i;
	int 	c;
	char	*format;
	char	buf[256];
	uchar_t	byte;

	ST_FUNC(dev, st_clean_print);


	if (title) {
		(void) sprintf(buf, "%s:\n", title);
		scsi_log(dev, label, level, "%s", buf);
		level = CE_CONT;
	}

	for (i = 0; i < len; ) {
		buf[0] = 0;
		for (c = 0; c < 8 && i < len; c++, i++) {
			byte = (uchar_t)data[i];
			if (byte < 0x10)
				format = "0x0%x ";
			else
				format = "0x%x ";
			(void) sprintf(&buf[(int)strlen(buf)], format, byte);
		}
		(void) sprintf(&buf[(int)strlen(buf)], "\n");

		scsi_log(dev, label, level, "%s\n", buf);
		level = CE_CONT;
	}
}

/*
 * Conditionally enabled debugging
 */
#ifdef	STDEBUG
static void
st_debug_cmds(struct scsi_tape *un, int com, int count, int wait)
{
	char tmpbuf[64];

	ST_FUNC(ST_DEVINFO, st_debug_cmds);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "cmd=%s count=0x%x (%d)	 %ssync\n",
	    scsi_cmd_name(com, scsi_cmds, tmpbuf),
	    count, count,
	    wait == ASYNC_CMD ? "a" : "");
}
#endif	/* STDEBUG */

/*
 * Returns pointer to name of minor node name of device 'dev'.
 */
static char *
st_dev_name(dev_t dev)
{
	struct scsi_tape *un;
	const char density[] = { 'l', 'm', 'h', 'c' };
	static char name[32];
	minor_t minor;
	int instance;
	int nprt = 0;

	minor = getminor(dev);
	instance = ((minor & 0xff80) >> 5) | (minor & 3);
	un = ddi_get_soft_state(st_state, instance);
	if (un) {
		ST_FUNC(ST_DEVINFO, st_dev_name);
	}

	name[nprt] = density[(minor & MT_DENSITY_MASK) >> 3];

	if (minor & MT_BSD) {
		name[++nprt] = 'b';
	}

	if (minor & MT_NOREWIND) {
		name[++nprt] = 'n';
	}

	/* NULL terminator */
	name[++nprt] = 0;

	return (name);
}

/*
 * Soft error reporting, so far unique to each drive
 *
 * Currently supported: exabyte and DAT soft error reporting
 */
static int
st_report_exabyte_soft_errors(dev_t dev, int flag)
{
	uchar_t *sensep;
	int amt;
	int rval = 0;
	char cdb[CDB_GROUP0], *c = cdb;
	struct uscsi_cmd *com;

	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_report_exabyte_soft_errors);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_report_exabyte_soft_errors(dev = 0x%lx, flag = %d)\n",
	    dev, flag);

	ASSERT(mutex_owned(ST_MUTEX));

	com = kmem_zalloc(sizeof (*com), KM_SLEEP);
	sensep = kmem_zalloc(TAPE_SENSE_LENGTH, KM_SLEEP);

	*c++ = SCMD_REQUEST_SENSE;
	*c++ = 0;
	*c++ = 0;
	*c++ = 0;
	*c++ = TAPE_SENSE_LENGTH;
	/*
	 * set CLRCNT (byte 5, bit 7 which clears the error counts)
	 */
	*c   = (char)0x80;

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP0;
	com->uscsi_bufaddr = (caddr_t)sensep;
	com->uscsi_buflen = TAPE_SENSE_LENGTH;
	com->uscsi_flags = USCSI_DIAGNOSE | USCSI_SILENT | USCSI_READ;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;

	rval = st_uscsi_cmd(un, com, FKIOCTL);
	if (rval || com->uscsi_status) {
		goto done;
	}

	/*
	 * was there enough data?
	 */
	amt = (int)TAPE_SENSE_LENGTH - com->uscsi_resid;

	if ((amt >= 19) && un->un_kbytes_xferred) {
		uint_t count, error_rate;
		uint_t rate;

		if (sensep[21] & CLN) {
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "Periodic head cleaning required");
		}
		if (un->un_kbytes_xferred < (EXABYTE_MIN_TRANSFER/ONE_K)) {
			goto done;
		}
		/*
		 * check if soft error reporting needs to be done.
		 */
		count = sensep[16] << 16 | sensep[17] << 8 | sensep[18];
		count &= 0xffffff;
		error_rate = (count * 100)/un->un_kbytes_xferred;

#ifdef	STDEBUG
		if (st_soft_error_report_debug) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "Exabyte Soft Error Report:\n");
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "read/write error counter: %d\n", count);
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "number of bytes transferred: %dK\n",
			    un->un_kbytes_xferred);
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "error_rate: %d%%\n", error_rate);

			if (amt >= 22) {
				scsi_log(ST_DEVINFO, st_label, CE_CONT,
				    "unit sense: 0x%b 0x%b 0x%b\n",
				    sensep[19], SENSE_19_BITS,
				    sensep[20], SENSE_20_BITS,
				    sensep[21], SENSE_21_BITS);
			}
			if (amt >= 27) {
				scsi_log(ST_DEVINFO, st_label, CE_CONT,
				    "tracking retry counter: %d\n",
				    sensep[26]);
				scsi_log(ST_DEVINFO, st_label, CE_CONT,
				    "read/write retry counter: %d\n",
				    sensep[27]);
			}
		}
#endif

		if (flag & FWRITE) {
			rate = EXABYTE_WRITE_ERROR_THRESHOLD;
		} else {
			rate = EXABYTE_READ_ERROR_THRESHOLD;
		}
		if (error_rate >= rate) {
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "Soft error rate (%d%%) during %s was too high",
			    error_rate,
			    ((flag & FWRITE) ? wrg_str : rdg_str));
			scsi_log(ST_DEVINFO, st_label, CE_CONT,
			    "Please, replace tape cartridge\n");
		}
	}

done:
	kmem_free(com, sizeof (*com));
	kmem_free(sensep, TAPE_SENSE_LENGTH);

	if (rval != 0) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "exabyte soft error reporting failed\n");
	}
	return (rval);
}

/*
 * this is very specific to Archive 4mm dat
 */
#define	ONE_GIG	(ONE_K * ONE_K * ONE_K)

static int
st_report_dat_soft_errors(dev_t dev, int flag)
{
	uchar_t *sensep;
	int amt, i;
	int rval = 0;
	char cdb[CDB_GROUP1], *c = cdb;
	struct uscsi_cmd *com;
	struct scsi_arq_status status;

	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_report_dat_soft_errors);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_report_dat_soft_errors(dev = 0x%lx, flag = %d)\n", dev, flag);

	ASSERT(mutex_owned(ST_MUTEX));

	com = kmem_zalloc(sizeof (*com), KM_SLEEP);
	sensep = kmem_zalloc(LOG_SENSE_LENGTH, KM_SLEEP);

	*c++ = SCMD_LOG_SENSE_G1;
	*c++ = 0;
	*c++ = (flag & FWRITE) ? 0x42 : 0x43;
	*c++ = 0;
	*c++ = 0;
	*c++ = 0;
	*c++ = 2;
	*c++ = 0;
	*c++ = (char)LOG_SENSE_LENGTH;
	*c   = 0;
	com->uscsi_cdb    = cdb;
	com->uscsi_cdblen  = CDB_GROUP1;
	com->uscsi_bufaddr = (caddr_t)sensep;
	com->uscsi_buflen  = LOG_SENSE_LENGTH;
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_flags   = USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;
	rval = st_uscsi_cmd(un, com, FKIOCTL);
	if (rval) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "DAT soft error reporting failed\n");
	}
	if (rval || com->uscsi_status) {
		goto done;
	}

	/*
	 * was there enough data?
	 */
	amt = (int)LOG_SENSE_LENGTH - com->uscsi_resid;

	if ((amt >= MIN_LOG_SENSE_LENGTH) && un->un_kbytes_xferred) {
		int total, retries, param_code;

		total = -1;
		retries = -1;
		amt = sensep[3] + 4;


#ifdef STDEBUG
		if (st_soft_error_report_debug) {
			(void) printf("logsense:");
			for (i = 0; i < MIN_LOG_SENSE_LENGTH; i++) {
				if (i % 16 == 0) {
					(void) printf("\t\n");
				}
				(void) printf(" %x", sensep[i]);
			}
			(void) printf("\n");
		}
#endif

		/*
		 * parse the param_codes
		 */
		if (sensep[0] == 2 || sensep[0] == 3) {
			for (i = 4; i < amt; i++) {
				param_code = (sensep[i++] << 8);
				param_code += sensep[i++];
				i++; /* skip control byte */
				if (param_code == 5) {
					if (sensep[i++] == 4) {
						total = (sensep[i++] << 24);
						total += (sensep[i++] << 16);
						total += (sensep[i++] << 8);
						total += sensep[i];
					}
				} else if (param_code == 0x8007) {
					if (sensep[i++] == 2) {
						retries = sensep[i++] << 8;
						retries += sensep[i];
					}
				} else {
					i += sensep[i];
				}
			}
		}

		/*
		 * if the log sense returned valid numbers then determine
		 * the read and write error thresholds based on the amount of
		 * data transferred
		 */

		if (total > 0 && retries > 0) {
			short normal_retries = 0;
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "total xferred (%s) =%x, retries=%x\n",
			    ((flag & FWRITE) ? wrg_str : rdg_str),
			    total, retries);

			if (flag & FWRITE) {
				if (total <=
				    WRITE_SOFT_ERROR_WARNING_THRESHOLD) {
					normal_retries =
					    DAT_SMALL_WRITE_ERROR_THRESHOLD;
				} else {
					normal_retries =
					    DAT_LARGE_WRITE_ERROR_THRESHOLD;
				}
			} else {
				if (total <=
				    READ_SOFT_ERROR_WARNING_THRESHOLD) {
					normal_retries =
					    DAT_SMALL_READ_ERROR_THRESHOLD;
				} else {
					normal_retries =
					    DAT_LARGE_READ_ERROR_THRESHOLD;
				}
			}

			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			"normal retries=%d\n", normal_retries);

			if (retries >= normal_retries) {
				scsi_log(ST_DEVINFO, st_label, CE_WARN,
				    "Soft error rate (retries = %d) during "
				    "%s was too high",  retries,
				    ((flag & FWRITE) ? wrg_str : rdg_str));
				scsi_log(ST_DEVINFO, st_label, CE_CONT,
				    "Periodic head cleaning required "
				    "and/or replace tape cartridge\n");
			}

		} else if (total == -1 || retries == -1) {
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "log sense parameter code does not make sense\n");
		}
	}

	/*
	 * reset all values
	 */
	c = cdb;
	*c++ = SCMD_LOG_SELECT_G1;
	*c++ = 2;	/* this resets all values */
	*c++ = (char)0xc0;
	*c++ = 0;
	*c++ = 0;
	*c++ = 0;
	*c++ = 0;
	*c++ = 0;
	*c++ = 0;
	*c   = 0;
	com->uscsi_bufaddr = NULL;
	com->uscsi_buflen  = 0;
	com->uscsi_flags   = USCSI_DIAGNOSE | USCSI_SILENT;
	rval = st_uscsi_cmd(un, com, FKIOCTL);
	if (rval) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "DAT soft error reset failed\n");
	}
done:
	kmem_free(com, sizeof (*com));
	kmem_free(sensep, LOG_SENSE_LENGTH);
	return (rval);
}

static int
st_report_soft_errors(dev_t dev, int flag)
{
	GET_SOFT_STATE(dev);

	ST_FUNC(ST_DEVINFO, st_report_soft_errors);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_report_soft_errors(dev = 0x%lx, flag = %d)\n", dev, flag);

	ASSERT(mutex_owned(ST_MUTEX));

	switch (un->un_dp->type) {
	case ST_TYPE_EXB8500:
	case ST_TYPE_EXABYTE:
		return (st_report_exabyte_soft_errors(dev, flag));
		/*NOTREACHED*/
	case ST_TYPE_PYTHON:
		return (st_report_dat_soft_errors(dev, flag));
		/*NOTREACHED*/
	default:
		un->un_dp->options &= ~ST_SOFT_ERROR_REPORTING;
		return (-1);
	}
}

/*
 * persistent error routines
 */

/*
 * enable persistent errors, and set the throttle appropriately, checking
 * for flush-on-errors capability
 */
static void
st_turn_pe_on(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_turn_pe_on);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_pe_on\n");
	ASSERT(mutex_owned(ST_MUTEX));

	un->un_persistence = 1;

	/*
	 * only use flush-on-errors if auto-request-sense and untagged-qing are
	 * enabled.  This will simplify the error handling for request senses
	 */

	if (un->un_arq_enabled && un->un_untagged_qing) {
		uchar_t f_o_e;

		mutex_exit(ST_MUTEX);
		f_o_e = (scsi_ifsetcap(ROUTE, "flush-on-errors", 1, 1) == 1) ?
		    1 : 0;
		mutex_enter(ST_MUTEX);

		un->un_flush_on_errors = f_o_e;
	} else {
		un->un_flush_on_errors = 0;
	}

	if (un->un_flush_on_errors)
		un->un_max_throttle = (uchar_t)st_max_throttle;
	else
		un->un_max_throttle = 1;

	if (un->un_dp->options & ST_RETRY_ON_RECOVERED_DEFERRED_ERROR)
		un->un_max_throttle = 1;

	/* this will send a marker pkt */
	st_clear_pe(un);
}

/*
 * This turns persistent errors permanently off
 */
static void
st_turn_pe_off(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_turn_pe_off);
	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_pe_off\n");
	ASSERT(mutex_owned(ST_MUTEX));

	/* turn it off for good */
	un->un_persistence = 0;

	/* this will send a marker pkt */
	st_clear_pe(un);

	/* turn off flush on error capability, if enabled */
	if (un->un_flush_on_errors) {
		mutex_exit(ST_MUTEX);
		(void) scsi_ifsetcap(ROUTE, "flush-on-errors", 0, 1);
		mutex_enter(ST_MUTEX);
	}


	un->un_flush_on_errors = 0;
}

/*
 * This clear persistent errors, allowing more commands through, and also
 * sending a marker packet.
 */
static void
st_clear_pe(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_clear_pe);
	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_pe_clear\n");
	ASSERT(mutex_owned(ST_MUTEX));

	un->un_persist_errors = 0;
	un->un_throttle = un->un_last_throttle = 1;
	un->un_errno = 0;
	st_hba_unflush(un);
}

/*
 * This will flag persistent errors, shutting everything down, if the
 * application had enabled persistent errors via MTIOCPERSISTENT
 */
static void
st_set_pe_flag(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_set_pe_flag);
	ASSERT(mutex_owned(ST_MUTEX));

	if (un->un_persistence) {
		ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG, "st_pe_flag\n");
		un->un_persist_errors = 1;
		un->un_throttle = un->un_last_throttle = 0;
		cv_broadcast(&un->un_sbuf_cv);
	}
}

static int
st_do_reserve(struct scsi_tape *un)
{
	int rval;
	int was_lost = un->un_rsvd_status & ST_LOST_RESERVE;

	ST_FUNC(ST_DEVINFO, st_do_reserve);

	/*
	 * Issue a Throw-Away reserve command to clear the
	 * check condition.
	 * If the current behaviour of reserve/release is to
	 * hold reservation across opens , and if a Bus reset
	 * has been issued between opens then this command
	 * would set the ST_LOST_RESERVE flags in rsvd_status.
	 * In this case return an EACCES so that user knows that
	 * reservation has been lost in between opens.
	 * If this error is not returned and we continue with
	 * successful open , then user may think position of the
	 * tape is still the same but inreality we would rewind the
	 * tape and continue from BOT.
	 */
	rval = st_reserve_release(un, ST_RESERVE, st_uscsi_cmd);
	if (rval) {
		if ((un->un_rsvd_status & ST_LOST_RESERVE_BETWEEN_OPENS) ==
		    ST_LOST_RESERVE_BETWEEN_OPENS) {
			un->un_rsvd_status &= ~(ST_LOST_RESERVE | ST_RESERVE);
			un->un_errno = EACCES;
			return (EACCES);
		}
		rval = st_reserve_release(un, ST_RESERVE, st_uscsi_cmd);
	}
	if (rval == 0) {
		un->un_rsvd_status |= ST_INIT_RESERVE;
	}
	if (was_lost) {
		un->un_running.pmode = invalid;
	}

	return (rval);
}

static int
st_check_cdb_for_need_to_reserve(struct scsi_tape *un, uchar_t *cdb)
{
	int rval;
	cmd_attribute const *attrib;

	ST_FUNC(ST_DEVINFO, st_check_cdb_for_need_to_reserve);

	/*
	 * If already reserved no need to do it again.
	 * Also if Reserve and Release are disabled Just return.
	 */
	if ((un->un_rsvd_status & (ST_APPLICATION_RESERVATIONS)) ||
	    ((un->un_rsvd_status & (ST_RESERVE | ST_LOST_RESERVE)) ==
	    ST_RESERVE) || (un->un_dp->options & ST_NO_RESERVE_RELEASE)) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_NOTE,
		    "st_check_cdb_for_need_to_reserve() reserve unneeded %s",
		    st_print_scsi_cmd((uchar_t)cdb[0]));
		return (0);
	}

	/* See if command is on the list */
	attrib = st_lookup_cmd_attribute(cdb[0]);

	if (attrib == NULL) {
		rval = 1; /* Not found, when in doubt reserve */
	} else if ((attrib->requires_reserve) != 0) {
		rval = 1;
	} else if ((attrib->reserve_byte) != 0) {
		/*
		 * cmd is on list.
		 * if byte is zero always allowed.
		 */
		rval = 1;
	} else if (((cdb[attrib->reserve_byte]) &
	    (attrib->reserve_mask)) != 0) {
		rval = 1;
	} else {
		rval = 0;
	}

	if (rval) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_NOTE,
		    "Command %s requires reservation",
		    st_print_scsi_cmd(cdb[0]));

		rval = st_do_reserve(un);
	}

	return (rval);
}

static int
st_check_cmd_for_need_to_reserve(struct scsi_tape *un, uchar_t cmd, int cnt)
{
	int rval;
	cmd_attribute const *attrib;

	ST_FUNC(ST_DEVINFO, st_check_cmd_for_need_to_reserve);

	/*
	 * Do not reserve when already reserved, when not supported or when
	 * auto-rewinding on device closure.
	 */
	if ((un->un_rsvd_status & (ST_APPLICATION_RESERVATIONS)) ||
	    ((un->un_rsvd_status & (ST_RESERVE | ST_LOST_RESERVE)) ==
	    ST_RESERVE) || (un->un_dp->options & ST_NO_RESERVE_RELEASE) ||
	    ((un->un_state == ST_STATE_CLOSING) && (cmd == SCMD_REWIND))) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_NOTE,
		    "st_check_cmd_for_need_to_reserve() reserve unneeded %s",
		    st_print_scsi_cmd(cmd));
		return (0);
	}

	/* search for this command on the list */
	attrib = st_lookup_cmd_attribute(cmd);

	if (attrib == NULL) {
		rval = 1; /* Not found, when in doubt reserve */
	} else if ((attrib->requires_reserve) != 0) {
		rval = 1;
	} else if ((attrib->reserve_byte) != 0) {
		/*
		 * cmd is on list.
		 * if byte is zero always allowed.
		 */
		rval = 1;
	} else if (((attrib->reserve_mask) & cnt) != 0) {
		rval = 1;
	} else {
		rval = 0;
	}

	if (rval) {
		ST_DEBUG6(ST_DEVINFO, st_label, CE_NOTE,
		    "Cmd %s requires reservation", st_print_scsi_cmd(cmd));

		rval = st_do_reserve(un);
	}

	return (rval);
}

static int
st_reserve_release(struct scsi_tape *un, int cmd, ubufunc_t ubf)
{
	struct uscsi_cmd	uscsi_cmd;
	int			rval;
	char			cdb[CDB_GROUP0];
	struct scsi_arq_status	stat;



	ST_FUNC(ST_DEVINFO, st_reserve_release);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_reserve_release: %s \n",
	    (cmd == ST_RELEASE)?  "Releasing":"Reserving");

	bzero(&cdb, CDB_GROUP0);
	if (cmd == ST_RELEASE) {
		cdb[0] = SCMD_RELEASE;
	} else {
		cdb[0] = SCMD_RESERVE;
	}
	bzero(&uscsi_cmd, sizeof (struct uscsi_cmd));
	uscsi_cmd.uscsi_flags = USCSI_WRITE | USCSI_RQENABLE;
	uscsi_cmd.uscsi_cdb = cdb;
	uscsi_cmd.uscsi_cdblen = CDB_GROUP0;
	uscsi_cmd.uscsi_timeout = un->un_dp->non_motion_timeout;
	uscsi_cmd.uscsi_rqbuf = (caddr_t)&stat;
	uscsi_cmd.uscsi_rqlen = sizeof (stat);

	rval = ubf(un, &uscsi_cmd, FKIOCTL);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_reserve_release: rval(1)=%d\n", rval);

	if (rval) {
		if (uscsi_cmd.uscsi_status == STATUS_RESERVATION_CONFLICT) {
			rval = EACCES;
		}
		/*
		 * dynamically turn off reserve/release support
		 * in case of drives which do not support
		 * reserve/release command(ATAPI drives).
		 */
		if (un->un_status == KEY_ILLEGAL_REQUEST) {
			if ((un->un_dp->options & ST_NO_RESERVE_RELEASE) == 0) {
				un->un_dp->options |= ST_NO_RESERVE_RELEASE;
				ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "Tape unit does not support "
				    "reserve/release \n");
			}
			rval = 0;
		}
	}
	return (rval);
}

static int
st_take_ownership(struct scsi_tape *un, ubufunc_t ubf)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_take_ownership);

	ASSERT(mutex_owned(ST_MUTEX));

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_take_ownership: Entering ...\n");


	rval = st_reserve_release(un, ST_RESERVE, ubf);
	/*
	 * XXX -> Should reset be done only if we get EACCES.
	 * .
	 */
	if (rval) {
		if (st_reset(un, RESET_LUN) == 0) {
			return (EIO);
		}
		un->un_rsvd_status &=
		    ~(ST_LOST_RESERVE | ST_RESERVATION_CONFLICT);

		mutex_exit(ST_MUTEX);
		delay(drv_usectohz(ST_RESERVATION_DELAY));
		mutex_enter(ST_MUTEX);
		/*
		 * remove the check condition.
		 */
		(void) st_reserve_release(un, ST_RESERVE, ubf);
		rval = st_reserve_release(un, ST_RESERVE, ubf);
		if (rval != 0) {
			if ((st_reserve_release(un, ST_RESERVE, ubf))
			    != 0) {
				rval = (un->un_rsvd_status &
				    ST_RESERVATION_CONFLICT) ? EACCES : EIO;
				return (rval);
			}
		}
		/*
		 * Set tape state to ST_STATE_OFFLINE , in case if
		 * the user wants to continue and start using
		 * the tape.
		 */
		un->un_state = ST_STATE_OFFLINE;
		un->un_rsvd_status |= ST_INIT_RESERVE;
	}
	return (rval);
}

static int
st_create_errstats(struct scsi_tape *un, int instance)
{
	char	kstatname[KSTAT_STRLEN];

	ST_FUNC(ST_DEVINFO, st_create_errstats);

	/*
	 * Create device error kstats
	 */

	if (un->un_errstats == (kstat_t *)0) {
		(void) sprintf(kstatname, "st%d,err", instance);
		un->un_errstats = kstat_create("sterr", instance, kstatname,
		    "device_error", KSTAT_TYPE_NAMED,
		    sizeof (struct st_errstats) / sizeof (kstat_named_t),
		    KSTAT_FLAG_PERSISTENT);

		if (un->un_errstats) {
			struct st_errstats	*stp;

			stp = (struct st_errstats *)un->un_errstats->ks_data;
			kstat_named_init(&stp->st_softerrs, "Soft Errors",
			    KSTAT_DATA_ULONG);
			kstat_named_init(&stp->st_harderrs, "Hard Errors",
			    KSTAT_DATA_ULONG);
			kstat_named_init(&stp->st_transerrs, "Transport Errors",
			    KSTAT_DATA_ULONG);
			kstat_named_init(&stp->st_vid, "Vendor",
			    KSTAT_DATA_CHAR);
			kstat_named_init(&stp->st_pid, "Product",
			    KSTAT_DATA_CHAR);
			kstat_named_init(&stp->st_revision, "Revision",
			    KSTAT_DATA_CHAR);
			kstat_named_init(&stp->st_serial, "Serial No",
			    KSTAT_DATA_CHAR);
			un->un_errstats->ks_private = un;
			un->un_errstats->ks_update = nulldev;
			kstat_install(un->un_errstats);
			/*
			 * Fill in the static data
			 */
			(void) strncpy(&stp->st_vid.value.c[0],
			    ST_INQUIRY->inq_vid, 8);
			/*
			 * XXX:  Emulex MT-02 (and emulators) predates
			 *	 SCSI-1 and has no vid & pid inquiry data.
			 */
			if (ST_INQUIRY->inq_len != 0) {
				(void) strncpy(&stp->st_pid.value.c[0],
				    ST_INQUIRY->inq_pid, 16);
				(void) strncpy(&stp->st_revision.value.c[0],
				    ST_INQUIRY->inq_revision, 4);
			}
		}
	}
	return (0);
}

static int
st_validate_tapemarks(struct scsi_tape *un, ubufunc_t ubf, tapepos_t *pos)
{
	int rval;
	bufunc_t bf = (ubf == st_uscsi_rcmd) ? st_rcmd : st_cmd;

	ST_FUNC(ST_DEVINFO, st_validate_tapemarks);

	ASSERT(MUTEX_HELD(&un->un_sd->sd_mutex));
	ASSERT(mutex_owned(ST_MUTEX));

	/* Can't restore an invalid position */
	if (pos->pmode == invalid) {
		return (4);
	}

	/*
	 * Assumtions:
	 *	If a position was read and is in logical position mode.
	 *	If a drive supports read position it supports locate.
	 *	If the read position type is not NO_POS. even though
	 *	   a read position make not have been attemped yet.
	 *
	 *	The drive can locate to the position.
	 */
	if (pos->pmode == logical || un->un_read_pos_type != NO_POS) {
		/*
		 * If position mode is logical or legacy mode try
		 * to locate there as it is faster.
		 * If it fails try the old way.
		 */
		scsi_log(ST_DEVINFO, st_label, CE_NOTE,
		    "Restoring tape position to lgclblkbo=0x%"PRIx64"....",
		    pos->lgclblkno);

		if (st_logical_block_locate(un, st_uscsi_cmd, &un->un_pos,
		    pos->lgclblkno, pos->partition) == 0) {
			/* Assume we are there copy rest of position back */
			if (un->un_pos.lgclblkno == pos->lgclblkno) {
				COPY_POS(&un->un_pos, pos);
			}
			return (0);
		}

		/*
		 * If logical block locate failed to restore a logical
		 * position, can't recover.
		 */
		if (pos->pmode == logical) {
			return (-1);
		}
	}


	scsi_log(ST_DEVINFO, st_label, CE_NOTE,
	    "Restoring tape position at fileno=%x, blkno=%x....",
	    pos->fileno, pos->blkno);

	/*
	 * Rewind ? Oh yeah, Fidelity has got the STK F/W changed
	 * so as not to rewind tape on RESETS: Gee, Has life ever
	 * been simple in tape land ?
	 */
	rval = bf(un, SCMD_REWIND, 0, SYNC_CMD);
	if (rval) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "Failed to restore the last file and block position: In"
		    " this state, Tape will be loaded at BOT during next open");
		un->un_pos.pmode = invalid;
		return (rval);
	}

	/* If the position was as the result of back space file */
	if (pos->blkno > (INF / 2)) {
		/* Go one extra file forward */
		pos->fileno++;
		/* Figure how many blocks to back into the previous file */
		pos->blkno = -(INF - pos->blkno);
	}

	/* Go to requested fileno */
	if (pos->fileno) {
		rval = st_cmd(un, SCMD_SPACE, Fmk(pos->fileno), SYNC_CMD);
		if (rval) {
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "Failed to restore the last file position: In this "
			    " state, Tape will be loaded at BOT during next"
			    " open %d", __LINE__);
			un->un_pos.pmode = invalid;
			pos->pmode = invalid;
			return (rval);
		}
	}

	/*
	 * If backing into a file we already did an extra file forward.
	 * Now we have to back over the filemark to get to the end of
	 * the previous file. The blkno has been ajusted to a negative
	 * value so we will get to the expected location.
	 */
	if (pos->blkno) {
		rval = bf(un, SCMD_SPACE, Fmk(-1), SYNC_CMD);
		if (rval) {
			scsi_log(ST_DEVINFO, st_label, CE_WARN,
			    "Failed to restore the last file position: In this "
			    " state, Tape will be loaded at BOT during next"
			    " open %d", __LINE__);
			un->un_pos.pmode = invalid;
			pos->pmode = invalid;
			return (rval);
		}
	}

	/*
	 * The position mode, block and fileno should be correct,
	 * This updates eof and logical position information.
	 */
	un->un_pos.eof = pos->eof;
	un->un_pos.lgclblkno = pos->lgclblkno;

	return (0);
}

/*
 * check sense key, ASC, ASCQ in order to determine if the tape needs
 * to be ejected
 */

static int
st_check_asc_ascq(struct scsi_tape *un)
{
	struct scsi_extended_sense *sensep = ST_RQSENSE;
	struct tape_failure_code   *code;

	ST_FUNC(ST_DEVINFO, st_check_asc_ascq);

	for (code = st_tape_failure_code; code->key != 0xff; code++) {
		if ((code->key  == sensep->es_key) &&
		    (code->add_code  == sensep->es_add_code) &&
		    (code->qual_code == sensep->es_qual_code))
			return (1);
	}
	return (0);
}

/*
 * st_logpage_supported() sends a Log Sense command with
 * page code = 0 = Supported Log Pages Page to the device,
 * to see whether the page 'page' is supported.
 * Return values are:
 * -1 if the Log Sense command fails
 * 0 if page is not supported
 * 1 if page is supported
 */

static int
st_logpage_supported(struct scsi_tape *un, uchar_t page)
{
	uchar_t *sp, *sensep;
	unsigned length;
	struct uscsi_cmd *com;
	struct scsi_arq_status status;
	int rval;
	char cdb[CDB_GROUP1] = {
		SCMD_LOG_SENSE_G1,
		0,
		SUPPORTED_LOG_PAGES_PAGE,
		0,
		0,
		0,
		0,
		0,
		(char)LOG_SENSE_LENGTH,
		0
	};

	ST_FUNC(ST_DEVINFO, st_logpage_supported);

	ASSERT(mutex_owned(ST_MUTEX));

	com = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);
	sensep = kmem_zalloc(LOG_SENSE_LENGTH, KM_SLEEP);

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP1;
	com->uscsi_bufaddr = (caddr_t)sensep;
	com->uscsi_buflen = LOG_SENSE_LENGTH;
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_flags =
	    USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;
	rval = st_uscsi_cmd(un, com, FKIOCTL);
	if (rval || com->uscsi_status) {
		/* uscsi-command failed */
		rval = -1;
	} else {

		sp = sensep + 3;

		for (length = *sp++; length > 0; length--, sp++) {

			if (*sp == page) {
				rval = 1;
				break;
			}
		}
	}
	kmem_free(com, sizeof (struct uscsi_cmd));
	kmem_free(sensep, LOG_SENSE_LENGTH);
	return (rval);
}


/*
 * st_check_clean_bit() gets the status of the tape's cleaning bit.
 *
 * If the device does support the TapeAlert log page, then the cleaning bit
 * information will be read from this page. Otherwise we will see if one of
 * ST_CLN_TYPE_1, ST_CLN_TYPE_2 or ST_CLN_TYPE_3 is set in the properties of
 * the device, which means, that we can get the cleaning bit information via
 * a RequestSense command.
 * If both methods of getting cleaning bit information are not supported
 * st_check_clean_bit() will return with 0. Otherwise st_check_clean_bit()
 * returns with
 * - MTF_TAPE_CLN_SUPPORTED if cleaning bit is not set or
 * - MTF_TAPE_CLN_SUPPORTED | MTF_TAPE_HEAD_DIRTY if cleaning bit is set.
 * If the call to st_uscsi_cmd() to do the Log Sense or the Request Sense
 * command fails, or if the amount of Request Sense data is not enough, then
 *  st_check_clean_bit() returns with -1.
 */

static int
st_check_clean_bit(struct scsi_tape *un)
{
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_check_clean_bit);

	ASSERT(mutex_owned(ST_MUTEX));

	if (un->un_HeadClean & TAPE_ALERT_NOT_SUPPORTED) {
		return (rval);
	}

	if (un->un_HeadClean == TAPE_ALERT_SUPPORT_UNKNOWN) {

		rval = st_logpage_supported(un, TAPE_SEQUENTIAL_PAGE);
		if (rval == -1) {
			return (0);
		}
		if (rval == 1) {

			un->un_HeadClean |= TAPE_SEQUENTIAL_SUPPORTED;
		}

		rval = st_logpage_supported(un, TAPE_ALERT_PAGE);
		if (rval == -1) {
			return (0);
		}
		if (rval == 1) {

			un->un_HeadClean |= TAPE_ALERT_SUPPORTED;
		}

		if (un->un_HeadClean == TAPE_ALERT_SUPPORT_UNKNOWN) {

			un->un_HeadClean = TAPE_ALERT_NOT_SUPPORTED;
		}
	}

	rval = 0;

	if (un->un_HeadClean & TAPE_SEQUENTIAL_SUPPORTED) {

		rval = st_check_sequential_clean_bit(un);
		if (rval == -1) {
			return (0);
		}
	}

	if ((rval == 0) && (un->un_HeadClean & TAPE_ALERT_SUPPORTED)) {

		rval = st_check_alert_flags(un);
		if (rval == -1) {
			return (0);
		}
	}

	if ((rval == 0) && (un->un_dp->options & ST_CLN_MASK)) {

		rval = st_check_sense_clean_bit(un);
		if (rval == -1) {
			return (0);
		}
	}

	/*
	 * If found a supported means to check need to clean.
	 */
	if (rval & MTF_TAPE_CLN_SUPPORTED) {

		/*
		 * head needs to be cleaned.
		 */
		if (rval & MTF_TAPE_HEAD_DIRTY) {

			/*
			 * Print log message only first time
			 * found needing cleaned.
			 */
			if ((un->un_HeadClean & TAPE_PREVIOUSLY_DIRTY) == 0) {

				scsi_log(ST_DEVINFO, st_label, CE_WARN,
				    "Periodic head cleaning required");

				un->un_HeadClean |= TAPE_PREVIOUSLY_DIRTY;
			}

		} else {

			un->un_HeadClean &= ~TAPE_PREVIOUSLY_DIRTY;
		}
	}

	return (rval);
}


static int
st_check_sequential_clean_bit(struct scsi_tape *un)
{
	int rval;
	int ix;
	ushort_t parameter;
	struct uscsi_cmd *cmd;
	struct log_sequential_page *sp;
	struct log_sequential_page_parameter *prm;
	struct scsi_arq_status status;
	char cdb[CDB_GROUP1] = {
		SCMD_LOG_SENSE_G1,
		0,
		TAPE_SEQUENTIAL_PAGE | CURRENT_CUMULATIVE_VALUES,
		0,
		0,
		0,
		0,
		(char)(sizeof (struct log_sequential_page) >> 8),
		(char)(sizeof (struct log_sequential_page)),
		0
	};

	ST_FUNC(ST_DEVINFO, st_check_sequential_clean_bit);

	cmd = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);
	sp  = kmem_zalloc(sizeof (struct log_sequential_page), KM_SLEEP);

	cmd->uscsi_flags   =
	    USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;
	cmd->uscsi_timeout = un->un_dp->non_motion_timeout;
	cmd->uscsi_cdb	   = cdb;
	cmd->uscsi_cdblen  = CDB_GROUP1;
	cmd->uscsi_bufaddr = (caddr_t)sp;
	cmd->uscsi_buflen  = sizeof (struct log_sequential_page);
	cmd->uscsi_rqlen   = sizeof (status);
	cmd->uscsi_rqbuf   = (caddr_t)&status;

	rval = st_uscsi_cmd(un, cmd, FKIOCTL);

	if (rval || cmd->uscsi_status || cmd->uscsi_resid) {

		rval = -1;

	} else if (sp->log_page.code != TAPE_SEQUENTIAL_PAGE) {

		rval = -1;
	}

	prm = &sp->param[0];

	for (ix = 0; rval == 0 && ix < TAPE_SEQUENTIAL_PAGE_PARA; ix++) {

		if (prm->log_param.length == 0) {
			break;
		}

		parameter = (((prm->log_param.pc_hi << 8) & 0xff00) +
		    (prm->log_param.pc_lo & 0xff));

		if (parameter == SEQUENTIAL_NEED_CLN) {

			rval = MTF_TAPE_CLN_SUPPORTED;
			if (prm->param_value[prm->log_param.length - 1]) {

				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "sequential log says head dirty\n");
				rval |= MTF_TAPE_HEAD_DIRTY;
			}
		}
		prm = (struct log_sequential_page_parameter *)
		    &prm->param_value[prm->log_param.length];
	}

	kmem_free(cmd, sizeof (struct uscsi_cmd));
	kmem_free(sp,  sizeof (struct log_sequential_page));

	return (rval);
}


static int
st_check_alert_flags(struct scsi_tape *un)
{
	struct st_tape_alert *ta;
	struct uscsi_cmd *com;
	struct scsi_arq_status status;
	unsigned ix, length;
	int rval;
	tape_alert_flags flag;
	char cdb[CDB_GROUP1] = {
		SCMD_LOG_SENSE_G1,
		0,
		TAPE_ALERT_PAGE | CURRENT_THRESHOLD_VALUES,
		0,
		0,
		0,
		0,
		(char)(sizeof (struct st_tape_alert) >> 8),
		(char)(sizeof (struct st_tape_alert)),
		0
	};

	ST_FUNC(ST_DEVINFO, st_check_alert_clean_bit);

	com = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);
	ta  = kmem_zalloc(sizeof (struct st_tape_alert), KM_SLEEP);

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP1;
	com->uscsi_bufaddr = (caddr_t)ta;
	com->uscsi_buflen = sizeof (struct st_tape_alert);
	com->uscsi_rqlen = sizeof (status);
	com->uscsi_rqbuf = (caddr_t)&status;
	com->uscsi_flags =
	    USCSI_DIAGNOSE | USCSI_RQENABLE | USCSI_READ;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;

	rval = st_uscsi_cmd(un, com, FKIOCTL);

	if (rval || com->uscsi_status || com->uscsi_resid) {

		rval = -1; /* uscsi-command failed */

	} else if (ta->log_page.code != TAPE_ALERT_PAGE) {

		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		"Not Alert Log Page returned 0x%X\n", ta->log_page.code);
		rval = -1;
	}

	length = (ta->log_page.length_hi << 8) + ta->log_page.length_lo;


	if (length != TAPE_ALERT_PARAMETER_LENGTH) {

		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "TapeAlert length %d\n", length);
	}


	for (ix = 0; ix < TAPE_ALERT_MAX_PARA; ix++) {

		/*
		 * if rval is bad before the first pass don't bother
		 */
		if (ix == 0 && rval != 0) {

			break;
		}

		flag = ((ta->param[ix].log_param.pc_hi << 8) +
		    ta->param[ix].log_param.pc_lo);

		if ((ta->param[ix].param_value & 1) == 0) {
			continue;
		}
		/*
		 * check to see if current parameter is of interest.
		 * CLEAN_FOR_ERRORS is vendor specific to 9840 9940 stk's.
		 */
		if ((flag == TAF_CLEAN_NOW) ||
		    (flag == TAF_CLEAN_PERIODIC) ||
		    ((flag == CLEAN_FOR_ERRORS) &&
		    (un->un_dp->type == ST_TYPE_STK9840))) {

			rval = MTF_TAPE_CLN_SUPPORTED;


			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "alert_page drive needs clean %d\n", flag);
			un->un_HeadClean |= TAPE_ALERT_STILL_DIRTY;
			rval |= MTF_TAPE_HEAD_DIRTY;

		} else if (flag == TAF_CLEANING_MEDIA) {

			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "alert_page drive was cleaned\n");
			un->un_HeadClean &= ~TAPE_ALERT_STILL_DIRTY;
		}

	}

	/*
	 * Report it as dirty till we see it cleaned
	 */
	if (un->un_HeadClean & TAPE_ALERT_STILL_DIRTY) {

		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "alert_page still dirty\n");
		rval |= MTF_TAPE_HEAD_DIRTY;
	}

	kmem_free(com, sizeof (struct uscsi_cmd));
	kmem_free(ta,  sizeof (struct st_tape_alert));

	return (rval);
}


static int
st_check_sense_clean_bit(struct scsi_tape *un)
{
	uchar_t *sensep;
	char cdb[CDB_GROUP0];
	struct uscsi_cmd *com;
	ushort_t byte_pos;
	uchar_t bit_mask;
	unsigned length;
	int index;
	int rval;

	ST_FUNC(ST_DEVINFO, st_check_sense_clean_bit);

	/*
	 * Since this tape does not support Tape Alert,
	 * we now try to get the cleanbit status via
	 * Request Sense.
	 */

	if ((un->un_dp->options & ST_CLN_MASK) == ST_CLN_TYPE_1) {

		index = 0;

	} else if ((un->un_dp->options & ST_CLN_MASK) == ST_CLN_TYPE_2) {

		index = 1;

	} else if ((un->un_dp->options & ST_CLN_MASK) == ST_CLN_TYPE_3) {

		index = 2;

	} else {

		return (-1);
	}

	byte_pos  = st_cln_bit_position[index].cln_bit_byte;
	bit_mask  = st_cln_bit_position[index].cln_bit_mask;
	length = byte_pos + 1;

	com    = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);
	sensep = kmem_zalloc(length, KM_SLEEP);

	cdb[0] = SCMD_REQUEST_SENSE;
	cdb[1] = 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = (char)length;
	cdb[5] = 0;

	com->uscsi_cdb = cdb;
	com->uscsi_cdblen = CDB_GROUP0;
	com->uscsi_bufaddr = (caddr_t)sensep;
	com->uscsi_buflen = length;
	com->uscsi_flags =
	    USCSI_DIAGNOSE | USCSI_SILENT | USCSI_READ;
	com->uscsi_timeout = un->un_dp->non_motion_timeout;

	rval = st_uscsi_cmd(un, com, FKIOCTL);

	if (rval || com->uscsi_status || com->uscsi_resid) {

		rval = -1;

	} else {

		rval = MTF_TAPE_CLN_SUPPORTED;
		if ((sensep[byte_pos] & bit_mask) == bit_mask) {

			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "sense data says head dirty\n");
			rval |= MTF_TAPE_HEAD_DIRTY;
		}
	}

	kmem_free(com, sizeof (struct uscsi_cmd));
	kmem_free(sensep, length);
	return (rval);
}

/*
 * st_clear_unit_attention
 *
 *  	run test unit ready's to clear out outstanding
 * 	unit attentions.
 * 	returns zero for SUCCESS or the errno from st_cmd call
 */
static int
st_clear_unit_attentions(dev_t dev_instance, int max_trys)
{
	int	i    = 0;
	int	rval;

	GET_SOFT_STATE(dev_instance);
	ST_FUNC(ST_DEVINFO, st_clear_unit_attentions);

	do {
		rval = st_cmd(un, SCMD_TEST_UNIT_READY, 0, SYNC_CMD);
	} while ((rval != 0) && (rval != ENXIO) && (++i < max_trys));
	return (rval);
}

static void
st_calculate_timeouts(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_calculate_timeouts);

	if (un->un_dp->non_motion_timeout == 0) {
		if (un->un_dp->options & ST_LONG_TIMEOUTS) {
			un->un_dp->non_motion_timeout =
			    st_io_time * st_long_timeout_x;
		} else {
			un->un_dp->non_motion_timeout = (ushort_t)st_io_time;
		}
	}

	if (un->un_dp->io_timeout == 0) {
		if (un->un_dp->options & ST_LONG_TIMEOUTS) {
			un->un_dp->io_timeout = st_io_time * st_long_timeout_x;
		} else {
			un->un_dp->io_timeout = (ushort_t)st_io_time;
		}
	}

	if (un->un_dp->rewind_timeout == 0) {
		if (un->un_dp->options & ST_LONG_TIMEOUTS) {
			un->un_dp->rewind_timeout =
			    st_space_time * st_long_timeout_x;
		} else {
			un->un_dp->rewind_timeout = (ushort_t)st_space_time;
		}
	}

	if (un->un_dp->space_timeout == 0) {
		if (un->un_dp->options & ST_LONG_TIMEOUTS) {
			un->un_dp->space_timeout =
			    st_space_time * st_long_timeout_x;
		} else {
			un->un_dp->space_timeout = (ushort_t)st_space_time;
		}
	}

	if (un->un_dp->load_timeout == 0) {
		if (un->un_dp->options & ST_LONG_TIMEOUTS) {
			un->un_dp->load_timeout =
			    st_space_time * st_long_timeout_x;
		} else {
			un->un_dp->load_timeout = (ushort_t)st_space_time;
		}
	}

	if (un->un_dp->unload_timeout == 0) {
		if (un->un_dp->options & ST_LONG_TIMEOUTS) {
			un->un_dp->unload_timeout =
			    st_space_time * st_long_timeout_x;
		} else {
			un->un_dp->unload_timeout = (ushort_t)st_space_time;
		}
	}

	if (un->un_dp->erase_timeout == 0) {
		if (un->un_dp->options & ST_LONG_ERASE) {
			un->un_dp->erase_timeout =
			    st_space_time * st_long_space_time_x;
		} else {
			un->un_dp->erase_timeout = (ushort_t)st_space_time;
		}
	}
}


static writablity
st_is_not_wormable(struct scsi_tape *un)
{
	ST_FUNC(ST_DEVINFO, st_is_not_wormable);
	return (RDWR);
}

static writablity
st_is_hp_dat_tape_worm(struct scsi_tape *un)
{
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_hp_dat_tape_worm);

	/* Mode sense should be current */
	if (un->un_mspl->media_type == 1) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has WORM media loaded\n");
		wrt = WORM;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has non WORM media loaded\n");
		wrt = RDWR;
	}
	return (wrt);
}

#define	HP_DAT_INQUIRY 0x4A
static writablity
st_is_hp_dat_worm(struct scsi_tape *un)
{
	char *buf;
	int result;
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_hp_dat_worm);

	buf = kmem_zalloc(HP_DAT_INQUIRY, KM_SLEEP);

	result = st_get_special_inquiry(un, HP_DAT_INQUIRY, buf, 0);

	if (result != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Read Standard Inquiry for WORM support failed");
		wrt = FAILED;
	} else if ((buf[40] & 1) == 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive is NOT WORMable\n");
		/* This drive doesn't support it so don't check again */
		un->un_dp->options &= ~ST_WORMABLE;
		wrt = RDWR;
		un->un_wormable = st_is_not_wormable;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive supports WORM version %d\n", buf[40] >> 1);
		un->un_wormable = st_is_hp_dat_tape_worm;
		wrt = un->un_wormable(un);
	}

	kmem_free(buf, HP_DAT_INQUIRY);

	/*
	 * If drive doesn't support it no point in checking further.
	 */
	return (wrt);
}

static writablity
st_is_hp_lto_tape_worm(struct scsi_tape *un)
{
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_hp_lto_tape_worm);

	/* Mode sense should be current */
	switch (un->un_mspl->media_type) {
	case 0x00:
		switch (un->un_mspl->density) {
		case 0x40:
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drive has standard Gen I media loaded\n");
			break;
		case 0x42:
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drive has standard Gen II media loaded\n");
			break;
		case 0x44:
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drive has standard Gen III media loaded\n");
			break;
		case 0x46:
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drive has standard Gen IV media loaded\n");
			break;
		default:
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drive has standard unknown 0x%X media loaded\n",
			    un->un_mspl->density);
		}
		wrt = RDWR;
		break;
	case 0x01:
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has WORM medium loaded\n");
		wrt = WORM;
		break;
	case 0x80:
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has CD-ROM emulation medium loaded\n");
		wrt = WORM;
		break;
	default:
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has an unexpected medium type 0x%X loaded\n",
		    un->un_mspl->media_type);
		wrt = RDWR;
	}

	return (wrt);
}

#define	LTO_REQ_INQUIRY 44
static writablity
st_is_hp_lto_worm(struct scsi_tape *un)
{
	char *buf;
	int result;
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_hp_lto_worm);

	buf = kmem_zalloc(LTO_REQ_INQUIRY, KM_SLEEP);

	result = st_get_special_inquiry(un, LTO_REQ_INQUIRY, buf, 0);

	if (result != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Read Standard Inquiry for WORM support failed");
		wrt = FAILED;
	} else if ((buf[40] & 1) == 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive is NOT WORMable\n");
		/* This drive doesn't support it so don't check again */
		un->un_dp->options &= ~ST_WORMABLE;
		wrt = RDWR;
		un->un_wormable = st_is_not_wormable;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive supports WORM version %d\n", buf[40] >> 1);
		un->un_wormable = st_is_hp_lto_tape_worm;
		wrt = un->un_wormable(un);
	}

	kmem_free(buf, LTO_REQ_INQUIRY);

	/*
	 * If drive doesn't support it no point in checking further.
	 */
	return (wrt);
}

static writablity
st_is_t10_worm_device(struct scsi_tape *un)
{
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_t10_worm_device);

	if (un->un_mspl->media_type == 0x3c) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has WORM media loaded\n");
		wrt = WORM;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has non WORM media loaded\n");
		wrt = RDWR;
	}
	return (wrt);
}

#define	SEQ_CAP_PAGE	(char)0xb0
static writablity
st_is_t10_worm(struct scsi_tape *un)
{
	char *buf;
	int result;
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_t10_worm);

	buf = kmem_zalloc(6, KM_SLEEP);

	result = st_get_special_inquiry(un, 6, buf, SEQ_CAP_PAGE);

	if (result != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Read Vitial Inquiry for Sequental Capability"
		    " WORM support failed %x", result);
		wrt = FAILED;
	} else if ((buf[4] & 1) == 0) {
		ASSERT(buf[1] == SEQ_CAP_PAGE);
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive is NOT WORMable\n");
		/* This drive doesn't support it so don't check again */
		un->un_dp->options &= ~ST_WORMABLE;
		wrt = RDWR;
		un->un_wormable = st_is_not_wormable;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive supports WORM\n");
		un->un_wormable = st_is_t10_worm_device;
		wrt = un->un_wormable(un);
	}

	kmem_free(buf, 6);

	return (wrt);
}


#define	STK_REQ_SENSE 26

static writablity
st_is_stk_worm(struct scsi_tape *un)
{
	char cdb[CDB_GROUP0] = {SCMD_REQUEST_SENSE, 0, 0, 0, STK_REQ_SENSE, 0};
	struct scsi_extended_sense *sense;
	struct uscsi_cmd *cmd;
	char *buf;
	int result;
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_stk_worm);

	cmd = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);
	buf = kmem_alloc(STK_REQ_SENSE, KM_SLEEP);
	sense = (struct scsi_extended_sense *)buf;

	cmd->uscsi_flags = USCSI_READ;
	cmd->uscsi_timeout = un->un_dp->non_motion_timeout;
	cmd->uscsi_cdb = &cdb[0];
	cmd->uscsi_bufaddr = buf;
	cmd->uscsi_buflen = STK_REQ_SENSE;
	cmd->uscsi_cdblen = CDB_GROUP0;
	cmd->uscsi_rqlen = 0;
	cmd->uscsi_rqbuf = NULL;

	result = st_uscsi_cmd(un, cmd, FKIOCTL);

	if (result != 0 || cmd->uscsi_status != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Request Sense for WORM failed");
		wrt = RDWR;
	} else if (sense->es_add_len + 8 < 24) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive didn't send enough sense data for WORM byte %d\n",
		    sense->es_add_len + 8);
		wrt = RDWR;
		un->un_wormable = st_is_not_wormable;
	} else if ((buf[24]) & 0x02) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has WORM tape loaded\n");
		wrt = WORM;
		un->un_wormable = st_is_stk_worm;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive has normal tape loaded\n");
		wrt = RDWR;
		un->un_wormable = st_is_stk_worm;
	}

	kmem_free(buf, STK_REQ_SENSE);
	kmem_free(cmd, sizeof (struct uscsi_cmd));
	return (wrt);
}

#define	DLT_INQ_SZ 44

static writablity
st_is_dlt_tape_worm(struct scsi_tape *un)
{
	caddr_t buf;
	int result;
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_dlt_tape_worm);

	buf = kmem_alloc(DLT_INQ_SZ, KM_SLEEP);

	/* Read Attribute Media Type */

	result = st_read_attributes(un, 0x0408, buf, 10, st_uscsi_cmd);

	/*
	 * If this quantum drive is attached via an HBA that cannot
	 * support thr read attributes command return error in the
	 * hope that someday they will support the t10 method.
	 */
	if (result == EINVAL && un->un_max_cdb_sz < CDB_GROUP4) {
		scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Read Attribute Command for WORM Media detection is not "
		    "supported on the HBA that this drive is attached to.");
		wrt = RDWR;
		un->un_wormable = st_is_not_wormable;
		goto out;
	}

	if (result != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Read Attribute Command for WORM Media returned 0x%x",
		    result);
		wrt = RDWR;
		un->un_dp->options &= ~ST_WORMABLE;
		goto out;
	}

	if ((uchar_t)buf[9] == 0x80) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive media is WORM\n");
		wrt = WORM;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive media is not WORM Media 0x%x\n", (uchar_t)buf[9]);
		wrt = RDWR;
	}

out:
	kmem_free(buf, DLT_INQ_SZ);
	return (wrt);
}

static writablity
st_is_dlt_worm(struct scsi_tape *un)
{
	caddr_t buf;
	int result;
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_dlt_worm);

	buf = kmem_alloc(DLT_INQ_SZ, KM_SLEEP);

	result = st_get_special_inquiry(un, DLT_INQ_SZ, buf, 0xC0);

	if (result != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Read Vendor Specific Inquiry for WORM support failed");
		wrt = RDWR;
		goto out;
	}

	if ((buf[2] & 1) == 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive is not WORMable\n");
		wrt = RDWR;
		un->un_dp->options &= ~ST_WORMABLE;
		un->un_wormable = st_is_not_wormable;
		goto out;
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Drive is WORMable\n");
		un->un_wormable = st_is_dlt_tape_worm;
		wrt = un->un_wormable(un);
	}
out:
	kmem_free(buf, DLT_INQ_SZ);

	return (wrt);
}

typedef struct {
	struct modeheader_seq header;
#if defined(_BIT_FIELDS_LTOH) /* X86 */
	uchar_t pagecode	:6,
				:2;
	uchar_t page_len;
	uchar_t syslogalive	:2,
		device		:1,
		abs		:1,
		ulpbot		:1,
		prth		:1,
		ponej		:1,
		ait		:1;
	uchar_t span;

	uchar_t			:6,
		worm		:1,
		mic		:1;
	uchar_t worm_cap	:1,
				:7;
	uint32_t		:32;
#else /* SPARC */
	uchar_t			:2,
		pagecode	:6;
	uchar_t page_len;
	uchar_t ait		:1,
		device		:1,
		abs		:1,
		ulpbot		:1,
		prth		:1,
		ponej		:1,
		syslogalive	:2;
	uchar_t span;
	uchar_t mic		:1,
		worm		:1,
				:6;
	uchar_t			:7,
		worm_cap	:1;
	uint32_t		:32;
#endif
}ait_dev_con;

#define	AIT_DEV_PAGE 0x31
static writablity
st_is_sony_worm(struct scsi_tape *un)
{
	int result;
	writablity wrt;
	ait_dev_con *ait_conf;

	ST_FUNC(ST_DEVINFO, st_is_sony_worm);

	ait_conf = kmem_zalloc(sizeof (ait_dev_con), KM_SLEEP);

	result = st_gen_mode_sense(un, st_uscsi_cmd, AIT_DEV_PAGE,
	    (struct seq_mode *)ait_conf, sizeof (ait_dev_con));

	if (result == 0) {

		if (ait_conf->pagecode != AIT_DEV_PAGE) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "returned page 0x%x not 0x%x AIT_DEV_PAGE\n",
			    ait_conf->pagecode, AIT_DEV_PAGE);
			wrt = RDWR;
			un->un_wormable = st_is_not_wormable;

		} else if (ait_conf->worm_cap) {

			un->un_wormable = st_is_sony_worm;

			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drives is WORMable\n");
			if (ait_conf->worm) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "Media is WORM\n");
				wrt = WORM;
			} else {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "Media is not WORM\n");
				wrt = RDWR;
			}

		} else {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drives not is WORMable\n");
			wrt = RDWR;
			/* No further checking required */
			un->un_dp->options &= ~ST_WORMABLE;
		}

	} else {

		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "AIT device config mode sense page read command failed"
		    " result = %d ", result);
		wrt = FAILED;
		un->un_wormable = st_is_not_wormable;
	}

	kmem_free(ait_conf, sizeof (ait_dev_con));
	return (wrt);
}

static writablity
st_is_drive_worm(struct scsi_tape *un)
{
	writablity wrt;

	ST_FUNC(ST_DEVINFO, st_is_sony_worm);

	switch (un->un_dp->type) {
	case MT_ISDLT:
		wrt = st_is_dlt_worm(un);
		break;

	case MT_ISSTK9840:
		wrt = st_is_stk_worm(un);
		break;

	case MT_IS8MM:
	case MT_ISAIT:
		wrt = st_is_sony_worm(un);
		break;

	case MT_LTO:
		if (strncmp("HP ", un->un_dp->vid, 3) == 0) {
			wrt = st_is_hp_lto_worm(un);
		} else {
			wrt = st_is_t10_worm(un);
		}
		break;

	case MT_ISDAT:
		if (strncmp("HP ", un->un_dp->vid, 3) == 0) {
			wrt = st_is_hp_dat_worm(un);
		} else {
			wrt = st_is_t10_worm(un);
		}
		break;

	default:
		wrt = FAILED;
		break;
	}

	/*
	 * If any of the above failed try the t10 standard method.
	 */
	if (wrt == FAILED) {
		wrt = st_is_t10_worm(un);
	}

	/*
	 * Unknown method for detecting WORM media.
	 */
	if (wrt == FAILED) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Unknown method for WORM media detection\n");
		wrt = RDWR;
		un->un_dp->options &= ~ST_WORMABLE;
	}

	return (wrt);
}

static int
st_read_attributes(struct scsi_tape *un, uint16_t attribute, void *pnt,
    size_t size, ubufunc_t bufunc)
{
	char cdb[CDB_GROUP4];
	int result;
	struct uscsi_cmd *cmd;
	struct scsi_arq_status status;

	caddr_t buf = (caddr_t)pnt;

	ST_FUNC(ST_DEVINFO, st_read_attributes);

	if (un->un_sd->sd_inq->inq_ansi < 3) {
		return (ENOTTY);
	}

	cmd = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);

	cdb[0] = (char)SCMD_READ_ATTRIBUTE;
	cdb[1] = 0;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	cdb[5] = 0;
	cdb[6] = 0;
	cdb[7] = 0;
	cdb[8] = (char)(attribute >> 8);
	cdb[9] = (char)(attribute);
	cdb[10] = (char)(size >> 24);
	cdb[11] = (char)(size >> 16);
	cdb[12] = (char)(size >> 8);
	cdb[13] = (char)(size);
	cdb[14] = 0;
	cdb[15] = 0;


	cmd->uscsi_flags = USCSI_READ | USCSI_RQENABLE | USCSI_DIAGNOSE;
	cmd->uscsi_timeout = un->un_dp->non_motion_timeout;
	cmd->uscsi_cdb = &cdb[0];
	cmd->uscsi_bufaddr = (caddr_t)buf;
	cmd->uscsi_buflen = size;
	cmd->uscsi_cdblen = sizeof (cdb);
	cmd->uscsi_rqlen = sizeof (status);
	cmd->uscsi_rqbuf = (caddr_t)&status;

	result = bufunc(un, cmd, FKIOCTL);

	if (result != 0 || cmd->uscsi_status != 0) {
		ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_read_attribute failed: result %d status %d\n",
		    result, cmd->uscsi_status);
		/*
		 * If this returns invalid operation code don't try again.
		 */
		if (un->un_sd->sd_sense->es_key == KEY_ILLEGAL_REQUEST &&
		    un->un_sd->sd_sense->es_add_code == 0x20) {
			result = ENOTTY;
		} else if (result == 0) {
			result = EIO;
		}

	} else {

		/*
		 * The attribute retured should match the attribute requested.
		 */
		if (buf[4] != cdb[8] || buf[5] != cdb[9]) {
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_read_attribute got wrong data back expected "
			    "0x%x got 0x%x\n", attribute, buf[6] << 8 | buf[7]);
			st_clean_print(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "bad? data", buf, size);
			result = EIO;
		}
	}

	kmem_free(cmd, sizeof (struct uscsi_cmd));

	return (result);
}

static int
st_get_special_inquiry(struct scsi_tape *un, uchar_t size, caddr_t dest,
    uchar_t page)
{
	char cdb[CDB_GROUP0];
	struct scsi_extended_sense *sense;
	struct uscsi_cmd *cmd;
	int result;

	ST_FUNC(ST_DEVINFO, st_get_special_inquiry);

	cdb[0] = SCMD_INQUIRY;
	cdb[1] = page ? 1 : 0;
	cdb[2] = page;
	cdb[3] = 0;
	cdb[4] = size;
	cdb[5] = 0;

	cmd = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);
	sense = kmem_alloc(sizeof (struct scsi_extended_sense), KM_SLEEP);

	cmd->uscsi_flags = USCSI_READ | USCSI_RQENABLE;
	cmd->uscsi_timeout = un->un_dp->non_motion_timeout;
	cmd->uscsi_cdb = &cdb[0];
	cmd->uscsi_bufaddr = dest;
	cmd->uscsi_buflen = size;
	cmd->uscsi_cdblen = CDB_GROUP0;
	cmd->uscsi_rqlen = sizeof (struct scsi_extended_sense);
	cmd->uscsi_rqbuf = (caddr_t)sense;

	result = st_uscsi_cmd(un, cmd, FKIOCTL);

	if (result != 0 || cmd->uscsi_status != 0) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_special_inquiry() failed for page %x", page);
		if (result == 0) {
			result = EIO;
		}
	}

	kmem_free(sense, sizeof (struct scsi_extended_sense));
	kmem_free(cmd, sizeof (struct uscsi_cmd));

	return (result);
}


static int
st_update_block_pos(struct scsi_tape *un, bufunc_t bf, int post_space)
{
	int rval = ENOTTY;
	uchar_t status = un->un_status;
	posmode previous_pmode = un->un_running.pmode;

	ST_FUNC(ST_DEVINFO, st_update_block_pos);

	while (un->un_read_pos_type != NO_POS) {
		rval = bf(un, SCMD_READ_POSITION, 32, SYNC_CMD);

		/*
		 * If read position command returned good status
		 * Parse the data to see if the position can be interpreted.
		 */
		if ((rval == 0) &&
		    ((rval = st_interpret_read_pos(un, &un->un_pos,
		    un->un_read_pos_type, 32, (caddr_t)un->un_read_pos_data,
		    post_space)) == 0)) {
			/*
			 * Update the running position as well if un_pos was
			 * ok. But only if recovery is enabled.
			 */
			if (st_recov_sz != sizeof (recov_info)) {
				break;
			}
			rval = st_interpret_read_pos(un, &un->un_running,
			    un->un_read_pos_type, 32,
			    (caddr_t)un->un_read_pos_data, post_space);
			un->un_status = status;
			break;
		} else if (un->un_status == KEY_UNIT_ATTENTION) {
			un->un_running.pmode = previous_pmode;
			continue;
		} else if (un->un_status != KEY_ILLEGAL_REQUEST) {
			scsi_log(ST_DEVINFO, st_label, CE_NOTE,
			    "st_update_block_pos() read position cmd 0x%x"
			    " returned 0x%x un_status = %d",
			    un->un_read_pos_type, rval, un->un_status);
			/* ENOTTY means it read garbage. try something else. */
			if (rval == ENOTTY) {
				rval = EIO; /* so ENOTTY is not final rval */
			} else {
				break;
			}
		} else {
			ST_DEBUG4(ST_DEVINFO, st_label, CE_NOTE,
			    "st_update_block_pos() read position cmd %x"
			    " returned %x", un->un_read_pos_type, rval);
			un->un_running.pmode = previous_pmode;
		}

		switch (un->un_read_pos_type) {
		case SHORT_POS:
			un->un_read_pos_type = NO_POS;
			break;

		case LONG_POS:
			un->un_read_pos_type = EXT_POS;
			break;

		case EXT_POS:
			un->un_read_pos_type = SHORT_POS;
			break;

		default:
			ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
			    "Unexpected read position type 0x%x",
			    un->un_read_pos_type);
		}
		un->un_status = KEY_NO_SENSE;
	}

	return (rval);
}

static int
st_get_read_pos(struct scsi_tape *un, buf_t *bp)
{
	int result;
	size_t d_sz;
	caddr_t pos_info;
	struct uscsi_cmd *cmd = (struct uscsi_cmd *)bp->b_back;

	ST_FUNC(ST_DEVINFO, st_get_read_pos);

	if (cmd->uscsi_bufaddr == NULL || cmd->uscsi_buflen <= 0) {
		return (0);
	}

	if (bp_mapin_common(bp, VM_NOSLEEP) == NULL) {

		scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "bp_mapin_common() failed");

		return (EIO);
	}

	d_sz = bp->b_bcount - bp->b_resid;
	if (d_sz == 0) {
		bp_mapout(bp);
		return (EIO);
	}

	/*
	 * Copy the buf to a double-word aligned memory that can hold the
	 * tape_position_t data structure.
	 */
	if ((pos_info = kmem_alloc(d_sz, KM_NOSLEEP)) == NULL) {
		scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "kmem_alloc() failed");
		bp_mapout(bp);
		return (EIO);
	}
	bcopy(bp->b_un.b_addr, pos_info, d_sz);

#ifdef STDEBUG
	if ((st_debug & 0x7) > 2) {
		st_clean_print(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_get_read_pos() position info",
		    pos_info, bp->b_bcount);
	}
#endif

	result = st_interpret_read_pos(un, &un->un_pos, cmd->uscsi_cdb[1],
	    d_sz, pos_info, 0);

	COPY_POS(&un->un_running, &un->un_pos);

	kmem_free(pos_info, d_sz);
	bp_mapout(bp);

	return (result);
}

#if defined(_BIG_ENDIAN)

#define	FIX_ENDIAN16(x)
#define	FIX_ENDIAN32(x)
#define	FIX_ENDIAN64(x)

#elif defined(_LITTLE_ENDIAN)

static void
st_swap16(uint16_t *val)
{
	uint16_t tmp;

	tmp = (*val >>  8) & 0xff;
	tmp |= (*val <<  8) & 0xff00;

	*val = tmp;
}

static void
st_swap32(uint32_t *val)
{
	uint32_t tmp;

	tmp =  (*val >> 24) & 0xff;
	tmp |= (*val >>  8) & 0xff00;
	tmp |= (*val <<  8) & 0xff0000;
	tmp |= (*val << 24) & 0xff000000;

	*val = tmp;
}

static void
st_swap64(uint64_t *val)
{
	uint32_t low;
	uint32_t high;

	low =  (uint32_t)(*val);
	high = (uint32_t)(*val >> 32);

	st_swap32(&low);
	st_swap32(&high);

	*val =  high;
	*val |= ((uint64_t)low << 32);
}

#define	FIX_ENDIAN16(x) st_swap16(x)
#define	FIX_ENDIAN32(x) st_swap32(x)
#define	FIX_ENDIAN64(x) st_swap64(x)
#endif

/*
 * st_interpret_read_pos()
 *
 * Returns:
 *	0	If secsessful.
 *	EIO	If read postion responce data was unuseable or invalid.
 *	ERANGE	If the position of the drive is too large for the read_p_type.
 *	ENOTTY	If the responce data looks invalid for the read position type.
 */

static int
st_interpret_read_pos(struct scsi_tape const *un, tapepos_t *dest,
    read_p_types type, size_t data_sz, const caddr_t responce, int post_space)
{
	int rval = 0;
	int flag = 0;
	tapepos_t org;

	ST_FUNC(ST_DEVINFO, st_interpret_read_pos);

	/*
	 * We expect the position value to change after a space command.
	 * So if post_space is set we don't print out what has changed.
	 */
	if ((dest != &un->un_pos) && (post_space == 0) &&
	    (st_recov_sz == sizeof (recov_info))) {
		COPY_POS(&org, dest);
		flag = 1;
	}

	/*
	 * See what kind of read position was requested.
	 */
	switch (type) {

	case SHORT_POS: /* Short data format */
	{
		tape_position_t *pos_info = (tape_position_t *)responce;
		uint32_t value;

		/* If reserved fields are non zero don't use the data */
		if (pos_info->reserved0 || pos_info->reserved1 ||
		    pos_info->reserved2[0] || pos_info->reserved2[1] ||
		    pos_info->reserved3) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Invalid Read Short Position Data returned\n");
			rval = EIO;
			break;
		}
		/*
		 * Position is to large to use this type of read position.
		 */
		if (pos_info->posi_err == 1) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Drive reported position error\n");
			rval = ERANGE;
			break;
		}
		/*
		 * If your at the begining of partition and end at the same
		 * time it's very small partition or bad data.
		 */
		if (pos_info->begin_of_part && pos_info->end_of_part) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "SHORT_POS returned begin and end of"
			    " partition\n");
			rval = EIO;
			break;
		}

		if (pos_info->blk_posi_unkwn == 0) {

			value = pos_info->host_block;
			FIX_ENDIAN32(&value);

			/*
			 * If the tape is rewound the host blcok should be 0.
			 */
			if ((pos_info->begin_of_part == 1) &&
			    (value != 0)) {
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "SHORT_POS returned begin of partition"
				    " but host block was 0x%x\n", value);
				rval = EIO;
				break;
			}

			if (dest->lgclblkno != value) {
				if (flag)
					flag++;
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "SHORT_POS current logical 0x%"PRIx64" read"
				    " 0x%x\n", dest->lgclblkno, value);
			}

			dest->lgclblkno = (uint64_t)value;

			/*
			 * If the begining of partition is true and the
			 * block number is zero we will beleive that it is
			 * rewound. Promote the pmode to legacy.
			 */
			if ((pos_info->begin_of_part == 1) &&
			    (value == 0)) {
				dest->blkno = 0;
				dest->fileno = 0;
				if (dest->pmode != legacy)
					dest->pmode = legacy;
			/*
			 * otherwise if the pmode was invalid,
			 * promote it to logical.
			 */
			} else if (dest->pmode == invalid) {
				dest->pmode = logical;
			}

			if (dest->partition != pos_info->partition_number) {
				if (flag)
					flag++;
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "SHORT_POS current partition %d read %d\n",
				    dest->partition,
				    pos_info->partition_number);
			}

			dest->partition = pos_info->partition_number;

		} else {
			dest->pmode = invalid;
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Tape drive reported block position as unknown\n");
		}
		break;
	}

	case LONG_POS: /* Long data format */
	{
		uint64_t value;
		tape_position_long_t *long_pos_info =
		    (tape_position_long_t *)responce;

		/* If reserved fields are non zero don't use the data */
		if ((long_pos_info->reserved0) ||
		    (long_pos_info->reserved1) ||
		    (long_pos_info->reserved2)) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Invalid Read Long Position Data returned\n");
			rval = ENOTTY;
			break;
		}

		/* Is position Valid */
		if (long_pos_info->blk_posi_unkwn == 0) {
			uint32_t part;

			value = long_pos_info->block_number;
			FIX_ENDIAN64(&value);

			/*
			 * If it says we are at the begining of partition
			 * the block value better be 0.
			 */
			if ((long_pos_info->begin_of_part == 1) &&
			    (value != 0)) {
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "LONG_POS returned begin of partition but"
				    " block number was 0x%"PRIx64"\n", value);
				rval = ENOTTY;
				break;
			}
			/*
			 * Can't be at the start and the end of the partition
			 * at the same time if the partition is larger the 0.
			 */
			if (long_pos_info->begin_of_part &&
			    long_pos_info->end_of_part) {
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "LONG_POS returned begin and end of"
				    " partition\n");
				rval = ENOTTY;
				break;
			}

			/*
			 * If the logical block number is not what we expected.
			 */
			if (dest->lgclblkno != value) {
				if (flag)
					flag++;
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "LONG_POS current logical 0x%"PRIx64
				    " read 0x%"PRIx64"\n",
				    dest->lgclblkno, value);
			}
			dest->lgclblkno = value;

			/*
			 * If the begining of partition is true and the
			 * block number is zero we will beleive that it is
			 * rewound. Promote the pmode to legacy.
			 */
			if ((long_pos_info->begin_of_part == 1) &&
			    (long_pos_info->block_number == 0)) {
				dest->blkno = 0;
				dest->fileno = 0;
				if (dest->pmode != legacy)
					dest->pmode = legacy;
			/*
			 * otherwise if the pmode was invalid,
			 * promote it to logical.
			 */
			} else if (dest->pmode == invalid) {
				dest->pmode = logical;
			}

			part = long_pos_info->partition;
			FIX_ENDIAN32(&part);
			if (dest->partition != part) {
				if (flag)
					flag++;
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "LONG_POS current partition %d"
				    " read %d\n", dest->partition, part);
			}
			dest->partition = part;
		} else {
			/*
			 * If the drive doesn't know location,
			 * we don't either.
			 */
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Tape drive reported block position as unknown\n");
			dest->pmode = invalid;
		}

		/* Is file position valid */
		if (long_pos_info->mrk_posi_unkwn == 0) {
			value = long_pos_info->file_number;
			FIX_ENDIAN64(&value);
			/*
			 * If it says we are at the begining of partition
			 * the block value better be 0.
			 */
			if ((long_pos_info->begin_of_part == 1) &&
			    (value != 0)) {
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "LONG_POS returned begin of partition but"
				    " block number was 0x%"PRIx64"\n", value);
				rval = ENOTTY;
				break;
			}
			if (((dest->pmode == legacy) ||
			    (dest->pmode == logical)) &&
			    (dest->fileno != value)) {
				if (flag)
					flag++;
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "LONG_POS fileno 0x%"PRIx64
				    " not un_pos %x\n", value,
				    dest->fileno);
			} else if (dest->pmode == invalid) {
				dest->pmode = logical;
			}
			dest->fileno = (int32_t)value;
		}

		if (dest->pmode != invalid && long_pos_info->end_of_part) {
			dest->eof = ST_EOT;
		}

		break;
	}

	case EXT_POS: /* Extended data format */
	{
		uint64_t value;
		uint16_t len;
		tape_position_ext_t *ext_pos_info =
		    (tape_position_ext_t *)responce;

		/* Make sure that there is enough data there */
		if (data_sz < 16) {
			break;
		}

		/* If reserved fields are non zero don't use the data */
		if (ext_pos_info->reserved0 || ext_pos_info->reserved1) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "EXT_POS reserved fields not zero\n");
			rval = ENOTTY;
			break;
		}

		/*
		 * In the unlikely event of overflowing 64 bits of position.
		 */
		if (ext_pos_info->posi_err != 0) {
			rval = ERANGE;
			break;
		}

		len = ext_pos_info->parameter_len;
		FIX_ENDIAN16(&len);

		if (len != 0x1c) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "EXT_POS parameter_len should be 0x1c was 0x%x\n",
			    len);
			rval = ENOTTY;
			break;
		}

		/* Is block position information valid */
		if (ext_pos_info->blk_posi_unkwn == 0) {

			value = ext_pos_info->host_block;
			FIX_ENDIAN64(&value);
			if ((ext_pos_info->begin_of_part == 1) &&
			    (value != 0)) {
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "EXT_POS returned begining of partition but"
				    " the host block was 0x%"PRIx64"\n", value);
				rval = ENOTTY;
				break;
			}

			if (dest->lgclblkno != value) {
				if (flag)
					flag++;
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "EXT_POS current logical 0x%"PRIx64
				    " read 0x%"PRIx64"\n",
				    dest->lgclblkno, value);
			}
			dest->lgclblkno = value;

			/*
			 * If the begining of partition is true and the
			 * block number is zero we will beleive that it is
			 * rewound. Promote the pmode to legacy.
			 */
			if ((ext_pos_info->begin_of_part == 1) &&
			    (ext_pos_info->host_block == 0)) {
				dest->blkno = 0;
				dest->fileno = 0;
				if (dest->pmode != legacy) {
					dest->pmode = legacy;
				}
			/*
			 * otherwise if the pmode was invalid,
			 * promote it to logical.
			 */
			} else if (dest->pmode == invalid) {
				dest->pmode = logical;
			}

			if (dest->partition != ext_pos_info->partition) {
				if (flag)
					flag++;
				ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "EXT_POS current partition %d read %d\n",
				    dest->partition,
				    ext_pos_info->partition);
			}
			dest->partition = ext_pos_info->partition;

		} else {
			dest->pmode = invalid;
		}
		break;
	}

	default:
		ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
		    "Got unexpected SCMD_READ_POSITION type %d\n", type);
		rval = EIO;
	}

	if ((flag > 1) && (rval == 0) && (org.pmode != invalid)) {
		st_print_position(ST_DEVINFO, st_label, CE_NOTE,
		    "position read in", &org);
		st_print_position(ST_DEVINFO, st_label, CE_NOTE,
		    "position read out", dest);
	}

	return (rval);
}

static int
st_logical_block_locate(struct scsi_tape *un, ubufunc_t ubf, tapepos_t *pos,
    uint64_t lblk, uchar_t partition)
{
	int rval;
	char cdb[CDB_GROUP4];
	struct uscsi_cmd *cmd;
	struct scsi_extended_sense sense;
	bufunc_t bf = (ubf == st_uscsi_cmd) ? st_cmd : st_rcmd;

	ST_FUNC(ST_DEVINFO, st_logical_block_locate);
	/*
	 * Not sure what to do when doing recovery and not wanting
	 * to update un_pos
	 */

	cmd = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);

	if (lblk <= INT32_MAX) {
		cmd->uscsi_cdblen = CDB_GROUP1;
		cdb[0] = SCMD_LOCATE;
		cdb[1] = pos->partition == partition ? 0 : 2;
		cdb[2] = 0;
		cdb[3] = (char)(lblk >> 24);
		cdb[4] = (char)(lblk >> 16);
		cdb[5] = (char)(lblk >> 8);
		cdb[6] = (char)(lblk);
		cdb[7] = 0;
		cdb[8] = partition;
		cdb[9] = 0;
	} else {
		/*
		 * If the drive doesn't give a 64 bit read position data
		 * it is unlikely it will accept 64 bit locates.
		 */
		if (un->un_read_pos_type != LONG_POS) {
			kmem_free(cmd, sizeof (struct uscsi_cmd));
			return (ERANGE);
		}
		cmd->uscsi_cdblen = CDB_GROUP4;
		cdb[0] = (char)SCMD_LOCATE_G4;
		cdb[1] = pos->partition == partition ? 0 : 2;
		cdb[2] = 0;
		cdb[3] = partition;
		cdb[4] = (char)(lblk >> 56);
		cdb[5] = (char)(lblk >> 48);
		cdb[6] = (char)(lblk >> 40);
		cdb[7] = (char)(lblk >> 32);
		cdb[8] = (char)(lblk >> 24);
		cdb[9] = (char)(lblk >> 16);
		cdb[10] = (char)(lblk >> 8);
		cdb[11] = (char)(lblk);
		cdb[12] = 0;
		cdb[13] = 0;
		cdb[14] = 0;
		cdb[15] = 0;
	}


	cmd->uscsi_flags = USCSI_WRITE | USCSI_DIAGNOSE | USCSI_RQENABLE;
	cmd->uscsi_rqbuf = (caddr_t)&sense;
	cmd->uscsi_rqlen = sizeof (sense);
	cmd->uscsi_timeout = un->un_dp->space_timeout;
	cmd->uscsi_cdb = cdb;

	rval = ubf(un, cmd, FKIOCTL);

	pos->pmode = logical;
	pos->eof = ST_NO_EOF;

	if (lblk > INT32_MAX) {
		/*
		 * XXX This is a work around till we handle Descriptor format
		 * sense data. Since we are sending a command where the standard
		 * sense data can not correctly represent a correct residual in
		 * 4 bytes.
		 */
		if (un->un_status == KEY_ILLEGAL_REQUEST) {
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Big LOCATE ILLEGAL_REQUEST: rval = %d\n", rval);
			/* Doesn't like big locate command */
			un->un_status = 0;
			rval = ERANGE;
		} else if ((un->un_pos.pmode == invalid) || (rval != 0)) {
			/* Aborted big locate command */
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Big LOCATE resulted in invalid pos: rval = %d\n",
			    rval);
			un->un_status = 0;
			rval = EIO;
		} else if (st_update_block_pos(un, bf, 1)) {
			/* read position failed */
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Big LOCATE and read pos: rval = %d\n", rval);
			rval = EIO;
		} else if (lblk > un->un_pos.lgclblkno) {
			/* read position worked but position was not expected */
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Big LOCATE and recover read less then desired 0x%"
			    PRIx64"\n", un->un_pos.lgclblkno);
			un->un_err_resid = lblk - un->un_pos.lgclblkno;
			un->un_status = KEY_BLANK_CHECK;
			rval = ESPIPE;
		} else if (lblk == un->un_pos.lgclblkno) {
			/* read position was what was expected */
			scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "Big LOCATE and recover seems to have worked\n");
			un->un_err_resid = 0;
			rval = 0;
		} else {
			ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
			    "BIGLOCATE end up going backwards");
			un->un_err_resid = lblk;
			rval = EIO;
		}

	} else if (rval == 0) {
		/* Worked as requested */
		pos->lgclblkno = lblk;

	} else if (((cmd->uscsi_status & ST_STATUS_MASK) == STATUS_CHECK) &&
	    (cmd->uscsi_resid != 0)) {
		/* Got part way there but wasn't enough blocks on tape */
		pos->lgclblkno = lblk - cmd->uscsi_resid;
		un->un_err_resid = cmd->uscsi_resid;
		un->un_status = KEY_BLANK_CHECK;
		rval = ESPIPE;

	} else if (st_update_block_pos(un, bf, 1) == 0) {
		/* Got part way there but drive didn't tell what we missed by */
		un->un_err_resid = lblk - pos->lgclblkno;
		un->un_status = KEY_BLANK_CHECK;
		rval = ESPIPE;

	} else {
		scsi_log(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Failed LOCATE and recover pos: rval = %d status = %d\n",
		    rval, cmd->uscsi_status);
		un->un_err_resid = lblk;
		un->un_status = KEY_ILLEGAL_REQUEST;
		pos->pmode = invalid;
		rval = EIO;
	}

	kmem_free(cmd, sizeof (struct uscsi_cmd));

	return (rval);
}

static int
st_mtfsf_ioctl(struct scsi_tape *un, int64_t files)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_mtfsf_ioctl);


	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_mtfsf_ioctl: count=%"PRIx64", eof=%x\n", files, un->un_pos.eof);
#if 0
	if ((IN_EOF(un->un_pos)) && (files == 1)) {
		un->un_pos.fileno++;
		un->un_pos.blkno = 0;
		return (0);
	}
#endif
	/* pmode == invalid already handled */
	if (un->un_pos.pmode == legacy) {
		/*
		 * forward space over filemark
		 *
		 * For ASF we allow a count of 0 on fsf which means
		 * we just want to go to beginning of current file.
		 * Equivalent to "nbsf(0)" or "bsf(1) + fsf".
		 * Allow stepping over double fmk with reel
		 */
		if ((un->un_pos.eof >= ST_EOT) &&
		    (files > 0) &&
		    ((un->un_dp->options & ST_REEL) == 0)) {
			/* we're at EOM */
			un->un_err_resid = files;
			un->un_status = KEY_BLANK_CHECK;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_mtfsf_ioctl: EIO : MTFSF at EOM");
			return (EIO);
		}

		/*
		 * physical tape position may not be what we've been
		 * telling the user; adjust the request accordingly
		 */
		if (IN_EOF(un->un_pos)) {
			un->un_pos.fileno++;
			un->un_pos.blkno = 0;
			/*
			 * For positive direction case, we're now covered.
			 * For zero or negative direction, we're covered
			 * (almost)
			 */
			files--;
		}

	}

	if (st_check_density_or_wfm(un->un_dev, 1, B_READ, STEPBACK)) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_mtfsf_ioctl: EIO : MTFSF density/wfm failed");
		return (EIO);
	}


	/*
	 * Forward space file marks.
	 * We leave ourselves at block zero
	 * of the target file number.
	 */
	if (files < 0) {
		rval = st_backward_space_files(un, -files, 0);
	} else {
		rval = st_forward_space_files(un, files);
	}

	return (rval);
}

static int
st_forward_space_files(struct scsi_tape *un, int64_t count)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_forward_space_files);

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "fspace: count=%"PRIx64", eof=%x\n", count, un->un_pos.eof);

	ASSERT(count >= 0);
	ASSERT(un->un_pos.pmode != invalid);

	/*
	 * A space with a count of zero means take me to the start of file.
	 */
	if (count == 0) {

		/* Hay look were already there */
		if (un->un_pos.pmode == legacy && un->un_pos.blkno == 0) {
			un->un_err_resid = 0;
			COPY_POS(&un->un_err_pos, &un->un_pos);
			return (0);
		}

		/*
		 * Well we are in the first file.
		 * A rewind will get to the start.
		 */
		if (un->un_pos.pmode == legacy && un->un_pos.fileno == 0) {
			rval = st_cmd(un, SCMD_REWIND, 0, SYNC_CMD);

		/*
		 * Can we backspace to get there?
		 * This should work in logical mode.
		 */
		} else if (un->un_dp->options & ST_BSF) {
			rval = st_space_to_begining_of_file(un);

		/*
		 * Can't back space but current file number is known,
		 * So rewind and space from the begining of the partition.
		 */
		} else if (un->un_pos.pmode == legacy) {
			rval = st_scenic_route_to_begining_of_file(un,
			    un->un_pos.fileno);

		/*
		 * pmode is logical and ST_BSF is not set.
		 * The LONG_POS read position contains the fileno.
		 * If the read position works, rewind and space.
		 */
		} else if (un->un_read_pos_type == LONG_POS) {
			rval = st_cmd(un, SCMD_READ_POSITION, 0, SYNC_CMD);
			if (rval) {
				/*
				 * We didn't get the file position from the
				 * read position command.
				 * We are going to trust the drive to backspace
				 * and then position after the filemark.
				 */
				rval = st_space_to_begining_of_file(un);
			}
			rval = st_interpret_read_pos(un, &un->un_pos, LONG_POS,
			    32, (caddr_t)un->un_read_pos_data, 0);
			if ((rval) && (un->un_pos.pmode == invalid)) {
				rval = st_space_to_begining_of_file(un);
			} else {
				rval = st_scenic_route_to_begining_of_file(un,
				    un->un_pos.fileno);
			}
		} else {
			rval = EIO;
		}
		/*
		 * If something didn't work we are lost
		 */
		if (rval != 0) {
			un->un_pos.pmode = invalid;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_mtioctop : EIO : fspace pmode invalid");

			rval = EIO;
		}

	} else {
		rval = st_space_fmks(un, count);
	}

	if (rval != EIO && count < 0) {
		/*
		 * we came here with a count < 0; we now need
		 * to skip back to end up before the filemark
		 */
		rval = st_backward_space_files(un, 1, 1);
	}

	return (rval);
}

static int
st_scenic_route_to_begining_of_file(struct scsi_tape *un, int32_t fileno)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_scenic_route_to_begining_of_file);

	if (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD)) {
		rval = EIO;
	} else if (st_cmd(un, SCMD_SPACE, Fmk(fileno), SYNC_CMD)) {
		rval = EIO;
	}

	return (rval);
}

static int
st_space_to_begining_of_file(struct scsi_tape *un)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_space_to_begining_of_file);

	/*
	 * Back space of the file at the begining of the file.
	 */
	rval = st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD);
	if (rval) {
		rval = EIO;
		return (rval);
	}

	/*
	 * Other interesting answers might be crashed BOT which isn't bad.
	 */
	if (un->un_status == SUN_KEY_BOT) {
		return (rval);
	}

	un->un_running.pmode = invalid;

	/*
	 * Now we are on the BOP side of the filemark. Forward space to
	 * the EOM side and we are at the begining of the file.
	 */
	rval = st_cmd(un, SCMD_SPACE, Fmk(1), SYNC_CMD);
	if (rval) {
		rval = EIO;
	}

	return (rval);
}

static int
st_mtfsr_ioctl(struct scsi_tape *un, int64_t count)
{

	ST_FUNC(ST_DEVINFO, st_mtfsr_ioctl);

	/*
	 * forward space to inter-record gap
	 *
	 */

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_ioctl_fsr: count=%"PRIx64", eof=%x\n", count, un->un_pos.eof);

	if (un->un_pos.pmode == legacy) {
		/*
		 * If were are at end of tape and count is forward.
		 * Return blank check.
		 */
		if ((un->un_pos.eof >= ST_EOT) && (count > 0)) {
			/* we're at EOM */
			un->un_err_resid = count;
			un->un_status = KEY_BLANK_CHECK;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_mtfsr_ioctl: EIO : MTFSR eof > ST_EOT");
			return (EIO);
		}

		/*
		 * If count is zero there is nothing to do.
		 */
		if (count == 0) {
			un->un_err_pos.fileno = un->un_pos.fileno;
			un->un_err_pos.blkno = un->un_pos.blkno;
			un->un_err_resid = 0;
			if (IN_EOF(un->un_pos) && SVR4_BEHAVIOR) {
				un->un_status = SUN_KEY_EOF;
			}
			return (0);
		}

		/*
		 * physical tape position may not be what we've been
		 * telling the user; adjust the position accordingly
		 */
		if (IN_EOF(un->un_pos)) {
			daddr_t blkno = un->un_pos.blkno;
			int fileno = un->un_pos.fileno;

			optype lastop = un->un_lastop;
			if (st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD)
			    == -1) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_mtfsr_ioctl:EIO:MTFSR count && IN_EOF");
				return (EIO);
			}

			un->un_pos.blkno = blkno;
			un->un_pos.fileno = fileno;
			un->un_lastop = lastop;
		}
	}

	if (st_check_density_or_wfm(un->un_dev, 1, B_READ, STEPBACK)) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_mtfsr_ioctl: EIO : MTFSR st_check_den");
		return (EIO);
	}

	return (st_space_records(un, count));
}

static int
st_space_records(struct scsi_tape *un, int64_t count)
{
	int64_t dblk;
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_space_records);

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_space_records: count=%"PRIx64", eof=%x\n",
	    count, un->un_pos.eof);

	if (un->un_pos.pmode == logical) {
		rval = st_cmd(un, SCMD_SPACE, Blk(count), SYNC_CMD);
		if (rval != 0) {
			rval = EIO;
		}
		return (rval);
	}

	dblk = count + un->un_pos.blkno;

	/* Already there */
	if (dblk == un->un_pos.blkno) {
		un->un_err_resid = 0;
		COPY_POS(&un->un_err_pos, &un->un_pos);
		return (0);
	}

	/*
	 * If the destination block is forward
	 * or the drive will backspace records.
	 */
	if (un->un_pos.blkno < dblk || (un->un_dp->options & ST_BSR)) {
		/*
		 * If we're spacing forward, or the device can
		 * backspace records, we can just use the SPACE
		 * command.
		 */
		dblk -= un->un_pos.blkno;
		if (st_cmd(un, SCMD_SPACE, Blk(dblk), SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_space_records:EIO:space_records can't spc");
			rval = EIO;
		} else if (un->un_pos.eof >= ST_EOF_PENDING) {
			/*
			 * check if we hit BOT/EOT
			 */
			if (dblk < 0 && un->un_pos.eof == ST_EOM) {
				un->un_status = SUN_KEY_BOT;
				un->un_pos.eof = ST_NO_EOF;
			} else if (dblk < 0 &&
			    un->un_pos.eof == ST_EOF_PENDING) {
				int residue = un->un_err_resid;
				/*
				 * we skipped over a filemark
				 * and need to go forward again
				 */
				if (st_cmd(un, SCMD_SPACE, Fmk(1), SYNC_CMD)) {
					ST_DEBUG2(ST_DEVINFO, st_label,
					    SCSI_DEBUG, "st_space_records: EIO"
					    " : can't space #2");
					rval = EIO;
				}
				un->un_err_resid = residue;
			}
			if (rval == 0) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_space_records: EIO : space_rec rval"
				    " == 0");
				rval = EIO;
			}
		}
	} else {
		/*
		 * else we rewind, space forward across filemarks to
		 * the desired file, and then space records to the
		 * desired block.
		 */

		int dfile = un->un_pos.fileno;	/* save current file */

		if (dblk < 0) {
			/*
			 * Wups - we're backing up over a filemark
			 */
			if (un->un_pos.blkno != 0 &&
			    (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD) ||
			    st_cmd(un, SCMD_SPACE, Fmk(dfile), SYNC_CMD))) {
				un->un_pos.pmode = invalid;
			}
			un->un_err_resid = -dblk;
			if (un->un_pos.fileno == 0 && un->un_pos.blkno == 0) {
				un->un_status = SUN_KEY_BOT;
				un->un_pos.eof = ST_NO_EOF;
			} else if (un->un_pos.fileno > 0) {
				un->un_status = SUN_KEY_EOF;
				un->un_pos.eof = ST_NO_EOF;
			}
			COPY_POS(&un->un_err_pos, &un->un_pos);
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_space_records:EIO:space_records : dblk < 0");
			rval = EIO;
		} else if (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD) ||
		    st_cmd(un, SCMD_SPACE, Fmk(dfile), SYNC_CMD) ||
		    st_cmd(un, SCMD_SPACE, Blk(dblk), SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_space_records: EIO :space_records : rewind "
			    "and space failed");
			un->un_pos.pmode = invalid;
			rval = EIO;
		}
	}

	return (rval);
}

static int
st_mtbsf_ioctl(struct scsi_tape *un, int64_t files)
{
	ST_FUNC(ST_DEVINFO, st_mtbsf_ioctl);

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_mtbsf_ioctl: count=%"PRIx64", eof=%x\n", files, un->un_pos.eof);
	/*
	 * backward space of file filemark (1/2" and 8mm)
	 * tape position will end on the beginning of tape side
	 * of the desired file mark
	 */
	if ((un->un_dp->options & ST_BSF) == 0) {
		return (ENOTTY);
	}

	if (un->un_pos.pmode == legacy) {

		/*
		 * If a negative count (which implies a forward space op)
		 * is specified, and we're at logical or physical eot,
		 * bounce the request.
		 */

		if (un->un_pos.eof >= ST_EOT && files < 0) {
			un->un_err_resid = files;
			un->un_status = SUN_KEY_EOT;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl_mt_bsf : EIO : MTBSF : eof > ST_EOF");
			return (EIO);
		}
		/*
		 * physical tape position may not be what we've been
		 * telling the user; adjust the request accordingly
		 */
		if (IN_EOF(un->un_pos)) {
			un->un_pos.fileno++;
			un->un_pos.blkno = 0;
			files++;
			ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_mtbsf_ioctl in eof: count=%"PRIx64", op=%x\n",
			    files, MTBSF);

		}
	}

	if (st_check_density_or_wfm(un->un_dev, 1, 0, STEPBACK)) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl : EIO : MTBSF : check den wfm");
		return (EIO);
	}

	if (files <= 0) {
		/*
		 * for a negative count, we need to step forward
		 * first and then step back again
		 */
		files = -files + 1;
		return (st_forward_space_files(un, files));
	}
	return (st_backward_space_files(un, files, 1));
}

static int
st_backward_space_files(struct scsi_tape *un, int64_t count, int infront)
{
	int64_t end_fileno;
	int64_t skip_cnt;
	int rval = 0;

	ST_FUNC(ST_DEVINFO, st_backward_space_files);

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_backward_space_files: count=%"PRIx64" eof=%x\n",
	    count, un->un_pos.eof);
	/*
	 * Backspace files (MTNBSF): infront == 0
	 *
	 *	For tapes that can backspace, backspace
	 *	count+1 filemarks and then run forward over
	 *	a filemark
	 *
	 *	For tapes that can't backspace,
	 *		calculate desired filenumber
	 *		(un->un_pos.fileno - count), rewind,
	 *		and then space forward this amount
	 *
	 * Backspace filemarks (MTBSF) infront == 1
	 *
	 *	For tapes that can backspace, backspace count
	 *	filemarks
	 *
	 *	For tapes that can't backspace, calculate
	 *	desired filenumber (un->un_pos.fileno - count),
	 *	add 1, rewind, space forward this amount,
	 *	and mark state as ST_EOF_PENDING appropriately.
	 */

	if (un->un_pos.pmode == logical) {

		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_backward_space_files: mt_op=%x count=%"PRIx64
		    "lgclblkno=%"PRIx64"\n", infront?MTBSF:MTNBSF, count,
		    un->un_pos.lgclblkno);


		/* In case a drive that won't back space gets in logical mode */
		if ((un->un_dp->options & ST_BSF) == 0) {
			rval = EIO;
			return (rval);
		}
		if ((infront == 1) &&
		    (st_cmd(un, SCMD_SPACE, Fmk(-count), SYNC_CMD))) {
			rval = EIO;
			return (rval);
		} else if ((infront == 0) &&
		    (st_cmd(un, SCMD_SPACE, Fmk((-count)-1), SYNC_CMD)) &&
		    (st_cmd(un, SCMD_SPACE, Fmk(1), SYNC_CMD))) {
			rval = EIO;
			return (rval);
		}
		return (rval);
	}

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_backward_space_files: mt_op=%x count=%"PRIx64
	    "fileno=%x blkno=%x\n",
	    infront?MTBSF:MTNBSF, count, un->un_pos.fileno, un->un_pos.blkno);



	/*
	 * Handle the simple case of BOT
	 * playing a role in these cmds.
	 * We do this by calculating the
	 * ending file number. If the ending
	 * file is < BOT, rewind and set an
	 * error and mark resid appropriately.
	 * If we're backspacing a file (not a
	 * filemark) and the target file is
	 * the first file on the tape, just
	 * rewind.
	 */

	/* figure expected destination of this SPACE command */
	end_fileno = un->un_pos.fileno - count;

	/*
	 * Would the end effect of this SPACE be the same as rewinding?
	 * If so just rewind instead.
	 */
	if ((infront != 0) && (end_fileno < 0) ||
	    (infront == 0) && (end_fileno <= 0)) {
		if (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_backward_space_files: EIO : "
			    "rewind in lou of BSF failed\n");
			rval = EIO;
		}
		if (end_fileno < 0) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_backward_space_files: EIO : "
			    "back space file greater then fileno\n");
			rval = EIO;
			un->un_err_resid = -end_fileno;
			un->un_status = SUN_KEY_BOT;
		}
		return (rval);
	}

	if (un->un_dp->options & ST_BSF) {
		skip_cnt = 1 - infront;
		/*
		 * If we are going to end up at the beginning
		 * of the file, we have to space one extra file
		 * first, and then space forward later.
		 */
		end_fileno = -(count + skip_cnt);
		ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "skip_cnt=%"PRIx64", tmp=%"PRIx64"\n",
		    skip_cnt, end_fileno);
		if (st_cmd(un, SCMD_SPACE, Fmk(end_fileno), SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_backward_space_files:EIO:back space fm failed");
			rval = EIO;
		}
	} else {
		if (st_cmd(un, SCMD_REWIND, 0, SYNC_CMD)) {
			rval = EIO;
		} else {
			skip_cnt = end_fileno + infront;
		}
	}

	/*
	 * If we have to space forward, do so...
	 */
	ST_DEBUG6(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "space forward skip_cnt=%"PRIx64", rval=%x\n", skip_cnt, rval);

	if (rval == 0 && skip_cnt) {
		if (st_cmd(un, SCMD_SPACE, Fmk(skip_cnt), SYNC_CMD)) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_backward_space_files:EIO:space fm skip count");
			rval = EIO;
		} else if (infront) {
			/*
			 * If we had to space forward, and we're
			 * not a tape that can backspace, mark state
			 * as if we'd just seen a filemark during a
			 * a read.
			 */
			if ((un->un_dp->options & ST_BSF) == 0) {
				un->un_pos.eof = ST_EOF_PENDING;
				un->un_pos.fileno -= 1;
				un->un_pos.blkno = LASTBLK;
				un->un_running.pmode = invalid;
			}
		}
	}

	if (rval != 0) {
		un->un_pos.pmode = invalid;
	}

	return (rval);
}

static int
st_mtnbsf_ioctl(struct scsi_tape *un, int64_t count)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_mtnbsf_ioctl);

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "nbsf: count=%"PRIx64", eof=%x\n", count, un->un_pos.eof);

	if (un->un_pos.pmode == legacy) {
		/*
		 * backward space file to beginning of file
		 *
		 * If a negative count (which implies a forward space op)
		 * is specified, and we're at logical or physical eot,
		 * bounce the request.
		 */

		if (un->un_pos.eof >= ST_EOT && count < 0) {
			un->un_err_resid = count;
			un->un_status = SUN_KEY_EOT;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl : EIO : > EOT and count < 0");
			return (EIO);
		}
		/*
		 * physical tape position may not be what we've been
		 * telling the user; adjust the request accordingly
		 */
		if (IN_EOF(un->un_pos)) {
			un->un_pos.fileno++;
			un->un_pos.blkno = 0;
			count++;
		}
	}

	if (st_check_density_or_wfm(un->un_dev, 1, 0, STEPBACK)) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl : EIO : MTNBSF check den and wfm");
		return (EIO);
	}

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "mtnbsf: count=%"PRIx64", eof=%x\n", count, un->un_pos.eof);

	if (count <= 0) {
		rval = st_forward_space_files(un, -count);
	} else {
		rval = st_backward_space_files(un, count, 0);
	}
	return (rval);
}

static int
st_mtbsr_ioctl(struct scsi_tape *un, int64_t num)
{
	ST_FUNC(ST_DEVINFO, st_mtbsr_ioctl);

	ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "bsr: count=%"PRIx64", eof=%x\n", num, un->un_pos.eof);

	if (un->un_pos.pmode == legacy) {
		/*
		 * backward space into inter-record gap
		 *
		 * If a negative count (which implies a forward space op)
		 * is specified, and we're at logical or physical eot,
		 * bounce the request.
		 */
		if (un->un_pos.eof >= ST_EOT && num < 0) {
			un->un_err_resid = num;
			un->un_status = SUN_KEY_EOT;
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "st_ioctl : EIO : MTBSR > EOT");
			return (EIO);
		}

		if (num == 0) {
			COPY_POS(&un->un_err_pos, &un->un_pos);
			un->un_err_resid = 0;
			if (IN_EOF(un->un_pos) && SVR4_BEHAVIOR) {
				un->un_status = SUN_KEY_EOF;
			}
			return (0);
		}

		/*
		 * physical tape position may not be what we've been
		 * telling the user; adjust the position accordingly.
		 * bsr can not skip filemarks and continue to skip records
		 * therefore if we are logically before the filemark but
		 * physically at the EOT side of the filemark, we need to step
		 * back; this allows fsr N where N > number of blocks in file
		 * followed by bsr 1 to position at the beginning of last block
		 */
		if (IN_EOF(un->un_pos)) {
			tapepos_t save;
			optype lastop = un->un_lastop;

			COPY_POS(&save, &un->un_pos);
			if (st_cmd(un, SCMD_SPACE, Fmk(-1), SYNC_CMD) == -1) {
				ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
				    "st_mtbsr_ioctl: EIO : MTBSR can't space");
				return (EIO);
			}

			COPY_POS(&un->un_pos, &save);
			un->un_lastop = lastop;
		}
	}

	un->un_pos.eof = ST_NO_EOF;

	if (st_check_density_or_wfm(un->un_dev, 1, 0, STEPBACK)) {
		ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "st_ioctl : EIO : MTBSR : can't set density or wfm");
		return (EIO);
	}

	num = -num;
	return (st_space_records(un, num));
}

static int
st_mtfsfm_ioctl(struct scsi_tape *un, int64_t cnt)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_mtfsfm_ioctl);

	rval = st_cmd(un, SCMD_SPACE, SPACE(SP_SQFLM, cnt), SYNC_CMD);
	if (rval == 0) {
		un->un_pos.pmode = logical;
	} else if ((un->un_status == KEY_ILLEGAL_REQUEST) &&
	    (un->un_sd->sd_sense->es_add_code == 0x24)) {
		/*
		 * Drive says invalid field in cdb.
		 * Doesn't like space multiple. Position isn't lost.
		 */
		un->un_err_resid = cnt;
		un->un_status = 0;
		rval = ENOTTY;
	} else {
		un->un_err_resid = cnt;
		un->un_pos.pmode = invalid;
	}
	return (rval);
}

static int
st_mtbsfm_ioctl(struct scsi_tape *un, int64_t cnt)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_mtbsfm_ioctl);

	rval = st_cmd(un, SCMD_SPACE, SPACE(SP_SQFLM, -cnt), SYNC_CMD);
	if (rval == 0) {
		un->un_pos.pmode = logical;
	} else if ((un->un_status == KEY_ILLEGAL_REQUEST) &&
	    (un->un_sd->sd_sense->es_add_code == 0x24)) {
		/*
		 * Drive says invalid field in cdb.
		 * Doesn't like space multiple. Position isn't lost.
		 */
		un->un_err_resid = cnt;
		un->un_status = 0;
		rval = ENOTTY;
	} else {
		un->un_err_resid = cnt;
		un->un_pos.pmode = invalid;
	}
	return (rval);
}

#ifdef	__x86

/*
 * release contig_mem and wake up waiting thread, if any
 */
static void
st_release_contig_mem(struct scsi_tape *un, struct contig_mem *cp)
{
	mutex_enter(ST_MUTEX);

	ST_FUNC(ST_DEVINFO, st_release_contig_mem);

	cp->cm_next = un->un_contig_mem;
	un->un_contig_mem = cp;
	un->un_contig_mem_available_num++;
	cv_broadcast(&un->un_contig_mem_cv);

	mutex_exit(ST_MUTEX);
}

/*
 * St_get_contig_mem will return a contig_mem if there is one available
 * in current system. Otherwise, it will try to alloc one, if the total
 * number of contig_mem is within st_max_contig_mem_num.
 * It will sleep, if allowed by caller or return NULL, if no contig_mem
 * is available for now.
 */
static struct contig_mem *
st_get_contig_mem(struct scsi_tape *un, size_t len, int alloc_flags)
{
	size_t rlen;
	struct contig_mem *cp = NULL;
	ddi_acc_handle_t acc_hdl;
	caddr_t addr;
	int big_enough = 0;
	int (*dma_alloc_cb)() = (alloc_flags == KM_SLEEP) ?
	    DDI_DMA_SLEEP : DDI_DMA_DONTWAIT;

	/* Try to get one available contig_mem */
	mutex_enter(ST_MUTEX);

	ST_FUNC(ST_DEVINFO, st_get_contig_mem);

	if (un->un_contig_mem_available_num > 0) {
		ST_GET_CONTIG_MEM_HEAD(un, cp, len, big_enough);
	} else if (un->un_contig_mem_total_num < st_max_contig_mem_num) {
		/*
		 * we failed to get one. we're going to
		 * alloc one more contig_mem for this I/O
		 */
		mutex_exit(ST_MUTEX);
		cp = (struct contig_mem *)kmem_zalloc(
		    sizeof (struct contig_mem) + biosize(),
		    alloc_flags);
		if (cp == NULL) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "alloc contig_mem failure\n");
			return (NULL); /* cannot get one */
		}
		cp->cm_bp = (struct buf *)
		    (((caddr_t)cp) + sizeof (struct contig_mem));
		bioinit(cp->cm_bp);
		mutex_enter(ST_MUTEX);
		un->un_contig_mem_total_num++; /* one more available */
	} else {
		/*
		 * we failed to get one and we're NOT allowed to
		 * alloc more contig_mem
		 */
		if (alloc_flags == KM_SLEEP) {
			while (un->un_contig_mem_available_num <= 0) {
				cv_wait(&un->un_contig_mem_cv, ST_MUTEX);
			}
			ST_GET_CONTIG_MEM_HEAD(un, cp, len, big_enough);
		} else {
			mutex_exit(ST_MUTEX);
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "alloc contig_mem failure\n");
			return (NULL); /* cannot get one */
		}
	}
	mutex_exit(ST_MUTEX);

	/* We need to check if this block of mem is big enough for this I/O */
	if (cp->cm_len < len) {
		/* not big enough, need to alloc a new one */
		if (ddi_dma_mem_alloc(un->un_contig_mem_hdl, len, &st_acc_attr,
		    DDI_DMA_STREAMING, dma_alloc_cb, NULL,
		    &addr, &rlen, &acc_hdl) != DDI_SUCCESS) {
			ST_DEBUG2(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "alloc contig_mem failure: not enough mem\n");
			st_release_contig_mem(un, cp);
			cp = NULL;
		} else {
			if (cp->cm_addr) {
				/* release previous one before attach new one */
				ddi_dma_mem_free(&cp->cm_acc_hdl);
			}
			mutex_enter(ST_MUTEX);
			un->un_max_contig_mem_len =
			    un->un_max_contig_mem_len >= len ?
			    un->un_max_contig_mem_len : len;
			mutex_exit(ST_MUTEX);

			/* attach new mem to this cp */
			cp->cm_addr = addr;
			cp->cm_acc_hdl = acc_hdl;
			cp->cm_len = len;

			goto alloc_ok; /* get one usable cp */
		}
	} else {
		goto alloc_ok; /* get one usable cp */
	}

	/* cannot find/alloc a usable cp, when we get here */

	mutex_enter(ST_MUTEX);
	if ((un->un_max_contig_mem_len < len) ||
	    (alloc_flags != KM_SLEEP)) {
		mutex_exit(ST_MUTEX);
		return (NULL);
	}

	/*
	 * we're allowed to sleep, and there is one big enough
	 * contig mem in the system, which is currently in use,
	 * wait for it...
	 */
	big_enough = 1;
	do {
		cv_wait(&un->un_contig_mem_cv, ST_MUTEX);
		ST_GET_CONTIG_MEM_HEAD(un, cp, len, big_enough);
	} while (cp == NULL);
	mutex_exit(ST_MUTEX);

	/* we get the big enough contig mem, finally */

alloc_ok:
	/* init bp attached to this cp */
	bioreset(cp->cm_bp);
	cp->cm_bp->b_un.b_addr = cp->cm_addr;
	cp->cm_bp->b_private = (void *)cp;

	return (cp);
}

/*
 * this is the biodone func for the bp used in big block I/O
 */
static int
st_bigblk_xfer_done(struct buf *bp)
{
	struct contig_mem *cp;
	struct buf *orig_bp;
	int ioerr;
	struct scsi_tape *un;

	/* sanity check */
	if (bp == NULL) {
		return (DDI_FAILURE);
	}

	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));
	if (un == NULL) {
		return (DDI_FAILURE);
	}

	ST_FUNC(ST_DEVINFO, st_bigblk_xfer_done);

	cp = (struct contig_mem *)bp->b_private;
	orig_bp = cp->cm_bp; /* get back the bp we have replaced */
	cp->cm_bp = bp;

	/* special handling for special I/O */
	if (cp->cm_use_sbuf) {
#ifndef __lock_lint
		ASSERT(un->un_sbuf_busy);
#endif
		un->un_sbufp = orig_bp;
		cp->cm_use_sbuf = 0;
	}

	orig_bp->b_resid = bp->b_resid;
	ioerr = geterror(bp);
	if (ioerr != 0) {
		bioerror(orig_bp, ioerr);
	} else if (orig_bp->b_flags & B_READ) {
		/* copy data back to original bp */
		(void) bp_copyout(bp->b_un.b_addr, orig_bp, 0,
		    bp->b_bcount - bp->b_resid);
	}

	st_release_contig_mem(un, cp);

	biodone(orig_bp);

	return (DDI_SUCCESS);
}

/*
 * We use this func to replace original bp that may not be able to do I/O
 * in big block size with one that can
 */
static struct buf *
st_get_bigblk_bp(struct buf *bp)
{
	struct contig_mem *cp;
	struct scsi_tape *un;
	struct buf *cont_bp;

	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));
	if (un == NULL) {
		return (bp);
	}

	ST_FUNC(ST_DEVINFO, st_get_bigblk_bp);

	/* try to get one contig_mem */
	cp = st_get_contig_mem(un, bp->b_bcount, KM_SLEEP);
	if (!cp) {
		scsi_log(ST_DEVINFO, st_label, CE_WARN,
		    "Cannot alloc contig buf for I/O for %lu blk size",
		    bp->b_bcount);
		return (bp);
	}
	cont_bp = cp->cm_bp;
	cp->cm_bp = bp;

	/* make sure that we "are" using un_sbufp for special I/O */
	if (bp == un->un_sbufp) {
#ifndef __lock_lint
		ASSERT(un->un_sbuf_busy);
#endif
		un->un_sbufp = cont_bp;
		cp->cm_use_sbuf = 1;
	}

	/* clone bp */
	cont_bp->b_bcount = bp->b_bcount;
	cont_bp->b_resid = bp->b_resid;
	cont_bp->b_iodone = st_bigblk_xfer_done;
	cont_bp->b_file = bp->b_file;
	cont_bp->b_offset = bp->b_offset;
	cont_bp->b_dip = bp->b_dip;
	cont_bp->b_error = 0;
	cont_bp->b_proc = NULL;
	cont_bp->b_flags = bp->b_flags & ~(B_PAGEIO | B_PHYS | B_SHADOW);
	cont_bp->b_shadow = NULL;
	cont_bp->b_pages = NULL;
	cont_bp->b_edev = bp->b_edev;
	cont_bp->b_dev = bp->b_dev;
	cont_bp->b_lblkno = bp->b_lblkno;
	cont_bp->b_forw = bp->b_forw;
	cont_bp->b_back = bp->b_back;
	cont_bp->av_forw = bp->av_forw;
	cont_bp->av_back = bp->av_back;
	cont_bp->b_bufsize = bp->b_bufsize;

	/* get data in original bp */
	if (bp->b_flags & B_WRITE) {
		(void) bp_copyin(bp, cont_bp->b_un.b_addr, 0, bp->b_bcount);
	}

	return (cont_bp);
}
#else
#ifdef __lock_lint
static int
st_bigblk_xfer_done(struct buf *bp)
{
	return (0);
}
#endif
#endif

static const char *eof_status[] =
{
	"NO_EOF",
	"EOF_PENDING",
	"EOF",
	"EOT_PENDING",
	"EOT",
	"EOM",
	"AFTER_EOM"
};
static const char *mode[] = {
	"invalid",
	"legacy",
	"logical"
};

static void
st_print_position(dev_info_t *dev, char *label, uint_t level,
const char *comment, tapepos_t *pos)
{
	ST_FUNC(dev, st_print_position);

	scsi_log(dev, label, level,
	    "%s Position data:\n", comment);
	scsi_log(dev, label, CE_CONT,
	    "Positioning mode = %s", mode[pos->pmode]);
	scsi_log(dev, label, CE_CONT,
	    "End Of File/Tape = %s", eof_status[pos->eof]);
	scsi_log(dev, label, CE_CONT,
	    "File Number      = 0x%x", pos->fileno);
	scsi_log(dev, label, CE_CONT,
	    "Block Number     = 0x%x", pos->blkno);
	scsi_log(dev, label, CE_CONT,
	    "Logical Block    = 0x%"PRIx64, pos->lgclblkno);
	scsi_log(dev, label, CE_CONT,
	    "Partition Number = 0x%x", pos->partition);
}
static int
st_check_if_media_changed(struct scsi_tape *un, caddr_t data, int size)
{

	int result = 0;
	int i;
	ST_FUNC(ST_DEVINFO, st_check_if_media_changed);

	/*
	 * find non alpha numeric working from the end.
	 */
	for (i = size - 1; i >= 0; i--) {
		if (ISALNUM(data[i]) == 0 || data[i] == ' ') {
			data[i] = 0;
			size = i;
		}
	}

	if (size == 1) {
		/*
		 * Drive seems to think its returning useful data
		 * but it looks like all junk
		 */
		return (result);
	}

	size++;

	/*
	 * Actually got a valid serial number.
	 * If never stored one before alloc space for it.
	 */
	if (un->un_media_id_len == 0) {
		un->un_media_id = kmem_zalloc(size, KM_SLEEP);
		un->un_media_id_len = size;
		(void) strncpy(un->un_media_id, data, min(size, strlen(data)));
		un->un_media_id[min(size, strlen(data))] = 0;
		ST_DEBUG1(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Found Media Id %s length = %d\n", un->un_media_id, size);
	} else if (size > un->un_media_id_len) {
		if (strncmp(un->un_media_id, data, size) != 0) {
			result = ESPIPE;
		}
		ST_DEBUG1(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Longer Media Id old ID:%s new ID:%s\n",
		    un->un_media_id, data);
		kmem_free(un->un_media_id, un->un_media_id_len);
		un->un_media_id = kmem_zalloc(size, KM_SLEEP);
		un->un_media_id_len = size;
		(void) strncpy(un->un_media_id, data, size);
		un->un_media_id[size] = 0;
	} else if (strncmp(data, un->un_media_id,
	    min(size, un->un_media_id_len)) != 0) {
		ST_DEBUG1(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Old Media Id %s length = %d New %s length = %d\n",
		    un->un_media_id, un->un_media_id_len, data, size);
		bzero(un->un_media_id, un->un_media_id_len);
		(void) strncpy(un->un_media_id, data, min(size, strlen(data)));
		un->un_media_id[min(size, strlen(data))] = 0;
		result = ESPIPE;
	} else {
		ST_DEBUG4(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "Media Id still %s\n", un->un_media_id);
	}

	ASSERT(strlen(un->un_media_id) <= size);

	return (result);
}
#define	ID_SIZE 32
typedef struct
{
	uchar_t avilable_data0;
	uchar_t avilable_data1;
	uchar_t avilable_data2;
	uchar_t avilable_data3;
	uchar_t attribute_msb;
	uchar_t attribute_lsb;
#ifdef _BIT_FIELDS_LTOH
	uchar_t format		: 2,
				: 5,
		read_only	: 1;
#else
	uchar_t read_only	: 1,
				: 5,
		format		: 2;
#endif
	uchar_t attribute_len_msb;
	uchar_t attribute_len_lsb;
}attribute_header;

typedef struct {
	attribute_header header;
	char data[1];
}mam_attribute;

static int
st_handle_hex_media_id(struct scsi_tape *un, void *pnt, int size)
{
	int result;
	int newsize = (size << 1) + 3; /* extra for leading 0x and null term */
	int i;
	uchar_t byte;
	char *format;
	uchar_t *data = (uchar_t *)pnt;
	char *buf = kmem_alloc(newsize, KM_SLEEP);

	ST_FUNC(ST_DEVINFO, st_handle_hex_media_id);

	(void) sprintf(buf, "0x");
	for (i = 0; i < size; i++) {
		byte = data[i];
		if (byte < 0x10)
			format = "0%x";
		else
			format = "%x";
		(void) sprintf(&buf[(int)strlen(buf)], format, byte);
	}
	result = st_check_if_media_changed(un, buf, newsize);

	kmem_free(buf, newsize);

	return (result);
}


static int
st_get_media_id_via_read_attribute(struct scsi_tape *un, ubufunc_t bufunc)
{
	int result;
	mam_attribute *buffer;
	int size;
	int newsize;

	ST_FUNC(ST_DEVINFO, st_get_media_id_via_read_attribute);
	size = sizeof (attribute_header) + max(un->un_media_id_len, ID_SIZE);
again:
	buffer = kmem_zalloc(size, KM_SLEEP);
	result = st_read_attributes(un, 0x0401, buffer, size, bufunc);
	if (result == 0) {

		newsize = (buffer->header.attribute_len_msb << 8) |
		    buffer->header.attribute_len_lsb;

		if (newsize + sizeof (attribute_header) > size) {
			ST_DEBUG(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "resizing read attribute data from %d to %d format"
			    " %d\n", size, (int)sizeof (attribute_header) +
			    newsize, buffer->header.format);
			kmem_free(buffer, size);
			size = newsize + sizeof (attribute_header);
			goto again;
		}

		un->un_media_id_method = st_get_media_id_via_read_attribute;
		if (buffer->header.format == 0) {
			result =
			    st_handle_hex_media_id(un, buffer->data, newsize);
		} else {
			result = st_check_if_media_changed(un, buffer->data,
			    newsize);
		}
	} else if (result == EINVAL && un->un_max_cdb_sz < CDB_GROUP4) {
		scsi_log(ST_DEVINFO, st_label, CE_NOTE,
		    "Read Attribute Command for Media Identification is not "
		    "supported on the HBA that this drive is attached to.");
		result = ENOTTY;
	}

	kmem_free(buffer, size);
	un->un_status = 0;

	return (result);
}


static int
st_get_media_id_via_media_serial_cmd(struct scsi_tape *un, ubufunc_t bufunc)
{
	char cdb[CDB_GROUP5];
	struct uscsi_cmd *ucmd;
	struct scsi_extended_sense sense;
	int rval;
	int size = max(un->un_media_id_len, ID_SIZE);
	caddr_t buf;

	ST_FUNC(ST_DEVINFO, st_get_media_id_via_media_serial_cmd);

	if (un->un_sd->sd_inq->inq_ansi < 3) {
		return (ENOTTY);
	}

	ucmd = kmem_zalloc(sizeof (struct uscsi_cmd), KM_SLEEP);
upsize:
	buf = kmem_alloc(size, KM_SLEEP);

	cdb[0] = (char)SCMD_SVC_ACTION_IN_G5;
	cdb[1] = SSVC_ACTION_READ_MEDIA_SERIAL;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	cdb[5] = 0;
	cdb[6] = (char)(size >> 24);
	cdb[7] = (char)(size >> 16);
	cdb[8] = (char)(size >> 8);
	cdb[9] = (char)(size);
	cdb[10] = 0;
	cdb[11] = 0;

	ucmd->uscsi_flags = USCSI_READ | USCSI_RQENABLE;
	ucmd->uscsi_timeout = un->un_dp->non_motion_timeout;
	ucmd->uscsi_cdb = &cdb[0];
	ucmd->uscsi_cdblen = sizeof (cdb);
	ucmd->uscsi_bufaddr = buf;
	ucmd->uscsi_buflen = size;
	ucmd->uscsi_rqbuf = (caddr_t)&sense;
	ucmd->uscsi_rqlen = sizeof (sense);

	rval = bufunc(un, ucmd, FKIOCTL);

	if (rval || ucmd->uscsi_status != 0) {
		ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
		    "media serial command returned %d scsi_status %d"
		    " rqstatus %d", rval, ucmd->uscsi_status,
		    ucmd->uscsi_rqstatus);
		/*
		 * If this returns invalid operation code don't try again.
		 */
		if (sense.es_key == KEY_ILLEGAL_REQUEST &&
		    sense.es_add_code == 0x20) {
			rval = ENOTTY;
		} else if (rval == 0) {
			rval = EIO;
		}
		un->un_status = 0;
	} else {
		int act_size;

		/*
		 * get reported size.
		 */
		act_size = (int)buf[3] | (int)(buf[2] << 8) |
		    (int)(buf[1] << 16) | (int)(buf[0] << 24);

		/* documentation says mod 4. */
		while (act_size & 3) {
			act_size++;
		}

		/*
		 * If reported size is larger that we our buffer.
		 * Free the old one and allocate one that is larger
		 * enough and re-issuse the command.
		 */
		if (act_size + 4 > size) {
			kmem_free(buf, size);
			size = act_size + 4;
			goto upsize;
		}

		if (act_size == 0) {
			ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
			    "media serial number is not available");
			un->un_status = 0;
			rval = 0;
		} else {
			/*
			 * set data pointer to point to the start
			 * of that serial number.
			 */
			un->un_media_id_method =
			    st_get_media_id_via_media_serial_cmd;
			rval =
			    st_check_if_media_changed(un, &buf[4], act_size);
		}
	}

	kmem_free(ucmd, sizeof (struct uscsi_cmd));
	kmem_free(buf, size);

	return (rval);
}


/* ARGSUSED */
static int
st_bogus_media_id(struct scsi_tape *un, ubufunc_t bufunc)
{
	ST_FUNC(ST_DEVINFO, st_bogus_media_id);

	ASSERT(un->un_media_id == NULL || un->un_media_id == bogusID);
	ASSERT(un->un_media_id_len == 0);
	un->un_media_id = (char *)bogusID;
	un->un_media_id_len = 0;
	return (0);
}

typedef int (*media_chk_function)(struct scsi_tape *, ubufunc_t bufunc);

media_chk_function media_chk_functions[] = {
	st_get_media_id_via_media_serial_cmd,
	st_get_media_id_via_read_attribute,
	st_bogus_media_id
};

static int
st_get_media_identification(struct scsi_tape *un, ubufunc_t bufunc)
{
	int result = 0;
	int i;

	ST_FUNC(ST_DEVINFO, st_get_media_identification);

	for (i = 0; i < ST_NUM_MEMBERS(media_chk_functions); i++) {
		if (result == ENOTTY) {
			/*
			 * Last operation type not supported by this device.
			 * Make so next time it doesn`t do that again.
			 */
			un->un_media_id_method = media_chk_functions[i];
		} else if (un->un_media_id_method != media_chk_functions[i] &&
		    un->un_media_id_method != st_get_media_identification) {
			continue;
		}
		result = media_chk_functions[i](un, bufunc);
		/*
		 * If result indicates the function was successful or
		 * that the media is not the same as last known, break.
		 */
		if (result == 0 || result == ESPIPE) {
			break;
		}
	}

	return (result);
}

static errstate
st_command_recovery(struct scsi_tape *un, struct scsi_pkt *pkt,
    errstate onentry)
{

	int ret;
	st_err_info *errinfo;
	recov_info *ri = (recov_info *)pkt->pkt_private;

	ST_FUNC(ST_DEVINFO, st_command_recovery);

	ASSERT(MUTEX_HELD(&un->un_sd->sd_mutex));

	ASSERT(un->un_recov_buf_busy == 0);

	/*
	 * Don't try and recover a reset that this device sent.
	 */
	if (un->un_rsvd_status & ST_INITIATED_RESET &&
	    onentry == DEVICE_RESET) {
		return (COMMAND_DONE_ERROR);
	}

	/*
	 * See if expected position was passed with scsi_pkt.
	 */
	if (ri->privatelen == sizeof (recov_info)) {

		/*
		 * Not for this command.
		 */
		if (ri->cmd_attrib->do_not_recover) {
			return (COMMAND_DONE_ERROR);
		}

		/*
		 * Create structure to hold all error state info.
		 */
		errinfo = kmem_zalloc(ST_ERR_INFO_SIZE, KM_SLEEP);
		errinfo->ei_error_type = onentry;
		errinfo->ei_failing_bp = ri->cmd_bp;
		COPY_POS(&errinfo->ei_expected_pos, &ri->pos);
	} else {
		/* disabled */
		return (COMMAND_DONE_ERROR);
	}

	bcopy(pkt, &errinfo->ei_failed_pkt, scsi_pkt_size());
	bcopy(pkt->pkt_scbp, &errinfo->ei_failing_status, SECMDS_STATUS_SIZE);
	ret = ddi_taskq_dispatch(un->un_recov_taskq, st_recover, errinfo,
	    DDI_NOSLEEP);
	ASSERT(ret == DDI_SUCCESS);
	if (ret != DDI_SUCCESS) {
		kmem_free(errinfo, ST_ERR_INFO_SIZE);
		return (COMMAND_DONE_ERROR);
	}
	return (JUST_RETURN); /* release calling thread */
}


static void
st_recov_ret(struct scsi_tape *un, st_err_info *errinfo, errstate err)
{
	int error_number;
	buf_t *bp;


	ST_FUNC(ST_DEVINFO, st_recov_ret);

	ASSERT(MUTEX_HELD(&un->un_sd->sd_mutex));
#if !defined(lint)
	_NOTE(LOCK_RELEASED_AS_SIDE_EFFECT(&un->un_sd->sd_mutex))
#endif

	bp = errinfo->ei_failing_bp;
	kmem_free(errinfo, ST_ERR_INFO_SIZE);

	switch (err) {
	case JUST_RETURN:
		mutex_exit(&un->un_sd->sd_mutex);
		return;

	case COMMAND_DONE:
	case COMMAND_DONE_ERROR_RECOVERED:
		ST_DO_KSTATS(bp, kstat_runq_exit);
		error_number = 0;
		break;

	default:
		ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
		    "st_recov_ret with unhandled errstat %d\n", err);
		/* FALLTHROUGH */
	case COMMAND_DONE_ERROR:
		un->un_pos.pmode = invalid;
		un->un_running.pmode = invalid;
		/* FALLTHROUGH */
	case COMMAND_DONE_EACCES:
		ST_DO_KSTATS(bp, kstat_waitq_exit);
		ST_DO_ERRSTATS(un, st_transerrs);
		error_number = EIO;
		st_set_pe_flag(un);
		break;

	}

	st_bioerror(bp, error_number);
	st_done_and_mutex_exit(un, bp);
}


static void
st_recover(void *arg)
{
	st_err_info *const errinfo = (st_err_info *)arg;
	uchar_t com = errinfo->ei_failed_pkt.pkt_cdbp[0];
	struct scsi_tape *un;
	tapepos_t cur_pos;
	int rval;
	errstate status = COMMAND_DONE_ERROR;
	recov_info *rcv;
	buf_t *bp;


	rcv = errinfo->ei_failed_pkt.pkt_private;
	ASSERT(rcv->privatelen == sizeof (recov_info));
	bp = rcv->cmd_bp;

	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));

	ASSERT(un != NULL);

	mutex_enter(ST_MUTEX);

	ST_FUNC(ST_DEVINFO, st_recover);

	ST_CDB(ST_DEVINFO, "Recovering command",
	    (caddr_t)errinfo->ei_failed_pkt.pkt_cdbp);
	ST_SENSE(ST_DEVINFO, "sense status for failed command",
	    (caddr_t)&errinfo->ei_failing_status,
	    sizeof (struct scsi_arq_status));
	ST_POS(ST_DEVINFO, rcv->cmd_attrib->recov_pos_type == POS_STARTING ?
	    "starting position for recovery command" :
	    "expected position for recovery command",
	    &errinfo->ei_expected_pos);

	rval = st_test_path_to_device(un);

	ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
	    "st_recover called with %s, TUR returned %d\n",
	    errstatenames[errinfo->ei_error_type], rval);
	/*
	 * If the drive responed to the TUR lets try and get it to sync
	 * any data it might have in the buffer.
	 */
	if (rval == 0 && rcv->cmd_attrib->chg_tape_data) {
		rval = st_rcmd(un, SCMD_WRITE_FILE_MARK, 0, SYNC_CMD);
		if (rval) {
			ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
			    "st_recover failed to flush, returned %d\n", rval);
			st_recov_ret(un, errinfo, COMMAND_DONE_ERROR);
			return;
		}
	}
	switch (errinfo->ei_error_type) {
	case ATTEMPT_RETRY:
	case COMMAND_TIMEOUT:
	case DEVICE_RESET:
	case PATH_FAILED:
		/*
		 * For now if we can't talk to the device we are done.
		 * If the drive is reserved we can try to get it back.
		 */
		if (rval != 0 && rval != EACCES) {
			st_recov_ret(un, errinfo, COMMAND_DONE_ERROR);
			return;
		}

		/*
		 * If reservation conflict and do a preempt, fail it.
		 */
		if ((un->un_rsvd_status &
		    (ST_APPLICATION_RESERVATIONS | ST_RESERVE)) != 0) {
			if ((errinfo->ei_failed_pkt.pkt_cdbp[0] ==
			    SCMD_PERSISTENT_RESERVE_OUT) &&
			    (errinfo->ei_failed_pkt.pkt_cdbp[1] ==
			    ST_SA_SCSI3_PREEMPT) &&
			    (SCBP_C(&errinfo->ei_failed_pkt) ==
			    STATUS_RESERVATION_CONFLICT)) {
				st_recov_ret(un, errinfo, COMMAND_DONE_ERROR);
				return;
			}
		}

		/*
		 * If we have already set a scsi II reserve and get a
		 * conflict on a scsi III type reserve fail without
		 * any attempt to recover.
		 */
		if ((un->un_rsvd_status & ST_RESERVE | ST_PRESERVE_RESERVE) &&
		    (errinfo->ei_failed_pkt.pkt_cdbp[0] ==
		    SCMD_PERSISTENT_RESERVE_OUT) ||
		    (errinfo->ei_failed_pkt.pkt_cdbp[0] ==
		    SCMD_PERSISTENT_RESERVE_IN)) {
			st_recov_ret(un, errinfo, COMMAND_DONE_EACCES);
			return;
		}

		/*
		 * If scsi II lost reserve try and get it back.
		 */
		if ((((un->un_rsvd_status &
		    (ST_LOST_RESERVE | ST_APPLICATION_RESERVATIONS)) ==
		    ST_LOST_RESERVE)) &&
		    (errinfo->ei_failed_pkt.pkt_cdbp[0] != SCMD_RELEASE)) {
			rval = st_reserve_release(un, ST_RESERVE,
			    st_uscsi_rcmd);
			if (rval != 0) {
				if (st_take_ownership(un, st_uscsi_rcmd) != 0) {
					st_recov_ret(un, errinfo,
					    COMMAND_DONE_EACCES);
					return;
				}
			}
			un->un_rsvd_status |= ST_RESERVE;
			un->un_rsvd_status &= ~(ST_RELEASE | ST_LOST_RESERVE |
			    ST_RESERVATION_CONFLICT | ST_INITIATED_RESET);
		}
		rval = st_make_sure_mode_data_is_correct(un, st_uscsi_rcmd);
		if (rval) {
			st_recov_ret(un, errinfo, COMMAND_DONE_ERROR);
			return;
		}
		break;
	case DEVICE_TAMPER:
		/*
		 * Check if the ASC/ASCQ says mode data has changed.
		 */
		if ((errinfo->ei_failing_status.sts_sensedata.es_add_code ==
		    0x2a) &&
		    (errinfo->ei_failing_status.sts_sensedata.es_qual_code ==
		    0x01)) {
			/*
			 * See if mode sense changed.
			 */
			rval = st_make_sure_mode_data_is_correct(un,
			    st_uscsi_rcmd);
			if (rval) {
				st_recov_ret(un, errinfo, COMMAND_DONE_ERROR);
				return;
			}
		}
		/*
		 * if we have a media id and its not bogus.
		 * Check to see if it the same.
		 */
		if (un->un_media_id != NULL && un->un_media_id != bogusID) {
			rval = st_get_media_identification(un, st_uscsi_rcmd);
			if (rval == ESPIPE) {
				st_recov_ret(un, errinfo, COMMAND_DONE_EACCES);
				return;
			}
		}
		break;
	default:
		ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
		    "Unhandled error type %s in st_recover() 0x%x\n",
		    errstatenames[errinfo->ei_error_type], com);
	}

	/*
	 * if command is retriable retry it.
	 * Special case here. The command attribute for SCMD_REQUEST_SENSE
	 * does not say that it is retriable. That because if you reissue a
	 * request sense and the target responds the sense data will have
	 * been consumed and no long be valid. If we get a busy status on
	 * request sense while the state is ST_STATE_SENSING this will
	 * reissue that pkt.
	 *
	 * XXX If this request sense gets sent to a different port then
	 * the original command that failed was sent on it will not get
	 * valid sense data for that command.
	 */
	if (rcv->cmd_attrib->retriable || un->un_rqs_bp == bp) {
		status = st_recover_reissue_pkt(un, &errinfo->ei_failed_pkt);

	/*
	 * if drive doesn't support read position we are done
	 */
	} else if (un->un_read_pos_type == NO_POS) {
		status = COMMAND_DONE_ERROR;
	/*
	 * If this command results in a changed tape position,
	 * lets see where we are.
	 */
	} else if (rcv->cmd_attrib->chg_tape_pos) {
		/*
		 * XXX May be a reason to choose a different type here.
		 * Long format has file position information.
		 * Short and Extended have information about whats
		 * in the buffer. St's positioning assumes in the buffer
		 * to be the same as on tape.
		 */
		rval = st_compare_expected_position(un, errinfo,
		    rcv->cmd_attrib, &cur_pos);
		if (rval == 0) {
			status = COMMAND_DONE;
		} else if (rval == EAGAIN) {
			status = st_recover_reissue_pkt(un,
			    &errinfo->ei_failed_pkt);
		} else {
			status = COMMAND_DONE_ERROR;
		}
	} else {
		ASSERT(0);
	}

	st_recov_ret(un, errinfo, status);
}

static void
st_recov_cb(struct scsi_pkt *pkt)
{
	struct scsi_tape *un;
	struct buf *bp;
	recov_info *rcv;
	errstate action = COMMAND_DONE_ERROR;
	int timout = ST_TRAN_BUSY_TIMEOUT; /* short (default) timeout */

	/*
	 * Get the buf from the packet.
	 */
	rcv = pkt->pkt_private;
	ASSERT(rcv->privatelen == sizeof (recov_info));
	bp = rcv->cmd_bp;

	/*
	 * get the unit from the buf.
	 */
	un = ddi_get_soft_state(st_state, MTUNIT(bp->b_edev));
	ASSERT(un != NULL);

	ST_FUNC(ST_DEVINFO, st_recov_cb);

	mutex_enter(ST_MUTEX);

	ASSERT(bp == un->un_recov_buf);


	switch (pkt->pkt_reason) {
	case CMD_CMPLT:
		if (un->un_arq_enabled && pkt->pkt_state & STATE_ARQ_DONE) {
			action = st_handle_autosense(un, bp, &rcv->pos);
		} else  if ((SCBP(pkt)->sts_busy) ||
		    (SCBP(pkt)->sts_chk) ||
		    (SCBP(pkt)->sts_vu7)) {
			action = st_check_error(un, pkt);
		} else {
			action = COMMAND_DONE;
		}
		break;
	case CMD_TIMEOUT:
		action = COMMAND_TIMEOUT;
		break;
	case CMD_TRAN_ERR:
		action = QUE_COMMAND;
		break;
	case CMD_DEV_GONE:
		if (un->un_multipath)
			action = PATH_FAILED;
		else
			action = COMMAND_DONE_ERROR;
		break;
	default:
		ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
		    "pkt_reason not handled yet %s",
		    scsi_rname(pkt->pkt_reason));
		action = COMMAND_DONE_ERROR;
	}

	/*
	 * check for undetected path failover.
	 */
	if (un->un_multipath) {
		if (scsi_pkt_allocated_correctly(pkt) &&
		    (un->un_last_path_instance != pkt->pkt_path_instance)) {
			if (un->un_state > ST_STATE_OPENING) {
				ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
				    "Failover detected in recovery, action is "
				    "%s\n", errstatenames[action]);
			}
			un->un_last_path_instance = pkt->pkt_path_instance;
		}
	}

	ST_RECOV(ST_DEVINFO, st_label, CE_WARN,
	    "Recovery call back got %s status on %s\n",
	    errstatenames[action], st_print_scsi_cmd(pkt->pkt_cdbp[0]));

	switch (action) {
	case COMMAND_DONE:
		break;

	case COMMAND_DONE_EACCES:
		bioerror(bp, EACCES);
		break;

	case COMMAND_DONE_ERROR_RECOVERED: /* XXX maybe wrong */
		ASSERT(0);
		break;

	case COMMAND_TIMEOUT:
	case COMMAND_DONE_ERROR:
		bioerror(bp, EIO);
		break;

	case DEVICE_RESET:
	case QUE_BUSY_COMMAND:
	case PATH_FAILED:
		/* longish timeout */
		timout = ST_STATUS_BUSY_TIMEOUT;
		/* FALLTHRU */
	case QUE_COMMAND:
	case DEVICE_TAMPER:
	case ATTEMPT_RETRY:
		/*
		 * let st_handle_intr_busy put this bp back on waitq and make
		 * checks to see if it is ok to requeue the command.
		 */
		ST_DO_KSTATS(bp, kstat_runq_back_to_waitq);

		/*
		 * Save the throttle before setting up the timeout
		 */
		if (un->un_throttle) {
			un->un_last_throttle = un->un_throttle;
		}
		mutex_exit(ST_MUTEX);
		if (st_handle_intr_busy(un, bp, timout) == 0) {
			return;		/* timeout is setup again */
		}
		mutex_enter(ST_MUTEX);
		un->un_pos.pmode = invalid;
		un->un_err_resid = bp->b_resid = bp->b_bcount;
		st_bioerror(bp, EIO);
		st_set_pe_flag(un);
		break;

	default:
		ST_DEBUG(ST_DEVINFO, st_label, CE_PANIC,
		    "Unhandled recovery state 0x%x\n", action);
		un->un_pos.pmode = invalid;
		un->un_err_resid = bp->b_resid = bp->b_bcount;
		st_bioerror(bp, EIO);
		st_set_pe_flag(un);
		break;
	}

	st_done_and_mutex_exit(un, bp);
}

static int
st_rcmd(struct scsi_tape *un, int com, int64_t count, int wait)
{
	struct buf *bp;
	int err;

	ST_FUNC(ST_DEVINFO, st_rcmd);

	ST_DEBUG3(ST_DEVINFO, st_label, SCSI_DEBUG,
	    "st_rcmd(un = 0x%p, com = 0x%x, count = %"PRIx64", wait = %d)\n",
	    (void *)un, com, count, wait);

	ASSERT(MUTEX_HELD(&un->un_sd->sd_mutex));
	ASSERT(mutex_owned(ST_MUTEX));

#ifdef STDEBUG
	if ((st_debug & 0x7)) {
		st_debug_cmds(un, com, count, wait);
	}
#endif

	while (un->un_recov_buf_busy)
		cv_wait(&un->un_recov_buf_cv, ST_MUTEX);
	un->un_recov_buf_busy = 1;

	bp = un->un_recov_buf;
	bzero(bp, sizeof (buf_t));

	bp->b_flags = (wait) ? B_BUSY : B_BUSY|B_ASYNC;

	err = st_setup_cmd(un, bp, com, count);

	un->un_recov_buf_busy = 0;

	cv_signal(&un->un_recov_buf_cv);

	return (err);
}

/* args used */
static int
st_uscsi_rcmd(struct scsi_tape *un, struct uscsi_cmd *ucmd, int flag)
{
	int rval;
	buf_t *bp;

	ST_FUNC(ST_DEVINFO, st_uscsi_rcmd);
	ASSERT(flag == FKIOCTL);

	/*
	 * Get buffer resources...
	 */
	while (un->un_recov_buf_busy)
		cv_wait(&un->un_recov_buf_cv, ST_MUTEX);
	un->un_recov_buf_busy = 1;

	bp = un->un_recov_buf;
	bzero(bp, sizeof (buf_t));

	bp->b_forw = (struct buf *)(uintptr_t)ucmd->uscsi_cdb[0];
	bp->b_back = (struct buf *)ucmd;

	mutex_exit(ST_MUTEX);
	rval = scsi_uscsi_handle_cmd(un->un_dev, UIO_SYSSPACE, ucmd,
	    st_strategy, bp, NULL);
	mutex_enter(ST_MUTEX);

	ucmd->uscsi_resid = bp->b_resid;

	/*
	 * Free resources
	 */
	un->un_recov_buf_busy = 0;
	cv_signal(&un->un_recov_buf_cv);

	return (rval);
}

/*
 * Add data to scsi_pkt to help know what to do if the command fails.
 */
static void
st_add_recovery_info_to_pkt(struct scsi_tape *un, buf_t *bp,
    struct scsi_pkt *pkt)
{
	uint64_t count;
	recov_info *rinfo = (recov_info *)pkt->pkt_private;

	ST_FUNC(ST_DEVINFO, st_add_recovery_info_to_pkt);

	ASSERT(rinfo->privatelen == sizeof (pkt_info) ||
	    rinfo->privatelen == sizeof (recov_info));

	SET_BP_PKT(bp, pkt);
	rinfo->cmd_bp = bp;

	if (rinfo->privatelen != sizeof (recov_info)) {
		return;
	}

	rinfo->cmd_bp = bp;

	rinfo->cmd_attrib = NULL;

	/*
	 * lookup the command attributes and add them to the recovery info.
	 */
	rinfo->cmd_attrib = st_lookup_cmd_attribute(pkt->pkt_cdbp[0]);

	ASSERT(rinfo->cmd_attrib);

	/*
	 * For commands that there is no way to figure the expected position
	 * once completed, we save the position the command was started from
	 * so that if they fail we can position back and try again.
	 * This has already been done in st_cmd() or st_iscsi_cmd().
	 */
	if (rinfo->cmd_attrib->recov_pos_type == POS_STARTING) {
		/* save current position as the starting position. */
		COPY_POS(&rinfo->pos, &un->un_pos);
		un->un_running.pmode = invalid;
		return;
	}

	/*
	 * Don't want to update the running position for recovery.
	 */
	if (bp == un->un_recov_buf) {
		rinfo->pos.pmode = un->un_running.pmode;
		return;
	}
	/*
	 * If running position is invalid copy the current position.
	 * Running being set invalid means we are not in a read, write
	 * or write filemark sequence.
	 * We'll copy the current position and start from there.
	 */
	if (un->un_running.pmode == invalid) {
		COPY_POS(&un->un_running, &un->un_pos);
		COPY_POS(&rinfo->pos, &un->un_running);
	} else {
		COPY_POS(&rinfo->pos, &un->un_running);
		if (rinfo->pos.pmode == legacy) {
			/*
			 * Always should be more logical blocks then
			 * data blocks and files marks.
			 */
			ASSERT((rinfo->pos.blkno >= 0) ?
			    rinfo->pos.lgclblkno >=
			    (rinfo->pos.blkno + rinfo->pos.fileno) : 1);
		}
	}

	/*
	 * If the command is not expected to change the drive position
	 * then the running position should be the expected position.
	 */
	if (rinfo->cmd_attrib->chg_tape_pos == 0) {
		ASSERT(rinfo->cmd_attrib->chg_tape_direction == DIR_NONE);
		return;
	}

	if (rinfo->cmd_attrib->explicit_cmd_set) {
		ASSERT(rinfo->pos.pmode != invalid);
		ASSERT(rinfo->cmd_attrib->get_cnt);
		count = rinfo->cmd_attrib->get_cnt(pkt->pkt_cdbp);
		/*
		 * This is a user generated CDB.
		 */
		if (bp == un->un_sbufp) {
			uint64_t lbn;

			lbn = rinfo->cmd_attrib->get_lba(pkt->pkt_cdbp);

			/*
			 * See if this CDB will generate a locate or change
			 * partition.
			 */
			if ((lbn != un->un_running.lgclblkno) ||
			    (pkt->pkt_cdbp[3] != un->un_running.partition)) {
				rinfo->pos.partition = pkt->pkt_cdbp[3];
				rinfo->pos.pmode = logical;
				rinfo->pos.lgclblkno = lbn;
				un->un_running.partition = pkt->pkt_cdbp[3];
				un->un_running.pmode = logical;
				un->un_running.lgclblkno = lbn;
			}
		} else {
			uint64_t lbn = un->un_running.lgclblkno;

			pkt->pkt_cdbp[3]  = (uchar_t)un->un_running.partition;

			pkt->pkt_cdbp[4]  = (uchar_t)(lbn >> 56);
			pkt->pkt_cdbp[5]  = (uchar_t)(lbn >> 48);
			pkt->pkt_cdbp[6]  = (uchar_t)(lbn >> 40);
			pkt->pkt_cdbp[7]  = (uchar_t)(lbn >> 32);
			pkt->pkt_cdbp[8]  = (uchar_t)(lbn >> 24);
			pkt->pkt_cdbp[9]  = (uchar_t)(lbn >> 16);
			pkt->pkt_cdbp[10] = (uchar_t)(lbn >> 8);
			pkt->pkt_cdbp[11] = (uchar_t)(lbn);
		}
		rinfo->pos.lgclblkno += count;
		rinfo->pos.blkno += count;
		un->un_running.lgclblkno += count;
		return;
	}

	if (rinfo->cmd_attrib->chg_tape_pos) {

		/* should not have got an invalid position from running. */
		if (un->un_mediastate == MTIO_INSERTED) {
			ASSERT(rinfo->pos.pmode != invalid);
		}

		/* should have either a get count or or get lba function */
		ASSERT(rinfo->cmd_attrib->get_cnt != NULL ||
		    rinfo->cmd_attrib->get_lba != NULL);

		/* only explicit commands have both and they're handled above */
		ASSERT(!(rinfo->cmd_attrib->get_cnt != NULL &&
		    rinfo->cmd_attrib->get_lba != NULL));

		/* if it has a get count function */
		if (rinfo->cmd_attrib->get_cnt != NULL) {
			count = rinfo->cmd_attrib->get_cnt(pkt->pkt_cdbp);
			if (count == 0) {
				return;
			}
			/*
			 * Changes position but doesn't transfer data.
			 * i.e. rewind, write_file_mark and load.
			 */
			if (rinfo->cmd_attrib->transfers_data == TRAN_NONE) {
				switch (rinfo->cmd_attrib->chg_tape_direction) {
				case DIR_NONE: /* Erase */
					ASSERT(rinfo->cmd_attrib->cmd ==
					    SCMD_ERASE);
					break;
				case DIR_FORW: /* write_file_mark */
					rinfo->pos.fileno += count;
					rinfo->pos.lgclblkno += count;
					rinfo->pos.blkno = 0;
					un->un_running.fileno += count;
					un->un_running.lgclblkno += count;
					un->un_running.blkno = 0;
					break;
				case DIR_REVC: /* rewind */
					rinfo->pos.fileno = 0;
					rinfo->pos.lgclblkno = 0;
					rinfo->pos.blkno = 0;
					rinfo->pos.eof = ST_NO_EOF;
					rinfo->pos.pmode = legacy;
					un->un_running.fileno = 0;
					un->un_running.lgclblkno = 0;
					un->un_running.blkno = 0;
					un->un_running.eof = ST_NO_EOF;
					if (un->un_running.pmode != legacy)
						un->un_running.pmode = legacy;
					break;
				case DIR_EITH: /* Load unload */
					ASSERT(rinfo->cmd_attrib->cmd ==
					    SCMD_LOAD);
					switch (count & (LD_LOAD | LD_RETEN |
					    LD_RETEN | LD_HOLD)) {
					case LD_UNLOAD:
					case LD_RETEN:
					case LD_HOLD:
					case LD_LOAD | LD_HOLD:
					case LD_EOT | LD_HOLD:
					case LD_RETEN | LD_HOLD:
						rinfo->pos.pmode = invalid;
						un->un_running.pmode = invalid;
						break;
					case LD_EOT:
					case LD_LOAD | LD_EOT:
						rinfo->pos.eof = ST_EOT;
						rinfo->pos.pmode = invalid;
						un->un_running.eof = ST_EOT;
						un->un_running.pmode = invalid;
						break;
					case LD_LOAD:
					case LD_RETEN | LD_LOAD:
						rinfo->pos.fileno = 0;
						rinfo->pos.lgclblkno = 0;
						rinfo->pos.blkno = 0;
						rinfo->pos.eof = ST_NO_EOF;
						rinfo->pos.pmode = legacy;
						un->un_running.fileno = 0;
						un->un_running.lgclblkno = 0;
						un->un_running.blkno = 0;
						un->un_running.eof = ST_NO_EOF;
						break;
					default:
						ASSERT(0);
					}
					break;
				default:
					ASSERT(0);
					break;
				}
			} else {
				/*
				 * Changes position and does transfer data.
				 * i.e. read or write.
				 */
				switch (rinfo->cmd_attrib->chg_tape_direction) {
				case DIR_FORW:
					rinfo->pos.lgclblkno += count;
					rinfo->pos.blkno += count;
					un->un_running.lgclblkno += count;
					un->un_running.blkno += count;
					break;
				case DIR_REVC:
					rinfo->pos.lgclblkno -= count;
					rinfo->pos.blkno -= count;
					un->un_running.lgclblkno -= count;
					un->un_running.blkno -= count;
					break;
				default:
					ASSERT(0);
					break;
				}
			}
		} else if (rinfo->cmd_attrib->get_lba != NULL) {
			/* Have a get LBA fuction. i.e. Locate */
			ASSERT(rinfo->cmd_attrib->chg_tape_direction ==
			    DIR_EITH);
			count = rinfo->cmd_attrib->get_lba(pkt->pkt_cdbp);
			un->un_running.lgclblkno = count;
			un->un_running.blkno = 0;
			un->un_running.fileno = 0;
			un->un_running.pmode = logical;
			rinfo->pos.lgclblkno = count;
			rinfo->pos.pmode = invalid;
		} else {
			ASSERT(0);
		}
		return;
	}

	ST_CDB(ST_DEVINFO, "Unhanded CDB for position prediction",
	    (char *)pkt->pkt_cdbp);

}

static int
st_make_sure_mode_data_is_correct(struct scsi_tape *un, ubufunc_t ubf)
{
	int rval;

	ST_FUNC(ST_DEVINFO, st_make_sure_mode_data_is_correct);

	/*
	 * check to see if mode data has changed.
	 */
	rval = st_check_mode_for_change(un, ubf);
	if (rval) {
		rval = st_gen_mode_select(un, ubf, un->un_mspl,
		    sizeof (struct seq_mode));
	}
	if (un->un_tlr_flag != TLR_NOT_SUPPORTED) {
		rval |= st_set_target_TLR_mode(un, ubf);
	}
	return (rval);
}

static int
st_check_mode_for_change(struct scsi_tape *un, ubufunc_t ubf)
{
	struct seq_mode *current;
	int rval;
	int i;
	caddr_t this;
	caddr_t that;

	ST_FUNC(ST_DEVINFO, st_check_mode_for_change);

	/* recovery called with mode tamper before mode selection */
	if (un->un_comp_page == (ST_DEV_DATACOMP_PAGE | ST_DEV_CONFIG_PAGE)) {
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Mode Select not done yet");
		return (0);
	}

	current = kmem_zalloc(sizeof (struct seq_mode), KM_SLEEP);

	rval = st_gen_mode_sense(un, ubf, un->un_comp_page, current,
	    sizeof (struct seq_mode));
	if (rval != 0) {
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Mode Sense for mode verification failed");
		kmem_free(current, sizeof (struct seq_mode));
		return (rval);
	}

	this = (caddr_t)current;
	that = (caddr_t)un->un_mspl;

	rval = bcmp(this, that, sizeof (struct seq_mode));
	if (rval == 0) {
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Found no changes in mode data");
	}
#ifdef STDEBUG
	else {
		for (i = 1; i < sizeof (struct seq_mode); i++) {
			if (this[i] != that[i]) {
				ST_RECOV(ST_DEVINFO, st_label, CE_CONT,
				    "sense data changed at byte %d was "
				    "0x%x now 0x%x", i,
				    (uchar_t)that[i], (uchar_t)this[i]);
			}
		}
	}
#endif
	kmem_free(current, sizeof (struct seq_mode));

	return (rval);
}

static int
st_test_path_to_device(struct scsi_tape *un)
{
	int rval = 0;
	int limit = st_retry_count;

	ST_FUNC(ST_DEVINFO, st_test_path_to_device);

	/*
	 * XXX Newer drives may not RESEVATION CONFLICT a TUR.
	 */
	do {
		if (rval != 0) {
			mutex_exit(ST_MUTEX);
			delay(drv_usectohz(1000000));
			mutex_enter(ST_MUTEX);
		}
		rval = st_rcmd(un, SCMD_TEST_UNIT_READY, 0, SYNC_CMD);
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "ping TUR returned 0x%x", rval);
		limit--;
	} while (((rval == EACCES) || (rval == EBUSY)) && limit);

	if (un->un_status == KEY_NOT_READY || un->un_mediastate == MTIO_EJECTED)
		rval = 0;

	return (rval);
}

/*
 * Does read position using recov_buf and doesn't update un_pos.
 * Does what ever kind of read position you want.
 */
static int
st_recovery_read_pos(struct scsi_tape *un, read_p_types type,
    read_pos_data_t *raw)
{
	int rval;
	struct uscsi_cmd cmd;
	struct scsi_arq_status status;
	char cdb[CDB_GROUP1];

	ST_FUNC(ST_DEVINFO, st_recovery_read_pos);
	bzero(&cmd, sizeof (cmd));

	cdb[0] = SCMD_READ_POSITION;
	cdb[1] = type;
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	cdb[5] = 0;
	cdb[6] = 0;
	cdb[7] = 0;
	cdb[8] = (type == EXT_POS) ? 28 : 0;
	cdb[9] = 0;

	cmd.uscsi_flags = USCSI_READ | USCSI_RQENABLE;
	cmd.uscsi_timeout = un->un_dp->non_motion_timeout;
	cmd.uscsi_cdb = cdb;
	cmd.uscsi_cdblen = sizeof (cdb);
	cmd.uscsi_rqlen = sizeof (status);
	cmd.uscsi_rqbuf = (caddr_t)&status;
	cmd.uscsi_bufaddr = (caddr_t)raw;
	switch (type) {
	case SHORT_POS:
		cmd.uscsi_buflen = sizeof (tape_position_t);
		break;
	case LONG_POS:
		cmd.uscsi_buflen = sizeof (tape_position_long_t);
		break;
	case EXT_POS:
		cmd.uscsi_buflen = sizeof (tape_position_ext_t);
		break;
	default:
		ASSERT(0);
	}

	rval = st_uscsi_rcmd(un, &cmd, FKIOCTL);
	if (cmd.uscsi_status) {
		rval = EIO;
	}
	return (rval);
}

static int
st_recovery_get_position(struct scsi_tape *un, tapepos_t *read,
    read_pos_data_t *raw)
{
	int rval;
	read_p_types type = un->un_read_pos_type;

	ST_FUNC(ST_DEVINFO, st_recovery_get_position);

	do {
		rval = st_recovery_read_pos(un, type, raw);
		if (rval != 0) {
			switch (type) {
			case SHORT_POS:
				type = NO_POS;
				break;

			case LONG_POS:
				type = EXT_POS;
				break;

			case EXT_POS:
				type = SHORT_POS;
				break;

			default:
				type = LONG_POS;
				break;

			}
		} else {
			if (type != un->un_read_pos_type) {
				un->un_read_pos_type = type;
			}
			break;
		}
	} while (type != NO_POS);

	if (rval == 0) {
		rval = st_interpret_read_pos(un, read, type,
		    sizeof (read_pos_data_t), (caddr_t)raw, 1);
	}
	return (rval);
}

/*
 * based on the command do we retry, continue or give up?
 * possable return values?
 *	zero do nothing looks fine.
 *	EAGAIN retry.
 *	EIO failed makes no sense.
 */
static int
st_compare_expected_position(struct scsi_tape *un, st_err_info *ei,
    cmd_attribute const * cmd_att, tapepos_t *read)
{
	int rval;
	read_pos_data_t *readp_datap;

	ST_FUNC(ST_DEVINFO, st_compare_expected_position);

	ASSERT(un != NULL);
	ASSERT(ei != NULL);
	ASSERT(read != NULL);
	ASSERT(cmd_att->chg_tape_pos);

	COPY_POS(read, &ei->ei_expected_pos);

	readp_datap = kmem_zalloc(sizeof (read_pos_data_t), KM_SLEEP);

	rval = st_recovery_get_position(un, read, readp_datap);

	kmem_free(readp_datap, sizeof (read_pos_data_t));

	if (rval != 0) {
		return (EIO);
	}

	ST_POS(ST_DEVINFO, "st_compare_expected_position", read);

	if ((read->pmode == invalid) ||
	    (ei->ei_expected_pos.pmode == invalid)) {
		return (EIO);
	}

	/*
	 * Command that changes tape position and have an expected position
	 * if it were to chave completed sucessfully.
	 */
	if (cmd_att->recov_pos_type == POS_EXPECTED) {
		uint32_t count;
		int64_t difference;
		uchar_t reposition = 0;

		ASSERT(cmd_att->get_cnt);
		count = cmd_att->get_cnt(ei->ei_failed_pkt.pkt_cdbp);

		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Got count from CDB and it was %d\n", count);

		/*
		 * At expected?
		 */
		if (read->lgclblkno == ei->ei_expected_pos.lgclblkno) {
			ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
			    "Found drive to be at expected position\n");

			/*
			 * If the command should move tape and it got a busy
			 * it shouldn't be in the expected position.
			 */
			if (ei->ei_failing_status.sts_status.sts_busy != 0) {
				reposition = 1;

			/*
			 * If the command doesn't transfer data should be good.
			 */
			} else if (cmd_att->transfers_data == TRAN_NONE) {
				return (0); /* Good */

			/*
			 * Command transfers data, should have done so.
			 */
			} else if (ei->ei_failed_pkt.pkt_state &
			    STATE_XFERRED_DATA) {
				return (0); /* Good */
			} else {
				reposition = 1;
			}
		}

		if (cmd_att->chg_tape_direction == DIR_FORW) {
			difference =
			    ei->ei_expected_pos.lgclblkno - read->lgclblkno;

			ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
			    "difference between expected and actual is %"
			    PRId64"\n", difference);
			if (count == difference && reposition == 0) {
				ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
				    "Found failed FORW command, retrying\n");
				return (EAGAIN);
			}

			/*
			 * If rewound or somewhere between the starting position
			 * and the expected position (partial read or write).
			 * Locate to the starting position and try the whole
			 * thing over again.
			 */
			if ((read->lgclblkno == 0) ||
			    ((difference > 0) && (difference < count))) {
				rval = st_logical_block_locate(un,
				    st_uscsi_rcmd, read,
				    ei->ei_expected_pos.lgclblkno - count,
				    ei->ei_expected_pos.partition);
				if (rval == 0) {
					ST_RECOV(ST_DEVINFO, st_label,
					    CE_NOTE, "reestablished FORW"
					    " command retrying\n");
					return (EAGAIN);
				}
			/*
			 * This handles flushed read ahead on the drive or
			 * an aborted read that presents as a busy and advanced
			 * the tape position.
			 */
			} else if ((cmd_att->transfers_data == TRAN_READ) &&
			    ((difference < 0) || (reposition == 1))) {
				rval = st_logical_block_locate(un,
				    st_uscsi_rcmd, read,
				    ei->ei_expected_pos.lgclblkno - count,
				    ei->ei_expected_pos.partition);
				if (rval == 0) {
					ST_RECOV(ST_DEVINFO, st_label,
					    CE_NOTE, "reestablished FORW"
					    " read command retrying\n");
					return (EAGAIN);
				}
			/*
			 * XXX swag seeing difference of 2 on write filemark.
			 * If the space to the starting position works on a
			 * write that means the previous write made it to tape.
			 * If not we lost data and have to give up.
			 *
			 * The plot thickens. Now I am attempting to cover a
			 * count of 1 and a differance of 2 on a write.
			 */
			} else if ((difference > count) || (reposition == 1)) {
				rval = st_logical_block_locate(un,
				    st_uscsi_rcmd, read,
				    ei->ei_expected_pos.lgclblkno - count,
				    ei->ei_expected_pos.partition);
				if (rval == 0) {
					ST_RECOV(ST_DEVINFO, st_label,
					    CE_NOTE, "reestablished FORW"
					    " write command retrying\n");
					return (EAGAIN);
				}
				ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
				    "Seek to block %"PRId64" returned %d\n",
				    ei->ei_expected_pos.lgclblkno - count,
				    rval);
			} else {
				ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
				    "Not expected transfers_data = %d "
				    "difference = %"PRId64,
				    cmd_att->transfers_data, difference);
			}

			return (EIO);

		} else if (cmd_att->chg_tape_direction == DIR_REVC) {
			/* Don't think we can write backwards */
			ASSERT(cmd_att->transfers_data != TRAN_WRTE);
			difference =
			    read->lgclblkno - ei->ei_expected_pos.lgclblkno;
			ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
			    "difference between expected and actual is %"
			    PRId64"\n", difference);
			if (count == difference && reposition == 0) {
				ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
				    "Found failed REVC command, retrying\n");
				return (EAGAIN);
			}
			if ((read->lgclblkno == 0) ||
			    ((difference > 0) && (difference < count))) {
				rval = st_logical_block_locate(un,
				    st_uscsi_rcmd, read,
				    ei->ei_expected_pos.lgclblkno + count,
				    ei->ei_expected_pos.partition);
				if (rval == 0) {
					ST_RECOV(ST_DEVINFO, st_label,
					    CE_NOTE, "reestablished REVC"
					    " command retrying\n");
					return (EAGAIN);
				}
			/* This handles read ahead in reverse direction */
			} else if ((cmd_att->transfers_data == TRAN_READ) &&
			    (difference < 0) || (reposition == 1)) {
				rval = st_logical_block_locate(un,
				    st_uscsi_rcmd, read,
				    ei->ei_expected_pos.lgclblkno - count,
				    ei->ei_expected_pos.partition);
				if (rval == 0) {
					ST_RECOV(ST_DEVINFO, st_label,
					    CE_NOTE, "reestablished REVC"
					    " read command retrying\n");
					return (EAGAIN);
				}
			} else {
				ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
				    "Not expected transfers_data = %d "
				    "difference = %"PRId64,
				    cmd_att->transfers_data, difference);
			}
			return (EIO);

		} else {
			/*
			 * Commands that change tape position either
			 * direction or don't change position should not
			 * get here.
			 */
			ASSERT(0);
		}
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Didn't find a recoverable position, Failing\n");

	/*
	 * Command that changes tape position and can only be recovered
	 * by going back to the point of origin and retrying.
	 *
	 * Example SCMD_SPACE.
	 */
	} else if (cmd_att->recov_pos_type == POS_STARTING) {
		/*
		 * This type of command stores the starting position.
		 * If the read position is the starting position,
		 * reissue the command.
		 */
		if (ei->ei_expected_pos.lgclblkno == read->lgclblkno) {
			ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
			    "Found Space command at starting position, "
			    "Reissuing\n");
			return (EAGAIN);
		}
		/*
		 * Not in the position that the command was originally issued,
		 * Attempt to locate to that position.
		 */
		rval = st_logical_block_locate(un, st_uscsi_rcmd, read,
		    ei->ei_expected_pos.lgclblkno,
		    ei->ei_expected_pos.partition);
		if (rval) {
			ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
			    "Found Space at an unexpected position and locate "
			    "back to starting position failed\n");
			return (EIO);
		}
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Found Space at an unexpected position and locate "
		    "back to starting position worked, Reissuing\n");
		return (EAGAIN);
	}
	st_print_position(ST_DEVINFO, st_label, CE_NOTE,
	    "Unhandled attribute/expected position", &ei->ei_expected_pos);
	st_print_position(ST_DEVINFO, st_label, CE_NOTE,
	    "Read position above did not make sense", read);
	ASSERT(0);
	return (EIO);
}

static errstate
st_recover_reissue_pkt(struct scsi_tape *un, struct scsi_pkt *oldpkt)
{
	buf_t *bp;
	buf_t *pkt_bp;
	struct scsi_pkt *newpkt;
	cmd_attribute const *attrib;
	recov_info *rcv = oldpkt->pkt_private;
	uint_t cdblen;
	int queued = 0;
	int rval;
	int flags = 0;
	int stat_size =
	    (un->un_arq_enabled ? sizeof (struct scsi_arq_status) : 1);

	ST_FUNC(ST_DEVINFO, st_recover_reissue_pkt);

	bp = rcv->cmd_bp;

	if (rcv->privatelen == sizeof (recov_info)) {
		attrib = rcv->cmd_attrib;
	} else {
		attrib = st_lookup_cmd_attribute(oldpkt->pkt_cdbp[0]);
	}

	/*
	 * Some non-uscsi commands use the b_bcount for values that
	 * have nothing to do with how much data is transfered.
	 * In those cases we need to hide the buf_t from scsi_init_pkt().
	 */
	if ((BP_UCMD(bp)) && (bp->b_bcount)) {
		pkt_bp = bp;
	} else if (attrib->transfers_data == TRAN_NONE) {
		pkt_bp = NULL;
	} else {
		pkt_bp = bp;
	}

	/*
	 * if this is a queued command make sure it the only one in the
	 * run queue.
	 */
	if (bp != un->un_sbufp && bp != un->un_recov_buf) {
		ASSERT(un->un_runqf == un->un_runql);
		ASSERT(un->un_runqf == bp);
		queued = 1;
	}

	cdblen = scsi_cdb_size[CDB_GROUPID(oldpkt->pkt_cdbp[0])];

	if (pkt_bp == un->un_rqs_bp) {
		flags |= PKT_CONSISTENT;
		stat_size = 1;
	}

	newpkt = scsi_init_pkt(ROUTE, NULL, pkt_bp, cdblen,
	    stat_size, rcv->privatelen, flags, NULL_FUNC, NULL);
	if (newpkt == NULL) {
		ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
		    "Reissue pkt scsi_init_pkt() failure\n");
		return (COMMAND_DONE_ERROR);
	}

	ASSERT(newpkt->pkt_resid == 0);
	bp->b_flags &= ~(B_DONE);
	bp->b_resid = 0;
	st_bioerror(bp, 0);

	bcopy(oldpkt->pkt_private, newpkt->pkt_private, rcv->privatelen);

	newpkt->pkt_comp = oldpkt->pkt_comp;
	newpkt->pkt_time = oldpkt->pkt_time;

	bzero(newpkt->pkt_scbp, stat_size);
	bcopy(oldpkt->pkt_cdbp, newpkt->pkt_cdbp, cdblen);

	newpkt->pkt_state = 0;
	newpkt->pkt_statistics = 0;

	/*
	 * oldpkt passed in was a copy of the original.
	 * to distroy we need the address of the original.
	 */
	oldpkt = BP_PKT(bp);

	if (oldpkt == un->un_rqs) {
		ASSERT(bp == un->un_rqs_bp);
		un->un_rqs = newpkt;
	}

	SET_BP_PKT(bp, newpkt);

	scsi_destroy_pkt(oldpkt);

	rval = st_transport(un, newpkt);
	if (rval == TRAN_ACCEPT) {
		return (JUST_RETURN);
	}
	ST_RECOV(ST_DEVINFO, st_label, CE_NOTE,
	    "Reissue pkt st_transport(0x%x) failure\n", rval);
	if (rval != TRAN_BUSY) {
		return (COMMAND_DONE_ERROR);
	}
	mutex_exit(ST_MUTEX);
	rval = st_handle_start_busy(un, bp, ST_TRAN_BUSY_TIMEOUT, queued);
	mutex_enter(ST_MUTEX);
	if (rval) {
		return (COMMAND_DONE_ERROR);
	}

	return (JUST_RETURN);
}

static int
st_transport(struct scsi_tape *un, struct scsi_pkt *pkt)
{
	int status;

	ST_FUNC(ST_DEVINFO, st_transport);

	ST_CDB(ST_DEVINFO, "transport CDB", (caddr_t)pkt->pkt_cdbp);

	mutex_exit(ST_MUTEX);

	status = scsi_transport(pkt);

	mutex_enter(ST_MUTEX);

	return (status);
}

/*
 * Removed the buf_t bp from the queue referenced to by head and tail.
 * Returns the buf_t pointer if it is found in the queue.
 * Returns NULL if it is not found.
 */
static buf_t *
st_remove_from_queue(buf_t **head, buf_t **tail, buf_t *bp)
{
	buf_t *runqbp;
	buf_t *prevbp = NULL;

	for (runqbp = *head; runqbp != 0; runqbp = runqbp->av_forw) {
		if (runqbp == bp) {
			/* found it, is it at the head? */
			if (runqbp == *head) {
				*head = bp->av_forw;
			} else {
				prevbp->av_forw = bp->av_forw;
			}
			if (*tail == bp) {
				*tail = prevbp;
			}
			bp->av_forw = NULL;
			return (bp); /* found and removed */
		}
		prevbp = runqbp;
	}
	return (NULL);
}

/*
 * Adds a buf_t to the queue pointed to by head and tail.
 * Adds it either to the head end or the tail end based on which
 * the passed variable end (head or tail) points at.
 */
static void
st_add_to_queue(buf_t **head, buf_t **tail, buf_t *end, buf_t *bp)
{

	bp->av_forw = NULL;
	if (*head) {
		/* Queue is not empty */
		if (end == *head) {
			/* Add at front of queue */
			bp->av_forw = *head;
			*head = bp;
		} else if (end == *tail) {
			/* Add at end of queue */
			(*tail)->av_forw = bp;
			*tail = bp;
		} else {
			ASSERT(0);
		}
	} else {
		/* Queue is empty */
		*head = bp;
		*tail = bp;
	}
}


static uint64_t
st_get_cdb_g0_rw_count(uchar_t *cdb)
{
	uint64_t count;

	if ((cdb[1]) & 1) {
		/* fixed block mode, the count is the number of blocks */
		count =
		    cdb[2] << 16 |
		    cdb[3] << 8 |
		    cdb[4];
	} else {
		/* variable block mode, the count is the block size */
		count = 1;
	}
	return (count);
}

static uint64_t
st_get_cdb_g0_sign_count(uchar_t *cdb)
{
	uint64_t count;

	count =
	    cdb[2] << 16 |
	    cdb[3] << 8 |
	    cdb[4];
	/*
	 * If the sign bit of the 3 byte value is set, extended it.
	 */
	if (count & 0x800000) {
		count |= 0xffffffffff000000;
	}
	return (count);
}

static uint64_t
st_get_cdb_g0_count(uchar_t *cdb)
{
	uint64_t count;

	count =
	    cdb[2] << 16 |
	    cdb[3] << 8 |
	    cdb[4];
	return (count);
}

static uint64_t
st_get_cdb_g5_rw_cnt(uchar_t *cdb)
{
	uint64_t count;

	if ((cdb[1]) & 1) {
		/* fixed block mode */
		count =
		    cdb[12] << 16 |
		    cdb[13] << 8 |
		    cdb[14];
	} else {
		/* variable block mode */
		count = 1;
	}
	return (count);
}

static uint64_t
st_get_no_count(uchar_t *cdb)
{
	ASSERT(cdb[0] == SCMD_REWIND);
	return ((uint64_t)cdb[0]);
}

static uint64_t
st_get_load_options(uchar_t *cdb)
{
	return ((uint64_t)(cdb[4] | (LD_HOLD << 1)));
}

static uint64_t
st_get_erase_options(uchar_t *cdb)
{
	return (cdb[1] | (cdb[0] << 8));
}

static uint64_t
st_get_cdb_g1_lba(uchar_t *cdb)
{
	uint64_t lba;

	lba =
	    cdb[3] << 24 |
	    cdb[4] << 16 |
	    cdb[5] << 8 |
	    cdb[6];
	return (lba);
}

static uint64_t
st_get_cdb_g5_count(uchar_t *cdb)
{
	uint64_t count =
	    cdb[12] << 16 |
	    cdb[13] << 8 |
	    cdb[14];

	return (count);
}

static uint64_t
st_get_cdb_g4g5_cnt(uchar_t *cdb)
{
	uint64_t lba;

	lba =
	    (uint64_t)cdb[4] << 56 |
	    (uint64_t)cdb[5] << 48 |
	    (uint64_t)cdb[6] << 40 |
	    (uint64_t)cdb[7] << 32 |
	    (uint64_t)cdb[8] << 24 |
	    (uint64_t)cdb[9] << 16 |
	    (uint64_t)cdb[10] << 8 |
	    (uint64_t)cdb[11];
	return (lba);
}

static const cmd_attribute cmd_attributes[] = {
	{ SCMD_READ,
	    1, 0, 1, 0, 0, DIR_FORW, TRAN_READ, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g0_rw_count },
	{ SCMD_WRITE,
	    1, 0, 1, 1, 0, DIR_FORW, TRAN_WRTE, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g0_rw_count },
	{ SCMD_TEST_UNIT_READY,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_REWIND,
	    1, 1, 1, 0, 0, DIR_REVC, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, st_get_no_count },
	{ SCMD_REQUEST_SENSE,
	    0, 0, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_READ_BLKLIM,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_READ_G4,
	    1, 0, 1, 0, 1, DIR_FORW, TRAN_READ, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g5_rw_cnt, st_get_cdb_g4g5_cnt },
	{ SCMD_WRITE_G4,
	    1, 0, 1, 1, 1, DIR_FORW, TRAN_WRTE, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g5_rw_cnt, st_get_cdb_g4g5_cnt },
	{ SCMD_READ_REVERSE,
	    1, 0, 1, 1, 0, DIR_REVC, TRAN_READ, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g0_rw_count },
	{ SCMD_READ_REVERSE_G4,
	    1, 0, 1, 1, 1, DIR_REVC, TRAN_READ, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g5_rw_cnt, st_get_cdb_g4g5_cnt },
	{ SCMD_WRITE_FILE_MARK,
	    1, 0, 1, 1, 0, DIR_FORW, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g0_count },
	{ SCMD_WRITE_FILE_MARK_G4,
	    1, 0, 1, 1, 1, DIR_FORW, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g5_count, st_get_cdb_g4g5_cnt },
	{ SCMD_SPACE,
	    1, 0, 1, 0, 0, DIR_EITH, TRAN_NONE, POS_STARTING,
	    0, 0, 0, st_get_cdb_g0_sign_count },
	{ SCMD_SPACE_G4,
	    1, 0, 1, 0, 0, DIR_EITH, TRAN_NONE, POS_STARTING,
	    0, 0, 0, st_get_cdb_g4g5_cnt },
	{ SCMD_INQUIRY,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_VERIFY_G0,
	    1, 0, 1, 0, 0, DIR_FORW, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g0_rw_count },
	{ SCMD_VERIFY_G4,
	    1, 0, 1, 0, 1, DIR_FORW, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, st_get_cdb_g5_rw_cnt, st_get_cdb_g4g5_cnt },
	{ SCMD_RECOVER_BUF,
	    1, 0, 1, 1, 0, DIR_REVC, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_MODE_SELECT,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_WRTE, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_RESERVE,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_RELEASE,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_ERASE,
	    1, 0, 1, 1, 0, DIR_NONE, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, st_get_erase_options },
	{ SCMD_MODE_SENSE,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_LOAD,
	    1, 1, 1, 0, 0, DIR_EITH, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, st_get_load_options },
	{ SCMD_GDIAG,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    1, 0, 0 },
	{ SCMD_SDIAG,
	    1, 0, 1, 1, 0, DIR_EITH, TRAN_WRTE, POS_EXPECTED,
	    1, 0, 0 },
	{ SCMD_DOORLOCK,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_NONE, POS_EXPECTED,
	    0, 4, 3 },
	{ SCMD_LOCATE,
	    1, 1, 1, 0, 0, DIR_EITH, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, NULL, st_get_cdb_g1_lba },
	{ SCMD_READ_POSITION,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_WRITE_BUFFER,
	    1, 0, 0, 0, 0, DIR_NONE, TRAN_WRTE, POS_EXPECTED,
	    1, 0, 0 },
	{ SCMD_READ_BUFFER,
	    1, 0, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    1, 0, 0 },
	{ SCMD_REPORT_DENSITIES,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_LOG_SELECT_G1,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_WRTE, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_LOG_SENSE_G1,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_PRIN,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_PROUT,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_WRTE, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_READ_ATTRIBUTE,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_WRITE_ATTRIBUTE,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_WRTE, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_LOCATE_G4,
	    1, 1, 1, 0, 0, DIR_EITH, TRAN_NONE, POS_EXPECTED,
	    0, 0, 0, NULL, st_get_cdb_g4g5_cnt },
	{ SCMD_REPORT_LUNS,
	    0, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_SVC_ACTION_IN_G5,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_MAINTENANCE_IN,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_READ, POS_EXPECTED,
	    0, 0, 0 },
	{ SCMD_MAINTENANCE_OUT,
	    1, 1, 0, 0, 0, DIR_NONE, TRAN_WRTE, POS_EXPECTED,
	    0, 0, 0 },
	{ 0xff, /* Default attribute for unsupported commands */
	    1, 0, 0, 0, 0, DIR_NONE, TRAN_NONE, POS_STARTING,
	    1, 0, 0, NULL, NULL }
};

static const cmd_attribute *
st_lookup_cmd_attribute(unsigned char cmd)
{
	int i;
	cmd_attribute const *attribute;

	for (i = 0; i < ST_NUM_MEMBERS(cmd_attributes); i++) {
		attribute = &cmd_attributes[i];
		if (attribute->cmd == cmd) {
			return (attribute);
		}
	}
	ASSERT(attribute);
	return (attribute);
}

static int
st_reset(struct scsi_tape *un, int reset_type)
{
	int rval;

	ASSERT(MUTEX_HELD(&un->un_sd->sd_mutex));

	ST_FUNC(ST_DEVINFO, st_reset);
	un->un_rsvd_status |= ST_INITIATED_RESET;
	mutex_exit(ST_MUTEX);
	do {
		rval = scsi_reset(&un->un_sd->sd_address, reset_type);
		if (rval == 0) {
			switch (reset_type) {
			case RESET_LUN:
				ST_DEBUG3(ST_DEVINFO, st_label, CE_WARN,
				    "LUN reset failed trying target reset");
				reset_type = RESET_TARGET;
				break;
			case RESET_TARGET:
				ST_DEBUG3(ST_DEVINFO, st_label, CE_WARN,
				    "target reset failed trying bus reset");
				reset_type = RESET_BUS;
				break;
			case RESET_BUS:
				ST_DEBUG3(ST_DEVINFO, st_label, CE_WARN,
				    "bus reset failed trying all reset");
				reset_type = RESET_ALL;
			default:
				mutex_enter(ST_MUTEX);
				return (rval);
			}
		}
	} while (rval == 0);
	mutex_enter(ST_MUTEX);
	return (rval);
}

#define	SAS_TLR_MOD_LEN sizeof (struct seq_mode)
static int
st_set_target_TLR_mode(struct scsi_tape *un, ubufunc_t ubf)
{
	int ret;
	int amount = SAS_TLR_MOD_LEN;
	struct seq_mode *mode_data;

	ST_FUNC(ST_DEVINFO, st_set_target_TLR_mode);

	mode_data = kmem_zalloc(SAS_TLR_MOD_LEN, KM_SLEEP);
	ret = st_gen_mode_sense(un, ubf, 0x18, mode_data, amount);
	if (ret != DDI_SUCCESS) {
		if (ret != EACCES)
			un->un_tlr_flag = TLR_NOT_SUPPORTED;
		goto out;
	}
	if (mode_data->data_len != amount + 1) {
		amount = mode_data->data_len + 1;
	}
	/* Must be SAS protocol */
	if (mode_data->page.saslun.protocol_id != 6) {
		un->un_tlr_flag = TLR_NOT_SUPPORTED;
		ret = ENOTSUP;
		goto out;
	}
	if (un->un_tlr_flag == TLR_SAS_ONE_DEVICE) {
		if (mode_data->page.saslun.tran_layer_ret == 1)
			goto out;
		mode_data->page.saslun.tran_layer_ret = 1;
	} else {
		if (mode_data->page.saslun.tran_layer_ret == 0)
			goto out;
		mode_data->page.saslun.tran_layer_ret = 0;
	}
	ret = st_gen_mode_select(un, ubf, mode_data, amount);
	if (ret != DDI_SUCCESS) {
		if (ret != EACCES)
			un->un_tlr_flag = TLR_NOT_SUPPORTED;
	} else {
		if (mode_data->page.saslun.tran_layer_ret == 0)
			un->un_tlr_flag = TLR_NOT_KNOWN;
		else
			un->un_tlr_flag = TLR_SAS_ONE_DEVICE;
	}
#ifdef STDEBUG
	st_clean_print(ST_DEVINFO, st_label, SCSI_DEBUG, "TLR data sent",
	    (char *)mode_data, amount);
#endif
out:
	kmem_free(mode_data, SAS_TLR_MOD_LEN);
	return (ret);
}


static void
st_reset_notification(caddr_t arg)
{
	struct scsi_tape *un = (struct scsi_tape *)arg;

	ST_FUNC(ST_DEVINFO, st_reset_notification);
	mutex_enter(ST_MUTEX);

	un->un_unit_attention_flags |= 2;
	if ((un->un_rsvd_status & (ST_RESERVE | ST_APPLICATION_RESERVATIONS)) ==
	    ST_RESERVE) {
		un->un_rsvd_status |= ST_LOST_RESERVE;
		ST_DEBUG2(ST_DEVINFO, st_label, CE_WARN,
		    "Lost Reservation notification");
	} else {
		ST_DEBUG2(ST_DEVINFO, st_label, CE_WARN,
		    "reset notification");
	}

	if ((un->un_restore_pos == 0) &&
	    (un->un_state == ST_STATE_CLOSED) ||
	    (un->un_state == ST_STATE_OPEN_PENDING_IO) ||
	    (un->un_state == ST_STATE_CLOSING)) {
		un->un_restore_pos = 1;
	}
	ST_DEBUG6(ST_DEVINFO, st_label, CE_WARN,
	    "reset and state was %d\n", un->un_state);
	mutex_exit(ST_MUTEX);
}