1use alloc::{
2 collections::{BTreeMap, BTreeSet},
3 sync::Arc,
4 vec::Vec,
5};
6use core::{fmt, sync::atomic::AtomicUsize};
7
8use ax_fs_ng::file::CachedPagePin;
9use ax_memory_addr::{
10 MemoryAddr, PAGE_SIZE_4K, PageIter4K, PhysAddr, VirtAddr, VirtAddrRange, is_aligned_4k,
11};
12#[cfg(not(any(target_arch = "aarch64", target_arch = "loongarch64")))]
13use ax_mm::RootEntryShare;
14use ax_runtime::hal::{
15 mem::phys_to_virt,
16 paging::{
17 InstalledHugeSplit, MappingFlags, PageTable, PageTableEntry, PageTableMapDeposit,
18 PageTableMapPlan, PageTableMovePlan, PagingAllocator, PagingError,
19 },
20 trap::PageFaultFlags,
21};
22
23use crate::{
24 StarryError, StarryResult,
25 config::USER_HEAP_BASE,
26 mm::{ProcessVmStat, ProcessVmStatSnapshot, UserVirtualAddressLayout},
27 sync::{IrqMutex, LockdepMutexExt, Mutex, try_reserve_irq_vec},
28};
29
30#[cfg(test)]
31fn complete_page_fault_with(
32 handled: bool,
33 vaddr: VirtAddr,
34 update_mmu_cache: impl FnOnce(VirtAddr),
35) -> bool {
36 if handled {
37 update_mmu_cache(vaddr);
38 }
39 handled
40}
41
42mod accounting;
43mod backend;
44pub(crate) mod domain;
45pub mod lifecycle;
46pub(crate) mod mutation;
47pub(crate) mod objects;
48pub(crate) mod reclaim;
49pub(crate) mod vma;
50
51pub(crate) use self::domain::PageTableDomain;
52pub use self::mutation::{
56 AppliedMutation, EvictionResult, MappingDelta, MutationError, MutationGate, MutationReceipt,
57 MutationState, PreparedMutation, PteDelta, PublishEvent, PublishedMutation,
58 PublishedPendingTlb, QuarantineError, QuarantineFailure, ResidentDelta, TlbQuarantine,
59 TlbRange, TlbRequest, VmaDelta,
60};
61use self::{
62 accounting::ResidentWatermark,
63 backend::{
64 FaultFallback, FaultMaterialization, FaultPteSnapshot, PopulateRequest, PreparedPteOwner,
65 ProviderPublication, PteMaterialization, PteOwnerTransition,
66 },
67};
68pub use self::{
69 backend::*,
70 lifecycle::*,
71 objects::{
72 EvictionError, EvictionLease, FrameLease, MappingGraphError, MappingSlot, MappingSlotKey,
73 PageId, PageObject, PageState, RmapSet, SlotState, WritebackError, WritebackLease,
74 },
75 reclaim::*,
76 vma::*,
77};
78
79#[cfg(all(test, axtest))]
80static MAPPING_GRAPH_SNAPSHOT_CALLS: AtomicUsize = AtomicUsize::new(0);
81
82#[derive(Clone, Copy, PartialEq, Eq)]
83enum MovedPageDestination {
84 SourceOwner,
86 TargetOwner { slot_va: VirtAddr },
88}
89
90#[derive(Clone, Copy)]
91struct MovedPage {
92 src_va: VirtAddr,
93 dst_va: VirtAddr,
94 paddr: PhysAddr,
95 page_size: usize,
96 destination: MovedPageDestination,
97}
98
99enum PreparedMovedSlot {
100 Relocate {
101 source_key: MappingSlotKey,
102 target_key: MappingSlotKey,
103 source: Arc<MappingSlot>,
104 replacement: Arc<MappingSlot>,
105 },
106 DetachSource {
107 source_key: MappingSlotKey,
108 target_key: MappingSlotKey,
109 source: Arc<MappingSlot>,
110 },
111}
112const CLONED_ADDR_SPACE_LOCK_SUBCLASS: u32 = 1;
113
114#[derive(Clone, Copy)]
115struct ForkParentPteProtection {
116 va: VirtAddr,
117 paddr: PhysAddr,
118 page_size: usize,
119 original_flags: MappingFlags,
120 protected_flags: MappingFlags,
121}
122
123struct PreparedForkParentMutation {
124 mutation: PreparedMutation,
125 ptes: Vec<ForkParentPteProtection>,
126 ranges: Vec<VirtAddrRange>,
127}
128
129#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
130pub(crate) struct ResidentPageCounts {
131 pub anon: u64,
132 pub file: u64,
133 pub shmem: u64,
134}
135
136impl ResidentPageCounts {
137 pub const fn total(self) -> u64 {
138 self.anon
139 .saturating_add(self.file)
140 .saturating_add(self.shmem)
141 }
142
143 fn checked_delta_to(self, after: Self) -> StarryResult<ResidentDelta> {
144 fn delta(before: u64, after: u64) -> StarryResult<i64> {
145 let value = i128::from(after) - i128::from(before);
146 i64::try_from(value).map_err(|_| StarryError::BadState)
147 }
148
149 Ok(ResidentDelta {
150 anon: delta(self.anon, after.anon)?,
151 file: delta(self.file, after.file)?,
152 shmem: delta(self.shmem, after.shmem)?,
153 })
154 }
155
156 fn checked_apply(&mut self, delta: ResidentDelta) -> StarryResult {
157 fn apply(current: u64, delta: i64) -> StarryResult<u64> {
158 if delta >= 0 {
159 current
160 .checked_add(u64::try_from(delta).map_err(|_| StarryError::BadState)?)
161 .ok_or(StarryError::BadState)
162 } else {
163 current
164 .checked_sub(delta.unsigned_abs())
165 .ok_or(StarryError::BadState)
166 }
167 }
168
169 self.anon = apply(self.anon, delta.anon)?;
170 self.file = apply(self.file, delta.file)?;
171 self.shmem = apply(self.shmem, delta.shmem)?;
172 Ok(())
173 }
174
175 fn checked_add_pages(&mut self, kind: Option<RssKind>, pages: u64) -> StarryResult {
176 let bucket = match kind {
177 Some(RssKind::Anon) => &mut self.anon,
178 Some(RssKind::File) => &mut self.file,
179 Some(RssKind::Shmem) => &mut self.shmem,
180 None => return Ok(()),
181 };
182 *bucket = bucket.checked_add(pages).ok_or(StarryError::BadState)?;
183 Ok(())
184 }
185
186 fn checked_negated_delta(self) -> StarryResult<ResidentDelta> {
187 Ok(ResidentDelta {
188 anon: -i64::try_from(self.anon).map_err(|_| StarryError::BadState)?,
189 file: -i64::try_from(self.file).map_err(|_| StarryError::BadState)?,
190 shmem: -i64::try_from(self.shmem).map_err(|_| StarryError::BadState)?,
191 })
192 }
193
194 fn checked_positive_delta(self) -> StarryResult<ResidentDelta> {
195 Ok(ResidentDelta {
196 anon: i64::try_from(self.anon).map_err(|_| StarryError::BadState)?,
197 file: i64::try_from(self.file).map_err(|_| StarryError::BadState)?,
198 shmem: i64::try_from(self.shmem).map_err(|_| StarryError::BadState)?,
199 })
200 }
201}
202
203impl ResidentDelta {
204 fn for_pages(kind: Option<RssKind>, pages: i64) -> Self {
205 match kind {
206 Some(RssKind::Anon) => Self {
207 anon: pages,
208 ..Self::default()
209 },
210 Some(RssKind::File) => Self {
211 file: pages,
212 ..Self::default()
213 },
214 Some(RssKind::Shmem) => Self {
215 shmem: pages,
216 ..Self::default()
217 },
218 None => Self::default(),
219 }
220 }
221
222 fn checked_add_assign(&mut self, other: Self) -> StarryResult {
223 self.anon = self
224 .anon
225 .checked_add(other.anon)
226 .ok_or(StarryError::BadState)?;
227 self.file = self
228 .file
229 .checked_add(other.file)
230 .ok_or(StarryError::BadState)?;
231 self.shmem = self
232 .shmem
233 .checked_add(other.shmem)
234 .ok_or(StarryError::BadState)?;
235 Ok(())
236 }
237}
238
239#[derive(Debug, Clone, Copy, Default)]
240struct PteOwnerPublication {
241 satisfied_pages: usize,
242 mapping_delta: MappingDelta,
243 resident_delta: ResidentDelta,
244}
245
246struct PreparedSlotPublication {
247 key: MappingSlotKey,
248 previous: Option<Arc<MappingSlot>>,
249 replacement: Option<Arc<MappingSlot>>,
250 resident_kind: Option<RssKind>,
251 provider_publication: ProviderPublication,
252 mapping_delta: MappingDelta,
253 resident_delta: ResidentDelta,
254}
255
256#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
257struct MappingSlotFingerprint {
258 key: MappingSlotKey,
259 mapping: MappingId,
260 page: PageId,
261 page_order: PageOrder,
262}
263
264struct MappingGraphSnapshot {
268 slots: Vec<MappingSlotFingerprint>,
269 resident: ResidentPageCounts,
270}
271
272impl MappingGraphSnapshot {
273 fn delta_to(&self, after: &Self) -> StarryResult<(MappingDelta, ResidentDelta)> {
274 let mut before_index = 0usize;
275 let mut after_index = 0usize;
276 let mut attached = 0usize;
277 let mut detached = 0usize;
278 while before_index < self.slots.len() && after_index < after.slots.len() {
279 match self.slots[before_index].cmp(&after.slots[after_index]) {
280 core::cmp::Ordering::Less => {
281 detached = detached.checked_add(1).ok_or(StarryError::BadState)?;
282 before_index += 1;
283 }
284 core::cmp::Ordering::Equal => {
285 before_index += 1;
286 after_index += 1;
287 }
288 core::cmp::Ordering::Greater => {
289 attached = attached.checked_add(1).ok_or(StarryError::BadState)?;
290 after_index += 1;
291 }
292 }
293 }
294 detached = detached
295 .checked_add(self.slots.len() - before_index)
296 .ok_or(StarryError::BadState)?;
297 attached = attached
298 .checked_add(after.slots.len() - after_index)
299 .ok_or(StarryError::BadState)?;
300 Ok((
301 MappingDelta {
302 attached: u32::try_from(attached).map_err(|_| StarryError::BadState)?,
303 detached: u32::try_from(detached).map_err(|_| StarryError::BadState)?,
304 },
305 self.resident.checked_delta_to(after.resident)?,
306 ))
307 }
308}
309
310#[derive(Debug, Clone, Copy)]
314pub struct MappingPermissions {
315 pub current: MappingFlags,
316 pub reported: MappingFlags,
317 pub maximum: MappingFlags,
318}
319
320#[derive(Debug, Clone, Copy)]
321pub(crate) struct MappingPublication {
322 replace: bool,
323 huge_page_advice: HugePageAdvice,
324 lock_mode: VmaLockMode,
325 advice_policy: VmaAdvicePolicy,
326 memlock_limit: Option<MemlockLimit>,
327}
328
329#[derive(Debug, Clone, Copy)]
335pub(crate) struct MemlockLimit {
336 page_limit: u64,
337 bypass_limit: bool,
338 may_lock: bool,
339 exceeded_error: MemlockLimitError,
340}
341
342#[derive(Debug, Clone, Copy)]
343enum MemlockLimitError {
344 NoMemory,
345 WouldBlock,
346}
347
348impl MemlockLimit {
349 pub(crate) const fn for_mlock(byte_limit: u64, bypass_limit: bool) -> Self {
350 Self {
351 page_limit: byte_limit / PAGE_SIZE_4K as u64,
352 bypass_limit,
353 may_lock: byte_limit != 0 || bypass_limit,
354 exceeded_error: MemlockLimitError::NoMemory,
355 }
356 }
357
358 pub(crate) const fn for_mapping(byte_limit: u64, bypass_limit: bool) -> Self {
359 Self {
360 page_limit: byte_limit / PAGE_SIZE_4K as u64,
361 bypass_limit,
362 may_lock: byte_limit != 0 || bypass_limit,
363 exceeded_error: MemlockLimitError::WouldBlock,
364 }
365 }
366
367 pub(crate) const fn can_lock(self) -> bool {
368 self.may_lock
369 }
370
371 fn validate(self, locked_pages: u64) -> StarryResult {
372 if self.bypass_limit || locked_pages <= self.page_limit {
373 return Ok(());
374 }
375 Err(match self.exceeded_error {
376 MemlockLimitError::NoMemory => StarryError::NoMemory,
377 MemlockLimitError::WouldBlock => StarryError::WouldBlock,
378 })
379 }
380}
381
382impl MappingPublication {
383 const fn new(replace: bool) -> Self {
384 Self {
385 replace,
386 huge_page_advice: HugePageAdvice::Default,
387 lock_mode: VmaLockMode::Unlocked,
388 advice_policy: VmaAdvicePolicy::DEFAULT,
389 memlock_limit: None,
390 }
391 }
392
393 pub(crate) const fn mmap(
394 replace: bool,
395 lock_mode: VmaLockMode,
396 memlock_limit: Option<MemlockLimit>,
397 ) -> Self {
398 Self {
399 replace,
400 huge_page_advice: HugePageAdvice::Default,
401 lock_mode,
402 advice_policy: VmaAdvicePolicy::DEFAULT,
403 memlock_limit,
404 }
405 }
406
407 const fn mremap(
408 replace: bool,
409 huge_page_advice: HugePageAdvice,
410 lock_mode: VmaLockMode,
411 advice_policy: VmaAdvicePolicy,
412 memlock_limit: Option<MemlockLimit>,
413 ) -> Self {
414 Self {
415 replace,
416 huge_page_advice,
417 lock_mode,
418 advice_policy,
419 memlock_limit,
420 }
421 }
422}
423
424pub(crate) enum AddressSpaceMutationOutcome {
431 Complete,
432 PublishedPendingTlb(StarryError),
433}
434
435impl AddressSpaceMutationOutcome {
436 fn into_result(self) -> StarryResult {
437 match self {
438 Self::Complete => Ok(()),
439 Self::PublishedPendingTlb(error) => Err(error),
440 }
441 }
442}
443
444#[derive(Debug, Clone, Copy)]
448struct HeapState {
449 start: usize,
450 current: usize,
451}
452
453impl HeapState {
454 const fn new(start: usize) -> Self {
455 Self {
456 start,
457 current: start,
458 }
459 }
460}
461
462#[derive(Debug, Clone, Copy, Default)]
468struct ExecutableDataLayout {
469 start: usize,
470 end: usize,
471}
472
473impl ExecutableDataLayout {
474 fn try_new(start: usize, end: usize) -> StarryResult<Self> {
475 if start == 0 || end < start {
476 return Err(StarryError::MalformedExecutable);
477 }
478 Ok(Self { start, end })
479 }
480
481 fn size(self) -> Option<usize> {
482 self.end.checked_sub(self.start)
483 }
484}
485
486fn checked_page_align_up(value: usize) -> StarryResult<usize> {
487 value
488 .checked_add(PAGE_SIZE_4K - 1)
489 .map(|rounded| rounded & !(PAGE_SIZE_4K - 1))
490 .ok_or(StarryError::InvalidInput)
491}
492
493struct ResidentLeafPreimage {
494 va: VirtAddr,
495 paddr: PhysAddr,
496 page_size: usize,
497 flags: MappingFlags,
498 backend: MappingOperation,
499 page: Arc<PageObject>,
500 slot: Arc<MappingSlot>,
501}
502
503#[derive(Clone, Copy)]
504struct OccupiedPteLeaf {
505 range: VirtAddrRange,
506 paddr: PhysAddr,
507 flags: MappingFlags,
508}
509
510struct MappingPreimage {
511 vma_root: Arc<VmaMap>,
512 vm_stat: ProcessVmStatSnapshot,
513 leaves: Vec<ResidentLeafPreimage>,
514}
515
516struct AppliedHugeSplit {
517 installed: InstalledHugeSplit,
518 old_slot: Arc<MappingSlot>,
519 child_keys: Vec<MappingSlotKey>,
520 child_slots: Vec<Arc<MappingSlot>>,
521 previous_mapping_slots: BTreeMap<MappingSlotKey, Arc<MappingSlot>>,
522}
523
524#[derive(Clone, Copy)]
525struct ProtectionLeafPreimage {
526 va: VirtAddr,
527 paddr: PhysAddr,
528 page_size: usize,
529 flags: MappingFlags,
530}
531
532#[derive(Default)]
538struct RetiredMappingOwners {
539 backends: Vec<MappingOperation>,
540 pages: Vec<Arc<PageObject>>,
541 cache_pins: Vec<CachedPagePin>,
542 deferred_evictions: Vec<Arc<PageObject>>,
547}
548
549impl RetiredMappingOwners {
550 fn is_empty(&self) -> bool {
551 self.backends.is_empty()
552 && self.pages.is_empty()
553 && self.cache_pins.is_empty()
554 && self.deferred_evictions.is_empty()
555 }
556}
557
558struct RetiredMappingBatch {
559 epoch: VmEpoch,
560 owners: RetiredMappingOwners,
561}
562
563#[derive(Debug)]
564enum CommitMutationError {
565 Unpublished(StarryError),
568 PublishedPendingTlb(StarryError),
572}
573
574#[derive(Debug, Clone, Copy, PartialEq, Eq)]
575enum MutationPublication {
576 Complete,
577 PendingTlb,
578}
579
580struct PageFaultPlan {
581 base_epoch: VmEpoch,
582 space_id: AddressSpaceId,
583 vaddr: VirtAddr,
584 range: VirtAddrRange,
585 vma_flags: MappingFlags,
586 access_flags: MappingFlags,
587 operation: MappingOperation,
588 request: PopulateRequest,
589 preimage: FaultPteSnapshot,
590 map_plans: Option<PageFaultMapPlans>,
591}
592
593struct PageFaultMapPlans {
594 preferred: PageTableMapPlan,
595 fallback: Option<PageTableMapPlan>,
596}
597
598fn prepare_mapping_publication_mutation(
599 gate: &MutationGate,
600 space_id: AddressSpaceId,
601 active_targets: &Arc<AtomicUsize>,
602 start: VirtAddr,
603 size: usize,
604 replaces_existing: bool,
605) -> PreparedMutation {
606 let mut mutation = if replaces_existing {
612 gate.begin_with_active_targets(space_id, active_targets.clone())
613 } else {
614 gate.begin_fresh_mapping(space_id)
615 };
616 if let Some(range) = TlbRange::new(start, size) {
617 mutation.add_tlb_range(range);
618 }
619 mutation
620}
621
622struct PreparedPageFault {
623 plan: PageFaultPlan,
624 materialization: FaultMaterialization,
625 map_deposit: Option<PageTableMapDeposit>,
626}
627
628impl PreparedPageFault {
629 fn into_apply_attempt(self) -> PageFaultApplyAttempt {
630 PageFaultApplyAttempt {
631 prepared: Some(self),
632 orphaned_map_deposit: None,
633 }
634 }
635
636 fn cancel(self) -> StarryResult {
637 self.plan
638 .operation
639 .cancel_prepared_fault_publication(self.materialization)
640 }
641}
642
643struct PageFaultApplyAttempt {
650 prepared: Option<PreparedPageFault>,
651 orphaned_map_deposit: Option<PageTableMapDeposit>,
654}
655
656impl PageFaultApplyAttempt {
657 fn prepared(&self) -> &PreparedPageFault {
658 self.prepared
659 .as_ref()
660 .expect("page-fault apply attempt must retain its prepared token")
661 }
662
663 fn take_prepared(&mut self) -> PreparedPageFault {
664 self.prepared
665 .take()
666 .expect("page-fault apply attempt must consume its prepared token once")
667 }
668
669 fn take_map_deposit(&mut self) -> Option<PageTableMapDeposit> {
670 self.prepared.as_mut()?.map_deposit.take()
671 }
672
673 fn restore_map_deposit(&mut self, deposit: PageTableMapDeposit) {
674 if let Some(prepared) = self.prepared.as_mut()
675 && prepared.map_deposit.is_none()
676 {
677 prepared.map_deposit = Some(deposit);
678 return;
679 }
680 debug_assert!(self.orphaned_map_deposit.is_none());
684 self.orphaned_map_deposit = Some(deposit);
685 }
686
687 fn cancel(self) -> StarryResult {
688 let Self {
689 prepared,
690 orphaned_map_deposit,
691 } = self;
692 drop(orphaned_map_deposit);
693 prepared
694 .expect("cancelled page-fault apply attempt must retain its prepared token")
695 .cancel()
696 }
697
698 fn release_to_repair_state(self) {
706 debug_assert!(self.prepared.is_some());
707 drop(self);
708 }
709}
710
711enum PageFaultApplyOutcome {
712 Complete(FaultResult),
713 Cancel(FaultResult),
714 NeedsRepair(FaultResult),
715 CancelPendingTlb {
718 request: TlbRequest,
719 targets: Arc<AtomicUsize>,
720 },
721 PendingTlb {
722 request: TlbRequest,
723 targets: Arc<AtomicUsize>,
724 },
725}
726
727#[derive(Debug, Clone, Copy, PartialEq, Eq)]
731pub(crate) enum EvictMappingOutcome {
732 Complete,
733 PublishedPendingTlb,
734 NeedsRepair,
735}
736
737pub struct AddrSpace {
739 id: AddressSpaceId,
740 layout: UserVirtualAddressLayout,
742 vma_root: Arc<VmaMap>,
746 heap: HeapState,
750 executable_data: ExecutableDataLayout,
751 pt: PageTable,
752 pte_domain: PageTableDomain,
755 mutation_gate: MutationGate,
757 published_epoch: Arc<core::sync::atomic::AtomicU64>,
761 pub vm_stat: ProcessVmStat,
764 resident_pages: ResidentPageCounts,
768 resident_watermark: ResidentWatermark,
769 tlb_quarantine: TlbQuarantine,
772 tlb_targets: Arc<AtomicUsize>,
776 mapping_slots: BTreeMap<MappingSlotKey, Arc<MappingSlot>>,
780 retired_mapping_batches: IrqMutex<Vec<RetiredMappingBatch>>,
785}
786
787impl AddrSpace {
788 pub const fn base(&self) -> VirtAddr {
790 self.layout.range().start
791 }
792
793 pub const fn end(&self) -> VirtAddr {
795 self.layout.task_size()
796 }
797
798 pub fn size(&self) -> usize {
800 self.layout.range().size()
801 }
802
803 pub const fn stack_top(&self) -> VirtAddr {
805 self.layout.stack_top()
806 }
807
808 pub(crate) const fn heap_start(&self) -> usize {
811 self.heap.start
812 }
813
814 pub(crate) const fn heap_break(&self) -> usize {
816 self.heap.current
817 }
818
819 pub(crate) fn set_executable_data_layout(&mut self, start: usize, end: usize) -> StarryResult {
822 let layout = ExecutableDataLayout::try_new(start, end)?;
823 if start < self.base().as_usize() || end > self.end().as_usize() {
824 return Err(StarryError::MalformedExecutable);
825 }
826 self.executable_data = layout;
827 Ok(())
828 }
829
830 pub(crate) const fn executable_data_bounds(&self) -> (usize, usize) {
831 (self.executable_data.start, self.executable_data.end)
832 }
833
834 pub(crate) fn executable_data_size(&self) -> Option<usize> {
835 self.executable_data.size()
836 }
837
838 pub(crate) fn resize_heap_break(
846 &mut self,
847 requested: usize,
848 initial_mapping_end: usize,
849 ) -> StarryResult<AddressSpaceMutationOutcome> {
850 let old_break = self.heap.current;
851 let old_aligned = checked_page_align_up(old_break)?;
852 let new_aligned = checked_page_align_up(requested)?;
853
854 let outcome = if new_aligned > old_aligned {
855 let map_start = initial_mapping_end.max(old_aligned);
856 let map_size = new_aligned.saturating_sub(map_start);
857 if map_size == 0 {
858 AddressSpaceMutationOutcome::Complete
859 } else {
860 let start = VirtAddr::from(map_start);
861 let flags = MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER;
862 self.map_with_permissions_mode_classified(
863 start,
864 map_size,
865 MappingPermissions {
866 current: flags,
867 reported: flags,
868 maximum: flags,
869 },
870 false,
871 MappingOperation::new_alloc(start, PAGE_SIZE_4K, "[heap]"),
872 MappingPublication::new(false),
873 )?
874 }
875 } else if new_aligned < old_aligned {
876 let unmap_start = initial_mapping_end.max(new_aligned);
877 let unmap_size = old_aligned.saturating_sub(unmap_start);
878 if unmap_size == 0 {
879 AddressSpaceMutationOutcome::Complete
880 } else {
881 self.unmap_classified(VirtAddr::from(unmap_start), unmap_size)?
882 }
883 } else {
884 AddressSpaceMutationOutcome::Complete
885 };
886
887 self.heap.current = requested;
890 Ok(outcome)
891 }
892
893 pub(crate) fn translate(&self, vaddr: VirtAddr) -> StarryResult<PhysAddr> {
896 self.pt
897 .query(vaddr)
898 .map(|(paddr, ..)| paddr)
899 .map_err(Into::into)
900 }
901
902 pub(crate) fn resident_span(&self, vaddr: VirtAddr) -> Option<usize> {
907 self.pt.query(vaddr).ok().map(|(_, _, size)| size)
908 }
909
910 pub(crate) fn resident_bytes_from(&self, vaddr: VirtAddr) -> Option<usize> {
914 let key = MappingSlotKey {
915 space_id: self.id,
916 va: vaddr,
917 };
918 let (_, slot) = self.mapping_slots.range(..=key).next_back()?;
919 if slot.state() != SlotState::Present {
920 return None;
921 }
922 let bytes = PAGE_SIZE_4K.checked_shl(slot.page_order.get().into())?;
923 let end = slot.va.checked_add(bytes)?;
924 (vaddr >= slot.va && vaddr < end).then(|| end.as_usize() - vaddr.as_usize())
925 }
926
927 fn mapping_slots_overlapping(
934 &self,
935 range: VirtAddrRange,
936 ) -> impl Iterator<Item = (&MappingSlotKey, &Arc<MappingSlot>)> {
937 let start = MappingSlotKey {
938 space_id: self.id,
939 va: range.start,
940 };
941 let end = MappingSlotKey {
942 space_id: self.id,
943 va: range.end,
944 };
945 let predecessor = self
946 .mapping_slots
947 .range(..start)
948 .next_back()
949 .filter(move |(key, slot)| key.space_id == self.id && slot.overlaps(range));
950 let inside = self
951 .mapping_slots
952 .range(start..end)
953 .filter(move |(key, slot)| key.space_id == self.id && slot.overlaps(range));
954 predecessor.into_iter().chain(inside)
955 }
956
957 fn mapping_slot_summary(
958 &self,
959 range: VirtAddrRange,
960 ) -> StarryResult<(usize, usize, ResidentPageCounts)> {
961 let mut slots = 0usize;
962 let mut materialized_pages = 0usize;
963 let mut resident = ResidentPageCounts::default();
964 for (_, slot) in self.mapping_slots_overlapping(range) {
965 if slot.state() != SlotState::Present {
966 return Err(StarryError::BadState);
967 }
968 let pages = 1usize
969 .checked_shl(slot.page_order.get().into())
970 .ok_or(StarryError::BadState)?;
971 slots = slots.checked_add(1).ok_or(StarryError::BadState)?;
972 materialized_pages = materialized_pages
973 .checked_add(pages)
974 .ok_or(StarryError::BadState)?;
975 resident.checked_add_pages(
976 slot.resident_kind(),
977 u64::try_from(pages).map_err(|_| StarryError::BadState)?,
978 )?;
979 }
980 Ok((slots, materialized_pages, resident))
981 }
982
983 fn resident_counts_from_all_slots(&self) -> StarryResult<ResidentPageCounts> {
984 let mut resident = ResidentPageCounts::default();
985 for slot in self.mapping_slots.values() {
986 if slot.state() != SlotState::Present {
987 return Err(StarryError::BadState);
988 }
989 let pages = 1u64
990 .checked_shl(slot.page_order.get().into())
991 .ok_or(StarryError::BadState)?;
992 resident.checked_add_pages(slot.resident_kind(), pages)?;
993 }
994 Ok(resident)
995 }
996
997 fn occupied_pte_leaves_overlapping(
1004 &self,
1005 ranges: &[VirtAddrRange],
1006 ) -> StarryResult<Vec<OccupiedPteLeaf>> {
1007 let mut leaves = Vec::new();
1008 for range in ranges {
1009 for entry in self.pt.walk_occupied_range(range.start, range.end) {
1010 let leaf_start = entry.vaddr;
1011 let page_size = self
1012 .pt
1013 .mapping_size_for_level(entry.level)
1014 .ok_or(StarryError::BadState)?;
1015 let leaf_end = leaf_start
1016 .checked_add(page_size)
1017 .ok_or(StarryError::BadState)?;
1018 if leaf_start >= range.end || leaf_end <= range.start {
1019 continue;
1020 }
1021 if leaf_start < range.start || leaf_end > range.end {
1022 return Err(StarryError::OperationNotSupported);
1023 }
1024 let is_directory_level = entry.level > 1;
1025 leaves.try_reserve(1).map_err(|_| StarryError::NoMemory)?;
1026 leaves.push(OccupiedPteLeaf {
1027 range: VirtAddrRange::new(leaf_start, leaf_end),
1028 paddr: entry.pte.paddr(is_directory_level),
1029 flags: entry.pte.config(is_directory_level),
1030 });
1031 }
1032 }
1033 Ok(leaves)
1034 }
1035
1036 fn materialized_slots_overlapping(
1041 &self,
1042 ranges: &[VirtAddrRange],
1043 ) -> StarryResult<Vec<(MappingSlotKey, Arc<MappingSlot>, OccupiedPteLeaf)>> {
1044 let leaves = self.occupied_pte_leaves_overlapping(ranges)?;
1045 let mut slots = Vec::new();
1046 slots
1047 .try_reserve(leaves.len())
1048 .map_err(|_| StarryError::NoMemory)?;
1049 for leaf in leaves {
1050 let key = MappingSlotKey {
1051 space_id: self.id,
1052 va: leaf.range.start,
1053 };
1054 let slot = self
1055 .mapping_slots
1056 .get(&key)
1057 .cloned()
1058 .ok_or(StarryError::BadState)?;
1059 let expected_size = PAGE_SIZE_4K
1060 .checked_shl(slot.page_order.get().into())
1061 .ok_or(StarryError::BadState)?;
1062 if slot.state() != SlotState::Present
1063 || slot.mm_id != self.id
1064 || slot.va != key.va
1065 || expected_size != leaf.range.size()
1066 || slot.mapped_paddr() != Some(leaf.paddr)
1067 {
1068 return Err(StarryError::BadState);
1069 }
1070 slots.push((key, slot, leaf));
1071 }
1072 let overlapping_slots = ranges
1073 .iter()
1074 .map(|range| self.mapping_slots_overlapping(*range).count())
1075 .sum::<usize>();
1076 if overlapping_slots != slots.len() {
1077 return Err(StarryError::BadState);
1078 }
1079 Ok(slots)
1080 }
1081
1082 pub(crate) fn validate_materialized_leaf_boundaries(
1087 &self,
1088 start: VirtAddr,
1089 size: usize,
1090 ) -> StarryResult {
1091 self.validate_region(start, size)?;
1092 let range =
1093 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
1094 for (key, slot, leaf) in self.materialized_slots_overlapping(&[range])? {
1095 let page_size = leaf.range.size();
1096 let leaf_end = slot
1097 .va
1098 .checked_add(page_size)
1099 .ok_or(StarryError::BadState)?;
1100 if slot.va < range.start || leaf_end > range.end {
1101 return Err(StarryError::OperationNotSupported);
1102 }
1103 if key.va != slot.va
1104 || slot.mm_id != self.id
1105 || slot.state() != SlotState::Present
1106 || slot.mapped_paddr() != Some(leaf.paddr)
1107 {
1108 return Err(StarryError::BadState);
1109 }
1110 }
1111 Ok(())
1112 }
1113
1114 fn apply_partial_huge_splits(
1119 &mut self,
1120 range: VirtAddrRange,
1121 ) -> StarryResult<Vec<AppliedHugeSplit>> {
1122 let mut candidates = Vec::new();
1123 candidates
1124 .try_reserve(2)
1125 .map_err(|_| StarryError::NoMemory)?;
1126 for (key, slot) in self.mapping_slots_overlapping(range) {
1127 if slot.page_order == PageOrder::BASE || !slot.overlaps(range) {
1128 continue;
1129 }
1130 let slot_size = PAGE_SIZE_4K
1131 .checked_shl(slot.page_order.get().into())
1132 .ok_or(StarryError::BadState)?;
1133 let slot_end = slot
1134 .va
1135 .checked_add(slot_size)
1136 .ok_or(StarryError::BadState)?;
1137 if range.start <= slot.va && slot_end <= range.end {
1138 continue;
1139 }
1140 candidates
1141 .try_reserve(1)
1142 .map_err(|_| StarryError::NoMemory)?;
1143 candidates.push((*key, slot.clone()));
1144 }
1145
1146 let mut applied = Vec::new();
1147 applied
1148 .try_reserve(candidates.len())
1149 .map_err(|_| StarryError::NoMemory)?;
1150 for (key, slot) in candidates {
1151 match self.apply_one_partial_huge_split(key, slot) {
1152 Ok(split) => applied.push(split),
1153 Err(error) => {
1154 if !self.rollback_applied_huge_splits(applied) {
1155 self.mutation_gate.mark_needs_repair();
1156 return Err(StarryError::BadState);
1157 }
1158 return Err(error);
1159 }
1160 }
1161 }
1162 Ok(applied)
1163 }
1164
1165 fn apply_partial_huge_splits_for_ranges(
1170 &mut self,
1171 ranges: &[VirtAddrRange],
1172 ) -> StarryResult<Vec<AppliedHugeSplit>> {
1173 let capacity = ranges.len().checked_mul(2).ok_or(StarryError::NoMemory)?;
1174 let mut applied = Vec::new();
1175 applied
1176 .try_reserve_exact(capacity)
1177 .map_err(|_| StarryError::NoMemory)?;
1178 for range in ranges {
1179 match self.apply_partial_huge_splits(*range) {
1180 Ok(mut splits) => applied.append(&mut splits),
1181 Err(error) => {
1182 if !self.rollback_applied_huge_splits(applied) {
1183 self.mutation_gate.mark_needs_repair();
1184 return Err(StarryError::BadState);
1185 }
1186 return Err(error);
1187 }
1188 }
1189 }
1190 Ok(applied)
1191 }
1192
1193 fn apply_one_partial_huge_split(
1194 &mut self,
1195 old_key: MappingSlotKey,
1196 old_slot: Arc<MappingSlot>,
1197 ) -> StarryResult<AppliedHugeSplit> {
1198 if old_slot.page_order != PageOrder::new(9)
1201 || old_slot.state() != SlotState::Present
1202 || !self
1203 .mapping_slots
1204 .get(&old_key)
1205 .is_some_and(|slot| Arc::ptr_eq(slot, &old_slot))
1206 {
1207 return Err(StarryError::OperationNotSupported);
1208 }
1209 let block_size = PAGE_SIZE_4K
1210 .checked_shl(old_slot.page_order.get().into())
1211 .ok_or(StarryError::BadState)?;
1212 let block_range = VirtAddrRange::try_from_start_size(old_slot.va, block_size)
1213 .ok_or(StarryError::BadState)?;
1214 let (mapped_paddr, _, mapped_size) = self.pt.query(old_slot.va)?;
1215 if old_slot.mapped_paddr() != Some(mapped_paddr)
1216 || mapped_size != block_size
1217 || old_slot.page.frame().size() < block_size
1218 {
1219 return Err(StarryError::BadState);
1220 }
1221
1222 let child_count = block_size / PAGE_SIZE_4K;
1223 let mut child_keys = Vec::new();
1224 let mut child_slots = Vec::new();
1225 child_keys
1226 .try_reserve_exact(child_count)
1227 .map_err(|_| StarryError::NoMemory)?;
1228 child_slots
1229 .try_reserve_exact(child_count)
1230 .map_err(|_| StarryError::NoMemory)?;
1231 for index in 0..child_count {
1232 let offset = index
1233 .checked_mul(PAGE_SIZE_4K)
1234 .ok_or(StarryError::BadState)?;
1235 let va = old_slot
1236 .va
1237 .checked_add(offset)
1238 .ok_or(StarryError::BadState)?;
1239 let key = MappingSlotKey {
1240 space_id: self.id,
1241 va,
1242 };
1243 let child = MappingSlot::new_with_frame_offset(
1244 old_slot.mapping,
1245 self.id,
1246 va,
1247 PageOrder::BASE,
1248 old_slot.page.clone(),
1249 old_slot
1250 .frame_offset()
1251 .checked_add(offset)
1252 .ok_or(StarryError::BadState)?,
1253 old_slot.resident_kind(),
1254 )
1255 .ok_or(StarryError::BadState)?;
1256 child_keys.push(key);
1257 child_slots.push(Arc::new(child));
1258 }
1259
1260 let mut next_mapping_slots = self.mapping_slots.clone();
1263 let removed = next_mapping_slots
1264 .remove(&old_key)
1265 .ok_or(StarryError::BadState)?;
1266 if !Arc::ptr_eq(&removed, &old_slot) {
1267 return Err(StarryError::BadState);
1268 }
1269 for (key, slot) in child_keys.iter().copied().zip(child_slots.iter().cloned()) {
1270 if next_mapping_slots.insert(key, slot).is_some() {
1271 return Err(StarryError::BadState);
1272 }
1273 }
1274
1275 let old_keys = [old_key];
1279 let mut graph_reservation = old_slot
1280 .page
1281 .prepare_mapping_graph_replace(&old_keys, &child_keys)
1282 .map_err(|error| match error {
1283 MappingGraphError::ResourceExhausted | MappingGraphError::RefOverflow => {
1284 StarryError::NoMemory
1285 }
1286 _ => StarryError::BadState,
1287 })?;
1288
1289 let deposit = old_slot
1290 .take_huge_split_deposit()
1291 .ok_or(StarryError::BadState)?;
1292 let mutation_gate = &self.mutation_gate;
1293 let pte_domain = &self.pte_domain;
1294 let pt = &mut self.pt;
1295 let stripe = pte_domain.lock_range(block_range);
1296 let installed = match pt.try_split_huge_page_with(deposit) {
1297 Ok(installed) => installed,
1298 Err(failure) => {
1299 let (error, deposit) = failure.into_parts();
1300 match old_slot.restore_huge_split_deposit(deposit) {
1301 Ok(()) => {
1302 drop(stripe);
1303 drop(graph_reservation);
1304 return Err(error.into());
1305 }
1306 Err(orphaned) => {
1307 drop(stripe);
1311 drop(graph_reservation);
1312 drop(orphaned);
1313 mutation_gate.mark_needs_repair();
1314 return Err(StarryError::BadState);
1315 }
1316 }
1317 }
1318 };
1319
1320 if let Err(graph_error) = old_slot.page.replace_mapping_graph_reserved(
1321 &old_keys,
1322 &child_keys,
1323 &mut graph_reservation,
1324 ) {
1325 let restored = pt.restore_huge_split(installed);
1326 match restored {
1327 Ok(deposit) => match old_slot.restore_huge_split_deposit(deposit) {
1328 Ok(()) => {
1329 drop(stripe);
1330 drop(graph_reservation);
1331 return Err(match graph_error {
1332 MappingGraphError::ResourceExhausted
1333 | MappingGraphError::RefOverflow => StarryError::NoMemory,
1334 _ => StarryError::BadState,
1335 });
1336 }
1337 Err(orphaned) => {
1338 drop(stripe);
1339 drop(graph_reservation);
1340 drop(orphaned);
1341 }
1342 },
1343 Err(_) => {
1344 drop(stripe);
1345 drop(graph_reservation);
1346 }
1347 }
1348 mutation_gate.mark_needs_repair();
1349 return Err(StarryError::BadState);
1350 }
1351
1352 let mut published_children = 0usize;
1353 for child in &child_slots {
1354 if !child.publish_after_graph_replace() {
1355 break;
1356 }
1357 published_children += 1;
1358 }
1359 let old_detached =
1360 published_children == child_slots.len() && old_slot.detach_after_graph_replace();
1361 if !old_detached {
1362 for child in child_slots[..published_children].iter().rev() {
1363 let _ = child.reserve_after_graph_replace();
1364 }
1365 let graph_restored = old_slot
1366 .page
1367 .replace_mapping_graph_reserved(&child_keys, &old_keys, &mut graph_reservation)
1368 .is_ok();
1369 let restored = pt.restore_huge_split(installed);
1370 let (deposit_restored, orphaned) = match restored {
1371 Ok(deposit) => match old_slot.restore_huge_split_deposit(deposit) {
1372 Ok(()) => (true, None),
1373 Err(orphaned) => (false, Some(orphaned)),
1374 },
1375 Err(_) => (false, None),
1376 };
1377 drop(stripe);
1378 drop(graph_reservation);
1379 drop(orphaned);
1380 if graph_restored && deposit_restored {
1381 return Err(StarryError::BadState);
1382 }
1383 mutation_gate.mark_needs_repair();
1384 return Err(StarryError::BadState);
1385 }
1386
1387 drop(stripe);
1388 drop(graph_reservation);
1389 let previous_mapping_slots =
1390 core::mem::replace(&mut self.mapping_slots, next_mapping_slots);
1391 Ok(AppliedHugeSplit {
1392 installed,
1393 old_slot,
1394 child_keys,
1395 child_slots,
1396 previous_mapping_slots,
1397 })
1398 }
1399
1400 fn rollback_applied_huge_splits(&mut self, mut splits: Vec<AppliedHugeSplit>) -> bool {
1401 while let Some(split) = splits.pop() {
1402 let block_range = VirtAddrRange::try_from_start_size(
1403 split.installed.block_vaddr(),
1404 split.installed.block_size(),
1405 );
1406 let Some(block_range) = block_range else {
1407 return false;
1408 };
1409 let old_key = MappingSlotKey {
1410 space_id: self.id,
1411 va: split.old_slot.va,
1412 };
1413 let old_keys = [old_key];
1414 let mut graph_reservation = match split
1415 .old_slot
1416 .page
1417 .prepare_mapping_graph_replace(&split.child_keys, &old_keys)
1418 {
1419 Ok(reservation) => reservation,
1420 Err(_) => return false,
1421 };
1422 let pte_domain = &self.pte_domain;
1423 let pt = &mut self.pt;
1424 let stripe = pte_domain.lock_range(block_range);
1425 let Ok(deposit) = pt.restore_huge_split(split.installed) else {
1426 drop(stripe);
1427 drop(graph_reservation);
1428 return false;
1429 };
1430 if split
1431 .old_slot
1432 .page
1433 .replace_mapping_graph_reserved(
1434 &split.child_keys,
1435 &old_keys,
1436 &mut graph_reservation,
1437 )
1438 .is_err()
1439 {
1440 let orphaned = split.old_slot.restore_huge_split_deposit(deposit).err();
1441 drop(stripe);
1442 drop(graph_reservation);
1443 drop(orphaned);
1444 return false;
1445 }
1446 let slots_restored = !split
1447 .child_slots
1448 .iter()
1449 .any(|slot| !slot.detach_after_graph_replace())
1450 && split.old_slot.restore_after_graph_replace();
1451 let orphaned = split.old_slot.restore_huge_split_deposit(deposit).err();
1452 let deposit_restored = orphaned.is_none();
1453 drop(stripe);
1454 drop(graph_reservation);
1455 drop(orphaned);
1456 if !slots_restored || !deposit_restored {
1457 return false;
1458 }
1459 self.mapping_slots = split.previous_mapping_slots;
1460 }
1461 true
1462 }
1463
1464 fn capture_protection_leaf_preimage(
1465 &self,
1466 range: VirtAddrRange,
1467 ) -> StarryResult<Vec<ProtectionLeafPreimage>> {
1468 let occupied = self.occupied_pte_leaves_overlapping(&[range])?;
1469 let mut leaves = Vec::new();
1470 leaves
1471 .try_reserve(occupied.len())
1472 .map_err(|_| StarryError::NoMemory)?;
1473 for leaf in occupied {
1474 leaves.push(ProtectionLeafPreimage {
1475 va: leaf.range.start,
1476 paddr: leaf.paddr,
1477 page_size: leaf.range.size(),
1478 flags: leaf.flags,
1479 });
1480 }
1481 Ok(leaves)
1482 }
1483
1484 fn restore_protection_leaf_preimage(&mut self, leaves: &[ProtectionLeafPreimage]) -> bool {
1485 for leaf in leaves.iter().rev() {
1486 let Ok((paddr, _, page_size)) = self.pt.query(leaf.va) else {
1487 return false;
1488 };
1489 if paddr != leaf.paddr || page_size != leaf.page_size {
1490 return false;
1491 }
1492 if self.pt.protect_page(leaf.va, leaf.flags) != Ok(leaf.page_size) {
1493 return false;
1494 }
1495 }
1496 true
1497 }
1498
1499 fn abort_unpublished_protection(
1500 &mut self,
1501 vma_root: Arc<VmaMap>,
1502 vm_stat: ProcessVmStatSnapshot,
1503 leaves: &[ProtectionLeafPreimage],
1504 splits: Vec<AppliedHugeSplit>,
1505 original_error: StarryError,
1506 ) -> StarryResult {
1507 let ptes_restored = self.restore_protection_leaf_preimage(leaves);
1508 self.vma_root = vma_root;
1509 self.vm_stat.restore(vm_stat);
1510 let splits_restored = self.rollback_applied_huge_splits(splits);
1511 if ptes_restored && splits_restored {
1512 self.mutation_gate.clear_repair();
1513 Err(original_error)
1514 } else {
1515 self.mutation_gate.mark_needs_repair();
1516 Err(StarryError::BadState)
1517 }
1518 }
1519
1520 #[cfg(not(any(target_arch = "aarch64", target_arch = "loongarch64")))]
1531 pub(crate) unsafe fn share_kernel_root_entries_from(
1532 &mut self,
1533 source: RootEntryShare<'_>,
1534 ) -> Result<(), PagingError> {
1535 unsafe { source.install_into(&mut self.pt) }
1538 }
1539
1540 const fn materialized_root(&self) -> PhysAddr {
1542 self.pt.root_paddr()
1543 }
1544
1545 pub fn contains_range(&self, start: VirtAddr, size: usize) -> bool {
1547 let Some(range) = VirtAddrRange::try_from_start_size(start, size) else {
1548 return false;
1549 };
1550 range.start >= self.base() && range.end <= self.end()
1551 }
1552
1553 pub fn new_empty(base: VirtAddr, size: usize) -> StarryResult<Self> {
1555 Self::new_with_layout(UserVirtualAddressLayout::from_range(base, size)?)
1556 }
1557
1558 pub(crate) fn new_user(layout: UserVirtualAddressLayout) -> StarryResult<Self> {
1560 Self::new_with_layout(layout)
1561 }
1562
1563 fn new_with_layout(layout: UserVirtualAddressLayout) -> StarryResult<Self> {
1564 Ok(Self {
1565 id: AddressSpaceId::allocate(),
1566 layout,
1567 vma_root: Arc::new(VmaMap::default()),
1568 heap: HeapState::new(USER_HEAP_BASE),
1569 executable_data: ExecutableDataLayout::default(),
1570 pt: PageTable::new(PagingAllocator).map_err(|_| StarryError::NoMemory)?,
1571 pte_domain: PageTableDomain::new(),
1572 mutation_gate: MutationGate::new(),
1573 published_epoch: Arc::new(core::sync::atomic::AtomicU64::new(0)),
1574 vm_stat: ProcessVmStat::new(),
1575 resident_pages: ResidentPageCounts::default(),
1576 resident_watermark: ResidentWatermark::new(),
1577 tlb_quarantine: TlbQuarantine::default(),
1578 tlb_targets: Arc::new(AtomicUsize::new(0)),
1579 mapping_slots: BTreeMap::new(),
1580 retired_mapping_batches: IrqMutex::new(Vec::new()),
1581 })
1582 }
1583
1584 pub const fn address_space_id(&self) -> AddressSpaceId {
1587 self.id
1588 }
1589
1590 pub fn vm_epoch(&self) -> VmEpoch {
1592 self.mutation_gate.current_epoch()
1593 }
1594
1595 pub(crate) fn tlb_targets(&self) -> Arc<AtomicUsize> {
1596 self.tlb_targets.clone()
1597 }
1598
1599 pub(crate) fn published_epoch_source(&self) -> Arc<core::sync::atomic::AtomicU64> {
1600 self.published_epoch.clone()
1601 }
1602
1603 fn publish_mutation_classified(
1604 &mut self,
1605 mutation: PreparedMutation,
1606 ) -> Result<MutationPublication, CommitMutationError> {
1607 let mut next_resident = self.resident_pages;
1608 next_resident
1609 .checked_apply(mutation.receipt().resident_delta)
1610 .map_err(CommitMutationError::Unpublished)?;
1611 match self.mutation_gate.commit(mutation) {
1612 Ok(receipt) => {
1613 self.resident_pages = next_resident;
1614 self.published_epoch.store(
1615 receipt.new_epoch.get(),
1616 core::sync::atomic::Ordering::Release,
1617 );
1618 self.resident_watermark
1619 .observe_resident_total(self.resident_pages.total());
1620 Ok(MutationPublication::Complete)
1621 }
1622 Err(MutationError::TlbPending) => {
1623 self.resident_pages = next_resident;
1624 self.published_epoch.store(
1625 self.mutation_gate.current_epoch().get(),
1626 core::sync::atomic::Ordering::Release,
1627 );
1628 self.resident_watermark
1633 .observe_resident_total(self.resident_pages.total());
1634 Ok(MutationPublication::PendingTlb)
1635 }
1636 Err(error) => Err(CommitMutationError::Unpublished(
1637 Self::map_unpublished_mutation_error(error),
1638 )),
1639 }
1640 }
1641
1642 fn map_unpublished_mutation_error(error: MutationError) -> StarryError {
1643 match error {
1644 MutationError::ResourceExhausted => StarryError::NoMemory,
1645 MutationError::PendingTlbOverlap => StarryError::ResourceBusy,
1646 MutationError::NeedsRepair
1647 | MutationError::EpochExhausted
1648 | MutationError::EpochConflict
1649 | MutationError::WrongState
1650 | MutationError::ApplyFailed
1651 | MutationError::TlbPending => StarryError::BadState,
1652 }
1653 }
1654
1655 fn commit_mutation_classified(
1656 &mut self,
1657 mutation: PreparedMutation,
1658 ) -> Result<(), CommitMutationError> {
1659 match self.publish_mutation_classified(mutation)? {
1660 MutationPublication::Complete => Ok(()),
1661 MutationPublication::PendingTlb => {
1662 self.service_pending_tlb()
1667 .map(|_| ())
1668 .map_err(CommitMutationError::PublishedPendingTlb)
1669 }
1670 }
1671 }
1672
1673 fn commit_mutation(&mut self, mutation: PreparedMutation) -> StarryResult {
1674 match self.commit_mutation_classified(mutation) {
1675 Ok(()) => Ok(()),
1676 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
1677 Err(CommitMutationError::Unpublished(error)) => {
1678 self.mutation_gate.mark_needs_repair();
1684 Err(error)
1685 }
1686 }
1687 }
1688
1689 pub fn service_pending_tlb(&self) -> StarryResult<usize> {
1696 let requests = self
1697 .mutation_gate
1698 .pending_requests()
1699 .map_err(|_| StarryError::NoMemory)?;
1700 Self::flush_tlb_requests(&requests, &self.tlb_targets)?;
1701 self.acknowledge_tlb_requests(&requests)
1702 }
1703
1704 fn flush_tlb_requests(requests: &[TlbRequest], tlb_targets: &AtomicUsize) -> StarryResult {
1705 for request in requests {
1706 let pending_targets = request.pending();
1707 let live_targets =
1711 pending_targets & tlb_targets.load(core::sync::atomic::Ordering::Acquire);
1712 if request.ranges.is_empty() && live_targets != 0 {
1713 ax_runtime::hal::cache::flush_tlb_all_on_cpus(live_targets)
1714 .map_err(Self::map_tlb_shootdown_error)?;
1715 } else if live_targets != 0 {
1716 for range in &request.ranges {
1717 ax_runtime::hal::cache::flush_tlb_range_on_cpus(
1718 live_targets,
1719 range.start,
1720 range.size,
1721 )
1722 .map_err(Self::map_tlb_shootdown_error)?;
1723 }
1724 }
1725 }
1726 Ok(())
1727 }
1728
1729 fn map_tlb_shootdown_error(error: ax_runtime::hal::cache::TlbShootdownError) -> StarryError {
1730 match error {
1731 ax_runtime::hal::cache::TlbShootdownError::Timeout => StarryError::TimedOut,
1732 ax_runtime::hal::cache::TlbShootdownError::Unsupported
1733 | ax_runtime::hal::cache::TlbShootdownError::CpuOffline => StarryError::Unsupported,
1734 ax_runtime::hal::cache::TlbShootdownError::GenerationExhausted => {
1735 StarryError::Errno(syscalls::Errno::EOVERFLOW)
1736 }
1737 ax_runtime::hal::cache::TlbShootdownError::Platform => StarryError::Io,
1738 }
1739 }
1740
1741 fn acknowledge_tlb_requests(&self, requests: &[TlbRequest]) -> StarryResult<usize> {
1742 let mut completed = 0;
1743 for request in requests {
1744 let pending_targets = request.pending();
1745 for cpu in 0..usize::BITS as usize {
1749 if pending_targets & (1usize << cpu) == 0 {
1750 continue;
1751 }
1752 match self.mutation_gate.acknowledge(self.id, request.epoch, cpu) {
1753 Ok(_) | Err(MutationError::WrongState) | Err(MutationError::TlbPending) => {}
1754 Err(_) => return Err(StarryError::BadState),
1755 }
1756 self.tlb_quarantine
1757 .acknowledge(self.id, request.epoch, cpu)
1758 .map_err(|_| StarryError::NoMemory)?;
1759 }
1760 if self
1761 .mutation_gate
1762 .pending_request(self.id, request.epoch)
1763 .is_none()
1764 {
1765 self.release_retired_mapping_owners(request.epoch);
1766 }
1767 completed += 1;
1768 }
1769 Ok(completed)
1770 }
1771
1772 fn prepare_mutation(&self) -> PreparedMutation {
1773 self.mutation_gate
1774 .begin_with_active_targets(self.id, self.tlb_targets.clone())
1775 }
1776
1777 fn prepare_metadata_mutation(&self) -> PreparedMutation {
1782 self.mutation_gate.begin(self.id, 0)
1783 }
1784
1785 fn prepare_mutation_range(&self, start: VirtAddr, size: usize) -> PreparedMutation {
1786 let mut mutation = self.prepare_mutation();
1787 if let Some(range) = TlbRange::new(start, size) {
1788 mutation.add_tlb_range(range);
1789 }
1790 mutation
1791 }
1792
1793 fn prepare_fresh_pte_mutation_range(&self, start: VirtAddr, size: usize) -> PreparedMutation {
1799 prepare_mapping_publication_mutation(
1800 &self.mutation_gate,
1801 self.id,
1802 &self.tlb_targets,
1803 start,
1804 size,
1805 false,
1806 )
1807 }
1808
1809 fn collect_retired_mapping_owner(
1816 &self,
1817 entry: &Arc<VmaEntry>,
1818 range: VirtAddrRange,
1819 owners: &mut RetiredMappingOwners,
1820 ) -> StarryResult {
1821 owners
1822 .backends
1823 .try_reserve(1)
1824 .map_err(|_| StarryError::NoMemory)?;
1825 let backend = entry.operation_clone();
1826
1827 if backend.shared_file_lease().is_some() {
1828 let start = entry.start().max(range.start).align_down_4k();
1829 let end = entry.end().min(range.end);
1830 let file_range = VirtAddrRange::new(start, end);
1831 let count = self.mapping_slots_overlapping(file_range).count();
1832 owners
1833 .cache_pins
1834 .try_reserve(count)
1835 .map_err(|_| StarryError::NoMemory)?;
1836 for (_, slot) in self.mapping_slots_overlapping(file_range) {
1837 if slot.state() != SlotState::Present
1838 || slot.mapping != backend.mapping_id()
1839 || slot.page_order != PageOrder::BASE
1840 {
1841 return Err(StarryError::BadState);
1842 }
1843 let paddr = slot.mapped_paddr().ok_or(StarryError::BadState)?;
1844 let (installed, _, page_size) = self.pt.query(slot.va)?;
1845 if installed != paddr || page_size != PAGE_SIZE_4K {
1846 return Err(StarryError::BadState);
1847 }
1848 let pin = backend
1849 .pin_file_cache_owner_for_mapping(slot.va, paddr)?
1850 .ok_or(StarryError::BadState)?;
1851 owners.cache_pins.push(pin);
1852 }
1853 }
1854 owners.backends.push(backend);
1855 Ok(())
1856 }
1857
1858 fn prepare_retired_mapping_owners(
1859 &self,
1860 range: VirtAddrRange,
1861 ) -> StarryResult<RetiredMappingOwners> {
1862 try_reserve_irq_vec(&self.retired_mapping_batches, 1).map_err(|_| StarryError::NoMemory)?;
1863
1864 let mut owners = RetiredMappingOwners::default();
1865 let mut failure = None;
1869 self.vma_root.for_each_overlapping_entry(range, |entry| {
1870 match self.collect_retired_mapping_owner(entry, range, &mut owners) {
1871 Ok(()) => true,
1872 Err(err) => {
1873 failure = Some(err);
1874 false
1875 }
1876 }
1877 });
1878 if let Some(err) = failure {
1879 return Err(err);
1880 }
1881
1882 let matching_slots = self.mapping_slots_overlapping(range).count();
1883 owners
1884 .pages
1885 .try_reserve(matching_slots)
1886 .map_err(|_| StarryError::NoMemory)?;
1887 for (_, slot) in self.mapping_slots_overlapping(range) {
1888 if slot.state() != SlotState::Present {
1889 return Err(StarryError::BadState);
1890 }
1891 owners.pages.push(slot.page.clone());
1892 }
1893 Ok(owners)
1894 }
1895
1896 fn prepare_deferred_eviction_owner(
1900 &self,
1901 page: &Arc<PageObject>,
1902 ) -> StarryResult<RetiredMappingOwners> {
1903 try_reserve_irq_vec(&self.retired_mapping_batches, 1).map_err(|_| StarryError::NoMemory)?;
1904 let mut owners = RetiredMappingOwners::default();
1905 owners
1906 .deferred_evictions
1907 .try_reserve(1)
1908 .map_err(|_| StarryError::NoMemory)?;
1909 owners.deferred_evictions.push(page.clone());
1910 Ok(owners)
1911 }
1912
1913 fn park_retired_mapping_owners(&self, epoch: VmEpoch, owners: RetiredMappingOwners) {
1914 if owners.is_empty() {
1915 return;
1916 }
1917 self.retired_mapping_batches
1920 .lock()
1921 .push(RetiredMappingBatch { epoch, owners });
1922 }
1923
1924 fn release_retired_mapping_owners(&self, epoch: VmEpoch) {
1925 loop {
1926 let batch = {
1927 let mut batches = self.retired_mapping_batches.lock();
1928 batches
1929 .iter()
1930 .position(|batch| batch.epoch == epoch)
1931 .map(|index| batches.swap_remove(index))
1932 };
1933 let Some(batch) = batch else {
1934 break;
1935 };
1936 debug_assert!(!batch.owners.is_empty());
1937 for page in &batch.owners.deferred_evictions {
1938 page.complete_eviction_tlb();
1939 }
1940 drop(batch);
1943 }
1944 }
1945
1946 fn pending_retired_mapping_batches(&self) -> usize {
1947 self.retired_mapping_batches.lock().len()
1948 }
1949
1950 pub fn pending_tlb_obligations(&self) -> usize {
1952 self.mutation_gate.pending_count()
1953 }
1954
1955 pub fn pending_tlb_requests(&self) -> StarryResult<Vec<TlbRequest>> {
1959 self.mutation_gate
1960 .pending_requests()
1961 .map_err(|_| StarryError::NoMemory)
1962 }
1963
1964 pub fn acknowledge_tlb(
1967 &self,
1968 space_id: AddressSpaceId,
1969 epoch: VmEpoch,
1970 cpu: usize,
1971 ) -> StarryResult<Vec<FrameLease>> {
1972 match self.mutation_gate.acknowledge(space_id, epoch, cpu) {
1973 Ok(_) | Err(MutationError::TlbPending) => {}
1974 Err(MutationError::WrongState)
1975 if self.tlb_quarantine.contains_request(space_id, epoch) =>
1976 {
1977 }
1982 Err(_) => return Err(StarryError::ResourceBusy),
1983 }
1984 let released = self
1985 .tlb_quarantine
1986 .acknowledge(space_id, epoch, cpu)
1987 .map_err(|_| StarryError::NoMemory)?;
1988 if self
1989 .mutation_gate
1990 .pending_request(space_id, epoch)
1991 .is_none()
1992 {
1993 self.release_retired_mapping_owners(epoch);
1994 }
1995 Ok(released)
1996 }
1997
1998 pub fn quarantine_frame(
1999 &self,
2000 frame: FrameLease,
2001 request: TlbRequest,
2002 ) -> Result<(), QuarantineFailure> {
2003 self.tlb_quarantine.try_defer(frame, request)
2004 }
2005
2006 fn validate_region(&self, start: VirtAddr, size: usize) -> StarryResult {
2007 if self.mutation_gate.needs_repair() {
2008 return Err(StarryError::BadState);
2009 }
2010 if size == 0 || !self.contains_range(start, size) {
2011 return Err(StarryError::NoMemory);
2012 }
2013 if !start.is_aligned_4k() || !is_aligned_4k(size) {
2014 return Err(StarryError::InvalidInput);
2015 }
2016 Ok(())
2017 }
2018
2019 fn capture_mapping_preimage(&self, range: VirtAddrRange) -> StarryResult<MappingPreimage> {
2023 self.capture_mapping_preimage_ranges(&[range])
2024 }
2025
2026 fn capture_mapping_preimage_ranges(
2031 &self,
2032 ranges: &[VirtAddrRange],
2033 ) -> StarryResult<MappingPreimage> {
2034 for (index, range) in ranges.iter().enumerate() {
2035 self.validate_region(range.start, range.size())?;
2036 if ranges[..index]
2037 .iter()
2038 .any(|previous| previous.overlaps(*range))
2039 {
2040 return Err(StarryError::InvalidInput);
2041 }
2042 }
2043 let resident_slots = self.materialized_slots_overlapping(ranges)?;
2044 let mut leaves = Vec::new();
2045 leaves
2046 .try_reserve(resident_slots.len())
2047 .map_err(|_| StarryError::NoMemory)?;
2048 for (key, slot, occupied_leaf) in resident_slots {
2049 let page_size = occupied_leaf.range.size();
2050 let end = slot
2051 .va
2052 .checked_add(page_size)
2053 .ok_or(StarryError::BadState)?;
2054 let range = ranges
2055 .iter()
2056 .find(|range| slot.overlaps(**range))
2057 .ok_or(StarryError::BadState)?;
2058 if slot.va < range.start || end > range.end {
2062 return Err(StarryError::OperationNotSupported);
2063 }
2064 let paddr = occupied_leaf.paddr;
2065 let backend = self
2066 .vma_root
2067 .lookup_entry(slot.va)
2068 .map(|entry| entry.operation_clone())
2069 .ok_or(StarryError::BadState)?;
2070 let page = slot.page.clone();
2071 let frame_start = page.frame().paddr().as_usize();
2072 let frame_end = frame_start
2073 .checked_add(page.frame().size())
2074 .ok_or(StarryError::BadState)?;
2075 let leaf_start = paddr.as_usize();
2076 let leaf_end = leaf_start
2077 .checked_add(page_size)
2078 .ok_or(StarryError::BadState)?;
2079 if key.va != slot.va
2080 || slot.state() != SlotState::Present
2081 || slot.mm_id != self.id
2082 || slot.mapping != backend.mapping_id()
2083 || slot.mapped_paddr() != Some(paddr)
2084 || leaf_start < frame_start
2085 || leaf_end > frame_end
2086 {
2087 return Err(StarryError::BadState);
2088 }
2089 leaves.push(ResidentLeafPreimage {
2090 va: slot.va,
2091 paddr,
2092 page_size,
2093 flags: occupied_leaf.flags,
2094 backend,
2095 page,
2096 slot,
2097 });
2098 }
2099 Ok(MappingPreimage {
2100 vma_root: self.vma_root.clone(),
2101 vm_stat: self.vm_stat.snapshot(),
2102 leaves,
2103 })
2104 }
2105
2106 fn restore_mapping_preimage(
2110 &mut self,
2111 range: VirtAddrRange,
2112 preimage: MappingPreimage,
2113 ) -> StarryResult {
2114 self.restore_mapping_preimage_ranges(&[range], preimage)
2115 }
2116
2117 fn abort_unpublished_mapping_mutation(
2125 &mut self,
2126 range: VirtAddrRange,
2127 preimage: MappingPreimage,
2128 original_error: StarryError,
2129 ) -> StarryResult {
2130 self.abort_unpublished_parked_mapping_mutation(range, preimage, None, original_error)
2131 }
2132
2133 fn abort_file_eviction_mutation(
2138 &mut self,
2139 range: VirtAddrRange,
2140 preimage: MappingPreimage,
2141 parked_epoch: Option<VmEpoch>,
2142 original_error: StarryError,
2143 ) -> Result<EvictMappingOutcome, StarryError> {
2144 match self.restore_mapping_preimage(range, preimage) {
2145 Ok(()) => {
2146 if let Some(epoch) = parked_epoch {
2147 self.release_retired_mapping_owners(epoch);
2148 }
2149 self.mutation_gate.clear_repair();
2150 Err(original_error)
2151 }
2152 Err(_) => {
2153 self.mutation_gate.mark_needs_repair();
2154 Ok(EvictMappingOutcome::NeedsRepair)
2155 }
2156 }
2157 }
2158
2159 fn abort_unpublished_parked_mapping_mutation(
2165 &mut self,
2166 range: VirtAddrRange,
2167 preimage: MappingPreimage,
2168 parked_epoch: Option<VmEpoch>,
2169 original_error: StarryError,
2170 ) -> StarryResult {
2171 self.abort_unpublished_parked_mapping_mutation_ranges(
2172 &[range],
2173 preimage,
2174 parked_epoch,
2175 original_error,
2176 )
2177 }
2178
2179 fn abort_unpublished_parked_mapping_mutation_ranges(
2180 &mut self,
2181 ranges: &[VirtAddrRange],
2182 preimage: MappingPreimage,
2183 parked_epoch: Option<VmEpoch>,
2184 original_error: StarryError,
2185 ) -> StarryResult {
2186 match self.restore_mapping_preimage_ranges(ranges, preimage) {
2187 Ok(()) => {
2188 if let Some(epoch) = parked_epoch {
2189 self.release_retired_mapping_owners(epoch);
2190 }
2191 self.mutation_gate.clear_repair();
2192 Err(original_error)
2193 }
2194 Err(_) => {
2195 self.mutation_gate.mark_needs_repair();
2196 Err(StarryError::BadState)
2197 }
2198 }
2199 }
2200
2201 fn abort_unpublished_split_mapping_mutation(
2206 &mut self,
2207 range: VirtAddrRange,
2208 preimage: MappingPreimage,
2209 parked_epoch: Option<VmEpoch>,
2210 splits: Vec<AppliedHugeSplit>,
2211 original_error: StarryError,
2212 ) -> StarryResult {
2213 self.abort_unpublished_split_mapping_mutation_ranges(
2214 &[range],
2215 preimage,
2216 parked_epoch,
2217 splits,
2218 original_error,
2219 )
2220 }
2221
2222 fn abort_unpublished_split_mapping_mutation_ranges(
2226 &mut self,
2227 ranges: &[VirtAddrRange],
2228 preimage: MappingPreimage,
2229 parked_epoch: Option<VmEpoch>,
2230 splits: Vec<AppliedHugeSplit>,
2231 original_error: StarryError,
2232 ) -> StarryResult {
2233 if self
2234 .restore_mapping_preimage_ranges(ranges, preimage)
2235 .is_ok()
2236 && self.rollback_applied_huge_splits(splits)
2237 {
2238 if let Some(epoch) = parked_epoch {
2239 self.release_retired_mapping_owners(epoch);
2240 }
2241 self.mutation_gate.clear_repair();
2242 Err(original_error)
2243 } else {
2244 self.mutation_gate.mark_needs_repair();
2245 Err(StarryError::BadState)
2246 }
2247 }
2248
2249 fn abort_unpublished_huge_splits(
2253 &mut self,
2254 splits: Vec<AppliedHugeSplit>,
2255 original_error: StarryError,
2256 ) -> StarryResult {
2257 if self.rollback_applied_huge_splits(splits) {
2258 self.mutation_gate.clear_repair();
2259 Err(original_error)
2260 } else {
2261 self.mutation_gate.mark_needs_repair();
2262 Err(StarryError::BadState)
2263 }
2264 }
2265
2266 fn restore_mapping_preimage_ranges(
2267 &mut self,
2268 ranges: &[VirtAddrRange],
2269 preimage: MappingPreimage,
2270 ) -> StarryResult {
2271 let mut current_memfds = Vec::new();
2272 for range in ranges {
2273 current_memfds.extend(crate::syscall::memfd_collect_metas_touching_mprotect_range(
2274 self,
2275 range.start,
2276 range.size(),
2277 ));
2278 }
2279 let MappingPreimage {
2280 vma_root,
2281 vm_stat,
2282 leaves,
2283 } = preimage;
2284 if self.detach_current_materialized_ranges(ranges).is_err() {
2285 self.mutation_gate.mark_needs_repair();
2286 return Err(StarryError::BadState);
2287 }
2288 for range in ranges {
2289 self.detach_mapping_slots(*range)?;
2290 }
2291 self.vma_root = vma_root.clone();
2292 for leaf in leaves {
2293 if leaf
2294 .backend
2295 .restore_resident_preimage(
2296 ResidentLeafRestore {
2297 va: leaf.va,
2298 paddr: leaf.paddr,
2299 page_size: leaf.page_size,
2300 flags: leaf.flags,
2301 page: Some(&leaf.page),
2302 },
2303 &mut self.pt,
2304 )
2305 .is_err()
2306 {
2307 self.mutation_gate.mark_needs_repair();
2308 return Err(StarryError::BadState);
2309 }
2310 let key = MappingSlotKey {
2311 space_id: self.id,
2312 va: leaf.va,
2313 };
2314 if self.mapping_slots.contains_key(&key)
2315 || !leaf.slot.restore()
2316 || self.mapping_slots.insert(key, leaf.slot).is_some()
2317 {
2318 self.mutation_gate.mark_needs_repair();
2319 return Err(StarryError::BadState);
2320 }
2321 leaf.page.set_resident_kind(
2322 self.mapping_slots
2323 .get(&key)
2324 .and_then(|slot| slot.resident_kind()),
2325 );
2326 }
2327 self.vma_root = vma_root;
2328 self.vm_stat.restore(vm_stat);
2329 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(self, ¤t_memfds);
2330 for range in ranges {
2331 let restored_memfds = crate::syscall::memfd_collect_metas_touching_mprotect_range(
2332 self,
2333 range.start,
2334 range.size(),
2335 );
2336 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
2337 self,
2338 &restored_memfds,
2339 );
2340 }
2341 Ok(())
2342 }
2343
2344 pub fn find_free_area(
2352 &self,
2353 hint: VirtAddr,
2354 size: usize,
2355 limit: VirtAddrRange,
2356 align: usize,
2357 ) -> Option<VirtAddr> {
2358 self.vma_root.find_free_area(hint, size, limit, align)
2359 }
2360
2361 pub fn find_area_snapshot(&self, vaddr: VirtAddr) -> Option<Arc<VmaSnapshot>> {
2363 self.vma_root.lookup(vaddr)
2364 }
2365
2366 pub fn vma_map_snapshot(&self) -> Arc<VmaMap> {
2368 self.vma_root.clone()
2369 }
2370
2371 pub fn vma_snapshots_in_range(
2373 &self,
2374 start: VirtAddr,
2375 size: usize,
2376 ) -> StarryResult<Vec<Arc<VmaSnapshot>>> {
2377 let range =
2378 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2379 if range.is_empty() {
2380 return Ok(Vec::new());
2381 }
2382 Ok(self.vma_root.lookup_range(range))
2383 }
2384
2385 pub(crate) fn vma_inspection_records(&self) -> StarryResult<Vec<VmaInspectionRecord>> {
2386 let mut records = Vec::new();
2387 records
2388 .try_reserve(self.vma_root.len())
2389 .map_err(|_| StarryError::NoMemory)?;
2390 for entry in self.vma_root.iter_entries() {
2391 records.push(entry.inspection_record()?);
2392 }
2393 Ok(records)
2394 }
2395
2396 pub(crate) fn max_mapped_end(&self) -> Option<VirtAddr> {
2397 self.vma_root.iter().last().map(|vma| vma.range.end)
2398 }
2399
2400 pub(crate) fn next_advice_fragment(
2401 &self,
2402 cursor: VirtAddr,
2403 end: VirtAddr,
2404 ) -> Option<VmaAdviceFragment> {
2405 self.vma_root
2406 .iter_entries()
2407 .find(|entry| entry.end() > cursor && entry.start() < end)
2408 .and_then(|entry| entry.advice_fragment(cursor, end))
2409 }
2410
2411 pub(crate) fn validate_mprotect_mapping_capabilities(
2412 &self,
2413 start: VirtAddr,
2414 size: usize,
2415 flags: MappingFlags,
2416 ) -> StarryResult<Vec<SharedFileMappingLease>> {
2417 let range =
2418 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2419 let operations = self.mapping_operation_fragments(range, true)?;
2420 let mut files = Vec::new();
2421 files
2422 .try_reserve(operations.len())
2423 .map_err(|_| StarryError::NoMemory)?;
2424 for (_, operation) in operations {
2425 operation.check_mprotect_flags(flags)?;
2426 if let Some(file) = operation.shared_file_lease() {
2427 files.push(file);
2428 }
2429 }
2430 Ok(files)
2431 }
2432
2433 pub(crate) fn shared_futex_identity(&self, address: VirtAddr) -> Option<SharedFutexIdentity> {
2434 self.vma_root
2435 .lookup_entry(address)
2436 .and_then(|entry| entry.operation().shared_futex_identity(address))
2437 }
2438
2439 pub(crate) fn mincore_probe(&self, address: VirtAddr) -> Option<VmaResidencyProbe> {
2440 self.vma_root
2441 .lookup_entry(address)
2442 .map(|entry| entry.residency_probe())
2443 }
2444
2445 pub(crate) fn mremap_source(&self, address: VirtAddr) -> Option<VmaMremapSource> {
2446 self.vma_root
2447 .lookup_entry(address)
2448 .map(|entry| entry.mremap_source())
2449 }
2450
2451 pub(crate) fn shared_file_vma_at(&self, address: VirtAddr) -> Option<SharedFileVmaRecord> {
2452 self.vma_root
2453 .lookup_entry(address)
2454 .and_then(|entry| entry.shared_file_record())
2455 }
2456
2457 pub(crate) fn shared_file_vmas(&self) -> Vec<SharedFileVmaRecord> {
2458 self.vma_root
2459 .iter_entries()
2460 .filter_map(|entry| entry.shared_file_record())
2461 .collect()
2462 }
2463
2464 #[allow(clippy::too_many_arguments)]
2465 pub(crate) fn mremap_move_from_source(
2466 &mut self,
2467 source: &VmaMremapSource,
2468 src: VirtAddr,
2469 src_size: usize,
2470 target: VirtAddr,
2471 target_size: usize,
2472 huge_page_advice: HugePageAdvice,
2473 dontunmap: bool,
2474 source_offset: usize,
2475 replace_target: bool,
2476 memlock_limit: Option<MemlockLimit>,
2477 ) -> StarryResult {
2478 let operation = source.relocated_operation(target, source_offset, target_size)?;
2479 self.mremap_move_transaction(
2480 src,
2481 src_size,
2482 target,
2483 target_size,
2484 MappingPermissions {
2485 current: source.rights(),
2486 reported: source.reported_rights(),
2487 maximum: source.max_rights(),
2488 },
2489 operation,
2490 huge_page_advice,
2491 source.lock_mode(),
2492 source.advice_policy(),
2493 dontunmap,
2494 replace_target,
2495 memlock_limit,
2496 )
2497 }
2498
2499 pub(crate) fn duplicate_shared_mremap_source(
2500 &mut self,
2501 source: &VmaMremapSource,
2502 target: VirtAddr,
2503 target_size: usize,
2504 source_offset: usize,
2505 replace_target: bool,
2506 memlock_limit: Option<MemlockLimit>,
2507 ) -> StarryResult {
2508 let object = source.shared_object().ok_or(StarryError::InvalidInput)?;
2509 let backend_start = target
2510 .as_usize()
2511 .checked_sub(source_offset)
2512 .map(VirtAddr::from_usize)
2513 .ok_or(StarryError::InvalidInput)?;
2514 self.map_mremap_duplicate(
2515 target,
2516 target_size,
2517 MappingPermissions {
2518 current: source.rights(),
2519 reported: source.reported_rights(),
2520 maximum: source.rights(),
2521 },
2522 MappingOperation::new_shared(backend_start, object),
2523 MappingPublication::mremap(
2524 replace_target,
2525 source.huge_page_advice(),
2526 source.lock_mode(),
2527 source.advice_policy(),
2528 memlock_limit,
2529 ),
2530 )
2531 }
2532
2533 fn validate_memlock_successor(
2534 &self,
2535 successor: &VmaMap,
2536 memlock_limit: Option<MemlockLimit>,
2537 ) -> StarryResult {
2538 let previous_locked = self.vma_root.locked_pages().ok_or(StarryError::BadState)?;
2539 let successor_locked = successor.locked_pages().ok_or(StarryError::BadState)?;
2540 if successor_locked <= previous_locked {
2541 return Ok(());
2542 }
2543 memlock_limit
2544 .ok_or(StarryError::BadState)?
2545 .validate(successor_locked)
2546 }
2547
2548 fn publish_vma_metadata_successor(
2549 &mut self,
2550 previous_root: Arc<VmaMap>,
2551 successor: VmaMap,
2552 operation: &'static str,
2553 ) -> StarryResult {
2554 let before_vmas = previous_root.len();
2555 let after_vmas = successor.len();
2556 let mut mutation = self.prepare_metadata_mutation();
2557 mutation.set_vma_delta(VmaDelta {
2558 split: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
2559 merged: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
2560 ..VmaDelta::default()
2561 });
2562 self.vma_root = Arc::new(successor);
2563 match self.commit_mutation_classified(mutation) {
2564 Ok(()) => Ok(()),
2565 Err(CommitMutationError::Unpublished(error)) => {
2566 self.vma_root = previous_root;
2567 Err(error)
2568 }
2569 Err(CommitMutationError::PublishedPendingTlb(error)) => {
2570 self.mutation_gate.mark_needs_repair();
2574 warn!(
2575 "metadata-only {operation} unexpectedly required TLB acknowledgement: {error}"
2576 );
2577 Err(StarryError::BadState)
2578 }
2579 }
2580 }
2581
2582 pub fn advise_huge_pages(
2589 &mut self,
2590 start: VirtAddr,
2591 size: usize,
2592 advice: HugePageAdvice,
2593 ) -> StarryResult {
2594 self.validate_region(start, size)?;
2595 let range =
2596 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2597 let previous_root = self.vma_root.clone();
2598 let affected = previous_root.lookup_range(range);
2599 if affected.is_empty() || !previous_root.contains_range(start, size) {
2600 return Err(StarryError::NoMemory);
2601 }
2602 if affected.iter().all(|vma| vma.huge_page_advice == advice) {
2603 return Ok(());
2604 }
2605
2606 let successor = previous_root
2607 .with_huge_page_advice(range, advice)
2608 .ok_or(StarryError::NoMemory)?;
2609 self.publish_vma_metadata_successor(previous_root, successor, "VMA THP advice")
2610 }
2611
2612 pub(crate) fn advise_vma_policy(
2615 &mut self,
2616 start: VirtAddr,
2617 size: usize,
2618 update: VmaAdviceUpdate,
2619 ) -> StarryResult {
2620 self.validate_region(start, size)?;
2621 let range =
2622 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2623 let previous_root = self.vma_root.clone();
2624 let affected = previous_root.lookup_range(range);
2625 if affected.is_empty() || !previous_root.contains_range(start, size) {
2626 return Err(StarryError::NoMemory);
2627 }
2628 if affected
2629 .iter()
2630 .all(|vma| vma.advice_policy.apply(update) == vma.advice_policy)
2631 {
2632 return Ok(());
2633 }
2634 let successor = previous_root
2635 .with_advice_update(range, update)
2636 .ok_or(StarryError::NoMemory)?;
2637 self.publish_vma_metadata_successor(previous_root, successor, "VMA madvise policy")
2638 }
2639
2640 fn set_vma_lock_mode(
2647 &mut self,
2648 start: VirtAddr,
2649 size: usize,
2650 lock_mode: VmaLockMode,
2651 memlock_limit: Option<MemlockLimit>,
2652 ) -> StarryResult {
2653 self.validate_region(start, size)?;
2654 let range =
2655 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2656 let previous_root = self.vma_root.clone();
2657 let affected = previous_root.lookup_range(range);
2658 if affected.is_empty() || !previous_root.contains_range(start, size) {
2659 return Err(StarryError::NoMemory);
2660 }
2661 if affected.iter().all(|vma| vma.lock_mode == lock_mode) {
2662 return Ok(());
2663 }
2664
2665 let successor = previous_root
2666 .with_lock_mode(range, lock_mode)
2667 .ok_or(StarryError::NoMemory)?;
2668 self.validate_memlock_successor(&successor, memlock_limit)?;
2669 self.publish_vma_metadata_successor(previous_root, successor, "VMA lock update")
2670 }
2671
2672 pub(crate) fn lock_vma_range(
2673 &mut self,
2674 start: VirtAddr,
2675 size: usize,
2676 lock_mode: VmaLockMode,
2677 memlock_limit: MemlockLimit,
2678 ) -> StarryResult {
2679 if !lock_mode.is_locked() {
2680 return Err(StarryError::InvalidInput);
2681 }
2682 self.set_vma_lock_mode(start, size, lock_mode, Some(memlock_limit))
2683 }
2684
2685 pub(crate) fn unlock_vma_range(&mut self, start: VirtAddr, size: usize) -> StarryResult {
2686 self.set_vma_lock_mode(start, size, VmaLockMode::Unlocked, None)
2687 }
2688
2689 fn publish_prepared_pte_owners(
2701 &mut self,
2702 operation: &MappingOperation,
2703 range: VirtAddrRange,
2704 materialization: &PteMaterialization,
2705 ) -> StarryResult<PteOwnerPublication> {
2706 let owners = materialization.owners();
2707 let mut publications = Vec::new();
2708 publications
2709 .try_reserve(owners.len())
2710 .map_err(|_| StarryError::NoMemory)?;
2711 let mut seen = Vec::new();
2712 seen.try_reserve(owners.len())
2713 .map_err(|_| StarryError::NoMemory)?;
2714 let mut mapping_delta = MappingDelta::default();
2715 let mut resident_delta = ResidentDelta::default();
2716
2717 for owner in owners {
2721 let publication = self.prepare_slot_publication(operation, range, owner)?;
2722 if seen.contains(&publication.key) {
2723 return Err(StarryError::BadState);
2724 }
2725 seen.push(publication.key);
2726 mapping_delta.attached = mapping_delta
2727 .attached
2728 .checked_add(publication.mapping_delta.attached)
2729 .ok_or(StarryError::BadState)?;
2730 mapping_delta.detached = mapping_delta
2731 .detached
2732 .checked_add(publication.mapping_delta.detached)
2733 .ok_or(StarryError::BadState)?;
2734 resident_delta.checked_add_assign(publication.resident_delta)?;
2735 publications.push(publication);
2736 }
2737
2738 for publication in publications {
2739 self.apply_slot_publication(operation, publication)?;
2740 }
2741 Ok(PteOwnerPublication {
2742 satisfied_pages: materialization.satisfied_pages(),
2743 mapping_delta,
2744 resident_delta,
2745 })
2746 }
2747
2748 fn publish_prepared_fault_owner(
2749 &mut self,
2750 operation: &MappingOperation,
2751 range: VirtAddrRange,
2752 materialization: &FaultMaterialization,
2753 ) -> StarryResult<PteOwnerPublication> {
2754 let Some(owner) = materialization.owner() else {
2755 return Ok(PteOwnerPublication {
2756 satisfied_pages: materialization.satisfied_pages(),
2757 ..PteOwnerPublication::default()
2758 });
2759 };
2760 let publication = self.prepare_slot_publication(operation, range, owner)?;
2763 let mapping_delta = publication.mapping_delta;
2764 let resident_delta = publication.resident_delta;
2765 self.apply_slot_publication(operation, publication)?;
2766 Ok(PteOwnerPublication {
2767 satisfied_pages: materialization.satisfied_pages(),
2768 mapping_delta,
2769 resident_delta,
2770 })
2771 }
2772
2773 fn prepare_slot_publication(
2774 &mut self,
2775 operation: &MappingOperation,
2776 range: VirtAddrRange,
2777 owner: &PreparedPteOwner,
2778 ) -> StarryResult<PreparedSlotPublication> {
2779 let va = owner.va;
2780 let paddr = owner.paddr;
2781 let page_size = owner.page_size;
2782 let page = &owner.page;
2783 let resident_kind = owner.resident_kind;
2784 let transition = owner.transition;
2785 let provider_publication = owner.provider_publication;
2786 if page_size < PAGE_SIZE_4K || !page_size.is_power_of_two() || !va.is_aligned(page_size) {
2787 return Err(StarryError::BadState);
2788 }
2789 let leaf_range =
2790 VirtAddrRange::try_from_start_size(va, page_size).ok_or(StarryError::BadState)?;
2791 if !range.contains_range(leaf_range) {
2792 return Err(StarryError::BadState);
2793 }
2794 let frame_start = page.frame().paddr().as_usize();
2795 let frame_end = frame_start
2796 .checked_add(page.frame().size())
2797 .ok_or(StarryError::BadState)?;
2798 let leaf_start = paddr.as_usize();
2799 let leaf_end = leaf_start
2800 .checked_add(page_size)
2801 .ok_or(StarryError::BadState)?;
2802 if leaf_start < frame_start || leaf_end > frame_end {
2803 return Err(StarryError::BadState);
2804 }
2805 match self.pt.query(va) {
2806 Ok((installed, _, installed_size))
2807 if installed == paddr && installed_size == page_size => {}
2808 Ok(_) | Err(_) => return Err(StarryError::BadState),
2809 }
2810 if !matches!(page.state(), PageState::Present | PageState::LazyFree) {
2811 return Err(StarryError::BadState);
2812 }
2813
2814 let key = MappingSlotKey {
2815 space_id: self.id,
2816 va,
2817 };
2818 let previous = self.mapping_slots.get(&key).cloned();
2819 let order = page_size
2820 .trailing_zeros()
2821 .checked_sub(PAGE_SIZE_4K.trailing_zeros())
2822 .and_then(|order| u8::try_from(order).ok())
2823 .map(PageOrder::new)
2824 .ok_or(StarryError::BadState)?;
2825 let same_owner = previous.as_ref().is_some_and(|slot| {
2826 slot.state() == SlotState::Present
2827 && slot.mapping == operation.mapping_id()
2828 && slot.page_order == order
2829 && slot.mapped_paddr() == Some(paddr)
2830 && Arc::ptr_eq(&slot.page, page)
2831 && (order == PageOrder::BASE || slot.has_huge_split_deposit())
2832 });
2833 match transition {
2834 PteOwnerTransition::Updated if !same_owner => return Err(StarryError::BadState),
2835 PteOwnerTransition::Replaced if previous.is_none() || same_owner => {
2836 return Err(StarryError::BadState);
2837 }
2838 PteOwnerTransition::Installed
2839 | PteOwnerTransition::Replaced
2840 | PteOwnerTransition::Updated => {}
2841 }
2842
2843 let replacement = if same_owner {
2844 None
2845 } else {
2846 let split_deposit = if order == PageOrder::BASE {
2847 None
2848 } else {
2849 Some(self.pt.prepare_huge_split(va)?)
2850 };
2851 let frame_offset = paddr
2852 .as_usize()
2853 .checked_sub(page.frame().paddr().as_usize())
2854 .ok_or(StarryError::BadState)?;
2855 let slot = MappingSlot::new_with_frame_offset(
2856 operation.mapping_id(),
2857 self.id,
2858 va,
2859 order,
2860 page.clone(),
2861 frame_offset,
2862 resident_kind,
2863 )
2864 .ok_or(StarryError::BadState)?;
2865 let slot = match split_deposit {
2866 Some(deposit) => slot
2867 .attach_huge_split_deposit(deposit)
2868 .map_err(|_| StarryError::BadState)?,
2869 None => slot,
2870 };
2871 Some(Arc::new(slot))
2872 };
2873 let mut mapping_delta = MappingDelta::default();
2874 let mut resident_delta = ResidentDelta::default();
2875 if !same_owner {
2876 mapping_delta.attached = 1;
2877 mapping_delta.detached = u32::from(previous.is_some());
2878 }
2879 if let Some(previous) = &previous {
2880 let pages = 1i64
2881 .checked_shl(previous.page_order.get().into())
2882 .ok_or(StarryError::BadState)?;
2883 resident_delta
2884 .checked_add_assign(ResidentDelta::for_pages(previous.resident_kind(), -pages))?;
2885 }
2886 let pages = 1i64
2887 .checked_shl(order.get().into())
2888 .ok_or(StarryError::BadState)?;
2889 resident_delta.checked_add_assign(ResidentDelta::for_pages(resident_kind, pages))?;
2890 Ok(PreparedSlotPublication {
2891 key,
2892 previous,
2893 replacement,
2894 resident_kind,
2895 provider_publication,
2896 mapping_delta,
2897 resident_delta,
2898 })
2899 }
2900
2901 fn apply_slot_publication(
2902 &mut self,
2903 operation: &MappingOperation,
2904 publication: PreparedSlotPublication,
2905 ) -> StarryResult {
2906 let PreparedSlotPublication {
2907 key,
2908 previous,
2909 replacement,
2910 resident_kind,
2911 provider_publication,
2912 mapping_delta: _,
2913 resident_delta: _,
2914 } = publication;
2915 let Some(replacement) = replacement else {
2916 let current = self.mapping_slots.get(&key).ok_or(StarryError::BadState)?;
2917 if previous
2918 .as_ref()
2919 .is_none_or(|previous| !Arc::ptr_eq(previous, current))
2920 {
2921 return Err(StarryError::BadState);
2922 }
2923 current.set_resident_kind(resident_kind);
2924 current.page.set_resident_kind(resident_kind);
2925 if provider_publication == ProviderPublication::Pending {
2926 operation.finish_page_publication(key.va, ¤t.page)?;
2927 }
2928 return Ok(());
2929 };
2930
2931 if let Some(previous) = &previous {
2932 let Some(current) = self.mapping_slots.remove(&key) else {
2933 return Err(StarryError::BadState);
2934 };
2935 if !Arc::ptr_eq(previous, ¤t) || !current.detach() {
2936 self.mapping_slots.insert(key, current);
2937 return Err(StarryError::BadState);
2938 }
2939 } else if self.mapping_slots.contains_key(&key) {
2940 return Err(StarryError::BadState);
2941 }
2942
2943 if !replacement.publish() {
2944 if let Some(previous) = previous
2945 && (!previous.restore() || self.mapping_slots.insert(key, previous).is_some())
2946 {
2947 self.mutation_gate.mark_needs_repair();
2948 }
2949 return Err(StarryError::BadState);
2950 }
2951 if provider_publication == ProviderPublication::Pending
2952 && let Err(error) = operation.finish_page_publication(key.va, &replacement.page)
2953 {
2954 let replacement_detached = replacement.detach();
2955 let previous_restored = previous.is_none_or(|previous| {
2956 previous.restore() && self.mapping_slots.insert(key, previous).is_none()
2957 });
2958 if !replacement_detached || !previous_restored {
2959 self.mutation_gate.mark_needs_repair();
2960 return Err(StarryError::BadState);
2961 }
2962 return Err(error);
2963 }
2964 if self.mapping_slots.insert(key, replacement).is_some() {
2965 self.mutation_gate.mark_needs_repair();
2966 return Err(StarryError::BadState);
2967 }
2968 Ok(())
2969 }
2970
2971 fn detach_mapping_slots(&mut self, range: VirtAddrRange) -> StarryResult {
2972 let keys: Vec<_> = self
2973 .mapping_slots_overlapping(range)
2974 .map(|(key, _)| *key)
2975 .collect();
2976 for key in keys {
2977 if let Some(slot) = self.mapping_slots.remove(&key)
2978 && !slot.detach()
2979 {
2980 self.mapping_slots.insert(key, slot);
2981 return Err(StarryError::BadState);
2982 }
2983 }
2984 Ok(())
2985 }
2986
2987 pub(crate) fn evict_file_mapping_slot(
2991 &mut self,
2992 key: MappingSlotKey,
2993 page: &Arc<PageObject>,
2994 ) -> Result<EvictMappingOutcome, StarryError> {
2995 if key.space_id != self.id {
2996 return Err(StarryError::BadState);
2997 }
2998 let slot = self
2999 .mapping_slots
3000 .get(&key)
3001 .cloned()
3002 .ok_or(StarryError::BadState)?;
3003 if !Arc::ptr_eq(&slot.page, page) {
3004 return Err(StarryError::BadState);
3005 }
3006
3007 let range = VirtAddrRange::from_start_size(key.va, PAGE_SIZE_4K);
3008 let preimage = self.capture_mapping_preimage(range)?;
3009 let retired_owner = self.prepare_deferred_eviction_owner(page)?;
3010 let mut mutation = self.prepare_mutation_range(key.va, PAGE_SIZE_4K);
3011 let retire_epoch = mutation
3012 .receipt()
3013 .base_epoch
3014 .checked_next()
3015 .ok_or(StarryError::BadState)?;
3016 mutation.set_pte_delta(PteDelta {
3017 unmapped: 1,
3018 ..PteDelta::default()
3019 });
3020 mutation.set_mapping_delta(MappingDelta {
3021 detached: 1,
3022 ..MappingDelta::default()
3023 });
3024 mutation.set_resident_delta(ResidentDelta::for_pages(slot.resident_kind(), -1));
3025
3026 let unmap_plan = self.pt.plan_unmap_page(key.va)?;
3027 if slot.mapped_paddr() != Some(unmap_plan.paddr()) || unmap_plan.page_size() != PAGE_SIZE_4K
3028 {
3029 return Err(StarryError::BadState);
3030 }
3031 let apply_result = (|| -> StarryResult {
3032 let unmapped = {
3033 let pte_domain = &self.pte_domain;
3034 let pt = &mut self.pt;
3035 let range = VirtAddrRange::from_start_size(key.va, PAGE_SIZE_4K);
3036 let _structure = pte_domain.lock_structure();
3037 let _stripe = pte_domain.lock_range(range);
3038 pt.try_unmap_page_with(unmap_plan)?
3039 };
3040 if slot.mapped_paddr() != Some(unmapped.0) || unmapped.2 != PAGE_SIZE_4K {
3041 return Err(StarryError::BadState);
3042 }
3043 let removed = self
3047 .mapping_slots
3048 .remove(&key)
3049 .ok_or(StarryError::BadState)?;
3050 if !Arc::ptr_eq(&removed, &slot) || !removed.detach() {
3051 return Err(StarryError::BadState);
3052 }
3053 Ok(())
3054 })();
3055 if let Err(error) = apply_result {
3056 return self.abort_file_eviction_mutation(range, preimage, None, error);
3057 }
3058
3059 self.park_retired_mapping_owners(retire_epoch, retired_owner);
3060 match self.commit_mutation_classified(mutation) {
3061 Ok(()) => {
3062 self.release_retired_mapping_owners(retire_epoch);
3063 Ok(EvictMappingOutcome::Complete)
3064 }
3065 Err(CommitMutationError::PublishedPendingTlb(_)) => {
3066 Ok(EvictMappingOutcome::PublishedPendingTlb)
3067 }
3068 Err(CommitMutationError::Unpublished(error)) => {
3069 self.abort_file_eviction_mutation(range, preimage, Some(retire_epoch), error)
3070 }
3071 }
3072 }
3073
3074 pub(crate) fn protect_file_mapping_slot(
3077 &mut self,
3078 key: MappingSlotKey,
3079 page: &Arc<PageObject>,
3080 ) -> StarryResult {
3081 if key.space_id != self.id {
3082 return Err(StarryError::BadState);
3083 }
3084 let slot = self.mapping_slots.get(&key).ok_or(StarryError::BadState)?;
3085 if !Arc::ptr_eq(&slot.page, page) {
3086 return Err(StarryError::BadState);
3087 }
3088
3089 let (paddr, flags, page_size) = self.pt.query(key.va)?;
3090 if slot.mapped_paddr() != Some(paddr) || page_size != PAGE_SIZE_4K {
3091 return Err(StarryError::BadState);
3092 }
3093 if !flags.contains(MappingFlags::WRITE) {
3094 return Ok(());
3095 }
3096 let mut mutation = self.prepare_mutation_range(key.va, PAGE_SIZE_4K);
3097 mutation.set_pte_delta(PteDelta {
3098 protected: 1,
3099 ..PteDelta::default()
3100 });
3101 {
3102 let pt = &mut self.pt;
3103 let _stripe = self
3104 .pte_domain
3105 .lock_range(VirtAddrRange::from_start_size(key.va, PAGE_SIZE_4K));
3106 pt.remap_page(key.va, paddr, flags - MappingFlags::WRITE)?;
3109 }
3110 self.commit_mutation(mutation)
3115 }
3116
3117 pub fn resident_mapping_slots(&self) -> Vec<Arc<MappingSlot>> {
3118 self.mapping_slots.values().cloned().collect()
3119 }
3120
3121 fn capture_mapping_graph_snapshot(
3122 &self,
3123 ranges: &[VirtAddrRange],
3124 ) -> StarryResult<MappingGraphSnapshot> {
3125 #[cfg(all(test, axtest))]
3126 MAPPING_GRAPH_SNAPSHOT_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3127 let slot_count = ranges
3128 .iter()
3129 .map(|range| self.mapping_slots_overlapping(*range).count())
3130 .sum();
3131 let mut slots = Vec::new();
3132 slots
3133 .try_reserve(slot_count)
3134 .map_err(|_| StarryError::NoMemory)?;
3135 let mut seen = BTreeSet::new();
3136 let mut resident = ResidentPageCounts::default();
3137 for range in ranges {
3138 for (key, slot) in self.mapping_slots_overlapping(*range) {
3139 if slot.state() != SlotState::Present || !seen.insert(*key) {
3140 continue;
3141 }
3142 slots.push(MappingSlotFingerprint {
3143 key: *key,
3144 mapping: slot.mapping,
3145 page: slot.page.id,
3146 page_order: slot.page_order,
3147 });
3148 let pages = 1u64
3149 .checked_shl(u32::from(slot.page_order.get()))
3150 .ok_or(StarryError::BadState)?;
3151 match slot.resident_kind() {
3152 Some(RssKind::Anon) => {
3153 resident.anon = resident
3154 .anon
3155 .checked_add(pages)
3156 .ok_or(StarryError::BadState)?;
3157 }
3158 Some(RssKind::File) => {
3159 resident.file = resident
3160 .file
3161 .checked_add(pages)
3162 .ok_or(StarryError::BadState)?;
3163 }
3164 Some(RssKind::Shmem) => {
3165 resident.shmem = resident
3166 .shmem
3167 .checked_add(pages)
3168 .ok_or(StarryError::BadState)?;
3169 }
3170 None => {}
3171 }
3172 }
3173 }
3174 slots.sort_unstable();
3175 Ok(MappingGraphSnapshot { slots, resident })
3176 }
3177
3178 fn set_mapping_graph_receipt_delta(
3179 &self,
3180 mutation: &mut PreparedMutation,
3181 before: &MappingGraphSnapshot,
3182 ranges: &[VirtAddrRange],
3183 ) -> StarryResult {
3184 let after = self.capture_mapping_graph_snapshot(ranges)?;
3185 let (mapping_delta, resident_delta) = before.delta_to(&after)?;
3186 mutation.set_mapping_delta(mapping_delta);
3187 mutation.set_resident_delta(resident_delta);
3188 Ok(())
3189 }
3190
3191 pub(crate) fn resident_page_counts(&self) -> ResidentPageCounts {
3193 self.resident_pages
3194 }
3195
3196 #[cfg(all(test, axtest))]
3197 fn reset_mapping_graph_snapshot_calls_for_test(&self) {
3198 MAPPING_GRAPH_SNAPSHOT_CALLS.store(0, core::sync::atomic::Ordering::Relaxed);
3199 }
3200
3201 #[cfg(all(test, axtest))]
3202 fn mapping_graph_snapshot_calls_for_test(&self) -> usize {
3203 MAPPING_GRAPH_SNAPSHOT_CALLS.load(core::sync::atomic::Ordering::Relaxed)
3204 }
3205
3206 pub(crate) fn resident_hiwater_pages(&self) -> u64 {
3207 self.resident_watermark.hiwater_pages()
3208 }
3209
3210 fn mapping_operation_fragments(
3214 &self,
3215 range: VirtAddrRange,
3216 require_full_coverage: bool,
3217 ) -> StarryResult<Vec<(VirtAddrRange, MappingOperation)>> {
3218 let mut fragments = Vec::new();
3219 fragments
3220 .try_reserve(self.vma_root.len())
3221 .map_err(|_| StarryError::NoMemory)?;
3222 let mut covered = range.start;
3223 for entry in self.vma_root.iter_entries() {
3224 if entry.start() >= range.end {
3225 break;
3226 }
3227 if entry.end() <= range.start {
3228 continue;
3229 }
3230 let fragment =
3231 VirtAddrRange::new(entry.start().max(range.start), entry.end().min(range.end));
3232 if require_full_coverage && fragment.start > covered {
3233 return Err(StarryError::NoMemory);
3234 }
3235 covered = covered.max(fragment.end);
3236 fragments.push((fragment, entry.operation_clone()));
3237 }
3238 if require_full_coverage && covered < range.end {
3239 return Err(StarryError::NoMemory);
3240 }
3241 Ok(fragments)
3242 }
3243
3244 fn detach_materialized_operation(
3249 &mut self,
3250 range: VirtAddrRange,
3251 operation: &MappingOperation,
3252 ) -> bool {
3253 let Ok(occupied) = self.occupied_pte_leaves_overlapping(&[range]) else {
3254 return false;
3255 };
3256 let leaves: Vec<_> = occupied.into_iter().map(|leaf| leaf.range).collect();
3257 if leaves
3258 .iter()
3259 .any(|leaf| !operation.validate_unmap_range(*leaf, &self.pt))
3260 {
3261 return false;
3262 }
3263 leaves
3264 .into_iter()
3265 .all(|leaf| operation.unmap_range(leaf, &mut self.pt).is_ok())
3266 }
3267
3268 fn detach_current_materialized_ranges(&mut self, ranges: &[VirtAddrRange]) -> StarryResult {
3272 let leaves = self.occupied_pte_leaves_overlapping(ranges)?;
3273 let mut operations = Vec::new();
3274 operations
3275 .try_reserve(leaves.len())
3276 .map_err(|_| StarryError::NoMemory)?;
3277 for leaf in leaves {
3278 let operation = self
3279 .vma_root
3280 .lookup_entry(leaf.range.start)
3281 .map(|entry| entry.operation_clone())
3282 .ok_or(StarryError::BadState)?;
3283 operations.push((leaf.range, operation));
3284 }
3285 if operations
3286 .iter()
3287 .any(|(leaf, operation)| !operation.validate_unmap_range(*leaf, &self.pt))
3288 {
3289 return Err(StarryError::BadState);
3290 }
3291 for (leaf, operation) in operations {
3292 operation.unmap_range(leaf, &mut self.pt)?;
3293 }
3294 Ok(())
3295 }
3296
3297 #[allow(clippy::too_many_arguments)]
3298 fn prepare_mapping_successor(
3299 &self,
3300 range: VirtAddrRange,
3301 permissions: MappingPermissions,
3302 operation: &MappingOperation,
3303 huge_page_advice: HugePageAdvice,
3304 lock_mode: VmaLockMode,
3305 advice_policy: VmaAdvicePolicy,
3306 replace: bool,
3307 ) -> StarryResult<VmaMap> {
3308 let entry = self
3309 .vma_root
3310 .prepare_mapping_entry(
3311 range,
3312 permissions.current,
3313 permissions.reported,
3314 permissions.maximum,
3315 huge_page_advice,
3316 lock_mode,
3317 advice_policy,
3318 operation.clone(),
3319 )
3320 .ok_or(StarryError::BadState)?;
3321 let successor = self.vma_root.with_mapping_entry(entry, replace).ok_or(
3322 if self.vma_root.overlaps(range) && !replace {
3323 StarryError::AlreadyExists
3324 } else {
3325 StarryError::BadState
3326 },
3327 )?;
3328 Ok(successor)
3329 }
3330
3331 fn apply_mapping_pages_unpublished(
3335 &mut self,
3336 range: VirtAddrRange,
3337 permissions: MappingPermissions,
3338 operation: &MappingOperation,
3339 replace: bool,
3340 ) -> StarryResult<PteMaterialization> {
3341 let replaced = if replace {
3342 self.mapping_operation_fragments(range, false)?
3343 } else {
3344 Vec::new()
3345 };
3346 if replaced
3347 .iter()
3348 .any(|(fragment, old)| !old.validate_unmap_range(*fragment, &self.pt))
3349 || (!replace && !operation.validate_map_range(range, &self.pt))
3350 {
3351 return Err(StarryError::BadState);
3352 }
3353 for (fragment, old) in &replaced {
3354 old.unmap_range(*fragment, &mut self.pt)?;
3355 }
3356 match operation.map_range(range, permissions.current, &mut self.pt) {
3357 Ok(materialization) => Ok(materialization),
3358 Err(error) => {
3359 if !self.detach_materialized_operation(range, operation) {
3360 self.mutation_gate.mark_needs_repair();
3361 return Err(StarryError::BadState);
3362 }
3363 Err(error)
3364 }
3365 }
3366 }
3367
3368 #[allow(clippy::too_many_arguments)]
3371 fn apply_mapping_unpublished(
3372 &mut self,
3373 range: VirtAddrRange,
3374 permissions: MappingPermissions,
3375 operation: &MappingOperation,
3376 huge_page_advice: HugePageAdvice,
3377 lock_mode: VmaLockMode,
3378 advice_policy: VmaAdvicePolicy,
3379 memlock_limit: Option<MemlockLimit>,
3380 replace: bool,
3381 ) -> StarryResult<PteMaterialization> {
3382 let successor = self.prepare_mapping_successor(
3383 range,
3384 permissions,
3385 operation,
3386 huge_page_advice,
3387 lock_mode,
3388 advice_policy,
3389 replace,
3390 )?;
3391 self.validate_memlock_successor(&successor, memlock_limit)?;
3392 let materialization =
3393 self.apply_mapping_pages_unpublished(range, permissions, operation, replace)?;
3394 self.vma_root = Arc::new(successor);
3395 Ok(materialization)
3396 }
3397
3398 fn apply_unmap_pages_unpublished(&mut self, range: VirtAddrRange) -> StarryResult {
3399 let operations = self.mapping_operation_fragments(range, false)?;
3400 if operations
3401 .iter()
3402 .any(|(fragment, operation)| !operation.validate_unmap_range(*fragment, &self.pt))
3403 {
3404 return Err(StarryError::BadState);
3405 }
3406 for (fragment, operation) in operations {
3407 operation.unmap_range(fragment, &mut self.pt)?;
3408 }
3409 Ok(())
3410 }
3411
3412 fn apply_unmap_unpublished(&mut self, range: VirtAddrRange) -> StarryResult {
3416 let successor = self
3417 .vma_root
3418 .without_range(range)
3419 .ok_or(StarryError::BadState)?;
3420 self.apply_unmap_pages_unpublished(range)?;
3421 self.vma_root = Arc::new(successor);
3422 Ok(())
3423 }
3424
3425 fn apply_protection_unpublished(
3427 &mut self,
3428 range: VirtAddrRange,
3429 flags: MappingFlags,
3430 reported_flags: MappingFlags,
3431 ) -> StarryResult {
3432 let successor = self
3433 .vma_root
3434 .with_permissions(range, flags, reported_flags)
3435 .ok_or(StarryError::NoMemory)?;
3436 let operations = self.mapping_operation_fragments(range, true)?;
3437 if operations
3438 .iter()
3439 .any(|(fragment, operation)| !operation.validate_protect_range(*fragment, &self.pt))
3440 {
3441 return Err(StarryError::BadState);
3442 }
3443 for (fragment, operation) in operations {
3444 operation.protect_range(fragment, flags, &mut self.pt)?;
3445 }
3446 self.vma_root = Arc::new(successor);
3447 Ok(())
3448 }
3449
3450 fn apply_extend_unpublished(
3451 &mut self,
3452 address: VirtAddr,
3453 additional_size: usize,
3454 memlock_limit: Option<MemlockLimit>,
3455 ) -> StarryResult<(VirtAddrRange, MappingOperation, PteMaterialization)> {
3456 let entry = self
3457 .vma_root
3458 .lookup_entry(address)
3459 .ok_or(StarryError::InvalidInput)?;
3460 let suffix = VirtAddrRange::try_from_start_size(entry.end(), additional_size)
3461 .ok_or(StarryError::InvalidInput)?;
3462 let operation = entry.operation_clone();
3463 let flags = entry.rights();
3464 let successor = self
3465 .vma_root
3466 .with_extended_right(address, additional_size)
3467 .ok_or(StarryError::AlreadyExists)?;
3468 self.validate_memlock_successor(&successor, memlock_limit)?;
3469 if !operation.validate_map_range(suffix, &self.pt) {
3470 return Err(StarryError::BadState);
3471 }
3472 let materialization = match operation.map_range(suffix, flags, &mut self.pt) {
3473 Ok(materialization) => materialization,
3474 Err(error) => {
3475 if !self.detach_materialized_operation(suffix, &operation) {
3476 self.mutation_gate.mark_needs_repair();
3477 return Err(StarryError::BadState);
3478 }
3479 return Err(error);
3480 }
3481 };
3482 self.vma_root = Arc::new(successor);
3483 Ok((suffix, operation, materialization))
3484 }
3485
3486 pub fn map_linear(
3495 &mut self,
3496 start_vaddr: VirtAddr,
3497 start_paddr: PhysAddr,
3498 size: usize,
3499 flags: MappingFlags,
3500 ) -> StarryResult {
3501 self.validate_region(start_vaddr, size)?;
3502 let range = VirtAddrRange::from_start_size(start_vaddr, size);
3503 let preimage = self.capture_mapping_preimage(range)?;
3504 let graph_preimage = self.capture_mapping_graph_snapshot(&[range])?;
3505 let mut mutation = self.prepare_mutation_range(start_vaddr, size);
3506
3507 if !start_paddr.is_aligned_4k() {
3508 return Err(StarryError::InvalidInput);
3509 }
3510 if start_paddr.checked_add(size).is_none() {
3511 return Err(StarryError::InvalidInput);
3512 }
3513
3514 let operation = MappingOperation::new_linear(start_vaddr, start_paddr, false);
3515 let materialization = match self.apply_mapping_unpublished(
3516 range,
3517 MappingPermissions {
3518 current: flags,
3519 reported: flags,
3520 maximum: flags,
3521 },
3522 &operation,
3523 HugePageAdvice::Default,
3524 VmaLockMode::Unlocked,
3525 VmaAdvicePolicy::default(),
3526 None,
3527 false,
3528 ) {
3529 Ok(materialization) => materialization,
3530 Err(error) => {
3531 return self.abort_unpublished_mapping_mutation(range, preimage, error);
3532 }
3533 };
3534 mutation.set_vma_delta(VmaDelta {
3535 inserted: 1,
3536 ..VmaDelta::default()
3537 });
3538 mutation.set_pte_delta(PteDelta {
3539 mapped: u32::try_from(size / PAGE_SIZE_4K).unwrap_or(u32::MAX),
3540 ..PteDelta::default()
3541 });
3542 self.vm_stat.on_map((size / PAGE_SIZE_4K) as u64);
3543 if let Err(error) = self.publish_prepared_pte_owners(&operation, range, &materialization) {
3544 return self.abort_unpublished_mapping_mutation(range, preimage, error);
3545 }
3546 if let Err(error) =
3547 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[range])
3548 {
3549 return self.abort_unpublished_mapping_mutation(range, preimage, error);
3550 }
3551 match self.commit_mutation_classified(mutation) {
3552 Ok(()) => Ok(()),
3553 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
3554 Err(CommitMutationError::Unpublished(error)) => {
3555 self.abort_unpublished_mapping_mutation(range, preimage, error)
3556 }
3557 }
3558 }
3559
3560 pub fn map(
3561 &mut self,
3562 start: VirtAddr,
3563 size: usize,
3564 flags: MappingFlags,
3565 populate: bool,
3566 backend: MappingOperation,
3567 ) -> StarryResult {
3568 self.map_with_reported_flags(start, size, flags, flags, populate, backend)
3569 }
3570
3571 pub(crate) fn map_outcome(
3578 &mut self,
3579 start: VirtAddr,
3580 size: usize,
3581 flags: MappingFlags,
3582 populate: bool,
3583 backend: MappingOperation,
3584 ) -> StarryResult<AddressSpaceMutationOutcome> {
3585 self.map_with_permissions_mode_classified(
3586 start,
3587 size,
3588 MappingPermissions {
3589 current: flags,
3590 reported: flags,
3591 maximum: flags,
3592 },
3593 populate,
3594 backend,
3595 MappingPublication::new(false),
3596 )
3597 }
3598
3599 pub fn map_with_reported_flags(
3600 &mut self,
3601 start: VirtAddr,
3602 size: usize,
3603 flags: MappingFlags,
3604 reported_flags: MappingFlags,
3605 populate: bool,
3606 backend: MappingOperation,
3607 ) -> StarryResult {
3608 self.map_with_permissions(
3609 start,
3610 size,
3611 MappingPermissions {
3612 current: flags,
3613 reported: reported_flags,
3614 maximum: flags,
3615 },
3616 populate,
3617 backend,
3618 )
3619 }
3620
3621 pub fn map_with_permissions_replace(
3628 &mut self,
3629 start: VirtAddr,
3630 size: usize,
3631 permissions: MappingPermissions,
3632 populate: bool,
3633 backend: MappingOperation,
3634 replace: bool,
3635 ) -> StarryResult {
3636 self.map_with_permissions_mode(
3637 start,
3638 size,
3639 permissions,
3640 populate,
3641 backend,
3642 MappingPublication::new(replace),
3643 )
3644 }
3645
3646 pub(crate) fn map_with_permissions_publication(
3650 &mut self,
3651 start: VirtAddr,
3652 size: usize,
3653 permissions: MappingPermissions,
3654 populate: bool,
3655 backend: MappingOperation,
3656 publication: MappingPublication,
3657 ) -> StarryResult {
3658 self.map_with_permissions_mode(start, size, permissions, populate, backend, publication)
3659 }
3660
3661 pub(crate) fn map_mremap_duplicate(
3665 &mut self,
3666 start: VirtAddr,
3667 size: usize,
3668 permissions: MappingPermissions,
3669 backend: MappingOperation,
3670 publication: MappingPublication,
3671 ) -> StarryResult {
3672 self.map_with_permissions_mode(start, size, permissions, false, backend, publication)
3673 }
3674
3675 pub fn map_with_permissions(
3680 &mut self,
3681 start: VirtAddr,
3682 size: usize,
3683 permissions: MappingPermissions,
3684 populate: bool,
3685 backend: MappingOperation,
3686 ) -> StarryResult {
3687 self.map_with_permissions_mode(
3688 start,
3689 size,
3690 permissions,
3691 populate,
3692 backend,
3693 MappingPublication::new(false),
3694 )
3695 }
3696
3697 fn map_with_permissions_mode(
3698 &mut self,
3699 start: VirtAddr,
3700 size: usize,
3701 permissions: MappingPermissions,
3702 populate: bool,
3703 backend: MappingOperation,
3704 publication: MappingPublication,
3705 ) -> StarryResult {
3706 self.map_with_permissions_mode_classified(
3707 start,
3708 size,
3709 permissions,
3710 populate,
3711 backend,
3712 publication,
3713 )?
3714 .into_result()
3715 }
3716
3717 fn map_with_permissions_mode_classified(
3718 &mut self,
3719 start: VirtAddr,
3720 size: usize,
3721 permissions: MappingPermissions,
3722 populate: bool,
3723 backend: MappingOperation,
3724 publication: MappingPublication,
3725 ) -> StarryResult<AddressSpaceMutationOutcome> {
3726 let MappingPublication {
3727 replace,
3728 huge_page_advice,
3729 lock_mode,
3730 advice_policy,
3731 memlock_limit,
3732 } = publication;
3733 self.validate_region(start, size)?;
3734 if !permissions.maximum.contains(permissions.current) {
3735 return Err(StarryError::PermissionDenied);
3736 }
3737 let range =
3738 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
3739 let mut mutation = prepare_mapping_publication_mutation(
3740 &self.mutation_gate,
3741 self.id,
3742 &self.tlb_targets,
3743 start,
3744 size,
3745 replace,
3746 );
3747 self.mutation_gate
3748 .validate_publish_preconditions(&mutation)
3749 .map_err(Self::map_unpublished_mutation_error)?;
3750 let removed_pages = if replace {
3753 self.vma_root
3754 .iter_entries()
3755 .filter(|entry| entry.start() < range.end && entry.end() > range.start)
3756 .try_fold(0u64, |pages, entry| {
3757 let lo = entry.start().max(range.start);
3758 let hi = entry.end().min(range.end);
3759 let fragment = hi.checked_sub_addr(lo).ok_or(StarryError::InvalidInput)?;
3760 pages
3761 .checked_add((fragment / PAGE_SIZE_4K) as u64)
3762 .ok_or(StarryError::InvalidInput)
3763 })?
3764 } else {
3765 0
3766 };
3767 let mapping_preimage = self.capture_mapping_preimage(range)?;
3768 let graph_preimage = self.capture_mapping_graph_snapshot(&[range])?;
3769 let retire_epoch = mutation
3770 .receipt()
3771 .base_epoch
3772 .checked_next()
3773 .ok_or(StarryError::BadState)?;
3774 let retired_owners = replace
3775 .then(|| self.prepare_retired_mapping_owners(range))
3776 .transpose()?;
3777
3778 let touched_memfds = if replace {
3783 crate::syscall::memfd_collect_metas_touching_mprotect_range(self, start, size)
3784 } else {
3785 Vec::new()
3786 };
3787
3788 let map_materialization = match self.apply_mapping_unpublished(
3789 range,
3790 permissions,
3791 &backend,
3792 huge_page_advice,
3793 lock_mode,
3794 advice_policy,
3795 memlock_limit,
3796 replace,
3797 ) {
3798 Ok(materialization) => materialization,
3799 Err(error) => {
3800 return self
3801 .abort_unpublished_mapping_mutation(range, mapping_preimage, error)
3802 .map(|()| AddressSpaceMutationOutcome::Complete);
3803 }
3804 };
3805 if let Some(owners) = retired_owners {
3806 self.park_retired_mapping_owners(retire_epoch, owners);
3807 }
3808 if removed_pages != 0
3809 && let Err(error) = self.detach_mapping_slots(range)
3810 {
3811 return self
3812 .abort_unpublished_parked_mapping_mutation(
3813 range,
3814 mapping_preimage,
3815 replace.then_some(retire_epoch),
3816 error,
3817 )
3818 .map(|()| AddressSpaceMutationOutcome::Complete);
3819 }
3820 if let Err(error) = self.publish_prepared_pte_owners(&backend, range, &map_materialization)
3821 {
3822 return self
3823 .abort_unpublished_parked_mapping_mutation(
3824 range,
3825 mapping_preimage,
3826 replace.then_some(retire_epoch),
3827 error,
3828 )
3829 .map(|()| AddressSpaceMutationOutcome::Complete);
3830 }
3831 if populate
3832 && let Err(populate_error) = self.apply_populate_area(start, size, permissions.current)
3833 {
3834 return self
3835 .abort_unpublished_parked_mapping_mutation(
3836 range,
3837 mapping_preimage,
3838 replace.then_some(retire_epoch),
3839 populate_error,
3840 )
3841 .map(|()| AddressSpaceMutationOutcome::Complete);
3842 }
3843 mutation.set_vma_delta(VmaDelta {
3844 inserted: 1,
3845 removed: u32::try_from(removed_pages).unwrap_or(u32::MAX),
3846 ..VmaDelta::default()
3847 });
3848 self.vm_stat.on_map((size / PAGE_SIZE_4K) as u64);
3849 if removed_pages != 0 {
3850 self.vm_stat.on_unmap(removed_pages);
3851 }
3852 if let Err(error) =
3853 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[range])
3854 {
3855 return self
3856 .abort_unpublished_parked_mapping_mutation(
3857 range,
3858 mapping_preimage,
3859 replace.then_some(retire_epoch),
3860 error,
3861 )
3862 .map(|()| AddressSpaceMutationOutcome::Complete);
3863 }
3864 match self.commit_mutation_classified(mutation) {
3865 Ok(()) => {
3866 if replace {
3867 self.release_retired_mapping_owners(retire_epoch);
3868 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
3869 self,
3870 &touched_memfds,
3871 );
3872 } else {
3873 crate::syscall::memfd_on_after_map(self, start);
3874 }
3875 Ok(AddressSpaceMutationOutcome::Complete)
3876 }
3877 Err(CommitMutationError::PublishedPendingTlb(error)) => {
3878 if replace {
3879 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
3880 self,
3881 &touched_memfds,
3882 );
3883 } else {
3884 crate::syscall::memfd_on_after_map(self, start);
3885 }
3886 Ok(AddressSpaceMutationOutcome::PublishedPendingTlb(error))
3887 }
3888 Err(CommitMutationError::Unpublished(error)) => self
3889 .abort_unpublished_parked_mapping_mutation(
3890 range,
3891 mapping_preimage,
3892 replace.then_some(retire_epoch),
3893 error,
3894 )
3895 .map(|()| AddressSpaceMutationOutcome::Complete),
3896 }
3897 }
3898
3899 fn apply_populate_area(
3903 &mut self,
3904 mut start: VirtAddr,
3905 size: usize,
3906 access_flags: MappingFlags,
3907 ) -> StarryResult<usize> {
3908 self.validate_region(start, size)?;
3909 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
3910 let mut populated = 0usize;
3911
3912 loop {
3913 let area_end = {
3914 let Some(entry) = self.vma_root.lookup_entry(start) else {
3915 break;
3916 };
3917 let entry_end = entry.end();
3918 let range = VirtAddrRange::new(start, entry_end.min(end));
3919 let flags = entry.rights();
3920 let backend = entry.operation_clone();
3921 let request = PopulateRequest::area(range, backend.page_size())?;
3922 let materialization =
3923 backend.populate(self.id, request, flags, access_flags, &mut self.pt)?;
3924 let publication =
3925 self.publish_prepared_pte_owners(&backend, range, &materialization)?;
3926 populated = populated
3927 .checked_add(publication.satisfied_pages)
3928 .ok_or(StarryError::NoMemory)?;
3929 entry_end
3930 };
3931 start = area_end;
3932 assert!(start.is_aligned_4k());
3933 if start >= end {
3934 break;
3935 }
3936 }
3937
3938 if start < end {
3939 return Err(StarryError::NoMemory);
3941 }
3942
3943 Ok(populated)
3944 }
3945
3946 fn materialized_range_satisfies_access(
3955 &self,
3956 range: VirtAddrRange,
3957 access_flags: MappingFlags,
3958 ) -> bool {
3959 let required = access_flags | MappingFlags::USER;
3960 let mut cursor = range.start;
3961 while cursor < range.end {
3962 let Ok((_, flags, leaf_size)) = self.pt.query(cursor) else {
3963 return false;
3964 };
3965 if leaf_size < PAGE_SIZE_4K || !leaf_size.is_power_of_two() || !flags.contains(required)
3966 {
3967 return false;
3968 }
3969 let Some(leaf_end) = cursor.align_down(leaf_size).checked_add(leaf_size) else {
3970 return false;
3971 };
3972 if leaf_end <= cursor {
3973 return false;
3974 }
3975 cursor = leaf_end.min(range.end);
3976 }
3977 true
3978 }
3979
3980 pub fn populate_area(
3982 &mut self,
3983 start: VirtAddr,
3984 size: usize,
3985 access_flags: MappingFlags,
3986 ) -> StarryResult {
3987 let range =
3988 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
3989 self.validate_region(start, size)?;
3990 if self.can_access_range(start, size, access_flags)
3991 && self.materialized_range_satisfies_access(range, access_flags)
3992 {
3993 return Ok(());
3994 }
3995 let preimage = self.capture_mapping_preimage(range)?;
3996 let graph_preimage = self.capture_mapping_graph_snapshot(&[range])?;
3997 let mut mutation = self.prepare_mutation_range(start, size);
3998 let retire_epoch = mutation
3999 .receipt()
4000 .base_epoch
4001 .checked_next()
4002 .ok_or(StarryError::BadState)?;
4003 let retired_owners = (!graph_preimage.slots.is_empty())
4004 .then(|| self.prepare_retired_mapping_owners(range))
4005 .transpose()?;
4006 let populated = match self.apply_populate_area(start, size, access_flags) {
4007 Ok(populated) => populated,
4008 Err(populate_error) => {
4009 if self.restore_mapping_preimage(range, preimage).is_err() {
4010 return Err(StarryError::BadState);
4011 }
4012 return Err(populate_error);
4013 }
4014 };
4015
4016 mutation.set_pte_delta(PteDelta {
4017 mapped: u32::try_from(populated).unwrap_or(u32::MAX),
4018 ..PteDelta::default()
4019 });
4020 if let Err(error) =
4021 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[range])
4022 {
4023 return self.abort_unpublished_mapping_mutation(range, preimage, error);
4024 }
4025 if let Some(owners) = retired_owners {
4026 self.park_retired_mapping_owners(retire_epoch, owners);
4027 }
4028 match self.commit_mutation_classified(mutation) {
4029 Ok(()) => {
4030 self.release_retired_mapping_owners(retire_epoch);
4031 Ok(())
4032 }
4033 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
4034 Err(CommitMutationError::Unpublished(error)) => self
4035 .abort_unpublished_parked_mapping_mutation(
4036 range,
4037 preimage,
4038 Some(retire_epoch),
4039 error,
4040 ),
4041 }
4042 }
4043
4044 pub fn discard_range(&mut self, start: VirtAddr, size: usize) -> StarryResult {
4047 self.validate_region(start, size)?;
4048 let retired_range = VirtAddrRange::from_start_size(start, size);
4049 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
4050
4051 let mut frags: alloc::vec::Vec<(VirtAddrRange, MappingOperation)> = alloc::vec::Vec::new();
4052 frags
4053 .try_reserve(self.vma_root.len())
4054 .map_err(|_| StarryError::NoMemory)?;
4055 let mut covered = start;
4056 for entry in self.vma_root.iter_entries() {
4057 if entry.start() >= end {
4058 break;
4059 }
4060 if entry.end() <= start {
4061 continue;
4062 }
4063 let frag_start = entry.start().max(start);
4064 let frag_end = entry.end().min(end);
4065 if frag_start > covered {
4066 return Err(StarryError::NoMemory);
4067 }
4068 let backend = entry.operation_clone();
4069 backend.validate_discard_fragment(VirtAddrRange::new(frag_start, frag_end))?;
4073 frags.push((VirtAddrRange::new(frag_start, frag_end), backend));
4074 covered = frag_end;
4075 }
4076 if covered < end {
4077 return Err(StarryError::NoMemory);
4078 }
4079
4080 let mut mutation = self.prepare_mutation_range(start, size);
4081 mutation
4082 .try_reserve_tlb_ranges(2)
4083 .map_err(|_| StarryError::NoMemory)?;
4084 let retire_epoch = mutation
4085 .receipt()
4086 .base_epoch
4087 .checked_next()
4088 .ok_or(StarryError::BadState)?;
4089 let splits = self.apply_partial_huge_splits(retired_range)?;
4090 for index in 0..splits.len() {
4091 let split = &splits[index];
4092 let Some(tlb_range) =
4093 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
4094 else {
4095 return self.abort_unpublished_huge_splits(splits, StarryError::BadState);
4096 };
4097 mutation.add_tlb_range(tlb_range);
4098 }
4099 if frags
4100 .iter()
4101 .any(|(range, backend)| !backend.validate_unmap_range(*range, &self.pt))
4102 {
4103 return self.abort_unpublished_huge_splits(splits, StarryError::OperationNotSupported);
4104 }
4105
4106 let preimage = match self.capture_mapping_preimage(retired_range) {
4107 Ok(preimage) => preimage,
4108 Err(error) => return self.abort_unpublished_huge_splits(splits, error),
4109 };
4110 let retired_owners = match self.prepare_retired_mapping_owners(retired_range) {
4111 Ok(owners) => owners,
4112 Err(error) => {
4113 return self.abort_unpublished_split_mapping_mutation(
4114 retired_range,
4115 preimage,
4116 None,
4117 splits,
4118 error,
4119 );
4120 }
4121 };
4122 let Ok((detached_slots, retired_pages, retired_resident)) =
4123 self.mapping_slot_summary(retired_range)
4124 else {
4125 return self.abort_unpublished_split_mapping_mutation(
4126 retired_range,
4127 preimage,
4128 None,
4129 splits,
4130 StarryError::BadState,
4131 );
4132 };
4133 let split_slots = splits.iter().try_fold(0usize, |slots, split| {
4134 slots.checked_add(split.child_slots.len().saturating_sub(1))
4135 });
4136 let Some(split_slots) = split_slots else {
4137 return self.abort_unpublished_split_mapping_mutation(
4138 retired_range,
4139 preimage,
4140 None,
4141 splits,
4142 StarryError::BadState,
4143 );
4144 };
4145
4146 let deferred_tlb = DeferredTlbRetireGuard::enter();
4147 for (range, backend) in frags {
4148 if let Err(error) = backend.unmap_range(range, &mut self.pt) {
4149 drop(deferred_tlb);
4150 if let Err(flush_error) = crate::mm::flush_tlb_range_sync(start, size) {
4151 warn!("discard repair could not invalidate {start:?}+{size:#x}: {flush_error}");
4152 }
4153 return self.abort_unpublished_split_mapping_mutation(
4154 retired_range,
4155 preimage,
4156 None,
4157 splits,
4158 error,
4159 );
4160 }
4161 }
4162 drop(deferred_tlb);
4163 if let Err(error) = self.detach_mapping_slots(retired_range) {
4164 return self.abort_unpublished_split_mapping_mutation(
4165 retired_range,
4166 preimage,
4167 None,
4168 splits,
4169 error,
4170 );
4171 }
4172 self.park_retired_mapping_owners(retire_epoch, retired_owners);
4173 mutation.set_pte_delta(PteDelta {
4174 unmapped: u32::try_from(retired_pages).unwrap_or(u32::MAX),
4175 ..PteDelta::default()
4176 });
4177 mutation.set_mapping_delta(MappingDelta {
4178 attached: u32::try_from(split_slots).unwrap_or(u32::MAX),
4179 detached: u32::try_from(detached_slots).unwrap_or(u32::MAX),
4180 });
4181 mutation.set_resident_delta(retired_resident.checked_negated_delta()?);
4182
4183 match self.commit_mutation_classified(mutation) {
4184 Ok(()) => {
4185 self.release_retired_mapping_owners(retire_epoch);
4186 Ok(())
4187 }
4188 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
4189 Err(CommitMutationError::Unpublished(error)) => self
4190 .abort_unpublished_split_mapping_mutation(
4191 retired_range,
4192 preimage,
4193 Some(retire_epoch),
4194 splits,
4195 error,
4196 ),
4197 }
4198 }
4199
4200 pub fn mark_lazy_free(&mut self, start: VirtAddr, size: usize) -> StarryResult {
4208 self.validate_region(start, size)?;
4209 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
4210 let mut covered = start;
4211 for entry in self.vma_root.iter_entries() {
4212 if entry.start() >= end {
4213 break;
4214 }
4215 if entry.end() <= start {
4216 continue;
4217 }
4218 let fragment_start = entry.start().max(start);
4219 if fragment_start > covered {
4220 return Err(StarryError::NoMemory);
4221 }
4222 if !entry.operation().is_private_anonymous() {
4223 return Err(StarryError::InvalidInput);
4224 }
4225 covered = entry.end().min(end);
4226 }
4227 if covered < end {
4228 return Err(StarryError::NoMemory);
4229 }
4230
4231 let range = VirtAddrRange::from_start_size(start, size);
4232 let mut candidates = Vec::new();
4233 candidates
4234 .try_reserve(self.mapping_slots_overlapping(range).count())
4235 .map_err(|_| StarryError::NoMemory)?;
4236 for (_, slot) in self.mapping_slots_overlapping(range) {
4237 if slot.page_order != PageOrder::BASE || slot.page.mapping_refs() != 1 {
4238 continue;
4239 }
4240 match slot.page.state() {
4241 PageState::LazyFree => continue,
4242 PageState::Present => {}
4243 PageState::Reserved
4244 | PageState::Evicting
4245 | PageState::Writeback
4246 | PageState::Retired => return Err(StarryError::ResourceBusy),
4247 }
4248 let (paddr, flags, page_size) = self.pt.query(slot.va)?;
4249 if slot.mapped_paddr() != Some(paddr) || page_size != PAGE_SIZE_4K {
4250 return Err(StarryError::BadState);
4251 }
4252 candidates.push((slot.va, paddr, flags, slot.page.clone()));
4253 }
4254 if candidates.is_empty() {
4255 return Ok(());
4256 }
4257
4258 let mut mutation = self.prepare_mutation_range(start, size);
4259 let pt = &mut self.pt;
4260 let stripes = self.pte_domain.lock_range(range);
4261 let mut protected = 0usize;
4262 for &(va, paddr, flags, _) in &candidates {
4263 if !flags.contains(MappingFlags::WRITE) {
4264 continue;
4265 }
4266 if pt
4269 .remap_page(va, paddr, flags - MappingFlags::WRITE)
4270 .is_err()
4271 {
4272 for &(old_va, old_paddr, old_flags, _) in candidates.iter().rev() {
4273 if old_flags.contains(MappingFlags::WRITE) {
4274 let _ = pt.remap_page(old_va, old_paddr, old_flags);
4276 }
4277 }
4278 return Err(StarryError::BadState);
4279 }
4280 protected += 1;
4281 }
4282 drop(stripes);
4283
4284 for (marked, (_, _, _, page)) in candidates.iter().enumerate() {
4285 if !page.mark_lazy_free() {
4286 for (_, _, _, marked_page) in candidates[..marked].iter().rev() {
4287 let _ = marked_page.clear_lazy_free();
4288 }
4289 let pt = &mut self.pt;
4290 let _stripes = self.pte_domain.lock_range(range);
4291 for &(va, paddr, flags, _) in &candidates {
4292 if flags.contains(MappingFlags::WRITE) {
4293 let _ = pt.remap_page(va, paddr, flags);
4295 }
4296 }
4297 return Err(StarryError::ResourceBusy);
4298 }
4299 }
4300
4301 mutation.set_pte_delta(PteDelta {
4302 protected: u32::try_from(protected).unwrap_or(u32::MAX),
4303 ..PteDelta::default()
4304 });
4305 match self.commit_mutation_classified(mutation) {
4306 Ok(()) => {
4307 lifecycle::request_lazy_free_reclaim();
4308 Ok(())
4309 }
4310 Err(CommitMutationError::PublishedPendingTlb(error)) => {
4311 lifecycle::request_lazy_free_reclaim();
4314 Err(error)
4315 }
4316 Err(CommitMutationError::Unpublished(error)) => {
4317 for (_, _, _, page) in candidates.iter().rev() {
4318 if !page.clear_lazy_free() {
4319 self.mutation_gate.mark_needs_repair();
4320 return Err(StarryError::BadState);
4321 }
4322 }
4323 let pt = &mut self.pt;
4324 let _stripes = self.pte_domain.lock_range(range);
4325 for &(va, paddr, flags, _) in &candidates {
4326 if flags.contains(MappingFlags::WRITE)
4327 && pt.remap_page(va, paddr, flags).is_err()
4328 {
4329 self.mutation_gate.mark_needs_repair();
4330 return Err(StarryError::BadState);
4331 }
4332 }
4333 Err(error)
4334 }
4335 }
4336 }
4337
4338 pub(crate) fn reclaim_lazy_free_pages(&mut self, limit: usize) -> StarryResult<usize> {
4343 if limit == 0 {
4344 return Ok(0);
4345 }
4346 let mut reclaimed = 0;
4347 let mut cursor = None;
4348 while reclaimed < limit {
4349 let eligible = |(key, slot): (&MappingSlotKey, &Arc<MappingSlot>)| {
4350 (slot.page_order == PageOrder::BASE
4351 && slot.page.state() == PageState::LazyFree
4352 && slot.page.mapping_refs() == 1)
4353 .then(|| (*key, slot.page.clone()))
4354 };
4355 let candidate = if let Some(after) = cursor {
4356 self.mapping_slots
4357 .range((
4358 core::ops::Bound::Excluded(after),
4359 core::ops::Bound::Unbounded,
4360 ))
4361 .find_map(eligible)
4362 } else {
4363 self.mapping_slots.iter().find_map(eligible)
4364 };
4365 let Some((key, page)) = candidate else {
4366 break;
4367 };
4368 cursor = Some(key);
4369 let Some(entry) = self.vma_root.lookup_entry(key.va) else {
4370 return Err(StarryError::BadState);
4371 };
4372 if !entry.operation().is_private_anonymous() {
4373 return Err(StarryError::BadState);
4374 }
4375 let still_reclaimable = self.mapping_slots.get(&key).is_some_and(|slot| {
4376 Arc::ptr_eq(&slot.page, &page)
4377 && page.state() == PageState::LazyFree
4378 && page.mapping_refs() == 1
4379 });
4380 if !still_reclaimable {
4381 continue;
4382 }
4383 self.discard_range(key.va, PAGE_SIZE_4K)?;
4384 if !page.rmap.is_empty()
4385 || page.mapping_refs() != 0
4386 || !page.transition(PageState::LazyFree, PageState::Retired)
4387 {
4388 self.mutation_gate.mark_needs_repair();
4389 return Err(StarryError::BadState);
4390 }
4391 reclaimed += 1;
4392 }
4393 Ok(reclaimed)
4394 }
4395
4396 pub fn unmap(&mut self, start: VirtAddr, size: usize) -> StarryResult {
4401 self.unmap_classified(start, size)?.into_result()
4402 }
4403
4404 pub(crate) fn unmap_outcome(
4409 &mut self,
4410 start: VirtAddr,
4411 size: usize,
4412 ) -> StarryResult<AddressSpaceMutationOutcome> {
4413 self.unmap_classified(start, size)
4414 }
4415
4416 fn unmap_classified(
4417 &mut self,
4418 start: VirtAddr,
4419 size: usize,
4420 ) -> StarryResult<AddressSpaceMutationOutcome> {
4421 self.validate_region(start, size)?;
4422 let range = VirtAddrRange::from_start_size(start, size);
4423 let before_vmas = self.vma_root.len();
4424 let memfd_deltas = crate::syscall::memfd_prepare_aspace_unmap_deltas(self, start, size);
4428 let mut mutation = self.prepare_mutation_range(start, size);
4429 let retire_epoch = mutation
4430 .receipt()
4431 .base_epoch
4432 .checked_next()
4433 .ok_or(StarryError::BadState)?;
4434 mutation
4435 .try_reserve_tlb_ranges(2)
4436 .map_err(|_| StarryError::NoMemory)?;
4437 let splits = self.apply_partial_huge_splits(range)?;
4438 for index in 0..splits.len() {
4439 let split = &splits[index];
4440 let Some(tlb_range) =
4441 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
4442 else {
4443 return self
4444 .abort_unpublished_huge_splits(splits, StarryError::BadState)
4445 .map(|()| AddressSpaceMutationOutcome::Complete);
4446 };
4447 mutation.add_tlb_range(tlb_range);
4448 }
4449 let preimage = match self.capture_mapping_preimage(range) {
4450 Ok(preimage) => preimage,
4451 Err(error) => {
4452 return self
4453 .abort_unpublished_huge_splits(splits, error)
4454 .map(|()| AddressSpaceMutationOutcome::Complete);
4455 }
4456 };
4457 let retired_owners = match self.prepare_retired_mapping_owners(range) {
4458 Ok(owners) => owners,
4459 Err(error) => {
4460 return self
4461 .abort_unpublished_huge_splits(splits, error)
4462 .map(|()| AddressSpaceMutationOutcome::Complete);
4463 }
4464 };
4465 let Ok((detached_slots, detached_resident_pages, detached_resident)) =
4466 self.mapping_slot_summary(range)
4467 else {
4468 return self
4469 .abort_unpublished_huge_splits(splits, StarryError::BadState)
4470 .map(|()| AddressSpaceMutationOutcome::Complete);
4471 };
4472 let split_slots = splits.iter().try_fold(0usize, |slots, split| {
4473 slots.checked_add(split.child_slots.len().saturating_sub(1))
4474 });
4475 let Some(split_slots) = split_slots else {
4476 return self
4477 .abort_unpublished_huge_splits(splits, StarryError::BadState)
4478 .map(|()| AddressSpaceMutationOutcome::Complete);
4479 };
4480
4481 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
4483 let removed_pages: u64 = self
4484 .vma_root
4485 .iter_entries()
4486 .filter(|entry| entry.start() < end && entry.end() > start)
4487 .map(|entry| {
4488 let lo = entry.start().max(start);
4489 let hi = entry.end().min(end);
4490 ((hi - lo) / PAGE_SIZE_4K) as u64
4491 })
4492 .sum();
4493
4494 let deferred_tlb = DeferredTlbRetireGuard::enter();
4495 if let Err(error) = self.apply_unmap_unpublished(range) {
4496 drop(deferred_tlb);
4497 if let Err(flush_error) = crate::mm::flush_tlb_range_sync(start, size) {
4498 warn!("unmap repair could not invalidate {start:?}+{size:#x}: {flush_error}");
4499 }
4500 return self
4501 .abort_unpublished_split_mapping_mutation(range, preimage, None, splits, error)
4502 .map(|()| AddressSpaceMutationOutcome::Complete);
4503 }
4504 drop(deferred_tlb);
4505 if let Err(error) = self.detach_mapping_slots(range) {
4506 return self
4507 .abort_unpublished_split_mapping_mutation(range, preimage, None, splits, error)
4508 .map(|()| AddressSpaceMutationOutcome::Complete);
4509 }
4510 self.park_retired_mapping_owners(retire_epoch, retired_owners);
4511 mutation.set_pte_delta(PteDelta {
4512 unmapped: u32::try_from(detached_resident_pages).unwrap_or(u32::MAX),
4513 ..PteDelta::default()
4514 });
4515 mutation.set_mapping_delta(MappingDelta {
4516 attached: u32::try_from(split_slots).unwrap_or(u32::MAX),
4517 detached: u32::try_from(detached_slots).unwrap_or(u32::MAX),
4518 });
4519 mutation.set_resident_delta(detached_resident.checked_negated_delta()?);
4520 self.vm_stat.on_unmap(removed_pages);
4521 let after_vmas = self.vma_root.len();
4522 mutation.set_vma_delta(VmaDelta {
4523 removed: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
4524 split: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
4525 ..VmaDelta::default()
4526 });
4527 match self.commit_mutation_classified(mutation) {
4528 Ok(()) => {
4529 self.release_retired_mapping_owners(retire_epoch);
4530 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
4531 Ok(AddressSpaceMutationOutcome::Complete)
4532 }
4533 Err(CommitMutationError::PublishedPendingTlb(error)) => {
4534 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
4535 Ok(AddressSpaceMutationOutcome::PublishedPendingTlb(error))
4536 }
4537 Err(CommitMutationError::Unpublished(error)) => self
4538 .abort_unpublished_split_mapping_mutation(
4539 range,
4540 preimage,
4541 Some(retire_epoch),
4542 splits,
4543 error,
4544 )
4545 .map(|()| AddressSpaceMutationOutcome::Complete),
4546 }
4547 }
4548
4549 fn prepare_moved_slots(
4550 &mut self,
4551 moved_pages: &[MovedPage],
4552 target_mapping: MappingId,
4553 ) -> StarryResult<Vec<PreparedMovedSlot>> {
4554 let mut prepared = Vec::new();
4555 prepared
4556 .try_reserve(moved_pages.len())
4557 .map_err(|_| StarryError::NoMemory)?;
4558
4559 for moved in moved_pages {
4560 let source_key = MappingSlotKey {
4561 space_id: self.id,
4562 va: moved.src_va,
4563 };
4564 let target_slot_va = match moved.destination {
4565 MovedPageDestination::SourceOwner => moved.dst_va,
4566 MovedPageDestination::TargetOwner { slot_va } => slot_va,
4567 };
4568 let target_key = MappingSlotKey {
4569 space_id: self.id,
4570 va: target_slot_va,
4571 };
4572 if matches!(moved.destination, MovedPageDestination::SourceOwner)
4573 && prepared.iter().any(|entry| {
4574 matches!(
4575 entry,
4576 PreparedMovedSlot::Relocate {
4577 target_key: existing,
4578 ..
4579 } if *existing == target_key
4580 )
4581 })
4582 {
4583 return Err(StarryError::BadState);
4584 }
4585 let source = self
4586 .mapping_slots
4587 .get(&source_key)
4588 .cloned()
4589 .ok_or(StarryError::BadState)?;
4590 let page_order = moved
4591 .page_size
4592 .trailing_zeros()
4593 .checked_sub(PAGE_SIZE_4K.trailing_zeros())
4594 .and_then(|order| u8::try_from(order).ok())
4595 .map(PageOrder::new)
4596 .ok_or(StarryError::BadState)?;
4597 let frame_start = source.page.frame().paddr().as_usize();
4598 let frame_end = frame_start
4599 .checked_add(source.page.frame().size())
4600 .ok_or(StarryError::BadState)?;
4601 let leaf_start = moved.paddr.as_usize();
4602 let leaf_end = leaf_start
4603 .checked_add(moved.page_size)
4604 .ok_or(StarryError::BadState)?;
4605 if source.state() != SlotState::Present
4606 || source.mm_id != self.id
4607 || source.va != moved.src_va
4608 || source.page_order != page_order
4609 || source.mapped_paddr() != Some(moved.paddr)
4610 || leaf_start < frame_start
4611 || leaf_end > frame_end
4612 || (page_order != PageOrder::BASE && !source.has_huge_split_deposit())
4613 {
4614 return Err(StarryError::BadState);
4615 }
4616
4617 match moved.destination {
4618 MovedPageDestination::SourceOwner => {
4619 let replacement = MappingSlot::new_with_frame_offset(
4620 target_mapping,
4621 self.id,
4622 moved.dst_va,
4623 page_order,
4624 source.page.clone(),
4625 source.frame_offset(),
4626 source.resident_kind(),
4627 )
4628 .ok_or(StarryError::BadState)?;
4629 let replacement = if page_order == PageOrder::BASE {
4630 replacement
4631 } else {
4632 replacement
4633 .attach_huge_split_deposit(self.pt.prepare_huge_split(moved.dst_va)?)
4634 .map_err(|_| StarryError::BadState)?
4635 };
4636 prepared.push(PreparedMovedSlot::Relocate {
4637 source_key,
4638 target_key,
4639 source,
4640 replacement: Arc::new(replacement),
4641 });
4642 }
4643 MovedPageDestination::TargetOwner { .. } => {
4644 prepared.push(PreparedMovedSlot::DetachSource {
4645 source_key,
4646 target_key,
4647 source,
4648 });
4649 }
4650 }
4651 }
4652 Ok(prepared)
4653 }
4654
4655 fn publish_moved_slots(&mut self, prepared: Vec<PreparedMovedSlot>) -> StarryResult {
4656 for entry in prepared {
4657 match entry {
4658 PreparedMovedSlot::Relocate {
4659 source_key,
4660 target_key,
4661 source,
4662 replacement,
4663 } => {
4664 if self.mapping_slots.contains_key(&target_key) {
4665 return Err(StarryError::BadState);
4666 }
4667 let removed = self
4668 .mapping_slots
4669 .remove(&source_key)
4670 .ok_or(StarryError::BadState)?;
4671 if !Arc::ptr_eq(&removed, &source) {
4672 self.mapping_slots.insert(source_key, removed);
4673 return Err(StarryError::BadState);
4674 }
4675 if let Err(error) = source.relocate_to(&replacement) {
4676 let restored = source.state() == SlotState::Present
4677 && self
4678 .mapping_slots
4679 .insert(source_key, source.clone())
4680 .is_none();
4681 if !restored || error == MappingGraphError::RollbackFailed {
4682 self.mutation_gate.mark_needs_repair();
4683 }
4684 return Err(match error {
4685 MappingGraphError::ResourceExhausted => StarryError::NoMemory,
4686 _ => StarryError::BadState,
4687 });
4688 }
4689 if self.mapping_slots.insert(target_key, replacement).is_some() {
4690 self.mutation_gate.mark_needs_repair();
4693 return Err(StarryError::BadState);
4694 }
4695 }
4696 PreparedMovedSlot::DetachSource {
4697 source_key,
4698 target_key,
4699 source,
4700 } => {
4701 let target = self
4702 .mapping_slots
4703 .get(&target_key)
4704 .ok_or(StarryError::BadState)?;
4705 if target.state() != SlotState::Present {
4706 return Err(StarryError::BadState);
4707 }
4708 let removed = self
4709 .mapping_slots
4710 .remove(&source_key)
4711 .ok_or(StarryError::BadState)?;
4712 if !Arc::ptr_eq(&removed, &source) || !removed.detach() {
4713 self.mapping_slots.insert(source_key, removed);
4714 return Err(StarryError::BadState);
4715 }
4716 }
4717 }
4718 }
4719 Ok(())
4720 }
4721
4722 fn apply_move_pages(
4727 &mut self,
4728 src: VirtAddr,
4729 dst: VirtAddr,
4730 size: usize,
4731 ) -> StarryResult<Vec<MovedPage>> {
4732 let move_range =
4733 VirtAddrRange::try_from_start_size(src, size).ok_or(StarryError::InvalidInput)?;
4734 let dst_range =
4735 VirtAddrRange::try_from_start_size(dst, size).ok_or(StarryError::InvalidInput)?;
4736 if move_range.overlaps(dst_range) {
4737 return Err(StarryError::InvalidInput);
4743 }
4744 self.validate_materialized_leaf_boundaries(src, size)?;
4745 let source_slots = self.materialized_slots_overlapping(&[move_range])?;
4746 let mut mapped_pages = alloc::vec::Vec::new();
4747 mapped_pages
4748 .try_reserve(source_slots.len())
4749 .map_err(|_| StarryError::NoMemory)?;
4750 for (key, slot, occupied_leaf) in source_slots {
4751 let offset = key.va.checked_sub_addr(src).ok_or(StarryError::BadState)?;
4752 let dst_va = dst.checked_add(offset).ok_or(StarryError::InvalidInput)?;
4753 let paddr = occupied_leaf.paddr;
4754 let flags = occupied_leaf.flags;
4755 let page_size = occupied_leaf.range.size();
4756 let expected_size = PAGE_SIZE_4K
4757 .checked_shl(slot.page_order.get().into())
4758 .ok_or(StarryError::BadState)?;
4759 if slot.state() != SlotState::Present
4760 || slot.va != key.va
4761 || slot.mm_id != self.id
4762 || slot.mapped_paddr() != Some(paddr)
4763 || page_size != expected_size
4764 {
4765 return Err(StarryError::BadState);
4766 }
4767 if !key.va.is_aligned(page_size) || !dst_va.is_aligned(page_size) {
4768 return Err(StarryError::OperationNotSupported);
4769 }
4770 mapped_pages.push((key.va, dst_va, paddr, flags, page_size));
4771 }
4772
4773 let mut moved_pages = alloc::vec::Vec::new();
4774 moved_pages
4775 .try_reserve(mapped_pages.len())
4776 .map_err(|_| StarryError::NoMemory)?;
4777 let mut move_plans = Vec::<PageTableMovePlan>::new();
4778 move_plans
4779 .try_reserve(mapped_pages.len())
4780 .map_err(|_| StarryError::NoMemory)?;
4781
4782 for &(_, dst_va, _, _, page_size) in &mapped_pages {
4787 match self.pt.query_occupied(dst_va) {
4788 Ok(_) => {}
4789 Err(PagingError::NotMapped) => {
4790 let plan = self.pt.plan_map_page(dst_va, page_size)?;
4791 if let Some(deposit) = plan.prepare_path()? {
4792 let apply_result = {
4793 let _structure = self.pte_domain.lock_structure();
4794 self.pt.try_install_map_path(deposit)
4795 };
4796 if let Err(failure) = apply_result {
4797 let (error, deposit) = failure.into_parts();
4798 drop(deposit);
4802 return Err(error.into());
4803 }
4804 }
4805 }
4806 Err(error) => return Err(error.into()),
4807 }
4808 }
4809
4810 for &(src_va, dst_va, paddr, flags, page_size) in &mapped_pages {
4811 let plan = self.pt.plan_move_page(src_va, dst_va)?;
4812 if plan.source_vaddr() != src_va
4813 || plan.destination_vaddr() != dst_va
4814 || plan.paddr() != paddr
4815 || plan.config() != flags
4816 || plan.page_size() != page_size
4817 {
4818 return Err(StarryError::BadState);
4819 }
4820 let destination = if plan.destination_is_occupied() {
4821 let target_size = plan.destination_page_size();
4822 if target_size < PAGE_SIZE_4K || !target_size.is_power_of_two() {
4823 return Err(StarryError::BadState);
4824 }
4825 MovedPageDestination::TargetOwner {
4826 slot_va: dst_va.align_down(target_size),
4827 }
4828 } else {
4829 MovedPageDestination::SourceOwner
4830 };
4831 moved_pages.push(MovedPage {
4832 src_va,
4833 dst_va,
4834 paddr,
4835 page_size,
4836 destination,
4837 });
4838 move_plans.push(plan);
4839 }
4840
4841 let pte_domain = &self.pte_domain;
4846 let cursor = &mut self.pt;
4847 let apply_result = {
4848 let _structure = pte_domain.lock_structure();
4849 let pte_stripes = pte_domain.lock_ranges(&[move_range, dst_range]);
4850 debug_assert!(!pte_stripes.stripe_indices().is_empty());
4851 cursor.try_move_pages_with(&move_plans)
4852 };
4853 let applied = apply_result?;
4854 if applied != moved_pages.len() {
4855 self.mutation_gate.mark_needs_repair();
4856 return Err(StarryError::BadState);
4857 }
4858 Ok(moved_pages)
4859 }
4860
4861 #[allow(clippy::too_many_arguments)]
4865 pub(crate) fn mremap_move_transaction(
4866 &mut self,
4867 src: VirtAddr,
4868 src_size: usize,
4869 target: VirtAddr,
4870 target_size: usize,
4871 permissions: MappingPermissions,
4872 target_backend: MappingOperation,
4873 huge_page_advice: HugePageAdvice,
4874 lock_mode: VmaLockMode,
4875 advice_policy: VmaAdvicePolicy,
4876 dontunmap: bool,
4877 replace_target: bool,
4878 memlock_limit: Option<MemlockLimit>,
4879 ) -> StarryResult {
4880 self.validate_region(src, src_size)?;
4881 self.validate_region(target, target_size)?;
4882 if !permissions.maximum.contains(permissions.current) {
4883 return Err(StarryError::PermissionDenied);
4884 }
4885 let source_range =
4886 VirtAddrRange::try_from_start_size(src, src_size).ok_or(StarryError::InvalidInput)?;
4887 let target_range = VirtAddrRange::try_from_start_size(target, target_size)
4888 .ok_or(StarryError::InvalidInput)?;
4889 if source_range.overlaps(target_range) {
4890 return Err(StarryError::InvalidInput);
4891 }
4892 let move_size = src_size.min(target_size);
4893 let moved_source_range =
4894 VirtAddrRange::try_from_start_size(src, move_size).ok_or(StarryError::InvalidInput)?;
4895
4896 let source_page_size = self
4897 .vma_root
4898 .lookup_entry(src)
4899 .map(|entry| entry.operation().page_size())
4900 .ok_or(StarryError::BadAddress)?;
4901 let source_replacement =
4902 dontunmap.then(|| MappingOperation::new_alloc(src, source_page_size, ""));
4903 let rollback_ranges = [source_range, target_range];
4904 let tail_range = if src_size > move_size {
4905 Some(
4906 VirtAddrRange::try_from_start_size(
4907 src.checked_add(move_size)
4908 .ok_or(StarryError::InvalidInput)?,
4909 src_size - move_size,
4910 )
4911 .ok_or(StarryError::InvalidInput)?,
4912 )
4913 } else {
4914 None
4915 };
4916
4917 let target_removed_pages = if replace_target {
4918 self.vma_root
4919 .iter_entries()
4920 .filter(|entry| {
4921 entry.start() < target_range.end && entry.end() > target_range.start
4922 })
4923 .try_fold(0u64, |pages, entry| {
4924 let lo = entry.start().max(target_range.start);
4925 let hi = entry.end().min(target_range.end);
4926 let bytes = hi.checked_sub_addr(lo).ok_or(StarryError::BadState)?;
4927 pages
4928 .checked_add((bytes / PAGE_SIZE_4K) as u64)
4929 .ok_or(StarryError::InvalidInput)
4930 })?
4931 } else {
4932 0
4933 };
4934 let target_successor = self.prepare_mapping_successor(
4938 target_range,
4939 permissions,
4940 &target_backend,
4941 huge_page_advice,
4942 lock_mode,
4943 advice_policy,
4944 replace_target,
4945 )?;
4946 let mut final_successor = target_successor.clone();
4947 if dontunmap {
4948 final_successor = final_successor
4949 .without_range(moved_source_range)
4950 .ok_or(StarryError::BadState)?;
4951 let replacement = source_replacement.as_ref().ok_or(StarryError::BadState)?;
4952 let replacement_entry = final_successor
4953 .prepare_mapping_entry(
4954 moved_source_range,
4955 permissions.current,
4956 permissions.reported,
4957 permissions.maximum,
4958 huge_page_advice,
4959 VmaLockMode::Unlocked,
4963 advice_policy,
4964 replacement.clone(),
4965 )
4966 .ok_or(StarryError::BadState)?;
4967 final_successor = final_successor
4968 .with_mapping_entry(replacement_entry, false)
4969 .ok_or(StarryError::BadState)?;
4970 } else {
4971 if let Some(tail) = tail_range {
4972 final_successor = final_successor
4973 .without_range(tail)
4974 .ok_or(StarryError::BadState)?;
4975 }
4976 final_successor = final_successor
4977 .without_range(moved_source_range)
4978 .ok_or(StarryError::BadState)?;
4979 }
4980 self.validate_memlock_successor(&final_successor, memlock_limit)?;
4981 let before_vmas = self.vma_root.len();
4982 let graph_preimage = self.capture_mapping_graph_snapshot(&rollback_ranges)?;
4983
4984 let mut mutation = self.prepare_mutation_range(src, src_size);
4985 mutation
4986 .try_reserve_tlb_ranges(5)
4987 .map_err(|error| match error {
4988 MutationError::ResourceExhausted => StarryError::NoMemory,
4989 _ => StarryError::BadState,
4990 })?;
4991 mutation
4992 .add_tlb_range(TlbRange::new(target, target_size).ok_or(StarryError::InvalidInput)?);
4993 let retire_epoch = mutation
4994 .receipt()
4995 .base_epoch
4996 .checked_next()
4997 .ok_or(StarryError::BadState)?;
4998
4999 let split_ranges = [moved_source_range, target_range];
5005 let splits = self.apply_partial_huge_splits_for_ranges(&split_ranges)?;
5006 for index in 0..splits.len() {
5007 let split = &splits[index];
5008 let Some(tlb_range) =
5009 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
5010 else {
5011 return self.abort_unpublished_huge_splits(splits, StarryError::BadState);
5012 };
5013 mutation.add_tlb_range(tlb_range);
5014 }
5015
5016 let preimage = match self.capture_mapping_preimage_ranges(&rollback_ranges) {
5017 Ok(preimage) => preimage,
5018 Err(error) => return self.abort_unpublished_huge_splits(splits, error),
5019 };
5020 let target_owners = match replace_target
5021 .then(|| self.prepare_retired_mapping_owners(target_range))
5022 .transpose()
5023 {
5024 Ok(owners) => owners,
5025 Err(error) => {
5026 return self.abort_unpublished_split_mapping_mutation_ranges(
5027 &rollback_ranges,
5028 preimage,
5029 None,
5030 splits,
5031 error,
5032 );
5033 }
5034 };
5035 let tail_owners = match tail_range
5036 .map(|range| self.prepare_retired_mapping_owners(range))
5037 .transpose()
5038 {
5039 Ok(owners) => owners,
5040 Err(error) => {
5041 return self.abort_unpublished_split_mapping_mutation_ranges(
5042 &rollback_ranges,
5043 preimage,
5044 None,
5045 splits,
5046 error,
5047 );
5048 }
5049 };
5050
5051 let mut memfd_deltas = crate::syscall::memfd_prepare_aspace_replace_deltas(
5052 self,
5053 target,
5054 target_size,
5055 permissions.current,
5056 &target_backend,
5057 );
5058 if dontunmap {
5059 if let Some(replacement) = source_replacement.as_ref() {
5060 memfd_deltas.extend(crate::syscall::memfd_prepare_aspace_replace_deltas(
5061 self,
5062 src,
5063 move_size,
5064 permissions.current,
5065 replacement,
5066 ));
5067 }
5068 } else {
5069 memfd_deltas.extend(crate::syscall::memfd_prepare_aspace_unmap_deltas(
5070 self, src, src_size,
5071 ));
5072 }
5073
5074 let apply_result = (|| -> StarryResult<usize> {
5075 let target_materialization = self.apply_mapping_pages_unpublished(
5076 target_range,
5077 permissions,
5078 &target_backend,
5079 replace_target,
5080 )?;
5081 self.vma_root = Arc::new(target_successor.clone());
5082
5083 let moved_pages = self.apply_move_pages(src, target, move_size)?;
5084 let prepared_moved_slots =
5085 self.prepare_moved_slots(&moved_pages, target_backend.mapping_id())?;
5086 if !dontunmap && let Some(tail) = tail_range {
5087 self.apply_unmap_pages_unpublished(tail)?;
5088 }
5089
5090 if replace_target {
5091 self.detach_mapping_slots(target_range)?;
5092 }
5093 self.publish_prepared_pte_owners(
5094 &target_backend,
5095 target_range,
5096 &target_materialization,
5097 )?;
5098 self.publish_moved_slots(prepared_moved_slots)?;
5099 if !dontunmap && let Some(tail) = tail_range {
5100 self.detach_mapping_slots(tail)?;
5101 }
5102 if self
5103 .mapping_slots_overlapping(moved_source_range)
5104 .next()
5105 .is_some()
5106 {
5107 return Err(StarryError::BadState);
5108 }
5109 self.vma_root = Arc::new(final_successor.clone());
5110
5111 self.vm_stat.on_map((target_size / PAGE_SIZE_4K) as u64);
5112 if target_removed_pages != 0 {
5113 self.vm_stat.on_unmap(target_removed_pages);
5114 }
5115 if !dontunmap {
5116 self.vm_stat.on_unmap((src_size / PAGE_SIZE_4K) as u64);
5117 }
5118 Ok(moved_pages.len())
5119 })();
5120
5121 let moved_leaves = match apply_result {
5122 Ok(moved) => moved,
5123 Err(error) => {
5124 return self.abort_unpublished_split_mapping_mutation_ranges(
5125 &rollback_ranges,
5126 preimage,
5127 None,
5128 splits,
5129 error,
5130 );
5131 }
5132 };
5133 if let Some(owners) = target_owners {
5134 self.park_retired_mapping_owners(retire_epoch, owners);
5135 }
5136 if let Some(owners) = tail_owners {
5137 self.park_retired_mapping_owners(retire_epoch, owners);
5138 }
5139
5140 let after_vmas = self.vma_root.len();
5141 mutation.set_vma_delta(VmaDelta {
5142 inserted: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
5143 removed: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
5144 ..VmaDelta::default()
5145 });
5146 mutation.set_pte_delta(PteDelta {
5147 mapped: u32::try_from(moved_leaves).unwrap_or(u32::MAX),
5148 unmapped: u32::try_from(moved_leaves).unwrap_or(u32::MAX),
5149 ..PteDelta::default()
5150 });
5151 if let Err(error) =
5152 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &rollback_ranges)
5153 {
5154 return self.abort_unpublished_split_mapping_mutation_ranges(
5155 &rollback_ranges,
5156 preimage,
5157 Some(retire_epoch),
5158 splits,
5159 error,
5160 );
5161 }
5162
5163 match self.commit_mutation_classified(mutation) {
5164 Ok(()) => {
5165 self.release_retired_mapping_owners(retire_epoch);
5166 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5167 Ok(())
5168 }
5169 Err(CommitMutationError::PublishedPendingTlb(error)) => {
5170 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5171 Err(error)
5172 }
5173 Err(CommitMutationError::Unpublished(error)) => self
5174 .abort_unpublished_split_mapping_mutation_ranges(
5175 &rollback_ranges,
5176 preimage,
5177 Some(retire_epoch),
5178 splits,
5179 error,
5180 ),
5181 }
5182 }
5183
5184 pub fn extend_area(&mut self, addr: VirtAddr, additional_size: usize) -> StarryResult {
5186 self.extend_area_with_memlock(addr, additional_size, None)
5187 }
5188
5189 pub(crate) fn extend_area_with_memlock(
5190 &mut self,
5191 addr: VirtAddr,
5192 additional_size: usize,
5193 memlock_limit: Option<MemlockLimit>,
5194 ) -> StarryResult {
5195 if additional_size == 0 {
5196 return Ok(());
5197 }
5198 let entry = self
5199 .vma_root
5200 .lookup_entry(addr)
5201 .ok_or(StarryError::InvalidInput)?;
5202 if !additional_size.is_multiple_of(PAGE_SIZE_4K) {
5203 return Err(StarryError::InvalidInput);
5204 }
5205 let old_end = entry.end();
5206 let grown = VirtAddrRange::try_from_start_size(old_end, additional_size)
5207 .ok_or(StarryError::InvalidInput)?;
5208 let preimage = self.capture_mapping_preimage(grown)?;
5209 let graph_preimage = self.capture_mapping_graph_snapshot(&[grown])?;
5210 let mut mutation = self.prepare_mutation_range(old_end, additional_size);
5211 if entry
5212 .end()
5213 .checked_add(additional_size)
5214 .is_none_or(|new_end| new_end > self.end())
5215 {
5216 return Err(StarryError::NoMemory);
5217 }
5218 let (materialized_range, operation, materialization) =
5219 match self.apply_extend_unpublished(addr, additional_size, memlock_limit) {
5220 Ok(applied) => applied,
5221 Err(error) => {
5222 return self.abort_unpublished_mapping_mutation(grown, preimage, error);
5223 }
5224 };
5225 if materialized_range != grown {
5226 return self.abort_unpublished_mapping_mutation(grown, preimage, StarryError::BadState);
5227 }
5228 self.vm_stat.on_map((additional_size / PAGE_SIZE_4K) as u64);
5229 if let Err(error) = self.publish_prepared_pte_owners(&operation, grown, &materialization) {
5230 return self.abort_unpublished_mapping_mutation(grown, preimage, error);
5231 }
5232 if let Err(error) =
5233 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[grown])
5234 {
5235 return self.abort_unpublished_mapping_mutation(grown, preimage, error);
5236 }
5237 match self.commit_mutation_classified(mutation) {
5238 Ok(()) => Ok(()),
5239 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
5240 Err(CommitMutationError::Unpublished(error)) => {
5241 self.abort_unpublished_mapping_mutation(grown, preimage, error)
5242 }
5243 }
5244 }
5245
5246 fn process_area_data<F>(&self, start: VirtAddr, size: usize, mut f: F) -> StarryResult
5250 where
5251 F: FnMut(VirtAddr, usize, usize),
5252 {
5253 if size == 0 {
5254 return Ok(());
5255 }
5256 if !self.contains_range(start, size) {
5257 return Err(StarryError::InvalidInput);
5258 }
5259 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
5260 let end_align_up = end
5264 .as_usize()
5265 .checked_add(PAGE_SIZE_4K - 1)
5266 .map(|value| VirtAddr::from_usize(value & !(PAGE_SIZE_4K - 1)))
5267 .ok_or(StarryError::InvalidInput)?;
5268 let page_start = start.align_down_4k();
5269 let pages = PageIter4K::new(page_start, end_align_up).ok_or(StarryError::InvalidInput)?;
5270 let mut copied = 0usize;
5271 for vaddr in pages {
5272 let (paddr, ..) = self.pt.query(vaddr).map_err(|_| StarryError::BadAddress)?;
5273 let page_offset = if vaddr == page_start {
5274 start.align_offset_4k()
5275 } else {
5276 0
5277 };
5278 let copy_size = (PAGE_SIZE_4K - page_offset).min(size - copied);
5279 if copy_size == 0 {
5280 break;
5281 }
5282 let paddr = paddr
5283 .checked_add(page_offset)
5284 .ok_or(StarryError::BadAddress)?;
5285 f(phys_to_virt(paddr), copied, copy_size);
5286 copied = copied
5287 .checked_add(copy_size)
5288 .ok_or(StarryError::InvalidInput)?;
5289 }
5290 (copied == size)
5291 .then_some(())
5292 .ok_or(StarryError::BadAddress)
5293 }
5294
5295 pub fn read(&self, start: VirtAddr, buf: &mut [u8]) -> StarryResult {
5296 self.process_area_data(start, buf.len(), |src, offset, read_size| unsafe {
5297 core::ptr::copy_nonoverlapping(src.as_ptr(), buf.as_mut_ptr().add(offset), read_size);
5298 })
5299 }
5300
5301 pub fn write(&self, start: VirtAddr, buf: &[u8]) -> StarryResult {
5308 self.process_area_data(start, buf.len(), |dst, offset, write_size| unsafe {
5309 core::ptr::copy_nonoverlapping(buf.as_ptr().add(offset), dst.as_mut_ptr(), write_size);
5310 })
5311 }
5312
5313 pub fn sync_modified_text(&self, start: VirtAddr, size: usize) -> StarryResult {
5315 if size == 0 {
5316 return Ok(());
5317 }
5318
5319 self.process_area_data(start, size, |dst, _offset, sync_size| {
5320 ax_runtime::hal::cache::clean_dcache_to_pou(dst, sync_size);
5321 })?;
5322 ax_runtime::hal::cache::flush_icache_all();
5323 Ok(())
5324 }
5325
5326 pub fn protect(&mut self, start: VirtAddr, size: usize, flags: MappingFlags) -> StarryResult {
5331 self.protect_with_reported_flags(start, size, flags, flags)
5332 }
5333
5334 pub fn protect_with_reported_flags(
5335 &mut self,
5336 start: VirtAddr,
5337 size: usize,
5338 flags: MappingFlags,
5339 reported_flags: MappingFlags,
5340 ) -> StarryResult {
5341 self.validate_region(start, size)?;
5342 let range = VirtAddrRange::from_start_size(start, size);
5343
5344 start.checked_add(size).ok_or(StarryError::InvalidInput)?;
5349 let mut denied = false;
5353 self.vma_root.for_each_overlapping_entry(range, |entry| {
5354 if entry.max_rights().contains(flags) {
5355 return true;
5356 }
5357 denied = true;
5358 false
5359 });
5360 if denied {
5361 return Err(StarryError::PermissionDenied);
5362 }
5363
5364 let vma_preimage = self.vma_root.clone();
5365 let vm_stat_preimage = self.vm_stat.snapshot();
5366 let before_vmas = self.vma_root.len();
5367 let mut mutation = self.prepare_mutation_range(start, size);
5368 mutation
5369 .try_reserve_tlb_ranges(2)
5370 .map_err(|_| StarryError::NoMemory)?;
5371 let splits = self.apply_partial_huge_splits(range)?;
5372 for split in &splits {
5373 let Some(tlb_range) =
5374 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
5375 else {
5376 return self.abort_unpublished_protection(
5377 vma_preimage,
5378 vm_stat_preimage,
5379 &[],
5380 splits,
5381 StarryError::BadState,
5382 );
5383 };
5384 mutation.add_tlb_range(tlb_range);
5385 }
5386 let protection_preimage = match self.capture_protection_leaf_preimage(range) {
5387 Ok(preimage) => preimage,
5388 Err(error) => {
5389 return self.abort_unpublished_protection(
5390 vma_preimage,
5391 vm_stat_preimage,
5392 &[],
5393 splits,
5394 error,
5395 );
5396 }
5397 };
5398 let protected_leaves = protection_preimage.len();
5399 mutation.set_pte_delta(PteDelta {
5400 protected: u32::try_from(protected_leaves).unwrap_or(u32::MAX),
5401 ..PteDelta::default()
5402 });
5403 let split_slots = splits
5404 .iter()
5405 .map(|split| split.child_slots.len().saturating_sub(1))
5406 .sum::<usize>();
5407 mutation.set_mapping_delta(MappingDelta {
5408 attached: u32::try_from(split_slots).unwrap_or(u32::MAX),
5409 ..MappingDelta::default()
5410 });
5411 let touched_memfds =
5412 crate::syscall::memfd_collect_metas_touching_mprotect_range(self, start, size);
5413 if let Err(error) = self.apply_protection_unpublished(range, flags, reported_flags) {
5414 return self.abort_unpublished_protection(
5415 vma_preimage,
5416 vm_stat_preimage,
5417 &protection_preimage,
5418 splits,
5419 error,
5420 );
5421 }
5422 let after_vmas = self.vma_root.len();
5423 mutation.set_vma_delta(VmaDelta {
5424 split: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
5425 merged: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
5426 ..VmaDelta::default()
5427 });
5428
5429 match self.commit_mutation_classified(mutation) {
5430 Ok(()) => {
5431 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
5432 self,
5433 &touched_memfds,
5434 );
5435 Ok(())
5436 }
5437 Err(CommitMutationError::PublishedPendingTlb(error)) => {
5438 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
5442 self,
5443 &touched_memfds,
5444 );
5445 Err(error)
5446 }
5447 Err(CommitMutationError::Unpublished(error)) => self.abort_unpublished_protection(
5448 vma_preimage,
5449 vm_stat_preimage,
5450 &protection_preimage,
5451 splits,
5452 error,
5453 ),
5454 }
5455 }
5456
5457 fn ensure_quiescent_for_content_clear(&self) -> StarryResult {
5458 if self.tlb_targets.load(core::sync::atomic::Ordering::Acquire) != 0
5459 || self.mutation_gate.pending_count() != 0
5460 || self.pending_retired_mapping_batches() != 0
5461 {
5462 return Err(StarryError::ResourceBusy);
5463 }
5464 Ok(())
5465 }
5466
5467 fn clear_quiescent_contents(&mut self) -> StarryResult {
5472 self.ensure_quiescent_for_content_clear()?;
5473 let range = self.layout.range();
5474 let operations = self.mapping_operation_fragments(range, false)?;
5475 if operations
5476 .iter()
5477 .any(|(fragment, operation)| !operation.validate_unmap_range(*fragment, &self.pt))
5478 {
5479 return Err(StarryError::BadState);
5480 }
5481
5482 let deferred_tlb = DeferredTlbRetireGuard::enter();
5483 let clear_result = operations
5491 .into_iter()
5492 .try_for_each(|(fragment, operation)| operation.unmap_range(fragment, &mut self.pt));
5493 drop(deferred_tlb);
5494 if let Err(error) = clear_result {
5495 if let Err(flush_error) = crate::mm::flush_tlb_range_sync(range.start, range.size()) {
5496 warn!(
5497 "quiescent address-space clear could not invalidate {:?}+{:#x}: {flush_error}",
5498 range.start,
5499 range.size()
5500 );
5501 }
5502 self.mutation_gate.mark_needs_repair();
5503 return Err(error);
5504 }
5505 let slots = core::mem::take(&mut self.mapping_slots);
5506 for slot in slots.into_values() {
5507 slot.detach();
5508 }
5509 self.resident_watermark.reset();
5510 self.vm_stat.on_clear();
5511 self.vma_root = Arc::new(VmaMap::default());
5512 self.mutation_gate.clear_repair();
5515 Ok(())
5516 }
5517
5518 pub(crate) fn reset_uninstalled_for_loader(&mut self) -> StarryResult {
5527 self.ensure_quiescent_for_content_clear()?;
5528 let range = self.layout.range();
5529 let memfd_deltas =
5530 crate::syscall::memfd_prepare_aspace_unmap_deltas(self, range.start, range.size());
5531 self.clear_quiescent_contents()?;
5532 self.resident_pages = ResidentPageCounts::default();
5533 self.heap = HeapState::new(USER_HEAP_BASE);
5534 self.executable_data = ExecutableDataLayout::default();
5535 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5536 Ok(())
5537 }
5538
5539 fn clear_retired_contents(&mut self) -> StarryResult {
5542 self.ensure_quiescent_for_content_clear()?;
5543 let base_epoch = self.vm_epoch();
5544 base_epoch.checked_next().ok_or(StarryError::BadState)?;
5545 let range = self.layout.range();
5546 let removed_vmas = self.vma_root.len();
5547 let detached_slots = self.mapping_slots.len();
5548 let materialized_pages = self
5549 .mapping_slots
5550 .values()
5551 .try_fold(0usize, |pages, slot| {
5552 pages.checked_add(1usize.checked_shl(slot.page_order.get().into())?)
5553 })
5554 .ok_or(StarryError::BadState)?;
5555 let memfd_deltas =
5556 crate::syscall::memfd_prepare_aspace_unmap_deltas(self, range.start, range.size());
5557 let mut mutation = self.prepare_mutation_range(range.start, range.size());
5558 mutation.set_vma_delta(VmaDelta {
5559 removed: u32::try_from(removed_vmas).unwrap_or(u32::MAX),
5560 ..VmaDelta::default()
5561 });
5562 mutation.set_pte_delta(PteDelta {
5563 unmapped: u32::try_from(materialized_pages).unwrap_or(u32::MAX),
5564 ..PteDelta::default()
5565 });
5566 mutation.set_mapping_delta(MappingDelta {
5567 detached: u32::try_from(detached_slots).unwrap_or(u32::MAX),
5568 ..MappingDelta::default()
5569 });
5570 mutation.set_resident_delta(self.resident_pages.checked_negated_delta()?);
5571 self.clear_quiescent_contents()?;
5572 let result = self.commit_mutation(mutation);
5573 if self.vm_epoch() != base_epoch {
5574 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5575 }
5576 result
5577 }
5578
5579 pub(crate) fn try_reclaim_contents(&mut self) -> StarryResult {
5581 if self.tlb_targets.load(core::sync::atomic::Ordering::Acquire) != 0
5587 || self.mutation_gate.pending_count() != 0
5588 || self.pending_retired_mapping_batches() != 0
5589 {
5590 return Err(StarryError::ResourceBusy);
5591 }
5592 self.clear_retired_contents()?;
5593 let epoch = self.vm_epoch();
5594
5595 let targets = self.tlb_targets.load(core::sync::atomic::Ordering::Acquire);
5602 let request = TlbRequest::new(self.id, epoch, targets);
5603 debug_assert!(request.is_complete());
5604 unsafe {
5610 self.pt.detach(|token| token.reclaim());
5611 }
5612 Ok(())
5613 }
5614
5615 pub fn can_access_range(
5620 &self,
5621 start: VirtAddr,
5622 size: usize,
5623 access_flags: MappingFlags,
5624 ) -> bool {
5625 let Some(range) = VirtAddrRange::try_from_start_size(start, size) else {
5626 return false;
5627 };
5628 if range.is_empty() {
5629 return false;
5630 }
5631 let mut cursor = range.start;
5635 let mut permitted = false;
5636 self.vma_root.for_each_overlapping(range, |vma| {
5637 if vma.range.end <= cursor {
5638 return true;
5639 }
5640 if vma.range.start > cursor || !vma.rights.contains(access_flags) {
5641 return false;
5642 }
5643 cursor = vma.range.end.min(range.end);
5644 if cursor >= range.end {
5645 permitted = true;
5646 return false;
5647 }
5648 true
5649 });
5650 permitted
5651 }
5652
5653 fn fault_transaction_page_size(
5662 &self,
5663 vaddr: VirtAddr,
5664 vma_range: VirtAddrRange,
5665 policy_size: usize,
5666 ) -> StarryResult<usize> {
5667 match self.pt.query(vaddr) {
5668 Ok((_, _, leaf_size)) => return Ok(leaf_size),
5669 Err(PagingError::NotMapped) => {}
5670 Err(error) => return Err(error.into()),
5671 }
5672 if policy_size == PAGE_SIZE_4K {
5673 return Ok(PAGE_SIZE_4K);
5674 }
5675 if policy_size < PAGE_SIZE_4K
5676 || !policy_size.is_power_of_two()
5677 || !policy_size.is_multiple_of(PAGE_SIZE_4K)
5678 {
5679 return Err(StarryError::BadState);
5680 }
5681
5682 let policy_start = vaddr.align_down(policy_size);
5683 let policy_range = VirtAddrRange::try_from_start_size(policy_start, policy_size)
5684 .ok_or(StarryError::BadState)?;
5685 if !vma_range.contains_range(policy_range) {
5686 return Ok(PAGE_SIZE_4K);
5687 }
5688
5689 if self
5694 .pt
5695 .walk_occupied_range(policy_range.start, policy_range.end)
5696 .next()
5697 .is_some()
5698 {
5699 return Ok(PAGE_SIZE_4K);
5700 }
5701 Ok(policy_size)
5702 }
5703
5704 fn plan_page_fault(
5705 &self,
5706 vaddr: VirtAddr,
5707 access_flags: PageFaultFlags,
5708 thp_mode: TransparentHugePageMode,
5709 ) -> Result<PageFaultPlan, FaultResult> {
5710 if self.mutation_gate.needs_repair() {
5711 return Err(FaultResult::Sigbus(BusCode::ObjErr));
5714 }
5715 if !self.layout.range().contains(vaddr) {
5716 return Err(FaultResult::Unmapped);
5717 }
5718 let access_flags = MappingFlags::from(access_flags);
5719 let Some(entry) = self.vma_root.lookup_entry(vaddr) else {
5720 return Err(FaultResult::Unmapped);
5721 };
5722 let vma = entry.snapshot().clone();
5723 let flags = vma.rights;
5724 if !flags.contains(access_flags) {
5725 return Err(FaultResult::PermissionDenied);
5726 }
5727 let backend = entry.operation_clone();
5728 let Some(policy_size) = vma
5729 .group
5730 .page_policy
5731 .fault_leaf_size(vma.huge_page_advice, thp_mode)
5732 else {
5733 return Err(FaultResult::Unmapped);
5734 };
5735 let page_size = match self.fault_transaction_page_size(vaddr, vma.range, policy_size) {
5736 Ok(page_size) => page_size,
5737 Err(error) => {
5738 warn!("could not classify page-fault leaf for {vaddr:?}: {error}");
5739 return Err(FaultResult::Retry);
5740 }
5741 };
5742 let page_start = vaddr.align_down(page_size);
5743 let Some(range) = VirtAddrRange::try_from_start_size(page_start, page_size) else {
5744 return Err(FaultResult::Unmapped);
5745 };
5746 let fault_fallback = if vma.group.page_policy.permits_fault_fallback() {
5747 FaultFallback::BasePage
5748 } else {
5749 FaultFallback::Forbidden
5750 };
5751 let request = match PopulateRequest::fault(range, page_size, vaddr, fault_fallback) {
5752 Ok(request) => request,
5753 Err(_) => return Err(FaultResult::Unmapped),
5754 };
5755 let preimage = match FaultPteSnapshot::capture(&self.pt, page_start) {
5756 Ok(preimage) => preimage,
5757 Err(error) => {
5758 warn!("could not capture page-fault PTE at {page_start:?}: {error}");
5759 return Err(FaultResult::Retry);
5760 }
5761 };
5762 let map_plans = if preimage == FaultPteSnapshot::NotMapped {
5763 let preferred = match self.pt.plan_map_page(page_start, page_size) {
5764 Ok(plan) => plan,
5765 Err(error) => {
5766 warn!("could not plan page-table path for {page_start:?}: {error}");
5767 return Err(FaultResult::Retry);
5768 }
5769 };
5770 let fallback = if page_size > PAGE_SIZE_4K && fault_fallback == FaultFallback::BasePage
5771 {
5772 let fallback_start = vaddr.align_down_4k();
5773 match self.pt.plan_map_page(fallback_start, PAGE_SIZE_4K) {
5774 Ok(plan) => Some(plan),
5775 Err(error) => {
5776 warn!(
5777 "could not plan fallback page-table path for {fallback_start:?}: \
5778 {error}"
5779 );
5780 return Err(FaultResult::Retry);
5781 }
5782 }
5783 } else {
5784 None
5785 };
5786 Some(PageFaultMapPlans {
5787 preferred,
5788 fallback,
5789 })
5790 } else {
5791 None
5792 };
5793 Ok(PageFaultPlan {
5794 base_epoch: self.vm_epoch(),
5795 space_id: self.id,
5796 vaddr,
5797 range,
5798 vma_flags: flags,
5799 access_flags,
5800 operation: backend,
5801 request,
5802 preimage,
5803 map_plans,
5804 })
5805 }
5806
5807 fn classify_fault_error(file_backed: bool, error: StarryError) -> FaultResult {
5808 if matches!(
5809 error,
5810 StarryError::NoMemory
5811 | StarryError::Paging(PagingError::NoMemory)
5812 | StarryError::Vfs(axfs_ng_vfs::VfsError::NoMemory)
5813 ) {
5814 return FaultResult::NoMemory;
5815 }
5816 if matches!(error, StarryError::ResourceBusy) {
5817 return FaultResult::Retry;
5818 }
5819 if !file_backed {
5820 return FaultResult::Unmapped;
5821 }
5822 match error {
5823 StarryError::ResourceBusy | StarryError::Vfs(axfs_ng_vfs::VfsError::ResourceBusy) => {
5824 FaultResult::Retry
5825 }
5826 StarryError::BadAddress => FaultResult::Sigbus(BusCode::AdrErr),
5827 StarryError::Io | StarryError::Vfs(_) => FaultResult::Sigbus(BusCode::ObjErr),
5828 _ => FaultResult::Unmapped,
5829 }
5830 }
5831
5832 fn prepare_fault_materialization(
5833 plan: &PageFaultPlan,
5834 request: PopulateRequest,
5835 ) -> Result<FaultMaterialization, FaultResult> {
5836 match plan.operation.prepare_fault(
5837 plan.space_id,
5838 request,
5839 plan.vma_flags,
5840 plan.access_flags,
5841 plan.preimage,
5842 ) {
5843 Ok(materialization) => Ok(materialization),
5844 Err(error) => {
5845 warn!(
5846 "failed to prepare page fault for {:?} ({:?}): {error}",
5847 plan.vaddr, plan.vma_flags
5848 );
5849 Err(Self::classify_fault_error(
5850 plan.operation.is_file_backed(),
5851 error,
5852 ))
5853 }
5854 }
5855 }
5856
5857 fn cancel_fault_materialization(
5858 plan: &PageFaultPlan,
5859 materialization: FaultMaterialization,
5860 ) -> Result<(), FaultResult> {
5861 plan.operation
5862 .cancel_prepared_fault_publication(materialization)
5863 .map_err(|error| {
5864 warn!(
5865 "failed to cancel prepared page fault for {:?}: {error}",
5866 plan.vaddr
5867 );
5868 FaultResult::Retry
5869 })
5870 }
5871
5872 fn prepare_page_fault(mut plan: PageFaultPlan) -> Result<PreparedPageFault, FaultResult> {
5873 let mut materialization = Self::prepare_fault_materialization(&plan, plan.request)?;
5874 let installed_owner = materialization.owner().and_then(|owner| {
5875 (owner.transition == PteOwnerTransition::Installed).then_some((
5876 owner.va,
5877 owner.paddr,
5878 owner.page_size,
5879 ))
5880 });
5881 let map_deposit = if let Some((owner_va, owner_paddr, owner_page_size)) = installed_owner {
5882 let Some(plans) = plan.map_plans.take() else {
5883 Self::cancel_fault_materialization(&plan, materialization)?;
5884 return Err(FaultResult::Retry);
5885 };
5886 let PageFaultMapPlans {
5887 preferred,
5888 mut fallback,
5889 } = plans;
5890 let preferred_selected =
5891 preferred.vaddr() == owner_va && preferred.page_size() == owner_page_size;
5892 let fallback_selected = fallback.as_ref().is_some_and(|fallback| {
5893 fallback.vaddr() == owner_va && fallback.page_size() == owner_page_size
5894 });
5895 if !preferred_selected && !fallback_selected {
5896 Self::cancel_fault_materialization(&plan, materialization)?;
5897 return Err(FaultResult::Retry);
5898 }
5899 let Some(flags) = materialization.pte_flags() else {
5900 Self::cancel_fault_materialization(&plan, materialization)?;
5901 return Err(FaultResult::Retry);
5902 };
5903
5904 if fallback_selected {
5905 let Some(fallback_request) = plan.request.into_base_page_fallback() else {
5906 Self::cancel_fault_materialization(&plan, materialization)?;
5907 return Err(FaultResult::Retry);
5908 };
5909 plan.request = fallback_request;
5910 plan.range = fallback_request.range();
5911 let Some(fallback) = fallback.take() else {
5912 Self::cancel_fault_materialization(&plan, materialization)?;
5913 return Err(FaultResult::Retry);
5914 };
5915 match fallback.prepare(owner_paddr, flags) {
5916 Ok(deposit) => Some(deposit),
5917 Err(error) => {
5918 warn!(
5919 "could not prepare fallback page-table path for {owner_va:?}: {error}"
5920 );
5921 Self::cancel_fault_materialization(&plan, materialization)?;
5922 return Err(Self::classify_fault_error(false, error.into()));
5923 }
5924 }
5925 } else {
5926 match preferred.prepare(owner_paddr, flags) {
5927 Ok(deposit) => Some(deposit),
5928 Err(PagingError::NoMemory) if fallback.is_some() => {
5929 Self::cancel_fault_materialization(&plan, materialization)?;
5934 let Some(fallback_request) = plan.request.into_base_page_fallback() else {
5935 return Err(FaultResult::Retry);
5936 };
5937 plan.request = fallback_request;
5938 plan.range = fallback_request.range();
5939 materialization =
5940 Self::prepare_fault_materialization(&plan, fallback_request)?;
5941 let Some(owner) = materialization.owner() else {
5942 Self::cancel_fault_materialization(&plan, materialization)?;
5943 return Err(FaultResult::Retry);
5944 };
5945 let Some(fallback) = fallback.take() else {
5946 Self::cancel_fault_materialization(&plan, materialization)?;
5947 return Err(FaultResult::Retry);
5948 };
5949 if owner.transition != PteOwnerTransition::Installed
5950 || owner.va != fallback.vaddr()
5951 || owner.page_size != fallback.page_size()
5952 {
5953 Self::cancel_fault_materialization(&plan, materialization)?;
5954 return Err(FaultResult::Retry);
5955 }
5956 let Some(flags) = materialization.pte_flags() else {
5957 Self::cancel_fault_materialization(&plan, materialization)?;
5958 return Err(FaultResult::Retry);
5959 };
5960 match fallback.prepare(owner.paddr, flags) {
5961 Ok(deposit) => Some(deposit),
5962 Err(error) => {
5963 warn!(
5964 "could not prepare base-page table path for {:?}: {error}",
5965 owner.va
5966 );
5967 Self::cancel_fault_materialization(&plan, materialization)?;
5968 return Err(Self::classify_fault_error(false, error.into()));
5969 }
5970 }
5971 }
5972 Err(error) => {
5973 warn!("could not prepare page-table path for {owner_va:?}: {error}");
5974 Self::cancel_fault_materialization(&plan, materialization)?;
5975 return Err(Self::classify_fault_error(false, error.into()));
5976 }
5977 }
5978 }
5979 } else {
5980 None
5981 };
5982 Ok(PreparedPageFault {
5983 plan,
5984 materialization,
5985 map_deposit,
5986 })
5987 }
5988
5989 fn page_fault_plan_is_current(&self, plan: &PageFaultPlan) -> bool {
5990 if self.vm_epoch() != plan.base_epoch || !plan.preimage.matches(plan.range.start, &self.pt)
5991 {
5992 return false;
5993 }
5994 self.vma_root.lookup_entry(plan.vaddr).is_some_and(|entry| {
5995 entry.snapshot().rights == plan.vma_flags
5996 && entry.snapshot().range.contains_range(plan.range)
5997 && entry.operation().mapping_id() == plan.operation.mapping_id()
5998 })
5999 }
6000
6001 fn apply_prepared_page_fault(
6002 &mut self,
6003 attempt: &mut PageFaultApplyAttempt,
6004 ) -> PageFaultApplyOutcome {
6005 if !self.page_fault_plan_is_current(&attempt.prepared().plan) {
6006 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6007 }
6008
6009 let (pages, file_backed, vaddr, vma_flags, range, access_flags, fault_preimage) = {
6010 let prepared = attempt.prepared();
6011 (
6012 prepared.materialization.satisfied_pages(),
6013 prepared.plan.operation.is_file_backed(),
6014 prepared.plan.vaddr,
6015 prepared.plan.vma_flags,
6016 prepared.plan.range,
6017 prepared.plan.access_flags,
6018 prepared.plan.preimage,
6019 )
6020 };
6021 if pages == 0 {
6022 let result = if file_backed {
6023 FaultResult::Sigbus(BusCode::AdrErr)
6024 } else {
6025 warn!("no pages prepared for {vaddr:?} ({vma_flags:?})");
6026 FaultResult::Unmapped
6027 };
6028 return PageFaultApplyOutcome::Cancel(result);
6029 }
6030 if attempt.prepared().materialization.owner().is_none() {
6031 return PageFaultApplyOutcome::Cancel(FaultResult::Handled);
6032 }
6033 let (owner_va, owner_paddr, owner_page_size, owner_transition, desired_flags) = {
6034 let prepared = attempt.prepared();
6035 let owner = prepared
6036 .materialization
6037 .owner()
6038 .expect("checked fault owner must remain present");
6039 let Some(desired_flags) = prepared.materialization.pte_flags() else {
6040 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6041 };
6042 (
6043 owner.va,
6044 owner.paddr,
6045 owner.page_size,
6046 owner.transition,
6047 desired_flags,
6048 )
6049 };
6050 let mapping_preimage = match self.capture_mapping_preimage(range) {
6051 Ok(preimage) => preimage,
6052 Err(error) => {
6053 warn!("could not retain page-fault preimage for {vaddr:?}: {error}");
6054 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6055 }
6056 };
6057 let replaces_owner = attempt
6058 .prepared()
6059 .materialization
6060 .owner()
6061 .is_some_and(|owner| owner.transition == PteOwnerTransition::Replaced);
6062 let retired_owners = if replaces_owner {
6063 match self.prepare_retired_mapping_owners(range) {
6064 Ok(owners) => Some(owners),
6065 Err(error) => {
6066 warn!("could not reserve page-fault retire owners for {vaddr:?}: {error}");
6067 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6068 }
6069 }
6070 } else {
6071 None
6072 };
6073 let lazy_free_page = access_flags
6074 .contains(MappingFlags::WRITE)
6075 .then(|| {
6076 self.mapping_slots
6077 .get(&MappingSlotKey {
6078 space_id: self.id,
6079 va: owner_va,
6080 })
6081 .filter(|slot| slot.page.state() == PageState::LazyFree)
6082 .map(|slot| slot.page.clone())
6083 })
6084 .flatten();
6085 let fresh_install = matches!(fault_preimage, FaultPteSnapshot::NotMapped)
6086 && attempt
6087 .prepared()
6088 .materialization
6089 .owner()
6090 .is_some_and(|owner| owner.transition == PteOwnerTransition::Installed);
6091 let mut mutation = if fresh_install {
6092 self.prepare_fresh_pte_mutation_range(range.start, range.size())
6093 } else {
6094 self.prepare_mutation_range(range.start, range.size())
6095 };
6096 if let Some(request) = self.mutation_gate.pending_overlap_request(&mutation) {
6101 return PageFaultApplyOutcome::CancelPendingTlb {
6102 request,
6103 targets: self.tlb_targets(),
6104 };
6105 }
6106 let Some(retire_epoch) = mutation.receipt().base_epoch.checked_next() else {
6107 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6108 };
6109
6110 let mut map_deposit = if owner_transition == PteOwnerTransition::Installed {
6111 match attempt.take_map_deposit() {
6112 Some(deposit) => Some(deposit),
6113 None => return PageFaultApplyOutcome::Cancel(FaultResult::Retry),
6114 }
6115 } else {
6116 None
6117 };
6118 let apply_result = {
6119 let _structure = (owner_transition == PteOwnerTransition::Installed)
6120 .then(|| self.pte_domain.lock_structure());
6121 let _stripe = self.pte_domain.lock_range(range);
6122 let pt = &mut self.pt;
6123 let preimage_matches = fault_preimage.matches(range.start, pt);
6124 let result = if !preimage_matches {
6125 Err(PagingError::stale_map_deposit(owner_va))
6126 } else {
6127 match owner_transition {
6128 PteOwnerTransition::Installed => {
6129 let deposit = map_deposit
6130 .take()
6131 .expect("fresh page fault must retain its map deposit");
6132 match pt.try_map_page_with(deposit) {
6133 Ok(()) => Ok(owner_page_size),
6134 Err(failure) => {
6135 let (error, deposit) = failure.into_parts();
6136 map_deposit = Some(deposit);
6137 Err(error)
6138 }
6139 }
6140 }
6141 PteOwnerTransition::Replaced | PteOwnerTransition::Updated => {
6142 pt.remap_page(owner_va, owner_paddr, desired_flags)
6143 }
6144 }
6145 };
6146 result.and_then(|installed_size| {
6147 (installed_size == owner_page_size)
6148 .then_some(installed_size)
6149 .ok_or(PagingError::NotMapped)
6150 })
6151 };
6152 if let Some(deposit) = map_deposit.take() {
6153 attempt.restore_map_deposit(deposit);
6154 }
6155 if let Err(error) = apply_result {
6156 warn!("could not apply prepared page fault for {vaddr:?}: {error}");
6157 if fault_preimage.matches(range.start, &self.pt) {
6158 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6159 }
6160 if self
6161 .restore_mapping_preimage(range, mapping_preimage)
6162 .is_ok()
6163 {
6164 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6165 } else {
6166 self.mutation_gate.mark_needs_repair();
6167 }
6168 return PageFaultApplyOutcome::NeedsRepair(FaultResult::Retry);
6169 }
6170
6171 mutation.set_pte_delta(PteDelta {
6172 mapped: u32::try_from(pages).unwrap_or(u32::MAX),
6173 ..PteDelta::default()
6174 });
6175 let publication = match self.publish_prepared_fault_owner(
6176 &attempt.prepared().plan.operation,
6177 range,
6178 &attempt.prepared().materialization,
6179 ) {
6180 Ok(publication) => publication,
6181 Err(error) => {
6182 warn!("could not publish prepared page owner for {vaddr:?}: {error}");
6183 if self
6184 .restore_mapping_preimage(range, mapping_preimage)
6185 .is_err()
6186 {
6187 self.mutation_gate.mark_needs_repair();
6188 return PageFaultApplyOutcome::NeedsRepair(FaultResult::Retry);
6189 }
6190 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6191 }
6192 };
6193 let PreparedPageFault {
6194 plan: _,
6195 materialization: _,
6196 map_deposit,
6197 } = attempt.take_prepared();
6198 debug_assert!(map_deposit.is_none());
6199 mutation.set_mapping_delta(publication.mapping_delta);
6200 mutation.set_resident_delta(publication.resident_delta);
6201 if let Some(page) = &lazy_free_page
6202 && !page.clear_lazy_free()
6203 {
6204 if self
6205 .restore_mapping_preimage(range, mapping_preimage)
6206 .is_err()
6207 {
6208 self.mutation_gate.mark_needs_repair();
6209 }
6210 return PageFaultApplyOutcome::Complete(FaultResult::Retry);
6211 }
6212 if let Some(owners) = retired_owners {
6213 self.park_retired_mapping_owners(retire_epoch, owners);
6214 }
6215 match self.publish_mutation_classified(mutation) {
6216 Ok(MutationPublication::Complete) => {
6217 self.release_retired_mapping_owners(retire_epoch);
6218 PageFaultApplyOutcome::Complete(FaultResult::Handled)
6219 }
6220 Ok(MutationPublication::PendingTlb) => {
6221 let Some(request) = self.mutation_gate.pending_request(self.id, retire_epoch)
6222 else {
6223 self.mutation_gate.mark_needs_repair();
6224 return PageFaultApplyOutcome::Complete(FaultResult::Retry);
6225 };
6226 PageFaultApplyOutcome::PendingTlb {
6227 request,
6228 targets: self.tlb_targets(),
6229 }
6230 }
6231 Err(CommitMutationError::Unpublished(error)) => {
6232 warn!("page-fault publication for {vaddr:?} failed before publish: {error}");
6233 if self
6234 .restore_mapping_preimage(range, mapping_preimage)
6235 .is_err()
6236 {
6237 self.mutation_gate.mark_needs_repair();
6238 } else {
6239 self.release_retired_mapping_owners(retire_epoch);
6240 if let Some(page) = &lazy_free_page
6241 && !page.mark_lazy_free()
6242 {
6243 self.mutation_gate.mark_needs_repair();
6244 }
6245 }
6246 PageFaultApplyOutcome::Complete(FaultResult::Retry)
6247 }
6248 Err(CommitMutationError::PublishedPendingTlb(error)) => {
6249 warn!("unexpected synchronous TLB result for page fault {vaddr:?}: {error}");
6250 let Some(request) = self.mutation_gate.pending_request(self.id, retire_epoch)
6251 else {
6252 self.mutation_gate.mark_needs_repair();
6253 return PageFaultApplyOutcome::Complete(FaultResult::Retry);
6254 };
6255 PageFaultApplyOutcome::PendingTlb {
6256 request,
6257 targets: self.tlb_targets(),
6258 }
6259 }
6260 }
6261 }
6262
6263 #[cfg(all(test, axtest))]
6266 fn handle_page_fault_result(
6267 &mut self,
6268 vaddr: VirtAddr,
6269 access_flags: PageFaultFlags,
6270 ) -> FaultResult {
6271 let plan =
6272 match self.plan_page_fault(vaddr, access_flags, TransparentHugePageMode::default()) {
6273 Ok(plan) => plan,
6274 Err(result) => return result,
6275 };
6276 let prepared = match Self::prepare_page_fault(plan) {
6277 Ok(prepared) => prepared,
6278 Err(result) => return result,
6279 };
6280 let mut attempt = prepared.into_apply_attempt();
6281 let result = match self.apply_prepared_page_fault(&mut attempt) {
6282 PageFaultApplyOutcome::Complete(result) => result,
6283 PageFaultApplyOutcome::Cancel(result) => {
6284 if attempt.cancel().is_ok() {
6285 result
6286 } else {
6287 FaultResult::Retry
6288 }
6289 }
6290 PageFaultApplyOutcome::NeedsRepair(result) => {
6291 attempt.release_to_repair_state();
6292 result
6293 }
6294 PageFaultApplyOutcome::CancelPendingTlb { request, targets } => {
6295 if attempt.cancel().is_ok()
6296 && Self::flush_tlb_requests(core::slice::from_ref(&request), &targets).is_ok()
6297 {
6298 let _ = self.acknowledge_tlb_requests(core::slice::from_ref(&request));
6299 }
6300 FaultResult::Retry
6301 }
6302 PageFaultApplyOutcome::PendingTlb { request, targets } => {
6303 if Self::flush_tlb_requests(core::slice::from_ref(&request), &targets).is_ok()
6304 && self
6305 .acknowledge_tlb_requests(core::slice::from_ref(&request))
6306 .is_ok()
6307 {
6308 FaultResult::Handled
6309 } else {
6310 FaultResult::Retry
6311 }
6312 }
6313 };
6314 complete_page_fault_with(
6315 matches!(result, FaultResult::Handled),
6316 vaddr,
6317 ax_runtime::hal::cache::update_mmu_cache,
6318 );
6319 result
6320 }
6321
6322 fn prepare_fork_parent_mutation(&self) -> StarryResult<Option<PreparedForkParentMutation>> {
6328 let mut mutation = self.prepare_mutation();
6329 let mut ptes = Vec::new();
6330 let mut ranges = Vec::new();
6331
6332 for entry in self.vma_root.iter_entries() {
6333 if entry.snapshot().advice_policy.dont_fork() {
6334 continue;
6335 }
6336 if !entry.operation().requires_fork_write_protect() {
6337 continue;
6338 }
6339 let mut range_changed = false;
6340 for leaf in self.occupied_pte_leaves_overlapping(&[entry.range()])? {
6341 let page_size = leaf.range.size();
6342 if page_size < PAGE_SIZE_4K || !page_size.is_power_of_two() {
6343 return Err(StarryError::BadState);
6344 }
6345 let protected_flags = leaf.flags - MappingFlags::WRITE;
6346 if protected_flags != leaf.flags {
6347 ptes.try_reserve(1).map_err(|_| StarryError::NoMemory)?;
6348 ptes.push(ForkParentPteProtection {
6349 va: leaf.range.start,
6350 paddr: leaf.paddr,
6351 page_size,
6352 original_flags: leaf.flags,
6353 protected_flags,
6354 });
6355 range_changed = true;
6356 }
6357 }
6358 if range_changed {
6359 ranges.try_reserve(1).map_err(|_| StarryError::NoMemory)?;
6360 ranges.push(entry.range());
6361 mutation
6362 .try_add_tlb_range(
6363 TlbRange::new(entry.start(), entry.size())
6364 .ok_or(StarryError::InvalidInput)?,
6365 )
6366 .map_err(|error| match error {
6367 MutationError::ResourceExhausted => StarryError::NoMemory,
6368 _ => StarryError::BadState,
6369 })?;
6370 }
6371 }
6372
6373 if ptes.is_empty() {
6374 return Ok(None);
6375 }
6376 mutation.set_pte_delta(PteDelta {
6377 protected: u32::try_from(ptes.len()).unwrap_or(u32::MAX),
6378 ..PteDelta::default()
6379 });
6380 Ok(Some(PreparedForkParentMutation {
6381 mutation,
6382 ptes,
6383 ranges,
6384 }))
6385 }
6386
6387 fn rollback_fork_parent_ptes(
6388 cursor: &mut PageTable,
6389 applied: &[ForkParentPteProtection],
6390 ) -> bool {
6391 let mut complete = true;
6392 for protection in applied.iter().rev() {
6393 let current_matches =
6394 cursor
6395 .query(protection.va)
6396 .is_ok_and(|(paddr, flags, page_size)| {
6397 paddr == protection.paddr
6398 && flags == protection.protected_flags
6399 && page_size == protection.page_size
6400 });
6401 if !current_matches
6402 || cursor
6403 .protect_page(protection.va, protection.original_flags)
6404 .is_err()
6405 {
6406 complete = false;
6407 }
6408 }
6409 complete
6410 }
6411
6412 fn apply_fork_parent_mutation(&mut self, prepared: PreparedForkParentMutation) -> StarryResult {
6415 let PreparedForkParentMutation {
6416 mutation,
6417 ptes,
6418 ranges,
6419 } = prepared;
6420 let pt = &mut self.pt;
6421 let pte_stripes = self.pte_domain.lock_ranges(&ranges);
6422 let cursor = pt;
6426 for (applied, protection) in ptes.iter().enumerate() {
6427 let preimage_matches =
6428 cursor
6429 .query(protection.va)
6430 .is_ok_and(|(paddr, flags, page_size)| {
6431 paddr == protection.paddr
6432 && flags == protection.original_flags
6433 && page_size == protection.page_size
6434 });
6435 if !preimage_matches
6436 || cursor
6437 .protect_page(protection.va, protection.protected_flags)
6438 .is_err()
6439 {
6440 if !Self::rollback_fork_parent_ptes(cursor, &ptes[..applied]) {
6441 self.mutation_gate.mark_needs_repair();
6442 return Err(StarryError::BadState);
6443 }
6444 return Err(StarryError::BadState);
6445 }
6446 }
6447 drop(pte_stripes);
6448
6449 match self.commit_mutation_classified(mutation) {
6455 Ok(()) => Ok(()),
6456 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
6457 Err(CommitMutationError::Unpublished(error)) => {
6458 let pt = &mut self.pt;
6459 let _pte_stripes = self.pte_domain.lock_ranges(&ranges);
6460 let restored = Self::rollback_fork_parent_ptes(pt, &ptes);
6464 if restored {
6465 self.mutation_gate.clear_repair();
6466 Err(error)
6467 } else {
6468 self.mutation_gate.mark_needs_repair();
6469 Err(StarryError::BadState)
6470 }
6471 }
6472 }
6473 }
6474
6475 fn abort_unpublished_clone(child: &mut Self) -> StarryResult {
6476 match child.reset_uninstalled_for_loader() {
6477 Ok(()) => Ok(()),
6478 Err(error)
6479 if child.vma_root.is_empty()
6480 && child.mapping_slots.is_empty()
6481 && child.pending_retired_mapping_batches() == 0 =>
6482 {
6483 warn!(
6488 "unpublished fork child cleared all mappings but could not publish cleanup \
6489 epoch: {error}"
6490 );
6491 Ok(())
6492 }
6493 Err(error) => Err(error),
6494 }
6495 }
6496
6497 pub fn try_clone(&mut self) -> StarryResult<Arc<Mutex<Self>>> {
6507 let parent_mutation = self.prepare_fork_parent_mutation()?;
6511 let new_aspace = Arc::new(Mutex::new(Self::new_with_layout(self.layout)?));
6512
6513 let mut guard = new_aspace.lock_nested(CLONED_ADDR_SPACE_LOCK_SUBCLASS);
6517 guard.heap = self.heap;
6518 guard.executable_data = self.executable_data;
6519 let mut child_memfd_deltas = Vec::new();
6520 let mut child_vss_pages = 0u64;
6521
6522 let child_preparation = (|| -> StarryResult {
6523 let self_modify = &mut self.pt;
6524 for entry in self.vma_root.iter_entries() {
6525 if entry.snapshot().advice_policy.dont_fork() {
6526 continue;
6527 }
6528 let (new_backend, materialization) = entry.operation().clone_map(
6529 entry.range(),
6530 entry.rights(),
6531 self_modify,
6532 &mut guard.pt,
6533 )?;
6534 let start = entry.start();
6535 child_memfd_deltas.extend(crate::syscall::memfd_prepare_aspace_replace_deltas(
6536 &guard,
6537 start,
6538 entry.size(),
6539 entry.rights(),
6540 &new_backend,
6541 ));
6542
6543 let child_entry = guard
6544 .vma_root
6545 .prepare_mapping_entry(
6546 entry.range(),
6547 entry.rights(),
6548 entry.reported_rights(),
6549 entry.max_rights(),
6550 entry.snapshot().huge_page_advice,
6551 VmaLockMode::Unlocked,
6552 entry.snapshot().advice_policy,
6553 new_backend.clone(),
6554 )
6555 .ok_or(StarryError::BadState)?;
6556 let child_root = guard
6557 .vma_root
6558 .with_mapping_entry(child_entry, false)
6559 .ok_or(StarryError::BadState)?;
6560 guard.vma_root = Arc::new(child_root);
6561 guard.publish_prepared_pte_owners(&new_backend, entry.range(), &materialization)?;
6562 child_vss_pages = child_vss_pages
6563 .checked_add((entry.size() / PAGE_SIZE_4K) as u64)
6564 .ok_or(StarryError::BadState)?;
6565 }
6566
6567 guard.vm_stat.seed_clone(child_vss_pages);
6570 Ok(())
6571 })();
6572
6573 if let Err(error) = child_preparation {
6574 if let Err(cleanup_error) = Self::abort_unpublished_clone(&mut guard) {
6575 warn!(
6576 "fork child preparation failed ({error}); unpublished cleanup also failed \
6577 ({cleanup_error})"
6578 );
6579 return Err(StarryError::BadState);
6580 }
6581 return Err(error);
6582 }
6583
6584 if let Some(parent_mutation) = parent_mutation
6588 && let Err(error) = self.apply_fork_parent_mutation(parent_mutation)
6589 {
6590 if let Err(cleanup_error) = Self::abort_unpublished_clone(&mut guard) {
6591 warn!(
6592 "fork parent publication failed ({error}); unpublished child cleanup also \
6593 failed ({cleanup_error})"
6594 );
6595 return Err(StarryError::BadState);
6596 }
6597 return Err(error);
6598 }
6599
6600 if !guard.vma_root.is_empty() {
6604 let mut child_mutation = guard.prepare_mutation();
6605 child_mutation.set_vma_delta(VmaDelta {
6606 inserted: u32::try_from(guard.vma_root.len()).unwrap_or(u32::MAX),
6607 ..VmaDelta::default()
6608 });
6609 child_mutation.set_pte_delta(PteDelta {
6610 mapped: u32::try_from(guard.mapping_slots.len()).unwrap_or(u32::MAX),
6611 ..PteDelta::default()
6612 });
6613 child_mutation.set_mapping_delta(MappingDelta {
6614 attached: u32::try_from(guard.mapping_slots.len()).unwrap_or(u32::MAX),
6615 ..MappingDelta::default()
6616 });
6617 child_mutation.set_resident_delta(
6618 guard
6619 .resident_counts_from_all_slots()?
6620 .checked_positive_delta()?,
6621 );
6622 if let Err(error) = guard.commit_mutation(child_mutation) {
6623 if let Err(cleanup_error) = Self::abort_unpublished_clone(&mut guard) {
6624 warn!(
6625 "fork child publication failed ({error}); unpublished cleanup also failed \
6626 ({cleanup_error})"
6627 );
6628 return Err(StarryError::BadState);
6629 }
6630 return Err(error);
6631 }
6632 }
6633 crate::syscall::memfd_apply_shared_writable_deltas(&child_memfd_deltas);
6634 drop(guard);
6635
6636 Ok(new_aspace)
6637 }
6638}
6639
6640#[cfg(all(test, not(axtest)))]
6641fn page_fault_completion_updates_only_success_for_test() -> bool {
6642 use core::cell::Cell;
6643
6644 let calls = Cell::new(0);
6645 let observed = Cell::new(VirtAddr::from(0));
6646 let success = complete_page_fault_with(true, VirtAddr::from(0x4567), |vaddr| {
6647 calls.set(calls.get() + 1);
6648 observed.set(vaddr);
6649 });
6650 let rejected = complete_page_fault_with(false, VirtAddr::from(0x89ab), |_| {
6651 calls.set(calls.get() + 1);
6652 });
6653
6654 success && !rejected && calls.get() == 1 && observed.get() == VirtAddr::from(0x4567)
6655}
6656
6657impl fmt::Debug for AddrSpace {
6658 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6659 f.debug_struct("AddrSpace")
6660 .field("id", &self.id)
6661 .field("layout", &self.layout)
6662 .field("page_table_root", &self.pt.root_paddr())
6663 .field("vma_root", &self.vma_root)
6664 .field("vm_epoch", &self.vm_epoch())
6665 .finish()
6666 }
6667}
6668
6669impl Drop for AddrSpace {
6670 fn drop(&mut self) {
6671 let has_retired_batches = self.pending_retired_mapping_batches() != 0;
6679 if !self.vma_root.is_empty() || !self.mapping_slots.is_empty() || has_retired_batches {
6680 warn!(
6681 "address space {} dropped before retire/reclaim; mappings intentionally retained",
6682 self.id.get()
6683 );
6684 self.pt.leak();
6690 if has_retired_batches {
6691 let batches = core::mem::take(&mut *self.retired_mapping_batches.lock());
6692 for batch in batches {
6693 core::mem::forget(batch);
6694 }
6695 }
6696 }
6697 }
6698}
6699
6700#[cfg(test)]
6701mod tests {
6702 use alloc::sync::Arc;
6703 use core::sync::atomic::AtomicUsize;
6704
6705 use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr};
6706
6707 use super::{
6708 AddressSpaceId, MutationError, MutationGate, TlbRange, VmEpoch,
6709 prepare_mapping_publication_mutation,
6710 };
6711
6712 #[cfg(all(test, not(axtest)))]
6713 #[test]
6714 fn page_fault_completion_updates_only_success() {
6715 assert!(super::page_fault_completion_updates_only_success_for_test());
6716 }
6717
6718 #[cfg_attr(axtest, axtest::axtest)]
6719 #[cfg_attr(not(axtest), test)]
6720 fn fresh_mapping_publication_has_no_tlb_targets() {
6721 let gate = MutationGate::new();
6722 let id = AddressSpaceId::allocate();
6723 let targets = Arc::new(AtomicUsize::new(0b1110));
6724 let start = VirtAddr::from(0x20_0000);
6725
6726 let fresh =
6727 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, false);
6728 assert_eq!(fresh.receipt().tlb_obligation.targets(), 0);
6729
6730 let replacement =
6731 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, true);
6732 assert_eq!(replacement.receipt().tlb_obligation.targets(), 0b1110);
6733 }
6734
6735 #[cfg_attr(axtest, axtest::axtest)]
6736 #[cfg_attr(not(axtest), test)]
6737 fn fresh_mapping_cannot_reuse_range_with_pending_shootdown() {
6738 let gate = MutationGate::new();
6739 let id = AddressSpaceId::allocate();
6740 let targets = Arc::new(AtomicUsize::new(0b1));
6741 let start = VirtAddr::from(0x20_0000);
6742 let mut unmap = gate.begin(id, 0b1);
6743 unmap.add_tlb_range(TlbRange::new(start, PAGE_SIZE_4K).unwrap());
6744 assert_eq!(gate.commit(unmap).unwrap_err(), MutationError::TlbPending);
6745
6746 let nonoverlapping = prepare_mapping_publication_mutation(
6747 &gate,
6748 id,
6749 &targets,
6750 start + PAGE_SIZE_4K * 2,
6751 PAGE_SIZE_4K,
6752 false,
6753 );
6754 gate.validate_publish_preconditions(&nonoverlapping)
6755 .unwrap();
6756 gate.commit(nonoverlapping).unwrap();
6757
6758 let fresh =
6759 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, false);
6760 assert_eq!(
6761 gate.validate_publish_preconditions(&fresh),
6762 Err(MutationError::PendingTlbOverlap)
6763 );
6764 assert_eq!(
6765 gate.commit(fresh).unwrap_err(),
6766 MutationError::PendingTlbOverlap
6767 );
6768 assert_eq!(gate.current_epoch(), VmEpoch::new(2));
6769
6770 gate.acknowledge(id, VmEpoch::new(1), 0).unwrap().unwrap();
6771 let retry =
6772 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, false);
6773 gate.validate_publish_preconditions(&retry).unwrap();
6774 gate.commit(retry).unwrap();
6775 }
6776
6777 #[cfg_attr(axtest, axtest::axtest)]
6778 #[cfg_attr(not(axtest), test)]
6779 fn pending_full_flush_blocks_every_fresh_mapping_range() {
6780 let gate = MutationGate::new();
6781 let id = AddressSpaceId::allocate();
6782 let targets = Arc::new(AtomicUsize::new(0b1));
6783 assert_eq!(
6784 gate.commit(gate.begin(id, 0b1)).unwrap_err(),
6785 MutationError::TlbPending
6786 );
6787
6788 let fresh = prepare_mapping_publication_mutation(
6789 &gate,
6790 id,
6791 &targets,
6792 VirtAddr::from(0x40_0000),
6793 PAGE_SIZE_4K,
6794 false,
6795 );
6796 assert_eq!(
6797 gate.commit(fresh).unwrap_err(),
6798 MutationError::PendingTlbOverlap
6799 );
6800 assert_eq!(gate.current_epoch(), VmEpoch::new(1));
6801 }
6802
6803 #[cfg(axtest)]
6804 fn refault_waits_for_discard_shootdown(full_flush: bool) {
6805 use ax_runtime::hal::trap::PageFaultFlags;
6806
6807 use super::{AddrSpace, FaultResult, MappingFlags, MappingOperation, PagingError};
6808
6809 let start = VirtAddr::from(0x7200_0000);
6810 let mut aspace = AddrSpace::new_empty(start, PAGE_SIZE_4K).unwrap();
6811 let flags = MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER;
6812 aspace
6813 .map(
6814 start,
6815 PAGE_SIZE_4K,
6816 flags,
6817 true,
6818 MappingOperation::new_alloc(start, PAGE_SIZE_4K, "[discard-refault]"),
6819 )
6820 .unwrap();
6821 aspace.discard_range(start, PAGE_SIZE_4K).unwrap();
6822 let mut discard = aspace.mutation_gate.begin(aspace.id, 1);
6825 if !full_flush {
6826 discard.add_tlb_range(TlbRange::new(start, PAGE_SIZE_4K).unwrap());
6827 }
6828 assert_eq!(
6829 aspace.mutation_gate.commit(discard).unwrap_err(),
6830 MutationError::TlbPending
6831 );
6832 let epoch = aspace.vm_epoch();
6833 let plan = aspace
6834 .plan_page_fault(
6835 start,
6836 PageFaultFlags::READ | PageFaultFlags::USER,
6837 Default::default(),
6838 )
6839 .ok()
6840 .unwrap();
6841 let prepared = AddrSpace::prepare_page_fault(plan).ok().unwrap();
6842 let mut attempt = prepared.into_apply_attempt();
6843 let outcome = aspace.apply_prepared_page_fault(&mut attempt);
6844 let unpublished = matches!(aspace.pt.query(start), Err(PagingError::NotMapped))
6845 && aspace.vm_epoch() == epoch
6846 && aspace.mapping_slots.is_empty()
6847 && attempt.prepared.is_some()
6848 && !aspace.mutation_gate.needs_repair();
6849 drop(outcome);
6850 if attempt.prepared.is_some() {
6851 attempt.cancel().unwrap();
6852 }
6853 aspace
6854 .mutation_gate
6855 .acknowledge(aspace.id, epoch, 0)
6856 .unwrap()
6857 .unwrap();
6858 let retry =
6859 aspace.handle_page_fault_result(start, PageFaultFlags::READ | PageFaultFlags::USER);
6860 let recovered = matches!(retry, FaultResult::Handled) && aspace.pt.query(start).is_ok();
6861 aspace.reset_uninstalled_for_loader().unwrap();
6862 assert!(
6863 unpublished,
6864 "refault must leave the PTE, epoch and owner graph untouched until discard is \
6865 acknowledged"
6866 );
6867 assert!(
6868 recovered,
6869 "acknowledged discard must allow refault to make progress"
6870 );
6871 }
6872
6873 #[cfg(axtest)]
6874 #[axtest::axtest]
6875 fn refault_waits_for_pending_discard_range() {
6876 refault_waits_for_discard_shootdown(false);
6877 }
6878
6879 #[cfg(axtest)]
6880 #[axtest::axtest]
6881 fn refault_waits_for_pending_discard_full_flush() {
6882 refault_waits_for_discard_shootdown(true);
6883 }
6884}