Ausam/sys/dmr/msc.c

Find at most related files.
including files from this version of Unix.

#
/*
 *	Optimised Microcomputer Systems 1100 driver
 *	used with Ampex DM 9100 disc drives.
 *	Dated 8 - 3 - 78.
 *
 *	When instructed correctly (via the ifdefs) this driver will attempt
 *	to queue io requests by drive, cylinder and sector, and endeavour
 *	to overlap seeks with data transfers for more than one drive.
 *	Method is very similar to Childrens Museum RK driver.
 *
 *	Some simple error correction and recovery is attempted, but nothing
 *	fancy. You have been WARNED.
 *
 *	Bugs and comments to	Peter Ivanov
 *				Dept. Computer Science,
 *				University of New South Wales.
 */

#include	"../defines.h"
#include	"../param.h"
#include	"../buf.h"
#include	"../conf.h"
#include	"../seg.h"
#include	"../user.h"

/*	global defines	*/
#define	SWEEP	/* enable checking to force max head travel */
/*#define	NO_SEEK		/* to stop pre-seeking */
/*#define	VOLUME_STATS	/* define to enable stats collection per volume */
/*#define	DRIVE_STATS	/* define to enable stats collection per drive and cylinder */
#define		IO_STATS	/* count sectors read & written */
/*#define	NO_OFFLINEQ	/* stop qing of requests for offlined drives */
/*#define	DROP_OFFLINEQ	/* delete drive request q when offlined */
/*#define	RETRIES_ONLY	/* don't print error messages for first soft error */
/*#define	LAST_CYL	/* to accumulate the info about the last seek distance on drive */

/*	configuration details	*/
#define	MSADDR	0176700	/* address of device */
#define	NDRV	2	/* the number of real 9100 drives on controller */
#define	MSCYLS	411	/* 411 cylinders per pack */
#define	MSTRK	19	/* number of tracks per cyl (platters) */
#define	MSSEC	21	/* number of sectors per track */
#define	MSBYTE	512	/* number of bytes per sector */
#define	TVOL	(sizeof ms_sizes/sizeof ms_sizes[0])	/* total number of volumes */
#define	MSAGE	100	/* number of times this block may be pre-empted for io
				   before io is forced	*/
#define	INTLV	5	/* interleaving factor for rp04 type drives */
#define	MSSPL	spl5	/* priority of ms disc */

/*	definition of device registers	*/
struct	{
	int	msccra;	/* command completion register A */
	int	msccrb;	/* command completion register B */
	int	mscsr;	/* control and status register */
	int	msctra;	/* command transmission register A */
	int	msctrb;	/* command transmission register B */
/*	int	filler;	/* dummy filler	*/
/*	int	mscsr;	/* second occurrence of control and status reg */
	};

/*	structure of an incore disc command descriptor - one per drive	*/
struct	msdesc
	{
	int	 ds_id;		/* command identifier - drive number for us */
	int	 ds_stat;	/* status word */
	char	 ds_nexthi;	/* hi portion of link to next descriptor */
	char	 ds_sfc;	/* system fault code */
	unsigned ds_nextlo;	/* lo portion of link to next descriptor */
	int	 ds_discop;	/* disc operation code */
	int	 ds_drive;	/* disc drive number */
	int	 ds_cylinder;	/* disc cylinder number */
	int	 ds_head;	/* disc head number (track) */
	int	 ds_sector;	/* disc sector number */
	unsigned ds_wcount;	/* word count */
	unsigned ds_memlo;	/* lo part of memory address */
	unsigned ds_memhi;	/* hi part of memory address - watch bit 6! */
	unsigned ds_rid1;	/* part one of record id on "readid"s */
	unsigned ds_rid2;	/* part two of record id on "readid"s */
/*
 *	it is possible for the controller to return many more words than this
 *	up to 41 infact but the command which causes this will NEVER be used.
 */
	};

/*
 *	The following maps logical volumes of various sizes to locations
 *	on various drives. This must be altered to add extra drives.
 *	PLEASE GET THEM CORRECT FIRST TIME.
 */

