Skip to contentSkip to conversation

rust / dma_buf: Add abstractions for dma_fence

PATCH Superseded Philipp Stanner 6 participants 20 messages Jun 16 – Jun 17

Subsystems (6)

v2 of 11 Superseded Latest is v11 →

lore
Share & tools
lore

Philipp Stanner

author maintainer
#0 lore raw

v2 cover rust / dma_buf: Add abstractions for dma_fence

New version. Most significant change is regarding RCU. See changelog below. Also see [1].

Changes since v1:
- Remove unnecessary mutable references (Alice)
- Split up unsafe comments where possible (Danilo)
- Remove PhantomData + implement FenceCtx ops trait (Boris)
- Consistently call FenceCtx generic data T. FenceDataType is derived from that. (Boris)
- Add abstractions for call_rcu() and synchronize_rcu() (Danilo)
- Add ECANCELED error code in Rust (Alice)
- Remove the rcu_barrier() from FenceCtx::drop() – because we now use call_rcu(), there can be no UAF access to the FenceCtx anymore. In any case, it is illegal to use either call_rcu() or synchronize_rcu() in FenceCtx::drop(), because our new drop_driver_fence_data() can run in atomic context and might put the last fence_ctx reference.
So we now only have to guard against module unload, which it seems either the driver or Rust driver-core / module unload infrastructure must solve.
- Minor formatting etc. changes
- Add C helpers to MAINTAINERS. (Danilo)
- Ensure that Fence::is_signaled() is fully synchronized, i.e., all callbacks really have run. See [1] and [2]. (Myself, Christian König)

Changes since the RFCs:
- Include support for ForeignOwnable for ARef, so that a Fence can be stuffed into an XArray et al. (Code by Danilo)
- Implement ForeignOwnable (with new borrow type) for DriverFence, so that it can be stuffed into an XArray.
- Include the rcu::RcuBox data type to defer dropping data with RCU (Cody by Alice)
- Port DmaFence to RcuBox to make UAF bugs through later, new dma_fence callbacks (backend_ops) impossible.
- Force users to pass their fence data in an RcuBox (or have it not need drop()) through a Sealed trait.
- Document the rules for the user's DriverFence::data's drop implementation very clearly (deadlock danger).
- rustfmt, Clippy.
- Various style suggestions, safety comments, etc. (Önur)
- Add __rust_helper prefix to helper functions. (Önur)

Changes in RFC v3:
- Omit JobQueue patches for now
- Completely redesign the memory layout: Instead of a Fence refcounting a DriverFence, both now live in the same allocation to allow for future support the dma_fence backend_ops callbacks which need to do container_of. (mostly Boris's feedback)
- Allow for pre-allocating fences to avoid deadlocks when submitting jobs to a GPU. (Boris)
- Simultaneously, allow for pre-preparing fence callback objects, so the driver can allocate them when it sees fit. (code largely stolen and inspired by Daniel).
- Signal fences on drop, ensure synchronization.
- Force users to set an error code when signalling.
- Write more documentation
- A ton of minor other changes.

[1] https://lore.kernel.org/dri-devel/[email protected]/
[2] https://lore.kernel.org/dri-devel/[email protected]/

Alright, so since the last RFCs did not reveal significant design issues, I decided to transition this series to a v1 and hope that we can get it upstream.

This now includes code for more common infrastructure that dma_fence needs, contributed by Danilo and Alice.

---

Old cover letter for RFC:

So, this is the spiritual successor of the first / second RFC [1]. v2 also contained code for drm::JobQueue, but mostly to show how the fence code would be used. JobQueue is under heavy rework right now, so I don't want to bother your eyes with it. The docstring examples should show how Rust fences are supposed to be used, though.

This v3 contains a huge amount of highly valuable feedback from a variety of people, notably Boris, but also from Alice, Gary and Danilo.

There are some TODOs open (a better trait for fence backend_ops and RCU support), but my hope is that this effort is now finally approaching its end.

I would greatly appreciate feedback and especially more information about what might be missing to make this usable, which is obviously where Daniel's and Boris's feedback will be valuable once more.

Please regard this patch just as what it's titled: an RFC, to discuss a bit more and to inform a broader community about what the current state is and where this is heading at.

Many regards,
Philipp

[1] https://lore.kernel.org/rust-for-linux/[email protected]/

Danilo Krummrich (1):
rust: types: implement ForeignOwnable for ARef<T>

Philipp Stanner (5):
rust: sync: Add abstraction for rcu_barrier()
rust: sync: Add abstraction for synchronize_rcu()
rust: error: Add ECANCELED error code
rust: Add dma_fence abstractions
MAINTAINERS: Add entry for Rust dma-buf

 MAINTAINERS                      |   3 +
 rust/bindings/bindings_helper.h  |   1 +
 rust/helpers/dma_fence.c         |  48 ++
 rust/helpers/helpers.c           |   1 +
 rust/kernel/dma_buf/dma_fence.rs | 884 +++++++++++++++++++++++++++++++
 rust/kernel/dma_buf/mod.rs       |  14 +
 rust/kernel/error.rs             |   1 +
 rust/kernel/lib.rs               |   1 +
 rust/kernel/sync/aref.rs         |  39 ++
 rust/kernel/sync/rcu.rs          |  14 +
 10 files changed, 1006 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
Signature · 1 line
2.54.0
author maintainer
#1 lore raw

PATCH v2 1/6 rust: types: implement ForeignOwnable for ARef<T>

From: Danilo Krummrich <[email protected]>

Implement ForeignOwnable for ARef<T>, making it possible for C code to own an ARef<T>.

Since ARef represents shared ownership, BorrowedMut is &T rather than &mut T, matching the semantics of the underlying reference-counted type.

---
 rust/kernel/sync/aref.rs | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
rust/kernel/sync/aref.rs +39 -0
index 9989f56d0605..82907383c44b 100644
@@ -17,6 +17,10 @@
 //! [`Arc`]: crate::sync::Arc
 //! [`Arc<T>`]: crate::sync::Arc
 
+use crate::{
+    prelude::*,
+    types::ForeignOwnable, //
+};
 use core::{marker::PhantomData, mem::ManuallyDrop, ops::Deref, ptr::NonNull};
 
 /// Types that are _always_ reference counted.
@@ -183,6 +187,41 @@ fn eq(&self, other: &ARef<U>) -> bool {
 }
 impl<T: AlwaysRefCounted + Eq> Eq for ARef<T> {}
 
+// SAFETY: `into_foreign` returns a pointer from `NonNull::as_ptr`, so it's non-null. The
+// `ARef` invariant guarantees that `ptr` points to a valid `T`, so it's aligned to `T`.
+unsafe impl<T: AlwaysRefCounted + 'static> ForeignOwnable for ARef<T> {
+    const FOREIGN_ALIGN: usize = core::mem::align_of::<T>();
+
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a T;
+
+    fn into_foreign(self) -> *mut c_void {
+        ARef::into_raw(self).as_ptr().cast()
+    }
+
+    unsafe fn from_foreign(ptr: *mut c_void) -> Self {
+        // SAFETY: The safety requirements of this function ensure that `ptr` comes from a previous
+        // call to `Self::into_foreign`.
+        let ptr = unsafe { NonNull::new_unchecked(ptr.cast()) };
+
+        // SAFETY: `ptr` came from `into_foreign`, which consumed an `ARef` without decrementing
+        // the refcount, so we can transfer the ownership to the new `ARef`.
+        unsafe { ARef::from_raw(ptr) }
+    }
+
+    unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {
+        // SAFETY: The safety requirements of this method ensure that the object remains alive and
+        // immutable for the duration of 'a.
+        unsafe { &*ptr.cast() }
+    }
+
+    unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {
+        // SAFETY: The safety requirements for `borrow_mut` are a superset of the safety
+        // requirements for `borrow`.
+        unsafe { <Self as ForeignOwnable>::borrow(ptr) }
+    }
+}
+
 impl<T, U> PartialEq<&'_ U> for ARef<T>
 where
     T: AlwaysRefCounted + PartialEq<U>,
