/* $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: vm_swp.c 1.29 89/11/17 $"

/*
 * vm_swp.c
 *	Various kinds of physical IO: physio(), swap(), etc.
 */

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

#include "sys/types.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/devsw.h"
#include "sys/sysmacros.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/vnode.h"
#include "sys/buf.h"
#include "sys/conf.h"
#include "sys/swap.h"
#include "sys/timer.h"
#include "sys/proc.h"
#include "sys/vm.h"
#include "sys/map.h"
#include "sys/uio.h"
#include "sys/var.h"
#include "sys/cmn_err.h"

#include "sys/engine.h"

#include "sys/pte.h"
#include "sys/intctl.h"
#include "sys/plocal.h"
#include "sys/gate.h"

/*
 * Swap IO headers -
 * They contain the necessary information for the swap I/O.
 * At any given time, a swap header can be in three
 * different lists. When free it is in the free list, 
 * when allocated and the I/O queued, it is on the swap 
 * device list, and finally, if the operation was a dirty 
 * page push, when the I/O completes, it is inserted 
 * in a list of cleaned pages to be processed by the cleanup daemon.
 */

struct	buf	*swbuf;			/* set in allockds() to alloc'd hdrs */
struct	buf	*bswlist;		/* free-list header */
lock_t		swbuf_mutex;		/* lock alloc/dealloc of swap header */
sema_t		swbuf_wait;		/* wait here for swap headers */
sema_t		swap_sema;		/* mutex swdevt[] table */
struct	dev	swdev;			/* dev object for swap device */
extern struct	devops swdevops;	/* devops for swap driver */
extern	int	sw_b_major;		/* major number of swap device */

struct	buf	*swbuf_alloc();

#ifdef	PERFSTAT
int		numswfree;		/* current # free swap buffers */
int		minswfree;		/* min # free swap headers ever */
#endif	/* PERFSTAT  */
/*
 * swapdevinit()
 *	Set up the dev associated with the swap pseudo-driver.
 *
 * Only a single dev is needed for the swap device.  Initialize
 * it and make it available.
 */

swapdevinit()
{
	swdev.d_dev = makedev(sw_b_major, 0);
	swdev.d_refcnt = 1;
	swdev.d_ops = &swdevops;
	if (devsw_add(&swdev) != 0) {
		cmn_err(CE_PANIC, "bswinit: adding swdev");
		/*
		 *+ The initialization of the swapping driver ran into
		 *+ problems when adding the swap device to devsw.  This
		 *+ is probably due to a device driver that is
		 *+ also adding this device to devsw.
		 */
	}
	dev_deref(&swdev);
}

/*
 * bswinit()
 *	Count swap devices and initialize linked list of free swap headers.
 *
 * These do not actually point to buffers, but rather to pages that
 * are being swapped in and out.
 */

bswinit()
{
	register struct buf *sp;
	register int i;
	extern	caddr_t	topmem;

	nswap = roundup(swdevt[0].sw_nblks, dmmax_sw) * nswdev;
	swfree(0);

	/*
	 * Init swap-header alloc/wait list and mutex structures,
	 * and swbuf header semas.
	 *
	 * Bufinit() does a redundant init of "b_alloc" semaphore,
	 * since allocation is off the swap-header list (bswlist).
	 *
	 * bswlist is initially NULL, since it lives in .bss
	 */

	init_lock(&swbuf_mutex);
	init_sema(&swbuf_wait, 0);
	init_sema(&swap_sema, 1);

	for (i = 0, sp = swbuf; i < v.v_pbuf; i++, sp++) {
		bufinit(sp);
		sp->av_forw = bswlist;
		bswlist = sp;
	}

	init_sema(&drain_dirty, 0);
	init_lock(&swpq_mutex);
	init_sema(&swapout_sync, 0);
	init_sema(&runout, 0);
}

/*
 * swap I/O -
 *	Copy some pages in/out of memory.
 *
 * 'pte' arg is pte of 1st page involved with the IO; pte's assumed
 * contiguous.
 *
 * This is basically a front-end to swapio(), to allow pageout() to
 * conditionally allocate swap-buf headers.
 */

swap(p, dblkno, pte, nbytes, flag, vp)
	struct	proc	*p;
	swblk_t		dblkno;
	struct	pte	*pte;
	int		nbytes;
	int		flag;
	struct vnode	*vp;
{
	struct buf	*bp;

	/*
	 * Allocate a swap-header and set it up for the IO,
	 * waiting for it if none immediately available, then
	 * do the swapio and release the buffer.
	 */

	bp = swbuf_alloc(1);
	swapio(bp, p, dblkno, pte, nbytes, flag, vp);
	swbuf_free(bp);
}

/*
 * swapio()
 *	Swap-IO -- also allows asynch IO to be started if B_DIRTY is
 *	in `flag'.
 *
 * 'pte' arg is pte of 1st page involved with the IO; pte's assumed
 * contiguous.
 *
 * Assumes all drivers that get swap/page IO can handle a KLMAX*CLBYTES
 * size request for either B_PTEIO or B_PTBIO.
 */

