static char rcsid[] = "$Header: pib.c,v 820.1 86/12/04 19:56:07 root Exp $";
static char sccsid[]="@(#)pib.c	1.6 (Valid) Release 7.25 3/20/85";

/************************************************************************
*									*
*				Copyright 1984				*
*			VALID LOGIC SYSTEMS INCORPORATED		*
*									*
*	This listing contains confidential proprietary information	*
*	which is not to be disclosed to unauthorized persons without	*
*	written consent of an officer of Valid Logic Systems 		*
*	Incorporated.							*
*									*
*	The copyright notice appearing above is included to provide	*
*	statutory protection in the event of unauthorized or 		*
*	unintentional public disclosure.				*
*									*
************************************************************************/

/*	Valid Peripheral Board:  Board-dependent code			*/
/*
 * Notes:
 *	This file implements the board routines for the IBM pib.
 *	It is not known by cdevsw[]; rather, it communicates with
 *	the rest of the operating system through the higher level
 *	routines in vpb.c.
 *	The VAX version of the operating system uses the vp.c to
 *	implement PIB files.
 *	This file comes from versions 4.4(6/13/83) and 6.7(10/15/83).
 *	I was erroneously given a 4.4-version driver and did a lot
 *	of work on it before discovering the error; rather than
 *	starting over, I just merged the later driver in.
 *
 * Known features:
 *	No particular checking is done by pibcp on the return value
 *	of uiomove.  In particular, uiomove can return an EFAULT
 *	(bad user address) and pibcp will believe that the i/o
 *	succeeded.
 *	FIX: This can be fixed trivially by a small mod to pibcp.
 *	2-4-84: fixed this one (sbs)
 *
 *	The 8086 board code makes available to the Versatec a command/
 *	data and waits for an interrupt from the Versatec; when it
 *	receives one, it rings the UNIX doorbell, and the napping writer
 *	gets a wakeup().  If the Versatec is not plugged in, or is
 *	turned off, the napper will sleep a long time.
 *	FIX: The timeout structure, implemented for RSCS, might work.
 *	Must check the board code first, though, to see if RSCS is
 *	special-cased.
 *
 *	To restate the obvious, having this many drivers is dubious
 *	practice, and certainly very confusing.
 *
 * Steven Sargent 2-3-84
 */
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/buf.h"
#include "../h/conf.h"
#include "../h/proc.h"
#include "../h/file.h"		/* mdh */
#include "../h/uio.h"		/* sbs */
#include "../machine/cpu.h"	/* sbs */
#include "../s32/debug.h"
#include "../s32dev/useful.h"
#include "../s32dev/regmuck.h"
#include "../h/kernel.h"	/* jam */

#define PIBMNBIT 5
#define PIBMNMSK (1<<PIBMNBIT)-1
#define PIBSHBIT 0
#define PIBSHMSK (1<<PIBSHBIT)-1
#define NBD	1

typedef struct {
	short	pdev;			/* physical device of this minor*/
	short	rw;			/* permitted mode		*/
	long	lowctl, highctl;	/* bounds of permitted ioctls	*/
} pdtentry;

#define	VERS	0
#define SERA	1
#define SERB	2
#define DR11	3
#define	DIAGN	4
#define NODVC	5
#define MAXREAL	4

/* versatec device numbers */
#define VERSPLOT    12
#define VERSPRINT   28
#define DR11DEV     11

#include "../s32dev/vpb.h"

byte pdvcflg[PIBMNMSK+1];

static pdtentry pdevtbl[(1<<PIBMNBIT)] = {
	/* Describe each minor device: */
	{ SERA, VPBWRITE, 0, 0 },	/* 00 - 07 Polled serial A */
	{ SERA, VPBWRITE, 0, 0 },
	{ SERA, VPBWRITE, 0, 0 },
	{ SERA, VPBWRITE, 0, 0 },
	{ SERA, VPBWRITE, 0, 0 },
	{ SERA, VPBWRITE, 0, 0 },
	{ SERA, VPBWRITE, 0, 0 },
	{ SERA, VPBWRITE, 0, 0 },
	{ SERA, VPBWRITE, RSCS, RSCS },	/* 08 Link serial A */
	{ SERA, VPBREAD, 0, 0},		/* 09 Internal link device	*/
	{ SERA, VPBWRITE, 0, 0 },	/* 0A Asynch exclusive A	*/
	{ DR11, VPBREAD|VPBWRITE, 0, 0 },/* 0B DR-11			*/
	{ VERS, VPBREAD|VPBWRITE, VRSFMFD, VRSFLUSH },/* 0C Versatec Plot mode*/
	{ NODVC, -1, 0, 0},	/* 0D - 0F Reserved		*/
	{ NODVC, -1, 0, 0},
	{ NODVC, -1, 0, 0},
	{ SERB, VPBWRITE, 0, 0 },	/* 10 - 17 Polled serial B */
	{ SERB, VPBWRITE, 0, 0 },
	{ SERB, VPBWRITE, 0, 0 },
	{ SERB, VPBWRITE, 0, 0 },
	{ SERB, VPBWRITE, 0, 0 },
	{ SERB, VPBWRITE, 0, 0 },
	{ SERB, VPBWRITE, 0, 0 },
	{ SERB, VPBWRITE, 0, 0 },
	{ SERB, VPBWRITE, RSCS, RSCS },	/* 18 Link serial B		*/
	{ SERB, VPBREAD, 0, 0},		/* 19 Internal link input device*/
	{ SERB, VPBWRITE, 0, 0 },	/* 1A Asynch exclusive B	*/
	{ DR11, VPBWRITE, 0, 0 },	/* 1B DR-11 control path	*/
	{ VERS, VPBWRITE, VRSFMFD, VRSLTER },	/*1C Versatec Print mode*/
	{ NODVC, -1, 0, 0},	/* 1D - 1E Reserved		*/
	{ NODVC, -1, 0, 0},
	{ DIAGN, -1, 0, 0}

};	/* 1F Diagnostic, load device	*/


