/* $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 */

#ident "$Header: spec_dio.c 1.4 90/08/30 $"

/*
 * spec_dio.c
 *	specfs Direct I/O support routines
 */

/* $Log:	spec_dio.c,v $
 */

#include "sys/types.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/mutex.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/errno.h"
#include "sys/buf.h"
#include "sys/vnode.h"
#include "sys/uio.h"
#include "sys/dio.h"
#include "sys/file.h"
#include "sys/devsw.h"
#include "sys/snode.h"
#include "sys/vmmeter.h"
#include "sys/vmparam.h"
#include "sys/pte.h"
#include "sys/intctl.h"
#include "sys/plocal.h"
#include "sys/cmn_err.h"
#include "sys/ufsinode.h"
#include "sys/var.h"

extern long max_RAW_IO;
extern caddr_t kmem_zalloc();

/*
 * TODO
 *
 * ISSUES
 *
 *
 * Vnode or File descriptor reference
 *
 * At this time the code maintains a reference to the file descriptor form
 * just before the I/O is stated until the completed request is polled.
 * After implementing this I realized it should hold a reference to the file
 * descriptor instead.  The VN_HOLD would be replaced with a FDBUMP and and
 * the VN_RELE should be replaced with a call to ofile_deref.  This would
 * allow the call to aio_pend_wait to be removed in spec_close, since the
 * final close could not occur because of the reference to the file
 * descriptor being held.  Additionally the vnode pointers in AIO_buf and
 * AIO_uio would be changed file structure pointers.
 *
 *
 * vallocRSslot
 *
 * Currently after vallocRSslot finds a page for replacement, a call is made
 * to aio_pending (if the process is doing asynchronous I/O).  Aio_pending
 * returns true if the page is being used as an asynchronous I/O buffer,
 * false otherwise.  If the page was being used as a buffer vallocRSslot
 * returns immediately.  It appears to me that there are placing in the
 * kernel that assume vallocRSslot will allocate a slot.  There may be a
 * problem for these callers.  VallocRSslot could be made more intelligent so
 * that it it kept looking if the page was a buffer.  It would have to make
 * sure that it did not look at more than the resident set size number of
 * pages or it could go into an infinite loop.  Also aio_pending could return
 * the number of pages to skip to get past the end of the buffer.  This
 * could prevent needless checks on those pages.  Before anything is done
 * this should be discussed with rbk.
 *
 *
 * ENHANCEMENTS
 *
 * DIO_Apoll timeout value
 *
 * This code was taken from the poll system call, to enable timeout values
 * of other than 0 and \-1, just go back to poll and get the rest of the
 * code.  There was not time to test it, so it was eliminated.
 *
 * aio_pid in AIO_req
 *
 * The code currently allows this value to be zero (for self) or the current
 * pid.  It should be changed to allow any pid sharing the AIO structure.
 * The pid could be evaluated by converting to a proc pointer and verifying
 * that this procs p_aio is equal to p->p_aio.  This check will need to be
 * made twice.  Once during argument validation and again just before the
 * signal is sent (the proc could have died and a new process be using the
 * process structure).
 *
 * Asynchronous I/O on VREG files
 *
 * It appears that the current mechanisms will support asynchronous I/O on
 * regular files, with only a few modifications.  This should be examined
 * more and implemented if possible.  At least one of the database companies
 * is asking for this.
 *
 * New Functions for the DIO System Call
 *
 * The system call was designed so that new functions could be added easily.
 * There have already been two request for new functions.  These are a
 * function that would return an indication if a file descriptor was capable
 * of asynchronous I/O, and a function to set the maximum number of
 * outstanding request a process can have.  The first request came from
 * SYBASE.  They want it so that they can easily determine at runtime if
 * they can use asynchronous I/O or if they must use synchronous I/O.  The
 * second request came from the SPIE group.  They want to move the limit up
 * for stress testing and move it down for boundary condition testing.  It
 * is likely users will want to do the same thing.
 *
 * A third possible function would be the DIO_Aclose function.  This would
 * clean up all outstanding asynchronous request and release the kernel's
 * internal asynchronous I/O structure.  The process would have to do
 * another DIO_Ainit before any more asynchronous I/O request could be
 * processed.
 */