swapio(bp, p, dblkno, pte, nbytes, flag, vp)
	register struct buf *bp;
	struct	proc	*p;
	swblk_t		dblkno;
	struct	pte	*pte;
	int		nbytes;
	register int	flag;
	struct vnode	*vp;
{
	register uint	c;

	/*
	 * Set up buf-header for the IO.
	 */

	bp->b_flags = B_PHYS | flag;
	if ((flag & (B_DIRTY|B_PGIN)) == 0)
		if (flag & B_READ)
			l.cnt.v_pswpin += btoc(nbytes);
		else
			l.cnt.v_pswpout += btoc(nbytes);
	bp->b_proc = p;				/* information only */
	bp->b_un.b_pte = pte;
	bp->b_iotype = (flag & B_PAGET) ? B_PTBIO : B_PTEIO;
	while (nbytes > 0) {
		bp->b_bcount = nbytes;
		bp->b_blkno = dblkno;
		bp->b_devp = vp->v_rdevp;
		bp->b_vp = vp;
		if (nbytes > (KLMAX-1)*CLBYTES) {
			/*
			 * Big enough that must let driver limit it.
			 * Insure transfer is multiple of CLBYTES in
			 * case B_PTEIO and driver limits to non-multiple.
			 */
			VOP_MINPHYS(bp);
			bp->b_bcount &= ~CLOFSET;
		}
		c = bp->b_bcount;
		ASSERT((c & (NBPG-1)) == 0, "swapio: count");
		/*
		 *+ The swapio function, which does the actual swapping
		 *+ of pages, was invoked with a size that wasn't a multiple
		 *+ of the system page size.
		 */
		physstratvp(bp, PSWP);
		if (flag & B_DIRTY) {
			ASSERT(c == nbytes, "swapio: big push");
			/*
			 *+ swapio initiated an asynchronous write with
			 *+ a size that was greater than the maximum size
			 *+ that drivers can handle in a single request.
			 */
			return;
		}
		bp->b_un.b_pte += btop(c);
		if (bp->b_flags & B_ERROR) {
			/*
			 * Currently, panic if can't Read or Write a
			 * page-table or Uarea (this can be handled better).
			 * If reading or writing normal pages, can just kill.
			 */
			if (flag & (B_UAREA|B_PAGET))
				cmn_err(CE_PANIC, "swapio: hard IO err in swap");
				/*
				 *+ A hard disk I/O error occurred when the kernel tried to
				 *+ swap a process's page tables or U-area.
				 */
			/*
			 * If vp is marked bad, assume mapped file error
			 * else generic swap IO error.
			 */
			if (vp->v_badmap)
				mmap_swkill(p, (int) vp->v_badmap);
			else
				swkill(p, "swap IO error");
			break;
		}
		nbytes -= c;
		dblkno += btodb(c);
	}
}

/*
 * swbuf_alloc()
 *	Allocate a swap-buf header.  Conditionally wait for one if none free.
 */

struct buf *
swbuf_alloc(can_wait)
	int	can_wait;
{
	struct buf *bp;
	spl_t	s;

	for (;;) {
		s = p_lock(&swbuf_mutex, SPLSWP);
		if (bswlist) {
			bp = bswlist;
			bswlist = bp->av_forw;
#ifdef	PERFSTAT
			if (--numswfree < minswfree)
				minswfree = numswfree;
#endif	/* PERFSTAT  */
			v_lock(&swbuf_mutex, s);
			return (bp);
		} else if (!can_wait) {
			v_lock(&swbuf_mutex, s);
			return (0);
		}
		(void)p_sema_v_lock(&swbuf_wait, PSWP+1, &swbuf_mutex);
	}
}

/*
 * swbuf_free()
 *	Release a swap-buf header.
 */

swbuf_free(bp)
	register struct buf *bp;
{
	spl_t	s;

	s = p_lock(&swbuf_mutex, SPLSWP);
	bp->b_flags &= ~(B_PHYS|B_PAGET|B_UAREA|B_DIRTY);
	bp->av_forw = bswlist;
	bswlist = bp;
	if (blocked_sema(&swbuf_wait))
		vall_sema(&swbuf_wait);
#ifdef	PERFSTAT
	++numswfree;
#endif	/* PERFSTAT  */
	v_lock(&swbuf_mutex, s);
}

/*
 * mmap_swkill()
 *	Kill a process due to mapped-file fault error.
 */

mmap_swkill(p, reason)
	struct	proc	*p;
	int	reason;
{
	swkill(p, (reason & MM_STALETEXT)
			? "text modification"
			: "IO error in program text or mapped file");
}

/*
 * swkill()
 *	Print mesg as to why, then kill process.
 */