Signature · 1 line
2.54.0
#2 lore raw

Re: [PATCH v2 1/6] rust: types: implement ForeignOwnable for ARef<T>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Missing `#[inline]` attributes on small forwarding functions in abstraction crate. --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing #[inline] attributes on small forwarding functions in abstraction crate. --

commit a8487c7d01286e697fa7028c9540c62f1483807e
Author: Danilo Krummrich <[email protected]>

rust: types: implement ForeignOwnable for ARef<T>

Implement ForeignOwnable for ARef<T>, making it possible for C code to own an ARef<T>.

Quoted 4 lines
rust/kernel/sync/aref.rs +0 -0
index 9989f56d06052..82907383c44b5 100644

[ ... ]

Quoted 5 lines
rust/kernel/sync/aref.rs +1 -0
@@ -183,6 +187,41 @@ fn eq(&self, other: &ARef<U>) -> bool {
 }
 impl<T: AlwaysRefCounted + Eq> Eq for ARef<T> {}
 
+// SAFETY: `into_foreign` returns a pointer from `NonNull::as_ptr`, so it's non-null. The

rust/kernel/sync/aref.rs fn eq(&self, other: &ARef<U>) -> bool { · line 200

Quoted 10 lines
rust/kernel/sync/aref.rs +10 -0
+// `ARef` invariant guarantees that `ptr` points to a valid `T`, so it's aligned to `T`.
+unsafe impl<T: AlwaysRefCounted + 'static> ForeignOwnable for ARef<T> {
+    const FOREIGN_ALIGN: usize = core::mem::align_of::<T>();
+
+    type Borrowed<'a> = &'a T;
+    type BorrowedMut<'a> = &'a T;
+
+    fn into_foreign(self) -> *mut c_void {
+        ARef::into_raw(self).as_ptr().cast()
+    }

[Severity: Low]
This isn't a bug, but should into_foreign(), from_foreign(), borrow(), and borrow_mut() be annotated with #[inline]?

The Rust subsystem guidelines suggest that small or forwarding functions in kernel abstractions should use the #[inline] attribute.

Quoted 23 lines
rust/kernel/sync/aref.rs +23 -0
+
+    unsafe fn from_foreign(ptr: *mut c_void) -> Self {
+        // SAFETY: The safety requirements of this function ensure that `ptr` comes from a previous
+        // call to `Self::into_foreign`.
+        let ptr = unsafe { NonNull::new_unchecked(ptr.cast()) };
+
+        // SAFETY: `ptr` came from `into_foreign`, which consumed an `ARef` without decrementing
+        // the refcount, so we can transfer the ownership to the new `ARef`.
+        unsafe { ARef::from_raw(ptr) }
+    }
+
+    unsafe fn borrow<'a>(ptr: *mut c_void) -> &'a T {
+        // SAFETY: The safety requirements of this method ensure that the object remains alive and
+        // immutable for the duration of 'a.
+        unsafe { &*ptr.cast() }
+    }
+
+    unsafe fn borrow_mut<'a>(ptr: *mut c_void) -> &'a T {
+        // SAFETY: The safety requirements for `borrow_mut` are a superset of the safety
+        // requirements for `borrow`.
+        unsafe { <Self as ForeignOwnable>::borrow(ptr) }
+    }
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author maintainer
#3 lore raw

PATCH v2 2/6 rust: sync: Add abstraction for rcu_barrier()

In reply to Philipp Stanner

rcu_barrier() is a frequently used C function which is always safe to be called.

Add a safe abstraction for rcu_barrier().

---
 rust/kernel/sync/rcu.rs | 6 ++++++
 1 file changed, 6 insertions(+)
rust/kernel/sync/rcu.rs +6 -0
index a32bef6e490b..eef34bf86259 100644
@@ -50,3 +50,9 @@ fn drop(&mut self) {
 pub fn read_lock() -> Guard {
     Guard::new()
 }
+
+/// Wait for all pending call_rcu() callbacks, if there are any.
+pub fn rcu_barrier() {
+    // SAFETY: `rcu_barrier()` is always safe to be called. It just might wait for a grace period.
+    unsafe { bindings::rcu_barrier() };
+}
Signature · 1 line
2.54.0
#4 lore raw

Re: [PATCH v2 2/6] rust: sync: Add abstraction for rcu_barrier()

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] Missing `#[inline]` annotation on the `rcu_barrier` abstraction function. --

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing #[inline] annotation on the rcu_barrier abstraction function. --

commit 429e3c9d9e99f90b82501282309c58b5cf00107c
Author: Philipp Stanner <[email protected]>

rust: sync: Add abstraction for rcu_barrier()

This commit introduces a safe Rust abstraction for the C function rcu_barrier(), allowing Rust code to wait for all pending call_rcu() callbacks to complete.

Quoted 4 lines
rust/kernel/sync/rcu.rs +0 -0
index a32bef6e490b0..eef34bf862596 100644

[ ... ]

rust/kernel/sync/rcu.rs fn drop(&mut self) { · line 55

Quoted 2 lines
rust/kernel/sync/rcu.rs +2 -0
+/// Wait for all pending call_rcu() callbacks, if there are any.
+pub fn rcu_barrier() {

[Severity: Low]
This isn't a bug, but should this abstraction function be annotated with #[inline]? The Rust subsystem guidelines indicate that small functions or those that simply forward to a binding call should include the inline annotation.

Quoted 3 lines
rust/kernel/sync/rcu.rs +3 -0
+    // SAFETY: `rcu_barrier()` is always safe to be called. It just might wait for a grace period.
+    unsafe { bindings::rcu_barrier() };
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
author maintainer
#5 lore raw

PATCH v2 3/6 rust: sync: Add abstraction for synchronize_rcu()

In reply to Philipp Stanner

synchronize_rcu() is a frequently used C function which is always safe to be called.

Add a safe abstraction for synchronize_rcu().

---
 rust/kernel/sync/rcu.rs | 8 ++++++++
 1 file changed, 8 insertions(+)
rust/kernel/sync/rcu.rs +8 -0
index eef34bf86259..d86d615467b1 100644
@@ -56,3 +56,11 @@ pub fn rcu_barrier() {
     // SAFETY: `rcu_barrier()` is always safe to be called. It just might wait for a grace period.
     unsafe { bindings::rcu_barrier() };
 }
+
+/// Wait for one RCU grace period.
+///
+/// You typically do this to wait for everyone holding a [`Guard`].
+pub fn synchronize_rcu() {
+    // SAFETY: `synchronize_rcu()` is always safe to be called. It just waits for a grace period.
+    unsafe { bindings::synchronize_rcu() };
+}
Signature · 1 line
2.54.0
#6 lore raw

Re: [PATCH v2 3/6] rust: sync: Add abstraction for synchronize_rcu()

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] Missing `#[inline]` annotation on the `synchronize_rcu()` abstraction. - [Low] Existing uses of…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] Missing #[inline] annotation on the synchronize_rcu() abstraction.
- [Low] Existing uses of bindings::synchronize_rcu() were not updated to use the new safe abstraction. --

commit 3f3ebd383ebd5385e032e8ee41ba8e2dc2c9f4e9
Author: Philipp Stanner <[email protected]>

rust: sync: Add abstraction for synchronize_rcu()

This commit adds a safe Rust abstraction for the synchronize_rcu() function to wait for a grace period.

Quoted 4 lines
rust/kernel/sync/rcu.rs +0 -0
index eef34bf862596..d86d615467b1e 100644

[ ... ]

rust/kernel/sync/rcu.rs pub fn rcu_barrier() { · line 63

Quoted 9 lines
rust/kernel/sync/rcu.rs +5 -0
@@ -56,3 +56,11 @@ pub fn rcu_barrier() {
     // SAFETY: `rcu_barrier()` is always safe to be called. It just might wait for a grace period.
     unsafe { bindings::rcu_barrier() };
 }
+
+/// Wait for one RCU grace period.
+///
+/// You typically do this to wait for everyone holding a [`Guard`].
+pub fn synchronize_rcu() {

[Severity: Low]
This isn't a bug, but should this function have an #[inline] annotation?

According to the Rust subsystem guidelines, functions in abstractions that are small or simply forward to a binding call should be annotated with #[inline].

Additionally, should the existing usages of the raw C binding be updated to use this new safe abstraction?

For example, drop() in rust/kernel/sync/poll.rs currently does:

    unsafe { bindings::synchronize_rcu() };

And revoke_internal() in rust/kernel/revocable.rs:

    unsafe { bindings::synchronize_rcu() };

Updating these existing callers would help reduce unnecessary unsafe blocks across the codebase now that a safe wrapper is available.

Quoted 3 lines
rust/kernel/sync/rcu.rs +3 -0
+    // SAFETY: `synchronize_rcu()` is always safe to be called. It just waits for a grace period.
+    unsafe { bindings::synchronize_rcu() };
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
author maintainer
#7 lore raw

PATCH v2 4/6 rust: error: Add ECANCELED error code

In reply to Philipp Stanner

The Rust dma_fence abstractions need the ECANCELED error code.

Add ECANCELED error code.

---
 rust/kernel/error.rs | 1 +
 1 file changed, 1 insertion(+)
rust/kernel/error.rs +1 -0
index 05cf869ac090..dda15db7cddf 100644
@@ -88,6 +88,7 @@ macro_rules! declare_err {
     declare_err!(EIOCBQUEUED, "iocb queued, will get completion event.");
     declare_err!(ERECALLCONFLICT, "Conflict with recalled state.");
     declare_err!(ENOGRACE, "NFS file lock reclaim refused.");
+    declare_err!(ECANCELED, "Request has been canceled.");
 }
 
 /// Generic integer kernel error.
Signature · 1 line
2.54.0
author maintainer
#8 lore raw

PATCH v2 5/6 rust: Add dma_fence abstractions

In reply to Philipp Stanner

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.

---
 rust/bindings/bindings_helper.h  |   1 +
 rust/helpers/dma_fence.c         |  48 ++
 rust/helpers/helpers.c           |   1 +
 rust/kernel/dma_buf/dma_fence.rs | 884 +++++++++++++++++++++++++++++++
 rust/kernel/dma_buf/mod.rs       |  14 +
 rust/kernel/lib.rs               |   1 +
 6 files changed, 949 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
rust/bindings/bindings_helper.h +1 -0
index 446dbeaf0866..814d7740e686 100644
@@ -52,6 +52,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>
rust/helpers/dma_fence.c new file +48 -0

48 lines not on this page · raw message

rust/helpers/helpers.c +1 -0
index 625921e27dfb..d9114d0b3c8f 100644
@@ -57,6 +57,7 @@
 #include "cred.c"
 #include "device.c"
 #include "dma.c"
+#include "dma_fence.c"
 #include "dma-resv.c"
 #include "drm.c"
 #include "err.c"
rust/kernel/dma_buf/dma_fence.rs new file +884 -0

884 lines not on this page · raw message

rust/kernel/dma_buf/mod.rs new file +14 -0
new file mode 100644
index 000000000000..fb353ce042ce
@@ -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, //
+};
rust/kernel/lib.rs +1 -0
index b72b2fbe046d..a05ccaa7598c 100644
@@ -63,6 +63,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;
Signature · 1 line
2.54.0
#9 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [Critical] Incorrect struct layout in `DriverFenceData` places `rcu_head` before `inner: Fence`, causing…

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] Incorrect struct layout in DriverFenceData places rcu_head before inner: Fence, causing misaligned pointer casts and SLUB corruption when the C backend frees the interior pointer.
- [Critical] DriverFence::drop prematurely destroys FenceCtx and user data before the dma_fence reference count reaches 0, causing Use-After-Free.
- [High] FenceCtx<T> and DriverFence<T> lack a 'static bound on T, allowing safe Rust code to erase lifetimes and cause Use-After-Free.
- [High] FenceCbRegistration::new leaks an ARef<Fence> reference count on the error path.
- [High] Dropping the last reference to FenceCtx from the RCU callback will execute the user's T (context data) Drop implementation in softirq context.
--

commit 95eb8ea421a40fbf4b3b019ea0a7b2c3928e1e11
Author: Philipp Stanner <[email protected]>
rust: Add dma_fence abstractions

This commit introduces Rust abstractions for the C dma_fence framework. It provides types such as DriverFence, Fence, and FenceCtx to cleanly decouple fence issuers and consumers while interacting with the C backend.

Quoted 6 lines
rust/kernel/dma_buf/dma_fence.rs new file +0 -0
new file mode 100644
index 0000000000000..3af2dee58e13f
@@ -0,0 +1,884 @@

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 64

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+/// 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]
+pub struct FenceCtx<T: Send + Sync> {

[Severity: High]
Does T need a static bound here?

Fence contains an Opaque<bindings::dma_fence> without lifetime parameters, making it implicitly static. DriverFence::as_fence allows extracting an owned ARef<Fence>. Because it is static, safe Rust code could send this ARef to threads that outlive T.

If T contains non-static references, the ARef<Fence> will keep DriverFenceData alive beyond T's lifetime. Could this allow lifetime erasure and a subsequent use-after-free?

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+    /// The fence context number.
+    nr: u64,
+    /// The sequence number for the next fence created.
+    seqno: AtomicU64,

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 91

Quoted 13 lines
rust/kernel/dma_buf/dma_fence.rs new file +13 -0
+#[allow(unused_unsafe)]
+impl<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,
+    };

[Severity: Critical]
Will the C backend crash when trying to free this fence?

By leaving release as None, the dma_fence backend defaults to using dma_fence_free(). As defined later in DriverFenceData, the dma_fence is embedded at a non-zero offset. dma_fence_free() will call kfree_rcu() on this interior pointer. Does this trigger a bad pointer panic in the SLUB allocator?

[ ... ]

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+    /// Create a [`FenceCtx`] from an associated [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///

rust/kernel/dma_buf/dma_fence.rs line 210

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+    /// `ptr` must be a valid pointer to a dma_fence which resides within a [`Fence`],
+    /// which in turn resides in a [`DriverFenceData`].
+    unsafe fn from_raw_fence<'a>(ptr: *mut bindings::dma_fence) -> &'a Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<T>;

[Severity: Critical]
Can this direct cast lead to misaligned structure access?

The comment states Fence is the first member, but rcu_head is actually defined before it in DriverFenceData. Casting the pointer directly to DriverFenceData without accounting for the 16-byte offset of the inner field could cause memory corruption when fctx is accessed later.

[ ... ]

Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +4 -0
+    pub fn new<'a>(fence: &'a Fence, callback: T) -> impl PinInit<Self, CallbackError<T>> + 'a
+    where
+        T: 'a,
+    {

[ ... ]

Quoted 23 lines
rust/kernel/dma_buf/dma_fence.rs new file +23 -0
+        unsafe {
+            pin_init_from_closure(move |slot: *mut Self| {
+                let slot_callback = &raw mut (*slot).callback;
+                let slot_fence = &raw mut (*slot).fence;
+                let slot_cb = &raw mut (*slot).cb;
+
+                // Write callback and fence first — must be visible before
+                // dma_fence_add_callback makes the registration live.
+                core::ptr::write(slot_callback, callback);
+                core::ptr::write(slot_fence, ARef::from(fence));
+
+                let ret = to_result(bindings::dma_fence_add_callback(
+                    fence.inner.get(),
+                    Opaque::cast_into(slot_cb),
+                    Some(Self::dma_fence_callback),
+                ));
+
+                match ret {
+                    Ok(()) => Ok(()),
+                    Err(e) => {
+                        // Read back what we wrote to leave the slot clean.
+                        let cb_back = core::ptr::read(slot_callback);
+

rust/kernel/dma_buf/dma_fence.rs line 361

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+                        if e.to_errno() == ENOENT.to_errno() {
+                            Err(CallbackError::AlreadySignaled(cb_back))
+                        } else {
+                            Err(CallbackError::Other(e))
+                        }
+                    }
+                }
+            })
+        }
+    }

