blob: b7bfad95794c7d2a2372195de22c9c56c77a122f [file] [edit]
// 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.
#![no_std]
use pw_atomic::{AtomicBool, AtomicUsize};
use pw_log::info;
pub use time::{Duration, Instant};
pub mod interrupt_controller;
#[cfg(feature = "user_space")]
pub mod object;
#[cfg(not(feature = "std_panic_handler"))]
mod panic;
pub mod scheduler;
pub mod sync;
#[cfg(feature = "user_space")]
pub mod syscall;
pub use target::shutdown;
mod target;
mod trace;
use interrupt_controller::InterruptController;
use kernel_config::{KernelConfig, KernelConfigInterface};
#[cfg(feature = "user_space")]
pub use object::NullObjectTable;
#[doc(hidden)]
pub use scheduler::thread::{Process, Stack, StackStorage, StackStorageExt, Thread, ThreadState};
use scheduler::timer::TimerQueue;
use scheduler::{PreemptDisableGuard, SchedulerState, ThreadLocalState, thread};
pub use scheduler::{Priority, sleep_until, start_thread, yield_timeslice};
use sync::spinlock::{BareSpinLock, SpinLock, SpinLockGuard};
#[cfg(feature = "user_space")]
pub use syscall::SyscallArgs;
pub trait Arch: 'static + Copy + thread::ThreadArg {
type ThreadState: ThreadState;
type BareSpinLock: BareSpinLock;
type Clock: time::Clock;
type AtomicBool: AtomicBool;
type AtomicUsize: AtomicUsize;
#[cfg(feature = "user_space")]
type SyscallArgs<'a>: SyscallArgs<'a>;
type InterruptController: InterruptController;
/// Switches to a new thread.
///
/// - `sched_state`: A guard for the global `SchedulerState`
/// - This may be dropped and re-acquired across this function; the
/// returned guard is either still held or newly re-acquired
/// - `old_thread_state`: The thread we're moving away from
/// - `new_thread_state`: The thread we're moving to; must match
/// `current_thread` and the container for this `ThreadState`
///
/// Returns:
/// - `sched_state`: A guard for the global `SchedulerState`
/// - `switched`: `true` if a context switch happened, `false` otherwise.
///
/// If `switched` is `false`, the implementation guarantees that forward
/// progress will be made. For example, a context switch may be deferred
/// to an interrupt handler (like PendSV) which is pending. The caller
/// does not need to retry or take further action to ensure the switch
/// occurs.
#[allow(clippy::missing_safety_doc)]
unsafe fn context_switch(
self,
sched_state: SpinLockGuard<'_, Self, SchedulerState<Self>>,
old_thread_state: *mut Self::ThreadState,
new_thread_state: *mut Self::ThreadState,
) -> (SpinLockGuard<'_, Self, SchedulerState<Self>>, bool)
where
Self: Kernel;
fn thread_local_state(self) -> &'static ThreadLocalState<Self>;
fn now(self) -> Instant<Self::Clock>;
fn get_interrupt_controller(self) -> &'static SpinLock<Self, Self::InterruptController>
where
Self: Kernel,
{
&self.get_state().arch_state.interrupt_controller
}
#[allow(dead_code)]
fn idle(self) {}
/// Early architecture initialization, called before the scheduler is bootstrapped.
///
/// This is invoked during kernel startup before any threads exist and before
/// the scheduler is operational. Architecture code **must not** call
/// `scheduler::tick()` or any other scheduler functions during this phase.
///
/// Use this for hardware initialization that must happen before the scheduler
/// starts, such as setting up the interrupt controller or memory protection.
fn early_init(self) {}
/// Architecture initialization, called after the scheduler is bootstrapped.
///
/// This is invoked after the scheduler is fully operational and threads can
/// be scheduled. It is safe to call `scheduler::tick()` once `init()` is invoked.
///
/// Use this for initialization that depends on the scheduler being ready,
/// such as starting timer interrupts that will drive `scheduler::tick()`.
///
/// # Contract
///
/// - `early_init()` is always called before `init()`
/// - `scheduler::tick()` **must not** be called by architecture code before
/// `init()` is invoked
/// - Once `init()` is invoked, timer interrupts may safely call `scheduler::tick()`.
/// This includes interrupts that fire before `init()` returns. At this point,
/// only the bootstrap thread exists in the system and any context switch
/// requests will early exit, making this safe.
fn init(self) {}
fn panic() -> ! {
#[allow(clippy::empty_loop)]
loop {}
}
}
pub struct ArchState<K: Kernel> {
interrupt_controller: SpinLock<K, K::InterruptController>,
}
impl<K: Kernel> ArchState<K> {
#[must_use]
pub const fn new(interrupt_controller: K::InterruptController) -> Self {
Self {
interrupt_controller: SpinLock::new(interrupt_controller),
}
}
}
pub trait Kernel: Arch + Sync {
fn get_state(self) -> &'static KernelState<Self>;
fn get_scheduler(self) -> &'static SpinLock<Self, SchedulerState<Self>> {
&self.get_state().scheduler
}
fn get_timer_queue(self) -> &'static SpinLock<Self, TimerQueue<Self>> {
&self.get_state().timer_queue
}
}
pub struct KernelState<K: Kernel> {
arch_state: ArchState<K>,
scheduler: SpinLock<K, SchedulerState<K>>,
timer_queue: SpinLock<K, TimerQueue<K>>,
// TODO: https://pwbug.dev/479857256 - Add configurable trace buffer size.
#[cfg(feature = "tracing")]
pub trace_buffer: pw_kernel_tracing::Buffer<K::AtomicUsize, 2048>,
}
impl<K: Kernel> KernelState<K> {
#[must_use]
pub const fn new(arch_state: ArchState<K>) -> Self {
Self {
arch_state,
scheduler: SpinLock::new(SchedulerState::new()),
timer_queue: SpinLock::new(TimerQueue::new()),
#[cfg(feature = "tracing")]
trace_buffer: pw_kernel_tracing::Buffer::new(),
}
}
}
#[cfg(not(feature = "tracing"))]
#[macro_export]
macro_rules! annotate_kernel_trace_buffer {
($trace_buffer:expr) => {{}};
}
#[cfg(feature = "tracing")]
#[macro_export]
macro_rules! annotate_kernel_trace_buffer {
($trace_buffer:expr) => {{
#[repr(C, packed(1))]
struct TraceBufferAnnotation {
name: &'static str,
addr: *const (),
size: usize,
}
unsafe impl Sync for TraceBufferAnnotation {};
#[unsafe(link_section = ".pw_kernel.annotations.trace_buffer")]
#[used]
static _TRACE_BUFFER_ANNOTATION: TraceBufferAnnotation = TraceBufferAnnotation {
name: "kernel",
addr: unsafe { $trace_buffer.buffer() },
size: $trace_buffer.buffer_len(),
};
}};
}
#[macro_export]
macro_rules! annotate_kernel_state {
($state:expr) => {{ $crate::annotate_kernel_trace_buffer!($state.trace_buffer) }};
}
/// Initializes an [`InitKernelState`] in static storage.
///
/// # Examples
///
/// Here's an example of using `static_init_state!` on Cortex-M:
///
/// ```
/// struct Arch;
///
/// #[cortex_m_rt::entry]
/// fn main() -> ! {
/// Target::console_init();
///
/// kernel::static_init_state!(static mut INIT_STATE: InitKernelState<Arch>);
///
/// // SAFETY: `main` is only executed once, so we never generate more than one
/// // `&mut` reference to `INIT_STATE`.
/// kernel::Kernel::main(Arch, unsafe { &mut INIT_STATE });
/// }
/// ```
#[macro_export]
macro_rules! static_init_state {
($vis:vis static mut $name:ident: InitKernelState<$kernel:ty>) => {
$vis static mut $name: $crate::InitKernelState<$kernel> = {
use $crate::__private::kernel_config;
use kernel_config::KernelConfigInterface as _;
use kernel::StackStorageExt as _;
use $crate::Priority;
type Stack = $crate::StackStorage<{ kernel_config::KernelConfig::KERNEL_STACK_SIZE_BYTES }>;
static mut BOOTSTRAP_STACK: Stack = Stack::ZEROED;
static mut IDLE_STACK: Stack = Stack::ZEROED;
$crate::annotate_stack!("bootstrap", &raw const BOOTSTRAP_STACK, kernel_config::KernelConfig::KERNEL_STACK_SIZE_BYTES);
$crate::annotate_stack!("idle", &raw const IDLE_STACK, kernel_config::KernelConfig::KERNEL_STACK_SIZE_BYTES);
$crate::InitKernelState {
bootstrap_thread: $crate::ThreadStorage {
thread: $crate::Thread::new("bootstrap", Priority::DEFAULT_PRIORITY, $crate::Stack::new()),
// SAFETY: We're in a block used to initialize a `static`,
// which is only executed once.
stack: unsafe { &mut BOOTSTRAP_STACK },
},
idle_thread: $crate::ThreadStorage {
thread: $crate::Thread::new("idle", Priority::IDLE_PRIORITY, $crate::Stack::new()),
// SAFETY: We're in a block used to initialize a `static`,
// which is only executed once.
stack: unsafe { &mut IDLE_STACK},
},
}
};
// A null address is used as the ID of the kernel process for annotations
// due to not being able to access the address of the kernel process
// from here.
$crate::annotate_thread_from_address!(
"bootstrap",
$kernel,
&raw const $name.bootstrap_thread.thread,
core::ptr::null());
$crate::annotate_thread_from_address!(
"idle",
$kernel,
&raw const $name.idle_thread.thread,
core::ptr::null());
};
}
#[doc(hidden)]
pub struct ThreadStorage<K: Kernel> {
pub thread: Thread<K>,
// We store the stack out of line so that it is treated as a separate
// allocation from the containing `ThreadStorage`. The stack itself is
// zero-initialized, while the `Thread` is not. If we include the stack in
// the same allocation, then the entire allocation must be placed in the
// binary itself (in `.data`). By contrast, if it is out of line, then the
// fact that the whole stack is zero-initialized means that the linker can
// put it in `.bss` and omit its contents from the binary entirely.
//
// See https://pwrev.dev/303372 (Ie93cfc52215753e1b4482d5fe3058dc53fcfa86a)
// for more information.
pub stack: &'static mut StackStorage<{ KernelConfig::KERNEL_STACK_SIZE_BYTES }>,
}
pub struct InitKernelState<K: Kernel> {
#[doc(hidden)]
pub bootstrap_thread: ThreadStorage<K>,
#[doc(hidden)]
pub idle_thread: ThreadStorage<K>,
}
// Module re-exporting modules into a scope that can be referenced by macros
// in this crate.
#[doc(hidden)]
pub mod macro_exports {
pub use foreign_box;
pub use pw_assert;
}
pub fn main<K: Kernel>(kernel: K, init_state: &'static mut InitKernelState<K>) -> ! {
let preempt_guard = PreemptDisableGuard::new(kernel);
kernel.get_interrupt_controller().lock(kernel).early_init();
target::console_init();
info!("Welcome to Maize on {}!", target::name() as &str);
kernel.early_init();
// Prepare the scheduler for thread initialization.
scheduler::initialize(kernel);
let bootstrap_thread = thread::init_thread_in(
kernel,
&mut init_state.bootstrap_thread.thread,
init_state.bootstrap_thread.stack,
"bootstrap",
Priority::DEFAULT_PRIORITY,
bootstrap_thread_entry,
&mut init_state.idle_thread,
);
info!("Created initial thread; bootstrapping");
// special case where we bootstrap the system by half context switching to this thread
scheduler::bootstrap_scheduler(kernel, preempt_guard, bootstrap_thread);
// never get to here
}
// completion of main in thread context
fn bootstrap_thread_entry<K: Kernel>(
kernel: K,
idle_thread_storage: &'static mut ThreadStorage<K>,
) {
info!("Welcome to the first thread, continuing bootstrap");
pw_assert::assert!(K::InterruptController::interrupts_enabled());
kernel.init();
let idle_thread = thread::init_thread_in(
kernel,
&mut idle_thread_storage.thread,
idle_thread_storage.stack,
"idle",
Priority::IDLE_PRIORITY,
idle_thread_entry,
0,
);
scheduler::start_thread(kernel, idle_thread);
target::main()
}
fn idle_thread_entry<K: Kernel>(kernel: K, _arg: usize) {
// Fake idle thread to keep the runqueue from being empty if all threads are blocked.
pw_assert::assert!(K::InterruptController::interrupts_enabled());
loop {
kernel.idle();
}
}
/// Stores `t` in stack memory and provides a `&'static mut T` to a function
/// which will never return.
///
/// Since `F: FnOnce(...) -> !`, the stack memory holding `t` will never be
/// reclaimed, and so it can live forever (ie, be referenced using `&'static mut
/// T`).
///
/// If `f` unwinds, `with_static` will `loop {}` forever to prevent `t` from
/// being reclaimed.
pub fn with_static<T: 'static, F: FnOnce(&'static mut T)>(t: T, f: F) -> ! {
struct LoopOnDrop<T>(T);
impl<T> Drop for LoopOnDrop<T> {
fn drop(&mut self) {
#[allow(clippy::empty_loop)]
loop {}
}
}
let mut t = LoopOnDrop(t);
let tp: *mut T = &mut t.0;
// SAFETY: Since we `drop(t)` after the `loop {}`, `t` will only go out of
// scope if `f` unwinds. If this happens, `LoopOnDrop::drop` will `loop {}`
// forever before its inner `T` is destructed. Thus, `t.0` will never be
// destructed, and so it is sound to synthesize a `'static` reference to
// `t`.
//
// See for more analysis: https://github.com/rust-lang/unsafe-code-guidelines/issues/565
f(unsafe { &mut *tp });
#[allow(clippy::empty_loop)]
loop {}
#[allow(unreachable_code)]
drop(t);
}
#[doc(hidden)]
pub mod __private {
/// Takes a mutable reference to a global static.
///
/// # Safety
///
/// Each invocation of `static_mut_ref!` must be executed at most once at
/// run time.
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`
#[macro_export]
macro_rules! static_mut_ref {
($ty:ty = $value:expr) => {{
use $crate::__private::foreign_box::StaticStorage;
static mut __STATIC: StaticStorage<$ty> = StaticStorage::new();
// Alias value to allow nested `unsafe` statements without warning.
let value = $value;
// SAFETY: The caller promises that this macro will be executed at
// most once fulfilling `StaticStorage`'s precondition.
unsafe { __STATIC.init(value) }
}};
}
pub use foreign_box;
pub use kernel_config;
pub use time;
}