/* up.c 3.24 09/27/80 */ /* * UNIBUS disk driver with overlapped seeks and ECC recovery. * * This driver works marginally on an Emulex SC-11B controller with rev * level J microcode, defining: * int olducode = 1; * to force CPU stalling delays. * * It has worked with no delays and no problems on a prototype * SC-21 controller. Emulex intends to upgrade all SC-11s on VAXes to SC-21s. * You should get a SC-21 to replace any SC-11 on a VAX. * * SC-11B Controller switch settings: * SW1-1 5/19 surfaces (off, 19 surfaces on Ampex 9300) * SW1-2 chksum enable (off, checksum disabled) * SW1-3 volume select (off, 815 cylinders) * SW1-4 sector select (on, 32 sectors) * SW1-5 unused (off) * SW1-6 port select (on, single port) * SW1-7 npr delay (off, disable) * SW1-8 ecc test mode (off, disable) * and top mounted switches: * SW2-1 extend opcodes (off=open, disable) * SW2-2 extend diag (off=open, disable) * SW2-3 4 wd dma burst (on=closed, enable) * SW2-4 unused (off=open) */ #include "../h/param.h" #include "../h/systm.h" #include "../h/dk.h" #include "../h/buf.h" #include "../h/conf.h" #include "../h/dir.h" #include "../h/user.h" #include "../h/map.h" #include "../h/pte.h" #include "../h/mba.h" #include "../h/mtpr.h" #include "../h/uba.h" #include "../h/vm.h" /* * Define number of drives, and range of sampling information to be used. * * Normally, DK_N .. DK_N+NUP-1 gather individual drive stats, * and DK_N+NUP gathers controller transferring stats. * * If DK_N+NUP > DK_NMAX, then transfer stats are divided per drive. * If DK_NMAX is yet smaller, some drives are not monitored. */ #define DK_N 2 #define DK_NMAX 3 #define ushort unsigned short struct device { ushort upcs1; /* control and status register 1 */ short upwc; /* word count register */ ushort upba; /* UNIBUS address register */ ushort upda; /* desired address register */ ushort upcs2; /* control and status register 2 */ ushort upds; /* drive Status */ ushort uper1; /* error register 1 */ ushort upas; /* attention summary */ ushort upla; /* look ahead */ ushort updb; /* data buffer */ ushort upmr; /* maintenance */ ushort updt; /* drive type */ ushort upsn; /* serial number */ ushort upof; /* offset register */ ushort updc; /* desired cylinder address register */ ushort upcc; /* current cylinder */ ushort uper2; /* error register 2 */ ushort uper3; /* error register 3 */ ushort upec1; /* burst error bit position */ ushort upec2; /* burst error bit pattern */ }; /* * Software extension to the upas register, so we can * postpone starting SEARCH commands until the controller * is not transferring. */ int upsoftas; /* * If upseek then we don't issue SEARCH commands but rather just * settle for a SEEK to the correct cylinder. */ int upseek; #define UPADDR ((struct device *)(UBA0_DEV + 0176700)) #define NUP 2 /* Number of drives this installation */ #define NSECT 32 #define NTRAC 19 /* * Constants controlling on-cylinder SEARCH usage. * * upSDIST/2 msec time needed to start transfer * upRDIST/2 msec tolerable rotational latency when on-cylinder * * If we are no closer than upSDIST sectors and no further than upSDIST+upRDIST * and in the driver then we take it as it is. Otherwise we do a SEARCH * requesting an interrupt upSDIST sectors in advance. */ #define _upSDIST 6 /* 3.0 msec */ #define _upRDIST 6 /* 3.0 msec */ int upSDIST = _upSDIST; int upRDIST = _upRDIST; /* * To fill a 300M drive: * A is designed to be used as a root. * B is suitable for a swap area. * H is the primary storage area. * On systems with RP06'es, we normally use only 291346 blocks of the H * area, and use DEF or G to cover the rest of the drive. The C system * covers the whole drive and can be used for pack-pack copying. */ struct size { daddr_t nblocks; int cyloff; } up_sizes[8] = { 15884, 0, /* A=cyl 0 thru 26 */ 33440, 27, /* B=cyl 27 thru 81 */ 495520, 0, /* C=cyl 0 thru 814 */ 15884, 562, /* D=cyl 562 thru 588 */ 55936, 589, /* E=cyl 589 thru 680 */ 81472, 681, /* F=cyl 681 thru 814 */ 153824, 562, /* G=cyl 562 thru 814 */ 445664, 82, /* H=cyl 82 thru 814 */ /* Later, and more safely for H area... 291346, 82, /* H=cyl 82 thru 561 */ }; /* * The following defines are used in offset positioning * when trying to recover disk errors, with the constants being * +/- microinches. Note that header compare inhibit (HCI) is not * tried (this makes sense only during read, in any case.) * * NOT ALL OF THESE ARE IMPLEMENTED ON 9300!?! */ #define P400 020 #define M400 0220 #define P800 040 #define M800 0240 #define P1200 060 #define M1200 0260 #define HCI 020000 int up_offset[16] = { P400, M400, P400, M400, P800, M800, P800, M800, P1200, M1200, P1200, M1200, 0, 0, 0, 0, }; /* * Each drive has a table uputab[i]. On this table are sorted the * pending requests implementing an elevator algorithm (see dsort.c.) * In the upustart() routine, each drive is independently advanced * until it is on the desired cylinder for the next transfer and near * the desired sector. The drive is then chained onto the uptab * table, and the transfer is initiated by the upstart() routine. * When the transfer is completed the driver reinvokes the upustart() * routine to set up the next transfer. */ struct buf uptab; struct buf uputab[NUP]; struct buf rupbuf; /* Buffer for raw i/o */ /* Drive commands, placed in upcs1 */ #define GO 01 /* Go bit, set in all commands */ #define PRESET 020 /* Preset drive at init or after errors */ #define OFFSET 014 /* Offset heads to try to recover error */ #define RTC 016 /* Return to center-line after OFFSET */ #define SEARCH 030 /* Search for cylinder+sector */ #define SEEK 04 /* Seek to cylinder */ #define RECAL 06 /* Recalibrate, needed after seek error */ #define DCLR 010 /* Drive clear, after error */ #define WCOM 060 /* Write */ #define RCOM 070 /* Read */ /* Other bits of upcs1 */ #define IE 0100 /* Controller wide interrupt enable */ #define TRE 040000 /* Transfer error */ #define RDY 0200 /* Transfer terminated */ /* Drive status bits of upds */ #define PIP 020000 /* Positioning in progress */ #define ERR 040000 /* Error has occurred, DCLR necessary */ #define VV 0100 /* Volume is valid, set by PRESET */ #define DPR 0400 /* Drive has been preset */ #define MOL 010000 /* Drive is online, heads loaded, etc */ #define DRY 0200 /* Drive ready */ /* Bits of upcs2 */ #define CLR 040 /* Controller clear */ /* Bits of uper1 */ #define DCK 0100000 /* Ecc error occurred */ #define ECH 0100 /* Ecc error was unrecoverable */ #define WLE 04000 /* Attempt to write read-only drive */ /* Bits of upof; the offset bits above are also in this register */ #define FMT22 010000 /* 16 bits/word, must be always set */ #define b_cylin b_resid int up_ubinfo; /* Information about UBA usage saved here */ /* * The EMULEX controller balks if accessed quickly after * certain operations. With rev J delays seem to be needed only * when selecting a new unit, and in drive initialization type * like PRESET and DCLR. The following variables control the delay * DELAY(n) is approximately n usec. */ int olducode = 1; int idelay = 500; /* Delay after PRESET or DCLR */ int osdelay = 150; /* Old delay after selecting drive in upcs2 */ int ordelay = 100; /* Old delay after SEARCH */ int oasdel = 100; /* Old delay after clearing bit in upas */ int nsdelay = 25; #define DELAY(N) { register int d; d = N; while (--d > 0); } int nwaitcs2; /* How many sdelay loops ? */ int neasycs2; /* How many sdelay loops not needed ? */ int up_wticks; /* Ticks waiting for interrupt */ int upwstart; /* Have started guardian */ int upwatch(); #ifdef INTRLVE daddr_t dkblock(); #endif /* * Queue an i/o request for a drive, checking first that it is in range. * * A unit start is issued if the drive is inactive, causing * a SEARCH for the correct cylinder/sector. If the drive is * already nearly on the money and the controller is not transferring * we kick it to start the transfer. */ upstrategy(bp) register struct buf *bp; { register struct buf *dp; register unit, xunit; long sz, bn; if (upwstart == 0) { timeout((caddr_t)upwatch, 0, HZ); upwstart++; } xunit = minor(bp->b_dev) & 077; sz = bp->b_bcount; sz = (sz+511) >> 9; /* transfer size in 512 byte sectors */ unit = dkunit(bp); if (unit >= NUP || bp->b_blkno < 0 || (bn = dkblock(bp))+sz > up_sizes[xunit&07].nblocks) { bp->b_flags |= B_ERROR; iodone(bp); return; } bp->b_cylin = bn/(NSECT*NTRAC) + up_sizes[xunit&07].cyloff; dp = &uputab[unit]; (void) spl5(); disksort(dp, bp); if (dp->b_active == 0) { (void) upustart(unit); if (uptab.b_actf && uptab.b_active == 0) (void) upstart(); } (void) spl0(); } /* * Start activity on specified drive; called when drive is inactive * and new transfer request arrives and also when upas indicates that * a SEARCH command is complete. */ upustart(unit) register unit; { register struct buf *bp, *dp; register struct device *upaddr = UPADDR; daddr_t bn; int sn, cn, csn; int didie = 0; /* * Other drivers tend to say something like * upaddr->upcs1 = IE; * upaddr->upas = 1<= NUP) goto out; if (unit+DK_N <= DK_NMAX) dk_busy &= ~(1<<(unit+DK_N)); dp = &uputab[unit]; if ((bp = dp->b_actf) == NULL) goto out; /* * The SC-11B doesn't start SEARCH commands when transfers are * in progress. In fact, it tends to get confused when given * SEARCH'es during transfers, generating interrupts with neither * RDY nor a bit in the upas register. Thus we defer * until an interrupt when a transfer is pending. */ if (uptab.b_active) { upsoftas |= 1<b_active) goto done; dp->b_active = 1; if ((upaddr->upcs2 & 07) != unit) { upaddr->upcs2 = unit; DELAY(olducode ? osdelay : nsdelay); nwaitcs2++; } else neasycs2++; /* * If we have changed packs or just initialized, * then the volume will not be valid; if so, clear * the drive, preset it and put in 16bit/word mode. */ if ((upaddr->upds & VV) == 0) { upaddr->upcs1 = IE|DCLR|GO; DELAY(idelay); upaddr->upcs1 = IE|PRESET|GO; DELAY(idelay); upaddr->upof = FMT22; didie = 1; } if ((upaddr->upds & (DPR|MOL)) != (DPR|MOL)) goto done; /* * Do enough of the disk address decoding to determine * which cylinder and sector the request is on. * If we are on the correct cylinder and the desired sector * lies between upSDIST and upSDIST+upRDIST sectors ahead of us, then * we don't bother to SEARCH but just begin the transfer asap. * Otherwise ask for a interrupt upSDIST sectors ahead. */ bn = dkblock(bp); cn = bp->b_cylin; sn = bn%(NSECT*NTRAC); sn = (sn+NSECT-upSDIST)%NSECT; if (cn - upaddr->updc) goto search; /* Not on-cylinder */ else if (upseek) goto done; /* Ok just to be on-cylinder */ csn = (upaddr->upla>>6) - sn - 1; if (csn < 0) csn += NSECT; if (csn > NSECT-upRDIST) goto done; search: upaddr->updc = cn; if (upseek) upaddr->upcs1 = IE|SEEK|GO; else { upaddr->upda = sn; upaddr->upcs1 = IE|SEARCH|GO; } didie = 1; /* * Mark this unit busy. */ unit += DK_N; if (unit <= DK_NMAX && DK_N+NUP <= DK_NMAX) { dk_busy |= 1<b_forw = NULL; if (uptab.b_actf == NULL) uptab.b_actf = dp; else uptab.b_actl->b_forw = dp; uptab.b_actl = dp; out: return (didie); } /* * Start a transfer; call from top level at spl5() or on interrupt. */ upstart() { register struct buf *bp, *dp; register unit; register struct device *upaddr; daddr_t bn; int dn, sn, tn, cn, cmd; loop: /* * Pick a drive off the queue of ready drives, and * perform the first transfer on its queue. * * Looping here is completely for the sake of drives which * are not present and on-line, for which we completely clear the * request queue. */ if ((dp = uptab.b_actf) == NULL) return (0); if ((bp = dp->b_actf) == NULL) { uptab.b_actf = dp->b_forw; goto loop; } /* * Mark the controller busy, and multi-part disk address. * Select the unit on which the i/o is to take place. */ uptab.b_active++; unit = minor(bp->b_dev) & 077; dn = dkunit(bp); bn = dkblock(bp); cn = up_sizes[unit&07].cyloff; cn += bn/(NSECT*NTRAC); sn = bn%(NSECT*NTRAC); tn = sn/NSECT; sn %= NSECT; upaddr = UPADDR; if ((upaddr->upcs2 & 07) != dn) { upaddr->upcs2 = dn; /* DELAY(sdelay); Provided by ubasetup() */ nwaitcs2++; } else neasycs2++; up_ubinfo = ubasetup(bp, 1); /* Providing delay */ /* * If drive is not present and on-line, then * get rid of this with an error and loop to get * rid of the rest of its queued requests. * (Then on to any other ready drives.) */ if ((upaddr->upds & (DPR|MOL)) != (DPR|MOL)) { printf("!DPR || !MOL, unit %d, ds %o", dn, upaddr->upds); if ((upaddr->upds & (DPR|MOL)) != (DPR|MOL)) { printf("-- hard\n"); uptab.b_active = 0; uptab.b_errcnt = 0; dp->b_actf = bp->av_forw; dp->b_active = 0; bp->b_flags |= B_ERROR; iodone(bp); /* A funny place to do this ... */ ubafree(up_ubinfo), up_ubinfo = 0; goto loop; } printf("-- came back\n"); } /* * If this is a retry, then with the 16'th retry we * begin to try offsetting the heads to recover the data. */ if (uptab.b_errcnt >= 16 && (bp->b_flags&B_WRITE) == 0) { upaddr->upof = up_offset[uptab.b_errcnt & 017] | FMT22; upaddr->upcs1 = IE|OFFSET|GO; DELAY(idelay); while (upaddr->upds & PIP) DELAY(25); } /* * Now set up the transfer, retrieving the high * 2 bits of the UNIBUS address from the information * returned by ubasetup() for the cs1 register bits 8 and 9. */ upaddr->updc = cn; upaddr->upda = (tn << 8) + sn; upaddr->upba = up_ubinfo; upaddr->upwc = -bp->b_bcount / sizeof (short); cmd = (up_ubinfo >> 8) & 0x300; if (bp->b_flags & B_READ) cmd |= IE|RCOM|GO; else cmd |= IE|WCOM|GO; upaddr->upcs1 = cmd; /* * This is a controller busy situation. * Record in dk slot NUP+DK_N (after last drive) * unless there aren't that many slots reserved for * us in which case we record this as a drive busy * (if there is room for that). */ unit = dn+DK_N; if (NUP+DK_N == DK_NMAX) unit = NUP+DK_N; if (unit <= DK_NMAX) { dk_busy |= 1<b_bcount>>6; } return (1); } /* * Handle a device interrupt. * * If the transferring drive needs attention, service it * retrying on error or beginning next transfer. * Service all other ready drives, calling ustart to transfer * their blocks to the ready queue in uptab, and then restart * the controller if there is anything to do. */ upintr() { register struct buf *bp, *dp; register unit; register struct device *upaddr = UPADDR; int as = upaddr->upas & 0377; int oupsoftas; int needie = 1; (void) spl6(); up_wticks = 0; if (uptab.b_active) { /* * The drive is transferring, thus the hardware * (say the designers) will only interrupt when the transfer * completes; check for it anyways. */ if ((upaddr->upcs1 & RDY) == 0) { printf("!RDY: cs1 %o, ds %o, wc %d\n", upaddr->upcs1, upaddr->upds, upaddr->upwc); printf("as=%d act %d %d %d\n", as, uptab.b_active, uputab[0].b_active, uputab[1].b_active); } /* * Mark controller or drive not busy, and check for an * error condition which may have resulted from the transfer. */ dp = uptab.b_actf; bp = dp->b_actf; unit = dkunit(bp); if (DK_N+NUP == DK_NMAX) dk_busy &= ~(1<<(DK_N+NUP)); else if (DK_N+unit <= DK_NMAX) dk_busy &= ~(1<<(DK_N+unit)); if ((upaddr->upcs2 & 07) != unit) { upaddr->upcs2 = unit; DELAY(olducode ? osdelay : nsdelay); nwaitcs2++; } else neasycs2++; if ((upaddr->upds&ERR) || (upaddr->upcs1&TRE)) { /* * An error occurred, indeed. Select this unit * to get at the drive status (a SEARCH may have * intervened to change the selected unit), and * wait for the command which caused the interrupt * to complete (DRY). */ while ((upaddr->upds & DRY) == 0) DELAY(25); /* * After 28 retries (16 w/o servo offsets, and then * 12 with servo offsets), or if we encountered * an error because the drive is write-protected, * give up. Print an error message on the last 2 * retries before a hard failure. */ if (++uptab.b_errcnt > 28 || upaddr->uper1&WLE) bp->b_flags |= B_ERROR; else uptab.b_active = 0; /* To force retry */ if (uptab.b_errcnt > 27) deverror(bp, upaddr->upcs2, upaddr->uper1); /* * If this was a correctible ECC error, let upecc * do the dirty work to correct it. If upecc * starts another READ for the rest of the data * then it returns 1 (having set uptab.b_active). * Otherwise we are done and fall through to * finish up. */ if ((upaddr->uper1&(DCK|ECH))==DCK && upecc(upaddr, bp)) return; /* * Clear the drive and, every 4 retries, recalibrate * to hopefully help clear up seek positioning problems. */ upaddr->upcs1 = TRE|IE|DCLR|GO; DELAY(idelay); needie = 0; if ((uptab.b_errcnt&07) == 4) { upaddr->upcs1 = RECAL|GO|IE; DELAY(idelay); while(upaddr->upds & PIP) DELAY(25); } } /* * If we are still noted as active, then no * (further) retries are necessary. * * Make sure the correct unit is selected, * return it to centerline if necessary, and mark * this i/o complete, starting the next transfer * on this drive with the upustart routine (if any). */ if (uptab.b_active) { if (uptab.b_errcnt >= 16) { upaddr->upcs1 = RTC|GO|IE; DELAY(idelay); while (upaddr->upds & PIP) DELAY(25); needie = 0; } uptab.b_active = 0; uptab.b_errcnt = 0; uptab.b_actf = dp->b_forw; dp->b_active = 0; dp->b_errcnt = 0; dp->b_actf = bp->av_forw; bp->b_resid = (-upaddr->upwc * sizeof(short)); if (bp->b_resid) printf("resid %d ds %o er? %o %o %o\n", bp->b_resid, upaddr->upds, upaddr->uper1, upaddr->uper2, upaddr->uper3); iodone(bp); if(dp->b_actf) if (upustart(unit)) needie = 0; } as &= ~(1<upcs1 & TRE) { upaddr->upcs1 = TRE; DELAY(idelay); } } /* * If we have a unit with an outstanding SEARCH, * and the hardware indicates the unit requires attention, * the bring the drive to the ready queue. * Finally, if the controller is not transferring * start it if any drives are now ready to transfer. */ as |= upsoftas; oupsoftas = upsoftas; upsoftas = 0; for (unit = 0; unit < NUP; unit++) if ((as|oupsoftas) & (1<upas = 1<upcs1 = IE; } upread(dev) { physio(upstrategy, &rupbuf, dev, B_READ, minphys); } upwrite(dev) { physio(upstrategy, &rupbuf, dev, B_WRITE, minphys); } /* * Correct an ECC error, and restart the i/o to complete * the transfer if necessary. This is quite complicated because * the transfer may be going to an odd memory address base and/or * across a page boundary. */ upecc(up, bp) register struct device *up; register struct buf *bp; { struct uba_regs *ubp = (struct uba_regs *)UBA0; register int i; caddr_t addr; int reg, bit, byte, npf, mask, o, cmd, ubaddr; int bn, cn, tn, sn; /* * Npf is the number of sectors transferred before the sector * containing the ECC error, and reg is the UBA register * mapping (the first part of) the transfer. * O is offset within a memory page of the first byte transferred. */ npf = btop((up->upwc * sizeof(short)) + bp->b_bcount) - 1; reg = btop(up_ubinfo&0x3ffff) + npf; o = (int)bp->b_un.b_addr & PGOFSET; printf("%D ", bp->b_blkno+npf); prdev("ECC", bp->b_dev); mask = up->upec2; if (mask == 0) { up->upof = FMT22; /* == RTC ???? */ DELAY(idelay); return (0); } /* * Flush the buffered data path, and compute the * byte and bit position of the error. The variable i * is the byte offset in the transfer, the variable byte * is the offset from a page boundary in main memory. */ ubp->uba_dpr[(up_ubinfo>>28)&0x0f] |= BNE; i = up->upec1 - 1; /* -1 makes 0 origin */ bit = i&07; i = (i&~07)>>3; byte = i + o; /* * Correct while possible bits remain of mask. Since mask * contains 11 bits, we continue while the bit offset is > -11. * Also watch out for end of this block and the end of the whole * transfer. */ while (i < 512 && (int)ptob(npf)+i < bp->b_bcount && bit > -11) { addr = ptob(ubp->uba_map[reg+btop(byte)].pg_pfnum)+ (byte & PGOFSET); putmemc(addr, getmemc(addr)^(mask<upwc == 0) return (0); /* * Have to continue the transfer... clear the drive, * and compute the position where the transfer is to continue. * We have completed npf+1 sectors of the transfer already; * restart at offset o of next sector (i.e. in UBA register reg+1). */ up->upcs1 = TRE|IE|DCLR|GO; DELAY(idelay); bn = dkblock(bp); cn = bp->b_cylin; sn = bn%(NSECT*NTRAC) + npf + 1; tn = sn/NSECT; sn %= NSECT; cn += tn/NTRAC; tn %= NTRAC; up->updc = cn; up->upda = (tn << 8) | sn; ubaddr = (int)ptob(reg+1) + o; up->upba = ubaddr; cmd = (ubaddr >> 8) & 0x300; cmd |= IE|GO|RCOM; up->upcs1 = cmd; return (1); } /* * Reset driver after UBA init. * Cancel software state of all pending transfers * and restart all units and the controller. */ upreset() { int unit; printf(" up"); uptab.b_active = 0; uptab.b_actf = uptab.b_actl = 0; if (DK_N+NUP == DK_NMAX) dk_busy &= ~(1<<(DK_N+NUP)); if (up_ubinfo) { printf("<%d>", (up_ubinfo>>28)&0xf); ubafree(up_ubinfo), up_ubinfo = 0; } UPADDR->upcs2 = CLR; /* clear controller */ DELAY(idelay); for (unit = 0; unit < NUP; unit++) { uputab[unit].b_active = 0; (void) upustart(unit); } (void) upstart(); } /* * Wake up every second and if an interrupt is pending * but nothing has happened increment a counter. * If nothing happens for 20 seconds, reset the controller * and begin anew. */ upwatch() { int i; timeout((caddr_t)upwatch, 0, HZ); if (uptab.b_active == 0) { for (i = 0; i < NUP; i++) if (uputab[i].b_active) goto active; up_wticks = 0; /* idling */ return; } active: up_wticks++; if (up_wticks >= 20) { up_wticks = 0; printf("LOST INTERRUPT RESET"); upreset(); printf("\n"); } }