[Severity: High]
Does this error path leak the ARef reference count?

Earlier in this closure, ARef::from(fence) is written into slot_fence, which increments the reference count. If dma_fence_add_callback() fails, the error path reads back slot_callback to return it, but slot_fence is neither read back nor manually dropped.

Since pin_init_from_closure assumes the slot is uninitialized on error and won't run drop on it, will this permanently leak the ARef<Fence> and its memory allocation?

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 521

Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +8 -0
+#[repr(C)] // Necessary to guarantee that `inner` always comes first so that we can cast.
+#[pin_data]
+struct DriverFenceData<T: Send + Sync + FenceCtxOps> {
+    /// Callback head for dropping this in a deferred manner through RCU.
+    rcu_head: bindings::callback_head,
+    #[pin]
+    /// The inner fence.
+    inner: Fence,

[Severity: Critical]
Is the inner Fence guaranteed to be at offset 0 here?

The struct is marked repr(C), but rcu_head precedes inner. This shifts the embedded Fence by the size of the callback_head, directly conflicting with the assumption in from_raw_fence that Fence is the first member.

[ ... ]

rust/kernel/dma_buf/dma_fence.rs line 598

Quoted 6 lines
rust/kernel/dma_buf/dma_fence.rs new file +6 -0
+pub struct DriverFence<T: Send + Sync + FenceCtxOps> {
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<T>>,
+}

[Severity: High]
Similar to FenceCtx, does DriverFence also require a static bound on T to prevent safe code from erasing lifetimes and causing a use-after-free?

[ ... ]

Quoted 13 lines
rust/kernel/dma_buf/dma_fence.rs new file +13 -0
+/// Finally really drop this `DriverFence<T>`
+///
+/// # Safety
+///
+/// `head` references the `rcu_head` field of an `DriverFenceData<T>`. All
+/// accessors to that `DriverFenceData<T>` must be gone by now. This must be
+/// ensured by signalling the associated `DriverFence<T>` and then waiting
+/// for a grace period until calling this function here.
+unsafe extern "C" fn drop_driver_fence_data<T: Send + Sync + FenceCtxOps>(
+    head: *mut bindings::callback_head,
+) {
+    // SAFETY: Caller provides a pointer to the `rcu_head` field of a `DriverFenceData<C>`.
+    let fence_data = unsafe { container_of!(head, DriverFenceData<T>, rcu_head) };

rust/kernel/dma_buf/dma_fence.rs line 871

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+
+    // SAFETY: `fence_data` was created validly above. All the fence's data will
+    // only drop below, but the raw pointer to the raw C `dma_fence` remains
+    // valid because the reference count is only decremented at the end of the
+    // function.
+    let fence = unsafe { (*fence_data).inner.inner.get() };
+
+    // SAFETY: A grace period has passed. All accessors to that `DriverFenceData`
+    // are gone now. We have exclusive access and can now drop it.
+    unsafe { drop_in_place(fence_data) };

[Severity: High]
Could this drop the user's data T in softirq context?

Since drop_driver_fence_data runs as an RCU callback, it executes in atomic (softirq) context. If this drop_in_place() puts the last reference to the Arc<FenceCtx<T>>, it will trigger the deallocation of the context and drop its inner data T.

There appear to be no trait bounds or documentation enforcing that T must only perform operations legal in atomic context. If a user provides context data whose Drop implementation sleeps (like acquiring a mutex or freeing large structures), would this trigger a sleeping in atomic context kernel panic?

[Severity: Critical]
Does this prematurely destroy the user data and FenceCtx before the dma_fence reference count actually reaches zero?

The dma_fence refcount is shared with ARef<Fence>. If an ARef<Fence> is still alive when DriverFence drops, this RCU callback will execute drop_in_place(fence_data). This destroys the Arc<FenceCtx> and the user data. However, the C dma_fence is still alive with a non-zero refcount.

When the refcount eventually hits zero later on, the C backend could trigger callbacks like get_driver_name() via trace_dma_fence_destroy(). Could those callbacks access the already-freed fence_data.fctx, leading to a use-after-free?

Should the Rust data instead be dropped in a custom release callback in dma_fence_ops when the refcount actually reaches 0?

Quoted 13 lines
rust/kernel/dma_buf/dma_fence.rs new file +13 -0
+
+    // The `drop_in_place()` above might drop the `FenceCtx` by putting the last
+    // reference.
+
+    // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is
+    // valid and initialized. It is valid until the refcount drops
+    // to 0, which can earliest happen once we drop the `DriverFence`'s reference
+    // here.
+    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.
+}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
reviewer
#10 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 110 lines · On Tue, 16 Jun 2026 10:28:17 +0200 Philipp Stanner <[email protected]> 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.

---
 rust/bindings/bindings_helper.h  |   1 +
 rust/helpers/dma_fence.c         |  48 ++
 rust/helpers/helpers.c           |   1 +
 rust/kernel/dma_buf/dma_fence.rs | 884 +++++++++++++++++++++++++++++++
 rust/kernel/dma_buf/mod.rs       |  14 +
 rust/kernel/lib.rs               |   1 +
 6 files changed, 949 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
rust/bindings/bindings_helper.h +1 -0
index 446dbeaf0866..814d7740e686 100644
@@ -52,6 +52,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>
rust/helpers/dma_fence.c new file +13 -0
new file mode 100644
index 000000000000..0e08411098fa
@@ -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);
+}