struct	{
	unsigned	nblocks;
	int	cyloff;
	int	drive;
#	ifdef	VOLUME_STATS
	long	refcount;	/* count of accesses to this volume */
#	endif
} ms_sizes[] {
/*	 0:	  0->163	(164cyls)	drive 0	*/ {	65436l,	  0,	0, }	/* system and source */
/*	 1:	164->213	( 50cyls)	drive 0	*/ ,{	19950l,	164,	0, }	/* users and swap */
/*	 2:	214->245	( 32cyls)	drive 0	*/ ,{	12768l,	214,	0, }	/* system disc  */
/*	 3:	246->409	( 164cyls)	drive 0	*/ ,{	65436l,	246,	0, }	/* system users */
/*	 4:	410->411	( 1cyls)	drive 0	*/ ,{	399l,	410,	0, }	/* MSC DIAGNOSTIC AREA */
/*	 5:	  0->163	(164cyls)	drive 1	*/ ,{	65436l,	  0,	1, }	/* secondary system and swap */
/*	 6:	164->213	( 50cyls)	drive 1	*/ ,{	19950l,	164,	1, }	/* user disk 1 */
/*	 7:	214->245	( 32cyls)	drive 1	*/ ,{	12768l,	214,	1, }	/* tmp disc  */
/*	 8:	246->409	( 164cyls)	drive 1	*/ ,{	65436l,	246,	1, }	/* user disc 2 */
/*	 9:	410->411	( 1cyls)	drive 1	*/ ,{	399l,	410,	1, }	/* MSC DIAGNOSTIC AREA */
};

#	ifdef	IO_STATS
	long	msrsec;		/* sectors read */
	long	mswsec;		/* sectors writ */
#	endif
/*
 *	structure of a disc queue
 */

struct msq
	{
	struct	buf	*ms_bufp;	/* pointer to first buffer in queue for this drive */
	struct	msdesc	ms_desc;	/* descriptor for this drive */
	int	ms_flags;		/* flags for qs */
#ifdef	SWEEP
	int	ms_lcyl;		/* last cylinder accessed on this drive */
	char	ms_dirf;		/* current direction of head movement */
#endif
#ifdef	DRIVE_STATS
	char	ms_nreq;		/* number of requests in queue - stats */
	char	ms_rmax;			/* high water mark for q */
	unsigned	ms_cyls[MSCYLS];	/* accesses per cylinder */
#endif
	} ms_q[NDRV];

/*	definition of io error retry strategy	*/
int	retry[]
	{
	0,	/* no variance */
	0,	/* no variance */
	0,	/* no variance */
	1,	/* early data strobe */
	2,	/* late data strobe */
	3,	/* positive cylinder offset, normal data strobe */
	4,	/* negative cylinder offset, normal data strobe */
	5,	/* positive cylinder offset, early data strobe */
	6,	/* negative cylinder offset, early data strobe */
	7,	/* positive cylinder offset, late data strobe */
	8	/* negative cylinder offset, late data strobe */
	};

/*	and so the max number of retries may be defined as	*/
#define	MSERRMAX	(sizeof retry/2)

/*
 *	We use a few buffer variables for other purposes here, ie:
 *		b_scratch	to hold unit/cylinder address of this request
 *		b_resid		to hold track/sector address of this request
 *		av_back		to hold request age count
 */

#define	ms_cyl		b_scratch	/* at least an int */
#define	ms_sector	b_resid.lobyte
#define	ms_track	b_resid.hibyte
#define	ms_age		av_back.integ

struct	devtab	mstab;

#ifndef	RAW_BUFFER_POOL
struct	buf	msbuf;
#endif

/*	some handy masks	*/
#define	BYTE	0377
#define	LSD	07	/* least significant octal digit */
#define	UNIT	016000	/* the position of unit no. in ms_cyl */
#define	CYL	0777	/* the desired/present cylinder bits */

/*	some equally handy numbers	*/
#define	UP	1	/* the disc cyls are moving up */
#define	DOWN	0	/* the disc cyld are moving down */
#define	SBYTE	8	/* amount by which to shift for a byte */
#define	SUNIT	10	/* amount by which to shift unit nos. */

/*	some flags		*/
#define	MS_IDLE		0	/* drive is quiescent */
#define	MS_SEEK		1	/* seeking flag for q header */
#define	MS_READY	2	/* ready for io flag for q header */
#define	MS_ERROR	3	/* doing error recovery */
#define	MS_IO		4	/* actually doing io */
#define	MS_RECAL	5	/* actually doing a recal */

