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 executable_file: Option<Arc<ax_fs_ng::file::ExecutableFile>>,
753 pt: PageTable,
754 pte_domain: PageTableDomain,
757 mutation_gate: MutationGate,
759 published_epoch: Arc<core::sync::atomic::AtomicU64>,
763 pub vm_stat: ProcessVmStat,
766 resident_pages: ResidentPageCounts,
770 resident_watermark: ResidentWatermark,
771 tlb_quarantine: TlbQuarantine,
774 tlb_targets: Arc<AtomicUsize>,
778 mapping_slots: BTreeMap<MappingSlotKey, Arc<MappingSlot>>,
782 retired_mapping_batches: IrqMutex<Vec<RetiredMappingBatch>>,
787}
788
789impl AddrSpace {
790 pub const fn base(&self) -> VirtAddr {
792 self.layout.range().start
793 }
794
795 pub const fn end(&self) -> VirtAddr {
797 self.layout.task_size()
798 }
799
800 pub fn size(&self) -> usize {
802 self.layout.range().size()
803 }
804
805 pub const fn stack_top(&self) -> VirtAddr {
807 self.layout.stack_top()
808 }
809
810 pub(crate) const fn heap_start(&self) -> usize {
813 self.heap.start
814 }
815
816 pub(crate) const fn heap_break(&self) -> usize {
818 self.heap.current
819 }
820
821 pub(crate) fn set_executable_file(&mut self, file: ax_fs_ng::file::ExecutableFile) {
823 self.executable_file = Some(Arc::new(file));
824 }
825
826 fn take_file_accesses(
829 &mut self,
830 ) -> (
831 Option<Arc<ax_fs_ng::file::ExecutableFile>>,
832 Vec<Arc<ax_fs_ng::file::WriteAccess>>,
833 ) {
834 let mut writers = Vec::new();
835 for entry in self.vma_root.iter_entries() {
836 if let Some(access) = entry.operation().take_write_access() {
837 writers.push(access);
838 }
839 }
840 (self.executable_file.take(), writers)
841 }
842
843 pub(crate) fn set_executable_data_layout(&mut self, start: usize, end: usize) -> StarryResult {
846 let layout = ExecutableDataLayout::try_new(start, end)?;
847 if start < self.base().as_usize() || end > self.end().as_usize() {
848 return Err(StarryError::MalformedExecutable);
849 }
850 self.executable_data = layout;
851 Ok(())
852 }
853
854 pub(crate) const fn executable_data_bounds(&self) -> (usize, usize) {
855 (self.executable_data.start, self.executable_data.end)
856 }
857
858 pub(crate) fn executable_data_size(&self) -> Option<usize> {
859 self.executable_data.size()
860 }
861
862 pub(crate) fn resize_heap_break(
870 &mut self,
871 requested: usize,
872 initial_mapping_end: usize,
873 ) -> StarryResult<AddressSpaceMutationOutcome> {
874 let old_break = self.heap.current;
875 let old_aligned = checked_page_align_up(old_break)?;
876 let new_aligned = checked_page_align_up(requested)?;
877
878 let outcome = if new_aligned > old_aligned {
879 let map_start = initial_mapping_end.max(old_aligned);
880 let map_size = new_aligned.saturating_sub(map_start);
881 if map_size == 0 {
882 AddressSpaceMutationOutcome::Complete
883 } else {
884 let start = VirtAddr::from(map_start);
885 let flags = MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER;
886 self.map_with_permissions_mode_classified(
887 start,
888 map_size,
889 MappingPermissions {
890 current: flags,
891 reported: flags,
892 maximum: flags,
893 },
894 false,
895 MappingOperation::new_alloc(start, PAGE_SIZE_4K, "[heap]"),
896 MappingPublication::new(false),
897 )?
898 }
899 } else if new_aligned < old_aligned {
900 let unmap_start = initial_mapping_end.max(new_aligned);
901 let unmap_size = old_aligned.saturating_sub(unmap_start);
902 if unmap_size == 0 {
903 AddressSpaceMutationOutcome::Complete
904 } else {
905 self.unmap_classified(VirtAddr::from(unmap_start), unmap_size)?
906 }
907 } else {
908 AddressSpaceMutationOutcome::Complete
909 };
910
911 self.heap.current = requested;
914 Ok(outcome)
915 }
916
917 pub(crate) fn translate(&self, vaddr: VirtAddr) -> StarryResult<PhysAddr> {
920 self.pt
921 .query(vaddr)
922 .map(|(paddr, ..)| paddr)
923 .map_err(Into::into)
924 }
925
926 pub(crate) fn resident_span(&self, vaddr: VirtAddr) -> Option<usize> {
931 self.pt.query(vaddr).ok().map(|(_, _, size)| size)
932 }
933
934 pub(crate) fn resident_bytes_from(&self, vaddr: VirtAddr) -> Option<usize> {
938 let key = MappingSlotKey {
939 space_id: self.id,
940 va: vaddr,
941 };
942 let (_, slot) = self.mapping_slots.range(..=key).next_back()?;
943 if slot.state() != SlotState::Present {
944 return None;
945 }
946 let bytes = PAGE_SIZE_4K.checked_shl(slot.page_order.get().into())?;
947 let end = slot.va.checked_add(bytes)?;
948 (vaddr >= slot.va && vaddr < end).then(|| end.as_usize() - vaddr.as_usize())
949 }
950
951 fn mapping_slots_overlapping(
958 &self,
959 range: VirtAddrRange,
960 ) -> impl Iterator<Item = (&MappingSlotKey, &Arc<MappingSlot>)> {
961 let start = MappingSlotKey {
962 space_id: self.id,
963 va: range.start,
964 };
965 let end = MappingSlotKey {
966 space_id: self.id,
967 va: range.end,
968 };
969 let predecessor = self
970 .mapping_slots
971 .range(..start)
972 .next_back()
973 .filter(move |(key, slot)| key.space_id == self.id && slot.overlaps(range));
974 let inside = self
975 .mapping_slots
976 .range(start..end)
977 .filter(move |(key, slot)| key.space_id == self.id && slot.overlaps(range));
978 predecessor.into_iter().chain(inside)
979 }
980
981 fn mapping_slot_summary(
982 &self,
983 range: VirtAddrRange,
984 ) -> StarryResult<(usize, usize, ResidentPageCounts)> {
985 let mut slots = 0usize;
986 let mut materialized_pages = 0usize;
987 let mut resident = ResidentPageCounts::default();
988 for (_, slot) in self.mapping_slots_overlapping(range) {
989 if slot.state() != SlotState::Present {
990 return Err(StarryError::BadState);
991 }
992 let pages = 1usize
993 .checked_shl(slot.page_order.get().into())
994 .ok_or(StarryError::BadState)?;
995 slots = slots.checked_add(1).ok_or(StarryError::BadState)?;
996 materialized_pages = materialized_pages
997 .checked_add(pages)
998 .ok_or(StarryError::BadState)?;
999 resident.checked_add_pages(
1000 slot.resident_kind(),
1001 u64::try_from(pages).map_err(|_| StarryError::BadState)?,
1002 )?;
1003 }
1004 Ok((slots, materialized_pages, resident))
1005 }
1006
1007 fn resident_counts_from_all_slots(&self) -> StarryResult<ResidentPageCounts> {
1008 let mut resident = ResidentPageCounts::default();
1009 for slot in self.mapping_slots.values() {
1010 if slot.state() != SlotState::Present {
1011 return Err(StarryError::BadState);
1012 }
1013 let pages = 1u64
1014 .checked_shl(slot.page_order.get().into())
1015 .ok_or(StarryError::BadState)?;
1016 resident.checked_add_pages(slot.resident_kind(), pages)?;
1017 }
1018 Ok(resident)
1019 }
1020
1021 fn occupied_pte_leaves_overlapping(
1028 &self,
1029 ranges: &[VirtAddrRange],
1030 ) -> StarryResult<Vec<OccupiedPteLeaf>> {
1031 let mut leaves = Vec::new();
1032 for range in ranges {
1033 for entry in self.pt.walk_occupied_range(range.start, range.end) {
1034 let leaf_start = entry.vaddr;
1035 let page_size = self
1036 .pt
1037 .mapping_size_for_level(entry.level)
1038 .ok_or(StarryError::BadState)?;
1039 let leaf_end = leaf_start
1040 .checked_add(page_size)
1041 .ok_or(StarryError::BadState)?;
1042 if leaf_start >= range.end || leaf_end <= range.start {
1043 continue;
1044 }
1045 if leaf_start < range.start || leaf_end > range.end {
1046 return Err(StarryError::OperationNotSupported);
1047 }
1048 let is_directory_level = entry.level > 1;
1049 leaves.try_reserve(1).map_err(|_| StarryError::NoMemory)?;
1050 leaves.push(OccupiedPteLeaf {
1051 range: VirtAddrRange::new(leaf_start, leaf_end),
1052 paddr: entry.pte.paddr(is_directory_level),
1053 flags: entry.pte.config(is_directory_level),
1054 });
1055 }
1056 }
1057 Ok(leaves)
1058 }
1059
1060 fn materialized_slots_overlapping(
1065 &self,
1066 ranges: &[VirtAddrRange],
1067 ) -> StarryResult<Vec<(MappingSlotKey, Arc<MappingSlot>, OccupiedPteLeaf)>> {
1068 let leaves = self.occupied_pte_leaves_overlapping(ranges)?;
1069 let mut slots = Vec::new();
1070 slots
1071 .try_reserve(leaves.len())
1072 .map_err(|_| StarryError::NoMemory)?;
1073 for leaf in leaves {
1074 let key = MappingSlotKey {
1075 space_id: self.id,
1076 va: leaf.range.start,
1077 };
1078 let slot = self
1079 .mapping_slots
1080 .get(&key)
1081 .cloned()
1082 .ok_or(StarryError::BadState)?;
1083 let expected_size = PAGE_SIZE_4K
1084 .checked_shl(slot.page_order.get().into())
1085 .ok_or(StarryError::BadState)?;
1086 if slot.state() != SlotState::Present
1087 || slot.mm_id != self.id
1088 || slot.va != key.va
1089 || expected_size != leaf.range.size()
1090 || slot.mapped_paddr() != Some(leaf.paddr)
1091 {
1092 return Err(StarryError::BadState);
1093 }
1094 slots.push((key, slot, leaf));
1095 }
1096 let overlapping_slots = ranges
1097 .iter()
1098 .map(|range| self.mapping_slots_overlapping(*range).count())
1099 .sum::<usize>();
1100 if overlapping_slots != slots.len() {
1101 return Err(StarryError::BadState);
1102 }
1103 Ok(slots)
1104 }
1105
1106 pub(crate) fn validate_materialized_leaf_boundaries(
1111 &self,
1112 start: VirtAddr,
1113 size: usize,
1114 ) -> StarryResult {
1115 self.validate_region(start, size)?;
1116 let range =
1117 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
1118 for (key, slot, leaf) in self.materialized_slots_overlapping(&[range])? {
1119 let page_size = leaf.range.size();
1120 let leaf_end = slot
1121 .va
1122 .checked_add(page_size)
1123 .ok_or(StarryError::BadState)?;
1124 if slot.va < range.start || leaf_end > range.end {
1125 return Err(StarryError::OperationNotSupported);
1126 }
1127 if key.va != slot.va
1128 || slot.mm_id != self.id
1129 || slot.state() != SlotState::Present
1130 || slot.mapped_paddr() != Some(leaf.paddr)
1131 {
1132 return Err(StarryError::BadState);
1133 }
1134 }
1135 Ok(())
1136 }
1137
1138 fn apply_partial_huge_splits(
1143 &mut self,
1144 range: VirtAddrRange,
1145 ) -> StarryResult<Vec<AppliedHugeSplit>> {
1146 let mut candidates = Vec::new();
1147 candidates
1148 .try_reserve(2)
1149 .map_err(|_| StarryError::NoMemory)?;
1150 for (key, slot) in self.mapping_slots_overlapping(range) {
1151 if slot.page_order == PageOrder::BASE || !slot.overlaps(range) {
1152 continue;
1153 }
1154 let slot_size = PAGE_SIZE_4K
1155 .checked_shl(slot.page_order.get().into())
1156 .ok_or(StarryError::BadState)?;
1157 let slot_end = slot
1158 .va
1159 .checked_add(slot_size)
1160 .ok_or(StarryError::BadState)?;
1161 if range.start <= slot.va && slot_end <= range.end {
1162 continue;
1163 }
1164 candidates
1165 .try_reserve(1)
1166 .map_err(|_| StarryError::NoMemory)?;
1167 candidates.push((*key, slot.clone()));
1168 }
1169
1170 let mut applied = Vec::new();
1171 applied
1172 .try_reserve(candidates.len())
1173 .map_err(|_| StarryError::NoMemory)?;
1174 for (key, slot) in candidates {
1175 match self.apply_one_partial_huge_split(key, slot) {
1176 Ok(split) => applied.push(split),
1177 Err(error) => {
1178 if !self.rollback_applied_huge_splits(applied) {
1179 self.mutation_gate.mark_needs_repair();
1180 return Err(StarryError::BadState);
1181 }
1182 return Err(error);
1183 }
1184 }
1185 }
1186 Ok(applied)
1187 }
1188
1189 fn apply_partial_huge_splits_for_ranges(
1194 &mut self,
1195 ranges: &[VirtAddrRange],
1196 ) -> StarryResult<Vec<AppliedHugeSplit>> {
1197 let capacity = ranges.len().checked_mul(2).ok_or(StarryError::NoMemory)?;
1198 let mut applied = Vec::new();
1199 applied
1200 .try_reserve_exact(capacity)
1201 .map_err(|_| StarryError::NoMemory)?;
1202 for range in ranges {
1203 match self.apply_partial_huge_splits(*range) {
1204 Ok(mut splits) => applied.append(&mut splits),
1205 Err(error) => {
1206 if !self.rollback_applied_huge_splits(applied) {
1207 self.mutation_gate.mark_needs_repair();
1208 return Err(StarryError::BadState);
1209 }
1210 return Err(error);
1211 }
1212 }
1213 }
1214 Ok(applied)
1215 }
1216
1217 fn apply_one_partial_huge_split(
1218 &mut self,
1219 old_key: MappingSlotKey,
1220 old_slot: Arc<MappingSlot>,
1221 ) -> StarryResult<AppliedHugeSplit> {
1222 if old_slot.page_order != PageOrder::new(9)
1225 || old_slot.state() != SlotState::Present
1226 || !self
1227 .mapping_slots
1228 .get(&old_key)
1229 .is_some_and(|slot| Arc::ptr_eq(slot, &old_slot))
1230 {
1231 return Err(StarryError::OperationNotSupported);
1232 }
1233 let block_size = PAGE_SIZE_4K
1234 .checked_shl(old_slot.page_order.get().into())
1235 .ok_or(StarryError::BadState)?;
1236 let block_range = VirtAddrRange::try_from_start_size(old_slot.va, block_size)
1237 .ok_or(StarryError::BadState)?;
1238 let (mapped_paddr, _, mapped_size) = self.pt.query(old_slot.va)?;
1239 if old_slot.mapped_paddr() != Some(mapped_paddr)
1240 || mapped_size != block_size
1241 || old_slot.page.frame().size() < block_size
1242 {
1243 return Err(StarryError::BadState);
1244 }
1245
1246 let child_count = block_size / PAGE_SIZE_4K;
1247 let mut child_keys = Vec::new();
1248 let mut child_slots = Vec::new();
1249 child_keys
1250 .try_reserve_exact(child_count)
1251 .map_err(|_| StarryError::NoMemory)?;
1252 child_slots
1253 .try_reserve_exact(child_count)
1254 .map_err(|_| StarryError::NoMemory)?;
1255 for index in 0..child_count {
1256 let offset = index
1257 .checked_mul(PAGE_SIZE_4K)
1258 .ok_or(StarryError::BadState)?;
1259 let va = old_slot
1260 .va
1261 .checked_add(offset)
1262 .ok_or(StarryError::BadState)?;
1263 let key = MappingSlotKey {
1264 space_id: self.id,
1265 va,
1266 };
1267 let child = MappingSlot::new_with_frame_offset(
1268 old_slot.mapping,
1269 self.id,
1270 va,
1271 PageOrder::BASE,
1272 old_slot.page.clone(),
1273 old_slot
1274 .frame_offset()
1275 .checked_add(offset)
1276 .ok_or(StarryError::BadState)?,
1277 old_slot.resident_kind(),
1278 )
1279 .ok_or(StarryError::BadState)?;
1280 child_keys.push(key);
1281 child_slots.push(Arc::new(child));
1282 }
1283
1284 let mut next_mapping_slots = self.mapping_slots.clone();
1287 let removed = next_mapping_slots
1288 .remove(&old_key)
1289 .ok_or(StarryError::BadState)?;
1290 if !Arc::ptr_eq(&removed, &old_slot) {
1291 return Err(StarryError::BadState);
1292 }
1293 for (key, slot) in child_keys.iter().copied().zip(child_slots.iter().cloned()) {
1294 if next_mapping_slots.insert(key, slot).is_some() {
1295 return Err(StarryError::BadState);
1296 }
1297 }
1298
1299 let old_keys = [old_key];
1303 let mut graph_reservation = old_slot
1304 .page
1305 .prepare_mapping_graph_replace(&old_keys, &child_keys)
1306 .map_err(|error| match error {
1307 MappingGraphError::ResourceExhausted | MappingGraphError::RefOverflow => {
1308 StarryError::NoMemory
1309 }
1310 _ => StarryError::BadState,
1311 })?;
1312
1313 let deposit = old_slot
1314 .take_huge_split_deposit()
1315 .ok_or(StarryError::BadState)?;
1316 let mutation_gate = &self.mutation_gate;
1317 let pte_domain = &self.pte_domain;
1318 let pt = &mut self.pt;
1319 let stripe = pte_domain.lock_range(block_range);
1320 let installed = match pt.try_split_huge_page_with(deposit) {
1321 Ok(installed) => installed,
1322 Err(failure) => {
1323 let (error, deposit) = failure.into_parts();
1324 match old_slot.restore_huge_split_deposit(deposit) {
1325 Ok(()) => {
1326 drop(stripe);
1327 drop(graph_reservation);
1328 return Err(error.into());
1329 }
1330 Err(orphaned) => {
1331 drop(stripe);
1335 drop(graph_reservation);
1336 drop(orphaned);
1337 mutation_gate.mark_needs_repair();
1338 return Err(StarryError::BadState);
1339 }
1340 }
1341 }
1342 };
1343
1344 if let Err(graph_error) = old_slot.page.replace_mapping_graph_reserved(
1345 &old_keys,
1346 &child_keys,
1347 &mut graph_reservation,
1348 ) {
1349 let restored = pt.restore_huge_split(installed);
1350 match restored {
1351 Ok(deposit) => match old_slot.restore_huge_split_deposit(deposit) {
1352 Ok(()) => {
1353 drop(stripe);
1354 drop(graph_reservation);
1355 return Err(match graph_error {
1356 MappingGraphError::ResourceExhausted
1357 | MappingGraphError::RefOverflow => StarryError::NoMemory,
1358 _ => StarryError::BadState,
1359 });
1360 }
1361 Err(orphaned) => {
1362 drop(stripe);
1363 drop(graph_reservation);
1364 drop(orphaned);
1365 }
1366 },
1367 Err(_) => {
1368 drop(stripe);
1369 drop(graph_reservation);
1370 }
1371 }
1372 mutation_gate.mark_needs_repair();
1373 return Err(StarryError::BadState);
1374 }
1375
1376 let mut published_children = 0usize;
1377 for child in &child_slots {
1378 if !child.publish_after_graph_replace() {
1379 break;
1380 }
1381 published_children += 1;
1382 }
1383 let old_detached =
1384 published_children == child_slots.len() && old_slot.detach_after_graph_replace();
1385 if !old_detached {
1386 for child in child_slots[..published_children].iter().rev() {
1387 let _ = child.reserve_after_graph_replace();
1388 }
1389 let graph_restored = old_slot
1390 .page
1391 .replace_mapping_graph_reserved(&child_keys, &old_keys, &mut graph_reservation)
1392 .is_ok();
1393 let restored = pt.restore_huge_split(installed);
1394 let (deposit_restored, orphaned) = match restored {
1395 Ok(deposit) => match old_slot.restore_huge_split_deposit(deposit) {
1396 Ok(()) => (true, None),
1397 Err(orphaned) => (false, Some(orphaned)),
1398 },
1399 Err(_) => (false, None),
1400 };
1401 drop(stripe);
1402 drop(graph_reservation);
1403 drop(orphaned);
1404 if graph_restored && deposit_restored {
1405 return Err(StarryError::BadState);
1406 }
1407 mutation_gate.mark_needs_repair();
1408 return Err(StarryError::BadState);
1409 }
1410
1411 drop(stripe);
1412 drop(graph_reservation);
1413 let previous_mapping_slots =
1414 core::mem::replace(&mut self.mapping_slots, next_mapping_slots);
1415 Ok(AppliedHugeSplit {
1416 installed,
1417 old_slot,
1418 child_keys,
1419 child_slots,
1420 previous_mapping_slots,
1421 })
1422 }
1423
1424 fn rollback_applied_huge_splits(&mut self, mut splits: Vec<AppliedHugeSplit>) -> bool {
1425 while let Some(split) = splits.pop() {
1426 let block_range = VirtAddrRange::try_from_start_size(
1427 split.installed.block_vaddr(),
1428 split.installed.block_size(),
1429 );
1430 let Some(block_range) = block_range else {
1431 return false;
1432 };
1433 let old_key = MappingSlotKey {
1434 space_id: self.id,
1435 va: split.old_slot.va,
1436 };
1437 let old_keys = [old_key];
1438 let mut graph_reservation = match split
1439 .old_slot
1440 .page
1441 .prepare_mapping_graph_replace(&split.child_keys, &old_keys)
1442 {
1443 Ok(reservation) => reservation,
1444 Err(_) => return false,
1445 };
1446 let pte_domain = &self.pte_domain;
1447 let pt = &mut self.pt;
1448 let stripe = pte_domain.lock_range(block_range);
1449 let Ok(deposit) = pt.restore_huge_split(split.installed) else {
1450 drop(stripe);
1451 drop(graph_reservation);
1452 return false;
1453 };
1454 if split
1455 .old_slot
1456 .page
1457 .replace_mapping_graph_reserved(
1458 &split.child_keys,
1459 &old_keys,
1460 &mut graph_reservation,
1461 )
1462 .is_err()
1463 {
1464 let orphaned = split.old_slot.restore_huge_split_deposit(deposit).err();
1465 drop(stripe);
1466 drop(graph_reservation);
1467 drop(orphaned);
1468 return false;
1469 }
1470 let slots_restored = !split
1471 .child_slots
1472 .iter()
1473 .any(|slot| !slot.detach_after_graph_replace())
1474 && split.old_slot.restore_after_graph_replace();
1475 let orphaned = split.old_slot.restore_huge_split_deposit(deposit).err();
1476 let deposit_restored = orphaned.is_none();
1477 drop(stripe);
1478 drop(graph_reservation);
1479 drop(orphaned);
1480 if !slots_restored || !deposit_restored {
1481 return false;
1482 }
1483 self.mapping_slots = split.previous_mapping_slots;
1484 }
1485 true
1486 }
1487
1488 fn capture_protection_leaf_preimage(
1489 &self,
1490 range: VirtAddrRange,
1491 ) -> StarryResult<Vec<ProtectionLeafPreimage>> {
1492 let occupied = self.occupied_pte_leaves_overlapping(&[range])?;
1493 let mut leaves = Vec::new();
1494 leaves
1495 .try_reserve(occupied.len())
1496 .map_err(|_| StarryError::NoMemory)?;
1497 for leaf in occupied {
1498 leaves.push(ProtectionLeafPreimage {
1499 va: leaf.range.start,
1500 paddr: leaf.paddr,
1501 page_size: leaf.range.size(),
1502 flags: leaf.flags,
1503 });
1504 }
1505 Ok(leaves)
1506 }
1507
1508 fn restore_protection_leaf_preimage(&mut self, leaves: &[ProtectionLeafPreimage]) -> bool {
1509 for leaf in leaves.iter().rev() {
1510 let Ok((paddr, _, page_size)) = self.pt.query(leaf.va) else {
1511 return false;
1512 };
1513 if paddr != leaf.paddr || page_size != leaf.page_size {
1514 return false;
1515 }
1516 if self.pt.protect_page(leaf.va, leaf.flags) != Ok(leaf.page_size) {
1517 return false;
1518 }
1519 }
1520 true
1521 }
1522
1523 fn abort_unpublished_protection(
1524 &mut self,
1525 vma_root: Arc<VmaMap>,
1526 vm_stat: ProcessVmStatSnapshot,
1527 leaves: &[ProtectionLeafPreimage],
1528 splits: Vec<AppliedHugeSplit>,
1529 original_error: StarryError,
1530 ) -> StarryResult {
1531 let ptes_restored = self.restore_protection_leaf_preimage(leaves);
1532 self.vma_root = vma_root;
1533 self.vm_stat.restore(vm_stat);
1534 let splits_restored = self.rollback_applied_huge_splits(splits);
1535 if ptes_restored && splits_restored {
1536 self.mutation_gate.clear_repair();
1537 Err(original_error)
1538 } else {
1539 self.mutation_gate.mark_needs_repair();
1540 Err(StarryError::BadState)
1541 }
1542 }
1543
1544 #[cfg(not(any(target_arch = "aarch64", target_arch = "loongarch64")))]
1555 pub(crate) unsafe fn share_kernel_root_entries_from(
1556 &mut self,
1557 source: RootEntryShare<'_>,
1558 ) -> Result<(), PagingError> {
1559 unsafe { source.install_into(&mut self.pt) }
1562 }
1563
1564 const fn materialized_root(&self) -> PhysAddr {
1566 self.pt.root_paddr()
1567 }
1568
1569 pub fn contains_range(&self, start: VirtAddr, size: usize) -> bool {
1571 let Some(range) = VirtAddrRange::try_from_start_size(start, size) else {
1572 return false;
1573 };
1574 range.start >= self.base() && range.end <= self.end()
1575 }
1576
1577 pub fn new_empty(base: VirtAddr, size: usize) -> StarryResult<Self> {
1579 Self::new_with_layout(UserVirtualAddressLayout::from_range(base, size)?)
1580 }
1581
1582 pub(crate) fn new_user(layout: UserVirtualAddressLayout) -> StarryResult<Self> {
1584 Self::new_with_layout(layout)
1585 }
1586
1587 fn new_with_layout(layout: UserVirtualAddressLayout) -> StarryResult<Self> {
1588 Ok(Self {
1589 id: AddressSpaceId::allocate(),
1590 layout,
1591 vma_root: Arc::new(VmaMap::default()),
1592 heap: HeapState::new(USER_HEAP_BASE),
1593 executable_data: ExecutableDataLayout::default(),
1594 executable_file: None,
1595 pt: PageTable::new(PagingAllocator).map_err(|_| StarryError::NoMemory)?,
1596 pte_domain: PageTableDomain::new(),
1597 mutation_gate: MutationGate::new(),
1598 published_epoch: Arc::new(core::sync::atomic::AtomicU64::new(0)),
1599 vm_stat: ProcessVmStat::new(),
1600 resident_pages: ResidentPageCounts::default(),
1601 resident_watermark: ResidentWatermark::new(),
1602 tlb_quarantine: TlbQuarantine::default(),
1603 tlb_targets: Arc::new(AtomicUsize::new(0)),
1604 mapping_slots: BTreeMap::new(),
1605 retired_mapping_batches: IrqMutex::new(Vec::new()),
1606 })
1607 }
1608
1609 pub const fn address_space_id(&self) -> AddressSpaceId {
1612 self.id
1613 }
1614
1615 pub fn vm_epoch(&self) -> VmEpoch {
1617 self.mutation_gate.current_epoch()
1618 }
1619
1620 pub(crate) fn tlb_targets(&self) -> Arc<AtomicUsize> {
1621 self.tlb_targets.clone()
1622 }
1623
1624 pub(crate) fn published_epoch_source(&self) -> Arc<core::sync::atomic::AtomicU64> {
1625 self.published_epoch.clone()
1626 }
1627
1628 fn publish_mutation_classified(
1629 &mut self,
1630 mutation: PreparedMutation,
1631 ) -> Result<MutationPublication, CommitMutationError> {
1632 let mut next_resident = self.resident_pages;
1633 next_resident
1634 .checked_apply(mutation.receipt().resident_delta)
1635 .map_err(CommitMutationError::Unpublished)?;
1636 match self.mutation_gate.commit(mutation) {
1637 Ok(receipt) => {
1638 self.resident_pages = next_resident;
1639 self.published_epoch.store(
1640 receipt.new_epoch.get(),
1641 core::sync::atomic::Ordering::Release,
1642 );
1643 self.resident_watermark
1644 .observe_resident_total(self.resident_pages.total());
1645 Ok(MutationPublication::Complete)
1646 }
1647 Err(MutationError::TlbPending) => {
1648 self.resident_pages = next_resident;
1649 self.published_epoch.store(
1650 self.mutation_gate.current_epoch().get(),
1651 core::sync::atomic::Ordering::Release,
1652 );
1653 self.resident_watermark
1658 .observe_resident_total(self.resident_pages.total());
1659 Ok(MutationPublication::PendingTlb)
1660 }
1661 Err(error) => Err(CommitMutationError::Unpublished(
1662 Self::map_unpublished_mutation_error(error),
1663 )),
1664 }
1665 }
1666
1667 fn map_unpublished_mutation_error(error: MutationError) -> StarryError {
1668 match error {
1669 MutationError::ResourceExhausted => StarryError::NoMemory,
1670 MutationError::PendingTlbOverlap => StarryError::ResourceBusy,
1671 MutationError::NeedsRepair
1672 | MutationError::EpochExhausted
1673 | MutationError::EpochConflict
1674 | MutationError::WrongState
1675 | MutationError::ApplyFailed
1676 | MutationError::TlbPending => StarryError::BadState,
1677 }
1678 }
1679
1680 fn commit_mutation_classified(
1681 &mut self,
1682 mutation: PreparedMutation,
1683 ) -> Result<(), CommitMutationError> {
1684 match self.publish_mutation_classified(mutation)? {
1685 MutationPublication::Complete => Ok(()),
1686 MutationPublication::PendingTlb => {
1687 self.service_pending_tlb()
1692 .map(|_| ())
1693 .map_err(CommitMutationError::PublishedPendingTlb)
1694 }
1695 }
1696 }
1697
1698 fn commit_mutation(&mut self, mutation: PreparedMutation) -> StarryResult {
1699 match self.commit_mutation_classified(mutation) {
1700 Ok(()) => Ok(()),
1701 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
1702 Err(CommitMutationError::Unpublished(error)) => {
1703 self.mutation_gate.mark_needs_repair();
1709 Err(error)
1710 }
1711 }
1712 }
1713
1714 pub fn service_pending_tlb(&self) -> StarryResult<usize> {
1721 let requests = self
1722 .mutation_gate
1723 .pending_requests()
1724 .map_err(|_| StarryError::NoMemory)?;
1725 Self::flush_tlb_requests(&requests, &self.tlb_targets)?;
1726 self.acknowledge_tlb_requests(&requests)
1727 }
1728
1729 fn flush_tlb_requests(requests: &[TlbRequest], tlb_targets: &AtomicUsize) -> StarryResult {
1730 for request in requests {
1731 let pending_targets = request.pending();
1732 let live_targets =
1736 pending_targets & tlb_targets.load(core::sync::atomic::Ordering::Acquire);
1737 if request.ranges.is_empty() && live_targets != 0 {
1738 ax_runtime::hal::cache::flush_tlb_all_on_cpus(live_targets)
1739 .map_err(Self::map_tlb_shootdown_error)?;
1740 } else if live_targets != 0 {
1741 for range in &request.ranges {
1742 ax_runtime::hal::cache::flush_tlb_range_on_cpus(
1743 live_targets,
1744 range.start,
1745 range.size,
1746 )
1747 .map_err(Self::map_tlb_shootdown_error)?;
1748 }
1749 }
1750 }
1751 Ok(())
1752 }
1753
1754 fn map_tlb_shootdown_error(error: ax_runtime::hal::cache::TlbShootdownError) -> StarryError {
1755 match error {
1756 ax_runtime::hal::cache::TlbShootdownError::Timeout => StarryError::TimedOut,
1757 ax_runtime::hal::cache::TlbShootdownError::Unsupported
1758 | ax_runtime::hal::cache::TlbShootdownError::CpuOffline => StarryError::Unsupported,
1759 ax_runtime::hal::cache::TlbShootdownError::GenerationExhausted => {
1760 StarryError::Errno(syscalls::Errno::EOVERFLOW)
1761 }
1762 ax_runtime::hal::cache::TlbShootdownError::Platform => StarryError::Io,
1763 }
1764 }
1765
1766 fn acknowledge_tlb_requests(&self, requests: &[TlbRequest]) -> StarryResult<usize> {
1767 let mut completed = 0;
1768 for request in requests {
1769 let pending_targets = request.pending();
1770 for cpu in 0..usize::BITS as usize {
1774 if pending_targets & (1usize << cpu) == 0 {
1775 continue;
1776 }
1777 match self.mutation_gate.acknowledge(self.id, request.epoch, cpu) {
1778 Ok(_) | Err(MutationError::WrongState) | Err(MutationError::TlbPending) => {}
1779 Err(_) => return Err(StarryError::BadState),
1780 }
1781 self.tlb_quarantine
1782 .acknowledge(self.id, request.epoch, cpu)
1783 .map_err(|_| StarryError::NoMemory)?;
1784 }
1785 if self
1786 .mutation_gate
1787 .pending_request(self.id, request.epoch)
1788 .is_none()
1789 {
1790 self.release_retired_mapping_owners(request.epoch);
1791 }
1792 completed += 1;
1793 }
1794 Ok(completed)
1795 }
1796
1797 fn prepare_mutation(&self) -> PreparedMutation {
1798 self.mutation_gate
1799 .begin_with_active_targets(self.id, self.tlb_targets.clone())
1800 }
1801
1802 fn prepare_metadata_mutation(&self) -> PreparedMutation {
1807 self.mutation_gate.begin(self.id, 0)
1808 }
1809
1810 fn prepare_mutation_range(&self, start: VirtAddr, size: usize) -> PreparedMutation {
1811 let mut mutation = self.prepare_mutation();
1812 if let Some(range) = TlbRange::new(start, size) {
1813 mutation.add_tlb_range(range);
1814 }
1815 mutation
1816 }
1817
1818 fn prepare_fresh_pte_mutation_range(&self, start: VirtAddr, size: usize) -> PreparedMutation {
1824 prepare_mapping_publication_mutation(
1825 &self.mutation_gate,
1826 self.id,
1827 &self.tlb_targets,
1828 start,
1829 size,
1830 false,
1831 )
1832 }
1833
1834 fn collect_retired_mapping_owner(
1841 &self,
1842 entry: &Arc<VmaEntry>,
1843 range: VirtAddrRange,
1844 owners: &mut RetiredMappingOwners,
1845 ) -> StarryResult {
1846 owners
1847 .backends
1848 .try_reserve(1)
1849 .map_err(|_| StarryError::NoMemory)?;
1850 let backend = entry.operation_clone();
1851
1852 if backend.shared_file_lease().is_some() {
1853 let start = entry.start().max(range.start).align_down_4k();
1854 let end = entry.end().min(range.end);
1855 let file_range = VirtAddrRange::new(start, end);
1856 let count = self.mapping_slots_overlapping(file_range).count();
1857 owners
1858 .cache_pins
1859 .try_reserve(count)
1860 .map_err(|_| StarryError::NoMemory)?;
1861 for (_, slot) in self.mapping_slots_overlapping(file_range) {
1862 if slot.state() != SlotState::Present
1863 || slot.mapping != backend.mapping_id()
1864 || slot.page_order != PageOrder::BASE
1865 {
1866 return Err(StarryError::BadState);
1867 }
1868 let paddr = slot.mapped_paddr().ok_or(StarryError::BadState)?;
1869 let (installed, _, page_size) = self.pt.query(slot.va)?;
1870 if installed != paddr || page_size != PAGE_SIZE_4K {
1871 return Err(StarryError::BadState);
1872 }
1873 let pin = backend
1874 .pin_file_cache_owner_for_mapping(slot.va, paddr)?
1875 .ok_or(StarryError::BadState)?;
1876 owners.cache_pins.push(pin);
1877 }
1878 }
1879 owners.backends.push(backend);
1880 Ok(())
1881 }
1882
1883 fn prepare_retired_mapping_owners(
1884 &self,
1885 range: VirtAddrRange,
1886 ) -> StarryResult<RetiredMappingOwners> {
1887 try_reserve_irq_vec(&self.retired_mapping_batches, 1).map_err(|_| StarryError::NoMemory)?;
1888
1889 let mut owners = RetiredMappingOwners::default();
1890 let mut failure = None;
1894 self.vma_root.for_each_overlapping_entry(range, |entry| {
1895 match self.collect_retired_mapping_owner(entry, range, &mut owners) {
1896 Ok(()) => true,
1897 Err(err) => {
1898 failure = Some(err);
1899 false
1900 }
1901 }
1902 });
1903 if let Some(err) = failure {
1904 return Err(err);
1905 }
1906
1907 let matching_slots = self.mapping_slots_overlapping(range).count();
1908 owners
1909 .pages
1910 .try_reserve(matching_slots)
1911 .map_err(|_| StarryError::NoMemory)?;
1912 for (_, slot) in self.mapping_slots_overlapping(range) {
1913 if slot.state() != SlotState::Present {
1914 return Err(StarryError::BadState);
1915 }
1916 owners.pages.push(slot.page.clone());
1917 }
1918 Ok(owners)
1919 }
1920
1921 fn prepare_deferred_eviction_owner(
1925 &self,
1926 page: &Arc<PageObject>,
1927 ) -> StarryResult<RetiredMappingOwners> {
1928 try_reserve_irq_vec(&self.retired_mapping_batches, 1).map_err(|_| StarryError::NoMemory)?;
1929 let mut owners = RetiredMappingOwners::default();
1930 owners
1931 .deferred_evictions
1932 .try_reserve(1)
1933 .map_err(|_| StarryError::NoMemory)?;
1934 owners.deferred_evictions.push(page.clone());
1935 Ok(owners)
1936 }
1937
1938 fn park_retired_mapping_owners(&self, epoch: VmEpoch, owners: RetiredMappingOwners) {
1939 if owners.is_empty() {
1940 return;
1941 }
1942 self.retired_mapping_batches
1945 .lock()
1946 .push(RetiredMappingBatch { epoch, owners });
1947 }
1948
1949 fn release_retired_mapping_owners(&self, epoch: VmEpoch) {
1950 loop {
1951 let batch = {
1952 let mut batches = self.retired_mapping_batches.lock();
1953 batches
1954 .iter()
1955 .position(|batch| batch.epoch == epoch)
1956 .map(|index| batches.swap_remove(index))
1957 };
1958 let Some(batch) = batch else {
1959 break;
1960 };
1961 debug_assert!(!batch.owners.is_empty());
1962 for page in &batch.owners.deferred_evictions {
1963 page.complete_eviction_tlb();
1964 }
1965 drop(batch);
1968 }
1969 }
1970
1971 fn pending_retired_mapping_batches(&self) -> usize {
1972 self.retired_mapping_batches.lock().len()
1973 }
1974
1975 pub fn pending_tlb_obligations(&self) -> usize {
1977 self.mutation_gate.pending_count()
1978 }
1979
1980 pub fn pending_tlb_requests(&self) -> StarryResult<Vec<TlbRequest>> {
1984 self.mutation_gate
1985 .pending_requests()
1986 .map_err(|_| StarryError::NoMemory)
1987 }
1988
1989 pub fn acknowledge_tlb(
1992 &self,
1993 space_id: AddressSpaceId,
1994 epoch: VmEpoch,
1995 cpu: usize,
1996 ) -> StarryResult<Vec<FrameLease>> {
1997 match self.mutation_gate.acknowledge(space_id, epoch, cpu) {
1998 Ok(_) | Err(MutationError::TlbPending) => {}
1999 Err(MutationError::WrongState)
2000 if self.tlb_quarantine.contains_request(space_id, epoch) =>
2001 {
2002 }
2007 Err(_) => return Err(StarryError::ResourceBusy),
2008 }
2009 let released = self
2010 .tlb_quarantine
2011 .acknowledge(space_id, epoch, cpu)
2012 .map_err(|_| StarryError::NoMemory)?;
2013 if self
2014 .mutation_gate
2015 .pending_request(space_id, epoch)
2016 .is_none()
2017 {
2018 self.release_retired_mapping_owners(epoch);
2019 }
2020 Ok(released)
2021 }
2022
2023 pub fn quarantine_frame(
2024 &self,
2025 frame: FrameLease,
2026 request: TlbRequest,
2027 ) -> Result<(), QuarantineFailure> {
2028 self.tlb_quarantine.try_defer(frame, request)
2029 }
2030
2031 fn validate_region(&self, start: VirtAddr, size: usize) -> StarryResult {
2032 if self.mutation_gate.needs_repair() {
2033 return Err(StarryError::BadState);
2034 }
2035 if size == 0 || !self.contains_range(start, size) {
2036 return Err(StarryError::NoMemory);
2037 }
2038 if !start.is_aligned_4k() || !is_aligned_4k(size) {
2039 return Err(StarryError::InvalidInput);
2040 }
2041 Ok(())
2042 }
2043
2044 fn capture_mapping_preimage(&self, range: VirtAddrRange) -> StarryResult<MappingPreimage> {
2048 self.capture_mapping_preimage_ranges(&[range])
2049 }
2050
2051 fn capture_mapping_preimage_ranges(
2056 &self,
2057 ranges: &[VirtAddrRange],
2058 ) -> StarryResult<MappingPreimage> {
2059 for (index, range) in ranges.iter().enumerate() {
2060 self.validate_region(range.start, range.size())?;
2061 if ranges[..index]
2062 .iter()
2063 .any(|previous| previous.overlaps(*range))
2064 {
2065 return Err(StarryError::InvalidInput);
2066 }
2067 }
2068 let resident_slots = self.materialized_slots_overlapping(ranges)?;
2069 let mut leaves = Vec::new();
2070 leaves
2071 .try_reserve(resident_slots.len())
2072 .map_err(|_| StarryError::NoMemory)?;
2073 for (key, slot, occupied_leaf) in resident_slots {
2074 let page_size = occupied_leaf.range.size();
2075 let end = slot
2076 .va
2077 .checked_add(page_size)
2078 .ok_or(StarryError::BadState)?;
2079 let range = ranges
2080 .iter()
2081 .find(|range| slot.overlaps(**range))
2082 .ok_or(StarryError::BadState)?;
2083 if slot.va < range.start || end > range.end {
2087 return Err(StarryError::OperationNotSupported);
2088 }
2089 let paddr = occupied_leaf.paddr;
2090 let backend = self
2091 .vma_root
2092 .lookup_entry(slot.va)
2093 .map(|entry| entry.operation_clone())
2094 .ok_or(StarryError::BadState)?;
2095 let page = slot.page.clone();
2096 let frame_start = page.frame().paddr().as_usize();
2097 let frame_end = frame_start
2098 .checked_add(page.frame().size())
2099 .ok_or(StarryError::BadState)?;
2100 let leaf_start = paddr.as_usize();
2101 let leaf_end = leaf_start
2102 .checked_add(page_size)
2103 .ok_or(StarryError::BadState)?;
2104 if key.va != slot.va
2105 || slot.state() != SlotState::Present
2106 || slot.mm_id != self.id
2107 || slot.mapping != backend.mapping_id()
2108 || slot.mapped_paddr() != Some(paddr)
2109 || leaf_start < frame_start
2110 || leaf_end > frame_end
2111 {
2112 return Err(StarryError::BadState);
2113 }
2114 leaves.push(ResidentLeafPreimage {
2115 va: slot.va,
2116 paddr,
2117 page_size,
2118 flags: occupied_leaf.flags,
2119 backend,
2120 page,
2121 slot,
2122 });
2123 }
2124 Ok(MappingPreimage {
2125 vma_root: self.vma_root.clone(),
2126 vm_stat: self.vm_stat.snapshot(),
2127 leaves,
2128 })
2129 }
2130
2131 fn restore_mapping_preimage(
2135 &mut self,
2136 range: VirtAddrRange,
2137 preimage: MappingPreimage,
2138 ) -> StarryResult {
2139 self.restore_mapping_preimage_ranges(&[range], preimage)
2140 }
2141
2142 fn abort_unpublished_mapping_mutation(
2150 &mut self,
2151 range: VirtAddrRange,
2152 preimage: MappingPreimage,
2153 original_error: StarryError,
2154 ) -> StarryResult {
2155 self.abort_unpublished_parked_mapping_mutation(range, preimage, None, original_error)
2156 }
2157
2158 fn abort_file_eviction_mutation(
2163 &mut self,
2164 range: VirtAddrRange,
2165 preimage: MappingPreimage,
2166 parked_epoch: Option<VmEpoch>,
2167 original_error: StarryError,
2168 ) -> Result<EvictMappingOutcome, StarryError> {
2169 match self.restore_mapping_preimage(range, preimage) {
2170 Ok(()) => {
2171 if let Some(epoch) = parked_epoch {
2172 self.release_retired_mapping_owners(epoch);
2173 }
2174 self.mutation_gate.clear_repair();
2175 Err(original_error)
2176 }
2177 Err(_) => {
2178 self.mutation_gate.mark_needs_repair();
2179 Ok(EvictMappingOutcome::NeedsRepair)
2180 }
2181 }
2182 }
2183
2184 fn abort_unpublished_parked_mapping_mutation(
2190 &mut self,
2191 range: VirtAddrRange,
2192 preimage: MappingPreimage,
2193 parked_epoch: Option<VmEpoch>,
2194 original_error: StarryError,
2195 ) -> StarryResult {
2196 self.abort_unpublished_parked_mapping_mutation_ranges(
2197 &[range],
2198 preimage,
2199 parked_epoch,
2200 original_error,
2201 )
2202 }
2203
2204 fn abort_unpublished_parked_mapping_mutation_ranges(
2205 &mut self,
2206 ranges: &[VirtAddrRange],
2207 preimage: MappingPreimage,
2208 parked_epoch: Option<VmEpoch>,
2209 original_error: StarryError,
2210 ) -> StarryResult {
2211 match self.restore_mapping_preimage_ranges(ranges, preimage) {
2212 Ok(()) => {
2213 if let Some(epoch) = parked_epoch {
2214 self.release_retired_mapping_owners(epoch);
2215 }
2216 self.mutation_gate.clear_repair();
2217 Err(original_error)
2218 }
2219 Err(_) => {
2220 self.mutation_gate.mark_needs_repair();
2221 Err(StarryError::BadState)
2222 }
2223 }
2224 }
2225
2226 fn abort_unpublished_split_mapping_mutation(
2231 &mut self,
2232 range: VirtAddrRange,
2233 preimage: MappingPreimage,
2234 parked_epoch: Option<VmEpoch>,
2235 splits: Vec<AppliedHugeSplit>,
2236 original_error: StarryError,
2237 ) -> StarryResult {
2238 self.abort_unpublished_split_mapping_mutation_ranges(
2239 &[range],
2240 preimage,
2241 parked_epoch,
2242 splits,
2243 original_error,
2244 )
2245 }
2246
2247 fn abort_unpublished_split_mapping_mutation_ranges(
2251 &mut self,
2252 ranges: &[VirtAddrRange],
2253 preimage: MappingPreimage,
2254 parked_epoch: Option<VmEpoch>,
2255 splits: Vec<AppliedHugeSplit>,
2256 original_error: StarryError,
2257 ) -> StarryResult {
2258 if self
2259 .restore_mapping_preimage_ranges(ranges, preimage)
2260 .is_ok()
2261 && self.rollback_applied_huge_splits(splits)
2262 {
2263 if let Some(epoch) = parked_epoch {
2264 self.release_retired_mapping_owners(epoch);
2265 }
2266 self.mutation_gate.clear_repair();
2267 Err(original_error)
2268 } else {
2269 self.mutation_gate.mark_needs_repair();
2270 Err(StarryError::BadState)
2271 }
2272 }
2273
2274 fn abort_unpublished_huge_splits(
2278 &mut self,
2279 splits: Vec<AppliedHugeSplit>,
2280 original_error: StarryError,
2281 ) -> StarryResult {
2282 if self.rollback_applied_huge_splits(splits) {
2283 self.mutation_gate.clear_repair();
2284 Err(original_error)
2285 } else {
2286 self.mutation_gate.mark_needs_repair();
2287 Err(StarryError::BadState)
2288 }
2289 }
2290
2291 fn restore_mapping_preimage_ranges(
2292 &mut self,
2293 ranges: &[VirtAddrRange],
2294 preimage: MappingPreimage,
2295 ) -> StarryResult {
2296 let mut current_memfds = Vec::new();
2297 for range in ranges {
2298 current_memfds.extend(crate::syscall::memfd_collect_metas_touching_mprotect_range(
2299 self,
2300 range.start,
2301 range.size(),
2302 ));
2303 }
2304 let MappingPreimage {
2305 vma_root,
2306 vm_stat,
2307 leaves,
2308 } = preimage;
2309 if self.detach_current_materialized_ranges(ranges).is_err() {
2310 self.mutation_gate.mark_needs_repair();
2311 return Err(StarryError::BadState);
2312 }
2313 for range in ranges {
2314 self.detach_mapping_slots(*range)?;
2315 }
2316 self.vma_root = vma_root.clone();
2317 for leaf in leaves {
2318 if leaf
2319 .backend
2320 .restore_resident_preimage(
2321 ResidentLeafRestore {
2322 va: leaf.va,
2323 paddr: leaf.paddr,
2324 page_size: leaf.page_size,
2325 flags: leaf.flags,
2326 page: Some(&leaf.page),
2327 },
2328 &mut self.pt,
2329 )
2330 .is_err()
2331 {
2332 self.mutation_gate.mark_needs_repair();
2333 return Err(StarryError::BadState);
2334 }
2335 let key = MappingSlotKey {
2336 space_id: self.id,
2337 va: leaf.va,
2338 };
2339 if self.mapping_slots.contains_key(&key)
2340 || !leaf.slot.restore()
2341 || self.mapping_slots.insert(key, leaf.slot).is_some()
2342 {
2343 self.mutation_gate.mark_needs_repair();
2344 return Err(StarryError::BadState);
2345 }
2346 leaf.page.set_resident_kind(
2347 self.mapping_slots
2348 .get(&key)
2349 .and_then(|slot| slot.resident_kind()),
2350 );
2351 }
2352 self.vma_root = vma_root;
2353 self.vm_stat.restore(vm_stat);
2354 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(self, ¤t_memfds);
2355 for range in ranges {
2356 let restored_memfds = crate::syscall::memfd_collect_metas_touching_mprotect_range(
2357 self,
2358 range.start,
2359 range.size(),
2360 );
2361 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
2362 self,
2363 &restored_memfds,
2364 );
2365 }
2366 Ok(())
2367 }
2368
2369 pub fn find_free_area(
2377 &self,
2378 hint: VirtAddr,
2379 size: usize,
2380 limit: VirtAddrRange,
2381 align: usize,
2382 ) -> Option<VirtAddr> {
2383 self.vma_root.find_free_area(hint, size, limit, align)
2384 }
2385
2386 pub fn find_area_snapshot(&self, vaddr: VirtAddr) -> Option<Arc<VmaSnapshot>> {
2388 self.vma_root.lookup(vaddr)
2389 }
2390
2391 pub fn vma_map_snapshot(&self) -> Arc<VmaMap> {
2393 self.vma_root.clone()
2394 }
2395
2396 pub fn vma_snapshots_in_range(
2398 &self,
2399 start: VirtAddr,
2400 size: usize,
2401 ) -> StarryResult<Vec<Arc<VmaSnapshot>>> {
2402 let range =
2403 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2404 if range.is_empty() {
2405 return Ok(Vec::new());
2406 }
2407 Ok(self.vma_root.lookup_range(range))
2408 }
2409
2410 pub(crate) fn vma_inspection_records(&self) -> StarryResult<Vec<VmaInspectionRecord>> {
2411 let mut records = Vec::new();
2412 records
2413 .try_reserve(self.vma_root.len())
2414 .map_err(|_| StarryError::NoMemory)?;
2415 for entry in self.vma_root.iter_entries() {
2416 records.push(entry.inspection_record()?);
2417 }
2418 Ok(records)
2419 }
2420
2421 pub(crate) fn max_mapped_end(&self) -> Option<VirtAddr> {
2422 self.vma_root.iter().last().map(|vma| vma.range.end)
2423 }
2424
2425 pub(crate) fn next_advice_fragment(
2426 &self,
2427 cursor: VirtAddr,
2428 end: VirtAddr,
2429 ) -> Option<VmaAdviceFragment> {
2430 self.vma_root
2431 .iter_entries()
2432 .find(|entry| entry.end() > cursor && entry.start() < end)
2433 .and_then(|entry| entry.advice_fragment(cursor, end))
2434 }
2435
2436 pub(crate) fn validate_mprotect_mapping_capabilities(
2437 &self,
2438 start: VirtAddr,
2439 size: usize,
2440 flags: MappingFlags,
2441 ) -> StarryResult<Vec<SharedFileMappingLease>> {
2442 let range =
2443 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2444 let operations = self.mapping_operation_fragments(range, true)?;
2445 let mut files = Vec::new();
2446 files
2447 .try_reserve(operations.len())
2448 .map_err(|_| StarryError::NoMemory)?;
2449 for (_, operation) in operations {
2450 operation.check_mprotect_flags(flags)?;
2451 if let Some(file) = operation.shared_file_lease() {
2452 files.push(file);
2453 }
2454 }
2455 Ok(files)
2456 }
2457
2458 pub(crate) fn shared_futex_identity(&self, address: VirtAddr) -> Option<SharedFutexIdentity> {
2459 self.vma_root
2460 .lookup_entry(address)
2461 .and_then(|entry| entry.operation().shared_futex_identity(address))
2462 }
2463
2464 pub(crate) fn mincore_probe(&self, address: VirtAddr) -> Option<VmaResidencyProbe> {
2465 self.vma_root
2466 .lookup_entry(address)
2467 .map(|entry| entry.residency_probe())
2468 }
2469
2470 pub(crate) fn mremap_source(&self, address: VirtAddr) -> Option<VmaMremapSource> {
2471 self.vma_root
2472 .lookup_entry(address)
2473 .map(|entry| entry.mremap_source())
2474 }
2475
2476 pub(crate) fn shared_file_vma_at(&self, address: VirtAddr) -> Option<SharedFileVmaRecord> {
2477 self.vma_root
2478 .lookup_entry(address)
2479 .and_then(|entry| entry.shared_file_record())
2480 }
2481
2482 pub(crate) fn shared_file_vmas(&self) -> Vec<SharedFileVmaRecord> {
2483 self.vma_root
2484 .iter_entries()
2485 .filter_map(|entry| entry.shared_file_record())
2486 .collect()
2487 }
2488
2489 #[allow(clippy::too_many_arguments)]
2490 pub(crate) fn mremap_move_from_source(
2491 &mut self,
2492 source: &VmaMremapSource,
2493 src: VirtAddr,
2494 src_size: usize,
2495 target: VirtAddr,
2496 target_size: usize,
2497 huge_page_advice: HugePageAdvice,
2498 dontunmap: bool,
2499 source_offset: usize,
2500 replace_target: bool,
2501 memlock_limit: Option<MemlockLimit>,
2502 ) -> StarryResult {
2503 let operation = source.relocated_operation(target, source_offset, target_size)?;
2504 self.mremap_move_transaction(
2505 src,
2506 src_size,
2507 target,
2508 target_size,
2509 MappingPermissions {
2510 current: source.rights(),
2511 reported: source.reported_rights(),
2512 maximum: source.max_rights(),
2513 },
2514 operation,
2515 huge_page_advice,
2516 source.lock_mode(),
2517 source.advice_policy(),
2518 dontunmap,
2519 replace_target,
2520 memlock_limit,
2521 )
2522 }
2523
2524 pub(crate) fn duplicate_shared_mremap_source(
2525 &mut self,
2526 source: &VmaMremapSource,
2527 target: VirtAddr,
2528 target_size: usize,
2529 source_offset: usize,
2530 replace_target: bool,
2531 memlock_limit: Option<MemlockLimit>,
2532 ) -> StarryResult {
2533 let object = source.shared_object().ok_or(StarryError::InvalidInput)?;
2534 let backend_start = target
2535 .as_usize()
2536 .checked_sub(source_offset)
2537 .map(VirtAddr::from_usize)
2538 .ok_or(StarryError::InvalidInput)?;
2539 self.map_mremap_duplicate(
2540 target,
2541 target_size,
2542 MappingPermissions {
2543 current: source.rights(),
2544 reported: source.reported_rights(),
2545 maximum: source.rights(),
2546 },
2547 MappingOperation::new_shared(backend_start, object),
2548 MappingPublication::mremap(
2549 replace_target,
2550 source.huge_page_advice(),
2551 source.lock_mode(),
2552 source.advice_policy(),
2553 memlock_limit,
2554 ),
2555 )
2556 }
2557
2558 fn validate_memlock_successor(
2559 &self,
2560 successor: &VmaMap,
2561 memlock_limit: Option<MemlockLimit>,
2562 ) -> StarryResult {
2563 let previous_locked = self.vma_root.locked_pages().ok_or(StarryError::BadState)?;
2564 let successor_locked = successor.locked_pages().ok_or(StarryError::BadState)?;
2565 if successor_locked <= previous_locked {
2566 return Ok(());
2567 }
2568 memlock_limit
2569 .ok_or(StarryError::BadState)?
2570 .validate(successor_locked)
2571 }
2572
2573 fn publish_vma_metadata_successor(
2574 &mut self,
2575 previous_root: Arc<VmaMap>,
2576 successor: VmaMap,
2577 operation: &'static str,
2578 ) -> StarryResult {
2579 let before_vmas = previous_root.len();
2580 let after_vmas = successor.len();
2581 let mut mutation = self.prepare_metadata_mutation();
2582 mutation.set_vma_delta(VmaDelta {
2583 split: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
2584 merged: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
2585 ..VmaDelta::default()
2586 });
2587 self.vma_root = Arc::new(successor);
2588 match self.commit_mutation_classified(mutation) {
2589 Ok(()) => Ok(()),
2590 Err(CommitMutationError::Unpublished(error)) => {
2591 self.vma_root = previous_root;
2592 Err(error)
2593 }
2594 Err(CommitMutationError::PublishedPendingTlb(error)) => {
2595 self.mutation_gate.mark_needs_repair();
2599 warn!(
2600 "metadata-only {operation} unexpectedly required TLB acknowledgement: {error}"
2601 );
2602 Err(StarryError::BadState)
2603 }
2604 }
2605 }
2606
2607 pub fn advise_huge_pages(
2614 &mut self,
2615 start: VirtAddr,
2616 size: usize,
2617 advice: HugePageAdvice,
2618 ) -> StarryResult {
2619 self.validate_region(start, size)?;
2620 let range =
2621 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2622 let previous_root = self.vma_root.clone();
2623 let affected = previous_root.lookup_range(range);
2624 if affected.is_empty() || !previous_root.contains_range(start, size) {
2625 return Err(StarryError::NoMemory);
2626 }
2627 if affected.iter().all(|vma| vma.huge_page_advice == advice) {
2628 return Ok(());
2629 }
2630
2631 let successor = previous_root
2632 .with_huge_page_advice(range, advice)
2633 .ok_or(StarryError::NoMemory)?;
2634 self.publish_vma_metadata_successor(previous_root, successor, "VMA THP advice")
2635 }
2636
2637 pub(crate) fn advise_vma_policy(
2640 &mut self,
2641 start: VirtAddr,
2642 size: usize,
2643 update: VmaAdviceUpdate,
2644 ) -> StarryResult {
2645 self.validate_region(start, size)?;
2646 let range =
2647 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2648 let previous_root = self.vma_root.clone();
2649 let affected = previous_root.lookup_range(range);
2650 if affected.is_empty() || !previous_root.contains_range(start, size) {
2651 return Err(StarryError::NoMemory);
2652 }
2653 if affected
2654 .iter()
2655 .all(|vma| vma.advice_policy.apply(update) == vma.advice_policy)
2656 {
2657 return Ok(());
2658 }
2659 let successor = previous_root
2660 .with_advice_update(range, update)
2661 .ok_or(StarryError::NoMemory)?;
2662 self.publish_vma_metadata_successor(previous_root, successor, "VMA madvise policy")
2663 }
2664
2665 fn set_vma_lock_mode(
2672 &mut self,
2673 start: VirtAddr,
2674 size: usize,
2675 lock_mode: VmaLockMode,
2676 memlock_limit: Option<MemlockLimit>,
2677 ) -> StarryResult {
2678 self.validate_region(start, size)?;
2679 let range =
2680 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
2681 let previous_root = self.vma_root.clone();
2682 let affected = previous_root.lookup_range(range);
2683 if affected.is_empty() || !previous_root.contains_range(start, size) {
2684 return Err(StarryError::NoMemory);
2685 }
2686 if affected.iter().all(|vma| vma.lock_mode == lock_mode) {
2687 return Ok(());
2688 }
2689
2690 let successor = previous_root
2691 .with_lock_mode(range, lock_mode)
2692 .ok_or(StarryError::NoMemory)?;
2693 self.validate_memlock_successor(&successor, memlock_limit)?;
2694 self.publish_vma_metadata_successor(previous_root, successor, "VMA lock update")
2695 }
2696
2697 pub(crate) fn lock_vma_range(
2698 &mut self,
2699 start: VirtAddr,
2700 size: usize,
2701 lock_mode: VmaLockMode,
2702 memlock_limit: MemlockLimit,
2703 ) -> StarryResult {
2704 if !lock_mode.is_locked() {
2705 return Err(StarryError::InvalidInput);
2706 }
2707 self.set_vma_lock_mode(start, size, lock_mode, Some(memlock_limit))
2708 }
2709
2710 pub(crate) fn unlock_vma_range(&mut self, start: VirtAddr, size: usize) -> StarryResult {
2711 self.set_vma_lock_mode(start, size, VmaLockMode::Unlocked, None)
2712 }
2713
2714 fn publish_prepared_pte_owners(
2726 &mut self,
2727 operation: &MappingOperation,
2728 range: VirtAddrRange,
2729 materialization: &PteMaterialization,
2730 ) -> StarryResult<PteOwnerPublication> {
2731 let owners = materialization.owners();
2732 let mut publications = Vec::new();
2733 publications
2734 .try_reserve(owners.len())
2735 .map_err(|_| StarryError::NoMemory)?;
2736 let mut seen = Vec::new();
2737 seen.try_reserve(owners.len())
2738 .map_err(|_| StarryError::NoMemory)?;
2739 let mut mapping_delta = MappingDelta::default();
2740 let mut resident_delta = ResidentDelta::default();
2741
2742 for owner in owners {
2746 let publication = self.prepare_slot_publication(operation, range, owner)?;
2747 if seen.contains(&publication.key) {
2748 return Err(StarryError::BadState);
2749 }
2750 seen.push(publication.key);
2751 mapping_delta.attached = mapping_delta
2752 .attached
2753 .checked_add(publication.mapping_delta.attached)
2754 .ok_or(StarryError::BadState)?;
2755 mapping_delta.detached = mapping_delta
2756 .detached
2757 .checked_add(publication.mapping_delta.detached)
2758 .ok_or(StarryError::BadState)?;
2759 resident_delta.checked_add_assign(publication.resident_delta)?;
2760 publications.push(publication);
2761 }
2762
2763 for publication in publications {
2764 self.apply_slot_publication(operation, publication)?;
2765 }
2766 Ok(PteOwnerPublication {
2767 satisfied_pages: materialization.satisfied_pages(),
2768 mapping_delta,
2769 resident_delta,
2770 })
2771 }
2772
2773 fn publish_prepared_fault_owner(
2774 &mut self,
2775 operation: &MappingOperation,
2776 range: VirtAddrRange,
2777 materialization: &FaultMaterialization,
2778 ) -> StarryResult<PteOwnerPublication> {
2779 let Some(owner) = materialization.owner() else {
2780 return Ok(PteOwnerPublication {
2781 satisfied_pages: materialization.satisfied_pages(),
2782 ..PteOwnerPublication::default()
2783 });
2784 };
2785 let publication = self.prepare_slot_publication(operation, range, owner)?;
2788 let mapping_delta = publication.mapping_delta;
2789 let resident_delta = publication.resident_delta;
2790 self.apply_slot_publication(operation, publication)?;
2791 Ok(PteOwnerPublication {
2792 satisfied_pages: materialization.satisfied_pages(),
2793 mapping_delta,
2794 resident_delta,
2795 })
2796 }
2797
2798 fn prepare_slot_publication(
2799 &mut self,
2800 operation: &MappingOperation,
2801 range: VirtAddrRange,
2802 owner: &PreparedPteOwner,
2803 ) -> StarryResult<PreparedSlotPublication> {
2804 let va = owner.va;
2805 let paddr = owner.paddr;
2806 let page_size = owner.page_size;
2807 let page = &owner.page;
2808 let resident_kind = owner.resident_kind;
2809 let transition = owner.transition;
2810 let provider_publication = owner.provider_publication;
2811 if page_size < PAGE_SIZE_4K || !page_size.is_power_of_two() || !va.is_aligned(page_size) {
2812 return Err(StarryError::BadState);
2813 }
2814 let leaf_range =
2815 VirtAddrRange::try_from_start_size(va, page_size).ok_or(StarryError::BadState)?;
2816 if !range.contains_range(leaf_range) {
2817 return Err(StarryError::BadState);
2818 }
2819 let frame_start = page.frame().paddr().as_usize();
2820 let frame_end = frame_start
2821 .checked_add(page.frame().size())
2822 .ok_or(StarryError::BadState)?;
2823 let leaf_start = paddr.as_usize();
2824 let leaf_end = leaf_start
2825 .checked_add(page_size)
2826 .ok_or(StarryError::BadState)?;
2827 if leaf_start < frame_start || leaf_end > frame_end {
2828 return Err(StarryError::BadState);
2829 }
2830 match self.pt.query(va) {
2831 Ok((installed, _, installed_size))
2832 if installed == paddr && installed_size == page_size => {}
2833 Ok(_) | Err(_) => return Err(StarryError::BadState),
2834 }
2835 if !matches!(page.state(), PageState::Present | PageState::LazyFree) {
2836 return Err(StarryError::BadState);
2837 }
2838
2839 let key = MappingSlotKey {
2840 space_id: self.id,
2841 va,
2842 };
2843 let previous = self.mapping_slots.get(&key).cloned();
2844 let order = page_size
2845 .trailing_zeros()
2846 .checked_sub(PAGE_SIZE_4K.trailing_zeros())
2847 .and_then(|order| u8::try_from(order).ok())
2848 .map(PageOrder::new)
2849 .ok_or(StarryError::BadState)?;
2850 let same_owner = previous.as_ref().is_some_and(|slot| {
2851 slot.state() == SlotState::Present
2852 && slot.mapping == operation.mapping_id()
2853 && slot.page_order == order
2854 && slot.mapped_paddr() == Some(paddr)
2855 && Arc::ptr_eq(&slot.page, page)
2856 && (order == PageOrder::BASE || slot.has_huge_split_deposit())
2857 });
2858 match transition {
2859 PteOwnerTransition::Updated if !same_owner => return Err(StarryError::BadState),
2860 PteOwnerTransition::Replaced if previous.is_none() || same_owner => {
2861 return Err(StarryError::BadState);
2862 }
2863 PteOwnerTransition::Installed
2864 | PteOwnerTransition::Replaced
2865 | PteOwnerTransition::Updated => {}
2866 }
2867
2868 let replacement = if same_owner {
2869 None
2870 } else {
2871 let split_deposit = if order == PageOrder::BASE {
2872 None
2873 } else {
2874 Some(self.pt.prepare_huge_split(va)?)
2875 };
2876 let frame_offset = paddr
2877 .as_usize()
2878 .checked_sub(page.frame().paddr().as_usize())
2879 .ok_or(StarryError::BadState)?;
2880 let slot = MappingSlot::new_with_frame_offset(
2881 operation.mapping_id(),
2882 self.id,
2883 va,
2884 order,
2885 page.clone(),
2886 frame_offset,
2887 resident_kind,
2888 )
2889 .ok_or(StarryError::BadState)?;
2890 let slot = match split_deposit {
2891 Some(deposit) => slot
2892 .attach_huge_split_deposit(deposit)
2893 .map_err(|_| StarryError::BadState)?,
2894 None => slot,
2895 };
2896 Some(Arc::new(slot))
2897 };
2898 let mut mapping_delta = MappingDelta::default();
2899 let mut resident_delta = ResidentDelta::default();
2900 if !same_owner {
2901 mapping_delta.attached = 1;
2902 mapping_delta.detached = u32::from(previous.is_some());
2903 }
2904 if let Some(previous) = &previous {
2905 let pages = 1i64
2906 .checked_shl(previous.page_order.get().into())
2907 .ok_or(StarryError::BadState)?;
2908 resident_delta
2909 .checked_add_assign(ResidentDelta::for_pages(previous.resident_kind(), -pages))?;
2910 }
2911 let pages = 1i64
2912 .checked_shl(order.get().into())
2913 .ok_or(StarryError::BadState)?;
2914 resident_delta.checked_add_assign(ResidentDelta::for_pages(resident_kind, pages))?;
2915 Ok(PreparedSlotPublication {
2916 key,
2917 previous,
2918 replacement,
2919 resident_kind,
2920 provider_publication,
2921 mapping_delta,
2922 resident_delta,
2923 })
2924 }
2925
2926 fn apply_slot_publication(
2927 &mut self,
2928 operation: &MappingOperation,
2929 publication: PreparedSlotPublication,
2930 ) -> StarryResult {
2931 let PreparedSlotPublication {
2932 key,
2933 previous,
2934 replacement,
2935 resident_kind,
2936 provider_publication,
2937 mapping_delta: _,
2938 resident_delta: _,
2939 } = publication;
2940 let Some(replacement) = replacement else {
2941 let current = self.mapping_slots.get(&key).ok_or(StarryError::BadState)?;
2942 if previous
2943 .as_ref()
2944 .is_none_or(|previous| !Arc::ptr_eq(previous, current))
2945 {
2946 return Err(StarryError::BadState);
2947 }
2948 current.set_resident_kind(resident_kind);
2949 current.page.set_resident_kind(resident_kind);
2950 if provider_publication == ProviderPublication::Pending {
2951 operation.finish_page_publication(key.va, ¤t.page)?;
2952 }
2953 return Ok(());
2954 };
2955
2956 if let Some(previous) = &previous {
2957 let Some(current) = self.mapping_slots.remove(&key) else {
2958 return Err(StarryError::BadState);
2959 };
2960 if !Arc::ptr_eq(previous, ¤t) || !current.detach() {
2961 self.mapping_slots.insert(key, current);
2962 return Err(StarryError::BadState);
2963 }
2964 } else if self.mapping_slots.contains_key(&key) {
2965 return Err(StarryError::BadState);
2966 }
2967
2968 if !replacement.publish() {
2969 if let Some(previous) = previous
2970 && (!previous.restore() || self.mapping_slots.insert(key, previous).is_some())
2971 {
2972 self.mutation_gate.mark_needs_repair();
2973 }
2974 return Err(StarryError::BadState);
2975 }
2976 if provider_publication == ProviderPublication::Pending
2977 && let Err(error) = operation.finish_page_publication(key.va, &replacement.page)
2978 {
2979 let replacement_detached = replacement.detach();
2980 let previous_restored = previous.is_none_or(|previous| {
2981 previous.restore() && self.mapping_slots.insert(key, previous).is_none()
2982 });
2983 if !replacement_detached || !previous_restored {
2984 self.mutation_gate.mark_needs_repair();
2985 return Err(StarryError::BadState);
2986 }
2987 return Err(error);
2988 }
2989 if self.mapping_slots.insert(key, replacement).is_some() {
2990 self.mutation_gate.mark_needs_repair();
2991 return Err(StarryError::BadState);
2992 }
2993 Ok(())
2994 }
2995
2996 fn detach_mapping_slots(&mut self, range: VirtAddrRange) -> StarryResult {
2997 let keys: Vec<_> = self
2998 .mapping_slots_overlapping(range)
2999 .map(|(key, _)| *key)
3000 .collect();
3001 for key in keys {
3002 if let Some(slot) = self.mapping_slots.remove(&key)
3003 && !slot.detach()
3004 {
3005 self.mapping_slots.insert(key, slot);
3006 return Err(StarryError::BadState);
3007 }
3008 }
3009 Ok(())
3010 }
3011
3012 pub(crate) fn evict_file_mapping_slot(
3016 &mut self,
3017 key: MappingSlotKey,
3018 page: &Arc<PageObject>,
3019 ) -> Result<EvictMappingOutcome, StarryError> {
3020 if key.space_id != self.id {
3021 return Err(StarryError::BadState);
3022 }
3023 let slot = self
3024 .mapping_slots
3025 .get(&key)
3026 .cloned()
3027 .ok_or(StarryError::BadState)?;
3028 if !Arc::ptr_eq(&slot.page, page) {
3029 return Err(StarryError::BadState);
3030 }
3031
3032 let range = VirtAddrRange::from_start_size(key.va, PAGE_SIZE_4K);
3033 let preimage = self.capture_mapping_preimage(range)?;
3034 let retired_owner = self.prepare_deferred_eviction_owner(page)?;
3035 let mut mutation = self.prepare_mutation_range(key.va, PAGE_SIZE_4K);
3036 let retire_epoch = mutation
3037 .receipt()
3038 .base_epoch
3039 .checked_next()
3040 .ok_or(StarryError::BadState)?;
3041 mutation.set_pte_delta(PteDelta {
3042 unmapped: 1,
3043 ..PteDelta::default()
3044 });
3045 mutation.set_mapping_delta(MappingDelta {
3046 detached: 1,
3047 ..MappingDelta::default()
3048 });
3049 mutation.set_resident_delta(ResidentDelta::for_pages(slot.resident_kind(), -1));
3050
3051 let unmap_plan = self.pt.plan_unmap_page(key.va)?;
3052 if slot.mapped_paddr() != Some(unmap_plan.paddr()) || unmap_plan.page_size() != PAGE_SIZE_4K
3053 {
3054 return Err(StarryError::BadState);
3055 }
3056 let apply_result = (|| -> StarryResult {
3057 let unmapped = {
3058 let pte_domain = &self.pte_domain;
3059 let pt = &mut self.pt;
3060 let range = VirtAddrRange::from_start_size(key.va, PAGE_SIZE_4K);
3061 let _structure = pte_domain.lock_structure();
3062 let _stripe = pte_domain.lock_range(range);
3063 pt.try_unmap_page_with(unmap_plan)?
3064 };
3065 if slot.mapped_paddr() != Some(unmapped.0) || unmapped.2 != PAGE_SIZE_4K {
3066 return Err(StarryError::BadState);
3067 }
3068 let removed = self
3072 .mapping_slots
3073 .remove(&key)
3074 .ok_or(StarryError::BadState)?;
3075 if !Arc::ptr_eq(&removed, &slot) || !removed.detach() {
3076 return Err(StarryError::BadState);
3077 }
3078 Ok(())
3079 })();
3080 if let Err(error) = apply_result {
3081 return self.abort_file_eviction_mutation(range, preimage, None, error);
3082 }
3083
3084 self.park_retired_mapping_owners(retire_epoch, retired_owner);
3085 match self.commit_mutation_classified(mutation) {
3086 Ok(()) => {
3087 self.release_retired_mapping_owners(retire_epoch);
3088 Ok(EvictMappingOutcome::Complete)
3089 }
3090 Err(CommitMutationError::PublishedPendingTlb(_)) => {
3091 Ok(EvictMappingOutcome::PublishedPendingTlb)
3092 }
3093 Err(CommitMutationError::Unpublished(error)) => {
3094 self.abort_file_eviction_mutation(range, preimage, Some(retire_epoch), error)
3095 }
3096 }
3097 }
3098
3099 pub(crate) fn protect_file_mapping_slot(
3102 &mut self,
3103 key: MappingSlotKey,
3104 page: &Arc<PageObject>,
3105 ) -> StarryResult {
3106 if key.space_id != self.id {
3107 return Err(StarryError::BadState);
3108 }
3109 let slot = self.mapping_slots.get(&key).ok_or(StarryError::BadState)?;
3110 if !Arc::ptr_eq(&slot.page, page) {
3111 return Err(StarryError::BadState);
3112 }
3113
3114 let (paddr, flags, page_size) = self.pt.query(key.va)?;
3115 if slot.mapped_paddr() != Some(paddr) || page_size != PAGE_SIZE_4K {
3116 return Err(StarryError::BadState);
3117 }
3118 if !flags.contains(MappingFlags::WRITE) {
3119 return Ok(());
3120 }
3121 let mut mutation = self.prepare_mutation_range(key.va, PAGE_SIZE_4K);
3122 mutation.set_pte_delta(PteDelta {
3123 protected: 1,
3124 ..PteDelta::default()
3125 });
3126 {
3127 let pt = &mut self.pt;
3128 let _stripe = self
3129 .pte_domain
3130 .lock_range(VirtAddrRange::from_start_size(key.va, PAGE_SIZE_4K));
3131 pt.remap_page(key.va, paddr, flags - MappingFlags::WRITE)?;
3134 }
3135 self.commit_mutation(mutation)
3140 }
3141
3142 pub fn resident_mapping_slots(&self) -> Vec<Arc<MappingSlot>> {
3143 self.mapping_slots.values().cloned().collect()
3144 }
3145
3146 fn capture_mapping_graph_snapshot(
3147 &self,
3148 ranges: &[VirtAddrRange],
3149 ) -> StarryResult<MappingGraphSnapshot> {
3150 #[cfg(all(test, axtest))]
3151 MAPPING_GRAPH_SNAPSHOT_CALLS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3152 let slot_count = ranges
3153 .iter()
3154 .map(|range| self.mapping_slots_overlapping(*range).count())
3155 .sum();
3156 let mut slots = Vec::new();
3157 slots
3158 .try_reserve(slot_count)
3159 .map_err(|_| StarryError::NoMemory)?;
3160 let mut seen = BTreeSet::new();
3161 let mut resident = ResidentPageCounts::default();
3162 for range in ranges {
3163 for (key, slot) in self.mapping_slots_overlapping(*range) {
3164 if slot.state() != SlotState::Present || !seen.insert(*key) {
3165 continue;
3166 }
3167 slots.push(MappingSlotFingerprint {
3168 key: *key,
3169 mapping: slot.mapping,
3170 page: slot.page.id,
3171 page_order: slot.page_order,
3172 });
3173 let pages = 1u64
3174 .checked_shl(u32::from(slot.page_order.get()))
3175 .ok_or(StarryError::BadState)?;
3176 match slot.resident_kind() {
3177 Some(RssKind::Anon) => {
3178 resident.anon = resident
3179 .anon
3180 .checked_add(pages)
3181 .ok_or(StarryError::BadState)?;
3182 }
3183 Some(RssKind::File) => {
3184 resident.file = resident
3185 .file
3186 .checked_add(pages)
3187 .ok_or(StarryError::BadState)?;
3188 }
3189 Some(RssKind::Shmem) => {
3190 resident.shmem = resident
3191 .shmem
3192 .checked_add(pages)
3193 .ok_or(StarryError::BadState)?;
3194 }
3195 None => {}
3196 }
3197 }
3198 }
3199 slots.sort_unstable();
3200 Ok(MappingGraphSnapshot { slots, resident })
3201 }
3202
3203 fn set_mapping_graph_receipt_delta(
3204 &self,
3205 mutation: &mut PreparedMutation,
3206 before: &MappingGraphSnapshot,
3207 ranges: &[VirtAddrRange],
3208 ) -> StarryResult {
3209 let after = self.capture_mapping_graph_snapshot(ranges)?;
3210 let (mapping_delta, resident_delta) = before.delta_to(&after)?;
3211 mutation.set_mapping_delta(mapping_delta);
3212 mutation.set_resident_delta(resident_delta);
3213 Ok(())
3214 }
3215
3216 pub(crate) fn resident_page_counts(&self) -> ResidentPageCounts {
3218 self.resident_pages
3219 }
3220
3221 #[cfg(all(test, axtest))]
3222 fn reset_mapping_graph_snapshot_calls_for_test(&self) {
3223 MAPPING_GRAPH_SNAPSHOT_CALLS.store(0, core::sync::atomic::Ordering::Relaxed);
3224 }
3225
3226 #[cfg(all(test, axtest))]
3227 fn mapping_graph_snapshot_calls_for_test(&self) -> usize {
3228 MAPPING_GRAPH_SNAPSHOT_CALLS.load(core::sync::atomic::Ordering::Relaxed)
3229 }
3230
3231 pub(crate) fn resident_hiwater_pages(&self) -> u64 {
3232 self.resident_watermark.hiwater_pages()
3233 }
3234
3235 fn mapping_operation_fragments(
3239 &self,
3240 range: VirtAddrRange,
3241 require_full_coverage: bool,
3242 ) -> StarryResult<Vec<(VirtAddrRange, MappingOperation)>> {
3243 let mut fragments = Vec::new();
3244 fragments
3245 .try_reserve(self.vma_root.len())
3246 .map_err(|_| StarryError::NoMemory)?;
3247 let mut covered = range.start;
3248 for entry in self.vma_root.iter_entries() {
3249 if entry.start() >= range.end {
3250 break;
3251 }
3252 if entry.end() <= range.start {
3253 continue;
3254 }
3255 let fragment =
3256 VirtAddrRange::new(entry.start().max(range.start), entry.end().min(range.end));
3257 if require_full_coverage && fragment.start > covered {
3258 return Err(StarryError::NoMemory);
3259 }
3260 covered = covered.max(fragment.end);
3261 fragments.push((fragment, entry.operation_clone()));
3262 }
3263 if require_full_coverage && covered < range.end {
3264 return Err(StarryError::NoMemory);
3265 }
3266 Ok(fragments)
3267 }
3268
3269 fn detach_materialized_operation(
3274 &mut self,
3275 range: VirtAddrRange,
3276 operation: &MappingOperation,
3277 ) -> bool {
3278 let Ok(occupied) = self.occupied_pte_leaves_overlapping(&[range]) else {
3279 return false;
3280 };
3281 let leaves: Vec<_> = occupied.into_iter().map(|leaf| leaf.range).collect();
3282 if leaves
3283 .iter()
3284 .any(|leaf| !operation.validate_unmap_range(*leaf, &self.pt))
3285 {
3286 return false;
3287 }
3288 leaves
3289 .into_iter()
3290 .all(|leaf| operation.unmap_range(leaf, &mut self.pt).is_ok())
3291 }
3292
3293 fn detach_current_materialized_ranges(&mut self, ranges: &[VirtAddrRange]) -> StarryResult {
3297 let leaves = self.occupied_pte_leaves_overlapping(ranges)?;
3298 let mut operations = Vec::new();
3299 operations
3300 .try_reserve(leaves.len())
3301 .map_err(|_| StarryError::NoMemory)?;
3302 for leaf in leaves {
3303 let operation = self
3304 .vma_root
3305 .lookup_entry(leaf.range.start)
3306 .map(|entry| entry.operation_clone())
3307 .ok_or(StarryError::BadState)?;
3308 operations.push((leaf.range, operation));
3309 }
3310 if operations
3311 .iter()
3312 .any(|(leaf, operation)| !operation.validate_unmap_range(*leaf, &self.pt))
3313 {
3314 return Err(StarryError::BadState);
3315 }
3316 for (leaf, operation) in operations {
3317 operation.unmap_range(leaf, &mut self.pt)?;
3318 }
3319 Ok(())
3320 }
3321
3322 #[allow(clippy::too_many_arguments)]
3323 fn prepare_mapping_successor(
3324 &self,
3325 range: VirtAddrRange,
3326 permissions: MappingPermissions,
3327 operation: &MappingOperation,
3328 huge_page_advice: HugePageAdvice,
3329 lock_mode: VmaLockMode,
3330 advice_policy: VmaAdvicePolicy,
3331 replace: bool,
3332 ) -> StarryResult<VmaMap> {
3333 let entry = self
3334 .vma_root
3335 .prepare_mapping_entry(
3336 range,
3337 permissions.current,
3338 permissions.reported,
3339 permissions.maximum,
3340 huge_page_advice,
3341 lock_mode,
3342 advice_policy,
3343 operation.clone(),
3344 )
3345 .ok_or(StarryError::BadState)?;
3346 let successor = self.vma_root.with_mapping_entry(entry, replace).ok_or(
3347 if self.vma_root.overlaps(range) && !replace {
3348 StarryError::AlreadyExists
3349 } else {
3350 StarryError::BadState
3351 },
3352 )?;
3353 Ok(successor)
3354 }
3355
3356 fn apply_mapping_pages_unpublished(
3360 &mut self,
3361 range: VirtAddrRange,
3362 permissions: MappingPermissions,
3363 operation: &MappingOperation,
3364 replace: bool,
3365 ) -> StarryResult<PteMaterialization> {
3366 let replaced = if replace {
3367 self.mapping_operation_fragments(range, false)?
3368 } else {
3369 Vec::new()
3370 };
3371 if replaced
3372 .iter()
3373 .any(|(fragment, old)| !old.validate_unmap_range(*fragment, &self.pt))
3374 || (!replace && !operation.validate_map_range(range, &self.pt))
3375 {
3376 return Err(StarryError::BadState);
3377 }
3378 for (fragment, old) in &replaced {
3379 old.unmap_range(*fragment, &mut self.pt)?;
3380 }
3381 match operation.map_range(range, permissions.current, &mut self.pt) {
3382 Ok(materialization) => Ok(materialization),
3383 Err(error) => {
3384 if !self.detach_materialized_operation(range, operation) {
3385 self.mutation_gate.mark_needs_repair();
3386 return Err(StarryError::BadState);
3387 }
3388 Err(error)
3389 }
3390 }
3391 }
3392
3393 #[allow(clippy::too_many_arguments)]
3396 fn apply_mapping_unpublished(
3397 &mut self,
3398 range: VirtAddrRange,
3399 permissions: MappingPermissions,
3400 operation: &MappingOperation,
3401 huge_page_advice: HugePageAdvice,
3402 lock_mode: VmaLockMode,
3403 advice_policy: VmaAdvicePolicy,
3404 memlock_limit: Option<MemlockLimit>,
3405 replace: bool,
3406 ) -> StarryResult<PteMaterialization> {
3407 let successor = self.prepare_mapping_successor(
3408 range,
3409 permissions,
3410 operation,
3411 huge_page_advice,
3412 lock_mode,
3413 advice_policy,
3414 replace,
3415 )?;
3416 self.validate_memlock_successor(&successor, memlock_limit)?;
3417 let materialization =
3418 self.apply_mapping_pages_unpublished(range, permissions, operation, replace)?;
3419 self.vma_root = Arc::new(successor);
3420 Ok(materialization)
3421 }
3422
3423 fn apply_unmap_pages_unpublished(&mut self, range: VirtAddrRange) -> StarryResult {
3424 let operations = self.mapping_operation_fragments(range, false)?;
3425 if operations
3426 .iter()
3427 .any(|(fragment, operation)| !operation.validate_unmap_range(*fragment, &self.pt))
3428 {
3429 return Err(StarryError::BadState);
3430 }
3431 for (fragment, operation) in operations {
3432 operation.unmap_range(fragment, &mut self.pt)?;
3433 }
3434 Ok(())
3435 }
3436
3437 fn apply_unmap_unpublished(&mut self, range: VirtAddrRange) -> StarryResult {
3441 let successor = self
3442 .vma_root
3443 .without_range(range)
3444 .ok_or(StarryError::BadState)?;
3445 self.apply_unmap_pages_unpublished(range)?;
3446 self.vma_root = Arc::new(successor);
3447 Ok(())
3448 }
3449
3450 fn apply_protection_unpublished(
3452 &mut self,
3453 range: VirtAddrRange,
3454 flags: MappingFlags,
3455 reported_flags: MappingFlags,
3456 ) -> StarryResult {
3457 let successor = self
3458 .vma_root
3459 .with_permissions(range, flags, reported_flags)
3460 .ok_or(StarryError::NoMemory)?;
3461 let operations = self.mapping_operation_fragments(range, true)?;
3462 if operations
3463 .iter()
3464 .any(|(fragment, operation)| !operation.validate_protect_range(*fragment, &self.pt))
3465 {
3466 return Err(StarryError::BadState);
3467 }
3468 for (fragment, operation) in operations {
3469 operation.protect_range(fragment, flags, &mut self.pt)?;
3470 }
3471 self.vma_root = Arc::new(successor);
3472 Ok(())
3473 }
3474
3475 fn apply_extend_unpublished(
3476 &mut self,
3477 address: VirtAddr,
3478 additional_size: usize,
3479 memlock_limit: Option<MemlockLimit>,
3480 ) -> StarryResult<(VirtAddrRange, MappingOperation, PteMaterialization)> {
3481 let entry = self
3482 .vma_root
3483 .lookup_entry(address)
3484 .ok_or(StarryError::InvalidInput)?;
3485 let suffix = VirtAddrRange::try_from_start_size(entry.end(), additional_size)
3486 .ok_or(StarryError::InvalidInput)?;
3487 let operation = entry.operation_clone();
3488 let flags = entry.rights();
3489 let successor = self
3490 .vma_root
3491 .with_extended_right(address, additional_size)
3492 .ok_or(StarryError::AlreadyExists)?;
3493 self.validate_memlock_successor(&successor, memlock_limit)?;
3494 if !operation.validate_map_range(suffix, &self.pt) {
3495 return Err(StarryError::BadState);
3496 }
3497 let materialization = match operation.map_range(suffix, flags, &mut self.pt) {
3498 Ok(materialization) => materialization,
3499 Err(error) => {
3500 if !self.detach_materialized_operation(suffix, &operation) {
3501 self.mutation_gate.mark_needs_repair();
3502 return Err(StarryError::BadState);
3503 }
3504 return Err(error);
3505 }
3506 };
3507 self.vma_root = Arc::new(successor);
3508 Ok((suffix, operation, materialization))
3509 }
3510
3511 pub fn map_linear(
3520 &mut self,
3521 start_vaddr: VirtAddr,
3522 start_paddr: PhysAddr,
3523 size: usize,
3524 flags: MappingFlags,
3525 ) -> StarryResult {
3526 self.validate_region(start_vaddr, size)?;
3527 let range = VirtAddrRange::from_start_size(start_vaddr, size);
3528 let preimage = self.capture_mapping_preimage(range)?;
3529 let graph_preimage = self.capture_mapping_graph_snapshot(&[range])?;
3530 let mut mutation = self.prepare_mutation_range(start_vaddr, size);
3531
3532 if !start_paddr.is_aligned_4k() {
3533 return Err(StarryError::InvalidInput);
3534 }
3535 if start_paddr.checked_add(size).is_none() {
3536 return Err(StarryError::InvalidInput);
3537 }
3538
3539 let operation = MappingOperation::new_linear(start_vaddr, start_paddr, false);
3540 let materialization = match self.apply_mapping_unpublished(
3541 range,
3542 MappingPermissions {
3543 current: flags,
3544 reported: flags,
3545 maximum: flags,
3546 },
3547 &operation,
3548 HugePageAdvice::Default,
3549 VmaLockMode::Unlocked,
3550 VmaAdvicePolicy::default(),
3551 None,
3552 false,
3553 ) {
3554 Ok(materialization) => materialization,
3555 Err(error) => {
3556 return self.abort_unpublished_mapping_mutation(range, preimage, error);
3557 }
3558 };
3559 mutation.set_vma_delta(VmaDelta {
3560 inserted: 1,
3561 ..VmaDelta::default()
3562 });
3563 mutation.set_pte_delta(PteDelta {
3564 mapped: u32::try_from(size / PAGE_SIZE_4K).unwrap_or(u32::MAX),
3565 ..PteDelta::default()
3566 });
3567 self.vm_stat.on_map((size / PAGE_SIZE_4K) as u64);
3568 if let Err(error) = self.publish_prepared_pte_owners(&operation, range, &materialization) {
3569 return self.abort_unpublished_mapping_mutation(range, preimage, error);
3570 }
3571 if let Err(error) =
3572 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[range])
3573 {
3574 return self.abort_unpublished_mapping_mutation(range, preimage, error);
3575 }
3576 match self.commit_mutation_classified(mutation) {
3577 Ok(()) => Ok(()),
3578 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
3579 Err(CommitMutationError::Unpublished(error)) => {
3580 self.abort_unpublished_mapping_mutation(range, preimage, error)
3581 }
3582 }
3583 }
3584
3585 pub fn map(
3586 &mut self,
3587 start: VirtAddr,
3588 size: usize,
3589 flags: MappingFlags,
3590 populate: bool,
3591 backend: MappingOperation,
3592 ) -> StarryResult {
3593 self.map_with_reported_flags(start, size, flags, flags, populate, backend)
3594 }
3595
3596 pub(crate) fn map_outcome(
3603 &mut self,
3604 start: VirtAddr,
3605 size: usize,
3606 flags: MappingFlags,
3607 populate: bool,
3608 backend: MappingOperation,
3609 ) -> StarryResult<AddressSpaceMutationOutcome> {
3610 self.map_with_permissions_mode_classified(
3611 start,
3612 size,
3613 MappingPermissions {
3614 current: flags,
3615 reported: flags,
3616 maximum: flags,
3617 },
3618 populate,
3619 backend,
3620 MappingPublication::new(false),
3621 )
3622 }
3623
3624 pub fn map_with_reported_flags(
3625 &mut self,
3626 start: VirtAddr,
3627 size: usize,
3628 flags: MappingFlags,
3629 reported_flags: MappingFlags,
3630 populate: bool,
3631 backend: MappingOperation,
3632 ) -> StarryResult {
3633 self.map_with_permissions(
3634 start,
3635 size,
3636 MappingPermissions {
3637 current: flags,
3638 reported: reported_flags,
3639 maximum: flags,
3640 },
3641 populate,
3642 backend,
3643 )
3644 }
3645
3646 pub fn map_with_permissions_replace(
3653 &mut self,
3654 start: VirtAddr,
3655 size: usize,
3656 permissions: MappingPermissions,
3657 populate: bool,
3658 backend: MappingOperation,
3659 replace: bool,
3660 ) -> StarryResult {
3661 self.map_with_permissions_mode(
3662 start,
3663 size,
3664 permissions,
3665 populate,
3666 backend,
3667 MappingPublication::new(replace),
3668 )
3669 }
3670
3671 pub(crate) fn map_with_permissions_publication(
3675 &mut self,
3676 start: VirtAddr,
3677 size: usize,
3678 permissions: MappingPermissions,
3679 populate: bool,
3680 backend: MappingOperation,
3681 publication: MappingPublication,
3682 ) -> StarryResult {
3683 self.map_with_permissions_mode(start, size, permissions, populate, backend, publication)
3684 }
3685
3686 pub(crate) fn map_mremap_duplicate(
3690 &mut self,
3691 start: VirtAddr,
3692 size: usize,
3693 permissions: MappingPermissions,
3694 backend: MappingOperation,
3695 publication: MappingPublication,
3696 ) -> StarryResult {
3697 self.map_with_permissions_mode(start, size, permissions, false, backend, publication)
3698 }
3699
3700 pub fn map_with_permissions(
3705 &mut self,
3706 start: VirtAddr,
3707 size: usize,
3708 permissions: MappingPermissions,
3709 populate: bool,
3710 backend: MappingOperation,
3711 ) -> StarryResult {
3712 self.map_with_permissions_mode(
3713 start,
3714 size,
3715 permissions,
3716 populate,
3717 backend,
3718 MappingPublication::new(false),
3719 )
3720 }
3721
3722 fn map_with_permissions_mode(
3723 &mut self,
3724 start: VirtAddr,
3725 size: usize,
3726 permissions: MappingPermissions,
3727 populate: bool,
3728 backend: MappingOperation,
3729 publication: MappingPublication,
3730 ) -> StarryResult {
3731 self.map_with_permissions_mode_classified(
3732 start,
3733 size,
3734 permissions,
3735 populate,
3736 backend,
3737 publication,
3738 )?
3739 .into_result()
3740 }
3741
3742 fn map_with_permissions_mode_classified(
3743 &mut self,
3744 start: VirtAddr,
3745 size: usize,
3746 permissions: MappingPermissions,
3747 populate: bool,
3748 backend: MappingOperation,
3749 publication: MappingPublication,
3750 ) -> StarryResult<AddressSpaceMutationOutcome> {
3751 let MappingPublication {
3752 replace,
3753 huge_page_advice,
3754 lock_mode,
3755 advice_policy,
3756 memlock_limit,
3757 } = publication;
3758 self.validate_region(start, size)?;
3759 if !permissions.maximum.contains(permissions.current) {
3760 return Err(StarryError::PermissionDenied);
3761 }
3762 let range =
3763 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
3764 let mut mutation = prepare_mapping_publication_mutation(
3765 &self.mutation_gate,
3766 self.id,
3767 &self.tlb_targets,
3768 start,
3769 size,
3770 replace,
3771 );
3772 self.mutation_gate
3773 .validate_publish_preconditions(&mutation)
3774 .map_err(Self::map_unpublished_mutation_error)?;
3775 let removed_pages = if replace {
3778 self.vma_root
3779 .iter_entries()
3780 .filter(|entry| entry.start() < range.end && entry.end() > range.start)
3781 .try_fold(0u64, |pages, entry| {
3782 let lo = entry.start().max(range.start);
3783 let hi = entry.end().min(range.end);
3784 let fragment = hi.checked_sub_addr(lo).ok_or(StarryError::InvalidInput)?;
3785 pages
3786 .checked_add((fragment / PAGE_SIZE_4K) as u64)
3787 .ok_or(StarryError::InvalidInput)
3788 })?
3789 } else {
3790 0
3791 };
3792 let mapping_preimage = self.capture_mapping_preimage(range)?;
3793 let graph_preimage = self.capture_mapping_graph_snapshot(&[range])?;
3794 let retire_epoch = mutation
3795 .receipt()
3796 .base_epoch
3797 .checked_next()
3798 .ok_or(StarryError::BadState)?;
3799 let retired_owners = replace
3800 .then(|| self.prepare_retired_mapping_owners(range))
3801 .transpose()?;
3802
3803 let touched_memfds = if replace {
3808 crate::syscall::memfd_collect_metas_touching_mprotect_range(self, start, size)
3809 } else {
3810 Vec::new()
3811 };
3812
3813 let map_materialization = match self.apply_mapping_unpublished(
3814 range,
3815 permissions,
3816 &backend,
3817 huge_page_advice,
3818 lock_mode,
3819 advice_policy,
3820 memlock_limit,
3821 replace,
3822 ) {
3823 Ok(materialization) => materialization,
3824 Err(error) => {
3825 return self
3826 .abort_unpublished_mapping_mutation(range, mapping_preimage, error)
3827 .map(|()| AddressSpaceMutationOutcome::Complete);
3828 }
3829 };
3830 if let Some(owners) = retired_owners {
3831 self.park_retired_mapping_owners(retire_epoch, owners);
3832 }
3833 if removed_pages != 0
3834 && let Err(error) = self.detach_mapping_slots(range)
3835 {
3836 return self
3837 .abort_unpublished_parked_mapping_mutation(
3838 range,
3839 mapping_preimage,
3840 replace.then_some(retire_epoch),
3841 error,
3842 )
3843 .map(|()| AddressSpaceMutationOutcome::Complete);
3844 }
3845 if let Err(error) = self.publish_prepared_pte_owners(&backend, range, &map_materialization)
3846 {
3847 return self
3848 .abort_unpublished_parked_mapping_mutation(
3849 range,
3850 mapping_preimage,
3851 replace.then_some(retire_epoch),
3852 error,
3853 )
3854 .map(|()| AddressSpaceMutationOutcome::Complete);
3855 }
3856 if populate
3857 && let Err(populate_error) = self.apply_populate_area(start, size, permissions.current)
3858 {
3859 return self
3860 .abort_unpublished_parked_mapping_mutation(
3861 range,
3862 mapping_preimage,
3863 replace.then_some(retire_epoch),
3864 populate_error,
3865 )
3866 .map(|()| AddressSpaceMutationOutcome::Complete);
3867 }
3868 mutation.set_vma_delta(VmaDelta {
3869 inserted: 1,
3870 removed: u32::try_from(removed_pages).unwrap_or(u32::MAX),
3871 ..VmaDelta::default()
3872 });
3873 self.vm_stat.on_map((size / PAGE_SIZE_4K) as u64);
3874 if removed_pages != 0 {
3875 self.vm_stat.on_unmap(removed_pages);
3876 }
3877 if let Err(error) =
3878 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[range])
3879 {
3880 return self
3881 .abort_unpublished_parked_mapping_mutation(
3882 range,
3883 mapping_preimage,
3884 replace.then_some(retire_epoch),
3885 error,
3886 )
3887 .map(|()| AddressSpaceMutationOutcome::Complete);
3888 }
3889 match self.commit_mutation_classified(mutation) {
3890 Ok(()) => {
3891 if replace {
3892 self.release_retired_mapping_owners(retire_epoch);
3893 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
3894 self,
3895 &touched_memfds,
3896 );
3897 } else {
3898 crate::syscall::memfd_on_after_map(self, start);
3899 }
3900 Ok(AddressSpaceMutationOutcome::Complete)
3901 }
3902 Err(CommitMutationError::PublishedPendingTlb(error)) => {
3903 if replace {
3904 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
3905 self,
3906 &touched_memfds,
3907 );
3908 } else {
3909 crate::syscall::memfd_on_after_map(self, start);
3910 }
3911 Ok(AddressSpaceMutationOutcome::PublishedPendingTlb(error))
3912 }
3913 Err(CommitMutationError::Unpublished(error)) => self
3914 .abort_unpublished_parked_mapping_mutation(
3915 range,
3916 mapping_preimage,
3917 replace.then_some(retire_epoch),
3918 error,
3919 )
3920 .map(|()| AddressSpaceMutationOutcome::Complete),
3921 }
3922 }
3923
3924 fn apply_populate_area(
3928 &mut self,
3929 mut start: VirtAddr,
3930 size: usize,
3931 access_flags: MappingFlags,
3932 ) -> StarryResult<usize> {
3933 self.validate_region(start, size)?;
3934 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
3935 let mut populated = 0usize;
3936
3937 loop {
3938 let area_end = {
3939 let Some(entry) = self.vma_root.lookup_entry(start) else {
3940 break;
3941 };
3942 let entry_end = entry.end();
3943 let range = VirtAddrRange::new(start, entry_end.min(end));
3944 let flags = entry.rights();
3945 let backend = entry.operation_clone();
3946 let request = PopulateRequest::area(range, backend.page_size())?;
3947 let materialization =
3948 backend.populate(self.id, request, flags, access_flags, &mut self.pt)?;
3949 let publication =
3950 self.publish_prepared_pte_owners(&backend, range, &materialization)?;
3951 populated = populated
3952 .checked_add(publication.satisfied_pages)
3953 .ok_or(StarryError::NoMemory)?;
3954 entry_end
3955 };
3956 start = area_end;
3957 assert!(start.is_aligned_4k());
3958 if start >= end {
3959 break;
3960 }
3961 }
3962
3963 if start < end {
3964 return Err(StarryError::NoMemory);
3966 }
3967
3968 Ok(populated)
3969 }
3970
3971 fn materialized_range_satisfies_access(
3980 &self,
3981 range: VirtAddrRange,
3982 access_flags: MappingFlags,
3983 ) -> bool {
3984 let required = access_flags | MappingFlags::USER;
3985 let mut cursor = range.start;
3986 while cursor < range.end {
3987 let Ok((_, flags, leaf_size)) = self.pt.query(cursor) else {
3988 return false;
3989 };
3990 if leaf_size < PAGE_SIZE_4K || !leaf_size.is_power_of_two() || !flags.contains(required)
3991 {
3992 return false;
3993 }
3994 let Some(leaf_end) = cursor.align_down(leaf_size).checked_add(leaf_size) else {
3995 return false;
3996 };
3997 if leaf_end <= cursor {
3998 return false;
3999 }
4000 cursor = leaf_end.min(range.end);
4001 }
4002 true
4003 }
4004
4005 pub fn populate_area(
4007 &mut self,
4008 start: VirtAddr,
4009 size: usize,
4010 access_flags: MappingFlags,
4011 ) -> StarryResult {
4012 let range =
4013 VirtAddrRange::try_from_start_size(start, size).ok_or(StarryError::InvalidInput)?;
4014 self.validate_region(start, size)?;
4015 if self.can_access_range(start, size, access_flags)
4016 && self.materialized_range_satisfies_access(range, access_flags)
4017 {
4018 return Ok(());
4019 }
4020 let preimage = self.capture_mapping_preimage(range)?;
4021 let graph_preimage = self.capture_mapping_graph_snapshot(&[range])?;
4022 let mut mutation = self.prepare_mutation_range(start, size);
4023 let retire_epoch = mutation
4024 .receipt()
4025 .base_epoch
4026 .checked_next()
4027 .ok_or(StarryError::BadState)?;
4028 let retired_owners = (!graph_preimage.slots.is_empty())
4029 .then(|| self.prepare_retired_mapping_owners(range))
4030 .transpose()?;
4031 let populated = match self.apply_populate_area(start, size, access_flags) {
4032 Ok(populated) => populated,
4033 Err(populate_error) => {
4034 if self.restore_mapping_preimage(range, preimage).is_err() {
4035 return Err(StarryError::BadState);
4036 }
4037 return Err(populate_error);
4038 }
4039 };
4040
4041 mutation.set_pte_delta(PteDelta {
4042 mapped: u32::try_from(populated).unwrap_or(u32::MAX),
4043 ..PteDelta::default()
4044 });
4045 if let Err(error) =
4046 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[range])
4047 {
4048 return self.abort_unpublished_mapping_mutation(range, preimage, error);
4049 }
4050 if let Some(owners) = retired_owners {
4051 self.park_retired_mapping_owners(retire_epoch, owners);
4052 }
4053 match self.commit_mutation_classified(mutation) {
4054 Ok(()) => {
4055 self.release_retired_mapping_owners(retire_epoch);
4056 Ok(())
4057 }
4058 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
4059 Err(CommitMutationError::Unpublished(error)) => self
4060 .abort_unpublished_parked_mapping_mutation(
4061 range,
4062 preimage,
4063 Some(retire_epoch),
4064 error,
4065 ),
4066 }
4067 }
4068
4069 pub fn discard_range(&mut self, start: VirtAddr, size: usize) -> StarryResult {
4072 self.validate_region(start, size)?;
4073 let retired_range = VirtAddrRange::from_start_size(start, size);
4074 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
4075
4076 let mut frags: alloc::vec::Vec<(VirtAddrRange, MappingOperation)> = alloc::vec::Vec::new();
4077 frags
4078 .try_reserve(self.vma_root.len())
4079 .map_err(|_| StarryError::NoMemory)?;
4080 let mut covered = start;
4081 for entry in self.vma_root.iter_entries() {
4082 if entry.start() >= end {
4083 break;
4084 }
4085 if entry.end() <= start {
4086 continue;
4087 }
4088 let frag_start = entry.start().max(start);
4089 let frag_end = entry.end().min(end);
4090 if frag_start > covered {
4091 return Err(StarryError::NoMemory);
4092 }
4093 let backend = entry.operation_clone();
4094 backend.validate_discard_fragment(VirtAddrRange::new(frag_start, frag_end))?;
4098 frags.push((VirtAddrRange::new(frag_start, frag_end), backend));
4099 covered = frag_end;
4100 }
4101 if covered < end {
4102 return Err(StarryError::NoMemory);
4103 }
4104
4105 let mut mutation = self.prepare_mutation_range(start, size);
4106 mutation
4107 .try_reserve_tlb_ranges(2)
4108 .map_err(|_| StarryError::NoMemory)?;
4109 let retire_epoch = mutation
4110 .receipt()
4111 .base_epoch
4112 .checked_next()
4113 .ok_or(StarryError::BadState)?;
4114 let splits = self.apply_partial_huge_splits(retired_range)?;
4115 for index in 0..splits.len() {
4116 let split = &splits[index];
4117 let Some(tlb_range) =
4118 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
4119 else {
4120 return self.abort_unpublished_huge_splits(splits, StarryError::BadState);
4121 };
4122 mutation.add_tlb_range(tlb_range);
4123 }
4124 if frags
4125 .iter()
4126 .any(|(range, backend)| !backend.validate_unmap_range(*range, &self.pt))
4127 {
4128 return self.abort_unpublished_huge_splits(splits, StarryError::OperationNotSupported);
4129 }
4130
4131 let preimage = match self.capture_mapping_preimage(retired_range) {
4132 Ok(preimage) => preimage,
4133 Err(error) => return self.abort_unpublished_huge_splits(splits, error),
4134 };
4135 let retired_owners = match self.prepare_retired_mapping_owners(retired_range) {
4136 Ok(owners) => owners,
4137 Err(error) => {
4138 return self.abort_unpublished_split_mapping_mutation(
4139 retired_range,
4140 preimage,
4141 None,
4142 splits,
4143 error,
4144 );
4145 }
4146 };
4147 let Ok((detached_slots, retired_pages, retired_resident)) =
4148 self.mapping_slot_summary(retired_range)
4149 else {
4150 return self.abort_unpublished_split_mapping_mutation(
4151 retired_range,
4152 preimage,
4153 None,
4154 splits,
4155 StarryError::BadState,
4156 );
4157 };
4158 let split_slots = splits.iter().try_fold(0usize, |slots, split| {
4159 slots.checked_add(split.child_slots.len().saturating_sub(1))
4160 });
4161 let Some(split_slots) = split_slots else {
4162 return self.abort_unpublished_split_mapping_mutation(
4163 retired_range,
4164 preimage,
4165 None,
4166 splits,
4167 StarryError::BadState,
4168 );
4169 };
4170
4171 let deferred_tlb = DeferredTlbRetireGuard::enter();
4172 for (range, backend) in frags {
4173 if let Err(error) = backend.unmap_range(range, &mut self.pt) {
4174 drop(deferred_tlb);
4175 if let Err(flush_error) = crate::mm::flush_tlb_range_sync(start, size) {
4176 warn!("discard repair could not invalidate {start:?}+{size:#x}: {flush_error}");
4177 }
4178 return self.abort_unpublished_split_mapping_mutation(
4179 retired_range,
4180 preimage,
4181 None,
4182 splits,
4183 error,
4184 );
4185 }
4186 }
4187 drop(deferred_tlb);
4188 if let Err(error) = self.detach_mapping_slots(retired_range) {
4189 return self.abort_unpublished_split_mapping_mutation(
4190 retired_range,
4191 preimage,
4192 None,
4193 splits,
4194 error,
4195 );
4196 }
4197 self.park_retired_mapping_owners(retire_epoch, retired_owners);
4198 mutation.set_pte_delta(PteDelta {
4199 unmapped: u32::try_from(retired_pages).unwrap_or(u32::MAX),
4200 ..PteDelta::default()
4201 });
4202 mutation.set_mapping_delta(MappingDelta {
4203 attached: u32::try_from(split_slots).unwrap_or(u32::MAX),
4204 detached: u32::try_from(detached_slots).unwrap_or(u32::MAX),
4205 });
4206 mutation.set_resident_delta(retired_resident.checked_negated_delta()?);
4207
4208 match self.commit_mutation_classified(mutation) {
4209 Ok(()) => {
4210 self.release_retired_mapping_owners(retire_epoch);
4211 Ok(())
4212 }
4213 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
4214 Err(CommitMutationError::Unpublished(error)) => self
4215 .abort_unpublished_split_mapping_mutation(
4216 retired_range,
4217 preimage,
4218 Some(retire_epoch),
4219 splits,
4220 error,
4221 ),
4222 }
4223 }
4224
4225 pub fn mark_lazy_free(&mut self, start: VirtAddr, size: usize) -> StarryResult {
4233 self.validate_region(start, size)?;
4234 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
4235 let mut covered = start;
4236 for entry in self.vma_root.iter_entries() {
4237 if entry.start() >= end {
4238 break;
4239 }
4240 if entry.end() <= start {
4241 continue;
4242 }
4243 let fragment_start = entry.start().max(start);
4244 if fragment_start > covered {
4245 return Err(StarryError::NoMemory);
4246 }
4247 if !entry.operation().is_private_anonymous() {
4248 return Err(StarryError::InvalidInput);
4249 }
4250 covered = entry.end().min(end);
4251 }
4252 if covered < end {
4253 return Err(StarryError::NoMemory);
4254 }
4255
4256 let range = VirtAddrRange::from_start_size(start, size);
4257 let mut candidates = Vec::new();
4258 candidates
4259 .try_reserve(self.mapping_slots_overlapping(range).count())
4260 .map_err(|_| StarryError::NoMemory)?;
4261 for (_, slot) in self.mapping_slots_overlapping(range) {
4262 if slot.page_order != PageOrder::BASE || slot.page.mapping_refs() != 1 {
4263 continue;
4264 }
4265 match slot.page.state() {
4266 PageState::LazyFree => continue,
4267 PageState::Present => {}
4268 PageState::Reserved
4269 | PageState::Evicting
4270 | PageState::Writeback
4271 | PageState::Retired => return Err(StarryError::ResourceBusy),
4272 }
4273 let (paddr, flags, page_size) = self.pt.query(slot.va)?;
4274 if slot.mapped_paddr() != Some(paddr) || page_size != PAGE_SIZE_4K {
4275 return Err(StarryError::BadState);
4276 }
4277 candidates.push((slot.va, paddr, flags, slot.page.clone()));
4278 }
4279 if candidates.is_empty() {
4280 return Ok(());
4281 }
4282
4283 let mut mutation = self.prepare_mutation_range(start, size);
4284 let pt = &mut self.pt;
4285 let stripes = self.pte_domain.lock_range(range);
4286 let mut protected = 0usize;
4287 for &(va, paddr, flags, _) in &candidates {
4288 if !flags.contains(MappingFlags::WRITE) {
4289 continue;
4290 }
4291 if pt
4294 .remap_page(va, paddr, flags - MappingFlags::WRITE)
4295 .is_err()
4296 {
4297 for &(old_va, old_paddr, old_flags, _) in candidates.iter().rev() {
4298 if old_flags.contains(MappingFlags::WRITE) {
4299 let _ = pt.remap_page(old_va, old_paddr, old_flags);
4301 }
4302 }
4303 return Err(StarryError::BadState);
4304 }
4305 protected += 1;
4306 }
4307 drop(stripes);
4308
4309 for (marked, (_, _, _, page)) in candidates.iter().enumerate() {
4310 if !page.mark_lazy_free() {
4311 for (_, _, _, marked_page) in candidates[..marked].iter().rev() {
4312 let _ = marked_page.clear_lazy_free();
4313 }
4314 let pt = &mut self.pt;
4315 let _stripes = self.pte_domain.lock_range(range);
4316 for &(va, paddr, flags, _) in &candidates {
4317 if flags.contains(MappingFlags::WRITE) {
4318 let _ = pt.remap_page(va, paddr, flags);
4320 }
4321 }
4322 return Err(StarryError::ResourceBusy);
4323 }
4324 }
4325
4326 mutation.set_pte_delta(PteDelta {
4327 protected: u32::try_from(protected).unwrap_or(u32::MAX),
4328 ..PteDelta::default()
4329 });
4330 match self.commit_mutation_classified(mutation) {
4331 Ok(()) => {
4332 lifecycle::request_lazy_free_reclaim();
4333 Ok(())
4334 }
4335 Err(CommitMutationError::PublishedPendingTlb(error)) => {
4336 lifecycle::request_lazy_free_reclaim();
4339 Err(error)
4340 }
4341 Err(CommitMutationError::Unpublished(error)) => {
4342 for (_, _, _, page) in candidates.iter().rev() {
4343 if !page.clear_lazy_free() {
4344 self.mutation_gate.mark_needs_repair();
4345 return Err(StarryError::BadState);
4346 }
4347 }
4348 let pt = &mut self.pt;
4349 let _stripes = self.pte_domain.lock_range(range);
4350 for &(va, paddr, flags, _) in &candidates {
4351 if flags.contains(MappingFlags::WRITE)
4352 && pt.remap_page(va, paddr, flags).is_err()
4353 {
4354 self.mutation_gate.mark_needs_repair();
4355 return Err(StarryError::BadState);
4356 }
4357 }
4358 Err(error)
4359 }
4360 }
4361 }
4362
4363 pub(crate) fn reclaim_lazy_free_pages(&mut self, limit: usize) -> StarryResult<usize> {
4368 if limit == 0 {
4369 return Ok(0);
4370 }
4371 let mut reclaimed = 0;
4372 let mut cursor = None;
4373 while reclaimed < limit {
4374 let eligible = |(key, slot): (&MappingSlotKey, &Arc<MappingSlot>)| {
4375 (slot.page_order == PageOrder::BASE
4376 && slot.page.state() == PageState::LazyFree
4377 && slot.page.mapping_refs() == 1)
4378 .then(|| (*key, slot.page.clone()))
4379 };
4380 let candidate = if let Some(after) = cursor {
4381 self.mapping_slots
4382 .range((
4383 core::ops::Bound::Excluded(after),
4384 core::ops::Bound::Unbounded,
4385 ))
4386 .find_map(eligible)
4387 } else {
4388 self.mapping_slots.iter().find_map(eligible)
4389 };
4390 let Some((key, page)) = candidate else {
4391 break;
4392 };
4393 cursor = Some(key);
4394 let Some(entry) = self.vma_root.lookup_entry(key.va) else {
4395 return Err(StarryError::BadState);
4396 };
4397 if !entry.operation().is_private_anonymous() {
4398 return Err(StarryError::BadState);
4399 }
4400 let still_reclaimable = self.mapping_slots.get(&key).is_some_and(|slot| {
4401 Arc::ptr_eq(&slot.page, &page)
4402 && page.state() == PageState::LazyFree
4403 && page.mapping_refs() == 1
4404 });
4405 if !still_reclaimable {
4406 continue;
4407 }
4408 self.discard_range(key.va, PAGE_SIZE_4K)?;
4409 if !page.rmap.is_empty()
4410 || page.mapping_refs() != 0
4411 || !page.transition(PageState::LazyFree, PageState::Retired)
4412 {
4413 self.mutation_gate.mark_needs_repair();
4414 return Err(StarryError::BadState);
4415 }
4416 reclaimed += 1;
4417 }
4418 Ok(reclaimed)
4419 }
4420
4421 pub fn unmap(&mut self, start: VirtAddr, size: usize) -> StarryResult {
4426 self.unmap_classified(start, size)?.into_result()
4427 }
4428
4429 pub(crate) fn unmap_outcome(
4434 &mut self,
4435 start: VirtAddr,
4436 size: usize,
4437 ) -> StarryResult<AddressSpaceMutationOutcome> {
4438 self.unmap_classified(start, size)
4439 }
4440
4441 fn unmap_classified(
4442 &mut self,
4443 start: VirtAddr,
4444 size: usize,
4445 ) -> StarryResult<AddressSpaceMutationOutcome> {
4446 self.validate_region(start, size)?;
4447 let range = VirtAddrRange::from_start_size(start, size);
4448 let before_vmas = self.vma_root.len();
4449 let memfd_deltas = crate::syscall::memfd_prepare_aspace_unmap_deltas(self, start, size);
4453 let mut mutation = self.prepare_mutation_range(start, size);
4454 let retire_epoch = mutation
4455 .receipt()
4456 .base_epoch
4457 .checked_next()
4458 .ok_or(StarryError::BadState)?;
4459 mutation
4460 .try_reserve_tlb_ranges(2)
4461 .map_err(|_| StarryError::NoMemory)?;
4462 let splits = self.apply_partial_huge_splits(range)?;
4463 for index in 0..splits.len() {
4464 let split = &splits[index];
4465 let Some(tlb_range) =
4466 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
4467 else {
4468 return self
4469 .abort_unpublished_huge_splits(splits, StarryError::BadState)
4470 .map(|()| AddressSpaceMutationOutcome::Complete);
4471 };
4472 mutation.add_tlb_range(tlb_range);
4473 }
4474 let preimage = match self.capture_mapping_preimage(range) {
4475 Ok(preimage) => preimage,
4476 Err(error) => {
4477 return self
4478 .abort_unpublished_huge_splits(splits, error)
4479 .map(|()| AddressSpaceMutationOutcome::Complete);
4480 }
4481 };
4482 let retired_owners = match self.prepare_retired_mapping_owners(range) {
4483 Ok(owners) => owners,
4484 Err(error) => {
4485 return self
4486 .abort_unpublished_huge_splits(splits, error)
4487 .map(|()| AddressSpaceMutationOutcome::Complete);
4488 }
4489 };
4490 let Ok((detached_slots, detached_resident_pages, detached_resident)) =
4491 self.mapping_slot_summary(range)
4492 else {
4493 return self
4494 .abort_unpublished_huge_splits(splits, StarryError::BadState)
4495 .map(|()| AddressSpaceMutationOutcome::Complete);
4496 };
4497 let split_slots = splits.iter().try_fold(0usize, |slots, split| {
4498 slots.checked_add(split.child_slots.len().saturating_sub(1))
4499 });
4500 let Some(split_slots) = split_slots else {
4501 return self
4502 .abort_unpublished_huge_splits(splits, StarryError::BadState)
4503 .map(|()| AddressSpaceMutationOutcome::Complete);
4504 };
4505
4506 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
4508 let removed_pages: u64 = self
4509 .vma_root
4510 .iter_entries()
4511 .filter(|entry| entry.start() < end && entry.end() > start)
4512 .map(|entry| {
4513 let lo = entry.start().max(start);
4514 let hi = entry.end().min(end);
4515 ((hi - lo) / PAGE_SIZE_4K) as u64
4516 })
4517 .sum();
4518
4519 let deferred_tlb = DeferredTlbRetireGuard::enter();
4520 if let Err(error) = self.apply_unmap_unpublished(range) {
4521 drop(deferred_tlb);
4522 if let Err(flush_error) = crate::mm::flush_tlb_range_sync(start, size) {
4523 warn!("unmap repair could not invalidate {start:?}+{size:#x}: {flush_error}");
4524 }
4525 return self
4526 .abort_unpublished_split_mapping_mutation(range, preimage, None, splits, error)
4527 .map(|()| AddressSpaceMutationOutcome::Complete);
4528 }
4529 drop(deferred_tlb);
4530 if let Err(error) = self.detach_mapping_slots(range) {
4531 return self
4532 .abort_unpublished_split_mapping_mutation(range, preimage, None, splits, error)
4533 .map(|()| AddressSpaceMutationOutcome::Complete);
4534 }
4535 self.park_retired_mapping_owners(retire_epoch, retired_owners);
4536 mutation.set_pte_delta(PteDelta {
4537 unmapped: u32::try_from(detached_resident_pages).unwrap_or(u32::MAX),
4538 ..PteDelta::default()
4539 });
4540 mutation.set_mapping_delta(MappingDelta {
4541 attached: u32::try_from(split_slots).unwrap_or(u32::MAX),
4542 detached: u32::try_from(detached_slots).unwrap_or(u32::MAX),
4543 });
4544 mutation.set_resident_delta(detached_resident.checked_negated_delta()?);
4545 self.vm_stat.on_unmap(removed_pages);
4546 let after_vmas = self.vma_root.len();
4547 mutation.set_vma_delta(VmaDelta {
4548 removed: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
4549 split: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
4550 ..VmaDelta::default()
4551 });
4552 match self.commit_mutation_classified(mutation) {
4553 Ok(()) => {
4554 self.release_retired_mapping_owners(retire_epoch);
4555 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
4556 Ok(AddressSpaceMutationOutcome::Complete)
4557 }
4558 Err(CommitMutationError::PublishedPendingTlb(error)) => {
4559 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
4560 Ok(AddressSpaceMutationOutcome::PublishedPendingTlb(error))
4561 }
4562 Err(CommitMutationError::Unpublished(error)) => self
4563 .abort_unpublished_split_mapping_mutation(
4564 range,
4565 preimage,
4566 Some(retire_epoch),
4567 splits,
4568 error,
4569 )
4570 .map(|()| AddressSpaceMutationOutcome::Complete),
4571 }
4572 }
4573
4574 fn prepare_moved_slots(
4575 &mut self,
4576 moved_pages: &[MovedPage],
4577 target_mapping: MappingId,
4578 ) -> StarryResult<Vec<PreparedMovedSlot>> {
4579 let mut prepared = Vec::new();
4580 prepared
4581 .try_reserve(moved_pages.len())
4582 .map_err(|_| StarryError::NoMemory)?;
4583
4584 for moved in moved_pages {
4585 let source_key = MappingSlotKey {
4586 space_id: self.id,
4587 va: moved.src_va,
4588 };
4589 let target_slot_va = match moved.destination {
4590 MovedPageDestination::SourceOwner => moved.dst_va,
4591 MovedPageDestination::TargetOwner { slot_va } => slot_va,
4592 };
4593 let target_key = MappingSlotKey {
4594 space_id: self.id,
4595 va: target_slot_va,
4596 };
4597 if matches!(moved.destination, MovedPageDestination::SourceOwner)
4598 && prepared.iter().any(|entry| {
4599 matches!(
4600 entry,
4601 PreparedMovedSlot::Relocate {
4602 target_key: existing,
4603 ..
4604 } if *existing == target_key
4605 )
4606 })
4607 {
4608 return Err(StarryError::BadState);
4609 }
4610 let source = self
4611 .mapping_slots
4612 .get(&source_key)
4613 .cloned()
4614 .ok_or(StarryError::BadState)?;
4615 let page_order = moved
4616 .page_size
4617 .trailing_zeros()
4618 .checked_sub(PAGE_SIZE_4K.trailing_zeros())
4619 .and_then(|order| u8::try_from(order).ok())
4620 .map(PageOrder::new)
4621 .ok_or(StarryError::BadState)?;
4622 let frame_start = source.page.frame().paddr().as_usize();
4623 let frame_end = frame_start
4624 .checked_add(source.page.frame().size())
4625 .ok_or(StarryError::BadState)?;
4626 let leaf_start = moved.paddr.as_usize();
4627 let leaf_end = leaf_start
4628 .checked_add(moved.page_size)
4629 .ok_or(StarryError::BadState)?;
4630 if source.state() != SlotState::Present
4631 || source.mm_id != self.id
4632 || source.va != moved.src_va
4633 || source.page_order != page_order
4634 || source.mapped_paddr() != Some(moved.paddr)
4635 || leaf_start < frame_start
4636 || leaf_end > frame_end
4637 || (page_order != PageOrder::BASE && !source.has_huge_split_deposit())
4638 {
4639 return Err(StarryError::BadState);
4640 }
4641
4642 match moved.destination {
4643 MovedPageDestination::SourceOwner => {
4644 let replacement = MappingSlot::new_with_frame_offset(
4645 target_mapping,
4646 self.id,
4647 moved.dst_va,
4648 page_order,
4649 source.page.clone(),
4650 source.frame_offset(),
4651 source.resident_kind(),
4652 )
4653 .ok_or(StarryError::BadState)?;
4654 let replacement = if page_order == PageOrder::BASE {
4655 replacement
4656 } else {
4657 replacement
4658 .attach_huge_split_deposit(self.pt.prepare_huge_split(moved.dst_va)?)
4659 .map_err(|_| StarryError::BadState)?
4660 };
4661 prepared.push(PreparedMovedSlot::Relocate {
4662 source_key,
4663 target_key,
4664 source,
4665 replacement: Arc::new(replacement),
4666 });
4667 }
4668 MovedPageDestination::TargetOwner { .. } => {
4669 prepared.push(PreparedMovedSlot::DetachSource {
4670 source_key,
4671 target_key,
4672 source,
4673 });
4674 }
4675 }
4676 }
4677 Ok(prepared)
4678 }
4679
4680 fn publish_moved_slots(&mut self, prepared: Vec<PreparedMovedSlot>) -> StarryResult {
4681 for entry in prepared {
4682 match entry {
4683 PreparedMovedSlot::Relocate {
4684 source_key,
4685 target_key,
4686 source,
4687 replacement,
4688 } => {
4689 if self.mapping_slots.contains_key(&target_key) {
4690 return Err(StarryError::BadState);
4691 }
4692 let removed = self
4693 .mapping_slots
4694 .remove(&source_key)
4695 .ok_or(StarryError::BadState)?;
4696 if !Arc::ptr_eq(&removed, &source) {
4697 self.mapping_slots.insert(source_key, removed);
4698 return Err(StarryError::BadState);
4699 }
4700 if let Err(error) = source.relocate_to(&replacement) {
4701 let restored = source.state() == SlotState::Present
4702 && self
4703 .mapping_slots
4704 .insert(source_key, source.clone())
4705 .is_none();
4706 if !restored || error == MappingGraphError::RollbackFailed {
4707 self.mutation_gate.mark_needs_repair();
4708 }
4709 return Err(match error {
4710 MappingGraphError::ResourceExhausted => StarryError::NoMemory,
4711 _ => StarryError::BadState,
4712 });
4713 }
4714 if self.mapping_slots.insert(target_key, replacement).is_some() {
4715 self.mutation_gate.mark_needs_repair();
4718 return Err(StarryError::BadState);
4719 }
4720 }
4721 PreparedMovedSlot::DetachSource {
4722 source_key,
4723 target_key,
4724 source,
4725 } => {
4726 let target = self
4727 .mapping_slots
4728 .get(&target_key)
4729 .ok_or(StarryError::BadState)?;
4730 if target.state() != SlotState::Present {
4731 return Err(StarryError::BadState);
4732 }
4733 let removed = self
4734 .mapping_slots
4735 .remove(&source_key)
4736 .ok_or(StarryError::BadState)?;
4737 if !Arc::ptr_eq(&removed, &source) || !removed.detach() {
4738 self.mapping_slots.insert(source_key, removed);
4739 return Err(StarryError::BadState);
4740 }
4741 }
4742 }
4743 }
4744 Ok(())
4745 }
4746
4747 fn apply_move_pages(
4752 &mut self,
4753 src: VirtAddr,
4754 dst: VirtAddr,
4755 size: usize,
4756 ) -> StarryResult<Vec<MovedPage>> {
4757 let move_range =
4758 VirtAddrRange::try_from_start_size(src, size).ok_or(StarryError::InvalidInput)?;
4759 let dst_range =
4760 VirtAddrRange::try_from_start_size(dst, size).ok_or(StarryError::InvalidInput)?;
4761 if move_range.overlaps(dst_range) {
4762 return Err(StarryError::InvalidInput);
4768 }
4769 self.validate_materialized_leaf_boundaries(src, size)?;
4770 let source_slots = self.materialized_slots_overlapping(&[move_range])?;
4771 let mut mapped_pages = alloc::vec::Vec::new();
4772 mapped_pages
4773 .try_reserve(source_slots.len())
4774 .map_err(|_| StarryError::NoMemory)?;
4775 for (key, slot, occupied_leaf) in source_slots {
4776 let offset = key.va.checked_sub_addr(src).ok_or(StarryError::BadState)?;
4777 let dst_va = dst.checked_add(offset).ok_or(StarryError::InvalidInput)?;
4778 let paddr = occupied_leaf.paddr;
4779 let flags = occupied_leaf.flags;
4780 let page_size = occupied_leaf.range.size();
4781 let expected_size = PAGE_SIZE_4K
4782 .checked_shl(slot.page_order.get().into())
4783 .ok_or(StarryError::BadState)?;
4784 if slot.state() != SlotState::Present
4785 || slot.va != key.va
4786 || slot.mm_id != self.id
4787 || slot.mapped_paddr() != Some(paddr)
4788 || page_size != expected_size
4789 {
4790 return Err(StarryError::BadState);
4791 }
4792 if !key.va.is_aligned(page_size) || !dst_va.is_aligned(page_size) {
4793 return Err(StarryError::OperationNotSupported);
4794 }
4795 mapped_pages.push((key.va, dst_va, paddr, flags, page_size));
4796 }
4797
4798 let mut moved_pages = alloc::vec::Vec::new();
4799 moved_pages
4800 .try_reserve(mapped_pages.len())
4801 .map_err(|_| StarryError::NoMemory)?;
4802 let mut move_plans = Vec::<PageTableMovePlan>::new();
4803 move_plans
4804 .try_reserve(mapped_pages.len())
4805 .map_err(|_| StarryError::NoMemory)?;
4806
4807 for &(_, dst_va, _, _, page_size) in &mapped_pages {
4812 match self.pt.query_occupied(dst_va) {
4813 Ok(_) => {}
4814 Err(PagingError::NotMapped) => {
4815 let plan = self.pt.plan_map_page(dst_va, page_size)?;
4816 if let Some(deposit) = plan.prepare_path()? {
4817 let apply_result = {
4818 let _structure = self.pte_domain.lock_structure();
4819 self.pt.try_install_map_path(deposit)
4820 };
4821 if let Err(failure) = apply_result {
4822 let (error, deposit) = failure.into_parts();
4823 drop(deposit);
4827 return Err(error.into());
4828 }
4829 }
4830 }
4831 Err(error) => return Err(error.into()),
4832 }
4833 }
4834
4835 for &(src_va, dst_va, paddr, flags, page_size) in &mapped_pages {
4836 let plan = self.pt.plan_move_page(src_va, dst_va)?;
4837 if plan.source_vaddr() != src_va
4838 || plan.destination_vaddr() != dst_va
4839 || plan.paddr() != paddr
4840 || plan.config() != flags
4841 || plan.page_size() != page_size
4842 {
4843 return Err(StarryError::BadState);
4844 }
4845 let destination = if plan.destination_is_occupied() {
4846 let target_size = plan.destination_page_size();
4847 if target_size < PAGE_SIZE_4K || !target_size.is_power_of_two() {
4848 return Err(StarryError::BadState);
4849 }
4850 MovedPageDestination::TargetOwner {
4851 slot_va: dst_va.align_down(target_size),
4852 }
4853 } else {
4854 MovedPageDestination::SourceOwner
4855 };
4856 moved_pages.push(MovedPage {
4857 src_va,
4858 dst_va,
4859 paddr,
4860 page_size,
4861 destination,
4862 });
4863 move_plans.push(plan);
4864 }
4865
4866 let pte_domain = &self.pte_domain;
4871 let cursor = &mut self.pt;
4872 let apply_result = {
4873 let _structure = pte_domain.lock_structure();
4874 let pte_stripes = pte_domain.lock_ranges(&[move_range, dst_range]);
4875 debug_assert!(!pte_stripes.stripe_indices().is_empty());
4876 cursor.try_move_pages_with(&move_plans)
4877 };
4878 let applied = apply_result?;
4879 if applied != moved_pages.len() {
4880 self.mutation_gate.mark_needs_repair();
4881 return Err(StarryError::BadState);
4882 }
4883 Ok(moved_pages)
4884 }
4885
4886 #[allow(clippy::too_many_arguments)]
4890 pub(crate) fn mremap_move_transaction(
4891 &mut self,
4892 src: VirtAddr,
4893 src_size: usize,
4894 target: VirtAddr,
4895 target_size: usize,
4896 permissions: MappingPermissions,
4897 target_backend: MappingOperation,
4898 huge_page_advice: HugePageAdvice,
4899 lock_mode: VmaLockMode,
4900 advice_policy: VmaAdvicePolicy,
4901 dontunmap: bool,
4902 replace_target: bool,
4903 memlock_limit: Option<MemlockLimit>,
4904 ) -> StarryResult {
4905 self.validate_region(src, src_size)?;
4906 self.validate_region(target, target_size)?;
4907 if !permissions.maximum.contains(permissions.current) {
4908 return Err(StarryError::PermissionDenied);
4909 }
4910 let source_range =
4911 VirtAddrRange::try_from_start_size(src, src_size).ok_or(StarryError::InvalidInput)?;
4912 let target_range = VirtAddrRange::try_from_start_size(target, target_size)
4913 .ok_or(StarryError::InvalidInput)?;
4914 if source_range.overlaps(target_range) {
4915 return Err(StarryError::InvalidInput);
4916 }
4917 let move_size = src_size.min(target_size);
4918 let moved_source_range =
4919 VirtAddrRange::try_from_start_size(src, move_size).ok_or(StarryError::InvalidInput)?;
4920
4921 let source_page_size = self
4922 .vma_root
4923 .lookup_entry(src)
4924 .map(|entry| entry.operation().page_size())
4925 .ok_or(StarryError::BadAddress)?;
4926 let source_replacement =
4927 dontunmap.then(|| MappingOperation::new_alloc(src, source_page_size, ""));
4928 let rollback_ranges = [source_range, target_range];
4929 let tail_range = if src_size > move_size {
4930 Some(
4931 VirtAddrRange::try_from_start_size(
4932 src.checked_add(move_size)
4933 .ok_or(StarryError::InvalidInput)?,
4934 src_size - move_size,
4935 )
4936 .ok_or(StarryError::InvalidInput)?,
4937 )
4938 } else {
4939 None
4940 };
4941
4942 let target_removed_pages = if replace_target {
4943 self.vma_root
4944 .iter_entries()
4945 .filter(|entry| {
4946 entry.start() < target_range.end && entry.end() > target_range.start
4947 })
4948 .try_fold(0u64, |pages, entry| {
4949 let lo = entry.start().max(target_range.start);
4950 let hi = entry.end().min(target_range.end);
4951 let bytes = hi.checked_sub_addr(lo).ok_or(StarryError::BadState)?;
4952 pages
4953 .checked_add((bytes / PAGE_SIZE_4K) as u64)
4954 .ok_or(StarryError::InvalidInput)
4955 })?
4956 } else {
4957 0
4958 };
4959 let target_successor = self.prepare_mapping_successor(
4963 target_range,
4964 permissions,
4965 &target_backend,
4966 huge_page_advice,
4967 lock_mode,
4968 advice_policy,
4969 replace_target,
4970 )?;
4971 let mut final_successor = target_successor.clone();
4972 if dontunmap {
4973 final_successor = final_successor
4974 .without_range(moved_source_range)
4975 .ok_or(StarryError::BadState)?;
4976 let replacement = source_replacement.as_ref().ok_or(StarryError::BadState)?;
4977 let replacement_entry = final_successor
4978 .prepare_mapping_entry(
4979 moved_source_range,
4980 permissions.current,
4981 permissions.reported,
4982 permissions.maximum,
4983 huge_page_advice,
4984 VmaLockMode::Unlocked,
4988 advice_policy,
4989 replacement.clone(),
4990 )
4991 .ok_or(StarryError::BadState)?;
4992 final_successor = final_successor
4993 .with_mapping_entry(replacement_entry, false)
4994 .ok_or(StarryError::BadState)?;
4995 } else {
4996 if let Some(tail) = tail_range {
4997 final_successor = final_successor
4998 .without_range(tail)
4999 .ok_or(StarryError::BadState)?;
5000 }
5001 final_successor = final_successor
5002 .without_range(moved_source_range)
5003 .ok_or(StarryError::BadState)?;
5004 }
5005 self.validate_memlock_successor(&final_successor, memlock_limit)?;
5006 let before_vmas = self.vma_root.len();
5007 let graph_preimage = self.capture_mapping_graph_snapshot(&rollback_ranges)?;
5008
5009 let mut mutation = self.prepare_mutation_range(src, src_size);
5010 mutation
5011 .try_reserve_tlb_ranges(5)
5012 .map_err(|error| match error {
5013 MutationError::ResourceExhausted => StarryError::NoMemory,
5014 _ => StarryError::BadState,
5015 })?;
5016 mutation
5017 .add_tlb_range(TlbRange::new(target, target_size).ok_or(StarryError::InvalidInput)?);
5018 let retire_epoch = mutation
5019 .receipt()
5020 .base_epoch
5021 .checked_next()
5022 .ok_or(StarryError::BadState)?;
5023
5024 let split_ranges = [moved_source_range, target_range];
5030 let splits = self.apply_partial_huge_splits_for_ranges(&split_ranges)?;
5031 for index in 0..splits.len() {
5032 let split = &splits[index];
5033 let Some(tlb_range) =
5034 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
5035 else {
5036 return self.abort_unpublished_huge_splits(splits, StarryError::BadState);
5037 };
5038 mutation.add_tlb_range(tlb_range);
5039 }
5040
5041 let preimage = match self.capture_mapping_preimage_ranges(&rollback_ranges) {
5042 Ok(preimage) => preimage,
5043 Err(error) => return self.abort_unpublished_huge_splits(splits, error),
5044 };
5045 let target_owners = match replace_target
5046 .then(|| self.prepare_retired_mapping_owners(target_range))
5047 .transpose()
5048 {
5049 Ok(owners) => owners,
5050 Err(error) => {
5051 return self.abort_unpublished_split_mapping_mutation_ranges(
5052 &rollback_ranges,
5053 preimage,
5054 None,
5055 splits,
5056 error,
5057 );
5058 }
5059 };
5060 let tail_owners = match tail_range
5061 .map(|range| self.prepare_retired_mapping_owners(range))
5062 .transpose()
5063 {
5064 Ok(owners) => owners,
5065 Err(error) => {
5066 return self.abort_unpublished_split_mapping_mutation_ranges(
5067 &rollback_ranges,
5068 preimage,
5069 None,
5070 splits,
5071 error,
5072 );
5073 }
5074 };
5075
5076 let mut memfd_deltas = crate::syscall::memfd_prepare_aspace_replace_deltas(
5077 self,
5078 target,
5079 target_size,
5080 permissions.current,
5081 &target_backend,
5082 );
5083 if dontunmap {
5084 if let Some(replacement) = source_replacement.as_ref() {
5085 memfd_deltas.extend(crate::syscall::memfd_prepare_aspace_replace_deltas(
5086 self,
5087 src,
5088 move_size,
5089 permissions.current,
5090 replacement,
5091 ));
5092 }
5093 } else {
5094 memfd_deltas.extend(crate::syscall::memfd_prepare_aspace_unmap_deltas(
5095 self, src, src_size,
5096 ));
5097 }
5098
5099 let apply_result = (|| -> StarryResult<usize> {
5100 let target_materialization = self.apply_mapping_pages_unpublished(
5101 target_range,
5102 permissions,
5103 &target_backend,
5104 replace_target,
5105 )?;
5106 self.vma_root = Arc::new(target_successor.clone());
5107
5108 let moved_pages = self.apply_move_pages(src, target, move_size)?;
5109 let prepared_moved_slots =
5110 self.prepare_moved_slots(&moved_pages, target_backend.mapping_id())?;
5111 if !dontunmap && let Some(tail) = tail_range {
5112 self.apply_unmap_pages_unpublished(tail)?;
5113 }
5114
5115 if replace_target {
5116 self.detach_mapping_slots(target_range)?;
5117 }
5118 self.publish_prepared_pte_owners(
5119 &target_backend,
5120 target_range,
5121 &target_materialization,
5122 )?;
5123 self.publish_moved_slots(prepared_moved_slots)?;
5124 if !dontunmap && let Some(tail) = tail_range {
5125 self.detach_mapping_slots(tail)?;
5126 }
5127 if self
5128 .mapping_slots_overlapping(moved_source_range)
5129 .next()
5130 .is_some()
5131 {
5132 return Err(StarryError::BadState);
5133 }
5134 self.vma_root = Arc::new(final_successor.clone());
5135
5136 self.vm_stat.on_map((target_size / PAGE_SIZE_4K) as u64);
5137 if target_removed_pages != 0 {
5138 self.vm_stat.on_unmap(target_removed_pages);
5139 }
5140 if !dontunmap {
5141 self.vm_stat.on_unmap((src_size / PAGE_SIZE_4K) as u64);
5142 }
5143 Ok(moved_pages.len())
5144 })();
5145
5146 let moved_leaves = match apply_result {
5147 Ok(moved) => moved,
5148 Err(error) => {
5149 return self.abort_unpublished_split_mapping_mutation_ranges(
5150 &rollback_ranges,
5151 preimage,
5152 None,
5153 splits,
5154 error,
5155 );
5156 }
5157 };
5158 if let Some(owners) = target_owners {
5159 self.park_retired_mapping_owners(retire_epoch, owners);
5160 }
5161 if let Some(owners) = tail_owners {
5162 self.park_retired_mapping_owners(retire_epoch, owners);
5163 }
5164
5165 let after_vmas = self.vma_root.len();
5166 mutation.set_vma_delta(VmaDelta {
5167 inserted: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
5168 removed: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
5169 ..VmaDelta::default()
5170 });
5171 mutation.set_pte_delta(PteDelta {
5172 mapped: u32::try_from(moved_leaves).unwrap_or(u32::MAX),
5173 unmapped: u32::try_from(moved_leaves).unwrap_or(u32::MAX),
5174 ..PteDelta::default()
5175 });
5176 if let Err(error) =
5177 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &rollback_ranges)
5178 {
5179 return self.abort_unpublished_split_mapping_mutation_ranges(
5180 &rollback_ranges,
5181 preimage,
5182 Some(retire_epoch),
5183 splits,
5184 error,
5185 );
5186 }
5187
5188 match self.commit_mutation_classified(mutation) {
5189 Ok(()) => {
5190 self.release_retired_mapping_owners(retire_epoch);
5191 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5192 Ok(())
5193 }
5194 Err(CommitMutationError::PublishedPendingTlb(error)) => {
5195 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5196 Err(error)
5197 }
5198 Err(CommitMutationError::Unpublished(error)) => self
5199 .abort_unpublished_split_mapping_mutation_ranges(
5200 &rollback_ranges,
5201 preimage,
5202 Some(retire_epoch),
5203 splits,
5204 error,
5205 ),
5206 }
5207 }
5208
5209 pub fn extend_area(&mut self, addr: VirtAddr, additional_size: usize) -> StarryResult {
5211 self.extend_area_with_memlock(addr, additional_size, None)
5212 }
5213
5214 pub(crate) fn extend_area_with_memlock(
5215 &mut self,
5216 addr: VirtAddr,
5217 additional_size: usize,
5218 memlock_limit: Option<MemlockLimit>,
5219 ) -> StarryResult {
5220 if additional_size == 0 {
5221 return Ok(());
5222 }
5223 let entry = self
5224 .vma_root
5225 .lookup_entry(addr)
5226 .ok_or(StarryError::InvalidInput)?;
5227 if !additional_size.is_multiple_of(PAGE_SIZE_4K) {
5228 return Err(StarryError::InvalidInput);
5229 }
5230 let old_end = entry.end();
5231 let grown = VirtAddrRange::try_from_start_size(old_end, additional_size)
5232 .ok_or(StarryError::InvalidInput)?;
5233 let preimage = self.capture_mapping_preimage(grown)?;
5234 let graph_preimage = self.capture_mapping_graph_snapshot(&[grown])?;
5235 let mut mutation = self.prepare_mutation_range(old_end, additional_size);
5236 if entry
5237 .end()
5238 .checked_add(additional_size)
5239 .is_none_or(|new_end| new_end > self.end())
5240 {
5241 return Err(StarryError::NoMemory);
5242 }
5243 let (materialized_range, operation, materialization) =
5244 match self.apply_extend_unpublished(addr, additional_size, memlock_limit) {
5245 Ok(applied) => applied,
5246 Err(error) => {
5247 return self.abort_unpublished_mapping_mutation(grown, preimage, error);
5248 }
5249 };
5250 if materialized_range != grown {
5251 return self.abort_unpublished_mapping_mutation(grown, preimage, StarryError::BadState);
5252 }
5253 self.vm_stat.on_map((additional_size / PAGE_SIZE_4K) as u64);
5254 if let Err(error) = self.publish_prepared_pte_owners(&operation, grown, &materialization) {
5255 return self.abort_unpublished_mapping_mutation(grown, preimage, error);
5256 }
5257 if let Err(error) =
5258 self.set_mapping_graph_receipt_delta(&mut mutation, &graph_preimage, &[grown])
5259 {
5260 return self.abort_unpublished_mapping_mutation(grown, preimage, error);
5261 }
5262 match self.commit_mutation_classified(mutation) {
5263 Ok(()) => Ok(()),
5264 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
5265 Err(CommitMutationError::Unpublished(error)) => {
5266 self.abort_unpublished_mapping_mutation(grown, preimage, error)
5267 }
5268 }
5269 }
5270
5271 fn process_area_data<F>(&self, start: VirtAddr, size: usize, mut f: F) -> StarryResult
5275 where
5276 F: FnMut(VirtAddr, usize, usize),
5277 {
5278 if size == 0 {
5279 return Ok(());
5280 }
5281 if !self.contains_range(start, size) {
5282 return Err(StarryError::InvalidInput);
5283 }
5284 let end = start.checked_add(size).ok_or(StarryError::InvalidInput)?;
5285 let end_align_up = end
5289 .as_usize()
5290 .checked_add(PAGE_SIZE_4K - 1)
5291 .map(|value| VirtAddr::from_usize(value & !(PAGE_SIZE_4K - 1)))
5292 .ok_or(StarryError::InvalidInput)?;
5293 let page_start = start.align_down_4k();
5294 let pages = PageIter4K::new(page_start, end_align_up).ok_or(StarryError::InvalidInput)?;
5295 let mut copied = 0usize;
5296 for vaddr in pages {
5297 let (paddr, ..) = self.pt.query(vaddr).map_err(|_| StarryError::BadAddress)?;
5298 let page_offset = if vaddr == page_start {
5299 start.align_offset_4k()
5300 } else {
5301 0
5302 };
5303 let copy_size = (PAGE_SIZE_4K - page_offset).min(size - copied);
5304 if copy_size == 0 {
5305 break;
5306 }
5307 let paddr = paddr
5308 .checked_add(page_offset)
5309 .ok_or(StarryError::BadAddress)?;
5310 f(phys_to_virt(paddr), copied, copy_size);
5311 copied = copied
5312 .checked_add(copy_size)
5313 .ok_or(StarryError::InvalidInput)?;
5314 }
5315 (copied == size)
5316 .then_some(())
5317 .ok_or(StarryError::BadAddress)
5318 }
5319
5320 pub fn read(&self, start: VirtAddr, buf: &mut [u8]) -> StarryResult {
5321 self.process_area_data(start, buf.len(), |src, offset, read_size| unsafe {
5322 core::ptr::copy_nonoverlapping(src.as_ptr(), buf.as_mut_ptr().add(offset), read_size);
5323 })
5324 }
5325
5326 pub fn write(&self, start: VirtAddr, buf: &[u8]) -> StarryResult {
5333 self.process_area_data(start, buf.len(), |dst, offset, write_size| unsafe {
5334 core::ptr::copy_nonoverlapping(buf.as_ptr().add(offset), dst.as_mut_ptr(), write_size);
5335 })
5336 }
5337
5338 pub fn sync_modified_text(&self, start: VirtAddr, size: usize) -> StarryResult {
5340 if size == 0 {
5341 return Ok(());
5342 }
5343
5344 self.process_area_data(start, size, |dst, _offset, sync_size| {
5345 let range = ax_cpu::cache::CacheRange::new(dst, sync_size)
5346 .expect("mapped text chunk must not wrap");
5347 unsafe { ax_cpu::cache::clean_dcache_range_to_pou(range) };
5350 })?;
5351 ax_cpu::cache::flush_icache_all();
5352 Ok(())
5353 }
5354
5355 pub fn protect(&mut self, start: VirtAddr, size: usize, flags: MappingFlags) -> StarryResult {
5360 self.protect_with_reported_flags(start, size, flags, flags)
5361 }
5362
5363 pub fn protect_with_reported_flags(
5364 &mut self,
5365 start: VirtAddr,
5366 size: usize,
5367 flags: MappingFlags,
5368 reported_flags: MappingFlags,
5369 ) -> StarryResult {
5370 self.validate_region(start, size)?;
5371 let range = VirtAddrRange::from_start_size(start, size);
5372
5373 start.checked_add(size).ok_or(StarryError::InvalidInput)?;
5378 let mut denied = false;
5382 self.vma_root.for_each_overlapping_entry(range, |entry| {
5383 if entry.max_rights().contains(flags) {
5384 return true;
5385 }
5386 denied = true;
5387 false
5388 });
5389 if denied {
5390 return Err(StarryError::PermissionDenied);
5391 }
5392
5393 let vma_preimage = self.vma_root.clone();
5394 let vm_stat_preimage = self.vm_stat.snapshot();
5395 let before_vmas = self.vma_root.len();
5396 let mut mutation = self.prepare_mutation_range(start, size);
5397 mutation
5398 .try_reserve_tlb_ranges(2)
5399 .map_err(|_| StarryError::NoMemory)?;
5400 let splits = self.apply_partial_huge_splits(range)?;
5401 for split in &splits {
5402 let Some(tlb_range) =
5403 TlbRange::new(split.installed.block_vaddr(), split.installed.block_size())
5404 else {
5405 return self.abort_unpublished_protection(
5406 vma_preimage,
5407 vm_stat_preimage,
5408 &[],
5409 splits,
5410 StarryError::BadState,
5411 );
5412 };
5413 mutation.add_tlb_range(tlb_range);
5414 }
5415 let protection_preimage = match self.capture_protection_leaf_preimage(range) {
5416 Ok(preimage) => preimage,
5417 Err(error) => {
5418 return self.abort_unpublished_protection(
5419 vma_preimage,
5420 vm_stat_preimage,
5421 &[],
5422 splits,
5423 error,
5424 );
5425 }
5426 };
5427 let protected_leaves = protection_preimage.len();
5428 mutation.set_pte_delta(PteDelta {
5429 protected: u32::try_from(protected_leaves).unwrap_or(u32::MAX),
5430 ..PteDelta::default()
5431 });
5432 let split_slots = splits
5433 .iter()
5434 .map(|split| split.child_slots.len().saturating_sub(1))
5435 .sum::<usize>();
5436 mutation.set_mapping_delta(MappingDelta {
5437 attached: u32::try_from(split_slots).unwrap_or(u32::MAX),
5438 ..MappingDelta::default()
5439 });
5440 let touched_memfds =
5441 crate::syscall::memfd_collect_metas_touching_mprotect_range(self, start, size);
5442 if flags.contains(MappingFlags::EXECUTE) {
5443 for leaf in &protection_preimage {
5444 let key = MappingSlotKey {
5445 space_id: self.id,
5446 va: leaf.va,
5447 };
5448 let slot = self
5449 .mapping_slots
5450 .get(&key)
5451 .expect("published executable leaf must retain its mapping owner");
5452 slot.page.prepare_executable_mapping(
5453 leaf.paddr,
5454 leaf.page_size,
5455 flags | (leaf.flags & (MappingFlags::DEVICE | MappingFlags::UNCACHED)),
5456 );
5457 }
5458 }
5459 if let Err(error) = self.apply_protection_unpublished(range, flags, reported_flags) {
5460 return self.abort_unpublished_protection(
5461 vma_preimage,
5462 vm_stat_preimage,
5463 &protection_preimage,
5464 splits,
5465 error,
5466 );
5467 }
5468 let after_vmas = self.vma_root.len();
5469 mutation.set_vma_delta(VmaDelta {
5470 split: u32::try_from(after_vmas.saturating_sub(before_vmas)).unwrap_or(u32::MAX),
5471 merged: u32::try_from(before_vmas.saturating_sub(after_vmas)).unwrap_or(u32::MAX),
5472 ..VmaDelta::default()
5473 });
5474
5475 match self.commit_mutation_classified(mutation) {
5476 Ok(()) => {
5477 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
5478 self,
5479 &touched_memfds,
5480 );
5481 Ok(())
5482 }
5483 Err(CommitMutationError::PublishedPendingTlb(error)) => {
5484 crate::syscall::memfd_resync_shared_writable_counts_after_mprotect(
5488 self,
5489 &touched_memfds,
5490 );
5491 Err(error)
5492 }
5493 Err(CommitMutationError::Unpublished(error)) => self.abort_unpublished_protection(
5494 vma_preimage,
5495 vm_stat_preimage,
5496 &protection_preimage,
5497 splits,
5498 error,
5499 ),
5500 }
5501 }
5502
5503 fn ensure_quiescent_for_content_clear(&self) -> StarryResult {
5504 if self.tlb_targets.load(core::sync::atomic::Ordering::Acquire) != 0
5505 || self.mutation_gate.pending_count() != 0
5506 || self.pending_retired_mapping_batches() != 0
5507 {
5508 return Err(StarryError::ResourceBusy);
5509 }
5510 Ok(())
5511 }
5512
5513 fn clear_quiescent_contents(&mut self) -> StarryResult {
5518 self.ensure_quiescent_for_content_clear()?;
5519 let range = self.layout.range();
5520 let operations = self.mapping_operation_fragments(range, false)?;
5521 if operations
5522 .iter()
5523 .any(|(fragment, operation)| !operation.validate_unmap_range(*fragment, &self.pt))
5524 {
5525 return Err(StarryError::BadState);
5526 }
5527
5528 let deferred_tlb = DeferredTlbRetireGuard::enter();
5529 let clear_result = operations
5537 .into_iter()
5538 .try_for_each(|(fragment, operation)| operation.unmap_range(fragment, &mut self.pt));
5539 drop(deferred_tlb);
5540 if let Err(error) = clear_result {
5541 if let Err(flush_error) = crate::mm::flush_tlb_range_sync(range.start, range.size()) {
5542 warn!(
5543 "quiescent address-space clear could not invalidate {:?}+{:#x}: {flush_error}",
5544 range.start,
5545 range.size()
5546 );
5547 }
5548 self.mutation_gate.mark_needs_repair();
5549 return Err(error);
5550 }
5551 let slots = core::mem::take(&mut self.mapping_slots);
5552 for slot in slots.into_values() {
5553 slot.detach();
5554 }
5555 self.resident_watermark.reset();
5556 self.vm_stat.on_clear();
5557 self.vma_root = Arc::new(VmaMap::default());
5558 self.executable_file = None;
5559 self.mutation_gate.clear_repair();
5562 Ok(())
5563 }
5564
5565 pub(crate) fn reset_uninstalled_for_loader(&mut self) -> StarryResult {
5574 self.ensure_quiescent_for_content_clear()?;
5575 let range = self.layout.range();
5576 let memfd_deltas =
5577 crate::syscall::memfd_prepare_aspace_unmap_deltas(self, range.start, range.size());
5578 self.clear_quiescent_contents()?;
5579 self.resident_pages = ResidentPageCounts::default();
5580 self.heap = HeapState::new(USER_HEAP_BASE);
5581 self.executable_data = ExecutableDataLayout::default();
5582 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5583 Ok(())
5584 }
5585
5586 fn clear_retired_contents(&mut self) -> StarryResult {
5589 self.ensure_quiescent_for_content_clear()?;
5590 let base_epoch = self.vm_epoch();
5591 base_epoch.checked_next().ok_or(StarryError::BadState)?;
5592 let range = self.layout.range();
5593 let removed_vmas = self.vma_root.len();
5594 let detached_slots = self.mapping_slots.len();
5595 let materialized_pages = self
5596 .mapping_slots
5597 .values()
5598 .try_fold(0usize, |pages, slot| {
5599 pages.checked_add(1usize.checked_shl(slot.page_order.get().into())?)
5600 })
5601 .ok_or(StarryError::BadState)?;
5602 let memfd_deltas =
5603 crate::syscall::memfd_prepare_aspace_unmap_deltas(self, range.start, range.size());
5604 let mut mutation = self.prepare_mutation_range(range.start, range.size());
5605 mutation.set_vma_delta(VmaDelta {
5606 removed: u32::try_from(removed_vmas).unwrap_or(u32::MAX),
5607 ..VmaDelta::default()
5608 });
5609 mutation.set_pte_delta(PteDelta {
5610 unmapped: u32::try_from(materialized_pages).unwrap_or(u32::MAX),
5611 ..PteDelta::default()
5612 });
5613 mutation.set_mapping_delta(MappingDelta {
5614 detached: u32::try_from(detached_slots).unwrap_or(u32::MAX),
5615 ..MappingDelta::default()
5616 });
5617 mutation.set_resident_delta(self.resident_pages.checked_negated_delta()?);
5618 self.clear_quiescent_contents()?;
5619 let result = self.commit_mutation(mutation);
5620 if self.vm_epoch() != base_epoch {
5621 crate::syscall::memfd_apply_shared_writable_deltas(&memfd_deltas);
5622 }
5623 result
5624 }
5625
5626 pub(crate) fn try_reclaim_contents(&mut self) -> StarryResult {
5628 if self.tlb_targets.load(core::sync::atomic::Ordering::Acquire) != 0
5634 || self.mutation_gate.pending_count() != 0
5635 || self.pending_retired_mapping_batches() != 0
5636 {
5637 return Err(StarryError::ResourceBusy);
5638 }
5639 self.clear_retired_contents()?;
5640 let epoch = self.vm_epoch();
5641
5642 let targets = self.tlb_targets.load(core::sync::atomic::Ordering::Acquire);
5649 let request = TlbRequest::new(self.id, epoch, targets);
5650 debug_assert!(request.is_complete());
5651 unsafe {
5657 self.pt.detach(|token| token.reclaim());
5658 }
5659 Ok(())
5660 }
5661
5662 pub fn can_access_range(
5667 &self,
5668 start: VirtAddr,
5669 size: usize,
5670 access_flags: MappingFlags,
5671 ) -> bool {
5672 let Some(range) = VirtAddrRange::try_from_start_size(start, size) else {
5673 return false;
5674 };
5675 if range.is_empty() {
5676 return false;
5677 }
5678 let mut cursor = range.start;
5682 let mut permitted = false;
5683 self.vma_root.for_each_overlapping(range, |vma| {
5684 if vma.range.end <= cursor {
5685 return true;
5686 }
5687 if vma.range.start > cursor || !vma.rights.contains(access_flags) {
5688 return false;
5689 }
5690 cursor = vma.range.end.min(range.end);
5691 if cursor >= range.end {
5692 permitted = true;
5693 return false;
5694 }
5695 true
5696 });
5697 permitted
5698 }
5699
5700 fn fault_transaction_page_size(
5709 &self,
5710 vaddr: VirtAddr,
5711 vma_range: VirtAddrRange,
5712 policy_size: usize,
5713 ) -> StarryResult<usize> {
5714 match self.pt.query(vaddr) {
5715 Ok((_, _, leaf_size)) => return Ok(leaf_size),
5716 Err(PagingError::NotMapped) => {}
5717 Err(error) => return Err(error.into()),
5718 }
5719 if policy_size == PAGE_SIZE_4K {
5720 return Ok(PAGE_SIZE_4K);
5721 }
5722 if policy_size < PAGE_SIZE_4K
5723 || !policy_size.is_power_of_two()
5724 || !policy_size.is_multiple_of(PAGE_SIZE_4K)
5725 {
5726 return Err(StarryError::BadState);
5727 }
5728
5729 let policy_start = vaddr.align_down(policy_size);
5730 let policy_range = VirtAddrRange::try_from_start_size(policy_start, policy_size)
5731 .ok_or(StarryError::BadState)?;
5732 if !vma_range.contains_range(policy_range) {
5733 return Ok(PAGE_SIZE_4K);
5734 }
5735
5736 if self
5741 .pt
5742 .walk_occupied_range(policy_range.start, policy_range.end)
5743 .next()
5744 .is_some()
5745 {
5746 return Ok(PAGE_SIZE_4K);
5747 }
5748 Ok(policy_size)
5749 }
5750
5751 fn plan_page_fault(
5752 &self,
5753 vaddr: VirtAddr,
5754 access_flags: PageFaultFlags,
5755 thp_mode: TransparentHugePageMode,
5756 ) -> Result<PageFaultPlan, FaultResult> {
5757 if self.mutation_gate.needs_repair() {
5758 return Err(FaultResult::Sigbus(BusCode::ObjErr));
5761 }
5762 if !self.layout.range().contains(vaddr) {
5763 return Err(FaultResult::Unmapped);
5764 }
5765 let access_flags = MappingFlags::from(access_flags);
5766 let Some(entry) = self.vma_root.lookup_entry(vaddr) else {
5767 return Err(FaultResult::Unmapped);
5768 };
5769 let vma = entry.snapshot().clone();
5770 let flags = vma.rights;
5771 if !flags.contains(access_flags) {
5772 return Err(FaultResult::PermissionDenied);
5773 }
5774 let backend = entry.operation_clone();
5775 let Some(policy_size) = vma
5776 .group
5777 .page_policy
5778 .fault_leaf_size(vma.huge_page_advice, thp_mode)
5779 else {
5780 return Err(FaultResult::Unmapped);
5781 };
5782 let page_size = match self.fault_transaction_page_size(vaddr, vma.range, policy_size) {
5783 Ok(page_size) => page_size,
5784 Err(error) => {
5785 warn!("could not classify page-fault leaf for {vaddr:?}: {error}");
5786 return Err(FaultResult::Retry);
5787 }
5788 };
5789 let page_start = vaddr.align_down(page_size);
5790 let Some(range) = VirtAddrRange::try_from_start_size(page_start, page_size) else {
5791 return Err(FaultResult::Unmapped);
5792 };
5793 let fault_fallback = if vma.group.page_policy.permits_fault_fallback() {
5794 FaultFallback::BasePage
5795 } else {
5796 FaultFallback::Forbidden
5797 };
5798 let request = match PopulateRequest::fault(range, page_size, vaddr, fault_fallback) {
5799 Ok(request) => request,
5800 Err(_) => return Err(FaultResult::Unmapped),
5801 };
5802 let preimage = match FaultPteSnapshot::capture(&self.pt, page_start) {
5803 Ok(preimage) => preimage,
5804 Err(error) => {
5805 warn!("could not capture page-fault PTE at {page_start:?}: {error}");
5806 return Err(FaultResult::Retry);
5807 }
5808 };
5809 let map_plans = if preimage == FaultPteSnapshot::NotMapped {
5810 let preferred = match self.pt.plan_map_page(page_start, page_size) {
5811 Ok(plan) => plan,
5812 Err(error) => {
5813 warn!("could not plan page-table path for {page_start:?}: {error}");
5814 return Err(FaultResult::Retry);
5815 }
5816 };
5817 let fallback = if page_size > PAGE_SIZE_4K && fault_fallback == FaultFallback::BasePage
5818 {
5819 let fallback_start = vaddr.align_down_4k();
5820 match self.pt.plan_map_page(fallback_start, PAGE_SIZE_4K) {
5821 Ok(plan) => Some(plan),
5822 Err(error) => {
5823 warn!(
5824 "could not plan fallback page-table path for {fallback_start:?}: \
5825 {error}"
5826 );
5827 return Err(FaultResult::Retry);
5828 }
5829 }
5830 } else {
5831 None
5832 };
5833 Some(PageFaultMapPlans {
5834 preferred,
5835 fallback,
5836 })
5837 } else {
5838 None
5839 };
5840 Ok(PageFaultPlan {
5841 base_epoch: self.vm_epoch(),
5842 space_id: self.id,
5843 vaddr,
5844 range,
5845 vma_flags: flags,
5846 access_flags,
5847 operation: backend,
5848 request,
5849 preimage,
5850 map_plans,
5851 })
5852 }
5853
5854 fn classify_fault_error(file_backed: bool, error: StarryError) -> FaultResult {
5855 if matches!(
5856 error,
5857 StarryError::NoMemory
5858 | StarryError::Paging(PagingError::NoMemory)
5859 | StarryError::Vfs(axfs_ng_vfs::VfsError::NoMemory)
5860 ) {
5861 return FaultResult::NoMemory;
5862 }
5863 if matches!(error, StarryError::ResourceBusy) {
5864 return FaultResult::Retry;
5865 }
5866 if !file_backed {
5867 return FaultResult::Unmapped;
5868 }
5869 match error {
5870 StarryError::ResourceBusy | StarryError::Vfs(axfs_ng_vfs::VfsError::ResourceBusy) => {
5871 FaultResult::Retry
5872 }
5873 StarryError::BadAddress => FaultResult::Sigbus(BusCode::AdrErr),
5874 StarryError::Io | StarryError::Vfs(_) => FaultResult::Sigbus(BusCode::ObjErr),
5875 _ => FaultResult::Unmapped,
5876 }
5877 }
5878
5879 fn prepare_fault_materialization(
5880 plan: &PageFaultPlan,
5881 request: PopulateRequest,
5882 ) -> Result<FaultMaterialization, FaultResult> {
5883 match plan.operation.prepare_fault(
5884 plan.space_id,
5885 request,
5886 plan.vma_flags,
5887 plan.access_flags,
5888 plan.preimage,
5889 ) {
5890 Ok(materialization) => Ok(materialization),
5891 Err(error) => {
5892 warn!(
5893 "failed to prepare page fault for {:?} ({:?}): {error}",
5894 plan.vaddr, plan.vma_flags
5895 );
5896 Err(Self::classify_fault_error(
5897 plan.operation.is_file_backed(),
5898 error,
5899 ))
5900 }
5901 }
5902 }
5903
5904 fn cancel_fault_materialization(
5905 plan: &PageFaultPlan,
5906 materialization: FaultMaterialization,
5907 ) -> Result<(), FaultResult> {
5908 plan.operation
5909 .cancel_prepared_fault_publication(materialization)
5910 .map_err(|error| {
5911 warn!(
5912 "failed to cancel prepared page fault for {:?}: {error}",
5913 plan.vaddr
5914 );
5915 FaultResult::Retry
5916 })
5917 }
5918
5919 fn prepare_page_fault(mut plan: PageFaultPlan) -> Result<PreparedPageFault, FaultResult> {
5920 let mut materialization = Self::prepare_fault_materialization(&plan, plan.request)?;
5921 let installed_owner = materialization.owner().and_then(|owner| {
5922 (owner.transition == PteOwnerTransition::Installed).then_some((
5923 owner.va,
5924 owner.paddr,
5925 owner.page_size,
5926 ))
5927 });
5928 let map_deposit = if let Some((owner_va, owner_paddr, owner_page_size)) = installed_owner {
5929 let Some(plans) = plan.map_plans.take() else {
5930 Self::cancel_fault_materialization(&plan, materialization)?;
5931 return Err(FaultResult::Retry);
5932 };
5933 let PageFaultMapPlans {
5934 preferred,
5935 mut fallback,
5936 } = plans;
5937 let preferred_selected =
5938 preferred.vaddr() == owner_va && preferred.page_size() == owner_page_size;
5939 let fallback_selected = fallback.as_ref().is_some_and(|fallback| {
5940 fallback.vaddr() == owner_va && fallback.page_size() == owner_page_size
5941 });
5942 if !preferred_selected && !fallback_selected {
5943 Self::cancel_fault_materialization(&plan, materialization)?;
5944 return Err(FaultResult::Retry);
5945 }
5946 let Some(flags) = materialization.pte_flags() else {
5947 Self::cancel_fault_materialization(&plan, materialization)?;
5948 return Err(FaultResult::Retry);
5949 };
5950
5951 if fallback_selected {
5952 let Some(fallback_request) = plan.request.into_base_page_fallback() else {
5953 Self::cancel_fault_materialization(&plan, materialization)?;
5954 return Err(FaultResult::Retry);
5955 };
5956 plan.request = fallback_request;
5957 plan.range = fallback_request.range();
5958 let Some(fallback) = fallback.take() else {
5959 Self::cancel_fault_materialization(&plan, materialization)?;
5960 return Err(FaultResult::Retry);
5961 };
5962 match fallback.prepare(owner_paddr, flags) {
5963 Ok(deposit) => Some(deposit),
5964 Err(error) => {
5965 warn!(
5966 "could not prepare fallback page-table path for {owner_va:?}: {error}"
5967 );
5968 Self::cancel_fault_materialization(&plan, materialization)?;
5969 return Err(Self::classify_fault_error(false, error.into()));
5970 }
5971 }
5972 } else {
5973 match preferred.prepare(owner_paddr, flags) {
5974 Ok(deposit) => Some(deposit),
5975 Err(PagingError::NoMemory) if fallback.is_some() => {
5976 Self::cancel_fault_materialization(&plan, materialization)?;
5981 let Some(fallback_request) = plan.request.into_base_page_fallback() else {
5982 return Err(FaultResult::Retry);
5983 };
5984 plan.request = fallback_request;
5985 plan.range = fallback_request.range();
5986 materialization =
5987 Self::prepare_fault_materialization(&plan, fallback_request)?;
5988 let Some(owner) = materialization.owner() else {
5989 Self::cancel_fault_materialization(&plan, materialization)?;
5990 return Err(FaultResult::Retry);
5991 };
5992 let Some(fallback) = fallback.take() else {
5993 Self::cancel_fault_materialization(&plan, materialization)?;
5994 return Err(FaultResult::Retry);
5995 };
5996 if owner.transition != PteOwnerTransition::Installed
5997 || owner.va != fallback.vaddr()
5998 || owner.page_size != fallback.page_size()
5999 {
6000 Self::cancel_fault_materialization(&plan, materialization)?;
6001 return Err(FaultResult::Retry);
6002 }
6003 let Some(flags) = materialization.pte_flags() else {
6004 Self::cancel_fault_materialization(&plan, materialization)?;
6005 return Err(FaultResult::Retry);
6006 };
6007 match fallback.prepare(owner.paddr, flags) {
6008 Ok(deposit) => Some(deposit),
6009 Err(error) => {
6010 warn!(
6011 "could not prepare base-page table path for {:?}: {error}",
6012 owner.va
6013 );
6014 Self::cancel_fault_materialization(&plan, materialization)?;
6015 return Err(Self::classify_fault_error(false, error.into()));
6016 }
6017 }
6018 }
6019 Err(error) => {
6020 warn!("could not prepare page-table path for {owner_va:?}: {error}");
6021 Self::cancel_fault_materialization(&plan, materialization)?;
6022 return Err(Self::classify_fault_error(false, error.into()));
6023 }
6024 }
6025 }
6026 } else {
6027 None
6028 };
6029 Ok(PreparedPageFault {
6030 plan,
6031 materialization,
6032 map_deposit,
6033 })
6034 }
6035
6036 fn page_fault_plan_is_current(&self, plan: &PageFaultPlan) -> bool {
6037 if self.vm_epoch() != plan.base_epoch || !plan.preimage.matches(plan.range.start, &self.pt)
6038 {
6039 return false;
6040 }
6041 self.vma_root.lookup_entry(plan.vaddr).is_some_and(|entry| {
6042 entry.snapshot().rights == plan.vma_flags
6043 && entry.snapshot().range.contains_range(plan.range)
6044 && entry.operation().mapping_id() == plan.operation.mapping_id()
6045 })
6046 }
6047
6048 fn apply_prepared_page_fault(
6049 &mut self,
6050 attempt: &mut PageFaultApplyAttempt,
6051 ) -> PageFaultApplyOutcome {
6052 if !self.page_fault_plan_is_current(&attempt.prepared().plan) {
6053 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6054 }
6055
6056 let (pages, file_backed, vaddr, vma_flags, range, access_flags, fault_preimage) = {
6057 let prepared = attempt.prepared();
6058 (
6059 prepared.materialization.satisfied_pages(),
6060 prepared.plan.operation.is_file_backed(),
6061 prepared.plan.vaddr,
6062 prepared.plan.vma_flags,
6063 prepared.plan.range,
6064 prepared.plan.access_flags,
6065 prepared.plan.preimage,
6066 )
6067 };
6068 if pages == 0 {
6069 let result = if file_backed {
6070 FaultResult::Sigbus(BusCode::AdrErr)
6071 } else {
6072 warn!("no pages prepared for {vaddr:?} ({vma_flags:?})");
6073 FaultResult::Unmapped
6074 };
6075 return PageFaultApplyOutcome::Cancel(result);
6076 }
6077 if attempt.prepared().materialization.owner().is_none() {
6078 return PageFaultApplyOutcome::Cancel(FaultResult::Handled);
6079 }
6080 let (owner_va, owner_paddr, owner_page_size, owner_transition, desired_flags) = {
6081 let prepared = attempt.prepared();
6082 let owner = prepared
6083 .materialization
6084 .owner()
6085 .expect("checked fault owner must remain present");
6086 let Some(desired_flags) = prepared.materialization.pte_flags() else {
6087 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6088 };
6089 (
6090 owner.va,
6091 owner.paddr,
6092 owner.page_size,
6093 owner.transition,
6094 desired_flags,
6095 )
6096 };
6097 let mapping_preimage = match self.capture_mapping_preimage(range) {
6098 Ok(preimage) => preimage,
6099 Err(error) => {
6100 warn!("could not retain page-fault preimage for {vaddr:?}: {error}");
6101 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6102 }
6103 };
6104 let replaces_owner = attempt
6105 .prepared()
6106 .materialization
6107 .owner()
6108 .is_some_and(|owner| owner.transition == PteOwnerTransition::Replaced);
6109 let retired_owners = if replaces_owner {
6110 match self.prepare_retired_mapping_owners(range) {
6111 Ok(owners) => Some(owners),
6112 Err(error) => {
6113 warn!("could not reserve page-fault retire owners for {vaddr:?}: {error}");
6114 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6115 }
6116 }
6117 } else {
6118 None
6119 };
6120 let lazy_free_page = access_flags
6121 .contains(MappingFlags::WRITE)
6122 .then(|| {
6123 self.mapping_slots
6124 .get(&MappingSlotKey {
6125 space_id: self.id,
6126 va: owner_va,
6127 })
6128 .filter(|slot| slot.page.state() == PageState::LazyFree)
6129 .map(|slot| slot.page.clone())
6130 })
6131 .flatten();
6132 let fresh_install = matches!(fault_preimage, FaultPteSnapshot::NotMapped)
6133 && attempt
6134 .prepared()
6135 .materialization
6136 .owner()
6137 .is_some_and(|owner| owner.transition == PteOwnerTransition::Installed);
6138 let mut mutation = if fresh_install {
6139 self.prepare_fresh_pte_mutation_range(range.start, range.size())
6140 } else {
6141 self.prepare_mutation_range(range.start, range.size())
6142 };
6143 if let Some(request) = self.mutation_gate.pending_overlap_request(&mutation) {
6148 return PageFaultApplyOutcome::CancelPendingTlb {
6149 request,
6150 targets: self.tlb_targets(),
6151 };
6152 }
6153 let Some(retire_epoch) = mutation.receipt().base_epoch.checked_next() else {
6154 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6155 };
6156
6157 let mut map_deposit = if owner_transition == PteOwnerTransition::Installed {
6158 match attempt.take_map_deposit() {
6159 Some(deposit) => Some(deposit),
6160 None => return PageFaultApplyOutcome::Cancel(FaultResult::Retry),
6161 }
6162 } else {
6163 None
6164 };
6165 let apply_result = {
6166 let _structure = (owner_transition == PteOwnerTransition::Installed)
6167 .then(|| self.pte_domain.lock_structure());
6168 let _stripe = self.pte_domain.lock_range(range);
6169 let pt = &mut self.pt;
6170 let preimage_matches = fault_preimage.matches(range.start, pt);
6171 let result = if !preimage_matches {
6172 Err(PagingError::stale_map_deposit(owner_va))
6173 } else {
6174 let owner = attempt
6177 .prepared()
6178 .materialization
6179 .owner()
6180 .expect("prepared fault retains its page until PTE publication");
6181 owner
6182 .page
6183 .prepare_executable_mapping(owner_paddr, owner_page_size, desired_flags);
6184 match owner_transition {
6185 PteOwnerTransition::Installed => {
6186 let deposit = map_deposit
6187 .take()
6188 .expect("fresh page fault must retain its map deposit");
6189 match pt.try_map_page_with(deposit) {
6190 Ok(()) => Ok(owner_page_size),
6191 Err(failure) => {
6192 let (error, deposit) = failure.into_parts();
6193 map_deposit = Some(deposit);
6194 Err(error)
6195 }
6196 }
6197 }
6198 PteOwnerTransition::Replaced | PteOwnerTransition::Updated => {
6199 pt.remap_page(owner_va, owner_paddr, desired_flags)
6200 }
6201 }
6202 };
6203 result.and_then(|installed_size| {
6204 (installed_size == owner_page_size)
6205 .then_some(installed_size)
6206 .ok_or(PagingError::NotMapped)
6207 })
6208 };
6209 if let Some(deposit) = map_deposit.take() {
6210 attempt.restore_map_deposit(deposit);
6211 }
6212 if let Err(error) = apply_result {
6213 warn!("could not apply prepared page fault for {vaddr:?}: {error}");
6214 if fault_preimage.matches(range.start, &self.pt) {
6215 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6216 }
6217 if self
6218 .restore_mapping_preimage(range, mapping_preimage)
6219 .is_ok()
6220 {
6221 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6222 } else {
6223 self.mutation_gate.mark_needs_repair();
6224 }
6225 return PageFaultApplyOutcome::NeedsRepair(FaultResult::Retry);
6226 }
6227
6228 mutation.set_pte_delta(PteDelta {
6229 mapped: u32::try_from(pages).unwrap_or(u32::MAX),
6230 ..PteDelta::default()
6231 });
6232 let publication = match self.publish_prepared_fault_owner(
6233 &attempt.prepared().plan.operation,
6234 range,
6235 &attempt.prepared().materialization,
6236 ) {
6237 Ok(publication) => publication,
6238 Err(error) => {
6239 warn!("could not publish prepared page owner for {vaddr:?}: {error}");
6240 if self
6241 .restore_mapping_preimage(range, mapping_preimage)
6242 .is_err()
6243 {
6244 self.mutation_gate.mark_needs_repair();
6245 return PageFaultApplyOutcome::NeedsRepair(FaultResult::Retry);
6246 }
6247 return PageFaultApplyOutcome::Cancel(FaultResult::Retry);
6248 }
6249 };
6250 let PreparedPageFault {
6251 plan: _,
6252 materialization: _,
6253 map_deposit,
6254 } = attempt.take_prepared();
6255 debug_assert!(map_deposit.is_none());
6256 mutation.set_mapping_delta(publication.mapping_delta);
6257 mutation.set_resident_delta(publication.resident_delta);
6258 if let Some(page) = &lazy_free_page
6259 && !page.clear_lazy_free()
6260 {
6261 if self
6262 .restore_mapping_preimage(range, mapping_preimage)
6263 .is_err()
6264 {
6265 self.mutation_gate.mark_needs_repair();
6266 }
6267 return PageFaultApplyOutcome::Complete(FaultResult::Retry);
6268 }
6269 if let Some(owners) = retired_owners {
6270 self.park_retired_mapping_owners(retire_epoch, owners);
6271 }
6272 match self.publish_mutation_classified(mutation) {
6273 Ok(MutationPublication::Complete) => {
6274 self.release_retired_mapping_owners(retire_epoch);
6275 PageFaultApplyOutcome::Complete(FaultResult::Handled)
6276 }
6277 Ok(MutationPublication::PendingTlb) => {
6278 let Some(request) = self.mutation_gate.pending_request(self.id, retire_epoch)
6279 else {
6280 self.mutation_gate.mark_needs_repair();
6281 return PageFaultApplyOutcome::Complete(FaultResult::Retry);
6282 };
6283 PageFaultApplyOutcome::PendingTlb {
6284 request,
6285 targets: self.tlb_targets(),
6286 }
6287 }
6288 Err(CommitMutationError::Unpublished(error)) => {
6289 warn!("page-fault publication for {vaddr:?} failed before publish: {error}");
6290 if self
6291 .restore_mapping_preimage(range, mapping_preimage)
6292 .is_err()
6293 {
6294 self.mutation_gate.mark_needs_repair();
6295 } else {
6296 self.release_retired_mapping_owners(retire_epoch);
6297 if let Some(page) = &lazy_free_page
6298 && !page.mark_lazy_free()
6299 {
6300 self.mutation_gate.mark_needs_repair();
6301 }
6302 }
6303 PageFaultApplyOutcome::Complete(FaultResult::Retry)
6304 }
6305 Err(CommitMutationError::PublishedPendingTlb(error)) => {
6306 warn!("unexpected synchronous TLB result for page fault {vaddr:?}: {error}");
6307 let Some(request) = self.mutation_gate.pending_request(self.id, retire_epoch)
6308 else {
6309 self.mutation_gate.mark_needs_repair();
6310 return PageFaultApplyOutcome::Complete(FaultResult::Retry);
6311 };
6312 PageFaultApplyOutcome::PendingTlb {
6313 request,
6314 targets: self.tlb_targets(),
6315 }
6316 }
6317 }
6318 }
6319
6320 #[cfg(all(test, axtest))]
6323 fn handle_page_fault_result(
6324 &mut self,
6325 vaddr: VirtAddr,
6326 access_flags: PageFaultFlags,
6327 ) -> FaultResult {
6328 let plan =
6329 match self.plan_page_fault(vaddr, access_flags, TransparentHugePageMode::default()) {
6330 Ok(plan) => plan,
6331 Err(result) => return result,
6332 };
6333 let prepared = match Self::prepare_page_fault(plan) {
6334 Ok(prepared) => prepared,
6335 Err(result) => return result,
6336 };
6337 let mut attempt = prepared.into_apply_attempt();
6338 let result = match self.apply_prepared_page_fault(&mut attempt) {
6339 PageFaultApplyOutcome::Complete(result) => result,
6340 PageFaultApplyOutcome::Cancel(result) => {
6341 if attempt.cancel().is_ok() {
6342 result
6343 } else {
6344 FaultResult::Retry
6345 }
6346 }
6347 PageFaultApplyOutcome::NeedsRepair(result) => {
6348 attempt.release_to_repair_state();
6349 result
6350 }
6351 PageFaultApplyOutcome::CancelPendingTlb { request, targets } => {
6352 if attempt.cancel().is_ok()
6353 && Self::flush_tlb_requests(core::slice::from_ref(&request), &targets).is_ok()
6354 {
6355 let _ = self.acknowledge_tlb_requests(core::slice::from_ref(&request));
6356 }
6357 FaultResult::Retry
6358 }
6359 PageFaultApplyOutcome::PendingTlb { request, targets } => {
6360 if Self::flush_tlb_requests(core::slice::from_ref(&request), &targets).is_ok()
6361 && self
6362 .acknowledge_tlb_requests(core::slice::from_ref(&request))
6363 .is_ok()
6364 {
6365 FaultResult::Handled
6366 } else {
6367 FaultResult::Retry
6368 }
6369 }
6370 };
6371 complete_page_fault_with(
6372 matches!(result, FaultResult::Handled),
6373 vaddr,
6374 ax_cpu::mmu::update_mmu_cache,
6375 );
6376 result
6377 }
6378
6379 fn prepare_fork_parent_mutation(&self) -> StarryResult<Option<PreparedForkParentMutation>> {
6385 let mut mutation = self.prepare_mutation();
6386 let mut ptes = Vec::new();
6387 let mut ranges = Vec::new();
6388
6389 for entry in self.vma_root.iter_entries() {
6390 if entry.snapshot().advice_policy.dont_fork() {
6391 continue;
6392 }
6393 if !entry.operation().requires_fork_write_protect() {
6394 continue;
6395 }
6396 let mut range_changed = false;
6397 for leaf in self.occupied_pte_leaves_overlapping(&[entry.range()])? {
6398 let page_size = leaf.range.size();
6399 if page_size < PAGE_SIZE_4K || !page_size.is_power_of_two() {
6400 return Err(StarryError::BadState);
6401 }
6402 let protected_flags = leaf.flags - MappingFlags::WRITE;
6403 if protected_flags != leaf.flags {
6404 ptes.try_reserve(1).map_err(|_| StarryError::NoMemory)?;
6405 ptes.push(ForkParentPteProtection {
6406 va: leaf.range.start,
6407 paddr: leaf.paddr,
6408 page_size,
6409 original_flags: leaf.flags,
6410 protected_flags,
6411 });
6412 range_changed = true;
6413 }
6414 }
6415 if range_changed {
6416 ranges.try_reserve(1).map_err(|_| StarryError::NoMemory)?;
6417 ranges.push(entry.range());
6418 mutation
6419 .try_add_tlb_range(
6420 TlbRange::new(entry.start(), entry.size())
6421 .ok_or(StarryError::InvalidInput)?,
6422 )
6423 .map_err(|error| match error {
6424 MutationError::ResourceExhausted => StarryError::NoMemory,
6425 _ => StarryError::BadState,
6426 })?;
6427 }
6428 }
6429
6430 if ptes.is_empty() {
6431 return Ok(None);
6432 }
6433 mutation.set_pte_delta(PteDelta {
6434 protected: u32::try_from(ptes.len()).unwrap_or(u32::MAX),
6435 ..PteDelta::default()
6436 });
6437 Ok(Some(PreparedForkParentMutation {
6438 mutation,
6439 ptes,
6440 ranges,
6441 }))
6442 }
6443
6444 fn rollback_fork_parent_ptes(
6445 cursor: &mut PageTable,
6446 applied: &[ForkParentPteProtection],
6447 ) -> bool {
6448 let mut complete = true;
6449 for protection in applied.iter().rev() {
6450 let current_matches =
6451 cursor
6452 .query(protection.va)
6453 .is_ok_and(|(paddr, flags, page_size)| {
6454 paddr == protection.paddr
6455 && flags == protection.protected_flags
6456 && page_size == protection.page_size
6457 });
6458 if !current_matches
6459 || cursor
6460 .protect_page(protection.va, protection.original_flags)
6461 .is_err()
6462 {
6463 complete = false;
6464 }
6465 }
6466 complete
6467 }
6468
6469 fn apply_fork_parent_mutation(&mut self, prepared: PreparedForkParentMutation) -> StarryResult {
6472 let PreparedForkParentMutation {
6473 mutation,
6474 ptes,
6475 ranges,
6476 } = prepared;
6477 let pt = &mut self.pt;
6478 let pte_stripes = self.pte_domain.lock_ranges(&ranges);
6479 let cursor = pt;
6483 for (applied, protection) in ptes.iter().enumerate() {
6484 let preimage_matches =
6485 cursor
6486 .query(protection.va)
6487 .is_ok_and(|(paddr, flags, page_size)| {
6488 paddr == protection.paddr
6489 && flags == protection.original_flags
6490 && page_size == protection.page_size
6491 });
6492 if !preimage_matches
6493 || cursor
6494 .protect_page(protection.va, protection.protected_flags)
6495 .is_err()
6496 {
6497 if !Self::rollback_fork_parent_ptes(cursor, &ptes[..applied]) {
6498 self.mutation_gate.mark_needs_repair();
6499 return Err(StarryError::BadState);
6500 }
6501 return Err(StarryError::BadState);
6502 }
6503 }
6504 drop(pte_stripes);
6505
6506 match self.commit_mutation_classified(mutation) {
6512 Ok(()) => Ok(()),
6513 Err(CommitMutationError::PublishedPendingTlb(error)) => Err(error),
6514 Err(CommitMutationError::Unpublished(error)) => {
6515 let pt = &mut self.pt;
6516 let _pte_stripes = self.pte_domain.lock_ranges(&ranges);
6517 let restored = Self::rollback_fork_parent_ptes(pt, &ptes);
6521 if restored {
6522 self.mutation_gate.clear_repair();
6523 Err(error)
6524 } else {
6525 self.mutation_gate.mark_needs_repair();
6526 Err(StarryError::BadState)
6527 }
6528 }
6529 }
6530 }
6531
6532 fn abort_unpublished_clone(child: &mut Self) -> StarryResult {
6533 match child.reset_uninstalled_for_loader() {
6534 Ok(()) => Ok(()),
6535 Err(error)
6536 if child.vma_root.is_empty()
6537 && child.mapping_slots.is_empty()
6538 && child.pending_retired_mapping_batches() == 0 =>
6539 {
6540 warn!(
6545 "unpublished fork child cleared all mappings but could not publish cleanup \
6546 epoch: {error}"
6547 );
6548 Ok(())
6549 }
6550 Err(error) => Err(error),
6551 }
6552 }
6553
6554 pub fn try_clone(&mut self) -> StarryResult<Arc<Mutex<Self>>> {
6564 let parent_mutation = self.prepare_fork_parent_mutation()?;
6568 let new_aspace = Arc::new(Mutex::new(Self::new_with_layout(self.layout)?));
6569
6570 let mut guard = new_aspace.lock_nested(CLONED_ADDR_SPACE_LOCK_SUBCLASS);
6574 guard.heap = self.heap;
6575 guard.executable_data = self.executable_data;
6576 guard.executable_file = self.executable_file.clone();
6577 let mut child_memfd_deltas = Vec::new();
6578 let mut child_vss_pages = 0u64;
6579
6580 let child_preparation = (|| -> StarryResult {
6581 let self_modify = &mut self.pt;
6582 for entry in self.vma_root.iter_entries() {
6583 if entry.snapshot().advice_policy.dont_fork() {
6584 continue;
6585 }
6586 let (new_backend, materialization) = entry.operation().clone_map(
6587 entry.range(),
6588 entry.rights(),
6589 self_modify,
6590 &mut guard.pt,
6591 )?;
6592 let start = entry.start();
6593 child_memfd_deltas.extend(crate::syscall::memfd_prepare_aspace_replace_deltas(
6594 &guard,
6595 start,
6596 entry.size(),
6597 entry.rights(),
6598 &new_backend,
6599 ));
6600
6601 let child_entry = guard
6602 .vma_root
6603 .prepare_mapping_entry(
6604 entry.range(),
6605 entry.rights(),
6606 entry.reported_rights(),
6607 entry.max_rights(),
6608 entry.snapshot().huge_page_advice,
6609 VmaLockMode::Unlocked,
6610 entry.snapshot().advice_policy,
6611 new_backend.clone(),
6612 )
6613 .ok_or(StarryError::BadState)?;
6614 let child_root = guard
6615 .vma_root
6616 .with_mapping_entry(child_entry, false)
6617 .ok_or(StarryError::BadState)?;
6618 guard.vma_root = Arc::new(child_root);
6619 guard.publish_prepared_pte_owners(&new_backend, entry.range(), &materialization)?;
6620 child_vss_pages = child_vss_pages
6621 .checked_add((entry.size() / PAGE_SIZE_4K) as u64)
6622 .ok_or(StarryError::BadState)?;
6623 }
6624
6625 guard.vm_stat.seed_clone(child_vss_pages);
6628 Ok(())
6629 })();
6630
6631 if let Err(error) = child_preparation {
6632 if let Err(cleanup_error) = Self::abort_unpublished_clone(&mut guard) {
6633 warn!(
6634 "fork child preparation failed ({error}); unpublished cleanup also failed \
6635 ({cleanup_error})"
6636 );
6637 return Err(StarryError::BadState);
6638 }
6639 return Err(error);
6640 }
6641
6642 if let Some(parent_mutation) = parent_mutation
6646 && let Err(error) = self.apply_fork_parent_mutation(parent_mutation)
6647 {
6648 if let Err(cleanup_error) = Self::abort_unpublished_clone(&mut guard) {
6649 warn!(
6650 "fork parent publication failed ({error}); unpublished child cleanup also \
6651 failed ({cleanup_error})"
6652 );
6653 return Err(StarryError::BadState);
6654 }
6655 return Err(error);
6656 }
6657
6658 if !guard.vma_root.is_empty() {
6662 let mut child_mutation = guard.prepare_mutation();
6663 child_mutation.set_vma_delta(VmaDelta {
6664 inserted: u32::try_from(guard.vma_root.len()).unwrap_or(u32::MAX),
6665 ..VmaDelta::default()
6666 });
6667 child_mutation.set_pte_delta(PteDelta {
6668 mapped: u32::try_from(guard.mapping_slots.len()).unwrap_or(u32::MAX),
6669 ..PteDelta::default()
6670 });
6671 child_mutation.set_mapping_delta(MappingDelta {
6672 attached: u32::try_from(guard.mapping_slots.len()).unwrap_or(u32::MAX),
6673 ..MappingDelta::default()
6674 });
6675 child_mutation.set_resident_delta(
6676 guard
6677 .resident_counts_from_all_slots()?
6678 .checked_positive_delta()?,
6679 );
6680 if let Err(error) = guard.commit_mutation(child_mutation) {
6681 if let Err(cleanup_error) = Self::abort_unpublished_clone(&mut guard) {
6682 warn!(
6683 "fork child publication failed ({error}); unpublished cleanup also failed \
6684 ({cleanup_error})"
6685 );
6686 return Err(StarryError::BadState);
6687 }
6688 return Err(error);
6689 }
6690 }
6691 crate::syscall::memfd_apply_shared_writable_deltas(&child_memfd_deltas);
6692 drop(guard);
6693
6694 Ok(new_aspace)
6695 }
6696}
6697
6698#[cfg(all(test, not(axtest)))]
6699fn page_fault_completion_updates_only_success_for_test() -> bool {
6700 use core::cell::Cell;
6701
6702 let calls = Cell::new(0);
6703 let observed = Cell::new(VirtAddr::from(0));
6704 let success = complete_page_fault_with(true, VirtAddr::from(0x4567), |vaddr| {
6705 calls.set(calls.get() + 1);
6706 observed.set(vaddr);
6707 });
6708 let rejected = complete_page_fault_with(false, VirtAddr::from(0x89ab), |_| {
6709 calls.set(calls.get() + 1);
6710 });
6711
6712 success && !rejected && calls.get() == 1 && observed.get() == VirtAddr::from(0x4567)
6713}
6714
6715impl fmt::Debug for AddrSpace {
6716 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
6717 f.debug_struct("AddrSpace")
6718 .field("id", &self.id)
6719 .field("layout", &self.layout)
6720 .field("page_table_root", &self.pt.root_paddr())
6721 .field("vma_root", &self.vma_root)
6722 .field("vm_epoch", &self.vm_epoch())
6723 .finish()
6724 }
6725}
6726
6727impl Drop for AddrSpace {
6728 fn drop(&mut self) {
6729 let has_retired_batches = self.pending_retired_mapping_batches() != 0;
6737 if !self.vma_root.is_empty() || !self.mapping_slots.is_empty() || has_retired_batches {
6738 warn!(
6739 "address space {} dropped before retire/reclaim; mappings intentionally retained",
6740 self.id.get()
6741 );
6742 self.pt.leak();
6748 if has_retired_batches {
6749 let batches = core::mem::take(&mut *self.retired_mapping_batches.lock());
6750 for batch in batches {
6751 core::mem::forget(batch);
6752 }
6753 }
6754 }
6755 }
6756}
6757
6758#[cfg(test)]
6759mod tests {
6760 use alloc::sync::Arc;
6761 use core::sync::atomic::AtomicUsize;
6762
6763 use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr};
6764
6765 use super::{
6766 AddressSpaceId, MutationError, MutationGate, TlbRange, VmEpoch,
6767 prepare_mapping_publication_mutation,
6768 };
6769
6770 #[cfg(all(test, not(axtest)))]
6771 #[test]
6772 fn page_fault_completion_updates_only_success() {
6773 assert!(super::page_fault_completion_updates_only_success_for_test());
6774 }
6775
6776 #[cfg_attr(axtest, axtest::axtest)]
6777 #[cfg_attr(not(axtest), test)]
6778 fn fresh_mapping_publication_has_no_tlb_targets() {
6779 let gate = MutationGate::new();
6780 let id = AddressSpaceId::allocate();
6781 let targets = Arc::new(AtomicUsize::new(0b1110));
6782 let start = VirtAddr::from(0x20_0000);
6783
6784 let fresh =
6785 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, false);
6786 assert_eq!(fresh.receipt().tlb_obligation.targets(), 0);
6787
6788 let replacement =
6789 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, true);
6790 assert_eq!(replacement.receipt().tlb_obligation.targets(), 0b1110);
6791 }
6792
6793 #[cfg_attr(axtest, axtest::axtest)]
6794 #[cfg_attr(not(axtest), test)]
6795 fn fresh_mapping_cannot_reuse_range_with_pending_shootdown() {
6796 let gate = MutationGate::new();
6797 let id = AddressSpaceId::allocate();
6798 let targets = Arc::new(AtomicUsize::new(0b1));
6799 let start = VirtAddr::from(0x20_0000);
6800 let mut unmap = gate.begin(id, 0b1);
6801 unmap.add_tlb_range(TlbRange::new(start, PAGE_SIZE_4K).unwrap());
6802 assert_eq!(gate.commit(unmap).unwrap_err(), MutationError::TlbPending);
6803
6804 let nonoverlapping = prepare_mapping_publication_mutation(
6805 &gate,
6806 id,
6807 &targets,
6808 start + PAGE_SIZE_4K * 2,
6809 PAGE_SIZE_4K,
6810 false,
6811 );
6812 gate.validate_publish_preconditions(&nonoverlapping)
6813 .unwrap();
6814 gate.commit(nonoverlapping).unwrap();
6815
6816 let fresh =
6817 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, false);
6818 assert_eq!(
6819 gate.validate_publish_preconditions(&fresh),
6820 Err(MutationError::PendingTlbOverlap)
6821 );
6822 assert_eq!(
6823 gate.commit(fresh).unwrap_err(),
6824 MutationError::PendingTlbOverlap
6825 );
6826 assert_eq!(gate.current_epoch(), VmEpoch::new(2));
6827
6828 gate.acknowledge(id, VmEpoch::new(1), 0).unwrap().unwrap();
6829 let retry =
6830 prepare_mapping_publication_mutation(&gate, id, &targets, start, PAGE_SIZE_4K, false);
6831 gate.validate_publish_preconditions(&retry).unwrap();
6832 gate.commit(retry).unwrap();
6833 }
6834
6835 #[cfg_attr(axtest, axtest::axtest)]
6836 #[cfg_attr(not(axtest), test)]
6837 fn pending_full_flush_blocks_every_fresh_mapping_range() {
6838 let gate = MutationGate::new();
6839 let id = AddressSpaceId::allocate();
6840 let targets = Arc::new(AtomicUsize::new(0b1));
6841 assert_eq!(
6842 gate.commit(gate.begin(id, 0b1)).unwrap_err(),
6843 MutationError::TlbPending
6844 );
6845
6846 let fresh = prepare_mapping_publication_mutation(
6847 &gate,
6848 id,
6849 &targets,
6850 VirtAddr::from(0x40_0000),
6851 PAGE_SIZE_4K,
6852 false,
6853 );
6854 assert_eq!(
6855 gate.commit(fresh).unwrap_err(),
6856 MutationError::PendingTlbOverlap
6857 );
6858 assert_eq!(gate.current_epoch(), VmEpoch::new(1));
6859 }
6860
6861 #[cfg(axtest)]
6862 fn refault_waits_for_discard_shootdown(full_flush: bool) {
6863 use ax_runtime::hal::trap::PageFaultFlags;
6864
6865 use super::{AddrSpace, FaultResult, MappingFlags, MappingOperation, PagingError};
6866
6867 let start = VirtAddr::from(0x7200_0000);
6868 let mut aspace = AddrSpace::new_empty(start, PAGE_SIZE_4K).unwrap();
6869 let flags = MappingFlags::READ | MappingFlags::WRITE | MappingFlags::USER;
6870 aspace
6871 .map(
6872 start,
6873 PAGE_SIZE_4K,
6874 flags,
6875 true,
6876 MappingOperation::new_alloc(start, PAGE_SIZE_4K, "[discard-refault]"),
6877 )
6878 .unwrap();
6879 aspace.discard_range(start, PAGE_SIZE_4K).unwrap();
6880 let mut discard = aspace.mutation_gate.begin(aspace.id, 1);
6883 if !full_flush {
6884 discard.add_tlb_range(TlbRange::new(start, PAGE_SIZE_4K).unwrap());
6885 }
6886 assert_eq!(
6887 aspace.mutation_gate.commit(discard).unwrap_err(),
6888 MutationError::TlbPending
6889 );
6890 let epoch = aspace.vm_epoch();
6891 let plan = aspace
6892 .plan_page_fault(
6893 start,
6894 PageFaultFlags::READ | PageFaultFlags::USER,
6895 Default::default(),
6896 )
6897 .ok()
6898 .unwrap();
6899 let prepared = AddrSpace::prepare_page_fault(plan).ok().unwrap();
6900 let mut attempt = prepared.into_apply_attempt();
6901 let outcome = aspace.apply_prepared_page_fault(&mut attempt);
6902 let unpublished = matches!(aspace.pt.query(start), Err(PagingError::NotMapped))
6903 && aspace.vm_epoch() == epoch
6904 && aspace.mapping_slots.is_empty()
6905 && attempt.prepared.is_some()
6906 && !aspace.mutation_gate.needs_repair();
6907 drop(outcome);
6908 if attempt.prepared.is_some() {
6909 attempt.cancel().unwrap();
6910 }
6911 aspace
6912 .mutation_gate
6913 .acknowledge(aspace.id, epoch, 0)
6914 .unwrap()
6915 .unwrap();
6916 let retry =
6917 aspace.handle_page_fault_result(start, PageFaultFlags::READ | PageFaultFlags::USER);
6918 let recovered = matches!(retry, FaultResult::Handled) && aspace.pt.query(start).is_ok();
6919 aspace.reset_uninstalled_for_loader().unwrap();
6920 assert!(
6921 unpublished,
6922 "refault must leave the PTE, epoch and owner graph untouched until discard is \
6923 acknowledged"
6924 );
6925 assert!(
6926 recovered,
6927 "acknowledged discard must allow refault to make progress"
6928 );
6929 }
6930
6931 #[cfg(axtest)]
6932 #[axtest::axtest]
6933 fn refault_waits_for_pending_discard_range() {
6934 refault_waits_for_discard_shootdown(false);
6935 }
6936
6937 #[cfg(axtest)]
6938 #[axtest::axtest]
6939 fn refault_waits_for_pending_discard_full_flush() {
6940 refault_waits_for_discard_shootdown(true);
6941 }
6942}