#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/mman.h>
#include <sys/device.h>
#include <sys/queue.h>
#include <sys/malloc.h>
#include <sys/mutex2.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>
#include <vm/vm_zone.h>
#include <vm/vm_page2.h>
static pgo_dealloc_t dev_pager_dealloc;
static pgo_getpage_t dev_pager_getpage;
static pgo_putpages_t dev_pager_putpages;
static pgo_haspage_t dev_pager_haspage;
struct pagerops devicepagerops = {
.pgo_dealloc = dev_pager_dealloc,
.pgo_getpage = dev_pager_getpage,
.pgo_putpages = dev_pager_putpages,
.pgo_haspage = dev_pager_haspage
};
static TAILQ_HEAD(, vm_page) dev_freepages_list =
TAILQ_HEAD_INITIALIZER(dev_freepages_list);
static MALLOC_DEFINE(M_FICTITIOUS_PAGES, "device-mapped pages",
"Device mapped pages");
static vm_page_t dev_pager_getfake (vm_paddr_t, int);
static void dev_pager_putfake (vm_page_t);
static struct pagerlst dev_pager_object_list =
TAILQ_HEAD_INITIALIZER(dev_pager_object_list);
static struct mtx dev_pager_mtx = MTX_INITIALIZER("devpgr");
static int old_dev_pager_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot,
vm_ooffset_t foff, struct ucred *cred, u_short *pg_color);
static void old_dev_pager_dtor(void *handle);
static int old_dev_pager_fault(vm_object_t object, vm_ooffset_t offset,
int prot, vm_page_t *mres);
static struct cdev_pager_ops old_dev_pager_ops = {
.cdev_pg_ctor = old_dev_pager_ctor,
.cdev_pg_dtor = old_dev_pager_dtor,
.cdev_pg_fault = old_dev_pager_fault
};
vm_object_t
cdev_pager_lookup(void *handle)
{
vm_object_t object;
mtx_lock(&dev_pager_mtx);
object = vm_pager_object_lookup(&dev_pager_object_list, handle);
mtx_unlock(&dev_pager_mtx);
return (object);
}
vm_object_t
cdev_pager_allocate(void *handle, enum obj_type tp, struct cdev_pager_ops *ops,
vm_ooffset_t size, vm_prot_t prot, vm_ooffset_t foff, struct ucred *cred)
{
cdev_t dev;
vm_object_t object;
vm_pindex_t pindex;
u_short color;
if (foff & PAGE_MASK)
return (NULL);
size = round_page64(size);
pindex = OFF_TO_IDX(foff + size);
if (ops->cdev_pg_ctor(handle, size, prot, foff, cred, &color) != 0)
return (NULL);
mtx_lock(&dev_pager_mtx);
object = vm_pager_object_lookup(&dev_pager_object_list, handle);
if (object == NULL) {
object = vm_object_allocate_hold(tp, pindex);
object->handle = handle;
object->un_pager.devp.ops = ops;
object->un_pager.devp.dev = handle;
TAILQ_INIT(&object->un_pager.devp.devp_pglist);
if (ops->cdev_pg_ctor == old_dev_pager_ctor) {
dev = handle;
dev->si_object = object;
}
TAILQ_INSERT_TAIL(&dev_pager_object_list, object,
pager_object_entry);
vm_object_drop(object);
} else {
vm_object_hold(object);
if (pindex > object->size)
object->size = pindex;
KASSERT(object->type == tp,
("Inconsistent device pager type %p %d", object, tp));
vm_object_drop(object);
}
mtx_unlock(&dev_pager_mtx);
return (object);
}
vm_object_t
dev_pager_alloc(void *handle, off_t size, vm_prot_t prot, off_t foff)
{
return (cdev_pager_allocate(handle, OBJT_DEVICE, &old_dev_pager_ops,
size, prot, foff, NULL));
}
void
cdev_pager_free_page(vm_object_t object, vm_page_t m)
{
if (object->type == OBJT_MGTDEVICE) {
KKASSERT((m->flags & PG_FICTITIOUS) != 0);
pmap_page_protect(m, VM_PROT_NONE);
vm_page_remove(m);
vm_page_wakeup(m);
} else if (object->type == OBJT_DEVICE) {
TAILQ_REMOVE(&object->un_pager.devp.devp_pglist, m, pageq);
dev_pager_putfake(m);
}
}
static void
dev_pager_dealloc(vm_object_t object)
{
vm_page_t m;
object->un_pager.devp.ops->cdev_pg_dtor(object->un_pager.devp.dev);
mtx_lock(&dev_pager_mtx);
TAILQ_REMOVE(&dev_pager_object_list, object, pager_object_entry);
mtx_unlock(&dev_pager_mtx);
if (object->type == OBJT_DEVICE) {
while ((m = TAILQ_FIRST(&object->un_pager.devp.devp_pglist)) !=
NULL) {
TAILQ_REMOVE(&object->un_pager.devp.devp_pglist,
m, pageq);
dev_pager_putfake(m);
}
}
}
static int
dev_pager_getpage(vm_object_t object, vm_pindex_t pindex,
vm_page_t *mpp, int seqaccess)
{
int error;
error = object->un_pager.devp.ops->cdev_pg_fault(
object, IDX_TO_OFF(pindex),
PROT_READ, mpp);
return (error);
}
static void
dev_pager_putpages(vm_object_t object, vm_page_t *m,
int count, int flags, int *rtvals)
{
panic("dev_pager_putpage called");
}
static boolean_t
dev_pager_haspage(vm_object_t object, vm_pindex_t pindex)
{
return (TRUE);
}
static vm_page_t
dev_pager_getfake(vm_paddr_t paddr, int pat_mode)
{
vm_page_t m;
m = kmalloc(sizeof(*m), M_FICTITIOUS_PAGES, M_WAITOK|M_ZERO);
pmap_page_init(m);
m->flags = PG_FICTITIOUS | PG_UNQUEUED;
m->valid = VM_PAGE_BITS_ALL;
m->dirty = 0;
m->queue = PQ_NONE;
m->object = NULL;
m->busy_count = PBUSY_LOCKED;
m->wire_count = 1;
m->hold_count = 0;
m->phys_addr = paddr;
m->pat_mode = pat_mode;
spin_init(&m->spin, "dev_page");
return (m);
}
static void
dev_pager_putfake(vm_page_t m)
{
if (!(m->flags & PG_FICTITIOUS))
panic("dev_pager_putfake: bad page");
KKASSERT(m->object == NULL);
KKASSERT(m->hold_count == 0);
kfree(m, M_FICTITIOUS_PAGES);
}
static int
old_dev_pager_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot,
vm_ooffset_t foff, struct ucred *cred, u_short *color)
{
unsigned int npages;
vm_offset_t off;
cdev_t dev;
dev = handle;
npages = OFF_TO_IDX(size);
for (off = foff; npages--; off += PAGE_SIZE) {
if (dev_dmmap(dev, off, (int)prot, NULL) == -1)
return (EINVAL);
}
return (0);
}
static void old_dev_pager_dtor(void *handle)
{
cdev_t dev;
dev = handle;
if (dev != NULL) {
KKASSERT(dev->si_object);
dev->si_object = NULL;
}
}
static int
old_dev_pager_fault(vm_object_t object, vm_ooffset_t offset,
int prot, vm_page_t *mres)
{
vm_paddr_t paddr;
vm_page_t page;
vm_offset_t pidx = OFF_TO_IDX(offset);
cdev_t dev;
page = *mres;
dev = object->handle;
paddr = pmap_phys_address(dev_dmmap(dev, offset, prot, NULL));
KASSERT(paddr != -1,("dev_pager_getpage: map function returns error"));
KKASSERT(object->type == OBJT_DEVICE);
if (page->flags & PG_FICTITIOUS) {
page->phys_addr = paddr;
page->valid = VM_PAGE_BITS_ALL;
} else {
page = dev_pager_getfake(paddr, object->memattr);
vm_object_hold(object);
TAILQ_INSERT_TAIL(&object->un_pager.devp.devp_pglist,
page, pageq);
vm_page_free(*mres);
if (vm_page_insert(page, object, pidx) == FALSE) {
panic("dev_pager_getpage: page (%p,%016jx) exists",
object, (uintmax_t)pidx);
}
vm_object_drop(object);
}
return (VM_PAGER_OK);
}