/* DIO syscall
 * First argument is a command.  depending on the command it
 * may require 0 to 5 more arguments.
 */

DIO()
{
	register struct a {
		int	cmd;
		int	arg2;
		int	arg3;
		int	arg4;
		int	arg5;
		int	arg6;
	} *uap = (struct a *)u.u_ap;
	int	error;
	int	cmd;

	cmd = uap->cmd;
	switch (cmd) {

	case DIO_WRITE:
	case DIO_READ: {

		struct AIO_req *aioptr, req;
		struct vnode *vp;
		struct file *fp;
		int fdes;
		char *cbuf;
		int count;
		off_t offset;
		int flags;

		/* get arguments */
		fdes = uap->arg2;
		cbuf = (char *) uap->arg3;
		count = uap->arg4;
		offset = uap->arg5;
		aioptr = (struct AIO_req *) uap->arg6;

		/* fetch request structure if it exists */
		if (aioptr == NULL) {
			req.aio_flags = 0;
		} else if ((error = copyin((caddr_t) aioptr, (caddr_t) &req,
			sizeof (req))) != 0) {
			break;
		}

		flags = req.aio_flags;

		/* check for any undefined flags set */
		if ((flags & (AIO_ASYNC | AIO_DELAY)) != flags) {
			error = EINVAL;
			break;
		}
		/*
		 * Basic sanity check on arguments.
		 */

		if (offset < 0 || count < 0 || (offset + count) < 0) {
			error = EINVAL;
			break;
		}

		/*
		 * Get object referenced by fd.  Insist on readable
		 * or writable as appropriate.
		 */

		if ((error = getvnodefp(fdes, &fp)) != 0)
			break;

		if ((fp->f_flag & (cmd == DIO_READ ? FREAD : FWRITE)) == 0) {
			error = EBADF;
			break;
		}

		vp = fp->f_vnode;

		switch(vp->v_type) {
		case VREG:
			/* ASYNC I/O not implemented on regular files yet */
			if (flags & AIO_ASYNC) {
				error = EINVAL;
				break;
			}
			/*
			 * Kludge for now: insist on UFS vnode for a
			 * regular file and non-asynchronous I/O.  Should
			 * generalize this into a VOP() an implement
			 * asynchronous I/O
			 */
			if ((vp->v_op == &ufs_vnodeops) && (flags == 0)) {
				error = ufs_DIO(vp, cbuf, count, offset,
				    cmd == DIO_READ ? UIO_READ : UIO_WRITE);
			} else {
				error = EINVAL;
			}
			break;

		case VCHR:
			error = spec_dio(vp, fp, cbuf, count, offset,
				cmd == DIO_READ ? UIO_READ : UIO_WRITE, &req);
			break;

		default:
			error = EINVAL;
			break;
		}
		break;
	}

	case DIO_AINIT:
		error = aio_init();
		break;

	case DIO_APOLL:
		if (u.u_procp->p_aio == NULL) {
			error = EUNATCH;
		} else {
			u.u_r.r_val1 = aio_poll(u.u_procp->p_aio, uap->arg2,
				uap->arg3, uap->arg4,
				(struct AIO_status *) uap->arg5,
				uap->arg6);
			return;
		}
		break;
	default:
		error = EINVAL;
		break;
	}

	if (error) {
		u.u_error = error;
		u.u_r.r_val1 = -1;
	}
}

/*
 * aio_init - Initialize asynchronous I/O structures for a process
 */
