On Fri, 03 Jul 2026 09:31:40 +0200 Philipp Stanner phasta@kernel.org wrote:
C's dma_fence's are synchronisation primitives that will be needed by all Rust GPU drivers.
The dma_fence framework sets a number of rules, notably:
- fences must only be signalled once
- all fences must be signalled at some point
- fence error codes must only be set before signalling
- every pointer to a fence must be backed by a reference
All those rules are being addressed by these abstractions.
To cleanly decouple fence issuers and consumers, two types are provided:
- DriverFence: the only fence type that can be signalled and that carries driver-specific data.
- Fence: the fence type to be shared with other drivers and / or userspace. The only type callbacks can be registered on. Cannot be signalled.
Hereby, a Fence lives in the same chunk of memory as a DriverFence. Both share the refcount of the underlying C dma_fence. Since this implementation does not provide a custom dma_fence_backend_ops.release() function, the memory is freed by the dma_fence backend once the refcount drops to 0.
To create a DriverFence, the user must first allocate a DriverFenceAllocation, so that the creation of the DriverFence later on can always succeed. Otherwise, deadlocks could occur if fences need to be created in a GPU job submission path.
Synchronization is ensured by the dma_fence backend.
All DriverFence's created through this abstraction must be signalled by the creator with an error code. In case a DriverFence drops without being signalled beforehand, it is signalled with -ECANCELLED as its error and a warning is printed. This allows the Rust abstraction to very cleanly decouple fence issuer and consumer by relying on the decoupling mechanisms in the C backend, which ensures through RCU and the 'signalled' fence-flag that dma_fence_backend_ops functions cannot access the potentially unloaded driver code anymore.
Signalling fences on drop thus grants many advantages. Not signalling fences on drop would risk deadlock and does not grant real advantages: By definition only the drivers can ensure that a fence always represents the hardware's state correctly.
This implementation models a DmaFenceCtx (fence context) object on which fences are to be created, thereby ensuring correct sequence numbering according to the timeline.
dma_fence supports a variety of callbacks. The mandatory callbacks (get_timeline_name() and get_driver_name()) are implemented in this patch. For convenience, they store those name parameters in the fence context, saving the driver from implementing these two callbacks.
Support for other callbacks (like for hardware signalling) is prepared for through the fact that both DriverFence and Fence live in the same allocation, allowing for usage of container_of from the callback to access the driver-specific data.
Synchronization for backend_ops callbacks is ensured by only running the Rust deconstructor delayed with call_rcu(), which prevents UAF-bugs should a DriverFence drop while a Fence callback is currently operating on the associated driver data.
Add abstractions for dma_fence in Rust.
Signed-off-by: Philipp Stanner phasta@kernel.org
rust/bindings/bindings_helper.h | 1 + rust/helpers/dma_fence.c | 48 ++ rust/helpers/helpers.c | 1 + rust/kernel/dma_buf/dma_fence.rs | 894 +++++++++++++++++++++++++++++++ rust/kernel/dma_buf/mod.rs | 14 + rust/kernel/lib.rs | 1 + 6 files changed, 959 insertions(+) create mode 100644 rust/helpers/dma_fence.c create mode 100644 rust/kernel/dma_buf/dma_fence.rs create mode 100644 rust/kernel/dma_buf/mod.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 1124785e210b..54b62d952e01 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -53,6 +53,7 @@ #include <linux/debugfs.h> #include <linux/device/faux.h> #include <linux/dma-direction.h> +#include <linux/dma-fence.h> #include <linux/dma-mapping.h> #include <linux/dma-resv.h> #include <linux/errname.h> diff --git a/rust/helpers/dma_fence.c b/rust/helpers/dma_fence.c new file mode 100644 index 000000000000..0e08411098fa --- /dev/null +++ b/rust/helpers/dma_fence.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0
+#include <linux/dma-fence.h>
+__rust_helper void rust_helper_dma_fence_get(struct dma_fence *f) +{
- dma_fence_get(f);
+}
+__rust_helper void rust_helper_dma_fence_put(struct dma_fence *f) +{
- dma_fence_put(f);
+}
+__rust_helper bool rust_helper_dma_fence_begin_signalling(void) +{
- return dma_fence_begin_signalling();
+}
+__rust_helper void rust_helper_dma_fence_end_signalling(bool cookie) +{
- dma_fence_end_signalling(cookie);
+}
+__rust_helper bool rust_helper_dma_fence_is_signaled(struct dma_fence *f) +{
- return dma_fence_is_signaled(f);
+}
+__rust_helper bool rust_helper_dma_fence_test_signaled_flag(struct dma_fence *f) +{
- return dma_fence_test_signaled_flag(f);
+}
+__rust_helper void rust_helper_dma_fence_lock_irqsave(struct dma_fence *f, unsigned long *flags) +{
- dma_fence_lock_irqsave(f, *flags);
+}
+__rust_helper void rust_helper_dma_fence_unlock_irqrestore(struct dma_fence *f, unsigned long *flags) +{
- dma_fence_unlock_irqrestore(f, *flags);
+}
+__rust_helper void rust_helper_dma_fence_set_error(struct dma_fence *f, int error) +{
- dma_fence_set_error(f, error);
+} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 998e31052e66..4ab8aa9da7e7 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -58,6 +58,7 @@ #include "cred.c" #include "device.c" #include "dma.c" +#include "dma_fence.c" #include "dma-resv.c" #include "drm.c" #include "drm_gpuvm.c" diff --git a/rust/kernel/dma_buf/dma_fence.rs b/rust/kernel/dma_buf/dma_fence.rs new file mode 100644 index 000000000000..cbe8f447a603 --- /dev/null +++ b/rust/kernel/dma_buf/dma_fence.rs @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (T) 2025, 2026 Red Hat Inc.: +// - Philipp Stanner pstanner@redhat.com
+//! DriverFence support. +//! +//! Reference: https://docs.kernel.org/driver-api/dma-buf.html#c.dma_fence +//! +//! T header: [`include/linux/dma-fence.h`](srctree/include/linux/dma-fence.h)
+use crate::{
- alloc::AllocError,
- bindings,
- container_of,
- error::to_result,
- prelude::*,
- types::ForeignOwnable,
- types::Opaque,
- warn_on, //
+};
+use core::{
- marker::PhantomData,
- mem::ManuallyDrop,
- ops::Deref,
- ptr,
- ptr::{
drop_in_place,NonNull, //- },
- sync::atomic::{
AtomicU64,Ordering, //- }, //
+};
+use kernel::{
- str::CString,
- sync::{
aref::{ARef,AlwaysRefCounted, //},rcu::rcu_barrier, //- }, //
+};
+/// VTable for dma_fence backend_ops callbacks. +// +// Mandatory dma_fence backend_ops are implemented implicitly through +// [`FenceCtx`]. Additional ones shall get implemented on this trait, which then +// shall be demanded for the fence context data. +pub trait FenceCtxOps {
- /// The generic payload data for [`DriverFence`]s created on this fctx.
- type FenceDataType: Send + Sync;
+}
+/// A dma-fence context. A fence context takes care of associating related fences +/// with each other, providing each with raising sequence numbers and a common +/// identifier. +#[pin_data(PinnedDrop)] +pub struct FenceCtx<T: Send + Sync> {
- /// The fence context number.
- nr: u64,
- /// The sequence number for the next fence created.
- seqno: AtomicU64,
- // The name parameters can be accessed by the dma_fence backend_ops. UAF
- // errors are prevented by the `call_rcu()` in `drop_driver_fence_data()`.
- /// The name of the driver this FenceCtx's fences belong to.
- driver_name: CString,
- /// The name of the timeline this FenceCtx's fences belong to.
- timeline_name: CString,
- /// The user's data.
- #[pin]
- data: T,
+}
+#[allow(unused_unsafe)]
Why is this needed?
+impl<'a, T: Send + Sync + FenceCtxOps> FenceCtx<T> {
- // This can later be extended as a vtable in case other parties need support
- // for the more "exotic" callbacks.
- const OPS: bindings::dma_fence_ops = bindings::dma_fence_ops {
get_driver_name: Some(Self::get_driver_name),get_timeline_name: Some(Self::get_timeline_name),enable_signaling: None,signaled: None,wait: None,release: None,set_deadline: None,- };
- /// Create a new `FenceCtx`.
- pub fn new<E>(
driver_name: CString,timeline_name: CString,data: impl PinInit<T, E>,- ) -> impl PinInit<Self, Error>
- where
[...]
- unsafe { bindings::dma_fence_put(fence) };
- // The actual memory the data associated with a `DriverFence` lives in
- // gets freed by the C dma_fence backend once the fence's refcount reaches 0.
+} diff --git a/rust/kernel/dma_buf/mod.rs b/rust/kernel/dma_buf/mod.rs new file mode 100644 index 000000000000..fb353ce042ce --- /dev/null +++ b/rust/kernel/dma_buf/mod.rs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT
+//! DMA-buf subsystem abstractions.
+pub mod dma_fence;
+pub use self::dma_fence::{
- DriverFence,
- Fence,
- FenceCb,
- FenceCbRegistration,
- FenceCtx,
- FenceCtxOps, //
+}; diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 9512af7156df..dc54d8af471f 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -64,6 +64,7 @@ pub mod device_id; pub mod devres; pub mod dma; +pub mod dma_buf; pub mod driver; #[cfg(CONFIG_DRM = "y")] pub mod drm; -- 2.54.0