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, true, 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, true, Duration::from_millis(50));
125	/// assert_eq!(bits & 1, 1);
126	/// ```
127	#[inline]
128	pub fn wait_with_to_tick(&self, mask: EventBits, wait_for_all_bits: bool, timeout_ticks: impl ToTick) -> EventBits {
129		self.wait(mask, wait_for_all_bits, 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 `mask` is satisfied - every bit in it set when
410	/// `wait_for_all_bits` is `true` (AND), or any single bit in it set when
411	/// `false` (OR) - or `timeout_ticks` elapses (pass [`TickType::MAX`] to
412	/// wait forever), whichever comes first. Always returns the bits
413	/// actually observed, whether or not they satisfy `mask` - check the
414	/// return value to tell a timeout apart from success.
415	///
416	/// # Examples
417	///
418	/// ```
419	/// use osal_rs::os::*;
420	///
421	/// let events = EventGroup::new().unwrap();
422	/// events.set(0b01);
423	///
424	/// // Only bit 0 is set, so AND-waiting on bit 1 too times out...
425	/// let bits = events.wait(0b11, true, 10);
426	/// assert_ne!(bits & 0b11, 0b11);
427	///
428	/// // ...but OR-waiting on the same mask succeeds immediately, since
429	/// // bit 0 alone is enough.
430	/// let bits = events.wait(0b11, false, 10);
431	/// assert_eq!(bits & 0b01, 0b01);
432	/// ```
433	fn wait(&self, mask: EventBits, wait_for_all_bits: bool, timeout_ticks: TickType) -> EventBits {
434		if self.is_null() {
435			return 0;
436		}
437
438		let satisfied = |bits: EventBits| if wait_for_all_bits { bits & mask == mask } else { bits & mask != 0 };
439
440		unsafe {
441			pthread_mutex_lock(self.mutex_ptr());
442		}
443
444		// The mask is re-checked in a loop after every wake-up: both
445		// `pthread_cond_wait`/`pthread_cond_timedwait` may return spuriously,
446		// and a wake caused by an unrelated `set()` may not satisfy this
447		// waiter's mask yet.
448		let result = if timeout_ticks == TickType::MAX {
449			// TickType::MAX is the "wait forever" sentinel: no deadline,
450			// block until `mask` is satisfied.
451			loop {
452				let bits = unsafe { *self.bits_ptr() };
453				if satisfied(bits) {
454					break bits;
455				}
456				unsafe {
457					pthread_cond_wait(self.cond_ptr(), self.mutex_ptr());
458				}
459			}
460		} else {
461			// Bounded wait: the deadline is computed once up front, then
462			// every re-wake races against that same fixed point in time
463			// (rather than restarting a fresh relative timeout each loop).
464			let deadline = monotonic_deadline(Duration::from_millis((timeout_ticks as u64).saturating_mul(TICK_PERIOD_MS)));
465
466			loop {
467				let bits = unsafe { *self.bits_ptr() };
468				if satisfied(bits) {
469					break bits;
470				}
471				if unsafe { pthread_cond_timedwait(self.cond_ptr(), self.mutex_ptr(), &deadline) } == ETIMEDOUT {
472					break unsafe { *self.bits_ptr() };
473				}
474			}
475		};
476
477		unsafe {
478			pthread_mutex_unlock(self.mutex_ptr());
479		}
480
481		result
482	}
483
484	/// Destroys the underlying pthread objects and resets this event group
485	/// to its "null" state. Safe to call more than once - a second call is a
486	/// no-op - and called automatically on [`Drop`] if not called explicitly.
487	///
488	/// # Examples
489	///
490	/// ```
491	/// use osal_rs::os::*;
492	///
493	/// let mut events = EventGroup::new().unwrap();
494	/// events.delete();
495	/// assert!(events.is_null());
496	///
497	/// events.delete(); // no-op, does not panic
498	/// ```
499	fn delete(&mut self) {
500		if self.is_null() {
501			return;
502		}
503
504		unsafe {
505			pthread_mutex_destroy(self.mutex_ptr());
506			pthread_cond_destroy(self.cond_ptr());
507		}
508
509		// Reset to the "null" state so a second `delete()` call (e.g. from
510		// `Drop` after an explicit `delete()`) is a no-op rather than
511		// destroying the same pthread objects twice.
512		*self.0.get_mut() = EventGroupHandle::default();
513		*self.1.get_mut() = 0;
514	}
515}
516
517impl Drop for EventGroup {
518	fn drop(&mut self) {
519		if self.is_null() {
520			return;
521		}
522		// Safety net for callers that don't call `delete()` explicitly.
523		self.delete();
524	}
525}
526
527impl Deref for EventGroup {
528	type Target = EventGroupHandle;
529
530	fn deref(&self) -> &Self::Target {
531		// Read-only escape hatch to the raw (mutex, condvar) handle.
532		unsafe { &*self.0.get() }
533	}
534}
535
536impl Debug for EventGroup {
537	fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
538		f.debug_struct("EventGroup")
539			.field("handle", unsafe { &*self.0.get() })
540			.field("bits", unsafe { &*self.1.get() })
541			.finish()
542	}
543}
544
545impl Display for EventGroup {
546	fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
547		write!(f, "EventGroup {{ handle: {:?}, bits: {:#X} }}", unsafe { &*self.0.get() }, unsafe { *self.1.get() })
548	}
549}