/* ccra bits */
#define DSKPON	0100000		/* disk drive powered on */
#define CMDQERR	0010000		/* command queue error */
#define CMDNXM	0004000		/* nxm during command descriptor fetch */
#define CMDPAR	0002000		/* memory parity during descriptor fetch */

/* csr bits */
#define RDYCC	0100000		/* ready from command completion */
#define RESET	0002000		/* reset controller */
#define CCACK	0001000		/* command completion acknowledge */
#define CARDY	0000400		/* command address ready */
#define RDYCT	0000200		/* ready for command transmission */
#define CTIE	0000100		/* command transmission interrupt enable */
#define CCIE	0000040		/* command completion interrupt enable */
#define PWRCYC	0000020		/* controller power cycle */
#define CTLRON	0000010		/* controller power is on */
#define DMAON	0000004		/* direct memory access in progress */
#define BOOT	0000001		/* load bootstrap into location 0 */

/* status bits */
#define RETRY	0100000		/* retry occurred due to data miss */
#define DATAERR 0040000		/* uncorrectable data error (read) */
#define ECCERR	0020000		/* ecc correction made */
#define IDERR	0010000		/* error in sector ID field */
#define ADDRERR 0004000		/* bad address in sector ID field */
#define WRTPRTC	0002000		/* write protected sector */
#define ALTSECT	0001000		/* alternate sector flag */
#define DATMISS 0000400		/* data miss (hard) */
#define SEEKERR 0000200		/* illegal seek or seek incomplete */
#define DRVNRDY 0000100		/* drive not ready */
#define DRVFALT 0000020		/* drive fault or select lock */
#define NXMERR	0000010		/* transfer referenced non-existant memory */
#define PARERR	0000004		/* memory parity error during transfer */
#define	SYSFLT	0000002		/* system fault */
#define PRTCDRV 0000001		/* write protected drive */

/* a few opcodes */
#define	DSTATE	02		/* obtain drive status */
#define RECAL	04		/* recalibrate drive */
#define SEEK	05		/* seek specified cylinder */
#define WRITE	06		/* write data */
#define READ	020		/* read data */

char	ms_onlinef;
#ifdef	UNIBUS_MAP & MAPPED_BUFFERS
int	*ms_map, ms_offset;	/* UNIBUS MAP register and address */
#endif	UNIBUS_MAP & MAPPED_BUFFERS
long	ms_transf;	/* words transferred (for error rate calculations) */
unsigned	ms_softerrs;


msopen()
{
	register int	n;
	register struct msq	*qp;
	register struct msdesc	*msd;

#ifdef	MALLOC_UMAP
	extern int umap[];
#endif	MALLOC_UMAP

	/*
	 *	if there are no active drives,
	 *		then clear controller
	 *		initialise
	 */

#ifdef	UNIBUS_MAP & MAPPED_BUFFERS
	if( !ms_map)
	{
		if( (n = malloc( umap, 1)) == NULL)
		{
			u.u_error = EIO;
			return;
		}
		ms_map = &(UBMAP->r[ (--n) << 1]);
		*ms_map = ms_q;
		*(ms_map + 1) = 0;
		ms_offset = n;
	}
#endif	UNIBUS_MAP & MAPPED_BUFFERS
	MSSPL();

	if( !ms_onlinef)
	{
		MSADDR->mscsr = RESET;
#ifndef	UNIBUS_MAP & MAPPED_BUFFERS
		MSADDR->msctra = 0;
#endif	UNIBUS_MAP & MAPPED_BUFFERS
		MSADDR->mscsr = CCIE;
		ms_onlinef = 0377;
		for(n=0; n<NDRV; n++)
		{
			/* initialise the relevant memory places */
			msd = &(qp = &ms_q[n])->ms_desc;
			qp->ms_flags = MS_IDLE;
			msd->ds_nexthi=0;
			msd->ds_nextlo=0;
			msd->ds_id=n;
			msd->ds_drive=n;
		}
	}

	spl0();
}

