/* * Interdata 800 bpi mag tape driver * * This is a 'first attempt' at a driver, and should be thrown * away and rewritten. The major known problems are: * - it probably won't work for multiple transports * - attempting a backspace from loadpoint will hang up the controller * (this can be cleared manually by a reset/forward/reset/online * sequence) * - error checking, particularly for end-of-tape, is insufficient * * This has now advanced to being a second attempt, which may have * added improvements in the following areas * - better checking for strange situations * (like backspace or rewind from load point) * (or write protected tapes) * - more intelligent handling of EOT * - better handling of write errs (leave some blank tape before retry) * * ... kre Oct 81 * * 2/6/82 twg!day: * If no write-enable ring, and you attempt to write, tape positions * back a block. * The erase-forward on write errors can bomb out with large blocks * because you can run out of retries before you have spaced forward * past the bad spot. */ /* from dev/mt.c 2.4 (Melbourne) 81/10/07 */ #include "../h/local.h" #ifdef SCCS_ID static char SCCS_ID [] = "@(#)mta.c 3.2 14:44:58 - 83/04/11 "; #endif SCCS_ID #include "../h/param.h" #include "../h/conf.h" #include "../h/dir.h" #include "../h/user.h" #include "../h/buf.h" #include "../h/selch.h" #include "../h/tty.h" /* NMTERR is the max number of error retries. * No error printouts if loudatape == NMTERR. * Only fatal error printouts if loudatape == NMTERR -1. * All error printouts if loudatape == NMTERR -1. * It can be patched in the a.out or in core. */ #define NMTERR 8 int loudatape = NMTERR - 1; extern int nmta; /* number of tape transports */ extern char mtaaddr[]; /* tape addresses */ extern int mtaselch; /* selch number for tapes */ int mtaio(), mtascintr(); struct buf mtaab; /* Values for m_status */ #define ISOPENED 01 #define ISDU 02 #define ISWRITING 04 #define ISBOT 010 #define ISEOT 020 #define ISWPROT 040 #define BACKWARDS 0100 #define EOTISOK 0200 #define ISWERR 0400 /* * The next bit of this (down to & including the struct tape definition, * but not the space allocation) should be in some include file somewhere. * Just for now (and probably forever) I can't be bothered. */ #define MTIOFUNC (('m'<<8)|1) #define MTIOCEOT (('m'<<8)|2) #define MTIOCGET (('m'<<8)|3) struct tape { int m_status; /* status of transport */ int m_blkno; /* current block position in file */ int m_lastrec; /* next record # at end of file */ int m_eot; /* number of blocks past EOT */ } tape[4]; /* Values for mtaab.b_active */ #define INACTIVE 0 /* inactive */ #define SCOM 1 #define SSFOR 2 /* space forward */ #define SSREV 3 /* space reverse */ #define SIO 4 /* do read or write */ #define SBOT 5 /* write a filemark */ struct selchq mtascq = { &mtaio, &mtascintr, 0, 0 }; struct buf rmtabuf, cmtabuf; caddr_t mtaendaddr; /* end address from controller */ /* * Tape controller commands */ typedef int Tapecmd; #define ENABLE 0x40 #define CLEAR 0x20 #define READ 0x21 /* Read a record */ #define WRITE 0x22 /* Write a record */ #define WF 0x30 /* Write filemark */ #define RW 0x38 /* Rewind */ #define FF 0x23 /* Forward space file */ #define BF 0x13 /* Back space file */ #define BR 0x11 /* Back space record */ #define OPEN 0xff /* * Tape status */ #define ERR 0x80 #define EOF 0x40 #define EOT 0x20 #define NMTN 0x10 #define BSY 0x04 #define DU 0x01 mtaopen(dev, flag) { register struct tape *mta; register unit; extern short isp[]; extern tapeint(); mtaab.b_flags |= B_TAPE; if ((unit=minor(dev)&03) >= nmta || (mta = &tape[unit])->m_status & ISOPENED) { u.u_error = ENXIO; return; } /* * the following line is an attempt to overcome a problem * with our tape drive (all tape drives) that causes huge * numbers of unwanted interrupts, flooding the system queue * and stopping unix. Fix is to have low core immediate interrupt * handler disable the device on first interrupt. We must * enable it wherever we expect to get an interrupt */ /* isp[mtaaddr[unit]] = (short)tapeint; */ mta->m_status = mta->m_blkno = 0; if (!(flag & 2)) /* open for writing */ mta->m_status = ISWPROT; mta->m_lastrec = 1000000; mtacommand(dev, OPEN); if (mta->m_status & ISDU) u.u_error = ENXIO; if (u.u_error == 0) mta->m_status |= ISOPENED; } /* ARGSUSED */ mtaclose(dev, flag) register int dev; { register struct tape *mta; register writing; mta = &tape[minor(dev)&03]; writing = 0; if (mta->m_status&ISWRITING) { writing++; mtacommand(dev, WF); mtacommand(dev, WF); } if (minor(dev) & 04) { if (writing) mtacommand(dev, BR); } else mtacommand(dev, RW); mta->m_status = 0; } mtacommand(dev, command) Tapecmd command; { register struct buf *bp; trace(0x20<<16, "mtacommand", command); bp = &cmtabuf; spl5(); while (bp->b_flags & B_BUSY) { bp->b_flags |= B_WANTED; sleep(bp, PRIBIO); } bp->b_flags |= B_BUSY | B_READ; spl0(); bp->b_dev = dev; bp->b_blkno = (daddr_t) command; mtastrategy(bp); iowait(bp); if (bp->b_flags & B_WANTED) wakeup(bp); bp->b_flags = 0; } mtastrategy(abp) struct buf *abp; { register struct buf *bp; register struct tape *mta; register int *p; register daddr_t fsblkno; mta = &tape[minor((bp = abp)->b_dev)&03]; #ifdef UCB_NKB fsblkno = dbtofsb(bp->b_blkno); #else UCB_NKB fsblkno = bp->b_blkno; #endif UCB_NKB if (bp != &cmtabuf) { p = &mta->m_lastrec; if (fsblkno > *p) { bp->b_flags |= B_ERROR; iodone(bp); return; } if (fsblkno == *p && (bp->b_flags&B_READ)) { bp->b_resid = bp->b_bcount; clrbuf(bp); iodone(bp); return; } if ((bp->b_flags&B_READ) == 0) *p = fsblkno + 1; } else { register cmd = (Tapecmd) bp->b_blkno; if ((cmd == BR || cmd == RW) && mta->m_status&ISBOT || (cmd == WF && mta->m_status&ISWPROT)) { if (cmd == WF) bp->b_flags |= B_ERROR; iodone(bp); return; } } bp->av_forw = 0; spl5(); if (mtaab.b_actf) mtaab.b_actl->av_forw = bp; else mtaab.b_actf = bp; mtaab.b_actl = bp; if (mtaab.b_active == INACTIVE) mtastart(); spl0(); } mtastart() { register struct tape *mta; register struct buf *bp; register daddr_t fsblkno; while (bp = mtaab.b_actf) { #ifdef UCB_NKB fsblkno = dbtofsb(bp->b_blkno); #else UCB_NKB fsblkno = bp->b_blkno; #endif UCB_NKB mta = &tape[minor(bp->b_dev)&03]; if (bp != &cmtabuf && (mta->m_status&ISDU)) { abort: bp->b_flags |= B_ERROR; mtaab.b_actf = bp->av_forw; mtaab.b_active = INACTIVE; iodone(bp); continue; } mta->m_status &= ~ISWRITING; if (bp == &cmtabuf) mtaab.b_active = SCOM; else if (fsblkno == mta->m_blkno) { if (mta->m_eot > NMTERR) goto abort; mtaab.b_active = SIO; if ((bp->b_flags&B_READ) == 0) { if (mta->m_status & ISWPROT) goto abort; if (mta->m_eot && !(mta->m_status & EOTISOK)) { bp->b_error = ENOSPC; if (mta->m_lastrec > fsblkno) mta->m_lastrec = fsblkno; goto abort; } mta->m_status |= ISWRITING; /* * According to the Interdata tape manual, * when writing the first block it is necessary * to write a file mark first and backspace * over it. If this is not done, the block * is sometimes not written correctly. * * We also do this if we have had a write err * to skip the bad spot */ if (mta->m_status&(ISBOT|ISWERR)) { mtaab.b_active = SBOT; mta->m_status &= ~ISWERR; } } } else if (fsblkno > mta->m_blkno) { if (mta->m_eot > NMTERR) goto abort; mtaab.b_active = SSFOR; } else mtaab.b_active = SSREV; selchreq(mtaselch, &mtascq, minor(bp->b_dev)&03); return; } } mtaio(unit) register int unit; { register struct buf *bp; register addr; register stat; register struct tape *mta; trace(0x20<<16, "mtaio", mtaab.b_active); if ((bp = mtaab.b_actf) == 0) return; unit = minor(bp->b_dev)&03; addr = mtaaddr[unit]; mta = &tape[unit]; mta->m_status &= ~BACKWARDS; switch(mtaab.b_active) { case SCOM: switch ((Tapecmd) bp->b_blkno) { case OPEN: oc(addr, CLEAR); oc(addr, ENABLE); if ((stat = ss(addr))&DU) mta->m_status |= ISDU; /* * here we just "assume" that EOT when tape is opened * means load point. This is not so disastrous, as we * will fix it after first i/o. It does however, make * it illegal to do a BR at this point. */ if (stat&EOT) { mta->m_status |= ISBOT; mta->m_eot = 0; /* reset eot flg at load pt */ } mtaab.b_active = INACTIVE; break; case RW: /* * nb: we don't check ISBOT here, as we want to * be able to rewind if the tape was opened at * or past the EOT mark (which is incorrectly * diagnosed as BOT). */ oc(addr, RW); stat = ss(addr); if (!(stat & BSY)) { if (!(stat & (DU|ERR))) { mta->m_status |= ISBOT; mta->m_blkno = 0; mta->m_eot = 0; /* reset eot flg */ } fini: mtaab.b_active = 0; mtaab.b_actf = bp->av_forw; iodone(bp); mtastart(); } mta->m_status |= BACKWARDS; break; case WF: oc(addr, WF); if (ss(addr) & NMTN) goto nowrit; /* no write ring */ break; case BR: case BF: if (mta->m_status & ISBOT) goto fini; oc(addr, (Tapecmd) bp->b_blkno); if (ss(addr) & NMTN) goto fini; mta->m_status |= BACKWARDS; break; case READ: case FF: /* shouldn't need default: as all possible cases are explicit */ default: oc(addr, (Tapecmd) bp->b_blkno); if (ss(addr)&NMTN) goto fini; break; } trace(0x10<<16, "mtaoc", bp->b_blkno); trace(0x10<<16, "status", ss(addr)); mtaab.b_actf = bp->av_forw; iodone(bp); selchfree(mtaselch); return; case SSREV: if (mta->m_status & ISBOT) goto fini; oc(addr, BR); stat = ss(addr); trace(0x10<<16, "mtaoc", BR); trace(0x10<<16, "status", stat); if (stat & NMTN) goto fini; mta->m_status |= BACKWARDS; return; case SBOT: oc(addr, WF); stat = ss(addr); trace(0x10<<16, "mtaoc", WF); trace(0x10<<16, "status", stat); if (stat & NMTN) { /* write protected */ nowrit: bp->b_flags |= B_ERROR; mta->m_status |= ISWPROT; goto fini; } return; case SSFOR: case SIO: oc(mtaselch, STOP); wdh(mtaselch, bp->b_un.b_addr); wdh(mtaselch, bp->b_un.b_addr + bp->b_bcount - 1); trace(0x10<<16, "mtarw", bp->b_un.b_addr); if ((bp->b_flags&B_READ) || mtaab.b_active == SSFOR) { oc(addr, READ); /* oc(mtaselch, READ_GO); */ sgo(mtaselch, READ_GO, addr); } else { oc(addr, WRITE); /* * the following test might be unnecessary, as * contrary to statements in the P.E. tape manual, * NMTN always appears to reset after a WRITE cmd * If there is no write ring, it is indicated by * the universal ERR bit, after the tape has actually * gone through the motions of writing the block. * People with weak hearts should be warned not * to watch. */ if (ss(addr) & NMTN) goto nowrit; /* oc(mtaselch, GO); */ sgo(mtaselch, GO, addr); } } } mtascintr(dev, stat, unit) { oc(mtaselch, STOP); /* * It would be nice to be able to do without the length rounding * that goes on here so tapes with odd block lengths could be handled. * But, selch's are dumb, don't act like the manual says, and it * doesn't work. (NB: this is from with experience on an 8/32, it * hasn't been tried on 3200 series machines) * (it is ok for writing, so it could be done then, but that is * not usually the case that bothers anyone) */ mtaendaddr = (caddr_t) ((rdh(mtaselch)+1) & ~01); } mtaintr(dev, stat) { register struct buf *bp; register struct tape *mta; register op; trace(0x10<<16, "interrupt", mtaaddr[dev]); trace(0x10<<16, "status", stat); /* * If NMTN status is not set, wait for another * interrupt. NMTN should be the last bit to set. * * NB: we must enable interrupts again here, as low level code * has disable them, If it hadn't, we might have been swamped * with them by now. */ if ((stat&NMTN) == 0) return; op = mtaab.b_active; mtaab.b_active = INACTIVE; if ((bp = mtaab.b_actf) == 0) return; mta = &tape[minor(bp->b_dev)&03]; if (stat & EOT) { if (mta->m_status & BACKWARDS) { if ((mta->m_status & ISEOT) == 0) { mta->m_blkno = 0; mta->m_status |= ISBOT; mta->m_eot = 0; } else if (--mta->m_eot < 0) mta->m_eot = 0; } else { mta->m_status |= ISEOT; mta->m_eot++; } } else { mta->m_status &= ~(ISEOT|ISBOT); mta->m_eot = 0; } if (op == SCOM) { mtastart(); return; } selchfree(mtaselch); mta->m_status &= ~ISDU; if (stat&DU) { mta->m_status |= ISDU; mta->m_status &= ~ISWPROT; mtaab.b_actf = bp->av_forw; bp->b_flags |= B_ERROR; iodone(bp); mtastart(); return; } if (op == SSREV) { if ((mta->m_status & ISBOT) == 0) mta->m_blkno--; } else mta->m_blkno++; if ((stat&ERR) && op == SIO) { if (loudatape <= mtaab.b_errcnt) deverror(bp, stat, dev); if (++mtaab.b_errcnt < NMTERR) { if ((bp->b_flags & B_READ) == 0) mta->m_status |= ISWERR; mtaab.b_active = SSREV; selchreq(mtaselch, &mtascq, minor(bp->b_dev)&03); return; } bp->b_flags |= B_ERROR; } if (op == SIO || (stat&(EOF|EOT)) && op == SSFOR) { mtaab.b_errcnt = 0; mtaab.b_actf = bp->av_forw; bp->b_resid = stat&(EOF|EOT) ? bp->b_bcount : bp->b_bcount - (mtaendaddr - bp->b_un.b_addr); iodone(bp); } mtastart(); } /* * Raw magtape interface */ mtaread(dev) { mtaseek(dev); physio(mtastrategy, &rmtabuf, dev, B_READ); } mtawrite(dev) { mtaseek(dev); physio(mtastrategy, &rmtabuf, dev, B_WRITE); } /* * Kludge to ignore seeks on raw mag tape by making block no. look right */ mtaseek(dev) { register struct tape *mta; mta = &tape[minor(dev)&03]; mta->m_lastrec = (mta->m_blkno = u.u_offset>>BSHIFT) + 1; } /* * Ioctl call is used to issue commands to raw mag tape * First word is command function */ char mtacmds[8] = { /* command functions */ FF, /* 0 - forward space file */ BF, /* 1 - back space file */ 0, /* 2 */ WF, /* 3 - write file mark */ READ, /* 4 - forward space record * (since there is no FR command, we simply * do a read without starting up the selch * and ignore the overrun error) */ BR, /* 5 - back space record */ 0, /* 6 */ RW /* 7 - rewind */ }; /* ARGSUSED */ mtaioctl(dev, cmd, addr, flag) caddr_t addr; { register fn, com; register struct tape *mta = &tape[minor(dev)&03]; switch (cmd) { default: return(1); case TIOCSETP: /* for old timers */ case MTIOFUNC: if ((fn = fuword(addr)) < 0 || fn > 7 || !(com = mtacmds[fn])) { if (fn == -1) u.u_error = EFAULT; else u.u_error = ENXIO; return(0); } if (com == WF && mta->m_status & ISWPROT) { u.u_error = EIO; return(0); } mtacommand(dev, com); return(0); case MTIOCEOT: mta->m_status |= EOTISOK; return(0); case MTIOCGET: if (copyout((caddr_t)mta, (caddr_t)addr, sizeof(struct tape))) u.u_error = EFAULT; return(0); } } /* * Dump all of memory to magtape in DUMPBLK-byte blocks */ #define DUMPBLK 8192 mtadump() { extern char *memtop; register int selch, mta; register char *memp; /* Set up device addresses */ selch = mtaselch; mta = mtaaddr[0]; /* * Write a filemark at the start of the tape & backspace over it */ oc(selch, STOP); oc(mta, CLEAR); oc(mta, RW); mtawait(mta); oc(mta, WF); mtawait(mta); oc(mta, BR); mtawait(mta); /* * Write all memory to tape */ for (memp = (char *)0; memp < memtop; memp += DUMPBLK) { wdh(selch, memp); wdh(selch, memp + DUMPBLK - 1); oc(mta, WRITE); oc(selch, GO); while (ss(selch) & SELCHBSY) ; oc(selch, STOP); mtawait(mta); } /* * Write two filemarks end of tape and rewind */ oc(mta, WF); mtawait(mta); oc(mta, RW); mtawait(mta); } /* * Sense-status loop to wait for 'NO MOTION' status */ mtawait(addr) { while ((ss(addr) & NMTN) == 0) ; }