---
# System prepended metadata

title: Custom Circular DMA Handler

---

## Design goals
- User doesn't need to know anything about htif/tcif flags
- Runs user code from the interrupt at top priority, doesn't require executor overhead or an additional interrupt executor
- Fully static, no fnptrs
- No cost if you don't use it
- No unsafe user code required
- Overruns are always detectable, and can be handled without panic
- Circular writes (i.e. DAC) should work too with a few changes

## Downsides
- It is possible to run user code at dma irq priority. Does this break any assumptions?
- UB if the future is mem::forgotten (same as any other dma future)

## Example user code
```rust
// CaptureBuffer is part of the business logic of my oscilloscope app.
// It is a circular buffer that looks for a rising edge

struct MyIsr {
    capture_buffer: CaptureBuffer,
}

// `run` is called inside the dma interrupt
impl Isr for MyIsr {
    type Word = u16;

    fn run(&mut self, buffer: Result<DmaBuffer<Self::Word>, OverrunError>) -> ControlFlow<()> {
        let buffer = buffer.unwrap();
        
        // this isn't required, but it might lead to better codegen
        assert_eq!(buffer.len(), DMA_BUFFER_SIZE / 2);
        
        for sample in buffer {
            let sample: u16 = sample.load();
            // returns `Break` when the capture buffer is ready to be displayed
            self.capture_buffer.write(sample)?;
            // TODO: we aren't calling `finish` when we break
            // fortunatly, overruns are still checked automatically when the DmaBuffer is dropped
        }
        
        // this does the same thing as dropping the DmaBuffer
        buffer.finish().unwrap();
        
        ControlFlow::Continue(())
    }
}

// this declares the interrupt handler, as well as a static `IsrState<MyIsr>`
bind_custom_interrupt!(MyIsr, DMA1_CH1);

let capture_buffer = CaptureBuffer::new();

let mut my_isr = MyIsr{ capture_buffer };

let mut dma_buffer = [0; DMA_BUFFER_SIZE];

// this checks at compile time to ensure that we registered MyIsr for DMA1_CH1
adc1.circ_dma_read(&mut p.PA0, &mut my_isr, &mut dma_buffer &mut p.DMA1_CH1).await;

display_buffer(&my_isr.capture_buffer);
```

## Possible changes
- Should `run` return a `Poll`? What if the isr was a future? The waker could pend the interrupt

```rust
// This function runs in a dma executor at dma interrupt priority.
// A dma executor is special because it can only run one task, which is known at compile time.
// Each dma channel can have it's own dma executor with a different task.
// This task can be joined from a different executor, and it can return a value.
#[dma_task(DMA1_CH1)]
async fn my_isr(capture_buffer: &mut CaptureBuffer, cancel_token: &CancelToken) -> Result<(), MyError> {
    // This part is a bit sketchy, it needs to use the waker to look up the buffer address and size.
    // This function would panic if it isn't called from the dma executor.
    let buffer_provider = buffer_provider().await;
    
    // This could be a try block.
    async fn handle_capture(capture_buffer: &mut CaptureBuffer, buffer_provider: BufferProvider) -> Result<(), MyError> {
        loop {
            let sample_buffer = buffer_provider.next().await.map_err(|_| MyError::Overrun)?;
            let samples = sample_buffer.iter().map(|sample| sample.load()); // impl Iterator<Item = u16>
            let is_done = capture_buffer.process_samples(samples);
            // If you forget to call finish, the buffer will still
            // check for overflow when it is dropped, and panic if there is.
            sample_buffer.finish().map_err(|_| MyError::Overrun)?;
            if is_done {
                return Ok(())
            }
        }
    }
    
    let capture_future = handle_capture(capture_buffer, buffer_provider);
    let cancel_future = cancel_token.map(|_| Err(MyError::Cancelled));
    
    (capture_future, cancel_future).race().await
}

// This runs in a normal executor.
async fn my_task(adc1: &mut ADC1, pa0: &mut PA0, dma1_ch1: &mut DMA1_CH1, display: &mut Display, cancel_token: &CancelToken) -> Result<(), MyError> {
    let mut capture_buffer = CaptureBuffer::new();

    let mut dma_buffer = [0; DMA_BUFFER_SIZE];

    adc1.circ_dma_read(pa0, my_isr(&mut capture_buffer, cancel_token), &mut dma_buffer, dma1_ch1).await?;
    
    display.display_buffer(&capture_buffer);
    
    Ok(())
}
```

- Cancellation
    - Cooperative cancelation is supported at htif and tcif
    - It is currently not possible to cancel before a half is completed
    - Dropping the future currently panics
- How to support different handlers at different times on the same channel?
    - Put all handlers in an enum, register that as the handler. Generate that enum with a macro? Can we make that enum invisible to the user, or do they need to construct a variant?
- Store the user state directly in the static state?
    - User can still impl Isr for &mut MyIsr
