/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:vd.c 12.0$ */
/* $ACIS:vd.c 12.0$ */
/* $Source: /ibm/acis/usr/sys/caio/RCS/vd.c,v $ */

#if !defined(lint) && !defined(NO_RCS_HDRS)
static char *rcsid = "$Header:vd.c 12.0$";
#endif


#ifndef	lint
static char *rcsid_vd_c = "$Header:vd.c 12.0$";
#endif	lint

/* virtual disk driver for VAX Unix kernel */
/* Written by Michael Greenwald April 1982 */
/* Modified by lwa to try to avoid reboot on timeout, 11/16/84 */
/* Modified by lwa to fix nonce/uid bug 1/15/85 */
/* Modified by Jis to add vdopen routine. 12/5/85 */

#define EVDBAD		70		/* hack hack hack... should be errno.h */
#ifdef RVD
#include "vd.h"				/* this defines NVD */
#if NVD > 0

#include "../h/errno.h"
#include "../h/param.h"
#include "../h/systm.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/proc.h"
#include "../h/file.h"
#include "../h/conf.h"
#ifdef VFS
#include "../h/vfs.h"
#include "../ufs/mount.h"
#else VFS
#include "../h/mount.h"
#endif VFS
#include "../h/vm.h"
#include "../h/reboot.h"
#include "../netinet/in.h"
#include "../machine/pte.h"
#include "../h/buf.h"			/* Must be after pte if NVD defined */
#ifndef ibm032
#include "../machine/mtpr.h"
#else
#include "../machine/mmu.h"
#include "../machine/dkio.h"
#define SBMODEL
#ifdef SBMODEL
#define spl_high()	_spl2()		/* at ibm032 level 2 */
#undef SBMODEL
#endif
#endif ibm032
#include "../machineio/vdreg.h"

#define min(a,b)	(a<b?a:b)


#define TRUE 1
#define FALSE 0

#define	b_nonce	b_pfcent		/* this is really bogus; it won't
					 * work for paging, etc.  But for
					 * now it will do.
					 */

struct	vd_long_dev vddinfo[NVD];	/* device descriptor for each drive */
struct	vd_longstat	vd_longstat;	/* virtual disk statistics buffer */
struct	buf	vdutab[NVD];	/* Buffer head for queue for each drive */
struct	buf	rvdbuf[NVD];	/* One buffer for each drive for raw I/O */

int	vddflg = FALSE;
#ifdef ibm032
caddr_t real_buf_addr();
#else
struct	pte	*vdpte[VDMAXBF+1];
struct	r_chain	fchain[NCHAINS];
struct	r_chain	*getchain();
caddr_t	vdpfstrt;
caddr_t	rvd_copy_d();
#endif ibm032
unsigned int	minvdphys();
			
int	vdup	= 0;

#define u_long	unsigned long
struct	buf	*vdfind();
struct	buf	*vdfirst();
u_long		vdgettime();

#define mark_as_bad(badcount) {vd_longstat.vdstat.bad_blk += (badcount);}

/* This code is called by clients in three ways.  The typical way is for 
 * a client (usually bio, or physio) to call vdstrategy.  vdstrategy starts 
 * up the request and returns.  Raw I/O is done through vdread and vdwrite,
 * which both call physio, which in turn calls vdstrategy.  vdspin, and 
 * vdspind begin a spinup and spindown of a virtual drive.  And finally 
 * vdstats can return statistics to the user.
 *
 * The code can also be entered on packet receipt (calls by rvd_input into
 * vdblock, vdack, vdwack, vdspack, & vderror), and on timer interrupts
 * (vd_slowtimo).
 *
 * Packet receipt is at software interrupt level (ipnet? maybe) and timeout
 * routines are at software interrupt.  vdstrategy can be called from the
 * interrupt stack.  All places where the queue is modified, must therefore 
 * be protected from one another. And all places where the queue is searched
 * down, must be protected.
 */

/*  */
/*
 * Routine called to set up the world. Initializes vddinfo and fchain.
 */
vd_init()
{
	register	i;
	caddr_t		vaddr;
	register	struct pte *tpte;
	int		vdopen();
	extern		int	nchrdev, nblkdev;
	extern		struct	cdevsw	cdevsw[];
	extern		struct	bdevsw	bdevsw[];

	if (vdup > 0) return;
#ifndef ibm032
	vaddr = vdpfstrt;
	for (i=0;i <= VDMAXBF; i++) {
	    vdpte[i] = &(Sysmap[((u_long)vaddr & 0x7fffffff)>>PGSHIFT]);
	    mtpr(TBIS,vaddr);
	    vaddr += NBPG;
	}
#endif
	/*
	 * Search bdevsw and cdevsw for the vdopen routines
	 * to we can figure out the block and char major numbers
	 */
	for(i = 0; i < nblkdev; i++)
		if(bdevsw[i].d_open == vdopen) {
			vd_longstat.blk_major = i;
			break;
		}
	for(i = 0; i < nchrdev; i++)
		if(cdevsw[i].d_open == vdopen) {
			vd_longstat.char_major = i;
			break;
		}
	for (i=0;i<NVD;i++){
		vddinfo[i].vd_device.state = UNUSED;	/* Clean up the world */
		vddinfo[i].vd_device.q_len = 0;
		vddinfo[i].vd_device.reqs_out = 0;
		vdutab[i].b_actf = NULL;	/* Read queue */
		vdutab[i].b_actl = NULL;
		vdutab[i].b_forw = NULL;	/* Write queue */
		vdutab[i].b_back = NULL;
		vdutab[i].b_dev = makedev(vd_longstat.blk_major,i);
		vdutab[i].b_nonce = vdgettime(); /* an initial nonce */
	}
	for (i=0;i<STQLEN;i++) vd_longstat.vdstat.q_size[i] = 0;
	vdup++;			/* So we don't get initialized twice */
#ifndef ibm032
	for (i=0;i < NCHAINS - 1;i++){
	    fchain[i].next = &fchain[i+1];
	}
	fchain[NCHAINS - 1].next = NULL;
#endif
	vd_longstat.struct_vers = VD_VERSION;
	vd_longstat.num_drives = NVD;

	return;
}
/*  */
/* This open routine just checks to see if the disk is spun up in a mode
   compatible with the open attempt. */
   
vdopen(dev, flag)
     dev_t dev;
     int flag;
{
     register int drive = minor(dev);
     register struct vd_long_dev *vdi;

	if(drive == VD_CONTROL_UNIT)
		return(0);

	if ((drive >= NVD) || (drive < 0))
		return (ENXIO);  /* No such drive. */
	vdi = &vddinfo[drive];
	if (vdi->vd_device.state != SPUNUP)
		return (ENXIO); /* Must first be spunup */

	/* if open for writing, then spinup mode cannot be "read-only" mode. */
	if ((flag&FWRITE) && ((vdi->vd_device.mode & ~RVDHARD) == RVDMRO))
		return (ENXIO);
	return (0);		/* All systems GO. */
}
/*  */
/* Routine called by bio to queue up a request. Since all the buffering is
 * done on the server, this is very simple. Put it on the queue for this drive,
 * and send request.
 * We don't have to check for duplicates, because, except for
 * raw I/O, it is done a block at a time, and bp would have been found.
 * For raw I/O we do the checking in the read and write routines.
 */

