Skip to main content

embedded_gui/
completion.rs

1//! ISR-safe DMA completion signaling for async and sync present paths.
2//!
3//! [`CompletionSlot`] is runtime-agnostic: call [`CompletionSlot::signal`] from a
4//! DMA ISR, poll with [`CompletionSlot::is_signaled`] in RTIC/bare-metal tasks, or
5//! await via [`WaitTransfer`] / [`WaitTransferFuture`] under Embassy.
6
7use core::cell::Cell;
8use core::future::Future;
9use core::pin::Pin;
10use core::sync::atomic::{AtomicBool, Ordering};
11use core::task::{Context, Poll, Waker};
12
13use critical_section::Mutex;
14use embedded_graphics_core::pixelcolor::Rgb565;
15use embedded_graphics_framebuf::{FrameBuf, backends::DMACapableFrameBufferBackend};
16
17use crate::display_backend::{AsyncDmaTransfer, DmaTransfer};
18
19/// One-shot completion flag with optional async waker notification.
20///
21/// Reset before starting DMA, signal from the transfer-complete ISR.
22pub struct CompletionSlot {
23    signaled: AtomicBool,
24    waker: Mutex<Cell<Option<Waker>>>,
25}
26
27impl CompletionSlot {
28    /// Create a cleared completion slot.
29    pub const fn new() -> Self {
30        Self {
31            signaled: AtomicBool::new(false),
32            waker: Mutex::new(Cell::new(None)),
33        }
34    }
35
36    /// Clear the slot before kicking off a new DMA transfer.
37    pub fn reset(&self) {
38        self.signaled.store(false, Ordering::Release);
39        critical_section::with(|cs| {
40            self.waker.borrow(cs).set(None);
41        });
42    }
43
44    /// Mark complete and wake any registered async task.
45    ///
46    /// Safe to call from interrupt context.
47    pub fn signal(&self) {
48        self.signaled.store(true, Ordering::Release);
49        critical_section::with(|cs| {
50            if let Some(waker) = self.waker.borrow(cs).take() {
51                waker.wake();
52            }
53        });
54    }
55}
56
57impl Default for CompletionSlot {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl CompletionSlot {
64    /// Returns `true` after [`signal`](Self::signal) until the next [`reset`](Self::reset).
65    pub fn is_signaled(&self) -> bool {
66        self.signaled.load(Ordering::Acquire)
67    }
68
69    /// Poll for completion, registering `cx`'s waker when still pending.
70    pub fn poll_wait(&self, cx: &mut Context<'_>) -> Poll<()> {
71        if self.is_signaled() {
72            return Poll::Ready(());
73        }
74        critical_section::with(|cs| {
75            self.waker.borrow(cs).set(Some(cx.waker().clone()));
76        });
77        if self.is_signaled() {
78            Poll::Ready(())
79        } else {
80            Poll::Pending
81        }
82    }
83}
84
85/// Reference [`AsyncDmaTransfer`] token backed by a [`CompletionSlot`].
86pub struct WaitTransfer<FB>
87where
88    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
89{
90    framebuffer: Option<FrameBuf<Rgb565, FB>>,
91    completion: &'static CompletionSlot,
92}
93
94impl<FB> WaitTransfer<FB>
95where
96    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
97{
98    /// Build a transfer token; `completion` must outlive all in-flight DMA ops.
99    pub fn new(framebuffer: FrameBuf<Rgb565, FB>, completion: &'static CompletionSlot) -> Self {
100        completion.reset();
101        Self {
102            framebuffer: Some(framebuffer),
103            completion,
104        }
105    }
106
107    /// The completion slot wired to this transfer (for ISR handlers).
108    pub const fn completion(&self) -> &'static CompletionSlot {
109        self.completion
110    }
111}
112
113impl<FB> DmaTransfer for WaitTransfer<FB>
114where
115    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
116{
117    type Buffer = FrameBuf<Rgb565, FB>;
118
119    fn is_done(&self) -> bool {
120        self.completion.is_signaled()
121    }
122
123    fn wait(self) -> Self::Buffer {
124        while !self.completion.is_signaled() {
125            core::hint::spin_loop();
126        }
127        self.framebuffer
128            .expect("WaitTransfer polled after completion")
129    }
130}
131
132/// Future returned by [`WaitTransfer::wait_async`].
133pub struct WaitTransferFuture<FB>
134where
135    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
136{
137    inner: Option<WaitTransfer<FB>>,
138}
139
140impl<FB> AsyncDmaTransfer for WaitTransfer<FB>
141where
142    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
143{
144    type WaitFuture = WaitTransferFuture<FB>;
145
146    fn wait_async(self) -> Self::WaitFuture {
147        WaitTransferFuture { inner: Some(self) }
148    }
149}
150
151impl<FB> Future for WaitTransferFuture<FB>
152where
153    FB: DMACapableFrameBufferBackend<Color = Rgb565>,
154{
155    type Output = FrameBuf<Rgb565, FB>;
156
157    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
158        let this = unsafe { self.get_unchecked_mut() };
159        let inner = this
160            .inner
161            .as_mut()
162            .expect("WaitTransferFuture polled after completion");
163        match inner.completion.poll_wait(cx) {
164            Poll::Ready(()) => Poll::Ready(
165                this.inner
166                    .take()
167                    .expect("WaitTransferFuture polled after completion")
168                    .framebuffer
169                    .expect("WaitTransferFuture polled after completion"),
170            ),
171            Poll::Pending => Poll::Pending,
172        }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    extern crate std;
179    use super::*;
180    use embedded_graphics_core::pixelcolor::RgbColor;
181    use embedded_graphics_framebuf::backends::{EndianCorrectedBuffer, EndianCorrection};
182    use std::sync::Arc;
183    use std::task::{Context, Poll, Wake, Waker};
184
185    type TestBackend = EndianCorrectedBuffer<'static, Rgb565>;
186
187    struct TestWake(Arc<AtomicBool>);
188
189    impl Wake for TestWake {
190        fn wake(self: Arc<Self>) {
191            self.0.store(true, Ordering::Release);
192        }
193        fn wake_by_ref(self: &Arc<Self>) {
194            self.0.store(true, Ordering::Release);
195        }
196    }
197
198    fn make_fb() -> FrameBuf<Rgb565, TestBackend> {
199        let data: &'static mut [Rgb565] = std::vec![Rgb565::BLACK; 4].leak();
200        FrameBuf::new(
201            EndianCorrectedBuffer::new(data, EndianCorrection::ToLittleEndian),
202            2,
203            2,
204        )
205    }
206
207    #[test]
208    fn completion_slot_sync_wait() {
209        static DONE: CompletionSlot = CompletionSlot::new();
210        let fb = make_fb();
211        let xfer = WaitTransfer::new(fb, &DONE);
212        assert!(!xfer.is_done());
213        DONE.signal();
214        assert!(xfer.is_done());
215        let _ = xfer.wait();
216    }
217
218    #[test]
219    fn completion_slot_async_wake() {
220        static DONE: CompletionSlot = CompletionSlot::new();
221        let fb = make_fb();
222        let xfer = WaitTransfer::new(fb, &DONE);
223        let mut fut = xfer.wait_async();
224        let woken = Arc::new(AtomicBool::new(false));
225        let waker = Waker::from(Arc::new(TestWake(Arc::clone(&woken))));
226        let mut cx = Context::from_waker(&waker);
227        assert!(matches!(Pin::new(&mut fut).poll(&mut cx), Poll::Pending));
228        DONE.signal();
229        assert!(matches!(Pin::new(&mut fut).poll(&mut cx), Poll::Ready(_)));
230    }
231}