/* * Copyright (c) 1982, 1986 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. * * %W% (Berkeley) %D% */ /* * up.c, driver for Emulex SC21, SC31 disk controllers * with support for disklabels. * UNTESTED. */ #ifdef UPDEBUG int updebug; #endif #ifdef UPBDEBUG int upbdebug; #endif #include "up.h" #if NSC > 0 /* * UNIBUS disk driver with: * overlapped seeks, * ECC recovery, and * bad sector forwarding. * * TODO: * Check that offset recovery code works */ #include "../machine/pte.h" #include "param.h" #include "systm.h" #include "dkstat.h" #include "dkbad.h" #include "ioctl.h" #include "disklabel.h" #include "buf.h" #include "conf.h" #include "dir.h" #include "file.h" #include "user.h" #include "map.h" #include "vm.h" #include "cmap.h" #include "uio.h" #include "kernel.h" #include "syslog.h" #include "stat.h" #include "../vax/mtpr.h" #include "../vax/cpu.h" #include "../vax/nexus.h" #include "ubavar.h" #include "ubareg.h" #include "upreg.h" #define COMPAT_42 #define B_FORMAT B_XXX #define upunit(dev) (minor(dev) >> 3) #define uppart(dev) (minor(dev) & 07) #define upminor(unit, part) (((unit) << 3) | (part)) int upprobe(), upslave(), upattach(), updgo(), upintr(); struct uba_ctlr *upminfo[NSC]; struct uba_device *updinfo[NUP]; #define UPIPUNITS 8 struct uba_device *upip[NSC][UPIPUNITS]; /* fuji w/fixed head gives n,n+4 */ u_short upstd[] = { 0776700, 0774400, 0776300, 0 }; struct uba_driver scdriver = { upprobe, upslave, upattach, updgo, upstd, "up", updinfo, "sc", upminfo }; struct buf uputab[NUP]; char upinit[NUP]; u_char up_offset[16] = { UPOF_P400, UPOF_M400, UPOF_P400, UPOF_M400, UPOF_P800, UPOF_M800, UPOF_P800, UPOF_M800, UPOF_P1200, UPOF_M1200, UPOF_P1200, UPOF_M1200, 0, 0, 0, 0 }; struct buf bupbuf[NUP]; struct dkbad upbad[NUP]; struct upsoftc { u_char sc_recal; /* recalibrate state */ u_char sc_doseeks; /* perform explicit seeks */ #ifdef COMPAT_42 u_char sc_hdr; /* next i/o includes header */ #endif short sc_state; /* open fsm */ short sc_wlabel; /* label sector is currently writeable */ u_long sc_openpart; /* bit mask of open subunits */ u_long sc_copenpart; /* bit mask of open character subunits */ u_long sc_bopenpart; /* bit mask of open block subunits */ int sc_blkdone; /* amount sucessfully transfered */ daddr_t sc_badbn; /* replacement block number */ short sc_softas; /* bitmask of drives needing attention */ short sc_ndrive; /* count of drives */ int sc_wticks; /* watchdog timer */ int sc_status; /* copy of drive status reg after format */ int sc_upds; /* copy of upds reg after format */ int sc_er1; /* copy of error reg 1 after format */ int sc_er2; /* copy of error reg 2 after format */ } upsoftc[NSC]; /* * Drive states. Used during steps of open/initialization. * States < OPEN (> 0) are transient, during an open operation. * OPENRAW is used for unlabeled disks, * to inhibit bad-sector forwarding or allow format operations. */ #define CLOSED 0 /* disk is closed. */ #define WANTOPEN 1 /* open requested, not started */ #define WANTOPENRAW 2 /* open requested, no label */ #define RDLABEL 3 /* reading pack label */ #define RDBADTBL 4 /* reading bad-sector table */ #define OPEN 5 /* initialized and ready */ #define OPENRAW 6 /* open, no label or badsect */ #define b_cylin b_resid /* cyl number, for disksort */ #define b_bufsiz b_bdone /* amount done, in ui_tab only */ int upwstart, upwatch(); /* Have started guardian */ int upseek; int upwaitdry; upprobe(reg) caddr_t reg; { register int br, cvec; #ifdef lint br = 0; cvec = br; br = cvec; upintr(0); #endif ((struct updevice *)reg)->upcs1 = UP_IE|UP_RDY; DELAY(10); ((struct updevice *)reg)->upcs1 = 0; return (sizeof (struct updevice)); } upslave(ui, reg) struct uba_device *ui; caddr_t reg; { register struct updevice *upaddr = (struct updevice *)reg; upaddr->upcs1 = 0; /* conservative */ upaddr->upcs2 = ui->ui_slave; upaddr->upcs1 = UP_NOP|UP_GO; if (upaddr->upcs2&UPCS2_NED) { upaddr->upcs1 = UP_DCLR|UP_GO; return (0); } return (1); } upattach(ui) register struct uba_device *ui; { register int unit = ui->ui_unit; if (upwstart == 0) { timeout(upwatch, (caddr_t)0, hz); upwstart++; } upip[ui->ui_ctlr][ui->ui_slave] = ui; up_softc[ui->ui_ctlr].sc_ndrive++; /* * Try to initialize device and read pack label. */ if (upinit(upminor(unit, 0), 0) == 0) { printf(": %s", uplabel[unit].d_typename); #ifdef notyet addswap(makedev(UPMAJOR, upminor(unit, 0)), &uplabel[unit]); #endif } else printf(": offline"); } upopen(dev, flags, fmt) dev_t dev; int flags, fmt; { register int unit = upunit(dev); register struct upsoftc *sc; register struct disklabel *lp; register struct partition *pp; struct uba_device *ui; int s, error, part = uppart(dev), mask = 1 << part; daddr_t start, end; if (unit >= NUP || (ui = updinfo[unit]) == 0 || ui->ui_alive == 0) return (ENXIO); sc = &upsoftc[unit]; lp = &uplabel[unit]; s = spl5(); while (sc->sc_state != OPEN && sc->sc_state != OPENRAW && sc->sc_state != CLOSED) sleep ((caddr_t)sc, PZERO+1); splx(s); if (sc->sc_state != OPEN && sc->sc_state != OPENRAW) if (error = upinit(dev, flags)) return (error); if (part >= lp->d_npartitions) return (ENXIO); /* * Warn if a partion is opened * that overlaps another partition which is open * unless one is the "raw" partition (whole disk). */ #define RAWPART 2 /* 'c' partition */ /* XXX */ if ((sc->sc_openpart & mask) == 0 && part != RAWPART) { pp = &lp->d_partitions[part]; start = pp->p_offset; end = pp->p_offset + pp->p_size; for (pp = lp->d_partitions; pp < &lp->d_partitions[lp->d_npartitions]; pp++) { if (pp->p_offset + pp->p_size <= start || pp->p_offset >= end) continue; if ((s = pp - lp->d_partitions) == RAWPART) continue; if (sc->sc_openpart & (1 << s)) log(LOG_WARNING, "up%d%c: overlaps open partition (%c)\n", unit, part + 'a', s + 'a'); } } switch (fmt) { case S_IFCHR: sc->sc_copenpart |= mask; break; case S_IFBLK: sc->sc_bopenpart |= mask; break; } sc->sc_openpart |= mask; return (0); } /* ARGSUSED */ upclose(dev, flags, fmt) dev_t dev; int flags, fmt; { register int unit = upunit(dev); register struct upsoftc *sc = &upsoftc[unit]; struct uba_device *ui = updinfo[unit]; int s; switch (fmt) { case S_IFCHR: sc->sc_copenpart &= ~(1 << uppart(dev)); break; case S_IFBLK: sc->sc_bopenpart &= ~(1 << uppart(dev)); break; } if (sc->sc_openpart) sc->sc_openpart = sc->sc_copenpart | sc->sc_bopenpart; /* * If this was the last open partition on the drive, then * we must wait for the i/o to complete (so caller knows its * ok to turn the drive off). If not the last, no problem, * as some other partition must still be active. (Potential * problem is some part of the "drive" can be spun down * while leaving other part of same drive running ..) * * (this will also happen if we have disabled the drive because * of an unanticipated pack change, but no harm there) */ if (sc->sc_openpart == 0) { s = spl5(); while (ui->ui_tab.b_actf) sleep((caddr_t)sc, PZERO - 1); splx(s); sc->sc_state = CLOSED; } return (0); } upinit(dev, flags) dev_t dev; int flags; { register struct upsoftc *sc; register struct buf *bp; register struct disklabel *lp; struct uba_device *ui; struct dkbad *db; char *msg, *readdisklabel(); int unit, i, error = 0; extern int cold; unit = upunit(dev); sc = &upsoftc[unit]; lp = &uplabel[unit]; ui = updinfo[unit]; sc->sc_state = WANTOPEN; /* * Obtain the physical drive characteristics from the * controller, for use until the label overrides them. */ upphysical(ui->ui_addr, lp); if (flags & O_NDELAY) goto raw; /* * Preset, pack acknowledge will be done in upstart * during first read operation. */ if (msg = readdisklabel(dev, upstrategy, lp)) { if (cold) printf(": %s", msg); else log(LOG_ERR, "up%d: %s\n", unit, msg); #ifdef COMPAT_42 if (upmaptype(ui, lp) == 0) #endif goto raw; } /* * Seconds per word = (60 / rpm) / (nsectors * secsize/2) */ if (ui->ui_dk >= 0 && lp->d_rpm) dk_mspw[ui->ui_dk] = 120.0 / (lp->d_rpm * lp->d_nsectors * lp->d_secsize); /* * Read bad sector table into memory. */ bp = geteblk(DEV_BSIZE); /* max sector size */ bp->b_dev = dev; sc->sc_state = RDBADTBL; i = 0; do { u.u_error = 0; /* XXX */ bp->b_flags = B_BUSY | B_READ; bp->b_blkno = lp->d_secperunit - lp->d_nsectors + i; bp->b_bcount = lp->d_secsize; bp->b_cylin = lp->d_ncylinders - 1; upstrategy(bp); biowait(bp); } while ((bp->b_flags & B_ERROR) && (i += 2) < 10 && i < lp->d_nsectors); db = (struct dkbad *)(bp->b_un.b_addr); if ((bp->b_flags & B_ERROR) == 0 && db->bt_mbz == 0 && db->bt_flag == 0) { upbad[unit] = *db; sc->sc_state = OPEN; } else { log(LOG_ERR, "up%d: %s bad-sector file\n", unit, (bp->b_flags & B_ERROR) ? "can't read" : "format error in"); u.u_error = 0; /* XXX */ sc->sc_state = OPENRAW; } bp->b_flags = B_INVAL | B_AGE; brelse(bp); done: wakeup((caddr_t)sc); return (error); raw: sc->sc_state = OPENRAW; wakeup((caddr_t)sc); return (error); } /* * obtain the physical drive layout from the controller prom * (we know this isn't a dec controller, so we can assume sanity) */ upphysical(upaddr, lp) register struct updevice *upaddr; register struct disklabel *lp; { upaddr->upcs1 = 0; upaddr->upcs2 = ui->ui_slave; upaddr->uphr = UPHR_MAXTRAK; lp->d_ntracks = upaddr->uphr + 1; upaddr->uphr = UPHR_MAXSECT; lp->d_nsectors = upaddr->uphr + 1; upaddr->uphr = UPHR_MAXCYL; lp->d_ncylinders = upaddr->uphr + 1; lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors; lp->d_secperunit = lp->d_secpercyl * lp->d_ncylinders; lp->d_npartitions = 1; /* ??? (allow label write) */ lp->d_partitions[0].p_offset = 0; lp->d_partitions[0].p_size = lp->d_secperunit; lp->d_secsize = DEV_BSIZE; /* assume this */ upaddr->upcs2 = UPCS2_CLR; } upstrategy(bp) register struct buf *bp; { register struct uba_device *ui; register struct disklabel *lp; register struct upsoftc *sc; register int unit; register struct buf *dp; long bn, sz; daddr_t maxsz; int xunit = uppart(bp->b_dev); int s; sz = (bp->b_bcount + DEV_BSIZE - 1) >> DEV_BSHIFT; unit = hpunit(bp->b_dev); if (unit >= NUP) { bp->b_error = ENXIO; goto bad; } ui = updinfo[unit]; if (ui == 0 || ui->ui_alive == 0) { bp->b_error = ENXIO; goto bad; } sc = &upsoftc[unit]; lp = &uplabel[unit]; if (sc->sc_state < OPEN) goto q; if ((sc->sc_openpart & (1 << xunit)) == 0) { bp->b_error = ENODEV; goto bad; } maxsz = lp->d_partitions[xunit].p_size; if (bp->b_blkno < 0 || bp->b_blkno + sz > maxsz) { if (bp->b_blkno == maxsz) { bp->b_resid = bp->b_bcount; goto done; } sz = maxsz - bp->b_blkno; if (sz <= 0) { bp->b_error = EINVAL; goto bad; } bp->b_bcount = sz << DEV_BSHIFT; } bp->b_cylin = (bp->b_blkno + lp->d_partitions[xunit].p_offset) / lp->d_secpercyl; q: s = spl5(); dp = &uputab[ui->ui_unit]; disksort(dp, bp); if (dp->b_active == 0) { (void) upustart(ui); bp = &ui->ui_mi->um_tab; if (bp->b_actf && bp->b_active == 0) (void) upstart(ui->ui_mi); } splx(s); return; bad: bp->b_flags |= B_ERROR; done: biodone(bp); return; } /* * Unit start routine. * Seek the drive to be where the data is * and then generate another interrupt * to actually start the transfer. * If there is only one drive on the controller, * 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 transferrring.) */ upustart(ui) register struct uba_device *ui; { register struct buf *bp, *dp; register struct uba_ctlr *um; register struct updevice *upaddr; register struct disklabel *lp; struct upsoftc *sc = &upsoftc[ui->ui_unit]; daddr_t bn; int sn, tn, dist; /* * The SC21 cancels commands if you just say * cs1 = UP_IE * so we are cautious about handling of cs1. * Also don't bother to clear as bits other than in upintr(). */ int didie = 0; if (ui == 0) return (0); um = ui->ui_mi; dk_busy &= ~(1<<ui->ui_dk); dp = &uputab[ui->ui_unit]; retry: if ((bp = dp->b_actf) == NULL) return (0); /* * If the controller is active, just remember * that this device would like to be positioned... * if we tried to position now we would confuse the SC21. */ if (um->um_tab.b_active) { up_softc[um->um_ctlr].sc_softas |= 1<<ui->ui_slave; return (0); } /* * If we have already positioned this drive, * then just put it on the ready queue. */ if (dp->b_active) goto done; dp->b_active = 1; lp = &uplabel[ui->ui_unit]; upaddr = (struct updevice *)um->um_addr; upaddr->upcs2 = ui->ui_slave; switch (sc->sc_recal) { case 1: (void)HPWAIT(mi, hpaddr); upaddr->updc = bp->b_cylin; upaddr->upcs1 = UP_IE|UP_SEEK|UP_GO; sc->sc_recal++; return (1); case 2: break; } sc->sc_recal = 0; /* * If drive has just come up, * setup the pack. */ if ((upaddr->upds & UPDS_VV) == 0) { struct buf *bbp = &bupbuf[ui->ui_unit]; if (sc->sc_state == OPEN && lp->d_flags & D_REMOVABLE) { if (sc->sc_openpart) log(LOG_ERR, "up%d: volume changed\n", ui->ui_unit); sc->sc_openpart = 0; do { dp->b_actf = bp->b_forw; bp->b_flags |= B_ERROR; bp->b_errno = ENXIO; iodone(bp); } while (bp = dp->b_actf); return (0); } upaddr->upcs1 = UP_IE|UP_DCLR|UP_GO; upaddr->upcs1 = UP_IE|UP_PRESET|UP_GO; upaddr->upof = UPOF_FMT22; if (sc->sc_state == WANTOPENRAW) { sc->sc_state = OPENRAW; return (1); } if (sc->sc_state == WANTOPEN) sc->sc_state = RDLABEL; didie = 1; } /* * If drive is offline, forget about positioning. */ if ((upaddr->upds & (UPDS_DPR|UPDS_MOL)) != (UPDS_DPR|UPDS_MOL)) goto done; /* * If there is only one drive, * dont bother searching. */ if (up_softc[um->um_ctlr].sc_ndrive == 1) goto done; /* * Figure out where this transfer is going to * and see if we are close enough to justify not searching. */ st = &upst[ui->ui_type]; bn = bp->b_blkno; sn = bn % lp->d_secpercyl; tn = sn / lp->d_nsectors; sn = sn % lp->d_nsectors; if (bp->b_cylin == upaddr->updc) { if (sc->sc_doseeks) goto done; /* Ok just to be on-cylinder */ dist = sn - (upaddr->upla>>6) - 1; if (dist < 0) dist += lp->d_nsectors; if (dist <= lp->d_maxdist && dist >= lp->d_mindist) goto done; } else upaddr->updc = bp->b_cylin; /* * Not on cylinder at correct position, * seek/search. */ if (sc->sc_doseeks) upaddr->upcs1 = UP_IE|UP_SEEK|UP_GO; else { sn = (sn + lp->d_nsectors - lp->d_sdist) % lp->d_nsectors; upaddr->upda = sn; upaddr->upcs1 = UP_IE|UP_SEARCH|UP_GO; } didie = 1; /* * Mark unit busy for iostat. */ if (ui->ui_dk >= 0) { dk_busy |= 1<<ui->ui_dk; dk_seek[ui->ui_dk]++; } goto out; done: /* * Device is ready to go. * Put it on the ready queue for the controller * (unless its already there.) */ if (dp->b_active != 2) { dp->b_forw = NULL; if (um->um_tab.b_actf == NULL) um->um_tab.b_actf = dp; else um->um_tab.b_actl->b_forw = dp; um->um_tab.b_actl = dp; dp->b_active = 2; } out: return (didie); } /* * Start up a transfer on a drive. */ upstart(um) register struct uba_ctlr *um; { register struct buf *bp, *dp; register struct uba_device *ui; register struct updevice *upaddr; register struct disklabel *lp = &uplabel[ui->ui_unit]; struct upsoftc *sc = &upsoftc[ui->ui_unit]; daddr_t bn; int cn, dn, sn, tn, cmd, waitdry; loop: /* * Pull a request off the controller queue */ if ((dp = um->um_tab.b_actf) == NULL) return (0); if ((bp = dp->b_actf) == NULL) { um->um_tab.b_actf = dp->b_forw; goto loop; } /* * Mark controller busy, and * determine destination of this request. */ um->um_tab.b_active++; ui = updinfo[upunit(bp->b_dev)]; bn = bp->b_blkno; dn = ui->ui_slave; st = &upst[ui->ui_type]; sn = bn%st->nspc; tn = sn/st->nsect; sn %= st->nsect; upaddr = (struct updevice *)ui->ui_addr; /* * Select drive if not selected already. */ if ((upaddr->upcs2&07) != dn) upaddr->upcs2 = dn; /* * Check that it is ready and online */ waitdry = 0; while ((upaddr->upds&UPDS_DRY) == 0) { log(LOG_ERR, "up%d: ds wait ds=%o\n", upunit(bp->b_dev), upaddr->upds); if (++waitdry > 512) break; upwaitdry++; } if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) { /* * this should be "log()", but log with split * messages is very messy, and we also depend here * on there being a real (measureable) time taken * doing the printf, to give the drive a chance to * recover .. printf polls the terminal, so takes ages */ printf("up%d: not ready", upunit(bp->b_dev)); if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) { printf("\n"); um->um_tab.b_active = 0; um->um_tab.b_errcnt = 0; dp->b_actf = bp->av_forw; dp->b_active = 0; bp->b_flags |= B_ERROR; iodone(bp); goto loop; } /* * Oh, well, sometimes this * happens, for reasons unknown. */ printf(" (flakey)\n"); } /* * Setup for the transfer, and get in the * UNIBUS adaptor queue. */ if (bp->b_flags & B_BAD) { bn = sc->sc_badbn; cn = bn / lp->d_secpercyl; } else { bn = bp->b_blkno; cn = bp->b_cylin; } sn = bn % lp->d_secpercyl; if ((bp->b_flags & B_BAD) == 0) sn += sc->sc_blkdone; tn = sn / lp->d_nsectors; sn %= lp->d_nsectors; cn += tn / lp->d_ntracks; tn %= lp->d_ntracks; upaddr->updc = cn; upaddr->upda = (tn << 8) + sn; if (bp->b_bcount >= 0) upaddr->upwc = (-bp->b_bcount + ui->ui_tab.b_bdone) / sizeof (short); else upaddr->upwc = (bp->b_wcount + ui->ui_tab.b_bdone) / sizeof (short); switch (bp->b_flags & (B_READ|B_WRITE|B_FORMAT)) { case B_READ: cmd = UP_IE|UP_RCOM|UP_GO; break; case B_WRITE: cmd = UP_IE|UP_WCOM|UP_GO; break; case B_READ|B_FORMAT: cmd = UP_IE|UP_RHDR|UP_GO; break; case B_WRITE|B_FORMAT: cmd = UP_IE|UP_WHDR|UP_GO; break; } um->um_cmd = cmd; ui->ui_tab.b_bdone = dbtob(sc->sc_blkdone); (void) ubago(ui); return (1); } /* * Now all ready to go, stuff the registers. */ updgo(um) struct uba_ctlr *um; { register struct updevice *upaddr = (struct updevice *)um->um_addr; um->um_tab.b_active = 2; /* should now be 2 */ upaddr->upba = um->um_ubinfo; upaddr->upcs1 = um->um_cmd|((um->um_ubinfo>>8)&0x300); } /* * Handle a disk interrupt. */ upintr(sc21) register sc21; { register struct buf *bp, *dp; register struct uba_ctlr *um = upminfo[sc21]; register struct uba_device *ui; register struct updevice *upaddr = (struct updevice *)um->um_addr; register unit; struct up_softc *sc = &up_softc[um->um_ctlr]; int as = (upaddr->upas & 0377) | sc->sc_softas; int needie = 1, waitdry; sc->sc_wticks = 0; sc->sc_softas = 0; /* * If controller wasn't transferring, then this is an * interrupt for attention status on seeking drives. * Just service them. */ if (um->um_tab.b_active != 2 && !sc->sc_recal) { if (upaddr->upcs1 & UP_TRE) upaddr->upcs1 = UP_TRE; goto doattn; } um->um_tab.b_active = 1; /* * Get device and block structures, and a pointer * to the uba_device for the drive. Select the drive. */ dp = um->um_tab.b_actf; bp = dp->b_actf; ui = updinfo[upunit(bp->b_dev)]; dk_busy &= ~(1 << ui->ui_dk); if ((upaddr->upcs2&07) != ui->ui_slave) upaddr->upcs2 = ui->ui_slave; /* * Check for and process errors on * either the drive or the controller. */ if ((upaddr->upds&UPDS_ERR) || (upaddr->upcs1&UP_TRE)) { er1 = upaddr->uper1; er2 = upaddr->uper2; if (bp->b_flags & B_BAD) { npf = bp->b_error; bn = sc->sc_badbn; } else { npf = btop(bp->b_bcount + upaddr->upwc * sizeof(short)); if (er1 & (UPER_DCK | UPER_ECH)) npf--; bn = bp->b_blkno + npf; } if (UPWAIT(upaddr, upunit(bp->b_dev)) == 0) goto hard; #ifdef UPDEBUG if (updebug) { log(LOG_DEBUG, "uperr: bp %x cyl %d blk %d blkdone %d as %o dc %x da %x\n", bp, bp->b_cylin, bn, sc->sc_blkdone, upaddr->upas, upaddr->updc, upaddr->upda); log(LOG_DEBUG, "errcnt %d er1 %b er2 %b wc -%d (bc %d)\n", um->um_tab.b_errcnt, er1, UPER1_BITS, er2, UPER2_BITS, -up->upwc,-up->upwc*sizeof(short)); } #endif if (er1 & UPER1_HCRC) { er1 &= ~(UPER1_HCE|UPER1_FER); er2 &= ~UPER2_BSE; } if (er1 & UPER1_WLE) { /* * Give up on write locked devices * immediately. */ log(LOG_WARNING, "up%d: write locked\n", upunit(bp->b_dev)); bp->b_flags |= B_ERROR; } else if (bp->b_flags & B_FORMAT) { goto hard; } else if (er2 & UPER2_BSE) { if (upecc(ui, BSW)) return; goto hard; } else if ((er1 & (UPER1_DCK|UPER1_ECH)) == UPER1_DCK && um->um_tab.b_errcnt >= 3) { if (upecc(ui, ECC)) return; /* see comment in hp.c */ if (ui->ui_tab.b_errcnt == 3) ui->ui_tab.b_errcnt = 0; } else if (er1 & UPER1_HCRC && upecc(ui, BSE)) { return; } else if (++um->um_tab.b_errcnt > 27 || er1 & HPER1_HARD) { hard: bp->b_blkno = bn; /* XXX */ harderr(bp, "up"); printf("cn=%d tn=%d sn=%d cs2=%b er1=%b er2=%b", upaddr->updc, ((upaddr->upda)>>8)&077, (upaddr->upda)&037, upaddr->upcs2, UPCS2_BITS, upaddr->uper1, UPER1_BITS, upaddr->uper2, UPER2_BITS); if (bp->b_flags & B_FORMAT) printf(" (hdr i/o)"); printf("\n"); bp->b_flags |= B_ERROR; bp->b_flags &= ~B_BAD; } else um->um_tab.b_active = 0; /* force retry */ /* * Clear drive error and, every eight attempts, * (starting with the fourth) * recalibrate to clear the slate. */ upaddr->upcs1 = UP_TRE|UP_IE|UP_DCLR|UP_GO; needie = 0; if ((um->um_tab.b_errcnt&07) == 4 && um->um_tab.b_active == 0) { upaddr->upcs1 = UP_RECAL|UP_IE|UP_GO; sc->sc_recal = 0; goto nextrecal; } } #ifdef UPDEBUG else if (updebug && sc->sc_recal) { log(LOG_DEBUG, "up: recal %d errcnt %d er1=%b er2=%b\n", sc->sc_recal, um->um_tab.b_errcnt, upaddr->uper1, UPER1_BITS, upaddr->uper2, UPER2_BITS); } #endif /* * Advance recalibration finite state machine * if recalibrate in progress, through * RECAL * SEEK * OFFSET (optional) * RETRY */ switch (sc->sc_recal) { case 1: upaddr->updc = bp->b_cylin; upaddr->upcs1 = UP_SEEK|UP_IE|UP_GO; goto nextrecal; case 2: if (um->um_tab.b_errcnt < 16 || (bp->b_flags&B_READ) == 0) goto donerecal; upaddr->upof = up_offset[um->um_tab.b_errcnt&017] | UPOF_FMT22; upaddr->upcs1 = UP_IE|UP_OFFSET|UP_GO; goto nextrecal; nextrecal: sc->sc_recal++; um->um_tab.b_active = 1; return; donerecal: case 3: sc->sc_recal = 0; um->um_tab.b_active = 0; break; } /* * If still ``active'', then don't need any more retries. */ if (um->um_tab.b_active) { /* * If we were offset positioning, * return to centerline. */ if (um->um_tab.b_errcnt >= 16) { upaddr->upof = UPOF_FMT22; upaddr->upcs1 = UP_RTC|UP_GO|UP_IE; while (upaddr->upds & UPDS_PIP) DELAY(25); needie = 0; } um->um_tab.b_active = 0; um->um_tab.b_errcnt = 0; um->um_tab.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)); sc->sc_blkdone = 0; if (sc->sc_openpart == 0) wakeup((caddr_t)sc); iodone(bp); /* * If this unit has more work to do, * then start it up right away. */ if (dp->b_actf) if (upustart(ui)) needie = 0; } as &= ~(1<<ui->ui_slave); /* * Release unibus resources and flush data paths. */ ubadone(um); doattn: /* * Process other units which need attention. * For each unit which needs attention, call * the unit start routine to place the slave * on the controller device queue. */ while (unit = ffs((long)as)) { unit--; /* was 1 origin */ as &= ~(1<<unit); upaddr->upas = 1<<unit; if (unit < UPIPUNITS && upustart(upip[sc21][unit])) needie = 0; } /* * If the controller is not transferring, but * there are devices ready to transfer, start * the controller. */ if (um->um_tab.b_actf && um->um_tab.b_active == 0) if (upstart(um)) needie = 0; if (needie) upaddr->upcs1 = UP_IE; } upioctl(dev, cmd, data, flag) dev_t dev; int cmd; caddr_t data; int flag; { int unit = upunit(dev); register struct disklabel *lp; register struct format_op *fop; int error = 0; int upformat(); lp = &uplabel[unit]; switch (cmd) { case DIOCGDINFO: *(struct disklabel *)data = *lp; break; case DIOCGPART: ((struct partinfo *)data)->disklab = lp; ((struct partinfo *)data)->part = &lp->d_partitions[uppart(dev)]; break; case DIOCSDINFO: if ((flag & FWRITE) == 0) error = EBADF; else *lp = *(struct disklabel *)data; break; case DIOCWDINFO: if ((flag & FWRITE) == 0) { error = EBADF; break; } { struct buf *bp; struct disklabel *dlp; *lp = *(struct disklabel *)data; bp = geteblk(lp->d_secsize); bp->b_dev = makedev(major(dev), upminor(upunit(dev), 0)); bp->b_blkno = LABELSECTOR; bp->b_bcount = lp->d_secsize; bp->b_flags = B_READ; dlp = (struct disklabel *)(bp->b_un.b_addr + LABELOFFSET); upstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { error = u.u_error; /* XXX */ u.u_error = 0; goto bad; } *dlp = *lp; bp->b_flags = B_WRITE; upstrategy(bp); biowait(bp); if (bp->b_flags & B_ERROR) { error = u.u_error; /* XXX */ u.u_error = 0; } bad: brelse(bp); break; } #ifdef notyet case DIOCWFORMAT: if ((flag & FWRITE) == 0) { error = EBADF; break; } { struct uio auio; struct iovec aiov; fop = (struct format_op *)data; aiov.iov_base = fop->df_buf; aiov.iov_len = fop->df_count; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_resid = fop->df_count; auio.uio_segflg = 0; auio.uio_offset = fop->df_startblk * lp->d_secsize; error = physio(upformat, &rupbuf[unit], dev, B_WRITE, minphys, &auio); fop->df_count -= auio.uio_resid; fop->df_reg[0] = sc->sc_status; fop->df_reg[1] = sc->sc_error; } break; #endif default: error = ENOTTY; break; } return (error); } upformat(bp) struct buf *bp; { bp->b_flags |= B_FORMAT; return (upstrategy(bp)); } /* * 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(ui, flag) register struct uba_device *ui; int flag; { register struct updevice *up = (struct updevice *)ui->ui_addr; register struct buf *bp = uputab[ui->ui_unit].b_actf; register struct uba_ctlr *um = ui->ui_mi; register struct disklabel *lp = &hplabel[mi->mi_unit]; struct uba_regs *ubp = ui->ui_hd->uh_uba; struct hpsoftc *sc = &hpsoftc[mi->mi_unit]; register int i; caddr_t addr; int reg, bit, byte, npf, mask, o, cmd, ubaddr; int bn, cn, tn, sn; int bcr; /* * 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. */ bcr = -up->upwc * sizeof(short); if (flag == CONT) npf = bp->b_error; else { npf = bp->b_bcount - bcr; /* * Watch out for fractional sector at end of transfer; * want to round up if finished, otherwise round down. */ if (bcr == 0) npf += 511; npf = btodb(npf); } reg = btop(um->um_ubinfo&0x3ffff) + npf; o = (int)bp->b_un.b_addr & PGOFSET; mask = up->upec2; #ifdef UPECCDEBUG if (upeccdebug) log(LOG_DEBUG, "npf %d reg %x o %d mask %o pos %d\n", npf, reg, o, mask, up->upec1); #endif bn = bp->b_blkno; cn = bp->b_cylin; sn = bn % lp->d_secpercyl + npf; tn = sn / lp->d_nsectors; sn %= lp->d_nsectors; cn += tn / lp->d_ntracks; tn %= lp->d_ntracks; bn += npf; ubapurge(um); um->um_tab.b_active = 2; /* * action taken depends on the flag */ switch (flag) { case ECC: { register int i; caddr_t addr; struct pte mpte; int bit, byte, mask; npf--; /* because block in error is previous block */ bn--; reg--; if (bp->b_flags & B_BAD) bn = sc->sc_badbn; log(LOG_WARNING, "up%d%c: soft ecc sn%d\n", upunit(bp->b_dev), 'a' + uppart(bp->b_dev), bp->b_blkno + npf); /* * 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. */ i = up->upec1 - 1; /* -1 makes 0 origin */ mask = up->upec2; 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)dbtob(npf)+i < bp->b_bcount && bit > -11) { struct pte pte; pte = ubp->uba_map[reg + btop(byte)]; addr = ptob(pte.pg_pfnum) + (byte & PGOFSET); #ifdef UPECCDEBUG printf("addr %x map reg %x\n", addr, *(int *)(&ubp->uba_map[reg+btop(byte)])); printf("old: %x, ", getmemc(addr)); #endif putmemc(addr, getmemc(addr)^(mask<<bit)); #ifdef UPECCDEBUG printf("new: %x\n", getmemc(addr)); #endif byte++; i++; bit -= 8; } if (bcr == 0) return (0); npf++; reg++; break; } case BSE: if (sc->sc_state == OPENRAW) return (0); #ifdef UPBDEBUG if (upbdebug) log(LOG_DEBUG, "upecc, BSE: bn %d cn %d tn %d sn %d\n", bn, cn, tn, sn); #endif if (bp->b_flags & B_BAD) return (0); /* * if not in bad sector table, return 0 */ if ((bn = isbad(&upbad[ui->ui_unit], cn, tn, sn)) < 0) return(0); /* * flag this one as bad */ bp->b_flags |= B_BAD; bp->b_error = npf + 1; #ifdef UPBDEBUG if (upbdebug) log(LOG_DEBUG "BSE: restart at %d\n",npf+1); #endif bn = lp->d_ncylinders * lp->d_secpercyl - lp->d_nsectors - 1 - bn; sc->sc_badbn = bn; fixregs: cn = bn / lp->d_secpercyl; sn = bn % lp->d_secpercyl; tn = sn / lp->d_nsectors; sn %= lp->d_nsectors; bcr = bp->b_bcount - (int)ptob(npf); bcr = MIN(bcr, 512); up->upwc = -bcr; #ifdef UPBDEBUG if (upbdebug) log(LOG_DEBUG, "revector to cn %d tn %d sn %d\n", cn, tn, sn); #endif break; case CONT: #ifdef UPBDEBUG if (upbdebug) log(LOG_DEBUG, "upecc, CONT: bn %d cn %d tn %d sn %d\n", bn, cn, tn, sn); #endif bp->b_flags &= ~B_BAD; if ((int)ptob(npf) >= bp->b_bcount) return (0); up->upwc = -((bp->b_bcount - (int)ptob(npf)) / sizeof(short)); break; } if (up->upwc == 0) { um->um_tab.b_active = 0; /* retry */ 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). */ #ifdef notdef up->uper1 = 0; up->upcs1 |= UP_GO; #else up->upcs1 = UP_TRE|UP_IE|UP_DCLR|UP_GO; up->updc = cn; up->upda = (tn << 8) | sn; ubaddr = (int)ptob(reg) + o; up->upba = ubaddr; cmd = (ubaddr >> 8) & 0x300; cmd |= ((bp->b_flags&B_READ)?UP_RCOM:UP_WCOM)|UP_IE|UP_GO; um->um_tab.b_errcnt = 0; up->upcs1 = cmd; #endif sc->sc_blkdone = npf; return (1); } /* * Reset driver after UBA init. * Cancel software state of all pending transfers * and restart all units and the controller. */ upreset(uban) int uban; { register struct uba_ctlr *um; register struct uba_device *ui; register sc21, unit; for (sc21 = 0; sc21 < NSC; sc21++) { if ((um = upminfo[sc21]) == 0 || um->um_ubanum != uban || um->um_alive == 0) continue; printf(" sc%d", sc21); um->um_tab.b_active = 0; um->um_tab.b_actf = um->um_tab.b_actl = 0; up_softc[sc21].sc_recal = 0; up_softc[sc21].sc_wticks = 0; if (um->um_ubinfo) { printf("<%d>", (um->um_ubinfo>>28)&0xf); um->um_ubinfo = 0; } ((struct updevice *)(um->um_addr))->upcs2 = UPCS2_CLR; for (unit = 0; unit < NUP; unit++) { if ((ui = updinfo[unit]) == 0) continue; if (ui->ui_alive == 0 || ui->ui_mi != um) continue; uputab[unit].b_active = 0; (void) upustart(ui); } (void) upstart(um); } } /* * 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 UNIBUS * and begin anew. */ upwatch() { register struct uba_ctlr *um; register sc21, unit; register struct up_softc *sc; timeout(upwatch, (caddr_t)0, hz); for (sc21 = 0; sc21 < NSC; sc21++) { um = upminfo[sc21]; if (um == 0 || um->um_alive == 0) continue; sc = &up_softc[sc21]; if (um->um_tab.b_active == 0) { for (unit = 0; unit < NUP; unit++) if (uputab[unit].b_active && updinfo[unit]->ui_mi == um) goto active; sc->sc_wticks = 0; continue; } active: sc->sc_wticks++; if (sc->sc_wticks >= 20) { sc->sc_wticks = 0; printf("sc%d: lost interrupt\n", sc21); ubareset(um->um_ubanum); } } } #define DBSIZE 20 updump(dev) dev_t dev; { struct updevice *upaddr; char *start; int num, blk, unit; struct size *sizes; register struct uba_regs *uba; register struct uba_device *ui; register short *rp; struct upst *st; register int retry; register struct disklabel *lp; unit = upunit(dev); if (unit >= NUP) return (ENXIO); #define phys(cast, addr) ((cast)((int)addr & 0x7fffffff)) ui = phys(struct uba_device *, updinfo[unit]); if (ui == 0 || ui->ui_alive == 0) return (ENXIO); uba = phys(struct uba_hd *, ui->ui_hd)->uh_physuba; ubainit(uba); upaddr = (struct updevice *)ui->ui_physaddr; DELAY(5000000); num = maxfree; upaddr->upcs2 = unit; DELAY(100); upaddr->upcs1 = UP_DCLR|UP_GO; upaddr->upcs1 = UP_PRESET|UP_GO; upaddr->upof = UPOF_FMT22; retry = 0; do { DELAY(25); if (++retry > 527) break; } while ((upaddr->upds & UP_RDY) == 0); if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) return (EFAULT); start = 0; if (dumplo < 0) return (EINVAL); lp = &uplabel[unit]; /* should check uupart(dev) < lp->p_npartitions ? */ if (dumplo + num >= lp->d_partitions[uppart(dev)].p_size) num = lp->d_partitions[uppart(dev)].p_size - dumplo; while (num > 0) { register struct pte *io; register int i; int blk, cn, sn, tn; daddr_t bn; blk = num > DBSIZE ? DBSIZE : num; io = uba->uba_map; for (i = 0; i < blk; i++) *(int *)io++ = (btop(start)+i) | (1<<21) | UBAMR_MRV; *(int *)io = 0; bn = dumplo + btop(start); cn = (bn + lp->d_partitions[hppart(dev)].p_offset) / lp->d_secpercyl; sn = bn % lp->d_secpercyl; tn = sn / lp->d_nsectors; sn = sn % lp->d_nsectors; upaddr->updc = cn; rp = (short *) &upaddr->upda; *rp = (tn << 8) + sn; *--rp = 0; *--rp = -blk*NBPG / sizeof (short); *--rp = UP_GO|UP_WCOM; retry = 0; do { DELAY(25); if (++retry > 527) break; } while ((upaddr->upcs1 & UP_RDY) == 0); if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) { printf("up%d: not ready", unit); if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) { printf("\n"); return (EIO); } printf(" (flakey)\n"); } if (upaddr->upds&UPDS_ERR) return (EIO); start += blk*NBPG; num -= blk; } return (0); } upsize(dev) dev_t dev; { register int unit = upunit(dev); struct uba_device *ui; if (unit >= NUP || (ui = updinfo[unit]) == 0 || ui->ui_alive == 0 || upsoftc[unit].sc_state != OPEN) return (-1); return ((int)uplabel[unit].d_partitions[uppart(dev)].p_size); } #ifdef COMPAT_42 /* * Compatibility code to fake up pack label * for unlabeled disks. */ struct size { daddr_t nblocks; int cyloff; } up9300_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 */ 81376, 681, /* F=cyl 681 thru 814 */ 153728, 562, /* G=cyl 562 thru 814 */ 291346, 82, /* H=cyl 82 thru 561 */ }, up9766_sizes[8] = { 15884, 0, /* A=cyl 0 thru 26 */ 33440, 27, /* B=cyl 27 thru 81 */ 500384, 0, /* C=cyl 0 thru 822 */ 15884, 562, /* D=cyl 562 thru 588 */ 55936, 589, /* E=cyl 589 thru 680 */ 86240, 681, /* F=cyl 681 thru 822 */ 158592, 562, /* G=cyl 562 thru 822 */ 291346, 82, /* H=cyl 82 thru 561 */ }, up160_sizes[8] = { 15884, 0, /* A=cyl 0 thru 49 */ 33440, 50, /* B=cyl 50 thru 154 */ 263360, 0, /* C=cyl 0 thru 822 */ 15884, 155, /* D=cyl 155 thru 204 */ 55936, 205, /* E=cyl 205 thru 379 */ 141664, 380, /* F=cyl 380 thru 822 */ 213664, 155, /* G=cyl 155 thru 822 */ 0, 0, }, upam_sizes[8] = { 15884, 0, /* A=cyl 0 thru 31 */ 33440, 32, /* B=cyl 32 thru 97 */ 524288, 0, /* C=cyl 0 thru 1023 */ 15884, 668, /* D=cyl 668 thru 699 */ 55936, 700, /* E=cyl 700 thru 809 */ 109472, 810, /* F=cyl 810 thru 1023 */ 182176, 668, /* G=cyl 668 thru 1023 */ 291346, 98, /* H=cyl 98 thru 667 */ }, up980_sizes[8] = { 15884, 0, /* A=cyl 0 thru 99 */ 33440, 100, /* B=cyl 100 thru 308 */ 131680, 0, /* C=cyl 0 thru 822 */ 15884, 309, /* D=cyl 309 thru 408 */ 55936, 409, /* E=cyl 409 thru 758 */ 10080, 759, /* F=cyl 759 thru 822 */ 82080, 309, /* G=cyl 309 thru 822 */ 0, 0, }, upeagle_sizes[8] = { 15884, 0, /* A=cyl 0 thru 16 */ 66880, 17, /* B=cyl 17 thru 86 */ 808320, 0, /* C=cyl 0 thru 841 */ 15884, 391, /* D=cyl 391 thru 407 */ 307200, 408, /* E=cyl 408 thru 727 */ 109296, 728, /* F=cyl 728 thru 841 */ 432816, 391, /* G=cyl 391 thru 841 */ 291346, 87, /* H=cyl 87 thru 390 */ }; struct upst { short nsect; /* # sectors/track */ short ntrak; /* # tracks/cylinder */ short nspc; /* # sectors/cylinder */ short ncyl; /* # cylinders */ struct size *sizes; /* partition tables */ short sdist; /* seek distance metric */ short rdist; /* rotational distance metric */ short mdist; /* min rotation (all guesswork) */ } upst[] = { { 32, 19, 32*19, 815, up9300_sizes, 3, 4, 1 }, /*9300*/ { 32, 19, 32*19, 823, up9766_sizes, 3, 4, 1 }, /*9766*/ { 32, 10, 32*10, 823, up160_sizes, 3, 4, 1 }, /*fuji160m*/ { 32, 16, 32*16, 1024, upam_sizes, 7, 8, 2 }, /*Capricorn*/ { 32, 5, 32*5, 823, up980_sizes, 3, 4, 1 }, /*DM980*/ { 48, 20, 48*20, 842, upeagle_sizes, 15, 8, 3 }, /*EAGLE*/ { 0, 0, 0, 0, 0, 0, 0, 0 } }; /* * These variable are all measured in sectors. * Sdist is how much to "lead" in the search for a desired sector * (i.e. if want N, search for N-sdist.) * Maxdist and mindist define the region right before our desired sector within * which we don't bother searching. We don't search when we are already less * then maxdist and more than mindist sectors "before" our desired sector. * Maxdist should be >= sdist. * * Beware, sdist, mindist and maxdist are not well tuned * for many of the drives listed in this table. * Try patching things with something i/o intensive * running and watch iostat. */ /* * Map apparent SC[23]1 drive type into manufacturer * specific configuration. */ upmaptype(ui, lp) register struct uba_device *ui; register struct disklabel *lp; { register int type; register struct upst *st; upphysical(ui->ui_addr, lp); /* get the real drive numbers */ for (st = upst, type = 0; st->nsect != 0; st++, type++) if (lp->d_ntracks == st->ntrak && lp->d_nsectors == st->nsect && lp->d_ncylinders == st->ncyl) break; if (st->nsect == 0) { printf(": %dx%dx%d (s,t,c)?", lp->d_nsectors, lp->d_ntracks, lp->d_ncylinders); return (0); } ui->ui_type = type; /* * set up minimal disk label. */ st = &hpst[type]; lp->d_secsize = 512; lp->d_nsectors = st->nsect; lp->d_ntracks = st->ntrak; lp->d_secpercyl = st->nspc; lp->d_ncylinders = st->ncyl; lp->d_secperunit = st->nspc * st->ncyl; lp->d_sdist = st->sdist; lp->d_mindist = st->rdist; lp->d_maxdist = st->mdist; bcopy(hpst[type].name, lp->d_typename, sizeof(lp->d_typename)); lp->d_npartitions = 8; for (i = 0; i < 8; i++) { lp->d_partitions[i].p_offset = st->sizes[i].cyloff * lp->d_secpercyl; lp->d_partitions[i].p_size = st->sizes[i].nblocks; } return (1); } #endif /* COMPAT_42 */ #endif /* NSC > 0 */