int
aio_init()
{
	struct AIO *aiop;
	proc_t *p;

	/* Fail if Asynchronous I/O is not enabled */
	if (nabuf == 0) {
		return (EINVAL);
	}

	p = u.u_procp;
	/* If already initialized release AIO structure */
	aiop = p->p_aio;
	if (aiop != NULL) {
		aio_deref(aiop, p);
	}

	aiop = (struct AIO *) kmem_zalloc(sizeof(struct AIO));		

	if (aiop == NULL) {
		return (ENFILE);
	}
	init_lock(&aiop->aio_lock);
	init_sema(&aiop->aio_sema, 0);
	aiop->aio_refcnt = 1;
	AQ_INIT(&aiop->aio_pend);
	AQ_INIT(&aiop->aio_done);
	p->p_aio = aiop;
	return (0);
}


/*
 * aio_pend_wait - Wait for all of the request for the specified
 *		   process to complete.
 *	aiop	- pointer to asynchronous I/O object.
 *	p	- pointer to proc that we are removing.
 *	locked	- 1 if aio structure is locked.
 */
aio_pend_wait(aiop, p, locked)
	struct AIO *aiop;
	proc_t *p;
	int locked;
{
	struct buf *bp;
	spl_t spl;
	int done = 0;

	if (!locked) {
		spl = p_lock(&aiop->aio_lock, SPLFS);
	}

	/* wait for the pending list to drain of request for this process*/
	while (!(AQ_EMPTY(&aiop->aio_pend) || done)) {
		bp = (struct buf *) aiop->aio_pend.dl_next;
		for (done = 1; bp != (struct buf *) &aiop->aio_pend;
							bp = bp->b_forw) {
			if (p == bp->b_proc) {
				(void) p_sema_v_lock(&aiop->aio_sema,
					PRIBIO, &aiop->aio_lock);
				(void) p_lock(&aiop->aio_lock, SPLFS);
				done = 0;
				break;
			}
		}
	}

	if (!locked) {
		v_lock(&aiop->aio_lock, spl);
	}
}

/*
 * aio_pending - Return 1 if async I/O pending for this process and page.
 *	aiop	- pointer to asynchronous I/O object.
 *	p	- pointer to proc that we are removing.
	v	= arrdree of page being checked
 */
aio_pending(aiop, p, page)
	struct AIO *aiop;
	proc_t *p;
	caddr_t page;
{
	struct buf *bp;
	spl_t spl;

	spl = p_lock(&aiop->aio_lock, SPLFS);

	/* check the pending list for request for this process*/
	bp = (struct buf *) aiop->aio_pend.dl_next;
	for (; bp != (struct buf *) &aiop->aio_pend; bp = bp->b_forw) {
		if ((p == bp->b_proc)
		    && (page < bp->b_un.b_addr + bp->b_bcount)
		    && (bp->b_un.b_addr < page + CLSIZE)) {
			v_lock(&aiop->aio_lock, spl);
			return (1);
		}
	}
	v_lock(&aiop->aio_lock, spl);
	return (0);
}

/*
 * aio_ref - bump reference count on asynchronous I/O object.
 *	     aiop - pointer to asynchronous I/O object.
 */
aio_ref(aiop)
	struct AIO *aiop;
{
	spl_t spl;

	spl = p_lock(&aiop->aio_lock, SPLFS);
	aiop->aio_refcnt++;
	v_lock(&aiop->aio_lock, spl);
}

/*
 * aio_deref - decrement the reference count and free the asynchronous
 *	       I/O object is the count goes to zero.
 *	aiop - pointer to asynchronous I/O object.
 *	p    - pointer to proc that we are removing.
 */
