#define AGSM /* * Optimised RHP04, RJP04, RM02 and RM03 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. * * RM02 features added by Craig McGregor Architecture */ #include "../defines.h" #include "../param.h" #include "../buf.h" #include "../conf.h" #include "../user.h" #include "../seg.h" /* (no PRESEEK) #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 */ /* (RHP04) #define RM02 /* define if RM02 or RM03 drive */ /* if RM03 - define RJP04 */ /* (no ECC) #define ECC /* enable data check correction */ /* #define RHSTATS /* 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 rhcs1; /* Control and Status register 1 */ int rhwc; /* Word Count register */ int rhba; /* Bus address register */ int rhda; /* Desired disc address - sector/track */ int rhcs2; /* Control and Status register 2 */ int rhds; /* Drive status register */ int rher1; /* Error register 1 */ int rhas; /* Attention summary register */ int rhla; /* Disc position look-ahead register */ int rhdb; /* Data buffer */ int rhmr; /* Maintenance register */ int rhdt; /* Drive type encoding */ int rhsn; /* Drive serial number */ int rhof; /* Offset register - contains fmt16 bit */ int rhdc; /* Desired cylinder address register */ int rhcc; /* Current cylinder address register */ int rher2; /* Error register 2 */ int rher3; /* Error register 3 */ int rhpos; /* Burst error bit position */ int rhpat; /* Burst error bit pattern */ #ifndef RJP04 int rhbae; /* 11/70 bus extension register */ int rhcs3; /* Control and Status register 3 */ #endif RJP04 }; #ifndef RM02 /* rp04 characteristics */ #define NCYLS 411 /* cylinders per pack */ #define NTRKS 19 /* tracks per cylinder */ #define NSECS 22 /* sectors per track */ /* rp04 names */ #define RHOPEN hpopen #define RHSTRATEGY hpstrategy #define RHSTART hpstart #define RHINTR hpintr #define RHREGS hpregs #define RHECC hpecc #define RHREAD hpread #define RHWRITE hpwrite #define RHPHYS hpphys #define RHDINIT hpdinit #define RHPOWF hppowf #define RH_SIZES hp_sizes #define RHQ hpq #define RHINTLV hpintlv #define RHINTST hpintst #define RHTAB hptab #define RHBUF hpbuf #else RM02 /* rm02 characteristics */ #define NCYLS 823 /* cylinders per pack */ #define NTRKS 5 /* tracks per cylinder */ #define NSECS 32 /* sectors per track */ /* rm02 names */ #define RHOPEN rmopen #define RHSTRATEGY rmstrategy #define RHSTART rmstart #define RHINTR rmintr #define RHREGS rmregs #define RHECC rmecc #define RHREAD rmread #define RHWRITE rmwrite #define RHPHYS rmphys #define RHDINIT rmdinit #define RHPOWF rmpowf #define RH_SIZES rm_sizes #define RHQ rmq #define RHINTLV rmintlv #define RHINTST rmintst #define RHTAB rmtab #define RHBUF rmbuf #endif RM02 #define RHADDR 0176700 #define MAXDRV 8 /* the number of drives that can be attached */ #define TVOL (sizeof RH_SIZES/sizeof RH_SIZES[0]) /* total number of volumes */ #define RHAGE 10 /* number of times this block may be pre-empted for io before io is forced */ #define RHSPL spl5 /* priority of rh disc */ #ifdef AGSM #define NDRV 2 /* the number of real rp04/rm02 drives on controller */ #define PRESEEK #endif AGSM #ifdef ARCH #define NDRV 1 /* the number of real rp04/rm02 drives on controller */ #endif ARCH /* * 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; } RH_SIZES[] { #ifdef AGSM /* 00: 0->155 (156cyls) drive 0 */ 65208, 0, 0, /* user1: ADMIN + LIBRARY */ /* 01: 156->219 (64cyls) drive 0 */ 26752, 156, 0, /* spare or 'swap' or '/tmp' */ /* 02: 220->254 (35cyls) drive 0 */ 14630, 220, 0, /* system disk */ /* 03: 255->410 (156cyls) drive 0 */ 65208, 255, 0, /* user0: STAFF + STUDENTS */ /* 10: 0->155 (156cyls) drive 1 */ 65208, 0, 1, /* srce: SYSTEM SOURCE */ /* 11: 156->219 (64cyls) drive 1 */ 26752, 156, 1, /* /tmp + 'swap' */ /* 12: 220->254 (35cyls) drive 1 */ 14630, 220, 1, /* user2: non-agsm users */ /* 13: 255->410 (156cyls) drive 1 */ 65208, 255, 1, /* spare */ #endif AGSM #ifdef ARCH /* 00: 0->324 (325cyls) drive 0 */ 52000, 0, 0, /* user1: spare + small users */ /* 01: 325->426 (102cyls) drive 0 */ 16320, 325, 0, /* 'swap' or '/tmp' */ /* 02: 427->497 (71cyls) drive 0 */ 11360, 427, 0, /* system disk */ /* 03: 498->822 (325cyls) drive 0 */ 52000, 498, 0, /* user0: users */ #endif ARCH }; /* * structure of an rh disc queue */ struct rhq { char rhq_flags; /* flags for qs */ /* assumed zero - RHOFF==0 */ #define RHOFF 00 /* drive offline */ #ifdef PRESEEK #define RHSEEK 01 /* seeking flag for q header */ #endif PRESEEK #define RHREADY 02 /* ready for io flag for q header */ #define RHBUSY 03 /* data transfer in progress */ #define RHRECAL 04 /* recalibrate in progress */ #define RHIDLE 05 /* nought doing */ #ifdef SWEEP char rhq_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 *rhq_bufp; /* pointer to first buffer in queue for this drive */ #ifdef RHSTATS char rhq_nreq; /* number of requests in queue - stats */ char rhq_rmax; /* high water mark for q */ unsigned rh_cyls[NCYLS]; /* accesses per cylinder */ #endif RHSTATS } RHQ[NDRV]; #ifdef RHSTATS /* * gather statistics to decide on optimal interleaving factor. * When starting an io accumulate in RHINTLV the count of * each segment to go to io transfer start. */ long RHINTLV[NSECS*4]; /* divide disk into NSECS*4 segments */ #endif RHSTATS int intlv 1; /* interleaving factor for queueing io on same cylinder */ int RHINTST 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 rh_cyl b_scratch /* at least an int */ #define rh_sector b_resid.lobyte #define rh_track b_resid.hibyte #define rh_trksec b_resid #define rh_age av_back struct devtab RHTAB; #ifndef RAW_BUFFER_POOL struct buf RHBUF; #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 */ /* rhds 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 */ /* rhcs1 bits */ #define IE 0100 /* interrupt enable bit */ #define RDY 0200 /* controller ready */ #define TRE 040000 /* the OR of error bits */ /* rher1 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) */ /* rhcs2 bits */ #define CLR 040 /* controller clear */ /* rhof bits */ #define FMT16 010000 /* 16bit format (as opposed to 18-bit format) (DEC) */ RHOPEN(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 = RH_SIZES[n].drive; RHSPL(); if (RHQ[n].rhq_flags == RHOFF) { RHDINIT(n); if (RHQ[n].rhq_flags == RHOFF) { u.u_error = EOPENFAIL; } } spl0(); } RHSTRATEGY(bp) register struct buf *bp; { register unsigned p1, p2; int dirf; p1 = bp->b_dev.d_minor; /* * Validate the request */ p2 = &RH_SIZES[p1]; #ifdef NO_OFFLINEQ | DROP_OFFLINEQ if (p2->nblocks <= bp->b_blkno || RHQ[p2->drive] == RHOFF) #else NO_OFFLINEQ | DROP_OFFLINEQ 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->rh_age = RHAGE; bp->b_error = 0; /* clear error count */ bp->rh_cyl = p2->cyloff; p1 = bp->b_blkno; bp->rh_sector = lrem(p1,NSECS); /* sector */ p1 = ldiv(p1,NSECS); /* trk and cyl */ bp->rh_track = p1 % NTRKS; bp->rh_cyl =+ p1/NTRKS; p2 = &RHQ[p2->drive]; /* * Now "rh_cyl" contains "cylinder" * and "rh_trksec" contains the "track and sector" */ #ifdef RHSTATS p2->rh_cyls[bp->rh_cyl]++; p2->rhq_nreq++; if (p2->rhq_nreq > p2->rhq_rmax) p2->rhq_rmax = p2->rhq_nreq; #endif RHSTATS RHSPL(); if ((p1 = p2->rhq_bufp) == NULL) { /* this queue is empty */ p2->rhq_bufp = bp; RHSTART(); } else { /* * the queue is not empty, so place in queue so as to * minimise head movement. */ #ifdef SWEEP dirf = p2->rhq_dirf; #endif SWEEP p2 = p1->av_forw; while (p2) { /* skip any overtaken blocks */ if (!(p2->rh_age)) p1 = p2; p2 = p2->av_forw; } for (; p2 = p1->av_forw; p1 = p2) { if (p1->rh_cyl<=bp->rh_cyl && bp->rh_cyl<=p2->rh_cyl || p1->rh_cyl>=bp->rh_cyl && bp->rh_cyl>=p2->rh_cyl) { while (bp->rh_cyl == p2->rh_cyl) { /* ** for a cylinder match, do the ** rotational optimisation. ** intlv is presumably the optimal value. ** SEE RHSTATS as to how this could be done */ if (p2->rh_sector > p1->rh_sector) { if (bp->rh_sector > p1->rh_sector + intlv && bp->rh_sector < p2->rh_sector - intlv) break; } else { if (bp->rh_sector > p1->rh_sector + intlv || bp->rh_sector < p2->rh_sector - intlv) break; } p1 = p2; if (!(p2 = p1->av_forw)) break; } break; } #ifdef SWEEP else { if (dirf == UP) { if (p2->rh_cyl < p1->rh_cyl) if (bp->rh_cyl > p1->rh_cyl) break; else dirf = DOWN; } else { if (p2->rh_cyl > p1->rh_cyl) if (bp->rh_cyl < p1->rh_cyl) break; else dirf = UP; } } #endif SWEEP } bp->av_forw = p2; p1->av_forw = bp; while (p2) { /* count down overtaken blocks */ p2->rh_age--; p2 = p2->av_forw; } } spl0(); } /* * start seeks as required and possibly one data transfer. */ RHSTART() { /* called at RHSPL or greater */ register struct buf *bp; register struct rhq *qp; register int n; static int drv, ioage; /* we assume that these are zero initially */ #ifdef PRESEEK for (n = NDRV; --n >= 0;) { qp = &RHQ[n]; if ((bp = qp->rhq_bufp) && (qp->rhq_flags == RHIDLE)) { /* ** for all available drives start seeking */ RHADDR->rhcs2 = n; if (RHADDR->rhcc == bp->rh_cyl) { qp->rhq_flags = RHREADY; } else { int xx; #ifdef SWEEP if (bp->rh_cyl > RHADDR->rhcc) qp->rhq_dirf = UP; else if (bp->rh_cyl < RHADDR->rhcc) qp->rhq_dirf = DOWN; #endif SWEEP xx = bp->rh_sector - RHINTST; if (xx < 0) xx =+ 22; xx.hibyte = bp->rh_track; RHADDR->rhda = xx; RHADDR->rhdc = bp->rh_cyl; qp->rhq_flags = RHSEEK; RHADDR->rhcs1.lobyte = IE | SEARCH | GO; } } } #endif PRESEEK /* * check if possible to start an io */ for (n = NDRV; --n >= 0;) if (RHQ[n].rhq_flags == RHBUSY) return; if (RHADDR->rhcs1 & RDY) /* ensure controller available */ { /* ** try to start an IO */ n = NDRV; do { qp = &RHQ[drv]; if ((bp = qp->rhq_bufp) && qp->rhq_flags == RHREADY) { #ifdef RHSTATS int a; #endif RHSTATS qp->rhq_flags = RHBUSY; RHADDR->rhcs2 = drv; RHADDR->rhdc = bp->rh_cyl; #ifdef RHSTATS a = bp->rh_sector * 4; a =- RHADDR->rhla >> 4; if (a <= 3) a =+ NSECS*4; RHINTLV[a-4]++; #endif RHSTATS #ifdef RJP04 rhstart(bp, &RHADDR->rhda, bp->rh_trksec); #else RJP04 rhstart(bp, &RHADDR->rhda, bp->rh_trksec, &RHADDR->rhbae); #endif RJP04 if (--ioage <= 0) { ioage = RHAGE; if (++drv >= NDRV) drv = 0; } return; } if (++drv >= NDRV) drv=0; ioage = RHAGE; } while (--n > 0); } if (!(RHADDR->rhcs1 & 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 */ RHADDR->rhcs2 = n; if (RHADDR->rhds & DRY) { RHADDR->rhcs1 = IE; return; } } } RHINTR() { /* called at RHSPL or greater */ register int n; register struct rhq *qp; register struct buf *bp; /* ** An error has occured and/or a data transfer has completed. */ for (n=0; n<NDRV; n++) { RHADDR->rhcs2 = n; /* select drive */ qp = &RHQ[n]; if ((RHADDR->rhds & ATA) || qp->rhq_flags == RHBUSY) { bp = qp->rhq_bufp; if ((RHADDR->rhds & MOL) && !(RHADDR->rhds & VV)) { /* drive has come online */ #ifndef RM02 printf("HP drive %d turned on\n", n); #else RM02 printf("RM drive %d turned on\n", n); #endif RM02 RHDINIT(n); } else if (!(RHADDR->rhds & MOL)) { /* drive down - disable and flag */ #ifndef RM02 printf("HP drive %d turned off\n", n); #else RM02 printf("RM drive %d turned off\n", n); #endif RM02 qp->rhq_flags = RHOFF; #ifdef DROP_OFFLINEQ dropq: while (bp) { bp->b_flags =| B_ERROR; iodone(bp); bp = bp->av_forw; } qp->rhq_bufp = 0; #endif DROP_OFFLINEQ } else if (RHADDR->rhds & ERR) { /* a drive error */ int er1; /* saves the error register */ RHREGS(qp); er1 = RHADDR->rher1; RHADDR->rhcs1.lobyte = IE | DCLR | GO; while (!(RHADDR->rhds & DRY)); if (er1 & (UNS | DTE | OPI)) { if (RHADDR->rher1 & UNS) { /* Still unsafe, unload for safety */ qp->rhq_flags = RHOFF; RHADDR->rhcs1.lobyte = IE | UNLOAD | GO; #ifndef RM02 printf("HP drive %d UNSAFE\n", n); #else RM02 printf("RM drive %d UNSAFE\n", n); #endif RM02 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->rhq_flags = RHRECAL; RHADDR->rhcs1.lobyte = IE | RECAL | GO; } } #ifdef ECC else if ((er1 & DCK) && !(er1 & ECH)) { if (RHECC()) continue; /* IO resumed */ else RHADDR->rhcs1 = TRE | IE; goto trcmplt; /* IO done */ } #endif ECC switch(qp->rhq_flags) { case RHOFF: case RHREADY: case RHIDLE: break; case RHBUSY: RHADDR->rhcs1 = TRE | IE; if (er1 & AOE) { /* This might occur if RH_SIZES wrong */ RHADDR->rhcs1 = TRE | IE; goto trcmplt; } else if (er1 & WLE) { /* Drive is wrt protected - give up */ bp->b_flags =| B_ERROR; goto unlink; } #ifdef PRESEEK case RHSEEK: qp->rhq_flags = RHIDLE; #else PRESEEK qp->rhq_flags = RHREADY; #endif PRESEEK goto errec; case RHRECAL: if (qp->rhq_bufp) goto errec; } } else { /* * Operation complete. Transfer or seek/recalibrate */ if (qp->rhq_flags == RHBUSY) { if (RHADDR->rhcs1 & RDY) { if (RHADDR->rhcs1 & TRE) { RHREGS(qp); RHADDR->rhcs1 = TRE | IE; errec: if (++bp->b_error >= 10) { bp->b_flags =| B_ERROR; goto unlink; } } else { /* Transfer complete SUCCESS! */ trcmplt: bp->b_resid = RHADDR->rhwc; unlink: qp->rhq_bufp = bp->av_forw; iodone(bp); } } else { continue; } } #ifdef PRESEEK if (qp->rhq_flags == RHSEEK) qp->rhq_flags = RHREADY; else if (qp->rhq_flags != RHRECAL) qp->rhq_flags = RHIDLE; #else PRESEEK if (qp->rhq_flags != RHRECAL) qp->rhq_flags = RHREADY; #endif PRESEEK } clear: RHADDR->rhas = 1 << n; } } RHSTART(); } RHREGS(qp) struct rhq *qp; { static struct rherrmsgs { char *str; /* identify which register */ int *reg; /* address of device register */ } rherrmsgs[] { "ER1", &RHADDR->rher1, "CS1", &RHADDR->rhcs1, "DS", &RHADDR->rhds, "CS2", &RHADDR->rhcs2, "WC", &RHADDR->rhwc, "BA", &RHADDR->rhba, "DC", &RHADDR->rhdc, "DA", &RHADDR->rhda, "AS", &RHADDR->rhas, 0 }; register struct rherrmsgs *p = rherrmsgs; #ifndef RM02 printf("HPERR\t"); #else RM02 printf("RMERR\t"); #endif RM02 do { printf("%s=%o, ", p->str, *(p->reg)); p++; } while (p->str); printf("FLAGS=%o\n",qp->rhq_flags); } /* * Physical IO: * truncate transfers at the ends of logical volumes */ RHREAD(dev) int dev; { RHPHYS(dev, B_READ); } RHWRITE(dev) int dev; { RHPHYS(dev, B_WRITE); } RHPHYS(dev, flag) int dev; int flag; /* B_READ or B_WRITE */ { register unsigned a, b, c; a = RH_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(&RHSTRATEGY, 0, dev, flag); #else RAW_BUFFER_POOL physio(&RHSTRATEGY, &RHBUF, dev, flag); #endif RAW_BUFFER_POOL u.u_count =+ a; } RHDINIT(n) register n; { /* called at RHSPL or greater */ RHADDR->rhcs2 = n; if (RHADDR->rhds & MOL) /* this may generate a NED error */ /* RHINTR() will handle it */ { RHADDR->rhcs1.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 (!(RHADDR->rhds & DRY)); RHADDR->rhcs1.lobyte = IE | PRESET | GO; while (!(RHADDR->rhds & DRY)); RHADDR->rhof = FMT16; #ifdef PRESEEK RHQ[n].rhq_flags = RHIDLE; #else PRESEEK RHQ[n].rhq_flags = RHREADY; #endif PRESEEK } } #ifdef POWER_FAIL RHPOWF(rdev) { /* * rh 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 rhq *qp; register d; long n; RHADDR->rhcs2=CLR; for (d=0; d< NDRV; d++) RHQ[n].rhq_flags = RHOFF; if (rdev) { for (d=0; d<NDRV; d++) { /* * this drive was on when it died */ RHADDR->rhcs2 = d; for (;;) { if ((RHADDR->rhcs1 & RDY) && (RHADDR->rhds & MOL) && (RHADDR->rhds & DRY)) { RHDINIT(d); break; } for (n=2000000; n>0; n--); #ifndef RM02 printf("HPERR PWR FAIL RECVRY WAITING DRIVE %o\n", d); #else RM02 printf("RMERR PWR FAIL RECVRY WAITING DRIVE %o\n", d); #endif RM02 } } } RHSTART(); } #endif POWER_FAIL #ifdef ARCH help(); #endif ARCH