# Intrusive-style inheritance for DMA fence This document outlines how to implement inheritance for DMA fence using the intrusive pattern. The intrusive pattern was originally introduced for the workqueue abstraction. When using the intrusive-style inheritance pattern, there are two important pieces: The Rust struct containing the DMA fence, and the smart pointer used to store it. Let's first discuss the smart pointer. There are two choices: * Define a trait for a pointer usable with a dma fence. * Require that a specific smart pointer is used. In the case of dma fences, the lifetime of the object needs to be managed by the dma fence field, so you will only be able to use smart pointers that understand that. Therefore, it is reasonable to require a specific smart pointer. We will be using `ARef`. Generally, is-a relationships will usually require a specific smart pointer, and has-a relationships will usually use a trait to allow many different pointer types. ## Basic layout When using the intrusive style, the dma fence will be embedded as a field in the user struct. For example: ```rust struct MyFence { fence: DmaFence<Self>, other_data: Foo, } ``` Then, the user will need to implement a trait along these lines: ```rust trait DmaFenceOps: IsDmaFence { const USE_64BIT_SEQNO: bool; fn get_driver_name(&self) -> &CStr; fn get_timeline_name(&self) -> &CStr; fn enable_signaling(&self) -> bool; fn signaled(&self) -> bool; fn fence_value_str(&self, str: &[u8]); fn timeline_value_str(&self, str: &[u8]); } ``` Furthermore, we need a separate unsafe trait, which will be implemented by a macro. ```rust /// # Safety /// /// * The value of `OFFSET` must be correct. /// * The `raw_get_fence` and `fence_container_of` methods must /// be implemented as a simple pointer offset by the value `OFFSET`. /// * The implementation of `AlwaysRefcounted` must be implemented by /// calling `dma_fence_get` and `dma_fence_put` on the fence. unsafe trait IsDmaFence: AlwaysRefCounted { const OFFSET: usize; unsafe fn raw_get_fence(ptr: *mut Self) -> *mut DmaFence<Self>; unsafe fn fence_container_of(ptr: *mut DmaFence<Self>) -> *mut Self { unsafe { (ptr as *mut u8).sub(Self::OFFSET) as *mut Self } } } ``` The unsafe trait ensures that pointers to our custom dma fence behaves correctly, both from the Rust side and the C side. Usually, you will provide a macro for implementing the unsafe trait automatically. In this case, the macro should also implement `AlwaysRefCounted`. This macro ensures that dma fences can be implemented without the user writing any unsafe code. ## The macro The macro should be very similar to the one in the workqueue. It should output code that looks like this: ```rust unsafe impl IsDmaFence for MyFence { const OFFSET: usize = ::core::mem::offset_of!(Self, fence); unsafe fn raw_get_fence(ptr: *mut Self) -> *mut DmaFence<Self> { unsafe { ::core::ptr::addr_of_mut!((*ptr).fence) } } } unsafe impl AlwaysRefCounted for MyFence { fn inc_ref(&self) { unsafe { bindings::dma_fence_get(Self::raw_get_fence(self)) } } fn dec_ref(obj: NonNull<Self>) { unsafe { bindings::dma_fence_put(Self::raw_get_fence(obj)) } } } ``` Note that `raw_get_fence` is implemented in the macro to ensure that the field has the right type. If the field is not a `DmaFence<Self>`, then this will trigger a compilation failure. This check is necessary for the macro to be safe. ## The DmaFence struct The custom struct will hold a field of `DmaFence`. We need to define that type. ```rust struct DmaFence<T> { fence: Opaque<bindings::dma_fence>, _phantom: PhantomData<T>, } ``` The purpose of the generic parameter `T` is to tell the fence how it should construct the `dma_fence_ops` vtable. You will want two methods: ```rust impl<T: DmaFenceOps> DmaFence<T> { const OPS: dma_fence_ops = create_ops::<T>(); pub fn new() -> impl PinInit<DmaFence<T>> { kernel::init::pin_init_from_closure(move |slot| { bindings::dma_fence_init(slot, &OPS, ...) }) } pub fn alloc(init: impl PinInit<T>) -> Result<ARef<T>> { let ptr = Box::into_raw(Box::<T>::new_uninit()?); match init.pin_init(ptr) { Ok(()) => ARef::from_raw(ptr.cast()), Err(err) => { drop(Box::from_raw(ptr)); Err(Err) }, } } } ``` ## The dma_fence_ops vtable To create a vtable that works with an intrusive-style dma fence, we can follow this approach: ```rust const fn create_ops<T: DmaFenceOps>() -> dma_fence_ops { dma_fence_ops { use_64bit_seqno: T::USE_64BIT_SEQNO, get_driver_name: get_driver_name::<T>, get_timeline_name: get_timeline_name::<T>, ..., release: release::<T>, } } ``` To implement normal methods, you can do this: ```rust extern "C" fn get_driver_name<T: DmaFenceOps>(fence: *mut bindings::dma_fence) { let rust_struct = T::fence_container_of(fence); let c_str = unsafe { (*rust_struct).get_driver_name() }; c_str.as_ptr() } ``` ### The release method The release method is a bit special. Instead of hooking in to a method on the `DmaFenceOps` trait, we instead hook into the `Drop` trait. Furthermore, we must ensure that the values are dropped after an rcu grace period. ```rust extern "C" release<T: DmaFenceOps>(fence: *mut bindings::dma_fence) { let rcu_head_ptr = core::ptr::addr_of_mut!((*fence).rcu); bindings::call_rcu(rcu_head_ptr, real_release); } extern "C" real_release<T: DmaFenceOps>(rcu: *mut bindings::rcu_head) { let fence = container_of!(rcu, bindings::dma_fence, rcu); let rust_fence = T::fence_container_of(fence); // core::ptr::drop_in_place(rust_fence); // bindings::kfree(rust_fence); drop(Box::from_raw(ptr)); } ``` As an optimization, you don't need to use `call_rcu` if the type has no destructor. ```rust extern "C" release_no_drop<T: DmaFenceOps>(fence: *mut bindings::dma_fence) { let rust_fence = T::fence_container_of(fence); bindings::kfree_rcu(rust_fence); } const fn create_ops<T: DmaFenceOps>() -> dma_fence_ops { dma_fence_ops { use_64bit_seqno: T::USE_64BIT_SEQNO, get_driver_name: get_driver_name::<T>, get_timeline_name: get_timeline_name::<T>, ..., release: if needs_drop::<T>() { release } else { release_no_drop }, } } ```