Skip to content

Commit e13d2ee

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 4f10296 commit e13d2ee

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
@@ -876,6 +876,31 @@ __drm_gpuvm_bo_list_add(struct drm_gpuvm *gpuvm, spinlock_t *lock,
876876
cond_spin_unlock(lock, !!lock);
877877
}
878878

879+
/**
880+
* drm_gpuvm_bo_is_zombie() - check whether this vm_bo is scheduled for cleanup
881+
* @vm_bo: the &drm_gpuvm_bo
882+
*
883+
* When a vm_bo is scheduled for cleanup using the bo_defer list, it is not
884+
* immediately removed from the evict and extobj lists. Therefore, anyone
885+
* iterating these lists should skip entries that are being destroyed.
886+
*
887+
* Checking the refcount without incrementing it is okay as long as the lock
888+
* protecting the evict/extobj list is held for as long as you are using the
889+
* vm_bo, because even if the refcount hits zero while you are using it, freeing
890+
* the vm_bo requires taking the list's lock.
891+
*
892+
* Zombie entries can be observed on the evict and extobj lists regardless of
893+
* whether DRM_GPUVM_RESV_PROTECTED is used, but they remain on the lists for a
894+
* longer time when the resv lock is used because we can't take the resv lock
895+
* during run_job() in immediate mode, meaning that they need to remain on the
896+
* lists until drm_gpuvm_bo_deferred_cleanup() is called.
897+
*/
898+
static bool
899+
drm_gpuvm_bo_is_zombie(struct drm_gpuvm_bo *vm_bo)
900+
{
901+
return !kref_read(&vm_bo->kref);
902+
}
903+
879904
/**
880905
* drm_gpuvm_bo_list_add() - insert a vm_bo into the given list
881906
* @__vm_bo: the &drm_gpuvm_bo
@@ -1081,6 +1106,8 @@ drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
10811106
INIT_LIST_HEAD(&gpuvm->evict.list);
10821107
spin_lock_init(&gpuvm->evict.lock);
10831108

1109+
init_llist_head(&gpuvm->bo_defer);
1110+
10841111
kref_init(&gpuvm->kref);
10851112

10861113
gpuvm->name = name ? name : "unknown";
@@ -1122,6 +1149,8 @@ drm_gpuvm_fini(struct drm_gpuvm *gpuvm)
11221149
"Extobj list should be empty.\n");
11231150
drm_WARN(gpuvm->drm, !list_empty(&gpuvm->evict.list),
11241151
"Evict list should be empty.\n");
1152+
drm_WARN(gpuvm->drm, !llist_empty(&gpuvm->bo_defer),
1153+
"VM BO cleanup list should be empty.\n");
11251154

11261155
drm_gem_object_put(gpuvm->r_obj);
11271156
}
@@ -1217,6 +1246,9 @@ drm_gpuvm_prepare_objects_locked(struct drm_gpuvm *gpuvm,
12171246

12181247
drm_gpuvm_resv_assert_held(gpuvm);
12191248
list_for_each_entry(vm_bo, &gpuvm->extobj.list, list.entry.extobj) {
1249+
if (drm_gpuvm_bo_is_zombie(vm_bo))
1250+
continue;
1251+
12201252
ret = exec_prepare_obj(exec, vm_bo->obj, num_fences);
12211253
if (ret)
12221254
break;
@@ -1460,6 +1492,9 @@ drm_gpuvm_validate_locked(struct drm_gpuvm *gpuvm, struct drm_exec *exec)
14601492

14611493
list_for_each_entry_safe(vm_bo, next, &gpuvm->evict.list,
14621494
list.entry.evict) {
1495+
if (drm_gpuvm_bo_is_zombie(vm_bo))
1496+
continue;
1497+
14631498
ret = ops->vm_bo_validate(vm_bo, exec);
14641499
if (ret)
14651500
break;
@@ -1560,6 +1595,7 @@ drm_gpuvm_bo_create(struct drm_gpuvm *gpuvm,
15601595

15611596
INIT_LIST_HEAD(&vm_bo->list.entry.extobj);
15621597
INIT_LIST_HEAD(&vm_bo->list.entry.evict);
1598+
init_llist_node(&vm_bo->list.entry.bo_defer);
15631599

15641600
return vm_bo;
15651601
}
@@ -1621,6 +1657,126 @@ drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo)
16211657
}
16221658
EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put);
16231659

1660+
/*
1661+
* drm_gpuvm_bo_into_zombie() - called when the vm_bo becomes a zombie due to
1662+
* deferred cleanup
1663+
*
1664+
* If deferred cleanup is used, then this must be called right after the vm_bo
1665+
* refcount drops to zero. Must be called with GEM mutex held. After releasing
1666+
* the GEM mutex, drm_gpuvm_bo_defer_zombie_cleanup() must be called.
1667+
*/
1668+
static void
1669+
drm_gpuvm_bo_into_zombie(struct kref *kref)
1670+
{
1671+
struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
1672+
kref);
1673+
1674+
if (!drm_gpuvm_resv_protected(vm_bo->vm)) {
1675+
drm_gpuvm_bo_list_del(vm_bo, extobj, true);
1676+
drm_gpuvm_bo_list_del(vm_bo, evict, true);
1677+
}
1678+
1679+
list_del(&vm_bo->list.entry.gem);
1680+
}
1681+
1682+
/*
1683+
* drm_gpuvm_bo_defer_zombie_cleanup() - adds a new zombie vm_bo to the
1684+
* bo_defer list
1685+
*
1686+
* Called after drm_gpuvm_bo_into_zombie(). GEM mutex must not be held.
1687+
*
1688+
* It's important that the GEM stays alive for the duration in which we hold
1689+
* the mutex, but the instant we add the vm_bo to bo_defer, another thread
1690+
* might call drm_gpuvm_bo_deferred_cleanup() and put the GEM. Therefore, to
1691+
* avoid kfreeing a mutex we are holding, the GEM mutex must be released
1692+
* *before* calling this function.
1693+
*/
1694+
static void
1695+
drm_gpuvm_bo_defer_zombie_cleanup(struct drm_gpuvm_bo *vm_bo)
1696+
{
1697+
llist_add(&vm_bo->list.entry.bo_defer, &vm_bo->vm->bo_defer);
1698+
}
1699+
1700+
static void
1701+
drm_gpuvm_bo_defer_free(struct kref *kref)
1702+
{
1703+
struct drm_gpuvm_bo *vm_bo = container_of(kref, struct drm_gpuvm_bo,
1704+
kref);
1705+
1706+
drm_gpuvm_bo_into_zombie(kref);
1707+
mutex_unlock(&vm_bo->obj->gpuva.lock);
1708+
drm_gpuvm_bo_defer_zombie_cleanup(vm_bo);
1709+
}
1710+
1711+
/**
1712+
* drm_gpuvm_bo_put_deferred() - drop a struct drm_gpuvm_bo reference with
1713+
* deferred cleanup
1714+
* @vm_bo: the &drm_gpuvm_bo to release the reference of
1715+
*
1716+
* This releases a reference to @vm_bo.
1717+
*
1718+
* This might take and release the GEMs GPUVA lock. You should call
1719+
* drm_gpuvm_bo_deferred_cleanup() later to complete the cleanup process.
1720+
*
1721+
* Returns: true if vm_bo is being destroyed, false otherwise.
1722+
*/
1723+
bool
1724+
drm_gpuvm_bo_put_deferred(struct drm_gpuvm_bo *vm_bo)
1725+
{
1726+
if (!vm_bo)
1727+
return false;
1728+
1729+
drm_WARN_ON(vm_bo->vm->drm, !drm_gpuvm_immediate_mode(vm_bo->vm));
1730+
1731+
return !!kref_put_mutex(&vm_bo->kref,
1732+
drm_gpuvm_bo_defer_free,
1733+
&vm_bo->obj->gpuva.lock);
1734+
}
1735+
EXPORT_SYMBOL_GPL(drm_gpuvm_bo_put_deferred);
1736+
1737+
/**
1738+
* drm_gpuvm_bo_deferred_cleanup() - clean up BOs in the deferred list
1739+
* deferred cleanup
1740+
* @gpuvm: the VM to clean up
1741+
*
1742+
* Cleans up &drm_gpuvm_bo instances in the deferred cleanup list.
1743+
*/
1744+
void
1745+
drm_gpuvm_bo_deferred_cleanup(struct drm_gpuvm *gpuvm)
1746+
{
1747+
const struct drm_gpuvm_ops *ops = gpuvm->ops;
1748+
struct drm_gpuvm_bo *vm_bo;
1749+
struct drm_gem_object *obj;
1750+
struct llist_node *bo_defer;
1751+
1752+
bo_defer = llist_del_all(&gpuvm->bo_defer);
1753+
if (!bo_defer)
1754+
return;
1755+
1756+
if (drm_gpuvm_resv_protected(gpuvm)) {
1757+
dma_resv_lock(drm_gpuvm_resv(gpuvm), NULL);
1758+
llist_for_each_entry(vm_bo, bo_defer, list.entry.bo_defer) {
1759+
drm_gpuvm_bo_list_del(vm_bo, extobj, false);
1760+
drm_gpuvm_bo_list_del(vm_bo, evict, false);
1761+
}
1762+
dma_resv_unlock(drm_gpuvm_resv(gpuvm));
1763+
}
1764+
1765+
while (bo_defer) {
1766+
vm_bo = llist_entry(bo_defer, struct drm_gpuvm_bo, list.entry.bo_defer);
1767+
bo_defer = bo_defer->next;
1768+
obj = vm_bo->obj;
1769+
if (ops && ops->vm_bo_free)
1770+
ops->vm_bo_free(vm_bo);
1771+
else
1772+
kfree(vm_bo);
1773+
1774+
drm_gpuvm_put(gpuvm);
1775+
drm_gem_object_put(obj);
1776+
}
1777+
}
1778+
EXPORT_SYMBOL_GPL(drm_gpuvm_bo_deferred_cleanup);
1779+
16241780
static struct drm_gpuvm_bo *
16251781
__drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
16261782
struct drm_gem_object *obj)
@@ -1948,6 +2104,40 @@ drm_gpuva_unlink(struct drm_gpuva *va)
19482104
}
19492105
EXPORT_SYMBOL_GPL(drm_gpuva_unlink);
19502106