[...]

Quoted 5 lines
rust/helpers/dma_fence.c new file +5 -0
+
+    /// Create a [`FenceCtx`] from an associated [`bindings::dma_fence`].
+    ///
+    /// # Safety
+    ///

rust/helpers/dma_fence.c

Quoted 10 lines
rust/helpers/dma_fence.c new file +10 -0
+    /// `ptr` must be a valid pointer to a dma_fence which resides within a [`Fence`],
+    /// which in turn resides in a [`DriverFenceData`].
+    unsafe fn from_raw_fence<'a>(ptr: *mut bindings::dma_fence) -> &'a Self {
+        let opaque_fence = Opaque::cast_from(ptr);
+
+        // SAFETY: Safe due to the function's overall safety requirements.
+        let fence_ptr = unsafe { container_of!(opaque_fence, Fence, inner) };
+
+        // DriverFenceData is repr(C) and a Fence is its first member.
+        let fence_data_ptr = fence_ptr as *mut DriverFenceData<T>;

Either the field ordering on the type or this code is wrong because the first member of DriverFenceData is rcu_head.

Quoted 17 lines
rust/helpers/dma_fence.c new file +17 -0
+
+        // SAFETY: Safe because of the safety comment directly above.
+        let fence_data = unsafe { &*fence_data_ptr };
+
+        &fence_data.fctx
+    }
+}
+
+/// Error type for fence callback registration.
+///
+/// Generic over `T` so that `AlreadySignaled` can return the callback to the
+/// caller, allowing it to reclaim any resources owned by the callback (e.g.,
+/// a fence handle that needs to be signaled).
+#[derive(Debug)]
+pub enum CallbackError<T = ()> {
+    /// The fence was already signaled. The callback is returned so the caller
+    /// can extract owned resources without losing them.

[...]

Quoted 22 lines
rust/helpers/dma_fence.c new file +22 -0
+        //
+        // Without this, Drop can race with a concurrent signal:
+        //   CPU0 (signal, lock held): take() -> signaled(fence_ref) (in progress)
+        //   CPU1 (drop): sees is_some()==false -> skips lock -> frees struct
+        //   CPU0: accesses fence_ref -> use-after-free
+        //
+        // When the callback has already fired, the signal path detached the
+        // list node via INIT_LIST_HEAD, so dma_fence_remove_callback just sees
+        // an empty node and returns false — the lock acquisition is the only
+        // thing that matters.
+        //
+        // SAFETY: The fence pointer is valid and the cb was initialized by
+        // dma_fence_add_callback during construction.
+        unsafe {
+            bindings::dma_fence_remove_callback(self.fence.as_raw(), self.cb.get());
+        }
+    }
+}
+
+// SAFETY: FenceCbRegistration can be sent between threads
+unsafe impl<T: FenceCb> Send for FenceCbRegistration<T> {}
+

