Ausam/sys/dmr/hp.c
/*
* 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