/* $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_sw.c 1.35 90/09/07 $"

/*
 * vm_sw.c
 *	Swap "driver"; handles interleaving.
 */

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

#include "sys/types.h"
#include "sys/devsw.h"
#include "sys/sysmacros.h"
#include "sys/signal.h"
#include "sys/param.h"
#include "sys/debug.h"
#include "sys/systm.h"
#include "sys/mutex.h"
#include "sys/buf.h"
#include "sys/conf.h"
#include "sys/swap.h"
#include "sys/user.h"
#include "sys/errno.h"
#include "sys/vnode.h"
#include "sys/map.h"
#include "sys/uio.h"
#include "sys/file.h"
#include "sys/vmmeter.h"
#include "sys/cmn_err.h"
#include "sys/open.h"

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

extern int maxpgio;		/* page-rate threshold */
extern int nswdev;		/* number of swap devices */
int	nswap;			/* size of swap space */

int	nswapmap;		/* # entries in swap-space map */
struct	map	*swapmap;	/* swap-space allocation map */


extern	int	swstrat();
extern	int	swminphys();
extern	int	swread();
extern	int	swwrite();
extern	int	nodev();
extern	int	nulldev();
extern	int	nostrmsgio();
extern	int	nostrpoll();
extern	int	swopen();

struct	devops	swdevops = {
	swopen,			/* d_open */
	nulldev,		/* d_close */
	swstrat,		/* d_strategy */
	swminphys,		/* d_minphys */
	nodev,			/* d_psize */
	nodev,			/* d_print */
	swread,			/* d_read */
	swwrite,		/* d_write */
	nodev,			/* d_ioctl */
	nodev,			/* d_mmap */
	0,			/* d_intr */
	0,			/* d_str */
	nostrmsgio,		/* d_strmsgio */
	nostrpoll		/* d_strpoll */
};

/*
 * swopen()
 *	Only used to set up flags so that unsigned offsets may be used
 */
swopen(dp)
	struct dev *dp;
{
	dp->d_flags |= D_UNSOFF;
	return(0);
}

/*
 * swstrat()
 *	Indirect driver for multi-controller paging.
 *
 * Note that since physio() doesn't know about interleaved swap, RAW
 * IO only tracks real swap-space if requests don't cross dmmax_sw boundaries.
 * Swap IO obeys this.
 */

/*ARGSUSED*/
swstrat(dp, bp)
	struct	dev *dp;
	register struct buf *bp;
{
	register struct swdevt *sw;
	int	sz;
	int	off;
	int	seg;
	extern	dev_t rootdev;
	unsigned long blkno = bp->b_blkno;

	bp->b_status = B_STATUS_OK;	  /* the default, for quick returns */
	sz = howmany(bp->b_bcount, DEV_BSIZE);
	/*
	 * A mini-root gets copied into the front of the swap
	 * and we run over top of the swap area just long
	 * enough for us to do a mkfs and cpio of the real
	 * root.  10M is reserved for the miniroot.  This should be
	 * enough for the forseeable future - means swap must be
	 * at least 10M+. Since we explicitly use the first swap
	 * device when running on the miniroot, we can have only
	 * one swap device. This avoids problems where the relocated
	 * block number lands on a different, and unconfigured,
	 * disk stripe.
	 */
#define	MINIROOTSIZE	20480
	if (rootdev == swdevt[0].sw_dev) {
		blkno += MINIROOTSIZE;
		sw = &swdevt[0];
	} else {
		/*
		 * Insist request doesn't cross dmmax_sw boundary.
		 */
		off = blkno % dmmax_sw;
		if (off+sz > dmmax_sw) {
			bp->b_flags |= B_ERROR;
			iodone(bp);
			return;
		}
		seg = blkno / dmmax_sw;
		sw = &swdevt[seg % nswdev];
		blkno = (seg / nswdev) * dmmax_sw + off;
	}
	bp->b_blkno = (daddr_t)blkno;

	if (sw->sw_devp == (struct dev *)0) {		/* blkno was invalid */
		bp->b_flags |= B_ERROR;
		iodone(bp);
		return;
	}
	bp->b_devp = sw->sw_devp;

	/*
	 * Insure it fits in swap partition.
	 */

	if (bp->b_blkno+sz > sw->sw_nblks) {
		bp->b_flags |= B_ERROR;
		iodone(bp);
	} else
		DEV_STRATEGY(sw->sw_devp, bp, (daddr_t)0);
}

/*
 * swminphys()
 *	Indirect minphys procedure -- call the one from the real driver.
 *
 * Don't call driver minphys() if out of range on blocks; swstrat() will
 * error the request.
 *
 * Check is redundant with swstrat(); done here in case called thru
 * swread()/swwrite().  Awkward in swread()/swwrite() since uio may
 * reference multiple swap chunks.  Most page IO's won't call swminphys()
 * anyhow (see swapio()).
 *
 * Note: very low probability race with swapon() turning on sw_freed
 * between now and physio() calling swstrat() (won't have limited count).
 */