vdstrategy(bp)

	register struct buf *bp;
{
	register struct vd_long_dev	*vdi;
	register int	drive;		/* drive number */
	register struct	buf	*dp;	/* Head of buf queue for this drive */
	long bn, size;			/* block number, and size of transfer in blocks */
	int	last;

	size = vdnblks(bp->b_bcount);	/* How many disk blocks? */

	drive = minor(bp->b_dev);	/* Drive number (minor is a macro defined in types.h */

#ifndef ibm032
	bp->m_chain = NULL;		/* Clear so not freed if bad request */
#endif

/* Check if valid request */
	if (drive >= NVD){		/* We aint got that many drives */
		bp->b_error = ENXIO;
		goto bad;
	}
	vdi = &vddinfo[drive];
	if ((vdi->vd_device.state != SPUNUP)) { /* Not spun up */
		bp->b_error = ENXIO;
		goto bad;
	}

/* OK drive is valid, how about block number? */
	if ((bn = vdkblock(bp))+size > vdi->vd_device.nblocks)
		goto bad;
	if (size > VDMAXBURST) {
		printf("VD%d: Burst %d > max\n",drive,size);
		goto bad;
	}
	dp = &vdutab[drive];		/* Get queue head */


/*
 * we use b_bitmap as a bitmap to mark blocks outstanding on this request.
 * For example, if someone requests 7 blocks, we set the first (low order) 7
 * bits of b_bitmap. Then, as we receive blocks we clear the appropriate bits.
 * When b_bitmap is zero, then we are finished.
 */
	bp->b_bitmap = ~0;		/* Fill with 1s */
	bp->b_bitmap >>= (VDMAXBURST - (unsigned int)size);
					/* And adjust so that 1s are only in places
					where we are actually sending the data. */

/*
 * If it is physio we need to get the addresses in core so that when the
 * packets come in, we can put the data in the appropriate place. (Also,
 * if we have to retransmit a write request, we can get the data from the
 * appropriate place) 
 */
	if (bp->b_flags & B_PHYS){
#ifndef ibm032
struct	r_chain *m;
int	o,npf;
register struct pte *ptep,*pmap;
unsigned v;
struct	proc	*rproc;
int	i;

	    if ((m = getchain()) == NULL) {
		bp->b_error = EVDBAD;
		goto bad;
	    }
	    bp->m_chain = m;	/* Get place to store page table */
	    pmap = m->pmap;	/* Address of pmap array*/
	    v = btop(bp->b_un.b_addr);
				/* First page in page table */
	    o = (int)bp->b_un.b_addr & PGOFSET;
				/* offset into page */
	    m->offset = o;	/* Save offset in cached page table */
	    npf = btoc(bp->b_bcount + o);
				/* Number of pages we need entries for */

				/* If it is B_DIRTY then we use the pageout */
				/* daemons page table instead of b_proc */
	    rproc = bp->b_flags & B_DIRTY ? & proc[2] : bp->b_proc;
	    if (bp->b_flags & B_UAREA) {
		for (i=UPAGES - bp->b_bcount/NBPG; i < UPAGES; i++) {
		    pmap++->pg_pfnum = rproc->p_addr[i].pg_pfnum;
		}
	    }
	    else { 
		if (bp->b_flags & B_PAGET) /* Black Magic... */
		    ptep = &Usrptmap[btokmx((struct pte *)bp->b_un.b_addr)];
		else 			/* Normal case */
		    ptep = vtopte(rproc, v);

		while (npf-- != 0){	/* Copy entry for each page */
		    pmap++->pg_pfnum = ptep++->pg_pfnum;
		}
	    }
	    *(int *)pmap++ = 0;		/* To catch wierd references */

#endif
	}

	if ((vdi->vd_device.reqs_out + size) > vdi->vd_device.maxqlen)
		vd_longstat.vdstat.pushes++;

	vdi->vd_device.reqs_out += size;
/*
 * This gets really hairy because there is not enough room in the buf header
 * to store all this info.  Basicly b_errcnt is used as the retransmit
 * indicator. The lower 4 bits are used as a send counter and b_errcnt & ~0xf is
 * the time the first retransmit should be sent.
 */
		/* Get time add fudge factor, use for nonce */
	if (bp->b_flags & B_READ)
		bp->b_errcnt = ((vdgettime() + size*VDPKTCOST) & ~0xf) | 1;
	else
		bp->b_errcnt = ((vdgettime() + size*VDPKTCOST*2) & ~0xf) | 1;
	bp->b_nonce = ++(dp->b_nonce); /* get a unique ID */
	if (!vdsort(dp, bp, size)){	/* Put ours on queue */
		bp->b_error = EVDBAD;	/* If it wouldn't go return error */
		goto bad;	
	}
				/* Keep meters up to date */
	if (bp->b_flags & B_READ)
		vd_longstat.vdstat.blk_rqs += size;
	else
		vd_longstat.vdstat.blk_wrt += size;

	vdi->vd_device.q_len++;		/* Measure queue length */
	if(vdi->vd_device.q_len > STQLEN) vdi->vd_device.q_len = STQLEN;
		vd_longstat.vdstat.q_size[vdi->vd_device.q_len]++;

	vdstart(bp,vdi,1);		/* send request */

#ifdef RVDDBUG
	if (vddflg)
	printf("vdstrategy: dr %d,bn %d,sz %d,fl %x,n %X\n",
			drive,bn,bp->b_bcount,bp->b_flags,bp->b_nonce);
#endif

	return;

bad:
	bp->b_flags |= B_ERROR;
#ifndef ibm032
	if (bp->m_chain != NULL) freechain(bp->m_chain);
#endif
	iodone(bp);
	return;
}

/*
 * vdsort puts buffer on list for drive.
 * We order the buffers in ascending order. Eventually we might remember
 * the value of the middle buffer and search from the front or back, (it
 * is a doubly linked list), depending which is quicker. (If we want to search
 * at interrupt level that can make the difference.)
 * This can only be called from user level. (Right now it is called from
 * vdstrategy). That is why we can afford to spl0 instead of splx.
 * But, that is not important yet.
 */

vdsort(dp, bp, size)
	register struct buf *dp, *bp;
	u_long	size;

{
	register struct buf *ap;
	int s;

	s = splnet();		/* So if we chase pointers at
				 * interrupt level, we don't go off
				 * into hyper-space */
	ap = vdfind (dp,(u_long)(bp->b_blkno)); /* find position on q */
	if (ap == bp)
		printf("vd: Queue error\n");

					/* now, thread us in */

	bp->av_back=ap;			/* set our backpointer */
	if (ap == NULL) {		/* We are now the first on list */
		bp->av_forw = dp->b_actf; /* Point to next */
		dp->b_actf = bp;	/* Now we are first */
	}
	else {
		bp->av_forw = ap->av_forw; /* get next buffer */
		ap->av_forw = bp;	/* Let previous point to us */
	}
	if (bp->av_forw == NULL)	/* If we are last.. */
		dp->b_actl = bp;
	else
		bp->av_forw->av_back = bp; /* otherwise,let next point to us */
	(void)splx(s);
	return(1);

}

