rust / dma_buf: Add abstractions for dma_fence
Subsystems (7)
Philipp Stanner
maintainerv6 cover rust / dma_buf: Add abstractions for dma_fence
Changes since v5:
- Because Rust can "forget" DriverFences, it could bypass the lifetime ensuring that DriverFences don't outlive a FenceContext. Mitigate this by having the FenceContext keep track of unsignaled fences. (Danilo)
- To support the point above, add support for memory reservations to XArray.
- To support the point above, have FenceContext::alloc_fence() increment the sequence number. This is necessary for the xarray index. It might become critical with fence seqno holes.. please review.
- Remove acronyms in declarations. (Daniel)
- Allow for providing initial seqno in FenceContext::new(). (Daniel)
- Flesh out documentation about DriverFence <-> Fence. (Daniel)
- Flesh out documentation about weirdness in C backend. (Daniel)
- Add new, purely internal locking helper / guard for dma_fence.
- Use the kernel atomics (Gary).
- Curse angrily at my computer.
Changes since v4:
- Fix an uninitialized memory bug for FenceCbRegistration with ManuallyDrop.
- Return FenceCtx as impl PinInit
- Make FenceCtx return an impl PinInit<T, Error> (Danilo)
- Reformat some comments
Changes since v3:
- Add a FIXME for an encountered Rust compiler bug. (Gary)
- Add new Rust files also to DRM drivers & common infrastructure MAINTAINERS file. (Danilo)
- Reposition ECANCELED error code. (Miguel)
- Replace refcounted FenceCtx in DriverFenceData with a reference plus life time. (Boris)
- Re-add rcu_barrier() patch, since we now can use it for dropping the fence context. (Danilo)
- Add forgotten R-b from Alice, and Acks for MAINTAINERS from Christian and Sumit.
Changes since v2:
- Don't drop DriverFenceData as a whole, but only the members we really want to drop. Gives more robustness. (Gary).
- Break apart large pin_init_from_closure(). (Danilo, Onur)
- Remove rcu_barrier() and synchronize_rcu() from FenceCtx::drop(). FenceCtx might drop in atomic context, where you must not perform those operations. With the current way C dma_fence is designed, the driver must wait for a grace period manually until it unloads.
- Repair the DriverFenceBorrow implementation, properly injecting a life time into it. (Danilo)
- Fix memory layout bug for rcu_head. (Onur)
- Drop RCU patches, since this series doesn't need them anymore.
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: error: Add ECANCELED error code
rust: sync: Add abstraction for rcu_barrier()
rust: xarray: Add support for index reservations
rust: Add dma_fence abstractions
MAINTAINERS: Add entry for Rust dma-buf
MAINTAINERS | 5 + rust/bindings/bindings_helper.h | 1 + rust/helpers/dma_fence.c | 48 ++ rust/helpers/helpers.c | 1 + rust/helpers/xarray.c | 10 + rust/kernel/dma_buf/dma_fence.rs | 1079 ++++++++++++++++++++++++++++++ rust/kernel/dma_buf/mod.rs | 14 + rust/kernel/error.rs | 1 + rust/kernel/lib.rs | 1 + rust/kernel/sync/aref.rs | 40 ++ rust/kernel/sync/rcu.rs | 20 + rust/kernel/xarray.rs | 45 +- 12 files changed, 1264 insertions(+), 1 deletion(-) 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
base-commit: 78900738d981c97aad929e33620340d0d9bcfc82
Signature · 1 line
2.55.0
Philipp Stanner
maintainerPATCH v6 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 | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+)
rust/kernel/sync/aref.rs +40 -0
@@ -24,6 +24,11 @@ ptr::NonNull, // }; +use crate ::{ + prelude::*, + types::ForeignOwnable ,// +}; +/// Types that are _always_ reference counted. /// /// It allows such types to define their own custom ref increment and decrement functions.
@@ -188,6 +193,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> = &'aT ; +type BorrowedMut <'a> = &'aT ; + +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 ) -> &'aT { +// 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 ) -> &'aT { +// 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.55.0
Philipp Stanner
maintainerPATCH v6 2/6 rust: error: Add ECANCELED error code
In reply to Philipp StannerThe 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
@@ -67,6 +67,7 @@ macro_rules! declare_err {declare_err! (EOVERFLOW ,"Value too large for defined data type." );declare_err! (EMSGSIZE ,"Message too long." );declare_err! (ETIMEDOUT ,"Connection timed out." ); +declare_err! (ECANCELED ,"Request has been canceled." );declare_err! (ERESTARTSYS ,"Restart the system call." );declare_err! (ERESTARTNOINTR ,"System call was interrupted by a signal and will be restarted." );declare_err! (ERESTARTNOHAND ,"Restart if no handler." );
Signature · 1 line
2.55.0
Re: [PATCH v6 2/6] rust: error: Add ECANCELED error code
Quoted 6 lines · Philipp Stanner
The Rust
dma_fenceabstractions need the ECANCELED error code.Add ECANCELED error code.
Signed-off-by Philipp Stanner <[email protected]>Tested-by Daniel Almeida <[email protected]>
Alistair
Quoted 18 lines
---
rust/kernel/error.rs | 1 +1 file changed, 1 insertion(+)rust/kernel/error.rs +0
-0@@ -67,6 +67,7 @@ macro_rules! declare_err {declare_err!(EOVERFLOW, "Value too large for defined data type.");
declare_err!(EMSGSIZE, "Message too long."); declare_err!(ETIMEDOUT, "Connection timed out.");
+ declare_err!(ECANCELED, "Request has been canceled."); declare_err!(ERESTARTSYS, "Restart the system call."); declare_err!(ERESTARTNOINTR, "System call was interrupted by a signal and will be restarted."); declare_err!(ERESTARTNOHAND, "Restart if no handler.");
Re: [PATCH v6 2/6] rust: error: Add ECANCELED error code
In reply to Philipp StannerQuoted 6 lines · Philipp Stanner
The Rust
dma_fenceabstractions need the ECANCELED error code.Add ECANCELED error code.
Signed-off-by Philipp Stanner <[email protected]>Tested-by Daniel Almeida <[email protected]>
No need for this, all remaining codes are being added via
https://lore.kernel.org/rust-for-linux/[email protected]/
Please pick up that patch instead.
Re: [PATCH v6 2/6] rust: error: Add ECANCELED error code
Quoted 5 lines · Timur Tabi
Quoted 4 lines · Philipp Stanner
The Rust
dma_fenceabstractions need the ECANCELED error code.Add ECANCELED error code.
Quoted 8 lines
Quoted 2 lines
Signed-off-by Philipp Stanner <[email protected]>Tested-by Daniel Almeida <[email protected]>No need for this, all remaining codes are being added via
https://lore.kernel.org/rust-for-linux/[email protected]/
Please pick up that patch instead.
Ok, pulled it in my local branch for now, thx for the heads-up.
I see that your patch is from June. If it lands in drm-rust-next soonish I'd simply rebase.
Regards
P.
Re: [PATCH v6 2/6] rust: error: Add ECANCELED error code
Quoted 5 lines · Philipp Stanner
Ok, pulled it in my local branch for now, thx for the heads-up.
I see that your patch is from June. If it lands in drm-rust-next soonish I'd simply rebase.
I will take Timur's this cycle, so it should be fine either way.
Cheers,
Miguel
Philipp Stanner
maintainerPATCH v6 3/6 rust: sync: Add abstraction for rcu_barrier()
In reply to Philipp Stannerrcu_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 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
rust/kernel/sync/rcu.rs +20 -0
@@ -50,3 +50,23 @@ fn drop(&mut self) {pub fn read_lock () ->Guard {Guard ::new () } + +/// Wait until all in-flight call_rcu() callbacks complete. +/// +/// Note that this primitive does not necessarily wait for an RCU grace period +/// to complete. For example, if there are no RCU callbacks queued anywhere +/// in the system, then rcu_barrier() is within its rights to return +/// immediately, without waiting for anything, much less an RCU grace period. +/// In fact, rcu_barrier() will normally not result in any RCU grace periods +/// beyond those that were already destined to be executed. +/// +/// In kernels built with CONFIG_RCU_LAZY=y, this function also hurries all +/// pending lazy RCU callbacks. +/// +/// Note that this is one of the RCU primitives which must not be called in +/// atomic context. +#[inline] +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.55.0
Philipp Stanner
maintainerPATCH v6 4/6 rust: xarray: Add support for index reservations
In reply to Philipp StannerOne often cannot allocate in the kernel with the desired flags, most notably in atomic context. Pre-allocating the memory is the preferred solution in such situations.
Add support for xa_reserve() in the Rust abstractions of xarray. Create a Reservation object similar to a lock-guard, that can be dropped once the reservation is no longer needed or once the index was stored to.
---
Please regard this more as an RFC.
I need pre-allocating in XArray for DmaFence. How exactly we achieve this is open for discussion.
P.
--- rust/helpers/xarray.c | 10 ++++++++++ rust/kernel/xarray.rs | 45 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-)
rust/helpers/xarray.c +10 -0
@@ -26,3 +26,13 @@ __rust_helper void rust_helper_xa_unlock(struct xarray *xa) {return xa_unlock (xa); } + +__rust_helper int rust_helper_xa_reserve(struct xarray *xa, unsigned long index, gfp_t flags) +{ +return xa_reserve (xa, index, flags); +} + +__rust_helper void rust_helper_xa_release(struct xarray *xa, unsigned long index) +{ +xa_release (xa, index); +}
rust/kernel/xarray.rs +44 -1
57 lines not on this page · raw message
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v6 4/6] rust: xarray: Add support for index reservations
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] Calling `XArray::reserve()` or dropping `Reservation` while holding the XArray lock (`Guard`)…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Calling XArray::reserve() or dropping Reservation while holding the XArray lock (Guard) causes a guaranteed A-A self-deadlock.
- [High] SAFETY and INVARIANT comments in XArray::reserve and Reservation::drop are factually wrong, reference nonexistent variables, and actively promote deadlocking code.
- [Medium] The Reservation::drop behavior allows multiple threads to accidentally destroy each other's reservations, leading to unexpected -ENOMEM errors during atomic stores. --
--- Patch [4]: [PATCH v6 4/6] rust: xarray: Add support for index reservations --- commit b249894b0dc68234b47a8d9e38c201513786806c
Author: Philipp Stanner <[email protected]>
rust: xarray: Add support for index reservations
This commit adds support for xa_reserve() to the Rust XArray abstractions by introducing reserve() and a Reservation drop guard.
Quoted 4 lines
rust/kernel/xarray.rs +0
-0
[ ... ]
Quoted 5 lines
rust/kernel/xarray.rs +1
-0@@ -147,6 +150,46 @@ pub fn lock(&self) -> Guard<'_, T> { _not_send: NotThreadSafe, } } +
rust/kernel/xarray.rs pub fn lock(&self) -> Guard<'_, T> { · line 163
Quoted 10 lines
rust/kernel/xarray.rs +10
-0+/// Reserve memory in the XArray at the appropriate index. If this call +/// succeeds, later storing at the index will not require an allocation. +/// +/// Loading from reserved entries will return `None`. +pub fn reserve (&self, index:usize , gfp: alloc::Flags ) ->Result <Reservation <'_,T >> { +// SAFETY: +// - `self.xa.xa` is always valid by the type invariant. +// - The caller holds the lock. +// +// INVARIANT: `new` came from `T::into_foreign`.
[Severity: High]
The safety comment in reserve() explicitly states "The caller holds the lock." However, both xa_reserve() and xa_release() internally acquire the xa_lock spinlock.
If the caller follows this documentation and acquires the lock via XArray::lock() before calling reserve(), won't this result in an A-A self-deadlock when xa_reserve() attempts to acquire the same non-reentrant spinlock?
Also, this isn't a bug, but the INVARIANT references a nonexistent variable new. Is this a copy-paste error from another function?
rust/kernel/xarray.rs pub fn lock(&self) -> Guard<'_, T> { · line 166
Quoted 3 lines
rust/kernel/xarray.rs +3
-0+let ret =unsafe { bindings::xa_reserve (self.xa.get (), index, gfp.as_raw ()) }; +match ret { +0 =>Ok (Reservation { xa: self, index }),
[Severity: Medium]
In C, xa_reserve() returns 0 both when it successfully reserves an empty slot and when the slot is already reserved (XA_ZERO_ENTRY).
If two threads concurrently call reserve() on the same index, won't both succeed and receive a Reservation guard?
When the first thread's guard drops and unconditionally calls xa_release(), won't it overwrite XA_ZERO_ENTRY with NULL, silently destroying the second thread's active reservation and potentially leading to an unexpected -ENOMEM on a later atomic store?
Quoted 13 lines
rust/kernel/xarray.rs +13
-0+ _ => Err(Error::from_errno(ret)), + } + } +} + +/// An index reservation object. +/// +/// As long as you keep this object alive, your reservation will stay valid. If +/// someone has stored to this index in the meantime, dropping your reservation +/// does nothing, which is why you can always drop this reservation object once +/// you performed your store operation. +/// +/// Refer to the main C xarray documentation for more details.
rust/kernel/xarray.rs pub fn lock(&self) -> Guard<'_, T> { · line 189
Quoted 10 lines
rust/kernel/xarray.rs +10
-0+pub struct Reservation <'a,T :ForeignOwnable > { + xa: &'aXArray <T >, + index:usize , +} + +impl <T :ForeignOwnable >Drop for Reservation <'_,T > { +fn drop (&mut self) { +// SAFETY: +// - `self.xa` is always valid by the type invariant. +// - If `self.id` is not used or has been stored to by a racing party,
[Severity: High]
This isn't a bug, but the safety comment here in the drop() method references self.id which does not exist on this struct. Should this be self.index instead?
rust/kernel/xarray.rs pub fn lock(&self) -> Guard<'_, T> { · line 191
Quoted 2 lines
rust/kernel/xarray.rs +2
-0+// `xa_release()` will do nothing. +unsafe { bindings::xa_release (self.xa.xa.get (), self.index) };
[Severity: High]
Since xa_release() internally acquires the xa_lock spinlock, will this cause a deadlock if the Reservation guard is dropped in Drop for Reservation while the caller still holds the XArray::lock() guard?
Quoted 2 lines
rust/kernel/xarray.rs +1
-0+ } }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Andreas Hindborg
maintainerRe: [PATCH v6 4/6] rust: xarray: Add support for index reservations
In reply to Philipp StannerQuoted 8 lines · Philipp Stanner
One often cannot allocate in the kernel with the desired flags, most notably in atomic context. Pre-allocating the memory is the preferred solution in such situations.
Add support for
xa_reserve()in the Rust abstractions of xarray. Create a Reservation object similar to a lock-guard, that can be dropped once the reservation is no longer needed or once the index was stored to.
Quoted 6 lines
Signed-off-by Philipp Stanner <[email protected]>---
Please regard this more as an RFC.I need pre-allocating in XArray for DmaFence. How exactly we achieve this is open for discussion.
Do you need to actually reserve a key, or do you just need atomic allocation?
I have a series that solves the allocation issue here [1]. Tamir did a reservation API a while back [2].
Best regards,
Andreas Hindborg
[1] https://lore.kernel.org/r/[email protected]
[2] https://lore.kernel.org/r/[email protected]
Re: [PATCH v6 4/6] rust: xarray: Add support for index reservations
Quoted 14 lines · Andreas Hindborg
Quoted 12 lines · Philipp Stanner
One often cannot allocate in the kernel with the desired flags, most notably in atomic context. Pre-allocating the memory is the preferred solution in such situations.
Add support for
xa_reserve()in the Rust abstractions of xarray. Create a Reservation object similar to a lock-guard, that can be dropped once the reservation is no longer needed or once the index was stored to.Signed-off-by Philipp Stanner <[email protected]>---
Please regard this more as an RFC.
Quoted 5 lines
Quoted 2 lines
I need pre-allocating in XArray for DmaFence. How exactly we achieve this is open for discussion.
Do you need to actually reserve a key, or do you just need atomic allocation?
I would just have needed a position to store to, but the fence sequence number would be the only reasonable index, so you'd also reserve a key.
Anyways, please forget about this for now, we abstained from using the XArray in the current revision (v8) of this patch series.
Thx
P.
Philipp Stanner
maintainerPATCH v6 5/6 rust: Add dma_fence abstractions
In reply to Philipp StannerC'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 signaled once
- all fences must be signaled at some point
- fence error codes must only be set before signaling
- 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 signaled 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 signaled.
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 signaled by the creator with an error code. In case a DriverFence drops without being signaled beforehand, it is signaled 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 'signaled' 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 signaling 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 DmaFenceContext 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 signaling) 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.
It is expected that other callbacks, added in the future, also mostly operate on the generic data in the FenceContext. To make this safe, the implementation ensures through a lifetime that a DriverFence cannot outlive its FenceContext. Since this can, currently, not be fully guaranteed (core::mem::forget() could make the compiler forget about the memory) in Rust, an additional safety measure is implemented: the FenceContext keeps track of all unsignaled fences, and should it ever drop with such a fence present, it will signal it, ensuring full decoupling.
Synchronization for dma_fence_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. Since they can also operate on the FenceContext's data, its drop implementation also performs the necessary delay with rcu_barrier().
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 | 1079 ++++++++++++++++++++++++++++++ rust/kernel/dma_buf/mod.rs | 14 + rust/kernel/lib.rs | 1 + 6 files changed, 1144 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
@@ -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>
rust/helpers/dma_fence.c new file +48 -0
48 lines not on this page · raw message
rust/helpers/helpers.c +1 -0
@@ -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"
rust/kernel/dma_buf/dma_fence.rs new file +1079 -0
1,079 lines not on this page · raw message
rust/kernel/dma_buf/mod.rs new file +14 -0
@@ -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 , +FenceCallback , +FenceCallbackRegistration , +FenceContext , +FenceContextOps ,// +};
rust/kernel/lib.rs +1 -0
@@ -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;
Signature · 1 line
2.55.0
[email protected]
AI reviewRe: [PATCH v6 5/6] rust: Add dma_fence abstractions
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider: - [Critical] Use-After-Free of `FenceContext` in `dma_fence_ops` callbacks because `Fence` outlives the…
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [Critical] Use-After-Free of FenceContext in dma_fence_ops callbacks because Fence outlives the context.
- [Critical] Passing a NULL pointer for the spinlock to dma_fence_init violates the C API contract and causes an immediate kernel panic.
- [High] Concurrent allocation failures in fence_alloc corrupt the shared sequence number atomic, causing duplicate sequence numbers.
- [High] Using GFP_KERNEL while holding a spinlock guard sleeps in atomic context.
- [High] FenceContext::drop holds the XArray spinlock while invoking dma_fence_signal_locked, risking deadlocks and lock inversions.
- [High] Iterating from 0 to highest_seqno under a spinlock in FenceContext::drop causes unbounded latency and stalls.
- [High] Casting the 64-bit seqno to usize for XArray indexing truncates on 32-bit platforms, causing index collisions.
- [High] Implementing ForeignOwnable for DriverFence<'a, T> erases its lifetime, allowing safe Rust code to trigger UAF.
- [Medium] Dropping a DriverFence without explicitly signaling it leaks DriverFenceData and the Fence in the XArray.
- [Medium] FenceCallbackRegistration::new leaks the callback state if dma_fence_add_callback returns an error other than ENOENT. --
--- Patch [5]: [PATCH v6 5/6] rust: Add dma_fence abstractions --- commit df80ab90d41cfd4c4088816a593de340df6f198e
Author: Philipp Stanner <[email protected]>
rust: Add dma_fence abstractions
This commit adds Rust abstractions for the dma_fence framework. It provides DriverFence for signaling and Fence for consumption, tying them to a DmaFenceContext to manage sequence numbering and timelines.
Quoted 13 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -147,15 +147,15 @@ impl<'a, T: Send + Sync + FenceContextOps> FenceContext<T> {fn get_next_seqno (&self) ->u64 { self.seqno.fetch_add (1 ,Relaxed ) }fn decrement_seqno (&self) {let _ = self.seqno.fetch_sub (1 ,Relaxed ); }
[ ... ]
rust/kernel/dma_buf/dma_fence.rs impl<'a, T: Send + Sync + FenceContextOps> FenceContext<T> { · line 182
Quoted 6 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -178,14 +178,14 @@ impl<'a, T: Send + Sync + FenceContextOps> FenceContext<T> {// Take the next seqno right now, so that we can use it for pre-allocating // an entry in the xarray. let seqno:u64 = self.get_next_seqno ();match self.fences.reserve (seqnoas usize ,GFP_KERNEL ) {
[Severity: High]
Does casting the 64-bit seqno to usize for XArray indexing cause truncation on 32-bit platforms?
DMA fence sequence numbers are u64 to prevent wrap-around. When the sequence number exceeds u32::MAX, the cast to usize on 32-bit platforms truncates the value. Will this overwrite existing unsignaled fences in the XArray and cause fatal corruption of the fence tracking mechanism?
rust/kernel/dma_buf/dma_fence.rs impl<'a, T: Send + Sync + FenceContextOps> FenceContext<T> { · line 190
Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0Ok(reservation) => Ok(DriverFenceAllocation { data, seqno, reservation, ops: &Self::OPS, }), Err(e) => { self.decrement_seqno ();
[Severity: High]
Does rolling back the sequence number corrupt the timeline?
If the reservation fails, the code calls self.decrement_seqno(), which executes fetch_sub(1, Relaxed).
If Thread B acquires seqno 2, and Thread A (which acquired seqno 1) fails and decrements the counter back to 1, won't a subsequent Thread C be assigned seqno 2, creating duplicate sequence numbers and breaking the DMA fence timeline guarantees?
Quoted 4 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0Err (e)} } }
[ ... ]
Quoted 15 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -189,14 +189,14 @@ impl<'a, T: Send + Sync + FenceContextOps> FenceContext<T> {extern "C" fn get_driver_name (ptr: *mut bindings::dma_fence ) -> *const c_char {// SAFETY: The C backend only invokes this callback with ptr pointing // to a valid, unsignaled bindings::dma_fence. All fences created in // this module always reside within Fence which always resides in a // DriverFenceData, thus satisfying the function's safety // requirements. let fctx =unsafe {Self ::from_raw_fence (ptr) }; fctx.driver_name.as_char_ptr () }extern "C" fn get_timeline_name (ptr: *mut bindings::dma_fence ) -> *const c_char {// SAFETY: The C backend only invokes this callback with ptr pointing // to a valid, unsignaled bindings::dma_fence. All fences created in
Quoted 7 lines
// this module always reside within Fence which always resides in a
// DriverFenceData, thus satisfying the function's safety
// requirements.
let fctx = unsafe { Self::from_raw_fence(ptr) };
fctx.timeline_name.as_char_ptr()
}[Severity: Critical]
Can this code introduce a use-after-free risk?
If a Fence is exported to userspace (for example via a sync_file), userspace could call SYNC_IOC_FILE_INFO on the descriptor after the driver's job is complete and the FenceContext has been dropped.
This triggers the C backend to call fence->ops->get_driver_name. The Rust callback retrieves the FenceContext reference from the DriverFenceData and dereferences fctx.driver_name.
Since FenceContext::drop does not wait for the Fence refcount to drop to 0, does this result in a direct read from freed memory?
[ ... ]
rust/kernel/dma_buf/dma_fence.rs impl<T: FenceContextOps + Send + Sync> PinnedDrop for FenceContext<T> { · line 254
Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -248,15 +248,15 @@ impl<T: FenceContextOps + Send + Sync> PinnedDrop for FenceContext<T> {fn drop (self:Pin <&mut Self >) {let highest_seqno = self.get_current_seqno ();let fences_guard = self.fences.lock ();// TODO: Implement a public iterator for XArray and use it here. for seqnoin 0 ..highest_seqno {
[Severity: High]
Could iterating from 0 to highest_seqno under a spinlock cause unbounded latency and stalls?
FenceContext::drop linearly loops from 0 to highest_seqno, performing an XArray lookup on every iteration while holding the fences_guard spinlock.
Since drivers often initialize FenceContext with a very large initial_seqno, could this loop run billions of times with preemption disabled?
Quoted 8 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0if let Some (fence) = fences_guard.get (seqnoas usize ) {let fence = fence.lock ();// SAFETY: We still hold a reference, thus the pointer is valid. let signaled:bool =unsafe { bindings::dma_fence_test_signaled_flag (*fence.deref ()) };if !signaled {// SAFETY: We still hold a reference, thus the pointer is
Quoted 7 lines
// valid. The fence must not have been signaled yet, which we
// check directly above. The lock must be held, which we
// acquired above.
unsafe { bindings::dma_fence_set_error(*fence.deref(), ECANCELED.to_errno()) };
// SAFETY: We still hold a reference, thus the pointer is
// valid. The lock must be held, which we acquired above.
unsafe { bindings::dma_fence_signal_locked(*fence.deref()) };[Severity: High]
Is it safe to invoke dma_fence_signal_locked while holding the subsystem internal XArray lock?
While holding the fences_guard spinlock, the code calls bindings::dma_fence_signal_locked. This synchronously invokes any registered dma_fence_ops callbacks.
If any callback attempts to interact with the same FenceContext, or attempts to take a driver lock that is held elsewhere while a fence is signaled, won't this guarantee an ABBA deadlock?
Quoted 4 lines
} else {
pr_warn!("Signaled Fences in FenceContext.");
}
}[ ... ]
rust/kernel/dma_buf/dma_fence.rs impl<T: FenceCallback> FenceCallbackRegistration<T> { · line 362
Quoted 7 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -357,11 +357,11 @@ impl<T: FenceCallback> FenceCallbackRegistration<T> {// and pass it to the user again. let cb_back =unsafe {ManuallyDrop ::take (callback) };Err (CallbackError ::AlreadySignaled (cb_back)) }else {Err (CallbackError ::Other (e)) }
[Severity: Medium]
Does this code leak the callback state if dma_fence_add_callback returns an error other than ENOENT?
The user's callback is moved into a ManuallyDrop<T>. If the C backend fails with an error other than ENOENT, the error is returned directly.
Because the callback is never extracted from ManuallyDrop on this error path, is it permanently leaked when the partially initialized struct is cleaned up?
[ ... ]
rust/kernel/dma_buf/dma_fence.rs impl<'a, T: Send + Sync + FenceContextOps> DriverFenceAllocation<'a, T> { · line 677
Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -668,14 +668,14 @@ impl<'a, T: Send + Sync + FenceContextOps> DriverFenceAllocation<'a, T> {pub fn new_fence (self) ->DriverFence <'a,T > {// We feed the C dma_fence backend a NULL for the spinlock so that it // uses per-fence locks automatically. let null_ptr: *mut bindings::spinlock = ptr::null_mut ();let fence_ptr = self.as_raw ();// SAFETY: fence_ptr has been created directly above. It will live // at least as long as Self. The same applies to &Self::OPS. unsafe { bindings::dma_fence_init (fence_ptr, self.ops, null_ptr, self.data.fctx.nr, self.seqno) };
[Severity: Critical]
Will this hardcoded NULL pointer passed directly to the C initialization function cause an immediate kernel panic?
The C implementation in drivers/dma-buf/dma-fence.c starts with BUG_ON(!lock). Does passing ptr::null_mut() here violate the C API contract and crash the system upon fence creation?
Quoted 11 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0let fence =ARef ::from (&self.data.inner);let mut fences_guard = self.data.fctx.fences.lock ();// Can never be an error because we already allocated an entry for // exactly this sequence number. // TODO: write some sort of XArray::replace() function or mechanism for // reserving entries so that this can obviously never fail. if fences_guard .store(self.seqno as usize, fence, GFP_KERNEL) .is_err() {
[Severity: High]
Is it safe to use GFP_KERNEL while holding a spinlock guard?
The code acquires the XArray spinlock via self.data.fctx.fences.lock(), and then calls fences_guard.store() with GFP_KERNEL.
Since GFP_KERNEL implies __GFP_DIRECT_RECLAIM, which allows the allocation to sleep, doesn't this strictly violate atomic context constraints and risk system deadlocks?
[ ... ]
Quoted 9 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -768,7 +768,7 @@ impl<'a, T: Send + Sync + FenceContextOps> Deref for DriverFenceBorrow<'a, T> { } }// SAFETY: The Rust dma_fence abstractions are already designed around the inner // C dma_fence, which can serve safely as the identification point when being // owned by C. Moreover, safety is ensured by not dropping DriverFence and by // only allowing operations without side effects on the Borrowed type. unsafe impl<T: Send + Sync + FenceContextOps + 'static> ForeignOwnable for DriverFence<'_, T> {
[Severity: High]
Does implementing ForeignOwnable for DriverFence erase its lifetime?
The DriverFence type relies on its lifetime to ensure it cannot outlive its associated FenceContext. Implementing ForeignOwnable for DriverFence allows safe Rust code to convert the object into a raw pointer.
When it is later reconstructed from the raw pointer, the compiler cannot enforce the lifetime dependency. Could this allow it to outlive the FenceContext and trigger a use-after-free when accessing the context to signal the fence?
[ ... ]
Quoted 15 lines
rust/kernel/dma_buf/dma_fence.rs new file +0
-0@@ -833,14 +833,14 @@ impl<'a, T: Send + Sync + FenceContextOps> Drop for DriverFence<'a, T> {fn drop (&mut self) {let guard = self.as_fence ().lock ();// 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: guard is valid until the call_rcu() below. let signaled:bool =unsafe { bindings::dma_fence_test_signaled_flag (*guard.deref ()) };if warn_on! (!signaled) {// SAFETY: guard 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 (*guard.deref (),ECANCELED .to_errno ()) };// SAFETY: guard is valid until the call_rcu() below. The lock
Quoted 7 lines
// must be held, which we acquired above.
unsafe { bindings::dma_fence_signal_locked(*guard.deref()) };
}
drop(guard);
// SAFETY: Valid because self is valid.
let rcu_head_ptr = unsafe { &raw mut (*self.data.as_ptr()).rcu_head };[Severity: Medium]
Should this drop implementation clean up the fctx.fences XArray entry?
If a DriverFence drops without being explicitly signaled first, this code signals it with -ECANCELED and schedules RCU destruction, but fails to remove the fence from the XArray.
Will the XArray continue to hold an ARef<Fence>, keeping the dma_fence reference count above zero indefinitely and causing a permanent memory leak?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Philipp Stanner
maintainerPATCH v6 6/6 MAINTAINERS: Add entry for Rust dma-buf
In reply to Philipp StannerRust 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 | 5 +++++ 1 file changed, 5 insertions(+)
MAINTAINERS +5 -0
@@ -7622,6 +7622,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)
@@ -7635,6 +7636,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
@@ -8621,7 +8624,9 @@ T: git https://gitlab.freedesktop.org/drm/rust/kernel.git F: drivers/gpu/drm/nova/ F: drivers/gpu/drm/tyr/ F: drivers/gpu/nova-core/ +F: rust/helpers/dma_fence.c F: rust/helpers/gpu.c +F: rust/kernel/dma_buf/ F: rust/kernel/drm/ F: rust/kernel/gpu.rs F: rust/kernel/gpu/
Signature · 1 line
2.55.0