[PATCH 4/4] drm: Add per-signal compare functionality to drm_user_fence
From: Srinivasan Shanmugam
Date: Wed Sep 02 2026 - 11:12:16 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() with mm_ok=true only if
the comparison passes. If the process MM is gone or the read fails, the
worker is called with mm_ok=false to allow mandatory housekeeping (e.g.
wake_up()).
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: I200dd4bf32286ba9bdd2ecb68fffb3c205f9fd52
---
drivers/gpu/drm/drm_user_fence.c | 97 +++++++++++++++++++++++++++++---
include/drm/drm_user_fence.h | 52 ++++++++++++++++-
2 files changed, 138 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/drm_user_fence.c b/drivers/gpu/drm/drm_user_fence.c
index 0f229b7210a9..06a5683db10d 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,46 @@ 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;
+ }
+ }
+ }
+
+ /*
+ * Always invoke the worker so drivers can perform mandatory
+ * housekeeping (e.g. wake_up()). Pass false if the compare
+ * filter suppressed the write.
+ */
+ ufence->ops->worker(ufence, call_worker ? !!mm : false);
if (mm) {
kthread_unuse_mm(mm);
@@ -63,8 +105,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
+ * with mm_ok=true if the value at @addr matches @value according to @op.
+ * The worker is always called for mandatory housekeeping.
+ * If the MM is gone or the read fails, mm_ok is passed as false.
+ *
+ * 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 (!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 36c61aa7d102..1e3b017cbc30 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
@@ -106,11 +128,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