extern struct bdt pibdt;
char pibreload;		/* generation */
extern char *vgHardCopyMem;      /* Physical Address of hard copy memory */

/* 
 * "posn" records position in current record. Multibus address of 
 * next byte and amount of free space.	
 */
typedef struct {
	byte	*next;   /* Next available byte in current record */
	short	free,   /* Number of free bytes available        */
		cmd; 
} posn;


/* 
 * The "record" structure is the unmapped memory structure containing 
 * data being communicated between the 8086 and the 68000.  The typedef 
 * describes only the header, but "len" bytes of data follow.  Note, that 
 * all addresses are physical addresses, and addresses and 
 * lengths are stored in 68000 word format.		
 */
typedef	struct rec {
	struct rec *next;      /* Pointer to the next record structure */
	short       len,       /* Number of bytes of data		   */
		    cmd;       /* Command 				   */
} record;

/*
 * The "rlist" structure describes the mapped memory structure describing
 * a list of (unmapped) records.  The cells vtophead and vtoptail are 
 * initialized to the physical address of the cells head and tail, 
 * respectively, and are used as constants in comparing for end-of-chain 
 * conditions. (This avoids the overhead of repeatedly calling vtop during 
 * data transfers).  "rlists" are initialized by "initq".  Lock is the cell 
 * used to serialize updates of the chain.  On the 68000 side, the 
 * assembler language routine "tas" does the locking.  "cur" is used solely 
 * by the 68000 code, and represents the record incompletely
 * filled or emptied.
 */
typedef struct {
	/* Note: these byte definitions are reversed in the 8086 code */

	byte    flags, 
		lock, 
		xnum, 
		thrsh;
	record	*head, 
		*vtophead, 
		*tail, 
		*vtoptail;
} rlist;

typedef struct {
	byte	status, 
		mode;
	short	timeout, 
		maxcmd, 
		obufnm, 
		ibufnm, 
		reclen;
	byte	*bufptr;
	rlist	infree, 
		in, 
		out, 
		outfree;
	posn	cur;

} comm;

typedef struct {
	int	coff[MAXREAL];
	short	bugflg, 
		baudrt, 
		PAreserved[6];
	comm	c[MAXREAL];
} PA;

#define GOBUG 0x0001
#define INTPEND 0x8001


#define DEQWT	0x80
#define ENQPST	0x40

#define SPINCOUNT 300	/* Try for lock using tas this many times       */

/* External procedures  */
extern	boolean tas();	/* Arg is address of lock			*/
extern caddr_t iosetmap(); /* Arg is phys address; returns virtual address */
extern	regmuck();	/* Tweaks a physical address (bit on/off, etc.	*/
extern	byte *valaddr(); /* Check board addr/range; return real addr/-8 */
extern	errlog();	/* Log error to console				*/
extern	vtop();		/* virtual-to-physical translator		*/

/*  Awaken the butler on the pib.  These magical numbers have meaning
 *  to the on-board routines.
 */
#define RING_PIBS_DOORBELL(mbaddr) \
		regmuck(BYSET, (mbaddr) + INTPEND, &pibdt.ring.value)

/*********************************************************************
 * The procedure "enqrec"  places the record described by (next,len) 
 * on the tail of the list anchored by *listp.
 *********************************************************************/
enqrec(listp, next, len, cmd, bp)
register rlist *listp;	/* List to which this record should be appended.*/
byte         *next;	/* Real address of 1st empty byte in record	*/
int           len;	/* Length used in record			*/
int           cmd;	/* Command (or transaction type.)		*/
struct bdata *bp;	/* Board to post if threshold reached		*/
{
	register int	i,
		x;
	byte	lval;
	byte	*l,
		*r;
	register record	*v,
		*end;


DODEBUG(D_PIB,("enqrec: listp=0x%X,next=0x%X,len=%d,cmd=%d,bp=0x%X,lock=0x%X\n",
	    listp,next,len,cmd,bp,listp->lock));

	/* Attempt to lock the rlist so we can do the record transaction
	 */
	x = spl6();
	for(i=0; i<SPINCOUNT; i++) {
		if (tas(&listp->lock)) 
			break;
		lval = listp->lock;
	}
	if (i==SPINCOUNT) {
		printf("***no enq done: lval=0x%x, i=%d\n",lval,i);
		DODEBUG(D_PIB,("enqrec: returns\n"));
		splx(x);
		return;
	}

	/* Increment the transaction count	*/
	(listp->xnum)++;	

	/* Form the record's start addr */
	r = next - len - (sizeof(struct rec));