/*
 * Start up a transfer on a drive by sending a request to the server.
 * If type is 0, then vdstart is guaranteed to not block (and therefore
 * not be pre-empted). If type is 1, then vdstart will try **very** hard
 * to send the request - possibly blocking in the process. If type is
 * 0, we are guaranteed not to block waiting on an mbuf.
 */
vdstart(bp,vdi,type)
	struct buf *bp;		/* pointer to buffered request */
	struct vd_long_dev *vdi;	/* pointer to drive descriptor */
	int	type;
{
	if (bp->b_flags & B_READ)
		return(vdpread(bp,vdi,type));
	else
		return(vdpwrite(bp,vdi,type));
}

/**/
/* System calls */
/*
 * "Load" a disk pack into a drive, and spin up a virtual drive
 */
vdspin ()
{
	u.u_error = _vdspin((struct vd_spinup *)(u.u_ap));
}

static	int	_vdspin(sp)
struct	vd_spinup	*sp;
{
	register u_long drive;
	register struct buf *dp;
	register struct vd_long_dev *vdi;
	u_long errcod;
	struct	sockaddr_in	server_name;
	short	i;

	errcod = 0;
	if (vdup == 0) vd_init();

	drive = sp->drive;
#ifdef RVDDBUG
	if (vddflg) {
		printf("vd%d: spinup req\n",drive);
		printf("vd%d: mode = %d\n", drive, sp->mode);
	}
#endif

	if (drive >= NVD) {
	    errcod = RVDEND;
	    goto err_ret;
	}
	if (((sp->mode & ~RVDHARD) != RVDMRO) &&
			((sp->mode & ~RVDHARD) != RVDMSHR) &&
			((sp->mode & ~RVDHARD) != RVDMEXC)) {
	        errcod = RVDEBMD;
		goto err_ret;
	}
	vdi = &vddinfo[drive];
	if ((vdi->vd_device.state != SPUNDOWN) &&
			(vdi->vd_device.state != UNUSED)) {
		if((errcod = _vdspind(drive, 0)) != 0)
			goto err_ret;
	}

	copyin((caddr_t)sp->uspin,(caddr_t)&vdi->args,sizeof(struct spinargs));
	copyin((caddr_t)sp->server,(caddr_t)&server_name,sizeof(struct sockaddr_in));

	dp = &vdutab[drive];
	dp->b_error = 0;
	vdi->vd_device.drive = drive;
	vdi->vd_device.nonce = vdgettime();
	vdi->vd_device.index = 0;
	vdi->vd_device.status = 0;
	vdi->vd_device.state = SPINWAIT;
	vdi->vd_device.mode = sp->mode;
	vdi->vd_device.server.s_addr = server_name.sin_addr.s_addr;
	for (i=0;i<32;i++)
		vdi->vd_device.capability[i] = vdi->args.capab[i];

	dp->b_errcnt = vdi->vd_device.nonce & ~0xf;

		/* Weep, all ye who enter here */
	rvdspin(drive,vdi->args.name, vdi->args.capab,
		(vdi->vd_device.mode & ~RVDHARD), (u_char)VDMAXBF,
		vdi->vd_device.nonce, &vdi->vd_device.server);
	(void)splnet();
	while (vdi->vd_device.state == SPINWAIT) {
		sleep((caddr_t *)dp,PZERO+1);
		(void)spl0();
		if (vdi->vd_device.state == SPINWAIT)
			rvdspin(drive, vdi->args.name, vdi->args.capab,
				(vdi->vd_device.mode & ~RVDHARD),
				(u_char)VDMAXBF, vdi->vd_device.nonce,
				&(vdi->vd_device.server));
		(void)splnet();
	}
	(void)spl0();

	if (vdi->vd_device.state == SPUNUP) return(0);
	errcod = dp->b_error;
err_ret:
	copyout((caddr_t)&errcod,(caddr_t)(sp->errp),sizeof(u_long));
	return(EVDBAD);
}

#ifdef	KERBEROS
/* Kerberos authorized spinup.  This code is the same as the regular spinup
 * but will copy a Kerberos authenticator from user space to system space.
 */
static	int	_vdauthspin(sp)
struct	vd_auth_spinup	*sp;
{
	register u_long drive;
	register struct buf *dp;
	register struct vd_long_dev *vdi;
	u_long errcod;
	struct	sockaddr_in	server_name;
	int	authent_size;
	struct ktext authent;

	errcod = 0;
	if (vdup == 0) vd_init();

	drive = sp->drive;
#ifdef RVDDBUG
	if (vddflg) {
		printf("vd%d: authspinup req\n",drive);
		printf("vd%d: mode = %d\n", drive, sp->mode);
	}
#endif

	if (drive >= NVD) {
	    errcod = RVDEND;
	    goto err_ret;
	}
	if (((sp->mode & ~RVDHARD) != RVDMRO) &&
			((sp->mode & ~RVDHARD) != RVDMSHR) &&
			((sp->mode & ~RVDHARD) != RVDMEXC)) {
	        errcod = RVDEBMD;
		goto err_ret;
	}
	vdi = &vddinfo[drive];
	if ((vdi->vd_device.state != SPUNDOWN) &&
			(vdi->vd_device.state != UNUSED)) {
		if ((errcod = _vdspind(drive, 0)) != 0)
			goto err_ret;
	}

	copyin((caddr_t)sp->uspin,(caddr_t)&vdi->args,sizeof(struct spinargs));
	copyin((caddr_t)sp->server,(caddr_t)&server_name,
				sizeof(struct sockaddr_in));

	/* Copy in the Kerberos authenticator.  This is done by first copying
	 * in the size and then using that to copy in the authenticator.  The
	 * system address of the authenticator is passed to the protocol level.
	 */
	copyin((caddr_t)sp->authent,&authent_size, sizeof(int));
	authent_size += sizeof(int) + sizeof(long);
	if (authent_size > sizeof(struct ktext))
		authent_size = sizeof(struct ktext);
	copyin((caddr_t)sp->authent,&authent, authent_size);

	dp = &vdutab[drive];
	dp->b_error = 0;
	vdi->vd_device.drive = drive;
	vdi->vd_device.nonce = vdgettime();
	vdi->vd_device.index = 0;
	vdi->vd_device.status = 0;
	vdi->vd_device.state = SPINWAIT;
	vdi->vd_device.mode = sp->mode;
	vdi->vd_device.server.s_addr = server_name.sin_addr.s_addr;

	dp->b_errcnt = vdi->vd_device.nonce & ~0xf;

	/* Call the RVD protocol level to send packet for authorized spinup.
	 */
	rvdauthspin(drive,vdi->args.name, &authent,
		(vdi->vd_device.mode & ~RVDHARD), (u_char)VDMAXBF,
		vdi->vd_device.nonce, &vdi->vd_device.server);
	(void)splnet();

