Files
seta75D 2e8a93c394 Init
2021-10-11 18:20:23 -03:00

4300 lines
116 KiB
C

#include "st.h"
#if NST > 0
#ident "@(#)st.c 1.1 92/07/30 SMI"
/*#define STDEBUG /* Allow compiling of debug code */
/*
* Generic Driver for 1/2-inch reel, 1/2-inch cartridge,
* and 1/4-inch cartridge embedded SCSI tape drives.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/user.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/map.h>
#include <sys/vm.h>
#include <sys/mtio.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/syslog.h>
#include <sun/dklabel.h>
#include <sun/dkio.h>
#include <machine/psl.h>
#include <machine/mmu.h>
#include <machine/cpu.h>
#include <sundev/mbvar.h>
#include <sundev/screg.h>
#include <sundev/sireg.h>
#include <sundev/scsi.h>
#include <sundev/streg.h>
extern struct scsi_unit stunits[];
extern struct scsi_unit_subr scsi_unit_subr[];
extern struct scsi_tape stape[];
extern struct st_drive_table st_drivetab[];
extern int cpu, nstape, st_drivetab_size;
struct st_drive_table st_drivetab1[] = ST_DRIVE1_INFO;
static char *iopb_error = "st%d: no iopb space for buffer\n";
static char *wrong_media =
"st%d: wrong tape for writing, use DC6150 tape (or equivalent)\n";
static char *tape_worn1 = "st%d: warning, the tape may be wearing out or\n";
static char *tape_worn2 = " the head may need cleaning or\n";
static char *tape_worn3 = " the drive may have a problem.\n";
static char *wprotect_error = "st%d: write protected\n";
static char *size_error = "st%d: %s: not modulo %d block size\n";
static char *position_error = "st%d: %s positioning error\n";
static char *rewinding_msg = "st%d: warning, rewinding tape\n";
static char *format_error = "st%d: format change failed\n";
static char *format_msg = "st%d: warning, using alternate tape format\n";
static char *retry_msg = "st%d: %s retries= %u (%u.%u%%), file= %u, block= %u\n";
static char *hard_error = "st%d: %s failed\n";
static char *sense_msg1 = "st%d error: sense key(0x%x): %s\n";
static char *sense_msg2 = "st%d error: sense key(0x%x): %s, error code(0x%x): %s\n";
#define LONG (sizeof(u_long))
#define LONG_ALIGN(arg) (((u_int)arg + LONG -1) & ~(LONG -1))
#define SHORT_TIMEOUT 4 /* Short timeout (2*minutes) */
#define NORMAL_TIMEOUT 6 /* Read/Write timeout (2*minutes) */
#define REWIND_TIMEOUT 12 /* Rewind timeout (2*minutes) */
#define SPACE_TIMEOUT 120 /* Space file timeout (2*minutes) */
#define ERASE_TIMEOUT 120 /* Erase timeout (2*minutes) */
#define LONG_ERASE_TIMEOUT 360 /* 3 hr. erase timeout (2*minutes) */
#define SIGNALS (sigmask(SIGHUP) | sigmask(SIGKILL) | sigmask(SIGQUIT) | \
sigmask(SIGSTOP) | sigmask(SIGCONT) | sigmask(SIGINT) | \
sigmask(SIGALRM) | sigmask(SIGTSTP) | sigmask(SIGTTIN) | \
sigmask(SIGTTOU) | sigmask(SIGTERM))
/*#define PRIST ((PZERO+1) | PCATCH) /* For interruptible sleep */
#define PRIST PRIBIO /* For non-interruptible sleep */
int st_sleep = PRIBIO; /* For non-interruptible sleep */
/*int st_sleep = PRIST; /* For interruptible sleep */
int st_debug = 0; /* 0 = normal operation
* 1 = extended error info
* 2 = adds debug info
* 3 = all status info
*/
#ifdef STDEBUG
short st_retry = 0; /* Enable special soft error reporting */
/* Handy debugging 0, 1, and 2 argument printfs */
#define DPRINTF(str) \
if (st_debug > 1) printf(str)
#define DPRINTF1(str, arg1) \
if (st_debug > 1) printf(str,arg1)
#define DPRINTF2(str, arg1, arg2) \
if (st_debug > 1) printf(str,arg1,arg2)
/* Handy extended error reporting 0, 1, and 2 argument printfs */
#define EPRINTF(str) \
if (st_debug) printf(str)
#define EPRINTF1(str, arg1) \
if (st_debug) printf(str,arg1)
#define EPRINTF2(str, arg1, arg2) \
if (st_debug) printf(str,arg1,arg2)
#else STDEBUG
short st_retry = 1; /* Disable special soft error reporting */
#define DPRINTF(str)
#define DPRINTF1(str, arg2)
#define DPRINTF2(str, arg1, arg2)
#define EPRINTF(str)
#define EPRINTF1(str, arg2)
#define EPRINTF2(str, arg1, arg2)
#endif STDEBUG
/*
* NOTES:
* This device driver supports both fixed length block I/O or
* variable length block I/O. Fixed length I/O is common for
* 1/4-inch tape drives. Variable length is common for 1/2-inch
* reel (Pertec) tape drives and various other types of tape
* drives.
*
* To add a new tape device one has sereral choices. One
* can rely on the built-in adaptive code to take care of
* things. The default tape drive id will be reported for
* this class of tape drive.
*
* The next method of adding a tape drive is to use adb
* and enter the pertinent drive information in the st_drive_table
* structure of st.o. This solution allows customers to add
* tape devices which can support more than one tape density.
* There is one spare entry provided, named SPARE in the table,
* to allow for this contingency.
*
* The final solution for adding a new tape device rquires
* access to the source of st.c and streg.h. First add a
* new entry in the streg.h file table, st_drive_table.
* Then modify the following routines in st.c to provide
* complete support for this device: st_get_retries,
* st_get_error_code, and st_print_error. Also, a new
* error message table may need to be optionally added
* (like st_emulex_error_str).
*
* Command timeouts don't work 100% of the time. Si raises
* it's interrupt priority so high that timeouts are blocked.
* Unfortunately, this is usually triggered by spurious phase changes
* and it doesn't appear to ever lower it. Under these conditions,
* the whole system will hang.
*
* Generic tape device operation assumes the device is capable
* of fixed-length I/O. A mode sense is done to obtain the device's
* physical record size. If the record size is zero, the default
* physical record size (512 bytes/record) will be used. This
* will be checked at mode select time. Also, only one density/format
* is supported, density 0.
*
* Be careful when modifying the following variables as these
* are used throughout this driver: dsi->un_lastiow, dsi->next_block,
* dsi->un_last_block, dsi->un_openf, dsi->un_ctype, dsi->un_eof,
* dsi->un_last_bcount, dsi->un_last_count, dsi->un_offset,
* and dsi->un_iovlen.
*/
/*
* Return a pointer to this unit's unit structure.
*/
stunitptr(md)
register struct mb_device *md;
{
return ((int) &stunits[md->md_unit]);
}
/*
* Deadman timer for tape commands. Also, used for generating timing delays
* while waiting for tape to load.
*/
static
sttimer(dev)
register dev_t dev;
{
register struct scsi_tape *dsi;
register struct scsi_unit *un;
register struct scsi_ctlr *c;
register u_int unit;
unit = MTUNIT(dev);
un = &stunits[unit];
dsi = &stape[unit];
c = un->un_c;
if (dsi->un_openf >= OPENING) {
(*c->c_ss->scs_start)(un);
if ((dsi->un_timeout > 0) && (--dsi->un_timeout == 0)) {
/* Process command timeout for normal I/O operations */
log(LOG_CRIT, "st%d: I/O request timeout\n", unit);
dsi->un_timeout = 4;
if ((*c->c_ss->scs_deque)(c, un)) {
/* Can't recover, reset! */
dsi->un_timeout = 0;
dsi->un_openf = CLOSED;
(*c->c_ss->scs_reset)(c, 1);
}
} else {
EPRINTF1("st%d: sttimer running ", unit);
EPRINTF2("open= %d, timeout= %d\n",
dsi->un_openf, dsi->un_timeout);
}
timeout(sttimer, (caddr_t)dev, 30*hz);
} else {
/* Process tape opening delay timeout */
DPRINTF1("st%d: sttimer running...\n", unit);
wakeup((caddr_t)dev);
}
}
/*
* Attach device (boot time). Initialize data structures and
* allocate inquiry/mode_select/mode_sense/sense buffer.
*/
stattach(md)
register struct mb_device *md;
{
register struct scsi_unit *un;
register struct scsi_tape *dsi;
/*DPRINTF("stattach:\n");*/
un = &stunits[md->md_unit];
dsi = &stape[md->md_unit];
un->un_md = md;
un->un_mc = md->md_mc;
un->un_unit = md->md_unit;
un->un_target = TARGET(md->md_slave);
un->un_lun = LUN(md->md_slave);
un->un_ss = &scsi_unit_subr[TYPE(md->md_flags)];
un->un_blkno = un->un_count = 0;
un->un_present = 0;
dsi->un_bufcnt = 0; /* No I/O request in que */
dsi->un_iovlen = 0; /* Guarantee block size check */
dsi->un_offset = 0;
dsi->un_lastior = dsi->un_lastiow = 0;
dsi->un_next_block = 0; /* Assume file #0, Blk #0 */
dsi->un_last_block = INF;
dsi->un_blocks = 0;
dsi->un_records = 0;
dsi->un_fileno = 0;
dsi->un_eof = ST_NO_EOF;
dsi->un_timeout = 0; /* Disable timeouts */
dsi->un_reset_occurred = 2; /* Force init */
dsi->un_density = 0;
dsi->un_openf = CLOSED;
dsi->un_ctype = ST_TYPE_INVALID; /* Set drive defaults */
dsi->un_flags = 0; /* Special un->un_flags copy */
dsi->un_last_count = 1; /* Must not match un_count */
dsi->un_last_bcount = 0;
dsi->un_mspl = NULL;
dsi->un_msplsize = 0;
dsi->un_dtab = (int *)&st_drivetab1[2]; /* Set default device */
}
/*
* Log error stats. Called by stinit and stopen.
*/
static
stinit_error(dsi, status)
register struct scsi_tape *dsi;
u_char status;
{
dsi->un_err_resid = 0;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = status;
dsi->un_reset_occurred = 2;
dsi->un_openf = CLOSED;
}
/*
* Initialize tape device (called by stopen).
* Check if device on-line and identify who it is.
*/
/*ARGSUSED*/
static
stinit(dev, io_flag)
dev_t dev;
int io_flag;
{
register struct scsi_tape *dsi;
register struct scsi_unit *un;
register struct scsi_inquiry_data *sid;
register struct st_drive_table *dp;
struct st_sense *ssd;
register int i;
int unit, error = 0;
long buf_size;
u_char flags;
unit = MTUNIT(dev);
un = &stunits[unit];
dsi = &stape[unit];
flags = dsi->un_flags; /* save current settings */
/*DPRINTF2("stinit: un= 0x%x, un->un_c= 0x%x\n", un, un->un_c);*/
/*
* If first time for drive or after it had a SE_FATAL error
* to a full initialization pass to check it out. Otherwise,
* if we already know who it is, use the short check.
*/
if (dsi->un_ctype == ST_TYPE_INVALID ||
dsi->un_ctype == ST_TYPE_DEFAULT ||
un->un_present == 0) {
buf_size = DEV_BSIZE + LONG;
dsi->un_ctype = ST_TYPE_DEFAULT; /* Set drive defaults */
} else {
buf_size = sizeof (struct scsi_inquiry_data) + LONG;
}
/*
* Allocate buffer for mode_select/inquiry/request_sense data
* in iopb memory and align to long word if not done already.
*/
if ((int)dsi->un_mspl == NULL) {
sid = (struct scsi_inquiry_data *) rmalloc(iopbmap, buf_size);
if (sid == NULL) {
log(LOG_CRIT, iopb_error, MTUNIT(un->un_unit));
return (ENXIO);
}
dsi->un_msplsize = buf_size;
dsi->un_rmspl = (int *)sid;
sid = (struct scsi_inquiry_data *) LONG_ALIGN(sid);
dsi->un_mspl = (int *)sid;
EPRINTF2("st%d: stinit: buffer= 0x%x", MTUNIT(un->un_unit),sid);
EPRINTF2(", dsi= 0x%x, un= 0x%x\n", dsi, un);
}
sid = (struct scsi_inquiry_data *) dsi->un_mspl;
ssd = (struct st_sense *) dsi->un_mspl;
#ifdef lint
sid = sid; ssd = ssd;
#endif lint
/*
* Check if the tape drive is ready. If it's not ready then
* either the tape has been changed, or it's offline, or maybe
* it's not there (offline). Note, this also catches power-on reset
* and tape change conditions as they both generate unit attention
* check condition status. If the drive is auto-loading, we'll
* get a busy indication until it's done. In any case, it
* probably would be a good idea to set the reset occurred
* indicator.
*/
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi)) {
/*
* If we're in read mode, you'd better want to read.
* Otherwise, we're going to have to see if you've
* changed the tape. If we have to check, this'll
* probably rewind the tape too.
*/
if (dsi->un_lastior && ((io_flag & FWRITE) == 0) &&
(dsi->un_fileno != 0)) {
EPRINTF("stinit: Sysgen in read mode\n");
goto STINIT_EXIT;
}
if (dsi->un_lastior && (dsi->un_fileno != 0) &&
(dsi->un_next_block != 0)) {
log(LOG_ERR, rewinding_msg, unit);
}
dsi->un_flags = SC_UNF_NO_DISCON; /* no disconnects */
dsi->un_openf = OPENING;
if (stcmd(dev, SC_REQUEST_SENSE, 0) &&
dsi->un_openf != OPENING_SYSGEN) {
EPRINTF("stinit: Sysgen restart\n");
dsi->un_ctype = ST_TYPE_DEFAULT; /* Force identify */
dsi->un_reset_occurred = 2;
dsi->un_fileno = 0;
dsi->un_next_block = 0;
} else {
EPRINTF("stinit: Sysgen ready\n");
goto STINIT_EXIT;
}
}
#endif ST_SYSGEN
dsi->un_flags = SC_UNF_NO_DISCON; /* no disconnects */
dsi->un_openf = OPENING; /* special error handling */
if (stcmd(dev, SC_TEST_UNIT_READY, 0)) {
/* Major SCSI device failure, stop! */
DPRINTF("stinit: check condition\n");
if (dsi->un_openf == CLOSED) {
EPRINTF1("st%d: hardware failure, check drive\n", unit);
error = ENXIO;
goto STINIT_ERROR;
}
/*
* Last chance -- If this is the first command after
* a SCSI reset, we'll always get a unit attention error
* the first time. Giving it one more chance neatly
* hides it. By the way retrying this command also
* provides the necessary delay if you just inserted
* the tape and tried to access it. Don't change the
* number of test unit ready retries!
*
* Note, if sysgen support is enabled, we'll have to do
* a request sense after the first test unit ready or
* the dumb sysgen won't ever clear its check condition.
*/
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi) || IS_DEFAULT(dsi)) {
dsi->un_openf = OPENING;
if (stcmd(dev, SC_REQUEST_SENSE, 0) &&
dsi->un_openf == OPENING_SYSGEN) {
EPRINTF("stinit: Sysgen tape drive found\n\n");
goto STINIT_EXIT;
}
}
#endif ST_SYSGEN
/*
* Auto-loading tape drives need a few seconds
* after tape insertion to figure out that a tape
* has been inserted.
*/
dsi->un_openf = OPENING_DELAY;
timeout(sttimer, (caddr_t)dev, 3*hz);
(void) sleep((caddr_t) dev, PRIBIO);
untimeout(sttimer, (caddr_t)dev);
dsi->un_reset_occurred = 2;
/*
* Do 2 test unit readys to survive the case of the tape
* drive finished auto-loading between the first test unit
* ready and here. We have to do it twice to get both the
* unit attention and the drive not ready errors. If this
* fails, then either we're not finished auto-loading or
* the drive really is offline.
*/
dsi->un_openf = OPENING;
if (stcmd(dev, SC_TEST_UNIT_READY, 0) == 0) {
dsi->un_openf = OPENING;
(void) stcmd(dev, SC_TEST_UNIT_READY, 0);
}
if (dsi->un_openf == OPEN_FAILED_LOADING) {
/*
* This takes care of auto-load busy status until the
* tape drive is ready. Each loop sleeps for 2 Sec.
* to reduce CPU loading as this process can take
* quite a while.
*/
i = MAX_AUTOLOAD_DELAY;
dsi->un_openf = OPENING;
while((stcmd(dev, SC_TEST_UNIT_READY, 0)) &&
(i-- > 0)) {
dsi->un_openf = OPENING_DELAY;
timeout(sttimer, (caddr_t)dev, 2*hz);
/* Allow user to control-C out of this sleep */
if (sleep((caddr_t)dev, PRIST)) {
EPRINTF("stopen: sleep interrupted\n");
error = EINTR;
goto STINIT_ERROR;
}
untimeout(sttimer, (caddr_t)dev);
}
}
/* If tape isn't loaded by now, quit! */
if (dsi->un_openf == OPEN_FAILED_LOADING ||
dsi->un_openf == OPEN_FAILED_TAPE ||
dsi->un_openf == CLOSED) {
error = EIO;
goto STINIT_ERROR;
}
/*
* If we had a check condition on the initial test unit
* ready we may have had a media change error. Do a
* mode sense to check. Note, if this is the first
* time or a Sysgen controller, don't do it!
* Note, this will set a read-only bit via
* stintr_opening routine which can be checked in stopen.
*/
DPRINTF("stinit: after check condition\n");
if (dsi->un_ctype != ST_TYPE_DEFAULT &&
dsi->un_ctype != ST_TYPE_SYSGEN) {
dsi->un_openf = OPENING;
(void) stcmd(dev, SC_MODE_SENSE, 0);
}
}
/*
* If this is the first time that this device has been opened,
* we'll need to figure out what type of tape drive it is. After
* that we'll always remember it. Also, start up the deadman
* timer.
*/
if (dsi->un_ctype != ST_TYPE_DEFAULT) {
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi))
dsi->un_reset_occurred = 2;
#endif ST_SYSGEN
DPRINTF("stinit: quick exit\n");
un->un_present = 1;
dsi->un_openf = OPEN; /* normal error handling */
dsi->un_flags = flags; /* restore saved flags */
dsi->un_timeout = 0;
timeout(sttimer, (caddr_t)dev, 30*hz);
return (0);
}
DPRINTF("stinit: doing inquiry\n");
dsi->un_openf = OPENING;
if (stcmd(dev, SC_INQUIRY, 0)) {
#ifdef ST_SYSGEN
/*
* Check for Sysgen tape controller. This is done by
* doing a mode sense followed by a request sense after
* it fails. If the error code is 0x20, then we're
* talking to a sysgen. Otherwise, it's a non-CCS tape
* controller. Also, make sure there is a tape loaded
* in the tape drive, or else !
*/
dsi->un_openf = OPENING;
if (stcmd(dev, SC_REQUEST_SENSE, 0) == 0) {
if (dsi->un_openf == OPENING_SYSGEN) {
#ifdef sun2
/*
* Check for a cipher tape drive if we're on
* a Sun-2/120 system. A cipher tape driver
* can be recognized by issuing the vendor
* unique read extended status command. Note,
* that afterwards that a SCSI bus reset will
* be required to reset the QIC-02 bus. This
* is necessary because it does not support the
* QIC-24 format (it erases the tape instead).
*
* Do a cipher read status command to see
* if it's an old QIC-11 drive. If command
* works, we'll have to reset the bus to
* continue. And of course, we have to
* do a test unit ready to get rid of reset
* status.
*/
if ((cpu == CPU_SUN2_120) &&
(stcmd(dev, SC_READ_XSTATUS_CIPHER, 0) == 0)) {
EPRINTF("stinit: Sun2 Cipher drive found\n");
(*un->un_c->c_ss->scs_reset)(un->un_c, 0);
dsi->un_openf = OPENING;
(void) stcmd(dev, SC_TEST_UNIT_READY, 0);
dsi->un_openf = OPENING;
(void) stcmd(dev, SC_REQUEST_SENSE, 0);
dp = &st_drivetab1[1];
goto STINIT_SET_DRIVE;
} else if (cpu == CPU_SUN2_120) {
(void) stcmd(dev, SC_REQUEST_SENSE, 0);
}
#endif sun2
EPRINTF("stinit: Sysgen tape controller found\n\n");
dp = &st_drivetab1[0];
goto STINIT_SET_DRIVE;
} else if (dsi->un_openf == OPEN_FAILED_TAPE) {
EPRINTF1("st%d: no tape loaded\n", unit);
dsi->un_options = 0;
error = EIO;
goto STINIT_ERROR;
}
}
#endif ST_SYSGEN
/* Handle non-CCS tape drive types */
/*DPRINTF1("st%d: unknown SCSI device found\n", unit);*/
dsi->un_ctype = ST_TYPE_INVALID;
error = ENXIO;
goto STINIT_ERROR;
}
/*
* Determine type of tape controller (see streg.h).
*/
DPRINTF("stinit: checking inquiry data\n");
for (dp = st_drivetab; dp < &st_drivetab[st_drivetab_size]; dp++) {
if (bcmp(sid->vid, dp->vid, dp->length) == 0) {
/* Drive found, set stats */
goto STINIT_SET_DRIVE;
}
}
/*
* Don't know anything about this tape drive. Use at your own risk !!
* Note, if block size is zero, we'll assumed variable-length
* block size.
*/
if (un->un_present == 0) {
EPRINTF1("stinit: vid= <%s>\n", sid->vid);
EPRINTF2(" dtype= %d, rdf= %d\n", sid->dtype, sid->rdf);
#ifdef STDEBUG
if (st_debug > 2)
st_print_sid_buffer(sid, 32);
#endif STDEBUG
if (sid->dtype != 1) {
EPRINTF("stinit: not a tape\n");
dsi->un_ctype = ST_TYPE_INVALID;
error = ENXIO;
goto STINIT_ERROR;
}
log(LOG_WARNING, "st%d: warning, unknown tape drive found\n",
unit);
}
dsi->un_openf = OPENING;
(void) stcmd(dev, SC_MODE_SENSE, 0);
if (dsi->un_dev_bsize != 0)
dp = &st_drivetab1[2]; /* Fixed-length I/O */
else
dp = &st_drivetab1[3]; /* Variable-length I/O */
/* goto STINIT_SET_DRIVE; */
STINIT_SET_DRIVE:
/* Store tape drive characteristics. */
EPRINTF1("stinit: %s drive found\n\n", dp->name);
dsi->un_dtab = (int *)dp;
dsi->un_ctype = dp->type;
dsi->un_dev_bsize = dp->bsize;
dsi->un_options = dp->options;
/* If zero, this could panic kernel if tape repositioned */
if (dsi->un_dev_bsize == 0)
dsi->un_dev_bsize = DEV_BSIZE;
dsi->un_phys_bsize = dsi->un_dev_bsize;
#ifdef STDEBUG
if (st_debug > 2) {
printf(" bsize= 0x%x type= 0x%x\n", dp->bsize, dp->type);
printf(" density= 0x%x, 0x%x, 0x%x, 0x%x\n",
dp->density[0], dp->density[1],
dp->density[2], dp->density[3]);
printf(" speed= 0x%x, 0x%x, 0x%x, 0x%x\n",
dp->speed[0], dp->speed[1],
dp->speed[2], dp->speed[3]);
}
#endif STDEBUG
/*
* Do mode sense to check for write protected tape,
* except for Sysgen, which doesn't do mode sense's.
*/
if (! IS_SYSGEN(dsi)) {
dsi->un_openf = OPENING;
(void) stcmd(dev, SC_MODE_SENSE, 0);
}
STINIT_EXIT:
/* Check if disconnects disabled */
if (dsi->un_options & ST_NODISCON) {
/*DPRINTF("stinit: disconnects disabled\n");*/
flags = SC_UNF_NO_DISCON;
} else {
/*DPRINTF("stinit: disconnects enabled\n");*/
flags = 0;
}
/*
* Tape is ready to go. Set mode to OPEN to signify the
* fact. Also, start the free-running deadman timer.
* Return successful status to stopen.
*/
un->un_present = 1;
dsi->un_openf = OPEN;
dsi->un_flags = flags; /* restore saved flags */
dsi->un_timeout = 0;
timeout(sttimer, (caddr_t)dev, 30*hz);
return (0);
STINIT_ERROR:
dsi->un_flags = flags; /* restore saved flags */
stinit_error(dsi, SC_NOT_READY);
if ((int)dsi->un_mspl) {
rmfree(iopbmap, (long)dsi->un_msplsize, (u_long)dsi->un_rmspl);
dsi->un_mspl = NULL;
dsi->un_msplsize = 0;
}
return (error);
}
/*
* Main entry point for opening tape driver. Checks for device
* unused and present. Also, sets recording format/density.
*/
stopen(dev, flag)
register dev_t dev;
int flag;
{
register struct scsi_unit *un;
register u_int unit;
register int i, s;
register int status;
register struct scsi_tape *dsi;
unit = MTUNIT(dev);
if (unit >= nstape) {
EPRINTF1("st%d: stopen: invalid unit\n", unit);
return (ENXIO);
}
un = &stunits[unit];
dsi = &stape[unit];
if (un->un_mc == 0) {
EPRINTF1("st%d: stopen: invalid unit\n", unit);
return (ENXIO);
}
/*
* Can't open a drive that's already open. If closing
* or closed, go ahead with open. If open, wait for a
* while and hope it'll free up. If not, return busy.
* Note, raise interrupt priority high enough to prevent
* I/O requestor from being swapped while we're checking.
*/
s = splr(pritospl(un->un_mc->mc_intpri));
if (dsi->un_openf > CLOSING) {
(void) splx(s);
for(i = 0; i < MAX_AUTOLOAD_DELAY *2; i++) {
EPRINTF("stopen: waiting...\n");
/* Allow user to control-C out of this sleep */
if (sleep((caddr_t) &lbolt, PRIST)) {
EPRINTF("stopen: sleep interrupted\n");
return (EINTR);
}
s = splr(pritospl(un->un_mc->mc_intpri));
if (dsi->un_openf <= CLOSING)
goto STOPEN_READY;
(void) splx(s);
}
if (dsi->un_openf == OPENING && dsi->un_timeout == 0)
dsi->un_timeout = 4; /* Insurance */
(void) splx(s);
EPRINTF1("stopen: open state= %d\n", dsi->un_openf);
return (EBUSY);
}
STOPEN_READY:
dsi->un_openf = OPENING;
(void) splx(s);
/*
* Open tape drive. Also, make sure it's online. Note, this
* will also set stintr state machine to open state.
*/
if ((status=stinit(dev, flag)) != 0)
return (status);
/* dsi->un_openf = OPEN; */
/*
* While we're opening the tape, check for write protect. If
* it is and we're opening the tape for write operations, you're
* dead. Note, this error will also force the tape drive to be
* reinitialized via stinit. Also, log stats for 'mt status'.
*/
if (flag & FWRITE) {
if (dsi->un_read_only > 1) {
/* Check if write protected or wrong media error */
if (dsi->un_read_only > 2) {
log(LOG_ERR, wrong_media, unit);
stinit_error( dsi, SC_MEDIA);
} else {
log(LOG_ERR, wprotect_error, unit);
stinit_error(dsi, SC_WRITE_PROTECT);
}
return (EACCES);
}
dsi->un_read_only = 0;
/* If opening in write-only mode, force eof write */
if (flag == FWRITE)
dsi->un_lastiow = 3;
/*
* If opening for read only, set the write protect flag if
* it's not already set.
*/
} else if ((flag & FWRITE) == 0 && dsi->un_read_only == 0)
dsi->un_read_only = 1;
/*
* If at BOT, use whatever the user selects. Otherwise,
* use what worked last time.
*/
if (dsi->un_fileno == 0 && dsi->un_next_block == 0)
dsi->un_options &= ~ST_AUTODEN_OVERRIDE;
/*
* If a device reset occurred, the mode select information
* should be given to the tape device again just to be safe.
*/
if (dsi->un_reset_occurred) {
EPRINTF("stopen: resetting tape device\n");
if (dsi->un_reset_occurred > 1) {
dsi->un_fileno = 0; /* suppress rewinding msg */
dsi->un_next_block = 0;
dsi->un_offset = 0;
if (! (IS_SYSGEN(dsi) || IS_EMULEX(dsi)))
(void) stcmd(dev, SC_RESERVE, 0);
}
dsi->un_density = -1; /* force mode select */
dsi->un_options &= ~ST_AUTODEN_OVERRIDE;
dsi->un_eof = ST_NO_EOF;
dsi->un_reset_occurred = 0;
}
#ifdef sun2
/*
* For Sysgen on Sun 2, if it doesn't support QIC-24 don't do mode
* selects. Note, if this is attempted for this combo the tape will
* be erased.)
*/
if (dsi->un_options & ST_NO_QIC24) {
EPRINTF("stopen: using default density\n");
dsi->un_density = SC_DENSITY0;
return (0);
}
#endif sun2
switch (minor(dev) & MT_DENSITY_MASK) {
case MT_DENSITY1:
/* Check for need to convert density */
if (dsi->un_density == SC_DENSITY0 ||
(dsi->un_options & ST_AUTODEN_OVERRIDE)) {
break;
}
if (dsi->un_fileno != 0 || dsi->un_next_block != 0)
log(LOG_ERR, rewinding_msg, unit);
/* Special case -- mode select does not abort open */
if ((status=stcmd(dev, SC_REWIND, 0)) == EINTR ||
(status=stcmd(dev, SC_DENSITY0, 0))) {
log(LOG_WARNING, format_error, unit);
if (status == EINTR) {
dsi->un_ctype = ST_TYPE_INVALID;
dsi->un_openf = CLOSED;
return (status);
}
}
dsi->un_density = SC_DENSITY0;
break;
case MT_DENSITY2:
/* Check for need to convert density */
if (dsi->un_density == SC_DENSITY1)
break;
/* Must convert to specified density */
if (dsi->un_options & ST_AUTODEN_OVERRIDE){
log(LOG_WARNING, format_msg, unit);
break;
}
if (dsi->un_fileno != 0 || dsi->un_next_block != 0)
log(LOG_ERR, rewinding_msg, unit);
if ((status=stcmd(dev, SC_REWIND, 0)) == EINTR ||
(status=stcmd(dev, SC_DENSITY1, 0))) {
log(LOG_WARNING, format_error, unit);
dsi->un_ctype = ST_TYPE_INVALID;
dsi->un_openf = CLOSED;
return (status);
}
dsi->un_density = SC_DENSITY1;
break;
case MT_DENSITY3:
/* Check for need to convert density */
if (dsi->un_density == SC_DENSITY2)
break;
/* Must convert to specified density */
if (dsi->un_options & ST_AUTODEN_OVERRIDE){
log(LOG_WARNING, format_msg, unit);
break;
}
if (dsi->un_fileno != 0 || dsi->un_next_block != 0)
log(LOG_ERR, rewinding_msg, unit);
if ((status=stcmd(dev, SC_REWIND, 0)) == EINTR ||
(status=stcmd(dev, SC_DENSITY2, 0))) {
log(LOG_WARNING, format_error, unit);
dsi->un_ctype = ST_TYPE_INVALID;
dsi->un_openf = CLOSED;
return (status);
}
dsi->un_density = SC_DENSITY2;
break;
case MT_DENSITY4:
/* Check for need to convert density */
if (dsi->un_density == SC_DENSITY3)
break;
/* Must convert to specified density */
if (dsi->un_options & ST_AUTODEN_OVERRIDE){
log(LOG_WARNING, format_msg, unit);
break;
}
if (dsi->un_fileno != 0 || dsi->un_next_block != 0)
log(LOG_ERR, rewinding_msg, unit);
if ((status=stcmd(dev, SC_REWIND, 0)) == EINTR ||
(status=stcmd(dev, SC_DENSITY3, 0))) {
log(LOG_WARNING, format_error, unit);
dsi->un_ctype = ST_TYPE_INVALID;
dsi->un_openf = CLOSED;
return (status);
}
dsi->un_density = SC_DENSITY3;
break;
}
return (0);
}
/*
* Write 1 or 2 filemarks, depending on type of device.
* If lastiow = 3 and norewind, write 1 eof.
* If lastiow = 3 and rewind, write 1 or 2 eofs.
* If lastiow = 1 and no rewind, don't write an eof.
* If lastiow = 1 and rewind, write 0 or 1 eofs.
*/
static
stwrite_eof(dev, dsi)
register dev_t dev;
register struct scsi_tape *dsi;
{
int status, state;
/*
* If the last I/O to the tape drive is complete, write a
* filemark before closing. Note, for 1/2-inch reel tape
* two filemarks will be written for Pertec compatibility.
* Note, if opened with write and no data is written,
* we'll always write a filemark.
*/
/* Write eom and rewind */
if (dsi->un_lastiow == 3) {
/* Write eom and rewind */
if (dsi->un_options & ST_REEL) {
EPRINTF("stwrite_eof: 2 eoms\n");
status = stcmd(dev, SC_WRITE_FILE_MARK, 2);
} else {
EPRINTF("stwrite_eof: 1 eoms\n");
status = stcmd(dev, SC_WRITE_FILE_MARK, 1);
}
dsi->un_lastiow = 0; /* Done writing */
/* Write eof and continue */
} else if (dsi->un_lastiow == 2) {
if (dsi->un_options & ST_REEL) {
EPRINTF("stwrite_eof: 2 eoms\n");
status = stcmd(dev, SC_WRITE_FILE_MARK, 2);
if (status == 0) {
EPRINTF("stwrite_eof: backspace filemark\n");
status = stcmd(dev, SC_BSPACE_FILE, 1);
dsi->un_offset = 0;
dsi->un_next_block = 0;
dsi->un_last_block = 0;
}
dsi->un_lastiow = 0; /* Done writing */
} else {
EPRINTF("stwrite_eof: 1 eof\n");
status = stcmd(dev, SC_WRITE_FILE_MARK, 1);
dsi->un_lastiow = 1; /* Have been writing */
}
/* Write rest of eom and rewind */
} else if (dsi->un_lastiow == 1 && (dsi->un_options & ST_REEL)) {
EPRINTF("stwrite_eof: 1 eom\n");
status = stcmd(dev, SC_WRITE_FILE_MARK, 1);
dsi->un_lastiow = 0; /* Done writing */
}
/* Handle filemark write delay for Exabyte */
if (IS_EXABYTE(dsi)) {
untimeout(sttimer, (caddr_t)dev);
state = dsi->un_openf;
dsi->un_openf = OPENING_DELAY;
timeout(sttimer, (caddr_t)dev, 5*hz);
(void) sleep((caddr_t) dev, PRIBIO);
untimeout(sttimer, (caddr_t)dev);
dsi->un_openf = state;
timeout(sttimer, (caddr_t)dev, 30*hz);
}
return (status);
}
/*ARGSUSED*/
stclose(dev, flag)
register dev_t dev;
register int flag;
{
register struct scsi_unit *un;
register struct scsi_tape *dsi;
register struct st_drive_table *dp;
u_long blocks;
int status, resid;
un = &stunits[MTUNIT(dev)];
dsi = &stape[MTUNIT(dev)];
dp = (struct st_drive_table *)dsi->un_dtab;
#ifdef lint
flag = flag;
#endif lint
/*
* If we're closed already, something majorly went wrong.
*/
if (dsi->un_openf == CLOSED) {
untimeout(sttimer, (caddr_t)dev);
return;
}
/*
* Manufacturing patch to allow printing of all soft errors.
*/
if (st_retry != 0 && dsi->un_lastiow)
dsi->un_retry_limit = dp->max_wretries[dsi->un_density-SC_DENSITY0];
else if (st_retry != 0)
dsi->un_retry_limit = dp->max_rretries[dsi->un_density-SC_DENSITY0];
else if (st_retry == 0)
dsi->un_retry_limit = 0;
/*
* If we've been reading or writing and we're above the threshold,
* check the soft error rates to verify that the tape is ok.
* The threshold is used to delay checking until the sample size is
* valid. (It also helps compensate for the high wear area at BOT
* too.)
*
* XXX Note, if you back up the tape, the soft error rate is not
* necessarily correct. I suspect one should treat a backspace
* as a rewind and reset the soft error counters.
*/
if ((dsi->un_options & ST_VARIABLE) == 0 &&
dsi->un_dev_bsize != 512)
/* fixed-length devices whose physical block size is not 512 */
blocks = (dsi->un_blocks * dsi->un_dev_bsize) >> 9;
else
blocks = dsi->un_blocks;
if ((dsi->un_lastior || dsi->un_lastiow) &&
blocks > (dp->retry_threshold >> 9)) {
register int soft_errors;
/*
* Get read/write retries count from the drive. Have yet to
* see two vendors do this the same way. Note for HP,
* conditionally reset the error long (because they won't
* do it on a rewind like everybody else).
*/
if (! IS_SYSGEN(dsi)) {
status = dsi->un_status;
resid = dsi->un_err_resid;
if (dsi->un_options & ST_ERRLOG)
(void) stcmd(dev, SC_READ_LOG, minor(dev) & MT_NOREWIND);
else
(void) stcmd(dev, SC_REQUEST_SENSE, 0);
dsi->un_status = status;
dsi->un_err_resid = resid;
}
/*
* Compensation for fixed block length devices used in
* variable block length mode (e.g. Exaybte).
*
* XXX: ST_PHYSREC presumes ST_VARIABLE set too.
*/
/*
* paranoid check for zero divide
* (theoretically this can't happen
*/
if (dsi->un_blocks == 0)
dsi->un_blocks++;
if (dsi->un_options & ST_PHYSREC) {
soft_errors = (dsi->un_phys_bsize + 511) / 512;
soft_errors = (1000 * dsi->un_retry_ct * soft_errors) /
dsi->un_blocks;
} else if (dsi->un_options & ST_VARIABLE) {
if (dsi->un_records == 0)
dsi->un_records++;
soft_errors = (1000 * dsi->un_retry_ct) /
dsi->un_records;
} else {
soft_errors = (1000 * dsi->un_retry_ct) / dsi->un_blocks;
}
/*
* If soft errors beyond limits, warn user of possible problems.
* Suppress wear-out message if manuf. mode enabled.
*/
if (soft_errors > dsi->un_retry_limit) {
if (dsi->un_retry_limit != 0) {
log(LOG_ERR, tape_worn1, un-stunits);
log(LOG_ERR, tape_worn2);
log(LOG_ERR, tape_worn3);
}
if (dsi->un_lastiow == 0) {
log(LOG_ERR, retry_msg, un-stunits, "read",
dsi->un_retry_ct, soft_errors /10, soft_errors %10,
dsi->un_fileno, dsi->un_next_block);
} else {
log(LOG_ERR, retry_msg, un-stunits, "write",
dsi->un_retry_ct, soft_errors /10, soft_errors %10,
dsi->un_fileno, dsi->un_next_block);
}
}
}
/*
* Write filemarks as needed.
*/
if (dsi->un_lastiow) {
if ((minor(dev) & MT_NOREWIND) && dsi->un_lastiow == 3)
dsi->un_lastiow--;
(void) stwrite_eof(dev, dsi);
}
/*
* If rewind on close, set closing driver state and allow
* stintr to close the driver down. Otherwise, now
* close the driver down.
*/
EPRINTF2("stclose: file= %u, block= %u\n",
dsi->un_fileno, dsi->un_next_block);
if ((minor(dev) & MT_NOREWIND) == 0) {
DPRINTF("stclose: rewinding...\n");
untimeout(sttimer, (caddr_t)dev);
dsi->un_options &= ~ST_NO_POSITION;
dsi->un_lastiow = 0;
dsi->un_openf = CLOSING;
(void) stcmd(dev, SC_REWIND, -1);
} else {
DPRINTF("stclose: no rewind...\n");
untimeout(sttimer, (caddr_t)dev);
dsi->un_options |= ST_AUTODEN_OVERRIDE;
dsi->un_openf = CLOSED;
if (dsi->un_eof == ST_EOF_PENDING ||
dsi->un_eof == ST_EOF_LOG ||
dsi->un_eof == ST_EOF) {
dsi->un_eof = ST_NO_EOF;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_fileno++;
dsi->un_lastior = 0;
dsi->un_offset = 0;
}
}
}
/*
* Run a command initiated internally by the driver.
* Returns 0 for no error. Otherwise, returns error code of error.
*/
stcmd(dev, cmd, count)
dev_t dev;
u_int cmd;
int count;
{
register struct buf *bp;
register int s;
register int error;
register struct scsi_unit *un;
register struct scsi_tape *dsi;
register u_int unit;
#ifdef DISABLE
register struct proc *p = u.u_procp;
int saved_signals;
#endif
unit = MTUNIT(dev);
un = &stunits[unit];
dsi = &stape[unit];
bp = &un->un_sbuf;
#ifdef lint
dsi = dsi;
#endif lint
/*
* If this ever happens, we can't run any more
* commands until we're opened.
*/
if (dsi->un_openf == CLOSED) {
EPRINTF("stcmd: device closed\n");
return (EIO);
}
#ifdef DISABLE
/* Block ALL non-essential signals for the duration. */
(void) splhigh();
saved_signals = p->p_sigmask;
p->p_sigmask |= ~SIGNALS;
(void) spl0();
#endif
s = splr(pritospl(un->un_mc->mc_intpri));
while (bp->b_flags & B_BUSY) {
/*
* Special test because B_BUSY never gets cleared in
* the non-waiting rewind case.
*/
if ((bp->b_bcount == -1) && (bp->b_flags & B_DONE))
break;
bp->b_flags |= B_WANTED;
/*DPRINTF1("stcmd: sleeping..., bp= 0x%x\n", bp);*/
/* Allow user to control-C out of this sleep */
if (sleep((caddr_t)bp, PRIST)) {
#ifdef DISABLE
(void) splhigh();
p->p_sigmask = saved_signals; /* Restore signal mask */
(void) splx(s);
#endif
EPRINTF("stcmd: sleep interrupted\n");
return (EINTR);
}
}
/*DPRINTF1("stcmd: waking..., bp= 0x%x\n", bp);*/
bp->b_flags = B_BUSY | B_READ | B_WANTED;
(void) splx(s);
bp->b_dev = dev;
bp->b_bcount = count;
un->un_scmd = cmd;
ststrategy(bp);
if (dsi->un_openf <= OPENING)
timeout(sttimer, (caddr_t)dev, 30*hz);
/* In case of rewind on close, don't wait. */
if (cmd == SC_REWIND && count == -1) {
#ifdef DISABLE
(void) splhigh();
p->p_sigmask = saved_signals; /* Restore signal mask */
#endif
untimeout(sttimer, (caddr_t)dev);
return (0);
}
s = splr(pritospl(un->un_mc->mc_intpri));
while ((bp->b_flags & B_DONE) == 0) {
/*
* Allow user to control-C out of this sleep.
* Note, this will force a rewind on the next open
* because we've now lost our position on the tape.
*/
/*DPRINTF1("stcmd: still sleeping..., bp= 0x%x\n", bp);*/
if (sleep((caddr_t)bp, st_sleep) && dsi->un_openf == OPEN) {
(void) splx(s);
log(LOG_ERR, "st%d: tape synchronization lost\n",
unit);
dsi->un_reset_occurred = 1;
dsi->un_next_block++;
dsi->un_status = SC_INTERRUPTED;
bp->b_flags |= B_ERROR;
bp->b_error = EINTR;
break;
}
}
#ifdef DISABLE
(void) splhigh();
p->p_sigmask = saved_signals; /* Restore signal mask */
(void) splx(s);
#endif
if (dsi->un_openf <= OPENING)
untimeout(sttimer, (caddr_t)dev);
/*DPRINTF1("stcmd: waking..., bp= 0x%x\n", bp);*/
error = geterror(bp);
if (bp->b_flags & B_WANTED)
wakeup((caddr_t) bp);
bp->b_flags &= B_ERROR; /* Clears B_BUSY */
return (error);
}
/*
* Run a command either from the user or from the driver.
*/
ststrategy(bp)
register struct buf *bp;
{
register struct scsi_unit *un;
register struct mb_device *md;
register u_int unit;
register int s;
register struct buf *ap;
register struct scsi_tape *dsi;
unit = MTUNIT(bp->b_dev);
#ifdef STDEBUG
if (unit >= nstape) {
EPRINTF1("st%d: ststrategy: invalid unit\n", unit);
bp->b_flags |= B_ERROR;
iodone(bp);
return;
}
#endif STDEBUG
un = &stunits[unit];
md = un->un_md;
dsi = &stape[unit];
/*DPRINTF1("ststrategy: bp= 0x%x\n", bp);*/
/*DPRINTF2(" un= 0x%x, un->un_c= 0x%x\n", un, un->un_c);*/
if (dsi->un_openf != OPEN && bp != &un->un_sbuf) {
EPRINTF("ststrategy: device not open\n");
bp->b_flags |= B_ERROR;
iodone(bp);
return;
}
s = splr(pritospl(un->un_mc->mc_intpri));
while (dsi->un_bufcnt >= MAXSTBUF) {
/*DPRINTF1("ststrategy: sleeping on bp= 0x%x\n", bp);*/
/* Allow user to control-C out of this sleep */
if (sleep((caddr_t)&dsi->un_bufcnt, PRIST)) {
(void) splx(s);
EPRINTF("ststrategy: sleep interrupted\n");
bp->b_flags |= B_ERROR;
bp->b_error = EINTR;
iodone(bp);
return;
}
}
dsi->un_bufcnt++;
/*
* Put the block at the end of the queue. Should probably have
* a pointer to the end of the queue, but the queue can't get too
* long, so the added code complexity probably isn't worth it.
*/
/*DPRINTF1("ststrategy: queing bp= 0x%x\n", bp);*/
ap = (struct buf *)(&md->md_utab);
while (ap->b_actf != NULL)
ap = ap->b_actf;
ap->b_actf = bp;
bp->b_actf = NULL;
/*
* Call unit start routine to queue up device, if it currently isn't
* queued. Then call start routine to run the next SCSI command.
*/
(*un->un_c->c_ss->scs_ustart)(un);
(*un->un_c->c_ss->scs_start)(un);
(void) splx(s);
}
/*
* Start the operation. Called by the host adapter to start the command.
*/
/*ARGSUSED*/
ststart(bp, un)
register struct buf *bp;
register struct scsi_unit *un;
{
register struct scsi_tape *dsi;
dsi = &stape[MTUNIT(bp->b_dev)];
/*DPRINTF("ststart:\n");*/
/*
* Process raw I/O tape commands.
*/
if (bp == &un->un_rbuf) {
/*DPRINTF("ststart: raw I/O\n");*/
if (bp->b_flags & B_READ) {
/*
* Can read past EOF. Note, if at EOF, we'll
* return 0 bytes. After that, we'll start reading
* on the next file.
*/
if (dsi->un_eof != ST_NO_EOF) {
if (dsi->un_eof == ST_EOF) {
#ifdef ST_EOF_READTHRU
EPRINTF("ststart: clearing eof\n");
dsi->un_eof = ST_NO_EOF;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_fileno++;
dsi->un_lastior = 0;
dsi->un_options |= ST_NO_POSITION;
goto ST_START_RD;
#endif ST_EOF_READTHRU
}
EPRINTF("ststart: eof\n");
dsi->un_eof = ST_EOF;
dsi->un_status = SC_EOF;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_resid = bp->b_resid = bp->b_bcount;
if (dsi->un_bufcnt-- >= MAXSTBUF)
wakeup((caddr_t) &dsi->un_bufcnt);
return (0);
}
ST_START_RD:
un->un_cmd = SC_READ;
} else {
if (dsi->un_eof != ST_NO_EOF) {
EPRINTF("ststart: eot\n");
dsi->un_status = SC_EOT;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_err_resid = bp->b_resid = bp->b_bcount;
if (dsi->un_bufcnt-- >= MAXSTBUF)
wakeup((caddr_t) &dsi->un_bufcnt);
return (0);
}
un->un_cmd = SC_WRITE;
}
un->un_count = bp->b_bcount;
un->un_flags = SC_UNF_DVMA;
bp->b_resid = 0;
return (1);
/*
* Process internal I/O tape commands using our internal buffer.
*/
} else if (bp == &un->un_sbuf) {
/*DPRINTF("ststart: internal I/O\n");*/
un->un_cmd = un->un_scmd;
un->un_count = bp->b_bcount;
un->un_flags = 0;
bp->b_resid = 0;
return (1);
/*
* Process block I/O tape commands.
*/
} else {
DPRINTF("ststart: block mode I/O\n");
if (bp->b_flags & B_READ) {
/*
* Can't read past EOF. Note, if at EOF,
* we'll return 0 bytes.
*/
if (dsi->un_eof != ST_NO_EOF) {
EPRINTF("ststart: eof\n");
if (dsi->un_eof == ST_EOF_PENDING)
dsi->un_eof = ST_EOF_LOG;
dsi->un_status = SC_EOF;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_err_resid = bp->b_resid = bp->b_bcount;
if (dsi->un_bufcnt-- >= MAXSTBUF)
wakeup((caddr_t) &dsi->un_bufcnt);
return (0);
}
un->un_cmd = SC_READ;
/* Must be block mode write operation */
} else {
if (dsi->un_eof != ST_NO_EOF) {
EPRINTF("ststart: eot\n");
dsi->un_status = SC_EOT;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_err_resid = bp->b_resid = bp->b_bcount;
if (dsi->un_bufcnt-- >= MAXSTBUF)
wakeup((caddr_t) &dsi->un_bufcnt);
return (0);
}
un->un_cmd = SC_WRITE;
}
un->un_count = bp->b_bcount;
un->un_flags = SC_UNF_DVMA;
bp->b_resid = 0;
return (1);
}
}
/*
* Make a command description block. Called by the host adapter driver.
*/
stmkcdb(un)
register struct scsi_unit *un;
{
register struct scsi_cdb6 *cdb;
register struct scsi_tape *dsi;
register struct st_drive_table *dp;
register struct st_sense *ssd;
register struct st_ms_mspl *em;
register struct st_ms_exabyte *ems;
register struct st_log *elog;
u_int count, density, speed;
dsi = &stape[un->un_unit];
dp = (struct st_drive_table *)dsi->un_dtab;
cdb = (struct scsi_cdb6 *)&un->un_cdb;
bzero((caddr_t)cdb, sizeof (struct scsi_cdb6));
cdb->cmd = un->un_cmd;
cdb->lun = un->un_lun;
#ifdef lint
ssd = NULL; ssd = ssd;
#endif lint
switch (un->un_cmd) {
case SC_READ:
DPRINTF1("st%d: stmkcdb: read\n", un-stunits);
un->un_flags |= (SC_UNF_RECV_DATA | dsi->un_flags);
un->un_dma_addr = un->un_baddr;
dsi->un_timeout = NORMAL_TIMEOUT;
if (dsi->un_options & ST_VARIABLE) {
/* cdb->t_code = 0; /* Variable-length */
count = un->un_dma_count = un->un_count;
cdb->mid_count = (un->un_count >> 8) & 0xff;
cdb->low_count = count & 0xff;
} else {
cdb->t_code = 1; /* Fixed-length mode */
if (un->un_count != dsi->un_last_count) {
count = un->un_count / dsi->un_dev_bsize;
} else {
count = dsi->un_last_bcount;
}
dsi->un_last_count = un->un_dma_count = un->un_count;
dsi->un_last_bcount = count;
cdb->mid_count = (count >> 8) & 0xff;
cdb->low_count = count & 0xff;
}
break;
case SC_WRITE:
DPRINTF1("st%d: stmkcdb: write\n", un-stunits);
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_baddr;
dsi->un_timeout = NORMAL_TIMEOUT;
if (dsi->un_options & ST_VARIABLE) {
/* cdb->t_code = 0; /* Variable-length */
count = un->un_dma_count = un->un_count;
cdb->mid_count = (un->un_count >> 8) & 0xff;
cdb->low_count = count & 0xff;
} else {
cdb->t_code = 1; /* Fixed-length mode */
if (un->un_count != dsi->un_last_count) {
count = un->un_count / dsi->un_dev_bsize;
} else {
count = dsi->un_last_bcount;
}
dsi->un_last_count = un->un_dma_count = un->un_count;
dsi->un_last_bcount = count;
cdb->mid_count = (count >> 8) & 0xff;
cdb->low_count = count & 0xff;
}
break;
case SC_WRITE_FILE_MARK:
case SC_LOAD:
EPRINTF1("st%d: stmkcdb: write eof\n", un-stunits);
cdb->low_count = un->un_count; /* Limit is 255 */
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = NORMAL_TIMEOUT;
break;
case SC_BSPACE_FILE:
un->un_count = - un->un_count;
/* Fall through to sc_space_file... */
#ifdef STDEBUG
EPRINTF1("st%d: stmkcdb: backspace file\n", un-stunits);
cdb->t_code = 1; /* Space files, not records */
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
cdb->cmd = SC_SPACE_REC;
cdb->high_count = (un->un_count >> 16) & 0xff;
cdb->mid_count = (un->un_count >> 8) & 0xff;
cdb->low_count = un->un_count & 0xff;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = SPACE_TIMEOUT;
break;
#endif STDEBUG
case SC_SPACE_FILE:
EPRINTF1("st%d: stmkcdb: space file\n", un-stunits);
cdb->t_code = 1; /* Space files, not records */
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
cdb->cmd = SC_SPACE_REC;
cdb->high_count = (un->un_count >> 16) & 0xff;
cdb->mid_count = (un->un_count >> 8) & 0xff;
cdb->low_count = un->un_count & 0xff;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = SPACE_TIMEOUT;
break;
case SC_SPACE_REC:
DPRINTF1("st%d: stmkcdb: space rec\n", un-stunits);
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
cdb->cmd = SC_SPACE_REC;
cdb->high_count = (un->un_count >> 16) & 0xff;
cdb->mid_count = (un->un_count >> 8) & 0xff;
cdb->low_count = un->un_count & 0xff;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = NORMAL_TIMEOUT;
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi)) {
/*
* Even though Sysgen says it can do space recs,
* it really can't. Use reads to do the job.
*/
cdb->cmd = SC_READ;
un->un_flags |= (SC_UNF_RECV_DATA | dsi->un_flags);
un->un_dma_addr = (int)dsi->un_mspl - (int)DVMA;
cdb->t_code = 1;
un->un_dma_count = DEV_BSIZE;
un->un_count = cdb->low_count = 1;
}
#endif ST_SYSGEN
break;
case SC_REQUEST_SENSE:
EPRINTF1("st%d: stmkcdb: request sense\n", un-stunits);
dsi->un_timeout = SHORT_TIMEOUT;
un->un_flags |= (SC_UNF_RECV_DATA | dsi->un_flags);
un->un_dma_addr = (int)dsi->un_mspl - (int)DVMA;
un->un_dma_count = cdb->low_count =
sizeof (struct st_sense);
bzero((caddr_t)(dsi->un_mspl), sizeof (struct st_sense));
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi) || dsi->un_openf == OPENING) {
un->un_dma_count = cdb->low_count =
sizeof (struct sysgen_sense);
}
#endif ST_SYSGEN
return;
/* break; */
case SC_READ_APPEND:
/* Special read command for write append. */
EPRINTF1("st%d: stmkcdb: read append\n", un-stunits);
cdb->cmd = SC_READ;
un->un_flags |= (SC_UNF_RECV_DATA | dsi->un_flags);
un->un_dma_addr = (int)un->un_sbuf.b_un.b_addr - (int)DVMA;
un->un_dma_count = dsi->un_dev_bsize;
dsi->un_timeout = NORMAL_TIMEOUT;
if (dsi->un_options & ST_VARIABLE) {
/* cdb->t_code = 0; */
cdb->mid_count = (dsi->un_dev_bsize >> 8) & 0xff;
cdb->low_count = dsi->un_dev_bsize & 0xff;
} else {
cdb->t_code = 1;
cdb->mid_count = 0;
cdb->low_count = 1;
}
return;
/* break; */
/* case SC_RETENSION: */
case SC_REWIND:
EPRINTF1("st%d: stmkcdb: rewind\n", un-stunits);
if (dsi->un_reten_rewind && (dsi->un_options & ST_QIC)) {
/* If QIC tape, allow retensioning */
cdb->cmd = SC_LOAD;
cdb->low_count = 3;
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi)) {
cdb->cmd = SC_REWIND;
cdb->low_count = 0;
cdb->vu_57 = 1;
}
#endif ST_SYSGEN
}
dsi->un_reten_rewind = 0;
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = REWIND_TIMEOUT;
break;
case SC_UNLOAD:
EPRINTF1("st%d: stmkcdb: unload\n", un-stunits);
cdb->cmd = SC_LOAD;
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = REWIND_TIMEOUT;
break;
case SC_INQUIRY:
EPRINTF1("st%d: stmkcdb: inquiry\n", un-stunits);
un->un_flags |= (SC_UNF_RECV_DATA | dsi->un_flags);
un->un_dma_addr = (int)dsi->un_mspl - (int)DVMA;
un->un_dma_count = cdb->low_count =
sizeof (struct scsi_inquiry_data);
dsi->un_timeout = SHORT_TIMEOUT;
bzero((caddr_t)(dsi->un_mspl), sizeof (struct scsi_inquiry_data));
break;
case SC_ERASE_TAPE:
EPRINTF1("st%d: stmkcdb: erase tape\n", un-stunits);
cdb->t_code = un->un_count; /* Erase type */
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = ERASE_TIMEOUT;
if (dsi->un_options & ST_LONG_ERASE)
dsi->un_timeout = LONG_ERASE_TIMEOUT; /* 3 hours */
break;
case SC_READ_LOG:
if (un->un_count) {
EPRINTF1("st%d: stmkcdb: read log\n", un-stunits);
cdb->t_code = 1; /* Don't reset log */
} else {
EPRINTF1("st%d: stmkcdb: read log and reset\n",
un-stunits);
cdb->t_code = 0; /* Reset log */
}
cdb->high_count = 0x12; /* Tape log */
dsi->un_timeout = SHORT_TIMEOUT;
un->un_flags |= (SC_UNF_RECV_DATA | dsi->un_flags);
elog = (struct st_log *)dsi->un_mspl;
bzero((caddr_t)elog, sizeof (struct st_log));
un->un_dma_addr = (int)dsi->un_mspl - (int)DVMA;
un->un_dma_count = cdb->low_count =
sizeof (struct st_log);
break;
case SC_MODE_SENSE:
EPRINTF1("st%d: stmkcdb: mode sense\n", un-stunits);
dsi->un_timeout = SHORT_TIMEOUT;
un->un_flags |= (SC_UNF_RECV_DATA | dsi->un_flags);
em = (struct st_ms_mspl *)dsi->un_mspl;
bzero((caddr_t)em, sizeof (struct st_ms_mspl));
un->un_dma_addr = (int)dsi->un_mspl - (int)DVMA;
un->un_dma_count = cdb->low_count =
sizeof (struct st_ms_mspl);
break;
case SC_DENSITY0:
EPRINTF1("st%d: density0 selected\n", un-stunits);
density = dp->density[0];
speed = dp->speed[0];
goto MODE; /* goto SC_MODE_SELECT... */
/* break; */
case SC_DENSITY1:
EPRINTF1("st%d: density1 selected\n", un-stunits);
density = dp->density[1];
speed = dp->speed[1];
goto MODE; /* goto SC_MODE_SELECT... */
/* break; */
case SC_DENSITY2:
EPRINTF1("st%d: density2 selected\n", un-stunits);
density = dp->density[2];
speed = dp->speed[2];
goto MODE; /* goto SC_MODE_SELECT... */
/* break; */
case SC_DENSITY3:
EPRINTF1("st%d: density3 selected\n", un-stunits);
density = dp->density[3];
speed = dp->speed[3];
goto MODE; /* goto SC_MODE_SELECT... */
/* break; */
case SC_MODE_SELECT:
MODE:
dsi->un_timeout = SHORT_TIMEOUT;
un->un_cmd = cdb->cmd = SC_MODE_SELECT;
em = (struct st_ms_mspl *)dsi->un_mspl;
bzero((caddr_t)em, sizeof (struct scsi_inquiry_data));
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = (int)dsi->un_mspl - (int)DVMA;
un->un_dma_count = cdb->low_count =
sizeof (struct st_ms_mspl);
em->bufm = 1;
em->bd_len = MS_BD_LEN;
em->density = density;
em->speed = speed;
if (dsi->un_options & ST_VARIABLE) {
EPRINTF("stmkcdb: Variable length I/O selected\n");
} else {
EPRINTF("stmkcdb: Fixed length I/O selected\n");
em->mid_bl = (dsi->un_dev_bsize >> 8) & 0xff;
em->low_bl = dsi->un_dev_bsize & 0xff;
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi)) {
un->un_flags = dsi->un_flags |
(un->un_flags & ~SC_UNF_RECV_DATA);
cdb->cmd = SC_QIC02;
cdb->high_count = density;
cdb->low_count = 0;
un->un_dma_count = 0;
}
#endif ST_SYSGEN
}
/* For MT-02, disable soft error reporting */
if (IS_EMULEX(dsi)) {
ems = (struct st_ms_exabyte *)dsi->un_mspl;
ems->optional1 = 0x01;
un->un_dma_count++;
cdb->low_count++;
/* For Exabyte, enable even byte disconnect */
} else if (IS_EXABYTE(dsi)) {
ems = (struct st_ms_exabyte *)dsi->un_mspl;
/* ems->optional1 = 0x04; */
ems->optional1 = 0x0C;
un->un_dma_count++;
cdb->low_count++;
}
break;
case SC_TEST_UNIT_READY:
#ifdef STDEBUG
DPRINTF1("st%d: stmkcdb: test unit\n", un-stunits);
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = SHORT_TIMEOUT;
break;
#endif STDEBUG
case SC_RESERVE:
#ifdef STDEBUG
EPRINTF1("st%d: stmkcdb: reserve\n", un-stunits);
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = SHORT_TIMEOUT;
break;
#endif STDEBUG
case SC_RELEASE:
EPRINTF1("st%d: stmkcdb: release\n", un-stunits);
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
un->un_dma_addr = un->un_dma_count = 0;
dsi->un_timeout = SHORT_TIMEOUT;
break;
#ifdef sun2
case SC_READ_XSTATUS_CIPHER:
DPRINTF1("st%d: stmkcdb: read cipher status\n", un-stunits);
dsi->un_timeout = NORMAL_TIMEOUT;
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
cdb->cmd = un->un_cmd = SC_QIC02;
cdb->high_count = SC_READ_XSTATUS_CIPHER;
un->un_dma_addr = un->un_dma_count = 0;
break;
#endif sun2
default:
dsi->un_timeout = NORMAL_TIMEOUT;
un->un_flags = (un->un_flags & ~SC_UNF_RECV_DATA) |
dsi->un_flags;
EPRINTF2("st%d: stmkcdb: invalid command %x\n", un-stunits,
un->un_cmd);
break;
}
/* Save last command for stintr error recovery */
dsi->un_last_cmd = un->un_cmd;
}
typedef enum stintr_error_resolution {
real_error, /* We found a genuine error */
psuedo_error, /* What looked like an error is actually OK */
more_processing /* We need to run another command */
} stintr_error_resolution;
stintr_error_resolution stintr_handle_error(),
stintr_ran_append(), stintr_append_error(),
stintr_file_mark_detected(), stintr_eot(),
stintr_change(), stintr_ran_change();
stintr(c, resid, error)
register struct scsi_ctlr *c;
register u_int error;
int resid;
{
register struct scsi_unit *un;
register struct buf *bp;
register struct scsi_tape *dsi;
register struct st_sense *ssd;
register u_char status = 0;
/*DPRINTF("stintr:\n");*/
un = c->c_un;
bp = un->un_md->md_utab.b_forw;
if (bp == NULL || bp->b_flags & B_DONE) {
EPRINTF("stintr: bp = NULL\n");
return;
}
dsi = &stape[MTUNIT(bp->b_dev)];
#ifdef STDEBUG
/*DPRINTF2("stintr: c= 0x%x, un= 0x%x, ", c, un);*/
/*DPRINTF1("bp= 0x%x\n", bp);*/
if (st_debug > 2)
st_print_cmd_status(error, un, dsi);
#endif STDEBUG
if (error || (dsi->un_openf < OPEN) || resid) {
/*
* Special processing for driver command timeout errors.
* Also, log location tape died at.
*/
if (error == SE_TIMEOUT) {
dsi->un_openf = CLOSED;
dsi->un_status = SC_TIMEOUT;
dsi->un_err_resid = resid;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_reset_occurred = 1;
bp->b_flags |= B_ERROR;
bp->b_error = EIO;
goto STINTR_WRAPUP;
}
/*
* Special processing for scsi bus failures. Note,
* this error is implies a SCSI bus handshake failure.
* SCSI may now be dead too.
*/
if (error == SE_FATAL) {
dsi->un_openf = CLOSED;
dsi->un_status = SC_FATAL;
dsi->un_reset_occurred = 1;
dsi->un_err_resid = resid;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
bp->b_flags |= B_ERROR;
bp->b_error = EIO;
goto STINTR_WRAPUP;
}
/*
* Check if we're closing the tape device.
*/
if (dsi->un_openf <= CLOSING) {
#ifdef STDEBUG
if (dsi->un_openf == CLOSED) {
EPRINTF("stintr: warning, already closed\n");
}
#endif STDEBUG
dsi->un_openf = CLOSED;
untimeout(sttimer, (caddr_t)(bp->b_dev));
goto STINTR_SUCCESS;
}
/*
* Special processing for opening the tape.
*/
ssd = (struct st_sense *)dsi->un_mspl;
if (dsi->un_openf <= OPENING) {
stintr_opening(un, dsi, bp, ssd);
goto STINTR_WRAPUP;
}
DPRINTF2("stintr: dma count= %u blk count= %u ",
un->un_dma_count, un->un_count);
DPRINTF2("error= %d resid= %d\n", error, resid);
/*
* Processing for failed command -- do a request sense.
*/
if (un->un_scb.chk &&
(dsi->un_openf == OPEN ||
dsi->un_openf == RETRYING_CMD)) {
stintr_sense(dsi, un, resid);
return;
}
/*
* Finished running request sense, verify that it
* worked and restore state variable. Actual error
* processing will be done by stintr_handle_error.
*/
if (dsi->un_openf == SENSING ||
dsi->un_openf == SENSING_RETRY) {
stintr_ran_sense(dsi, un, &resid);
}
/*
* Finished with density change. Retry command.
*/
if (dsi->un_openf == DENSITY_CHANGING) {
if (stintr_ran_change(dsi, un, &resid) == real_error) {
EPRINTF("stintr: density change failed\n");
bp->b_flags |= B_ERROR;
goto STINTR_WRAPUP;
}
return;
}
/*
* Special processing for retried commands.
*/
if (dsi->un_openf == RETRYING_CMD) {
un->un_flags &= ~SC_UNF_GET_SENSE;
if (un->un_scb.chk || un->un_scb.busy) {
EPRINTF("stintr: retried cmd failed\n");
dsi->un_err_resid = resid;
dsi->un_status = ssd->key;
bp->b_flags |= B_ERROR;
dsi->un_openf = OPEN;
goto STINTR_WRAPUP;
} else {
/*DPRINTF("stintr: retried cmd worked\n");*/
dsi->un_err_resid = resid;
dsi->un_status = 0;
dsi->un_openf = OPEN;
goto STINTR_SUCCESS;
}
}
/*
* Process all other errors here
*/
switch (stintr_handle_error(c, un, bp, dsi, &resid, error)) {
case real_error:
/* This error is FATAL ! */
/*DPRINTF("stintr: real error handling\n");*/
bp->b_flags |= B_ERROR; goto STINTR_WRAPUP;
case psuedo_error:
/* This error really isn't an error. */
/*DPRINTF("stintr: psuedo error handling\n");*/
status = dsi->un_status; goto STINTR_SUCCESS;
case more_processing:
/*DPRINTF("stintr: more processing\n");*/
return;
}
} else {
STINTR_SUCCESS:
/*
* Process all successful commands with driver in
* OPEN state here.
*/
switch (un->un_cmd) {
case SC_READ:
dsi->un_status = status;
dsi->un_lastior = 2;
if (dsi->un_options & ST_VARIABLE) {
dsi->un_next_block++;
dsi->un_records++;
dsi->un_blocks += un->un_count >> 9; /* /512 */
} else {
dsi->un_records++;
dsi->un_next_block += dsi->un_last_bcount;
dsi->un_blocks += dsi->un_last_bcount;
}
break;
case SC_WRITE:
dsi->un_status = status;
dsi->un_lastiow = 3;
if (dsi->un_options & ST_VARIABLE) {
dsi->un_next_block++;
dsi->un_records++;
dsi->un_blocks += un->un_count >> 9; /* /512 */
} else {
dsi->un_records++;
dsi->un_next_block += dsi->un_last_bcount;
dsi->un_blocks += dsi->un_last_bcount;
}
break;
case SC_WRITE_FILE_MARK:
dsi->un_eof = ST_NO_EOF;
dsi->un_lastiow = 1;
dsi->un_offset = 0;
dsi->un_next_block = 0;
dsi->un_last_block = 0; /* no reads allowed */
dsi->un_fileno += un->un_count;
break;
case SC_BSPACE_FILE:
/*DPRINTF("stintr: bspace file\n");*/
dsi->un_next_block = INF;
dsi->un_offset = INF;
goto ST_SPACE_FILE;
case SC_SPACE_FILE:
/*DPRINTF("stintr: space file\n");*/
dsi->un_next_block = 0;
dsi->un_offset = 0;
ST_SPACE_FILE:
dsi->un_lastior = dsi->un_lastiow = 0;
dsi->un_last_block = INF;
dsi->un_eof = ST_NO_EOF;
dsi->un_fileno += un->un_count;
break;
case SC_SPACE_REC:
/*DPRINTF("stintr: space rec\n");*/
dsi->un_lastior = 1;
dsi->un_next_block += un->un_count;
break;
case SC_REWIND:
/*DPRINTF("stintr: rewind\n");*/
dsi->un_lastior = dsi->un_lastiow = 0;
dsi->un_offset = 0;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_blocks = 0;
dsi->un_records = 0;
dsi->un_fileno = 0;
dsi->un_eof = ST_NO_EOF;
break;
case SC_MODE_SELECT:
case SC_ERASE_TAPE:
case SC_LOAD:
case SC_UNLOAD:
case SC_QIC02:
dsi->un_lastior = dsi->un_lastiow = 0;
dsi->un_offset = 0;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_blocks = 0;
dsi->un_records = 0;
dsi->un_fileno = 0;
dsi->un_eof = ST_NO_EOF;
dsi->un_retry_ct = 0;
dsi->un_underruns = 0;
dsi->un_options &=
~(ST_AUTODEN_OVERRIDE | ST_NO_POSITION);
break;
case SC_REQUEST_SENSE:
case SC_READ_LOG:
dsi->un_retry_ct = st_get_retries(dsi, error);
break;
case SC_RESERVE:
case SC_RELEASE:
case SC_INQUIRY:
case SC_MODE_SENSE:
#ifdef sun2
case SC_READ_XSTATUS_CIPHER:
#endif sun2
break;
default:
/* If you get here, there's a bug in the driver */
EPRINTF2("st%d: stintr: invalid command %x\n",
un-stunits, un->un_cmd);
break;
}
STINTR_WRAPUP:
/*
* Wrap up command processing for a command that has run
* to completion (successfully or otherwise).
*/
dsi->un_timeout = 0; /* Disable time-outs */
if (bp == &un->un_sbuf &&
((un->un_flags & SC_UNF_DVMA) == 0)) {
(*c->c_ss->scs_done)(un->un_md);
} else {
mbudone(un->un_md);
un->un_flags &= ~SC_UNF_DVMA;
}
if (dsi->un_bufcnt-- >= MAXSTBUF)
wakeup((caddr_t) &dsi->un_bufcnt);
}
}
/*
* Handle a possible tape error.
*/
static stintr_error_resolution
stintr_handle_error(c, un, bp, dsi, resid, error)
register struct scsi_ctlr *c;
register struct scsi_unit *un;
register struct buf *bp;
register struct scsi_tape *dsi;
int *resid;
u_int error;
{
register struct st_sense *ssd = (struct st_sense *)dsi->un_mspl;
register struct st_drive_table *dp;
dp = (struct st_drive_table *)dsi->un_dtab;
/* Log error info for "mt status" (ioctl) error reporting */
dsi->un_err_resid = *resid;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = ssd->key;
dsi->un_retry_ct = st_get_retries(dsi, error);
if (un->un_scb.chk) {
/*
* If you get a length error and resid != 0, you read a smaller
* record than you thought it was. No problem. Otherwise,
* we've lost part of a record which is an EINVAL error.
*/
if (ST_LENGTH(dsi, ssd)) {
EPRINTF("stintr_handle_error: length error\n");
dsi->un_options |= ST_NO_POSITION;
bp->b_resid = *resid;
if (*resid == 0 || dsi->un_last_cmd != SC_READ) {
dsi->un_status = SC_LENGTH;
dsi->un_err_resid = bp->b_resid = bp->b_bcount;
bp->b_error = EINVAL;
return (real_error);
}
/* Something else failed too. */
if (! ssd->key)
return (psuedo_error);
}
if (ST_FILE_MARK(dsi, ssd)) {
return (stintr_file_mark_detected(un, bp, dsi, *resid));
}
/*
* Special processing for tape write append.
*/
if (dsi->un_openf == APPEND_TESTING) {
DPRINTF("stintr_handle_error: checking write append test\n");
return (stintr_ran_append(dsi, un, resid));
/*
* Check for tape write append error. Note, only available
* for QIC tapes and writing or writing filemarks.
*/
} else if (ssd->key == SC_ILLEGAL_REQUEST &&
(dsi->un_options & ST_QIC) &&
(dsi->un_last_cmd == SC_WRITE ||
dsi->un_last_cmd == SC_WRITE_FILE_MARK)) {
EPRINTF("stintr_handle_error: write append error\n");
return (stintr_append_error(dsi, un, *resid));
/*
* Special processing for auto-density (format) during
* first read at BOT. Triggered by read, write, space
* file, and space record which fails with blank check
* error.
*/
} else if (((dsi->un_last_cmd == SC_READ) ||
(dsi->un_last_cmd == SC_SPACE_REC) ||
(dsi->un_last_cmd == SC_SPACE_FILE)) &&
(ssd->key == SC_BLANK_CHECK) &&
(dp->density[0] != dp->density[1]) &&
(dsi->un_fileno < ST_AUTODEN_LIMIT) &&
(dsi->un_next_block == 0)) {
EPRINTF("stintr_handle_error: changing density\n");
return (stintr_change(dsi, un, *resid));
/*
* Check for EOT. Note, two cases are checked for.
*/
} else if (ssd->key == SC_BLANK_CHECK) {
EPRINTF("stintr_handle_error: blank check\n");
return (stintr_eot(bp, dsi, *resid));
} else if (ST_END_OF_TAPE(dsi, ssd)) {
EPRINTF("stintr_handle_error: eot\n");
dsi->un_status = SC_EOT;
return (stintr_eot(bp, dsi, *resid));
} else if (ssd->key == SC_NOT_READY) {
EPRINTF("stintr_handle_error: no tape\n");
dsi->un_reset_occurred = 2;
return (real_error);
} else if (ssd->key == SC_WRITE_PROTECT &&
(dsi->un_last_cmd == SC_WRITE ||
dsi->un_last_cmd == SC_WRITE_FILE_MARK)) {
if (dsi->un_ctype == ST_TYPE_ARCHIVE ||
dsi->un_ctype == ST_TYPE_WANGTEK) {
log(LOG_ERR, wrong_media, un-stunits);
dsi->un_status = SC_MEDIA;
dsi->un_read_only = 3;
} else {
EPRINTF("stintr_handle_error: write protected\n");
dsi->un_read_only = 2;
}
bp->b_error = EACCES;
return (real_error);
/*
* A block had to be read/written more than once but was
* successfully read/written. Just bump stats and consider
* the operation a success.
*/
} else if (ssd->key == SC_RECOVERABLE_ERROR) {
EPRINTF("stintr_handle_error: soft error\n");
return (psuedo_error);
} else if (ssd->key == SC_UNIT_ATTENTION) {
EPRINTF("stintr_handle_error: reset\n");
dsi->un_reset_occurred = 2;
return (real_error);
} else if (ssd->key == SC_ILLEGAL_REQUEST) {
#ifdef ST_SYSGEN
/*
* Sysgen will fail to rewind if it's in the middle of
* a file. Also, space file has the same problem.
*/
if (IS_SYSGEN(dsi) &&
(dsi->un_last_cmd == SC_REWIND ||
dsi->un_last_cmd == SC_SPACE_FILE)) {
return (real_error);
}
#endif ST_SYSGEN
/*
* Mode selects, reserve, and release can fail on
* foreign tape drives. Report error, but don't
* print error message in this case.
*/
if (dsi->un_last_cmd == SC_MODE_SELECT ||
dsi->un_last_cmd == SC_RESERVE ||
dsi->un_last_cmd == SC_RELEASE)
return (real_error);
st_error_message(c, dsi, LOG_CRIT);
return (real_error);
/*
* Have an sense key error we aren't decoding.
*/
} else {
st_error_message(c, dsi, LOG_CRIT);
return (real_error);
}
/*
* Check for reservation conflict error.
*/
} else if (un->un_scb.is && un->un_scb.busy) {
EPRINTF("stintr_handle_error: reservation conflict\n");
dsi->un_status = SC_RESERVATION;
bp->b_error = EPERM;
return (real_error);
/*
* Check for busy error. Note, this must follow reservation
* conflict test.
*/
} else if (un->un_scb.busy) {
EPRINTF("stintr_handle_error: busy\n");
dsi->un_status = SC_BUSY;
return (real_error);
/*
* Check for host adapter error.
*/
} else if (error != 0) {
EPRINTF1("stintr: host adapter error, error= %d\n", error);
dsi->un_status = SC_FATAL;
dsi->un_err_resid = bp->b_resid = *resid;
st_error_message(c, dsi, LOG_ERR);
return (real_error);
/*
* Have left over residue data from last command. Note, for
* Request sense and mode select, this is allowable as there
* are variations here from vendor to vendor.
*/
} else if (*resid != 0) {
if (un->un_cmd == SC_REQUEST_SENSE ||
dsi->un_last_cmd == SC_MODE_SELECT)
return (psuedo_error);
EPRINTF1("stintr: residue error, residue= %d\n", *resid);
dsi->un_status = SC_RESIDUE;
dsi->un_err_resid = bp->b_resid = *resid;
#ifdef STDEBUG
st_error_message(c, dsi, LOG_ERR);
#endif STDEBUG
return (real_error);
/*
* Have an unknown error. Don't know what went wrong.
*/
} else {
EPRINTF("stintr_handle_error: unknown error\n");
st_error_message(c, dsi, LOG_CRIT);
return (real_error);
}
}
/*
* Special processing for opening and closing the tape drive.
* Note, no request sense is performed until the device is open.
*/
static
stintr_opening(un, dsi, bp, ssd)
register struct scsi_unit *un;
register struct scsi_tape *dsi;
register struct buf *bp;
register struct st_sense *ssd;
{
register struct sysgen_sense *sgs;
register struct scsi_cdb6 *cdb;
register struct st_ms_mspl *smsd;
cdb = (struct scsi_cdb6 *)&un->un_cdb;
smsd = (struct st_ms_mspl *)ssd;
sgs = (struct sysgen_sense *)ssd;
/* Reservation conflict error (no hope) */
if (un->un_scb.is && un->un_scb.busy) {
EPRINTF("stintr_opening: reservation error\n");
dsi->un_status = SC_RESERVATION;
bp->b_error = EPERM;
dsi->un_openf = OPEN_FAILED;
bp->b_flags |= B_ERROR;
return;
#ifdef ST_SYSGEN
/*
* Since the sysgen will always return an illegal command
* error if the tape is not rewound, we have to handle this
* special case here during opening.
*/
} else if ((IS_SYSGEN(dsi) || IS_DEFAULT(dsi)) &&
(cdb->cmd == SC_REQUEST_SENSE) && sgs->illegal) {
EPRINTF("stintr_opening: sysgen found\n");
dsi->un_openf = OPENING_SYSGEN;
bp->b_flags |= B_ERROR;
return;
} else if (IS_SYSGEN(dsi) && (cdb->cmd == SC_REQUEST_SENSE)) {
dsi->un_openf = OPENING_SYSGEN;
dsi->un_read_only = 0;
if (sgs->write_prot) {
EPRINTF("stintr_opening: sysgen read only\n");
dsi->un_read_only = 2;
}
if (sgs->no_cart) {
EPRINTF("stintr_opening: no tape\n");
dsi->un_openf = OPEN_FAILED_TAPE;
bp->b_flags |= B_ERROR;
return;
}
#endif ST_SYSGEN
/* Check condition error (maybe recoverable) */
} else if (un->un_scb.chk) {
EPRINTF("stintr_opening: check condition\n");
dsi->un_openf = OPEN_FAILED_TAPE;
bp->b_flags |= B_ERROR;
return;
/* Busy error (recoverable) */
} else if (un->un_scb.busy) {
EPRINTF("stintr_opening: busy\n");
dsi->un_openf = OPEN_FAILED_LOADING;
bp->b_flags |= B_ERROR;
return;
}
/*
* If we were closing the tape then opened it before it was done,
* we've screwed up handling the rewind command. This is fixed here.
*/
if (cdb->cmd == SC_REWIND) {
DPRINTF("stintr_opening: rewind done\n");
dsi->un_lastior = dsi->un_lastiow = 0;
dsi->un_offset = 0;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_fileno = 0;
dsi->un_eof = ST_NO_EOF;
}
/*
* If running mode_sense, check if tape is write protected. Note, using
* mode sense during normal operation will cause a fault condition.
*/
if (cdb->cmd == SC_MODE_SENSE) {
DPRINTF1("stintr_opening: density= 0x%x\n", smsd->density);
dsi->un_read_only = 0;
if (smsd->wp) {
DPRINTF("stintr_opening: CCS read only\n");
dsi->un_read_only = 2;
}
if (IS_DEFAULT(dsi)) {
dsi->un_dev_bsize = (smsd->high_bl <<16) +
(smsd->mid_bl <<8) + (smsd->low_bl);
DPRINTF1("stintr_opening: block size= %d\n",
dsi->un_dev_bsize);
}
}
#ifdef ST_SYSGEN
/*
* Since a request sense is issued after an inquiry cmd, this
* will cause a fault condition. Thus the first byte of status
* should be 0x20 if it's a sysgen controller; otherwise it's
* some other drive. Also, verify that tape is loaded in drive
* and check if it's write protected.
*/
if ((cdb->cmd == SC_REQUEST_SENSE) && (sgs->disk_sense[0] == 0x20)) {
dsi->un_openf = OPENING_SYSGEN;
dsi->un_read_only = 0;
if (sgs->write_prot) {
DPRINTF("stintr_opening: sysgen read only\n");
dsi->un_read_only = 2;
}
if (sgs->no_cart) {
dsi->un_openf = OPEN_FAILED_TAPE;
}
}
#endif ST_SYSGEN
}
/*
* Command failed, need to run a request sense command to determine why.
*/
static
stintr_sense(dsi, un, resid)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
register int resid;
{
/*
* Note that ststart will call stmkcdb, which will notice
* that the flag is set and not do the copy of the cdb,
* doing a request sense rather than the normal command.
*/
stsave_cmd(un, resid);
if (dsi->un_openf == RETRYING_CMD)
dsi->un_openf = SENSING_RETRY;
else
dsi->un_openf = SENSING;
un->un_cmd = SC_REQUEST_SENSE;
(*un->un_c->c_ss->scs_go)(un->un_md);
}
/*
* Cleanup after running request sense command to see why the real
* command failed.
*/
static
stintr_ran_sense(dsi, un, resid_ptr)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
register int *resid_ptr;
{
/* Restore to previous state */
if (dsi->un_openf == SENSING_RETRY)
dsi->un_openf = RETRYING_CMD;
else
dsi->un_openf = OPEN;
/*
* Check if request sense command failed. This should never happen!
*/
if (un->un_scb.chk || un->un_scb.busy) {
dsi->un_status = SC_FATAL;
EPRINTF("stintr_ran_sense: request sense failed\n");
}
/* Restore failed command which caused request sense to be run. */
strestore_cmd(dsi, un, resid_ptr);
#ifdef ST_SYSGEN
/* Special processing for sysgen tape controller. */
if (IS_SYSGEN(dsi))
stintr_sysgen(dsi, un);
#endif ST_SYSGEN
}
#ifdef ST_SYSGEN
/*
* Translate Sysgen QIC-02 status in SCSI status that the
* error handler can understand. In other words, map
* QIC-02 sense status into SCSI sense key info.
*/
static
stintr_sysgen(dsi, un)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
{
register struct sysgen_sense *sgs;
register struct st_sense *ssd;
sgs = (struct sysgen_sense *) dsi->un_mspl;
ssd = (struct st_sense *) dsi->un_mspl;
sgs->disk_sense[2] = 0; /* sense key = no sense */
if (sgs->bot && (dsi->un_fileno != 0) &&
(dsi->un_next_block != 0)) {
log(LOG_ERR, "st%d: warning, tape rewound\n", un-stunits);
dsi->un_reset_occurred = 2;
ssd->key = SC_NOT_READY;
}
/* Handle the following errors to guarantee detection. */
if (sgs->file_mark)
ssd->fil_mk = 1;
if (sgs->eot)
ssd->eom = 1;
if (sgs->write_prot) {
dsi->un_read_only = 2;
ssd->key = SC_WRITE_PROTECT;
}
if (sgs->disk_sense[0] == 0x20 || sgs->illegal) {
EPRINTF("stintr_sysgen: illegal request\n");
ssd->key = SC_ILLEGAL_REQUEST;
} else if (sgs->not_ready || sgs->reset || sgs->no_cart) {
EPRINTF("stintr_sysgen: not ready\n");
ssd->key = SC_NOT_READY;
} else if (sgs->no_data) {
EPRINTF("stintr_sysgen: no data\n");
ssd->key = SC_BLANK_CHECK;
} else if (sgs->write_prot) {
EPRINTF("stintr_sysgen: write protected\n");
dsi->un_read_only = 2;
ssd->key = SC_WRITE_PROTECT;
} else if (sgs->data_err) {
EPRINTF("stintr_sysgen: hardware error\n");
ssd->key = SC_HARDWARE_ERROR;
} else if (sgs->retries) {
EPRINTF("stintr_sysgen: soft error\n");
ssd->key = SC_RECOVERABLE_ERROR;
}
}
#endif ST_SYSGEN
/*
* Change tape density/format setting to other one if read fails at BOT.
*/
static stintr_error_resolution
stintr_change(dsi, un, resid)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
int resid;
{
EPRINTF("stintr_change:\n");
/*
* Note that ststart will call stmkcdb, which will notice
* that the flag is set and not do the copy of the cdb,
* doing a request sense rather than the normal command.
*/
stsave_cmd(un, resid);
dsi->un_openf = DENSITY_CHANGING;
un->un_flags &= ~SC_UNF_DVMA;
un->un_cmd = SC_REWIND;
dsi->un_timeout = 12;
(*un->un_c->c_ss->scs_go)(un->un_md);
return (more_processing);
}
/*
* Check results after changing tape density/format. Try running read
* command again.
* FIXME: really should extend this to 4 densities from 2.
*/
static stintr_error_resolution
stintr_ran_change(dsi, un, resid_ptr)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
register int *resid_ptr;
{
/*
* Check if any command failed. If it did, it's all over.
*/
/*DPRINTF("stintr_ran_change:\n");*/
if (un->un_scb.chk || un->un_scb.busy || un->un_scb.is) {
EPRINTF("stintr_ran_change: command failed\n");
/* FIXME: should we or should we not give up on error ??? */
dsi->un_openf = OPEN;
if (un->un_cmd == SC_READ)
un->un_flags |= SC_UNF_DVMA;
strestore_cmd(dsi, un, resid_ptr);
return (real_error);
}
/*
* If tape is rewound, first change density, and then retry
* failed command. Note, only done for first 2 densities.
*/
switch (un->un_cmd) {
case SC_REWIND:
dsi->un_lastior = 0;
dsi->un_lastiow = 0;
dsi->un_offset = 0;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_fileno = 0;
dsi->un_eof = ST_NO_EOF;
dsi->un_err_resid = 0;
dsi->un_status = 0;
dsi->un_options &= ~ST_AUTODEN_OVERRIDE;
dsi->un_timeout = 4;
if (dsi->un_density == SC_DENSITY0) {
EPRINTF("stintr_ran_change: trying density2\n");
un->un_cmd = SC_DENSITY1;
} else {
EPRINTF("stintr_ran_change: trying density1\n");
un->un_cmd = SC_DENSITY0;
}
break;
/* case SC_DENSITY0: */
/* case SC_DENSITY1: */
case SC_MODE_SELECT:
if (dsi->un_density == SC_DENSITY0)
dsi->un_density = SC_DENSITY1;
else
dsi->un_density = SC_DENSITY0;
strestore_cmd(dsi, un, resid_ptr);
dsi->un_options |= ST_AUTODEN_OVERRIDE;
dsi->un_openf = RETRYING_CMD;
if (un->un_cmd == SC_READ) {
EPRINTF("stintr_ran_change: read\n");
dsi->un_timeout = 8;
un->un_flags |= SC_UNF_DVMA;
} else {
EPRINTF("stintr_ran_change: space file\n");
dsi->un_timeout = 60;
}
break;
}
(*un->un_c->c_ss->scs_go)(un->un_md);
return (more_processing);
}
/*
* A filemark was detected. Normally, no error is returned; but a
* non-zero residue transfer count is returned to signal the error.
*/
static stintr_error_resolution
stintr_file_mark_detected(un, bp, dsi, resid)
register struct scsi_unit *un;
register struct buf *bp;
register struct scsi_tape *dsi;
register int resid;
{
switch (dsi->un_last_cmd) {
case SC_READ:
EPRINTF("stintr_file_mark: read\n");
if (dsi->un_options & ST_VARIABLE) {
--dsi->un_next_block;
} else if (resid > 0) {
dsi->un_next_block -= (resid / dsi->un_dev_bsize);
}
dsi->un_last_block = dsi->un_next_block;
break;
case SC_SPACE_REC:
EPRINTF("stintr_file_mark: space rec\n");
dsi->un_last_block = dsi->un_next_block;
dsi->un_err_resid = resid = bp->b_bcount;
dsi->un_status = SC_EOF;
bp->b_resid = resid;
dsi->un_eof = ST_EOF_LOG;
return (real_error);
/* break; */
case SC_SPACE_FILE:
EPRINTF("stintr_file_mark: space file\n");
break;
default:
EPRINTF2("st%d: EOF on cmd 0x%x\n",
un-stunits, un->un_cmd);
break;
}
/*
* If EOF, don't fail this time. Return what data we could
* get and set flag to inhibit further reads.
*/
bp->b_resid = resid;
if (resid == un->un_count) {
EPRINTF("stintr_file_mark: eof\n");
dsi->un_eof = ST_EOF;
dsi->un_status = SC_EOF;
} else if (resid > 0) {
EPRINTF("stintr_file_mark: log eof\n");
dsi->un_eof = ST_EOF_LOG;
} else {
EPRINTF("stintr_file_mark: pending eof\n");
dsi->un_eof = ST_EOF_PENDING;
}
return (psuedo_error);
}
/*
* End of tape. Note, due to buffered mode I/O, there's
* no telling where you really died from eot. We trust
* that the tape drive takes care of these little details.
* Normally no error condition is returned; however, a
* non-zero residue is returned to indicate eot for writes.
* No further writing will be allowed until the tape is changed.
*/
static stintr_error_resolution
stintr_eot(bp, dsi, resid)
register struct buf *bp;
register struct scsi_tape *dsi;
register int resid;
{
/*
* Set residual dma count. Note, for Sysgen's
* this is terminal.
*/
if (IS_SYSGEN(dsi))
bp->b_resid = bp->b_bcount;
else
bp->b_resid = resid;
/* EOT stops file positioning operations */
if ((dsi->un_last_cmd == SC_SPACE_REC) ||
(dsi->un_last_cmd == SC_SPACE_FILE)) {
EPRINTF("stintr_eot: space\n");
bp->b_resid = bp->b_bcount;
return (real_error);
}
/* Ignore EOT for reads */
if (dsi->un_last_cmd == SC_READ) {
EPRINTF("stintr_eot: read\n");
if (dsi->un_options & ST_VARIABLE) {
--dsi->un_next_block;
} else if (resid > 0) {
dsi->un_next_block -= (resid / dsi->un_dev_bsize);
}
dsi->un_last_block = dsi->un_next_block;
return (psuedo_error);
}
/*
* For writes which hit EOT, stop all further writes until a file
* mark written. Note, partial transfers are counted as complete
* transfer failure.
*/
if (dsi->un_last_cmd == SC_WRITE) {
EPRINTF("stintr_eot: write\n");
/* If no resid, override default resid */
dsi->un_lastiow = 3; /* Force filemark write */
dsi->un_eof = ST_EOT; /* no more writes */
}
return (psuedo_error);
}
/*
* Handle an append error. QIC tapes don't allow you to overwrite data
* due to the way data is recorded on the tape. Also, they return a
* false indication of this error if the heads are positioned after
* the last file. The controller depends on blank tape not filemarks
* to know when to let you write. We'd like to start writing after the
* last filemark.
*
* We handle this problem by doing a read and checking
* to see what happens. If it fails, retry the write as it should work
* now. If it works, we're trying to illegally overwrite data.
* Appending files on QIC tapes is such fun!
*/
static stintr_error_resolution
stintr_append_error(dsi, un, resid)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
int resid;
{
register caddr_t *sbuf;
/*
* Allocate a scratch buffer for the read if buffer size is not equal
* to DEV_BSIZE. Otherwise, use internal buffer. We appropriate the
* sbuf for this.
*/
if (dsi->un_dev_bsize == dsi->un_msplsize) {
sbuf = (caddr_t *)dsi->un_mspl;
} else {
sbuf =(caddr_t *) rmalloc(iopbmap,
(long)(dsi->un_dev_bsize + LONG));
if ((int)sbuf == NULL) {
log(LOG_CRIT, iopb_error, MTUNIT(un->un_unit));
return (real_error);
}
}
/* Save the original address for rmfree and word align buffer. */
un->un_scratch_addr = (caddr_t)sbuf;
sbuf = (caddr_t *) LONG_ALIGN(sbuf);
un->un_sbuf.b_un.b_addr = (caddr_t)sbuf;
/*
* s?start will call s?mkcdb, which will notice
* that we are using a special read command and
* not do the copy of the cdb.
*/
stsave_cmd(un, resid);
dsi->un_openf = APPEND_TESTING;
un->un_cmd = SC_READ_APPEND;
(*un->un_c->c_ss->scs_go)(un->un_md);
return (more_processing);
}
/*
* Cleanup after running a read command on behalf of append error evaluation.
*/
static stintr_error_resolution
stintr_ran_append(dsi, un, resid_ptr)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
int *resid_ptr;
{
if (dsi->un_dev_bsize != dsi->un_msplsize) {
rmfree(iopbmap, (long)(dsi->un_dev_bsize + LONG),
(u_long)(un->un_scratch_addr));
}
/*
* Read failed, thus we are at EOM, so retry write command.
*/
if (un->un_scb.chk) {
DPRINTF("stintr_ran_append: retrying write append\n");
strestore_cmd(dsi, un, resid_ptr);
dsi->un_openf = RETRYING_CMD;
(*un->un_c->c_ss->scs_go)(un->un_md);
return (more_processing);
/*
* Read worked, which means we are not at EOM. Thus,
* the controller legitimately reported the append error.
*/
} else {
DPRINTF("stintr_ran_append: cannot write append\n");
strestore_cmd(dsi, un, resid_ptr);
dsi->un_openf = OPEN;
return (real_error);
}
}
static
st_print_cmd(un)
register struct scsi_unit *un;
{
register int x;
register u_char *y = (u_char *)&(un->un_cdb);
#ifdef lint
un = un;
#endif lint
printf("st%d: failed cmd =", un-stunits);
for (x = 0; x < sizeof (struct scsi_cdb6); x++)
printf(" %x", *y++);
printf("\n");
}
#ifdef STDEBUG
st_print_sid_buffer(sid, count)
register struct scsi_inquiry_data *sid;
register int count;
{
register int x;
register u_char *y = (u_char *)sid;
EPRINTF("sid buffer:");
for (x = 0; x < count; x++)
printf(" %x", *y++);
printf("\n\n");
}
st_print_cmd_status(error, un, dsi)
register u_int error;
register struct scsi_unit *un;
register struct scsi_tape *dsi;
{
register int x;
register u_char *y = (u_char *)&(un->un_cdb);
register struct scsi_ctlr *c = un->un_c;
/* Driver open, no error */
if (error == 0 && (dsi->un_openf == OPEN)) {
EPRINTF("stcmd worked:");
/* Driver open, error */
} else if (error && (dsi->un_openf == OPEN)) {
EPRINTF("stcmd failed:");
/* Driver not open, error */
} else if (error && (dsi->un_openf != OPEN)) {
EPRINTF("stcmd failed (no sense):");
/* Dump Sense data for failed cmd */
} else if (error != 0 && (dsi->un_openf == SENSING)) {
st_error_message(c, dsi, NULL);
return;
}
for (x = 0; x < sizeof (struct scsi_cdb6); x++)
printf(" %x", *y++);
printf("\n");
}
#endif STDEBUG
/*
* Restore old command state after running request sense command.
*/
stsave_cmd(un, resid)
register struct scsi_unit *un;
register int resid;
{
/* Save away old command state. */
un->un_saved_cmd.saved_cmd = un->un_cmd;
un->un_saved_cmd.saved_scb = un->un_scb;
un->un_saved_cmd.saved_cdb = un->un_cdb;
un->un_saved_cmd.saved_resid = resid;
un->un_saved_cmd.saved_dma_addr = un->un_dma_addr;
un->un_saved_cmd.saved_dma_count = un->un_dma_count;
un->un_flags |= SC_UNF_GET_SENSE;
}
/*
* Restore old command state after running request sense command.
*/
strestore_cmd(dsi, un, resid_ptr)
register struct scsi_tape *dsi;
register struct scsi_unit *un;
register int *resid_ptr;
{
un->un_cmd = un->un_saved_cmd.saved_cmd;
un->un_scb = un->un_saved_cmd.saved_scb;
un->un_cdb = un->un_saved_cmd.saved_cdb;
*resid_ptr = un->un_saved_cmd.saved_resid;
un->un_dma_addr = un->un_saved_cmd.saved_dma_addr;
un->un_dma_count = un->un_saved_cmd.saved_dma_count;
un->un_flags &= ~SC_UNF_GET_SENSE;
dsi->un_last_cmd = un->un_cmd;
}
static char *st_key_error_str[] = SENSE_KEY_INFO;
#define MAX_KEY_ERROR_STR \
(sizeof(st_key_error_str)/sizeof(st_key_error_str[0]))
static char *st_hp_error_str[] = {
"", /* 0x00 */
"", /* 0x01 */
"", /* 0x02 */
"write fault", /* 0x03 */
"drive not ready", /* 0x04 */
"drive not selected", /* 0x05 */
"", /* 0x06 */
"", /* 0x07 */
"logical unit fault", /* 0x08 */
"", /* 0x09 */
"error log overflow", /* 0x0a */
"time-out error", /* 0x0b */
"", /* 0x0c */
"soft write error", /* 0x0d */
"soft interface error", /* 0x0e */
"", /* 0x0f */
"", /* 0x10 */
"hard read error", /* 0x11 */
"", /* 0x12 */
"space command error", /* 0x13 */
"no record found", /* 0x14 */
"locate error", /* 0x15 */
"", /* 0x16 */
"soft read error", /* 0x17 */
"soft write error", /* 0x18 */
"", /* 0x19 */
"parameter overrun", /* 0x1a */
"synch. xfer error", /* 0x1b */
"", /* 0x1c */
"verify error", /* 0x1d */
"", /* 0x1e */
"hard write error", /* 0x1f */
"invalid command", /* 0x20 */
"invalid block address", /* 0x21 */
"", /* 0x22 */
"space cmd error", /* 0x23 */
"", /* 0x24 */
"invalid cdb lun", /* 0x25 */
"invalid cdb field", /* 0x26 */
"write protected", /* 0x27 */
"media changed", /* 0x28 */
"drive reset", /* 0x29 */
"mode select changed", /* 0x2a */
"block length error", /* 0x2b */
"cmd sequence error", /* 0x2c */
"overwrite error", /* 0x2d */
"blank tape error", /* 0x2e */
"", /* 0x2f */
"unknown tape format", /* 0x30 */
"format failed", /* 0x31 */
"", /* 0x32 */
"tape length error", /* 0x33 */
"invalid cdb", /* 0x34 */
"undetected ecc error", /* 0x35 */
"no gap found", /* 0x36 */
"miscorrected error", /* 0x37 */
"block sequence error", /* 0x38 */
"tape not ready", /* 0x39 */
"no tape installed", /* 0x3a */
"tape position error", /* 0x3b */
0
};
#define MAX_HP_ERROR_STR \
(sizeof(st_hp_error_str)/sizeof(st_hp_error_str[0]))
static char *st_emulex_error_str[] = {
"", /* 0x00 */
"", /* 0x01 */
"", /* 0x02 */
"", /* 0x03 */
"drive not ready", /* 0x04 */
"", /* 0x05 */
"", /* 0x06 */
"", /* 0x07 */
"", /* 0x08 */
"no tape", /* 0x09 */
"tape too short", /* 0x0a */
"drive timeout", /* 0x0b */
"", /* 0x0c */
"", /* 0x0d */
"", /* 0x0e */
"", /* 0x0f */
"", /* 0x10 */
"hard data error", /* 0x11 */
"", /* 0x12 */
"", /* 0x13 */
"block not found", /* 0x14 */
"", /* 0x15 */
"dma timeout", /* 0x16 */
"write protected", /* 0x17 */
"soft data error", /* 0x18 */
"bad block", /* 0x19 */
"", /* 0x1a */
"", /* 0x1b */
"filemark detected", /* 0x1c */
"compare error", /* 0x1d */
"", /* 0x1e */
"", /* 0x1f */
"invalid command", /* 0x20 */
"", /* 0x21 */
"", /* 0x22 */
"", /* 0x23 */
"", /* 0x24 */
"", /* 0x25 */
"", /* 0x26 */
"", /* 0x27 */
"", /* 0x28 */
"", /* 0x29 */
"", /* 0x2a */
"", /* 0x2b */
"", /* 0x2c */
"", /* 0x2d */
"", /* 0x2e */
"", /* 0x2f */
"unit attention", /* 0x30 */
"command timeout", /* 0x31 */
"", /* 0x32 */
"append error", /* 0x33 */
"end-of-media", /* 0x34 */
0
};
#define MAX_EMULEX_ERROR_STR \
(sizeof(st_emulex_error_str)/sizeof(st_emulex_error_str[0]))
/*
* scsi command decode table.
* FIXME: should merge with sd and form composite table.
*/
static char *st_cmds[] = {
"\010read", /* 0x08 */
"\012write", /* 0x0a */
"\020filemark write", /* 0x10 */
"\201backspace filemark", /* 0x81 */
"\202forwardspace filemark", /* 0x82 */
"\021space record", /* 0x11 */
"\001rewind", /* 0x01 */
"\202retension", /* 0x83 */
"\031erase tape", /* 0x19 */
"\003request sense", /* 0x03 */
"\037read log", /* 0x1f */
"\200unload", /* 0x80 */
"\033load", /* 0x1b */
"\026reserve", /* 0x16 */
"\027release", /* 0x17 */
"\036door lock", /* 0x1e */
"\203read append", /* 0x84 */
"\025mode select", /* 0x15 */
"\032mode sense", /* 0x1a */
"\022inquiry", /* 0x12 */
"\204read append", /* 0x84 */
"\000test unit ready", /* 0x00 */
0
};
/*
* Return the secondary error code, if available.
*/
static u_char
st_get_error_code(dsi, ssd)
register struct scsi_tape *dsi;
register struct st_sense *ssd;
{
switch (dsi->un_ctype) {
case ST_TYPE_EMULEX:
return (ssd->optional_8);
case ST_TYPE_HP:
case ST_TYPE_LMS:
case ST_TYPE_FUJI:
case ST_TYPE_KENNEDY:
return (ssd->error);
}
return (0); /* No secondary error code */
}
/*
* Return the text string associated with the sense key value.
*/
static char *
st_print_key(key_code)
register u_char key_code;
{
static char *unknown_key = "unknown key";
if ((key_code > MAX_KEY_ERROR_STR -1) ||
st_key_error_str[key_code] == NULL) {
return (unknown_key);
}
return (st_key_error_str[key_code]);
}
/*
* Return the text string associated with the secondary error code,
* if availiable.
*/
static char *
st_print_error(dsi, error_code)
register struct scsi_tape *dsi;
register u_char error_code;
{
static char *unknown_error = " ";
switch (dsi->un_ctype) {
case ST_TYPE_HP:
case ST_TYPE_KENNEDY:
case ST_TYPE_LMS:
case ST_TYPE_FUJI:
if (MAX_HP_ERROR_STR > error_code &&
st_hp_error_str[error_code] != NULL)
return (st_hp_error_str[error_code]);
break;
case ST_TYPE_EMULEX:
if ((MAX_EMULEX_ERROR_STR > error_code) &&
st_emulex_error_str[error_code] != NULL)
return (st_emulex_error_str[error_code]);
break;
}
return (unknown_error);
}
/*
* Print the sense key and secondary error codes and dump out the sense bytes.
*/
static
st_error_message(c, dsi, log_error)
register struct scsi_ctlr *c;
register struct scsi_tape *dsi;
int log_error;
{
register struct st_sense *ssd;
register u_char *cp;
register int i;
register u_char error_code;
struct scsi_unit *un = c->c_un;
char *cmdname, **cmdtbl;
int unknown_cmd = 1;
/* In debug mode, dump all cdb bytes too. */
if (st_debug)
st_print_cmd(un);
/* Decode command name */
if (log_error) {
cmdtbl = st_cmds;
cmdname = "<unknown cmd>";
while (*cmdtbl != (char *) NULL) {
if ((u_char)un->un_cmd == (u_char) *cmdtbl[0]) {
cmdname = (char *)((int)(*cmdtbl)+1);
unknown_cmd = 0;
break;
}
cmdtbl++;
}
log(log_error, hard_error, un-stunits, cmdname);
if (unknown_cmd)
st_print_cmd(un);
}
ssd = (struct st_sense *)cp = (struct st_sense *)dsi->un_mspl;
error_code = st_get_error_code(dsi, ssd);
if (error_code) {
log(LOG_ERR, sense_msg2, un-stunits,
ssd->key, st_print_key(ssd->key),
error_code, st_print_error(dsi, error_code));
} else {
log(LOG_ERR, sense_msg1, un-stunits,
ssd->key, st_print_key(ssd->key));
}
if (st_debug) {
printf(" sense =");
for (i = 0; i < sizeof(struct st_sense); i++)
printf(" %x", *cp++);
printf("\n\n");
}
}
/*
* Return the retry count. This tends to be very vendor unique.
*/
static int
st_get_retries(dsi, error)
register struct scsi_tape *dsi;
u_int error;
{
register struct sysgen_sense *sgs;
register struct st_sense *ssd;
register struct st_log *elog;
ssd = (struct st_sense *) dsi->un_mspl;
/*
* These devices maintain their own soft error logging
* statistics. For them, we'll just use their data.
* Otherwise, we'll just count recoverable errors.
*/
switch (dsi->un_ctype) {
case ST_TYPE_WANGTEK:
return ((ssd->optional_10 <<8) + ssd->optional_11);
case ST_TYPE_ARCHIVE:
return ((ssd->error <<8) + ssd->error1);
case ST_TYPE_EMULEX:
return ((ssd->optional_9 <<8) + ssd->optional_10);
case ST_TYPE_HP:
case ST_TYPE_LMS:
case ST_TYPE_FUJI:
/* Only valid if read log command worked. */
if (dsi->un_last_cmd != SC_READ_LOG && error)
break;
elog = (struct st_log *) dsi->un_mspl;
if (dsi->un_lastiow)
return ((elog->wr_soft[0] <<8) + elog->wr_soft[1]);
else
return ((elog->rd_soft[0] <<8) + elog->rd_soft[1]);
case ST_TYPE_SYSGEN:
sgs = (struct sysgen_sense *) dsi->un_mspl;
return (sgs->retry_ct);
case ST_TYPE_EXB8200:
case ST_TYPE_EXB8500:
return ((ssd->optional_16 <<16) + (ssd->optional_17 <<8) +
ssd->optional_18);
}
/*
* The default case is to count the number of times
* a recoverable error sense key is posted.
*/
if (dsi->un_status == SC_RECOVERABLE_ERROR)
return (dsi->un_retry_ct +1);
else
return (dsi->un_retry_ct);
}
/*
* Perform max. record blocking. If greater than 64KB -1, block into
* 64 KB -2 requests or DMA engine will fail. Otherwise, let it through
* unmodified.
*/
void
stminphys(bp)
struct buf *bp;
{
if (bp->b_bcount > MAX_ST_DEV_SIZE +1)
bp->b_bcount = MAX_ST_DEV_SIZE;
}
stread(dev, uio)
register dev_t dev;
register struct uio *uio;
{
register struct scsi_unit *un;
register int resid;
register u_int unit;
register struct scsi_tape *dsi;
/*DPRINTF("stread:\n");*/
unit = MTUNIT(dev);
if (unit >= nstape) {
EPRINTF2("st%d: stread: invalid unit %d\n", unit, unit);
return (ENXIO);
}
un = &stunits[unit];
dsi = &stape[unit];
#ifdef lint
resid = 0; resid = resid;
#endif lint
if (dsi->un_options & ST_VARIABLE) {
#ifdef ST_AUTOPOSITION
/*
* Position the tape to the desired record. Note,
* no repositioning will take place if the request
* count is not the same as the device block size.
* The device block size is the request size of block 0.
*/
if (uio->uio_offset != dsi->un_offset) {
if ((dsi->un_options & ST_NO_POSITION) == 0 &&
#if ST_AUTOPOSITION < 1
uio->uio_iov->iov_len == BLKDEV_IOSIZE &&
#endif ST_AUTOPOSITION < 1
uio->uio_iov->iov_len == dsi->un_dev_bsize) {
un->un_blkno = uio->uio_offset / dsi->un_dev_bsize;
EPRINTF2("stread: want blk= %u, at blk= %u\n",
un->un_blkno, dsi->un_next_block);
if (stposition_block(dev, dsi, un->un_blkno)) {
dsi->un_options |= ST_NO_POSITION;
EPRINTF("stread: warning, tape not repositioned\n");
DPRINTF1("stread: block size= %d\n",
dsi->un_dev_bsize);
return (EIO);
} else {
dsi->un_dev_bsize = uio->uio_iov->iov_len;
DPRINTF("stread: tape repositioned\n");
DPRINTF1("stread: block size= %d\n",
dsi->un_dev_bsize);
}
}
} else if (uio->uio_offset == 0) {
dsi->un_dev_bsize = uio->uio_iov->iov_len;
EPRINTF1("stread: block size= %d\n",
dsi->un_dev_bsize);
}
dsi->un_offset = uio->uio_offset + uio->uio_iov->iov_len;
#endif ST_AUTOPOSITION
resid = uio->uio_resid;
return (physio(ststrategy, &un->un_rbuf, dev, B_READ,
stminphys, uio));
/* HANDLE FIXED LENGTH BLOCK CASE */
} else {
/*
* If read data is not a multiple of our block size,
* we won't let you read.
*/
if (dsi->un_iovlen != uio->uio_iov->iov_len) {
DPRINTF("stread: checking block size\n");
dsi->un_iovlen = uio->uio_iov->iov_len;
if (uio->uio_iov->iov_len % dsi->un_dev_bsize) {
log(LOG_ERR, size_error, unit,
"stread", dsi->un_dev_bsize);
return (EINVAL);
}
}
#ifdef ST_AUTOPOSITION
/*
* Position the tape to the desired record.
*/
if (uio->uio_offset != dsi->un_offset) {
un->un_blkno = uio->uio_offset / dsi->un_dev_bsize;
#if ST_AUTOPOSITION < 1
if ((dsi->un_options & ST_NO_POSITION) == 0 &&
uio->uio_iov->iov_len == BLKDEV_IOSIZE) {
EPRINTF2("stread: repositioning, want blk= %u, at blk= %u\n",
(int)un->un_blkno, dsi->un_next_block);
if (stposition_block(dev, dsi, un->un_blkno)) {
dsi->un_options |= ST_NO_POSITION;
EPRINTF("stread: repositioning failure\n");
return (EIO);
}
}
#else ST_AUTOPOSITION < 1
if ((dsi->un_options & ST_NO_POSITION) == 0) {
EPRINTF2("stread: repositioning, want blk= %u, at blk= %u\n",
(int)un->un_blkno, dsi->un_next_block);
if (stposition_block(dev, dsi, un->un_blkno)) {
dsi->un_options |= ST_NO_POSITION;
EPRINTF("stread: repositioning failure\n");
return (EIO);
}
}
#endif ST_AUTOPOSITION < 1
}
dsi->un_offset = uio->uio_offset + uio->uio_iov->iov_len;
#endif ST_AUTOPOSITION
resid = uio->uio_resid;
return (physio(ststrategy, &un->un_rbuf, dev, B_READ,
minphys, uio));
}
}
stwrite(dev, uio)
register dev_t dev;
register struct uio *uio;
{
register struct scsi_unit *un;
register struct scsi_tape *dsi;
register u_int unit;
/*DPRINTF("stwrite:\n");*/
unit = MTUNIT(dev);
if (unit >= nstape) {
EPRINTF2("st%d: stwrite: invalid unit %d\n", unit, unit);
return (ENXIO);
}
un = &stunits[unit];
dsi = &stape[unit];
/*
* If the tape was opened read-only, don't allow writes.
*/
if (dsi->un_read_only) {
log(LOG_ERR, wprotect_error,unit);
dsi->un_err_resid = uio->uio_iov->iov_len;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_WRITE_PROTECT;
return (EACCES);
}
if (dsi->un_options & ST_VARIABLE) {
#ifdef ST_AUTOPOSITION
/*
* Check tape position. Only allow repositioning to the
* beginning of a file. Do not allow positioning within
* a file. Note, if this is a QIC tape you'll die later
* since overwriting is prohibited.
*/
if (uio->uio_offset != dsi->un_offset) {
if (uio->uio_offset != 0 ||
#if ST_AUTOPOSITION < 1
uio->uio_iov->iov_len != BLKDEV_IOSIZE ||
#endif ST_AUTOPOSITION < 1
stposition_block(dev, dsi, 0)) {
EPRINTF("stwrite: warning, tape not repositioned\n");
EPRINTF2("stwrite: want blk= %u, at blk=%u\n",
(uio->uio_offset / dsi->un_dev_bsize),
dsi->un_next_block);
} else {
DPRINTF("stwrite: tape repositioned\n");
dsi->un_dev_bsize = uio->uio_iov->iov_len;
}
} else {
dsi->un_dev_bsize = uio->uio_iov->iov_len;
}
dsi->un_offset = uio->uio_offset + uio->uio_iov->iov_len;
#endif ST_AUTOPOSITION
return (physio(ststrategy, &un->un_rbuf, dev, B_WRITE,
stminphys, uio));
/* HANDLE FIXED LENGTH BLOCK CASE */
} else {
/*
* If write data is not a multiple of our block size,
* we won't let you write.
*/
if (dsi->un_iovlen != uio->uio_iov->iov_len) {
DPRINTF("stwrite: checking block size\n");
if (uio->uio_iov->iov_len % dsi->un_dev_bsize) {
log(LOG_ERR, size_error, unit,
"stwrite", dsi->un_dev_bsize);
return (EINVAL);
}
dsi->un_iovlen = uio->uio_iov->iov_len;
}
#ifdef ST_AUTOPOSITION
/*
* Check tape position. Only allow repositioning to the
* beginning of a file. Do not allow positioning within
* a file. Note, if this is a QIC tape you'll die later
* since overwriting is prohibited.
*/
if (uio->uio_offset != dsi->un_offset) {
if (uio->uio_offset != 0 ||
#if ST_AUTOPOSITION < 1
uio->uio_iov->iov_len != BLKDEV_IOSIZE ||
#endif ST_AUTOPOSITION < 1
stposition_block(dev, dsi, 0)) {
EPRINTF("stwrite: warning, tape not repositioned\n");
EPRINTF2("stwrite: want blk= %u, at blk= %u\n",
(uio->uio_offset / dsi->un_dev_bsize),
dsi->un_next_block);
}
}
dsi->un_offset = uio->uio_offset + uio->uio_iov->iov_len;
#endif ST_AUTOPOSITION
return (physio(ststrategy, &un->un_rbuf, dev, B_WRITE,
minphys, uio));
}
}
/*
* Position_eom - position the tape to the end of recorded media.
* Returns 0 (success) or error code (failure).
*/
/*ARGSUSED*/
static
stposition_eom(dev, dsi, fileno)
register dev_t dev;
register struct scsi_tape *dsi;
register int fileno;
{
register int i, err;
register u_int unit;
unit = MTUNIT(dev);
#ifdef lint
unit = unit;
#endif lint
for (i = 0; i < fileno; i++) {
EPRINTF("stposition_eom: space 1 file\n");
if (err=stcmd(dev, SC_SPACE_FILE, 1)) {
if (dsi->un_status == SC_BLANK_CHECK)
break;
EPRINTF("stposition_eom: failed\n");
dsi->un_err_resid = fileno - i;
return (err); /* Failed */
}
/*
* If not QIC, check for the end of recorded data.
*/
if ((dsi->un_options & ST_QIC) == 0) {
EPRINTF("stposition_eom: space rec\n");
if (err=stcmd(dev, SC_SPACE_REC, 1)) {
EPRINTF1("stposition_eom: space rec status = 0x%x\n",
dsi->un_status);
dsi->un_status = SC_BLANK_CHECK;
break;
}
}
}
dsi->un_err_resid = fileno - i;
if (dsi->un_options & ST_REEL) {
EPRINTF("stposition_eom: backspace filemark\n");
if (err=stcmd(dev, SC_BSPACE_FILE, 1)) {
EPRINTF("stposition_eom: ... failed\n");
return (err); /* Failed */
} else {
/* Fix up position info. */
dsi->un_fileno++;
dsi->un_err_resid--;
}
}
EPRINTF("stposition_eom: positioned at eom\n");
dsi->un_next_block = 0;
dsi->un_eof = ST_NO_EOF;
return (0);
}
/*
* Position_file - position the tape to the specified absolute file number.
* Returns 0 (success) or error code (failure).
*/
static
stposition_file(dev, dsi, fileno)
register dev_t dev;
register struct scsi_tape *dsi;
register int fileno;
{
register int i, err;
register u_int unit;
unit = MTUNIT(dev);
/*
* If the file number is negative or zero, just rewind the tape
* and assume he wants file 0. Note, for Sysgen, we'll try this
* twice since it fails first time if you're within a file.
*/
if (fileno <= 0) {
EPRINTF("stposition_file: back spacing to BOT...\n");
if ((err=stcmd(dev, SC_REWIND, 0)) &&
err != EINTR &&
(err=stcmd(dev, SC_REWIND, 0)))
goto ST_FILE_ERROR;
dsi->un_err_resid = - fileno;
if (fileno != 0)
dsi->un_status = SC_BOT;
/*
* If the file number is less than our current file
* position, then we'll have to backspace some files.
* Note, for dumb QIC drives, we'll have to rewind
* and space forward since they can't backspace files.
*/
} else if (fileno < dsi->un_fileno) {
EPRINTF("stposition_file: back spacing...\n");
if (dsi->un_options & ST_BSF) {
i = dsi->un_fileno - fileno +1;
if ((err=stcmd(dev, SC_BSPACE_FILE, i)) ||
err == EINTR ||
(err=stcmd(dev, SC_SPACE_FILE, 1)))
goto ST_FILE_ERROR;
} else {
if ((err=stcmd(dev, SC_REWIND, 0)) ||
err == EINTR ||
(err=stcmd(dev, SC_SPACE_FILE, fileno)))
goto ST_FILE_ERROR_RETRY;
}
/*
* If the file number is greater than the current file
* position, then we'll have to forward space some files.
*/
} else if (fileno > dsi->un_fileno) {
EPRINTF("stposition_file: forward spacing...\n");
i = fileno - dsi->un_fileno;
if ((dsi->un_options & ST_QIC) &&
dsi->un_fileno == 0 && i > 1) {
/* Have to retry for Sysgens */
if (err=stcmd(dev, SC_SPACE_FILE, 1))
goto ST_FILE_ERROR_RETRY;
i = i -1;
}
if (err=stcmd(dev, SC_SPACE_FILE, i))
goto ST_FILE_ERROR_RETRY;
/*
* If the file number is the same as the current file
* position, then we'll just sit here.
*/
} else if ((fileno == dsi->un_fileno) && (dsi->un_next_block != 0)) {
EPRINTF("stposition_file: begining of file...\n");
if (dsi->un_options & ST_BSF) {
if ((err=stcmd(dev, SC_BSPACE_FILE, 1)) ||
err == EINTR ||
(err=stcmd(dev, SC_SPACE_FILE, 1)))
goto ST_FILE_ERROR;
} else {
if ((err=stcmd(dev, SC_REWIND, 0)) ||
err == EINTR ||
(err=stcmd(dev, SC_SPACE_FILE, fileno)))
goto ST_FILE_ERROR_RETRY;
}
}
dsi->un_next_block = 0;
return (0);
/*
* If the forward space fails, try again, but space 1 file at a
* time to catch location of failure. Note, for 1/2" reel
* tape, we'll have to backspace 1 filemark since the tape
* ends with 2 filemarks.
*
* Suppress printing extraneous error messages on failed FSFs if
* we are at EOM. This allows the user to use FSF to get to the
* EOM by doing mt fsf <bignum>. We will however, return a
* error on exit.
*/
ST_FILE_ERROR_RETRY:
/* Suppress original error, if we're successful */
if (err != EINTR && (err=stcmd(dev, SC_REWIND, 0) == 0)) {
EPRINTF("stposition_file: retrying space file...\n");
if ((err=stposition_eom(dev, dsi, fileno)) == 0) {
dsi->un_next_block = 0;
return (0);
}
}
ST_FILE_ERROR:
log(LOG_ERR, position_error, unit, "file");
return (err);
}
/*
* Position_tape - position the tape at the requested block from
* the begining of the current tape file. Returns 0 (success) or
* error code (failure).
* After positioning the tape, dsi->un_next_block reflects the
* new position.
* Note, this routine does not support writing; only reading.
* For writes, you should pad with null blocks and only fail on
* backward motion.
*/
/*ARGSUSED*/
static
stposition_block(dev, dsi, block)
register dev_t dev;
register struct scsi_tape *dsi;
register int block;
{
register int delta;
int fileno, err=0;
u_int unit;
unit = MTUNIT(dev);
fileno = dsi->un_fileno;
dsi->un_err_resid = 0;
/*
* If block < 0, position to block 0 and return error as there
* are no blocks < 0. If block = 0, position to block 0.
*/
if (block <= 0) {
EPRINTF("stposition_block: beginning of file...\n");
if (dsi->un_next_block)
err=stposition_file(dev, dsi, fileno);
dsi->un_err_resid = - block;
/* If block < 0, return error (eof or bot) */
if (err == 0 && block != 0) {
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
if (fileno)
dsi->un_status = SC_EOF;
else
dsi->un_status = SC_BOT;
return (EIO);
}
/* If block = 0, we're done */
return (err);
}
/*
* If record is behind us, space backward to it.
*/
delta = block - dsi->un_next_block;
if (delta < 0) {
EPRINTF1("stposition_block: space %d blocks\n",
delta);
if (dsi->un_options & ST_BSR) {
if (err=stcmd(dev, SC_SPACE_REC, delta))
goto ST_BLOCK_ERROR_RETRY;
} else if ((dsi->un_options & ST_NO_FSR) == 0) {
if ((err=stposition_file(dev, dsi, dsi->un_fileno)) ||
err == EINTR ||
(err=stcmd(dev, SC_SPACE_REC, block)))
goto ST_BLOCK_ERROR_RETRY;
} else {
/* Handle Sysgen here (no FSR) */
goto ST_BLOCK_ERROR_RETRY;
}
/*
* If record is ahead of us, space forward to it.
*/
} else if (delta > 0) {
EPRINTF1("stposition_block: space %d blocks\n",
delta);
if ((dsi->un_options & ST_NO_FSR) == 0) {
if (err=stcmd(dev, SC_SPACE_REC, delta))
goto ST_BLOCK_ERROR_RETRY;
} else {
/* Handle Sysgen here (no FSR) */
goto ST_BLOCK_ERROR_RETRY;
}
}
/* Handle delta = 0 case here */
/*DPRINTF("stposition_block: position ok\n");*/
return (err);
/*
* If the forward space record fails, try again, but space
* 1 record at a time to catch location of failure. Note,
*
* Suppress printing error messages on failed FSRs if we are
* at EOF. This allows the user to use FSF to get to the
* EOF by doing mt fsr <bignum>.
*/
ST_BLOCK_ERROR_RETRY:
EPRINTF("stposition_block: retrying space record...\n");
if (dsi->un_eof == ST_EOF_PENDING || dsi->un_eof == ST_EOF_LOG ||
dsi->un_eof == ST_EOF) {
EPRINTF("stposition_block: logging eof\n");
dsi->un_eof = ST_NO_EOF;
dsi->un_fileno++;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_lastior = 0;
dsi->un_offset = 0;
}
if (err=stposition_file(dev, dsi, fileno))
goto ST_BLOCK_ERROR;
if (block != 0) {
for (delta = 0; delta < block; delta++) {
DPRINTF("stposition_block: space 1 block\n");
if (err=stcmd(dev, SC_SPACE_REC, 1)) {
dsi->un_err_resid = block - delta;
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi) &&
(dsi->un_status == SC_EOF)) {
dsi->un_err_resid--;
dsi->un_err_blkno = dsi->un_next_block;
}
#endif ST_SYSGEN
if (dsi->un_status == SC_EOF) {
EPRINTF("stposition_block: eof\n");
break;
}
goto ST_BLOCK_ERROR;
}
}
return (0);
} else {
dsi->un_err_resid = block;
return (0);
}
ST_BLOCK_ERROR:
log(LOG_ERR, position_error, unit, "block");
return (err);
}
/*ARGSUSED*/
stioctl(dev, cmd, data, flag)
dev_t dev;
register u_int cmd;
register caddr_t data;
int flag;
{
register struct mtop *mtop;
register int callcount, fcount;
register struct mtget *mtget;
register u_int unit;
register struct scsi_tape *dsi;
register int err= 0;
u_char reten_rewind = 0;
static u_int ops[] = {
SC_WRITE_FILE_MARK, /* write tape mark */
SC_SPACE_FILE, /* forward space filemark */
SC_BSPACE_FILE, /* backspace filemark */
SC_SPACE_REC, /* forward space record */
SC_SPACE_REC, /* backspace record */
SC_REWIND, /* rewind tape */
SC_UNLOAD, /* unload */
SC_REQUEST_SENSE, /* get status */
SC_REWIND, /* retension */
SC_ERASE_TAPE, /* erase entire tape */
SC_SPACE_FILE, /* position to EOM */
SC_BSPACE_FILE, /* backspace filemark then forward space
* filemark */
};
unit = MTUNIT(dev);
if (unit >= nstape) {
EPRINTF1("st%d: stioctl: invalid unit\n", unit);
return (ENXIO);
}
dsi = &stape[unit];
switch (cmd) {
case MTIOCTOP: /* Tape operation */
mtop = (struct mtop *) data;
switch (mtop->mt_op) {
case MTNBSF:
dsi->un_status = 0;
fcount = dsi->un_fileno - mtop->mt_count;
if (dsi->un_eof == ST_EOF_PENDING ||
dsi->un_eof == ST_EOF_LOG ||
dsi->un_eof == ST_EOF) {
EPRINTF("stioctl: logging eof\n");
dsi->un_eof = ST_NO_EOF;
dsi->un_fileno++;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_lastior = 0;
dsi->un_offset = 0;
}
DPRINTF2("MTNBSF: file= %u, delta= %d\n",
dsi->un_fileno, fcount);
if (dsi->un_lastiow) {
(void) stwrite_eof(dev, dsi);
dsi->un_lastiow = 0;
}
dsi->un_options &= ~ST_NO_POSITION;
if (err=stposition_file(dev, dsi, fcount))
return (err);
return (0);
case MTFSF:
if (dsi->un_lastiow) {
EPRINTF("stioctl: can't fsf after writing\n");
dsi->un_err_resid = mtop->mt_count;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_ILLEGAL_REQUEST;
return (EIO);
}
dsi->un_status = 0;
fcount = dsi->un_fileno + mtop->mt_count;
if (dsi->un_eof == ST_EOF_PENDING ||
dsi->un_eof == ST_EOF_LOG ||
dsi->un_eof == ST_EOF) {
EPRINTF("stioctl: logging eof\n");
dsi->un_eof = ST_NO_EOF;
dsi->un_fileno++;
dsi->un_next_block = 0;
dsi->un_last_block = INF;
dsi->un_lastior = 0;
dsi->un_offset = 0;
}
DPRINTF2("MTFSF: file= %u, delta= %d\n",
dsi->un_fileno, fcount);
dsi->un_options &= ~ST_NO_POSITION;
if (err=stposition_file(dev, dsi, fcount))
return (err);
return (0);
case MTBSR:
dsi->un_status = 0;
fcount = dsi->un_next_block - mtop->mt_count;
if (err=stposition_block(dev, dsi, fcount))
return (err);
return (0);
case MTFSR:
if (dsi->un_lastiow) {
EPRINTF("stioctl: can't fsr after writing\n");
dsi->un_err_resid = mtop->mt_count;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_ILLEGAL_REQUEST;
return (EIO);
}
dsi->un_status = 0;
fcount = dsi->un_next_block + mtop->mt_count;
if (err=stposition_block(dev, dsi, fcount))
return (err);
return (0);
case MTRETEN:
/* Only for cartridge tape */
if ((dsi->un_options & ST_QIC) == 0) {
dsi->un_err_resid = mtop->mt_count;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_ILLEGAL_REQUEST;
return (ENOTTY);
}
reten_rewind = 1;
/* Fall through into MTREW... */
case MTREW:
dsi->un_status = 0;
dsi->un_err_resid = 0;
if (mtop->mt_count > 1)
dsi->un_err_resid = mtop->mt_count -1;
if (dsi->un_lastiow) {
(void) stwrite_eof(dev, dsi);
dsi->un_lastiow = 0;
}
#ifdef ST_SYSGEN
/*
* If Sysgen and in the middle of a file, rewind
* twice since the first will fail.
*/
if (IS_SYSGEN(dsi) && dsi->un_next_block != 0)
err = stcmd(dev, SC_REWIND, 0);
#endif ST_SYSGEN
if (err == EINTR || (err=stcmd(dev, SC_REWIND, 0)))
return (err);
dsi->un_options &= ~ST_NO_POSITION;
dsi->un_reten_rewind = reten_rewind;
err = stcmd(dev, SC_REWIND, 0);
return (err);
case MTWEOF:
if (dsi->un_read_only) {
log(LOG_ERR, wprotect_error,unit);
dsi->un_err_resid = mtop->mt_count;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_WRITE_PROTECT;
return (EACCES);
}
dsi->un_status = 0;
dsi->un_err_resid = 0;
#ifdef ST_SYSGEN
if (IS_SYSGEN(dsi)) {
callcount = 1;
fcount = mtop->mt_count;
} else {
callcount = mtop->mt_count;
fcount = 1;
}
#else ST_SYSGEN
callcount = 1;
fcount = mtop->mt_count;
#endif ST_SYSGEN
break;
case MTBSF:
/*
* This ioctl only works for those devices
* which can bacspace filemarks (e.g. 1/2" tape).
*/
if ((dsi->un_options & ST_BSF) == 0) {
dsi->un_err_resid = mtop->mt_count;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_ILLEGAL_REQUEST;
return (ENOTTY);
}
dsi->un_status = 0;
callcount = 1;
fcount = mtop->mt_count;
break;
case MTERASE:
DPRINTF("stioctl: erase\n");
if (dsi->un_read_only) {
log(LOG_ERR, wprotect_error,unit);
dsi->un_offset = 0;
dsi->un_err_resid = 0;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_WRITE_PROTECT;
return (EACCES);
}
dsi->un_status = 0;
dsi->un_err_resid = 0;
if (mtop->mt_count > 1)
dsi->un_err_resid = mtop->mt_count -1;
#ifdef ST_SYSGEN
/* If in the middle of a file, doit twice. */
if (IS_SYSGEN(dsi) && dsi->un_next_block != 0)
(void) stcmd(dev, SC_REWIND, 0);
#endif ST_SYSGEN
if ((err=stcmd(dev, SC_REWIND, 0)) == EINTR)
return (err);
callcount = 1;
fcount = 1;
break;
case MTOFFL:
DPRINTF("stioctl: offline\n");
dsi->un_status = 0;
dsi->un_err_resid = 0;
if (mtop->mt_count > 1)
dsi->un_err_resid = mtop->mt_count -1;
if (dsi->un_lastiow) {
(void) stwrite_eof(dev, dsi);
dsi->un_lastiow = 0;
}
dsi->un_read_only = 0; /* for broken QIC-150's */
dsi->un_options &= ~ST_NO_POSITION;
#ifdef ST_SYSGEN
/* If Sysgen, do it twice as first one will fail. */
if (IS_SYSGEN(dsi)) {
(void) stcmd(dev, SC_REWIND, 0);
(void) stcmd(dev, SC_REWIND, 0);
return (0);
}
#endif ST_SYSGEN
(void) stcmd(dev, SC_REWIND, 0);
(void) stcmd(dev, SC_UNLOAD, 0);
(void) stcmd(dev, SC_RELEASE, 0); /* Do last! */
return (0);
case MTEOM:
EPRINTF("stioctl: eom\n");
dsi->un_status = 0;
if (dsi->un_lastiow == 0)
err=stposition_eom(dev, dsi, INF);
dsi->un_options &= ~ST_NO_POSITION;
dsi->un_err_resid = 0; /* hide files not skipped */
if (err)
return (err);
dsi->un_status = 0; /* hide error code too */
return (0);
case MTNOP:
dsi->un_status = 0;
return (0);
default:
EPRINTF("stioctl: illegal command\n");
dsi->un_err_resid = mtop->mt_count;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_ILLEGAL_REQUEST;
return (ENOTTY);
}
if (callcount <= 0 || fcount <= 0) {
EPRINTF("stioctl: invalid parameters\n");
return (EINVAL);
}
while (--callcount >= 0) {
if ((err=stcmd(dev, ops[mtop->mt_op], fcount))) {
EPRINTF("stioctl: command failed\n");
return (err);
}
}
/* For HP, force rewind and reset soft error accounting. */
if (mtop->mt_op == MTERASE && (dsi->un_options & ST_REEL)) {
(void) stcmd(dev, SC_REWIND, 0);
if (dsi->un_options & ST_ERRLOG)
(void) stcmd(dev, SC_READ_LOG, 0);
}
return (0);
case MTIOCGET: /* Get tape status */
DPRINTF("stioctl: get tape status\n");
mtget = (struct mtget *) data;
mtget->mt_erreg = dsi->un_status;
mtget->mt_dsreg = dsi->un_retry_ct;
/* If no error, report current file position. */
if (dsi->un_status == 0) {
mtget->mt_resid = 0;
mtget->mt_fileno = dsi->un_fileno;
mtget->mt_blkno = dsi->un_next_block;
} else {
mtget->mt_resid = dsi->un_err_resid;
mtget->mt_fileno = dsi->un_err_fileno;
mtget->mt_blkno = dsi->un_err_blkno;
}
dsi->un_status = 0; /* Reset status */
mtget->mt_type = dsi->un_ctype;
mtget->mt_flags = MTF_SCSI | MTF_ASF;
if ((mtget->mt_type >= MT_ISCDC) &&
(mtget->mt_type <= MT_ISHP)) { /* 1/2" reels */
mtget->mt_flags |= MTF_REEL;
mtget->mt_bf = 20;
}
else { /* 1/4" cartridges */
switch (mtget->mt_type) {
/* older 1/4" cartridge tapes */
case MT_ISSYSGEN11:
case MT_ISSYSGEN:
case MT_ISAR:
mtget->mt_bf = 126;
break;
/* current 1/4" cartridge tapes */
default:
mtget->mt_bf = 40;
break;
}
}
#ifdef sun2
if (dsi->un_options & ST_NO_QIC24)
mtget->mt_type--;
#endif sun2
return (0);
default:
EPRINTF("stioctl: illegal command\n");
dsi->un_err_resid = 1;
dsi->un_err_fileno = dsi->un_fileno;
dsi->un_err_blkno = dsi->un_next_block;
dsi->un_status = SC_ILLEGAL_REQUEST;
return (ENOTTY);
}
}
#endif NST > 0