rust/helpers/dma_fence.c

Quoted 10 lines
rust/helpers/dma_fence.c new file +10 -0
+// SAFETY: &FenceCbRegistration can be shared between threads if &T can.
+unsafe impl<T: FenceCb> Sync for FenceCbRegistration<T> where T: Sync {}
+
+/// The receiving counterpart of a [`DriverFence`], designed to register callbacks
+/// on, check the signalled state etc. A [`Fence`] cannot be signalled.
+/// A [`Fence`] is always refcounted.
+pub struct Fence {
+    /// The actual dma_fence passed to C.
+    inner: Opaque<bindings::dma_fence>,
+}

I am unsure whether it is safe to cast the pointer in Fence::from_raw without Fence being #[repr(transparent)] as the layout compatibility is not guaranteed explicitly.

Regards,
Onur

Quoted 25 lines
rust/helpers/dma_fence.c new file +25 -0
+
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Send for Fence {}
+// SAFETY: Fences are literally designed to be shared between threads.
+unsafe impl Sync for Fence {}
+
+impl Fence {
+    /// Check whether the fence was signalled at the moment of the function call.
+    ///
+    /// Note that this can return `true` for a [`Fence`] whose [`DriverFence`]
+    /// has not yet been dropped. The reason is that the fence ops callbacks can
+    /// cause the fence to get signaled by the C backend.
+    pub fn is_signaled(&self) -> bool {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // We shouuld not use `dma_fence_is_signaled_locked()` here, because
+        // according to the C backend's recommendations, that function is problematic
+        // and we should avoid calling that function with a lock held.
+
+        // SAFETY: `self` is valid by definition. We take the spinlock above.
+        let ret = unsafe { bindings::dma_fence_is_signaled(fence) };
+
+        // To guarantee that an API caller can 100% rely on the signalling being

[...]

Quoted 40 lines
rust/helpers/dma_fence.c new file +5 -0
+    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.
+}
rust/kernel/dma_buf/mod.rs new file +14 -0
new file mode 100644
index 000000000000..fb353ce042ce
@@ -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, //
+};
rust/kernel/lib.rs +1 -0
index b72b2fbe046d..a05ccaa7598c 100644
@@ -63,6 +63,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;
Signature · 1 line
2.54.0
#11 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

