blob: 47357935eb6efd218c0711988db382348d308b17 [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::mem::ManuallyDrop;
use core::ops::Range;
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
use exit_status::ExitStatus;
#[cfg(not(feature = "user_space"))]
use foreign_box::ForeignBox;
#[cfg(feature = "user_space")]
use foreign_box::{ForeignBox, ForeignRc};
use list::*;
use memory_config::{MemoryConfig as _, MemoryRegionType};
use pw_atomic::{AtomicAdd, AtomicSub, AtomicZero};
use pw_log::info;
use pw_status::Result;
use pw_time_core::Instant;
use crate::Kernel;
#[cfg(feature = "user_space")]
use crate::object::{KernelObject, ObjectTable};
use crate::scheduler::SchedulerState;
use crate::scheduler::thread::{Thread, ThreadHandle, ThreadState};
use crate::sync::event::{Event, EventConfig, EventSignaler};
use crate::sync::spinlock::SpinLockGuard;
/// Runtime state of a Process.
#[derive(Copy, Clone, PartialEq)]
pub enum ProcessState {
/// Process is registered with the scheduled because it is either new or has been joined.
Inactive,
/// Process is registered with the scheduler and participates in execution.
Active,
/// Process termination has been requested; threads are being terminated.
Terminating,
/// Process has no active threads and is terminated, waiting to be joined.
Terminated,
}
pub(super) fn process_state_to_string(s: ProcessState) -> &'static str {
match s {
ProcessState::Inactive => "Inactive",
ProcessState::Active => "Active",
ProcessState::Terminating => "Terminating",
ProcessState::Terminated => "Terminated",
}
}
pub struct Process<K: Kernel> {
// List of the processes in the system
pub link: Link,
// TODO - konkers: allow this to be tokenized.
pub name: &'static str,
pub(crate) memory_config: <K::ThreadState as ThreadState>::MemoryConfig,
#[cfg(feature = "user_space")]
object_table: ForeignBox<dyn ObjectTable<K>>,
#[cfg(feature = "user_space")]
pub object: Option<foreign_box::ForeignRc<K::AtomicUsize, crate::object::ProcessObject<K>>>,
pub(super) thread_list: UnsafeList<Thread<K>, ProcessThreadListAdapter<K>>,
pub(super) ref_count: K::AtomicUsize,
pub(super) state: ProcessState,
pub(super) join_event: Option<EventSignaler<K>>,
pub(super) exit_status: Option<ExitStatus>,
}
// Safety: Process internal state is protected by the scheduler lock or atomic operations.
unsafe impl<K: Kernel> Send for Process<K> {}
unsafe impl<K: Kernel> Sync for Process<K> {}
list::define_adapter!(pub ProcessListAdapter<K: Kernel> => Process<K>::link);
list::define_adapter!(pub ProcessThreadListAdapter<K: Kernel> => Thread<K>::process_link);
impl<K: Kernel> Process<K> {
/// Creates a new, empty, unregistered process.
#[must_use]
pub const fn new(
name: &'static str,
memory_config: <K::ThreadState as ThreadState>::MemoryConfig,
#[cfg(feature = "user_space")] object_table: ForeignBox<dyn ObjectTable<K>>,
) -> Self {
Self {
link: Link::new(),
name,
memory_config,
#[cfg(feature = "user_space")]
object_table,
#[cfg(feature = "user_space")]
object: None,
thread_list: UnsafeList::new(),
ref_count: K::AtomicUsize::ZERO,
state: ProcessState::Inactive,
join_event: None,
exit_status: None,
}
}
/// Manually increment the processes refcount
///
/// SAFETY: This should only be done by the scheduler to maintain a
/// virtual, reference on the kernel process during initialization.
pub(super) unsafe fn manually_increment_ref_count(&mut self) {
self.ref_count.fetch_add(1, Ordering::Acquire);
}
#[cfg(feature = "user_space")]
pub fn get_object(
&self,
kernel: K,
handle: u32,
) -> Option<ForeignRc<K::AtomicUsize, dyn KernelObject<K>>> {
self.object_table.get_object(kernel, handle)
}
#[cfg(feature = "user_space")]
pub fn reset_objects(&self, kernel: K) -> Result<()> {
self.object_table.reset_all(kernel)
}
/// # Safety
/// Caller must ensure exclusive access to the process and `thread` and all
/// threads in the process' thread list. This can be accomplished by holding
/// the scheduler lock.
pub unsafe fn add_to_thread_list(&mut self, thread: &mut Thread<K>) {
// SAFETY: Caller guarantees exclusive access to the process and `thread`
// and all threads in the process' thread list.
unsafe {
let thread = NonNull::from(thread);
pw_assert::assert!(!self.thread_list.is_element_linked(thread));
self.thread_list.push_front_unchecked(thread);
}
}
pub fn range_has_access(&self, access_type: MemoryRegionType, range: Range<usize>) -> bool {
self.memory_config
.range_has_access(access_type, range.start, range.end)
}
/// A simple ID for debugging purposes, currently the pointer to the thread
/// structure itself.
///
/// # Safety
///
/// The returned value should not be relied upon as being a valid pointer.
/// Even in the current implementation, `id` does not expose the pointer's
/// provenance.
#[must_use]
pub fn id(&self) -> usize {
// Note: any changes to this also need to be reflected in
// `const_id_from_process_address`.
core::ptr::from_ref(self).addr()
}
/// Computes the ID of a process from its address.
///
/// # Safety
///
/// This exists to allow process annotations to be generated at compile time
/// for static processes that are initialized at runtime. This should not be
/// called in any other circumstance.
#[must_use]
pub const unsafe fn const_id_from_process_address(addr: *const ()) -> *const () {
addr
}
pub fn dump(&self, kernel: K) {
info!(
"Process '{}' ({:#010x}) state: {}",
self.name as &str,
self.id() as usize,
process_state_to_string(self.state) as &str
);
#[cfg(feature = "user_space")]
{
self.object_table.dump(kernel);
}
#[cfg(not(feature = "user_space"))]
{
// Suppress unused warning when user_space not enabled.
let _ = kernel;
}
unsafe {
let _ = self
.thread_list
.for_each(|thread| -> core::result::Result<(), ()> {
thread.dump();
Ok(())
});
}
}
}
pub struct ProcessHandle<K: Kernel> {
pub(crate) process: NonNull<Process<K>>,
kernel: K,
}
// Safety: ProcessHandle is a handle to a reference counted process. All operations
// on the process are protected by the scheduler lock or atomic operations.
unsafe impl<K: Kernel> Send for ProcessHandle<K> {}
unsafe impl<K: Kernel> Sync for ProcessHandle<K> {}
impl<K: Kernel> ProcessHandle<K> {
// `ProcessHandle`s should only be created when adding a process to the
// scheduler.
pub(super) fn new(process: NonNull<Process<K>>, kernel: K) -> Self {
unsafe {
process.as_ref().ref_count.fetch_add(1, Ordering::Acquire);
}
Self { process, kernel }
}
/// Request termination of the process.
///
/// Sets the process state to Terminating and requests termination for all
/// threads in the process.
///
/// This is an ASYNCHRONOUS operation.
pub fn terminate(&self, kernel: K, status: ExitStatus) {
kernel
.get_scheduler()
.lock(kernel)
.process_terminate(kernel, self, status);
}
/// Returns the current state of the process.
#[must_use]
pub fn get_state_locked(
&self,
_sched: &SpinLockGuard<'_, K, SchedulerState<K>>,
) -> ProcessState {
// SAFETY: `self.process` is guaranteed to be valid because it is reference
// counted. The state is guaranteed to be coherent because the sched lock
// is taken.
unsafe { self.process.as_ref().state }
}
pub fn join(self, kernel: K) -> Result<(ForeignBox<Process<K>>, ExitStatus)> {
match self.join_until(kernel, Instant::<K::Clock>::MAX) {
crate::scheduler::ProcessJoinResult::Joined(process, status) => {
#[cfg(feature = "user_space")]
process.reset_objects(kernel)?;
Ok((process, status))
}
crate::scheduler::ProcessJoinResult::Err { error, .. } => Err(error),
}
}
/// Attempts to join the referenced process.
///
/// If `ProcessTryJoinResult::Joined` is returned, the caller is responsible
/// for resetting the process's objects using `process.reset_objects(kernel)`.
pub fn try_join(self, kernel: K) -> crate::scheduler::ProcessTryJoinResult<K> {
let (_, res) = kernel
.get_scheduler()
.lock(kernel)
.process_try_join(kernel, self, None);
res
}
/// Join the referenced process with a deadline
///
/// Waits until the all other references to the process are dropped and the
/// process terminates.
pub fn join_until(
mut self,
kernel: K,
deadline: Instant<K::Clock>,
) -> crate::scheduler::ProcessJoinResult<K> {
let join_event = Event::new(kernel, EventConfig::ManualReset);
loop {
let (_, res) = kernel.get_scheduler().lock(kernel).process_try_join(
kernel,
self,
Some(join_event.get_signaler()),
);
match res {
crate::scheduler::ProcessTryJoinResult::Err {
error: e,
process: process_handle,
} => {
return crate::scheduler::ProcessJoinResult::Err {
error: e,
process: process_handle,
};
}
crate::scheduler::ProcessTryJoinResult::Joined(process_box, status) => {
return crate::scheduler::ProcessJoinResult::Joined(process_box, status);
}
crate::scheduler::ProcessTryJoinResult::Wait(process_handle) => {
self = process_handle
}
};
if let Err(e) = join_event.wait_until(deadline) {
kernel
.get_scheduler()
.lock(kernel)
.process_cancel_try_join(&mut self);
return crate::scheduler::ProcessJoinResult::Err {
error: e,
process: self,
};
}
}
}
/// Drop the `ProcessHandle` while the scheduler lock is held.
///
/// This is an internal version for use by the scheduler while it is holding
/// the scheduler lock.
pub(super) fn drop_locked(
mut self,
kernel: K,
mut sched: SpinLockGuard<'_, K, SchedulerState<K>>,
) -> SpinLockGuard<'_, K, SchedulerState<K>> {
unsafe {
let prev_value = self
.process
.as_ref()
.ref_count
.fetch_sub(1, Ordering::Release);
// If this ref was one of two outstanding references to the process,
// the other reference may be attempting to join. Let the scheduler
// notify the join request if it is outstanding.
if prev_value == 2 {
sched = sched.process_signal_join(kernel, &mut self);
}
}
// Ensure that the trait version of drop is not run.
let _ = ManuallyDrop::new(self);
sched
}
pub fn add_thread(&mut self, kernel: K, thread_handle: &mut ThreadHandle<K>) -> Result<()> {
kernel
.get_scheduler()
.lock(kernel)
.process_add_thread(self, thread_handle)
}
pub fn range_has_access_locked(
&self,
_sched: &SpinLockGuard<'_, K, SchedulerState<K>>,
region_type: MemoryRegionType,
range: Range<usize>,
) -> bool {
// SAFETY: `self.process` is valid because `ProcessHandle` guarantees the
// lifetime of the pointed-to `Process`.
unsafe { self.process.as_ref().range_has_access(region_type, range) }
}
}
impl<K: Kernel> Clone for ProcessHandle<K> {
fn clone(&self) -> Self {
unsafe {
self.process
.as_ref()
.ref_count
.fetch_add(1, Ordering::Acquire);
}
Self {
process: self.process,
kernel: self.kernel,
}
}
}
impl<K: Kernel> Drop for ProcessHandle<K> {
fn drop(&mut self) {
unsafe {
let prev_value = self
.process
.as_ref()
.ref_count
.fetch_sub(1, Ordering::Release);
// If this ref was one of two outstanding references to the process,
// the other reference may be attempting to join. Let the scheduler
// notify the join request if it is outstanding.
if prev_value == 2 {
let _ = self
.kernel
.get_scheduler()
.lock(self.kernel)
.process_signal_join(self.kernel, self);
}
}
}
}
/// Add debug annotations for a static process
///
/// This macro is called by `init_non_priv_process` and should not be called
/// directly.
#[macro_export]
macro_rules! annotate_process_from_address {
($name:expr, $arch:ty, $process_address:expr) => {{
#[repr(C, packed(1))]
struct ProcessAnnotation {
name: &'static str,
id: *const (),
}
unsafe impl Sync for ProcessAnnotation {};
#[unsafe(link_section = ".pw_kernel.annotations.process")]
#[used]
static _PROCESS_ANNOTATION: ProcessAnnotation = ProcessAnnotation {
name: $name,
id: unsafe {
$crate::Process::<$arch>::const_id_from_process_address(
$process_address as *const (),
)
},
};
}};
}
/// Declare static storage for a non-privileged process.
///
/// Creates a static in named `storage_ident` in the calling scope which holds
/// the storage for a static process.
#[cfg(feature = "user_space")]
#[macro_export]
macro_rules! declare_non_priv_process_storage {
($storage_ident:ident) => {
static $storage_ident: $crate::__private::foreign_box::StaticStorage<
$crate::scheduler::Process<arch::Arch>,
> = $crate::__private::foreign_box::StaticStorage::new();
};
}
/// Get the constant process ID of a non-privileged process.
#[cfg(feature = "user_space")]
#[macro_export]
macro_rules! non_priv_process_id {
($storage:expr) => {{
use $crate::scheduler::Process;
unsafe {
Process::<arch::Arch>::const_id_from_process_address(unsafe { $storage.address() })
}
}};
}
/// Declares a new [`Process`] in a previously declared static process storage.
///
/// # Safety
///
/// Each invocation of `declare_non_priv_process!` must be executed at most once at
/// run time.
#[cfg(feature = "user_space")]
#[macro_export]
macro_rules! declare_non_priv_process {
($storage:expr, $name:literal, $memory_config:expr, $object_table:expr $(,)?) => {{
use $crate::__private::foreign_box::StaticStorage;
use $crate::Kernel;
use $crate::object::ObjectTable;
use $crate::scheduler::Process;
/// SAFETY: This must be executed at most once at run time. See
/// StaticStorage::init() for more details.
#[allow(clippy::mut_from_ref)]
unsafe fn __declare_non_priv_process(
storage: &'static StaticStorage<Process<arch::Arch>>,
object_table: ForeignBox<dyn ObjectTable<arch::Arch>>,
) -> ForeignBox<Process<arch::Arch>> {
use pw_log::info;
use $crate::__private::foreign_box::ForeignBox;
info!(
"Allocating non-privileged process '{}'",
$name as &'static str
);
// SAFETY: The caller promises that this function will be executed
// at most once.
let proc = unsafe { storage.init(Process::new($name, $memory_config, object_table)) };
ForeignBox::from(proc)
}
$crate::annotate_process_from_address!($name, arch::Arch, unsafe { $storage.address() });
__declare_non_priv_process(&$storage, $object_table)
}};
}