1use core::future::poll_fn;
26use core::task::Poll;
27
28use embassy_futures::select::{Either3, select3};
29use embassy_sync::blocking_mutex::raw::RawMutex;
30use umsh_radio_loraphy::{Channels, RxFrame, TxRequest};
31
32pub async fn radio_mux<M, const RX: usize, const TX: usize>(
38 real: &'static Channels<M, RX, TX>,
39 clients: &'static [&'static Channels<M, RX, TX>],
40) -> !
41where
42 M: RawMutex,
43{
44 let mut in_flight: Option<usize> = None;
46 let mut arbitration_start: usize = 0;
49
50 loop {
51 let tx_done = async {
54 match in_flight {
55 Some(_) => real.tx_done.wait().await,
56 None => core::future::pending().await,
57 }
58 };
59 let next_tx = async {
62 match in_flight {
63 None => receive_any_tx(clients, arbitration_start).await,
64 Some(_) => core::future::pending().await,
65 }
66 };
67
68 match select3(real.rx.receive(), tx_done, next_tx).await {
69 Either3::First(frame) => {
70 for client in clients {
71 let copy = RxFrame {
72 data: frame.data.clone(),
73 info: frame.info,
74 };
75 if client.rx.try_send(copy).is_ok() {
76 client.rx_waker.wake();
77 }
78 }
79 }
80 Either3::Second(result) => {
81 if let Some(owner) = in_flight.take() {
82 clients[owner].tx_done.signal(result);
83 }
84 }
85 Either3::Third((who, request)) => {
86 let _ = clients[who].tx_done.try_take();
92 real.tx.send(request).await;
93 in_flight = Some(who);
94 arbitration_start = (who + 1) % clients.len();
95 }
96 }
97 }
98}
99
100async fn receive_any_tx<M, const RX: usize, const TX: usize>(
103 clients: &[&Channels<M, RX, TX>],
104 start: usize,
105) -> (usize, TxRequest)
106where
107 M: RawMutex,
108{
109 poll_fn(move |cx| {
110 for client in clients {
113 let _ = client.tx.poll_ready_to_receive(cx);
114 }
115 for offset in 0..clients.len() {
116 let index = (start + offset) % clients.len();
117 if let Ok(request) = clients[index].tx.try_receive() {
118 return Poll::Ready((index, request));
119 }
120 }
121 Poll::Pending
122 })
123 .await
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use core::future::Future;
130 use core::sync::atomic::{AtomicUsize, Ordering};
131 use core::task::{Context, Waker};
132 use embassy_futures::select::{Either, select};
133 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
134 use lora_phy::mod_params::RadioError;
135 use std::sync::Arc;
136 use umsh_hal::{CadPolicy, RxInfo, Snr, TxError};
137
138 type TestCh = Channels<NoopRawMutex, 4, 2>;
139
140 fn block_on<F: Future>(future: F) -> F::Output {
141 let mut future = core::pin::pin!(future);
142 let waker = Waker::noop();
143 let mut context = Context::from_waker(&waker);
144 loop {
145 if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
146 return output;
147 }
148 }
149 }
150
151 fn channels() -> &'static TestCh {
152 Box::leak(Box::new(Channels::new()))
153 }
154
155 fn run<F: Future>(
158 real: &'static TestCh,
159 clients: &'static [&'static TestCh],
160 scenario: F,
161 ) -> F::Output {
162 block_on(async {
163 match select(radio_mux(real, clients), scenario).await {
164 Either::First(_) => unreachable!("mux never returns"),
165 Either::Second(output) => output,
166 }
167 })
168 }
169
170 fn rx_frame(tag: u8) -> RxFrame {
171 let mut data = heapless::Vec::new();
172 data.push(tag).unwrap();
173 RxFrame {
174 data,
175 info: RxInfo {
176 len: 1,
177 rssi: -40,
178 snr: Snr::from_decibels(5),
179 lqi: None,
180 },
181 }
182 }
183
184 fn tx_request(tag: u8) -> TxRequest {
185 let mut data = heapless::Vec::new();
186 data.push(tag).unwrap();
187 TxRequest {
188 data,
189 power_dbm: None,
190 cad: CadPolicy::Skip,
191 }
192 }
193
194 #[test]
195 fn tx_completions_route_to_the_requesting_client() {
196 let real = channels();
197 let a = channels();
198 let b = channels();
199 let clients: &'static [&'static TestCh] = Box::leak(Box::new([a, b]));
200
201 run(real, clients, async {
202 a.tx.send(tx_request(0xA1)).await;
203 b.tx.send(tx_request(0xB1)).await;
204
205 let granted = real.tx.receive().await;
208 assert_eq!(granted.data.as_slice(), &[0xA1]);
209 assert!(
210 real.tx.try_receive().is_err(),
211 "B granted while A in flight"
212 );
213
214 real.tx_done
215 .signal(Err(TxError::Io(RadioError::TransmitTimeout)));
216 assert!(a.tx_done.wait().await.is_err());
217 assert!(b.tx_done.try_take().is_none(), "completion leaked to B");
218
219 let granted = real.tx.receive().await;
220 assert_eq!(granted.data.as_slice(), &[0xB1]);
221 real.tx_done.signal(Ok(()));
222 assert!(b.tx_done.wait().await.is_ok());
223 assert!(a.tx_done.try_take().is_none(), "completion leaked to A");
224 });
225 }
226
227 #[test]
228 fn tx_grants_round_robin_under_contention() {
229 let real = channels();
230 let a = channels();
231 let b = channels();
232 let clients: &'static [&'static TestCh] = Box::leak(Box::new([a, b]));
233
234 run(real, clients, async {
235 a.tx.send(tx_request(0xA1)).await;
236 a.tx.send(tx_request(0xA2)).await;
237 b.tx.send(tx_request(0xB1)).await;
238
239 let mut order = std::vec::Vec::new();
241 for _ in 0..3 {
242 let granted = real.tx.receive().await;
243 order.push(granted.data[0]);
244 real.tx_done.signal(Ok(()));
245 let owner = if order.last() == Some(&0xB1) { b } else { a };
247 assert!(owner.tx_done.wait().await.is_ok());
248 }
249 assert_eq!(order, [0xA1, 0xB1, 0xA2]);
250 });
251 }
252
253 #[test]
254 fn rx_fans_out_to_every_client() {
255 let real = channels();
256 let a = channels();
257 let b = channels();
258 let clients: &'static [&'static TestCh] = Box::leak(Box::new([a, b]));
259
260 struct CountingWake(AtomicUsize);
261 impl std::task::Wake for CountingWake {
262 fn wake(self: Arc<Self>) {
263 self.0.fetch_add(1, Ordering::SeqCst);
264 }
265 }
266
267 run(real, clients, async {
268 let wakes = Arc::new(CountingWake(AtomicUsize::new(0)));
271 b.rx_waker.register(&Waker::from(wakes.clone()));
272
273 real.rx.send(rx_frame(0x11)).await;
274
275 let got_a = a.rx.receive().await;
276 assert_eq!(got_a.data.as_slice(), &[0x11]);
277 assert_eq!(got_a.info.rssi, -40);
278 let got_b = b.rx.receive().await;
279 assert_eq!(got_b.data.as_slice(), &[0x11]);
280 assert!(wakes.0.load(Ordering::SeqCst) > 0, "rx_waker not woken");
281 });
282 }
283
284 #[test]
285 fn rx_overflow_drops_only_the_full_client() {
286 let real = channels();
287 let a = channels();
288 let b = channels();
289 let clients: &'static [&'static TestCh] = Box::leak(Box::new([a, b]));
290
291 run(real, clients, async {
292 for tag in 0..6u8 {
295 real.rx.send(rx_frame(tag)).await;
296 let got = b.rx.receive().await;
297 assert_eq!(got.data.as_slice(), &[tag]);
298 }
299 for expected in 0..4u8 {
300 let got = a.rx.try_receive().expect("frame dropped early");
301 assert_eq!(got.data.as_slice(), &[expected]);
302 }
303 assert!(a.rx.try_receive().is_err(), "overflow frame not dropped");
304 });
305 }
306
307 #[test]
308 fn rx_continues_while_tx_in_flight() {
309 let real = channels();
310 let a = channels();
311 let b = channels();
312 let clients: &'static [&'static TestCh] = Box::leak(Box::new([a, b]));
313
314 run(real, clients, async {
315 a.tx.send(tx_request(0xA1)).await;
316 let granted = real.tx.receive().await;
317 assert_eq!(granted.data.as_slice(), &[0xA1]);
318
319 real.rx.send(rx_frame(0x22)).await;
321 assert_eq!(b.rx.receive().await.data.as_slice(), &[0x22]);
322 assert_eq!(a.rx.receive().await.data.as_slice(), &[0x22]);
323
324 real.tx_done.signal(Ok(()));
325 assert!(a.tx_done.wait().await.is_ok());
326 });
327 }
328
329 #[test]
330 fn stale_latched_completion_is_cleared_at_grant() {
331 let real = channels();
332 let a = channels();
333 let clients: &'static [&'static TestCh] = Box::leak(Box::new([a]));
334
335 run(real, clients, async {
336 a.tx_done
338 .signal(Err(TxError::Io(RadioError::TransmitTimeout)));
339
340 a.tx.send(tx_request(0xA1)).await;
341 let granted = real.tx.receive().await;
342 assert_eq!(granted.data.as_slice(), &[0xA1]);
343 assert!(a.tx_done.try_take().is_none(), "stale completion survived");
346
347 real.tx_done.signal(Ok(()));
348 assert!(a.tx_done.wait().await.is_ok());
349 });
350 }
351}