All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
To: intel-xe@lists.freedesktop.org
Cc: "Thomas Hellström" <thomas.hellstrom@linux.intel.com>
Subject: [RFC PATCH] dma-buf: Add generic dma_resv wound-wait transaction API
Date: Fri,  5 Jun 2026 13:26:58 +0200	[thread overview]
Message-ID: <20260605112700.181040-2-thomas.hellstrom@linux.intel.com> (raw)
In-Reply-To: <20260605112700.181040-1-thomas.hellstrom@linux.intel.com>

Introduce dma_resv_txn, a subsystem-agnostic wound-wait locking
protocol for sets of dma_resv objects.  It is a lower-level analogue
of drm_exec, usable with any object that embeds or references a
dma_resv — not just GEM buffers.

Each participant type provides an ops vtable with callbacks to retrieve
the associated dma_resv and release the object reference.

dma_resv_txn_force_retry() allows callers to trigger an unconditional
retry to recover from non-ww errors such as memory pressure.

Assisted-by: GitHub_Copilot:claude-sonnet-4.6
Signed-off-by: Thomas Hellström <thomas.hellstrom@linux.intel.com>
---
 drivers/dma-buf/Makefile       |   2 +-
 drivers/dma-buf/dma-resv-txn.c | 300 +++++++++++++++++++++++++++++++++
 include/linux/dma-resv-txn.h   | 223 ++++++++++++++++++++++++
 3 files changed, 524 insertions(+), 1 deletion(-)
 create mode 100644 drivers/dma-buf/dma-resv-txn.c
 create mode 100644 include/linux/dma-resv-txn.h

diff --git a/drivers/dma-buf/Makefile b/drivers/dma-buf/Makefile
index b25d7550bacf..6ea13c4bc896 100644
--- a/drivers/dma-buf/Makefile
+++ b/drivers/dma-buf/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
-obj-y := dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \
+obj-y := dma-resv-txn.o dma-buf.o dma-fence.o dma-fence-array.o dma-fence-chain.o \
 	 dma-fence-unwrap.o dma-resv.o dma-buf-mapping.o
 obj-$(CONFIG_DMABUF_HEAPS)	+= dma-heap.o
 obj-$(CONFIG_DMABUF_HEAPS)	+= heaps/
