/*
 *	SCCS id	@(#)xp.c	2.1 (Berkeley)	8/23/83
 *	This driver has been modified to perform bad-sector forwarding.
 *	It has not been tested after that modification.
 *	If you want to use it, install it instead of the normal xp driver,
 *	AND TEST BOTH BAD-SECTOR FORWARDING AND ECC CORRECTION.
 *	Also, if you are using XP_PROBE (xpslave determines drive type),
 *	you will have to force the selection of the number of cylinders for RP's
 *	to RP06_CYL or RP04_CYL, assuming that you have only one or the other,
 *	or else punt.
 */
#define	HP_CYL	RP06_CYL

/*
 *	RM02/03/05, RP04/05/06, DIVA disk driver.
 *	SI Eagle added 9/20/83.
 *	This driver will handle most variants of SMD drives
 *	on one or more controllers.
 *	If XP_PROBE is defined, it includes a probe routine
 *	that will determine the number and type of drives attached
 *	to each controller; otherwise, the data structures must be
 *	initialized.
 *
 *	For simplicity we use hpreg.h instead of an xpreg.h.  The bits
 *	are the same.
 */

#include "xp.h"
#if	NXP > 0
#include "param.h"
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/dir.h>
#include <sys/user.h>
#include <sys/seg.h>
#include <sys/hpreg.h>
#ifndef	INTRLVE
#include <sys/inline.h>
#endif
#ifdef	UNIBUS_MAP
#include <sys/uba.h>
#endif
#include <sys/dkbad.h>

#define	XP_SDIST	2
#define	XP_RDIST	6

int	xp_offset[] =
{
	HPOF_P400,	HPOF_M400,	HPOF_P400,	HPOF_M400,
	HPOF_P800,	HPOF_M800,	HPOF_P800,	HPOF_M800,
	HPOF_P1200,	HPOF_M1200,	HPOF_P1200,	HPOF_M1200,
	0,		0,		0,		0
};

/*
 *  Xp_drive and xp_controller may be initialized in ioconf.c,
 *  or they may be filled in at boot time if XP_PROBE is enabled.
 *  Xp_controller address fields must be initialized for any boot devices,
 *  however.
 */
struct	xp_drive xp_drive[NXP];
struct	xp_controller xp_controller[NXP_CONTROLLER];

struct	buf	xptab;
struct	buf	rxpbuf[NXP];
struct	buf	xputab[NXP];
#ifdef BADSECT
struct	dkbad	xpbad[NXP];
struct	buf	bxpbuf[NXP];
bool_t	xp_init[NXP];
#endif

#ifdef	INTRLVE
extern	daddr_t	dkblock();
#endif

/*
 *  Attach controllers whose addresses are known at boot time.
 *  Stop at the first not found, so that the drive numbering
 *  won't get confused.
 */
xproot()
{
	register i;
	register struct hpdevice *xpaddr;

	for (i = 0; i < NXP_CONTROLLER; i++)
		if (((xpaddr = xp_controller[i].xp_addr) == 0)
		    || (xpattach(xpaddr, i) == 0))
			break;
}

/*
 *  Attach controller at xpaddr.
 *  Mark as nonexistent if xpaddr is 0;
 *  otherwise attach slaves if probing.
 *  NOTE: if probing for drives, this routine must be called
 *  once per controller, in ascending controller numbers.
 */
xpattach(xpaddr, unit)
register struct hpdevice *xpaddr;
{
	register struct xp_controller *xc = &xp_controller[unit];
#ifdef	XP_PROBE
	static int last_attached = -1;
#endif

	if ((unsigned) unit >= NXP_CONTROLLER)
		return(0);
	if ((xpaddr != 0) && (fioword(xpaddr) != -1)) {
		xc->xp_addr = xpaddr;
#if	PDP11 == 70 || PDP11 == GENERIC
		if (fioword(&(xpaddr->hpbae)) != -1)
			xc->xp_flags |= XP_RH70;
#endif
#ifdef	XP_PROBE
		/*
		 *  If already attached, ignore (don't want to renumber drives)
		 */
		if (unit > last_attached) {
			last_attached = unit;
			xpslave(xpaddr, xc);
		}
#endif
		return(1);
	}
	xc->xp_addr = 0;
	return(0);
}

