Skip to content

Commit 3ecfa22

Browse files
Darksonnjannau
authored andcommitted
drm/gpuvm: add deferred vm_bo cleanup
When using GPUVM in immediate mode, it is necessary to call drm_gpuvm_unlink() from the fence signalling critical path. However, unlink may call drm_gpuvm_bo_put(), which causes some challenges: 1. drm_gpuvm_bo_put() often requires you to take resv locks, which you can't do from the fence signalling critical path. 2. drm_gpuvm_bo_put() calls drm_gem_object_put(), which is often going to be unsafe to call from the fence signalling critical path. To solve these issues, add a deferred version of drm_gpuvm_unlink() that adds the vm_bo to a deferred cleanup list, and then clean it up later. The new methods take the GEMs GPUVA lock internally rather than letting the caller do it because it also needs to perform an operation after releasing the mutex again. This is to prevent freeing the GEM while holding the mutex (more info as comments in the patch). This means that the new methods can only be used with DRM_GPUVM_IMMEDIATE_MODE. Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com> Acked-by: Danilo Krummrich <dakr@kernel.org> Link: https://lore.kernel.org/r/20251006-vmbo-defer-v4-1-30cbd2c05adb@google.com [aliceryhl: fix formatting of vm_bo = llist_entry(...) line] Signed-off-by: Alice Ryhl <aliceryhl@google.com>
1 parent 2518202 commit 3ecfa22

2 files changed

Lines changed: 206 additions & 0 deletions

File tree

drivers/gpu/drm/drm_gpuvm.c

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,31 @@ __drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
812812
cond_spin_unlock(lock, !!lock);
813813
}
814814

