summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiranjana Vishwanathapura <niranjana.vishwanathapura@intel.com>2026-07-13 13:23:19 -0700
committerNiranjana Vishwanathapura <niranjana.vishwanathapura@intel.com>2026-07-13 13:29:24 -0700
commitc373eaaada2fe8eb7f8b7b0cefa997309264965d (patch)
treee4883350af887e5487d46daad535ff10c22b180f
parent412c885692cade356a394835934d3f36ae8f6aca (diff)
downloadlinux-c373eaaada2fe8eb7f8b7b0cefa997309264965d.tar.gz
linux-c373eaaada2fe8eb7f8b7b0cefa997309264965d.zip
drm/xe/hw_engine_group: propagate suspend failures during mode switch
The hw engine group fault-mode switch suspends all faulting LR queues but ignored the suspend()/suspend_wait() return value. A suspend() can fail (e.g. the queue is killed/banned/wedged), leaving the queue un-suspended, so silently continuing could later resume a queue that was never suspended. Propagate the failure instead: in xe_hw_engine_group_add_exec_queue() bail out if suspend() fails, and in xe_hw_engine_group_suspend_faulting_lr_jobs() undo the partial suspend via a new err_resume path that resumes the sibling queues already suspended in this call. Record per-queue success with lr.suspended so only queues that were actually suspended are waited on and resumed, and skip the cleanup resume() when suspend_wait() failed or the queue was reset/killed/banned/wedged (its suspend may not have completed, so resuming would trip the !suspend_pending assert in the resume path; teardown resolves its state instead). Gate the group resume worker (hw_engine_group_resume_lr_jobs_func()) on lr.suspended for the same reason, so it only resumes queues that were actually suspended. v2: Don't let a dying queue block the switch (Matt Brost) Assisted-by: Github-Copilot:Claude-opus-4.8 Signed-off-by: Niranjana Vishwanathapura <niranjana.vishwanathapura@intel.com> Reviewed-by: Matthew Brost <matthew.brost@intel.com> Link: https://patch.msgid.link/20260713202317.2187787-12-niranjana.vishwanathapura@intel.com
-rw-r--r--drivers/gpu/drm/xe/xe_hw_engine_group.c85
1 files changed, 82 insertions, 3 deletions
diff --git a/drivers/gpu/drm/xe/xe_hw_engine_group.c b/drivers/gpu/drm/xe/xe_hw_engine_group.c
index 02cf32ae5aa9..0804b426b6f9 100644
--- a/drivers/gpu/drm/xe/xe_hw_engine_group.c
+++ b/drivers/gpu/drm/xe/xe_hw_engine_group.c
@@ -34,6 +34,15 @@ hw_engine_group_resume_lr_jobs_func(struct work_struct *w)
if (!xe_vm_in_fault_mode(q->vm))
continue;
+ /*
+ * Only resume queues that were actually suspended. A queue whose
+ * suspend() failed (e.g. killed/banned/wedged) was never
+ * suspended, so it must not be resumed.
+ */
+ if (!READ_ONCE(q->lr.suspended))
+ continue;
+
+ WRITE_ONCE(q->lr.suspended, false);
q->ops->resume(q);
}
@@ -140,7 +149,18 @@ int xe_hw_engine_group_add_exec_queue(struct xe_hw_engine_group *group, struct x
return err;
if (xe_vm_in_fault_mode(q->vm) && group->cur_mode == EXEC_MODE_DMA_FENCE) {
- q->ops->suspend(q);
+ /*
+ * suspend() can fail (e.g. killed/banned/wedged), leaving the
+ * queue un-suspended. Propagate the failure so the queue is not
+ * added; on failure nothing was suspended, so there is nothing to
+ * undo. Only record the queue as suspended (and later resume it)
+ * once suspend() has succeeded.
+ */
+ err = q->ops->suspend(q);
+ if (err)
+ goto err_suspend;
+
+ WRITE_ONCE(q->lr.suspended, true);
err = q->ops->suspend_wait(q);
if (err)
goto err_suspend;
@@ -216,8 +236,22 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group
return -EAGAIN;
xe_gt_stats_incr(q->gt, XE_GT_STATS_ID_HW_ENGINE_GROUP_SUSPEND_LR_QUEUE_COUNT, 1);
+ /*
+ * suspend() only fails when the queue is killed/banned/wedged.
+ * Such a queue is being torn down (its removal from HW is handled
+ * by the kill/ban teardown), so it is not a live fault-mode
+ * context the mode switch must preempt. Skip it rather than
+ * failing the switch, otherwise one dying sibling would block a
+ * dma-fence submission on the healthy queues in the group. Only
+ * queues recorded as suspended below are later waited on and
+ * resumed.
+ */
+ err = q->ops->suspend(q);
+ if (err)
+ continue;
+
+ WRITE_ONCE(q->lr.suspended, true);
need_resume = true;
- q->ops->suspend(q);
gt = q->gt;
}
@@ -225,9 +259,13 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group
if (!xe_vm_in_fault_mode(q->vm))
continue;
+ /* Only wait on queues that were actually suspended above. */
+ if (!READ_ONCE(q->lr.suspended))
+ continue;
+
err = q->ops->suspend_wait(q);
if (err)
- return err;
+ goto err_resume;
}
if (gt) {
@@ -240,6 +278,47 @@ static int xe_hw_engine_group_suspend_faulting_lr_jobs(struct xe_hw_engine_group
xe_hw_engine_group_resume_faulting_lr_jobs(group);
return 0;
+
+err_resume:
+ /*
+ * A suspend_wait() failed partway through the mode switch. Resume the
+ * sibling queues that were already suspended in this call so they are
+ * not left suspended forever.
+ *
+ * resume() requires the suspend to have completed (suspend_pending
+ * cleared) or it trips the !suspend_pending assert. So skip the resume
+ * when either:
+ * - suspend_wait_blocking() fails: on a GuC timeout it bans the queue
+ * and triggers cleanup, so the queue is being torn down; or
+ * - reset_status() is true: the queue was reset/killed/banned/wedged.
+ * suspend_wait() can return success in this case via its killed/
+ * stopped wait condition while suspend_pending is still set, and the
+ * queue is being torn down anyway, so its state is resolved by
+ * teardown rather than by a resume here.
+ * In either case leave the queue marked suspended.
+ *
+ * Use the *blocking* (uninterruptible) wait here: the queues resumed on
+ * this path may belong to a different process than the one that
+ * triggered the mode switch. An interruptible suspend_wait() would
+ * return -ERESTARTSYS if the triggering task is signalled, skip the
+ * resume, and leave the other process's queue suspended forever
+ * (cross-process DoS).
+ */
+ list_for_each_entry(q, &group->exec_queue_list, hw_engine_group_link) {
+ if (!xe_vm_in_fault_mode(q->vm))
+ continue;
+
+ if (!READ_ONCE(q->lr.suspended))
+ continue;
+
+ if (q->ops->suspend_wait_blocking(q) || q->ops->reset_status(q))
+ continue;
+
+ WRITE_ONCE(q->lr.suspended, false);
+ q->ops->resume(q);
+ }
+
+ return err;
}
/**