[PATCH 4/4] drm: Add per-signal compare functionality to drm_user_fence
From: Srinivasan Shanmugam
Date: Wed Sep 02 2026 - 09:57:58 EST
GPU drivers sometimes need to read a value from a userspace VA when a
dma-fence signals, compare it with an expected value, and only perform
the deferred work (e.g. eventfd_signal) if the comparison passes. This
is the per-signal filtering pattern used in AMDGPU's EOP eventfd path.
Add optional compare fields to drm_user_fence and a new helper
drm_user_fence_set_compare() to configure them. Supported operators are
==, !=, >=.
When cmp_op is set, drm_user_fence reads the value from userspace via
copy_from_user_nofault() and calls ops->worker() only if the comparison
passes. If the process MM is gone or the read fails, the worker is
skipped since the comparison cannot be performed.
Drivers that do not need filtering (e.g. XE) leave cmp_op unset and the
worker is called unconditionally — no behavioral change.
Suggested-by: Christian König <christian.koenig@xxxxxxx>
Cc: Alex Deucher <alexander.deucher@xxxxxxx>
Cc: Matthew Brost <matthew.brost@xxxxxxxxx>
Cc: Thomas Hellström <thomas.hellstrom@xxxxxxxxxxxxxxx>
Cc: dri-devel@xxxxxxxxxxxxxxxxxxxxx
Cc: amd-gfx@xxxxxxxxxxxxxxxxxxxxx
Signed-off-by: Srinivasan Shanmugam <srinivasan.shanmugam@xxxxxxx>
Change-Id: Ie9620ccccdb894512e7a11e7fb5e4544e5951ec4
---
drivers/gpu/drm/drm_user_fence.c | 93 +++++++++++++++++++++++++++++---
include/drm/drm_user_fence.h | 52 ++++++++++++++++--
2 files changed, 134 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/drm_user_fence.c b/drivers/gpu/drm/drm_user_fence.c
index 0f229b7210a9..fcbdf40d1758 100644
--- a/drivers/gpu/drm/drm_user_fence.c
+++ b/drivers/gpu/drm/drm_user_fence.c
@@ -2,16 +2,25 @@
/*
* Copyright © 2024 The Linux Foundation
*
- * DRM user fence — extends drm_work_fence with kthread_use_mm() support.
+ * DRM user fence helper.
*
- * Use this when a GPU fence signals and work needs to access userspace
- * memory (copy_to_user, fault-able operations) from a kthread context.
- * For work that does not require userspace memory access, use
- * drm_work_fence directly.
+ * Extends drm_work_fence with the ability to access userspace memory
+ * from workqueue context by borrowing the process MM via kthread_use_mm().
+ *
+ * Drivers that need to write completion status to userspace (e.g., user
+ * fences, signaling eventfds) embed drm_user_fence and implement
+ * ops->worker() to do the actual write.
+ *
+ * Optionally, drivers may configure a per-signal compare via
+ * drm_user_fence_set_compare(): work is skipped unless the value at a
+ * userspace address matches the expected value at signal time.
*/
#include <linux/kthread.h>
+#include <linux/mm.h>
#include <linux/sched/mm.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
#include <drm/drm_user_fence.h>
@@ -30,13 +39,42 @@ static void drm_user_fence_do_work(struct drm_work_fence *wfence)
struct drm_user_fence *ufence =
container_of(wfence, struct drm_user_fence, base);
struct mm_struct *mm = NULL;
+ bool call_worker = true;
if (mmget_not_zero(ufence->mm)) {
mm = ufence->mm;
kthread_use_mm(mm);
}
- ufence->ops->worker(ufence, !!mm);
+ if (ufence->cmp_op != DRM_USER_FENCE_CMP_NONE &&
+ !(wfence->fence && wfence->fence->error)) {
+ if (!mm) {
+ call_worker = false;
+ } else {
+ __le64 raw;
+
+ /*
+ * Use copy_from_user_nofault() to prevent a
+ * userfaultfd-registered page from blocking this
+ * workqueue thread indefinitely (DoS).
+ */
+ if (copy_from_user_nofault(&raw, ufence->cmp_addr,
+ sizeof(raw))) {
+ call_worker = false;
+ } else {
+ /* GPU writes LE; convert before comparing. */
+ u64 cur_val = le64_to_cpu(raw);
+
+ if (!drm_user_fence_cmp_match(cur_val,
+ ufence->cmp_value,
+ ufence->cmp_op))
+ call_worker = false;
+ }
+ }
+ }
+
+ if (call_worker)
+ ufence->ops->worker(ufence, !!mm);
if (mm) {
kthread_unuse_mm(mm);
@@ -63,8 +101,47 @@ void drm_user_fence_init(struct drm_user_fence *ufence,
const struct drm_user_fence_ops *ops)
{
drm_work_fence_init(&ufence->base, wq, &drm_user_fence_wfence_ops);
- ufence->mm = current->mm;
+ ufence->mm = current->mm;
mmgrab(ufence->mm);
- ufence->ops = ops;
+ ufence->ops = ops;
+ ufence->cmp_op = DRM_USER_FENCE_CMP_NONE;
+ ufence->cmp_addr = NULL;
+ ufence->cmp_value = 0;
}
EXPORT_SYMBOL_GPL(drm_user_fence_init);
+
+/**
+ * drm_user_fence_set_compare - Set per-signal compare filter
+ * @ufence: user fence
+ * @addr: 8-byte-aligned userspace address to read from at signal time
+ * @value: expected value to compare against
+ * @op: comparison operator; pass %DRM_USER_FENCE_CMP_NONE to disable
+ *
+ * When @op is not %DRM_USER_FENCE_CMP_NONE, the worker is only called
+ * if the value at @addr matches @value according to @op. If the MM is
+ * gone or the read fails, the worker is suppressed.
+ *
+ * Must only be called before drm_work_fence_add_callback().
+ */
+void drm_user_fence_set_compare(struct drm_user_fence *ufence,
+ u64 __user *addr, u64 value,
+ enum drm_user_fence_cmp op)
+{
+ /*
+ * get_user() of u64 is not atomic on 32-bit — caller should not
+ * reach here on non-64-bit kernels.
+ */
+ if (WARN_ON_ONCE(!IS_ENABLED(CONFIG_64BIT)))
+ return;
+
+ if (op != DRM_USER_FENCE_CMP_NONE) {
+ if (WARN_ON_ONCE(!addr ||
+ !IS_ALIGNED((unsigned long)addr, sizeof(u64))))
+ return;
+ }
+
+ ufence->cmp_addr = addr;
+ ufence->cmp_value = value;
+ ufence->cmp_op = op;
+}
+EXPORT_SYMBOL_GPL(drm_user_fence_set_compare);
diff --git a/include/drm/drm_user_fence.h b/include/drm/drm_user_fence.h
index 9b92498e6009..56918928bb4f 100644
--- a/include/drm/drm_user_fence.h
+++ b/include/drm/drm_user_fence.h
@@ -20,8 +20,9 @@ struct drm_user_fence_ops {
* @worker: Called from workqueue context with the process MM active.
*
* If @mm_ok is true, kthread_use_mm() is active and userspace memory
- * may be accessed safely. If @mm_ok is false, the process MM was
- * already gone; skip the userspace write.
+ * (copy_to_user, etc.) may be accessed safely.
+ * If @mm_ok is false, the process MM was already gone; skip the
+ * userspace write.
*
* wake_up() or other post-signal housekeeping should also happen here.
*/
@@ -34,11 +35,23 @@ struct drm_user_fence_ops {
void (*destroy)(struct drm_user_fence *ufence);
};
+/**
+ * enum drm_user_fence_cmp - compare operator for per-signal filtering
+ */
+enum drm_user_fence_cmp {
+ DRM_USER_FENCE_CMP_NONE = 0,
+ DRM_USER_FENCE_CMP_EQ,
+ DRM_USER_FENCE_CMP_NEQ,
+ DRM_USER_FENCE_CMP_GTE,
+};
+
/**
* struct drm_user_fence - DRM user fence with MM borrowing
*
* Extends drm_work_fence with kthread_use_mm() support for drivers
* that need to access userspace memory when a GPU fence signals.
+ * For work that does not need userspace memory access, use
+ * drm_work_fence directly.
*
* Call drm_user_fence_init() at creation and drm_user_fence_add_callback()
* to arm on a dma-fence. Call drm_user_fence_cancel_sync() before teardown.
@@ -50,11 +63,20 @@ struct drm_user_fence {
struct mm_struct *mm;
/** @ops: Driver operations. */
const struct drm_user_fence_ops *ops;
+ /** @cmp_addr: Userspace address to read for per-signal compare. */
+ u64 __user *cmp_addr;
+ /** @cmp_value: Expected value for per-signal compare. */
+ u64 cmp_value;
+ /** @cmp_op: Compare operator; DRM_USER_FENCE_CMP_NONE disables. */
+ enum drm_user_fence_cmp cmp_op;
};
void drm_user_fence_init(struct drm_user_fence *ufence,
struct workqueue_struct *wq,
const struct drm_user_fence_ops *ops);
+void drm_user_fence_set_compare(struct drm_user_fence *ufence,
+ u64 __user *addr, u64 value,
+ enum drm_user_fence_cmp op);
/**
* drm_user_fence_get - Acquire a reference to a user fence
@@ -105,11 +127,35 @@ static inline bool drm_user_fence_cancel(struct drm_user_fence *ufence)
* drm_user_fence_cancel_sync - Cancel callback and wait for worker to finish
* @ufence: user fence
*
- * Must be called during teardown before freeing resources. May sleep.
+ * Must be called during teardown before freeing any resources accessed
+ * by ops->worker(). May sleep.
*/
static inline void drm_user_fence_cancel_sync(struct drm_user_fence *ufence)
{
drm_work_fence_cancel_sync(&ufence->base);
}
+/**
+ * drm_user_fence_cmp_match - Test a value against the compare filter
+ * @cur_val: value read from userspace (already converted from LE)
+ * @cmp_value: expected value
+ * @op: comparison operator
+ *
+ * Return: true if the comparison passes, false otherwise.
+ */
+static inline bool drm_user_fence_cmp_match(u64 cur_val, u64 cmp_value,
+ enum drm_user_fence_cmp op)
+{
+ switch (op) {
+ case DRM_USER_FENCE_CMP_EQ:
+ return cur_val == cmp_value;
+ case DRM_USER_FENCE_CMP_NEQ:
+ return cur_val != cmp_value;
+ case DRM_USER_FENCE_CMP_GTE:
+ return cur_val >= cmp_value;
+ default:
+ return false;
+ }
+}
+
#endif /* __DRM_USER_FENCE_H__ */
--
2.34.1