root/usr/src/uts/common/io/scsi/targets/st.c
/*
 * 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.
 *  Copyright (c) 2011 Bayard G. Bell. 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.
 */

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(4D) 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, 0) == 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) == 0);

        (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.
         */
        (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)(uintptr_t)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;
                }
                /* FALLTHROUGH */

        default:
                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;
                                }
                                /* FALLTHROUGH */
                        default:
                                /* 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.eof == ST_EOM);
                                nblks = 0;
                                nfiles = 0;
                        } else {
                                ASSERT(un->un_pos.eof == 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;
                        }
                        /* FALLTHROUGH */
                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;
                                break;
                        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);
}