msstrategy(bp)
register struct buf	*bp;
{
	register unsigned	p1, p2;
	register int	f;

	bp->b_error = 0;		/* so that error retrys are correct */
	p1 = bp->b_dev.d_minor & BYTE;

/*
 *	Validate the request
 */

	if(
		(p1 >= TVOL)
	    || ((p2 = &ms_sizes[p1])->nblocks <= bp->b_blkno)
#ifdef	NO_OFFLINEQ | DROP_OFFLINEQ
	    || ( !(ms_onlinef & (1<< (p2->drive))))
#endif
	  )
	{
		bp->b_flags =| B_ERROR;
		iodone(bp);
		return;
	}

#ifdef	_1170
	if(bp->b_flags & B_PHYS)
		mapalloc(bp);
#endif
#	ifdef	VOLUME_STATS

	p2->refcount++;
#	endif
#	ifdef	IO_STATS
	if( bp->b_flags & B_READ )
		msrsec =- bp->b_wcount.hibyte;
	else
		mswsec =- bp->b_wcount.hibyte;
#	endif

	bp->av_forw = 0;
	bp->ms_age = MSAGE;
	bp->ms_cyl = p2->cyloff | ((f = p2->drive & LSD) << SUNIT);

	p1 = bp->b_blkno;
	bp->ms_sector = lrem(p1,MSSEC); /* sector */
	p1 = ldiv(p1,MSSEC); /* trk and cyl */
	bp->ms_track = p1 % MSTRK;
	bp->ms_cyl =+ p1/MSTRK;
	p2 = &ms_q[f];

/*
 *	Now "ms_cyl" contains "drive and cylinder"
 *	"ms_track" contains the head number,
 *	and "ms_sector" contains the sector.
 */

#ifdef	DRIVE_STATS
	p2->ms_cyls[bp->ms_cyl & CYL]++;
	p2->ms_nreq++;
#endif

	MSSPL();
	if ((p1 = p2->ms_bufp) == NULL)
	{
		/* this queue is empty */
		p2->ms_bufp = bp;
		msstart();
	}
	  else
	{
		/*
		 * the queue is not empty, so place in queue so as to
		 * minimise head movement.
		 */

#ifdef	SWEEP
		f = p2->ms_dirf;
#endif

		p2 = p1->av_forw;
		while(p2)
		{
			/* skip any overtaken blocks */
			if( !(p2->ms_age))
				p1 = p2;
			p2 = p2->av_forw;
		}

		for(; p2 = p1->av_forw; p1 = p2)

			if (	p1->ms_cyl <= bp->ms_cyl
			     && bp->ms_cyl <= p2->ms_cyl
			     || p1->ms_cyl >= bp->ms_cyl
			     && bp->ms_cyl >= p2->ms_cyl
			 ){
				while (bp->ms_cyl == p2->ms_cyl)
				{
					/*
					 * for a cylinder match, do the
					 * rotational optimisation.
					 * INTLV should be set to an optimal
					 * value, thought to be around 5
					 * and should NOT be altered if the discs
					 * are reformatted or the free list
					 * reordered...... good luck.
					 */
					if(p2->ms_sector > p1->ms_sector)
					{
						if(bp->ms_sector > p1->ms_sector + INTLV
						&& bp->ms_sector < p2->ms_sector - INTLV)
							goto out;
					}
					  else
						if(bp->ms_sector > p1->ms_sector + INTLV
						|| bp->ms_sector < p2->ms_sector - INTLV)
							goto out;
					p1 = p2;
					if( !(p2 = p1->av_forw)) goto out;
				}
				break;
			}

#ifdef SWEEP
			  else
				if (f == UP)
				{
					if(p2->ms_cyl < p1->ms_cyl)
						if(bp->ms_cyl > p1->ms_cyl)
							goto out;
						else
							f = DOWN;
				}
				  else
				{
					if(p2->ms_cyl > p1->ms_cyl)
						if(bp->ms_cyl < p1->ms_cyl)
							goto out;
						else
							f = UP;
				}
#endif

	out:
		bp->av_forw = p2;
		p1->av_forw = bp;

		while(p2)
		{
			/* count down overtaken blocks */
			if( p2->ms_age)
				p2->ms_age--;
			p2 = p2->av_forw;
		}
	}

	spl0();

}

/*
 *	msstart() goes through all the queues and sets everybody
 *	seeking that should be.
 */
