Skip to content

Commit 2f2cc53

Browse files
aalteresmattrope
authored andcommitted
drm/i915/guc: Close deregister-context race against CT-loss
If we are at the end of suspend or very early in resume its possible an async fence signal (via rcu_call) is triggered to free_engines which could lead us to the execution of the context destruction worker (after a prior worker flush). Thus, when suspending, insert rcu_barriers at the start of i915_gem_suspend (part of driver's suspend prepare) and again in i915_gem_suspend_late so that all such cases have completed and context destruction list isn't missing anything. In destroyed_worker_func, close the race against CT-loss by checking that CT is enabled before calling into deregister_destroyed_contexts. Based on testing, guc_lrc_desc_unpin may still race and fail as we traverse the GuC's context-destroy list because the CT could be disabled right before calling GuC's CT send function. We've witnessed this race condition once every ~6000-8000 suspend-resume cycles while ensuring workloads that render something onscreen is continuously started just before we suspend (and the workload is small enough to complete and trigger the queued engine/context free-up either very late in suspend or very early in resume). In such a case, we need to unroll the entire process because guc-lrc-unpin takes a gt wakeref which only gets released in the G2H IRQ reply that never comes through in this corner case. Without the unroll, the taken wakeref is leaked and will cascade into a kernel hang later at the tail end of suspend in this function: intel_wakeref_wait_for_idle(&gt->wakeref) (called by) - intel_gt_pm_wait_for_idle (called by) - wait_for_suspend Thus, do an unroll in guc_lrc_desc_unpin and deregister_destroyed_- contexts if guc_lrc_desc_unpin fails due to CT send falure. When unrolling, keep the context in the GuC's destroy-list so it can get picked up on the next destroy worker invocation (if suspend aborted) or get fully purged as part of a GuC sanitization (end of suspend) or a reset flow. Signed-off-by: Alan Previn <[email protected]> Signed-off-by: Anshuman Gupta <[email protected]> Tested-by: Mousumi Jana <[email protected]> Acked-by: Daniele Ceraolo Spurio <[email protected]> Reviewed-by: Rodrigo Vivi <[email protected]> Signed-off-by: Matt Roper <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent 5e83c06 commit 2f2cc53

File tree

2 files changed

+78
-5
lines changed

2 files changed

+78
-5
lines changed

drivers/gpu/drm/i915/gem/i915_gem_pm.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ void i915_gem_suspend(struct drm_i915_private *i915)
2828
GEM_TRACE("%s\n", dev_name(i915->drm.dev));
2929

3030
intel_wakeref_auto(&i915->runtime_pm.userfault_wakeref, 0);
31+
/*
32+
* On rare occasions, we've observed the fence completion triggers
33+
* free_engines asynchronously via rcu_call. Ensure those are done.
34+
* This path is only called on suspend, so it's an acceptable cost.
35+
*/
36+
rcu_barrier();
37+
3138
flush_workqueue(i915->wq);
3239

3340
/*
@@ -160,6 +167,9 @@ void i915_gem_suspend_late(struct drm_i915_private *i915)
160167
* machine in an unusable condition.
161168
*/
162169

170+
/* Like i915_gem_suspend, flush tasks staged from fence triggers */
171+
rcu_barrier();
172+
163173
for_each_gt(gt, i915, i)
164174
intel_gt_suspend_late(gt);
165175

drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ set_context_destroyed(struct intel_context *ce)
236236
ce->guc_state.sched_state |= SCHED_STATE_DESTROYED;
237237
}
238238

