[net.sources] A replacement for the 4.3BSD UDA50 driver

chris@mimsy.UUCP (04/05/87)

This is (or, depending on article latency and ordering at your
site, was or will be) a replacement for the 4.3BSD UDA50 driver.

Why you should use this driver:

	It fixes a number of bugs in the existing driver.  Most
	of these show up only when your hardware breaks.

	It decodes errors reasonably well.  Older drivers print
	incomplete and/or incomprehensible data.

Why you should *not* use this driver:

	It has no MicroVAX support.  (There is a problem in the
	crash dump code on uVaxen, and it does not handle RD53s
	and such.  If you add RD disk support, it will work as
	well as the 4.3BSD driver.)

The driver itself is in three separate shell archives.  These should
be run in an empty directory.  Read the file `Installation' for
installation instructions.  There is a replacement manual entry
which describes everything I know about each of the various device
errors that the driver handles.

If you choose to accept this mission ... oops, wrong tape.  If you
run into trouble, or have changes for the driver, send me mail.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris@mimsy.umd.edu

chris@mimsy.UUCP (04/05/87)

: Run this shell script with "sh" not "csh"
PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
export PATH
all=FALSE
if [ x$1 = x-a ]; then
	all=TRUE
fi
echo Extracting Installation
sed 's/^X//' <<'//go.sysin dd *' >Installation
Installation instructions (such as they are):

1.  Install the `vax' files in /sys/vax.  These are:

	mscp.c		(new)
	mscp.h		(replacement - be sure to keep backups!)
	mscpvar.h	(new)

2.  Install the `vaxuba' files in /sys/vaxuba.  These are:

	uba.c.diff	(patch)
	ubavar.h.diff	(patch)
	uda.c		(replacement)
	udareg.h	(replacement)

    You can use the `patch' program (in /usr/new) to apply the patches.

3.  Install the `vaxif' file in /sys/vaxif:

	if_ec.c.diff	(patch)

4.  Edit /sys/conf/files.vax: add the line

	vax/mscp.c	optional genmscp

    (in alphabetical order if you like to preseve such things).

5.  Edit your configuration file (/sys/conf/PICKLE or whatnot): add the line

	pseudo-device	genmscp

    You should also add the same line to the LINT configuration file,
    if you run lint on your kernel code.

6.  Configure a new kernel (see `Installing and Operating 4.3BSD' in the
    System Manager's Manual).

	/etc/config PICKLE	# or whatever
	cd ../PICKLE		# or whatever
	make depend
	make

    This will take some time.

7.  Put the new kernel in /, and try booting it.  If it works, cheer.
    Put the new manual entry in /usr/man/man4/uda.4, and the standalone
    uda.c in /sys/stand/uda.c.
//go.sysin dd *
if [ `wc -c < Installation` != 1219 ]; then
	made=FALSE
	echo error transmitting Installation --
	echo length should be 1219, not `wc -c < Installation`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 Installation
	echo -n '	'; ls -ld Installation
fi
echo Making directory vax
mkdir vax
echo Extracting vax/mscp.c
sed 's/^X//' <<'//go.sysin dd *' >vax/mscp.c
X/*
 * MSCP generic driver routines
 */

#include "param.h"
#include "buf.h"
#include "errno.h"
#include "dk.h"
#include "syslog.h"

#include "../vaxuba/ubavar.h"

#include "mscp.h"
#include "mscpvar.h"

#define	PCMD	PSWP		/* priority for command packet waits */

X/*
 * During transfers, Unibus mapping info is saved in the buffer's
 * `ubinfo' field.
 */
#define	b_ubinfo b_resid

X/*
 * Get a command packet.  Second argument is true iff we are
 * to wait if necessary.  Return NULL if none are available and
 * we cannot wait.
 */
struct mscp *
mscp_getcp(mi, canwait)
	register struct mscp_info *mi;
	int canwait;
{
#define	mri	(&mi->mi_cmd)
	register struct mscp *mp;
	register int i;
	int s = spl5();

again:
	/*
	 * Ensure that we have some command credits, and
	 * that the next command packet is free.
	 */
	if (mi->mi_credits <= MSCP_MINCREDITS) {
		if (!canwait) {
			splx(s);
			return (NULL);
		}
		mi->mi_wantcredits = 1;
		sleep((caddr_t) &mi->mi_wantcredits, PCMD);
		goto again;
	}
	i = mri->mri_next;
	if (mri->mri_desc[i] & MSCP_OWN) {
		if (!canwait) {
			splx(s);
			return (NULL);
		}
		mi->mi_wantcmd = 1;
		sleep((caddr_t) &mi->mi_wantcmd, PCMD);
		goto again;
	}
	mi->mi_credits--;
	mri->mri_desc[i] &= ~MSCP_INT;
	mri->mri_next = (mri->mri_next + 1) % mri->mri_size;
	splx(s);
	mp = &mri->mri_ring[i];

	/*
	 * Initialise some often-zero fields.
	 * ARE THE LAST TWO NECESSARY IN GENERAL?  IT SURE WOULD BE
	 * NICE IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS.
	 */
	mp->mscp_msglen = MSCP_MSGLEN;
	mp->mscp_flags = 0;
	mp->mscp_modifier = 0;
	mp->mscp_seq.seq_bytecount = 0;
	mp->mscp_seq.seq_buffer = 0;
X/*???*/	mp->mscp_sccc.sccc_errlgfl = 0;
X/*???*/	mp->mscp_sccc.sccc_copyspd = 0;
	return (mp);
#undef	mri
}

#ifdef AVOID_EMULEX_BUG
int	mscp_aeb_xor = 0x8000bb80;
#endif

X/*
 * Do a device go.  The driver calls this routine when its go
 * routine is called; that occurs when the Unibus code has allocated
 * resources for the transfer.  Save the resource information in
 * bp->b_ubinfo, and finish the MSCP packet.
 *
 * N.B.: If we were blocked in ubago(), the drive could have gone
 * off line and might still be that way.  We should probably handle
 * such a case by changing this command into an on line request,
 * freeing the Unibus resources, and requeuing the transfer.
 */
mscp_go(um, mi, mp)
	register struct uba_ctlr *um;
	register struct mscp_info *mi;
	register struct mscp *mp;
{
	register struct buf *bp, *dp;

	/*
	 * Now is also the time to move the transfer off the
	 * controller and drive queues.  However, if there are
	 * more transfers on the drive queue, re-append the drive
	 * to the controller queue.  The point of all this, by
	 * the way, is to try to keep as many drives busy as
	 * possible---to deal the controller's credits out to the
	 * drives in a `fair share' arrangement.  (To do this fully
	 * would be more trouble than it is worth, though.)
	 */
	dp = um->um_tab.b_actf;
	bp = dp->b_actf;
	dp->b_actf = bp->av_forw;	/* transfer off drive queue */
	um->um_tab.b_actf = dp->b_forw;	/* drive off ctlr queue */
	if (dp->b_actf != NULL) {	/* if more on the drive queue */
		APPEND(dp, &um->um_tab, b_forw);/* append to ctlr queue */
	} else
		dp->b_active = 0;

	/*
	 * Move the buffer to the I/O wait queue.
	 */
	bp->av_back = mi->mi_wtab.av_back;
	bp->av_forw = &mi->mi_wtab;
	mi->mi_wtab.av_back->av_forw = bp;
	mi->mi_wtab.av_back = bp;

	/*
	 * Copy the mapping info, finish the command packet, and give
	 * it to the device.  The device's dgo routine should then
	 * initiate polling.
	 */
	bp->b_ubinfo = um->um_ubinfo;
#ifdef AVOID_EMULEX_BUG
	/*
	 * The Emulex SC41/MS will occasionally zero the lower half word
	 * of the command reference number.  The upper half word remains
	 * intact.  To keep running, we convert the buffer address into
	 * a small but nonzero integer that is unique over all pending
	 * transfers, and store that value in the upper half word.  To
	 * catch occurrances of the bug (so that we can gripe to Emulex),
	 * we also put a nonzero value in the lower word.
	 */
	{
		register u_int i = mi->mi_nextbp;

		do {		/* find a free value */
			if (mi->mi_bp[i] == 0)
				goto found;
			i = (i + 1) % AEB_MAX_BP;
		} while (i != mi->mi_nextbp);
		panic("mscp_go: AEB_MAX_BP too small");
found:
		mi->mi_bp[i++] = bp;
		mi->mi_nextbp = i % AEB_MAX_BP;
		mp->mscp_cmdref = (i << 16) ^ mscp_aeb_xor;
	}
#else
	mp->mscp_cmdref = (long) bp;
#endif
	mp->mscp_seq.seq_buffer = (bp->b_ubinfo & 0x3ffff) |
		(UBAI_BDP(bp->b_ubinfo) << 24);
	*mp->mscp_seq.seq_addr |= MSCP_OWN | MSCP_INT;
}

X/*
 * Handle a response ring transition.
 */
mscp_dorsp(mi)
	register struct mscp_info *mi;
{
	register struct uba_ctlr *um = mi->mi_um;
	register struct uba_device *ui;
	register struct buf *bp;
	register struct mscp *mp;
	register int nextrsp;
	struct mscp_driver *md = mi->mi_md;
	char *ctlrname, *drivename;
	int st, error;

	ctlrname = um->um_driver->ud_mname;
	drivename = um->um_driver->ud_dname;
	nextrsp = mi->mi_rsp.mri_next;
loop:
	if (mi->mi_rsp.mri_desc[nextrsp] & MSCP_OWN) {
		/*
		 * No more responses.  Remember the next expected
		 * response index.  Check to see if we have some
		 * credits back, and wake up sleepers if so.
		 */
		mi->mi_rsp.mri_next = nextrsp;
		if (mi->mi_wantcredits && mi->mi_credits > MSCP_MINCREDITS) {
			mi->mi_wantcredits = 0;
			wakeup((caddr_t) &mi->mi_wantcredits);
		}
		return;
	}

	/*
	 * Found a response.  Update credit information.  If there is
	 * nothing else to do, jump to `done' to get the next response.
	 */
	mp = &mi->mi_rsp.mri_ring[nextrsp];
	mi->mi_credits += MSCP_CREDITS(mp->mscp_msgtc);
	switch (MSCP_MSGTYPE(mp->mscp_msgtc)) {

	case MSCPT_SEQ:
		break;

	case MSCPT_DATAGRAM:
		(*md->md_dgram)(um, mp);
		goto done;

	case MSCPT_CREDITS:
		goto done;

	case MSCPT_MAINTENANCE:
	default:
		printf("%s%d: unit %d: unknown message type 0x%x ignored\n",
			ctlrname, um->um_ctlr, mp->mscp_unit,
			MSCP_MSGTYPE(mp->mscp_msgtc));
		goto done;
	}

	/*
	 * Controllers are allowed to interrupt as any drive, so we
	 * must check the command before checking for a drive.
	 */
	if (mp->mscp_opcode == (M_OP_SETCTLRC | M_OP_END)) {
		(*md->md_ctlrdone)(um, mp);
		goto done;
	}

	/*
	 * Find the drive info.  If there is none, and this is an
	 * available attention response, try configuring a new drive.
	 */
	if (mp->mscp_unit > md->md_ndpc) {
		printf("%s%d: unit %d out of range\n",
			ctlrname, um->um_ctlr, mp->mscp_unit);
		goto done;
	}
	if ((ui = mi->mi_ip[mp->mscp_unit]) == NULL) {
		if ((*md->md_unconf)(um, mp) != MSCP_DONE) {
			printf("%s%d: unit %d not configured, ",
				ctlrname, um->um_ctlr, mp->mscp_unit);
			if (mp->mscp_opcode == M_OP_AVAILATTN)
				printf("available attn");
			else
				printf("stray response op 0x%x status 0x%x",
					mp->mscp_opcode, mp->mscp_status);
			printf(" ignored\n");
		}
		goto done;
	}

	/*
	 * Handle individual responses.
	 */
	st = mp->mscp_status & M_ST_MASK;
	error = 0;
	switch (mp->mscp_opcode) {

	case M_OP_END:
		/*
		 * The controller presents a bogus END packet when
		 * a read/write command is given with an illegal
		 * block number.  This is contrary to the MSCP
		 * specification (ENDs are to be given only for
		 * invalid commands), but that is the way of it.
		 */
		if (st == M_ST_INVALCMD && mp->mscp_cmdref != 0) {
			printf("%s%d: bad lbn (%d)?\n",
				drivename, ui->ui_unit, mp->mscp_seq.seq_lbn);
			error = EIO;
			goto rwend;
		}
		goto unknown;

	case M_OP_ONLINE | M_OP_END:
		/*
		 * Finished an ON LINE request.  Call the driver to
		 * find out whether it succeeded.  If so, mark it on
		 * line.
		 */
		if (ui->ui_flags & UNIT_ONLINE) {
			printf("%s%d: duplicate ONLINE ignored\n",
				drivename, ui->ui_unit);
			break;
		}
		if ((*md->md_online)(ui, mp) == MSCP_DONE)
			ui->ui_flags |= UNIT_ONLINE;
		break;

	case M_OP_GETUNITST | M_OP_END:
		/*
		 * Got unit status.  Call the driver to find out
		 * whether it succeeded, and if so, mark it.
		 */
		if ((*md->md_gotstatus)(ui, mp) == MSCP_DONE)
			ui->ui_flags |= UNIT_HAVESTATUS;
		break;

	case M_OP_AVAILATTN:
		/*
		 * The drive went offline and we did not notice.
		 * Mark it off line now, to force an on line request
		 * next, so we can make sure it is still the same
		 * drive.
		 *
		 * IF THE UDA DRIVER HAS A COMMAND AWAITING UNIBUS
		 * RESOURCES, THAT COMMAND MAY GO OUT BEFORE THE ON
		 * LINE.  IS IT WORTH FIXING??
		 */
		ui->ui_flags &= ~(UNIT_ONLINE | UNIT_HAVESTATUS);
		break;

	case M_OP_READ | M_OP_END:
	case M_OP_WRITE | M_OP_END:
		/*
		 * A transfer finished.  Get the buffer, and release its
		 * map registers via ubadone().  If the command finished
		 * with an off line or available status, the drive went
		 * off line (the idiot controller does not tell us until
		 * it comes back *on* line, or until we try to use it).
		 */
		if (mp->mscp_cmdref == 0) {
			/*
			 * No buffer means there is a bug somewhere!
			 */
			printf("%s%d: io done, but no buffer?\n",
				drivename, ui->ui_unit);
			mscp_hexdump(mp);
			break;
		}

rwend:
#ifdef AVOID_EMULEX_BUG
		{
			register u_short *p = (u_short *) &mp->mscp_cmdref;

			/*
			 * Note any errors on the part of the controller.
			 * The lower word should be zero after exclusive
			 * or'ing with mscp_aeb_xor, and the upper should
			 * then be in the range [1..AEB_MAX_BP].
			 */
			mp->mscp_cmdref ^= mscp_aeb_xor;
			p[1]--;
			if (p[1] >= AEB_MAX_BP)
				panic("unrecoverable Emulex screwup");
			if (p[0] == 0)
				mi->mi_ok++;
			else {
				/*
				 * Calculate the expected response,
				 * assuming p[1] is correct.  The
				 * actual response is then the expected
				 * response xor p[0].
				 */
				int sb = ((p[1] + 1) << 16) ^ mscp_aeb_xor;

				log(LOG_WARNING, "\
Emulex SC41/MS screwup: %s%d, got %d correct, then changed 0x%x to 0x%x\n",
					ctlrname, um->um_ctlr,
					mi->mi_ok, sb, sb ^ p[0]);
				mi->mi_ok = 0;
			}
			/* convert index back to buffer, and mark free */
			bp = mi->mi_bp[p[1]];
			mi->mi_bp[p[1]] = 0;
		}
#else
		bp = (struct buf *) mp->mscp_cmdref;
#ifdef MSCP_PARANOIA
		{
			register struct buf *q = mi->mi_wtab.av_forw;

			/*
			 * Ensure that this response corresponds to
			 * some outstanding request.  If not, ignore
			 * it entirely.  This will likely cause a
			 * Unibus reset soon, after which the controller
			 * just might behave.
			 */
			while (q != bp && q != &mi->mi_wtab)
				q = q->av_forw;
			if (q != bp) {
				printf("%s%d: bad response packet ignored\n",
					ctlrname, um->um_ctlr);
				mscp_hexdump(mp);
				goto out;
			}
		}
#endif MSCP_PARANOIA
#endif AVOID_EMULEX_BUG

		/*
		 * Mark any error-due-to-bad-LBN (via `goto rwend').
		 * WHAT STATUS WILL THESE HAVE?  IT SURE WOULD BE NICE
		 * IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS.
		 */
		if (error) {
			bp->b_flags |= B_ERROR;
			bp->b_error = error;
		}
		um->um_ubinfo = bp->b_ubinfo;
		ubadone(um);
		if (st == M_ST_OFFLINE || st == M_ST_AVAILABLE)
			ui->ui_flags &= ~(UNIT_ONLINE | UNIT_HAVESTATUS);

		/*
		 * Unlink the transfer from the wait queue mi_wtab.
		 * If there are no more transfers on the drive queue
		 * for this drive, and it is a profiled disk, turn
		 * off its busy bit.
		 */
		bp->av_back->av_forw = bp->av_forw;
		bp->av_forw->av_back = bp->av_back;
		if (ui->ui_dk >= 0 && md->md_utab[ui->ui_unit].b_forw == NULL)
			dk_busy &= ~(1 << ui->ui_dk);

		/*
		 * If the transfer has something to do with bad
		 * block forwarding, let the driver handle the
		 * rest.
		 */
		if ((bp->b_flags & B_BAD) != 0 && md->md_bb != NULL) {
			(*md->md_bb)(ui, mp, bp);
			goto out;
		}

		/*
		 * If the transfer failed, give the driver a crack
		 * at fixing things up.
		 */
		if (st != M_ST_SUCCESS) {
			switch ((*md->md_ioerr)(ui, mp, bp)) {

			case MSCP_DONE:		/* fixed */
				break;

			case MSCP_RESTARTED:	/* still working on it */
				goto out;

			case MSCP_FAILED:	/* no luck */
				harderr(bp, drivename);
				mscp_printevent(mp);
				bp->b_flags |= B_ERROR;
				bp->b_error = EIO;
				break;
			}
		}

		/*
		 * Set the residual count and mark the transfer as
		 * done.  If the I/O wait queue is now empty, release
		 * the shared BDP, if any.
		 */
		bp->b_resid = bp->b_bcount - mp->mscp_seq.seq_bytecount;
		biodone(bp);
		if (um->um_bdp && mi->mi_wtab.av_forw == &mi->mi_wtab)
			ubarelse(um->um_ubanum, &um->um_bdp);
out:
		break;
		
	case M_OP_REPLACE | M_OP_END:
		/*
		 * A replace operation finished.  Just let the driver
		 * handle it (if it does replaces).
		 */
		if (md->md_replace == NULL)
			printf("%s%d: bogus REPLACE end\n",
				drivename, ui->ui_unit);
		else
			(*md->md_replace)(ui, mp);
		break;

	default:
		/*
		 * If it is not one of the above, we cannot handle it.
		 * (And we should not have received it, for that matter.)
		 */
unknown:
		printf("%s%d: unknown opcode 0x%x status 0x%x ignored\n",
			drivename, ui->ui_unit, mp->mscp_opcode,
			mp->mscp_status);
		mscp_hexdump(mp);
		break;
	}

	/*
	 * If the drive needs to be put back in the controller queue,
	 * do that now.  (`bp' below ought to be `dp', but they are all
	 * struct buf *.)  Note that b_active was cleared in the driver;
	* we presume that there is something to be done, hence reassert it.
	 */
	if (ui->ui_flags & UNIT_REQUEUE) {
		bp = &md->md_utab[ui->ui_unit];
if (bp->b_active) panic("mscp_dorsp requeue");
		APPEND(bp, &um->um_tab, b_forw);
		bp->b_active = 1;
		ui->ui_flags &= ~UNIT_REQUEUE;
	}

done:
	/*
	 * Give back the response packet, and take a look at the next.
	 */
	mp->mscp_msglen = MSCP_MSGLEN;
	mi->mi_rsp.mri_desc[nextrsp] |= MSCP_OWN;
	nextrsp = (nextrsp + 1) % mi->mi_rsp.mri_size;
	goto loop;
}

