uefi_async/common/
signal.rs

1use core::cell::{RefCell, UnsafeCell};
2use core::future::Future;
3use core::pin::Pin;
4use core::sync::atomic::{AtomicBool, Ordering};
5use core::task::{Context, Poll};
6use spin::Mutex;
7
8pub mod single {
9    use super::*;
10
11    /// A Single-Core, single-producer, single-consumer (SPSC) signaling primitive for asynchronous synchronization.
12    ///
13    /// `Signal` allows one asynchronous task to notify another task and optionally pass data.
14    /// In a single-core, cooperative multitasking environment like UEFI, it acts as a
15    /// lightweight "oneshot" channel without the need for complex locking or heap-allocated wakers.
16    ///
17    /// # Design
18    /// The signal uses a `RefCell` to store data. The `wait()` method produces a `Future`
19    /// that polls the internal state until the data is provided via `signal()`.
20    ///
21    /// # Examples
22    ///
23    /// ```rust
24    /// // Defined as a static or shared resource
25    /// static ASSET_LOADED: Signal<TextureHandle> = Signal::new();
26    ///
27    /// async fn background_loader() {
28    ///     let texture = load_texture_gop("logo.bmp").await;
29    ///     // Notify the renderer that the texture is ready
30    ///     ASSET_LOADED.signal(texture);
31    /// }
32    ///
33    /// async fn renderer_task() {
34    ///     // Suspend execution until the signal is triggered
35    ///     let texture = ASSET_LOADED.wait().await;
36    ///     draw_to_screen(texture);
37    /// }
38    /// ```
39    #[derive(Debug)]
40    pub struct Signal<Data> (RefCell<Option<Data>>);
41
42    /// A `Future` that resolves when the associated [`Signal`] receives data.
43    ///
44    /// Created by the [`Signal::wait`] method.
45    #[derive(Debug)]
46    pub struct _Signal<'bemly_, Data> (&'bemly_ Signal<Data>);
47
48    impl<Data> Signal<Data> {
49        /// Creates a new, empty signal.
50        pub const fn new() -> Self { Self(RefCell::new(None)) }
51        /// Triggers the signal and stores the provided data.
52        ///
53        /// This will make the associated [`_Signal`] future resolve on its next poll.
54        pub fn signal(&self, value: Data) { *self.0.borrow_mut() = Some(value) }
55
56        /// Returns a future that resolves to the data when the signal is triggered.
57        ///
58        /// Note: The data is "consumed" (taken out) once the future resolves.
59        pub fn wait(&self) -> _Signal<'_, Data> { _Signal(self) }
60
61        /// Checks if the signal has been triggered without consuming the data.
62        pub fn is_triggered(&self) -> bool { self.0.borrow().is_some() }
63    }
64    impl<Data> Future for _Signal<'_, Data> {
65        type Output = Data;
66        /// Polls the internal state of the signal.
67        ///
68        /// Returns [`Poll::Ready`] if data is present, consuming it from the signal.
69        /// Otherwise, returns [`Poll::Pending`].
70        fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
71            match self.0.0.borrow_mut().take() {
72                Some(data) => Poll::Ready(data),
73                None => Poll::Pending,
74            }
75        }
76    }
77
78    /// A low-level, zero-overhead signaling primitive for cross-task communication.
79    /// Single-core, multi-asynchronous, Unsafe, Fast
80    ///
81    /// `UnsafeSignal` allows one asynchronous task to wait for a specific piece of data
82    /// or a "trigger" event sent by another task or an interrupt handler.
83    ///
84    /// # Safety
85    /// This type is marked as `Sync` to allow static definition. However, it uses
86    /// `UnsafeCell` internally without locking. It is designed for single-threaded
87    /// executors (like the UEFI executor) where re-entrancy is managed by the scheduler.
88    #[derive(Debug)]
89    pub struct UnsafeSignal<Data> (UnsafeCell<Option<Data>>);
90    /// A handle returned by [`UnsafeSignal::wait`], implementing [`Future`].
91    #[derive(Debug)]
92    pub struct _UnsafeSignal<'bemly_, Data> (&'bemly_ UnsafeSignal<Data>);
93    unsafe impl<Data> Sync for UnsafeSignal<Data> {}
94    impl<Data> UnsafeSignal<Data> {
95        /// Creates a new, untriggered signal.
96        pub const fn new() -> Self { Self(UnsafeCell::new(None)) }
97        /// Triggers the signal by providing a value.
98        ///
99        /// Any task currently `.await`-ing the associated [`_UnsafeSignal`] future
100        /// will be resolved on its next poll.
101        pub fn signal(&self, value: Data) { unsafe { *self.0.get() = Some(value) } }
102        /// Checks if the signal has been triggered without consuming the data.
103        pub fn is_triggered(&self) -> bool { unsafe { (*self.0.get()).is_some() } }
104        /// Returns a future that resolves when the signal is triggered.
105        ///
106        /// # Example
107        /// ```rust
108        /// static ASYNC_EVENT: UnsafeSignal<u32> = UnsafeSignal::new();
109        ///
110        /// // In Task A:
111        /// async fn wait_for_event() {
112        ///     let data = ASYNC_EVENT.wait().await;
113        ///     println!("Received: {}", data);
114        /// }
115        ///
116        /// // In Task B (or Sync context):
117        /// fn trigger_event() {
118        ///     ASYNC_EVENT.signal(42);
119        /// }
120        /// ```
121        pub fn wait(&self) -> _UnsafeSignal<'_, Data> { _UnsafeSignal(self) }
122    }
123    impl<Data> Future for _UnsafeSignal<'_, Data> {
124        type Output = Data;
125        /// Polls the signal to check if data is available.
126        ///
127        /// If the signal has been triggered, the data is taken from the signal
128        /// (resetting it to an empty state) and returned as [`Poll::Ready`].
129        fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
130            let data_ptr = self.0.0.get();
131            unsafe {
132                if (*data_ptr).is_some() {
133                    // Takes the data out, leaving None in its place.
134                    Poll::Ready((*data_ptr).take().expect("Signal data is None"))
135                } else { Poll::Pending }
136            }
137        }
138    }
139}
140
141
142pub mod multiple {
143    use super::*;
144
145    /// A cross-task/cross-core synchronization primitive for passing data between producers and consumers.
146    ///
147    /// `MultiCoreSignal` acts as a high-performance, single-slot mailbox. It allows one task
148    /// (or core) to notify another task that data is ready. It uses an `AtomicBool` for
149    /// fast "dirty" checks before acquiring a heavier `Mutex` lock, minimizing contention.
150    ///
151    /// # Design
152    /// This signal is **reset-on-read**. When the consumer `.await`s the signal and successfully
153    /// receives the data, the internal state is cleared, and subsequent polls will return
154    /// `Poll::Pending` until the producer calls `signal()` again.
155    ///
156    /// # Examples
157    ///
158    /// ```rust,no_run
159    /// static RENDER_SIGNAL: MultiCoreSignal<u64> = MultiCoreSignal::new();
160    ///
161    /// /// Producer: Runs physics calculations and notifies the renderer
162    /// async fn physics_task() {
163    ///     let mut frame_count = 0;
164    ///     loop {
165    ///         do_physics_calc();
166    ///         frame_count += 1;
167    ///
168    ///         // Update the signal with the new frame index
169    ///         RENDER_SIGNAL.signal(frame_count);
170    ///
171    ///         Yield.await;
172    ///     }
173    /// }
174    ///
175    /// /// Consumer: Waits for the signal and renders the frame
176    /// async fn render_task() {
177    ///     loop {
178    ///         // Execution suspends here until RENDER_SIGNAL.signal() is called
179    ///         let frame = RENDER_SIGNAL.wait().await;
180    ///
181    ///         draw_frame(frame);
182    ///
183    ///         // Yield once to allow other tasks to process before next wait
184    ///         Yield.await;
185    ///     }
186    /// }
187    /// ```
188    #[derive(Debug)]
189    pub struct MultiCoreSignal<Data> {
190        data: Mutex<Option<Data>>,
191        has_data: AtomicBool,
192    }
193
194    /// A handle representing the "wait" state of a [`MultiCoreSignal`].
195    ///
196    /// This struct implements [`Future`], allowing it to be integrated into
197    /// an asynchronous executor.
198    #[derive(Debug)]
199    pub struct _MultiCoreSignal<'bemly_, Data> (&'bemly_ MultiCoreSignal<Data>);
200
201    impl<Data> MultiCoreSignal<Data> {
202        /// Creates a new, empty `MultiCoreSignal`.
203        pub const fn new() -> Self { Self { data: Mutex::new(None), has_data: AtomicBool::new(false) } }
204        /// Deposits data into the signal and notifies any waiting consumers.
205        ///
206        /// If data already exists in the slot and hasn't been read yet,
207        /// it will be overwritten (Last-Write-Wins).
208        pub fn signal(&self, data: Data) {
209            *self.data.lock() = Some(data);
210            self.has_data.store(true, Ordering::Release)
211        }
212        /// Returns a future that resolves when data is signaled.
213        pub fn wait(&self) -> _MultiCoreSignal<'_, Data> { _MultiCoreSignal(self) }
214        /// Manually clears the signal data and resets the notification flag.
215        pub fn reset(&self) {
216            self.has_data.store(false, Ordering::Relaxed);
217            *self.data.lock() = None
218        }
219    }
220    impl<'bemly_, Data> Future for _MultiCoreSignal<'bemly_, Data> {
221        type Output = Data;
222        /// Attempts to consume the data from the signal.
223        ///
224        /// If the `has_data` flag is set, it attempts to lock the internal data.
225        /// If data is found, it is taken out (leaving `None`), the flag is reset,
226        /// and `Poll::Ready(data)` is returned.
227        fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
228            // Fast atomic check (Acquire) to avoid unnecessary Mutex locking
229            if !self.0.has_data.load(Ordering::Acquire) { return Poll::Pending }
230            match self.0.data.lock().take() {
231                Some(data) => {
232                    self.0.has_data.store(false, Ordering::Relaxed);
233                    Poll::Ready(data)
234                },
235                // Fallback in case another consumer/core stole the data between the check and the lock
236                None => Poll::Pending,
237            }
238        }
239    }
240}
241
242
243