#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvm_swap.c,v 1.234 2026/06/27 14:56:29 riastradh Exp $");
#include "opt_uvmhist.h"
#include "opt_compat_netbsd.h"
#include "opt_ddb.h"
#include "opt_vmswap.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/atomic.h>
#include <sys/buf.h>
#include <sys/bufq.h>
#include <sys/conf.h>
#include <sys/cprng.h>
#include <sys/proc.h>
#include <sys/namei.h>
#include <sys/disklabel.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/vmem.h>
#include <sys/blist.h>
#include <sys/mount.h>
#include <sys/pool.h>
#include <sys/kmem.h>
#include <sys/syscallargs.h>
#include <sys/swap.h>
#include <sys/kauth.h>
#include <sys/sysctl.h>
#include <sys/workqueue.h>
#include <uvm/uvm.h>
#include <miscfs/specfs/specdev.h>
#include <crypto/aes/aes.h>
#include <crypto/aes/aes_cbc.h>
struct swapdev {
dev_t swd_dev;
int swd_flags;
int swd_priority;
int swd_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;
struct aesenc swd_enckey;
struct aesdec swd_deckey;
bool swd_encinit;
kmutex_t swd_lock;
int swd_bsize;
int swd_maxactive;
struct bufq_state *swd_tab;
int swd_active;
};
struct swappri {
int spi_priority;
TAILQ_HEAD(spi_swapdev, swapdev) spi_swapdev;
LIST_ENTRY(swappri) spi_swappri;
};
static vmem_t *swapmap;
LIST_HEAD(swap_priority, swappri);
static struct swap_priority swap_priority;
static kmutex_t uvm_swap_data_lock __cacheline_aligned;
static krwlock_t swap_syscall_lock;
bool uvm_swap_init_done = false;
static int sw_reg_count = 0;
static struct workqueue *sw_reg_workqueue;
u_int uvm_swapisfull_factor = 99;
#if VMSWAP_DEFAULT_PLAINTEXT
bool uvm_swap_encrypt = false;
#else
bool uvm_swap_encrypt = true;
#endif
static struct swapdev *swapdrum_getsdp(int);
static struct swapdev *swaplist_find(struct vnode *, bool);
static void swaplist_insert(struct swapdev *,
struct swappri *, int);
static void swaplist_trim(void);
static int swap_on(struct lwp *, struct swapdev *);
static int swap_off(struct lwp *, struct swapdev *);
static void sw_reg_strategy(struct swapdev *, struct buf *, int);
static void sw_reg_biodone(struct buf *);
static void sw_reg_iodone(struct work *wk, void *dummy);
static void sw_reg_start(struct swapdev *);
static int uvm_swap_io(struct vm_page **, int, int, int);
static void uvm_swap_genkey(struct swapdev *);
static void uvm_swap_encryptpage(struct swapdev *, void *, int);
static void uvm_swap_decryptpage(struct swapdev *, void *, int);
void
uvm_swap_init(void)
{
UVMHIST_FUNC(__func__);
UVMHIST_CALLED(pdhist);
LIST_INIT(&swap_priority);
uvmexp.nswapdev = 0;
rw_init(&swap_syscall_lock);
mutex_init(&uvm_swap_data_lock, MUTEX_DEFAULT, IPL_NONE);
if (bdevvp(swapdev, &swapdev_vp))
panic("%s: can't get vnode for swap device", __func__);
if (vn_lock(swapdev_vp, LK_EXCLUSIVE | LK_RETRY))
panic("%s: can't lock swap device", __func__);
if (VOP_OPEN(swapdev_vp, FREAD | FWRITE, NOCRED))
panic("%s: can't open swap device", __func__);
VOP_UNLOCK(swapdev_vp);
swapmap = vmem_create("swapmap", 1, INT_MAX - 1,
1, NULL, NULL, NULL,
0, VM_NOSLEEP, IPL_NONE);
if (swapmap == 0) {
panic("%s: vmem_create failed", __func__);
}
uvm_swap_init_done = true;
UVMHIST_LOG(pdhist, "<- done", 0, 0, 0, 0);
}
static void
swaplist_insert(struct swapdev *sdp, struct swappri *newspp, int priority)
{
struct swappri *spp, *pspp;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
KASSERT(rw_write_held(&swap_syscall_lock));
KASSERT(mutex_owned(&uvm_swap_data_lock));
if (LIST_EMPTY(&swap_priority)) {
KASSERT(uvmexp.swpginuse == 0);
KASSERT(uvmexp.swpgonly == 0);
KASSERT(uvmexp.swpages == 0);
KASSERT(uvmexp.swpgavail == 0);
}
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;
UVMHIST_LOG(pdhist, "created new swappri = %jd",
priority, 0, 0, 0);
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 {
kmem_free(newspp, sizeof(*newspp));
}
sdp->swd_priority = priority;
TAILQ_INSERT_TAIL(&spp->spi_swapdev, sdp, swd_next);
uvmexp.nswapdev++;
}
static struct swapdev *
swaplist_find(struct vnode *vp, bool remove)
{
struct swapdev *sdp;
struct swappri *spp;
KASSERT(rw_lock_held(&swap_syscall_lock));
KASSERT(remove ? rw_write_held(&swap_syscall_lock) : 1);
KASSERT(mutex_owned(&uvm_swap_data_lock));
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (sdp->swd_vp == vp) {
if (remove) {
TAILQ_REMOVE(&spp->spi_swapdev,
sdp, swd_next);
uvmexp.nswapdev--;
}
return(sdp);
}
}
}
return (NULL);
}
static void
swaplist_trim(void)
{
struct swappri *spp, *nextspp;
KASSERT(rw_write_held(&swap_syscall_lock));
KASSERT(mutex_owned(&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);
kmem_free(spp, sizeof(*spp));
}
if (LIST_EMPTY(&swap_priority)) {
KASSERT(uvmexp.swpginuse == 0);
KASSERT(uvmexp.swpgonly == 0);
KASSERT(uvmexp.swpages == 0);
KASSERT(uvmexp.swpgavail == 0);
}
}
static struct swapdev *
swapdrum_getsdp(int pgno)
{
struct swapdev *sdp;
struct swappri *spp;
KASSERT(mutex_owned(&uvm_swap_data_lock));
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (sdp->swd_flags & SWF_FAKE)
continue;
if (pgno >= sdp->swd_drumoffset &&
pgno < (sdp->swd_drumoffset + sdp->swd_drumsize)) {
return sdp;
}
}
}
return NULL;
}
static bool __debugused
swapdrum_sdp_is(int pgno, struct swapdev *sdp)
{
bool result;
mutex_enter(&uvm_swap_data_lock);
result = swapdrum_getsdp(pgno) == sdp;
mutex_exit(&uvm_swap_data_lock);
return result;
}
void
swapsys_lock(krw_t op)
{
rw_enter(&swap_syscall_lock, op);
}
void
swapsys_unlock(void)
{
rw_exit(&swap_syscall_lock);
}
static void
swapent_cvt(struct swapent *se, const struct swapdev *sdp, int inuse)
{
se->se_dev = sdp->swd_dev;
se->se_flags = sdp->swd_flags;
se->se_nblks = sdp->swd_nblks;
se->se_npgbad = sdp->swd_npgbad;
se->se_inuse = inuse;
se->se_priority = sdp->swd_priority;
KASSERT(sdp->swd_pathlen < sizeof(se->se_path));
strcpy(se->se_path, sdp->swd_path);
}
int (*uvm_swap_stats13)(const struct sys_swapctl_args *, register_t *) =
(void *)enosys;
int (*uvm_swap_stats50)(const struct sys_swapctl_args *, register_t *) =
(void *)enosys;
int (*uvm_swap_stats110)(const struct sys_swapctl_args *, register_t *) =
(void *)enosys;
int
sys_swapctl(struct lwp *l, const struct sys_swapctl_args *uap,
register_t *retval)
{
struct vnode *vp;
struct nameidata nd;
struct swappri *spp;
struct swapdev *sdp;
#define SWAP_PATH_MAX (PATH_MAX + 1)
char *userpath;
size_t len = 0;
int error;
int priority;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
if (SCARG(uap, cmd) == SWAP_NSWAP) {
const int nswapdev = uvmexp.nswapdev;
UVMHIST_LOG(pdhist, "<- done SWAP_NSWAP=%jd", nswapdev,
0, 0, 0);
*retval = nswapdev;
return 0;
}
userpath = kmem_alloc(SWAP_PATH_MAX, KM_SLEEP);
rw_enter(&swap_syscall_lock, RW_WRITER);
switch (SCARG(uap, cmd)) {
case SWAP_STATS13:
error = (*uvm_swap_stats13)(uap, retval);
goto out;
case SWAP_STATS50:
error = (*uvm_swap_stats50)(uap, retval);
goto out;
case SWAP_STATS110:
error = (*uvm_swap_stats110)(uap, retval);
goto out;
case SWAP_STATS:
error = uvm_swap_stats(SCARG(uap, arg), SCARG(uap, misc),
NULL, sizeof(struct swapent), retval);
UVMHIST_LOG(pdhist, "<- done SWAP_STATS", 0, 0, 0, 0);
goto out;
case SWAP_GETDUMPDEV:
error = copyout(&dumpdev, SCARG(uap, arg), sizeof(dumpdev));
goto out;
default:
break;
}
if ((error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_SWAPCTL,
0, NULL, NULL, NULL)))
goto out;
if (SCARG(uap, cmd) == SWAP_DUMPOFF) {
dumpdev = NODEV;
dumpcdev = NODEV;
cpu_dumpconf();
goto out;
}
if (SCARG(uap, arg) == NULL) {
vp = rootvp;
vref(vp);
if (vn_lock(vp, LK_EXCLUSIVE)) {
vrele(vp);
error = EBUSY;
goto out;
}
if (SCARG(uap, cmd) == SWAP_ON &&
copystr("miniroot", userpath, SWAP_PATH_MAX, &len))
panic("swapctl: miniroot copy failed");
} else {
struct pathbuf *pb;
error = pathbuf_copyin(SCARG(uap, arg), &pb);
if (error) {
goto out;
}
if (SCARG(uap, cmd) == SWAP_ON) {
pathbuf_copystring(pb, userpath, SWAP_PATH_MAX);
len = strlen(userpath) + 1;
}
NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | TRYEMULROOT, pb);
if ((error = namei(&nd))) {
pathbuf_destroy(pb);
goto out;
}
vp = nd.ni_vp;
pathbuf_destroy(pb);
}
error = 0;
switch (SCARG(uap, cmd)) {
case SWAP_DUMPDEV:
if (vp->v_type != VBLK) {
error = ENOTBLK;
break;
}
if (bdevsw_lookup(vp->v_rdev)) {
dumpdev = vp->v_rdev;
dumpcdev = devsw_blk2chr(dumpdev);
} else
dumpdev = NODEV;
cpu_dumpconf();
break;
case SWAP_CTL:
priority = SCARG(uap, misc);
spp = kmem_alloc(sizeof(*spp), KM_SLEEP);
mutex_enter(&uvm_swap_data_lock);
if ((sdp = swaplist_find(vp, true)) == NULL) {
error = ENOENT;
} else {
swaplist_insert(sdp, spp, priority);
swaplist_trim();
}
mutex_exit(&uvm_swap_data_lock);
if (error)
kmem_free(spp, sizeof(*spp));
break;
case SWAP_ON:
priority = SCARG(uap, misc);
sdp = kmem_zalloc(sizeof(*sdp), KM_SLEEP);
spp = kmem_alloc(sizeof(*spp), KM_SLEEP);
mutex_init(&sdp->swd_lock, MUTEX_DEFAULT, IPL_NONE);
sdp->swd_flags = SWF_FAKE;
sdp->swd_vp = vp;
sdp->swd_dev = (vp->v_type == VBLK) ? vp->v_rdev : NODEV;
bufq_alloc(&sdp->swd_tab, "disksort", BUFQ_SORT_RAWBLOCK);
mutex_enter(&uvm_swap_data_lock);
if (swaplist_find(vp, false) != NULL) {
error = EBUSY;
mutex_exit(&uvm_swap_data_lock);
bufq_free(sdp->swd_tab);
mutex_destroy(&sdp->swd_lock);
kmem_free(sdp, sizeof(*sdp));
kmem_free(spp, sizeof(*spp));
break;
}
swaplist_insert(sdp, spp, priority);
mutex_exit(&uvm_swap_data_lock);
KASSERT(len > 0);
sdp->swd_pathlen = len;
sdp->swd_path = kmem_alloc(len, KM_SLEEP);
if (copystr(userpath, sdp->swd_path, len, 0) != 0)
panic("swapctl: copystr");
if ((error = swap_on(l, sdp)) != 0) {
mutex_enter(&uvm_swap_data_lock);
(void) swaplist_find(vp, true);
swaplist_trim();
mutex_exit(&uvm_swap_data_lock);
bufq_free(sdp->swd_tab);
kmem_free(sdp->swd_path, sdp->swd_pathlen);
mutex_destroy(&sdp->swd_lock);
kmem_free(sdp, sizeof(*sdp));
break;
}
break;
case SWAP_OFF:
mutex_enter(&uvm_swap_data_lock);
if ((sdp = swaplist_find(vp, false)) == NULL) {
mutex_exit(&uvm_swap_data_lock);
error = ENXIO;
break;
}
if ((sdp->swd_flags & (SWF_INUSE|SWF_ENABLE)) == 0) {
mutex_exit(&uvm_swap_data_lock);
error = EBUSY;
break;
}
error = swap_off(l, sdp);
break;
default:
error = EINVAL;
}
vput(vp);
out:
rw_exit(&swap_syscall_lock);
kmem_free(userpath, SWAP_PATH_MAX);
UVMHIST_LOG(pdhist, "<- done! error=%jd", error, 0, 0, 0);
return (error);
}
int
uvm_swap_stats(char *ptr, int misc,
void (*f)(void *, const struct swapent *), size_t len,
register_t *retval)
{
struct swappri *spp;
struct swapdev *sdp, **sdps, **sp;
struct swapent sep;
size_t sdpsize = 0;
struct swapdev *stackbuf[8];
int count, slots;
int error;
KASSERT(len <= sizeof(sep));
if (len == 0)
return ENOSYS;
if (misc < 0)
return EINVAL;
if (misc == 0 || uvmexp.nswapdev == 0)
return 0;
KASSERT(rw_lock_held(&swap_syscall_lock));
sdps = NULL;
slots = uvmexp.nswapdev;
if (slots > misc)
slots = misc;
if ((SIZE_T_MAX / sizeof sdp) <= misc)
return E2BIG;
sdpsize = (size_t)slots * sizeof sdp;
if (sdpsize <= sizeof stackbuf) {
sdps = stackbuf;
} else {
rw_exit(&swap_syscall_lock);
sdps = kmem_alloc(sdpsize, KM_SLEEP);
rw_enter(&swap_syscall_lock, RW_READER);
}
KASSERT(rw_lock_held(&swap_syscall_lock));
mutex_enter(&uvm_swap_data_lock);
sp = sdps;
count = 0;
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (++count <= slots)
*sp++ = sdp;
}
}
mutex_exit(&uvm_swap_data_lock);
if (misc > slots)
misc = slots;
if (misc > count)
misc = count;
error = 0;
count = 0;
sp = sdps;
while (misc --> 0) {
int inuse;
sdp = *sp++;
inuse = btodb((uint64_t)sdp->swd_npginuse <<
PAGE_SHIFT);
memset(&sep, 0, sizeof(sep));
swapent_cvt(&sep, sdp, inuse);
if (f)
(*f)(&sep, &sep);
if ((error = copyout(&sep, ptr, len)) != 0)
goto out;
ptr += len;
count++;
}
*retval = count;
out:;
if (sdps != stackbuf) {
kmem_free(sdps, sdpsize);
}
return error;
}
static int
swap_on(struct lwp *l, struct swapdev *sdp)
{
struct vnode *vp;
int error, npages, nblocks, size;
long addr;
vmem_addr_t result;
struct vattr va;
dev_t dev;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
vp = sdp->swd_vp;
dev = sdp->swd_dev;
if (vp != rootvp) {
if ((error = VOP_OPEN(vp, FREAD|FWRITE, l->l_cred)))
return (error);
}
UVMHIST_LOG(pdhist, " dev=%jd, major(dev)=%jd",
dev, major(dev), 0, 0);
switch (vp->v_type) {
case VBLK:
if ((nblocks = bdev_size(dev)) == -1) {
error = ENXIO;
goto bad;
}
break;
case VREG:
if ((error = VOP_GETATTR(vp, &va, l->l_cred)))
goto bad;
nblocks = (int)btodb(va.va_size);
sdp->swd_bsize = 1 << vp->v_mount->mnt_fs_bshift;
if (vp->v_tag == VT_NFS)
sdp->swd_maxactive = 2;
else
sdp->swd_maxactive = 8;
break;
default:
error = ENXIO;
goto bad;
}
sdp->swd_nblks = nblocks;
npages = dbtob((uint64_t)nblocks) >> PAGE_SHIFT;
if (vp->v_type == VBLK) {
size = npages - 1;
addr = 1;
} else {
size = npages;
addr = 0;
}
if (size < 1) {
UVMHIST_LOG(pdhist, " size <= 1!!", 0, 0, 0, 0);
error = EINVAL;
goto bad;
}
UVMHIST_LOG(pdhist," dev=%#jx: size=%jd addr=%jd",
dev, size, addr, 0);
sdp->swd_blist = blist_create(npages);
blist_free(sdp->swd_blist, addr, size);
sdp->swd_encinit = false;
if (vp == rootvp) {
struct mount *mp;
struct statvfs *sp;
int rootblocks, rootpages;
mp = rootvnode->v_mount;
sp = &mp->mnt_stat;
rootblocks = sp->f_blocks * btodb(sp->f_frsize);
rootblocks += (rootblocks >> 5) +
(rootblocks >> 6) +
(rootblocks >> 7);
rootpages = round_page(dbtob(rootblocks)) >> PAGE_SHIFT;
if (rootpages > size)
panic("swap_on: miniroot larger than swap?");
if (rootpages != blist_fill(sdp->swd_blist, addr, rootpages)) {
panic("swap_on: unable to preserve miniroot");
}
size -= rootpages;
printf("Preserved %d pages of miniroot ", rootpages);
printf("leaving %d pages of swap\n", size);
}
vref(vp);
error = vmem_alloc(swapmap, npages, VM_BESTFIT | VM_SLEEP, &result);
if (error != 0)
panic("swapdrum_add");
if (vp->v_type != VBLK) {
if (sw_reg_count++ == 0) {
KASSERT(sw_reg_workqueue == NULL);
if (workqueue_create(&sw_reg_workqueue, "swapiod",
sw_reg_iodone, NULL, PRIBIO, IPL_SOFTBIO,
WQ_MPSAFE) != 0)
panic("%s: workqueue_create failed", __func__);
}
}
sdp->swd_drumoffset = (int)result;
sdp->swd_drumsize = npages;
sdp->swd_npages = size;
mutex_enter(&uvm_swap_data_lock);
sdp->swd_flags &= ~SWF_FAKE;
sdp->swd_flags |= (SWF_INUSE|SWF_ENABLE);
uvmexp.swpages += size;
uvmexp.swpgavail += size;
mutex_exit(&uvm_swap_data_lock);
return (0);
bad:
if (sdp->swd_blist) {
blist_destroy(sdp->swd_blist);
}
if (vp != rootvp) {
(void)VOP_CLOSE(vp, FREAD|FWRITE, l->l_cred);
}
return (error);
}
static int
swap_off(struct lwp *l, struct swapdev *sdp)
{
int npages = sdp->swd_npages;
int error = 0;
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(pdhist,
" dev=%#jx, npages=%jd", sdp->swd_dev,npages, 0, 0);
KASSERT(rw_write_held(&swap_syscall_lock));
KASSERT(mutex_owned(&uvm_swap_data_lock));
sdp->swd_flags &= ~SWF_ENABLE;
uvmexp.swpgavail -= npages;
mutex_exit(&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) {
mutex_enter(&uvm_swap_data_lock);
sdp->swd_flags |= SWF_ENABLE;
uvmexp.swpgavail += npages;
mutex_exit(&uvm_swap_data_lock);
return error;
}
if (sdp->swd_vp->v_type != VBLK) {
KASSERT(sw_reg_count > 0);
KASSERT(sw_reg_workqueue != NULL);
if (--sw_reg_count == 0) {
workqueue_destroy(sw_reg_workqueue);
sw_reg_workqueue = NULL;
}
}
vrele(sdp->swd_vp);
if (sdp->swd_vp != rootvp) {
(void) VOP_CLOSE(sdp->swd_vp, FREAD|FWRITE, l->l_cred);
}
mutex_enter(&uvm_swap_data_lock);
uvmexp.swpages -= npages;
KASSERTMSG(uvmexp.swpginuse >= sdp->swd_npgbad,
"swpginuse %d sdp->swd_npgbad %d",
uvmexp.swpginuse, sdp->swd_npgbad);
uvmexp.swpginuse -= sdp->swd_npgbad;
if (swaplist_find(sdp->swd_vp, true) == NULL)
panic("%s: swapdev not in list", __func__);
swaplist_trim();
mutex_exit(&uvm_swap_data_lock);
vmem_free(swapmap, sdp->swd_drumoffset, sdp->swd_drumsize);
blist_destroy(sdp->swd_blist);
bufq_free(sdp->swd_tab);
explicit_memset(&sdp->swd_enckey, 0, sizeof sdp->swd_enckey);
explicit_memset(&sdp->swd_deckey, 0, sizeof sdp->swd_deckey);
mutex_destroy(&sdp->swd_lock);
kmem_free(sdp, sizeof(*sdp));
return (0);
}
void
uvm_swap_shutdown(struct lwp *l)
{
struct swapdev *sdp;
struct swappri *spp;
struct vnode *vp;
int error;
if (!uvm_swap_init_done || uvmexp.nswapdev == 0)
return;
printf("turning off swap...");
rw_enter(&swap_syscall_lock, RW_WRITER);
mutex_enter(&uvm_swap_data_lock);
again:
LIST_FOREACH(spp, &swap_priority, spi_swappri)
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
if (sdp->swd_flags & SWF_FAKE)
continue;
if ((sdp->swd_flags & (SWF_INUSE|SWF_ENABLE)) == 0)
continue;
#ifdef DEBUG
printf("\nturning off swap on %s...", sdp->swd_path);
#endif
vn_lock(vp = sdp->swd_vp, LK_EXCLUSIVE|LK_RETRY);
vref(vp);
error = swap_off(l, sdp);
vput(vp);
mutex_enter(&uvm_swap_data_lock);
if (error) {
printf("stopping swap on %s failed "
"with error %d\n", sdp->swd_path, error);
TAILQ_REMOVE(&spp->spi_swapdev, sdp, swd_next);
uvmexp.nswapdev--;
swaplist_trim();
}
goto again;
}
printf(" done\n");
mutex_exit(&uvm_swap_data_lock);
rw_exit(&swap_syscall_lock);
}
static int
swopen(dev_t dev, int flag, int mode, struct lwp *l)
{
static bool inited = false;
if (!inited) {
inited = true;
return 0;
}
return ENODEV;
}
static void
iobuf_redirect(struct buf *bp, struct vnode *vp)
{
if ((bp->b_flags & B_READ) == 0) {
mutex_enter(bp->b_objlock);
vwakeup(bp);
mutex_exit(bp->b_objlock);
mutex_enter(vp->v_interlock);
vp->v_numoutput++;
mutex_exit(vp->v_interlock);
}
bp->b_vp = vp;
bp->b_objlock = vp->v_interlock;
}
struct sw_physio_decrypt_context {
void *orig_buf;
void *orig_private;
void (*orig_iodone)(struct buf *);
int swslot;
};
static void
sw_physio_decrypt_iodone(struct buf *bp)
{
struct sw_physio_decrypt_context *ctx = bp->b_private;
void (*cb)(struct buf *bp) = ctx->orig_iodone;
size_t npages = bp->b_bcount >> PAGE_SHIFT;
KASSERT(ctx->swslot > 0);
KASSERT(npages << PAGE_SHIFT == bp->b_bcount);
if (bp->b_error == 0) {
if (bp->b_resid == 0) {
uvm_swap_decrypt_pages(ctx->swslot, bp->b_data,
npages);
memcpy(ctx->orig_buf, (uint8_t *)bp->b_data,
bp->b_bcount);
} else {
bp->b_error = EIO;
}
}
kmem_intr_free(bp->b_data, bp->b_bcount);
bp->b_data = ctx->orig_buf;
if (bp->b_error != 0) {
bp->b_resid = bp->b_bcount;
}
bp->b_private = ctx->orig_private;
kmem_intr_free(ctx, sizeof(*ctx));
(cb)(bp);
}
static void
swstrategy(struct buf *bp)
{
struct swapdev *sdp;
struct vnode *vp;
int pageno, bn;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
KASSERT((bp->b_flags & B_RAW) == 0 ||
rw_read_held(&swap_syscall_lock));
if ((dbtob((int64_t)bp->b_blkno) & PAGE_MASK) != 0 ||
(bp->b_bcount & PAGE_MASK) != 0) {
bp->b_error = ENOTSUP;
bp->b_resid = bp->b_bcount;
biodone(bp);
return;
}
pageno = dbtob((int64_t)bp->b_blkno) >> PAGE_SHIFT;
mutex_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(pageno);
KASSERT((bp->b_flags & B_RAW) != 0 ||
(sdp != NULL && sdp->swd_npginuse > 0));
mutex_exit(&uvm_swap_data_lock);
if (sdp == NULL) {
bp->b_error = EINVAL;
bp->b_resid = bp->b_bcount;
biodone(bp);
UVMHIST_LOG(pdhist, " failed to get swap device", 0, 0, 0, 0);
return;
}
if ((bp->b_flags & B_RAW) != 0 &&
atomic_load_relaxed(&uvm_swap_encrypt)) {
struct sw_physio_decrypt_context *ctx;
if ((bp->b_flags & B_READ) == 0) {
bp->b_error = ENOTSUP;
bp->b_resid = bp->b_bcount;
biodone(bp);
return;
}
ctx = kmem_intr_alloc(sizeof(*ctx), KM_SLEEP);
ctx->swslot = dbtob((int64_t)bp->b_blkno) >> PAGE_SHIFT;
KASSERT(ctx->swslot > 0);
ctx->orig_buf = bp->b_data;
ctx->orig_private = bp->b_private;
ctx->orig_iodone = bp->b_iodone;
bp->b_data = kmem_intr_alloc(bp->b_bcount, KM_SLEEP);
bp->b_private = ctx;
bp->b_iodone = sw_physio_decrypt_iodone;
}
pageno -= sdp->swd_drumoffset;
bn = btodb((uint64_t)pageno << PAGE_SHIFT);
UVMHIST_LOG(pdhist,
" Rd/Wr (0/1) %jd: mapoff=%#jx bn=%#jx bcount=%jd",
((bp->b_flags & B_READ) == 0) ? 1 : 0,
sdp->swd_drumoffset, bn, bp->b_bcount);
vp = sdp->swd_vp;
switch (vp->v_type) {
default:
panic("%s: vnode type 0x%x", __func__, vp->v_type);
case VBLK:
iobuf_redirect(bp, vp);
bp->b_blkno = bn;
VOP_STRATEGY(vp, bp);
return;
case VREG:
sw_reg_strategy(sdp, bp, bn);
return;
}
}
static int
swread(dev_t dev, struct uio *uio, int ioflag)
{
int ret;
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(pdhist,
" dev=%#jx offset=%#jx", dev, uio->uio_offset, 0, 0);
rw_enter(&swap_syscall_lock, RW_READER);
ret = physio(swstrategy, NULL, dev, B_READ, minphys, uio);
rw_exit(&swap_syscall_lock);
return ret;
}
static int
swwrite(dev_t dev, struct uio *uio, int ioflag)
{
int ret;
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(pdhist,
" dev=%#jx offset=%#jx", dev, uio->uio_offset, 0, 0);
rw_enter(&swap_syscall_lock, RW_READER);
ret = physio(swstrategy, NULL, dev, B_WRITE, minphys, uio);
rw_exit(&swap_syscall_lock);
return ret;
}
const struct bdevsw swap_bdevsw = {
.d_open = swopen,
.d_close = noclose,
.d_strategy = swstrategy,
.d_ioctl = noioctl,
.d_dump = nodump,
.d_psize = nosize,
.d_discard = nodiscard,
.d_flag = D_OTHER | D_MPSAFE,
};
const struct cdevsw swap_cdevsw = {
.d_open = nullopen,
.d_close = nullclose,
.d_read = swread,
.d_write = swwrite,
.d_ioctl = noioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = nommap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_OTHER | D_MPSAFE,
};
static void
sw_reg_strategy(struct swapdev *sdp, struct buf *bp, int bn)
{
struct vnode *devvp;
daddr_t nbn;
off_t byteoff;
int offset;
int off, nra, error, sz, resid;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
error = 0;
bp->b_resid = bp->b_bcount;
byteoff = dbtob((uint64_t)bn);
offset = 0;
for (resid = bp->b_resid; resid; resid -= sz) {
struct buf *nbp;
nra = 0;
error = VOP_BMAP(sdp->swd_vp, byteoff / sdp->swd_bsize,
&devvp, &nbn, &nra);
if (error == 0 && nbn == (daddr_t)-1) {
error = EIO;
}
if (error) {
break;
}
off = byteoff % sdp->swd_bsize;
sz = (1 + nra) * sdp->swd_bsize - off;
if (sz > resid)
sz = resid;
UVMHIST_LOG(pdhist, "sw_reg_strategy: "
"vp %#jx/%#jx offset %#jx/%#jx",
(uintptr_t)sdp->swd_vp, (uintptr_t)devvp, byteoff, nbn);
nbp = getiobuf(devvp, !uvm_lwp_is_pagedaemon(curlwp));
if (nbp == NULL) {
error = ENOMEM;
break;
}
nestiobuf_setup(bp, nbp, offset, sz);
iobuf_redirect(nbp, devvp);
nbp->b_blkno = nbn + btodb(off);
KASSERT(nbp->b_iodone == nestiobuf_iodone);
nbp->b_private2 = sdp;
nbp->b_iodone = sw_reg_biodone;
mutex_enter(&sdp->swd_lock);
bufq_put(sdp->swd_tab, nbp);
sw_reg_start(sdp);
mutex_exit(&sdp->swd_lock);
byteoff += sz;
offset += sz;
}
if (resid > 0) {
KASSERT(error != 0);
nestiobuf_done(bp, resid, error);
}
}
static void
sw_reg_start(struct swapdev *sdp)
{
struct buf *bp;
struct vnode *vp;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
KASSERT(mutex_owned(&sdp->swd_lock));
if ((sdp->swd_flags & SWF_BUSY) != 0)
return;
sdp->swd_flags |= SWF_BUSY;
while (sdp->swd_active < sdp->swd_maxactive) {
bp = bufq_get(sdp->swd_tab);
if (bp == NULL)
break;
sdp->swd_active++;
UVMHIST_LOG(pdhist,
"sw_reg_start: bp %#jx vp %#jx blkno %#jx cnt %#jx",
(uintptr_t)bp, (uintptr_t)bp->b_vp, (uintptr_t)bp->b_blkno,
bp->b_bcount);
vp = bp->b_vp;
VOP_STRATEGY(vp, bp);
}
sdp->swd_flags &= ~SWF_BUSY;
}
static void
sw_reg_biodone(struct buf *bp)
{
workqueue_enqueue(sw_reg_workqueue, &bp->b_work, NULL);
}
static void
sw_reg_iodone(struct work *wk, void *dummy)
{
struct buf *nbp = (void *)wk;
struct swapdev *sdp = nbp->b_private2;
KASSERT(&nbp->b_work == wk);
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(pdhist, " bp=%#jx vp=%#jx blkno=%#jx addr=%#jx",
(uintptr_t)nbp, (uintptr_t)nbp->b_vp, nbp->b_blkno,
(uintptr_t)nbp->b_data);
UVMHIST_LOG(pdhist, " cnt=%#jx resid=%#jx",
nbp->b_bcount, nbp->b_resid, 0, 0);
mutex_enter(&sdp->swd_lock);
KASSERT(sdp->swd_active > 0);
sdp->swd_active--;
sw_reg_start(sdp);
mutex_exit(&sdp->swd_lock);
nestiobuf_iodone(nbp);
}
int
uvm_swap_alloc(int *nslots , bool lessok)
{
struct swapdev *sdp;
struct swappri *spp;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
if (uvmexp.nswapdev < 1)
return 0;
if (*nslots > BLIST_MAX_ALLOC) {
if (__predict_false(lessok == false))
return 0;
*nslots = BLIST_MAX_ALLOC;
}
mutex_enter(&uvm_swap_data_lock);
ReTry:
LIST_FOREACH(spp, &swap_priority, spi_swappri) {
TAILQ_FOREACH(sdp, &spp->spi_swapdev, swd_next) {
uint64_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 == BLIST_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;
uvmexp.swpginuse += *nslots;
mutex_exit(&uvm_swap_data_lock);
UVMHIST_LOG(pdhist,
"success! returning %jd slots starting at %jd",
*nslots, result + sdp->swd_drumoffset, 0, 0);
return (result + sdp->swd_drumoffset);
}
}
if (*nslots > 1 && lessok) {
*nslots = 1;
goto ReTry;
}
mutex_exit(&uvm_swap_data_lock);
return 0;
}
bool
uvm_swapisfull(void)
{
int swpgonly;
bool rv;
if (uvmexp.swpages == 0) {
return true;
}
mutex_enter(&uvm_swap_data_lock);
KASSERT(uvmexp.swpgonly <= uvmexp.swpages);
swpgonly = (int)((uint64_t)uvmexp.swpgonly * 100 /
uvm_swapisfull_factor);
rv = (swpgonly >= uvmexp.swpgavail);
mutex_exit(&uvm_swap_data_lock);
return (rv);
}
void
uvm_swap_markbad(int startslot, int nslots)
{
struct swapdev *sdp;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(pdhist);
mutex_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(startslot);
KASSERT(sdp != NULL);
KASSERT(uvmexp.swpgonly >= nslots);
atomic_add_int(&uvmexp.swpgonly, -nslots);
sdp->swd_npgbad += nslots;
UVMHIST_LOG(pdhist, "now %jd bad", sdp->swd_npgbad, 0,0,0);
mutex_exit(&uvm_swap_data_lock);
}
void
uvm_swap_free(int startslot, int nslots)
{
struct swapdev *sdp;
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(pdhist, "freeing %jd slots starting at %jd", nslots,
startslot, 0, 0);
if (startslot == SWSLOT_BAD) {
return;
}
mutex_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(startslot);
KASSERT(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;
KASSERTMSG(uvmexp.swpginuse >= nslots, "swpginuse %d nslots %d",
uvmexp.swpginuse, nslots);
uvmexp.swpginuse -= nslots;
mutex_exit(&uvm_swap_data_lock);
}
int
uvm_swap_put(int swslot, struct vm_page **ppsp, int npages, int flags)
{
int error;
error = uvm_swap_io(ppsp, swslot, npages, B_WRITE |
((flags & PGO_SYNCIO) ? 0 : B_ASYNC));
return error;
}
int
uvm_swap_get(struct vm_page *page, int swslot, int flags)
{
int error;
atomic_inc_uint(&uvmexp.nswget);
KASSERT(flags & PGO_SYNCIO);
if (swslot == SWSLOT_BAD) {
return EIO;
}
error = uvm_swap_io(&page, swslot, 1, B_READ |
((flags & PGO_SYNCIO) ? 0 : B_ASYNC));
if (error == 0) {
KASSERT(uvmexp.swpgonly > 0);
atomic_dec_uint(&uvmexp.swpgonly);
}
return error;
}
static void
uvm_swap_encrypt_pages(int startslot, void *p, int npages)
{
struct swapdev *sdp;
int i;
if (!atomic_load_relaxed(&uvm_swap_encrypt)) {
return;
}
mutex_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(startslot);
if (!sdp->swd_encinit) {
uvm_swap_genkey(sdp);
}
KASSERT(sdp->swd_encinit);
mutex_exit(&uvm_swap_data_lock);
for (i = 0; i < npages; i++) {
int s = startslot + i;
KDASSERT(swapdrum_sdp_is(s, sdp));
KASSERT(s >= sdp->swd_drumoffset);
s -= sdp->swd_drumoffset;
KASSERT(s < sdp->swd_drumsize);
uvm_swap_encryptpage(sdp,
(void *)((uint8_t *)p + (vsize_t)i*PAGE_SIZE), s);
}
}
void
uvm_swap_decrypt_pages(int startslot, void *p, int npages)
{
struct swapdev *sdp;
bool encinit;
int i;
if (!atomic_load_relaxed(&uvm_swap_encrypt)) {
return;
}
mutex_enter(&uvm_swap_data_lock);
sdp = swapdrum_getsdp(startslot);
encinit = sdp->swd_encinit;
mutex_exit(&uvm_swap_data_lock);
if (!encinit) {
memset(p, 0, npages * PAGE_SIZE);
return;
}
for (i = 0; i < npages; i++) {
int s = startslot + i;
KDASSERT(swapdrum_sdp_is(s, sdp));
KASSERT(s >= sdp->swd_drumoffset);
s -= sdp->swd_drumoffset;
KASSERT(s < sdp->swd_drumsize);
uvm_swap_decryptpage(sdp,
(void *)((uint8_t *)p + (vsize_t)i*PAGE_SIZE), s);
}
}
static int
uvm_swap_io(struct vm_page **pps, int startslot, int npages, int flags)
{
daddr_t startblk;
struct buf *bp;
vaddr_t kva;
int error, mapinflags;
bool write, async, swap_encrypt;
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(pdhist,
"<- called, startslot=%jd, npages=%jd, flags=%#jx",
startslot, npages, flags, 0);
write = (flags & B_READ) == 0;
async = (flags & B_ASYNC) != 0;
swap_encrypt = atomic_load_relaxed(&uvm_swap_encrypt);
KASSERT(!uvm_lwp_is_pagedaemon(curlwp) || write);
KASSERT(!uvm_lwp_is_pagedaemon(curlwp) || async);
bp = getiobuf(swapdev_vp, !uvm_lwp_is_pagedaemon(curlwp));
if (bp == NULL) {
uvm_aio_aiodone_pages(pps, npages, true, ENOMEM);
return ENOMEM;
}
startblk = btodb((uint64_t)startslot << PAGE_SHIFT);
mapinflags = !write ?
UVMPAGER_MAPIN_WAITOK|UVMPAGER_MAPIN_READ :
UVMPAGER_MAPIN_WAITOK|UVMPAGER_MAPIN_WRITE;
if (write && swap_encrypt)
mapinflags |= UVMPAGER_MAPIN_READ;
kva = uvm_pagermapin(pps, npages, mapinflags);
if (write) {
uvm_swap_encrypt_pages(startslot, (void *)kva, npages);
}
bp->b_cflags = BC_BUSY | BC_NOCACHE;
bp->b_flags = (flags & (B_READ|B_ASYNC));
bp->b_proc = &proc0;
bp->b_vnbufs.le_next = NOLIST;
bp->b_data = (void *)kva;
bp->b_blkno = startblk;
bp->b_bufsize = bp->b_bcount = npages << PAGE_SHIFT;
if (write) {
mutex_enter(swapdev_vp->v_interlock);
swapdev_vp->v_numoutput++;
mutex_exit(swapdev_vp->v_interlock);
}
if (async) {
bp->b_iodone = uvm_aio_aiodone;
UVMHIST_LOG(pdhist, "doing async!", 0, 0, 0, 0);
if (uvm_lwp_is_pagedaemon(curlwp))
BIO_SETPRIO(bp, BPRIO_TIMECRITICAL);
else
BIO_SETPRIO(bp, BPRIO_TIMELIMITED);
} else {
bp->b_iodone = NULL;
BIO_SETPRIO(bp, BPRIO_TIMECRITICAL);
}
UVMHIST_LOG(pdhist,
"about to start io: data = %#jx blkno = %#jx, bcount = %jd",
(uintptr_t)bp->b_data, bp->b_blkno, bp->b_bcount, 0);
VOP_STRATEGY(swapdev_vp, bp);
if (async) {
KASSERT(write);
return 0;
}
error = biowait(bp);
if (error)
goto out;
if (!write) {
uvm_swap_decrypt_pages(startslot, (void *)kva, npages);
}
out:
uvm_pagermapout(kva, npages);
if (write) {
mutex_enter(swapdev_vp->v_interlock);
vwakeup(bp);
mutex_exit(swapdev_vp->v_interlock);
}
putiobuf(bp);
UVMHIST_LOG(pdhist, "<- done (sync) error=%jd", error, 0, 0, 0);
return (error);
}
static void
uvm_swap_genkey(struct swapdev *sdp)
{
uint8_t key[32];
KASSERT(!sdp->swd_encinit);
cprng_strong(kern_cprng, key, sizeof key, 0);
aes_setenckey256(&sdp->swd_enckey, key);
aes_setdeckey256(&sdp->swd_deckey, key);
explicit_memset(key, 0, sizeof key);
sdp->swd_encinit = true;
}
static void
uvm_swap_encryptpage(struct swapdev *sdp, void *kva, int slot)
{
uint8_t preiv[16] __aligned(16) = {0}, iv[16] __aligned(16);
le32enc(preiv, slot);
aes_enc(&sdp->swd_enckey, (const void *)preiv, iv, AES_256_NROUNDS);
aes_cbc_enc(&sdp->swd_enckey, kva, kva, PAGE_SIZE, iv,
AES_256_NROUNDS);
explicit_memset(&iv, 0, sizeof iv);
}
static void
uvm_swap_decryptpage(struct swapdev *sdp, void *kva, int slot)
{
uint8_t preiv[16] __aligned(16) = {0}, iv[16] __aligned(16);
le32enc(preiv, slot);
aes_enc(&sdp->swd_enckey, (const void *)preiv, iv, AES_256_NROUNDS);
aes_cbc_dec(&sdp->swd_deckey, kva, kva, PAGE_SIZE, iv,
AES_256_NROUNDS);
explicit_memset(&iv, 0, sizeof iv);
}
static int
sysctl_kern_uvm_swap_encrypt(SYSCTLFN_ARGS)
{
struct sysctlnode node;
int swap_encrypt = uvm_swap_encrypt;
int error;
node = *rnode;
node.sysctl_data = &swap_encrypt;
error = sysctl_lookup(SYSCTLFN_CALL(&node));
if (error != 0 || newp == NULL) {
return error;
}
error = 0;
mutex_enter(&uvm_swap_data_lock);
if (uvm_swap_encrypt != swap_encrypt) {
if (LIST_EMPTY(&swap_priority)) {
uvm_swap_encrypt = swap_encrypt;
} else {
error = EBUSY;
}
}
mutex_exit(&uvm_swap_data_lock);
return error;
}
SYSCTL_SETUP(sysctl_uvmswap_setup, "sysctl uvmswap setup")
{
sysctl_createv(clog, 0, NULL, NULL,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_BOOL, "swap_encrypt",
SYSCTL_DESCR("Encrypt data when swapped out to disk"),
sysctl_kern_uvm_swap_encrypt, 0, NULL, 0,
CTL_VM, CTL_CREATE, CTL_EOL);
}