#
/*  cm.c driver for minitape controller, character device */

#include "../head/param.h"
#include "../head/conf.h"
#include "../head/user.h"
#include "../head/userx.h"

/* commands for tape control system */

#define DINIT	0100	/* drive initialization */
#define UNLD	0105	/* unload cartridge -- wait for completion */
#define SNB	0111	/* set byte cnt n  -  all drives */
			/* sent as lob and hob each with even parity */
#define SMF	0114	/* set record cnt m for spacing -  all drives */
			/* sent as lob and hob each with even parity */
#define LDTS	0121	/* load drive and track select */
#define WRT	0124	/* write n bytes */
#define WTM	0130	/* write a tape mark */
#define WGP	0135	/* write an extended gap */
#define RD	0141	/* read n bytes or less */
#define RSW	0144	/* read status word */
#define SPFM	0150	/* space forward m records */
#define SPRM	0155	/* space reverse m records */
#define STMF	0160	/* space to tape mark forward */
#define STMR	0165	/* space to tape mark reverse */
#define RWD	0171	/* rewind to LPT  */
#define RCRC	0174	/* read crc character */
#define CLAF	0200	/* clear all flags  (status word) */

/* status word, first byte bits 0-6, 7 prog uses */

#define RDER	1	/* read error exists after 10 retries */
			/* or total failure in writing */
#define EW	2	/* early warning */
#define TMD	4	/* tape mark detected */
#define DPTM	010	/* n is smaller than record read, */
			/* or bytes transferred in write not equal to n */
#define WNWEN	020	/* write with protect on - attempted */
#define SPTD	040	/* space operation terminated at EOT or BOT */
#define NONL	0100	/* selected drive off line */
#define TERR	0200	/* timeout on request to controller */

/* status word, second byte bits 0-6, 7 prog uses */

#define TOR	0400	/* tape off reel */
#define EOT	01000	/* end of tape */
#define BOT	02000	/* begining of tape */
#define LPT	04000	/* load point */
#define NRDY	010000	/* execute while not ready */
#define ILCMD	020000	/* illegal cmd or wrong parity on cmd or its arg */
#define RWDG	040000	/* selected drive rewinding when set */
#define SERR	0100000	/*can't read or can't clear status */

#define INTAE	0100	/* data interrupt enable (A interrupt) */
#define INTBE	040	/* command interrupt enable (B interrupt) */
#define CSR0	1	/* control bit 0 to device, set = cmd */
#define CSRN	0	/* control bit 0 to device, clr = data */
#define	REQA	0200	/* request A */
#define	REQB	0100000	/* request B */

#define RAW	05
#define ILLEGAL	03	/* track adr overflow */
#define NUNIT	4
#define CMADDR	0167750	/* address of dr11-c */
#define FWRITE	02
 

struct {
	int cstat;	/* dr11-c offsets */
	int cout;
	int cin;
	};

int c_active;		/* controller busy flag*/

char	c_test[NUNIT];	/* < 04 = single track only */
char	c_open[NUNIT];
char	c_ldts[NUNIT] {020,040,0100,0200}; /* bits 4-7 = unit. 0,1 = track */
char	c_flgb[NUNIT];
int	c_stau[NUNIT];	/* drive status words */
int	c_mblkno[NUNIT];
int	c_drv;
char	c_flgc;
char	c_sleep;

		/* minors for drives 1,2,3,4 respectively */
		/* track 1 single file 0,8,16,24 */
		/* track 2 single file 1,9,17,25 */
		/* track 3 single file 2,10,18,26 */
		/* track 4 single file 3,11,19,27 */
		/* multi-track single file 4,12,20,28 */
		/* multi-track multi-file 5,13,21,29 */

		/* NOTE: The maximum permissible tranfer rate of data or */
		/* arguments to or from the cartridge controller is 100kc */

cmopen(dev, flag)
{
	register drv,drv1;

	drv1 = drv = dev.d_minor;
	drv = (drv >> 3) & 03;			/* physical drive */
	if (c_open[drv])
		u.u_error = ENXIO;		/* already in use */
	else {
		c_open[drv]++;			/* mark unit busy */
		c_mblkno[drv] = 0;		/* clr seek # */
		c_stau[drv] = 0;		/* clr status */
		c_flgb[drv] = 1;		/* initialize drive */
		c_test[drv] = drv1 =& 07;
		if (drv1 != RAW)		/*  if not RAW new track */
		{
			c_ldts[drv] =& 0360;
			c_ldts[drv] =| (drv1 & 03);
		}
	     }
}