	/* Don't count on *listp being accessible with r*/
	end = listp->vtoptail;	

        v = (record *)iosetmap(r);	
        v->next = end;	/* Initialize its "next" ptr as tail cell	*/
        v->len  = len;
        v->cmd  = cmd;
        DODEBUG(D_PIB,("enqrec2: v=0x%x, end=0x%x, r=0x%x\n",v,end,r));
        iounsetmap(r);

	/* update next field of tail */
        v = (record *)iosetmap(l=(byte *)listp->tail);  
        v->next = (record *)r;	
        DODEBUG(D_PIB,("enqrec3: v=0x%x, l=0x%x\n",v,l));
        iounsetmap(l);

	listp->tail=(record *)r;	/* New tail record		*/

	/* Post the fact that we are available if somebody is waiting
	 * on this q.
	 */
	if ((listp->flags & DEQWT) && (listp->xnum >= listp->thrsh)) {
		listp->flags &= ~DEQWT;
		listp->flags |= ENQPST;
		listp->lock=0;		
		RING_PIBS_DOORBELL(bp->boardMba);
	} else
		listp->lock=0;		/* Relinquish lock		*/

	splx(x);
	DODEBUG(D_PIB,("enqrec: returns\n"));
}


/*********************************************************************
 * The "deqrec" procedure removes the top record from (*listp). Place 
 * real at of 1st data byte, length of data and command in (*hp).
 *********************************************************************/
deqrec(listp, hp)
	register rlist *listp;
	register posn  *hp;
{
	register short	i,
		len,
		cmd;
	int	x;
	byte	lval;
	byte	*r;
	record	*n,
		*v;

DODEBUG(D_PIB,("deqrec: listp=0x%X,hp=0x%X,lock=0x%X\n",listp,hp,listp->lock));

	/* Attempt to get lock -- sbs why isn't this done with getlock??? */
	x = spl6();
	for(i=0; i<SPINCOUNT; i++) {
		if (tas(&listp->lock)) 
			break;
		lval = listp->lock;
	}
	if (i==SPINCOUNT) {
		printf("***no deq done: lval=0x%x, i=%d\n", lval, i);
		splx(x);
		return(FALSE);
	}

	/* We now have the lock and may update the list */
	if ((r = (byte *)listp->head) != (byte *)listp->vtoptail) { 
		/* There is a record available.  Snag the puppy.
		 */
		(listp->xnum)--;

		/* Fetch the parameters -- do it this way so we only
		 * use one scratch segment
		 */
		v   = (record *)iosetmap(r);
		n   = v->next;
		len = v->len;
		cmd = v->cmd;
		iounsetmap(r);

		listp->head = n;
		hp->next    = r + len + sizeof(struct rec);
		hp->free    = len;
		hp->cmd     = cmd;

		if ((byte *)listp->tail == r) 
			listp->tail = listp->vtophead;

		/* clear wakeup signal	*/
		listp->flags &= ~ENQPST;
		i = TRUE;
	} else {		/* No avail records.  Set waiting flag */
		listp->flags |= DEQWT;	
		i = FALSE;
	}

	listp->lock = 0;		/* release the lock */
	splx(x);			/* ... and restore old spl */

	DODEBUG(D_PIB,("deqrec rtrn: n=0x%X, len=%d, cmd=%d, return=%d\n",
		    n,len,cmd,i));
	return(i);
}



/******************************************************************* 
 * initq: Initialize an rlist.
 *******************************************************************/
byte *
initq(listp, bufp, buflen, bufnum, bp)
	register rlist	*listp;
	register byte	*bufp;
	int	bufnum, 
		buflen;
	struct bdata *bp;
{ 
	register int i;

DODEBUG(D_PIB,("initq: listp=0x%x, bufp=0x%x, buflen=0x%x, bufnum=0x%x\n",
	            listp, bufp, buflen, bufnum));

	listp->head = listp->vtoptail 
		= (record *)(vtop(&listp->tail) & 0xFFFFFF);
	listp->tail = listp->vtophead 
		= (record *)(vtop(&listp->head) & 0xFFFFFF);

	listp->thrsh = 0;	/* For now */
	listp->xnum  = 0;
	listp->lock  = 0;
	listp->flags = 0;

	for (i=0; i<bufnum; i++) {
		enqrec(listp,bufp + sizeof(struct rec), 0, 0, bp);
		bufp += sizeof(struct rec) + buflen;
	}
	DODEBUG(D_PIB,("initq: bufp returned as 0x%x\n",bufp));
	return(bufp);
}


/* After a sleep, or when we are first called, we must establish
 * addressing to the 8086 shared data area: do so w/ this macro.
 */
#define MAPIN_PIB(pa, cp, hp) \
	pa  = (PA *)iosetmap(bp->privMba); \
	cp = &(pa->c[pdevtbl[mnrdvc].pdev]); \
	hp = &(cp->cur);

#define UNMAP_PIB() \
	iounsetmap(bp->privMba)

comm *
wdeqrec(mnrdvc,bp,cp,q)
register struct bdata *bp;
register comm *cp;
register rlist *q;
{
	register posn *hp= &cp->cur;
	register int j=spl6();		
	register PA *pa;
	while (!deqrec(q, hp)) {	
		register char pibgen=pibreload;
		UNMAP_PIB(); sleep(hp, PPIB); MAPIN_PIB(pa, cp, hp);
		if (pibgen!=pibreload) {cp=0; break;}
	}
	splx(j);
	return(cp);
}