2107+
/**
2108+
* drm_gpuva_unlink_defer() - unlink a &drm_gpuva with deferred vm_bo cleanup
2109+
* @va: the &drm_gpuva to unlink
2110+
*
2111+
* Similar to drm_gpuva_unlink(), but uses drm_gpuvm_bo_put_deferred() and takes
2112+
* the lock for the caller.
2113+
*/
2114+
void
2115+
drm_gpuva_unlink_defer(struct drm_gpuva *va)
2116+
{
2117+
struct drm_gem_object *obj = va->gem.obj;
2118+
struct drm_gpuvm_bo *vm_bo = va->vm_bo;
2119+
bool should_defer_bo;
2120+
2121+
if (unlikely(!obj))
2122+
return;
2123+
2124+
drm_WARN_ON(vm_bo->vm->drm, !drm_gpuvm_immediate_mode(vm_bo->vm));
2125+
2126+
mutex_lock(&obj->gpuva.lock);
2127+
list_del_init(&va->gem.entry);
2128+
2129+
/*
2130+
* This is drm_gpuvm_bo_put_deferred() except we already hold the mutex.
2131+
*/
2132+
should_defer_bo = kref_put(&vm_bo->kref, drm_gpuvm_bo_into_zombie);
2133+
mutex_unlock(&obj->gpuva.lock);
2134+
if (should_defer_bo)
2135+
drm_gpuvm_bo_defer_zombie_cleanup(vm_bo);
2136+
2137+
va->vm_bo = NULL;
2138+
}
2139+
EXPORT_SYMBOL_GPL(drm_gpuva_unlink_defer);
2140+
19512141
/**
19522142
* drm_gpuva_find_first() - find the first &drm_gpuva in the given range
19532143
* @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

@@ -173,6 +174,7 @@ void drm_gpuva_remove(struct drm_gpuva *va);
173174

174175
void drm_gpuva_link(struct drm_gpuva *va, struct drm_gpuvm_bo *vm_bo);
175176
void drm_gpuva_unlink(struct drm_gpuva *va);
177+
void drm_gpuva_unlink_defer(struct drm_gpuva *va);
176178

177179
struct drm_gpuva *drm_gpuva_find(struct drm_gpuvm *gpuvm,
178180
u64 addr, u64 range);
@@ -352,6 +354,11 @@ struct drm_gpuvm {
352354
*/
353355
spinlock_t lock;
354356
} evict;
357+
358+
/**
359+
* @bo_defer: structure holding vm_bos that need to be destroyed
360+
*/
361+
struct llist_head bo_defer;
355362
};
356363

357364
void drm_gpuvm_init(struct drm_gpuvm *gpuvm, const char *name,
@@ -735,6 +742,12 @@ struct drm_gpuvm_bo {
735742
* &drm_gpuvms evict list.
736743
*/
737744
struct list_head evict;
745+
746+
/**
747+
* @list.entry.bo_defer: List entry to attach to
748+
* the &drm_gpuvms bo_defer list.
749+
*/
750+
struct llist_node bo_defer;
738751
} entry;
739752
} list;
740753
};
@@ -767,6 +780,9 @@ drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
767780

768781
bool drm_gpuvm_bo_put(struct drm_gpuvm_bo *vm_bo);
769782

783+
bool drm_gpuvm_bo_put_deferred(struct drm_gpuvm_bo *vm_bo);
784+
void drm_gpuvm_bo_deferred_cleanup(struct drm_gpuvm *gpuvm);
785+
770786
struct drm_gpuvm_bo *
771787
drm_gpuvm_bo_find(struct drm_gpuvm *gpuvm,
772788
struct drm_gem_object *obj);

0 commit comments

Comments
 (0)