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

/*
 * 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"

#ifdef  SCCS_ID
static char SCCS_ID [] = "@(#)msm.c        1.1      10:23:21 - 82/10/18 ";
#endif  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 512-byte blocks per track */
#define NBPC    (msmst[msmtype[unit]].nbpc) /* 512-byte blocks per cylinder */
#define OFFSET  (msmst[msmtype[unit]].offset) /* head offset for cdd disc */
#define CTRLTYP (msmst[msmtype[unit]].ctrltyp) /* 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];

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

/* 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=0;

/*
 * 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[unit].b_actf == 0)
		msmtab[unit].b_actf = bp;
	else
		msmtab[unit].b_actl->av_forw = bp;
	msmtab[unit].b_actl = bp;
	if (msmtab[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[unit].b_actf))
		return;
#ifdef  MONITORING

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

	msmtab[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

	msmcyl[unit] = bn[unit] / NBPC;
	bn[unit] %= NBPC;
	msmhead[unit] = (bn[unit] / NBPT) | OFFSET;
	msmsector[unit] = (bn[unit] % NBPT)<<1;
	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[unit].b_active = WALT;
		oc(msmaddr[unit], ENABLE);
		selchfree(msmselch[unit]);
		return;
	}
	devmap[msmaddr[unit]] = unit;
	msmtab[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;



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


	switch (msmtab[unit].b_active) {
		case WALT:
			msmtab[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;

	msmtab[unit].b_active = WIO;
	if (!(bp = msmtab[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]);
	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]);
	oc(msmaddr[unit], SETHEAD|DISARM);
	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[unit].b_actf))
		return;
	if (msmtab[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) < dm->dm_baddr + dm->dm_nblk) {
			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[unit].b_errcnt = 0;
	msmtab[unit].b_active = 0;
	if( unit < 4 )
		dk_busy &= ~(1<<unit);
	msmtab[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[unit].b_errcnt > errthres ){
		deverror(bp, stat, addr);
		if( stat == 0x4E ) ++msm4eerr[unit];
	}
	if( stat != 0x4e ) ++msmanyerr[unit];

	if (++msmtab[unit].b_errcnt <= NMSMERR) {
		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[unit].b_errcnt = 0;
	bp->b_flags |= B_ERROR;
	msmtab[unit].b_active = 0;
	dk_busy &= ~(1<<unit);
	msmtab[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);
}

