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 <[email protected]>
Cc: Alex Deucher <[email protected]>
Cc: Matthew Brost <[email protected]>
Cc: Thomas Hellström <[email protected]>
Cc: [email protected]
Cc: [email protected]
Signed-off-by: Srinivasan Shanmugam <[email protected]>
Change-Id: I03a726d5368674d06bd40fe7d11effd88c492741
---
v6:
 - Add WARN_ON_ONCE(!IS_ENABLED(CONFIG_64BIT)) in
   drm_user_fence_set_compare() to warn if called on a 32-bit system.
   Plain WARN_ON_ONCE() is used since drm_user_fence has no
   struct drm_device * reference. (Thomas Hellström review)

 drivers/gpu/drm/drm_user_fence.c | 82 +++++++++++++++++++++++++++++++-
 include/drm/drm_user_fence.h     | 29 +++++++++++
 2 files changed, 110 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/drm_user_fence.c b/drivers/gpu/drm/drm_user_fence.c
index 664178e2d74c..e55d03ebf75f 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,38 @@ 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)
+{
+       WARN_ON_ONCE(!IS_ENABLED(CONFIG_64BIT));
+
+       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

Reply via email to