[PATCH 4/4] drm: Add per-signal compare functionality to drm_user_fence

From: Srinivasan Shanmugam

Date: Sun Aug 30 2026 - 01:57:32 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_addr is set, drm_user_fence reads the value from userspace
via get_user() and calls ops->worker() only if the comparison passes.
If the process MM is gone and cmp_addr is set, the worker is skipped
since the comparison cannot be performed.

Drivers that do not need filtering (e.g. XE) leave cmp_addr NULL 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: I44fe8ec50babaa140dd875c00e249e0f6edc0551
---
drivers/gpu/drm/drm_user_fence.c | 80 +++++++++++++++++++++++++++++++-
include/drm/drm_user_fence.h | 29 ++++++++++++
2 files changed, 108 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_user_fence.c b/drivers/gpu/drm/drm_user_fence.c
index 664178e2d74c..9e141533622e 100644
--- a/drivers/gpu/drm/drm_user_fence.c
+++ b/drivers/gpu/drm/drm_user_fence.c
@@ -12,21 +12,68 @@

#include <linux/kthread.h>
#include <linux/sched/mm.h>
+#include <linux/uaccess.h>

#include <drm/drm_user_fence.h>

+static bool drm_user_fence_cmp_match(u64 cur_val, u64 expected,
+ enum drm_user_fence_cmp op)
+{
+ switch (op) {
+ case DRM_USER_FENCE_CMP_EQ:
+ return cur_val == expected;
+ case DRM_USER_FENCE_CMP_NE:
+ return cur_val != expected;
+ case DRM_USER_FENCE_CMP_GT:
+ return cur_val > expected;
+ case DRM_USER_FENCE_CMP_GE:
+ return cur_val >= expected;
+ case DRM_USER_FENCE_CMP_LT:
+ return cur_val < expected;
+ case DRM_USER_FENCE_CMP_LE:
+ return cur_val <= expected;
+ default:
+ return true;
+ }
+}
+
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);
bool mm_ok = false;
+ bool call_worker = true;

if (mmget_not_zero(ufence->mm)) {
kthread_use_mm(ufence->mm);
mm_ok = true;
}

- ufence->ops->worker(ufence, mm_ok);
+ /*
+ * Per-signal comparison: read a value from userspace and compare
+ * with the expected value. Skip ops->worker if the condition is
+ * not met. Drivers that do not need filtering leave cmp_addr NULL.
+ *
+ * If the MM is gone and cmp_addr is set we cannot perform the
+ * comparison, so skip the worker rather than calling it without
+ * having verified the condition.
+ */
+ if (ufence->cmp_op != DRM_USER_FENCE_CMP_NONE) {
+ if (!mm_ok) {
+ call_worker = false;
+ } else {
+ u64 cur_val;
+
+ if (get_user(cur_val, ufence->cmp_addr) ||
+ !drm_user_fence_cmp_match(cur_val,
+ ufence->cmp_value,
+ ufence->cmp_op))
+ call_worker = false;
+ }
+ }
+
+ if (call_worker)
+ ufence->ops->worker(ufence, mm_ok);

if (mm_ok) {
kthread_unuse_mm(ufence->mm);
@@ -65,5 +112,36 @@ void drm_user_fence_init(struct drm_user_fence *ufence,
ufence->mm = current->mm;
mmgrab(ufence->mm);
ufence->ops = ops;
+ ufence->cmp_addr = NULL;
+ ufence->cmp_value = 0;
+ ufence->cmp_op = DRM_USER_FENCE_CMP_NONE;
}
EXPORT_SYMBOL_GPL(drm_user_fence_init);
+
+/**
+ * drm_user_fence_set_compare - Configure per-signal value comparison
+ * @ufence: user fence
+ * @addr: userspace VA to read when the fence signals
+ * @value: expected value to compare against
+ * @op: comparison operator (see &enum drm_user_fence_cmp)
+ *
+ * When set, drm_user_fence reads @addr via get_user() each time the
+ * fence signals and calls ops->worker() only if the comparison passes.
+ * This enables per-signal filtering without open-coding the read+compare
+ * pattern in each driver.
+ *
+ * Must be called after drm_user_fence_init() and before
+ * drm_user_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)
+{
+ if (WARN_ON(op != DRM_USER_FENCE_CMP_NONE && !addr))
+ 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 2b2b640f510f..764dd55ed3a2 100644
--- a/include/drm/drm_user_fence.h
+++ b/include/drm/drm_user_fence.h
@@ -33,6 +33,22 @@ struct drm_user_fence_ops {
void (*destroy)(struct drm_user_fence *ufence);
};

+/**
+ * enum drm_user_fence_cmp - comparison operators for per-signal filtering
+ *
+ * Used with drm_user_fence_set_compare() to control when ops->worker is
+ * called based on a value read from userspace.
+ */
+enum drm_user_fence_cmp {
+ DRM_USER_FENCE_CMP_NONE = 0, /* always call worker — default */
+ DRM_USER_FENCE_CMP_EQ, /* call worker if *addr == value */
+ DRM_USER_FENCE_CMP_NE, /* call worker if *addr != value */
+ DRM_USER_FENCE_CMP_GT, /* call worker if *addr > value */
+ DRM_USER_FENCE_CMP_GE, /* call worker if *addr >= value */
+ DRM_USER_FENCE_CMP_LT, /* call worker if *addr < value */
+ DRM_USER_FENCE_CMP_LE, /* call worker if *addr <= value */
+};
+
/**
* struct drm_user_fence - DRM user fence with MM borrowing
*
@@ -51,11 +67,24 @@ struct drm_user_fence {
struct mm_struct *mm;
/** @ops: Driver operations. */
const struct drm_user_fence_ops *ops;
+ /**
+ * @cmp_addr: Userspace VA to read for per-signal comparison.
+ * NULL means always call ops->worker (default XE behavior).
+ * Set via drm_user_fence_set_compare().
+ */
+ u64 __user *cmp_addr;
+ /** @cmp_value: Expected value for comparison. */
+ u64 cmp_value;
+ /** @cmp_op: Comparison operator. */
+ 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);

static inline void drm_user_fence_get(struct drm_user_fence *ufence)
{
--
2.34.1