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> {
839 let mut last_user = false;
840 {
841 let _gate = self.inner.lifecycle_gate.lock();
842 if self.owner.swap(false, Ordering::Relaxed) {
843 let previous = self.inner.user_refs.load(Ordering::Relaxed);
844 debug_assert!(previous > 0, "MmHandle user reference underflow");
845 self.inner.user_refs.store(previous - 1, Ordering::Release);
846 if previous == 1 {
847 last_user = true;
848 let _ = self.inner.state.compare_exchange(
849 MmState::Live as u8,
850 MmState::Retiring as u8,
851 Ordering::AcqRel,
852 Ordering::Acquire,
853 );
854 }
855 self.inner.maybe_retire_locked();
856 }
857 }
858 if last_user {
859 let file_accesses = self.inner.aspace.lock().take_file_accesses();
865 drop(file_accesses);
866 }
867 self.retire_if_quiescent()
868 }
869}
870
871impl Drop for MmHandle {
872 fn drop(&mut self) {
873 {
874 let _gate = self.inner.lifecycle_gate.lock();
875 if !self.owner.swap(false, Ordering::Relaxed) {
876 return;
877 }
878 let previous = self.inner.user_refs.load(Ordering::Relaxed);
879 debug_assert!(previous > 0, "MmHandle user reference underflow");
880 self.inner.user_refs.store(previous - 1, Ordering::Release);
881 if previous == 1 {
882 let _ = self.inner.state.compare_exchange(
883 MmState::Live as u8,
884 MmState::Retiring as u8,
885 Ordering::AcqRel,
886 Ordering::Acquire,
887 );
888 }
889 self.inner.maybe_retire_locked();
890 }
891 queue_if_retired(&self.inner);
892 }
893}
894
895pub struct MmPin(Arc<MmInner>);
899
900impl MmPin {
901 pub fn id(&self) -> AddressSpaceId {
902 self.0.id
903 }
904
905 pub fn transparent_huge_page_mode(&self) -> TransparentHugePageMode {
907 self.0.transparent_huge_page_mode()
908 }
909
910 pub fn set_transparent_huge_page_mode(&self, mode: TransparentHugePageMode) {
912 let _aspace = self.0.aspace.lock();
913 self.0
914 .transparent_huge_page_mode
915 .store(mode as u8, Ordering::Release);
916 }
917
918 pub fn handle_page_fault_result(
920 &self,
921 vaddr: VirtAddr,
922 access_flags: PageFaultFlags,
923 ) -> FaultResult {
924 let plan = {
925 let aspace = self.0.aspace.lock();
926 let mode = self.0.transparent_huge_page_mode();
927 match aspace.plan_page_fault(vaddr, access_flags, mode) {
928 Ok(plan) => plan,
929 Err(result) => return result,
930 }
931 };
932 let prepared = match AddrSpace::prepare_page_fault(plan) {
936 Ok(prepared) => prepared,
937 Err(result) => return result,
938 };
939 let mut attempt = prepared.into_apply_attempt();
940 let outcome = {
941 let mut aspace = self.0.aspace.lock();
942 aspace.apply_prepared_page_fault(&mut attempt)
943 };
944 let result = match outcome {
945 PageFaultApplyOutcome::Complete(result) => result,
946 PageFaultApplyOutcome::Cancel(result) => {
947 if attempt.cancel().is_ok() {
948 result
949 } else {
950 FaultResult::Retry
951 }
952 }
953 PageFaultApplyOutcome::NeedsRepair(result) => {
954 attempt.release_to_repair_state();
955 result
956 }
957 PageFaultApplyOutcome::CancelPendingTlb { request, targets } => {
958 if attempt.cancel().is_ok()
963 && AddrSpace::flush_tlb_requests(core::slice::from_ref(&request), &targets)
964 .is_ok()
965 {
966 let aspace = self.0.aspace.lock();
967 let _ = aspace.acknowledge_tlb_requests(core::slice::from_ref(&request));
968 }
969 FaultResult::Retry
970 }
971 PageFaultApplyOutcome::PendingTlb { request, targets } => {
972 if AddrSpace::flush_tlb_requests(core::slice::from_ref(&request), &targets).is_err()
973 {
974 FaultResult::Retry
975 } else {
976 let aspace = self.0.aspace.lock();
977 if aspace
978 .acknowledge_tlb_requests(core::slice::from_ref(&request))
979 .is_ok()
980 {
981 FaultResult::Handled
982 } else {
983 FaultResult::Retry
984 }
985 }
986 }
987 };
988 if matches!(result, FaultResult::Handled) {
989 ax_cpu::mmu::update_mmu_cache(vaddr);
990 }
991 result
992 }
993
994 pub fn handle_page_fault(&self, vaddr: VirtAddr, access_flags: PageFaultFlags) -> bool {
996 loop {
997 match self.handle_page_fault_result(vaddr, access_flags) {
998 FaultResult::Handled => return true,
999 FaultResult::Retry => crate::task::yield_now(),
1004 _ => return false,
1005 }
1006 }
1007 }
1008
1009 pub(crate) fn activation_for_switch(
1013 &self,
1014 cpu: usize,
1015 ) -> Result<ActivationLease, ActivationError> {
1016 MmInner::acquire_activation(
1017 &self.0,
1018 cpu,
1019 ActivationMode::SchedulerHandoff,
1020 ActivationAuthority::PinnedContinuation,
1021 )
1022 }
1023}
1024
1025impl Deref for MmPin {
1026 type Target = Mutex<AddrSpace>;
1027
1028 fn deref(&self) -> &Self::Target {
1029 &self.0.aspace
1030 }
1031}
1032
1033impl Drop for MmPin {
1034 fn drop(&mut self) {
1035 {
1036 let _gate = self.0.lifecycle_gate.lock();
1037 let previous = self.0.kernel_pins.load(Ordering::Relaxed);
1038 debug_assert!(previous > 0, "MmPin reference underflow");
1039 self.0.kernel_pins.store(previous - 1, Ordering::Release);
1040 self.0.maybe_retire_locked();
1041 }
1042 queue_if_retired(&self.0);
1043 }
1044}
1045
1046pub struct ActivationLease {
1049 inner: Arc<MmInner>,
1050 cpu: usize,
1051 installed: InstalledPageTableRoot,
1052 released: bool,
1053}
1054
1055impl ActivationLease {
1056 pub fn installed(&self) -> InstalledPageTableRoot {
1057 self.installed
1058 }
1059
1060 pub const fn cpu(&self) -> usize {
1061 self.cpu
1062 }
1063
1064 pub(crate) fn into_scheduler_activation(
1066 mut self,
1067 ) -> ax_runtime::thread::SchedulerAddressSpaceActivation {
1068 let activation = ax_runtime::thread::SchedulerAddressSpaceActivation::new(
1069 task_address_space(self.installed),
1070 self.cpu,
1071 self.inner.clone(),
1072 );
1073 self.released = true;
1074 activation
1075 }
1076
1077 pub(crate) fn release_after_root_switch(mut self) {
1084 release_activation_accounting(&self.inner, self.cpu);
1085 self.released = true;
1086 }
1087
1088 pub fn release_after_kernel_switch(self) {
1091 self.release_after_root_switch();
1092 }
1093}
1094
1095impl Drop for ActivationLease {
1096 fn drop(&mut self) {
1097 if !self.released {
1098 abandon_activation(self.inner.clone(), self.cpu);
1099 }
1100 }
1101}
1102
1103fn abandon_activation(inner: Arc<MmInner>, cpu: usize) {
1104 {
1105 let _gate = inner.lifecycle_gate.lock();
1106 inner
1107 .state
1108 .store(MmState::NeedsRepair as u8, Ordering::Release);
1109 }
1110 warn!(
1111 "address-space activation for mm {} cpu {} dropped before root-switch proof",
1112 inner.id.get(),
1113 cpu
1114 );
1115 enqueue_repair_candidate(inner);
1116}
1117
1118fn release_activation_accounting(inner: &Arc<MmInner>, cpu: usize) {
1119 {
1120 let _gate = inner.lifecycle_gate.lock();
1121 let previous = inner.active_count.load(Ordering::Relaxed);
1122 debug_assert!(previous > 0, "ActivationLease reference underflow");
1123 inner.active_count.store(previous - 1, Ordering::Release);
1124 if cpu < usize::BITS as usize {
1125 let cpu_refs = &inner.active_per_cpu[cpu];
1126 let previous_cpu = cpu_refs.load(Ordering::Relaxed);
1127 debug_assert!(previous_cpu > 0, "per-CPU activation reference underflow");
1128 cpu_refs.store(previous_cpu - 1, Ordering::Release);
1129 if previous_cpu == 1 {
1130 inner
1131 .active_mask
1132 .fetch_and(!(1usize << cpu), Ordering::Release);
1133 }
1134 }
1135 inner.maybe_retire_locked();
1136 }
1137 queue_if_retired(inner);
1138}
1139
1140impl ax_runtime::thread::SchedulerAddressSpaceOwner for MmInner {
1141 fn release_after_root_switch(
1142 self: Arc<Self>,
1143 proof: ax_runtime::thread::AddressSpaceSwitchProof,
1144 ) {
1145 release_activation_accounting(&self, proof.cpu());
1146 }
1147
1148 fn cancel_before_install(self: Arc<Self>, cpu: usize) {
1149 release_activation_accounting(&self, cpu);
1150 }
1151
1152 fn abandon(self: Arc<Self>, cpu: usize) {
1153 abandon_activation(self, cpu);
1154 }
1155}
1156
1157fn task_address_space(installed: InstalledAddressSpace) -> ax_hal::context::InstalledAddressSpace {
1158 ax_hal::context::InstalledAddressSpace::user(
1159 installed.space_id().get(),
1160 installed.root(),
1161 installed.tag().hardware_tag,
1162 installed.tag().generation,
1163 installed.epoch().get(),
1164 match installed.tag().mode {
1165 TagMode::Tagged => ax_hal::context::InstalledAddressSpaceMode::Tagged,
1166 TagMode::FullFlush => ax_hal::context::InstalledAddressSpaceMode::FullFlush,
1167 },
1168 )
1169 .expect("typed Starry address-space installation must remain valid")
1170}
1171
1172struct RuntimeMmOwner {
1175 _inner: Arc<MmInner>,
1176 pin: IrqMutex<Option<MmPin>>,
1177}
1178
1179unsafe impl ax_runtime::thread::UserAddressSpaceOwner for RuntimeMmOwner {
1184 fn prepare_activation(
1185 &self,
1186 cpu: usize,
1187 ) -> Result<
1188 ax_runtime::thread::SchedulerAddressSpaceActivation,
1189 ax_runtime::task::thread::TaskError,
1190 > {
1191 self.pin
1192 .lock()
1193 .as_ref()
1194 .ok_or(ax_runtime::task::thread::TaskError::InvalidRuntimeHandle)?
1195 .activation_for_switch(cpu)
1196 .map(ActivationLease::into_scheduler_activation)
1197 .map_err(|_| ax_runtime::task::thread::TaskError::InvalidRuntimeHandle)
1198 }
1199
1200 fn detach_from_task(&self) {
1201 let pin = self.pin.lock().take();
1202 drop(pin);
1203 }
1204}
1205
1206impl MmHandle {
1207 pub(crate) fn scheduler_address_space(
1209 &self,
1210 ) -> Result<ax_runtime::thread::TaskAddressSpace, ax_runtime::task::thread::TaskError> {
1211 let pin = self
1212 .pin()
1213 .map_err(|_| ax_runtime::task::thread::TaskError::InvalidRuntimeHandle)?;
1214 ax_runtime::thread::TaskAddressSpace::new_managed(
1215 self.installed().root(),
1216 self.inner.runtime_cpu_state.clone(),
1217 RuntimeMmOwner {
1218 _inner: self.inner.clone(),
1219 pin: IrqMutex::new(Some(pin)),
1220 },
1221 )
1222 }
1223}
1224
1225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1226pub enum ActivationError {
1227 Retired,
1228 AlreadyActive,
1229 InvalidCpu,
1230 Overflow,
1231}
1232
1233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1234pub enum CloneUserRefError {
1235 Retired,
1236 Overflow,
1237}
1238
1239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1240pub enum PinError {
1241 Retired,
1242 Overflow,
1243}
1244
1245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1246pub enum MmCreateError {
1247 DuplicateIdentity,
1248}
1249
1250pub struct RetirePermit(Option<Arc<MmInner>>);
1252
1253static RETIRE_QUEUE: IrqMutex<MmWorkQueue> = IrqMutex::new(MmWorkQueue::new());
1257static REPAIR_QUEUE: IrqMutex<MmWorkQueue> = IrqMutex::new(MmWorkQueue::new());
1258static RECLAIMER_STARTED: AtomicBool = AtomicBool::new(false);
1259static REPAIR_RETRY_REQUESTED: AtomicBool = AtomicBool::new(false);
1260
1261struct CoalescedReclaimRequest {
1262 pending: AtomicBool,
1263}
1264
1265impl CoalescedReclaimRequest {
1266 const fn new() -> Self {
1267 Self {
1268 pending: AtomicBool::new(false),
1269 }
1270 }
1271
1272 fn request(&self) {
1273 self.pending.store(true, Ordering::Release);
1274 }
1275
1276 fn run_one_batch(&self, limit: usize, reclaim: impl FnOnce(usize) -> usize) -> usize {
1277 if limit == 0 || !self.pending.swap(false, Ordering::AcqRel) {
1278 return 0;
1279 }
1280 let reclaimed = reclaim(limit);
1281 if reclaimed >= limit {
1282 self.request();
1285 }
1286 reclaimed
1287 }
1288}
1289
1290static LAZY_FREE_RECLAIM: CoalescedReclaimRequest = CoalescedReclaimRequest::new();
1291#[cfg(test)]
1292static LAZY_FREE_RECLAIM_REQUESTS: AtomicUsize = AtomicUsize::new(0);
1293
1294pub(super) fn request_lazy_free_reclaim() {
1301 #[cfg(test)]
1302 LAZY_FREE_RECLAIM_REQUESTS.fetch_add(1, Ordering::Relaxed);
1303 LAZY_FREE_RECLAIM.request();
1304}
1305
1306#[cfg(test)]
1307pub(super) fn lazy_free_reclaim_request_count_for_test() -> usize {
1308 LAZY_FREE_RECLAIM_REQUESTS.load(Ordering::Relaxed)
1309}
1310
1311fn queue_if_retired(inner: &Arc<MmInner>) {
1312 if let Some(permit) = MmInner::take_retire_permit(inner) {
1313 enqueue_retire(permit);
1314 }
1315}
1316
1317pub fn enqueue_retire(permit: RetirePermit) {
1319 drop(permit);
1320}
1321
1322fn enqueue_mm_work(queue: &IrqMutex<MmWorkQueue>, inner: Arc<MmInner>) {
1323 let duplicate = queue.lock().push(inner);
1324 drop(duplicate);
1327}
1328
1329fn enqueue_repair_candidate(inner: Arc<MmInner>) {
1330 enqueue_mm_work(&REPAIR_QUEUE, inner);
1331}
1332
1333pub fn reap_retired(limit: usize) -> (usize, usize) {
1337 if limit == 0 {
1338 return (0, 0);
1339 }
1340 let count = {
1341 let queue = RETIRE_QUEUE.lock();
1342 limit.min(queue.len())
1343 };
1344 let mut reclaimed = 0;
1345 let mut failed = 0;
1346 for _ in 0..count {
1347 let Some(inner) = RETIRE_QUEUE.lock().pop() else {
1348 break;
1349 };
1350 if inner.state() == MmState::Freed {
1351 release_mm_shell(inner);
1355 continue;
1356 }
1357 match RetirePermit(Some(inner)).reclaim() {
1358 Ok(()) => reclaimed += 1,
1359 Err(_) => failed += 1,
1360 }
1361 }
1362 (reclaimed, failed)
1363}
1364
1365pub fn reclaim_live_lazy_free_pages(limit: usize) -> usize {
1368 if limit == 0 {
1369 return 0;
1370 }
1371 let visit_limit = {
1372 let registry = MM_REGISTRY.lock();
1373 registry.len()
1374 };
1375 reclaim_registered_items(
1376 limit,
1377 visit_limit,
1378 &LAZY_FREE_SCAN_CURSOR,
1379 next_registered_mm_after,
1380 |inner, remaining| {
1381 let Some(inner) = inner else {
1382 return 0;
1383 };
1384 let Ok(pin) = MmInner::try_pin(&inner) else {
1385 return 0;
1386 };
1387 match pin.lock().reclaim_lazy_free_pages(remaining) {
1388 Ok(pages) => pages,
1389 Err(error) => {
1390 warn!(
1391 "lazy-free reclaim for address space {} entered repair: {error}",
1392 pin.id().get()
1393 );
1394 0
1395 }
1396 }
1397 },
1398 )
1399}
1400
1401pub struct RepairPermit(Arc<MmInner>);
1405
1406pub fn take_repair_candidates(limit: usize) -> Vec<RepairPermit> {
1407 let count = {
1408 let queue = REPAIR_QUEUE.lock();
1409 limit.min(queue.len())
1410 };
1411 let mut candidates: Vec<RepairPermit> = Vec::new();
1412 if candidates.try_reserve_exact(count).is_err() {
1413 return candidates;
1414 }
1415 for _ in 0..count {
1416 let Some(inner) = REPAIR_QUEUE.lock().pop() else {
1417 break;
1418 };
1419 candidates.push(RepairPermit(inner));
1420 }
1421 candidates
1422}
1423
1424pub fn request_repair_retry() {
1431 REPAIR_RETRY_REQUESTED.store(true, Ordering::Release);
1432}
1433
1434impl RepairPermit {
1435 pub fn retry(self) -> Result<(), ReclaimError> {
1436 {
1437 let _gate = self.0.lifecycle_gate.lock();
1438 if !self.0.is_quiescent_locked()
1439 || self
1440 .0
1441 .state
1442 .compare_exchange(
1443 MmState::NeedsRepair as u8,
1444 MmState::Retired as u8,
1445 Ordering::AcqRel,
1446 Ordering::Acquire,
1447 )
1448 .is_err()
1449 {
1450 return Err(ReclaimError::NotRetired);
1451 }
1452 self.0.retire_queued.store(false, Ordering::Release);
1453 }
1454 let permit = MmInner::take_retire_permit(&self.0).ok_or(ReclaimError::NotRetired)?;
1455 enqueue_retire(permit);
1456 Ok(())
1457 }
1458}
1459
1460pub fn spawn_reclaimer_task() {
1464 if RECLAIMER_STARTED
1465 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
1466 .is_err()
1467 {
1468 return;
1469 }
1470 ax_std::thread::Builder::new()
1471 .name("starry-mm-reclaimer".to_owned())
1472 .spawn(|| {
1473 loop {
1474 let _ = ax_mm::retry_kernel_virtual_quarantines(16);
1475 let _ = LAZY_FREE_RECLAIM.run_one_batch(16, reclaim_live_lazy_free_pages);
1476 let _ = reap_retired(16);
1477 if REPAIR_RETRY_REQUESTED.swap(false, Ordering::AcqRel) {
1478 for permit in take_repair_candidates(16) {
1483 let _ = permit.retry();
1484 }
1485 }
1486 ax_std::thread::sleep(core::time::Duration::from_millis(10));
1487 }
1488 })
1489 .expect("MM reclaimer thread must start before userspace");
1490}
1491
1492impl RetirePermit {
1493 fn into_inner(mut self) -> Arc<MmInner> {
1494 self.0.take().expect("retire permit is consumed once")
1495 }
1496
1497 pub fn reclaim(self) -> Result<(), ReclaimError> {
1498 let inner = self.into_inner();
1499 let result = Self::reclaim_inner(&inner);
1500 if result.is_ok() {
1501 release_mm_shell(inner);
1502 } else {
1503 enqueue_repair_candidate(inner);
1504 }
1505 result
1506 }
1507
1508 fn reclaim_inner(inner: &Arc<MmInner>) -> Result<(), ReclaimError> {
1509 {
1510 let _gate = inner.lifecycle_gate.lock();
1511 if !inner.is_quiescent_locked()
1512 || inner
1513 .state
1514 .compare_exchange(
1515 MmState::Retired as u8,
1516 MmState::Reclaiming as u8,
1517 Ordering::AcqRel,
1518 Ordering::Acquire,
1519 )
1520 .is_err()
1521 {
1522 return Err(ReclaimError::NotRetired);
1523 }
1524 }
1525 let result = inner.aspace.lock().try_reclaim_contents();
1526 match result {
1527 Ok(()) => {
1528 {
1529 let _gate = inner.lifecycle_gate.lock();
1530 inner.state.store(MmState::Freed as u8, Ordering::Release);
1531 }
1532 unregister_mm(inner);
1533 Ok(())
1534 }
1535 Err(_) => {
1536 {
1537 let _gate = inner.lifecycle_gate.lock();
1538 inner
1539 .state
1540 .store(MmState::NeedsRepair as u8, Ordering::Release);
1541 inner.retire_queued.store(false, Ordering::Release);
1542 }
1543 Err(ReclaimError::Backend)
1544 }
1545 }
1546 }
1547
1548 pub fn retry(self) -> Result<(), ReclaimError> {
1550 RepairPermit(self.into_inner()).retry()
1551 }
1552}
1553
1554fn release_mm_shell(inner: Arc<MmInner>) {
1558 match Arc::try_unwrap(inner) {
1559 Ok(inner) => drop(inner),
1560 Err(inner) => enqueue_mm_work(&RETIRE_QUEUE, inner),
1561 }
1562}
1563
1564impl Drop for RetirePermit {
1565 fn drop(&mut self) {
1566 if let Some(inner) = self.0.take() {
1567 enqueue_mm_work(&RETIRE_QUEUE, inner);
1568 }
1569 }
1570}
1571
1572impl Drop for RepairPermit {
1573 fn drop(&mut self) {
1574 if self.0.state() == MmState::NeedsRepair {
1575 enqueue_repair_candidate(self.0.clone());
1576 } else {
1577 enqueue_mm_work(&RETIRE_QUEUE, self.0.clone());
1578 }
1579 }
1580}
1581
1582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1583pub enum ReclaimError {
1584 NotRetired,
1585 Backend,
1586}
1587
1588#[cfg(all(test, axtest))]
1589mod tests {
1590 use super::*;
1591 use crate::mm::MappingOperation;
1592
1593 #[axtest::axtest]
1594 fn failed_direct_reclaim_preserves_repair_ownership() {
1595 let aspace = Arc::new(Mutex::new(
1596 AddrSpace::new_empty(VirtAddr::from(0x7400_0000), 0x1000).unwrap(),
1597 ));
1598 let handle = MmHandle::from_arc(aspace).unwrap();
1599 let weak = Arc::downgrade(&handle.inner);
1600 handle
1601 .inner
1602 .aspace
1603 .lock()
1604 .mutation_gate
1605 .fail_next_commit_before_publish();
1606 let permit = handle.release_user_ref().unwrap();
1607 drop(handle);
1608 assert_eq!(permit.reclaim(), Err(ReclaimError::Backend));
1609 let inner = weak
1610 .upgrade()
1611 .expect("failed reclaim must retain its MM for repair");
1612 assert_eq!(inner.state(), MmState::NeedsRepair);
1613 drop(take_repair_candidates(usize::MAX));
1615 drop(inner);
1616 assert!(weak.upgrade().is_some());
1617 for candidate in take_repair_candidates(usize::MAX) {
1618 if candidate.0.id == weak.upgrade().unwrap().id {
1619 candidate.retry().unwrap();
1620 }
1621 }
1622 let _ = reap_retired(usize::MAX);
1623 let _ = reap_retired(usize::MAX);
1624 assert!(weak.upgrade().is_none());
1625 }
1626
1627 #[axtest::axtest]
1628 fn mm_pin_services_blocking_discard_before_refault_retry() {
1629 use ax_runtime::hal::paging::MappingFlags;
1630
1631 use super::super::{MutationError, TlbRange};
1632
1633 let start = VirtAddr::from(0x7500_0000);
1634 let aspace = Arc::new(Mutex::new(AddrSpace::new_empty(start, 0x1000).unwrap()));
1635 let handle = MmHandle::from_arc(aspace).unwrap();
1636 let pin = handle.pin().unwrap();
1637 {
1638 let mut aspace = pin.lock();
1639 aspace
1640 .map(
1641 start,
1642 0x1000,
1643 MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER,
1644 true,
1645 MappingOperation::new_alloc(start, 0x1000, "[pin-refault]"),
1646 )
1647 .unwrap();
1648 aspace.discard_range(start, 0x1000).unwrap();
1649 let mut discard = aspace.mutation_gate.begin(aspace.id, 1);
1650 discard.add_tlb_range(TlbRange::new(start, 0x1000).unwrap());
1651 assert_eq!(
1652 aspace.mutation_gate.commit(discard).unwrap_err(),
1653 MutationError::TlbPending
1654 );
1655 }
1656 let access = PageFaultFlags::READ | PageFaultFlags::USER;
1657 assert!(matches!(
1658 pin.handle_page_fault_result(start, access),
1659 FaultResult::Retry
1660 ));
1661 assert_eq!(pin.lock().pending_tlb_obligations(), 0);
1662 assert!(matches!(
1663 pin.handle_page_fault_result(start, access),
1664 FaultResult::Handled
1665 ));
1666 drop(pin);
1667 let permit = handle.release_user_ref().unwrap();
1668 drop(handle);
1669 permit.reclaim().unwrap();
1670 }
1671
1672 #[axtest::axtest]
1673 fn faultable_user_copy_retries_pending_tlb_before_reporting_success() {
1674 use ax_runtime::hal::paging::MappingFlags;
1675
1676 use super::super::{MutationError, TlbRange};
1677
1678 let start = VirtAddr::from(0x7600_0000);
1679 let aspace = Arc::new(Mutex::new(AddrSpace::new_empty(start, 0x1000).unwrap()));
1680 let handle = MmHandle::from_arc(aspace).unwrap();
1681 let pin = handle.pin().unwrap();
1682 {
1683 let mut aspace = pin.lock();
1684 aspace
1685 .map(
1686 start,
1687 0x1000,
1688 MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER,
1689 true,
1690 MappingOperation::new_alloc(start, 0x1000, "[copy-refault]"),
1691 )
1692 .unwrap();
1693 aspace.discard_range(start, 0x1000).unwrap();
1694 let mut discard = aspace.mutation_gate.begin(aspace.id, 1);
1695 discard.add_tlb_range(TlbRange::new(start, 0x1000).unwrap());
1696 assert_eq!(
1697 aspace.mutation_gate.commit(discard).unwrap_err(),
1698 MutationError::TlbPending
1699 );
1700 }
1701 assert!(pin.handle_page_fault(start, PageFaultFlags::WRITE));
1704 assert_eq!(pin.lock().pending_tlb_obligations(), 0);
1705 assert!(
1706 pin.lock()
1707 .pt
1708 .query(start)
1709 .is_ok_and(|(_, flags, _)| flags.contains(MappingFlags::WRITE))
1710 );
1711 assert!(!pin.handle_page_fault(start, PageFaultFlags::EXECUTE));
1712 assert!(!pin.handle_page_fault(start + 0x1000, PageFaultFlags::WRITE));
1713 let exhausted = AddrSpace::classify_fault_error(false, crate::StarryError::NoMemory);
1714 pin.lock().mutation_gate.mark_needs_repair();
1715 let quarantined = pin.handle_page_fault_result(start, PageFaultFlags::WRITE);
1716 pin.lock().mutation_gate.clear_repair();
1718 drop(pin);
1719 let permit = handle.release_user_ref().unwrap();
1720 drop(handle);
1721 permit.reclaim().unwrap();
1722 assert!(
1723 exhausted != FaultResult::Retry && matches!(quarantined, FaultResult::Sigbus(_)),
1724 "allocation failure and repair quarantine are terminal: {exhausted:?}, {quarantined:?}"
1725 );
1726 assert_eq!(exhausted, FaultResult::NoMemory);
1727 assert_eq!(
1728 quarantined,
1729 FaultResult::Sigbus(super::super::BusCode::ObjErr)
1730 );
1731 }
1732
1733 #[axtest::axtest]
1734 fn abandoned_activation_retains_the_unproved_hardware_root() {
1735 let aspace = Arc::new(Mutex::new(
1736 AddrSpace::new_empty(VirtAddr::from(0x7300_0000), 0x1000).unwrap(),
1737 ));
1738 let handle = MmHandle::from_arc(aspace).unwrap();
1739 let weak = Arc::downgrade(&handle.inner);
1740 let activation = handle.activation(0).unwrap();
1743 drop(handle);
1744 drop(activation);
1745 let retained = weak.upgrade();
1746 assert!(
1747 retained.is_some(),
1748 "an unproved active root must stay owned by repair quarantine"
1749 );
1750 let inner = retained.unwrap();
1751 assert_eq!(inner.state(), MmState::NeedsRepair);
1752 assert_eq!(inner.active_count.load(Ordering::Acquire), 1);
1753 {
1755 let _gate = inner.lifecycle_gate.lock();
1756 inner.active_count.store(0, Ordering::Release);
1757 inner.active_mask.store(0, Ordering::Release);
1758 inner.active_per_cpu[0].store(0, Ordering::Release);
1759 }
1760 for candidate in take_repair_candidates(usize::MAX) {
1761 if Arc::ptr_eq(&candidate.0, &inner) {
1762 candidate.retry().unwrap();
1763 } else {
1764 drop(candidate);
1765 }
1766 }
1767 drop(inner);
1768 let _ = reap_retired(usize::MAX);
1769 let _ = reap_retired(usize::MAX);
1770 assert!(weak.upgrade().is_none());
1771 }
1772
1773 #[axtest::axtest]
1774 fn lazy_free_reclaim_only_scans_after_a_publication_edge() {
1775 let request = CoalescedReclaimRequest::new();
1776 let mut scans = 0usize;
1777
1778 assert_eq!(
1779 request.run_one_batch(16, |_| {
1780 scans += 1;
1781 0
1782 }),
1783 0
1784 );
1785 assert_eq!(scans, 0, "an idle worker must not scan live MMs");
1786
1787 request.request();
1788 request.request();
1789 assert_eq!(
1790 request.run_one_batch(16, |limit| {
1791 scans += 1;
1792 limit
1793 }),
1794 16
1795 );
1796 assert_eq!(scans, 1, "coalesced publications need one scan");
1797
1798 assert_eq!(
1799 request.run_one_batch(16, |_| {
1800 scans += 1;
1801 3
1802 }),
1803 3,
1804 "a full batch must schedule one bounded continuation"
1805 );
1806 assert_eq!(scans, 2);
1807 assert_eq!(request.run_one_batch(16, |_| unreachable!()), 0);
1808 }
1809
1810 #[axtest::axtest]
1811 fn lazy_free_batches_advance_the_production_registry_cursor() {
1812 let mut registry = BTreeMap::new();
1813 registry.insert(AddressSpaceId(2), ());
1814 registry.insert(AddressSpaceId(5), ());
1815 registry.insert(AddressSpaceId(9), ());
1816 let cursor = AtomicU64::new(0);
1817 let mut visits = Vec::new();
1818
1819 for _ in 0..4 {
1820 assert_eq!(
1821 reclaim_registered_items(
1822 1,
1823 registry.len(),
1824 &cursor,
1825 |after| {
1826 let id = next_registry_id_after(®istry, after)?;
1827 Some((id, id))
1828 },
1829 |id, remaining| {
1830 assert_eq!(remaining, 1);
1831 visits.push(id);
1832 1
1833 },
1834 ),
1835 1
1836 );
1837 }
1838
1839 assert_eq!(
1840 visits,
1841 [
1842 AddressSpaceId(2),
1843 AddressSpaceId(5),
1844 AddressSpaceId(9),
1845 AddressSpaceId(2),
1846 ]
1847 );
1848 }
1849
1850 #[axtest::axtest]
1851 fn tag_allocator_uses_explicit_full_flush_fallback() {
1852 for capacity in [0, 1] {
1853 let mut allocator = AddressSpaceTagAllocator::new(capacity);
1854 let allocation = allocator.allocate().unwrap();
1855 assert_eq!(allocator.mode(), TagMode::FullFlush);
1856 assert_eq!(allocation.tag, AddressSpaceTag::full_flush(0));
1857 assert!(!allocation.rollover);
1858 }
1859 }
1860
1861 #[axtest::axtest]
1862 fn tag_allocator_represents_the_complete_sixteen_bit_space() {
1863 let mut allocator = AddressSpaceTagAllocator::new(1 << 16);
1864 allocator.next = u32::from(u16::MAX);
1865
1866 let last = allocator.allocate().unwrap();
1867 assert_eq!(last.tag, AddressSpaceTag::tagged(u16::MAX, 0));
1868 assert!(!last.rollover);
1869
1870 let reused = allocator.allocate().unwrap();
1871 assert_eq!(reused.tag, AddressSpaceTag::tagged(1, 1));
1872 assert!(reused.rollover);
1873 }
1874
1875 #[axtest::axtest]
1876 fn tag_allocator_rollover_restarts_at_one_in_a_new_generation() {
1877 let mut allocator = AddressSpaceTagAllocator::new(4);
1878 for expected in 1..4 {
1879 let allocation = allocator.allocate().unwrap();
1880 assert_eq!(allocation.tag, AddressSpaceTag::tagged(expected, 0));
1881 assert!(!allocation.rollover);
1882 }
1883
1884 let allocation = allocator.allocate().unwrap();
1885 assert_eq!(allocation.tag, AddressSpaceTag::tagged(1, 1));
1886 assert!(allocation.rollover);
1887 }
1888
1889 #[axtest::axtest]
1890 fn tag_allocator_never_wraps_an_exhausted_generation() {
1891 let mut allocator = AddressSpaceTagAllocator::new(4);
1892 allocator.next = allocator.capacity;
1893 allocator.generation = u64::MAX;
1894
1895 assert_eq!(
1896 allocator.allocate(),
1897 Err(TagAllocationError::GenerationExhausted)
1898 );
1899 assert_eq!(allocator.generation(), u64::MAX);
1900 assert_eq!(allocator.next, allocator.capacity);
1901 }
1902
1903 #[axtest::axtest]
1904 fn activation_and_mutation_share_active_cpu_mask() {
1905 let aspace = Arc::new(Mutex::new(
1906 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
1907 ));
1908 let mutation_targets = aspace.lock().tlb_targets();
1909 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
1910
1911 let epoch_before = handle.installed().epoch;
1912 {
1913 let mut guard = aspace.lock();
1914 let start = guard.base();
1915 guard
1916 .map(
1917 start,
1918 ax_memory_addr::PAGE_SIZE_4K,
1919 ax_runtime::hal::paging::MappingFlags::READ
1920 | ax_runtime::hal::paging::MappingFlags::USER,
1921 false,
1922 MappingOperation::new_alloc(
1923 start,
1924 ax_memory_addr::PAGE_SIZE_4K,
1925 "[epoch-source-test]",
1926 ),
1927 )
1928 .unwrap();
1929 }
1930 assert_eq!(handle.installed().epoch, epoch_before.next());
1931
1932 assert!(Arc::ptr_eq(&mutation_targets, &handle.inner.active_mask));
1933 drop(aspace);
1934
1935 let activation = handle.activation(2).unwrap();
1936 assert_eq!(mutation_targets.load(Ordering::Acquire), 1usize << 2);
1937
1938 activation.release_after_kernel_switch();
1939 assert_eq!(mutation_targets.load(Ordering::Acquire), 0);
1940
1941 let permit = handle
1942 .release_user_ref()
1943 .expect("an inactive ownerless address space must become reclaimable");
1944 permit.reclaim().unwrap();
1945 }
1946
1947 #[axtest::axtest]
1948 fn mm_pin_fault_prepares_outside_and_publishes_through_the_live_mm() {
1949 let start = ax_memory_addr::VirtAddr::from_usize(0x4000);
1950 let aspace = Arc::new(Mutex::new(AddrSpace::new_empty(start, 0x1000).unwrap()));
1951 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
1952 {
1953 let mut guard = aspace.lock();
1954 guard
1955 .map(
1956 start,
1957 ax_memory_addr::PAGE_SIZE_4K,
1958 ax_runtime::hal::paging::MappingFlags::READ
1959 | ax_runtime::hal::paging::MappingFlags::WRITE
1960 | ax_runtime::hal::paging::MappingFlags::USER,
1961 false,
1962 MappingOperation::new_alloc(
1963 start,
1964 ax_memory_addr::PAGE_SIZE_4K,
1965 "[mm-pin-fault-test]",
1966 ),
1967 )
1968 .unwrap();
1969 }
1970
1971 let pin = handle.pin().unwrap();
1972 let activation = handle.activation(2).unwrap();
1973 assert_eq!(
1974 pin.handle_page_fault_result(start, PageFaultFlags::READ | PageFaultFlags::USER,),
1975 FaultResult::Handled
1976 );
1977 {
1978 let guard = aspace.lock();
1979 assert_eq!(guard.resident_page_counts().anon, 1);
1980 assert!(guard.pending_tlb_requests().unwrap().is_empty());
1981 assert_eq!(
1982 guard
1983 .mutation_gate
1984 .last_retired_receipt()
1985 .unwrap()
1986 .tlb_obligation
1987 .targets(),
1988 0,
1989 "a previously-none PTE must not shoot down another CPU"
1990 );
1991 }
1992
1993 activation.release_after_kernel_switch();
1994 drop(pin);
1995 drop(aspace);
1996 let permit = handle
1997 .release_user_ref()
1998 .expect("the quiescent MM must become reclaimable after the fault");
1999 permit.reclaim().unwrap();
2000 }
2001
2002 #[axtest::axtest]
2003 fn completed_root_switch_is_not_frozen_into_a_new_tlb_obligation() {
2004 let aspace = Arc::new(Mutex::new(
2005 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
2006 ));
2007 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
2008 let activation = handle.activation(2).unwrap();
2009
2010 let mutation = aspace.lock().prepare_mutation();
2011 activation.release_after_kernel_switch();
2012
2013 let receipt = aspace
2014 .lock()
2015 .mutation_gate
2016 .commit(mutation)
2017 .expect("a CPU that completed its root switch must not remain a TLB target");
2018 assert_eq!(receipt.tlb_obligation.targets(), 0);
2019 assert_eq!(handle.active_cpu_mask(), 0);
2020
2021 let permit = handle
2022 .release_user_ref()
2023 .expect("an inactive ownerless address space must become reclaimable");
2024 permit.reclaim().unwrap();
2025 }
2026
2027 #[axtest::axtest]
2028 fn last_user_owner_cannot_reclaim_an_active_address_space() {
2029 let aspace = Arc::new(Mutex::new(
2030 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
2031 ));
2032 let handle = MmHandle::from_arc(aspace.clone()).unwrap();
2033 let activation = handle.activation(2).unwrap();
2034 let root = handle.installed().root;
2035
2036 assert!(is_address_space_live(handle.id()));
2037 assert!(handle.release_user_ref().is_none());
2038 assert_eq!(handle.state(), MmState::Retiring);
2039 assert!(!is_address_space_live(handle.id()));
2040 assert_eq!(handle.installed().root, root);
2041 assert_eq!(handle.cpu_state().active_cpus, 1usize << 2);
2042 assert_ne!(aspace.lock().materialized_root().as_usize(), 0);
2043
2044 activation.release_after_kernel_switch();
2045 assert_eq!(handle.state(), MmState::Retired);
2046
2047 let (reclaimed, failed) = reap_retired(usize::MAX);
2048 assert!(reclaimed >= 1);
2049 assert_eq!(failed, 0);
2050 assert_eq!(handle.state(), MmState::Freed);
2051 }
2052
2053 #[axtest::axtest]
2054 fn pinned_exit_continuation_can_run_while_the_mm_is_retiring() {
2055 let aspace = Arc::new(Mutex::new(
2056 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
2057 ));
2058 let handle = MmHandle::from_arc(aspace).unwrap();
2059 let pin = handle.pin().unwrap();
2060
2061 assert!(handle.release_user_ref().is_none());
2062 assert_eq!(handle.state(), MmState::Retiring);
2063
2064 let activation = pin
2065 .activation_for_switch(2)
2066 .expect("a pinned exit continuation must remain schedulable while retiring");
2067 assert_eq!(handle.active_cpu_mask(), 1usize << 2);
2068 activation.release_after_kernel_switch();
2069
2070 drop(pin);
2071 assert_eq!(handle.state(), MmState::Retired);
2072 let (reclaimed, failed) = reap_retired(usize::MAX);
2073 assert!(reclaimed >= 1);
2074 assert_eq!(failed, 0);
2075 assert_eq!(handle.state(), MmState::Freed);
2076 }
2077
2078 #[axtest::axtest]
2079 fn retire_permit_revalidates_quiescence_under_the_lifecycle_gate() {
2080 let aspace = Arc::new(Mutex::new(
2081 AddrSpace::new_empty(ax_memory_addr::VirtAddr::from_usize(0x1000), 0x1000).unwrap(),
2082 ));
2083 let handle = MmHandle::from_arc(aspace).unwrap();
2084 let pin = handle.pin().unwrap();
2085
2086 assert!(handle.release_user_ref().is_none());
2087 assert_eq!(handle.state(), MmState::Retiring);
2088
2089 {
2093 let _gate = handle.inner.lifecycle_gate.lock();
2094 handle
2095 .inner
2096 .state
2097 .store(MmState::Retired as u8, Ordering::Release);
2098 }
2099 assert!(MmInner::take_retire_permit(&handle.inner).is_none());
2100 assert!(!handle.inner.retire_queued.load(Ordering::Acquire));
2101
2102 {
2103 let _gate = handle.inner.lifecycle_gate.lock();
2104 handle
2105 .inner
2106 .state
2107 .store(MmState::Retiring as u8, Ordering::Release);
2108 }
2109 drop(pin);
2110 assert_eq!(handle.state(), MmState::Retired);
2111 let (reclaimed, failed) = reap_retired(usize::MAX);
2112 assert!(reclaimed >= 1);
2113 assert_eq!(failed, 0);
2114 assert_eq!(handle.state(), MmState::Freed);
2115 }
2116}