	while (vdi->vd_device.state == SPINWAIT) {
		sleep((caddr_t *)dp,PZERO+1);
		(void)spl0();
		if (vdi->vd_device.state == SPINWAIT)
			rvdauthspin(drive, vdi->args.name, &authent,
				(vdi->vd_device.mode & ~RVDHARD),
				(u_char)VDMAXBF, vdi->vd_device.nonce,
				&(vdi->vd_device.server));
		(void)splnet();
	}
	(void)spl0();

	if (vdi->vd_device.state == SPUNUP) return(0);
	errcod = dp->b_error;
err_ret:
	copyout((caddr_t)&errcod,(caddr_t)(sp->errp),sizeof(u_long));
	return(EVDBAD);
}
#endif	KERBEROS

vdspind ()
{
	u.u_error = _vdspind((u_long)u.u_ap[0], 0);
}

static int _vdspind(drive, force)
u_long	drive;
{
	register struct buf *dp, *bp;
	register struct vd_long_dev *vdi;
	register struct mount *mp = 0;

	if (drive > NVD)
		return(ENXIO);
	vdi = &vddinfo[drive];
	if(vdi->vd_device.state == UNUSED && vdi->vd_device.state == SPUNDOWN)
		return(ENXIO);
	dp = &vdutab[drive];

	if(!force)
		for (mp = &mount[0]; mp < &mount[NMOUNT]; mp++)
			if (mp->m_bufp != 0 && dp->b_dev == mp->m_dev)
				return(EBUSY);

	vdi->vd_device.nonce = vdgettime();
	while ((bp = dp->b_actf) != NULL)
		sleep ((caddr_t *)bp,PUSER);	/*
						 * Don't interrupt until last
						 * block has been flushed 
						 */
			/* This priority must be lower than the priority of
			   anything else waiting on bp. Otherwise, if when
			   wakeup (bp) is executed, vdspind will sleep on the
			   same bp!!! (because it hasn't been removed from the
			   queue yet.)  Once it gets removed, it won't be
			   awakened for a long time. */
	vdi->vd_device.state = SPINDWAIT;
	dp->b_errcnt = vdgettime();

		/* Weep, all ye who enter here */
	rvdspind(drive, vdi->vd_device.index, vdi->vd_device.nonce,
		vdi->vd_device.capability, &(vdi->vd_device.server));
	(void)splnet();
	while (vdi->vd_device.state == SPINDWAIT) {
	    sleep((caddr_t *)dp,PZERO+1);
	    (void)spl0();
	    if (vdi->vd_device.state == SPINDWAIT)
		rvdspind(drive, vdi->vd_device.index, vdi->vd_device.nonce,
			vdi->vd_device.capability, &(vdi->vd_device.server));
	    (void)splnet();
	}
	(void)spl0();

	if (vdi->vd_device.state == SPUNDOWN)
		return(0);
	else
		return(ENXIO);
}

vdstats ()
{
	register struct a {
	    struct vd_stat *u_data;
	    struct vd_device *u_drives;
	} *uap;
	register	int i;

	uap = (struct a *)u.u_ap;
	copyout((caddr_t)&vd_longstat.vdstat,(caddr_t)(uap->u_data),
				sizeof (struct vd_stat));
	for( i = 0; i < NVD; i++)
		copyout((caddr_t)&vddinfo[i].vd_device,
			(caddr_t)(uap->u_drives++), sizeof(struct vd_device));
	return;
}


vdabort(drive)
{
register struct buf *dp, *bp;
register struct vd_long_dev *vdi;

	if (drive > NVD)
		return(ENXIO);

	vdi = &vddinfo[drive];
	if(vdi->vd_device.state == UNUSED && vdi->vd_device.state == SPUNDOWN)
		return(ENXIO);

	dp = &vdutab[drive];

	vdi->vd_device.state = SPUNDOWN;
	dp->b_errcnt = FUTURE;
	dp->b_error = RVDETIM;
	wakeup((caddr_t)dp);

	for(bp = dp->b_actf; bp; bp = bp->av_forw)
		vdbad(vdi, dp, bp, RVDETIM);

	return(0);
}

/*ARGSUSED3*/
vdioctl(dev, cmd, data, flag)
	dev_t dev;
	caddr_t data;
{   
	register int	i;
	register struct	vd_device	*drives;

	if((minor(dev) != VD_CONTROL_UNIT) && (cmd != DKIOCGPART))
		return (EBADF);

	switch (cmd) {
		case VDIOCABORT:
			return(vdabort(*(u_long *)data));

		case VDIOCSPINUP:
			return(_vdspin((struct vd_spinup *)data));

#ifdef	KERBEROS
		case VDIOCAUTHSPINUP:
			return(_vdauthspin((struct vd_auth_spinup *)data));
#endif	KERBEROS

		case VDIOCSPINDOWN:
			return(_vdspind(*(u_long *)data, 0));

		case VDIOCSPINDFORCE:
			return(_vdspind(*(u_long *)data, 1));

		case VDIOCGETSTAT:
			copyout((caddr_t)&vd_longstat, *(caddr_t *)data,
			sizeof(vd_longstat));
			break;

		case VDIOCGETDRIVE:
			copyout((caddr_t)vddinfo, *(caddr_t *)data, 
					sizeof(vddinfo));
			break;
#ifdef ibm032
       		case DKIOCGPART: {
               		register struct dkpart *dk = (struct dkpart *) data;
			int	drive = minor(dev);

			if (drive >= NVD || 
			  (vddinfo[drive].vd_device.state != SPUNUP))
				return(ENODEV); 
			/* size */
	                dk->dk_size = vddinfo[drive].vd_device.nblocks;
       		        dk->dk_start = 0;                 /* start */
                	dk->dk_blocksize = VDBSIZE;       /* device blocksize */
                	dk->dk_ntrack = 1;         	  /* tracks/cylinder */
                	dk->dk_nsector = dk->dk_size;     /* sectors/track */
                	dk->dk_ncyl = 1;            	  /* cylinders */
                	strcpy(dk->dk_name,"rvdpack"); 	  /* copy name */
                	return(0);
                }
#endif ibm032

		default:
			return (ENXIO);
	}
	return(0);
}

/**/
/*
 * Handle a "virtual disk" interrupt.(i.e. a packet receipt)
 */

/* An ACK for a spinup packet. */

vdspack (drive, index, nonce, blockn, burst, qlen, bfactor, vd_uid)
	u_long	drive;
	u_long	index, nonce, blockn;
	u_short	burst, qlen;	/* Flow control parameters. We ignore for now */
	u_char	bfactor;	/* Blocking factor for combining writes */
	u_long	vd_uid;
{
	register struct vd_long_dev *vdi;
	register struct buf *dp;

	if (drive > NVD) {			/* no drive here... */
		mark_as_bad(1);
		return;
	}

	vdi = &vddinfo[drive];
	dp = &vdutab[drive];

					/* Ack for SPIN-UP packet */
	if (vdi->vd_device.state == SPINWAIT){
		if (vdi->vd_device.nonce == nonce){
			vdi->vd_device.index = index;
			vdi->vd_device.nblocks = blockn;
			vdi->vd_device.state = SPUNUP;
			vdi->vd_device.burst = burst;
			vdi->vd_device.maxqlen = qlen;
			vdi->vd_device.reqs_out = 0;
			vdi->vd_device.bfactor =
				((VDMAXBF >= bfactor) ? bfactor : VDMAXBF);
			vdi->vd_uid = vd_uid;
			dp->b_errcnt = FUTURE;	/* Death timer should never
						   reach this value */

		}
		else {
			vd_longstat.vdstat.bad_nonce++;
			return;
		}
	}
	else {
		vd_longstat.vdstat.bad_state++;
		return;
	}
	wakeup((caddr_t)dp);
	return;
}

