1use alloc::boxed::Box;
2use core::{
3 cell::UnsafeCell,
4 mem::offset_of,
5 pin::Pin,
6 ptr::{self, NonNull},
7};
8
9use ax_hal::{
10 cpu::context::TaskAnchor,
11 percpu::{CpuPin, ExecutionContextHeader, PreparedContextSwitch, PreviousContextBinding},
12};
13use ax_task::{
14 runtime::{
15 RuntimeHandleResult, RuntimeStatus,
16 resource::{ExecutionContextHandle, KernelContextRequest, StackHandle, UserContextRequest},
17 switch::{
18 ContextThreadBinding, CurrentThreadPublication, RuntimeSwitchPlan, ThreadIdentityV1,
19 },
20 },
21 thread::TaskError,
22};
23
24use super::{
25 resources::{RuntimeStack, runtime_tls_pointer},
26 runtime_status_error, with_current_cpu_pin,
27};
28
29pub fn diagnose_current_stack_guard_page_fault(fault: ax_memory_addr::VirtAddr) -> bool {
31 #[cfg(feature = "stack-guard-page")]
32 {
33 unsafe {
36 with_current_cpu_pin(|cpu_pin| {
37 let Ok(context) = current_runtime_context(cpu_pin) else {
38 return false;
39 };
40 let stack = context.stack.into_raw();
41 if stack == 0 {
42 return false;
43 }
44 let stack = &*ptr::with_exposed_provenance::<RuntimeStack>(stack);
47 let super::resources::StackBacking::VirtualPages(allocation) = &stack.backing
48 else {
49 return false;
50 };
51 let guard_start = allocation.reservation_range().start.as_usize();
52 let guard_end = allocation.usable_range().start.as_usize();
53 if !(guard_start..guard_end).contains(&fault.as_usize()) {
54 return false;
55 }
56 error!(
57 "task stack guard page hit: fault_addr={:#x}, stack=[{:#x}..{:#x}), \
58 guard=[{:#x}..{:#x})",
59 fault.as_usize(),
60 guard_end,
61 stack.usable_top,
62 guard_start,
63 guard_end,
64 );
65 true
66 })
67 }
68 }
69 #[cfg(not(feature = "stack-guard-page"))]
70 {
71 let _ = fault;
72 false
73 }
74}
75
76struct RuntimeSwitchTail {
77 previous: NonNull<ExecutionContextHeader>,
78 binding: PreviousContextBinding,
79 #[cfg(feature = "qperf-metrics")]
80 qperf_runtime_tail_started_ns: u64,
81 #[cfg(feature = "qperf-metrics")]
82 qperf_switch_started_ns: u64,
83}
84
85#[repr(C)]
92struct RuntimeContext {
93 header: ExecutionContextHeader,
94 publication: UnsafeCell<CurrentThreadPublication>,
95 inner: Box<UnsafeCell<ax_hal::context::TaskContext>>,
96 stack: StackHandle,
97 switch_tail: UnsafeCell<Option<RuntimeSwitchTail>>,
98}
99
100#[derive(Clone, Copy)]
101enum InitialPreemptionState {
102 Enabled,
103 BootstrapDisabled,
104}
105
106const _: () = assert!(offset_of!(RuntimeContext, header) == 0);
107
108impl RuntimeContext {
109 fn allocate(
110 inner: ax_hal::context::TaskContext,
111 stack: StackHandle,
112 preemption: InitialPreemptionState,
113 ) -> Result<*mut RuntimeContext, RuntimeStatus> {
114 let inner = super::allocation::try_box(UnsafeCell::new(inner))?;
115 let header = match preemption {
116 InitialPreemptionState::Enabled => ExecutionContextHeader::new(),
117 InitialPreemptionState::BootstrapDisabled => ExecutionContextHeader::new_bootstrap(),
118 };
119 Ok(Box::into_raw(super::allocation::try_box(Self {
120 header,
121 publication: UnsafeCell::new(CurrentThreadPublication::NONE),
122 inner,
123 stack,
124 switch_tail: UnsafeCell::new(None),
125 })?))
126 }
127
128 fn header(&self) -> Pin<&ExecutionContextHeader> {
129 unsafe { Pin::new_unchecked(&self.header) }
132 }
133
134 fn has_switch_tail(&self) -> bool {
135 unsafe { (*self.switch_tail.get()).is_some() }
138 }
139
140 unsafe fn stage_switch_tail(&self, tail: RuntimeSwitchTail) -> Result<(), RuntimeStatus> {
141 let slot = unsafe { &mut *self.switch_tail.get() };
144 if slot.is_some() {
145 return Err(RuntimeStatus::Busy);
146 }
147 *slot = Some(tail);
148 Ok(())
149 }
150
151 unsafe fn finish_switch_tail(&self) -> (u64, u64) {
152 let slot = unsafe { &mut *self.switch_tail.get() };
155 let tail = slot
156 .take()
157 .expect("incoming runtime context is missing its switch tail");
158 let previous = unsafe { Pin::new_unchecked(tail.previous.as_ref()) };
161 unsafe { tail.binding.finish(previous) }
162 .expect("runtime switch tail did not own the exact previous CPU binding");
163 #[cfg(feature = "qperf-metrics")]
164 return (
165 tail.qperf_runtime_tail_started_ns,
166 tail.qperf_switch_started_ns,
167 );
168 #[cfg(not(feature = "qperf-metrics"))]
169 (0, 0)
170 }
171}
172
173fn runtime_context(
174 handle: ExecutionContextHandle,
175) -> Result<&'static RuntimeContext, RuntimeStatus> {
176 if handle.is_none() {
177 return Err(RuntimeStatus::InvalidHandle);
178 }
179 let context = ptr::with_exposed_provenance::<RuntimeContext>(handle.into_raw());
180 let context = unsafe { &*context };
183 if !ptr::eq(
184 ptr::addr_of!(context.header),
185 context as *const RuntimeContext as *const ExecutionContextHeader,
186 ) {
187 return Err(RuntimeStatus::InvalidHandle);
188 }
189 Ok(context)
190}
191
192fn current_runtime_context(cpu_pin: &CpuPin) -> Result<&'static RuntimeContext, RuntimeStatus> {
193 let current = ax_hal::percpu::current_context(cpu_pin)
194 .map_err(|_| RuntimeStatus::InvalidHandle)?
195 .as_ptr()
196 .expose_provenance();
197 let header = ptr::with_exposed_provenance::<ExecutionContextHeader>(current);
198 let header = unsafe { &*header };
201 let context = unsafe { &*ptr::from_ref(header).cast::<RuntimeContext>() };
205 if !ptr::eq(context.header().get_ref(), header) {
206 return Err(RuntimeStatus::InvalidHandle);
207 }
208 Ok(context)
209}
210
211#[cfg(feature = "uspace")]
213pub(super) struct RuntimeUserBinding {
214 #[cfg(all(target_arch = "x86_64", feature = "fp-simd"))]
215 context: NonNull<RuntimeContext>,
216}
217
218#[cfg(feature = "uspace")]
219impl RuntimeUserBinding {
220 pub(super) fn prepare_user_fp_return(&self) {
221 #[cfg(all(target_arch = "x86_64", feature = "fp-simd"))]
222 {
223 let context = unsafe { self.context.as_ref() };
227 let architecture_context = unsafe { &*context.inner.get() };
228 architecture_context.prepare_user_return_fp();
229 }
230 }
231}
232
233#[cfg(feature = "uspace")]
234pub(super) fn bind_current_user_context(
235 cpu_pin: &CpuPin<'_>,
236) -> Result<RuntimeUserBinding, RuntimeStatus> {
237 let context = current_runtime_context(cpu_pin)?;
238 if context.has_switch_tail() {
239 return Err(RuntimeStatus::UnsafeContext);
240 }
241 let publication = unsafe { *context.publication.get() };
244 if !publication.identity().is_bound() || publication.owner().is_none() {
245 return Err(RuntimeStatus::InvalidHandle);
246 }
247 Ok(RuntimeUserBinding {
248 #[cfg(all(target_arch = "x86_64", feature = "fp-simd"))]
249 context: NonNull::from(context),
250 })
251}
252
253pub(super) fn bind_bootstrap_runtime_context(
254 cpu_pin: &CpuPin,
255 handle: ExecutionContextHandle,
256 kernel_tls: usize,
257) -> Result<(), TaskError> {
258 let boot_context =
259 ax_hal::percpu::current_context(cpu_pin).map_err(|_| TaskError::InvalidConfiguration)?;
260 if !ax_hal::percpu::is_permanent_boot_context(boot_context)
261 .map_err(|_| TaskError::InvalidConfiguration)?
262 {
263 return Err(TaskError::InvalidConfiguration);
264 }
265 let context = runtime_context(handle).map_err(runtime_status_error)?;
266 unsafe { ax_hal::percpu::install_bootstrap_context(cpu_pin, context.header()) }
269 .map_err(|_| TaskError::InvalidConfiguration)?;
270 #[cfg(kernel_tls)]
271 unsafe {
273 ax_hal::percpu::install_bootstrap_kernel_tls(
274 cpu_pin,
275 ax_hal::context::KernelTlsBase::new(kernel_tls),
276 );
277 }
278 #[cfg(not(kernel_tls))]
279 assert_eq!(
280 kernel_tls, 0,
281 "TLS-disabled bootstrap must retain a zero TLS identity"
282 );
283 Ok(())
284}
285
286pub(super) fn finish_runtime_context_switch_tail() -> bool {
287 #[cfg(feature = "qperf-metrics")]
288 let qperf_incoming_tail_started_ns = crate::clock_event_runtime::monotonic_now().as_nanos();
289 let (_qperf_runtime_tail_started_ns, _qperf_switch_started_ns) = unsafe {
292 with_current_cpu_pin(|cpu_pin| {
293 let current = current_runtime_context(cpu_pin)
294 .expect("incoming scheduler context is not runtime-owned");
295 current.finish_switch_tail()
297 })
298 };
299 #[cfg(feature = "qperf-metrics")]
300 let qperf_incoming_tail_finished_ns = crate::clock_event_runtime::monotonic_now().as_nanos();
301 #[cfg(feature = "qperf-metrics")]
302 {
303 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
304 21,
305 _qperf_switch_started_ns,
306 qperf_incoming_tail_started_ns,
307 );
308 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
309 22,
310 qperf_incoming_tail_started_ns,
311 qperf_incoming_tail_finished_ns,
312 );
313 ax_task::diagnostics::qperf_record_switch_phase_runtime_tail(
314 _qperf_runtime_tail_started_ns,
315 qperf_incoming_tail_finished_ns,
316 );
317 }
318 super::address_space::take_context_switch_reclaim_ready()
319}
320
321pub(super) fn create_runtime_context(request: KernelContextRequest) -> RuntimeHandleResult {
322 create_runtime_context_parts(request.stack, request.entry, request.tls)
323}
324
325pub(super) fn create_user_runtime_context(request: UserContextRequest) -> RuntimeHandleResult {
326 #[cfg(not(feature = "uspace"))]
327 {
328 let _ = request;
329 RuntimeHandleResult::failure(RuntimeStatus::Unsupported)
330 }
331 #[cfg(feature = "uspace")]
332 {
333 create_runtime_context_parts(request.stack, request.entry, request.tls)
334 }
335}
336
337fn create_runtime_context_parts(
338 stack_handle: StackHandle,
339 entry: ax_task::runtime::resource::KernelEntry,
340 tls_handle: ax_task::runtime::resource::TlsHandle,
341) -> RuntimeHandleResult {
342 #[cfg(feature = "fault-injection")]
343 if super::creation_probe::record(super::creation_probe::CreationEvent::Context) {
344 return RuntimeHandleResult::failure(RuntimeStatus::NoMemory);
345 }
346 if stack_handle.is_none() {
347 return RuntimeHandleResult::failure(RuntimeStatus::InvalidHandle);
348 }
349 let stack = unsafe { &*ptr::with_exposed_provenance::<RuntimeStack>(stack_handle.into_raw()) };
351 let tls_pointer = runtime_tls_pointer(tls_handle);
352 let mut context = ax_hal::context::TaskContext::new();
353 context.init(
354 entry as usize,
355 ax_memory_addr::VirtAddr::from(stack.usable_top),
356 ax_hal::context::KernelTlsBase::new(tls_pointer),
357 );
358 match RuntimeContext::allocate(context, stack_handle, InitialPreemptionState::Enabled) {
359 Ok(context) => RuntimeHandleResult::success(context.expose_provenance()),
360 Err(status) => RuntimeHandleResult::failure(status),
361 }
362}
363
364pub(super) fn create_bootstrap_context() -> ExecutionContextHandle {
365 let context = ax_hal::context::TaskContext::new();
366 let context = RuntimeContext::allocate(
367 context,
368 StackHandle::NONE,
369 InitialPreemptionState::BootstrapDisabled,
370 )
371 .expect("bootstrap context allocation failed");
372 unsafe { ExecutionContextHandle::from_raw(context.expose_provenance()) }
375}
376
377pub(super) fn destroy_runtime_context(handle: ExecutionContextHandle) -> RuntimeStatus {
378 if handle.is_none() {
379 return RuntimeStatus::InvalidHandle;
380 }
381 let context = ptr::with_exposed_provenance_mut::<RuntimeContext>(handle.into_raw());
382 let context_ref = unsafe { &*context };
385 if context_ref.header.cpu_area().is_some() || context_ref.has_switch_tail() {
386 return RuntimeStatus::Busy;
387 }
388 drop(unsafe { Box::from_raw(context) });
391 #[cfg(feature = "fault-injection")]
392 super::creation_probe::record(super::creation_probe::CreationEvent::DropContext);
393 RuntimeStatus::Success
394}
395
396pub(super) fn bind_runtime_context_thread(binding: ContextThreadBinding) -> RuntimeStatus {
397 #[cfg(feature = "fault-injection")]
398 if super::creation_probe::record(super::creation_probe::CreationEvent::Bind) {
399 return RuntimeStatus::NoMemory;
400 }
401 if !binding.publication.identity().is_bound() || binding.publication.owner().is_none() {
402 return RuntimeStatus::InvalidArgument;
403 }
404 let Ok(context) = runtime_context(binding.context) else {
405 return RuntimeStatus::InvalidHandle;
406 };
407 if unsafe { *context.publication.get() } != CurrentThreadPublication::NONE {
409 return RuntimeStatus::InvalidArgument;
410 }
411 unsafe { *context.publication.get() = binding.publication };
414 unsafe { &mut *context.inner.get() }
418 .set_task_anchor(TaskAnchor::new(context.header().as_non_null()));
419 RuntimeStatus::Success
420}
421
422pub(super) fn scheduler_current_thread_publication() -> CurrentThreadPublication {
424 let Ok(header) = (unsafe { ax_hal::percpu::current_context_unpinned() }) else {
428 return CurrentThreadPublication::NONE;
429 };
430 if unsafe { header.as_ref() }.is_permanent_boot_context() {
433 return CurrentThreadPublication::NONE;
434 }
435 let context = header.as_ptr().cast::<RuntimeContext>();
436 unsafe { *(*context).publication.get() }
439}
440
441pub(super) fn scheduler_current_thread_identity() -> ThreadIdentityV1 {
443 scheduler_current_thread_publication().identity()
444}
445
446#[cfg(all(not(target_arch = "riscv64"), feature = "fp-simd", feature = "uspace"))]
447pub(super) fn validate_current_user_fp_clone_context() -> Result<(), TaskError> {
448 if !ax_cpu::interrupt::irqs_enabled() || ax_hal::irq::in_irq_context() {
449 return Err(TaskError::UnsafeContext);
450 }
451 ax_cpu::interrupt::disable_irqs();
452 let result = unsafe {
455 with_current_cpu_pin(|cpu_pin| {
456 current_runtime_context(cpu_pin)
457 .map(|_| ())
458 .map_err(runtime_status_error)
459 })
460 };
461 ax_cpu::interrupt::enable_irqs();
462 result
463}
464
465#[cfg(all(not(target_arch = "riscv64"), feature = "fp-simd", feature = "uspace"))]
466pub(super) fn inherit_current_user_fp_state(child_context: usize) {
467 assert!(
468 ax_cpu::interrupt::irqs_enabled() && !ax_hal::irq::in_irq_context(),
469 "FPU inheritance requires ordinary task context",
470 );
471 assert_ne!(child_context, 0, "FPU inheritance requires a child context");
472 let child = ptr::with_exposed_provenance_mut::<RuntimeContext>(child_context);
473 ax_cpu::interrupt::disable_irqs();
474 unsafe {
478 with_current_cpu_pin(|cpu_pin| {
479 let parent = current_runtime_context(cpu_pin)
480 .unwrap_or_else(|status| panic!("invalid FPU clone parent context: {status:?}"));
481 assert!(!core::ptr::eq(parent, child));
482 let parent_architecture_context = &*parent.inner.get();
483 let child_architecture_context = &mut *(*child).inner.get();
484 parent_architecture_context.clone_user_fp_state_into(child_architecture_context);
485 })
486 };
487 ax_cpu::interrupt::enable_irqs();
488}
489
490#[cfg(all(target_arch = "x86_64", feature = "fp-simd", feature = "uspace"))]
491pub(super) fn capture_current_user_fp_state()
492-> Result<ax_hal::cpu::registers::UserXstate, TaskError> {
493 if !ax_cpu::interrupt::irqs_enabled() || ax_hal::irq::in_irq_context() {
494 return Err(TaskError::UnsafeContext);
495 }
496 ax_cpu::interrupt::disable_irqs();
497 let result = unsafe {
500 with_current_cpu_pin(|cpu_pin| {
501 let context = current_runtime_context(cpu_pin).map_err(runtime_status_error)?;
502 let architecture_context = &*context.inner.get();
505 Ok(architecture_context.capture_user_fp_state())
506 })
507 };
508 ax_cpu::interrupt::enable_irqs();
509 result
510}
511
512#[cfg(all(target_arch = "x86_64", feature = "fp-simd", feature = "uspace"))]
513pub(super) fn replace_current_user_fp_state(
514 state: ax_hal::cpu::registers::UserXstate,
515) -> Result<(), TaskError> {
516 if !ax_cpu::interrupt::irqs_enabled() || ax_hal::irq::in_irq_context() {
517 return Err(TaskError::UnsafeContext);
518 }
519 ax_cpu::interrupt::disable_irqs();
520 let result = unsafe {
524 with_current_cpu_pin(|cpu_pin| {
525 let context = current_runtime_context(cpu_pin).map_err(runtime_status_error)?;
526 let architecture_context = &mut *context.inner.get();
529 architecture_context.replace_user_fp_state(state);
530 Ok(())
531 })
532 };
533 ax_cpu::interrupt::enable_irqs();
534 result
535}
536
537pub(super) fn reset_current_user_fp_state() -> Result<(), TaskError> {
538 #[cfg(all(target_arch = "x86_64", feature = "fp-simd", feature = "uspace"))]
539 {
540 if !ax_cpu::interrupt::irqs_enabled() || ax_hal::irq::in_irq_context() {
541 return Err(TaskError::UnsafeContext);
542 }
543 ax_cpu::interrupt::disable_irqs();
544 let result = unsafe {
547 with_current_cpu_pin(|cpu_pin| {
548 let context = current_runtime_context(cpu_pin).map_err(runtime_status_error)?;
549 let architecture_context = &mut *context.inner.get();
552 architecture_context.reset_user_fp_state();
553 Ok(())
554 })
555 };
556 ax_cpu::interrupt::enable_irqs();
557 result
558 }
559 #[cfg(not(all(target_arch = "x86_64", feature = "fp-simd", feature = "uspace")))]
560 {
561 Ok(())
562 }
563}
564
565fn prepare_runtime_thread_switch<'switch>(
566 pin: &'switch CpuPin<'_>,
567 previous: &'static RuntimeContext,
568 next: &'static RuntimeContext,
569) -> (PreparedContextSwitch<'switch>, PreviousContextBinding) {
570 unsafe { ax_hal::percpu::prepare_context_switch(pin, previous.header(), next.header()) }
577 .unwrap_or_else(|error| panic!("failed to prepare runtime context switch: {error}"))
578}
579
580#[cfg(all(target_arch = "riscv64", feature = "fp-simd"))]
581pub(super) fn install_initial_fp_state(context: usize, fp_state: ax_hal::cpu::registers::FpState) {
582 let context = ptr::with_exposed_provenance_mut::<RuntimeContext>(context);
583 unsafe { (*(*context).inner.get()).fp_state = fp_state };
586}
587
588pub(super) unsafe fn switch_runtime_context(plan: RuntimeSwitchPlan) {
589 let previous_address_space = plan.previous_address_space();
590 let next_address_space = plan.next_address_space();
591 let same_address_space = plan.same_address_space();
592 let previous_raw = plan.previous_context().into_raw();
593 let next_raw = plan.next_context().into_raw();
594 let previous = ptr::with_exposed_provenance_mut::<RuntimeContext>(previous_raw);
595 let next = ptr::with_exposed_provenance_mut::<RuntimeContext>(next_raw);
596 unsafe {
599 with_current_cpu_pin(|pin| {
600 #[cfg(feature = "qperf-metrics")]
601 let qperf_prepare_entry_finished_ns =
602 crate::clock_event_runtime::monotonic_now().as_nanos();
603 let previous_context = &*previous;
606 let next_context = &*next;
607 let previous_arch_context = &mut *previous_context.inner.get();
608 let next_arch_context = &mut *next_context.inner.get();
609 debug_assert_eq!(
610 previous_arch_context.task_anchor(),
611 Some(TaskAnchor::new(previous_context.header().as_non_null())),
612 "outgoing architecture context retained a different current header"
613 );
614 debug_assert_eq!(
615 next_arch_context.task_anchor(),
616 Some(TaskAnchor::new(next_context.header().as_non_null())),
617 "incoming architecture context retained a different current header"
618 );
619 let prepared_address_space =
620 super::address_space::prepare_runtime_address_space_switch(
621 pin,
622 previous_address_space,
623 next_address_space,
624 same_address_space,
625 super::address_space::AddressSpaceTransitionPhase::ContextSwitch,
626 )
627 .unwrap_or_else(|status| {
628 panic!("failed to prepare runtime address-space switch: {status:?}")
629 });
630 #[cfg(feature = "qperf-metrics")]
631 let qperf_prepare_mm_finished_ns =
632 crate::clock_event_runtime::monotonic_now().as_nanos();
633 let (prepared, previous_binding) =
636 prepare_runtime_thread_switch(pin, previous_context, next_context);
637 #[cfg(feature = "qperf-metrics")]
638 let qperf_prepare_binding_finished_ns =
639 crate::clock_event_runtime::monotonic_now().as_nanos();
640 assert_eq!(
641 next_arch_context.task_anchor(),
642 Some(TaskAnchor::new(prepared.next_header())),
643 "prepared switch token must belong to the next task context",
644 );
645 previous_arch_context.prepare_switch_to(next_arch_context);
646 #[cfg(feature = "qperf-metrics")]
647 let qperf_runtime_tail_started_ns =
648 crate::clock_event_runtime::monotonic_now().as_nanos();
649 #[cfg(feature = "qperf-metrics")]
650 {
651 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
652 17,
653 plan.qperf_prepare_started_ns(),
654 qperf_prepare_entry_finished_ns,
655 );
656 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
657 18,
658 qperf_prepare_entry_finished_ns,
659 qperf_prepare_mm_finished_ns,
660 );
661 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
662 19,
663 qperf_prepare_mm_finished_ns,
664 qperf_prepare_binding_finished_ns,
665 );
666 ax_task::diagnostics::qperf_record_switch_scheduler_detail(
667 20,
668 qperf_prepare_binding_finished_ns,
669 qperf_runtime_tail_started_ns,
670 );
671 ax_task::diagnostics::qperf_record_switch_phase_prepare(
672 plan.qperf_prepare_started_ns(),
673 qperf_runtime_tail_started_ns,
674 );
675 }
676 #[cfg(feature = "qperf-metrics")]
677 let qperf_switch_started_ns = crate::clock_event_runtime::monotonic_now().as_nanos();
678 let tail = RuntimeSwitchTail {
679 previous: previous_context.header().as_non_null(),
680 binding: previous_binding,
681 #[cfg(feature = "qperf-metrics")]
682 qperf_runtime_tail_started_ns,
683 #[cfg(feature = "qperf-metrics")]
684 qperf_switch_started_ns,
685 };
686 next_context
687 .stage_switch_tail(tail)
688 .unwrap_or_else(|status| panic!("failed to stage runtime switch tail: {status:?}"));
689 let switch_baton = crate::guard::prepare_scheduler_switch_baton(pin);
694 prepared_address_space.commit();
695 switch_baton.transfer();
696 prepared.commit();
701 previous_arch_context.switch_to(next_arch_context);
702 })
703 };
704}
705
706#[cfg(test)]
707mod tests {
708 use core::mem::MaybeUninit;
709
710 use cpu_local::{CpuAreaPrefix, CpuAreaRef, CpuIndex};
711
712 use super::*;
713
714 #[test]
715 fn switch_tail_consumes_the_exact_previous_binding_once() {
716 std::thread::spawn(|| {
717 let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
718 let base = storage.as_mut_ptr() as usize;
719 storage.write(CpuAreaPrefix::initialize(CpuIndex::try_from(0).unwrap(), base).unwrap());
720 let area = unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap();
723 unsafe { cpu_local::install_cpu_area(area) }.unwrap();
725
726 let previous = RuntimeContext::allocate(
727 ax_hal::context::TaskContext::new(),
728 StackHandle::NONE,
729 InitialPreemptionState::Enabled,
730 )
731 .unwrap();
732 let next = RuntimeContext::allocate(
733 ax_hal::context::TaskContext::new(),
734 StackHandle::NONE,
735 InitialPreemptionState::Enabled,
736 )
737 .unwrap();
738
739 unsafe {
742 cpu_local::with_cpu_pin(|pin| {
743 let previous = &*previous;
744 let next = &*next;
745 cpu_local::install_bootstrap_context(pin, previous.header()).unwrap();
746 let (prepared, binding) =
747 cpu_local::prepare_context_switch(pin, previous.header(), next.header())
748 .unwrap();
749 prepared.commit();
750
751 next.stage_switch_tail(RuntimeSwitchTail {
752 previous: previous.header().as_non_null(),
753 binding,
754 #[cfg(feature = "qperf-metrics")]
755 qperf_runtime_tail_started_ns: 0,
756 #[cfg(feature = "qperf-metrics")]
757 qperf_switch_started_ns: 0,
758 })
759 .unwrap();
760 next.finish_switch_tail();
761 assert!(!next.has_switch_tail());
762 assert_eq!(previous.header.cpu_area(), None);
763 })
764 }
765 .unwrap();
766 })
767 .join()
768 .expect("modeled CPU must complete the switch tail");
769 }
770
771 #[cfg(feature = "host-test")]
772 #[test]
773 fn switch_prepare_reuses_current_register_and_pinned_area_identity() {
774 std::thread::spawn(|| {
775 let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
776 let base = storage.as_mut_ptr() as usize;
777 storage.write(CpuAreaPrefix::initialize(CpuIndex::try_from(0).unwrap(), base).unwrap());
778 let area = unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap();
781 unsafe { cpu_local::install_cpu_area(area) }.unwrap();
783
784 let previous = RuntimeContext::allocate(
785 ax_hal::context::TaskContext::new(),
786 StackHandle::NONE,
787 InitialPreemptionState::Enabled,
788 )
789 .unwrap();
790 let next = RuntimeContext::allocate(
791 ax_hal::context::TaskContext::new(),
792 StackHandle::NONE,
793 InitialPreemptionState::Enabled,
794 )
795 .unwrap();
796
797 unsafe {
800 cpu_local::with_cpu_pin(|pin| {
801 let previous = &*previous;
802 let next = &*next;
803 cpu_local::install_bootstrap_context(pin, previous.header()).unwrap();
804 cpu_local::host_test::reset_register_read_counts();
805
806 let (prepared, _binding) = prepare_runtime_thread_switch(pin, previous, next);
807 let reads = cpu_local::host_test::register_read_counts();
808 assert_eq!(
809 reads.current_context, 1,
810 "switch preparation must validate current publication exactly once"
811 );
812 assert_eq!(
813 reads.binding_observations, 1,
814 "switch preparation must observe the outgoing binding exactly once"
815 );
816 assert_eq!(
817 reads.initialized_area_validations, 0,
818 "switch preparation must reuse the area identity carried by the CPU pin"
819 );
820 drop(prepared);
821 })
822 }
823 .unwrap();
824 })
825 .join()
826 .expect("modeled CPU must complete switch preparation");
827 }
828
829 #[test]
830 fn current_runtime_context_trusts_switch_binding_publication() {
831 std::thread::spawn(|| {
832 let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
833 let base = storage.as_mut_ptr() as usize;
834 storage.write(CpuAreaPrefix::initialize(CpuIndex::try_from(0).unwrap(), base).unwrap());
835 let area = unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap();
838 unsafe { cpu_local::install_cpu_area(area) }.unwrap();
840
841 let current = RuntimeContext::allocate(
842 ax_hal::context::TaskContext::new(),
843 StackHandle::NONE,
844 InitialPreemptionState::Enabled,
845 )
846 .unwrap();
847
848 unsafe {
851 cpu_local::with_cpu_pin(|pin| {
852 let expected = &*current;
853 cpu_local::install_bootstrap_context(pin, expected.header()).unwrap();
854 cpu_local::host_test::reset_register_read_counts();
855
856 let observed = current_runtime_context(pin).unwrap();
857 assert!(ptr::eq(observed, expected));
858 let reads = cpu_local::host_test::register_read_counts();
859 assert_eq!(reads.current_context, 1);
860 assert_eq!(
861 reads.binding_observations, 0,
862 "the pinned current lookup must trust switch-time binding validation"
863 );
864 })
865 }
866 .unwrap();
867 })
868 .join()
869 .expect("modeled CPU must complete current lookup");
870 }
871
872 #[test]
873 fn current_publication_queries_do_not_resample_cpu_area() {
874 std::thread::spawn(|| {
875 let storage = Box::leak(Box::new(MaybeUninit::<CpuAreaPrefix>::uninit()));
876 let base = storage.as_mut_ptr() as usize;
877 storage.write(CpuAreaPrefix::initialize(CpuIndex::try_from(0).unwrap(), base).unwrap());
878 let area = unsafe { CpuAreaRef::from_initialized_base(base) }.unwrap();
881 unsafe { cpu_local::install_cpu_area(area) }.unwrap();
883
884 let current = RuntimeContext::allocate(
885 ax_hal::context::TaskContext::new(),
886 StackHandle::NONE,
887 InitialPreemptionState::Enabled,
888 )
889 .unwrap();
890
891 unsafe {
894 cpu_local::with_cpu_pin(|pin| {
895 let current = &*current;
896 cpu_local::install_bootstrap_context(pin, current.header()).unwrap();
897 cpu_local::host_test::reset_register_read_counts();
898
899 assert_eq!(
900 scheduler_current_thread_publication(),
901 CurrentThreadPublication::NONE,
902 );
903 let reads = cpu_local::host_test::register_read_counts();
904 assert_eq!(reads.current_context, 1);
905 assert_eq!(
906 reads.cpu_base, 0,
907 "current publication lookup must not resample the CPU-area base",
908 );
909
910 cpu_local::host_test::reset_register_read_counts();
911 assert_eq!(scheduler_current_thread_identity(), ThreadIdentityV1::NONE);
912 let reads = cpu_local::host_test::register_read_counts();
913 assert_eq!(reads.current_context, 1);
914 assert_eq!(
915 reads.cpu_base, 0,
916 "current identity lookup must not resample the CPU-area base",
917 );
918 })
919 }
920 .unwrap();
921 })
922 .join()
923 .expect("modeled CPU must classify its current runtime context");
924 }
925}