X/*
 * Dump the entire contents of an MSCP packet in hex.  Mainly useful
 * for debugging....
 */
mscp_hexdump(mp)
	register struct mscp *mp;
{
	register long *p = (long *) mp;
	register int i = mp->mscp_msglen;

	if (i > 256)		/* sanity */
		i = 256;
	i /= sizeof (*p);	/* ASSUMES MULTIPLE OF sizeof(long) */
	while (--i >= 0)
		printf("0x%x ", *p++);
	printf("\n");
}

X/*
 * Requeue outstanding transfers, e.g., after Unibus reset.
 * Also requeue any drives that have on line or unit status
 * info pending.
 */
mscp_requeue(mi)
	struct mscp_info *mi;
{
	register struct uba_ctlr *um = mi->mi_um;
	register struct uba_device *ui;
	register struct mscp_driver *md = mi->mi_md;
	register struct buf *bp, *dp;
	register int unit;
	struct buf *nextbp;

	/*
	 * Clear the controller chain.  Mark everything un-busy; we
	 * will soon fix any that are in fact busy.
	 */
	um->um_tab.b_actf = NULL;
	um->um_tab.b_active = 0;
	for (unit = 0, dp = md->md_utab; unit < md->md_nunits; unit++, dp++) {
		dp->b_forw = NULL;
		dp->b_active = 0;
	}

	/*
	 * Scan the wait queue, linking buffers onto drive queues.
	 * Note that these must be put at the front of the drive queue,
	 * lest we reorder I/O operations.
	 */
	for (bp = mi->mi_wtab.av_back; bp != &mi->mi_wtab; bp = nextbp) {
		nextbp = bp->av_back;
		dp = &md->md_utab[minor(bp->b_dev) >> md->md_unitshift];
		bp->av_forw = dp->b_actf;
		if (dp->b_actf == NULL)
			dp->b_actl = bp;
		dp->b_actf = bp;
	}
	mi->mi_wtab.av_forw = mi->mi_wtab.av_back = &mi->mi_wtab;

	/*
	 * Scan for drives waiting for on line or status responses,
	 * and for drives with pending transfers.  Put these on the
	 * controller queue, and mark the controller busy.
	 */
	for (unit = 0, dp = md->md_utab; unit < md->md_nunits; unit++, dp++) {
		if ((ui = um->um_driver->ud_dinfo[unit]) == NULL ||
		    ui->ui_mi != um || ui->ui_alive == 0)
			continue;
		/* does this next line belong here?? */
		ui->ui_flags &= ~(UNIT_HAVESTATUS | UNIT_ONLINE);
		if ((ui->ui_flags & UNIT_REQUEUE) == 0 && dp->b_actf == NULL)
			continue;
		ui->ui_flags &= ~UNIT_REQUEUE;
		APPEND(dp, &um->um_tab, b_forw);
		dp->b_active = 1;
		um->um_tab.b_active = 1;
	}

#ifdef AVOID_EMULEX_BUG
	/*
	 * ... and clear the index-to-buffer table.
	 */
	for (unit = 0; unit < AEB_MAX_BP; unit++)
		mi->mi_bp[unit] = 0;
#endif
}


X/*
 * MSCP error reporting
 */

X/*
 * Messages for the various subcodes.
 */
static char unknown_msg[] = "unknown subcode";

X/*
 * Subcodes for Success (0)
 */
static char *succ_msgs[] = {
	"normal",		/* 0 */
	"spin down ignored",	/* 1 = Spin-Down Ignored */
	"still connected",	/* 2 = Still Connected */
	unknown_msg,
	"dup. unit #",		/* 4 = Duplicate Unit Number */
	unknown_msg,
	unknown_msg,
	unknown_msg,
	"already online",	/* 8 = Already Online */
	unknown_msg,
	unknown_msg,
	unknown_msg,
	unknown_msg,
	unknown_msg,
	unknown_msg,
	unknown_msg,
	"still online",		/* 16 = Still Online */
};

X/*
 * Subcodes for Invalid Command (1)
 */
static char *icmd_msgs[] = {
	"invalid msg length",	/* 0 = Invalid Message Length */
};

X/*
 * Subcodes for Command Aborted (2)
 */
X/* none known */

X/*
 * Subcodes for Unit Offline (3)
 */
static char *offl_msgs[] = {
	"unknown drive",	/* 0 = Unknown, or online to other ctlr */
	"not mounted",		/* 1 = Unmounted, or RUN/STOP at STOP */
	"inoperative",		/* 2 = Unit Inoperative */
	unknown_msg,
	"duplicate",		/* 4 = Duplicate Unit Number */
	unknown_msg,
	unknown_msg,
	unknown_msg,
	"in diagnosis",		/* 8 = Disabled by FS or diagnostic */
};

X/*
 * Subcodes for Unit Available (4)
 */
X/* none known */

X/*
 * Subcodes for Media Format Error (5)
 */
static char *media_fmt_msgs[] = {
	"fct unread - edc",	/* 0 = FCT unreadable */
	"invalid sector header",/* 1 = Invalid Sector Header */
	"not 512 sectors",	/* 2 = Not 512 Byte Sectors */
	"not formatted",	/* 3 = Not Formatted */
	"fct ecc",		/* 4 = FCT ECC */
};

X/*
 * Subcodes for Write Protected (6)
 * N.B.:  Code 6 subcodes are 7 bits higher than other subcodes
 * (i.e., bits 12-15).
 */
static char *wrprot_msgs[] = {
	unknown_msg,
	"software",		/* 1 = Software Write Protect */
	"hardware",		/* 2 = Hardware Write Protect */
};

X/*
 * Subcodes for Compare Error (7)
 */
X/* none known */

X/*
 * Subcodes for Data Error (8)
 */
static char *data_msgs[] = {
	"forced error",		/* 0 = Forced Error (software) */
	unknown_msg,
	"header compare",	/* 2 = Header Compare Error */
	"sync timeout",		/* 3 = Sync Timeout Error */
	unknown_msg,
	unknown_msg,
	unknown_msg,
	"uncorrectable ecc",	/* 7 = Uncorrectable ECC */
	"1 symbol ecc",		/* 8 = 1 bit ECC */
	"2 symbol ecc",		/* 9 = 2 bit ECC */
	"3 symbol ecc",		/* 10 = 3 bit ECC */
	"4 symbol ecc",		/* 11 = 4 bit ECC */
	"5 symbol ecc",		/* 12 = 5 bit ECC */
	"6 symbol ecc",		/* 13 = 6 bit ECC */
	"7 symbol ecc",		/* 14 = 7 bit ECC */
	"8 symbol ecc",		/* 15 = 8 bit ECC */
};

X/*
 * Subcodes for Host Buffer Access Error (9)
 */
static char *host_buffer_msgs[] = {
	unknown_msg,
	"odd xfer addr",	/* 1 = Odd Transfer Address */
	"odd xfer count",	/* 2 = Odd Transfer Count */
	"non-exist. memory",	/* 3 = Non-Existent Memory */
	"memory parity",	/* 4 = Memory Parity Error */
};

X/*
 * Subcodes for Controller Error (10)
 */
static char *cntlr_msgs[] = {
	unknown_msg,
	"serdes overrun",	/* 1 = Serialiser/Deserialiser Overrun */
	"edc",			/* 2 = Error Detection Code? */
	"inconsistant internal data struct",/* 3 = Internal Error */
};

X/*
 * Subcodes for Drive Error (11)
 */
static char *drive_msgs[] = {
	unknown_msg,
	"sdi command timeout",	/* 1 = SDI Command Timeout */
	"ctlr detected protocol",/* 2 = Controller Detected Protocol Error */
	"positioner",		/* 3 = Positioner Error */
	"lost rd/wr ready",	/* 4 = Lost R/W Ready Error */
	"drive clock dropout",	/* 5 = Lost Drive Clock */
	"lost recvr ready",	/* 6 = Lost Receiver Ready */
	"drive detected error",	/* 7 = Drive Error */
	"ctlr detected pulse or parity",/* 8 = Pulse or Parity Error */
};

X/*
 * The following table correlates message codes with the
 * decoding strings.
 */
struct code_decode {
	char	*cdc_msg;
	int	cdc_nsubcodes;
	char	**cdc_submsgs;
} code_decode[] = {
#define	SC(m)	sizeof (m) / sizeof (m[0]), m
	"success",			SC(succ_msgs),
	"invalid command",		SC(icmd_msgs),
	"command aborted",		0, 0,
	"unit offline",		SC(offl_msgs),
	"unit available",		0, 0,
	"media format error",		SC(media_fmt_msgs),
	"write protected",		SC(wrprot_msgs),
	"compare error",		0, 0,
	"data error",			SC(data_msgs),
	"host buffer access error",	SC(host_buffer_msgs),
	"controller error",		SC(cntlr_msgs),
	"drive error",			SC(drive_msgs),
#undef SC
};

X/*
 * Print the decoded error event from an MSCP error datagram.
 */
mscp_printevent(mp)
	struct mscp *mp;
{
	register int event = mp->mscp_event;
	register struct code_decode *cdc;
	int c, sc;
	char *cm, *scm;

	/*
	 * The code is the lower six bits of the event number (aka
	 * status).  If that is 6 (write protect), the subcode is in
	 * bits 12-15; otherwise, it is in bits 5-11.
	 * I WONDER WHAT THE OTHER BITS ARE FOR.  IT SURE WOULD BE
	 * NICE IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS.
	 */
	c = event & M_ST_MASK;
	sc = (c != 6 ? event >> 5 : event >> 12) & 0x7ff;
	if (c >= sizeof code_decode / sizeof code_decode[0])
		cm = "- unknown code", scm = "??";
	else {
		cdc = &code_decode[c];
		cm = cdc->cdc_msg;
		if (sc >= cdc->cdc_nsubcodes)
			scm = unknown_msg;
		else
			scm = cdc->cdc_submsgs[sc];
	}
	printf("%s (%s) (code %d, subcode %d)\n", cm, scm, c, sc);
}

X/*
 * Print the code and logical block number for an error packet.
 * THIS IS PROBABLY PECULIAR TO DISK DRIVES.  IT SURE WOULD BE
 * NICE IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS.
 */
mscp_decodeerror(um, mp)
	register struct uba_ctlr *um;
	register struct mscp *mp;
{
	/*
	 * For bad blocks, mp->mscp_erd.erd_hdr identifies a code and
	 * the logical block number.  Code 0 is a regular block; code 6
	 * is a replacement block.  The remaining codes are currently
	 * undefined.  The code is in the upper four bits of the header
	 * (bits 0-27 are the lbn).
	 */
	int issoft = mp->mscp_flags & (M_LF_SUCC | M_LF_CONT);
	static char *codemsg[16] = {
		"lbn", "code 1", "code 2", "code 3",
		"code 4", "code 5", "rbn", "code 7",
		"code 8", "code 9", "code 10", "code 11",
		"code 12", "code 13", "code 14", "code 15"
	};
#define BADCODE(h)	(codemsg[(unsigned)(h) >> 28])
#define BADLBN(h)	((h) & 0xfffffff)

	printf("%s%d: %s error datagram%s: ", um->um_driver->ud_mname,
		um->um_ctlr, issoft ? "soft" : "hard",
		mp->mscp_flags & M_LF_CONT ? " (continuing)" : "");
	switch (mp->mscp_format & 0377) {

	case M_FM_CTLRERR:	/* controller error */
		break;

	case M_FM_BUSADDR:	/* host memory access error */
		printf("memory addr 0x%x: ", mp->mscp_erd.erd_busaddr);
		break;

	case M_FM_DISKTRN:
		printf("unit %d: level %d retry %d, %s %d: ",
			mp->mscp_unit,
			mp->mscp_erd.erd_level, mp->mscp_erd.erd_retry,
			BADCODE(mp->mscp_erd.erd_hdr),
			BADLBN(mp->mscp_erd.erd_hdr));
		break;

	case M_FM_SDI:
		printf("unit %d: %s %d: ", mp->mscp_unit,
			BADCODE(mp->mscp_erd.erd_hdr),
			BADLBN(mp->mscp_erd.erd_hdr));
		break;

	case M_FM_SMLDSK:
		printf("unit %d: small disk error, cyl %d: ",
			mp->mscp_unit, mp->mscp_erd.erd_sdecyl);
		break;

	default:
		printf("unit %d: unknown error, format 0x%x: ",
			mp->mscp_unit, mp->mscp_format);
	}
	mscp_printevent(mp);
#undef BADCODE
#undef BADLBN
}
//go.sysin dd *
if [ `wc -c < vax/mscp.c` != 23071 ]; then
	made=FALSE
	echo error transmitting vax/mscp.c --
	echo length should be 23071, not `wc -c < vax/mscp.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vax/mscp.c
	echo -n '	'; ls -ld vax/mscp.c
fi
echo Extracting vax/mscp.h
sed 's/^X//' <<'//go.sysin dd *' >vax/mscp.h
X/*	mscp.h	6.1	83/07/29	*/

X/*
 * Definitions for the Mass Storage Control Protocol
 * I WISH I KNEW WHAT MORE OF THESE WERE.  IT SURE WOULD BE NICE
 * IF DEC SOLD DOCUMENTATION FOR THEIR OWN CONTROLLERS.
 */

X/*
 * Control message opcodes
 */
#define	M_OP_ABORT	0x01	/* Abort command */
#define	M_OP_GETCMDST	0x02	/* Get command status command */
#define	M_OP_GETUNITST	0x03	/* Get unit status command */
#define	M_OP_SETCTLRC	0x04	/* Set controller characteristics command */
#define	M_OP_SEREX	0x07	/* Serious exception end message */
#define	M_OP_AVAILABLE	0x08	/* Available command */
#define	M_OP_ONLINE	0x09	/* Online command */
#define	M_OP_SETUNITC	0x0a	/* Set unit characteristics command */
#define	M_OP_DTACCPATH	0x0b	/* Determine access paths command */
#define	M_OP_ACCESS	0x10	/* Access command */
#define	M_OP_COMPCD	0x11	/* Compare controller data command */
#define	M_OP_ERASE	0x12	/* Erase command */
#define	M_OP_FLUSH	0x13	/* Flush command */
#define	M_OP_REPLACE	0x14	/* Replace command */
#define	M_OP_COMPHD	0x20	/* Compare host data command */
#define	M_OP_READ	0x21	/* Read command */
#define	M_OP_WRITE	0x22	/* Write command */
#define	M_OP_AVAILATTN	0x40	/* Available attention message */
#define	M_OP_DUPUNIT	0x41	/* Duplicate unit number attention message */
#define	M_OP_ACCPATH	0x42	/* Access path attention message */
#define	M_OP_END	0x80	/* End message flag */


X/*
 * Generic command modifiers
 */
#define	M_MD_EXPRS	0x8000	/* Express request */
#define	M_MD_COMP	0x4000	/* Compare */
#define	M_MD_CLSEX	0x2000	/* Clear serious exception */
#define	M_MD_ERROR	0x1000	/* Force error */
#define	M_MD_SCCHH	0x0800	/* Suppress caching (high speed) */
#define	M_MD_SCCHL	0x0400	/* Suppress caching (low speed) */
#define	M_MD_SECOR	0x0200	/* Suppress error correction */
#define	M_MD_SEREC	0x0100	/* Suppress error recovery */
#define	M_MD_SSHDW	0x0080	/* Suppress shadowing */
#define	M_MD_WBKNV	0x0040	/* Write back (non-volatile) */
#define	M_MD_WBKVL	0x0020	/* Write back (volatile) */
#define	M_MD_WRSEQ	0x0010	/* Write shadow set one unit at a time */

X/*
 * AVAILABLE command modifiers
 */
#define	M_AVM_ALLCD	0x0002	/* All class drivers */
#define	M_AVM_SPINDOWN	0x0001	/* Spin down */

X/*
 * FLUSH command modifiers
 */
#define	M_FLM_FLUSHENU	0x0001	/* Flush entire unit */
#define	M_FLM_VOLATILE	0x0002	/* Volatile only */

X/*
 * GET UNIT STATUS command modifiers
 */
#define	M_GUM_NEXTUNIT	0x0001	/* Next unit */

X/*
 * ONLINE command modifiers
 */
#define	M_OLM_RIP	0x0001	/* Allow self destruction */
#define	M_OLM_IGNMF	0x0002	/* Ignore media format error */

X/*
 * ONLINE and SET UNIT CHARACTERISTICS command modifiers
 */
#define	M_OSM_ALTERHI	0x0020	/* Alter host identifier */
#define	M_OSM_SHADOWSP	0x0010	/* Shadow unit specified */
#define	M_OSM_CLEARWBL	0x0008	/* Clear write-back data lost */
#define	M_OSM_SETWRPROT	0x0004	/* Set write protect */

X/*
 * REPLACE command modifiers
 */
#define	M_RPM_PRIMARY	0x0001	/* Primary replacement block */

X/*
 * End message flags
 */
#define	M_EF_BBLKR	0x80	/* Bad block reported */
#define	M_EF_BBLKU	0x40	/* Bad block unreported */
#define	M_EF_ERLOG	0x20	/* Error log generated */
#define	M_EF_SEREX	0x10	/* Serious exception */

X/*
 * Controller flags
 */
#define	M_CF_ATTN	0x80	/* Enable attention messages */
#define	M_CF_MISC	0x40	/* Enable miscellaneous error log messages */
#define	M_CF_OTHER	0x20	/* Enable other host's error log messages */
#define	M_CF_THIS	0x10	/* Enable this host's error log messages */
#define	M_CF_MLTHS	0x04	/* Multi-host */
#define	M_CF_SHADW	0x02	/* Shadowing */
#define	M_CF_576	0x01	/* 576 byte sectors */

X/*
 * Unit flags
 */
#define	M_UF_REPLC	0x8000	/* Controller initiated bad block replacement */
#define	M_UF_INACT	0x4000	/* Inactive shadow set unit */
#define	M_UF_WRTPH	0x2000	/* Write protect (hardware) */
#define	M_UF_WRTPS	0x1000	/* Write protect (software or volume) */
#define	M_UF_SCCHH	0x8000	/* Suppress caching (high speed) */
#define	M_UF_SCCHL	0x4000	/* Suppress caching (low speed) */
#define	M_UF_RMVBL	0x0080	/* Removable media */
#define	M_UF_WBKNV	0x0040	/* Write back (non-volatile) */
#define	M_UF_576	0x0004	/* 576 byte sectors */
#define	M_UF_CMPWR	0x0002	/* Compare writes */
#define	M_UF_CMPRD	0x0001	/* Compare reads */

X/*
 * Error Log message format codes
 */
#define	M_FM_CTLRERR	0x00	/* Controller error */
#define	M_FM_BUSADDR	0x01	/* Host memory access error */
#define	M_FM_DISKTRN	0x02	/* Disk transfer error */
#define	M_FM_SDI	0x03	/* SDI error */
#define	M_FM_SMLDSK	0x04	/* Small disk error */

X/*
 * Error Log message flags
 */
#define	M_LF_SUCC	0x80	/* Operation successful */
#define	M_LF_CONT	0x40	/* Operation continuing */
#define	M_LF_SQNRS	0x01	/* Sequence number reset */

X/*
 * Status codes
 */
#define	M_ST_MASK	0x1f	/* Status code mask */
#define	M_ST_SUCCESS	0x00	/* Success */
#define	M_ST_INVALCMD	0x01	/* Invalid command */
#define	M_ST_ABORTED	0x02	/* Command aborted */
#define	M_ST_OFFLINE	0x03	/* Unit offline */
#define	M_ST_AVAILABLE	0x04	/* Unit available */
#define	M_ST_MFMTERR	0x05	/* Media format error */
#define	M_ST_WRPROT	0x06	/* Write protected */
#define	M_ST_COMPERR	0x07	/* Compare error */
#define	M_ST_DATAERR	0x08	/* Data error */
#define	M_ST_HOSTBUFERR	0x09	/* Host buffer access error */
#define	M_ST_CTLRERR	0x0a	/* Controller error */
#define	M_ST_DRIVEERR	0x0b	/* Drive error */
#define	M_ST_DIAG	0x1f	/* Message from an internal diagnostic */

X/*
 * Subcodes of M_ST_OFFLINE
 */
#define	M_OFFLINE_UNKNOWN	(0 << 5) /* unknown or on other ctlr */
#define	M_OFFLINE_UNMOUNTED	(1 << 5) /* unmounted or RUN/STOP at STOP */
#define	M_OFFLINE_INOPERATIVE	(2 << 5) /* inoperative? */
#define	M_OFFLINE_DUPLICATE	(4 << 5) /* duplicate unit number */
#define	M_OFFLINE_INDIAGNOSTIC	(8 << 5) /* disabled by FS or diagnostic */

X/*
 * An MSCP packet begins with a header giving the length of
 * the entire packet (including the header itself)(?), two bytes
 * of device specific data, and the a whole bunch of variants
 * depending on message type.
 *
 * N.B.:  In most cases we distinguish between a `command' and
 * an `end' variant as well.  The command variant is that which
 * is given to the controller; the `end' variant is its response.
 */

