-/* up.c 4.6 %G% */
+/* up.c 4.49 82/05/19 */
-#include "../conf/up.h"
-#if NUP > 0
-#if SC11 > 0
-#include "../dev/up.c.SC11"
-#else
+#include "up.h"
+#if NSC > 0
/*
* UNIBUS disk driver with overlapped seeks and ECC recovery.
+ *
+ * TODO:
+ * Add bad sector forwarding code
+ * Check that offset recovery code works
*/
-#define DELAY(N) { register int d; d = N; while (--d > 0); }
#include "../h/param.h"
#include "../h/systm.h"
+#include "../h/cpu.h"
+#include "../h/nexus.h"
#include "../h/dk.h"
#include "../h/buf.h"
#include "../h/conf.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"
+#include "../h/ubavar.h"
+#include "../h/ubareg.h"
+#include "../h/cmap.h"
-#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 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 2 /* 1.0 msec */
-#define _upRDIST 4 /* 2.0 msec */
+#include "../h/upreg.h"
-int upSDIST = _upSDIST;
-int upRDIST = _upRDIST;
+struct up_softc {
+ int sc_softas;
+ int sc_ndrive;
+ int sc_wticks;
+ int sc_recal;
+} up_softc[NSC];
-/*
- * 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.
- *
- * Note: sizes here are for AMPEX drives with 815 cylinders.
- * CDC drives can make the F,G, and H areas larger as they have 823 cylinders.
- */
+/* THIS SHOULD BE READ OFF THE PACK, PER DRIVE */
struct size
{
daddr_t nblocks;
int cyloff;
} up_sizes[8] = {
+#ifdef ERNIE
+ 49324, 0, /* A=cyl 0 thru 26 */
+#else
15884, 0, /* A=cyl 0 thru 26 */
+#endif
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 */
+#ifndef NOBADSECT
+ 81376, 681, /* F=cyl 681 thru 814 */
+ 153728, 562, /* G=cyl 562 thru 814 */
+#else
+ 81472, 681,
+ 153824, 562,
+#endif
291346, 82, /* H=cyl 82 thru 561 */
+}, fj_sizes[8] = {
+ 15884, 0, /* A=cyl 0 thru 49 */
+ 33440, 50, /* B=cyl 50 thru 154 */
+ 263360, 0, /* C=cyl 0 thru 822 */
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ 0, 0,
+#ifndef NOBADSECT
+ 213664, 155, /* H=cyl 155 thru 822 */
+#else
+ 213760, 155,
+#endif
+}, am_sizes[8] = {
+ 15884, 0, /* A=cyl 0 thru 31 */
+ 33440, 32, /* B=cyl 32 thru 97 */
+ 524288, 0, /* C=cyl 0 thru 1023 */
+ 27786, 668,
+ 27786, 723,
+ 125440, 778,
+ 181760, 668, /* G=cyl 668 thru 1022 */
+ 291346, 98, /* H=cyl 98 thru 667 */
};
+/* END OF STUFF WHICH SHOULD BE READ IN PER DISK */
/*
- * 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.)
- *
- * NB: Not all drives/controllers emulate all of these.
+ * On a 780 upSDIST could be 2, but
+ * in the interest of 750's...
*/
-#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,
-};
+#define _upSDIST 3 /* 1.5 msec */
+#define _upRDIST 4 /* 2.0 msec */
-/*
- * 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;
+int upSDIST = _upSDIST;
+int upRDIST = _upRDIST;
+
+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];
-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 */
-#define MXF 01000
-#define NEM 04000
-
-/* 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 */
+struct upst {
+ short nsect;
+ short ntrak;
+ short nspc;
+ short ncyl;
+ struct size *sizes;
+} upst[] = {
+ 32, 19, 32*19, 823, up_sizes, /* 9300/cdc */
+/* 9300 actually has 815 cylinders... */
+ 32, 10, 32*10, 823, fj_sizes, /* fujitsu 160m */
+ 32, 16, 32*16, 1024, am_sizes, /* ampex capricorn */
+};
-#define b_cylin b_resid
+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
+};
-int up_ubinfo; /* Information about UBA usage saved here */
+struct buf rupbuf[NUP];
-int up_wticks; /* Ticks waiting for interrupt */
-int upwstart; /* Have started guardian */
-int upwatch();
+#define b_cylin b_resid
#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;
+
+int upwstart, upwatch(); /* Have started guardian */
+int upseek;
+int upwaitdry;
+
+/*ARGSUSED*/
+upprobe(reg)
+ caddr_t reg;
{
- register struct buf *dp;
- register unit, xunit;
- long sz, bn;
+ register int br, cvec;
+
+#ifdef lint
+ br = 0; cvec = br; br = cvec;
+#endif
+ ((struct updevice *)reg)->upcs1 = UP_IE|UP_RDY;
+ DELAY(10);
+ ((struct updevice *)reg)->upcs1 = 0;
+ return (1);
+}
+
+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 struct updevice *upaddr;
if (upwstart == 0) {
- timeout(upwatch, (caddr_t)0, HZ);
+ timeout(upwatch, (caddr_t)0, hz);
upwstart++;
}
- xunit = minor(bp->b_dev) & 077;
- sz = bp->b_bcount;
- sz = (sz+511) >> 9; /* transfer size in 512 byte sectors */
+ if (ui->ui_dk >= 0)
+ dk_mspw[ui->ui_dk] = .0000020345;
+ upip[ui->ui_ctlr][ui->ui_slave] = ui;
+ up_softc[ui->ui_ctlr].sc_ndrive++;
+ upaddr = (struct updevice *)ui->ui_addr;
+ upaddr->upcs1 = 0;
+ upaddr->upcs2 = ui->ui_slave;
+ upaddr->uphr = UPHR_MAXTRAK;
+ if (upaddr->uphr == 9)
+ ui->ui_type = 1; /* fujitsu hack */
+ else if (upaddr->uphr == 15)
+ ui->ui_type = 2; /* ampex hack */
+ upaddr->upcs2 = UPCS2_CLR;
+/*
+ upaddr->uphr = UPHR_MAXCYL;
+ printf("maxcyl %d\n", upaddr->uphr);
+ upaddr->uphr = UPHR_MAXTRAK;
+ printf("maxtrak %d\n", upaddr->uphr);
+ upaddr->uphr = UPHR_MAXSECT;
+ printf("maxsect %d\n", upaddr->uphr);
+*/
+}
+
+upstrategy(bp)
+ register struct buf *bp;
+{
+ register struct uba_device *ui;
+ register struct upst *st;
+ register int unit;
+ register struct buf *dp;
+ int xunit = minor(bp->b_dev) & 07;
+ long bn, sz;
+
+ sz = (bp->b_bcount+511) >> 9;
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;
- }
- if (DK_N+unit <= DK_NMAX)
- dk_mspw[DK_N+unit] = .0000020345;
- bp->b_cylin = bn/(NSECT*NTRAC) + up_sizes[xunit&07].cyloff;
- dp = &uputab[unit];
+ if (unit >= NUP)
+ goto bad;
+ ui = updinfo[unit];
+ if (ui == 0 || ui->ui_alive == 0)
+ goto bad;
+ st = &upst[ui->ui_type];
+ if (bp->b_blkno < 0 ||
+ (bn = dkblock(bp))+sz > st->sizes[xunit].nblocks)
+ goto bad;
+ bp->b_cylin = bn/st->nspc + st->sizes[xunit].cyloff;
(void) spl5();
+ dp = &uputab[ui->ui_unit];
disksort(dp, bp);
if (dp->b_active == 0) {
- (void) upustart(unit);
- if (uptab.b_actf && uptab.b_active == 0)
- (void) upstart();
+ (void) upustart(ui);
+ bp = &ui->ui_mi->um_tab;
+ if (bp->b_actf && bp->b_active == 0)
+ (void) upstart(ui->ui_mi);
}
(void) spl0();
+ return;
+
+bad:
+ bp->b_flags |= B_ERROR;
+ iodone(bp);
+ return;
}
/*
- * 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.
+ * 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(unit)
-register unit;
+upustart(ui)
+ register struct uba_device *ui;
{
register struct buf *bp, *dp;
- register struct device *upaddr = UPADDR;
+ register struct uba_ctlr *um;
+ register struct updevice *upaddr;
+ register struct upst *st;
daddr_t bn;
- int sn, cn, csn;
- int didie = 0;
-
+ int sn, csn;
/*
- * Other drivers tend to say something like
- * upaddr->upcs1 = IE;
- * upaddr->upas = 1<<unit;
- * here, but some controllers will cancel a command
- * happens to be sitting in the cs1 if you clear the go
- * bit by storing there (so the first is not safe).
- *
- * Thus we keep careful track of when we re-enable IE
- * after an interrupt and do it only if we didn't issue
- * a command which re-enabled it as a matter of course.
- * We clear bits in upas in the interrupt routine, when
- * no transfers are active.
+ * 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().
*/
- if (unit >= NUP)
- goto out;
- if (unit+DK_N <= DK_NMAX)
- dk_busy &= ~(1<<(unit+DK_N));
- dp = &uputab[unit];
+ int didie = 0;
+
+ if (ui == 0)
+ return (0);
+ um = ui->ui_mi;
+ dk_busy &= ~(1<<ui->ui_dk);
+ dp = &uputab[ui->ui_unit];
if ((bp = dp->b_actf) == NULL)
goto out;
/*
- * Most controllers don't start SEARCH commands when transfers are
- * in progress. In fact, some tend 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 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 (uptab.b_active) {
- upsoftas |= 1<<unit;
+ 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;
- if ((upaddr->upcs2 & 07) != unit)
- upaddr->upcs2 = unit;
+ upaddr = (struct updevice *)um->um_addr;
+ upaddr->upcs2 = ui->ui_slave;
/*
- * 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 drive has just come up,
+ * setup the pack.
*/
- if ((upaddr->upds & VV) == 0) {
- upaddr->upcs1 = IE|DCLR|GO;
- upaddr->upcs1 = IE|PRESET|GO;
- upaddr->upof = FMT22;
+ if ((upaddr->upds & UPDS_VV) == 0) {
+ /* SHOULD WARN SYSTEM THAT THIS HAPPENED */
+ upaddr->upcs1 = UP_IE|UP_DCLR|UP_GO;
+ upaddr->upcs1 = UP_IE|UP_PRESET|UP_GO;
+ upaddr->upof = UPOF_FMT22;
didie = 1;
}
- if ((upaddr->upds & (DPR|MOL)) != (DPR|MOL))
+ /*
+ * 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;
/*
- * 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.
+ * 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 = dkblock(bp);
- cn = bp->b_cylin;
- sn = bn%(NSECT*NTRAC);
- sn = (sn+NSECT-upSDIST)%NSECT;
-
- if (cn - upaddr->updc)
+ sn = bn%st->nspc;
+ sn = (sn + st->nsect - upSDIST) % st->nsect;
+ if (bp->b_cylin - 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)
+ csn += st->nsect;
+ if (csn > st->nsect - upRDIST)
goto done;
-
search:
- upaddr->updc = cn;
+ upaddr->updc = bp->b_cylin;
+ /*
+ * Not on cylinder at correct position,
+ * seek/search.
+ */
if (upseek)
- upaddr->upcs1 = IE|SEEK|GO;
+ upaddr->upcs1 = UP_IE|UP_SEEK|UP_GO;
else {
upaddr->upda = sn;
- upaddr->upcs1 = IE|SEARCH|GO;
+ upaddr->upcs1 = UP_IE|UP_SEARCH|UP_GO;
}
didie = 1;
/*
- * Mark this unit busy.
+ * Mark unit busy for iostat.
*/
- unit += DK_N;
- if (unit <= DK_NMAX) {
- dk_busy |= 1<<unit;
- dk_seek[unit]++;
+ if (ui->ui_dk >= 0) {
+ dk_busy |= 1<<ui->ui_dk;
+ dk_seek[ui->ui_dk]++;
}
goto out;
-
done:
/*
- * This unit is ready to go so
- * link it onto the chain of ready disks.
+ * Device is ready to go.
+ * Put it on the ready queue for the controller
+ * (unless its already there.)
*/
- dp->b_forw = NULL;
- if (uptab.b_actf == NULL)
- uptab.b_actf = dp;
- else
- uptab.b_actl->b_forw = dp;
- uptab.b_actl = dp;
-
+ 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 a transfer; call from top level at spl5() or on interrupt.
+ * Start up a transfer on a drive.
*/
-upstart()
+upstart(um)
+ register struct uba_ctlr *um;
{
register struct buf *bp, *dp;
- register unit;
- register struct device *upaddr;
+ register struct uba_device *ui;
+ register struct updevice *upaddr;
+ struct upst *st;
daddr_t bn;
- int dn, sn, tn, cn, cmd;
+ int dn, sn, tn, cmd, waitdry;
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.
+ * Pull a request off the controller queue
*/
- if ((dp = uptab.b_actf) == NULL)
+ if ((dp = um->um_tab.b_actf) == NULL)
return (0);
if ((bp = dp->b_actf) == NULL) {
- uptab.b_actf = dp->b_forw;
+ um->um_tab.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.
+ * Mark controller busy, and
+ * determine destination of this request.
*/
- uptab.b_active++;
- unit = minor(bp->b_dev) & 077;
- dn = dkunit(bp);
+ um->um_tab.b_active++;
+ ui = updinfo[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)
+ 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;
- up_ubinfo = ubasetup(bp, 1);
/*
- * 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.)
+ * Check that it is ready and online
*/
- 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;
+ waitdry = 0;
+ while ((upaddr->upds&UPDS_DRY) == 0) {
+ if (++waitdry > 512)
+ break;
+ upwaitdry++;
+ }
+ if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) {
+ printf("up%d: not ready", dkunit(bp));
+ 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);
- /* 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;
- while (upaddr->upds & PIP)
- DELAY(25);
+ /*
+ * Oh, well, sometimes this
+ * happens, for reasons unknown.
+ */
+ printf(" (flakey)\n");
}
/*
- * 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.
+ * Setup for the transfer, and get in the
+ * UNIBUS adaptor queue.
*/
- upaddr->updc = cn;
+ upaddr->updc = bp->b_cylin;
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;
+ cmd = UP_IE|UP_RCOM|UP_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 (unit <= DK_NMAX) {
- dk_busy |= 1<<unit;
- dk_xfer[unit]++;
- dk_wds[unit] += bp->b_bcount>>6;
- }
+ cmd = UP_IE|UP_WCOM|UP_GO;
+ um->um_cmd = cmd;
+ (void) ubago(ui);
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.
+ * 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++; /* should now be 2 */
+ upaddr->upba = um->um_ubinfo;
+ upaddr->upcs1 = um->um_cmd|((um->um_ubinfo>>8)&0x300);
+}
+
+/*
+ * Handle a disk interrupt.
*/
-upintr()
+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;
- 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);
+ 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;
+ }
+ /*
+ * 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[dkunit(bp)];
+ 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)) {
+ waitdry = 0;
+ while ((upaddr->upds & UPDS_DRY) == 0) {
+ if (++waitdry > 512)
+ break;
+ upwaitdry++;
}
- /*
- * Mark 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+unit <= DK_NMAX)
- dk_busy &= ~(1<<(DK_N+unit));
- if ((upaddr->upcs2 & 07) != unit)
- upaddr->upcs2 = unit;
- if ((upaddr->upds&ERR) || (upaddr->upcs1&TRE)) {
- int cs2;
- /*
- * 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);
+ if (upaddr->uper1&UPER1_WLE) {
/*
- * 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.
+ * Give up on write locked devices
+ * immediately.
*/
- 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)
- cs2 = (int)upaddr->upcs2;
- deverror(bp, (int)upaddr->upcs2,
- (int)upaddr->uper1);
+ printf("up%d: write locked\n", dkunit(bp));
+ bp->b_flags |= B_ERROR;
+ } else if (++um->um_tab.b_errcnt > 27) {
/*
- * 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.
+ * After 28 retries (16 without offset, and
+ * 12 with offset positioning) give up.
*/
- if ((upaddr->uper1&(DCK|ECH))==DCK && upecc(upaddr, bp))
- return;
+ harderr(bp, "up");
+ printf("cs2=%b er1=%b er2=%b\n",
+ upaddr->upcs2, UPCS2_BITS,
+ upaddr->uper1, UPER1_BITS,
+ upaddr->uper2, UPER2_BITS);
+ bp->b_flags |= B_ERROR;
+ } else {
/*
- * Clear the drive and, every 4 retries, recalibrate
- * to hopefully help clear up seek positioning problems.
+ * Retriable error.
+ * If a soft ecc, correct it (continuing
+ * by returning if necessary.
+ * Otherwise fall through and retry the transfer
*/
- upaddr->upcs1 = TRE|IE|DCLR|GO;
+ um->um_tab.b_active = 0; /* force retry */
+ if ((upaddr->uper1&(UPER1_DCK|UPER1_ECH))==UPER1_DCK)
+ if (upecc(ui))
+ return;
+ }
+ /*
+ * 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;
+ }
+ }
+ /*
+ * 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;
- if ((uptab.b_errcnt&07) == 4) {
- upaddr->upcs1 = RECAL|GO|IE;
- while(upaddr->upds & PIP)
- DELAY(25);
- }
- if (uptab.b_errcnt == 28 && cs2&(NEM|MXF)) {
- printf("FLAKEY UP ");
- ubareset();
- return;
- }
}
+ 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));
+ iodone(bp);
/*
- * 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 this unit has more work to do,
+ * then start it up right away.
*/
- if (uptab.b_active) {
- if (uptab.b_errcnt >= 16) {
- upaddr->upcs1 = RTC|GO|IE;
- while (upaddr->upds & PIP)
- DELAY(25);
+ if (dp->b_actf)
+ if (upustart(ui))
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<<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(as)) {
+ unit--; /* was 1 origin */
as &= ~(1<<unit);
- upsoftas &= ~(1<<unit);
- ubafree(up_ubinfo), up_ubinfo = 0;
- } else {
- if (upaddr->upcs1 & TRE)
- upaddr->upcs1 = TRE;
+ upaddr->upas = 1<<unit;
+ if (unit < UPIPUNITS && upustart(upip[sc21][unit]))
+ needie = 0;
}
/*
- * 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.
+ * If the controller is not transferring, but
+ * there are devices ready to transfer, start
+ * the controller.
*/
- as |= upsoftas;
- oupsoftas = upsoftas;
- upsoftas = 0;
- for (unit = 0; unit < NUP; unit++)
- if ((as|oupsoftas) & (1<<unit)) {
- if (as & (1<<unit))
- upaddr->upas = 1<<unit;
- if (upustart(unit))
- needie = 0;
- }
- if (uptab.b_actf && uptab.b_active == 0)
- if (upstart())
+ if (um->um_tab.b_actf && um->um_tab.b_active == 0)
+ if (upstart(um))
needie = 0;
if (needie)
- upaddr->upcs1 = IE;
+ upaddr->upcs1 = UP_IE;
}
upread(dev)
+ dev_t dev;
{
+ register int unit = minor(dev) >> 3;
- physio(upstrategy, &rupbuf, dev, B_READ, minphys);
+ if (unit >= NUP)
+ u.u_error = ENXIO;
+ else
+ physio(upstrategy, &rupbuf[unit], dev, B_READ, minphys);
}
upwrite(dev)
+ dev_t dev;
{
+ register int unit = minor(dev) >> 3;
- physio(upstrategy, &rupbuf, dev, B_WRITE, minphys);
+ if (unit >= NUP)
+ u.u_error = ENXIO;
+ else
+ physio(upstrategy, &rupbuf[unit], dev, B_WRITE, minphys);
}
/*
* 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;
+upecc(ui)
+ register struct uba_device *ui;
{
- struct uba_regs *ubp = (struct uba_regs *)UBA0;
+ 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 upst *st;
+ struct uba_regs *ubp = ui->ui_hd->uh_uba;
register int i;
caddr_t addr;
int reg, bit, byte, npf, mask, o, cmd, ubaddr;
* 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;
+ reg = btop(um->um_ubinfo&0x3ffff) + npf;
o = (int)bp->b_un.b_addr & PGOFSET;
- printf("%D ", bp->b_blkno+npf);
- prdev("ECC", bp->b_dev);
+ printf("up%d%c: soft ecc sn%d\n", dkunit(bp),
+ 'a'+(minor(bp->b_dev)&07), bp->b_blkno + npf);
mask = up->upec2;
- if (mask == 0) {
- up->upof = FMT22; /* == RTC ???? */
- return (0);
- }
+#ifdef UPECCDEBUG
+ printf("npf %d reg %x o %d mask %o pos %d\n", npf, reg, o, mask,
+ up->upec1);
+#endif
/*
* 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;
+ ubapurge(um);
i = up->upec1 - 1; /* -1 makes 0 origin */
bit = i&07;
i = (i&~07)>>3;
while (i < 512 && (int)ptob(npf)+i < bp->b_bcount && bit > -11) {
addr = ptob(ubp->uba_map[reg+btop(byte)].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;
}
- uptab.b_active++; /* Either complete or continuing... */
+ um->um_tab.b_active = 2; /* Either complete or continuing... */
if (up->upwc == 0)
return (0);
/*
* 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;
+#ifdef notdef
+ up->uper1 = 0;
+ up->upcs1 |= UP_GO;
+#else
+ up->upcs1 = UP_TRE|UP_IE|UP_DCLR|UP_GO;
bn = dkblock(bp);
+ st = &upst[ui->ui_type];
cn = bp->b_cylin;
- sn = bn%(NSECT*NTRAC) + npf + 1;
- tn = sn/NSECT;
- sn %= NSECT;
- cn += tn/NTRAC;
- tn %= NTRAC;
+ sn = bn%st->nspc + npf + 1;
+ tn = sn/st->nsect;
+ sn %= st->nsect;
+ cn += tn/st->ntrak;
+ tn %= st->ntrak;
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;
+ cmd |= UP_IE|UP_GO|UP_RCOM;
up->upcs1 = cmd;
+#endif
return (1);
}
* Cancel software state of all pending transfers
* and restart all units and the controller.
*/
-upreset()
+upreset(uban)
+ int uban;
{
- int unit;
-
- printf(" up");
- DELAY(15000000); /* give it time to self-test */
- uptab.b_active = 0;
- uptab.b_actf = uptab.b_actl = 0;
- if (up_ubinfo) {
- printf("<%d>", (up_ubinfo>>28)&0xf);
- ubafree(up_ubinfo), up_ubinfo = 0;
- }
- UPADDR->upcs2 = CLR; /* clear controller */
- for (unit = 0; unit < NUP; unit++) {
- uputab[unit].b_active = 0;
- (void) upustart(unit);
+ 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);
+ ubadone(um);
+ }
+ ((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);
}
- (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
+ * If nothing happens for 20 seconds, reset the UNIBUS
* and begin anew.
*/
upwatch()
{
- int i;
-
- timeout(upwatch, (caddr_t)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;
- }
+ 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:
- up_wticks++;
- if (up_wticks >= 20) {
- up_wticks = 0;
- printf("LOST INTERRUPT RESET");
- upreset();
- printf("\n");
+ sc->sc_wticks++;
+ if (sc->sc_wticks >= 20) {
+ sc->sc_wticks = 0;
+ printf("sc%d: lost interrupt\n", sc21);
+ ubareset(um->um_ubanum);
+ }
}
}
-#endif
+
+#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;
+
+ unit = minor(dev) >> 3;
+ if (unit >= NUP)
+ return (ENXIO);
+#define phys(cast, addr) ((cast)((int)addr & 0x7fffffff))
+ ui = phys(struct uba_device *, updinfo[unit]);
+ if (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(2000000);
+ num = maxfree;
+ start = 0;
+ upaddr->upcs2 = unit;
+ DELAY(100);
+ if ((upaddr->upcs1&UP_DVA) == 0)
+ return (EFAULT);
+ if ((upaddr->upds & UPDS_VV) == 0) {
+ upaddr->upcs1 = UP_DCLR|UP_GO;
+ upaddr->upcs1 = UP_PRESET|UP_GO;
+ upaddr->upof = UPOF_FMT22;
+ }
+ if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY)
+ return (EFAULT);
+ st = &upst[ui->ui_type];
+ sizes = phys(struct size *, st->sizes);
+ if (dumplo < 0 || dumplo + num >= sizes[minor(dev)&07].nblocks)
+ return (EINVAL);
+ while (num > 0) {
+ register struct pte *io;
+ register int i;
+ int 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/st->nspc + sizes[minor(dev)&07].cyloff;
+ sn = bn%st->nspc;
+ tn = sn/st->nsect;
+ sn = sn%st->nsect;
+ upaddr->updc = cn;
+ rp = (short *) &upaddr->upda;
+ *rp = (tn << 8) + sn;
+ *--rp = 0;
+ *--rp = -blk*NBPG / sizeof (short);
+ *--rp = UP_GO|UP_WCOM;
+ do {
+ DELAY(25);
+ } while ((upaddr->upcs1 & UP_RDY) == 0);
+ if (upaddr->upds&UPDS_ERR)
+ return (EIO);
+ start += blk*NBPG;
+ num -= blk;
+ }
+ return (0);
+}
#endif