vdack(drive, index, nonce)

	u_long	drive,index, nonce;
{
	register struct vd_long_dev *vdi;
	register struct buf *dp;

	if (drive > NVD){			/* no drive here... */
		mark_as_bad(1);
		return;
	}

	vdi = &vddinfo[drive];
	dp = &vdutab[drive];
					/* Ack for SPIN-DOWN */
	if (vdi->vd_device.state == SPINDWAIT){
		if (vdi->vd_device.nonce == nonce)
			vdi->vd_device.state = SPUNDOWN;
		else {
			vd_longstat.vdstat.bad_nonce++;
			return;
		}
	}
	else {
		vd_longstat.vdstat.bad_state++;
		return;
	}
	wakeup((caddr_t)dp);
	return;
}

vderror(drive, error, index, nonce, data, dlen)	/* ERROR packet */
	u_char	error;
	u_long	drive, index, nonce;
	u_char	*data;
	int	dlen;
{
	register struct vd_long_dev *vdi;
	register struct buf *dp, *bp, *ap;

	vd_longstat.vdstat.err_rcv++;
	if (drive > NVD)
		return;
	
	vdi = &vddinfo[drive];
	dp = &vdutab[drive];

	switch(error) {

/* these should only occur during a  spinup */

	case RVDETGH:
	case RVDETCG:
	case RVDEXMD:
	case RVDEBMD:
	case RVDETBMD:
	case RVDEPDIS:
	case RVDESPN:
	case RVDEPACK:
	case RVDEOMD:
	case RVDEBPWD:
		if (vdi->vd_device.state == SPINWAIT){
			vdi->vd_device.state = SPUNDOWN;
			dp->b_error = (short)error;
			wakeup((caddr_t)dp);
			for (bp = dp->b_actf; bp; bp = ap) {
				ap = bp->av_forw;
				vdbad(vdi, dp, bp, error);
			}
		}
		else
			printf("VD:unexpected error %d in non-spinwait state\n",
				error);
		break;

	case RVDEND:			/* server probably crashed */
		if (vdi->vd_device.state == SPINDWAIT) { /* just spin it down */
			vdi->vd_device.state = SPUNDOWN;
			dp->b_error = ENXIO;
			wakeup((caddr_t)dp);
		} else {
			vdi->vd_device.state = SPINWAIT;
			dp->b_errcnt = vdgettime() & ~0xf;
			rvdrespin(drive, vdi->vd_uid, vdi->args.capab,
				(vdi->vd_device.mode & ~RVDHARD),
				(u_char)VDMAXBF, vdi->vd_device.nonce,
				&(vdi->vd_device.server));
		}
		break;

	case RVDENAH:			/* report to user code */
	case RVDESNA:
	case RVDEBVER:
		if (vdi->vd_device.state == SPINDWAIT) { /* just spin it down */
			vdi->vd_device.state = SPUNDOWN;
			dp->b_error = ENXIO;
			wakeup((caddr_t)dp);
		} else {
			for (bp = dp->b_actf; bp; bp = ap) {
				ap = bp->av_forw;
				vdbad(vdi, dp, bp, error);
			}
		/* Error is permanent. Attempt no more i/o over the net. */
			vdi->vd_device.state = CRASHED;
			printf("WARNING vd%d: Server crashed: unmount, spindown and spinup again\n", drive);
		}
		break;

	case RVDEPKT:		/* errors in the user code; what do we do? */
	case RVDEZBL:
	case RVDETBL:
				/* Should be individually logged */
		break;

	case RVDEIDX:		/* just store new index */
		vdi->vd_device.index = index;
		break;

	case RVDECKSM:		/* Ignore for now */
		break;

	case RVDEIDA:
		if (dlen >= 3) {	/* dlen is length in longs */
			printf("vd%d: identical pack already spunup\n", drive);
			vdspack(drive,index,nonce, ntohl(*(u_long *)data),
				ntohs(*(u_short *)(data+4)),
				ntohs(*(u_short *)(data+6)),
				*(u_char *)(data+11));
		}
		break;

	case RVDENOER:
	default:
		printf("Unhandled VD Err(%d)\n",error);
	}

	return;
}

