2023-10-08 23:33:14

by Danilo Krummrich

[permalink] [raw]
Subject: [PATCH drm-misc-next v6 0/6] [RFC] DRM GPUVM features


Currently GPUVM offers common infrastructure to track GPU VA allocations
and mappings, generically connect GPU VA mappings to their backing
buffers and perform more complex mapping operations on the GPU VA space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make GPUVM represent the
basis of a VM implementation. In this context, this patch series aims at
generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

The implementation introduces struct drm_gpuvm_bo, which serves as abstraction
combining a struct drm_gpuvm and struct drm_gem_object, similar to what
amdgpu does with struct amdgpu_bo_vm. While this adds a bit of complexity it
improves the efficiency of tracking external and evicted GEM objects.

This patch series is also available at [3].

[1] https://gitlab.freedesktop.org/nouvelles/kernel/-/commits/gpuvm-next

Changes in V2:
==============
- rename 'drm_gpuva_manager' -> 'drm_gpuvm' which generally leads to more
consistent naming
- properly separate commits (introduce common dma-resv, drm_gpuvm_bo
abstraction, etc.)
- remove maple tree for tracking external objects, use a list drm_gpuvm_bos
per drm_gpuvm instead
- rework dma-resv locking helpers (Thomas)
- add a locking helper for a given range of the VA space (Christian)
- make the GPUVA manager buildable as module, rather than drm_exec
builtin (Christian)

Changes in V3:
==============
- rename missing function and files (Boris)
- warn if vm_obj->obj != obj in drm_gpuva_link() (Boris)
- don't expose drm_gpuvm_bo_destroy() (Boris)
- unlink VM_BO from GEM in drm_gpuvm_bo_destroy() rather than
drm_gpuva_unlink() and link within drm_gpuvm_bo_obtain() to keep
drm_gpuvm_bo instances unique
- add internal locking to external and evicted object lists to support drivers
updating the VA space from within the fence signalling critical path (Boris)
- unlink external objects and evicted objects from the GPUVM's list in
drm_gpuvm_bo_destroy()
- add more documentation and fix some kernel doc issues

Changes in V4:
==============
- add a drm_gpuvm_resv() helper (Boris)
- add a drm_gpuvm::<list_name>::local_list field (Boris)
- remove drm_gpuvm_bo_get_unless_zero() helper (Boris)
- fix missing NULL assignment in get_next_vm_bo_from_list() (Boris)
- keep a drm_gem_object reference on potential vm_bo destroy (alternatively we
could free the vm_bo and drop the vm_bo's drm_gem_object reference through
async work)
- introduce DRM_GPUVM_RESV_PROTECTED flag to indicate external locking through
the corresponding dma-resv locks to optimize for drivers already holding
them when needed; add the corresponding lock_assert_held() calls (Thomas)
- make drm_gpuvm_bo_evict() per vm_bo and add a drm_gpuvm_bo_gem_evict()
helper (Thomas)
- pass a drm_gpuvm_bo in drm_gpuvm_ops::vm_bo_validate() (Thomas)
- documentation fixes

Changes in V5:
==============
- use a root drm_gem_object provided by the driver as a base for the VM's
common dma-resv (Christian)
- provide a helper to allocate a "dummy" root GEM object in case a driver
specific root GEM object isn't available
- add a dedicated patch for nouveau to make use of the GPUVM's shared dma-resv
- improve documentation (Boris)
- the following patches are removed from the series, since they already landed
in drm-misc-next
- f72c2db47080 ("drm/gpuvm: rename struct drm_gpuva_manager to struct drm_gpuvm")
- fe7acaa727e1 ("drm/gpuvm: allow building as module")
- 78f54469b871 ("drm/nouveau: uvmm: rename 'umgr' to 'base'")

Changes in V6:
==============
- add drm_gpuvm_bo::evicted field protected by the drm_gem_object's dma-resv
lock (Thomas)
- additionally to the original proposal, always use drm_gpuvm_bo::evicted
regardless of the used locking scheme and always keep it up to date
- remove unneccesary get->put dance in drm_gpuva_unlink() (Thomas)
- fix commit message wording (Thomas)
- fix kernel doc warnings (kernel test robot)

Danilo Krummrich (6):
drm/gpuvm: add common dma-resv per struct drm_gpuvm
drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm
drm/gpuvm: add an abstraction for a VM / BO combination
drm/gpuvm: track/lock/validate external/evicted objects
drm/nouveau: make use of the GPUVM's shared dma-resv
drm/nouveau: use GPUVM common infrastructure

drivers/gpu/drm/drm_gpuvm.c | 1038 +++++++++++++++++++++--
drivers/gpu/drm/nouveau/nouveau_bo.c | 15 +-
drivers/gpu/drm/nouveau/nouveau_bo.h | 5 +
drivers/gpu/drm/nouveau/nouveau_exec.c | 52 +-
drivers/gpu/drm/nouveau/nouveau_exec.h | 4 -
drivers/gpu/drm/nouveau/nouveau_gem.c | 10 +-
drivers/gpu/drm/nouveau/nouveau_sched.h | 4 +-
drivers/gpu/drm/nouveau/nouveau_uvmm.c | 183 ++--
drivers/gpu/drm/nouveau/nouveau_uvmm.h | 1 -
include/drm/drm_gem.h | 32 +-
include/drm/drm_gpuvm.h | 471 +++++++++-
11 files changed, 1633 insertions(+), 182 deletions(-)


base-commit: c1698c73f4aaef2fd406da1c0a92e1c8f7b7780c
--
2.41.0


2023-10-08 23:33:50

by Danilo Krummrich

[permalink] [raw]
Subject: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects

Currently the DRM GPUVM offers common infrastructure to track GPU VA
allocations and mappings, generically connect GPU VA mappings to their
backing buffers and perform more complex mapping operations on the GPU VA
space.

However, there are more design patterns commonly used by drivers, which
can potentially be generalized in order to make the DRM GPUVM represent
a basis for GPU-VM implementations. In this context, this patch aims
at generalizing the following elements.

1) Provide a common dma-resv for GEM objects not being used outside of
this GPU-VM.

2) Provide tracking of external GEM objects (GEM objects which are
shared with other GPU-VMs).

3) Provide functions to efficiently lock all GEM objects dma-resv the
GPU-VM contains mappings of.

4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
of, such that validation of evicted GEM objects is accelerated.

5) Provide some convinience functions for common patterns.

Big thanks to Boris Brezillon for his help to figure out locking for
drivers updating the GPU VA space within the fence signalling path.

Suggested-by: Matthew Brost <[email protected]>
Signed-off-by: Danilo Krummrich <[email protected]>
---
drivers/gpu/drm/drm_gpuvm.c | 646 ++++++++++++++++++++++++++++++++++++
include/drm/drm_gpuvm.h | 246 ++++++++++++++
2 files changed, 892 insertions(+)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 28282283ddaf..6977bd30eca5 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -82,6 +82,21 @@
* &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
* particular combination. If not existent a new instance is created and linked
* to the &drm_gem_object.
+ *
+ * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
+ * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
+ * list are maintained in order to accelerate locking of dma-resv locks and
+ * validation of evicted objects bound in a &drm_gpuvm. For instance, all
+ * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
+ * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
+ * order to validate all evicted &drm_gem_objects. It is also possible to lock
+ * additional &drm_gem_objects by providing the corresponding parameters to
+ * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
+ * use of helper functions such as drm_gpuvm_prepare_range() or
+ * drm_gpuvm_prepare_objects().
+ *
+ * Every bound &drm_gem_object is treated as external object when its &dma_resv
+ * structure is different than the &drm_gpuvm's common &dma_resv structure.
*/

/**
@@ -429,6 +444,20 @@
* Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
* &drm_gem_object must be able to observe previous creations and destructions
* of &drm_gpuvm_bos in order to keep instances unique.
+ *
+ * The &drm_gpuvm's lists for keeping track of external and evicted objects are
+ * protected against concurrent insertion / removal and iteration internally.
+ *
+ * However, drivers still need ensure to protect concurrent calls to functions
+ * iterating those lists, namely drm_gpuvm_prepare_objects() and
+ * drm_gpuvm_validate().
+ *
+ * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
+ * that the corresponding &dma_resv locks are held in order to protect the
+ * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
+ * the corresponding lockdep checks are enabled. This is an optimization for
+ * drivers which are capable of taking the corresponding &dma_resv locks and
+ * hence do not require internal locking.
*/

/**
@@ -641,6 +670,195 @@
* }
*/

+/**
+ * get_next_vm_bo_from_list() - get the next vm_bo element
+ * @__gpuvm: The GPU VM
+ * @__list_name: The name of the list we're iterating on
+ * @__local_list: A pointer to the local list used to store already iterated items
+ * @__prev_vm_bo: The previous element we got from drm_gpuvm_get_next_cached_vm_bo()
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from
+ * the list, so list insertion deletion can happen concurrently.
+ *
+ * Elements popped from the original list are kept in a local list, so removal
+ * and is_empty checks can still happen while we're iterating the list.
+ */
+#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo) \
+ ({ \
+ struct drm_gpuvm_bo *__vm_bo = NULL; \
+ \
+ drm_gpuvm_bo_put(__prev_vm_bo); \
+ \
+ spin_lock(&(__gpuvm)->__list_name.lock); \
+ if (!(__gpuvm)->__list_name.local_list) \
+ (__gpuvm)->__list_name.local_list = __local_list; \
+ else \
+ WARN_ON((__gpuvm)->__list_name.local_list != __local_list); \
+ \
+ while (!list_empty(&(__gpuvm)->__list_name.list)) { \
+ __vm_bo = list_first_entry(&(__gpuvm)->__list_name.list, \
+ struct drm_gpuvm_bo, \
+ list.entry.__list_name); \
+ if (kref_get_unless_zero(&__vm_bo->kref)) { \
+ list_move_tail(&(__vm_bo)->list.entry.__list_name, \
+ __local_list); \
+ break; \
+ } else { \
+ list_del_init(&(__vm_bo)->list.entry.__list_name); \
+ __vm_bo = NULL; \
+ } \
+ } \
+ spin_unlock(&(__gpuvm)->__list_name.lock); \
+ \
+ __vm_bo; \
+ })
+
+/**
+ * for_each_vm_bo_in_list() - internal vm_bo list iterator
+ *
+ * This helper is here to provide lockless list iteration. Lockless as in, the
+ * iterator releases the lock immediately after picking the first element from the
+ * list, hence list insertion and deletion can happen concurrently.
+ *
+ * It is not allowed to re-assign the vm_bo pointer from inside this loop.
+ *
+ * Typical use:
+ *
+ * struct drm_gpuvm_bo *vm_bo;
+ * LIST_HEAD(my_local_list);
+ *
+ * ret = 0;
+ * for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
+ * ret = do_something_with_vm_bo(..., vm_bo);
+ * if (ret)
+ * break;
+ * }
+ * drm_gpuvm_bo_put(vm_bo);
+ * restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
+ *
+ *
+ * Only used for internal list iterations, not meant to be exposed to the outside
+ * world.
+ */
+#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo) \
+ for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name, \
+ __local_list, NULL); \
+ __vm_bo; \
+ __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name, \
+ __local_list, __vm_bo))
+
+static void
+__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+ struct list_head *list, struct list_head **local_list)
+{
+ /* Merge back the two lists, moving local list elements to the
+ * head to preserve previous ordering, in case it matters.
+ */
+ spin_lock(lock);
+ if (*local_list) {
+ list_splice(*local_list, list);
+ *local_list = NULL;
+ }
+ spin_unlock(lock);
+}
+
+/**
+ * restore_vm_bo_list() - move vm_bo elements back to their original list
+ * @__gpuvm: The GPU VM
+ * @__list_name: The name of the list we're iterating on
+ *
+ * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
+ * to restore the original state and let new iterations take place.
+ */
+#define restore_vm_bo_list(__gpuvm, __list_name) \
+ __restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock, \
+ &(__gpuvm)->__list_name.list, \
+ &(__gpuvm)->__list_name.local_list)
+
+static void
+cond_spin_lock(spinlock_t *lock, bool cond)
+{
+ if (cond)
+ spin_lock(lock);
+}
+
+static void
+cond_spin_unlock(spinlock_t *lock, bool cond)
+{
+ if (cond)
+ spin_unlock(lock);
+}
+
+static void
+__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+ struct list_head *entry, struct list_head *list)
+{
+ cond_spin_lock(lock, !!lock);
+ if (list_empty(entry))
+ list_add_tail(entry, list);
+ cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Inserts the given @__vm_bo into the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock) \
+ __drm_gpuvm_bo_list_add((__vm_bo)->vm, \
+ __lock ? &(__vm_bo)->vm->__list_name.lock : \
+ NULL, \
+ &(__vm_bo)->list.entry.__list_name, \
+ &(__vm_bo)->vm->__list_name.list)
+
+static void
+__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
+ struct list_head *entry, bool init)
+{
+ cond_spin_lock(lock, !!lock);
+ if (init) {
+ if (!list_empty(entry))
+ list_del_init(entry);
+ } else {
+ list_del(entry);
+ }
+ cond_spin_unlock(lock, !!lock);
+}
+
+/**
+ * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock) \
+ __drm_gpuvm_bo_list_del((__vm_bo)->vm, \
+ __lock ? &(__vm_bo)->vm->__list_name.lock : \
+ NULL, \
+ &(__vm_bo)->list.entry.__list_name, \
+ true)
+
+/**
+ * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
+ * @__vm_bo: the &drm_gpuvm_bo
+ * @__list_name: the name of the list to insert into
+ * @__lock: whether to lock with the internal spinlock
+ *
+ * Removes the given @__vm_bo from the list specified by @__list_name.
+ */
+#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock) \
+ __drm_gpuvm_bo_list_del((__vm_bo)->vm, \
+ __lock ? &(__vm_bo)->vm->__list_name.lock : \
+ NULL, \
+ &(__vm_bo)->list.entry.__list_name, \
+ false)
+
#define to_drm_gpuva(__node) container_of((__node), struct drm_gpuva, rb.node)

#define GPUVA_START(node) ((node)->va.addr)
@@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
gpuvm->rb.tree = RB_ROOT_CACHED;
INIT_LIST_HEAD(&gpuvm->rb.list);

+ INIT_LIST_HEAD(&gpuvm->extobj.list);
+ spin_lock_init(&gpuvm->extobj.lock);
+
+ INIT_LIST_HEAD(&gpuvm->evict.list);
+ spin_lock_init(&gpuvm->evict.lock);
+
drm_gpuvm_check_overflow(start_offset, range);
gpuvm->mm_start = start_offset;
gpuvm->mm_range = range;
@@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
"GPUVA tree is not empty, potentially leaking memory.\n");

+ WARN(!list_empty(&gpuvm->extobj.list), "Extobj list should be empty.\n");
+ WARN(!list_empty(&gpuvm->evict.list), "Evict list should be empty.\n");
+
drm_gem_object_put(gpuvm->r_obj);
}
EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);

+static int
+__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ unsigned int num_fences)
+{
+ struct drm_gpuvm_bo *vm_bo;
+ LIST_HEAD(extobjs);
+ int ret = 0;
+
+ for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
+ ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+ if (ret)
+ break;
+ }
+ /* Drop ref in case we break out of the loop. */
+ drm_gpuvm_bo_put(vm_bo);
+ restore_vm_bo_list(gpuvm, extobj);
+
+ return ret;
+}
+
+static int
+drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ unsigned int num_fences)
+{
+ struct drm_gpuvm_bo *vm_bo;
+ int ret = 0;
+
+ drm_gpuvm_resv_assert_held(gpuvm);
+ list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
+ ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
+ if (ret)
+ break;
+
+ if (vm_bo->evicted)
+ drm_gpuvm_bo_list_add(vm_bo, evict, false);
+ }
+
+ return ret;
+}
+
+/**
+ * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Note: This function is safe against concurrent insertion and removal of
+ * external objects, however it is not safe against concurrent usage itself.
+ *
+ * Drivers need to make sure to protect this case with either an outer VM lock
+ * or by calling drm_gpuvm_prepare_vm() before this function within the
+ * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
+ * mutual exclusion.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ unsigned int num_fences)
+{
+ if (drm_gpuvm_resv_protected(gpuvm))
+ return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
+ num_fences);
+ else
+ return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
+
+/**
+ * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec locking context
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
+ * and @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
+ u64 addr, u64 range, unsigned int num_fences)
+{
+ struct drm_gpuva *va;
+ u64 end = addr + range;
+ int ret;
+
+ drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
+ struct drm_gem_object *obj = va->gem.obj;
+
+ ret = drm_exec_prepare_obj(exec, obj, num_fences);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
+
+/**
+ * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @num_fences: the amount of &dma_fences to reserve
+ * @interruptible: sleep interruptible if waiting
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given
+ * &drm_gpuvm contains mappings of.
+ *
+ * Addionally, when calling this function with struct drm_gpuvm_exec::extra
+ * being set the driver receives the given @fn callback to lock additional
+ * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
+ * would call drm_exec_prepare_obj() from within this callback.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
+ unsigned int num_fences,
+ bool interruptible)
+{
+ struct drm_gpuvm *gpuvm = vm_exec->vm;
+ struct drm_exec *exec = &vm_exec->exec;
+ uint32_t flags;
+ int ret;
+
+ flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
+ DRM_EXEC_IGNORE_DUPLICATES;
+
+ drm_exec_init(exec, flags);
+
+ drm_exec_until_all_locked(exec) {
+ ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
+ drm_exec_retry_on_contention(exec);
+ if (ret)
+ goto err;
+
+ ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
+ drm_exec_retry_on_contention(exec);
+ if (ret)
+ goto err;
+
+ if (vm_exec->extra.fn) {
+ ret = vm_exec->extra.fn(vm_exec, num_fences);
+ drm_exec_retry_on_contention(exec);
+ if (ret)
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ drm_exec_fini(exec);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
+
+static int
+fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int num_fences)
+{
+ struct {
+ struct drm_gem_object **objs;
+ unsigned int num_objs;
+ } *args = vm_exec->extra.priv;
+
+ return drm_exec_prepare_array(&vm_exec->exec, args->objs,
+ args->num_objs, num_fences);
+}
+
+/**
+ * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @objs: additional &drm_gem_objects to lock
+ * @num_objs: the number of additional &drm_gem_objects to lock
+ * @num_fences: the amount of &dma_fences to reserve
+ * @interruptible: sleep interruptible if waiting
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
+ * contains mappings of, plus the ones given through @objs.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+ struct drm_gem_object **objs,
+ unsigned int num_objs,
+ unsigned int num_fences,
+ bool interruptible)
+{
+ struct {
+ struct drm_gem_object **objs;
+ unsigned int num_objs;
+ } args;
+
+ args.objs = objs;
+ args.num_objs = num_objs;
+
+ vm_exec->extra.fn = fn_lock_array;
+ vm_exec->extra.priv = &args;
+
+ return drm_gpuvm_exec_lock(vm_exec, num_fences, interruptible);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
+
+/**
+ * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @addr: the start address within the VA space
+ * @range: the range to iterate within the VA space
+ * @num_fences: the amount of &dma_fences to reserve
+ * @interruptible: sleep interruptible if waiting
+ *
+ * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
+ * @addr + @range.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+ u64 addr, u64 range,
+ unsigned int num_fences,
+ bool interruptible)
+{
+ struct drm_gpuvm *gpuvm = vm_exec->vm;
+ struct drm_exec *exec = &vm_exec->exec;
+ uint32_t flags;
+ int ret;
+
+ flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
+ DRM_EXEC_IGNORE_DUPLICATES;
+
+ drm_exec_init(exec, flags);
+
+ drm_exec_until_all_locked(exec) {
+ ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
+ num_fences);
+ drm_exec_retry_on_contention(exec);
+ if (ret)
+ goto err;
+ }
+
+ return ret;
+
+err:
+ drm_exec_fini(exec);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
+
+static int
+__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+ const struct drm_gpuvm_ops *ops = gpuvm->ops;
+ struct drm_gpuvm_bo *vm_bo;
+ LIST_HEAD(evict);
+ int ret = 0;
+
+ for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
+ ret = ops->vm_bo_validate(vm_bo, exec);
+ if (ret)
+ break;
+ }
+ /* Drop ref in case we break out of the loop. */
+ drm_gpuvm_bo_put(vm_bo);
+ restore_vm_bo_list(gpuvm, evict);
+
+ return ret;
+}
+
+static int
+drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+ const struct drm_gpuvm_ops *ops = gpuvm->ops;
+ struct drm_gpuvm_bo *vm_bo, *next;
+ int ret = 0;
+
+ drm_gpuvm_resv_assert_held(gpuvm);
+
+ /* Iterate list safely, drivers typically remove the current entry from
+ * their drm_gpuvm_ops::vm_bo_validate callback. Drivers might also
+ * re-add the entry on failure; this is safe since on failure we break
+ * out of the loop.
+ */
+ list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
+ list.entry.evict) {
+ ret = ops->vm_bo_validate(vm_bo, exec);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * drm_gpuvm_validate() - validate all BOs marked as evicted
+ * @gpuvm: the &drm_gpuvm to validate evicted BOs
+ * @exec: the &drm_exec instance used for locking the GPUVM
+ *
+ * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
+ * objects being mapped in the given &drm_gpuvm.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+int
+drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
+{
+ const struct drm_gpuvm_ops *ops = gpuvm->ops;
+
+ if (unlikely(!ops || !ops->vm_bo_validate))
+ return -ENOTSUPP;
+
+ if (drm_gpuvm_resv_protected(gpuvm))
+ return drm_gpuvm_validate_locked(gpuvm, exec);
+ else
+ return __drm_gpuvm_validate(gpuvm, exec);
+
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
+
+/**
+ * drm_gpuvm_resv_add_fence - add fence to private and all extobj
+ * dma-resv
+ * @gpuvm: the &drm_gpuvm to add a fence to
+ * @exec: the &drm_exec locking context
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ */
+void
+drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ struct dma_fence *fence,
+ enum dma_resv_usage private_usage,
+ enum dma_resv_usage extobj_usage)
+{
+ struct drm_gem_object *obj;
+ unsigned long index;
+
+ drm_exec_for_each_locked_object(exec, index, obj) {
+ dma_resv_assert_held(obj->resv);
+ dma_resv_add_fence(obj->resv, fence,
+ drm_gpuvm_is_extobj(gpuvm, obj) ?
+ private_usage : extobj_usage);
+ }
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
+
/**
* drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
* @gpuvm: The &drm_gpuvm the @obj is mapped in.
@@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
INIT_LIST_HEAD(&vm_bo->list.gpuva);
INIT_LIST_HEAD(&vm_bo->list.entry.gem);

+ INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
+ INIT_LIST_HEAD(&vm_bo->list.entry.evict);
+
drm_gem_object_get(obj);

return vm_bo;
@@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
if (!lock)
drm_gpuvm_resv_assert_held(gpuvm);

+ drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
+ drm_gpuvm_bo_list_del(vm_bo, evict, lock);
+
list_del(&vm_bo->list.entry.gem);

drm_gem_object_put(obj);
@@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
}
EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);

+/**
+ * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
+ * extobj list
+ * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
+ *
+ * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
+ * already and if the corresponding &drm_gem_object is an external object,
+ * actually.
+ */
+void
+drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
+{
+ struct drm_gpuvm *gpuvm = vm_bo->vm;
+ bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+ if (!lock)
+ drm_gpuvm_resv_assert_held(gpuvm);
+
+ if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
+ drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
+
+/**
+ * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
+ * evicted list
+ * @vm_bo: the &drm_gpuvm_bo to add or remove
+ * @evict: indicates whether the object is evicted
+ *
+ * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
+ */
+void
+drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
+{
+ struct drm_gpuvm *gpuvm = vm_bo->vm;
+ struct drm_gem_object *obj = vm_bo->obj;
+ bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+ dma_resv_assert_held(obj->resv);
+
+ if (drm_gpuvm_is_extobj(gpuvm, obj)) {
+ vm_bo->evicted = evict;
+
+ if (!lock)
+ return;
+ }
+
+ if (evict)
+ drm_gpuvm_bo_list_add(vm_bo, evict, lock);
+ else
+ drm_gpuvm_bo_list_del_init(vm_bo, evict, lock);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_evict);
+
static int
__drm_gpuva_insert(struct drm_gpuvm *gpuvm,
struct drm_gpuva *va)
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index ddb0b8d323cf..eadc3ea5bf57 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -31,6 +31,7 @@
#include <linux/types.h>

#include <drm/drm_gem.h>
+#include <drm/drm_exec.h>

struct drm_gpuvm;
struct drm_gpuvm_bo;
@@ -272,6 +273,50 @@ struct drm_gpuvm {
* @r_obj: Root GEM object; representing the GPUVM's common &dma_resv.
*/
struct drm_gem_object *r_obj;
+
+ /**
+ * @extobj: structure holding the extobj list
+ */
+ struct {
+ /**
+ * @list: &list_head storing &drm_gpuvm_bos serving as
+ * external object
+ */
+ struct list_head list;
+
+ /**
+ * @local_list: pointer to the local list temporarily storing
+ * entries from the external object list
+ */
+ struct list_head *local_list;
+
+ /**
+ * @lock: spinlock to protect the extobj list
+ */
+ spinlock_t lock;
+ } extobj;
+
+ /**
+ * @evict: structure holding the evict list and evict list lock
+ */
+ struct {
+ /**
+ * @list: &list_head storing &drm_gpuvm_bos currently being
+ * evicted
+ */
+ struct list_head list;
+
+ /**
+ * @local_list: pointer to the local list temporarily storing
+ * entries from the evicted object list
+ */
+ struct list_head *local_list;
+
+ /**
+ * @lock: spinlock to protect the evict list
+ */
+ spinlock_t lock;
+ } evict;
};

void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
@@ -329,6 +374,22 @@ drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
#define drm_gpuvm_resv_assert_held(gpuvm__) \
dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))

+/**
+ * drm_gpuvm_is_extobj() - indicates whether the given &drm_gem_object is an
+ * external object
+ * @gpuvm: the &drm_gpuvm to check
+ * @obj: the &drm_gem_object to check
+ *
+ * Returns: true if the &drm_gem_object &dma_resv differs from the
+ * &drm_gpuvms &dma_resv, false otherwise
+ */
+static inline bool
+drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj)
+{
+ return obj && obj->resv != drm_gpuvm_resv(gpuvm);
+}
+
static inline struct drm_gpuva *
__drm_gpuva_next(struct drm_gpuva *va)
{
@@ -407,6 +468,140 @@ __drm_gpuva_next(struct drm_gpuva *va)
#define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)

+/**
+ * struct drm_gpuvm_exec - &drm_gpuvm abstraction of &drm_exec
+ *
+ * This structure should be created on the stack as &drm_exec should be.
+ *
+ * Optionally, @extra can be set in order to lock additional &drm_gem_objects.
+ */
+struct drm_gpuvm_exec {
+ /**
+ * @exec: the &drm_exec structure
+ */
+ struct drm_exec exec;
+
+ /**
+ * @vm: the &drm_gpuvm to lock its DMA reservations
+ */
+ struct drm_gpuvm *vm;
+
+ /**
+ * @extra: Callback and corresponding private data for the driver to
+ * lock arbitrary additional &drm_gem_objects.
+ */
+ struct {
+ /**
+ * @fn: The driver callback to lock additional &drm_gem_objects.
+ */
+ int (*fn)(struct drm_gpuvm_exec *vm_exec,
+ unsigned int num_fences);
+
+ /**
+ * @priv: driver private data for the @fn callback
+ */
+ void *priv;
+ } extra;
+};
+
+/**
+ * drm_gpuvm_prepare_vm() - prepare the GPUVMs common dma-resv
+ * @gpuvm: the &drm_gpuvm
+ * @exec: the &drm_exec context
+ * @num_fences: the amount of &dma_fences to reserve
+ *
+ * Calls drm_exec_prepare_obj() for the GPUVMs dummy &drm_gem_object.
+ *
+ * Using this function directly, it is the drivers responsibility to call
+ * drm_exec_init() and drm_exec_fini() accordingly.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline int
+drm_gpuvm_prepare_vm(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ unsigned int num_fences)
+{
+ return drm_exec_prepare_obj(exec, gpuvm->r_obj, num_fences);
+}
+
+int drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ unsigned int num_fences);
+
+int drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ u64 addr, u64 range,
+ unsigned int num_fences);
+
+int drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
+ unsigned int num_fences,
+ bool interruptible);
+
+int drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
+ struct drm_gem_object **objs,
+ unsigned int num_objs,
+ unsigned int num_fences,
+ bool interruptible);
+
+int drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
+ u64 addr, u64 range,
+ unsigned int num_fences,
+ bool interruptible);
+
+/**
+ * drm_gpuvm_exec_unlock() - lock all dma-resv of all assoiciated BOs
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * Releases all dma-resv locks of all &drm_gem_objects previously acquired
+ * through drm_gpuvm_exec_lock() or its variants.
+ *
+ * Returns: 0 on success, negative error code on failure.
+ */
+static inline void
+drm_gpuvm_exec_unlock(struct drm_gpuvm_exec *vm_exec)
+{
+ drm_exec_fini(&vm_exec->exec);
+}
+
+int drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec);
+void drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
+ struct drm_exec *exec,
+ struct dma_fence *fence,
+ enum dma_resv_usage private_usage,
+ enum dma_resv_usage extobj_usage);
+
+/**
+ * drm_gpuvm_exec_resv_add_fence()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ * @fence: fence to add
+ * @private_usage: private dma-resv usage
+ * @extobj_usage: extobj dma-resv usage
+ *
+ * See drm_gpuvm_resv_add_fence().
+ */
+static inline void
+drm_gpuvm_exec_resv_add_fence(struct drm_gpuvm_exec *vm_exec,
+ struct dma_fence *fence,
+ enum dma_resv_usage private_usage,
+ enum dma_resv_usage extobj_usage)
+{
+ drm_gpuvm_resv_add_fence(vm_exec->vm, &vm_exec->exec, fence,
+ private_usage, extobj_usage);
+}
+
+/**
+ * drm_gpuvm_exec_validate()
+ * @vm_exec: the &drm_gpuvm_exec wrapper
+ *
+ * See drm_gpuvm_validate().
+ */
+static inline int
+drm_gpuvm_exec_validate(struct drm_gpuvm_exec *vm_exec)
+{
+ return drm_gpuvm_validate(vm_exec->vm, &vm_exec->exec);
+}
+
/**
* struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
* &drm_gem_object combination
@@ -435,6 +630,12 @@ struct drm_gpuvm_bo {
*/
struct drm_gem_object *obj;

+ /**
+ * @evicted: Indicates whether the &drm_gem_object is evicted; field
+ * protected by the &drm_gem_object's dma-resv lock.
+ */
+ bool evicted;
+
/**
* @kref: The reference count for this &drm_gpuvm_bo.
*/
@@ -459,6 +660,18 @@ struct drm_gpuvm_bo {
* gpuva list.
*/
struct list_head gem;
+
+ /**
+ * @evict: List entry to attach to the &drm_gpuvms
+ * extobj list.
+ */
+ struct list_head extobj;
+
+ /**
+ * @evict: List entry to attach to the &drm_gpuvms evict
+ * list.
+ */
+ struct list_head evict;
} entry;
} list;
};
@@ -493,6 +706,27 @@ struct drm_gpuvm_bo *
drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
struct drm_gem_object *obj);

+void drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict);
+
+/**
+ * drm_gpuvm_bo_gem_evict()
+ * @obj: the &drm_gem_object
+ * @evict: indicates whether @obj is evicted
+ *
+ * See drm_gpuvm_bo_evict().
+ */
+static inline void
+drm_gpuvm_bo_gem_evict(struct drm_gem_object *obj, bool evict)
+{
+ struct drm_gpuvm_bo *vm_bo;
+
+ drm_gem_gpuva_assert_lock_held(obj);
+ drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+ drm_gpuvm_bo_evict(vm_bo, evict);
+}
+
+void drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo);
+
/**
* drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
* @va__: &drm_gpuva structure to assign to in each iteration step
@@ -855,6 +1089,18 @@ struct drm_gpuvm_ops {
*/
void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);

+ /**
+ * @vm_bo_validate: called from drm_gpuvm_validate()
+ *
+ * Drivers receive this callback for every evicted &drm_gem_object being
+ * mapped in the corresponding &drm_gpuvm.
+ *
+ * Typically, drivers would call their driver specific variant of
+ * ttm_bo_validate() from within this callback.
+ */
+ int (*vm_bo_validate)(struct drm_gpuvm_bo *vm_bo,
+ struct drm_exec *exec);
+
/**
* @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
* mapping once all previous steps were completed
--
2.41.0

2023-10-08 23:33:57

by Danilo Krummrich

[permalink] [raw]
Subject: [PATCH drm-misc-next v6 6/6] drm/nouveau: use GPUVM common infrastructure

GPUVM provides common infrastructure to track external and evicted GEM
objects as well as locking and validation helpers.

Especially external and evicted object tracking is a huge improvement
compared to the current brute force approach of iterating all mappings
in order to lock and validate the GPUVM's GEM objects. Hence, make us of
it.

Signed-off-by: Danilo Krummrich <[email protected]>
---
drivers/gpu/drm/nouveau/nouveau_bo.c | 4 +-
drivers/gpu/drm/nouveau/nouveau_exec.c | 52 +++----------
drivers/gpu/drm/nouveau/nouveau_exec.h | 4 -
drivers/gpu/drm/nouveau/nouveau_sched.h | 4 +-
drivers/gpu/drm/nouveau/nouveau_uvmm.c | 99 ++++++++++++++++---------
5 files changed, 80 insertions(+), 83 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index dbb3facfd23d..62371fe39e96 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -1067,17 +1067,18 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
{
struct nouveau_drm *drm = nouveau_bdev(bo->bdev);
struct nouveau_bo *nvbo = nouveau_bo(bo);
+ struct drm_gem_object *obj = &bo->base;
struct ttm_resource *old_reg = bo->resource;
struct nouveau_drm_tile *new_tile = NULL;
int ret = 0;

-
if (new_reg->mem_type == TTM_PL_TT) {
ret = nouveau_ttm_tt_bind(bo->bdev, bo->ttm, new_reg);
if (ret)
return ret;
}

+ drm_gpuvm_bo_gem_evict(obj, evict);
nouveau_bo_move_ntfy(bo, new_reg);
ret = ttm_bo_wait_ctx(bo, ctx);
if (ret)
@@ -1142,6 +1143,7 @@ nouveau_bo_move(struct ttm_buffer_object *bo, bool evict,
out_ntfy:
if (ret) {
nouveau_bo_move_ntfy(bo, bo->resource);
+ drm_gpuvm_bo_gem_evict(obj, !evict);
}
return ret;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.c b/drivers/gpu/drm/nouveau/nouveau_exec.c
index b4239af29e5a..ba6913a3efb6 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.c
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.c
@@ -1,7 +1,5 @@
// SPDX-License-Identifier: MIT

-#include <drm/drm_exec.h>
-
#include "nouveau_drv.h"
#include "nouveau_gem.h"
#include "nouveau_mem.h"
@@ -91,9 +89,6 @@ nouveau_exec_job_submit(struct nouveau_job *job)
struct nouveau_exec_job *exec_job = to_nouveau_exec_job(job);
struct nouveau_cli *cli = job->cli;
struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(cli);
- struct drm_exec *exec = &job->exec;
- struct drm_gem_object *obj;
- unsigned long index;
int ret;

ret = nouveau_fence_new(&exec_job->fence);
@@ -101,52 +96,29 @@ nouveau_exec_job_submit(struct nouveau_job *job)
return ret;

nouveau_uvmm_lock(uvmm);
- drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
- DRM_EXEC_IGNORE_DUPLICATES);
- drm_exec_until_all_locked(exec) {
- struct drm_gpuva *va;
-
- drm_gpuvm_for_each_va(va, &uvmm->base) {
- if (unlikely(va == &uvmm->base.kernel_alloc_node))
- continue;
-
- ret = drm_exec_prepare_obj(exec, va->gem.obj, 1);
- drm_exec_retry_on_contention(exec);
- if (ret)
- goto err_uvmm_unlock;
- }
+ job->vm_exec.vm = &uvmm->base;
+ ret = drm_gpuvm_exec_lock(&job->vm_exec, 1, false);
+ if (ret) {
+ nouveau_uvmm_unlock(uvmm);
+ return ret;
}
nouveau_uvmm_unlock(uvmm);

- drm_exec_for_each_locked_object(exec, index, obj) {
- struct nouveau_bo *nvbo = nouveau_gem_object(obj);
-
- ret = nouveau_bo_validate(nvbo, true, false);
- if (ret)
- goto err_exec_fini;
+ ret = drm_gpuvm_exec_validate(&job->vm_exec);
+ if (ret) {
+ drm_gpuvm_exec_unlock(&job->vm_exec);
+ return ret;
}

return 0;
-
-err_uvmm_unlock:
- nouveau_uvmm_unlock(uvmm);
-err_exec_fini:
- drm_exec_fini(exec);
- return ret;
-
}

static void
nouveau_exec_job_armed_submit(struct nouveau_job *job)
{
- struct drm_exec *exec = &job->exec;
- struct drm_gem_object *obj;
- unsigned long index;
-
- drm_exec_for_each_locked_object(exec, index, obj)
- dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
- drm_exec_fini(exec);
+ drm_gpuvm_exec_resv_add_fence(&job->vm_exec, job->done_fence,
+ job->resv_usage, job->resv_usage);
+ drm_gpuvm_exec_unlock(&job->vm_exec);
}

static struct dma_fence *
diff --git a/drivers/gpu/drm/nouveau/nouveau_exec.h b/drivers/gpu/drm/nouveau/nouveau_exec.h
index 778cacd90f65..b815de2428f3 100644
--- a/drivers/gpu/drm/nouveau/nouveau_exec.h
+++ b/drivers/gpu/drm/nouveau/nouveau_exec.h
@@ -3,16 +3,12 @@
#ifndef __NOUVEAU_EXEC_H__
#define __NOUVEAU_EXEC_H__

-#include <drm/drm_exec.h>
-
#include "nouveau_drv.h"
#include "nouveau_sched.h"

struct nouveau_exec_job_args {
struct drm_file *file_priv;
struct nouveau_sched_entity *sched_entity;
-
- struct drm_exec exec;
struct nouveau_channel *chan;

struct {
diff --git a/drivers/gpu/drm/nouveau/nouveau_sched.h b/drivers/gpu/drm/nouveau/nouveau_sched.h
index 27ac19792597..54379af6f925 100644
--- a/drivers/gpu/drm/nouveau/nouveau_sched.h
+++ b/drivers/gpu/drm/nouveau/nouveau_sched.h
@@ -5,7 +5,7 @@

#include <linux/types.h>

-#include <drm/drm_exec.h>
+#include <drm/drm_gpuvm.h>
#include <drm/gpu_scheduler.h>

#include "nouveau_drv.h"
@@ -54,7 +54,7 @@ struct nouveau_job {
struct drm_file *file_priv;
struct nouveau_cli *cli;

- struct drm_exec exec;
+ struct drm_gpuvm_exec vm_exec;
enum dma_resv_usage resv_usage;
struct dma_fence *done_fence;

diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 436b0ac74ffe..ba0f7fcb6f7c 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -438,8 +438,9 @@ nouveau_uvma_region_complete(struct nouveau_uvma_region *reg)
static void
op_map_prepare_unwind(struct nouveau_uvma *uvma)
{
+ struct drm_gpuva *va = &uvma->va;
nouveau_uvma_gem_put(uvma);
- drm_gpuva_remove(&uvma->va);
+ drm_gpuva_remove(va);
nouveau_uvma_free(uvma);
}

@@ -468,6 +469,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
break;
case DRM_GPUVA_OP_REMAP: {
struct drm_gpuva_op_remap *r = &op->remap;
+ struct drm_gpuva *va = r->unmap->va;

if (r->next)
op_map_prepare_unwind(new->next);
@@ -475,7 +477,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
if (r->prev)
op_map_prepare_unwind(new->prev);

- op_unmap_prepare_unwind(r->unmap->va);
+ op_unmap_prepare_unwind(va);
break;
}
case DRM_GPUVA_OP_UNMAP:
@@ -634,6 +636,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm,
goto unwind;
}
}
+
break;
}
case DRM_GPUVA_OP_REMAP: {
@@ -1146,13 +1149,44 @@ bind_link_gpuvas(struct bind_job_op *bop)
}
}

+static int
+bind_lock_extra(struct drm_gpuvm_exec *vm_exec, unsigned int num_fences)
+{
+ struct nouveau_uvmm_bind_job *bind_job = vm_exec->extra.priv;
+ struct drm_exec *exec = &vm_exec->exec;
+ struct bind_job_op *op;
+ int ret;
+
+ list_for_each_op(op, &bind_job->ops) {
+ struct drm_gpuva_op *va_op;
+
+ if (IS_ERR_OR_NULL(op->ops))
+ continue;
+
+ drm_gpuva_for_each_op(va_op, op->ops) {
+ struct drm_gem_object *obj = op_gem_obj(va_op);
+
+ if (unlikely(!obj))
+ continue;
+
+ if (va_op->op != DRM_GPUVA_OP_UNMAP)
+ continue;
+
+ ret = drm_exec_prepare_obj(exec, obj, num_fences);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static int
nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
{
struct nouveau_uvmm *uvmm = nouveau_cli_uvmm(job->cli);
struct nouveau_uvmm_bind_job *bind_job = to_uvmm_bind_job(job);
struct nouveau_sched_entity *entity = job->entity;
- struct drm_exec *exec = &job->exec;
struct bind_job_op *op;
int ret;

@@ -1170,6 +1204,8 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
dma_resv_unlock(obj->resv);
if (IS_ERR(op->vm_bo))
return PTR_ERR(op->vm_bo);
+
+ drm_gpuvm_bo_extobj_add(op->vm_bo);
}

ret = bind_validate_op(job, op);
@@ -1192,6 +1228,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
* unwind all GPU VA space changes on failure.
*/
nouveau_uvmm_lock(uvmm);
+
list_for_each_op(op, &bind_job->ops) {
switch (op->op) {
case OP_MAP_SPARSE:
@@ -1303,30 +1340,13 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
}
}

- drm_exec_init(exec, DRM_EXEC_INTERRUPTIBLE_WAIT |
- DRM_EXEC_IGNORE_DUPLICATES);
- drm_exec_until_all_locked(exec) {
- list_for_each_op(op, &bind_job->ops) {
- struct drm_gpuva_op *va_op;
+ job->vm_exec.vm = &uvmm->base;
+ job->vm_exec.extra.fn = bind_lock_extra;
+ job->vm_exec.extra.priv = bind_job;

- if (IS_ERR_OR_NULL(op->ops))
- continue;
-
- drm_gpuva_for_each_op(va_op, op->ops) {
- struct drm_gem_object *obj = op_gem_obj(va_op);
-
- if (unlikely(!obj))
- continue;
-
- ret = drm_exec_prepare_obj(exec, obj, 1);
- drm_exec_retry_on_contention(exec);
- if (ret) {
- op = list_last_op(&bind_job->ops);
- goto unwind;
- }
- }
- }
- }
+ ret = drm_gpuvm_exec_lock(&job->vm_exec, 1, false);
+ if (ret)
+ goto unwind_continue;

list_for_each_op(op, &bind_job->ops) {
struct drm_gpuva_op *va_op;
@@ -1426,21 +1446,16 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
}

nouveau_uvmm_unlock(uvmm);
- drm_exec_fini(exec);
+ drm_gpuvm_exec_unlock(&job->vm_exec);
return ret;
}

static void
nouveau_uvmm_bind_job_armed_submit(struct nouveau_job *job)
{
- struct drm_exec *exec = &job->exec;
- struct drm_gem_object *obj;
- unsigned long index;
-
- drm_exec_for_each_locked_object(exec, index, obj)
- dma_resv_add_fence(obj->resv, job->done_fence, job->resv_usage);
-
- drm_exec_fini(exec);
+ drm_gpuvm_exec_resv_add_fence(&job->vm_exec, job->done_fence,
+ job->resv_usage, job->resv_usage);
+ drm_gpuvm_exec_unlock(&job->vm_exec);
}

static struct dma_fence *
@@ -1832,6 +1847,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
}
}

+static int
+nouveau_uvmm_bo_validate(struct drm_gpuvm_bo *vm_bo, struct drm_exec *exec)
+{
+ struct nouveau_bo *nvbo = nouveau_gem_object(vm_bo->obj);
+
+ return nouveau_bo_validate(nvbo, true, false);
+}
+
+static const struct drm_gpuvm_ops gpuvm_ops = {
+ .vm_bo_validate = nouveau_uvmm_bo_validate,
+};
+
int
nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
u64 kernel_managed_addr, u64 kernel_managed_size)
@@ -1874,7 +1901,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
NOUVEAU_VA_SPACE_START,
NOUVEAU_VA_SPACE_END,
kernel_managed_addr, kernel_managed_size,
- NULL);
+ &gpuvm_ops);
/* GPUVM takes care from here on. */
drm_gem_object_put(r_obj);

--
2.41.0

2023-10-08 23:34:00

by Danilo Krummrich

[permalink] [raw]
Subject: [PATCH drm-misc-next v6 1/6] drm/gpuvm: add common dma-resv per struct drm_gpuvm

Provide a common dma-resv for GEM objects not being used outside of this
GPU-VM. This is used in a subsequent patch to generalize dma-resv,
external and evicted object handling and GEM validation.

Signed-off-by: Danilo Krummrich <[email protected]>
---
drivers/gpu/drm/drm_gpuvm.c | 56 +++++++++++++++++++++++++-
drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 +++++-
include/drm/drm_gpuvm.h | 35 +++++++++++++++-
3 files changed, 99 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 02ecb45a2544..ebda9d594165 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -61,6 +61,15 @@
* contained within struct drm_gpuva already. Hence, for inserting &drm_gpuva
* entries from within dma-fence signalling critical sections it is enough to
* pre-allocate the &drm_gpuva structures.
+ *
+ * &drm_gem_objects which are private to a single VM can share a common
+ * &dma_resv in order to improve locking efficiency (e.g. with &drm_exec).
+ * For this purpose drivers must pass a &drm_gem_object to drm_gpuvm_init(), in
+ * the following called 'root object', which serves as the container of the
+ * GPUVM's shared &dma_resv. This root object can be a driver specific
+ * &drm_gem_object, such as the &drm_gem_object containing the root page table,
+ * but it can also be a 'dummy' object, which can be allocated with
+ * drm_gpuvm_root_object_alloc().
*/

/**
@@ -652,9 +661,47 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
!drm_gpuvm_in_kernel_node(gpuvm, addr, range);
}

+static void
+drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
+{
+ drm_gem_object_release(obj);
+ kfree(obj);
+}
+
+static const struct drm_gem_object_funcs drm_gpuvm_object_funcs = {
+ .free = drm_gpuvm_gem_object_free,
+};
+
+/**
+ * drm_gpuvm_root_object_alloc() - allocate a dummy &drm_gem_object
+ * @drm: the drivers &drm_device
+ *
+ * Allocates a dummy &drm_gem_object which can be passed to drm_gpuvm_init() in
+ * order to serve as root GEM object providing the &drm_resv shared across
+ * &drm_gem_objects local to a single GPUVM.
+ *
+ * Returns: the &drm_gem_object on success, NULL on failure
+ */
+struct drm_gem_object *
+drm_gpuvm_root_object_alloc(struct drm_device *drm)
+{
+ struct drm_gem_object *obj;
+
+ obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+ if (!obj)
+ return NULL;
+
+ obj->funcs = &drm_gpuvm_object_funcs;
+ drm_gem_private_object_init(drm, obj, 0);
+
+ return obj;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
+
/**
* drm_gpuvm_init() - initialize a &drm_gpuvm
* @gpuvm: pointer to the &drm_gpuvm to initialize
+ * @r_obj: the root &drm_gem_object providing the GPUVM's common &dma_resv
* @name: the name of the GPU VA space
* @start_offset: the start offset of the GPU VA space
* @range: the size of the GPU VA space
@@ -668,7 +715,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
* &name is expected to be managed by the surrounding driver structures.
*/
void
-drm_gpuvm_init(struct drm_gpuvm *gpuvm,
+drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
const char *name,
u64 start_offset, u64 range,
u64 reserve_offset, u64 reserve_range,
@@ -683,6 +730,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,

gpuvm->name = name ? name : "unknown";
gpuvm->ops = ops;
+ gpuvm->r_obj = r_obj;
+
+ drm_gem_object_get(r_obj);

memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct drm_gpuva));

@@ -713,7 +763,9 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
__drm_gpuva_remove(&gpuvm->kernel_alloc_node);

WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
- "GPUVA tree is not empty, potentially leaking memory.");
+ "GPUVA tree is not empty, potentially leaking memory.\n");
+
+ drm_gem_object_put(gpuvm->r_obj);
}
EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);

diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 5cf892c50f43..4dea847ef989 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1808,8 +1808,9 @@ int
nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
u64 kernel_managed_addr, u64 kernel_managed_size)
{
- int ret;
+ struct drm_gem_object *r_obj;
u64 kernel_managed_end = kernel_managed_addr + kernel_managed_size;
+ int ret;

mutex_init(&uvmm->mutex);
dma_resv_init(&uvmm->resv);
@@ -1833,14 +1834,22 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
goto out_unlock;
}

+ r_obj = drm_gpuvm_root_object_alloc(cli->drm->dev);
+ if (!r_obj) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+
uvmm->kernel_managed_addr = kernel_managed_addr;
uvmm->kernel_managed_size = kernel_managed_size;

- drm_gpuvm_init(&uvmm->base, cli->name,
+ drm_gpuvm_init(&uvmm->base, r_obj, cli->name,
NOUVEAU_VA_SPACE_START,
NOUVEAU_VA_SPACE_END,
kernel_managed_addr, kernel_managed_size,
NULL);
+ /* GPUVM takes care from here on. */
+ drm_gem_object_put(r_obj);

ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
cli->vmm.vmm.object.oclass, RAW,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index c7ed6bf441d4..0aec14d8b259 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -238,9 +238,15 @@ struct drm_gpuvm {
* @ops: &drm_gpuvm_ops providing the split/merge steps to drivers
*/
const struct drm_gpuvm_ops *ops;
+
+ /**
+ * @r_obj: Root GEM object; representing the GPUVM's common &dma_resv.
+ */
+ struct drm_gem_object *r_obj;
};

-void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
+void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
+ const char *name,
u64 start_offset, u64 range,
u64 reserve_offset, u64 reserve_range,
const struct drm_gpuvm_ops *ops);
@@ -248,6 +254,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);

bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);

+struct drm_gem_object *
+drm_gpuvm_root_object_alloc(struct drm_device *drm);
+
+/**
+ * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
+ */
+#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
+
+/**
+ * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding the &drm_gpuvm's
+ * &dma_resv
+ * @gpuvm__: the &drm_gpuvm
+ *
+ * Returns: a pointer to the &drm_gem_object holding the &drm_gpuvm's shared
+ * &dma_resv
+ */
+#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
+
+#define drm_gpuvm_resv_held(gpuvm__) \
+ dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+ dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
static inline struct drm_gpuva *
__drm_gpuva_next(struct drm_gpuva *va)
{
--
2.41.0

2023-10-08 23:34:04

by Danilo Krummrich

[permalink] [raw]
Subject: [PATCH drm-misc-next v6 2/6] drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm

Introduce flags for struct drm_gpuvm, this required by subsequent
commits.

Signed-off-by: Danilo Krummrich <[email protected]>
---
drivers/gpu/drm/drm_gpuvm.c | 4 +++-
drivers/gpu/drm/nouveau/nouveau_uvmm.c | 2 +-
include/drm/drm_gpuvm.h | 17 ++++++++++++++++-
3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index ebda9d594165..6368dfdbe9dd 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -703,6 +703,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
* @gpuvm: pointer to the &drm_gpuvm to initialize
* @r_obj: the root &drm_gem_object providing the GPUVM's common &dma_resv
* @name: the name of the GPU VA space
+ * @flags: the &drm_gpuvm_flags for this GPUVM
* @start_offset: the start offset of the GPU VA space
* @range: the size of the GPU VA space
* @reserve_offset: the start of the kernel reserved GPU VA area
@@ -716,7 +717,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
*/
void
drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
- const char *name,
+ const char *name, enum drm_gpuvm_flags flags,
u64 start_offset, u64 range,
u64 reserve_offset, u64 reserve_range,
const struct drm_gpuvm_ops *ops)
@@ -729,6 +730,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
gpuvm->mm_range = range;

gpuvm->name = name ? name : "unknown";
+ gpuvm->flags = flags;
gpuvm->ops = ops;
gpuvm->r_obj = r_obj;

diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 4dea847ef989..93ad2ba7ec8b 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1843,7 +1843,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
uvmm->kernel_managed_addr = kernel_managed_addr;
uvmm->kernel_managed_size = kernel_managed_size;

- drm_gpuvm_init(&uvmm->base, r_obj, cli->name,
+ drm_gpuvm_init(&uvmm->base, r_obj, cli->name, 0,
NOUVEAU_VA_SPACE_START,
NOUVEAU_VA_SPACE_END,
kernel_managed_addr, kernel_managed_size,
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 0aec14d8b259..13539f32c2e2 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -183,6 +183,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
return va->flags & DRM_GPUVA_INVALIDATED;
}

+/**
+ * enum drm_gpuvm_flags - flags for struct drm_gpuvm
+ */
+enum drm_gpuvm_flags {
+ /**
+ * @DRM_GPUVM_USERBITS: user defined bits
+ */
+ DRM_GPUVM_USERBITS = (1 << 0),
+};
+
/**
* struct drm_gpuvm - DRM GPU VA Manager
*
@@ -201,6 +211,11 @@ struct drm_gpuvm {
*/
const char *name;

+ /**
+ * @flags: the &drm_gpuvm_flags of this GPUVM
+ */
+ enum drm_gpuvm_flags flags;
+
/**
* @mm_start: start of the VA space
*/
@@ -246,7 +261,7 @@ struct drm_gpuvm {
};

void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
- const char *name,
+ const char *name, enum drm_gpuvm_flags flags,
u64 start_offset, u64 range,
u64 reserve_offset, u64 reserve_range,
const struct drm_gpuvm_ops *ops);
--
2.41.0

2023-10-08 23:34:09

by Danilo Krummrich

[permalink] [raw]
Subject: [PATCH drm-misc-next v6 5/6] drm/nouveau: make use of the GPUVM's shared dma-resv

DRM GEM objects private to a single GPUVM can use a shared dma-resv.
Make use of the shared dma-resv of GPUVM rather than a driver specific
one.

The shared dma-resv originates from a "root" GEM object serving as
container for the dma-resv to make it compatible with drm_exec.

In order to make sure the object proving the shared dma-resv can't be
freed up before the objects making use of it, let every such GEM object
take a reference on it.

Signed-off-by: Danilo Krummrich <[email protected]>
---
drivers/gpu/drm/nouveau/nouveau_bo.c | 11 +++++++++--
drivers/gpu/drm/nouveau/nouveau_bo.h | 5 +++++
drivers/gpu/drm/nouveau/nouveau_gem.c | 10 ++++++++--
drivers/gpu/drm/nouveau/nouveau_uvmm.c | 7 ++-----
drivers/gpu/drm/nouveau/nouveau_uvmm.h | 1 -
5 files changed, 24 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.c b/drivers/gpu/drm/nouveau/nouveau_bo.c
index 19cab37ac69c..dbb3facfd23d 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.c
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.c
@@ -148,10 +148,17 @@ nouveau_bo_del_ttm(struct ttm_buffer_object *bo)
* If nouveau_bo_new() allocated this buffer, the GEM object was never
* initialized, so don't attempt to release it.
*/
- if (bo->base.dev)
+ if (bo->base.dev) {
+ /* Gem objects not being shared with other VMs get their
+ * dma_resv from a root GEM object.
+ */
+ if (nvbo->no_share)
+ drm_gem_object_put(nvbo->r_obj);
+
drm_gem_object_release(&bo->base);
- else
+ } else {
dma_resv_fini(&bo->base._resv);
+ }

kfree(nvbo);
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_bo.h b/drivers/gpu/drm/nouveau/nouveau_bo.h
index 07f671cf895e..70c551921a9e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_bo.h
+++ b/drivers/gpu/drm/nouveau/nouveau_bo.h
@@ -26,6 +26,11 @@ struct nouveau_bo {
struct list_head entry;
int pbbo_index;
bool validate_mapped;
+
+ /* Root GEM object we derive the dma_resv of in case this BO is not
+ * shared between VMs.
+ */
+ struct drm_gem_object *r_obj;
bool no_share;

/* GPU address space is independent of CPU word size */
diff --git a/drivers/gpu/drm/nouveau/nouveau_gem.c b/drivers/gpu/drm/nouveau/nouveau_gem.c
index c0b10d8d3d03..7715baf85c7e 100644
--- a/drivers/gpu/drm/nouveau/nouveau_gem.c
+++ b/drivers/gpu/drm/nouveau/nouveau_gem.c
@@ -111,7 +111,8 @@ nouveau_gem_object_open(struct drm_gem_object *gem, struct drm_file *file_priv)
if (vmm->vmm.object.oclass < NVIF_CLASS_VMM_NV50)
return 0;

- if (nvbo->no_share && uvmm && &uvmm->resv != nvbo->bo.base.resv)
+ if (nvbo->no_share && uvmm &&
+ drm_gpuvm_resv(&uvmm->base) != nvbo->bo.base.resv)
return -EPERM;

ret = ttm_bo_reserve(&nvbo->bo, false, false, NULL);
@@ -245,7 +246,7 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
if (unlikely(!uvmm))
return -EINVAL;

- resv = &uvmm->resv;
+ resv = drm_gpuvm_resv(&uvmm->base);
}

if (!(domain & (NOUVEAU_GEM_DOMAIN_VRAM | NOUVEAU_GEM_DOMAIN_GART)))
@@ -288,6 +289,11 @@ nouveau_gem_new(struct nouveau_cli *cli, u64 size, int align, uint32_t domain,
if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_TESLA)
nvbo->valid_domains &= domain;

+ if (nvbo->no_share) {
+ nvbo->r_obj = drm_gpuvm_resv_obj(&uvmm->base);
+ drm_gem_object_get(nvbo->r_obj);
+ }
+
*pnvbo = nvbo;
return 0;
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 4e46f850e65f..436b0ac74ffe 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -1841,7 +1841,6 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
int ret;

mutex_init(&uvmm->mutex);
- dma_resv_init(&uvmm->resv);
mt_init_flags(&uvmm->region_mt, MT_FLAGS_LOCK_EXTERN);
mt_set_external_lock(&uvmm->region_mt, &uvmm->mutex);

@@ -1884,14 +1883,14 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli *cli,
kernel_managed_addr, kernel_managed_size,
NULL, 0, &cli->uvmm.vmm.vmm);
if (ret)
- goto out_free_gpuva_mgr;
+ goto out_gpuvm_fini;

cli->uvmm.vmm.cli = cli;
mutex_unlock(&cli->mutex);

return 0;

-out_free_gpuva_mgr:
+out_gpuvm_fini:
drm_gpuvm_destroy(&uvmm->base);
out_unlock:
mutex_unlock(&cli->mutex);
@@ -1949,6 +1948,4 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
nouveau_vmm_fini(&uvmm->vmm);
drm_gpuvm_destroy(&uvmm->base);
mutex_unlock(&cli->mutex);
-
- dma_resv_fini(&uvmm->resv);
}
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
index a308c59760a5..878cc7958483 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h
@@ -12,7 +12,6 @@ struct nouveau_uvmm {
struct nouveau_vmm vmm;
struct maple_tree region_mt;
struct mutex mutex;
- struct dma_resv resv;

u64 kernel_managed_addr;
u64 kernel_managed_size;
--
2.41.0

2023-10-08 23:34:16

by Danilo Krummrich

[permalink] [raw]
Subject: [PATCH drm-misc-next v6 3/6] drm/gpuvm: add an abstraction for a VM / BO combination

Add an abstraction layer between the drm_gpuva mappings of a particular
drm_gem_object and this GEM object itself. The abstraction represents a
combination of a drm_gem_object and drm_gpuvm. The drm_gem_object holds
a list of drm_gpuvm_bo structures (the structure representing this
abstraction), while each drm_gpuvm_bo contains list of mappings of this
GEM object.

This has multiple advantages:

1) We can use the drm_gpuvm_bo structure to attach it to various lists
of the drm_gpuvm. This is useful for tracking external and evicted
objects per VM, which is introduced in subsequent patches.

2) Finding mappings of a certain drm_gem_object mapped in a certain
drm_gpuvm becomes much cheaper.

3) Drivers can derive and extend the structure to easily represent
driver specific states of a BO for a certain GPUVM.

The idea of this abstraction was taken from amdgpu, hence the credit for
this idea goes to the developers of amdgpu.

Cc: Christian König <[email protected]>
Signed-off-by: Danilo Krummrich <[email protected]>
---
drivers/gpu/drm/drm_gpuvm.c | 332 +++++++++++++++++++++----
drivers/gpu/drm/nouveau/nouveau_uvmm.c | 64 +++--
include/drm/drm_gem.h | 32 +--
include/drm/drm_gpuvm.h | 177 ++++++++++++-
4 files changed, 521 insertions(+), 84 deletions(-)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index 6368dfdbe9dd..28282283ddaf 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -70,6 +70,18 @@
* &drm_gem_object, such as the &drm_gem_object containing the root page table,
* but it can also be a 'dummy' object, which can be allocated with
* drm_gpuvm_root_object_alloc().
+ *
+ * In order to connect a struct drm_gpuva its backing &drm_gem_object each
+ * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and each
+ * &drm_gpuvm_bo contains a list of &&drm_gpuva structures.
+ *
+ * A &drm_gpuvm_bo is an abstraction that represents a combination of a
+ * &drm_gpuvm and a &drm_gem_object. Every such combination should be unique.
+ * This is ensured by the API through drm_gpuvm_bo_obtain() and
+ * drm_gpuvm_bo_obtain_prealloc() which first look into the corresponding
+ * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
+ * particular combination. If not existent a new instance is created and linked
+ * to the &drm_gem_object.
*/

/**
@@ -395,21 +407,28 @@
/**
* DOC: Locking
*
- * Generally, the GPU VA manager does not take care of locking itself, it is
- * the drivers responsibility to take care about locking. Drivers might want to
- * protect the following operations: inserting, removing and iterating
- * &drm_gpuva objects as well as generating all kinds of operations, such as
- * split / merge or prefetch.
- *
- * The GPU VA manager also does not take care of the locking of the backing
- * &drm_gem_object buffers GPU VA lists by itself; drivers are responsible to
- * enforce mutual exclusion using either the GEMs dma_resv lock or alternatively
- * a driver specific external lock. For the latter see also
- * drm_gem_gpuva_set_lock().
- *
- * However, the GPU VA manager contains lockdep checks to ensure callers of its
- * API hold the corresponding lock whenever the &drm_gem_objects GPU VA list is
- * accessed by functions such as drm_gpuva_link() or drm_gpuva_unlink().
+ * In terms of managing &drm_gpuva entries DRM GPUVM does not take care of
+ * locking itself, it is the drivers responsibility to take care about locking.
+ * Drivers might want to protect the following operations: inserting, removing
+ * and iterating &drm_gpuva objects as well as generating all kinds of
+ * operations, such as split / merge or prefetch.
+ *
+ * DRM GPUVM also does not take care of the locking of the backing
+ * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo abstractions by
+ * itself; drivers are responsible to enforce mutual exclusion using either the
+ * GEMs dma_resv lock or alternatively a driver specific external lock. For the
+ * latter see also drm_gem_gpuva_set_lock().
+ *
+ * However, DRM GPUVM contains lockdep checks to ensure callers of its API hold
+ * the corresponding lock whenever the &drm_gem_objects GPU VA list is accessed
+ * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but also
+ * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
+ *
+ * The latter is required since on creation and destruction of a &drm_gpuvm_bo
+ * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects gpuva list.
+ * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
+ * &drm_gem_object must be able to observe previous creations and destructions
+ * of &drm_gpuvm_bos in order to keep instances unique.
*/

/**
@@ -439,6 +458,7 @@
* {
* struct drm_gpuva_ops *ops;
* struct drm_gpuva_op *op
+ * struct drm_gpuvm_bo *vm_bo;
*
* driver_lock_va_space();
* ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
@@ -446,6 +466,10 @@
* if (IS_ERR(ops))
* return PTR_ERR(ops);
*
+ * vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
+ * if (IS_ERR(vm_bo))
+ * return PTR_ERR(vm_bo);
+ *
* drm_gpuva_for_each_op(op, ops) {
* struct drm_gpuva *va;
*
@@ -458,7 +482,7 @@
*
* driver_vm_map();
* drm_gpuva_map(gpuvm, va, &op->map);
- * drm_gpuva_link(va);
+ * drm_gpuva_link(va, vm_bo);
*
* break;
* case DRM_GPUVA_OP_REMAP: {
@@ -485,11 +509,11 @@
* driver_vm_remap();
* drm_gpuva_remap(prev, next, &op->remap);
*
- * drm_gpuva_unlink(va);
* if (prev)
- * drm_gpuva_link(prev);
+ * drm_gpuva_link(prev, va->vm_bo);
* if (next)
- * drm_gpuva_link(next);
+ * drm_gpuva_link(next, va->vm_bo);
+ * drm_gpuva_unlink(va);
*
* break;
* }
@@ -505,6 +529,7 @@
* break;
* }
* }
+ * drm_gpuvm_bo_put(vm_bo);
* driver_unlock_va_space();
*
* return 0;
@@ -514,6 +539,7 @@
*
* struct driver_context {
* struct drm_gpuvm *gpuvm;
+ * struct drm_gpuvm_bo *vm_bo;
* struct drm_gpuva *new_va;
* struct drm_gpuva *prev_va;
* struct drm_gpuva *next_va;
@@ -534,6 +560,7 @@
* struct drm_gem_object *obj, u64 offset)
* {
* struct driver_context ctx;
+ * struct drm_gpuvm_bo *vm_bo;
* struct drm_gpuva_ops *ops;
* struct drm_gpuva_op *op;
* int ret = 0;
@@ -543,16 +570,23 @@
* ctx.new_va = kzalloc(sizeof(*ctx.new_va), GFP_KERNEL);
* ctx.prev_va = kzalloc(sizeof(*ctx.prev_va), GFP_KERNEL);
* ctx.next_va = kzalloc(sizeof(*ctx.next_va), GFP_KERNEL);
- * if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
+ * ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+ * if (!ctx.new_va || !ctx.prev_va || !ctx.next_va || !vm_bo) {
* ret = -ENOMEM;
* goto out;
* }
*
+ * // Typically protected with a driver specific GEM gpuva lock
+ * // used in the fence signaling path for drm_gpuva_link() and
+ * // drm_gpuva_unlink(), hence pre-allocate.
+ * ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
+ *
* driver_lock_va_space();
* ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj, offset);
* driver_unlock_va_space();
*
* out:
+ * drm_gpuvm_bo_put(ctx.vm_bo);
* kfree(ctx.new_va);
* kfree(ctx.prev_va);
* kfree(ctx.next_va);
@@ -565,7 +599,7 @@
*
* drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
*
- * drm_gpuva_link(ctx->new_va);
+ * drm_gpuva_link(ctx->new_va, ctx->vm_bo);
*
* // prevent the new GPUVA from being freed in
* // driver_mapping_create()
@@ -577,22 +611,23 @@
* int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
* {
* struct driver_context *ctx = __ctx;
+ * struct drm_gpuva *va = op->remap.unmap->va;
*
* drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op->remap);
*
- * drm_gpuva_unlink(op->remap.unmap->va);
- * kfree(op->remap.unmap->va);
- *
* if (op->remap.prev) {
- * drm_gpuva_link(ctx->prev_va);
+ * drm_gpuva_link(ctx->prev_va, va->vm_bo);
* ctx->prev_va = NULL;
* }
*
* if (op->remap.next) {
- * drm_gpuva_link(ctx->next_va);
+ * drm_gpuva_link(ctx->next_va, va->vm_bo);
* ctx->next_va = NULL;
* }
*
+ * drm_gpuva_unlink(va);
+ * kfree(va);
+ *
* return 0;
* }
*
@@ -771,6 +806,194 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
}
EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);

+/**
+ * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * If provided by the driver, this function uses the &drm_gpuvm_ops
+ * vm_bo_alloc() callback to allocate.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj)
+{
+ const struct drm_gpuvm_ops *ops = gpuvm->ops;
+ struct drm_gpuvm_bo *vm_bo;
+
+ if (ops && ops->vm_bo_alloc)
+ vm_bo = ops->vm_bo_alloc();
+ else
+ vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
+
+ if (unlikely(!vm_bo))
+ return NULL;
+
+ vm_bo->vm = gpuvm;
+ vm_bo->obj = obj;
+
+ kref_init(&vm_bo->kref);
+ INIT_LIST_HEAD(&vm_bo->list.gpuva);
+ INIT_LIST_HEAD(&vm_bo->list.entry.gem);
+
+ drm_gem_object_get(obj);
+
+ return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
+
+static void
+drm_gpuvm_bo_destroy(struct kref *kref)
+{
+ struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
+ kref);
+ struct drm_gpuvm *gpuvm = vm_bo->vm;
+ const struct drm_gpuvm_ops *ops = gpuvm->ops;
+ struct drm_gem_object *obj = vm_bo->obj;
+ bool lock = !drm_gpuvm_resv_protected(gpuvm);
+
+ drm_gem_gpuva_assert_lock_held(obj);
+ if (!lock)
+ drm_gpuvm_resv_assert_held(gpuvm);
+
+ list_del(&vm_bo->list.entry.gem);
+
+ drm_gem_object_put(obj);
+
+ if (ops && ops->vm_bo_free)
+ ops->vm_bo_free(vm_bo);
+ else
+ kfree(vm_bo);
+}
+
+/**
+ * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to release the reference of
+ *
+ * This releases a reference to @vm_bo.
+ *
+ * If the reference count drops to zero, the &gpuvm_bo is destroyed, which
+ * includes removing it from the GEMs gpuva list. Hence, if a call to this
+ * function can potentially let the reference count to zero the caller must
+ * hold the dma-resv or driver specific GEM gpuva lock.
+ */
+void
+drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
+{
+ if (vm_bo)
+ kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
+
+static struct drm_gpuvm_bo *
+__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj)
+{
+ struct drm_gpuvm_bo *vm_bo;
+
+ drm_gem_gpuva_assert_lock_held(obj);
+
+ drm_gem_for_each_gpuvm_bo(vm_bo, obj)
+ if (vm_bo->vm == gpuvm)
+ return vm_bo;
+
+ return NULL;
+}
+
+/**
+ * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
+ * &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj)
+{
+ struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
+
+ return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
+
+/**
+ * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo for the
+ * given &drm_gpuvm and &drm_gem_object
+ * @gpuvm: The &drm_gpuvm the @obj is mapped in.
+ * @obj: The &drm_gem_object being mapped in the @gpuvm.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the &drm_gpuvm_bo accordingly. If not found, allocates a new
+ * &drm_gpuvm_bo.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on failure
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj)
+{
+ struct drm_gpuvm_bo *vm_bo;
+
+ vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+ if (vm_bo)
+ return vm_bo;
+
+ vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
+ if (!vm_bo)
+ return ERR_PTR(-ENOMEM);
+
+ list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
+
+ return vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
+
+/**
+ * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the &drm_gpuvm_bo
+ * for the given &drm_gpuvm and &drm_gem_object
+ * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
+ *
+ * Find the &drm_gpuvm_bo representing the combination of the given
+ * &drm_gpuvm and &drm_gem_object. If found, increases the reference
+ * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo reference
+ * count is decreased. If not found @__vm_bo is returned without further
+ * increase of the reference count.
+ *
+ * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
+ *
+ * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no existing
+ * &drm_gpuvm_bo was found
+ */
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
+{
+ struct drm_gpuvm *gpuvm = __vm_bo->vm;
+ struct drm_gem_object *obj = __vm_bo->obj;
+ struct drm_gpuvm_bo *vm_bo;
+
+ vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
+ if (vm_bo) {
+ drm_gpuvm_bo_put(__vm_bo);
+ return vm_bo;
+ }
+
+ list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
+
+ return __vm_bo;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
+
static int
__drm_gpuva_insert(struct drm_gpuvm *gpuvm,
struct drm_gpuva *va)
@@ -860,24 +1083,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
/**
* drm_gpuva_link() - link a &drm_gpuva
* @va: the &drm_gpuva to link
+ * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
*
- * This adds the given &va to the GPU VA list of the &drm_gem_object it is
- * associated with.
+ * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo and the
+ * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
+ *
+ * For every &drm_gpuva entry added to the &drm_gpuvm_bo an additional
+ * reference of the latter is taken.
*
* This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
*/
void
-drm_gpuva_link(struct drm_gpuva *va)
+drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
{
struct drm_gem_object *obj = va->gem.obj;

if (unlikely(!obj))
return;

+ WARN_ON(obj != vm_bo->obj);
drm_gem_gpuva_assert_lock_held(obj);

- list_add_tail(&va->gem.entry, &obj->gpuva.list);
+ drm_gpuvm_bo_get(vm_bo);
+
+ va->vm_bo = vm_bo;
+ list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
}
EXPORT_SYMBOL_GPL(drm_gpuva_link);

@@ -888,13 +1120,22 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
* This removes the given &va from the GPU VA list of the &drm_gem_object it is
* associated with.
*
+ * This removes the given &va from the GPU VA list of the &drm_gpuvm_bo and
+ * the &drm_gpuvm_bo from the &drm_gem_object it is associated with in case
+ * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
+ *
+ * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a reference of
+ * the latter is dropped.
+ *
* This function expects the caller to protect the GEM's GPUVA list against
- * concurrent access using the GEMs dma_resv lock.
+ * concurrent access using either the GEMs dma_resv lock or a driver specific
+ * lock set through drm_gem_gpuva_set_lock().
*/
void
drm_gpuva_unlink(struct drm_gpuva *va)
{
struct drm_gem_object *obj = va->gem.obj;
+ struct drm_gpuvm_bo *vm_bo = va->vm_bo;

if (unlikely(!obj))
return;
@@ -902,6 +1143,9 @@ drm_gpuva_unlink(struct drm_gpuva *va)
drm_gem_gpuva_assert_lock_held(obj);

list_del_init(&va->gem.entry);
+ va->vm_bo = NULL;
+
+ drm_gpuvm_bo_put(vm_bo);
}
EXPORT_SYMBOL_GPL(drm_gpuva_unlink);

@@ -1046,10 +1290,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
struct drm_gpuva *next,
struct drm_gpuva_op_remap *op)
{
- struct drm_gpuva *curr = op->unmap->va;
- struct drm_gpuvm *gpuvm = curr->vm;
+ struct drm_gpuva *va = op->unmap->va;
+ struct drm_gpuvm *gpuvm = va->vm;

- drm_gpuva_remove(curr);
+ drm_gpuva_remove(va);

if (op->prev) {
drm_gpuva_init_from_op(prev, op->prev);
@@ -1693,9 +1937,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);

/**
- * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
- * @gpuvm: the &drm_gpuvm representing the GPU VA space
- * @obj: the &drm_gem_object to unmap
+ * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
+ * @vm_bo: the &drm_gpuvm_bo abstraction
*
* This function creates a list of operations to perform unmapping for every
* GPUVA attached to a GEM.
@@ -1712,15 +1955,14 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
* Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR on failure
*/
struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
- struct drm_gem_object *obj)
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
{
struct drm_gpuva_ops *ops;
struct drm_gpuva_op *op;
struct drm_gpuva *va;
int ret;

- drm_gem_gpuva_assert_lock_held(obj);
+ drm_gem_gpuva_assert_lock_held(vm_bo->obj);

ops = kzalloc(sizeof(*ops), GFP_KERNEL);
if (!ops)
@@ -1728,8 +1970,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,

INIT_LIST_HEAD(&ops->list);

- drm_gem_for_each_gpuva(va, obj) {
- op = gpuva_op_alloc(gpuvm);
+ drm_gpuvm_bo_for_each_va(va, vm_bo) {
+ op = gpuva_op_alloc(vm_bo->vm);
if (!op) {
ret = -ENOMEM;
goto err_free_ops;
@@ -1743,10 +1985,10 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
return ops;

err_free_ops:
- drm_gpuva_ops_free(gpuvm, ops);
+ drm_gpuva_ops_free(vm_bo->vm, ops);
return ERR_PTR(ret);
}
-EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);

/**
* drm_gpuva_ops_free() - free the given &drm_gpuva_ops
diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
index 93ad2ba7ec8b..4e46f850e65f 100644
--- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
+++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
@@ -62,6 +62,8 @@ struct bind_job_op {
enum vm_bind_op op;
u32 flags;

+ struct drm_gpuvm_bo *vm_bo;
+
struct {
u64 addr;
u64 range;
@@ -1113,22 +1115,28 @@ bind_validate_region(struct nouveau_job *job)
}

static void
-bind_link_gpuvas(struct drm_gpuva_ops *ops, struct nouveau_uvma_prealloc *new)
+bind_link_gpuvas(struct bind_job_op *bop)
{
+ struct nouveau_uvma_prealloc *new = &bop->new;
+ struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
+ struct drm_gpuva_ops *ops = bop->ops;
struct drm_gpuva_op *op;

drm_gpuva_for_each_op(op, ops) {
switch (op->op) {
case DRM_GPUVA_OP_MAP:
- drm_gpuva_link(&new->map->va);
+ drm_gpuva_link(&new->map->va, vm_bo);
break;
- case DRM_GPUVA_OP_REMAP:
+ case DRM_GPUVA_OP_REMAP: {
+ struct drm_gpuva *va = op->remap.unmap->va;
+
if (op->remap.prev)
- drm_gpuva_link(&new->prev->va);
+ drm_gpuva_link(&new->prev->va, va->vm_bo);
if (op->remap.next)
- drm_gpuva_link(&new->next->va);
- drm_gpuva_unlink(op->remap.unmap->va);
+ drm_gpuva_link(&new->next->va, va->vm_bo);
+ drm_gpuva_unlink(va);
break;
+ }
case DRM_GPUVA_OP_UNMAP:
drm_gpuva_unlink(op->unmap.va);
break;
@@ -1150,10 +1158,18 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)

list_for_each_op(op, &bind_job->ops) {
if (op->op == OP_MAP) {
- op->gem.obj = drm_gem_object_lookup(job->file_priv,
- op->gem.handle);
- if (!op->gem.obj)
+ struct drm_gem_object *obj;
+
+ obj = drm_gem_object_lookup(job->file_priv,
+ op->gem.handle);
+ if (!(op->gem.obj = obj))
return -ENOENT;
+
+ dma_resv_lock(obj->resv, NULL);
+ op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base, obj);
+ dma_resv_unlock(obj->resv);
+ if (IS_ERR(op->vm_bo))
+ return PTR_ERR(op->vm_bo);
}

ret = bind_validate_op(job, op);
@@ -1364,7 +1380,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job *job)
case OP_UNMAP_SPARSE:
case OP_MAP:
case OP_UNMAP:
- bind_link_gpuvas(op->ops, &op->new);
+ bind_link_gpuvas(op);
break;
default:
break;
@@ -1511,6 +1527,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct work_struct *work)
if (!IS_ERR_OR_NULL(op->ops))
drm_gpuva_ops_free(&uvmm->base, op->ops);

+ if (!IS_ERR_OR_NULL(op->vm_bo)) {
+ dma_resv_lock(obj->resv, NULL);
+ drm_gpuvm_bo_put(op->vm_bo);
+ dma_resv_unlock(obj->resv);
+ }
+
if (obj)
drm_gem_object_put(obj);
}
@@ -1776,15 +1798,18 @@ void
nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem *mem)
{
struct drm_gem_object *obj = &nvbo->bo.base;
+ struct drm_gpuvm_bo *vm_bo;
struct drm_gpuva *va;

dma_resv_assert_held(obj->resv);

- drm_gem_for_each_gpuva(va, obj) {
- struct nouveau_uvma *uvma = uvma_from_va(va);
+ drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+ drm_gpuvm_bo_for_each_va(va, vm_bo) {
+ struct nouveau_uvma *uvma = uvma_from_va(va);

- nouveau_uvma_map(uvma, mem);
- drm_gpuva_invalidate(va, false);
+ nouveau_uvma_map(uvma, mem);
+ drm_gpuva_invalidate(va, false);
+ }
}
}

@@ -1792,15 +1817,18 @@ void
nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
{
struct drm_gem_object *obj = &nvbo->bo.base;
+ struct drm_gpuvm_bo *vm_bo;
struct drm_gpuva *va;

dma_resv_assert_held(obj->resv);

- drm_gem_for_each_gpuva(va, obj) {
- struct nouveau_uvma *uvma = uvma_from_va(va);
+ drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
+ drm_gpuvm_bo_for_each_va(va, vm_bo) {
+ struct nouveau_uvma *uvma = uvma_from_va(va);

- nouveau_uvma_unmap(uvma);
- drm_gpuva_invalidate(va, true);
+ nouveau_uvma_unmap(uvma);
+ drm_gpuva_invalidate(va, true);
+ }
}
}

diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
index 16364487fde9..369505447acd 100644
--- a/include/drm/drm_gem.h
+++ b/include/drm/drm_gem.h
@@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
* drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
* @obj: the &drm_gem_object
*
- * This initializes the &drm_gem_object's &drm_gpuva list.
+ * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
*
* Calling this function is only necessary for drivers intending to support the
* &drm_driver_feature DRIVER_GEM_GPUVA.
@@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct drm_gem_object *obj)
}

/**
- * drm_gem_for_each_gpuva() - iternator to walk over a list of gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bo structure to assign to in each iteration step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
*
- * This iterator walks over all &drm_gpuva structures associated with the
- * &drm_gpuva_manager.
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
+ * &drm_gem_object.
*/
-#define drm_gem_for_each_gpuva(entry__, obj__) \
- list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
+ list_for_each_entry(entry__, &(obj__)->gpuva.list, list.entry.gem)

/**
- * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a list of
- * gpuvas
- * @entry__: &drm_gpuva structure to assign to in each iteration step
- * @next__: &next &drm_gpuva to store the next step
- * @obj__: the &drm_gem_object the &drm_gpuvas to walk are associated with
+ * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a list of
+ * &drm_gpuvm_bo
+ * @entry__: &drm_gpuvm_bostructure to assign to in each iteration step
+ * @next__: &next &drm_gpuvm_bo to store the next step
+ * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are associated with
*
- * This iterator walks over all &drm_gpuva structures associated with the
+ * This iterator walks over all &drm_gpuvm_bo structures associated with the
* &drm_gem_object. It is implemented with list_for_each_entry_safe(), hence
* it is save against removal of elements.
*/
-#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
- list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, gem.entry)
+#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
+ list_for_each_entry_safe(entry__, next__, &(obj__)->gpuva.list, list.entry.gem)

#endif /* __DRM_GEM_H__ */
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 13539f32c2e2..ddb0b8d323cf 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -26,12 +26,14 @@
*/

#include <linux/list.h>
+#include <linux/dma-resv.h>
#include <linux/rbtree.h>
#include <linux/types.h>

#include <drm/drm_gem.h>

struct drm_gpuvm;
+struct drm_gpuvm_bo;
struct drm_gpuvm_ops;

/**
@@ -72,6 +74,12 @@ struct drm_gpuva {
*/
struct drm_gpuvm *vm;

+ /**
+ * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
+ * &drm_gem_object
+ */
+ struct drm_gpuvm_bo *vm_bo;
+
/**
* @flags: the &drm_gpuva_flags for this mapping
*/
@@ -107,7 +115,7 @@ struct drm_gpuva {
struct drm_gem_object *obj;

/**
- * @entry: the &list_head to attach this object to a &drm_gem_object
+ * @entry: the &list_head to attach this object to a &drm_gpuvm_bo
*/
struct list_head entry;
} gem;
@@ -140,7 +148,7 @@ struct drm_gpuva {
int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
void drm_gpuva_remove(struct drm_gpuva *va);

-void drm_gpuva_link(struct drm_gpuva *va);
+void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo);
void drm_gpuva_unlink(struct drm_gpuva *va);

struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
@@ -187,10 +195,16 @@ static inline bool drm_gpuva_invalidated(struct drm_gpuva *va)
* enum drm_gpuvm_flags - flags for struct drm_gpuvm
*/
enum drm_gpuvm_flags {
+ /**
+ * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally by the
+ * GPUVM's &dma_resv lock
+ */
+ DRM_GPUVM_RESV_PROTECTED = (1 << 0),
+
/**
* @DRM_GPUVM_USERBITS: user defined bits
*/
- DRM_GPUVM_USERBITS = (1 << 0),
+ DRM_GPUVM_USERBITS = (1 << 1),
};

/**
@@ -272,6 +286,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
struct drm_gem_object *
drm_gpuvm_root_object_alloc(struct drm_device *drm);

+/**
+ * drm_gpuvm_resv_protected() - indicates whether &DRM_GPUVM_RESV_PROTECTED is
+ * set
+ * @gpuvm: the &drm_gpuvm
+ *
+ * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false otherwise.
+ */
+static inline bool
+drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
+{
+ return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
+}
+
/**
* drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
* @gpuvm__: the &drm_gpuvm
@@ -290,6 +317,12 @@ drm_gpuvm_root_object_alloc(struct drm_device *drm);
*/
#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)

+#define drm_gpuvm_resv_held(gpuvm__) \
+ dma_resv_held(drm_gpuvm_resv(gpuvm__))
+
+#define drm_gpuvm_resv_assert_held(gpuvm__) \
+ dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
+
#define drm_gpuvm_resv_held(gpuvm__) \
dma_resv_held(drm_gpuvm_resv(gpuvm__))

@@ -374,6 +407,117 @@ __drm_gpuva_next(struct drm_gpuva *va)
#define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list, rb.entry)

+/**
+ * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
+ * &drm_gem_object combination
+ *
+ * This structure is an abstraction representing a &drm_gpuvm and
+ * &drm_gem_object combination. It serves as an indirection to accelerate
+ * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
+ * &drm_gem_object.
+ *
+ * Furthermore it is used cache evicted GEM objects for a certain GPU-VM to
+ * accelerate validation.
+ *
+ * Typically, drivers want to create an instance of a struct drm_gpuvm_bo once
+ * a GEM object is mapped first in a GPU-VM and release the instance once the
+ * last mapping of the GEM object in this GPU-VM is unmapped.
+ */
+struct drm_gpuvm_bo {
+
+ /**
+ * @vm: The &drm_gpuvm the @obj is mapped in.
+ */
+ struct drm_gpuvm *vm;
+
+ /**
+ * @obj: The &drm_gem_object being mapped in @vm.
+ */
+ struct drm_gem_object *obj;
+
+ /**
+ * @kref: The reference count for this &drm_gpuvm_bo.
+ */
+ struct kref kref;
+
+ /**
+ * @list: Structure containing all &list_heads.
+ */
+ struct {
+ /**
+ * @gpuva: The list of linked &drm_gpuvas.
+ */
+ struct list_head gpuva;
+
+ /**
+ * @entry: Structure containing all &list_heads serving as
+ * entry.
+ */
+ struct {
+ /**
+ * @gem: List entry to attach to the &drm_gem_objects
+ * gpuva list.
+ */
+ struct list_head gem;
+ } entry;
+ } list;
+};
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj);
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
+
+/**
+ * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
+ * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
+ *
+ * This function acquires an additional reference to @vm_bo. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm_bo *
+drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
+{
+ kref_get(&vm_bo->kref);
+ return vm_bo;
+}
+
+void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
+
+struct drm_gpuvm_bo *
+drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
+ struct drm_gem_object *obj);
+
+/**
+ * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo.
+ */
+#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
+ list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
+
+/**
+ * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a list of
+ * &drm_gpuva
+ * @va__: &drm_gpuva structure to assign to in each iteration step
+ * @next__: &next &drm_gpuva to store the next step
+ * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated with
+ *
+ * This iterator walks over all &drm_gpuva structures associated with the
+ * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(), hence
+ * it is save against removal of elements.
+ */
+#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
+ list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva, gem.entry)
+
/**
* enum drm_gpuva_op_type - GPU VA operation type
*
@@ -643,8 +787,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
u64 addr, u64 range);

struct drm_gpuva_ops *
-drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
- struct drm_gem_object *obj);
+drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);

void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
struct drm_gpuva_ops *ops);
@@ -688,6 +831,30 @@ struct drm_gpuvm_ops {
*/
void (*op_free)(struct drm_gpuva_op *op);

+ /**
+ * @vm_bo_alloc: called when the &drm_gpuvm allocates
+ * a struct drm_gpuvm_bo
+ *
+ * Some drivers may want to embed struct drm_gpuvm_bo into driver
+ * specific structures. By implementing this callback drivers can
+ * allocate memory accordingly.
+ *
+ * This callback is optional.
+ */
+ struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
+
+ /**
+ * @vm_bo_free: called when the &drm_gpuvm frees a
+ * struct drm_gpuvm_bo
+ *
+ * Some drivers may want to embed struct drm_gpuvm_bo into driver
+ * specific structures. By implementing this callback drivers can
+ * free the previously allocated memory accordingly.
+ *
+ * This callback is optional.
+ */
+ void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
+
/**
* @sm_step_map: called from &drm_gpuvm_sm_map to finally insert the
* mapping once all previous steps were completed
--
2.41.0

2023-10-09 13:37:19

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects


On 10/9/23 01:32, Danilo Krummrich wrote:
> Currently the DRM GPUVM offers common infrastructure to track GPU VA
> allocations and mappings, generically connect GPU VA mappings to their
> backing buffers and perform more complex mapping operations on the GPU VA
> space.
>
> However, there are more design patterns commonly used by drivers, which
> can potentially be generalized in order to make the DRM GPUVM represent
> a basis for GPU-VM implementations. In this context, this patch aims
> at generalizing the following elements.
>
> 1) Provide a common dma-resv for GEM objects not being used outside of
> this GPU-VM.
>
> 2) Provide tracking of external GEM objects (GEM objects which are
> shared with other GPU-VMs).
>
> 3) Provide functions to efficiently lock all GEM objects dma-resv the
> GPU-VM contains mappings of.
>
> 4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
> of, such that validation of evicted GEM objects is accelerated.
>
> 5) Provide some convinience functions for common patterns.
>
> Big thanks to Boris Brezillon for his help to figure out locking for
> drivers updating the GPU VA space within the fence signalling path.
>
> Suggested-by: Matthew Brost <[email protected]>
> Signed-off-by: Danilo Krummrich <[email protected]>
> ---
> drivers/gpu/drm/drm_gpuvm.c | 646 ++++++++++++++++++++++++++++++++++++
> include/drm/drm_gpuvm.h | 246 ++++++++++++++
> 2 files changed, 892 insertions(+)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
> index 28282283ddaf..6977bd30eca5 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -82,6 +82,21 @@
> * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
> * particular combination. If not existent a new instance is created and linked
> * to the &drm_gem_object.
> + *
> + * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
> + * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
> + * list are maintained in order to accelerate locking of dma-resv locks and
> + * validation of evicted objects bound in a &drm_gpuvm. For instance, all
> + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
> + * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
> + * order to validate all evicted &drm_gem_objects. It is also possible to lock
> + * additional &drm_gem_objects by providing the corresponding parameters to
> + * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
> + * use of helper functions such as drm_gpuvm_prepare_range() or
> + * drm_gpuvm_prepare_objects().
> + *
> + * Every bound &drm_gem_object is treated as external object when its &dma_resv
> + * structure is different than the &drm_gpuvm's common &dma_resv structure.
> */
>
> /**
> @@ -429,6 +444,20 @@
> * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
> * &drm_gem_object must be able to observe previous creations and destructions
> * of &drm_gpuvm_bos in order to keep instances unique.
> + *
> + * The &drm_gpuvm's lists for keeping track of external and evicted objects are
> + * protected against concurrent insertion / removal and iteration internally.
> + *
> + * However, drivers still need ensure to protect concurrent calls to functions
> + * iterating those lists, namely drm_gpuvm_prepare_objects() and
> + * drm_gpuvm_validate().
> + *
> + * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
> + * that the corresponding &dma_resv locks are held in order to protect the
> + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
> + * the corresponding lockdep checks are enabled. This is an optimization for
> + * drivers which are capable of taking the corresponding &dma_resv locks and
> + * hence do not require internal locking.
> */
>
> /**
> @@ -641,6 +670,195 @@
> * }
> */
>
> +/**
> + * get_next_vm_bo_from_list() - get the next vm_bo element
> + * @__gpuvm: The GPU VM
> + * @__list_name: The name of the list we're iterating on
> + * @__local_list: A pointer to the local list used to store already iterated items
> + * @__prev_vm_bo: The previous element we got from drm_gpuvm_get_next_cached_vm_bo()
> + *
> + * This helper is here to provide lockless list iteration. Lockless as in, the
> + * iterator releases the lock immediately after picking the first element from
> + * the list, so list insertion deletion can happen concurrently.
> + *
> + * Elements popped from the original list are kept in a local list, so removal
> + * and is_empty checks can still happen while we're iterating the list.
> + */
> +#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo) \
> + ({ \
> + struct drm_gpuvm_bo *__vm_bo = NULL; \
> + \
> + drm_gpuvm_bo_put(__prev_vm_bo); \
> + \
> + spin_lock(&(__gpuvm)->__list_name.lock); \
> + if (!(__gpuvm)->__list_name.local_list) \
> + (__gpuvm)->__list_name.local_list = __local_list; \
> + else \
> + WARN_ON((__gpuvm)->__list_name.local_list != __local_list); \
> + \
> + while (!list_empty(&(__gpuvm)->__list_name.list)) { \
> + __vm_bo = list_first_entry(&(__gpuvm)->__list_name.list, \
> + struct drm_gpuvm_bo, \
> + list.entry.__list_name); \
> + if (kref_get_unless_zero(&__vm_bo->kref)) { \
> + list_move_tail(&(__vm_bo)->list.entry.__list_name, \
> + __local_list); \
> + break; \
> + } else { \
> + list_del_init(&(__vm_bo)->list.entry.__list_name); \
> + __vm_bo = NULL; \
> + } \
> + } \
> + spin_unlock(&(__gpuvm)->__list_name.lock); \
> + \
> + __vm_bo; \
> + })
> +
> +/**
> + * for_each_vm_bo_in_list() - internal vm_bo list iterator
> + *
> + * This helper is here to provide lockless list iteration. Lockless as in, the
> + * iterator releases the lock immediately after picking the first element from the
> + * list, hence list insertion and deletion can happen concurrently.
> + *
> + * It is not allowed to re-assign the vm_bo pointer from inside this loop.
> + *
> + * Typical use:
> + *
> + * struct drm_gpuvm_bo *vm_bo;
> + * LIST_HEAD(my_local_list);
> + *
> + * ret = 0;
> + * for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
> + * ret = do_something_with_vm_bo(..., vm_bo);
> + * if (ret)
> + * break;
> + * }
> + * drm_gpuvm_bo_put(vm_bo);
> + * restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
> + *
> + *
> + * Only used for internal list iterations, not meant to be exposed to the outside
> + * world.
> + */
> +#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo) \
> + for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name, \
> + __local_list, NULL); \
> + __vm_bo; \
> + __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name, \
> + __local_list, __vm_bo))
> +
> +static void
> +__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
> + struct list_head *list, struct list_head **local_list)
> +{
> + /* Merge back the two lists, moving local list elements to the
> + * head to preserve previous ordering, in case it matters.
> + */
> + spin_lock(lock);
> + if (*local_list) {
> + list_splice(*local_list, list);
> + *local_list = NULL;
> + }
> + spin_unlock(lock);
> +}
> +
> +/**
> + * restore_vm_bo_list() - move vm_bo elements back to their original list
> + * @__gpuvm: The GPU VM
> + * @__list_name: The name of the list we're iterating on
> + *
> + * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
> + * to restore the original state and let new iterations take place.
> + */
> +#define restore_vm_bo_list(__gpuvm, __list_name) \
> + __restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock, \
> + &(__gpuvm)->__list_name.list, \
> + &(__gpuvm)->__list_name.local_list)
> +
> +static void
> +cond_spin_lock(spinlock_t *lock, bool cond)
> +{
> + if (cond)
> + spin_lock(lock);
> +}
> +
> +static void
> +cond_spin_unlock(spinlock_t *lock, bool cond)
> +{
> + if (cond)
> + spin_unlock(lock);
> +}
> +
> +static void
> +__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
> + struct list_head *entry, struct list_head *list)
> +{
> + cond_spin_lock(lock, !!lock);
> + if (list_empty(entry))
> + list_add_tail(entry, list);
> + cond_spin_unlock(lock, !!lock);
> +}
> +
> +/**
> + * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
> + * @__vm_bo: the &drm_gpuvm_bo
> + * @__list_name: the name of the list to insert into
> + * @__lock: whether to lock with the internal spinlock
> + *
> + * Inserts the given @__vm_bo into the list specified by @__list_name.
> + */
> +#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock) \
> + __drm_gpuvm_bo_list_add((__vm_bo)->vm, \
> + __lock ? &(__vm_bo)->vm->__list_name.lock : \
> + NULL, \
> + &(__vm_bo)->list.entry.__list_name, \
> + &(__vm_bo)->vm->__list_name.list)
> +
> +static void
> +__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
> + struct list_head *entry, bool init)
> +{
> + cond_spin_lock(lock, !!lock);
> + if (init) {
> + if (!list_empty(entry))
> + list_del_init(entry);
> + } else {
> + list_del(entry);
> + }
> + cond_spin_unlock(lock, !!lock);
> +}
> +
> +/**
> + * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
> + * @__vm_bo: the &drm_gpuvm_bo
> + * @__list_name: the name of the list to insert into
> + * @__lock: whether to lock with the internal spinlock
> + *
> + * Removes the given @__vm_bo from the list specified by @__list_name.
> + */
> +#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock) \
> + __drm_gpuvm_bo_list_del((__vm_bo)->vm, \
> + __lock ? &(__vm_bo)->vm->__list_name.lock : \
> + NULL, \
> + &(__vm_bo)->list.entry.__list_name, \
> + true)
> +
> +/**
> + * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
> + * @__vm_bo: the &drm_gpuvm_bo
> + * @__list_name: the name of the list to insert into
> + * @__lock: whether to lock with the internal spinlock
> + *
> + * Removes the given @__vm_bo from the list specified by @__list_name.
> + */
> +#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock) \
> + __drm_gpuvm_bo_list_del((__vm_bo)->vm, \
> + __lock ? &(__vm_bo)->vm->__list_name.lock : \
> + NULL, \
> + &(__vm_bo)->list.entry.__list_name, \
> + false)
> +
> #define to_drm_gpuva(__node) container_of((__node), struct drm_gpuva, rb.node)
>
> #define GPUVA_START(node) ((node)->va.addr)
> @@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
> gpuvm->rb.tree = RB_ROOT_CACHED;
> INIT_LIST_HEAD(&gpuvm->rb.list);
>
> + INIT_LIST_HEAD(&gpuvm->extobj.list);
> + spin_lock_init(&gpuvm->extobj.lock);
> +
> + INIT_LIST_HEAD(&gpuvm->evict.list);
> + spin_lock_init(&gpuvm->evict.lock);
> +
> drm_gpuvm_check_overflow(start_offset, range);
> gpuvm->mm_start = start_offset;
> gpuvm->mm_range = range;
> @@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
> "GPUVA tree is not empty, potentially leaking memory.\n");
>
> + WARN(!list_empty(&gpuvm->extobj.list), "Extobj list should be empty.\n");
> + WARN(!list_empty(&gpuvm->evict.list), "Evict list should be empty.\n");
> +
> drm_gem_object_put(gpuvm->r_obj);
> }
> EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>
> +static int
> +__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
> + struct drm_exec *exec,
> + unsigned int num_fences)
> +{
> + struct drm_gpuvm_bo *vm_bo;
> + LIST_HEAD(extobjs);
> + int ret = 0;
> +
> + for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
> + ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
> + if (ret)
> + break;
> + }
> + /* Drop ref in case we break out of the loop. */
> + drm_gpuvm_bo_put(vm_bo);
> + restore_vm_bo_list(gpuvm, extobj);
> +
> + return ret;
> +}
> +
> +static int
> +drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
> + struct drm_exec *exec,
> + unsigned int num_fences)
> +{
> + struct drm_gpuvm_bo *vm_bo;
> + int ret = 0;
> +
> + drm_gpuvm_resv_assert_held(gpuvm);
> + list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
> + ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
> + if (ret)
> + break;
> +
> + if (vm_bo->evicted)
> + drm_gpuvm_bo_list_add(vm_bo, evict, false);

Clear vm_bo->evicted here?


> + }
> +
> + return ret;
> +}
> +
> +/**
> + * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
> + * @gpuvm: the &drm_gpuvm
> + * @exec: the &drm_exec locking context
> + * @num_fences: the amount of &dma_fences to reserve
> + *
> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
> + * &drm_gpuvm contains mappings of.
> + *
> + * Using this function directly, it is the drivers responsibility to call
> + * drm_exec_init() and drm_exec_fini() accordingly.
> + *
> + * Note: This function is safe against concurrent insertion and removal of
> + * external objects, however it is not safe against concurrent usage itself.
> + *
> + * Drivers need to make sure to protect this case with either an outer VM lock
> + * or by calling drm_gpuvm_prepare_vm() before this function within the
> + * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
> + * mutual exclusion.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
> + struct drm_exec *exec,
> + unsigned int num_fences)
> +{
> + if (drm_gpuvm_resv_protected(gpuvm))
> + return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
> + num_fences);
> + else
> + return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
> +
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
> +
> +/**
> + * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
> + * @gpuvm: the &drm_gpuvm
> + * @exec: the &drm_exec locking context
> + * @addr: the start address within the VA space
> + * @range: the range to iterate within the VA space
> + * @num_fences: the amount of &dma_fences to reserve
> + *
> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
> + * and @addr + @range.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
> + u64 addr, u64 range, unsigned int num_fences)
> +{
> + struct drm_gpuva *va;
> + u64 end = addr + range;
> + int ret;
> +
> + drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
> + struct drm_gem_object *obj = va->gem.obj;
> +
> + ret = drm_exec_prepare_obj(exec, obj, num_fences);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
> +
> +/**
> + * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + * @num_fences: the amount of &dma_fences to reserve
> + * @interruptible: sleep interruptible if waiting
> + *
> + * Acquires all dma-resv locks of all &drm_gem_objects the given
> + * &drm_gpuvm contains mappings of.
> + *
> + * Addionally, when calling this function with struct drm_gpuvm_exec::extra
> + * being set the driver receives the given @fn callback to lock additional
> + * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
> + * would call drm_exec_prepare_obj() from within this callback.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
> + unsigned int num_fences,
> + bool interruptible)
> +{
> + struct drm_gpuvm *gpuvm = vm_exec->vm;
> + struct drm_exec *exec = &vm_exec->exec;
> + uint32_t flags;
> + int ret;
> +
> + flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
> + DRM_EXEC_IGNORE_DUPLICATES;
> +
> + drm_exec_init(exec, flags);
> +
> + drm_exec_until_all_locked(exec) {
> + ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
> + drm_exec_retry_on_contention(exec);
> + if (ret)
> + goto err;
> +
> + ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
> + drm_exec_retry_on_contention(exec);
> + if (ret)
> + goto err;
> +
> + if (vm_exec->extra.fn) {
> + ret = vm_exec->extra.fn(vm_exec, num_fences);
> + drm_exec_retry_on_contention(exec);
> + if (ret)
> + goto err;
> + }
> + }
> +
> + return 0;
> +
> +err:
> + drm_exec_fini(exec);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
> +
> +static int
> +fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int num_fences)
> +{
> + struct {
> + struct drm_gem_object **objs;
> + unsigned int num_objs;
> + } *args = vm_exec->extra.priv;
> +
> + return drm_exec_prepare_array(&vm_exec->exec, args->objs,
> + args->num_objs, num_fences);
> +}
> +
> +/**
> + * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + * @objs: additional &drm_gem_objects to lock
> + * @num_objs: the number of additional &drm_gem_objects to lock
> + * @num_fences: the amount of &dma_fences to reserve
> + * @interruptible: sleep interruptible if waiting
> + *
> + * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
> + * contains mappings of, plus the ones given through @objs.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
> + struct drm_gem_object **objs,
> + unsigned int num_objs,
> + unsigned int num_fences,
> + bool interruptible)
> +{
> + struct {
> + struct drm_gem_object **objs;
> + unsigned int num_objs;
> + } args;
> +
> + args.objs = objs;
> + args.num_objs = num_objs;
> +
> + vm_exec->extra.fn = fn_lock_array;
> + vm_exec->extra.priv = &args;
> +
> + return drm_gpuvm_exec_lock(vm_exec, num_fences, interruptible);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
> +
> +/**
> + * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + * @addr: the start address within the VA space
> + * @range: the range to iterate within the VA space
> + * @num_fences: the amount of &dma_fences to reserve
> + * @interruptible: sleep interruptible if waiting
> + *
> + * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
> + * @addr + @range.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
> + u64 addr, u64 range,
> + unsigned int num_fences,
> + bool interruptible)
> +{
> + struct drm_gpuvm *gpuvm = vm_exec->vm;
> + struct drm_exec *exec = &vm_exec->exec;
> + uint32_t flags;
> + int ret;
> +
> + flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
> + DRM_EXEC_IGNORE_DUPLICATES;
> +
> + drm_exec_init(exec, flags);
> +
> + drm_exec_until_all_locked(exec) {
> + ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
> + num_fences);
> + drm_exec_retry_on_contention(exec);
> + if (ret)
> + goto err;
> + }
> +
> + return ret;
> +
> +err:
> + drm_exec_fini(exec);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
> +
> +static int
> +__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
> +{
> + const struct drm_gpuvm_ops *ops = gpuvm->ops;
> + struct drm_gpuvm_bo *vm_bo;
> + LIST_HEAD(evict);
> + int ret = 0;
> +
> + for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
> + ret = ops->vm_bo_validate(vm_bo, exec);
> + if (ret)
> + break;
> + }
> + /* Drop ref in case we break out of the loop. */
> + drm_gpuvm_bo_put(vm_bo);
> + restore_vm_bo_list(gpuvm, evict);
> +
> + return ret;
> +}
> +
> +static int
> +drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
> +{
> + const struct drm_gpuvm_ops *ops = gpuvm->ops;
> + struct drm_gpuvm_bo *vm_bo, *next;
> + int ret = 0;
> +
> + drm_gpuvm_resv_assert_held(gpuvm);
> +
> + /* Iterate list safely, drivers typically remove the current entry from
> + * their drm_gpuvm_ops::vm_bo_validate callback. Drivers might also
> + * re-add the entry on failure; this is safe since on failure we break
> + * out of the loop.
> + */
> + list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
> + list.entry.evict) {
> + ret = ops->vm_bo_validate(vm_bo, exec);
> + if (ret)
> + break;
> + }
> +
> + return ret;
> +}
> +
> +/**
> + * drm_gpuvm_validate() - validate all BOs marked as evicted
> + * @gpuvm: the &drm_gpuvm to validate evicted BOs
> + * @exec: the &drm_exec instance used for locking the GPUVM
> + *
> + * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
> + * objects being mapped in the given &drm_gpuvm.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
> +{
> + const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +
> + if (unlikely(!ops || !ops->vm_bo_validate))
> + return -ENOTSUPP;
> +
> + if (drm_gpuvm_resv_protected(gpuvm))
> + return drm_gpuvm_validate_locked(gpuvm, exec);
> + else
> + return __drm_gpuvm_validate(gpuvm, exec);
> +
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
> +
> +/**
> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
> + * dma-resv
> + * @gpuvm: the &drm_gpuvm to add a fence to
> + * @exec: the &drm_exec locking context
> + * @fence: fence to add
> + * @private_usage: private dma-resv usage
> + * @extobj_usage: extobj dma-resv usage
> + */
> +void
> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
> + struct drm_exec *exec,
> + struct dma_fence *fence,
> + enum dma_resv_usage private_usage,
> + enum dma_resv_usage extobj_usage)
> +{
> + struct drm_gem_object *obj;
> + unsigned long index;
> +
> + drm_exec_for_each_locked_object(exec, index, obj) {
> + dma_resv_assert_held(obj->resv);
> + dma_resv_add_fence(obj->resv, fence,
> + drm_gpuvm_is_extobj(gpuvm, obj) ?
> + private_usage : extobj_usage);
> + }
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
> +
> /**
> * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
> * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> @@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> INIT_LIST_HEAD(&vm_bo->list.gpuva);
> INIT_LIST_HEAD(&vm_bo->list.entry.gem);
>
> + INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
> + INIT_LIST_HEAD(&vm_bo->list.entry.evict);
> +
> drm_gem_object_get(obj);
>
> return vm_bo;
> @@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
> if (!lock)
> drm_gpuvm_resv_assert_held(gpuvm);
>
> + drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
> + drm_gpuvm_bo_list_del(vm_bo, evict, lock);
> +
> list_del(&vm_bo->list.entry.gem);
>
> drm_gem_object_put(obj);
> @@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
> }
> EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
>
> +/**
> + * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
> + * extobj list
> + * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
> + *
> + * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
> + * already and if the corresponding &drm_gem_object is an external object,
> + * actually.
> + */
> +void
> +drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
> +{
> + struct drm_gpuvm *gpuvm = vm_bo->vm;
> + bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> + if (!lock)
> + drm_gpuvm_resv_assert_held(gpuvm);
> +
> + if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
> + drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
> +
> +/**
> + * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
> + * evicted list
> + * @vm_bo: the &drm_gpuvm_bo to add or remove
> + * @evict: indicates whether the object is evicted
> + *
> + * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
> + */
> +void
> +drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
> +{
> + struct drm_gpuvm *gpuvm = vm_bo->vm;
> + struct drm_gem_object *obj = vm_bo->obj;
> + bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> + dma_resv_assert_held(obj->resv);
> +
> + if (drm_gpuvm_is_extobj(gpuvm, obj)) {
> + vm_bo->evicted = evict;
Does the lock case also need this?
> +
> + if (!lock)
> + return;

Here the !lock case can not remove the gpuvm_bo from the list?

Small patch here that I've been using for xe:

https://patchwork.freedesktop.org/patch/561545/?series=124817&rev=1

Thanks,

Thomas


2023-10-09 14:46:59

by Danilo Krummrich

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects

On 10/9/23 15:36, Thomas Hellström wrote:
>
> On 10/9/23 01:32, Danilo Krummrich wrote:
>> Currently the DRM GPUVM offers common infrastructure to track GPU VA
>> allocations and mappings, generically connect GPU VA mappings to their
>> backing buffers and perform more complex mapping operations on the GPU VA
>> space.
>>
>> However, there are more design patterns commonly used by drivers, which
>> can potentially be generalized in order to make the DRM GPUVM represent
>> a basis for GPU-VM implementations. In this context, this patch aims
>> at generalizing the following elements.
>>
>> 1) Provide a common dma-resv for GEM objects not being used outside of
>>     this GPU-VM.
>>
>> 2) Provide tracking of external GEM objects (GEM objects which are
>>     shared with other GPU-VMs).
>>
>> 3) Provide functions to efficiently lock all GEM objects dma-resv the
>>     GPU-VM contains mappings of.
>>
>> 4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
>>     of, such that validation of evicted GEM objects is accelerated.
>>
>> 5) Provide some convinience functions for common patterns.
>>
>> Big thanks to Boris Brezillon for his help to figure out locking for
>> drivers updating the GPU VA space within the fence signalling path.
>>
>> Suggested-by: Matthew Brost <[email protected]>
>> Signed-off-by: Danilo Krummrich <[email protected]>
>> ---
>>   drivers/gpu/drm/drm_gpuvm.c | 646 ++++++++++++++++++++++++++++++++++++
>>   include/drm/drm_gpuvm.h     | 246 ++++++++++++++
>>   2 files changed, 892 insertions(+)
>>
>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>> index 28282283ddaf..6977bd30eca5 100644
>> --- a/drivers/gpu/drm/drm_gpuvm.c
>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>> @@ -82,6 +82,21 @@
>>    * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
>>    * particular combination. If not existent a new instance is created and linked
>>    * to the &drm_gem_object.
>> + *
>> + * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
>> + * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
>> + * list are maintained in order to accelerate locking of dma-resv locks and
>> + * validation of evicted objects bound in a &drm_gpuvm. For instance, all
>> + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
>> + * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
>> + * order to validate all evicted &drm_gem_objects. It is also possible to lock
>> + * additional &drm_gem_objects by providing the corresponding parameters to
>> + * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
>> + * use of helper functions such as drm_gpuvm_prepare_range() or
>> + * drm_gpuvm_prepare_objects().
>> + *
>> + * Every bound &drm_gem_object is treated as external object when its &dma_resv
>> + * structure is different than the &drm_gpuvm's common &dma_resv structure.
>>    */
>>   /**
>> @@ -429,6 +444,20 @@
>>    * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
>>    * &drm_gem_object must be able to observe previous creations and destructions
>>    * of &drm_gpuvm_bos in order to keep instances unique.
>> + *
>> + * The &drm_gpuvm's lists for keeping track of external and evicted objects are
>> + * protected against concurrent insertion / removal and iteration internally.
>> + *
>> + * However, drivers still need ensure to protect concurrent calls to functions
>> + * iterating those lists, namely drm_gpuvm_prepare_objects() and
>> + * drm_gpuvm_validate().
>> + *
>> + * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
>> + * that the corresponding &dma_resv locks are held in order to protect the
>> + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
>> + * the corresponding lockdep checks are enabled. This is an optimization for
>> + * drivers which are capable of taking the corresponding &dma_resv locks and
>> + * hence do not require internal locking.
>>    */
>>   /**
>> @@ -641,6 +670,195 @@
>>    *    }
>>    */
>> +/**
>> + * get_next_vm_bo_from_list() - get the next vm_bo element
>> + * @__gpuvm: The GPU VM
>> + * @__list_name: The name of the list we're iterating on
>> + * @__local_list: A pointer to the local list used to store already iterated items
>> + * @__prev_vm_bo: The previous element we got from drm_gpuvm_get_next_cached_vm_bo()
>> + *
>> + * This helper is here to provide lockless list iteration. Lockless as in, the
>> + * iterator releases the lock immediately after picking the first element from
>> + * the list, so list insertion deletion can happen concurrently.
>> + *
>> + * Elements popped from the original list are kept in a local list, so removal
>> + * and is_empty checks can still happen while we're iterating the list.
>> + */
>> +#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo)    \
>> +    ({                                        \
>> +        struct drm_gpuvm_bo *__vm_bo = NULL;                    \
>> +                                            \
>> +        drm_gpuvm_bo_put(__prev_vm_bo);                        \
>> +                                            \
>> +        spin_lock(&(__gpuvm)->__list_name.lock);                \
>> +        if (!(__gpuvm)->__list_name.local_list)                    \
>> +            (__gpuvm)->__list_name.local_list = __local_list;        \
>> +        else                                    \
>> +            WARN_ON((__gpuvm)->__list_name.local_list != __local_list);    \
>> +                                            \
>> +        while (!list_empty(&(__gpuvm)->__list_name.list)) {            \
>> +            __vm_bo = list_first_entry(&(__gpuvm)->__list_name.list,    \
>> +                           struct drm_gpuvm_bo,            \
>> +                           list.entry.__list_name);        \
>> +            if (kref_get_unless_zero(&__vm_bo->kref)) {            \
>> +                list_move_tail(&(__vm_bo)->list.entry.__list_name,    \
>> +                           __local_list);                \
>> +                break;                            \
>> +            } else {                            \
>> +                list_del_init(&(__vm_bo)->list.entry.__list_name);    \
>> +                __vm_bo = NULL;                        \
>> +            }                                \
>> +        }                                    \
>> +        spin_unlock(&(__gpuvm)->__list_name.lock);                \
>> +                                            \
>> +        __vm_bo;                                \
>> +    })
>> +
>> +/**
>> + * for_each_vm_bo_in_list() - internal vm_bo list iterator
>> + *
>> + * This helper is here to provide lockless list iteration. Lockless as in, the
>> + * iterator releases the lock immediately after picking the first element from the
>> + * list, hence list insertion and deletion can happen concurrently.
>> + *
>> + * It is not allowed to re-assign the vm_bo pointer from inside this loop.
>> + *
>> + * Typical use:
>> + *
>> + *    struct drm_gpuvm_bo *vm_bo;
>> + *    LIST_HEAD(my_local_list);
>> + *
>> + *    ret = 0;
>> + *    for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
>> + *        ret = do_something_with_vm_bo(..., vm_bo);
>> + *        if (ret)
>> + *            break;
>> + *    }
>> + *    drm_gpuvm_bo_put(vm_bo);
>> + *    restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
>> + *
>> + *
>> + * Only used for internal list iterations, not meant to be exposed to the outside
>> + * world.
>> + */
>> +#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo)    \
>> +    for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,        \
>> +                        __local_list, NULL);        \
>> +         __vm_bo;                                \
>> +         __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,        \
>> +                        __local_list, __vm_bo))
>> +
>> +static void
>> +__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>> +             struct list_head *list, struct list_head **local_list)
>> +{
>> +    /* Merge back the two lists, moving local list elements to the
>> +     * head to preserve previous ordering, in case it matters.
>> +     */
>> +    spin_lock(lock);
>> +    if (*local_list) {
>> +        list_splice(*local_list, list);
>> +        *local_list = NULL;
>> +    }
>> +    spin_unlock(lock);
>> +}
>> +
>> +/**
>> + * restore_vm_bo_list() - move vm_bo elements back to their original list
>> + * @__gpuvm: The GPU VM
>> + * @__list_name: The name of the list we're iterating on
>> + *
>> + * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
>> + * to restore the original state and let new iterations take place.
>> + */
>> +#define restore_vm_bo_list(__gpuvm, __list_name)            \
>> +    __restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock,    \
>> +                 &(__gpuvm)->__list_name.list,        \
>> +                 &(__gpuvm)->__list_name.local_list)
>> +
>> +static void
>> +cond_spin_lock(spinlock_t *lock, bool cond)
>> +{
>> +    if (cond)
>> +        spin_lock(lock);
>> +}
>> +
>> +static void
>> +cond_spin_unlock(spinlock_t *lock, bool cond)
>> +{
>> +    if (cond)
>> +        spin_unlock(lock);
>> +}
>> +
>> +static void
>> +__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>> +            struct list_head *entry, struct list_head *list)
>> +{
>> +    cond_spin_lock(lock, !!lock);
>> +    if (list_empty(entry))
>> +        list_add_tail(entry, list);
>> +    cond_spin_unlock(lock, !!lock);
>> +}
>> +
>> +/**
>> + * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
>> + * @__vm_bo: the &drm_gpuvm_bo
>> + * @__list_name: the name of the list to insert into
>> + * @__lock: whether to lock with the internal spinlock
>> + *
>> + * Inserts the given @__vm_bo into the list specified by @__list_name.
>> + */
>> +#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock)            \
>> +    __drm_gpuvm_bo_list_add((__vm_bo)->vm,                    \
>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>> +                     NULL,                    \
>> +                &(__vm_bo)->list.entry.__list_name,        \
>> +                &(__vm_bo)->vm->__list_name.list)
>> +
>> +static void
>> +__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>> +            struct list_head *entry, bool init)
>> +{
>> +    cond_spin_lock(lock, !!lock);
>> +    if (init) {
>> +        if (!list_empty(entry))
>> +            list_del_init(entry);
>> +    } else {
>> +        list_del(entry);
>> +    }
>> +    cond_spin_unlock(lock, !!lock);
>> +}
>> +
>> +/**
>> + * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
>> + * @__vm_bo: the &drm_gpuvm_bo
>> + * @__list_name: the name of the list to insert into
>> + * @__lock: whether to lock with the internal spinlock
>> + *
>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>> + */
>> +#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock)        \
>> +    __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>> +                     NULL,                    \
>> +                &(__vm_bo)->list.entry.__list_name,        \
>> +                true)
>> +
>> +/**
>> + * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
>> + * @__vm_bo: the &drm_gpuvm_bo
>> + * @__list_name: the name of the list to insert into
>> + * @__lock: whether to lock with the internal spinlock
>> + *
>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>> + */
>> +#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock)            \
>> +    __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>> +                     NULL,                    \
>> +                &(__vm_bo)->list.entry.__list_name,        \
>> +                false)
>> +
>>   #define to_drm_gpuva(__node)    container_of((__node), struct drm_gpuva, rb.node)
>>   #define GPUVA_START(node) ((node)->va.addr)
>> @@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
>>       gpuvm->rb.tree = RB_ROOT_CACHED;
>>       INIT_LIST_HEAD(&gpuvm->rb.list);
>> +    INIT_LIST_HEAD(&gpuvm->extobj.list);
>> +    spin_lock_init(&gpuvm->extobj.lock);
>> +
>> +    INIT_LIST_HEAD(&gpuvm->evict.list);
>> +    spin_lock_init(&gpuvm->evict.lock);
>> +
>>       drm_gpuvm_check_overflow(start_offset, range);
>>       gpuvm->mm_start = start_offset;
>>       gpuvm->mm_range = range;
>> @@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>       WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
>>            "GPUVA tree is not empty, potentially leaking memory.\n");
>> +    WARN(!list_empty(&gpuvm->extobj.list), "Extobj list should be empty.\n");
>> +    WARN(!list_empty(&gpuvm->evict.list), "Evict list should be empty.\n");
>> +
>>       drm_gem_object_put(gpuvm->r_obj);
>>   }
>>   EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>> +static int
>> +__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>> +                struct drm_exec *exec,
>> +                unsigned int num_fences)
>> +{
>> +    struct drm_gpuvm_bo *vm_bo;
>> +    LIST_HEAD(extobjs);
>> +    int ret = 0;
>> +
>> +    for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>> +        if (ret)
>> +            break;
>> +    }
>> +    /* Drop ref in case we break out of the loop. */
>> +    drm_gpuvm_bo_put(vm_bo);
>> +    restore_vm_bo_list(gpuvm, extobj);
>> +
>> +    return ret;
>> +}
>> +
>> +static int
>> +drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
>> +                 struct drm_exec *exec,
>> +                 unsigned int num_fences)
>> +{
>> +    struct drm_gpuvm_bo *vm_bo;
>> +    int ret = 0;
>> +
>> +    drm_gpuvm_resv_assert_held(gpuvm);
>> +    list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>> +        if (ret)
>> +            break;
>> +
>> +        if (vm_bo->evicted)
>> +            drm_gpuvm_bo_list_add(vm_bo, evict, false);
>
> Clear vm_bo->evicted here?

Why? It's still evicted, hence why not indicate it? It could be useful for a
validate_range() use case.

>
>
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +/**
>> + * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
>> + * @gpuvm: the &drm_gpuvm
>> + * @exec: the &drm_exec locking context
>> + * @num_fences: the amount of &dma_fences to reserve
>> + *
>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
>> + * &drm_gpuvm contains mappings of.
>> + *
>> + * Using this function directly, it is the drivers responsibility to call
>> + * drm_exec_init() and drm_exec_fini() accordingly.
>> + *
>> + * Note: This function is safe against concurrent insertion and removal of
>> + * external objects, however it is not safe against concurrent usage itself.
>> + *
>> + * Drivers need to make sure to protect this case with either an outer VM lock
>> + * or by calling drm_gpuvm_prepare_vm() before this function within the
>> + * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
>> + * mutual exclusion.
>> + *
>> + * Returns: 0 on success, negative error code on failure.
>> + */
>> +int
>> +drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>> +              struct drm_exec *exec,
>> +              unsigned int num_fences)
>> +{
>> +    if (drm_gpuvm_resv_protected(gpuvm))
>> +        return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
>> +                            num_fences);
>> +    else
>> +        return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>> +
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
>> +
>> +/**
>> + * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
>> + * @gpuvm: the &drm_gpuvm
>> + * @exec: the &drm_exec locking context
>> + * @addr: the start address within the VA space
>> + * @range: the range to iterate within the VA space
>> + * @num_fences: the amount of &dma_fences to reserve
>> + *
>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
>> + * and @addr + @range.
>> + *
>> + * Returns: 0 on success, negative error code on failure.
>> + */
>> +int
>> +drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
>> +            u64 addr, u64 range, unsigned int num_fences)
>> +{
>> +    struct drm_gpuva *va;
>> +    u64 end = addr + range;
>> +    int ret;
>> +
>> +    drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
>> +        struct drm_gem_object *obj = va->gem.obj;
>> +
>> +        ret = drm_exec_prepare_obj(exec, obj, num_fences);
>> +        if (ret)
>> +            return ret;
>> +    }
>> +
>> +    return 0;
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
>> +
>> +/**
>> + * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>> + * @num_fences: the amount of &dma_fences to reserve
>> + * @interruptible: sleep interruptible if waiting
>> + *
>> + * Acquires all dma-resv locks of all &drm_gem_objects the given
>> + * &drm_gpuvm contains mappings of.
>> + *
>> + * Addionally, when calling this function with struct drm_gpuvm_exec::extra
>> + * being set the driver receives the given @fn callback to lock additional
>> + * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
>> + * would call drm_exec_prepare_obj() from within this callback.
>> + *
>> + * Returns: 0 on success, negative error code on failure.
>> + */
>> +int
>> +drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
>> +            unsigned int num_fences,
>> +            bool interruptible)
>> +{
>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>> +    struct drm_exec *exec = &vm_exec->exec;
>> +    uint32_t flags;
>> +    int ret;
>> +
>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>> +        DRM_EXEC_IGNORE_DUPLICATES;
>> +
>> +    drm_exec_init(exec, flags);
>> +
>> +    drm_exec_until_all_locked(exec) {
>> +        ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
>> +        drm_exec_retry_on_contention(exec);
>> +        if (ret)
>> +            goto err;
>> +
>> +        ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>> +        drm_exec_retry_on_contention(exec);
>> +        if (ret)
>> +            goto err;
>> +
>> +        if (vm_exec->extra.fn) {
>> +            ret = vm_exec->extra.fn(vm_exec, num_fences);
>> +            drm_exec_retry_on_contention(exec);
>> +            if (ret)
>> +                goto err;
>> +        }
>> +    }
>> +
>> +    return 0;
>> +
>> +err:
>> +    drm_exec_fini(exec);
>> +    return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
>> +
>> +static int
>> +fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int num_fences)
>> +{
>> +    struct {
>> +        struct drm_gem_object **objs;
>> +        unsigned int num_objs;
>> +    } *args = vm_exec->extra.priv;
>> +
>> +    return drm_exec_prepare_array(&vm_exec->exec, args->objs,
>> +                      args->num_objs, num_fences);
>> +}
>> +
>> +/**
>> + * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>> + * @objs: additional &drm_gem_objects to lock
>> + * @num_objs: the number of additional &drm_gem_objects to lock
>> + * @num_fences: the amount of &dma_fences to reserve
>> + * @interruptible: sleep interruptible if waiting
>> + *
>> + * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
>> + * contains mappings of, plus the ones given through @objs.
>> + *
>> + * Returns: 0 on success, negative error code on failure.
>> + */
>> +int
>> +drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
>> +              struct drm_gem_object **objs,
>> +              unsigned int num_objs,
>> +              unsigned int num_fences,
>> +              bool interruptible)
>> +{
>> +    struct {
>> +        struct drm_gem_object **objs;
>> +        unsigned int num_objs;
>> +    } args;
>> +
>> +    args.objs = objs;
>> +    args.num_objs = num_objs;
>> +
>> +    vm_exec->extra.fn = fn_lock_array;
>> +    vm_exec->extra.priv = &args;
>> +
>> +    return drm_gpuvm_exec_lock(vm_exec, num_fences, interruptible);
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
>> +
>> +/**
>> + * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>> + * @addr: the start address within the VA space
>> + * @range: the range to iterate within the VA space
>> + * @num_fences: the amount of &dma_fences to reserve
>> + * @interruptible: sleep interruptible if waiting
>> + *
>> + * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
>> + * @addr + @range.
>> + *
>> + * Returns: 0 on success, negative error code on failure.
>> + */
>> +int
>> +drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
>> +              u64 addr, u64 range,
>> +              unsigned int num_fences,
>> +              bool interruptible)
>> +{
>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>> +    struct drm_exec *exec = &vm_exec->exec;
>> +    uint32_t flags;
>> +    int ret;
>> +
>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>> +        DRM_EXEC_IGNORE_DUPLICATES;
>> +
>> +    drm_exec_init(exec, flags);
>> +
>> +    drm_exec_until_all_locked(exec) {
>> +        ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
>> +                          num_fences);
>> +        drm_exec_retry_on_contention(exec);
>> +        if (ret)
>> +            goto err;
>> +    }
>> +
>> +    return ret;
>> +
>> +err:
>> +    drm_exec_fini(exec);
>> +    return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
>> +
>> +static int
>> +__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>> +{
>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>> +    struct drm_gpuvm_bo *vm_bo;
>> +    LIST_HEAD(evict);
>> +    int ret = 0;
>> +
>> +    for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>> +        if (ret)
>> +            break;
>> +    }
>> +    /* Drop ref in case we break out of the loop. */
>> +    drm_gpuvm_bo_put(vm_bo);
>> +    restore_vm_bo_list(gpuvm, evict);
>> +
>> +    return ret;
>> +}
>> +
>> +static int
>> +drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>> +{
>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>> +    struct drm_gpuvm_bo *vm_bo, *next;
>> +    int ret = 0;
>> +
>> +    drm_gpuvm_resv_assert_held(gpuvm);
>> +
>> +    /* Iterate list safely, drivers typically remove the current entry from
>> +     * their drm_gpuvm_ops::vm_bo_validate callback. Drivers might also
>> +     * re-add the entry on failure; this is safe since on failure we break
>> +     * out of the loop.
>> +     */
>> +    list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
>> +                 list.entry.evict) {
>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>> +        if (ret)
>> +            break;
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +/**
>> + * drm_gpuvm_validate() - validate all BOs marked as evicted
>> + * @gpuvm: the &drm_gpuvm to validate evicted BOs
>> + * @exec: the &drm_exec instance used for locking the GPUVM
>> + *
>> + * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
>> + * objects being mapped in the given &drm_gpuvm.
>> + *
>> + * Returns: 0 on success, negative error code on failure.
>> + */
>> +int
>> +drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>> +{
>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>> +
>> +    if (unlikely(!ops || !ops->vm_bo_validate))
>> +        return -ENOTSUPP;
>> +
>> +    if (drm_gpuvm_resv_protected(gpuvm))
>> +        return drm_gpuvm_validate_locked(gpuvm, exec);
>> +    else
>> +        return __drm_gpuvm_validate(gpuvm, exec);
>> +
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
>> +
>> +/**
>> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
>> + * dma-resv
>> + * @gpuvm: the &drm_gpuvm to add a fence to
>> + * @exec: the &drm_exec locking context
>> + * @fence: fence to add
>> + * @private_usage: private dma-resv usage
>> + * @extobj_usage: extobj dma-resv usage
>> + */
>> +void
>> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
>> +             struct drm_exec *exec,
>> +             struct dma_fence *fence,
>> +             enum dma_resv_usage private_usage,
>> +             enum dma_resv_usage extobj_usage)
>> +{
>> +    struct drm_gem_object *obj;
>> +    unsigned long index;
>> +
>> +    drm_exec_for_each_locked_object(exec, index, obj) {
>> +        dma_resv_assert_held(obj->resv);
>> +        dma_resv_add_fence(obj->resv, fence,
>> +                   drm_gpuvm_is_extobj(gpuvm, obj) ?
>> +                   private_usage : extobj_usage);
>> +    }
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
>> +
>>   /**
>>    * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
>>    * @gpuvm: The &drm_gpuvm the @obj is mapped in.
>> @@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
>>       INIT_LIST_HEAD(&vm_bo->list.gpuva);
>>       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
>> +    INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
>> +    INIT_LIST_HEAD(&vm_bo->list.entry.evict);
>> +
>>       drm_gem_object_get(obj);
>>       return vm_bo;
>> @@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
>>       if (!lock)
>>           drm_gpuvm_resv_assert_held(gpuvm);
>> +    drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
>> +    drm_gpuvm_bo_list_del(vm_bo, evict, lock);
>> +
>>       list_del(&vm_bo->list.entry.gem);
>>       drm_gem_object_put(obj);
>> @@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
>>   }
>>   EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
>> +/**
>> + * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
>> + * extobj list
>> + * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
>> + *
>> + * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
>> + * already and if the corresponding &drm_gem_object is an external object,
>> + * actually.
>> + */
>> +void
>> +drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
>> +{
>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>> +
>> +    if (!lock)
>> +        drm_gpuvm_resv_assert_held(gpuvm);
>> +
>> +    if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
>> +        drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
>> +
>> +/**
>> + * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
>> + * evicted list
>> + * @vm_bo: the &drm_gpuvm_bo to add or remove
>> + * @evict: indicates whether the object is evicted
>> + *
>> + * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
>> + */
>> +void
>> +drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
>> +{
>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>> +    struct drm_gem_object *obj = vm_bo->obj;
>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>> +
>> +    dma_resv_assert_held(obj->resv);
>> +
>> +    if (drm_gpuvm_is_extobj(gpuvm, obj)) {
>> +        vm_bo->evicted = evict;
> Does the lock case also need this?

It doesn't need it by itself, but since we have drm_gpuvm_bo::evicted now, I want it to
consistently indicate whether the BO is evicted or not.

>> +
>> +        if (!lock)
>> +            return;
>
> Here the !lock case can not remove the gpuvm_bo from the list?

You mean because we'd expect that drm_gpuvm_bo_evict(vm_bo, false) can only be called from
within gpuvm_validate(), which requires the VM's resv lock? What if there is a ttm_validate()
call for only this GEM obj?

My idea was to remove VM_BOs from the evicted list in gpuvm_validate() directly, but I'm afraid
I forgot that.

>
> Small patch here that I've been using for xe:
>
> https://patchwork.freedesktop.org/patch/561545/?series=124817&rev=1
>
> Thanks,
>
> Thomas
>
>

2023-10-09 20:55:58

by Danilo Krummrich

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects

On 10/9/23 16:45, Danilo Krummrich wrote:
> On 10/9/23 15:36, Thomas Hellström wrote:
>>
>> On 10/9/23 01:32, Danilo Krummrich wrote:
>>> Currently the DRM GPUVM offers common infrastructure to track GPU VA
>>> allocations and mappings, generically connect GPU VA mappings to their
>>> backing buffers and perform more complex mapping operations on the GPU VA
>>> space.
>>>
>>> However, there are more design patterns commonly used by drivers, which
>>> can potentially be generalized in order to make the DRM GPUVM represent
>>> a basis for GPU-VM implementations. In this context, this patch aims
>>> at generalizing the following elements.
>>>
>>> 1) Provide a common dma-resv for GEM objects not being used outside of
>>>     this GPU-VM.
>>>
>>> 2) Provide tracking of external GEM objects (GEM objects which are
>>>     shared with other GPU-VMs).
>>>
>>> 3) Provide functions to efficiently lock all GEM objects dma-resv the
>>>     GPU-VM contains mappings of.
>>>
>>> 4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
>>>     of, such that validation of evicted GEM objects is accelerated.
>>>
>>> 5) Provide some convinience functions for common patterns.
>>>
>>> Big thanks to Boris Brezillon for his help to figure out locking for
>>> drivers updating the GPU VA space within the fence signalling path.
>>>
>>> Suggested-by: Matthew Brost <[email protected]>
>>> Signed-off-by: Danilo Krummrich <[email protected]>
>>> ---
>>>   drivers/gpu/drm/drm_gpuvm.c | 646 ++++++++++++++++++++++++++++++++++++
>>>   include/drm/drm_gpuvm.h     | 246 ++++++++++++++
>>>   2 files changed, 892 insertions(+)
>>>
>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>> index 28282283ddaf..6977bd30eca5 100644
>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>> @@ -82,6 +82,21 @@
>>>    * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
>>>    * particular combination. If not existent a new instance is created and linked
>>>    * to the &drm_gem_object.
>>> + *
>>> + * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
>>> + * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
>>> + * list are maintained in order to accelerate locking of dma-resv locks and
>>> + * validation of evicted objects bound in a &drm_gpuvm. For instance, all
>>> + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
>>> + * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
>>> + * order to validate all evicted &drm_gem_objects. It is also possible to lock
>>> + * additional &drm_gem_objects by providing the corresponding parameters to
>>> + * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
>>> + * use of helper functions such as drm_gpuvm_prepare_range() or
>>> + * drm_gpuvm_prepare_objects().
>>> + *
>>> + * Every bound &drm_gem_object is treated as external object when its &dma_resv
>>> + * structure is different than the &drm_gpuvm's common &dma_resv structure.
>>>    */
>>>   /**
>>> @@ -429,6 +444,20 @@
>>>    * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
>>>    * &drm_gem_object must be able to observe previous creations and destructions
>>>    * of &drm_gpuvm_bos in order to keep instances unique.
>>> + *
>>> + * The &drm_gpuvm's lists for keeping track of external and evicted objects are
>>> + * protected against concurrent insertion / removal and iteration internally.
>>> + *
>>> + * However, drivers still need ensure to protect concurrent calls to functions
>>> + * iterating those lists, namely drm_gpuvm_prepare_objects() and
>>> + * drm_gpuvm_validate().
>>> + *
>>> + * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
>>> + * that the corresponding &dma_resv locks are held in order to protect the
>>> + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
>>> + * the corresponding lockdep checks are enabled. This is an optimization for
>>> + * drivers which are capable of taking the corresponding &dma_resv locks and
>>> + * hence do not require internal locking.
>>>    */
>>>   /**
>>> @@ -641,6 +670,195 @@
>>>    *    }
>>>    */
>>> +/**
>>> + * get_next_vm_bo_from_list() - get the next vm_bo element
>>> + * @__gpuvm: The GPU VM
>>> + * @__list_name: The name of the list we're iterating on
>>> + * @__local_list: A pointer to the local list used to store already iterated items
>>> + * @__prev_vm_bo: The previous element we got from drm_gpuvm_get_next_cached_vm_bo()
>>> + *
>>> + * This helper is here to provide lockless list iteration. Lockless as in, the
>>> + * iterator releases the lock immediately after picking the first element from
>>> + * the list, so list insertion deletion can happen concurrently.
>>> + *
>>> + * Elements popped from the original list are kept in a local list, so removal
>>> + * and is_empty checks can still happen while we're iterating the list.
>>> + */
>>> +#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo)    \
>>> +    ({                                        \
>>> +        struct drm_gpuvm_bo *__vm_bo = NULL;                    \
>>> +                                            \
>>> +        drm_gpuvm_bo_put(__prev_vm_bo);                        \
>>> +                                            \
>>> +        spin_lock(&(__gpuvm)->__list_name.lock);                \
>>> +        if (!(__gpuvm)->__list_name.local_list)                    \
>>> +            (__gpuvm)->__list_name.local_list = __local_list;        \
>>> +        else                                    \
>>> +            WARN_ON((__gpuvm)->__list_name.local_list != __local_list);    \
>>> +                                            \
>>> +        while (!list_empty(&(__gpuvm)->__list_name.list)) {            \
>>> +            __vm_bo = list_first_entry(&(__gpuvm)->__list_name.list,    \
>>> +                           struct drm_gpuvm_bo,            \
>>> +                           list.entry.__list_name);        \
>>> +            if (kref_get_unless_zero(&__vm_bo->kref)) {            \
>>> +                list_move_tail(&(__vm_bo)->list.entry.__list_name,    \
>>> +                           __local_list);                \
>>> +                break;                            \
>>> +            } else {                            \
>>> +                list_del_init(&(__vm_bo)->list.entry.__list_name);    \
>>> +                __vm_bo = NULL;                        \
>>> +            }                                \
>>> +        }                                    \
>>> +        spin_unlock(&(__gpuvm)->__list_name.lock);                \
>>> +                                            \
>>> +        __vm_bo;                                \
>>> +    })
>>> +
>>> +/**
>>> + * for_each_vm_bo_in_list() - internal vm_bo list iterator
>>> + *
>>> + * This helper is here to provide lockless list iteration. Lockless as in, the
>>> + * iterator releases the lock immediately after picking the first element from the
>>> + * list, hence list insertion and deletion can happen concurrently.
>>> + *
>>> + * It is not allowed to re-assign the vm_bo pointer from inside this loop.
>>> + *
>>> + * Typical use:
>>> + *
>>> + *    struct drm_gpuvm_bo *vm_bo;
>>> + *    LIST_HEAD(my_local_list);
>>> + *
>>> + *    ret = 0;
>>> + *    for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
>>> + *        ret = do_something_with_vm_bo(..., vm_bo);
>>> + *        if (ret)
>>> + *            break;
>>> + *    }
>>> + *    drm_gpuvm_bo_put(vm_bo);
>>> + *    restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
>>> + *
>>> + *
>>> + * Only used for internal list iterations, not meant to be exposed to the outside
>>> + * world.
>>> + */
>>> +#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo)    \
>>> +    for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,        \
>>> +                        __local_list, NULL);        \
>>> +         __vm_bo;                                \
>>> +         __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,        \
>>> +                        __local_list, __vm_bo))
>>> +
>>> +static void
>>> +__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>> +             struct list_head *list, struct list_head **local_list)
>>> +{
>>> +    /* Merge back the two lists, moving local list elements to the
>>> +     * head to preserve previous ordering, in case it matters.
>>> +     */
>>> +    spin_lock(lock);
>>> +    if (*local_list) {
>>> +        list_splice(*local_list, list);
>>> +        *local_list = NULL;
>>> +    }
>>> +    spin_unlock(lock);
>>> +}
>>> +
>>> +/**
>>> + * restore_vm_bo_list() - move vm_bo elements back to their original list
>>> + * @__gpuvm: The GPU VM
>>> + * @__list_name: The name of the list we're iterating on
>>> + *
>>> + * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
>>> + * to restore the original state and let new iterations take place.
>>> + */
>>> +#define restore_vm_bo_list(__gpuvm, __list_name)            \
>>> +    __restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock,    \
>>> +                 &(__gpuvm)->__list_name.list,        \
>>> +                 &(__gpuvm)->__list_name.local_list)
>>> +
>>> +static void
>>> +cond_spin_lock(spinlock_t *lock, bool cond)
>>> +{
>>> +    if (cond)
>>> +        spin_lock(lock);
>>> +}
>>> +
>>> +static void
>>> +cond_spin_unlock(spinlock_t *lock, bool cond)
>>> +{
>>> +    if (cond)
>>> +        spin_unlock(lock);
>>> +}
>>> +
>>> +static void
>>> +__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>> +            struct list_head *entry, struct list_head *list)
>>> +{
>>> +    cond_spin_lock(lock, !!lock);
>>> +    if (list_empty(entry))
>>> +        list_add_tail(entry, list);
>>> +    cond_spin_unlock(lock, !!lock);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
>>> + * @__vm_bo: the &drm_gpuvm_bo
>>> + * @__list_name: the name of the list to insert into
>>> + * @__lock: whether to lock with the internal spinlock
>>> + *
>>> + * Inserts the given @__vm_bo into the list specified by @__list_name.
>>> + */
>>> +#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock)            \
>>> +    __drm_gpuvm_bo_list_add((__vm_bo)->vm,                    \
>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>> +                     NULL,                    \
>>> +                &(__vm_bo)->list.entry.__list_name,        \
>>> +                &(__vm_bo)->vm->__list_name.list)
>>> +
>>> +static void
>>> +__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>> +            struct list_head *entry, bool init)
>>> +{
>>> +    cond_spin_lock(lock, !!lock);
>>> +    if (init) {
>>> +        if (!list_empty(entry))
>>> +            list_del_init(entry);
>>> +    } else {
>>> +        list_del(entry);
>>> +    }
>>> +    cond_spin_unlock(lock, !!lock);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
>>> + * @__vm_bo: the &drm_gpuvm_bo
>>> + * @__list_name: the name of the list to insert into
>>> + * @__lock: whether to lock with the internal spinlock
>>> + *
>>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>>> + */
>>> +#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock)        \
>>> +    __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>> +                     NULL,                    \
>>> +                &(__vm_bo)->list.entry.__list_name,        \
>>> +                true)
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
>>> + * @__vm_bo: the &drm_gpuvm_bo
>>> + * @__list_name: the name of the list to insert into
>>> + * @__lock: whether to lock with the internal spinlock
>>> + *
>>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>>> + */
>>> +#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock)            \
>>> +    __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>> +                     NULL,                    \
>>> +                &(__vm_bo)->list.entry.__list_name,        \
>>> +                false)
>>> +
>>>   #define to_drm_gpuva(__node)    container_of((__node), struct drm_gpuva, rb.node)
>>>   #define GPUVA_START(node) ((node)->va.addr)
>>> @@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
>>>       gpuvm->rb.tree = RB_ROOT_CACHED;
>>>       INIT_LIST_HEAD(&gpuvm->rb.list);
>>> +    INIT_LIST_HEAD(&gpuvm->extobj.list);
>>> +    spin_lock_init(&gpuvm->extobj.lock);
>>> +
>>> +    INIT_LIST_HEAD(&gpuvm->evict.list);
>>> +    spin_lock_init(&gpuvm->evict.lock);
>>> +
>>>       drm_gpuvm_check_overflow(start_offset, range);
>>>       gpuvm->mm_start = start_offset;
>>>       gpuvm->mm_range = range;
>>> @@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>       WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
>>>            "GPUVA tree is not empty, potentially leaking memory.\n");
>>> +    WARN(!list_empty(&gpuvm->extobj.list), "Extobj list should be empty.\n");
>>> +    WARN(!list_empty(&gpuvm->evict.list), "Evict list should be empty.\n");
>>> +
>>>       drm_gem_object_put(gpuvm->r_obj);
>>>   }
>>>   EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>> +static int
>>> +__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>>> +                struct drm_exec *exec,
>>> +                unsigned int num_fences)
>>> +{
>>> +    struct drm_gpuvm_bo *vm_bo;
>>> +    LIST_HEAD(extobjs);
>>> +    int ret = 0;
>>> +
>>> +    for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
>>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>>> +        if (ret)
>>> +            break;
>>> +    }
>>> +    /* Drop ref in case we break out of the loop. */
>>> +    drm_gpuvm_bo_put(vm_bo);
>>> +    restore_vm_bo_list(gpuvm, extobj);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int
>>> +drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
>>> +                 struct drm_exec *exec,
>>> +                 unsigned int num_fences)
>>> +{
>>> +    struct drm_gpuvm_bo *vm_bo;
>>> +    int ret = 0;
>>> +
>>> +    drm_gpuvm_resv_assert_held(gpuvm);
>>> +    list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
>>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>>> +        if (ret)
>>> +            break;
>>> +
>>> +        if (vm_bo->evicted)
>>> +            drm_gpuvm_bo_list_add(vm_bo, evict, false);
>>
>> Clear vm_bo->evicted here?
>
> Why? It's still evicted, hence why not indicate it? It could be useful for a
> validate_range() use case.
>
>>
>>
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
>>> + * @gpuvm: the &drm_gpuvm
>>> + * @exec: the &drm_exec locking context
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + *
>>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
>>> + * &drm_gpuvm contains mappings of.
>>> + *
>>> + * Using this function directly, it is the drivers responsibility to call
>>> + * drm_exec_init() and drm_exec_fini() accordingly.
>>> + *
>>> + * Note: This function is safe against concurrent insertion and removal of
>>> + * external objects, however it is not safe against concurrent usage itself.
>>> + *
>>> + * Drivers need to make sure to protect this case with either an outer VM lock
>>> + * or by calling drm_gpuvm_prepare_vm() before this function within the
>>> + * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
>>> + * mutual exclusion.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>>> +              struct drm_exec *exec,
>>> +              unsigned int num_fences)
>>> +{
>>> +    if (drm_gpuvm_resv_protected(gpuvm))
>>> +        return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
>>> +                            num_fences);
>>> +    else
>>> +        return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>>> +
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
>>> +
>>> +/**
>>> + * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
>>> + * @gpuvm: the &drm_gpuvm
>>> + * @exec: the &drm_exec locking context
>>> + * @addr: the start address within the VA space
>>> + * @range: the range to iterate within the VA space
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + *
>>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
>>> + * and @addr + @range.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
>>> +            u64 addr, u64 range, unsigned int num_fences)
>>> +{
>>> +    struct drm_gpuva *va;
>>> +    u64 end = addr + range;
>>> +    int ret;
>>> +
>>> +    drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
>>> +        struct drm_gem_object *obj = va->gem.obj;
>>> +
>>> +        ret = drm_exec_prepare_obj(exec, obj, num_fences);
>>> +        if (ret)
>>> +            return ret;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
>>> +
>>> +/**
>>> + * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + * @interruptible: sleep interruptible if waiting
>>> + *
>>> + * Acquires all dma-resv locks of all &drm_gem_objects the given
>>> + * &drm_gpuvm contains mappings of.
>>> + *
>>> + * Addionally, when calling this function with struct drm_gpuvm_exec::extra
>>> + * being set the driver receives the given @fn callback to lock additional
>>> + * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
>>> + * would call drm_exec_prepare_obj() from within this callback.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
>>> +            unsigned int num_fences,
>>> +            bool interruptible)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>>> +    struct drm_exec *exec = &vm_exec->exec;
>>> +    uint32_t flags;
>>> +    int ret;
>>> +
>>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>>> +        DRM_EXEC_IGNORE_DUPLICATES;
>>> +
>>> +    drm_exec_init(exec, flags);
>>> +
>>> +    drm_exec_until_all_locked(exec) {
>>> +        ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
>>> +        drm_exec_retry_on_contention(exec);
>>> +        if (ret)
>>> +            goto err;
>>> +
>>> +        ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>>> +        drm_exec_retry_on_contention(exec);
>>> +        if (ret)
>>> +            goto err;
>>> +
>>> +        if (vm_exec->extra.fn) {
>>> +            ret = vm_exec->extra.fn(vm_exec, num_fences);
>>> +            drm_exec_retry_on_contention(exec);
>>> +            if (ret)
>>> +                goto err;
>>> +        }
>>> +    }
>>> +
>>> +    return 0;
>>> +
>>> +err:
>>> +    drm_exec_fini(exec);
>>> +    return ret;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
>>> +
>>> +static int
>>> +fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int num_fences)
>>> +{
>>> +    struct {
>>> +        struct drm_gem_object **objs;
>>> +        unsigned int num_objs;
>>> +    } *args = vm_exec->extra.priv;
>>> +
>>> +    return drm_exec_prepare_array(&vm_exec->exec, args->objs,
>>> +                      args->num_objs, num_fences);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>> + * @objs: additional &drm_gem_objects to lock
>>> + * @num_objs: the number of additional &drm_gem_objects to lock
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + * @interruptible: sleep interruptible if waiting
>>> + *
>>> + * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
>>> + * contains mappings of, plus the ones given through @objs.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
>>> +              struct drm_gem_object **objs,
>>> +              unsigned int num_objs,
>>> +              unsigned int num_fences,
>>> +              bool interruptible)
>>> +{
>>> +    struct {
>>> +        struct drm_gem_object **objs;
>>> +        unsigned int num_objs;
>>> +    } args;
>>> +
>>> +    args.objs = objs;
>>> +    args.num_objs = num_objs;
>>> +
>>> +    vm_exec->extra.fn = fn_lock_array;
>>> +    vm_exec->extra.priv = &args;
>>> +
>>> +    return drm_gpuvm_exec_lock(vm_exec, num_fences, interruptible);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
>>> +
>>> +/**
>>> + * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>> + * @addr: the start address within the VA space
>>> + * @range: the range to iterate within the VA space
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + * @interruptible: sleep interruptible if waiting
>>> + *
>>> + * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
>>> + * @addr + @range.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
>>> +              u64 addr, u64 range,
>>> +              unsigned int num_fences,
>>> +              bool interruptible)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>>> +    struct drm_exec *exec = &vm_exec->exec;
>>> +    uint32_t flags;
>>> +    int ret;
>>> +
>>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>>> +        DRM_EXEC_IGNORE_DUPLICATES;
>>> +
>>> +    drm_exec_init(exec, flags);
>>> +
>>> +    drm_exec_until_all_locked(exec) {
>>> +        ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
>>> +                          num_fences);
>>> +        drm_exec_retry_on_contention(exec);
>>> +        if (ret)
>>> +            goto err;
>>> +    }
>>> +
>>> +    return ret;
>>> +
>>> +err:
>>> +    drm_exec_fini(exec);
>>> +    return ret;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
>>> +
>>> +static int
>>> +__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>> +{
>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +    struct drm_gpuvm_bo *vm_bo;
>>> +    LIST_HEAD(evict);
>>> +    int ret = 0;
>>> +
>>> +    for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
>>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>>> +        if (ret)
>>> +            break;
>>> +    }
>>> +    /* Drop ref in case we break out of the loop. */
>>> +    drm_gpuvm_bo_put(vm_bo);
>>> +    restore_vm_bo_list(gpuvm, evict);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int
>>> +drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>> +{
>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +    struct drm_gpuvm_bo *vm_bo, *next;
>>> +    int ret = 0;
>>> +
>>> +    drm_gpuvm_resv_assert_held(gpuvm);
>>> +
>>> +    /* Iterate list safely, drivers typically remove the current entry from
>>> +     * their drm_gpuvm_ops::vm_bo_validate callback. Drivers might also
>>> +     * re-add the entry on failure; this is safe since on failure we break
>>> +     * out of the loop.
>>> +     */
>>> +    list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
>>> +                 list.entry.evict) {
>>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>>> +        if (ret)
>>> +            break;
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_validate() - validate all BOs marked as evicted
>>> + * @gpuvm: the &drm_gpuvm to validate evicted BOs
>>> + * @exec: the &drm_exec instance used for locking the GPUVM
>>> + *
>>> + * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
>>> + * objects being mapped in the given &drm_gpuvm.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>> +{
>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +
>>> +    if (unlikely(!ops || !ops->vm_bo_validate))
>>> +        return -ENOTSUPP;
>>> +
>>> +    if (drm_gpuvm_resv_protected(gpuvm))
>>> +        return drm_gpuvm_validate_locked(gpuvm, exec);
>>> +    else
>>> +        return __drm_gpuvm_validate(gpuvm, exec);
>>> +
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
>>> +
>>> +/**
>>> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
>>> + * dma-resv
>>> + * @gpuvm: the &drm_gpuvm to add a fence to
>>> + * @exec: the &drm_exec locking context
>>> + * @fence: fence to add
>>> + * @private_usage: private dma-resv usage
>>> + * @extobj_usage: extobj dma-resv usage
>>> + */
>>> +void
>>> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
>>> +             struct drm_exec *exec,
>>> +             struct dma_fence *fence,
>>> +             enum dma_resv_usage private_usage,
>>> +             enum dma_resv_usage extobj_usage)
>>> +{
>>> +    struct drm_gem_object *obj;
>>> +    unsigned long index;
>>> +
>>> +    drm_exec_for_each_locked_object(exec, index, obj) {
>>> +        dma_resv_assert_held(obj->resv);
>>> +        dma_resv_add_fence(obj->resv, fence,
>>> +                   drm_gpuvm_is_extobj(gpuvm, obj) ?
>>> +                   private_usage : extobj_usage);
>>> +    }
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
>>> +
>>>   /**
>>>    * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
>>>    * @gpuvm: The &drm_gpuvm the @obj is mapped in.
>>> @@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
>>>       INIT_LIST_HEAD(&vm_bo->list.gpuva);
>>>       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
>>> +    INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
>>> +    INIT_LIST_HEAD(&vm_bo->list.entry.evict);
>>> +
>>>       drm_gem_object_get(obj);
>>>       return vm_bo;
>>> @@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
>>>       if (!lock)
>>>           drm_gpuvm_resv_assert_held(gpuvm);
>>> +    drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
>>> +    drm_gpuvm_bo_list_del(vm_bo, evict, lock);
>>> +
>>>       list_del(&vm_bo->list.entry.gem);
>>>       drm_gem_object_put(obj);
>>> @@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
>>>   }
>>>   EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
>>> +/**
>>> + * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
>>> + * extobj list
>>> + * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
>>> + *
>>> + * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
>>> + * already and if the corresponding &drm_gem_object is an external object,
>>> + * actually.
>>> + */
>>> +void
>>> +drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>>> +
>>> +    if (!lock)
>>> +        drm_gpuvm_resv_assert_held(gpuvm);
>>> +
>>> +    if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
>>> +        drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
>>> + * evicted list
>>> + * @vm_bo: the &drm_gpuvm_bo to add or remove
>>> + * @evict: indicates whether the object is evicted
>>> + *
>>> + * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
>>> + */
>>> +void
>>> +drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>>> +    struct drm_gem_object *obj = vm_bo->obj;
>>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>>> +
>>> +    dma_resv_assert_held(obj->resv);
>>> +
>>> +    if (drm_gpuvm_is_extobj(gpuvm, obj)) {
>>> +        vm_bo->evicted = evict;
>> Does the lock case also need this?
>
> It doesn't need it by itself, but since we have drm_gpuvm_bo::evicted now, I want it to
> consistently indicate whether the BO is evicted or not.
>
>>> +
>>> +        if (!lock)
>>> +            return;
>>
>> Here the !lock case can not remove the gpuvm_bo from the list?
>
> You mean because we'd expect that drm_gpuvm_bo_evict(vm_bo, false) can only be called from
> within gpuvm_validate(), which requires the VM's resv lock? What if there is a ttm_validate()
> call for only this GEM obj?
>
> My idea was to remove VM_BOs from the evicted list in gpuvm_validate() directly, but I'm afraid
> I forgot that.

Fixed in [1].

[1] https://gitlab.freedesktop.org/nouvelles/kernel/-/tree/gpuvm-next-fixes

>
>>
>> Small patch here that I've been using for xe:
>>
>> https://patchwork.freedesktop.org/patch/561545/?series=124817&rev=1
>>
>> Thanks,
>>
>> Thomas
>>
>>

2023-10-10 06:26:50

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects


On 10/9/23 16:45, Danilo Krummrich wrote:
> On 10/9/23 15:36, Thomas Hellström wrote:
>>
>> On 10/9/23 01:32, Danilo Krummrich wrote:
>>> Currently the DRM GPUVM offers common infrastructure to track GPU VA
>>> allocations and mappings, generically connect GPU VA mappings to their
>>> backing buffers and perform more complex mapping operations on the
>>> GPU VA
>>> space.
>>>
>>> However, there are more design patterns commonly used by drivers, which
>>> can potentially be generalized in order to make the DRM GPUVM represent
>>> a basis for GPU-VM implementations. In this context, this patch aims
>>> at generalizing the following elements.
>>>
>>> 1) Provide a common dma-resv for GEM objects not being used outside of
>>>     this GPU-VM.
>>>
>>> 2) Provide tracking of external GEM objects (GEM objects which are
>>>     shared with other GPU-VMs).
>>>
>>> 3) Provide functions to efficiently lock all GEM objects dma-resv the
>>>     GPU-VM contains mappings of.
>>>
>>> 4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
>>>     of, such that validation of evicted GEM objects is accelerated.
>>>
>>> 5) Provide some convinience functions for common patterns.
>>>
>>> Big thanks to Boris Brezillon for his help to figure out locking for
>>> drivers updating the GPU VA space within the fence signalling path.
>>>
>>> Suggested-by: Matthew Brost <[email protected]>
>>> Signed-off-by: Danilo Krummrich <[email protected]>
>>> ---
>>>   drivers/gpu/drm/drm_gpuvm.c | 646
>>> ++++++++++++++++++++++++++++++++++++
>>>   include/drm/drm_gpuvm.h     | 246 ++++++++++++++
>>>   2 files changed, 892 insertions(+)
>>>
>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>> index 28282283ddaf..6977bd30eca5 100644
>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>> @@ -82,6 +82,21 @@
>>>    * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
>>> of this
>>>    * particular combination. If not existent a new instance is
>>> created and linked
>>>    * to the &drm_gem_object.
>>> + *
>>> + * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm,
>>> are also used
>>> + * as entry for the &drm_gpuvm's lists of external and evicted
>>> objects. Those
>>> + * list are maintained in order to accelerate locking of dma-resv
>>> locks and
>>> + * validation of evicted objects bound in a &drm_gpuvm. For
>>> instance, all
>>> + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked
>>> by calling
>>> + * drm_gpuvm_exec_lock(). Once locked drivers can call
>>> drm_gpuvm_validate() in
>>> + * order to validate all evicted &drm_gem_objects. It is also
>>> possible to lock
>>> + * additional &drm_gem_objects by providing the corresponding
>>> parameters to
>>> + * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop
>>> while making
>>> + * use of helper functions such as drm_gpuvm_prepare_range() or
>>> + * drm_gpuvm_prepare_objects().
>>> + *
>>> + * Every bound &drm_gem_object is treated as external object when
>>> its &dma_resv
>>> + * structure is different than the &drm_gpuvm's common &dma_resv
>>> structure.
>>>    */
>>>   /**
>>> @@ -429,6 +444,20 @@
>>>    * Subsequent calls to drm_gpuvm_bo_obtain() for the same
>>> &drm_gpuvm and
>>>    * &drm_gem_object must be able to observe previous creations and
>>> destructions
>>>    * of &drm_gpuvm_bos in order to keep instances unique.
>>> + *
>>> + * The &drm_gpuvm's lists for keeping track of external and evicted
>>> objects are
>>> + * protected against concurrent insertion / removal and iteration
>>> internally.
>>> + *
>>> + * However, drivers still need ensure to protect concurrent calls
>>> to functions
>>> + * iterating those lists, namely drm_gpuvm_prepare_objects() and
>>> + * drm_gpuvm_validate().
>>> + *
>>> + * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED
>>> flag to indicate
>>> + * that the corresponding &dma_resv locks are held in order to
>>> protect the
>>> + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is
>>> disabled and
>>> + * the corresponding lockdep checks are enabled. This is an
>>> optimization for
>>> + * drivers which are capable of taking the corresponding &dma_resv
>>> locks and
>>> + * hence do not require internal locking.
>>>    */
>>>   /**
>>> @@ -641,6 +670,195 @@
>>>    *    }
>>>    */
>>> +/**
>>> + * get_next_vm_bo_from_list() - get the next vm_bo element
>>> + * @__gpuvm: The GPU VM
>>> + * @__list_name: The name of the list we're iterating on
>>> + * @__local_list: A pointer to the local list used to store already
>>> iterated items
>>> + * @__prev_vm_bo: The previous element we got from
>>> drm_gpuvm_get_next_cached_vm_bo()
>>> + *
>>> + * This helper is here to provide lockless list iteration. Lockless
>>> as in, the
>>> + * iterator releases the lock immediately after picking the first
>>> element from
>>> + * the list, so list insertion deletion can happen concurrently.
>>> + *
>>> + * Elements popped from the original list are kept in a local list,
>>> so removal
>>> + * and is_empty checks can still happen while we're iterating the
>>> list.
>>> + */
>>> +#define get_next_vm_bo_from_list(__gpuvm, __list_name,
>>> __local_list, __prev_vm_bo)    \
>>> +    ({                                        \
>>> +        struct drm_gpuvm_bo *__vm_bo = NULL;                    \
>>> +                                            \
>>> + drm_gpuvm_bo_put(__prev_vm_bo);                        \
>>> +                                            \
>>> + spin_lock(&(__gpuvm)->__list_name.lock); \
>>> +        if (!(__gpuvm)->__list_name.local_list)                    \
>>> +            (__gpuvm)->__list_name.local_list = __local_list;        \
>>> +        else                                    \
>>> +            WARN_ON((__gpuvm)->__list_name.local_list !=
>>> __local_list);    \
>>> +                                            \
>>> +        while (!list_empty(&(__gpuvm)->__list_name.list))
>>> {            \
>>> +            __vm_bo =
>>> list_first_entry(&(__gpuvm)->__list_name.list,    \
>>> +                           struct drm_gpuvm_bo,            \
>>> +                           list.entry.__list_name);        \
>>> +            if (kref_get_unless_zero(&__vm_bo->kref)) {            \
>>> + list_move_tail(&(__vm_bo)->list.entry.__list_name,    \
>>> +                           __local_list);                \
>>> +                break;                            \
>>> +            } else {                            \
>>> + list_del_init(&(__vm_bo)->list.entry.__list_name);    \
>>> +                __vm_bo = NULL;                        \
>>> +            }                                \
>>> +        }                                    \
>>> + spin_unlock(&(__gpuvm)->__list_name.lock); \
>>> +                                            \
>>> +        __vm_bo;                                \
>>> +    })
>>> +
>>> +/**
>>> + * for_each_vm_bo_in_list() - internal vm_bo list iterator
>>> + *
>>> + * This helper is here to provide lockless list iteration. Lockless
>>> as in, the
>>> + * iterator releases the lock immediately after picking the first
>>> element from the
>>> + * list, hence list insertion and deletion can happen concurrently.
>>> + *
>>> + * It is not allowed to re-assign the vm_bo pointer from inside
>>> this loop.
>>> + *
>>> + * Typical use:
>>> + *
>>> + *    struct drm_gpuvm_bo *vm_bo;
>>> + *    LIST_HEAD(my_local_list);
>>> + *
>>> + *    ret = 0;
>>> + *    for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list,
>>> vm_bo) {
>>> + *        ret = do_something_with_vm_bo(..., vm_bo);
>>> + *        if (ret)
>>> + *            break;
>>> + *    }
>>> + *    drm_gpuvm_bo_put(vm_bo);
>>> + *    restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
>>> + *
>>> + *
>>> + * Only used for internal list iterations, not meant to be exposed
>>> to the outside
>>> + * world.
>>> + */
>>> +#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list,
>>> __vm_bo)    \
>>> +    for (__vm_bo = get_next_vm_bo_from_list(__gpuvm,
>>> __list_name,        \
>>> +                        __local_list, NULL);        \
>>> +         __vm_bo;                                \
>>> +         __vm_bo = get_next_vm_bo_from_list(__gpuvm,
>>> __list_name,        \
>>> +                        __local_list, __vm_bo))
>>> +
>>> +static void
>>> +__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>> +             struct list_head *list, struct list_head **local_list)
>>> +{
>>> +    /* Merge back the two lists, moving local list elements to the
>>> +     * head to preserve previous ordering, in case it matters.
>>> +     */
>>> +    spin_lock(lock);
>>> +    if (*local_list) {
>>> +        list_splice(*local_list, list);
>>> +        *local_list = NULL;
>>> +    }
>>> +    spin_unlock(lock);
>>> +}
>>> +
>>> +/**
>>> + * restore_vm_bo_list() - move vm_bo elements back to their
>>> original list
>>> + * @__gpuvm: The GPU VM
>>> + * @__list_name: The name of the list we're iterating on
>>> + *
>>> + * When we're done iterating a vm_bo list, we should call
>>> restore_vm_bo_list()
>>> + * to restore the original state and let new iterations take place.
>>> + */
>>> +#define restore_vm_bo_list(__gpuvm, __list_name)            \
>>> +    __restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock,    \
>>> +                 &(__gpuvm)->__list_name.list, \
>>> +                 &(__gpuvm)->__list_name.local_list)
>>> +
>>> +static void
>>> +cond_spin_lock(spinlock_t *lock, bool cond)
>>> +{
>>> +    if (cond)
>>> +        spin_lock(lock);
>>> +}
>>> +
>>> +static void
>>> +cond_spin_unlock(spinlock_t *lock, bool cond)
>>> +{
>>> +    if (cond)
>>> +        spin_unlock(lock);
>>> +}
>>> +
>>> +static void
>>> +__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>> +            struct list_head *entry, struct list_head *list)
>>> +{
>>> +    cond_spin_lock(lock, !!lock);
>>> +    if (list_empty(entry))
>>> +        list_add_tail(entry, list);
>>> +    cond_spin_unlock(lock, !!lock);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
>>> + * @__vm_bo: the &drm_gpuvm_bo
>>> + * @__list_name: the name of the list to insert into
>>> + * @__lock: whether to lock with the internal spinlock
>>> + *
>>> + * Inserts the given @__vm_bo into the list specified by @__list_name.
>>> + */
>>> +#define drm_gpuvm_bo_list_add(__vm_bo, __list_name,
>>> __lock)            \
>>> + __drm_gpuvm_bo_list_add((__vm_bo)->vm,                    \
>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>> +                     NULL,                    \
>>> + &(__vm_bo)->list.entry.__list_name,        \
>>> +                &(__vm_bo)->vm->__list_name.list)
>>> +
>>> +static void
>>> +__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>> +            struct list_head *entry, bool init)
>>> +{
>>> +    cond_spin_lock(lock, !!lock);
>>> +    if (init) {
>>> +        if (!list_empty(entry))
>>> +            list_del_init(entry);
>>> +    } else {
>>> +        list_del(entry);
>>> +    }
>>> +    cond_spin_unlock(lock, !!lock);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
>>> + * @__vm_bo: the &drm_gpuvm_bo
>>> + * @__list_name: the name of the list to insert into
>>> + * @__lock: whether to lock with the internal spinlock
>>> + *
>>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>>> + */
>>> +#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name,
>>> __lock)        \
>>> + __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>> +                     NULL,                    \
>>> + &(__vm_bo)->list.entry.__list_name,        \
>>> +                true)
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
>>> + * @__vm_bo: the &drm_gpuvm_bo
>>> + * @__list_name: the name of the list to insert into
>>> + * @__lock: whether to lock with the internal spinlock
>>> + *
>>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>>> + */
>>> +#define drm_gpuvm_bo_list_del(__vm_bo, __list_name,
>>> __lock)            \
>>> + __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>> +                     NULL,                    \
>>> + &(__vm_bo)->list.entry.__list_name,        \
>>> +                false)
>>> +
>>>   #define to_drm_gpuva(__node)    container_of((__node), struct
>>> drm_gpuva, rb.node)
>>>   #define GPUVA_START(node) ((node)->va.addr)
>>> @@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct
>>> drm_gem_object *r_obj,
>>>       gpuvm->rb.tree = RB_ROOT_CACHED;
>>>       INIT_LIST_HEAD(&gpuvm->rb.list);
>>> +    INIT_LIST_HEAD(&gpuvm->extobj.list);
>>> +    spin_lock_init(&gpuvm->extobj.lock);
>>> +
>>> +    INIT_LIST_HEAD(&gpuvm->evict.list);
>>> +    spin_lock_init(&gpuvm->evict.lock);
>>> +
>>>       drm_gpuvm_check_overflow(start_offset, range);
>>>       gpuvm->mm_start = start_offset;
>>>       gpuvm->mm_range = range;
>>> @@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>       WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
>>>            "GPUVA tree is not empty, potentially leaking memory.\n");
>>> +    WARN(!list_empty(&gpuvm->extobj.list), "Extobj list should be
>>> empty.\n");
>>> +    WARN(!list_empty(&gpuvm->evict.list), "Evict list should be
>>> empty.\n");
>>> +
>>>       drm_gem_object_put(gpuvm->r_obj);
>>>   }
>>>   EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>> +static int
>>> +__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>>> +                struct drm_exec *exec,
>>> +                unsigned int num_fences)
>>> +{
>>> +    struct drm_gpuvm_bo *vm_bo;
>>> +    LIST_HEAD(extobjs);
>>> +    int ret = 0;
>>> +
>>> +    for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
>>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>>> +        if (ret)
>>> +            break;
>>> +    }
>>> +    /* Drop ref in case we break out of the loop. */
>>> +    drm_gpuvm_bo_put(vm_bo);
>>> +    restore_vm_bo_list(gpuvm, extobj);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int
>>> +drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
>>> +                 struct drm_exec *exec,
>>> +                 unsigned int num_fences)
>>> +{
>>> +    struct drm_gpuvm_bo *vm_bo;
>>> +    int ret = 0;
>>> +
>>> +    drm_gpuvm_resv_assert_held(gpuvm);
>>> +    list_for_each_entry(vm_bo, &gpuvm->extobj.list,
>>> list.entry.extobj) {
>>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>>> +        if (ret)
>>> +            break;
>>> +
>>> +        if (vm_bo->evicted)
>>> +            drm_gpuvm_bo_list_add(vm_bo, evict, false);
>>
>> Clear vm_bo->evicted here?
>
> Why? It's still evicted, hence why not indicate it? It could be useful
> for a
> validate_range() use case.

I guess that boils down to what vm_bo->evicted is supposed to mean. I
have been using it as "This bo needs to be put on the evicted list", but
if we instead mean "This bo was once evicted and might need revalidation
and needs rebinding to this VM" then it's OK not to clear it, I guess.
But note that another VM might have already re-validated the gem BO, and
also if the locking loop or validate loop restarts due to -EINTR or
-EDEADLK, then the drm_gpuvm_bo_list_add() will be called multiple
times, which is OK but unnecessary. So I'd vote for "This bo needs to be
put on the eviced list".

>
>>
>>
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
>>> + * @gpuvm: the &drm_gpuvm
>>> + * @exec: the &drm_exec locking context
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + *
>>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
>>> + * &drm_gpuvm contains mappings of.
>>> + *
>>> + * Using this function directly, it is the drivers responsibility
>>> to call
>>> + * drm_exec_init() and drm_exec_fini() accordingly.
>>> + *
>>> + * Note: This function is safe against concurrent insertion and
>>> removal of
>>> + * external objects, however it is not safe against concurrent
>>> usage itself.
>>> + *
>>> + * Drivers need to make sure to protect this case with either an
>>> outer VM lock
>>> + * or by calling drm_gpuvm_prepare_vm() before this function within
>>> the
>>> + * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv
>>> lock ensures
>>> + * mutual exclusion.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>>> +              struct drm_exec *exec,
>>> +              unsigned int num_fences)
>>> +{
>>> +    if (drm_gpuvm_resv_protected(gpuvm))
>>> +        return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
>>> +                            num_fences);
>>> +    else
>>> +        return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>>> +
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
>>> +
>>> +/**
>>> + * drm_gpuvm_prepare_range() - prepare all BOs mapped within a
>>> given range
>>> + * @gpuvm: the &drm_gpuvm
>>> + * @exec: the &drm_exec locking context
>>> + * @addr: the start address within the VA space
>>> + * @range: the range to iterate within the VA space
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + *
>>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped
>>> between @addr
>>> + * and @addr + @range.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec
>>> *exec,
>>> +            u64 addr, u64 range, unsigned int num_fences)
>>> +{
>>> +    struct drm_gpuva *va;
>>> +    u64 end = addr + range;
>>> +    int ret;
>>> +
>>> +    drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
>>> +        struct drm_gem_object *obj = va->gem.obj;
>>> +
>>> +        ret = drm_exec_prepare_obj(exec, obj, num_fences);
>>> +        if (ret)
>>> +            return ret;
>>> +    }
>>> +
>>> +    return 0;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
>>> +
>>> +/**
>>> + * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + * @interruptible: sleep interruptible if waiting
>>> + *
>>> + * Acquires all dma-resv locks of all &drm_gem_objects the given
>>> + * &drm_gpuvm contains mappings of.
>>> + *
>>> + * Addionally, when calling this function with struct
>>> drm_gpuvm_exec::extra
>>> + * being set the driver receives the given @fn callback to lock
>>> additional
>>> + * dma-resv in the context of the &drm_gpuvm_exec instance.
>>> Typically, drivers
>>> + * would call drm_exec_prepare_obj() from within this callback.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
>>> +            unsigned int num_fences,
>>> +            bool interruptible)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>>> +    struct drm_exec *exec = &vm_exec->exec;
>>> +    uint32_t flags;
>>> +    int ret;
>>> +
>>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>>> +        DRM_EXEC_IGNORE_DUPLICATES;
>>> +
>>> +    drm_exec_init(exec, flags);
>>> +
>>> +    drm_exec_until_all_locked(exec) {
>>> +        ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
>>> +        drm_exec_retry_on_contention(exec);
>>> +        if (ret)
>>> +            goto err;
>>> +
>>> +        ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>>> +        drm_exec_retry_on_contention(exec);
>>> +        if (ret)
>>> +            goto err;
>>> +
>>> +        if (vm_exec->extra.fn) {
>>> +            ret = vm_exec->extra.fn(vm_exec, num_fences);
>>> +            drm_exec_retry_on_contention(exec);
>>> +            if (ret)
>>> +                goto err;
>>> +        }
>>> +    }
>>> +
>>> +    return 0;
>>> +
>>> +err:
>>> +    drm_exec_fini(exec);
>>> +    return ret;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
>>> +
>>> +static int
>>> +fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int num_fences)
>>> +{
>>> +    struct {
>>> +        struct drm_gem_object **objs;
>>> +        unsigned int num_objs;
>>> +    } *args = vm_exec->extra.priv;
>>> +
>>> +    return drm_exec_prepare_array(&vm_exec->exec, args->objs,
>>> +                      args->num_objs, num_fences);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_exec_lock_array() - lock all dma-resv of all
>>> assoiciated BOs
>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>> + * @objs: additional &drm_gem_objects to lock
>>> + * @num_objs: the number of additional &drm_gem_objects to lock
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + * @interruptible: sleep interruptible if waiting
>>> + *
>>> + * Acquires all dma-resv locks of all &drm_gem_objects the given
>>> &drm_gpuvm
>>> + * contains mappings of, plus the ones given through @objs.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
>>> +              struct drm_gem_object **objs,
>>> +              unsigned int num_objs,
>>> +              unsigned int num_fences,
>>> +              bool interruptible)
>>> +{
>>> +    struct {
>>> +        struct drm_gem_object **objs;
>>> +        unsigned int num_objs;
>>> +    } args;
>>> +
>>> +    args.objs = objs;
>>> +    args.num_objs = num_objs;
>>> +
>>> +    vm_exec->extra.fn = fn_lock_array;
>>> +    vm_exec->extra.priv = &args;
>>> +
>>> +    return drm_gpuvm_exec_lock(vm_exec, num_fences, interruptible);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
>>> +
>>> +/**
>>> + * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a
>>> given range
>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>> + * @addr: the start address within the VA space
>>> + * @range: the range to iterate within the VA space
>>> + * @num_fences: the amount of &dma_fences to reserve
>>> + * @interruptible: sleep interruptible if waiting
>>> + *
>>> + * Acquires all dma-resv locks of all &drm_gem_objects mapped
>>> between @addr and
>>> + * @addr + @range.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
>>> +              u64 addr, u64 range,
>>> +              unsigned int num_fences,
>>> +              bool interruptible)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>>> +    struct drm_exec *exec = &vm_exec->exec;
>>> +    uint32_t flags;
>>> +    int ret;
>>> +
>>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>>> +        DRM_EXEC_IGNORE_DUPLICATES;
>>> +
>>> +    drm_exec_init(exec, flags);
>>> +
>>> +    drm_exec_until_all_locked(exec) {
>>> +        ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
>>> +                          num_fences);
>>> +        drm_exec_retry_on_contention(exec);
>>> +        if (ret)
>>> +            goto err;
>>> +    }
>>> +
>>> +    return ret;
>>> +
>>> +err:
>>> +    drm_exec_fini(exec);
>>> +    return ret;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
>>> +
>>> +static int
>>> +__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>> +{
>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +    struct drm_gpuvm_bo *vm_bo;
>>> +    LIST_HEAD(evict);
>>> +    int ret = 0;
>>> +
>>> +    for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
>>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>>> +        if (ret)
>>> +            break;
>>> +    }
>>> +    /* Drop ref in case we break out of the loop. */
>>> +    drm_gpuvm_bo_put(vm_bo);
>>> +    restore_vm_bo_list(gpuvm, evict);
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +static int
>>> +drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec
>>> *exec)
>>> +{
>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +    struct drm_gpuvm_bo *vm_bo, *next;
>>> +    int ret = 0;
>>> +
>>> +    drm_gpuvm_resv_assert_held(gpuvm);
>>> +
>>> +    /* Iterate list safely, drivers typically remove the current
>>> entry from
>>> +     * their drm_gpuvm_ops::vm_bo_validate callback. Drivers might
>>> also
>>> +     * re-add the entry on failure; this is safe since on failure
>>> we break
>>> +     * out of the loop.
>>> +     */
>>> +    list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
>>> +                 list.entry.evict) {
>>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>>> +        if (ret)
>>> +            break;
>>> +    }
>>> +
>>> +    return ret;
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_validate() - validate all BOs marked as evicted
>>> + * @gpuvm: the &drm_gpuvm to validate evicted BOs
>>> + * @exec: the &drm_exec instance used for locking the GPUVM
>>> + *
>>> + * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all
>>> evicted buffer
>>> + * objects being mapped in the given &drm_gpuvm.
>>> + *
>>> + * Returns: 0 on success, negative error code on failure.
>>> + */
>>> +int
>>> +drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>> +{
>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +
>>> +    if (unlikely(!ops || !ops->vm_bo_validate))
>>> +        return -ENOTSUPP;
>>> +
>>> +    if (drm_gpuvm_resv_protected(gpuvm))
>>> +        return drm_gpuvm_validate_locked(gpuvm, exec);
>>> +    else
>>> +        return __drm_gpuvm_validate(gpuvm, exec);
>>> +
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
>>> +
>>> +/**
>>> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
>>> + * dma-resv
>>> + * @gpuvm: the &drm_gpuvm to add a fence to
>>> + * @exec: the &drm_exec locking context
>>> + * @fence: fence to add
>>> + * @private_usage: private dma-resv usage
>>> + * @extobj_usage: extobj dma-resv usage
>>> + */
>>> +void
>>> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
>>> +             struct drm_exec *exec,
>>> +             struct dma_fence *fence,
>>> +             enum dma_resv_usage private_usage,
>>> +             enum dma_resv_usage extobj_usage)
>>> +{
>>> +    struct drm_gem_object *obj;
>>> +    unsigned long index;
>>> +
>>> +    drm_exec_for_each_locked_object(exec, index, obj) {
>>> +        dma_resv_assert_held(obj->resv);
>>> +        dma_resv_add_fence(obj->resv, fence,
>>> +                   drm_gpuvm_is_extobj(gpuvm, obj) ?
>>> +                   private_usage : extobj_usage);
>>> +    }
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
>>> +
>>>   /**
>>>    * drm_gpuvm_bo_create() - create a new instance of struct
>>> drm_gpuvm_bo
>>>    * @gpuvm: The &drm_gpuvm the @obj is mapped in.
>>> @@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
>>>       INIT_LIST_HEAD(&vm_bo->list.gpuva);
>>>       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
>>> +    INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
>>> +    INIT_LIST_HEAD(&vm_bo->list.entry.evict);
>>> +
>>>       drm_gem_object_get(obj);
>>>       return vm_bo;
>>> @@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
>>>       if (!lock)
>>>           drm_gpuvm_resv_assert_held(gpuvm);
>>> +    drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
>>> +    drm_gpuvm_bo_list_del(vm_bo, evict, lock);
>>> +
>>>       list_del(&vm_bo->list.entry.gem);
>>>       drm_gem_object_put(obj);
>>> @@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct
>>> drm_gpuvm_bo *__vm_bo)
>>>   }
>>>   EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
>>> +/**
>>> + * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its
>>> &drm_gpuvm's
>>> + * extobj list
>>> + * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj
>>> list.
>>> + *
>>> + * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on
>>> the list
>>> + * already and if the corresponding &drm_gem_object is an external
>>> object,
>>> + * actually.
>>> + */
>>> +void
>>> +drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>>> +
>>> +    if (!lock)
>>> +        drm_gpuvm_resv_assert_held(gpuvm);
>>> +
>>> +    if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
>>> +        drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from
>>> the &drm_gpuvms
>>> + * evicted list
>>> + * @vm_bo: the &drm_gpuvm_bo to add or remove
>>> + * @evict: indicates whether the object is evicted
>>> + *
>>> + * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms
>>> evicted list.
>>> + */
>>> +void
>>> +drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
>>> +{
>>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>>> +    struct drm_gem_object *obj = vm_bo->obj;
>>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>>> +
>>> +    dma_resv_assert_held(obj->resv);
>>> +
>>> +    if (drm_gpuvm_is_extobj(gpuvm, obj)) {
>>> +        vm_bo->evicted = evict;
>> Does the lock case also need this?
>
> It doesn't need it by itself, but since we have drm_gpuvm_bo::evicted
> now, I want it to
> consistently indicate whether the BO is evicted or not.

OK, I guess that ties to the meaning of bo->evicted, as discussed above.

>
>>> +
>>> +        if (!lock)
>>> +            return;
>>
>> Here the !lock case can not remove the gpuvm_bo from the list?
>
> You mean because we'd expect that drm_gpuvm_bo_evict(vm_bo, false) can
> only be called from
> within gpuvm_validate(), which requires the VM's resv lock? What if
> there is a ttm_validate()
> call for only this GEM obj?
>
> My idea was to remove VM_BOs from the evicted list in gpuvm_validate()
> directly, but I'm afraid
> I forgot that.

Yes, I think the helper could do it if validate() is successful. But
what I meant above was that if the *driver* is responsible for removing
object from the evicted list, then if it's a RESV_PROTECTED vm, it can't
do that because drm_gpuvm_bo_evict(gpuvm, false) will never get to
removing it from the list because it returns early.

Thanks,

Thomas


>
>>
>> Small patch here that I've been using for xe:
>>
>> https://patchwork.freedesktop.org/patch/561545/?series=124817&rev=1
>>
>> Thanks,
>>
>> Thomas
>>
>>
>

2023-10-10 06:41:35

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects


On 10/9/23 01:32, Danilo Krummrich wrote:
> Currently the DRM GPUVM offers common infrastructure to track GPU VA
> allocations and mappings, generically connect GPU VA mappings to their
> backing buffers and perform more complex mapping operations on the GPU VA
> space.
>
> However, there are more design patterns commonly used by drivers, which
> can potentially be generalized in order to make the DRM GPUVM represent
> a basis for GPU-VM implementations. In this context, this patch aims
> at generalizing the following elements.
>
> 1) Provide a common dma-resv for GEM objects not being used outside of
> this GPU-VM.
>
> 2) Provide tracking of external GEM objects (GEM objects which are
> shared with other GPU-VMs).
>
> 3) Provide functions to efficiently lock all GEM objects dma-resv the
> GPU-VM contains mappings of.
>
> 4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
> of, such that validation of evicted GEM objects is accelerated.
>
> 5) Provide some convinience functions for common patterns.
>
> Big thanks to Boris Brezillon for his help to figure out locking for
> drivers updating the GPU VA space within the fence signalling path.
>
> Suggested-by: Matthew Brost <[email protected]>
> Signed-off-by: Danilo Krummrich <[email protected]>
>
> +/**
> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
> + * dma-resv
> + * @gpuvm: the &drm_gpuvm to add a fence to
> + * @exec: the &drm_exec locking context
> + * @fence: fence to add
> + * @private_usage: private dma-resv usage
> + * @extobj_usage: extobj dma-resv usage
> + */
> +void
> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
> + struct drm_exec *exec,
> + struct dma_fence *fence,
> + enum dma_resv_usage private_usage,
> + enum dma_resv_usage extobj_usage)
> +{
> + struct drm_gem_object *obj;
> + unsigned long index;
> +
> + drm_exec_for_each_locked_object(exec, index, obj) {
> + dma_resv_assert_held(obj->resv);
> + dma_resv_add_fence(obj->resv, fence,
> + drm_gpuvm_is_extobj(gpuvm, obj) ?
> + private_usage : extobj_usage);

It looks like private_usage and extobj_usage are mixed up above?


> + }
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
> +

Thanks,

Thomas


2023-10-13 11:39:22

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 1/6] drm/gpuvm: add common dma-resv per struct drm_gpuvm

On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
> Provide a common dma-resv for GEM objects not being used outside of
> this
> GPU-VM. This is used in a subsequent patch to generalize dma-resv,
> external and evicted object handling and GEM validation.
>
> Signed-off-by: Danilo Krummrich <[email protected]>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 56
> +++++++++++++++++++++++++-
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 +++++-
>  include/drm/drm_gpuvm.h                | 35 +++++++++++++++-
>  3 files changed, 99 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 02ecb45a2544..ebda9d594165 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -61,6 +61,15 @@
>   * contained within struct drm_gpuva already. Hence, for inserting
> &drm_gpuva
>   * entries from within dma-fence signalling critical sections it is
> enough to
>   * pre-allocate the &drm_gpuva structures.
> + *
> + * &drm_gem_objects which are private to a single VM can share a
> common
> + * &dma_resv in order to improve locking efficiency (e.g. with
> &drm_exec).
> + * For this purpose drivers must pass a &drm_gem_object to
> drm_gpuvm_init(), in
> + * the following called 'root object', which serves as the container

Nit: Perhaps resv object altough it might typically be the root page-
table object, that doesn't have any meaning to drm_gpuvm, which uses it
solely as a container for the resv?

> of the
> + * GPUVM's shared &dma_resv. This root object can be a driver
> specific
> + * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
> + * but it can also be a 'dummy' object, which can be allocated with
> + * drm_gpuvm_root_object_alloc().
>   */
>  
>  /**
> @@ -652,9 +661,47 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>  }
>  
> +static void
> +drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
> +{
> +       drm_gem_object_release(obj);
> +       kfree(obj);
> +}
> +
> +static const struct drm_gem_object_funcs drm_gpuvm_object_funcs = {
> +       .free = drm_gpuvm_gem_object_free,
> +};
> +
> +/**
> + * drm_gpuvm_root_object_alloc() - allocate a dummy &drm_gem_object
> + * @drm: the drivers &drm_device
> + *
> + * Allocates a dummy &drm_gem_object which can be passed to
> drm_gpuvm_init() in
> + * order to serve as root GEM object providing the &drm_resv shared
> across
> + * &drm_gem_objects local to a single GPUVM.
> + *
> + * Returns: the &drm_gem_object on success, NULL on failure
> + */
> +struct drm_gem_object *
> +drm_gpuvm_root_object_alloc(struct drm_device *drm)
> +{
> +       struct drm_gem_object *obj;
> +
> +       obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> +       if (!obj)
> +               return NULL;
> +
> +       obj->funcs = &drm_gpuvm_object_funcs;
> +       drm_gem_private_object_init(drm, obj, 0);
> +
> +       return obj;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
> +
>  /**
>   * drm_gpuvm_init() - initialize a &drm_gpuvm
>   * @gpuvm: pointer to the &drm_gpuvm to initialize
> + * @r_obj: the root &drm_gem_object providing the GPUVM's common
> &dma_resv
>   * @name: the name of the GPU VA space
>   * @start_offset: the start offset of the GPU VA space
>   * @range: the size of the GPU VA space
> @@ -668,7 +715,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>   * &name is expected to be managed by the surrounding driver
> structures.
>   */
>  void
> -drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> +drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
> *r_obj,
>                const char *name,
>                u64 start_offset, u64 range,
>                u64 reserve_offset, u64 reserve_range,
> @@ -683,6 +730,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
>  
>         gpuvm->name = name ? name : "unknown";
>         gpuvm->ops = ops;
> +       gpuvm->r_obj = r_obj;
> +
> +       drm_gem_object_get(r_obj);
>  
>         memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct
> drm_gpuva));
>  
> @@ -713,7 +763,9 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>                 __drm_gpuva_remove(&gpuvm->kernel_alloc_node);
>  
>         WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
> -            "GPUVA tree is not empty, potentially leaking memory.");
> +            "GPUVA tree is not empty, potentially leaking
> memory.\n");

Should we cache the drm device in struct drm_gpuvm and use drm_warn()
here instead of WARN?

> +
> +       drm_gem_object_put(gpuvm->r_obj);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 5cf892c50f43..4dea847ef989 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1808,8 +1808,9 @@ int
>  nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli
> *cli,
>                   u64 kernel_managed_addr, u64 kernel_managed_size)
>  {
> -       int ret;
> +       struct drm_gem_object *r_obj;
>         u64 kernel_managed_end = kernel_managed_addr +
> kernel_managed_size;
> +       int ret;
>  
>         mutex_init(&uvmm->mutex);
>         dma_resv_init(&uvmm->resv);
> @@ -1833,14 +1834,22 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm,
> struct nouveau_cli *cli,
>                 goto out_unlock;
>         }
>  
> +       r_obj = drm_gpuvm_root_object_alloc(cli->drm->dev);
> +       if (!r_obj) {
> +               ret = -ENOMEM;
> +               goto out_unlock;
> +       }
> +
>         uvmm->kernel_managed_addr = kernel_managed_addr;
>         uvmm->kernel_managed_size = kernel_managed_size;
>  
> -       drm_gpuvm_init(&uvmm->base, cli->name,
> +       drm_gpuvm_init(&uvmm->base, r_obj, cli->name,
>                        NOUVEAU_VA_SPACE_START,
>                        NOUVEAU_VA_SPACE_END,
>                        kernel_managed_addr, kernel_managed_size,
>                        NULL);
> +       /* GPUVM takes care from here on. */
> +       drm_gem_object_put(r_obj);
>  
>         ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
>                             cli->vmm.vmm.object.oclass, RAW,
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index c7ed6bf441d4..0aec14d8b259 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -238,9 +238,15 @@ struct drm_gpuvm {
>          * @ops: &drm_gpuvm_ops providing the split/merge steps to
> drivers
>          */
>         const struct drm_gpuvm_ops *ops;
> +
> +       /**
> +        * @r_obj: Root GEM object; representing the GPUVM's common
> &dma_resv.
> +        */
> +       struct drm_gem_object *r_obj;
>  };
>  
> -void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> +void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
> *r_obj,
> +                   const char *name,
>                     u64 start_offset, u64 range,
>                     u64 reserve_offset, u64 reserve_range,
>                     const struct drm_gpuvm_ops *ops);
> @@ -248,6 +254,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>  
>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
> range);
>  
> +struct drm_gem_object *
> +drm_gpuvm_root_object_alloc(struct drm_device *drm);
> +
> +/**
> + * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
> + * @gpuvm__: the &drm_gpuvm
> + *
> + * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
> + */
> +#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
> +
> +/**
> + * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding the
> &drm_gpuvm's
> + * &dma_resv
> + * @gpuvm__: the &drm_gpuvm
> + *
> + * Returns: a pointer to the &drm_gem_object holding the
> &drm_gpuvm's shared
> + * &dma_resv
> + */
> +#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
> +
> +#define drm_gpuvm_resv_held(gpuvm__) \
> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
> +
> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
> +
>  static inline struct drm_gpuva *
>  __drm_gpuva_next(struct drm_gpuva *va)
>  {

Reviewed-by: Thomas Hellström <[email protected]>


2023-10-13 11:43:31

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 2/6] drm/gpuvm: add drm_gpuvm_flags to drm_gpuvm

On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
> Introduce flags for struct drm_gpuvm, this required by subsequent
> commits.
>
> Signed-off-by: Danilo Krummrich <[email protected]>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            |  4 +++-
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  2 +-
>  include/drm/drm_gpuvm.h                | 17 ++++++++++++++++-
>  3 files changed, 20 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index ebda9d594165..6368dfdbe9dd 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -703,6 +703,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
>   * @gpuvm: pointer to the &drm_gpuvm to initialize
>   * @r_obj: the root &drm_gem_object providing the GPUVM's common
> &dma_resv
>   * @name: the name of the GPU VA space
> + * @flags: the &drm_gpuvm_flags for this GPUVM

NIT: It looks like kerneldoc guidelines recommends using &enum
drm_gpuvm_flags in new code

>   * @start_offset: the start offset of the GPU VA space
>   * @range: the size of the GPU VA space
>   * @reserve_offset: the start of the kernel reserved GPU VA area
> @@ -716,7 +717,7 @@ EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
>   */
>  void
>  drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
> *r_obj,
> -              const char *name,
> +              const char *name, enum drm_gpuvm_flags flags,
>                u64 start_offset, u64 range,
>                u64 reserve_offset, u64 reserve_range,
>                const struct drm_gpuvm_ops *ops)
> @@ -729,6 +730,7 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct
> drm_gem_object *r_obj,
>         gpuvm->mm_range = range;
>  
>         gpuvm->name = name ? name : "unknown";
> +       gpuvm->flags = flags;
>         gpuvm->ops = ops;
>         gpuvm->r_obj = r_obj;
>  
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 4dea847ef989..93ad2ba7ec8b 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -1843,7 +1843,7 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm,
> struct nouveau_cli *cli,
>         uvmm->kernel_managed_addr = kernel_managed_addr;
>         uvmm->kernel_managed_size = kernel_managed_size;
>  
> -       drm_gpuvm_init(&uvmm->base, r_obj, cli->name,
> +       drm_gpuvm_init(&uvmm->base, r_obj, cli->name, 0,
>                        NOUVEAU_VA_SPACE_START,
>                        NOUVEAU_VA_SPACE_END,
>                        kernel_managed_addr, kernel_managed_size,
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 0aec14d8b259..13539f32c2e2 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -183,6 +183,16 @@ static inline bool drm_gpuva_invalidated(struct
> drm_gpuva *va)
>         return va->flags & DRM_GPUVA_INVALIDATED;
>  }
>  
> +/**
> + * enum drm_gpuvm_flags - flags for struct drm_gpuvm
> + */
> +enum drm_gpuvm_flags {
> +       /**
> +        * @DRM_GPUVM_USERBITS: user defined bits
> +        */
> +       DRM_GPUVM_USERBITS = (1 << 0),

BIT(0)

> +};
> +
>  /**
>   * struct drm_gpuvm - DRM GPU VA Manager
>   *
> @@ -201,6 +211,11 @@ struct drm_gpuvm {
>          */
>         const char *name;
>  
> +       /**
> +        * @flags: the &drm_gpuvm_flags of this GPUVM
enum?
> +        */
> +       enum drm_gpuvm_flags flags;
> +
>         /**
>          * @mm_start: start of the VA space
>          */
> @@ -246,7 +261,7 @@ struct drm_gpuvm {
>  };
>  
>  void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
> *r_obj,
> -                   const char *name,
> +                   const char *name, enum drm_gpuvm_flags flags,
>                     u64 start_offset, u64 range,
>                     u64 reserve_offset, u64 reserve_range,
>                     const struct drm_gpuvm_ops *ops);

Reviewed-by: Thomas Hellström <[email protected]>

2023-10-13 11:52:56

by Danilo Krummrich

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 1/6] drm/gpuvm: add common dma-resv per struct drm_gpuvm

On 10/13/23 13:38, Thomas Hellström wrote:
> On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
>> Provide a common dma-resv for GEM objects not being used outside of
>> this
>> GPU-VM. This is used in a subsequent patch to generalize dma-resv,
>> external and evicted object handling and GEM validation.
>>
>> Signed-off-by: Danilo Krummrich <[email protected]>
>> ---
>>  drivers/gpu/drm/drm_gpuvm.c            | 56
>> +++++++++++++++++++++++++-
>>  drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 +++++-
>>  include/drm/drm_gpuvm.h                | 35 +++++++++++++++-
>>  3 files changed, 99 insertions(+), 5 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>> b/drivers/gpu/drm/drm_gpuvm.c
>> index 02ecb45a2544..ebda9d594165 100644
>> --- a/drivers/gpu/drm/drm_gpuvm.c
>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>> @@ -61,6 +61,15 @@
>>   * contained within struct drm_gpuva already. Hence, for inserting
>> &drm_gpuva
>>   * entries from within dma-fence signalling critical sections it is
>> enough to
>>   * pre-allocate the &drm_gpuva structures.
>> + *
>> + * &drm_gem_objects which are private to a single VM can share a
>> common
>> + * &dma_resv in order to improve locking efficiency (e.g. with
>> &drm_exec).
>> + * For this purpose drivers must pass a &drm_gem_object to
>> drm_gpuvm_init(), in
>> + * the following called 'root object', which serves as the container
>
> Nit: Perhaps resv object altough it might typically be the root page-
> table object, that doesn't have any meaning to drm_gpuvm, which uses it
> solely as a container for the resv?

With "root" I didn't want to refer to the object representing the root
page-table object, but being *the* object every other (internal) object
needs to keep a reference to. Maybe I should be more explicit here and say
that drivers need to make sure every internal object requires a reference
to take a reference to this root object.

>
>> of the
>> + * GPUVM's shared &dma_resv. This root object can be a driver
>> specific
>> + * &drm_gem_object, such as the &drm_gem_object containing the root
>> page table,
>> + * but it can also be a 'dummy' object, which can be allocated with
>> + * drm_gpuvm_root_object_alloc().
>>   */
>>
>>  /**
>> @@ -652,9 +661,47 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>>                !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>>  }
>>
>> +static void
>> +drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
>> +{
>> +       drm_gem_object_release(obj);
>> +       kfree(obj);
>> +}
>> +
>> +static const struct drm_gem_object_funcs drm_gpuvm_object_funcs = {
>> +       .free = drm_gpuvm_gem_object_free,
>> +};
>> +
>> +/**
>> + * drm_gpuvm_root_object_alloc() - allocate a dummy &drm_gem_object
>> + * @drm: the drivers &drm_device
>> + *
>> + * Allocates a dummy &drm_gem_object which can be passed to
>> drm_gpuvm_init() in
>> + * order to serve as root GEM object providing the &drm_resv shared
>> across
>> + * &drm_gem_objects local to a single GPUVM.
>> + *
>> + * Returns: the &drm_gem_object on success, NULL on failure
>> + */
>> +struct drm_gem_object *
>> +drm_gpuvm_root_object_alloc(struct drm_device *drm)
>> +{
>> +       struct drm_gem_object *obj;
>> +
>> +       obj = kzalloc(sizeof(*obj), GFP_KERNEL);
>> +       if (!obj)
>> +               return NULL;
>> +
>> +       obj->funcs = &drm_gpuvm_object_funcs;
>> +       drm_gem_private_object_init(drm, obj, 0);
>> +
>> +       return obj;
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
>> +
>>  /**
>>   * drm_gpuvm_init() - initialize a &drm_gpuvm
>>   * @gpuvm: pointer to the &drm_gpuvm to initialize
>> + * @r_obj: the root &drm_gem_object providing the GPUVM's common
>> &dma_resv
>>   * @name: the name of the GPU VA space
>>   * @start_offset: the start offset of the GPU VA space
>>   * @range: the size of the GPU VA space
>> @@ -668,7 +715,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm,
>>   * &name is expected to be managed by the surrounding driver
>> structures.
>>   */
>>  void
>> -drm_gpuvm_init(struct drm_gpuvm *gpuvm,
>> +drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
>> *r_obj,
>>                const char *name,
>>                u64 start_offset, u64 range,
>>                u64 reserve_offset, u64 reserve_range,
>> @@ -683,6 +730,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
>>
>>         gpuvm->name = name ? name : "unknown";
>>         gpuvm->ops = ops;
>> +       gpuvm->r_obj = r_obj;
>> +
>> +       drm_gem_object_get(r_obj);
>>
>>         memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct
>> drm_gpuva));
>>
>> @@ -713,7 +763,9 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>                 __drm_gpuva_remove(&gpuvm->kernel_alloc_node);
>>
>>         WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
>> -            "GPUVA tree is not empty, potentially leaking memory.");
>> +            "GPUVA tree is not empty, potentially leaking
>> memory.\n");
>
> Should we cache the drm device in struct drm_gpuvm and use drm_warn()
> here instead of WARN?

I'd guess the additional backtrace of WARN() isn't overly useful in this
case. However, it might be a bit more obvious in dmesg due to its
verboseness. Not a strong opinion on that, though.

>
>> +
>> +       drm_gem_object_put(gpuvm->r_obj);
>>  }
>>  EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>
>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>> index 5cf892c50f43..4dea847ef989 100644
>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>> @@ -1808,8 +1808,9 @@ int
>>  nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli
>> *cli,
>>                   u64 kernel_managed_addr, u64 kernel_managed_size)
>>  {
>> -       int ret;
>> +       struct drm_gem_object *r_obj;
>>         u64 kernel_managed_end = kernel_managed_addr +
>> kernel_managed_size;
>> +       int ret;
>>
>>         mutex_init(&uvmm->mutex);
>>         dma_resv_init(&uvmm->resv);
>> @@ -1833,14 +1834,22 @@ nouveau_uvmm_init(struct nouveau_uvmm *uvmm,
>> struct nouveau_cli *cli,
>>                 goto out_unlock;
>>         }
>>
>> +       r_obj = drm_gpuvm_root_object_alloc(cli->drm->dev);
>> +       if (!r_obj) {
>> +               ret = -ENOMEM;
>> +               goto out_unlock;
>> +       }
>> +
>>         uvmm->kernel_managed_addr = kernel_managed_addr;
>>         uvmm->kernel_managed_size = kernel_managed_size;
>>
>> -       drm_gpuvm_init(&uvmm->base, cli->name,
>> +       drm_gpuvm_init(&uvmm->base, r_obj, cli->name,
>>                        NOUVEAU_VA_SPACE_START,
>>                        NOUVEAU_VA_SPACE_END,
>>                        kernel_managed_addr, kernel_managed_size,
>>                        NULL);
>> +       /* GPUVM takes care from here on. */
>> +       drm_gem_object_put(r_obj);
>>
>>         ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
>>                             cli->vmm.vmm.object.oclass, RAW,
>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>> index c7ed6bf441d4..0aec14d8b259 100644
>> --- a/include/drm/drm_gpuvm.h
>> +++ b/include/drm/drm_gpuvm.h
>> @@ -238,9 +238,15 @@ struct drm_gpuvm {
>>          * @ops: &drm_gpuvm_ops providing the split/merge steps to
>> drivers
>>          */
>>         const struct drm_gpuvm_ops *ops;
>> +
>> +       /**
>> +        * @r_obj: Root GEM object; representing the GPUVM's common
>> &dma_resv.
>> +        */
>> +       struct drm_gem_object *r_obj;
>>  };
>>
>> -void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>> +void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
>> *r_obj,
>> +                   const char *name,
>>                     u64 start_offset, u64 range,
>>                     u64 reserve_offset, u64 reserve_range,
>>                     const struct drm_gpuvm_ops *ops);
>> @@ -248,6 +254,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
>>
>>  bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64
>> range);
>>
>> +struct drm_gem_object *
>> +drm_gpuvm_root_object_alloc(struct drm_device *drm);
>> +
>> +/**
>> + * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
>> + * @gpuvm__: the &drm_gpuvm
>> + *
>> + * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
>> + */
>> +#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
>> +
>> +/**
>> + * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding the
>> &drm_gpuvm's
>> + * &dma_resv
>> + * @gpuvm__: the &drm_gpuvm
>> + *
>> + * Returns: a pointer to the &drm_gem_object holding the
>> &drm_gpuvm's shared
>> + * &dma_resv
>> + */
>> +#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
>> +
>> +#define drm_gpuvm_resv_held(gpuvm__) \
>> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
>> +
>> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
>> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
>> +
>>  static inline struct drm_gpuva *
>>  __drm_gpuva_next(struct drm_gpuva *va)
>>  {
>
> Reviewed-by: Thomas Hellström <[email protected]>
>
>

2023-10-13 12:06:08

by Danilo Krummrich

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects

On 10/10/23 08:26, Thomas Hellström wrote:
>
> On 10/9/23 16:45, Danilo Krummrich wrote:
>> On 10/9/23 15:36, Thomas Hellström wrote:
>>>
>>> On 10/9/23 01:32, Danilo Krummrich wrote:
>>>> Currently the DRM GPUVM offers common infrastructure to track GPU VA
>>>> allocations and mappings, generically connect GPU VA mappings to their
>>>> backing buffers and perform more complex mapping operations on the GPU VA
>>>> space.
>>>>
>>>> However, there are more design patterns commonly used by drivers, which
>>>> can potentially be generalized in order to make the DRM GPUVM represent
>>>> a basis for GPU-VM implementations. In this context, this patch aims
>>>> at generalizing the following elements.
>>>>
>>>> 1) Provide a common dma-resv for GEM objects not being used outside of
>>>>     this GPU-VM.
>>>>
>>>> 2) Provide tracking of external GEM objects (GEM objects which are
>>>>     shared with other GPU-VMs).
>>>>
>>>> 3) Provide functions to efficiently lock all GEM objects dma-resv the
>>>>     GPU-VM contains mappings of.
>>>>
>>>> 4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
>>>>     of, such that validation of evicted GEM objects is accelerated.
>>>>
>>>> 5) Provide some convinience functions for common patterns.
>>>>
>>>> Big thanks to Boris Brezillon for his help to figure out locking for
>>>> drivers updating the GPU VA space within the fence signalling path.
>>>>
>>>> Suggested-by: Matthew Brost <[email protected]>
>>>> Signed-off-by: Danilo Krummrich <[email protected]>
>>>> ---
>>>>   drivers/gpu/drm/drm_gpuvm.c | 646 ++++++++++++++++++++++++++++++++++++
>>>>   include/drm/drm_gpuvm.h     | 246 ++++++++++++++
>>>>   2 files changed, 892 insertions(+)
>>>>
>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
>>>> index 28282283ddaf..6977bd30eca5 100644
>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>> @@ -82,6 +82,21 @@
>>>>    * &drm_gem_object list of &drm_gpuvm_bos for an existing instance of this
>>>>    * particular combination. If not existent a new instance is created and linked
>>>>    * to the &drm_gem_object.
>>>> + *
>>>> + * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm, are also used
>>>> + * as entry for the &drm_gpuvm's lists of external and evicted objects. Those
>>>> + * list are maintained in order to accelerate locking of dma-resv locks and
>>>> + * validation of evicted objects bound in a &drm_gpuvm. For instance, all
>>>> + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked by calling
>>>> + * drm_gpuvm_exec_lock(). Once locked drivers can call drm_gpuvm_validate() in
>>>> + * order to validate all evicted &drm_gem_objects. It is also possible to lock
>>>> + * additional &drm_gem_objects by providing the corresponding parameters to
>>>> + * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop while making
>>>> + * use of helper functions such as drm_gpuvm_prepare_range() or
>>>> + * drm_gpuvm_prepare_objects().
>>>> + *
>>>> + * Every bound &drm_gem_object is treated as external object when its &dma_resv
>>>> + * structure is different than the &drm_gpuvm's common &dma_resv structure.
>>>>    */
>>>>   /**
>>>> @@ -429,6 +444,20 @@
>>>>    * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm and
>>>>    * &drm_gem_object must be able to observe previous creations and destructions
>>>>    * of &drm_gpuvm_bos in order to keep instances unique.
>>>> + *
>>>> + * The &drm_gpuvm's lists for keeping track of external and evicted objects are
>>>> + * protected against concurrent insertion / removal and iteration internally.
>>>> + *
>>>> + * However, drivers still need ensure to protect concurrent calls to functions
>>>> + * iterating those lists, namely drm_gpuvm_prepare_objects() and
>>>> + * drm_gpuvm_validate().
>>>> + *
>>>> + * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag to indicate
>>>> + * that the corresponding &dma_resv locks are held in order to protect the
>>>> + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is disabled and
>>>> + * the corresponding lockdep checks are enabled. This is an optimization for
>>>> + * drivers which are capable of taking the corresponding &dma_resv locks and
>>>> + * hence do not require internal locking.
>>>>    */
>>>>   /**
>>>> @@ -641,6 +670,195 @@
>>>>    *    }
>>>>    */
>>>> +/**
>>>> + * get_next_vm_bo_from_list() - get the next vm_bo element
>>>> + * @__gpuvm: The GPU VM
>>>> + * @__list_name: The name of the list we're iterating on
>>>> + * @__local_list: A pointer to the local list used to store already iterated items
>>>> + * @__prev_vm_bo: The previous element we got from drm_gpuvm_get_next_cached_vm_bo()
>>>> + *
>>>> + * This helper is here to provide lockless list iteration. Lockless as in, the
>>>> + * iterator releases the lock immediately after picking the first element from
>>>> + * the list, so list insertion deletion can happen concurrently.
>>>> + *
>>>> + * Elements popped from the original list are kept in a local list, so removal
>>>> + * and is_empty checks can still happen while we're iterating the list.
>>>> + */
>>>> +#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list, __prev_vm_bo)    \
>>>> +    ({                                        \
>>>> +        struct drm_gpuvm_bo *__vm_bo = NULL;                    \
>>>> +                                            \
>>>> + drm_gpuvm_bo_put(__prev_vm_bo);                        \
>>>> +                                            \
>>>> + spin_lock(&(__gpuvm)->__list_name.lock); \
>>>> +        if (!(__gpuvm)->__list_name.local_list)                    \
>>>> +            (__gpuvm)->__list_name.local_list = __local_list;        \
>>>> +        else                                    \
>>>> +            WARN_ON((__gpuvm)->__list_name.local_list != __local_list);    \
>>>> +                                            \
>>>> +        while (!list_empty(&(__gpuvm)->__list_name.list)) {            \
>>>> +            __vm_bo = list_first_entry(&(__gpuvm)->__list_name.list,    \
>>>> +                           struct drm_gpuvm_bo,            \
>>>> +                           list.entry.__list_name);        \
>>>> +            if (kref_get_unless_zero(&__vm_bo->kref)) {            \
>>>> + list_move_tail(&(__vm_bo)->list.entry.__list_name,    \
>>>> +                           __local_list);                \
>>>> +                break;                            \
>>>> +            } else {                            \
>>>> + list_del_init(&(__vm_bo)->list.entry.__list_name);    \
>>>> +                __vm_bo = NULL;                        \
>>>> +            }                                \
>>>> +        }                                    \
>>>> + spin_unlock(&(__gpuvm)->__list_name.lock); \
>>>> +                                            \
>>>> +        __vm_bo;                                \
>>>> +    })
>>>> +
>>>> +/**
>>>> + * for_each_vm_bo_in_list() - internal vm_bo list iterator
>>>> + *
>>>> + * This helper is here to provide lockless list iteration. Lockless as in, the
>>>> + * iterator releases the lock immediately after picking the first element from the
>>>> + * list, hence list insertion and deletion can happen concurrently.
>>>> + *
>>>> + * It is not allowed to re-assign the vm_bo pointer from inside this loop.
>>>> + *
>>>> + * Typical use:
>>>> + *
>>>> + *    struct drm_gpuvm_bo *vm_bo;
>>>> + *    LIST_HEAD(my_local_list);
>>>> + *
>>>> + *    ret = 0;
>>>> + *    for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list, vm_bo) {
>>>> + *        ret = do_something_with_vm_bo(..., vm_bo);
>>>> + *        if (ret)
>>>> + *            break;
>>>> + *    }
>>>> + *    drm_gpuvm_bo_put(vm_bo);
>>>> + *    restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
>>>> + *
>>>> + *
>>>> + * Only used for internal list iterations, not meant to be exposed to the outside
>>>> + * world.
>>>> + */
>>>> +#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list, __vm_bo)    \
>>>> +    for (__vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,        \
>>>> +                        __local_list, NULL);        \
>>>> +         __vm_bo;                                \
>>>> +         __vm_bo = get_next_vm_bo_from_list(__gpuvm, __list_name,        \
>>>> +                        __local_list, __vm_bo))
>>>> +
>>>> +static void
>>>> +__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>>> +             struct list_head *list, struct list_head **local_list)
>>>> +{
>>>> +    /* Merge back the two lists, moving local list elements to the
>>>> +     * head to preserve previous ordering, in case it matters.
>>>> +     */
>>>> +    spin_lock(lock);
>>>> +    if (*local_list) {
>>>> +        list_splice(*local_list, list);
>>>> +        *local_list = NULL;
>>>> +    }
>>>> +    spin_unlock(lock);
>>>> +}
>>>> +
>>>> +/**
>>>> + * restore_vm_bo_list() - move vm_bo elements back to their original list
>>>> + * @__gpuvm: The GPU VM
>>>> + * @__list_name: The name of the list we're iterating on
>>>> + *
>>>> + * When we're done iterating a vm_bo list, we should call restore_vm_bo_list()
>>>> + * to restore the original state and let new iterations take place.
>>>> + */
>>>> +#define restore_vm_bo_list(__gpuvm, __list_name)            \
>>>> +    __restore_vm_bo_list((__gpuvm), &(__gpuvm)->__list_name.lock,    \
>>>> +                 &(__gpuvm)->__list_name.list, \
>>>> +                 &(__gpuvm)->__list_name.local_list)
>>>> +
>>>> +static void
>>>> +cond_spin_lock(spinlock_t *lock, bool cond)
>>>> +{
>>>> +    if (cond)
>>>> +        spin_lock(lock);
>>>> +}
>>>> +
>>>> +static void
>>>> +cond_spin_unlock(spinlock_t *lock, bool cond)
>>>> +{
>>>> +    if (cond)
>>>> +        spin_unlock(lock);
>>>> +}
>>>> +
>>>> +static void
>>>> +__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>>> +            struct list_head *entry, struct list_head *list)
>>>> +{
>>>> +    cond_spin_lock(lock, !!lock);
>>>> +    if (list_empty(entry))
>>>> +        list_add_tail(entry, list);
>>>> +    cond_spin_unlock(lock, !!lock);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
>>>> + * @__vm_bo: the &drm_gpuvm_bo
>>>> + * @__list_name: the name of the list to insert into
>>>> + * @__lock: whether to lock with the internal spinlock
>>>> + *
>>>> + * Inserts the given @__vm_bo into the list specified by @__list_name.
>>>> + */
>>>> +#define drm_gpuvm_bo_list_add(__vm_bo, __list_name, __lock)            \
>>>> + __drm_gpuvm_bo_list_add((__vm_bo)->vm,                    \
>>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>>> +                     NULL,                    \
>>>> + &(__vm_bo)->list.entry.__list_name,        \
>>>> +                &(__vm_bo)->vm->__list_name.list)
>>>> +
>>>> +static void
>>>> +__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
>>>> +            struct list_head *entry, bool init)
>>>> +{
>>>> +    cond_spin_lock(lock, !!lock);
>>>> +    if (init) {
>>>> +        if (!list_empty(entry))
>>>> +            list_del_init(entry);
>>>> +    } else {
>>>> +        list_del(entry);
>>>> +    }
>>>> +    cond_spin_unlock(lock, !!lock);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
>>>> + * @__vm_bo: the &drm_gpuvm_bo
>>>> + * @__list_name: the name of the list to insert into
>>>> + * @__lock: whether to lock with the internal spinlock
>>>> + *
>>>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>>>> + */
>>>> +#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name, __lock)        \
>>>> + __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>>> +                     NULL,                    \
>>>> + &(__vm_bo)->list.entry.__list_name,        \
>>>> +                true)
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
>>>> + * @__vm_bo: the &drm_gpuvm_bo
>>>> + * @__list_name: the name of the list to insert into
>>>> + * @__lock: whether to lock with the internal spinlock
>>>> + *
>>>> + * Removes the given @__vm_bo from the list specified by @__list_name.
>>>> + */
>>>> +#define drm_gpuvm_bo_list_del(__vm_bo, __list_name, __lock)            \
>>>> + __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
>>>> +                __lock ? &(__vm_bo)->vm->__list_name.lock :    \
>>>> +                     NULL,                    \
>>>> + &(__vm_bo)->list.entry.__list_name,        \
>>>> +                false)
>>>> +
>>>>   #define to_drm_gpuva(__node)    container_of((__node), struct drm_gpuva, rb.node)
>>>>   #define GPUVA_START(node) ((node)->va.addr)
>>>> @@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object *r_obj,
>>>>       gpuvm->rb.tree = RB_ROOT_CACHED;
>>>>       INIT_LIST_HEAD(&gpuvm->rb.list);
>>>> +    INIT_LIST_HEAD(&gpuvm->extobj.list);
>>>> +    spin_lock_init(&gpuvm->extobj.lock);
>>>> +
>>>> +    INIT_LIST_HEAD(&gpuvm->evict.list);
>>>> +    spin_lock_init(&gpuvm->evict.lock);
>>>> +
>>>>       drm_gpuvm_check_overflow(start_offset, range);
>>>>       gpuvm->mm_start = start_offset;
>>>>       gpuvm->mm_range = range;
>>>> @@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>       WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
>>>>            "GPUVA tree is not empty, potentially leaking memory.\n");
>>>> +    WARN(!list_empty(&gpuvm->extobj.list), "Extobj list should be empty.\n");
>>>> +    WARN(!list_empty(&gpuvm->evict.list), "Evict list should be empty.\n");
>>>> +
>>>>       drm_gem_object_put(gpuvm->r_obj);
>>>>   }
>>>>   EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>> +static int
>>>> +__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>>>> +                struct drm_exec *exec,
>>>> +                unsigned int num_fences)
>>>> +{
>>>> +    struct drm_gpuvm_bo *vm_bo;
>>>> +    LIST_HEAD(extobjs);
>>>> +    int ret = 0;
>>>> +
>>>> +    for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
>>>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>>>> +        if (ret)
>>>> +            break;
>>>> +    }
>>>> +    /* Drop ref in case we break out of the loop. */
>>>> +    drm_gpuvm_bo_put(vm_bo);
>>>> +    restore_vm_bo_list(gpuvm, extobj);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static int
>>>> +drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
>>>> +                 struct drm_exec *exec,
>>>> +                 unsigned int num_fences)
>>>> +{
>>>> +    struct drm_gpuvm_bo *vm_bo;
>>>> +    int ret = 0;
>>>> +
>>>> +    drm_gpuvm_resv_assert_held(gpuvm);
>>>> +    list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
>>>> +        ret = drm_exec_prepare_obj(exec, vm_bo->obj, num_fences);
>>>> +        if (ret)
>>>> +            break;
>>>> +
>>>> +        if (vm_bo->evicted)
>>>> +            drm_gpuvm_bo_list_add(vm_bo, evict, false);
>>>
>>> Clear vm_bo->evicted here?
>>
>> Why? It's still evicted, hence why not indicate it? It could be useful for a
>> validate_range() use case.
>
> I guess that boils down to what vm_bo->evicted is supposed to mean. I have been using it as "This bo needs to be put on the evicted list", but if we instead mean "This bo was once evicted and might need revalidation and needs rebinding to this VM" then it's OK not to clear it, I guess. But note that another VM might have already re-validated the gem BO, and also if the locking loop or validate loop restarts due to -EINTR or -EDEADLK, then the drm_gpuvm_bo_list_add() will be called multiple times, which is OK but unnecessary. So I'd vote for "This bo needs to be put on the eviced list".

In case of a drm_exec loop restart, the additional drm_gpuvm_bo_list_add() is rather negligible. As mentioned, keeping drm_gpuvm_bo::evicted in an up to date state could be a useful addition to drivers. Besides that, I'd rather make this field safe to use by drivers than document that it's *not* safe to look up for drivers and should only be used with care internally.

>
>>
>>>
>>>
>>>> +    }
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
>>>> + * @gpuvm: the &drm_gpuvm
>>>> + * @exec: the &drm_exec locking context
>>>> + * @num_fences: the amount of &dma_fences to reserve
>>>> + *
>>>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
>>>> + * &drm_gpuvm contains mappings of.
>>>> + *
>>>> + * Using this function directly, it is the drivers responsibility to call
>>>> + * drm_exec_init() and drm_exec_fini() accordingly.
>>>> + *
>>>> + * Note: This function is safe against concurrent insertion and removal of
>>>> + * external objects, however it is not safe against concurrent usage itself.
>>>> + *
>>>> + * Drivers need to make sure to protect this case with either an outer VM lock
>>>> + * or by calling drm_gpuvm_prepare_vm() before this function within the
>>>> + * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv lock ensures
>>>> + * mutual exclusion.
>>>> + *
>>>> + * Returns: 0 on success, negative error code on failure.
>>>> + */
>>>> +int
>>>> +drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
>>>> +              struct drm_exec *exec,
>>>> +              unsigned int num_fences)
>>>> +{
>>>> +    if (drm_gpuvm_resv_protected(gpuvm))
>>>> +        return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
>>>> +                            num_fences);
>>>> +    else
>>>> +        return __drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>>>> +
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given range
>>>> + * @gpuvm: the &drm_gpuvm
>>>> + * @exec: the &drm_exec locking context
>>>> + * @addr: the start address within the VA space
>>>> + * @range: the range to iterate within the VA space
>>>> + * @num_fences: the amount of &dma_fences to reserve
>>>> + *
>>>> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped between @addr
>>>> + * and @addr + @range.
>>>> + *
>>>> + * Returns: 0 on success, negative error code on failure.
>>>> + */
>>>> +int
>>>> +drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec *exec,
>>>> +            u64 addr, u64 range, unsigned int num_fences)
>>>> +{
>>>> +    struct drm_gpuva *va;
>>>> +    u64 end = addr + range;
>>>> +    int ret;
>>>> +
>>>> +    drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
>>>> +        struct drm_gem_object *obj = va->gem.obj;
>>>> +
>>>> +        ret = drm_exec_prepare_obj(exec, obj, num_fences);
>>>> +        if (ret)
>>>> +            return ret;
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
>>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>>> + * @num_fences: the amount of &dma_fences to reserve
>>>> + * @interruptible: sleep interruptible if waiting
>>>> + *
>>>> + * Acquires all dma-resv locks of all &drm_gem_objects the given
>>>> + * &drm_gpuvm contains mappings of.
>>>> + *
>>>> + * Addionally, when calling this function with struct drm_gpuvm_exec::extra
>>>> + * being set the driver receives the given @fn callback to lock additional
>>>> + * dma-resv in the context of the &drm_gpuvm_exec instance. Typically, drivers
>>>> + * would call drm_exec_prepare_obj() from within this callback.
>>>> + *
>>>> + * Returns: 0 on success, negative error code on failure.
>>>> + */
>>>> +int
>>>> +drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
>>>> +            unsigned int num_fences,
>>>> +            bool interruptible)
>>>> +{
>>>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>>>> +    struct drm_exec *exec = &vm_exec->exec;
>>>> +    uint32_t flags;
>>>> +    int ret;
>>>> +
>>>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>>>> +        DRM_EXEC_IGNORE_DUPLICATES;
>>>> +
>>>> +    drm_exec_init(exec, flags);
>>>> +
>>>> +    drm_exec_until_all_locked(exec) {
>>>> +        ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
>>>> +        drm_exec_retry_on_contention(exec);
>>>> +        if (ret)
>>>> +            goto err;
>>>> +
>>>> +        ret = drm_gpuvm_prepare_objects(gpuvm, exec, num_fences);
>>>> +        drm_exec_retry_on_contention(exec);
>>>> +        if (ret)
>>>> +            goto err;
>>>> +
>>>> +        if (vm_exec->extra.fn) {
>>>> +            ret = vm_exec->extra.fn(vm_exec, num_fences);
>>>> +            drm_exec_retry_on_contention(exec);
>>>> +            if (ret)
>>>> +                goto err;
>>>> +        }
>>>> +    }
>>>> +
>>>> +    return 0;
>>>> +
>>>> +err:
>>>> +    drm_exec_fini(exec);
>>>> +    return ret;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
>>>> +
>>>> +static int
>>>> +fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int num_fences)
>>>> +{
>>>> +    struct {
>>>> +        struct drm_gem_object **objs;
>>>> +        unsigned int num_objs;
>>>> +    } *args = vm_exec->extra.priv;
>>>> +
>>>> +    return drm_exec_prepare_array(&vm_exec->exec, args->objs,
>>>> +                      args->num_objs, num_fences);
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_exec_lock_array() - lock all dma-resv of all assoiciated BOs
>>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>>> + * @objs: additional &drm_gem_objects to lock
>>>> + * @num_objs: the number of additional &drm_gem_objects to lock
>>>> + * @num_fences: the amount of &dma_fences to reserve
>>>> + * @interruptible: sleep interruptible if waiting
>>>> + *
>>>> + * Acquires all dma-resv locks of all &drm_gem_objects the given &drm_gpuvm
>>>> + * contains mappings of, plus the ones given through @objs.
>>>> + *
>>>> + * Returns: 0 on success, negative error code on failure.
>>>> + */
>>>> +int
>>>> +drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
>>>> +              struct drm_gem_object **objs,
>>>> +              unsigned int num_objs,
>>>> +              unsigned int num_fences,
>>>> +              bool interruptible)
>>>> +{
>>>> +    struct {
>>>> +        struct drm_gem_object **objs;
>>>> +        unsigned int num_objs;
>>>> +    } args;
>>>> +
>>>> +    args.objs = objs;
>>>> +    args.num_objs = num_objs;
>>>> +
>>>> +    vm_exec->extra.fn = fn_lock_array;
>>>> +    vm_exec->extra.priv = &args;
>>>> +
>>>> +    return drm_gpuvm_exec_lock(vm_exec, num_fences, interruptible);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a given range
>>>> + * @vm_exec: the &drm_gpuvm_exec wrapper
>>>> + * @addr: the start address within the VA space
>>>> + * @range: the range to iterate within the VA space
>>>> + * @num_fences: the amount of &dma_fences to reserve
>>>> + * @interruptible: sleep interruptible if waiting
>>>> + *
>>>> + * Acquires all dma-resv locks of all &drm_gem_objects mapped between @addr and
>>>> + * @addr + @range.
>>>> + *
>>>> + * Returns: 0 on success, negative error code on failure.
>>>> + */
>>>> +int
>>>> +drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
>>>> +              u64 addr, u64 range,
>>>> +              unsigned int num_fences,
>>>> +              bool interruptible)
>>>> +{
>>>> +    struct drm_gpuvm *gpuvm = vm_exec->vm;
>>>> +    struct drm_exec *exec = &vm_exec->exec;
>>>> +    uint32_t flags;
>>>> +    int ret;
>>>> +
>>>> +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
>>>> +        DRM_EXEC_IGNORE_DUPLICATES;
>>>> +
>>>> +    drm_exec_init(exec, flags);
>>>> +
>>>> +    drm_exec_until_all_locked(exec) {
>>>> +        ret = drm_gpuvm_prepare_range(gpuvm, exec, addr, range,
>>>> +                          num_fences);
>>>> +        drm_exec_retry_on_contention(exec);
>>>> +        if (ret)
>>>> +            goto err;
>>>> +    }
>>>> +
>>>> +    return ret;
>>>> +
>>>> +err:
>>>> +    drm_exec_fini(exec);
>>>> +    return ret;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
>>>> +
>>>> +static int
>>>> +__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>>> +{
>>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>>> +    struct drm_gpuvm_bo *vm_bo;
>>>> +    LIST_HEAD(evict);
>>>> +    int ret = 0;
>>>> +
>>>> +    for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
>>>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>>>> +        if (ret)
>>>> +            break;
>>>> +    }
>>>> +    /* Drop ref in case we break out of the loop. */
>>>> +    drm_gpuvm_bo_put(vm_bo);
>>>> +    restore_vm_bo_list(gpuvm, evict);
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +static int
>>>> +drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>>> +{
>>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>>> +    struct drm_gpuvm_bo *vm_bo, *next;
>>>> +    int ret = 0;
>>>> +
>>>> +    drm_gpuvm_resv_assert_held(gpuvm);
>>>> +
>>>> +    /* Iterate list safely, drivers typically remove the current entry from
>>>> +     * their drm_gpuvm_ops::vm_bo_validate callback. Drivers might also
>>>> +     * re-add the entry on failure; this is safe since on failure we break
>>>> +     * out of the loop.
>>>> +     */
>>>> +    list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
>>>> +                 list.entry.evict) {
>>>> +        ret = ops->vm_bo_validate(vm_bo, exec);
>>>> +        if (ret)
>>>> +            break;
>>>> +    }
>>>> +
>>>> +    return ret;
>>>> +}
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_validate() - validate all BOs marked as evicted
>>>> + * @gpuvm: the &drm_gpuvm to validate evicted BOs
>>>> + * @exec: the &drm_exec instance used for locking the GPUVM
>>>> + *
>>>> + * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted buffer
>>>> + * objects being mapped in the given &drm_gpuvm.
>>>> + *
>>>> + * Returns: 0 on success, negative error code on failure.
>>>> + */
>>>> +int
>>>> +drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
>>>> +{
>>>> +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>>> +
>>>> +    if (unlikely(!ops || !ops->vm_bo_validate))
>>>> +        return -ENOTSUPP;
>>>> +
>>>> +    if (drm_gpuvm_resv_protected(gpuvm))
>>>> +        return drm_gpuvm_validate_locked(gpuvm, exec);
>>>> +    else
>>>> +        return __drm_gpuvm_validate(gpuvm, exec);
>>>> +
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
>>>> + * dma-resv
>>>> + * @gpuvm: the &drm_gpuvm to add a fence to
>>>> + * @exec: the &drm_exec locking context
>>>> + * @fence: fence to add
>>>> + * @private_usage: private dma-resv usage
>>>> + * @extobj_usage: extobj dma-resv usage
>>>> + */
>>>> +void
>>>> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
>>>> +             struct drm_exec *exec,
>>>> +             struct dma_fence *fence,
>>>> +             enum dma_resv_usage private_usage,
>>>> +             enum dma_resv_usage extobj_usage)
>>>> +{
>>>> +    struct drm_gem_object *obj;
>>>> +    unsigned long index;
>>>> +
>>>> +    drm_exec_for_each_locked_object(exec, index, obj) {
>>>> +        dma_resv_assert_held(obj->resv);
>>>> +        dma_resv_add_fence(obj->resv, fence,
>>>> +                   drm_gpuvm_is_extobj(gpuvm, obj) ?
>>>> +                   private_usage : extobj_usage);
>>>> +    }
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
>>>> +
>>>>   /**
>>>>    * drm_gpuvm_bo_create() - create a new instance of struct drm_gpuvm_bo
>>>>    * @gpuvm: The &drm_gpuvm the @obj is mapped in.
>>>> @@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
>>>>       INIT_LIST_HEAD(&vm_bo->list.gpuva);
>>>>       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
>>>> +    INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
>>>> +    INIT_LIST_HEAD(&vm_bo->list.entry.evict);
>>>> +
>>>>       drm_gem_object_get(obj);
>>>>       return vm_bo;
>>>> @@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
>>>>       if (!lock)
>>>>           drm_gpuvm_resv_assert_held(gpuvm);
>>>> +    drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
>>>> +    drm_gpuvm_bo_list_del(vm_bo, evict, lock);
>>>> +
>>>>       list_del(&vm_bo->list.entry.gem);
>>>>       drm_gem_object_put(obj);
>>>> @@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
>>>>   }
>>>>   EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
>>>> +/**
>>>> + * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its &drm_gpuvm's
>>>> + * extobj list
>>>> + * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj list.
>>>> + *
>>>> + * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on the list
>>>> + * already and if the corresponding &drm_gem_object is an external object,
>>>> + * actually.
>>>> + */
>>>> +void
>>>> +drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
>>>> +{
>>>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>>>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>>>> +
>>>> +    if (!lock)
>>>> +        drm_gpuvm_resv_assert_held(gpuvm);
>>>> +
>>>> +    if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
>>>> +        drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the &drm_gpuvms
>>>> + * evicted list
>>>> + * @vm_bo: the &drm_gpuvm_bo to add or remove
>>>> + * @evict: indicates whether the object is evicted
>>>> + *
>>>> + * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms evicted list.
>>>> + */
>>>> +void
>>>> +drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
>>>> +{
>>>> +    struct drm_gpuvm *gpuvm = vm_bo->vm;
>>>> +    struct drm_gem_object *obj = vm_bo->obj;
>>>> +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
>>>> +
>>>> +    dma_resv_assert_held(obj->resv);
>>>> +
>>>> +    if (drm_gpuvm_is_extobj(gpuvm, obj)) {
>>>> +        vm_bo->evicted = evict;
>>> Does the lock case also need this?
>>
>> It doesn't need it by itself, but since we have drm_gpuvm_bo::evicted now, I want it to
>> consistently indicate whether the BO is evicted or not.
>
> OK, I guess that ties to the meaning of bo->evicted, as discussed above.
>
>>
>>>> +
>>>> +        if (!lock)
>>>> +            return;
>>>
>>> Here the !lock case can not remove the gpuvm_bo from the list?
>>
>> You mean because we'd expect that drm_gpuvm_bo_evict(vm_bo, false) can only be called from
>> within gpuvm_validate(), which requires the VM's resv lock? What if there is a ttm_validate()
>> call for only this GEM obj?
>>
>> My idea was to remove VM_BOs from the evicted list in gpuvm_validate() directly, but I'm afraid
>> I forgot that.
>
> Yes, I think the helper could do it if validate() is successful. But what I meant above was that if the *driver* is responsible for removing object from the evicted list, then if it's a RESV_PROTECTED vm, it can't do that because drm_gpuvm_bo_evict(gpuvm, false) will never get to removing it from the list because it returns early.

Well, with a RESV_PROTECTED VM we can't expect the evicted list to be up to date until we called drm_gpuvm_prepare_objects() by definition. In drm_gpuvm_prepare_objects() we'd need to

if (vm_bo->evicted)
add_to_list()
else
remove_from_list()

though, because a driver could lock and unlock those objects without validation.

>
> Thanks,
>
> Thomas
>
>
>>
>>>
>>> Small patch here that I've been using for xe:
>>>
>>> https://patchwork.freedesktop.org/patch/561545/?series=124817&rev=1
>>>
>>> Thanks,
>>>
>>> Thomas
>>>
>>>
>>
>

2023-10-13 12:06:19

by Danilo Krummrich

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects

On 10/10/23 08:40, Thomas Hellström wrote:
>
> On 10/9/23 01:32, Danilo Krummrich wrote:
>> Currently the DRM GPUVM offers common infrastructure to track GPU VA
>> allocations and mappings, generically connect GPU VA mappings to their
>> backing buffers and perform more complex mapping operations on the GPU VA
>> space.
>>
>> However, there are more design patterns commonly used by drivers, which
>> can potentially be generalized in order to make the DRM GPUVM represent
>> a basis for GPU-VM implementations. In this context, this patch aims
>> at generalizing the following elements.
>>
>> 1) Provide a common dma-resv for GEM objects not being used outside of
>>     this GPU-VM.
>>
>> 2) Provide tracking of external GEM objects (GEM objects which are
>>     shared with other GPU-VMs).
>>
>> 3) Provide functions to efficiently lock all GEM objects dma-resv the
>>     GPU-VM contains mappings of.
>>
>> 4) Provide tracking of evicted GEM objects the GPU-VM contains mappings
>>     of, such that validation of evicted GEM objects is accelerated.
>>
>> 5) Provide some convinience functions for common patterns.
>>
>> Big thanks to Boris Brezillon for his help to figure out locking for
>> drivers updating the GPU VA space within the fence signalling path.
>>
>> Suggested-by: Matthew Brost <[email protected]>
>> Signed-off-by: Danilo Krummrich <[email protected]>
>>
>> +/**
>> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
>> + * dma-resv
>> + * @gpuvm: the &drm_gpuvm to add a fence to
>> + * @exec: the &drm_exec locking context
>> + * @fence: fence to add
>> + * @private_usage: private dma-resv usage
>> + * @extobj_usage: extobj dma-resv usage
>> + */
>> +void
>> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
>> +             struct drm_exec *exec,
>> +             struct dma_fence *fence,
>> +             enum dma_resv_usage private_usage,
>> +             enum dma_resv_usage extobj_usage)
>> +{
>> +    struct drm_gem_object *obj;
>> +    unsigned long index;
>> +
>> +    drm_exec_for_each_locked_object(exec, index, obj) {
>> +        dma_resv_assert_held(obj->resv);
>> +        dma_resv_add_fence(obj->resv, fence,
>> +                   drm_gpuvm_is_extobj(gpuvm, obj) ?
>> +                   private_usage : extobj_usage);
>
> It looks like private_usage and extobj_usage are mixed up above?

Good catch, will fix.

>
>
>> +    }
>> +}
>> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
>> +
>
> Thanks,
>
> Thomas
>
>

2023-10-13 12:31:29

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 3/6] drm/gpuvm: add an abstraction for a VM / BO combination

On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
>
> This has multiple advantages:
>
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
>
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
>
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
>
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
>
> Cc: Christian König <[email protected]>
> Signed-off-by: Danilo Krummrich <[email protected]>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 332 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  64 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 177 ++++++++++++-
>  4 files changed, 521 insertions(+), 84 deletions(-)
>
> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 6368dfdbe9dd..28282283ddaf 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -70,6 +70,18 @@
>   * &drm_gem_object, such as the &drm_gem_object containing the root
> page table,
>   * but it can also be a 'dummy' object, which can be allocated with
>   * drm_gpuvm_root_object_alloc().
> + *
> + * In order to connect a struct drm_gpuva its backing
> &drm_gem_object each
NIT: Same as previous patch regarding kerneldoc references

> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> each
> + * &drm_gpuvm_bo contains a list of &&drm_gpuva structures.
> + *
> + * A &drm_gpuvm_bo is an abstraction that represents a combination
> of a
> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> be unique.
> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> corresponding
> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
> + * particular combination. If not existent a new instance is created
> and linked
> + * to the &drm_gem_object.
>   */
>  
>  /**
> @@ -395,21 +407,28 @@
>  /**
>   * DOC: Locking
>   *
> - * Generally, the GPU VA manager does not take care of locking
> itself, it is
> - * the drivers responsibility to take care about locking. Drivers
> might want to
> - * protect the following operations: inserting, removing and
> iterating
> - * &drm_gpuva objects as well as generating all kinds of operations,
> such as
> - * split / merge or prefetch.
> - *
> - * The GPU VA manager also does not take care of the locking of the
> backing
> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> responsible to
> - * enforce mutual exclusion using either the GEMs dma_resv lock or
> alternatively
> - * a driver specific external lock. For the latter see also
> - * drm_gem_gpuva_set_lock().
> - *
> - * However, the GPU VA manager contains lockdep checks to ensure
> callers of its
> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> VA list is
> - * accessed by functions such as drm_gpuva_link() or
> drm_gpuva_unlink().
> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> care of
> + * locking itself, it is the drivers responsibility to take care
> about locking.
> + * Drivers might want to protect the following operations:
> inserting, removing
> + * and iterating &drm_gpuva objects as well as generating all kinds
> of
> + * operations, such as split / merge or prefetch.
> + *
> + * DRM GPUVM also does not take care of the locking of the backing
> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> abstractions by
> + * itself; drivers are responsible to enforce mutual exclusion using
> either the
> + * GEMs dma_resv lock or alternatively a driver specific external
> lock. For the
> + * latter see also drm_gem_gpuva_set_lock().
> + *
> + * However, DRM GPUVM contains lockdep checks to ensure callers of
> its API hold
> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> is accessed
> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> also
> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> + *
> + * The latter is required since on creation and destruction of a
> &drm_gpuvm_bo
> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> gpuva list.
> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
> + * &drm_gem_object must be able to observe previous creations and
> destructions
> + * of &drm_gpuvm_bos in order to keep instances unique.
>   */
>  
>  /**
> @@ -439,6 +458,7 @@
>   *     {
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op
> + *             struct drm_gpuvm_bo *vm_bo;
>   *
>   *             driver_lock_va_space();
>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> @@ -446,6 +466,10 @@
>   *             if (IS_ERR(ops))
>   *                     return PTR_ERR(ops);
>   *
> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> + *             if (IS_ERR(vm_bo))
> + *                     return PTR_ERR(vm_bo);
> + *
>   *             drm_gpuva_for_each_op(op, ops) {
>   *                     struct drm_gpuva *va;
>   *
> @@ -458,7 +482,7 @@
>   *
>   *                             driver_vm_map();
>   *                             drm_gpuva_map(gpuvm, va, &op->map);
> - *                             drm_gpuva_link(va);
> + *                             drm_gpuva_link(va, vm_bo);
>   *
>   *                             break;
>   *                     case DRM_GPUVA_OP_REMAP: {
> @@ -485,11 +509,11 @@
>   *                             driver_vm_remap();
>   *                             drm_gpuva_remap(prev, next, &op-
> >remap);
>   *
> - *                             drm_gpuva_unlink(va);
>   *                             if (prev)
> - *                                     drm_gpuva_link(prev);
> + *                                     drm_gpuva_link(prev, va-
> >vm_bo);
>   *                             if (next)
> - *                                     drm_gpuva_link(next);
> + *                                     drm_gpuva_link(next, va-
> >vm_bo);
> + *                             drm_gpuva_unlink(va);
>   *
>   *                             break;
>   *                     }
> @@ -505,6 +529,7 @@
>   *                             break;
>   *                     }
>   *             }
> + *             drm_gpuvm_bo_put(vm_bo);
>   *             driver_unlock_va_space();
>   *
>   *             return 0;
> @@ -514,6 +539,7 @@
>   *
>   *     struct driver_context {
>   *             struct drm_gpuvm *gpuvm;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva *new_va;
>   *             struct drm_gpuva *prev_va;
>   *             struct drm_gpuva *next_va;
> @@ -534,6 +560,7 @@
>   *                               struct drm_gem_object *obj, u64
> offset)
>   *     {
>   *             struct driver_context ctx;
> + *             struct drm_gpuvm_bo *vm_bo;
>   *             struct drm_gpuva_ops *ops;
>   *             struct drm_gpuva_op *op;
>   *             int ret = 0;
> @@ -543,16 +570,23 @@
>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> GFP_KERNEL);
>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> GFP_KERNEL);
>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> GFP_KERNEL);
> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> !vm_bo) {
>   *                     ret = -ENOMEM;
>   *                     goto out;
>   *             }
>   *
> + *             // Typically protected with a driver specific GEM
> gpuva lock
> + *             // used in the fence signaling path for
> drm_gpuva_link() and
> + *             // drm_gpuva_unlink(), hence pre-allocate.
> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> + *
>   *             driver_lock_va_space();
>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> offset);
>   *             driver_unlock_va_space();
>   *
>   *     out:
> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>   *             kfree(ctx.new_va);
>   *             kfree(ctx.prev_va);
>   *             kfree(ctx.next_va);
> @@ -565,7 +599,7 @@
>   *
>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>   *
> - *             drm_gpuva_link(ctx->new_va);
> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>   *
>   *             // prevent the new GPUVA from being freed in
>   *             // driver_mapping_create()
> @@ -577,22 +611,23 @@
>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>   *     {
>   *             struct driver_context *ctx = __ctx;
> + *             struct drm_gpuva *va = op->remap.unmap->va;
>   *
>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> >remap);
>   *
> - *             drm_gpuva_unlink(op->remap.unmap->va);
> - *             kfree(op->remap.unmap->va);
> - *
>   *             if (op->remap.prev) {
> - *                     drm_gpuva_link(ctx->prev_va);
> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>   *                     ctx->prev_va = NULL;
>   *             }
>   *
>   *             if (op->remap.next) {
> - *                     drm_gpuva_link(ctx->next_va);
> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>   *                     ctx->next_va = NULL;
>   *             }
>   *
> + *             drm_gpuva_unlink(va);
> + *             kfree(va);
> + *
>   *             return 0;
>   *     }
>   *
> @@ -771,6 +806,194 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>  
> +/**
> + * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * If provided by the driver, this function uses the &drm_gpuvm_ops
> + * vm_bo_alloc() callback to allocate.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on

Kerneldoc uses "Return:" rather than "Returns:", (This seems to a
common thing throughout the series).

> failure
> + */
> +struct drm_gpuvm_bo *

Any particular reason there's line-break after the function type even
when it fits the ~100 char limit?

> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)



> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       if (ops && ops->vm_bo_alloc)
> +               vm_bo = ops->vm_bo_alloc();
> +       else
> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> +
> +       if (unlikely(!vm_bo))
> +               return NULL;
> +
> +       vm_bo->vm = gpuvm;
> +       vm_bo->obj = obj;
> +
> +       kref_init(&vm_bo->kref);
> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> +
> +       drm_gem_object_get(obj);

Perhaps group this with the vm_bo->obj assignment to emphasize that
that's the pointer that gets the reference?

> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> +
> +static void
> +drm_gpuvm_bo_destroy(struct kref *kref)
> +{
> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> drm_gpuvm_bo,
> +                                                 kref);
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       list_del(&vm_bo->list.entry.gem);
> +
> +       drm_gem_object_put(obj);

Not sure if we have any drivers utilizing vm_bo_free() yet, but it
might make sense to move the drm_gem_object_put() until after the
freeing below, in case vm_bo_free() wants to use it?

> +
> +       if (ops && ops->vm_bo_free)
> +               ops->vm_bo_free(vm_bo);
> +       else
> +               kfree(vm_bo);
> +}
> +
> +/**
> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> + *
> + * This releases a reference to @vm_bo.
> + *
> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> which
> + * includes removing it from the GEMs gpuva list. Hence, if a call
> to this
> + * function can potentially let the reference count to zero the
> caller must
> + * hold the dma-resv or driver specific GEM gpuva lock.
> + */
> +void
> +drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
> +{
> +       if (vm_bo)
> +               kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
> +
> +static struct drm_gpuvm_bo *
> +__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj)
> +               if (vm_bo->vm == gpuvm)
> +                       return vm_bo;
> +
> +       return NULL;
> +}
> +
> +/**
> + * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
> + * &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
> +
> +       return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
> +
> +/**
> + * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo
> for the
> + * given &drm_gpuvm and &drm_gem_object
> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the &drm_gpuvm_bo accordingly. If not found, allocates a
> new
> + * &drm_gpuvm_bo.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on
> failure
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo)
> +               return vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> +       if (!vm_bo)
> +               return ERR_PTR(-ENOMEM);
> +
> +       list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);

Lockdep check?

> +
> +       return vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
> +
> +/**
> + * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the
> &drm_gpuvm_bo
> + * for the given &drm_gpuvm and &drm_gem_object
> + * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
> + *
> + * Find the &drm_gpuvm_bo representing the combination of the given
> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> + * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo
> reference
> + * count is decreased. If not found @__vm_bo is returned without
> further
> + * increase of the reference count.
> + *
> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> + *
> + * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no
> existing
> + * &drm_gpuvm_bo was found
> + */
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
> +{
> +       struct drm_gpuvm *gpuvm = __vm_bo->vm;
> +       struct drm_gem_object *obj = __vm_bo->obj;
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> +       if (vm_bo) {
> +               drm_gpuvm_bo_put(__vm_bo);
> +               return vm_bo;
> +       }
> +
> +       list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);

Perhaps a lockdep check here?

> +
> +       return __vm_bo;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
> +
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>                    struct drm_gpuva *va)
> @@ -860,24 +1083,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>  /**
>   * drm_gpuva_link() - link a &drm_gpuva
>   * @va: the &drm_gpuva to link
> + * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
>   *
> - * This adds the given &va to the GPU VA list of the &drm_gem_object
> it is
> - * associated with.
> + * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo
> and the
> + * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
> + *
> + * For every &drm_gpuva entry added to the &drm_gpuvm_bo an
> additional
> + * reference of the latter is taken.
>   *
>   * This function expects the caller to protect the GEM's GPUVA list
> against

NIT: Referring to a "gem object" as a "GEM" catches my eye every time.
Perhaps that has become common practice? With my "it used to be like.."
hat on, I'd use gem object.

> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
> -drm_gpuva_link(struct drm_gpuva *va)
> +drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
>  
>         if (unlikely(!obj))
>                 return;
>  
> +       WARN_ON(obj != vm_bo->obj);

Can we use drm_WARN here?

>         drm_gem_gpuva_assert_lock_held(obj);
>  
> -       list_add_tail(&va->gem.entry, &obj->gpuva.list);
> +       drm_gpuvm_bo_get(vm_bo);
> +
> +       va->vm_bo = vm_bo;
> +       list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_link);
>  
> @@ -888,13 +1120,22 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
>   * This removes the given &va from the GPU VA list of the
> &drm_gem_object it is
>   * associated with.
>   *
> + * This removes the given &va from the GPU VA list of the
> &drm_gpuvm_bo and
> + * the &drm_gpuvm_bo from the &drm_gem_object it is associated with
> in case
> + * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
> + *
> + * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a
> reference of
> + * the latter is dropped.
> + *
>   * This function expects the caller to protect the GEM's GPUVA list
> against
> - * concurrent access using the GEMs dma_resv lock.
> + * concurrent access using either the GEMs dma_resv lock or a driver
> specific
> + * lock set through drm_gem_gpuva_set_lock().
>   */
>  void
>  drm_gpuva_unlink(struct drm_gpuva *va)
>  {
>         struct drm_gem_object *obj = va->gem.obj;
> +       struct drm_gpuvm_bo *vm_bo = va->vm_bo;
>  
>         if (unlikely(!obj))
>                 return;
> @@ -902,6 +1143,9 @@ drm_gpuva_unlink(struct drm_gpuva *va)
>         drm_gem_gpuva_assert_lock_held(obj);
>  
>         list_del_init(&va->gem.entry);
> +       va->vm_bo = NULL;
> +
> +       drm_gpuvm_bo_put(vm_bo);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
>  
> @@ -1046,10 +1290,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
>                 struct drm_gpuva *next,
>                 struct drm_gpuva_op_remap *op)
>  {
> -       struct drm_gpuva *curr = op->unmap->va;
> -       struct drm_gpuvm *gpuvm = curr->vm;
> +       struct drm_gpuva *va = op->unmap->va;
> +       struct drm_gpuvm *gpuvm = va->vm;
>  
> -       drm_gpuva_remove(curr);
> +       drm_gpuva_remove(va);
>  
>         if (op->prev) {
>                 drm_gpuva_init_from_op(prev, op->prev);
> @@ -1693,9 +1937,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>  EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>  
>  /**
> - * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> - * @gpuvm: the &drm_gpuvm representing the GPU VA space
> - * @obj: the &drm_gem_object to unmap
> + * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to
> unmap a GEM
> + * @vm_bo: the &drm_gpuvm_bo abstraction
>   *
>   * This function creates a list of operations to perform unmapping
> for every
>   * GPUVA attached to a GEM.
> @@ -1712,15 +1955,14 @@
> EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>   * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR
> on failure
>   */
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj)
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
>  {
>         struct drm_gpuva_ops *ops;
>         struct drm_gpuva_op *op;
>         struct drm_gpuva *va;
>         int ret;
>  
> -       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_gpuva_assert_lock_held(vm_bo->obj);
>  
>         ops = kzalloc(sizeof(*ops), GFP_KERNEL);
>         if (!ops)
> @@ -1728,8 +1970,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm
> *gpuvm,
>  
>         INIT_LIST_HEAD(&ops->list);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               op = gpuva_op_alloc(gpuvm);
> +       drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +               op = gpuva_op_alloc(vm_bo->vm);
>                 if (!op) {
>                         ret = -ENOMEM;
>                         goto err_free_ops;
> @@ -1743,10 +1985,10 @@ drm_gpuvm_gem_unmap_ops_create(struct
> drm_gpuvm *gpuvm,
>         return ops;
>  
>  err_free_ops:
> -       drm_gpuva_ops_free(gpuvm, ops);
> +       drm_gpuva_ops_free(vm_bo->vm, ops);
>         return ERR_PTR(ret);
>  }
> -EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
>  
>  /**
>   * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> index 93ad2ba7ec8b..4e46f850e65f 100644
> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> @@ -62,6 +62,8 @@ struct bind_job_op {
>         enum vm_bind_op op;
>         u32 flags;
>  
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         struct {
>                 u64 addr;
>                 u64 range;
> @@ -1113,22 +1115,28 @@ bind_validate_region(struct nouveau_job *job)
>  }
>  
>  static void
> -bind_link_gpuvas(struct drm_gpuva_ops *ops, struct
> nouveau_uvma_prealloc *new)
> +bind_link_gpuvas(struct bind_job_op *bop)
>  {
> +       struct nouveau_uvma_prealloc *new = &bop->new;
> +       struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
> +       struct drm_gpuva_ops *ops = bop->ops;
>         struct drm_gpuva_op *op;
>  
>         drm_gpuva_for_each_op(op, ops) {
>                 switch (op->op) {
>                 case DRM_GPUVA_OP_MAP:
> -                       drm_gpuva_link(&new->map->va);
> +                       drm_gpuva_link(&new->map->va, vm_bo);
>                         break;
> -               case DRM_GPUVA_OP_REMAP:
> +               case DRM_GPUVA_OP_REMAP: {
> +                       struct drm_gpuva *va = op->remap.unmap->va;
> +
>                         if (op->remap.prev)
> -                               drm_gpuva_link(&new->prev->va);
> +                               drm_gpuva_link(&new->prev->va, va-
> >vm_bo);
>                         if (op->remap.next)
> -                               drm_gpuva_link(&new->next->va);
> -                       drm_gpuva_unlink(op->remap.unmap->va);
> +                               drm_gpuva_link(&new->next->va, va-
> >vm_bo);
> +                       drm_gpuva_unlink(va);
>                         break;
> +               }
>                 case DRM_GPUVA_OP_UNMAP:
>                         drm_gpuva_unlink(op->unmap.va);
>                         break;
> @@ -1150,10 +1158,18 @@ nouveau_uvmm_bind_job_submit(struct
> nouveau_job *job)
>  
>         list_for_each_op(op, &bind_job->ops) {
>                 if (op->op == OP_MAP) {
> -                       op->gem.obj = drm_gem_object_lookup(job-
> >file_priv,
> -                                                           op-
> >gem.handle);
> -                       if (!op->gem.obj)
> +                       struct drm_gem_object *obj;
> +
> +                       obj = drm_gem_object_lookup(job->file_priv,
> +                                                   op->gem.handle);
> +                       if (!(op->gem.obj = obj))
>                                 return -ENOENT;
> +
> +                       dma_resv_lock(obj->resv, NULL);
> +                       op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base,
> obj);
> +                       dma_resv_unlock(obj->resv);
> +                       if (IS_ERR(op->vm_bo))
> +                               return PTR_ERR(op->vm_bo);
>                 }
>  
>                 ret = bind_validate_op(job, op);
> @@ -1364,7 +1380,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> *job)
>                 case OP_UNMAP_SPARSE:
>                 case OP_MAP:
>                 case OP_UNMAP:
> -                       bind_link_gpuvas(op->ops, &op->new);
> +                       bind_link_gpuvas(op);
>                         break;
>                 default:
>                         break;
> @@ -1511,6 +1527,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct
> work_struct *work)
>                 if (!IS_ERR_OR_NULL(op->ops))
>                         drm_gpuva_ops_free(&uvmm->base, op->ops);
>  
> +               if (!IS_ERR_OR_NULL(op->vm_bo)) {
> +                       dma_resv_lock(obj->resv, NULL);
> +                       drm_gpuvm_bo_put(op->vm_bo);
> +                       dma_resv_unlock(obj->resv);
> +               }
> +
>                 if (obj)
>                         drm_gem_object_put(obj);
>         }
> @@ -1776,15 +1798,18 @@ void
>  nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem
> *mem)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_map(uvma, mem);
> -               drm_gpuva_invalidate(va, false);
> +                       nouveau_uvma_map(uvma, mem);
> +                       drm_gpuva_invalidate(va, false);
> +               }
>         }
>  }
>  
> @@ -1792,15 +1817,18 @@ void
>  nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>  {
>         struct drm_gem_object *obj = &nvbo->bo.base;
> +       struct drm_gpuvm_bo *vm_bo;
>         struct drm_gpuva *va;
>  
>         dma_resv_assert_held(obj->resv);
>  
> -       drm_gem_for_each_gpuva(va, obj) {
> -               struct nouveau_uvma *uvma = uvma_from_va(va);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>  
> -               nouveau_uvma_unmap(uvma);
> -               drm_gpuva_invalidate(va, true);
> +                       nouveau_uvma_unmap(uvma);
> +                       drm_gpuva_invalidate(va, true);
> +               }
>         }
>  }
>  
> diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
> index 16364487fde9..369505447acd 100644
> --- a/include/drm/drm_gem.h
> +++ b/include/drm/drm_gem.h
> @@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
>   * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
>   * @obj: the &drm_gem_object
>   *
> - * This initializes the &drm_gem_object's &drm_gpuva list.
> + * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
>   *
>   * Calling this function is only necessary for drivers intending to
> support the
>   * &drm_driver_feature DRIVER_GEM_GPUVA.
> @@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct
> drm_gem_object *obj)
>  }
>  
>  /**
> - * drm_gem_for_each_gpuva() - iternator to walk over a list of
> gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of
> &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bo structure to assign to in each iteration
> step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> - * &drm_gpuva_manager.
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
> + * &drm_gem_object.
>   */
> -#define drm_gem_for_each_gpuva(entry__, obj__) \
> -       list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
> +       list_for_each_entry(entry__, &(obj__)->gpuva.list,
> list.entry.gem)
>  
>  /**
> - * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a
> list of
> - * gpuvas
> - * @entry__: &drm_gpuva structure to assign to in each iteration
> step
> - * @next__: &next &drm_gpuva to store the next step
> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> associated with
> + * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuvm_bo
> + * @entry__: &drm_gpuvm_bostructure to assign to in each iteration
> step
> + * @next__: &next &drm_gpuvm_bo to store the next step
> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> associated with
>   *
> - * This iterator walks over all &drm_gpuva structures associated
> with the
> + * This iterator walks over all &drm_gpuvm_bo structures associated
> with the
>   * &drm_gem_object. It is implemented with
> list_for_each_entry_safe(), hence
>   * it is save against removal of elements.
>   */
> -#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
> -       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, gem.entry)
> +#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
> +       list_for_each_entry_safe(entry__, next__, &(obj__)-
> >gpuva.list, list.entry.gem)
>  
>  #endif /* __DRM_GEM_H__ */
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index 13539f32c2e2..ddb0b8d323cf 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -26,12 +26,14 @@
>   */
>  
>  #include <linux/list.h>
> +#include <linux/dma-resv.h>
>  #include <linux/rbtree.h>
>  #include <linux/types.h>
>  
>  #include <drm/drm_gem.h>
>  
>  struct drm_gpuvm;
> +struct drm_gpuvm_bo;
>  struct drm_gpuvm_ops;
>  
>  /**
> @@ -72,6 +74,12 @@ struct drm_gpuva {
>          */
>         struct drm_gpuvm *vm;
>  
> +       /**
> +        * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
> +        * &drm_gem_object
> +        */
> +       struct drm_gpuvm_bo *vm_bo;
> +
>         /**
>          * @flags: the &drm_gpuva_flags for this mapping
>          */
> @@ -107,7 +115,7 @@ struct drm_gpuva {
>                 struct drm_gem_object *obj;
>  
>                 /**
> -                * @entry: the &list_head to attach this object to a
> &drm_gem_object
> +                * @entry: the &list_head to attach this object to a
> &drm_gpuvm_bo
>                  */
>                 struct list_head entry;
>         } gem;
> @@ -140,7 +148,7 @@ struct drm_gpuva {
>  int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
>  void drm_gpuva_remove(struct drm_gpuva *va);
>  
> -void drm_gpuva_link(struct drm_gpuva *va);
> +void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo
> *vm_bo);
>  void drm_gpuva_unlink(struct drm_gpuva *va);
>  
>  struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
> @@ -187,10 +195,16 @@ static inline bool drm_gpuva_invalidated(struct
> drm_gpuva *va)
>   * enum drm_gpuvm_flags - flags for struct drm_gpuvm
>   */
>  enum drm_gpuvm_flags {
> +       /**
> +        * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally
> by the
> +        * GPUVM's &dma_resv lock
> +        */
> +       DRM_GPUVM_RESV_PROTECTED = (1 << 0),
> +
>         /**
>          * @DRM_GPUVM_USERBITS: user defined bits
>          */
> -       DRM_GPUVM_USERBITS = (1 << 0),
> +       DRM_GPUVM_USERBITS = (1 << 1),
>  };
>  
>  /**
> @@ -272,6 +286,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm
> *gpuvm, u64 addr, u64 range);
>  struct drm_gem_object *
>  drm_gpuvm_root_object_alloc(struct drm_device *drm);
>  
> +/**
> + * drm_gpuvm_resv_protected() - indicates whether
> &DRM_GPUVM_RESV_PROTECTED is
> + * set
> + * @gpuvm: the &drm_gpuvm
> + *
> + * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false
> otherwise.
> + */
> +static inline bool
> +drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
> +{
> +       return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
> +}
> +
>  /**
>   * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
>   * @gpuvm__: the &drm_gpuvm
> @@ -290,6 +317,12 @@ drm_gpuvm_root_object_alloc(struct drm_device
> *drm);
>   */
>  #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
>  
> +#define drm_gpuvm_resv_held(gpuvm__) \
> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
> +
> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
> +
>  #define drm_gpuvm_resv_held(gpuvm__) \
>         dma_resv_held(drm_gpuvm_resv(gpuvm__))
>  
> @@ -374,6 +407,117 @@ __drm_gpuva_next(struct drm_gpuva *va)
>  #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
>         list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list,
> rb.entry)
>  
> +/**
> + * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
> + * &drm_gem_object combination
> + *
> + * This structure is an abstraction representing a &drm_gpuvm and
> + * &drm_gem_object combination. It serves as an indirection to
> accelerate
> + * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
> + * &drm_gem_object.
> + *
> + * Furthermore it is used cache evicted GEM objects for a certain
> GPU-VM to
> + * accelerate validation.
> + *
> + * Typically, drivers want to create an instance of a struct
> drm_gpuvm_bo once
> + * a GEM object is mapped first in a GPU-VM and release the instance
> once the
> + * last mapping of the GEM object in this GPU-VM is unmapped.
> + */
> +struct drm_gpuvm_bo {
> +
> +       /**
> +        * @vm: The &drm_gpuvm the @obj is mapped in.
Not refcounted. @vm may potentially be freed under us in the
!RESV_PROTECTED case.

> +        */
> +       struct drm_gpuvm *vm;
> +
> +       /**
> +        * @obj: The &drm_gem_object being mapped in @vm.

Refcounted pointer.

> +        */
> +       struct drm_gem_object *obj;
> +
> +       /**
> +        * @kref: The reference count for this &drm_gpuvm_bo.
> +        */
> +       struct kref kref;
> +
> +       /**
> +        * @list: Structure containing all &list_heads.
> +        */
> +       struct {
> +               /**
> +                * @gpuva: The list of linked &drm_gpuvas.
> +                */

Here we should also document how we ensure gpvuas are kept alive.
(which I presume is the lock protecting the gem object's vm_bo list
must be held all the time any gpuva obtained from this list is being
accessed).

> +               struct list_head gpuva;
> +
> +               /**
> +                * @entry: Structure containing all &list_heads
> serving as
> +                * entry.
> +                */
> +               struct {
> +                       /**
> +                        * @gem: List entry to attach to the
> &drm_gem_objects
> +                        * gpuva list.
> +                        */
> +                       struct list_head gem;
> +               } entry;
> +       } list;
> +};
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj);
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
> +
> +/**
> + * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
> + * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
> + *
> + * This function acquires an additional reference to @vm_bo. It is
> illegal to
> + * call this without already holding a reference. No locks required.
> + */
> +static inline struct drm_gpuvm_bo *
> +drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
> +{
> +       kref_get(&vm_bo->kref);
> +       return vm_bo;
> +}
> +
> +void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
> +
> +struct drm_gpuvm_bo *
> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> +                 struct drm_gem_object *obj);
> +
> +/**
> + * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of
> &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo.

Must hold the ... lock.


> + */
> +#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
> +       list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
> +
> +/**
> + * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a
> list of
> + * &drm_gpuva
> + * @va__: &drm_gpuva structure to assign to in each iteration step
> + * @next__: &next &drm_gpuva to store the next step
> + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> with
> + *
> + * This iterator walks over all &drm_gpuva structures associated
> with the
> + * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(),
> hence
> + * it is save against removal of elements.
> + */
> +#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
> +       list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva,
> gem.entry)

Same here.

> +
>  /**
>   * enum drm_gpuva_op_type - GPU VA operation type
>   *
> @@ -643,8 +787,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> *gpuvm,
>                                  u64 addr, u64 range);
>  
>  struct drm_gpuva_ops *
> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> -                              struct drm_gem_object *obj);
> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
>  
>  void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
>                         struct drm_gpuva_ops *ops);
> @@ -688,6 +831,30 @@ struct drm_gpuvm_ops {
>          */
>         void (*op_free)(struct drm_gpuva_op *op);
>  
> +       /**
> +        * @vm_bo_alloc: called when the &drm_gpuvm allocates
> +        * a struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * allocate memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
> +
> +       /**
> +        * @vm_bo_free: called when the &drm_gpuvm frees a
> +        * struct drm_gpuvm_bo
> +        *
> +        * Some drivers may want to embed struct drm_gpuvm_bo into
> driver
> +        * specific structures. By implementing this callback drivers
> can
> +        * free the previously allocated memory accordingly.
> +        *
> +        * This callback is optional.
> +        */
> +       void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
> +
>         /**
>          * @sm_step_map: called from &drm_gpuvm_sm_map to finally
> insert the
>          * mapping once all previous steps were completed

Thanks,
Thomas

2023-10-13 12:33:43

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 3/6] drm/gpuvm: add an abstraction for a VM / BO combination

On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
> Add an abstraction layer between the drm_gpuva mappings of a
> particular
> drm_gem_object and this GEM object itself. The abstraction represents
> a
> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> holds
> a list of drm_gpuvm_bo structures (the structure representing this
> abstraction), while each drm_gpuvm_bo contains list of mappings of
> this
> GEM object.
>
> This has multiple advantages:
>
> 1) We can use the drm_gpuvm_bo structure to attach it to various
> lists
>    of the drm_gpuvm. This is useful for tracking external and evicted
>    objects per VM, which is introduced in subsequent patches.
>
> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>    drm_gpuvm becomes much cheaper.
>
> 3) Drivers can derive and extend the structure to easily represent
>    driver specific states of a BO for a certain GPUVM.
>
> The idea of this abstraction was taken from amdgpu, hence the credit
> for
> this idea goes to the developers of amdgpu.
>
> Cc: Christian König <[email protected]>
> Signed-off-by: Danilo Krummrich <[email protected]>
> ---
>  drivers/gpu/drm/drm_gpuvm.c            | 332 +++++++++++++++++++++--
> --
>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  64 +++--
>  include/drm/drm_gem.h                  |  32 +--
>  include/drm/drm_gpuvm.h                | 177 ++++++++++++-
>  4 files changed, 521 insertions(+), 84 deletions(-)

Forgot to mention, there are a couple of checkpatch.pl --strict issues
with this patch that might need looking at.

Thanks,
Thomas

2023-10-13 13:00:34

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 1/6] drm/gpuvm: add common dma-resv per struct drm_gpuvm

On Fri, 2023-10-13 at 13:51 +0200, Danilo Krummrich wrote:
> On 10/13/23 13:38, Thomas Hellström wrote:
> > On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
> > > Provide a common dma-resv for GEM objects not being used outside
> > > of
> > > this
> > > GPU-VM. This is used in a subsequent patch to generalize dma-
> > > resv,
> > > external and evicted object handling and GEM validation.
> > >
> > > Signed-off-by: Danilo Krummrich <[email protected]>
> > > ---
> > >   drivers/gpu/drm/drm_gpuvm.c            | 56
> > > +++++++++++++++++++++++++-
> > >   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 +++++-
> > >   include/drm/drm_gpuvm.h                | 35 +++++++++++++++-
> > >   3 files changed, 99 insertions(+), 5 deletions(-)
> > >
> > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > b/drivers/gpu/drm/drm_gpuvm.c
> > > index 02ecb45a2544..ebda9d594165 100644
> > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > @@ -61,6 +61,15 @@
> > >    * contained within struct drm_gpuva already. Hence, for
> > > inserting
> > > &drm_gpuva
> > >    * entries from within dma-fence signalling critical sections
> > > it is
> > > enough to
> > >    * pre-allocate the &drm_gpuva structures.
> > > + *
> > > + * &drm_gem_objects which are private to a single VM can share a
> > > common
> > > + * &dma_resv in order to improve locking efficiency (e.g. with
> > > &drm_exec).
> > > + * For this purpose drivers must pass a &drm_gem_object to
> > > drm_gpuvm_init(), in
> > > + * the following called 'root object', which serves as the
> > > container
> >
> > Nit: Perhaps resv object altough it might typically be the root
> > page-
> > table object, that doesn't have any meaning to drm_gpuvm, which
> > uses it
> > solely as a container for the resv?
>
> With "root" I didn't want to refer to the object representing the
> root
> page-table object, but being *the* object every other (internal)
> object
> needs to keep a reference to.

OK, yes but the reason they need a reference is because of the shared
resv, so IMO resv_object is a good fit. (I later noticed there's even
the function name drm_gpuvm_resv_obj()). And it will probably get
confused with the driver's "root" page table object, but up to you.

> Maybe I should be more explicit here and say
> that drivers need to make sure every internal object requires a
> reference
> to take a reference to this root object.
>
> >
> > > of the
> > > + * GPUVM's shared &dma_resv. This root object can be a driver
> > > specific
> > > + * &drm_gem_object, such as the &drm_gem_object containing the
> > > root
> > > page table,
> > > + * but it can also be a 'dummy' object, which can be allocated
> > > with
> > > + * drm_gpuvm_root_object_alloc().
> > >    */
> > >  
> > >   /**
> > > @@ -652,9 +661,47 @@ drm_gpuvm_range_valid(struct drm_gpuvm
> > > *gpuvm,
> > >                 !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
> > >   }
> > >  
> > > +static void
> > > +drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
> > > +{
> > > +       drm_gem_object_release(obj);
> > > +       kfree(obj);
> > > +}
> > > +
> > > +static const struct drm_gem_object_funcs drm_gpuvm_object_funcs
> > > = {
> > > +       .free = drm_gpuvm_gem_object_free,
> > > +};
> > > +
> > > +/**
> > > + * drm_gpuvm_root_object_alloc() - allocate a dummy
> > > &drm_gem_object
> > > + * @drm: the drivers &drm_device
> > > + *
> > > + * Allocates a dummy &drm_gem_object which can be passed to
> > > drm_gpuvm_init() in
> > > + * order to serve as root GEM object providing the &drm_resv
> > > shared
> > > across
> > > + * &drm_gem_objects local to a single GPUVM.
> > > + *
> > > + * Returns: the &drm_gem_object on success, NULL on failure
> > > + */
> > > +struct drm_gem_object *
> > > +drm_gpuvm_root_object_alloc(struct drm_device *drm)
> > > +{
> > > +       struct drm_gem_object *obj;
> > > +
> > > +       obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> > > +       if (!obj)
> > > +               return NULL;
> > > +
> > > +       obj->funcs = &drm_gpuvm_object_funcs;
> > > +       drm_gem_private_object_init(drm, obj, 0);
> > > +
> > > +       return obj;
> > > +}
> > > +EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
> > > +
> > >   /**
> > >    * drm_gpuvm_init() - initialize a &drm_gpuvm
> > >    * @gpuvm: pointer to the &drm_gpuvm to initialize
> > > + * @r_obj: the root &drm_gem_object providing the GPUVM's common
> > > &dma_resv
> > >    * @name: the name of the GPU VA space
> > >    * @start_offset: the start offset of the GPU VA space
> > >    * @range: the size of the GPU VA space
> > > @@ -668,7 +715,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm
> > > *gpuvm,
> > >    * &name is expected to be managed by the surrounding driver
> > > structures.
> > >    */
> > >   void
> > > -drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> > > +drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
> > > *r_obj,
> > >                 const char *name,
> > >                 u64 start_offset, u64 range,
> > >                 u64 reserve_offset, u64 reserve_range,
> > > @@ -683,6 +730,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> > >  
> > >          gpuvm->name = name ? name : "unknown";
> > >          gpuvm->ops = ops;
> > > +       gpuvm->r_obj = r_obj;
> > > +
> > > +       drm_gem_object_get(r_obj);
> > >  
> > >          memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct
> > > drm_gpuva));
> > >  
> > > @@ -713,7 +763,9 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > >                  __drm_gpuva_remove(&gpuvm->kernel_alloc_node);
> > >  
> > >          WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
> > > -            "GPUVA tree is not empty, potentially leaking
> > > memory.");
> > > +            "GPUVA tree is not empty, potentially leaking
> > > memory.\n");
> >
> > Should we cache the drm device in struct drm_gpuvm and use
> > drm_warn()
> > here instead of WARN?
>
> I'd guess the additional backtrace of WARN() isn't overly useful in
> this
> case. However, it might be a bit more obvious in dmesg due to its
> verboseness. Not a strong opinion on that, though.

My bad. I meant drm_WARN(). In a multi-gpu environment it's nice to
have the extra device info.

/Thomas


>
> >
> > > +
> > > +       drm_gem_object_put(gpuvm->r_obj);
> > >   }
> > >   EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > >  
> > > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > index 5cf892c50f43..4dea847ef989 100644
> > > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > > @@ -1808,8 +1808,9 @@ int
> > >   nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli
> > > *cli,
> > >                    u64 kernel_managed_addr, u64
> > > kernel_managed_size)
> > >   {
> > > -       int ret;
> > > +       struct drm_gem_object *r_obj;
> > >          u64 kernel_managed_end = kernel_managed_addr +
> > > kernel_managed_size;
> > > +       int ret;
> > >  
> > >          mutex_init(&uvmm->mutex);
> > >          dma_resv_init(&uvmm->resv);
> > > @@ -1833,14 +1834,22 @@ nouveau_uvmm_init(struct nouveau_uvmm
> > > *uvmm,
> > > struct nouveau_cli *cli,
> > >                  goto out_unlock;
> > >          }
> > >  
> > > +       r_obj = drm_gpuvm_root_object_alloc(cli->drm->dev);
> > > +       if (!r_obj) {
> > > +               ret = -ENOMEM;
> > > +               goto out_unlock;
> > > +       }
> > > +
> > >          uvmm->kernel_managed_addr = kernel_managed_addr;
> > >          uvmm->kernel_managed_size = kernel_managed_size;
> > >  
> > > -       drm_gpuvm_init(&uvmm->base, cli->name,
> > > +       drm_gpuvm_init(&uvmm->base, r_obj, cli->name,
> > >                         NOUVEAU_VA_SPACE_START,
> > >                         NOUVEAU_VA_SPACE_END,
> > >                         kernel_managed_addr, kernel_managed_size,
> > >                         NULL);
> > > +       /* GPUVM takes care from here on. */
> > > +       drm_gem_object_put(r_obj);
> > >  
> > >          ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
> > >                              cli->vmm.vmm.object.oclass, RAW,
> > > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > > index c7ed6bf441d4..0aec14d8b259 100644
> > > --- a/include/drm/drm_gpuvm.h
> > > +++ b/include/drm/drm_gpuvm.h
> > > @@ -238,9 +238,15 @@ struct drm_gpuvm {
> > >           * @ops: &drm_gpuvm_ops providing the split/merge steps
> > > to
> > > drivers
> > >           */
> > >          const struct drm_gpuvm_ops *ops;
> > > +
> > > +       /**
> > > +        * @r_obj: Root GEM object; representing the GPUVM's
> > > common
> > > &dma_resv.
> > > +        */
> > > +       struct drm_gem_object *r_obj;
> > >   };
> > >  
> > > -void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
> > > +void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct
> > > drm_gem_object
> > > *r_obj,
> > > +                   const char *name,
> > >                      u64 start_offset, u64 range,
> > >                      u64 reserve_offset, u64 reserve_range,
> > >                      const struct drm_gpuvm_ops *ops);
> > > @@ -248,6 +254,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm
> > > *gpuvm);
> > >  
> > >   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64
> > > addr, u64
> > > range);
> > >  
> > > +struct drm_gem_object *
> > > +drm_gpuvm_root_object_alloc(struct drm_device *drm);
> > > +
> > > +/**
> > > + * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
> > > + * @gpuvm__: the &drm_gpuvm
> > > + *
> > > + * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
> > > + */
> > > +#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
> > > +
> > > +/**
> > > + * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding
> > > the
> > > &drm_gpuvm's
> > > + * &dma_resv
> > > + * @gpuvm__: the &drm_gpuvm
> > > + *
> > > + * Returns: a pointer to the &drm_gem_object holding the
> > > &drm_gpuvm's shared
> > > + * &dma_resv
> > > + */
> > > +#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
> > > +
> > > +#define drm_gpuvm_resv_held(gpuvm__) \
> > > +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
> > > +
> > > +#define drm_gpuvm_resv_assert_held(gpuvm__) \
> > > +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
> > > +
> > >   static inline struct drm_gpuva *
> > >   __drm_gpuva_next(struct drm_gpuva *va)
> > >   {
> >
> > Reviewed-by: Thomas Hellström <[email protected]>
> >
> >
>

2023-10-13 13:03:28

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects

On Fri, 2023-10-13 at 14:04 +0200, Danilo Krummrich wrote:
> On 10/10/23 08:26, Thomas Hellström wrote:
> >
> > On 10/9/23 16:45, Danilo Krummrich wrote:
> > > On 10/9/23 15:36, Thomas Hellström wrote:
> > > >
> > > > On 10/9/23 01:32, Danilo Krummrich wrote:
> > > > > Currently the DRM GPUVM offers common infrastructure to track
> > > > > GPU VA
> > > > > allocations and mappings, generically connect GPU VA mappings
> > > > > to their
> > > > > backing buffers and perform more complex mapping operations
> > > > > on the GPU VA
> > > > > space.
> > > > >
> > > > > However, there are more design patterns commonly used by
> > > > > drivers, which
> > > > > can potentially be generalized in order to make the DRM GPUVM
> > > > > represent
> > > > > a basis for GPU-VM implementations. In this context, this
> > > > > patch aims
> > > > > at generalizing the following elements.
> > > > >
> > > > > 1) Provide a common dma-resv for GEM objects not being used
> > > > > outside of
> > > > >     this GPU-VM.
> > > > >
> > > > > 2) Provide tracking of external GEM objects (GEM objects
> > > > > which are
> > > > >     shared with other GPU-VMs).
> > > > >
> > > > > 3) Provide functions to efficiently lock all GEM objects dma-
> > > > > resv the
> > > > >     GPU-VM contains mappings of.
> > > > >
> > > > > 4) Provide tracking of evicted GEM objects the GPU-VM
> > > > > contains mappings
> > > > >     of, such that validation of evicted GEM objects is
> > > > > accelerated.
> > > > >
> > > > > 5) Provide some convinience functions for common patterns.
> > > > >
> > > > > Big thanks to Boris Brezillon for his help to figure out
> > > > > locking for
> > > > > drivers updating the GPU VA space within the fence signalling
> > > > > path.
> > > > >
> > > > > Suggested-by: Matthew Brost <[email protected]>
> > > > > Signed-off-by: Danilo Krummrich <[email protected]>
> > > > > ---
> > > > >   drivers/gpu/drm/drm_gpuvm.c | 646
> > > > > ++++++++++++++++++++++++++++++++++++
> > > > >   include/drm/drm_gpuvm.h     | 246 ++++++++++++++
> > > > >   2 files changed, 892 insertions(+)
> > > > >
> > > > > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > > > > b/drivers/gpu/drm/drm_gpuvm.c
> > > > > index 28282283ddaf..6977bd30eca5 100644
> > > > > --- a/drivers/gpu/drm/drm_gpuvm.c
> > > > > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > > > > @@ -82,6 +82,21 @@
> > > > >    * &drm_gem_object list of &drm_gpuvm_bos for an existing
> > > > > instance of this
> > > > >    * particular combination. If not existent a new instance
> > > > > is created and linked
> > > > >    * to the &drm_gem_object.
> > > > > + *
> > > > > + * &drm_gpuvm_bo structures, since unique for a given
> > > > > &drm_gpuvm, are also used
> > > > > + * as entry for the &drm_gpuvm's lists of external and
> > > > > evicted objects. Those
> > > > > + * list are maintained in order to accelerate locking of
> > > > > dma-resv locks and
> > > > > + * validation of evicted objects bound in a &drm_gpuvm. For
> > > > > instance, all
> > > > > + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be
> > > > > locked by calling
> > > > > + * drm_gpuvm_exec_lock(). Once locked drivers can call
> > > > > drm_gpuvm_validate() in
> > > > > + * order to validate all evicted &drm_gem_objects. It is
> > > > > also possible to lock
> > > > > + * additional &drm_gem_objects by providing the
> > > > > corresponding parameters to
> > > > > + * drm_gpuvm_exec_lock() as well as open code the &drm_exec
> > > > > loop while making
> > > > > + * use of helper functions such as drm_gpuvm_prepare_range()
> > > > > or
> > > > > + * drm_gpuvm_prepare_objects().
> > > > > + *
> > > > > + * Every bound &drm_gem_object is treated as external object
> > > > > when its &dma_resv
> > > > > + * structure is different than the &drm_gpuvm's common
> > > > > &dma_resv structure.
> > > > >    */
> > > > >   /**
> > > > > @@ -429,6 +444,20 @@
> > > > >    * Subsequent calls to drm_gpuvm_bo_obtain() for the same
> > > > > &drm_gpuvm and
> > > > >    * &drm_gem_object must be able to observe previous
> > > > > creations and destructions
> > > > >    * of &drm_gpuvm_bos in order to keep instances unique.
> > > > > + *
> > > > > + * The &drm_gpuvm's lists for keeping track of external and
> > > > > evicted objects are
> > > > > + * protected against concurrent insertion / removal and
> > > > > iteration internally.
> > > > > + *
> > > > > + * However, drivers still need ensure to protect concurrent
> > > > > calls to functions
> > > > > + * iterating those lists, namely drm_gpuvm_prepare_objects()
> > > > > and
> > > > > + * drm_gpuvm_validate().
> > > > > + *
> > > > > + * Alternatively, drivers can set the
> > > > > &DRM_GPUVM_RESV_PROTECTED flag to indicate
> > > > > + * that the corresponding &dma_resv locks are held in order
> > > > > to protect the
> > > > > + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal
> > > > > locking is disabled and
> > > > > + * the corresponding lockdep checks are enabled. This is an
> > > > > optimization for
> > > > > + * drivers which are capable of taking the corresponding
> > > > > &dma_resv locks and
> > > > > + * hence do not require internal locking.
> > > > >    */
> > > > >   /**
> > > > > @@ -641,6 +670,195 @@
> > > > >    *    }
> > > > >    */
> > > > > +/**
> > > > > + * get_next_vm_bo_from_list() - get the next vm_bo element
> > > > > + * @__gpuvm: The GPU VM
> > > > > + * @__list_name: The name of the list we're iterating on
> > > > > + * @__local_list: A pointer to the local list used to store
> > > > > already iterated items
> > > > > + * @__prev_vm_bo: The previous element we got from
> > > > > drm_gpuvm_get_next_cached_vm_bo()
> > > > > + *
> > > > > + * This helper is here to provide lockless list iteration.
> > > > > Lockless as in, the
> > > > > + * iterator releases the lock immediately after picking the
> > > > > first element from
> > > > > + * the list, so list insertion deletion can happen
> > > > > concurrently.
> > > > > + *
> > > > > + * Elements popped from the original list are kept in a
> > > > > local list, so removal
> > > > > + * and is_empty checks can still happen while we're
> > > > > iterating the list.
> > > > > + */
> > > > > +#define get_next_vm_bo_from_list(__gpuvm, __list_name,
> > > > > __local_list, __prev_vm_bo)    \
> > > > > +    ({                                        \
> > > > > +        struct drm_gpuvm_bo *__vm_bo =
> > > > > NULL;                    \
> > > > > +                                            \
> > > > > + drm_gpuvm_bo_put(__prev_vm_bo);                        \
> > > > > +                                            \
> > > > > + spin_lock(&(__gpuvm)->__list_name.lock); \
> > > > > +        if (!(__gpuvm)-
> > > > > >__list_name.local_list)                    \
> > > > > +            (__gpuvm)->__list_name.local_list =
> > > > > __local_list;        \
> > > > > +        else                                    \
> > > > > +            WARN_ON((__gpuvm)->__list_name.local_list !=
> > > > > __local_list);    \
> > > > > +                                            \
> > > > > +        while (!list_empty(&(__gpuvm)->__list_name.list))
> > > > > {            \
> > > > > +            __vm_bo = list_first_entry(&(__gpuvm)-
> > > > > >__list_name.list,    \
> > > > > +                           struct drm_gpuvm_bo,            \
> > > > > +                           list.entry.__list_name);        \
> > > > > +            if (kref_get_unless_zero(&__vm_bo->kref))
> > > > > {            \
> > > > > + list_move_tail(&(__vm_bo)->list.entry.__list_name,    \
> > > > > +                           __local_list);                \
> > > > > +                break;                            \
> > > > > +            } else {                            \
> > > > > + list_del_init(&(__vm_bo)->list.entry.__list_name);    \
> > > > > +                __vm_bo = NULL;                        \
> > > > > +            }                                \
> > > > > +        }                                    \
> > > > > + spin_unlock(&(__gpuvm)->__list_name.lock); \
> > > > > +                                            \
> > > > > +        __vm_bo;                                \
> > > > > +    })
> > > > > +
> > > > > +/**
> > > > > + * for_each_vm_bo_in_list() - internal vm_bo list iterator
> > > > > + *
> > > > > + * This helper is here to provide lockless list iteration.
> > > > > Lockless as in, the
> > > > > + * iterator releases the lock immediately after picking the
> > > > > first element from the
> > > > > + * list, hence list insertion and deletion can happen
> > > > > concurrently.
> > > > > + *
> > > > > + * It is not allowed to re-assign the vm_bo pointer from
> > > > > inside this loop.
> > > > > + *
> > > > > + * Typical use:
> > > > > + *
> > > > > + *    struct drm_gpuvm_bo *vm_bo;
> > > > > + *    LIST_HEAD(my_local_list);
> > > > > + *
> > > > > + *    ret = 0;
> > > > > + *    for_each_vm_bo_in_list(gpuvm, <list_name>,
> > > > > &my_local_list, vm_bo) {
> > > > > + *        ret = do_something_with_vm_bo(..., vm_bo);
> > > > > + *        if (ret)
> > > > > + *            break;
> > > > > + *    }
> > > > > + *    drm_gpuvm_bo_put(vm_bo);
> > > > > + *    restore_vm_bo_list(gpuvm, <list_name>,
> > > > > &my_local_list);
> > > > > + *
> > > > > + *
> > > > > + * Only used for internal list iterations, not meant to be
> > > > > exposed to the outside
> > > > > + * world.
> > > > > + */
> > > > > +#define for_each_vm_bo_in_list(__gpuvm, __list_name,
> > > > > __local_list, __vm_bo)    \
> > > > > +    for (__vm_bo = get_next_vm_bo_from_list(__gpuvm,
> > > > > __list_name,        \
> > > > > +                        __local_list, NULL);        \
> > > > > +         __vm_bo;                                \
> > > > > +         __vm_bo = get_next_vm_bo_from_list(__gpuvm,
> > > > > __list_name,        \
> > > > > +                        __local_list, __vm_bo))
> > > > > +
> > > > > +static void
> > > > > +__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t
> > > > > *lock,
> > > > > +             struct list_head *list, struct list_head
> > > > > **local_list)
> > > > > +{
> > > > > +    /* Merge back the two lists, moving local list elements
> > > > > to the
> > > > > +     * head to preserve previous ordering, in case it
> > > > > matters.
> > > > > +     */
> > > > > +    spin_lock(lock);
> > > > > +    if (*local_list) {
> > > > > +        list_splice(*local_list, list);
> > > > > +        *local_list = NULL;
> > > > > +    }
> > > > > +    spin_unlock(lock);
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * restore_vm_bo_list() - move vm_bo elements back to their
> > > > > original list
> > > > > + * @__gpuvm: The GPU VM
> > > > > + * @__list_name: The name of the list we're iterating on
> > > > > + *
> > > > > + * When we're done iterating a vm_bo list, we should call
> > > > > restore_vm_bo_list()
> > > > > + * to restore the original state and let new iterations take
> > > > > place.
> > > > > + */
> > > > > +#define restore_vm_bo_list(__gpuvm, __list_name)           
> > > > > \
> > > > > +    __restore_vm_bo_list((__gpuvm), &(__gpuvm)-
> > > > > >__list_name.lock,    \
> > > > > +                 &(__gpuvm)->__list_name.list, \
> > > > > +                 &(__gpuvm)->__list_name.local_list)
> > > > > +
> > > > > +static void
> > > > > +cond_spin_lock(spinlock_t *lock, bool cond)
> > > > > +{
> > > > > +    if (cond)
> > > > > +        spin_lock(lock);
> > > > > +}
> > > > > +
> > > > > +static void
> > > > > +cond_spin_unlock(spinlock_t *lock, bool cond)
> > > > > +{
> > > > > +    if (cond)
> > > > > +        spin_unlock(lock);
> > > > > +}
> > > > > +
> > > > > +static void
> > > > > +__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t
> > > > > *lock,
> > > > > +            struct list_head *entry, struct list_head *list)
> > > > > +{
> > > > > +    cond_spin_lock(lock, !!lock);
> > > > > +    if (list_empty(entry))
> > > > > +        list_add_tail(entry, list);
> > > > > +    cond_spin_unlock(lock, !!lock);
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_bo_list_add() - insert a vm_bo into the given
> > > > > list
> > > > > + * @__vm_bo: the &drm_gpuvm_bo
> > > > > + * @__list_name: the name of the list to insert into
> > > > > + * @__lock: whether to lock with the internal spinlock
> > > > > + *
> > > > > + * Inserts the given @__vm_bo into the list specified by
> > > > > @__list_name.
> > > > > + */
> > > > > +#define drm_gpuvm_bo_list_add(__vm_bo, __list_name,
> > > > > __lock)            \
> > > > > + __drm_gpuvm_bo_list_add((__vm_bo)->vm,                    \
> > > > > +                __lock ? &(__vm_bo)->vm->__list_name.lock
> > > > > :    \
> > > > > +                     NULL,                    \
> > > > > + &(__vm_bo)->list.entry.__list_name,        \
> > > > > +                &(__vm_bo)->vm->__list_name.list)
> > > > > +
> > > > > +static void
> > > > > +__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t
> > > > > *lock,
> > > > > +            struct list_head *entry, bool init)
> > > > > +{
> > > > > +    cond_spin_lock(lock, !!lock);
> > > > > +    if (init) {
> > > > > +        if (!list_empty(entry))
> > > > > +            list_del_init(entry);
> > > > > +    } else {
> > > > > +        list_del(entry);
> > > > > +    }
> > > > > +    cond_spin_unlock(lock, !!lock);
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the
> > > > > given list
> > > > > + * @__vm_bo: the &drm_gpuvm_bo
> > > > > + * @__list_name: the name of the list to insert into
> > > > > + * @__lock: whether to lock with the internal spinlock
> > > > > + *
> > > > > + * Removes the given @__vm_bo from the list specified by
> > > > > @__list_name.
> > > > > + */
> > > > > +#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name,
> > > > > __lock)        \
> > > > > + __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
> > > > > +                __lock ? &(__vm_bo)->vm->__list_name.lock
> > > > > :    \
> > > > > +                     NULL,                    \
> > > > > + &(__vm_bo)->list.entry.__list_name,        \
> > > > > +                true)
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_bo_list_del() - remove a vm_bo from the given
> > > > > list
> > > > > + * @__vm_bo: the &drm_gpuvm_bo
> > > > > + * @__list_name: the name of the list to insert into
> > > > > + * @__lock: whether to lock with the internal spinlock
> > > > > + *
> > > > > + * Removes the given @__vm_bo from the list specified by
> > > > > @__list_name.
> > > > > + */
> > > > > +#define drm_gpuvm_bo_list_del(__vm_bo, __list_name,
> > > > > __lock)            \
> > > > > + __drm_gpuvm_bo_list_del((__vm_bo)->vm,                    \
> > > > > +                __lock ? &(__vm_bo)->vm->__list_name.lock
> > > > > :    \
> > > > > +                     NULL,                    \
> > > > > + &(__vm_bo)->list.entry.__list_name,        \
> > > > > +                false)
> > > > > +
> > > > >   #define to_drm_gpuva(__node)    container_of((__node),
> > > > > struct drm_gpuva, rb.node)
> > > > >   #define GPUVA_START(node) ((node)->va.addr)
> > > > > @@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
> > > > > struct drm_gem_object *r_obj,
> > > > >       gpuvm->rb.tree = RB_ROOT_CACHED;
> > > > >       INIT_LIST_HEAD(&gpuvm->rb.list);
> > > > > +    INIT_LIST_HEAD(&gpuvm->extobj.list);
> > > > > +    spin_lock_init(&gpuvm->extobj.lock);
> > > > > +
> > > > > +    INIT_LIST_HEAD(&gpuvm->evict.list);
> > > > > +    spin_lock_init(&gpuvm->evict.lock);
> > > > > +
> > > > >       drm_gpuvm_check_overflow(start_offset, range);
> > > > >       gpuvm->mm_start = start_offset;
> > > > >       gpuvm->mm_range = range;
> > > > > @@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm
> > > > > *gpuvm)
> > > > >       WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
> > > > >            "GPUVA tree is not empty, potentially leaking
> > > > > memory.\n");
> > > > > +    WARN(!list_empty(&gpuvm->extobj.list), "Extobj list
> > > > > should be empty.\n");
> > > > > +    WARN(!list_empty(&gpuvm->evict.list), "Evict list should
> > > > > be empty.\n");
> > > > > +
> > > > >       drm_gem_object_put(gpuvm->r_obj);
> > > > >   }
> > > > >   EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > > > > +static int
> > > > > +__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
> > > > > +                struct drm_exec *exec,
> > > > > +                unsigned int num_fences)
> > > > > +{
> > > > > +    struct drm_gpuvm_bo *vm_bo;
> > > > > +    LIST_HEAD(extobjs);
> > > > > +    int ret = 0;
> > > > > +
> > > > > +    for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
> > > > > +        ret = drm_exec_prepare_obj(exec, vm_bo->obj,
> > > > > num_fences);
> > > > > +        if (ret)
> > > > > +            break;
> > > > > +    }
> > > > > +    /* Drop ref in case we break out of the loop. */
> > > > > +    drm_gpuvm_bo_put(vm_bo);
> > > > > +    restore_vm_bo_list(gpuvm, extobj);
> > > > > +
> > > > > +    return ret;
> > > > > +}
> > > > > +
> > > > > +static int
> > > > > +drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
> > > > > +                 struct drm_exec *exec,
> > > > > +                 unsigned int num_fences)
> > > > > +{
> > > > > +    struct drm_gpuvm_bo *vm_bo;
> > > > > +    int ret = 0;
> > > > > +
> > > > > +    drm_gpuvm_resv_assert_held(gpuvm);
> > > > > +    list_for_each_entry(vm_bo, &gpuvm->extobj.list,
> > > > > list.entry.extobj) {
> > > > > +        ret = drm_exec_prepare_obj(exec, vm_bo->obj,
> > > > > num_fences);
> > > > > +        if (ret)
> > > > > +            break;
> > > > > +
> > > > > +        if (vm_bo->evicted)
> > > > > +            drm_gpuvm_bo_list_add(vm_bo, evict, false);
> > > >
> > > > Clear vm_bo->evicted here?
> > >
> > > Why? It's still evicted, hence why not indicate it? It could be
> > > useful for a
> > > validate_range() use case.
> >
> > I guess that boils down to what vm_bo->evicted is supposed to mean.
> > I have been using it as "This bo needs to be put on the evicted
> > list", but if we instead mean "This bo was once evicted and might
> > need revalidation and needs rebinding to this VM" then it's OK not
> > to clear it, I guess. But note that another VM might have already
> > re-validated the gem BO, and also if the locking loop or validate
> > loop restarts due to -EINTR or -EDEADLK, then the
> > drm_gpuvm_bo_list_add() will be called multiple times, which is OK
> > but unnecessary. So I'd vote for "This bo needs to be put on the
> > eviced list".
>
> In case of a drm_exec loop restart, the additional
> drm_gpuvm_bo_list_add() is rather negligible. As mentioned, keeping
> drm_gpuvm_bo::evicted in an up to date state could be a useful
> addition to drivers. Besides that, I'd rather make this field safe to
> use by drivers than document that it's *not* safe to look up for
> drivers and should only be used with care internally.
>
> >
> > >
> > > >
> > > >
> > > > > +    }
> > > > > +
> > > > > +    return ret;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
> > > > > + * @gpuvm: the &drm_gpuvm
> > > > > + * @exec: the &drm_exec locking context
> > > > > + * @num_fences: the amount of &dma_fences to reserve
> > > > > + *
> > > > > + * Calls drm_exec_prepare_obj() for all &drm_gem_objects the
> > > > > given
> > > > > + * &drm_gpuvm contains mappings of.
> > > > > + *
> > > > > + * Using this function directly, it is the drivers
> > > > > responsibility to call
> > > > > + * drm_exec_init() and drm_exec_fini() accordingly.
> > > > > + *
> > > > > + * Note: This function is safe against concurrent insertion
> > > > > and removal of
> > > > > + * external objects, however it is not safe against
> > > > > concurrent usage itself.
> > > > > + *
> > > > > + * Drivers need to make sure to protect this case with
> > > > > either an outer VM lock
> > > > > + * or by calling drm_gpuvm_prepare_vm() before this function
> > > > > within the
> > > > > + * drm_exec_until_all_locked() loop, such that the GPUVM's
> > > > > dma-resv lock ensures
> > > > > + * mutual exclusion.
> > > > > + *
> > > > > + * Returns: 0 on success, negative error code on failure.
> > > > > + */
> > > > > +int
> > > > > +drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
> > > > > +              struct drm_exec *exec,
> > > > > +              unsigned int num_fences)
> > > > > +{
> > > > > +    if (drm_gpuvm_resv_protected(gpuvm))
> > > > > +        return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
> > > > > +                            num_fences);
> > > > > +    else
> > > > > +        return __drm_gpuvm_prepare_objects(gpuvm, exec,
> > > > > num_fences);
> > > > > +
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_prepare_range() - prepare all BOs mapped within
> > > > > a given range
> > > > > + * @gpuvm: the &drm_gpuvm
> > > > > + * @exec: the &drm_exec locking context
> > > > > + * @addr: the start address within the VA space
> > > > > + * @range: the range to iterate within the VA space
> > > > > + * @num_fences: the amount of &dma_fences to reserve
> > > > > + *
> > > > > + * Calls drm_exec_prepare_obj() for all &drm_gem_objects
> > > > > mapped between @addr
> > > > > + * and @addr + @range.
> > > > > + *
> > > > > + * Returns: 0 on success, negative error code on failure.
> > > > > + */
> > > > > +int
> > > > > +drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct
> > > > > drm_exec *exec,
> > > > > +            u64 addr, u64 range, unsigned int num_fences)
> > > > > +{
> > > > > +    struct drm_gpuva *va;
> > > > > +    u64 end = addr + range;
> > > > > +    int ret;
> > > > > +
> > > > > +    drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
> > > > > +        struct drm_gem_object *obj = va->gem.obj;
> > > > > +
> > > > > +        ret = drm_exec_prepare_obj(exec, obj, num_fences);
> > > > > +        if (ret)
> > > > > +            return ret;
> > > > > +    }
> > > > > +
> > > > > +    return 0;
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_exec_lock() - lock all dma-resv of all
> > > > > assoiciated BOs
> > > > > + * @vm_exec: the &drm_gpuvm_exec wrapper
> > > > > + * @num_fences: the amount of &dma_fences to reserve
> > > > > + * @interruptible: sleep interruptible if waiting
> > > > > + *
> > > > > + * Acquires all dma-resv locks of all &drm_gem_objects the
> > > > > given
> > > > > + * &drm_gpuvm contains mappings of.
> > > > > + *
> > > > > + * Addionally, when calling this function with struct
> > > > > drm_gpuvm_exec::extra
> > > > > + * being set the driver receives the given @fn callback to
> > > > > lock additional
> > > > > + * dma-resv in the context of the &drm_gpuvm_exec instance.
> > > > > Typically, drivers
> > > > > + * would call drm_exec_prepare_obj() from within this
> > > > > callback.
> > > > > + *
> > > > > + * Returns: 0 on success, negative error code on failure.
> > > > > + */
> > > > > +int
> > > > > +drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
> > > > > +            unsigned int num_fences,
> > > > > +            bool interruptible)
> > > > > +{
> > > > > +    struct drm_gpuvm *gpuvm = vm_exec->vm;
> > > > > +    struct drm_exec *exec = &vm_exec->exec;
> > > > > +    uint32_t flags;
> > > > > +    int ret;
> > > > > +
> > > > > +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0
> > > > > |
> > > > > +        DRM_EXEC_IGNORE_DUPLICATES;
> > > > > +
> > > > > +    drm_exec_init(exec, flags);
> > > > > +
> > > > > +    drm_exec_until_all_locked(exec) {
> > > > > +        ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
> > > > > +        drm_exec_retry_on_contention(exec);
> > > > > +        if (ret)
> > > > > +            goto err;
> > > > > +
> > > > > +        ret = drm_gpuvm_prepare_objects(gpuvm, exec,
> > > > > num_fences);
> > > > > +        drm_exec_retry_on_contention(exec);
> > > > > +        if (ret)
> > > > > +            goto err;
> > > > > +
> > > > > +        if (vm_exec->extra.fn) {
> > > > > +            ret = vm_exec->extra.fn(vm_exec, num_fences);
> > > > > +            drm_exec_retry_on_contention(exec);
> > > > > +            if (ret)
> > > > > +                goto err;
> > > > > +        }
> > > > > +    }
> > > > > +
> > > > > +    return 0;
> > > > > +
> > > > > +err:
> > > > > +    drm_exec_fini(exec);
> > > > > +    return ret;
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
> > > > > +
> > > > > +static int
> > > > > +fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int
> > > > > num_fences)
> > > > > +{
> > > > > +    struct {
> > > > > +        struct drm_gem_object **objs;
> > > > > +        unsigned int num_objs;
> > > > > +    } *args = vm_exec->extra.priv;
> > > > > +
> > > > > +    return drm_exec_prepare_array(&vm_exec->exec, args-
> > > > > >objs,
> > > > > +                      args->num_objs, num_fences);
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_exec_lock_array() - lock all dma-resv of all
> > > > > assoiciated BOs
> > > > > + * @vm_exec: the &drm_gpuvm_exec wrapper
> > > > > + * @objs: additional &drm_gem_objects to lock
> > > > > + * @num_objs: the number of additional &drm_gem_objects to
> > > > > lock
> > > > > + * @num_fences: the amount of &dma_fences to reserve
> > > > > + * @interruptible: sleep interruptible if waiting
> > > > > + *
> > > > > + * Acquires all dma-resv locks of all &drm_gem_objects the
> > > > > given &drm_gpuvm
> > > > > + * contains mappings of, plus the ones given through @objs.
> > > > > + *
> > > > > + * Returns: 0 on success, negative error code on failure.
> > > > > + */
> > > > > +int
> > > > > +drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
> > > > > +              struct drm_gem_object **objs,
> > > > > +              unsigned int num_objs,
> > > > > +              unsigned int num_fences,
> > > > > +              bool interruptible)
> > > > > +{
> > > > > +    struct {
> > > > > +        struct drm_gem_object **objs;
> > > > > +        unsigned int num_objs;
> > > > > +    } args;
> > > > > +
> > > > > +    args.objs = objs;
> > > > > +    args.num_objs = num_objs;
> > > > > +
> > > > > +    vm_exec->extra.fn = fn_lock_array;
> > > > > +    vm_exec->extra.priv = &args;
> > > > > +
> > > > > +    return drm_gpuvm_exec_lock(vm_exec, num_fences,
> > > > > interruptible);
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_exec_lock_range() - prepare all BOs mapped
> > > > > within a given range
> > > > > + * @vm_exec: the &drm_gpuvm_exec wrapper
> > > > > + * @addr: the start address within the VA space
> > > > > + * @range: the range to iterate within the VA space
> > > > > + * @num_fences: the amount of &dma_fences to reserve
> > > > > + * @interruptible: sleep interruptible if waiting
> > > > > + *
> > > > > + * Acquires all dma-resv locks of all &drm_gem_objects
> > > > > mapped between @addr and
> > > > > + * @addr + @range.
> > > > > + *
> > > > > + * Returns: 0 on success, negative error code on failure.
> > > > > + */
> > > > > +int
> > > > > +drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
> > > > > +              u64 addr, u64 range,
> > > > > +              unsigned int num_fences,
> > > > > +              bool interruptible)
> > > > > +{
> > > > > +    struct drm_gpuvm *gpuvm = vm_exec->vm;
> > > > > +    struct drm_exec *exec = &vm_exec->exec;
> > > > > +    uint32_t flags;
> > > > > +    int ret;
> > > > > +
> > > > > +    flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0
> > > > > |
> > > > > +        DRM_EXEC_IGNORE_DUPLICATES;
> > > > > +
> > > > > +    drm_exec_init(exec, flags);
> > > > > +
> > > > > +    drm_exec_until_all_locked(exec) {
> > > > > +        ret = drm_gpuvm_prepare_range(gpuvm, exec, addr,
> > > > > range,
> > > > > +                          num_fences);
> > > > > +        drm_exec_retry_on_contention(exec);
> > > > > +        if (ret)
> > > > > +            goto err;
> > > > > +    }
> > > > > +
> > > > > +    return ret;
> > > > > +
> > > > > +err:
> > > > > +    drm_exec_fini(exec);
> > > > > +    return ret;
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
> > > > > +
> > > > > +static int
> > > > > +__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct
> > > > > drm_exec *exec)
> > > > > +{
> > > > > +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
> > > > > +    struct drm_gpuvm_bo *vm_bo;
> > > > > +    LIST_HEAD(evict);
> > > > > +    int ret = 0;
> > > > > +
> > > > > +    for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
> > > > > +        ret = ops->vm_bo_validate(vm_bo, exec);
> > > > > +        if (ret)
> > > > > +            break;
> > > > > +    }
> > > > > +    /* Drop ref in case we break out of the loop. */
> > > > > +    drm_gpuvm_bo_put(vm_bo);
> > > > > +    restore_vm_bo_list(gpuvm, evict);
> > > > > +
> > > > > +    return ret;
> > > > > +}
> > > > > +
> > > > > +static int
> > > > > +drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct
> > > > > drm_exec *exec)
> > > > > +{
> > > > > +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
> > > > > +    struct drm_gpuvm_bo *vm_bo, *next;
> > > > > +    int ret = 0;
> > > > > +
> > > > > +    drm_gpuvm_resv_assert_held(gpuvm);
> > > > > +
> > > > > +    /* Iterate list safely, drivers typically remove the
> > > > > current entry from
> > > > > +     * their drm_gpuvm_ops::vm_bo_validate callback. Drivers
> > > > > might also
> > > > > +     * re-add the entry on failure; this is safe since on
> > > > > failure we break
> > > > > +     * out of the loop.
> > > > > +     */
> > > > > +    list_for_each_entry_safe(vm_bo, next, &gpuvm-
> > > > > >evict.list,
> > > > > +                 list.entry.evict) {
> > > > > +        ret = ops->vm_bo_validate(vm_bo, exec);
> > > > > +        if (ret)
> > > > > +            break;
> > > > > +    }
> > > > > +
> > > > > +    return ret;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_validate() - validate all BOs marked as evicted
> > > > > + * @gpuvm: the &drm_gpuvm to validate evicted BOs
> > > > > + * @exec: the &drm_exec instance used for locking the GPUVM
> > > > > + *
> > > > > + * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all
> > > > > evicted buffer
> > > > > + * objects being mapped in the given &drm_gpuvm.
> > > > > + *
> > > > > + * Returns: 0 on success, negative error code on failure.
> > > > > + */
> > > > > +int
> > > > > +drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec
> > > > > *exec)
> > > > > +{
> > > > > +    const struct drm_gpuvm_ops *ops = gpuvm->ops;
> > > > > +
> > > > > +    if (unlikely(!ops || !ops->vm_bo_validate))
> > > > > +        return -ENOTSUPP;
> > > > > +
> > > > > +    if (drm_gpuvm_resv_protected(gpuvm))
> > > > > +        return drm_gpuvm_validate_locked(gpuvm, exec);
> > > > > +    else
> > > > > +        return __drm_gpuvm_validate(gpuvm, exec);
> > > > > +
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_resv_add_fence - add fence to private and all
> > > > > extobj
> > > > > + * dma-resv
> > > > > + * @gpuvm: the &drm_gpuvm to add a fence to
> > > > > + * @exec: the &drm_exec locking context
> > > > > + * @fence: fence to add
> > > > > + * @private_usage: private dma-resv usage
> > > > > + * @extobj_usage: extobj dma-resv usage
> > > > > + */
> > > > > +void
> > > > > +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
> > > > > +             struct drm_exec *exec,
> > > > > +             struct dma_fence *fence,
> > > > > +             enum dma_resv_usage private_usage,
> > > > > +             enum dma_resv_usage extobj_usage)
> > > > > +{
> > > > > +    struct drm_gem_object *obj;
> > > > > +    unsigned long index;
> > > > > +
> > > > > +    drm_exec_for_each_locked_object(exec, index, obj) {
> > > > > +        dma_resv_assert_held(obj->resv);
> > > > > +        dma_resv_add_fence(obj->resv, fence,
> > > > > +                   drm_gpuvm_is_extobj(gpuvm, obj) ?
> > > > > +                   private_usage : extobj_usage);
> > > > > +    }
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
> > > > > +
> > > > >   /**
> > > > >    * drm_gpuvm_bo_create() - create a new instance of struct
> > > > > drm_gpuvm_bo
> > > > >    * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> > > > > @@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm
> > > > > *gpuvm,
> > > > >       INIT_LIST_HEAD(&vm_bo->list.gpuva);
> > > > >       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> > > > > +    INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
> > > > > +    INIT_LIST_HEAD(&vm_bo->list.entry.evict);
> > > > > +
> > > > >       drm_gem_object_get(obj);
> > > > >       return vm_bo;
> > > > > @@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
> > > > >       if (!lock)
> > > > >           drm_gpuvm_resv_assert_held(gpuvm);
> > > > > +    drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
> > > > > +    drm_gpuvm_bo_list_del(vm_bo, evict, lock);
> > > > > +
> > > > >       list_del(&vm_bo->list.entry.gem);
> > > > >       drm_gem_object_put(obj);
> > > > > @@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct
> > > > > drm_gpuvm_bo *__vm_bo)
> > > > >   }
> > > > >   EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
> > > > > +/**
> > > > > + * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its
> > > > > &drm_gpuvm's
> > > > > + * extobj list
> > > > > + * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the
> > > > > extobj list.
> > > > > + *
> > > > > + * Adds the given @vm_bo to its &drm_gpuvm's extobj list if
> > > > > not on the list
> > > > > + * already and if the corresponding &drm_gem_object is an
> > > > > external object,
> > > > > + * actually.
> > > > > + */
> > > > > +void
> > > > > +drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
> > > > > +{
> > > > > +    struct drm_gpuvm *gpuvm = vm_bo->vm;
> > > > > +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
> > > > > +
> > > > > +    if (!lock)
> > > > > +        drm_gpuvm_resv_assert_held(gpuvm);
> > > > > +
> > > > > +    if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
> > > > > +        drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
> > > > > +}
> > > > > +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
> > > > > +
> > > > > +/**
> > > > > + * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to /
> > > > > from the &drm_gpuvms
> > > > > + * evicted list
> > > > > + * @vm_bo: the &drm_gpuvm_bo to add or remove
> > > > > + * @evict: indicates whether the object is evicted
> > > > > + *
> > > > > + * Adds a &drm_gpuvm_bo to or removes it from the
> > > > > &drm_gpuvms evicted list.
> > > > > + */
> > > > > +void
> > > > > +drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
> > > > > +{
> > > > > +    struct drm_gpuvm *gpuvm = vm_bo->vm;
> > > > > +    struct drm_gem_object *obj = vm_bo->obj;
> > > > > +    bool lock = !drm_gpuvm_resv_protected(gpuvm);
> > > > > +
> > > > > +    dma_resv_assert_held(obj->resv);
> > > > > +
> > > > > +    if (drm_gpuvm_is_extobj(gpuvm, obj)) {
> > > > > +        vm_bo->evicted = evict;
> > > > Does the lock case also need this?
> > >
> > > It doesn't need it by itself, but since we have
> > > drm_gpuvm_bo::evicted now, I want it to
> > > consistently indicate whether the BO is evicted or not.
> >
> > OK, I guess that ties to the meaning of bo->evicted, as discussed
> > above.
> >
> > >
> > > > > +
> > > > > +        if (!lock)
> > > > > +            return;
> > > >
> > > > Here the !lock case can not remove the gpuvm_bo from the list?
> > >
> > > You mean because we'd expect that drm_gpuvm_bo_evict(vm_bo,
> > > false) can only be called from
> > > within gpuvm_validate(), which requires the VM's resv lock? What
> > > if there is a ttm_validate()
> > > call for only this GEM obj?
> > >
> > > My idea was to remove VM_BOs from the evicted list in
> > > gpuvm_validate() directly, but I'm afraid
> > > I forgot that.
> >
> > Yes, I think the helper could do it if validate() is successful.
> > But what I meant above was that if the *driver* is responsible for
> > removing object from the evicted list, then if it's a
> > RESV_PROTECTED vm, it can't do that because
> > drm_gpuvm_bo_evict(gpuvm, false) will never get to removing it from
> > the list because it returns early.
>
> Well, with a RESV_PROTECTED VM we can't expect the evicted list to be
> up to date until we called drm_gpuvm_prepare_objects() by definition.
> In drm_gpuvm_prepare_objects() we'd need to
>
> if (vm_bo->evicted)
>    add_to_list()
> else
>    remove_from_list()
>
> though, because a driver could lock and unlock those objects without
> validation.

OK. Makes sense. No strong opinion from my side here.

/Thomas



>
> >
> > Thanks,
> >
> > Thomas
> >
> >
> > >
> > > >
> > > > Small patch here that I've been using for xe:
> > > >
> > > > https://patchwork.freedesktop.org/patch/561545/?series=124817&rev=1
> > > >
> > > > Thanks,
> > > >
> > > > Thomas
> > > >
> > > >
> > >
> >
>

2023-10-13 13:38:30

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects

Hi,

On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
> Currently the DRM GPUVM offers common infrastructure to track GPU VA
> allocations and mappings, generically connect GPU VA mappings to
> their
> backing buffers and perform more complex mapping operations on the
> GPU VA
> space.
>
> However, there are more design patterns commonly used by drivers,
> which
> can potentially be generalized in order to make the DRM GPUVM
> represent
> a basis for GPU-VM implementations. In this context, this patch aims
> at generalizing the following elements.
>
> 1) Provide a common dma-resv for GEM objects not being used outside
> of
>    this GPU-VM.
>
> 2) Provide tracking of external GEM objects (GEM objects which are
>    shared with other GPU-VMs).
>
> 3) Provide functions to efficiently lock all GEM objects dma-resv the
>    GPU-VM contains mappings of.
>
> 4) Provide tracking of evicted GEM objects the GPU-VM contains
> mappings
>    of, such that validation of evicted GEM objects is accelerated.
>
> 5) Provide some convinience functions for common patterns.
>
> Big thanks to Boris Brezillon for his help to figure out locking for
> drivers updating the GPU VA space within the fence signalling path.
>
> Suggested-by: Matthew Brost <[email protected]>
> Signed-off-by: Danilo Krummrich <[email protected]>
> ---
>  drivers/gpu/drm/drm_gpuvm.c | 646
> ++++++++++++++++++++++++++++++++++++
>  include/drm/drm_gpuvm.h     | 246 ++++++++++++++
>  2 files changed, 892 insertions(+)
>

There's a checkpatch.pl warning and a number of random macro CHECKs if
using --strict.

Also the overall s/Returns:/Return/ (and possibly function line break).


> diff --git a/drivers/gpu/drm/drm_gpuvm.c
> b/drivers/gpu/drm/drm_gpuvm.c
> index 28282283ddaf..6977bd30eca5 100644
> --- a/drivers/gpu/drm/drm_gpuvm.c
> +++ b/drivers/gpu/drm/drm_gpuvm.c
> @@ -82,6 +82,21 @@
>   * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> of this
>   * particular combination. If not existent a new instance is created
> and linked
>   * to the &drm_gem_object.
> + *
> + * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm,
> are also used
> + * as entry for the &drm_gpuvm's lists of external and evicted
> objects. Those
> + * list are maintained in order to accelerate locking of dma-resv
> locks and
s/list/lists/
> + * validation of evicted objects bound in a &drm_gpuvm. For
> instance, all
> + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked
> by calling
> + * drm_gpuvm_exec_lock(). Once locked drivers can call
> drm_gpuvm_validate() in
> + * order to validate all evicted &drm_gem_objects. It is also
> possible to lock
> + * additional &drm_gem_objects by providing the corresponding
> parameters to
> + * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop
> while making
> + * use of helper functions such as drm_gpuvm_prepare_range() or
> + * drm_gpuvm_prepare_objects().
> + *
> + * Every bound &drm_gem_object is treated as external object when
> its &dma_resv
> + * structure is different than the &drm_gpuvm's common &dma_resv
> structure.
>   */
>  
>  /**
> @@ -429,6 +444,20 @@
>   * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> and
>   * &drm_gem_object must be able to observe previous creations and
> destructions
>   * of &drm_gpuvm_bos in order to keep instances unique.
> + *
> + * The &drm_gpuvm's lists for keeping track of external and evicted
> objects are
> + * protected against concurrent insertion / removal and iteration
> internally.

> + *
> + * However, drivers still need ensure to protect concurrent calls to
> functions
> + * iterating those lists, namely drm_gpuvm_prepare_objects() and
> + * drm_gpuvm_validate().


> + *
> + * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag
> to indicate
> + * that the corresponding &dma_resv locks are held in order to
> protect the
> + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is
> disabled and
> + * the corresponding lockdep checks are enabled. This is an
> optimization for
> + * drivers which are capable of taking the corresponding &dma_resv
> locks and
> + * hence do not require internal locking.
>   */
>  
>  /**
> @@ -641,6 +670,195 @@
>   *     }
>   */
>  
> +/**
> + * get_next_vm_bo_from_list() - get the next vm_bo element

macros use a different kerneldoc syntax:
https://return42.github.io/linuxdoc/linuxdoc-howto/kernel-doc-syntax.html#macro

> + * @__gpuvm: The GPU VM
> + * @__list_name: The name of the list we're iterating on
> + * @__local_list: A pointer to the local list used to store already
> iterated items
> + * @__prev_vm_bo: The previous element we got from
> drm_gpuvm_get_next_cached_vm_bo()
> + *
> + * This helper is here to provide lockless list iteration. Lockless
> as in, the
> + * iterator releases the lock immediately after picking the first
> element from
> + * the list, so list insertion deletion can happen concurrently.
> + *
> + * Elements popped from the original list are kept in a local list,
> so removal
> + * and is_empty checks can still happen while we're iterating the
> list.
> + */
> +#define get_next_vm_bo_from_list(__gpuvm, __list_name, __local_list,
> __prev_vm_bo)     \
> +       ({                                                           
>                    \
> +               struct drm_gpuvm_bo *__vm_bo =
> NULL;                                    \
> +                                                                    
>                    \
> +               drm_gpuvm_bo_put(__prev_vm_bo);                      
>                    \
> +                                                                    
>                    \
> +               spin_lock(&(__gpuvm)-
> >__list_name.lock);                                \
> +               if (!(__gpuvm)-
> >__list_name.local_list)                                 \
> +                       (__gpuvm)->__list_name.local_list =
> __local_list;               \
> +               else                                                 
>                    \
> +                       WARN_ON((__gpuvm)->__list_name.local_list !=
> __local_list);     \
> +                                                                    
>                    \
> +               while (!list_empty(&(__gpuvm)->__list_name.list))
> {                     \
> +                       __vm_bo = list_first_entry(&(__gpuvm)-
> >__list_name.list,        \
> +                                                  struct
> drm_gpuvm_bo,                 \
> +                                                 
> list.entry.__list_name);             \
> +                       if (kref_get_unless_zero(&__vm_bo->kref))
> {                     \
> +                               list_move_tail(&(__vm_bo)-
> >list.entry.__list_name,      \
> +                                             
> __local_list);                           \
> +                               break;                               
>                    \
> +                       } else
> {                                                        \
> +                               list_del_init(&(__vm_bo)-
> >list.entry.__list_name);      \
> +                               __vm_bo =
> NULL;                                         \
> +                       }                                            
>                    \
> +               }                                                    
>                    \
> +               spin_unlock(&(__gpuvm)-
> >__list_name.lock);                              \
> +                                                                    
>                    \
> +               __vm_bo;                                             
>                    \
> +       })
> +
> +/**
> + * for_each_vm_bo_in_list() - internal vm_bo list iterator

Same here. Also missing the argument list, "iterator" is used for the
function macro when it's typically an object. Perhaps "iterate over
internal vm_bo lists"?

> + *
> + * This helper is here to provide lockless list iteration. Lockless
> as in, the
> + * iterator releases the lock immediately after picking the first
> element from the
> + * list, hence list insertion and deletion can happen concurrently.
> + *
> + * It is not allowed to re-assign the vm_bo pointer from inside this
> loop.
> + *
> + * Typical use:
> + *
> + *     struct drm_gpuvm_bo *vm_bo;
> + *     LIST_HEAD(my_local_list);
> + *
> + *     ret = 0;
> + *     for_each_vm_bo_in_list(gpuvm, <list_name>, &my_local_list,
> vm_bo) {
> + *             ret = do_something_with_vm_bo(..., vm_bo);
> + *             if (ret)
> + *                     break;
> + *     }
> + *     drm_gpuvm_bo_put(vm_bo);
> + *     restore_vm_bo_list(gpuvm, <list_name>, &my_local_list);
> + *
> + *
> + * Only used for internal list iterations, not meant to be exposed
> to the outside
> + * world.
> + */
> +#define for_each_vm_bo_in_list(__gpuvm, __list_name, __local_list,
> __vm_bo)    \
> +       for (__vm_bo = get_next_vm_bo_from_list(__gpuvm,
> __list_name,           \
> +                                               __local_list,
> NULL);            \
> +           
> __vm_bo;                                                           \
> +            __vm_bo = get_next_vm_bo_from_list(__gpuvm,
> __list_name,           \
> +                                               __local_list,
> __vm_bo))
> +
> +static void
> +__restore_vm_bo_list(struct drm_gpuvm *gpuvm, spinlock_t *lock,
> +                    struct list_head *list, struct list_head
> **local_list)
> +{
> +       /* Merge back the two lists, moving local list elements to
> the
> +        * head to preserve previous ordering, in case it matters.
> +        */
> +       spin_lock(lock);
> +       if (*local_list) {
> +               list_splice(*local_list, list);
> +               *local_list = NULL;
> +       }
> +       spin_unlock(lock);
> +}
> +
> +/**
> + * restore_vm_bo_list() - move vm_bo elements back to their original
> list

macro

> + * @__gpuvm: The GPU VM
> + * @__list_name: The name of the list we're iterating on
> + *
> + * When we're done iterating a vm_bo list, we should call
> restore_vm_bo_list()
> + * to restore the original state and let new iterations take place.
> + */
> +#define restore_vm_bo_list(__gpuvm,
> __list_name)                       \
> +       __restore_vm_bo_list((__gpuvm), &(__gpuvm)-
> >__list_name.lock,   \
> +                            &(__gpuvm)-
> >__list_name.list,              \
> +                            &(__gpuvm)->__list_name.local_list)
> +
> +static void
> +cond_spin_lock(spinlock_t *lock, bool cond)
> +{
> +       if (cond)
> +               spin_lock(lock);
> +}
> +
> +static void
> +cond_spin_unlock(spinlock_t *lock, bool cond)
> +{
> +       if (cond)
> +               spin_unlock(lock);
> +}
> +
> +static void
> +__drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
> +                       struct list_head *entry, struct list_head
> *list)
> +{
> +       cond_spin_lock(lock, !!lock);
> +       if (list_empty(entry))
> +               list_add_tail(entry, list);
> +       cond_spin_unlock(lock, !!lock);
> +}
> +
> +/**
> + * drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
macro

> + * @__vm_bo: the &drm_gpuvm_bo
> + * @__list_name: the name of the list to insert into
> + * @__lock: whether to lock with the internal spinlock
> + *
> + * Inserts the given @__vm_bo into the list specified by
> @__list_name.
> + */
> +#define drm_gpuvm_bo_list_add(__vm_bo, __list_name,
> __lock)                    \
> +       __drm_gpuvm_bo_list_add((__vm_bo)-
> >vm,                                  \
> +                               __lock ? &(__vm_bo)->vm-
> >__list_name.lock :     \
> +                                       
> NULL,                                  \
> +                               &(__vm_bo)-
> >list.entry.__list_name,             \
> +                               &(__vm_bo)->vm->__list_name.list)
> +
> +static void
> +__drm_gpuvm_bo_list_del(struct drm_gpuvm *gpuvm, spinlock_t *lock,
> +                       struct list_head *entry, bool init)
> +{
> +       cond_spin_lock(lock, !!lock);
> +       if (init) {
> +               if (!list_empty(entry))
> +                       list_del_init(entry);
> +       } else {
> +               list_del(entry);
> +       }
> +       cond_spin_unlock(lock, !!lock);
> +}
> +
> +/**
> + * drm_gpuvm_bo_list_del_init() - remove a vm_bo from the given list
macro
> + * @__vm_bo: the &drm_gpuvm_bo
> + * @__list_name: the name of the list to insert into
> + * @__lock: whether to lock with the internal spinlock
> + *
> + * Removes the given @__vm_bo from the list specified by
> @__list_name.
> + */
> +#define drm_gpuvm_bo_list_del_init(__vm_bo, __list_name,
> __lock)               \
> +       __drm_gpuvm_bo_list_del((__vm_bo)-
> >vm,                                  \
> +                               __lock ? &(__vm_bo)->vm-
> >__list_name.lock :     \
> +                                       
> NULL,                                  \
> +                               &(__vm_bo)-
> >list.entry.__list_name,             \
> +                               true)
> +
> +/**
> + * drm_gpuvm_bo_list_del() - remove a vm_bo from the given list
...
> + * @__vm_bo: the &drm_gpuvm_bo
> + * @__list_name: the name of the list to insert into
> + * @__lock: whether to lock with the internal spinlock
> + *
> + * Removes the given @__vm_bo from the list specified by
> @__list_name.
> + */
> +#define drm_gpuvm_bo_list_del(__vm_bo, __list_name,
> __lock)                    \
> +       __drm_gpuvm_bo_list_del((__vm_bo)-
> >vm,                                  \
> +                               __lock ? &(__vm_bo)->vm-
> >__list_name.lock :     \
> +                                       
> NULL,                                  \
> +                               &(__vm_bo)-
> >list.entry.__list_name,             \
> +                               false)
> +
>  #define to_drm_gpuva(__node)   container_of((__node), struct
> drm_gpuva, rb.node)
>  
>  #define GPUVA_START(node) ((node)->va.addr)
> @@ -760,6 +978,12 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct
> drm_gem_object *r_obj,
>         gpuvm->rb.tree = RB_ROOT_CACHED;
>         INIT_LIST_HEAD(&gpuvm->rb.list);
>  
> +       INIT_LIST_HEAD(&gpuvm->extobj.list);
> +       spin_lock_init(&gpuvm->extobj.lock);
> +
> +       INIT_LIST_HEAD(&gpuvm->evict.list);
> +       spin_lock_init(&gpuvm->evict.lock);
> +
>         drm_gpuvm_check_overflow(start_offset, range);
>         gpuvm->mm_start = start_offset;
>         gpuvm->mm_range = range;
> @@ -802,10 +1026,372 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>         WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
>              "GPUVA tree is not empty, potentially leaking
> memory.\n");
>  
> +       WARN(!list_empty(&gpuvm->extobj.list), "Extobj list should be
> empty.\n");
> +       WARN(!list_empty(&gpuvm->evict.list), "Evict list should be
> empty.\n");
> +

drm_WARN()?

>         drm_gem_object_put(gpuvm->r_obj);
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>  
> +static int
> +__drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
> +                           struct drm_exec *exec,
> +                           unsigned int num_fences)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +       LIST_HEAD(extobjs);
> +       int ret = 0;
> +
> +       for_each_vm_bo_in_list(gpuvm, extobj, &extobjs, vm_bo) {
> +               ret = drm_exec_prepare_obj(exec, vm_bo->obj,
> num_fences);
> +               if (ret)
> +                       break;
> +       }
> +       /* Drop ref in case we break out of the loop. */
> +       drm_gpuvm_bo_put(vm_bo);
> +       restore_vm_bo_list(gpuvm, extobj);
> +
> +       return ret;
> +}
> +
> +static int
> +drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
> +                                struct drm_exec *exec,
> +                                unsigned int num_fences)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +       int ret = 0;
> +
> +       drm_gpuvm_resv_assert_held(gpuvm);
> +       list_for_each_entry(vm_bo, &gpuvm->extobj.list,
> list.entry.extobj) {
> +               ret = drm_exec_prepare_obj(exec, vm_bo->obj,
> num_fences);
> +               if (ret)
> +                       break;
> +
> +               if (vm_bo->evicted)
> +                       drm_gpuvm_bo_list_add(vm_bo, evict, false);

Previous discussion

> +       }
> +
> +       return ret;
> +}
> +
> +/**
> + * drm_gpuvm_prepare_objects() - prepare all assoiciated BOs
> + * @gpuvm: the &drm_gpuvm
> + * @exec: the &drm_exec locking context
> + * @num_fences: the amount of &dma_fences to reserve
> + *
> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects the given
> + * &drm_gpuvm contains mappings of.
> + *
> + * Using this function directly, it is the drivers responsibility to
> call
> + * drm_exec_init() and drm_exec_fini() accordingly.
> + *
> + * Note: This function is safe against concurrent insertion and
> removal of
> + * external objects, however it is not safe against concurrent usage
> itself.
> + *
> + * Drivers need to make sure to protect this case with either an
> outer VM lock
> + * or by calling drm_gpuvm_prepare_vm() before this function within
> the
> + * drm_exec_until_all_locked() loop, such that the GPUVM's dma-resv
> lock ensures
> + * mutual exclusion.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
> +                         struct drm_exec *exec,
> +                         unsigned int num_fences)
> +{
> +       if (drm_gpuvm_resv_protected(gpuvm))
> +               return drm_gpuvm_prepare_objects_locked(gpuvm, exec,
> +                                                       num_fences);
> +       else
> +               return __drm_gpuvm_prepare_objects(gpuvm, exec,
> num_fences);
> +
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_objects);
> +
> +/**
> + * drm_gpuvm_prepare_range() - prepare all BOs mapped within a given
> range
> + * @gpuvm: the &drm_gpuvm
> + * @exec: the &drm_exec locking context
> + * @addr: the start address within the VA space
> + * @range: the range to iterate within the VA space
> + * @num_fences: the amount of &dma_fences to reserve
> + *
> + * Calls drm_exec_prepare_obj() for all &drm_gem_objects mapped
> between @addr
> + * and @addr + @range.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm, struct drm_exec
> *exec,
> +                       u64 addr, u64 range, unsigned int num_fences)
> +{
> +       struct drm_gpuva *va;
> +       u64 end = addr + range;
> +       int ret;
> +
> +       drm_gpuvm_for_each_va_range(va, gpuvm, addr, end) {
> +               struct drm_gem_object *obj = va->gem.obj;
> +
> +               ret = drm_exec_prepare_obj(exec, obj, num_fences);
> +               if (ret)
> +                       return ret;
> +       }
> +
> +       return 0;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_prepare_range);
> +
> +/**
> + * drm_gpuvm_exec_lock() - lock all dma-resv of all assoiciated BOs
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + * @num_fences: the amount of &dma_fences to reserve
> + * @interruptible: sleep interruptible if waiting
> + *
> + * Acquires all dma-resv locks of all &drm_gem_objects the given
> + * &drm_gpuvm contains mappings of.
> + *
> + * Addionally, when calling this function with struct
> drm_gpuvm_exec::extra
> + * being set the driver receives the given @fn callback to lock
> additional
> + * dma-resv in the context of the &drm_gpuvm_exec instance.
> Typically, drivers
> + * would call drm_exec_prepare_obj() from within this callback.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
> +                   unsigned int num_fences,
> +                   bool interruptible)

Could we let the driver select all drm_exec flags here? Avoiding
DRM_EXEC_IGNORE_DUPLICATES is IMO a good consistency check if the
driver supports it.

> +{
> +       struct drm_gpuvm *gpuvm = vm_exec->vm;
> +       struct drm_exec *exec = &vm_exec->exec;
> +       uint32_t flags;

It's unfortunate that drm_exec uses uint32_t rather than u32 for the
flags argument, (I'll send a patch to fix that) but we should be fine
using u32?

> +       int ret;
> +
> +       flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
> +               DRM_EXEC_IGNORE_DUPLICATES;
> +
> +       drm_exec_init(exec, flags);
> +
> +       drm_exec_until_all_locked(exec) {
> +               ret = drm_gpuvm_prepare_vm(gpuvm, exec, num_fences);
> +               drm_exec_retry_on_contention(exec);
> +               if (ret)
> +                       goto err;
> +
> +               ret = drm_gpuvm_prepare_objects(gpuvm, exec,
> num_fences);
> +               drm_exec_retry_on_contention(exec);
> +               if (ret)
> +                       goto err;
> +
> +               if (vm_exec->extra.fn) {
> +                       ret = vm_exec->extra.fn(vm_exec, num_fences);
> +                       drm_exec_retry_on_contention(exec);
> +                       if (ret)
> +                               goto err;
> +               }
> +       }
> +
> +       return 0;
> +
> +err:
> +       drm_exec_fini(exec);
> +       return ret;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock);
> +
> +static int
> +fn_lock_array(struct drm_gpuvm_exec *vm_exec, unsigned int
> num_fences)
> +{
> +       struct {
> +               struct drm_gem_object **objs;
> +               unsigned int num_objs;
> +       } *args = vm_exec->extra.priv;
> +
> +       return drm_exec_prepare_array(&vm_exec->exec, args->objs,
> +                                     args->num_objs, num_fences);
> +}
> +
> +/**
> + * drm_gpuvm_exec_lock_array() - lock all dma-resv of all
> assoiciated BOs
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + * @objs: additional &drm_gem_objects to lock
> + * @num_objs: the number of additional &drm_gem_objects to lock
> + * @num_fences: the amount of &dma_fences to reserve
> + * @interruptible: sleep interruptible if waiting
> + *
> + * Acquires all dma-resv locks of all &drm_gem_objects the given
> &drm_gpuvm
> + * contains mappings of, plus the ones given through @objs.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
> +                         struct drm_gem_object **objs,
> +                         unsigned int num_objs,
> +                         unsigned int num_fences,
> +                         bool interruptible)
u32 exec_flags?

> +{
> +       struct {
> +               struct drm_gem_object **objs;
> +               unsigned int num_objs;
> +       } args;
> +
> +       args.objs = objs;
> +       args.num_objs = num_objs;
> +
> +       vm_exec->extra.fn = fn_lock_array;
> +       vm_exec->extra.priv = &args;
> +
> +       return drm_gpuvm_exec_lock(vm_exec, num_fences,
> interruptible);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_array);
> +
> +/**
> + * drm_gpuvm_exec_lock_range() - prepare all BOs mapped within a
> given range
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + * @addr: the start address within the VA space
> + * @range: the range to iterate within the VA space
> + * @num_fences: the amount of &dma_fences to reserve
> + * @interruptible: sleep interruptible if waiting
> + *
> + * Acquires all dma-resv locks of all &drm_gem_objects mapped
> between @addr and
> + * @addr + @range.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
> +                         u64 addr, u64 range,
> +                         unsigned int num_fences,
> +                         bool interruptible)
Same here
> +{
> +       struct drm_gpuvm *gpuvm = vm_exec->vm;
> +       struct drm_exec *exec = &vm_exec->exec;
> +       uint32_t flags;
> +       int ret;
> +
> +       flags = interruptible ? DRM_EXEC_INTERRUPTIBLE_WAIT : 0 |
> +               DRM_EXEC_IGNORE_DUPLICATES;
> +
> +       drm_exec_init(exec, flags);
> +
> +       drm_exec_until_all_locked(exec) {
> +               ret = drm_gpuvm_prepare_range(gpuvm, exec, addr,
> range,
> +                                             num_fences);
> +               drm_exec_retry_on_contention(exec);
> +               if (ret)
> +                       goto err;
> +       }
> +
> +       return ret;
> +
> +err:
> +       drm_exec_fini(exec);
> +       return ret;
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_exec_lock_range);
> +
> +static int
> +__drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo;
> +       LIST_HEAD(evict);
> +       int ret = 0;
> +
> +       for_each_vm_bo_in_list(gpuvm, evict, &evict, vm_bo) {
> +               ret = ops->vm_bo_validate(vm_bo, exec);
> +               if (ret)
> +                       break;
> +       }
> +       /* Drop ref in case we break out of the loop. */
> +       drm_gpuvm_bo_put(vm_bo);
> +       restore_vm_bo_list(gpuvm, evict);
> +
> +       return ret;
> +}
> +
> +static int
> +drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec
> *exec)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +       struct drm_gpuvm_bo *vm_bo, *next;
> +       int ret = 0;
> +
> +       drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       /* Iterate list safely, drivers typically remove the current
> entry from
> +        * their drm_gpuvm_ops::vm_bo_validate callback. Drivers
> might also
> +        * re-add the entry on failure; this is safe since on failure
> we break
> +        * out of the loop.
> +        */
> +       list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
> +                                list.entry.evict) {
> +               ret = ops->vm_bo_validate(vm_bo, exec);
> +               if (ret)
> +                       break;
> +       }
> +
> +       return ret;
> +}
> +
> +/**
> + * drm_gpuvm_validate() - validate all BOs marked as evicted
> + * @gpuvm: the &drm_gpuvm to validate evicted BOs
> + * @exec: the &drm_exec instance used for locking the GPUVM
> + *
> + * Calls the &drm_gpuvm_ops::vm_bo_validate callback for all evicted
> buffer
> + * objects being mapped in the given &drm_gpuvm.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +int
> +drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
> +{
> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
> +
> +       if (unlikely(!ops || !ops->vm_bo_validate))
> +               return -ENOTSUPP;
> +
> +       if (drm_gpuvm_resv_protected(gpuvm))
> +               return drm_gpuvm_validate_locked(gpuvm, exec);
> +       else
> +               return __drm_gpuvm_validate(gpuvm, exec);
> +
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_validate);
> +
> +/**
> + * drm_gpuvm_resv_add_fence - add fence to private and all extobj
> + * dma-resv
> + * @gpuvm: the &drm_gpuvm to add a fence to
> + * @exec: the &drm_exec locking context
> + * @fence: fence to add
> + * @private_usage: private dma-resv usage
> + * @extobj_usage: extobj dma-resv usage
> + */
> +void
> +drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
> +                        struct drm_exec *exec,
> +                        struct dma_fence *fence,
> +                        enum dma_resv_usage private_usage,
> +                        enum dma_resv_usage extobj_usage)
> +{
> +       struct drm_gem_object *obj;
> +       unsigned long index;
> +
> +       drm_exec_for_each_locked_object(exec, index, obj) {
> +               dma_resv_assert_held(obj->resv);
> +               dma_resv_add_fence(obj->resv, fence,
> +                                  drm_gpuvm_is_extobj(gpuvm, obj) ?
> +                                  private_usage : extobj_usage);
> +       }
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_resv_add_fence);
> +
>  /**
>   * drm_gpuvm_bo_create() - create a new instance of struct
> drm_gpuvm_bo
>   * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> @@ -838,6 +1424,9 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
>         INIT_LIST_HEAD(&vm_bo->list.gpuva);
>         INIT_LIST_HEAD(&vm_bo->list.entry.gem);
>  
> +       INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
> +       INIT_LIST_HEAD(&vm_bo->list.entry.evict);
> +
>         drm_gem_object_get(obj);
>  
>         return vm_bo;
> @@ -858,6 +1447,9 @@ drm_gpuvm_bo_destroy(struct kref *kref)
>         if (!lock)
>                 drm_gpuvm_resv_assert_held(gpuvm);
>  
> +       drm_gpuvm_bo_list_del(vm_bo, extobj, lock);
> +       drm_gpuvm_bo_list_del(vm_bo, evict, lock);
> +
>         list_del(&vm_bo->list.entry.gem);
>  
>         drm_gem_object_put(obj);
> @@ -994,6 +1586,60 @@ drm_gpuvm_bo_obtain_prealloc(struct
> drm_gpuvm_bo *__vm_bo)
>  }
>  EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
>  
> +/**
> + * drm_gpuvm_bo_extobj_add() - adds the &drm_gpuvm_bo to its
> &drm_gpuvm's
> + * extobj list
> + * @vm_bo: The &drm_gpuvm_bo to add to its &drm_gpuvm's the extobj
> list.
> + *
> + * Adds the given @vm_bo to its &drm_gpuvm's extobj list if not on
> the list
> + * already and if the corresponding &drm_gem_object is an external
> object,
> + * actually.
> + */
> +void
> +drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo)
> +{
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       if (!lock)
> +               drm_gpuvm_resv_assert_held(gpuvm);
> +
> +       if (drm_gpuvm_is_extobj(gpuvm, vm_bo->obj))
> +               drm_gpuvm_bo_list_add(vm_bo, extobj, lock);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_extobj_add);
> +
> +/**
> + * drm_gpuvm_bo_evict() - add / remove a &drm_gpuvm_bo to / from the
> &drm_gpuvms
> + * evicted list
> + * @vm_bo: the &drm_gpuvm_bo to add or remove
> + * @evict: indicates whether the object is evicted
> + *
> + * Adds a &drm_gpuvm_bo to or removes it from the &drm_gpuvms
> evicted list.
> + */
> +void
> +drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict)
> +{
> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
> +       struct drm_gem_object *obj = vm_bo->obj;
> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
> +
> +       dma_resv_assert_held(obj->resv);
> +
> +       if (drm_gpuvm_is_extobj(gpuvm, obj)) {
> +               vm_bo->evicted = evict;
> +
> +               if (!lock)
> +                       return;
> +       }
> +
> +       if (evict)
> +               drm_gpuvm_bo_list_add(vm_bo, evict, lock);
> +       else
> +               drm_gpuvm_bo_list_del_init(vm_bo, evict, lock);
> +}
> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_evict);
> +
>  static int
>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>                    struct drm_gpuva *va)
> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> index ddb0b8d323cf..eadc3ea5bf57 100644
> --- a/include/drm/drm_gpuvm.h
> +++ b/include/drm/drm_gpuvm.h
> @@ -31,6 +31,7 @@
>  #include <linux/types.h>
>  
>  #include <drm/drm_gem.h>
> +#include <drm/drm_exec.h>
>  
>  struct drm_gpuvm;
>  struct drm_gpuvm_bo;
> @@ -272,6 +273,50 @@ struct drm_gpuvm {
>          * @r_obj: Root GEM object; representing the GPUVM's common
> &dma_resv.
>          */
>         struct drm_gem_object *r_obj;
> +
> +       /**
> +        * @extobj: structure holding the extobj list
> +        */
> +       struct {
> +               /**
> +                * @list: &list_head storing &drm_gpuvm_bos serving
> as
> +                * external object
> +                */
> +               struct list_head list;
> +
> +               /**
> +                * @local_list: pointer to the local list temporarily
> storing
> +                * entries from the external object list
> +                */
> +               struct list_head *local_list;
> +
> +               /**
> +                * @lock: spinlock to protect the extobj list
> +                */
> +               spinlock_t lock;
> +       } extobj;
> +
> +       /**
> +        * @evict: structure holding the evict list and evict list
> lock
> +        */
> +       struct {
> +               /**
> +                * @list: &list_head storing &drm_gpuvm_bos currently
> being
> +                * evicted
> +                */
> +               struct list_head list;
> +
> +               /**
> +                * @local_list: pointer to the local list temporarily
> storing
> +                * entries from the evicted object list
> +                */
> +               struct list_head *local_list;
> +
> +               /**
> +                * @lock: spinlock to protect the evict list
> +                */
> +               spinlock_t lock;
> +       } evict;
>  };
>  
>  void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
> *r_obj,
> @@ -329,6 +374,22 @@ drm_gpuvm_resv_protected(struct drm_gpuvm
> *gpuvm)
>  #define drm_gpuvm_resv_assert_held(gpuvm__) \
>         dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
>  
> +/**
> + * drm_gpuvm_is_extobj() - indicates whether the given
> &drm_gem_object is an
> + * external object
> + * @gpuvm: the &drm_gpuvm to check
> + * @obj: the &drm_gem_object to check
> + *
> + * Returns: true if the &drm_gem_object &dma_resv differs from the
> + * &drm_gpuvms &dma_resv, false otherwise
> + */
> +static inline bool
> +drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm,
> +                   struct drm_gem_object *obj)
> +{
> +       return obj && obj->resv != drm_gpuvm_resv(gpuvm);
> +}
> +
>  static inline struct drm_gpuva *
>  __drm_gpuva_next(struct drm_gpuva *va)
>  {
> @@ -407,6 +468,140 @@ __drm_gpuva_next(struct drm_gpuva *va)
>  #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
>         list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list,
> rb.entry)
>  
> +/**
> + * struct drm_gpuvm_exec - &drm_gpuvm abstraction of &drm_exec
> + *
> + * This structure should be created on the stack as &drm_exec should
> be.
> + *
> + * Optionally, @extra can be set in order to lock additional
> &drm_gem_objects.
> + */
> +struct drm_gpuvm_exec {
> +       /**
> +        * @exec: the &drm_exec structure
> +        */
> +       struct drm_exec exec;
> +
> +       /**
> +        * @vm: the &drm_gpuvm to lock its DMA reservations
> +        */
> +       struct drm_gpuvm *vm;
> +
> +       /**
> +        * @extra: Callback and corresponding private data for the
> driver to
> +        * lock arbitrary additional &drm_gem_objects.
> +        */
> +       struct {
> +               /**
> +                * @fn: The driver callback to lock additional
> &drm_gem_objects.
> +                */
> +               int (*fn)(struct drm_gpuvm_exec *vm_exec,
> +                         unsigned int num_fences);
> +
> +               /**
> +                * @priv: driver private data for the @fn callback
> +                */
> +               void *priv;
> +       } extra;
> +};
> +
> +/**
> + * drm_gpuvm_prepare_vm() - prepare the GPUVMs common dma-resv
> + * @gpuvm: the &drm_gpuvm
> + * @exec: the &drm_exec context
> + * @num_fences: the amount of &dma_fences to reserve
> + *
> + * Calls drm_exec_prepare_obj() for the GPUVMs dummy
> &drm_gem_object.
> + *
> + * Using this function directly, it is the drivers responsibility to
> call
> + * drm_exec_init() and drm_exec_fini() accordingly.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +static inline int
> +drm_gpuvm_prepare_vm(struct drm_gpuvm *gpuvm,
> +                    struct drm_exec *exec,
> +                    unsigned int num_fences)
> +{
> +       return drm_exec_prepare_obj(exec, gpuvm->r_obj, num_fences);
> +}
> +
> +int drm_gpuvm_prepare_objects(struct drm_gpuvm *gpuvm,
> +                             struct drm_exec *exec,
> +                             unsigned int num_fences);
> +
> +int drm_gpuvm_prepare_range(struct drm_gpuvm *gpuvm,
> +                           struct drm_exec *exec,
> +                           u64 addr, u64 range,
> +                           unsigned int num_fences);
> +
> +int drm_gpuvm_exec_lock(struct drm_gpuvm_exec *vm_exec,
> +                       unsigned int num_fences,
> +                       bool interruptible);
> +
> +int drm_gpuvm_exec_lock_array(struct drm_gpuvm_exec *vm_exec,
> +                             struct drm_gem_object **objs,
> +                             unsigned int num_objs,
> +                             unsigned int num_fences,
> +                             bool interruptible);
> +
> +int drm_gpuvm_exec_lock_range(struct drm_gpuvm_exec *vm_exec,
> +                             u64 addr, u64 range,
> +                             unsigned int num_fences,
> +                             bool interruptible);
> +
> +/**
> + * drm_gpuvm_exec_unlock() - lock all dma-resv of all assoiciated
> BOs
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + *
> + * Releases all dma-resv locks of all &drm_gem_objects previously
> acquired
> + * through drm_gpuvm_exec_lock() or its variants.
> + *
> + * Returns: 0 on success, negative error code on failure.
> + */
> +static inline void
> +drm_gpuvm_exec_unlock(struct drm_gpuvm_exec *vm_exec)
> +{
> +       drm_exec_fini(&vm_exec->exec);
> +}
> +
> +int drm_gpuvm_validate(struct drm_gpuvm *gpuvm, struct drm_exec
> *exec);
> +void drm_gpuvm_resv_add_fence(struct drm_gpuvm *gpuvm,
> +                             struct drm_exec *exec,
> +                             struct dma_fence *fence,
> +                             enum dma_resv_usage private_usage,
> +                             enum dma_resv_usage extobj_usage);
> +
> +/**
> + * drm_gpuvm_exec_resv_add_fence()
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + * @fence: fence to add
> + * @private_usage: private dma-resv usage
> + * @extobj_usage: extobj dma-resv usage
> + *
> + * See drm_gpuvm_resv_add_fence().
> + */
> +static inline void
> +drm_gpuvm_exec_resv_add_fence(struct drm_gpuvm_exec *vm_exec,
> +                             struct dma_fence *fence,
> +                             enum dma_resv_usage private_usage,
> +                             enum dma_resv_usage extobj_usage)
> +{
> +       drm_gpuvm_resv_add_fence(vm_exec->vm, &vm_exec->exec, fence,
> +                                private_usage, extobj_usage);
> +}
> +
> +/**
> + * drm_gpuvm_exec_validate()
> + * @vm_exec: the &drm_gpuvm_exec wrapper
> + *
> + * See drm_gpuvm_validate().
> + */
> +static inline int
> +drm_gpuvm_exec_validate(struct drm_gpuvm_exec *vm_exec)
> +{
> +       return drm_gpuvm_validate(vm_exec->vm, &vm_exec->exec);
> +}
> +
>  /**
>   * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
>   * &drm_gem_object combination
> @@ -435,6 +630,12 @@ struct drm_gpuvm_bo {
>          */
>         struct drm_gem_object *obj;
>  
> +       /**
> +        * @evicted: Indicates whether the &drm_gem_object is
> evicted; field
> +        * protected by the &drm_gem_object's dma-resv lock.
> +        */
> +       bool evicted;
> +
>         /**
>          * @kref: The reference count for this &drm_gpuvm_bo.
>          */
> @@ -459,6 +660,18 @@ struct drm_gpuvm_bo {
>                          * gpuva list.
>                          */
>                         struct list_head gem;
> +
> +                       /**
> +                        * @evict: List entry to attach to the
> &drm_gpuvms
> +                        * extobj list.
> +                        */
> +                       struct list_head extobj;
> +
> +                       /**
> +                        * @evict: List entry to attach to the
> &drm_gpuvms evict
> +                        * list.
> +                        */
> +                       struct list_head evict;
>                 } entry;
>         } list;
>  };
> @@ -493,6 +706,27 @@ struct drm_gpuvm_bo *
>  drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
>                   struct drm_gem_object *obj);
>  
> +void drm_gpuvm_bo_evict(struct drm_gpuvm_bo *vm_bo, bool evict);
> +
> +/**
> + * drm_gpuvm_bo_gem_evict()
> + * @obj: the &drm_gem_object
> + * @evict: indicates whether @obj is evicted
> + *
> + * See drm_gpuvm_bo_evict().
> + */
> +static inline void
> +drm_gpuvm_bo_gem_evict(struct drm_gem_object *obj, bool evict)
> +{
> +       struct drm_gpuvm_bo *vm_bo;
> +
> +       drm_gem_gpuva_assert_lock_held(obj);
> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj)
> +               drm_gpuvm_bo_evict(vm_bo, evict);
> +}
> +
> +void drm_gpuvm_bo_extobj_add(struct drm_gpuvm_bo *vm_bo);
> +
>  /**
>   * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of
> &drm_gpuva
>   * @va__: &drm_gpuva structure to assign to in each iteration step
> @@ -855,6 +1089,18 @@ struct drm_gpuvm_ops {
>          */
>         void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
>  
> +       /**
> +        * @vm_bo_validate: called from drm_gpuvm_validate()
> +        *
> +        * Drivers receive this callback for every evicted
> &drm_gem_object being
> +        * mapped in the corresponding &drm_gpuvm.
> +        *
> +        * Typically, drivers would call their driver specific
> variant of
> +        * ttm_bo_validate() from within this callback.
> +        */
> +       int (*vm_bo_validate)(struct drm_gpuvm_bo *vm_bo,
> +                             struct drm_exec *exec);
> +
>         /**
>          * @sm_step_map: called from &drm_gpuvm_sm_map to finally
> insert the
>          * mapping once all previous steps were completed

2023-10-16 10:56:27

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 4/6] drm/gpuvm: track/lock/validate external/evicted objects


On 10/13/23 15:37, Thomas Hellström wrote:
> Hi,
>
> On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
>> Currently the DRM GPUVM offers common infrastructure to track GPU VA
>> allocations and mappings, generically connect GPU VA mappings to
>> their
>> backing buffers and perform more complex mapping operations on the
>> GPU VA
>> space.
>>
>> However, there are more design patterns commonly used by drivers,
>> which
>> can potentially be generalized in order to make the DRM GPUVM
>> represent
>> a basis for GPU-VM implementations. In this context, this patch aims
>> at generalizing the following elements.
>>
>> 1) Provide a common dma-resv for GEM objects not being used outside
>> of
>>    this GPU-VM.
>>
>> 2) Provide tracking of external GEM objects (GEM objects which are
>>    shared with other GPU-VMs).
>>
>> 3) Provide functions to efficiently lock all GEM objects dma-resv the
>>    GPU-VM contains mappings of.
>>
>> 4) Provide tracking of evicted GEM objects the GPU-VM contains
>> mappings
>>    of, such that validation of evicted GEM objects is accelerated.
>>
>> 5) Provide some convinience functions for common patterns.
>>
>> Big thanks to Boris Brezillon for his help to figure out locking for
>> drivers updating the GPU VA space within the fence signalling path.
>>
>> Suggested-by: Matthew Brost <[email protected]>
>> Signed-off-by: Danilo Krummrich <[email protected]>
>> ---
>>  drivers/gpu/drm/drm_gpuvm.c | 646
>> ++++++++++++++++++++++++++++++++++++
>>  include/drm/drm_gpuvm.h     | 246 ++++++++++++++
>>  2 files changed, 892 insertions(+)
>>
> There's a checkpatch.pl warning and a number of random macro CHECKs if
> using --strict.
>
> Also the overall s/Returns:/Return/ (and possibly function line break).
>
>
>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>> b/drivers/gpu/drm/drm_gpuvm.c
>> index 28282283ddaf..6977bd30eca5 100644
>> --- a/drivers/gpu/drm/drm_gpuvm.c
>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>> @@ -82,6 +82,21 @@
>>   * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
>> of this
>>   * particular combination. If not existent a new instance is created
>> and linked
>>   * to the &drm_gem_object.
>> + *
>> + * &drm_gpuvm_bo structures, since unique for a given &drm_gpuvm,
>> are also used
>> + * as entry for the &drm_gpuvm's lists of external and evicted
>> objects. Those
>> + * list are maintained in order to accelerate locking of dma-resv
>> locks and
> s/list/lists/
>> + * validation of evicted objects bound in a &drm_gpuvm. For
>> instance, all
>> + * &drm_gem_object's &dma_resv of a given &drm_gpuvm can be locked
>> by calling
>> + * drm_gpuvm_exec_lock(). Once locked drivers can call
>> drm_gpuvm_validate() in
>> + * order to validate all evicted &drm_gem_objects. It is also
>> possible to lock
>> + * additional &drm_gem_objects by providing the corresponding
>> parameters to
>> + * drm_gpuvm_exec_lock() as well as open code the &drm_exec loop
>> while making
>> + * use of helper functions such as drm_gpuvm_prepare_range() or
>> + * drm_gpuvm_prepare_objects().
>> + *
>> + * Every bound &drm_gem_object is treated as external object when
>> its &dma_resv
>> + * structure is different than the &drm_gpuvm's common &dma_resv
>> structure.
>>   */
>>
>>  /**
>> @@ -429,6 +444,20 @@
>>   * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
>> and
>>   * &drm_gem_object must be able to observe previous creations and
>> destructions
>>   * of &drm_gpuvm_bos in order to keep instances unique.
>> + *
>> + * The &drm_gpuvm's lists for keeping track of external and evicted
>> objects are
>> + * protected against concurrent insertion / removal and iteration
>> internally.
>> + *
>> + * However, drivers still need ensure to protect concurrent calls to
>> functions
>> + * iterating those lists, namely drm_gpuvm_prepare_objects() and
>> + * drm_gpuvm_validate().
>
>> + *
>> + * Alternatively, drivers can set the &DRM_GPUVM_RESV_PROTECTED flag
>> to indicate
>> + * that the corresponding &dma_resv locks are held in order to
>> protect the
>> + * lists. If &DRM_GPUVM_RESV_PROTECTED is set, internal locking is
>> disabled and
>> + * the corresponding lockdep checks are enabled. This is an
>> optimization for
>> + * drivers which are capable of taking the corresponding &dma_resv
>> locks and
>> + * hence do not require internal locking.
>>   */
>>
>>  /**
>> @@ -641,6 +670,195 @@
>>   *     }
>>   */
>>
>> +/**
>> + * get_next_vm_bo_from_list() - get the next vm_bo element
> macros use a different kerneldoc syntax:
> https://return42.github.io/linuxdoc/linuxdoc-howto/kernel-doc-syntax.html#macro

The syntax for macros in that page does not appear to be valid from what
I can tell. Please ignore that.

/Thomas

2023-10-17 09:59:46

by Danilo Krummrich

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 3/6] drm/gpuvm: add an abstraction for a VM / BO combination

On Fri, Oct 13, 2023 at 02:30:29PM +0200, Thomas Hellstr?m wrote:
> On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
> > Add an abstraction layer between the drm_gpuva mappings of a
> > particular
> > drm_gem_object and this GEM object itself. The abstraction represents
> > a
> > combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
> > holds
> > a list of drm_gpuvm_bo structures (the structure representing this
> > abstraction), while each drm_gpuvm_bo contains list of mappings of
> > this
> > GEM object.
> >
> > This has multiple advantages:
> >
> > 1) We can use the drm_gpuvm_bo structure to attach it to various
> > lists
> > ?? of the drm_gpuvm. This is useful for tracking external and evicted
> > ?? objects per VM, which is introduced in subsequent patches.
> >
> > 2) Finding mappings of a certain drm_gem_object mapped in a certain
> > ?? drm_gpuvm becomes much cheaper.
> >
> > 3) Drivers can derive and extend the structure to easily represent
> > ?? driver specific states of a BO for a certain GPUVM.
> >
> > The idea of this abstraction was taken from amdgpu, hence the credit
> > for
> > this idea goes to the developers of amdgpu.
> >
> > Cc: Christian K?nig <[email protected]>
> > Signed-off-by: Danilo Krummrich <[email protected]>
> > ---
> > ?drivers/gpu/drm/drm_gpuvm.c??????????? | 332 +++++++++++++++++++++--
> > --
> > ?drivers/gpu/drm/nouveau/nouveau_uvmm.c |? 64 +++--
> > ?include/drm/drm_gem.h????????????????? |? 32 +--
> > ?include/drm/drm_gpuvm.h??????????????? | 177 ++++++++++++-
> > ?4 files changed, 521 insertions(+), 84 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/drm_gpuvm.c
> > b/drivers/gpu/drm/drm_gpuvm.c
> > index 6368dfdbe9dd..28282283ddaf 100644
> > --- a/drivers/gpu/drm/drm_gpuvm.c
> > +++ b/drivers/gpu/drm/drm_gpuvm.c
> > @@ -70,6 +70,18 @@
> > ? * &drm_gem_object, such as the &drm_gem_object containing the root
> > page table,
> > ? * but it can also be a 'dummy' object, which can be allocated with
> > ? * drm_gpuvm_root_object_alloc().
> > + *
> > + * In order to connect a struct drm_gpuva its backing
> > &drm_gem_object each
> NIT: Same as previous patch regarding kerneldoc references

I was intentionally using generic references here to make the documentation
more readable while still keeping references to be able to look up the
structure's fields.

>
> > + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
> > each
> > + * &drm_gpuvm_bo contains a list of &&drm_gpuva structures.
> > + *
> > + * A &drm_gpuvm_bo is an abstraction that represents a combination
> > of a
> > + * &drm_gpuvm and a &drm_gem_object. Every such combination should
> > be unique.
> > + * This is ensured by the API through drm_gpuvm_bo_obtain() and
> > + * drm_gpuvm_bo_obtain_prealloc() which first look into the
> > corresponding
> > + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
> > of this
> > + * particular combination. If not existent a new instance is created
> > and linked
> > + * to the &drm_gem_object.
> > ? */
> > ?
> > ?/**
> > @@ -395,21 +407,28 @@
> > ?/**
> > ? * DOC: Locking
> > ? *
> > - * Generally, the GPU VA manager does not take care of locking
> > itself, it is
> > - * the drivers responsibility to take care about locking. Drivers
> > might want to
> > - * protect the following operations: inserting, removing and
> > iterating
> > - * &drm_gpuva objects as well as generating all kinds of operations,
> > such as
> > - * split / merge or prefetch.
> > - *
> > - * The GPU VA manager also does not take care of the locking of the
> > backing
> > - * &drm_gem_object buffers GPU VA lists by itself; drivers are
> > responsible to
> > - * enforce mutual exclusion using either the GEMs dma_resv lock or
> > alternatively
> > - * a driver specific external lock. For the latter see also
> > - * drm_gem_gpuva_set_lock().
> > - *
> > - * However, the GPU VA manager contains lockdep checks to ensure
> > callers of its
> > - * API hold the corresponding lock whenever the &drm_gem_objects GPU
> > VA list is
> > - * accessed by functions such as drm_gpuva_link() or
> > drm_gpuva_unlink().
> > + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
> > care of
> > + * locking itself, it is the drivers responsibility to take care
> > about locking.
> > + * Drivers might want to protect the following operations:
> > inserting, removing
> > + * and iterating &drm_gpuva objects as well as generating all kinds
> > of
> > + * operations, such as split / merge or prefetch.
> > + *
> > + * DRM GPUVM also does not take care of the locking of the backing
> > + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
> > abstractions by
> > + * itself; drivers are responsible to enforce mutual exclusion using
> > either the
> > + * GEMs dma_resv lock or alternatively a driver specific external
> > lock. For the
> > + * latter see also drm_gem_gpuva_set_lock().
> > + *
> > + * However, DRM GPUVM contains lockdep checks to ensure callers of
> > its API hold
> > + * the corresponding lock whenever the &drm_gem_objects GPU VA list
> > is accessed
> > + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
> > also
> > + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
> > + *
> > + * The latter is required since on creation and destruction of a
> > &drm_gpuvm_bo
> > + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
> > gpuva list.
> > + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
> > and
> > + * &drm_gem_object must be able to observe previous creations and
> > destructions
> > + * of &drm_gpuvm_bos in order to keep instances unique.
> > ? */
> > ?
> > ?/**
> > @@ -439,6 +458,7 @@
> > ? *?????{
> > ? *?????????????struct drm_gpuva_ops *ops;
> > ? *?????????????struct drm_gpuva_op *op
> > + *?????????????struct drm_gpuvm_bo *vm_bo;
> > ? *
> > ? *?????????????driver_lock_va_space();
> > ? *?????????????ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
> > @@ -446,6 +466,10 @@
> > ? *?????????????if (IS_ERR(ops))
> > ? *?????????????????????return PTR_ERR(ops);
> > ? *
> > + *?????????????vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
> > + *?????????????if (IS_ERR(vm_bo))
> > + *?????????????????????return PTR_ERR(vm_bo);
> > + *
> > ? *?????????????drm_gpuva_for_each_op(op, ops) {
> > ? *?????????????????????struct drm_gpuva *va;
> > ? *
> > @@ -458,7 +482,7 @@
> > ? *
> > ? *?????????????????????????????driver_vm_map();
> > ? *?????????????????????????????drm_gpuva_map(gpuvm, va, &op->map);
> > - *?????????????????????????????drm_gpuva_link(va);
> > + *?????????????????????????????drm_gpuva_link(va, vm_bo);
> > ? *
> > ? *?????????????????????????????break;
> > ? *?????????????????????case DRM_GPUVA_OP_REMAP: {
> > @@ -485,11 +509,11 @@
> > ? *?????????????????????????????driver_vm_remap();
> > ? *?????????????????????????????drm_gpuva_remap(prev, next, &op-
> > >remap);
> > ? *
> > - *?????????????????????????????drm_gpuva_unlink(va);
> > ? *?????????????????????????????if (prev)
> > - *?????????????????????????????????????drm_gpuva_link(prev);
> > + *?????????????????????????????????????drm_gpuva_link(prev, va-
> > >vm_bo);
> > ? *?????????????????????????????if (next)
> > - *?????????????????????????????????????drm_gpuva_link(next);
> > + *?????????????????????????????????????drm_gpuva_link(next, va-
> > >vm_bo);
> > + *?????????????????????????????drm_gpuva_unlink(va);
> > ? *
> > ? *?????????????????????????????break;
> > ? *?????????????????????}
> > @@ -505,6 +529,7 @@
> > ? *?????????????????????????????break;
> > ? *?????????????????????}
> > ? *?????????????}
> > + *?????????????drm_gpuvm_bo_put(vm_bo);
> > ? *?????????????driver_unlock_va_space();
> > ? *
> > ? *?????????????return 0;
> > @@ -514,6 +539,7 @@
> > ? *
> > ? *?????struct driver_context {
> > ? *?????????????struct drm_gpuvm *gpuvm;
> > + *?????????????struct drm_gpuvm_bo *vm_bo;
> > ? *?????????????struct drm_gpuva *new_va;
> > ? *?????????????struct drm_gpuva *prev_va;
> > ? *?????????????struct drm_gpuva *next_va;
> > @@ -534,6 +560,7 @@
> > ? *?????????????????????????????? struct drm_gem_object *obj, u64
> > offset)
> > ? *?????{
> > ? *?????????????struct driver_context ctx;
> > + *?????????????struct drm_gpuvm_bo *vm_bo;
> > ? *?????????????struct drm_gpuva_ops *ops;
> > ? *?????????????struct drm_gpuva_op *op;
> > ? *?????????????int ret = 0;
> > @@ -543,16 +570,23 @@
> > ? *?????????????ctx.new_va = kzalloc(sizeof(*ctx.new_va),
> > GFP_KERNEL);
> > ? *?????????????ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
> > GFP_KERNEL);
> > ? *?????????????ctx.next_va = kzalloc(sizeof(*ctx.next_va),
> > GFP_KERNEL);
> > - *?????????????if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
> > + *?????????????ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> > + *?????????????if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
> > !vm_bo) {
> > ? *?????????????????????ret = -ENOMEM;
> > ? *?????????????????????goto out;
> > ? *?????????????}
> > ? *
> > + *?????????????// Typically protected with a driver specific GEM
> > gpuva lock
> > + *?????????????// used in the fence signaling path for
> > drm_gpuva_link() and
> > + *?????????????// drm_gpuva_unlink(), hence pre-allocate.
> > + *?????????????ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
> > + *
> > ? *?????????????driver_lock_va_space();
> > ? *?????????????ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
> > offset);
> > ? *?????????????driver_unlock_va_space();
> > ? *
> > ? *?????out:
> > + *?????????????drm_gpuvm_bo_put(ctx.vm_bo);
> > ? *?????????????kfree(ctx.new_va);
> > ? *?????????????kfree(ctx.prev_va);
> > ? *?????????????kfree(ctx.next_va);
> > @@ -565,7 +599,7 @@
> > ? *
> > ? *?????????????drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
> > ? *
> > - *?????????????drm_gpuva_link(ctx->new_va);
> > + *?????????????drm_gpuva_link(ctx->new_va, ctx->vm_bo);
> > ? *
> > ? *?????????????// prevent the new GPUVA from being freed in
> > ? *?????????????// driver_mapping_create()
> > @@ -577,22 +611,23 @@
> > ? *?????int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
> > ? *?????{
> > ? *?????????????struct driver_context *ctx = __ctx;
> > + *?????????????struct drm_gpuva *va = op->remap.unmap->va;
> > ? *
> > ? *?????????????drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
> > >remap);
> > ? *
> > - *?????????????drm_gpuva_unlink(op->remap.unmap->va);
> > - *?????????????kfree(op->remap.unmap->va);
> > - *
> > ? *?????????????if (op->remap.prev) {
> > - *?????????????????????drm_gpuva_link(ctx->prev_va);
> > + *?????????????????????drm_gpuva_link(ctx->prev_va, va->vm_bo);
> > ? *?????????????????????ctx->prev_va = NULL;
> > ? *?????????????}
> > ? *
> > ? *?????????????if (op->remap.next) {
> > - *?????????????????????drm_gpuva_link(ctx->next_va);
> > + *?????????????????????drm_gpuva_link(ctx->next_va, va->vm_bo);
> > ? *?????????????????????ctx->next_va = NULL;
> > ? *?????????????}
> > ? *
> > + *?????????????drm_gpuva_unlink(va);
> > + *?????????????kfree(va);
> > + *
> > ? *?????????????return 0;
> > ? *?????}
> > ? *
> > @@ -771,6 +806,194 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
> > ?}
> > ?EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
> > ?
> > +/**
> > + * drm_gpuvm_bo_create() - create a new instance of struct
> > drm_gpuvm_bo
> > + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> > + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> > + *
> > + * If provided by the driver, this function uses the &drm_gpuvm_ops
> > + * vm_bo_alloc() callback to allocate.
> > + *
> > + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
>
> Kerneldoc uses "Return:" rather than "Returns:", (This seems to a
> common thing throughout the series).

Gonna fix.

>
> > failure
> > + */
> > +struct drm_gpuvm_bo *
>
> Any particular reason there's line-break after the function type even
> when it fits the ~100 char limit?

Nope, just for consistency thoughout this source file.

>
> > +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> > +?????????????????? struct drm_gem_object *obj)
>
>
>
> > +{
> > +???????const struct drm_gpuvm_ops *ops = gpuvm->ops;
> > +???????struct drm_gpuvm_bo *vm_bo;
> > +
> > +???????if (ops && ops->vm_bo_alloc)
> > +???????????????vm_bo = ops->vm_bo_alloc();
> > +???????else
> > +???????????????vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
> > +
> > +???????if (unlikely(!vm_bo))
> > +???????????????return NULL;
> > +
> > +???????vm_bo->vm = gpuvm;
> > +???????vm_bo->obj = obj;
> > +
> > +???????kref_init(&vm_bo->kref);
> > +???????INIT_LIST_HEAD(&vm_bo->list.gpuva);
> > +???????INIT_LIST_HEAD(&vm_bo->list.entry.gem);
> > +
> > +???????drm_gem_object_get(obj);
>
> Perhaps group this with the vm_bo->obj assignment to emphasize that
> that's the pointer that gets the reference?

Yep, makes sense.

>
> > +
> > +???????return vm_bo;
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
> > +
> > +static void
> > +drm_gpuvm_bo_destroy(struct kref *kref)
> > +{
> > +???????struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
> > drm_gpuvm_bo,
> > +???????????????????????????????????????????????? kref);
> > +???????struct drm_gpuvm *gpuvm = vm_bo->vm;
> > +???????const struct drm_gpuvm_ops *ops = gpuvm->ops;
> > +???????struct drm_gem_object *obj = vm_bo->obj;
> > +???????bool lock = !drm_gpuvm_resv_protected(gpuvm);
> > +
> > +???????drm_gem_gpuva_assert_lock_held(obj);
> > +???????if (!lock)
> > +???????????????drm_gpuvm_resv_assert_held(gpuvm);
> > +
> > +???????list_del(&vm_bo->list.entry.gem);
> > +
> > +???????drm_gem_object_put(obj);
>
> Not sure if we have any drivers utilizing vm_bo_free() yet, but it
> might make sense to move the drm_gem_object_put() until after the
> freeing below, in case vm_bo_free() wants to use it?

Good point, gonna fix.

>
> > +
> > +???????if (ops && ops->vm_bo_free)
> > +???????????????ops->vm_bo_free(vm_bo);
> > +???????else
> > +???????????????kfree(vm_bo);
> > +}
> > +
> > +/**
> > + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
> > + * @vm_bo: the &drm_gpuvm_bo to release the reference of
> > + *
> > + * This releases a reference to @vm_bo.
> > + *
> > + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
> > which
> > + * includes removing it from the GEMs gpuva list. Hence, if a call
> > to this
> > + * function can potentially let the reference count to zero the
> > caller must
> > + * hold the dma-resv or driver specific GEM gpuva lock.
> > + */
> > +void
> > +drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
> > +{
> > +???????if (vm_bo)
> > +???????????????kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
> > +
> > +static struct drm_gpuvm_bo *
> > +__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> > +?????????????????? struct drm_gem_object *obj)
> > +{
> > +???????struct drm_gpuvm_bo *vm_bo;
> > +
> > +???????drm_gem_gpuva_assert_lock_held(obj);
> > +
> > +???????drm_gem_for_each_gpuvm_bo(vm_bo, obj)
> > +???????????????if (vm_bo->vm == gpuvm)
> > +???????????????????????return vm_bo;
> > +
> > +???????return NULL;
> > +}
> > +
> > +/**
> > + * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
> > + * &drm_gpuvm and &drm_gem_object
> > + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> > + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> > + *
> > + * Find the &drm_gpuvm_bo representing the combination of the given
> > + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> > + * count of the &drm_gpuvm_bo accordingly.
> > + *
> > + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
> > failure
> > + */
> > +struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> > +???????????????? struct drm_gem_object *obj)
> > +{
> > +???????struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
> > +
> > +???????return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
> > +
> > +/**
> > + * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo
> > for the
> > + * given &drm_gpuvm and &drm_gem_object
> > + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
> > + * @obj: The &drm_gem_object being mapped in the @gpuvm.
> > + *
> > + * Find the &drm_gpuvm_bo representing the combination of the given
> > + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> > + * count of the &drm_gpuvm_bo accordingly. If not found, allocates a
> > new
> > + * &drm_gpuvm_bo.
> > + *
> > + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> > + *
> > + * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on
> > failure
> > + */
> > +struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> > +?????????????????? struct drm_gem_object *obj)
> > +{
> > +???????struct drm_gpuvm_bo *vm_bo;
> > +
> > +???????vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> > +???????if (vm_bo)
> > +???????????????return vm_bo;
> > +
> > +???????vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
> > +???????if (!vm_bo)
> > +???????????????return ERR_PTR(-ENOMEM);
> > +
> > +???????list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
>
> Lockdep check?

Is inherited by drm_gpuvm_bo_find(), but I can also be explicit here.

>
> > +
> > +???????return vm_bo;
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
> > +
> > +/**
> > + * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the
> > &drm_gpuvm_bo
> > + * for the given &drm_gpuvm and &drm_gem_object
> > + * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
> > + *
> > + * Find the &drm_gpuvm_bo representing the combination of the given
> > + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
> > + * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo
> > reference
> > + * count is decreased. If not found @__vm_bo is returned without
> > further
> > + * increase of the reference count.
> > + *
> > + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
> > + *
> > + * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no
> > existing
> > + * &drm_gpuvm_bo was found
> > + */
> > +struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
> > +{
> > +???????struct drm_gpuvm *gpuvm = __vm_bo->vm;
> > +???????struct drm_gem_object *obj = __vm_bo->obj;
> > +???????struct drm_gpuvm_bo *vm_bo;
> > +
> > +???????vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
> > +???????if (vm_bo) {
> > +???????????????drm_gpuvm_bo_put(__vm_bo);
> > +???????????????return vm_bo;
> > +???????}
> > +
> > +???????list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
>
> Perhaps a lockdep check here?

Same as above.

>
> > +
> > +???????return __vm_bo;
> > +}
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
> > +
> > ?static int
> > ?__drm_gpuva_insert(struct drm_gpuvm *gpuvm,
> > ?????????????????? struct drm_gpuva *va)
> > @@ -860,24 +1083,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
> > ?/**
> > ? * drm_gpuva_link() - link a &drm_gpuva
> > ? * @va: the &drm_gpuva to link
> > + * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
> > ? *
> > - * This adds the given &va to the GPU VA list of the &drm_gem_object
> > it is
> > - * associated with.
> > + * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo
> > and the
> > + * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
> > + *
> > + * For every &drm_gpuva entry added to the &drm_gpuvm_bo an
> > additional
> > + * reference of the latter is taken.
> > ? *
> > ? * This function expects the caller to protect the GEM's GPUVA list
> > against
>
> NIT: Referring to a "gem object" as a "GEM" catches my eye every time.
> Perhaps that has become common practice? With my "it used to be like.."
> hat on, I'd use gem object.

I think this is the way it referred to in a lot of places, e.g. drm_exe.c,
drm_gem.c, etc. Hence, I'd like to stick with that.

>
> > - * concurrent access using the GEMs dma_resv lock.
> > + * concurrent access using either the GEMs dma_resv lock or a driver
> > specific
> > + * lock set through drm_gem_gpuva_set_lock().
> > ? */
> > ?void
> > -drm_gpuva_link(struct drm_gpuva *va)
> > +drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
> > ?{
> > ????????struct drm_gem_object *obj = va->gem.obj;
> > ?
> > ????????if (unlikely(!obj))
> > ????????????????return;
> > ?
> > +???????WARN_ON(obj != vm_bo->obj);
>
> Can we use drm_WARN here?

Sure!

>
> > ????????drm_gem_gpuva_assert_lock_held(obj);
> > ?
> > -???????list_add_tail(&va->gem.entry, &obj->gpuva.list);
> > +???????drm_gpuvm_bo_get(vm_bo);
> > +
> > +???????va->vm_bo = vm_bo;
> > +???????list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
> > ?}
> > ?EXPORT_SYMBOL_GPL(drm_gpuva_link);
> > ?
> > @@ -888,13 +1120,22 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
> > ? * This removes the given &va from the GPU VA list of the
> > &drm_gem_object it is
> > ? * associated with.
> > ? *
> > + * This removes the given &va from the GPU VA list of the
> > &drm_gpuvm_bo and
> > + * the &drm_gpuvm_bo from the &drm_gem_object it is associated with
> > in case
> > + * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
> > + *
> > + * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a
> > reference of
> > + * the latter is dropped.
> > + *
> > ? * This function expects the caller to protect the GEM's GPUVA list
> > against
> > - * concurrent access using the GEMs dma_resv lock.
> > + * concurrent access using either the GEMs dma_resv lock or a driver
> > specific
> > + * lock set through drm_gem_gpuva_set_lock().
> > ? */
> > ?void
> > ?drm_gpuva_unlink(struct drm_gpuva *va)
> > ?{
> > ????????struct drm_gem_object *obj = va->gem.obj;
> > +???????struct drm_gpuvm_bo *vm_bo = va->vm_bo;
> > ?
> > ????????if (unlikely(!obj))
> > ????????????????return;
> > @@ -902,6 +1143,9 @@ drm_gpuva_unlink(struct drm_gpuva *va)
> > ????????drm_gem_gpuva_assert_lock_held(obj);
> > ?
> > ????????list_del_init(&va->gem.entry);
> > +???????va->vm_bo = NULL;
> > +
> > +???????drm_gpuvm_bo_put(vm_bo);
> > ?}
> > ?EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
> > ?
> > @@ -1046,10 +1290,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
> > ????????????????struct drm_gpuva *next,
> > ????????????????struct drm_gpuva_op_remap *op)
> > ?{
> > -???????struct drm_gpuva *curr = op->unmap->va;
> > -???????struct drm_gpuvm *gpuvm = curr->vm;
> > +???????struct drm_gpuva *va = op->unmap->va;
> > +???????struct drm_gpuvm *gpuvm = va->vm;
> > ?
> > -???????drm_gpuva_remove(curr);
> > +???????drm_gpuva_remove(va);
> > ?
> > ????????if (op->prev) {
> > ????????????????drm_gpuva_init_from_op(prev, op->prev);
> > @@ -1693,9 +1937,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> > *gpuvm,
> > ?EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
> > ?
> > ?/**
> > - * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to
> > unmap a GEM
> > - * @gpuvm: the &drm_gpuvm representing the GPU VA space
> > - * @obj: the &drm_gem_object to unmap
> > + * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to
> > unmap a GEM
> > + * @vm_bo: the &drm_gpuvm_bo abstraction
> > ? *
> > ? * This function creates a list of operations to perform unmapping
> > for every
> > ? * GPUVA attached to a GEM.
> > @@ -1712,15 +1955,14 @@
> > EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
> > ? * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR
> > on failure
> > ? */
> > ?struct drm_gpuva_ops *
> > -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> > -????????????????????????????? struct drm_gem_object *obj)
> > +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
> > ?{
> > ????????struct drm_gpuva_ops *ops;
> > ????????struct drm_gpuva_op *op;
> > ????????struct drm_gpuva *va;
> > ????????int ret;
> > ?
> > -???????drm_gem_gpuva_assert_lock_held(obj);
> > +???????drm_gem_gpuva_assert_lock_held(vm_bo->obj);
> > ?
> > ????????ops = kzalloc(sizeof(*ops), GFP_KERNEL);
> > ????????if (!ops)
> > @@ -1728,8 +1970,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm
> > *gpuvm,
> > ?
> > ????????INIT_LIST_HEAD(&ops->list);
> > ?
> > -???????drm_gem_for_each_gpuva(va, obj) {
> > -???????????????op = gpuva_op_alloc(gpuvm);
> > +???????drm_gpuvm_bo_for_each_va(va, vm_bo) {
> > +???????????????op = gpuva_op_alloc(vm_bo->vm);
> > ????????????????if (!op) {
> > ????????????????????????ret = -ENOMEM;
> > ????????????????????????goto err_free_ops;
> > @@ -1743,10 +1985,10 @@ drm_gpuvm_gem_unmap_ops_create(struct
> > drm_gpuvm *gpuvm,
> > ????????return ops;
> > ?
> > ?err_free_ops:
> > -???????drm_gpuva_ops_free(gpuvm, ops);
> > +???????drm_gpuva_ops_free(vm_bo->vm, ops);
> > ????????return ERR_PTR(ret);
> > ?}
> > -EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
> > +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
> > ?
> > ?/**
> > ? * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
> > diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > index 93ad2ba7ec8b..4e46f850e65f 100644
> > --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
> > @@ -62,6 +62,8 @@ struct bind_job_op {
> > ????????enum vm_bind_op op;
> > ????????u32 flags;
> > ?
> > +???????struct drm_gpuvm_bo *vm_bo;
> > +
> > ????????struct {
> > ????????????????u64 addr;
> > ????????????????u64 range;
> > @@ -1113,22 +1115,28 @@ bind_validate_region(struct nouveau_job *job)
> > ?}
> > ?
> > ?static void
> > -bind_link_gpuvas(struct drm_gpuva_ops *ops, struct
> > nouveau_uvma_prealloc *new)
> > +bind_link_gpuvas(struct bind_job_op *bop)
> > ?{
> > +???????struct nouveau_uvma_prealloc *new = &bop->new;
> > +???????struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
> > +???????struct drm_gpuva_ops *ops = bop->ops;
> > ????????struct drm_gpuva_op *op;
> > ?
> > ????????drm_gpuva_for_each_op(op, ops) {
> > ????????????????switch (op->op) {
> > ????????????????case DRM_GPUVA_OP_MAP:
> > -???????????????????????drm_gpuva_link(&new->map->va);
> > +???????????????????????drm_gpuva_link(&new->map->va, vm_bo);
> > ????????????????????????break;
> > -???????????????case DRM_GPUVA_OP_REMAP:
> > +???????????????case DRM_GPUVA_OP_REMAP: {
> > +???????????????????????struct drm_gpuva *va = op->remap.unmap->va;
> > +
> > ????????????????????????if (op->remap.prev)
> > -???????????????????????????????drm_gpuva_link(&new->prev->va);
> > +???????????????????????????????drm_gpuva_link(&new->prev->va, va-
> > >vm_bo);
> > ????????????????????????if (op->remap.next)
> > -???????????????????????????????drm_gpuva_link(&new->next->va);
> > -???????????????????????drm_gpuva_unlink(op->remap.unmap->va);
> > +???????????????????????????????drm_gpuva_link(&new->next->va, va-
> > >vm_bo);
> > +???????????????????????drm_gpuva_unlink(va);
> > ????????????????????????break;
> > +???????????????}
> > ????????????????case DRM_GPUVA_OP_UNMAP:
> > ????????????????????????drm_gpuva_unlink(op->unmap.va);
> > ????????????????????????break;
> > @@ -1150,10 +1158,18 @@ nouveau_uvmm_bind_job_submit(struct
> > nouveau_job *job)
> > ?
> > ????????list_for_each_op(op, &bind_job->ops) {
> > ????????????????if (op->op == OP_MAP) {
> > -???????????????????????op->gem.obj = drm_gem_object_lookup(job-
> > >file_priv,
> > -?????????????????????????????????????????????????????????? op-
> > >gem.handle);
> > -???????????????????????if (!op->gem.obj)
> > +???????????????????????struct drm_gem_object *obj;
> > +
> > +???????????????????????obj = drm_gem_object_lookup(job->file_priv,
> > +?????????????????????????????????????????????????? op->gem.handle);
> > +???????????????????????if (!(op->gem.obj = obj))
> > ????????????????????????????????return -ENOENT;
> > +
> > +???????????????????????dma_resv_lock(obj->resv, NULL);
> > +???????????????????????op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base,
> > obj);
> > +???????????????????????dma_resv_unlock(obj->resv);
> > +???????????????????????if (IS_ERR(op->vm_bo))
> > +???????????????????????????????return PTR_ERR(op->vm_bo);
> > ????????????????}
> > ?
> > ????????????????ret = bind_validate_op(job, op);
> > @@ -1364,7 +1380,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
> > *job)
> > ????????????????case OP_UNMAP_SPARSE:
> > ????????????????case OP_MAP:
> > ????????????????case OP_UNMAP:
> > -???????????????????????bind_link_gpuvas(op->ops, &op->new);
> > +???????????????????????bind_link_gpuvas(op);
> > ????????????????????????break;
> > ????????????????default:
> > ????????????????????????break;
> > @@ -1511,6 +1527,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct
> > work_struct *work)
> > ????????????????if (!IS_ERR_OR_NULL(op->ops))
> > ????????????????????????drm_gpuva_ops_free(&uvmm->base, op->ops);
> > ?
> > +???????????????if (!IS_ERR_OR_NULL(op->vm_bo)) {
> > +???????????????????????dma_resv_lock(obj->resv, NULL);
> > +???????????????????????drm_gpuvm_bo_put(op->vm_bo);
> > +???????????????????????dma_resv_unlock(obj->resv);
> > +???????????????}
> > +
> > ????????????????if (obj)
> > ????????????????????????drm_gem_object_put(obj);
> > ????????}
> > @@ -1776,15 +1798,18 @@ void
> > ?nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem
> > *mem)
> > ?{
> > ????????struct drm_gem_object *obj = &nvbo->bo.base;
> > +???????struct drm_gpuvm_bo *vm_bo;
> > ????????struct drm_gpuva *va;
> > ?
> > ????????dma_resv_assert_held(obj->resv);
> > ?
> > -???????drm_gem_for_each_gpuva(va, obj) {
> > -???????????????struct nouveau_uvma *uvma = uvma_from_va(va);
> > +???????drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> > +???????????????drm_gpuvm_bo_for_each_va(va, vm_bo) {
> > +???????????????????????struct nouveau_uvma *uvma = uvma_from_va(va);
> > ?
> > -???????????????nouveau_uvma_map(uvma, mem);
> > -???????????????drm_gpuva_invalidate(va, false);
> > +???????????????????????nouveau_uvma_map(uvma, mem);
> > +???????????????????????drm_gpuva_invalidate(va, false);
> > +???????????????}
> > ????????}
> > ?}
> > ?
> > @@ -1792,15 +1817,18 @@ void
> > ?nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
> > ?{
> > ????????struct drm_gem_object *obj = &nvbo->bo.base;
> > +???????struct drm_gpuvm_bo *vm_bo;
> > ????????struct drm_gpuva *va;
> > ?
> > ????????dma_resv_assert_held(obj->resv);
> > ?
> > -???????drm_gem_for_each_gpuva(va, obj) {
> > -???????????????struct nouveau_uvma *uvma = uvma_from_va(va);
> > +???????drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
> > +???????????????drm_gpuvm_bo_for_each_va(va, vm_bo) {
> > +???????????????????????struct nouveau_uvma *uvma = uvma_from_va(va);
> > ?
> > -???????????????nouveau_uvma_unmap(uvma);
> > -???????????????drm_gpuva_invalidate(va, true);
> > +???????????????????????nouveau_uvma_unmap(uvma);
> > +???????????????????????drm_gpuva_invalidate(va, true);
> > +???????????????}
> > ????????}
> > ?}
> > ?
> > diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
> > index 16364487fde9..369505447acd 100644
> > --- a/include/drm/drm_gem.h
> > +++ b/include/drm/drm_gem.h
> > @@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
> > ? * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
> > ? * @obj: the &drm_gem_object
> > ? *
> > - * This initializes the &drm_gem_object's &drm_gpuva list.
> > + * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
> > ? *
> > ? * Calling this function is only necessary for drivers intending to
> > support the
> > ? * &drm_driver_feature DRIVER_GEM_GPUVA.
> > @@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct
> > drm_gem_object *obj)
> > ?}
> > ?
> > ?/**
> > - * drm_gem_for_each_gpuva() - iternator to walk over a list of
> > gpuvas
> > - * @entry__: &drm_gpuva structure to assign to in each iteration
> > step
> > - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> > associated with
> > + * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of
> > &drm_gpuvm_bo
> > + * @entry__: &drm_gpuvm_bo structure to assign to in each iteration
> > step
> > + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> > associated with
> > ? *
> > - * This iterator walks over all &drm_gpuva structures associated
> > with the
> > - * &drm_gpuva_manager.
> > + * This iterator walks over all &drm_gpuvm_bo structures associated
> > with the
> > + * &drm_gem_object.
> > ? */
> > -#define drm_gem_for_each_gpuva(entry__, obj__) \
> > -???????list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
> > +#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
> > +???????list_for_each_entry(entry__, &(obj__)->gpuva.list,
> > list.entry.gem)
> > ?
> > ?/**
> > - * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a
> > list of
> > - * gpuvas
> > - * @entry__: &drm_gpuva structure to assign to in each iteration
> > step
> > - * @next__: &next &drm_gpuva to store the next step
> > - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
> > associated with
> > + * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a
> > list of
> > + * &drm_gpuvm_bo
> > + * @entry__: &drm_gpuvm_bostructure to assign to in each iteration
> > step
> > + * @next__: &next &drm_gpuvm_bo to store the next step
> > + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
> > associated with
> > ? *
> > - * This iterator walks over all &drm_gpuva structures associated
> > with the
> > + * This iterator walks over all &drm_gpuvm_bo structures associated
> > with the
> > ? * &drm_gem_object. It is implemented with
> > list_for_each_entry_safe(), hence
> > ? * it is save against removal of elements.
> > ? */
> > -#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
> > -???????list_for_each_entry_safe(entry__, next__, &(obj__)-
> > >gpuva.list, gem.entry)
> > +#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
> > +???????list_for_each_entry_safe(entry__, next__, &(obj__)-
> > >gpuva.list, list.entry.gem)
> > ?
> > ?#endif /* __DRM_GEM_H__ */
> > diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
> > index 13539f32c2e2..ddb0b8d323cf 100644
> > --- a/include/drm/drm_gpuvm.h
> > +++ b/include/drm/drm_gpuvm.h
> > @@ -26,12 +26,14 @@
> > ? */
> > ?
> > ?#include <linux/list.h>
> > +#include <linux/dma-resv.h>
> > ?#include <linux/rbtree.h>
> > ?#include <linux/types.h>
> > ?
> > ?#include <drm/drm_gem.h>
> > ?
> > ?struct drm_gpuvm;
> > +struct drm_gpuvm_bo;
> > ?struct drm_gpuvm_ops;
> > ?
> > ?/**
> > @@ -72,6 +74,12 @@ struct drm_gpuva {
> > ???????? */
> > ????????struct drm_gpuvm *vm;
> > ?
> > +???????/**
> > +??????? * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
> > +??????? * &drm_gem_object
> > +??????? */
> > +???????struct drm_gpuvm_bo *vm_bo;
> > +
> > ????????/**
> > ???????? * @flags: the &drm_gpuva_flags for this mapping
> > ???????? */
> > @@ -107,7 +115,7 @@ struct drm_gpuva {
> > ????????????????struct drm_gem_object *obj;
> > ?
> > ????????????????/**
> > -??????????????? * @entry: the &list_head to attach this object to a
> > &drm_gem_object
> > +??????????????? * @entry: the &list_head to attach this object to a
> > &drm_gpuvm_bo
> > ???????????????? */
> > ????????????????struct list_head entry;
> > ????????} gem;
> > @@ -140,7 +148,7 @@ struct drm_gpuva {
> > ?int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
> > ?void drm_gpuva_remove(struct drm_gpuva *va);
> > ?
> > -void drm_gpuva_link(struct drm_gpuva *va);
> > +void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo
> > *vm_bo);
> > ?void drm_gpuva_unlink(struct drm_gpuva *va);
> > ?
> > ?struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
> > @@ -187,10 +195,16 @@ static inline bool drm_gpuva_invalidated(struct
> > drm_gpuva *va)
> > ? * enum drm_gpuvm_flags - flags for struct drm_gpuvm
> > ? */
> > ?enum drm_gpuvm_flags {
> > +???????/**
> > +??????? * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally
> > by the
> > +??????? * GPUVM's &dma_resv lock
> > +??????? */
> > +???????DRM_GPUVM_RESV_PROTECTED = (1 << 0),
> > +
> > ????????/**
> > ???????? * @DRM_GPUVM_USERBITS: user defined bits
> > ???????? */
> > -???????DRM_GPUVM_USERBITS = (1 << 0),
> > +???????DRM_GPUVM_USERBITS = (1 << 1),
> > ?};
> > ?
> > ?/**
> > @@ -272,6 +286,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm
> > *gpuvm, u64 addr, u64 range);
> > ?struct drm_gem_object *
> > ?drm_gpuvm_root_object_alloc(struct drm_device *drm);
> > ?
> > +/**
> > + * drm_gpuvm_resv_protected() - indicates whether
> > &DRM_GPUVM_RESV_PROTECTED is
> > + * set
> > + * @gpuvm: the &drm_gpuvm
> > + *
> > + * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false
> > otherwise.
> > + */
> > +static inline bool
> > +drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
> > +{
> > +???????return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
> > +}
> > +
> > ?/**
> > ? * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
> > ? * @gpuvm__: the &drm_gpuvm
> > @@ -290,6 +317,12 @@ drm_gpuvm_root_object_alloc(struct drm_device
> > *drm);
> > ? */
> > ?#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
> > ?
> > +#define drm_gpuvm_resv_held(gpuvm__) \
> > +???????dma_resv_held(drm_gpuvm_resv(gpuvm__))
> > +
> > +#define drm_gpuvm_resv_assert_held(gpuvm__) \
> > +???????dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
> > +
> > ?#define drm_gpuvm_resv_held(gpuvm__) \
> > ????????dma_resv_held(drm_gpuvm_resv(gpuvm__))
> > ?
> > @@ -374,6 +407,117 @@ __drm_gpuva_next(struct drm_gpuva *va)
> > ?#define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
> > ????????list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list,
> > rb.entry)
> > ?
> > +/**
> > + * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
> > + * &drm_gem_object combination
> > + *
> > + * This structure is an abstraction representing a &drm_gpuvm and
> > + * &drm_gem_object combination. It serves as an indirection to
> > accelerate
> > + * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
> > + * &drm_gem_object.
> > + *
> > + * Furthermore it is used cache evicted GEM objects for a certain
> > GPU-VM to
> > + * accelerate validation.
> > + *
> > + * Typically, drivers want to create an instance of a struct
> > drm_gpuvm_bo once
> > + * a GEM object is mapped first in a GPU-VM and release the instance
> > once the
> > + * last mapping of the GEM object in this GPU-VM is unmapped.
> > + */
> > +struct drm_gpuvm_bo {
> > +
> > +???????/**
> > +??????? * @vm: The &drm_gpuvm the @obj is mapped in.
> Not refcounted. @vm may potentially be freed under us in the
> !RESV_PROTECTED case.

It's the drivers responsibility to ensure the VM is not freed up as long as
VM_BOs with this VM do exist. However, this is nothing drivers need to care
about explicitly, since drivers are responsible to keep the VM alive as long
as mappings exist anyway. And all mappings being gone implies also all VM_BOs
being gone, since the lifetime of a VM_BO goes from the first mapping backed by
a certain object is created to the last mapping backed by this object is
removed. drm_gpuvm_destroy() will also warn if the GPUVM still contains
mappings.

I will document this more explicitly somewhere.

>
> > +??????? */
> > +???????struct drm_gpuvm *vm;
> > +
> > +???????/**
> > +??????? * @obj: The &drm_gem_object being mapped in @vm.
>
> Refcounted pointer.

Guess you want me to document that.

>
> > +??????? */
> > +???????struct drm_gem_object *obj;
> > +
> > +???????/**
> > +??????? * @kref: The reference count for this &drm_gpuvm_bo.
> > +??????? */
> > +???????struct kref kref;
> > +
> > +???????/**
> > +??????? * @list: Structure containing all &list_heads.
> > +??????? */
> > +???????struct {
> > +???????????????/**
> > +??????????????? * @gpuva: The list of linked &drm_gpuvas.
> > +??????????????? */
>
> Here we should also document how we ensure gpvuas are kept alive.
> (which I presume is the lock protecting the gem object's vm_bo list
> must be held all the time any gpuva obtained from this list is being
> accessed).

Correct, gonna document that.

>
> > +???????????????struct list_head gpuva;
> > +
> > +???????????????/**
> > +??????????????? * @entry: Structure containing all &list_heads
> > serving as
> > +??????????????? * entry.
> > +??????????????? */
> > +???????????????struct {
> > +???????????????????????/**
> > +??????????????????????? * @gem: List entry to attach to the
> > &drm_gem_objects
> > +??????????????????????? * gpuva list.
> > +??????????????????????? */
> > +???????????????????????struct list_head gem;
> > +???????????????} entry;
> > +???????} list;
> > +};
> > +
> > +struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
> > +?????????????????? struct drm_gem_object *obj);
> > +
> > +struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
> > +?????????????????? struct drm_gem_object *obj);
> > +struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *vm_bo);
> > +
> > +/**
> > + * drm_gpuvm_bo_get() - acquire a struct drm_gpuvm_bo reference
> > + * @vm_bo: the &drm_gpuvm_bo to acquire the reference of
> > + *
> > + * This function acquires an additional reference to @vm_bo. It is
> > illegal to
> > + * call this without already holding a reference. No locks required.
> > + */
> > +static inline struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
> > +{
> > +???????kref_get(&vm_bo->kref);
> > +???????return vm_bo;
> > +}
> > +
> > +void drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
> > +
> > +struct drm_gpuvm_bo *
> > +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
> > +???????????????? struct drm_gem_object *obj);
> > +
> > +/**
> > + * drm_gpuvm_bo_for_each_va() - iterator to walk over a list of
> > &drm_gpuva
> > + * @va__: &drm_gpuva structure to assign to in each iteration step
> > + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> > with
> > + *
> > + * This iterator walks over all &drm_gpuva structures associated
> > with the
> > + * &drm_gpuvm_bo.
>
> Must hold the ... lock.

Gonna fix here...

>
>
> > + */
> > +#define drm_gpuvm_bo_for_each_va(va__, vm_bo__) \
> > +???????list_for_each_entry(va__, &(vm_bo)->list.gpuva, gem.entry)
> > +
> > +/**
> > + * drm_gpuvm_bo_for_each_va_safe() - iterator to safely walk over a
> > list of
> > + * &drm_gpuva
> > + * @va__: &drm_gpuva structure to assign to in each iteration step
> > + * @next__: &next &drm_gpuva to store the next step
> > + * @vm_bo__: the &drm_gpuvm_bo the &drm_gpuva to walk are associated
> > with
> > + *
> > + * This iterator walks over all &drm_gpuva structures associated
> > with the
> > + * &drm_gpuvm_bo. It is implemented with list_for_each_entry_safe(),
> > hence
> > + * it is save against removal of elements.
> > + */
> > +#define drm_gpuvm_bo_for_each_va_safe(va__, next__, vm_bo__) \
> > +???????list_for_each_entry_safe(va__, next__, &(vm_bo)->list.gpuva,
> > gem.entry)
>
> Same here.

and here.

>
> > +
> > ?/**
> > ? * enum drm_gpuva_op_type - GPU VA operation type
> > ? *
> > @@ -643,8 +787,7 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
> > *gpuvm,
> > ???????????????????????????????? u64 addr, u64 range);
> > ?
> > ?struct drm_gpuva_ops *
> > -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
> > -????????????????????????????? struct drm_gem_object *obj);
> > +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo);
> > ?
> > ?void drm_gpuva_ops_free(struct drm_gpuvm *gpuvm,
> > ????????????????????????struct drm_gpuva_ops *ops);
> > @@ -688,6 +831,30 @@ struct drm_gpuvm_ops {
> > ???????? */
> > ????????void (*op_free)(struct drm_gpuva_op *op);
> > ?
> > +???????/**
> > +??????? * @vm_bo_alloc: called when the &drm_gpuvm allocates
> > +??????? * a struct drm_gpuvm_bo
> > +??????? *
> > +??????? * Some drivers may want to embed struct drm_gpuvm_bo into
> > driver
> > +??????? * specific structures. By implementing this callback drivers
> > can
> > +??????? * allocate memory accordingly.
> > +??????? *
> > +??????? * This callback is optional.
> > +??????? */
> > +???????struct drm_gpuvm_bo *(*vm_bo_alloc)(void);
> > +
> > +???????/**
> > +??????? * @vm_bo_free: called when the &drm_gpuvm frees a
> > +??????? * struct drm_gpuvm_bo
> > +??????? *
> > +??????? * Some drivers may want to embed struct drm_gpuvm_bo into
> > driver
> > +??????? * specific structures. By implementing this callback drivers
> > can
> > +??????? * free the previously allocated memory accordingly.
> > +??????? *
> > +??????? * This callback is optional.
> > +??????? */
> > +???????void (*vm_bo_free)(struct drm_gpuvm_bo *vm_bo);
> > +
> > ????????/**
> > ???????? * @sm_step_map: called from &drm_gpuvm_sm_map to finally
> > insert the
> > ???????? * mapping once all previous steps were completed
>
> Thanks,
> Thomas
>

2023-10-17 10:56:41

by Thomas Hellström

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 3/6] drm/gpuvm: add an abstraction for a VM / BO combination

Hi,

On 10/17/23 11:58, Danilo Krummrich wrote:
> On Fri, Oct 13, 2023 at 02:30:29PM +0200, Thomas Hellström wrote:
>> On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
>>> Add an abstraction layer between the drm_gpuva mappings of a
>>> particular
>>> drm_gem_object and this GEM object itself. The abstraction represents
>>> a
>>> combination of a drm_gem_object and drm_gpuvm. The drm_gem_object
>>> holds
>>> a list of drm_gpuvm_bo structures (the structure representing this
>>> abstraction), while each drm_gpuvm_bo contains list of mappings of
>>> this
>>> GEM object.
>>>
>>> This has multiple advantages:
>>>
>>> 1) We can use the drm_gpuvm_bo structure to attach it to various
>>> lists
>>>    of the drm_gpuvm. This is useful for tracking external and evicted
>>>    objects per VM, which is introduced in subsequent patches.
>>>
>>> 2) Finding mappings of a certain drm_gem_object mapped in a certain
>>>    drm_gpuvm becomes much cheaper.
>>>
>>> 3) Drivers can derive and extend the structure to easily represent
>>>    driver specific states of a BO for a certain GPUVM.
>>>
>>> The idea of this abstraction was taken from amdgpu, hence the credit
>>> for
>>> this idea goes to the developers of amdgpu.
>>>
>>> Cc: Christian König <[email protected]>
>>> Signed-off-by: Danilo Krummrich <[email protected]>
>>> ---
>>>  drivers/gpu/drm/drm_gpuvm.c            | 332 +++++++++++++++++++++--
>>> --
>>>  drivers/gpu/drm/nouveau/nouveau_uvmm.c |  64 +++--
>>>  include/drm/drm_gem.h                  |  32 +--
>>>  include/drm/drm_gpuvm.h                | 177 ++++++++++++-
>>>  4 files changed, 521 insertions(+), 84 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>>> b/drivers/gpu/drm/drm_gpuvm.c
>>> index 6368dfdbe9dd..28282283ddaf 100644
>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>> @@ -70,6 +70,18 @@
>>>   * &drm_gem_object, such as the &drm_gem_object containing the root
>>> page table,
>>>   * but it can also be a 'dummy' object, which can be allocated with
>>>   * drm_gpuvm_root_object_alloc().
>>> + *
>>> + * In order to connect a struct drm_gpuva its backing
>>> &drm_gem_object each
>> NIT: Same as previous patch regarding kerneldoc references
> I was intentionally using generic references here to make the documentation
> more readable while still keeping references to be able to look up the
> structure's fields.
>
>>> + * &drm_gem_object maintains a list of &drm_gpuvm_bo structures, and
>>> each
>>> + * &drm_gpuvm_bo contains a list of &&drm_gpuva structures.
>>> + *
>>> + * A &drm_gpuvm_bo is an abstraction that represents a combination
>>> of a
>>> + * &drm_gpuvm and a &drm_gem_object. Every such combination should
>>> be unique.
>>> + * This is ensured by the API through drm_gpuvm_bo_obtain() and
>>> + * drm_gpuvm_bo_obtain_prealloc() which first look into the
>>> corresponding
>>> + * &drm_gem_object list of &drm_gpuvm_bos for an existing instance
>>> of this
>>> + * particular combination. If not existent a new instance is created
>>> and linked
>>> + * to the &drm_gem_object.
>>>   */
>>>
>>>  /**
>>> @@ -395,21 +407,28 @@
>>>  /**
>>>   * DOC: Locking
>>>   *
>>> - * Generally, the GPU VA manager does not take care of locking
>>> itself, it is
>>> - * the drivers responsibility to take care about locking. Drivers
>>> might want to
>>> - * protect the following operations: inserting, removing and
>>> iterating
>>> - * &drm_gpuva objects as well as generating all kinds of operations,
>>> such as
>>> - * split / merge or prefetch.
>>> - *
>>> - * The GPU VA manager also does not take care of the locking of the
>>> backing
>>> - * &drm_gem_object buffers GPU VA lists by itself; drivers are
>>> responsible to
>>> - * enforce mutual exclusion using either the GEMs dma_resv lock or
>>> alternatively
>>> - * a driver specific external lock. For the latter see also
>>> - * drm_gem_gpuva_set_lock().
>>> - *
>>> - * However, the GPU VA manager contains lockdep checks to ensure
>>> callers of its
>>> - * API hold the corresponding lock whenever the &drm_gem_objects GPU
>>> VA list is
>>> - * accessed by functions such as drm_gpuva_link() or
>>> drm_gpuva_unlink().
>>> + * In terms of managing &drm_gpuva entries DRM GPUVM does not take
>>> care of
>>> + * locking itself, it is the drivers responsibility to take care
>>> about locking.
>>> + * Drivers might want to protect the following operations:
>>> inserting, removing
>>> + * and iterating &drm_gpuva objects as well as generating all kinds
>>> of
>>> + * operations, such as split / merge or prefetch.
>>> + *
>>> + * DRM GPUVM also does not take care of the locking of the backing
>>> + * &drm_gem_object buffers GPU VA lists and &drm_gpuvm_bo
>>> abstractions by
>>> + * itself; drivers are responsible to enforce mutual exclusion using
>>> either the
>>> + * GEMs dma_resv lock or alternatively a driver specific external
>>> lock. For the
>>> + * latter see also drm_gem_gpuva_set_lock().
>>> + *
>>> + * However, DRM GPUVM contains lockdep checks to ensure callers of
>>> its API hold
>>> + * the corresponding lock whenever the &drm_gem_objects GPU VA list
>>> is accessed
>>> + * by functions such as drm_gpuva_link() or drm_gpuva_unlink(), but
>>> also
>>> + * drm_gpuvm_bo_obtain() and drm_gpuvm_bo_put().
>>> + *
>>> + * The latter is required since on creation and destruction of a
>>> &drm_gpuvm_bo
>>> + * the &drm_gpuvm_bo is attached / removed from the &drm_gem_objects
>>> gpuva list.
>>> + * Subsequent calls to drm_gpuvm_bo_obtain() for the same &drm_gpuvm
>>> and
>>> + * &drm_gem_object must be able to observe previous creations and
>>> destructions
>>> + * of &drm_gpuvm_bos in order to keep instances unique.
>>>   */
>>>
>>>  /**
>>> @@ -439,6 +458,7 @@
>>>   *     {
>>>   *             struct drm_gpuva_ops *ops;
>>>   *             struct drm_gpuva_op *op
>>> + *             struct drm_gpuvm_bo *vm_bo;
>>>   *
>>>   *             driver_lock_va_space();
>>>   *             ops = drm_gpuvm_sm_map_ops_create(gpuvm, addr, range,
>>> @@ -446,6 +466,10 @@
>>>   *             if (IS_ERR(ops))
>>>   *                     return PTR_ERR(ops);
>>>   *
>>> + *             vm_bo = drm_gpuvm_bo_obtain(gpuvm, obj);
>>> + *             if (IS_ERR(vm_bo))
>>> + *                     return PTR_ERR(vm_bo);
>>> + *
>>>   *             drm_gpuva_for_each_op(op, ops) {
>>>   *                     struct drm_gpuva *va;
>>>   *
>>> @@ -458,7 +482,7 @@
>>>   *
>>>   *                             driver_vm_map();
>>>   *                             drm_gpuva_map(gpuvm, va, &op->map);
>>> - *                             drm_gpuva_link(va);
>>> + *                             drm_gpuva_link(va, vm_bo);
>>>   *
>>>   *                             break;
>>>   *                     case DRM_GPUVA_OP_REMAP: {
>>> @@ -485,11 +509,11 @@
>>>   *                             driver_vm_remap();
>>>   *                             drm_gpuva_remap(prev, next, &op-
>>>> remap);
>>>   *
>>> - *                             drm_gpuva_unlink(va);
>>>   *                             if (prev)
>>> - *                                     drm_gpuva_link(prev);
>>> + *                                     drm_gpuva_link(prev, va-
>>>> vm_bo);
>>>   *                             if (next)
>>> - *                                     drm_gpuva_link(next);
>>> + *                                     drm_gpuva_link(next, va-
>>>> vm_bo);
>>> + *                             drm_gpuva_unlink(va);
>>>   *
>>>   *                             break;
>>>   *                     }
>>> @@ -505,6 +529,7 @@
>>>   *                             break;
>>>   *                     }
>>>   *             }
>>> + *             drm_gpuvm_bo_put(vm_bo);
>>>   *             driver_unlock_va_space();
>>>   *
>>>   *             return 0;
>>> @@ -514,6 +539,7 @@
>>>   *
>>>   *     struct driver_context {
>>>   *             struct drm_gpuvm *gpuvm;
>>> + *             struct drm_gpuvm_bo *vm_bo;
>>>   *             struct drm_gpuva *new_va;
>>>   *             struct drm_gpuva *prev_va;
>>>   *             struct drm_gpuva *next_va;
>>> @@ -534,6 +560,7 @@
>>>   *                               struct drm_gem_object *obj, u64
>>> offset)
>>>   *     {
>>>   *             struct driver_context ctx;
>>> + *             struct drm_gpuvm_bo *vm_bo;
>>>   *             struct drm_gpuva_ops *ops;
>>>   *             struct drm_gpuva_op *op;
>>>   *             int ret = 0;
>>> @@ -543,16 +570,23 @@
>>>   *             ctx.new_va = kzalloc(sizeof(*ctx.new_va),
>>> GFP_KERNEL);
>>>   *             ctx.prev_va = kzalloc(sizeof(*ctx.prev_va),
>>> GFP_KERNEL);
>>>   *             ctx.next_va = kzalloc(sizeof(*ctx.next_va),
>>> GFP_KERNEL);
>>> - *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va) {
>>> + *             ctx.vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
>>> + *             if (!ctx.new_va || !ctx.prev_va || !ctx.next_va ||
>>> !vm_bo) {
>>>   *                     ret = -ENOMEM;
>>>   *                     goto out;
>>>   *             }
>>>   *
>>> + *             // Typically protected with a driver specific GEM
>>> gpuva lock
>>> + *             // used in the fence signaling path for
>>> drm_gpuva_link() and
>>> + *             // drm_gpuva_unlink(), hence pre-allocate.
>>> + *             ctx.vm_bo = drm_gpuvm_bo_obtain_prealloc(ctx.vm_bo);
>>> + *
>>>   *             driver_lock_va_space();
>>>   *             ret = drm_gpuvm_sm_map(gpuvm, &ctx, addr, range, obj,
>>> offset);
>>>   *             driver_unlock_va_space();
>>>   *
>>>   *     out:
>>> + *             drm_gpuvm_bo_put(ctx.vm_bo);
>>>   *             kfree(ctx.new_va);
>>>   *             kfree(ctx.prev_va);
>>>   *             kfree(ctx.next_va);
>>> @@ -565,7 +599,7 @@
>>>   *
>>>   *             drm_gpuva_map(ctx->vm, ctx->new_va, &op->map);
>>>   *
>>> - *             drm_gpuva_link(ctx->new_va);
>>> + *             drm_gpuva_link(ctx->new_va, ctx->vm_bo);
>>>   *
>>>   *             // prevent the new GPUVA from being freed in
>>>   *             // driver_mapping_create()
>>> @@ -577,22 +611,23 @@
>>>   *     int driver_gpuva_remap(struct drm_gpuva_op *op, void *__ctx)
>>>   *     {
>>>   *             struct driver_context *ctx = __ctx;
>>> + *             struct drm_gpuva *va = op->remap.unmap->va;
>>>   *
>>>   *             drm_gpuva_remap(ctx->prev_va, ctx->next_va, &op-
>>>> remap);
>>>   *
>>> - *             drm_gpuva_unlink(op->remap.unmap->va);
>>> - *             kfree(op->remap.unmap->va);
>>> - *
>>>   *             if (op->remap.prev) {
>>> - *                     drm_gpuva_link(ctx->prev_va);
>>> + *                     drm_gpuva_link(ctx->prev_va, va->vm_bo);
>>>   *                     ctx->prev_va = NULL;
>>>   *             }
>>>   *
>>>   *             if (op->remap.next) {
>>> - *                     drm_gpuva_link(ctx->next_va);
>>> + *                     drm_gpuva_link(ctx->next_va, va->vm_bo);
>>>   *                     ctx->next_va = NULL;
>>>   *             }
>>>   *
>>> + *             drm_gpuva_unlink(va);
>>> + *             kfree(va);
>>> + *
>>>   *             return 0;
>>>   *     }
>>>   *
>>> @@ -771,6 +806,194 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>  }
>>>  EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>
>>> +/**
>>> + * drm_gpuvm_bo_create() - create a new instance of struct
>>> drm_gpuvm_bo
>>> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
>>> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
>>> + *
>>> + * If provided by the driver, this function uses the &drm_gpuvm_ops
>>> + * vm_bo_alloc() callback to allocate.
>>> + *
>>> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
>> Kerneldoc uses "Return:" rather than "Returns:", (This seems to a
>> common thing throughout the series).
> Gonna fix.
>
>>> failure
>>> + */
>>> +struct drm_gpuvm_bo *
>> Any particular reason there's line-break after the function type even
>> when it fits the ~100 char limit?
> Nope, just for consistency thoughout this source file.
>
>>> +drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
>>> +                   struct drm_gem_object *obj)
>>
>>
>>> +{
>>> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +       struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +       if (ops && ops->vm_bo_alloc)
>>> +               vm_bo = ops->vm_bo_alloc();
>>> +       else
>>> +               vm_bo = kzalloc(sizeof(*vm_bo), GFP_KERNEL);
>>> +
>>> +       if (unlikely(!vm_bo))
>>> +               return NULL;
>>> +
>>> +       vm_bo->vm = gpuvm;
>>> +       vm_bo->obj = obj;
>>> +
>>> +       kref_init(&vm_bo->kref);
>>> +       INIT_LIST_HEAD(&vm_bo->list.gpuva);
>>> +       INIT_LIST_HEAD(&vm_bo->list.entry.gem);
>>> +
>>> +       drm_gem_object_get(obj);
>> Perhaps group this with the vm_bo->obj assignment to emphasize that
>> that's the pointer that gets the reference?
> Yep, makes sense.
>
>>> +
>>> +       return vm_bo;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_create);
>>> +
>>> +static void
>>> +drm_gpuvm_bo_destroy(struct kref *kref)
>>> +{
>>> +       struct drm_gpuvm_bo *vm_bo = container_of(kref, struct
>>> drm_gpuvm_bo,
>>> +                                                 kref);
>>> +       struct drm_gpuvm *gpuvm = vm_bo->vm;
>>> +       const struct drm_gpuvm_ops *ops = gpuvm->ops;
>>> +       struct drm_gem_object *obj = vm_bo->obj;
>>> +       bool lock = !drm_gpuvm_resv_protected(gpuvm);
>>> +
>>> +       drm_gem_gpuva_assert_lock_held(obj);
>>> +       if (!lock)
>>> +               drm_gpuvm_resv_assert_held(gpuvm);
>>> +
>>> +       list_del(&vm_bo->list.entry.gem);
>>> +
>>> +       drm_gem_object_put(obj);
>> Not sure if we have any drivers utilizing vm_bo_free() yet, but it
>> might make sense to move the drm_gem_object_put() until after the
>> freeing below, in case vm_bo_free() wants to use it?
> Good point, gonna fix.
>
>>> +
>>> +       if (ops && ops->vm_bo_free)
>>> +               ops->vm_bo_free(vm_bo);
>>> +       else
>>> +               kfree(vm_bo);
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_put() - drop a struct drm_gpuvm_bo reference
>>> + * @vm_bo: the &drm_gpuvm_bo to release the reference of
>>> + *
>>> + * This releases a reference to @vm_bo.
>>> + *
>>> + * If the reference count drops to zero, the &gpuvm_bo is destroyed,
>>> which
>>> + * includes removing it from the GEMs gpuva list. Hence, if a call
>>> to this
>>> + * function can potentially let the reference count to zero the
>>> caller must
>>> + * hold the dma-resv or driver specific GEM gpuva lock.
>>> + */
>>> +void
>>> +drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
>>> +{
>>> +       if (vm_bo)
>>> +               kref_put(&vm_bo->kref, drm_gpuvm_bo_destroy);
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
>>> +
>>> +static struct drm_gpuvm_bo *
>>> +__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
>>> +                   struct drm_gem_object *obj)
>>> +{
>>> +       struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +       drm_gem_gpuva_assert_lock_held(obj);
>>> +
>>> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj)
>>> +               if (vm_bo->vm == gpuvm)
>>> +                       return vm_bo;
>>> +
>>> +       return NULL;
>>> +}
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_find() - find the &drm_gpuvm_bo for the given
>>> + * &drm_gpuvm and &drm_gem_object
>>> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
>>> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
>>> + *
>>> + * Find the &drm_gpuvm_bo representing the combination of the given
>>> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
>>> + * count of the &drm_gpuvm_bo accordingly.
>>> + *
>>> + * Returns: a pointer to the &drm_gpuvm_bo on success, NULL on
>>> failure
>>> + */
>>> +struct drm_gpuvm_bo *
>>> +drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
>>> +                 struct drm_gem_object *obj)
>>> +{
>>> +       struct drm_gpuvm_bo *vm_bo = __drm_gpuvm_bo_find(gpuvm, obj);
>>> +
>>> +       return vm_bo ? drm_gpuvm_bo_get(vm_bo) : NULL;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_find);
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_obtain() - obtains and instance of the &drm_gpuvm_bo
>>> for the
>>> + * given &drm_gpuvm and &drm_gem_object
>>> + * @gpuvm: The &drm_gpuvm the @obj is mapped in.
>>> + * @obj: The &drm_gem_object being mapped in the @gpuvm.
>>> + *
>>> + * Find the &drm_gpuvm_bo representing the combination of the given
>>> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
>>> + * count of the &drm_gpuvm_bo accordingly. If not found, allocates a
>>> new
>>> + * &drm_gpuvm_bo.
>>> + *
>>> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
>>> + *
>>> + * Returns: a pointer to the &drm_gpuvm_bo on success, an ERR_PTR on
>>> failure
>>> + */
>>> +struct drm_gpuvm_bo *
>>> +drm_gpuvm_bo_obtain(struct drm_gpuvm *gpuvm,
>>> +                   struct drm_gem_object *obj)
>>> +{
>>> +       struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
>>> +       if (vm_bo)
>>> +               return vm_bo;
>>> +
>>> +       vm_bo = drm_gpuvm_bo_create(gpuvm, obj);
>>> +       if (!vm_bo)
>>> +               return ERR_PTR(-ENOMEM);
>>> +
>>> +       list_add_tail(&vm_bo->list.entry.gem, &obj->gpuva.list);
>> Lockdep check?
> Is inherited by drm_gpuvm_bo_find(), but I can also be explicit here.
>
>>> +
>>> +       return vm_bo;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain);
>>> +
>>> +/**
>>> + * drm_gpuvm_bo_obtain_prealloc() - obtains and instance of the
>>> &drm_gpuvm_bo
>>> + * for the given &drm_gpuvm and &drm_gem_object
>>> + * @__vm_bo: A pre-allocated struct drm_gpuvm_bo.
>>> + *
>>> + * Find the &drm_gpuvm_bo representing the combination of the given
>>> + * &drm_gpuvm and &drm_gem_object. If found, increases the reference
>>> + * count of the found &drm_gpuvm_bo accordingly, while the @__vm_bo
>>> reference
>>> + * count is decreased. If not found @__vm_bo is returned without
>>> further
>>> + * increase of the reference count.
>>> + *
>>> + * A new &drm_gpuvm_bo is added to the GEMs gpuva list.
>>> + *
>>> + * Returns: a pointer to the found &drm_gpuvm_bo or @__vm_bo if no
>>> existing
>>> + * &drm_gpuvm_bo was found
>>> + */
>>> +struct drm_gpuvm_bo *
>>> +drm_gpuvm_bo_obtain_prealloc(struct drm_gpuvm_bo *__vm_bo)
>>> +{
>>> +       struct drm_gpuvm *gpuvm = __vm_bo->vm;
>>> +       struct drm_gem_object *obj = __vm_bo->obj;
>>> +       struct drm_gpuvm_bo *vm_bo;
>>> +
>>> +       vm_bo = drm_gpuvm_bo_find(gpuvm, obj);
>>> +       if (vm_bo) {
>>> +               drm_gpuvm_bo_put(__vm_bo);
>>> +               return vm_bo;
>>> +       }
>>> +
>>> +       list_add_tail(&__vm_bo->list.entry.gem, &obj->gpuva.list);
>> Perhaps a lockdep check here?
> Same as above.
>
>>> +
>>> +       return __vm_bo;
>>> +}
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_obtain_prealloc);
>>> +
>>>  static int
>>>  __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
>>>                    struct drm_gpuva *va)
>>> @@ -860,24 +1083,33 @@ EXPORT_SYMBOL_GPL(drm_gpuva_remove);
>>>  /**
>>>   * drm_gpuva_link() - link a &drm_gpuva
>>>   * @va: the &drm_gpuva to link
>>> + * @vm_bo: the &drm_gpuvm_bo to add the &drm_gpuva to
>>>   *
>>> - * This adds the given &va to the GPU VA list of the &drm_gem_object
>>> it is
>>> - * associated with.
>>> + * This adds the given &va to the GPU VA list of the &drm_gpuvm_bo
>>> and the
>>> + * &drm_gpuvm_bo to the &drm_gem_object it is associated with.
>>> + *
>>> + * For every &drm_gpuva entry added to the &drm_gpuvm_bo an
>>> additional
>>> + * reference of the latter is taken.
>>>   *
>>>   * This function expects the caller to protect the GEM's GPUVA list
>>> against
>> NIT: Referring to a "gem object" as a "GEM" catches my eye every time.
>> Perhaps that has become common practice? With my "it used to be like.."
>> hat on, I'd use gem object.
> I think this is the way it referred to in a lot of places, e.g. drm_exe.c,
> drm_gem.c, etc. Hence, I'd like to stick with that.
>
>>> - * concurrent access using the GEMs dma_resv lock.
>>> + * concurrent access using either the GEMs dma_resv lock or a driver
>>> specific
>>> + * lock set through drm_gem_gpuva_set_lock().
>>>   */
>>>  void
>>> -drm_gpuva_link(struct drm_gpuva *va)
>>> +drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo)
>>>  {
>>>         struct drm_gem_object *obj = va->gem.obj;
>>>
>>>         if (unlikely(!obj))
>>>                 return;
>>>
>>> +       WARN_ON(obj != vm_bo->obj);
>> Can we use drm_WARN here?
> Sure!
>
>>>         drm_gem_gpuva_assert_lock_held(obj);
>>>
>>> -       list_add_tail(&va->gem.entry, &obj->gpuva.list);
>>> +       drm_gpuvm_bo_get(vm_bo);
>>> +
>>> +       va->vm_bo = vm_bo;
>>> +       list_add_tail(&va->gem.entry, &vm_bo->list.gpuva);
>>>  }
>>>  EXPORT_SYMBOL_GPL(drm_gpuva_link);
>>>
>>> @@ -888,13 +1120,22 @@ EXPORT_SYMBOL_GPL(drm_gpuva_link);
>>>   * This removes the given &va from the GPU VA list of the
>>> &drm_gem_object it is
>>>   * associated with.
>>>   *
>>> + * This removes the given &va from the GPU VA list of the
>>> &drm_gpuvm_bo and
>>> + * the &drm_gpuvm_bo from the &drm_gem_object it is associated with
>>> in case
>>> + * this call unlinks the last &drm_gpuva from the &drm_gpuvm_bo.
>>> + *
>>> + * For every &drm_gpuva entry removed from the &drm_gpuvm_bo a
>>> reference of
>>> + * the latter is dropped.
>>> + *
>>>   * This function expects the caller to protect the GEM's GPUVA list
>>> against
>>> - * concurrent access using the GEMs dma_resv lock.
>>> + * concurrent access using either the GEMs dma_resv lock or a driver
>>> specific
>>> + * lock set through drm_gem_gpuva_set_lock().
>>>   */
>>>  void
>>>  drm_gpuva_unlink(struct drm_gpuva *va)
>>>  {
>>>         struct drm_gem_object *obj = va->gem.obj;
>>> +       struct drm_gpuvm_bo *vm_bo = va->vm_bo;
>>>
>>>         if (unlikely(!obj))
>>>                 return;
>>> @@ -902,6 +1143,9 @@ drm_gpuva_unlink(struct drm_gpuva *va)
>>>         drm_gem_gpuva_assert_lock_held(obj);
>>>
>>>         list_del_init(&va->gem.entry);
>>> +       va->vm_bo = NULL;
>>> +
>>> +       drm_gpuvm_bo_put(vm_bo);
>>>  }
>>>  EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
>>>
>>> @@ -1046,10 +1290,10 @@ drm_gpuva_remap(struct drm_gpuva *prev,
>>>                 struct drm_gpuva *next,
>>>                 struct drm_gpuva_op_remap *op)
>>>  {
>>> -       struct drm_gpuva *curr = op->unmap->va;
>>> -       struct drm_gpuvm *gpuvm = curr->vm;
>>> +       struct drm_gpuva *va = op->unmap->va;
>>> +       struct drm_gpuvm *gpuvm = va->vm;
>>>
>>> -       drm_gpuva_remove(curr);
>>> +       drm_gpuva_remove(va);
>>>
>>>         if (op->prev) {
>>>                 drm_gpuva_init_from_op(prev, op->prev);
>>> @@ -1693,9 +1937,8 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm
>>> *gpuvm,
>>>  EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>>>
>>>  /**
>>> - * drm_gpuvm_gem_unmap_ops_create() - creates the &drm_gpuva_ops to
>>> unmap a GEM
>>> - * @gpuvm: the &drm_gpuvm representing the GPU VA space
>>> - * @obj: the &drm_gem_object to unmap
>>> + * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to
>>> unmap a GEM
>>> + * @vm_bo: the &drm_gpuvm_bo abstraction
>>>   *
>>>   * This function creates a list of operations to perform unmapping
>>> for every
>>>   * GPUVA attached to a GEM.
>>> @@ -1712,15 +1955,14 @@
>>> EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
>>>   * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR
>>> on failure
>>>   */
>>>  struct drm_gpuva_ops *
>>> -drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm *gpuvm,
>>> -                              struct drm_gem_object *obj)
>>> +drm_gpuvm_bo_unmap_ops_create(struct drm_gpuvm_bo *vm_bo)
>>>  {
>>>         struct drm_gpuva_ops *ops;
>>>         struct drm_gpuva_op *op;
>>>         struct drm_gpuva *va;
>>>         int ret;
>>>
>>> -       drm_gem_gpuva_assert_lock_held(obj);
>>> +       drm_gem_gpuva_assert_lock_held(vm_bo->obj);
>>>
>>>         ops = kzalloc(sizeof(*ops), GFP_KERNEL);
>>>         if (!ops)
>>> @@ -1728,8 +1970,8 @@ drm_gpuvm_gem_unmap_ops_create(struct drm_gpuvm
>>> *gpuvm,
>>>
>>>         INIT_LIST_HEAD(&ops->list);
>>>
>>> -       drm_gem_for_each_gpuva(va, obj) {
>>> -               op = gpuva_op_alloc(gpuvm);
>>> +       drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>> +               op = gpuva_op_alloc(vm_bo->vm);
>>>                 if (!op) {
>>>                         ret = -ENOMEM;
>>>                         goto err_free_ops;
>>> @@ -1743,10 +1985,10 @@ drm_gpuvm_gem_unmap_ops_create(struct
>>> drm_gpuvm *gpuvm,
>>>         return ops;
>>>
>>>  err_free_ops:
>>> -       drm_gpuva_ops_free(gpuvm, ops);
>>> +       drm_gpuva_ops_free(vm_bo->vm, ops);
>>>         return ERR_PTR(ret);
>>>  }
>>> -EXPORT_SYMBOL_GPL(drm_gpuvm_gem_unmap_ops_create);
>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap_ops_create);
>>>
>>>  /**
>>>   * drm_gpuva_ops_free() - free the given &drm_gpuva_ops
>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> index 93ad2ba7ec8b..4e46f850e65f 100644
>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>> @@ -62,6 +62,8 @@ struct bind_job_op {
>>>         enum vm_bind_op op;
>>>         u32 flags;
>>>
>>> +       struct drm_gpuvm_bo *vm_bo;
>>> +
>>>         struct {
>>>                 u64 addr;
>>>                 u64 range;
>>> @@ -1113,22 +1115,28 @@ bind_validate_region(struct nouveau_job *job)
>>>  }
>>>
>>>  static void
>>> -bind_link_gpuvas(struct drm_gpuva_ops *ops, struct
>>> nouveau_uvma_prealloc *new)
>>> +bind_link_gpuvas(struct bind_job_op *bop)
>>>  {
>>> +       struct nouveau_uvma_prealloc *new = &bop->new;
>>> +       struct drm_gpuvm_bo *vm_bo = bop->vm_bo;
>>> +       struct drm_gpuva_ops *ops = bop->ops;
>>>         struct drm_gpuva_op *op;
>>>
>>>         drm_gpuva_for_each_op(op, ops) {
>>>                 switch (op->op) {
>>>                 case DRM_GPUVA_OP_MAP:
>>> -                       drm_gpuva_link(&new->map->va);
>>> +                       drm_gpuva_link(&new->map->va, vm_bo);
>>>                         break;
>>> -               case DRM_GPUVA_OP_REMAP:
>>> +               case DRM_GPUVA_OP_REMAP: {
>>> +                       struct drm_gpuva *va = op->remap.unmap->va;
>>> +
>>>                         if (op->remap.prev)
>>> -                               drm_gpuva_link(&new->prev->va);
>>> +                               drm_gpuva_link(&new->prev->va, va-
>>>> vm_bo);
>>>                         if (op->remap.next)
>>> -                               drm_gpuva_link(&new->next->va);
>>> -                       drm_gpuva_unlink(op->remap.unmap->va);
>>> +                               drm_gpuva_link(&new->next->va, va-
>>>> vm_bo);
>>> +                       drm_gpuva_unlink(va);
>>>                         break;
>>> +               }
>>>                 case DRM_GPUVA_OP_UNMAP:
>>>                         drm_gpuva_unlink(op->unmap.va);
>>>                         break;
>>> @@ -1150,10 +1158,18 @@ nouveau_uvmm_bind_job_submit(struct
>>> nouveau_job *job)
>>>
>>>         list_for_each_op(op, &bind_job->ops) {
>>>                 if (op->op == OP_MAP) {
>>> -                       op->gem.obj = drm_gem_object_lookup(job-
>>>> file_priv,
>>> -                                                           op-
>>>> gem.handle);
>>> -                       if (!op->gem.obj)
>>> +                       struct drm_gem_object *obj;
>>> +
>>> +                       obj = drm_gem_object_lookup(job->file_priv,
>>> +                                                   op->gem.handle);
>>> +                       if (!(op->gem.obj = obj))
>>>                                 return -ENOENT;
>>> +
>>> +                       dma_resv_lock(obj->resv, NULL);
>>> +                       op->vm_bo = drm_gpuvm_bo_obtain(&uvmm->base,
>>> obj);
>>> +                       dma_resv_unlock(obj->resv);
>>> +                       if (IS_ERR(op->vm_bo))
>>> +                               return PTR_ERR(op->vm_bo);
>>>                 }
>>>
>>>                 ret = bind_validate_op(job, op);
>>> @@ -1364,7 +1380,7 @@ nouveau_uvmm_bind_job_submit(struct nouveau_job
>>> *job)
>>>                 case OP_UNMAP_SPARSE:
>>>                 case OP_MAP:
>>>                 case OP_UNMAP:
>>> -                       bind_link_gpuvas(op->ops, &op->new);
>>> +                       bind_link_gpuvas(op);
>>>                         break;
>>>                 default:
>>>                         break;
>>> @@ -1511,6 +1527,12 @@ nouveau_uvmm_bind_job_free_work_fn(struct
>>> work_struct *work)
>>>                 if (!IS_ERR_OR_NULL(op->ops))
>>>                         drm_gpuva_ops_free(&uvmm->base, op->ops);
>>>
>>> +               if (!IS_ERR_OR_NULL(op->vm_bo)) {
>>> +                       dma_resv_lock(obj->resv, NULL);
>>> +                       drm_gpuvm_bo_put(op->vm_bo);
>>> +                       dma_resv_unlock(obj->resv);
>>> +               }
>>> +
>>>                 if (obj)
>>>                         drm_gem_object_put(obj);
>>>         }
>>> @@ -1776,15 +1798,18 @@ void
>>>  nouveau_uvmm_bo_map_all(struct nouveau_bo *nvbo, struct nouveau_mem
>>> *mem)
>>>  {
>>>         struct drm_gem_object *obj = &nvbo->bo.base;
>>> +       struct drm_gpuvm_bo *vm_bo;
>>>         struct drm_gpuva *va;
>>>
>>>         dma_resv_assert_held(obj->resv);
>>>
>>> -       drm_gem_for_each_gpuva(va, obj) {
>>> -               struct nouveau_uvma *uvma = uvma_from_va(va);
>>> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
>>> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>>>
>>> -               nouveau_uvma_map(uvma, mem);
>>> -               drm_gpuva_invalidate(va, false);
>>> +                       nouveau_uvma_map(uvma, mem);
>>> +                       drm_gpuva_invalidate(va, false);
>>> +               }
>>>         }
>>>  }
>>>
>>> @@ -1792,15 +1817,18 @@ void
>>>  nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
>>>  {
>>>         struct drm_gem_object *obj = &nvbo->bo.base;
>>> +       struct drm_gpuvm_bo *vm_bo;
>>>         struct drm_gpuva *va;
>>>
>>>         dma_resv_assert_held(obj->resv);
>>>
>>> -       drm_gem_for_each_gpuva(va, obj) {
>>> -               struct nouveau_uvma *uvma = uvma_from_va(va);
>>> +       drm_gem_for_each_gpuvm_bo(vm_bo, obj) {
>>> +               drm_gpuvm_bo_for_each_va(va, vm_bo) {
>>> +                       struct nouveau_uvma *uvma = uvma_from_va(va);
>>>
>>> -               nouveau_uvma_unmap(uvma);
>>> -               drm_gpuva_invalidate(va, true);
>>> +                       nouveau_uvma_unmap(uvma);
>>> +                       drm_gpuva_invalidate(va, true);
>>> +               }
>>>         }
>>>  }
>>>
>>> diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
>>> index 16364487fde9..369505447acd 100644
>>> --- a/include/drm/drm_gem.h
>>> +++ b/include/drm/drm_gem.h
>>> @@ -580,7 +580,7 @@ int drm_gem_evict(struct drm_gem_object *obj);
>>>   * drm_gem_gpuva_init() - initialize the gpuva list of a GEM object
>>>   * @obj: the &drm_gem_object
>>>   *
>>> - * This initializes the &drm_gem_object's &drm_gpuva list.
>>> + * This initializes the &drm_gem_object's &drm_gpuvm_bo list.
>>>   *
>>>   * Calling this function is only necessary for drivers intending to
>>> support the
>>>   * &drm_driver_feature DRIVER_GEM_GPUVA.
>>> @@ -593,28 +593,28 @@ static inline void drm_gem_gpuva_init(struct
>>> drm_gem_object *obj)
>>>  }
>>>
>>>  /**
>>> - * drm_gem_for_each_gpuva() - iternator to walk over a list of
>>> gpuvas
>>> - * @entry__: &drm_gpuva structure to assign to in each iteration
>>> step
>>> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
>>> associated with
>>> + * drm_gem_for_each_gpuvm_bo() - iterator to walk over a list of
>>> &drm_gpuvm_bo
>>> + * @entry__: &drm_gpuvm_bo structure to assign to in each iteration
>>> step
>>> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
>>> associated with
>>>   *
>>> - * This iterator walks over all &drm_gpuva structures associated
>>> with the
>>> - * &drm_gpuva_manager.
>>> + * This iterator walks over all &drm_gpuvm_bo structures associated
>>> with the
>>> + * &drm_gem_object.
>>>   */
>>> -#define drm_gem_for_each_gpuva(entry__, obj__) \
>>> -       list_for_each_entry(entry__, &(obj__)->gpuva.list, gem.entry)
>>> +#define drm_gem_for_each_gpuvm_bo(entry__, obj__) \
>>> +       list_for_each_entry(entry__, &(obj__)->gpuva.list,
>>> list.entry.gem)
>>>
>>>  /**
>>> - * drm_gem_for_each_gpuva_safe() - iternator to safely walk over a
>>> list of
>>> - * gpuvas
>>> - * @entry__: &drm_gpuva structure to assign to in each iteration
>>> step
>>> - * @next__: &next &drm_gpuva to store the next step
>>> - * @obj__: the &drm_gem_object the &drm_gpuvas to walk are
>>> associated with
>>> + * drm_gem_for_each_gpuvm_bo_safe() - iterator to safely walk over a
>>> list of
>>> + * &drm_gpuvm_bo
>>> + * @entry__: &drm_gpuvm_bostructure to assign to in each iteration
>>> step
>>> + * @next__: &next &drm_gpuvm_bo to store the next step
>>> + * @obj__: the &drm_gem_object the &drm_gpuvm_bo to walk are
>>> associated with
>>>   *
>>> - * This iterator walks over all &drm_gpuva structures associated
>>> with the
>>> + * This iterator walks over all &drm_gpuvm_bo structures associated
>>> with the
>>>   * &drm_gem_object. It is implemented with
>>> list_for_each_entry_safe(), hence
>>>   * it is save against removal of elements.
>>>   */
>>> -#define drm_gem_for_each_gpuva_safe(entry__, next__, obj__) \
>>> -       list_for_each_entry_safe(entry__, next__, &(obj__)-
>>>> gpuva.list, gem.entry)
>>> +#define drm_gem_for_each_gpuvm_bo_safe(entry__, next__, obj__) \
>>> +       list_for_each_entry_safe(entry__, next__, &(obj__)-
>>>> gpuva.list, list.entry.gem)
>>>
>>>  #endif /* __DRM_GEM_H__ */
>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>> index 13539f32c2e2..ddb0b8d323cf 100644
>>> --- a/include/drm/drm_gpuvm.h
>>> +++ b/include/drm/drm_gpuvm.h
>>> @@ -26,12 +26,14 @@
>>>   */
>>>
>>>  #include <linux/list.h>
>>> +#include <linux/dma-resv.h>
>>>  #include <linux/rbtree.h>
>>>  #include <linux/types.h>
>>>
>>>  #include <drm/drm_gem.h>
>>>
>>>  struct drm_gpuvm;
>>> +struct drm_gpuvm_bo;
>>>  struct drm_gpuvm_ops;
>>>
>>>  /**
>>> @@ -72,6 +74,12 @@ struct drm_gpuva {
>>>          */
>>>         struct drm_gpuvm *vm;
>>>
>>> +       /**
>>> +        * @vm_bo: the &drm_gpuvm_bo abstraction for the mapped
>>> +        * &drm_gem_object
>>> +        */
>>> +       struct drm_gpuvm_bo *vm_bo;
>>> +
>>>         /**
>>>          * @flags: the &drm_gpuva_flags for this mapping
>>>          */
>>> @@ -107,7 +115,7 @@ struct drm_gpuva {
>>>                 struct drm_gem_object *obj;
>>>
>>>                 /**
>>> -                * @entry: the &list_head to attach this object to a
>>> &drm_gem_object
>>> +                * @entry: the &list_head to attach this object to a
>>> &drm_gpuvm_bo
>>>                  */
>>>                 struct list_head entry;
>>>         } gem;
>>> @@ -140,7 +148,7 @@ struct drm_gpuva {
>>>  int drm_gpuva_insert(struct drm_gpuvm *gpuvm, struct drm_gpuva *va);
>>>  void drm_gpuva_remove(struct drm_gpuva *va);
>>>
>>> -void drm_gpuva_link(struct drm_gpuva *va);
>>> +void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo
>>> *vm_bo);
>>>  void drm_gpuva_unlink(struct drm_gpuva *va);
>>>
>>>  struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
>>> @@ -187,10 +195,16 @@ static inline bool drm_gpuva_invalidated(struct
>>> drm_gpuva *va)
>>>   * enum drm_gpuvm_flags - flags for struct drm_gpuvm
>>>   */
>>>  enum drm_gpuvm_flags {
>>> +       /**
>>> +        * @DRM_GPUVM_RESV_PROTECTED: GPUVM is protected externally
>>> by the
>>> +        * GPUVM's &dma_resv lock
>>> +        */
>>> +       DRM_GPUVM_RESV_PROTECTED = (1 << 0),
>>> +
>>>         /**
>>>          * @DRM_GPUVM_USERBITS: user defined bits
>>>          */
>>> -       DRM_GPUVM_USERBITS = (1 << 0),
>>> +       DRM_GPUVM_USERBITS = (1 << 1),
>>>  };
>>>
>>>  /**
>>> @@ -272,6 +286,19 @@ bool drm_gpuvm_interval_empty(struct drm_gpuvm
>>> *gpuvm, u64 addr, u64 range);
>>>  struct drm_gem_object *
>>>  drm_gpuvm_root_object_alloc(struct drm_device *drm);
>>>
>>> +/**
>>> + * drm_gpuvm_resv_protected() - indicates whether
>>> &DRM_GPUVM_RESV_PROTECTED is
>>> + * set
>>> + * @gpuvm: the &drm_gpuvm
>>> + *
>>> + * Returns: true if &DRM_GPUVM_RESV_PROTECTED is set, false
>>> otherwise.
>>> + */
>>> +static inline bool
>>> +drm_gpuvm_resv_protected(struct drm_gpuvm *gpuvm)
>>> +{
>>> +       return gpuvm->flags & DRM_GPUVM_RESV_PROTECTED;
>>> +}
>>> +
>>>  /**
>>>   * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
>>>   * @gpuvm__: the &drm_gpuvm
>>> @@ -290,6 +317,12 @@ drm_gpuvm_root_object_alloc(struct drm_device
>>> *drm);
>>>   */
>>>  #define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
>>>
>>> +#define drm_gpuvm_resv_held(gpuvm__) \
>>> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
>>> +
>>> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
>>> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
>>> +
>>>  #define drm_gpuvm_resv_held(gpuvm__) \
>>>         dma_resv_held(drm_gpuvm_resv(gpuvm__))
>>>
>>> @@ -374,6 +407,117 @@ __drm_gpuva_next(struct drm_gpuva *va)
>>>  #define drm_gpuvm_for_each_va_safe(va__, next__, gpuvm__) \
>>>         list_for_each_entry_safe(va__, next__, &(gpuvm__)->rb.list,
>>> rb.entry)
>>>
>>> +/**
>>> + * struct drm_gpuvm_bo - structure representing a &drm_gpuvm and
>>> + * &drm_gem_object combination
>>> + *
>>> + * This structure is an abstraction representing a &drm_gpuvm and
>>> + * &drm_gem_object combination. It serves as an indirection to
>>> accelerate
>>> + * iterating all &drm_gpuvas within a &drm_gpuvm backed by the same
>>> + * &drm_gem_object.
>>> + *
>>> + * Furthermore it is used cache evicted GEM objects for a certain
>>> GPU-VM to
>>> + * accelerate validation.
>>> + *
>>> + * Typically, drivers want to create an instance of a struct
>>> drm_gpuvm_bo once
>>> + * a GEM object is mapped first in a GPU-VM and release the instance
>>> once the
>>> + * last mapping of the GEM object in this GPU-VM is unmapped.
>>> + */
>>> +struct drm_gpuvm_bo {
>>> +
>>> +       /**
>>> +        * @vm: The &drm_gpuvm the @obj is mapped in.
>> Not refcounted. @vm may potentially be freed under us in the
>> !RESV_PROTECTED case.
> It's the drivers responsibility to ensure the VM is not freed up as long as
> VM_BOs with this VM do exist. However, this is nothing drivers need to care
> about explicitly, since drivers are responsible to keep the VM alive as long
> as mappings exist anyway. And all mappings being gone implies also all VM_BOs
> being gone, since the lifetime of a VM_BO goes from the first mapping backed by
> a certain object is created to the last mapping backed by this object is
> removed. drm_gpuvm_destroy() will also warn if the GPUVM still contains
> mappings.
>
> I will document this more explicitly somewhere.
>
>>> +        */
>>> +       struct drm_gpuvm *vm;
>>> +
>>> +       /**
>>> +        * @obj: The &drm_gem_object being mapped in @vm.
>> Refcounted pointer.
> Guess you want me to document that.

Yes, that'd be good. Especially with internal lower-level locking it
will become crucial to know where we have strong vs weak referencing so
we know where to upgrade with kref_get_unless_zero().

Thanks,

Thomas


2023-10-17 13:19:45

by Danilo Krummrich

[permalink] [raw]
Subject: Re: [PATCH drm-misc-next v6 1/6] drm/gpuvm: add common dma-resv per struct drm_gpuvm

On 10/13/23 15:00, Thomas Hellström wrote:
> On Fri, 2023-10-13 at 13:51 +0200, Danilo Krummrich wrote:
>> On 10/13/23 13:38, Thomas Hellström wrote:
>>> On Mon, 2023-10-09 at 01:32 +0200, Danilo Krummrich wrote:
>>>> Provide a common dma-resv for GEM objects not being used outside
>>>> of
>>>> this
>>>> GPU-VM. This is used in a subsequent patch to generalize dma-
>>>> resv,
>>>> external and evicted object handling and GEM validation.
>>>>
>>>> Signed-off-by: Danilo Krummrich <[email protected]>
>>>> ---
>>>>   drivers/gpu/drm/drm_gpuvm.c            | 56
>>>> +++++++++++++++++++++++++-
>>>>   drivers/gpu/drm/nouveau/nouveau_uvmm.c | 13 +++++-
>>>>   include/drm/drm_gpuvm.h                | 35 +++++++++++++++-
>>>>   3 files changed, 99 insertions(+), 5 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/drm_gpuvm.c
>>>> b/drivers/gpu/drm/drm_gpuvm.c
>>>> index 02ecb45a2544..ebda9d594165 100644
>>>> --- a/drivers/gpu/drm/drm_gpuvm.c
>>>> +++ b/drivers/gpu/drm/drm_gpuvm.c
>>>> @@ -61,6 +61,15 @@
>>>>    * contained within struct drm_gpuva already. Hence, for
>>>> inserting
>>>> &drm_gpuva
>>>>    * entries from within dma-fence signalling critical sections
>>>> it is
>>>> enough to
>>>>    * pre-allocate the &drm_gpuva structures.
>>>> + *
>>>> + * &drm_gem_objects which are private to a single VM can share a
>>>> common
>>>> + * &dma_resv in order to improve locking efficiency (e.g. with
>>>> &drm_exec).
>>>> + * For this purpose drivers must pass a &drm_gem_object to
>>>> drm_gpuvm_init(), in
>>>> + * the following called 'root object', which serves as the
>>>> container
>>>
>>> Nit: Perhaps resv object altough it might typically be the root
>>> page-
>>> table object, that doesn't have any meaning to drm_gpuvm, which
>>> uses it
>>> solely as a container for the resv?
>>
>> With "root" I didn't want to refer to the object representing the
>> root
>> page-table object, but being *the* object every other (internal)
>> object
>> needs to keep a reference to.
>
> OK, yes but the reason they need a reference is because of the shared
> resv, so IMO resv_object is a good fit. (I later noticed there's even
> the function name drm_gpuvm_resv_obj()). And it will probably get
> confused with the driver's "root" page table object, but up to you.

Yeah, maybe 'resv object' is better, gonna change it.

>
>> Maybe I should be more explicit here and say
>> that drivers need to make sure every internal object requires a
>> reference
>> to take a reference to this root object.
>>
>>>
>>>> of the
>>>> + * GPUVM's shared &dma_resv. This root object can be a driver
>>>> specific
>>>> + * &drm_gem_object, such as the &drm_gem_object containing the
>>>> root
>>>> page table,
>>>> + * but it can also be a 'dummy' object, which can be allocated
>>>> with
>>>> + * drm_gpuvm_root_object_alloc().
>>>>    */
>>>>
>>>>   /**
>>>> @@ -652,9 +661,47 @@ drm_gpuvm_range_valid(struct drm_gpuvm
>>>> *gpuvm,
>>>>                 !drm_gpuvm_in_kernel_node(gpuvm, addr, range);
>>>>   }
>>>>
>>>> +static void
>>>> +drm_gpuvm_gem_object_free(struct drm_gem_object *obj)
>>>> +{
>>>> +       drm_gem_object_release(obj);
>>>> +       kfree(obj);
>>>> +}
>>>> +
>>>> +static const struct drm_gem_object_funcs drm_gpuvm_object_funcs
>>>> = {
>>>> +       .free = drm_gpuvm_gem_object_free,
>>>> +};
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_root_object_alloc() - allocate a dummy
>>>> &drm_gem_object
>>>> + * @drm: the drivers &drm_device
>>>> + *
>>>> + * Allocates a dummy &drm_gem_object which can be passed to
>>>> drm_gpuvm_init() in
>>>> + * order to serve as root GEM object providing the &drm_resv
>>>> shared
>>>> across
>>>> + * &drm_gem_objects local to a single GPUVM.
>>>> + *
>>>> + * Returns: the &drm_gem_object on success, NULL on failure
>>>> + */
>>>> +struct drm_gem_object *
>>>> +drm_gpuvm_root_object_alloc(struct drm_device *drm)
>>>> +{
>>>> +       struct drm_gem_object *obj;
>>>> +
>>>> +       obj = kzalloc(sizeof(*obj), GFP_KERNEL);
>>>> +       if (!obj)
>>>> +               return NULL;
>>>> +
>>>> +       obj->funcs = &drm_gpuvm_object_funcs;
>>>> +       drm_gem_private_object_init(drm, obj, 0);
>>>> +
>>>> +       return obj;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(drm_gpuvm_root_object_alloc);
>>>> +
>>>>   /**
>>>>    * drm_gpuvm_init() - initialize a &drm_gpuvm
>>>>    * @gpuvm: pointer to the &drm_gpuvm to initialize
>>>> + * @r_obj: the root &drm_gem_object providing the GPUVM's common
>>>> &dma_resv
>>>>    * @name: the name of the GPU VA space
>>>>    * @start_offset: the start offset of the GPU VA space
>>>>    * @range: the size of the GPU VA space
>>>> @@ -668,7 +715,7 @@ drm_gpuvm_range_valid(struct drm_gpuvm
>>>> *gpuvm,
>>>>    * &name is expected to be managed by the surrounding driver
>>>> structures.
>>>>    */
>>>>   void
>>>> -drm_gpuvm_init(struct drm_gpuvm *gpuvm,
>>>> +drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct drm_gem_object
>>>> *r_obj,
>>>>                 const char *name,
>>>>                 u64 start_offset, u64 range,
>>>>                 u64 reserve_offset, u64 reserve_range,
>>>> @@ -683,6 +730,9 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm,
>>>>
>>>>          gpuvm->name = name ? name : "unknown";
>>>>          gpuvm->ops = ops;
>>>> +       gpuvm->r_obj = r_obj;
>>>> +
>>>> +       drm_gem_object_get(r_obj);
>>>>
>>>>          memset(&gpuvm->kernel_alloc_node, 0, sizeof(struct
>>>> drm_gpuva));
>>>>
>>>> @@ -713,7 +763,9 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
>>>>                  __drm_gpuva_remove(&gpuvm->kernel_alloc_node);
>>>>
>>>>          WARN(!RB_EMPTY_ROOT(&gpuvm->rb.tree.rb_root),
>>>> -            "GPUVA tree is not empty, potentially leaking
>>>> memory.");
>>>> +            "GPUVA tree is not empty, potentially leaking
>>>> memory.\n");
>>>
>>> Should we cache the drm device in struct drm_gpuvm and use
>>> drm_warn()
>>> here instead of WARN?
>>
>> I'd guess the additional backtrace of WARN() isn't overly useful in
>> this
>> case. However, it might be a bit more obvious in dmesg due to its
>> verboseness. Not a strong opinion on that, though.
>
> My bad. I meant drm_WARN(). In a multi-gpu environment it's nice to
> have the extra device info.

Sure, gonna add a separate patch to change that in all places.

>
> /Thomas
>
>
>>
>>>
>>>> +
>>>> +       drm_gem_object_put(gpuvm->r_obj);
>>>>   }
>>>>   EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
>>>>
>>>> diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> index 5cf892c50f43..4dea847ef989 100644
>>>> --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c
>>>> @@ -1808,8 +1808,9 @@ int
>>>>   nouveau_uvmm_init(struct nouveau_uvmm *uvmm, struct nouveau_cli
>>>> *cli,
>>>>                    u64 kernel_managed_addr, u64
>>>> kernel_managed_size)
>>>>   {
>>>> -       int ret;
>>>> +       struct drm_gem_object *r_obj;
>>>>          u64 kernel_managed_end = kernel_managed_addr +
>>>> kernel_managed_size;
>>>> +       int ret;
>>>>
>>>>          mutex_init(&uvmm->mutex);
>>>>          dma_resv_init(&uvmm->resv);
>>>> @@ -1833,14 +1834,22 @@ nouveau_uvmm_init(struct nouveau_uvmm
>>>> *uvmm,
>>>> struct nouveau_cli *cli,
>>>>                  goto out_unlock;
>>>>          }
>>>>
>>>> +       r_obj = drm_gpuvm_root_object_alloc(cli->drm->dev);
>>>> +       if (!r_obj) {
>>>> +               ret = -ENOMEM;
>>>> +               goto out_unlock;
>>>> +       }
>>>> +
>>>>          uvmm->kernel_managed_addr = kernel_managed_addr;
>>>>          uvmm->kernel_managed_size = kernel_managed_size;
>>>>
>>>> -       drm_gpuvm_init(&uvmm->base, cli->name,
>>>> +       drm_gpuvm_init(&uvmm->base, r_obj, cli->name,
>>>>                         NOUVEAU_VA_SPACE_START,
>>>>                         NOUVEAU_VA_SPACE_END,
>>>>                         kernel_managed_addr, kernel_managed_size,
>>>>                         NULL);
>>>> +       /* GPUVM takes care from here on. */
>>>> +       drm_gem_object_put(r_obj);
>>>>
>>>>          ret = nvif_vmm_ctor(&cli->mmu, "uvmm",
>>>>                              cli->vmm.vmm.object.oclass, RAW,
>>>> diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
>>>> index c7ed6bf441d4..0aec14d8b259 100644
>>>> --- a/include/drm/drm_gpuvm.h
>>>> +++ b/include/drm/drm_gpuvm.h
>>>> @@ -238,9 +238,15 @@ struct drm_gpuvm {
>>>>           * @ops: &drm_gpuvm_ops providing the split/merge steps
>>>> to
>>>> drivers
>>>>           */
>>>>          const struct drm_gpuvm_ops *ops;
>>>> +
>>>> +       /**
>>>> +        * @r_obj: Root GEM object; representing the GPUVM's
>>>> common
>>>> &dma_resv.
>>>> +        */
>>>> +       struct drm_gem_object *r_obj;
>>>>   };
>>>>
>>>> -void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
>>>> +void drm_gpuvm_init(struct drm_gpuvm *gpuvm, struct
>>>> drm_gem_object
>>>> *r_obj,
>>>> +                   const char *name,
>>>>                      u64 start_offset, u64 range,
>>>>                      u64 reserve_offset, u64 reserve_range,
>>>>                      const struct drm_gpuvm_ops *ops);
>>>> @@ -248,6 +254,33 @@ void drm_gpuvm_destroy(struct drm_gpuvm
>>>> *gpuvm);
>>>>
>>>>   bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64
>>>> addr, u64
>>>> range);
>>>>
>>>> +struct drm_gem_object *
>>>> +drm_gpuvm_root_object_alloc(struct drm_device *drm);
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_resv() - returns the &drm_gpuvm's &dma_resv
>>>> + * @gpuvm__: the &drm_gpuvm
>>>> + *
>>>> + * Returns: a pointer to the &drm_gpuvm's shared &dma_resv
>>>> + */
>>>> +#define drm_gpuvm_resv(gpuvm__) ((gpuvm__)->r_obj->resv)
>>>> +
>>>> +/**
>>>> + * drm_gpuvm_resv_obj() - returns the &drm_gem_object holding
>>>> the
>>>> &drm_gpuvm's
>>>> + * &dma_resv
>>>> + * @gpuvm__: the &drm_gpuvm
>>>> + *
>>>> + * Returns: a pointer to the &drm_gem_object holding the
>>>> &drm_gpuvm's shared
>>>> + * &dma_resv
>>>> + */
>>>> +#define drm_gpuvm_resv_obj(gpuvm__) ((gpuvm__)->r_obj)
>>>> +
>>>> +#define drm_gpuvm_resv_held(gpuvm__) \
>>>> +       dma_resv_held(drm_gpuvm_resv(gpuvm__))
>>>> +
>>>> +#define drm_gpuvm_resv_assert_held(gpuvm__) \
>>>> +       dma_resv_assert_held(drm_gpuvm_resv(gpuvm__))
>>>> +
>>>>   static inline struct drm_gpuva *
>>>>   __drm_gpuva_next(struct drm_gpuva *va)
>>>>   {
>>>
>>> Reviewed-by: Thomas Hellström <[email protected]>
>>>
>>>
>>
>