1use alloc::{
9 borrow::ToOwned,
10 collections::BTreeMap,
11 sync::{Arc, Weak},
12 vec::Vec,
13};
14use core::{
15 ops::{
16 Bound::{Excluded, Unbounded},
17 Deref,
18 },
19 sync::atomic::{AtomicBool, AtomicU8, AtomicU64, AtomicUsize, Ordering},
20};
21
22use ax_memory_addr::{PhysAddr, VirtAddr};
23use ax_runtime::hal::trap::PageFaultFlags;
24
25use super::{AddrSpace, FaultResult, PageFaultApplyOutcome, TransparentHugePageMode};
26use crate::sync::{IrqMutex, Mutex};
27
28mod work_queue;
29use work_queue::{MmWorkLink, MmWorkQueue};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct AddressSpaceId(u64);
34
35impl AddressSpaceId {
36 pub(crate) fn allocate() -> Self {
37 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
38 Self(NEXT_ID.fetch_add(1, Ordering::Relaxed))
39 }
40
41 pub const fn get(self) -> u64 {
43 self.0
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
49pub struct VmEpoch(u64);
50
51impl VmEpoch {
52 pub const fn new(value: u64) -> Self {
53 Self(value)
54 }
55
56 pub const fn get(self) -> u64 {
57 self.0
58 }
59
60 pub const fn next(self) -> Self {
61 Self(self.0.saturating_add(1))
62 }
63
64 pub const fn checked_next(self) -> Option<Self> {
65 match self.0.checked_add(1) {
66 Some(value) => Some(Self(value)),
67 None => None,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct AddressSpaceTag {
75 pub hardware_tag: u16,
76 pub generation: u64,
77 pub mode: TagMode,
78}
79
80pub type CpuMask = usize;
83
84impl AddressSpaceTag {
85 pub const fn tagged(hardware_tag: u16, generation: u64) -> Self {
86 Self {
87 hardware_tag,
88 generation,
89 mode: TagMode::Tagged,
90 }
91 }
92
93 pub const fn full_flush(generation: u64) -> Self {
94 Self {
95 hardware_tag: 0,
96 generation,
97 mode: TagMode::FullFlush,
98 }
99 }
100
101 pub const fn is_tagged(self) -> bool {
102 matches!(self.mode, TagMode::Tagged)
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum TagMode {
108 Tagged,
109 FullFlush,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub struct TagAllocation {
115 pub tag: AddressSpaceTag,
116 pub rollover: bool,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum TagAllocationError {
124 GenerationExhausted,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct AddressSpaceTagAllocator {
136 capacity: u32,
137 next: u32,
138 generation: u64,
139 mode: TagMode,
140}
141
142impl AddressSpaceTagAllocator {
143 pub const fn new(capacity: u32) -> Self {
144 let capacity = if capacity > (1 << 16) {
145 1 << 16
146 } else {
147 capacity
148 };
149 if capacity <= 1 {
150 Self {
151 capacity,
152 next: 0,
153 generation: 0,
154 mode: TagMode::FullFlush,
155 }
156 } else {
157 Self {
158 capacity,
159 next: 1,
160 generation: 0,
161 mode: TagMode::Tagged,
162 }
163 }
164 }
165
166 pub const fn mode(self) -> TagMode {
167 self.mode
168 }
169
170 pub const fn capacity(self) -> u32 {
171 self.capacity
172 }
173
174 pub const fn generation(self) -> u64 {
175 self.generation
176 }
177
178 pub fn allocate(&mut self) -> Result<TagAllocation, TagAllocationError> {
179 if self.mode == TagMode::FullFlush {
180 return Ok(TagAllocation {
181 tag: AddressSpaceTag::full_flush(self.generation),
182 rollover: false,
183 });
184 }
185 let mut rollover = false;
186 if self.next >= self.capacity {
187 self.rollover()?;
188 rollover = true;
189 }
190 let tag = u16::try_from(self.next).map_err(|_| TagAllocationError::GenerationExhausted)?;
191 self.next = self
192 .next
193 .checked_add(1)
194 .ok_or(TagAllocationError::GenerationExhausted)?;
195 Ok(TagAllocation {
196 tag: AddressSpaceTag::tagged(tag, self.generation),
197 rollover,
198 })
199 }
200
201 pub fn rollover(&mut self) -> Result<u64, TagAllocationError> {
203 if self.mode == TagMode::FullFlush {
204 return Ok(self.generation);
205 }
206 self.generation = self
207 .generation
208 .checked_add(1)
209 .ok_or(TagAllocationError::GenerationExhausted)?;
210 self.next = 1;
211 Ok(self.generation)
212 }
213}
214
215static TAG_ALLOCATOR: IrqMutex<Option<AddressSpaceTagAllocator>> = IrqMutex::new(None);
222
223fn allocate_default_tag(epoch: u64) -> AddressSpaceTag {
224 let mut allocator_slot = TAG_ALLOCATOR.lock();
225 let allocator = allocator_slot.get_or_insert_with(|| {
226 AddressSpaceTagAllocator::new(ax_runtime::hal::cache::freeze_address_space_tag_capacity())
227 });
228 let mode = allocator.mode();
229 let capacity = allocator.capacity();
230 let generation = allocator.generation();
231 let allocation = allocator.allocate();
232 match (mode, capacity, allocation) {
233 (TagMode::Tagged, _, Ok(allocation)) => allocation.tag,
234 (_, _, Ok(allocation)) => AddressSpaceTag::full_flush(epoch.max(allocation.tag.generation)),
235 (_, 0, _) | (_, 1, _) | (_, _, Err(TagAllocationError::GenerationExhausted)) => {
236 AddressSpaceTag::full_flush(epoch.max(generation))
237 }
238 }
239}
240
241impl Default for AddressSpaceTag {
242 fn default() -> Self {
243 Self {
244 hardware_tag: 0,
245 generation: 0,
246 mode: TagMode::FullFlush,
247 }
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub struct InstalledPageTableRoot {
254 space_id: AddressSpaceId,
255 root: PhysAddr,
256 tag: AddressSpaceTag,
257 epoch: VmEpoch,
258}
259
260impl InstalledPageTableRoot {
261 pub const fn space_id(self) -> AddressSpaceId {
263 self.space_id
264 }
265
266 pub(crate) const fn root(self) -> PhysAddr {
267 self.root
268 }
269
270 pub const fn tag(self) -> AddressSpaceTag {
272 self.tag
273 }
274
275 pub const fn epoch(self) -> VmEpoch {
277 self.epoch
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub struct AddressSpaceCpuState {
286 pub mm_id: AddressSpaceId,
287 pub active_cpus: CpuMask,
288 pub installed_epoch: VmEpoch,
289 pub tag: AddressSpaceTag,
290}
291
292impl AddressSpaceCpuState {
293 pub fn is_active(&self, cpu: usize) -> bool {
294 cpu < usize::BITS as usize && self.active_cpus & (1usize << cpu) != 0
295 }
296}
297
298pub type InstalledAddressSpace = InstalledPageTableRoot;
301
302#[repr(u8)]
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub enum MmState {
305 Live = 0,
306 Retiring = 1,
307 Retired = 2,
308 Reclaiming = 3,
309 Freed = 4,
310 NeedsRepair = 5,
311}
312
313impl MmState {
314 fn from_u8(value: u8) -> Self {
315 match value {
316 0 => Self::Live,
317 1 => Self::Retiring,
318 2 => Self::Retired,
319 3 => Self::Reclaiming,
320 4 => Self::Freed,
321 _ => Self::NeedsRepair,
322 }
323 }
324}
325
326struct MmInner {
327 aspace: Arc<Mutex<AddrSpace>>,
328 id: AddressSpaceId,
329 root: AtomicUsize,
330 epoch: Arc<AtomicU64>,
331 tag: AddressSpaceTag,
332 transparent_huge_page_mode: AtomicU8,
333 install_seq: AtomicU64,
334 lifecycle_gate: IrqMutex<()>,
338 state: AtomicU8,
339 user_refs: AtomicUsize,
340 kernel_pins: AtomicUsize,
341 active_count: AtomicUsize,
342 active_mask: Arc<AtomicUsize>,
343 runtime_cpu_state: Arc<ax_runtime::thread::AddressSpaceCpuState>,
344 active_per_cpu: [AtomicUsize; usize::BITS as usize],
348 retire_queued: AtomicBool,
349 work_link: IrqMutex<MmWorkLink>,
352}
353
354#[derive(Clone, Copy)]
355enum ActivationMode {
356 Exclusive,
357 SchedulerHandoff,
358}
359
360enum ActivationAuthority<'a> {
361 UserOwner(&'a AtomicBool),
363 PinnedContinuation,
366}
367
368impl ActivationAuthority<'_> {
369 fn permits(&self, state: MmState) -> bool {
370 match self {
371 Self::UserOwner(owner) => {
372 owner.load(Ordering::Acquire) && matches!(state, MmState::Live)
373 }
374 Self::PinnedContinuation => matches!(state, MmState::Live | MmState::Retiring),
375 }
376 }
377}
378
379impl MmInner {
380 fn state(&self) -> MmState {
381 MmState::from_u8(self.state.load(Ordering::Acquire))
382 }
383
384 fn transparent_huge_page_mode(&self) -> TransparentHugePageMode {
385 TransparentHugePageMode::from_storage(
386 self.transparent_huge_page_mode.load(Ordering::Acquire),
387 )
388 }
389
390 fn is_quiescent_locked(&self) -> bool {
391 self.user_refs.load(Ordering::Relaxed) == 0
392 && self.kernel_pins.load(Ordering::Relaxed) == 0
393 && self.active_count.load(Ordering::Relaxed) == 0
394 && self.active_mask.load(Ordering::Relaxed) == 0
395 }
396
397 fn maybe_retire_locked(&self) {
398 if !self.is_quiescent_locked() {
399 return;
400 }
401 let _ = self.state.compare_exchange(
402 MmState::Retiring as u8,
403 MmState::Retired as u8,
404 Ordering::AcqRel,
405 Ordering::Acquire,
406 );
407 }
408
409 fn take_retire_permit(inner: &Arc<Self>) -> Option<RetirePermit> {
410 let _gate = inner.lifecycle_gate.lock();
411 inner.maybe_retire_locked();
412 if inner.state() != MmState::Retired
413 || !inner.is_quiescent_locked()
414 || inner
415 .retire_queued
416 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
417 .is_err()
418 {
419 return None;
420 }
421 Some(RetirePermit(Some(inner.clone())))
422 }
423
424 fn try_pin(inner: &Arc<Self>) -> Result<MmPin, PinError> {
425 let _gate = inner.lifecycle_gate.lock();
426 if inner.state() != MmState::Live {
427 return Err(PinError::Retired);
428 }
429 let pins = inner.kernel_pins.load(Ordering::Relaxed);
430 let Some(next_pins) = pins.checked_add(1) else {
431 return Err(PinError::Overflow);
432 };
433 inner.kernel_pins.store(next_pins, Ordering::Release);
434 Ok(MmPin(inner.clone()))
435 }
436
437 fn installed(&self) -> InstalledAddressSpace {
438 loop {
439 let sequence = self.install_seq.load(Ordering::Acquire);
440 if sequence & 1 != 0 {
441 core::hint::spin_loop();
442 continue;
443 }
444 let root = self.root.load(Ordering::Acquire);
445 let epoch = self.epoch.load(Ordering::Acquire);
446 if self.install_seq.load(Ordering::Acquire) == sequence {
447 let mut tag = self.tag;
448 if tag.mode == TagMode::FullFlush {
449 tag.generation = epoch;
453 }
454 return InstalledAddressSpace {
455 space_id: self.id,
456 root: PhysAddr::from_usize(root),
457 tag,
458 epoch: VmEpoch::new(epoch),
459 };
460 }
461 }
462 }
463
464 fn acquire_activation(
465 inner: &Arc<Self>,
466 cpu: usize,
467 mode: ActivationMode,
468 authority: ActivationAuthority<'_>,
469 ) -> Result<ActivationLease, ActivationError> {
470 if cpu >= ax_runtime::hal::cpu_num().min(usize::BITS as usize) {
471 return Err(ActivationError::InvalidCpu);
472 }
473 let _gate = inner.lifecycle_gate.lock();
474 if !authority.permits(inner.state()) {
475 return Err(ActivationError::Retired);
476 }
477 let bit = 1usize << cpu;
478 let cpu_refs = &inner.active_per_cpu[cpu];
479 if matches!(mode, ActivationMode::Exclusive) && cpu_refs.load(Ordering::Relaxed) != 0 {
480 return Err(ActivationError::AlreadyActive);
481 }
482 let previous_cpu = cpu_refs.load(Ordering::Relaxed);
483 let Some(next_cpu) = previous_cpu.checked_add(1) else {
484 return Err(ActivationError::Overflow);
485 };
486 let previous_total = inner.active_count.load(Ordering::Relaxed);
487 let Some(next_total) = previous_total.checked_add(1) else {
488 return Err(ActivationError::Overflow);
489 };
490 if previous_cpu == 0 {
491 inner.active_mask.fetch_or(bit, Ordering::Release);
492 }
493 cpu_refs.store(next_cpu, Ordering::Release);
494 inner.active_count.store(next_total, Ordering::Release);
495 Ok(ActivationLease {
496 inner: inner.clone(),
497 cpu,
498 installed: inner.installed(),
499 released: false,
500 })
501 }
502}
503
504type MmRegistry = BTreeMap<AddressSpaceId, Weak<MmInner>>;
515static MM_REGISTRY: Mutex<MmRegistry> = Mutex::new(BTreeMap::new());
516static LAZY_FREE_SCAN_CURSOR: AtomicU64 = AtomicU64::new(0);
517
518fn next_registry_id_after<V>(
519 registry: &BTreeMap<AddressSpaceId, V>,
520 after: AddressSpaceId,
521) -> Option<AddressSpaceId> {
522 registry
523 .range((Excluded(after), Unbounded))
524 .next()
525 .or_else(|| registry.first_key_value())
526 .map(|(&id, _)| id)
527}
528
529fn next_registered_mm_after(
534 after: AddressSpaceId,
535) -> Option<(AddressSpaceId, Option<Arc<MmInner>>)> {
536 let registry = MM_REGISTRY.lock();
537 let id = next_registry_id_after(®istry, after)?;
538 Some((id, registry.get(&id).and_then(Weak::upgrade)))
539}
540
541fn reclaim_registered_items<T>(
549 limit: usize,
550 visit_limit: usize,
551 cursor: &AtomicU64,
552 mut next: impl FnMut(AddressSpaceId) -> Option<(AddressSpaceId, T)>,
553 mut reclaim: impl FnMut(T, usize) -> usize,
554) -> usize {
555 if limit == 0 {
556 return 0;
557 }
558 let mut reclaimed = 0;
559 let mut visited = 0;
560 let mut first_visited = None;
561 while visited < visit_limit && reclaimed < limit {
562 let after = AddressSpaceId(cursor.load(Ordering::Acquire));
563 let Some((id, item)) = next(after) else {
564 break;
565 };
566 if first_visited == Some(id) {
567 break;
568 }
569 if first_visited.is_none() {
570 first_visited = Some(id);
571 }
572 cursor.store(id.get(), Ordering::Release);
573 visited += 1;
574 let remaining = limit - reclaimed;
575 let pages = reclaim(item, remaining);
576 debug_assert!(pages <= remaining);
577 reclaimed += pages.min(remaining);
578 }
579 reclaimed
580}
581
582fn registered_mm(id: AddressSpaceId) -> Option<Weak<MmInner>> {
583 MM_REGISTRY.lock().get(&id).cloned()
584}
585
586fn register_mm(inner: &Arc<MmInner>) -> Result<(), MmCreateError> {
587 let mut registry = MM_REGISTRY.lock();
588 if registry.contains_key(&inner.id) {
589 return Err(MmCreateError::DuplicateIdentity);
590 }
591 let displaced = registry.insert(inner.id, Arc::downgrade(inner));
592 drop(registry);
593 debug_assert!(displaced.is_none());
594 drop(displaced);
595 Ok(())
596}
597
598fn unregister_mm(inner: &Arc<MmInner>) {
599 let removed = remove_registered_mm(inner.id);
600 debug_assert!(removed, "address-space identity was not registered");
601}
602
603fn remove_registered_mm(id: AddressSpaceId) -> bool {
604 let removed = MM_REGISTRY.lock().remove(&id);
605 let found = removed.is_some();
606 drop(removed);
607 found
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq)]
611pub(crate) enum RmapMmLookupError {
612 Gone,
613 Busy,
614}
615
616pub(crate) fn is_address_space_live(id: AddressSpaceId) -> bool {
624 match registered_mm(id) {
625 None => true,
630 Some(weak) => weak
631 .upgrade()
632 .is_some_and(|inner| inner.state() == MmState::Live),
633 }
634}
635
636pub(crate) fn pin_mm_for_rmap(id: AddressSpaceId) -> Result<MmPin, RmapMmLookupError> {
640 let weak = registered_mm(id);
641 let Some(weak) = weak else {
642 return Err(RmapMmLookupError::Gone);
643 };
644 let Some(inner) = weak.upgrade() else {
645 remove_registered_mm(id);
646 return Err(RmapMmLookupError::Gone);
647 };
648 match inner.state() {
649 MmState::Live => MmInner::try_pin(&inner).map_err(|_| RmapMmLookupError::Busy),
650 MmState::Freed => Err(RmapMmLookupError::Gone),
651 MmState::Retiring | MmState::Retired | MmState::Reclaiming | MmState::NeedsRepair => {
652 Err(RmapMmLookupError::Busy)
653 }
654 }
655}
656
657pub struct MmHandle {
659 inner: Arc<MmInner>,
660 owner: AtomicBool,
665}
666
667impl MmHandle {
668 pub(crate) fn from_arc(aspace: Arc<Mutex<AddrSpace>>) -> Result<Self, MmCreateError> {
670 let epoch = aspace.lock().vm_epoch().get();
671 Self::from_arc_with_tag(aspace, allocate_default_tag(epoch))
672 }
673
674 pub(crate) fn from_arc_with_tag(
678 aspace: Arc<Mutex<AddrSpace>>,
679 tag: AddressSpaceTag,
680 ) -> Result<Self, MmCreateError> {
681 let (id, root, epoch, epoch_source, active_mask) = {
682 let guard = aspace.lock();
683 (
684 guard.address_space_id(),
685 guard.materialized_root().as_usize(),
686 guard.vm_epoch().get(),
687 guard.published_epoch_source(),
688 guard.tlb_targets(),
689 )
690 };
691 let tag = if tag.mode == TagMode::FullFlush {
692 AddressSpaceTag::full_flush(epoch)
693 } else {
694 tag
695 };
696 let handle = Self {
697 inner: Arc::new(MmInner {
698 aspace,
699 id,
700 root: AtomicUsize::new(root),
701 epoch: epoch_source,
702 tag,
703 transparent_huge_page_mode: AtomicU8::new(TransparentHugePageMode::Enabled as u8),
704 install_seq: AtomicU64::new(0),
705 lifecycle_gate: IrqMutex::new(()),
706 state: AtomicU8::new(MmState::Live as u8),
707 user_refs: AtomicUsize::new(1),
708 kernel_pins: AtomicUsize::new(0),
709 active_count: AtomicUsize::new(0),
710 runtime_cpu_state: Arc::new(
711 ax_runtime::thread::AddressSpaceCpuState::with_mm_active_mask(
712 PhysAddr::from_usize(root),
713 active_mask.clone(),
714 ),
715 ),
716 active_mask,
717 active_per_cpu: core::array::from_fn(|_| AtomicUsize::new(0)),
718 retire_queued: AtomicBool::new(false),
719 work_link: IrqMutex::new(MmWorkLink::default()),
720 }),
721 owner: AtomicBool::new(true),
722 };
723 register_mm(&handle.inner)?;
724 Ok(handle)
725 }
726
727 pub fn id(&self) -> AddressSpaceId {
728 self.inner.id
729 }
730
731 pub fn state(&self) -> MmState {
732 self.inner.state()
733 }
734
735 pub fn user_refs(&self) -> usize {
736 self.inner.user_refs.load(Ordering::Acquire)
737 }
738
739 pub fn kernel_pins(&self) -> usize {
740 self.inner.kernel_pins.load(Ordering::Acquire)
741 }
742
743 pub fn active_cpus(&self) -> usize {
744 self.inner.active_count.load(Ordering::Acquire)
745 }
746
747 pub fn active_cpu_mask(&self) -> usize {
748 self.inner.active_mask.load(Ordering::Acquire)
749 }
750
751 pub fn cpu_state(&self) -> AddressSpaceCpuState {
756 let installed = self.installed();
757 AddressSpaceCpuState {
758 mm_id: installed.space_id,
759 active_cpus: self.active_cpu_mask(),
760 installed_epoch: installed.epoch,
761 tag: installed.tag,
762 }
763 }
764
765 pub fn installed(&self) -> InstalledAddressSpace {
766 self.inner.installed()
767 }
768
769 pub fn transparent_huge_page_mode(&self) -> TransparentHugePageMode {
771 self.inner.transparent_huge_page_mode()
772 }
773
774 pub fn set_transparent_huge_page_mode(&self, mode: TransparentHugePageMode) {
776 let _aspace = self.inner.aspace.lock();
777 self.inner
778 .transparent_huge_page_mode
779 .store(mode as u8, Ordering::Release);
780 }
781
782 pub fn refresh_installation(&self) {
784 let guard = self.inner.aspace.lock();
785 let _gate = self.inner.lifecycle_gate.lock();
788 self.inner.install_seq.fetch_add(1, Ordering::AcqRel);
789 self.inner
790 .root
791 .store(guard.materialized_root().as_usize(), Ordering::Release);
792 self.inner
793 .epoch
794 .store(guard.vm_epoch().get(), Ordering::Release);
795 self.inner.install_seq.fetch_add(1, Ordering::Release);
796 }
797
798 pub fn clone_user_ref(&self) -> Result<Self, CloneUserRefError> {
800 let _gate = self.inner.lifecycle_gate.lock();
801 if !self.owner.load(Ordering::Relaxed) || self.inner.state() != MmState::Live {
802 return Err(CloneUserRefError::Retired);
803 }
804 let refs = self.inner.user_refs.load(Ordering::Relaxed);
805 let Some(next_refs) = refs.checked_add(1) else {
806 return Err(CloneUserRefError::Overflow);
807 };
808 self.inner.user_refs.store(next_refs, Ordering::Release);
809 Ok(Self {
810 inner: self.inner.clone(),
811 owner: AtomicBool::new(true),
812 })
813 }
814
815 pub fn pin(&self) -> Result<MmPin, PinError> {
816 MmInner::try_pin(&self.inner)
817 }
818
819 pub fn activation(&self, cpu: usize) -> Result<ActivationLease, ActivationError> {
820 MmInner::acquire_activation(
821 &self.inner,
822 cpu,
823 ActivationMode::Exclusive,
824 ActivationAuthority::UserOwner(&self.owner),
825 )
826 }
827
828 pub fn retire_if_quiescent(&self) -> Option<RetirePermit> {
830 MmInner::take_retire_permit(&self.inner)
831 }
832
833 pub fn release_user_ref(&self) -> Option<RetirePermit> {
838 {
839 let _gate = self.inner.lifecycle_gate.lock();
840 if self.owner.swap(false, Ordering::Relaxed) {
841 let previous = self.inner.user_refs.load(Ordering::Relaxed);
842 debug_assert!(previous > 0, "MmHandle user reference underflow");
843 self.inner.user_refs.store(previous - 1, Ordering::Release);
844 if previous == 1 {
845 let _ = self.inner.state.compare_exchange(
846 MmState::Live as u8,
847 MmState::Retiring as u8,
848 Ordering::AcqRel,
849 Ordering::Acquire,
850 );
851 }
852 self.inner.maybe_retire_locked();
853 }
854 }
855 self.retire_if_quiescent()
856 }
857}
858
859impl Drop for MmHandle {
860 fn drop(&mut self) {
861 {
862 let _gate = self.inner.lifecycle_gate.lock();
863 if !self.owner.swap(false, Ordering::Relaxed) {
864 return;
865 }
866 let previous = self.inner.user_refs.load(Ordering::Relaxed);
867 debug_assert!(previous > 0, "MmHandle user reference underflow");
868 self.inner.user_refs.store(previous - 1, Ordering::Release);
869 if previous == 1 {
870 let _ = self.inner.state.compare_exchange(
871 MmState::Live as u8,
872 MmState::Retiring as u8,
873 Ordering::AcqRel,
874 Ordering::Acquire,
875 );
876 }
877 self.inner.maybe_retire_locked();
878 }
879 queue_if_retired(&self.inner);
880 }
881}
882
883pub struct MmPin(Arc<MmInner>);
887
888impl MmPin {
889 pub fn id(&self) -> AddressSpaceId {
890 self.0.id
891 }
892
893 pub fn transparent_huge_page_mode(&self) -> TransparentHugePageMode {
895 self.0.transparent_huge_page_mode()
896 }
897
898 pub fn set_transparent_huge_page_mode(&self, mode: TransparentHugePageMode) {
900 let _aspace = self.0.aspace.lock();
901 self.0
902 .transparent_huge_page_mode
903 .store(mode as u8, Ordering::Release);
904 }
905
906 pub fn handle_page_fault_result(
908 &self,
909 vaddr: VirtAddr,
910 access_flags: PageFaultFlags,
911 ) -> FaultResult {
912 let plan = {
913 let aspace = self.0.aspace.lock();
914 let mode = self.0.transparent_huge_page_mode();
915 match aspace.plan_page_fault(vaddr, access_flags, mode) {
916 Ok(plan) => plan,
917 Err(result) => return result,
918 }
919 };
920 let prepared = match AddrSpace::prepare_page_fault(plan) {
924 Ok(prepared) => prepared,
925 Err(result) => return result,
926 };
927 let mut attempt = prepared.into_apply_attempt();
928 let outcome = {
929 let mut aspace = self.0.aspace.lock();
930 aspace.apply_prepared_page_fault(&mut attempt)
931 };
932 let result = match outcome {
933 PageFaultApplyOutcome::Complete(result) => result,
934 PageFaultApplyOutcome::Cancel(result) => {
935 if attempt.cancel().is_ok() {
936 result
937 } else {
938 FaultResult::Retry
939 }
940 }
941 PageFaultApplyOutcome::NeedsRepair(result) => {
942 attempt.release_to_repair_state();
943 result
944 }
945 PageFaultApplyOutcome::CancelPendingTlb { request, targets } => {
946 if attempt.cancel().is_ok()
951 && AddrSpace::flush_tlb_requests(core::slice::from_ref(&request), &targets)
952 .is_ok()
953 {
954 let aspace = self.0.aspace.lock();
955 let _ = aspace.acknowledge_tlb_requests(core::slice::from_ref(&request));
956 }
957 FaultResult::Retry
958 }
959 PageFaultApplyOutcome::PendingTlb { request, targets } => {
960 if AddrSpace::flush_tlb_requests(core::slice::from_ref(&request), &targets).is_err()
961 {
962 FaultResult::Retry
963 } else {
964 let aspace = self.0.aspace.lock();
965 if aspace
966 .acknowledge_tlb_requests(core::slice::from_ref(&request))
967 .is_ok()
968 {
969 FaultResult::Handled
970 } else {
971 FaultResult::Retry
972 }
973 }
974 }
975 };
976 if matches!(result, FaultResult::Handled) {
977 ax_runtime::hal::cache::update_mmu_cache(vaddr);
978 }
979 result
980 }
981
982 pub fn handle_page_fault(&self, vaddr: VirtAddr, access_flags: PageFaultFlags) -> bool {
984 loop {
985 match self.handle_page_fault_result(vaddr, access_flags) {
986 FaultResult::Handled => return true,
987 FaultResult::Retry => crate::task::yield_now(),
992 _ => return false,
993 }
994 }
995 }
996
997 pub(crate) fn activation_for_switch(
1001 &self,
1002 cpu: usize,
1003 ) -> Result<ActivationLease, ActivationError> {
1004 MmInner::acquire_activation(
1005 &self.0,
1006 cpu,
1007 ActivationMode::SchedulerHandoff,
1008 ActivationAuthority::PinnedContinuation,
1009 )
1010 }
1011}
1012
1013impl Deref for MmPin {
1014 type Target = Mutex<AddrSpace>;
1015
1016 fn deref(&self) -> &Self::Target {
1017 &self.0.aspace
1018 }
1019}
1020
1021impl Drop for MmPin {
1022 fn drop(&mut self) {
1023 {
1024 let _gate = self.0.lifecycle_gate.lock();
1025 let previous = self.0.kernel_pins.load(Ordering::Relaxed);
1026 debug_assert!(previous > 0, "MmPin reference underflow");
1027 self.0.kernel_pins.store(previous - 1, Ordering::Release);
1028 self.0.maybe_retire_locked();
1029 }
1030 queue_if_retired(&self.0);
1031 }
1032}
1033
1034pub struct ActivationLease {
1037 inner: Arc<MmInner>,
1038 cpu: usize,
1039 installed: InstalledPageTableRoot,
1040 released: bool,
1041}
1042
1043impl ActivationLease {
1044 pub fn installed(&self) -> InstalledPageTableRoot {
1045 self.installed
1046 }
1047
1048 pub const fn cpu(&self) -> usize {
1049 self.cpu
1050 }
1051
1052 pub(crate) fn into_scheduler_activation(
1054 mut self,
1055 ) -> ax_runtime::thread::SchedulerAddressSpaceActivation {
1056 let activation = ax_runtime::thread::SchedulerAddressSpaceActivation::new(
1057 task_address_space(self.installed),
1058 self.cpu,
1059 self.inner.clone(),
1060 );
1061 self.released = true;
1062 activation
1063 }
1064
1065 pub(crate) fn release_after_root_switch(mut self) {
1072 release_activation_accounting(&self.inner, self.cpu);
1073 self.released = true;
1074 }
1075
1076 pub fn release_after_kernel_switch(self) {
1079 self.release_after_root_switch();
1080 }
1081}
1082
1083impl Drop for ActivationLease {
1084 fn drop(&mut self) {
1085 if !self.released {
1086 abandon_activation(self.inner.clone(), self.cpu);
1087 }
1088 }
1089}
1090
1091fn abandon_activation(inner: Arc<MmInner>, cpu: usize) {
1092 {
1093 let _gate = inner.lifecycle_gate.lock();
1094 inner
1095 .state
1096 .store(MmState::NeedsRepair as u8, Ordering::Release);
1097 }
1098 warn!(
1099 "address-space activation for mm {} cpu {} dropped before root-switch proof",
1100 inner.id.get(),
1101 cpu
1102 );
1103 enqueue_repair_candidate(inner);
1104}
1105
1106fn release_activation_accounting(inner: &Arc<MmInner>, cpu: usize) {
1107 {
1108 let _gate = inner.lifecycle_gate.lock();
1109 let previous = inner.active_count.load(Ordering::Relaxed);
1110 debug_assert!(previous > 0, "ActivationLease reference underflow");
1111 inner.active_count.store(previous - 1, Ordering::Release);
1112 if cpu < usize::BITS as usize {
1113 let cpu_refs = &inner.active_per_cpu[cpu];
1114 let previous_cpu = cpu_refs.load(Ordering::Relaxed);
1115 debug_assert!(previous_cpu > 0, "per-CPU activation reference underflow");
1116 cpu_refs.store(previous_cpu - 1, Ordering::Release);
1117 if previous_cpu == 1 {
1118 inner
1119 .active_mask
1120 .fetch_and(!(1usize << cpu), Ordering::Release);
1121 }
1122 }
1123 inner.maybe_retire_locked();
1124 }
1125 queue_if_retired(inner);
1126}
1127
1128impl ax_runtime::thread::SchedulerAddressSpaceOwner for MmInner {
1129 fn release_after_root_switch(
1130 self: Arc<Self>,
1131 proof: ax_runtime::thread::AddressSpaceSwitchProof,
1132 ) {
1133 release_activation_accounting(&self, proof.cpu());
1134 }
1135
1136 fn cancel_before_install(self: Arc<Self>, cpu: usize) {
1137 release_activation_accounting(&self, cpu);
1138 }
1139
1140 fn abandon(self: Arc<Self>, cpu: usize) {
1141 abandon_activation(self, cpu);
1142 }
1143}
1144
1145fn task_address_space(installed: InstalledAddressSpace) -> ax_hal::context::InstalledAddressSpace {
1146 ax_hal::context::InstalledAddressSpace::user(
1147 installed.space_id().get(),
1148 installed.root(),
1149 installed.tag().hardware_tag,
1150 installed.tag().generation,
1151 installed.epoch().get(),
1152 match installed.tag().mode {
1153 TagMode::Tagged => ax_hal::context::InstalledAddressSpaceMode::Tagged,
1154 TagMode::FullFlush => ax_hal::context::InstalledAddressSpaceMode::FullFlush,
1155 },
1156 )
1157 .expect("typed Starry address-space installation must remain valid")
1158}
1159
1160struct RuntimeMmOwner {
1163 _inner: Arc<MmInner>,
1164 pin: IrqMutex<Option<MmPin>>,
1165}
1166
1167unsafe impl ax_runtime::thread::UserAddressSpaceOwner for RuntimeMmOwner {
1172 fn prepare_activation(
1173 &self,
1174 cpu: usize,
1175 ) -> Result<
1176 ax_runtime::thread::SchedulerAddressSpaceActivation,
1177 ax_runtime::task::thread::TaskError,
1178 > {
1179 self.pin
1180 .lock()
1181 .as_ref()
1182 .ok_or(ax_runtime::task::thread::TaskError::InvalidRuntimeHandle)?
1183 .activation_for_switch(cpu)
1184 .map(ActivationLease::into_scheduler_activation)
1185 .map_err(|_| ax_runtime::task::thread::TaskError::InvalidRuntimeHandle)
1186 }
1187
1188 fn detach_from_task(&self) {
1189 let pin = self.pin.lock().take();
1190 drop(pin);
1191 }
1192}
1193
1194impl MmHandle {
1195 pub(crate) fn scheduler_address_space(
1197 &self,
1198 ) -> Result<ax_runtime::thread::TaskAddressSpace, ax_runtime::task::thread::TaskError> {
1199 let pin = self
1200 .pin()
1201 .map_err(|_| ax_runtime::task::thread::TaskError::InvalidRuntimeHandle)?;
1202 ax_runtime::thread::TaskAddressSpace::new_managed(
1203 self.installed().root(),
1204 self.inner.runtime_cpu_state.clone(),
1205 RuntimeMmOwner {
1206 _inner: self.inner.clone(),
1207 pin: IrqMutex::new(Some(pin)),
1208 },
1209 )
1210 }
1211}
1212
1213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1214pub enum ActivationError {
1215 Retired,
1216 AlreadyActive,
1217 InvalidCpu,
1218 Overflow,
1219}
1220
1221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1222pub enum CloneUserRefError {
1223 Retired,
1224 Overflow,
1225}
1226
1227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1228pub enum PinError {
1229 Retired,
1230 Overflow,
1231}
1232
1233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1234pub enum MmCreateError {
1235 DuplicateIdentity,
1236}
1237
1238pub struct RetirePermit(Option<Arc<MmInner>>);
1240
1241static RETIRE_QUEUE: IrqMutex<MmWorkQueue> = IrqMutex::new(MmWorkQueue::new());
1245static REPAIR_QUEUE: IrqMutex<MmWorkQueue> = IrqMutex::new(MmWorkQueue::new());
1246static RECLAIMER_STARTED: AtomicBool = AtomicBool::new(false);
1247static REPAIR_RETRY_REQUESTED: AtomicBool = AtomicBool::new(false);
1248
1249struct CoalescedReclaimRequest {
1250 pending: AtomicBool,
1251}
1252
1253impl CoalescedReclaimRequest {
1254 const fn new() -> Self {
1255 Self {
1256 pending: AtomicBool::new(false),
1257 }
1258 }
1259
1260 fn request(&self) {
1261 self.pending.store(true, Ordering::Release);
1262 }
1263
1264 fn run_one_batch(&self, limit: usize, reclaim: impl FnOnce(usize) -> usize) -> usize {
1265 if limit == 0 || !self.pending.swap(false, Ordering::AcqRel) {
1266 return 0;
1267 }
1268 let reclaimed = reclaim(limit);
1269 if reclaimed >= limit {
1270 self.request();
1273 }
1274 reclaimed
1275 }
1276}
1277
1278static LAZY_FREE_RECLAIM: CoalescedReclaimRequest = CoalescedReclaimRequest::new();
1279#[cfg(test)]
1280static LAZY_FREE_RECLAIM_REQUESTS: AtomicUsize = AtomicUsize::new(0);
1281
1282pub(super) fn request_lazy_free_reclaim() {
1289 #[cfg(test)]
1290 LAZY_FREE_RECLAIM_REQUESTS.fetch_add(1, Ordering::Relaxed);
1291 LAZY_FREE_RECLAIM.request();
1292}
1293
1294#[cfg(test)]
1295pub(super) fn lazy_free_reclaim_request_count_for_test() -> usize {
1296 LAZY_FREE_RECLAIM_REQUESTS.load(Ordering::Relaxed)
1297}
1298
1299fn queue_if_retired(inner: &Arc<MmInner>) {
1300 if let Some(permit) = MmInner::take_retire_permit(inner) {
1301 enqueue_retire(permit);
1302 }
1303}
1304
1305pub fn enqueue_retire(permit: RetirePermit) {
1307 drop(permit);
1308}
1309
1310fn enqueue_mm_work(queue: &IrqMutex<MmWorkQueue>, inner: Arc<MmInner>) {
1311 let duplicate = queue.lock().push(inner);
1312 drop(duplicate);
1315}
1316
1317fn enqueue_repair_candidate(inner: Arc<MmInner>) {
1318 enqueue_mm_work(&REPAIR_QUEUE, inner);
1319}
1320
1321pub fn reap_retired(limit: usize) -> (usize, usize) {
1325 if limit == 0 {
1326 return (0, 0);
1327 }
1328 let count = {
1329 let queue = RETIRE_QUEUE.lock();
1330 limit.min(queue.len())
1331 };
1332 let mut reclaimed = 0;
1333 let mut failed = 0;
1334 for _ in 0..count {
1335 let Some(inner) = RETIRE_QUEUE.lock().pop() else {
1336 break;
1337 };
1338 if inner.state() == MmState::Freed {
1339 release_mm_shell(inner);
1343 continue;
1344 }
1345 match RetirePermit(Some(inner)).reclaim() {
1346 Ok(()) => reclaimed += 1,
1347 Err(_) => failed += 1,
1348 }
1349 }
1350 (reclaimed, failed)
1351}
1352
1353pub fn reclaim_live_lazy_free_pages(limit: usize) -> usize {
1356 if limit == 0 {
1357 return 0;
1358 }
1359 let visit_limit = {
1360 let registry = MM_REGISTRY.lock();
1361 registry.len()
1362 };
1363 reclaim_registered_items(
1364 limit,
1365 visit_limit,
1366 &LAZY_FREE_SCAN_CURSOR,
1367 next_registered_mm_after,
1368 |inner, remaining| {
1369 let Some(inner) = inner else {
1370 return 0;
1371 };
1372 let Ok(pin) = MmInner::try_pin(&inner) else {
1373 return 0;
1374 };
1375 match pin.lock().reclaim_lazy_free_pages(remaining) {
1376 Ok(pages) => pages,
1377 Err(error) => {
1378 warn!(
1379 "lazy-free reclaim for address space {} entered repair: {error}",
1380 pin.id().get()
1381 );
1382 0
1383 }
1384 }
1385 },
1386 )
1387}
1388
1389pub struct RepairPermit(Arc<MmInner>);
1393
1394pub fn take_repair_candidates(limit: usize) -> Vec<RepairPermit> {
1395 let count = {
1396 let queue = REPAIR_QUEUE.lock();
1397 limit.min(queue.len())
1398 };
1399 let mut candidates: Vec<RepairPermit> = Vec::new();
1400 if candidates.try_reserve_exact(count).is_err() {
1401 return candidates;
1402 }
1403 for _ in 0..count {
1404 let Some(inner) = REPAIR_QUEUE.lock().pop() else {
1405 break;
1406 };
1407 candidates.push(RepairPermit(inner));
1408 }
1409 candidates
1410}
1411
1412pub fn request_repair_retry() {
1419 REPAIR_RETRY_REQUESTED.store(true, Ordering::Release);
1420}
1421
1422impl RepairPermit {
1423 pub fn retry(self) -> Result<(), ReclaimError> {
1424 {
1425 let _gate = self.0.lifecycle_gate.lock();
1426 if !self.0.is_quiescent_locked()
1427 || self
1428 .0
1429 .state
1430 .compare_exchange(
1431 MmState::NeedsRepair as u8,
1432 MmState::Retired as u8,
1433 Ordering::AcqRel,
1434 Ordering::Acquire,
1435 )
1436 .is_err()
1437 {
1438 return Err(ReclaimError::NotRetired);
1439 }
1440 self.0.retire_queued.store(false, Ordering::Release);
1441 }
1442 let permit = MmInner::take_retire_permit(&self.0).ok_or(ReclaimError::NotRetired)?;
1443 enqueue_retire(permit);
1444 Ok(())
1445 }
1446}
1447
1448pub fn spawn_reclaimer_task() {
1452 if RECLAIMER_STARTED
1453 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
1454 .is_err()
1455 {
1456 return;
1457 }
1458 ax_std::thread::Builder::new()
1459 .name("starry-mm-reclaimer".to_owned())
1460 .spawn(|| {
1461 loop {
1462 let _ = ax_mm::retry_kernel_virtual_quarantines(16);
1463 let _ = LAZY_FREE_RECLAIM.run_one_batch(16, reclaim_live_lazy_free_pages);
1464 let _ = reap_retired(16);
1465 if REPAIR_RETRY_REQUESTED.swap(false, Ordering::AcqRel) {
1466 for permit in take_repair_candidates(16) {
1471 let _ = permit.retry();
1472 }
1473 }
1474 ax_std::thread::sleep(core::time::Duration::from_millis(10));
1475 }
1476 })
1477 .expect("MM reclaimer thread must start before userspace");
1478}
1479
1480impl RetirePermit {
1481 fn into_inner(mut self) -> Arc<MmInner> {
1482 self.0.take().expect("retire permit is consumed once")
1483 }
1484
1485 pub fn reclaim(self) -> Result<(), ReclaimError> {
1486 let inner = self.into_inner();
1487 let result = Self::reclaim_inner(&inner);
1488 if result.is_ok() {
1489 release_mm_shell(inner);
1490 } else {
1491 enqueue_repair_candidate(inner);
1492 }
1493 result
1494 }
1495
1496 fn reclaim_inner(inner: &Arc<MmInner>) -> Result<(), ReclaimError> {
1497 {
1498 let _gate = inner.lifecycle_gate.lock();
1499 if !inner.is_quiescent_locked()
1500 || inner
1501 .state
1502 .compare_exchange(
1503 MmState::Retired as u8,
1504 MmState::Reclaiming as u8,
1505 Ordering::AcqRel,
1506 Ordering::Acquire,
1507 )
1508 .is_err()
1509 {
1510 return Err(ReclaimError::NotRetired);
1511 }
1512 }
1513 let result = inner.aspace.lock().try_reclaim_contents();
1514 match result {
1515 Ok(()) => {
1516 {
1517 let _gate = inner.lifecycle_gate.lock();
1518 inner.state.store(MmState::Freed as u8, Ordering::Release);
1519 }
1520 unregister_mm(inner);
1521 Ok(())
1522 }
1523 Err(_) => {
1524 {
1525 let _gate = inner.lifecycle_gate.lock();
1526 inner
1527 .state
1528 .store(MmState::NeedsRepair as u8, Ordering::Release);
1529 inner.retire_queued.store(false, Ordering::Release);
1530 }
1531 Err(ReclaimError::Backend)
1532 }
1533 }
1534 }
1535
1536 pub fn retry(self) -> Result<(), ReclaimError> {
1538 RepairPermit(self.into_inner()).retry()
1539 }
1540}
1541
1542fn release_mm_shell(inner: Arc<MmInner>) {
1546 match Arc::try_unwrap(inner) {
1547 Ok(inner) => drop(inner),
1548 Err(inner) => enqueue_mm_work(&RETIRE_QUEUE, inner),
1549 }
1550}
1551
1552impl Drop for RetirePermit {
1553 fn drop(&mut self) {
1554 if let Some(inner) = self.0.take() {
1555 enqueue_mm_work(&RETIRE_QUEUE, inner);
1556 }
1557 }
1558}
1559
1560impl Drop for RepairPermit {
1561 fn drop(&mut self) {
1562 if self.0.state() == MmState::NeedsRepair {
1563 enqueue_repair_candidate(self.0.clone());
1564 } else {
1565 enqueue_mm_work(&RETIRE_QUEUE, self.0.clone());
1566 }
1567 }
1568}
1569
1570#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1571pub enum ReclaimError {
1572 NotRetired,
1573 Backend,
1574}
1575
1576#[cfg(all(test, axtest))]
1577mod tests {
1578 use super::*;
1579 use crate::mm::MappingOperation;
1580
1581 #[axtest::axtest]
1582 fn failed_direct_reclaim_preserves_repair_ownership() {
1583 let aspace = Arc::new(Mutex::new(
1584 AddrSpace::new_empty(VirtAddr::from(0x7400_0000), 0x1000).unwrap(),
1585 ));
1586 let handle = MmHandle::from_arc(aspace).unwrap();
1587 let weak = Arc::downgrade(&handle.inner);
1588 handle
1589 .inner
1590 .aspace
1591 .lock()
1592 .mutation_gate
1593 .fail_next_commit_before_publish();
1594 let permit = handle.release_user_ref().unwrap();
1595 drop(handle);
1596 assert_eq!(permit.reclaim(), Err(ReclaimError::Backend));
1597 let inner = weak
1598 .upgrade()
1599 .expect("failed reclaim must retain its MM for repair");
1600 assert_eq!(inner.state(), MmState::NeedsRepair);
1601 drop(take_repair_candidates(usize::MAX));
1603 drop(inner);
1604 assert!(weak.upgrade().is_some());
1605 for candidate in take_repair_candidates(usize::MAX) {
1606 if candidate.0.id == weak.upgrade().unwrap().id {
1607 candidate.retry().unwrap();
1608 }
1609 }
1610 let _ = reap_retired(usize::MAX);
1611 let _ = reap_retired(usize::MAX);
1612 assert!(weak.upgrade().is_none());
1613 }
1614
1615 #[axtest::axtest]
1616 fn mm_pin_services_blocking_discard_before_refault_retry() {
1617 use ax_runtime::hal::paging::MappingFlags;
1618
1619 use super::super::{MutationError, TlbRange};
1620
1621 let start = VirtAddr::from(0x7500_0000);
1622 let aspace = Arc::new(Mutex::new(AddrSpace::new_empty(start, 0x1000).unwrap()));
1623 let handle = MmHandle::from_arc(aspace).unwrap();
1624 let pin = handle.pin().unwrap();
1625 {
1626 let mut aspace = pin.lock();
1627 aspace
1628 .map(
1629 start,
1630 0x1000,
1631 MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER,
1632 true,
1633 MappingOperation::new_alloc(start, 0x1000, "[pin-refault]"),
1634 )
1635 .unwrap();
1636 aspace.discard_range(start, 0x1000).unwrap();
1637 let mut discard = aspace.mutation_gate.begin(aspace.id, 1);
1638 discard.add_tlb_range(TlbRange::new(start, 0x1000).unwrap());
1639 assert_eq!(
1640 aspace.mutation_gate.commit(discard).unwrap_err(),
1641 MutationError::TlbPending
1642 );
1643 }
1644 let access = PageFaultFlags::READ | PageFaultFlags::USER;
1645 assert!(matches!(
1646 pin.handle_page_fault_result(start, access),
1647 FaultResult::Retry
1648 ));
1649 assert_eq!(pin.lock().pending_tlb_obligations(), 0);
1650 assert!(matches!(
1651 pin.handle_page_fault_result(start, access),
1652 FaultResult::Handled
1653 ));
1654 drop(pin);
1655 let permit = handle.release_user_ref().unwrap();
1656 drop(handle);
1657 permit.reclaim().unwrap();
1658 }
1659
1660 #[axtest::axtest]
1661 fn faultable_user_copy_retries_pending_tlb_before_reporting_success() {
1662 use ax_runtime::hal::paging::MappingFlags;
1663
1664 use super::super::{MutationError, TlbRange};
1665
1666 let start = VirtAddr::from(0x7600_0000);
1667 let aspace = Arc::new(Mutex::new(AddrSpace::new_empty(start, 0x1000).unwrap()));
1668 let handle = MmHandle::from_arc(aspace).unwrap();
1669 let pin = handle.pin().unwrap();
1670 {
1671 let mut aspace = pin.lock();
1672 aspace
1673 .map(
1674 start,
1675 0x1000,
1676 MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER,
1677 true,
1678 MappingOperation::new_alloc(start, 0x1000, "[copy-refault]"),
1679 )
1680 .unwrap();
1681 aspace.discard_range(start, 0x1000).unwrap();
1682 let mut discard = aspace.mutation_gate.begin(aspace.id, 1);
1683 discard.add_tlb_range(TlbRange::new(start, 0x1000).unwrap());
1684 assert_eq!(
1685 aspace.mutation_gate.commit(discard).unwrap_err(),
1686 MutationError::TlbPending
1687 );
1688 }
1689 assert!(pin.handle_page_fault(start, PageFaultFlags::WRITE));
1692 assert_eq!(pin.lock().pending_tlb_obligations(), 0);
1693 assert!(
1694 pin.lock()
1695 .pt
1696 .query(start)
1697 .is_ok_and(|(_, flags, _)| flags.contains(MappingFlags::WRITE))
1698 );
1699 assert!(!pin.handle_page_fault(start, PageFaultFlags::EXECUTE));
1700 assert!(!pin.handle_page_fault(start + 0x1000, PageFaultFlags::WRITE));
1701 let exhausted = AddrSpace::classify_fault_error(false, crate::StarryError::NoMemory);
1702 pin.lock().mutation_gate.mark_needs_repair();
1703 let quarantined = pin.handle_page_fault_result(start, PageFaultFlags::WRITE);
1704 pin.lock().mutation_gate.clear_repair();
1706 drop(pin);
1707 let permit = handle.release_user_ref().unwrap();
1708 drop(handle);
1709 permit.reclaim().unwrap();
1710 assert!(
1711 exhausted != FaultResult::Retry && matches!(quarantined, FaultResult::Sigbus(_)),
1712 "allocation failure and repair quarantine are terminal: {exhausted:?}, {quarantined:?}"
1713 );
1714 assert_eq!(exhausted, FaultResult::NoMemory);
1715 assert_eq!(
1716 quarantined,
1717 FaultResult::Sigbus(super::super::BusCode::ObjErr)
1718 );
1719 }
1720
1721 #[axtest::axtest]
1722 fn abandoned_activation_retains_the_unproved_hardware_root() {
1723 let aspace = Arc::new(Mutex::new(
1724 AddrSpace::new_empty(VirtAddr::from(0x7300_0000), 0x1000).unwrap(),
1725 ));
1726 let handle = MmHandle::from_arc(aspace).unwrap();
1727 let weak = Arc::downgrade(&handle.inner);
1728 let activation = handle.activation(0).unwrap();
1731 drop(handle);
1732 drop(activation);
1733 let retained = weak.upgrade();
1734 assert!(
1735 retained.is_some(),
1736 "an unproved active root must stay owned by repair quarantine"
1737 );
1738 let inner = retained.unwrap();
1739 assert_eq!(inner.state(), MmState::NeedsRepair);
1740 assert_eq!(inner.active_count.load(Ordering::Acquire), 1);
1741 {
1743 let _gate = inner.lifecycle_gate.lock();
1744 inner.active_count.store(0, Ordering::Release);
1745 inner.active_mask.store(0, Ordering::Release);
1746 inner.active_per_cpu[0].store(0, Ordering::Release);
1747 }
1748 for candidate in take_repair_candidates(usize::MAX) {
1749 if Arc::ptr_eq(&candidate.0, &inner) {
1750 candidate.retry().unwrap();
1751 } else {
1752 drop(candidate);
1753 }
1754 }
1755 drop(inner);
1756 let _ = reap_retired(usize::MAX);
1757 let _ = reap_retired(usize::MAX);
1758 assert!(weak.upgrade().is_none());
1759 }
1760
1761 #[axtest::axtest]
1762 fn lazy_free_reclaim_only_scans_after_a_publication_edge() {
1763 let request = CoalescedReclaimRequest::new();
1764 let mut scans = 0usize;
1765
1766 assert_eq!(
1767 request.run_one_batch(16, |_| {
1768 scans += 1;
1769 0
1770 }),
1771 0
1772 );
1773 assert_eq!(scans, 0, "an idle worker must not scan live MMs");
1774
1775 request.request();
1776 request.request();
1777 assert_eq!(
1778 request.run_one_batch(16, |limit| {
1779 scans += 1;
1780 limit
1781 }),
1782 16
1783 );
1784 assert_eq!(scans, 1, "coalesced publications need one scan");
1785
1786 assert_eq!(
1787 request.run_one_batch(16, |_| {
1788 scans += 1;
1789 3
1790 }),
1791 3,
1792 "a full batch must schedule one bounded continuation"
1793 );
1794 assert_eq!(scans, 2);
1795 assert_eq!(request.run_one_batch(16, |_| unreachable!()), 0);
1796 }
1797
1798 #[axtest::axtest]
1799 fn lazy_free_batches_advance_the_production_registry_cursor() {
1800 let mut registry = BTreeMap::new();
1801 registry.insert(AddressSpaceId(2), ());
1802 registry.insert(AddressSpaceId(5), ());
1803 registry.insert(AddressSpaceId(9), ());
1804 let cursor = AtomicU64::new(0);
1805 let mut visits = Vec::new();
1806
1807 for _ in 0..4 {
1808 assert_eq!(
1809 reclaim_registered_items(
1810 1,
1811 registry.len(),
1812 &cursor,
1813 |after| {
1814 let id = next_registry_id_after(®istry, after)?;
1815 Some((id, id))
1816 },
1817 |id, remaining| {
1818 assert_eq!(remaining, 1);
1819 visits.push(id);
1820 1
1821 },
1822 ),
1823 1
1824 );
1825 }
1826
1827 assert_eq!(
1828 visits,
1829 [
1830 AddressSpaceId(2),
1831 AddressSpaceId(5),
1832 AddressSpaceId(9),
1833 AddressSpaceId(2),
1834 ]
1835 );
1836 }
1837
1838 #[axtest::axtest]
1839 fn tag_allocator_uses_explicit_full_flush_fallback() {
1840 for capacity in [0, 1] {
1841 let mut allocator = AddressSpaceTagAllocator::new(capacity);
1842 let allocation = allocator.allocate().unwrap();
1843 assert_eq!(allocator.mode(), TagMode::FullFlush);
1844 assert_eq!(allocation.tag, AddressSpaceTag::full_flush(0));
1845 assert!(!allocation.rollover);
1846 }
1847 }
1848
1849 #[axtest::axtest]
1850 fn tag_allocator_represents_the_complete_sixteen_bit_space() {
1851 let mut allocator = AddressSpaceTagAllocator::new(1 << 16);
1852 allocator.next = u32::from(u16::MAX);
1853
1854 let last = allocator.allocate().unwrap();
1855 assert_eq!(last.tag, AddressSpaceTag::tagged(u16::MAX, 0));
1856 assert!(!last.rollover);
1857
1858 let reused = allocator.allocate().unwrap();
1859 assert_eq!(reused.tag, AddressSpaceTag::tagged(1, 1));
1860 assert!(reused.rollover);
1861 }
1862
1863 #[axtest::axtest]
1864 fn tag_allocator_rollover_restarts_at_one_in_a_new_generation() {
1865 let mut allocator = AddressSpaceTagAllocator::new(4);
1866 for expected in 1..4 {
1867 let allocation = allocator.allocate().unwrap();
1868 assert_eq!(allocation.tag, AddressSpaceTag::tagged(expected, 0));
1869 assert!(!allocation.rollover);
1870 }
1871
1872 let allocation = allocator.allocate().unwrap();
1873 assert_eq!(allocation.tag, AddressSpaceTag::tagged(1, 1));
1874 assert!(allocation.rollover);
1875 }
1876
1877 #[axtest::axtest]
1878 fn tag_allocator_never_wraps_an_exhausted_generation() {
1879 let mut allocator = AddressSpaceTagAllocator::new(4);
1880 allocator.next = allocator.capacity;
1881 allocator.generation = u64::MAX;
1882
1883 assert_eq!(
1884 allocator.allocate(),
1885 Err(TagAllocationError::GenerationExhausted)
1886 );
1887 assert_eq!(allocator.generation(), u64::MAX);
1888 assert_eq!(allocator.next, allocator.capacity);
1889 }
1890
1891 #[axtest::axtest]
1892 fn activation_and_mutation_share_active_cpu_mask() {
1893 let aspace = Arc::new(Mutex::new(
1894 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
1895 ));
1896 let mutation_targets = aspace.lock().tlb_targets();
1897 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
1898
1899 let epoch_before = handle.installed().epoch;
1900 {
1901 let mut guard = aspace.lock();
1902 let start = guard.base();
1903 guard
1904 .map(
1905 start,
1906 ax_memory_addr::PAGE_SIZE_4K,
1907 ax_runtime::hal::paging::MappingFlags::READ
1908 | ax_runtime::hal::paging::MappingFlags::USER,
1909 false,
1910 MappingOperation::new_alloc(
1911 start,
1912 ax_memory_addr::PAGE_SIZE_4K,
1913 "[epoch-source-test]",
1914 ),
1915 )
1916 .unwrap();
1917 }
1918 assert_eq!(handle.installed().epoch, epoch_before.next());
1919
1920 assert!(Arc::ptr_eq(&mutation_targets, &handle.inner.active_mask));
1921 drop(aspace);
1922
1923 let activation = handle.activation(2).unwrap();
1924 assert_eq!(mutation_targets.load(Ordering::Acquire), 1usize << 2);
1925
1926 activation.release_after_kernel_switch();
1927 assert_eq!(mutation_targets.load(Ordering::Acquire), 0);
1928
1929 let permit = handle
1930 .release_user_ref()
1931 .expect("an inactive ownerless address space must become reclaimable");
1932 permit.reclaim().unwrap();
1933 }
1934
1935 #[axtest::axtest]
1936 fn mm_pin_fault_prepares_outside_and_publishes_through_the_live_mm() {
1937 let start = ax_memory_addr::VirtAddr::from_usize(0x4000);
1938 let aspace = Arc::new(Mutex::new(AddrSpace::new_empty(start, 0x1000).unwrap()));
1939 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
1940 {
1941 let mut guard = aspace.lock();
1942 guard
1943 .map(
1944 start,
1945 ax_memory_addr::PAGE_SIZE_4K,
1946 ax_runtime::hal::paging::MappingFlags::READ
1947 | ax_runtime::hal::paging::MappingFlags::WRITE
1948 | ax_runtime::hal::paging::MappingFlags::USER,
1949 false,
1950 MappingOperation::new_alloc(
1951 start,
1952 ax_memory_addr::PAGE_SIZE_4K,
1953 "[mm-pin-fault-test]",
1954 ),
1955 )
1956 .unwrap();
1957 }
1958
1959 let pin = handle.pin().unwrap();
1960 let activation = handle.activation(2).unwrap();
1961 assert_eq!(
1962 pin.handle_page_fault_result(start, PageFaultFlags::READ | PageFaultFlags::USER,),
1963 FaultResult::Handled
1964 );
1965 {
1966 let guard = aspace.lock();
1967 assert_eq!(guard.resident_page_counts().anon, 1);
1968 assert!(guard.pending_tlb_requests().unwrap().is_empty());
1969 assert_eq!(
1970 guard
1971 .mutation_gate
1972 .last_retired_receipt()
1973 .unwrap()
1974 .tlb_obligation
1975 .targets(),
1976 0,
1977 "a previously-none PTE must not shoot down another CPU"
1978 );
1979 }
1980
1981 activation.release_after_kernel_switch();
1982 drop(pin);
1983 drop(aspace);
1984 let permit = handle
1985 .release_user_ref()
1986 .expect("the quiescent MM must become reclaimable after the fault");
1987 permit.reclaim().unwrap();
1988 }
1989
1990 #[axtest::axtest]
1991 fn completed_root_switch_is_not_frozen_into_a_new_tlb_obligation() {
1992 let aspace = Arc::new(Mutex::new(
1993 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
1994 ));
1995 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
1996 let activation = handle.activation(2).unwrap();
1997
1998 let mutation = aspace.lock().prepare_mutation();
1999 activation.release_after_kernel_switch();
2000
2001 let receipt = aspace
2002 .lock()
2003 .mutation_gate
2004 .commit(mutation)
2005 .expect("a CPU that completed its root switch must not remain a TLB target");
2006 assert_eq!(receipt.tlb_obligation.targets(), 0);
2007 assert_eq!(handle.active_cpu_mask(), 0);
2008
2009 let permit = handle
2010 .release_user_ref()
2011 .expect("an inactive ownerless address space must become reclaimable");
2012 permit.reclaim().unwrap();
2013 }
2014
2015 #[axtest::axtest]
2016 fn last_user_owner_cannot_reclaim_an_active_address_space() {
2017 let aspace = Arc::new(Mutex::new(
2018 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
2019 ));
2020 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
2021 let activation = handle.activation(2).unwrap();
2022 let root = handle.installed().root;
2023
2024 assert!(is_address_space_live(handle.id()));
2025 assert!(handle.release_user_ref().is_none());
2026 assert_eq!(handle.state(), MmState::Retiring);
2027 assert!(!is_address_space_live(handle.id()));
2028 assert_eq!(handle.installed().root, root);
2029 assert_eq!(handle.cpu_state().active_cpus, 1usize << 2);
2030 assert_ne!(aspace.lock().materialized_root().as_usize(), 0);
2031
2032 activation.release_after_kernel_switch();
2033 assert_eq!(handle.state(), MmState::Retired);
2034
2035 let (reclaimed, failed) = reap_retired(usize::MAX);
2036 assert!(reclaimed >= 1);
2037 assert_eq!(failed, 0);
2038 assert_eq!(handle.state(), MmState::Freed);
2039 }
2040
2041 #[axtest::axtest]
2042 fn pinned_exit_continuation_can_run_while_the_mm_is_retiring() {
2043 let aspace = Arc::new(Mutex::new(
2044 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
2045 ));
2046 let handle = MmHandle::from_arc(aspace).unwrap();
2047 let pin = handle.pin().unwrap();
2048
2049 assert!(handle.release_user_ref().is_none());
2050 assert_eq!(handle.state(), MmState::Retiring);
2051
2052 let activation = pin
2053 .activation_for_switch(2)
2054 .expect("a pinned exit continuation must remain schedulable while retiring");
2055 assert_eq!(handle.active_cpu_mask(), 1usize << 2);
2056 activation.release_after_kernel_switch();
2057
2058 drop(pin);
2059 assert_eq!(handle.state(), MmState::Retired);
2060 let (reclaimed, failed) = reap_retired(usize::MAX);
2061 assert!(reclaimed >= 1);
2062 assert_eq!(failed, 0);
2063 assert_eq!(handle.state(), MmState::Freed);
2064 }
2065
2066 #[axtest::axtest]
2067 fn retire_permit_revalidates_quiescence_under_the_lifecycle_gate() {
2068 let aspace = Arc::new(Mutex::new(
2069 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
2070 ));
2071 let handle = MmHandle::from_arc(aspace).unwrap();
2072 let pin = handle.pin().unwrap();
2073
2074 assert!(handle.release_user_ref().is_none());
2075 assert_eq!(handle.state(), MmState::Retiring);
2076
2077 {
2081 let _gate = handle.inner.lifecycle_gate.lock();
2082 handle
2083 .inner
2084 .state
2085 .store(MmState::Retired as u8, Ordering::Release);
2086 }
2087 assert!(MmInner::take_retire_permit(&handle.inner).is_none());
2088 assert!(!handle.inner.retire_queued.load(Ordering::Acquire));
2089
2090 {
2091 let _gate = handle.inner.lifecycle_gate.lock();
2092 handle
2093 .inner
2094 .state
2095 .store(MmState::Retiring as u8, Ordering::Release);
2096 }
2097 drop(pin);
2098 assert_eq!(handle.state(), MmState::Retired);
2099 let (reclaimed, failed) = reap_retired(usize::MAX);
2100 assert!(reclaimed >= 1);
2101 assert_eq!(failed, 0);
2102 assert_eq!(handle.state(), MmState::Freed);
2103 }
2104}