Ausam/sys/dmr/hp.c

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

/*
 *	Optimised RHP04 and RJP04 driver
 *
 *	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 error correction and recovery is attempted.
 *	You have been WARNED.
 *
 *	Based on the novel by Peter Ivanov	UNSWDCS
 *
 *	Debugged and recoded by		Ian Johnstone	AGSM
 *				and
 *					Chris Maltby	DCS.
 */

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

#define	PRESEEK		/* enable overlapped seeks on multiple drives */
#define	SWEEP		/* enable checking to force max head travel */
/* ( no RJP04 )
#define	RJP04		/* define for RP04's on the unibus,
			   ONLY will work for non 11/70 because of rhstart.
			   undefine for RP04's on RH70	*/
/* ( no HPECC )
#define	HPECC		/* enable data check correction */
/*
#define	HPSTATS		/* define to enable stats collection */
/* ( no NO_OFFLINEQ )
#define	NO_OFFLINEQ	/* stop qing of requests for offlined drives */
/* ( no DROP_OFFLINEQ )
#define	DROP_OFFLINEQ	/* delete drive request q when offlined */

struct
{
	int	hpcs1;	/* Control and Status register 1 */
	int	hpwc;	/* Word Count register */
	int	hpba;	/* Bus address register */
	int	hpda;	/* Desired disc address - sector/track */
	int	hpcs2;	/* Control and Status register 2 */
	int	hpds;	/* Drive status register */
	int	hper1;	/* Error register 1 */
	int	hpas;	/* Attention summary register */
	int	hpla;	/* Disc position look-ahead register */
	int	hpdb;	/* Data buffer */
	int	hpmr;	/* Maintenance register */
	int	hpdt;	/* Drive type encoding */
	int	hpsn;	/* Drive serial number */
	int	hpof;	/* Offset register - contains fm22 bit */
	int	hpdc;	/* Desired cylinder address register */
	int	hpcc;	/* Current cylinder address register */
	int	hper2;	/* Error register 2 */
	int	hper3;	/* Error register 3 */
	int	hppos;	/* Burst error bit position */
	int	hppat;	/* Burst error bit pattern */
#ifndef	RJP04
	int	hpbae;	/* 11/70 bus extension register */
	int	hpcs3;	/* Control and Status register 3 */
#endif	RJP04
};

#define	HPADDR	0176700
#define	NDRV	  2	/* the number of real rp04 drives on controller */
#define	MAXDRV	  8	/* the number of drives that can be attached */
#define	NCYLS	411	/* cylinders per pack */
#define	NTRKS	 19	/* tracks per cylinder */
#define	NSECS	 22	/* sectors per track */
#define	TVOL	(sizeof hp_sizes/sizeof hp_sizes[0])	/* total number of volumes */
#define	HPAGE	10	/* number of times this block may be pre-empted for io
					   before io is forced	*/
#define	HPSPL	spl5	/* priority of hp disc */

/*
 *	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;
}
hp_sizes[]
{
/* 00:   0->155 (156cyls) drive 0 */	65208l,   0, 0,
/*     user1:  ADMIN + LIBRARY    */
/* 01: 156->219 ( 64cyls) drive 0 */	26752l, 156, 0,
/*     spare or 'swap' or '/tmp'  */
/* 02: 220->254 ( 35cyls) drive 0 */	14630l, 220, 0,
/*     system disk                */
/* 03: 255->410 (156cyls) drive 0 */	65208l, 255, 0,
/*     user0: STAFF + STUDENTS    */
/* 10:   0->155 (156cyls) drive 1 */	65208l,   0, 1,
/*     srce:  SYSTEM SOURCE       */
/* 11: 156->219 ( 64cyls) drive 1 */	26752l, 156, 1,
/*     /tmp + 'swap'              */
/* 12: 220->254 ( 35cyls) drive 1 */	14630l, 220, 1,
/*     user2: non-agsm users      */
/* 13: 255->410 (156cyls) drive 1 */	65208l, 255, 1,
/*     spare                      */
};

