#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: uvm_device.c,v 1.80 2022/07/07 13:27:02 riastradh Exp $");
#include "opt_uvmhist.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/proc.h>
#include <sys/kmem.h>
#include <uvm/uvm.h>
#include <uvm/uvm_device.h>
#include <uvm/uvm_pmap.h>
LIST_HEAD(udv_list_struct, uvm_device);
static struct udv_list_struct udv_list;
static kmutex_t udv_lock __cacheline_aligned;
static void udv_init(void);
static void udv_reference(struct uvm_object *);
static void udv_detach(struct uvm_object *);
static int udv_fault(struct uvm_faultinfo *, vaddr_t,
struct vm_page **, int, int, vm_prot_t,
int);
const struct uvm_pagerops uvm_deviceops = {
.pgo_init = udv_init,
.pgo_reference = udv_reference,
.pgo_detach = udv_detach,
.pgo_fault = udv_fault,
};
static void
udv_init(void)
{
LIST_INIT(&udv_list);
mutex_init(&udv_lock, MUTEX_DEFAULT, IPL_NONE);
}
struct uvm_object *
udv_attach(dev_t device, vm_prot_t accessprot,
voff_t off,
vsize_t size )
{
struct uvm_device *udv, *lcv;
const struct cdevsw *cdev;
dev_mmap_t *mapfn;
UVMHIST_FUNC(__func__);
UVMHIST_CALLARGS(maphist, "(device=%#jx)", device,0,0,0);
KASSERT(size > 0);
cdev = cdevsw_lookup(device);
if (cdev == NULL) {
return NULL;
}
mapfn = cdev->d_mmap;
if (mapfn == NULL || mapfn == nommap) {
return NULL;
}
if ((cdev->d_flag & D_NEGOFFSAFE) == 0 && off != UVM_UNKNOWN_OFFSET) {
if (off < 0)
return NULL;
#if SIZE_MAX > UINT32_MAX
if (size > __type_max(voff_t))
return NULL;
#endif
if (off > __type_max(voff_t) - size)
return NULL;
}
do {
KASSERTMSG((off % PAGE_SIZE) == 0, "off=%jd", (intmax_t)off);
KASSERTMSG(size >= PAGE_SIZE, "size=%"PRIuVSIZE, size);
if (cdev_mmap(device, off, accessprot) == -1)
return NULL;
KASSERT(off <= __type_max(voff_t) - PAGE_SIZE ||
(cdev->d_flag & D_NEGOFFSAFE) != 0);
if (__predict_false(off > __type_max(voff_t) - PAGE_SIZE)) {
CTASSERT(MIN_PAGE_SIZE >= 2);
off -= __type_max(voff_t);
off += PAGE_SIZE - 2;
off -= __type_max(voff_t);
} else {
off += PAGE_SIZE;
}
size -= PAGE_SIZE;
} while (size != 0);
for (;;) {
mutex_enter(&udv_lock);
LIST_FOREACH(lcv, &udv_list, u_list) {
if (device == lcv->u_device)
break;
}
if (lcv) {
if (lcv->u_flags & UVM_DEVICE_HOLD) {
lcv->u_flags |= UVM_DEVICE_WANTED;
UVM_UNLOCK_AND_WAIT(lcv, &udv_lock, false,
"udv_attach",0);
continue;
}
lcv->u_flags |= UVM_DEVICE_HOLD;
mutex_exit(&udv_lock);
rw_enter(lcv->u_obj.vmobjlock, RW_WRITER);
lcv->u_obj.uo_refs++;
rw_exit(lcv->u_obj.vmobjlock);
mutex_enter(&udv_lock);
if (lcv->u_flags & UVM_DEVICE_WANTED)
wakeup(lcv);
lcv->u_flags &= ~(UVM_DEVICE_WANTED|UVM_DEVICE_HOLD);
mutex_exit(&udv_lock);
return &lcv->u_obj;
}
mutex_exit(&udv_lock);
udv = kmem_alloc(sizeof(*udv), KM_SLEEP);
uvm_obj_init(&udv->u_obj, &uvm_deviceops, true, 1);
mutex_enter(&udv_lock);
LIST_FOREACH(lcv, &udv_list, u_list) {
if (device == lcv->u_device)
break;
}
if (lcv) {
mutex_exit(&udv_lock);
uvm_obj_destroy(&udv->u_obj, true);
kmem_free(udv, sizeof(*udv));
continue;
}
udv->u_flags = 0;
udv->u_device = device;
LIST_INSERT_HEAD(&udv_list, udv, u_list);
mutex_exit(&udv_lock);
return &udv->u_obj;
}
}
static void
udv_reference(struct uvm_object *uobj)
{
UVMHIST_FUNC(__func__); UVMHIST_CALLED(maphist);
rw_enter(uobj->vmobjlock, RW_WRITER);
uobj->uo_refs++;
UVMHIST_LOG(maphist, "<- done (uobj=%#jx, ref = %jd)",
(uintptr_t)uobj, uobj->uo_refs,0,0);
rw_exit(uobj->vmobjlock);
}
static void
udv_detach(struct uvm_object *uobj)
{
struct uvm_device *udv = (struct uvm_device *)uobj;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(maphist);
again:
rw_enter(uobj->vmobjlock, RW_WRITER);
if (uobj->uo_refs > 1) {
uobj->uo_refs--;
rw_exit(uobj->vmobjlock);
UVMHIST_LOG(maphist," <- done, uobj=%#jx, ref=%jd",
(uintptr_t)uobj,uobj->uo_refs,0,0);
return;
}
mutex_enter(&udv_lock);
if (udv->u_flags & UVM_DEVICE_HOLD) {
udv->u_flags |= UVM_DEVICE_WANTED;
rw_exit(uobj->vmobjlock);
UVM_UNLOCK_AND_WAIT(udv, &udv_lock, false, "udv_detach",0);
goto again;
}
LIST_REMOVE(udv, u_list);
if (udv->u_flags & UVM_DEVICE_WANTED)
wakeup(udv);
mutex_exit(&udv_lock);
rw_exit(uobj->vmobjlock);
uvm_obj_destroy(uobj, true);
kmem_free(udv, sizeof(*udv));
UVMHIST_LOG(maphist," <- done, freed uobj=%#jx", (uintptr_t)uobj,
0, 0, 0);
}
static int
udv_fault(struct uvm_faultinfo *ufi, vaddr_t vaddr, struct vm_page **pps,
int npages, int centeridx, vm_prot_t access_type,
int flags)
{
struct vm_map_entry *entry = ufi->entry;
struct uvm_object *uobj = entry->object.uvm_obj;
struct uvm_device *udv = (struct uvm_device *)uobj;
vaddr_t curr_va;
off_t curr_offset;
paddr_t paddr, mdpgno;
u_int mmapflags;
int lcv, retval;
dev_t device;
vm_prot_t mapprot;
UVMHIST_FUNC(__func__); UVMHIST_CALLED(maphist);
UVMHIST_LOG(maphist," flags=%#jx", flags,0,0,0);
if (UVM_ET_ISCOPYONWRITE(entry)) {
UVMHIST_LOG(maphist, "<- failed -- COW entry (etype=%#jx)",
entry->etype, 0,0,0);
uvmfault_unlockall(ufi, ufi->entry->aref.ar_amap, uobj);
return EIO;
}
device = udv->u_device;
if (cdevsw_lookup(device) == NULL) {
uvmfault_unlockall(ufi, ufi->entry->aref.ar_amap, uobj);
return EIO;
}
curr_offset = entry->offset + (vaddr - entry->start);
curr_va = vaddr;
retval = 0;
for (lcv = 0 ; lcv < npages ; lcv++, curr_offset += PAGE_SIZE,
curr_va += PAGE_SIZE) {
if ((flags & PGO_ALLPAGES) == 0 && lcv != centeridx)
continue;
if (pps[lcv] == PGO_DONTCARE)
continue;
mdpgno = cdev_mmap(device, curr_offset, access_type);
if (mdpgno == -1) {
retval = EIO;
break;
}
paddr = pmap_phys_address(mdpgno);
mmapflags = pmap_mmap_flags(mdpgno);
mapprot = ufi->entry->protection;
UVMHIST_LOG(maphist,
" MAPPING: device: pm=%#jx, va=%#jx, pa=%#jx, at=%jd",
(uintptr_t)ufi->orig_map->pmap, curr_va, paddr, mapprot);
if (pmap_enter(ufi->orig_map->pmap, curr_va, paddr, mapprot,
PMAP_CANFAIL | mapprot | mmapflags) != 0) {
pmap_update(ufi->orig_map->pmap);
uvmfault_unlockall(ufi, ufi->entry->aref.ar_amap,
uobj);
return ENOMEM;
}
}
pmap_update(ufi->orig_map->pmap);
uvmfault_unlockall(ufi, ufi->entry->aref.ar_amap, uobj);
return retval;
}