- Return a value from the transfer?
- Use a static array for the buffer
    - Pros
        - User doesn't need to create the buffer
        - We don't need to store the len
        - The compiler can do better unrolling (the same thing should be possible with an assert)
    - Cons
        - The user might want to put the state and the buffer in different memory regions

    - Options
        - User allocated slice
        - User allocated array
        - Statically allocated array

|| `&mut [T]` | `&mut [T; N]` | `[T; N]` |
|-|-|-|-|
|User doesn't need to create|❌|❌|✅|
|Don't need to store len|❌|✅|✅|
|Good unrolling|❔*|✅|✅|
|Allows dynamic size|✅|❌|❌|
|Allows buffer in different segment|✅|✅|❌|
|Less indirection|❌|❌|✅|
|Can use use buffer elsewhere|✅|✅|❌|

\* User can do `assert_eq(buffer.len(), BUFFER_SIZE/2)`. Also, if the len is maybeuninit, maybe the compiler can still figure it out at compile time?

## Pseudo implementation
```rust
struct OverrunError;

#[repr(transparent)]
struct BufferWord<Word: DmaWord>(Word::Atomic);

impl<Word: DmaWord> BufferWord<Word> {
    fn load(&self) -> Word {
        self.0.load(Ordering::Relaxed)
    }
}

pub struct DmaBuffer<'a, Word: DmaWord> {
    buffer: &'a [BufferWord<Word>]
    channel: AnyChannel,
}

impl<Word: DmaWord> Deref for DmaBuffer<Word> {
    type Target = [BufferWord<Word>]
    fn deref(&self) -> &Self::Target {
        self.buffer
    }
}

impl<Word: DmaWord> Drop for DmaBuffer<Word> {
    fn drop(&mut self) {
        self.check_overrun().unwrap();
    }
}

impl<Word: DmaWord> DmaBuffer<Word> {
    fn check_overrun(&self) -> Result<(), OverrunError> {
        let flags = self.channel.get_flags();
        if flags.has_htif() || flags.has_tcif() {
            Err(OverrunError)
        } else {
            Ok(())
        }
    }
    
    pub fn finish(self) -> Result<(), OverrunError> {
        let this = ManuallyDrop::new(self);
        this.check_overrun()
    }
}

// This needs to be pub so that the macro can delcare a static of it.
pub struct IsrState<UserState: Isr> {
    user_state: AtomicPtr<UserState>,
    buffer: UnsafeCell<*mut UserState::Word>;
    len: UnsafeCell<u16>;
    waker: AtomicWaker,
}

impl IsrState {
    pub const fn new() -> Self {        
        Self{
            user_state: AtomicPtr::new(null_mut()),
            buffer: UnsafeCell::new(null_mut()),
            len: UnsafeCell::new(0),
            waker: AtomicWaker::new(),
        }
    }
    
    unsafe fn on_isr(&self, ch: Channel) {
        let user_state = {
            let user_state = self.user_state.load(Ordering::Aquire);
            if user_state.is_null() {
                return;
            }
            &*user_state
        };
        
        let len = *state.len.get();
        let half_len = len / 2;
        
        let offset = match ch.check_and_clear_flags() {
            Neither => return;
            First => Ok(0);
            Second => Ok(half_len);
            Both => Err(OverrunError);
        };
        
        
        let buffer = offset.map(|offset| {
            let buffer: *mut UserState::Word  = *state.buffer.get();
            let buffer = buffer.add(offset);
            let buffer = slice::from_raw_parts(buffer.cast(), half_len);
            DmaBuffer::new(buffer, ch);
        });
        
        let cf = user_state.run(buffer);
        
        if cf.is_break() {
            ch.disable();
            Self::state_ptr().store(ptr::null_mut(), Ordering:: Release);
            self.waker.wake();
        }
    }
}

struct CircTransfer<'b, 'u, UserState: Isr> {
    state: &'static IsrState<UserState>,
    _phantom: PhantomData<(&'b mut [UserState::Word], &'u mut UserState)>,
}

impl<'b, 'u, UserState: Isr> CircTransfer<'b, 'u, UserState> {
    fn new(state: &'static IsrState, channel: AnyChannel, buffer: &'a mut [UserState::Word], user_state: &'a mut UserState) -> Self {
        let len = buffer.len();
        // make divisible by two, rounding down
        let len = (len / 2) * 2;
        let buffer = buffer.as_ptr();
        
        *state.buffer.get() = buffer;
        *state.len.get() = len;
        
        state.user_state.store(user_state, Ordering::Release);
        
        channel.start_circular_dma(buffer, len);
        
        Self{ state, _phantom: PhantomData }
    }
}

impl<'b, 'u, UserState: Isr> Future for CircTransfer<'b, 'u, UserState> {
    type Output = ();
    
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.state.waker.register(cx.waker())
        
        if self.state.user_state.load(Ordering::Aquire).is_null() {
            Poll::Ready(())
        } else {
            Poll::Pending
        }
    }
}

impl<'b, 'u, UserState: Isr> Drop for CircTransfer<'b, 'u, UserState> {
    fn drop(&mut self) {
        todo!();
    }
}
```
