/*
 * 'SMD' disk driver - 67MB, 256MB, all CDD's, and the ITDC
 */

/*
 * This driver now supports all of Perkin-Elmer's current disc drives.
 * Overlapping seeks are supported.
 * UCB_BHASH must be defined to use this driver, else bio.c requires
 * modification.
 */



#include "../h/local.h"

#ifndef  NO_SCCS_ID
static char SCCS_ID [] = "@(#)mmsm.c    	3.5	 16:20:19 - 83/04/13 ";
#endif NO_SCCS_ID



#include "../h/param.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/selch.h"
#include "../h/systm.h"
#include "../h/dskmap.h"
#ifdef   MONITORING
#include "../h/sysmon.h"        /* TEK-1 */
#endif   MONITORING

#ifdef  OVERLAP
int     overlap = 1;
#endif  OVERLAP

#define NBPT    (msmst[msmtype[unit]].nbpt) /* physical 256-byte blocks per track */
#define NBPC    (msmst[msmtype[unit]].nbpc) /* 256-byte blocks per cylinder */
#define OFFSET  (msmst[msmtype[unit]].offset) /* head offset for cdd disc */
#define CDDTYP  (msmst[msmtype[unit]].overlap) /* controler tpye 0=msm 1=idc*/
#define MAXMSM 16       /* max number of drives */

extern int      nmsm;          /* number of units (spindles) */
/* one of these for each unit: */
extern char     msmtype[];      /* D67, D256, etc. */
extern int      msmselch[];     /* selch address for MSM */
extern char     msmaddr[];      /* drive addresses */
extern char     msmcntl[];      /* controller address */
extern char     msmmap[];       /* layout number */
extern char     msmcntlnum[];   /* controller number: 0, 1, 2, etc. */
extern struct dskmap msmlayout[][8]; /* logical device mapping */
extern char     devmap[];
extern  char    msmspindle[];   /* real spindle for logical dish */

int msmseek(), msmscintr(), msmio();

/* one of each of these for each controller number */
struct selchq msmscq[MAXMSM] {
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
	{ &msmseek, &msmscintr, 0, 0  },
};
struct buf msmtab[MAXMSM];
struct buf rmsmbuf[MAXMSM];
int     msmcyl[MAXMSM];
int     msmhead[MAXMSM];
int     msmsector[MAXMSM];
int     msmsad[MAXMSM];
int     msmead[MAXMSM];
int     bn[MAXMSM];
int     msmcurcyl[MAXMSM];
int     msmnumsec[MAXMSM];
int     msmctltab[MAXMSM];
int     msm4eerr[MAXMSM];
int     msmanyerr[MAXMSM];
int     msmstat[MAXMSM];
int     msmlstun[MAXMSM];

struct msmst {
	char    nbpt;   /* 256-byte blocks per track */
	char    ntrak;  /* tracks per cylinder */
	short   nbpc;   /* 256-byte blocks per cylinder */
	short   ncyl;   /* number of cylinders */
	char    offset; /* head offset */
	char    type;   /* msm type */
	char    overlap; /* controler type */
} msmst[] = {
	64,     5,      64*5,   823,    0,      0,      1,      /* D67 */
	64,     19,     64*19,  823,    0,      1,      1,      /* D256 */
	64,     1,      64*1,   823,    0,      2,      0,      /* C13R CDD removable */
	64,     1,      64*1,   823,    0x10,   3,      0,      /* C13F CDD fixed */
	64,     3,      64*3,   823,    0x10,   4,      0,      /* C40F CDD fixed */
	64,     5,      64*5,   823,    0x10,   5,      0,      /* C67F CDD fixed */
	59,     5,      59*5,   815,    0,      6,      1,      /* ITDC */
};

/* disk drive status & commands */
#define ALTBSY  0x20
#define UNS     0x10
#define UNREADY 0x08
#define SINC    0x02
#define OFFL    0x01

#define DISABLE 0x80
#define ENABLE  0x40
#define DISARM  0xc0
#define SETHEAD 0x20
#define SETCYL  0x10
#define SEEK    0x02
#define RELEASE 0xb0

/* disk controller status & commands */
#define CNTL_UNRCV      0xc1
#define CYL_OV          0x10
#define IDLE            0x02

#define READ            0x01
#define WRITE           0x02
#define RESET           0x08

#define NMSMERR        10
int     errthres = -1;
int     cddlast = 0,cdddelay = 3000;

/*
 * Status in msmtab.b_active
 */
#define WSELCH  1       /* waiting for selch */
#define WALT    2       /* waiting for alternate channel release */
#define WSEEK   3       /* waiting for seek to complete */
#define WIO     4       /* waiting for I/O to complete */

/*
 *      disk strategy routine
 */