#ifdef	XP_PROBE
/*
 *  Determine what drives are attached to a controller;
 *  guess their types and fill in the drive structures.
 */
xpslave(xpaddr, xc)
register struct hpdevice *xpaddr;
struct xp_controller *xc;
{
	register struct xp_drive *xd;
	int j,  dummy;
	static int nxp = 0;
	extern	struct size dv_sizes[], hp_sizes[], rm_sizes[], rm5_sizes[], si_sizes[];

	for (j = 0; j < 8; j++) {
		xpaddr->hpcs1.w = 0;
		xpaddr->hpcs2.w = j;
		dummy = xpaddr->hpds;
		if (xpaddr->hpcs2.w & HPCS2_NED) {
			xpaddr->hpcs2.w = HPCS2_CLR;
			continue;
		}
		if (nxp < NXP) {
			xd = &xp_drive[nxp++];
			xd->xp_ctlr = xc;
			xd->xp_unit = j;
			/*
			 * If drive type is initialized,
			 * believe it.
			 */
			if (xd->xp_type == 0)
				xd->xp_type = xpaddr->hpdt & 077;
			switch (xd->xp_type) {

			case RM02:
			case RM03:
				xd->xp_nsect = RM_SECT;
				xd->xp_ntrack = RM_TRAC;
				xd->xp_nspc = RM_SECT * RM_TRAC;
				xd->xp_ncyl = RM_CYL;
				xd->xp_sizes = &rm_sizes;
				xd->xp_ctlr->xp_flags |= XP_NOCC;
				break;

			case RM5X:
				xd->xp_ncyl = RM5X_CYL;
				goto rm5;
			case RM05:
				xd->xp_ncyl = RM5_CYL;
			rm5:
				if ((xpaddr->hpsn & SI_SN_MSK) == SI_SN_DT) {
				    xd->xp_nsect = SI_SECT;
				    xd->xp_ntrack = SI_TRAC;
				    xd->xp_ncyl = SI_CYL;
				    xd->xp_nspc = SI_SECT * SI_TRAC;
				    xd->xp_sizes = &si_sizes;
				    xd->xp_ctlr->xp_flags |= XP_NOCC;
				} else {
				    xd->xp_nsect = RM5_SECT;
				    xd->xp_ntrack = RM5_TRAC;
				    xd->xp_nspc = RM5_SECT * RM5_TRAC;
				    xd->xp_sizes = &rm5_sizes;
				    xd->xp_ctlr->xp_flags |= XP_NOCC;
				}
				break;

			case RP:
				xd->xp_nsect = HP_SECT;
				xd->xp_ntrack = HP_TRAC;
				xd->xp_nspc = HP_SECT * HP_TRAC;
				xd->xp_ncyl = HP_CYL;
				xd->xp_sizes = &hp_sizes;
				break;

			case DV:
				xd->xp_nsect = DV_SECT;
				xd->xp_ntrack = DV_TRAC;
				xd->xp_nspc = DV_SECT * DV_TRAC;
				xd->xp_ncyl = DV_CYL;
				xd->xp_sizes = &dv_sizes;
				xd->xp_ctlr->xp_flags |= XP_NOSEARCH;
				break;

			default:
			    printf("xp%d: drive type %o unrecognized\n",
				nxp - 1, xd->xp_type);
				xd->xp_ctlr = NULL;
				break;
			}
		}
	}
}
#endif	XP_PROBE