cmclose(dev, flag)
{
	register int drv;

	drv = (dev.d_minor >> 3) &03;
	c_flgb[drv] = 3;			/* read close */
	if (flag & FWRITE) c_flgb[drv]--;		/* write close */
	cmstart(drv);
	c_open[drv] = 0;			/* free drive */
}


cmread(dev)

{
	register int drv;

	drv = (dev.d_minor >> 3) &03;		/* physical drive # */
	c_open[drv] = RD;
	cmstart(drv);
}

cmwrite(dev)
{
	register int drv;

	drv = (dev.d_minor >> 3) &03;		/* physical drive # */
	c_open[drv] = WRT;
	cmstart(drv);
}


cmstart(driv)
{
	extern char partab[];
	register int drv,b,c;

	drv = driv;				/* physical drive # */
	if(c_active)				/* sleep if in use */
	{
		spl5();
		c_sleep++;
		while(c_active) sleep(&c_sleep,-1);
		c_sleep--;
		spl0();
	}
	c_drv = drv;
	c_active++;				/* mark busy */
cwtrd:	if(cminit() < 0) goto cerror;
	switch(c_flgb[drv])
	{			/* initial & closing routines */

	case 2:	if((b = cmslp(WTM)) < 0) goto cerror;	/* write 1st tape mark */
		if(b & EOT)
		{
			if((c = cmrwd()) < 0) goto cerror;
		}
		if(cmslp(WTM) < 0) goto cerror;		/* write 2nd tape mark */
			if(b & EOT) goto cunlod;	/* only 2nd tm on track */

		if(cmslp(SPRM) < 0) goto cerror;	/* backspace 1 record */

	case 3:	if(c_test[drv] == RAW) goto cdone;
	cunlod:	if(cmslp(UNLD) < 0) goto cerror;	/* unload */
		goto cdone;


	case 1:	if (c_test[drv] == RAW)
		{
			if((c = cmstatus()) < 0) goto cerror;
			if (c & LPT)
			{
				c_ldts[drv] =& 0360;	/* track 0 */
				if(cminit() < 0) goto cerror;	/* chg track */
				goto cinit;
			}
			if((c & BOT) == 0) break;
		}

	cinit:	if(cmslp(DINIT) < 0) goto cerror;	/* initialize drive */

		if(cmslp(SMF) < 0) goto cerror;		/* spacing arg next */

		CMADDR->cstat = CSRN;
			CMADDR->cout = 0201;		/* set space for 1 record */
		cmdelay();
		CMADDR->cout = 0;			/* 2nd byte */
		if(cmwait(INTBE) < 0) goto cerror;

	}

		/* read & write operations */

	c_flgb[drv] = 0;
	if(((c = c_mblkno[drv]) != (b = lshift(u.u_offset,-9)))
	&& ((u.u_offset[1] & 0777) == 0))
	{
		if(cmseek(c,b) < 0) goto cerror;
		c_mblkno[drv] = b;
	}

	if(cmslp(SNB) < 0) goto cerror;		/* byte cnt args next */

	c = b = u.u_count;			/* positive byte cnt */
						/* cnt <10 or >2048 = error */
	b =& 0177;
	b =| (partab[b] & 0200);		/* add even parity */
	c = (c >> 7) & 0177;
	c =| (partab[c] & 0200);		/* add even parity */
	CMADDR->cstat = CSRN;
	CMADDR->cout = b;			/* send cnt low order byte */
	cmdelay();
	CMADDR->cout = c;			/* send cnt high order byte */
	if(cmwait(INTBE) < 0) goto cerror;

	if (c_open[drv] == WRT)			/* write */
	{
		if(cmslp(WRT) < 0) goto cerror;	/* write command */
		CMADDR->cstat = CSRN;
		while ((c = cpass()) != -1)
		CMADDR->cout = c;			/* char to controller */
		if((c = cmslp(0)) < 0) goto cerror;	/* sleep */
		if(c & EOT)
			if((c = cmrwd()) < 0) goto cerror;
			goto ccount;
	}

	if((c = cmslp(RD)) < 0) goto cerror;		/* sleep till readable */

	if(c & TMD) goto ccount;
	if(c & EOT)
	{
		if(c = cmrwd() < 0) goto cerror;
		goto cwtrd;			/* read again */
	}

	CMADDR->cstat = CSRN;
	while((CMADDR->cstat & REQB) == 0)		 /* record < than req */
		if ((passc(CMADDR->cin)) != 0) break;
		if(cmwait(INTBE) >= 0)	/* wait for B request */
		{
ccount:			c_mblkno[drv]++;
			goto cdone;
		}

cerror:	u.u_error = EIO;				/* report error */
cdone:	c_active = 0;					/* free controller */
	if(c_sleep > 0) wakeup(&c_sleep);
}

cmintr()
{

	CMADDR->cstat = 0;				/* kill interrupts */
	wakeup(&c_active);
}


cminit()
{
	if(cmslp(LDTS) < 0) return(-1);	/* select drive */
	CMADDR->cstat = CSRN;
	CMADDR->cout = c_ldts[c_drv];		/*  drive & track argument */
	if(cmwait(INTBE) < 0) return(-1);
}

cmseek(cur,sek)
{
register int x,y,z;

	x = cur;	/* current record block # */
	y = sek;	/* record block # sought */
	if(x < y)
	{
		while(x != y)
		{
			if((z = cmslp(SPFM)) < 0) break;
			if(z & SPTD)
			{
				if(cmrwd() < 0) break;
				if(cminit() != 0) break;
			}
			x++;
		}
		goto cog;
	}

	while(x != y)
	{
		if((z = cmslp(SPRM)) < 0) break;
		if(z & SPTD)
		{
			if(c_test[c_drv] > ILLEGAL && (c_ldts[c_drv] & 3) != 0)
			{
				c_ldts[c_drv]--;
				if(cminit() != 0) break;
				while((z & EOT) == 0)
					if((z = cmslp(STMF)) < 0)goto cog;
			}
			else break;
		}
		x--;
	}
cog:	if(x != y) return(-1);	/* failed */
}


cmdelay()
{
}


cmrwd()
{
	if(c_test[c_drv] > ILLEGAL && (c_ldts[c_drv] & 3) < ILLEGAL)
	{
		c_ldts[c_drv]++;		/* inc track */
	return(cmslp(RWD));			/* rewind: 0=ok,negative=trouble */
	}
	return(-1);				/* Illegal, no rewind performed */
}


cmslp(cmmd)
{
	register int b,c;

	if((c = cmmd))
	{
		CMADDR->cstat = CSR0;
		CMADDR->cout = c;		/* send command */
	}
	b = INTAE;
	if((c > WRT && c != RSW) || c < SNB)
	{
		if(c != RD) b = INTBE;
		spl5();				/* insure sleep */
		CMADDR->cstat =| b;		/* enable interrupt */
		while(CMADDR->cstat & b) sleep(&c_active,-1);
		spl0();
	}
	return(cmwait(b));			/* 0=ok,negative=trouble */
}

cmwait(boa)
{
	register int b,c,d;
	b = REQB;
	if(boa == INTBE) d = REQA;
	else
	{
		d = b;
		b = REQA;
	}
	for (c = 0;c < 200;c++)
	{
		if (CMADDR->cstat & b)		/* look for expected request */
		{
			if (CMADDR->cstat & d)	/* both requests, get status */
			{
				if(c_flgc) return(-1);
				return(cmstatus());
			}
			return(0);
		}
	}
	c_stau[c_drv] =| TERR;			/* request overdue = error */
	return(-1);				/* trouble */
}


cmstatus()
{
	register int c,b;

	c_flgc++;				/* cmstatus is caller */
	if(cmslp(RSW) < 0) goto werror;		/* read status */

	CMADDR->cstat = CSRN;
	c = CMADDR->cin & 0177;			/* 1st status byte */
	cmdelay();
	b = CMADDR->cin;			/* 2nd status byte */
	b = (b << 8) & 077400;
	c_stau[c_drv] = c =| b;
	if(cmwait(INTBE) < 0) goto werror;

	CMADDR->cstat = CSR0;
	CMADDR->cout = CLAF;			/* clear flags */
	if(cmwait(INTBE) < 0)
	{
werror:		c = -1;
		c_stau[c_drv] =| SERR;		/* status read or clr ng */
	}

	if (c & (RDER|DPTM|WNWEN|NONL|TOR|NRDY|ILCMD)) c = -1;
	c_flgc = 0;
	return(c);
}