/**********************************************************************
 * pibstat(mnrdvc, bp, flag)
 *	"flag" is mask to 'and' with status, replacing current status.
 *	Get status from minor device "mnrdvc" on board pointed to by"bp".
 *	Original status is returned as an "int".  Nonzero status is error.
 *	If "flag" == 0:	Clear the device's 'status'.
 **********************************************************************/
pibstat(mnrdvc, bp, flag)
struct	bdata	*bp;
{
	register comm	*cp;		/* the comm struct for this dev */
	register posn	*hp;		/* ??? */
        register PA	*pa;	/* temp to overcome bug in C compiler */
	register int	j;
	if (bp->privMba) {/* real PIB; get status */
		MAPIN_PIB(pa,cp,hp);
		j=cp->status; cp->status &= flag;
		UNMAP_PIB();
		return(j);
	}
	return(0);	/* then really shouldn't be calling here */
}

/**********************************************************************
 * pibio(mnrdvc, bp, flag, uio)
 *	Do I/O on real board "bp" (using minor "mnrdvc").  "Flag" is
 *	VPBREAD or VPBWRITE depending on the direction desired.
 *	All I/O is mediated by the "uio" structure.
 **********************************************************************/
pibio(mnrdvc, bp, flag, uio)
struct	bdata	*bp;
struct	uio	*uio;
{
	register int	retval = 0,	/* u.u_errno */
		left,			/* number left this transaction */
		count;			/* number per call to pibcp() */
	register comm	*cp;		/* the comm struct for this dev */
	register posn	*hp;		/* ??? */
        register PA *pa;	/* temp to overcome bug in C compiler */

	if (mnrdvc==VERSPLOT) {
		if (flag==VPBREAD) return(hcmread(mnrdvc,uio));
		else 
			if (pibstat(mnrdvc, bp, ~0)) return(EIO);
			return(hcmwrite(mnrdvc,uio));
	}
        if (0==(flag&pdevtbl[mnrdvc].rw)) return(EINVAL);  /* necessary? */
	left=count=uio->uio_resid;
	MAPIN_PIB(pa, cp, hp);
	while (left>0 && retval==0) {
		DODEBUG(D_PIB,(
		  "pibio/%s: cp=0x%X, hp=0x%X, uio=0x%X, left=%d, free=%d\n",
			   ((flag==VPBREAD) ? "read" : "write"),
			   cp, hp, uio, left, hp->free));
		if (cp->status) {retval=RSCS_ERRBASE|(0xFF&cp->status); break;}
		if (hp->free==0) {/* No data(read)/no space(write) */
			if (0==(cp=wdeqrec(mnrdvc,bp,cp,(flag==VPBREAD) ?
				&cp->in : &cp->outfree))) {retval=ENXIO; break;}
			hp= &cp->cur;
			if (flag==VPBWRITE) hp->free=cp->reclen;
			else if (hp->cmd!=CMDXFER) break;
		}
		/*  We have a record: read/write depending on the flag. */
		if (flag==VPBREAD) {
			if (count<hp->free) retval=INPWLN;
			else {
				/*retval = (retval == 0)? hp->free:INPTMO;*/
				if (retval) retval=INPTMO; /* sbs */
				count=hp->free;
			}
			retval = pibcp(hp->next-count, count, VPBREAD, uio);
			left=0; /* read data from only one record at a time */
			enqrec(&cp->infree,hp->next-hp->free,0,CMDXFER,bp);
			hp->free  = 0;
		} else {/* flag==VPBWRITE */
			count=(left>hp->free) ? hp->free : left;
			retval = pibcp(hp->next, count, VPBWRITE, uio);
			left-=count; hp->next+=count; hp->free-=count;
			if (hp->free==0) /* full buffer */
				enqrec(&cp->out,hp->next,cp->reclen,CMDXFER,bp);
		}
	}
	UNMAP_PIB();
	DODEBUG(D_PIB,("pibio returns 0x%X\n", retval));
	return(retval);
}



/* "Youuu raaaaaaaannnggg?"
 *		-- Lurch
 *
 *  Standard interrupt service: wake up the nappers and clear the bell.
 *  The surprise is that we cannot clear the interrupt ourselves (!).
 *  Instead, we ring the pib's doorbell; the pib lowers the interrupt,
 *  presumably on its own sweet time.
 */
pibint()
{ 
	register int i;
	register struct bdata *bp;

	for (bp= &pibdt.b[0], i=NBD; --i>=0; bp++) if (bp->privMba) {
		register PA *pa=(PA *)iosetmap(bp->privMba);
		DODEBUG(D_PIB,("pibint: brd=%d, pa=0x%x, flg=0x%x\n",
						NBD-1-i, pa, pa->bugflg));
		if (pa->bugflg&INTPEND) {/* Wakeup waiting processes */
			register int j;
			register comm *cp;
			pa->bugflg&=~INTPEND;
			for (cp= &pa->c[0],j=MAXREAL; --j>=0; cp++)
			if (ENQPST&(cp->outfree.flags|cp->in.flags)) {
				DODEBUG(D_PIB,("pib wake %x\n",MAXREAL-1-j));
				wakeup(&cp->cur);
			}
		}
		RING_PIBS_DOORBELL(bp->boardMba);  /* tell pib */
		iounsetmap(bp->privMba);
	}
}