/*ARGSUSED*/
swminphys(dp, bp)
	struct	dev *dp;
	register struct buf *bp;
{
	register struct swdevt *sw;
	int	seg;
	daddr_t blkno;

	seg = bp->b_blkno / dmmax_sw;
	sw = &swdevt[seg % nswdev];
	blkno = (seg / nswdev) * dmmax_sw + (bp->b_blkno % dmmax_sw);

	if ((blkno + howmany(bp->b_bcount, DEV_BSIZE)) <= sw->sw_nblks) {
		bp->b_devp = sw->sw_devp;
		DEV_MINPHYS(bp->b_devp, bp);
	}
}

swread(dp, uio)
	struct dev *dp;
	struct uio *uio;
{
	return (physio(dp, (struct buf *)0, B_READ, uio));
}

swwrite(dp, uio)
	struct dev *dp;
	struct uio *uio;
{
	return (physio(dp, (struct buf *)0, B_WRITE, uio));
}

/*
 * swapon()
 *	called from swapfunc() to add swap device.
 *
 * Return EEXIST if already swapping on this device.
 *
 */

swapon(si)
	swpi_t *si;
{
	struct vnode *vp;
	register struct swdevt *sp;
	register int i;
	int nblks;
	struct	dev *dev;

	u.u_error =
		lookupname(si->si_buf, UIOSEG_USER, FOLLOW_LINK,
			(struct vnode **)0, &vp);
	if (u.u_error)
		return;
	if (vp->v_type != VBLK) {
		u.u_error = ENOTBLK;
		VN_PUT(vp);
		return;
	}
	/*
	 * Establish a reference on the swap dev.
	 */
	dev = vp->v_rdevp = dev_ref(vp->v_rdev);
	if (!dev) {
		VN_PUT(vp);
		u.u_error = ENXIO;
		return;
	}
	VN_PUT(vp);

	/*
	 * Search for existing swap device and return error if found.
	 * This relies on there being no "holes" in swdevt[], ie all
	 * available slots are at the end of the table.
	 */

	(void)p_sema(&swap_sema, PWAIT);
	for (sp = &swdevt[0], i = 0; i < nswdev && sp->sw_devp; sp++, i++) {
		if (sp->sw_devp == dev) {
			dev_deref(dev);
			u.u_error = EEXIST;
			goto bad;
		}
	}
	if (i >= nswdev) {
		dev_deref(dev);
		u.u_error = ENOSPC;
		goto bad;
	}
	/*
	 * Remove the swap dev to prevent mounts from inadvertantly
	 * being done on this dev.  If unable to remove the dev,
	 * it means that something else has the dev open (eg, someone
	 * has it mounted), so fail with EBUSY.
	 */
	if (devsw_remove(dev) != 0) {
		dev_deref(dev);
		u.u_error = EBUSY;
		goto bad;
	}
	/*
	 * sp points to next available slot in swdevt[].
	 * i contains the number of active swap devices.
	 * Call driver to establish size of partition.
	 */
	sp->sw_devp = dev;
	sp->sw_dev = dev->d_dev;	/* this is visible in user-land */
	if (DEV_SIZE(dev))
		nblks = DEV_SIZE(dev);
	sp->sw_nblks = si->si_nblks;
	if (sp->sw_nblks == 0 || sp->sw_nblks > nblks)
		sp->sw_nblks = nblks;
	if (sp->sw_nblks < 0) {
		/* add back dev */
		(void)devsw_add(dev);
		dev_deref(dev);
		/* free slot */
		sp->sw_devp = (struct dev *)0;
		sp->sw_dev = (dev_t)0;
		sp->sw_nblks = 0;
		u.u_error = EINVAL;
		goto bad;;
	}
	/*
	 * Update the page-rate threshold since we just added
	 * another device. We add the original value of maxpgio
	 * (set in setupvm()) to maxpgio. The original value
	 * can be obtained by dividing maxpgio by the number of
	 * swap devices (since we add to it each time a swapon() is
	 * done). This is a bit racy since sched() also uses maxpgio
	 * but this is the only place where it is written and
	 * we're protected by swap_sema.
	 *
	 * Update nswap. Nswap is the largest possible swap block
	 * number. If the new device is larger than the largest
	 * previous device, make this the largest and set nswap
	 * appropriately.
	 */
	maxpgio += maxpgio/i;
	nblks = roundup(sp->sw_nblks, dmmax_sw);
	if (nblks > nswap/nswdev)
		nswap = nblks * nswdev;

	v_sema(&swap_sema);
	swfree(sp - swdevt);
	return;
bad:
	v_sema(&swap_sema);
	return;
}

