* [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads
@ 2026-09-25 13:33 Thomas Hellström
2026-09-25 13:33 ` [PATCH v3 1/3] drm: Provide a drm_dev_release_barrier() function to wait for device release callbacks Thomas Hellström
` (3 more replies)
0 siblings, 4 replies; 14+ messages in thread
From: Thomas Hellström @ 2026-09-25 13:33 UTC (permalink / raw)
To: intel-xe
Cc: Thomas Hellström, Matthew Brost, Rodrigo Vivi, Matthew Auld,
dri-devel, Danilo Krummrich, Alice Ryhl, Alex Deucher,
Christian König
Driver and shared DRM helper code is increasingly relying on bare
drm_device references (drm_dev_get()/drm_dev_put()) to keep a device's
software state around, without also pairing that with a reference on
the owning kernel module. Xe itself does this in several places, and
so does drm_gpuvm for the lifetime of a GPU VM. None of these
references currently prevent the owning module from being unloaded
while they, or the teardown work they can still trigger, are
outstanding, meaning driver code can end up executing after its own
module's text has already been freed.
This series closes that gap for xe:
- Patch 1 adds core DRM infrastructure allowing a driver to wait for
its outstanding device-release callbacks to finish before
proceeding with module unload.
- Patch 2 makes xe use this infrastructure to hold up module unload
until every xe_device instance has actually been released, rather
than only until the module's own refcount happens to reach zero,
with a diagnostic if this ends up taking an unexpectedly long time.
- Patch 3 fixes a related, previously unprotected case where the
teardown of a GPU VM or its address space mappings can be deferred
to run at an arbitrary later time, including after module unload has
already completed.
Together, these changes ensure `rmmod xe` cannot free the module's
memory while any of its devices, or asynchronous work stemming from
them, might still be executing.
v2:
- Use plain WARN_ON_ONCE() instead of drm_WARN_ON_ONCE(NULL, ...) in
drm_dev_release_barrier(), fixing a NULL pointer dereference on the
warning path itself (patch 1, sashiko)
- Updated the commit message of patch 2 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)
v3:
- drm_dev_release_barrier() now uses a single, global SRCU domain
shared by all drivers instead of requiring each driver to register
its own via a new &drm_driver.release_srcu field, accepting that a
driver's call may then occasionally block on unrelated drivers'
release callbacks (patch 1, patch 2, Christian König)
Thomas Hellström (3):
drm: Provide a drm_dev_release_barrier() function to wait for device
release callbacks
drm/xe: Don't unload the driver until all drm devices are freed
drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq
drivers/gpu/drm/drm_drv.c | 61 ++++++++++++++++++++++++++++++++++
drivers/gpu/drm/xe/xe_device.c | 32 ++++++++++++++++++
drivers/gpu/drm/xe/xe_device.h | 2 ++
drivers/gpu/drm/xe/xe_module.c | 24 +++++++++++--
drivers/gpu/drm/xe/xe_vm.c | 5 +--
include/drm/drm_drv.h | 1 +
6 files changed, 120 insertions(+), 5 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH v3 1/3] drm: Provide a drm_dev_release_barrier() function to wait for device release callbacks 2026-09-25 13:33 [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Thomas Hellström @ 2026-09-25 13:33 ` Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 2/3] drm/xe: Don't unload the driver until all drm devices are freed Thomas Hellström ` (2 subsequent siblings) 3 siblings, 0 replies; 14+ messages in thread From: Thomas Hellström @ 2026-09-25 13:33 UTC (permalink / raw) To: intel-xe Cc: Thomas Hellström, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Danilo Krummrich, Alice Ryhl, Alex Deucher, Christian König Driver and drm helper code is increasingly relying on holding a bare &drm_device reference (drm_dev_get()) without also holding a module reference on the module that created the device. For example drm_gpuvm_init() takes a drm_dev_get() reference on the &drm_gpuvm's behalf with no accompanying module reference at all, and drm_gpuvm_free() later calls the driver-supplied gpuvm->ops->vm_free() callback, which lives in the driver module, before dropping that reference. xe also takes bare drm_device references itself from several asynchronous contexts, such as GuC submission fence workers, EU stall, OA and PMU sampling code, relying only on those references being dropped before the underlying xe_device, and eventually the driver module, can be torn down. If the module that created such a device is unloaded while one of these bare references is still outstanding, and the corresponding drm_dev_put() only completes after the module has already been removed, the driver's ->release() callback, any drm managed release actions, or a driver callback such as gpuvm->ops->vm_free(), all of which live in that module's, by then freed, code, can end up being invoked out of memory that no longer contains valid code. Requiring every one of these bare drm_device references to also take a module reference, as drm_pagemap does today via try_module_get(), doesn't scale to shared helpers and driver-internal code with many call sites, and is easy to get wrong. Fix this properly by letting drivers keep a drm device-count and ensure the module isn't unloaded until that count has dropped to zero and until any release callback that had already started executing has also finished executing. To help with the latter, add a drm_dev_release_barrier() function. The function ensures that any caller that has started executing device release callbacks has also finished executing them. Use SRCU for the implementation, with a single, global SRCU domain shared by all drivers. This means a driver's call to drm_dev_release_barrier() may occasionally end up waiting for an unrelated driver's release callback to finish, but release callbacks are expected to run quickly, and sharing one domain avoids the bookkeeping that a per-driver SRCU domain would require, such as adding a new &drm_driver field that every existing struct drm_driver instance in the tree would need to be audited for, and initializing and cleaning up that domain around driver (un)registration. v2: - Use plain WARN_ON_ONCE() instead of drm_WARN_ON_ONCE(NULL, ...) in drm_dev_release_barrier(), since passing a NULL drm_device caused the warning path itself to dereference that NULL pointer inside dev_driver_string()/dev_name() (sashiko) v3: - Use a single, global SRCU domain shared by all drivers instead of requiring each driver to supply its own via a new &drm_driver.release_srcu field, accepting that a driver's call to drm_dev_release_barrier() may then occasionally block on unrelated drivers' release callbacks (Christian König) Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com> Assisted-by: LLM --- drivers/gpu/drm/drm_drv.c | 61 +++++++++++++++++++++++++++++++++++++++ include/drm/drm_drv.h | 1 + 2 files changed, 62 insertions(+) diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 0cdc606af8d1..0641e61ee931 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -918,21 +918,65 @@ struct drm_device *drm_dev_alloc(const struct drm_driver *driver, } EXPORT_SYMBOL(drm_dev_alloc); +/* + * Single, global SRCU domain used to synchronize completion of every + * driver's @release callback and drm managed release actions with + * drm_dev_release_barrier(). Sharing one domain across all drivers means a + * driver's call to drm_dev_release_barrier() may occasionally have to wait + * for unrelated drivers' release callbacks to finish, but that's a + * reasonable trade-off given that release callbacks are expected to run + * quickly, and it avoids the bookkeeping of a per-driver SRCU domain. + */ +DEFINE_STATIC_SRCU(drm_release_srcu); + static void drm_dev_release(struct kref *ref) { struct drm_device *dev = container_of(ref, struct drm_device, ref); + int idx; /* Just in case register/unregister was never called */ drm_debugfs_dev_fini(dev); + idx = srcu_read_lock(&drm_release_srcu); + if (dev->driver->release) dev->driver->release(dev); drm_managed_release(dev); + srcu_read_unlock(&drm_release_srcu, idx); + kfree(dev->managed.final_kfree); } +/** + * drm_dev_release_barrier() - Ensure drm device release callbacks are finished + * + * If a device release method or any of the drm managed release callbacks + * have been called for any drm_device, wait until all of them have finished + * executing. This function can be used to help determine whether it's safe + * to unload a driver module. + * + * Assume for example a driver maintains a device count which is decremented + * using a drmm callback or a device release callback. From a drm device + * lifetime POV, it's then safe to unload the driver when that device-count + * has reached zero and drm_dev_release_barrier() has been called. + * + * Since a single, global SRCU domain is used for all drivers, this function + * may also end up waiting for unrelated drivers' release callbacks to + * complete. + * + * This function only waits for the &drm_driver.release callback and drm + * managed release actions to finish. It does not, by itself, guarantee that + * whoever called drm_dev_put() to drop the reference triggering that release + * has itself finished running. See drm_dev_put() for that invariant. + */ +void drm_dev_release_barrier(void) +{ + synchronize_srcu(&drm_release_srcu); +} +EXPORT_SYMBOL(drm_dev_release_barrier); + /** * drm_dev_get - Take reference of a DRM device * @dev: device to take reference of or NULL @@ -958,6 +1002,23 @@ EXPORT_SYMBOL(drm_dev_get); * * This decreases the ref-count of @dev by one. The device is destroyed if the * ref-count drops to zero. + * + * If this call may drop the last reference, the calling code itself is + * responsible for ensuring it isn't unloaded (for example as part of a + * module) before this call has returned. This matters in particular for + * drivers relying on drm_dev_release_barrier() to determine when it's safe to + * unload, since that function only waits for the &drm_driver.release + * callback and drm managed release actions to finish, not for whoever calls + * drm_dev_put() to finish calling it. + * + * A common case is dropping the last reference from a deferred context, such + * as a workqueue item. In that case it's the responsibility of whoever + * queued that work item to guarantee it has run to completion before the + * module can unload, for example by draining a module-lifetime workqueue at + * module exit time. Holding a module reference only until the work item + * starts running is insufficient: that reference would already be dropped + * before this call runs, even though this call is what may still need the + * module's code to remain resident. */ void drm_dev_put(struct drm_device *dev) { diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h index b23830494ed4..169b404df54a 100644 --- a/include/drm/drm_drv.h +++ b/include/drm/drm_drv.h @@ -485,6 +485,7 @@ void drm_dev_exit(int idx); void drm_dev_unplug(struct drm_device *dev); int drm_dev_wedged_event(struct drm_device *dev, unsigned long method, struct drm_wedge_task_info *info); +void drm_dev_release_barrier(void); /** * drm_dev_is_unplugged - is a DRM device unplugged -- 2.55.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 2/3] drm/xe: Don't unload the driver until all drm devices are freed 2026-09-25 13:33 [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 1/3] drm: Provide a drm_dev_release_barrier() function to wait for device release callbacks Thomas Hellström @ 2026-09-25 13:33 ` Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq Thomas Hellström 2026-09-25 16:18 ` [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Danilo Krummrich 3 siblings, 0 replies; 14+ messages in thread From: Thomas Hellström @ 2026-09-25 13:33 UTC (permalink / raw) To: intel-xe Cc: Thomas Hellström, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Danilo Krummrich, Alice Ryhl, Alex Deucher, Christian König 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, then waiting for any release callback that has started executing to finish, using the drm_dev_release_barrier() infrastructure introduced in the previous commit. 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. drm_dev_release_barrier() uses a single, global SRCU domain shared by all drivers, so xe's wait for its own release callbacks may occasionally end up blocking on an unrelated driver's release path too, a trade-off accepted in favor of not requiring each driver to set up and tear down its own SRCU domain. 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) v3: - drm_dev_release_barrier() no longer takes a &drm_driver argument since it now uses a single, global SRCU domain; drop the &drm_driver.release_srcu registration on both xe drm_driver instances accordingly (Christian König) 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_device.c | 32 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/xe/xe_device.h | 2 ++ drivers/gpu/drm/xe/xe_module.c | 18 +++++++++++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/xe/xe_device.c b/drivers/gpu/drm/xe/xe_device.c index 205cb4e7f9e8..c54d734bd890 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,28 @@ 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)); + } + + /* Wait for any driver release callbacks to complete */ + drm_dev_release_barrier(); +} 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 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq 2026-09-25 13:33 [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 1/3] drm: Provide a drm_dev_release_barrier() function to wait for device release callbacks Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 2/3] drm/xe: Don't unload the driver until all drm devices are freed Thomas Hellström @ 2026-09-25 13:33 ` Thomas Hellström 2026-09-25 19:56 ` Matthew Brost 2026-09-25 16:18 ` [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Danilo Krummrich 3 siblings, 1 reply; 14+ messages in thread From: Thomas Hellström @ 2026-09-25 13:33 UTC (permalink / raw) To: intel-xe Cc: Thomas Hellström, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Danilo Krummrich, Alice Ryhl, Alex Deucher, Christian König 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 this by queueing this work on xe_destroy_wq instead, the existing module-lifetime workqueue already used for GuC exec queue teardown. Unlike a per-device workqueue, this requires no dereference of a struct xe_device that may already be gone by the time a 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 or xe_vm whose teardown queues work here has already dropped its drm_device reference and thus already queued that work. 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 | 5 +++-- 2 files changed, 7 insertions(+), 4 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..ee369e6c3b28 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,7 @@ 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); + xe_destroy_wq_queue(&vma->destroy_work); } static void xe_vm_assert_write_mode_or_garbage_collector(struct xe_vm *vm) @@ -2059,7 +2060,7 @@ 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); + xe_destroy_wq_queue(&vm->destroy_work); } struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id) -- 2.55.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq 2026-09-25 13:33 ` [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq Thomas Hellström @ 2026-09-25 19:56 ` Matthew Brost 2026-09-25 20:18 ` Matthew Brost 0 siblings, 1 reply; 14+ messages in thread From: Matthew Brost @ 2026-09-25 19:56 UTC (permalink / raw) To: Thomas Hellström Cc: intel-xe, Rodrigo Vivi, Matthew Auld, dri-devel, Danilo Krummrich, Alice Ryhl, Alex Deucher, Christian König On Fri, Sep 25, 2026 at 03:33:35PM +0200, Thomas Hellström wrote: > 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 this by queueing this work on xe_destroy_wq instead, the existing > module-lifetime workqueue already used for GuC exec queue teardown. > Unlike a per-device workqueue, this requires no dereference of a > struct xe_device that may already be gone by the time a 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 or xe_vm whose teardown queues work here has already dropped > its drm_device reference and thus already queued that work. > > 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 | 5 +++-- > 2 files changed, 7 insertions(+), 4 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..ee369e6c3b28 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,7 @@ 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); > + xe_destroy_wq_queue(&vma->destroy_work); > } > > static void xe_vm_assert_write_mode_or_garbage_collector(struct xe_vm *vm) > @@ -2059,7 +2060,7 @@ 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); > + xe_destroy_wq_queue(&vm->destroy_work); Actually this is still unsafe, right? destroy_work touches vm->xe which could be gone after gpuvm drops potentially the final drm_dev_put, right? Matt > } > > struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id) > -- > 2.55.0 > ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq 2026-09-25 19:56 ` Matthew Brost @ 2026-09-25 20:18 ` Matthew Brost 2026-09-28 8:50 ` Thomas Hellström 0 siblings, 1 reply; 14+ messages in thread From: Matthew Brost @ 2026-09-25 20:18 UTC (permalink / raw) To: Thomas Hellström Cc: intel-xe, Rodrigo Vivi, Matthew Auld, dri-devel, Danilo Krummrich, Alice Ryhl, Alex Deucher, Christian König On Fri, Sep 25, 2026 at 12:56:43PM -0700, Matthew Brost wrote: > On Fri, Sep 25, 2026 at 03:33:35PM +0200, Thomas Hellström wrote: > > 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 this by queueing this work on xe_destroy_wq instead, the existing > > module-lifetime workqueue already used for GuC exec queue teardown. > > Unlike a per-device workqueue, this requires no dereference of a > > struct xe_device that may already be gone by the time a 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 or xe_vm whose teardown queues work here has already dropped > > its drm_device reference and thus already queued that work. > > > > 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 | 5 +++-- > > 2 files changed, 7 insertions(+), 4 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..ee369e6c3b28 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,7 @@ 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); > > + xe_destroy_wq_queue(&vma->destroy_work); > > } > > > > static void xe_vm_assert_write_mode_or_garbage_collector(struct xe_vm *vm) > > @@ -2059,7 +2060,7 @@ 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); > > + xe_destroy_wq_queue(&vm->destroy_work); > > Actually this is still unsafe, right? > > destroy_work touches vm->xe which could be gone after gpuvm drops > potentially the final drm_dev_put, right? > Ignore this - we flush this queue before destorying any device. Matt > Matt > > > } > > > > struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id) > > -- > > 2.55.0 > > ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq 2026-09-25 20:18 ` Matthew Brost @ 2026-09-28 8:50 ` Thomas Hellström 0 siblings, 0 replies; 14+ messages in thread From: Thomas Hellström @ 2026-09-28 8:50 UTC (permalink / raw) To: Matthew Brost Cc: intel-xe, Rodrigo Vivi, Matthew Auld, dri-devel, Danilo Krummrich, Alice Ryhl, Alex Deucher, Christian König On Fri, 2026-09-25 at 13:18 -0700, Matthew Brost wrote: > On Fri, Sep 25, 2026 at 12:56:43PM -0700, Matthew Brost wrote: > > On Fri, Sep 25, 2026 at 03:33:35PM +0200, Thomas Hellström wrote: > > > 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 this by queueing this work on xe_destroy_wq instead, the > > > existing > > > module-lifetime workqueue already used for GuC exec queue > > > teardown. > > > Unlike a per-device workqueue, this requires no dereference of a > > > struct xe_device that may already be gone by the time a 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 or xe_vm whose teardown queues work here has already > > > dropped > > > its drm_device reference and thus already queued that work. > > > > > > 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 | 5 +++-- > > > 2 files changed, 7 insertions(+), 4 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..ee369e6c3b28 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,7 @@ 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); > > > + xe_destroy_wq_queue(&vma->destroy_work); > > > } > > > > > > static void xe_vm_assert_write_mode_or_garbage_collector(struct > > > xe_vm *vm) > > > @@ -2059,7 +2060,7 @@ 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); > > > + xe_destroy_wq_queue(&vm->destroy_work); > > > > Actually this is still unsafe, right? > > > > destroy_work touches vm->xe which could be gone after gpuvm drops > > potentially the final drm_dev_put, right? > > > > Ignore this - we flush this queue before destorying any device. Actually I think you have a point. The module-wide queue is destroyed as the very last thing the module does. Having both a per-device destroy queue and a module-wide one is confusing. Let me double-check this. With this series we could just grab a device reference and release it when we're done. Thanks, Thomas > > Matt > > > Matt > > > > > } > > > > > > struct xe_vm *xe_vm_lookup(struct xe_file *xef, u32 id) > > > -- > > > 2.55.0 > > > ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads 2026-09-25 13:33 [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Thomas Hellström ` (2 preceding siblings ...) 2026-09-25 13:33 ` [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq Thomas Hellström @ 2026-09-25 16:18 ` Danilo Krummrich 2026-09-28 8:46 ` Thomas Hellström 3 siblings, 1 reply; 14+ messages in thread From: Danilo Krummrich @ 2026-09-25 16:18 UTC (permalink / raw) To: Thomas Hellström Cc: intel-xe, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Alice Ryhl, Alex Deucher, Christian König On Fri Sep 25, 2026 at 3:33 PM CEST, Thomas Hellström wrote: > Driver and shared DRM helper code is increasingly relying on bare > drm_device references (drm_dev_get()/drm_dev_put()) to keep a device's > software state around, without also pairing that with a reference on > the owning kernel module. Xe itself does this in several places, and > so does drm_gpuvm for the lifetime of a GPU VM. None of these > references currently prevent the owning module from being unloaded > while they, or the teardown work they can still trigger, are > outstanding, meaning driver code can end up executing after its own > module's text has already been freed. Since you mention DRM GPUVM in a couple of places, how can this ever happen? It wouldn't make sense to keep a VM alive beyond driver unbind. I.e. it can't make its drm_device reference count reach module unload in the first place. Besides that, can you please remind me whether there are any other reasons than the release() callback why a DRM device must not outlive module unload? I don't think the correct solution is to constrain module unload. The release() callback shouldn't really do anything other than free the memory of the drm_device allocation. All other resources a driver may have should be released on driver unbind. There may be shared resources, such as e.g. a common workqueue, but those are module level things that have nothing to do with the DRM device. I am aware that a few drivers implement release(), but TBH it looks pretty broken. qxl_drm_release() is a great example, and it already calls it out itself. > This series closes that gap for xe: > > - Patch 1 adds core DRM infrastructure allowing a driver to wait for > its outstanding device-release callbacks to finish before > proceeding with module unload. Please see above; I also wonder why Xe cares in the first place. Xe doesn't implement release(), no? > - Patch 2 makes xe use this infrastructure to hold up module unload > until every xe_device instance has actually been released, rather > than only until the module's own refcount happens to reach zero, > with a diagnostic if this ends up taking an unexpectedly long time. > > - Patch 3 fixes a related, previously unprotected case where the > teardown of a GPU VM or its address space mappings can be deferred > to run at an arbitrary later time, including after module unload has > already completed. Huh? GPUVM tracks the GPU's VA space mappings, but after driver unbind there's no access to the GPU to manage anything anymore. How can this even work? Thanks, Danilo ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads 2026-09-25 16:18 ` [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Danilo Krummrich @ 2026-09-28 8:46 ` Thomas Hellström 2026-09-28 10:13 ` Danilo Krummrich 0 siblings, 1 reply; 14+ messages in thread From: Thomas Hellström @ 2026-09-28 8:46 UTC (permalink / raw) To: Danilo Krummrich Cc: intel-xe, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Alice Ryhl, Alex Deucher, Christian König On Fri, 2026-09-25 at 18:18 +0200, Danilo Krummrich wrote: > On Fri Sep 25, 2026 at 3:33 PM CEST, Thomas Hellström wrote: > > Driver and shared DRM helper code is increasingly relying on bare > > drm_device references (drm_dev_get()/drm_dev_put()) to keep a > > device's > > software state around, without also pairing that with a reference > > on > > the owning kernel module. Xe itself does this in several places, > > and > > so does drm_gpuvm for the lifetime of a GPU VM. None of these > > references currently prevent the owning module from being unloaded > > while they, or the teardown work they can still trigger, are > > outstanding, meaning driver code can end up executing after its own > > module's text has already been freed. > > Since you mention DRM GPUVM in a couple of places, how can this ever > happen? It > wouldn't make sense to keep a VM alive beyond driver unbind. I.e. it > can't make > its drm_device reference count reach module unload in the first > place. There seems to be a bit of misunderstanding here. Driver unbind removes the struct device from the driver, triggers device unplug, and eventually the devres release actions. IIRC the last devres action removes a *single reference* on the struct drm_device. Hence if there exists other reference holders on the struct drm_device (open files, exported dma-bufs, exported drm_pagemaps as an example), the drm_device will survive the driver unbind. So will open files and thus drm_gpuvms until user-space decides to remove them. Files, dma-bufs and drm_pagemaps all hold a driver module reference until they have successfully released the drm_device. The requirement is "If a drm_device reference is held, a module reference of the driver providing the drm_device must also be held, or if it's held by the driver itself, it must ensure at driver unload time that any drm_device references it holds are released and drmm release callbacks have finished executing." What this series in effect does is to change this to to "The driver won't unload until all drm_device references are gone, and all drmm release callbacks have finished executing." I agree that the use of drm_gpuvm in the documentation is a bit unfair. Since the code calling drm_dev_get() and drm_dev_put() is intended to be called from the driver, the reference in effect becomes the driver's responsibility, but if someone would, in the future change that so that those references are put from a worker from within the driver or even within drm_gpuvm itself, things would break. If a future code reviewer, developer or AI agent knows about the new drm_device reference guarantee, then that will lessen the review scope and code will become more rubost. > > Besides that, can you please remind me whether there are any other > reasons than > the release() callback why a DRM device must not outlive module > unload? The drmm release callbacks. > > I don't think the correct solution is to constrain module unload. The > release() > callback shouldn't really do anything other than free the memory of > the > drm_device allocation. All other resources a driver may have should > be released > on driver unbind. That is not true. See above. The alternative would be, as mentioned in the commit message of patch 2, IIRC that each drm_device itself hold a module reference of the creating module. But then you wouldn't be able to execute rmmod with devices alive, You would have to manually unbind all devices first. > > There may be shared resources, such as e.g. a common workqueue, but > those are > module level things that have nothing to do with the DRM device. I don't think that is correct either. Take a look at Matt Brost reply to patch 3 there where he points out that the drm device (a base class of the xe device) is acually referenced in a work item after the struct drm_device reference is put. (There is a workqueue naming confusion in xe, but I do believe that patch needs a fix). With the poposed series in place a simple fix would be to hold a drm_device reference across the workqueue item. The unload process would then block until all devices are unreferenced, and then again at destroy_worqueue time waiting for the work item epilogue to finish executing. > > I am aware that a few drivers implement release(), but TBH it looks > pretty > broken. qxl_drm_release() is a great example, and it already calls it > out > itself. drm_release is not of major interest. > > > This series closes that gap for xe: > > > > - Patch 1 adds core DRM infrastructure allowing a driver to wait > > for > > its outstanding device-release callbacks to finish before > > proceeding with module unload. > > Please see above; I also wonder why Xe cares in the first place. Xe > doesn't > implement release(), no? See above. > > > - Patch 2 makes xe use this infrastructure to hold up module unload > > until every xe_device instance has actually been released, rather > > than only until the module's own refcount happens to reach zero, > > with a diagnostic if this ends up taking an unexpectedly long > > time. > > > > - Patch 3 fixes a related, previously unprotected case where the > > teardown of a GPU VM or its address space mappings can be > > deferred > > to run at an arbitrary later time, including after module unload > > has > > already completed. > > Huh? GPUVM tracks the GPU's VA space mappings, but after driver > unbind there's > no access to the GPU to manage anything anymore. How can this even > work? > > > As previously mentioned, software device state may well outlive a driver unbind. This is all about its cleanup. Thanks, Thomas > > Thanks, > Danilo ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads 2026-09-28 8:46 ` Thomas Hellström @ 2026-09-28 10:13 ` Danilo Krummrich 2026-09-28 12:13 ` Thomas Hellström 0 siblings, 1 reply; 14+ messages in thread From: Danilo Krummrich @ 2026-09-28 10:13 UTC (permalink / raw) To: Thomas Hellström Cc: intel-xe, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Alice Ryhl, Alex Deucher, Christian König On Mon Sep 28, 2026 at 10:46 AM CEST, Thomas Hellström wrote: > On Fri, 2026-09-25 at 18:18 +0200, Danilo Krummrich wrote: >> On Fri Sep 25, 2026 at 3:33 PM CEST, Thomas Hellström wrote: >> > Driver and shared DRM helper code is increasingly relying on bare >> > drm_device references (drm_dev_get()/drm_dev_put()) to keep a >> > device's >> > software state around, without also pairing that with a reference >> > on >> > the owning kernel module. Xe itself does this in several places, >> > and >> > so does drm_gpuvm for the lifetime of a GPU VM. None of these >> > references currently prevent the owning module from being unloaded >> > while they, or the teardown work they can still trigger, are >> > outstanding, meaning driver code can end up executing after its own >> > module's text has already been freed. >> >> Since you mention DRM GPUVM in a couple of places, how can this ever >> happen? It >> wouldn't make sense to keep a VM alive beyond driver unbind. I.e. it >> can't make >> its drm_device reference count reach module unload in the first >> place. > > There seems to be a bit of misunderstanding here. > > Driver unbind removes the struct device from the driver, triggers > device unplug, and eventually the devres release actions. IIRC the last > devres action removes a *single reference* on the struct drm_device. Correct. > Hence if there exists other reference holders on the struct drm_device > (open files, exported dma-bufs, exported drm_pagemaps as an example), > the drm_device will survive the driver unbind. So will open files and > thus drm_gpuvms until user-space decides to remove them. There's two lifetimes we have to deal with in drivers: the lifetime of (bus / physical) device resources, which are managed by the driver and the software state that is represented through the class device to userspace (e.g. file handles). The former is bounded to the scope where the driver is bound to the device and the latter is unbounded and indeed depends on userspace. Either the subsystem or the driver has to decouple those lifetimes. I.e. if the driver is unbound it should clean up all GPUVMs as they represent the GPU's virtual address space and hence are associated with the hardware. However, the driver should not operated the hardware anymore after driver unbind. So, in your case it seems that file lifetime and VM lifetime are conflated although they should be separate. We can't have userspace to decide when we drop device resources, such as DMA mappings, I/O memory mappings, etc. > Files, dma-bufs and drm_pagemaps all hold a driver module reference > until they have successfully released the drm_device. The requirement > is "If a drm_device reference is held, a module reference of the driver > providing the drm_device must also be held, or if it's held by the > driver itself, it must ensure at driver unload time that any drm_device > references it holds are released and drmm release callbacks have > finished executing." > > What this series in effect does is to change this to to "The driver > won't unload until all drm_device references are gone, and all drmm > release callbacks have finished executing." > > I agree that the use of drm_gpuvm in the documentation is a bit unfair. > Since the code calling drm_dev_get() and drm_dev_put() is intended to > be called from the driver, the reference in effect becomes the driver's > responsibility, but if someone would, in the future change that so that > those references are put from a worker from within the driver or even > within drm_gpuvm itself, things would break. If a future code reviewer, > developer or AI agent knows about the new drm_device reference > guarantee, then that will lessen the review scope and code will become > more rubost. > >> >> Besides that, can you please remind me whether there are any other >> reasons than >> the release() callback why a DRM device must not outlive module >> unload? > > The drmm release callbacks. Right, I forgot about them for a second. However, they are similar to the release() callbacks as in they are the wrong cleanup model for driver private structures. drmm is a great tool for common subsystem structures that lifetime wise tie to the drm_device. But it is the wrong lifetime model for stuff that is used to operate the device, as this should be torn down on device unbind. I had a quick look at Xe and found this for instance: drmm_add_action_or_reset(&xe->drm, control_fini_action, gt) control_fini_action() stops a worker that writes device registeres, which must not be done after driver unbind anymore. Now, there's two options, either after driver unbind this work is never running (which would be correct), but then this could have been devm_add_action_or_reset(), or it does actually run after driver unbind, but this would violate the driver model. >> I don't think the correct solution is to constrain module unload. The >> release() >> callback shouldn't really do anything other than free the memory of >> the >> drm_device allocation. All other resources a driver may have should >> be released >> on driver unbind. > > That is not true. See above. I know it is not true in practice, but we are doing the wrong thing. We are conflating the unbounded userspace lifetime with the bounded lifetime from the driver model. I also know that we can't fix this easily, but I want to create some awareness, especially when we introduce more band aid for the status quo, such that we can subsequently address the fundamental lifetime problems. >> There may be shared resources, such as e.g. a common workqueue, but >> those are >> module level things that have nothing to do with the DRM device. > > I don't think that is correct either. Take a look at Matt Brost reply > to patch 3 there where he points out that the drm device (a base class > of the xe device) is acually referenced in a work item after the struct > drm_device reference is put. (There is a workqueue naming confusion in > xe, but I do believe that patch needs a fix). With the poposed series > in place a simple fix would be to hold a drm_device reference across > the workqueue item. The unload process would then block until all > devices are unreferenced, and then again at destroy_worqueue time > waiting for the work item epilogue to finish executing. Well, but the workqueue itself has a bounded lifetime, which should either be driver unbind or module unload (when shared between driver instances). The work items themselves should ideally not extend beyond driver unbind, because there shouldn't be anything to do for the driver after unbind, because all the hardware should be torn down and not touched at this point anymore. I had another brief look at Xe and found this: drmm_add_action_or_reset(&xe->drm, ggtt_fini_early, ggtt) where ggtt_fini_early() destroys a workqueue. It also calls drm_mm_takedown(&ggtt->mm); which IIUC is the range allocator for the global GTT. (The hardware is gone on driver unbind (i.e. no more GGTT is available for the driver), so there's shouldn't be a need for this drm_mm to live longer than driver unbind). This model ties the lifetime of all shorter lived resources that are bounded to the driver unload scope to the unbounded lifetime of the drm_device that is controlled by userspace. I.e. the model is backwards and hence also extends your driver structures and callback entry points not only beyond driver unbind, but also potentially beyond module unload. If it would be done the other way around, tear down everything on driver unload, and then use default trampolines for userspace still trying to call into the driver (which is also what DMA fence does and other subsystem do), all those issues go away. >> > - Patch 3 fixes a related, previously unprotected case where the >> > teardown of a GPU VM or its address space mappings can be >> > deferred >> > to run at an arbitrary later time, including after module unload >> > has >> > already completed. >> >> Huh? GPUVM tracks the GPU's VA space mappings, but after driver >> unbind there's >> no access to the GPU to manage anything anymore. How can this even >> work? > > As previously mentioned, software device state may well outlive a > driver unbind. This is all about its cleanup. GPUVM shouldn't be lifetime wise tied to a software state, it represents hardware resoruces. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads 2026-09-28 10:13 ` Danilo Krummrich @ 2026-09-28 12:13 ` Thomas Hellström 2026-09-28 13:03 ` Danilo Krummrich 0 siblings, 1 reply; 14+ messages in thread From: Thomas Hellström @ 2026-09-28 12:13 UTC (permalink / raw) To: Danilo Krummrich Cc: intel-xe, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Alice Ryhl, Alex Deucher, Christian König On Mon, 2026-09-28 at 12:13 +0200, Danilo Krummrich wrote: > On Mon Sep 28, 2026 at 10:46 AM CEST, Thomas Hellström wrote: > > On Fri, 2026-09-25 at 18:18 +0200, Danilo Krummrich wrote: > > > On Fri Sep 25, 2026 at 3:33 PM CEST, Thomas Hellström wrote: > > > > Driver and shared DRM helper code is increasingly relying on > > > > bare > > > > drm_device references (drm_dev_get()/drm_dev_put()) to keep a > > > > device's > > > > software state around, without also pairing that with a > > > > reference > > > > on > > > > the owning kernel module. Xe itself does this in several > > > > places, > > > > and > > > > so does drm_gpuvm for the lifetime of a GPU VM. None of these > > > > references currently prevent the owning module from being > > > > unloaded > > > > while they, or the teardown work they can still trigger, are > > > > outstanding, meaning driver code can end up executing after its > > > > own > > > > module's text has already been freed. > > > > > > Since you mention DRM GPUVM in a couple of places, how can this > > > ever > > > happen? It > > > wouldn't make sense to keep a VM alive beyond driver unbind. I.e. > > > it > > > can't make > > > its drm_device reference count reach module unload in the first > > > place. > > > > There seems to be a bit of misunderstanding here. > > > > Driver unbind removes the struct device from the driver, triggers > > device unplug, and eventually the devres release actions. IIRC the > > last > > devres action removes a *single reference* on the struct > > drm_device. > > Correct. > > > Hence if there exists other reference holders on the struct > > drm_device > > (open files, exported dma-bufs, exported drm_pagemaps as an > > example), > > the drm_device will survive the driver unbind. So will open files > > and > > thus drm_gpuvms until user-space decides to remove them. > > There's two lifetimes we have to deal with in drivers: the lifetime > of (bus / > physical) device resources, which are managed by the driver and the > software > state that is represented through the class device to userspace (e.g. > file > handles). > > The former is bounded to the scope where the driver is bound to the > device and > the latter is unbounded and indeed depends on userspace. > > Either the subsystem or the driver has to decouple those lifetimes. > I.e. if the > driver is unbound it should clean up all GPUVMs as they represent the > GPU's > virtual address space and hence are associated with the hardware. > However, the > driver should not operated the hardware anymore after driver unbind. I disagree here. At unbind time we decouple the HW and SW state, The device no longer uses it's pointers to the page-table so, for example VRAM page-tables can be torn down, system page-tables lose their dma- mappings, but in xe we don't tear down the page-table structure itself. If HW accesses are properly protected by drm_dev_enter() / drm_dev_exit(), Hw won't be accessed after unbind. > > So, in your case it seems that file lifetime and VM lifetime are > conflated > although they should be separate. I view the VM as software state, page-table pointers, dma-mappings and VRAM storage as HW state. It seems like what we're not agreeing on is where to separate those. I see no reason as to why we would complicate the driver to remove more than necessary at unbind time? > > We can't have userspace to decide when we drop device resources, such > as DMA > mappings, I/O memory mappings, etc. We don't (Unless we have bugs, and you may have stumbled on those below?) Those should be removed at unbind time. We should also revoke dma-buf mappings and SVM migrates all dma-buf mappings to system. > > > Files, dma-bufs and drm_pagemaps all hold a driver module reference > > until they have successfully released the drm_device. The > > requirement > > is "If a drm_device reference is held, a module reference of the > > driver > > providing the drm_device must also be held, or if it's held by the > > driver itself, it must ensure at driver unload time that any > > drm_device > > references it holds are released and drmm release callbacks have > > finished executing." > > > > What this series in effect does is to change this to to "The driver > > won't unload until all drm_device references are gone, and all drmm > > release callbacks have finished executing." > > > > I agree that the use of drm_gpuvm in the documentation is a bit > > unfair. > > Since the code calling drm_dev_get() and drm_dev_put() is intended > > to > > be called from the driver, the reference in effect becomes the > > driver's > > responsibility, but if someone would, in the future change that so > > that > > those references are put from a worker from within the driver or > > even > > within drm_gpuvm itself, things would break. If a future code > > reviewer, > > developer or AI agent knows about the new drm_device reference > > guarantee, then that will lessen the review scope and code will > > become > > more rubost. > > > > > > > > Besides that, can you please remind me whether there are any > > > other > > > reasons than > > > the release() callback why a DRM device must not outlive module > > > unload? > > > > The drmm release callbacks. > > Right, I forgot about them for a second. However, they are similar to > the > release() callbacks as in they are the wrong cleanup model for driver > private > structures. > > drmm is a great tool for common subsystem structures that lifetime > wise tie to > the drm_device. But it is the wrong lifetime model for stuff that is > used to > operate the device, as this should be torn down on device unbind. The current model used by xe (and amdgpu AFACT, that also ties vm lifetime to file lifetime) is to block all hardware access and dma at unbind time. The rest is state that doesn't necessarily need to be torn down at unbind time. I believe the current separation is mostly done with drm_dev_enter() / drm_dev_exit() and why should we enforce a change of that? I'd say the drivers should be free to release what's convenient. Also if drm_gpuvms are designed to not outlive the struct device, why do they need to take a struct drm_device reference in the first place, I mean I brought this problem up then and IIRC I think you argued the reference was needed and punted any problems it caused to the drivers? > > I had a quick look at Xe and found this for instance: > > drmm_add_action_or_reset(&xe->drm, control_fini_action, gt) > > control_fini_action() stops a worker that writes device registeres, > which must > not be done after driver unbind anymore. > > Now, there's two options, either after driver unbind this work is > never running > (which would be correct), but then this could have been > devm_add_action_or_reset(), or it does actually run after driver > unbind, but > this would violate the driver model. Agreed, Unless there is something protecting the hardware access after unplug in that control subsystem, that's a genuine bug, but that's separate from this discussion > > > > I don't think the correct solution is to constrain module unload. > > > The > > > release() > > > callback shouldn't really do anything other than free the memory > > > of > > > the > > > drm_device allocation. All other resources a driver may have > > > should > > > be released > > > on driver unbind. > > > > That is not true. See above. > > I know it is not true in practice, but we are doing the wrong thing. > We are > conflating the unbounded userspace lifetime with the bounded lifetime > from the > driver model. I don't think we are. As long as all HW access is given up or blocked, we're fine. > > I also know that we can't fix this easily, but I want to create some > awareness, > especially when we introduce more band aid for the status quo, such > that we can > subsequently address the fundamental lifetime problems. > > > > There may be shared resources, such as e.g. a common workqueue, > > > but > > > those are > > > module level things that have nothing to do with the DRM device. > > > > I don't think that is correct either. Take a look at Matt Brost > > reply > > to patch 3 there where he points out that the drm device (a base > > class > > of the xe device) is acually referenced in a work item after the > > struct > > drm_device reference is put. (There is a workqueue naming confusion > > in > > xe, but I do believe that patch needs a fix). With the poposed > > series > > in place a simple fix would be to hold a drm_device reference > > across > > the workqueue item. The unload process would then block until all > > devices are unreferenced, and then again at destroy_worqueue time > > waiting for the work item epilogue to finish executing. > > Well, but the workqueue itself has a bounded lifetime, which should > either be > driver unbind or module unload (when shared between driver > instances). The work > items themselves should ideally not extend beyond driver unbind, > because there > shouldn't be anything to do for the driver after unbind, because all > the > hardware should be torn down and not touched at this point anymore. Hardware isn't touched but I don't think there is any reason for cleanup tasks to stop executing at unbind time? > > I had another brief look at Xe and found this: > > drmm_add_action_or_reset(&xe->drm, ggtt_fini_early, ggtt) > > where ggtt_fini_early() destroys a workqueue. It also calls > > drm_mm_takedown(&ggtt->mm); > > which IIUC is the range allocator for the global GTT. (The hardware > is gone on > driver unbind (i.e. no more GGTT is available for the driver), so > there's > shouldn't be a need for this drm_mm to live longer than driver > unbind). Yes, this *can* probably be taken down at unbind time at the expense of subsystem and structure validity checking, but it doesn't have to. > > This model ties the lifetime of all shorter lived resources that are > bounded to > the driver unload scope to the unbounded lifetime of the drm_device > that is > controlled by userspace. > > I.e. the model is backwards and hence also extends your driver > structures and > callback entry points not only beyond driver unbind, but also > potentially beyond > module unload. > > If it would be done the other way around, tear down everything on > driver unload, > and then use default trampolines for userspace still trying to call > into the > driver (which is also what DMA fence does and other subsystem do), > all those > issues go away. But that is arguably something that's never going to happen, and even if it is, I think that's mostly orthogonal to code keeping references to drm_device. > > > > > - Patch 3 fixes a related, previously unprotected case where > > > > the > > > > teardown of a GPU VM or its address space mappings can be > > > > deferred > > > > to run at an arbitrary later time, including after module > > > > unload > > > > has > > > > already completed. > > > > > > Huh? GPUVM tracks the GPU's VA space mappings, but after driver > > > unbind there's > > > no access to the GPU to manage anything anymore. How can this > > > even > > > work? > > > > As previously mentioned, software device state may well outlive a > > driver unbind. This is all about its cleanup. > > GPUVM shouldn't be lifetime wise tied to a software state, it > represents > hardware resoruces. So then we can remove it's drm device reference? Why would it need to keep a reference to the software state guaranteed to outlive it? Regardless, I can remove all mentions of GPUVM in the documentation, but that doesn't remove the fact that keeping a reference to a struct drm_device without a module reference or a module presence guarantee is extremely fragile, and will be also in a model that removes a larger part of its software state at unbind time. IMO removing that fragility is a good thing. Thanks, Thomas ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads 2026-09-28 12:13 ` Thomas Hellström @ 2026-09-28 13:03 ` Danilo Krummrich 2026-09-28 13:37 ` Thomas Hellström 0 siblings, 1 reply; 14+ messages in thread From: Danilo Krummrich @ 2026-09-28 13:03 UTC (permalink / raw) To: Thomas Hellström Cc: intel-xe, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Alice Ryhl, Alex Deucher, Christian König On Mon Sep 28, 2026 at 2:13 PM CEST, Thomas Hellström wrote: > On Mon, 2026-09-28 at 12:13 +0200, Danilo Krummrich wrote: >> On Mon Sep 28, 2026 at 10:46 AM CEST, Thomas Hellström wrote: >> > On Fri, 2026-09-25 at 18:18 +0200, Danilo Krummrich wrote: >> > > On Fri Sep 25, 2026 at 3:33 PM CEST, Thomas Hellström wrote: >> > > > Driver and shared DRM helper code is increasingly relying on >> > > > bare >> > > > drm_device references (drm_dev_get()/drm_dev_put()) to keep a >> > > > device's >> > > > software state around, without also pairing that with a >> > > > reference >> > > > on >> > > > the owning kernel module. Xe itself does this in several >> > > > places, >> > > > and >> > > > so does drm_gpuvm for the lifetime of a GPU VM. None of these >> > > > references currently prevent the owning module from being >> > > > unloaded >> > > > while they, or the teardown work they can still trigger, are >> > > > outstanding, meaning driver code can end up executing after its >> > > > own >> > > > module's text has already been freed. >> > > >> > > Since you mention DRM GPUVM in a couple of places, how can this >> > > ever >> > > happen? It >> > > wouldn't make sense to keep a VM alive beyond driver unbind. I.e. >> > > it >> > > can't make >> > > its drm_device reference count reach module unload in the first >> > > place. >> > >> > There seems to be a bit of misunderstanding here. >> > >> > Driver unbind removes the struct device from the driver, triggers >> > device unplug, and eventually the devres release actions. IIRC the >> > last >> > devres action removes a *single reference* on the struct >> > drm_device. >> >> Correct. >> >> > Hence if there exists other reference holders on the struct >> > drm_device >> > (open files, exported dma-bufs, exported drm_pagemaps as an >> > example), >> > the drm_device will survive the driver unbind. So will open files >> > and >> > thus drm_gpuvms until user-space decides to remove them. >> >> There's two lifetimes we have to deal with in drivers: the lifetime >> of (bus / >> physical) device resources, which are managed by the driver and the >> software >> state that is represented through the class device to userspace (e.g. >> file >> handles). >> >> The former is bounded to the scope where the driver is bound to the >> device and >> the latter is unbounded and indeed depends on userspace. >> >> Either the subsystem or the driver has to decouple those lifetimes. >> I.e. if the >> driver is unbound it should clean up all GPUVMs as they represent the >> GPU's >> virtual address space and hence are associated with the hardware. >> However, the >> driver should not operated the hardware anymore after driver unbind. > > I disagree here. At unbind time we decouple the HW and SW state, The > device no longer uses it's pointers to the page-table so, for example > VRAM page-tables can be torn down, system page-tables lose their dma- > mappings, but in xe we don't tear down the page-table structure itself. > > If HW accesses are properly protected by drm_dev_enter() / > drm_dev_exit(), Hw won't be accessed after unbind. > > >> >> So, in your case it seems that file lifetime and VM lifetime are >> conflated >> although they should be separate. > > I view the VM as software state, page-table pointers, dma-mappings and > VRAM storage as HW state. > > It seems like what we're not agreeing on is where to separate those. I > see no reason as to why we would complicate the driver to remove more > than necessary at unbind time? This can certainly be done, correct. But, the VM itself represents a GPU's virtual address space and takes ownership of the corresponding hardware resources. We can indeed revoke the hardware resources from the VM implementation and leave it in place. But this messes with the ownership model within the VM implementation: Because now, and you say this a couple of times below, we need to guard all relevant entry points into the VM code with guards, such as drm_dev_{enter/exit}(). IOW, it creates partially uninitialized structures with stale pointers that we now have to guard against. It makes much more sense to tear down everything that owns device resources on driver unbind. I.e. why keep structures with stale pointers around that we have to guard against in the first place? It also gets us rid of the module unload issue as it allows us to prevent callbacks into the driver code after driver unbind on the subsystem level. >> We can't have userspace to decide when we drop device resources, such >> as DMA >> mappings, I/O memory mappings, etc. > > We don't (Unless we have bugs, and you may have stumbled on those > below?) Those should be removed at unbind time. We should also revoke > dma-buf mappings and SVM migrates all dma-buf mappings to system. > >> >> > Files, dma-bufs and drm_pagemaps all hold a driver module reference >> > until they have successfully released the drm_device. The >> > requirement >> > is "If a drm_device reference is held, a module reference of the >> > driver >> > providing the drm_device must also be held, or if it's held by the >> > driver itself, it must ensure at driver unload time that any >> > drm_device >> > references it holds are released and drmm release callbacks have >> > finished executing." >> > >> > What this series in effect does is to change this to to "The driver >> > won't unload until all drm_device references are gone, and all drmm >> > release callbacks have finished executing." >> > >> > I agree that the use of drm_gpuvm in the documentation is a bit >> > unfair. >> > Since the code calling drm_dev_get() and drm_dev_put() is intended >> > to >> > be called from the driver, the reference in effect becomes the >> > driver's >> > responsibility, but if someone would, in the future change that so >> > that >> > those references are put from a worker from within the driver or >> > even >> > within drm_gpuvm itself, things would break. If a future code >> > reviewer, >> > developer or AI agent knows about the new drm_device reference >> > guarantee, then that will lessen the review scope and code will >> > become >> > more rubost. >> > >> > > >> > > Besides that, can you please remind me whether there are any >> > > other >> > > reasons than >> > > the release() callback why a DRM device must not outlive module >> > > unload? >> > >> > The drmm release callbacks. >> >> Right, I forgot about them for a second. However, they are similar to >> the >> release() callbacks as in they are the wrong cleanup model for driver >> private >> structures. >> >> drmm is a great tool for common subsystem structures that lifetime >> wise tie to >> the drm_device. But it is the wrong lifetime model for stuff that is >> used to >> operate the device, as this should be torn down on device unbind. > > The current model used by xe (and amdgpu AFACT, that also ties vm > lifetime to file lifetime) is to block all hardware access and dma at > unbind time. The rest is state that doesn't necessarily need to be torn > down at unbind time. I believe the current separation is mostly done > with drm_dev_enter() / drm_dev_exit() and why should we enforce a > change of that? I'd say the drivers should be free to release what's > convenient. See the reasons above, it is not a good layer for the lifetime decoupling. > Also if drm_gpuvms are designed to not outlive the struct device, why > do they need to take a struct drm_device reference in the first place, > I mean I brought this problem up then and IIRC I think you argued the > reference was needed and punted any problems it caused to the drivers? The lifetime of the hardware resources that are owned by a GPUVM implementation is restricted by the underlying bus device (e.g. PCI) being bound to the driver. The lifetime of the DRM device as a class device is technically independent, it can live longer (which is likely), but it could technically also be shorter lived (which drivers don't do in practice). But even though drivers don't do this in practice, the dependency should be expressed: if GPUVM stores a pointer to a DRM device, it has to take a reference count. >> I had a quick look at Xe and found this for instance: >> >> drmm_add_action_or_reset(&xe->drm, control_fini_action, gt) >> >> control_fini_action() stops a worker that writes device registeres, >> which must >> not be done after driver unbind anymore. >> >> Now, there's two options, either after driver unbind this work is >> never running >> (which would be correct), but then this could have been >> devm_add_action_or_reset(), or it does actually run after driver >> unbind, but >> this would violate the driver model. > > Agreed, Unless there is something protecting the hardware access after > unplug in that control subsystem, that's a genuine bug, but that's > separate from this discussion I argue that it is related; surely, we can keep everything around until the DRM device is destroyed and just guard every single (callback) entry point. But, that's far more complicated and error prone than just shutting down the hardware on driver unbind and tear down all entry points on the subsystem level; it messes with the ownership model of structures leaving stale pointers behind. As mentioned, this is what subsystems commonly do in the kernel, and I don't see why DRM would be special in this regard. >> > > I don't think the correct solution is to constrain module unload. >> > > The >> > > release() >> > > callback shouldn't really do anything other than free the memory >> > > of >> > > the >> > > drm_device allocation. All other resources a driver may have >> > > should >> > > be released >> > > on driver unbind. >> > >> > That is not true. See above. >> >> I know it is not true in practice, but we are doing the wrong thing. >> We are >> conflating the unbounded userspace lifetime with the bounded lifetime >> from the >> driver model. > > I don't think we are. As long as all HW access is given up or blocked, > we're fine. Right, what you describe above works, but it leaves stale pointers and invalid structures behind that we then need to guard against. >> I also know that we can't fix this easily, but I want to create some >> awareness, >> especially when we introduce more band aid for the status quo, such >> that we can >> subsequently address the fundamental lifetime problems. >> >> > > There may be shared resources, such as e.g. a common workqueue, >> > > but >> > > those are >> > > module level things that have nothing to do with the DRM device. >> > >> > I don't think that is correct either. Take a look at Matt Brost >> > reply >> > to patch 3 there where he points out that the drm device (a base >> > class >> > of the xe device) is acually referenced in a work item after the >> > struct >> > drm_device reference is put. (There is a workqueue naming confusion >> > in >> > xe, but I do believe that patch needs a fix). With the poposed >> > series >> > in place a simple fix would be to hold a drm_device reference >> > across >> > the workqueue item. The unload process would then block until all >> > devices are unreferenced, and then again at destroy_worqueue time >> > waiting for the work item epilogue to finish executing. >> >> Well, but the workqueue itself has a bounded lifetime, which should >> either be >> driver unbind or module unload (when shared between driver >> instances). The work >> items themselves should ideally not extend beyond driver unbind, >> because there >> shouldn't be anything to do for the driver after unbind, because all >> the >> hardware should be torn down and not touched at this point anymore. > > Hardware isn't touched but I don't think there is any reason for > cleanup tasks to stop executing at unbind time? Well, it has the implications as stated above. And it means that instead of having a global SRCU read side critical section for a global entry point, we are forced to leave invalid pointers behind and have drivers protect them each individually with SRCU (i.e. drm_dev_enter() / drm_dev_exit()). >> I had another brief look at Xe and found this: >> >> drmm_add_action_or_reset(&xe->drm, ggtt_fini_early, ggtt) >> >> where ggtt_fini_early() destroys a workqueue. It also calls >> >> drm_mm_takedown(&ggtt->mm); >> >> which IIUC is the range allocator for the global GTT. (The hardware >> is gone on >> driver unbind (i.e. no more GGTT is available for the driver), so >> there's >> shouldn't be a need for this drm_mm to live longer than driver >> unbind). > > Yes, this *can* probably be taken down at unbind time at the expense of > subsystem and structure validity checking, but it doesn't have to. Same reasoning as above. >> This model ties the lifetime of all shorter lived resources that are >> bounded to >> the driver unload scope to the unbounded lifetime of the drm_device >> that is >> controlled by userspace. >> >> I.e. the model is backwards and hence also extends your driver >> structures and >> callback entry points not only beyond driver unbind, but also >> potentially beyond >> module unload. >> >> If it would be done the other way around, tear down everything on >> driver unload, >> and then use default trampolines for userspace still trying to call >> into the >> driver (which is also what DMA fence does and other subsystem do), >> all those >> issues go away. > > But that is arguably something that's never going to happen, and even > if it is, I think that's mostly orthogonal to code keeping references > to drm_device. Well, I hope we can move to this model eventually. I don't think it is orthogonal, if we don't have driver entry pointer after driver unbind anymore, there is no module unload problem anymore. And structs that keep a reference to a struct drm_device shouldn't be a problem in this regard either. >> > > > - Patch 3 fixes a related, previously unprotected case where >> > > > the >> > > > teardown of a GPU VM or its address space mappings can be >> > > > deferred >> > > > to run at an arbitrary later time, including after module >> > > > unload >> > > > has >> > > > already completed. >> > > >> > > Huh? GPUVM tracks the GPU's VA space mappings, but after driver >> > > unbind there's >> > > no access to the GPU to manage anything anymore. How can this >> > > even >> > > work? >> > >> > As previously mentioned, software device state may well outlive a >> > driver unbind. This is all about its cleanup. >> >> GPUVM shouldn't be lifetime wise tied to a software state, it >> represents >> hardware resoruces. > > So then we can remove it's drm device reference? Why would it need to > keep a reference to the software state guaranteed to outlive it? There is no structural guarantee that it can't happen that the DRM device is destroyed first, then GPUVM and then the driver is unbound. > Regardless, I can remove all mentions of GPUVM in the documentation, That's not my main concern, but I found it to be a good example for what actually is my concern: The lifetime model in DRM often conflates software and hardware state structurally; whether the consequence is either that we just keep hardware resources alive for longer than they should be alive, or whether we tear them down and leave invalid pointers behind that we then protect with lots of manual guard sections does not matter too much for me. Both is a consequence of the structurally conflated lifetime and ownership. > but that doesn't remove the fact that keeping a reference to a struct > drm_device without a module reference or a module presence guarantee is > extremely fragile, and will be also in a model that removes a larger > part of its software state at unbind time. IMO removing that fragility > is a good thing. Don't get me wrong, I don't object to this patch series. Well, at least not too much, requiring drivers to call drm_dev_release_barrier() in module unload is pretty ugly, and I don't know any other subsystem that has such a guard, because they do structurally prevent callbacks into drivers after driver unbind and drivers hence tie the lifetime of structures to driver unbind. But then there's reality and the things are as they are right now, so it might just be necessary. However, I hope that in the future we can improve this and have the subsystem provide the required guards on the subsystem callback level. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads 2026-09-28 13:03 ` Danilo Krummrich @ 2026-09-28 13:37 ` Thomas Hellström 2026-10-03 14:08 ` Danilo Krummrich 0 siblings, 1 reply; 14+ messages in thread From: Thomas Hellström @ 2026-09-28 13:37 UTC (permalink / raw) To: Danilo Krummrich Cc: intel-xe, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Alice Ryhl, Alex Deucher, Christian König On Mon, 2026-09-28 at 15:03 +0200, Danilo Krummrich wrote: > On Mon Sep 28, 2026 at 2:13 PM CEST, Thomas Hellström wrote: > > On Mon, 2026-09-28 at 12:13 +0200, Danilo Krummrich wrote: > > > On Mon Sep 28, 2026 at 10:46 AM CEST, Thomas Hellström wrote: > > > > On Fri, 2026-09-25 at 18:18 +0200, Danilo Krummrich wrote: > > > > > On Fri Sep 25, 2026 at 3:33 PM CEST, Thomas Hellström wrote: > > > > > > Driver and shared DRM helper code is increasingly relying > > > > > > on > > > > > > bare > > > > > > drm_device references (drm_dev_get()/drm_dev_put()) to keep > > > > > > a > > > > > > device's > > > > > > software state around, without also pairing that with a > > > > > > reference > > > > > > on > > > > > > the owning kernel module. Xe itself does this in several > > > > > > places, > > > > > > and > > > > > > so does drm_gpuvm for the lifetime of a GPU VM. None of > > > > > > these > > > > > > references currently prevent the owning module from being > > > > > > unloaded > > > > > > while they, or the teardown work they can still trigger, > > > > > > are > > > > > > outstanding, meaning driver code can end up executing after > > > > > > its > > > > > > own > > > > > > module's text has already been freed. > > > > > > > > > > Since you mention DRM GPUVM in a couple of places, how can > > > > > this > > > > > ever > > > > > happen? It > > > > > wouldn't make sense to keep a VM alive beyond driver unbind. > > > > > I.e. > > > > > it > > > > > can't make > > > > > its drm_device reference count reach module unload in the > > > > > first > > > > > place. > > > > > > > > There seems to be a bit of misunderstanding here. > > > > > > > > Driver unbind removes the struct device from the driver, > > > > triggers > > > > device unplug, and eventually the devres release actions. IIRC > > > > the > > > > last > > > > devres action removes a *single reference* on the struct > > > > drm_device. > > > > > > Correct. > > > > > > > Hence if there exists other reference holders on the struct > > > > drm_device > > > > (open files, exported dma-bufs, exported drm_pagemaps as an > > > > example), > > > > the drm_device will survive the driver unbind. So will open > > > > files > > > > and > > > > thus drm_gpuvms until user-space decides to remove them. > > > > > > There's two lifetimes we have to deal with in drivers: the > > > lifetime > > > of (bus / > > > physical) device resources, which are managed by the driver and > > > the > > > software > > > state that is represented through the class device to userspace > > > (e.g. > > > file > > > handles). > > > > > > The former is bounded to the scope where the driver is bound to > > > the > > > device and > > > the latter is unbounded and indeed depends on userspace. > > > > > > Either the subsystem or the driver has to decouple those > > > lifetimes. > > > I.e. if the > > > driver is unbound it should clean up all GPUVMs as they represent > > > the > > > GPU's > > > virtual address space and hence are associated with the hardware. > > > However, the > > > driver should not operated the hardware anymore after driver > > > unbind. > > > > I disagree here. At unbind time we decouple the HW and SW state, > > The > > device no longer uses it's pointers to the page-table so, for > > example > > VRAM page-tables can be torn down, system page-tables lose their > > dma- > > mappings, but in xe we don't tear down the page-table structure > > itself. > > > > If HW accesses are properly protected by drm_dev_enter() / > > drm_dev_exit(), Hw won't be accessed after unbind. > > > > > > > > > > So, in your case it seems that file lifetime and VM lifetime are > > > conflated > > > although they should be separate. > > > > I view the VM as software state, page-table pointers, dma-mappings > > and > > VRAM storage as HW state. > > > > It seems like what we're not agreeing on is where to separate > > those. I > > see no reason as to why we would complicate the driver to remove > > more > > than necessary at unbind time? > > This can certainly be done, correct. But, the VM itself represents a > GPU's > virtual address space and takes ownership of the corresponding > hardware > resources. > > We can indeed revoke the hardware resources from the VM > implementation and > leave it in place. But this messes with the ownership model within > the VM > implementation: > > Because now, and you say this a couple of times below, we need to > guard all > relevant entry points into the VM code with guards, such as > drm_dev_{enter/exit}(). > > IOW, it creates partially uninitialized structures with stale > pointers that we > now have to guard against. > > It makes much more sense to tear down everything that owns device > resources on > driver unbind. I.e. why keep structures with stale pointers around > that we have > to guard against in the first place? > > It also gets us rid of the module unload issue as it allows us to > prevent > callbacks into the driver code after driver unbind on the subsystem > level. > > > > We can't have userspace to decide when we drop device resources, > > > such > > > as DMA > > > mappings, I/O memory mappings, etc. > > > > We don't (Unless we have bugs, and you may have stumbled on those > > below?) Those should be removed at unbind time. We should also > > revoke > > dma-buf mappings and SVM migrates all dma-buf mappings to system. > > > > > > > > > Files, dma-bufs and drm_pagemaps all hold a driver module > > > > reference > > > > until they have successfully released the drm_device. The > > > > requirement > > > > is "If a drm_device reference is held, a module reference of > > > > the > > > > driver > > > > providing the drm_device must also be held, or if it's held by > > > > the > > > > driver itself, it must ensure at driver unload time that any > > > > drm_device > > > > references it holds are released and drmm release callbacks > > > > have > > > > finished executing." > > > > > > > > What this series in effect does is to change this to to "The > > > > driver > > > > won't unload until all drm_device references are gone, and all > > > > drmm > > > > release callbacks have finished executing." > > > > > > > > I agree that the use of drm_gpuvm in the documentation is a bit > > > > unfair. > > > > Since the code calling drm_dev_get() and drm_dev_put() is > > > > intended > > > > to > > > > be called from the driver, the reference in effect becomes the > > > > driver's > > > > responsibility, but if someone would, in the future change that > > > > so > > > > that > > > > those references are put from a worker from within the driver > > > > or > > > > even > > > > within drm_gpuvm itself, things would break. If a future code > > > > reviewer, > > > > developer or AI agent knows about the new drm_device reference > > > > guarantee, then that will lessen the review scope and code will > > > > become > > > > more rubost. > > > > > > > > > > > > > > Besides that, can you please remind me whether there are any > > > > > other > > > > > reasons than > > > > > the release() callback why a DRM device must not outlive > > > > > module > > > > > unload? > > > > > > > > The drmm release callbacks. > > > > > > Right, I forgot about them for a second. However, they are > > > similar to > > > the > > > release() callbacks as in they are the wrong cleanup model for > > > driver > > > private > > > structures. > > > > > > drmm is a great tool for common subsystem structures that > > > lifetime > > > wise tie to > > > the drm_device. But it is the wrong lifetime model for stuff that > > > is > > > used to > > > operate the device, as this should be torn down on device unbind. > > > > The current model used by xe (and amdgpu AFACT, that also ties vm > > lifetime to file lifetime) is to block all hardware access and dma > > at > > unbind time. The rest is state that doesn't necessarily need to be > > torn > > down at unbind time. I believe the current separation is mostly > > done > > with drm_dev_enter() / drm_dev_exit() and why should we enforce a > > change of that? I'd say the drivers should be free to release > > what's > > convenient. > > See the reasons above, it is not a good layer for the lifetime > decoupling. > > > Also if drm_gpuvms are designed to not outlive the struct device, > > why > > do they need to take a struct drm_device reference in the first > > place, > > I mean I brought this problem up then and IIRC I think you argued > > the > > reference was needed and punted any problems it caused to the > > drivers? > > The lifetime of the hardware resources that are owned by a GPUVM > implementation > is restricted by the underlying bus device (e.g. PCI) being bound to > the driver. > > The lifetime of the DRM device as a class device is technically > independent, it > can live longer (which is likely), but it could technically also be > shorter > lived (which drivers don't do in practice). But even though drivers > don't do > this in practice, the dependency should be expressed: if GPUVM stores > a pointer > to a DRM device, it has to take a reference count. It doesn't have to in a model where all users are removed before the drm device is freed. Following your argument, shouldn't that drm_device reference should also be accompanied by a module reference? Except that will block rmmod? > > > > I had a quick look at Xe and found this for instance: > > > > > > drmm_add_action_or_reset(&xe->drm, control_fini_action, > > > gt) > > > > > > control_fini_action() stops a worker that writes device > > > registeres, > > > which must > > > not be done after driver unbind anymore. > > > > > > Now, there's two options, either after driver unbind this work is > > > never running > > > (which would be correct), but then this could have been > > > devm_add_action_or_reset(), or it does actually run after driver > > > unbind, but > > > this would violate the driver model. > > > > Agreed, Unless there is something protecting the hardware access > > after > > unplug in that control subsystem, that's a genuine bug, but that's > > separate from this discussion > > I argue that it is related; surely, we can keep everything around > until the DRM > device is destroyed and just guard every single (callback) entry > point. > > But, that's far more complicated and error prone than just shutting > down the hardware > on driver unbind and tear down all entry points on the subsystem > level; it > messes with the ownership model of structures leaving stale pointers > behind. > > As mentioned, this is what subsystems commonly do in the kernel, and > I don't see > why DRM would be special in this regard. Without knowing for sure, I think this was the route taken with hotplugging. > > > > > > I don't think the correct solution is to constrain module > > > > > unload. > > > > > The > > > > > release() > > > > > callback shouldn't really do anything other than free the > > > > > memory > > > > > of > > > > > the > > > > > drm_device allocation. All other resources a driver may have > > > > > should > > > > > be released > > > > > on driver unbind. > > > > > > > > That is not true. See above. > > > > > > I know it is not true in practice, but we are doing the wrong > > > thing. > > > We are > > > conflating the unbounded userspace lifetime with the bounded > > > lifetime > > > from the > > > driver model. > > > > I don't think we are. As long as all HW access is given up or > > blocked, > > we're fine. > > Right, what you describe above works, but it leaves stale pointers > and invalid > structures behind that we then need to guard against. But isn't essentially what you describe a design where we release all dma_buf, file- and drm_pagemap references of struct drm_device at module_unload time. That would replace their references with SRCU protecting the drm_device pointer, falling back to a stub behaviour when unbind has been called. So the "Can I access hardware?" would be replaced by a "Can I access the DRM device?". That's an interesting idea, but would probably need careful work so fence waits etc. doesn't block the SRCU read sections. And ofc to avoid user-space regressing. > > Don't get me wrong, I don't object to this patch series. Well, at > least not too > much, requiring drivers to call drm_dev_release_barrier() in module > unload is > pretty ugly, and I don't know any other subsystem that has such a > guard, because > they do structurally prevent callbacks into drivers after driver > unbind and > drivers hence tie the lifetime of structures to driver unbind. FWIW, IIRC that SRCU is only strictly needed when non-driver code (pagemap, files and dma-buf) drops the last drm_device reference without having a module reference. And there is no such code (yet) AFAIK, so I can drop that patch to when we think it's necessary and we have other driver buy-ins. Thanks, Thomas ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads 2026-09-28 13:37 ` Thomas Hellström @ 2026-10-03 14:08 ` Danilo Krummrich 0 siblings, 0 replies; 14+ messages in thread From: Danilo Krummrich @ 2026-10-03 14:08 UTC (permalink / raw) To: Thomas Hellström Cc: intel-xe, Matthew Brost, Rodrigo Vivi, Matthew Auld, dri-devel, Alice Ryhl, Alex Deucher, Christian König On Mon Sep 28, 2026 at 3:37 PM CEST, Thomas Hellström wrote: > It doesn't have to in a model where all users are removed before the > drm device is freed. Following your argument, shouldn't that drm_device > reference should also be accompanied by a module reference? Except that > will block rmmod? Yes, if we establish that a DRM device must outlive a GPUVM (which by convention we implicitly do by storing a pointer) then you don't need a reference count. However, there's nothing that ensures the caller sticks to the convention. And given that the DRM device is reference counted already, taking the reference count is the correct thing to do. (As a side note, in Rust you can actually model and enforce this lifetime relationship at compile time without a reference count. You can even establish more granular lifetime relationships. For instance, you could have: struct GpuVm<'a> { drm: &'a drm::Device<Registered>, } and establish that a GPUVM can only ever live as long as the DRM device is registered and therefore implicitly also establish that the GPUVM can't outlive driver unbind, as a DRM device can't be registered beyond driver unbind and hence the lifetime 'a is guaranteed to end before driver unbind. In C you can only establish this by convention, and at least take the reference count.) The module reference seems orthogonal though, GPUVM is not actively emitting calls into anything (unlike a workqueue for instance), it's a passive data structure. So, there's no need for any defensive measure AFAICS. > Without knowing for sure, I think this was the route taken with > hotplugging. Sure, both approaches are there to cover hotplugging. Almost all class device implementations have to consider hotplugging as it is depends on the bus the physical device sits on whether hot(un)plug can happen. > But isn't essentially what you describe a design where we release all > dma_buf, file- and drm_pagemap references of struct drm_device at > module_unload time. That would replace their references with SRCU > protecting the drm_device pointer, falling back to a stub behaviour > when unbind has been called. So the "Can I access hardware?" would be > replaced by a "Can I access the DRM device?". I think the question is not "Can I access the DRM device?", the question is "Is the DRM device still registered?", or IOW, "Is the DRM device still backed by a driver?". There's a bounded lifetime when a driver is allowed to operated a device, which is between probe and remove. This is (typically) the same scope as the class device (e.g. DRM) is registered. After the driver is unbound from it's (physical) bus device, there's no value anymore in letting the driver operate the class device (which is exactly what register() / unregister() describes) in the first place. There's nothing hardware specific left at this point, so it is not a driver job anymore; the lifetime decoupling can be at subsystem / component level (e.g. DMA fence). > That's an interesting idea, but would probably need careful work so > fence waits etc. doesn't block the SRCU read sections. And ofc to avoid > user-space regressing. For the fence waits specifically, this can (or should) never happen. Driver fences must be signaled on driver unbind. The hardware is gone at this point, there's nothing left that could signal them otherwise. > FWIW, IIRC that SRCU is only strictly needed when non-driver code > (pagemap, files and dma-buf) drops the last drm_device reference > without having a module reference. And there is no such code (yet) > AFAIK, so I can drop that patch to when we think it's necessary and we > have other driver buy-ins. I think those components should do the lifetime decoupling work instead. Once the driver is unbound, none of the driver callbacks are "special" anymore as there's nothing hardware specific left at this point. ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-03 14:08 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-25 13:33 [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 1/3] drm: Provide a drm_dev_release_barrier() function to wait for device release callbacks Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 2/3] drm/xe: Don't unload the driver until all drm devices are freed Thomas Hellström 2026-09-25 13:33 ` [PATCH v3 3/3] drm/xe: Route deferred xe_vma/xe_vm teardown off system_dfl_wq Thomas Hellström 2026-09-25 19:56 ` Matthew Brost 2026-09-25 20:18 ` Matthew Brost 2026-09-28 8:50 ` Thomas Hellström 2026-09-25 16:18 ` [PATCH v3 0/3] drm, drm/xe: Protect against premature module unloads Danilo Krummrich 2026-09-28 8:46 ` Thomas Hellström 2026-09-28 10:13 ` Danilo Krummrich 2026-09-28 12:13 ` Thomas Hellström 2026-09-28 13:03 ` Danilo Krummrich 2026-09-28 13:37 ` Thomas Hellström 2026-10-03 14:08 ` Danilo Krummrich
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox