1use alloc::{sync::Arc, vec::Vec};
4use core::{
5 any::Any,
6 fmt,
7 hash::{Hash, Hasher},
8 sync::atomic::{AtomicBool, AtomicU8, Ordering},
9};
10
11use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, PhysAddr, VirtAddr, VirtAddrRange};
12use ax_runtime::hal::paging::HugeSplitDeposit;
13
14use super::{AddressSpaceId, MappingId, PageOrder, RssKind};
15use crate::sync::IrqMutex;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct PageId(u64);
19
20impl PageId {
21 pub fn allocate() -> Self {
22 static NEXT_ID: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(1);
23 Self(NEXT_ID.fetch_add(1, Ordering::Relaxed))
24 }
25
26 pub const fn new(value: u64) -> Self {
27 Self(value)
28 }
29
30 pub const fn get(self) -> u64 {
31 self.0
32 }
33}
34
35struct FrameAllocation {
41 base: PhysAddr,
42 bytes: usize,
43 release_align: Option<usize>,
47 release: fn(PhysAddr, usize),
52 _anchor: Option<Arc<dyn Any + Send + Sync>>,
56}
57
58impl Drop for FrameAllocation {
59 fn drop(&mut self) {
60 if let Some(align) = self.release_align.take() {
61 (self.release)(self.base, align);
62 }
63 }
64}
65
66#[derive(Clone)]
70pub struct FrameLease {
71 paddr: PhysAddr,
72 bytes: usize,
73 allocation: Arc<FrameAllocation>,
74}
75
76impl fmt::Debug for FrameLease {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 formatter
79 .debug_struct("FrameLease")
80 .field("paddr", &self.paddr)
81 .field("bytes", &self.bytes)
82 .field("owned", &self.allocation.release_align.is_some())
83 .field("anchored", &self.allocation._anchor.is_some())
84 .finish()
85 }
86}
87
88fn release_data_frame(paddr: PhysAddr, align: usize) {
89 super::backend::dealloc_frame(paddr, align);
90}
91
92impl FrameLease {
93 pub fn new(paddr: PhysAddr) -> Self {
94 Self {
95 paddr,
96 bytes: PAGE_SIZE_4K,
97 allocation: Arc::new(FrameAllocation {
98 base: paddr,
99 bytes: PAGE_SIZE_4K,
100 release_align: None,
101 release: release_data_frame,
102 _anchor: None,
103 }),
104 }
105 }
106
107 pub fn borrowed(
110 paddr: PhysAddr,
111 bytes: usize,
112 anchor: Option<Arc<dyn Any + Send + Sync>>,
113 ) -> Option<Self> {
114 if bytes == 0 {
115 return None;
116 }
117 Some(Self {
118 paddr,
119 bytes,
120 allocation: Arc::new(FrameAllocation {
121 base: paddr,
122 bytes,
123 release_align: None,
124 release: release_data_frame,
125 _anchor: anchor,
126 }),
127 })
128 }
129
130 pub unsafe fn owned(paddr: PhysAddr, align: usize) -> Self {
146 Self {
147 paddr,
148 bytes: align,
149 allocation: Arc::new(FrameAllocation {
150 base: paddr,
151 bytes: align,
152 release_align: Some(align),
153 release: release_data_frame,
154 _anchor: None,
155 }),
156 }
157 }
158
159 pub fn owned_with_releaser(
164 paddr: PhysAddr,
165 align: usize,
166 release: fn(PhysAddr, usize),
167 ) -> Self {
168 Self {
169 paddr,
170 bytes: align,
171 allocation: Arc::new(FrameAllocation {
172 base: paddr,
173 bytes: align,
174 release_align: Some(align),
175 release,
176 _anchor: None,
177 }),
178 }
179 }
180
181 pub const fn paddr(&self) -> PhysAddr {
182 self.paddr
183 }
184
185 pub const fn size(&self) -> usize {
186 self.bytes
187 }
188
189 pub fn sublease(&self, offset: usize, bytes: usize) -> Option<Self> {
193 if bytes == 0 {
194 return None;
195 }
196 let end = offset.checked_add(bytes)?;
197 if end > self.bytes {
198 return None;
199 }
200 let paddr = self.paddr.as_usize().checked_add(offset)?;
201 let allocation_offset = paddr.checked_sub(self.allocation.base.as_usize())?;
202 if allocation_offset.checked_add(bytes)? > self.allocation.bytes {
203 return None;
204 }
205 Some(Self {
206 paddr: PhysAddr::from_usize(paddr),
207 bytes,
208 allocation: self.allocation.clone(),
209 })
210 }
211}
212
213#[repr(u8)]
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum PageState {
216 Reserved = 0,
217 Present = 1,
218 LazyFree = 2,
220 Evicting = 3,
221 Writeback = 4,
222 Retired = 5,
223}
224
225impl PageState {
226 fn from_u8(value: u8) -> Self {
227 match value {
228 0 => Self::Reserved,
229 1 => Self::Present,
230 2 => Self::LazyFree,
231 3 => Self::Evicting,
232 4 => Self::Writeback,
233 _ => Self::Retired,
234 }
235 }
236}
237
238#[derive(Debug, Default)]
241pub struct RmapSet {
242 entries: IrqMutex<RmapEntries>,
243}
244
245#[derive(Debug, Default)]
246struct RmapEntries {
247 keys: Vec<MappingSlotKey>,
248 reserved: usize,
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
253pub struct MappingSlotKey {
254 pub space_id: AddressSpaceId,
255 pub va: VirtAddr,
256}
257
258pub(crate) struct MappingGraphReservation<'a> {
265 owner: &'a RmapSet,
266 additional: usize,
267 displaced: Vec<MappingSlotKey>,
268}
269
270impl Drop for MappingGraphReservation<'_> {
271 fn drop(&mut self) {
272 if self.additional != 0 {
273 let mut entries = self.owner.entries.lock();
274 entries.reserved -= self.additional;
275 }
276 drop(core::mem::take(&mut self.displaced));
278 }
279}
280
281impl Hash for MappingSlotKey {
282 fn hash<H: Hasher>(&self, state: &mut H) {
283 self.space_id.hash(state);
284 self.va.as_usize().hash(state);
285 }
286}
287
288impl RmapSet {
289 fn prepare_replace(
290 &self,
291 additional: usize,
292 ) -> Result<MappingGraphReservation<'_>, MappingGraphError> {
293 let mut replacement = Vec::new();
294 loop {
295 let mut entries = self.entries.lock();
296 let reserved = entries
297 .reserved
298 .checked_add(additional)
299 .ok_or(MappingGraphError::ResourceExhausted)?;
300 let required = entries
301 .keys
302 .len()
303 .checked_add(reserved)
304 .ok_or(MappingGraphError::ResourceExhausted)?;
305 if entries.keys.capacity() < required {
306 if replacement.capacity() < required {
307 drop(entries);
308 replacement
309 .try_reserve_exact(required)
310 .map_err(|_| MappingGraphError::ResourceExhausted)?;
311 continue;
312 }
313 replacement.extend_from_slice(&entries.keys);
314 core::mem::swap(&mut entries.keys, &mut replacement);
315 }
316 entries.reserved = reserved;
317 return Ok(MappingGraphReservation {
318 owner: self,
319 additional,
320 displaced: replacement,
321 });
322 }
323 }
324
325 fn replace_reserved(
328 &self,
329 old: &[MappingSlotKey],
330 new: &[MappingSlotKey],
331 reservation: &mut MappingGraphReservation<'_>,
332 ) -> Result<(), MappingGraphError> {
333 if !core::ptr::eq(reservation.owner, self) {
334 return Err(MappingGraphError::SlotIdentityMismatch);
335 }
336 if new.len().saturating_sub(old.len()) > reservation.additional {
337 return Err(MappingGraphError::SlotStateConflict);
338 }
339 let mut entries = self.entries.lock();
340 for (index, key) in old.iter().enumerate() {
341 if old[..index].contains(key) || !entries.keys.contains(key) {
342 return Err(MappingGraphError::MissingOldSlot);
343 }
344 }
345 for (index, key) in new.iter().enumerate() {
346 if new[..index].contains(key)
347 || (entries.keys.contains(key) && !old.contains(key))
348 {
349 return Err(MappingGraphError::DuplicateNewSlot);
350 }
351 }
352 for key in old {
353 let index = entries
354 .keys
355 .iter()
356 .position(|entry| entry == key)
357 .ok_or(MappingGraphError::MissingOldSlot)?;
358 entries.keys.swap_remove(index);
359 }
360 entries.keys.extend_from_slice(new);
361 entries.reserved -= reservation.additional;
362 reservation.additional = 0;
363 Ok(())
364 }
365
366 pub fn try_snapshot(&self) -> Result<Vec<MappingSlotKey>, MappingGraphError> {
367 let mut snapshot = Vec::new();
368 loop {
369 let required = self.entries.lock().keys.len();
370 if snapshot.capacity() < required {
371 snapshot
372 .try_reserve_exact(required.saturating_sub(snapshot.len()))
373 .map_err(|_| MappingGraphError::ResourceExhausted)?;
374 }
375 let entries = self.entries.lock();
376 if entries.keys.len() > snapshot.capacity() {
377 drop(entries);
378 continue;
379 }
380 snapshot.clear();
381 snapshot.extend_from_slice(&entries.keys);
382 return Ok(snapshot);
383 }
384 }
385
386 #[cfg(test)]
387 pub fn snapshot(&self) -> Vec<MappingSlotKey> {
388 self.try_snapshot()
389 .expect("test reverse-mapping snapshot allocation must succeed")
390 }
391
392 fn all_mappings_belong_to(&self, mm_id: AddressSpaceId, expected: u32) -> bool {
393 let entries = self.entries.lock();
394 usize::try_from(expected).is_ok_and(|expected| entries.keys.len() == expected)
395 && entries.keys.iter().all(|entry| entry.space_id == mm_id)
396 }
397
398 pub fn is_empty(&self) -> bool {
399 self.entries.lock().keys.is_empty()
400 }
401}
402
403pub struct PageObject {
404 pub id: PageId,
405 frame: FrameLease,
406 state: AtomicU8,
407 resident_kind: AtomicU8,
412 mapping_refs: core::sync::atomic::AtomicU32,
416 writeback_generation: core::sync::atomic::AtomicU64,
417 eviction_tlb_ready: AtomicBool,
421 mapping_graph: IrqMutex<()>,
426 pub rmap: RmapSet,
427}
428
429impl PageObject {
430 pub fn new(id: PageId, frame: FrameLease) -> Arc<Self> {
431 Self::new_with_resident_kind(id, frame, None)
432 }
433
434 pub fn new_with_resident_kind(
435 id: PageId,
436 frame: FrameLease,
437 resident_kind: Option<RssKind>,
438 ) -> Arc<Self> {
439 Arc::new(Self {
440 id,
441 frame,
442 state: AtomicU8::new(PageState::Reserved as u8),
443 resident_kind: AtomicU8::new(RssKind::slot_value(resident_kind)),
444 mapping_refs: core::sync::atomic::AtomicU32::new(0),
445 writeback_generation: core::sync::atomic::AtomicU64::new(0),
446 eviction_tlb_ready: AtomicBool::new(false),
447 mapping_graph: IrqMutex::new(()),
448 rmap: RmapSet::default(),
449 })
450 }
451
452 pub fn new_present(id: PageId, frame: FrameLease) -> Arc<Self> {
453 Self::new_present_with_resident_kind(id, frame, None)
454 }
455
456 pub fn new_present_with_resident_kind(
457 id: PageId,
458 frame: FrameLease,
459 resident_kind: Option<RssKind>,
460 ) -> Arc<Self> {
461 let page = Self::new_with_resident_kind(id, frame, resident_kind);
462 let _ = page.transition(PageState::Reserved, PageState::Present);
466 page
467 }
468
469 pub fn state(&self) -> PageState {
470 PageState::from_u8(self.state.load(Ordering::Acquire))
471 }
472
473 pub fn frame(&self) -> &FrameLease {
474 &self.frame
475 }
476
477 pub(super) fn prepare_executable_mapping(
481 &self,
482 paddr: PhysAddr,
483 size: usize,
484 flags: ax_runtime::hal::paging::MappingFlags,
485 ) {
486 use ax_runtime::hal::{mem::phys_to_virt, paging::MappingFlags};
487
488 if !flags.contains(MappingFlags::EXECUTE)
489 || flags.intersects(MappingFlags::DEVICE | MappingFlags::UNCACHED)
490 {
491 return;
492 }
493 let offset = paddr
494 .as_usize()
495 .checked_sub(self.frame.paddr().as_usize())
496 .expect("executable leaf must belong to its retained page");
497 assert!(
498 offset <= self.frame.size() && size <= self.frame.size() - offset,
499 "executable leaf exceeds its retained page"
500 );
501 let range = ax_cpu::cache::CacheRange::new(phys_to_virt(paddr), size)
502 .expect("retained RAM alias must not wrap");
503 unsafe { ax_cpu::cache::clean_dcache_range_to_pou(range) };
509 ax_cpu::cache::flush_icache_all();
512 }
513
514 pub fn resident_kind(&self) -> Option<RssKind> {
515 RssKind::from_slot_value(self.resident_kind.load(Ordering::Acquire))
516 }
517
518 pub(crate) fn set_resident_kind(&self, kind: Option<RssKind>) {
519 self.resident_kind
520 .store(RssKind::slot_value(kind), Ordering::Release);
521 }
522
523 pub fn mapping_refs(&self) -> u32 {
524 self.mapping_refs.load(Ordering::Acquire)
525 }
526
527 pub(crate) fn exclusively_mapped_by(&self, mm_id: AddressSpaceId) -> bool {
535 let _graph = self.mapping_graph.lock();
536 if !matches!(self.state(), PageState::Present | PageState::LazyFree) {
537 return false;
538 }
539 let mappings = self.mapping_refs.load(Ordering::Acquire);
540 mappings != 0 && self.rmap.all_mappings_belong_to(mm_id, mappings)
541 }
542
543 fn publish_slot_graph(&self, key: MappingSlotKey) -> bool {
544 let Ok(mut reservation) = self.rmap.prepare_replace(1) else {
545 return false;
546 };
547 let published = {
548 let _graph = self.mapping_graph.lock();
549 if !matches!(self.state(), PageState::Present | PageState::LazyFree) {
550 false
551 } else {
552 let current = self.mapping_refs.load(Ordering::Acquire);
553 let Some(next) = current.checked_add(1) else {
554 return false;
555 };
556 match self.rmap.replace_reserved(&[], &[key], &mut reservation) {
557 Err(_) => false,
558 Ok(()) if !matches!(self.state(), PageState::Present | PageState::LazyFree) => {
559 let _ = self.rmap.replace_reserved(&[key], &[], &mut reservation);
560 false
561 }
562 Ok(()) => {
563 self.mapping_refs.store(next, Ordering::Release);
564 true
565 }
566 }
567 }
568 };
569 drop(reservation);
572 published
573 }
574
575 fn detach_slot_graph(&self, key: MappingSlotKey) -> bool {
576 let Ok(mut reservation) = self.rmap.prepare_replace(0) else {
577 return false;
578 };
579 let (detached, became_exclusive) = {
580 let _graph = self.mapping_graph.lock();
581 let current = self.mapping_refs.load(Ordering::Acquire);
582 let Some(next) = current.checked_sub(1) else {
583 return false;
584 };
585 if self
586 .rmap
587 .replace_reserved(&[key], &[], &mut reservation)
588 .is_err()
589 {
590 (false, false)
591 } else {
592 self.mapping_refs.store(next, Ordering::Release);
593 (true, current > 1 && next == 1)
594 }
595 };
596 drop(reservation);
597 if became_exclusive && self.state() == PageState::LazyFree {
598 super::lifecycle::request_lazy_free_reclaim();
603 }
604 detached
605 }
606
607 pub(crate) fn prepare_mapping_graph_replace(
608 &self,
609 old: &[MappingSlotKey],
610 new: &[MappingSlotKey],
611 ) -> Result<MappingGraphReservation<'_>, MappingGraphError> {
612 self.rmap
613 .prepare_replace(new.len().saturating_sub(old.len()))
614 }
615
616 pub(crate) fn replace_mapping_graph_reserved(
617 &self,
618 old: &[MappingSlotKey],
619 new: &[MappingSlotKey],
620 reservation: &mut MappingGraphReservation<'_>,
621 ) -> Result<(), MappingGraphError> {
622 let became_exclusive = {
623 let _graph = self.mapping_graph.lock();
624 if !matches!(self.state(), PageState::Present | PageState::LazyFree) {
625 return Err(MappingGraphError::PageNotPresent);
626 }
627 let current = self.mapping_refs.load(Ordering::Acquire);
628 let next = if new.len() >= old.len() {
629 let additional = u32::try_from(new.len() - old.len())
630 .map_err(|_| MappingGraphError::RefOverflow)?;
631 current
632 .checked_add(additional)
633 .ok_or(MappingGraphError::RefOverflow)?
634 } else {
635 let removed = u32::try_from(old.len() - new.len())
636 .map_err(|_| MappingGraphError::RefUnderflow)?;
637 current
638 .checked_sub(removed)
639 .ok_or(MappingGraphError::RefUnderflow)?
640 };
641 self.rmap.replace_reserved(old, new, reservation)?;
642 self.mapping_refs.store(next, Ordering::Release);
643 current > 1 && next == 1
644 };
645 if became_exclusive && self.state() == PageState::LazyFree {
646 super::lifecycle::request_lazy_free_reclaim();
647 }
648 Ok(())
649 }
650
651 pub(crate) fn replace_mapping_graph(
655 &self,
656 old: &[MappingSlotKey],
657 new: &[MappingSlotKey],
658 ) -> Result<(), MappingGraphError> {
659 let mut reservation = self.prepare_mapping_graph_replace(old, new)?;
660 let result = self.replace_mapping_graph_reserved(old, new, &mut reservation);
661 drop(reservation);
662 result
663 }
664
665 pub(crate) fn transition(&self, expected: PageState, next: PageState) -> bool {
666 let valid = matches!(
667 (expected, next),
668 (PageState::Reserved, PageState::Present)
669 | (PageState::Present, PageState::LazyFree)
670 | (PageState::LazyFree, PageState::Present)
671 | (PageState::LazyFree, PageState::Evicting)
672 | (PageState::LazyFree, PageState::Retired)
673 | (PageState::Present, PageState::Evicting)
674 | (PageState::Present, PageState::Writeback)
675 | (PageState::Evicting, PageState::Present)
676 | (PageState::Evicting, PageState::Retired)
677 | (PageState::Writeback, PageState::Present)
678 | (PageState::Writeback, PageState::Retired)
679 );
680 valid
681 && self
682 .state
683 .compare_exchange(
684 expected as u8,
685 next as u8,
686 Ordering::AcqRel,
687 Ordering::Acquire,
688 )
689 .is_ok()
690 }
691
692 pub(crate) fn mark_lazy_free(&self) -> bool {
693 self.transition(PageState::Present, PageState::LazyFree)
694 }
695
696 pub(crate) fn clear_lazy_free(&self) -> bool {
697 self.transition(PageState::LazyFree, PageState::Present)
698 }
699
700 pub(crate) fn eviction_lease(self: &Arc<Self>) -> Result<EvictionLease, EvictionError> {
705 let _graph = self.mapping_graph.lock();
706 if !self.transition(PageState::Present, PageState::Evicting) {
707 return Err(EvictionError::NotPresent);
708 }
709 self.eviction_tlb_ready.store(false, Ordering::Release);
710 Ok(EvictionLease { page: self.clone() })
711 }
712
713 pub(crate) fn complete_eviction_tlb(&self) {
716 if self.state() == PageState::Evicting {
717 self.eviction_tlb_ready.store(true, Ordering::Release);
718 }
719 }
720
721 pub(crate) fn resume_eviction_lease(self: &Arc<Self>) -> Result<EvictionLease, EvictionError> {
725 if self.state() != PageState::Evicting
726 || self
727 .eviction_tlb_ready
728 .compare_exchange(true, false, Ordering::AcqRel, Ordering::Acquire)
729 .is_err()
730 {
731 return Err(EvictionError::Busy);
732 }
733 Ok(EvictionLease { page: self.clone() })
734 }
735
736 pub(crate) fn writeback_lease(self: &Arc<Self>) -> Result<WritebackLease, WritebackError> {
740 let _graph = self.mapping_graph.lock();
741 if !self.transition(PageState::Present, PageState::Writeback) {
742 return Err(WritebackError::Busy);
743 }
744 let generation = match self.writeback_generation.try_update(
745 Ordering::AcqRel,
746 Ordering::Acquire,
747 |current| current.checked_add(1),
748 ) {
749 Ok(previous) => previous + 1,
750 Err(_) => {
751 let _ = self.transition(PageState::Writeback, PageState::Present);
752 return Err(WritebackError::GenerationExhausted);
753 }
754 };
755 Ok(WritebackLease {
756 page: self.clone(),
757 generation,
758 })
759 }
760}
761
762#[derive(Debug, Clone, Copy, PartialEq, Eq)]
763pub enum MappingGraphError {
764 PageNotPresent,
765 MissingOldSlot,
766 DuplicateNewSlot,
767 ResourceExhausted,
768 RefOverflow,
769 RefUnderflow,
770 SlotStateConflict,
771 SlotIdentityMismatch,
772 RollbackFailed,
773}
774
775#[derive(Debug, Clone, Copy, PartialEq, Eq)]
776pub enum EvictionError {
777 NotPresent,
778 Busy,
779}
780
781#[derive(Debug, Clone, Copy, PartialEq, Eq)]
782pub enum WritebackError {
783 Busy,
784 GenerationExhausted,
785}
786
787pub struct EvictionLease {
789 page: Arc<PageObject>,
790}
791
792impl EvictionLease {
793 pub fn page(&self) -> &Arc<PageObject> {
794 &self.page
795 }
796
797 pub(crate) fn cancel(self) -> bool {
798 self.page.eviction_tlb_ready.store(false, Ordering::Release);
799 self.page
800 .transition(PageState::Evicting, PageState::Present)
801 }
802
803 pub(crate) fn retire(self) -> Result<(), (Self, EvictionError)> {
804 if !self.page.rmap.is_empty() || self.page.mapping_refs() != 0 {
805 return Err((self, EvictionError::Busy));
806 }
807 if self
808 .page
809 .transition(PageState::Evicting, PageState::Retired)
810 {
811 Ok(())
812 } else {
813 Err((self, EvictionError::Busy))
814 }
815 }
816}
817
818pub struct WritebackLease {
820 page: Arc<PageObject>,
821 generation: u64,
822}
823
824impl WritebackLease {
825 pub const fn generation(&self) -> u64 {
826 self.generation
827 }
828
829 pub(crate) fn cancel(self) -> bool {
830 self.page
831 .transition(PageState::Writeback, PageState::Present)
832 }
833
834 pub(crate) fn complete(self) -> Result<u64, (Self, WritebackError)> {
835 if self
836 .page
837 .transition(PageState::Writeback, PageState::Present)
838 {
839 Ok(self.generation)
840 } else {
841 Err((self, WritebackError::Busy))
842 }
843 }
844}
845
846#[repr(u8)]
847#[derive(Debug, Clone, Copy, PartialEq, Eq)]
848pub enum SlotState {
849 Reserved = 0,
850 Present = 1,
851 Detached = 2,
852}
853
854impl SlotState {
855 fn from_u8(value: u8) -> Self {
856 match value {
857 0 => Self::Reserved,
858 1 => Self::Present,
859 _ => Self::Detached,
860 }
861 }
862}
863
864pub struct MappingSlot {
866 pub mapping: MappingId,
867 pub mm_id: AddressSpaceId,
868 pub va: VirtAddr,
869 pub page_order: PageOrder,
870 pub page: Arc<PageObject>,
871 frame_offset: usize,
878 resident_kind: AtomicU8,
883 huge_split_deposit: IrqMutex<Option<HugeSplitDeposit>>,
888 state: AtomicU8,
889}
890
891impl MappingSlot {
892 #[cfg(test)]
893 pub(crate) fn new(
894 mapping: MappingId,
895 mm_id: AddressSpaceId,
896 va: VirtAddr,
897 page_order: PageOrder,
898 page: Arc<PageObject>,
899 resident_kind: Option<RssKind>,
900 ) -> Self {
901 Self {
902 mapping,
903 mm_id,
904 va,
905 page_order,
906 page,
907 frame_offset: 0,
908 resident_kind: AtomicU8::new(RssKind::slot_value(resident_kind)),
909 huge_split_deposit: IrqMutex::new(None),
910 state: AtomicU8::new(SlotState::Reserved as u8),
911 }
912 }
913
914 pub(crate) fn new_with_frame_offset(
915 mapping: MappingId,
916 mm_id: AddressSpaceId,
917 va: VirtAddr,
918 page_order: PageOrder,
919 page: Arc<PageObject>,
920 frame_offset: usize,
921 resident_kind: Option<RssKind>,
922 ) -> Option<Self> {
923 let bytes = PAGE_SIZE_4K.checked_shl(page_order.get().into())?;
924 let end = frame_offset.checked_add(bytes)?;
925 if !frame_offset.is_multiple_of(PAGE_SIZE_4K) || end > page.frame().size() {
926 return None;
927 }
928 Some(Self {
929 mapping,
930 mm_id,
931 va,
932 page_order,
933 page,
934 frame_offset,
935 resident_kind: AtomicU8::new(RssKind::slot_value(resident_kind)),
936 huge_split_deposit: IrqMutex::new(None),
937 state: AtomicU8::new(SlotState::Reserved as u8),
938 })
939 }
940
941 pub(crate) const fn frame_offset(&self) -> usize {
942 self.frame_offset
943 }
944
945 pub(crate) fn mapped_paddr(&self) -> Option<PhysAddr> {
946 self.page.frame().paddr().checked_add(self.frame_offset)
947 }
948
949 pub(crate) fn attach_huge_split_deposit(
953 self,
954 deposit: HugeSplitDeposit,
955 ) -> Result<Self, HugeSplitDeposit> {
956 {
957 let mut owner = self.huge_split_deposit.lock();
958 if owner.is_some() {
959 return Err(deposit);
960 }
961 *owner = Some(deposit);
962 }
963 Ok(self)
964 }
965
966 pub(crate) fn has_huge_split_deposit(&self) -> bool {
967 self.huge_split_deposit.lock().is_some()
968 }
969
970 pub(crate) fn take_huge_split_deposit(&self) -> Option<HugeSplitDeposit> {
971 self.huge_split_deposit.lock().take()
972 }
973
974 pub(crate) fn restore_huge_split_deposit(
975 &self,
976 deposit: HugeSplitDeposit,
977 ) -> Result<(), HugeSplitDeposit> {
978 let mut owner = self.huge_split_deposit.lock();
979 if owner.is_some() {
980 return Err(deposit);
981 }
982 *owner = Some(deposit);
983 Ok(())
984 }
985
986 pub(crate) fn overlaps(&self, range: VirtAddrRange) -> bool {
987 let Some(bytes) = PAGE_SIZE_4K.checked_shl(self.page_order.get().into()) else {
988 return false;
989 };
990 let Some(end) = self.va.checked_add(bytes) else {
991 return false;
992 };
993 self.va < range.end && range.start < end
994 }
995
996 pub fn state(&self) -> SlotState {
997 SlotState::from_u8(self.state.load(Ordering::Acquire))
998 }
999
1000 pub fn resident_kind(&self) -> Option<RssKind> {
1001 RssKind::from_slot_value(self.resident_kind.load(Ordering::Acquire))
1002 }
1003
1004 pub(crate) fn set_resident_kind(&self, kind: Option<RssKind>) {
1009 self.resident_kind
1010 .store(RssKind::slot_value(kind), Ordering::Release);
1011 }
1012
1013 pub(crate) fn publish(&self) -> bool {
1014 if self
1015 .state
1016 .compare_exchange(
1017 SlotState::Reserved as u8,
1018 SlotState::Present as u8,
1019 Ordering::AcqRel,
1020 Ordering::Acquire,
1021 )
1022 .is_err()
1023 {
1024 return false;
1025 }
1026 if self.page.publish_slot_graph(MappingSlotKey {
1027 space_id: self.mm_id,
1028 va: self.va,
1029 }) {
1030 true
1031 } else {
1032 let _ = self.state.compare_exchange(
1033 SlotState::Present as u8,
1034 SlotState::Reserved as u8,
1035 Ordering::AcqRel,
1036 Ordering::Acquire,
1037 );
1038 false
1039 }
1040 }
1041
1042 pub(crate) fn detach(&self) -> bool {
1043 self.detach_slot()
1044 }
1045
1046 pub(crate) fn restore(&self) -> bool {
1051 if self
1052 .state
1053 .compare_exchange(
1054 SlotState::Detached as u8,
1055 SlotState::Present as u8,
1056 Ordering::AcqRel,
1057 Ordering::Acquire,
1058 )
1059 .is_err()
1060 {
1061 return false;
1062 }
1063 if self.page.publish_slot_graph(MappingSlotKey {
1064 space_id: self.mm_id,
1065 va: self.va,
1066 }) {
1067 true
1068 } else {
1069 let _ = self.state.compare_exchange(
1070 SlotState::Present as u8,
1071 SlotState::Detached as u8,
1072 Ordering::AcqRel,
1073 Ordering::Acquire,
1074 );
1075 false
1076 }
1077 }
1078
1079 fn detach_slot(&self) -> bool {
1080 if self
1081 .state
1082 .compare_exchange(
1083 SlotState::Present as u8,
1084 SlotState::Detached as u8,
1085 Ordering::AcqRel,
1086 Ordering::Acquire,
1087 )
1088 .is_err()
1089 {
1090 return false;
1091 }
1092 if !self.page.detach_slot_graph(MappingSlotKey {
1093 space_id: self.mm_id,
1094 va: self.va,
1095 }) {
1096 let _ = self.state.compare_exchange(
1097 SlotState::Detached as u8,
1098 SlotState::Present as u8,
1099 Ordering::AcqRel,
1100 Ordering::Acquire,
1101 );
1102 return false;
1103 }
1104 true
1105 }
1106
1107 pub(crate) fn publish_after_graph_replace(&self) -> bool {
1108 self.state
1109 .compare_exchange(
1110 SlotState::Reserved as u8,
1111 SlotState::Present as u8,
1112 Ordering::AcqRel,
1113 Ordering::Acquire,
1114 )
1115 .is_ok()
1116 }
1117
1118 pub(crate) fn detach_after_graph_replace(&self) -> bool {
1119 self.state
1120 .compare_exchange(
1121 SlotState::Present as u8,
1122 SlotState::Detached as u8,
1123 Ordering::AcqRel,
1124 Ordering::Acquire,
1125 )
1126 .is_ok()
1127 }
1128
1129 pub(crate) fn relocate_to(&self, replacement: &Self) -> Result<(), MappingGraphError> {
1138 if self.state() != SlotState::Present || replacement.state() != SlotState::Reserved {
1139 return Err(MappingGraphError::SlotStateConflict);
1140 }
1141 if self.mm_id != replacement.mm_id
1142 || self.page_order != replacement.page_order
1143 || !Arc::ptr_eq(&self.page, &replacement.page)
1144 {
1145 return Err(MappingGraphError::SlotIdentityMismatch);
1146 }
1147
1148 let old_key = MappingSlotKey {
1149 space_id: self.mm_id,
1150 va: self.va,
1151 };
1152 let new_key = MappingSlotKey {
1153 space_id: replacement.mm_id,
1154 va: replacement.va,
1155 };
1156 if old_key == new_key {
1157 return Err(MappingGraphError::DuplicateNewSlot);
1158 }
1159
1160 self.page.replace_mapping_graph(&[old_key], &[new_key])?;
1161 if !replacement.publish_after_graph_replace() {
1162 if self
1163 .page
1164 .replace_mapping_graph(&[new_key], &[old_key])
1165 .is_err()
1166 {
1167 return Err(MappingGraphError::RollbackFailed);
1168 }
1169 return Err(MappingGraphError::SlotStateConflict);
1170 }
1171 if self.detach_after_graph_replace() {
1172 return Ok(());
1173 }
1174
1175 let replacement_reserved = replacement.reserve_after_graph_replace();
1176 let graph_restored = replacement_reserved
1177 && self
1178 .page
1179 .replace_mapping_graph(&[new_key], &[old_key])
1180 .is_ok();
1181 if !replacement_reserved || !graph_restored {
1182 return Err(MappingGraphError::RollbackFailed);
1183 }
1184 Err(MappingGraphError::SlotStateConflict)
1185 }
1186
1187 pub(crate) fn restore_after_graph_replace(&self) -> bool {
1188 self.state
1189 .compare_exchange(
1190 SlotState::Detached as u8,
1191 SlotState::Present as u8,
1192 Ordering::AcqRel,
1193 Ordering::Acquire,
1194 )
1195 .is_ok()
1196 }
1197
1198 pub(crate) fn reserve_after_graph_replace(&self) -> bool {
1199 self.state
1200 .compare_exchange(
1201 SlotState::Present as u8,
1202 SlotState::Reserved as u8,
1203 Ordering::AcqRel,
1204 Ordering::Acquire,
1205 )
1206 .is_ok()
1207 }
1208}
1209
1210#[cfg(test)]
1211mod tests {
1212 #[cfg(not(axtest))]
1213 use core::sync::atomic::AtomicUsize;
1214
1215 use super::*;
1216
1217 #[cfg(not(axtest))]
1218 static RELEASES: AtomicUsize = AtomicUsize::new(0);
1219
1220 #[cfg(not(axtest))]
1221 fn record_release(_paddr: PhysAddr, bytes: usize) {
1222 assert_eq!(bytes, PAGE_SIZE_4K * 4);
1223 RELEASES.fetch_add(1, Ordering::Relaxed);
1224 }
1225
1226 #[test]
1227 fn shared_page_tracks_slots_until_detach() {
1228 let page = PageObject::new(
1229 PageId::new(1),
1230 FrameLease::new(PhysAddr::from_usize(0x1000)),
1231 );
1232 assert!(page.transition(PageState::Reserved, PageState::Present));
1233 let id = AddressSpaceId::allocate();
1234 let slot_a = MappingSlot::new(
1235 MappingId::new(1),
1236 id,
1237 VirtAddr::from_usize(0x4000),
1238 PageOrder::BASE,
1239 page.clone(),
1240 Some(RssKind::Anon),
1241 );
1242 let slot_b = MappingSlot::new(
1243 MappingId::new(1),
1244 id,
1245 VirtAddr::from_usize(0x5000),
1246 PageOrder::BASE,
1247 page.clone(),
1248 Some(RssKind::Anon),
1249 );
1250 assert!(slot_a.publish());
1251 assert!(slot_b.publish());
1252 assert_eq!(page.rmap.snapshot().len(), 2);
1253 assert!(slot_a.detach());
1254 assert_eq!(page.rmap.snapshot().len(), 1);
1255 assert!(slot_b.detach());
1256 assert!(page.rmap.is_empty());
1257 }
1258
1259 #[cfg_attr(axtest, axtest::axtest)]
1260 #[cfg_attr(not(axtest), test)]
1261 fn concurrent_mapping_publication_preserves_reserved_capacity() {
1262 let page = PageObject::new_present(
1263 PageId::new(7),
1264 FrameLease::new(PhysAddr::from_usize(0x1000)),
1265 );
1266 let first = MappingSlotKey {
1267 space_id: AddressSpaceId::allocate(),
1268 va: VirtAddr::from_usize(0x4000),
1269 };
1270 let second = MappingSlotKey {
1271 space_id: AddressSpaceId::allocate(),
1272 va: first.va,
1273 };
1274 let mut first_reservation = page.prepare_mapping_graph_replace(&[], &[first]).unwrap();
1277 page.replace_mapping_graph(&[], &[second]).unwrap();
1278 page.replace_mapping_graph_reserved(&[], &[first], &mut first_reservation)
1279 .unwrap();
1280 drop(first_reservation);
1281 assert_eq!(page.mapping_refs(), 2);
1282 let mappings = page.rmap.snapshot();
1283 assert_eq!(mappings.len(), 2);
1284 assert!(mappings.contains(&first));
1285 assert!(mappings.contains(&second));
1286 let cancelled = page.prepare_mapping_graph_replace(&[], &[first]).unwrap();
1287 drop(cancelled);
1288 assert_eq!(page.rmap.entries.lock().reserved, 0);
1289 assert_eq!(page.mapping_refs(), 2);
1290 page.replace_mapping_graph(&[first, second], &[]).unwrap();
1291 assert_eq!(page.mapping_refs(), 0);
1292 assert!(page.rmap.is_empty());
1293 }
1294
1295 #[cfg_attr(axtest, axtest::axtest)]
1296 #[cfg_attr(not(axtest), test)]
1297 fn lazy_free_page_requeues_when_a_shared_mapping_becomes_exclusive() {
1298 let page = PageObject::new_present(
1299 PageId::new(5),
1300 FrameLease::new(PhysAddr::from_usize(0xa000)),
1301 );
1302 let mapping = MappingId::new(5);
1303 let parent = MappingSlot::new(
1304 mapping,
1305 AddressSpaceId::allocate(),
1306 VirtAddr::from_usize(0xa000),
1307 PageOrder::BASE,
1308 page.clone(),
1309 Some(RssKind::Anon),
1310 );
1311 let child = MappingSlot::new(
1312 mapping,
1313 AddressSpaceId::allocate(),
1314 VirtAddr::from_usize(0xb000),
1315 PageOrder::BASE,
1316 page.clone(),
1317 Some(RssKind::Anon),
1318 );
1319 assert!(parent.publish());
1320 assert!(child.publish());
1321 assert!(page.mark_lazy_free());
1322 assert_eq!(page.mapping_refs(), 2);
1323
1324 let requests = super::super::lifecycle::lazy_free_reclaim_request_count_for_test();
1325 assert!(child.detach());
1326 assert_eq!(page.mapping_refs(), 1);
1327 assert!(
1328 super::super::lifecycle::lazy_free_reclaim_request_count_for_test() > requests,
1329 "the 2 -> 1 mapping transition must publish a new reclaim edge"
1330 );
1331 assert!(parent.detach());
1332 }
1333
1334 #[cfg_attr(axtest, axtest::axtest)]
1335 #[cfg_attr(not(axtest), test)]
1336 fn lazy_free_page_requeues_after_a_batch_graph_replacement() {
1337 let page = PageObject::new_present(
1338 PageId::new(6),
1339 FrameLease::new(PhysAddr::from_usize(0xc000)),
1340 );
1341 let first = MappingSlotKey {
1342 space_id: AddressSpaceId::allocate(),
1343 va: VirtAddr::from_usize(0xc000),
1344 };
1345 let second = MappingSlotKey {
1346 space_id: AddressSpaceId::allocate(),
1347 va: VirtAddr::from_usize(0xd000),
1348 };
1349 let replacement = MappingSlotKey {
1350 space_id: first.space_id,
1351 va: VirtAddr::from_usize(0xe000),
1352 };
1353 page.replace_mapping_graph(&[], &[first, second]).unwrap();
1354 assert!(page.mark_lazy_free());
1355 assert_eq!(page.mapping_refs(), 2);
1356
1357 let requests = super::super::lifecycle::lazy_free_reclaim_request_count_for_test();
1358 let mut reservation = page
1359 .prepare_mapping_graph_replace(&[first, second], &[replacement])
1360 .unwrap();
1361 page.replace_mapping_graph_reserved(&[first, second], &[replacement], &mut reservation)
1362 .unwrap();
1363 drop(reservation);
1364
1365 assert_eq!(page.mapping_refs(), 1);
1366 assert!(
1367 super::super::lifecycle::lazy_free_reclaim_request_count_for_test() > requests,
1368 "a batch graph replacement ending at one mapping must publish a reclaim edge"
1369 );
1370 page.replace_mapping_graph(&[replacement], &[]).unwrap();
1371 }
1372
1373 #[test]
1374 fn reserved_page_cannot_publish_a_mapping_slot() {
1375 let page = PageObject::new(
1376 PageId::new(2),
1377 FrameLease::new(PhysAddr::from_usize(0x2000)),
1378 );
1379 let slot = MappingSlot::new(
1380 MappingId::new(2),
1381 AddressSpaceId::allocate(),
1382 VirtAddr::from_usize(0x6000),
1383 PageOrder::BASE,
1384 page,
1385 None,
1386 );
1387 assert!(!slot.publish());
1388 assert_eq!(slot.state(), SlotState::Reserved);
1389 }
1390
1391 #[cfg_attr(axtest, axtest::axtest)]
1392 #[cfg_attr(not(axtest), test)]
1393 fn relocation_replaces_rmap_key_without_changing_mapping_refs() {
1394 let page = PageObject::new_present(
1395 PageId::new(4),
1396 FrameLease::new(PhysAddr::from_usize(0x4000)),
1397 );
1398 let mm_id = AddressSpaceId::allocate();
1399 let old_va = VirtAddr::from_usize(0x8000);
1400 let new_va = VirtAddr::from_usize(0x9000);
1401 let old = MappingSlot::new(
1402 MappingId::new(4),
1403 mm_id,
1404 old_va,
1405 PageOrder::BASE,
1406 page.clone(),
1407 Some(RssKind::Anon),
1408 );
1409 let replacement = MappingSlot::new(
1410 MappingId::new(4),
1411 mm_id,
1412 new_va,
1413 PageOrder::BASE,
1414 page.clone(),
1415 Some(RssKind::Anon),
1416 );
1417 assert!(old.publish());
1418 assert_eq!(page.mapping_refs(), 1);
1419
1420 old.relocate_to(&replacement).unwrap();
1421
1422 assert_eq!(old.state(), SlotState::Detached);
1423 assert_eq!(replacement.state(), SlotState::Present);
1424 assert_eq!(page.mapping_refs(), 1);
1425 assert_eq!(
1426 page.rmap.snapshot(),
1427 alloc::vec![MappingSlotKey {
1428 space_id: mm_id,
1429 va: new_va,
1430 }]
1431 );
1432 assert!(replacement.detach());
1433 assert_eq!(page.mapping_refs(), 0);
1434 }
1435
1436 #[test]
1437 fn eviction_cannot_resume_before_tlb_retirement() {
1438 let page = PageObject::new_present(
1439 PageId::new(3),
1440 FrameLease::new(PhysAddr::from_usize(0x3000)),
1441 );
1442 let lease = page.eviction_lease().unwrap();
1443
1444 drop(lease);
1448 assert_eq!(page.state(), PageState::Evicting);
1449 assert!(matches!(
1450 page.resume_eviction_lease(),
1451 Err(EvictionError::Busy)
1452 ));
1453
1454 page.complete_eviction_tlb();
1455 let resumed = page.resume_eviction_lease().unwrap();
1456 assert!(resumed.cancel());
1457 assert_eq!(page.state(), PageState::Present);
1458 }
1459
1460 #[test]
1461 fn split_frame_leases_release_the_allocation_once_after_the_last_subpage() {
1462 RELEASES.store(0, Ordering::Relaxed);
1463 let owner = FrameLease::owned_with_releaser(
1464 PhysAddr::from_usize(0x20_0000),
1465 PAGE_SIZE_4K * 4,
1466 record_release,
1467 );
1468 let first = owner.sublease(0, PAGE_SIZE_4K).unwrap();
1469 let last = owner.sublease(PAGE_SIZE_4K * 3, PAGE_SIZE_4K).unwrap();
1470 assert_eq!(first.paddr(), PhysAddr::from_usize(0x20_0000));
1471 assert_eq!(last.paddr(), PhysAddr::from_usize(0x20_3000));
1472 assert!(owner.sublease(PAGE_SIZE_4K * 4, PAGE_SIZE_4K).is_none());
1473
1474 drop(owner);
1475 drop(first);
1476 assert_eq!(RELEASES.load(Ordering::Relaxed), 0);
1477 drop(last);
1478 assert_eq!(RELEASES.load(Ordering::Relaxed), 1);
1479 }
1480}