drm/gpuvm: reference count drm_gpuvm structures
authorDanilo Krummrich <dakr@redhat.com>
Wed, 8 Nov 2023 00:12:39 +0000 (01:12 +0100)
committerDanilo Krummrich <dakr@redhat.com>
Mon, 13 Nov 2023 17:19:06 +0000 (18:19 +0100)
Implement reference counting for struct drm_gpuvm.

Acked-by: Christian König <christian.koenig@amd.com>
Reviewed-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Signed-off-by: Danilo Krummrich <dakr@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20231108001259.15123-10-dakr@redhat.com
drivers/gpu/drm/drm_gpuvm.c
drivers/gpu/drm/nouveau/nouveau_uvmm.c
include/drm/drm_gpuvm.h

index 53e2c406fb04e3a194779b6506c75b4e2129b65c..ef968eba6fe6341e5514d3118136869b90a9e2d4 100644 (file)
@@ -746,6 +746,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
        gpuvm->rb.tree = RB_ROOT_CACHED;
        INIT_LIST_HEAD(&gpuvm->rb.list);
 
+       kref_init(&gpuvm->kref);
+
        gpuvm->name = name ? name : "unknown";
        gpuvm->flags = flags;
        gpuvm->ops = ops;
@@ -770,15 +772,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_init);
 
-/**
- * drm_gpuvm_destroy() - cleanup a &drm_gpuvm
- * @gpuvm: pointer to the &drm_gpuvm to clean up
- *
- * Note that it is a bug to call this function on a manager that still
- * holds GPU VA mappings.
- */
-void
-drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
+static void
+drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
 {
        gpuvm->name = NULL;
 
@@ -790,7 +785,35 @@ drm_gpuvm_destroy(struct drm_gpuvm *gpuvm)
 
        drm_gem_object_put(gpuvm->r_obj);
 }
-EXPORT_SYMBOL_GPL(drm_gpuvm_destroy);
+
+static void
+drm_gpuvm_free(struct kref *kref)
+{
+       struct drm_gpuvm *gpuvm = container_of(kref, struct drm_gpuvm, kref);
+
+       drm_gpuvm_fini(gpuvm);
+
+       if (drm_WARN_ON(gpuvm->drm, !gpuvm->ops->vm_free))
+               return;
+
+       gpuvm->ops->vm_free(gpuvm);
+}
+
+/**
+ * drm_gpuvm_put() - drop a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to release the reference of
+ *
+ * This releases a reference to @gpuvm.
+ *
+ * This function may be called from atomic context.
+ */
+void
+drm_gpuvm_put(struct drm_gpuvm *gpuvm)
+{
+       if (gpuvm)
+               kref_put(&gpuvm->kref, drm_gpuvm_free);
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_put);
 
 static int
 __drm_gpuva_insert(struct drm_gpuvm *gpuvm,
@@ -839,11 +862,21 @@ drm_gpuva_insert(struct drm_gpuvm *gpuvm,
 {
        u64 addr = va->va.addr;
        u64 range = va->va.range;
+       int ret;
 
        if (unlikely(!drm_gpuvm_range_valid(gpuvm, addr, range)))
                return -EINVAL;
 
-       return __drm_gpuva_insert(gpuvm, va);
+       ret = __drm_gpuva_insert(gpuvm, va);
+       if (likely(!ret))
+               /* Take a reference of the GPUVM for the successfully inserted
+                * drm_gpuva. We can't take the reference in
+                * __drm_gpuva_insert() itself, since we don't want to increse
+                * the reference count for the GPUVM's kernel_alloc_node.
+                */
+               drm_gpuvm_get(gpuvm);
+
+       return ret;
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_insert);
 
@@ -876,6 +909,7 @@ drm_gpuva_remove(struct drm_gpuva *va)
        }
 
        __drm_gpuva_remove(va);
+       drm_gpuvm_put(va->vm);
 }
 EXPORT_SYMBOL_GPL(drm_gpuva_remove);
 
