osal_rs/posix/event_group.rs
1/***************************************************************************
2 *
3 * osal-rs
4 * Copyright (C) 2026 Antonio Salsi <passy.linux@zresa.it>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
18 *
19 ***************************************************************************/
20
21//! Event group synchronization primitives for POSIX.
22//!
23//! [`EventGroup`] implements FreeRTOS-style event groups - a shared bit
24//! field that any thread can set/clear, and that other threads can block on
25//! until some combination of bits becomes set - on top of a
26//! `pthread_mutex_t` + `pthread_cond_t` pair, since plain POSIX/pthreads has
27//! no primitive that matches this shape directly.
28//!
29//! # Examples
30//!
31//! ```
32//! use osal_rs::os::*;
33//! use osal_rs::os::types::EventBits;
34//!
35//! const EVENT_A: EventBits = 1 << 0;
36//! const EVENT_B: EventBits = 1 << 1;
37//!
38//! let events = EventGroup::new().unwrap();
39//! events.set(EVENT_A | EVENT_B);
40//!
41//! let bits = events.wait(EVENT_A | EVENT_B, 100);
42//! assert_eq!(bits & (EVENT_A | EVENT_B), EVENT_A | EVENT_B);
43//! ```
44
45use core::cell::UnsafeCell;
46use core::ffi::c_long;
47use core::fmt::{Debug, Display, Formatter};
48use core::ops::Deref;
49use core::time::Duration;
50
51use crate::posix::config::TICK_PERIOD_MS;
52use crate::posix::ffi::{
53 CLOCK_MONOTONIC, ETIMEDOUT, PTHREAD_PRIO_INHERIT, clock_gettime, pthread_cond_broadcast, pthread_cond_destroy, pthread_cond_init, pthread_cond_t, pthread_cond_timedwait, pthread_cond_wait,
54 pthread_condattr_init, pthread_condattr_setclock, pthread_condattr_t, pthread_mutex_destroy, pthread_mutex_init, pthread_mutex_lock, pthread_mutex_t, pthread_mutex_trylock, pthread_mutex_unlock,
55 pthread_mutexattr_init, pthread_mutexattr_setprotocol, pthread_mutexattr_t, timespec,
56};
57use crate::posix::types::{ClockMonotonicHandle, EventBits, EventGroupHandle, TickType};
58use crate::traits::{EventGroupFn, ToTick};
59use crate::utils::{Error, Result};
60
61/// Computes an absolute deadline `timeout` from now on the monotonic clock,
62/// for `pthread_cond_timedwait` (this module's condition variable is created
63/// with `pthread_condattr_setclock(CLOCK_MONOTONIC)`, so its `abstime` is
64/// measured against that same clock).
65fn monotonic_deadline(timeout: Duration) -> timespec {
66 let mut now = timespec::default();
67 unsafe {
68 clock_gettime(CLOCK_MONOTONIC, &mut now);
69 }
70
71 let mut tv_sec = now.tv_sec + timeout.as_secs() as c_long;
72 let mut tv_nsec = now.tv_nsec + timeout.subsec_nanos() as c_long;
73
74 if tv_nsec >= 1_000_000_000 {
75 tv_sec += 1;
76 tv_nsec -= 1_000_000_000;
77 }
78
79 timespec { tv_sec, tv_nsec }
80}
81
82/// POSIX event group: a shared, thread-safe bit field with blocking waits.
83///
84/// See the module-level docs above for a full example and rationale.
85pub struct EventGroup(UnsafeCell<EventGroupHandle>, UnsafeCell<EventBits>);
86
87unsafe impl Send for EventGroup {}
88unsafe impl Sync for EventGroup {}
89
90impl EventGroup {
91 /// Largest usable bit mask: the top byte of [`EventBits`] is reserved
92 /// for bookkeeping (mirroring FreeRTOS, which reserves its own event
93 /// group bits the same way), so only the lower bits may be used as
94 /// application flags.
95 ///
96 /// # Examples
97 ///
98 /// ```
99 /// use osal_rs::os::EventGroup;
100 /// use osal_rs::os::types::EventBits;
101 ///
102 /// // A normal flag bit always falls within the usable mask...
103 /// let flag: EventBits = 1 << 3;
104 /// assert_eq!(EventGroup::MAX_MASK & flag, flag);
105 ///
106 /// // ...but the reserved top byte does not.
107 /// let reserved_bit: EventBits = !EventGroup::MAX_MASK;
108 /// assert_eq!(EventGroup::MAX_MASK & reserved_bit, 0);
109 /// ```
110 pub const MAX_MASK: EventBits = EventBits::MAX >> 8;
111
112 /// Blocks like [`EventGroup::wait`], but accepts any [`ToTick`] timeout
113 /// (e.g. a [`core::time::Duration`]) instead of a raw tick count.
114 ///
115 /// # Examples
116 ///
117 /// ```
118 /// use osal_rs::os::*;
119 /// use core::time::Duration;
120 ///
121 /// let events = EventGroup::new().unwrap();
122 /// events.set(1);
123 ///
124 /// let bits = events.wait_with_to_tick(1, Duration::from_millis(50));
125 /// assert_eq!(bits & 1, 1);
126 /// ```
127 #[inline]
128 pub fn wait_with_to_tick(&self, mask: EventBits, timeout_ticks: impl ToTick) -> EventBits {
129 self.wait(mask, timeout_ticks.to_ticks())
130 }
131
132 /// Creates a new, empty event group (all bits clear).
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use osal_rs::os::*;
138 ///
139 /// let events = EventGroup::new().unwrap();
140 /// assert_eq!(events.get(), 0);
141 /// ```
142 pub fn new() -> Result<Self> {
143
144 let mut mutex: pthread_mutex_t = Default::default();
145 let mut mutex_attr: pthread_mutexattr_t = Default::default();
146 let mut cond: pthread_cond_t = Default::default();
147 let mut cond_attr: pthread_condattr_t = Default::default();
148
149
150 unsafe {
151 // Bind the condvar to CLOCK_MONOTONIC so its absolute timeouts line
152 // up with the clock `monotonic_deadline` uses to build them.
153 pthread_condattr_init(&mut cond_attr);
154 pthread_condattr_setclock (&mut cond_attr, CLOCK_MONOTONIC);
155 pthread_cond_init (&mut cond, &cond_attr);
156 // Priority inheritance: a low-priority holder that blocks a
157 // higher-priority waiter gets temporarily boosted, avoiding
158 // priority inversion (same protocol as posix::mutex::RawMutex).
159 pthread_mutexattr_init (&mut mutex_attr);
160 pthread_mutexattr_setprotocol (&mut mutex_attr, PTHREAD_PRIO_INHERIT);
161 pthread_mutex_init (&mut mutex, &mutex_attr);
162
163 }
164
165 Ok(Self(UnsafeCell::new(ClockMonotonicHandle(mutex, cond)), UnsafeCell::new(0)))
166 }
167
168 // Raw pointers into the `UnsafeCell`s, needed because the pthread FFI
169 // takes `*mut`. `bits_ptr()` must only be dereferenced while holding
170 // `mutex_ptr()` locked, except for the racy peek in `get_from_isr()`.
171 fn mutex_ptr(&self) -> *mut pthread_mutex_t {
172 unsafe { &raw mut (*self.0.get()).0 }
173 }
174
175 fn cond_ptr(&self) -> *mut pthread_cond_t {
176 unsafe { &raw mut (*self.0.get()).1 }
177 }
178
179 fn bits_ptr(&self) -> *mut EventBits {
180 self.1.get()
181 }
182}
183
184impl EventGroupFn for EventGroup {
185 /// Returns `true` if this event group is never-initialized-or-already-deleted.
186 ///
187 /// Unlike [`crate::os::SemaphoreFn::is_null`], the bits themselves are not
188 /// part of this check: an event group legitimately sits at `0` bits
189 /// whenever nothing has been set yet, so that can't be used to detect
190 /// deletion.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// use osal_rs::os::*;
196 ///
197 /// let mut events = EventGroup::new().unwrap();
198 /// assert!(!events.is_null());
199 ///
200 /// events.delete();
201 /// assert!(events.is_null());
202 /// ```
203 fn is_null(&self) -> bool {
204 unsafe { (*self.0.get()).is_empty() }
205 }
206
207 /// Sets `bits` in the group (OR'd into the current value) and wakes any
208 /// thread blocked in [`EventGroup::wait`] whose mask may now be
209 /// satisfied. Returns the resulting bits after the update.
210 ///
211 /// # Examples
212 ///
213 /// ```
214 /// use osal_rs::os::*;
215 ///
216 /// let events = EventGroup::new().unwrap();
217 /// let bits = events.set(0b101);
218 /// assert_eq!(bits, 0b101);
219 ///
220 /// let bits = events.set(0b010);
221 /// assert_eq!(bits, 0b111);
222 /// ```
223 fn set(&self, bits: EventBits) -> EventBits {
224 if self.is_null() {
225 return 0;
226 }
227
228 unsafe {
229 pthread_mutex_lock(self.mutex_ptr());
230 }
231
232 let new_bits = unsafe {
233 *self.bits_ptr() |= bits;
234 *self.bits_ptr()
235 };
236
237 unsafe {
238 // Broadcast, not signal: any thread parked in `wait()`'s loop
239 // could be the one whose mask is now satisfied, so all of them
240 // are woken to re-check under the mutex; losers just go back to
241 // waiting instead of missing the wake-up.
242 pthread_cond_broadcast(self.cond_ptr());
243 pthread_mutex_unlock(self.mutex_ptr());
244 }
245
246 new_bits
247 }
248
249 /// ISR-safe variant of [`EventGroup::set`]. POSIX has no interrupt
250 /// context of its own, so this never blocks (`trylock` instead of
251 /// `lock`); it fails with [`Error::QueueFull`] if the mutex happens to be
252 /// contended rather than waiting for it.
253 ///
254 /// # Examples
255 ///
256 /// ```
257 /// use osal_rs::os::*;
258 ///
259 /// let events = EventGroup::new().unwrap();
260 /// events.set_from_isr(0b1).unwrap();
261 /// assert_eq!(events.get(), 0b1);
262 /// ```
263 fn set_from_isr(&self, bits: EventBits) -> Result<()> {
264 if self.is_null() {
265 return Err(Error::NullPtr);
266 }
267
268 // pthreads has no ISR context of its own; `trylock` keeps this
269 // non-blocking, as `_from_isr` callers expect (mirrors
270 // `Semaphore::signal_from_isr`). If the mutex is contended, bail out
271 // rather than blocking the "interrupt".
272 if unsafe { pthread_mutex_trylock(self.mutex_ptr()) } != 0 {
273 return Err(Error::QueueFull);
274 }
275
276 unsafe {
277 *self.bits_ptr() |= bits;
278 pthread_cond_broadcast(self.cond_ptr());
279 pthread_mutex_unlock(self.mutex_ptr());
280 }
281
282 Ok(())
283 }
284
285 /// Returns the currently set bits, without waiting for any of them.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// use osal_rs::os::*;
291 ///
292 /// let events = EventGroup::new().unwrap();
293 /// assert_eq!(events.get(), 0);
294 ///
295 /// events.set(0b11);
296 /// assert_eq!(events.get(), 0b11);
297 /// ```
298 fn get(&self) -> EventBits {
299 if self.is_null() {
300 return 0;
301 }
302
303 unsafe {
304 pthread_mutex_lock(self.mutex_ptr());
305 let bits = *self.bits_ptr();
306 pthread_mutex_unlock(self.mutex_ptr());
307 bits
308 }
309 }
310
311 /// ISR-safe variant of [`EventGroup::get`]. Falls back to a racy,
312 /// unlocked read if the mutex happens to be contended, rather than
313 /// blocking the "interrupt".
314 ///
315 /// # Examples
316 ///
317 /// ```
318 /// use osal_rs::os::*;
319 ///
320 /// let events = EventGroup::new().unwrap();
321 /// events.set(0b111);
322 /// assert_eq!(events.get_from_isr(), 0b111);
323 /// ```
324 fn get_from_isr(&self) -> EventBits {
325 if self.is_null() {
326 return 0;
327 }
328
329 if unsafe { pthread_mutex_trylock(self.mutex_ptr()) } != 0 {
330 // Contended: fall back to a racy read rather than blocking the
331 // "interrupt".
332 return unsafe { *self.bits_ptr() };
333 }
334
335 unsafe {
336 let bits = *self.bits_ptr();
337 pthread_mutex_unlock(self.mutex_ptr());
338 bits
339 }
340 }
341
342 /// Clears `bits` in the group and returns the value the bits held
343 /// *before* clearing.
344 ///
345 /// # Examples
346 ///
347 /// ```
348 /// use osal_rs::os::*;
349 ///
350 /// let events = EventGroup::new().unwrap();
351 /// events.set(0b111);
352 ///
353 /// let previous = events.clear(0b010);
354 /// assert_eq!(previous, 0b111);
355 /// assert_eq!(events.get(), 0b101);
356 /// ```
357 fn clear(&self, bits: EventBits) -> EventBits {
358 if self.is_null() {
359 return 0;
360 }
361
362 unsafe {
363 pthread_mutex_lock(self.mutex_ptr());
364 }
365
366 let previous_bits = unsafe {
367 let previous = *self.bits_ptr();
368 *self.bits_ptr() &= !bits;
369 previous
370 };
371
372 unsafe {
373 pthread_mutex_unlock(self.mutex_ptr());
374 }
375
376 previous_bits
377 }
378
379 /// ISR-safe variant of [`EventGroup::clear`]. Fails with
380 /// [`Error::QueueFull`] instead of blocking if the mutex is contended.
381 ///
382 /// # Examples
383 ///
384 /// ```
385 /// use osal_rs::os::*;
386 ///
387 /// let events = EventGroup::new().unwrap();
388 /// events.set(0b11);
389 /// events.clear_from_isr(0b01).unwrap();
390 /// assert_eq!(events.get(), 0b10);
391 /// ```
392 fn clear_from_isr(&self, bits: EventBits) -> Result<()> {
393 if self.is_null() {
394 return Err(Error::NullPtr);
395 }
396
397 if unsafe { pthread_mutex_trylock(self.mutex_ptr()) } != 0 {
398 return Err(Error::QueueFull);
399 }
400
401 unsafe {
402 *self.bits_ptr() &= !bits;
403 pthread_mutex_unlock(self.mutex_ptr());
404 }
405
406 Ok(())
407 }
408
409 /// Blocks until every bit in `mask` is set, or `timeout_ticks` elapses
410 /// (pass [`TickType::MAX`] to wait forever), whichever comes first.
411 /// Always returns the bits actually observed, whether or not they
412 /// satisfy `mask` - check the return value to tell a timeout apart from
413 /// success.
414 ///
415 /// # Examples
416 ///
417 /// ```
418 /// use osal_rs::os::*;
419 ///
420 /// let events = EventGroup::new().unwrap();
421 /// events.set(0b01);
422 ///
423 /// // Only bit 0 is set, so waiting on bit 1 too times out...
424 /// let bits = events.wait(0b11, 10);
425 /// assert_ne!(bits & 0b11, 0b11);
426 ///
427 /// // ...but waiting on just the bit that's already set succeeds immediately.
428 /// let bits = events.wait(0b01, 10);
429 /// assert_eq!(bits & 0b01, 0b01);
430 /// ```
431 fn wait(&self, mask: EventBits, timeout_ticks: TickType) -> EventBits {
432 if self.is_null() {
433 return 0;
434 }
435
436 unsafe {
437 pthread_mutex_lock(self.mutex_ptr());
438 }
439
440 // The mask is re-checked in a loop after every wake-up: both
441 // `pthread_cond_wait`/`pthread_cond_timedwait` may return spuriously,
442 // and a wake caused by an unrelated `set()` may not satisfy this
443 // waiter's mask yet.
444 let result = if timeout_ticks == TickType::MAX {
445 // TickType::MAX is the "wait forever" sentinel: no deadline,
446 // block until every bit in `mask` is set.
447 loop {
448 let bits = unsafe { *self.bits_ptr() };
449 if bits & mask == mask {
450 break bits;
451 }
452 unsafe {
453 pthread_cond_wait(self.cond_ptr(), self.mutex_ptr());
454 }
455 }
456 } else {
457 // Bounded wait: the deadline is computed once up front, then
458 // every re-wake races against that same fixed point in time
459 // (rather than restarting a fresh relative timeout each loop).
460 let deadline = monotonic_deadline(Duration::from_millis((timeout_ticks as u64).saturating_mul(TICK_PERIOD_MS)));
461
462 loop {
463 let bits = unsafe { *self.bits_ptr() };
464 if bits & mask == mask {
465 break bits;
466 }
467 if unsafe { pthread_cond_timedwait(self.cond_ptr(), self.mutex_ptr(), &deadline) } == ETIMEDOUT {
468 break unsafe { *self.bits_ptr() };
469 }
470 }
471 };
472
473 unsafe {
474 pthread_mutex_unlock(self.mutex_ptr());
475 }
476
477 result
478 }
479
480 /// Destroys the underlying pthread objects and resets this event group
481 /// to its "null" state. Safe to call more than once - a second call is a
482 /// no-op - and called automatically on [`Drop`] if not called explicitly.
483 ///
484 /// # Examples
485 ///
486 /// ```
487 /// use osal_rs::os::*;
488 ///
489 /// let mut events = EventGroup::new().unwrap();
490 /// events.delete();
491 /// assert!(events.is_null());
492 ///
493 /// events.delete(); // no-op, does not panic
494 /// ```
495 fn delete(&mut self) {
496 if self.is_null() {
497 return;
498 }
499
500 unsafe {
501 pthread_mutex_destroy(self.mutex_ptr());
502 pthread_cond_destroy(self.cond_ptr());
503 }
504
505 // Reset to the "null" state so a second `delete()` call (e.g. from
506 // `Drop` after an explicit `delete()`) is a no-op rather than
507 // destroying the same pthread objects twice.
508 *self.0.get_mut() = EventGroupHandle::default();
509 *self.1.get_mut() = 0;
510 }
511}
512
513impl Drop for EventGroup {
514 fn drop(&mut self) {
515 if self.is_null() {
516 return;
517 }
518 // Safety net for callers that don't call `delete()` explicitly.
519 self.delete();
520 }
521}
522
523impl Deref for EventGroup {
524 type Target = EventGroupHandle;
525
526 fn deref(&self) -> &Self::Target {
527 // Read-only escape hatch to the raw (mutex, condvar) handle.
528 unsafe { &*self.0.get() }
529 }
530}
531
532impl Debug for EventGroup {
533 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
534 f.debug_struct("EventGroup")
535 .field("handle", unsafe { &*self.0.get() })
536 .field("bits", unsafe { &*self.1.get() })
537 .finish()
538 }
539}
540
541impl Display for EventGroup {
542 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
543 write!(f, "EventGroup {{ handle: {:?}, bits: {:#X} }}", unsafe { &*self.0.get() }, unsafe { *self.1.get() })
544 }
545}