ax_task/runtime/switch.rs
1//! Context-switch plans, entry contracts and completion.
2
3use alloc::sync::Arc;
4use core::{marker::PhantomData, ptr::NonNull};
5
6pub use crate::runtime::switch::dispatch::{
7 schedule_current_cpu, schedule_current_cpu_from_irq_guard_exit,
8 schedule_current_cpu_from_preempt_exit,
9};
10use crate::{
11 runtime::{
12 RuntimeStatus, TaskSystemHandle,
13 context::{RuntimeIrqGuard, validate_task_context},
14 cpu::{CurrentCpuOwnerHandles, RuntimeCpuId},
15 resource::{AddressSpaceHandle, AddressSpaceMembarrierId, ExecutionContextHandle},
16 switch::dispatch::complete_current_context_switch_tail,
17 task_runtime,
18 },
19 thread::TaskError,
20};
21
22/// Completes switch tail and consumes the inherited IRQ guard on first entry.
23///
24/// Fresh context trampolines must invoke this before accessing thread-local
25/// state, enabling interrupts, polling futures, or calling user/OS code.
26/// Resumed contexts must not call it because their suspended scheduler guard
27/// consumes the same baton when the architecture switch returns.
28///
29/// # Safety
30///
31/// The caller must be the first instruction sequence of a freshly switched-in
32/// context. Exactly one scheduler IRQ guard must be inherited on this CPU, and
33/// this function must be called exactly once for that context's first entry.
34pub unsafe fn finish_initial_context_switch() -> Result<(), TaskError> {
35 validate_task_context()?;
36 let mut irq = RuntimeIrqGuard::enter();
37 // SAFETY: this trampoline inherits the transferred scheduler baton and
38 // the runtime IRQ guard retains its raw IRQ-off state through completion.
39 unsafe { complete_current_context_switch_tail(&mut irq)? };
40 drop(irq);
41 task_runtime::finish_initial_context_switch();
42 Ok(())
43}
44pub(crate) mod dispatch;
45
46use crate::runtime::handle::opaque_handle;
47
48opaque_handle!(
49 /// Opaque pointer to the Arc-backed scheduler core of the current thread.
50 ///
51 /// This value is useful only as part of a runtime-provided
52 /// [`CurrentThreadPublication`]. The scheduler may acquire a strong handle
53 /// from it only while a preemption pin proves that the published thread is
54 /// still current and therefore retains its owner-side strong reference.
55 CurrentThreadOwnerHandle,
56 "runtime::switch"
57);
58
59/// Move-only runtime transaction for one committed scheduler switch.
60///
61/// ax-task constructs this value only after the scheduler has committed two
62/// distinct live endpoints and released its internal locks. The execution
63/// contexts and logical address spaces travel through one runtime call, so a
64/// provider cannot activate an `mm` and then fail before preparing the matching
65/// architecture context. Consuming the transaction prevents replay.
66#[derive(Debug, Eq, PartialEq)]
67#[repr(C)]
68pub struct RuntimeSwitchPlan {
69 previous_context: ExecutionContextHandle,
70 previous_address_space: AddressSpaceHandle,
71 next_context: ExecutionContextHandle,
72 next_address_space: AddressSpaceHandle,
73 #[cfg(feature = "qperf-metrics")]
74 qperf_prepare_started_ns: u64,
75}
76
77impl RuntimeSwitchPlan {
78 pub(crate) fn new(
79 previous_context: ExecutionContextHandle,
80 previous_address_space: AddressSpaceHandle,
81 previous_address_space_identity: AddressSpaceMembarrierId,
82 next_context: ExecutionContextHandle,
83 next_address_space: AddressSpaceHandle,
84 next_address_space_identity: AddressSpaceMembarrierId,
85 ) -> Option<Self> {
86 debug_assert_eq!(
87 previous_address_space.is_none(),
88 previous_address_space_identity.is_none(),
89 );
90 debug_assert_eq!(
91 next_address_space.is_none(),
92 next_address_space_identity.is_none(),
93 );
94 debug_assert!(
95 previous_address_space != next_address_space
96 || previous_address_space_identity == next_address_space_identity,
97 );
98 if previous_context.is_none() || next_context.is_none() || previous_context == next_context
99 {
100 None
101 } else {
102 let same_address_space = !previous_address_space_identity.is_none()
103 && previous_address_space_identity == next_address_space_identity;
104 Some(Self {
105 previous_context,
106 previous_address_space: if same_address_space {
107 next_address_space
108 } else {
109 previous_address_space
110 },
111 next_context,
112 next_address_space,
113 #[cfg(feature = "qperf-metrics")]
114 qperf_prepare_started_ns: 0,
115 })
116 }
117 }
118
119 /// Returns the outgoing runtime context.
120 pub const fn previous_context(&self) -> ExecutionContextHandle {
121 self.previous_context
122 }
123
124 /// Returns the outgoing logical address space, canonicalized to the
125 /// incoming live token when both endpoints select the same `mm`.
126 pub const fn previous_address_space(&self) -> AddressSpaceHandle {
127 self.previous_address_space
128 }
129
130 /// Returns the incoming runtime context.
131 pub const fn next_context(&self) -> ExecutionContextHandle {
132 self.next_context
133 }
134
135 /// Returns the incoming scheduler-selected logical address space.
136 pub const fn next_address_space(&self) -> AddressSpaceHandle {
137 self.next_address_space
138 }
139
140 /// Returns whether both scheduler endpoints select the same logical `mm`.
141 pub const fn same_address_space(&self) -> bool {
142 !self.next_address_space.is_none()
143 && self.previous_address_space.into_raw() == self.next_address_space.into_raw()
144 }
145
146 #[cfg(feature = "qperf-metrics")]
147 pub(crate) fn set_qperf_prepare_started_ns(&mut self, started_ns: u64) {
148 self.qperf_prepare_started_ns = started_ns;
149 }
150
151 #[doc(hidden)]
152 #[cfg(feature = "qperf-metrics")]
153 pub const fn qperf_prepare_started_ns(&self) -> u64 {
154 self.qperf_prepare_started_ns
155 }
156}
157
158/// Immutable scheduler snapshot of one thread's runtime switch bindings.
159///
160/// Linux keeps the architecture context and `mm` selected by the rq transition
161/// reachable without taking a second task lock after `pick_next_task()`. The
162/// ax-task owner rq follows the same rule: task-control code republishes this
163/// value whenever the binding changes, and the switch plan consumes only the
164/// rq-owned snapshot.
165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
166pub(crate) struct ThreadRuntimeBinding {
167 context: ExecutionContextHandle,
168 address_space: AddressSpaceHandle,
169}
170
171impl ThreadRuntimeBinding {
172 pub(crate) const fn new(
173 context: ExecutionContextHandle,
174 address_space: AddressSpaceHandle,
175 ) -> Self {
176 Self {
177 context,
178 address_space,
179 }
180 }
181
182 pub(crate) const fn context(self) -> ExecutionContextHandle {
183 self.context
184 }
185
186 pub(crate) const fn address_space(self) -> AddressSpaceHandle {
187 self.address_space
188 }
189}
190
191/// Scheduler entry whose context constraints the runtime must validate.
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193#[repr(u32)]
194pub enum RuntimeScheduleOrigin {
195 /// A thread is about to publish or commit a blocking state.
196 Block = 0,
197 /// A thread voluntarily yields its remaining service.
198 Yield = 1,
199 /// A thread permanently exits.
200 Exit = 2,
201 /// A sticky preemption request is serviced from task context.
202 Preempt = 3,
203}
204
205/// Typed source of one scheduler-frame baton.
206///
207/// The runtime uses this value to validate and atomically transform its
208/// CPU-local preemption state. In particular, preemption-guard exits retain
209/// their final lock depth until the scheduler frame owns the baton, closing the
210/// interrupt window between enabling preemption and entering the scheduler.
211#[derive(Clone, Copy, Debug, Eq, PartialEq)]
212#[repr(u32)]
213pub enum RuntimeSchedulerEntry {
214 /// Ordinary task context with IRQs enabled and no preemption guard.
215 Task = 0,
216 /// Final task-context preemption guard exit with IRQs disabled.
217 ///
218 /// The runtime retains the final preemption depth while it disables raw
219 /// IRQs, then atomically converts that depth into the scheduler baton.
220 PreemptExit = 1,
221 /// Final IRQ-return preemption guard exit with IRQs still disabled.
222 IrqReturn = 2,
223 /// Final task-context IRQ publication guard exit with IRQs disabled.
224 ///
225 /// The runtime retains the final IRQ-guard depth after publishing local
226 /// scheduler work, then atomically converts that depth into the scheduler
227 /// baton. This is the local counterpart of a remote scheduler IPI.
228 IrqGuardExit = 3,
229 /// A repeated IRQ-return pass after the previous scheduler frame fully
230 /// released its switch baton.
231 ///
232 /// The caller enters with hardware IRQs disabled and preemption depth zero.
233 /// Before claiming the fresh scheduler baton, the runtime establishes one
234 /// ordinary preemption depth, opens the Linux-style IRQ window, disables
235 /// IRQs again, and atomically converts that depth into the scheduler baton.
236 IrqReturnContinuation = 4,
237}
238
239/// Raw IRQ state expected by the suspended scheduler continuation.
240///
241/// This is continuation-local rather than CPU-local: a context resumed by an
242/// IRQ-return schedule may itself have been suspended in an ordinary task
243/// schedule, and vice versa.
244#[derive(Clone, Copy, Debug, Eq, PartialEq)]
245#[repr(u32)]
246pub enum RuntimeSchedulerReturn {
247 /// Resume ordinary task context with local IRQs enabled.
248 Task = 0,
249 /// Resume the architecture trap epilogue with local IRQs disabled.
250 IrqReturn = 1,
251}
252
253/// Versioned generation-bearing thread identity for runtime context binding.
254///
255/// The explicit fields keep the scheduler's private integer encoding out of OS
256/// runtime implementations while remaining a value-only trait-FFI type.
257#[derive(Clone, Copy, Debug, Eq, PartialEq)]
258#[repr(C)]
259pub struct ThreadIdentityV1 {
260 /// Task-system registry slot.
261 pub slot: u32,
262 /// Non-zero reuse generation for `slot`.
263 pub generation: u32,
264}
265
266/// Immutable scheduler publication owned by one runtime execution context.
267///
268/// This is the Rust equivalent of Linux's architecture-selected `current`
269/// pointer: the identity and its Arc-backed owner address are installed once
270/// before the context can run, then remain immutable across preemption and
271/// migration. The owner address is never a standalone weak or strong handle.
272#[derive(Clone, Copy, Debug, Eq, PartialEq)]
273#[repr(C)]
274pub struct CurrentThreadPublication {
275 identity: ThreadIdentityV1,
276 owner: CurrentThreadOwnerHandle,
277}
278
279/// Atomic runtime result of claiming one scheduler frame.
280///
281/// A successful result carries every immutable capability selected under the
282/// same IRQ-off CPU pin: task system and owner CPU endpoints. Current-thread
283/// identity is read only by operations that need it, while this frame keeps
284/// the execution context pinned; scheduler selection itself uses `rq->curr`.
285#[derive(Clone, Copy, Debug, Eq, PartialEq)]
286#[repr(C)]
287pub struct RuntimeSchedulerFrameEnterResult {
288 system: TaskSystemHandle,
289 cpu: CurrentCpuOwnerHandles,
290}
291
292impl RuntimeSchedulerFrameEnterResult {
293 /// Creates a successful scheduler-frame capability snapshot.
294 ///
295 /// # Safety
296 ///
297 /// `system` must be non-empty. All handles must describe the CPU pinned by
298 /// the scheduler baton that was claimed in the same runtime transaction.
299 pub const unsafe fn success(system: TaskSystemHandle, cpu: CurrentCpuOwnerHandles) -> Self {
300 Self { system, cpu }
301 }
302
303 /// Creates an unsafe-context rejection without live capabilities.
304 pub const fn failure() -> Self {
305 Self {
306 system: TaskSystemHandle::NONE,
307 cpu: CurrentCpuOwnerHandles::NONE,
308 }
309 }
310
311 /// Returns the runtime entry status.
312 pub const fn status(self) -> RuntimeStatus {
313 if self.system.is_none() {
314 RuntimeStatus::UnsafeContext
315 } else {
316 RuntimeStatus::Success
317 }
318 }
319
320 /// Returns the pinned task-system capability.
321 pub const fn system(self) -> TaskSystemHandle {
322 self.system
323 }
324
325 /// Returns the pinned owner-CPU capability.
326 pub const fn cpu(self) -> CurrentCpuOwnerHandles {
327 self.cpu
328 }
329}
330
331/// Borrowed view of the scheduler-owned current-thread reference.
332///
333/// Unlike [`crate::thread::ThreadHandle`], this capability does not acquire an
334/// external lifetime lease. It is confined to the current execution context;
335/// the architecture publication and scheduler-owned `rq->curr` reference keep
336/// the pointed-to core alive until the synchronous operation returns.
337pub(crate) struct CurrentThreadRef {
338 identity: crate::thread::ThreadId,
339 core: NonNull<crate::thread::ThreadCore>,
340 _not_send: PhantomData<*mut ()>,
341}
342
343impl CurrentThreadRef {
344 pub(crate) const fn id(&self) -> crate::thread::ThreadId {
345 self.identity
346 }
347
348 pub(crate) fn runtime_core(&self) -> &crate::thread::ThreadCore {
349 // SAFETY: construction validates the current publication while the
350 // scheduler retains its owner-side reference. The borrow cannot
351 // outlive this non-Send capability.
352 unsafe { self.core.as_ref() }
353 }
354}
355
356impl CurrentThreadPublication {
357 /// Sentinel returned by an unbound bootstrap execution context.
358 pub const NONE: Self = Self {
359 identity: ThreadIdentityV1::NONE,
360 owner: CurrentThreadOwnerHandle::NONE,
361 };
362
363 /// Returns the generation-bearing scheduler identity.
364 pub const fn identity(self) -> ThreadIdentityV1 {
365 self.identity
366 }
367
368 /// Returns the opaque current-owner address.
369 pub const fn owner(self) -> CurrentThreadOwnerHandle {
370 self.owner
371 }
372
373 pub(crate) fn from_core(
374 identity: crate::thread::ThreadId,
375 core: &Arc<crate::thread::ThreadCore>,
376 ) -> Self {
377 let owner = Arc::as_ptr(core).expose_provenance();
378 // SAFETY: `core` supplies the live Arc allocation. Consumers may use
379 // this address only through the checked current-publication accessors
380 // while the matching runtime context remains the executing task.
381 let owner = unsafe { CurrentThreadOwnerHandle::from_raw(owner) };
382 Self {
383 identity: ThreadIdentityV1::new(identity.slot(), identity.generation()),
384 owner,
385 }
386 }
387
388 /// Borrows the scheduler-owned current reference without creating an
389 /// external handle or changing any Arc count.
390 ///
391 /// # Safety
392 ///
393 /// The runtime must have copied this publication from the architecture-
394 /// selected current context. The caller must use the returned capability
395 /// only in the synchronous operation of that context and must not exit the
396 /// thread while it remains live.
397 pub(crate) unsafe fn borrow_current(
398 self,
399 ) -> Result<CurrentThreadRef, crate::thread::TaskError> {
400 if !self.identity.is_bound() {
401 return Err(crate::thread::TaskError::NoRunnableThread);
402 }
403 let core = NonNull::new(core::ptr::with_exposed_provenance_mut::<
404 crate::thread::ThreadCore,
405 >(self.owner.into_raw()))
406 .ok_or(crate::thread::TaskError::InvalidRuntimeHandle)?;
407 let identity =
408 crate::thread::ThreadId::from_parts(self.identity.slot, self.identity.generation);
409 let current = CurrentThreadRef {
410 identity,
411 core,
412 _not_send: PhantomData,
413 };
414 if current.runtime_core().id() != identity {
415 return Err(crate::thread::TaskError::InvalidRuntimeHandle);
416 }
417 Ok(current)
418 }
419
420 /// Acquires an ordinary external scheduler handle from the current
421 /// context's owner publication.
422 ///
423 /// # Safety
424 ///
425 /// The runtime must have copied the publication from the architecture-
426 /// selected current task context. The scheduler must retain that thread's
427 /// owner-side `Arc` while the caller can execute or resume this operation.
428 pub(crate) unsafe fn acquire_handle(
429 self,
430 ) -> Result<crate::thread::ThreadHandle, crate::thread::TaskError> {
431 let core = unsafe {
432 // SAFETY: this method has the same current-context ownership
433 // contract as `acquire_scheduler_core`.
434 self.acquire_scheduler_core()?
435 };
436 Ok(crate::thread::ThreadHandle::from_core(core))
437 }
438
439 /// Acquires a scheduler-internal strong reference without publishing an
440 /// external management lifetime lease.
441 ///
442 /// # Safety
443 ///
444 /// The runtime must have copied the publication from the architecture-
445 /// selected current task context. The scheduler must retain that thread's
446 /// owner-side `Arc` while the caller can execute or resume this operation.
447 pub(crate) unsafe fn acquire_scheduler_core(
448 self,
449 ) -> Result<Arc<crate::thread::ThreadCore>, crate::thread::TaskError> {
450 if !self.identity.is_bound() {
451 return Err(crate::thread::TaskError::NoRunnableThread);
452 }
453 if self.owner.is_none() {
454 return Err(crate::thread::TaskError::InvalidRuntimeHandle);
455 }
456 let core =
457 core::ptr::with_exposed_provenance::<crate::thread::ThreadCore>(self.owner.into_raw());
458 // SAFETY: the current-task publication contract proves that an owner-
459 // side strong reference remains live across preemption and migration.
460 unsafe { Arc::increment_strong_count(core) };
461 // SAFETY: the increment above created exactly one strong reference for
462 // this reconstruction.
463 let core = unsafe { Arc::from_raw(core) };
464 let expected =
465 crate::thread::ThreadId::from_parts(self.identity.slot, self.identity.generation);
466 if core.id() != expected {
467 return Err(crate::thread::TaskError::InvalidRuntimeHandle);
468 }
469 Ok(core)
470 }
471}
472
473impl ThreadIdentityV1 {
474 /// Sentinel returned before a runtime context is bound to a scheduler thread.
475 pub const NONE: Self = Self {
476 slot: 0,
477 generation: 0,
478 };
479
480 /// Creates a runtime identity from its explicit generation-bearing parts.
481 pub const fn new(slot: u32, generation: u32) -> Self {
482 Self { slot, generation }
483 }
484
485 /// Returns whether this value names a published scheduler generation.
486 pub const fn is_bound(self) -> bool {
487 self.generation != 0
488 }
489}
490
491/// Immutable association between one runtime context and scheduler ownership.
492///
493/// Contexts are created before the scheduler allocates a generation-bearing
494/// thread ID. The scheduler submits this value exactly once after ID allocation
495/// and before the thread can become runnable. The publication keeps only a
496/// pointer-sized owner address; it does not transfer an Arc or external reaper
497/// lease across the trait-FFI boundary.
498#[derive(Clone, Copy, Debug, Eq, PartialEq)]
499#[repr(C)]
500pub struct ContextThreadBinding {
501 /// Live runtime-owned execution context to bind.
502 pub context: ExecutionContextHandle,
503 /// Immutable current-thread publication for this execution context.
504 pub publication: CurrentThreadPublication,
505}
506
507/// Allocation-free scheduler switch diagnostic record.
508#[derive(Clone, Copy, Debug, Eq, PartialEq)]
509#[repr(C)]
510pub struct SchedSwitchRecord {
511 /// Logical CPU performing the switch.
512 pub cpu: RuntimeCpuId,
513 /// Previous generation-based thread identifier encoded as a scalar.
514 pub previous_thread: u64,
515 /// Next generation-based thread identifier encoded as a scalar.
516 pub next_thread: u64,
517 /// Monotonic switch timestamp.
518 pub timestamp_ns: u64,
519 /// Policy-specific reason code defined by ax-task.
520 pub reason: u32,
521}
522
523pub use crate::sched::system::{
524 ScheduleDecision, SchedulerOutcome, SwitchInCompletion, YieldOutcome,
525};
526
527#[cfg(test)]
528mod switch_plan_tests {
529 use super::*;
530
531 #[test]
532 fn runtime_switch_plan_keeps_context_and_logical_mm_in_one_transaction() {
533 // SAFETY: opaque values are never dereferenced by this value-only
534 // contract test.
535 let previous_context = unsafe { ExecutionContextHandle::from_raw(0x1000) };
536 // SAFETY: see above.
537 let next_context = unsafe { ExecutionContextHandle::from_raw(0x2000) };
538 // SAFETY: see above.
539 let previous_mm = unsafe { AddressSpaceHandle::from_raw(0x3000) };
540 // SAFETY: see above.
541 let next_mm = unsafe { AddressSpaceHandle::from_raw(0x4000) };
542 // SAFETY: opaque values are compared only and never dereferenced.
543 let previous_mm_identity = unsafe { AddressSpaceMembarrierId::from_raw(0x5000) };
544 // SAFETY: see above.
545 let next_mm_identity = unsafe { AddressSpaceMembarrierId::from_raw(0x6000) };
546 let plan = RuntimeSwitchPlan::new(
547 previous_context,
548 previous_mm,
549 previous_mm_identity,
550 next_context,
551 next_mm,
552 next_mm_identity,
553 )
554 .expect("two distinct live contexts must form one runtime switch plan");
555
556 assert_eq!(plan.previous_context(), previous_context);
557 assert_eq!(plan.previous_address_space(), previous_mm);
558 assert_eq!(plan.next_context(), next_context);
559 assert_eq!(plan.next_address_space(), next_mm);
560 }
561}