/* * Copyright (c) 1986 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. * * @(#)xp.c 2.6 (2.11BSD GTE) 1998/4/3 */ /* * RM02/03/05, RP04/05/06/07, CDC 9766, SI, Fuji 160 and Eagle. This * driver will handle most variants of SMD drives on one or more controllers. */ #include "xp.h" #if NXPD > 0 #include "param.h" #include "../machine/seg.h" #include "systm.h" #include "buf.h" #include "conf.h" #include "user.h" #include "hpreg.h" #include "dkbad.h" #include "dk.h" #include "disklabel.h" #include "disk.h" #include "file.h" #include "map.h" #include "uba.h" #include "stat.h" #include "syslog.h" #define XP_SDIST 2 #define XP_RDIST 6 /* * 'xp' is unique amoung 2.11BSD disk drivers. Drives are not organized * into groups of 8 drives per controller. Instead drives are numbered * across controllers: the second drive ("unit 1") could be on the 2nd * controller. This has the effect of turning the high 5 bits of the minor * device number into the unit number and drives are thus numbered 0 thru 31. * * NOTE: this is different than /boot's view of the world. Sigh. */ #define XPUNIT(dev) (dkunit(dev) & 0x1f) 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, }; struct xp_controller { struct buf *xp_actf; /* pointer to next active xputab */ struct buf *xp_actl; /* pointer to last active xputab */ struct hpdevice *xp_addr; /* csr address */ char xp_rh70; /* massbus flag */ char xp_active; /* nonzero if doing a transfer */ }; struct xp_drive { struct xp_controller *xp_ctlr; /* controller to which slave attached */ int xp_unit; /* slave number */ u_short xp_nsect; u_short xp_ntrack; u_short xp_nspc; /* sectors/cylinder */ u_short xp_cc; /* current cylinder, for RM's */ u_long xp_dd0; /* drivedata longword 0 */ struct dkdevice xp_dk; /* kernel resident portion of label */ u_short xp_ncyl; /* cylinders per pack */ }; /* * Some shorthand for accessing the in-kernel label structure. */ #define xp_bopen xp_dk.dk_bopenmask #define xp_copen xp_dk.dk_copenmask #define xp_open xp_dk.dk_openmask #define xp_flags xp_dk.dk_flags #define xp_label xp_dk.dk_label #define xp_parts xp_dk.dk_parts struct xp_controller xp_controller[NXPC]; struct xp_drive xp_drive[NXPD]; struct buf xptab; struct buf xputab[NXPD]; #ifdef BADSECT struct dkbad xpbad[NXPD]; struct buf bxpbuf[NXPD]; #endif #ifdef UCB_METER static int xp_dkn = -1; /* number for iostat */ #endif int xpstrategy(); void xpgetinfo(); daddr_t xpsize(); extern size_t physmem; /* * Setup root SMD ('xp') device (use bootcsr passed from ROMs). In the event * that the system was not booted from a SMD drive but swapdev is a SMD device * we attach the first (0176700) controller. This would be a very unusual * configuration and is unlikely to be encountered. * * This is very confusing, it is an ugly hack, but short of moving autoconfig * back into the kernel there's nothing else I can think of to do. * * NOTE: the swap device must be on the controller used for booting since * that is the only one attached here - the other controllers are attached * by /etc/autoconfig when it runs later. */ xproot(csr) register struct hpdevice *csr; { if (!csr) /* XXX */ csr = (struct hpdevice *)0176700; /* XXX */ xpattach(csr, 0); } /* * Attach controller at xpaddr. Mark as nonexistent if xpaddr is 0; otherwise * attach slaves. This routine must be called once per controller * in ascending controller numbers. * * NOTE: This means that the 'xp' lines in /etc/dtab _MUST_ be in order * starting with 'xp 0' first. */ xpattach(xpaddr, unit) register struct hpdevice *xpaddr; int unit; /* controller number */ { register struct xp_controller *xc = &xp_controller[unit]; static int last_attached = -1; #ifdef UCB_METER if (xp_dkn < 0) { dk_alloc(&xp_dkn, NXPD, "xp", 0L); } #endif if ((unsigned)unit >= NXPC) return(0); if (xpaddr && (fioword(xpaddr) != -1)) { xc->xp_addr = xpaddr; if (fioword(&xpaddr->hpbae) != -1) xc->xp_rh70 = 1; /* * If already attached, ignore (don't want to renumber drives) */ if (unit > last_attached) { last_attached = unit; xpslave(xpaddr, xc); } return(1); } xc->xp_addr = 0; return(0); } /* * Determine what drives are attached to a controller; the type and geometry * information will be retrieved at open time from the disklabel. */ xpslave(xpaddr, xc) register struct hpdevice *xpaddr; struct xp_controller *xc; { register struct xp_drive *xd; register struct xpst *st; int j, dummy; static int nxp = 0; for (j = 0; j < 8; j++) { xpaddr->hpcs1.w = HP_NOP; xpaddr->hpcs2.w = j; xpaddr->hpcs1.w = HP_GO; /* testing... */ delay(6000L); dummy = xpaddr->hpds; if (xpaddr->hpcs2.w & HPCS2_NED) { xpaddr->hpcs2.w = HPCS2_CLR; continue; } if (nxp < NXPD) { xd = &xp_drive[nxp++]; xd->xp_ctlr = xc; xd->xp_unit = j; /* * Allocate the disklabel now. This is very early in the system's life * so fragmentation will be minimized if any labels are allocated from * main memory. Then initialize the flags to indicate a drive is present. */ xd->xp_label = disklabelalloc(); xd->xp_flags = DKF_ALIVE; } } } xpopen(dev, flags, mode) dev_t dev; int flags, mode; { register struct xp_drive *xd; int unit = XPUNIT(dev); int i, part = dkpart(dev), rpm; register int mask; if (unit >= NXPD) return(ENXIO); xd = &xp_drive[unit]; if ((xd->xp_flags & DKF_ALIVE) == 0) return(ENXIO); /* * Now we read the label. First wait for any pending opens/closes to * complete. */ while (xd->xp_flags & (DKF_OPENING|DKF_CLOSING)) sleep(xd, PRIBIO); /* * On first open get label (which has the geometry information as well as * the partition tables). We may block reading the label so be careful to * stop any other opens. */ if (xd->xp_open == 0) { xd->xp_flags |= DKF_OPENING; xpgetinfo(xd, dev); xd->xp_flags &= ~DKF_OPENING; wakeup(xd); } /* * Need to make sure the partition is not out of bounds. This requires * mapping in the external label. Since this only happens when a partition * is opened (at mount time for example) it is unlikely to be an efficiency * concern. */ mapseg5(xd->xp_label, LABELDESC); i = ((struct disklabel *)SEG5)->d_npartitions; rpm = ((struct disklabel *)SEG5)->d_rpm; normalseg5(); if (part >= i) return(ENXIO); #ifdef UCB_METER if (xp_dkn >= 0) { dk_wps[xp_dkn+unit] = (long) xd->xp_nsect * (rpm / 60) * 256L; } #endif mask = 1 << part; dkoverlapchk(xd->xp_open, dev, xd->xp_label, "xp"); if (mode == S_IFCHR) xd->xp_copen |= mask; else if (mode == S_IFBLK) xd->xp_bopen |= mask; else return(EINVAL); xd->xp_open |= mask; return(0); } xpclose(dev, flags, mode) dev_t dev; int flags, mode; { int s; register int mask; register struct xp_drive *xd; int unit = XPUNIT(dev); xd = &xp_drive[unit]; mask = 1 << dkpart(dev); if (mode == S_IFCHR) xd->xp_copen &= ~mask; else if (mode == S_IFBLK) xd->xp_bopen &= ~mask; else return(EINVAL); xd->xp_open = xd->xp_copen | xd->xp_bopen; if (xd->xp_open == 0) { xd->xp_flags |= DKF_CLOSING; s = splbio(); while (xputab[unit].b_actf) sleep(&xputab[unit], PRIBIO); xd->xp_flags &= ~DKF_CLOSING; splx(s); wakeup(xd); } return(0); } void xpdfltlbl(xd, lp) register struct xp_drive *xd; register struct disklabel *lp; { register struct partition *pi = &lp->d_partitions[0]; /* * NOTE: partition 0 ('a') is used to read the label. Therefore 'a' must * start at the beginning of the disk! If there is no label or the label * is corrupted then a label containing a geometry sufficient *only* to * read/write sector 1 (LABELSECTOR) is created. 1 track, 1 cylinder and * 2 sectors per track. */ bzero(lp, sizeof (*lp)); lp->d_type = DTYPE_SMD; lp->d_secsize = 512; /* XXX */ lp->d_nsectors = LABELSECTOR + 1; /* # sectors/track */ lp->d_ntracks = 1; /* # tracks/cylinder */ lp->d_secpercyl = LABELSECTOR + 1; /* # sectors/cylinder */ lp->d_ncylinders = 1; /* # cylinders */ lp->d_npartitions = 1; /* 1 partition = 'a' */ /* * Need to put the information where the driver expects it. This is normally * done after reading the label. Since we're creating a fake label we have to * copy the invented geometry information to the right place. */ xd->xp_nsect = lp->d_nsectors; xd->xp_ntrack = lp->d_ntracks; xd->xp_nspc = lp->d_secpercyl; xd->xp_ncyl = lp->d_ncylinders; xd->xp_dd0 = lp->d_drivedata[0]; pi->p_size = LABELSECTOR + 1; pi->p_fstype = FS_V71K; pi->p_frag = 1; pi->p_fsize = 1024; bcopy(pi, xd->xp_parts, sizeof (lp->d_partitions)); } /* * Read disklabel. It is tempting to generalize this routine so that * all disk drivers could share it. However by the time all of the * necessary parameters are setup and passed the savings vanish. Also, * each driver has a different method of calculating the number of blocks * to use if one large partition must cover the disk. * * This routine used to always return success and callers carefully checked * the return status. Silly. This routine will fake a label (a single * partition spanning the drive) if necessary but will never return an error. * * It is the caller's responsibility to check the validity of partition * numbers, etc. */ void xpgetinfo(xd, dev) register struct xp_drive *xd; dev_t dev; { struct disklabel locallabel; char *msg; register struct disklabel *lp = &locallabel; xpdfltlbl(xd, lp); msg = readdisklabel((dev & ~7) | 0, xpstrategy, lp); /* 'a' */ if (msg != 0) { log(LOG_NOTICE, "xp%da using labelonly geometry: %s\n", XPUNIT(dev), msg); xpdfltlbl(xd, lp); } mapseg5(xd->xp_label, LABELDESC); bcopy(lp, (struct disklabel *)SEG5, sizeof (struct disklabel)); normalseg5(); bcopy(lp->d_partitions, xd->xp_parts, sizeof (lp->d_partitions)); xd->xp_nsect = lp->d_nsectors; xd->xp_ntrack = lp->d_ntracks; xd->xp_nspc = lp->d_secpercyl; xd->xp_ncyl = lp->d_ncylinders; xd->xp_dd0 = lp->d_drivedata[0]; return; } xpstrategy(bp) register struct buf *bp; { register struct xp_drive *xd; struct partition *pi; int unit; struct buf *dp; register int s; unit = XPUNIT(bp->b_dev); xd = &xp_drive[unit]; if (unit >= NXPD || !xd->xp_ctlr || !(xd->xp_flags & DKF_ALIVE)) { bp->b_error = ENXIO; goto bad; } s = partition_check(bp, &xd->xp_dk); if (s < 0) goto bad; if (s == 0) goto done; if (xd->xp_ctlr->xp_rh70 == 0) mapalloc(bp); pi = &xd->xp_parts[dkpart(bp->b_dev)]; bp->b_cylin = (bp->b_blkno + pi->p_offset) / xd->xp_nspc; dp = &xputab[unit]; s = splbio(); disksort(dp, bp); if (dp->b_active == 0) { xpustart(unit); if (xd->xp_ctlr->xp_active == 0) xpstart(xd->xp_ctlr); } splx(s); return; bad: bp->b_flags |= B_ERROR; done: iodone(bp); return; } /* * 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, *bbp; 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; #ifdef UCB_METER if (xp_dkn >= 0) { dk_busy &= ~(1 << (xp_dkn + unit)); } #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) || !(xd->xp_flags & DKF_ONLINE)) { xpaddr->hpcs1.c[0] = HP_IE | HP_PRESET | HP_GO; xpaddr->hpof = HPOF_FMT22; xd->xp_flags |= DKF_ONLINE; #ifdef XPDEBUG log(LOG_NOTICE, "xp%d preset done\n", unit); #endif /* * XXX - The 'h' partition is used below to access the bad block area. This * XXX - will almost certainly be wrong if the user has defined another * XXX - partition to span the entire drive including the bad block area. It * XXX - is not known what to do about this. */ #ifdef BADSECT bbp = &bxpbuf[unit]; bbp->b_flags = B_READ | B_BUSY | B_PHYS; bbp->b_dev = bp->b_dev | 7; /* "h" partition whole disk */ bbp->b_bcount = sizeof(struct dkbad); bbp->b_un.b_addr = (caddr_t)&xpbad[unit]; bbp->b_blkno = (daddr_t)xd->xp_ncyl * xd->xp_nspc - xd->xp_nsect; bbp->b_cylin = xd->xp_ncyl - 1; if (xd->xp_ctlr->xp_rh70 == 0) mapalloc(bbp); dp->b_actf = bbp; bbp->av_forw = bp; bp = bbp; #endif BADSECT } #if NXPD > 1 /* * If drive is offline, forget about positioning. */ if (xpaddr->hpds & (HPDS_DREADY) != (HPDS_DREADY)) { xd->xp_flags &= ~DKF_ONLINE; goto done; } /* * Figure out where this transfer is going to * and see if we are close enough to justify not searching. */ bn = bp->b_blkno; cn = bp->b_cylin; sn = bn % xd->xp_nspc; sn += xd->xp_nsect - XP_SDIST; sn %= xd->xp_nsect; if ((!(xd->xp_dd0 & XP_CC) && (xd->xp_cc != cn)) || xpaddr->hpcc != cn) goto search; if (xd->xp_dd0 & 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_dd0 & XP_NOSEARCH) ? (HP_IE | HP_SEEK | HP_GO) : (HP_IE | HP_SEARCH | HP_GO); xd->xp_cc = cn; #ifdef UCB_METER /* * Mark unit busy for iostat. */ if (xp_dkn >= 0) { int dkn = xp_dkn + unit; dk_busy |= 1< 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; daddr_t bn; int unit, part, 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) { /* * No more requests for this drive, remove from controller queue and * look at next drive. We know we're at the head of the controller queue. * The drive may not need anything, in which case it might be shutting * down in xpclose() and a wakeup is done. */ dp->b_active = 0; xc->xp_actf = dp->b_forw; unit = dp - xputab; xd = &xp_drive[unit]; if (xd->xp_open == 0) wakeup(dp); /* finish close protocol */ goto loop; } /* * Mark controller busy and determine destination of this request. */ xc->xp_active++; part = dkpart(bp->b_dev); unit = XPUNIT(bp->b_dev); xd = &xp_drive[unit]; bn = bp->b_blkno; cn = (xd->xp_parts[part].p_offset + 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_DREADY) != (HPDS_DREADY)) { xd->xp_flags &= ~DKF_ONLINE; xc->xp_active = 0; dp->b_errcnt = 0; dp->b_actf = bp->av_forw; bp->b_flags |= B_ERROR; iodone(bp); goto loop; } xd->xp_flags |= DKF_ONLINE; 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_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; #ifdef XP_FORMAT if (minor(bp->b_dev) & 0200) unit |= bp->b_flags & B_READ ? HP_RHDR : HP_WHDR; else unit |= bp->b_flags & B_READ ? HP_RCOM : HP_WCOM; #else if (bp->b_flags & B_READ) unit |= HP_RCOM; else unit |= HP_WCOM; #endif xpaddr->hpcs1.w = unit; #ifdef UCB_METER if (xp_dkn >= 0) { int dkn = xp_dkn + XPUNIT(bp->b_dev); dk_busy |= 1<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 int unit; int as; xc = &xp_controller[dev]; xpaddr = xc->xp_addr; as = xpaddr->hpas & 0377; if (xc->xp_active) { /* * Get device and block structures. Select the drive. */ dp = xc->xp_actf; bp = dp->b_actf; #ifdef BADSECT if (bp->b_flags & B_BAD) if (xpecc(bp, CONT)) return; #endif unit = XPUNIT(bp->b_dev); #ifdef UCB_METER if (xp_dkn >= 0) { dk_busy &= ~(1 << (xp_dkn + unit)); } #endif 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. */ log(LOG_NOTICE, "xp%d: write locked\n", unit); bp->b_flags |= B_ERROR; #ifdef BADSECT } else if ((xpaddr->rmer2 & RMER2_BSE) || (xpaddr->hper1 & HPER1_FER)) { #ifdef XP_FORMAT /* * Allow this error on format devices. */ if (minor(bp->b_dev) & 0200) goto errdone; #endif if (xpecc(bp, BSE)) return; else goto hard; #endif BADSECT } else { /* * After 28 retries (16 without offset and * 12 with offset positioning), give up. */ if (++dp->b_errcnt > 28) { hard: harderr(bp, "xp"); log(LOG_NOTICE,"cs2=%b er1=%b er2=%b\n", xpaddr->hpcs2.w, HPCS2_BITS, xpaddr->hper1, HPER1_BITS, xpaddr->rmer2, RMER2_BITS); bp->b_flags |= B_ERROR; } else xc->xp_active = 0; } /* * 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, ECC)) return; errdone: 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); 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 < NXPD; unit++) if ((xp_drive[unit].xp_ctlr == xc) && (as & (1 << xp_drive[unit].xp_unit))) xpustart(unit); xpstart(xc); } #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, flag) register struct buf *bp; int flag; { 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 = XPUNIT(bp->b_dev); xd = &xp_drive[unit]; xpaddr = xd->xp_ctlr->xp_addr; #ifdef BADSECT if (flag == CONT) { npx = bp->b_error; bp->b_error = 0; ndone = npx * NBPG; wc = ((int)(ndone - bp->b_bcount)) / (int)NBPW; } else { #endif wc = xpaddr->hpwc; ndone = ((unsigned)wc * NBPW) + bp->b_bcount; npx = ndone / NBPG; #ifdef BADSECT } #endif ocmd = (xpaddr->hpcs1.w & ~HP_RDY) | HP_IE | HP_GO; bb = exadr(bp->b_xmem, bp->b_un.b_addr); bn = bp->b_blkno; cn = bp->b_cylin - (bn / xd->xp_nspc); bn += npx; cn += bn / xd->xp_nspc; sn = bn % xd->xp_nspc; tn = sn; tn /= xd->xp_nsect; sn %= xd->xp_nsect; switch (flag) { case ECC: log(LOG_NOTICE, "xp%d%c: soft ecc sn%D\n", unit, 'a' + dkpart(bp->b_dev), 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 - NBPG; 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; } break; #ifdef BADSECT case BSE: if ((bn = isbad(&xpbad[unit], cn, tn, sn)) < 0) return(0); bp->b_flags |= B_BAD; bp->b_error = npx + 1; bn = (daddr_t)xd->xp_ncyl * xd->xp_nspc - xd->xp_nsect - 1 - bn; cn = bn/xd->xp_nspc; sn = bn%xd->xp_nspc; tn = sn; tn /= xd->xp_nsect; sn %= xd->xp_nsect; log(LOG_NOTICE, "revector to cn %d tn %d sn %d\n", cn, tn, sn); wc = -(512 / (int)NBPW); break; case CONT: bp->b_flags &= ~B_BAD; log(LOG_NOTICE, "xpecc CONT: bn %D cn %d tn %d sn %d\n", bn, cn, tn, sn); break; #endif BADSECT } 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. */ xpaddr->hpcs2.w = xd->xp_unit; xpaddr->hpcs1.w = HP_TRE | HP_DCLR | HP_GO; addr = bb + ndone; xpaddr->hpdc = cn; xpaddr->hpda = (tn << 8) + sn; xpaddr->hpwc = wc; xpaddr->hpba = (caddr_t)addr; if (xd->xp_ctlr->xp_rh70) xpaddr->hpbae = (int)(addr >> 16); xpaddr->hpcs1.w = ocmd; return (1); } xpioctl(dev, cmd, data, flag) dev_t dev; int cmd; caddr_t data; int flag; { register int error; struct dkdevice *disk = &xp_drive[XPUNIT(dev)].xp_dk; error = ioctldisklabel(dev, cmd, data, flag, disk, xpstrategy); return(error); } #ifdef XP_DUMP /* * 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; { struct xp_drive *xd; register struct hpdevice *xpaddr; struct partition *pi; daddr_t bn, dumpsize; long paddr; int sn, count, memblks, unit; register struct ubmap *ubp; unit = XPUNIT(dev); xd = &xp_drive[unit]; if (unit > NXPD || xd->xp_ctlr == 0) return(EINVAL); if (!(xd->xp_flags & DKF_ALIVE)) return(ENXIO); pi = &xd->xp_parts[dkpart(dev)]; if (pi->p_fstype != FS_SWAP) return(EFTYPE); xpaddr = xd->xp_ctlr->xp_addr; dumpsize = xpsize(dev) - dumplo; memblks = ctod(physmem); if (dumplo < 0 || dumpsize <= 0) return(EINVAL); if (memblks > dumpsize) memblks = dumpsize; bn = dumplo + pi->p_offset; 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_DREADY) != (HPDS_DREADY)) return(EFAULT); ubp = &UBMAP[0]; for (paddr = 0L; memblks > 0; ) { count = MIN(memblks, DBSIZE); xpaddr->hpdc = bn / xd->xp_nspc; sn = bn % xd->xp_nspc; xpaddr->hpda = ((sn / xd->xp_nsect) << 8) | (sn % xd->xp_nsect); xpaddr->hpwc = -(count << (PGSHIFT - 1)); xpaddr->hper1 = 0; xpaddr->hper3 = 0; if (ubmap && (xd->xp_ctlr->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 = (caddr_t)loint(paddr); if (xd->xp_ctlr->xp_rh70) xpaddr->hpbae = hiint(paddr); xpaddr->hpcs1.w = HP_WCOM | HP_GO | ((paddr >> 8) & (03 << 8)); } /* Emulex controller emulating two RM03's needs a delay */ delay(50000L); while (xpaddr->hpcs1.w & HP_GO) continue; if (xpaddr->hpcs1.w & HP_TRE) return(EIO); paddr += (count << PGSHIFT); bn += count; memblks -= count; } return(0); } #endif XP_DUMP /* * Return the number of blocks in a partition. Call xpopen() to read the * label if necessary. If an open is necessary then a matching close * will be done. */ daddr_t xpsize(dev) register dev_t dev; { register struct xp_drive *xd; daddr_t psize; int didopen = 0; xd = &xp_drive[XPUNIT(dev)]; /* * This should never happen but if we get called early in the kernel's * life (before opening the swap or root devices) then we have to do * the open here. */ if (xd->xp_open == 0) { if (xpopen(dev, FREAD|FWRITE, S_IFBLK)) return(-1); didopen = 1; } psize = xd->xp_parts[dkpart(dev)].p_size; if (didopen) xpclose(dev, FREAD|FWRITE, S_IFBLK); return(psize); } #endif /* NXPD */