use alloc::sync::Arc;
use core::{marker::PhantomData, ptr::NonNull};
pub use crate::runtime::switch::dispatch::{
schedule_current_cpu, schedule_current_cpu_from_irq_guard_exit,
schedule_current_cpu_from_preempt_exit,
};
use crate::{
runtime::{
RuntimeStatus, TaskSystemHandle,
context::{RuntimeIrqGuard, validate_task_context},
cpu::{CurrentCpuOwnerHandles, RuntimeCpuId},
resource::{AddressSpaceHandle, AddressSpaceMembarrierId, ExecutionContextHandle},
switch::dispatch::complete_current_context_switch_tail,
task_runtime,
},
thread::TaskError,
};
pub unsafe fn finish_initial_context_switch() -> Result<(), TaskError> {
validate_task_context()?;
let mut irq = RuntimeIrqGuard::enter();
unsafe { complete_current_context_switch_tail(&mut irq)? };
drop(irq);
task_runtime::finish_initial_context_switch();
Ok(())
}
pub(crate) mod dispatch;
use crate::runtime::handle::opaque_handle;
opaque_handle!(
CurrentThreadOwnerHandle,
"runtime::switch"
);
#[derive(Debug, Eq, PartialEq)]
#[repr(C)]
pub struct RuntimeSwitchPlan {
previous_context: ExecutionContextHandle,
previous_address_space: AddressSpaceHandle,
next_context: ExecutionContextHandle,
next_address_space: AddressSpaceHandle,
#[cfg(feature = "qperf-metrics")]
qperf_prepare_started_ns: u64,
}
impl RuntimeSwitchPlan {
pub(crate) fn new(
previous_context: ExecutionContextHandle,
previous_address_space: AddressSpaceHandle,
previous_address_space_identity: AddressSpaceMembarrierId,
next_context: ExecutionContextHandle,
next_address_space: AddressSpaceHandle,
next_address_space_identity: AddressSpaceMembarrierId,
) -> Option<Self> {
debug_assert_eq!(
previous_address_space.is_none(),
previous_address_space_identity.is_none(),
);
debug_assert_eq!(
next_address_space.is_none(),
next_address_space_identity.is_none(),
);
debug_assert!(
previous_address_space != next_address_space
|| previous_address_space_identity == next_address_space_identity,
);
if previous_context.is_none() || next_context.is_none() || previous_context == next_context
{
None
} else {
let same_address_space = !previous_address_space_identity.is_none()
&& previous_address_space_identity == next_address_space_identity;
Some(Self {
previous_context,
previous_address_space: if same_address_space {
next_address_space
} else {
previous_address_space
},
next_context,
next_address_space,
#[cfg(feature = "qperf-metrics")]
qperf_prepare_started_ns: 0,
})
}
}
pub const fn previous_context(&self) -> ExecutionContextHandle {
self.previous_context
}
pub const fn previous_address_space(&self) -> AddressSpaceHandle {
self.previous_address_space
}
pub const fn next_context(&self) -> ExecutionContextHandle {
self.next_context
}
pub const fn next_address_space(&self) -> AddressSpaceHandle {
self.next_address_space
}
pub const fn same_address_space(&self) -> bool {
!self.next_address_space.is_none()
&& self.previous_address_space.into_raw() == self.next_address_space.into_raw()
}
#[cfg(feature = "qperf-metrics")]
pub(crate) fn set_qperf_prepare_started_ns(&mut self, started_ns: u64) {
self.qperf_prepare_started_ns = started_ns;
}
#[doc(hidden)]
#[cfg(feature = "qperf-metrics")]
pub const fn qperf_prepare_started_ns(&self) -> u64 {
self.qperf_prepare_started_ns
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ThreadRuntimeBinding {
context: ExecutionContextHandle,
address_space: AddressSpaceHandle,
}
impl ThreadRuntimeBinding {
pub(crate) const fn new(
context: ExecutionContextHandle,
address_space: AddressSpaceHandle,
) -> Self {
Self {
context,
address_space,
}
}
pub(crate) const fn context(self) -> ExecutionContextHandle {
self.context
}
pub(crate) const fn address_space(self) -> AddressSpaceHandle {
self.address_space
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum RuntimeScheduleOrigin {
Block = 0,
Yield = 1,
Exit = 2,
Preempt = 3,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum RuntimeSchedulerEntry {
Task = 0,
PreemptExit = 1,
IrqReturn = 2,
IrqGuardExit = 3,
IrqReturnContinuation = 4,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum RuntimeSchedulerReturn {
Task = 0,
IrqReturn = 1,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct ThreadIdentityV1 {
pub slot: u32,
pub generation: u32,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct CurrentThreadPublication {
identity: ThreadIdentityV1,
owner: CurrentThreadOwnerHandle,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct RuntimeSchedulerFrameEnterResult {
system: TaskSystemHandle,
cpu: CurrentCpuOwnerHandles,
}
impl RuntimeSchedulerFrameEnterResult {
pub const unsafe fn success(system: TaskSystemHandle, cpu: CurrentCpuOwnerHandles) -> Self {
Self { system, cpu }
}
pub const fn failure() -> Self {
Self {
system: TaskSystemHandle::NONE,
cpu: CurrentCpuOwnerHandles::NONE,
}
}
pub const fn status(self) -> RuntimeStatus {
if self.system.is_none() {
RuntimeStatus::UnsafeContext
} else {
RuntimeStatus::Success
}
}
pub const fn system(self) -> TaskSystemHandle {
self.system
}
pub const fn cpu(self) -> CurrentCpuOwnerHandles {
self.cpu
}
}
pub(crate) struct CurrentThreadRef {
identity: crate::thread::ThreadId,
core: NonNull<crate::thread::ThreadCore>,
_not_send: PhantomData<*mut ()>,
}
impl CurrentThreadRef {
pub(crate) const fn id(&self) -> crate::thread::ThreadId {
self.identity
}
pub(crate) fn runtime_core(&self) -> &crate::thread::ThreadCore {
unsafe { self.core.as_ref() }
}
}
impl CurrentThreadPublication {
pub const NONE: Self = Self {
identity: ThreadIdentityV1::NONE,
owner: CurrentThreadOwnerHandle::NONE,
};
pub const fn identity(self) -> ThreadIdentityV1 {
self.identity
}
pub const fn owner(self) -> CurrentThreadOwnerHandle {
self.owner
}
pub(crate) fn from_core(
identity: crate::thread::ThreadId,
core: &Arc<crate::thread::ThreadCore>,
) -> Self {
let owner = Arc::as_ptr(core).expose_provenance();
let owner = unsafe { CurrentThreadOwnerHandle::from_raw(owner) };
Self {
identity: ThreadIdentityV1::new(identity.slot(), identity.generation()),
owner,
}
}
pub(crate) unsafe fn borrow_current(
self,
) -> Result<CurrentThreadRef, crate::thread::TaskError> {
if !self.identity.is_bound() {
return Err(crate::thread::TaskError::NoRunnableThread);
}
let core = NonNull::new(core::ptr::with_exposed_provenance_mut::<
crate::thread::ThreadCore,
>(self.owner.into_raw()))
.ok_or(crate::thread::TaskError::InvalidRuntimeHandle)?;
let identity =
crate::thread::ThreadId::from_parts(self.identity.slot, self.identity.generation);
let current = CurrentThreadRef {
identity,
core,
_not_send: PhantomData,
};
if current.runtime_core().id() != identity {
return Err(crate::thread::TaskError::InvalidRuntimeHandle);
}
Ok(current)
}
pub(crate) unsafe fn acquire_handle(
self,
) -> Result<crate::thread::ThreadHandle, crate::thread::TaskError> {
let core = unsafe {
self.acquire_scheduler_core()?
};
Ok(crate::thread::ThreadHandle::from_core(core))
}
pub(crate) unsafe fn acquire_scheduler_core(
self,
) -> Result<Arc<crate::thread::ThreadCore>, crate::thread::TaskError> {
if !self.identity.is_bound() {
return Err(crate::thread::TaskError::NoRunnableThread);
}
if self.owner.is_none() {
return Err(crate::thread::TaskError::InvalidRuntimeHandle);
}
let core =
core::ptr::with_exposed_provenance::<crate::thread::ThreadCore>(self.owner.into_raw());
unsafe { Arc::increment_strong_count(core) };
let core = unsafe { Arc::from_raw(core) };
let expected =
crate::thread::ThreadId::from_parts(self.identity.slot, self.identity.generation);
if core.id() != expected {
return Err(crate::thread::TaskError::InvalidRuntimeHandle);
}
Ok(core)
}
}
impl ThreadIdentityV1 {
pub const NONE: Self = Self {
slot: 0,
generation: 0,
};
pub const fn new(slot: u32, generation: u32) -> Self {
Self { slot, generation }
}
pub const fn is_bound(self) -> bool {
self.generation != 0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct ContextThreadBinding {
pub context: ExecutionContextHandle,
pub publication: CurrentThreadPublication,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct SchedSwitchRecord {
pub cpu: RuntimeCpuId,
pub previous_thread: u64,
pub next_thread: u64,
pub timestamp_ns: u64,
pub reason: u32,
}
pub use crate::sched::system::{
ScheduleDecision, SchedulerOutcome, SwitchInCompletion, YieldOutcome,
};
#[cfg(test)]
mod switch_plan_tests {
use super::*;
#[test]
fn runtime_switch_plan_keeps_context_and_logical_mm_in_one_transaction() {
let previous_context = unsafe { ExecutionContextHandle::from_raw(0x1000) };
let next_context = unsafe { ExecutionContextHandle::from_raw(0x2000) };
let previous_mm = unsafe { AddressSpaceHandle::from_raw(0x3000) };
let next_mm = unsafe { AddressSpaceHandle::from_raw(0x4000) };
let previous_mm_identity = unsafe { AddressSpaceMembarrierId::from_raw(0x5000) };
let next_mm_identity = unsafe { AddressSpaceMembarrierId::from_raw(0x6000) };
let plan = RuntimeSwitchPlan::new(
previous_context,
previous_mm,
previous_mm_identity,
next_context,
next_mm,
next_mm_identity,
)
.expect("two distinct live contexts must form one runtime switch plan");
assert_eq!(plan.previous_context(), previous_context);
assert_eq!(plan.previous_address_space(), previous_mm);
assert_eq!(plan.next_context(), next_context);
assert_eq!(plan.next_address_space(), next_mm);
}
}