Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Matthew Brost <matthew.brost@intel.com>
To: Srinivasan Shanmugam <srinivasan.shanmugam@amd.com>
Cc: "Christian König" <christian.koenig@amd.com>,
	"Alex Deucher" <alexander.deucher@amd.com>,
	"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
	"Maxime Ripard" <mripard@kernel.org>,
	"Thomas Zimmermann" <tzimmermann@suse.de>,
	"David Airlie" <airlied@gmail.com>,
	"Simona Vetter" <simona@ffwll.ch>,
	"Sumit Semwal" <sumit.semwal@linaro.org>,
	"Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
	dri-devel@lists.freedesktop.org, intel-xe@lists.freedesktop.org,
	linux-media@vger.kernel.org, linaro-mm-sig@lists.linaro.org,
	linux-kernel@vger.kernel.org, amd-gfx@lists.freedesktop.org
Subject: Re: [PATCH v4 1/2] drm: Add common drm_user_fence helper
Date: Fri, 28 Aug 2026 00:17:03 -0700	[thread overview]
Message-ID: <apE17lJZmTD4+5jR@gsse-cloud1.jf.intel.com> (raw)
In-Reply-To: <20260828063103.4106629-2-srinivasan.shanmugam@amd.com>

On Fri, Aug 28, 2026 at 12:01:02PM +0530, Srinivasan Shanmugam wrote:
> Introduce a common DRM user fence helper providing the kref-managed,
> MM-borrowing dma-fence-callback-to-workqueue pattern used by drivers
> that must access userspace memory from a kthread context when a GPU
> fence signals.
> 
> XE uses this pattern (xe_sync.c) to write a fence completion value
> to a userspace VA. AMDGPU will use the same pattern to signal a
> per-queue eventfd from a user-queue EOP fence callback.
> 
> The helper provides:
>  - struct drm_user_fence: embeddable base structure
>  - struct drm_user_fence_ops: worker/destroy callbacks
>  - drm_user_fence_init(): initialize and grab the process MM
>  - drm_user_fence_get/put(): reference counting
>  - drm_user_fence_add_callback(): attach to a dma-fence
> 
> The worker callback receives a bool indicating whether the process
> MM was successfully obtained, allowing drivers to handle the
> unavailable-MM case (log, skip the userspace write, etc.) without
> duplicating the mmget/kthread_use_mm/mmput boilerplate.
> 
> Suggested-by: Christian König <christian.koenig@amd.com>
> Cc: Alex Deucher <alexander.deucher@amd.com>
> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
> Cc: Maxime Ripard <mripard@kernel.org>
> Cc: Thomas Zimmermann <tzimmermann@suse.de>
> Cc: David Airlie <airlied@gmail.com>
> Cc: Simona Vetter <simona@ffwll.ch>
> Cc: Sumit Semwal <sumit.semwal@linaro.org>
> Cc: Matthew Brost <matthew.brost@intel.com>

First off, I'm supportive of the idea of a common DRM layer for user
fences and updating Xe accordingly.

This isn't a complete review, but here's a quick initial suggestion.

Also, by the way, you're still fighting our CI [1]. Feel free to keep
hammering on it, as that's what it's there for. iirc if kunit fails as
in this case, nothing else will run. Ask AI and should be able to get
instructions on how to build our kunit + run it (it doesn't require
Intel hardware in a lot of cases).

[1] https://patchwork.freedesktop.org/series/172930/

