blob: 8473f247883b9c44ce32e23b7e8bc2bd5d6081ef [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
use pw_atomic::{AtomicAdd, AtomicLoad, AtomicSub, AtomicZero};
use pw_status::Result;
use pw_time_core::Instant;
use crate::scheduler::{WaitQueueLock, WaitQueueLockGuard, WaitType};
use crate::{Kernel, SchedulerState, SpinLockGuard};
/// Configuration for the behavior of an [`Event`].
#[derive(Eq, PartialEq)]
pub enum EventConfig {
/// When an [`Event`] is signaled, the first waiter to observe that signal
/// automatically clears the signaled state, returning the `Event` to the
/// un-signaled state.
AutoReset,
/// When an [`Event`] is signaled, it remains signaled indefinitely until
/// [`unsignal`] is called, which returns the `Event` to the un-signaled
/// state.
///
/// [`unsignal`]: EventSignaler::unsignal
ManualReset,
}
struct EventState {
signaled: bool,
}
/// A basic synchronization primitive allowing threads to block on an event
/// happening.
///
/// An `Event` starts in an un-signaled state. Threads can use [`wait`] or
/// [`wait_until`] to block until the state has changed to signaled. The signal
/// state is controlled by an [`EventSignaler`] via the [`signal`] and [`unsignal`]
/// methods.
///
/// Depending on what [`EventConfig`] is used, [`signal`] may either set the
/// signal permanently, or it may be automatically cleared once a single waiter
/// has been un-blocked by it.
///
/// # Panics
///
/// Panics on drop if there are [`EventSignaler`]s referencing this object.
///
/// [`wait`]: Event::wait
/// [`wait_until`]: Event::wait_until
/// [`signal`]: EventSignaler::signal
/// [`unsignal`]: EventSignaler::unsignal
pub struct Event<K: Kernel> {
config: EventConfig,
state: WaitQueueLock<K, EventState>,
signalers: K::AtomicUsize,
}
/// An signaler for an [`Event`]
///
/// [`EventSignaler`] is returned from [`Event::new()`] and is used to signal
/// the referenced Event. It can be cloned and the referenced [`Event`] will
/// panic if it is dropped when any [`EventSignaler`]s still reference it.
pub struct EventSignaler<K: Kernel> {
event: NonNull<Event<K>>,
}
// SAFETY: Event will panic if there are outstanding signalers referencing it
// guaranteeing that the event pointer will always be valid.
unsafe impl<K: Kernel> Send for EventSignaler<K> {}
unsafe impl<K: Kernel> Sync for EventSignaler<K> {}
impl<K: Kernel> Clone for EventSignaler<K> {
fn clone(&self) -> Self {
// Safety: Event will panic if there are outstanding signalers referencing it.
unsafe {
(*self.event.as_ptr())
.signalers
// TODO: rationalize ordering
.fetch_add(1, Ordering::SeqCst)
};
Self { event: self.event }
}
}
impl<K: Kernel> Drop for EventSignaler<K> {
fn drop(&mut self) {
// SAFETY: Event will panic if there are outstanding signalers referencing it.
unsafe {
self.event
.as_ref()
.signalers
// TODO: rationalize ordering
.fetch_sub(1, Ordering::SeqCst)
};
}
}
impl<K: Kernel> EventSignaler<K> {
/// Sets the `Event`'s state to signaled.
///
/// # Interrupt context
///
/// This method *is* safe to call in an interrupt context.
pub fn signal(&self) {
// SAFETY: Event will panic if there are outstanding signalers referencing it.
unsafe { self.event.as_ref().signal() };
}
/// Sets the `Event`'s state to signaled.
///
/// Behaves like `signal()` but allows passing in a scheduler lock. This
/// exists because the scheduler uses Events to handle signaling and joining
/// of process and thread termination. It is intentionally only visible
/// to the kernel crate as no external crates can take the scheduler lock.
///
/// # Interrupt context
///
/// This method *is* safe to call in an interrupt context.
pub(crate) fn signal_locked<'a>(
&self,
sched: SpinLockGuard<'a, K, SchedulerState<K>>,
) -> SpinLockGuard<'a, K, SchedulerState<K>> {
// SAFETY: Event will panic if there are outstanding signalers referencing it.
let event = unsafe { self.event.as_ref() };
let state = event.state.inherit_sched_lock(sched);
let state = event.signal_locked(state);
state.into_sched()
}
/// Sets the `Event`'s state to un-signaled.
///
/// # Interrupt context
///
/// This method *is* safe to call in an interrupt context.
pub fn unsignal(&self) {
// SAFETY: Event will panic if there are outstanding signalers referencing it.
unsafe { self.event.as_ref().unsignal() };
}
}
unsafe impl<K: Kernel> Sync for Event<K> {}
unsafe impl<K: Kernel> Send for Event<K> {}
impl<K: Kernel> Event<K> {
/// Constructs a new `Event` with the given configuration.
///
#[must_use]
pub const fn new(kernel: K, config: EventConfig) -> Self {
Self {
config,
state: WaitQueueLock::new(kernel, EventState { signaled: false }),
signalers: K::AtomicUsize::ZERO,
}
}
/// Returns an [`EventSignaler`] for signaling an `Event`.
///
#[must_use]
pub fn get_signaler(&self) -> EventSignaler<K> {
self.signalers.fetch_add(1, Ordering::SeqCst);
EventSignaler {
event: NonNull::from_ref(self),
}
}
/// Waits until the `Event` is in the signaled state.
///
/// If the event's configuration is [`AutoReset`], the thread which is
/// un-blocked by a signal also clears that signal, resetting its value to
/// un-signaled.
///
/// [`AutoReset`]: EventConfig::AutoReset
///
/// # Interrupt context
///
/// This method is *not* safe to call in an interrupt context.
pub fn wait(&self) -> Result<()> {
let mut state = self.state.lock();
if !state.signaled {
let (_, ret) = state.wait(WaitType::Interruptible);
return ret;
} else if self.config == EventConfig::AutoReset {
state.signaled = false;
}
Ok(())
}
/// Waits until the `Event` is in the signaled state or the `deadline` is
/// reached, whichever happens first.
///
/// If the event's configuration is [`EventConfig::AutoReset`], the thread
/// which is un-blocked by a signal also clears that signal, resetting its
/// value to un-signaled.
///
/// # Interrupt context
///
/// This method is *not* safe to call in an interrupt context.
pub fn wait_until(&self, deadline: Instant<K::Clock>) -> Result<()> {
let mut state = self.state.lock();
if !state.signaled {
let (_state, result) = state.wait_until(WaitType::Interruptible, deadline);
return result;
} else if self.config == EventConfig::AutoReset {
state.signaled = false;
}
Ok(())
}
fn signal(&self) {
self.signal_locked(self.state.lock());
}
fn signal_locked<'a>(
&self,
mut state: WaitQueueLockGuard<'a, K, EventState>,
) -> WaitQueueLockGuard<'a, K, EventState> {
if !state.signaled {
state = match self.config {
EventConfig::AutoReset => {
let (mut state, result) = state.wake_one();
if result == crate::scheduler::WakeResult::QueueEmpty {
state.signaled = true;
}
state
}
EventConfig::ManualReset => {
state.signaled = true;
state.wake_all()
}
};
}
state
}
fn unsignal(&self) {
self.state.lock().signaled = false;
}
}
impl<K: Kernel> Drop for Event<K> {
fn drop(&mut self) {
// TODO: rationalize ordering
let num_signalers = self.signalers.load(Ordering::SeqCst);
if num_signalers > 0 {
pw_assert::panic!(
"Event droped with {} active signalers",
num_signalers as usize
)
}
}
}