msstart()
{
	/* called at MSSPL or greater */

	register struct buf *bp;
	register struct msq *qp;
	register int	n;
	int	ioflg;

	static int	drv, ioage;

#ifndef	NO_SEEK

	for(n=0; n<NDRV; n++)
		if((bp=(qp= &ms_q[n])->ms_bufp) && (ms_onlinef & (1<<n)))
		{
			/*
			 *	for all active drives
			 */
			if(qp->ms_flags == MS_IDLE)
			{
				/*
				 *	which are seekable, start seeking
				 */

				if(qp->ms_lcyl == bp->ms_cyl)
					qp->ms_flags = MS_READY;
				else
				{
#ifdef	SWEEP
					if(bp->ms_cyl > qp->ms_lcyl)
						qp->ms_dirf = UP;
					else
						qp->ms_dirf = DOWN;
#endif	SWEEP
					mscstart(bp, qp, SEEK);
					qp->ms_flags = MS_SEEK;
				}
			}
		}
#endif

	/*
	 *	start a retry with recalibrate
	 */

	for(n=0; n<NDRV; n++)
		if((bp = (qp = &ms_q[n])->ms_bufp) && (ms_onlinef & (1<<n)))
		{
			/*
			 *	for all active drives
			 */
			if( qp->ms_flags == MS_ERROR)
			{
				/*
				 *	which are recalibratable
				 */

				mscstart(bp, qp, RECAL);
				qp->ms_flags = MS_RECAL;
				qp->ms_lcyl = 0;
			}
		}

	/*
	 * try to start an io
	 */
	ioflg = 0;
	do
	{
		for(n = 0; n < NDRV; n++)
		{
			/*
			 *	for the next io-able drive in a circular
			 *	lookup, start an io if waiting
			 */

			if((bp = (qp = &ms_q[drv])->ms_bufp) &&
#ifndef	NO_SEEK
				(qp->ms_flags  ==  MS_READY) &&
#endif
				(ms_onlinef & (1<<drv)))
			{
				ioflg++;
				if(ioage <= MSAGE)
				{
					if(bp->b_flags & B_READ)
						mscstart(bp, qp, READ);
					  else
						mscstart(bp, qp, WRITE);
					qp->ms_flags = MS_IO;
					ioage++;
					return;
				}
			}
			if( ++drv >= NDRV) drv = 0;
			ioage = 0;
		}
	}
	while( ioflg);		/* Ioage caused us to miss an I/O */

}


