1152 lines
26 KiB
C
1152 lines
26 KiB
C
#ifndef lint
|
|
static char sccsid[] = "@(#)tm.c 1.1 94/10/31";
|
|
#endif
|
|
|
|
/*
|
|
* Copyright (c) 1987 by Sun Microsystems, Inc.
|
|
*/
|
|
|
|
/*
|
|
* Driver for Computer Products Corp. TapeMaster controller
|
|
* Controller names are tm?
|
|
* Device names are mt?
|
|
* This driver lifted from the VAX TM/TE driver
|
|
*/
|
|
#include "mt.h"
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/buf.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/user.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 <sundev/mbvar.h>
|
|
#include <sundev/tmreg.h>
|
|
|
|
#define SPLTM() spl3()
|
|
#define PRITM (PZERO+5)
|
|
|
|
/*
|
|
* There is a ctmbuf per tape drive.
|
|
* It is used as the token to pass to the internal routines
|
|
* to execute tape ioctls.
|
|
* When the tape is rewinding on close we release
|
|
* the user process but any further attempts to use the tape drive
|
|
* before the rewind completes will hang waiting for ctmbuf.
|
|
*/
|
|
struct buf ctmbuf[NMT];
|
|
|
|
/*
|
|
* Raw tape operations use rtmbuf. The driver
|
|
* notices when rtmbuf is being used and allows the user
|
|
* program to continue after errors and read records
|
|
* not of the standard length (BSIZE).
|
|
*/
|
|
struct buf rtmbuf[NMT];
|
|
|
|
/*
|
|
* Driver Multibus interface routines and variables.
|
|
*/
|
|
int tmprobe(), tmslave(), tmattach(), tmgo(), tmdone(), tmpoll();
|
|
|
|
struct tmscp *tmscp = NULL;
|
|
|
|
#define NOTZERO 1 /* don't care value-but not zero (clr inst botch) */
|
|
#define ATTN(c) (((struct tmdevice *)c)->tmdev_attn=NOTZERO)
|
|
#define RESET(c) (((struct tmdevice *)c)->tmdev_reset=NOTZERO)
|
|
#define clrtpb(tp) bzero((caddr_t)(tp), sizeof *(tp));
|
|
|
|
struct mb_ctlr *tmcinfo[NTM];
|
|
struct mb_device *tmdinfo[NMT];
|
|
struct mb_driver tmdriver = {
|
|
tmprobe, tmslave, tmattach, tmgo, tmdone, tmpoll,
|
|
sizeof (struct tmdevice), "mt", tmdinfo, "tm", tmcinfo, MDR_BIODMA,
|
|
};
|
|
struct buf mtutab[NMT];
|
|
short mttotm[NMT];
|
|
struct tm_mbinfo *tm_mb[NTM];
|
|
|
|
/* bits in minor device */
|
|
#define TMUNIT(dev) (mttotm[MTUNIT(dev)])
|
|
#define T_HIDENS MT_DENSITY2 /* select high density */
|
|
|
|
#define INF (daddr_t)1000000L
|
|
|
|
/*
|
|
* Max # of buffers outstanding per unit
|
|
*/
|
|
#define MAXMTBUF 3
|
|
|
|
/*
|
|
* Software state per tape transport.
|
|
*
|
|
* 1. A tape drive is a unique-open device; we refuse opens when it is already.
|
|
* 2. We keep track of the current position on a block tape and seek
|
|
* before operations by forward/back spacing if necessary.
|
|
* 3. We remember if the last operation was a write on a tape, so if a tape
|
|
* is open read write and the last thing done is a write we can
|
|
* write a standard end of tape mark (two eofs).
|
|
* 4. We remember the status registers after the last command, using
|
|
* then internally and returning them to the SENSE ioctl.
|
|
*/
|
|
struct mt_softc {
|
|
char sc_openf; /* lock against multiple opens */
|
|
char sc_lastiow; /* last op was a write */
|
|
char sc_stream; /* tape is a streamer */
|
|
char sc_bufcnt; /* queued system buffer count */
|
|
daddr_t sc_blkno; /* block number, for block device tape */
|
|
daddr_t sc_nxrec; /* position of end of tape, if known */
|
|
u_short sc_erreg; /* copy of last erreg */
|
|
struct tmstat sc_tms; /* last unit status */
|
|
long sc_resid; /* copy of last bc */
|
|
daddr_t sc_timo; /* time until timeout expires */
|
|
short sc_tact; /* timeout is active */
|
|
int sc_firstopen; /* set on attach, cleared on first open */
|
|
} mt_softc[NMT];
|
|
|
|
/*
|
|
* States for mc->mc_tab.b_active, the per controller state flag.
|
|
* This is used to sequence control in the driver.
|
|
*/
|
|
#define SSEEK 1 /* seeking */
|
|
#define SIO 2 /* doing seq i/o */
|
|
#define SCOM 3 /* sending control command */
|
|
#define SREW 4 /* sending a drive rewind */
|
|
#define SERR 5 /* doing erase gap (error on write) */
|
|
|
|
/*
|
|
* Determine if there is a controller for
|
|
* a tm at address reg.
|
|
* The SCP must be allocatable to initialize the controller.
|
|
*/
|
|
tmprobe(reg, ctlr)
|
|
register struct tmdevice *reg;
|
|
int ctlr;
|
|
{
|
|
register struct tmscp *scp = (struct tmscp *)&DVMA[TM_SCPADDR];
|
|
register struct tm_mbinfo *tmb;
|
|
register struct tpb *tpb;
|
|
static tm_swab = 0;
|
|
|
|
if (pokec((char *)®->tmdev_reset, NOTZERO))
|
|
return (0); /* Reset failed */
|
|
|
|
if (tmscp == NULL &&
|
|
rmget(iopbmap, (long)sizeof (struct tmscp), (u_long)scp) == 0) {
|
|
printf("tm%d: can't get iopb at %x to initialize controller\n",
|
|
ctlr, TM_SCPADDR);
|
|
return (0);
|
|
}
|
|
tmscp = scp;
|
|
|
|
tm_mb[ctlr] = (struct tm_mbinfo *)rmalloc(iopbmap,
|
|
(long)sizeof (struct tm_mbinfo));
|
|
if (tm_mb[ctlr] == 0) {
|
|
printf("tm%d: can't get iopb space\n", ctlr);
|
|
return (0);
|
|
}
|
|
bzero((caddr_t)tm_mb[ctlr], sizeof (struct tm_mbinfo));
|
|
if (!tminit(reg, ctlr))
|
|
return (0);
|
|
if (tm_swab == 1)
|
|
return (sizeof (struct tmdevice));
|
|
/* Enable byte-swapping if we have a TapeMaster A */
|
|
tmb = tm_mb[ctlr];
|
|
tpb = &tmb->tmb_tpb;
|
|
/* Issue OPTION command */
|
|
clrtpb(tpb);
|
|
tpb->tm_cmd = TM_OPTION;
|
|
tpb->tm_cmd2 = 0;
|
|
tpb->tm_ctl.tmc_width = 1;
|
|
tpb->tm_rcount = TMOP_DOSWAB;
|
|
/* Get the gate */
|
|
while (tmb->tmb_ccb.tmccb_gate != TMG_OPEN)
|
|
;
|
|
tmb->tmb_ccb.tmccb_gate = TMG_CLOSED;
|
|
/* Start the command and wait for completion */
|
|
ATTN(reg);
|
|
while (tmb->tmb_ccb.tmccb_gate == TMG_CLOSED)
|
|
;
|
|
if (tpb->tm_stat.tms_error || (tpb->tm_rcount & TMOP_ISSWAB) == 0) {
|
|
if (tm_swab == -1) { /* already saw TapeMaster A */
|
|
printf("tm%d: older TapeMaster must be first\n", ctlr);
|
|
return (0);
|
|
}
|
|
tm_swab = 1;
|
|
tmdriver.mdr_flags |= MDR_SWAB;
|
|
} else
|
|
tm_swab = -1;
|
|
return (sizeof (struct tmdevice));
|
|
}
|
|
|
|
/*
|
|
* Always say that a unit is there.
|
|
* We can't tell for sure anyway, and this lets
|
|
* a guy plug one in without taking down the system
|
|
* (These are micros, after all!)
|
|
*/
|
|
/*ARGSUSED*/
|
|
tmslave(md, reg)
|
|
struct mb_device *md;
|
|
caddr_t reg;
|
|
{
|
|
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* Record attachment of the unit to the controller.
|
|
*/
|
|
/*ARGSUSED*/
|
|
tmattach(md)
|
|
struct mb_device *md;
|
|
{
|
|
|
|
/*
|
|
* Mttotm is used in TMUNIT to index the controller
|
|
* arrays given a mt unit number.
|
|
*/
|
|
mttotm[md->md_unit] = md->md_mc->mc_ctlr;
|
|
mt_softc[md->md_unit].sc_stream = md->md_flags ? 1 : 0;
|
|
mt_softc[md->md_unit].sc_firstopen = 1;
|
|
}
|
|
|
|
#define SPININIT 1000000
|
|
/*
|
|
* Initialize a controller
|
|
* Reset it, set up SCP, SCB, and CCB,
|
|
* and give it an attention.
|
|
* Make sure its there by waiting for the gate to open
|
|
* Once initialization is done, issue CONFIG just to be safe.
|
|
*/
|
|
tminit(reg, ctlr)
|
|
register struct tmdevice *reg;
|
|
{
|
|
register struct tm_mbinfo *tmb = tm_mb[ctlr];
|
|
register struct tpb *tpb;
|
|
register int spin;
|
|
|
|
RESET(reg);
|
|
|
|
/* setup System Configuration Pointer */
|
|
tmscp->tmscb_bus = tmscp->tmscb_busx = TMSCB_BUS16;
|
|
c68t86((long)&tmb->tmb_scb, &tmscp->tmscb_ptr);
|
|
|
|
/* setup System Configuration Block */
|
|
tmb->tmb_scb.tmscb_03 = tmb->tmb_scb.tmscb_03x = TMSCB_CONS;
|
|
c68t86((long)&tmb->tmb_ccb, &tmb->tmb_scb.tmccb_ptr);
|
|
|
|
/* setup Channel Control Block */
|
|
tmb->tmb_ccb.tmccb_gate = TMG_CLOSED;
|
|
|
|
ATTN(reg);
|
|
for (spin = SPININIT; tmb->tmb_ccb.tmccb_gate != TMG_OPEN; )
|
|
if (--spin <= 0)
|
|
break;
|
|
if (spin <= 0) {
|
|
printf("tm%d: no response from ctlr\n", ctlr);
|
|
return (0);
|
|
}
|
|
|
|
/* Finish CCB, point it at TPB */
|
|
tmb->tmb_ccb.tmccb_ccw = TMC_NORMAL;
|
|
tpb = &tmb->tmb_tpb;
|
|
c68t86((long)tpb, &tmb->tmb_ccb.tmtpb_ptr);
|
|
|
|
/* Issue CONFIG command */
|
|
clrtpb(tpb);
|
|
tpb->tm_cmd = TM_CONFIG;
|
|
tpb->tm_cmd2 = 0;
|
|
tpb->tm_ctl.tmc_width = 1;
|
|
|
|
/* Get the gate */
|
|
while (tmb->tmb_ccb.tmccb_gate != TMG_OPEN)
|
|
;
|
|
tmb->tmb_ccb.tmccb_gate = TMG_CLOSED;
|
|
|
|
/* Start the command and wait for completion */
|
|
ATTN(reg);
|
|
while (tmb->tmb_ccb.tmccb_gate == TMG_CLOSED)
|
|
;
|
|
|
|
/* Check and report errors */
|
|
if (tpb->tm_stat.tms_error) {
|
|
printf("tm%d: error %d during config\n", ctlr,
|
|
tpb->tm_stat.tms_error);
|
|
tpb->tm_stat.tms_error = 0;
|
|
return (0);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
int tmtimer();
|
|
/*
|
|
* Open the device. Tapes are unique open
|
|
* devices, so we refuse if it is already open.
|
|
* We also check that a tape is available, and
|
|
* don't block waiting here; if you want to wait
|
|
* for a tape you should timeout in user code.
|
|
*/
|
|
tmopen(dev, flag)
|
|
dev_t dev;
|
|
int flag;
|
|
{
|
|
register int mtunit;
|
|
register struct mb_device *md;
|
|
register struct mt_softc *sc;
|
|
|
|
mtunit = MTUNIT(dev);
|
|
if ((sc = &mt_softc[mtunit])->sc_openf)
|
|
return (EBUSY);
|
|
if (mtunit>=NMT || (md = tmdinfo[mtunit]) == 0 || md->md_alive == 0)
|
|
return (ENXIO);
|
|
sc->sc_openf = 1;
|
|
|
|
if (tmcommand(dev, TM_STATUS, 1)) { /* interrupted */
|
|
sc->sc_openf = 0;
|
|
return (EIO);
|
|
}
|
|
if (!sc->sc_tms.tms_online || !sc->sc_tms.tms_ready) {
|
|
uprintf("mt%d: not online\n", mtunit);
|
|
sc->sc_openf = 0;
|
|
return (EIO);
|
|
}
|
|
|
|
if ((flag&FWRITE) && sc->sc_tms.tms_prot) {
|
|
uprintf("mt%d: no write ring\n", mtunit);
|
|
sc->sc_openf = 0;
|
|
return (EIO);
|
|
}
|
|
sc->sc_blkno = (daddr_t)0;
|
|
sc->sc_nxrec = INF;
|
|
sc->sc_lastiow = 0;
|
|
if (sc->sc_tact == 0) {
|
|
sc->sc_timo = INF;
|
|
sc->sc_tact = 1;
|
|
timeout(tmtimer, (caddr_t)dev, 5*hz);
|
|
}
|
|
|
|
/*
|
|
* Rewind only the 1st time we call this open.
|
|
*/
|
|
if ((sc->sc_tms.tms_load) || sc->sc_firstopen) {
|
|
/*
|
|
* First time through, ensure we're at BOT.
|
|
*/
|
|
if (sc->sc_firstopen) {
|
|
if (!(sc->sc_tms.tms_load) &&
|
|
(tmcommand(dev, TM_REWIND, 0))) {
|
|
uprintf("tm%d: could not rewind\n", mtunit);
|
|
sc->sc_openf = 0;
|
|
return (EIO);
|
|
}
|
|
sc->sc_firstopen = 0;
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Close tape device.
|
|
*
|
|
* If tape was open for writing or last operation was
|
|
* a write, then write two EOF's and backspace over the last one.
|
|
* Unless this is a non-rewinding special file, rewind the tape.
|
|
* Make the tape available to others.
|
|
*/
|
|
tmclose(dev, flag)
|
|
register dev_t dev;
|
|
register flag;
|
|
{
|
|
register struct mt_softc *sc = &mt_softc[MTUNIT(dev)];
|
|
|
|
if (flag == FWRITE || (flag&FWRITE) && sc->sc_lastiow) {
|
|
if (tmcommand(dev, TM_WEOF, 1) ||
|
|
tmcommand(dev, TM_WEOF, 1) ||
|
|
tmcommand(dev, TM_SPACE|TM_REVERSE, 1)) { /* interrupted */
|
|
sc->sc_openf = 0;
|
|
}
|
|
}
|
|
if ((minor(dev)&MT_NOREWIND) == 0)
|
|
/*
|
|
* 0 count means don't hang waiting for rewind complete
|
|
* rather ctmbuf stays busy until the operation completes
|
|
* preventing further opens from completing by
|
|
* preventing a TM_SENSE from completing.
|
|
*/
|
|
(void) tmcommand(dev, TM_REWIND, 0);
|
|
sc->sc_openf = 0;
|
|
}
|
|
|
|
/*
|
|
* Execute a command on the tape drive
|
|
* a specified number of times.
|
|
* Allow signals to occur (with tsleep) so the
|
|
* poor user can use his terminal if something goes wrong
|
|
*/
|
|
tmcommand(dev, com, count)
|
|
dev_t dev;
|
|
int com, count;
|
|
{
|
|
register struct buf *bp;
|
|
register int s;
|
|
int error;
|
|
|
|
bp = &ctmbuf[MTUNIT(dev)];
|
|
s = SPLTM();
|
|
while (bp->b_flags&B_BUSY) {
|
|
/*
|
|
* This special check is because B_BUSY never
|
|
* gets cleared in the non-waiting rewind case.
|
|
*/
|
|
if (bp->b_repcnt == 0 && (bp->b_flags&B_DONE))
|
|
break;
|
|
bp->b_flags |= B_WANTED;
|
|
#ifdef TSLEEP
|
|
/* XXX - tsleep can be replaced by new PCATCH option to sleep */
|
|
if (tsleep((caddr_t)bp, PRITM, 0) == TS_SIG)
|
|
return (1);
|
|
#else
|
|
(void) sleep((caddr_t)bp, PRIBIO);
|
|
#endif
|
|
}
|
|
bp->b_flags = B_BUSY|B_READ;
|
|
(void) splx(s);
|
|
|
|
bp->b_dev = dev;
|
|
bp->b_repcnt = count;
|
|
bp->b_command = com;
|
|
bp->b_blkno = 0;
|
|
tmstrategy(bp);
|
|
/*
|
|
* In case of rewind from close, don't wait.
|
|
* This is the only case where count can be 0.
|
|
*/
|
|
if (count == 0)
|
|
return (0);
|
|
|
|
s = SPLTM();
|
|
while ((bp->b_flags&B_DONE) == 0) {
|
|
bp->b_flags |= B_WANTED;
|
|
#ifdef TSLEEP
|
|
/* XXX - tsleep can be replaced by new PCATCH option to sleep */
|
|
if (tsleep((caddr_t)bp, PRITM, 0) == TS_SIG)
|
|
return (1);
|
|
#else
|
|
(void) sleep((caddr_t)bp, PRIBIO);
|
|
#endif
|
|
}
|
|
(void) splx(s);
|
|
|
|
error = geterror(bp);
|
|
if (bp->b_flags&B_WANTED)
|
|
wakeup((caddr_t)bp);
|
|
bp->b_flags &= B_ERROR; /* note: clears B_BUSY */
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Queue a tape operation.
|
|
*/
|
|
tmstrategy(bp)
|
|
register struct buf *bp;
|
|
{
|
|
int mtunit = MTUNIT(bp->b_dev);
|
|
register struct mb_ctlr *mc;
|
|
register struct buf *dp;
|
|
register struct mt_softc *sc = &mt_softc[mtunit];
|
|
int s;
|
|
|
|
/*
|
|
* Put transfer at end of unit queue
|
|
*/
|
|
dp = &mtutab[mtunit];
|
|
bp->av_forw = NULL;
|
|
s = SPLTM();
|
|
while (sc->sc_bufcnt >= MAXMTBUF)
|
|
(void) sleep((caddr_t)&sc->sc_bufcnt, PRIBIO);
|
|
sc->sc_bufcnt++;
|
|
mc = tmdinfo[mtunit]->md_mc;
|
|
if (dp->b_actf == NULL) {
|
|
dp->b_actf = bp;
|
|
/*
|
|
* Transport not already active...
|
|
* put at end of controller queue.
|
|
*/
|
|
dp->b_forw = NULL;
|
|
if (mc->mc_tab.b_actf == NULL)
|
|
mc->mc_tab.b_actf = dp;
|
|
else
|
|
mc->mc_tab.b_actl->b_forw = dp;
|
|
mc->mc_tab.b_actl = dp;
|
|
} else
|
|
dp->b_actl->av_forw = bp;
|
|
dp->b_actl = bp;
|
|
/*
|
|
* If the controller is not busy, get
|
|
* it going.
|
|
*/
|
|
if (mc->mc_tab.b_active == 0)
|
|
tmstart(mc);
|
|
(void) splx(s);
|
|
}
|
|
|
|
/*
|
|
* Start activity on a tm controller.
|
|
*/
|
|
tmstart(mc)
|
|
register struct mb_ctlr *mc;
|
|
{
|
|
register struct buf *bp, *dp;
|
|
register struct mt_softc *sc;
|
|
register struct tpb *tpb;
|
|
int mtunit, cmd, count, size, fast;
|
|
daddr_t blkno;
|
|
|
|
/*
|
|
* Look for an idle transport on the controller.
|
|
*/
|
|
loop:
|
|
if ((dp = mc->mc_tab.b_actf) == NULL)
|
|
return;
|
|
if ((bp = dp->b_actf) == NULL) {
|
|
mc->mc_tab.b_actf = dp->b_forw;
|
|
goto loop;
|
|
}
|
|
mtunit = MTUNIT(bp->b_dev);
|
|
sc = &mt_softc[mtunit];
|
|
/*
|
|
* Default is that last command was NOT a write command;
|
|
* if we do a write command we will notice this in tmintr().
|
|
*/
|
|
sc->sc_lastiow = 0;
|
|
|
|
if ((int)sc->sc_openf < 0) {
|
|
/*
|
|
* Have had a hard error on a non-raw tape
|
|
* or the tape unit is now unavailable
|
|
* (e.g. taken off line).
|
|
*/
|
|
bp->b_flags |= B_ERROR;
|
|
goto next;
|
|
}
|
|
|
|
fast = 0;
|
|
count = 1;
|
|
if (bp == &ctmbuf[mtunit]) {
|
|
/*
|
|
* Execute control operation with the specified count.
|
|
*/
|
|
/*
|
|
* Set next state; give 5 minutes to complete
|
|
* rewind, or 10 seconds per iteration (minimum 60
|
|
* seconds and max 5 minutes) to complete other ops.
|
|
*/
|
|
switch (bp->b_command & ~TM_DIRBIT) {
|
|
case TM_REWIND:
|
|
case TM_UNLOAD:
|
|
mc->mc_tab.b_active = SREW;
|
|
sc->sc_timo = 5 * 60;
|
|
fast = 1;
|
|
break;
|
|
|
|
case TM_SEARCH:
|
|
case TM_REWINDX:
|
|
mc->mc_tab.b_active = SCOM;
|
|
sc->sc_timo = 20 * 60;
|
|
fast = 1;
|
|
break;
|
|
|
|
default:
|
|
mc->mc_tab.b_active = SCOM;
|
|
sc->sc_timo =
|
|
imin(imax(10*(int)bp->b_repcnt, 60), 5*60);
|
|
break;
|
|
}
|
|
count = bp->b_repcnt;
|
|
cmd = bp->b_command;
|
|
size = 0;
|
|
goto dobpcmd;
|
|
}
|
|
/*
|
|
* The following checks handle boundary cases for operation
|
|
* on non-raw tapes. On raw tapes the initialization of
|
|
* sc->sc_nxrec by tmphys causes them to be skipped normally
|
|
* (except in the case of retries).
|
|
*/
|
|
if (bdbtofsb(bp->b_blkno) > sc->sc_nxrec) {
|
|
/*
|
|
* Can't read past known end-of-file.
|
|
*/
|
|
bp->b_flags |= B_ERROR;
|
|
bp->b_error = ENXIO;
|
|
goto next;
|
|
}
|
|
if (bdbtofsb(bp->b_blkno) == sc->sc_nxrec &&
|
|
bp->b_flags&B_READ) {
|
|
/*
|
|
* Reading at end of file returns 0 bytes.
|
|
*/
|
|
clrbuf(bp);
|
|
bp->b_resid = bp->b_bcount;
|
|
goto next;
|
|
}
|
|
if ((bp->b_flags&B_READ) == 0) {
|
|
/*
|
|
* Writing sets EOF
|
|
*/
|
|
sc->sc_nxrec = bdbtofsb(bp->b_blkno) + 1;
|
|
}
|
|
/*
|
|
* If the data transfer command is in the correct place,
|
|
* fire the operation up.
|
|
*/
|
|
if ((blkno = sc->sc_blkno) == bdbtofsb(bp->b_blkno)) {
|
|
size = bp->b_bcount;
|
|
if ((bp->b_flags&B_READ) == 0) {
|
|
if (mc->mc_tab.b_active!=SERR && mc->mc_tab.b_errcnt) {
|
|
cmd = TM_ERASE;
|
|
mc->mc_tab.b_active = SERR;
|
|
size = 0;
|
|
count = 1;
|
|
} else {
|
|
cmd = TM_WRITE;
|
|
mc->mc_tab.b_active = SIO;
|
|
}
|
|
} else {
|
|
cmd = TM_READ;
|
|
mc->mc_tab.b_active = SIO;
|
|
}
|
|
sc->sc_timo = 60; /* premature, but should serve */
|
|
} else {
|
|
/*
|
|
* Tape positioned incorrectly;
|
|
* set to seek forwards or backwards to the correct spot.
|
|
* This happens for raw tapes only on error retries.
|
|
*/
|
|
mc->mc_tab.b_active = SSEEK;
|
|
if (blkno < bdbtofsb(bp->b_blkno)) {
|
|
cmd = TM_SPACE|TM_FORWARD;
|
|
count = bdbtofsb(bp->b_blkno) - blkno;
|
|
} else {
|
|
cmd = TM_SPACE|TM_REVERSE;
|
|
count = blkno - bdbtofsb(bp->b_blkno);
|
|
}
|
|
sc->sc_timo = imin(imax(10 * count, 60), 5 * 60);
|
|
fast = count > 1;
|
|
size = 0;
|
|
}
|
|
dobpcmd:
|
|
/*
|
|
* Do the command in bp.
|
|
*/
|
|
tpb = &tm_mb[mc->mc_ctlr]->tmb_tpb;
|
|
clrtpb(tpb);
|
|
tpb->tm_cmd = cmd &~ TM_DIRBIT;
|
|
tpb->tm_ctl.tmc_rev = cmd & TM_DIRBIT;
|
|
tpb->tm_ctl.tmc_width = 1;
|
|
if (sc->sc_stream) {
|
|
tpb->tm_ctl.tmc_speed = fast;
|
|
} else {
|
|
tpb->tm_ctl.tmc_speed = (minor(bp->b_dev) & T_HIDENS) ? 0 : 1;
|
|
}
|
|
tpb->tm_ctl.tmc_intr = 1;
|
|
tpb->tm_ctl.tmc_tape = tmdinfo[mtunit]->md_slave & 03;
|
|
tpb->tm_rcount = count;
|
|
tpb->tm_bsize = size;
|
|
if (size == 0)
|
|
tmgo(mc);
|
|
else
|
|
(void) mbgo(mc);
|
|
return;
|
|
|
|
next:
|
|
/*
|
|
* Done with this operation due to error or
|
|
* the fact that it doesn't do anything.
|
|
*/
|
|
mc->mc_tab.b_errcnt = 0;
|
|
dp->b_actf = bp->av_forw;
|
|
iodone(bp);
|
|
if (sc->sc_bufcnt-- >= MAXMTBUF)
|
|
wakeup((caddr_t)&sc->sc_bufcnt);
|
|
goto loop;
|
|
}
|
|
|
|
/*
|
|
* The Multibus resources we needed have been
|
|
* allocated to us; start the device.
|
|
*/
|
|
tmgo(mc)
|
|
register struct mb_ctlr *mc;
|
|
{
|
|
register struct tpb *tpb;
|
|
|
|
mc->mc_tab.b_flags &=~ B_DONE;
|
|
tpb = &tm_mb[mc->mc_ctlr]->tmb_tpb;
|
|
if (tpb->tm_bsize != 0)
|
|
c68t86((long)&DVMA[MBI_ADDR(mc->mc_mbinfo)], &tpb->tm_baddr);
|
|
if (tm_mb[mc->mc_ctlr]->tmb_ccb.tmccb_gate != TMG_OPEN)
|
|
printf("tmgo: gate wasn't open\n");
|
|
tm_mb[mc->mc_ctlr]->tmb_ccb.tmccb_gate = TMG_CLOSED;
|
|
ATTN(mc->mc_addr);
|
|
mc->mc_tab.b_flags |= B_BUSY;
|
|
}
|
|
|
|
/*
|
|
* Tm interrupt routine.
|
|
*/
|
|
tmintr(ctlr)
|
|
int ctlr;
|
|
{
|
|
register struct buf *dp;
|
|
register struct buf *bp;
|
|
register struct mb_ctlr *mc;
|
|
register struct mt_softc *sc;
|
|
register struct tpb *tpb;
|
|
register int mtunit, state;
|
|
int spins, err;
|
|
|
|
mc = tmcinfo[ctlr];
|
|
/* paranoid */
|
|
if (mc == NULL || mc->mc_tab.b_actf == NULL ||
|
|
(mc->mc_tab.b_flags & B_BUSY) == 0 ||
|
|
tm_mb[ctlr]->tmb_ccb.tmccb_gate != TMG_OPEN)
|
|
return;
|
|
|
|
tpb = &tm_mb[ctlr]->tmb_tpb;
|
|
dp = mc->mc_tab.b_actf;
|
|
bp = dp->b_actf;
|
|
if (bp == NULL)
|
|
panic("tmintr: queuing error");
|
|
mtunit = MTUNIT(bp->b_dev);
|
|
if (mtunit >= NMT)
|
|
panic("tmintr: queueing error 2");
|
|
sc = &mt_softc[mtunit];
|
|
sc->sc_tms = tpb->tm_stat;
|
|
if (sc->sc_tms.tms_eot && (bp->b_flags & B_READ) == B_WRITE)
|
|
sc->sc_resid = bp->b_bcount;
|
|
else
|
|
sc->sc_resid = bp->b_bcount - tpb->tm_count;
|
|
|
|
/* clear the interrupt */
|
|
clrtpb(tpb);
|
|
tpb->tm_cmd = TM_NOP;
|
|
tm_mb[ctlr]->tmb_ccb.tmccb_ccw = TMC_CLRINT;
|
|
tm_mb[ctlr]->tmb_ccb.tmccb_gate = TMG_CLOSED;
|
|
ATTN(mc->mc_addr);
|
|
spins = 0;
|
|
while (tm_mb[ctlr]->tmb_ccb.tmccb_gate != TMG_OPEN) {
|
|
if (spins++ > 1000) {
|
|
printf("tmintr: can't clear interrupt\n");
|
|
break;
|
|
}
|
|
DELAY(10);
|
|
}
|
|
|
|
mc->mc_tab.b_flags &= ~B_BUSY;
|
|
if ((state = mc->mc_tab.b_active) == 0) {
|
|
printf("tm%d: stray interrupt\n", mc->mc_ctlr);
|
|
return;
|
|
}
|
|
/*
|
|
* If last command was a rewind, and tape is still
|
|
* rewinding, wait for another interrupt, triggered
|
|
* by tmtimer
|
|
*/
|
|
if (state == SREW) {
|
|
if (sc->sc_tms.tms_online &&
|
|
(!sc->sc_tms.tms_load || !sc->sc_tms.tms_ready))
|
|
return;
|
|
state = SCOM;
|
|
}
|
|
|
|
/*
|
|
* An operation completed... record status
|
|
*/
|
|
sc->sc_timo = INF;
|
|
err = sc->sc_tms.tms_error;
|
|
if ((bp->b_flags & B_READ) == 0)
|
|
sc->sc_lastiow = 1;
|
|
if (err == E_EOT && sc->sc_tms.tms_load)
|
|
err = E_NOERROR;
|
|
/*
|
|
* Check for errors.
|
|
*/
|
|
if (err != E_NOERROR && err != E_SHORTREC) {
|
|
/*
|
|
* If we hit the end of the tape file, update our position.
|
|
*/
|
|
if (err == E_EOF || err == E_EOT) {
|
|
tmseteof(bp); /* set blkno and nxrec */
|
|
state = SCOM; /* force completion */
|
|
sc->sc_resid = bp->b_bcount;
|
|
goto opdone;
|
|
}
|
|
/*
|
|
* If error is not hard, and this was an i/o operation
|
|
* retry up to 8 times.
|
|
*/
|
|
if (state == SIO) {
|
|
if (++mc->mc_tab.b_errcnt < 7) {
|
|
sc->sc_blkno++; /* force repositioning */
|
|
goto opcont;
|
|
}
|
|
} else {
|
|
/*
|
|
* Hard or non-i/o errors on non-raw tape
|
|
* cause it to close.
|
|
*/
|
|
if ((int)sc->sc_openf > 0 && bp != &rtmbuf[mtunit])
|
|
sc->sc_openf = -1;
|
|
}
|
|
/*
|
|
* Couldn't recover error
|
|
*/
|
|
printf("mt%d: hard error bn=%d er=%x\n", mtunit,
|
|
bp->b_blkno, err);
|
|
bp->b_flags |= B_ERROR;
|
|
goto opdone;
|
|
}
|
|
/*
|
|
* Advance tape control FSM.
|
|
*/
|
|
switch (state) {
|
|
|
|
case SIO:
|
|
/*
|
|
* Read/write increments tape block number
|
|
*/
|
|
sc->sc_blkno++;
|
|
break;
|
|
|
|
case SCOM:
|
|
/*
|
|
* For forward/backward space record update current position.
|
|
*/
|
|
if (bp == &ctmbuf[mtunit])
|
|
switch (bp->b_command) {
|
|
|
|
case TM_SPACE|TM_FORWARD:
|
|
case TM_SPACEF|TM_FORWARD:
|
|
sc->sc_blkno += bp->b_repcnt;
|
|
break;
|
|
|
|
case TM_SPACE|TM_REVERSE:
|
|
case TM_SPACEF|TM_REVERSE:
|
|
sc->sc_blkno -= bp->b_repcnt;
|
|
break;
|
|
|
|
case TM_REWIND:
|
|
case TM_REWINDX:
|
|
sc->sc_blkno = 0;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SSEEK:
|
|
sc->sc_blkno = bdbtofsb(bp->b_blkno);
|
|
goto opcont;
|
|
|
|
case SERR:
|
|
goto opcont;
|
|
|
|
default:
|
|
panic("tmintr");
|
|
}
|
|
opdone:
|
|
mc->mc_tab.b_active = 0;
|
|
mc->mc_tab.b_flags |= B_DONE;
|
|
opcont:
|
|
if (mc->mc_mbinfo != 0)
|
|
mbdone(mc);
|
|
else
|
|
tmdone(mc);
|
|
}
|
|
|
|
/*
|
|
* Tm polling interrupt routine.
|
|
*/
|
|
tmpoll()
|
|
{
|
|
register struct mb_ctlr *mc;
|
|
register int ctlr;
|
|
|
|
for (ctlr = 0, mc = tmcinfo[0]; ctlr < NTM; ctlr++, mc++) {
|
|
if (mc != NULL && mc->mc_tab.b_actf != NULL &&
|
|
(mc->mc_tab.b_flags & B_BUSY) != 0 &&
|
|
tm_mb[ctlr]->tmb_ccb.tmccb_gate == TMG_OPEN) {
|
|
tmintr(ctlr);
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
tmdone(mc)
|
|
register struct mb_ctlr *mc;
|
|
{
|
|
register struct buf *dp;
|
|
register struct buf *bp;
|
|
register struct mt_softc *sc;
|
|
|
|
dp = mc->mc_tab.b_actf;
|
|
bp = dp->b_actf;
|
|
sc = &mt_softc[MTUNIT(bp->b_dev)];
|
|
if (mc->mc_tab.b_flags & B_DONE) {
|
|
/*
|
|
* Reset error count and remove
|
|
* from device queue.
|
|
*/
|
|
mc->mc_tab.b_errcnt = 0;
|
|
dp->b_actf = bp->av_forw;
|
|
bp->b_resid = sc->sc_resid;
|
|
iodone(bp);
|
|
if (sc->sc_bufcnt-- >= MAXMTBUF)
|
|
wakeup((caddr_t)&sc->sc_bufcnt);
|
|
/*
|
|
* Advance controller queue and put this
|
|
* unit back on the controller queue if
|
|
* the unit queue is not empty
|
|
*/
|
|
mc->mc_tab.b_actf = dp->b_forw;
|
|
if (dp->b_actf) {
|
|
dp->b_forw = NULL;
|
|
if (mc->mc_tab.b_actf == NULL)
|
|
mc->mc_tab.b_actf = dp;
|
|
else
|
|
mc->mc_tab.b_actl->b_forw = dp;
|
|
mc->mc_tab.b_actl = dp;
|
|
}
|
|
} else {
|
|
/*
|
|
* Circulate slave to end of controller
|
|
* queue to give other slaves a chance.
|
|
* No need to look at unit queue since operation
|
|
* is still in progress
|
|
*/
|
|
if (dp->b_forw) {
|
|
mc->mc_tab.b_actf = dp->b_forw;
|
|
dp->b_forw = NULL;
|
|
mc->mc_tab.b_actl->b_forw = dp;
|
|
mc->mc_tab.b_actl = dp;
|
|
}
|
|
}
|
|
if (mc->mc_tab.b_actf)
|
|
tmstart(mc);
|
|
}
|
|
|
|
tmtimer(dev)
|
|
int dev;
|
|
{
|
|
register struct mb_ctlr *mc = tmcinfo[TMUNIT(dev)];
|
|
register struct mt_softc *sc = &mt_softc[MTUNIT(dev)];
|
|
register struct tm_mbinfo *tmb = tm_mb[TMUNIT(dev)];
|
|
register struct tpb *tpb;
|
|
register int s, slave;
|
|
|
|
if (sc->sc_timo != INF && (sc->sc_timo -= 5) < 0) {
|
|
printf("mt%d: lost interrupt\n", MTUNIT(dev));
|
|
sc->sc_timo = INF;
|
|
s = SPLTM();
|
|
tmintr(mc->mc_ctlr);
|
|
(void) splx(s);
|
|
}
|
|
if (mc->mc_tab.b_active == SREW) { /* check rewind status */
|
|
s = SPLTM();
|
|
if ((mc->mc_tab.b_flags & B_BUSY) == 0 &&
|
|
tmb->tmb_ccb.tmccb_gate == TMG_OPEN) {
|
|
tmb->tmb_ccb.tmccb_gate = TMG_CLOSED;
|
|
tmb->tmb_ccb.tmccb_ccw = TMC_NORMAL;
|
|
tpb = &tmb->tmb_tpb;
|
|
clrtpb(tpb);
|
|
tpb->tm_cmd = TM_STATUS;
|
|
tpb->tm_ctl.tmc_width = 1;
|
|
tpb->tm_ctl.tmc_intr = 1;
|
|
slave = tmdinfo[MTUNIT(dev)]->md_slave & 03;
|
|
tpb->tm_ctl.tmc_tape = slave;
|
|
ATTN(mc->mc_addr);
|
|
mc->mc_tab.b_flags |= B_BUSY;
|
|
}
|
|
(void) splx(s);
|
|
}
|
|
timeout(tmtimer, (caddr_t)dev, 5*hz);
|
|
}
|
|
|
|
tmseteof(bp)
|
|
register struct buf *bp;
|
|
{
|
|
register int mtunit = MTUNIT(bp->b_dev);
|
|
register struct mt_softc *sc = &mt_softc[mtunit];
|
|
register struct tpb *tpb = &tm_mb[TMUNIT(bp->b_dev)]->tmb_tpb;
|
|
|
|
if (bp == &ctmbuf[mtunit]) {
|
|
if (sc->sc_blkno > bdbtofsb(bp->b_blkno)) {
|
|
/* reversing */
|
|
sc->sc_nxrec = bdbtofsb(bp->b_blkno) - tpb->tm_rcount;
|
|
sc->sc_blkno = sc->sc_nxrec;
|
|
} else {
|
|
/* spacing forward */
|
|
sc->sc_blkno = bdbtofsb(bp->b_blkno) + tpb->tm_rcount;
|
|
sc->sc_nxrec = sc->sc_blkno - 1;
|
|
}
|
|
return;
|
|
}
|
|
/* eof on read */
|
|
sc->sc_nxrec = bdbtofsb(bp->b_blkno);
|
|
}
|
|
|
|
tmread(dev, uio)
|
|
dev_t dev;
|
|
struct uio *uio;
|
|
{
|
|
int err;
|
|
|
|
if (err = tmphys(dev, uio))
|
|
return (err);
|
|
return (physio(tmstrategy, &rtmbuf[MTUNIT(dev)], dev, B_READ,
|
|
minphys, uio));
|
|
}
|
|
|
|
tmwrite(dev, uio)
|
|
dev_t dev;
|
|
struct uio *uio;
|
|
{
|
|
int err;
|
|
|
|
if (err = tmphys(dev, uio))
|
|
return (err);
|
|
return (physio(tmstrategy, &rtmbuf[MTUNIT(dev)], dev, B_WRITE,
|
|
minphys, uio));
|
|
}
|
|
|
|
/*
|
|
* Check that a raw device exists.
|
|
* If it does, set up sc_blkno and sc_nxrec
|
|
* so that the tape will appear positioned correctly.
|
|
* Also make sure buffer is word-aligned (hardware requirement)
|
|
*/
|
|
tmphys(dev, uio)
|
|
dev_t dev;
|
|
struct uio *uio;
|
|
{
|
|
register int mtunit = MTUNIT(dev);
|
|
register daddr_t a;
|
|
register struct mt_softc *sc;
|
|
register struct mb_device *md;
|
|
|
|
if (mtunit >= NMT || (md=tmdinfo[mtunit]) == 0 || md->md_alive == 0)
|
|
return (ENXIO);
|
|
if ((int)uio->uio_iov->iov_base & 1)
|
|
return (EINVAL);
|
|
sc = &mt_softc[mtunit];
|
|
a = bdbtofsb(uio->uio_offset / DEV_BSIZE);
|
|
sc->sc_blkno = a;
|
|
sc->sc_nxrec = a + 1;
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
tmioctl(dev, cmd, data, flag)
|
|
dev_t dev;
|
|
caddr_t data;
|
|
{
|
|
int mtunit = MTUNIT(dev);
|
|
register struct mt_softc *sc = &mt_softc[mtunit];
|
|
register callcount;
|
|
int fcount;
|
|
struct mtop *mtop;
|
|
struct mtget *mtget;
|
|
/* we depend of the values and order of the MT codes here */
|
|
static tmops[] = {
|
|
TM_WEOF,
|
|
TM_SEARCH|TM_FORWARD,
|
|
TM_SEARCH|TM_REVERSE,
|
|
TM_SPACEF|TM_FORWARD,
|
|
TM_SPACEF|TM_REVERSE,
|
|
TM_REWINDX,
|
|
TM_UNLOAD,
|
|
TM_STATUS,
|
|
};
|
|
|
|
switch (cmd) {
|
|
|
|
case MTIOCTOP: /* tape operation */
|
|
mtop = (struct mtop *)data;
|
|
switch (mtop->mt_op) {
|
|
case MTWEOF:
|
|
case MTFSF: case MTBSF:
|
|
callcount = mtop->mt_count;
|
|
fcount = 1;
|
|
break;
|
|
case MTFSR: case MTBSR:
|
|
callcount = 1;
|
|
fcount = mtop->mt_count;
|
|
break;
|
|
case MTREW: case MTOFFL: case MTNOP:
|
|
callcount = 1;
|
|
fcount = mtop->mt_count;
|
|
break;
|
|
default:
|
|
return (ENXIO);
|
|
}
|
|
if (callcount <= 0 || fcount <= 0)
|
|
return (ENXIO);
|
|
while (--callcount >= 0) {
|
|
if (tmcommand(dev, tmops[mtop->mt_op], fcount))
|
|
return (EIO);
|
|
if ((mtop->mt_op == MTFSR || mtop->mt_op == MTBSR) &&
|
|
sc->sc_tms.tms_eof)
|
|
return (EIO);
|
|
if (sc->sc_tms.tms_eot)
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case MTIOCGET:
|
|
mtget = (struct mtget *)data;
|
|
mtget->mt_dsreg = *(short *)&sc->sc_tms;
|
|
mtget->mt_erreg = sc->sc_tms.tms_error;
|
|
mtget->mt_resid = sc->sc_resid;
|
|
mtget->mt_type = MT_ISCPC;
|
|
mtget->mt_flags = MTF_REEL;
|
|
mtget->mt_bf = 20;
|
|
break;
|
|
|
|
default:
|
|
return (ENXIO);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
tmdump(dev)
|
|
dev_t dev;
|
|
{
|
|
|
|
return (ENXIO);
|
|
}
|
|
|
|
/*
|
|
* Convert a 68000 address into a 8086 address
|
|
* This involves translating a virtual address into a
|
|
* physical multibus address and converting the 20 bit result
|
|
* into a two word base and offset.
|
|
*/
|
|
static
|
|
c68t86(a68, a86)
|
|
long a68;
|
|
ptr86_t *a86;
|
|
{
|
|
|
|
a68 -= (long)DVMA;
|
|
a86->a_offset = a68 & 0xFFFF;
|
|
a86->a_base = (a68 & 0xF0000) >> 4;
|
|
}
|