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
