V10/sys/io/te16.c
/*
* TM03/TE16 tape driver
*
* TODO:
* multiple drives per formatter might work, but are untested
*/
#include "sys/param.h"
#include "sys/buf.h"
#include "sys/conf.h"
#include "sys/file.h"
#include "sys/user.h"
#include "sys/mbaddr.h"
#include "sys/mbsts.h"
#include "sys/subaddr.h"
#include "sys/mtio.h"
#include "sys/te16.h"
/*
* hardware stuff
*/
struct device {
int htcs1; /* control status */
int htds; /* drive status */
int hter; /* error */
int htmr; /* maintenance */
int htas; /* attention status */
int htfc; /* frame count */
int htdt; /* drive type */
int htck; /* nrzi check (crc) error character */
int htsn; /* serial number */
int httc; /* tape control */
};
/* htcs1 */
#define HT_GO 000001 /* go bit */
#define HT_REWOFFL 000002 /* rewind offline */
#define HT_REW 000006 /* rewind */
#define HT_DCLR 000010 /* drive clear */
#define HT_ERASE 000024 /* erase */
#define HT_WEOF 000026 /* write tape mark */
#define HT_SFORW 000030 /* space forward */
#define HT_SREV 000032 /* space reverse */
#define HT_WCOM 000060 /* write forward */
#define HT_RCOM 000070 /* read forward */
/* htds */
#define HTDS_ATA 0100000 /* attention active */
#define HTDS_ERR 0040000 /* composite error */
#define HTDS_MOL 0010000 /* medium on line */
#define HTDS_WRL 0004000 /* write lock */
#define HTDS_EOT 0002000 /* end of tape */
#define HTDS_DPR 0000400 /* drive present (always 1) */
#define HTDS_DRY 0000200 /* drive ready */
#define HTDS_SSC 0000100 /* slace status changed */
#define HTDS_PES 0000040 /* phase-encoded status */
#define HTDS_TM 0000004 /* tape mark */
#define HTDS_BOT 0000002 /* beginning of tape */
#define HTDS_SLA 0000001 /* slave attention */
/* hter */
#define HTER_CORCRC 0100000 /* correctible data or ecc */
#define HTER_UNS 0040000 /* unsafe */
#define HTER_OPI 0020000 /* operation incomplete */
#define HTER_DTE 0010000 /* drive timing error */
#define HTER_NEF 0004000 /* non-executable function */
#define HTER_CSITM 0002000 /* correctable skew/illegal tape mark */
#define HTER_FCE 0001000 /* frame count error */
#define HTER_NSG 0000400 /* non-standard gap */
#define HTER_PEFLRC 0000200 /* format error or lrc error */
#define HTER_DPAR 0000040 /* data parity error */
#define HTER_FMT 0000020 /* format error */
#define HTER_CPAR 0000010 /* control bus parity error */
#define HTER_RMR 0000004 /* register modification refused */
#define HTER_ILR 0000002 /* illegal register */
#define HTER_ILF 0000001 /* illegal function */
#define HTER_HARD \
(HTER_UNS|HTER_OPI|HTER_NEF|HTER_DPAR|HTER_FMT|HTER_CPAR| \
HTER_RMR|HTER_ILR|HTER_ILF)
/* htdt */
#define HTDT_SPR 0002000 /* slave present */
/* httc */
#define HTTC_800BPI 0001400 /* in bits 8-10, dens=1600 */
#define HTTC_1600BPI 0002000 /* in bits 8-10, dens=800 */
#define HTTC_PDP11 0000300 /* in bits 4-7, pdp11 normal format */
/*
* system stuff
*/
int te16open(), te16close(), te16read(), te16write(), te16ioctl(), te16strategy();
struct cdevsw te16cdev = cdinit(te16open, te16close, te16read, te16write, te16ioctl);
struct bdevsw te16bdev = bdinit(te16open, te16close, te16strategy, B_TAPE);
/*
* per-formatter stuff
*/
extern int tm03cnt;
extern struct tm03 tm03[];
extern struct mbaddr tm03addr[];
#define T_BUSY 01 /* controller busy */
#define NOUNIT 0377 /* no unit expected */
/*
* per-drive stuff
*/
extern int te16cnt;
extern struct te16 te16[];
extern struct subaddr te16addr[];
extern struct buf rte16buf[];
extern struct buf cte16buf[];
/*
* bits in minor device
*/
#define TEUNIT(dev) (minor(dev)&03)
#define H_NOREWIND 04
#define H_1600BPI 010
/*
* sc_flags
*/
#define H_BUSY 01 /* software drive busy */
#define H_CMD 02 /* hardware command pending */
#define H_WRITTEN 04 /* last operation was a write */
#define H_ERASED 010 /* last write retry was an erase gap */
#define H_OPEN 020 /* device open */
#define H_OFFLINE 040 /* device went offline; no io till close */
#define H_WONLY 0100 /* write-only */
/*
* scaling for block magtape blocks
*/
#define INF 1000000L /* a block number that won't exist */
#define TBLOCK 2 /* tape blocks per b_blkno block */
#define TBSIZE 1024
#define TBSHIFT 10
/*
* overloads in struct buf; for te16cmd
*/
#define b_repcnt b_bcount
#define b_command b_resid
te16open(dev, flag)
dev_t dev;
int flag;
{
register int teunit;
register struct te16 *sc;
register struct subaddr *sa;
register struct device *htaddr;
register int dt;
teunit = TEUNIT(dev);
if (teunit >= te16cnt || (sc = &te16[teunit])->sc_flags & H_OPEN) {
u.u_error = ENXIO;
return;
}
sc->sc_flags |= H_OPEN;
sa = &te16addr[teunit];
if (tm03init(sa->ctl, sa->unit, teunit) == 0) {
sc->sc_flags &=~ H_OPEN;
u.u_error = ENXIO;
return;
}
sc->sc_ctl = &tm03[sa->ctl];
htaddr = sc->sc_ctl->cc_addr;
sc->sc_dens = ((minor(dev)&H_1600BPI)?HTTC_1600BPI:HTTC_800BPI)|HTTC_PDP11|sa->unit;
spl5();
htaddr->httc = sc->sc_dens;
/* delay here? */
dt = htaddr->htdt;
sc->sc_dsreg = htaddr->htds;
spl0();
if ((dt & HTDT_SPR) == 0) {
printf("te16 %d absent\n", teunit);
sc->sc_flags &=~ H_OPEN;
u.u_error = ENODEV;
return;
}
if ((sc->sc_dsreg & HTDS_MOL) == 0) {
sc->sc_flags &=~ H_OPEN;
u.u_error = EIO;
return;
}
if ((flag&FWRITE) && (sc->sc_dsreg&HTDS_WRL)) {
sc->sc_flags &=~ H_OPEN;
u.u_error = EIO;
return;
}
if ((sc->sc_dsreg & HTDS_BOT) == 0 && flag&FWRITE
&& ((minor(dev) & H_1600BPI) != 0) != ((sc->sc_dsreg & HTDS_PES) != 0)) {
sc->sc_flags &=~ H_OPEN;
u.u_error = EIO;
return;
}
sc->sc_blkno = 0;
sc->sc_nxrec = INF;
if ((flag & (FREAD|FWRITE)) == FWRITE)
sc->sc_flags |= H_WONLY; /* stop block dev readahead */
}
tm03init(ctl, slave, dev)
register int ctl;
int slave, dev;
{
register struct tm03 *cc;
register int i;
if (ctl < 0 || slave < 0 || ctl >= tm03cnt || slave >= TM03DRIVES)
return (0);
cc = &tm03[ctl];
cc->cc_drives[slave] = dev;
if (cc->cc_addr)
return (1);
if ((cc->cc_addr = (struct device *)mbaddr(&tm03addr[ctl])) == 0
|| badaddr(&cc->cc_addr->htcs1, sizeof(long))
|| (cc->cc_addr->htds & HTDS_DPR) == 0) {
printf("tm03 %d absent\n", ctl);
return (0);
}
cc->cc_mbaddr = &tm03addr[ctl];
for (i = 0; i < TM03DRIVES; i++)
if (i != slave)
cc->cc_drives[i] = NOUNIT;
/* any reset worth doing? */
return (1);
}
te16close(dev)
register dev_t dev;
{
register struct te16 *sc = &te16[TEUNIT(dev)];
if (sc->sc_flags & (H_WONLY|H_WRITTEN)) {
te16cmd(dev, HT_WEOF, 1);
te16cmd(dev, HT_WEOF, 1);
te16cmd(dev, HT_SREV, 1);
}
if ((minor(dev)&H_NOREWIND) == 0)
te16cmd(dev, HT_REW, 0);
sc->sc_flags &= H_CMD|H_BUSY;
}
te16cmd(dev, com, count)
dev_t dev;
int com, count;
{
register struct buf *bp;
bp = &cte16buf[TEUNIT(dev)];
(void) spl5();
while (bp->b_flags&B_BUSY) {
if(bp->b_repcnt == 0 && (bp->b_flags&B_DONE))
break;
bp->b_flags |= B_WANTED;
sleep((caddr_t)bp, PRIBIO);
}
bp->b_flags = B_BUSY|B_READ;
(void) spl0();
bp->b_dev = dev;
bp->b_command = com;
bp->b_repcnt = count;
bp->b_blkno = 0;
te16strategy(bp);
if (count == 0) {
if (bp->b_flags & B_DONE)
geterror(bp);
return;
}
iowait(bp);
if (bp->b_flags&B_WANTED)
wakeup((caddr_t)bp);
bp->b_flags &=~ B_BUSY;
}
te16strategy(bp)
register struct buf *bp;
{
register struct te16 *sc;
sc = &te16[TEUNIT(bp->b_dev)];
bp->av_forw = NULL;
(void) spl5();
if (sc->sc_actf == NULL)
sc->sc_actf = bp;
else
sc->sc_actl->av_forw = bp;
sc->sc_actl = bp;
if ((sc->sc_flags & H_BUSY) == 0)
te16start(sc);
if (sc->sc_ctl->cc_actf && (sc->sc_ctl->cc_flags & T_BUSY) == 0)
tm03start(sc->sc_ctl);
(void) spl0();
}
te16start(sc)
register struct te16 *sc;
{
register struct buf *bp;
register struct device *htaddr;
register struct tm03 *cc;
register daddr_t bno;
cc = sc->sc_ctl;
loop:
if ((bp = sc->sc_actf) == 0)
return;
htaddr = cc->cc_addr;
htaddr->httc = sc->sc_dens;
if (htaddr->htds & HTDS_SSC)
htaddr->htcs1 = HT_DCLR|HT_GO;
sc->sc_dsreg = htaddr->htds;
sc->sc_erreg = htaddr->hter;
sc->sc_resid = htaddr->htfc;
sc->sc_flags &= ~H_WRITTEN;
if ((htaddr->htdt & HTDT_SPR) == 0 || (htaddr->htds & HTDS_MOL) == 0)
if (sc->sc_flags & H_OPEN)
sc->sc_flags |= H_OFFLINE;
if (sc->sc_flags & H_OFFLINE) {
bp->b_flags |= B_ERROR;
goto done;
}
/*
* command, start it
*/
if (bp == &cte16buf[TEUNIT(bp->b_dev)]) {
sc->sc_flags |= H_BUSY|H_CMD;
htaddr->htfc = -bp->b_bcount;
htaddr->htcs1 = bp->b_command|HT_GO;
return;
}
/*
* transfer, see if position needed (wretched block magtape)
*/
bno = bp->b_blkno/TBLOCK;
if (bno > sc->sc_nxrec) {
bp->b_error = ENXIO;
bp->b_flags |= B_ERROR;
goto done;
}
if (bp->b_flags & B_READ /* at EOF, or really just writing? */
&& (bno == sc->sc_nxrec || sc->sc_flags & H_WONLY)) {
bp->b_resid = bp->b_bcount;
goto done;
}
if ((bp->b_flags&B_READ)==0)
sc->sc_nxrec = bno + 1;
if (sc->sc_blkno != bno) { /* need position */
sc->sc_flags |= H_BUSY|H_CMD;
if (sc->sc_blkno < bno) {
htaddr->htfc = sc->sc_blkno - bno;
htaddr->htcs1 = HT_SFORW|HT_GO;
} else {
htaddr->htfc = bno - sc->sc_blkno;
htaddr->htcs1 = HT_SREV|HT_GO;
}
return;
}
/*
* special pleading for `erase gap' write error recovery
* -- is this in the right place? why bother?
*/
if ((bp->b_flags&B_READ) == 0) {
if (cc->cc_errcnt) {
if ((sc->sc_flags & H_ERASED) == 0) {
sc->sc_flags |= H_ERASED|H_BUSY|H_CMD;
htaddr->htcs1 = HT_ERASE|HT_GO;
return;
}
sc->sc_flags &= ~H_ERASED;
}
if (htaddr->htds & HTDS_EOT) { /* oops */
bp->b_resid = bp->b_bcount;
done:
sc->sc_actf = bp->av_forw;
iodone(bp);
goto loop;
}
}
/*
* time for a transfer
*/
cc = sc->sc_ctl;
sc->sc_next = NULL;
if (cc->cc_actf == NULL)
cc->cc_actf = sc;
else
cc->cc_actl->sc_next = sc;
cc->cc_actl = sc;
sc->sc_flags |= H_BUSY;
}
tm03start(cc)
register struct tm03 *cc;
{
register struct te16 *sc;
int tm03xfer();
if ((sc = cc->cc_actf) == NULL)
return;
if (sc->sc_actf == NULL)
panic("tm03start");
cc->cc_flags |= T_BUSY;
mbstart(cc->cc_mbaddr, sc->sc_actf, tm03xfer);
}
tm03xfer(bp)
register struct buf *bp;
{
register struct te16 *sc;
register struct device *htaddr;
sc = &te16[TEUNIT(bp->b_dev)];
htaddr = sc->sc_ctl->cc_addr;
htaddr->httc = sc->sc_dens;
htaddr->htfc = -bp->b_bcount;
sc->sc_flags |= H_CMD;
if (bp->b_flags & B_READ)
htaddr->htcs1 = HT_RCOM|HT_GO;
else
htaddr->htcs1 = HT_WCOM|HT_GO;
}
/*
* massbus interrupt
* if a transfer was pending, assume it finished;
* if attn set, something else finished too
*
* the loop to see which drives wanted attention is somewhat doubtful
* and has not been tested with multiple drives
*/
tm030int(ctl, mbsr, mbbc, attn)
int ctl, mbsr, mbbc, attn;
{
register struct tm03 *cc;
register struct device *htaddr;
register struct te16 *sc;
register int i;
cc = &tm03[ctl];
if ((htaddr = cc->cc_addr) == NULL) {
printf("tm03 %d: stray intr\n", ctl);
return;
}
htaddr->htas = attn; /* before we do anything */
if (cc->cc_flags & T_BUSY)
te16dtint(cc, mbsr);
if (attn) {
for (i = 0; i < TM03DRIVES; i++) {
if (cc->cc_drives[i] == NOUNIT)
continue;
sc = &te16[cc->cc_drives[i]];
htaddr->httc = sc->sc_dens; /* select drive */
if ((sc->sc_flags & H_CMD) == 0) {
sc->sc_dsreg = htaddr->htds; /* for BOT detection */
wakeup((caddr_t)sc);
if (htaddr->htds & HTDS_SSC)
htaddr->htcs1 = HT_DCLR|HT_GO;
continue;
}
if ((htaddr->htds & HTDS_DRY) == 0)
continue;
te16attn(sc);
}
}
if (cc->cc_actf)
tm03start(cc);
}
/*
* error recovery is somewhat doubtful
* particular specialties:
* - short reads show up as frame count errors,
* so ignore them for the raw device
* - reading at tape mark seems to produce a frame count error too
* - quietly ignore CRC errors the drive corrected
*/
te16dtint(cc, mbsr)
register struct tm03 *cc;
int mbsr;
{
register struct device *htaddr;
register struct buf *bp;
register struct te16 *sc;
int ds, er, mbs;
htaddr = cc->cc_addr;
if ((sc = cc->cc_actf) == NULL)
panic("te16dtint");
sc->sc_flags &=~ H_CMD;
bp = sc->sc_actf;
ds = sc->sc_dsreg = htaddr->htds;
er = sc->sc_erreg = htaddr->hter;
sc->sc_resid = htaddr->htfc;
#if BABBLE
printf("dt: ds%o er%o fc%o mbs%o\n", ds, er, sc->sc_resid, mbsr);
printf("bp: bn%d bc%d fl%o\n", bp->b_blkno, bp->b_bcount, bp->b_flags);
#endif
mbs = mbsr;
if (er)
htaddr->htcs1 = HT_DCLR|HT_GO;
sc->sc_blkno++;
if((bp->b_flags & B_READ) == 0)
sc->sc_flags |= H_WRITTEN;
if (bp == &rte16buf[TEUNIT(bp->b_dev)] || ds & HTDS_TM) {
if (er & HTER_FCE) {
er &=~ HTER_FCE;
mbs &=~ (MBSR_DTABT|MBSR_MBEXC);
}
}
if (er & HTER_HARD || (ds & HTDS_MOL) == 0 || mbs & MBSR_EBITS)
bp->b_flags |= B_ERROR;
else if (er) { /* soft error */
if (++cc->cc_errcnt >= 7)
bp->b_flags |= B_ERROR;
else
cc->cc_flags &=~ T_BUSY;
}
if (bp->b_flags & B_ERROR)
printf("te16 %d: hard error bn%d mbsr=%o er=%o ds=%o\n",
TEUNIT(bp->b_dev), bp->b_blkno/TBLOCK, mbsr, sc->sc_erreg, sc->sc_dsreg);
if ((bp->b_flags & B_READ) == 0)
bp->b_resid = -sc->sc_resid;
else {
if (ds & HTDS_TM) {
bp->b_resid = bp->b_bcount;
sc->sc_nxrec = bp->b_blkno/TBLOCK;
} else if (sc->sc_resid > bp->b_bcount) {
bp->b_flags |= B_ERROR;
bp->b_error = ENOMEM;
} else
bp->b_resid = bp->b_bcount - sc->sc_resid;
}
if (cc->cc_flags & T_BUSY) { /* we decided we were finished */
sc->sc_actf = bp->av_forw;
iodone(bp);
cc->cc_actf = sc->sc_next;
cc->cc_errcnt = 0;
cc->cc_flags &=~ T_BUSY;
}
sc->sc_flags &=~ H_BUSY;
if (sc->sc_actf)
te16start(sc);
}
te16attn(sc)
register struct te16 *sc;
{
register struct device *htaddr;
register struct buf *bp;
register struct tm03 *cc;
register int er, ds;
daddr_t bno;
sc->sc_flags &=~ H_CMD;
cc = sc->sc_ctl;
htaddr = cc->cc_addr;
ds = sc->sc_dsreg = htaddr->htds;
er = sc->sc_erreg = htaddr->hter;
sc->sc_resid = htaddr->htfc;
if (er)
htaddr->htcs1 = HT_DCLR|HT_GO;
wakeup((caddr_t)sc);
if ((bp = sc->sc_actf) == 0)
return;
#if BABBLE
printf("at: ds%o er%o fc%o\n", sc->sc_dsreg, sc->sc_erreg, sc->sc_resid);
printf("bp: bn%d bc%d fl%o cm%o\n", bp->b_blkno, bp->b_bcount, bp->b_flags, bp->b_command);
#endif
bno = bp->b_blkno/TBLOCK;
if (bp == &cte16buf[TEUNIT(bp->b_dev)]) {
switch ((int)bp->b_command) {
case HT_REWOFFL:
/* offline is on purpose; don't do anything special */
ds |= HTDS_MOL;
break;
case HT_SREV:
/* if backspace file hit bot, its not an error */
if (er == (HTER_NEF|HTER_FCE) && ds&HTDS_BOT
&& bp->b_repcnt == INF)
er &= ~HTER_NEF;
break;
}
er &= ~HTER_FCE;
if (er == 0)
ds &= ~HTDS_ERR;
}
if ((ds & (HTDS_ERR|HTDS_MOL)) != HTDS_MOL) {
if ((ds & HTDS_MOL) == 0 && sc->sc_flags & H_OPEN)
sc->sc_flags |= H_OFFLINE;
printf("te16 %d: hard error bn%d er%o ds%o\n",
TEUNIT(bp->b_dev), bno, sc->sc_erreg, sc->sc_dsreg);
bp->b_flags |= B_ERROR;
}
if (bp == &cte16buf[TEUNIT(bp->b_dev)])
bp->b_resid = -sc->sc_resid;
else
sc->sc_flags &=~ H_BUSY; /* done seeking, now xfer */
if ((ds & HTDS_TM) == 0)
sc->sc_blkno = bno;
else if (sc->sc_blkno > bno) {
sc->sc_nxrec = bno - sc->sc_resid;
sc->sc_blkno = sc->sc_nxrec;
} else {
sc->sc_blkno = bno + sc->sc_resid;
sc->sc_nxrec = sc->sc_blkno - 1;
}
if (sc->sc_flags & H_BUSY) {
sc->sc_actf = bp->av_forw;
iodone(bp);
sc->sc_flags &=~ H_BUSY;
}
if (sc->sc_actf)
te16start(sc);
}
te16read(dev)
dev_t dev;
{
te16phys(dev);
physio(te16strategy, &rte16buf[TEUNIT(dev)], dev, B_READ, minphys);
}
te16write(dev)
dev_t dev;
{
te16phys(dev);
physio(te16strategy, &rte16buf[TEUNIT(dev)], dev, B_WRITE, minphys);
}
te16phys(dev)
dev_t dev;
{
register struct te16 *sc;
sc = &te16[TEUNIT(dev)];
sc->sc_blkno = Lshift(u.u_offset, TBSHIFT);
sc->sc_nxrec = sc->sc_blkno + 1;
}
/*ARGSUSED*/
te16ioctl(dev, cmd, addr, flag)
dev_t dev;
int cmd;
caddr_t addr;
int flag;
{
register struct te16 *sc = &te16[TEUNIT(dev)];
register struct buf *bp = &cte16buf[TEUNIT(dev)];
register callcount;
int fcount, htcmd;
struct mtop mtop;
switch (cmd) {
case MTIOCTOP:
if (copyin((caddr_t)addr, (caddr_t)&mtop, sizeof(mtop))) {
u.u_error = EFAULT;
return;
}
if (mtop.mt_count <= 0) {
u.u_error = ENXIO;
return;
}
switch(mtop.mt_op) {
case MTWEOF:
htcmd = HT_WEOF;
callcount = mtop.mt_count;
fcount = 1;
break;
case MTFSF: case MTBSF:
htcmd = (mtop.mt_op == MTFSF) ? HT_SFORW : HT_SREV;
callcount = mtop.mt_count;
fcount = INF;
break;
case MTFSR: case MTBSR:
htcmd = (mtop.mt_op == MTFSF) ? HT_SFORW : HT_SREV;
callcount = 1;
fcount = mtop.mt_count;
break;
case MTREW: case MTOFFL:
te16cmd(dev, HT_REW, 1);
if (mtop.mt_op == MTREW)
return;
spl5();
while ((sc->sc_dsreg & HTDS_BOT) == 0)
sleep((caddr_t)sc, PZERO);
spl0();
te16cmd(dev, HT_REWOFFL, 0);
return;
default:
u.u_error = ENXIO;
return;
}
while (--callcount >= 0) {
te16cmd(dev, htcmd, fcount);
if (bp->b_resid
&& (mtop.mt_op == MTFSR || mtop.mt_op == MTBSR)) {
u.u_error = EIO;
break;
}
if (u.u_error || sc->sc_dsreg&HTDS_BOT)
break;
}
return;
default:
u.u_error = ENXIO;
return;
}
}