aio_deref(aiop, p)
	struct AIO *aiop;
	proc_t *p;
{
	spl_t spl;

	spl = p_lock(&aiop->aio_lock, SPLFS);

	/* wait for the pending list to drain of request for this process*/
	aio_pend_wait(aiop, p, 1);

	/*
	 * Now the pending I/O list is empty, de-reference the object.
	 * If this is last reference free the object after releasing any
	 * completed asynchronous I/O buffers on the done list.
	 */
	if (--aiop->aio_refcnt == 0) {
		/* free all completed buffers */
		while (!AQ_EMPTY(&aiop->aio_done)) {
			struct buf *bp;

			bp = (struct buf *) aiop->aio_done.dl_next;
			AQ_DEQUEUE(&aiop->aio_done, bp);
			aiop->aio_count--;
			v_sema(&bp->b_alloc);	/* release buffer */
			/*
			 * Release the vnode reference now that the
			 * I/O is completed.  This must be done in the
			 * user's context, otherwise it could be done at
			 * abiodone time.  This may cause I/O to be done,
			 * so release the lock on the asynchronous I/O
			 * structure.  The last refrence to the structure
			 * is being released, so there really is no one
			 * else that could be using this structure.
			 */
			v_lock(&aiop->aio_lock, spl);
			VN_RELE(((struct AIO_buf *) bp)->ab_vnp);
			abuf_free((struct AIO_buf *) bp);
			(void) p_lock(&aiop->aio_lock, SPLFS);
		}
		ASSERT_DEBUG(aiop->aio_count == 0,
			"aio_deref aio_count not zero");
		kmem_free((caddr_t) aiop, sizeof(struct AIO));
	}
	v_lock(&aiop->aio_lock, spl);
}

/*
 * Asynchronous I/O headers
 * They contain the necessary information to do asynchronous I/O.
 * An asynchronous I/O header will be on one of three different lists.
 * When free it is in the free list, when allocated and the I/O queued,
 * but not completed it will be on a pending I/O list.  When the I/O is
 * completed it is moved to a completed list.  After the completion has been
 * polled by the user process or on exit is is moved back to the free list.
 */

struct	AIO_buf	*abuflist;		/* free-list header */
lock_t		abuf_mutex;		/* lock (de)alloc of async header */
sema_t		abuf_wait;		/* wait here for async I/O headers */

/*
 * abufinit()
 *	Initialize linked list of free asynchronous buffer structures.
 *
 * These do not actually point to buffers, but rather to pages that
 * are being used for direct (raw) I/O.
 */

abufinit()
{
	register struct AIO_buf *sp;
	register int i;

	/*
	 * Init asynchronous I/O buffer structures, alloc list, 
	 * mutex structures, and abuf header semas.
	 *
	 * abuflist is initially NULL, since it lives in .bss
	 */

	init_lock(&abuf_mutex);
	init_sema(&abuf_wait, 0);

	if (abuf) {
		for (i = 0, sp = abuf; i < nabuf; i++, sp++) {
			bufinit((struct buf *) sp);
			sp->ab_buf.av_forw = (struct buf *) abuflist;
			abuflist = sp;
		}
	}
}

/*
 * abuf_alloc()
 *	Allocate an async I/0 buf header.  Conditionally wait for one
 *	if none free.  Return error code in error.
 */

struct AIO_buf *
abuf_alloc(can_wait, error)
	int	can_wait;
	int	*error;
{
	struct AIO_buf *bp;
	spl_t	s;

	*error = 0;
	for (;;) {
		s = p_lock(&abuf_mutex, SPLFS);
		if (abuflist) {
			bp = abuflist;
			abuflist = (struct AIO_buf *) bp->ab_buf.av_forw;
			v_lock(&abuf_mutex, s);
			return (bp);
		} else if (!can_wait) {
			v_lock(&abuf_mutex, s);
			*error = EWOULDBLOCK;
			return (0);
		}
		switch (p_sema_v_lock(&abuf_wait, (PZERO + 1) | PCATCH,
						&abuf_mutex)) {
		case 0:		/* due to v_sema */
		case STOPPED:	/* due to job control */
			continue;
		case PCATCH:
			*error = EINTR;
			return (0);
		}

	}
}

/*
 * abuf_free()
 *	Release an async I/0 buf header.
 */