msintr()
{
	/* called at MSSPL or greater */

	register struct msdesc	*msd;
	register struct msq	*qp;
	register struct buf	*bp;
	int	n;

	/*
	 *	Here we have three possibilities, these being
	 *		1) Normal command complete
	 *		2) Disc powered up
	 *		3) A command (NXM, PAR, QERR)
	 */

	if(MSADDR->msccra & (CMDQERR | CMDNXM | CMDPAR))
	{
		/* absolute failure, cannot recover */
		printf("MSERR FATAL CCRA ERROR\n");
		MSADDR->mscsr =| CCACK;
		return;
	}
	/*
	 *	now have three further possibilities, namely
	 *		1) drive powered up
	 *		2) error in completed command
	 *		3) command complete
	 */
	if(MSADDR->msccra & DSKPON)
	{
		/* a drive has powered up */
		printf("DRIVE POWERED ON, CCRB=%o\n", MSADDR->msccrb);
		ms_onlinef =| MSADDR->msccrb & BYTE;
		MSADDR->mscsr =| CCACK;
	}
	  else
	{
		/* command complete, with or without errors */
		bp = (qp = &ms_q[n = MSADDR->msccrb & BYTE])->ms_bufp;
		msd = &qp->ms_desc;
		MSADDR->mscsr =| CCACK;
		if(msd->ds_stat & ~(RETRY | ECCERR))
		{
			/* an error!!! */
			if(msd->ds_stat & (PRTCDRV|WRTPRTC|ALTSECT|NXMERR|PARERR))
			{
				/*
				 * operation cannot continue.
				 * NXMERR or PARERR are pretty nasty
				 * but we mustn't hang up driver.
				 */
				printf("MSERR UNRECOVERABLE\n");
/*debugging*/	printf("DS_WC=%d, DS_ML=%d, DS_MH=%d\n",msd->ds_wcount,msd->ds_memlo,msd->ds_memhi);
/*debugging*/	printf("BP_WC=%d, BP_ML=%d, BP_MH=%d\n",bp->b_wcount, bp->b_addr, bp->b_xmem);
				mserr(qp, bp, 1);
				bp->b_flags =| B_ERROR;
				msdone(qp, bp);
			}
			  else
			{
				/* error but can continue */
				mserr(qp, bp, 0);	/* only give error if already tried twice */
				if(msd->ds_stat & (ADDRERR | SEEKERR | DRVNRDY
					| SYSFLT | DRVFALT))
				{
					/* retry and recalibrate */
					qp->ms_flags = MS_ERROR;
				}
				 else
				{
					/* data error, retry by positioning */
					qp->ms_flags = MS_READY;
				}
				if(errcnt(bp))
				{
					msdone(qp, bp);
					if( (msd->ds_stat & DRVNRDY) ||
						(msd->ds_sfc == 1) )
					{
						ms_onlinef =& ~(1<<n);
#ifdef	DROP_OFFLINEQ
						while(bp=qp->ms_bufp)
						{
							bp->b_flags =| B_ERROR;
#ifdef	DRIVE_STATS
							if(qp->ms_nreq-- > qp->ms_rmax)
								qp->ms_rmax = qp->ms_nreq+1;
#endif
							qp->ms_bufp = bp->av_forw;
							iodone(bp);
						}
#endif
					}
				}
			}
		}
		  else
		{
			/* command complete */
			switch (qp->ms_flags)
			{
		    case MS_SEEK:	/* seek complete */
				qp->ms_flags = MS_READY;
				break;
		    case MS_RECAL:	/* error recovery complete */
				qp->ms_flags = MS_IDLE;
				break;
		    case MS_IO:		/* IO complete */
#ifdef	SWEEP
				qp->ms_lcyl = bp->ms_cyl;
#endif
				msdone(qp, bp);
				break;
		    default:		/* can NEVER happen */
				printf("MSERR \"FUCK\"\n");
				mserr(qp, bp, 1);
				break;
			}
		}
	}

	msstart();

}

errcnt(bp)
register struct buf	*bp;
{
	if( ++bp->b_error >= MSERRMAX)
	{
		bp->b_flags =| B_ERROR;
		return(1);
	}
	return(0);
}

msdone(qp, bp)
register struct msq	*qp;
register struct buf	*bp;
{
	qp->ms_flags = MS_IDLE;
	qp->ms_bufp = bp->av_forw;
#ifdef	DRIVE_STATS
	if(qp->ms_nreq-- > qp->ms_rmax)
		qp->ms_rmax = qp->ms_nreq+1;
#endif
	bp->b_resid = -qp->ms_desc.ds_wcount;
	iodone(bp);
}

mserr(qp, bp, flag)
register struct msq	*qp;
struct buf		*bp;
int			flag;
{
#ifdef	RETRIES_ONLY
	if(bp->b_error > 0 /* second try at least */ || flag)
#endif	RETRIES_ONLY
	{
		printf("MS %o,CRA=%o,SR=%o,ST=%o,FC=%o,OP=%o,CYL=%o,HD=%o,SEC=%o,RETRYS=%o\n",
			qp->ms_desc.ds_drive,
			MSADDR->msccra,
			MSADDR->mscsr,
			qp->ms_desc.ds_stat,
			qp->ms_desc.ds_sfc,
			qp->ms_desc.ds_discop,
			qp->ms_desc.ds_cylinder,
			qp->ms_desc.ds_head,
			qp->ms_desc.ds_sector,
			bp->b_error);
	}
	if((++ms_softerrs & 017)  == 0)
	{
		printf("Error rate %dx1,000 words per error\n",
			longdiv(((ms_transf + (1<<7))>>8), (ms_softerrs<<2)));
			/* really times 1024, but this is close enough */
	}
#ifdef LAST_CYL
	{
		int	diff;
		printf("lastcyl=%o, currcyl=%o, distance=",
			qp->ms_lcyl & CYL,
			bp->ms_cyl & CYL);
		if( (diff = (bp->ms_cyl & CYL) - (qp->ms_lcyl & CYL)) < 0)
			printf("-%o\n", -diff);
		else
			printf("+%o\n", diff);
	}
#endif LAST_CYL
}

/*
 *	Physical IO:
 *	truncate transfers at the ends of logical volumes
 */

