blob: e51bf2e5e5b5d6905955371a5e1f68cd7428f3be [file] [edit]
// Copyright 2026 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 foreign_box::{ForeignRc, ForeignRcState};
use list::RandomAccessForeignList;
use pw_status::{Error, Result};
use syscall_defs::WaitReturn;
use time::Instant;
use crate::Kernel;
use crate::object::{
KernelObject, ObjectBase, ObjectWaiter, ObjectWaiterListAdapter, Signals, WaiterState,
signal_all_matching_waiters_with_return_signals_locked, wait_on_object,
};
use crate::scheduler::SchedulerState;
use crate::sync::spinlock::{SpinLock, SpinLockGuard};
list::define_adapter!(pub ObjectListAdapter<K: Kernel> => ObjectBase<K>::wait_group_link);
struct WaitGroupState<K: Kernel> {
/// List of wait group members that have one or more active signals which are
/// waited on by this wait group.
///
/// # Safety
///
/// * Access to this list must be guarded by the `WaitGroupObject` state lock.
/// * Elements in this list must be valid `ObjectBase` instances.
/// * Elements in this list can only be in one list at a time.
/// * Elements in this list must have their `wait_group` member set to a `WaitGroupMember`
/// of this wait group.
signaled_objects: list::UnsafeList<ObjectBase<K>, ObjectListAdapter<K>>,
/// List of wait group members that do not have any active signals which are
/// waited on by this wait group.
///
/// # Safety
///
/// * Access to this list must be guarded by the `WaitGroupObject` state lock.
/// * Elements in this list must be valid `ObjectBase` instances.
/// * Elements in this list can only be in one list at a time.
/// * Elements in this list must have their `wait_group` member set to a `WaitGroupMember`
/// of this wait group.
unsignaled_objects: list::UnsafeList<ObjectBase<K>, ObjectListAdapter<K>>,
waiters: RandomAccessForeignList<ObjectWaiter<K>, ObjectWaiterListAdapter<K>>,
}
impl<K: Kernel> WaitGroupState<K> {
#[must_use]
pub const fn new() -> Self {
Self {
signaled_objects: list::UnsafeList::new(),
unsignaled_objects: list::UnsafeList::new(),
waiters: RandomAccessForeignList::new(),
}
}
}
impl<K: Kernel> WaiterState<K> for WaitGroupState<K> {
fn waiters(
&mut self,
) -> &mut RandomAccessForeignList<ObjectWaiter<K>, ObjectWaiterListAdapter<K>> {
&mut self.waiters
}
}
pub struct WaitGroupMember<K: Kernel> {
signal_mask: Signals,
user_data: usize,
wait_group: ForeignRc<K::AtomicUsize, WaitGroupObject<K>>,
is_signaled: bool,
}
impl<K: Kernel> WaitGroupMember<K> {
pub(crate) fn signal_locked<'a>(
&mut self,
kernel: K,
mut sched: SpinLockGuard<'a, K, SchedulerState<K>>,
active_signals: Signals,
base: &ObjectBase<K>,
) -> SpinLockGuard<'a, K, SchedulerState<K>> {
let signaled = active_signals.intersects(self.signal_mask);
if !signaled && !self.is_signaled {
return sched;
}
let mut lock = self.wait_group.state.lock(kernel);
let state = &mut *lock;
match (signaled, self.is_signaled) {
(true, false) => Self::move_member_between_lists(
&mut state.unsignaled_objects,
&mut state.signaled_objects,
base,
),
(false, true) => Self::move_member_between_lists(
&mut state.signaled_objects,
&mut state.unsignaled_objects,
base,
),
_ => {}
}
self.is_signaled = signaled;
if signaled {
sched = signal_all_matching_waiters_with_return_signals_locked(
sched,
&mut state.waiters,
Signals::READABLE,
active_signals,
self.user_data,
);
}
sched
}
fn move_member_between_lists(
from: &mut list::UnsafeList<ObjectBase<K>, ObjectListAdapter<K>>,
to: &mut list::UnsafeList<ObjectBase<K>, ObjectListAdapter<K>>,
base: &ObjectBase<K>,
) {
// Safety:
// - The `WaitGroupObject` state lock is held.
// - The list is valid per `WaitGroupState` list preconditions.
let Some(member) = (unsafe { from.unlink_element(base.into()) }) else {
pw_assert::panic!("Wait group member not in a list");
};
// Safety:
// - The preconditions from the above unsafe block apply here.
// - Upholds the invariant that the member is in a single list by
// removing it from the first list before adding it to the second.
unsafe { to.push_front_unchecked(member) };
}
}
/// Object for waiting on a group of objects.
///
/// When any member object is signaled, the `WaitGroupObject` itself becomes `READABLE`.
pub struct WaitGroupObject<K: Kernel> {
state: SpinLock<K, WaitGroupState<K>>,
}
impl<K: Kernel> WaitGroupObject<K> {
#[must_use]
pub const fn new() -> Self {
Self {
state: SpinLock::new(WaitGroupState::new()),
}
}
}
impl<K: Kernel> KernelObject<K> for WaitGroupObject<K> {
fn object_wait(
&self,
kernel: K,
signal_mask: Signals,
deadline: Instant<K::Clock>,
) -> Result<WaitReturn> {
let state = self.state.lock(kernel);
// Error if there are no members in this wait group.
// Safety:
// - The `WaitGroupObject` state lock is held.
// - The lists are valid per `WaitGroupState` list preconditions.
if unsafe { state.signaled_objects.is_empty() && state.unsignaled_objects.is_empty() } {
return Err(Error::InvalidArgument);
}
// Skip waiting if signals are already pending on any of the objects.
// Safety:
// - The `WaitGroupObject` state lock is held.
// - The `signaled_objects` list is valid per `WaitGroupState` list preconditions.
if let Some(already_signaled) = unsafe { state.signaled_objects.peek_head() } {
// Safety:
// - The list is guarded by the `WaitGroupObject` state lock, ensuring the
// pointer is valid.
// - `ObjectBase` is `Sync`, so it is safe to create a shared reference.
let object = unsafe { &(*already_signaled.as_ptr()) };
let object_state = object.state.lock(kernel);
let Some(wait_group_member) = object_state.wait_group.as_ref() else {
pw_assert::panic!(
"Object in wait group signaled objects list is not a member of a wait group."
)
};
return Ok(WaitReturn {
user_data: wait_group_member.user_data,
pending_signals: object_state.active_signals,
});
}
wait_on_object(kernel, &self.state, state, signal_mask, deadline)
}
unsafe fn wait_group_add(
&self,
kernel: K,
object: &dyn KernelObject<K>,
signal_mask: Signals,
user_data: usize,
) -> Result<()> {
let Some(base) = object.base() else {
// Currently, the only object which doesn't have a base is WaitGroupObject,
// so we use this property to test whether `object` is a wait group or not.
// In the future we could possibly add support for wait groups being a member
// of a wait group once there is a use case for it.
return Err(Error::InvalidArgument);
};
// Objects can only ever be in one wait_group at a time.
let mut object_state = base.state.lock(kernel);
if object_state.wait_group.is_some() {
return Err(Error::ResourceExhausted);
}
let is_signaled = object_state.active_signals.intersects(signal_mask);
let mut state = self.state.lock(kernel);
// TODO: b/487292381 - Improve the ergonamics of the object table and references which
// would allow the removal of unsafe on wait_group_add().
// Safety: The caller guarantees that `self` is wrapped in a [`ForeignRcState<A, T>`].
let wait_group_rc = unsafe { ForeignRcState::create_ref_from_inner(self) };
let entry = WaitGroupMember {
signal_mask,
user_data,
wait_group: wait_group_rc,
is_signaled,
};
object_state.wait_group = Some(entry);
if is_signaled {
// Safety: list guarded by WaitGroupObject state lock.
unsafe { state.signaled_objects.push_front_unchecked(base.into()) };
} else {
// Safety: list guarded by WaitGroupObject state lock.
unsafe { state.unsignaled_objects.push_front_unchecked(base.into()) };
}
Ok(())
}
fn wait_group_remove(&self, kernel: K, object: &dyn KernelObject<K>) -> Result<()> {
let Some(base) = object.base() else {
// Currently, the only object which doesn't have a base is WaitGroupObject,
// so we use this property to test whether `object` is a wait group or not.
// In the future we could possibly add support for wait groups being a member
// of a wait group once there is a use case for it.
return Err(Error::InvalidArgument);
};
let mut object_state = base.state.lock(kernel);
let Some(ref wait_group) = object_state.wait_group else {
// Object is not in a wait group.
return Err(Error::NotFound);
};
// Check the object is in this wait group.
if !core::ptr::eq(&*wait_group.wait_group, self) {
return Err(Error::NotFound);
}
let mut state = self.state.lock(kernel);
// The object to remove can be in either the signaled or unsignaled
// state, so check which list to remove it from.
if wait_group.is_signaled {
// Safety: list guarded by WaitGroupObject state lock.
unsafe { state.signaled_objects.unlink_element_unchecked(base.into()) }
} else {
// Safety: list guarded by WaitGroupObject state lock.
unsafe {
state
.unsignaled_objects
.unlink_element_unchecked(base.into())
}
}
object_state.wait_group = None;
Ok(())
}
fn reset(&self, kernel: K) -> Result<()> {
loop {
let mut wait_group_state = self.state.lock(kernel);
// Safety: Access to lists is guarded by WaitGroupObject state lock.
let base_ptr = unsafe {
wait_group_state
.signaled_objects
.pop_head()
.or_else(|| wait_group_state.unsignaled_objects.pop_head())
};
let Some(base_ptr) = base_ptr else {
break;
};
// Drop the wait group state lock before acquiring the object base lock
// to avoid potential deadlock.
drop(wait_group_state);
// Safety: The pointer is valid as long as the object lives.
let base = unsafe { base_ptr.as_ref() };
let mut base_state = base.state.lock(kernel);
base_state.wait_group = None;
}
Ok(())
}
}