Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Matthew Brost <matthew.brost@intel.com>
To: Niranjana Vishwanathapura <niranjana.vishwanathapura@intel.com>
Cc: <intel-xe@lists.freedesktop.org>, <thomas.hellstrom@linux.intel.com>
Subject: Re: [PATCH v2 5/5] drm/xe/multi_queue: preempt primary on queue group suspend
Date: Thu, 9 Jul 2026 13:43:10 -0700	[thread overview]
Message-ID: <alAH3ogvxAPcNmMq@gsse-cloud1.jf.intel.com> (raw)
In-Reply-To: <20260701050730.1776476-12-niranjana.vishwanathapura@intel.com>

On Tue, Jun 30, 2026 at 10:07:33PM -0700, Niranjana Vishwanathapura wrote:
> In a multi-queue group only the group's primary queue interfaces with
> GuC for scheduling; suspend/resume of secondary queues is handled
> internally and is not forwarded to GuC. As a result, suspending a
> secondary queue alone (e.g. on its preempt fence signalling) does not
> disable the primary's GuC context, so in-flight GPU work of the group
> is not actually preempted.
> 
> Make a secondary queue suspend/resume like any other queue, driven by
> its own xe_guc_exec_queue.suspend_count, and additionally forward the
> suspend/resume to the primary so the GPU is actually preempted. The
> forward is gated on the secondary's own 0->1 / 1->0 suspend_count
> transition, so each group member contributes exactly one suspend
> reference to the primary: the primary keeps its GuC context disabled
> until every member that suspended it has resumed, including across the
> resume-all-queues-each-rebind-cycle behavior. group->suspend_lock makes
> the secondary transition and the primary forward atomic, and a member
> leaving while still suspended (queue teardown) drops its reference on
> the primary.
> 
> guc_exec_queue_suspend_wait() now waits on the primary, so on a suspend
> timeout ban and tear down the whole group (set_exec_queue_group_banned()
> and xe_guc_exec_queue_group_trigger_cleanup()) rather than just the
> primary: the primary owns the group's GuC context, so its failure to
> suspend wedges every member.
> 
> v2: suspend whole group upon error in suspend_wait
> 

Same as last patch, if the upper layers are fixed this one LGTM:

Reviewed-by: Matthew Brost <matthew.brost@intel.com>