/* wake everybody up on a reload */
pibwake(bp)
struct bdata *bp;
{
	register PA *pa=(PA *)iosetmap(bp->privMba);
	register int j;
	register comm *cp;

	++pibreload;
	for (cp= &pa->c[0],j=MAXREAL; --j>=0; cp++) wakeup(&cp->cur);
	iounsetmap(bp->privMba);	/* set and unset must come in pairs */
}

/* dr11 file name arrays */
char vmsname[128];
static char cname[30];
static char *vmsdevname="SYS$DISK:";

vmsfname(ap)
register char *ap;
{
	register char *vmsp=vmsname+1;  /* leave first byte for mode */
	register char *cp;
	register short c;
	register short i;
	short int haveBracket = 0; /* help with VMS name construction */
	short int haveDev = 0;	 /* help with VMS name construction */

	*vmsp=0;
	DODEBUG(D_PIB,("vpopen/dr11: path = %s\n",ap));
	/* Process path name  
	"/dev/vms/[vaxnode::/][vaxdisk:/]vaxdir/vaxdir/file.extension"
	*/
	for (c=1,i=2; --i>=0; ) {/* get past "/dev/vms" */
		if (c!='\0') while ((c=fubyte(ap++))=='/');
		if (c!='\0') while ((c=fubyte(ap++))  && c!='/');
	}
	/* Termination condition ap points to the unix style VMS name */
	ap--;
	if (c==0) return(EINVAL); /* empty vms name */
	while (c!=0) {
		int inquote;
		cp=cname;
		while ('/'==(c=fubyte(ap++))); /* Gobble Slashes */
		/* Gobble name (directory or file) */
		for (inquote=0,i=30; c!=0 && --i>=0; *cp++=c,c=fubyte(ap++)) {
			if (inquote) {if (c=='"') inquote=0; continue;}
			else if (c=='"') inquote=1;
			else if (c=='/' || c=='.' || c==':') break;
			else if ((c>='a' && c<='z') ||
				 (c>='A' && c<='Z') ||
				 (c>='0' && c<='9') ||
				 (c=='$' || c=='_') ) continue;
			else return(EINVAL); /* non-VMS char */
		}
		*cp=0;
		if (c=='/') {/* cname contains a directory */
			if (!haveDev++) {/* Add VMS default device */
				cp=vmsdevname; while (*vmsp++= *cp++); --vmsp;
			}
			if (!haveBracket++) *vmsp++='[';
			else                *vmsp++='.';
			cp=cname; while (*vmsp++= *cp++); --vmsp;
		} else if (c==':') {/* cname is node or device */
			if (haveDev) return(EINVAL); /* non-VMS char */
			*cp++=c; c=fubyte(ap);
			if (c==':') {*cp++=c; ap++; /* node */}
			else haveDev++; /* dev */
			*cp=0;
			cp=cname; while (*vmsp++= *cp++); --vmsp;
			while ('/'==(c=fubyte(ap++))); /* Gobble Slashes */
			ap--;
		} else if (c=='.' || c==0) {
			if (!haveDev++) {/* Add VMS default device */
				cp=vmsdevname; while (*vmsp++= *cp++); --vmsp;
				*vmsp++='[';
			}
			/* cname is a file name. "." begins an extension */
			*vmsp++=']';
			cp=cname; while (*vmsp++= *cp++); --vmsp;
			*vmsp++='.'; break;
		} else return(EINVAL); /* non-VMS char */
	}
	/* add the extension */
	for (i=3; c!=0 && --i>=0; ) {
		c=fubyte(ap++); if (c=='/' || c=='.') break;
		*vmsp++=c;
	}
	*vmsp++=0;
	return(0);
}

/* pibopen(mnrdvc, bp, flag, uio)
 *	Open device "mnrdvc" on board "bp" with the flag "flag."
 *	(READ/WRITE).  The declaration of uio is just to match
 *	that in (*doio[x])(minor, bp, flag, uio) -- it is never
 *	used.
 */
pibopen(mnrdvc, bp, flag, junkuio)
	struct bdata *bp;
	struct uio	*junkuio;			/* ARGSUSED3 */
{  
	register int j;
	register comm	*cp;
	register posn	*hp;
	register PA *pa;
	struct iovec iov;
	struct uio uio;

