V7M/sys/dev/ts.c
/*
* UNIX/v7m TS11 - 1600 BPI tape driver
*
* Fred Canter 5/5/81
*
* Thanks to Jerry Brenner for most of this driver.
*
*******************************************************************
* *
* The TM11 and TS11 share the standard address (0172520) and *
* the standard vector (0224). This driver allows for the *
* coexistence of the TM11 and TS11 on the same system, by using *
* the minor device number to modify the TS11 CSR address. *
* The minor device number is added to the base address of 0172520 *
* to form the actual TS11 address. If the TS11 is the only tape *
* on the system, it's hardware address is set to 0172520 and the *
* minor device number is zero. If the system also has a TM11 tape *
* tape, the TS11 hardware address is set to 0172550 and the minor *
* device number is set to six. The minor device number is set by *
* the makefile in /dev, i.e., "make ts0" for the TS11 at 0172520 *
* or "make ts6" for the TS11 at 0172550. *
* The same technigue is usde to boot the TS11 using the M9312 *
* hardware bootstrap, i.e., "MS0" for TS11 at 0172520 and "MS6" *
* for TS11 at 0172550. *
* *
*******************************************************************
*/
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/file.h"
#include "../h/user.h"
int cmdpkt[5]; /* command packet. extra location is mod 4 alignment */
struct device
{
int tsdb; /* TS data buffer and bus address reg */
int tssr; /* TS status register */
};
struct mespkt
{
int mshdr; /* message packet header word */
int mssiz; /* size of message returned */
int msresid; /* remaining count register */
int mstsx0; /* extended status reg 0 */
int mstsx1; /* extended status reg 1 */
int mstsx2; /* extneded status reg 2 */
int mstsx3; /* extended status reg 3 */
};
struct compkt
{
int tscom; /* command packet command word */
int tsba; /* memory address */
int tsbae; /* extended address */
int tswc; /* byte count, record count, etc. */
};
struct chrdat
{
int msbptr; /* pointer to message buffer */
int msbae; /* hi address (bits 16 & 17) */
int msbsiz; /* size of message buffer */
int mschar; /* characteristics word */
};
struct chrdat chrbuf; /* characteristics buffer */
struct mespkt mesbuf; /* message buffer */
struct buf tstab;
struct buf ctsbuf;
struct buf rtsbuf;
char ts_flags;
int ts_openf;
int ts_stray;
daddr_t ts_blkno;
daddr_t ts_nxrec;
/*
* DO NOT !!!!, change the following TS11 address assignment
* without first reading the comments at the head of this driver.
* If the TS11 is at address 0172520 or 0172550, then the
* TSADDR definition should NOT be changed.
* If the TS11 is at some other address, then set the TSADDR
* to that address and make sure that the minor device
* number is set to zero (/dev/makefile "make ts0").
*/
#define TSADDR ((struct device *)0172520)
/* The following 3 lines are necessary to align com buffer on mod 4 */
#define t_temp (&cmdpkt[1]) /* get address of cmdpkt +2 */
#define t_temp1 (t_temp & 0177776)
#define combuf ((struct compkt *)t_temp1)
/* bit definitions for command word in ts command packet */
#define ACK 0100000 /* acknowledge bit */
#define CVC 040000 /* clear volume check */
#define OPP 020000 /* opposite. reverse recovery */
#define SWB 010000 /* swap bytes. for data xfer */
/* bit definitions for Command mode field during read command */
#define RNEXT 0 /* read next (forward) */
#define RPREV 0400 /* read previous (reverse) */
#define RRPRV 01000 /* reread previous (space rev, read two) */
#define RRNXT 01400 /* reread next (space fwd, read rev) */
/* bit definitions for Command mode field during write command */
#define WNEXT 0 /* Write data next */
#define WDRTY 01000 /* write data retry , space rev, erase, write data) */
/* bit definitions for command mode field during position command */
#define SPCFWD 0 /* space records forward */
#define SPCREV 0400 /* space records reverse */
#define SKTPF 01000 /* skip tape marks forward */
#define SKTPR 01400 /* skip tape marks reverse */
#define RWIND 02000 /* rewind */
/* bit definitions for command mode field during format command */
#define WEOF 0 /* write tape mark */
#define ERAS 0400 /* erase */
#define WEOFE 01000 /* write tape mark entry */
/* bit definitions for command mode field during control command */
#define MBREAL 0 /* message buffer release */
#define REWUNL 0400 /* Rewind and unload */
#define CLEAN 01000 /* clean */
/* additional definitions */
#define IEI 0200 /* interrupt enable bit */
/* command code definitions */
#define NOP 0
#define RCOM 01 /* read command */
#define WCHAR 04 /* write characteristics */
#define WCOM 05 /* write */
#define WSUSM 06 /* write subsystem memory */
#define POSIT 010 /* position command */
#define FORMT 011 /* Format command */
#define CONTRL 012 /* Control command */
#define INIT 013 /* initialize */
#define GSTAT 017 /* get status immediate */
/* definition of tssr bits */
#define SC 0100000 /* special condition */
#define UPE 040000 /* unibus parity error */
#define SPE 020000 /* Serial bus parity error */
#define RMR 010000 /* register modify refused */
#define NXM 04000 /* non-existent memory */
#define NBA 02000 /* Need Buffer address */
#define SSR 0200 /* Sub-System ready */
#define OFL 0100 /* off-line */
/* fatal termination class codes */
#define FTC 030 /* use this as a mask to get codes */
/* code = 00 see error code byte in tsx3 */
/* code = 01 I/O seq Crom or main Crom parity error */
/* code = 10 u-processor Crom parity error,I/O silo parity */
/* serial bus parity, or other fatal */
/* code = 11 A/C low. drive ac low */
/* termination class codes */
#define TCC 016 /* mask for termination class codes */
/* code = 000 normal termination */
/* code = 001 Attention condition
/* code = 010 Tape status alert
/* code = 011 Function reject
/* code = 100 Recoverable error - tape pos = 1 record down from
start of function
/* code = 101 Recoverable error - tape has not moved
/* code = 110 Unrecoverable error - tape position lost
/* code = 111 Fatal controller error - see fatal class bits */
/* definition of message buffer header word */
#define MAKC 0100000 /* acknowledge from controller */
#define MCCF 07400 /* mask for class code field */
/* class codes are */
/* 0 = ATTN, on or ofline
1 = ATTN, microdiagnostic failure
0 = FAIL, serial bus parity error
1 = FAIL, WRT LOCK
2 = FAIL, interlock or non-executable function
3 = FAIL, microdiagnostic error
*/
#define MMSC 037 /* mask for message code field */
/* message codes are
020 = end
021 = FAIL
022 = ERROR
023 = Attention
/* definition of extended status reg 0 bits */
#define TMK 0100000 /* Tape mark detected */
#define RLS 040000 /* Record length short */
#define LET 020000 /* Logical end of Tape */
#define RLL 010000 /* Record length long */
#define WLE 04000 /* Write lock error */
#define NEF 02000 /* Non-executable function */
#define ILC 01000 /* Illegal command */
#define ILA 0400 /* Illegal address */
#define MOT 0200 /* Capistan is moving */
#define ONL 0100 /* On Line */
#define IE 040 /* state of interrupt enable bit */
#define VCK 020 /* Volume Check */
#define PED 010 /* Phase encoded drive */
#define WLK 04 /* Write locked */
#define BOT 02 /* Tape at bot */
#define EOT 01 /* Tape at eot */
/* definitions of xstat1 */
#define DLT 0100000 /* Data late error */
#define COR 020000 /* Correctable data error */
#define CRS 010000 /* Crease detected */
#define TIG 04000 /* Trash in the gap */
#define DBF 02000 /* Deskew Buffer Fail */
#define SCK 01000 /* Speed check */
#define IPR 0200 /* Invalid preamble */
#define SYN 0100 /* Synch Failure */
#define IPO 040 /* invalid postamble */
#define IED 020 /* invalid end data */
#define POS 010 /* postamble short */
#define POL 04 /* postamble long */
#define UNC 02 /* Uncorrectable data error */
#define MTE 01 /* multi track error */
/* Definitions of XSTAT2 bits */
#define OPM 0100000 /* operation in progress (tape moving) */
#define SIP 040000 /* Silo Parity error */
#define BPE 020000 /* Serial bus Parity error at drive */
#define CAF 010000 /* Capstan Acceleration fail */
#define WCF 02000 /* Write card error */
#define DTP 0477 /* mask for Dead track bits */
/* bit definitions for XSTAT3 */
#define LMX 0200 /* Limit exceeded (tension arms) */
#define OPI 0100 /* operation incomplete */
#define REV 040 /* current operation in reverse */
#define CRF 020 /* capstan response fail */
#define DCK 010 /* density check */
#define NOI 04 /* no tape mark or preamble */
#define LXS 02 /* limit exceeded manual recovery */
#define RIB 01 /* Reverse into BOT */
#define SIO 01
#define SSEEK 02
#define SINIT 04
#define SRETRY 010
#define SCOM 020
#define SOK 040
#define SERROR 0100
#define SREWND 0200
#define S_WRITTEN 1
tsopen(dev, flag)
{
register int ds;
register struct device *tsadr;
/* if unit > 7 or if already open
then flag an error and return */
tsadr = TSADDR + (dev&07);
if (((minor(dev) & 0177) > 7) || ts_openf > 0)
{
u.u_error = ENXIO;
return;
}
if(tstab.b_active & SREWND)
{ /* this is a delay loop to allow rewind completion */
/* it allows for 2400 feet of tape plus a fudge factor */
/******** this loop assumes lbolt every 4 seconds *******/
for(ds = 0; ds < 125; ds++)
{
if(tsadr->tssr & SSR)
{
ds = 0; /* if ready then break */
break;
}
else
sleep(&lbolt, PZERO +1);
}
if(ds)
{
u.u_error = ENXIO; /* timeout. byebye */
return;
}
else
tstab.b_active = 0; /* rewind complete */
}
/* call init. if init returns 1 then failure.
flag error and return */
if(tsinit(tsadr, 0))
{
u.u_error = ENXIO;
return;
}
/* some house keeping here */
ts_blkno = 0; /* set current block # to 0 */
ts_nxrec = 1000000; /* set max accessable block # */
ts_flags = 0; /* clear status flags */
tstab.b_flags |= B_TAPE; /* Say we are a tape so no write
ahead */
ds = tscommand(dev, NOP); /* Get drive status */
/* if drive is off-line or open for write(read/write)
and write locked then fatal error */
if(tsadr->tssr & OFL || flag && (ds&WLK))
{
printf("Magtape %o offline or write locked\n",minor(dev)&0177);
u.u_error = ENXIO;
}
if(u.u_error == 0) /* if no error then say opened */
ts_openf = 1;
else
ts_openf = 0;
}
tsclose(dev, flag)
{
if(tstab.b_active & SREWND)
{
ts_openf = 0; /* say closed */
return;
}
/* if opened for write or read/write and was
written then write two tape marks
and back up one */
if(flag == FWRITE || ((flag & FWRITE) && (ts_flags & S_WRITTEN)))
{
tscommand(dev, (FORMT|WEOF));
tscommand(dev, (FORMT|WEOF));
tscommand(dev, (SPCREV|POSIT));
}
/* if the no-rewind bit in minor is clear then rewind */
if ((minor(dev)&0200) ==0 )
{
tscommand(dev, (RWIND|POSIT));
}
ts_openf = 0; /* say closed */
}
tscommand(dev, com)
{
register struct buf *bp;
bp = &ctsbuf; /* set buffer pointer to command table */
spl5(); /* grab some priority for this */
while(bp->b_flags&B_BUSY) /* if command table is busy */
{
bp->b_flags |= B_WANTED; /* then say we want it */
sleep((caddr_t)bp, PRIBIO); /* and sleep around */
}
spl0(); /* have the buffer so drop pri */
bp->b_dev = dev; /* associate with this device */
bp->b_resid = com; /* load desired command here */
bp->b_blkno = 0; /* don't need a block number */
bp->b_flags = B_BUSY|B_READ; /* mark command buffer busy */
tsstrategy(bp); /* do it */
iowait(bp); /* wait until it's done */
if(bp->b_flags&B_WANTED) /* if someone (me) wants it */
wakeup((caddr_t)bp); /* then say he can have it */
bp->b_flags = 0; /* say command buffer avail */
return(bp->b_resid); /* return drive status */
}
tsstrategy(bp)
{
register daddr_t *p;
if(bp->b_flags & B_PHYS) /* if RAW I/O call unibus map allocate */
mapalloc(bp);
if(bp != &ctsbuf) /* if not command buffer */
{
if(ts_openf < 0)
{
bp->b_flags |= B_ERROR; /* mark buffer bad */
iodone(bp);
return;
}
p = &ts_nxrec; /* get pointer to max accessable
block number */
if(bp->b_blkno > *p) /* if requested > max access */
{
bp->b_flags |= B_ERROR; /* thats a no-no */
bp->b_error = ENXIO; /* make sure it's known */
iodone(bp); /* return buffer */
return; /* bye - bye */
}
if(bp->b_blkno == *p && bp->b_flags&B_READ)
{
/* if requested block = max accessable & read */
clrbuf(bp); /* zero the buffer */
bp->b_resid = bp->b_bcount; /* didn't read */
iodone(bp); /* return the buffer */
return; /* bye - bye */
}
if((bp->b_flags&B_READ) == 0) /* if a write command */
{
*p = bp->b_blkno +1; /* set max access to requested
+ 1 */
ts_flags |= S_WRITTEN; /* say it's been written */
}
}
bp->av_forw = 0; /* clear the available forward link */
spl5(); /* grab some priority for this */
if (tstab.b_actf == NULL) /* if no forward link on the table */
tstab.b_actf = bp; /* then set this buffer as next */
else
tstab.b_actl->av_forw = bp; /* else link this one to
the end */
tstab.b_actl = bp; /* set a reverse link in table */
if (tstab.b_active == 0) /* if not active then */
tsstart(); /* start some activity */
spl0();
}
tsstart()
{
register struct buf *bp;
register struct device *tsadr;
register daddr_t blkno;
int com;
loop:
if ((bp = tstab.b_actf) == NULL)
return; /* no link so return */
tsadr = TSADDR + (bp->b_dev & 07);
blkno = ts_blkno; /* get current block # */
if(tsadr->tssr & OFL)
{ /* driver off-line. bad news */
printf("\nMagtape %o offline\n",minor(bp->b_dev)&0177);
ts_openf = -1; /* say this is fatal */
bp->b_flags |= B_ERROR; /* mark buffer bad */
goto next; /* start the flush loop */
}
if (bp == &ctsbuf) /* if this is command table */
{
if(bp->b_resid == NOP) /* No-op means get status */
{
bp->b_resid = mesbuf.mstsx0; /* hope this is the latest */
goto next; /* try the next one */
}
tstab.b_active = SCOM; /* say just general maint command */
combuf->tswc = 1; /* we only do one */
combuf->tscom = (ACK|IEI|bp->b_resid); /* load command */
tsadr->tsdb = &combuf->tscom; /* feed driver packet */
return; /* later */
}
if(ts_openf < 0)
{
bp->b_error = ENXIO; /* set fatal flag */
bp->b_flags |= B_ERROR; /* mark buffer bad */
goto next; /* start the flush loop */
}
if(bp->b_blkno > ts_nxrec)
{
bp->b_flags |= B_ERROR; /* block # out of range */
goto next; /* start the flush loop */
}
if(blkno != bp->b_blkno) /* if requested != current */
{
tstab.b_active = SSEEK; /* say this is a seek */
if(blkno < bp->b_blkno) /* we're not there yet so */
{ /* space forward to it */
combuf->tscom = (ACK|IEI|SPCFWD|POSIT);
combuf->tsba = bp->b_blkno - blkno; /* load block */
}
else
{ /* need to backup */
combuf->tscom = (ACK|IEI|SPCREV|POSIT);
combuf->tsba = blkno - bp->b_blkno; /* load block */
}
tsadr->tsdb = &combuf->tscom; /* feed driver packet */
return; /* bye - bye */
}
tstab.b_active = SIO; /* say this is read or write */
combuf->tsba = bp->b_un.b_addr; /* get low order address */
combuf->tsbae = bp->b_xmem; /* set extended bits */
combuf->tswc = bp->b_bcount; /* grab a positive byte count */
combuf->tscom = (ACK|IEI); /* set up acknowledge and IEI */
if(bp->b_flags &B_READ)
combuf->tscom |= (RNEXT|RCOM); /* read next record */
else
combuf->tscom |= (WNEXT|WCOM); /* write next record */
tsadr->tsdb = &combuf->tscom; /* feed packet to drive */
return;
/* this loop is good for flushing buffers on fatal errors */
next:
tstab.b_actf = bp->av_forw; /* grab next avail forward */
iodone(bp); /* say current buffer done */
goto loop; /* try some more */
}
tsintr()
{
register struct buf *bp;
register int state;
register struct device *tsadr;
if((bp = tstab.b_actf) == 0) /* grab a buffer pointer */
{
ts_stray++; /* no pointer. stray interrupt */
return;
}
tsadr = TSADDR + (bp->b_dev & 07);
state = tstab.b_active; /* get a copy of status */
if(tsadr->tssr & SC) /* Special Condition Set ?? */
{
/* look at the Termination Class code */
switch ((tsadr->tssr & TCC) >> 1)
{
case 07: /* fatal sub-system error */
case 06: /* non-recoverable error */
state = 0; /* this means dead */
break; /* break out */
case 05: /* recoverable. no tape motion */
++tstab.b_errcnt;
state = SRETRY; /* do a straight retry */
break;
case 04: /* recoverable. tape position +1 */
if(++tstab.b_errcnt < 10)
{
if(bp->b_flags & B_READ)
/* re-read previous */
combuf->tscom =(ACK|IEI|RRPRV|RCOM);
else
/* write data retry */
combuf->tscom=(ACK|IEI|WDRTY|WCOM);
tsadr->tsdb = &combuf->tscom;
return;
}
else
break;
case 03: /* function reject */
/* if VCK then someone diddled
with the drive */
/* this is fatal */
state = 0;
break;
case 02: /* Tape Status Alert */
if(mesbuf.mstsx0 & TMK)
{ /* End of file set max to this blk */
ts_nxrec = bp->b_blkno;
state = SOK;
break;
}
else if(mesbuf.mstsx0 & EOT)
{ /* End of tape set max to this blk */
state = 0; /* this is fatal */
break;
}
else if(mesbuf.mstsx0&RLS)
{ /* record length is short. do not
have to treat this as fatal because
the actual byte will be returned */
state = SIO; /* update block cnt */
break;
}
else if(mesbuf.mstsx0&RLL)
{
state = SIO;
bp->b_flags |= B_ERROR;
break;
}
break;
case 01: /* Attention Condition */
if(tsadr->tssr & OFL)
{
printf("\nMagtape %o offline\n",
minor(bp->b_dev)&0177);
state = 0; /* drive offline */
}
break;
default:
break;
}
if(tstab.b_errcnt > 4 || state == 0)
{
deverror(bp, tsadr->tssr, mesbuf.mstsx0);
deverror(bp, mesbuf.mstsx1, mesbuf.mstsx2);
deverror(bp, mesbuf.mstsx3, tstab.b_errcnt);
}
if(tstab.b_errcnt >= 10 || state == 0)
{
/* bad news. FATAL ERROR or No recovery */
tstab.b_errcnt = 0; /* clear error count */
bp->b_flags |= B_ERROR; /* mark this buffer bad */
ts_openf = -1; /* say no more */
if(state)
tsinit(tsadr, 0); /* init the drive. no rewind*/
else
tsinit(tsadr, 1); /* init the drive. rewind */
state = 0; /* just to be sure */
}
}
if((tstab.b_active & SREWND) == 0)
tstab.b_active = 0; /* say ok to do more */
switch (state)
{
case SRETRY: /* straight retry */
break;
case SIO: /* update block # and */
case SOK: /* fall through SCOM */
ts_blkno++;
case SCOM:
tstab.b_errcnt = 0; /* no errors */
tstab.b_actf = bp->av_forw; /* next buffer */
if(combuf->tscom & POSIT) /* if position */
bp->b_resid = 0; /* this could be
garbage */
else
bp->b_resid = mesbuf.msresid; /* get residual
count */
iodone(bp); /* return buffer */
break;
case SSEEK:
ts_blkno = bp->b_blkno; /* update blk no */
break;
default:
break;
}
tsstart(); /* start any pending I/O */
}
tsinit(addr, ini)
struct device *addr;
int ini;
{
int ds, cnt;
register struct device *tsadr;
tsadr = addr;
if(ini)
{
tsadr->tssr = 0; /* init with rewind */
tstab.b_active = SREWND;
return(0);
}
else
{
combuf->tscom = (ACK|CVC|INIT); /* init no rewind */
tsadr->tsdb = &combuf->tscom; /* feed drive packet */
for(cnt = 0; cnt < 10000; cnt++)
{ /* wait loop. hopefully */
if(tsadr->tssr & SSR) /* 3 - 5 msecs long */
break; /* have ready. break out*/
}
}
if(cnt >= 10000)
{ /* wait loop timeout */
printf("TS11 init failed\n");
deverror(&ctsbuf, tsadr->tssr, mesbuf.mstsx0);
return(1); /* bad stuff. say so */
}
chrbuf.msbptr = &mesbuf; /* show where message
buffer is */
chrbuf.msbae = 0; /* hope no extended addr */
chrbuf.msbsiz = 016; /* size of message buf */
chrbuf.mschar = 0; /* set characteristics */
combuf->tscom = (ACK|CVC|WCHAR); /* say this is write
characteristics */
combuf->tsba = &chrbuf; /* addr of chars buf */
combuf->tsbae = 0; /* hope no ext addr */
combuf->tswc = 010; /* size of chars buf */
tsadr->tsdb = &combuf->tscom; /* feed packet to drive */
for(cnt = 0; cnt < 10000; cnt++)
{
if(tsadr->tssr & SSR)
break;
}
if(cnt >= 10000)
{
printf("TS11 init failed\n");
deverror(&ctsbuf, tsadr->tssr, mesbuf.mstsx0);
return(1); /* bad stuff. say so */
}
if(((tsadr->tssr & TCC) >>1) > 1)
{
printf("TS11 init failed\n");
deverror(&ctsbuf, tsadr->tssr, mesbuf.mstsx0);
return(1); /* bad stuff. say so */
}
else
return(0); /* good init */
}
tsread(dev)
{
tsphys(dev);
physio(tsstrategy, &rtsbuf, dev, B_READ);
}
tswrite(dev)
{
tsphys(dev);
physio(tsstrategy, &rtsbuf, dev, B_WRITE);
}
tsphys(dev)
{
daddr_t a;
if((minor(dev) & 0177) < 8) /* only if drive 0, */
/* minor device 0 thru 7 */
{
a = u.u_offset >>9; /* grab the block offset */
ts_blkno = a; /* set current block */
ts_nxrec = a+1; /* set next max block # */
}
}