## 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!(); } } ```