        if (0==(flag&pdevtbl[mnrdvc].rw)) return(E_BADARG);
	uio.uio_resid=0;  /* no filename on most minor devices */
	if (mnrdvc==VERSPLOT || mnrdvc==VERSPRINT) {
		if (j=hcmopen(mnrdvc,
		    ( (flag&VPBWRITE)?FWRITE: ((flag&VPBREAD)?FREAD:flag) )) )
			return(j);
	} else if (mnrdvc==DR11DEV) {/* hold onto your hats! */
		struct args {char *path;};
		register char *p;
		if (j=vmsfname(((struct args *)u.u_ap)->path)) return(j);
		iov.iov_base=p=vmsname;
		*p++=flag; while (*p++);
		iov.iov_len=uio.uio_resid=(--p)-vmsname;
		uio.uio_iov= &iov;
		uio.uio_iovcnt=1;
		uio.uio_offset=0;
		uio.uio_segflg=1;  /* kernel address for vmsname */
	}
	MAPIN_PIB(pa, cp, hp);
	DODEBUG(D_PIB,("pibopen: mnrdvc=0x%x,bp=0x%x,i=0x%x,j=%d\n",
						mnrdvc, bp, pa, j));
	if (cp->mode>=PIBMNMSK) {/* initialize device q's on first open */
		register byte *bufarea;
		/* convert board buffer address to mbaddr */
		if (0>=(bufarea=valaddr(&pibdt,bp,cp->bufptr,
				   (cp->reclen+sizeof(struct rec)) * 
				   (cp->ibufnm + cp->obufnm)))) {
			UNMAP_PIB();
			errlog(&pibdt,bp,E_BADCOM,OPEN);
			return(EINVAL);
		}
		bufarea=initq(&cp->in ,bufarea,0,0,bp);
		bufarea=initq(&cp->out,bufarea,0,0,bp);
		cp->out.flags=DEQWT;
		bufarea=initq(&cp->infree ,bufarea,cp->reclen,cp->ibufnm,bp);
		bufarea=initq(&cp->outfree,bufarea,cp->reclen,cp->obufnm,bp);
	}
	cp->mode=mnrdvc; cp->status=0; hp= &(cp->cur);

	/* Get a record to inform the pib that it is "open." */
	if (0==(cp=wdeqrec(mnrdvc,bp,cp,&cp->outfree))) {
		UNMAP_PIB(); return(ENXIO);
	}
	hp= &cp->cur;

	/* Say OPEN */
	if (j=uio.uio_resid) {/* file name for vms */
		/* leave 2 bytes before name: 1 byte of mode, 1 byte of junk */
		pibcp(hp->next+1,j,VPBWRITE,&uio);
		++j; hp->next+=j; hp->free-=j;
	}
	enqrec(&cp->out,hp->next,j,CMDOPEN,bp);	hp->free=0;
	RING_PIBS_DOORBELL(bp->boardMba);
	UNMAP_PIB();
	if (j) {/* see if vms open succeeded */
		iov.iov_base=vmsname; iov.iov_len=uio.uio_resid=2;
		uio.uio_iov= &iov; uio.uio_iovcnt=1; uio.uio_offset=0;
		uio.uio_segflg=1;  /* kernel address for vmsname */
		if (j=pibio(mnrdvc,bp,VPBREAD,&uio)) return(j); /* protocol */
		if (vmsname[0]!='V' || vmsname[1]!='A') return(ENOENT);
	}

	if (mnrdvc==VERSPLOT && flag==VPBWRITE)
		pibctl(mnrdvc,bp,VRSBLOCKPLOT,uio);
	return(0);
}


/* pibclose(mnrdvc, bp, flag, uio)
 *	Close minor "mnrdvc" on board "bp".
 *	"f" and "uio" are unused (just here to match the call)
 *
 * There is no "last close" action to match the "first open" action --
 * is this why dynamic reloading of pibs seems to thrash?  (sbs)
 */
pibclose(mnrdvc, bp, flag, uio)
	struct bdata *bp;
	struct uio	 *uio;		/* ARGSUSED2 */
{
	register int retval,j;
	register comm *cp;
	register posn *hp;
	register PA *pa;

	if (0==(flag&pdevtbl[mnrdvc].rw)) return (EINVAL);  /* necessary? */
	retval = 0;
	if (mnrdvc==VERSPLOT || mnrdvc==VERSPRINT)
		retval=hcmclose(mnrdvc,flag==VPBWRITE? FWRITE:FREAD);
	MAPIN_PIB(pa, cp, hp);
        DODEBUG(D_PIB,("pibclose: flag=%d, cp=0x%X,hp->next=0x%X,hp->free=%d\n",
	                flag,cp,hp->next,hp->free));
	if (hp->cmd==CMDCLOSE) {/* PIB already requested a close */
		if (flag==VPBREAD) {
			enqrec(&cp->infree,hp->next,0,CMDXFER,bp); hp->free=0;
		} else if (flag==VPBWRITE && hp->free!=0 && hp->free!=cp->reclen) {
			enqrec(&cp->outfree,hp->next+hp->free-cp->reclen,0,CMDXFER,bp);
			hp->free=0;
		}
	} else {/* we tell PIB to close */
			/* flush partial output buffer */
		if (flag==VPBWRITE && hp->free!=0 && hp->free!=cp->reclen) {
			enqrec(&cp->out,hp->next,cp->reclen-hp->free,CMDXFER,bp);
			hp->free=0;
		}
			/* get record to contain CLOSE command */
		if (flag==VPBREAD || hp->free!=cp->reclen) {
			if (0==(cp=wdeqrec(mnrdvc,bp,cp,&cp->outfree))) {
				UNMAP_PIB(); return(ENXIO);
			}
		}
		hp= &cp->cur;
		enqrec(&cp->out, hp->next, 0, CMDCLOSE, bp); hp->free=0;
		/* flush input until PIB sends CMDCLOSE */
		if (flag==VPBREAD && mnrdvc!=VERSPLOT) for (;;) {
			if (0==(cp=wdeqrec(mnrdvc,bp,cp,&cp->in))) {
				UNMAP_PIB(); return(ENXIO);
			}
			hp= &cp->cur;
			enqrec(&cp->infree,hp->next-hp->free,0,CMDXFER,bp); hp->free=0;
			if (hp->cmd==CMDCLOSE) break;
		}
	}
	UNMAP_PIB();
	return(retval);
}

