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