> Cc: Thomas Hellström <thomas.hellstrom@linux.intel.com>
> Cc: dri-devel@lists.freedesktop.org
> Cc: intel-xe@lists.freedesktop.org
> Cc: linux-media@vger.kernel.org
> Cc: linaro-mm-sig@lists.linaro.org
> Cc: linux-kernel@vger.kernel.org
> Cc: amd-gfx@lists.freedesktop.org
> Signed-off-by: Srinivasan Shanmugam <srinivasan.shanmugam@amd.com>
> ---
> v4:
>  - Check cancel_work_sync() return value in drm_user_fence_cancel_sync()
>    and call drm_user_fence_put() if work was dequeued, fixing a memory
>    leak of the drm_user_fence, mm_struct and stored dma_fence when a
>    pending work item is cancelled. (Sashiko review)
> 
>  drivers/gpu/drm/Makefile         |   1 +
>  drivers/gpu/drm/drm_user_fence.c | 223 +++++++++++++++++++++++++++++++
>  include/drm/drm_user_fence.h     |  76 +++++++++++
>  3 files changed, 300 insertions(+)
>  create mode 100644 drivers/gpu/drm/drm_user_fence.c
>  create mode 100644 include/drm/drm_user_fence.h
> 
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index e97faabcd783..52de1f474535 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -69,6 +69,7 @@ drm-y := \
>  	drm_syncobj.o \
>  	drm_sysfs.o \
>  	drm_trace_points.o \
> +	drm_user_fence.o \
>  	drm_vblank.o \
>  	drm_vblank_work.o \
>  	drm_vma_manager.o \
> diff --git a/drivers/gpu/drm/drm_user_fence.c b/drivers/gpu/drm/drm_user_fence.c
> new file mode 100644
> index 000000000000..cdc47d092cbb
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_user_fence.c
> @@ -0,0 +1,223 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright © 2024 The Linux Foundation
> + *
> + * Common DRM user fence helper.
> + *
> + * When a GPU dma-fence signals, drivers often need to write a value to a
> + * userspace VA or notify userspace via an eventfd. Both operations require
> + * a valid process MM, which is not available in IRQ context.
> + *
> + * This helper queues a work item on fence signal. The work item borrows the
> + * process MM via kthread_use_mm() and calls ops->worker(), which the driver
> + * implements to perform the actual userspace access.
> + */
> +
> +#include <linux/kthread.h>
> +#include <linux/sched/mm.h>
> +#include <linux/workqueue.h>
> +
> +#include <drm/drm_user_fence.h>
> +
> +static void drm_user_fence_destroy(struct kref *kref)
> +{
> +	struct drm_user_fence *ufence =
> +		container_of(kref, struct drm_user_fence, refcount);
> +
> +	/* Release the extra reference stored for cancel() */
> +	if (ufence->fence)
> +		dma_fence_put(ufence->fence);
> +
> +	mmdrop(ufence->mm);
> +	ufence->ops->destroy(ufence);
> +}
> +
> +/**
> + * drm_user_fence_get - Acquire a reference to a user fence
> + * @ufence: user fence
> + */
> +void drm_user_fence_get(struct drm_user_fence *ufence)
> +{
> +	kref_get(&ufence->refcount);
> +}
> +EXPORT_SYMBOL_GPL(drm_user_fence_get);
> +
> +/**
> + * drm_user_fence_put - Release a reference to a user fence
> + * @ufence: user fence
> + */
> +void drm_user_fence_put(struct drm_user_fence *ufence)
> +{
> +	kref_put(&ufence->refcount, drm_user_fence_destroy);
> +}
> +EXPORT_SYMBOL_GPL(drm_user_fence_put);
> +
> +static void drm_user_fence_work(struct work_struct *w)
> +{
> +	struct drm_user_fence *ufence =
> +		container_of(w, struct drm_user_fence, work);
> +	bool mm_ok = false;
> +
> +	if (mmget_not_zero(ufence->mm)) {
> +		kthread_use_mm(ufence->mm);
> +		mm_ok = true;
> +	}
> +
> +	ufence->ops->worker(ufence, mm_ok);
> +
> +	if (mm_ok) {
> +		kthread_unuse_mm(ufence->mm);
> +		mmput(ufence->mm);
> +	}
> +
> +	drm_user_fence_put(ufence);
> +}
> +
> +static void drm_user_fence_cb(struct dma_fence *fence, struct dma_fence_cb *cb)
> +{
> +	struct drm_user_fence *ufence =
> +		container_of(cb, struct drm_user_fence, cb);
> +
> +	queue_work(ufence->wq, &ufence->work);
> +	/*
> +	 * Put the transferred reference from add_callback. The stored
> +	 * reference in ufence->fence is released in drm_user_fence_destroy().
> +	 */
> +	dma_fence_put(fence);
> +}
> +
> +/**
> + * drm_user_fence_init - Initialize a user fence
> + * @ufence: user fence to initialize
> + * @wq: workqueue to run the worker on (must be ordered if sequencing matters)
> + * @ops: driver operations
> + *
> + * Must be called from process context with a valid current->mm.
> + * Grabs a reference to current->mm via mmgrab().
> + */
> +void drm_user_fence_init(struct drm_user_fence *ufence,
> +			 struct workqueue_struct *wq,
> +			 const struct drm_user_fence_ops *ops)
> +{
> +	kref_init(&ufence->refcount);
> +	ufence->mm = current->mm;
> +	mmgrab(ufence->mm);
> +	ufence->wq = wq;
> +	ufence->ops = ops;
> +	ufence->fence = NULL;
> +	INIT_WORK(&ufence->work, drm_user_fence_work);
> +}
> +EXPORT_SYMBOL_GPL(drm_user_fence_init);
> +
> +/**
> + * drm_user_fence_add_callback - Attach a user fence to a dma-fence
> + * @ufence: user fence
> + * @fence: dma-fence to watch; ownership of this reference is transferred
> + *         to the callback — caller must NOT put it afterward.
> + *
> + * When @fence signals, a work item is queued that calls ops->worker() with
> + * the process MM active. If @fence has already signaled the work item is
> + * queued immediately.
> + *
> + * An additional reference to @fence is stored internally in @ufence to
> + * allow drm_user_fence_cancel() to be called safely without the caller
> + * needing to hold a separate fence reference.
> + *
> + * On any return value the caller's fence reference is consumed.
> + *
> + * Return: 0 on success, negative errno on error.
> + */
> +int drm_user_fence_add_callback(struct drm_user_fence *ufence,
> +				struct dma_fence *fence)
> +{
> +	int err;
> +
> +	drm_user_fence_get(ufence);
> +
> +	/* Extra ref stored for cancel() — lives until drm_user_fence_destroy() */
> +	ufence->fence = dma_fence_get(fence);
> +
> +	err = dma_fence_add_callback(fence, &ufence->cb, drm_user_fence_cb);
> +	if (err == -ENOENT) {
> +		/* fence already signaled — queue work and release transferred ref */
> +		queue_work(ufence->wq, &ufence->work);
> +		dma_fence_put(fence);
> +		err = 0;
> +	} else if (err) {
> +		dma_fence_put(ufence->fence);
> +		ufence->fence = NULL;
> +		drm_user_fence_put(ufence);
> +		dma_fence_put(fence);
> +	}
> +	/* on success: transferred ref goes to drm_user_fence_cb */
> +
> +	return err;
> +}
> +EXPORT_SYMBOL_GPL(drm_user_fence_add_callback);
> +
> +/**
> + * drm_user_fence_cancel - Cancel a pending user fence callback
> + * @ufence: user fence
> + *
> + * Attempts to remove the pending callback before driver context teardown.
> + * Must be called before the driver tears down its workqueue or ops.
> + * The caller must hold a reference to @ufence across this call.
> + *
> + * If the callback has already fired this returns false and no additional
> + * action is needed — the callback handles its own reference.
> + *
> + * If removal succeeds the callback reference is released internally.
> + * The caller must still release its own separate reference via
> + * drm_user_fence_put() when done with the object.
> + *
> + * This function is safe to call from atomic context as it only acquires
> + * the dma-fence spinlock internally. If the caller also needs to wait
> + * for the worker to finish, use drm_user_fence_cancel_sync() instead,
> + * which may sleep.
> + *
> + * Return: true if callback was removed, false if it had already fired.
> + */
> +bool drm_user_fence_cancel(struct drm_user_fence *ufence)
> +{
> +	struct dma_fence *fence = ufence->fence;
> +
> +	if (!fence)
> +		return false;
> +
> +	if (dma_fence_remove_callback(fence, &ufence->cb)) {
> +		/*
> +		 * Callback will not fire — release the transferred reference
> +		 * that would have been put by drm_user_fence_cb(). The stored
> +		 * reference in ufence->fence is released in destroy().
> +		 */
> +		dma_fence_put(fence);
> +		drm_user_fence_put(ufence);
> +		return true;
> +	}
> +
> +	/* Callback already fired — it handled its own cleanup */
> +	return false;
> +}
> +EXPORT_SYMBOL_GPL(drm_user_fence_cancel);
> +
> +/**
> + * drm_user_fence_cancel_sync - Cancel callback and wait for worker to finish
> + * @ufence: user fence
> + *
> + * Calls drm_user_fence_cancel() then cancel_work_sync() to guarantee
> + * the worker has fully completed before returning.
> + *
> + * This function may sleep. Must not be called from atomic or interrupt
> + * context. Use drm_user_fence_cancel() instead when sleeping is not
> + * allowed.
> + *
> + * Drivers must call this during teardown before freeing any resources
> + * accessed by ops->worker().
> + */
> +void drm_user_fence_cancel_sync(struct drm_user_fence *ufence)
> +{
> +	drm_user_fence_cancel(ufence);
> +	if (cancel_work_sync(&ufence->work))
> +		drm_user_fence_put(ufence);
> +}
> +EXPORT_SYMBOL_GPL(drm_user_fence_cancel_sync);
> diff --git a/include/drm/drm_user_fence.h b/include/drm/drm_user_fence.h
> new file mode 100644
> index 000000000000..02a02266ab93
> --- /dev/null
> +++ b/include/drm/drm_user_fence.h
> @@ -0,0 +1,75 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright © 2024 The Linux Foundation
> + */
> +
> +#ifndef __DRM_USER_FENCE_H__
> +#define __DRM_USER_FENCE_H__
> +
> +#include <linux/dma-fence.h>
> +#include <linux/kref.h>
> +#include <linux/workqueue.h>
> +
> +struct drm_user_fence;
> +
> +/**
> + * struct drm_user_fence_ops - driver callbacks for a DRM user fence
> + */
> +struct drm_user_fence_ops {
> +	/**
> +	 * @worker: Called from workqueue context.
> +	 *
> +	 * If @mm_ok is true, kthread_use_mm() is active and userspace memory
> +	 * (copy_to_user, eventfd_signal, etc.) may be accessed safely.
> +	 * If @mm_ok is false, the process MM was already gone; the driver
> +	 * should log a warning and skip the userspace write.
> +	 *
> +	 * wake_up() or other post-signal housekeeping should also happen here.
> +	 */
> +	void (*worker)(struct drm_user_fence *ufence, bool mm_ok);
> +
> +	/**
> +	 * @destroy: Called when the last reference is dropped.
> +	 * Free the containing structure here.
> +	 */
> +	void (*destroy)(struct drm_user_fence *ufence);
> +};
> +
> +/**
> + * struct drm_user_fence - embeddable DRM user fence
> + *
> + * Drivers embed this in their own structure and implement
> + * &drm_user_fence_ops. 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 driver teardown.
> + */
> +struct drm_user_fence {

Should this common layer be split into two distinct concepts?

- drm_work_fence: 90% of what is here, minus the kthread_use_mm() and
  mm-related code.
- drm_user_fence: a subclass of drm_work_fence that adds the
  kthread_use_mm() and mm-related code.

I suggest this because I was thinking about it the other day (I forget
the exact context) and reconsidered a pattern where a fence signals and
then I need a worker because some work must be done outside of IRQ
context. A user fence is one example, since copy_to_user() can fault,
which is not allowed in IRQ context. At various times in Xe we've had
multiple patterns like this, although at the moment user fences are
probably the only case that requires it. If we looked across DRM as a
whole, I suspect we'd find this pattern open-coded in a number of
places.

Yes, drm_user_fence would be a very thin layer on top of drm_work_fence,
but I still see value in the split.
 
Matt

> +	/** @refcount: Reference count. */
> +	struct kref refcount;
> +	/** @mm: Process MM grabbed at init time. */
> +	struct mm_struct *mm;
> +	/** @work: Work item queued when the dma-fence signals. */
> +	struct work_struct work;
> +	/** @cb: dma-fence callback. */
> +	struct dma_fence_cb cb;
> +	/**
> +	 * @fence: Extra reference held for safe cancel(). Set during
> +	 * add_callback, released in destroy().
> +	 */
> +	struct dma_fence *fence;
> +	/** @wq: Workqueue to run @work on. */
> +	struct workqueue_struct *wq;
> +	/** @ops: Driver operations. */
> +	const struct drm_user_fence_ops *ops;
> +};
> +
> +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_get(struct drm_user_fence *ufence);
> +void drm_user_fence_put(struct drm_user_fence *ufence);
> +int drm_user_fence_add_callback(struct drm_user_fence *ufence,
> +				struct dma_fence *fence);
> +bool drm_user_fence_cancel(struct drm_user_fence *ufence);
> +void drm_user_fence_cancel_sync(struct drm_user_fence *ufence);
> +
> +#endif /* __DRM_USER_FENCE_H__ */
> -- 
> 2.34.1
> 

  reply	other threads:[~2026-08-28  7:17 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <20260828063103.4106629-1-srinivasan.shanmugam@amd.com>
2026-08-28  6:31 ` [PATCH v4 1/2] drm: Add common drm_user_fence helper Srinivasan Shanmugam
2026-08-28  7:17   ` Matthew Brost [this message]
2026-08-28  8:06     ` SHANMUGAM, SRINIVASAN
2026-08-28  8:18       ` Christian König
2026-08-28  8:31         ` SHANMUGAM, SRINIVASAN
2026-08-28  9:12           ` Christian König
2026-08-28  9:32             ` SHANMUGAM, SRINIVASAN
2026-08-28  6:31 ` [PATCH v4 2/2] drm/xe: Convert xe_user_fence to drm_user_fence Srinivasan Shanmugam
2026-08-28  6:44 ` ✗ CI.checkpatch: warning for series starting with [v4,1/2] drm: Add common drm_user_fence helper Patchwork
2026-08-28  6:45 ` ✗ CI.KUnit: failure " Patchwork
     [not found] <20260828062917.4106569-1-srinivasan.shanmugam@amd.com>
2026-08-28  6:29 ` [PATCH v4 1/2] " Srinivasan Shanmugam

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=apE17lJZmTD4+5jR@gsse-cloud1.jf.intel.com \
    --to=matthew.brost@intel.com \
    --cc=airlied@gmail.com \
    --cc=alexander.deucher@amd.com \
    --cc=amd-gfx@lists.freedesktop.org \
    --cc=christian.koenig@amd.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=linaro-mm-sig@lists.linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=srinivasan.shanmugam@amd.com \
    --cc=sumit.semwal@linaro.org \
    --cc=thomas.hellstrom@linux.intel.com \
    --cc=tzimmermann@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox