From nobody Thu Oct 2 09:19:05 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6E37628F1; Thu, 18 Sep 2025 12:32:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758198765; cv=none; b=ghAhgd/a7fxoq1eOsbgwSEqztBPG9J+KyrtcZOyz+rL6jsSbjSJE/SwSt+qJ/cl6h9jt82Ym/iJFBa99RtcxOgVpXJXnb7mxg/DtJHiWYI7QibashQTJicelf8LlbDi3D4Hw0AC2A+qSUhRCxhKi6ef7sfP+Rj81tinjTV7wH0U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1758198765; c=relaxed/simple; bh=lBJlZlkQtcJLRQHlDzWJLy6/C7ta+RjAc0Dub0YKWRQ=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type; b=MizcJCZuCiP+yE1VefSy2sAbw/egTlaCi8vUVdJYuleEKqtuv0Jf3nQyQ2LPiEQedmAEODuYIRh8XChruMavp3x0mxiSYfn9KGAUJpHojs1K1PHLNA4xyU+JJTPhGsuDOIUhUcvB33YjHaRxk4SLfLAVNX4OdZ8+aZMuHhWFN5g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=htcsuIY4; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="htcsuIY4" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 61ED5C4CEEB; Thu, 18 Sep 2025 12:32:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1758198764; bh=lBJlZlkQtcJLRQHlDzWJLy6/C7ta+RjAc0Dub0YKWRQ=; h=From:To:Cc:Subject:Date:From; b=htcsuIY4UOPt4xsThGQzYiWVYH28tqyChm8D/aO5PRZIVPmsutGZbnr/tjO5lPH4+ jcHCu/17CgbhYd8iXGESYUjs5ayfrgzdVaG8PoWoiUkrJSaDYAEo29adpYkZlAhVVT Htw35zpFz0j7zpbaDTrc48RyuyQw59exkhj0oFeDjajogTn8v0DXlCWGsm7pT9yAgj UFX0msltFSAC0XfeqAhBvjRrzC8YAVULAVtPNLsLFuS8zNadlaOCME9CBlO3EcbAAz i60qcdy/D72JYzdEQ2nZQo/H5yQZ32wx/nW8uuZ0ANA7dg2745NeKFQZvTxpDM8F1y wxZNV2qnItrNg== From: Philipp Stanner To: Miguel Ojeda , Alex Gaynor , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Peter Zijlstra , Ingo Molnar , Will Deacon , Waiman Long , Nathan Chancellor , Nick Desaulniers , Bill Wendling , Justin Stitt , Sumit Semwal , =?UTF-8?q?Christian=20K=C3=B6nig?= , Greg Kroah-Hartman , Viresh Kumar , Asahi Lina , Daniel Almeida , Tamir Duberstein , Philipp Stanner , Wedson Almeida Filho , FUJITA Tomonori , Krishna Ketan Rai , Lyude Paul , Mitchell Levy Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, llvm@lists.linux.dev, dri-devel@lists.freedesktop.org Subject: [RFC PATCH] rust: sync: Add dma_fence abstractions Date: Thu, 18 Sep 2025 14:30:59 +0200 Message-ID: <20250918123100.124738-2-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable dma_fence is a synchronization mechanism which is needed by virtually all GPU drivers. A dma_fence offers many features, among which the most important ones are registering callbacks (for example to kick off a work item) which get executed once a fence gets signalled. dma_fence has a number of callbacks. Only the two most basic ones (get_driver_name(), get_timeline_name() are abstracted since they are enough to enable the basic functionality. Callbacks in Rust are registered by passing driver data which implements the Rust callback trait, whose function will be called by the C backend. dma_fence's are always refcounted, so implement AlwaysRefcounted for them. Once a reference drops to zero, the C backend calls a release function, where we implement drop_in_place() to conveniently marry that C-cleanup mechanism with Rust's ownership concepts. This patch provides basic functionality, but is still missing: - An implementation of PinInit for all driver data. - A clever implementation for working dma_fence_begin_signalling() guards. See the corresponding TODO in the code. - Additional useful helper functions such as dma_fence_is_signaled(). These _should_ be relatively trivial to implement, though. Signed-off-by: Philipp Stanner --- So. =C2=A1Hola! This is a highly WIP RFC. It's obviously at many places not yet conforming very well to Rust's standards. Nevertheless, it has progressed enough that I want to request comments from the community. There are a number of TODOs in the code to which I need input. Notably, it seems (half-)illegal to use a shared static reference to an Atomic, which I currently use for the dma_fence unit test / docstring test. I'm willing to rework that if someone suggests how. (Still, shouldn't changing a global Atomic always be legal? It can race, of course. But that's kind of the point of an atomic) What I want comments on the most is the design of the callbacks. I think it's a great opportunity to provide Rust drivers with rust-only callbacks, so that they don't have to bother about the C functions. dma_fence wise, only the most basic callbacks currently get implemented. For Nova, AFAICS, we don't need much more than signalling fences and registering callbacks. Another, solvable, issue I'm having is designing the dma_fence_begin_signallin() abstractions. There are TODOs about that in the code. That should ideally be robust and not racy. So we might want some sort of synchronized (locked?) way for using that abstraction. Regarding the manually created spinlock of mine: I so far never need that spinlock anywhere in Rust and wasn't sure what's then the best way to pass a "raw" spinlock to C. So much from my side. Hope to hear from you. (I've compiled and tested this with the unit test on the current -rc3) Philipp --- rust/bindings/bindings_helper.h | 1 + rust/helpers/dma_fence.c | 23 ++ rust/helpers/helpers.c | 1 + rust/helpers/spinlock.c | 5 + rust/kernel/sync.rs | 2 + rust/kernel/sync/dma_fence.rs | 388 ++++++++++++++++++++++++++++++++ 6 files changed, 420 insertions(+) create mode 100644 rust/helpers/dma_fence.c create mode 100644 rust/kernel/sync/dma_fence.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 84d60635e8a9..107cb6b6f4a4 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/dma_fence.c b/rust/helpers/dma_fence.c new file mode 100644 index 000000000000..a9fc4829bbae --- /dev/null +++ b/rust/helpers/dma_fence.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +void rust_helper_dma_fence_get(struct dma_fence *f) +{ + dma_fence_get(f); +} + +void rust_helper_dma_fence_put(struct dma_fence *f) +{ + dma_fence_put(f); +} + +bool rust_helper_dma_fence_begin_signalling(void) +{ + return dma_fence_begin_signalling(); +} + +void rust_helper_dma_fence_end_signalling(bool cookie) +{ + dma_fence_end_signalling(cookie); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 7cf7fe95e41d..99a7f7834c03 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -20,6 +20,7 @@ #include "cred.c" #include "device.c" #include "dma.c" +#include "dma_fence.c" #include "drm.c" #include "err.c" #include "fs.c" diff --git a/rust/helpers/spinlock.c b/rust/helpers/spinlock.c index 42c4bf01a23e..017ac447ebbd 100644 --- a/rust/helpers/spinlock.c +++ b/rust/helpers/spinlock.c @@ -16,6 +16,11 @@ void rust_helper___spin_lock_init(spinlock_t *lock, cons= t char *name, #endif /* CONFIG_DEBUG_SPINLOCK */ } =20 +void rust_helper_spin_lock_init(spinlock_t *lock) +{ + spin_lock_init(lock); +} + void rust_helper_spin_lock(spinlock_t *lock) { spin_lock(lock); diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs index 00f9b558a3ad..6e59526020bc 100644 --- a/rust/kernel/sync.rs +++ b/rust/kernel/sync.rs @@ -12,6 +12,7 @@ mod arc; pub mod aref; pub mod completion; +pub mod dma_fence; mod condvar; pub mod lock; mod locked_by; @@ -20,6 +21,7 @@ =20 pub use arc::{Arc, ArcBorrow, UniqueArc}; pub use completion::Completion; +pub use dma_fence::{DmaFence, DmaFenceCtx, DmaFenceNames, DmaFenceCb, DmaF= enceCbFunc}; pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult}; pub use lock::global::{global_lock, GlobalGuard, GlobalLock, GlobalLockBac= kend, GlobalLockedBy}; pub use lock::mutex::{new_mutex, Mutex, MutexGuard}; diff --git a/rust/kernel/sync/dma_fence.rs b/rust/kernel/sync/dma_fence.rs new file mode 100644 index 000000000000..a00bb10b2208 --- /dev/null +++ b/rust/kernel/sync/dma_fence.rs @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! DmaFence support. +//! +//! Reference: +//! +//! C header: [`include/linux/dma-fence.h`](srctree/include/linux/dma-fenc= e.h) + +use crate::{ + bindings, + prelude::*, + str::CStr, + types::ForeignOwnable, + types::{ARef, AlwaysRefCounted, Opaque}, +}; + +use core::{ + ptr::{drop_in_place, NonNull}, + sync::atomic::{AtomicU64, Ordering}, +}; + +use kernel::sync::{Arc, ArcBorrow}; + +/// The C dma_fence backend functions ask for certain name parameters at t= imes. In order to avoid +/// storing those names in [`DmaFence`] (there can be millions of fences a= t the same time, wasting +/// much memory), the driver must provide associated constants which the C= backend will access, +/// ultimately. +pub trait DmaFenceNames { + /// The driver's name. + const DRIVER_NAME: &CStr; + /// The name of the timeline the fence is associated with. + const TIMELINE_NAME: &CStr; +} + +/// Defines the callback function the dma-fence backend will call once the= fence gets signalled. +pub trait DmaFenceCbFunc { + /// The callback function. `cb` is a container of the data which the d= river passed in + /// [`DmaFence::register_callback`]. + fn callback(cb: Pin>>) + where + Self: Sized; +} + +/// Container for driver data which the driver gets back in its callback o= nce the fence gets +/// signalled. +#[pin_data] +pub struct DmaFenceCb { + /// C struct needed for the backend. + #[pin] + inner: Opaque, + /// Driver data. + #[pin] + pub data: T, +} + +impl DmaFenceCb { + fn new(data: impl PinInit) -> Result>> { + let cb =3D try_pin_init!(Self { + inner: Opaque::zeroed(), // This gets initialized by the C bac= kend. + data <- data, + }); + + KBox::pin_init(cb, GFP_KERNEL) + } + + /// Callback for the C dma_fence backend. + /// + /// # Safety + /// All data used and cast in this function was validly created by + /// [`DmaFence::register_callback`] and isn't modified by the C backen= d until this callback + /// here has run. + unsafe extern "C" fn callback( + _fence_ptr: *mut bindings::dma_fence, + cb_ptr: *mut bindings::dma_fence_cb, + ) { + let cb_ptr =3D Opaque::cast_from(cb_ptr); + + // SAFETY: The constructor guarantees that `cb_ptr` is always `inn= er` of a DmaFenceCb. + let cb_ptr =3D unsafe { crate::container_of!(cb_ptr, Self, inner) = }.cast_mut() as *mut c_void; + // SAFETY: `cp_ptr` is the heap memory of a Pin> becaus= e it was created by + // invoking ForeignOwnable::into_foreign() on such an instance. + let cb =3D unsafe { > as ForeignOwnable>::from_fore= ign(cb_ptr) }; + + // Pass ownership back over to the driver. + T::callback(cb); + } +} + +/// 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 DmaFenceCtx { + /// An opaque spinlock. Only ever passed to the C backend, never used = by Rust. + #[pin] + lock: Opaque, + /// The fence context number. + nr: u64, + /// The sequence number for the next fence created. + seqno: AtomicU64, +} + +impl DmaFenceCtx { + /// Create a new `DmaFenceCtx`. + pub fn new() -> Result> { + let ctx =3D pin_init!(Self { + // Feed in a non-Rust spinlock for now, since the Rust side ne= ver needs the lock. + lock <- Opaque::ffi_init(|slot: *mut bindings::spinlock| { + // SAFETY: `slot` is a valid pointer to an uninitialized `= struct spinlock_t`. + unsafe { bindings::spin_lock_init(slot) }; + }), + // SAFETY: `dma_fence_context_alloc()` merely works on a globa= l atomic. Parameter `1` + // is the number of contexts we want to allocate. + nr: unsafe { bindings::dma_fence_context_alloc(1) }, + seqno: AtomicU64::new(0), + }); + + Arc::pin_init(ctx, GFP_KERNEL) + } + + fn get_new_fence_seqno(&self) -> u64 { + self.seqno.fetch_add(1, Ordering::Relaxed) + } +} + +impl ArcBorrow<'_, DmaFenceCtx> { + /// Create a new fence, consuming `data`. + /// + /// The fence will increment the refcount of the fence context associa= ted with this + /// [`DmaFenceCtx`]. + pub fn new_fence( + &mut self, + data: impl PinInit, + ) -> Result>> { + let fctx: Arc =3D (*self).into(); + let seqno: u64 =3D fctx.get_new_fence_seqno(); + + // TODO: Should we reset seqno in case of failure? + // Pass `fctx` by value so that the fence will hold a reference to= the DmaFenceCtx as long + // as it lives. + DmaFence::new(fctx, data, &self.lock, self.nr, seqno) + } +} + +/// A synchronization primitive mainly for GPU drivers. +/// +/// DmaFences are always reference counted. The typical use case is that o= ne side registers +/// callbacks on the fence which will perform a certain action (such as qu= eueing work) once the +/// other side signals the fence. +/// +/// # Examples +/// +/// ``` +/// use kernel::c_str; +/// use kernel::sync::{Arc, ArcBorrow, DmaFence, DmaFenceCtx, DmaFenceName= s, DmaFenceCb, DmaFenceCbFunc}; +/// use core::sync::atomic::{self, AtomicBool}; +/// +/// static mut CHECKER: AtomicBool =3D AtomicBool::new(false); +/// +/// struct CallbackData { +/// i: u32, +/// } +/// +/// impl CallbackData { +/// fn new() -> Self { +/// Self { i: 9 } +/// } +/// } +/// +/// impl DmaFenceCbFunc for CallbackData { +/// fn callback(cb: Pin>>) where Self: Sized { +/// assert_eq!(cb.data.i, 9); +/// // SAFETY: Just to have an easy way for testing. This cannot r= ace with the checker +/// // because the fence signalling callbacks are executed synchro= nously. +/// unsafe { CHECKER.store(true, atomic::Ordering::Relaxed); } +/// } +/// } +/// +/// struct DriverData { +/// i: u32, +/// } +/// +/// impl DriverData { +/// fn new() -> Self { +/// Self { i: 5 } +/// } +/// } +/// +/// impl DmaFenceNames for DriverData { +/// const DRIVER_NAME: &CStr =3D c_str!("DUMMY_DRIVER"); +/// const TIMELINE_NAME: &CStr =3D c_str!("DUMMY_TIMELINE"); +/// } +/// +/// let data =3D DriverData::new(); +/// let fctx =3D DmaFenceCtx::new()?; +/// +/// let mut fence =3D fctx.as_arc_borrow().new_fence(data)?; +/// +/// let cb_data =3D CallbackData::new(); +/// fence.register_callback(cb_data); +/// // fence.begin_signalling(); +/// fence.signal()?; +/// // Now check wehether the callback was actually executed. +/// // SAFETY: `fence.signal()` above works sequentially. We just check he= re whether the signalling +/// // actually did set the boolean correctly. +/// unsafe { assert_eq!(CHECKER.load(atomic::Ordering::Relaxed), true); } +/// +/// Ok::<(), Error>(()) +/// ``` +#[pin_data] +pub struct DmaFence { + /// The actual dma_fence passed to C. + #[pin] + inner: Opaque, + /// User data. + #[pin] + data: T, + /// Marks whether the fence is currently in the signalling critical se= ction. + signalling: bool, + /// A boolean needed for the C backend's lockdep guard. + signalling_cookie: bool, + /// A reference to the associated [`DmaFenceCtx`] so that it cannot be= dropped while there are + /// still fences around. + fctx: Arc, +} + +// SAFETY: `DmaFence` is safe to be sent to any task. +unsafe impl Send for DmaFence {} + +// SAFETY: `DmaFence` is safe to be accessed concurrently. +unsafe impl Sync for DmaFence {} + +// SAFETY: These implement the C backends refcounting methods which are pr= oven to work correctly. +unsafe impl AlwaysRefCounted for DmaFence { + fn inc_ref(&self) { + // SAFETY: `self.as_raw()` is a pointer to a valid `struct dma_fen= ce`. + unsafe { bindings::dma_fence_get(self.as_raw()) } + } + + /// # Safety + /// + /// `ptr`must be a valid pointer to a [`DmaFence`]. + unsafe fn dec_ref(ptr: NonNull) { + // SAFETY: `ptr` is never a NULL pointer; and when `dec_ref()` is = called + // the fence is by definition still valid. + let fence =3D unsafe { (*ptr.as_ptr()).inner.get() }; + + // SAFETY: Valid because `fence` was created validly above. + unsafe { bindings::dma_fence_put(fence) } + } +} + +impl DmaFence { + // TODO: There could be a subtle potential problem here? The LLVM comp= iler backend can create + // several versions of this constant. Their content would be identical= , but their addresses + // different. + const OPS: bindings::dma_fence_ops =3D Self::ops_create(); + + /// Create an initializer for a new [`DmaFence`]. + fn new( + fctx: Arc, + data: impl PinInit, // TODO: The driver data should implement P= inInit + lock: &Opaque, + context: u64, + seqno: u64, + ) -> Result> { + let fence =3D pin_init!(Self { + inner <- Opaque::ffi_init(|slot: *mut bindings::dma_fence| { + let lock_ptr =3D &raw const (*lock); + // SAFETY: `slot` is a valid pointer to an uninitialized `= struct dma_fence`. + // `lock_ptr` is a pointer to the spinlock of the fence co= ntext, which is shared + // among all the fences. This can't become a UAF because e= ach fence takes a + // reference of the fence context. + unsafe { bindings::dma_fence_init(slot, &Self::OPS, Opaque= ::cast_into(lock_ptr), context, seqno) }; + }), + data <- data, + signalling: false, + signalling_cookie: false, + fctx: fctx, + }); + + let b =3D KBox::pin_init(fence, GFP_KERNEL)?; + + // SAFETY: We don't move the contents of `b` anywhere here. After = unwrapping it, ARef will + // take care of preventing memory moves. + let rawptr =3D KBox::into_raw(unsafe { Pin::into_inner_unchecked(b= ) }); + + // SAFETY: `rawptr` was created validly above. + let aref =3D unsafe { ARef::from_raw(NonNull::new_unchecked(rawptr= )) }; + + Ok(aref) + } + + /// Mark the beginning of a DmaFence signalling critical section. Shou= ld be called once a fence + /// gets published. + /// + /// The signalling critical section is marked as finished automaticall= y once the fence signals. + pub fn begin_signalling(&mut self) { + // FIXME: this needs to be mutable, obviously, but we can't borrow= mutably. *sigh* + self.signalling =3D true; + // TODO: Should we warn if a user tries to do this several times f= or the same + // fence? And should we ignore the request if the fence is already= signalled? + + // SAFETY: `dma_fence_begin_signalling()` works on global lockdep = data and calling it is + // always safe. + self.signalling_cookie =3D unsafe { bindings::dma_fence_begin_sign= alling() }; + } + + const fn ops_create() -> bindings::dma_fence_ops { + // SAFETY: Zeroing out memory on the stack is always safe. + let mut ops: bindings::dma_fence_ops =3D unsafe { core::mem::zeroe= d() }; + + ops.get_driver_name =3D Some(Self::get_driver_name); + ops.get_timeline_name =3D Some(Self::get_timeline_name); + ops.release =3D Some(Self::release); + + ops + } + + extern "C" fn get_driver_name(_ptr: *mut bindings::dma_fence) -> *cons= t c_char { + T::DRIVER_NAME.as_ptr() + } + + extern "C" fn get_timeline_name(_ptr: *mut bindings::dma_fence) -> *co= nst c_char { + T::TIMELINE_NAME.as_ptr() + } + + /// The release function called by the C backend once the refcount dro= ps to 0. We use this to + /// drop the Rust dma-fence, too. Since [`DmaFence`] implements [`Alwa= ysRefCounted`], this is + /// perfectly safe and a convenient way to concile the two release mec= hanisms of C and Rust. + unsafe extern "C" fn release(ptr: *mut bindings::dma_fence) { + let ptr =3D Opaque::cast_from(ptr); + + // SAFETY: The constructor guarantees that `ptr` is always the inn= er fence of a DmaFence. + let fence =3D unsafe { crate::container_of!(ptr, Self, inner) }.ca= st_mut(); + + // SAFETY: See above. Also, the release callback will only be call= ed once, when the + // refcount drops to 0, and when that happens the fence is by defi= nition still valid. + unsafe { drop_in_place(fence) }; + } + + /// Signal the fence. This will invoke all registered callbacks. + pub fn signal(&self) -> Result { + // SAFETY: `self` is refcounted. + let ret =3D unsafe { bindings::dma_fence_signal(self.as_raw()) }; + if ret !=3D 0 { + return Err(Error::from_errno(ret)); + } + + if self.signalling { + // SAFETY: `dma_fence_end_signalling()` works on global lockde= p data. The only + // parameter is a boolean passed by value. + unsafe { bindings::dma_fence_end_signalling(self.signalling_co= okie) }; + } + + Ok(()) + } + + /// Register a callback on a [`DmaFence`]. The callback will be invoke= d in the fence's + /// signalling path, i.e., critical section. + /// + /// Consumes `data`. `data` is passed back to the implemented callback= function when the fence + /// gets signalled. + pub fn register_callback(&self, data: imp= l PinInit) -> Result { + let cb =3D DmaFenceCb::new(data)?; + let ptr =3D cb.into_foreign() as *mut DmaFenceCb; + // SAFETY: `ptr` was created validly directly above. + let inner_cb =3D unsafe { (*ptr).inner.get() }; + + // SAFETY: `self.as_raw()` is valid because `self` is refcounted, = `inner_cb` was created + // validly above and was turned into a ForeignOwnable, so it won't= be dropped. `callback` + // has static life time. + let ret =3D unsafe { + bindings::dma_fence_add_callback( + self.as_raw(), + inner_cb, + Some(DmaFenceCb::::callback), + ) + }; + if ret !=3D 0 { + return Err(Error::from_errno(ret)); + } + Ok(()) + } + + fn as_raw(&self) -> *mut bindings::dma_fence { + self.inner.get() + } +} --=20 2.49.0