#include "opt_swap.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sysmsg.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/nlookup.h>
#include <sys/sysctl.h>
#include <sys/dmap.h>
#include <sys/vnode.h>
#include <sys/fcntl.h>
#include <sys/blist.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <vm/swap_pager.h>
#include <vm/vm_zone.h>
#include <vm/vm_param.h>
#include <sys/mutex2.h>
#include <sys/spinlock2.h>
#ifndef NSWAPDEV
#define NSWAPDEV 4
#endif
static struct swdevt should_be_malloced[NSWAPDEV];
struct swdevt *swdevt = should_be_malloced;
static swblk_t nswap;
static struct mtx swap_mtx = MTX_INITIALIZER("swpmtx");
int nswdev = NSWAPDEV;
swblk_t vm_swap_size;
swblk_t vm_swap_max;
static int swapoff_one(int index);
struct vnode *swapdev_vp;
static int
swapdev_strategy(struct vop_strategy_args *ap)
{
struct bio *bio = ap->a_bio;
struct bio *nbio;
struct buf *bp = bio->bio_buf;
swblk_t sz, off, seg, blkno, nblkno;
int index;
struct swdevt *sp;
sz = howmany(bp->b_bcount, PAGE_SIZE);
blkno = (swblk_t)(bio->bio_offset >> PAGE_SHIFT);
nbio = push_bio(bio);
if (nswdev > 1) {
off = blkno % SWB_DMMAX;
if (off + sz > SWB_DMMAX) {
bp->b_error = EINVAL;
bp->b_flags |= B_ERROR;
biodone(bio);
return 0;
}
seg = blkno / SWB_DMMAX;
index = seg % nswdev;
seg /= nswdev;
nbio->bio_offset = (off_t)(seg * SWB_DMMAX + off) << PAGE_SHIFT;
} else {
index = 0;
nbio->bio_offset = bio->bio_offset;
}
nblkno = (swblk_t)(nbio->bio_offset >> PAGE_SHIFT);
sp = &swdevt[index];
if (nblkno + sz > sp->sw_nblks) {
bp->b_error = EINVAL;
bp->b_flags |= B_ERROR;
biodone(bio);
return 0;
}
if (sp->sw_vp == NULL) {
bp->b_error = ENODEV;
bp->b_flags |= B_ERROR;
biodone(bio);
return 0;
}
vn_strategy(sp->sw_vp, nbio);
return 0;
}
static int
swapdev_inactive(struct vop_inactive_args *ap)
{
vrecycle(ap->a_vp);
return(0);
}
static int
swapdev_reclaim(struct vop_reclaim_args *ap)
{
return(0);
}
static struct vop_ops swapdev_vnode_vops = {
.vop_default = vop_defaultop,
.vop_strategy = swapdev_strategy,
.vop_inactive = swapdev_inactive,
.vop_reclaim = swapdev_reclaim
};
static struct vop_ops *swapdev_vnode_vops_p = &swapdev_vnode_vops;
VNODEOP_SET(swapdev_vnode_vops);
int
sys_swapon(struct sysmsg *sysmsg, const struct swapon_args *uap)
{
struct thread *td = curthread;
struct vattr attr;
struct vnode *vp;
struct nlookupdata nd;
int error;
error = caps_priv_check_self(SYSCAP_RESTRICTEDROOT);
if (error)
return (error);
mtx_lock(&swap_mtx);
vp = NULL;
error = nlookup_init(&nd, uap->name, UIO_USERSPACE, NLC_FOLLOW);
if (error == 0)
error = nlookup(&nd);
if (error == 0)
error = cache_vref(&nd.nl_nch, nd.nl_cred, &vp);
nlookup_done(&nd);
if (error) {
mtx_unlock(&swap_mtx);
return (error);
}
if (vn_isdisk(vp, &error)) {
error = swaponvp(td, vp, 0);
} else if (vp->v_type == VREG && vp->v_tag == VT_NFS &&
(error = VOP_GETATTR(vp, &attr)) == 0) {
error = swaponvp(td, vp, attr.va_size / DEV_BSIZE);
}
if (error)
vrele(vp);
mtx_unlock(&swap_mtx);
return (error);
}
int
swaponvp(struct thread *td, struct vnode *vp, u_quad_t nblks)
{
swblk_t aligned_nblks;
int64_t dpsize;
struct ucred *cred;
struct swdevt *sp;
swblk_t vsbase;
swblk_t dvbase;
cdev_t dev;
int index;
int error;
swblk_t blk;
cred = td->td_ucred;
lwkt_gettoken(&vm_token);
mtx_lock(&swap_mtx);
if (!swapdev_vp) {
error = getspecialvnode(VT_NON, NULL, &swapdev_vnode_vops_p,
&swapdev_vp, 0, 0);
if (error)
panic("Cannot get vnode for swapdev");
swapdev_vp->v_type = VNON;
vsetflags(swapdev_vp, VKVABIO);
vx_unlock(swapdev_vp);
}
for (sp = swdevt, index = 0 ; index < nswdev; index++, sp++) {
if (sp->sw_vp == vp) {
error = EBUSY;
goto done;
}
if (!sp->sw_vp)
goto found;
}
error = EINVAL;
goto done;
found:
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
error = VOP_OPEN(vp, FREAD | FWRITE, cred, NULL);
vn_unlock(vp);
if (error)
goto done;
if (vp->v_type == VCHR)
dev = vp->v_rdev;
else
dev = NULL;
if (nblks == 0 && dev != NULL) {
dpsize = dev_dpsize(dev);
if (dpsize == -1) {
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
VOP_CLOSE(vp, FREAD | FWRITE, NULL);
vn_unlock(vp);
error = ENXIO;
goto done;
}
nblks = (u_quad_t)dpsize;
}
if (nblks == 0) {
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
VOP_CLOSE(vp, FREAD | FWRITE, NULL);
vn_unlock(vp);
error = ENXIO;
goto done;
}
nblks &= ~(u_quad_t)(ctodb(1) - 1);
nblks = dbtoc(nblks);
if (nblks > BLIST_MAXBLKS / nswdev) {
kprintf("exceeded maximum of %ld blocks per swap unit\n",
(long)BLIST_MAXBLKS / nswdev);
vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
VOP_CLOSE(vp, FREAD | FWRITE, NULL);
vn_unlock(vp);
error = ENXIO;
goto done;
}
sp->sw_vp = vp;
sp->sw_dev = devid_from_dev(dev);
sp->sw_device = dev;
sp->sw_flags = SW_FREED;
sp->sw_nused = 0;
aligned_nblks = (swblk_t)((nblks + SWB_DMMASK) &
~(u_swblk_t)SWB_DMMASK);
sp->sw_nblks = aligned_nblks;
if (aligned_nblks * nswdev > nswap)
nswap = aligned_nblks * nswdev;
if (swapblist == NULL)
swapblist = blist_create(nswap);
else
blist_resize(&swapblist, nswap, 0);
for (dvbase = SWB_DMMAX; dvbase < aligned_nblks; dvbase += SWB_DMMAX) {
blk = min(aligned_nblks - dvbase, SWB_DMMAX);
vsbase = index * SWB_DMMAX + dvbase * nswdev;
blist_free(swapblist, vsbase, blk);
vm_swap_size += blk;
vm_swap_max += blk;
}
swap_pager_newswap();
error = 0;
done:
mtx_unlock(&swap_mtx);
lwkt_reltoken(&vm_token);
return (error);
}
int
sys_swapoff(struct sysmsg *sysmsg, const struct swapoff_args *uap)
{
struct vnode *vp;
struct nlookupdata nd;
struct swdevt *sp;
int error, index;
error = caps_priv_check_self(SYSCAP_RESTRICTEDROOT);
if (error)
return (error);
mtx_lock(&swap_mtx);
vp = NULL;
error = nlookup_init(&nd, uap->name, UIO_USERSPACE, NLC_FOLLOW);
if (error == 0)
error = nlookup(&nd);
if (error == 0)
error = cache_vref(&nd.nl_nch, nd.nl_cred, &vp);
nlookup_done(&nd);
if (error)
goto done;
for (sp = swdevt, index = 0; index < nswdev; index++, sp++) {
if (sp->sw_vp == vp)
goto found;
}
error = EINVAL;
goto done;
found:
error = swapoff_one(index);
swap_pager_newswap();
done:
mtx_unlock(&swap_mtx);
return (error);
}
static int
swapoff_one(int index)
{
swblk_t blk, aligned_nblks;
swblk_t dvbase, vsbase;
u_int pq_active_clean, pq_inactive_clean;
struct swdevt *sp;
struct vm_page marker;
vm_page_t m;
int q;
mtx_lock(&swap_mtx);
sp = &swdevt[index];
aligned_nblks = sp->sw_nblks;
pq_active_clean = pq_inactive_clean = 0;
for (q = 0; q < PQ_L2_SIZE; ++q) {
bzero(&marker, sizeof(marker));
marker.flags = PG_FICTITIOUS | PG_MARKER;
marker.busy_count = PBUSY_LOCKED;
marker.queue = PQ_ACTIVE + q;
marker.pc = q;
marker.wire_count = 1;
vm_page_queues_spin_lock(marker.queue);
TAILQ_INSERT_HEAD(&vm_page_queues[marker.queue].pl,
&marker, pageq);
while ((m = TAILQ_NEXT(&marker, pageq)) != NULL) {
TAILQ_REMOVE(&vm_page_queues[marker.queue].pl,
&marker, pageq);
TAILQ_INSERT_AFTER(&vm_page_queues[marker.queue].pl, m,
&marker, pageq);
if (m->flags & (PG_MARKER | PG_FICTITIOUS))
continue;
if (vm_page_busy_try(m, FALSE) == 0) {
vm_page_queues_spin_unlock(marker.queue);
if (m->dirty == 0) {
vm_page_test_dirty(m);
if (m->dirty == 0)
++pq_active_clean;
}
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker.queue);
}
}
TAILQ_REMOVE(&vm_page_queues[marker.queue].pl, &marker, pageq);
vm_page_queues_spin_unlock(marker.queue);
marker.queue = PQ_INACTIVE + q;
marker.pc = q;
vm_page_queues_spin_lock(marker.queue);
TAILQ_INSERT_HEAD(&vm_page_queues[marker.queue].pl,
&marker, pageq);
while ((m = TAILQ_NEXT(&marker, pageq)) != NULL) {
TAILQ_REMOVE(
&vm_page_queues[marker.queue].pl,
&marker, pageq);
TAILQ_INSERT_AFTER(
&vm_page_queues[marker.queue].pl,
m, &marker, pageq);
if (m->flags & (PG_MARKER | PG_FICTITIOUS))
continue;
if (vm_page_busy_try(m, FALSE) == 0) {
vm_page_queues_spin_unlock(marker.queue);
if (m->dirty == 0) {
vm_page_test_dirty(m);
if (m->dirty == 0)
++pq_inactive_clean;
}
vm_page_wakeup(m);
vm_page_queues_spin_lock(marker.queue);
}
}
TAILQ_REMOVE(&vm_page_queues[marker.queue].pl,
&marker, pageq);
vm_page_queues_spin_unlock(marker.queue);
}
if (vmstats.v_free_count + vmstats.v_cache_count + pq_active_clean +
pq_inactive_clean + vm_swap_size < aligned_nblks + nswap_lowat) {
mtx_unlock(&swap_mtx);
return (ENOMEM);
}
sp->sw_flags |= SW_CLOSING;
for (dvbase = SWB_DMMAX; dvbase < aligned_nblks; dvbase += SWB_DMMAX) {
blk = min(aligned_nblks - dvbase, SWB_DMMAX);
vsbase = index * SWB_DMMAX + dvbase * nswdev;
vm_swap_size -= blist_fill(swapblist, vsbase, blk);
vm_swap_max -= blk;
}
if (swap_pager_swapoff(index) && swap_pager_swapoff(index)) {
mtx_unlock(&swap_mtx);
return (EINTR);
}
vn_lock(sp->sw_vp, LK_EXCLUSIVE | LK_RETRY);
VOP_CLOSE(sp->sw_vp, FREAD | FWRITE, NULL);
vn_unlock(sp->sw_vp);
vrele(sp->sw_vp);
bzero(swdevt + index, sizeof(struct swdevt));
for (sp = swdevt, aligned_nblks = 0; sp < swdevt + nswdev; sp++) {
if (sp->sw_vp)
aligned_nblks = max(aligned_nblks, sp->sw_nblks);
}
nswap = aligned_nblks * nswdev;
if (nswap == 0) {
blist_destroy(swapblist);
swapblist = NULL;
vrele(swapdev_vp);
swapdev_vp = NULL;
} else {
blist_resize(&swapblist, nswap, 0);
}
mtx_unlock(&swap_mtx);
return (0);
}
void
swapacctspace(swblk_t base, swblk_t count)
{
int index;
swblk_t seg;
vm_swap_size += count;
seg = base / SWB_DMMAX;
index = seg % nswdev;
swdevt[index].sw_nused -= count;
}
static int
sysctl_vm_swap_info(SYSCTL_HANDLER_ARGS)
{
struct xswdev xs;
struct swdevt *sp;
int error;
int n;
error = 0;
for (n = 0; n < nswdev; ++n) {
sp = &swdevt[n];
xs.xsw_size = sizeof(xs);
xs.xsw_version = XSWDEV_VERSION;
xs.xsw_blksize = PAGE_SIZE;
xs.xsw_dev = sp->sw_dev;
xs.xsw_flags = sp->sw_flags;
xs.xsw_nblks = sp->sw_nblks;
xs.xsw_used = sp->sw_nused;
error = SYSCTL_OUT(req, &xs, sizeof(xs));
if (error)
break;
}
return (error);
}
SYSCTL_INT(_vm, OID_AUTO, nswapdev, CTLFLAG_RD, &nswdev, 0,
"Number of swap devices");
SYSCTL_NODE(_vm, OID_AUTO, swap_info_array, CTLFLAG_RD, sysctl_vm_swap_info,
"Swap statistics by device");