msmstrategy(bp)
register struct buf     *bp;
{
	register int unit;
#ifdef  MONITORING
	extern struct buf swbuf1 , swbuf2;
	register        *p1,*p2;
#endif  MONITORING

	bp->b_resid = bp->b_bcount;

	unit = (minor((bp)->b_dev) >> 3);
	if (unit >= nmsm ){
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}

	/*
	 * Block no. too high -- looks like EOF for raw read, error otherwise
	 */
	if (bp->b_blkno >= msmlayout[msmmap[unit]][minor(bp->b_dev)&07].dm_nblk) {
		if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ))
			bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
	if (bp->b_blkno+bp->b_bcount/PGSIZE > msmlayout[msmmap[unit]][minor(bp->b_dev)&07].dm_nblk) {
		bp->b_resid = bp->b_bcount = (msmlayout[msmmap[unit]][minor(bp->b_dev)&07].dm_nblk - bp->b_blkno)*PGSIZE;
	}
	if( unit < 4 ){
		dk_numb[unit]++;
		dk_wds[unit] += (bp->b_bcount>>6);
	}
	bp->av_forw = 0;
#ifdef  MONITORING

	if((bp == &swbuf1) || (bp == &swbuf2))
		p1 = &sysmon.swapmon;
	    else
		p1 = &sysmon.hpmon;
	p1->access[unit]++;
	p1->sizes += ( bp->b_resid + PGSIZE -1 ) >> PGSHIFT;

#endif  MONITORING
	spl5();
	if (msmtab[msmspindle[unit]].b_actf == 0)
		msmtab[msmspindle[unit]].b_actf = bp;
	else
		msmtab[msmspindle[unit]].b_actl->av_forw = bp;
	msmtab[msmspindle[unit]].b_actl = bp;
	if (msmtab[msmspindle[unit]].b_active == 0)
		msmstart(unit);
	spl0();
}

/*
 * start next disk i/o operation
 *      - set up drive address, cylinder/head/sector address
 *      - set up memory start & end addresses
 *      - initiate seek
 */

msmstart(unit)
register        int     unit;
{
	register struct buf *bp;
	register stat;
#ifdef  MONITORING

	extern struct buf swbuf1,swbuf2;
	register struct dkmon    *p1;
	register int  *p2;

#endif MONITORING

	if (!(bp = msmtab[msmspindle[unit]].b_actf))
		return;
#ifdef  MONITORING

	if((bp == &swbuf1) || (bp == &swbuf2))
		p1 = &sysmon.swapmon;
	    else
		p1 = &sysmon.hpmon;
#endif  MONITORING

	msmtab[msmspindle[unit]].b_active = WSELCH;
	if( unit < 4 )
		dk_busy |= 1<<unit;


	bn[unit] = bp->b_blkno + msmlayout[msmmap[unit]][minor(bp->b_dev)&07].dm_baddr;

#ifdef  MONITORING

	msmcurcyl[unit] = msmcyl[unit];

#endif  MONITORING
/*
 * We are doing Address calculation here in terms of
 * 256 byte blocks on the disc.  However UNIX wants
 * I/O in terms of 512 byte blocks, so the bn[unit] is
 * terms of 512 byte blocks.
 */
/* We will locate the address on the disc by first
 * finding the cylinder and the track and the sector
 * and the head inside the cylinder associated with
 * this data transfer operation.
 */
#ifdef DEBUG
/*
printf("Block Number requested: %d\n",bn[unit]);
printf("OLD ADDRESS: cyl=%d head %d sector %d\n",bn[unit]/(NBPC/2),
       (bn[unit]%(NBPC/2))/(NBPT/2), ((bn[unit]%(NBPC/2))%(NBPT/2))<< 1);
*/
#endif
	bn[unit] *= 2 ; /* Convert to 256 byte block request */
	msmcyl[unit] = bn[unit] / NBPC;
	bn[unit] %= NBPC;
	msmhead[unit] = (bn[unit] / NBPT) | OFFSET;
	msmsector[unit] = ((bn[unit]) % NBPT);
#ifdef DEBUG
/*
 * Error Check for the itdc to
 * See if we are getting bad head
 * address
 */
	if ( msmtype[unit] == 6 && msmhead[unit] > 4 ){ 
		printf("Bad Head: bloc=%d, sector=%d cyl=%d head=%d\n",
		bn[unit],msmsector[unit],msmcyl[unit],msmhead[unit]);
		bp->b_flags |= B_ERROR ;
		iodone(bp);
		return;
	}
#endif
	msmnumsec[unit] = msmsector[unit];
	msmsad[unit] = bp->b_un.b_addr;
	msmead[unit] = bp->b_un.b_addr + bp->b_bcount - 1;
#ifdef  MONITORING

		if( msmnumsec[unit] = msmcyl[unit] - msmcurcyl[unit]){
			p1->nseeks[unit]++;
			p1->sseeks[unit] += msmnumsec[unit] < 0 ? -msmnumsec[unit] : msmnumsec[unit];
		}

#endif  MONITORING

	msmscq[unit].sq_sstart = &msmseek;
	selchreq(msmselch[unit], &msmscq[unit], unit);
}