> Assisted-by: Github-Copilot:Claude-opus-4.8
> Signed-off-by: Niranjana Vishwanathapura <niranjana.vishwanathapura@intel.com>
> ---
>  drivers/gpu/drm/xe/xe_exec_queue.c       |   1 +
>  drivers/gpu/drm/xe/xe_exec_queue_types.h |   6 +
>  drivers/gpu/drm/xe/xe_guc_submit.c       | 174 ++++++++++++++++++++---
>  3 files changed, 161 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/gpu/drm/xe/xe_exec_queue.c b/drivers/gpu/drm/xe/xe_exec_queue.c
> index 1b5ca3ce578a..df95855a3d61 100644
> --- a/drivers/gpu/drm/xe/xe_exec_queue.c
> +++ b/drivers/gpu/drm/xe/xe_exec_queue.c
> @@ -842,6 +842,7 @@ static int xe_exec_queue_group_init(struct xe_device *xe, struct xe_exec_queue *
>  	group->primary = q;
>  	group->cgp_bo = bo;
>  	INIT_LIST_HEAD(&group->list);
> +	spin_lock_init(&group->suspend_lock);
>  	xa_init_flags(&group->xa, XA_FLAGS_ALLOC1);
>  	mutex_init(&group->list_lock);
>  	q->multi_queue.group = group;
> diff --git a/drivers/gpu/drm/xe/xe_exec_queue_types.h b/drivers/gpu/drm/xe/xe_exec_queue_types.h
> index dbb2ee8eb5de..bf76a879aedc 100644
> --- a/drivers/gpu/drm/xe/xe_exec_queue_types.h
> +++ b/drivers/gpu/drm/xe/xe_exec_queue_types.h
> @@ -62,6 +62,12 @@ struct xe_exec_queue_group {
>  	struct list_head list;
>  	/** @list_lock: Secondary queue list lock */
>  	struct mutex list_lock;
> +	/**
> +	 * @suspend_lock: Makes a secondary's suspend/resume and its forwarding
> +	 * to the primary atomic. Nested outside of the queue's message lock
> +	 * (@xe_guc_exec_queue.sched.msg_lock).
> +	 */
> +	spinlock_t suspend_lock;
>  	/** @sync_pending: CGP_SYNC_DONE g2h response pending */
>  	bool sync_pending;
>  	/** @banned: Group banned */
> diff --git a/drivers/gpu/drm/xe/xe_guc_submit.c b/drivers/gpu/drm/xe/xe_guc_submit.c
> index f7a3ff0d0b1d..b1591d02eed0 100644
> --- a/drivers/gpu/drm/xe/xe_guc_submit.c
> +++ b/drivers/gpu/drm/xe/xe_guc_submit.c
> @@ -1681,11 +1681,24 @@ guc_exec_queue_timedout_job(struct drm_sched_job *drm_job)
>  	return DRM_GPU_SCHED_STAT_NO_HANG;
>  }
>  
> +static void guc_exec_queue_multi_queue_drop_suspend(struct xe_exec_queue *q);
> +
>  static void guc_exec_queue_fini(struct xe_exec_queue *q)
>  {
>  	struct xe_guc_exec_queue *ge = q->guc;
>  	struct xe_guc *guc = exec_queue_to_guc(q);
>  
> +	/*
> +	 * A secondary can leave the group while still preempt suspended (e.g.
> +	 * xe_vm_remove_compute_exec_queue() forces its preempt fence to signal,
> +	 * which suspends it). It holds one forwarded suspend reference on the
> +	 * primary, so drop it and resume the primary if it was the last member
> +	 * that had it suspended. Primaries forward to nobody, so they don't need
> +	 * this.
> +	 */
> +	if (xe_exec_queue_is_multi_queue_secondary(q))
> +		guc_exec_queue_multi_queue_drop_suspend(q);
> +
>  	if (xe_exec_queue_is_multi_queue_secondary(q)) {
>  		struct xe_exec_queue_group *group = q->multi_queue.group;
>  
> @@ -2163,17 +2176,22 @@ static int guc_exec_queue_set_multi_queue_priority(struct xe_exec_queue *q,
>  	return 0;
>  }
>  
> -static int guc_exec_queue_suspend(struct xe_exec_queue *q)
> +/*
> + * Core suspend: take a suspend reference on @q and, on the first reference,
> + * disable its GuC context so the GPU is actually preempted. Caller must have
> + * ensured @q is not killed/banned/wedged. Returns true if this was the first
> + * suspend reference (the 0->1 transition).
> + */
> +static bool __guc_exec_queue_suspend(struct xe_exec_queue *q)
>  {
>  	struct xe_guc_exec_queue *ge = q->guc;
>  	struct xe_gpu_scheduler *sched = &ge->sched;
>  	struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_SUSPEND;
> -
> -	if (exec_queue_killed_or_banned_or_wedged(q))
> -		return -EINVAL;
> +	bool first;
>  
>  	xe_sched_msg_lock(sched);
> -	if (++ge->suspend_count == 1) {
> +	first = (++ge->suspend_count == 1);
> +	if (first) {
>  		bool added = guc_exec_queue_try_add_msg(q, msg, SUSPEND);
>  
>  		/* slot must be free at 0->1 */
> @@ -2182,6 +2200,68 @@ static int guc_exec_queue_suspend(struct xe_exec_queue *q)
>  	}
>  	xe_sched_msg_unlock(sched);
>  
> +	return first;
> +}
> +
> +/*
> + * Core resume: drop a suspend reference on @q and, on the last reference,
> + * re-enable its GuC context. Returns true if this dropped the last suspend
> + * reference (the 1->0 transition).
> + */
> +static bool __guc_exec_queue_resume(struct xe_exec_queue *q)
> +{
> +	struct xe_guc_exec_queue *ge = q->guc;
> +	struct xe_gpu_scheduler *sched = &ge->sched;
> +	struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_RESUME;
> +	struct xe_guc *guc = exec_queue_to_guc(q);
> +	bool last;
> +
> +	xe_sched_msg_lock(sched);
> +	xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending);
> +	xe_gt_assert(guc_to_gt(guc), ge->suspend_count > 0);
> +	last = (--ge->suspend_count == 0);
> +	if (last) {
> +		bool added = guc_exec_queue_try_add_msg(q, msg, RESUME);
> +
> +		/* slot must be free at 1->0 */
> +		xe_gt_assert(guc_to_gt(guc), added);
> +	}
> +	xe_sched_msg_unlock(sched);
> +
> +	return last;
> +}
> +
> +static int guc_exec_queue_suspend(struct xe_exec_queue *q)
> +{
> +	if (exec_queue_killed_or_banned_or_wedged(q))
> +		return -EINVAL;
> +
> +	/*
> +	 * Non-multi-queue queues and multi-queue primaries suspend themselves
> +	 * directly: their own msg_lock makes the suspend_count 0->1 transition
> +	 * and the suspend_pending update atomic, so no group level serialization
> +	 * is needed.
> +	 */
> +	if (!xe_exec_queue_is_multi_queue_secondary(q)) {
> +		__guc_exec_queue_suspend(q);
> +		return 0;
> +	}
> +
> +	/*
> +	 * A secondary doesn't interface with GuC: suspend it like any other
> +	 * queue (its own suspend_count drives its internally handled scheduler
> +	 * state) and, only on its own 0->1 transition, forward the suspend to the
> +	 * primary so the GPU is actually preempted. Hold @suspend_lock so that
> +	 * observing the secondary's transition and forwarding it to the primary
> +	 * happen atomically; this keeps the primary's refcount paired with member
> +	 * transitions even if the same secondary is suspended and resumed
> +	 * concurrently across rebind cycles.
> +	 */
> +	scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) {
> +		if (__guc_exec_queue_suspend(q))
> +			__guc_exec_queue_suspend(xe_exec_queue_multi_queue_primary(q));
> +	}
> +
>  	return 0;
>  }
>  
> @@ -2191,6 +2271,19 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q)
>  	struct xe_device *xe = guc_to_xe(guc);
>  	int ret;
>  
> +	/*
> +	 * In multi-queue mode the primary owns the GuC scheduling context for
> +	 * the whole group, so wait on the primary's suspend to complete. All
> +	 * group members share the same GuC/device, so guc, xe and timeout above
> +	 * are computed from @q directly.
> +	 *
> +	 * A secondary's suspend is short-circuited (no GuC round-trip) and, as
> +	 * its SUSPEND message precedes the primary's on the shared FIFO
> +	 * submit_wq, completes before the primary's. So waiting on the primary
> +	 * is sufficient.
> +	 */
> +	q = xe_exec_queue_multi_queue_primary(q);
> +
>  	/*
>  	 * Likely don't need to check exec_queue_killed() as we clear
>  	 * suspend_pending upon kill but to be paranoid but races in which
> @@ -2232,10 +2325,20 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q)
>  		 * tripping the !suspend_pending assert (the RESUME message is
>  		 * dropped for a banned queue), and triggering cleanup tears the
>  		 * context down.
> +		 *
> +		 * @q is the primary here; it owns the group's GuC context, so a
> +		 * failure to suspend it wedges the whole group. Ban and tear
> +		 * down the entire group in the multi-queue case.
>  		 */
> -		set_exec_queue_banned(q);
> -		__suspend_fence_signal(q);
> -		xe_guc_exec_queue_trigger_cleanup(q);
> +		if (xe_exec_queue_is_multi_queue(q)) {
> +			set_exec_queue_group_banned(q);
> +			__suspend_fence_signal(q);
> +			xe_guc_exec_queue_group_trigger_cleanup(q);
> +		} else {
> +			set_exec_queue_banned(q);
> +			__suspend_fence_signal(q);
> +			xe_guc_exec_queue_trigger_cleanup(q);
> +		}
>  		return -ETIME;
>  	} else if (IS_SRIOV_VF(xe) && !WAIT_COND) {
>  		/* Corner case on RESFIX DONE where vf_recovery() changes */
> @@ -2255,21 +2358,52 @@ static int guc_exec_queue_suspend_wait(struct xe_exec_queue *q)
>  
>  static void guc_exec_queue_resume(struct xe_exec_queue *q)
>  {
> -	struct xe_guc_exec_queue *ge = q->guc;
> -	struct xe_gpu_scheduler *sched = &ge->sched;
> -	struct xe_sched_msg *msg = ge->static_msgs + STATIC_MSG_RESUME;
> -	struct xe_guc *guc = exec_queue_to_guc(q);
> +	/*
> +	 * Non-multi-queue queues and multi-queue primaries resume themselves
> +	 * directly; their own msg_lock is sufficient.
> +	 */
> +	if (!xe_exec_queue_is_multi_queue_secondary(q)) {
> +		__guc_exec_queue_resume(q);
> +		return;
> +	}
>  
> -	xe_sched_msg_lock(sched);
> -	xe_gt_assert(guc_to_gt(guc), !ge->suspend_pending);
> -	xe_gt_assert(guc_to_gt(guc), ge->suspend_count > 0);
> -	if (--ge->suspend_count == 0) {
> -		bool added = guc_exec_queue_try_add_msg(q, msg, RESUME);
> +	/*
> +	 * Mirror of guc_exec_queue_suspend(): resume the secondary like any
> +	 * other queue and, only on its own 1->0 transition, forward the resume
> +	 * to the primary so the primary's GuC context is re-enabled once the
> +	 * last member that suspended it resumes. @suspend_lock keeps the
> +	 * secondary transition and the primary forward atomic.
> +	 */
> +	scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) {
> +		if (__guc_exec_queue_resume(q))
> +			__guc_exec_queue_resume(xe_exec_queue_multi_queue_primary(q));
> +	}
> +}
>  
> -		/* slot must be free at 1->0 */
> -		xe_gt_assert(guc_to_gt(guc), added);
> +/*
> + * Drop a leaving secondary's forwarded suspend reference on the primary and
> + * resume the primary if this was the last member that had it suspended.
> + * See guc_exec_queue_fini().
> + */
> +static void guc_exec_queue_multi_queue_drop_suspend(struct xe_exec_queue *q)
> +{
> +	scoped_guard(spinlock, &q->multi_queue.group->suspend_lock) {
> +		struct xe_exec_queue *primary = xe_exec_queue_multi_queue_primary(q);
> +
> +		/*
> +		 * A suspended secondary holds exactly one suspend reference on the
> +		 * primary (forwarded on its 0->1 transition). If it leaves while
> +		 * still suspended, release that reference so the primary is not
> +		 * kept disabled forever.
> +		 */
> +		if (!READ_ONCE(q->guc->suspend_count))
> +			break;
> +
> +		if (exec_queue_killed_or_banned_or_wedged(primary))
> +			break;
> +
> +		__guc_exec_queue_resume(primary);
>  	}
> -	xe_sched_msg_unlock(sched);
>  }
>  
>  static bool guc_exec_queue_reset_status(struct xe_exec_queue *q)
> -- 
> 2.43.0
> 

  reply	other threads:[~2026-07-09 20:43 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-01  5:07 [PATCH v2 0/5] drm/xe: balance exec queue suspend/resume Niranjana Vishwanathapura
2026-07-01  5:07 ` [PATCH v2 1/5] drm/xe: only resume exec queues that were actually suspended Niranjana Vishwanathapura
2026-07-09 20:33   ` Matthew Brost
2026-07-01  5:07 ` [PATCH v2 2/5] drm/xe/hw_engine_group: propagate suspend failures during mode switch Niranjana Vishwanathapura
2026-07-09 20:20   ` Matthew Brost
2026-07-10  4:13     ` Niranjana Vishwanathapura
2026-07-10 18:23       ` Matthew Brost
2026-07-01  5:07 ` [PATCH v2 3/5] drm/xe/guc: wait killably for suspend and ban queue on timeout Niranjana Vishwanathapura
2026-07-09 20:36   ` Matthew Brost
2026-07-09 20:40     ` Matthew Brost
2026-07-10  4:30       ` Niranjana Vishwanathapura
2026-07-10  5:40         ` Matthew Brost
2026-07-01  5:07 ` [PATCH v2 4/5] drm/xe/guc: Add suspend refcount to exec queue ops Niranjana Vishwanathapura
2026-07-09 20:41   ` Matthew Brost
2026-07-01  5:07 ` [PATCH v2 5/5] drm/xe/multi_queue: preempt primary on queue group suspend Niranjana Vishwanathapura
2026-07-09 20:43   ` Matthew Brost [this message]
2026-07-01  5:14 ` ✗ CI.checkpatch: warning for drm/xe: balance exec queue suspend/resume (rev4) Patchwork
2026-07-01  5:15 ` ✓ CI.KUnit: success " Patchwork
2026-07-01  6:06 ` ✓ Xe.CI.BAT: " Patchwork
2026-07-01 21:17 ` ✓ Xe.CI.FULL: " Patchwork

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=alAH3ogvxAPcNmMq@gsse-cloud1.jf.intel.com \
    --to=matthew.brost@intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=niranjana.vishwanathapura@intel.com \
    --cc=thomas.hellstrom@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox