Skip to content

Commit 3392291

Browse files
dbluemanRob Clark
authored andcommitted
drm/msm: Fix shrinker deadlock
With PROVE_LOCKING on an Snapdragon X1 and VM reclaim pressure, we see: ====================================================== WARNING: possible circular locking dependency detected 7.0.0-debug+ #43 Tainted: G W ------------------------------------------------------ kswapd0/82 is trying to acquire lock: ffff800080ec3870 (reservation_ww_class_acquire){+.+.}-{0:0}, at: msm_gem_shrinker_scan+0x17c/0x400 [msm] but task is already holding lock: ffffc31709b263b8 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat+0x88/0x988 which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #2 (fs_reclaim){+.+.}-{0:0}: __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 fs_reclaim_acquire+0xd0/0xf0 dma_resv_lockdep+0x224/0x348 do_one_initcall+0x84/0x5d0 do_initcalls+0x194/0x1d8 kernel_init_freeable+0x128/0x180 kernel_init+0x2c/0x160 ret_from_fork+0x10/0x20 -> #1 (reservation_ww_class_mutex){+.+.}-{4:4}: __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 dma_resv_lockdep+0x1a8/0x348 do_one_initcall+0x84/0x5d0 do_initcalls+0x194/0x1d8 kernel_init_freeable+0x128/0x180 kernel_init+0x2c/0x160 ret_from_fork+0x10/0x20 -> #0 (reservation_ww_class_acquire){+.+.}-{0:0}: check_prev_add+0x114/0x790 validate_chain+0x594/0x6f0 __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 drm_gem_lru_scan+0x1ac/0x440 msm_gem_shrinker_scan+0x17c/0x400 [msm] do_shrink_slab+0x150/0x4a0 shrink_slab+0x144/0x460 shrink_one+0x9c/0x1b0 shrink_many+0x27c/0x5c0 shrink_node+0x344/0x550 balance_pgdat+0x2c0/0x988 kswapd+0x11c/0x318 kthread+0x10c/0x128 ret_from_fork+0x10/0x20 other info that might help us debug this: Chain exists of: reservation_ww_class_acquire --> reservation_ww_class_mutex --> fs_reclaim Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(fs_reclaim); lock(reservation_ww_class_mutex); lock(fs_reclaim); lock(reservation_ww_class_acquire); *** DEADLOCK *** 1 lock held by kswapd0/82: #0: ffffc31709b263b8 (fs_reclaim){+.+.}-{0:0}, at: balance_pgdat+0x88/0x988 stack backtrace: CPU: 4 UID: 0 PID: 82 Comm: kswapd0 Tainted: G W 7.0.0-debug+ #43 PREEMPT(full) Tainted: [W]=WARN Hardware name: LENOVO 21BX0016US/21BX0016US, BIOS N3HET94W (1.66 ) 09/15/2025 Call trace: show_stack+0x20/0x40 (C) dump_stack_lvl+0x9c/0xd0 dump_stack+0x18/0x30 print_circular_bug+0x114/0x120 check_noncircular+0x178/0x198 check_prev_add+0x114/0x790 validate_chain+0x594/0x6f0 __lock_acquire+0x4d0/0xad0 lock_acquire.part.0+0xc4/0x248 lock_acquire+0x8c/0x248 drm_gem_lru_scan+0x1ac/0x440 msm_gem_shrinker_scan+0x17c/0x400 [msm] do_shrink_slab+0x150/0x4a0 shrink_slab+0x144/0x460 shrink_one+0x9c/0x1b0 shrink_many+0x27c/0x5c0 shrink_node+0x344/0x550 balance_pgdat+0x2c0/0x988 kswapd+0x11c/0x318 kthread+0x10c/0x128 ret_from_fork+0x10/0x20 kswapd0 holding fs_reclaim calls the MSM shrinker, which calls dma_resv_lock. This in turn acquires fs_reclaim. Fix this deadlock by using dma_resv_trylock() instead, dropping the subsequently unused passed wait-wound lock 'ticket'. Cc: stable@vger.kernel.org Signed-off-by: Daniel J Blueman <daniel@quora.org> Fixes: fe4952b ("drm/msm: Convert vm locking") Patchwork: https://patchwork.freedesktop.org/patch/723564/ Message-ID: <20260508065722.18785-1-daniel@quora.org> [rob: fixup compile errors, replace lockdep splat with something legible] Signed-off-by: Rob Clark <robin.clark@oss.qualcomm.com>
1 parent 3f9ed5f commit 3392291