abuf_free(bp)
	register struct AIO_buf *bp;
{
	spl_t	s;

	s = p_lock(&abuf_mutex, SPLFS);
	bp->ab_buf.b_flags &= ~(B_PHYS|B_PAGET|B_UAREA|B_DIRTY);
	bp->ab_buf.av_forw = (struct buf *) abuflist;
	abuflist = bp;
	if (blocked_sema(&abuf_wait))
		vall_sema(&abuf_wait);
	v_lock(&abuf_mutex, s);
}

/*
 * abiodone()
 *	Move the asynchronous I/O request from the waiting list to
 *	the completed list.  Signal process that I/O has completed.
 */
abiodone(bp)
	struct	 buf *bp;
{
	register struct AIO *aio;
	spl_t	spl;
	proc_t	*procp;
	int	sig;

	/* local VM no need */
	/* vsunlock(bp->b_un.b_addr, bp->b_bcount, bp->b_flags & B_READ); */

	aio = ((struct AIO_buf *) bp)->ab_aio;
	ASSERT(aio != (struct AIO *) NULL, "AIO structure is NULL");
	/*
	 *+ In the asynchronous I/O done routine (abiodone), the process
	 *+ that queued the I/O request no longer has an AIO structure and
	 *+ thus is not enabled for asynchronous I/O.
	 */
	spl = p_lock(&aio->aio_lock, SPLFS);

	/* remove it from the list of I/O waiting to complete */
	AQ_DEQUEUE(&aio->aio_pend, bp);

	/* then add it to the list of completed I/O's */
	AQ_APPEND(&aio->aio_done, bp);

	/*
	 * Once we release the lock, the buffer we just moved may be freed.
	 * Copy out the info we need.
	 */
	procp = ((struct AIO_buf *) bp)->ab_procp;
	sig = ((struct AIO_buf *) bp)->ab_sig;
	v_lock(&aio->aio_lock, spl);

	/* send signal if requested */
	if (sig != 0) {
		psignal(procp, sig);
	}

	/* wakeup anyone that is waiting for the I/O to complete */
	vall_sema(&aio->aio_sema);
}

/*
 * aphysio()
 *	Raw asynchronous I/O.  Called from driver read/write routines.
 *
 * The arguments are
 *	A pointer to the device object, through which the strategy
 *	  and minphys routines are derived.
 *	Read/write flag
 *	The uio to use in transferring the data to/from the user
 *	The excess amount of this read that extends beyond EOF for this
 *	device.
 *	The flags for this request.
 *
 * Essentially all the work is computing physical addresses and
 * validating them.  If the user has the proper access privileges, the
 * process is marked not swappable and the pages involved in the I/O are
 * faulted and "locked".  The locking is based on the local model VM,
 * implicitly locking by faulting into the resident set (see vslock()).
 *
 * This allocates the buffer again for each element of the iovec.  Could
 * be smarter and allocate once, but think this isn't worth much.  Also
 * the iovec is limited to a length of one.
 */