X/*
 * Generic sequential message variant (command and response).
 */
struct mscpv_seq {
	long	seq_bytecount;		/* byte count */
#define	seq_rbn		seq_bytecount	/* aka RBN (replace) */
#define	seq_outref	seq_bytecount	/* aka outref (abort/get cmd status) */
	long	seq_buffer;		/* buffer descriptor */
	long	seq_xxx1[2];	/* ? */	/* unused */
	long	seq_lbn;		/* logical block number */
	long	seq_xxx2;	/* ? */	/* unused */
	long	*seq_addr;		/* pointer to cmd descriptor */
	long	seq_software[4];	/* reserved to software; unused */
};

X/*
 * Set Controller Characteristics command variant
 */
struct mscpv_sccc {
	u_short	sccc_version;		/* MSCP version number */
	u_short	sccc_ctlrflags;		/* controller flags */
	u_short	sccc_hosttimo;		/* host timeout */
	u_short	sccc_usefrac;		/* use fraction */
	long	sccc_time;		/* time and date */
	long	sccc_xxx1;	/* ? */
	long	sccc_errlgfl;	/* ? */
	short	sccc_xxx2;	/* ? */
	short	sccc_copyspd;	/* ? */
	long	*sccc_addr;		/* pointer to cmd descriptor */
};

X/*
 * Set Controller Characteristics end variant
 */
struct mscpv_scce {
	u_short	scce_version;		/* MSCP version number */
	u_short	scce_ctlrflags;		/* controller flags */
	u_short	scce_ctlrtimo;		/* controller timeout */
	u_short	scce_ctlrcmdl;		/* ??? */
	quad	scce_ctlrid;		/* controller ID */
	long	scce_xxx[3];	/* ? */
	long	scce_volser;		/* volume serial number */
};

X/*
 * On Line command variant
 */
struct mscpv_onlc {
	long	onlc_xxx1[4];	/* ? */
	long	onlc_errlgfl;		/* error log flag? */
	short	onlc_xxx2;	/* ? */
	short	onlc_copyspd;		/* copy speed? */
	long	*onlc_addr;		/* pointer to cmd descriptor */
};

X/*
 * On Line end variant
 */
struct mscpv_onle {
	long	onle_xxx1[3];	/* ? */
X/*???*/	short	onle_xxx2;	/* ? */
	u_char	onle_drivetype;		/* drive type index (same in guse) */
	char	onle_xxx3;	/* ? */
	long	onle_mediaid;		/* media type id (same in guse) */
	long	onle_xxx4;	/* ? */
	long	onle_unitsize;		/* unit size in sectors */
	long	onle_volser;		/* volume serial number */
};

X/*
 * Get Unit Status end variant (and Avail Attn?)
 */
struct mscpv_guse {
	u_short	guse_multunit;		/* multi-unit code */
	u_short	guse_unitflags;		/* unit flags */
	long	guse_hostid;		/* host id */
	long	guse_unitid0;	/*???*/
	short	guse_unitid1;	/*???*/
	u_char	guse_drivetype;		/* drive type index */
	u_char	guse_unitid2;	/*???*/
	long	guse_mediaid;		/* media type id (encoded) */
	short	guse_shadowunit;	/* shadow unit */
	short	guse_shadowstat;	/* shadow status */
	u_short	guse_nspt;		/* sectors per track */
	u_short	guse_group;		/* group size (?) */
	u_short	guse_ntpc;		/* tracks per cylinder */
	u_short	guse_xxx;		/* reserved */
	u_short	guse_rctsize;		/* RCT size (sectors) */
	u_char	guse_nrpt;		/* RBNs per track */
	u_char	guse_nrct;		/* number of RCTs */
};

X/*
 * Error datagram variant.
 */
struct mscpv_erd {
	quad	erd_ctlrid;		/* controller ID */
	u_char	erd_ctlrsoftware;	/* controller software version */
	u_char	erd_ctlrhardware;	/* controller hardware version */
	u_short	erd_multiunit;		/* multi-unit code (?) */
	union {
		u_long	un_busaddr;	/* bus address, if mem access err */
		quad	un_unitid;	/* unit id, otherwise */
	} erd_un1;
#define	erd_busaddr	erd_un1.un_busaddr
#define	erd_unitid	erd_un1.un_unitid
	u_char	erd_unitsoftware;	/* unit software version */
	u_char	erd_unithardware;	/* unit hardware version */
	union {
		u_char	un_b[2];	/* level, retry (if disk xfer err) */
		u_short	un_s;		/* cylinder (if small disk error) */
	} erd_un2;
#define	erd_level	erd_un2.un_b[0]
#define	erd_retry	erd_un2.un_b[1]
#define	erd_sdecyl	erd_un2.un_s
	long	erd_volser;		/* volume serial number */
	u_long	erd_hdr;		/* `header' (block number) */
	char	erd_sdistat[12];	/* SDI status information (?) */
};

X/*
 * I am making brash assumptions about the first four bytes of all
 * MSCP packets.  These appear to be true for both UDA50s and TMSCP
 * devices (TU81, TA81, TK50).  DEC claim that these four bytes are
 * not part of MSCP itself, yet at least the length is necessary
 * for, e.g., error checking.
 */
struct mscp {
	u_short	mscp_msglen;		/* length in bytes */
	u_char	mscp_msgtc;		/* type (high 4 bits) and credits */
	u_char	mscp_vcid;		/* virtual circuit ID */
	long	mscp_cmdref;		/* command reference number */
	u_short	mscp_unit;		/* unit number */
	u_short	mscp_seqnum;		/* sequence number */
	u_char	mscp_opcode;		/* opcode */
#define	mscp_format	mscp_opcode	/* aka format (datagrams) */
	u_char	mscp_flags;		/* flags */
	u_short	mscp_modifier;		/* modifier (commands) */
#define	mscp_status	mscp_modifier	/* aka status (ends) */
#define	mscp_event	mscp_modifier	/* aka event (datagrams) */
	union {
		struct	mscpv_seq un_seq;	/* generic sequential msg */
		struct	mscpv_sccc un_sccc;	/* SCC command */
		struct	mscpv_scce un_scce;	/* SCC end */
		struct	mscpv_onlc un_onlc;	/* on line command */
		struct	mscpv_onle un_onle;	/* on line end */
		struct	mscpv_guse un_guse;	/* get unit status */
		struct	mscpv_erd un_erd;	/* error datagram */
	} mscp_un;
X/*???*/	long	mscp_xxx;		/* pad to 64 bytes */
};

X/*
 * Define message length according to the DEC specifications by dropping
 * the four byte header.
 */
#define	MSCP_MSGLEN	(sizeof (struct mscp) - 4)

X/*
 * Shorthand
 */

X/*
 * Generic packet
 */
#define	mscp_seq	mscp_un.un_seq

X/*
 * Set Controller Characteristics packet
 */
#define	mscp_sccc	mscp_un.un_sccc

X/*
 * Set Controller Characteristics end packet
 */
#define	mscp_scce	mscp_un.un_scce

X/*
 * Online / Set Unit Characteristics command packet
 */
#define	mscp_onlc	mscp_un.un_onlc

X/*
 * Online end packet
 */
#define	mscp_onle	mscp_un.un_onle

X/*
 * Get Unit Status end packet
 */
#define	mscp_guse	mscp_un.un_guse

X/*
 * MSCP Error Log packet
 */
#define	mscp_erd	mscp_un.un_erd


X/*
 * Macros to break up mscp_msgtc, and types.
 */
#define	MSCP_MSGTYPE(m)	((m) & 0xf0)
#define	MSCP_CREDITS(m)	((m) & 0x0f)

#define	MSCPT_SEQ		0x00	/* sequential message */
#define	MSCPT_DATAGRAM		0x10	/* error datagram */
#define	MSCPT_CREDITS		0x20	/* credit notification */
#define	MSCPT_MAINTENANCE	0xf0	/* who knows */


X/*
 * Here begin more perhaps brash assumptions about MSCP devices...
 */

X/*
 * MSCP controllers have `command rings' and `response rings'.  A
 * command ring is a pool of MSCP packets that the host uses to give
 * commands to the controller; a response ring is a pool of MSCP
 * packets that the controller uses to give back responses.  Entries
 * in the command and response rings are `owned' by either the host
 * or the controller; only the owner is allowed to alter any of the
 * fields in the MSCP packet.  Thus, free command packets are owned
 * by the host, and free response packets by the controller.  When
 * the host gives a packet to the controller, it tells the controller
 * by touching a device register; when the controller gives a response
 * to the host, it generates an interrupt if enabled, and sets
 * a device register as well.
 *
 * The pool is `described' by a set of pointers to the packets, along
 * with the two flags below.
 */
#define	MSCP_OWN	0x80000000	/* controller owns this packet */
#define	MSCP_INT	0x40000000	/* controller should interrupt */
//go.sysin dd *
if [ `wc -c < vax/mscp.h` != 13411 ]; then
	made=FALSE
	echo error transmitting vax/mscp.h --
	echo length should be 13411, not `wc -c < vax/mscp.h`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vax/mscp.h
	echo -n '	'; ls -ld vax/mscp.h
fi
echo Extracting vax/mscpvar.h
sed 's/^X//' <<'//go.sysin dd *' >vax/mscpvar.h
X/*
 * MSCP generic driver configuration
 */

X/*
 * Enabling MSCP_PARANOIA makes the response code perform various checks
 * on the hardware.  (Right now it verifies only the buffer pointer in
 * mscp_cmdref.)
 *
 * Enabling AVOID_EMULEX_BUG selects an alternative method of identifying
 * transfers in progress, which gets around a rather peculiar bug in the
 * SC41/MS.  Enabling MSCP_PARANOIA instead should work, but will cause
 * `extra' Unibus resets.
 *
 * Either of these flags can simply be included as an `options' line in
 * your configuration file.
 */

X/* #define MSCP_PARANOIA */
X/* #define AVOID_EMULEX_BUG */

X/*
 * Per driver information.
 *
 * md_ndpc sets the maximum unit number allowed in response packets.
 * md_nunits is the number of drives attached to all controllers.
 * md_unitshift is the divisor for converting a minor device number
 * to a unit index for the device queues in md_utab.
 *
 * The routines are called from the generic response dispatcher.
 * The first three (dgram, ctlrdone, and unconf) get passed a pointer
 * to the uba_ctlr and to the packet; the rest get a pointer to the
 * uba_device and to the packet (`um, mp' and `ui, mp' respectively).
 * The routines unconf, online, gotstatus, and ioerr are functions
 * and should return one of the values given below.  In addition,
 * the ioerr and bb routines get a third argument, `bp': a pointer
 * to the buffer describing the transfer in error.
 */
struct mscp_driver {
	int	md_ndpc;		/* number of drives per ctlr */
	int	md_nunits;		/* total number drives (all ctlrs) */
	int	md_unitshift;		/* device number to unit: >> count */
	struct	buf *md_utab;		/* pointer to device queues */
	int	(*md_dgram)();		/* error datagram */
	int	(*md_ctlrdone)();	/* controller operation complete */
	int	(*md_unconf)();		/* response from unconfigured drive */
	int	(*md_online)();		/* drive on line */
	int	(*md_gotstatus)();	/* got unit status */
	int	(*md_replace)();	/* replace done */
	int	(*md_ioerr)();		/* read or write failed */
	int	(*md_bb)();		/* B_BAD io done */
};

X/*
 * Return values from functions.
 * MSCP_RESTARTED is peculiar to I/O errors.
 */
#define	MSCP_DONE	0		/* all ok */
#define	MSCP_FAILED	1		/* no go */
#define	MSCP_RESTARTED	2		/* transfer restarted */

X/*
 * Ring information, per ring (one each for commands and responses).
 */
struct mscp_ri {
	int	mri_size;		/* ring size */
	int	mri_next;		/* next (expected|free) */
	long	*mri_desc;		/* base address of descriptors */
	struct	mscp *mri_ring;		/* base address of packets */
};

X/*
 * Per device information.
 *
 * mi_ip is a pointer to the inverting pointers (things that get `ui's
 * given unit numbers) FOR THIS CONTROLLER (NOT the whole set!).
 *
 * mi_wtab holds a queue of those transfers that were started but have
 * not yet finished.  Other Unibus drivers do not need this as they hand
 * out requests one at a time.  MSCP devices, however, take a slew of
 * requests and pick their own order to execute them.  This means that
 * we have to have a place to move transfers that were given to the
 * controller, so we can tell those apart from those that have not yet
 * been handed out; mi_wtab is that place.
 */
struct mscp_info {
	struct	mscp_driver *mi_md;	/* pointer to driver info */
	struct	uba_ctlr *mi_um;	/* pointer to ctlr */
	struct	uba_device **mi_ip;	/* pointer to inverting pointers */
	struct	mscp_ri mi_cmd;		/* MSCP command ring info */
	struct	mscp_ri mi_rsp;		/* MSCP response ring info */
	short	mi_credits;		/* transfer credits */
	char	mi_wantcmd;		/* waiting for command packet */
	char	mi_wantcredits;		/* waiting for transfer credits */
	struct	buf mi_wtab;		/* transfer wait queue */
#ifdef AVOID_EMULEX_BUG
#define	AEB_MAX_BP	32		/* max pend xfers (power of 2) XXX */
	struct	buf *mi_bp[AEB_MAX_BP];	/* xfer no. to buffer */
	u_int	mi_nextbp;		/* generates unique xfer no's */
	int	mi_ok;			/* for error rate statistics */
#endif AVOID_EMULEX_BUG
};

X/*
 * We have run out of credits when mi_credits is <= MSCP_MINCREDITS.
 * It is still possible to issue one command in this case, but it must
 * not be a data transfer.  E.g., `get command status' or `abort command'
 * is legal, while `read' is not.
 */
#define	MSCP_MINCREDITS	1

X/*
 * Flags for mscp_getcp().
 */
#define	MSCP_WAIT	1
#define	MSCP_DONTWAIT	0

struct	mscp *mscp_getcp();	/* get a command packet */

X/*
 * Unit flags
 */
#define	UNIT_ONLINE	0x01	/* drive is on line */
#define	UNIT_HAVESTATUS	0x02	/* got unit status */
#define	UNIT_REQUEUE	0x04	/* requeue after response */

X/*
 * Handle a command ring transition: wake up sleepers for command packets.
 * This is too simple to bother with a function call.
 */
#define	MSCP_DOCMD(mi) { \
	if ((mi)->mi_wantcmd) { \
		(mi)->mi_wantcmd = 0; \
		wakeup((caddr_t) &(mi)->mi_wantcmd); \
	} \
}

X/*
 * The following macro appends a buffer to a drive queue or a drive to
 * a controller queue, given the name of the forward link.  Use as
 * `APPEND(dp, &um->um_tab, b_forw)' or `APPEND(bp, dp, av_forw)',
 * where `bp' is a transfer request, `dp' is a drive queue, and `um_tab'
 * is a controller queue.  (That is, the forward link for controller
 * queues is `b_forw'; for drive queues, it is `av_forw'.)
 */
#define	APPEND(bp, queue, link) { \
	(bp)->link = NULL; \
	if ((queue)->b_actf == NULL) \
		(queue)->b_actf = (bp); \
	else \
		(queue)->b_actl->link = (bp); \
	(queue)->b_actl = (bp); \
}
//go.sysin dd *
if [ `wc -c < vax/mscpvar.h` != 5413 ]; then
	made=FALSE
	echo error transmitting vax/mscpvar.h --
	echo length should be 5413, not `wc -c < vax/mscpvar.h`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vax/mscpvar.h
	echo -n '	'; ls -ld vax/mscpvar.h
fi
made=TRUE
if [ $made = TRUE ]; then
	chmod 755 vax
	echo -n '	'; ls -ld vax
fi
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris@mimsy.umd.edu

chris@mimsy.UUCP (04/05/87)

: Run this shell script with "sh" not "csh"
PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
export PATH
all=FALSE
if [ x$1 = x-a ]; then
	all=TRUE
