Coherent4.2.10/conf/haict/src/haict.c
/* (-lgl
* Coherent 386 release 4.2
* Copyright (c) 1982, 1994 by Mark Williams Company.
* All rights reserved. May not be copied without permission.
* For copying permission and licensing info, write licensing@mwc.com
-lgl) */
/***********************************************************************
* Module: haict.c
*
* Unix device driver functions for accessing SCSI tape drives as
* character devices. Conforms to Mark Williams Coherent definition
* of the Unix Device Driver interface for Coherent v4.2.
*
* The philosophy of this driver is to support basic functions on
* the tape drive (read, write, retension, rewind, skip, etc). There
* are more features out there for all the SCSI tape drives out there
* than I know what to do with. I leave custom support for these
* drives to the people who have them. To this end this drive will
* blindly follow whatever information it can get using Mode Sense
* and Read Block Limits CDB's. These tests are done at open time.
* An application can change the operation of the driver by applying
* the mode select command through I/O Control mechanism.
*
* Copyright (c) 1993, Christopher Sean Hilton, All Rights Reserved.
*
* Last Modified: Fri Jun 17 12:05:35 1994 by [kroot]
*
* $Id: haict.c,v 2.4 93/08/19 04:02:38 nigel Exp Locker: nigel $
*
* $Log: haict.c,v $
* Revision 2.4 93/08/19 04:02:38 nigel
* Nigel's R83
*/
#include <errno.h>
#include <stddef.h>
#include <sys/coherent.h>
#include <sys/cmn_err.h>
#include <sys/buf.h>
#include <sys/inode.h>
#include <sys/stat.h>
#include <sys/sched.h>
#include <sys/mtioctl.h>
#include <sys/tape.h>
#include <sys/file.h>
#include <sys/haiscsi.h>
#include <sys/haiioctl.h>
#define LOCAL static
#if __GNUC__
#define Register
#else
#define Register register
#endif
#define REWINDTAPE 0x01
#define IMMEDIATE 0x0010
#define REQSENSE 0x03
#define READBLKLMT 0x05
#define READ 0x08
#define WRITE 0x0a
#define WRITEFM 0x10
#define SPACE 0x11
#define SKIP_BLK 0x00
#define SKIP_FM 0x01
#define SKIP_SEQFM 0x02
#define SKIP_EOT 0x03
#define MODESELECT 0x15
#define ERASE 0x19
#define ERASE_BLOCK 0x0000
#define ERASE_TAPE 0x0001
#define MODESENSE 0x1a
#define LOAD 0x1b
#define RETENSION 0x0020
#define CTDIRTY 0x0001
#define CTCLOSING 0x0002
#define CTILI 0x0020 /* Sensekey's Illegal Length Indicator */
#define CTEOM 0x0040 /* Sensekey's End OF Media bit */
#define CTFILEMARK 0x0080 /* Sensekey's Filemark bit */
#define CTSKMASK (CTILI | CTEOM | CTFILEMARK)
#define CTRDMD 0x0100 /* we are reading from the tape */
#define CTWRTMD 0x0200 /* we are writing to the tape */
/*
* There wasn't much of a difference in speed between 32 and 40 block
* in my experiance so save as much kalloc memory as possible.
*/
#define STDCACHESZ (8 * BSIZE) /* 32 Block Cache for each device */
#define TDCCACHESZ (10 * BSIZE) /* Little bigger on the tandberg */
#ifndef HAICTVERBOSE
#define HAICTVERBOSE 0x0001 /* Switch console messages on/off */
#endif
typedef enum {
CTIDLE = 0,
CTINIT,
CTFBRD,
CTVBRD,
CTFBWRT,
CTVBWRT,
CTLASTWRT,
CTSENSE,
CTWRITEFM,
CTSPACE,
CTREWIND,
CTERASE,
CTLOADRETEN,
CTIOCTL
} ctstate_t;
/* Block Descriptors in the mode sense command. */
typedef struct blkdscr_s *blkdscr_p;
typedef struct blkdscr_s {
union {
unsigned char mediatype;
unsigned long totalblocks;
} mt;
union {
unsigned char reserved;
unsigned long blocksize;
} rb;
} blkdscr_t;
typedef struct blklim_s *blklim_p;
typedef struct blklim_s {
unsigned blmax; /* Maximum size for Reads/Writes */
unsigned short blmin; /* Minimum size for Reads/Writes */
} blklim_t;
typedef struct ctctrl_s *ctctrl_p;
typedef struct ctctrl_s {
char *cache, /* Transfer Cache */
*start; /* Start of data in cache */
haft_t * haft; /* Host adapter functions */
size_t cachesize, /* Size of cache */
avail; /* bytes availaible in cache */
ctstate_t state;
unsigned block, /* Block size of device */
blmax; /* Block limits maximum */
unsigned short blmin, /* Block Limits minimum */
flags, /* Flags from device */
inuse; /* In Use flag */
srb_t srb; /* SCSI Request block for transfers */
} ctctrl_t;
LOCAL int ctinit __PROTO((haft_t *, int));
LOCAL void ctopen __PROTO((dev_t, int));
LOCAL void ctclose __PROTO((dev_t));
LOCAL void ctread __PROTO((dev_t, IO *));
LOCAL void ctwrite __PROTO((dev_t, IO *));
LOCAL void ctioctl __PROTO((dev_t, int, char *));
LOCAL int fillcache __PROTO((ctctrl_p));
LOCAL int flushcache __PROTO((ctctrl_p));
#define min(a, b) ((a) < (b) ? (a) : (b))
static dca_t dca = {
ctopen, /* Open */
ctclose, /* Close */
hainonblk, /* No Block point here but don't just drop Buffers */
ctread, /* Read */
ctwrite, /* Write */
ctioctl, /* Ioctl */
ctinit, /* Load */
NULL, /* Unload */
NULL /* Poll */
};
dca_p ctdca = &dca;
static ctctrl_p ctdevs[MAXDEVS];
extern int haict_tdcbug;
extern size_t haict_cache;
/***********************************************************************
* Utility functions.
*/
#define ctvmsg(l, cmd) { if (HAICTVERBOSE & (l)) { (cmd); } }
#define ctsleepPri(ctrl, value) \
(((ctrl)->flags & CTCLOSING) ? slpriNoSig : (value))
#define tandberg(id) ((bit(id) & haict_tdcbug) != 0)
/***********************************************************************
* ctbusywait()
*
* Wait for the tape drive state to return to idle. This is easy
* for two reasons: 1) With no block entry point its safe to sleep
* at any time. 2) We shouldn't really need this anyhow. This is
* unneccessary because without a block routine and with only one
* process able to open the tape drive at a time the state of the
* tape drive driver is well defined. So, why is it here you ask?
* because one day some user might fork a process that owns the tape
* drive. This would cause 40 days and nights worth of rain etc.
* Now all that will happen is both processes will be able to write/read
* from the tape drive and the data that they get will be complete
* garbage. However, the kernel will not break.
*/
#if __USE_PROTO__
LOCAL int ctbusywait(Register ctctrl_p c, Register ctstate_t newstate)
#else
LOCAL int
ctbusywait(c, newstate)
Register ctctrl_p c;
Register ctstate_t newstate;
#endif
{
Register int s;
int retval;
s = sphi();
retval = 1;
while (c->state != CTIDLE)
if (x_sleep(& c->srb.status, pritape, slpriSigCatch, "ctbsywt")) {
set_user_error (EINTR);
retval = 0;
break;
}
c->state = newstate;
spl(s);
return retval;
} /* ctbusywait() */
/***********************************************************************
* loadtape()
*
* Move the tape to the load point.
*/
#if __USE_PROTO__
LOCAL int loadtape(ctctrl_p c, int opt)
#else
LOCAL int
loadtape(c, opt)
ctctrl_p c;
int opt;
#endif
{
Register srb_p r = & c->srb;
Register g0cmd_p g0 = & r->cdb.g0;
if (!ctbusywait(c, CTLOADRETEN))
return 0;
r->buf.space = PHYS_ADDR | WATCH_REQACK;
r->buf.ba_phys = NULL;
r->buf.size = 0;
r->xferdir = 0;
r->timeout = 300;
memset(g0, 0, sizeof(cdb_t));
g0->opcode = LOAD;
g0->xfr_len = 1; /* Move tape to load point.*/
if (opt & IMMEDIATE)
g0->lun_lba |= 1;
if (opt & RETENSION)
g0->xfr_len |= 2;
doscsi(c->haft, r, 4, pritape, slpriSigCatch, "loadtape");
if (r->status != ST_GOOD && printerror(r, "Load failed"))
set_user_error (EIO);
c->state = CTIDLE;
c->flags &= ~(CTFILEMARK | CTEOM);
return (r->status == ST_GOOD);
} /* loadtape() */
/***********************************************************************
* writefm()
*
* Write Filemarks on the tape.
*/
#if __USE_PROTO__
LOCAL void writefm(ctctrl_p c, int count)
#else
LOCAL void
writefm(c, count)
ctctrl_p c;
int count;
#endif
{
Register srb_p r = &(c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
if (!ctbusywait(c, CTWRITEFM))
return;
r->buf.space = PHYS_ADDR | WATCH_REQACK;
r->buf.ba_phys = NULL;
r->buf.size = 0;
r->xferdir = 0;
r->timeout = 40;
g0->opcode = WRITEFM;
g0->lun_lba = (r->lun << 5);
g0->lba_mid = ((unsigned char *) &count)[2];
g0->lba_low = ((unsigned char *) &count)[1];
g0->xfr_len = ((unsigned char *) &count)[0];
g0->control = 0;
doscsi(c->haft, r, 4, pritape, ctsleepPri(c, slpriSigCatch), "writefm");
if (r->status != ST_GOOD && printerror(r, "Write filemarks failed"))
set_user_error (EIO);
c->state = CTIDLE;
} /* writefm() */
/***********************************************************************
* space()
*
* Space over blocks/filemarks/etc.
*/
#if __USE_PROTO__
LOCAL void space(ctctrl_p c, int count, int object)
#else
LOCAL void
space(c, count, object)
ctctrl_p c;
int count;
int object;
#endif
{
Register srb_p r = &(c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
if (!ctbusywait(c, CTSPACE))
return;
r->buf.space = PHYS_ADDR | WATCH_REQACK;
r->buf.ba_phys = NULL;
r->buf.size = 0;
r->xferdir = 0;
r->timeout = 300;
g0->opcode = SPACE;
g0->lun_lba = (r->lun << 5) | (object & 3);
g0->lba_mid = ((unsigned char *) &count)[2];
g0->lba_low = ((unsigned char *) &count)[1];
g0->xfr_len = ((unsigned char *) &count)[0];
g0->control = 0;
doscsi(c->haft, r, 2, pritape, slpriSigCatch, "space");
if (r->status != ST_GOOD && printerror(r, "Space failed"))
set_user_error (EIO);
c->state = CTIDLE;
} /* space() */
/***********************************************************************
* rewind()
*
* Rewind the tape drive back to the load point.
*/
#if __USE_PROTO__
LOCAL void rewind(ctctrl_p c, int wait)
#else
LOCAL void
rewind(c, wait)
ctctrl_p c;
int wait;
#endif
{
Register srb_p r = &(c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
if (!ctbusywait(c, CTREWIND))
return;
r->buf.space = PHYS_ADDR | WATCH_REQACK;
r->buf.ba_phys = NULL;
r->buf.size = 0;
r->timeout = 300;
r->xferdir = 0;
memset(g0, 0, sizeof(cdb_t));
g0->opcode = REWINDTAPE;
if (!wait)
g0->lun_lba = (r->lun << 5) | 1;
doscsi(c->haft, r, 2, pritape, ctsleepPri(c, slpriSigCatch), "rewind");
if (r->status != ST_GOOD && printerror(r, "Rewind failed"))
set_user_error (EIO);
c->flags = 0;
c->state = CTIDLE;
} /* rewind() */
#if __USE_PROTO__
LOCAL void erase(ctctrl_p c, int to_eot)
#else
LOCAL void
erase(c, to_eot)
ctctrl_p c;
int to_eot;
#endif
{
Register srb_p r = &(c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
if (!ctbusywait(c, CTERASE))
return;
r->buf.space = PHYS_ADDR | WATCH_REQACK;
r->buf.ba_phys = NULL;
r->buf.size = 0;
r->timeout = 300;
r->xferdir = 0;
memset(g0, 0, sizeof(cdb_t));
g0->opcode = ERASE;
g0->lun_lba = (r->lun << 5);
if (to_eot)
g0->lun_lba |= 1;
doscsi(c->haft, r, 2, pritape, slpriSigCatch, "erase");
if (r->status != ST_GOOD && printerror(r, "Erase failed"))
set_user_error (EIO);
if (to_eot)
c->flags &= ~(CTFILEMARK | CTEOM | CTILI | CTDIRTY);
c->state = CTIDLE;
} /* erase() */
/***********************************************************************
* Device Driver Entry Point routines. *
***********************************************************************/
/***********************************************************************
* ctinit()
*
* Initialize the tape device at (id). This doesn't do anything,
* not even verify that the drive is there because it could be powered
* off.
*/
#if __USE_PROTO__
LOCAL int ctinit(haft_t * haft, Register int id)
#else
LOCAL int
ctinit(haft, id)
haft_t * haft;
Register int id;
#endif
{
Register ctctrl_p c = kalloc(sizeof(ctctrl_t));
if (!haft) {
cmn_err(CE_WARN, "haict: No host adapter function table.");
return 0;
}
if (!c) {
cmn_err(CE_WARN, "haict: Could not allocate control structure.");
return 0;
}
cmn_err(CE_CONT, "%d: HAI SCSI Tape Module v1.9\n", id);
memset(c, 0, sizeof(ctctrl_t));
c->haft = haft;
c->inuse = 0;
/*
* Now set up the cache. One of two types either a really big one
* in physical memory. Or a smaller one in kalloc memory allocated
* at open time. Assume the latter.
*/
c->cache = NULL;
if (haict_cache) { /* Configured for physical memory cache */
c->cachesize = haict_cache;
c->cache = (char *) getPhysMem(haict_cache);
}
if (!c->cache) {
if (haict_cache)
haict_cache = 0;
c->cachesize = (tandberg(id)) ? TDCCACHESZ : STDCACHESZ;
}
c->start = NULL;
c->srb.target = id;
c->srb.lun = 0;
c->state = CTIDLE;
ctdevs[id] = c;
return 1;
}
#if __USE_PROTO__
LOCAL void ctopen(dev_t dev, int mode)
#else
LOCAL void
ctopen(dev, mode)
dev_t dev;
int mode;
#endif
{
Register ctctrl_p c = ctdevs[tid(dev)];
Register srb_p r = &(c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
int rblerf; /* read block limits error flag */
int s;
char buf[64];
blkdscr_p bd = (blkdscr_p) (buf + 4);
blklim_p bl = (blklim_p) (buf);
if (!c) {
set_user_error (ENXIO);
return;
}
if ((mode != IPR) && (mode != IPW)) {
set_user_error (EINVAL);
return;
}
s = sphi();
if (c->inuse) {
set_user_error (EBUSY);
goto done;
}
c->inuse = 1;
c->state = CTINIT;
r->dev = dev; /* Save the rewind bit for close.*/
/***************************************************************
* Media_check: Make sure that there is a tape in the drive.
* The test unit ready command returns whether or not the
* tape drive is has a tape and is ready. We have to retry
* this command several times because a bus_device_resets
* or tape change is reported as a failed test_unit_ready
* followed by a successful one.
*/
r->buf.space = PHYS_ADDR | WATCH_REQACK;
r->buf.ba_phys = NULL;
r->buf.size = 0;
r->timeout = 2;
r->xferdir = 0;
memset(g0, 0, sizeof(cdb_t)); /* Test Unit Ready */
memset(r->sensebuf, 0, sizeof(r->sensebuf));
doscsi(c->haft, r, 4, pritape, slpriSigCatch, "ctopen");
/***************************************************************
* If the command fails there probably wasn't a tape in the
* drive.
*/
if (r->status != ST_GOOD) { /* Is there a tape in the drive? */
if (r->status != ST_USRABRT) {
/* Otherwise assume no tape.*/
set_user_error (ENXIO);
devmsg (r->dev, "Tape drive not ready.");
}
goto openfailed;
}
else {
/*******************************************************
* Do a load command on the tandberg tape drives when
* the drive is busy retensioning. This blocks the
* applications from sending too many commands to
* the drive on startup.
*/
if (tandberg(tid(dev)) && r->sensebuf[0] == 0x70 && r->sensebuf[2] == 0x06) {
r->buf.space = PHYS_ADDR | WATCH_REQACK;
r->buf.ba_phys = NULL;
r->buf.size = 0;
r->xferdir = 0;
r->timeout = 300;
memset(g0, 0, sizeof(cdb_t));
g0->opcode = LOAD;
g0->xfr_len = 1; /* Move tape to load point.*/
doscsi(c->haft, r, 4, pritape, slpriSigCatch, "ctopen");
if (r->status != ST_GOOD) {
if (printerror(r, "Load failed - TDC3600 not ready"))
set_user_error (ENXIO);
goto openfailed;
}
}
}
ctvmsg(0x0100, (devmsg(r->dev, "Read block limits.")));
/***************************************************************
* Tandberg's Read block limits command is broken.
*/
if (tandberg(tid(dev))) {
c->blmin = c->blmax = 512;
rblerf = 0;
}
else {
/*******************************************************
* Do a read block limits to find out what the drive
* is capable of. We need either Read block limits
* or Mode Sense to work here. If we cannot get either
* then we have problems.
*/
r->buf.space = KRNL_ADDR | WATCH_REQACK;
r->buf.addr.caddr = (caddr_t) bl;
r->buf.size = sizeof(blklim_t);
r->xferdir = DMAREAD;
r->timeout = 2;
memset(g0, 0, sizeof(cdb_t));
g0->opcode = READBLKLMT;
g0->xfr_len = 6;
doscsi(c->haft, r, 3, pritape, slpriSigCatch, "ctopen");
if (rblerf = (r->status != ST_GOOD)) {
ctvmsg(0x0010, (printerror(r, "Read Block LImits")));
c->blmax = c->blmin = 0;
}
else {
flip(bl->blmax); /* SCSI to INTEL order */
flip(bl->blmin); /* Ditto */
c->blmax = (bl->blmax & 0x00ffffff);
c->blmin = bl->blmin;
}
}
/***************************************************************
* Now for the mode sense. This should return at least one
* block descriptor which we can use to figure out the buffer
* size that the tape drive is using. Most of the streaming
* tape drives don't support variable mode operation. I don't
* know about the DATs
*/
r->buf.space = KRNL_ADDR | WATCH_REQACK;
r->buf.addr.caddr = (caddr_t) buf;
r->buf.size = sizeof(buf);
r->xferdir = DMAREAD;
r->timeout = 2;
memset(g0, 0, sizeof(cdb_t));
g0->opcode = MODESENSE;
g0->xfr_len = sizeof(buf);
doscsi(c->haft, r, 3, pritape, slpriSigCatch, "ctopen");
if (r->status != ST_GOOD) {
if (printerror(r, "Mode sense failed"))
set_user_error (EIO);
goto openfailed;
}
/***********************************************************************
* If tape drive opened in write mode make sure the tape is not write
* protected now.
*/
if (mode == IPW && (buf[2] & 0x80) != 0) {
devmsg(dev, "Tape is write protected");
set_user_error (ENXIO);
goto openfailed;
}
/***************************************************************
* According to SCSI-1 the first media descriptor is the default
* we will use this one. SCSI-2 is much clearer on this.
*/
if (buf[3]) { /* If mode sense returned any media descriptors */
bd->rb.blocksize &= 0xffffff00;
flip(bd->rb.blocksize);
c->block = bd->rb.blocksize;
if (c->block) {
if (haict_cache)
c->cachesize = haict_cache;
else
c->cachesize = (tandberg(tid(dev))) ? TDCCACHESZ : STDCACHESZ;
if (c->cachesize % c->block)
c->cachesize -= (c->cachesize % c->block);
}
}
else {
devmsg(r->dev, "No media descriptors: Contact Mark Williams Tech support");
set_user_error (ENXIO);
goto openfailed;
}
ctvmsg(0x0010, devmsg(dev, "Blocksize: %d bytes.", c->block));
/***********************************************************************
* One last check: If we aren't using block mode (!c->block)
* and we didn't get any block limits then we cannot support this
* drive.
*/
if (!c->block && rblerf) {
devmsg(r->dev, "<No block limits on variable mode tape drive>");
devmsg(r->dev, "<Contact Mark Williams Tech Support>");
set_user_error (ENXIO);
goto openfailed;
}
c->flags = (c->flags & (CTDIRTY | CTEOM)) |
((mode == IPR) ? CTRDMD : CTWRTMD);
if (c->block) {
if (!haict_cache) {
c->cache = kalloc(c->cachesize);
if (!c->cache) {
devmsg(dev, "Could not allocate tape cache");
set_user_error (ENOMEM);
goto openfailed;
}
}
c->avail = (c->flags & CTRDMD) ? 0 : c->cachesize;
c->start = c->cache;
}
c->state = CTIDLE;
goto done;
openfailed:
c->state = CTIDLE;
c->inuse = 0;
done:
spl(s);
} /* ctopen() */
/***********************************************************************
* ctclose()
*
* Close the SCSI Device at (dev).
*/
#if __USE_PROTO__
LOCAL void ctclose(Register dev_t dev)
#else
LOCAL void
ctclose(dev)
Register dev_t dev;
#endif
{
Register ctctrl_p c = ctdevs[tid(dev)];
Register srb_p r = &(c->srb);
int s;
if (!c) {
set_user_error (ENXIO);
return;
}
s = sphi();
c->flags |= CTCLOSING;
if (c->block && (c->flags & CTDIRTY)) {
if (ctbusywait(c, CTLASTWRT))
flushcache(c);
c->state = CTIDLE;
}
spl(s);
if (!haict_cache && c->cache) {
kfree(c->cache);
c->cache = c->start = NULL;
c->avail = 0;
}
if (c->flags & CTDIRTY)
writefm(c, 1);
if (r->dev & REWIND) {
if (c->flags & CTDIRTY)
writefm(c, 1);
rewind(c, 0);
}
c->inuse = 0;
return;
} /* ctclose() */
/***********************************************************************
* fillcache() -- Read from the tape into the cache (really?)
*
* return 0 and set u.u_error on any errors.
*/
#if __USE_LOCAL__
LOCAL int fillcache(Register ctctrl_p c)
#else
LOCAL int
fillcache(c)
Register ctctrl_p c;
#endif
{
Register srb_p r = (&c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
size_t blocks;
extsense_p e;
int info;
int retval = 0;
r->buf.space = KRNL_ADDR;
r->buf.addr.caddr = (caddr_t) c->cache;
r->buf.size = c->cachesize;
r->xferdir = DMAREAD;
r->timeout = 30;
r->tries = 0;
g0->opcode = READ;
g0->lun_lba = (r->lun << 5) | 1;
blocks = c->cachesize / c->block;
g0->lba_mid = ((unsigned char *) &blocks)[2];
g0->lba_low = ((unsigned char *) &blocks)[1];
g0->xfr_len = ((unsigned char *) &blocks)[0];
g0->control = 0;
doscsi(c->haft, r, 1, pritape, slpriSigCatch, "ctblkrd");
switch (r->status) {
case ST_GOOD:
c->start = c->cache;
c->avail = r->buf.size;
retval = 1;
break;
case ST_CHKCOND:
e = (extsense_p) r->sensebuf;
if ((e->errorcode & 0x70) == 0x70) {
info = 0;
if (e->errorcode & 0x80) {
info = e->info;
flip(info);
}
if (e->sensekey & (CTFILEMARK | CTEOM)) {
c->flags |= (e->sensekey & (CTFILEMARK | CTEOM));
c->start = c->cache;
c->avail = c->cachesize - (info * c->block);
retval = 1;
break;
}
}
printsense(r->dev, "Read failed", (extsense_p) r->sensebuf);
set_user_error (EIO);
retval = 0;
break;
case ST_USRABRT:
set_user_error (EINTR);
c->start = c->cache;
c->avail = 0;
retval = 0;
break;
default:
devmsg(r->dev, "Read failed: status (0x%x)", r->status);
set_user_error (EIO);
retval = 0;
break;
}
return retval;
} /* fillcache() */
/***********************************************************************
* ctfbrd() -- Fixed block read handler. Reads from the tape
* drive through the cache when the tape drive is
* in fixed block mode.
*/
#if __USE_PROTO__
LOCAL void ctfbrd(Register ctctrl_p c, Register IO *iop)
#else
LOCAL void
ctfbrd(c, iop)
Register ctctrl_p c;
Register IO *iop;
#endif
{
Register size_t reqcount, /* Total bytes transfered toward request */
xfrsize; /* Current transfer size */
size_t total, /* System global memory total transfer size */
size; /* System global memory current transfer size */
if (!ctbusywait(c, CTFBRD))
return;
reqcount = 0;
while (iop->io_ioc) {
xfrsize = min(c->avail, iop->io_ioc);
if (xfrsize > 0) {
switch (iop->io_seg) {
case IOSYS:
memcpy(iop->io.vbase + reqcount, c->start, xfrsize);
break;
case IOUSR:
kucopy(c->start, iop->io.vbase + reqcount, xfrsize);
break;
case IOPHY:
total = 0;
while (total < xfrsize) {
size = min(xfrsize - total, NBPC);
xpcopy(c->start + total,
iop->io.pbase + reqcount + total,
size, SEL_386_KD | SEG_VIRT);
total += size;
}
break;
}
c->start += xfrsize;
c->avail -= xfrsize;
reqcount += xfrsize;
iop->io_ioc -= xfrsize;
}
if (iop->io_ioc) {
if (c->flags & CTFILEMARK) {
c->flags &= ~CTFILEMARK;
break;
}
if (c->flags & CTEOM) {
set_user_error (EIO);
break;
}
if (!fillcache(c))
break;
}
} /* while */
c->state = CTIDLE;
} /* ctfbrd() */
/***********************************************************************
* ctvbrd() -- Variable block read entry point.
*/
#if __USE_PROTO__
LOCAL void ctvbrd(Register ctctrl_p c, IO *iop)
#else
LOCAL void
ctvbrd(c, iop)
Register ctctrl_p c;
IO *iop;
#endif
{
Register srb_p r = &(c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
size_t xfrsize;
extsense_p e;
int info;
if (!ctbusywait(c, CTVBRD))
return;
if (c->flags & CTEOM) {
set_user_error (EIO);
return;
}
if (c->flags & CTFILEMARK) {
c->flags &= ~CTFILEMARK;
return;
}
switch (iop->io_seg) {
case IOSYS:
r->buf.space = KRNL_ADDR;
r->buf.addr.caddr = iop->io.vbase;
break;
case IOUSR:
r->buf.space = USER_ADDR;
r->buf.addr.caddr = iop->io.vbase;
break;
case IOPHY:
r->buf.space = PHYS_ADDR;
r->buf.addr.paddr = iop->io.pbase;
break;
}
r->buf.size = xfrsize = iop->io_ioc;
r->xferdir = DMAREAD;
r->timeout = 30;
g0->opcode = READ;
g0->lun_lba = (r->lun << 5);
g0->lba_mid = ((unsigned char *) &xfrsize)[2];
g0->lba_low = ((unsigned char *) &xfrsize)[1];
g0->xfr_len = ((unsigned char *) &xfrsize)[0];
g0->control = 0;
doscsi(c->haft, r, 1, pritape, slpriSigCatch, "ctvbrd");
switch (r->status) {
case ST_GOOD:
iop->io_ioc -= r->buf.size;
break;
case ST_CHKCOND:
e = (extsense_p) r->sensebuf;
if ((e->errorcode & 0x70) == 0x70) {
info = 0;
if (e->errorcode & 0x80) {
info = (long) e->info;
flip(info);
}
if (e->sensekey & (CTFILEMARK | CTEOM)) {
c->flags |= (e->sensekey & (CTFILEMARK | CTEOM));
break;
}
else if (e->sensekey & CTILI) {
devmsg(r->dev,
"Read failed buffer size %d blocksize %d",
xfrsize,
xfrsize - info);
if (info > 0)
iop->io_ioc -= (xfrsize - info);
else
set_user_error (EIO);
break;
}
}
printsense(r->dev, "Read failed", (extsense_p) r->sensebuf);
set_user_error (EIO);
break;
case ST_USRABRT:
break;
default:
devmsg(r->dev, "Read failed: status (0x%x)", r->status);
set_user_error (EIO);
break;
}
c->state = CTIDLE;
} /* ctvbrd() */
/***********************************************************************
* ctread() -- OS Read entry point.
*/
#if __USE_PROTO__
LOCAL void ctread(dev_t dev, Register IO *iop)
#else
LOCAL void
ctread(dev, iop)
dev_t dev;
IO *iop;
#endif
{
Register ctctrl_p c = ctdevs[tid(dev)];
if (!c) {
set_user_error (EINVAL);
return;
}
if (c->block)
ctfbrd(c, iop);
else
ctvbrd(c, iop);
} /* ctread() */
/***********************************************************************
* flushcache() -- flush the data in the cache to the tape.
*
* returns 0 and sets u.u_error on failure else returns 1.
*/
#if __USE_LOCAL__
LOCAL int flushcache(Register ctctrl_p c)
#else
LOCAL int
flushcache(c)
Register ctctrl_p c;
#endif
{
Register srb_p r = (&c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
size_t xfrsize;
extsense_p e;
int info;
int retval = 0;
if (c->avail >= c->cachesize)
return 1;
r->buf.space = KRNL_ADDR;
r->buf.addr.caddr = (caddr_t) c->cache;
r->buf.size = xfrsize = c->cachesize - c->avail;
r->xferdir = DMAWRITE;
r->timeout = 30;
r->tries = 0;
g0->opcode = WRITE;
g0->lun_lba = (r->lun << 5);
if (c->block) {
g0->lun_lba |= 1;
xfrsize = (xfrsize + c->block - 1) / c->block;
}
g0->lba_mid = ((unsigned char *) &xfrsize)[2];
g0->lba_low = ((unsigned char *) &xfrsize)[1];
g0->xfr_len = ((unsigned char *) &xfrsize)[0];
g0->control = 0;
doscsi(c->haft, r, 1, pritape, ctsleepPri(c, slpriSigCatch), "ctblkwrt");
switch (r->status) {
case ST_GOOD:
c->start = c->cache;
c->avail = c->cachesize;
retval = 1;
break;
case ST_CHKCOND:
e = (extsense_p) r->sensebuf;
if ((e->errorcode & 0x70) == 0x70) {
info = 0;
if (e->errorcode & 0x80) {
info = e->info;
flip(info);
}
if (e->sensekey & CTEOM) {
c->flags |= CTEOM;
devmsg(r->dev, "End of tape on block write");
}
}
printsense(r->dev, "Read failed", (extsense_p) r->sensebuf);
set_user_error (EIO);
retval = 0;
break;
case ST_USRABRT:
retval = 0;
break;
default:
devmsg(r->dev, "Read failed: status (0x%x)", r->status);
set_user_error (EIO);
retval = 0;
break;
}
return retval;
} /* flushcache() */
/***********************************************************************
* ctfbwrt() -- Fixed block write. This should be fast because
* it uses the tapes drives optimum setting and it
* goes through a cache.
*/
#if __USE_PROTO__
LOCAL void ctfbwrt(Register ctctrl_p c, Register IO *iop)
#else
LOCAL void
ctfbwrt(c, iop)
Register ctctrl_p c;
Register IO *iop;
#endif
{
Register size_t reqcount, /* Total bytes transfered */
xfrsize; /* Current transfer size */
size_t total, /* System global memory total transfer size */
size; /* System global memory current transfer size */
if (!ctbusywait(c, CTFBWRT))
return;
reqcount = 0;
while (iop->io_ioc) {
xfrsize = min(c->avail, iop->io_ioc);
if (xfrsize) {
switch (iop->io_seg) {
case IOSYS:
memcpy(c->start, iop->io.vbase + reqcount, xfrsize);
break;
case IOUSR:
ukcopy(iop->io.vbase + reqcount, c->start, xfrsize);
break;
case IOPHY:
total = 0;
while (total < xfrsize) {
size = min(xfrsize - total, NBPC);
pxcopy(iop->io.pbase + reqcount + total,
c->start + total,
size, SEL_386_KD | SEG_VIRT);
total += size;
}
break;
}
c->start += xfrsize;
c->avail -= xfrsize;
reqcount += xfrsize;
iop->io_ioc -= xfrsize;
}
if (iop->io_ioc) {
if (!flushcache(c))
break;
}
} /* while */
c->state = CTIDLE;
} /* ctfbwrt() */
/***********************************************************************
* ctvbwrt() -- Variable block writes.
*/
#if __USE_PROTO__
LOCAL void ctvbwrt(Register ctctrl_p c, Register IO *iop)
#else
LOCAL void
ctvbwrt(c, iop)
Register ctctrl_p c;
Register IO *iop;
#endif
{
Register srb_p r = &(c->srb);
Register g0cmd_p g0 = &(r->cdb.g0);
size_t xfrsize;
extsense_p e;
int info;
if (!ctbusywait(c, CTVBWRT))
return;
if (c->blmax && iop->io_ioc > c->blmax) {
devmsg(r->dev, "Tape Error: maximum read/write size is %d bytes.", c->blmax);
set_user_error (EIO);
return;
}
switch (iop->io_seg) {
case IOSYS:
r->buf.space = KRNL_ADDR;
r->buf.addr.caddr = iop->io.vbase;
break;
case IOUSR:
r->buf.space = USER_ADDR;
r->buf.addr.caddr = iop->io.vbase;
break;
case IOPHY:
r->buf.space = PHYS_ADDR;
r->buf.addr.paddr = iop->io.pbase;
break;
}
xfrsize = min(iop->io_ioc, c->blmin);
r->buf.size = xfrsize;
r->xferdir = DMAWRITE;
r->timeout = 30;
g0->opcode = WRITE;
g0->lun_lba = (r->lun << 5);
g0->lba_mid = ((unsigned char *) &xfrsize)[2];
g0->lba_low = ((unsigned char *) &xfrsize)[1];
g0->xfr_len = ((unsigned char *) &xfrsize)[0];
g0->control = 0;
doscsi(c->haft, r, 1, pritape, slpriSigCatch, "ctvbwrt");
switch (r->status) {
case ST_GOOD:
iop->io_ioc -= r->buf.size;
break;
case ST_CHKCOND:
e = (extsense_p) r->sensebuf;
if ((e->errorcode & 0x70) == 0x70) {
info = 0;
if (e->errorcode & 0x80) {
info = (long) e->info;
flip(info);
}
if (e->sensekey & CTEOM) {
c->flags |= CTEOM;
devmsg(r->dev, "End of tape");
}
}
printsense(r->dev, "Write failed", (extsense_p) r->sensebuf);
set_user_error (EIO);
break;
case ST_USRABRT:
break;
default:
devmsg(r->dev, "Read failed: status (0x%x)", r->status);
set_user_error (EIO);
break;
}
c->state = CTIDLE;
} /* ctvbwrt() */
/***********************************************************************
* ctwrite() -- Write entry point for tape drive.
*/
#if __USE_PROTO__
LOCAL void ctwrite(Register dev_t dev, Register IO *iop)
#else
LOCAL void
ctwrite(dev, iop)
Register dev_t dev;
Register IO *iop;
#endif
{
Register ctctrl_p c = ctdevs[tid(dev)];
if (!c) {
set_user_error (ENXIO);
return;
}
c->flags |= CTDIRTY;
if (c->block)
ctfbwrt(c, iop);
else
ctvbwrt(c, iop);
} /* ctwrite() */
/***********************************************************************
* ctioctl()
*
* I/O Control Entry point for Cartridge tape devices.
*
* This function had been modified to allow applications level programs
* to select modes and features for the tape drive.As stated above,
* the philosophy of this driver is to provide least common denominator
* support for all tape drives. I know that you spend big bucks to
* get that (insert your favorite drive brand/model). If I decide
* to support everything out there on the market then I won't be able
* to write network drivers, serial drivers, etc. So if you need
* to do something to the tape drive to make it work (mode sense/select)
* you can do it through this ctioctl as an applications program.
*/
#if __USE_PROTO__
LOCAL void ctioctl(dev_t dev, Register int cmd, char *vec)
#else
LOCAL void
ctioctl(dev, cmd, vec)
dev_t dev;
Register int cmd;
char *vec;
#endif
{
Register ctctrl_p c = ctdevs[tid(dev)];
Register srb_p r = &(c->srb);
int s;
if (!c) {
set_user_error (EINVAL);
return;
}
switch (cmd) {
case T_RST:
resetdevice(c->haft, tid(dev));
break;
case MTREWIND: /* Rewind */
case T_RWD:
if (c->flags & CTDIRTY)
writefm(c, 1);
rewind(c, 1);
break;
case MTWEOF: /* Write end of file mark */
case T_WRFILEM:
writefm(c, 1);
break;
case MTRSKIP: /* Record skip */
space(c, 1, SKIP_BLK);
break;
case MTFSKIP: /* File skip */
case T_SFF:
space(c, 1, SKIP_FM);
break;
case MTTENSE: /* Tension tape */
case T_RETENSION:
if (c->flags & CTDIRTY)
writefm(c, 1);
loadtape(c, RETENSION);
break;
case MTERASE: /* Erase tape */
case T_ERASE:
erase(c, ERASE_TAPE);
break;
case MTDEC: /* DEC mode */
case MTIBM: /* IBM mode */
case MT800: /* 800 bpi */
case MT1600: /* 1600 bpi */
case MT6250: /* 6250 bpi */
return;
default:
if (!ctbusywait(c, CTIOCTL))
return;
s = sphi();
haiioctl(r, cmd, vec);
c->state = CTIDLE;
spl(s);
break;
}
} /* ctioctl() */