/*
 * 'MSM1' disk driver - 67MB and 256MB
 */

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

#ifdef  SCCS_ID
static char SCCS_ID [] = "@(#)msm1.c    	3.1	 17:30:22 - 81/11/18 ";
#endif  SCCS_ID

#include "../h/param.h"
#undef   trace
#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

#define DN      1               /* disk number for monitoring */
#define NBPT	32		/* blocks per track */
#define NBPC    (msm1size? 608 : 160)   /* blocks per cylinder */
#define NMSM1ERR        10              /* number of error retries */

extern int      nmsm1;          /* number of disks */
extern int      msm1selch;      /* selch address for MSM1 */
extern int      msm1cntl;       /* controller address */
extern char     msm1addr[];     /* drive addresses */
extern int      msm1size;       /* 0 => 67MB,  1 => 256MB */
extern struct dskmap msm1map[]; /* logical device mapping */

int msm1seek(), msm1scintr();

struct buf      msm1tab;
struct buf      rmsm1buf;

struct selchq msm1scq {
	&msm1seek,
	&msm1scintr,
	0
};

int     msm1drive;
int     msm1cyl;
int     msm1head;
int     msm1sector;
int     msm1sad, msm1ead;


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

/*
 * Status in msm1tab.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
 */
msm1strategy(bp)
register struct buf	*bp;
{
#ifdef	MONITORING
	extern struct buf swbuf1 , swbuf2;
	register	*p1,*p2;
#endif	MONITORING
	bp->b_resid = bp->b_bcount;
	if ((minor(bp->b_dev)&07)==1)
/*              printf("strat blkno=%x count=%x flag=%x\n",bp->b_blkno, btoc(bp->b_bcount), bp->b_flags&(B_READ)); */

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

	/*
	 * Block no. too high -- looks like EOF for raw read, error otherwise
	 */
	if (bp->b_blkno >= msm1map[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;
	}
	dk_numb[DN]++;
	dk_wds[DN] += (bp->b_bcount>>6);
	bp->av_forw = 0;
#ifdef	MONITORING

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

#endif	MONITORING
	spl5();
	if (msm1tab.b_actf == 0)
		msm1tab.b_actf = bp;
	else
		msm1tab.b_actl->av_forw = bp;
	msm1tab.b_actl = bp;
	if (msm1tab.b_active == 0)
		msm1start();
	spl0();
}

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

msm1start()
{
	register struct buf *bp;
	register stat;
	register bn;
#ifdef	MONITORING

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

#endif MONITORING

	if (!(bp = msm1tab.b_actf))
		return;
#ifdef	MONITORING

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

	msm1tab.b_active = WSELCH;
	dk_busy |= 1<<DN;

	trace(0x80<<16, "mstart", bp);

	msm1drive = msm1addr[minor(bp->b_dev)>>3];
	bn = bp->b_blkno + msm1map[minor(bp->b_dev)&07].dm_baddr;

#ifdef	MONITORING

	msm1curcyl = msm1cyl;

#endif  MONITORING

	msm1cyl = bn / NBPC;
	bn %= NBPC;
	msm1head = bn / NBPT;
	if ((minor(bp->b_dev)>>3) == 1) msm1head |= 0x10;
	msm1sector = (bn % NBPT)<<1;
	msm1numsec = msm1sector;
	msm1sad = bp->b_un.b_addr;
	msm1ead = bp->b_un.b_addr + bp->b_bcount - 1;
#ifdef	MONITORING

		if( msm1numsec = msm1cyl - msm1curcyl){
			p1->nseeks[DN]++;
			p1->sseeks[DN] += msm1numsec < 0 ? -msm1numsec : msm1numsec;
		}

#endif	MONITORING

	selchreq(msm1selch, &msm1scq);
}

msm1seek()
{
	register stat;

	trace(0x80<<16, "seek", msm1drive);
	trace(0x80<<16, "cyl", msm1cyl);

	while ((ss(msm1cntl)&IDLE) == 0)
		;
	 ss(msm1drive);
	 ss(msm1drive);
	if ((stat = ss(msm1drive))&ALTBSY) {
		trace(0x80<<16, "alt", stat);
		msm1tab.b_active = WALT;
		oc(msm1drive, ENABLE);
		selchfree(msm1selch);
		return;
	}
	trace(0x80<<16, "stat", stat);
	msm1tab.b_active = WSEEK;
	wh(msm1drive, msm1cyl);
	while (((stat=ss(msm1cntl))&IDLE) == 0)
		;
	oc(msm1drive, SETCYL|DISARM);
	while (((stat=ss(msm1cntl))&IDLE) == 0)
		;
	oc(msm1drive, SEEK|ENABLE);
	while (((stat=ss(msm1cntl))&IDLE) == 0)
		;
	 ss(msm1drive);
}

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

msm1intr(dev,stat)
{
	register struct buf *bp;
	register scmd, ccmd;


	trace(0x40<<16, "interrupt", msm1addr[dev]);
	trace(0x40<<16, "status", stat);

	if (!(bp = msm1tab.b_actf))
		return;


	switch (msm1tab.b_active) {
		case WALT:
			msm1tab.b_active = WSELCH;
			selchreq(msm1selch, &msm1scq);
			return;

		case WSEEK:
			msm1tab.b_active = WIO;
			break;

		default:
			return;
	}

	if (stat & (UNS|UNREADY|SINC|OFFL|ALTBSY)) {
		msm1error(bp, stat, msm1drive);
		return;
	}

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

	trace(0x80<<16, "mcmd", ccmd);
	trace(0x80<<16, "head", msm1head);
	trace(0x80<<16, "sector", msm1sector);

	oc(msm1selch, STOP);
	trace(0x80<<16, "bufstart", msm1sad);
	trace(0x80<<16, "bufend", msm1ead);
	oc(msm1cntl, RESET);
	wdh(msm1selch, msm1sad);
	wdh(msm1selch, msm1ead);
	wh(msm1drive, msm1head);
	oc(msm1drive, SETHEAD|DISARM);
	while ((ss(msm1cntl)&IDLE) == 0)
		;
	wd(msm1cntl, msm1sector);
	wh(msm1cntl, ((msm1head ) <<10)+msm1cyl);
	wh(msm1drive, msm1head);
	oc(msm1drive, SETHEAD|DISARM);
	while ((ss(msm1cntl)&IDLE) == 0)
		;
	oc(msm1cntl, ccmd);
	oc(msm1selch, scmd);
}

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

msm1scintr(dev, stat)
{
	register struct buf *bp;

	trace(0x40<<16, "selch interrupt", msm1selch);
	oc(msm1selch, STOP);
}

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

msm1cintr(dev, stat)
{
	register struct buf *bp;
	register struct dskmap *dm;
#ifdef	MONITORING

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

#endif	MONITORING

	trace(0x40<<16, "interrupt", msm1cntl);
	trace(0x40<<16, "status", stat);


	if (!(bp = msm1tab.b_actf))
		return;
	if (msm1tab.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(msm1cntl, RESET);
		msm1error(bp, stat, msm1cntl);
		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(msm1cntl, RESET);
		oc(msm1selch, STOP);
		msm1sad += (rdh(msm1selch)-msm1sad+2)&~0377;
		trace(0x40<<16, "cyl ov", msm1sad);
		msm1sector = 0;
		if ((minor(bp->b_dev)>>3) == 1) msm1head = 0x10;
		bp->b_resid = msm1ead - msm1sad;
		dm = &msm1map[minor(bp->b_dev)&07];
		if ((++msm1cyl*NBPC) < dm->dm_baddr + dm->dm_nblk) {
			msm1seek();
			return;
		} else
			if ((bp->b_flags&(B_PHYS|B_READ)) != (B_PHYS|B_READ))
				bp->b_flags |= B_ERROR;
	}

	trace(0x80<<16, "release", msm1drive);
	oc(msm1drive, RELEASE);
	/***
	     This sense status is required to fully release the
	     drive when operating with more than one drive on a
	     single controller.
	***/
	ss(msm1drive);
	while ((ss(msm1cntl)&IDLE) == 0)
		;
	msm1tab.b_errcnt = 0;
	msm1tab.b_active = 0;
	dk_busy &= ~(1<<DN);
	msm1tab.b_actf = bp->av_forw;
	iodone(bp);
	selchfree(msm1selch);
	msm1start();
}

msm1error(bp, stat, addr)
register struct buf	 *bp;
{
	deverror(bp, stat, addr);
	if (++msm1tab.b_errcnt <= NMSM1ERR) {
		msm1seek();
		return;
	}
	trace(0x80<<16, "release", msm1drive);
	oc(msm1drive, RELEASE);
	/*** See comment above on ss after release ***/
	ss(msm1drive);
	while ((ss(msm1cntl)&IDLE) == 0)
		;
	msm1tab.b_errcnt = 0;
	bp->b_flags |= B_ERROR;
	msm1tab.b_active = 0;
	dk_busy &= ~(1<<DN);
	msm1tab.b_actf = bp->av_forw;
	iodone(bp);
	selchfree(msm1selch);
	msm1start();
}

/*
 * 'Raw' disc interface
 */
msm1read(dev)
{
	physio(msm1strategy, &rmsm1buf, dev, B_READ);
}

msm1write(dev)
{
	physio(msm1strategy, &rmsm1buf, dev, B_WRITE);
}
