/* * 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 [] = "@(#)mtb.c 3.2 14:45:38 - 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 loudbtape == NMTERR. * Only fatal error printouts if loudbtape == NMTERR -1. * All error printouts if loudbtape == NMTERR -1. * It can be patched in the a.out or in core. */ #define NMTERR 8 int loudbtape = NMTERR - 1; extern int nmtb; /* number of tape transports */ extern char mtbaddr[]; /* tape addresses */ extern int mtbselch; /* selch number for tapes */ int mtbio(), mtbscintr(); struct buf mtbab; /* 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 mtbab.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 mtbscq = { &mtbio, &mtbscintr, 0, 0 }; struct buf rmtbbuf, cmtbbuf; caddr_t mtbendaddr; /* 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 mtbopen(dev, flag) { register struct tape *mtb; register unit; extern short isp[]; extern tapeint(); mtbab.b_flags |= B_TAPE; if ((unit=minor(dev)&03) >= nmtb || (mtb = &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[mtbaddr[unit]] = (short)tapeint; */ mtb->m_status = mtb->m_blkno = 0; if (!(flag & 2)) /* open for writing */ mtb->m_status = ISWPROT; mtb->m_lastrec = 1000000; mtbcommand(dev, OPEN); if (mtb->m_status & ISDU) u.u_error = ENXIO; if (u.u_error == 0) mtb->m_status |= ISOPENED; } /* ARGSUSED */ mtbclose(dev, flag) register int dev; { register struct tape *mtb; register writing; mtb = &tape[minor(dev)&03]; writing = 0; if (mtb->m_status&ISWRITING) { writing++; mtbcommand(dev, WF); mtbcommand(dev, WF); } if (minor(dev) & 04) { if (writing) mtbcommand(dev, BR); } else mtbcommand(dev, RW); mtb->m_status = 0; } mtbcommand(dev, command) Tapecmd command; { register struct buf *bp; trace(0x20<<16, "mtbcommand", command); bp = &cmtbbuf; 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; mtbstrategy(bp); iowait(bp); if (bp->b_flags & B_WANTED) wakeup(bp); bp->b_flags = 0; } mtbstrategy(abp) struct buf *abp; { register struct buf *bp; register struct tape *mtb; register int *p; register daddr_t fsblkno; mtb = &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 != &cmtbbuf) { p = &mtb->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) && mtb->m_status&ISBOT || (cmd == WF && mtb->m_status&ISWPROT)) { if (cmd == WF) bp->b_flags |= B_ERROR; iodone(bp); return; } } bp->av_forw = 0; spl5(); if (mtbab.b_actf) mtbab.b_actl->av_forw = bp; else mtbab.b_actf = bp; mtbab.b_actl = bp; if (mtbab.b_active == INACTIVE) mtbstart(); spl0(); } mtbstart() { register struct tape *mtb; register struct buf *bp; register daddr_t fsblkno; while (bp = mtbab.b_actf) { #ifdef UCB_NKB fsblkno = dbtofsb(bp->b_blkno); #else UCB_NKB fsblkno = bp->b_blkno; #endif UCB_NKB mtb = &tape[minor(bp->b_dev)&03]; if (bp != &cmtbbuf && (mtb->m_status&ISDU)) { abort: bp->b_flags |= B_ERROR; mtbab.b_actf = bp->av_forw; mtbab.b_active = INACTIVE; iodone(bp); continue; } mtb->m_status &= ~ISWRITING; if (bp == &cmtbbuf) mtbab.b_active = SCOM; else if (fsblkno == mtb->m_blkno) { if (mtb->m_eot > NMTERR) goto abort; mtbab.b_active = SIO; if ((bp->b_flags&B_READ) == 0) { if (mtb->m_status & ISWPROT) goto abort; if (mtb->m_eot && !(mtb->m_status & EOTISOK)) { bp->b_error = ENOSPC; if (mtb->m_lastrec > fsblkno) mtb->m_lastrec = fsblkno; goto abort; } mtb->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 (mtb->m_status&(ISBOT|ISWERR)) { mtbab.b_active = SBOT; mtb->m_status &= ~ISWERR; } } } else if (fsblkno > mtb->m_blkno) { if (mtb->m_eot > NMTERR) goto abort; mtbab.b_active = SSFOR; } else mtbab.b_active = SSREV; selchreq(mtbselch, &mtbscq, minor(bp->b_dev)&03); return; } } mtbio(unit) register int unit; { register struct buf *bp; register addr; register stat; register struct tape *mtb; trace(0x20<<16, "mtbio", mtbab.b_active); if ((bp = mtbab.b_actf) == 0) return; unit = minor(bp->b_dev)&03; addr = mtbaddr[unit]; mtb = &tape[unit]; mtb->m_status &= ~BACKWARDS; switch(mtbab.b_active) { case SCOM: switch ((Tapecmd) bp->b_blkno) { case OPEN: oc(addr, CLEAR); oc(addr, ENABLE); if ((stat = ss(addr))&DU) mtb->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) { mtb->m_status |= ISBOT; mtb->m_eot = 0; /* reset eot flg at load pt */ } mtbab.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))) { mtb->m_status |= ISBOT; mtb->m_blkno = 0; mtb->m_eot = 0; /* reset eot flg */ } fini: mtbab.b_active = 0; mtbab.b_actf = bp->av_forw; iodone(bp); mtbstart(); } mtb->m_status |= BACKWARDS; break; case WF: oc(addr, WF); if (ss(addr) & NMTN) goto nowrit; /* no write ring */ break; case BR: case BF: if (mtb->m_status & ISBOT) goto fini; oc(addr, (Tapecmd) bp->b_blkno); if (ss(addr) & NMTN) goto fini; mtb->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, "mtboc", bp->b_blkno); trace(0x10<<16, "status", ss(addr)); mtbab.b_actf = bp->av_forw; iodone(bp); selchfree(mtbselch); return; case SSREV: if (mtb->m_status & ISBOT) goto fini; oc(addr, BR); stat = ss(addr); trace(0x10<<16, "mtboc", BR); trace(0x10<<16, "status", stat); if (stat & NMTN) goto fini; mtb->m_status |= BACKWARDS; return; case SBOT: oc(addr, WF); stat = ss(addr); trace(0x10<<16, "mtboc", WF); trace(0x10<<16, "status", stat); if (stat & NMTN) { /* write protected */ nowrit: bp->b_flags |= B_ERROR; mtb->m_status |= ISWPROT; goto fini; } return; case SSFOR: case SIO: oc(mtbselch, STOP); wdh(mtbselch, bp->b_un.b_addr); wdh(mtbselch, bp->b_un.b_addr + bp->b_bcount - 1); trace(0x10<<16, "mtbrw", bp->b_un.b_addr); if ((bp->b_flags&B_READ) || mtbab.b_active == SSFOR) { oc(addr, READ); /* oc(mtbselch, READ_GO); */ sgo(mtbselch, 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(mtbselch, GO); */ sgo(mtbselch, GO, addr); } } } mtbscintr(dev, stat, unit) { oc(mtbselch, 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) */ mtbendaddr = (caddr_t) ((rdh(mtbselch)+1) & ~01); } mtbintr(dev, stat) { register struct buf *bp; register struct tape *mtb; register op; trace(0x10<<16, "interrupt", mtbaddr[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 = mtbab.b_active; mtbab.b_active = INACTIVE; if ((bp = mtbab.b_actf) == 0) return; mtb = &tape[minor(bp->b_dev)&03]; if (stat & EOT) { if (mtb->m_status & BACKWARDS) { if ((mtb->m_status & ISEOT) == 0) { mtb->m_blkno = 0; mtb->m_status |= ISBOT; mtb->m_eot = 0; } else if (--mtb->m_eot < 0) mtb->m_eot = 0; } else { mtb->m_status |= ISEOT; mtb->m_eot++; } } else { mtb->m_status &= ~(ISEOT|ISBOT); mtb->m_eot = 0; } if (op == SCOM) { mtbstart(); return; } selchfree(mtbselch); mtb->m_status &= ~ISDU; if (stat&DU) { mtb->m_status |= ISDU; mtb->m_status &= ~ISWPROT; mtbab.b_actf = bp->av_forw; bp->b_flags |= B_ERROR; iodone(bp); mtbstart(); return; } if (op == SSREV) { if ((mtb->m_status & ISBOT) == 0) mtb->m_blkno--; } else mtb->m_blkno++; if ((stat&ERR) && op == SIO) { if (loudbtape <= mtbab.b_errcnt) deverror(bp, stat, dev); if (++mtbab.b_errcnt < NMTERR) { if ((bp->b_flags & B_READ) == 0) mtb->m_status |= ISWERR; mtbab.b_active = SSREV; selchreq(mtbselch, &mtbscq, minor(bp->b_dev)&03); return; } bp->b_flags |= B_ERROR; } if (op == SIO || (stat&(EOF|EOT)) && op == SSFOR) { mtbab.b_errcnt = 0; mtbab.b_actf = bp->av_forw; bp->b_resid = stat&(EOF|EOT) ? bp->b_bcount : bp->b_bcount - (mtbendaddr - bp->b_un.b_addr); iodone(bp); } mtbstart(); } /* * Raw magtape interface */ mtbread(dev) { mtbseek(dev); physio(mtbstrategy, &rmtbbuf, dev, B_READ); } mtbwrite(dev) { mtbseek(dev); physio(mtbstrategy, &rmtbbuf, dev, B_WRITE); } /* * Kludge to ignore seeks on raw mag tape by making block no. look right */ mtbseek(dev) { register struct tape *mtb; mtb = &tape[minor(dev)&03]; mtb->m_lastrec = (mtb->m_blkno = u.u_offset>>BSHIFT) + 1; } /* * Ioctl call is used to issue commands to raw mag tape * First word is command function */ char mtbcmds[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 */ mtbioctl(dev, cmd, addr, flag) caddr_t addr; { register fn, com; register struct tape *mtb = &tape[minor(dev)&03]; switch (cmd) { default: return(1); case TIOCSETP: /* for old timers */ case MTIOFUNC: if ((fn = fuword(addr)) < 0 || fn > 7 || !(com = mtbcmds[fn])) { if (fn == -1) u.u_error = EFAULT; else u.u_error = ENXIO; return(0); } if (com == WF && mtb->m_status & ISWPROT) { u.u_error = EIO; return(0); } mtbcommand(dev, com); return(0); case MTIOCEOT: mtb->m_status |= EOTISOK; return(0); case MTIOCGET: if (copyout((caddr_t)mtb, (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 mtbdump() { extern char *memtop; register int selch, mtb; register char *memp; /* Set up device addresses */ selch = mtbselch; mtb = mtbaddr[0]; /* * Write a filemark at the start of the tape & backspace over it */ oc(selch, STOP); oc(mtb, CLEAR); oc(mtb, RW); mtbwait(mtb); oc(mtb, WF); mtbwait(mtb); oc(mtb, BR); mtbwait(mtb); /* * Write all memory to tape */ for (memp = (char *)0; memp < memtop; memp += DUMPBLK) { wdh(selch, memp); wdh(selch, memp + DUMPBLK - 1); oc(mtb, WRITE); oc(selch, GO); while (ss(selch) & SELCHBSY) ; oc(selch, STOP); mtbwait(mtb); } /* * Write two filemarks end of tape and rewind */ oc(mtb, WF); mtbwait(mtb); oc(mtb, RW); mtbwait(mtb); } /* * Sense-status loop to wait for 'NO MOTION' status */ mtbwait(addr) { while ((ss(addr) & NMTN) == 0) ; }