1 file changed

Lines changed: 16 additions & 24 deletions

File tree

drivers/gpu/drm/msm/msm_gem_shrinker.c

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -43,40 +43,30 @@ msm_gem_shrinker_count(struct shrinker *shrinker, struct shrink_control *sc)
4343
}
4444

4545
static bool
46-
with_vm_locks(struct ww_acquire_ctx *ticket,
47-
void (*fn)(struct drm_gem_object *obj),
46+
with_vm_locks(void (*fn)(struct drm_gem_object *obj),
4847
struct drm_gem_object *obj)
4948
{
5049
/*
5150
* Track last locked entry for for unwinding locks in error and
5251
* success paths
5352
*/
5453
struct drm_gpuvm_bo *vm_bo, *last_locked = NULL;
55-
int ret = 0;
54+
bool locked = true;
5655

5756
drm_gem_for_each_gpuvm_bo (vm_bo, obj) {
5857
struct dma_resv *resv = drm_gpuvm_resv(vm_bo->vm);
5958

6059
if (resv == obj->resv)
6160
continue;
6261

63-
ret = dma_resv_lock(resv, ticket);
64-
65-
/*
66-
* Since we already skip the case when the VM and obj
67-
* share a resv (ie. _NO_SHARE objs), we don't expect
68-
* to hit a double-locking scenario... which the lock
69-
* unwinding cannot really cope with.
70-
*/
71-
WARN_ON(ret == -EALREADY);
72-
7362
/*
74-
* Don't bother with slow-lock / backoff / retry sequence,
75-
* if we can't get the lock just give up and move on to
76-
* the next object.
63+
* dma_resv_lock can't be used due to acquiring 'ticket' before the
64+
* fs_reclaim lock, which is held in shrinker context
7765
*/
78-
if (ret)
66+
if (!dma_resv_trylock(resv)) {
67+
locked = false;
7968
goto out_unlock;
69+
}
8070

8171
/*
8272
* Hold a ref to prevent the vm_bo from being freed
@@ -108,31 +98,31 @@ with_vm_locks(struct ww_acquire_ctx *ticket,
10898
}
10999
}
110100

111-
return ret == 0;
101+
return locked;
112102
}
113103

114104
static bool
115-
purge(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket)
105+
purge(struct drm_gem_object *obj, struct ww_acquire_ctx *)
116106
{
117107
if (!is_purgeable(to_msm_bo(obj)))
118108
return false;
119109

120110
if (msm_gem_active(obj))
121111
return false;
122112

123-
return with_vm_locks(ticket, msm_gem_purge, obj);
113+
return with_vm_locks(msm_gem_purge, obj);
124114
}
125115

126116
static bool
127-
evict(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket)
117+
evict(struct drm_gem_object *obj, struct ww_acquire_ctx *)
128118
{
129119
if (is_unevictable(to_msm_bo(obj)))
130120
return false;
131121

132122
if (msm_gem_active(obj))
133123
return false;
134124

135-
return with_vm_locks(ticket, msm_gem_evict, obj);
125+
return with_vm_locks(msm_gem_evict, obj);
136126
}
137127

138128
static bool
@@ -164,7 +154,6 @@ static unsigned long
164154
msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
165155
{
166156
struct msm_drm_private *priv = shrinker->private_data;
167-
struct ww_acquire_ctx ticket;
168157
struct {
169158
struct drm_gem_lru *lru;
170159
bool (*shrink)(struct drm_gem_object *obj, struct ww_acquire_ctx *ticket);
@@ -185,11 +174,14 @@ msm_gem_shrinker_scan(struct shrinker *shrinker, struct shrink_control *sc)
185174
for (unsigned i = 0; (nr > 0) && (i < ARRAY_SIZE(stages)); i++) {
186175
if (!stages[i].cond)
187176
continue;
177+
/*
178+
* 'ticket' not needed on trylock paths
179+
*/
188180
stages[i].freed =
189181
drm_gem_lru_scan(stages[i].lru, nr,
190182
&stages[i].remaining,
191183
stages[i].shrink,
192-
&ticket);
184+
NULL);
193185
nr -= stages[i].freed;
194186
freed += stages[i].freed;
195187
remaining += stages[i].remaining;

0 commit comments

Comments
 (0)