Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
To: intel-xe@lists.freedesktop.org
Cc: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
	"Matthew Brost" <matthew.brost@intel.com>,
	"Rodrigo Vivi" <rodrigo.vivi@intel.com>,
	"Matthew Auld" <matthew.auld@intel.com>
Subject: [PATCH v4 2/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq
Date: Tue, 29 Sep 2026 16:29:09 +0200	[thread overview]
Message-ID: <20260929142910.47480-3-thomas.hellstrom@linux.intel.com> (raw)
In-Reply-To: <20260929142910.47480-1-thomas.hellstrom@linux.intel.com>

xe_vma_destroy() can defer the final teardown of a struct xe_vma to a
dma_fence completion callback (vma_destroy_cb()), and xe_vm_free() (the
drm_gpuvm_ops.vm_free callback) always defers struct xe_vm teardown to
a work item, since destroying a VM needs to sleep. Both used to queue
their work on system_dfl_wq, a global, kernel-wide workqueue that xe
has no control over and never waits on during module unload.

drm_gpuvm_free() drops its drm_device reference immediately after
calling xe_vm_free(), without waiting for the deferred work to run.
The same applies one level down: whichever xe_vma or xe_vm reference
happens to be the last one can trigger this chain from a dma_fence
callback that may fire at an arbitrary time, including after the
owning file has already been closed and its own module reference
dropped. Since nothing tracks or waits for work queued on
system_dfl_wq, `rmmod xe` could succeed and free the module's text
while vma_destroy_work_func() or vm_destroy_work_func() is still
queued or running on it, jumping into freed code.

Fix xe_vm_free() by queueing its work on the per-device xe->destroy_wq
instead. drm_gpuvm still holds a drm_device reference at the point
xe_vm_free() runs (it is only dropped after xe_vm_free() returns), so
xe->destroy_wq is guaranteed to still be alive and to be drained by
xe_device_destroy() before the device, and hence xe->destroy_wq
itself, goes away.

vma_destroy_cb() cannot use the same per-device queue:
xe_vma_destroy_late(), run from that work, can itself drop the last
reference to the owning xe_vm, which would recursively trigger
xe_vm_free() and queue work on the same xe->destroy_wq that is
currently executing this work item, deadlocking
xe_device_destroy()'s destroy_workqueue(xe->destroy_wq) call against
itself. Fix this one by queueing on xe_destroy_wq instead, the
module-lifetime workqueue already used for GuC exec queue teardown.
Unlike the per-device queue, this requires no dereference of a struct
xe_device that may already be gone by the time the deferred callback
fires, and unlike system_dfl_wq it is guaranteed to be drained by
xe_destroy_wq_module_exit() before the module is unloaded, following
the drm_pagemap_dev_hold()/unhold_work precedent of using a workqueue
that is waited on at module unload rather than a bare module
reference. The previous commit's reordering of xe_destroy_wq_exit()
to run after xe_device_exit() guarantees that xe_destroy_wq is only
torn down once the device-count has reached zero, i.e. after any
xe_vma whose teardown queues work here has already dropped its
drm_device reference and thus already queued that work.

v4:
- Add code comments
- Use the xe->destroy_wq for vm->destroy_work (Matt Brost)

Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Assisted-by: LLM
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
---
 drivers/gpu/drm/xe/xe_module.c |  6 ++++--
 drivers/gpu/drm/xe/xe_vm.c     | 18 +++++++++++++++---
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
index c61bd33546f2..897724cb5cfb 100644
--- a/drivers/gpu/drm/xe/xe_module.c
+++ b/drivers/gpu/drm/xe/xe_module.c
@@ -114,8 +114,10 @@ static void xe_destroy_wq_module_exit(void)
  * xe_destroy_wq_queue() - Queue work on the destroy workqueue
  * @work: work item to queue
  *
- * The destroy workqueue has module lifetime and is used for GuC exec queue
- * teardown that can outlive a single xe_device. SVM pagemap destroy uses the
+ * The destroy workqueue has module lifetime, and is guaranteed to outlive
+ * any xe_device, and to be drained before the module is unloaded. It is used
+ * for GuC exec queue and xe_vm/xe_vma teardown that can be deferred past the
+ * lifetime of the xe_device that triggered it. SVM pagemap destroy uses the
  * per-device xe->destroy_wq instead.
  *
  * Return: %true if @work was queued, %false if it was already pending.
diff --git a/drivers/gpu/drm/xe/xe_vm.c b/drivers/gpu/drm/xe/xe_vm.c
index 390da884c727..e07384c1c2dc 100644
--- a/drivers/gpu/drm/xe/xe_vm.c
+++ b/drivers/gpu/drm/xe/xe_vm.c
@@ -29,6 +29,7 @@
 #include "xe_exec_queue.h"
 #include "xe_gt.h"
 #include "xe_migrate.h"
+#include "xe_module.h"
 #include "xe_pagefault.h"
 #include "xe_pat.h"
 #include "xe_pm.h"
@@ -1249,7 +1250,14 @@ static void vma_destroy_cb(struct dma_fence *fence,
 	struct xe_vma *vma = container_of(cb, struct xe_vma, destroy_cb);
 
 	INIT_WORK(&vma->destroy_work, vma_destroy_work_func);
-	queue_work(system_dfl_wq, &vma->destroy_work);
+
+	/*
+	 * The destroy work puts a vm reference which may put the last
+	 * device reference. Hence we can't queue this work on the device
+	 * destroy queue since that may deadlock. Use the module-wide
+	 * destroy queue.
+	 */
+	xe_destroy_wq_queue(&vma->destroy_work);
 }
 
 static void xe_vm_assert_write_mode_or_garbage_collector(struct xe_vm *vm)
@@ -2058,8 +2066,12 @@ static void xe_vm_free(struct drm_gpuvm *gpuvm)
 {
 	struct xe_vm *vm = container_of(gpuvm, struct xe_vm, gpuvm);
 
-	/* To destroy the VM we need to be able to sleep */
-	queue_work(system_dfl_wq, &vm->destroy_work);
+	/*
+	 * To destroy the VM we need to be able to sleep.
+	 * drm_gpuvm keeps at least one device reference at this
+	 * point so xe->destroy_wq must still be alive.
+	 */
+	queue_work(vm->xe->destroy_wq, &vm->destroy_work);
 }
 
 struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id)
-- 
2.55.0


  parent reply	other threads:[~2026-09-29 14:29 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-29 14:29 [PATCH v4 0/3] drm/xe: Protect against premature module unloads Thomas Hellström
2026-09-29 14:29 ` [PATCH v4 1/3] drm/xe: Don't unload the driver until all drm devices are freed Thomas Hellström
2026-09-29 14:41   ` sashiko-bot
2026-09-29 14:51     ` Thomas Hellström
2026-09-29 14:29 ` Thomas Hellström [this message]
2026-09-29 14:29 ` [PATCH v4 3/3] drm/xe: Route execlist exec queue teardown off system_dfl_wq Thomas Hellström
2026-09-29 16:18   ` Matthew Brost
2026-09-29 14:38 ` ✓ CI.KUnit: success for drm/xe: Protect against premature module unloads Patchwork
2026-09-29 16:04 ` ✓ Xe.CI.BAT: " Patchwork
2026-09-29 18:14 ` ✗ Xe.CI.FULL: failure " Patchwork
2026-10-01 10:06   ` Thomas Hellström

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=20260929142910.47480-3-thomas.hellstrom@linux.intel.com \
    --to=thomas.hellstrom@linux.intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=matthew.auld@intel.com \
    --cc=matthew.brost@intel.com \
    --cc=rodrigo.vivi@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