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 1/3] drm/xe: Don't unload the driver until all drm devices are freed
Date: Tue, 29 Sep 2026 16:29:08 +0200 [thread overview]
Message-ID: <20260929142910.47480-2-thomas.hellstrom@linux.intel.com> (raw)
In-Reply-To: <20260929142910.47480-1-thomas.hellstrom@linux.intel.com>
xe, and shared helpers it uses such as drm_gpuvm, already take bare
drm_device references (drm_dev_get()) from several contexts, for
example GuC submission fence workers, EU stall, OA and PMU sampling
code, and drm_gpuvm's own vm object lifetime, without pairing them
with a module reference. Since xe_exit() is only invoked after the
module's own refcount has dropped to zero, none of these references
currently prevent `rmmod xe` from proceeding while they, or the
underlying xe_device release path they can trigger, are still
outstanding, i.e. driver code belonging to a module whose text is
being freed could still end up executing.
Close this gap by keeping a device-count and waiting for it to reach
zero at module unload.
The wait for the device-count to reach zero at module unload is
unbounded and non-interruptible. Rather than blocking silently forever
if a reference is ever leaked, use wait_var_event_timeout() with a 20s
timeout, well above the typical maximum dma_fence signalling time, and
warn once if devices still remain by then, before falling back to an
unbounded wait_var_event() so a stuck rmmod is at least observable
instead of an indefinite, silent hang.
Note that if a reference genuinely leaks, this still ends up as an
indefinite uninterruptible sleep, which may eventually trip the
kernel's hung-task watchdog. The alternative would be for these bare
drm_device references to also take a module reference, which would
instead make the module unable to be unloaded unless all its devices are
manually unbound first. The wait-based approach is chosen here since it
keeps rmmod usable in the common case.
xe_device_exit() is added as a new module exit hook. Its entry in the
init_funcs[] table is placed between xe_destroy_wq_module_init and
xe_register_pci_driver, so that (exit functions run in reverse array
order) it executes after xe_unregister_pci_driver() has forced all
devices to unbind, but before xe_destroy_wq_module_exit() tears down
the module-lifetime xe_destroy_wq. This preserves xe_destroy_wq's
existing teardown ordering relative to xe_sched_job_module_exit() and
xe_hw_fence_module_exit(), which destroy kmem_caches that work drained
from xe_destroy_wq relies on, while ensuring xe_destroy_wq itself is
only torn down once xe_device_exit() has confirmed no more work can be
queued onto it.
v2:
- Updated commit message to describe the actual implementation (a
single 20s wait_var_event_timeout() followed by one pr_warn() and an
unbounded wait_var_event(), rather than a loop retrying with a
diagnostic every 10s) and its uninterruptible-sleep tradeoff
(sashiko)
v4:
- No longer sync on drmm release callbacks finished execution
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
Assisted-by: LLM
Reviewed-by: Matthew Brost <matthew.brost@intel.com> # v3
---
drivers/gpu/drm/xe/xe_device.c | 29 +++++++++++++++++++++++++++++
drivers/gpu/drm/xe/xe_device.h | 2 ++
drivers/gpu/drm/xe/xe_module.c | 18 +++++++++++++++++-
3 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c
index 205cb4e7f9e8..10436b5bd03f 100644
--- a/drivers/gpu/drm/xe/xe_device.c
+++ b/drivers/gpu/drm/xe/xe_device.c
@@ -372,6 +372,9 @@ bool xe_device_is_admin_only(const struct xe_device *xe)
}
#endif
+/* Number of allocated struct xe_device */
+static atomic_t xe_device_count;
+
static void xe_device_destroy(struct drm_device *dev, void *dummy)
{
struct xe_device *xe = to_xe_device(dev);
@@ -391,6 +394,9 @@ static void xe_device_destroy(struct drm_device *dev, void *dummy)
destroy_workqueue(xe->destroy_wq);
ttm_device_fini(&xe->ttm);
+
+ if (atomic_dec_and_test(&xe_device_count))
+ wake_up_var(&xe_device_count);
}
/**
@@ -461,6 +467,7 @@ int xe_device_init_early(struct xe_device *xe)
return err;
xe_bo_dev_init(&xe->bo_device);
+ atomic_inc(&xe_device_count);
err = drmm_add_action_or_reset(&xe->drm, xe_device_destroy, NULL);
if (err)
return err;
@@ -1501,3 +1508,25 @@ struct xe_vm *xe_device_asid_to_vm(struct xe_device *xe, u32 asid)
return vm;
}
+
+/**
+ * xe_device_exit() - Device subsystem exit function.
+ *
+ * Exit function to be called at module unload time.
+ */
+void xe_device_exit(void)
+{
+ /*
+ * Wait for all devices to be freed. 20s is well above the typical
+ * maximum dma_fence signalling time, so warn and keep waiting if
+ * we're still not done by then, since it may indicate a leaked
+ * xe_device reference is stalling module unload.
+ */
+ if (!wait_var_event_timeout(&xe_device_count,
+ !atomic_read(&xe_device_count),
+ HZ * 20)) {
+ pr_warn("%s: Waiting for %d xe device(s) to be freed before unloading.\n",
+ DRIVER_NAME, atomic_read(&xe_device_count));
+ wait_var_event(&xe_device_count, !atomic_read(&xe_device_count));
+ }
+}
diff --git a/drivers/gpu/drm/xe/xe_device.h b/drivers/gpu/drm/xe/xe_device.h
index 6d3d6d5eba29..83d6dafab53c 100644
--- a/drivers/gpu/drm/xe/xe_device.h
+++ b/drivers/gpu/drm/xe/xe_device.h
@@ -283,6 +283,8 @@ static inline bool xe_device_is_admin_only(const struct xe_device *xe)
}
#endif
+void xe_device_exit(void);
+
/*
* Occasionally it is seen that the G2H worker starts running after a delay of more than
* a second even after being queued and activated by the Linux workqueue subsystem. This
diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
index 4bc28dfc1992..c61bd33546f2 100644
--- a/drivers/gpu/drm/xe/xe_module.c
+++ b/drivers/gpu/drm/xe/xe_module.c
@@ -12,7 +12,7 @@
#include <drm/drm_module.h>
#include "xe_defaults.h"
-#include "xe_device_types.h"
+#include "xe_device.h"
#include "xe_drv.h"
#include "xe_configfs.h"
#include "xe_hw_fence.h"
@@ -162,6 +162,22 @@ static const struct init_funcs init_funcs[] = {
.init = xe_destroy_wq_module_init,
.exit = xe_destroy_wq_module_exit,
},
+ /*
+ * xe_destroy_wq_module_exit() must run after xe_device_exit()
+ * (below), since freeing a device can still queue work on
+ * xe_destroy_wq that must be drained before the module can safely
+ * unload. At the same time, xe_device_exit() must run after
+ * xe_unregister_pci_driver() (below), and xe_destroy_wq_module_exit()
+ * must run before xe_sched_job_module_exit() and
+ * xe_hw_fence_module_exit() (above), whose kmem_caches are still used
+ * by work drained from xe_destroy_wq. Exit functions run in reverse
+ * array order, so this entry must sit between the
+ * xe_destroy_wq_module_init entry above and the xe_register_pci_driver
+ * entry below.
+ */
+ {
+ .exit = xe_device_exit,
+ },
{
.init = xe_register_pci_driver,
.exit = xe_unregister_pci_driver,
--
2.55.0
next prev 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 ` Thomas Hellström [this message]
2026-09-29 14:41 ` [PATCH v4 1/3] drm/xe: Don't unload the driver until all drm devices are freed sashiko-bot
2026-09-29 14:51 ` Thomas Hellström
2026-09-29 14:29 ` [PATCH v4 2/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq Thomas Hellström
2026-09-29 14:29 ` [PATCH v4 3/3] drm/xe: Route execlist exec queue " 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-2-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 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.