#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/namei.h>
#include <sys/disklabel.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/vnode.h>
#include <sys/fcntl.h>
#include <sys/extent.h>
#include <sys/blist.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/pool.h>
#include <sys/syscallargs.h>
#include <sys/swap.h>
#include <sys/disk.h>
#include <sys/task.h>
#include <sys/pledge.h>
#if defined(NFSCLIENT)
#include <sys/socket.h>
#include <netinet/in.h>
#include <nfs/nfsproto.h>
#include <nfs/nfsdiskless.h>
#endif
#include <uvm/uvm.h>
#ifdef UVM_SWAP_ENCRYPT
#include <uvm/uvm_swap_encrypt.h>
#endif
#include <sys/specdev.h>
#include "vnd.h"
struct swapdev {
struct swapent swd_se;
#define swd_dev swd_se.se_dev
#define swd_flags swd_se.se_flags
#define swd_priority swd_se.se_priority
#define swd_inuse swd_se.se_inuse
#define swd_nblks swd_se.se_nblks
char *swd_path;
int swd_pathlen;
int swd_npages;
int swd_npginuse;
int swd_npgbad;
int swd_drumoffset;
int swd_drumsize;
blist_t swd_blist;
struct vnode *swd_vp;
TAILQ_ENTRY(swapdev) swd_next;
int swd_bsize;
int swd_maxactive;
int swd_active;
struct bufq swd_bufq;
struct ucred *swd_cred;
#ifdef UVM_SWAP_ENCRYPT
#define SWD_KEY_SHIFT 7
#define SWD_KEY(x,y) &((x)->swd_keys[((y) - (x)->swd_drumoffset) >> SWD_KEY_SHIFT])
#define SWD_KEY_SIZE(x) (((x) + (1 << SWD_KEY_SHIFT) - 1) >> SWD_KEY_SHIFT)
#define SWD_DCRYPT_SHIFT 5
#define SWD_DCRYPT_BITS 32
#define SWD_DCRYPT_MASK (SWD_DCRYPT_BITS - 1)
#define SWD_DCRYPT_OFF(x) ((x) >> SWD_DCRYPT_SHIFT)
#define SWD_DCRYPT_BIT(x) ((x) & SWD_DCRYPT_MASK)
#define SWD_DCRYPT_SIZE(x) (SWD_DCRYPT_OFF((x) + SWD_DCRYPT_MASK) * sizeof(u_int32_t))
u_int32_t *swd_decrypt;
struct swap_key *swd_keys;
#endif
};
struct swappri {
int spi_priority;
TAILQ_HEAD(spi_swapdev, swapdev) spi_swapdev;
LIST_ENTRY(swappri) spi_swappri;
};
struct vndxfer {
struct buf *vx_bp;
struct swapdev *vx_sdp;
int vx_error;
int vx_pending;
int vx_flags;
#define VX_BUSY 1
#define VX_DEAD 2
};
struct vndbuf {
struct buf vb_buf;
struct vndxfer *vb_vnx;
struct task vb_task;
};
struct pool vndxfer_pool;
struct pool vndbuf_pool;
struct extent *swapmap;
LIST_HEAD(swap_priority, swappri);
struct swap_priority swap_priority;
struct mutex uvm_swap_data_lock = MUTEX_INITIALIZER(IPL_MPFLOOR);
struct rwlock swap_syscall_lock = RWLOCK_INITIALIZER("swplk");
void swapdrum_add(struct swapdev *, int);
struct swapdev *swapdrum_getsdp(int);
struct swapdev *swaplist_find(struct vnode *, int);
void swaplist_insert(struct swapdev *,
struct swappri *, int);
void swaplist_trim(void);
int swap_on(struct proc *, struct swapdev *);
int swap_off(struct proc *, struct swapdev *);
void sw_reg_strategy(struct swapdev *, struct buf *, int);
void sw_reg_iodone(struct buf *);
void sw_reg_iodone_internal(void *);
void sw_reg_start(struct swapdev *);
int uvm_swap_io(struct vm_page **, int, int, int);
void swapmount(void);
#ifdef UVM_SWAP_ENCRYPT
void uvm_swap_markdecrypt(struct swapdev *, int, int, int);
boolean_t uvm_swap_needdecrypt(struct swapdev *, int);
void uvm_swap_initcrypt(struct swapdev *, int);
#endif
void
uvm_swap_init(void)
{
LIST_INIT(&swap_priority);
atomic_store_int(&uvmexp.nswapdev, 0);
if (!swapdev_vp && bdevvp(swapdev, &swapdev_vp))
panic("uvm_swap_init: can't get vnode for swap device");
swapmap = extent_create("swapmap", 1, INT_MAX,
M_VMSWAP, 0, 0, EX_NOWAIT);
if (swapmap == 0)
panic("uvm_swap_init: extent_create failed");
pool_init(&vndxfer_pool, sizeof(struct vndxfer), 0, IPL_BIO, 0,
"swp vnx", NULL);
pool_init(&vndbuf_pool, sizeof(struct vndbuf), 0, IPL_BIO, 0,
"swp vnd", NULL);
swapmount();
}
#ifdef UVM_SWAP_ENCRYPT
void
uvm_swap_initcrypt_all(void)
{
struct swapdev *sdp;
struct swappri *spp;
int npages;
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (sdp->swd_decrypt == NULL) {
npages = dbtob((uint64_t)sdp->swd_nblks) >>
PAGE_SHIFT;
uvm_swap_initcrypt(sdp, npages);
}
}
}
}
void
uvm_swap_initcrypt(struct swapdev *sdp, int npages)
{
sdp->swd_decrypt = malloc(SWD_DCRYPT_SIZE(npages), M_VMSWAP,
M_WAITOK|M_ZERO);
sdp->swd_keys = mallocarray(SWD_KEY_SIZE(npages),
sizeof(struct swap_key), M_VMSWAP, M_WAITOK|M_ZERO);
}
void
uvm_swap_markdecrypt(struct swapdev *sdp, int startslot, int npages,
int decrypt)
{
int pagestart, i;
int off, bit;
if (!sdp)
return;
pagestart = startslot - sdp->swd_drumoffset;
for (i = 0; i < npages; i++, pagestart++) {
off = SWD_DCRYPT_OFF(pagestart);
bit = SWD_DCRYPT_BIT(pagestart);
if (decrypt)
sdp->swd_decrypt[off] |= 1 << bit;
else
sdp->swd_decrypt[off] &= ~(1 << bit);
}
}
boolean_t
uvm_swap_needdecrypt(struct swapdev *sdp, int off)
{
if (!swap_encrypt_initialized)
return FALSE;
if (!sdp)
return FALSE;
off -= sdp->swd_drumoffset;
return sdp->swd_decrypt[SWD_DCRYPT_OFF(off)] & (1 << SWD_DCRYPT_BIT(off)) ?
TRUE : FALSE;
}
void
uvm_swap_finicrypt_all(void)
{
struct swapdev *sdp;
struct swappri *spp;
struct swap_key *key;
unsigned int nkeys;
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (sdp->swd_decrypt == NULL)
continue;
nkeys = dbtob((uint64_t)sdp->swd_nblks) >> PAGE_SHIFT;
key = sdp->swd_keys + (SWD_KEY_SIZE(nkeys) - 1);
do {
if (key->refcount != 0)
swap_key_delete(key);
} while (key-- != sdp->swd_keys);
}
}
}
#endif
void
swaplist_insert(struct swapdev *sdp, struct swappri *newspp, int priority)
{
struct swappri *spp, *pspp;
KASSERT(rw_write_held(&swap_syscall_lock));
MUTEX_ASSERT_LOCKED(&uvm_swap_data_lock);
pspp = NULL;
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
if (priority <= spp->spi_priority)
break;
pspp = spp;
}
if (spp == NULL || spp->spi_priority != priority) {
spp = newspp;
spp->spi_priority = priority;
TAILQ_INIT(&spp->spi_swapdev);
if (pspp)
LIST_INSERT_AFTER(pspp, spp, spi_swappri);
else
LIST_INSERT_HEAD(&swap_priority, spp, spi_swappri);
} else {
free(newspp, M_VMSWAP, sizeof(*newspp));
}
sdp->swd_priority = priority;
TAILQ_INSERT_TAIL(&spp->spi_swapdev, sdp, swd_next);
atomic_inc_int(&uvmexp.nswapdev);
}
struct swapdev *
swaplist_find(struct vnode *vp, boolean_t remove)
{
struct swapdev *sdp;
struct swappri *spp;
KASSERT(rw_write_held(&swap_syscall_lock));
MUTEX_ASSERT_LOCKED(&uvm_swap_data_lock);
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (sdp->swd_vp != vp)
continue;
if (remove) {
TAILQ_REMOVE(&spp->spi_swapdev, sdp, swd_next);
atomic_dec_int(&uvmexp.nswapdev);
}
return (sdp);
}
}
return (NULL);
}
void
swaplist_trim(void)
{
struct swappri *spp, *nextspp;
KASSERT(rw_write_held(&swap_syscall_lock));
MUTEX_ASSERT_LOCKED(&uvm_swap_data_lock);
LIST_FOREACH_SAFE(spp, &swap_priority, spi_swappri, nextspp) {
if (!TAILQ_EMPTY(&spp->spi_swapdev))
continue;
LIST_REMOVE(spp, spi_swappri);
free(spp, M_VMSWAP, sizeof(*spp));
}
}
void
swapdrum_add(struct swapdev *sdp, int npages)
{
u_long result;
if (extent_alloc(swapmap, npages, EX_NOALIGN, 0, EX_NOBOUNDARY,
EX_WAITOK, &result))
panic("swapdrum_add");
sdp->swd_drumoffset = result;
sdp->swd_drumsize = npages;
}
struct swapdev *
swapdrum_getsdp(int pgno)
{
struct swapdev *sdp;
struct swappri *spp;
MUTEX_ASSERT_LOCKED(&uvm_swap_data_lock);
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (pgno >= sdp->swd_drumoffset &&
pgno < (sdp->swd_drumoffset + sdp->swd_drumsize)) {
return sdp;
}
}
}
return NULL;
}
int
sys_swapctl(struct proc *p, void *v, register_t *retval)
{
struct sys_swapctl_args
*uap = (struct sys_swapctl_args *)v;
struct vnode *vp;
struct nameidata nd;
struct swappri *spp;
struct swapdev *sdp;
struct swapent *sep;
char userpath[MAXPATHLEN];
size_t len;
int count, error, misc;
int priority;
misc = SCARG(uap, misc);
if ((error = pledge_swapctl(p, SCARG(uap, cmd))))
return error;
rw_enter_write(&swap_syscall_lock);
if (SCARG(uap, cmd) == SWAP_NSWAP) {
*retval = atomic_load_sint(&uvmexp.nswapdev);
error = 0;
goto out;
}
if (SCARG(uap, cmd) == SWAP_STATS) {
sep = (struct swapent *)SCARG(uap, arg);
count = 0;
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (count >= misc)
continue;
sdp->swd_inuse =
btodb((u_int64_t)sdp->swd_npginuse <<
PAGE_SHIFT);
error = copyout(&sdp->swd_se, sep,
sizeof(struct swapent));
if (error)
goto out;
error = copyoutstr(sdp->swd_path,
sep->se_path, sizeof(sep->se_path), NULL);
if (error)
goto out;
count++;
sep++;
}
}
*retval = count;
error = 0;
goto out;
}
if ((error = suser(p)))
goto out;
error = copyinstr(SCARG(uap, arg), userpath, sizeof(userpath), &len);
if (error)
goto out;
disk_map(userpath, userpath, sizeof(userpath), DM_OPENBLCK);
NDINIT(&nd, LOOKUP, FOLLOW|LOCKLEAF, UIO_SYSSPACE, userpath, p);
if ((error = namei(&nd)))
goto out;
vp = nd.ni_vp;
error = 0;
switch(SCARG(uap, cmd)) {
case SWAP_DUMPDEV:
if (vp->v_type != VBLK) {
error = ENOTBLK;
break;
}
dumpdev = vp->v_rdev;
break;
case SWAP_CTL:
priority = SCARG(uap, misc);
spp = malloc(sizeof *spp, M_VMSWAP, M_WAITOK);
mtx_enter(&uvm_swap_data_lock);
if ((sdp = swaplist_find(vp, 1)) == NULL) {
error = ENOENT;
} else {
swaplist_insert(sdp, spp, priority);
swaplist_trim();
}
mtx_leave(&uvm_swap_data_lock);
if (error)
free(spp, M_VMSWAP, sizeof(*spp));
break;
case SWAP_ON:
if (vp->v_type == VREG &&
(vp->v_mount->mnt_flag & MNT_SWAPPABLE) == 0) {
error = ENOTSUP;
break;
}
priority = SCARG(uap, misc);
sdp = malloc(sizeof *sdp, M_VMSWAP, M_WAITOK|M_ZERO);
spp = malloc(sizeof *spp, M_VMSWAP, M_WAITOK);
sdp->swd_flags = SWF_FAKE;
sdp->swd_vp = vp;
sdp->swd_dev = (vp->v_type == VBLK) ? vp->v_rdev : NODEV;
if (vp->v_type == VREG) {
sdp->swd_cred = crdup(p->p_ucred);
}
mtx_enter(&uvm_swap_data_lock);
if (swaplist_find(vp, 0) != NULL) {
error = EBUSY;
mtx_leave(&uvm_swap_data_lock);
if (vp->v_type == VREG) {
crfree(sdp->swd_cred);
}
free(sdp, M_VMSWAP, sizeof *sdp);
free(spp, M_VMSWAP, sizeof *spp);
break;
}
swaplist_insert(sdp, spp, priority);
mtx_leave(&uvm_swap_data_lock);
sdp->swd_pathlen = len;
sdp->swd_path = malloc(sdp->swd_pathlen, M_VMSWAP, M_WAITOK);
strlcpy(sdp->swd_path, userpath, len);
if ((error = swap_on(p, sdp)) != 0) {
mtx_enter(&uvm_swap_data_lock);
(void) swaplist_find(vp, 1);
swaplist_trim();
mtx_leave(&uvm_swap_data_lock);
if (vp->v_type == VREG) {
crfree(sdp->swd_cred);
}
free(sdp->swd_path, M_VMSWAP, sdp->swd_pathlen);
free(sdp, M_VMSWAP, sizeof(*sdp));
break;
}
break;
case SWAP_OFF:
mtx_enter(&uvm_swap_data_lock);
if ((sdp = swaplist_find(vp, 0)) == NULL) {
mtx_leave(&uvm_swap_data_lock);
error = ENXIO;
break;
}
if ((sdp->swd_flags & (SWF_INUSE|SWF_ENABLE)) == 0) {
mtx_leave(&uvm_swap_data_lock);
error = EBUSY;
break;
}
error = swap_off(p, sdp);
break;
default:
error = EINVAL;
}
vput(vp);
out:
rw_exit_write(&swap_syscall_lock);
return (error);
}
int
swap_on(struct proc *p, struct swapdev *sdp)
{
struct vnode *vp;
int error, npages, nblocks, size;
long addr;
struct vattr va;
#if defined(NFSCLIENT)
extern const struct vops nfs_vops;
#endif
dev_t dev;
vp = sdp->swd_vp;
dev = sdp->swd_dev;
#if NVND > 0
if (bdevsw[major(dev)].d_strategy == vndstrategy)
return (EOPNOTSUPP);
#endif
if (vp != rootvp) {
if ((error = VOP_OPEN(vp, FREAD|FWRITE, p->p_ucred, p)))
return (error);
}
switch (vp->v_type) {
case VBLK:
if (bdevsw[major(dev)].d_psize == 0 ||
(nblocks = (*bdevsw[major(dev)].d_psize)(dev)) == -1) {
error = ENXIO;
goto bad;
}
break;
case VREG:
if ((error = VOP_GETATTR(vp, &va, p->p_ucred, p)))
goto bad;
nblocks = (int)btodb(va.va_size);
if ((error =
VFS_STATFS(vp->v_mount, &vp->v_mount->mnt_stat, p)) != 0)
goto bad;
sdp->swd_bsize = vp->v_mount->mnt_stat.f_iosize;
#if defined(NFSCLIENT)
if (vp->v_op == &nfs_vops)
sdp->swd_maxactive = 2;
else
#endif
sdp->swd_maxactive = 8;
bufq_init(&sdp->swd_bufq, BUFQ_FIFO);
break;
default:
error = ENXIO;
goto bad;
}
sdp->swd_nblks = nblocks;
npages = dbtob((u_int64_t)nblocks) >> PAGE_SHIFT;
if (vp->v_type == VBLK) {
size = npages - 1;
addr = 1;
} else {
size = npages;
addr = 0;
}
if (size < 1) {
error = EINVAL;
goto bad;
}
sdp->swd_blist = blist_create(npages);
blist_free(sdp->swd_blist, addr, size);
#ifdef HIBERNATE
if (dev == swdevt[0] && vp->v_type == VBLK && size > 3 ) {
if (blist_fill(sdp->swd_blist, npages - 1, 1) != 1)
panic("hibernate reserve");
}
#endif
vref(vp);
#ifdef UVM_SWAP_ENCRYPT
if (uvm_doswapencrypt)
uvm_swap_initcrypt(sdp, npages);
#endif
swapdrum_add(sdp, npages);
sdp->swd_npages = size;
mtx_enter(&uvm_swap_data_lock);
sdp->swd_flags &= ~SWF_FAKE;
sdp->swd_flags |= (SWF_INUSE|SWF_ENABLE);
atomic_add_int(&uvmexp.swpages, size);
mtx_leave(&uvm_swap_data_lock);
return (0);
bad:
if (vp != rootvp)
(void)VOP_CLOSE(vp, FREAD|FWRITE, p->p_ucred, p);
return (error);
}
int
swap_off(struct proc *p, struct swapdev *sdp)
{
int npages = sdp->swd_npages;
int error = 0;
KASSERT(rw_write_held(&swap_syscall_lock));
MUTEX_ASSERT_LOCKED(&uvm_swap_data_lock);
sdp->swd_flags &= ~SWF_ENABLE;
mtx_leave(&uvm_swap_data_lock);
if (uao_swap_off(sdp->swd_drumoffset,
sdp->swd_drumoffset + sdp->swd_drumsize) ||
amap_swap_off(sdp->swd_drumoffset,
sdp->swd_drumoffset + sdp->swd_drumsize)) {
error = ENOMEM;
} else if (sdp->swd_npginuse > sdp->swd_npgbad) {
error = EBUSY;
}
if (error) {
mtx_enter(&uvm_swap_data_lock);
sdp->swd_flags |= SWF_ENABLE;
mtx_leave(&uvm_swap_data_lock);
return error;
}
if (sdp->swd_vp->v_type == VREG) {
crfree(sdp->swd_cred);
bufq_destroy(&sdp->swd_bufq);
}
vrele(sdp->swd_vp);
if (sdp->swd_vp != rootvp) {
(void) VOP_CLOSE(sdp->swd_vp, FREAD|FWRITE, p->p_ucred, p);
}
mtx_enter(&uvm_swap_data_lock);
atomic_sub_int(&uvmexp.swpages, npages);
if (swaplist_find(sdp->swd_vp, 1) == NULL)
panic("swap_off: swapdev not in list");
swaplist_trim();
mtx_leave(&uvm_swap_data_lock);
extent_free(swapmap, sdp->swd_drumoffset, sdp->swd_drumsize,
EX_WAITOK);
blist_destroy(sdp->swd_blist);
free(sdp, M_VMSWAP, sizeof(*sdp));
return (0);
}
void
swstrategy(struct buf *bp)
{
struct swapdev *sdp;
int s, pageno, bn;
pageno = dbtob((u_int64_t)bp->b_blkno) >> PAGE_SHIFT;
sdp = uvm_swap_getsdp(pageno);
if (sdp == NULL) {
bp->b_error = EINVAL;
bp->b_flags |= B_ERROR;
s = splbio();
biodone(bp);
splx(s);
return;
}
pageno -= sdp->swd_drumoffset;
bn = btodb((u_int64_t)pageno << PAGE_SHIFT);
switch (sdp->swd_vp->v_type) {
default:
panic("swstrategy: vnode type 0x%x", sdp->swd_vp->v_type);
case VBLK:
s = splbio();
buf_replacevnode(bp, sdp->swd_vp);
bp->b_blkno = bn;
splx(s);
VOP_STRATEGY(bp->b_vp, bp);
return;
case VREG:
sw_reg_strategy(sdp, bp, bn);
return;
}
}
void
sw_reg_strategy(struct swapdev *sdp, struct buf *bp, int bn)
{
struct vnode *vp;
struct vndxfer *vnx;
daddr_t nbn;
caddr_t addr;
off_t byteoff;
int s, off, nra, error, sz, resid;
vnx = pool_get(&vndxfer_pool, PR_WAITOK);
vnx->vx_flags = VX_BUSY;
vnx->vx_error = 0;
vnx->vx_pending = 0;
vnx->vx_bp = bp;
vnx->vx_sdp = sdp;
error = 0;
bp->b_resid = bp->b_bcount;
addr = bp->b_data;
byteoff = dbtob((u_int64_t)bn);
for (resid = bp->b_resid; resid; resid -= sz) {
struct vndbuf *nbp;
nra = 0;
error = VOP_BMAP(sdp->swd_vp, byteoff / sdp->swd_bsize,
&vp, &nbn, &nra);
if (error == 0 && nbn == -1) {
#if 1
panic("sw_reg_strategy: swap to sparse file");
#else
error = EIO;
#endif
}
if (error) {
s = splbio();
vnx->vx_error = error;
goto out;
}
off = byteoff % sdp->swd_bsize;
sz = (1 + nra) * sdp->swd_bsize - off;
if (sz > resid)
sz = resid;
nbp = pool_get(&vndbuf_pool, PR_WAITOK);
nbp->vb_buf.b_flags = bp->b_flags | B_CALL;
nbp->vb_buf.b_bcount = sz;
nbp->vb_buf.b_bufsize = sz;
nbp->vb_buf.b_error = 0;
nbp->vb_buf.b_data = addr;
nbp->vb_buf.b_bq = NULL;
nbp->vb_buf.b_blkno = nbn + btodb(off);
nbp->vb_buf.b_proc = bp->b_proc;
nbp->vb_buf.b_iodone = sw_reg_iodone;
nbp->vb_buf.b_vp = NULL;
nbp->vb_buf.b_vnbufs.le_next = NOLIST;
if (bp->b_dirtyend == 0) {
nbp->vb_buf.b_dirtyoff = 0;
nbp->vb_buf.b_dirtyend = sz;
} else {
nbp->vb_buf.b_dirtyoff =
max(0, bp->b_dirtyoff - (bp->b_bcount-resid));
nbp->vb_buf.b_dirtyend =
min(sz,
max(0, bp->b_dirtyend - (bp->b_bcount-resid)));
}
if (bp->b_validend == 0) {
nbp->vb_buf.b_validoff = 0;
nbp->vb_buf.b_validend = sz;
} else {
nbp->vb_buf.b_validoff =
max(0, bp->b_validoff - (bp->b_bcount-resid));
nbp->vb_buf.b_validend =
min(sz,
max(0, bp->b_validend - (bp->b_bcount-resid)));
}
nbp->vb_vnx = vnx;
task_set(&nbp->vb_task, sw_reg_iodone_internal, nbp);
s = splbio();
if (vnx->vx_error != 0) {
pool_put(&vndbuf_pool, nbp);
goto out;
}
vnx->vx_pending++;
bgetvp(vp, &nbp->vb_buf);
bufq_queue(&sdp->swd_bufq, &nbp->vb_buf);
sw_reg_start(sdp);
splx(s);
byteoff += sz;
addr += sz;
}
s = splbio();
out:
vnx->vx_flags &= ~VX_BUSY;
if (vnx->vx_pending == 0) {
if (vnx->vx_error != 0) {
bp->b_error = vnx->vx_error;
bp->b_flags |= B_ERROR;
}
pool_put(&vndxfer_pool, vnx);
biodone(bp);
}
splx(s);
}
void
sw_reg_start(struct swapdev *sdp)
{
struct buf *bp;
if ((sdp->swd_flags & SWF_BUSY) != 0)
return;
sdp->swd_flags |= SWF_BUSY;
while (sdp->swd_active < sdp->swd_maxactive) {
bp = bufq_dequeue(&sdp->swd_bufq);
if (bp == NULL)
break;
sdp->swd_active++;
if ((bp->b_flags & B_READ) == 0)
bp->b_vp->v_numoutput++;
VOP_STRATEGY(bp->b_vp, bp);
}
sdp->swd_flags &= ~SWF_BUSY;
}
void
sw_reg_iodone(struct buf *bp)
{
struct vndbuf *vbp = (struct vndbuf *)bp;
task_add(systq, &vbp->vb_task);
}
void
sw_reg_iodone_internal(void *xvbp)
{
struct vndbuf *vbp = xvbp;
struct vndxfer *vnx = vbp->vb_vnx;
struct buf *pbp = vnx->vx_bp;
struct swapdev *sdp = vnx->vx_sdp;
int resid, s;
s = splbio();
resid = vbp->vb_buf.b_bcount - vbp->vb_buf.b_resid;
pbp->b_resid -= resid;
vnx->vx_pending--;
if (vbp->vb_buf.b_error)
vnx->vx_error = vbp->vb_buf.b_error;
if (vbp->vb_buf.b_vp != NULL) {
brelvp(&vbp->vb_buf);
}
pool_put(&vndbuf_pool, vbp);
if (vnx->vx_error != 0) {
pbp->b_flags |= B_ERROR;
pbp->b_error = vnx->vx_error;
if ((vnx->vx_flags & VX_BUSY) == 0 && vnx->vx_pending == 0) {
pool_put(&vndxfer_pool, vnx);
biodone(pbp);
}
} else if (pbp->b_resid == 0) {
KASSERT(vnx->vx_pending == 0);
if ((vnx->vx_flags & VX_BUSY) == 0) {
pool_put(&vndxfer_pool, vnx);
biodone(pbp);
}
}
sdp->swd_active--;
sw_reg_start(sdp);
splx(s);
}
int
uvm_swap_alloc(int *nslots, boolean_t lessok)
{
struct swapdev *sdp;
struct swappri *spp;
if (atomic_load_sint(&uvmexp.nswapdev) < 1)
return 0;
KERNEL_ASSERT_LOCKED();
mtx_enter(&uvm_swap_data_lock);
ReTry:
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
swblk_t result;
if ((sdp->swd_flags & SWF_ENABLE) == 0)
continue;
if (sdp->swd_npginuse + *nslots > sdp->swd_npages)
continue;
result = blist_alloc(sdp->swd_blist, *nslots);
if (result == SWAPBLK_NONE) {
continue;
}
KASSERT(result < sdp->swd_drumsize);
TAILQ_REMOVE(&spp->spi_swapdev, sdp, swd_next);
TAILQ_INSERT_TAIL(&spp->spi_swapdev, sdp, swd_next);
sdp->swd_npginuse += *nslots;
atomic_add_int(&uvmexp.swpginuse, *nslots);
mtx_leave(&uvm_swap_data_lock);
return result + sdp->swd_drumoffset;
}
}
if (*nslots > 1 && lessok) {
*nslots = 1;
goto ReTry;
}
mtx_leave(&uvm_swap_data_lock);
return 0;
}
int
uvm_swapisfilled(void)
{
int result;
mtx_enter(&uvm_swap_data_lock);
KASSERT(atomic_load_sint(&uvmexp.swpginuse) <=
atomic_load_sint(&uvmexp.swpages));
result = (atomic_load_sint(&uvmexp.swpginuse) + SWCLUSTPAGES) >=
atomic_load_sint(&uvmexp.swpages);
mtx_leave(&uvm_swap_data_lock);
return result;
}
int
uvm_swapisfull(void)
{
int result;
mtx_enter(&uvm_swap_data_lock);
KASSERT(atomic_load_sint(&uvmexp.swpgonly) <=
atomic_load_sint(&uvmexp.swpages));
result = (atomic_load_sint(&uvmexp.swpgonly) >=
((long)atomic_load_sint(&uvmexp.swpages) * 99 / 100));
mtx_leave(&uvm_swap_data_lock);
return result;
}
void
uvm_swap_markbad(int startslot, int nslots)
{
struct swapdev *sdp;
mtx_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(startslot);
if (sdp != NULL) {
sdp->swd_npgbad += nslots;
}
mtx_leave(&uvm_swap_data_lock);
}
void
uvm_swap_free(int startslot, int nslots)
{
struct swapdev *sdp;
if (startslot == SWSLOT_BAD) {
return;
}
KERNEL_LOCK();
mtx_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(startslot);
KASSERT(atomic_load_sint(&uvmexp.nswapdev) >= 1);
KASSERT(sdp != NULL);
KASSERT(sdp->swd_npginuse >= nslots);
blist_free(sdp->swd_blist, startslot - sdp->swd_drumoffset, nslots);
sdp->swd_npginuse -= nslots;
atomic_sub_int(&uvmexp.swpginuse, nslots);
mtx_leave(&uvm_swap_data_lock);
#ifdef UVM_SWAP_ENCRYPT
{
int i;
if (swap_encrypt_initialized) {
for (i = 0; i < nslots; i++)
if (uvm_swap_needdecrypt(sdp, startslot + i)) {
struct swap_key *key;
key = SWD_KEY(sdp, startslot + i);
if (key->refcount != 0)
swap_key_put(key);
}
uvm_swap_markdecrypt(sdp, startslot, nslots, 0);
}
}
#endif
KERNEL_UNLOCK();
}
int
uvm_swap_put(int swslot, struct vm_page **ppsp, int npages, int flags)
{
int result;
result = uvm_swap_io(ppsp, swslot, npages, B_WRITE |
((flags & PGO_SYNCIO) ? 0 : B_ASYNC));
return (result);
}
int
uvm_swap_get(struct vm_page *page, int swslot, int flags)
{
int result;
atomic_inc_int(&uvmexp.nswget);
KASSERT(flags & PGO_SYNCIO);
if (swslot == SWSLOT_BAD) {
return VM_PAGER_ERROR;
}
KERNEL_LOCK();
result = uvm_swap_io(&page, swslot, 1, B_READ);
KERNEL_UNLOCK();
if (result == VM_PAGER_OK || result == VM_PAGER_PEND) {
atomic_dec_int(&uvmexp.swpgonly);
}
return (result);
}
struct swapdev *
uvm_swap_getsdp(int slot)
{
struct swapdev *sdp;
mtx_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(slot);
mtx_leave(&uvm_swap_data_lock);
return sdp;
}
int
uvm_swap_io(struct vm_page **pps, int startslot, int npages, int flags)
{
daddr_t startblk;
struct buf *bp;
vaddr_t kva;
int result, s, mapinflags, pflag;
boolean_t write, async;
#ifdef UVM_SWAP_ENCRYPT
struct swapdev *sdp;
int encrypt = 0;
#endif
KERNEL_ASSERT_LOCKED();
write = (flags & B_READ) == 0;
async = (flags & B_ASYNC) != 0;
startblk = btodb((u_int64_t)startslot << PAGE_SHIFT);
mapinflags = UVMPAGER_MAPIN_READ;
if (!async)
mapinflags |= UVMPAGER_MAPIN_WAITOK;
kva = uvm_pagermapin(pps, npages, mapinflags);
if (kva == 0)
return (VM_PAGER_AGAIN);
#ifdef UVM_SWAP_ENCRYPT
if (write) {
if (uvm_doswapencrypt)
encrypt = 1;
}
if (swap_encrypt_initialized || encrypt) {
sdp = uvm_swap_getsdp(startslot);
}
#endif
pflag = (async || curproc == uvm.pagedaemon_proc) ? PR_NOWAIT :
PR_WAITOK;
bp = pool_get(&bufpool, pflag | PR_ZERO);
if (bp == NULL) {
uvm_pagermapout(kva, npages);
return (VM_PAGER_AGAIN);
}
#ifdef UVM_SWAP_ENCRYPT
if (write && encrypt) {
int i;
caddr_t src, dst;
src = (caddr_t) kva;
dst = (caddr_t) kva;
for (i = 0; i < npages; i++) {
int slot = startslot + i;
uvm_swap_encryptpg(sdp, src, dst, slot);
atomic_setbits_int(&pps[i]->pg_flags, PQ_ENCRYPT);
src += PAGE_SIZE;
dst += PAGE_SIZE;
}
}
#endif
if (!write) {
flags &= ~B_ASYNC;
async = 0;
}
bp->b_flags = B_BUSY | B_NOCACHE | B_RAW | (flags & (B_READ|B_ASYNC));
bp->b_proc = &proc0;
bp->b_vnbufs.le_next = NOLIST;
bp->b_data = (caddr_t)kva;
bp->b_bq = NULL;
bp->b_blkno = startblk;
s = splbio();
bp->b_vp = NULL;
buf_replacevnode(bp, swapdev_vp);
splx(s);
bp->b_bufsize = bp->b_bcount = (long)npages << PAGE_SHIFT;
if (write) {
bp->b_dirtyoff = 0;
bp->b_dirtyend = npages << PAGE_SHIFT;
#ifdef UVM_SWAP_ENCRYPT
if (swap_encrypt_initialized)
uvm_swap_markdecrypt(sdp, startslot, npages, encrypt);
#endif
s = splbio();
swapdev_vp->v_numoutput++;
splx(s);
}
if (async) {
bp->b_flags |= B_CALL | (curproc == uvm.pagedaemon_proc ?
B_PDAEMON : 0);
bp->b_iodone = uvm_aio_biodone;
}
VOP_STRATEGY(bp->b_vp, bp);
if (async)
return (VM_PAGER_PEND);
(void) biowait(bp);
result = (bp->b_flags & B_ERROR) ? VM_PAGER_ERROR : VM_PAGER_OK;
#ifdef UVM_SWAP_ENCRYPT
if (!write && !(bp->b_flags & B_ERROR)) {
int i;
caddr_t data = (caddr_t)kva;
caddr_t dst = (caddr_t)kva;
for (i = 0; i < npages; i++) {
int slot = startslot + i;
if (uvm_swap_needdecrypt(sdp, slot)) {
if (uvm_swap_decryptpg(sdp, data, dst, slot)) {
result = VM_PAGER_ERROR;
break;
}
}
data += PAGE_SIZE;
dst += PAGE_SIZE;
}
}
#endif
uvm_pagermapout(kva, npages);
s = splbio();
if (bp->b_vp)
brelvp(bp);
if (write && bp->b_vp)
vwakeup(bp->b_vp);
pool_put(&bufpool, bp);
splx(s);
return (result);
}
#ifdef UVM_SWAP_ENCRYPT
void
uvm_swap_encryptpg(struct swapdev *sdp, caddr_t data, caddr_t dst, int slot)
{
struct swap_key *key;
uint64_t block;
key = SWD_KEY(sdp, slot);
swap_key_get(key);
block = btodb((uint64_t)slot << PAGE_SHIFT);
swap_encrypt(key, data, dst, block, PAGE_SIZE);
}
int
uvm_swap_decryptpg(struct swapdev *sdp, caddr_t data, caddr_t dst, int slot)
{
struct swap_key *key;
uint64_t block;
KASSERT(uvm_swap_needdecrypt(sdp, slot));
key = SWD_KEY(sdp, slot);
if (key->refcount == 0)
return -1;
block = btodb((uint64_t)slot << PAGE_SHIFT);
swap_decrypt(key, data, dst, block, PAGE_SIZE);
return 0;
}
#endif
void
swapmount(void)
{
struct swapdev *sdp;
struct swappri *spp;
struct vnode *vp;
dev_t swap_dev = swdevt[0];
char *nam;
char path[MNAMELEN + 1];
if (swap_dev == NODEV)
return;
rw_enter_write(&swap_syscall_lock);
#if defined(NFSCLIENT)
if (swap_dev == NETDEV) {
extern struct nfs_diskless nfs_diskless;
snprintf(path, sizeof(path), "%s",
nfs_diskless.nd_swap.ndm_host);
vp = nfs_diskless.sw_vp;
goto gotit;
} else
#endif
if (bdevvp(swap_dev, &vp)) {
rw_exit_write(&swap_syscall_lock);
return;
}
if ((nam = findblkname(major(swap_dev))))
snprintf(path, sizeof(path), "/dev/%s%d%c", nam,
DISKUNIT(swap_dev), DL_PARTNUM2NAME(DISKPART(swap_dev)));
else
snprintf(path, sizeof(path), "blkdev0x%x",
swap_dev);
#if defined(NFSCLIENT)
gotit:
#endif
sdp = malloc(sizeof(*sdp), M_VMSWAP, M_WAITOK|M_ZERO);
spp = malloc(sizeof(*spp), M_VMSWAP, M_WAITOK);
sdp->swd_flags = SWF_FAKE;
sdp->swd_dev = swap_dev;
sdp->swd_pathlen = strlen(path) + 1;
sdp->swd_path = malloc(sdp->swd_pathlen, M_VMSWAP, M_WAITOK | M_ZERO);
strlcpy(sdp->swd_path, path, sdp->swd_pathlen);
sdp->swd_vp = vp;
mtx_enter(&uvm_swap_data_lock);
swaplist_insert(sdp, spp, 0);
mtx_leave(&uvm_swap_data_lock);
if (swap_on(curproc, sdp)) {
mtx_enter(&uvm_swap_data_lock);
swaplist_find(vp, 1);
swaplist_trim();
vput(sdp->swd_vp);
mtx_leave(&uvm_swap_data_lock);
rw_exit_write(&swap_syscall_lock);
free(sdp->swd_path, M_VMSWAP, sdp->swd_pathlen);
free(sdp, M_VMSWAP, sizeof(*sdp));
return;
}
rw_exit_write(&swap_syscall_lock);
}
#ifdef HIBERNATE
int
uvm_hibswap(dev_t dev, u_long *sp, u_long *ep)
{
struct swapdev *sdp, *swd = NULL;
struct swappri *spp;
if (atomic_load_sint(&uvmexp.nswapdev) < 1 || dev != swdevt[0])
return (1);
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (sdp->swd_dev == dev)
swd = sdp;
}
}
if (swd == NULL || (swd->swd_flags & SWF_ENABLE) == 0)
return (1);
blist_gapfind(swd->swd_blist, sp, ep);
if (*ep - *sp == 0)
return (1);
*ep -= 1;
return (0);
}
#endif
#ifdef DDB
void
swap_print_all(int (*pr)(const char *, ...))
{
struct swappri *spp;
struct swapdev *sdp;
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
#ifdef HIBERNATE
u_long bgap = 0, egap = 0;
#endif
pr("swap %p path \"%s\" flags 0x%x\n", sdp,
sdp->swd_path, sdp->swd_flags);
blist_print(sdp->swd_blist);
#ifdef HIBERNATE
if (!uvm_hibswap(sdp->swd_dev, &bgap, &egap))
pr("hibernate gap: [0x%lx, 0x%lx] size=%lu\n",
bgap, egap, (egap - bgap + 1));
else
pr("hibernate gap: not found\n");
#endif
}
}
}
#endif