xpstrategy(bp)
register struct buf *bp;
{
	register struct xp_drive *xd;
	register unit;
	struct buf *dp;
	short	pseudo_unit;
	int	s;
	long	bn;

	unit = dkunit(bp);
	pseudo_unit = minor(bp->b_dev) & 07;

	if ((unit >= NXP) || ((xd = &xp_drive[unit])->xp_ctlr == 0) ||
	   (xd->xp_ctlr->xp_addr == 0)) { 
		bp->b_error = ENXIO;
		goto errexit;
	}
	if ((bp->b_blkno < 0) ||
	   ((bn = dkblock(bp)) + ((bp->b_bcount + 511) >> 9)
	     > xd->xp_sizes[pseudo_unit].nblocks)) {
		bp->b_error = EINVAL;
errexit:
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
#ifdef	UNIBUS_MAP
	if ((xd->xp_ctlr->xp_flags & XP_RH70) == 0)
		mapalloc(bp);
#endif	UNIBUS_MAP
	bp->b_cylin = bn / xd->xp_nspc
		+ xd->xp_sizes[pseudo_unit].cyloff;
	dp = &xputab[unit];
	s = spl5();
	disksort(dp, bp);
	if (dp->b_active == 0) {
		xpustart(unit);
		if (xd->xp_ctlr->xp_active == 0)
			xpstart(xd->xp_ctlr);
	}
	splx(s);
}

/*
 * Unit start routine.
 * Seek the drive to where the data are
 * and then generate another interrupt
 * to actually start the transfer.
 * If there is only one drive
 * or we are very close to the data, don't
 * bother with the search.  If called after
 * searching once, don't bother to look
 * where we are, just queue for transfer (to avoid
 * positioning forever without transferring).
 */
xpustart(unit)
int unit;
{
	register struct xp_drive *xd;
	register struct hpdevice *xpaddr;
	register struct buf *dp;
	struct	buf *bp;
	daddr_t	bn;
	int	sn, cn, csn;

	xd = &xp_drive[unit];
	xpaddr = xd->xp_ctlr->xp_addr;
	xpaddr->hpcs2.w = xd->xp_unit;
	xpaddr->hpcs1.c[0] = HP_IE;
	xpaddr->hpas = 1 << xd->xp_unit;

	if (unit >= NXP)
		return;
#ifdef	XP_DKN
	dk_busy &= ~(1 << (unit + XP_DKN));
#endif
	dp = &xputab[unit];
	if ((bp=dp->b_actf) == NULL)
		return;
	/*
	 * If we have already positioned this drive,
	 * then just put it on the ready queue.
	 */
	if (dp->b_active)
		goto done;
	dp->b_active++;
	/*
	 * If drive has just come up,
	 * set up the pack.
	 */
#ifdef BADSECT
	if (((xpaddr->hpds & HPDS_VV) == 0) || (xp_init[unit] == 0))
#else
	if ((xpaddr->hpds & HPDS_VV) == 0)
#endif
	{
#ifdef BADSECT
		struct buf *bbp = &bxpbuf[unit];
#endif
		/* SHOULD WARN SYSTEM THAT THIS HAPPENED */
		xpaddr->hpcs1.c[0] = HP_IE | HP_PRESET | HP_GO;
		xpaddr->hpof = HPOF_FMT22;
#ifdef BADSECT
		xp_init[unit] = 1;
		bbp->b_flags = B_READ | B_BUSY | B_PHYS;
		bbp->b_dev = bp->b_dev;
		bbp->b_bcount = sizeof(struct dkbad);
		bbp->b_un.b_addr = (caddr_t)&xpbad[unit];
		bbp->b_blkno = (daddr_t)xd->xp_ncyl*xd->xp_nspc - xd->xp_nsect;
		bbp->b_cylin = xd->xp_ncyl - 1;
#ifdef	UNIBUS_MAP
		if ((xd->xp_ctlr->xp_flags & XP_RH70) == 0)
			mapalloc(bbp);
#endif	UNIBUS_MAP
		dp->b_actf = bbp;
		bbp->av_forw = bp;
		bp = bbp;
#endif	BADSECT
	}
#if	NXP > 1
	/*
	 * If drive is offline, forget about positioning.
	 */
	if ((xpaddr->hpds & (HPDS_DPR | HPDS_MOL)) != (HPDS_DPR | HPDS_MOL))
		goto done;

	/*
	 * Figure out where this transfer is going to
	 * and see if we are close enough to justify not searching.
	 */
	bn = dkblock(bp);
	cn = bp->b_cylin;
	sn = bn % xd->xp_nspc;
	sn += xd->xp_nsect - XP_SDIST;
	sn %= xd->xp_nsect;

	if (((xd->xp_ctlr->xp_flags & XP_NOCC) && (xd->xp_cc != cn))
	    || xpaddr->hpcc != cn)
		goto search;
	if (xd->xp_ctlr->xp_flags & XP_NOSEARCH)
		goto done;
	csn = (xpaddr->hpla >> 6) - sn + XP_SDIST - 1;
	if (csn < 0)
		csn += xd->xp_nsect;
	if (csn > xd->xp_nsect - XP_RDIST)
		goto done;

search:
	xpaddr->hpdc = cn;
	xpaddr->hpda = sn;
	xpaddr->hpcs1.c[0] = (xd->xp_ctlr->xp_flags & XP_NOSEARCH)?
		(HP_IE | HP_SEEK | HP_GO) : (HP_IE | HP_SEARCH | HP_GO);
	xd->xp_cc = cn;
#ifdef	XP_DKN
	/*
	 * Mark unit busy for iostat.
	 */
	unit += XP_DKN;
	dk_busy |= 1 << unit;
	dk_numb[unit]++;
#endif	XP_DKN
	return;
#endif	NXP > 1

done:
	/*
	 * Device is ready to go.
	 * Put it on the ready queue for the controller.
	 */
	dp->b_forw = NULL;
	if (xd->xp_ctlr->xp_actf == NULL)
		xd->xp_ctlr->xp_actf = dp;
	else
		xd->xp_ctlr->xp_actl->b_forw = dp;
	xd->xp_ctlr->xp_actl = dp;
}

/*
 * Start up a transfer on a controller.
 */
xpstart(xc)
register struct xp_controller *xc;
{
	register struct hpdevice *xpaddr;
	register struct buf *bp;
	struct xp_drive *xd;
	struct	buf *dp;
	int	unit;
	short	pseudo_unit;
	daddr_t	bn;
	int	sn, tn, cn;

	xpaddr = xc->xp_addr;
loop:
	/*
	 * Pull a request off the controller queue.
	 */
	if ((dp = xc->xp_actf) == NULL)
		return;
	if ((bp = dp->b_actf) == NULL) {
		xc->xp_actf = dp->b_forw;
		goto loop;
	}
	/*
	 * Mark controller busy and
	 * determine destination of this request.
	 */
	xc->xp_active++;
	pseudo_unit = minor(bp->b_dev) & 07;
	unit = dkunit(bp);
	xd = &xp_drive[unit];
	bn = dkblock(bp);
	cn = bp->b_cylin;
	sn = bn % xd->xp_nspc;
	tn = sn / xd->xp_nsect;
	sn = sn % xd->xp_nsect;

	/*
	 * Select drive.
	 */
	xpaddr->hpcs2.w = xd->xp_unit;
	/*
	 * Check that it is ready and online.
	 */
	if ((xpaddr->hpds & (HPDS_DPR | HPDS_MOL)) != (HPDS_DPR | HPDS_MOL)) {
		xc->xp_active = 0;
		dp->b_errcnt = 0;
		dp->b_actf = bp->av_forw;
		bp->b_flags |= B_ERROR;
		iodone(bp);
		goto loop;
	}
	if (dp->b_errcnt >= 16 && (bp->b_flags & B_READ)) {
		xpaddr->hpof = xp_offset[dp->b_errcnt & 017] | HPOF_FMT22;
		xpaddr->hpcs1.w = HP_OFFSET | HP_GO;
		while ((xpaddr->hpds & (HPDS_PIP | HPDS_DRY)) != HPDS_DRY)
			;
	}
	xpaddr->hpdc = cn;
	xpaddr->hpda = (tn << 8) + sn;
	xpaddr->hpba = bp->b_un.b_addr;
#if	PDP11 == 70 || PDP11 == GENERIC
	if (xc->xp_flags & XP_RH70)
		xpaddr->hpbae = bp->b_xmem;
#endif
	xpaddr->hpwc = -(bp->b_bcount >> 1);

	/*
	 * Warning:  unit is being used as a temporary.
	 */
	unit = ((bp->b_xmem & 3) << 8) | HP_IE | HP_GO;
#ifdef	XP_FORMAT
	if (minor(bp->b_dev) & 0200)
		unit |= bp->b_flags & B_READ? HP_RHDR : HP_WHDR;
	else
		unit |= bp->b_flags & B_READ? HP_RCOM : HP_WCOM;
#else
	if (bp->b_flags & B_READ)
		unit |= HP_RCOM;
	else
		unit |= HP_WCOM;
#endif
	xpaddr->hpcs1.w = unit;

#ifdef	XP_DKN
	unit = xc - &xp_controller[0] + XP_DKN + NXP;
	dk_busy |= 1 << unit;
	dk_numb[unit]++;
	dk_wds[unit] += bp->b_bcount >> 6;
#endif
}

/*
 * Handle a disk interrupt.
 */
xpintr(dev)
int dev;
{
	register struct	hpdevice *xpaddr;
	register struct buf *dp;
	struct xp_controller *xc;
	struct xp_drive *xd;
	struct	buf *bp;
	register unit;
	int	as, i, j;

	xc = &xp_controller[dev];
	xpaddr = xc->xp_addr;
	as = xpaddr->hpas & 0377;
	if (xc->xp_active) {
#ifdef	XP_DKN
		dk_busy &= ~(1 << (dev + XP_DKN + NXP));
#endif
		/*
		 * Get device and block structures.  Select the drive.
		 */
		dp = xc->xp_actf;
		bp = dp->b_actf;
#ifdef BADSECT
		if (bp->b_flags&B_BAD)
			if (xpecc(bp, CONT))
				return;
#endif
		unit = dkunit(bp);
		xd = &xp_drive[unit];
		xpaddr->hpcs2.c[0] = xd->xp_unit;
		/*
		 * Check for and process errors.
		 */
		if (xpaddr->hpcs1.w & HP_TRE) {
			while ((xpaddr->hpds & HPDS_DRY) == 0)
				;
			if (xpaddr->hper1 & HPER1_WLE) {
				/*
				 *	Give up on write locked deviced
				 *	immediately.
				 */
				printf("xp%d: write locked\n", unit);
				bp->b_flags |= B_ERROR;
#ifdef	BADSECT
			} else if ((xpaddr->rmer2 & RMER2_BSE)
			    || (xpaddr->hper1 & HPER1_FER)) {
#ifdef	XP_FORMAT
				/*
				 * Allow this error on format devices.
				 */
				if (minor(bp->b_dev) & 0200)
					goto errdone;
#endif
				if (xpecc(bp, BSE))
					return;
				else
					goto hard;
#endif	BADSECT
			} else {
				/*
				 * After 28 retries (16 without offset and
				 * 12 with offset positioning), give up.
				 */
				if (++dp->b_errcnt > 28) {
hard:
#ifdef	UCB_DEVERR
				    harderr(bp, "xp");
				    printf("cs2=%b er1=%b\n", xpaddr->hpcs2.w,
					HPCS2_BITS, xpaddr->hper1, HPER1_BITS);
#else
				    deverror(bp, xpaddr->hpcs2.w, xpaddr->hper1);
#endif
				    bp->b_flags |= B_ERROR;
				} else
				    xc->xp_active = 0;
			}
#ifdef UCB_ECC
			/*
			 * If soft ecc, correct it (continuing
			 * by returning if necessary).
			 * Otherwise, fall through and retry the transfer.
			 */
			if((xpaddr->hper1 & (HPER1_DCK|HPER1_ECH)) == HPER1_DCK)
				if (xpecc(bp, ECC))
					return;
#endif
errdone:
			xpaddr->hpcs1.w = HP_TRE | HP_IE | HP_DCLR | HP_GO;
			if ((dp->b_errcnt & 07) == 4) {
				xpaddr->hpcs1.w = HP_RECAL | HP_IE | HP_GO;
				while ((xpaddr->hpds & (HPDS_PIP | HPDS_DRY)) != HPDS_DRY)
					;
			}
			xd->xp_cc = -1;
		}
		if (xc->xp_active) {
			if (dp->b_errcnt) {
				xpaddr->hpcs1.w = HP_RTC | HP_GO;
				while ((xpaddr->hpds & (HPDS_PIP | HPDS_DRY)) != HPDS_DRY)
					;
			}
			xc->xp_active = 0;
			xc->xp_actf = dp->b_forw;
			dp->b_active = 0;
			dp->b_errcnt = 0;
			dp->b_actf = bp->b_actf;
			xd->xp_cc = bp->b_cylin;
			bp->b_resid = - (xpaddr->hpwc << 1);
			iodone(bp);
			xpaddr->hpcs1.w = HP_IE;
			if (dp->b_actf)
				xpustart(unit);
		}
		as &= ~(1 << xp_drive[unit].xp_unit);
	} else
		{
		if (as == 0)
			xpaddr->hpcs1.w = HP_IE;
		xpaddr->hpcs1.c[1] = HP_TRE >> 8;
	}
	for (unit = 0; unit < NXP; unit++)
		if ((xp_drive[unit].xp_ctlr == xc) && 
			(as & (1 << xp_drive[unit].xp_unit)))
				xpustart(unit);
	xpstart(xc);
}

xpread(dev)
dev_t	dev;
{
	physio(xpstrategy, &rxpbuf[(minor(dev) >> 3) & 07], dev, B_READ);
}

xpwrite(dev)
dev_t	dev;
{
	physio(xpstrategy, &rxpbuf[(minor(dev) >> 3) & 07], dev, B_WRITE);
}


#ifdef	UCB_ECC
#define	exadr(x,y)	(((long)(x) << 16) | (unsigned)(y))

/*
 * Correct an ECC error and restart the i/o to complete
 * the transfer if necessary.  This is quite complicated because
 * the correction may be going to an odd memory address base
 * and the transfer may cross a sector boundary.
 */
xpecc(bp, flag)
register struct	buf *bp;
{
	register struct xp_drive *xd;
	register struct	hpdevice *xpaddr;
	register unsigned byte;
	ubadr_t	bb, addr;
	long	wrong;
	int	bit, wc;
	unsigned ndone, npx;
	int	ocmd;
	int	cn, tn, sn;
	daddr_t	bn;
#ifdef	UNIBUS_MAP
	struct	ubmap *ubp;
#endif
	int	unit;

	/*
	 *	ndone is #bytes including the error
	 *	which is assumed to be in the last disk page transferred.
	 */
	unit = dkunit(bp);
	xd = &xp_drive[unit];
	xpaddr = xd->xp_ctlr->xp_addr;
#ifdef	BADSECT
	if (flag == CONT) {
		npx = bp->b_error;
		bp->b_error = 0;
		ndone = npx * PGSIZE;
		wc = ((int)(ndone - bp->b_bcount)) / NBPW;
	} else
#endif
	{
		wc = xpaddr->hpwc;
		ndone = (wc * NBPW) + bp->b_bcount;
		npx = ndone / PGSIZE;
	}
	ocmd = (xpaddr->hpcs1.w & ~HP_DRY) | HP_IE | HP_GO;
	bb = exadr(bp->b_xmem, bp->b_un.b_addr);
	bn = dkblock(bp);
	cn = bp->b_cylin - (bn / xd->xp_nspc);
	bn += npx;
	cn += bn / xd->xp_nspc;
	sn = bn % xd->xp_nspc;
/*	tn = sn / xd->xp_nsect;  /* CC complains about this ??? */
	tn = sn;
	tn /= xd->xp_nsect;
	sn %= xd->xp_nsect;

	switch (flag) {
	case ECC:
		printf("xp%d%c: soft ecc bn %D\n",
			unit, 'a' + (minor(bp->b_dev) & 07),
			bp->b_blkno + (npx - 1));
		wrong = xpaddr->hpec2;
		if (wrong == 0) {
			xpaddr->hpof = HPOF_FMT22;
			xpaddr->hpcs1.w |= HP_IE;
			return (0);
		}

		/*
		 *	Compute the byte/bit position of the err
		 *	within the last disk page transferred.
		 *	Hpec1 is origin-1.
		 */
		byte = xpaddr->hpec1 - 1;
		bit = byte & 07;
		byte >>= 3;
		byte += ndone - PGSIZE;
		wrong <<= bit;

		/*
		 *	Correct until mask is zero or until end of transfer,
		 *	whichever comes first.
		 */
		while (byte < bp->b_bcount && wrong != 0) {
			addr = bb + byte;
#ifdef	UNIBUS_MAP
			if (bp->b_flags & (B_MAP|B_UBAREMAP)) {
				/*
				 * Simulate UNIBUS map if UNIBUS transfer.
				 */
				ubp = UBMAP + ((addr >> 13) & 037);
				addr = exadr(ubp->ub_hi, ubp->ub_lo)
				    + (addr & 017777);
			}
#endif
			putmemc(addr, getmemc(addr) ^ (int) wrong);
			byte++;
			wrong >>= 8;
		}
		break;
#ifdef BADSECT
	case BSE:
		if ((bn = isbad(&xpbad[unit], cn, tn, sn)) < 0)
			return(0);
		bp->b_flags |= B_BAD;
		bp->b_error = npx + 1;
		bn = (daddr_t)xd->xp_ncyl * xd->xp_nspc - xd->xp_nsect - 1 - bn;
		cn = bn/xd->xp_nspc;
		sn = bn%xd->xp_nspc;
	/*	tn = sn/xd->xp_nsect; /* CC can't hack this */
		tn = sn;
		tn /= xd->xp_nsect;
		sn %= xd->xp_nsect;
#ifdef DEBUG
		printf("revector to cn %d tn %d sn %d\n", cn, tn, sn);
#endif
		wc = -(512 / NBPW);
		break;

	case CONT:
		bp->b_flags &= ~B_BAD;
#ifdef DEBUG
		printf("xpecc CONT: bn %D cn %d tn %d sn %d\n", bn, cn, tn, sn);
#endif
		break;
#endif	BADSECT
	}

	xd->xp_ctlr->xp_active++;
	if (wc == 0)
		return (0);

	/*
	 * Have to continue the transfer.  Clear the drive
	 * and compute the position where the transfer is to continue.
	 * We have completed npx sectors of the transfer already.
	 */
	xpaddr->hpcs2.w = xd->xp_unit;
	xpaddr->hpcs1.w = HP_TRE | HP_DCLR | HP_GO;

	addr = bb + ndone;
	xpaddr->hpdc = cn;
	xpaddr->hpda = (tn << 8) + sn;
	xpaddr->hpwc = wc;
	xpaddr->hpba = (int) addr;
#if	PDP11 == 70 || PDP11 == GENERIC
	if (xd->xp_ctlr->xp_flags & XP_RH70)
		xpaddr->hpbae = (int) (addr >> 16);
#endif
	xpaddr->hpcs1.w = ocmd;
	return (1);
}
#endif	UCB_ECC

#if	defined(XP_DUMP) && defined(UCB_AUTOBOOT)
/*
 *  Dump routine.
 *  Dumps from dumplo to end of memory/end of disk section for minor(dev).
 *  It uses the UNIBUS map to dump all of memory if there is a UNIBUS map
 *  and this isn't an RH70.  This depends on UNIBUS_MAP being defined.
 */

#ifdef	UNIBUS_MAP
#define	DBSIZE	(UBPAGE/PGSIZE)		/* unit of transfer, one UBPAGE */
#else
#define DBSIZE	16			/* unit of transfer, same number */
#endif

xpdump(dev)
dev_t dev;
{
	/* ONLY USE 2 REGISTER VARIABLES, OR C COMPILER CHOKES */
	register struct xp_drive *xd;
	register struct hpdevice *xpaddr;
	daddr_t	bn, dumpsize;
	long	paddr;
	int	sn, count;
#ifdef	UNIBUS_MAP
	extern	bool_t ubmap;
	struct ubmap *ubp;
#endif

	if ((bdevsw[major(dev)].d_strategy != xpstrategy)	/* paranoia */
	    || ((dev=minor(dev)) > (NXP << 3)))
		return(EINVAL);
	xd = &xp_drive[dev >> 3];
	dev &= 07;
	if (xd->xp_ctlr == 0)
		return(EINVAL);
	xpaddr = xd->xp_ctlr->xp_addr;
	dumpsize = xd->xp_sizes[dev].nblocks;
	if ((dumplo < 0) || (dumplo >= dumpsize))
		return(EINVAL);
	dumpsize -= dumplo;

	xpaddr->hpcs2.w = xd->xp_unit;
	if ((xpaddr->hpds & HPDS_VV) == 0) {
		xpaddr->hpcs1.w = HP_DCLR | HP_GO;
		xpaddr->hpcs1.w = HP_PRESET | HP_GO;
		xpaddr->hpof = HPOF_FMT22;
	}
	if ((xpaddr->hpds & (HPDS_DPR | HPDS_MOL)) != (HPDS_DPR | HPDS_MOL))
		return(EFAULT);
#ifdef	UNIBUS_MAP
	ubp = &UBMAP[0];
#endif
	for (paddr = 0L; dumpsize > 0; dumpsize -= count) {
		count = dumpsize>DBSIZE? DBSIZE: dumpsize;
		bn = dumplo + (paddr >> PGSHIFT);
		xpaddr->hpdc = bn / xd->xp_nspc
			+ xd->xp_sizes[dev].cyloff;
		sn = bn % xd->xp_nspc;
		xpaddr->hpda = ((sn / xd->xp_nsect) << 8) | (sn % xd->xp_nsect);
		xpaddr->hpwc = -(count << (PGSHIFT - 1));
#ifdef	UNIBUS_MAP
		/*
		 *  If UNIBUS_MAP exists, use
		 *  the map, unless on an 11/70 with RH70.
		 */
		if (ubmap && ((xd->xp_ctlr->xp_flags & XP_RH70) == 0)) {
			ubp->ub_lo = loint(paddr);
			ubp->ub_hi = hiint(paddr);
			xpaddr->hpba = 0;
			xpaddr->hpcs1.w = HP_WCOM | HP_GO;
		}
		else
#endif
			{
			/*
			 *  Non-UNIBUS map, or 11/70 RH70 (MASSBUS)
			 */
			xpaddr->hpba = loint(paddr);
#if	PDP11 == 70 || PDP11 == GENERIC
			if (xd->xp_ctlr->xp_flags & XP_RH70)
				xpaddr->hpbae = hiint(paddr);
#endif
			xpaddr->hpcs1.w = HP_WCOM | HP_GO | ((paddr >> 8) & (03 << 8));
		}
		while (xpaddr->hpcs1.w & HP_GO)
			;
		if (xpaddr->hpcs1.w & HP_TRE) {
			if (xpaddr->hpcs2.w & HPCS2_NEM)
				return(0);	/* made it to end of memory */
			return(EIO);
		}
		paddr += (DBSIZE << PGSHIFT);
	}
	return(0);		/* filled disk minor dev */
}
#endif	XP_DUMP
#endif	NXP
