1421 lines
33 KiB
C
1421 lines
33 KiB
C
/*
|
|
* Written by Julian Elischer (julian@tfs.com)
|
|
* for TRW Financial Systems for use under the MACH(2.5) operating system.
|
|
* Hacked by Theo de Raadt <deraadt@fsa.ca>
|
|
*
|
|
* TRW Financial Systems, in accordance with their agreement with Carnegie
|
|
* Mellon University, makes this software available to CMU to distribute
|
|
* or use in any manner that they see fit as long as this message is kept with
|
|
* the software. For this reason TFS also grants any other persons or
|
|
* organisations permission to use or modify this software.
|
|
*
|
|
* TFS supplies this software to be publicly redistributed
|
|
* on the understanding that TFS is not responsible for the correct
|
|
* functioning of this software in any circumstances.
|
|
*
|
|
* $Id: st.c,v 1.17 1993/12/17 08:51:06 mycroft Exp $
|
|
*/
|
|
|
|
/*
|
|
* To do:
|
|
* work out some better way of guessing what a good timeout is going
|
|
* to be depending on whether we expect to retension or not.
|
|
*
|
|
*/
|
|
|
|
#include "st.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/mtio.h>
|
|
#include <sys/dkbad.h>
|
|
#include <sys/disklabel.h>
|
|
|
|
#include <scsi/scsi_all.h>
|
|
#include <scsi/scsi_tape.h>
|
|
#include <scsi/scsiconf.h>
|
|
#include <scsi/stdefs.h>
|
|
|
|
long int ststrats, stqueues;
|
|
|
|
#define ST_RETRIES 4
|
|
|
|
#define UNITSHIFT 4
|
|
#define MODE(z) ((minor(z) & 0x03))
|
|
#define DSTY(z) (((minor(z) >> 2) & 0x03))
|
|
#define UNIT(z) ((minor(z) >> UNITSHIFT))
|
|
|
|
#undef NST
|
|
#define NST ( makedev(1,0) >> UNITSHIFT)
|
|
|
|
#define DSTY_QIC120 3
|
|
#define DSTY_QIC150 2
|
|
#define DSTY_QIC525 1
|
|
|
|
#define QIC120 0x0f
|
|
#define QIC150 0x10
|
|
#define QIC525 0x11
|
|
|
|
#define ESUCCESS 0
|
|
|
|
int st_debug = 0;
|
|
|
|
struct st_data *st_data[NST];
|
|
static int next_st_unit = 0;
|
|
|
|
/*
|
|
* The routine called by the low level scsi routine when it discovers
|
|
* A device suitable for this driver
|
|
*/
|
|
int
|
|
stattach(int masunit, struct scsi_switch *sw, int physid, int *unit)
|
|
{
|
|
struct st_data *st;
|
|
unsigned char *tbl;
|
|
int targ, lun, i;
|
|
|
|
targ = physid >> 3;
|
|
lun = physid & 7;
|
|
|
|
/*printf("stattach: st%d at %s%d target %d lun %d\n",
|
|
*unit, sw->name, masunit, targ, lun);*/
|
|
|
|
if(*unit==-1) {
|
|
for(i=0; i<NST && *unit==-1; i++)
|
|
if(st_data[i]==NULL)
|
|
*unit = i;
|
|
}
|
|
if(*unit >= NST || *unit==-1)
|
|
return 0;
|
|
if(st_data[*unit])
|
|
return 0;
|
|
|
|
st = st_data[*unit] = (struct st_data *)malloc(sizeof *st,
|
|
M_TEMP, M_NOWAIT);
|
|
bzero(st, sizeof *st);
|
|
|
|
st->sc_sw = sw;
|
|
st->ctlr = masunit;
|
|
st->targ = targ;
|
|
st->lu = lun;
|
|
|
|
/*
|
|
* Use the subdriver to request information regarding
|
|
* the drive. We cannot use interrupts yet, so the
|
|
* request must specify this.
|
|
*/
|
|
if( st_mode_sense(*unit, SCSI_NOSLEEP | SCSI_NOMASK | SCSI_SILENT))
|
|
printf("st%d at %s%d targ %d lun %d: %d blocks of %d bytes\n",
|
|
*unit, sw->name, masunit, targ, lun,
|
|
st->numblks, st->blksiz);
|
|
else
|
|
printf("st%d at %s%d targ %d lun %d: offline\n",
|
|
*unit, sw->name, masunit, targ, lun);
|
|
|
|
/*
|
|
* Set up the bufs for this device
|
|
*/
|
|
st->buf_queue.b_active = 0;
|
|
st->buf_queue.b_actf = 0;
|
|
st->buf_queue.b_actl = 0;
|
|
st->initialized = 1;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* open the device.
|
|
*/
|
|
int
|
|
stopen(dev_t dev)
|
|
{
|
|
int errcode = 0;
|
|
int unit, mode, dsty;
|
|
int dsty_code;
|
|
struct st_data *st;
|
|
unit = UNIT(dev);
|
|
mode = MODE(dev);
|
|
dsty = DSTY(dev);
|
|
st = st_data[unit];
|
|
|
|
/*
|
|
* Check the unit is legal
|
|
*/
|
|
if( unit >= NST )
|
|
return ENXIO;
|
|
if(!st)
|
|
return ENXIO;
|
|
|
|
/*
|
|
* Only allow one at a time
|
|
*/
|
|
if(st->flags & ST_OPEN) {
|
|
errcode = EBUSY;
|
|
goto bad;
|
|
}
|
|
/*
|
|
* Set up the mode flags according to the minor number
|
|
* ensure all open flags are in a known state
|
|
*/
|
|
st->flags &= ~ST_PER_OPEN;
|
|
switch(mode) {
|
|
case 2:
|
|
case 0:
|
|
st->flags &= ~ST_NOREWIND;
|
|
break;
|
|
case 3:
|
|
case 1:
|
|
st->flags |= ST_NOREWIND;
|
|
break;
|
|
default:
|
|
printf("st%d: bad mode (minor number) %d\n", unit, mode);
|
|
return EINVAL;
|
|
}
|
|
/*
|
|
* Check density code: 0 is drive default
|
|
*/
|
|
switch(dsty) {
|
|
case 0:
|
|
dsty_code = 0;
|
|
break;
|
|
case DSTY_QIC120:
|
|
dsty_code = QIC120;
|
|
break;
|
|
case DSTY_QIC150:
|
|
dsty_code = QIC150;
|
|
break;
|
|
case DSTY_QIC525:
|
|
dsty_code = QIC525;
|
|
break;
|
|
default:
|
|
printf("st%d: bad density (minor number) %d\n", unit, dsty);
|
|
return EINVAL;
|
|
}
|
|
|
|
if(scsi_debug & (PRINTROUTINES | TRACEOPENS))
|
|
printf("st%d: open dev=0x%x (unit %d (of %d))\n", unit, dev, NST);
|
|
|
|
/*
|
|
* Make sure the device has been initialised
|
|
*/
|
|
if (!st->initialized) {
|
|
/*printf("st%d: uninitialized\n", unit);*/
|
|
return ENXIO;
|
|
}
|
|
|
|
/*
|
|
* Check that it is still responding and ok.
|
|
*/
|
|
if (!(st_req_sense(unit, 0))) {
|
|
errcode = EIO;
|
|
if(scsi_debug & TRACEOPENS)
|
|
printf("st%d: not responding\n", unit);
|
|
goto bad;
|
|
}
|
|
if(scsi_debug & TRACEOPENS)
|
|
printf("st%d: is responding\n", unit);
|
|
|
|
if(!(st_test_ready(unit, 0))) {
|
|
printf("st%d: not ready\n", unit);
|
|
return EIO;
|
|
}
|
|
|
|
if(!st->info_valid) /* is media new? */
|
|
if(!st_load(unit, LD_LOAD, 0))
|
|
return EIO;
|
|
|
|
if(!st_rd_blk_lim(unit, 0))
|
|
return EIO;
|
|
|
|
if(!st_mode_sense(unit, 0))
|
|
return EIO;
|
|
|
|
if(!st_mode_select(unit, 0, dsty_code))
|
|
return EIO;
|
|
|
|
st->info_valid = TRUE;
|
|
|
|
st_prevent(unit, PR_PREVENT, 0); /* who cares if it fails? */
|
|
|
|
/*
|
|
* Load the physical device parameters
|
|
*/
|
|
if(scsi_debug & TRACEOPENS)
|
|
printf("st%d: params ", unit);
|
|
st->flags |= ST_OPEN;
|
|
|
|
bad:
|
|
return errcode;
|
|
}
|
|
|
|
/*
|
|
* close the device.. only called if we are the LAST
|
|
* occurence of an open device
|
|
*/
|
|
int
|
|
stclose(dev_t dev)
|
|
{
|
|
unsigned char unit, mode;
|
|
struct st_data *st;
|
|
|
|
unit = UNIT(dev);
|
|
mode = MODE(dev);
|
|
st = st_data[unit];
|
|
|
|
if(scsi_debug & TRACEOPENS)
|
|
printf("st%d: close\n", unit);
|
|
if(st->flags & ST_WRITTEN)
|
|
st_write_filemarks(unit, 1, 0);
|
|
|
|
st->flags &= ~ST_WRITTEN;
|
|
switch(mode) {
|
|
case 0:
|
|
st_rewind(unit, FALSE, SCSI_SILENT);
|
|
st_prevent(unit, PR_ALLOW, SCSI_SILENT);
|
|
break;
|
|
case 1:
|
|
st_prevent(unit, PR_ALLOW, SCSI_SILENT);
|
|
break;
|
|
case 2:
|
|
st_rewind(unit, FALSE, SCSI_SILENT);
|
|
st_prevent(unit, PR_ALLOW, SCSI_SILENT);
|
|
st_load(unit, LD_UNLOAD, SCSI_SILENT);
|
|
break;
|
|
case 3:
|
|
st_prevent(unit, PR_ALLOW, SCSI_SILENT);
|
|
st_load(unit, LD_UNLOAD, SCSI_SILENT);
|
|
break;
|
|
default:
|
|
printf("st%d: bad mode (minor number) %d (how's it open?)\n",
|
|
unit, mode);
|
|
return EINVAL;
|
|
}
|
|
st->flags &= ~ST_PER_OPEN;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* trim the size of the transfer if needed,
|
|
* called by physio
|
|
* basically the smaller of our min and the scsi driver's*
|
|
* minphys
|
|
*/
|
|
void
|
|
stminphys(struct buf *bp)
|
|
{
|
|
(*(st_data[UNIT(bp->b_dev)]->sc_sw->scsi_minphys))(bp);
|
|
}
|
|
|
|
/*
|
|
* Actually translate the requested transfer into
|
|
* one the physical driver can understand
|
|
* The transfer is described by a buf and will include
|
|
* only one physical transfer.
|
|
*/
|
|
int
|
|
ststrategy(struct buf *bp)
|
|
{
|
|
struct st_data *st;
|
|
struct buf *dp;
|
|
unsigned char unit;
|
|
unsigned int opri;
|
|
|
|
if (bp->b_bcount == 0)
|
|
goto done;
|
|
|
|
ststrats++;
|
|
unit = UNIT((bp->b_dev));
|
|
st = st_data[unit];
|
|
if(scsi_debug & PRINTROUTINES)
|
|
printf("\nststrategy ");
|
|
if(scsi_debug & SHOWREQUESTS)
|
|
printf("st%d: %d bytes @ blk%d\n", unit, bp->b_bcount, bp->b_blkno);
|
|
|
|
/*
|
|
* Odd sized request on fixed drives are verboten
|
|
*/
|
|
if((st->flags & ST_FIXEDBLOCKS) && bp->b_bcount % st->blkmin) {
|
|
printf("st%d: bad request, must be multiple of %d\n",
|
|
unit, st->blkmin);
|
|
bp->b_error = EIO;
|
|
goto bad;
|
|
}
|
|
|
|
stminphys(bp);
|
|
opri = splbio();
|
|
dp = &st->buf_queue;
|
|
|
|
/*
|
|
* Place it in the queue of disk activities for this tape*
|
|
* at the end
|
|
*/
|
|
while ( dp->b_actf)
|
|
dp = dp->b_actf;
|
|
dp->b_actf = bp;
|
|
bp->b_actf = NULL;
|
|
|
|
/*
|
|
* Tell the device to get going on the transfer if it's
|
|
* not doing anything, otherwise just wait for completion*
|
|
*/
|
|
ststart(unit);
|
|
|
|
splx(opri);
|
|
return;
|
|
bad:
|
|
bp->b_flags |= B_ERROR;
|
|
done:
|
|
/*
|
|
* Correctly set the buf to indicate a completed xfer
|
|
*/
|
|
iodone(bp);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* ststart looks to see if there is a buf waiting for the device
|
|
* and that the device is not already busy. If both are true,
|
|
* It deques the buf and creates a scsi command to perform the
|
|
* transfer in the buf. The transfer request will call st_done
|
|
* on completion, which will in turn call this routine again
|
|
* so that the next queued transfer is performed.
|
|
* The bufs are queued by the strategy routine (ststrategy)
|
|
*
|
|
* This routine is also called after other non-queued requests
|
|
* have been made of the scsi driver, to ensure that the queue
|
|
* continues to be drained.
|
|
*/
|
|
/* ststart() is called at splbio */
|
|
int
|
|
ststart(int unit)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
register struct buf *bp = 0, *dp;
|
|
struct scsi_rw_tape cmd;
|
|
struct scsi_xfer *xs;
|
|
int drivecount, blkno, nblk;
|
|
|
|
if(scsi_debug & PRINTROUTINES)
|
|
printf("st%d: start\n", unit);
|
|
|
|
/*
|
|
* See if there is a buf to do and we are not already
|
|
* doing one
|
|
*/
|
|
xs = &st->scsi_xfer;
|
|
if(xs->flags & INUSE)
|
|
return; /* unit already underway */
|
|
|
|
trynext:
|
|
if(st->blockwait) {
|
|
wakeup((caddr_t)&st->blockwait);
|
|
return;
|
|
}
|
|
|
|
dp = &st->buf_queue;
|
|
if ((bp = dp->b_actf) != NULL)
|
|
dp->b_actf = bp->b_actf;
|
|
else
|
|
return;
|
|
xs->flags = INUSE; /* Now ours */
|
|
|
|
/*
|
|
* We have a buf, now we should move the data into
|
|
* a scsi_xfer definition and try start it
|
|
*/
|
|
|
|
/*
|
|
* If we are at a filemark but have not reported it yet
|
|
* then we should report it now
|
|
*/
|
|
if(st->flags & ST_AT_FILEMARK) {
|
|
bp->b_error = 0;
|
|
bp->b_flags |= B_ERROR; /* EOF*/
|
|
st->flags &= ~ST_AT_FILEMARK;
|
|
biodone(bp);
|
|
xs->flags = 0; /* won't need it now */
|
|
goto trynext;
|
|
}
|
|
|
|
/*
|
|
* If we are at EOM but have not reported it yet
|
|
* then we should report it now
|
|
*/
|
|
if(st->flags & ST_AT_EOM) {
|
|
bp->b_error = EIO;
|
|
bp->b_flags |= B_ERROR;
|
|
st->flags &= ~ST_AT_EOM;
|
|
biodone(bp);
|
|
xs->flags = 0; /* won't need it now */
|
|
goto trynext;
|
|
}
|
|
|
|
/*
|
|
* Fill out the scsi command
|
|
*/
|
|
bzero(&cmd, sizeof(cmd));
|
|
if((bp->b_flags & B_READ) == B_WRITE) {
|
|
st->flags |= ST_WRITTEN;
|
|
xs->flags |= SCSI_DATA_OUT;
|
|
}
|
|
else
|
|
xs->flags |= SCSI_DATA_IN;
|
|
cmd.op_code = (bp->b_flags & B_READ) ? READ_COMMAND_TAPE : WRITE_COMMAND_TAPE;
|
|
|
|
/*
|
|
* Handle "fixed-block-mode" tape drives by using the *
|
|
* block count instead of the length.
|
|
*/
|
|
if(st->flags & ST_FIXEDBLOCKS) {
|
|
cmd.fixed = 1;
|
|
lto3b(bp->b_bcount/st->blkmin, cmd.len);
|
|
}
|
|
else
|
|
lto3b(bp->b_bcount, cmd.len);
|
|
|
|
/*
|
|
* Fill out the scsi_xfer structure
|
|
* Note: we cannot sleep as we may be an interrupt
|
|
*/
|
|
xs->flags |= SCSI_NOSLEEP;
|
|
xs->adapter = st->ctlr;
|
|
xs->targ = st->targ;
|
|
xs->lu = st->lu;
|
|
xs->retries = 1; /* can't retry on tape*/
|
|
xs->timeout = 200000; /* allow 200 secs for retension */
|
|
xs->cmd = (struct scsi_generic *)&cmd;
|
|
xs->cmdlen = sizeof(cmd);
|
|
xs->data = (u_char *)bp->b_un.b_addr;
|
|
xs->datalen = bp->b_bcount;
|
|
xs->resid = bp->b_bcount;
|
|
xs->when_done = st_done;
|
|
xs->done_arg = unit;
|
|
xs->done_arg2 = (int)xs;
|
|
xs->error = XS_NOERROR;
|
|
xs->bp = bp;
|
|
|
|
#if defined(OSF) || defined(FIX_ME)
|
|
if (bp->b_flags & B_PHYS) {
|
|
xs->data = (u_char*)map_pva_kva(bp->b_proc, bp->b_un.b_addr,
|
|
bp->b_bcount, st_window[unit],
|
|
(bp->b_flags&B_READ)?B_WRITE:B_READ);
|
|
} else
|
|
xs->data = (u_char*)bp->b_un.b_addr;
|
|
#endif /* defined(OSF) */
|
|
|
|
if ( (*(st->sc_sw->scsi_cmd))(xs) != SUCCESSFULLY_QUEUED) {
|
|
printf("st%d: oops not queued", unit);
|
|
xs->error = XS_DRIVER_STUFFUP;
|
|
st_done(unit, xs);
|
|
}
|
|
stqueues++;
|
|
}
|
|
|
|
/*
|
|
* This routine is called by the scsi interrupt when
|
|
* the transfer is complete.
|
|
*/
|
|
int
|
|
st_done(int unit, struct scsi_xfer *xs)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct buf *bp;
|
|
int retval;
|
|
|
|
if(scsi_debug & PRINTROUTINES)
|
|
printf("st%d: done\n", unit);
|
|
if (! (xs->flags & INUSE))
|
|
panic("scsi_xfer not in use!");
|
|
|
|
if(bp = xs->bp) {
|
|
switch(xs->error) {
|
|
case XS_NOERROR:
|
|
bp->b_flags &= ~B_ERROR;
|
|
bp->b_error = 0;
|
|
bp->b_resid = 0;
|
|
break;
|
|
case XS_SENSE:
|
|
retval = st_interpret_sense(unit, xs);
|
|
if(retval) {
|
|
/*
|
|
* We have a real error, the bit should
|
|
* be set to indicate this. The return
|
|
* value will contain the unix error code*
|
|
* that the error interpretation routine
|
|
* thought was suitable, so pass this
|
|
* value back in the buf structure.
|
|
* Furthermore we return information
|
|
* saying that no data was transferred
|
|
*/
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = retval;
|
|
bp->b_resid = bp->b_bcount;
|
|
st->flags &= ~(ST_AT_FILEMARK|ST_AT_EOM);
|
|
} else if(xs->resid && ( xs->resid != xs->datalen )) {
|
|
/*
|
|
* Here we have the tricky part..
|
|
* We successfully read less data than
|
|
* we requested. (but not 0)
|
|
*------for variable blocksize tapes:----*
|
|
* UNDER 386BSD:
|
|
* We should legitimatly have the error
|
|
* bit set, with the error value set to
|
|
* zero.. This is to indicate to the
|
|
* physio code that while we didn't get
|
|
* as much information as was requested,
|
|
* we did reach the end of the record
|
|
* and so physio should not call us
|
|
* again for more data... we have it all
|
|
* SO SET THE ERROR BIT!
|
|
*
|
|
* UNDER MACH (CMU) and NetBSD:
|
|
* To indicate the same as above, we
|
|
* need only have a non 0 resid that is
|
|
* less than the b_bcount, but the
|
|
* ERROR BIT MUST BE CLEAR! (sigh)
|
|
*
|
|
* UNDER OSF1:
|
|
* To indicate the same as above, we
|
|
* need to have a non 0 resid that is
|
|
* less than the b_bcount, but the
|
|
* ERROR BIT MUST BE SET! (gasp)(sigh)
|
|
*
|
|
*-------for fixed blocksize device------*
|
|
* We could have read some successful
|
|
* records before hitting
|
|
* the EOF or EOT. These must be passed
|
|
* to the user, before we report the
|
|
* EOx. Only if there is no data for the
|
|
* user do we report it now. (via an EIO
|
|
* for EOM and resid == count for EOF).
|
|
* We will report the EOx NEXT time..
|
|
*/
|
|
bp->b_flags &= ~B_ERROR;
|
|
bp->b_error = 0;
|
|
bp->b_resid = xs->resid;
|
|
if((st->flags & ST_FIXEDBLOCKS)) {
|
|
bp->b_resid *= st->blkmin;
|
|
if( (st->flags & ST_AT_EOM)
|
|
&& (bp->b_resid == bp->b_bcount)) {
|
|
bp->b_error = EIO;
|
|
st->flags &= ~ST_AT_EOM;
|
|
}
|
|
}
|
|
xs->error = XS_NOERROR;
|
|
break;
|
|
} else {
|
|
/*
|
|
* We have come out of the error handler
|
|
* with no error code.. we have also
|
|
* not had an ili (would have gone to
|
|
* the previous clause). Now we need to
|
|
* distiguish between succesful read of
|
|
* no data (EOF or EOM) and successfull
|
|
* read of all requested data.
|
|
* At least all o/s agree that:
|
|
* 0 bytes read with no error is EOF
|
|
* 0 bytes read with an EIO is EOM
|
|
*/
|
|
bp->b_resid = bp->b_bcount;
|
|
if(st->flags & ST_AT_FILEMARK) {
|
|
st->flags &= ~ST_AT_FILEMARK;
|
|
bp->b_flags &= ~B_ERROR;
|
|
bp->b_error = 0;
|
|
break;
|
|
}
|
|
if(st->flags & ST_AT_EOM) {
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = EIO;
|
|
st->flags &= ~ST_AT_EOM;
|
|
break;
|
|
}
|
|
printf("st%d: error ignored\n", unit);
|
|
}
|
|
break;
|
|
case XS_TIMEOUT:
|
|
printf("st%d: timeout\n", unit);
|
|
break;
|
|
case XS_BUSY: /* should retry -- how? */
|
|
/*
|
|
* SHOULD put buf back at head of queue
|
|
* and decrement retry count in (*xs)
|
|
* HOWEVER, this should work as a kludge
|
|
*/
|
|
if(xs->retries--) {
|
|
xs->flags &= ~ITSDONE;
|
|
xs->error = XS_NOERROR;
|
|
if ( (*(st->sc_sw->scsi_cmd))(xs)
|
|
== SUCCESSFULLY_QUEUED) {
|
|
/* don't wake the job, ok? */
|
|
return;
|
|
}
|
|
printf("st%d: device busy\n");
|
|
xs->flags |= ITSDONE;
|
|
}
|
|
case XS_DRIVER_STUFFUP:
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = EIO;
|
|
break;
|
|
default:
|
|
printf("st%d: unknown error category %d from scsi driver\n",
|
|
unit, xs->error);
|
|
}
|
|
biodone(bp);
|
|
xs->flags = 0; /* no longer in use */
|
|
ststart(unit); /* If there's another waiting.. do it */
|
|
} else
|
|
wakeup((caddr_t)xs);
|
|
}
|
|
|
|
/*
|
|
* Perform special action on behalf of the user
|
|
* Knows about the internals of this device
|
|
*/
|
|
int
|
|
stioctl(dev_t dev, int cmd, caddr_t arg, int mode)
|
|
{
|
|
struct st_data *st;
|
|
struct mtop *mt;
|
|
struct mtget *g;
|
|
unsigned int opri;
|
|
unsigned char unit;
|
|
register i, j;
|
|
int errcode=0, number, flags, ret;
|
|
|
|
/*
|
|
* Find the device that the user is talking about
|
|
*/
|
|
flags = 0; /* give error messages, act on errors etc. */
|
|
unit = UNIT(dev);
|
|
st = st_data[unit];
|
|
|
|
if(unit >= NST)
|
|
return ENXIO;
|
|
if(!st)
|
|
return ENXIO;
|
|
|
|
switch(cmd) {
|
|
default:
|
|
return EINVAL;
|
|
case MTIOCGET:
|
|
g = (struct mtget *)arg;
|
|
bzero(g, sizeof *g);
|
|
g->mt_type = 0x7; /* Ultrix compat */
|
|
ret=TRUE;
|
|
break;
|
|
case MTIOCTOP:
|
|
mt = (struct mtop *)arg;
|
|
|
|
if (st_debug)
|
|
printf("[sctape_sstatus: %x %x]\n", mt->mt_op, mt->mt_count);
|
|
|
|
/* compat: in U*x it is a short */
|
|
number = mt->mt_count;
|
|
switch ((short)(mt->mt_op)) {
|
|
case MTWEOF: /* write an end-of-file record */
|
|
ret = st_write_filemarks(unit, number, flags);
|
|
st->flags &= ~ST_WRITTEN;
|
|
break;
|
|
case MTFSF: /* forward space file */
|
|
ret = st_space(unit, number, SP_FILEMARKS, flags);
|
|
break;
|
|
case MTBSF: /* backward space file */
|
|
ret = st_space(unit, -number, SP_FILEMARKS, flags);
|
|
break;
|
|
case MTFSR: /* forward space record */
|
|
ret = st_space(unit, number, SP_BLKS, flags);
|
|
break;
|
|
case MTBSR: /* backward space record */
|
|
ret = st_space(unit, -number, SP_BLKS, flags);
|
|
break;
|
|
case MTREW: /* rewind */
|
|
ret = st_rewind(unit, FALSE, flags);
|
|
break;
|
|
case MTOFFL: /* rewind and put the drive offline */
|
|
if((ret = st_rewind(unit, FALSE, flags))) {
|
|
st_prevent(unit, PR_ALLOW, 0);
|
|
ret = st_load(unit, LD_UNLOAD, flags);
|
|
} else
|
|
printf("st%d: rewind failed; unit still loaded\n");
|
|
break;
|
|
case MTNOP: /* no operation, sets status only */
|
|
case MTCACHE: /* enable controller cache */
|
|
case MTNOCACHE: /* disable controller cache */
|
|
ret = TRUE;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
break;
|
|
case MTIOCIEOT:
|
|
case MTIOCEEOT:
|
|
ret=TRUE;
|
|
break;
|
|
}
|
|
return ret ? ESUCCESS : EIO;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check with the device that it is ok, (via scsi driver)*
|
|
*/
|
|
int
|
|
st_req_sense(int unit, int flags)
|
|
{
|
|
struct scsi_sense_data sense;
|
|
struct scsi_sense scsi_cmd;
|
|
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = REQUEST_SENSE;
|
|
scsi_cmd.length = sizeof(sense);
|
|
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)&sense, sizeof(sense),
|
|
100000, flags | SCSI_DATA_IN) != 0)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Get scsi driver to send a "are you ready" command
|
|
*/
|
|
int
|
|
st_test_ready(int unit, int flags)
|
|
{
|
|
struct scsi_test_unit_ready scsi_cmd;
|
|
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = TEST_UNIT_READY;
|
|
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)0, 0, 100000, flags) != 0)
|
|
return FALSE;
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
#ifdef __STDC__
|
|
#define b2tol(a) (((unsigned)(a##_1) << 8) + (unsigned)a##_0 )
|
|
#else
|
|
#define b2tol(a) (((unsigned)(a/**/_1) << 8) + (unsigned)a/**/_0 )
|
|
#endif
|
|
|
|
/*
|
|
* Ask the drive what it's min and max blk sizes are.
|
|
*/
|
|
int
|
|
st_rd_blk_lim(int unit, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_blk_limits scsi_cmd;
|
|
struct scsi_blk_limits_data scsi_blkl;
|
|
|
|
st = st_data[unit];
|
|
|
|
/*
|
|
* First check if we have it all loaded
|
|
*/
|
|
if (st->info_valid)
|
|
goto done;
|
|
|
|
/*
|
|
* do a 'Read Block Limits'
|
|
*/
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = READ_BLK_LIMITS;
|
|
|
|
/*
|
|
* do the command, update the global values
|
|
*/
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)&scsi_blkl, sizeof(scsi_blkl),
|
|
5000, flags | SCSI_DATA_IN) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: read block limits failed\n", unit);
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
}
|
|
if (st_debug)
|
|
printf("st%d: block size min %d max %d\n", unit,
|
|
b2tol(scsi_blkl.min_length),
|
|
_3btol(&scsi_blkl.max_length_2));
|
|
|
|
st->blkmin = b2tol(scsi_blkl.min_length);
|
|
st->blkmax = _3btol(&scsi_blkl.max_length_2);
|
|
|
|
done:
|
|
if(st->blkmin && (st->blkmin == st->blkmax))
|
|
st->flags |= ST_FIXEDBLOCKS;
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Get the scsi driver to send a full inquiry to the
|
|
* device and use the results to fill out the global
|
|
* parameter structure.
|
|
*/
|
|
int
|
|
st_mode_sense(int unit, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_mode_sense scsi_cmd;
|
|
struct {
|
|
struct scsi_mode_header_tape header;
|
|
struct blk_desc blk_desc;
|
|
} scsi_s;
|
|
|
|
/*
|
|
* First check if we have it all loaded
|
|
*/
|
|
if(st->info_valid)
|
|
return TRUE;
|
|
/*
|
|
* First do a mode sense
|
|
*/
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = MODE_SENSE;
|
|
scsi_cmd.length = sizeof(scsi_s);
|
|
|
|
/*
|
|
* do the command, but we don't need the results
|
|
* just print them for our interest's sake
|
|
*/
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)&scsi_s, sizeof(scsi_s),
|
|
5000, flags | SCSI_DATA_IN) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: mode sense failed\n", unit);
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
}
|
|
if (st_debug)
|
|
printf("st%d: %d blocks of %d bytes, write %s, %sbuffered\n",
|
|
unit,
|
|
_3btol((u_char *)&scsi_s.blk_desc.nblocks),
|
|
_3btol((u_char *)&scsi_s.blk_desc.blklen),
|
|
scsi_s.header.write_protected ? "protected" : "enabled",
|
|
scsi_s.header.buf_mode ? "" : "un");
|
|
|
|
st->numblks = _3btol((u_char *)&scsi_s.blk_desc.nblocks);
|
|
st->blksiz = _3btol((u_char *)&scsi_s.blk_desc.blklen);
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Get the scsi driver to send a full inquiry to the
|
|
* device and use the results to fill out the global
|
|
* parameter structure.
|
|
*/
|
|
int
|
|
st_mode_select(int unit, int flags, int dsty_code)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_mode_select scsi_cmd;
|
|
struct {
|
|
struct scsi_mode_header_tape header;
|
|
struct blk_desc blk_desc;
|
|
} dat;
|
|
|
|
/*
|
|
* Set up for a mode select
|
|
*/
|
|
bzero(&dat, sizeof(dat));
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = MODE_SELECT;
|
|
scsi_cmd.length = sizeof(dat);
|
|
dat.header.blk_desc_len = sizeof(struct blk_desc);
|
|
dat.header.buf_mode = 1;
|
|
dat.blk_desc.density = dsty_code;
|
|
if(st->flags & ST_FIXEDBLOCKS)
|
|
lto3b(st->blkmin, dat.blk_desc.blklen);
|
|
|
|
/* lto3b( st->numblks, dat.blk_desc.nblocks); use defaults!!!!
|
|
lto3b( st->blksiz, dat.blk_desc.blklen);
|
|
*/
|
|
/*
|
|
* do the command
|
|
*/
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)&dat, sizeof(dat),
|
|
5000, flags | SCSI_DATA_OUT) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: mode select failed\n", unit);
|
|
#if 0
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* skip N blocks/filemarks/seq filemarks/eom
|
|
*/
|
|
int
|
|
st_space(int unit, int number, int what, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_space scsi_cmd;
|
|
|
|
/* if we are at a filemark now, we soon won't be*/
|
|
st->flags &= ~(ST_AT_FILEMARK | ST_AT_EOM);
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = SPACE;
|
|
scsi_cmd.code = what;
|
|
lto3b(number, scsi_cmd.number);
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)0, 0, 600000, flags) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: %s space failed\n", unit,
|
|
(number > 0) ? "forward" : "backward");
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* write N filemarks
|
|
*/
|
|
int
|
|
st_write_filemarks(int unit, int number, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_write_filemarks scsi_cmd;
|
|
|
|
st->flags &= ~(ST_AT_FILEMARK);
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = WRITE_FILEMARKS;
|
|
lto3b(number, scsi_cmd.number);
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)0, 0, 100000, flags) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: write file marks failed\n", unit);
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* load /unload (with retension if true)
|
|
*/
|
|
int
|
|
st_load(int unit, int type, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_load scsi_cmd;
|
|
|
|
st->flags &= ~(ST_AT_FILEMARK | ST_AT_EOM);
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = LOAD_UNLOAD;
|
|
scsi_cmd.load=type;
|
|
if (type == LD_LOAD)
|
|
{
|
|
/*scsi_cmd.reten=TRUE;*/
|
|
scsi_cmd.reten=FALSE;
|
|
}
|
|
else
|
|
{
|
|
scsi_cmd.reten=FALSE;
|
|
}
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)0, 0, 30000, flags) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: %s failed\n", unit,
|
|
type == LD_LOAD ? "load" : "unload");
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Prevent or allow the user to remove the tape
|
|
*/
|
|
int
|
|
st_prevent(int unit, int type, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_prevent scsi_cmd;
|
|
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = PREVENT_ALLOW;
|
|
scsi_cmd.prevent=type;
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)0, 0, 5000, flags) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: %s failed\n", unit,
|
|
type == PR_PREVENT ? "prevent" : "allow");
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Rewind the device
|
|
*/
|
|
int
|
|
st_rewind(int unit, int immed, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_rewind scsi_cmd;
|
|
|
|
st->flags &= ~(ST_AT_FILEMARK | ST_AT_EOM);
|
|
bzero(&scsi_cmd, sizeof(scsi_cmd));
|
|
scsi_cmd.op_code = REWIND;
|
|
scsi_cmd.immed=immed;
|
|
if (st_scsi_cmd(unit, (struct scsi_generic *)&scsi_cmd,
|
|
sizeof(scsi_cmd), (u_char *)0, 0, immed?5000:300000, flags) != 0) {
|
|
if(!(flags & SCSI_SILENT))
|
|
printf("st%d: rewind failed\n", unit);
|
|
st->info_valid = FALSE;
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* ask the scsi driver to perform a command for us.
|
|
* Call it through the switch table, and tell it which
|
|
* sub-unit we want, and what target and lu we wish to
|
|
* talk to. Also tell it where to find the command
|
|
* how long int is.
|
|
* Also tell it where to read/write the data, and how
|
|
* long the data is supposed to be
|
|
*/
|
|
int
|
|
st_scsi_cmd(int unit, struct scsi_generic *scsi_cmd, int cmdlen,
|
|
u_char *data_addr, int datalen, int timeout, int flags)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_xfer *xs;
|
|
int retval, s;
|
|
|
|
if(scsi_debug & PRINTROUTINES)
|
|
printf("\nst_scsi_cmd%d ", unit);
|
|
if(!st->sc_sw) {
|
|
printf("st%d: not set up\n", unit);
|
|
return EINVAL;
|
|
}
|
|
|
|
xs = &st->scsi_xfer;
|
|
if(!(flags & SCSI_NOMASK))
|
|
s = splbio();
|
|
st->blockwait++; /* there is someone waiting */
|
|
while (xs->flags & INUSE)
|
|
tsleep((caddr_t)&st->blockwait, PRIBIO+1, "st_cmd1", 0);
|
|
st->blockwait--;
|
|
xs->flags = INUSE;
|
|
if(!(flags & SCSI_NOMASK))
|
|
splx(s);
|
|
|
|
/*
|
|
* Fill out the scsi_xfer structure
|
|
*/
|
|
xs->flags |= flags;
|
|
xs->adapter = st->ctlr;
|
|
xs->targ = st->targ;
|
|
xs->lu = st->lu;
|
|
xs->retries = ST_RETRIES;
|
|
xs->timeout = timeout;
|
|
xs->cmd = scsi_cmd;
|
|
xs->cmdlen = cmdlen;
|
|
xs->data = data_addr;
|
|
xs->datalen = datalen;
|
|
xs->resid = datalen;
|
|
xs->when_done = (flags & SCSI_NOMASK) ? (int (*)())0 : st_done;
|
|
xs->done_arg = unit;
|
|
xs->done_arg2 = (int)xs;
|
|
retry:
|
|
xs->error = XS_NOERROR;
|
|
xs->bp = 0;
|
|
retval = (*(st->sc_sw->scsi_cmd))(xs);
|
|
switch(retval) {
|
|
case SUCCESSFULLY_QUEUED:
|
|
s = splbio();
|
|
while(!(xs->flags & ITSDONE))
|
|
tsleep((caddr_t)xs,PRIBIO+1, "st_cmd2", 0);
|
|
splx(s);
|
|
case HAD_ERROR:
|
|
case COMPLETE:
|
|
switch(xs->error) {
|
|
case XS_NOERROR:
|
|
retval = ESUCCESS;
|
|
break;
|
|
case XS_SENSE:
|
|
retval = st_interpret_sense(unit, xs);
|
|
/* only useful for reads */
|
|
if (retval)
|
|
st->flags &= ~(ST_AT_FILEMARK | ST_AT_EOM);
|
|
else {
|
|
xs->error = XS_NOERROR;
|
|
retval = ESUCCESS;
|
|
}
|
|
break;
|
|
case XS_DRIVER_STUFFUP:
|
|
retval = EIO;
|
|
break;
|
|
case XS_TIMEOUT:
|
|
case XS_BUSY:
|
|
if(xs->retries-- ) {
|
|
xs->flags &= ~ITSDONE;
|
|
goto retry;
|
|
}
|
|
retval = EIO;
|
|
break;
|
|
default:
|
|
retval = EIO;
|
|
printf("st%d: unknown error category %d from scsi driver\n",
|
|
unit, xs->error);
|
|
break;
|
|
}
|
|
break;
|
|
case TRY_AGAIN_LATER:
|
|
if(xs->retries--) {
|
|
xs->flags &= ~ITSDONE;
|
|
goto retry;
|
|
}
|
|
retval = EIO;
|
|
break;
|
|
default:
|
|
retval = EIO;
|
|
}
|
|
xs->flags = 0; /* it's free! */
|
|
ststart(unit);
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Look at the returned sense and act on the error and detirmine
|
|
* The unix error number to pass back... (0 = report no error)
|
|
*/
|
|
|
|
int
|
|
st_interpret_sense(int unit, struct scsi_xfer *xs)
|
|
{
|
|
struct st_data *st = st_data[unit];
|
|
struct scsi_sense_data *sense;
|
|
int silent = xs->flags & SCSI_SILENT, key;
|
|
|
|
/*
|
|
* If errors are ok, report a success
|
|
*/
|
|
if(xs->flags & SCSI_ERR_OK)
|
|
return ESUCCESS;
|
|
|
|
/*
|
|
* Get the sense fields and work out what CLASS
|
|
*/
|
|
sense = &(xs->sense);
|
|
if(st_debug) {
|
|
int count = 0;
|
|
printf("code%x class%x valid%x\n", sense->error_code,
|
|
sense->error_class, sense->valid);
|
|
printf("seg%x key%x ili%x eom%x fmark%x\n",
|
|
sense->ext.extended.segment, sense->ext.extended.sense_key,
|
|
sense->ext.extended.ili, sense->ext.extended.eom,
|
|
sense->ext.extended.filemark);
|
|
printf("info: %x %x %x %x followed by %d extra bytes\n",
|
|
sense->ext.extended.info[0], sense->ext.extended.info[1],
|
|
sense->ext.extended.info[2], sense->ext.extended.info[3],
|
|
sense->ext.extended.extra_len);
|
|
printf("extra: ");
|
|
while(count < sense->ext.extended.extra_len)
|
|
printf("%x ", sense->ext.extended.extra_bytes[count++]);
|
|
printf("\n");
|
|
}
|
|
|
|
switch(sense->error_class) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
if(!silent) {
|
|
printf("st%d: error class %d code %d\n", unit,
|
|
sense->error_class, sense->error_code);
|
|
if(sense->valid)
|
|
printf("block no. %d (decimal)\n",
|
|
(sense->ext.unextended.blockhi <<16),
|
|
+ (sense->ext.unextended.blockmed <<8),
|
|
+ (sense->ext.unextended.blocklow ));
|
|
}
|
|
return EIO;
|
|
case 7:
|
|
/*
|
|
* If it's class 7, use the extended stuff and interpret
|
|
* the key
|
|
*/
|
|
if(sense->ext.extended.eom)
|
|
st->flags |= ST_AT_EOM;
|
|
if(sense->ext.extended.filemark)
|
|
st->flags |= ST_AT_FILEMARK;
|
|
|
|
if(sense->ext.extended.ili) {
|
|
if(sense->valid) {
|
|
/*
|
|
* In all ili cases, note that
|
|
* the resid is non-0 AND not
|
|
* unchanged.
|
|
*/
|
|
xs->resid = ntohl(*((long *)sense->ext.extended.info));
|
|
if(xs->bp) {
|
|
if(xs->resid < 0) {
|
|
/* never on block devices */
|
|
/*
|
|
* it's only really bad
|
|
* if we have lost data
|
|
* (the record was
|
|
* bigger than the read)
|
|
*/
|
|
return EIO;
|
|
}
|
|
}
|
|
} else
|
|
printf("st%d: bad length error?", unit);
|
|
}
|
|
|
|
key = sense->ext.extended.sense_key;
|
|
switch(key) {
|
|
case 0x0:
|
|
return ESUCCESS;
|
|
case 0x1:
|
|
if(!silent) {
|
|
printf("st%d: soft error (corrected)", unit);
|
|
if(sense->valid) {
|
|
printf(" block %d\n",
|
|
(sense->ext.extended.info[0] <<24)|
|
|
(sense->ext.extended.info[1] <<16)|
|
|
(sense->ext.extended.info[2] <<8)|
|
|
(sense->ext.extended.info[3] ));
|
|
} else
|
|
printf("\n");
|
|
}
|
|
return ESUCCESS;
|
|
case 0x2:
|
|
if(!silent)
|
|
printf("st%d: not ready\n", unit);
|
|
return ENODEV;
|
|
case 0x3:
|
|
if(!silent) {
|
|
printf("st%d: medium error", unit);
|
|
if(sense->valid) {
|
|
printf(" block %d\n",
|
|
(sense->ext.extended.info[0] <<24)|
|
|
(sense->ext.extended.info[1] <<16)|
|
|
(sense->ext.extended.info[2] <<8)|
|
|
(sense->ext.extended.info[3] ));
|
|
} else
|
|
printf("\n");
|
|
}
|
|
return EIO;
|
|
case 0x4:
|
|
if(!silent)
|
|
printf("st%d: component failure\n",
|
|
unit);
|
|
return EIO;
|
|
case 0x5:
|
|
if(!silent)
|
|
printf("st%d: illegal request\n", unit);
|
|
return EINVAL;
|
|
case 0x6:
|
|
if(!silent)
|
|
printf("st%d: media change\n", unit);
|
|
st->flags &= ~(ST_AT_FILEMARK|ST_AT_EOM);
|
|
st->info_valid = FALSE;
|
|
if (st->flags & ST_OPEN) /* TEMP!!!! */
|
|
return EIO;
|
|
return ESUCCESS;
|
|
case 0x7:
|
|
if(!silent) {
|
|
printf("st%d: attempted protection violation",
|
|
unit);
|
|
if(sense->valid) {
|
|
printf(" block %d\n",
|
|
(sense->ext.extended.info[0] <<24)|
|
|
(sense->ext.extended.info[1] <<16)|
|
|
(sense->ext.extended.info[2] <<8)|
|
|
(sense->ext.extended.info[3] ));
|
|
} else
|
|
printf("\n");
|
|
}
|
|
return EACCES;
|
|
case 0x8:
|
|
if(!silent) {
|
|
printf("st%d: block wrong state (worm)", unit);
|
|
if(sense->valid) {
|
|
printf(" block %d\n",
|
|
(sense->ext.extended.info[0] <<24)|
|
|
(sense->ext.extended.info[1] <<16)|
|
|
(sense->ext.extended.info[2] <<8)|
|
|
(sense->ext.extended.info[3] ));
|
|
} else
|
|
printf("\n");
|
|
}
|
|
return EIO;
|
|
case 0x9:
|
|
if(!silent)
|
|
printf("st%d: vendor unique\n", unit);
|
|
return EIO;
|
|
case 0xa:
|
|
if(!silent)
|
|
printf("st%d: copy aborted\n", unit);
|
|
return EIO;
|
|
case 0xb:
|
|
if(!silent)
|
|
printf("st%d: command aborted\n", unit);
|
|
return EIO;
|
|
case 0xc:
|
|
if(!silent) {
|
|
printf("st%d: search returned", unit);
|
|
if(sense->valid) {
|
|
printf(" block %d\n",
|
|
(sense->ext.extended.info[0] <<24)|
|
|
(sense->ext.extended.info[1] <<16)|
|
|
(sense->ext.extended.info[2] <<8)|
|
|
(sense->ext.extended.info[3] ));
|
|
} else
|
|
printf("\n");
|
|
}
|
|
return ESUCCESS;
|
|
case 0xd:
|
|
if(!silent)
|
|
printf("st%d: volume overflow\n", unit);
|
|
return ENOSPC;
|
|
case 0xe:
|
|
if(!silent) {
|
|
printf("st%d: verify miscompare\n", unit);
|
|
if(sense->valid) {
|
|
printf("block no. %d (decimal)\n",
|
|
(sense->ext.extended.info[0] <<24)|
|
|
(sense->ext.extended.info[1] <<16)|
|
|
(sense->ext.extended.info[2] <<8)|
|
|
(sense->ext.extended.info[3] ));
|
|
} else
|
|
printf("\n");
|
|
}
|
|
return EIO;
|
|
case 0xf:
|
|
if(!silent)
|
|
printf("st%d: unknown error key\n", unit);
|
|
return EIO;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|