msmseek(unit)
register int unit;
{
	register stat;

	while ((ss(msmcntl[unit])&IDLE) == 0)
		;
	 ss(msmaddr[unit]);
	 ss(msmaddr[unit]);
	if ((stat = ss(msmaddr[unit]))&ALTBSY) {
		msmtab[msmspindle[unit]].b_active = WALT;
		oc(msmaddr[unit], ENABLE);
		selchfree(msmselch[unit]);
		return;
	}
	devmap[msmaddr[unit]] = unit;
	msmtab[msmspindle[unit]].b_active = WSEEK;
	wh(msmaddr[unit], msmcyl[unit]);
	while (((stat=ss(msmcntl[unit]))&IDLE) == 0)
		;
	oc(msmaddr[unit], SETCYL|DISARM);
	while (((stat=ss(msmcntl[unit]))&IDLE) == 0)
		;
	oc(msmaddr[unit], SEEK|ENABLE);
	while (((stat=ss(msmcntl[unit]))&IDLE) == 0)
		;
	ss(msmaddr[unit]);
	ss(msmaddr[unit]);
#ifdef  OVERLAP
	selchfree(msmselch[unit]);
#endif  OVERLAP
}

/*
 * disk interrupt routine
 *      -disk interrupts after a seek, or when Alternate Channel Busy clears
 *      -check status, initiate read/write or seek
 */

msmintr(unit,stat)
register char unit;
{
	register struct buf *bp;
	register scmd, ccmd;
	register i;


	msmstat[unit] = stat;
	if (!(bp = msmtab[msmspindle[unit]].b_actf))
		return;


	switch (msmtab[msmspindle[unit]].b_active) {
		case WALT:
			msmtab[msmspindle[unit]].b_active = WSELCH;
			msmscq[unit].sq_sstart = &msmseek;
			selchreq(msmselch[unit], &msmscq[unit], unit);
			return;

		case WSEEK:
#ifdef  OVERLAP
			msmscq[unit].sq_sstart = &msmio;
			selchreq(msmselch[unit], &msmscq[unit], unit);
			return;
#endif  OVERLAP
			break;

		default:
			return;
	}
	msmio(unit);
}
msmio(unit)
register        unit;
{
	register struct buf *bp;
	register scmd, ccmd;
	register stat,i;

	msmtab[msmspindle[unit]].b_active = WIO;
	if (!(bp = msmtab[msmspindle[unit]].b_actf))
		return;

	stat = msmstat[unit];

	if (stat & (UNS|UNREADY|SINC|OFFL|ALTBSY)) {
		msmerror(bp, stat, msmaddr[unit]);
		return;
	}

	if (bp->b_flags & B_READ) {
		scmd = READ_GO;
		ccmd = READ|ENABLE;
	} else {
		scmd = GO;
		ccmd = WRITE|ENABLE;
	}


	oc(msmselch[unit], STOP);
	wdh(msmselch[unit], msmsad[unit]);
	wdh(msmselch[unit], msmead[unit]);
	wh(msmaddr[unit], msmhead[unit]);
	wh(msmaddr[unit], msmhead[unit]);
	oc(msmaddr[unit], SETHEAD|DISARM);
	while ((ss(msmcntl[unit])&IDLE) == 0)
		;
	wd(msmcntl[unit], msmsector[unit]);
	wh(msmcntl[unit], ((msmhead[unit])<<10)+msmcyl[unit]);
	wh(msmaddr[unit], msmhead[unit]);
	wh(msmaddr[unit], msmhead[unit]);
	oc(msmaddr[unit], SETHEAD|DISARM);
		if( CDDTYP == 0 && msmlstun[msmspindle[unit]] != unit ){
			msmlstun[msmspindle[unit]] = unit;
			for( i = 0; i < cdddelay; i++ )
				;
		}
	while ((ss(msmcntl[unit])&IDLE) == 0)
		;
	oc(msmcntl[unit], ccmd);
	oc(msmselch[unit], scmd);
	msmctltab[devmap[msmcntl[unit]]] = unit;
}

/*
 * selch interrupt routine
 *      -selch interrupt will be followed by a controller interrupt
 *              ( nothing to do here? )
 */

msmscintr(dev, stat, unit)
register int unit;
{

	oc(msmselch[unit], STOP);
}

