#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvm_loan.c,v 1.104 2020/06/11 22:21:05 ad Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/mman.h>
#include <uvm/uvm.h>
#ifdef UVMHIST
UVMHIST_DEFINE(loanhist);
#endif
static int uvm_loananon(struct uvm_faultinfo *, void ***,
int, struct vm_anon *);
static int uvm_loanuobj(struct uvm_faultinfo *, void ***,
int, vaddr_t);
static int uvm_loanzero(struct uvm_faultinfo *, void ***, int);
static void uvm_unloananon(struct vm_anon **, int);
static void uvm_unloanpage(struct vm_page **, int);
static int uvm_loanpage(struct vm_page **, int, bool);
static inline int
uvm_loanentry(struct uvm_faultinfo *ufi, void ***output, int flags)
{
vaddr_t curaddr = ufi->orig_rvaddr;
vsize_t togo = ufi->size;
struct vm_aref *aref = &ufi->entry->aref;
struct uvm_object *uobj = ufi->entry->object.uvm_obj;
struct vm_anon *anon;
int rv, result = 0;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(loanhist);
if (aref->ar_amap) {
amap_lock(aref->ar_amap, RW_WRITER);
}
while (togo) {
if (aref->ar_amap) {
anon = amap_lookup(aref, curaddr - ufi->entry->start);
} else {
anon = NULL;
}
if (anon) {
rv = uvm_loananon(ufi, output, flags, anon);
} else if (uobj) {
rv = uvm_loanuobj(ufi, output, flags, curaddr);
} else if (UVM_ET_ISCOPYONWRITE(ufi->entry)) {
rv = uvm_loanzero(ufi, output, flags);
} else {
uvmfault_unlockall(ufi, aref->ar_amap, uobj);
rv = -1;
}
KASSERT(rv > 0 || aref->ar_amap == NULL ||
!rw_write_held(aref->ar_amap->am_lock));
KASSERT(rv > 0 || uobj == NULL ||
!rw_write_held(uobj->vmobjlock));
if (rv < 0) {
UVMHIST_LOG(loanhist, "failure %jd", rv, 0,0,0);
return (-1);
}
if (rv == 0) {
UVMHIST_LOG(loanhist, "relock failure %jd", result
,0,0,0);
return (result);
}
result++;
togo -= PAGE_SIZE;
curaddr += PAGE_SIZE;
}
if (aref->ar_amap) {
amap_unlock(aref->ar_amap);
}
uvmfault_unlockmaps(ufi, false);
UVMHIST_LOG(loanhist, "done %jd", result, 0,0,0);
return (result);
}
int
uvm_loan(struct vm_map *map, vaddr_t start, vsize_t len, void *v, int flags)
{
struct uvm_faultinfo ufi;
void **result, **output;
int rv, error;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(loanhist);
KASSERT(((flags & UVM_LOAN_TOANON) == 0) ^
((flags & UVM_LOAN_TOPAGE) == 0));
result = v;
output = &result[0];
while (len > 0) {
ufi.orig_map = map;
ufi.orig_rvaddr = start;
ufi.orig_size = len;
if (!uvmfault_lookup(&ufi, false)) {
error = ENOENT;
goto fail;
}
rv = uvm_loanentry(&ufi, &output, flags);
if (rv < 0) {
error = EINVAL;
goto fail;
}
if (rv) {
rv <<= PAGE_SHIFT;
len -= rv;
start += rv;
}
}
UVMHIST_LOG(loanhist, "success", 0,0,0,0);
return 0;
fail:
if (output - result) {
if (flags & UVM_LOAN_TOANON) {
uvm_unloananon((struct vm_anon **)result,
output - result);
} else {
uvm_unloanpage((struct vm_page **)result,
output - result);
}
}
UVMHIST_LOG(loanhist, "error %jd", error,0,0,0);
return (error);
}
int
uvm_loananon(struct uvm_faultinfo *ufi, void ***output, int flags,
struct vm_anon *anon)
{
struct vm_page *pg;
int error;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(loanhist);
if (flags & UVM_LOAN_TOANON) {
KASSERT(rw_write_held(anon->an_lock));
pg = anon->an_page;
if (pg && (pg->flags & PG_ANON) != 0 && anon->an_ref == 1) {
if (pg->wire_count > 0) {
UVMHIST_LOG(loanhist, "->A wired %#jx",
(uintptr_t)pg, 0, 0, 0);
uvmfault_unlockall(ufi,
ufi->entry->aref.ar_amap,
ufi->entry->object.uvm_obj);
return (-1);
}
pmap_page_protect(pg, VM_PROT_READ);
}
anon->an_ref++;
**output = anon;
(*output)++;
UVMHIST_LOG(loanhist, "->A done", 0,0,0,0);
return (1);
}
KASSERT(rw_write_held(anon->an_lock));
error = uvmfault_anonget(ufi, ufi->entry->aref.ar_amap, anon);
if (error) {
UVMHIST_LOG(loanhist, "error %jd", error,0,0,0);
KASSERT(error != ENOLCK);
if (error == ERESTART) {
return (0);
}
if (error == EAGAIN) {
kpause("loanagain", false, hz/2, NULL);
return (0);
}
return (-1);
}
pg = anon->an_page;
if (pg->wire_count > 0) {
UVMHIST_LOG(loanhist, "->K wired %#jx", (uintptr_t)pg, 0, 0, 0);
KASSERT(pg->uobject == NULL);
uvmfault_unlockall(ufi, ufi->entry->aref.ar_amap, NULL);
return (-1);
}
if (pg->loan_count == 0) {
pmap_page_protect(pg, VM_PROT_READ);
}
uvm_pagelock(pg);
pg->loan_count++;
KASSERT(pg->loan_count > 0);
uvm_pageactivate(pg);
uvm_pageunlock(pg);
**output = pg;
(*output)++;
if (pg->uobject)
rw_exit(pg->uobject->vmobjlock);
UVMHIST_LOG(loanhist, "->K done", 0,0,0,0);
return (1);
}
static int
uvm_loanpage(struct vm_page **pgpp, int npages, bool busied)
{
int i;
int error = 0;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(loanhist);
for (i = 0; i < npages; i++) {
struct vm_page *pg = pgpp[i];
KASSERT(pg->uobject != NULL);
KASSERT(pg->uobject == pgpp[0]->uobject);
KASSERT(!(pg->flags & (PG_RELEASED|PG_PAGEOUT)));
KASSERT(rw_write_held(pg->uobject->vmobjlock));
KASSERT(busied == ((pg->flags & PG_BUSY) != 0));
if (pg->wire_count > 0) {
UVMHIST_LOG(loanhist, "wired %#jx", (uintptr_t)pg,
0, 0, 0);
error = EBUSY;
break;
}
if (pg->loan_count == 0) {
pmap_page_protect(pg, VM_PROT_READ);
}
uvm_pagelock(pg);
pg->loan_count++;
KASSERT(pg->loan_count > 0);
uvm_pageactivate(pg);
uvm_pageunlock(pg);
}
if (busied) {
uvm_page_unbusy(pgpp, npages);
}
if (error) {
krwlock_t *slock = pgpp[0]->uobject->vmobjlock;
rw_exit(slock);
uvm_unloan(pgpp, i, UVM_LOAN_TOPAGE);
rw_enter(slock, RW_WRITER);
}
UVMHIST_LOG(loanhist, "done %jd", error, 0, 0, 0);
return error;
}
#define UVM_LOAN_GET_CHUNK 16
static int
uvm_loanuobjchunk(struct uvm_object *uobj, voff_t pgoff, int orignpages,
struct vm_page **pgpp)
{
int error, npages;
rw_enter(uobj->vmobjlock, RW_WRITER);
reget:
npages = orignpages;
error = (*uobj->pgops->pgo_get)(uobj, pgoff, pgpp, &npages, 0,
VM_PROT_READ, 0, PGO_SYNCIO);
switch (error) {
case 0:
KASSERT(npages == orignpages);
rw_enter(uobj->vmobjlock, RW_WRITER);
for (int i = 0; i < npages; i++) {
KASSERT(pgpp[i]->uobject->vmobjlock == uobj->vmobjlock);
if ((pgpp[i]->flags & PG_RELEASED) != 0) {
uvm_page_unbusy(pgpp, npages);
goto reget;
}
}
error = uvm_loanpage(pgpp, npages, true);
rw_exit(uobj->vmobjlock);
if (error != 0) {
memset(pgpp, 0, sizeof(pgpp[0]) * npages);
}
return error;
case EAGAIN:
kpause("loanuopg", false, hz/2, NULL);
rw_enter(uobj->vmobjlock, RW_WRITER);
goto reget;
default:
return error;
}
}
int
uvm_loanuobjpages(struct uvm_object *uobj, voff_t pgoff, int npages,
struct vm_page **pgpp)
{
int ndone, error, chunk;
KASSERT(npages > 0);
memset(pgpp, 0, sizeof(pgpp[0]) * npages);
for (ndone = 0; ndone < npages; ndone += chunk) {
chunk = MIN(UVM_LOAN_GET_CHUNK, npages - ndone);
error = uvm_loanuobjchunk(uobj, pgoff + (ndone << PAGE_SHIFT),
chunk, pgpp + ndone);
if (error != 0) {
if (ndone != 0) {
uvm_unloan(pgpp, ndone, UVM_LOAN_TOPAGE);
}
break;
}
}
return error;
}
static int
uvm_loanuobj(struct uvm_faultinfo *ufi, void ***output, int flags, vaddr_t va)
{
struct vm_amap *amap = ufi->entry->aref.ar_amap;
struct uvm_object *uobj = ufi->entry->object.uvm_obj;
struct vm_page *pg;
int error, npages;
bool locked;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(loanhist);
rw_enter(uobj->vmobjlock, RW_WRITER);
if (uobj->pgops->pgo_get) {
npages = 1;
pg = NULL;
error = (*uobj->pgops->pgo_get)(uobj,
va - ufi->entry->start + ufi->entry->offset,
&pg, &npages, 0, VM_PROT_READ, MADV_NORMAL, PGO_LOCKED);
} else {
error = EIO;
}
if (error && error != EBUSY) {
uvmfault_unlockall(ufi, amap, uobj);
return (-1);
}
if (error == EBUSY) {
uvmfault_unlockall(ufi, amap, NULL);
npages = 1;
error = (*uobj->pgops->pgo_get)(uobj,
va - ufi->entry->start + ufi->entry->offset,
&pg, &npages, 0, VM_PROT_READ, MADV_NORMAL, PGO_SYNCIO);
if (error) {
if (error == EAGAIN) {
kpause("fltagain2", false, hz/2, NULL);
return (0);
}
return (-1);
}
locked = uvmfault_relock(ufi);
if (locked && amap)
amap_lock(amap, RW_WRITER);
uobj = pg->uobject;
rw_enter(uobj->vmobjlock, RW_WRITER);
if ((pg->flags & PG_RELEASED) != 0 ||
(locked && amap && amap_lookup(&ufi->entry->aref,
ufi->orig_rvaddr - ufi->entry->start))) {
if (locked)
uvmfault_unlockall(ufi, amap, NULL);
locked = false;
}
if ((pg->flags & PG_RELEASED) == 0) {
uvm_pagelock(pg);
uvm_pagewakeup(pg);
uvm_pageunlock(pg);
pg->flags &= ~PG_BUSY;
UVM_PAGE_OWN(pg, NULL);
}
if (locked == false) {
if (pg->flags & PG_RELEASED) {
uvm_pagefree(pg);
}
rw_exit(uobj->vmobjlock);
return (0);
}
}
KASSERT(uobj->vmobjlock == pg->uobject->vmobjlock);
if ((flags & UVM_LOAN_TOANON) == 0) {
if (uvm_loanpage(&pg, 1, false)) {
uvmfault_unlockall(ufi, amap, uobj);
return (-1);
}
rw_exit(uobj->vmobjlock);
**output = pg;
(*output)++;
return (1);
}
#ifdef notdef
if (pg->uanon) {
anon = pg->uanon;
anon->an_ref++;
uvm_pagelock(pg);
uvm_pagewakeup(pg);
uvm_pageunlock(pg);
pg->flags &= ~PG_BUSY;
UVM_PAGE_OWN(pg, NULL);
rw_exit(uobj->vmobjlock);
**output = anon;
(*output)++;
return (1);
}
anon = uvm_analloc();
if (anon == NULL) {
goto fail;
}
if (pg->wire_count > 0) {
UVMHIST_LOG(loanhist, "wired %#jx", (uintptr_t)pg, 0, 0, 0);
goto fail;
}
if (pg->loan_count == 0) {
pmap_page_protect(pg, VM_PROT_READ);
}
uvm_pagelock(pg);
pg->loan_count++;
KASSERT(pg->loan_count > 0);
pg->uanon = anon;
anon->an_page = pg;
anon->an_lock =
uvm_pageactivate(pg);
uvm_pagewakeup(pg);
uvm_pageunlock(pg);
pg->flags &= ~PG_BUSY;
UVM_PAGE_OWN(pg, NULL);
rw_exit(uobj->vmobjlock);
rw_exit(&anon->an_lock);
**output = anon;
(*output)++;
return (1);
fail:
UVMHIST_LOG(loanhist, "fail", 0,0,0,0);
uvm_pagelock(pg);
uvm_pagewakeup(pg);
uvm_pageunlock(pg);
pg->flags &= ~PG_BUSY;
UVM_PAGE_OWN(pg, NULL);
uvmfault_unlockall(ufi, amap, uobj, NULL);
if (anon) {
anon->an_ref--;
uvm_anfree(anon);
}
#endif
return (-1);
}
static struct uvm_object uvm_loanzero_object;
static krwlock_t uvm_loanzero_lock __cacheline_aligned;
static int
uvm_loanzero(struct uvm_faultinfo *ufi, void ***output, int flags)
{
struct vm_page *pg;
struct vm_amap *amap = ufi->entry->aref.ar_amap;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(loanhist);
again:
rw_enter(uvm_loanzero_object.vmobjlock, RW_WRITER);
pg = uvm_pagelookup(&uvm_loanzero_object, 0);
if (__predict_false(pg == NULL)) {
while ((pg = uvm_pagealloc(&uvm_loanzero_object, 0, NULL,
UVM_PGA_ZERO)) == NULL) {
rw_exit(uvm_loanzero_object.vmobjlock);
uvmfault_unlockall(ufi, amap, NULL);
uvm_wait("loanzero");
if (!uvmfault_relock(ufi)) {
return (0);
}
if (amap) {
amap_lock(amap, RW_WRITER);
}
goto again;
}
pg->flags &= ~(PG_BUSY|PG_FAKE);
pg->flags |= PG_RDONLY;
uvm_pagelock(pg);
uvm_pageactivate(pg);
uvm_pagewakeup(pg);
uvm_pageunlock(pg);
UVM_PAGE_OWN(pg, NULL);
}
if ((flags & UVM_LOAN_TOANON) == 0) {
mutex_enter(&pg->interlock);
pg->loan_count++;
KASSERT(pg->loan_count > 0);
mutex_exit(&pg->interlock);
rw_exit(uvm_loanzero_object.vmobjlock);
**output = pg;
(*output)++;
return (1);
}
#ifdef notdef
if (pg->uanon) {
anon = pg->uanon;
rw_enter(&anon->an_lock, RW_WRITER);
anon->an_ref++;
rw_exit(&anon->an_lock);
rw_exit(uvm_loanzero_object.vmobjlock);
**output = anon;
(*output)++;
return (1);
}
anon = uvm_analloc();
if (anon == NULL) {
rw_exit(uvm_loanzero_object.vmobjlock);
uvmfault_unlockall(ufi, amap, NULL, NULL);
return (-1);
}
anon->an_page = pg;
pg->uanon = anon;
uvm_pagelock(pg);
pg->loan_count++;
KASSERT(pg->loan_count > 0);
uvm_pageactivate(pg);
uvm_pageunlock(pg);
rw_exit(&anon->an_lock);
rw_exit(uvm_loanzero_object.vmobjlock);
**output = anon;
(*output)++;
return (1);
#else
return (-1);
#endif
}
static void
uvm_unloananon(struct vm_anon **aloans, int nanons)
{
#ifdef notdef
struct vm_anon *anon, *to_free = NULL;
amap_lock(amap, RW_WRITER);
while (nanons-- > 0) {
anon = *aloans++;
if (--anon->an_ref == 0) {
uvm_anfree(anon);
}
}
amap_unlock(amap);
#endif
}
static void
uvm_unloanpage(struct vm_page **ploans, int npages)
{
struct vm_page *pg;
krwlock_t *slock;
while (npages-- > 0) {
pg = *ploans++;
mutex_enter(&pg->interlock);
slock = NULL;
while (pg->uobject != NULL || pg->uanon != NULL) {
if (pg->uobject != NULL) {
slock = pg->uobject->vmobjlock;
} else {
slock = pg->uanon->an_lock;
}
if (rw_tryenter(slock, RW_WRITER)) {
break;
}
kpause("livelock", false, 1, &pg->interlock);
slock = NULL;
}
KASSERT(pg->loan_count > 0);
pg->loan_count--;
if (pg->uobject == NULL && pg->uanon != NULL &&
(pg->flags & PG_ANON) == 0) {
KASSERT(pg->loan_count > 0);
pg->loan_count--;
pg->flags |= PG_ANON;
}
mutex_exit(&pg->interlock);
if (pg->loan_count == 0 && pg->uobject == NULL &&
pg->uanon == NULL) {
KASSERT((pg->flags & PG_BUSY) == 0);
uvm_pagefree(pg);
}
if (slock != NULL) {
rw_exit(slock);
}
}
}
void
uvm_unloan(void *v, int npages, int flags)
{
if (flags & UVM_LOAN_TOANON) {
uvm_unloananon(v, npages);
} else {
uvm_unloanpage(v, npages);
}
}
static int
ulz_put(struct uvm_object *uobj, voff_t start, voff_t stop, int flags)
{
struct vm_page *pg;
KDASSERT(uobj == &uvm_loanzero_object);
if ((flags & PGO_FREE) == 0) {
rw_exit(uobj->vmobjlock);
return 0;
}
pg = uvm_pagelookup(uobj, 0);
KASSERT(pg != NULL);
uvm_pagelock(pg);
if (pg->uanon) {
uvm_pageactivate(pg);
} else {
uvm_pagedequeue(pg);
}
uvm_pageunlock(pg);
rw_exit(uobj->vmobjlock);
return 0;
}
static const struct uvm_pagerops ulz_pager = {
.pgo_put = ulz_put,
};
void
uvm_loan_init(void)
{
rw_init(&uvm_loanzero_lock);
uvm_obj_init(&uvm_loanzero_object, &ulz_pager, false, 0);
uvm_obj_setlock(&uvm_loanzero_object, &uvm_loanzero_lock);
UVMHIST_INIT(loanhist, 300);
}
struct vm_page *
uvm_loanbreak(struct vm_page *uobjpage)
{
struct vm_page *pg;
struct uvm_object *uobj __diagused = uobjpage->uobject;
KASSERT(uobj != NULL);
KASSERT(rw_write_held(uobj->vmobjlock));
pg = uvm_pagealloc(NULL, 0, NULL, 0);
if (pg == NULL)
return NULL;
uvm_pagecopy(uobjpage, pg);
pg->flags &= ~PG_FAKE;
KASSERT(uvm_pagegetdirty(pg) == UVM_PAGE_STATUS_DIRTY);
pmap_page_protect(uobjpage, VM_PROT_NONE);
if ((uobjpage->flags & PG_BUSY) != 0) {
uobjpage->flags &= ~PG_BUSY;
UVM_PAGE_OWN(uobjpage, NULL);
}
uvm_pagelock2(uobjpage, pg);
uvm_pagewakeup(uobjpage);
if (uobjpage->uanon == NULL)
uvm_pagedequeue(uobjpage);
uvm_pagereplace(uobjpage, pg);
uvm_pageactivate(pg);
uvm_pageunlock2(uobjpage, pg);
return pg;
}
int
uvm_loanbreak_anon(struct vm_anon *anon, struct uvm_object *uobj)
{
struct vm_page *newpg, *oldpg;
unsigned oldstatus;
KASSERT(rw_write_held(anon->an_lock));
KASSERT(uobj == NULL || rw_write_held(uobj->vmobjlock));
KASSERT(anon->an_page->loan_count > 0);
newpg = uvm_pagealloc(NULL, 0, NULL, 0);
if (newpg == NULL) {
return ENOMEM;
}
oldpg = anon->an_page;
uvm_pagecopy(oldpg, newpg);
KASSERT(uvm_pagegetdirty(newpg) == UVM_PAGE_STATUS_DIRTY);
pmap_page_protect(oldpg, VM_PROT_NONE);
oldstatus = uvm_pagegetdirty(anon->an_page);
uvm_pagelock2(oldpg, newpg);
if (uobj == NULL) {
KASSERT((oldpg->flags & PG_ANON) != 0);
oldpg->flags &= ~PG_ANON;
uvm_pagedequeue(oldpg);
}
oldpg->uanon = NULL;
if (uobj) {
KASSERT((oldpg->pqflags & PG_ANON) == 0);
oldpg->loan_count--;
}
anon->an_page = newpg;
newpg->uanon = anon;
newpg->flags |= PG_ANON;
uvm_pageactivate(newpg);
uvm_pageunlock2(oldpg, newpg);
newpg->flags &= ~(PG_BUSY|PG_FAKE);
UVM_PAGE_OWN(newpg, NULL);
if (uobj) {
rw_exit(uobj->vmobjlock);
}
kpreempt_disable();
if (uobj == NULL) {
CPU_COUNT(CPU_COUNT_ANONUNKNOWN + oldstatus, -1);
}
CPU_COUNT(CPU_COUNT_ANONDIRTY, 1);
kpreempt_enable();
return 0;
}