Quoted 7 lines · Onur Özkan

On Tue, 16 Jun 2026 10:28:17 +0200

Quoted 3 lines

+
+        // DriverFenceData is repr(C) and a Fence is its first member. +        let fence_data_ptr = fence_ptr as *mut DriverFenceData<T>;

Either the field ordering on the type or this code is wrong because the first member of DriverFenceData is rcu_head.

Yeah, that's a bug introduced in this revision. Thx for pointing it out. Already fixed it downstream.

I'm just wondering why my test did not blow up.

Quoted 6 lines
Quoted 2 lines

+}

I am unsure whether it is safe to cast the pointer in Fence::from_raw without Fence being #[repr(transparent)] as the layout compatibility is not guaranteed explicitly.

Shouldn't #[repr(C)] do that trick, too? Do you need both?

Who can answer that?

P.

reviewer
#12 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

Quoted 21 lines · On Tue, 16 Jun 2026 16:38:51 +0200 Philipp Stanner <[email protected]> wrote:
Quoted 7 lines · Onur Özkan

On Tue, 16 Jun 2026 10:28:17 +0200

Quoted 3 lines

+
+        // DriverFenceData is repr(C) and a Fence is its first member. +        let fence_data_ptr = fence_ptr as *mut DriverFenceData<T>;

Either the field ordering on the type or this code is wrong because the first member of DriverFenceData is rcu_head.

Yeah, that's a bug introduced in this revision. Thx for pointing it out. Already fixed it downstream.

I'm just wondering why my test did not blow up.

Quoted 6 lines
Quoted 2 lines

+}

I am unsure whether it is safe to cast the pointer in Fence::from_raw without Fence being #[repr(transparent)] as the layout compatibility is not guaranteed explicitly.

Quoted 6 lines

Shouldn't #[repr(C)] do that trick, too? Do you need both?

Who can answer that?

P.

You shouldn't use both and I think it wouldn't compile anyway. repr(C) only makes sense when the type have multiple fields.

Thanks,
Onur

maintainer
#13 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

Quoted 1 line · Onur Özkan

repr(C) only makes sense when the type have multiple fields.

I don't think that's universally true. repr(transparent) guarantees the layout and ABI match the inner field. The wrapper is passed through calls exactly as the field would be. repr(C) guarantees C-compatible layout and makes the type follow the platform's C ABI for passing an aggregate, which isn't always how the bare field is passed.

reviewer
#14 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

Quoted 8 lines · On Tue, 16 Jun 2026 17:28:04 +0200 Danilo Krummrich <[email protected]> wrote:
Quoted 1 line · Onur Özkan

repr(C) only makes sense when the type have multiple fields.

I don't think that's universally true. repr(transparent) guarantees the layout and ABI match the inner field. The wrapper is passed through calls exactly as the field would be. repr(C) guarantees C-compatible layout and makes the type follow the platform's C ABI for passing an aggregate, which isn't always how the bare field is passed.

I was too focused on the field ordering perspective during the review, sorry for that.

Onur

maintainer
#15 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 17 lines · Philipp Stanner
Quoted 7 lines · Onur Özkan

On Tue, 16 Jun 2026 10:28:17 +0200

Quoted 3 lines

+
+        // DriverFenceData is repr(C) and a Fence is its first member. +        let fence_data_ptr = fence_ptr as *mut DriverFenceData<T>;

Either the field ordering on the type or this code is wrong because the first member of DriverFenceData is rcu_head.

Yeah, that's a bug introduced in this revision. Thx for pointing it out. Already fixed it downstream.

I'm just wondering why my test did not blow up.

Quoted 3 lines
Quoted 2 lines

+}

Quoted 7 lines
Quoted 3 lines

I am unsure whether it is safe to cast the pointer in Fence::from_raw without Fence being #[repr(transparent)] as the layout compatibility is not guaranteed explicitly.

Shouldn't #[repr(C)] do that trick, too? Do you need both?

Who can answer that?