vdblock(drive, index, nonce, blockn, count, status, datap)
	register u_long	drive;
	register u_long	blockn;
	register u_long	count;
	u_long	index;
	u_long	status;
	u_long	nonce;
	caddr_t	datap;
{
	struct	vd_long_dev *vdi;
	struct	buf *bp, *dp, *ap;
	caddr_t	p;
	unsigned int	offset,blsize,i,blocks;
	register int	acked;
	struct	r_chain	*rp;
	int good,s,nonoverlap;
#ifdef ibm032
	int ss;
	u_long bleft;
	u_long blsave;
	u_long tleft;
	struct mbuf *mdp;
	caddr_t rbp;
#endif
	if (drive > NVD) {			/* no drive here... */
		mark_as_bad(count);
		return;
		}
	vdi = &vddinfo[drive];
#ifdef RVDDBUG
	if (vddflg)
	    printf("vdb%d: bn %d (%d) n %X st %X\n",drive,blockn,count,nonce,status);
#endif
	dp = &vdutab[drive];
	if (vdi->vd_device.state != SPUNUP){
		vd_longstat.vdstat.bad_state++;
		return;
		}

	s = splnet();
	dp->b_errcnt = FUTURE;

	bp = vdfirst(dp,nonce);

	/*
	 * This error test is here in case count <= 0.
	 */
	if (status & RVDSTVAL) {
	    vd_longstat.vdstat.bad_data += count;
	    printf ("vd%d: bad block bn %X st %X\n",drive,blockn,status);
	    if (status & RVDSTADR) 
	        printf("vd%d: bad addr\n",drive);
	    if (status & RVDSTDIS)
	        printf("vd%d: physical drive has been disabled.\n",drive);
	    vdbad(vdi,dp,bp,ENXIO);
	    (void) splx(s);
	    return;
	}

	for (;count > 0;bp=ap) {
	    if (bp == NULL) {
		mark_as_bad(count);
		(void)splx(s);
		return;
 	    }

	    ap = bp->av_forw;
	    if (!(bp->b_flags & B_READ)) { continue;}

    	    if (blockn < bp->b_blkno) {
		nonoverlap = bp->b_blkno - blockn;
		nonoverlap = min(nonoverlap,count);
		mark_as_bad(nonoverlap);
		count -= nonoverlap;
		blockn = bp->b_blkno;
	    }
	    else if ((bp->b_blkno + vdnblks(bp->b_bcount)) <= blockn) {
		continue;
	    }

	    if (status & RVDSTVAL) {
		vd_longstat.vdstat.bad_data += count;
		printf ("vd%d: bad block bn %X st %X\n",drive,blockn,status);
		if (status & RVDSTADR) 
		    printf("vd%d: bad addr\n",drive);
		if (status & RVDSTDIS)
		    printf("vd%d: physical drive has been disabled.\n",drive);
		vdbad(vdi,dp,bp,ENXIO);
		(void) splx(s);
		return;
	    }
	    blocks = (bp->b_blkno + vdnblks(bp->b_bcount)) - blockn;
	    acked = min(blocks,count);
	    offset = blockn - bp->b_blkno;
	    blsize = min(acked*VDBSIZE,bp->b_bcount-(offset*VDBSIZE));
#ifndef ibm032
	    if ((bp->b_flags & B_PHYS) == 0)
		p = (caddr_t)((u_long)bp->b_un.b_words + offset*VDBSIZE);
	    else {
		p = (caddr_t)((u_long)vdpfstrt | bp->m_chain->offset);
/* This stuff might need to be in an splx */
		for (i=0;i < acked;i++) {
		    if ((vdpte[i]->pg_pfnum
			 = bp->m_chain->pmap[offset+i].pg_pfnum) == 0)
			panic("vd copy zero pte %d",i);
		    mtpr(TBIS,(u_long)p+i*NBPG);
		}
		if ((blsize + bp->m_chain->offset) > acked*NBPG) {
			if(0 == (vdpte[acked]->pg_pfnum
				= bp->m_chain->pmap[offset + acked].pg_pfnum))
					panic("vd copy1 zero pte %d", acked);
			mtpr(TBIS,(u_long)p+acked*NBPG);
		}
	    }
/* Copy data from packet. If it is last block, only copy interesting data */
	    datap = rvd_copy_d(datap,p,blsize);
#else
	    ss = spl_high();
	    mdp = (struct mbuf *) datap;
	    SET_VR0;
	    p = (caddr_t) ((u_long)bp->b_un.b_words + offset*VDBSIZE);
	    while (blsize > 0) {
	    	rbp = real_buf_addr (bp, p);
	    	bleft = min (blsize, NBPG - ((u_long)p & PGOFSET));
	    	blsave = bleft;
	    	while (bleft > 0) {
			tleft = min (bleft, mdp->m_len);
			bcopy (mtod(mdp, caddr_t), rbp, tleft);
			rbp = (caddr_t) ((u_long)rbp + tleft);
			if (mdp->m_len > bleft) {
				mdp->m_off += tleft;
				mdp->m_len -= tleft;
			}
			else mdp = mdp->m_next;
			bleft -= tleft;
			if (mdp == NULL) goto done_copy;
		}
	    	blsize -= blsave;
	    	p = (caddr_t) ((u_long)p + blsave);
	    }
done_copy:
	    CLR_VR0;
	    splx (ss);
#endif

/* Mark block as received */
	    good = clearbitmap(bp,blockn,acked);
	    vdi->vd_device.reqs_out -= good;
	    mark_as_bad(acked - good);
	    vdvalid(dp,bp);

	    blockn += acked;
	    count -= acked;
	}
	splx(s);
	return;
}

					/* Ack for WRITE */
vdwack(drive, index, nonce, blockn, status, count)
	register u_long	drive;
	register u_long	blockn;
	u_long index;
	long count;
	u_long status;
	u_long nonce;
{
	struct vd_long_dev *vdi;
	register struct buf *bp;
	struct buf	*dp, *ap;
	unsigned int	offset;
	unsigned int	blocks;
	register int	acked;
	int		good;
	int		nonoverlap;
	int		s;

	if (drive > NVD) {		/* no drive here... */
		mark_as_bad(count);
		return;
	}

	vdi = &vddinfo[drive];
#ifdef RVDDBUG
	if (vddflg)
	printf("vdwk%d: bn %d sz %d st %X n %X",
			drive,blockn,count,status,nonce); /* No NL */
#endif
	dp = &vdutab[drive];
					/* Write response */
	if (status & RVDSTVAL){
		vd_longstat.vdstat.bad_data++;
		if (status & RVDSTWRL)
			printf("vd%d: write locked. ", drive);
		if (status & RVDSTDIS)
			printf("vd%d: physical drive disabled. ", drive);
		printf("st %X\n",status);
	}

	s = splnet();
	dp->b_errcnt = FUTURE;

	bp = vdfirst(dp, nonce);

	for (;count > 0;bp=ap) {
	    if (bp == NULL) {
		mark_as_bad(count);
		(void)splx(s);
		return;
	    }
	    ap = bp->av_forw;
	    if (bp->b_flags & B_READ) {
		continue;
	    }
	    if (blockn < bp->b_blkno) {
		nonoverlap = bp->b_blkno - blockn;
		nonoverlap = min(nonoverlap,count);
		mark_as_bad(nonoverlap);
		count -= nonoverlap;
		blockn = bp->b_blkno;
	    }
	    else if ((bp->b_blkno + vdnblks(bp->b_bcount)) <= blockn) {
		continue;
	    }

	    blocks = (bp->b_blkno + vdnblks(bp->b_bcount)) - blockn;
	    acked = min(blocks,count);
	    if (status & RVDSTVAL) {
		if (status & RVDSTWRL)
			vdbad(vdi,dp,bp,EPERM);
		if (status & RVDSTDIS)
			vdbad(vdi,dp,bp,ENXIO);
	    } else {
		good = clearbitmap(bp,blockn,acked);
		vdi->vd_device.reqs_out -= good;
		mark_as_bad(acked - good);

		vdvalid(dp,bp);
	    }
	    blockn += acked;
	    count -= acked;

	}
	(void)splx(s);

#ifdef RVDDBUG
	if (vddflg) printf("%X\n",bp);
#endif

	return;
}

			/* Servers status */
			/* Server requests our status */

/**/
/* Physio calls */
vdread(dev, uio)
	dev_t dev;
	struct	uio *uio;
{
	register int drive = minor(dev);
	register struct buf *bp;
	unsigned int bn;

	if (drive >= NVD){ return(ENXIO); }
	
	bp = &rvdbuf[drive];
	bn = (unsigned int)(bp->b_blkno);
#ifdef RVDDBUG
	if (vddflg) printf("vdread: dr %d, bn %d bp %X\n",drive,bn,bp);
#endif

	return(physio(vdstrategy, bp, dev, B_READ, minvdphys, uio));
}

vdwrite(dev, uio)
	dev_t dev;
	struct	uio *uio;
{
	register int drive = minor(dev);
	register struct buf *bp;
	unsigned int bn;

	if (drive >= NVD){ return(ENXIO); }

	bp = &rvdbuf[drive];
	bn = (unsigned int)(bp->b_blkno);

#ifdef RVDDBUG
	if (vddflg) printf("vdwrite: dr %d, bn %d bp %X\n",drive,bn,bp);
#endif

	return(physio(vdstrategy, bp, dev, B_WRITE, minvdphys, uio));
}



/**/
/* Internal management routine */
/*
 * Wake up every 0.5 seconds and check for packets that need to be
 * retransmitted. Give up on packets that have waited too long, and if
 * possible, start up queued packets.
 */
