blob: d52518fa7f7711fff30fb55d8c9b9e5e897d7217 [file]
// 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 core::cell::UnsafeCell;
use core::ptr::NonNull;
#[cfg(not(feature = "user_space"))]
use foreign_box::ForeignBox;
#[cfg(feature = "user_space")]
use foreign_box::ForeignBox;
use list::ForeignList;
use pw_status::{Error, Result};
use pw_time_core::Instant;
use crate::Kernel;
use crate::scheduler::RescheduleReason;
use crate::scheduler::locks::SchedLockGuard;
use crate::scheduler::thread::{State, Thread, ThreadListAdapter, ThreadOwner};
use crate::scheduler::timer::{self, Timer};
const WAIT_QUEUE_DEBUG: bool = false;
macro_rules! wait_queue_debug {
($($args:expr),*) => {{
log_if::debug_if!(WAIT_QUEUE_DEBUG, $($args),*)
}}
}
pub struct WaitQueue<K: Kernel> {
pub(super) queue: ForeignList<Thread<K>, ThreadListAdapter<K>>,
}
unsafe impl<K: Kernel> Sync for WaitQueue<K> {}
unsafe impl<K: Kernel> Send for WaitQueue<K> {}
impl<K: Kernel> WaitQueue<K> {
#[allow(dead_code, clippy::new_without_default)]
#[must_use]
pub const fn new() -> Self {
Self {
queue: ForeignList::new(),
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub enum WaitType {
// These variant values must mirror the values in thread `State` so conversions
// from WaitType to State can be optimized out.
Interruptible = 6,
NonInterruptible = 7,
}
const _: () = assert!(WaitType::Interruptible as u8 == State::WaitingInterruptible as u8);
const _: () = assert!(WaitType::NonInterruptible as u8 == State::WaitingNonInterruptible as u8);
impl From<WaitType> for State {
fn from(value: WaitType) -> Self {
// Note: Since the values align (see above) this is optimized out.
match value {
WaitType::Interruptible => State::WaitingInterruptible,
WaitType::NonInterruptible => State::WaitingNonInterruptible,
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum WakeResult {
Woken,
QueueEmpty,
}
impl<K: Kernel> SchedLockGuard<'_, K, WaitQueue<K>> {
fn add_to_queue_and_reschedule(
mut self,
mut thread: ForeignBox<Thread<K>>,
wait_type: WaitType,
) -> Self {
let current_thread_id = thread.id();
let current_thread_name = thread.name;
let new_state = wait_type.into();
thread.state = new_state;
thread.owner = ThreadOwner::WaitQueue {
queue: NonNull::from_ref(&self),
wait_type,
};
self.queue.push_back(thread);
wait_queue_debug!(
"WaitQueue: thread '{}' ({:#010x}) rescheduling",
current_thread_name as &str,
current_thread_id as usize
);
self.block(current_thread_id, new_state)
}
// Safety:
// Caller guarantees that thread is non-null, valid, and process_timeout
// has exclusive access to `waiting_thread`.
unsafe fn process_timeout(&mut self, waiting_thread: *mut Thread<K>) -> Option<Error> {
if !(unsafe { (*waiting_thread).state }.is_waiting()) {
// Thread has already been woken.
return None;
}
let Some(mut thread) = (unsafe {
self.queue
.remove_element(NonNull::new_unchecked(waiting_thread))
}) else {
pw_assert::panic!("Thread no longer in wait queue");
};
wait_queue_debug!(
"WaitQueue: timeout for thread '{}' ({:#010x})",
thread.name as &str,
thread.id() as usize
);
thread.state = State::Ready;
self.sched_mut()
.algorithm
.schedule_thread(thread, RescheduleReason::Woken);
Some(Error::DeadlineExceeded)
}
#[allow(clippy::must_use_candidate)]
pub fn wake_one(mut self) -> (Self, WakeResult) {
let Some(mut thread) = self.queue.pop_head() else {
return (self, WakeResult::QueueEmpty);
};
wait_queue_debug!(
"WaitQueue: waking thread '{}' ({:#010x})",
thread.name as &str,
thread.id() as usize
);
thread.state = State::Ready;
self.sched_mut()
.algorithm
.schedule_thread(thread, RescheduleReason::Woken);
(
self.try_reschedule(RescheduleReason::Preempted),
WakeResult::Woken,
)
}
#[allow(clippy::return_self_not_must_use, clippy::must_use_candidate)]
pub fn wake_all(mut self) -> Self {
loop {
let result;
(self, result) = self.wake_one();
if result == WakeResult::QueueEmpty {
return self;
}
}
}
#[allow(clippy::return_self_not_must_use, clippy::must_use_candidate)]
pub fn wait(mut self, wait_type: WaitType) -> (Self, Result<()>) {
// If the current thread is terminating and this is an interruptible wait,
// return early with an error.
if self.sched().current_thread().terminating && wait_type == WaitType::Interruptible {
return (self, Err(Error::Cancelled));
}
let thread = self.sched_mut().take_current_thread();
wait_queue_debug!(
"WaitQueue: thread '{}' ({:#010x}) waiting",
thread.name as &str,
thread.id() as usize
);
self = self.add_to_queue_and_reschedule(thread, wait_type);
wait_queue_debug!(
"WaitQueue: thread '{}' ({:#010x}) resumed",
self.sched().current_thread_name() as &str,
self.sched().current_thread_id() as usize
);
// Return `Error::Cancelled` if the wait was interrupted because this
// thread was terminated while it was waiting.
if self.sched().current_thread().terminating && wait_type == WaitType::Interruptible {
(self, Err(Error::Cancelled))
} else {
(self, Ok(()))
}
}
pub fn wait_until(
mut self,
wait_type: WaitType,
deadline: Instant<K::Clock>,
) -> (Self, Result<()>) {
// If the current thread is terminating and this is an interruptible wait,
// return early with an error.
if self.sched().current_thread().terminating && wait_type == WaitType::Interruptible {
return (self, Err(Error::Cancelled));
}
let mut thread = self.sched_mut().take_current_thread();
wait_queue_debug!(
"WaitQueue: thread '{}' ({:#010x}) wait_until",
thread.name as &str,
thread.id() as usize
);
// Smuggle references to the thread and wait queue into the callback.
// Safety:
// * The thread will always be active for the lifetime of the wait as
// it can not be joined while in a WaitQueue.
// * The wait queue will outlive the callback because it will either
// fire while the thread is in the wait queue or will be the timer
// will be canceled before this function returns.
// * All access to thread_ptr and wait_queue_ptr in the callback are
// done while the wait queue lock is held.
let thread_ptr = unsafe { thread.as_mut_ptr() };
let smuggled_wait_queue = unsafe { self.smuggle() };
// Safety:
// * Only accessed while the wait_queue_lock is held;
let result: UnsafeCell<Result<()>> = UnsafeCell::new(Ok(()));
let result_ptr = result.get();
// Timeout callback will remove the thread from the wait queue and put
// it back on the run queue.
let mut callback_closure = move |_kernel, callback: ForeignBox<Timer<K>>, _now| {
// Safety: wait queue lock is valid for the lifetime of the callback.
let mut wait_queue = unsafe { smuggled_wait_queue.lock() };
// Safety: the wait queue lock protects access to the thread.
wait_queue_debug!(
"WaitQueue: timeout callback for thread '{}' ({:#010x}) (state: {})",
unsafe { (*thread_ptr).name } as &str,
(unsafe { (*thread_ptr).id() }) as usize,
unsafe { crate::scheduler::thread::to_string((*thread_ptr).state) } as &str
);
// Safety: We know that thread_ptr is valid for the life of `wait_until`
// and this callback will either be called or canceled before `wait_until`
// returns.
if let Some(error) = unsafe { wait_queue.process_timeout(thread_ptr) } {
// Safety: Acquisition of the wait queue lock at the beginning of
// the callback ensures mutual exclusion with accesses from the
// body of `wait_until`.
unsafe { result_ptr.write_volatile(Err(error)) };
}
let _ = callback.consume();
None // Don't re-arm
};
let mut callback = Timer::new(deadline, unsafe {
ForeignBox::new_from_ptr(&raw mut callback_closure)
});
let callback_ptr = &raw mut callback;
timer::schedule_timer(self.kernel, unsafe {
ForeignBox::new_from_ptr(callback_ptr)
});
// Safety: It is important hold on to the WaitQueue lock that is returned
// from reschedule as the pointers needed by the timer canceling code
// below rely on it for correctness.
self = self.add_to_queue_and_reschedule(thread, wait_type);
wait_queue_debug!(
"WaitQueue: thread '{}' ({:#010x}) resumed",
self.sched().current_thread_name() as &str,
self.sched().current_thread_id() as usize
);
// Cancel timeout callback if has not already fired.
//
// Safety: callback_ptr is valid until callback goes out of scope.
unsafe {
timer::cancel_and_consume_timer(self.kernel, NonNull::new_unchecked(callback_ptr))
};
wait_queue_debug!(
"WaitQueue: thread '{}' ({:#010x}) exiting wait_until",
self.sched().current_thread_name() as &str,
self.sched().current_thread_id() as usize
);
// Safety:
//
// At this point the thread will be in the run queue by virtue of
// `reschedule()` return and the timer callback will have fired or be
// canceled. This leaves no dangling references to our "smuggled"
// pointers.
//
// It is also now safe to read the result UnsafeCell
// Return `Error::Cancelled` if the wait was interrupted because this
// thread was terminated while it was waiting.
if self.sched().current_thread().terminating && wait_type == WaitType::Interruptible {
(self, Err(Error::Cancelled))
} else {
(self, unsafe { result.get().read_volatile() })
}
}
}