You have #[repr(C)] for DriverFence, but not on Fence. Fence::from_raw does require layout equivalence between Fence and bindings::dma_fence and this is done by adding #[repr(transparent)] on Fence.

Best,
Gary

#16 lore raw

Re: [PATCH v2 5/6] rust: Add dma_fence abstractions

In reply to Philipp Stanner
Quoted 2 lines · Philipp Stanner

[…]

Quoted 3 lines

Add abstractions for dma_fence in Rust.

RFC for one thing I'm worried about below

Quoted 14 lines

---
 rust/bindings/bindings_helper.h  |   1 + rust/helpers/dma_fence.c         |  48 ++ rust/helpers/helpers.c           |   1 + rust/kernel/dma_buf/dma_fence.rs | 884 +++++++++++++++++++++++++++++++ rust/kernel/dma_buf/mod.rs       |  14 +
 rust/kernel/lib.rs               |   1 +

 6 files changed, 949 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

[…]

Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +9 -0
+
+/// The receiving counterpart of a [`DriverFence`], designed to register callbacks
+/// on, check the signalled state etc. A [`Fence`] cannot be signalled.
+/// A [`Fence`] is always refcounted.
+pub struct Fence {
+    /// The actual dma_fence passed to C.
+    inner: Opaque<bindings::dma_fence>,
+}
+

[…]

Quoted 21 lines
rust/kernel/dma_buf/dma_fence.rs new file +18 -0
+
+#[repr(C)] // Necessary to guarantee that `inner` always comes first so that we can cast.
+#[pin_data]
+struct DriverFenceData<T: Send + Sync + FenceCtxOps> {
+    /// Callback head for dropping this in a deferred manner through RCU.
+    rcu_head: bindings::callback_head,
+    #[pin]
+    /// The inner fence.
+    inner: Fence,
+    /// Pointer to access the FenceCtx. Useful for obtaining name parameters.
+    fctx: Arc<FenceCtx<T>>,
+    /// The API user's data. This must either not need drop, or must delay its
+    /// drop by a grace period. It is essential that the data only performs
+    /// operations legal in atomic context in its [`Drop`] implementation.
+    #[pin]
+    data: T::FenceDataType,
+}
+

[…]

Quoted 9 lines
rust/kernel/dma_buf/dma_fence.rs new file +8 -0
+pub struct DriverFence<T: Send + Sync + FenceCtxOps> {
+    /// The actual content of the fence. Lives in a raw pointer so that its
+    /// memory can be managed independently. Valid until both the [`DriverFence`]
+    /// and all associated [`Fence`]s have disappeared.
+    data: NonNull<DriverFenceData<T>>,
+}
+
+

[…]

Quoted 70 lines

}

rust/kernel/dma_buf/dma_fence.rs new file +69 -0
+
+impl<T: Send + Sync + FenceCtxOps> Drop for DriverFence<T> {
+    fn drop(&mut self) {
+        let fence = self.as_raw();
+        let mut fence_flags: usize = 0;
+        let flag_ptr = &raw mut fence_flags;
+
+        // SAFETY: `fence` is valid until the `call_rcu()` below. `flag_ptr` is
+        // merely a pointer to an integer, which lives as long as this function.
+        unsafe { bindings::dma_fence_lock_irqsave(fence, flag_ptr) };
+
+        // Use dma_fence_test_signaled_flag() instead of dma_fence_is_signaled_locked()
+        // because the C backend wants to get rid of the latter.
+
+        // SAFETY: `fence` is valid until the `call_rcu()` below.
+        let signaled: bool = unsafe { bindings::dma_fence_test_signaled_flag(fence) };
+        if warn_on!(!signaled) {
+            // SAFETY: `fence` is valid until the `call_rcu()` below. The fence
+            // must not have been signaled yet, which we check directly above.
+            unsafe { bindings::dma_fence_set_error(fence, ECANCELED.to_errno()) };
+            // SAFETY: `fence` is valid until the `call_rcu()` below. The lock must
+            // be held, which we acquired above.
+            unsafe { bindings::dma_fence_signal_locked(fence) };
+        }
+        // SAFETY: `fence` is valid until the `call_rcu()` below. `flag_ptr` is
+        // merely a pointer to an integer, which lives as long as this function.
+        // The lock must be held, which we acquired above.
+        unsafe { bindings::dma_fence_unlock_irqrestore(fence, flag_ptr) };
+
+        // SAFETY: Valid because `self` is valid.
+        let rcu_head_ptr = unsafe { &raw mut (*self.data.as_ptr()).rcu_head };
+
+        // `DriverFenceData` but could be accessed through some dma_fence callbacks
+        // right now. Access is being revoked in principle above by signalling
+        // the fence, but since the C backend does not guarantee perfect full
+        // synchronization, we have to wait for one grace period to ensure that
+        // all accessors of `DriverFenceData` (through the dma_fence_ops accessible
+        // through a `Fence`) are gone.
+
+        // SAFETY: `call_rcu()` is always safe to be called. `rcu_head_ptr` was created
+        // validly above. The module must perform a `synchronize_rcu()` or
+        // `rcu_barrier()` call to guard against module unload.
+        unsafe { bindings::call_rcu(rcu_head_ptr, Some(drop_driver_fence_data::<T>)) };
+    }
+}
+
+// TODO:
+// The entire call_rcu() mechanism in the drop above and the code below would be
+// unnecessary if C's dma_fence_signal() could be reworked in a way that after it
+// ran, the caller knows that no fence_ops callbacks can be running anymore.
+// In other words, if the dma_fence backend would use its spinlock for full
+// synchronization.
+//
+// Then we could move the drop_in_place() and dma_fence_put() upwards into the
+// drop() implementation and call it a day.
+
+/// Finally really drop this `DriverFence<T>`
+///
+/// # Safety
+///
+/// `head` references the `rcu_head` field of an `DriverFenceData<T>`. All
+/// accessors to that `DriverFenceData<T>` must be gone by now. This must be
+/// ensured by signalling the associated `DriverFence<T>` and then waiting
+/// for a grace period until calling this function here.
+unsafe extern "C" fn drop_driver_fence_data<T: Send + Sync + FenceCtxOps>(
+    head: *mut bindings::callback_head,
+) {
+    // SAFETY: Caller provides a pointer to the `rcu_head` field of a `DriverFenceData<C>`.
+    let fence_data = unsafe { container_of!(head, DriverFenceData<T>, rcu_head) };

rust/kernel/dma_buf/dma_fence.rs line 871

Quoted 10 lines
rust/kernel/dma_buf/dma_fence.rs new file +10 -0
+
+    // SAFETY: `fence_data` was created validly above. All the fence's data will
+    // only drop below, but the raw pointer to the raw C `dma_fence` remains
+    // valid because the reference count is only decremented at the end of the
+    // function.
+    let fence = unsafe { (*fence_data).inner.inner.get() };
+
+    // SAFETY: A grace period has passed. All accessors to that `DriverFenceData`
+    // are gone now. We have exclusive access and can now drop it.
+    unsafe { drop_in_place(fence_data) };

Here we would drop DriverFenceData. We do it like that (RCU delay) now because of two-fold reasoning:

a) dropping too soon could deallocate the FenceCtx, causing UAF.