815+
/**
816+
* drm_gpuvm_bo_is_zombie() - check whether this vm_bo is scheduled for cleanup
817+
* @vm_bo: the &drm_gpuvm_bo
818+
*
819+
* When a vm_bo is scheduled for cleanup using the bo_defer list, it is not
820+
* immediately removed from the evict and extobj lists. Therefore, anyone
821+
* iterating these lists should skip entries that are being destroyed.
822+
*
823+
* Checking the refcount without incrementing it is okay as long as the lock
824+
* protecting the evict/extobj list is held for as long as you are using the
825+
* vm_bo, because even if the refcount hits zero while you are using it, freeing
826+
* the vm_bo requires taking the list's lock.
827+
*
828+
* Zombie entries can be observed on the evict and extobj lists regardless of
829+
* whether DRM_GPUVM_RESV_PROTECTED is used, but they remain on the lists for a
830+
* longer time when the resv lock is used because we can't take the resv lock
831+
* during run_job() in immediate mode, meaning that they need to remain on the
832+
* lists until drm_gpuvm_bo_deferred_cleanup() is called.
833+
*/
834+
static bool
835+
drm_gpuvm_bo_is_zombie(struct drm_gpuvm_bo *vm_bo)
836+
{
837+
return !kref_read(&vm_bo->kref);
838+
}
839+
815840
/**
816841
* drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
817842
* @__vm_bo: the &drm_gpuvm_bo
@@ -1017,6 +1042,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
10171042
INIT_LIST_HEAD(&gpuvm->evict.list);
10181043
spin_lock_init(&gpuvm->evict.lock);
10191044

1045+
init_llist_head(&gpuvm->bo_defer);
1046+
10201047
kref_init(&gpuvm->kref);
10211048

10221049
gpuvm->name = name ? name : "unknown";
@@ -1058,6 +1085,8 @@ drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
10581085
"Extobj list should be empty.\n");
10591086
drm_WARN(gpuvm->drm, !list_empty(&gpuvm->evict.list),
10601087
"Evict list should be empty.\n");
1088+
drm_WARN(gpuvm->drm, !llist_empty(&gpuvm->bo_defer),
1089+
"VM BO cleanup list should be empty.\n");
10611090

10621091
drm_gem_object_put(gpuvm->r_obj);
10631092
}
@@ -1153,6 +1182,9 @@ drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
11531182

11541183
drm_gpuvm_resv_assert_held(gpuvm);
11551184
list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
1185+
if (drm_gpuvm_bo_is_zombie(vm_bo))
1186+
continue;
1187+
11561188
ret = exec_prepare_obj(exec, vm_bo->obj, num_fences);
11571189
if (ret)
11581190
break;
@@ -1396,6 +1428,9 @@ drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
13961428

13971429
list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
13981430
list.entry.evict) {
1431+
if (drm_gpuvm_bo_is_zombie(vm_bo))
1432+
continue;
1433+
13991434
ret = ops->vm_bo_validate(vm_bo, exec);
14001435
if (ret)
14011436
break;
@@ -1496,6 +1531,7 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
14961531

14971532
INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
14981533
INIT_LIST_HEAD(&vm_bo->list.entry.evict);
1534+
init_llist_node(&vm_bo->list.entry.bo_defer);
14991535

15001536
return vm_bo;
15011537
}
@@ -1557,6 +1593,126 @@ drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
15571593
}
15581594
EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
15591595

1596+
/*
1597+
* drm_gpuvm_bo_into_zombie() - called when the vm_bo becomes a zombie due to
1598+
* deferred cleanup
1599+
*
1600+
* If deferred cleanup is used, then this must be called right after the vm_bo
1601+
* refcount drops to zero. Must be called with GEM mutex held. After releasing
1602+
* the GEM mutex, drm_gpuvm_bo_defer_zombie_cleanup() must be called.
1603+
*/
1604+
static void
1605+
drm_gpuvm_bo_into_zombie(struct kref *kref)
1606+
{
1607+
struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
1608+
kref);
1609+
1610+
if (!drm_gpuvm_resv_protected(vm_bo->vm)) {
1611+
drm_gpuvm_bo_list_del(vm_bo, extobj, true);
1612+
drm_gpuvm_bo_list_del(vm_bo, evict, true);
1613+
}
1614+
1615+
list_del(&vm_bo->list.entry.gem);
1616+
}
1617+
1618+
/*
1619+
* drm_gpuvm_bo_defer_zombie_cleanup() - adds a new zombie vm_bo to the
1620+
* bo_defer list
1621+
*
1622+
* Called after drm_gpuvm_bo_into_zombie(). GEM mutex must not be held.
1623+
*
1624+
* It's important that the GEM stays alive for the duration in which we hold
1625+
* the mutex, but the instant we add the vm_bo to bo_defer, another thread
1626+
* might call drm_gpuvm_bo_deferred_cleanup() and put the GEM. Therefore, to
1627+
* avoid kfreeing a mutex we are holding, the GEM mutex must be released
1628+
* *before* calling this function.
1629+
*/
1630+
static void
1631+
drm_gpuvm_bo_defer_zombie_cleanup(struct drm_gpuvm_bo *vm_bo)
1632+
{
1633+
llist_add(&vm_bo->list.entry.bo_defer, &vm_bo->vm->bo_defer);
1634+
}
1635+
1636+
static void
1637+
drm_gpuvm_bo_defer_free(struct kref *kref)
1638+
{
1639+
struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
1640+
kref);
1641+
1642+
drm_gpuvm_bo_into_zombie(kref);
1643+
mutex_unlock(&vm_bo->obj->gpuva.lock);
1644+
drm_gpuvm_bo_defer_zombie_cleanup(vm_bo);
1645+
}
1646+
1647+
/**
1648+
* drm_gpuvm_bo_put_deferred() - drop a struct drm_gpuvm_bo reference with
1649+
* deferred cleanup
1650+
* @vm_bo: the &drm_gpuvm_bo to release the reference of
1651+
*
1652+
* This releases a reference to @vm_bo.
1653+
*
1654+
* This might take and release the GEMs GPUVA lock. You should call
1655+
* drm_gpuvm_bo_deferred_cleanup() later to complete the cleanup process.
1656+
*
1657+
* Returns: true if vm_bo is being destroyed, false otherwise.
1658+
*/
1659+
bool
1660+
drm_gpuvm_bo_put_deferred(struct drm_gpuvm_bo *vm_bo)
1661+
{
1662+
if (!vm_bo)
1663+
return false;
1664+
1665+
drm_WARN_ON(vm_bo->vm->drm, !drm_gpuvm_immediate_mode(vm_bo->vm));
1666+
1667+
return !!kref_put_mutex(&vm_bo->kref,
1668+
drm_gpuvm_bo_defer_free,
1669+
&vm_bo->obj->gpuva.lock);
1670+
}
1671+
EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put_deferred);
1672+
1673+
/**
1674+
* drm_gpuvm_bo_deferred_cleanup() - clean up BOs in the deferred list
1675+
* deferred cleanup
1676+
* @gpuvm: the VM to clean up
1677+
*
1678+
* Cleans up &drm_gpuvm_bo instances in the deferred cleanup list.
1679+
*/
1680+
void
1681+
drm_gpuvm_bo_deferred_cleanup(struct drm_gpuvm *gpuvm)
1682+
{
1683+
const struct drm_gpuvm_ops *ops = gpuvm->ops;
1684+
struct drm_gpuvm_bo *vm_bo;
1685+
struct drm_gem_object *obj;
1686+
struct llist_node *bo_defer;
1687+
1688+
bo_defer = llist_del_all(&gpuvm->bo_defer);
1689+
if (!bo_defer)
1690+
return;
1691+
1692+
if (drm_gpuvm_resv_protected(gpuvm)) {
1693+
dma_resv_lock(drm_gpuvm_resv(gpuvm), NULL);
1694+
llist_for_each_entry(vm_bo, bo_defer, list.entry.bo_defer) {
1695+
drm_gpuvm_bo_list_del(vm_bo, extobj, false);
1696+
drm_gpuvm_bo_list_del(vm_bo, evict, false);
1697+
}
1698+
dma_resv_unlock(drm_gpuvm_resv(gpuvm));
1699+
}
1700+
1701+
while (bo_defer) {
1702+
vm_bo = llist_entry(bo_defer, struct drm_gpuvm_bo, list.entry.bo_defer);
1703+
bo_defer = bo_defer->next;
1704+
obj = vm_bo->obj;
1705+
if (ops && ops->vm_bo_free)
1706+
ops->vm_bo_free(vm_bo);
1707+
else
1708+
kfree(vm_bo);
1709+
1710+
drm_gpuvm_put(gpuvm);
1711+
drm_gem_object_put(obj);
1712+
}
1713+
}
1714+
EXPORT_SYMBOL_GPL(drm_gpuvm_bo_deferred_cleanup);
1715+
15601716
static struct drm_gpuvm_bo *
15611717
__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
15621718
struct drm_gem_object *obj)
@@ -1884,6 +2040,40 @@ drm_gpuva_unlink(struct drm_gpuva *va)
18842040
}
18852041
EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
18862042

