/** * DOC: Overview * * GPU Shared Virtual Memory (GPU SVM) layer for the Direct Rendering Manager (DRM) * is a component of the DRM framework designed to manage shared virtual memory * between the CPU and GPU. It enables efficient data exchange and processing * for GPU-accelerated applications by allowing memory sharing and * synchronization between the CPU's and GPU's virtual address spaces. * * Key GPU SVM Components: * * - Notifiers: * Used for tracking memory intervals and notifying the GPU of changes, * notifiers are sized based on a GPU SVM initialization parameter, with a * recommendation of 512M or larger. They maintain a Red-BlacK tree and a * list of ranges that fall within the notifier interval. Notifiers are * tracked within a GPU SVM Red-BlacK tree and list and are dynamically * inserted or removed as ranges within the interval are created or * destroyed. * - Ranges: * Represent memory ranges mapped in a DRM device and managed by GPU SVM. * They are sized based on an array of chunk sizes, which is a GPU SVM * initialization parameter, and the CPU address space. Upon GPU fault, * the largest aligned chunk that fits within the faulting CPU address * space is chosen for the range size. Ranges are expected to be * dynamically allocated on GPU fault and removed on an MMU notifier UNMAP * event. As mentioned above, ranges are tracked in a notifier's Red-Black * tree. * * - Operations: * Define the interface for driver-specific GPU SVM operations such as * range allocation, notifier allocation, and invalidations. * * - Device Memory Allocations: * Embedded structure containing enough information for GPU SVM to migrate * to / from device memory. * * - Device Memory Operations: * Define the interface for driver-specific device memory operations * release memory, populate pfns, and copy to / from device memory. * * This layer provides interfaces for allocating, mapping, migrating, and * releasing memory ranges between the CPU and GPU. It handles all core memory * management interactions (DMA mapping, HMM, and migration) and provides * driver-specific virtual functions (vfuncs). This infrastructure is sufficient * to build the expected driver components for an SVM implementation as detailed * below. * * Expected Driver Components: * * - GPU page fault handler: * Used to create ranges and notifiers based on the fault address, * optionally migrate the range to device memory, and create GPU bindings. * * - Garbage collector: * Used to unmap and destroy GPU bindings for ranges. Ranges are expected * to be added to the garbage collector upon a MMU_NOTIFY_UNMAP event in * notifier callback. * * - Notifier callback: * Used to invalidate and DMA unmap GPU bindings for ranges.
*/
/** * DOC: Locking * * GPU SVM handles locking for core MM interactions, i.e., it locks/unlocks the * mmap lock as needed. * * GPU SVM introduces a global notifier lock, which safeguards the notifier's * range RB tree and list, as well as the range's DMA mappings and sequence * number. GPU SVM manages all necessary locking and unlocking operations, * except for the recheck range's pages being valid * (drm_gpusvm_range_pages_valid) when the driver is committing GPU bindings. * This lock corresponds to the ``driver->update`` lock mentioned in * Documentation/mm/hmm.rst. Future revisions may transition from a GPU SVM * global lock to a per-notifier lock if finer-grained locking is deemed * necessary. * * In addition to the locking mentioned above, the driver should implement a * lock to safeguard core GPU SVM function calls that modify state, such as * drm_gpusvm_range_find_or_insert and drm_gpusvm_range_remove. This lock is * denoted as 'driver_svm_lock' in code examples. Finer grained driver side * locking should also be possible for concurrent GPU fault processing within a * single GPU SVM. The 'driver_svm_lock' can be via drm_gpusvm_driver_set_lock * to add annotations to GPU SVM.
*/
/** * DOC: Partial Unmapping of Ranges * * Partial unmapping of ranges (e.g., 1M out of 2M is unmapped by CPU resulting * in MMU_NOTIFY_UNMAP event) presents several challenges, with the main one * being that a subset of the range still has CPU and GPU mappings. If the * backing store for the range is in device memory, a subset of the backing * store has references. One option would be to split the range and device * memory backing store, but the implementation for this would be quite * complicated. Given that partial unmappings are rare and driver-defined range * sizes are relatively small, GPU SVM does not support splitting of ranges. * * With no support for range splitting, upon partial unmapping of a range, the * driver is expected to invalidate and destroy the entire range. If the range * has device memory as its backing, the driver is also expected to migrate any * remaining pages back to RAM.
*/
/** * DOC: Examples * * This section provides three examples of how to build the expected driver * components: the GPU page fault handler, the garbage collector, and the * notifier callback. * * The generic code provided does not include logic for complex migration * policies, optimized invalidations, fined grained driver locking, or other * potentially required driver locking (e.g., DMA-resv locks). * * 1) GPU page fault handler * * .. code-block:: c * * int driver_bind_range(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range) * { * int err = 0; * * driver_alloc_and_setup_memory_for_bind(gpusvm, range); * * drm_gpusvm_notifier_lock(gpusvm); * if (drm_gpusvm_range_pages_valid(range)) * driver_commit_bind(gpusvm, range); * else * err = -EAGAIN; * drm_gpusvm_notifier_unlock(gpusvm); * * return err; * } * * int driver_gpu_fault(struct drm_gpusvm *gpusvm, unsigned long fault_addr, * unsigned long gpuva_start, unsigned long gpuva_end) * { * struct drm_gpusvm_ctx ctx = {}; * int err; * * driver_svm_lock(); * retry: * // Always process UNMAPs first so view of GPU SVM ranges is current * driver_garbage_collector(gpusvm); * * range = drm_gpusvm_range_find_or_insert(gpusvm, fault_addr, * gpuva_start, gpuva_end, * &ctx); * if (IS_ERR(range)) { * err = PTR_ERR(range); * goto unlock; * } * * if (driver_migration_policy(range)) { * err = drm_pagemap_populate_mm(driver_choose_drm_pagemap(), * gpuva_start, gpuva_end, gpusvm->mm, * ctx->timeslice_ms); * if (err) // CPU mappings may have changed * goto retry; * } * * err = drm_gpusvm_range_get_pages(gpusvm, range, &ctx); * if (err == -EOPNOTSUPP || err == -EFAULT || err == -EPERM) { // CPU mappings changed * if (err == -EOPNOTSUPP) * drm_gpusvm_range_evict(gpusvm, range); * goto retry; * } else if (err) { * goto unlock; * } * * err = driver_bind_range(gpusvm, range); * if (err == -EAGAIN) // CPU mappings changed * goto retry * * unlock: * driver_svm_unlock(); * return err; * } * * 2) Garbage Collector * * .. code-block:: c * * void __driver_garbage_collector(struct drm_gpusvm *gpusvm, * struct drm_gpusvm_range *range) * { * assert_driver_svm_locked(gpusvm); * * // Partial unmap, migrate any remaining device memory pages back to RAM * if (range->flags.partial_unmap) * drm_gpusvm_range_evict(gpusvm, range); * * driver_unbind_range(range); * drm_gpusvm_range_remove(gpusvm, range); * } * * void driver_garbage_collector(struct drm_gpusvm *gpusvm) * { * assert_driver_svm_locked(gpusvm); * * for_each_range_in_garbage_collector(gpusvm, range) * __driver_garbage_collector(gpusvm, range); * } * * 3) Notifier callback * * .. code-block:: c * * void driver_invalidation(struct drm_gpusvm *gpusvm, * struct drm_gpusvm_notifier *notifier, * const struct mmu_notifier_range *mmu_range) * { * struct drm_gpusvm_ctx ctx = { .in_notifier = true, }; * struct drm_gpusvm_range *range = NULL; * * driver_invalidate_device_pages(gpusvm, mmu_range->start, mmu_range->end); * * drm_gpusvm_for_each_range(range, notifier, mmu_range->start, * mmu_range->end) { * drm_gpusvm_range_unmap_pages(gpusvm, range, &ctx); * * if (mmu_range->event != MMU_NOTIFY_UNMAP) * continue; * * drm_gpusvm_range_set_unmapped(range, mmu_range); * driver_garbage_collector_add(gpusvm, range); * } * }
*/
/** * npages_in_range() - Calculate the number of pages in a given range * @start: The start address of the range * @end: The end address of the range * * This macro calculates the number of pages in a given memory range, * specified by the start and end addresses. It divides the difference * between the end and start addresses by the page size (PAGE_SIZE) to * determine the number of pages in the range. * * Return: The number of pages in the specified range.
*/ staticunsignedlong
npages_in_range(unsignedlong start, unsignedlong end)
{ return (end - start) >> PAGE_SHIFT;
}
/** * drm_gpusvm_range_find() - Find GPU SVM range from GPU SVM notifier * @notifier: Pointer to the GPU SVM notifier structure. * @start: Start address of the range * @end: End address of the range * * Return: A pointer to the drm_gpusvm_range if found or NULL
*/ struct drm_gpusvm_range *
drm_gpusvm_range_find(struct drm_gpusvm_notifier *notifier, unsignedlong start, unsignedlong end)
{ struct interval_tree_node *itree;
itree = interval_tree_iter_first(¬ifier->root, start, end - 1);
/** * drm_gpusvm_for_each_range_safe() - Safely iterate over GPU SVM ranges in a notifier * @range__: Iterator variable for the ranges * @next__: Iterator variable for the ranges temporay storage * @notifier__: Pointer to the GPU SVM notifier * @start__: Start address of the range * @end__: End address of the range * * This macro is used to iterate over GPU SVM ranges in a notifier while * removing ranges from it.
*/ #define drm_gpusvm_for_each_range_safe(range__, next__, notifier__, start__, end__) \ for ((range__) = drm_gpusvm_range_find((notifier__), (start__), (end__)), \
(next__) = __drm_gpusvm_range_next(range__); \
(range__) && (drm_gpusvm_range_start(range__) < (end__)); \
(range__) = (next__), (next__) = __drm_gpusvm_range_next(range__))
/** * __drm_gpusvm_notifier_next() - get the next drm_gpusvm_notifier in the list * @notifier: a pointer to the current drm_gpusvm_notifier * * Return: A pointer to the next drm_gpusvm_notifier if available, or NULL if * the current notifier is the last one or if the input notifier is * NULL.
*/ staticstruct drm_gpusvm_notifier *
__drm_gpusvm_notifier_next(struct drm_gpusvm_notifier *notifier)
{ if (notifier && !list_is_last(¬ifier->entry,
¬ifier->gpusvm->notifier_list)) return list_next_entry(notifier, entry);
/** * drm_gpusvm_for_each_notifier() - Iterate over GPU SVM notifiers in a gpusvm * @notifier__: Iterator variable for the notifiers * @notifier__: Pointer to the GPU SVM notifier * @start__: Start address of the notifier * @end__: End address of the notifier * * This macro is used to iterate over GPU SVM notifiers in a gpusvm.
*/ #define drm_gpusvm_for_each_notifier(notifier__, gpusvm__, start__, end__) \ for ((notifier__) = notifier_iter_first(&(gpusvm__)->root, (start__), (end__) - 1); \
(notifier__) && (drm_gpusvm_notifier_start(notifier__) < (end__)); \
(notifier__) = __drm_gpusvm_notifier_next(notifier__))
/** * drm_gpusvm_for_each_notifier_safe() - Safely iterate over GPU SVM notifiers in a gpusvm * @notifier__: Iterator variable for the notifiers * @next__: Iterator variable for the notifiers temporay storage * @notifier__: Pointer to the GPU SVM notifier * @start__: Start address of the notifier * @end__: End address of the notifier * * This macro is used to iterate over GPU SVM notifiers in a gpusvm while * removing notifiers from it.
*/ #define drm_gpusvm_for_each_notifier_safe(notifier__, next__, gpusvm__, start__, end__) \ for ((notifier__) = notifier_iter_first(&(gpusvm__)->root, (start__), (end__) - 1), \
(next__) = __drm_gpusvm_notifier_next(notifier__); \
(notifier__) && (drm_gpusvm_notifier_start(notifier__) < (end__)); \
(notifier__) = (next__), (next__) = __drm_gpusvm_notifier_next(notifier__))
/** * drm_gpusvm_notifier_invalidate() - Invalidate a GPU SVM notifier. * @mni: Pointer to the mmu_interval_notifier structure. * @mmu_range: Pointer to the mmu_notifier_range structure. * @cur_seq: Current sequence number. * * This function serves as a generic MMU notifier for GPU SVM. It sets the MMU * notifier sequence number and calls the driver invalidate vfunc under * gpusvm->notifier_lock. * * Return: true if the operation succeeds, false otherwise.
*/ staticbool
drm_gpusvm_notifier_invalidate(struct mmu_interval_notifier *mni, conststruct mmu_notifier_range *mmu_range, unsignedlong 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)) returnfalse;
/** * drm_gpusvm_init() - Initialize the GPU SVM. * @gpusvm: Pointer to the GPU SVM structure. * @name: Name of the GPU SVM. * @drm: Pointer to the DRM device structure. * @mm: Pointer to the mm_struct for the address space. * @device_private_page_owner: Device private pages owner. * @mm_start: Start address of GPU SVM. * @mm_range: Range of the GPU SVM. * @notifier_size: Size of individual notifiers. * @ops: Pointer to the operations structure for GPU SVM. * @chunk_sizes: Pointer to the array of chunk sizes used in range allocation. * Entries should be powers of 2 in descending order with last * entry being SZ_4K. * @num_chunks: Number of chunks. * * This function initializes the GPU SVM. * * Return: 0 on success, a negative error code on failure.
*/ int drm_gpusvm_init(struct drm_gpusvm *gpusvm, constchar *name, struct drm_device *drm, struct mm_struct *mm, void *device_private_page_owner, unsignedlong mm_start, unsignedlong mm_range, unsignedlong notifier_size, conststruct drm_gpusvm_ops *ops, constunsignedlong *chunk_sizes, int num_chunks)
{ if (!ops->invalidate || !num_chunks) return -EINVAL;
/** * drm_gpusvm_notifier_find() - Find GPU SVM notifier * @gpusvm: Pointer to the GPU SVM structure * @fault_addr: Fault address * * This function finds the GPU SVM notifier associated with the fault address. * * Return: Pointer to the GPU SVM notifier on success, NULL otherwise.
*/ staticstruct drm_gpusvm_notifier *
drm_gpusvm_notifier_find(struct drm_gpusvm *gpusvm, unsignedlong fault_addr)
{ return notifier_iter_first(&gpusvm->root, fault_addr, fault_addr + 1);
}
/** * to_drm_gpusvm_notifier() - retrieve the container struct for a given rbtree node * @node: a pointer to the rbtree node embedded within a drm_gpusvm_notifier struct * * Return: A pointer to the containing drm_gpusvm_notifier structure.
*/ staticstruct drm_gpusvm_notifier *to_drm_gpusvm_notifier(struct rb_node *node)
{ return container_of(node, struct drm_gpusvm_notifier, itree.rb);
}
/** * drm_gpusvm_notifier_insert() - Insert GPU SVM notifier * @gpusvm: Pointer to the GPU SVM structure * @notifier: Pointer to the GPU SVM notifier structure * * This function inserts the GPU SVM notifier into the GPU SVM RB tree and list.
*/ staticvoid drm_gpusvm_notifier_insert(struct drm_gpusvm *gpusvm, struct drm_gpusvm_notifier *notifier)
{ struct rb_node *node; struct list_head *head;
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);
}
/** * drm_gpusvm_notifier_remove() - Remove GPU SVM notifier * @gpusvm: Pointer to the GPU SVM tructure * @notifier: Pointer to the GPU SVM notifier structure * * This function removes the GPU SVM notifier from the GPU SVM RB tree and list.
*/ staticvoid drm_gpusvm_notifier_remove(struct drm_gpusvm *gpusvm, struct drm_gpusvm_notifier *notifier)
{
interval_tree_remove(¬ifier->itree, &gpusvm->root);
list_del(¬ifier->entry);
}
/** * drm_gpusvm_fini() - Finalize the GPU SVM. * @gpusvm: Pointer to the GPU SVM structure. * * This function finalizes the GPU SVM by cleaning up any remaining ranges and * notifiers, and dropping a reference to struct MM.
*/ void drm_gpusvm_fini(struct drm_gpusvm *gpusvm)
{ struct drm_gpusvm_notifier *notifier, *next;
/** * drm_gpusvm_notifier_free() - Free GPU SVM notifier * @gpusvm: Pointer to the GPU SVM structure * @notifier: Pointer to the GPU SVM notifier structure * * This function frees the GPU SVM notifier structure.
*/ staticvoid 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);
}
/** * to_drm_gpusvm_range() - retrieve the container struct for a given rbtree node * @node: a pointer to the rbtree node embedded within a drm_gpusvm_range struct * * Return: A pointer to the containing drm_gpusvm_range structure.
*/ staticstruct drm_gpusvm_range *to_drm_gpusvm_range(struct rb_node *node)
{ return container_of(node, struct drm_gpusvm_range, itree.rb);
}
/** * drm_gpusvm_range_insert() - Insert GPU SVM range * @notifier: Pointer to the GPU SVM notifier structure * @range: Pointer to the GPU SVM range structure * * This function inserts the GPU SVM range into the notifier RB tree and list.
*/ staticvoid drm_gpusvm_range_insert(struct drm_gpusvm_notifier *notifier, struct drm_gpusvm_range *range)
{ struct rb_node *node; struct list_head *head;
/** * __drm_gpusvm_range_remove() - Remove GPU SVM range * @notifier: Pointer to the GPU SVM notifier structure * @range: Pointer to the GPU SVM range structure * * This macro removes the GPU SVM range from the notifier RB tree and list.
*/ staticvoid __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);
}
/** * drm_gpusvm_range_alloc() - Allocate GPU SVM range * @gpusvm: Pointer to the GPU SVM structure * @notifier: Pointer to the GPU SVM notifier structure * @fault_addr: Fault address * @chunk_size: Chunk size * @migrate_devmem: Flag indicating whether to migrate device memory * * This function allocates and initializes the GPU SVM range structure. * * Return: Pointer to the allocated GPU SVM range on success, ERR_PTR() on failure.
*/ staticstruct drm_gpusvm_range *
drm_gpusvm_range_alloc(struct drm_gpusvm *gpusvm, struct drm_gpusvm_notifier *notifier, unsignedlong fault_addr, unsignedlong chunk_size, bool migrate_devmem)
{ struct drm_gpusvm_range *range;
if (gpusvm->ops->range_alloc)
range = gpusvm->ops->range_alloc(gpusvm); else
range = kzalloc(sizeof(*range), GFP_KERNEL);
/** * drm_gpusvm_hmm_pfn_to_order() - Get the largest CPU mapping order. * @hmm_pfn: The current hmm_pfn. * @hmm_pfn_index: Index of the @hmm_pfn within the pfn array. * @npages: Number of pages within the pfn array i.e the hmm range size. * * To allow skipping PFNs with the same flags (like when they belong to * the same huge PTE) when looping over the pfn array, take a given a hmm_pfn, * and return the largest order that will fit inside the CPU PTE, but also * crucially accounting for the original hmm range boundaries. * * Return: The largest order that will safely fit within the size of the hmm_pfn * CPU PTE.
*/ staticunsignedint drm_gpusvm_hmm_pfn_to_order(unsignedlong hmm_pfn, unsignedlong hmm_pfn_index, unsignedlong npages)
{ unsignedlong size;
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);
}
/** * drm_gpusvm_range_chunk_size() - Determine chunk size for GPU SVM range * @gpusvm: Pointer to the GPU SVM structure * @notifier: Pointer to the GPU SVM notifier structure * @vas: Pointer to the virtual memory area structure * @fault_addr: Fault address * @gpuva_start: Start address of GPUVA which mirrors CPU * @gpuva_end: End address of GPUVA which mirrors CPU * @check_pages_threshold: Check CPU pages for present threshold * * This function determines the chunk size for the GPU SVM range based on the * fault address, GPU SVM chunk sizes, existing GPU SVM ranges, and the virtual * memory area boundaries. * * Return: Chunk size on success, LONG_MAX on failure.
*/ staticunsignedlong
drm_gpusvm_range_chunk_size(struct drm_gpusvm *gpusvm, struct drm_gpusvm_notifier *notifier, struct vm_area_struct *vas, unsignedlong fault_addr, unsignedlong gpuva_start, unsignedlong gpuva_end, unsignedlong check_pages_threshold)
{ unsignedlong 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 allocation more than page, ensure not to overlap with existing * ranges.
*/ if (end - start != SZ_4K) { struct drm_gpusvm_range *range;
range = drm_gpusvm_range_find(notifier, start, end); if (range) {
++i; goto retry;
}
/* * XXX: Only create range on pages CPU has faulted in. Without * this check, or prefault, on BMG 'xe_exec_system_allocator --r * process-many-malloc' fails. In the failure case, each process * mallocs 16k but the CPU VMA is ~128k which results in 64k SVM * ranges. When migrating the SVM ranges, some processes fail in * drm_pagemap_migrate_to_devmem with 'migrate.cpages != npages' * and then upon drm_gpusvm_range_get_pages device pages from * other processes are collected + faulted in which creates all * sorts of problems. Unsure exactly how this happening, also * problem goes away if 'xe_exec_system_allocator --r * process-many-malloc' mallocs at least 64k at a time.
*/ if (end - start <= check_pages_threshold &&
!drm_gpusvm_check_pages(gpusvm, notifier, start, end)) {
++i; goto retry;
}
}
return end - start;
}
#ifdef CONFIG_LOCKDEP /** * drm_gpusvm_driver_lock_held() - Assert GPU SVM driver lock is held * @gpusvm: Pointer to the GPU SVM structure. * * Ensure driver lock is held.
*/ staticvoid 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 staticvoid drm_gpusvm_driver_lock_held(struct drm_gpusvm *gpusvm)
{
} #endif
/** * drm_gpusvm_find_vma_start() - Find start address for first VMA in range * @gpusvm: Pointer to the GPU SVM structure * @start: The inclusive start user address. * @end: The exclusive end user address. * * Returns: The start address of first VMA within the provided range, * ULONG_MAX otherwise. Assumes start_addr < end_addr.
*/ unsignedlong
drm_gpusvm_find_vma_start(struct drm_gpusvm *gpusvm, unsignedlong start, unsignedlong end)
{ struct mm_struct *mm = gpusvm->mm; struct vm_area_struct *vma; unsignedlong 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;
/** * drm_gpusvm_range_find_or_insert() - Find or insert GPU SVM range * @gpusvm: Pointer to the GPU SVM structure * @fault_addr: Fault address * @gpuva_start: Start address of GPUVA which mirrors CPU * @gpuva_end: End address of GPUVA which mirrors CPU * @ctx: GPU SVM context * * This function finds or inserts a newly allocated a GPU SVM range based on the * fault address. Caller must hold a lock to protect range lookup and insertion. * * Return: Pointer to the GPU SVM range on success, ERR_PTR() on failure.
*/ struct drm_gpusvm_range *
drm_gpusvm_range_find_or_insert(struct drm_gpusvm *gpusvm, unsignedlong fault_addr, unsignedlong gpuva_start, unsignedlong gpuva_end, conststruct 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; unsignedlong chunk_size; int err; bool migrate_devmem;
range = drm_gpusvm_range_find(notifier, fault_addr, fault_addr + 1); if (range) goto out_mmunlock; /* * XXX: Short-circuiting migration based on migrate_vma_* current * limitations. If/when migrate_vma_* add more support, this logic will * have to change.
*/
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); 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);
/** * __drm_gpusvm_range_unmap_pages() - Unmap pages associated with a GPU SVM range (internal) * @gpusvm: Pointer to the GPU SVM structure * @range: Pointer to the GPU SVM range structure * @npages: Number of pages to unmap * * This function unmap pages associated with a GPU SVM range. Assumes and * asserts correct locking is in place when called.
*/ staticvoid __drm_gpusvm_range_unmap_pages(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range, unsignedlong npages)
{ unsignedlong i, j; struct drm_pagemap *dpagemap = range->dpagemap; struct device *dev = gpusvm->drm->dev;
/* WRITE_ONCE pairs with READ_ONCE for opportunistic checks */
flags.has_devmem_pages = false;
flags.has_dma_mapping = false;
WRITE_ONCE(range->flags.__flags, flags.__flags);
range->dpagemap = NULL;
}
}
/** * drm_gpusvm_range_free_pages() - Free pages associated with a GPU SVM range * @gpusvm: Pointer to the GPU SVM structure * @range: Pointer to the GPU SVM range structure * * This function frees the dma address array associated with a GPU SVM range.
*/ staticvoid drm_gpusvm_range_free_pages(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range)
{
lockdep_assert_held(&gpusvm->notifier_lock);
if (range->dma_addr) {
kvfree(range->dma_addr);
range->dma_addr = NULL;
}
}
/** * drm_gpusvm_range_remove() - Remove GPU SVM range * @gpusvm: Pointer to the GPU SVM structure * @range: Pointer to the GPU SVM range to be removed * * This function removes the specified GPU SVM range and also removes the parent * GPU SVM notifier if no more ranges remain in the notifier. The caller must * hold a lock to protect range and notifier removal.
*/ void drm_gpusvm_range_remove(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range)
{ unsignedlong 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)); if (WARN_ON_ONCE(!notifier)) return;
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);
/** * drm_gpusvm_range_get() - Get a reference to GPU SVM range * @range: Pointer to the GPU SVM range * * This function increments the reference count of the specified GPU SVM range. * * Return: Pointer to the GPU SVM range.
*/ struct drm_gpusvm_range *
drm_gpusvm_range_get(struct drm_gpusvm_range *range)
{
kref_get(&range->refcount);
/** * drm_gpusvm_range_destroy() - Destroy GPU SVM range * @refcount: Pointer to the reference counter embedded in the GPU SVM range * * This function destroys the specified GPU SVM range when its reference count * reaches zero. If a custom range-free function is provided, it is invoked to * free the range; otherwise, the range is deallocated using kfree().
*/ staticvoid 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);
}
/** * drm_gpusvm_range_put() - Put a reference to GPU SVM range * @range: Pointer to the GPU SVM range * * This function decrements the reference count of the specified GPU SVM range * and frees it when the count reaches zero.
*/ 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);
/** * drm_gpusvm_range_pages_valid() - GPU SVM range pages valid * @gpusvm: Pointer to the GPU SVM structure * @range: Pointer to the GPU SVM range structure * * This function determines if a GPU SVM range pages are valid. Expected be * called holding gpusvm->notifier_lock and as the last step before committing a * GPU binding. This is akin to a notifier seqno check in the HMM documentation * but due to wider notifiers (i.e., notifiers which span multiple ranges) this * function is required for finer grained checking (i.e., per range) if pages * are valid. * * Return: True if GPU SVM range has valid pages, False otherwise
*/ bool drm_gpusvm_range_pages_valid(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range)
{
lockdep_assert_held(&gpusvm->notifier_lock);
/** * drm_gpusvm_range_pages_valid_unlocked() - GPU SVM range pages valid unlocked * @gpusvm: Pointer to the GPU SVM structure * @range: Pointer to the GPU SVM range structure * * This function determines if a GPU SVM range pages are valid. Expected be * called without holding gpusvm->notifier_lock. * * Return: True if GPU SVM range has valid pages, False otherwise
*/ staticbool
drm_gpusvm_range_pages_valid_unlocked(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range)
{ bool pages_valid;
if (!range->dma_addr) returnfalse;
drm_gpusvm_notifier_lock(gpusvm);
pages_valid = drm_gpusvm_range_pages_valid(gpusvm, range); if (!pages_valid)
drm_gpusvm_range_free_pages(gpusvm, range);
drm_gpusvm_notifier_unlock(gpusvm);
return pages_valid;
}
/** * drm_gpusvm_range_get_pages() - Get pages for a GPU SVM range * @gpusvm: Pointer to the GPU SVM structure * @range: Pointer to the GPU SVM range structure * @ctx: GPU SVM context * * This function gets pages for a GPU SVM range and ensures they are mapped for * DMA access. * * Return: 0 on success, negative error code on failure.
*/ int drm_gpusvm_range_get_pages(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range, conststruct drm_gpusvm_ctx *ctx)
{ struct mmu_interval_notifier *notifier = &range->notifier->notifier; struct hmm_range hmm_range = {
.default_flags = HMM_PFN_REQ_FAULT | (ctx->read_only ? 0 :
HMM_PFN_REQ_WRITE),
.notifier = notifier,
.start = drm_gpusvm_range_start(range),
.end = drm_gpusvm_range_end(range),
.dev_private_owner = gpusvm->device_private_page_owner,
}; struct mm_struct *mm = gpusvm->mm; void *zdd; unsignedlong timeout =
jiffies + msecs_to_jiffies(HMM_RANGE_DEFAULT_TIMEOUT); unsignedlong i, j; unsignedlong npages = npages_in_range(drm_gpusvm_range_start(range),
drm_gpusvm_range_end(range)); unsignedlong num_dma_mapped; unsignedint order = 0; unsignedlong *pfns; int err = 0; struct dev_pagemap *pagemap; struct drm_pagemap *dpagemap; struct drm_gpusvm_range_flags flags;
retry:
hmm_range.notifier_seq = mmu_interval_read_begin(notifier); if (drm_gpusvm_range_pages_valid_unlocked(gpusvm, range)) 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;
}
map_pages: /* * Perform all dma mappings under the notifier lock to not * access freed pages. A notifier will either block on * the notifier lock or unmap dma.
*/
drm_gpusvm_notifier_lock(gpusvm);
/** * drm_gpusvm_range_unmap_pages() - Unmap pages associated with a GPU SVM range * drm_gpusvm_range_evict() - Evict GPU SVM range * @gpusvm: Pointer to the GPU SVM structure * @range: Pointer to the GPU SVM range structure * @ctx: GPU SVM context * * This function unmaps pages associated with a GPU SVM range. If @in_notifier * is set, it is assumed that gpusvm->notifier_lock is held in write mode; if it * is clear, it acquires gpusvm->notifier_lock in read mode. Must be called on * each GPU SVM range attached to notifier in gpusvm->ops->invalidate for IOMMU * security model.
*/ void drm_gpusvm_range_unmap_pages(struct drm_gpusvm *gpusvm, struct drm_gpusvm_range *range, conststruct drm_gpusvm_ctx *ctx)
{ unsignedlong npages = npages_in_range(drm_gpusvm_range_start(range),
drm_gpusvm_range_end(range));
if (ctx->in_notifier)
lockdep_assert_held_write(&gpusvm->notifier_lock); else
drm_gpusvm_notifier_lock(gpusvm);
/** * drm_gpusvm_has_mapping() - Check if GPU SVM has mapping for the given address range * @gpusvm: Pointer to the GPU SVM structure. * @start: Start address * @end: End address * * Return: True if GPU SVM has mapping, False otherwise
*/ bool drm_gpusvm_has_mapping(struct drm_gpusvm *gpusvm, unsignedlong start, unsignedlong end)
{ struct drm_gpusvm_notifier *notifier;
/** * drm_gpusvm_range_set_unmapped() - Mark a GPU SVM range as unmapped * @range: Pointer to the GPU SVM range structure. * @mmu_range: Pointer to the MMU notifier range structure. * * This function marks a GPU SVM range as unmapped and sets the partial_unmap flag * if the range partially falls within the provided MMU notifier range.
*/ void drm_gpusvm_range_set_unmapped(struct drm_gpusvm_range *range, conststruct mmu_notifier_range *mmu_range)
{
lockdep_assert_held_write(&range->gpusvm->notifier_lock);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.