/*
 *	structure of an hp disc queue
 */

struct hpq
{
	char	hpq_flags;		/* flags for qs */
					/* assumed zero - HPOFF==0 */
#define	HPOFF		00	/* drive offline */
#ifdef	PRESEEK
#define	HPSEEK		01	/* seeking flag for q header */
#endif	PRESEEK
#define	HPREADY		02	/* ready for io flag for q header */
#define	HPBUSY		03	/* data transfer in progress */
#define	HPRECAL		04	/* recalibrate in progress */
#define	HPIDLE		05	/* nought doing */

#ifdef	SWEEP
	char	hpq_dirf;		/* current direction of head movement */
#define	UP	1		/* the disc cyls are moving up */
#define	DOWN	0		/* the disc cyls are moving down */

#endif	SWEEP
	struct	buf	*hpq_bufp;	/* pointer to first buffer in queue for this drive */
#ifdef	HPSTATS
	char	hpq_nreq;		/* number of requests in queue - stats */
	char	hpq_rmax;			/* high water mark for q */
	unsigned hp_cyls[NCYLS];	/* accesses per cylinder */
#endif	HPSTATS
} hpq[NDRV];

#ifdef	HPSTATS
/*
 *	gather statistics to decide on optimal interleaving factor.
 *	When starting an io accumulate in hpintlv the count of
 *	each segment to go to io transfer start.
 */
long hpintlv[88];	/* divide disk into 88 segments */
#endif	HPSTATS

int intlv 1;	/* interleaving factor for queueing io on same cylinder */
int hpintst 2;	/* DEBUG try this much look-ahead */


/*
 *	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	hp_cyl		b_scratch	/* at least an int */
#define	hp_sector	b_resid.lobyte
#define	hp_track	b_resid.hibyte
#define	hp_trksec	b_resid
#define	hp_age		av_back

struct	devtab	hptab;

#ifndef	RAW_BUFFER_POOL
struct	buf	hpbuf;
#endif	RAW_BUFFER_POOL

/* various drive commands utilised herein */

#define	GO		 01
#define	PRESET		020	/* read-in-preset */
#define	RECAL		 06	/* recalibrate */
#define	DCLR		010	/* drive clear */
#define	SEEK		 04	/* seek */
#define	SEARCH		030	/* search */
#define	UNLOAD		 02	/* unload pack */

/*	hpds bits	*/

#define	DRY	   0200		/* drive ready */
#define	VV	   0100		/* volume valid */
#define	MOL	 010000		/* medium on line */
#define	ERR	 040000		/* the OR of error bits */
#define	ATA	0100000		/* attention bit */

/*	hpcs1 bits	*/

#define	IE	   0100		/* interrupt enable bit */
#define	RDY	   0200		/* controller ready */
#define	TRE	 040000		/* the OR of error bits */

/*	hper1 bits	*/

#define	ECH	 000100		/* hard ecc error */
#define	AOE	 001000		/* end of drive error */
#define	WLE	 004000		/* Write protect error */
#define	DTE	 010000		/* drive timing error */
#define	OPI	 020000		/* operation incomplete */
#define	UNS	 040000		/* drive unsafe */
#define	DCK	0100000		/* data check error for RHPs and RJPs (DEC) */

/*	hpcs2 bits	*/

#define	CLR	040		/* controller clear */

/*	hpof bits	*/

#define	FMT22	 010000		/* 16bit format for RHPs and RJPs (DEC) */




hpopen(dev)
{
	register int	n;

	/*
	**	If the requested drive is offline -> error
	**	Initialize it if necessary. Error if this fails.
	*/
	if((n = dev.d_minor) >= TVOL)
	{
		u.u_error = EOPENFAIL;
		return;
	}
	n = hp_sizes[n].drive;
	HPSPL();
	if( hpq[n].hpq_flags == HPOFF)
	{
		hpdinit(n);
		if(hpq[n].hpq_flags == HPOFF)
		{
			u.u_error = EOPENFAIL;
		}
	}
	spl0();
}

