1use alloc::{boxed::Box, sync::Arc};
4#[cfg(feature = "uspace")]
5use core::sync::atomic::AtomicBool;
6#[cfg(feature = "qperf-metrics")]
7use core::sync::atomic::AtomicU64;
8use core::{
9 marker::PhantomData,
10 mem::align_of,
11 ptr,
12 sync::atomic::{AtomicU32, AtomicUsize, Ordering},
13};
14
15use ax_hal::percpu::CpuPin;
16use ax_memory_addr::PhysAddr;
17use ax_task::{
18 runtime::{
19 RuntimeStatus,
20 resource::{
21 AddressSpaceDestroyOutcome, AddressSpaceHandle, AddressSpaceMembarrierId,
22 AddressSpaceMembarrierState, AddressSpaceReclaimArmOutcome, AddressSpaceToken,
23 MembarrierRegistration, MembarrierRegistrationPhase,
24 },
25 },
26 thread::TaskError,
27};
28
29use super::mm_activation::UserAddressSpaceOwner;
30#[cfg(feature = "uspace")]
31use super::mm_activation::{AddressSpaceSwitchProof, SchedulerAddressSpaceActivation};
32#[cfg(feature = "uspace")]
33use super::with_current_cpu_pin;
34
35trait TaskAddressSpaceOwner: Send + Sync {
37 fn detach_from_task(&self);
40
41 #[cfg(feature = "uspace")]
42 fn prepare_activation(
43 &self,
44 _cpu: usize,
45 ) -> Result<Option<SchedulerAddressSpaceActivation>, RuntimeStatus> {
46 Ok(None)
47 }
48}
49
50struct ManagedTaskAddressSpaceOwner<T>(T);
51
52impl<T: UserAddressSpaceOwner> TaskAddressSpaceOwner for ManagedTaskAddressSpaceOwner<T> {
53 fn detach_from_task(&self) {
54 self.0.detach_from_task();
55 }
56
57 #[cfg(feature = "uspace")]
58 fn prepare_activation(
59 &self,
60 cpu: usize,
61 ) -> Result<Option<SchedulerAddressSpaceActivation>, RuntimeStatus> {
62 self.0
63 .prepare_activation(cpu)
64 .map(Some)
65 .map_err(|_| RuntimeStatus::InvalidHandle)
66 }
67}
68
69struct RetainedTaskAddressSpaceOwner<T>(T);
70
71impl<T: Send + Sync> TaskAddressSpaceOwner for RetainedTaskAddressSpaceOwner<T> {
72 fn detach_from_task(&self) {}
73}
74
75struct DetachableTaskAddressSpaceOwner<T> {
76 owner: T,
77 detached: core::sync::atomic::AtomicBool,
78 detach: fn(&T),
79}
80
81impl<T: Send + Sync> TaskAddressSpaceOwner for DetachableTaskAddressSpaceOwner<T> {
82 fn detach_from_task(&self) {
83 if !self.detached.swap(true, Ordering::AcqRel) {
84 (self.detach)(&self.owner);
85 }
86 }
87}
88
89struct RuntimeAddressSpace {
90 active_leases: AtomicUsize,
95 reclaim_waiting: AtomicUsize,
96 cpu_state: Arc<AddressSpaceCpuState>,
97 _owner: Box<dyn TaskAddressSpaceOwner>,
98}
99
100#[cfg(feature = "uspace")]
101impl RuntimeAddressSpace {
102 fn root(&self) -> usize {
103 self.cpu_state.root()
104 }
105}
106
107const _: () = assert!(crate::CPU_CAPACITY <= usize::BITS as usize);
108
109#[cfg(feature = "qperf-metrics")]
110static ACTIVE_MM_SAME_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
111#[cfg(feature = "qperf-metrics")]
112static ACTIVE_MM_DIFFERENT_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
113#[cfg(feature = "qperf-metrics")]
114static ACTIVE_MM_KERNEL_LAZY_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
115#[cfg(feature = "qperf-metrics")]
116static ACTIVE_MM_HARDWARE_ROOT_WRITES: AtomicU64 = AtomicU64::new(0);
117#[cfg(feature = "qperf-metrics")]
118static ACTIVE_MM_LEASE_ACTIVATIONS: AtomicU64 = AtomicU64::new(0);
119#[cfg(feature = "qperf-metrics")]
120static ACTIVE_MM_LEASE_DEACTIVATIONS: AtomicU64 = AtomicU64::new(0);
121#[cfg(feature = "qperf-metrics")]
122static ACTIVE_MM_RECLAIM_READY: AtomicU64 = AtomicU64::new(0);
123#[cfg(feature = "qperf-metrics")]
124static ACTIVE_MM_RECLAIM_DESTROYED: AtomicU64 = AtomicU64::new(0);
125
126#[cfg(feature = "qperf-metrics")]
127#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
128pub(super) struct QperfAddressSpaceMetricsSnapshot {
129 pub(super) same_activations: u64,
130 pub(super) different_activations: u64,
131 pub(super) kernel_lazy_activations: u64,
132 pub(super) hardware_root_writes: u64,
133 pub(super) lease_activations: u64,
134 pub(super) lease_deactivations: u64,
135 pub(super) reclaim_ready: u64,
136 pub(super) reclaim_destroyed: u64,
137}
138
139#[cfg(feature = "qperf-metrics")]
140pub(super) fn qperf_address_space_metrics_snapshot() -> QperfAddressSpaceMetricsSnapshot {
141 QperfAddressSpaceMetricsSnapshot {
142 same_activations: ACTIVE_MM_SAME_ACTIVATIONS.load(Ordering::Relaxed),
143 different_activations: ACTIVE_MM_DIFFERENT_ACTIVATIONS.load(Ordering::Relaxed),
144 kernel_lazy_activations: ACTIVE_MM_KERNEL_LAZY_ACTIVATIONS.load(Ordering::Relaxed),
145 hardware_root_writes: ACTIVE_MM_HARDWARE_ROOT_WRITES.load(Ordering::Relaxed),
146 lease_activations: ACTIVE_MM_LEASE_ACTIVATIONS.load(Ordering::Relaxed),
147 lease_deactivations: ACTIVE_MM_LEASE_DEACTIVATIONS.load(Ordering::Relaxed),
148 reclaim_ready: ACTIVE_MM_RECLAIM_READY.load(Ordering::Relaxed),
149 reclaim_destroyed: ACTIVE_MM_RECLAIM_DESTROYED.load(Ordering::Relaxed),
150 }
151}
152
153pub struct AddressSpaceCpuState {
160 root: usize,
161 active_mask: ActiveCpuMask,
162 membarrier_bits: AtomicU32,
163}
164
165enum ActiveCpuMask {
166 Runtime(AtomicUsize),
167 Mm(Arc<AtomicUsize>),
168}
169
170impl AddressSpaceCpuState {
171 pub fn new(root: PhysAddr) -> Self {
173 Self {
174 root: root.as_usize(),
175 active_mask: ActiveCpuMask::Runtime(AtomicUsize::new(0)),
176 membarrier_bits: AtomicU32::new(0),
177 }
178 }
179
180 pub fn with_mm_active_mask(root: PhysAddr, active_mask: Arc<AtomicUsize>) -> Self {
183 Self {
184 root: root.as_usize(),
185 active_mask: ActiveCpuMask::Mm(active_mask),
186 membarrier_bits: AtomicU32::new(0),
187 }
188 }
189
190 fn matches_root(&self, root: PhysAddr) -> bool {
191 self.root() == root.as_usize()
192 }
193
194 fn root(&self) -> usize {
195 self.root
196 }
197
198 pub fn active_mask(&self) -> usize {
200 match &self.active_mask {
201 ActiveCpuMask::Runtime(mask) => mask.load(Ordering::Acquire),
202 ActiveCpuMask::Mm(mask) => mask.load(Ordering::Acquire),
203 }
204 }
205
206 fn membarrier_state(this: &Arc<Self>) -> AddressSpaceMembarrierState {
207 let raw = Arc::as_ptr(this).expose_provenance();
208 let identity = unsafe { AddressSpaceMembarrierId::from_raw(raw) };
211 let bits = this.membarrier_bits.load(Ordering::SeqCst);
212 unsafe { AddressSpaceMembarrierState::new(identity, bits) }
215 }
216
217 fn update_membarrier_state(
218 this: &Arc<Self>,
219 registration: MembarrierRegistration,
220 phase: MembarrierRegistrationPhase,
221 ) -> AddressSpaceMembarrierState {
222 let bit = match phase {
223 MembarrierRegistrationPhase::Begin => registration.requested_bit(),
224 MembarrierRegistrationPhase::Complete => {
225 assert!(
226 this.membarrier_bits.load(Ordering::SeqCst) & registration.requested_bit() != 0,
227 "membarrier registration completed before its requested phase"
228 );
229 registration.ready_bit()
230 }
231 };
232 this.membarrier_bits.fetch_or(bit, Ordering::SeqCst);
233 Self::membarrier_state(this)
234 }
235
236 #[cfg(any(feature = "uspace", test))]
237 fn cpu_bit(cpu_id: usize) -> usize {
238 1usize.checked_shl(cpu_id as u32).unwrap_or_else(|| {
239 panic!("CPU {cpu_id} cannot be represented in an address-space mask")
240 })
241 }
242
243 #[cfg(any(feature = "uspace", test))]
244 fn activate(&self, cpu_id: usize) {
245 if let ActiveCpuMask::Runtime(mask) = &self.active_mask {
246 mask.fetch_or(Self::cpu_bit(cpu_id), Ordering::Release);
247 }
248 }
249
250 #[cfg(any(feature = "uspace", test))]
251 fn deactivate(&self, cpu_id: usize) {
252 if let ActiveCpuMask::Runtime(mask) = &self.active_mask {
253 mask.fetch_and(!Self::cpu_bit(cpu_id), Ordering::Release);
254 }
255 }
256}
257
258pub struct TaskAddressSpace(Option<AddressSpaceToken>);
260
261impl TaskAddressSpace {
262 pub fn new(root: PhysAddr, owner: impl Send + Sync + 'static) -> Result<Self, TaskError> {
264 Self::new_with_owner(
265 root,
266 super::allocation::try_arc(AddressSpaceCpuState::new(root))
267 .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
268 super::allocation::try_box(RetainedTaskAddressSpaceOwner(owner))
269 .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
270 )
271 }
272
273 pub fn new_with_task_detach<T: Send + Sync + 'static>(
280 root: PhysAddr,
281 cpu_state: Arc<AddressSpaceCpuState>,
282 owner: T,
283 detach: fn(&T),
284 ) -> Result<Self, TaskError> {
285 Self::new_with_owner(
286 root,
287 cpu_state,
288 super::allocation::try_box(DetachableTaskAddressSpaceOwner {
289 owner,
290 detached: core::sync::atomic::AtomicBool::new(false),
291 detach,
292 })
293 .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
294 )
295 }
296
297 pub fn new_managed<T: UserAddressSpaceOwner + 'static>(
300 root: PhysAddr,
301 cpu_state: Arc<AddressSpaceCpuState>,
302 owner: T,
303 ) -> Result<Self, TaskError> {
304 Self::new_with_owner(
305 root,
306 cpu_state,
307 super::allocation::try_box(ManagedTaskAddressSpaceOwner(owner))
308 .map_err(|status| TaskError::RuntimeFailure(status as u32))?,
309 )
310 }
311
312 fn new_with_owner(
313 root: PhysAddr,
314 cpu_state: Arc<AddressSpaceCpuState>,
315 owner: Box<dyn TaskAddressSpaceOwner>,
316 ) -> Result<Self, TaskError> {
317 #[cfg(feature = "fault-injection")]
318 if super::creation_probe::record(super::creation_probe::CreationEvent::Mm) {
319 return Err(TaskError::RuntimeFailure(RuntimeStatus::NoMemory as u32));
320 }
321 if root.as_usize() == 0 || !cpu_state.matches_root(root) {
322 return Err(TaskError::InvalidRuntimeHandle);
323 }
324 let address_space = super::allocation::try_box(RuntimeAddressSpace {
325 active_leases: AtomicUsize::new(0),
326 reclaim_waiting: AtomicUsize::new(0),
327 cpu_state,
328 _owner: owner,
329 })
330 .map_err(|status| TaskError::RuntimeFailure(status as u32))?;
331 let raw = Box::into_raw(address_space).expose_provenance();
332 Ok(Self(Some(unsafe { AddressSpaceToken::from_raw(raw) })))
335 }
336
337 #[cfg(any(feature = "uspace", test))]
338 pub(super) fn handle(&self) -> AddressSpaceHandle {
339 self.0
340 .as_ref()
341 .unwrap_or_else(|| unreachable!("address-space token already transferred"))
342 .handle()
343 }
344
345 #[cfg(feature = "uspace")]
346 pub(super) fn token_mut(&mut self) -> &mut AddressSpaceToken {
347 self.0
348 .as_mut()
349 .unwrap_or_else(|| unreachable!("address-space token already transferred"))
350 }
351
352 pub(super) fn take_token(&mut self) -> AddressSpaceToken {
353 self.0
354 .take()
355 .unwrap_or_else(|| unreachable!("address-space token already transferred"))
356 }
357}
358
359fn detach_runtime_address_space_owner(address_space: AddressSpaceHandle) {
360 runtime_address_space(address_space)
361 .unwrap_or_else(|_| panic!("address-space detach received an invalid owning handle"))
362 ._owner
363 .detach_from_task();
364}
365
366#[cfg(any(feature = "uspace", test))]
367fn detach_replaced_address_space_owner(address_space: AddressSpaceHandle) {
368 detach_runtime_address_space_owner(address_space);
369}
370
371impl Drop for TaskAddressSpace {
372 fn drop(&mut self) {
373 let Some(address_space) = self.0.take() else {
374 return;
375 };
376 detach_runtime_address_space_owner(address_space.handle());
377 let outcome = destroy_runtime_address_space(address_space.handle());
378 assert_eq!(
379 outcome,
380 AddressSpaceDestroyOutcome::Released,
381 "unpublished address space retained an active CPU lease"
382 );
383 }
384}
385
386#[ax_percpu::def_percpu]
387static ACTIVE_ADDRESS_SPACE: usize = 0;
388
389#[ax_percpu::def_percpu]
390#[cfg(feature = "uspace")]
391static ACTIVE_MM_ACTIVATION: Option<SchedulerAddressSpaceActivation> = None;
392
393#[cfg(feature = "uspace")]
394fn replace_active_activation(
395 pin: &CpuPin<'_>,
396 next: Option<SchedulerAddressSpaceActivation>,
397) -> Option<SchedulerAddressSpaceActivation> {
398 debug_assert!(!ax_cpu::interrupt::irqs_enabled());
399 unsafe {
402 ax_percpu::with_exclusive_cpu(pin, |exclusive| {
403 ACTIVE_MM_ACTIVATION
404 .with_current_mut(exclusive, |active| core::mem::replace(active, next))
405 })
406 }
407}
408
409#[cfg(feature = "uspace")]
410fn install_mm_identity(installed: ax_hal::context::InstalledAddressSpace) {
411 unsafe { ax_cpu::mmu::install_user_address_space(installed.hardware()) };
414 #[cfg(feature = "qperf-metrics")]
415 ACTIVE_MM_HARDWARE_ROOT_WRITES.fetch_add(1, Ordering::Relaxed);
416}
417
418#[ax_percpu::def_percpu]
424#[cfg(feature = "uspace")]
425static CONTEXT_SWITCH_RECLAIM_READY: AtomicBool = AtomicBool::new(false);
426
427fn runtime_address_space(
428 address_space: AddressSpaceHandle,
429) -> Result<&'static RuntimeAddressSpace, RuntimeStatus> {
430 let raw = address_space.into_raw();
431 if raw == 0 || !raw.is_multiple_of(align_of::<RuntimeAddressSpace>()) {
432 return Err(RuntimeStatus::InvalidHandle);
433 }
434 let address_space = ptr::with_exposed_provenance::<RuntimeAddressSpace>(raw);
435 Ok(unsafe { &*address_space })
438}
439
440#[cfg(feature = "uspace")]
441fn offline_kernel_root() -> usize {
442 if cfg!(any(target_arch = "x86_64", target_arch = "riscv64")) {
443 unsafe { with_current_cpu_pin(super::bootstrap::offline_kernel_root) }
446 } else {
447 0
450 }
451}
452
453#[cfg(feature = "uspace")]
454pub(super) fn current_hardware_root() -> usize {
455 ax_cpu::mmu::read_user_page_table().as_usize()
456}
457
458#[cfg(feature = "uspace")]
459pub(super) fn validate_current_user_address_space(
460 pin: &CpuPin<'_>,
461 selected: AddressSpaceHandle,
462) -> Result<(), RuntimeStatus> {
463 if selected.is_none() {
464 return Err(RuntimeStatus::InvalidHandle);
465 }
466 let active_raw = ACTIVE_ADDRESS_SPACE.read_current(pin);
467 if active_raw == 0 {
468 return Err(RuntimeStatus::InvalidHandle);
469 }
470 let active = runtime_address_space(unsafe { AddressSpaceHandle::from_raw(active_raw) })?;
473 let selected = runtime_address_space(selected)?;
474 let cpu_bit = AddressSpaceCpuState::cpu_bit(pin.area().cpu_index().as_usize());
475 if !same_logical_address_space(active, selected)
476 || active.active_leases.load(Ordering::Acquire) == 0
477 || active.cpu_state.active_mask() & cpu_bit == 0
478 || current_hardware_root() != active.root()
479 {
480 return Err(RuntimeStatus::InvalidHandle);
481 }
482 Ok(())
483}
484
485#[cfg(any(feature = "uspace", test))]
486#[derive(Clone, Copy, Debug, Eq, PartialEq)]
487enum HardwareAddressSpaceTransition {
488 SameAddressSpace,
489 DifferentAddressSpace,
490}
491
492#[cfg(any(feature = "uspace", test))]
493fn hardware_root_install_required(
494 current_root: usize,
495 next_root: usize,
496 transition: HardwareAddressSpaceTransition,
497) -> bool {
498 current_root != next_root || transition == HardwareAddressSpaceTransition::DifferentAddressSpace
499}
500
501#[cfg(feature = "uspace")]
502fn install_hardware_root(root: usize, transition: HardwareAddressSpaceTransition) {
503 if hardware_root_install_required(current_hardware_root(), root, transition) {
504 let root = ax_memory_addr::PhysAddr::from(root);
505 unsafe { ax_cpu::mmu::write_user_page_table(root) };
508 #[cfg(feature = "qperf-metrics")]
509 ACTIVE_MM_HARDWARE_ROOT_WRITES.fetch_add(1, Ordering::Relaxed);
510 #[cfg(not(target_arch = "x86_64"))]
516 ax_cpu::mmu::flush_tlb(None);
517 }
518}
519
520#[cfg(feature = "uspace")]
521fn enter_lazy_kernel_address_space() {
522 #[cfg(target_arch = "aarch64")]
527 install_hardware_root(0, HardwareAddressSpaceTransition::DifferentAddressSpace);
528}
529
530#[cfg(feature = "uspace")]
531fn commit_user_address_space_activation(
532 cpu_id: usize,
533 previous_raw: usize,
534 previous: Option<&RuntimeAddressSpace>,
535 next_raw: usize,
536 next: &RuntimeAddressSpace,
537 install_root: impl FnOnce(usize, HardwareAddressSpaceTransition),
538 publish_active: impl FnOnce(usize),
539) -> bool {
540 let same_address_space = next_raw == previous_raw
541 || previous.is_some_and(|previous| same_logical_address_space(previous, next));
542 if same_address_space {
543 #[cfg(feature = "qperf-metrics")]
544 ACTIVE_MM_SAME_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
545 debug_assert_eq!(
546 previous.map(RuntimeAddressSpace::root),
547 Some(next.root()),
548 "one address-space CPU tracker cannot describe different roots"
549 );
550 install_root(
556 next.root(),
557 HardwareAddressSpaceTransition::SameAddressSpace,
558 );
559 return false;
560 }
561 #[cfg(feature = "qperf-metrics")]
562 ACTIVE_MM_DIFFERENT_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
563 next.active_leases.fetch_add(1, Ordering::AcqRel);
564 next.cpu_state.activate(cpu_id);
565 #[cfg(feature = "qperf-metrics")]
566 ACTIVE_MM_LEASE_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
567 install_root(
568 next.root(),
569 HardwareAddressSpaceTransition::DifferentAddressSpace,
570 );
571 publish_active(next_raw);
572 if let Some(previous) = previous {
573 previous.cpu_state.deactivate(cpu_id);
574 #[cfg(feature = "qperf-metrics")]
575 ACTIVE_MM_LEASE_DEACTIVATIONS.fetch_add(1, Ordering::Relaxed);
576 release_active_cpu(previous)
577 } else {
578 false
579 }
580}
581
582#[cfg(feature = "uspace")]
583fn same_logical_address_space(first: &RuntimeAddressSpace, second: &RuntimeAddressSpace) -> bool {
584 Arc::ptr_eq(&first.cpu_state, &second.cpu_state)
585}
586
587enum PreparedAddressSpaceAction {
588 KernelLazy,
589 #[cfg(all(feature = "uspace", not(target_arch = "aarch64")))]
590 SameUser,
591 #[cfg(feature = "uspace")]
592 User {
593 next_raw: usize,
594 next: &'static RuntimeAddressSpace,
595 activation: Option<SchedulerAddressSpaceActivation>,
596 },
597}
598
599#[derive(Clone, Copy, Debug, Eq, PartialEq)]
600pub(super) enum AddressSpaceTransitionPhase {
601 #[cfg(feature = "uspace")]
602 CurrentTask,
603 ContextSwitch,
604}
605
606#[must_use = "a prepared address-space switch must be committed with its context switch"]
613pub(super) struct PreparedAddressSpaceSwitch<'pin, 'cpu> {
614 #[cfg(feature = "uspace")]
615 pin: &'pin CpuPin<'cpu>,
616 phase: AddressSpaceTransitionPhase,
617 #[cfg(feature = "uspace")]
618 previous_raw: usize,
619 #[cfg(feature = "uspace")]
620 previous: Option<&'static RuntimeAddressSpace>,
621 action: PreparedAddressSpaceAction,
622 _not_send_or_sync: PhantomData<(&'pin CpuPin<'cpu>, *mut ())>,
623}
624
625impl PreparedAddressSpaceSwitch<'_, '_> {
626 #[inline(always)]
628 pub(super) fn commit(self) {
629 #[cfg(feature = "uspace")]
630 let pin = self.pin;
631 match self.phase {
632 #[cfg(feature = "uspace")]
633 AddressSpaceTransitionPhase::CurrentTask => assert!(
634 !ax_cpu::interrupt::irqs_enabled(),
635 "current-task address-space commit requires local IRQ exclusion"
636 ),
637 AddressSpaceTransitionPhase::ContextSwitch => {}
638 }
639 #[cfg(not(feature = "uspace"))]
640 debug_assert_eq!(
641 self.phase,
642 AddressSpaceTransitionPhase::ContextSwitch,
643 "kernel-only builds prepare address spaces only for scheduler switches"
644 );
645 #[cfg(feature = "uspace")]
646 assert_eq!(
647 ACTIVE_ADDRESS_SPACE.read_current(pin),
648 self.previous_raw,
649 "active address space changed after switch preparation"
650 );
651
652 match self.action {
653 PreparedAddressSpaceAction::KernelLazy => {
654 #[cfg(feature = "uspace")]
655 {
656 #[cfg(feature = "qperf-metrics")]
657 ACTIVE_MM_KERNEL_LAZY_ACTIVATIONS.fetch_add(1, Ordering::Relaxed);
658 enter_lazy_kernel_address_space();
659 }
660 }
661 #[cfg(all(feature = "uspace", not(target_arch = "aarch64")))]
662 PreparedAddressSpaceAction::SameUser => {}
663 #[cfg(feature = "uspace")]
664 PreparedAddressSpaceAction::User {
665 next_raw,
666 next,
667 mut activation,
668 } => {
669 let cpu_id = pin.area().cpu_index().as_usize();
670 let installed = activation
671 .as_ref()
672 .map(SchedulerAddressSpaceActivation::installed)
673 .or_else(|| {
674 if !self
675 .previous
676 .is_some_and(|previous| same_logical_address_space(previous, next))
677 {
678 return None;
679 }
680 ACTIVE_MM_ACTIVATION.with_current(pin, |active| {
681 active
682 .as_ref()
683 .map(SchedulerAddressSpaceActivation::installed)
684 })
685 });
686 let reclaim_ready = commit_user_address_space_activation(
687 pin.area().cpu_index().as_usize(),
688 self.previous_raw,
689 self.previous,
690 next_raw,
691 next,
692 |root, transition| {
693 if let Some(installed) = installed {
694 if hardware_root_install_required(
695 current_hardware_root(),
696 root,
697 transition,
698 ) {
699 install_mm_identity(installed);
700 }
701 } else {
702 install_hardware_root(root, transition);
703 }
704 },
705 |active| {
706 if let Some(next) = activation.as_mut() {
707 next.commit(cpu_id);
708 }
709 let previous = replace_active_activation(pin, activation.take());
710 ACTIVE_ADDRESS_SPACE.write_current(pin, active);
711 if let Some(previous) = previous {
712 previous.release(AddressSpaceSwitchProof::new(cpu_id));
713 }
714 },
715 );
716 if reclaim_ready {
717 route_reclaim_notification(
718 self.phase,
719 || {
720 CONTEXT_SWITCH_RECLAIM_READY.with_current(pin, |pending| {
721 assert!(
722 !pending.swap(true, Ordering::AcqRel),
723 "context switch retained an unconsumed active-mm reclaim edge"
724 );
725 });
726 },
727 ax_task::runtime::resource::notify_address_space_reclaim,
728 );
729 }
730 }
731 }
732 }
733}
734
735pub(super) fn prepare_runtime_address_space_switch<'pin, 'cpu>(
737 _pin: &'pin CpuPin<'cpu>,
738 previous_selected: AddressSpaceHandle,
739 next_selected: AddressSpaceHandle,
740 same_address_space: bool,
741 phase: AddressSpaceTransitionPhase,
742) -> Result<PreparedAddressSpaceSwitch<'pin, 'cpu>, RuntimeStatus> {
743 #[cfg(feature = "fault-injection")]
744 super::creation_probe::record_mm_switch(!previous_selected.is_none(), !next_selected.is_none());
745 #[cfg(feature = "uspace")]
746 let pin = _pin;
747 #[cfg(feature = "uspace")]
748 let cpu_id = pin.area().cpu_index().as_usize();
749 #[cfg(any(not(feature = "uspace"), target_arch = "aarch64"))]
750 let _ = same_address_space;
751
752 #[cfg(feature = "uspace")]
753 {
754 let previous_raw = ACTIVE_ADDRESS_SPACE.read_current(pin);
755 #[cfg(not(target_arch = "aarch64"))]
756 if same_address_space {
757 debug_assert!(!previous_selected.is_none());
758 debug_assert!(!next_selected.is_none());
759 debug_assert_ne!(previous_raw, 0);
760 return Ok(PreparedAddressSpaceSwitch {
761 pin,
762 phase,
763 previous_raw,
764 previous: None,
765 action: PreparedAddressSpaceAction::SameUser,
766 _not_send_or_sync: PhantomData,
767 });
768 }
769 let previous = if previous_raw == 0 {
770 None
771 } else {
772 let previous = unsafe { AddressSpaceHandle::from_raw(previous_raw) };
775 Some(runtime_address_space(previous)?)
776 };
777
778 #[cfg(not(target_arch = "aarch64"))]
779 if let Some((previous, next)) = previous.zip(
780 (!next_selected.is_none())
781 .then(|| runtime_address_space(next_selected))
782 .transpose()?,
783 ) && same_logical_address_space(previous, next)
784 {
785 debug_assert!(!previous_selected.is_none());
793 debug_assert!(
794 runtime_address_space(previous_selected)
795 .is_ok_and(|selected| same_logical_address_space(previous, selected))
796 );
797 debug_assert_ne!(previous.active_leases.load(Ordering::Acquire), 0);
798 debug_assert!(
799 previous.cpu_state.active_mask() & AddressSpaceCpuState::cpu_bit(cpu_id) != 0
800 );
801 return Ok(PreparedAddressSpaceSwitch {
802 pin,
803 phase,
804 previous_raw,
805 previous: Some(previous),
806 action: PreparedAddressSpaceAction::SameUser,
807 _not_send_or_sync: PhantomData,
808 });
809 }
810
811 if let Some(previous) = previous {
812 let bit = AddressSpaceCpuState::cpu_bit(cpu_id);
813 if previous.active_leases.load(Ordering::Acquire) == 0
814 || previous.cpu_state.active_mask() & bit == 0
815 {
816 return Err(RuntimeStatus::InvalidHandle);
817 }
818 }
819
820 let previous_state = if previous_selected.is_none() {
821 None
822 } else {
823 let selected = runtime_address_space(previous_selected)?;
824 let Some(active) = previous else {
825 return Err(RuntimeStatus::InvalidArgument);
826 };
827 if !same_logical_address_space(active, selected) {
828 return Err(RuntimeStatus::InvalidArgument);
829 }
830 Some(selected)
831 };
832
833 let (next_state, action) = if next_selected.is_none() {
834 (None, PreparedAddressSpaceAction::KernelLazy)
835 } else {
836 let next = runtime_address_space(next_selected)?;
837 (
838 Some(next),
839 PreparedAddressSpaceAction::User {
840 next_raw: next_selected.into_raw(),
841 next,
842 activation: if previous
843 .is_some_and(|previous| same_logical_address_space(previous, next))
844 {
845 None
846 } else {
847 next._owner.prepare_activation(cpu_id)?
848 },
849 },
850 )
851 };
852
853 let changed_address_space = match (previous_state, next_state) {
858 (Some(previous), Some(next)) => !same_logical_address_space(previous, next),
859 (Some(_), None) | (None, Some(_)) => true,
860 (None, None) => false,
861 };
862 if changed_address_space {
863 core::sync::atomic::fence(Ordering::SeqCst);
867 }
868
869 Ok(PreparedAddressSpaceSwitch {
870 pin,
871 phase,
872 previous_raw,
873 previous,
874 action,
875 _not_send_or_sync: PhantomData,
876 })
877 }
878
879 #[cfg(not(feature = "uspace"))]
880 {
881 if !previous_selected.is_none() || !next_selected.is_none() {
882 return Err(RuntimeStatus::Unsupported);
883 }
884 Ok(PreparedAddressSpaceSwitch {
885 phase,
886 action: PreparedAddressSpaceAction::KernelLazy,
887 _not_send_or_sync: PhantomData,
888 })
889 }
890}
891
892pub(super) fn release_current_active_address_space() {
893 #[cfg(feature = "uspace")]
894 let reclaim_ready = unsafe {
895 with_current_cpu_pin(|pin| {
896 let previous_raw = ACTIVE_ADDRESS_SPACE.read_current(pin);
897 if previous_raw == 0 {
898 return false;
899 }
900 install_hardware_root(
901 offline_kernel_root(),
902 HardwareAddressSpaceTransition::DifferentAddressSpace,
903 );
904 ax_cpu::mmu::flush_tlb(None);
906 if let Some(activation) = replace_active_activation(pin, None) {
907 activation.release(AddressSpaceSwitchProof::new(
908 pin.area().cpu_index().as_usize(),
909 ));
910 }
911 ACTIVE_ADDRESS_SPACE.write_current(pin, 0);
912 let previous = AddressSpaceHandle::from_raw(previous_raw);
913 let previous = runtime_address_space(previous)
914 .unwrap_or_else(|_| panic!("offline CPU retained a stale active address space"));
915 previous
916 .cpu_state
917 .deactivate(pin.area().cpu_index().as_usize());
918 #[cfg(feature = "qperf-metrics")]
919 ACTIVE_MM_LEASE_DEACTIVATIONS.fetch_add(1, Ordering::Relaxed);
920 release_active_cpu(previous)
921 })
922 };
923 #[cfg(feature = "uspace")]
924 if reclaim_ready {
925 ax_task::runtime::resource::notify_address_space_reclaim();
926 }
927}
928
929pub(super) fn destroy_runtime_address_space(
930 address_space: AddressSpaceHandle,
931) -> AddressSpaceDestroyOutcome {
932 let address_space = runtime_address_space(address_space)
933 .unwrap_or_else(|_| panic!("address-space destruction received an invalid owning handle"));
934 if address_space.active_leases.load(Ordering::Acquire) != 0 {
935 return AddressSpaceDestroyOutcome::Active;
936 }
937 let raw = address_space as *const RuntimeAddressSpace as *mut RuntimeAddressSpace;
938 drop(unsafe { Box::from_raw(raw) });
941 #[cfg(feature = "qperf-metrics")]
942 ACTIVE_MM_RECLAIM_DESTROYED.fetch_add(1, Ordering::Relaxed);
943 AddressSpaceDestroyOutcome::Released
944}
945
946pub(super) fn arm_runtime_address_space_reclaim(
947 address_space: AddressSpaceHandle,
948) -> AddressSpaceReclaimArmOutcome {
949 let address_space = runtime_address_space(address_space)
950 .unwrap_or_else(|_| panic!("address-space reclaim arm received an invalid owning handle"));
951 address_space.reclaim_waiting.store(1, Ordering::Release);
952 if address_space.active_leases.load(Ordering::Acquire) == 0 {
953 address_space.reclaim_waiting.store(0, Ordering::Release);
954 AddressSpaceReclaimArmOutcome::Ready
955 } else {
956 AddressSpaceReclaimArmOutcome::Armed
957 }
958}
959
960pub(super) fn runtime_address_space_membarrier_state(
961 address_space: AddressSpaceHandle,
962) -> AddressSpaceMembarrierState {
963 let address_space = runtime_address_space(address_space)
964 .unwrap_or_else(|_| panic!("membarrier received an invalid address-space handle"));
965 AddressSpaceCpuState::membarrier_state(&address_space.cpu_state)
966}
967
968pub(super) fn update_runtime_address_space_membarrier_state(
969 address_space: AddressSpaceHandle,
970 registration: MembarrierRegistration,
971 phase: MembarrierRegistrationPhase,
972) -> AddressSpaceMembarrierState {
973 let address_space = runtime_address_space(address_space).unwrap_or_else(|_| {
974 panic!("membarrier registration received an invalid address-space handle")
975 });
976 AddressSpaceCpuState::update_membarrier_state(&address_space.cpu_state, registration, phase)
977}
978
979#[cfg(any(feature = "uspace", test))]
980fn release_active_cpu(address_space: &RuntimeAddressSpace) -> bool {
981 let active = address_space.active_leases.fetch_sub(1, Ordering::AcqRel);
982 assert!(active >= 1, "active address-space lease count underflow");
983 let reclaim_ready = active == 1 && address_space.reclaim_waiting.swap(0, Ordering::AcqRel) != 0;
984 #[cfg(feature = "qperf-metrics")]
985 if reclaim_ready {
986 ACTIVE_MM_RECLAIM_READY.fetch_add(1, Ordering::Relaxed);
987 }
988 reclaim_ready
989}
990
991#[cfg(any(feature = "uspace", test))]
992fn route_reclaim_notification(
993 phase: AddressSpaceTransitionPhase,
994 defer: impl FnOnce(),
995 publish: impl FnOnce(),
996) {
997 #[cfg(not(feature = "uspace"))]
998 let _ = publish;
999 match phase {
1000 #[cfg(feature = "uspace")]
1001 AddressSpaceTransitionPhase::CurrentTask => publish(),
1002 AddressSpaceTransitionPhase::ContextSwitch => defer(),
1003 }
1004}
1005
1006pub(super) fn take_context_switch_reclaim_ready() -> bool {
1007 #[cfg(feature = "uspace")]
1008 {
1009 unsafe {
1012 with_current_cpu_pin(|pin| {
1013 CONTEXT_SWITCH_RECLAIM_READY
1014 .with_current(pin, |pending| pending.swap(false, Ordering::AcqRel))
1015 })
1016 }
1017 }
1018 #[cfg(not(feature = "uspace"))]
1019 {
1020 false
1021 }
1022}
1023
1024#[cfg(feature = "uspace")]
1025fn commit_current_task_address_space_transition<T>(
1026 next_selected: AddressSpaceHandle,
1027 action: impl FnOnce() -> Result<T, TaskError>,
1028) -> Result<T, TaskError> {
1029 let _irq = crate::task::sync::IrqSaveGuard::new();
1030 unsafe {
1033 with_current_cpu_pin(|pin| {
1034 let previous_selected = ax_task::runtime::resource::current_address_space_handle()?;
1035 let prepared = prepare_runtime_address_space_switch(
1036 pin,
1037 previous_selected,
1038 next_selected,
1039 false,
1040 AddressSpaceTransitionPhase::CurrentTask,
1041 )
1042 .map_err(super::runtime_status_error)?;
1043 let result = action()?;
1044 prepared.commit();
1046 Ok(result)
1047 })
1048 }
1049}
1050
1051pub fn switch_current_address_space(address_space: TaskAddressSpace) -> Result<(), TaskError> {
1053 #[cfg(feature = "uspace")]
1054 {
1055 let mut address_space = address_space;
1056 let next_handle = address_space.handle();
1057 let previous = commit_current_task_address_space_transition(next_handle, || {
1058 ax_task::runtime::resource::replace_current_address_space(address_space.token_mut())
1059 })?;
1060 let transferred = address_space.take_token();
1061 debug_assert!(transferred.is_none());
1062
1063 detach_replaced_address_space_owner(previous.handle());
1066 ax_task::runtime::resource::release_address_space_token(previous)
1067 }
1068 #[cfg(not(feature = "uspace"))]
1069 {
1070 let _ = address_space;
1071 Err(TaskError::RuntimeFailure(RuntimeStatus::Unsupported as u32))
1072 }
1073}
1074
1075pub fn detach_current_address_space() -> Result<(), TaskError> {
1078 #[cfg(feature = "uspace")]
1079 {
1080 let previous = commit_current_task_address_space_transition(
1081 AddressSpaceHandle::NONE,
1082 ax_task::runtime::resource::detach_current_address_space,
1083 )?;
1084
1085 detach_runtime_address_space_owner(previous.handle());
1089 ax_task::runtime::resource::release_address_space_token(previous)
1090 }
1091 #[cfg(not(feature = "uspace"))]
1092 {
1093 Err(TaskError::RuntimeFailure(RuntimeStatus::Unsupported as u32))
1094 }
1095}
1096
1097#[cfg(test)]
1098mod tests {
1099 use alloc::sync::Arc;
1100 use core::sync::atomic::{AtomicUsize, Ordering};
1101
1102 use super::*;
1103
1104 struct CountDrop(Arc<AtomicUsize>);
1105
1106 impl Drop for CountDrop {
1107 fn drop(&mut self) {
1108 self.0.fetch_add(1, Ordering::Release);
1109 }
1110 }
1111
1112 #[test]
1113 fn switch_activation_defers_reclaim_notification_until_switch_tail() {
1114 let deferred = AtomicUsize::new(0);
1115 let published = AtomicUsize::new(0);
1116
1117 route_reclaim_notification(
1118 AddressSpaceTransitionPhase::ContextSwitch,
1119 || {
1120 deferred.fetch_add(1, Ordering::Relaxed);
1121 },
1122 || {
1123 published.fetch_add(1, Ordering::Relaxed);
1124 },
1125 );
1126
1127 assert_eq!(deferred.load(Ordering::Relaxed), 1);
1128 assert_eq!(published.load(Ordering::Relaxed), 0);
1129 }
1130
1131 #[test]
1132 fn active_cpu_lease_blocks_owner_destruction_until_release() {
1133 let drops = Arc::new(AtomicUsize::new(0));
1134 let mut token = TaskAddressSpace::new(
1135 ax_memory_addr::PhysAddr::from(0x4000),
1136 CountDrop(Arc::clone(&drops)),
1137 )
1138 .unwrap();
1139 let handle = token.handle();
1140 let runtime = runtime_address_space(handle).unwrap();
1141 runtime.active_leases.fetch_add(1, Ordering::AcqRel);
1142 let owned = token.take_token();
1143
1144 assert_eq!(
1145 destroy_runtime_address_space(handle),
1146 AddressSpaceDestroyOutcome::Active
1147 );
1148 assert_eq!(
1149 arm_runtime_address_space_reclaim(handle),
1150 AddressSpaceReclaimArmOutcome::Armed
1151 );
1152 assert_eq!(drops.load(Ordering::Acquire), 0);
1153
1154 assert!(release_active_cpu(runtime));
1155 assert_eq!(
1156 destroy_runtime_address_space(handle),
1157 AddressSpaceDestroyOutcome::Released
1158 );
1159 assert_eq!(drops.load(Ordering::Acquire), 1);
1160 assert!(!owned.is_none());
1161 }
1162
1163 #[test]
1164 fn task_detach_releases_task_owner_before_lazy_cpu_lease() {
1165 struct CountDetach {
1166 detaches: Arc<AtomicUsize>,
1167 drops: Arc<AtomicUsize>,
1168 }
1169
1170 impl Drop for CountDetach {
1171 fn drop(&mut self) {
1172 self.drops.fetch_add(1, Ordering::Release);
1173 }
1174 }
1175
1176 fn detach(owner: &CountDetach) {
1177 owner.detaches.fetch_add(1, Ordering::Release);
1178 }
1179
1180 let detaches = Arc::new(AtomicUsize::new(0));
1181 let drops = Arc::new(AtomicUsize::new(0));
1182 let mut token = TaskAddressSpace::new_with_task_detach(
1183 ax_memory_addr::PhysAddr::from(0x4000),
1184 Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000))),
1185 CountDetach {
1186 detaches: Arc::clone(&detaches),
1187 drops: Arc::clone(&drops),
1188 },
1189 detach,
1190 )
1191 .unwrap();
1192 let handle = token.handle();
1193 let runtime = runtime_address_space(handle).unwrap();
1194 runtime.active_leases.fetch_add(1, Ordering::AcqRel);
1195 let owned = token.take_token();
1196
1197 detach_runtime_address_space_owner(handle);
1198 detach_runtime_address_space_owner(handle);
1199 assert_eq!(detaches.load(Ordering::Acquire), 1);
1200 assert_eq!(drops.load(Ordering::Acquire), 0);
1201 assert_eq!(
1202 destroy_runtime_address_space(handle),
1203 AddressSpaceDestroyOutcome::Active
1204 );
1205
1206 assert!(!release_active_cpu(runtime));
1207 assert_eq!(
1208 destroy_runtime_address_space(handle),
1209 AddressSpaceDestroyOutcome::Released
1210 );
1211 assert_eq!(drops.load(Ordering::Acquire), 1);
1212 assert!(!owned.is_none());
1213 }
1214
1215 #[test]
1216 fn replaced_task_owner_is_detached_before_runtime_release() {
1217 struct CountDetach(Arc<AtomicUsize>);
1218
1219 fn detach(owner: &CountDetach) {
1220 owner.0.fetch_add(1, Ordering::Release);
1221 }
1222
1223 let detaches = Arc::new(AtomicUsize::new(0));
1224 let mut token = TaskAddressSpace::new_with_task_detach(
1225 PhysAddr::from(0x4000),
1226 Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000))),
1227 CountDetach(Arc::clone(&detaches)),
1228 detach,
1229 )
1230 .unwrap();
1231 let handle = token.handle();
1232 let owned = token.take_token();
1233
1234 detach_replaced_address_space_owner(handle);
1235 assert_eq!(detaches.load(Ordering::Acquire), 1);
1236 assert_eq!(
1237 destroy_runtime_address_space(handle),
1238 AddressSpaceDestroyOutcome::Released
1239 );
1240 assert!(!owned.is_none());
1241 }
1242
1243 #[test]
1244 fn shared_cpu_state_publishes_and_withdraws_cpu_footprints() {
1245 let tracker = AddressSpaceCpuState::new(PhysAddr::from(0x4000));
1246
1247 tracker.activate(1);
1248 tracker.activate(3);
1249 assert_eq!(tracker.active_mask(), (1usize << 1) | (1usize << 3));
1250
1251 tracker.deactivate(1);
1252 assert_eq!(tracker.active_mask(), 1usize << 3);
1253 }
1254
1255 #[test]
1256 fn address_space_cpu_state_rejects_mismatched_root() {
1257 let tracker = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1258 let token =
1259 TaskAddressSpace::new_with_task_detach(PhysAddr::from(0x8000), tracker, (), |_| {});
1260
1261 assert!(matches!(token, Err(TaskError::InvalidRuntimeHandle)));
1262 }
1263
1264 #[test]
1265 fn shared_mm_tokens_share_membarrier_identity_and_registration() {
1266 let cpu_state = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1267 let first = TaskAddressSpace::new_with_task_detach(
1268 PhysAddr::from(0x4000),
1269 Arc::clone(&cpu_state),
1270 (),
1271 |_| {},
1272 )
1273 .unwrap();
1274 let second = TaskAddressSpace::new_with_task_detach(
1275 PhysAddr::from(0x4000),
1276 Arc::clone(&cpu_state),
1277 (),
1278 |_| {},
1279 )
1280 .unwrap();
1281
1282 let requested = update_runtime_address_space_membarrier_state(
1283 first.handle(),
1284 MembarrierRegistration::PrivateExpedited,
1285 MembarrierRegistrationPhase::Begin,
1286 );
1287 let observed = runtime_address_space_membarrier_state(second.handle());
1288 assert_eq!(requested, observed);
1289 assert!(observed.requested(MembarrierRegistration::PrivateExpedited));
1290 assert!(!observed.ready(MembarrierRegistration::PrivateExpedited));
1291
1292 let ready = update_runtime_address_space_membarrier_state(
1293 second.handle(),
1294 MembarrierRegistration::PrivateExpedited,
1295 MembarrierRegistrationPhase::Complete,
1296 );
1297 assert_eq!(
1298 runtime_address_space_membarrier_state(first.handle()),
1299 ready
1300 );
1301 assert!(ready.ready(MembarrierRegistration::PrivateExpedited));
1302 }
1303
1304 #[cfg(feature = "uspace")]
1305 #[test]
1306 fn same_mm_activation_retains_the_existing_cpu_lease_without_hardware_work() {
1307 let tracker = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1308 let previous = TaskAddressSpace::new_with_task_detach(
1309 PhysAddr::from(0x4000),
1310 Arc::clone(&tracker),
1311 (),
1312 |_| {},
1313 )
1314 .unwrap();
1315 let next = TaskAddressSpace::new_with_task_detach(
1316 PhysAddr::from(0x4000),
1317 Arc::clone(&tracker),
1318 (),
1319 |_| {},
1320 )
1321 .unwrap();
1322 let previous_runtime = runtime_address_space(previous.handle()).unwrap();
1323 let next_runtime = runtime_address_space(next.handle()).unwrap();
1324 previous_runtime.active_leases.store(1, Ordering::Release);
1325 tracker.activate(0);
1326 let hardware_root = AtomicUsize::new(0x4000);
1327 let hardware_installs = AtomicUsize::new(0);
1328 let active_publications = AtomicUsize::new(0);
1329
1330 let reclaim_ready = commit_user_address_space_activation(
1331 0,
1332 previous.handle().into_raw(),
1333 Some(previous_runtime),
1334 next.handle().into_raw(),
1335 next_runtime,
1336 |root, _transition| {
1337 if hardware_root.swap(root, Ordering::AcqRel) != root {
1338 hardware_installs.fetch_add(1, Ordering::Relaxed);
1339 }
1340 },
1341 |_| {
1342 active_publications.fetch_add(1, Ordering::Relaxed);
1343 },
1344 );
1345
1346 let previous_leases = previous_runtime.active_leases.load(Ordering::Acquire);
1347 let next_leases = next_runtime.active_leases.load(Ordering::Acquire);
1348 previous_runtime.active_leases.store(0, Ordering::Release);
1349 next_runtime.active_leases.store(0, Ordering::Release);
1350 tracker.deactivate(0);
1351
1352 assert_eq!(previous_leases, 1);
1353 assert_eq!(next_leases, 0);
1354 assert_eq!(hardware_installs.load(Ordering::Relaxed), 0);
1355 assert_eq!(active_publications.load(Ordering::Relaxed), 0);
1356 assert!(!reclaim_ready);
1357 }
1358
1359 #[cfg(feature = "uspace")]
1360 #[test]
1361 fn lazy_kernel_root_is_restored_before_same_mm_user_execution() {
1362 let tracker = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1363 let previous = TaskAddressSpace::new_with_task_detach(
1364 PhysAddr::from(0x4000),
1365 Arc::clone(&tracker),
1366 (),
1367 |_| {},
1368 )
1369 .unwrap();
1370 let next = TaskAddressSpace::new_with_task_detach(
1371 PhysAddr::from(0x4000),
1372 Arc::clone(&tracker),
1373 (),
1374 |_| {},
1375 )
1376 .unwrap();
1377 let previous_runtime = runtime_address_space(previous.handle()).unwrap();
1378 let next_runtime = runtime_address_space(next.handle()).unwrap();
1379 previous_runtime.active_leases.store(1, Ordering::Release);
1380 tracker.activate(0);
1381 let hardware_root = AtomicUsize::new(0);
1382 let hardware_installs = AtomicUsize::new(0);
1383 let active_publications = AtomicUsize::new(0);
1384
1385 let reclaim_ready = commit_user_address_space_activation(
1386 0,
1387 previous.handle().into_raw(),
1388 Some(previous_runtime),
1389 next.handle().into_raw(),
1390 next_runtime,
1391 |root, _transition| {
1392 if hardware_root.swap(root, Ordering::AcqRel) != root {
1393 hardware_installs.fetch_add(1, Ordering::Relaxed);
1394 }
1395 },
1396 |_| {
1397 active_publications.fetch_add(1, Ordering::Relaxed);
1398 },
1399 );
1400
1401 let previous_leases = previous_runtime.active_leases.load(Ordering::Acquire);
1402 let next_leases = next_runtime.active_leases.load(Ordering::Acquire);
1403 previous_runtime.active_leases.store(0, Ordering::Release);
1404 next_runtime.active_leases.store(0, Ordering::Release);
1405 tracker.deactivate(0);
1406
1407 assert_eq!(hardware_root.load(Ordering::Acquire), 0x4000);
1408 assert_eq!(hardware_installs.load(Ordering::Relaxed), 1);
1409 assert_eq!(previous_leases, 1);
1410 assert_eq!(next_leases, 0);
1411 assert_eq!(active_publications.load(Ordering::Relaxed), 0);
1412 assert!(!reclaim_ready);
1413 }
1414
1415 #[cfg(feature = "uspace")]
1416 #[test]
1417 fn same_mm_chain_releases_the_retained_active_handle_on_other_mm_switch() {
1418 let shared = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x4000)));
1419 let other = Arc::new(AddressSpaceCpuState::new(PhysAddr::from(0x8000)));
1420 let first = TaskAddressSpace::new_with_task_detach(
1421 PhysAddr::from(0x4000),
1422 Arc::clone(&shared),
1423 (),
1424 |_| {},
1425 )
1426 .unwrap();
1427 let second = TaskAddressSpace::new_with_task_detach(
1428 PhysAddr::from(0x4000),
1429 Arc::clone(&shared),
1430 (),
1431 |_| {},
1432 )
1433 .unwrap();
1434 let third = TaskAddressSpace::new_with_task_detach(
1435 PhysAddr::from(0x8000),
1436 Arc::clone(&other),
1437 (),
1438 |_| {},
1439 )
1440 .unwrap();
1441 let first_runtime = runtime_address_space(first.handle()).unwrap();
1442 let second_runtime = runtime_address_space(second.handle()).unwrap();
1443 let third_runtime = runtime_address_space(third.handle()).unwrap();
1444 first_runtime.active_leases.store(1, Ordering::Release);
1445 shared.activate(0);
1446 let active = AtomicUsize::new(first.handle().into_raw());
1447
1448 assert!(same_logical_address_space(first_runtime, second_runtime));
1449 assert!(!commit_user_address_space_activation(
1450 0,
1451 active.load(Ordering::Acquire),
1452 Some(first_runtime),
1453 second.handle().into_raw(),
1454 second_runtime,
1455 |_, _| {},
1456 |next| active.store(next, Ordering::Release),
1457 ));
1458 assert_eq!(active.load(Ordering::Acquire), first.handle().into_raw());
1459 assert_eq!(first_runtime.active_leases.load(Ordering::Acquire), 1);
1460 assert_eq!(second_runtime.active_leases.load(Ordering::Acquire), 0);
1461
1462 assert!(!commit_user_address_space_activation(
1463 0,
1464 active.load(Ordering::Acquire),
1465 Some(first_runtime),
1466 third.handle().into_raw(),
1467 third_runtime,
1468 |_, transition| {
1469 assert_eq!(
1470 transition,
1471 HardwareAddressSpaceTransition::DifferentAddressSpace
1472 );
1473 },
1474 |next| active.store(next, Ordering::Release),
1475 ));
1476 assert_eq!(active.load(Ordering::Acquire), third.handle().into_raw());
1477 assert_eq!(first_runtime.active_leases.load(Ordering::Acquire), 0);
1478 assert_eq!(second_runtime.active_leases.load(Ordering::Acquire), 0);
1479 assert_eq!(third_runtime.active_leases.load(Ordering::Acquire), 1);
1480 assert_eq!(shared.active_mask(), 0);
1481 assert_eq!(other.active_mask(), 1);
1482
1483 third_runtime.active_leases.store(0, Ordering::Release);
1484 other.deactivate(0);
1485 }
1486
1487 #[test]
1488 fn different_address_space_reusing_same_root_requires_hardware_install() {
1489 assert!(hardware_root_install_required(
1490 0x4000,
1491 0x4000,
1492 HardwareAddressSpaceTransition::DifferentAddressSpace,
1493 ));
1494 assert!(!hardware_root_install_required(
1495 0x4000,
1496 0x4000,
1497 HardwareAddressSpaceTransition::SameAddressSpace,
1498 ));
1499 }
1500}