index 54be12c1272f28c88c546e8b9730fe32fbdbd8ce..cb2f06565c4620298f8d62423d87953e0daeb73b 100644 (file)
@@ -1780,6 +1780,18 @@ nouveau_uvmm_bo_unmap_all(struct nouveau_bo *nvbo)
        }
 }
 
+static void
+nouveau_uvmm_free(struct drm_gpuvm *gpuvm)
+{
+       struct nouveau_uvmm *uvmm = uvmm_from_gpuvm(gpuvm);
+
+       kfree(uvmm);
+}
+
+static const struct drm_gpuvm_ops gpuvm_ops = {
+       .vm_free = nouveau_uvmm_free,
+};
+
 int
 nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
                           void *data,
@@ -1830,7 +1842,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
                       NOUVEAU_VA_SPACE_END,
                       init->kernel_managed_addr,
                       init->kernel_managed_size,
-                      NULL);
+                      &gpuvm_ops);
        /* GPUVM takes care from here on. */
        drm_gem_object_put(r_obj);
 
@@ -1849,8 +1861,7 @@ nouveau_uvmm_ioctl_vm_init(struct drm_device *dev,
        return 0;
 
 out_gpuvm_fini:
-       drm_gpuvm_destroy(&uvmm->base);
-       kfree(uvmm);
+       drm_gpuvm_put(&uvmm->base);
 out_unlock:
        mutex_unlock(&cli->mutex);
        return ret;
@@ -1902,7 +1913,6 @@ nouveau_uvmm_fini(struct nouveau_uvmm *uvmm)
 
        mutex_lock(&cli->mutex);
        nouveau_vmm_fini(&uvmm->vmm);
-       drm_gpuvm_destroy(&uvmm->base);
-       kfree(uvmm);
+       drm_gpuvm_put(&uvmm->base);
        mutex_unlock(&cli->mutex);
 }
index 0c2e24155a93d7caa2c7b1112b2df3265089a2bd..4e6e1fd3485ade3d782e6178a72b19a4eb08264a 100644 (file)
@@ -247,6 +247,11 @@ struct drm_gpuvm {
                struct list_head list;
        } rb;
 
+       /**
+        * @kref: reference count of this object
+        */
+       struct kref kref;
+
        /**
         * @kernel_alloc_node:
         *
@@ -273,7 +278,23 @@ void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
                    u64 start_offset, u64 range,
                    u64 reserve_offset, u64 reserve_range,
                    const struct drm_gpuvm_ops *ops);
-void drm_gpuvm_destroy(struct drm_gpuvm *gpuvm);
+
+/**
+ * drm_gpuvm_get() - acquire a struct drm_gpuvm reference
+ * @gpuvm: the &drm_gpuvm to acquire the reference of
+ *
+ * This function acquires an additional reference to @gpuvm. It is illegal to
+ * call this without already holding a reference. No locks required.
+ */
+static inline struct drm_gpuvm *
+drm_gpuvm_get(struct drm_gpuvm *gpuvm)
+{
+       kref_get(&gpuvm->kref);
+
+       return gpuvm;
+}
+
+void drm_gpuvm_put(struct drm_gpuvm *gpuvm);
 
 bool drm_gpuvm_range_valid(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
 bool drm_gpuvm_interval_empty(struct drm_gpuvm *gpuvm, u64 addr, u64 range);
@@ -673,6 +694,14 @@ static inline void drm_gpuva_init_from_op(struct drm_gpuva *va,
  * operations to drivers.
  */
 struct drm_gpuvm_ops {
+       /**
+        * @vm_free: called when the last reference of a struct drm_gpuvm is
+        * dropped
+        *
+        * This callback is mandatory.
+        */
+       void (*vm_free)(struct drm_gpuvm *gpuvm);
+
        /**
         * @op_alloc: called when the &drm_gpuvm allocates
         * a struct drm_gpuva_op