swkill(p, mesg)
	struct proc *p;
	char	*mesg;
{

	/*
	 * To be sure no looping (e.g. sched() trying to swap out) mark
	 * process locked in core (as though done by user) so no one will
	 * try to swap it out.
	 */

	++p->p_noswap;

	/*
	 * If process has already been sent a
	 * kill signal, do nothing.
	 */

	if (SIGM_ISMEMBER(p->p_sig, SIGKILL))
		return;

	/*
	 * Generate a "useful" message and kill the process.
	 */

	cmn_err(CE_NOTE, "pid %d: killed due to %s\n", p->p_pid, mesg);
	/*
	 *+ A process was killed during a swap operation due to
	 *+ an error or a resource shortage.  The message printed
	 *+ will give more information.
	 */
	cmn_err(CE_TO_USER, "sorry, pid %d was killed due to %s\n", p->p_pid, mesg);
	/*
	 *+ A process was killed during a swap operation due to
	 *+ an error or a resource shortage.  The message printed
	 *+ will give more information.
	 */

	psignal(p, SIGKILL);
}

/*
 * physio()
 *	Raw 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.
 *	A buffer, which will always be a special buffer
 *	  header owned exclusively by the device for this purpose
 *	Read/write flag
 *	The uio to use in transferring the data to/from the user
 *
 * 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.
 *
 * This physio() works for mono-P drivers if they have same buf-header
 * and have initialized the buffers (see bufinit()).
 */

/*
 * Max size RAW IO request (bytes).
 */

long	max_RAW_IO = 128*1024;

physio(dev, bp, rw, uio)
	register struct	dev *dev;
	register struct buf *bp;
	int		rw;
	struct	uio	*uio;
{
	register struct iovec *iov;
	register int	c;
	register struct	proc *p = u.u_procp;
	caddr_t		a;
	int		resid;
	int		error = 0;
	long		rs_need;
	int		abflg;

	if (abflg = (bp == NULL))
		bp = swbuf_alloc(1);

	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) {
			error = EFAULT;
			break;
		}

		(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;

		while (iov->iov_len > 0) {
			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;
			DEV_MINPHYS(dev, bp);

			c = bp->b_bcount;
			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) {
					bp->b_flags |= B_ERROR;
					bp->b_error = ENOMEM;
					break;
				}
			}

			/*
			 * 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.
			 */

			++p->p_noswap;			/* non-swappable */
			vslock(a, c, rw & B_READ);
			L_INCR(&procstate.ps_physwait);
			physstrat(bp, dev, PRIBIO);
			L_DECR(&procstate.ps_physwait);
			/* vsunlock(a, c, rw); */	/* local VM no need */
			--p->p_noswap;			/* re-swappable */
			c -= bp->b_resid;
			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++;
			}
			bp->b_un.b_addr += c;
			iov->iov_len -= c;
			uio->uio_resid -= c;
			uio->uio_offset += c;
			/* temp kludge for tape drives */
			if (bp->b_resid || (bp->b_flags&B_ERROR))
				break;
		}
		bp->b_flags &= ~B_PHYS;
		error = geterror(bp);
		resid = bp->b_resid;
		v_sema(&bp->b_alloc);			/* release buffer */
		if (resid || error)			/* resid: for tapes */
			break;
	}
	if (abflg)
		swbuf_free(bp);
	return (error);
}

/*
 * minphys()
 *	Default procedure to limit phys, swap transfers.
 *
 * Most drivers will have their own procedure.
 */

unsigned
minphys(bp)
	struct buf *bp;
{
	if (bp->b_bcount > max_RAW_IO)
		bp->b_bcount = max_RAW_IO;
}

/*
 * Called from driver read/write routines before physio. physck
 * truncates raw i/o requests that overflow partition boundaries.
 *
 * returns 0 if ok (with possible changes in the request size).
 * returns -1 if reading from exactly the end of the partition
 *	ie ok, but i/o not required since size is 0.
 * returns EINVAL otherwise.
 */
physck(lim, uio, rw, diff)
	off_t lim;
	struct uio *uio;
	int *diff;
{
	register int cnt;
	register struct iovec *iov;
	register off_t hi;

	*diff = 0;
	if (uio->uio_offset >= lim) {
		if (uio->uio_offset > lim || rw == B_WRITE)
			return(EINVAL);
		return(-1);	/* ok, but no i/o */
	}
	hi = uio->uio_offset + uio->uio_resid;
	if (hi > lim) {
		cnt = hi - lim;
		*diff = cnt;	/* return diff between requested and actual */
		uio->uio_resid -= cnt;
		iov = uio->uio_iov; iov += uio->uio_iovcnt;
		while (cnt) {
			--iov;
			if (iov->iov_len <= cnt) {
				if (--uio->uio_iovcnt == 0)
					return(EINVAL);
				cnt -= iov->iov_len;
			} else {
				iov->iov_len -= cnt;
				cnt = 0;
			}
		}
	}
	return(0);
}
