/* * SCCS id @(#)xp.c 2.1 (Berkeley) 8/23/83 */ /* * RM02/03/05, RP04/05/06, DIVA disk driver. * Fuji 160 added 11/2/84. r2 megatest * SI Eagle added 9/20/83. * This driver will handle most variants of SMD drives * on one or more controllers. * If XP_PROBE is defined, it includes a probe routine * that will determine the number and type of drives attached * to each controller; otherwise, the data structures must be * initialized. * * For simplicity we use hpreg.h instead of an xpreg.h. The bits * are the same. */ #include "xp.h" #if NXP > 0 #include "param.h" #include #include #include #include #include #include #include #ifndef INTRLVE #include #endif #include #define XP_SDIST 2 #define XP_RDIST 6 int xp_offset[] = { HPOF_P400, HPOF_M400, HPOF_P400, HPOF_M400, HPOF_P800, HPOF_M800, HPOF_P800, HPOF_M800, HPOF_P1200, HPOF_M1200, HPOF_P1200, HPOF_M1200, 0, 0, 0, 0 }; /* * Xp_drive and xp_controller may be initialized in ioconf.c, * or they may be filled in at boot time if XP_PROBE is enabled. * Xp_controller address fields must be initialized for any boot devices, * however. */ struct xp_drive xp_drive[NXP]; struct xp_controller xp_controller[NXP_CONTROLLER]; struct buf xptab; struct buf xputab[NXP]; #ifdef INTRLVE extern daddr_t dkblock(); #endif /* * Attach controllers whose addresses are known at boot time. * Stop at the first not found, so that the drive numbering * won't get confused. */ xproot() { register i; register struct hpdevice *xpaddr; for (i = 0; i < NXP_CONTROLLER; i++) if (((xpaddr = xp_controller[i].xp_addr) == 0) || (xpattach(xpaddr, i) == 0)) break; } /* * Attach controller at xpaddr. * Mark as nonexistent if xpaddr is 0; * otherwise attach slaves if probing. * NOTE: if probing for drives, this routine must be called * once per controller, in ascending controller numbers. */ xpattach(xpaddr, unit) register struct hpdevice *xpaddr; { register struct xp_controller *xc = &xp_controller[unit]; #ifdef XP_PROBE static int last_attached = -1; #endif if ((unsigned) unit >= NXP_CONTROLLER) return(0); if ((xpaddr != 0) && (fioword(xpaddr) != -1)) { xc->xp_addr = xpaddr; if (fioword(&(xpaddr->hpbae)) != -1) xc->xp_flags |= XP_RH70; #ifdef XP_PROBE /* * If already attached, ignore (don't want to renumber drives) */ if (unit > last_attached) { last_attached = unit; xpslave(xpaddr, xc); } #endif return(1); } xc->xp_addr = 0; return(0); } #ifdef XP_PROBE /* * Determine what drives are attached to a controller; * guess their types and fill in the drive structures. */ xpslave(xpaddr, xc) register struct hpdevice *xpaddr; struct xp_controller *xc; { register struct xp_drive *xd; int j, dummy; static int nxp = 0; #ifdef MEGATEST extern struct size dv_sizes[],hp_sizes[],rm_sizes[],rm5_sizes[],si_sizes[],rm2x_sizes[]; #else extern struct size dv_sizes[],hp_sizes[],rm_sizes[],rm5_sizes[],si_sizes[]; #endif for (j = 0; j < 8; j++) { xpaddr->hpcs1.w = 0; xpaddr->hpcs2.w = j; dummy = xpaddr->hpds; if (xpaddr->hpcs2.w & HPCS2_NED) { xpaddr->hpcs2.w = HPCS2_CLR; continue; } if (nxp < NXP) { xd = &xp_drive[nxp++]; xd->xp_ctlr = xc; xd->xp_unit = j; /* * If drive type is initialized, * believe it. */ if (xd->xp_type == 0) xd->xp_type = xpaddr->hpdt & 077; switch (xd->xp_type) { case RM02: case RM03: xd->xp_nsect = RM_SECT; xd->xp_ntrack = RM_TRAC; xd->xp_nspc = RM_SECT * RM_TRAC; xd->xp_sizes = &rm_sizes; xd->xp_ctlr->xp_flags |= XP_NOCC; break; #ifdef MEGATEST case RM2X: xd->xp_nsect = RM2X_SECT; xd->xp_ntrack = RM2X_TRAC; xd->xp_nspc = RM2X_SECT * RM2X_TRAC; xd->xp_sizes = &rm2x_sizes; xd->xp_ctlr->xp_flags |= XP_NOCC; break; #endif MEGATEST case RM05: case RM5X: if ((xpaddr->hpsn & SI_SN_MSK) == SI_SN_DT) { xd->xp_nsect = SI_SECT; xd->xp_ntrack = SI_TRAC; xd->xp_nspc = SI_SECT * SI_TRAC; xd->xp_sizes = &si_sizes; xd->xp_ctlr->xp_flags |= XP_NOCC; } else { xd->xp_nsect = RM5_SECT; xd->xp_ntrack = RM5_TRAC; xd->xp_nspc = RM5_SECT * RM5_TRAC; xd->xp_sizes = &rm5_sizes; xd->xp_ctlr->xp_flags |= XP_NOCC; } break; case RP: xd->xp_nsect = HP_SECT; xd->xp_ntrack = HP_TRAC; xd->xp_nspc = HP_SECT * HP_TRAC; xd->xp_sizes = &hp_sizes; break; case DV: xd->xp_nsect = DV_SECT; xd->xp_ntrack = DV_TRAC; xd->xp_nspc = DV_SECT * DV_TRAC; xd->xp_sizes = &dv_sizes; xd->xp_ctlr->xp_flags |= XP_NOSEARCH; break; default: printf("xp%d: drive type %o unrecognized\n", nxp - 1, xd->xp_type); xd->xp_ctlr = NULL; break; } } } } #endif XP_PROBE xpstrategy(bp) register struct buf *bp; { register struct xp_drive *xd; register unit; struct buf *dp; short pseudo_unit; int s; long bn; unit = dkunit(bp); pseudo_unit = minor(bp->b_dev) & 07; if ((unit >= NXP) || ((xd = &xp_drive[unit])->xp_ctlr == 0) || (xd->xp_ctlr->xp_addr == 0)) { bp->b_error = ENXIO; goto errexit; } if ((bp->b_blkno < 0) || ((bn = dkblock(bp)) + ((bp->b_bcount + 511) >> 9) > xd->xp_sizes[pseudo_unit].nblocks)) { bp->b_error = EINVAL; errexit: bp->b_flags |= B_ERROR; iodone(bp); return; } if ((xd->xp_ctlr->xp_flags & XP_RH70) == 0) mapalloc(bp); #ifdef EXT_LSI if(bp->b_flags & B_PHYS) if(Sofub_alloc(bp) == 0) return; #endif bp->b_cylin = bn / xd->xp_nspc + xd->xp_sizes[pseudo_unit].cyloff; dp = &xputab[unit]; s = spl5(); disksort(dp, bp); if (dp->b_active == 0) { xpustart(unit); if (xd->xp_ctlr->xp_active == 0) xpstart(xd->xp_ctlr); } splx(s); } /* * Unit start routine. * Seek the drive to where the data are * and then generate another interrupt * to actually start the transfer. * If there is only one drive * or we are very close to the data, don't * bother with the search. If called after * searching once, don't bother to look * where we are, just queue for transfer (to avoid * positioning forever without transferring). */ xpustart(unit) int unit; { register struct xp_drive *xd; register struct hpdevice *xpaddr; register struct buf *dp; struct buf *bp; daddr_t bn; int sn, cn, csn; xd = &xp_drive[unit]; xpaddr = xd->xp_ctlr->xp_addr; xpaddr->hpcs2.w = xd->xp_unit; xpaddr->hpcs1.c[0] = HP_IE; xpaddr->hpas = 1 << xd->xp_unit; if (unit >= NXP) return; #ifdef XP_DKN dk_busy &= ~(1 << (unit + XP_DKN)); #endif dp = &xputab[unit]; if ((bp=dp->b_actf) == NULL) return; /* * If we have already positioned this drive, * then just put it on the ready queue. */ if (dp->b_active) goto done; dp->b_active++; /* * If drive has just come up, * set up the pack. */ if ((xpaddr->hpds & HPDS_VV) == 0) { /* SHOULD WARN SYSTEM THAT THIS HAPPENED */ xpaddr->hpcs1.c[0] = HP_IE | HP_PRESET | HP_GO; xpaddr->hpof = HPOF_FMT22; } #if NXP > 1 /* * If drive is offline, forget about positioning. */ if ((xpaddr->hpds & (HPDS_DPR | HPDS_MOL)) != (HPDS_DPR | HPDS_MOL)) goto done; /* * Figure out where this transfer is going to * and see if we are close enough to justify not searching. */ bn = dkblock(bp); cn = bp->b_cylin; sn = bn % xd->xp_nspc; sn += xd->xp_nsect - XP_SDIST; sn %= xd->xp_nsect; if (((xd->xp_ctlr->xp_flags & XP_NOCC) && (xd->xp_cc != cn)) || xpaddr->hpcc != cn) goto search; if (xd->xp_ctlr->xp_flags & XP_NOSEARCH) goto done; csn = (xpaddr->hpla >> 6) - sn + XP_SDIST - 1; if (csn < 0) csn += xd->xp_nsect; if (csn > xd->xp_nsect - XP_RDIST) goto done; search: xpaddr->hpdc = cn; xpaddr->hpda = sn; xpaddr->hpcs1.c[0] = (xd->xp_ctlr->xp_flags & XP_NOSEARCH)? (HP_IE | HP_SEEK | HP_GO) : (HP_IE | HP_SEARCH | HP_GO); xd->xp_cc = cn; #ifdef XP_DKN /* * Mark unit busy for iostat. */ unit += XP_DKN; dk_busy |= 1 << unit; dk_numb[unit]++; #endif XP_DKN return; #endif NXP > 1 done: /* * Device is ready to go. * Put it on the ready queue for the controller. */ dp->b_forw = NULL; if (xd->xp_ctlr->xp_actf == NULL) xd->xp_ctlr->xp_actf = dp; else xd->xp_ctlr->xp_actl->b_forw = dp; xd->xp_ctlr->xp_actl = dp; } /* * Start up a transfer on a controller. */ xpstart(xc) register struct xp_controller *xc; { register struct hpdevice *xpaddr; register struct buf *bp; struct xp_drive *xd; struct buf *dp; int unit; short pseudo_unit; daddr_t bn; int sn, tn, cn; xpaddr = xc->xp_addr; loop: /* * Pull a request off the controller queue. */ if ((dp = xc->xp_actf) == NULL) return; if ((bp = dp->b_actf) == NULL) { xc->xp_actf = dp->b_forw; goto loop; } /* * Mark controller busy and * determine destination of this request. */ xc->xp_active++; pseudo_unit = minor(bp->b_dev) & 07; unit = dkunit(bp); xd = &xp_drive[unit]; bn = dkblock(bp); cn = xd->xp_sizes[pseudo_unit].cyloff; cn += bn / xd->xp_nspc; sn = bn % xd->xp_nspc; tn = sn / xd->xp_nsect; sn = sn % xd->xp_nsect; /* * Select drive. */ xpaddr->hpcs2.w = xd->xp_unit; /* * Check that it is ready and online. */ if ((xpaddr->hpds & (HPDS_DPR | HPDS_MOL)) != (HPDS_DPR | HPDS_MOL)) { xc->xp_active = 0; dp->b_errcnt = 0; dp->b_actf = bp->av_forw; bp->b_flags |= B_ERROR; iodone(bp); goto loop; } if (dp->b_errcnt >= 16 && (bp->b_flags & B_READ)) { xpaddr->hpof = xp_offset[dp->b_errcnt & 017] | HPOF_FMT22; xpaddr->hpcs1.w = HP_OFFSET | HP_GO; while ((xpaddr->hpds & (HPDS_PIP | HPDS_DRY)) != HPDS_DRY) ; } xpaddr->hpdc = cn; xpaddr->hpda = (tn << 8) + sn; xpaddr->hpba = bp->b_un.b_addr; if (xc->xp_flags & XP_RH70) xpaddr->hpbae = bp->b_xmem; xpaddr->hpwc = -(bp->b_bcount >> 1); /* * Warning: unit is being used as a temporary. */ unit = ((bp->b_xmem & 3) << 8) | HP_IE | HP_GO; if (bp->b_flags & B_READ) unit |= HP_RCOM; else unit |= HP_WCOM; xpaddr->hpcs1.w = unit; #ifdef XP_DKN unit = xc - &xp_controller[0] + XP_DKN + NXP; dk_busy |= 1 << unit; dk_numb[unit]++; dk_wds[unit] += bp->b_bcount >> 6; #endif } /* * Handle a disk interrupt. */ xpintr(dev) int dev; { register struct hpdevice *xpaddr; register struct buf *dp; struct xp_controller *xc; struct xp_drive *xd; struct buf *bp; register unit; int as, i, j; xc = &xp_controller[dev]; xpaddr = xc->xp_addr; as = xpaddr->hpas & 0377; if (xc->xp_active) { #ifdef XP_DKN dk_busy &= ~(1 << (dev + XP_DKN + NXP)); #endif /* * Get device and block structures. Select the drive. */ dp = xc->xp_actf; bp = dp->b_actf; unit = dkunit(bp); xd = &xp_drive[unit]; xpaddr->hpcs2.c[0] = xd->xp_unit; /* * Check for and process errors. */ if (xpaddr->hpcs1.w & HP_TRE) { while ((xpaddr->hpds & HPDS_DRY) == 0) ; if (xpaddr->hper1 & HPER1_WLE) { /* * Give up on write locked deviced * immediately. */ printf("xp%d: write locked\n", unit); bp->b_flags |= B_ERROR; } else { /* * After 28 retries (16 without offset and * 12 with offset positioning), give up. */ if (++dp->b_errcnt > 28) { #ifdef UCB_DEVERR harderr(bp, "xp"); printf("cs2=%b er1=%b\n", xpaddr->hpcs2.w, HPCS2_BITS, xpaddr->hper1, HPER1_BITS); #else deverror(bp, xpaddr->hpcs2.w, xpaddr->hper1); #endif bp->b_flags |= B_ERROR; } else xc->xp_active = 0; } #ifdef UCB_ECC /* * If soft ecc, correct it (continuing * by returning if necessary). * Otherwise, fall through and retry the transfer. */ if((xpaddr->hper1 & (HPER1_DCK|HPER1_ECH)) == HPER1_DCK) if (xpecc(bp)) return; #endif xpaddr->hpcs1.w = HP_TRE | HP_IE | HP_DCLR | HP_GO; if ((dp->b_errcnt & 07) == 4) { xpaddr->hpcs1.w = HP_RECAL | HP_IE | HP_GO; while ((xpaddr->hpds & (HPDS_PIP | HPDS_DRY)) != HPDS_DRY) ; } xd->xp_cc = -1; } if (xc->xp_active) { if (dp->b_errcnt) { xpaddr->hpcs1.w = HP_RTC | HP_GO; while ((xpaddr->hpds & (HPDS_PIP | HPDS_DRY)) != HPDS_DRY) ; } xc->xp_active = 0; xc->xp_actf = dp->b_forw; dp->b_active = 0; dp->b_errcnt = 0; dp->b_actf = bp->b_actf; xd->xp_cc = bp->b_cylin; bp->b_resid = - (xpaddr->hpwc << 1); #ifdef EXT_LSI if(bp->b_flags & B_PHYS) Sofub_relse(bp,bp->b_bcount); #endif iodone(bp); xpaddr->hpcs1.w = HP_IE; if (dp->b_actf) xpustart(unit); } as &= ~(1 << xp_drive[unit].xp_unit); } else { if (as == 0) xpaddr->hpcs1.w = HP_IE; xpaddr->hpcs1.c[1] = HP_TRE >> 8; } for (unit = 0; unit < NXP; unit++) if ((xp_drive[unit].xp_ctlr == xc) && (as & (1 << xp_drive[unit].xp_unit))) xpustart(unit); xpstart(xc); } #ifdef UCB_ECC #define exadr(x,y) (((long)(x) << 16) | (unsigned)(y)) /* * Correct an ECC error and restart the i/o to complete * the transfer if necessary. This is quite complicated because * the correction may be going to an odd memory address base * and the transfer may cross a sector boundary. */ xpecc(bp) register struct buf *bp; { register struct xp_drive *xd; register struct hpdevice *xpaddr; register unsigned byte; ubadr_t bb, addr; long wrong; int bit, wc; unsigned ndone, npx; int ocmd; int cn, tn, sn; daddr_t bn; struct ubmap *ubp; int unit; /* * ndone is #bytes including the error * which is assumed to be in the last disk page transferred. */ unit = dkunit(bp); xd = &xp_drive[unit]; xpaddr = xd->xp_ctlr->xp_addr; wc = xpaddr->hpwc; ndone = (wc * NBPW) + bp->b_bcount; npx = ndone / PGSIZE; printf("xp%d%c: soft ecc bn %D\n", unit, 'a' + (minor(bp->b_dev) & 07), bp->b_blkno + (npx - 1)); wrong = xpaddr->hpec2; if (wrong == 0) { xpaddr->hpof = HPOF_FMT22; xpaddr->hpcs1.w |= HP_IE; return (0); } /* * Compute the byte/bit position of the err * within the last disk page transferred. * Hpec1 is origin-1. */ byte = xpaddr->hpec1 - 1; bit = byte & 07; byte >>= 3; byte += ndone - PGSIZE; bb = exadr(bp->b_xmem, bp->b_un.b_addr); wrong <<= bit; /* * Correct until mask is zero or until end of transfer, * whichever comes first. */ while (byte < bp->b_bcount && wrong != 0) { addr = bb + byte; if (bp->b_flags & (B_MAP|B_UBAREMAP)) { /* * Simulate UNIBUS map if UNIBUS transfer. */ ubp = UBMAP + ((addr >> 13) & 037); addr = exadr(ubp->ub_hi, ubp->ub_lo) + (addr & 017777); } putmemc(addr, getmemc(addr) ^ (int) wrong); byte++; wrong >>= 8; } xd->xp_ctlr->xp_active++; if (wc == 0) return (0); /* * Have to continue the transfer. Clear the drive * and compute the position where the transfer is to continue. * We have completed npx sectors of the transfer already. */ ocmd = (xpaddr->hpcs1.w & ~HP_RDY) | HP_IE | HP_GO; xpaddr->hpcs2.w = xd->xp_unit; xpaddr->hpcs1.w = HP_TRE | HP_DCLR | HP_GO; bn = dkblock(bp); cn = bp->b_cylin - (bn / xd->xp_nspc); bn += npx; addr = bb + ndone; cn += bn / xd->xp_nspc; sn = bn % xd->xp_nspc; /* tn = sn / xd->xp_nsect; CC complains about this ??? */ tn = sn; tn /= xd->xp_nsect; sn %= xd->xp_nsect; xpaddr->hpdc = cn; xpaddr->hpda = (tn << 8) + sn; xpaddr->hpwc = ((int)(ndone - bp->b_bcount)) / NBPW; xpaddr->hpba = (int) addr; if (xd->xp_ctlr->xp_flags & XP_RH70) xpaddr->hpbae = (int) (addr >> 16); xpaddr->hpcs1.w = ocmd; return (1); } #endif UCB_ECC #if defined(XP_DUMP) && defined(UCB_AUTOBOOT) /* * Dump routine. * Dumps from dumplo to end of memory/end of disk section for minor(dev). */ #define DBSIZE 16 /* number of blocks to write */ xpdump(dev) dev_t dev; { /* ONLY USE 2 REGISTER VARIABLES, OR C COMPILER CHOKES */ register struct xp_drive *xd; register struct hpdevice *xpaddr; daddr_t bn, dumpsize; long paddr; int sn, count; extern bool_t ubmap; struct ubmap *ubp; if ((bdevsw[major(dev)].d_strategy != xpstrategy) /* paranoia */ || ((dev=minor(dev)) > (NXP << 3))) return(EINVAL); xd = &xp_drive[dev >> 3]; dev &= 07; if (xd->xp_ctlr == 0) return(EINVAL); xpaddr = xd->xp_ctlr->xp_addr; dumpsize = xd->xp_sizes[dev].nblocks; if ((dumplo < 0) || (dumplo >= dumpsize)) return(EINVAL); dumpsize -= dumplo; xpaddr->hpcs2.w = xd->xp_unit; if ((xpaddr->hpds & HPDS_VV) == 0) { xpaddr->hpcs1.w = HP_DCLR | HP_GO; xpaddr->hpcs1.w = HP_PRESET | HP_GO; xpaddr->hpof = HPOF_FMT22; } if ((xpaddr->hpds & (HPDS_DPR | HPDS_MOL)) != (HPDS_DPR | HPDS_MOL)) return(EFAULT); ubp = &UBMAP[0]; for (paddr = 0L; dumpsize > 0; dumpsize -= count) { count = dumpsize>DBSIZE? DBSIZE: dumpsize; bn = dumplo + (paddr >> PGSHIFT); xpaddr->hpdc = bn / xd->xp_nspc + xd->xp_sizes[dev].cyloff; sn = bn % xd->xp_nspc; xpaddr->hpda = ((sn / xd->xp_nsect) << 8) | (sn % xd->xp_nsect); xpaddr->hpwc = -(count << (PGSHIFT - 1)); if (ubmap && ((xd->xp_ctlr->xp_flags & XP_RH70) == 0)) { ubp->ub_lo = loint(paddr); ubp->ub_hi = hiint(paddr); xpaddr->hpba = 0; xpaddr->hpcs1.w = HP_WCOM | HP_GO; } else { /* * Non-UNIBUS map, or 11/70 RH70 (MASSBUS) */ xpaddr->hpba = loint(paddr); if (xd->xp_ctlr->xp_flags & XP_RH70) xpaddr->hpbae = hiint(paddr); xpaddr->hpcs1.w = HP_WCOM | HP_GO | ((paddr >> 8) & (03 << 8)); } while (xpaddr->hpcs1.w & HP_GO) ; if (xpaddr->hpcs1.w & HP_TRE) { if (xpaddr->hpcs2.w & HPCS2_NEM) return(0); /* made it to end of memory */ return(EIO); } paddr += (DBSIZE << PGSHIFT); } return(0); /* filled disk minor dev */ } #endif XP_DUMP #endif NXP