Skip to main content

embedded_draw_target/
completion.rs

1//! ISR-safe DMA completion signaling for async and sync present paths.
2
3use core::cell::Cell;
4use core::future::Future;
5use core::pin::Pin;
6use core::sync::atomic::{AtomicBool, Ordering};
7use core::task::{Context, Poll, Waker};
8
9use critical_section::Mutex;
10use embedded_graphics_core::pixelcolor::Rgb565;
11use embedded_graphics_framebuf::{FrameBuf, backends::DMACapableFrameBufferBackend};
12
13/// Trait representing a DMA transfer token.
14pub trait DmaTransfer {
15    /// The buffer type returned when the transfer finishes.
16    type Buffer;
17
18    /// Returns `true` if the DMA transfer has completed.
19    fn is_done(&self) -> bool;
20
21    /// Block until the transfer completes, recovering ownership of the buffer.
22    fn wait(self) -> Self::Buffer;
23}
24
25/// Trait representing an async DMA transfer token.
26pub trait AsyncDmaTransfer: DmaTransfer {
27    /// The future type returned by [`wait_async`](Self::wait_async).
28    type WaitFuture: Future<Output = Self::Buffer>;
29
30    /// Return a future that resolves when the DMA transfer completes.
31    fn wait_async(self) -> Self::WaitFuture;
32}
33
34/// One-shot completion flag with optional async waker notification.
35pub struct CompletionSlot {
36    signaled: AtomicBool,
37    waker: Mutex<Cell<Option<Waker>>>,
38}
39
40impl CompletionSlot {
41    /// Create a cleared completion slot.
42    pub const fn new() -> Self {
43        Self {
44            signaled: AtomicBool::new(false),
45            waker: Mutex::new(Cell::new(None)),
46        }
47    }
48
49    /// Clear the slot before kicking off a new DMA transfer.
50    pub fn reset(&self) {
51        self.signaled.store(false, Ordering::Release);
52        critical_section::with(|cs| {
53            self.waker.borrow(cs).set(None);
54        });
55    }
56
57    /// Mark complete and wake any registered async task.
58    pub fn signal(&self) {
59        self.signaled.store(true, Ordering::Release);
60        critical_section::with(|cs| {
61            if let Some(waker) = self.waker.borrow(cs).take() {
62                waker.wake();
63            }
64        });
65    }
66
67    /// Returns `true` after [`signal`](Self::signal) until the next [`reset`](Self::reset).
68    pub fn is_signaled(&self) -> bool {
69        self.signaled.load(Ordering::Acquire)
70    }
71
72    /// Poll for completion, registering `cx`'s waker when still pending.
73    pub fn poll_wait(&self, cx: &mut Context<'_>) -> Poll<()> {
74        if self.is_signaled() {
75            return Poll::Ready(());
76        }
77        critical_section::with(|cs| {
78            self.waker.borrow(cs).set(Some(cx.waker().clone()));
79        });
80        if self.is_signaled() {
81            Poll::Ready(())
82        } else {
83            Poll::Pending
84        }
85    }
86}
87
88impl Default for CompletionSlot {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94/// Reference [`AsyncDmaTransfer`] token backed by a [`CompletionSlot`].
95pub struct WaitTransfer<FB>
96where
97    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
98{
99    framebuffer: Option<FrameBuf<Rgb565, FB>>,
100    completion: &'static CompletionSlot,
101}
102
103impl<FB> WaitTransfer<FB>
104where
105    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
106{
107    /// Build a transfer token; `completion` must outlive all in-flight DMA ops.
108    pub fn new(framebuffer: FrameBuf<Rgb565, FB>, completion: &'static CompletionSlot) -> Self {
109        completion.reset();
110        Self {
111            framebuffer: Some(framebuffer),
112            completion,
113        }
114    }
115
116    /// The completion slot wired to this transfer.
117    pub const fn completion(&self) -> &'static CompletionSlot {
118        self.completion
119    }
120}
121
122impl<FB> DmaTransfer for WaitTransfer<FB>
123where
124    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
125{
126    type Buffer = FrameBuf<Rgb565, FB>;
127
128    fn is_done(&self) -> bool {
129        self.completion.is_signaled()
130    }
131
132    fn wait(self) -> Self::Buffer {
133        while !self.completion.is_signaled() {
134            core::hint::spin_loop();
135        }
136        self.framebuffer
137            .expect("WaitTransfer polled after completion")
138    }
139}
140
141/// Future returned by [`WaitTransfer::wait_async`].
142pub struct WaitTransferFuture<FB>
143where
144    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
145{
146    inner: Option<WaitTransfer<FB>>,
147}
148
149impl<FB> AsyncDmaTransfer for WaitTransfer<FB>
150where
151    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
152{
153    type WaitFuture = WaitTransferFuture<FB>;
154
155    fn wait_async(self) -> Self::WaitFuture {
156        WaitTransferFuture { inner: Some(self) }
157    }
158}
159
160impl<FB> Future for WaitTransferFuture<FB>
161where
162    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
163{
164    type Output = FrameBuf<Rgb565, FB>;
165
166    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
167        let this = unsafe { self.get_unchecked_mut() };
168        let inner = this
169            .inner
170            .as_mut()
171            .expect("WaitTransferFuture polled after completion");
172        match inner.completion.poll_wait(cx) {
173            Poll::Ready(()) => Poll::Ready(
174                this.inner
175                    .take()
176                    .expect("WaitTransferFuture polled after completion")
177                    .framebuffer
178                    .expect("WaitTransferFuture polled after completion"),
179            ),
180            Poll::Pending => Poll::Pending,
181        }
182    }
183}