239+
static inline void
240+
clr_context_destroyed(struct intel_context *ce)
241+
{
242+
lockdep_assert_held(&ce->guc_state.lock);
243+
ce->guc_state.sched_state &= ~SCHED_STATE_DESTROYED;
244+
}
245+
239246
static inline bool context_pending_disable(struct intel_context *ce)
240247
{
241248
return ce->guc_state.sched_state & SCHED_STATE_PENDING_DISABLE;
@@ -613,6 +620,8 @@ static int guc_submission_send_busy_loop(struct intel_guc *guc,
613620
u32 g2h_len_dw,
614621
bool loop)
615622
{
623+
int ret;
624+
616625
/*
617626
* We always loop when a send requires a reply (i.e. g2h_len_dw > 0),
618627
* so we don't handle the case where we don't get a reply because we
@@ -623,7 +632,11 @@ static int guc_submission_send_busy_loop(struct intel_guc *guc,
623632
if (g2h_len_dw)
624633
atomic_inc(&guc->outstanding_submission_g2h);
625634

626-
return intel_guc_send_busy_loop(guc, action, len, g2h_len_dw, loop);
635+
ret = intel_guc_send_busy_loop(guc, action, len, g2h_len_dw, loop);
636+
if (ret)
637+
atomic_dec(&guc->outstanding_submission_g2h);
638+
639+
return ret;
627640
}
628641

629642
int intel_guc_wait_for_pending_msg(struct intel_guc *guc,
@@ -3288,12 +3301,13 @@ static void guc_context_close(struct intel_context *ce)
32883301
spin_unlock_irqrestore(&ce->guc_state.lock, flags);
32893302
}
32903303

3291-
static inline void guc_lrc_desc_unpin(struct intel_context *ce)
3304+
static inline int guc_lrc_desc_unpin(struct intel_context *ce)
32923305
{
32933306
struct intel_guc *guc = ce_to_guc(ce);
32943307
struct intel_gt *gt = guc_to_gt(guc);
32953308
unsigned long flags;
32963309
bool disabled;
3310+
int ret;
32973311

32983312
GEM_BUG_ON(!intel_gt_pm_is_awake(gt));
32993313
GEM_BUG_ON(!ctx_id_mapped(guc, ce->guc_id.id));
@@ -3304,18 +3318,41 @@ static inline void guc_lrc_desc_unpin(struct intel_context *ce)
33043318
spin_lock_irqsave(&ce->guc_state.lock, flags);
33053319
disabled = submission_disabled(guc);
33063320
if (likely(!disabled)) {
3321+
/*
3322+
* Take a gt-pm ref and change context state to be destroyed.
3323+
* NOTE: a G2H IRQ that comes after will put this gt-pm ref back
3324+
*/
33073325
__intel_gt_pm_get(gt);
33083326
set_context_destroyed(ce);
33093327
clr_context_registered(ce);
33103328
}
33113329
spin_unlock_irqrestore(&ce->guc_state.lock, flags);
3330+
33123331
if (unlikely(disabled)) {
33133332
release_guc_id(guc, ce);
33143333
__guc_context_destroy(ce);
3315-
return;
3334+
return 0;
33163335
}
33173336

3318-
deregister_context(ce, ce->guc_id.id);
3337+
/*
3338+
* GuC is active, lets destroy this context, but at this point we can still be racing
3339+
* with suspend, so we undo everything if the H2G fails in deregister_context so
3340+
* that GuC reset will find this context during clean up.
3341+
*/
3342+
ret = deregister_context(ce, ce->guc_id.id);
3343+
if (ret) {
3344+
spin_lock(&ce->guc_state.lock);
3345+
set_context_registered(ce);
3346+
clr_context_destroyed(ce);
3347+
spin_unlock(&ce->guc_state.lock);
3348+
/*
3349+
* As gt-pm is awake at function entry, intel_wakeref_put_async merely decrements
3350+
* the wakeref immediately but per function spec usage call this after unlock.
3351+
*/
3352+
intel_wakeref_put_async(&gt->wakeref);
3353+
}
3354+
3355+
return ret;
33193356
}
33203357

33213358
static void __guc_context_destroy(struct intel_context *ce)
@@ -3383,7 +3420,22 @@ static void deregister_destroyed_contexts(struct intel_guc *guc)
33833420
if (!ce)
33843421
break;
33853422

3386-
guc_lrc_desc_unpin(ce);
3423+
if (guc_lrc_desc_unpin(ce)) {
3424+
/*
3425+
* This means GuC's CT link severed mid-way which could happen
3426+
* in suspend-resume corner cases. In this case, put the
3427+
* context back into the destroyed_contexts list which will
3428+
* get picked up on the next context deregistration event or
3429+
* purged in a GuC sanitization event (reset/unload/wedged/...).
3430+
*/
3431+
spin_lock_irqsave(&guc->submission_state.lock, flags);
3432+
list_add_tail(&ce->destroyed_link,
3433+
&guc->submission_state.destroyed_contexts);
3434+
spin_unlock_irqrestore(&guc->submission_state.lock, flags);
3435+
/* Bail now since the list might never be emptied if h2gs fail */
3436+
break;
3437+
}
3438+
33873439
}
33883440
}
33893441

@@ -3394,6 +3446,17 @@ static void destroyed_worker_func(struct work_struct *w)
33943446
struct intel_gt *gt = guc_to_gt(guc);
33953447
intel_wakeref_t wakeref;
33963448

3449+
/*
3450+
* In rare cases we can get here via async context-free fence-signals that
3451+
* come very late in suspend flow or very early in resume flows. In these
3452+
* cases, GuC won't be ready but just skipping it here is fine as these
3453+
* pending-destroy-contexts get destroyed totally at GuC reset time at the
3454+
* end of suspend.. OR.. this worker can be picked up later on the next
3455+
* context destruction trigger after resume-completes
3456+
*/
3457+
if (!intel_guc_is_ready(guc))
3458+
return;
3459+
33973460
with_intel_gt_pm(gt, wakeref)
33983461
deregister_destroyed_contexts(guc);
33993462
}

0 commit comments

Comments
 (0)