/*
 * swfree()
 *	Free the index'th portion of the swap map.
 *
 * Each of the nswdev devices provides 1/nswdev'th of the swap
 * space, which is laid out with blocks of dmmax_sw pages circularly
 * among the devices. The first block of each dmmax_sw chunk  of each
 * device (except the first) is never freed (ie pretend it's permanently
 * allocated). This simplifies rmalloc in the swapmap case since it doesn't
 * have to special case allocations that cross device boundaries. We need
 * to do this since though the swap driver "stripes" across devices, it
 * doesn't allow requests to span device boundaries. All this can go away
 * if the swap driver becomes a true striping driver.
 */

swfree(index)
	int index;
{
	register swblk_t vsbase;
	register long blk;
	struct dev *dev;
	register swblk_t dvbase;
	register int nblks;

	dev = swdevt[index].sw_devp;
	DEV_OPEN(dev, FREAD|FWRITE, OTYP_SWP);
	nblks = swdevt[index].sw_nblks;
	for (dvbase = 0; dvbase < nblks; dvbase += dmmax_sw) {
		blk = nblks - dvbase;
		vsbase = index*dmmax_sw + dvbase*nswdev;
		ASSERT(vsbase < nswap, "swfree: too much swap space");
		/*
		 *+ The kernel discovered an inconsistency when adding a new swap
		 *+ device.  It appeared that swap addresses greater than the
		 *+ expected maximum were being added.
		 */
		if (blk > dmmax_sw)
			blk = dmmax_sw;
		if (vsbase == 0) {
			/*
			 * Initialize the swapmap. Don't free block 0 (it's
			 * an error return from rmalloc).
			 */
			rminit(swapmap, blk-ctod(1), vsbase+ctod(1), "swap", nswapmap);
		} else {
			if (index != 0)
				swapfree(blk-ctod(1), vsbase+ctod(1));
			else
				swapfree(blk, vsbase);
		}
	}
}

/*
 * swapalloc()
 *	Try to allocate space from the swap-map.
 *
 * If doing dynamic swap space allocation, complain if this allocation
 * causes available swap space to cross below a threshold.  This prints
 * each time available space crosses the threshold.  Another approach is
 * to print each time an allocation leaves space lower, but insure sufficient
 * time passes between printfs (eg 60 seconds).
 */

static	int	swap_nfree;		/* current number free */
extern	int	swap_low_water;		/* threshold for console printf */
extern	int	dynamic_swap_space;	/* is dynamic space alloc enabled?? */

swapalloc(size)
	long	size;
{
	long	blk;

	P_GATE(G_SWAP);
	if (blk = rmalloc(swapmap, size)) {
		swap_nfree -= size;
		if (dynamic_swap_space &&
		    swap_nfree < swap_low_water &&
		    (swap_nfree + size) >= swap_low_water)
			cmn_err(CE_WARN, "SWAP SPACE IS LOW:  Correct or processes may be killed");
			/*
			 *+ Physical swap space is below a system threshold and
			 *+ dynamic swap space allocation is enabled.  If more
			 *+ swap space isn't made available, the system might be
			 *+ forced to kill processes to create free memory.
			 */
	}
	V_GATE(G_SWAP);

	return(blk);
}

/*
 * swapfree()
 *	Release space into the swap-map.
 */

swapfree(size, blk)
	long	size;
	long	blk;
{
	P_GATE(G_SWAP);
	rmfree(swapmap, size, blk);
	swap_nfree += size;
	V_GATE(G_SWAP);
}

/*
 * swapconf()
 *	Configure swap space and related parameters.
 *	Relies on setconf() initializing swdevt[0], so
 *	must be called after it.
 */

swapconf()
{
	extern struct swdevt swdevt[];
	register struct swdevt *swp = &swdevt[0];
	register int nblks;

	/*
	 * Call driver to establish real size for swdevt[0].
	 * If driver returns -1 (or configuration has this),
	 * panic since it's required for args and some swap
	 * space.
	 */

	nblks = DEV_SIZE(swp->sw_devp);
	if (swp->sw_nblks == 0 || swp->sw_nblks > nblks)
		swp->sw_nblks = nblks;
	if (swp->sw_nblks < 0) {
		cmn_err( CE_PANIC, "swapconf: no arg space");
		/*
		 *+ There are no blocks available on the swap device.  Probably
		 *+ a bad swap device has been selected to swap on.  Corrective
		 *+ action:  correct
		 *+ the boot string or fix the device it references.
		 */
	}

	/*
	 * Set up dmap parameters (swap space representation objects).
	 */

	init_dmap();
}