diff --git a/drivers/dma-buf/dma-resv-txn.c b/drivers/dma-buf/dma-resv-txn.c
new file mode 100644
index 000000000000..3f86add4f700
--- /dev/null
+++ b/drivers/dma-buf/dma-resv-txn.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Copyright (C) 2022 Christian König <christian.koenig@amd.com>
+ * Copyright (C) 2023 Rob Clark <robdclark@chromium.org>
+ * Copyright (C) 2023-2026 Thomas Hellström <thomas.hellstrom@linux.intel.com>
+ * Copyright (C) 2024 Danilo Krummrich <dakr@kernel.org>
+ * Copyright (C) 2025 Thomas Zimmermann <tzimmermann@suse.de>
+ *
+ * Based on drivers/gpu/drm/drm_exec.c
+ */
+
+#include <linux/dma-resv-txn.h>
+#include <linux/dma-resv.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+/**
+ * DOC: Overview
+ *
+ * dma_resv_txn provides a generic wound-wait locking protocol for sets of
+ * dma_resv objects.  It is a lower-level, subsystem-agnostic analogue of
+ * drm_exec, usable with any object that embeds or references a dma_resv —
+ * not just GEM buffers.
+ *
+ * The caller must take a reference to each owning object before calling
+ * dma_resv_txn_lock(); that reference is unconditionally consumed by the
+ * call regardless of the return value:
+ *
+ * * On success the reference is stored in the array slot; the transaction
+ *   releases it via @ops->put when the slot is removed.
+ * * On -EDEADLK the reference is transferred to @txn->contended and held
+ *   across the unlock/retry cycle.
+ * * On all other returns the reference is released immediately via @ops->put.
+ */
+
+static void txn_obj_put(struct dma_resv_txn_obj *obj)
+{
+	if (obj->ops && obj->ops->put)
+		obj->ops->put(obj);
+}
+
+static void txn_unlock_all(struct dma_resv_txn *txn)
+{
+	while (txn->num_objects) {
+		struct dma_resv_txn_obj *obj = &txn->objects[--txn->num_objects];
+
+		dma_resv_unlock(obj->ops->resv(obj));
+		txn_obj_put(obj);
+	}
+
+	if (txn->prelocked.ops) {
+		dma_resv_unlock(txn->prelocked.ops->resv(&txn->prelocked));
+		txn_obj_put(&txn->prelocked);
+		txn->prelocked.ops = NULL;
+	}
+}
+
+static int txn_obj_append(struct dma_resv_txn *txn, void *object,
+			  const struct dma_resv_txn_obj_ops *ops)
+{
+	if (unlikely(txn->num_objects == txn->max_objects)) {
+		size_t old_size = txn->max_objects * sizeof(*txn->objects);
+		void *tmp;
+
+		tmp = kvrealloc(txn->objects, old_size + PAGE_SIZE, GFP_KERNEL);
+		if (!tmp)
+			return -ENOMEM;
+
+		txn->objects = tmp;
+		txn->max_objects += PAGE_SIZE / sizeof(*txn->objects);
+	}
+
+	txn->objects[txn->num_objects++] = (struct dma_resv_txn_obj){
+		.ops    = ops,
+		.object = object,
+	};
+	return 0;
+}
+
+/**
+ * dma_resv_txn_init() - initialize a transaction
+ * @txn: the transaction to initialize
+ * @flags: DMA_RESV_TXN_* control flags
+ * @nr: initial capacity hint; 0 means use a default
+ *
+ * Prepares the transaction for use.
+ */
+void dma_resv_txn_init(struct dma_resv_txn *txn, u32 flags, unsigned int nr)
+{
+	if (!nr)
+		nr = PAGE_SIZE / sizeof(*txn->objects);
+
+	txn->flags      = flags;
+	txn->started    = false;
+	txn->objects    = kvmalloc_array(nr, sizeof(*txn->objects), GFP_KERNEL);
+	txn->max_objects = txn->objects ? nr : 0;
+	txn->num_objects = 0;
+	txn->contended.ops  = NULL;
+	txn->prelocked.ops  = NULL;
+}
+EXPORT_SYMBOL_GPL(dma_resv_txn_init);
+
+/**
+ * dma_resv_txn_fini() - finalize a transaction
+ * @txn: the transaction to finalize
+ *
+ * Unlocks all held objects, releases all references, and frees resources.
+ * Safe to call at any point after dma_resv_txn_init().
+ */
+void dma_resv_txn_fini(struct dma_resv_txn *txn)
+{
+	txn_unlock_all(txn);
+	kvfree(txn->objects);
+
+	if (txn->contended.ops) {
+		txn_obj_put(&txn->contended);
+		txn->contended.ops = NULL;
+	}
+
+	if (txn->started)
+		ww_acquire_fini(&txn->ctx);
+}
+EXPORT_SYMBOL_GPL(dma_resv_txn_fini);
+
+/**
+ * dma_resv_txn_cleanup() - loop condition for dma_resv_txn_until_all_locked()
+ * @txn: the transaction
+ *
+ * Not intended for direct use; drive the retry loop with
+ * dma_resv_txn_until_all_locked() instead.
+ *
+ * Return: true if the loop body should run, false when all locks are held.
+ */
+bool dma_resv_txn_cleanup(struct dma_resv_txn *txn)
+{
+	if (!txn->started) {
+		ww_acquire_init(&txn->ctx, &reservation_ww_class);
+		txn->started = true;
+		return true;
+	}
+
+	if (likely(!txn->contended.ops)) {
+		ww_acquire_done(&txn->ctx);
+		return false;
+	}
+
+	txn_unlock_all(txn);
+	return true;
+}
+EXPORT_SYMBOL_GPL(dma_resv_txn_cleanup);
+
+static int txn_lock_contended(struct dma_resv_txn *txn)
+{
+	struct dma_resv_txn_obj *cont = &txn->contended;
+	struct dma_resv *resv;
+	int ret;
+
+	if (!cont->ops)
+		return 0;
+
+	resv = cont->ops->resv(cont);
+
+	if (txn->flags & DMA_RESV_TXN_INTERRUPTIBLE) {
+		ret = dma_resv_lock_slow_interruptible(resv, &txn->ctx);
+		if (unlikely(ret)) {
+			txn_obj_put(cont);
+			cont->ops = NULL;
+			return ret;
+		}
+	} else {
+		dma_resv_lock_slow(resv, &txn->ctx);
+	}
+
+	txn->prelocked = *cont;
+	cont->ops = NULL;
+	return 0;
+}
+
+/**
+ * dma_resv_txn_lock() - lock a dma_resv object under this transaction
+ * @txn: the transaction
+ * @object: opaque pointer to the owning object; stored for iteration
+ * @ops: operations for this object, including the @resv accessor
+ *
+ * The caller must hold one reference to the owning object before calling.
+ * That reference is always consumed, regardless of the return value:
+ *
+ * * 0 — success; reference stored in the locked-objects array.
+ * * -EDEADLK — contention detected; reference transferred to
+ *   @txn->contended.  Call dma_resv_txn_retry_on_contention().
+ * * -EALREADY — the resv returned by @ops->resv() is already locked by this
+ *   context; reference released via @ops->put.  Suppress with
+ *   DMA_RESV_TXN_IGNORE_DUPLICATES.
+ * * Other negative — interrupted or allocation failure; reference released
+ *   via @ops->put.
+ *
+ * Return: 0 on success, negative error code otherwise.
+ */
+int dma_resv_txn_lock(struct dma_resv_txn *txn, void *object,
+		      const struct dma_resv_txn_obj_ops *ops)
+{
+	struct dma_resv_txn_obj caller = { .ops = ops, .object = object };
+	struct dma_resv *resv = ops->resv(&caller);
+	int ret;
+
+	ret = txn_lock_contended(txn);
+	if (unlikely(ret)) {
+		txn_obj_put(&caller);
+		return ret;
+	}
+
+	/*
+	 * If the prelocked resv matches, the lock is already held from the
+	 * slow path.  Consume the caller's ref into the array and release
+	 * the separate ref held by the prelocked slot.
+	 */
+	if (txn->prelocked.ops &&
+	    txn->prelocked.ops->resv(&txn->prelocked) == resv) {
+		ret = txn_obj_append(txn, object, ops);
+		if (unlikely(ret)) {
+			dma_resv_unlock(resv);
+			txn_obj_put(&txn->prelocked);
+			txn->prelocked.ops = NULL;
+			txn_obj_put(&caller);
+			return ret;
+		}
+		txn_obj_put(&txn->prelocked);
+		txn->prelocked.ops = NULL;
+		return 0;
+	}
+
+	if (txn->flags & DMA_RESV_TXN_INTERRUPTIBLE)
+		ret = dma_resv_lock_interruptible(resv, &txn->ctx);
+	else
+		ret = dma_resv_lock(resv, &txn->ctx);
+
+	if (unlikely(ret == -EDEADLK)) {
+		/* Transfer caller's ref to the contended slot. */
+		txn->contended = caller;
+		return -EDEADLK;
+	}
+
+	if (unlikely(ret)) {
+		if (ret == -EALREADY &&
+		    (txn->flags & DMA_RESV_TXN_IGNORE_DUPLICATES))
+			ret = 0;
+		txn_obj_put(&caller);
+		return ret;
+	}
+
+	ret = txn_obj_append(txn, object, ops);
+	if (unlikely(ret)) {
+		dma_resv_unlock(resv);
+		txn_obj_put(&caller);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dma_resv_txn_lock);
+
+/**
+ * dma_resv_txn_unlock() - unlock and remove a single object
+ * @txn: the transaction
+ * @object: opaque pointer identifying the object to unlock
+ *
+ * Scans the locked-objects array from the tail for an entry whose @object
+ * field matches @object, unlocks its resv, releases its reference via
+ * @ops->put, and compacts the array.  Scanning from the tail is efficient
+ * for the common case of unlocking the most recently locked objects.
+ */
+void dma_resv_txn_unlock(struct dma_resv_txn *txn, void *object)
+{
+	unsigned int i;
+
+	for (i = txn->num_objects; i--;) {
+		struct dma_resv_txn_obj *obj = &txn->objects[i];
+
+		if (obj->object != object)
+			continue;
+
+		dma_resv_unlock(obj->ops->resv(obj));
+		txn_obj_put(obj);
+
+		/* Compact: shift remaining entries down by one. */
+		for (++i; i < txn->num_objects; ++i)
+			txn->objects[i - 1] = txn->objects[i];
+		--txn->num_objects;
+		return;
+	}
+
+	WARN(1, "%s: object %p not found in transaction\n", __func__, object);
+}
+EXPORT_SYMBOL_GPL(dma_resv_txn_unlock);
+
+MODULE_DESCRIPTION("dma-resv transaction locking");
+MODULE_LICENSE("Dual MIT/GPL");
+
diff --git a/include/linux/dma-resv-txn.h b/include/linux/dma-resv-txn.h
new file mode 100644
index 000000000000..26be05db51eb
--- /dev/null
+++ b/include/linux/dma-resv-txn.h
@@ -0,0 +1,223 @@
+/* SPDX-License-Identifier: GPL-2.0 OR MIT */
+/*
+ * Copyright (C) 2022 Christian König <christian.koenig@amd.com>
+ * Copyright (C) 2023 Rob Clark <robdclark@chromium.org>
+ * Copyright (C) 2023-2026 Thomas Hellström <thomas.hellstrom@linux.intel.com>
+ *
+ * Based on include/drm/drm_exec.h
+ */
+
+#ifndef __LINUX_DMA_RESV_TXN_H
+#define __LINUX_DMA_RESV_TXN_H
+
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <linux/ww_mutex.h>
+
+struct dma_resv;
+
+/**
+ * define DMA_RESV_TXN_INTERRUPTIBLE - use interruptible waits
+ *
+ * Use interruptible variants of the ww_mutex locking functions.
+ */
+#define DMA_RESV_TXN_INTERRUPTIBLE	BIT(0)
+
+/**
+ * define DMA_RESV_TXN_IGNORE_DUPLICATES - silently skip already-locked resvs
+ *
+ * When a dma_resv is already held by this transaction (e.g. because multiple
+ * objects share the same dma_resv), return 0 instead of -EALREADY.
+ */
+#define DMA_RESV_TXN_IGNORE_DUPLICATES	BIT(1)
+
+/**
+ * struct dma_resv_txn_obj - one participant in a transaction
+ *
+ * Represents an object registered with a transaction.  @object is opaque
+ * to the transaction and available to the @ops callbacks.
+ */
+struct dma_resv_txn_obj {
+	/** @ops: operations for this object; NULL marks an unoccupied slot */
+	const struct dma_resv_txn_obj_ops *ops;
+	/** @object: opaque pointer to the owning object */
+	void *object;
+};
+
+/**
+ * struct dma_resv_txn_obj_ops - per-participant operations
+ *
+ * @resv is mandatory; @put is optional.
+ */
+struct dma_resv_txn_obj_ops {
+	/**
+	 * @resv: return the &dma_resv associated with @obj.
+	 *
+	 * Must not return NULL.  Required.
+	 */
+	struct dma_resv *(*resv)(struct dma_resv_txn_obj *obj);
+	/**
+	 * @put: release the caller's reference to the owning object.
+	 *
+	 * Use @obj->object to identify the owner.  May be NULL.
+	 */
+	void (*put)(struct dma_resv_txn_obj *obj);
+};
+
+/**
+ * struct dma_resv_txn - wound-wait transaction over dma_resv objects
+ *
+ * Manages the wound-wait retry loop required to acquire multiple dma_resv
+ * locks without deadlock.  Drive with dma_resv_txn_until_all_locked().
+ *
+ * The caller must take a reference to each owning object before calling
+ * dma_resv_txn_lock(); that reference is unconditionally consumed by the
+ * call regardless of the return value.
+ */
+struct dma_resv_txn {
+	/** @flags: DMA_RESV_TXN_* control flags */
+	u32 flags;
+	/** @started: wound-wait context active */
+	bool started;
+	/** @ctx: wound-wait acquire context (ticket) */
+	struct ww_acquire_ctx ctx;
+	/**
+	 * @num_objects: number of objects currently in @objects
+	 */
+	unsigned int num_objects;
+	/**
+	 * @max_objects: allocated capacity of @objects
+	 */
+	unsigned int max_objects;
+	/**
+	 * @objects: array of locked object descriptors.
+	 */
+	struct dma_resv_txn_obj *objects;
+	/**
+	 * @contended: object whose resv caused -EDEADLK; held until the next retry.
+	 */
+	struct dma_resv_txn_obj contended;
+	/**
+	 * @prelocked: object acquired via the slow path, pending claim by the
+	 * next matching dma_resv_txn_lock() call.
+	 */
+	struct dma_resv_txn_obj prelocked;
+};
+
+void dma_resv_txn_init(struct dma_resv_txn *txn, u32 flags, unsigned int nr);
+void dma_resv_txn_fini(struct dma_resv_txn *txn);
+bool dma_resv_txn_cleanup(struct dma_resv_txn *txn);
+int dma_resv_txn_lock(struct dma_resv_txn *txn, void *object,
+		      const struct dma_resv_txn_obj_ops *ops);
+void dma_resv_txn_unlock(struct dma_resv_txn *txn, void *object);
+
+/* Internal helpers for the iteration macros; do not use directly. */
+#define __dma_resv_txn_for_each_obj_ops(txn, obj, __ops, __curs)	\
+	for (struct dma_resv_txn_obj *__curs = (txn)->objects;		\
+	     __curs < &(txn)->objects[(txn)->num_objects] &&		\
+		     ((obj) = __curs->object, true);			\
+	     ++__curs)							\
+		if (unlikely(__curs->ops != (__ops))) {} else
+
+#define __dma_resv_txn_for_each_obj_ops_reverse(txn, obj, __ops, __idx)	\
+	for (unsigned long __idx = (txn)->num_objects;			\
+	     __idx-- > 0 &&						\
+		     ((obj) = (txn)->objects[__idx].object, true);	\
+	     )								\
+		if (unlikely((txn)->objects[__idx].ops != (__ops))) {} else
+
+/**
+ * dma_resv_txn_for_each_obj_ops - iterate over locked objects with matching ops
+ * @txn: the transaction
+ * @obj: cursor (void *), set to the @object field of each matching slot
+ * @ops: pointer to the &dma_resv_txn_obj_ops to match against
+ *
+ * Iterate over all locked objects in @txn whose @ops pointer matches @ops.
+ * Useful for subsystems that share a transaction and need to visit only
+ * their own objects.
+ */
+#define dma_resv_txn_for_each_obj_ops(txn, obj, ops)			\
+	__dma_resv_txn_for_each_obj_ops(txn, obj, ops, __UNIQUE_ID(__drt_c))
+
+/**
+ * dma_resv_txn_for_each_obj_ops_reverse - iterate in reverse lock order
+ * @txn: the transaction
+ * @obj: cursor (void *), set to the @object field of each matching slot
+ * @ops: pointer to the &dma_resv_txn_obj_ops to match against
+ *
+ * Like dma_resv_txn_for_each_obj_ops() but in reverse acquisition order.
+ * Required when unlocking.
+ */
+#define dma_resv_txn_for_each_obj_ops_reverse(txn, obj, ops)		\
+	__dma_resv_txn_for_each_obj_ops_reverse(txn, obj, ops,		\
+						__UNIQUE_ID(__drt_c))
+
+/* Internal helper for dma_resv_txn_until_all_locked(). Do not use directly. */
+#define __dma_resv_txn_until_all_locked(txn, _label)			\
+_label:									\
+	for (void *const __maybe_unused __txn_retry_ptr = &&_label;	\
+	     dma_resv_txn_cleanup(txn);)
+
+/**
+ * dma_resv_txn_until_all_locked() - retry loop until all objects locked
+ * @txn: the transaction
+ *
+ * Drives the wound-wait retry loop.  The body is re-entered as long as
+ * contention is detected; at the start of each iteration no locks are held.
+ * Use dma_resv_txn_retry_on_contention() after each lock call.
+ *
+ * Example::
+ *
+ *   dma_resv_txn_until_all_locked(&txn) {
+ *       ret = dma_resv_txn_lock(&txn, obj_a, &my_ops);
+ *       dma_resv_txn_retry_on_contention(&txn);
+ *       if (ret) goto error;
+ *
+ *       ret = dma_resv_txn_lock(&txn, obj_b, &my_ops);
+ *       dma_resv_txn_retry_on_contention(&txn);
+ *       if (ret) goto error;
+ *   }
+ */
+#define dma_resv_txn_until_all_locked(txn)				\
+	__dma_resv_txn_until_all_locked(txn, __UNIQUE_ID(dma_resv_txn_l))
+
+/**
+ * dma_resv_txn_retry_on_contention() - restart loop if contention set
+ * @txn: the transaction
+ *
+ * Must be called after each dma_resv_txn_lock() inside the
+ * dma_resv_txn_until_all_locked() body.
+ */
+#define dma_resv_txn_retry_on_contention(txn)				\
+	do {								\
+		if (unlikely(dma_resv_txn_is_contended(txn)))		\
+			goto *__txn_retry_ptr;				\
+	} while (0)
+
+/**
+ * dma_resv_txn_force_retry() - unconditionally restart the retry loop
+ * @txn: the transaction
+ *
+ * Force a fresh retry iteration regardless of ww contention.  Use this to
+ * unwind the current locking round after a non-ww error (e.g. memory
+ * pressure).  Must be called inside dma_resv_txn_until_all_locked() and
+ * only when no contention is pending.
+ */
+#define dma_resv_txn_force_retry(txn)					\
+	do {								\
+		WARN_ON(dma_resv_txn_is_contended(txn));		\
+		goto *__txn_retry_ptr;					\
+	} while (0)
+
+/**
+ * dma_resv_txn_is_contended() - check whether contention was detected
+ * @txn: the transaction
+ *
+ * Return: true if contention was detected and the retry loop must restart.
+ */
+static inline bool dma_resv_txn_is_contended(struct dma_resv_txn *txn)
+{
+	return !!txn->contended.ops;
+}
+
+#endif /* __LINUX_DMA_RESV_TXN_H */
-- 
2.54.0


  reply	other threads:[~2026-06-05 11:27 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-05 11:26 [RFC PATCH 0/3] Use a dma_resv wound-wait transaction API for drm_exec Thomas Hellström
2026-06-05 11:26 ` Thomas Hellström [this message]
2026-06-05 11:26 ` [RFC PATCH] drm/exec: Rebuild drm_exec on top of dma_resv_txn Thomas Hellström
2026-06-05 11:27 ` [RFC PATCH] drm/xe/tests: Add drm_exec locking benchmark kunit test Thomas Hellström
2026-06-05 12:16 ` ✗ CI.checkpatch: warning for " Patchwork
2026-06-05 12:18 ` ✓ CI.KUnit: success " Patchwork
2026-06-05 13:13 ` ✓ Xe.CI.BAT: " Patchwork
2026-06-05 23:49 ` ✓ Xe.CI.FULL: " Patchwork

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=20260605112700.181040-2-thomas.hellstrom@linux.intel.com \
    --to=thomas.hellstrom@linux.intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.