aphysio(dev, rw, uio, excess, flags)
	struct		dev *dev;
	int		rw;
	struct	uio	*uio;
	int		excess;
	int		flags;
{
	register struct buf *bp;
	register struct AIO *aio;
	register struct iovec *iov;
	register int	c;
	register proc_t *p = u.u_procp;
	caddr_t		a;
	long		rs_need;
	spl_t		spl;
	int		error;

	/*
	 * Allow iov of only one element and it must have a length of
	 * less than max_RAW_IO for now.
	 */
	if ((uio->uio_iovcnt > 1) || (uio->uio_iov->iov_len > max_RAW_IO))
		return(E2BIG);

	for (iov = uio->uio_iov; uio->uio_iovcnt; iov++, uio->uio_iovcnt--) {
		/*
		 * Verify address space can accept the transfer.
		 * Note: useracc() fails phys-mapped address space.
		 */
		if (useracc(iov->iov_base,(uint)iov->iov_len,rw==B_READ?B_WRITE:B_READ) == NULL) {
			return(EFAULT);
		}

		/*
		 * For now if any of I/O is out of the partition, reject the
		 * entire operation.
		 */
	
		if (excess != 0)
			return (EINVAL);

		/* get pointer to AIO structure */
		aio = p->p_aio;
		ASSERT(aio != (struct AIO *) NULL,
			"AIO structure is NULL");
		/*
		 *+ In the asynchronous physical I/O routine
		 *+ (aphysio), the process that is making the
		 *+ I/O request no longer has an AIO structure
		 *+ and thus is not enabled for asynchronous I/O.
		 */

		bp = (struct buf *) abuf_alloc((flags & IO_NDELAY) ? 0 : 1,
				&error);
		if (bp == NULL)
			return (error);

		(void) p_sema(&bp->b_alloc, PRIBIO+1);	/* allocate buffer */
		bp->b_error = 0;
		bp->b_flags = B_PHYS;			/* iov_len == 0? */
		bp->b_proc = p;
		bp->b_un.b_addr = iov->iov_base;

		/* fill in the extended part of the buf structure */
		((struct AIO_buf *) bp)->ab_aio = aio;
		((struct AIO_buf *) bp)->ab_extra = excess;
		((struct AIO_buf *) bp)->ab_key =
				((struct AIO_uio *) uio)->au_key;
		((struct AIO_buf *) bp)->ab_sig =
				((struct AIO_uio *) uio)->au_sig;
		((struct AIO_buf *) bp)->ab_procp =
				((struct AIO_uio *) uio)->au_procp;
		((struct AIO_buf *) bp)->ab_vnp =
				((struct AIO_uio *) uio)->au_vnp;
		/*
		 * Get a reference to the vnode.  This will not be
		 * released until after the I/O is complete.  This
		 * will insure that the vnode does not go away on us
		 */
		VN_HOLD(((struct AIO_uio *) uio)->au_vnp);

		bp->b_flags = B_PHYS | rw;
		bp->b_devp = dev;
		bp->b_blkno = btodb(uio->uio_offset);
		bp->b_bcount = iov->iov_len;

		/*
		 * Restrict transfer to what driver/HW can manage,
		 * and in any case to a max system IO size.
		 * Then determine exact Rset size needed for the
		 * transfer and grow Rset (if necessary) to allow it
		 * (the idea being to go for greater device thruput).
		 *
		 * PFF won't adjust Rset during this since
		 * process won't do PFF adjust unless returning
		 * to user-mode.
		 */

		bp->b_iotype = B_RAWIO;
		if (bp->b_bcount > max_RAW_IO)
			bp->b_bcount = max_RAW_IO;
		c = bp->b_bcount;
		DEV_MINPHYS(dev, bp);

		/*
		 * Do not allow it to break the I/O up into multiple
		 * I/O's for now
		 */
		if (c != bp->b_bcount) {
			abuf_free((struct AIO_buf *) bp);
			/* release buffer */
			v_sema(&bp->b_alloc);
			return(E2BIG);
		}
		a = bp->b_un.b_addr;

		rs_need = (((int)a & CLOFSET) + c + CLOFSET) / CLBYTES;
		if (p->p_rscurr < rs_need) {
			/*
			 * If can't adjust, fail the IO -- system
			 * has unreasonable vt_maxRS and truncating
			 * IO can do strange things (eg, to tape).
			 */
			vsetRS(rs_need);
			if (p->p_rscurr < rs_need) {
				/* release buffer */
				v_sema(&bp->b_alloc);
				abuf_free((struct AIO_buf *) bp);
				return(ENOMEM);
			}
		}

		/*
		 * Nail the pages in memory and do the transfer.
		 * Note: current vslock() allows bump p_noswap
		 * after vslock() returns -- maybe better to
		 * have vslock() bump, vsunlock() decr.
		 */

		/* non-swappable while faulting pages */
		++p->p_noswap;

		/* add it to the list of pending I/O's--lock against pageout */
		spl = p_lock(&aio->aio_lock, SPLFS);
		AQ_APPEND(&aio->aio_pend, bp); 
		aio->aio_count++;
		v_lock(&aio->aio_lock, spl);

		vslock(a, c, rw & B_READ);

		/*
		 * Clear noswap flag, pending I/O will keep
		 * process from swapping
		 */
		--p->p_noswap;

		BIODONE(bp) = 0;	/* new IO starting */
		bp->b_flags |= B_CALL;	/* have it call special */
		bp->b_iodone = abiodone;/* biodone routine */

		DEV_STRATEGY(dev, bp, (daddr_t)0);

		if (rw & B_READ)  {
			l.cnt.v_phreadblks += btodb(c);
			l.cnt.v_phread++;
			u.u_pst.ps_phread++;
		} else {
			l.cnt.v_phwriteblks += btodb(c);
			l.cnt.v_phwrite++;
			u.u_pst.ps_phwrite++;
		}

		iov->iov_len -= c;
		uio->uio_resid -= c;
		uio->uio_offset += c;

	}
	return (0);
}