vd_slowtimo()
{
static	u_long	timelast = 0;

	register struct	buf *bp;
	register struct	vd_long_dev *vdi;
	register u_long	rxmt;
	int drive;
	struct	buf *dp;
	u_long	timenow;
	u_long	giveupt;
	int	burst = 0;
	int	s;

	timenow = vdgettime();
	giveupt = timenow - (VDMAXTRY*VDTIM*1000);
	rxmt = timenow - (VDTIM*1000);

	for (drive = 0; drive < NVD; drive++) {

		vdi = &vddinfo[drive];
		dp = &vdutab[drive];

		switch(vdi->vd_device.state) {
		case SPUNDOWN:
		case UNUSED:
			continue;

		case SPINWAIT:
		case SPINDWAIT:
			if((timenow-VDSPINTRY*1000) >= (u_long)(dp->b_errcnt)) {
				vdi->vd_device.state = SPUNDOWN;
				dp->b_errcnt = FUTURE;
				dp->b_error = RVDETIM;
				wakeup((caddr_t)dp);
			} else if (timelast >= (u_long)(dp->b_errcnt) &&
					(dp->b_errcnt++ & 1))
				wakeup((caddr_t)dp);
			if(dp->b_actf)
				for(bp = dp->b_actf; bp; bp = bp->av_forw)
					vdbad(vdi, dp, bp, RVDETIM);
			continue;

		default:
			if (giveupt >= (u_long)(dp->b_errcnt)) {
				dp->b_errcnt = FUTURE;
				vd_longstat.vdstat.timeout++;
				printf("vd timeout on drive %D\n",drive);
				s = splnet();
				for(bp = dp->b_actf; bp; bp = bp->av_forw)
					vdbad(vdi, dp, bp, RVDETIM);
				(void)splx(s);
				continue;
			}
			break;
		}

		s = splnet();
		for(bp = dp->b_actf; bp && burst < vdi->vd_device.burst;
							bp = bp->av_forw) {
/*
 * This gets really hairy because there is not enough room in the buf header
 * to store all this info.  Basicly b_errcnt is used as the retransmit
 * indicator. The lower 4 bits are used as a send counter and b_errcnt & ~0xf is
 * the time the first retransmit should be sent.
 */
			switch(bp->b_errcnt & 0xf) {
			case 1:
				if(timenow < bp->b_errcnt) 
					continue;
				/* else fall through */
			case 3: case 7: case 14:
				burst += vdstart(bp,vdi,0);
				vd_longstat.vdstat.rxmts += vdunacked(bp);
				if(((vdi->vd_device.mode & RVDHARD) == 0) &&
						(dp->b_errcnt == FUTURE))
					dp->b_errcnt = (u_long)timenow;
				bp->b_errcnt++;
				break;
			case 15:
				if(rxmt > (u_long)(bp->b_errcnt)) {
					bp->b_errcnt = (long)timenow | 0xf;
					burst += vdstart(bp,vdi,0);
					vd_longstat.long_rxmts += vdunacked(bp);
				}
				continue;
			default:
				bp->b_errcnt++;
				break;
			}
		}
		(void)splx(s);
	}
	timelast = timenow;
}

/**/

/* Some utility routines */

/*
 * vdfind finds a buffers place on a sorted device list. dp points to the head
 * of the device list. bn is the block number of the buffer to be found.
 * If the buffer is not on the list, vdfind returns a pointer to the buffer
 * that should immediately precede it on the queue. (null if it should be the
 * first buffer on queue)
 */

struct buf *vdfind(dp, bn)
	register u_long bn;
	register struct buf *dp;
{
	register struct buf *ap;
	int s;

	if ((ap = dp->b_actf) != NULL)	/* If there are already bufs on drive */
	    if (bn >= (u_long)(ap->b_blkno)) {
					/* then, if we go after the first one */
		s = splnet();
		for (;ap->av_forw;ap = ap->av_forw) {
					/* Find the block that will precede us  */
		    if (bn < ap->av_forw->b_blkno)
			break;
		}
		(void)splx(s);
	    }
	    else return(NULL);

	return(ap);
}

/*
 * vdfirst finds the first buffer in the sorted device list which contains
 * the specified nonce, and returns a pointer to it.  If there is no such
 * buffer, vdfirst returns NULL.
 * This routine must only be called at splnet() or higher.
 */

struct buf *vdfirst(dp, nonce)
register struct buf *dp;
register u_long nonce;
{
	register struct buf *ap;

	for (ap = dp->b_actf; ap != NULL; ap = ap->av_forw)
		if (ap->b_nonce == nonce)
			return(ap);
	return(NULL);
}


/*
 * vdbad marks a buffer as bad I/O and returns it to user 
 */

vdbad(vdi, dp, bp, error)
	register struct buf *dp, *bp;
	struct	vd_long_dev *vdi;
	short	error;
{
	register int i,sent;

	vdi->vd_device.reqs_out -= vdunacked(bp);

	bp->b_flags |= B_ERROR;
				/* How much wasn't dealt with? We only report
				 * contigous bytes starting from the beginning
				 * as actually finished.
				 */

	sent = bp->b_bcount;
	for (i=0;((VDBSIZE*i)<bp->b_bcount);i++){
	    if (bp->b_bitmap & (1<<i)) {	/* This one wasn't done */
		sent = VDBSIZE*i;
		break;
	    }
	}
	bp->b_resid = bp->b_bcount - sent;

	if ((bp->b_error = error) == 0) bp->b_error = dp->b_error;
	vddact(dp, bp);
}

/*
 * vdvalid marks a buffer as completed I/O, fills in the appropriate
 * fields and returns it to user 
 */

vdvalid(dp, bp)
	register struct buf *dp, *bp;
{
	if (bp->b_bitmap != 0) return;

/* Round number of bytes up to Block size. Return twos complement */
/*
 *	bp->b_resid = -((((bp->b_bcount + VDBSIZE - 1)
 *				>> VDBSHIFT)
 *				    << VDBSHIFT));
 */
	bp->b_resid = 0;
	vddact(dp, bp);
}

/* vddq dequeues a buffer (pointed to by bp) from a doubly linked list,
 * which is headed by the buffer pointed to by dp.
 */
vddq(dp, bp)
	register struct buf *bp, *dp;
{
	int s;

	s = splnet();
	if (bp->av_back == NULL)
		dp->b_actf = bp->av_forw;
	else
		bp->av_back->av_forw = bp->av_forw;

	if (bp->av_forw == NULL)
		dp->b_actl = bp->av_back;
	else
		bp->av_forw->av_back = bp->av_back;
	bp->av_forw = NULL;
	bp->av_back = NULL;
	(void)splx(s);
}

/*
 * vddact de-activates a buffer. i.e. it removes it from the active list,
 * signals iodone, and if there are any buffers waiting on it, starts them
 * up and puts them on the active list.
 */

vddact(dp, bp)
	register struct buf *bp, *dp;
{
	register int	drive;

	vddq(dp,bp);		/* Pull it off queue */
	drive = minor(dp->b_dev);
	if ((vddinfo[drive].vd_device.q_len--)<=0){
		if (vddflg) printf("vd%d:N Qlen\n",drive);
		vddinfo[drive].vd_device.q_len = 0;
	}
	vd_longstat.vdstat.q_size[vddinfo[drive].vd_device.q_len]++;
#ifndef ibm032
	if (bp->m_chain != NULL) freechain(bp->m_chain);
#endif
	iodone(bp);		/* ... and tell user it is finished */
	return;
		
}

/* How many as yet unacked blocks in a request */

vdunacked (bp)
struct buf	*bp;
{
	register int i;
	int	count = 0;

	for (i=0;i < vdnblks(bp->b_bcount);i++)
	    if (bp->b_bitmap & (1 << i)) count++;

	return(count);
}

/* Return time in milliseconds */

u_long vdgettime()
{
extern	struct	timeval	time;

	register u_long msec = time.tv_usec/1000;
	msec += time.tv_sec*1000;
	return(msec);
}
/*  */


/* Routines that interface to net stuff */
/* Send an RVD read request on buffer pointed to by bp */
vdpread(bp,vdi,type)
	register struct buf *bp;
	register struct vd_long_dev *vdi;
{
	register u_long drive;
	int sstate,errcode,pkts;
	register i;
	u_long size,sblk,ssize;

	errcode = 0;			/* FALSE */
	pkts = 0;
	drive = vdi->vd_device.drive;

	size = vdnblks(bp->b_bcount);
#ifdef RVDDBUG
	if (vddflg)
	printf("vdpread: vd%d bn%d sz%d bm%X n%X\n",drive,bp->b_blkno,size,bp->b_bitmap,bp->b_nonce);
#endif
	sstate = NOTSEEN;
	ssize = 0;
	for (i=0;i < size;i++) {
		if (bp->b_bitmap & (1 << i)){
			if (sstate == NOTSEEN){
				sblk = bp->b_blkno + i;
				sstate = INBURST;
				ssize = 1;
			}
			else if (sstate == INBURST)
				ssize++;
		}
		else if ((bp->b_bitmap & (1 << i)) == 0){
			if (sstate == INBURST){
				sstate = NOTSEEN;
				pkts++;
				errcode = rvdread(drive,(u_long)bp->b_nonce,
					vdi->vd_device.index, sblk,ssize,
					&(vdi->vd_device.server),type);
				if (errcode != 0 && errcode != ENOBUFS)
					rvdread(drive, (u_long)bp->b_nonce,
						vdi->vd_device.index, sblk,
						ssize, &(vdi->vd_device.server),
						type);
			}
		}
	}
	if (sstate == INBURST) {
		pkts++;
		errcode = rvdread(drive, (u_long)bp->b_nonce,
			vdi->vd_device.index, sblk, ssize,
			&(vdi->vd_device.server), type);
		if (errcode != 0 && errcode != ENOBUFS)
			errcode = rvdread(drive, (u_long)bp->b_nonce,
				vdi->vd_device.index, sblk, ssize,
				&(vdi->vd_device.server), type);
	}

	return(pkts);
}
/* Send an RVD write request on buffer pointed to by bp */

/*
 * The stuff that is going on with last is that when we **KNOW** that a packet
 * got trashed for whatever reason, then we immediately resubmit it, instead
 * of waiting to time out on it. If the last packet failed twice, we tell the
 * guy who called us.(i.e. return (last))
 */
vdpwrite(bp,vdi,type)
	register struct buf *bp;
	register struct vd_long_dev *vdi;
	int type;
{
	int	errcode,pkts;
	int	size,i;
	register int bf;
	register int count;

	errcode = 0;			/* FALSE */
	pkts = 0;

	size = (unsigned int)vdnblks(bp->b_bcount);
#ifdef RVDDBUG
	if (vddflg)
	printf("vdpwrite: vd%d bn%d sz%d bm%x n%X\n",vdi->vd_device.drive,bp->b_blkno,size,bp->b_bitmap,bp->b_nonce);
#endif
	bf = vdi->vd_device.bfactor;
	count = 0;
	for (i=0;i <= size;i++){	/* Go through entire bitmap, */
					/*  1 past the end! */
	    if (bp->b_bitmap & (1 << i)) {
		count++;
		if (count >= bf) {
		    pkts++;
		    errcode = vdfwrite(vdi,bp,i + 1 - count,count,size,type);
		    count = 0;
		}
	    }
	    else if (count > 0) {
		pkts++;
		errcode = vdfwrite(vdi,bp,i - count,count,size,type);
		count = 0;
	    }
	}
	return(pkts);
}

vdfwrite(vdi,bp,blockno,size,total,type)
	register u_long blockno;
	register u_long size;
	struct vd_long_dev *vdi;
	u_long	total;
	struct	buf *bp;
	int	type;
{
	register u_long drive;
	register int i;
	caddr_t	p;
	int	errcode;
	int	s;

	drive = vdi->vd_device.drive;

	s = splnet();			/* synch access to vdpte's */
#ifndef ibm032
	if ((bp->b_flags & B_PHYS) != 0) {
	    p = (caddr_t)((u_long)vdpfstrt | bp->m_chain->offset);
	    for (i=0;i <= size;i++) {
		    vdpte[i]->pg_pfnum =
			bp->m_chain->pmap[blockno + i].pg_pfnum;
		    mtpr(TBIS,(u_long)p+NBPG*i);
	    }
	}
	else p = (caddr_t)
		((u_long)bp->b_un.b_words + VDBSIZE*blockno);
#else
	p = (caddr_t) ((u_long)bp->b_un.b_words + VDBSIZE*blockno);
#endif
	errcode = rvdwrite(drive,bp,vdi->vd_device.index,total,blockno,size,p,
				&(vdi->vd_device.server), type);
	if (errcode != 0 && errcode != ENOBUFS)
		errcode = rvdwrite(drive,bp,vdi->vd_device.index,total,blockno,
			size,p, &(vdi->vd_device.server), type);
	splx(s);
	return(errcode);
}

unsigned minvdphys(bp)
	register struct buf *bp;
{
	if (bp->b_bcount > VDMAXBURST*VDBSIZE)
		bp->b_bcount = VDMAXBURST*VDBSIZE;
}


/**/
/* Chain management routines */


#ifndef ibm032
struct r_chain *getchain() {
	register struct r_chain *rp;
	int s;

	s = spl6();
	rp = fchain->next;
	if (rp == NULL) return(NULL);
	fchain->next = rp->next;
	rp->next = NULL;
	(void)splx(s);
	return(rp);
}

freechain(cp)
	register struct r_chain *cp;
{
	int s;

	s = spl6();
	cp->next = fchain->next;
	fchain->next = cp;
	(void)splx(s);
}
#endif

clearbitmap(bp,bn,size)

	struct buf *bp;
	unsigned int bn,size;
{
	unsigned int offset;
	register int i;
	register unsigned int count;

	count = 0;
	offset = bn - bp->b_blkno;
	size = min(size,(vdnblks(bp->b_bcount) - offset));
	for (i=offset;i<offset+size;i++)
	    if (bp->b_bitmap & (1 << i)) count ++;

	bp->b_bitmap &= ~(((1 << size) - 1) << offset);
	return(count);
}


#endif
#endif RVD
