Skip to main content

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}