use core::marker::PhantomData;
use crate::{
runtime::{
context::{
RuntimeCpuPin, RuntimeIrqGuard, RuntimeSchedulerFrameGuard, runtime_current_cpu_mut,
runtime_task_system, validate_schedule_context,
},
switch::{
RuntimeScheduleOrigin, RuntimeSchedulerEntry, SchedSwitchRecord, ScheduleDecision,
SchedulerOutcome,
},
task_runtime,
},
sched::system::{CurrentExitPermit, SchedulerRequestScope},
thread::{TaskError, ThreadId, ThreadState, current::current_thread_handle},
};
pub fn schedule_current_cpu() -> Result<SchedulerOutcome, TaskError> {
schedule_current_cpu_with_entry(RuntimeSchedulerEntry::Task)
}
pub unsafe fn schedule_current_cpu_from_preempt_exit(
entry: RuntimeSchedulerEntry,
) -> Result<SchedulerOutcome, TaskError> {
if !matches!(
entry,
RuntimeSchedulerEntry::PreemptExit | RuntimeSchedulerEntry::IrqReturn
) {
return Err(TaskError::UnsafeContext);
}
schedule_current_cpu_with_entry(entry)
}
pub unsafe fn schedule_current_cpu_from_irq_guard_exit() -> Result<SchedulerOutcome, TaskError> {
schedule_current_cpu_with_entry(RuntimeSchedulerEntry::IrqGuardExit)
}
fn schedule_current_cpu_with_entry(
mut entry: RuntimeSchedulerEntry,
) -> Result<SchedulerOutcome, TaskError> {
let original_entry = entry;
loop {
let request_scope = scheduler_request_scope(entry, original_entry);
let mut scheduler_frame =
RuntimeSchedulerFrameGuard::enter(RuntimeScheduleOrigin::Preempt, entry)?;
let system = scheduler_frame.task_system();
let current_publication = scheduler_frame.current_thread_publication();
let (mut outcome, no_switch_request_pending) = {
let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
let current_state = unsafe { cpu.scheduler_current_lifecycle_state() };
let outcome = if !cpu.scheduler_request_pending(request_scope) && !cpu.has_remote_work()
{
if current_state == Some(ThreadState::Parking) {
SchedulerOutcome::ParkingDeferred
} else {
SchedulerOutcome::Quiescent
}
} else {
let current = unsafe { current_publication.borrow_current()? };
unsafe {
system.schedule_if_requested_in_scheduler_frame(
cpu.as_mut(),
¤t,
request_scope,
)?
}
};
let request_pending = match outcome.decision() {
Some(decision) if decision.requires_context_switch() => None,
Some(_) | None => Some(cpu.scheduler_request_pending(request_scope)),
};
(outcome, request_pending)
};
if let Some(decision) = outcome.decision_mut() {
execute_switch_plan(&mut scheduler_frame, decision);
}
let needs_reschedule = if let Some(request_pending) = no_switch_request_pending {
request_pending
} else {
scheduler_frame.scheduler_request_pending(request_scope)?
};
let repeat = preempt_schedule_needs_repeat(&outcome, needs_reschedule);
drop(scheduler_frame);
if !repeat {
return Ok(outcome);
}
entry = match entry {
RuntimeSchedulerEntry::IrqReturn | RuntimeSchedulerEntry::IrqReturnContinuation => {
RuntimeSchedulerEntry::IrqReturnContinuation
}
RuntimeSchedulerEntry::Task
| RuntimeSchedulerEntry::PreemptExit
| RuntimeSchedulerEntry::IrqGuardExit => RuntimeSchedulerEntry::Task,
};
}
}
fn scheduler_request_scope(
entry: RuntimeSchedulerEntry,
original_entry: RuntimeSchedulerEntry,
) -> SchedulerRequestScope {
if matches!(
original_entry,
RuntimeSchedulerEntry::PreemptExit | RuntimeSchedulerEntry::IrqGuardExit
) {
return SchedulerRequestScope::Immediate;
}
match entry {
RuntimeSchedulerEntry::Task => SchedulerRequestScope::All,
RuntimeSchedulerEntry::PreemptExit
| RuntimeSchedulerEntry::IrqReturn
| RuntimeSchedulerEntry::IrqGuardExit
| RuntimeSchedulerEntry::IrqReturnContinuation => SchedulerRequestScope::Immediate,
}
}
fn preempt_schedule_needs_repeat(outcome: &SchedulerOutcome, needs_reschedule: bool) -> bool {
needs_reschedule && !outcome.parking_deferred()
}
pub fn yield_current_cpu() -> Result<(), TaskError> {
#[cfg(feature = "qperf-metrics")]
let scheduler_started_ns = task_runtime::monotonic_now().as_nanos();
let mut scheduler_frame = RuntimeSchedulerFrameGuard::enter(
RuntimeScheduleOrigin::Yield,
RuntimeSchedulerEntry::Task,
)?;
#[cfg(feature = "qperf-metrics")]
let scheduler_frame_entered_ns = task_runtime::monotonic_now().as_nanos();
let system = scheduler_frame.task_system();
#[cfg(feature = "qperf-metrics")]
let scheduler_dispatch_started_ns;
let mut outcome = {
let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
#[cfg(feature = "qperf-metrics")]
{
scheduler_dispatch_started_ns = task_runtime::monotonic_now().as_nanos();
}
unsafe { system.yield_current_in_scheduler_frame(cpu.as_mut())? }
};
#[cfg(feature = "qperf-metrics")]
let scheduler_dispatch_finished_ns = task_runtime::monotonic_now().as_nanos();
if let Some(decision) = outcome.decision_mut() {
#[cfg(feature = "qperf-metrics")]
{
crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
7,
scheduler_started_ns,
scheduler_frame_entered_ns,
);
crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
8,
scheduler_frame_entered_ns,
scheduler_dispatch_started_ns,
);
crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
9,
scheduler_dispatch_started_ns,
scheduler_dispatch_finished_ns,
);
crate::diagnostics::counters::qperf_record_switch_phase_scheduler(
scheduler_started_ns,
scheduler_dispatch_finished_ns,
);
}
execute_switch_plan(&mut scheduler_frame, decision);
}
Ok(())
}
pub fn exit_current_thread() -> Result<(), TaskError> {
let permit = prepare_current_exit()?;
commit_current_exit(permit)
}
pub struct ExitPermit {
system: CurrentExitPermit,
_not_send: PhantomData<*mut ()>,
}
pub fn prepare_current_exit() -> Result<ExitPermit, TaskError> {
validate_schedule_context(RuntimeScheduleOrigin::Exit)?;
let current = current_thread_handle()?;
let mut irq = RuntimeIrqGuard::enter();
let system = runtime_task_system()?;
let mut cpu = runtime_current_cpu_mut(&mut irq)?;
let system = system.prepare_current_exit(cpu.as_mut(), ¤t)?;
Ok(ExitPermit {
system,
_not_send: PhantomData,
})
}
pub fn commit_current_exit(permit: ExitPermit) -> ! {
let thread = permit.system.thread();
let mut scheduler_frame =
RuntimeSchedulerFrameGuard::enter(RuntimeScheduleOrigin::Exit, RuntimeSchedulerEntry::Task)
.unwrap_or_else(|_| task_runtime::fatal_invariant(0x4558_0010, thread.as_u64() as _));
let system = scheduler_frame.task_system();
let mut decision = {
let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)
.unwrap_or_else(|_| task_runtime::fatal_invariant(0x4558_0013, thread.as_u64() as _));
unsafe { system.commit_prepared_current_exit(cpu.as_mut(), permit.system) }
};
execute_switch_plan(&mut scheduler_frame, &mut decision);
task_runtime::fatal_invariant(4, decision.previous().map_or(0, ThreadId::as_u64) as usize)
}
pub(crate) fn execute_switch_plan(
scheduler_frame: &mut RuntimeSchedulerFrameGuard,
decision: &mut ScheduleDecision,
) {
if !decision.requires_context_switch() {
return;
}
#[cfg(feature = "qperf-metrics")]
let prepare_started_ns = task_runtime::monotonic_now().as_nanos();
let Some(previous) = decision.previous() else {
task_runtime::fatal_invariant(1, decision.next().as_u64() as usize);
};
let next = decision.next();
let previous_extension = {
let mut cpu = runtime_current_cpu_mut(scheduler_frame)
.unwrap_or_else(|_| task_runtime::fatal_invariant(6, next.as_u64() as usize));
let handoff = cpu
.as_mut()
.switch_handoff_mut()
.unwrap_or_else(|| task_runtime::fatal_invariant(6, next.as_u64() as usize));
if handoff.previous().id() != previous || handoff.incoming().id() != next {
task_runtime::fatal_invariant(6, next.as_u64() as usize);
}
handoff.previous().extension_view()
};
let plan = decision
.take_runtime_switch_plan()
.unwrap_or_else(|| task_runtime::fatal_invariant(6, next.as_u64() as usize));
#[cfg(feature = "qperf-metrics")]
let switch_validate_finished_ns = task_runtime::monotonic_now().as_nanos();
let trace_wake = task_runtime::trace_sched_switch(SchedSwitchRecord {
cpu: scheduler_frame.cpu_id(),
previous_thread: previous.as_u64(),
next_thread: next.as_u64(),
timestamp_ns: decision.timestamp_ns(),
reason: decision.switch_reason() as u32,
});
if let Some(wake) = trace_wake {
let mut cpu = runtime_current_cpu_mut(scheduler_frame)
.unwrap_or_else(|_| task_runtime::fatal_invariant(6, next.as_u64() as usize));
cpu.as_mut()
.switch_handoff_mut()
.unwrap_or_else(|| task_runtime::fatal_invariant(6, next.as_u64() as usize))
.install_trace_wake(wake);
}
#[cfg(feature = "qperf-metrics")]
let switch_trace_finished_ns = task_runtime::monotonic_now().as_nanos();
if let Some(extension) = previous_extension {
unsafe {
(extension.ops().on_switch_out)(extension.data(), previous, decision.switch_reason())
};
}
#[cfg(feature = "qperf-metrics")]
let switch_out_hook_finished_ns = task_runtime::monotonic_now().as_nanos();
#[cfg(feature = "qperf-metrics")]
crate::diagnostics::counters::record_context_switch(decision.switch_reason());
#[cfg(feature = "qperf-metrics")]
let switch_accounting_finished_ns = task_runtime::monotonic_now().as_nanos();
#[cfg(feature = "qperf-metrics")]
let plan = {
crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
26,
prepare_started_ns,
switch_validate_finished_ns,
);
crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
27,
switch_validate_finished_ns,
switch_trace_finished_ns,
);
crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
28,
switch_trace_finished_ns,
switch_out_hook_finished_ns,
);
crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
29,
switch_out_hook_finished_ns,
switch_accounting_finished_ns,
);
let mut plan = plan;
plan.set_qperf_prepare_started_ns(prepare_started_ns);
plan
};
unsafe { task_runtime::switch_context(plan) };
scheduler_frame.refresh_current_cpu();
if unsafe { complete_current_context_switch_tail_in_scheduler_frame(scheduler_frame) }.is_err()
{
task_runtime::fatal_invariant(5, 0);
}
}
pub(crate) unsafe fn complete_current_context_switch_tail(
pin: &mut impl RuntimeCpuPin,
) -> Result<(), TaskError> {
let system = runtime_task_system()?;
unsafe { finish_switch_tail(system, pin) }
}
unsafe fn complete_current_context_switch_tail_in_scheduler_frame(
scheduler_frame: &mut RuntimeSchedulerFrameGuard,
) -> Result<(), TaskError> {
let system = scheduler_frame.task_system();
unsafe { finish_switch_tail(system, scheduler_frame) }
}
unsafe fn finish_switch_tail(
system: &crate::runtime::TaskSystem,
pin: &mut impl RuntimeCpuPin,
) -> Result<(), TaskError> {
let completion = {
let mut cpu = runtime_current_cpu_mut(pin)?;
unsafe { system.complete_context_switch_in_scheduler_frame(cpu.as_mut())? }
};
completion.finish();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn preempt_exit_continuation_does_not_consume_lazy_requests() {
assert_eq!(
scheduler_request_scope(
RuntimeSchedulerEntry::Task,
RuntimeSchedulerEntry::PreemptExit,
),
SchedulerRequestScope::Immediate
);
assert_eq!(
scheduler_request_scope(
RuntimeSchedulerEntry::Task,
RuntimeSchedulerEntry::IrqGuardExit,
),
SchedulerRequestScope::Immediate
);
assert_eq!(
scheduler_request_scope(RuntimeSchedulerEntry::Task, RuntimeSchedulerEntry::Task),
SchedulerRequestScope::All
);
}
}