/*
 * spec_dio - direct raw I/O for character special devices
 */

int
spec_dio(vp, fp, cbuf, count, offset, rw, aioptr)
	struct	vnode *vp;
	struct	file *fp;
	char	*cbuf;
	int	count;
	long	offset;
	enum	uio_rw rw;
	struct	AIO_req	*aioptr;
{
	struct AIO_uio auio;
	struct uio *uiop = (struct uio *) &auio ;
	struct iovec aiov;
	int error;
	int flags;
	int sig;
	int pid;

	flags = aioptr->aio_flags;
	/* If async validate parameters and pass them on */
	if (flags & AIO_ASYNC) {
		if (((VTOS(vp)->s_devp->d_flags) & D_ASYNC) == 0) {
			return (EINVAL);
		}

		/* Fail if Asynchronous I/O is not enabled */
		if (nabuf == 0) {
			return (EINVAL);
		}

		/* disallow zero length reads and writes */

		if (count == 0) {
			return (EINVAL);
		}			

		/*
		 * Fail if Asynchronous I/O is not initialized for
		 * this process
		 */
		if (u.u_procp->p_aio == NULL) {
			return (EUNATCH);
		}

		/* Do not allow more than maxaio request at a time */
		if (u.u_procp->p_aio->aio_count >= maxaio) {
			return (EMFILE);
		}

		sig = aioptr->aio_sig;
		/* check for valid signal number */
		if (sig < 0 || sig >= NSIG) {
			return (EINVAL);
		}
		if (sig != 0 && SIGM_ISMEMBER(sigmask(sig), CANTMASK)) {
			return (EINVAL);
		}

		pid = aioptr->aio_pid;
		if ((sig != 0) && !(pid == 0 || pid == u.u_procp->p_pid)) {
			return (EPERM);
		}

		/*
		 * pass the key, signal and process values with
		 * the uio structure
		 */
		auio.au_key = aioptr->aio_key;
		auio.au_sig = sig;
		auio.au_procp = u.u_procp;
		auio.au_vnp = vp;

	}
	aiov.iov_base = (caddr_t) cbuf;
	uiop->uio_resid = aiov.iov_len = count;
	uiop->uio_iov = &aiov;
	uiop->uio_iovcnt = 1;
	uiop->uio_segflg = UIOSEG_USER;

	/* Writability of file already verified */

	/*
	 * It is not necessary to do IO to character devices with
	 * locked vnode.  This is because the file size or shape
	 * cannot be changed.
	 */

	/* offset was already validated */
	uiop->uio_offset = fp->f_offset = offset;
	error = VOP_RDWR(vp, uiop, rw,
			(((fp->f_flag & FAPPEND) ? IO_APPEND : 0) |
			((fp->f_flag & FSYNC) ? IO_SYNC : 0) |
			((flags & AIO_DELAY) ? 0 : IO_NDELAY) |
			((flags & AIO_ASYNC) ? IO_ASYNC : 0)) ,
			fp->f_cred);

