1use core::marker::PhantomData;
2
3use crate::{
4 runtime::{
5 context::{
6 RuntimeCpuPin, RuntimeIrqGuard, RuntimeSchedulerFrameGuard, runtime_current_cpu_mut,
7 runtime_task_system, validate_schedule_context,
8 },
9 switch::{
10 RuntimeScheduleOrigin, RuntimeSchedulerEntry, SchedSwitchRecord, ScheduleDecision,
11 SchedulerOutcome,
12 },
13 task_runtime,
14 },
15 sched::system::{CurrentExitPermit, SchedulerRequestScope},
16 thread::{TaskError, ThreadId, ThreadState, current::current_thread_handle},
17};
18
19pub fn schedule_current_cpu() -> Result<SchedulerOutcome, TaskError> {
30 schedule_current_cpu_with_entry(RuntimeSchedulerEntry::Task)
31}
32
33pub unsafe fn schedule_current_cpu_from_preempt_exit(
42 entry: RuntimeSchedulerEntry,
43) -> Result<SchedulerOutcome, TaskError> {
44 if !matches!(
45 entry,
46 RuntimeSchedulerEntry::PreemptExit | RuntimeSchedulerEntry::IrqReturn
47 ) {
48 return Err(TaskError::UnsafeContext);
49 }
50 schedule_current_cpu_with_entry(entry)
51}
52
53pub unsafe fn schedule_current_cpu_from_irq_guard_exit() -> Result<SchedulerOutcome, TaskError> {
61 schedule_current_cpu_with_entry(RuntimeSchedulerEntry::IrqGuardExit)
62}
63
64fn schedule_current_cpu_with_entry(
65 mut entry: RuntimeSchedulerEntry,
66) -> Result<SchedulerOutcome, TaskError> {
67 let original_entry = entry;
68 loop {
69 let request_scope = scheduler_request_scope(entry, original_entry);
70 let mut scheduler_frame =
71 RuntimeSchedulerFrameGuard::enter(RuntimeScheduleOrigin::Preempt, entry)?;
72 let system = scheduler_frame.task_system();
73 let current_publication = scheduler_frame.current_thread_publication();
74 let (mut outcome, no_switch_request_pending) = {
75 let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
76 let current_state = unsafe { cpu.scheduler_current_lifecycle_state() };
78 let outcome = if !cpu.scheduler_request_pending(request_scope) && !cpu.has_remote_work()
79 {
80 if current_state == Some(ThreadState::Parking) {
81 SchedulerOutcome::ParkingDeferred
82 } else {
83 SchedulerOutcome::Quiescent
84 }
85 } else {
86 let current = unsafe { current_publication.borrow_current()? };
89 unsafe {
91 system.schedule_if_requested_in_scheduler_frame(
92 cpu.as_mut(),
93 ¤t,
94 request_scope,
95 )?
96 }
97 };
98 let request_pending = match outcome.decision() {
99 Some(decision) if decision.requires_context_switch() => None,
100 Some(_) | None => Some(cpu.scheduler_request_pending(request_scope)),
101 };
102 (outcome, request_pending)
103 };
104 if let Some(decision) = outcome.decision_mut() {
105 execute_switch_plan(&mut scheduler_frame, decision);
106 }
107 let needs_reschedule = if let Some(request_pending) = no_switch_request_pending {
108 request_pending
109 } else {
110 scheduler_frame.scheduler_request_pending(request_scope)?
111 };
112 let repeat = preempt_schedule_needs_repeat(&outcome, needs_reschedule);
113 drop(scheduler_frame);
114 if !repeat {
115 return Ok(outcome);
116 }
117 entry = match entry {
118 RuntimeSchedulerEntry::IrqReturn | RuntimeSchedulerEntry::IrqReturnContinuation => {
119 RuntimeSchedulerEntry::IrqReturnContinuation
120 }
121 RuntimeSchedulerEntry::Task
122 | RuntimeSchedulerEntry::PreemptExit
123 | RuntimeSchedulerEntry::IrqGuardExit => RuntimeSchedulerEntry::Task,
124 };
125 }
126}
127
128fn scheduler_request_scope(
129 entry: RuntimeSchedulerEntry,
130 original_entry: RuntimeSchedulerEntry,
131) -> SchedulerRequestScope {
132 if matches!(
133 original_entry,
134 RuntimeSchedulerEntry::PreemptExit | RuntimeSchedulerEntry::IrqGuardExit
135 ) {
136 return SchedulerRequestScope::Immediate;
141 }
142 match entry {
143 RuntimeSchedulerEntry::Task => SchedulerRequestScope::All,
144 RuntimeSchedulerEntry::PreemptExit
145 | RuntimeSchedulerEntry::IrqReturn
146 | RuntimeSchedulerEntry::IrqGuardExit
147 | RuntimeSchedulerEntry::IrqReturnContinuation => SchedulerRequestScope::Immediate,
148 }
149}
150
151fn preempt_schedule_needs_repeat(outcome: &SchedulerOutcome, needs_reschedule: bool) -> bool {
152 needs_reschedule && !outcome.parking_deferred()
153}
154
155pub fn yield_current_cpu() -> Result<(), TaskError> {
157 #[cfg(feature = "qperf-metrics")]
158 let scheduler_started_ns = task_runtime::monotonic_now().as_nanos();
159 let mut scheduler_frame = RuntimeSchedulerFrameGuard::enter(
160 RuntimeScheduleOrigin::Yield,
161 RuntimeSchedulerEntry::Task,
162 )?;
163 #[cfg(feature = "qperf-metrics")]
164 let scheduler_frame_entered_ns = task_runtime::monotonic_now().as_nanos();
165 let system = scheduler_frame.task_system();
166 #[cfg(feature = "qperf-metrics")]
167 let scheduler_dispatch_started_ns;
168 let mut outcome = {
169 let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
170 #[cfg(feature = "qperf-metrics")]
171 {
172 scheduler_dispatch_started_ns = task_runtime::monotonic_now().as_nanos();
173 }
174 unsafe { system.yield_current_in_scheduler_frame(cpu.as_mut())? }
176 };
177 #[cfg(feature = "qperf-metrics")]
178 let scheduler_dispatch_finished_ns = task_runtime::monotonic_now().as_nanos();
179 if let Some(decision) = outcome.decision_mut() {
180 #[cfg(feature = "qperf-metrics")]
181 {
182 crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
183 7,
184 scheduler_started_ns,
185 scheduler_frame_entered_ns,
186 );
187 crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
188 8,
189 scheduler_frame_entered_ns,
190 scheduler_dispatch_started_ns,
191 );
192 crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
193 9,
194 scheduler_dispatch_started_ns,
195 scheduler_dispatch_finished_ns,
196 );
197 crate::diagnostics::counters::qperf_record_switch_phase_scheduler(
198 scheduler_started_ns,
199 scheduler_dispatch_finished_ns,
200 );
201 }
202 execute_switch_plan(&mut scheduler_frame, decision);
203 }
204 Ok(())
205}
206
207pub fn exit_current_thread() -> Result<(), TaskError> {
209 let permit = prepare_current_exit()?;
210 commit_current_exit(permit)
211}
212
213pub struct ExitPermit {
215 system: CurrentExitPermit,
216 _not_send: PhantomData<*mut ()>,
217}
218
219pub fn prepare_current_exit() -> Result<ExitPermit, TaskError> {
222 validate_schedule_context(RuntimeScheduleOrigin::Exit)?;
223 let current = current_thread_handle()?;
224 let mut irq = RuntimeIrqGuard::enter();
225 let system = runtime_task_system()?;
226 let mut cpu = runtime_current_cpu_mut(&mut irq)?;
227 let system = system.prepare_current_exit(cpu.as_mut(), ¤t)?;
228 Ok(ExitPermit {
229 system,
230 _not_send: PhantomData,
231 })
232}
233
234pub fn commit_current_exit(permit: ExitPermit) -> ! {
239 let thread = permit.system.thread();
240 let mut scheduler_frame =
241 RuntimeSchedulerFrameGuard::enter(RuntimeScheduleOrigin::Exit, RuntimeSchedulerEntry::Task)
242 .unwrap_or_else(|_| task_runtime::fatal_invariant(0x4558_0010, thread.as_u64() as _));
243 let system = scheduler_frame.task_system();
244 let mut decision = {
245 let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)
246 .unwrap_or_else(|_| task_runtime::fatal_invariant(0x4558_0013, thread.as_u64() as _));
247 unsafe { system.commit_prepared_current_exit(cpu.as_mut(), permit.system) }
249 };
250 execute_switch_plan(&mut scheduler_frame, &mut decision);
251 task_runtime::fatal_invariant(4, decision.previous().map_or(0, ThreadId::as_u64) as usize)
254}
255
256pub(crate) fn execute_switch_plan(
257 scheduler_frame: &mut RuntimeSchedulerFrameGuard,
258 decision: &mut ScheduleDecision,
259) {
260 if !decision.requires_context_switch() {
261 return;
262 }
263 #[cfg(feature = "qperf-metrics")]
264 let prepare_started_ns = task_runtime::monotonic_now().as_nanos();
265 let Some(previous) = decision.previous() else {
266 task_runtime::fatal_invariant(1, decision.next().as_u64() as usize);
267 };
268 let next = decision.next();
269 let previous_extension = {
270 let mut cpu = runtime_current_cpu_mut(scheduler_frame)
271 .unwrap_or_else(|_| task_runtime::fatal_invariant(6, next.as_u64() as usize));
272 let handoff = cpu
273 .as_mut()
274 .switch_handoff_mut()
275 .unwrap_or_else(|| task_runtime::fatal_invariant(6, next.as_u64() as usize));
276 if handoff.previous().id() != previous || handoff.incoming().id() != next {
277 task_runtime::fatal_invariant(6, next.as_u64() as usize);
278 }
279 handoff.previous().extension_view()
280 };
281 let plan = decision
282 .take_runtime_switch_plan()
283 .unwrap_or_else(|| task_runtime::fatal_invariant(6, next.as_u64() as usize));
284 #[cfg(feature = "qperf-metrics")]
285 let switch_validate_finished_ns = task_runtime::monotonic_now().as_nanos();
286 let trace_wake = task_runtime::trace_sched_switch(SchedSwitchRecord {
291 cpu: scheduler_frame.cpu_id(),
292 previous_thread: previous.as_u64(),
293 next_thread: next.as_u64(),
294 timestamp_ns: decision.timestamp_ns(),
295 reason: decision.switch_reason() as u32,
296 });
297 if let Some(wake) = trace_wake {
298 let mut cpu = runtime_current_cpu_mut(scheduler_frame)
299 .unwrap_or_else(|_| task_runtime::fatal_invariant(6, next.as_u64() as usize));
300 cpu.as_mut()
301 .switch_handoff_mut()
302 .unwrap_or_else(|| task_runtime::fatal_invariant(6, next.as_u64() as usize))
303 .install_trace_wake(wake);
304 }
305 #[cfg(feature = "qperf-metrics")]
306 let switch_trace_finished_ns = task_runtime::monotonic_now().as_nanos();
307 if let Some(extension) = previous_extension {
308 unsafe {
312 (extension.ops().on_switch_out)(extension.data(), previous, decision.switch_reason())
313 };
314 }
315 #[cfg(feature = "qperf-metrics")]
316 let switch_out_hook_finished_ns = task_runtime::monotonic_now().as_nanos();
317 #[cfg(feature = "qperf-metrics")]
318 crate::diagnostics::counters::record_context_switch(decision.switch_reason());
319 #[cfg(feature = "qperf-metrics")]
320 let switch_accounting_finished_ns = task_runtime::monotonic_now().as_nanos();
321 #[cfg(feature = "qperf-metrics")]
322 let plan = {
323 crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
324 26,
325 prepare_started_ns,
326 switch_validate_finished_ns,
327 );
328 crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
329 27,
330 switch_validate_finished_ns,
331 switch_trace_finished_ns,
332 );
333 crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
334 28,
335 switch_trace_finished_ns,
336 switch_out_hook_finished_ns,
337 );
338 crate::diagnostics::counters::qperf_record_switch_scheduler_detail(
339 29,
340 switch_out_hook_finished_ns,
341 switch_accounting_finished_ns,
342 );
343 let mut plan = plan;
344 plan.set_qperf_prepare_started_ns(prepare_started_ns);
345 plan
346 };
347 unsafe { task_runtime::switch_context(plan) };
351 scheduler_frame.refresh_current_cpu();
352 if unsafe { complete_current_context_switch_tail_in_scheduler_frame(scheduler_frame) }.is_err()
355 {
356 task_runtime::fatal_invariant(5, 0);
357 }
358}
359
360pub(crate) unsafe fn complete_current_context_switch_tail(
369 pin: &mut impl RuntimeCpuPin,
370) -> Result<(), TaskError> {
371 let system = runtime_task_system()?;
372 unsafe { finish_switch_tail(system, pin) }
374}
375
376unsafe fn complete_current_context_switch_tail_in_scheduler_frame(
383 scheduler_frame: &mut RuntimeSchedulerFrameGuard,
384) -> Result<(), TaskError> {
385 let system = scheduler_frame.task_system();
386 unsafe { finish_switch_tail(system, scheduler_frame) }
388}
389
390unsafe fn finish_switch_tail(
395 system: &crate::runtime::TaskSystem,
396 pin: &mut impl RuntimeCpuPin,
397) -> Result<(), TaskError> {
398 let completion = {
399 let mut cpu = runtime_current_cpu_mut(pin)?;
400 unsafe { system.complete_context_switch_in_scheduler_frame(cpu.as_mut())? }
402 };
403 completion.finish();
404 Ok(())
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn preempt_exit_continuation_does_not_consume_lazy_requests() {
413 assert_eq!(
414 scheduler_request_scope(
415 RuntimeSchedulerEntry::Task,
416 RuntimeSchedulerEntry::PreemptExit,
417 ),
418 SchedulerRequestScope::Immediate
419 );
420 assert_eq!(
421 scheduler_request_scope(
422 RuntimeSchedulerEntry::Task,
423 RuntimeSchedulerEntry::IrqGuardExit,
424 ),
425 SchedulerRequestScope::Immediate
426 );
427 assert_eq!(
428 scheduler_request_scope(RuntimeSchedulerEntry::Task, RuntimeSchedulerEntry::Task),
429 SchedulerRequestScope::All
430 );
431 }
432}