2043+
/**
2044+
* drm_gpuva_unlink_defer() - unlink a &drm_gpuva with deferred vm_bo cleanup
2045+
* @va: the &drm_gpuva to unlink
2046+
*
2047+
* Similar to drm_gpuva_unlink(), but uses drm_gpuvm_bo_put_deferred() and takes
2048+
* the lock for the caller.
2049+
*/
2050+
void
2051+
drm_gpuva_unlink_defer(struct drm_gpuva *va)
2052+
{
2053+
struct drm_gem_object *obj = va->gem.obj;
2054+
struct drm_gpuvm_bo *vm_bo = va->vm_bo;
2055+
bool should_defer_bo;
2056+
2057+
if (unlikely(!obj))
2058+
return;
2059+
2060+
drm_WARN_ON(vm_bo->vm->drm, !drm_gpuvm_immediate_mode(vm_bo->vm));
2061+
2062+
mutex_lock(&obj->gpuva.lock);
2063+
list_del_init(&va->gem.entry);
2064+
2065+
/*
2066+
* This is drm_gpuvm_bo_put_deferred() except we already hold the mutex.
2067+
*/
2068+
should_defer_bo = kref_put(&vm_bo->kref, drm_gpuvm_bo_into_zombie);
2069+
mutex_unlock(&obj->gpuva.lock);
2070+
if (should_defer_bo)
2071+
drm_gpuvm_bo_defer_zombie_cleanup(vm_bo);
2072+
2073+
va->vm_bo = NULL;
2074+
}
2075+
EXPORT_SYMBOL_GPL(drm_gpuva_unlink_defer);
2076+
18872077
/**
18882078
* drm_gpuva_find_first() - find the first &drm_gpuva in the given range
18892079
* @gpuvm: the &drm_gpuvm to search in

include/drm/drm_gpuvm.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include <linux/dma-resv.h>
2929
#include <linux/list.h>
30+
#include <linux/llist.h>
3031
#include <linux/rbtree.h>
3132
#include <linux/types.h>
3233

@@ -159,6 +160,7 @@ void drm_gpuva_remove(struct drm_gpuva *va);
159160

160161
void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo);
161162
void drm_gpuva_unlink(struct drm_gpuva *va);
163+
void drm_gpuva_unlink_defer(struct drm_gpuva *va);
162164

163165
struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
164166
u64 addr, u64 range);
@@ -349,6 +351,11 @@ struct drm_gpuvm {
349351
*/
350352
spinlock_t lock;
351353
} evict;
354+
355+
/**
356+
* @bo_defer: structure holding vm_bos that need to be destroyed
357+
*/
358+
struct llist_head bo_defer;
352359
};
353360

354361
void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
@@ -732,6 +739,12 @@ struct drm_gpuvm_bo {
732739
* &drm_gpuvms evict list.
733740
*/
734741
struct list_head evict;
742+
743+
/**
744+
* @list.entry.bo_defer: List entry to attach to
745+
* the &drm_gpuvms bo_defer list.
746+
*/
747+
struct llist_node bo_defer;
735748
} entry;
736749
} list;
737750
};
@@ -764,6 +777,9 @@ drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
764777

765778
bool drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
766779

780+
bool drm_gpuvm_bo_put_deferred(struct drm_gpuvm_bo *vm_bo);
781+
void drm_gpuvm_bo_deferred_cleanup(struct drm_gpuvm *gpuvm);
782+
767783
struct drm_gpuvm_bo *
768784
drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
769785
struct drm_gem_object *obj);

0 commit comments

Comments
 (0)