fi
echo Making directory man
mkdir man
echo Extracting man/uda.4
sed 's/^X//' <<'//go.sysin dd *' >man/uda.4
X.TH UDA 4 "University of Maryland"
X.UC 4
X.SH NAME
uda \- UDA50 disk controller interface
X.SH SYNOPSIS
X.B "controller uda0 at uba0 csr 0172150 vector udintr"
X.br
X.b "disk ra0 at uda0 drive 0"
X.SH DESCRIPTION
This is a driver for the DEC UDA50 disk controller and other
compatible controllers.  The UDA50 communicates with the host through
a packet protocol known as the Mass Storage Control Protocol (MSCP).
Consult the file
X.RI < vax/mscp.h >
for a detailed description of this protocol.
X.PP
XFiles with minor device numbers 0 through 7 refer to various portions
of drive 0; minor devices 8 through 15 refer to drive 1, etc.  The
standard device names begin with `ra' followed by the drive number
and then a letter a-h for partitions 0-7 respectively.
The character ? stands here for a drive number in the range 0-7.
X.PP
The block files access the disk via the system's normal buffering
mechanism mechanism and may be read and written without regard to
physical disk records.  There is also a `raw' interface which provides
for direct transmission between the disk and the user's read or write
buffer.  A single read or write call results in exactly one I/O
operation and therefore raw I/O is considerably more efficient when
many words are transmitted.  The names of the raw files conventionally
begin with an extra `r'.
X.PP
In raw I/O counts should be a multiple of 512 bytes (a disk sector).
Likewise
X.I seek
calls should specify a multiple of 512 bytes.
X.SH "DISK SUPPORT"
This driver configures the type of each drive when it is first
encountered.  A partition table in the driver is required for each type
of disk.  The origin and size (in sectors) of the pseudo-disks on each
drive are shown below.  Not all partitions begin on cylinder
boundaries, as on other drives, because previous drivers used one
partition table for all drive types.  Variants of the partition tables
are common; check the driver and the file
X.IR /etc/disktab ( disktab (5))
for other possibilities.
X.PP
X.nf
X.ta .5i +\w'000000    'u +\w'000000    'u +\w'000000    'u +\w'000000    'u
X.PP
RA60 partitions
	disk	start	length
	ra?a	0	15884
	ra?b	15884	33440
	ra?c	0	400176
	ra?d	49324	82080	same as 4.2BSD ra?g
	ra?e	131404	268772	same as 4.2BSD ra?h
	ra?f	49324	350852
	ra?g	242606	157570
	ra?h	49324	193282
X.PP
RA80 partitions
	disk	start	length
	ra?a	0	15884
	ra?b	15884	33440
	ra?c	0	242606
	ra?e	49324	193282	same as old Berkeley ra?g
	ra?f	49324	82080	same as 4.2BSD ra?g
	ra?g	49910	192696
	ra?h	131404	111202	same as 4.2BSD
X.PP
RA81 partitions
	disk	start	length
	ra?a	0	15884
	ra?b	16422	66880
	ra?c	0	891072
	ra?d	375564	15884
	ra?e	391986	307200
	ra?f	699720	191352
	ra?g	375564	515508
	ra?h	83538	291346
X.PP
RA81 partitions with 4.2BSD-compatible partitions
	disk	start	length
	ra?a	0	15884
	ra?b	16422	66880
	ra?c	0	891072
	ra?d	49324	82080	same as 4.2BSD ra?g
	ra?e	131404	759668	same as 4.2BSD ra?h
	ra?f	412490	478582	same as 4.2BSD ra?f
	ra?g	375564	515508
	ra?h	83538	291346
X.DT
X.fi
X.PP
The ra?a partition is normally used for the root file system, the ra?b
partition as a paging area, and the ra?c partition for pack-pack
copying (it maps the entire disk).
X.SH FILES
X/dev/ra[0-9][a-f]
X.br
X/dev/rra[0-9][a-f]
X.SH DIAGNOSTICS
X.TP
panic: udaslave
No command packets were available while the driver was looking
for disk drives.  The controller is not extending enough credits
to use the drives.
X.TP
uda%d: no response to Get Unit Status request
A disk drive was found, but did not respond to a status request.
This is either a hardware problem or someone pulling unit number
plugs very fast.
X.TP
uda%d: unit %d off line
While searching for drives, the controller found one that
seems to be manually disabled.  It is ignored.
X.TP
uda%d: unable to get unit status
Something went wrong while trying to determine the status of
a disk drive.  This is followed by an error detail.
X.TP
uda%d: unit %d, next %d
This probably never happens, but I wanted to know if it did.  I
have no idea what one should do about it.
X.TP
uda%d: cannot handle unit number %d (max is %d)
The controller found a drive whose unit number is too large.
Valid unit numbers are those in the range [0..7].
X.TP
uda%d: unit %d (media ID `%s') is of unknown type %d; ignored
The controller found a drive whose type is not known, and thus has
no partitioning.  The drive has been ignored.  You can add the type
to the udatypes[] table, now that you know what it is:  The media
ID will be something like `DU RA25'.
X.TP
uda%d: uballoc map failed
Unibus resource map allocation failed during initialisation.  This
can only happen if you have 496 devices on a Unibus.
X.TP
uda%d: timeout during init
The controller did not initialise within ten seconds.  A hardware
problem, but it sometimes goes away if you try again.
X.TP
uda%d: init failed, sa=%b
The controller refused to initalise.
X.TP
uda%d: controller hung
The controller never finished initialisation.  Retrying may sometimes
fix it.
X.TP
ra%d: drive will not come on line
The drive will not come on line, probably because it is spun down.
This should be preceded by a message giving details as to why the
drive stayed off line.
X.TP
uda%d: still hung
When the controller hangs, the driver occasionally tries to reinitialise
it.  This means it just tried, without success.
X.TP
panic: udastart: bp==NULL
A bug in the driver has put an empty drive queue on a controller queue.
X.TP
uda%d: command ring too small
If you increase NCMDL2, you may see a performance improvement.
(See /sys/vaxuba/uda.c.)
X.TP
panic: udastart
A drive was found marked for status or on-line functions while performing
status or on-line functions.  This indicates a bug in the driver.
X.TP
uda%d: controller error, sa=%b
The controller reported an error.  The driver will reset it and retry
pending I/O.
X.TP
uda%d: stray intr
The controller interrupted when it should have stayed quiet.  The
interrupt has been ignored.
X.TP
uda%d: init step %d failed, sa=%b
The controller reported an error during the named initialisation step.
The driver will retry initialisation later.
X.TP
uda%d: version %d model %d
An informational message giving the revision level of the controller.
X.TP
uda%d: DMA burst size set to %d
An informational message showing the DMA burst size, in words.
X.TP
panic: udaintr
Indicates a bug in the generic MSCP code.
X.TP
uda%d: driver bug, state %d
The driver has a bogus value for the controller state.  Something
is quite wrong.  This is immediately followed by a `panic: udastate'.
X.TP
uda%d: purge bdp %d
A benign message tracing BDP purges.  I have been trying to figure
out what BDP purges are for.  You might want to comment out this
call to log() in /sys/vaxuba/uda.c.
X.TP
X.RI "uda%d: SETCTLRC failed: " detail
The Set Controller Characteristics command (the last part of the
controller initialisation sequence) failed.  The
X.I detail
message tells why.
X.TP
X.RI "uda%d: attempt to bring ra%d on line failed: " detail
The drive could not be brought on line.  The
X.I detail
message tells why.
X.TP
uda%d: ra%d: unknown type %d
The type index of the named drive is not known to the driver, so the
drive will be ignored.
X.TP
ra%d: changed types! was %s
A drive somehow changed from one kind to another, e.g., from an RA80
to an RA60.  The driver believes the new type.
X.TP
ra%d: %s, size = %d sectors
The named drive is of the given type, and has that many sectors of
user-file area.  This is printed during configuration.
X.TP
X.RI "uda%d: attempt to get status for ra%d failed: " detail
A status request failed.  The
X.I detail
message should tell why.
X.TP
ra%d: unit %d, nspt %d, group %d, ntpc %d, rctsize %d,
X.br
X.ti -5
nrpt %d, nrct %d
X.br
Information about the geometry of the named drive.  This is not
used by the driver, but can one setting up
X.I disktab
entries, e.g.  Note that the sectors per track, group, and tracks per
cylinder values are those after bad blocking is accounted for, and will
differ slightly from the actual hardware setup.  This message also
reports the MSCP unit number for the drive.  Errors tend to include
only the MSCP unit number, rather than the drive number, since that
is all the driver can tell at the time.
X.TP
ra%d: bad block report: %d
The drive has reported the given block as bad.  If there are multiple
bad blocks, the drive will report only the first; in this case this
message will be followed by `+ others'.  Get DEC to forward the
block with EVRLK.
X.TP
ra%d: serious exception reported
I have no idea what this really means.
X.TP
panic: udareplace
The controller reported completion of a REPLACE operation.  The
driver never issues any REPLACEs, so something is wrong.
X.TP
panic: udabb
The controller reported completion of bad block related I/O.  The
driver never issues any such, so something is wrong.
X.TP
uda%d: lost interrupt
The controller has gone out to lunch, and is being reset to try to bring
it back.
X.TP
panic: mscp_go: AEB_MAX_BP too small
You defined AVOID_EMULEX_BUG and increased NCMDL2 and Emulex has
new firmware.  Raise AEB_MAX_BP or turn off AVOID_EMULEX_BUG.
X.TP
uda%d: unit %d: unknown message type 0x%x ignored
The controller responded with a mysterious message type. See
X/sys/vax/mscp.h for a list of known message types.  This is probably
a controller hardware problem.
X.TP
uda%d: unit %d out of range
The disk drive unit number (the unit plug) is higher than the
maximum number the driver allows (currently 7).
X.TP
uda%d: unit %d not configured, \fImessage\fP ignored
The named disk drive has announced its presence to the controller,
but was not, or cannot now be, configured into the running system.
X.I Message
is one of `available attention' (an `I am here' message) or
`stray response op 0x%x status 0x%x' (anything else).
X.TP
ra%d: bad lbn (%d)?
The drive has reported an invalid command error, probably due to an
invalid block number.  If the lbn value is very much greater than the
size reported by the drive, this is the problem.  It is probably due to
an improperly configured partition table.  Other invalid commands
indicate a bug in the driver, or hardware trouble.
X.TP
ra%d: duplicate ONLINE ignored
The drive has come on-line while already on-line.  This condition
can probably be ignored (and has been).
X.TP
ra%d: io done, but no buffer?
Hardware trouble, or a bug; the drive has finished an I/O request,
but the response has an invalid (zero) command reference number.
X.TP
Emulex SC41/MS screwup: uda%d, got %d correct, then
X.br
X.ti -5
changed 0x%x to 0x%x
X.br
You turned on AVOID_EMULEX_BUG, and the driver successfully
avoided the bug.  The number of correctly-handled requests is
reported, along with the expected and actual values relating to
the bug being avoided.
X.TP
panic: unrecoverable Emulex screwup
You turned on AVOID_EMULEX_BUG, but Emulex was too clever and
avoided the avoidance.  Try turning on MSCP_PARANOIA instead.
X.TP
uda%d: bad response packet ignored
You turned on MSCP_PARANOIA, and the driver caught the controller in
a lie.  The lie has been ignored, and the controller will soon be
reset (after a `lost' interrupt).  This is followed by a hex dump of
the offending packet.
X.TP
ra%d: bogus REPLACE end
The drive has reported finishing a bad sector replacement, but the
driver never issues bad sector replacement commands.  The report
is ignored.  This is likely a hardware problem.
X.TP
ra%d: unknown opcode 0x%x status 0x%x ignored
The drive has reported something that the driver cannot understand.
Perhaps DEC has been inventive, or perhaps your hardware is ill.
This is followed by a hex dump of the offending packet.
X.TP
uda%d: %s error datagram
The controller has reported some kind of error, either `hard'
(unrecoverable) or `soft' (recoverable).  If the controller is going on
(attempting to fix the problem), this message includes the remark
`(continuing)'.  Emulex controllers wrongly claim that all soft errors
are hard errors.  This message may be followed by
one of the following 5 messages, depending on its type, and will always
be followed by a failure detail message (also listed below).
X.RS
X.TP
memory addr 0x%x
A host memory access error; this is the address that could not be
read.
X.TP
unit %d: level %d retry %d, %s %d
A typical disk error; the retry count and error recovery levels are
printed, along with the block type (`lbn', or logical block; or `rbn',
or replacement block) and number.  If the string is something else, DEC
has been clever, or your hardware has gone to Australia for vacation
(unless you live there; then it might be in New Zealand, or Brazil).
X.TP
unit %d: %s %d
Also a disk error, but an `SDI' error, whatever that is.  (I doubt
it has anything to do with Ronald Reagan.)  This lists the block
type (`lbn' or `rbn') and number.
X.TP
unit %d: small disk error, cyl %d
Yet another kind of disk error, but for small disks.  (`That's what
it says, guv'nor.  Dunnask me what it means.')
X.TP
unit %d: unknown error, format 0x%x
A mysterious error: the given format code is not known.
X.RE
X.PP
The detail messages are as follows:
X.RS
X.TP
success (%s) (code 0, subcode %d)
Everything worked, but the controller thought it would let you know
that something went wrong.  No matter what subcode, this can probably
be ignored.
X.TP
invalid command (%s) (code 1, subcode %d)
This probably cannot occur unless the hardware is out; %s should be
`invalid msg length', meaning some command was too short or too long.
X.TP
command aborted (unknown subcode) (code 2, subcode %d)
This should never occur, as the driver never aborts commands.
X.TP
unit offline (%s) (code 3, subcode %d)
The drive is offline, either because it is not around (`unknown
drive'), stopped (`not mounted'), out of order (`inoperative'), has the
same unit number as some other drive (`duplicate'), or has been
disabled for diagnostics (`in diagnosis').
X.TP
unit available (unknown subcode) (code 4, subcode %d)
The controller has decided to report a perfectly normal event as
an error.  (Why?)
X.TP
media format error (%s) (code 5, subcode %d)
The drive cannot be used without reformatting.  The Format Control
Table cannot be read (`fct unread - edc'), there is a bad sector
header (`invalid sector header'), the drive is not set for 512-byte
sectors (`not 512 sectors'), the drive is not formatted (`not formatted'),
or the FCT has an uncorrectable ECC error (`fct ecc').
X.TP
write protected (%s) (code 6, subcode %d)
The drive is write protected, either by the front panel switch
(`hardware') or via the driver (`software').  The driver never
sets software write protect.
X.TP
compare error (unknown subcode) (code 7, subcode %d)
A compare operation showed some sort of difference.  The driver
never uses compare operations.
X.TP
data error (%s) (code 7, subcode %d)
Something went wrong reading or writing a data sector.  A `forced
error' is a software-asserted error used to mark a sector that contains
suspect data.  Rewriting the sector will clear the forced error.  This
is normally set only during bad block replacment, and the driver does
no bad block replacement, so these should not occur.  A `header
compare' error probably means the block is shot.  A `sync timeout'
presumably has something to do with sector synchronisation.
An `uncorrectable ecc' error is an ordinary data error that cannot
be fixed via ECC logic.  A `%d symbol ecc' error is a data error
that can be (and presumably has been) corrected by the ECC logic.
It might indicate a sector that is imperfect but usable, or that
is starting to go bad.  If any of these errors recur, the sector
may need to be replaced.
X.TP
host buffer access error (%s) (code %d, subcode %d)
Something went wrong while trying to copy data to or from the host
(Vax).  The subcode is one of `odd xfer addr', `odd xfer count',
`non-exist. memory', or `memory parity'.  The first two could be a
software glitch; the last two indicate hardware problems.
X.TP
controller error (%s) (code %d, subcode %d)
The controller has detected a hardware error in itself.  A
`serdes overrun' is a serialiser / deserialiser overrun; `edc'
probably stands for `error detection code'; and `inconsistent
internal data struct' is obvious.
X.TP
drive error (%s) (code %d, subcode %d)
Either the controller or the drive has detected a hardware error
in the drive.  I am not sure what an `sdi command timeout' is, but
these seem to occur benignly on occasion.  A `ctlr detected protocol'
error means that the controller and drive do not agree on a protocol;
this could be a cabling problem, or a version mismatch.  A `positioner'
error means the drive seek hardware is ailing; `lost rd/wr ready'
means the drive read/write logic is sick; and `drive clock dropout'
means that the drive clock logic is bad, or the media is hopelessly
scrambled.  I have no idea what `lost recvr ready' means.  A `drive 
detected error' is a catch-all for drive hardware trouble; `ctlr
detected pulse or parity' errors are often caused by cabling problems.
X.RE
X.SH BUGS
The partition tables attempt to combine compatibility with previous
drivers and functionality; this is impossible.  The best solution
would be to read the partition tables off the drive.
X.PP
This version of the driver does not support RA25s, RD52s, and RD53s.
//go.sysin dd *
if [ `wc -c < man/uda.4` != 17142 ]; then
	made=FALSE
	echo error transmitting man/uda.4 --
	echo length should be 17142, not `wc -c < man/uda.4`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 man/uda.4
	echo -n '	'; ls -ld man/uda.4
fi
made=TRUE
if [ $made = TRUE ]; then
	chmod 755 man
	echo -n '	'; ls -ld man
fi
echo Making directory stand
mkdir stand
echo Extracting stand/uda.c
sed 's/^X//' <<'//go.sysin dd *' >stand/uda.c
X/*
 * 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.
 *
 *	@(#)uda.c	7.1 (Berkeley) 6/5/86
 */

X/*
 * UDA50/RAxx disk device driver
 */
#include "../machine/pte.h"

#include "../h/param.h"
#include "../h/inode.h"
#include "../h/fs.h"

#include "saio.h"
#include "savax.h"

#define	NRA	4
X/*
 * Parameters for the communications area
 */
#define	NRSPL2	0
#define	NCMDL2	0
#define	NRSP	(1<<NRSPL2)
#define	NCMD	(1<<NCMDL2)

#include "../vaxuba/udareg.h"
#include "../vaxuba/ubareg.h"
#include "../vax/mscp.h"

u_short udastd[] = { 0772150 };

struct iob	cudbuf;

struct udadevice *udaddr = 0;

struct uda {
	struct udaca	uda_ca;
	struct mscp	uda_rsp;
	struct mscp	uda_cmd;
} uda;

struct uda *ud_ubaddr;			/* Unibus address of uda structure */

X/* int ra25_off[] = { 0, 15884, 0, -1, -1, -1, 25916, -1 }; */
int ra60_off[] = { 0, 15884, 0, 49324, 131404, 49324, 242606, 49324 };
int ra80_off[] = { 0, 15884, 0, -1, 49324, 49324, 49910, 131404 };
#ifndef	UCBRA
#ifdef RA_COMPAT
int ra81_off[] = { 0, 16422, 0, 49324, 131404, 412490, 375564, 83538 };
#else
int ra81_off[] = { 0, 16422, 0, 375564, 391986, 699720, 375564, 83538 };
#endif
#else
int ra81_off[] = { 0, 15884, 0, 242606, 258490, 565690, 242606, 49324 };
#endif
int *ra_off[] = {
	0,
	ra80_off,		/* 1 = ra80 */
	ra81_off,		/* 2 = old ra81 microcode */
	0,			/* 3 = old ra60?? */
	ra60_off,		/* 4 = ra60 */
	ra81_off,		/* 5 = ra81 */
	/* WHAT TYPE IS ra25? */
};
#define	NTYPES (sizeof(ra_off) / sizeof(ra_off[0]))

static int ra_type[NRA];

raopen(io)
	register struct iob *io;
{
	register struct mscp *mp;
	static int udainit, udadriveinit[NRA];
	register int i, t;
	daddr_t off;

	if (udaddr == 0)
		udaddr = (struct udadevice *)ubamem(io->i_unit, udastd[0]);
	if (ud_ubaddr == 0) {
		/*
		 * Initialise cudbuf.i_unit so that controllers
		 * on UNIBUSes other than 0 can be used.
		 */
		cudbuf.i_unit = io->i_unit;
		cudbuf.i_ma = (caddr_t)&uda;
		cudbuf.i_cc = sizeof(uda);
		ud_ubaddr = (struct uda *)ubasetup(&cudbuf, 2);
	}
	if (udainit == 0) {
		udaddr->udaip = 0;
		while ((udaddr->udasa & UDA_STEP1) == 0)
			;
		udaddr->udasa = UDA_ERR;
		while ((udaddr->udasa & UDA_STEP2) == 0)
			;
		udaddr->udasa = (short)&ud_ubaddr->uda_ca.ca_rspdsc[0];
		while ((udaddr->udasa & UDA_STEP3) == 0)
			;
		udaddr->udasa =
			(short)(((int)&ud_ubaddr->uda_ca.ca_rspdsc[0]) >> 16);
		while ((udaddr->udasa & UDA_STEP4) == 0)
			;
		udaddr->udasa = UDA_GO;
		uda.uda_ca.ca_rspdsc[0] = (long)&ud_ubaddr->uda_rsp.mscp_cmdref;
		uda.uda_ca.ca_cmddsc[0] = (long)&ud_ubaddr->uda_cmd.mscp_cmdref;
		if (udcmd(M_OP_SETCTLRC)) {
			_stop("ra: open error, SETCTLRC");
			return;
		}
		udainit = 1;
	}
	i = io->i_unit & 7;
	if (udadriveinit[i] == 0) {
		uda.uda_cmd.mscp_unit = i;
		if (udcmd(M_OP_ONLINE)) {
			_stop("ra: open error, ONLINE");
			return;
		}
		t = ra_type[i] = uda.uda_rsp.mscp_onle.onle_drivetype;
		if (t < 0 || t >= NTYPES || ra_off[t] == 0) {
			printf("uda%d ra%d: disk type %d unknown\n",
				io->i_unit >> 3, i, t);
			_stop("ra: bad type");
		}
		udadriveinit[i] = 1;
	}
	if ((t = io->i_boff) < 0 || t > 7)
		_stop("ra: bad unit");
	off = ra_off[ra_type[i]][t];
	if (off == -1)
		_stop("ra: bad partition");
	io->i_boff = off;
}

udcmd(op)
	int op;
{
	int i;

	uda.uda_cmd.mscp_opcode = op;
	uda.uda_rsp.mscp_msglen = MSCP_MSGLEN;
	uda.uda_cmd.mscp_msglen = MSCP_MSGLEN;
	uda.uda_ca.ca_rspdsc[0] |= MSCP_OWN | MSCP_INT;
	uda.uda_ca.ca_cmddsc[0] |= MSCP_OWN | MSCP_INT;
	i = udaddr->udaip;
	for (;;) {
		if (uda.uda_ca.ca_cmdint)
			uda.uda_ca.ca_cmdint = 0;
		/* should ignore trash a la uda dump code */
		if (uda.uda_ca.ca_rspint)
			break;
	}
	uda.uda_ca.ca_rspint = 0;
	if (uda.uda_rsp.mscp_opcode != (op | M_OP_END) ||
	    (uda.uda_rsp.mscp_status & M_ST_MASK) != M_ST_SUCCESS)
		return (1);
	return (0);
}

rastrategy(io, func)
	register struct iob *io;
{
	register struct mscp *mp;
	int ubinfo;

	ubinfo = ubasetup(io, 1);
	mp = &uda.uda_cmd;
	mp->mscp_unit = io->i_unit&7;
	mp->mscp_seq.seq_lbn = io->i_bn;
	mp->mscp_seq.seq_bytecount = io->i_cc;
	mp->mscp_seq.seq_buffer = (ubinfo & 0x3ffff) |
		((ubinfo >> 4) & (0xf << 24));
	if (udcmd(func == READ ? M_OP_READ : M_OP_WRITE)) {
		printf("ra: I/O error\n");
		ubafree(io, ubinfo);
		return(-1);
	}
	ubafree(io, ubinfo);
	return(io->i_cc);
}

X/*ARGSUSED*/
raioctl(io, cmd, arg)
	struct iob *io;
	int cmd;
	caddr_t arg;
{

	return (ECMD);
}
//go.sysin dd *
if [ `wc -c < stand/uda.c` != 4510 ]; then
	made=FALSE
	echo error transmitting stand/uda.c --
	echo length should be 4510, not `wc -c < stand/uda.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 stand/uda.c
	echo -n '	'; ls -ld stand/uda.c
fi
made=TRUE
if [ $made = TRUE ]; then
	chmod 755 stand
	echo -n '	'; ls -ld stand
fi
echo Making directory vaxif
mkdir vaxif
echo Extracting vaxif/if_ec.c.diff
sed 's/^X//' <<'//go.sysin dd *' >vaxif/if_ec.c.diff
*** vaxif/if_ec.c.4.3	Thu Jun  5 20:04:36 1986
--- vaxif/if_ec.c	Tue Feb 10 20:57:32 1987
***************
*** 58,62 ****
  u_short ecstd[] = { 0 };
  struct	uba_driver ecdriver =
! 	{ ecprobe, 0, ecattach, 0, ecstd, "ec", ecinfo, 0, 0, 0, ecubamem };
  
  int	ecinit(),ecioctl(),ecoutput(),ecreset();
--- 58,62 ----
  u_short ecstd[] = { 0 };
  struct	uba_driver ecdriver =
! 	{ ecprobe, 0, ecattach, 0, ecstd, "ec", ecinfo, 0, 0, 0, 0, ecubamem };
  
  int	ecinit(),ecioctl(),ecoutput(),ecreset();
//go.sysin dd *
if [ `wc -c < vaxif/if_ec.c.diff` != 499 ]; then
	made=FALSE
	echo error transmitting vaxif/if_ec.c.diff --
	echo length should be 499, not `wc -c < vaxif/if_ec.c.diff`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vaxif/if_ec.c.diff
	echo -n '	'; ls -ld vaxif/if_ec.c.diff
fi
made=TRUE
if [ $made = TRUE ]; then
	chmod 755 vaxif
	echo -n '	'; ls -ld vaxif
fi
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris@mimsy.umd.edu

chris@mimsy.UUCP (04/05/87)

: Run this shell script with "sh" not "csh"
PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
export PATH
all=FALSE
if [ x$1 = x-a ]; then
	all=TRUE
fi
echo Making directory vaxuba
mkdir vaxuba
echo Extracting vaxuba/uba.c.diff
sed 's/^X//' <<'//go.sysin dd *' >vaxuba/uba.c.diff
*** vaxuba/uba.c.4.3	Thu Jun  5 04:19:32 1986
--- vaxuba/uba.c	Tue Feb 10 21:14:26 1987
***************
*** 33,36 ****
--- 33,38 ----
  #define	spluba	spl7		/* IPL 17 */
  
+ #define	BDPMASK	0xf0000000	/* see ubavar.h */
+ 
  /*
   * Do transfer on device argument.  The controller
***************
*** 38,66 ****
   * We queue for resource wait in the uba code if necessary.
   * We return 1 if the transfer was started, 0 if it was not.
!  * If you call this routine with the head of the queue for a
!  * UBA, it will automatically remove the device from the UBA
!  * queue before it returns.  If some other device is given
!  * as argument, it will be added to the request queue if the
!  * request cannot be started immediately.  This means that
!  * passing a device which is on the queue but not at the head
!  * of the request queue is likely to be a disaster.
   */
! ubago(ui)
  	register struct uba_device *ui;
  {
  	register struct uba_ctlr *um = ui->ui_mi;
  	register struct uba_hd *uh;
  	register int s, unit;
  
  	uh = &uba_hd[um->um_ubanum];
  	s = spluba();
! 	if (um->um_driver->ud_xclu && uh->uh_users > 0 || uh->uh_xclu)
  		goto rwait;
! 	um->um_ubinfo = ubasetup(um->um_ubanum, um->um_tab.b_actf->b_actf,
! 	    UBA_NEEDBDP|UBA_CANTWAIT);
  	if (um->um_ubinfo == 0)
  		goto rwait;
  	uh->uh_users++;
! 	if (um->um_driver->ud_xclu)
  		uh->uh_xclu = 1;
  	splx(s);
--- 40,93 ----
   * We queue for resource wait in the uba code if necessary.
   * We return 1 if the transfer was started, 0 if it was not.
!  *
!  * The onq argument must be zero iff the device is not on the
!  * queue for this UBA.  If onq is set, the device must be at the
!  * head of the queue.  In any case, if the transfer is started,
!  * the device will be off the queue, and if not, it will be on.
!  *
!  * Drivers that allocate one BDP and hold it for some time should
!  * set ud_keepbdp.  In this case um_bdp tells which BDP is allocated
!  * to the controller, unless it is zero, indicating that the controller
!  * does not now have a BDP.
   */
! ubaqueue(ui, onq)
  	register struct uba_device *ui;
+ 	int onq;
  {
  	register struct uba_ctlr *um = ui->ui_mi;
  	register struct uba_hd *uh;
+ 	register struct uba_driver *ud;
  	register int s, unit;
  
  	uh = &uba_hd[um->um_ubanum];
+ 	ud = um->um_driver;
  	s = spluba();
! 	/*
! 	 * Honor exclusive BDP use requests.
! 	 */
! 	if (ud->ud_xclu && uh->uh_users > 0 || uh->uh_xclu)
  		goto rwait;
! 	if (ud->ud_keepbdp) {
! 		/*
! 		 * First get just a BDP (though in fact it comes with
! 		 * one map register too).
! 		 */
! 		if (um->um_bdp == 0) {
! 			um->um_bdp = uballoc(um->um_ubanum,
! 				(caddr_t)0, 0, UBA_NEEDBDP|UBA_CANTWAIT);
! 			if (um->um_bdp == 0)
! 				goto rwait;
! 		}
! 		/* now share it with this transfer */
! 		um->um_ubinfo = ubasetup(um->um_ubanum,
! 			um->um_tab.b_actf->b_actf,
! 			um->um_bdp|UBA_HAVEBDP|UBA_CANTWAIT);
! 	} else
! 		um->um_ubinfo = ubasetup(um->um_ubanum,
! 			um->um_tab.b_actf->b_actf, UBA_NEEDBDP|UBA_CANTWAIT);
  	if (um->um_ubinfo == 0)
  		goto rwait;
  	uh->uh_users++;
! 	if (ud->ud_xclu)
  		uh->uh_xclu = 1;
  	splx(s);
***************
*** 71,80 ****
  		dk_wds[unit] += um->um_tab.b_actf->b_actf->b_bcount>>6;
  	}
! 	if (uh->uh_actf == ui)
  		uh->uh_actf = ui->ui_forw;
! 	(*um->um_driver->ud_dgo)(um);
  	return (1);
  rwait:
! 	if (uh->uh_actf != ui) {
  		ui->ui_forw = NULL;
  		if (uh->uh_actf == NULL)
--- 98,107 ----
  		dk_wds[unit] += um->um_tab.b_actf->b_actf->b_bcount>>6;
  	}
! 	if (onq)
  		uh->uh_actf = ui->ui_forw;
! 	(*ud->ud_dgo)(um);
  	return (1);
  rwait:
! 	if (!onq) {
  		ui->ui_forw = NULL;
  		if (uh->uh_actf == NULL)
***************
*** 96,99 ****
--- 123,128 ----
  		uh->uh_xclu = 0;
  	uh->uh_users--;
+ 	if (um->um_driver->ud_keepbdp)
+ 		um->um_ubinfo &= ~BDPMASK;	/* keep BDP for misers */
  	ubarelse(um->um_ubanum, &um->um_ubinfo);
  }
***************
*** 273,277 ****
  		wakeup((caddr_t)&uh->uh_mrwant);
  	}
! 	while (uh->uh_actf && ubago(uh->uh_actf))
  		;
  }
--- 302,306 ----
  		wakeup((caddr_t)&uh->uh_mrwant);
  	}
! 	while (uh->uh_actf && ubaqueue(uh->uh_actf, 1))
  		;
  }
//go.sysin dd *
if [ `wc -c < vaxuba/uba.c.diff` != 4146 ]; then
	made=FALSE
	echo error transmitting vaxuba/uba.c.diff --
	echo length should be 4146, not `wc -c < vaxuba/uba.c.diff`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vaxuba/uba.c.diff
	echo -n '	'; ls -ld vaxuba/uba.c.diff
fi
echo Extracting vaxuba/ubavar.h.diff
sed 's/^X//' <<'//go.sysin dd *' >vaxuba/ubavar.h.diff
*** vaxuba/ubavar.h.4.3	Thu Jun  5 04:20:06 1986
--- vaxuba/ubavar.h	Tue Feb 10 20:56:55 1987
***************
*** 60,64 ****
  };
  
- #ifndef LOCORE
  /*
   * Per-controller structure.
--- 60,63 ----
***************
*** 83,86 ****
--- 82,86 ----
  	int	um_cmd;		/* communication to dgo() */
  	int	um_ubinfo;	/* save unibus registers, etc */
+ 	int	um_bdp;		/* for controllers that hang on to bdp's */
  	struct	buf um_tab;	/* queue of devices for this controller */
  };
***************
*** 121,125 ****
  	struct	uba_hd *ui_hd;
  };
- #endif
  
  /*
--- 121,124 ----
***************
*** 141,144 ****
--- 140,144 ----
  	struct	uba_ctlr **ud_minfo;	/* backpointers to ubminit structs */
  	short	ud_xclu;		/* want exclusive use of bdp's */
+ 	short	ud_keepbdp;		/* hang on to bdp's once allocated */
  	int	(*ud_ubamem)();		/* see if dedicated memory is present */
  };
***************
*** 164,167 ****
--- 164,169 ----
  #ifndef LOCORE
  #ifdef KERNEL
+ #define	ubago(ui)	ubaqueue(ui, 0)
+ 
  /*
   * UBA related kernel variables
//go.sysin dd *
if [ `wc -c < vaxuba/ubavar.h.diff` != 1033 ]; then
	made=FALSE
	echo error transmitting vaxuba/ubavar.h.diff --
	echo length should be 1033, not `wc -c < vaxuba/ubavar.h.diff`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vaxuba/ubavar.h.diff
	echo -n '	'; ls -ld vaxuba/ubavar.h.diff
fi
echo Extracting vaxuba/uda.c
sed 's/^X//' <<'//go.sysin dd *' >vaxuba/uda.c
X/*
 * UDA50/MSCP device driver
 */

X/*
 * TODO
 *	write bad block forwarding code
 */

#include "ra.h"

#if NUDA > 0

X/*
 * CONFIGURATION OPTIONS.  The next three defines are tunable -- tune away!
 *
 * NRSPL2 and NCMDL2 control the number of response and command
 * packets respectively.  They may be any value from 0 to 7, though
 * setting them higher than 5 is unlikely to be of any value.
 * If you get warnings about your command ring being too small,
 * try increasing the values by one.
 *
 * MAXUNIT controls the maximum unit number (number of drives per
 * controller) we are prepared to handle.
 *
 * DEFAULT_BURST must be at least 1.
 */
#define	NRSPL2	5		/* log2 number of response packets */
#define NCMDL2	5		/* log2 number of command packets */
#define	MAXUNIT	8		/* maximum allowed unit number */
#define	DEFAULT_BURST	4	/* default DMA burst size */

#include "../machine/pte.h"

#include "param.h"
#include "systm.h"
#include "buf.h"
#include "conf.h"
#include "dir.h"
#include "user.h"
#include "map.h"
#include "vm.h"
#include "dk.h"
#include "cmap.h"
#include "syslog.h"

#include "../vax/cpu.h"
#include "ubareg.h"
#include "ubavar.h"

#define	NRSP	(1 << NRSPL2)
#define	NCMD	(1 << NCMDL2)

#include "udareg.h"
#include "../vax/mscp.h"
#include "../vax/mscpvar.h"
#include "../vax/mtpr.h"

X/*
 * Backwards compatibility:  Reuse the old names.  Should fix someday.
 */
#define	udaprobe	udprobe
#define	udaslave	udslave
#define	udaattach	udattach
#define	udaopen		udopen
#define	udastrategy	udstrategy
#define	udaread		udread
#define	udawrite	udwrite
#define	udareset	udreset
#define	udaintr		udintr
#define	udadump		uddump
#define	udasize		udsize

X/*
 * UDA communications area and MSCP packet pools, per controller.
 */
struct	uda {
	struct	udaca uda_ca;		/* communications area */
	struct	mscp uda_rsp[NRSP];	/* response packets */
	struct	mscp uda_cmd[NCMD];	/* command packets */
} uda[NUDA];

X/*
 * Software status, per controller.
 */
struct	uda_softc {
	struct	uda *sc_uda;	/* Unibus address of uda struct */
	short	sc_state;	/* UDA50 state; see below */
	short	sc_flags;	/* flags; see below */
	int	sc_micro;	/* microcode revision */
	int	sc_ivec;	/* interrupt vector address */
	struct	mscp_info sc_mi;/* MSCP info (per mscpvar.h) */
	int	sc_wticks;	/* watchdog timer ticks */
} uda_softc[NUDA];

X/*
 * Controller states
 */
#define	ST_IDLE		0	/* uninitialised */
#define	ST_STEP1	1	/* in `STEP 1' */
#define	ST_STEP2	2	/* in `STEP 2' */
#define	ST_STEP3	3	/* in `STEP 3' */
#define	ST_SETCHAR	4	/* in `Set Controller Characteristics' */
#define	ST_RUN		5	/* up and running */

X/*
 * Flags
 */
#define	SC_MAPPED	0x01	/* mapped in Unibus I/O space */
#define	SC_INSTART	0x02	/* inside udastart() */
#define	SC_GRIPED	0x04	/* griped about cmd ring too small */
#define	SC_INSLAVE	0x08	/* inside udaslave() */
#define	SC_DOWAKE	0x10	/* wakeup when ctlr init done */

X/*
 * Device to unit number and partition:
 */
#define	UNITSHIFT	3
#define	UNITMASK	7
#define	udaunit(dev)	(minor(dev) >> UNITSHIFT)
#define	udapart(dev)	(minor(dev) & UNITMASK)

X/* THIS SHOULD BE READ OFF THE PACK, PER DRIVE */
struct size {
	daddr_t nblocks;
	daddr_t blkoff;
} ra81_sizes[8] = {
#ifdef MARYLAND
#ifdef ENEEVAX
	30706,	0,		/* A=cyl    0 thru   42 + 2 sectors */
	40696,	30706,		/* B=cyl   43 thru   99 - 2 sectors */
	-1,	0,		/* C=cyl    0 thru 1247 */
	-1,	71400,		/* D=cyl  100 thru 1247 */

	15884,	0,		/* E=blk      0 thru  15883 */
	33440,	15884,		/* F=blk  15884 thru  49323 */
	82080,	49324,		/* G=blk  49324 thru 131403 */
	-1,	131404,		/* H=blk 131404 thru    end */
#else
	67832,	0,		/* A=cyl    0 thru   94 + 2 sectors */
	67828,	67832,		/* B=cyl   95 thru  189 - 2 sectors */
	-1,	0,		/* C=cyl    0 thru 1247 */
	-1,	135660,		/* D=cyl  190 thru 1247 */
	0,	0,
	0,	0,
	0,	0,
	0,	0,
#endif ENEEVAX
#else
	/* THE FOLLOWING ARE STRAIGHT FROM THE 4.3BSD uda.c */
	/* THIS KIND OF GARBAGE IS WHY THIS SHOULD BE READ FROM THE PACK */

X/*
 * These are the new standard partition sizes for ra81's.
 * An RA_COMPAT system is compiled with D, E, and F corresponding
 * to the 4.2 partitions for G, H, and F respectively.
 */
#ifndef	UCBRA
	15884,	0,		/* A=sectors 0 thru 15883 */
	66880,	16422,		/* B=sectors 16422 thru 83301 */
	891072,	0,		/* C=sectors 0 thru 891071 */
#ifdef RA_COMPAT
	82080,	49324,		/* 4.2 G => D=sectors 49324 thru 131403 */
	759668,	131404,		/* 4.2 H => E=sectors 131404 thru 891071 */
	478582,	412490,		/* 4.2 F => F=sectors 412490 thru 891071 */
#else
	15884,	375564,		/* D=sectors 375564 thru 391447 */
	307200,	391986,		/* E=sectors 391986 thru 699185 */
	191352,	699720,		/* F=sectors 699720 thru 891071 */
#endif RA_COMPAT
	515508,	375564,		/* G=sectors 375564 thru 891071 */
	291346,	83538,		/* H=sectors 83538 thru 374883 */

X/*
 * These partitions correspond to the sizes used by sites at Berkeley,
 * and by those sites that have received copies of the Berkeley driver
 * with deltas 6.2 or greater (11/15/83).
 */
#else UCBRA

	15884,	0,		/* A=sectors 0 thru 15883 */
	33440,	15884,		/* B=sectors 15884 thru 49323 */
	891072,	0,		/* C=sectors 0 thru 891071 */
	15884,	242606,		/* D=sectors 242606 thru 258489 */
	307200,	258490,		/* E=sectors 258490 thru 565689 */
	325382,	565690,		/* F=sectors 565690 thru 891071 */
	648466,	242606,		/* G=sectors 242606 thru 891071 */
	193282,	49324,		/* H=sectors 49324 thru 242605 */

#endif UCBRA
#endif MARYLAND
},
#if GYRE
cdc9771_sizes[8] = {		/* HACK: treat some RA81s as 9771s on gyre */
	79680,	0,		/* A = cyl    0 thru   59 */
	79680,	79680,		/* B = cyl   60 thru  119 */
	-1,	0,		/* C = cyl    0 thru 1021 */
	-1,	159360,		/* D = cyl  120 thru 1021 */
	0,	0,
	0,	0,
	0,	0,
	0,	0,
},
#endif
ra80_sizes[8] = {
	15884,	0,		/* A=blk 0 thru 15883 */
	33440,	15884,		/* B=blk 15884 thru 49323 */
	-1,	0,		/* C=blk 0 thru end */
	0,	0,
	0,	0,
	0,	0,
	82080,	49324,		/* G=blk 49324 thru 131403 */
	-1,	131404,		/* H=blk 131404 thru end */
}, ra60_sizes[8] = {
	15884,	0,		/* A=blk 0 thru 15883 */
	33440,	15884,		/* B=blk 15884 thru 49323 */
	-1,	0,		/* C=blk 0 thru end */
	-1,	49324,		/* D=blk 49324 thru end */
	0,	0,
	0,	0,
	82080,	49324,		/* G=blk 49324 thru 131403 */
	-1,	131404,		/* H=blk 131404 thru end */
};
X/* END OF STUFF WHICH SHOULD BE READ IN PER DISK */

X/*
 * Drive type index decoding table.  `ut_name' is null iff the
 * type is not known.
 */
struct	udatypes {
	char	*ut_name;	/* drive type name */
	struct	size *ut_sizes;	/* partition tables */
} udatypes[] = {
	NULL,		NULL,
	"ra80",		ra80_sizes,	/* 1 = ra80 */
	"old ra81",	ra81_sizes,	/* 2 = old ra81 microcode */
	NULL,		NULL,		/* 3 = old ra60?? */
	"ra60",		ra60_sizes,	/* 4 = ra60 */
	"ra81",		ra81_sizes,	/* 5 = ra81 */
#if GYRE
	/*
	 * This CDC partition hack depends on the fact that the
	 * Emulex SC41/MS controller is `version 6 model 6' and
	 * the current DEC devices are `version 5 model 6'.
	 */
	"cdc9771",	cdc9771_sizes,
#define	CDCTYPE	6	/* note that this is past the last real type */
#define	ISCDC(sc) ((sc)->sc_micro == 0x66)
#endif
};

#define NTYPES 6

X/*
 * Definition of the driver for autoconf.
 */
int	udaprobe(), udaslave(), udaattach(), udadgo(), udaintr();
struct	uba_ctlr *udaminfo[NUDA];
struct	uba_device *udadinfo[NRA];

u_short	udastd[] = { 0772150, 0772550, 0777550, 0 };
struct	uba_driver udadriver =
 { udaprobe, udaslave, udaattach, udadgo, udastd, "ra", udadinfo, "uda",
   udaminfo };

X/*
 * More driver definitions, for generic MSCP code.
 */
int	udadgram(), udactlrdone(), udaunconf(), udaonline(), udagotstatus();
int	udaioerror(), udareplace(), udabb();

struct	buf udautab[NRA];	/* per drive transfer queue */

struct	mscp_driver udamscpdriver =
 { MAXUNIT, NRA, UNITSHIFT, udautab,
   udadgram, udactlrdone, udaunconf,
   udaonline, udagotstatus, udareplace, udaioerror, udabb };

X/*
 * Miscellaneous private variables.
 */
struct	buf rudabuf[NRA];	/* raw I/O buffer headers */

char	udasr_bits[] = UDASR_BITS;

struct	uba_device *udaip[NUDA][MAXUNIT];
				/* inverting pointers: ctlr & unit => Unibus
				   device pointer */

int	udaburst[NUDA] = {0};	/* burst size, per UDA50, zero => default;
				   in data space so patchable via adb */

daddr_t	ra_dsize[NRA];		/* drive sizes, from on line end packets */

struct	mscp udaslavereply;	/* get unit status response packet, set
				   for udaslave by udaunconf, via udaintr */

static struct uba_ctlr *probeum;/* this is a hack---autoconf should pass ctlr
				   info to slave routine; instead, we remember
				   the last ctlr argument to probe */

int	udawstart, udawatch();	/* watchdog timer */

X/*
 * Externals
 */
int	wakeup();
int	hz;

X/*
 * Poke at a supposed UDA50 to see if it is there.
 * This routine duplicates some of the code in udainit() only
 * because autoconf has not set up the right information yet.
 * We have to do everything `by hand'.
 */
udaprobe(reg, ctlr, um)
	caddr_t reg;
	int ctlr;
	struct uba_ctlr *um;
{
	register int br, cvec;
	register struct uda_softc *sc;
	register struct udadevice *udaddr;
	register struct mscp_info *mi;
	int timeout, tries;

#ifdef VAX750
	/*
	 * The UDA50 wants to share BDPs on 750s, but not on 780s or
	 * 8600s.  (730s have no BDPs anyway.)  Toward this end, we
	 * here set the `keep bdp' flag in the per-driver information
	 * if this is a 750.  (We just need to do it once, but it is
	 * easiest to do it now, for each UDA50.)
	 */
	if (cpu == VAX_750)
		udadriver.ud_keepbdp = 1;
#endif

	probeum = um;			/* remember for udaslave() */
#ifdef lint
	br = 0; cvec = br; br = cvec; udaintr(0);
#endif
	/*
	 * Set up the controller-specific generic MSCP driver info.
	 * Note that this should really be done in the (nonexistent)
	 * controller attach routine.
	 */
	sc = &uda_softc[ctlr];
	mi = &sc->sc_mi;
	mi->mi_md = &udamscpdriver;
	mi->mi_um = um;
	mi->mi_ip = udaip[ctlr];
	mi->mi_cmd.mri_size = NCMD;
	mi->mi_cmd.mri_desc = uda[ctlr].uda_ca.ca_cmddsc;
	mi->mi_cmd.mri_ring = uda[ctlr].uda_cmd;
	mi->mi_rsp.mri_size = NRSP;
	mi->mi_rsp.mri_desc = uda[ctlr].uda_ca.ca_rspdsc;
	mi->mi_rsp.mri_ring = uda[ctlr].uda_rsp;
	mi->mi_wtab.av_forw = mi->mi_wtab.av_back = &mi->mi_wtab;

	/*
	 * More controller specific variables.  Again, this should
	 * be in the controller attach routine.
	 */
	if (udaburst[ctlr] == 0)
		udaburst[ctlr] = DEFAULT_BURST;
		
	/*
	 * Get an interrupt vector.  Note that even if the controller
	 * does not respond, we keep the vector.  This is not a serious
	 * problem; but it would be easily fixed if we had a controller
	 * attach routine.  Sigh.
	 */
	sc->sc_ivec = (uba_hd[numuba].uh_lastiv -= 4);
	udaddr = (struct udadevice *) reg;

	/*
	 * Initialise the controller (partially).  The UDA50 programmer's
	 * manual states that if initialisation fails, it should be retried
	 * at least once, but after a second failure the port should be
	 * considered `down'; it also mentions that the controller should
	 * initialise within ten seconds.  Or so I hear; I have not seen
	 * this manual myself.
	 *
	 * N.B.: mfpr(TODR) will not work on uVaxen.
	 */
	tries = 0;
again:
	udaddr->udaip = 0;		/* start initialisation */
	timeout = mfpr(TODR) + 1000;	/* timeout in 10 seconds */
	while ((udaddr->udasa & UDA_STEP1) == 0)
		if (mfpr(TODR) > timeout)
			goto bad;
	udaddr->udasa = UDA_ERR | (NCMDL2 << 11) | (NRSPL2 << 8) | UDA_IE |
		(sc->sc_ivec >> 2);
	while ((udaddr->udasa & UDA_STEP2) == 0)
		if (mfpr(TODR) > timeout)
			goto bad;

	/* should have interrupted by now */
	return (sizeof (struct udadevice));
bad:
	if (++tries < 2)
		goto again;
	return (0);
}

X/*
 * Find a slave.  We allow wildcard slave numbers (something autoconf
 * is not really prepared to deal with); and we need to know the
 * controller number to talk to the UDA.  For the latter, we keep
 * track of the last controller probed, since a controller probe
 * immediately precedes all slave probes for that controller.  For the
 * former, we simply put the unit number into ui->ui_slave after we
 * have found one.
 *
 * Note that by the time udaslave is called, the interrupt vector
 * for the UDA50 has been set up (so that udaunconf() will be called).
 */
udaslave(ui, reg)
	register struct uba_device *ui;
	caddr_t reg;
{
	register struct uba_ctlr *um = probeum;
	register struct mscp *mp;
	register struct uda_softc *sc;
	int next = 0, type, timeout, tries, i;

#ifdef lint
	i = 0; i = i;
#endif
	/*
	 * Make sure the controller is fully initialised, by waiting
	 * for it if necessary.
	 */
	sc = &uda_softc[um->um_ctlr];
	if (sc->sc_state == ST_RUN)
		goto findunit;
	tries = 0;
again:
	if (udainit(ui->ui_ctlr))
		return (0);
	timeout = mfpr(TODR) + 1000;		/* 10 seconds */
	while (mfpr(TODR) < timeout)
		if (sc->sc_state == ST_RUN)	/* made it */
			goto findunit;
	if (++tries < 2)
		goto again;
	printf("uda%d: controller hung\n", um->um_ctlr);
	return (0);

	/*
	 * The controller is all set; go find the unit.  Grab an
	 * MSCP packet and send out a Get Unit Status command, with
	 * the `next unit' modifier if we are looking for a generic
	 * unit.  We set the `in slave' flag so that udaunconf()
	 * knows to copy the response to `udaslavereply'.
	 */
findunit:
	udaslavereply.mscp_opcode = 0;
	sc->sc_flags |= SC_INSLAVE;
	if ((mp = mscp_getcp(&sc->sc_mi, MSCP_DONTWAIT)) == NULL)
		panic("udaslave");		/* `cannot happen' */
	mp->mscp_opcode = M_OP_GETUNITST;
	if (ui->ui_slave == '?') {
		mp->mscp_unit = next;
		mp->mscp_modifier = M_GUM_NEXTUNIT;
	} else {
		mp->mscp_unit = ui->ui_slave;
		mp->mscp_modifier = 0;
	}
	*mp->mscp_seq.seq_addr |= MSCP_OWN | MSCP_INT;
	i = ((struct udadevice *) reg)->udaip;	/* initiate polling */
	mp = &udaslavereply;
	timeout = mfpr(TODR) + 1000;
	while (mfpr(TODR) < timeout)
		if (mp->mscp_opcode)
			goto gotit;
	printf("uda%d: no response to Get Unit Status request\n",
		um->um_ctlr);
	sc->sc_flags &= ~SC_INSLAVE;
	return (0);

gotit:
	sc->sc_flags &= ~SC_INSLAVE;

	/*
	 * Got a slave response.  If the unit is there, use it.
	 */
	switch (mp->mscp_status & M_ST_MASK) {

	case M_ST_SUCCESS:	/* worked */
	case M_ST_AVAILABLE:	/* found another drive */
		break;		/* use it */

	case M_ST_OFFLINE:
		/*
		 * Figure out why it is off line.  It may be because
		 * it is nonexistent, or because it is spun down, or
		 * for some other reason.
		 */
		switch (mp->mscp_status & ~M_ST_MASK) {

		case M_OFFLINE_UNKNOWN:
			/*
			 * No such drive, and there are none with
			 * higher unit numbers either, if we are
			 * using M_GUM_NEXTUNIT.
			 */
			return (0);

		case M_OFFLINE_UNMOUNTED:
			/*
			 * The drive is not spun up.  Use it anyway.
			 *
			 * N.B.: this seems to be a common occurrance
			 * after a power failure.  The first attempt
			 * to bring it on line seems to spin it up
			 * (and thus takes several minutes).  Perhaps
			 * we should note here that the on-line may
			 * take longer than usual.
			 */
			break;

		default:
			/*
			 * In service, or something else equally unusable.
			 */
			printf("uda%d: unit %d off line: ", um->um_ctlr,
				mp->mscp_unit);
			mscp_printevent(mp);
			goto try_another;
		}
		break;

	default:
		printf("uda%d: unable to get unit status: ", um->um_ctlr);
		mscp_printevent(mp);
		return (0);
	}

	/*
	 * Does this ever happen?  What (if anything) does it mean?
	 */
	if (mp->mscp_unit < next) {
		printf("uda%d: unit %d, next %d\n",
			um->um_ctlr, mp->mscp_unit, next);
		return (0);
	}

	if (mp->mscp_unit >= MAXUNIT) {
		printf("uda%d: cannot handle unit number %d (max is %d)\n",
			um->um_ctlr, mp->mscp_unit, MAXUNIT - 1);
		return (0);
	}

	/*
	 * See if we already handle this drive.
	 * (Only likely if ui->ui_slave=='?'.)
	 */
	if (udaip[um->um_ctlr][mp->mscp_unit] != NULL)
		goto try_another;

	/*
	 * Make sure we know about this kind of drive.
	 * Others say we should treat unknowns as RA81s; I am
	 * not sure this is safe.
	 */
	type = mp->mscp_guse.guse_drivetype;
	if (type >= NTYPES || udatypes[type].ut_name == 0) {
		printf("uda%d: unit %d (media ID `", um->um_ctlr,
			mp->mscp_unit);
		uda_decode_media(mp->mscp_guse.guse_mediaid);
		printf("') is of unknown type %d; ignored\n", type);
try_another:
		if (ui->ui_slave != '?')
			return (0);
		next = mp->mscp_unit + 1;
		goto findunit;
	}
#if GYRE
	if (ISCDC(sc))
		type = CDCTYPE;
#endif

	/*
	 * Voila!
	 */
	ui->ui_type = type;
	ui->ui_flags = 0;	/* not on line, nor anything else */
	ui->ui_slave = mp->mscp_unit;
	return (1);
}

X/*
 * Decode and print the media ID.  It is made up of five 5-bit
 * `characters' and 7 bits of numeric information.  BITS(i)
 * selects character i's bits; CHAR returns the corresponding
 * character.
 */
uda_decode_media(id)
	register long id;
{
	int c4, c3, c2, c1, c0;
#define	BITS(i)	((id >> ((i) * 5 + 7)) & 0x1f)
#define	CHAR(c)	((c) ? (c) + '@' : ' ')

	c4 = BITS(4);
	c3 = BITS(3);
	c2 = BITS(2);
	c1 = BITS(1);
	c0 = BITS(0);
	printf("%c%c %c%c%c%d", CHAR(c4), CHAR(c3), CHAR(c2),
		CHAR(c1), CHAR(c0), id & 0x7f);
#undef	BITS
#undef	CHAR
}

X/*
 * Attach a found slave.  Make sure the watchdog timer is running.
 * If this disk is being profiled, fill in the `mspw' value (used by
 * what?).  Set up the inverting pointer, and attempt to bring the
 * drive on line.
 */
udaattach(ui)
	register struct uba_device *ui;
{

	if (udawstart == 0) {
		timeout(udawatch, (caddr_t) 0, hz);
		udawstart++;
	}
	if (ui->ui_dk >= 0)
		dk_mspw[ui->ui_dk] = 1.0 / (60 * 31 * 256);	/* approx */
	udaip[ui->ui_ctlr][ui->ui_slave] = ui;
	(void) uda_bringonline(&uda_softc[ui->ui_ctlr], ui, 1);
	/* should we get its status too? */
}

X/*
 * Initialise a UDA50.  Return true iff something goes wrong.
 */
udainit(ctlr)
	int ctlr;
{
	register struct uda_softc *sc;
	register struct udadevice *udaddr;
	struct uba_ctlr *um;
	int timo, ubinfo;

	sc = &uda_softc[ctlr];
	um = udaminfo[ctlr];
	if ((sc->sc_flags & SC_MAPPED) == 0) {
		/*
		 * Map the communication area and command and
		 * response packets into Unibus space.
		 */
		ubinfo = uballoc(um->um_ubanum, (caddr_t) &uda[ctlr],
			sizeof (struct uda), UBA_CANTWAIT);
		if (ubinfo == 0) {
			printf("uda%d: uballoc map failed\n", ctlr);
			return (-1);
		}
		sc->sc_uda = (struct uda *) (ubinfo & 0x3ffff);
		sc->sc_flags |= SC_MAPPED;
	}

	/*
	 * While we are thinking about it, reset the next command
	 * and response indicies.
	 */
	sc->sc_mi.mi_cmd.mri_next = 0;
	sc->sc_mi.mi_rsp.mri_next = 0;

	/*
	 * Start up the hardware initialisation sequence.
	 */
#define	STEP0MASK	(UDA_ERR | UDA_STEP4 | UDA_STEP3 | UDA_STEP2 | \
			 UDA_STEP1 | UDA_NV)

	sc->sc_state = ST_IDLE;	/* in case init fails */
	udaddr = (struct udadevice *) um->um_addr;
	udaddr->udaip = 0;
	timo = mfpr(TODR) + 1000;
	while ((udaddr->udasa & STEP0MASK) == 0) {
		if (mfpr(TODR) > timo) {
			printf("uda%d: timeout during init\n", ctlr);
			return (-1);
		}
	}
	if ((udaddr->udasa & STEP0MASK) != UDA_STEP1) {
		printf("uda%d: init failed, sa=%b\n", ctlr,
			udaddr->udasa, udasr_bits);
		return (-1);
	}

	/*
	 * Success!  Record new state, and start step 1 initialisation.
	 * The rest is done in the interrupt handler.
	 */
	sc->sc_state = ST_STEP1;
	udaddr->udasa = UDA_ERR | (NCMDL2 << 11) | (NRSPL2 << 8) | UDA_IE |
	    (sc->sc_ivec >> 2);
	return (0);
}

X/*
 * Open a drive.
 */
X/*ARGSUSED*/
udaopen(dev, flag)
	dev_t dev;
	int flag;
{
	register int unit;
	register struct uba_device *ui;
	register struct uda_softc *sc;
	int s;

	/*
	 * Make sure this is a reasonable open request.
	 */
	unit = udaunit(dev);
	if (unit >= NRA || (ui = udadinfo[unit]) == 0 || ui->ui_alive == 0)
		return (ENXIO);

	/*
	 * Make sure the controller is running, by (re)initialising it if
	 * necessary.
	 */
	sc = &uda_softc[ui->ui_ctlr];
	s = spl5();
	if (sc->sc_state != ST_RUN) {
		if (sc->sc_state == ST_IDLE && udainit(ui->ui_ctlr)) {
			splx(s);
			return (EIO);
		}
		/*
		 * In case it does not come up, make sure we will be
		 * restarted in 10 seconds.  This corresponds to the
		 * 10 second timeouts in udaprobe() and udaslave().
		 */
		sc->sc_flags |= SC_DOWAKE;
		timeout(wakeup, (caddr_t) sc, 10 * hz);
		sleep((caddr_t) sc, PRIBIO);
		if (sc->sc_state != ST_RUN) {
			splx(s);
			printf("uda%d: controller hung\n", ui->ui_ctlr);
			return (EIO);
		}
		untimeout(wakeup, (caddr_t) sc);
	}
	if ((ui->ui_flags & UNIT_ONLINE) == 0) {
		/*
		 * Bring the drive on line so we can find out how
		 * big it is.  If it is not spun up, it will not
		 * come on line; this cannot really be considered
		 * an `error condition'.
		 */
		if (uda_bringonline(sc, ui, 0)) {
			splx(s);
			printf("ra%d: drive will not come on line\n", unit);
			return (EIO);
		}
	}
	splx(s);
	return (0);
}

X/*
 * Bring a drive on line.  In case it fails to respond, we set
 * a timeout on it.  The `nosleep' parameter should be set if
 * we are to spin-wait; otherwise this must be called at spl5().
 */
uda_bringonline(sc, ui, nosleep)
	register struct uda_softc *sc;
	register struct uba_device *ui;
	int nosleep;
{
	register struct mscp *mp;
	int i;

	if (nosleep) {
		mp = mscp_getcp(&sc->sc_mi, MSCP_DONTWAIT);
		if (mp == NULL)
			return (-1);
	} else
		mp = mscp_getcp(&sc->sc_mi, MSCP_WAIT);
	mp->mscp_opcode = M_OP_ONLINE;
	mp->mscp_unit = ui->ui_slave;
	mp->mscp_cmdref = (long) &ui->ui_flags;
	*mp->mscp_seq.seq_addr |= MSCP_OWN | MSCP_INT;
	i = ((struct udadevice *) ui->ui_addr)->udaip;

	if (nosleep) {
		i = mfpr(TODR) + 1000;
		while ((ui->ui_flags & UNIT_ONLINE) == 0)
			if (mfpr(TODR) > i)
				return (-1);
	} else {
		timeout(wakeup, (caddr_t) &ui->ui_flags, 10 * hz);
		sleep((caddr_t) &ui->ui_flags, PRIBIO);
		if ((ui->ui_flags & UNIT_ONLINE) == 0)
			return (-1);
		untimeout(wakeup, (caddr_t) &ui->ui_flags);
	}
	return (0);	/* made it */
}

X/*
 * Queue a transfer request, and if possible, hand it to the controller.
 *
 * This routine is broken into two so that the internal version
 * udastrat1() can be called by the (nonexistent, as yet) bad block
 * revectoring routine.
 */
udastrategy(bp)
	register struct buf *bp;
{
	register int unit;
	register struct uba_device *ui;
	register struct size *st;
	daddr_t sz, maxsz;

	/*
	 * Make sure this is a reasonable drive to use.
	 */
	if ((unit = udaunit(bp->b_dev)) >= NRA ||
	    (ui = udadinfo[unit]) == NULL || ui->ui_alive == 0) {
		bp->b_error = ENXIO;
		bp->b_flags |= B_ERROR;
		biodone(bp);
		return;
	}

	/*
	 * Determine the size of the transfer, and make sure it is
	 * within the boundaries of the drive.
	 */
	sz = (bp->b_bcount + 511) >> 9;
	st = &udatypes[ui->ui_type].ut_sizes[udapart(bp->b_dev)];
	if ((maxsz = st->nblocks) < 0)
		maxsz = ra_dsize[unit] - st->blkoff;
	if (bp->b_blkno < 0 || bp->b_blkno + sz > maxsz ||
	    st->blkoff >= ra_dsize[unit]) {
		/* if exactly at end of disk, return an EOF */
		if (bp->b_blkno == maxsz)
			bp->b_resid = bp->b_bcount;
		else {
			bp->b_error = EINVAL;
			bp->b_flags |= B_ERROR;
		}
		biodone(bp);
		return;
	}
	udastrat1(bp);
}

X/*
 * Work routine for udastrategy.
 */
udastrat1(bp)
	register struct buf *bp;
{
	register int unit = udaunit(bp->b_dev);
	register struct uba_ctlr *um;
	register struct buf *dp;
	struct uba_device *ui;
	int s = spl5();

	/*
	 * Append the buffer to the drive queue, and if it is not
	 * already there, the drive to the controller queue.  (However,
	 * if the drive queue is marked to be requeued, we must be
	 * awaiting an on line or get unit status command; in this
	 * case, leave it off the controller queue.)
	 */
	um = (ui = udadinfo[unit])->ui_mi;
	dp = &udautab[unit];
	APPEND(bp, dp, av_forw);
	if (dp->b_active == 0 && (ui->ui_flags & UNIT_REQUEUE) == 0) {
		APPEND(dp, &um->um_tab, b_forw);
		dp->b_active++;
	}

	/*
	 * Start activity on the controller.  Note that unlike other
	 * Unibus drivers, we must always do this, not just when the
	 * controller is not active.
	 */
	udastart(um);
	splx(s);
}

X/*
 * Start up whatever transfers we can find.
 * Note that udastart() must be called at spl5().
 */
udastart(um)
	register struct uba_ctlr *um;
{
	register struct uda_softc *sc = &uda_softc[um->um_ctlr];
	register struct buf *bp, *dp;
	register struct mscp *mp;
	struct uba_device *ui;
	struct udadevice *udaddr;
	int i;

#ifdef lint
	i = 0; i = i;
#endif
	/*
	 * If it is not running, try (again and again...) to initialise
	 * it.  If it is currently initialising just ignore it for now.
	 */
	if (sc->sc_state != ST_RUN) {
		if (sc->sc_state == ST_IDLE && udainit(um->um_ctlr))
			printf("uda%d: still hung\n", um->um_ctlr);
		return;
	}

	/*
	 * If um_cmd is nonzero, this controller is on the Unibus
	 * resource wait queue.  It will not help to try more requests;
	 * instead, when the Unibus unblocks and calls udadgo(), we
	 * will call udastart() again.
	 */
	if (um->um_cmd)
		return;

	sc->sc_flags |= SC_INSTART;
	udaddr = (struct udadevice *) um->um_addr;

loop:
	/*
	 * Service the drive at the head of the queue.  We take exactly
	 * one transfer from this drive, then move it to the end of the
	 * controller queue, so as to get more drive overlap.
	 */
	if ((dp = um->um_tab.b_actf) == NULL) {
		um->um_tab.b_active = 0;
		goto out;
	}

	/*
	 * Get the first request from the drive queue.  There must be
	 * one, or something is rotten.
	 */
	if ((bp = dp->b_actf) == NULL)
		panic("udastart: bp==NULL\n");

	if (udaddr->udasa & UDA_ERR) {	/* ctlr fatal error */
		udasaerror(um);
		goto out;
	}

	/*
	 * Get an MSCP packet, then figure out what to do.  If
	 * we cannot get a command packet, the command ring may
	 * be too small:  We should have at least as many command
	 * packets as credits, for best performance.
	 */
	if ((mp = mscp_getcp(&sc->sc_mi, MSCP_DONTWAIT)) == NULL) {
		if (sc->sc_mi.mi_credits > MSCP_MINCREDITS &&
		    (sc->sc_flags & SC_GRIPED) == 0) {
			log(LOG_NOTICE, "uda%d: command ring too small\n",
				um->um_ctlr);
			sc->sc_flags |= SC_GRIPED;/* complain only once */
		}
		goto out;
	}

	/*
	 * Mark the controller active, but do not move the drive off
	 * the controller queue: ubago() wants it there.
	 */
	um->um_tab.b_active = 1;

	/*
	 * Bring the drive on line if it is not already.  Get its status
	 * if we do not already have it.  Otherwise just start the transfer.
	 */
	ui = udadinfo[udaunit(bp->b_dev)];
	if ((ui->ui_flags & UNIT_ONLINE) == 0) {
		mp->mscp_opcode = M_OP_ONLINE;
		goto common;
	}
	if ((ui->ui_flags & UNIT_HAVESTATUS) == 0) {
		mp->mscp_opcode = M_OP_GETUNITST;
common:
if (ui->ui_flags & UNIT_REQUEUE) panic("udastart");
		/*
		 * Take the drive off the controller queue.  When the
		 * command finishes, make sure the drive is requeued.
		 */
		um->um_tab.b_actf = dp->b_forw;
		dp->b_active = 0;
		ui->ui_flags |= UNIT_REQUEUE;
		mp->mscp_unit = ui->ui_slave;
		*mp->mscp_seq.seq_addr |= MSCP_OWN | MSCP_INT;
		i = udaddr->udaip;
		goto loop;
	}

	mp->mscp_opcode = (bp->b_flags & B_READ) ? M_OP_READ : M_OP_WRITE;
	mp->mscp_unit = ui->ui_slave;
	mp->mscp_seq.seq_lbn = bp->b_blkno +
		udatypes[ui->ui_type].ut_sizes[udapart(bp->b_dev)].blkoff;
	mp->mscp_seq.seq_bytecount = bp->b_bcount;
	/* mscp_cmdref and mscp_buffer are filled in by mscp_go() */

	/*
	 * Drop the packet pointer into the `command' field so udadgo()
	 * can tell what to start.  If ubago returns 1, we can do another
	 * transfer.  If not, um_cmd will still point at mp, so we will
	 * know that we are waiting for resources.
	 */
	um->um_cmd = (int) mp;
	if (ubago(ui))
		goto loop;

	/*
	 * All done, or blocked in ubago().
	 */
out:
	sc->sc_flags &= ~SC_INSTART;
}

X/*
 * Start a transfer.
 *
 * If we are not called from within udastart(), we must have been
 * blocked, so call udastart to do more requests (if any).  If
 * this calls us again immediately we will not recurse, because
 * that time we will be in udastart().  Clever....
 */
udadgo(um)
	register struct uba_ctlr *um;
{
	struct uda_softc *sc = &uda_softc[um->um_ctlr];
	int i;

#ifdef lint
	i = 0; i = i;
#endif
	/*
	 * Fill in the MSCP packet and move the buffer to the
	 * I/O wait queue.  Mark the controller as no longer on
	 * the resource queue, and initiate polling.
	 */
	mscp_go(um, &sc->sc_mi, (struct mscp *) um->um_cmd);
	um->um_cmd = 0;	
	i = ((struct udadevice *) um->um_addr)->udaip;

	if ((sc->sc_flags & SC_INSTART) == 0)
		udastart(um);
}

X/*
 * The error bit was set in the controller status register.  Gripe,
 * reset the controller, requeue pending transfers.
 */
udasaerror(um)
	register struct uba_ctlr *um;
{

	printf("uda%d: controller error, sa=%b\n", um->um_ctlr,
		((struct udadevice *) um->um_addr)->udasa, udasr_bits);
	mscp_requeue(&uda_softc[um->um_ctlr].sc_mi);
	(void) udainit(um->um_ctlr);
}

X/*
 * Interrupt routine.  Depending on the state of the controller,
 * continue initialisation, or acknowledge command and response
 * interrupts, and process responses.
 */
udaintr(ctlr)
	int ctlr;
{
	register struct uba_ctlr *um = udaminfo[ctlr];
	register struct uda_softc *sc = &uda_softc[ctlr];
	register struct udadevice *udaddr = (struct udadevice *) um->um_addr;
	register struct uda *ud;
	register struct mscp *mp;
	register int i;

	sc->sc_wticks = 0;	/* reset interrupt watchdog */

	/*
	 * Combinations during steps 1, 2, and 3: STEPnMASK
	 * corresponds to which bits should be tested;
	 * STEPnGOOD corresponds to the pattern that should
	 * appear after the interrupt from STEPn initialisation.
	 * All steps test the bits in ALLSTEPS.
	 */
#define	ALLSTEPS	(UDA_ERR | UDA_STEP4 | UDA_STEP3 | UDA_STEP2 | \
			 UDA_STEP1 | UDA_IE)

#define	STEP1MASK	(ALLSTEPS | UDA_NCNRMASK)
#define	STEP1GOOD	(UDA_STEP2 | UDA_IE | (NCMDL2 << 3) | NRSPL2)

#define	STEP2MASK	(ALLSTEPS | UDA_IVECMASK)
#define	STEP2GOOD	(UDA_STEP3 | UDA_IE | (sc->sc_ivec >> 2))

#define	STEP3MASK	ALLSTEPS
#define	STEP3GOOD	UDA_STEP4

	switch (sc->sc_state) {

	case ST_IDLE:
		/*
		 * Ignore unsolicited interrupts.
		 */
		log(LOG_WARNING, "uda%d: stray intr\n", ctlr);
		return;

	case ST_STEP1:
		/*
		 * Begin step two initialisation.
		 */
		if ((udaddr->udasa & STEP1MASK) != STEP1GOOD) {
			i = 1;
initfailed:
			printf("uda%d: init step %d failed, sa=%b\n",
				ctlr, i, udaddr->udasa, udasr_bits);
			sc->sc_state = ST_IDLE;
			if (sc->sc_flags & SC_DOWAKE) {
				sc->sc_flags &= ~SC_DOWAKE;
				wakeup((caddr_t) sc);
			}
			return;
		}
		udaddr->udasa = (int) &sc->sc_uda->uda_ca.ca_rspdsc[0] |
			(cpu == VAX_780 || cpu == VAX_8600 ? UDA_PI : 0);
		sc->sc_state = ST_STEP2;
		return;

	case ST_STEP2:
		/*
		 * Begin step 3 initialisation.
		 */
		if ((udaddr->udasa & STEP2MASK) != STEP2GOOD) {
			i = 2;
			goto initfailed;
		}
		udaddr->udasa = ((int) &sc->sc_uda->uda_ca.ca_rspdsc[0]) >> 16;
		sc->sc_state = ST_STEP3;
		return;

	case ST_STEP3:
		/*
		 * Set controller characteristics (finish initialisation).
		 */
		if ((udaddr->udasa & STEP3MASK) != STEP3GOOD) {
			i = 3;
			goto initfailed;
		}
		i = udaddr->udasa & 0xff;
		if (i != sc->sc_micro) {
			sc->sc_micro = i;
			printf("uda%d: version %d model %d\n",
				ctlr, i & 0xf, i >> 4);
		}

		/*
		 * Present the burst size, then remove it.  Why this
		 * should be done this way, I have no idea.
		 *
		 * Note that this assumes udaburst[ctlr] > 0.
		 */
		udaddr->udasa = UDA_GO | (udaburst[ctlr] - 1) << 2;
		udaddr->udasa = UDA_GO;
		printf("uda%d: DMA burst size set to %d\n",
			ctlr, udaburst[ctlr]);

		udainitds(ctlr);	/* initialise data structures */

		/*
		 * Before we can get a command packet, we need some
		 * credits.  Fake some up to keep mscp_getcp() happy,
		 * get a packet, and cancel all credits (the right
		 * number should come back in the response to the
		 * SCC packet).
		 */
		sc->sc_mi.mi_credits = MSCP_MINCREDITS + 1;
		mp = mscp_getcp(&sc->sc_mi, MSCP_DONTWAIT);
		if (mp == NULL)	/* `cannot happen' */
			panic("udaintr");
		sc->sc_mi.mi_credits = 0;
		mp->mscp_opcode = M_OP_SETCTLRC;
		/*
		 * WHICH OF THE FOLLOWING ARE REQUIRED??
		 * IT SURE WOULD BE NICE IF DEC SOLD DOCUMENTATION
		 * FOR THEIR OWN CONTROLLERS.
		 */
		mp->mscp_unit = 0;
		mp->mscp_flags = 0;
		mp->mscp_modifier = 0;
		mp->mscp_seq.seq_bytecount = 0;
		mp->mscp_seq.seq_buffer = 0;
		mp->mscp_sccc.sccc_errlgfl = 0;
		mp->mscp_sccc.sccc_copyspd = 0;
		mp->mscp_sccc.sccc_ctlrflags = M_CF_ATTN | M_CF_MISC |
			M_CF_THIS;
		*mp->mscp_sccc.sccc_addr |= MSCP_OWN | MSCP_INT;
		i = udaddr->udaip;
		sc->sc_state = ST_SETCHAR;
		return;

	case ST_SETCHAR:
	case ST_RUN:
		/*
		 * Handle Set Ctlr Characteristics responses and operational
		 * responses (via mscp_dorsp).
		 */
		break;

	default:
		printf("uda%d: driver bug, state %d\n", ctlr, sc->sc_state);
		panic("udastate");
	}

	if (udaddr->udasa & UDA_ERR) {	/* ctlr fatal error */
		udasaerror(um);
		return;
	}

	ud = &uda[ctlr];

	/*
	 * Handle buffer purge requests.
	 * I have never seen these to work usefully, thus the log().
	 */
	if (ud->uda_ca.ca_bdp) {
		log(LOG_DEBUG, "uda%d: purge bdp %d\n",
			ctlr, ud->uda_ca.ca_bdp);
		UBAPURGE(um->um_hd->uh_uba, ud->uda_ca.ca_bdp);
		ud->uda_ca.ca_bdp = 0;
		udaddr->udasa = 0;	/* signal purge complete */
	}

	/*
	 * Check for response and command ring transitions.
	 */
	if (ud->uda_ca.ca_rspint) {
		ud->uda_ca.ca_rspint = 0;
		mscp_dorsp(&sc->sc_mi);
	}
	if (ud->uda_ca.ca_cmdint) {
		ud->uda_ca.ca_cmdint = 0;
		MSCP_DOCMD(&sc->sc_mi);
	}
	udastart(um);
}

X/*
 * Initialise the various data structures that control the UDA50.
 */
udainitds(ctlr)
	int ctlr;
{
	register struct uda *ud = &uda[ctlr];
	register struct uda *uud = uda_softc[ctlr].sc_uda;
	register struct mscp *mp;
	register int i;

	for (i = 0, mp = ud->uda_rsp; i < NRSP; i++, mp++) {
		ud->uda_ca.ca_rspdsc[i] = MSCP_OWN | MSCP_INT |
			(long) &uud->uda_rsp[i].mscp_cmdref;
		mp->mscp_seq.seq_addr = &ud->uda_ca.ca_rspdsc[i];
		mp->mscp_msglen = MSCP_MSGLEN;
	}
	for (i = 0, mp = ud->uda_cmd; i < NCMD; i++, mp++) {
		ud->uda_ca.ca_cmddsc[i] = MSCP_INT |
			(long) &uud->uda_cmd[i].mscp_cmdref;
		mp->mscp_seq.seq_addr = &ud->uda_ca.ca_cmddsc[i];
		mp->mscp_msglen = MSCP_MSGLEN;
	}
}

X/*
 * Handle an error datagram.  All we do now is decode it.
 */
udadgram(um, mp)
	struct uba_ctlr *um;
	struct mscp *mp;
{

	mscp_decodeerror(um, mp);
}

X/*
 * The Set Controller Characteristics command finished.
 * Record the new state of the controller.
 */
udactlrdone(um, mp)
	register struct uba_ctlr *um;
	struct mscp *mp;
{
	register struct uda_softc *sc = &uda_softc[um->um_ctlr];

	if ((mp->mscp_status & M_ST_MASK) == M_ST_SUCCESS)
		sc->sc_state = ST_RUN;
	else {
		printf("uda%d: SETCTLRC failed: ",
			um->um_ctlr, mp->mscp_status);
		mscp_printevent(mp);
		sc->sc_state = ST_IDLE;
	}
	um->um_tab.b_active = 0;
	if (sc->sc_flags & SC_DOWAKE) {
		sc->sc_flags &= ~SC_DOWAKE;
		wakeup((caddr_t) sc);
	}
}

X/*
 * Received a response from an as-yet unconfigured drive.  Configure it
 * in, if possible.
 */
udaunconf(um, mp)
	struct uba_ctlr *um;
	register struct mscp *mp;
{

	/*
	 * If it is a slave response, copy it to udaslavereply for
	 * udaslave() to look at.
	 */
	if (mp->mscp_opcode == (M_OP_GETUNITST | M_OP_END) &&
	    (uda_softc[um->um_ctlr].sc_flags & SC_INSLAVE) != 0) {
		udaslavereply = *mp;
		return (MSCP_DONE);
	}

	/*
	 * Otherwise, it had better be an available attention response.
	 */
	if (mp->mscp_opcode != M_OP_AVAILATTN)
		return (MSCP_FAILED);

	/* do what autoconf does */
	return (MSCP_FAILED);	/* not yet, arwhite, not yet */
}

X/*
 * A drive came on line.  Check its type and size.  Return DONE if
 * we think the drive is truly on line.  In any case, awaken anyone
 * sleeping on the drive on-line-ness.
 */
udaonline(ui, mp)
	register struct uba_device *ui;
	struct mscp *mp;
{
	register int type;

	wakeup((caddr_t) &ui->ui_flags);
	if ((mp->mscp_status & M_ST_MASK) != M_ST_SUCCESS) {
		printf("uda%d: attempt to bring ra%d on line failed: ",
			ui->ui_ctlr, ui->ui_unit);
		mscp_printevent(mp);
		return (MSCP_FAILED);
	}

	type = mp->mscp_onle.onle_drivetype;
	if (type >= NTYPES || udatypes[type].ut_name == 0) {
		printf("uda%d: ra%d: unknown type %d\n",
			ui->ui_ctlr, ui->ui_unit, type);
		return (MSCP_FAILED);
	}
#if GYRE			/* special partition hack */
	if (ISCDC(&uda_softc[ui->ui_ctlr]))
		type = CDCTYPE;
#endif
	/*
	 * Note any change of types.  Not sure if we should do
	 * something special about them, or if so, what....
	 */
	if (type != ui->ui_type) {
		printf("ra%d: changed types! was %s\n",
			ui->ui_unit, udatypes[ui->ui_type].ut_name);
		ui->ui_type = type;
	}
	ra_dsize[ui->ui_unit] = (daddr_t) mp->mscp_onle.onle_unitsize;
	printf("ra%d: %s, size = %d sectors\n",
		ui->ui_unit, udatypes[type].ut_name, ra_dsize[ui->ui_unit]);
	return (MSCP_DONE);
}

X/*
 * We got some (configured) unit's status.  Return DONE if it succeeded.
 */
udagotstatus(ui, mp)
	register struct uba_device *ui;
	register struct mscp *mp;
{

	if ((mp->mscp_status & M_ST_MASK) != M_ST_SUCCESS) {
		printf("uda%d: attempt to get status for ra%d failed: ",
			ui->ui_ctlr, ui->ui_unit);
		mscp_printevent(mp);
		return (MSCP_FAILED);
	}
	/* need to record later for bad block forwarding - for now, print */
	printf("\
ra%d: unit %d, nspt %d, group %d, ntpc %d, rctsize %d, nrpt %d, nrct %d\n",
		ui->ui_unit, mp->mscp_unit,
		mp->mscp_guse.guse_nspt, mp->mscp_guse.guse_group,
		mp->mscp_guse.guse_ntpc, mp->mscp_guse.guse_rctsize,
		mp->mscp_guse.guse_nrpt, mp->mscp_guse.guse_nrct);
	return (MSCP_DONE);
}

X/*
 * A transfer failed.  We get a chance to fix or restart it.
 * Need to write the bad block forwaring code first....
 */
X/*ARGSUSED*/
udaioerror(ui, mp, bp)
	register struct uba_device *ui;
	register struct mscp *mp;
	struct buf *bp;
{

	if (mp->mscp_flags & M_EF_BBLKR) {
		/*
		 * A bad block report.  Eventually we will
		 * restart this transfer, but for now, just
		 * log it and give up.
		 */
		log(LOG_ERR, "ra%d: bad block report: %d%s\n",
			ui->ui_unit, mp->mscp_seq.seq_lbn,
			mp->mscp_flags & M_EF_BBLKU ? " + others" : "");
	} else {
		/*
		 * What the heck IS a `serious exception' anyway?
		 * IT SURE WOULD BE NICE IF DEC SOLD DOCUMENTATION
		 * FOR THEIR OWN CONTROLLERS.
		 */
		if (mp->mscp_flags & M_EF_SEREX)
			log(LOG_ERR, "ra%d: serious exception reported\n",
				ui->ui_unit);
	}
	return (MSCP_FAILED);
}

X/*
 * A replace operation finished.
 */
X/*ARGSUSED*/
udareplace(ui, mp)
	struct uba_device *ui;
	struct mscp *mp;
{

	panic("udareplace");
}

X/*
 * A bad block related operation finished.
 */
X/*ARGSUSED*/
udabb(ui, mp, bp)
	struct uba_device *ui;
	struct mscp *mp;
	struct buf *bp;
{

	panic("udabb");
}


X/*
 * Raw read request.
 */
udaread(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register int unit = udaunit(dev);

	if (unit >= NRA)
		return (ENXIO);
	return (physio(udastrategy, &rudabuf[unit], dev, B_READ,
		minphys, uio));
}

X/*
 * Raw write request.
 */
udawrite(dev, uio)
	dev_t dev;
	struct uio *uio;
{
	register int unit = udaunit(dev);

	if (unit >= NRA)
		return (ENXIO);
	return (physio(udastrategy, &rudabuf[unit], dev, B_WRITE,
		minphys, uio));
}

#ifdef notyet
X/*
 * I/O controls.  Not yet!
 */
udaioctl(dev, cmd, flag, data)
	dev_t dev;
	int cmd, flag;
	caddr_t data;
{
	int error = 0;
	register int unit = udaunit(dev);

	if (unit >= NRA || uddinfo[unit] == NULL)
		return (ENXIO);

	switch (cmd) {

	case UDAIOCREPLACE:
		/*
		 * Initiate bad block replacement for the given LBN.
		 * (Should we allow modifiers?)
		 */
		error = EOPNOTSUPP;
		break;

	case UDAIOCGMICRO:
		/*
		 * Return the microcode revision for the UDA50 running
		 * this drive.
		 */
		*(int *) data = uda_softc[uddinfo[unit]->ui_ctlr].sc_micro;
		break;

	case UDAIOCGSIZE:
		/*
		 * Return the size (in 512 byte blocks) of this
		 * disk drive.
		 */
		*(daddr_t *) data = ra_dsize[unit];
		break;

	default:
		error = EINVAL;
		break;
	}
	return (error);
}
#endif

X/*
 * A Unibus reset has occurred on UBA uban.  Reinitialise the controller(s)
 * on that Unibus, and requeue outstanding I/O.
 */
udareset(uban)
	int uban;
{
	register struct uba_ctlr *um;
	register struct uba_device *ui;
	register struct uda_softc *sc;
	register int unit, ctlr;
	struct buf *dp;

	for (ctlr = 0, sc = uda_softc; ctlr < NUDA; ctlr++, sc++) {
		if ((um = udaminfo[ctlr]) == NULL || um->um_ubanum != uban ||
		    um->um_alive == 0)
			continue;
		printf(" uda%d", ctlr);

		/*
		 * Our BDP (if any) is gone; our command (if any) is
		 * flushed; the device is no longer mapped; and the
		 * UDA50 is not yet initialised.
		 */
		if (um->um_bdp) {
			printf("<%d>", (um->um_bdp >> 28) & 15);
			um->um_bdp = 0;
		}
		um->um_ubinfo = 0;
		um->um_cmd = 0;
		sc->sc_flags &= ~SC_MAPPED;
		sc->sc_state = ST_IDLE;

		/* reset queues and requeue pending transfers */
		mscp_requeue(&sc->sc_mi);

		/*
		 * If it fails to initialise we will notice later and
		 * try again (and again...).  Do not call udastart()
		 * here; it will be done after the controller finishes
		 * initialisation.
		 */
		if (udainit(ctlr))
			printf(" (hung)");
	}
}

X/*
 * Watchdog timer:  If the controller is active, and no interrupts
 * have occurred for 30 seconds, assume it has gone away.
 */
udawatch()
{
	register int i, unit;
	register struct uba_ctlr *um;
	register struct uda_softc *sc;

	timeout(udawatch, (caddr_t) 0, hz);	/* every second */
	for (i = 0, sc = uda_softc; i < NUDA; i++, sc++) {
		if ((um = udaminfo[i]) == 0 || um->um_alive == 0)
			continue;
		if (um->um_tab.b_active ||
		    sc->sc_mi.mi_wtab.av_forw != &sc->sc_mi.mi_wtab ||
		    sc->sc_state != ST_IDLE && sc->sc_state != ST_RUN)
			goto active;
		for (unit = 0; unit < NRA; unit++)
			if (udautab[unit].b_active &&
			    udadinfo[unit]->ui_mi == um)
				goto active;
		sc->sc_wticks = 0;
		continue;
active:
		if (++sc->sc_wticks >= 30) {
			sc->sc_wticks = 0;
			printf("uda%d: lost interrupt\n", i);
			ubareset(um->um_ubanum);
		}
	}
}

X/*
 * Do a panic dump.  We set up the controller for one command packet
 * and one response packet, for which we use `struct uda1'.
 */
struct	uda1 {
	struct	uda1ca uda1_ca;	/* communications area */
	struct	mscp uda1_rsp;	/* response packet */
	struct	mscp uda1_cmd;	/* command packet */
} uda1;

#define	DBSIZE	32		/* dump 16K at a time */

udadump(dev)
	dev_t dev;
{
	struct udadevice *udaddr;
	struct uda1 *ud_ubaddr;
	char *start;
	int num, blk, unit, maxsz, blkoff, reg;
	register struct uba_regs *uba;
	register struct uba_device *ui;
	register struct uda1 *ud;
	register struct pte *io;
	register int i;

	/*
	 * Make sure the device is a reasonable place on which to dump.
	 */
	unit = udaunit(dev);
	if (unit >= NRA)
		return (ENXIO);
#define	phys(cast, addr)	((cast) ((int) addr & 0x7fffffff))
	ui = phys(struct uba_device *, udadinfo[unit]);
	if (ui == NULL || ui->ui_alive == 0)
		return (ENXIO);

	/*
	 * Find and initialise the UBA; get the physical address of the
	 * device registers, and of communications area and command and
	 * response packet.
	 */
	uba = phys(struct uba_hd *, ui->ui_hd)->uh_physuba;
	ubainit(uba);
	udaddr = (struct udadevice *) ui->ui_physaddr;
	ud = phys(struct uda1 *, &uda1);

	/*
	 * Map the ca+packets into Unibus I/O space so the UDA50 can get
	 * at them.  Use the registers at the end of the Unibus map (since
	 * we will use the registers at the beginning to map the memory
	 * we are dumping).
	 */
	num = btoc(sizeof (struct uda1)) + 1;
	reg = NUBMREG - num;
	io = &uba->uba_map[reg];
	for (i = 0; i < num; i++)
		*(int *)io++ = UBAMR_MRV | (btop(ud) + i);
	ud_ubaddr = (struct uda1 *) (((int) ud & PGOFSET) | (reg << 9));

	/*
	 * Initialise the controller, with one command and one response
	 * packet.
	 */
	udaddr->udaip = 0;
	if (udadumpwait(udaddr, UDA_STEP1))
		return (EFAULT);
	udaddr->udasa = UDA_ERR;
	if (udadumpwait(udaddr, UDA_STEP2))
		return (EFAULT);
	udaddr->udasa = (int) &ud_ubaddr->uda1_ca.ca_rspdsc;
	if (udadumpwait(udaddr, UDA_STEP3))
		return (EFAULT);
	udaddr->udasa = ((int) &ud_ubaddr->uda1_ca.ca_rspdsc) >> 16;
	if (udadumpwait(udaddr, UDA_STEP4))
		return (EFAULT);
	uda_softc[ui->ui_ctlr].sc_micro = udaddr->udasa & 0xff;
	udaddr->udasa = UDA_GO;

	/*
	 * Set up the command and response descriptor, then set the
	 * controller characteristics and bring the drive on line.
	 * Note that all uninitialised locations in uda1_cmd are zero.
	 */
	ud->uda1_ca.ca_rspdsc = (long) &ud_ubaddr->uda1_rsp.mscp_cmdref;
	ud->uda1_ca.ca_cmddsc = (long) &ud_ubaddr->uda1_cmd.mscp_cmdref;
	/* ud->uda1_cmd.mscp_sccc.sccc_ctlrflags = 0; */
	/* ud->uda1_cmd.mscp_sccc.sccc_version = 0; */
	if (udadumpcmd(M_OP_SETCTLRC, ud, ui))
		return (EFAULT);
	ud->uda1_cmd.mscp_unit = ui->ui_slave;
	if (udadumpcmd(M_OP_ONLINE, ud, ui))
		return (EFAULT);

	/*
	 * Pick up the drive type from the on line end packet;
	 * convert that to a dump area size and a disk offset.
	 */
	i = ud->uda1_rsp.mscp_onle.onle_drivetype;
	if (i >= NTYPES || udatypes[i].ut_name == 0) {
		printf("disk type %d unknown\ndump ");
		return (EINVAL);
	}
#if GYRE
	if (ISCDC(&uda_softc[ui->ui_ctlr]))
		i = CDCTYPE;
#endif
	printf("on %s ", udatypes[i].ut_name);
	maxsz = udatypes[i].ut_sizes[udapart(dev)].nblocks;
	blkoff = udatypes[i].ut_sizes[udapart(dev)].blkoff;

	/*
	 * Dump all of physical memory, or as much as will fit in the
	 * space provided.
	 */
	start = 0;
	num = maxfree;
	if (dumplo < 0)
		return (EINVAL);
	if (dumplo + num >= maxsz)
		num = maxsz - dumplo;
	blkoff += dumplo;

	/*
	 * Write out memory, DBSIZE pages at a time.
	 * N.B.: this code depends on the fact that the sector
	 * size == the page size.
	 */
	while (num > 0) {
		blk = num > DBSIZE ? DBSIZE : num;
		io = uba->uba_map;
		/*
		 * Map in the pages to write, leaving an invalid entry
		 * at the end to guard against wild Unibus transfers.
		 * Then do the write.
		 */
		for (i = 0; i < blk; i++)
			*(int *) io++ = UBAMR_MRV | (btop(start) + i);
		*(int *) io = 0;
		ud->uda1_cmd.mscp_unit = ui->ui_slave;
		ud->uda1_cmd.mscp_seq.seq_lbn = btop(start) + blkoff;
		ud->uda1_cmd.mscp_seq.seq_bytecount = blk << PGSHIFT;
		if (udadumpcmd(M_OP_WRITE, ud, ui))
			return (EIO);
		start += blk << PGSHIFT;
		num -= blk;
	}
	return (0);		/* made it! */
}

X/*
 * Wait for some of the bits in `bits' to come on.  If the error bit
 * comes on, or ten seconds pass without response, return true (error).
 */
udadumpwait(udaddr, bits)
	register struct udadevice *udaddr;
	register int bits;
{
	register int timo = mfpr(TODR) + 1000;

	while ((udaddr->udasa & bits) == 0) {
		if (udaddr->udasa & UDA_ERR) {
			printf("udasa=%b\ndump ", udaddr->udasa, udasr_bits);
			return (1);
		}
		if (mfpr(TODR) >= timo) {
			printf("timeout\ndump ");
			return (1);
		}
	}
	return (0);
}

X/*
 * Feed a command to the UDA50, wait for its response, and return
 * true iff something went wrong.
 */
udadumpcmd(op, ud, ui)
	int op;
	register struct uda1 *ud;
	struct uba_device *ui;
{
	register struct udadevice *udaddr;
	register int n;
#define mp (&ud->uda1_rsp)

	udaddr = (struct udadevice *) ui->ui_physaddr;
	ud->uda1_cmd.mscp_opcode = op;
	ud->uda1_cmd.mscp_msglen = MSCP_MSGLEN;
	ud->uda1_rsp.mscp_msglen = MSCP_MSGLEN;
	ud->uda1_ca.ca_rspdsc |= MSCP_OWN | MSCP_INT;
	ud->uda1_ca.ca_cmddsc |= MSCP_OWN | MSCP_INT;
	if (udaddr->udasa & UDA_ERR) {
		printf("udasa=%b\ndump ", udaddr->udasa, udasr_bits);
		return (1);
	}
	n = udaddr->udaip;
	n = mfpr(TODR) + 1000;
	for (;;) {
		if (mfpr(TODR) > n) {
			printf("timeout\ndump ");
			return (1);
		}
		if (ud->uda1_ca.ca_cmdint)
			ud->uda1_ca.ca_cmdint = 0;
		if (ud->uda1_ca.ca_rspint == 0)
			continue;
		ud->uda1_ca.ca_rspint = 0;
		if (mp->mscp_opcode == (op | M_OP_END))
			break;
		printf("\n");
		switch (MSCP_MSGTYPE(mp->mscp_msgtc)) {

		case MSCPT_SEQ:
			printf("sequential");
			break;

		case MSCPT_DATAGRAM:
			mscp_decodeerror(ui->ui_mi, mp);
			printf("datagram");
			break;

		case MSCPT_CREDITS:
			printf("credits");
			break;

		case MSCPT_MAINTENANCE:
			printf("maintenance");
			break;

		default:
			printf("unknown (type 0x%x)",
				MSCP_MSGTYPE(mp->mscp_msgtc));
			break;
		}
		printf(" ignored\ndump ");
		ud->uda1_ca.ca_rspdsc |= MSCP_OWN | MSCP_INT;
	}
	if ((mp->mscp_status & M_ST_MASK) != M_ST_SUCCESS) {
		printf("error: op 0x%x => 0x%x status 0x%x\ndump ", op,
			mp->mscp_opcode, mp->mscp_status);
		return (1);
	}
	return (0);
#undef mp
}

X/*
 * Return the size of a partition, if known, or -1 if not.
 */
udasize(dev)
	dev_t dev;
{
	register int unit = udaunit(dev);
	register struct uba_device *ui;
	register struct size *st;

	if (unit >= NRA || (ui = udadinfo[unit]) == NULL || ui->ui_alive == 0)
		return (-1);
	st = &udatypes[ui->ui_type].ut_sizes[udapart(dev)];
	if (st->nblocks == -1) {
		int s = spl5();

		/*
		 * We need to have the drive on line to find the size
		 * of this particular partition.
		 * IS IT OKAY TO GO TO SLEEP IN THIS ROUTINE?
		 * (If not, better not page on one of these...)
		 */
		if ((ui->ui_flags & UNIT_ONLINE) == 0) {
			if (uda_bringonline(&uda_softc[unit], ui, 0)) {
				splx(s);
				return (-1);
			}
		}
		splx(s);
		if (st->blkoff > ra_dsize[unit])
			return (-1);
		return (ra_dsize[unit] - st->blkoff);
	}
	return (st->nblocks);
}
#endif
//go.sysin dd *
if [ `wc -c < vaxuba/uda.c` != 48872 ]; then
	made=FALSE
	echo error transmitting vaxuba/uda.c --
	echo length should be 48872, not `wc -c < vaxuba/uda.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vaxuba/uda.c
	echo -n '	'; ls -ld vaxuba/uda.c
fi
echo Extracting vaxuba/udareg.h
sed 's/^X//' <<'//go.sysin dd *' >vaxuba/udareg.h
X/*
 * UDA50 registers and structures
 */

X/*
 * Writing any value to udaip starts initialisation.  Reading from it
 * when the UDA is running makes the UDA look through the command ring
 * to find any new commands.  Reading udasa gives status; writing it
 * during initialisation sets things up.
 */
struct udadevice {
	u_short	udaip;		/* initialisation and polling */
	u_short	udasa;		/* status and address */
};

X/*
 * Bits in UDA status register during initialisation
 */
#define	UDA_ERR		0x8000	/* error */
#define	UDA_STEP4	0x4000	/* step 4 has started */
#define	UDA_STEP3	0x2000	/* step 3 has started */
#define	UDA_STEP2	0x1000	/* step 2 has started */
#define	UDA_STEP1	0x0800	/* step 1 has started */
#define	UDA_NV		0x0400	/* no host settable interrupt vector */
#define	UDA_QB		0x0200	/* controller supports Q22 bus */
#define	UDA_DI		0x0100	/* controller implements diagnostics */
#define	UDA_IE		0x0080	/* interrupt enable */
#define	UDA_NCNRMASK	0x003f	/* in STEP1, bits 0-2=NCMDL2, 3-5=NRSPL2 */
#define	UDA_IVECMASK	0x007f	/* in STEP2, bits 0-6 are interruptvec / 4 */
#define	UDA_PI		0x0001	/* host requests adapter purge interrupts */

X/*
 * Bits in UDA status register after initialisation
 */
#define	UDA_GO		0x0001	/* run */

#define	UDASR_BITS \
"\20\20ERR\17STEP4\16STEP3\15STEP2\14STEP1\13NV\12QB\11DI\10IE\1GO"

X/*
 * UDA Communications Area.  Note that this structure definition
 * requires NRSP and NCMD to be defined already.
 */
struct udaca {
	short	ca_xxx1;	/* unused */
	char	ca_xxx2;	/* unused */
	char	ca_bdp;		/* BDP to purge */
	short	ca_cmdint;	/* command ring transition flag */
	short	ca_rspint;	/* response ring transition flag */
	long	ca_rspdsc[NRSP];/* response descriptors */
	long	ca_cmddsc[NCMD];/* command descriptors */
};

X/*
 * Simplified routines (e.g., uddump) reprogram the UDA50 for one command
 * and one response at a time; uda1ca is like udaca except that it provides
 * exactly one command and response descriptor.
 */
struct uda1ca {
	short	ca_xxx1;
	char	ca_xxx2;
	char	ca_bdp;
	short	ca_cmdint;
	short	ca_rspint;
	long	ca_rspdsc;
	long	ca_cmddsc;
};
//go.sysin dd *
if [ `wc -c < vaxuba/udareg.h` != 2112 ]; then
	made=FALSE
	echo error transmitting vaxuba/udareg.h --
	echo length should be 2112, not `wc -c < vaxuba/udareg.h`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 vaxuba/udareg.h
	echo -n '	'; ls -ld vaxuba/udareg.h
fi
made=TRUE
if [ $made = TRUE ]; then
	chmod 755 vaxuba
	echo -n '	'; ls -ld vaxuba
fi
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7690)
UUCP:	seismo!mimsy!chris	ARPA/CSNet:	chris@mimsy.umd.edu