/* Service ioctl requests.  Perhaps the weirdest of these routines.
 */
pibctl(mnrdvc, bp, cmd, bufp)
struct bdata *bp;
unsigned cmd;
byte         *bufp;			/* vpbdo thinks this is a struct uio *,
					 * but it isn`t (sbs) */
{  
	register int	retval = 0,	/* return(retval) */
		x;			/* splx(x) */
	register comm	*cp;		/* board "common" area pointer */
	posn	*hp;			/* current position */
	register PA *pa;

	DODEBUG(D_PIB,("entered pibctl: dev = 0x%X, cmd=0x%X\n",mnrdvc,cmd));
	if (cmd<pdevtbl[mnrdvc].lowctl || cmd>pdevtbl[mnrdvc].highctl) 
		return(EINVAL);

	MAPIN_PIB(pa, cp, hp);
	DODEBUG(D_PIB,("pibctl: cp=0x%X,hp->free=%d, cmd=0x%X\n",
			cp,hp->free, cmd));
	cmd-=RSCS; cmd&=0xff;
	switch (cmd) {
	case 0: /* RSCS */
		/* Validate args*/
		/* put output in buffer obtained from INFREE */
		/* enqueue on OUT with cmd = RSCS */
		/* immediately input from IN	*/
		{ 
			/* The scam is to call pibio with a home-made
			 * uio structure, then pack the goodies back
			 * into the user's RSCS data structure which
			 * is returned from this ioctl routine.
			 */
			register RSCSparms *rscs = (RSCSparms *)bufp;
			struct	iovec	aiov;
			struct	uio	auio;
			int	rcln;	/* save cp->reclen for after unmap */

			DODEBUG(D_PIB,("RSCS: out=0x%X, outl=%d, ",
				       rscs->outbuf,rscs->outlen));
			DODEBUG(D_PIB,("inp=0x%X, inpl=%d, tmo=%d,",
				      rscs->inpbuf,rscs->inplen,rscs->inptime));
			cp->timeout = rscs->inptime;
			rcln = cp->reclen;
			UNMAP_PIB();
			retval = OUTWLN;

			/* Invalid output length.
			 */
			if (rscs->outlen > rcln)
				goto rscsdone;

			MAPIN_PIB(pa, cp, hp);
			/*
			 * buffer is either on infree (from last xchg) 
			 * or outfree (from open). Must prime it into 
			 * hp in either case.
			 */
			x = spl6();
			if (!deqrec(&cp->infree, hp)) {
				if (0==(cp=wdeqrec(mnrdvc,bp,cp,&cp->outfree))){
					splx(x); UNMAP_PIB(); return(ENXIO);
				}
				hp= &cp->cur;
			}
			splx(x);

			/* whole buffer available. */
			hp->free = cp->reclen;	
			UNMAP_PIB();

			/* Fix up auio for write.
			 */
			auio.uio_iovcnt = 1;
			auio.uio_iov = &aiov;
			auio.uio_resid = aiov.iov_len = rscs->outlen;
			aiov.iov_base = (caddr_t)rscs->outbuf;
			auio.uio_segflg = 0;	/* user's data segment */

			/*  Do the transfer.
			 */
			if (retval = pibio(mnrdvc, bp, VPBWRITE, &auio))
				goto rscsdone;

			/* Send record with RSCS write on it.
			 */
			MAPIN_PIB(pa, cp, hp);
			if(hp->free)
				enqrec(&cp->out, hp->next, cp->reclen-hp->free,
							    CMDXFER, bp);
			hp->free = 0;	/* show exhausted */
			/* unmap because pibio can sleep
			 */
			UNMAP_PIB();
			/* Fix up auio for read.
			 */
			auio.uio_iovcnt = 1;
			auio.uio_iov = &aiov;
			auio.uio_resid = aiov.iov_len = rscs->inplen;
			aiov.iov_base = (caddr_t)rscs->inpbuf;
			auio.uio_segflg = 0;	/* user's data segment */
			retval = pibio(mnrdvc + 1, bp, VPBREAD, &auio);
			rscs->inplen -= auio.uio_resid;
rscsdone:
			/* Either we have a real transfer error, in which
			 * case we send back "status", or there was some
			 * parameter booboo.
			 */
			rscs->rstatus = 0;	/* be deterministic */
			if (retval<0) {/* real RSCS error */
				rscs->rstatus=retval;  /* put RSCS info here */
				retval=0;  /* let ioctl() pass back struct */
			}
			/* must remap only so unmap succeeds */
			pa = (PA *)iosetmap(bp->privMba);
			DODEBUG(D_PIB,(" rtnd inpln=%d\n", rscs->inplen));
		}
		break;

	case 1:     /* VRSFMFD */
	case 2:     /* VRSPLOT */
	case 3:     /* VRSPRNT */
	case 4:     /* VRSLTER */
	case 5:     /* VRSBLOCKPLOT */

	        DODEBUG(D_PIB,("VERSATEC control operation\n"));
		if (hp->free != 0 || cmd== 5) {
			if (0==(cp=wdeqrec(mnrdvc,bp,cp,&cp->outfree))) {
				retval=ENXIO; break;
			}
			hp= &cp->cur;
		}
		if (cmd == 5) {
			/* VERSBLOCKPLOT (Forgive me I did not start this) */

			register char *physnext;
			register char *virtnext;

			DODEBUG(D_PIB,("pibctl VERS block plot\n"));

			physnext = (char *) hp->next;
			virtnext = (char *) iosetmap (physnext);
			*((long *)virtnext) = (long) vgHardCopyMem;
			virtnext           += sizeof(vgHardCopyMem);

			iounsetmap (physnext);
			hp->free = cp->reclen - sizeof(vgHardCopyMem);
			hp->next += sizeof(vgHardCopyMem) ;
		}
		enqrec(&cp->out,hp->next,cp->reclen-hp->free,cmd+CMDCLOSE,bp);
		hp->free = 0;
		break;

	case 6:     /* VRSFLUSH */

		DODEBUG(D_PIB,("VERS Flush. obufnm=%d, xnum=%d\n",
				   cp->obufnm, cp->outfree.xnum));
		/* Lock the output area */
		x = spl6();
		if (!getlock(&cp->outfree)) {
			printf("VERS flush. could not get lock\n");
			goto dontsmash;		/* sbs */
			/* splx(x);
			break; */
		}

		while (cp->obufnm > cp->outfree.xnum) {
			char pibgen;
			DODEBUG(D_PIB,("Flush sleep. obufnm=%d, xnum=%d\n",
				       cp->obufnm, cp->outfree.xnum));
			cp->outfree.flags |= DEQWT;	/* Ask for interrupt */
			cp->outfree.lock = 0;		/* release lock */
			pibgen=pibreload;
			UNMAP_PIB();			/* free addresses so  */
			sleep(hp, PPIB);		/*   we can rest easy */
			MAPIN_PIB(pa, cp, hp);		/* restore addressing */
			if (pibgen!=pibreload) {retval=ENXIO; break;}
			if (!getlock(&cp->outfree)) {	/* get lock again */
				printf("VERS flush. could not get lock\n");
				/* break;  -- sbs */
				goto dontsmash;
			}
		}

		/* Release lock */
		cp->outfree.lock = 0;
dontsmash: /* sbs: if we never had the lock, we shouldn't unlock it! */
		splx(x);
		break;

	default: 
		panic("pibctl");	/* "Cannot happen" */
        }

        UNMAP_PIB();
	DODEBUG(D_PIB,("pibctl: returns 0x%X\n",retval));
	return(retval);
}


