#define pr_fmt(fmt) "[TTM] " fmt
#include <drm/ttm/ttm_module.h>
#include <drm/ttm/ttm_bo_driver.h>
#include <drm/ttm/ttm_placement.h>
#include <drm/drm_vma_manager.h>
#include <linux/mm.h>
#include <linux/pfn_t.h>
#include <linux/rbtree.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/mem_encrypt.h>
#include <sys/sysctl.h>
#include <vm/vm.h>
#include <vm/vm_page.h>
#include <vm/vm_page2.h>
#define TTM_BO_VM_NUM_PREFAULT 16
static int ttm_bo_vm_fault_idle(struct ttm_buffer_object *bo,
struct vm_fault *vmf)
{
int ret = 0;
if (likely(!bo->moving))
goto out_unlock;
if (dma_fence_is_signaled(bo->moving))
goto out_clear;
if (vmf->flags & FAULT_FLAG_ALLOW_RETRY) {
ret = VM_FAULT_RETRY;
if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT)
goto out_unlock;
ttm_bo_get(bo);
up_read(&vmf->vma->vm_mm->mmap_sem);
(void) dma_fence_wait(bo->moving, true);
ttm_bo_unreserve(bo);
ttm_bo_put(bo);
down_read(&vmf->vma->vm_mm->mmap_sem);
goto out_unlock;
}
ret = dma_fence_wait(bo->moving, true);
if (unlikely(ret != 0)) {
ret = (ret != -ERESTARTSYS) ? VM_FAULT_SIGBUS :
VM_FAULT_NOPAGE;
goto out_unlock;
}
out_clear:
dma_fence_put(bo->moving);
bo->moving = NULL;
out_unlock:
return ret;
}
static int drm_unstall = -1;
SYSCTL_INT(_debug, OID_AUTO, unstall, CTLFLAG_RW, &drm_unstall, 0, "");
static int ttm_bo_vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
panic("ttm_bo_vm_fault");
#if 0
struct ttm_buffer_object *bo = (struct ttm_buffer_object *)
vma->vm_private_data;
struct ttm_bo_device *bdev = bo->bdev;
unsigned long page_offset;
unsigned long page_last;
unsigned long pfn;
struct ttm_tt *ttm = NULL;
struct page *page;
int err;
int i;
vm_fault_t ret = VM_FAULT_NOPAGE;
unsigned long address = vmf->address;
struct ttm_mem_type_manager *man =
&bdev->man[bo->mem.mem_type];
struct vm_area_struct cvma;
err = ttm_bo_reserve(bo, true, true, NULL);
if (unlikely(err != 0)) {
if (err != -EBUSY)
return VM_FAULT_NOPAGE;
if (vmf->flags & FAULT_FLAG_ALLOW_RETRY) {
if (!(vmf->flags & FAULT_FLAG_RETRY_NOWAIT)) {
ttm_bo_get(bo);
up_read(&vmf->vma->vm_mm->mmap_sem);
(void) ttm_bo_wait_unreserved(bo);
ttm_bo_put(bo);
}
return VM_FAULT_RETRY;
}
return VM_FAULT_NOPAGE;
}
if (bo->ttm && (bo->ttm->page_flags & TTM_PAGE_FLAG_SG)) {
ret = VM_FAULT_SIGBUS;
goto out_unlock;
}
if (bdev->driver->fault_reserve_notify) {
err = bdev->driver->fault_reserve_notify(bo);
switch (err) {
case 0:
break;
case -EBUSY:
case -ERESTARTSYS:
ret = VM_FAULT_NOPAGE;
goto out_unlock;
default:
ret = VM_FAULT_SIGBUS;
goto out_unlock;
}
}
ret = ttm_bo_vm_fault_idle(bo, vmf);
if (unlikely(ret != 0)) {
if (ret == VM_FAULT_RETRY &&
!(vmf->flags & FAULT_FLAG_RETRY_NOWAIT)) {
return ret;
}
goto out_unlock;
}
err = ttm_mem_io_lock(man, true);
if (unlikely(err != 0)) {
ret = VM_FAULT_NOPAGE;
goto out_unlock;
}
err = ttm_mem_io_reserve_vm(bo);
if (unlikely(err != 0)) {
ret = VM_FAULT_SIGBUS;
goto out_io_unlock;
}
page_offset = ((address - vma->vm_start) >> PAGE_SHIFT) +
vma->vm_pgoff - drm_vma_node_start(&bo->vma_node);
page_last = vma_pages(vma) + vma->vm_pgoff -
drm_vma_node_start(&bo->vma_node);
if (unlikely(page_offset >= bo->num_pages)) {
ret = VM_FAULT_SIGBUS;
goto out_io_unlock;
}
cvma = *vma;
cvma.vm_page_prot = vm_get_page_prot(cvma.vm_flags);
if (bo->mem.bus.is_iomem) {
cvma.vm_page_prot = ttm_io_prot(bo->mem.placement,
cvma.vm_page_prot);
} else {
struct ttm_operation_ctx ctx = {
.interruptible = false,
.no_wait_gpu = false,
.flags = TTM_OPT_FLAG_FORCE_ALLOC
};
ttm = bo->ttm;
cvma.vm_page_prot = ttm_io_prot(bo->mem.placement,
cvma.vm_page_prot);
if (ttm_tt_populate(ttm, &ctx)) {
ret = VM_FAULT_OOM;
goto out_io_unlock;
}
}
for (i = 0; i < TTM_BO_VM_NUM_PREFAULT; ++i) {
if (bo->mem.bus.is_iomem) {
cvma.vm_page_prot = pgprot_decrypted(cvma.vm_page_prot);
pfn = ttm_bo_io_mem_pfn(bo, page_offset);
} else {
page = ttm->pages[page_offset];
if (unlikely(!page && i == 0)) {
ret = VM_FAULT_OOM;
goto out_io_unlock;
} else if (unlikely(!page)) {
break;
}
page->index = drm_vma_node_start(&bo->vma_node) +
page_offset;
pfn = page_to_pfn(page);
}
if (vma->vm_flags & VM_MIXEDMAP)
ret = vmf_insert_mixed(&cvma, address,
__pfn_to_pfn_t(pfn, PFN_DEV));
else
ret = vmf_insert_pfn(&cvma, address, pfn);
if (unlikely((ret == VM_FAULT_NOPAGE && i > 0)))
break;
else if (unlikely(ret & VM_FAULT_ERROR))
goto out_io_unlock;
address += PAGE_SIZE;
if (unlikely(++page_offset >= page_last))
break;
}
ret = VM_FAULT_NOPAGE;
out_io_unlock:
ttm_mem_io_unlock(man);
out_unlock:
ttm_bo_unreserve(bo);
return ret;
#endif
}
static void ttm_bo_vm_open(struct vm_area_struct *vma)
{
struct ttm_buffer_object *bo =
(struct ttm_buffer_object *)vma->vm_private_data;
#if 0
WARN_ON(bo->bdev->dev_mapping != vma->vm_file->f_mapping);
#endif
ttm_bo_get(bo);
}
static void ttm_bo_vm_close(struct vm_area_struct *vma)
{
struct ttm_buffer_object *bo = (struct ttm_buffer_object *)vma->vm_private_data;
ttm_bo_put(bo);
vma->vm_private_data = NULL;
}
static int ttm_bo_vm_access_kmap(struct ttm_buffer_object *bo,
unsigned long offset,
uint8_t *buf, int len, int write)
{
unsigned long page = offset >> PAGE_SHIFT;
unsigned long bytes_left = len;
int ret;
offset -= page << PAGE_SHIFT;
do {
unsigned long bytes = min(bytes_left, PAGE_SIZE - offset);
struct ttm_bo_kmap_obj map;
void *ptr;
bool is_iomem;
ret = ttm_bo_kmap(bo, page, 1, &map);
if (ret)
return ret;
ptr = (uint8_t *)ttm_kmap_obj_virtual(&map, &is_iomem) + offset;
WARN_ON_ONCE(is_iomem);
if (write)
memcpy(ptr, buf, bytes);
else
memcpy(buf, ptr, bytes);
ttm_bo_kunmap(&map);
page++;
buf += bytes;
bytes_left -= bytes;
offset = 0;
} while (bytes_left);
return len;
}
static int ttm_bo_vm_access(struct vm_area_struct *vma, unsigned long addr,
void *buf, int len, int write)
{
unsigned long offset = (addr) - vma->vm_start;
struct ttm_buffer_object *bo = vma->vm_private_data;
int ret;
if (len < 1 || (offset + len) >> PAGE_SHIFT > bo->num_pages)
return -EIO;
ret = ttm_bo_reserve(bo, true, false, NULL);
if (ret)
return ret;
switch (bo->mem.mem_type) {
case TTM_PL_SYSTEM:
if (unlikely(bo->ttm->page_flags & TTM_PAGE_FLAG_SWAPPED)) {
ret = ttm_tt_swapin(bo->ttm);
if (unlikely(ret != 0))
return ret;
}
case TTM_PL_TT:
ret = ttm_bo_vm_access_kmap(bo, offset, buf, len, write);
break;
default:
if (bo->bdev->driver->access_memory)
ret = bo->bdev->driver->access_memory(
bo, offset, buf, len, write);
else
ret = -EIO;
}
ttm_bo_unreserve(bo);
return ret;
}
static const struct vm_operations_struct ttm_bo_vm_ops = {
.fault = ttm_bo_vm_fault,
.open = ttm_bo_vm_open,
.close = ttm_bo_vm_close,
.access = ttm_bo_vm_access
};
static struct ttm_buffer_object *ttm_bo_vm_lookup(struct ttm_bo_device *bdev,
unsigned long offset,
unsigned long pages)
{
struct drm_vma_offset_node *node;
struct ttm_buffer_object *bo = NULL;
drm_vma_offset_lock_lookup(&bdev->vma_manager);
node = drm_vma_offset_lookup_locked(&bdev->vma_manager, offset, pages);
if (likely(node)) {
bo = container_of(node, struct ttm_buffer_object, vma_node);
bo = ttm_bo_get_unless_zero(bo);
}
drm_vma_offset_unlock_lookup(&bdev->vma_manager);
if (!bo)
pr_err("Could not find buffer object to map\n");
return bo;
}
int ttm_bo_mmap(struct file *filp, struct vm_area_struct *vma,
struct ttm_bo_device *bdev)
{
struct ttm_bo_driver *driver;
struct ttm_buffer_object *bo;
int ret;
bo = ttm_bo_vm_lookup(bdev, vma->vm_pgoff, vma_pages(vma));
if (unlikely(!bo))
return -EINVAL;
driver = bo->bdev->driver;
if (unlikely(!driver->verify_access)) {
ret = -EPERM;
goto out_unref;
}
ret = driver->verify_access(bo, filp);
if (unlikely(ret != 0))
goto out_unref;
vma->vm_ops = &ttm_bo_vm_ops;
vma->vm_private_data = bo;
vma->vm_flags |= VM_MIXEDMAP;
vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
return 0;
out_unref:
ttm_bo_put(bo);
return ret;
}
EXPORT_SYMBOL(ttm_bo_mmap);
int ttm_fbdev_mmap(struct vm_area_struct *vma, struct ttm_buffer_object *bo)
{
if (vma->vm_pgoff != 0)
return -EACCES;
ttm_bo_get(bo);
vma->vm_ops = &ttm_bo_vm_ops;
vma->vm_private_data = bo;
vma->vm_flags |= VM_MIXEDMAP;
vma->vm_flags |= VM_IO | VM_DONTEXPAND;
return 0;
}
EXPORT_SYMBOL(ttm_fbdev_mmap);
static int
ttm_bo_vm_fault_dfly(vm_object_t vm_obj, vm_ooffset_t offset,
int prot, vm_page_t *mres)
{
struct ttm_buffer_object *bo = vm_obj->handle;
struct ttm_bo_device *bdev = bo->bdev;
struct ttm_tt *ttm = NULL;
vm_page_t m;
int ret;
int retval = VM_PAGER_OK;
struct ttm_mem_type_manager *man =
&bdev->man[bo->mem.mem_type];
struct vm_area_struct cvma;
#ifdef __DragonFly__
struct vm_area_struct vmas;
struct vm_area_struct *vma = &vmas;
struct vm_fault vmfs;
struct vm_fault *vmf = &vmfs;
memset(vma, 0, sizeof(*vma));
memset(vmf, 0, sizeof(*vmf));
vma->vm_mm = current->mm;
vmf->vma = vma;
int retry_count = 0;
#endif
vm_object_pip_add(vm_obj, 1);
KKASSERT(*mres == NULL);
retry:
down_read(&vma->vm_mm->mmap_sem);
m = NULL;
ret = ttm_bo_reserve(bo, true, true, NULL);
if (unlikely(ret != 0)) {
if (ret != -EBUSY) {
retval = VM_PAGER_ERROR;
goto out_unlock2;
}
if (vmf->flags & FAULT_FLAG_ALLOW_RETRY || 1) {
if (!(vmf->flags & FAULT_FLAG_RETRY_NOWAIT)) {
up_read(&vma->vm_mm->mmap_sem);
(void) ttm_bo_wait_unreserved(bo);
down_read(&vma->vm_mm->mmap_sem);
}
#ifndef __DragonFly__
return VM_FAULT_RETRY;
#else
up_read(&vma->vm_mm->mmap_sem);
lwkt_yield();
goto retry;
#endif
}
retval = VM_PAGER_ERROR;
goto out_unlock2;
}
if (bdev->driver->fault_reserve_notify) {
ret = bdev->driver->fault_reserve_notify(bo);
switch (ret) {
case 0:
break;
case -EBUSY:
lwkt_yield();
case -ERESTARTSYS:
case -EINTR:
retval = VM_PAGER_ERROR;
goto out_unlock1;
default:
retval = VM_PAGER_ERROR;
goto out_unlock1;
}
}
ret = ttm_bo_vm_fault_idle(bo, vmf);
if (unlikely(ret != 0)) {
if (retry_count >= 100) {
retval = VM_PAGER_ERROR;
goto out_unlock1;
} else {
retry_count++;
ttm_bo_unreserve(bo);
up_read(&vma->vm_mm->mmap_sem);
int dummy;
tsleep(&dummy, 0, "blah", 1);
goto retry;
}
}
ret = ttm_mem_io_lock(man, true);
if (unlikely(ret != 0)) {
retval = VM_PAGER_ERROR;
goto out_unlock1;
}
ret = ttm_mem_io_reserve_vm(bo);
if (unlikely(ret != 0)) {
retval = VM_PAGER_ERROR;
goto out_io_unlock1;
}
if (unlikely(OFF_TO_IDX(offset) >= bo->num_pages)) {
retval = VM_PAGER_ERROR;
goto out_io_unlock1;
}
cvma = *vma;
#if 0
cvma.vm_page_prot = vm_get_page_prot(cvma.vm_flags);
#else
cvma.vm_page_prot = 0;
#endif
if (bo->mem.bus.is_iomem) {
#ifdef __DragonFly__
m = vm_phys_fictitious_to_vm_page(bo->mem.bus.base +
bo->mem.bus.offset + offset);
pmap_page_set_memattr(m, ttm_io_prot(bo->mem.placement, 0));
#endif
cvma.vm_page_prot = ttm_io_prot(bo->mem.placement,
cvma.vm_page_prot);
} else {
ttm = bo->ttm;
cvma.vm_page_prot = ttm_io_prot(bo->mem.placement,
cvma.vm_page_prot);
struct ttm_operation_ctx ctx = {
.interruptible = false,
.no_wait_gpu = false,
.flags = TTM_OPT_FLAG_FORCE_ALLOC
};
if (ttm_tt_populate(ttm, &ctx)) {
retval = VM_PAGER_ERROR;
goto out_io_unlock1;
}
m = (struct vm_page *)ttm->pages[OFF_TO_IDX(offset)];
if (unlikely(!m)) {
retval = VM_PAGER_ERROR;
goto out_io_unlock1;
}
pmap_page_set_memattr(m,
(bo->mem.placement & TTM_PL_FLAG_CACHED) ?
VM_MEMATTR_WRITE_BACK : ttm_io_prot(bo->mem.placement, 0));
}
if (vm_page_busy_try(m, FALSE)) {
kprintf("r");
vm_page_sleep_busy(m, FALSE, "ttmvmf");
ttm_mem_io_unlock(man);
ttm_bo_unreserve(bo);
up_read(&vma->vm_mm->mmap_sem);
goto retry;
}
m->valid = VM_PAGE_BITS_ALL;
*mres = m;
out_io_unlock1:
ttm_mem_io_unlock(man);
out_unlock1:
ttm_bo_unreserve(bo);
out_unlock2:
vm_object_pip_wakeup(vm_obj);
up_read(&vma->vm_mm->mmap_sem);
return (retval);
}
static int
ttm_bo_vm_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot,
vm_ooffset_t foff, struct ucred *cred, u_short *color)
{
*color = 0;
return (0);
}
static void
ttm_bo_vm_dtor(void *handle)
{
struct ttm_buffer_object *bo = handle;
ttm_bo_unref(&bo);
}
static struct cdev_pager_ops ttm_pager_ops = {
.cdev_pg_fault = ttm_bo_vm_fault_dfly,
.cdev_pg_ctor = ttm_bo_vm_ctor,
.cdev_pg_dtor = ttm_bo_vm_dtor
};
int
ttm_bo_mmap_single(struct file *fp, struct drm_device *dev,
vm_ooffset_t *offset, vm_size_t size,
struct vm_object **obj_res, int nprot)
{
struct ttm_bo_device *bdev = dev->drm_ttm_bdev;
struct ttm_buffer_object *bo;
struct vm_object *vm_obj;
struct vm_area_struct vma;
int ret;
*obj_res = NULL;
bzero(&vma, sizeof(vma));
vma.vm_start = *offset;
vma.vm_end = vma.vm_start + size;
vma.vm_pgoff = vma.vm_start >> PAGE_SHIFT;
ret = ttm_bo_mmap(fp, &vma, bdev);
if (ret == 0) {
bo = vma.vm_private_data;
vm_obj = cdev_pager_allocate(bo, OBJT_MGTDEVICE,
&ttm_pager_ops,
size, nprot, 0,
curthread->td_ucred);
if (vm_obj) {
*obj_res = vm_obj;
*offset = 0;
} else {
ttm_bo_unref(&bo);
ret = EINVAL;
}
}
return ret;
}
EXPORT_SYMBOL(ttm_bo_mmap_single);
#ifdef __DragonFly__
void ttm_bo_release_mmap(struct ttm_buffer_object *bo);
void
ttm_bo_release_mmap(struct ttm_buffer_object *bo)
{
vm_object_t vm_obj;
vm_obj = cdev_pager_lookup(bo);
if (vm_obj == NULL)
return;
VM_OBJECT_LOCK(vm_obj);
#if 1
vm_object_page_remove(vm_obj, 0, 0, false);
#else
vm_page_t m;
int i;
for (i = 0; i < bo->num_pages; i++) {
m = vm_page_lookup_busy_wait(vm_obj, i, TRUE, "ttm_unm");
if (m == NULL)
continue;
cdev_pager_free_page(vm_obj, m);
}
#endif
VM_OBJECT_UNLOCK(vm_obj);
vm_object_deallocate(vm_obj);
}
#endif
#if 0
int ttm_fbdev_mmap(struct vm_area_struct *vma, struct ttm_buffer_object *bo)
{
if (vma->vm_pgoff != 0)
return -EACCES;
vma->vm_ops = &ttm_bo_vm_ops;
vma->vm_private_data = ttm_bo_reference(bo);
vma->vm_flags |= VM_IO | VM_MIXEDMAP | VM_DONTEXPAND;
return 0;
}
EXPORT_SYMBOL(ttm_fbdev_mmap);
#endif