#include <linux/dma-mapping.h>
#include <linux/export.h>
#include <linux/hmm.h>
#include <linux/hugetlb_inline.h>
#include <linux/memremap.h>
#include <linux/mm_types.h>
#include <linux/slab.h>
#include <drm/drm_device.h>
#include <drm/drm_gpusvm.h>
#include <drm/drm_pagemap.h>
#include <drm/drm_print.h>
static unsigned long
npages_in_range(unsigned long start, unsigned long end)
{
return (end - start) >> PAGE_SHIFT;
}
struct drm_gpusvm_notifier *
drm_gpusvm_notifier_find(struct drm_gpusvm *gpusvm, unsigned long start,
unsigned long end)
{
struct interval_tree_node *itree;
itree = interval_tree_iter_first(&gpusvm->root, start, end - 1);
if (itree)
return container_of(itree, struct drm_gpusvm_notifier, itree);
else
return NULL;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_notifier_find);
struct drm_gpusvm_range *
drm_gpusvm_range_find(struct drm_gpusvm_notifier *notifier, unsigned long start,
unsigned long end)
{
struct interval_tree_node *itree;
itree = interval_tree_iter_first(¬ifier->root, start, end - 1);
if (itree)
return container_of(itree, struct drm_gpusvm_range, itree);
else
return NULL;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_find);
static bool
drm_gpusvm_notifier_invalidate(struct mmu_interval_notifier *mni,
const struct mmu_notifier_range *mmu_range,
unsigned long cur_seq)
{
struct drm_gpusvm_notifier *notifier =
container_of(mni, typeof(*notifier), notifier);
struct drm_gpusvm *gpusvm = notifier->gpusvm;
if (!mmu_notifier_range_blockable(mmu_range))
return false;
down_write(&gpusvm->notifier_lock);
mmu_interval_set_seq(mni, cur_seq);
gpusvm->ops->invalidate(gpusvm, notifier, mmu_range);
up_write(&gpusvm->notifier_lock);
return true;
}
static const struct mmu_interval_notifier_ops drm_gpusvm_notifier_ops = {
.invalidate = drm_gpusvm_notifier_invalidate,
};
int drm_gpusvm_init(struct drm_gpusvm *gpusvm,
const char *name, struct drm_device *drm,
struct mm_struct *mm,
unsigned long mm_start, unsigned long mm_range,
unsigned long notifier_size,
const struct drm_gpusvm_ops *ops,
const unsigned long *chunk_sizes, int num_chunks)
{
if (mm) {
if (!ops->invalidate || !num_chunks)
return -EINVAL;
mmgrab(mm);
} else {
if (ops || num_chunks || mm_range || notifier_size)
return -EINVAL;
}
gpusvm->name = name;
gpusvm->drm = drm;
gpusvm->mm = mm;
gpusvm->mm_start = mm_start;
gpusvm->mm_range = mm_range;
gpusvm->notifier_size = notifier_size;
gpusvm->ops = ops;
gpusvm->chunk_sizes = chunk_sizes;
gpusvm->num_chunks = num_chunks;
gpusvm->root = RB_ROOT_CACHED;
INIT_LIST_HEAD(&gpusvm->notifier_list);
init_rwsem(&gpusvm->notifier_lock);
fs_reclaim_acquire(GFP_KERNEL);
might_lock(&gpusvm->notifier_lock);
fs_reclaim_release(GFP_KERNEL);
#ifdef CONFIG_LOCKDEP
gpusvm->lock_dep_map = NULL;
#endif
return 0;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_init);
static struct drm_gpusvm_notifier *to_drm_gpusvm_notifier(struct rb_node *node)
{
return container_of(node, struct drm_gpusvm_notifier, itree.rb);
}
static void drm_gpusvm_notifier_insert(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_notifier *notifier)
{
struct rb_node *node;
struct list_head *head;
interval_tree_insert(¬ifier->itree, &gpusvm->root);
node = rb_prev(¬ifier->itree.rb);
if (node)
head = &(to_drm_gpusvm_notifier(node))->entry;
else
head = &gpusvm->notifier_list;
list_add(¬ifier->entry, head);
}
static void drm_gpusvm_notifier_remove(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_notifier *notifier)
{
interval_tree_remove(¬ifier->itree, &gpusvm->root);
list_del(¬ifier->entry);
}
void drm_gpusvm_fini(struct drm_gpusvm *gpusvm)
{
struct drm_gpusvm_notifier *notifier, *next;
drm_gpusvm_for_each_notifier_safe(notifier, next, gpusvm, 0, LONG_MAX) {
struct drm_gpusvm_range *range, *__next;
mmu_interval_notifier_remove(¬ifier->notifier);
notifier->flags.removed = true;
drm_gpusvm_for_each_range_safe(range, __next, notifier, 0,
LONG_MAX)
drm_gpusvm_range_remove(gpusvm, range);
}
if (gpusvm->mm)
mmdrop(gpusvm->mm);
WARN_ON(!RB_EMPTY_ROOT(&gpusvm->root.rb_root));
}
EXPORT_SYMBOL_GPL(drm_gpusvm_fini);
static struct drm_gpusvm_notifier *
drm_gpusvm_notifier_alloc(struct drm_gpusvm *gpusvm, unsigned long fault_addr)
{
struct drm_gpusvm_notifier *notifier;
if (gpusvm->ops->notifier_alloc)
notifier = gpusvm->ops->notifier_alloc();
else
notifier = kzalloc_obj(*notifier);
if (!notifier)
return ERR_PTR(-ENOMEM);
notifier->gpusvm = gpusvm;
notifier->itree.start = ALIGN_DOWN(fault_addr, gpusvm->notifier_size);
notifier->itree.last = ALIGN(fault_addr + 1, gpusvm->notifier_size) - 1;
INIT_LIST_HEAD(¬ifier->entry);
notifier->root = RB_ROOT_CACHED;
INIT_LIST_HEAD(¬ifier->range_list);
return notifier;
}
static void drm_gpusvm_notifier_free(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_notifier *notifier)
{
WARN_ON(!RB_EMPTY_ROOT(¬ifier->root.rb_root));
if (gpusvm->ops->notifier_free)
gpusvm->ops->notifier_free(notifier);
else
kfree(notifier);
}
static struct drm_gpusvm_range *to_drm_gpusvm_range(struct rb_node *node)
{
return container_of(node, struct drm_gpusvm_range, itree.rb);
}
static void drm_gpusvm_range_insert(struct drm_gpusvm_notifier *notifier,
struct drm_gpusvm_range *range)
{
struct rb_node *node;
struct list_head *head;
drm_gpusvm_notifier_lock(notifier->gpusvm);
interval_tree_insert(&range->itree, ¬ifier->root);
node = rb_prev(&range->itree.rb);
if (node)
head = &(to_drm_gpusvm_range(node))->entry;
else
head = ¬ifier->range_list;
list_add(&range->entry, head);
drm_gpusvm_notifier_unlock(notifier->gpusvm);
}
static void __drm_gpusvm_range_remove(struct drm_gpusvm_notifier *notifier,
struct drm_gpusvm_range *range)
{
interval_tree_remove(&range->itree, ¬ifier->root);
list_del(&range->entry);
}
static struct drm_gpusvm_range *
drm_gpusvm_range_alloc(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_notifier *notifier,
unsigned long fault_addr, unsigned long chunk_size,
bool migrate_devmem)
{
struct drm_gpusvm_range *range;
if (gpusvm->ops->range_alloc)
range = gpusvm->ops->range_alloc(gpusvm);
else
range = kzalloc_obj(*range);
if (!range)
return ERR_PTR(-ENOMEM);
kref_init(&range->refcount);
range->gpusvm = gpusvm;
range->notifier = notifier;
range->itree.start = ALIGN_DOWN(fault_addr, chunk_size);
range->itree.last = ALIGN(fault_addr + 1, chunk_size) - 1;
INIT_LIST_HEAD(&range->entry);
range->pages.notifier_seq = LONG_MAX;
range->pages.flags.migrate_devmem = migrate_devmem ? 1 : 0;
return range;
}
static unsigned int drm_gpusvm_hmm_pfn_to_order(unsigned long hmm_pfn,
unsigned long hmm_pfn_index,
unsigned long npages)
{
unsigned long size;
size = 1UL << hmm_pfn_to_map_order(hmm_pfn);
size -= (hmm_pfn & ~HMM_PFN_FLAGS) & (size - 1);
hmm_pfn_index += size;
if (hmm_pfn_index > npages)
size -= (hmm_pfn_index - npages);
return ilog2(size);
}
static bool drm_gpusvm_check_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_notifier *notifier,
unsigned long start, unsigned long end,
void *dev_private_owner)
{
struct hmm_range hmm_range = {
.default_flags = 0,
.notifier = ¬ifier->notifier,
.start = start,
.end = end,
.dev_private_owner = dev_private_owner,
};
unsigned long timeout =
jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
unsigned long *pfns;
unsigned long npages = npages_in_range(start, end);
int err, i;
mmap_assert_locked(gpusvm->mm);
pfns = kvmalloc_array(npages, sizeof(*pfns), GFP_KERNEL);
if (!pfns)
return false;
hmm_range.notifier_seq = mmu_interval_read_begin(¬ifier->notifier);
hmm_range.hmm_pfns = pfns;
while (true) {
err = hmm_range_fault(&hmm_range);
if (err == -EBUSY) {
if (time_after(jiffies, timeout))
break;
hmm_range.notifier_seq =
mmu_interval_read_begin(¬ifier->notifier);
continue;
}
break;
}
if (err)
goto err_free;
for (i = 0; i < npages;) {
if (!(pfns[i] & HMM_PFN_VALID)) {
err = -EFAULT;
goto err_free;
}
i += 0x1 << drm_gpusvm_hmm_pfn_to_order(pfns[i], i, npages);
}
err_free:
kvfree(pfns);
return err ? false : true;
}
enum drm_gpusvm_scan_result drm_gpusvm_scan_mm(struct drm_gpusvm_range *range,
void *dev_private_owner,
const struct dev_pagemap *pagemap)
{
struct mmu_interval_notifier *notifier = &range->notifier->notifier;
unsigned long start = drm_gpusvm_range_start(range);
unsigned long end = drm_gpusvm_range_end(range);
struct hmm_range hmm_range = {
.default_flags = 0,
.notifier = notifier,
.start = start,
.end = end,
.dev_private_owner = dev_private_owner,
};
unsigned long timeout =
jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
enum drm_gpusvm_scan_result state = DRM_GPUSVM_SCAN_UNPOPULATED, new_state;
unsigned long *pfns;
unsigned long npages = npages_in_range(start, end);
const struct dev_pagemap *other = NULL;
int err, i;
pfns = kvcalloc(npages, sizeof(*pfns), GFP_KERNEL);
if (!pfns)
return DRM_GPUSVM_SCAN_UNPOPULATED;
hmm_range.hmm_pfns = pfns;
retry:
hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
mmap_read_lock(range->gpusvm->mm);
while (true) {
err = hmm_range_fault(&hmm_range);
if (err == -EBUSY) {
if (time_after(jiffies, timeout))
break;
hmm_range.notifier_seq =
mmu_interval_read_begin(notifier);
continue;
}
break;
}
mmap_read_unlock(range->gpusvm->mm);
if (err)
goto err_free;
drm_gpusvm_notifier_lock(range->gpusvm);
if (mmu_interval_read_retry(notifier, hmm_range.notifier_seq)) {
drm_gpusvm_notifier_unlock(range->gpusvm);
goto retry;
}
for (i = 0; i < npages;) {
struct page *page;
const struct dev_pagemap *cur = NULL;
if (!(pfns[i] & HMM_PFN_VALID)) {
state = DRM_GPUSVM_SCAN_UNPOPULATED;
break;
}
page = hmm_pfn_to_page(pfns[i]);
if (is_device_private_page(page) ||
is_device_coherent_page(page))
cur = page_pgmap(page);
if (cur == pagemap) {
new_state = DRM_GPUSVM_SCAN_EQUAL;
} else if (cur && (cur == other || !other)) {
new_state = DRM_GPUSVM_SCAN_OTHER;
other = cur;
} else if (cur) {
new_state = DRM_GPUSVM_SCAN_MIXED_DEVICE;
} else {
new_state = DRM_GPUSVM_SCAN_SYSTEM;
}
if (state == DRM_GPUSVM_SCAN_UNPOPULATED) {
state = new_state;
} else if (state != new_state) {
if (new_state == DRM_GPUSVM_SCAN_SYSTEM ||
state == DRM_GPUSVM_SCAN_SYSTEM)
state = DRM_GPUSVM_SCAN_MIXED;
else if (state != DRM_GPUSVM_SCAN_MIXED)
state = DRM_GPUSVM_SCAN_MIXED_DEVICE;
}
i += 1ul << drm_gpusvm_hmm_pfn_to_order(pfns[i], i, npages);
}
drm_gpusvm_notifier_unlock(range->gpusvm);
err_free:
kvfree(pfns);
return state;
}
EXPORT_SYMBOL(drm_gpusvm_scan_mm);
static unsigned long
drm_gpusvm_range_chunk_size(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_notifier *notifier,
struct vm_area_struct *vas,
unsigned long fault_addr,
unsigned long gpuva_start,
unsigned long gpuva_end,
unsigned long check_pages_threshold,
void *dev_private_owner)
{
unsigned long start, end;
int i = 0;
retry:
for (; i < gpusvm->num_chunks; ++i) {
start = ALIGN_DOWN(fault_addr, gpusvm->chunk_sizes[i]);
end = ALIGN(fault_addr + 1, gpusvm->chunk_sizes[i]);
if (start >= vas->vm_start && end <= vas->vm_end &&
start >= drm_gpusvm_notifier_start(notifier) &&
end <= drm_gpusvm_notifier_end(notifier) &&
start >= gpuva_start && end <= gpuva_end)
break;
}
if (i == gpusvm->num_chunks)
return LONG_MAX;
if (end - start != SZ_4K) {
struct drm_gpusvm_range *range;
range = drm_gpusvm_range_find(notifier, start, end);
if (range) {
++i;
goto retry;
}
if (end - start <= check_pages_threshold &&
!drm_gpusvm_check_pages(gpusvm, notifier, start, end, dev_private_owner)) {
++i;
goto retry;
}
}
return end - start;
}
#ifdef CONFIG_LOCKDEP
static void drm_gpusvm_driver_lock_held(struct drm_gpusvm *gpusvm)
{
if ((gpusvm)->lock_dep_map)
lockdep_assert(lock_is_held_type((gpusvm)->lock_dep_map, 0));
}
#else
static void drm_gpusvm_driver_lock_held(struct drm_gpusvm *gpusvm)
{
}
#endif
unsigned long
drm_gpusvm_find_vma_start(struct drm_gpusvm *gpusvm,
unsigned long start,
unsigned long end)
{
struct mm_struct *mm = gpusvm->mm;
struct vm_area_struct *vma;
unsigned long addr = ULONG_MAX;
if (!mmget_not_zero(mm))
return addr;
mmap_read_lock(mm);
vma = find_vma_intersection(mm, start, end);
if (vma)
addr = vma->vm_start;
mmap_read_unlock(mm);
mmput(mm);
return addr;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_find_vma_start);
struct drm_gpusvm_range *
drm_gpusvm_range_find_or_insert(struct drm_gpusvm *gpusvm,
unsigned long fault_addr,
unsigned long gpuva_start,
unsigned long gpuva_end,
const struct drm_gpusvm_ctx *ctx)
{
struct drm_gpusvm_notifier *notifier;
struct drm_gpusvm_range *range;
struct mm_struct *mm = gpusvm->mm;
struct vm_area_struct *vas;
bool notifier_alloc = false;
unsigned long chunk_size;
int err;
bool migrate_devmem;
drm_gpusvm_driver_lock_held(gpusvm);
if (fault_addr < gpusvm->mm_start ||
fault_addr > gpusvm->mm_start + gpusvm->mm_range)
return ERR_PTR(-EINVAL);
if (!mmget_not_zero(mm))
return ERR_PTR(-EFAULT);
notifier = drm_gpusvm_notifier_find(gpusvm, fault_addr, fault_addr + 1);
if (!notifier) {
notifier = drm_gpusvm_notifier_alloc(gpusvm, fault_addr);
if (IS_ERR(notifier)) {
err = PTR_ERR(notifier);
goto err_mmunlock;
}
notifier_alloc = true;
err = mmu_interval_notifier_insert(¬ifier->notifier,
mm,
drm_gpusvm_notifier_start(notifier),
drm_gpusvm_notifier_size(notifier),
&drm_gpusvm_notifier_ops);
if (err)
goto err_notifier;
}
mmap_read_lock(mm);
vas = vma_lookup(mm, fault_addr);
if (!vas) {
err = -ENOENT;
goto err_notifier_remove;
}
if (!ctx->read_only && !(vas->vm_flags & VM_WRITE)) {
err = -EPERM;
goto err_notifier_remove;
}
if (vas->vm_flags & (VM_IO | VM_PFNMAP)) {
err = -EIO;
goto err_notifier_remove;
}
range = drm_gpusvm_range_find(notifier, fault_addr, fault_addr + 1);
if (range)
goto out_mmunlock;
migrate_devmem = ctx->devmem_possible &&
vma_is_anonymous(vas) && !is_vm_hugetlb_page(vas);
chunk_size = drm_gpusvm_range_chunk_size(gpusvm, notifier, vas,
fault_addr, gpuva_start,
gpuva_end,
ctx->check_pages_threshold,
ctx->device_private_page_owner);
if (chunk_size == LONG_MAX) {
err = -EINVAL;
goto err_notifier_remove;
}
range = drm_gpusvm_range_alloc(gpusvm, notifier, fault_addr, chunk_size,
migrate_devmem);
if (IS_ERR(range)) {
err = PTR_ERR(range);
goto err_notifier_remove;
}
drm_gpusvm_range_insert(notifier, range);
if (notifier_alloc)
drm_gpusvm_notifier_insert(gpusvm, notifier);
out_mmunlock:
mmap_read_unlock(mm);
mmput(mm);
return range;
err_notifier_remove:
mmap_read_unlock(mm);
if (notifier_alloc)
mmu_interval_notifier_remove(¬ifier->notifier);
err_notifier:
if (notifier_alloc)
drm_gpusvm_notifier_free(gpusvm, notifier);
err_mmunlock:
mmput(mm);
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_find_or_insert);
static void __drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages,
unsigned long npages)
{
struct drm_pagemap *dpagemap = svm_pages->dpagemap;
struct device *dev = gpusvm->drm->dev;
unsigned long i, j;
lockdep_assert_held(&gpusvm->notifier_lock);
if (svm_pages->flags.has_dma_mapping) {
struct drm_gpusvm_pages_flags flags = {
.__flags = svm_pages->flags.__flags,
};
bool use_iova = dma_use_iova(&svm_pages->state);
if (use_iova) {
if (svm_pages->state_offset)
dma_iova_unlink(dev, &svm_pages->state, 0,
svm_pages->state_offset,
svm_pages->dma_addr[0].dir, 0);
dma_iova_free(dev, &svm_pages->state);
}
for (i = 0, j = 0; i < npages; j++) {
struct drm_pagemap_addr *addr = &svm_pages->dma_addr[j];
if (addr->proto == DRM_INTERCONNECT_SYSTEM) {
if (!use_iova)
dma_unmap_page(dev,
addr->addr,
PAGE_SIZE << addr->order,
addr->dir);
} else if (dpagemap && dpagemap->ops->device_unmap)
dpagemap->ops->device_unmap(dpagemap,
dev, addr);
i += 1 << addr->order;
}
flags.has_devmem_pages = false;
flags.has_dma_mapping = false;
WRITE_ONCE(svm_pages->flags.__flags, flags.__flags);
drm_pagemap_put(svm_pages->dpagemap);
svm_pages->dpagemap = NULL;
}
}
static void __drm_gpusvm_free_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages)
{
lockdep_assert_held(&gpusvm->notifier_lock);
if (svm_pages->dma_addr) {
kvfree(svm_pages->dma_addr);
svm_pages->dma_addr = NULL;
}
}
void drm_gpusvm_free_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages,
unsigned long npages)
{
drm_gpusvm_notifier_lock(gpusvm);
__drm_gpusvm_unmap_pages(gpusvm, svm_pages, npages);
__drm_gpusvm_free_pages(gpusvm, svm_pages);
drm_gpusvm_notifier_unlock(gpusvm);
}
EXPORT_SYMBOL_GPL(drm_gpusvm_free_pages);
void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range)
{
unsigned long npages = npages_in_range(drm_gpusvm_range_start(range),
drm_gpusvm_range_end(range));
struct drm_gpusvm_notifier *notifier;
drm_gpusvm_driver_lock_held(gpusvm);
notifier = drm_gpusvm_notifier_find(gpusvm,
drm_gpusvm_range_start(range),
drm_gpusvm_range_start(range) + 1);
if (WARN_ON_ONCE(!notifier))
return;
drm_gpusvm_notifier_lock(gpusvm);
__drm_gpusvm_unmap_pages(gpusvm, &range->pages, npages);
__drm_gpusvm_free_pages(gpusvm, &range->pages);
__drm_gpusvm_range_remove(notifier, range);
drm_gpusvm_notifier_unlock(gpusvm);
drm_gpusvm_range_put(range);
if (RB_EMPTY_ROOT(¬ifier->root.rb_root)) {
if (!notifier->flags.removed)
mmu_interval_notifier_remove(¬ifier->notifier);
drm_gpusvm_notifier_remove(gpusvm, notifier);
drm_gpusvm_notifier_free(gpusvm, notifier);
}
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_remove);
struct drm_gpusvm_range *
drm_gpusvm_range_get(struct drm_gpusvm_range *range)
{
kref_get(&range->refcount);
return range;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_get);
static void drm_gpusvm_range_destroy(struct kref *refcount)
{
struct drm_gpusvm_range *range =
container_of(refcount, struct drm_gpusvm_range, refcount);
struct drm_gpusvm *gpusvm = range->gpusvm;
if (gpusvm->ops->range_free)
gpusvm->ops->range_free(range);
else
kfree(range);
}
void drm_gpusvm_range_put(struct drm_gpusvm_range *range)
{
kref_put(&range->refcount, drm_gpusvm_range_destroy);
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_put);
static bool drm_gpusvm_pages_valid(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages)
{
lockdep_assert_held(&gpusvm->notifier_lock);
return svm_pages->flags.has_devmem_pages || svm_pages->flags.has_dma_mapping;
}
bool drm_gpusvm_range_pages_valid(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range)
{
return drm_gpusvm_pages_valid(gpusvm, &range->pages);
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_pages_valid);
static bool drm_gpusvm_pages_valid_unlocked(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages)
{
bool pages_valid;
if (!svm_pages->dma_addr)
return false;
drm_gpusvm_notifier_lock(gpusvm);
pages_valid = drm_gpusvm_pages_valid(gpusvm, svm_pages);
if (!pages_valid)
__drm_gpusvm_free_pages(gpusvm, svm_pages);
drm_gpusvm_notifier_unlock(gpusvm);
return pages_valid;
}
int drm_gpusvm_get_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages,
struct mm_struct *mm,
struct mmu_interval_notifier *notifier,
unsigned long pages_start, unsigned long pages_end,
const struct drm_gpusvm_ctx *ctx)
{
struct hmm_range hmm_range = {
.default_flags = HMM_PFN_REQ_FAULT | (ctx->read_only ? 0 :
HMM_PFN_REQ_WRITE),
.notifier = notifier,
.start = pages_start,
.end = pages_end,
.dev_private_owner = ctx->device_private_page_owner,
};
void *zdd;
unsigned long timeout =
jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
unsigned long i, j;
unsigned long npages = npages_in_range(pages_start, pages_end);
unsigned long num_dma_mapped;
unsigned int order = 0;
unsigned long *pfns;
int err = 0;
struct dev_pagemap *pagemap;
struct drm_pagemap *dpagemap;
struct drm_gpusvm_pages_flags flags;
enum dma_data_direction dma_dir = ctx->read_only ? DMA_TO_DEVICE :
DMA_BIDIRECTIONAL;
struct dma_iova_state *state = &svm_pages->state;
retry:
if (time_after(jiffies, timeout))
return -EBUSY;
hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
if (drm_gpusvm_pages_valid_unlocked(gpusvm, svm_pages))
goto set_seqno;
pfns = kvmalloc_array(npages, sizeof(*pfns), GFP_KERNEL);
if (!pfns)
return -ENOMEM;
if (!mmget_not_zero(mm)) {
err = -EFAULT;
goto err_free;
}
hmm_range.hmm_pfns = pfns;
while (true) {
mmap_read_lock(mm);
err = hmm_range_fault(&hmm_range);
mmap_read_unlock(mm);
if (err == -EBUSY) {
if (time_after(jiffies, timeout))
break;
hmm_range.notifier_seq =
mmu_interval_read_begin(notifier);
continue;
}
break;
}
mmput(mm);
if (err)
goto err_free;
*state = (struct dma_iova_state){};
svm_pages->state_offset = 0;
map_pages:
drm_gpusvm_notifier_lock(gpusvm);
flags.__flags = svm_pages->flags.__flags;
if (flags.unmapped) {
drm_gpusvm_notifier_unlock(gpusvm);
err = -EFAULT;
goto err_free;
}
if (mmu_interval_read_retry(notifier, hmm_range.notifier_seq)) {
drm_gpusvm_notifier_unlock(gpusvm);
kvfree(pfns);
goto retry;
}
if (!svm_pages->dma_addr) {
drm_gpusvm_notifier_unlock(gpusvm);
svm_pages->dma_addr =
kvzalloc_objs(*svm_pages->dma_addr, npages);
if (!svm_pages->dma_addr) {
err = -ENOMEM;
goto err_free;
}
goto map_pages;
}
zdd = NULL;
pagemap = NULL;
num_dma_mapped = 0;
for (i = 0, j = 0; i < npages; ++j) {
struct page *page = hmm_pfn_to_page(pfns[i]);
order = drm_gpusvm_hmm_pfn_to_order(pfns[i], i, npages);
if (is_device_private_page(page) ||
is_device_coherent_page(page)) {
struct drm_pagemap_zdd *__zdd =
drm_pagemap_page_zone_device_data(page);
if (!ctx->allow_mixed &&
zdd != __zdd && i > 0) {
err = -EOPNOTSUPP;
goto err_unmap;
}
zdd = __zdd;
if (pagemap != page_pgmap(page)) {
if (pagemap) {
err = -EOPNOTSUPP;
goto err_unmap;
}
pagemap = page_pgmap(page);
dpagemap = drm_pagemap_page_to_dpagemap(page);
if (drm_WARN_ON(gpusvm->drm, !dpagemap)) {
err = -EAGAIN;
goto err_unmap;
}
drm_pagemap_get(dpagemap);
drm_pagemap_put(svm_pages->dpagemap);
svm_pages->dpagemap = dpagemap;
}
svm_pages->dma_addr[j] =
dpagemap->ops->device_map(dpagemap,
gpusvm->drm->dev,
page, order,
dma_dir);
if (dma_mapping_error(gpusvm->drm->dev,
svm_pages->dma_addr[j].addr)) {
err = -EFAULT;
goto err_unmap;
}
} else {
dma_addr_t addr;
if (is_zone_device_page(page) ||
(pagemap && !ctx->allow_mixed)) {
err = -EOPNOTSUPP;
goto err_unmap;
}
if (ctx->devmem_only) {
err = -EFAULT;
goto err_unmap;
}
if (!i)
dma_iova_try_alloc(gpusvm->drm->dev, state,
0, npages * PAGE_SIZE);
if (dma_use_iova(state)) {
err = dma_iova_link(gpusvm->drm->dev, state,
hmm_pfn_to_phys(pfns[i]),
svm_pages->state_offset,
PAGE_SIZE << order,
dma_dir, 0);
if (err)
goto err_unmap;
addr = state->addr + svm_pages->state_offset;
svm_pages->state_offset += PAGE_SIZE << order;
} else {
addr = dma_map_page(gpusvm->drm->dev,
page, 0,
PAGE_SIZE << order,
dma_dir);
if (dma_mapping_error(gpusvm->drm->dev, addr)) {
err = -EFAULT;
goto err_unmap;
}
}
svm_pages->dma_addr[j] = drm_pagemap_addr_encode
(addr, DRM_INTERCONNECT_SYSTEM, order,
dma_dir);
}
i += 1 << order;
num_dma_mapped = i;
flags.has_dma_mapping = true;
}
if (dma_use_iova(state)) {
err = dma_iova_sync(gpusvm->drm->dev, state, 0,
svm_pages->state_offset);
if (err)
goto err_unmap;
}
if (pagemap)
flags.has_devmem_pages = true;
WRITE_ONCE(svm_pages->flags.__flags, flags.__flags);
drm_gpusvm_notifier_unlock(gpusvm);
kvfree(pfns);
set_seqno:
svm_pages->notifier_seq = hmm_range.notifier_seq;
return 0;
err_unmap:
svm_pages->flags.has_dma_mapping = true;
__drm_gpusvm_unmap_pages(gpusvm, svm_pages, num_dma_mapped);
drm_gpusvm_notifier_unlock(gpusvm);
err_free:
kvfree(pfns);
if (err == -EAGAIN)
goto retry;
return err;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_get_pages);
int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range,
const struct drm_gpusvm_ctx *ctx)
{
return drm_gpusvm_get_pages(gpusvm, &range->pages, gpusvm->mm,
&range->notifier->notifier,
drm_gpusvm_range_start(range),
drm_gpusvm_range_end(range), ctx);
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_get_pages);
void drm_gpusvm_unmap_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_pages *svm_pages,
unsigned long npages,
const struct drm_gpusvm_ctx *ctx)
{
if (ctx->in_notifier)
lockdep_assert_held_write(&gpusvm->notifier_lock);
else
drm_gpusvm_notifier_lock(gpusvm);
__drm_gpusvm_unmap_pages(gpusvm, svm_pages, npages);
if (!ctx->in_notifier)
drm_gpusvm_notifier_unlock(gpusvm);
}
EXPORT_SYMBOL_GPL(drm_gpusvm_unmap_pages);
void drm_gpusvm_range_unmap_pages(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range,
const struct drm_gpusvm_ctx *ctx)
{
unsigned long npages = npages_in_range(drm_gpusvm_range_start(range),
drm_gpusvm_range_end(range));
return drm_gpusvm_unmap_pages(gpusvm, &range->pages, npages, ctx);
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_unmap_pages);
int drm_gpusvm_range_evict(struct drm_gpusvm *gpusvm,
struct drm_gpusvm_range *range)
{
struct mmu_interval_notifier *notifier = &range->notifier->notifier;
struct hmm_range hmm_range = {
.default_flags = HMM_PFN_REQ_FAULT,
.notifier = notifier,
.start = drm_gpusvm_range_start(range),
.end = drm_gpusvm_range_end(range),
.dev_private_owner = NULL,
};
unsigned long timeout =
jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT);
unsigned long *pfns;
unsigned long npages = npages_in_range(drm_gpusvm_range_start(range),
drm_gpusvm_range_end(range));
int err = 0;
struct mm_struct *mm = gpusvm->mm;
if (!mmget_not_zero(mm))
return -EFAULT;
pfns = kvmalloc_array(npages, sizeof(*pfns), GFP_KERNEL);
if (!pfns) {
mmput(mm);
return -ENOMEM;
}
hmm_range.hmm_pfns = pfns;
while (!time_after(jiffies, timeout)) {
hmm_range.notifier_seq = mmu_interval_read_begin(notifier);
if (time_after(jiffies, timeout)) {
err = -ETIME;
break;
}
mmap_read_lock(mm);
err = hmm_range_fault(&hmm_range);
mmap_read_unlock(mm);
if (err != -EBUSY)
break;
}
kvfree(pfns);
mmput(mm);
return err;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_evict);
bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsigned long start,
unsigned long end)
{
struct drm_gpusvm_notifier *notifier;
drm_gpusvm_for_each_notifier(notifier, gpusvm, start, end) {
struct drm_gpusvm_range *range = NULL;
drm_gpusvm_for_each_range(range, notifier, start, end)
return true;
}
return false;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_has_mapping);
void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range,
const struct mmu_notifier_range *mmu_range)
{
lockdep_assert_held_write(&range->gpusvm->notifier_lock);
range->pages.flags.unmapped = true;
if (drm_gpusvm_range_start(range) < mmu_range->start ||
drm_gpusvm_range_end(range) > mmu_range->end)
range->pages.flags.partial_unmap = true;
}
EXPORT_SYMBOL_GPL(drm_gpusvm_range_set_unmapped);
MODULE_DESCRIPTION("DRM GPUSVM");
MODULE_LICENSE("GPL");