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