/* Attempt to lock record
 */
getlock(listp)
	register rlist   *listp;
{
	register int i;

	for(i=0; i<SPINCOUNT; i++) 
		if (tas(&listp->lock)) 
			break;
	return(i != SPINCOUNT);
	/* sbs 1-19-84:
	 * Noted that the construction
	 *	for (i = 0; i < SPINCOUNT; i++)
	 *		if (tas(&listp->lock))
	 *			return 1;
	 *	return 0;
	 * doesn't work.
	 */
}

/* Copy "count" bytes between "addr" (which may be physical, and so must be
 * mapped) and "uio", which is known to be a kernel virtual address, according
 * to the direction of the "rw" flag.
 * Values:
 *	0	everything's a hunky dory
 *	EFAULT	ubd
 *
 * sbs 1-16-84.  Replaces copybuf in pib.c/pibio().
 *	Also, it's used as the transfer routine by the pseudo-board
 *	routines of vpb.c/vpbdo() -- that's why it's outside the NIBMPIB
 *	#if.
 */
pibcp(addr, count, rw, uio)
unsigned addr;
struct	uio	*uio;
{
	extern	pagesize, pageshift;

	register len;
	register u_int	virtpg, realpg;		/* expression temporaries */
	register savepg1, savepg2;		/* where to save old map */
	int	sseg_sz = pagesize << 1;	/* 1 scratch seg (2 pages) */
	int	uioresult = 0;			/* uiomove result */

	while (count > 0 && uioresult == 0) {
		/* The length of the transfer is bounded by a scratch segment
		 * boundary.  First compute the maximum number of bytes that
		 * can be xfer`d without crossing a boundary, then make sure
		 * it isn`t greater than the actual # to be transferred.
		 */
		len = sseg_sz - (addr & (sseg_sz-1));
		if (len > count)
			len = count;
		/* Map in the real address, and transfer the bytes
		 */
		virtpg = (SSEG2_VA >> pageshift); realpg = (addr >> pageshift);
		savepg1 = setpagemap(virtpg, realpg);
		savepg2 = setpagemap(virtpg + 1, realpg + 1);
		uioresult = uiomove(SSEG2_VA|(addr & pagemask), len,
			(rw == VPBREAD)? UIO_READ: UIO_WRITE, uio);
		/* Release the real address
		 */
		setpagemap(virtpg, savepg1);
		setpagemap(virtpg + 1, savepg2);
		count -= len, addr += len;
	}
	return(u.u_error = uioresult);
}
