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
This commit is contained in:
Niranjana Vishwanathapura
2026-07-13 13:23:19 -07:00
parent 412c885692
commit c373eaaada

View File

@@ -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;
}
/**