All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Raag Jadav" <raag.jadav@intel.com>, intel-xe@lists.freedesktop.org
Subject: Re: [PATCH v12 07/10] drm/xe/pm: Introduce xe_device_suspend/resume()
Date: Tue, 03 Nov 2026 06:48:55 +0000	[thread overview]
Message-ID: <sashiko-outbox-194672@kernel.org> (raw)
In-Reply-To: <20261103060649.1015468-8-raag.jadav@intel.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] drm/xe: work_tdr livelock and scheduler race in xe_device_suspend() FLR path
- [High] drm/xe: missing ULLS teardown and synchronization across FLR in xe_device_suspend()
- [High] drm/xe: missing re-initialization of xe->cpu_bind->q in xe_device_resume()
--

commit c9d7243b4c02644f3740b46ba51d0f51d005d2ba
Author: Raag Jadav <raag.jadav@intel.com>

drm/xe/pm: Introduce xe_device_suspend/resume()

This commit extracts the system suspend and resume logic from xe_pm.c into
common xe_device_suspend() and xe_device_resume() helpers in xe_device.c,
adding an flr parameter to support PCIe FLR preparation and
re-initialization alongside system suspend and resume.

> diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
> index 07a7ab6ac0832..c54a42cdf9a47 100644
> --- a/drivers/gpu/drm/xe/xe_device.c
> +++ b/drivers/gpu/drm/xe/xe_device.c

[ ... ]

