# 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 },
}
}
```