unsigned	msresidue;

msread(dev)
int	dev;
{
	register unsigned resid;

	if(msphys(dev))
	{
		resid = msresidue;
#ifndef	RAW_BUFFER_POOL
		physio(msstrategy, &msbuf, dev, B_READ);
#else	RAW_BUFFER_POOL
		physio(msstrategy,      0, dev, B_READ);
#endif	RAW_BUFFER_POOL
		u.u_count =+ resid;
	}
}

mswrite(dev)
int	dev;
{
	register unsigned resid;

	if(msphys(dev))
	{
		resid = msresidue;
#ifndef	RAW_BUFFER_POOL
		physio(msstrategy, &msbuf, dev, B_WRITE);
#else	RAW_BUFFER_POOL
		physio(msstrategy,      0, dev, B_WRITE);
#endif	RAW_BUFFER_POOL
		u.u_count =+ resid;
	}
}

msphys(dev)
int	dev;
{
	register unsigned a, b, c;

	b = u.u_offset >> 9;
	if( ((c = dev.d_minor & BYTE) >= TVOL) ||
		((a = ms_sizes[c].nblocks) <= b) )
	{
		u.u_error = ENXIO;
		return(0);
	}
	c = u.u_count;
	if(ldiv(c+511, 512) + b > a)
		c = (a - b) << 9;
	msresidue = u.u_count - c;
	u.u_count = c;
	return(1);
}

#ifdef	MSPOWERFAIL
mspowerf(rdev)
{
	/*
	 *	Msc-Ampex disc power fail recovery routine
	 *
	 *	It is ASSUMED that
	 *
	 *	. a power fail has occured and the cpu did a reset
	 *	. mem management registers have been restored
	 *	. as have unibus map regs as have
	 *	. the other vital ones.
	 */

	register struct buf	*bp;
	register struct msq	*qp;
	register	d;

	long	n;

	MSADDR->hpcs2=CLR;
#ifndef	NO_SEEK
	for(d=0; d< NDRV; d++)
		ms_q[n].ms_flags = MS_IDLE;
#endif
	if(rdev)
	{
		for(d=0; d<NDRV; d++)
			if(ms_onlinef & (1<<d))
			{
				/*
				 *	this drive was on when it died
				 */

				MSADDR->hpcs2 = d;
				for(;;)
				{
					if( (MSADDR->hpcs1 & CRDY) && (MSADDR->hpds & MOL) && (MSADDR->hpds & DRY) )
					{
						msdinit(d);
						break;
					}
					for(n=2000000; n>0; n--);
					printf("MSERR PWR FAIL RECVRY WAITING DRIVE %o\n", d);
				}
			}
	}
	  else
	{
		ms_onlinef = 0;
	}

	msstart();

	printf("MSERR PWR FAIL RECVRD\n");
}

#endif

/*****************************************************************************************/
/*****************************************************************************************/
/*****************************************************************************************/
/*****************************************************************************************/

/*
 *	Start routine for MSC controller
 */

mscstart(bp, qp, com)
register struct buf	*bp;
register struct msq	*qp;
int	com;
{
	register char *msd;

	/*
	 *	ensure that RDYCT is up before doing it
	 */

	while( !(MSADDR->mscsr & RDYCT));
	msd = &qp->ms_desc;
	if(com & (READ | WRITE))
	{
		msd->ds_memlo = bp->b_addr;
		msd->ds_memhi = bp->b_xmem & 03;
		msd->ds_wcount = -bp->b_wcount;
		ms_transf =- bp->b_wcount;
		msd->ds_sector = bp->ms_sector;
		msd->ds_head = bp->ms_track;
	}
	msd->ds_cylinder = bp->ms_cyl & CYL;

	if(com == READ)
		com =| retry[bp->b_error];
	msd->ds_discop = com;

#ifndef	MAPPED_BUFFERS & UNIBUS_MAP
	MSADDR->msctrb = msd;
#else	MAPPED_BUFFERS & UNIBUS_MAP
	MSADDR->msctrb = msd + (ms_offset << 13) - *ms_map;
	MSADDR->msctra = ms_offset >> 3;
#endif	MAPPED_BUFFERS & UNIBUS_MAP


	MSADDR->mscsr = CCIE | CARDY;

}