hpstrategy(bp)
register struct buf	*bp;
{
	register unsigned	p1, p2;
	int dirf;

	p1 = bp->b_dev.d_minor;

	/*
	 *	Validate the request
	 */

	p2 = &hp_sizes[p1];
#ifdef	NO_OFFLINEQ | DROP_OFFLINEQ
	if( p2->nblocks <= bp->b_blkno || hpq[p2->drive] == HPOFF )
#else
	if( p2->nblocks <= bp->b_blkno )
#endif	NO_OFFLINEQ | DROP_OFFLINEQ
	{
		bp->b_flags =| B_ERROR;
		iodone(bp);
		return;
	}

#ifdef	_1170 & RJP04
	if(bp->b_flags & B_PHYS)
	{
		mapalloc(bp);
	}
#endif	_1170 & RJP04

	bp->av_forw = 0;
	bp->hp_age = HPAGE;
	bp->b_error = 0;	/* clear error count */
	bp->hp_cyl = p2->cyloff;

	p1 = bp->b_blkno;
	bp->hp_sector = lrem(p1,NSECS);	/* sector */
	p1 = ldiv(p1,NSECS); /* trk and cyl */
	bp->hp_track = p1 % NTRKS;
	bp->hp_cyl =+ p1/NTRKS;
	p2 = &hpq[p2->drive];

/*
 *	Now "hp_cyl" contains "cylinder"
 *	and "hp_trksec" contains the "track and sector"
 */

#ifdef	HPSTATS
	p2->hp_cyls[bp->hp_cyl]++;
	p2->hpq_nreq++;
	if( p2->hpq_nreq > p2->hpq_rmax ) p2->hpq_rmax = p2->hpq_nreq;
#endif	HPSTATS

	HPSPL();
	if ((p1 = p2->hpq_bufp) == NULL)
	{
		/* this queue is empty */
		p2->hpq_bufp = bp;
		hpstart();
	}
	else
	{
		/*
		 * the queue is not empty, so place in queue so as to
		 * minimise head movement.
		 */
#ifdef	SWEEP
		dirf = p2->hpq_dirf;
#endif	SWEEP

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

		for( ; p2 = p1->av_forw; p1 = p2)
		{
			if( p1->hp_cyl<=bp->hp_cyl && bp->hp_cyl<=p2->hp_cyl
			 || p1->hp_cyl>=bp->hp_cyl && bp->hp_cyl>=p2->hp_cyl )
			{
				while (bp->hp_cyl == p2->hp_cyl)
				{
					/*
					** for a cylinder match, do the
					** rotational optimisation.
					** intlv is presumably the optimal value.
					** SEE HPSTATS as to how this could be done
					*/
					if(p2->hp_sector > p1->hp_sector)
					{
						if(bp->hp_sector > p1->hp_sector + intlv
						&& bp->hp_sector < p2->hp_sector - intlv)
							break;
					}
					else
					{
						if(bp->hp_sector > p1->hp_sector + intlv
						|| bp->hp_sector < p2->hp_sector - intlv)
							break;
					}
					p1 = p2;
					if( !(p2 = p1->av_forw) ) break;
				}
				break;
			}

#ifdef SWEEP
			else
			{
				if (dirf == UP)
				{
					if(p2->hp_cyl < p1->hp_cyl)
						if(bp->hp_cyl > p1->hp_cyl)
							break;
						else
							dirf = DOWN;
				}
				else
				{
					if(p2->hp_cyl > p1->hp_cyl)
						if(bp->hp_cyl < p1->hp_cyl)
							break;
						else
							dirf = UP;
				}
			}
#endif	SWEEP
		}

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

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

	spl0();
}

/*
 *	start seeks as required and possibly one data transfer.
 */
hpstart()
{
	/* called at HPSPL or greater */

	register struct buf *bp;
	register struct hpq *qp;
	register int	n;

	static int	drv, ioage;	/* we assume that these are zero initially */


#ifdef	PRESEEK
	for( n = NDRV; --n >= 0;)
	{
		qp = &hpq[n];
		if( (bp = qp->hpq_bufp) && (qp->hpq_flags == HPIDLE) )
		{
			/*
			**	for all available drives start seeking
			*/
			HPADDR->hpcs2 = n;
			if(HPADDR->hpcc == bp->hp_cyl)
			{
				qp->hpq_flags = HPREADY;
			}
			else
			{
				int xx;
#ifdef	SWEEP
				if(bp->hp_cyl > HPADDR->hpcc)
					qp->hpq_dirf = UP;
				else if(bp->hp_cyl < HPADDR->hpcc)
					qp->hpq_dirf = DOWN;
#endif	SWEEP
				xx = bp->hp_sector - hpintst;
				if( xx < 0 ) xx =+ 22;
				xx.hibyte = bp->hp_track;
				HPADDR->hpda = xx;
				HPADDR->hpdc = bp->hp_cyl;
				qp->hpq_flags = HPSEEK;
				HPADDR->hpcs1.lobyte = IE | SEARCH | GO;
			}
		}
	}
#endif	PRESEEK
	/*
	 *	check if possible to start an io
	 */
	for( n = NDRV; --n >= 0;)
		if(hpq[n].hpq_flags == HPBUSY ) return;

	if(HPADDR->hpcs1 & RDY)	/* ensure controller available */
	{
		/*
		**	try to start an IO
		*/
		n = NDRV;
		do
		{
			qp = &hpq[drv];
			if( (bp = qp->hpq_bufp) && qp->hpq_flags == HPREADY)
			{
#ifdef	HPSTATS
				int a;

#endif	HPSTATS
				qp->hpq_flags = HPBUSY;
				HPADDR->hpcs2 = drv;
				HPADDR->hpdc = bp->hp_cyl;
#ifdef	HPSTATS
				a = bp->hp_sector * 4;
				a =- HPADDR->hpla >> 4;
				if( a <= 3 ) a =+ 88;
				hpintlv[a-4]++;
#endif	HPSTATS
#ifdef	RJP04
				rhstart(bp, &HPADDR->hpda, bp->hp_trksec);
#else
				rhstart(bp, &HPADDR->hpda, bp->hp_trksec, &HPADDR->hpbae);
#endif	RJP04
				if( --ioage <= 0)
				{
					ioage = HPAGE;
					if( ++drv >= NDRV)
						drv = 0;
				}
				return;
			}
			if( ++drv >= NDRV) drv=0;
			ioage = HPAGE;
		} while (--n > 0);
	}

	if( !(HPADDR->hpcs1 & IE))
		for(n=0; n<NDRV; n++)
		{
			/*
			 *	if the IE bit has not been set,
			 *	then nothing is happening and nothing
			 *	was started, so find a drive which will accept
			 *	a command and set the IE bit with a nop
			 */

			HPADDR->hpcs2 = n;
			if(HPADDR->hpds & DRY)
			{
				HPADDR->hpcs1 = IE;
				return;
			}
		}

}


hpintr()
{
	/* called at HPSPL or greater */

	register int	n;
	register struct hpq *qp;
	register struct buf *bp;

	/*
	**	An error has occured and/or a data transfer has completed.
	*/


	for(n=0; n<NDRV; n++)
	{
		HPADDR->hpcs2 = n;	/* select drive */
		qp = &hpq[n];
		if( (HPADDR->hpds & ATA) || qp->hpq_flags == HPBUSY)
		{
			bp = qp->hpq_bufp;
			if( (HPADDR->hpds & MOL) && !(HPADDR->hpds & VV) )
			{
				/* drive has come online */
				printf("HP drive %d turned on\n", n);
				hpdinit(n);
			}
			else if( !(HPADDR->hpds & MOL) )
			{
				/* drive down - disable and flag */
				printf("HP drive %d turned off\n", n);
				qp->hpq_flags = HPOFF;
#ifdef	DROP_OFFLINEQ
		dropq:
				while(bp)
				{
					bp->b_flags =| B_ERROR;
					iodone(bp);
					bp = bp->av_forw;
				}
				qp->hpq_bufp = 0;
#endif	DROP_OFFLINEQ
			}
			else if( HPADDR->hpds & ERR)
			{
				/* a drive error */
				int	er1;	/* saves the error register */

				hpregs(qp);
				er1 = HPADDR->hper1;
				HPADDR->hpcs1.lobyte = IE | DCLR | GO;
				while( !(HPADDR->hpds & DRY));
				if( er1 & (UNS | DTE | OPI) )
				{
					if( HPADDR->hper1 & UNS)
					{
						/* Still unsafe, unload for safety */
						qp->hpq_flags = HPOFF;
						HPADDR->hpcs1.lobyte = IE | UNLOAD | GO;
						printf("HP drive %d UNSAFE\n", n);
						printf("\nSTOP and re-START drive to recover\n\n");
#ifdef	DROP_OFFLINEQ
						goto dropq;
#endif	DROP_OFFLINEQ
					}
					else
					{
						/* Okay now, recal and go */
						qp->hpq_flags = HPRECAL;
						HPADDR->hpcs1.lobyte = IE | RECAL | GO;
					}
				}
#ifdef	HPECC
				else if( (er1 & DCK) && !(er1 & ECH))
				{
					if( hpecc() )
						continue; /* IO resumed */
					else
						HPADDR->hpcs1 = TRE | IE;
						goto trcmplt; /* IO done */
				}
#endif	HPECC
				switch(qp->hpq_flags)
				{
			    case HPOFF:
			    case HPREADY:
			    case HPIDLE:
					break;
			    case HPBUSY:
					HPADDR->hpcs1 = TRE | IE;
					if( er1 & AOE )
					{
						/* This might occur if hp_sizes wrong */
						HPADDR->hpcs1 = TRE | IE;
						goto trcmplt;
					}
					else if( er1 & WLE )
					{
						/* Drive is wrt protected - give up */
						bp->b_flags =| B_ERROR;
						goto unlink;
					}
#ifdef	PRESEEK
			    case HPSEEK:
					qp->hpq_flags = HPIDLE;
#else
					qp->hpq_flags = HPREADY;
#endif	PRESEEK
					goto errec;
			    case HPRECAL:
					if( qp->hpq_bufp )
						goto errec;
				}
			}
			else
			{
				/*
				 * Operation complete. Transfer or seek/recalibrate
				 */
				if( qp->hpq_flags == HPBUSY)
				{
					if(HPADDR->hpcs1 & RDY)
					{
						if( HPADDR->hpcs1 & TRE )
						{
							hpregs(qp);
							HPADDR->hpcs1 = TRE | IE;
				errec:
							if( ++bp->b_error >= 10 )
							{
								bp->b_flags =| B_ERROR;
								goto unlink;
							}
						}
						else
						{
							/* Transfer complete SUCCESS! */
				trcmplt:
							bp->b_resid = HPADDR->hpwc;
				unlink:
							qp->hpq_bufp = bp->av_forw;
							iodone(bp);
						}
					}
					else
					{
						continue;
					}
				}
#ifdef	PRESEEK
				if( qp->hpq_flags == HPSEEK )
					qp->hpq_flags = HPREADY;
				else if(qp->hpq_flags != HPRECAL)
					qp->hpq_flags = HPIDLE;
#else
				if(qp->hpq_flags != HPRECAL)
					qp->hpq_flags = HPREADY;
#endif	PRESEEK
			}
	clear:
			HPADDR->hpas = 1 << n;
		}
	}
	hpstart();

}

hpregs(qp)
struct hpq	*qp;
{
	static struct hperrmsgs
	{
			char *str;	/* identify which register */
			int  *reg;	/* address of device register */
	}
	hperrmsgs[]
	{
			"ER1", &HPADDR->hper1,
			"CS1", &HPADDR->hpcs1,
			"DS",  &HPADDR->hpds,
			"CS2", &HPADDR->hpcs2,
			"WC",  &HPADDR->hpwc,
			"BA",  &HPADDR->hpba,
			"DC",  &HPADDR->hpdc,
			"DA",  &HPADDR->hpda,
			"AS",  &HPADDR->hpas,
			0
	};
	register struct hperrmsgs *p = hperrmsgs;

	printf("HPERR\t");
	do
	{
			printf("%s=%o, ", p->str, *(p->reg) );
			p++;
	} while( p->str );
	printf("FLAGS=%o\n",qp->hpq_flags);
}

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

hpread(dev)
int	dev;
{
	hpphys( dev , B_READ );
}

hpwrite(dev)
int	dev;
{
	hpphys( dev , B_WRITE );
}

hpphys( dev , flag )
int	dev;
int	flag;	/* B_READ or B_WRITE */
{
	register unsigned a, b, c;

	a = hp_sizes[dev.d_minor & 07].nblocks;
	b = u.u_offset >> 9;
	if(b >= a)
	{
		u.u_error = ENXIO;
		return;
	}
	c = u.u_count;
	if(ldiv(c+511, 512) + b > a)
		c = (a - b) << 9;
	a = u.u_count - c;
	u.u_count = c;
#ifdef	RAW_BUFFER_POOL
	physio(hpstrategy,      0, dev, flag);
#else
	physio(hpstrategy, &hpbuf, dev, flag);
#endif	RAW_BUFFER_POOL
	u.u_count =+ a;
}

hpdinit(n)
register n;
{
	/* called at HPSPL or greater */

	HPADDR->hpcs2 = n;
	if(HPADDR->hpds & MOL)	/* this may generate a NED error */
				/* hpintr will handle it */
	{
		HPADDR->hpcs1.lobyte = IE | DCLR | GO;
		/*
		**	possibly some risk in these while loops here and
		**	in previous code in that if the drive is maybe
		**	MOL & !(DRY) ever....... not very likely and you
		**	would be buggered if it was.....
		*/
		while( !(HPADDR->hpds & DRY) );
		HPADDR->hpcs1.lobyte = IE | PRESET | GO;
		while( !(HPADDR->hpds & DRY) );
		HPADDR->hpof = FMT22;
#ifdef	PRESEEK
		hpq[n].hpq_flags = HPIDLE;
#else
		hpq[n].hpq_flags = HPREADY;
#endif	PRESEEK
	}
}

#ifdef	POWER_FAIL
hppowf(rdev)
{
	/*
	 *	hp 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 hpq	*qp;
	register	d;

	long	n;

	HPADDR->hpcs2=CLR;
	for(d=0; d< NDRV; d++)
		hpq[n].hpq_flags = HPOFF;
	if(rdev)
	{
		for(d=0; d<NDRV; d++)
			{
				/*
				 *	this drive was on when it died
				 */

				HPADDR->hpcs2 = d;
				for(;;)
				{
					if( (HPADDR->hpcs1 & RDY) && (HPADDR->hpds & MOL) && (HPADDR->hpds & DRY) )
					{
						hpdinit(d);
						break;
					}
					for(n=2000000; n>0; n--);
					printf("HPERR PWR FAIL RECVRY WAITING DRIVE %o\n", d);
				}
			}
	}
	hpstart();

}
#endif	POWER_FAIL