	/* If not async return to the user the number of bytes transferred */
	if ((flags & AIO_ASYNC) == 0) {
		u.u_r.r_val1 = count - uiop->uio_resid;
	}
	return (error);
}
	
/*
 * aio_poll - return list of completed asynchronous I/O request.
 */
aio_poll(aio, count, key, mask, aiop, timeout)
	struct AIO *aio;
	int count, key, mask;
	struct AIO_status *aiop;
	int timeout;
{
	struct AIO_buf *bp;
	struct AIO_status status;
	spl_t spl;
	
	int i;

	/* Do not allow negative counts */
	if (count < 0) {
		u.u_error = EINVAL;
		return (-1);
	}	
	/* Fail if Asynchronous I/O is not enabled *.
	if (nabuf == 0) {
		u.u_error = EINVAL;
		return (-1);
	}
	/* Only allow timeout values of 0 (don't wait) and -1 (wait forever) */
	if (!(timeout == 0 || timeout == -1)) {
		u.u_error = EINVAL;
		return (-1);
	}
	/* Special case counts of zero to always succeed */
	if (count == 0) {
		u.u_error = 0;
		return (0);
	}	
	spl = p_lock(&aio->aio_lock, SPLFS);

	if (AQ_EMPTY(&aio->aio_done)) {
		if (timeout == 0) {
			v_lock(&aio->aio_lock, spl);
			return (EWOULDBLOCK);
		} else {
			while (AQ_EMPTY(&aio->aio_done)) {
				switch (p_sema_v_lock(&aio->aio_sema,
					(PZERO + 1) | PCATCH,
					&aio->aio_lock)) {
				case 0:		/* due to v_sema */
				case STOPPED:	/* due to job control */
					spl = p_lock(&aio->aio_lock, SPLFS);
					continue;
				case PCATCH:
					u.u_error = EINTR;
					return (-1);
				}
			}				
		}
	}

	/* mask key value for comparisons */
	key &= mask;

	bp = (struct AIO_buf *) aio->aio_done.dl_next;
	
	for (i = 0; (i < count) && (bp != (struct AIO_buf *)&aio->aio_done);) {
		if (key == (bp->ab_key & mask)) {
			status.aio_error = geterror((struct buf *) bp);
			status.aio_size = bp->ab_buf.b_bcount;
			status.aio_key = bp->ab_key;
			aio->aio_count--;
			/* remove it from the list of completed I/O's */
			AQ_DEQUEUE(&aio->aio_done, bp);
			v_sema(&bp->ab_buf.b_alloc);	/* release buffer */
			/*
			 * Release the vnode reference now that the I/O
			 * is completed.  This must be done in the user's
			 * context, otherwise it could be done at
			 * abiodone time.  The lock on the aio structure
			 * must be released since the VN_RELE may cause 
			 * I/O to be done.
			 */
			v_lock(&aio->aio_lock, spl);
			VN_RELE(bp->ab_vnp);
			abuf_free(bp);

			/* copy out this entry */
			if ((u.u_error = copyout((caddr_t) &status,
					(caddr_t) (&aiop[i]),
					sizeof(struct AIO_status))) != 0) {
				return(-1);
			}
			i++;

			spl = p_lock(&aio->aio_lock, SPLFS);
			/*
			 * Since after we released the lock another process
			 * could have changed the buffer queue in the aio
			 * structure, we must start from the begining of the
			 * list again.  If there is nothing on the list it
			 * is time to leave, we have found at least one
			 * request.
			 */

			bp = (struct AIO_buf *) aio->aio_done.dl_next;
		} else {
			bp = (struct AIO_buf *) bp->ab_buf.b_forw;
		}
	}

	v_lock(&aio->aio_lock, spl);
	return (i);
}