> @@ -1384,3 +1386,150 @@ void xe_device_exit(void)
>  		wait_var_event(&xe_device_count, !atomic_read(&xe_device_count));
>  	}
>  }
> +
> +/**
> + * xe_device_suspend() - Common helper for system suspend and FLR prepare
> + * @xe: xe device instance
> + * @flr: indicate FLR prepare
> + *
> + * Return: 0 on success, negative error code otherwise.
> + */
> +int xe_device_suspend(struct xe_device *xe, bool flr)
> +{
> +	struct xe_gt *gt;
> +	u8 id;
> +	int err;
> +
> +	err = xe_pxp_pm_suspend(xe->pxp);
> +	if (err)
> +		return err;
> +
> +	xe_late_bind_wait_for_worker_completion(&xe->late_bind);
> +
> +	for_each_gt(gt, xe, id)
> +		xe_gt_suspend_prepare(gt);
> +
> +	if (flr) {
> +		for_each_gt(gt, xe, id)
> +			xe_gt_flr_prepare(gt);

[Severity: High]
Can this FLR preparation path livelock on sched->base.work_tdr or leave the
scheduler running when xe_device_resume() later calls xe_sched_reinit()?

In xe_gt_flr_prepare() -> xe_uc_flr_prepare(), uc_flr_prepare() and
uc_flr_sanitize() are queued and flushed back-to-back on gt->ordered_wq:

	INIT_WORK_ONSTACK(&flr.work, uc_flr_prepare);
	queue_work(gt->ordered_wq, &flr.work);
	flush_work(&flr.work);
	destroy_work_on_stack(&flr.work);

	INIT_WORK_ONSTACK(&flr.work, uc_flr_sanitize);
	queue_work(gt->ordered_wq, &flr.work);
	flush_work(&flr.work);
	destroy_work_on_stack(&flr.work);

When an exec queue has jobs in flight, uc_flr_prepare() ->
xe_guc_flr_prepare() -> xe_guc_submit_pause_abort() starts submission and
calls guc_exec_queue_kill() -> xe_guc_exec_queue_trigger_cleanup() ->
xe_sched_tdr_queue_imm(), which sets sched->base.timeout = 0 and queues
sched->base.work_tdr (drm_sched_job_timedout) on gt->ordered_wq with delay 0.

When drm_sched_job_timedout() runs on gt->ordered_wq before
uc_flr_sanitize(), guc_exec_queue_timedout_job() marks the job fence with
-ETIME, calls xe_sched_submission_start(), and returns
DRM_GPU_SCHED_STAT_NO_HANG. drm_sched_job_timedout() then calls
drm_sched_job_reinsert_on_false_timeout(), which puts the signaled job back
on sched->base.pending_list and queues work_free_job on
sched->base.submit_wq (a separate workqueue from gt->ordered_wq), followed
immediately by drm_sched_start_timeout_unlocked(). Unless work_free_job on
submit_wq has already completed on another CPU and removed the job from
pending_list before that call, drm_sched_start_timeout() sees
sched->base.timeout == 0 and a non-empty pending_list, re-queueing work_tdr
with delay 0 on gt->ordered_wq behind uc_flr_sanitize().

Next on gt->ordered_wq, uc_flr_sanitize() -> xe_uc_stop() -> xe_guc_stop() ->
xe_guc_submit_stop() -> guc_exec_queue_stop() calls
xe_sched_submission_stop(), which sets sched->base.pause_submit = true and
cancels work_free_job. If work_free_job had not run yet on submit_wq, the
signaled job remains on sched->base.pending_list. When the re-queued
work_tdr runs, guc_exec_queue_timedout_job() sees DMA_FENCE_FLAG_SIGNALED_BIT
set and returns DRM_GPU_SCHED_STAT_NO_HANG without starting the scheduler;
drm_sched_job_reinsert_on_false_timeout() re-adds the job to pending_list
while drm_sched_run_free_queue() is a no-op because drm_sched_is_stopped() is
true, and drm_sched_start_timeout_unlocked() re-queues work_tdr with delay 0
in an infinite loop on gt->ordered_wq.

Conversely, if a job was still in entity->job_queue when uc_flr_prepare()
ran, work_run_job on sched->base.submit_wq can pop the job into pending_list
and queue work_tdr on gt->ordered_wq after uc_flr_sanitize() was queued.
After uc_flr_sanitize() stops the scheduler, work_tdr runs
guc_exec_queue_timedout_job(), which calls xe_sched_submission_start() and
leaves pause_submit == false, triggering:

	xe_gt_assert(ge->q->gt, drm_sched_is_stopped(&sched->base));

in xe_sched_reinit() during xe_device_resume(xe, true).

[Severity: High]
What stops tile->migrate->ulls.exit_work from racing with FLR preparation
and re-initialization or leaving stale ULLS state after reset?

On DGFX platforms, GPU page faults (xe_pagefault_service()) and SVM
prefetches (xe_vm_prefetch()) call xe_migrate_ulls_enter(), which sets
m->ulls.enabled = true under m->job_mutex, submits a ULLS_ENTER job to m->q,
and schedules m->ulls.exit_work (xe_migrate_ulls_exit()) on
system_percpu_wq. Neither xe_device_suspend(xe, true) nor
xe_device_resume(xe, true) (via xe_gt_reinit() -> xe_migrate_reinit())
disables or flushes m->ulls.exit_work, takes m->job_mutex, or resets
m->ulls.enabled (unlike xe_migrate_fini()).

This appears to allow three failure modes across FLR:

1. If m->ulls.exit_work fires while m->q->guc->sched is stopped (after
uc_flr_sanitize() during the FLR window, or in xe_device_resume(xe, true)
before xe_migrate_reinit()), xe_migrate_ulls_exit() sees
xe_exec_queue_is_idle(m->q, 1) and calls __xe_migrate_job_push(m, job,
ULLS_EXIT), which arms the job via xe_sched_job_arm() ->
xe_lrc_init_seqno_fence(), incrementing m->q->lrc[0]->fence_ctx.next_seqno
and queueing the job on m->q->entity. When xe_migrate_reinit() ->
xe_exec_queue_reinit(m->q) -> xe_lrc_ctx_init() runs without m->job_mutex,
it writes lrc->fence_ctx.next_seqno - 1 into __xe_lrc_seqno_map(lrc) and
__xe_lrc_start_seqno_map(lrc), marking the unexecuted ULLS_EXIT job's seqno
as completed in the hardware seqno page before the job is emitted in
xe_guc_submit_start().

2. If m->ulls.exit_work fires during xe_uc_flr_prepare() between
xe_guc_submit_pause_abort() (which sets EXEC_QUEUE_STATE_KILLED on m->q) and
uc_flr_sanitize(), guc_exec_queue_run_job() sees
exec_queue_killed_or_banned_or_wedged(m->q), skips q->ring_ops->emit_job(job)
(leaving job->ptrs[0].head == 0), and places the unsignaled ULLS_EXIT job on
sched->base.pending_list. During xe_device_resume(xe, true), after
xe_lrc_ctx_init() writes MI_ARB_ON_OFF into the ring, xe_guc_submit_start()
-> guc_exec_queue_start(m->q) finds that pending job and rewinds
lrc->ring.tail, RING_HEAD, and RING_TAIL to job->ptrs[0].head (0),
overwriting the ring state initialized by xe_lrc_ctx_init().

3. If m->ulls.exit_work has not fired before FLR resume completes,
m->ulls.enabled remains true even though the hardware reset wiped engine
state, causing subsequent xe_migrate_ulls_enter() calls to skip submitting a
ULLS_ENTER job.

[ ... ]

> +int xe_device_resume(struct xe_device *xe, bool flr)
> +{
> +	struct xe_tile *tile;
> +	struct xe_gt *gt;
> +	u8 id;
> +	int err;
> +
> +	err = xe_pcode_ready(xe, true);
> +	if (err)
> +		return err;
> +
> +	assert_lmem_ready(xe);
> +
> +	for_each_tile(tile, xe, id)
> +		xe_wa_apply_tile_workarounds(tile);
> +
> +	if (flr) {
> +		err = xe_bo_restore_map(xe);
> +		if (err)
> +			return err;
> +
> +		for_each_gt(gt, xe, id) {
> +			err = xe_gt_reinit(gt);
> +			if (err)
> +				return err;
> +		}
> +	}

[Severity: High]
Does the FLR re-initialization branch also need to re-initialize
xe->cpu_bind->q?

During probe, xe_device_probe() -> xe_cpu_bind_init() creates the device-wide
kernel bind queue xe->cpu_bind->q with EXEC_QUEUE_FLAG_KERNEL |
EXEC_QUEUE_FLAG_MIGRATE, and guc_exec_queue_init() inserts it into
guc->submission_state.exec_queue_lookup.

During xe_device_suspend(xe, true), xe_gt_flr_prepare() ->
xe_uc_flr_prepare() -> xe_guc_flr_prepare() -> xe_guc_submit_pause_abort()
iterates over guc->submission_state.exec_queue_lookup and calls
guc_exec_queue_kill() on every queue, which sets EXEC_QUEUE_STATE_KILLED on
xe->cpu_bind->q and zeroes its scheduler timeout.

In xe_device_resume(xe, true), xe_gt_reinit() only re-initializes
tile->migrate->q via xe_migrate_reinit() -> xe_exec_queue_reinit(m->q),
leaving xe->cpu_bind->q with EXEC_QUEUE_STATE_KILLED set. When a subsequent
pipelined page-table update is scheduled on xe->cpu_bind->q (via
xe_vm_rebind(), xe_vma_rebind(), xe_vm_range_rebind(), or
xe_vm_range_unbind() -> xe_cpu_bind_update_pgtables_job()),
guc_exec_queue_run_job() sees exec_queue_killed_or_banned_or_wedged(q) as
true:

	if (is_pt_job(job)) {
		xe_gt_assert(guc_to_gt(guc), !exec_queue_registered(q));

		run_pt_job(guc_to_xe(guc), job,
			   killed_or_banned_or_wedged_or_error);

and passes force_clear = true to run_pt_job() ->
xe_cpu_bind_update_pgtables_execute(), clearing page table entries instead of
populating them while still committing the VMA as present.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103060649.1015468-1-raag.jadav@intel.com?part=7

  reply	other threads:[~2026-11-03  6:49 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03  6:06 [PATCH v12 00/10] Introduce Xe PCIe FLR Raag Jadav
2026-11-03  6:06 ` [PATCH v12 01/10] drm/xe/uc_fw: Allow re-initializing firmware Raag Jadav
2026-11-03  6:06 ` [PATCH v12 02/10] drm/xe/guc_submit: Introduce guc_exec_queue_reinit_kernel() Raag Jadav
2026-11-03  6:06 ` [PATCH v12 03/10] drm/xe/gt: Introduce FLR helpers Raag Jadav
2026-11-03  6:39   ` sashiko-bot
2026-11-03  6:06 ` [PATCH v12 04/10] drm/xe/bo_evict: Introduce xe_bo_restore_map() Raag Jadav
2026-11-03  6:06 ` [PATCH v12 05/10] drm/xe/exec_queue: Introduce xe_exec_queue_reinit() Raag Jadav
2026-11-03  6:06 ` [PATCH v12 06/10] drm/xe/migrate: Introduce xe_migrate_reinit() Raag Jadav
2026-11-03  6:31   ` sashiko-bot
2026-11-03  6:06 ` [PATCH v12 07/10] drm/xe/pm: Introduce xe_device_suspend/resume() Raag Jadav
2026-11-03  6:48   ` sashiko-bot [this message]
2026-11-03  6:06 ` [PATCH v12 08/10] drm/xe: Introduce temporary device wedging Raag Jadav
2026-11-03  6:44   ` sashiko-bot
2026-11-03  6:06 ` [PATCH v12 09/10] drm/xe/pci: Introduce PCIe Function Level Reset Raag Jadav
2026-11-03  6:46   ` sashiko-bot
2026-11-03  6:06 ` [PATCH v12 10/10] drm/xe/doc: Wire up PCI Error Handling Raag Jadav
2026-11-03  6:51 ` ✗ CI.checkpatch: warning for Introduce Xe PCIe FLR (rev12) Patchwork
2026-11-03  6:53 ` ✓ CI.KUnit: success " Patchwork
2026-11-03  7:29 ` ✓ Xe.CI.BAT: " Patchwork
2026-11-03 12:43 ` ✓ 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=sashiko-outbox-194672@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=raag.jadav@intel.com \
    --cc=sashiko-reviews@lists.linux.dev \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.