/*
 * disk controller interrupt routine
 *      -check ending status, signal i/o done
 */

msmcintr(unit, stat)
register char   unit;
register int    stat;
{
	register struct buf *bp;
	register struct dskmap *dm;
#ifdef  MONITORING

	extern struct buf       swbuf1,swbuf2;
	register char    *p1;
	register int     savetime;

#endif  MONITORING

	unit = msmctltab[unit];


	if (!(bp = msmtab[msmspindle[unit]].b_actf))
		return;
	if (msmtab[msmspindle[unit]].b_active != WIO)
		return;

#ifdef  MONITORING

		if((bp == &swbuf1) || (bp == &swbuf2))
			p1 = &sysmon.swapmon;
		    else
			p1 = &sysmon.hpmon;

#endif  MONITORING

	if (stat & CNTL_UNRCV) {
		oc(msmcntl[unit], RESET);
		msmerror(bp, stat, msmcntl[unit]);
		return;
	}
	bp->b_resid = 0;

#ifdef  MONITORING

			savetime = time;
			savetime =- bp->b_error;
			p1->atimes += savetime < 0 ? -savetime : savetime;

#endif  MONITORING

	/*
	 * Cylinder overflow
	 */
	if (stat & CYL_OV) {
		oc(msmcntl[unit], RESET);
		oc(msmselch[unit], STOP);
		msmsad[unit] += (rdh(msmselch[unit])-msmsad[unit]+2)&~0377;
		msmsector[unit] = msmhead[unit] = 0;
		msmhead[unit] |= OFFSET;
		bp->b_resid = msmead[unit] - msmsad[unit];
		dm = &msmlayout[msmmap[unit]][minor(bp->b_dev)&07];
		if (++msmcyl[unit]*(NBPC/2) < dm->dm_baddr + dm->dm_nblk) {
#ifdef DEBUG
/*
 * 
 * For cylinder overlow in an ITDC
 */
   if(msmtype[unit] == 6) printf("Cylinder Overflow on the ITDC\n");
#endif
			msmseek(unit);
			return;
		} else
			if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ))
				bp->b_flags |= B_ERROR;
	}

	oc(msmaddr[unit], RELEASE);
	/***
	     This sense status is required to fully release the
	     drive when operating with more than one drive on a
	     single controller.
	***/
	ss(msmaddr[unit]);
	ss(msmaddr[unit]);
	while ((ss(msmcntl[unit])&IDLE) == 0)
		;
	msmtab[msmspindle[unit]].b_errcnt = 0;
	msmtab[msmspindle[unit]].b_active = 0;
	if( unit < 4 )
		dk_busy &= ~(1<<unit);
	msmtab[msmspindle[unit]].b_actf = bp->av_forw;
	iodone(bp);
	selchfree(msmselch[unit]);
	msmstart(unit);
	return;

bad:
	bp->b_flags |= B_ERROR;
	iodone(bp);
	return;
}

msmerror(bp, stat, addr)
register struct buf      *bp;
{
	register int unit;

	unit = (minor((bp)->b_dev) >> 3);
	if( msmtab[msmspindle[unit]].b_errcnt > errthres ){
		deverror(bp, stat, addr);
		if( stat == 0x4E ) ++msm4eerr[unit];
	}
	if( stat != 0x4e ) ++msmanyerr[unit];

	if (++msmtab[msmspindle[unit]].b_errcnt <= NMSMERR) {
		oc(msmaddr[unit], RELEASE);
		/*** See comment above on ss after release ***/
		ss(msmaddr[unit]);
		ss(msmaddr[unit]);
		while ((ss(msmcntl[unit])&IDLE) == 0)
			;
		msmseek(unit);
		return;
	}
	oc(msmaddr[unit], RELEASE);
	/*** See comment above on ss after release ***/
	ss(msmaddr[unit]);
	ss(msmaddr[unit]);
	while ((ss(msmcntl[unit])&IDLE) == 0)
		;
	msmtab[msmspindle[unit]].b_errcnt = 0;
	bp->b_flags |= B_ERROR;
	msmtab[msmspindle[unit]].b_active = 0;
	dk_busy &= ~(1<<unit);
	msmtab[msmspindle[unit]].b_actf = bp->av_forw;
	iodone(bp);
	selchfree(msmselch[unit]);
	msmstart(unit);
}

/*
 * 'Raw' disc interface
 */
msmread(dev)
char    dev;
{
	register int unit;

	physio(msmstrategy, &rmsmbuf[msmspindle[minor(dev) >> 3]], dev, B_READ);
}

msmwrite(dev)
char    dev;
{
	register int unit;

	physio(msmstrategy, &rmsmbuf[msmspindle[minor(dev) >> 3]], dev, B_WRITE);
}