b) drop_in_place() invalidates the fctx pointer, so the Rust compiler could hypothetically write some random data for CPU pipelining etc. reasons into the pointer, despite it still being used from the backend functions.

So what I'm wondering about is whether we also solved that problem for DriverFenceData.inner.inner, i.e., the C dma_fence which resides in that memory, which can be in use by an arbitrary number of refcounting Fence's

Does Opaque guarantee that the backing memory within DriverFenceData will never be interfered with in an undefined behavior way? The documentation does not explicitly seem to state that.

P.

author maintainer
#17 lore raw

PATCH v2 6/6 MAINTAINERS: Add entry for Rust dma-buf

In reply to Philipp Stanner

Rust does now have abstractions for dma_fence. These abstractions are quite complicated and require expertise with both the C and the Rust side. Therefore, using the existing entry also for maintenance of the Rust code appears reasonable.

Philipp volunteers to help maintain the dma_fence abstractions. Add a corresponding MAINTAINERS entry.

---
 MAINTAINERS | 3 +++
 1 file changed, 3 insertions(+)
MAINTAINERS +3 -0
index 2e8d160babc2..5d4289099771 100644
@@ -7521,6 +7521,7 @@ F:	fs/dlm/
 DMA BUFFER SHARING FRAMEWORK
 M:	Sumit Semwal <[email protected]>
 M:	Christian König <[email protected]>
+M:	Philipp Stanner <[email protected]>
 L:	[email protected]
 L:	[email protected]
 L:	[email protected] (moderated for non-subscribers)
@@ -7534,6 +7535,8 @@ F:	include/linux/dma-buf.h
 F:	include/linux/dma-buf/
 F:	include/linux/dma-resv.h
 F:	rust/helpers/dma-resv.c
+F:	rust/helpers/dma_fence.c
+F:	rust/kernel/dma_buf/
 K:	\bdma_(?:buf|fence|resv)\b
 
 DMA GENERIC OFFLOAD ENGINE SUBSYSTEM
Signature · 1 line
2.54.0
maintainer
#18 lore raw
In reply to Philipp Stanner
Quoted 1 line · Philipp Stanner

Changes since v1:

It seems that some of the comments from v1 [1][2] are not addressed, did you forget them?

In particular the pin_init_from_closure() looks unnecessary and can probably replaced with something like (untested):

	let mut callback = Some(callback);
	
	try_pin_init!(Self {
	    cb <- Opaque::try_ffi_init(|ptr| {
	        // SAFETY: `fence.inner.get()` is a valid, initialized `struct dma_fence`. `ptr`
	        // points to the `struct dma_fence_cb` field within the pinned allocation, so it
	        // remains valid until `dma_fence_remove_callback()` in `PinnedDrop` or until the
	        // callback fires.
	        let ret = unsafe {
	            to_result(bindings::dma_fence_add_callback(
	                fence.inner.get(),
	                ptr,
	                Some(Self::dma_fence_callback),
	            ))
	        };
	        match ret {
	            Ok(()) => Ok(()),
	            Err(e) => {
	                let cb_back = callback.take().expect("success path did not run");
	                if e == ENOENT {
	                    Err(CallbackError::AlreadySignaled(cb_back))
	                } else {
	                    Err(CallbackError::Other(e))
	                }
	            }
	        }
	    }),
	    callback: callback.take().expect("error path did not run"),
	    fence: ARef::from(fence),
	}? CallbackError<T>)

Please also see my previous comments regarding DriverFenceBorrow, it still seems to miss the lifetime and it should probably use ManuallyDrop<DriverFence>.

It also seems the patch series was created without --base, what's the base revision?

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/

#19 lore raw
Quoted 5 lines · Danilo Krummrich
Quoted 1 line · Philipp Stanner

Changes since v1:

It seems that some of the comments from v1 [1][2] are not addressed, did you forget them?

yeah, a bunch of stuff might have slipped me. Mea culpa. Need to address some more issues in v3 anyways; but wanted to present the rough RCU idea here as a heads-up.

Quoted 35 lines

In particular the pin_init_from_closure() looks unnecessary and can probably replaced with something like (untested):

	let mut callback = Some(callback);
	
	try_pin_init!(Self {
	    cb <- Opaque::try_ffi_init(|ptr| {
	        // SAFETY: `fence.inner.get()` is a valid, initialized `struct dma_fence`. `ptr`
	        // points to the `struct dma_fence_cb` field within the pinned allocation, so it
	        // remains valid until `dma_fence_remove_callback()` in `PinnedDrop` or until the
	        // callback fires.
	        let ret = unsafe {
	            to_result(bindings::dma_fence_add_callback(
	                fence.inner.get(),
	                ptr,
	                Some(Self::dma_fence_callback),
	            ))
	        };
	        match ret {
	            Ok(()) => Ok(()),
	            Err(e) => {
	                let cb_back = callback.take().expect("success path did not run");
	                if e == ENOENT {
	                    Err(CallbackError::AlreadySignaled(cb_back))
	                } else {
	                    Err(CallbackError::Other(e))
	                }
	            }
	        }
	    }),
	    callback: callback.take().expect("error path did not run"),
	    fence: ARef::from(fence),
	}? CallbackError<T>)
Quoted 2 lines

Please also see my previous comments regarding DriverFenceBorrow, it still seems to miss the lifetime and it should probably use ManuallyDrop<DriverFence>.

ACK.

Quoted 3 lines

It also seems the patch series was created without --base, what's the base revision?

Branched out from drm-misc-next for development purposes (since it heavily relies on dma-fence.c)

I can rebase on drm-rust-next for the next revision.

Quoted 3 lines

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/