1use alloc::{sync::Arc, vec::Vec};
4use core::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
5
6use ax_memory_addr::{VirtAddr, VirtAddrRange};
7use heapless::Vec as InlineVec;
8
9use super::{AddressSpaceId, VmEpoch, objects::FrameLease};
10use crate::sync::{IrqMutex, try_push_irq_vec, try_reserve_irq_vec};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum MutationState {
14 Prepared,
15 Applied,
16 PublishedPendingTlb,
17 Published,
18 Retired,
19 Aborted,
20 NeedsRepair,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum PublishEvent {
25 MappingPublished {
26 space_id: AddressSpaceId,
27 epoch: VmEpoch,
28 },
29 MappingRetired {
30 space_id: AddressSpaceId,
31 epoch: VmEpoch,
32 },
33}
34
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
36pub struct VmaDelta {
37 pub inserted: u32,
38 pub removed: u32,
39 pub split: u32,
40 pub merged: u32,
41}
42
43#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
44pub struct PteDelta {
45 pub mapped: u32,
46 pub unmapped: u32,
47 pub protected: u32,
48}
49
50#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
51pub struct MappingDelta {
52 pub attached: u32,
53 pub detached: u32,
54}
55
56#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
57pub struct ResidentDelta {
58 pub anon: i64,
59 pub file: i64,
60 pub shmem: i64,
61}
62
63impl ResidentDelta {
64 pub const fn total(self) -> i64 {
65 self.anon
66 .saturating_add(self.file)
67 .saturating_add(self.shmem)
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum EvictionResult {
73 Retired,
74 Busy,
75 Unsupported,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct TlbRange {
80 pub start: VirtAddr,
81 pub size: usize,
82}
83
84impl TlbRange {
85 pub fn new(start: VirtAddr, size: usize) -> Option<Self> {
86 VirtAddrRange::try_from_start_size(start, size).map(|_| Self { start, size })
87 }
88
89 fn overlaps(self, other: Self) -> bool {
90 let Some(left) = VirtAddrRange::try_from_start_size(self.start, self.size) else {
91 return true;
92 };
93 let Some(right) = VirtAddrRange::try_from_start_size(other.start, other.size) else {
94 return true;
95 };
96 left.overlaps(right)
97 }
98}
99
100#[derive(Debug, Clone)]
103pub struct TlbRequest {
104 pub space_id: AddressSpaceId,
105 pub epoch: VmEpoch,
106 pub targets: usize,
107 pub ranges: InlineVec<TlbRange, MAX_INLINE_TLB_RANGES>,
108 acknowledged: usize,
109 ranges_collapsed_to_full_flush: bool,
110}
111
112const MAX_INLINE_TLB_RANGES: usize = 8;
116
117const MAX_PUBLISH_EVENTS: usize = 2;
120
121#[derive(Debug)]
122struct QuarantinedFrame {
123 frame: FrameLease,
124 request: TlbRequest,
125}
126
127#[derive(Debug, Default)]
133pub struct TlbQuarantine {
134 entries: IrqMutex<Vec<QuarantinedFrame>>,
135}
136
137impl TlbQuarantine {
138 pub fn defer(&self, frame: FrameLease, request: TlbRequest) -> Result<(), QuarantineFailure> {
145 self.try_defer(frame, request)
146 }
147
148 pub fn try_defer(
149 &self,
150 frame: FrameLease,
151 request: TlbRequest,
152 ) -> Result<(), QuarantineFailure> {
153 if request.is_complete() {
154 return Ok(());
158 }
159 try_push_irq_vec(&self.entries, QuarantinedFrame { frame, request }).map_err(|entry| {
160 QuarantineFailure {
161 frame: entry.frame,
162 reason: QuarantineError::ResourceExhausted,
163 }
164 })
165 }
166
167 pub fn try_defer_recoverable(
172 &self,
173 frame: FrameLease,
174 request: TlbRequest,
175 ) -> Result<(), QuarantineFailure> {
176 self.try_defer(frame, request)
177 }
178
179 pub fn pending(&self) -> usize {
180 self.entries.lock().len()
181 }
182
183 pub fn requests(&self) -> Result<Vec<TlbRequest>, QuarantineError> {
184 let mut requests = Vec::new();
185 loop {
186 let required = self.entries.lock().len();
187 requests
188 .try_reserve_exact(required)
189 .map_err(|_| QuarantineError::ResourceExhausted)?;
190 let entries = self.entries.lock();
191 if entries.len() > requests.capacity() {
192 continue;
193 }
194 requests.extend(entries.iter().map(|entry| entry.request.clone()));
195 return Ok(requests);
196 }
197 }
198
199 pub fn contains_request(&self, space_id: AddressSpaceId, epoch: VmEpoch) -> bool {
200 self.entries
201 .lock()
202 .iter()
203 .any(|entry| entry.request.space_id == space_id && entry.request.epoch == epoch)
204 }
205
206 pub fn reap_ready(&self) -> Result<Vec<FrameLease>, QuarantineError> {
210 let mut released = Vec::new();
211 loop {
212 if released.len() == released.capacity() {
213 if !self
214 .entries
215 .lock()
216 .iter()
217 .any(|entry| entry.request.is_complete())
218 {
219 return Ok(released);
220 }
221 released
222 .try_reserve(1)
223 .map_err(|_| QuarantineError::ResourceExhausted)?;
224 }
225 let entry = {
226 let mut entries = self.entries.lock();
227 entries
228 .iter()
229 .position(|entry| entry.request.is_complete())
230 .map(|index| entries.swap_remove(index))
231 };
232 let Some(entry) = entry else {
233 return Ok(released);
234 };
235 let QuarantinedFrame { frame, request } = entry;
239 released.push(frame);
240 drop(request);
241 }
242 }
243
244 pub fn acknowledge(
246 &self,
247 space_id: AddressSpaceId,
248 epoch: VmEpoch,
249 cpu: usize,
250 ) -> Result<Vec<FrameLease>, QuarantineError> {
251 {
252 let mut entries = self.entries.lock();
253 for entry in entries.iter_mut() {
254 if entry.request.space_id == space_id && entry.request.epoch == epoch {
255 let _ = entry.request.acknowledge(cpu);
256 }
257 }
258 }
259 self.reap_ready()
260 }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
264pub enum QuarantineError {
265 ResourceExhausted,
266}
267
268#[derive(Debug)]
271pub struct QuarantineFailure {
272 pub frame: FrameLease,
273 pub reason: QuarantineError,
274}
275
276impl TlbRequest {
277 pub fn new(space_id: AddressSpaceId, epoch: VmEpoch, targets: usize) -> Self {
278 Self {
279 space_id,
280 epoch,
281 targets,
282 ranges: InlineVec::new(),
283 acknowledged: 0,
284 ranges_collapsed_to_full_flush: false,
285 }
286 }
287
288 pub fn with_range(mut self, range: TlbRange) -> Self {
289 self.add_range(range);
290 self
291 }
292
293 pub fn try_with_range(mut self, range: TlbRange) -> Result<Self, MutationError> {
296 self.add_range(range);
297 Ok(self)
298 }
299
300 fn add_range(&mut self, range: TlbRange) {
301 if self.ranges_collapsed_to_full_flush {
302 return;
303 }
304 if self.ranges.push(range).is_err() {
305 self.ranges.clear();
306 self.ranges_collapsed_to_full_flush = true;
307 }
308 }
309
310 pub const fn targets(&self) -> usize {
311 self.targets
312 }
313
314 pub const fn acknowledged_mask(&self) -> usize {
315 self.acknowledged
316 }
317
318 pub fn acknowledge(&mut self, cpu: usize) -> bool {
319 if cpu >= usize::BITS as usize {
320 return false;
321 }
322 let bit = 1usize << cpu;
323 if self.acknowledged & bit != 0 || self.targets & bit == 0 {
324 return false;
325 }
326 self.acknowledged |= bit;
327 true
328 }
329
330 pub fn pending(&self) -> usize {
331 self.targets & !self.acknowledged
332 }
333
334 pub fn is_complete(&self) -> bool {
335 self.pending() == 0
336 }
337
338 fn overlaps(&self, other: &Self) -> bool {
339 if self.space_id != other.space_id {
340 return false;
341 }
342 if self.ranges.is_empty() || other.ranges.is_empty() {
345 return true;
346 }
347 self.ranges
348 .iter()
349 .any(|left| other.ranges.iter().any(|right| left.overlaps(*right)))
350 }
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub enum MutationError {
355 WrongState,
356 EpochConflict,
357 ApplyFailed,
358 TlbPending,
359 PendingTlbOverlap,
360 ResourceExhausted,
361 NeedsRepair,
362 EpochExhausted,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366enum MutationPrecondition {
367 None,
368 NoPendingTlbOverlap,
369}
370
371pub struct MutationGate {
378 epoch: AtomicU64,
379 health: core::sync::atomic::AtomicU8,
380 #[cfg(test)]
381 fail_next_commit_before_publish: core::sync::atomic::AtomicBool,
382 #[cfg(test)]
383 last_retired_receipt: IrqMutex<Option<MutationReceipt>>,
384 commit_lock: IrqMutex<()>,
389 pending: IrqMutex<Vec<MutationReceipt>>,
393}
394
395impl Default for MutationGate {
396 fn default() -> Self {
397 Self::new()
398 }
399}
400
401impl MutationGate {
402 pub const fn new() -> Self {
403 Self {
404 epoch: AtomicU64::new(0),
405 health: core::sync::atomic::AtomicU8::new(0),
406 #[cfg(test)]
407 fail_next_commit_before_publish: core::sync::atomic::AtomicBool::new(false),
408 #[cfg(test)]
409 last_retired_receipt: IrqMutex::new(None),
410 commit_lock: IrqMutex::new(()),
411 pending: IrqMutex::new(Vec::new()),
412 }
413 }
414
415 pub fn current_epoch(&self) -> VmEpoch {
416 VmEpoch::new(self.epoch.load(Ordering::Acquire))
417 }
418
419 pub fn needs_repair(&self) -> bool {
420 self.health.load(Ordering::Acquire) != 0
421 }
422
423 pub fn mark_needs_repair(&self) {
424 self.health.store(1, Ordering::Release);
425 }
426
427 pub fn clear_repair(&self) {
428 self.health.store(0, Ordering::Release);
429 }
430
431 #[cfg(test)]
432 pub(crate) fn fail_next_commit_before_publish(&self) {
433 self.fail_next_commit_before_publish
434 .store(true, Ordering::Release);
435 }
436
437 #[cfg(test)]
438 pub(crate) fn last_retired_receipt(&self) -> Option<MutationReceipt> {
439 self.last_retired_receipt.lock().clone()
440 }
441
442 pub fn begin(&self, space_id: AddressSpaceId, targets: usize) -> PreparedMutation {
443 PreparedMutation::new(space_id, self.current_epoch(), targets)
444 }
445
446 pub fn begin_fresh_mapping(&self, space_id: AddressSpaceId) -> PreparedMutation {
453 let mut mutation = PreparedMutation::new(space_id, self.current_epoch(), 0);
454 mutation.precondition = MutationPrecondition::NoPendingTlbOverlap;
455 mutation
456 }
457
458 pub(super) fn validate_publish_preconditions(
465 &self,
466 mutation: &PreparedMutation,
467 ) -> Result<(), MutationError> {
468 let commit_guard = self.commit_lock.lock();
469 let pending_overlap = self.pending_overlap_request(mutation).is_some();
470 drop(commit_guard);
471 if pending_overlap {
472 Err(MutationError::PendingTlbOverlap)
473 } else {
474 Ok(())
475 }
476 }
477
478 pub(super) fn pending_overlap_request(
482 &self,
483 mutation: &PreparedMutation,
484 ) -> Option<TlbRequest> {
485 if mutation.precondition != MutationPrecondition::NoPendingTlbOverlap {
486 return None;
487 }
488 self.pending
489 .lock()
490 .iter()
491 .find(|pending| {
492 pending
493 .tlb_obligation
494 .overlaps(&mutation.receipt.tlb_obligation)
495 })
496 .map(|pending| pending.tlb_obligation.clone())
497 }
498
499 pub fn begin_with_active_targets(
506 &self,
507 space_id: AddressSpaceId,
508 active_targets: Arc<AtomicUsize>,
509 ) -> PreparedMutation {
510 PreparedMutation::new_with_active_targets(space_id, self.current_epoch(), active_targets)
511 }
512
513 pub fn commit(&self, mut mutation: PreparedMutation) -> Result<MutationReceipt, MutationError> {
520 #[cfg(test)]
521 if self
522 .fail_next_commit_before_publish
523 .swap(false, Ordering::AcqRel)
524 {
525 return Err(MutationError::ResourceExhausted);
526 }
527 if self.needs_repair() {
528 return Err(MutationError::NeedsRepair);
529 }
530 mutation.freeze_active_targets();
531 let needs_pending_slot = !mutation.receipt.tlb_obligation.is_complete();
532 let _commit_guard = loop {
538 if needs_pending_slot {
539 try_reserve_irq_vec(&self.pending, 1)
540 .map_err(|_| MutationError::ResourceExhausted)?;
541 }
542 let guard = self.commit_lock.lock();
543 let has_capacity = !needs_pending_slot || {
544 let pending = self.pending.lock();
545 pending.len() < pending.capacity()
546 };
547 if has_capacity {
548 break guard;
549 }
550 drop(guard);
551 };
552 let pending_overlap = self.pending_overlap_request(&mutation).is_some();
553 if pending_overlap {
554 drop(_commit_guard);
558 return Err(MutationError::PendingTlbOverlap);
559 }
560 let base_epoch = self.current_epoch();
561 let new_epoch = base_epoch
562 .checked_next()
563 .ok_or(MutationError::EpochExhausted)?;
564 let applied = mutation.apply(base_epoch)?;
569 if self
570 .epoch
571 .compare_exchange(
572 base_epoch.get(),
573 new_epoch.get(),
574 Ordering::AcqRel,
575 Ordering::Acquire,
576 )
577 .is_err()
578 {
579 let _ = applied.abort();
580 return Err(MutationError::EpochConflict);
581 }
582 let pending = applied.publish(new_epoch);
583 if pending.receipt().tlb_obligation.is_complete() {
584 let published = pending.finish()?;
585 let receipt = published.retire();
586 #[cfg(test)]
587 {
588 *self.last_retired_receipt.lock() = Some(receipt.clone());
589 }
590 return Ok(receipt);
591 }
592
593 self.pending.lock().push(pending.into_receipt());
594 Err(MutationError::TlbPending)
595 }
596
597 pub fn pending_count(&self) -> usize {
599 self.pending.lock().len()
600 }
601
602 pub fn pending_requests(&self) -> Result<Vec<TlbRequest>, MutationError> {
603 let mut requests = Vec::new();
604 loop {
605 let required = self.pending.lock().len();
606 requests
607 .try_reserve_exact(required)
608 .map_err(|_| MutationError::ResourceExhausted)?;
609 let pending = self.pending.lock();
610 if pending.len() > requests.capacity() {
611 continue;
612 }
613 requests.extend(pending.iter().map(|receipt| receipt.tlb_obligation.clone()));
614 return Ok(requests);
615 }
616 }
617
618 pub fn pending_request(&self, space_id: AddressSpaceId, epoch: VmEpoch) -> Option<TlbRequest> {
622 self.pending
623 .lock()
624 .iter()
625 .find(|receipt| {
626 receipt.tlb_obligation.space_id == space_id && receipt.new_epoch == epoch
627 })
628 .map(|receipt| receipt.tlb_obligation.clone())
629 }
630
631 pub fn acknowledge(
634 &self,
635 space_id: AddressSpaceId,
636 epoch: VmEpoch,
637 cpu: usize,
638 ) -> Result<Option<MutationReceipt>, MutationError> {
639 let mut pending = self.pending.lock();
640 let Some(index) = pending.iter().position(|receipt| {
641 receipt.tlb_obligation.space_id == space_id && receipt.new_epoch == epoch
642 }) else {
643 return Err(MutationError::WrongState);
644 };
645 let receipt = &mut pending[index];
646 if !receipt.tlb_obligation.acknowledge(cpu) {
647 return Err(MutationError::TlbPending);
648 }
649 if !receipt.tlb_obligation.is_complete() {
650 return Ok(None);
651 }
652 let mut receipt = pending.swap_remove(index);
653 receipt.state = MutationState::Retired;
654 receipt
655 .events
656 .push(PublishEvent::MappingRetired { space_id, epoch })
657 .expect("a receipt emits one retirement event");
658 #[cfg(test)]
659 {
660 *self.last_retired_receipt.lock() = Some(receipt.clone());
661 }
662 Ok(Some(receipt))
663 }
664}
665
666#[derive(Debug, Clone)]
667pub struct MutationReceipt {
668 pub base_epoch: VmEpoch,
669 pub new_epoch: VmEpoch,
670 pub vma_delta: VmaDelta,
671 pub pte_delta: PteDelta,
672 pub mapping_delta: MappingDelta,
673 pub resident_delta: ResidentDelta,
674 pub tlb_obligation: TlbRequest,
675 events: InlineVec<PublishEvent, MAX_PUBLISH_EVENTS>,
676 state: MutationState,
677}
678
679impl MutationReceipt {
680 pub fn state(&self) -> MutationState {
681 self.state
682 }
683
684 pub fn events(&self) -> &[PublishEvent] {
685 &self.events
686 }
687
688 pub fn space_id(&self) -> AddressSpaceId {
689 self.tlb_obligation.space_id
690 }
691
692 pub fn tlb_pending(&self) -> usize {
693 self.tlb_obligation.pending()
694 }
695}
696
697pub struct PreparedMutation {
700 receipt: MutationReceipt,
701 active_targets: Option<Arc<AtomicUsize>>,
702 precondition: MutationPrecondition,
703}
704
705impl PreparedMutation {
706 pub fn new(space_id: AddressSpaceId, base_epoch: VmEpoch, targets: usize) -> Self {
707 Self {
708 receipt: MutationReceipt {
709 base_epoch,
710 new_epoch: base_epoch,
711 vma_delta: VmaDelta::default(),
712 pte_delta: PteDelta::default(),
713 mapping_delta: MappingDelta::default(),
714 resident_delta: ResidentDelta::default(),
715 tlb_obligation: TlbRequest::new(space_id, base_epoch, targets),
716 events: InlineVec::new(),
717 state: MutationState::Prepared,
718 },
719 active_targets: None,
720 precondition: MutationPrecondition::None,
721 }
722 }
723
724 fn new_with_active_targets(
725 space_id: AddressSpaceId,
726 base_epoch: VmEpoch,
727 active_targets: Arc<AtomicUsize>,
728 ) -> Self {
729 let targets = active_targets.load(Ordering::Acquire);
730 let mut mutation = Self::new(space_id, base_epoch, targets);
731 mutation.active_targets = Some(active_targets);
732 mutation
733 }
734
735 fn freeze_active_targets(&mut self) {
746 if let Some(active_targets) = self.active_targets.take() {
747 self.receipt.tlb_obligation.targets = active_targets.load(Ordering::Acquire);
748 }
749 }
750
751 pub fn receipt(&self) -> &MutationReceipt {
752 &self.receipt
753 }
754
755 pub fn set_vma_delta(&mut self, delta: VmaDelta) {
758 self.receipt.vma_delta = delta;
759 }
760
761 pub fn set_pte_delta(&mut self, delta: PteDelta) {
762 self.receipt.pte_delta = delta;
763 }
764
765 pub fn set_mapping_delta(&mut self, delta: MappingDelta) {
766 self.receipt.mapping_delta = delta;
767 }
768
769 pub fn set_resident_delta(&mut self, delta: ResidentDelta) {
770 self.receipt.resident_delta = delta;
771 }
772
773 pub fn add_tlb_range(&mut self, range: TlbRange) {
774 self.receipt.tlb_obligation.add_range(range);
775 }
776
777 pub fn try_reserve_tlb_ranges(&mut self, additional: usize) -> Result<(), MutationError> {
778 let request = &mut self.receipt.tlb_obligation;
779 if !request.ranges_collapsed_to_full_flush
780 && request.ranges.len().saturating_add(additional) > MAX_INLINE_TLB_RANGES
781 {
782 request.ranges.clear();
783 request.ranges_collapsed_to_full_flush = true;
784 }
785 Ok(())
786 }
787
788 pub fn try_add_tlb_range(&mut self, range: TlbRange) -> Result<(), MutationError> {
789 self.receipt.tlb_obligation.add_range(range);
790 Ok(())
791 }
792
793 pub fn apply(mut self, current_epoch: VmEpoch) -> Result<AppliedMutation, MutationError> {
794 if self.receipt.state != MutationState::Prepared {
795 return Err(MutationError::WrongState);
796 }
797 if current_epoch != self.receipt.base_epoch {
798 self.receipt.state = MutationState::Aborted;
799 return Err(MutationError::EpochConflict);
800 }
801 self.receipt.state = MutationState::Applied;
802 Ok(AppliedMutation {
803 receipt: self.receipt,
804 })
805 }
806}
807
808pub struct AppliedMutation {
809 receipt: MutationReceipt,
810}
811
812impl AppliedMutation {
813 pub fn publish(mut self, new_epoch: VmEpoch) -> PublishedPendingTlb {
814 self.receipt.new_epoch = new_epoch;
815 self.receipt.tlb_obligation.epoch = new_epoch;
816 self.receipt
817 .events
818 .push(PublishEvent::MappingPublished {
819 space_id: self.receipt.tlb_obligation.space_id,
820 epoch: new_epoch,
821 })
822 .expect("a receipt emits one publication event");
823 self.receipt.state = MutationState::PublishedPendingTlb;
824 PublishedPendingTlb {
825 receipt: self.receipt,
826 }
827 }
828
829 pub fn abort(mut self) -> Result<(), MutationError> {
830 self.receipt.state = MutationState::Aborted;
831 Ok(())
832 }
833}
834
835pub struct PublishedPendingTlb {
836 receipt: MutationReceipt,
837}
838
839impl PublishedPendingTlb {
840 pub fn receipt(&self) -> &MutationReceipt {
841 &self.receipt
842 }
843
844 pub fn acknowledge(mut self, cpu: usize) -> Result<Self, MutationError> {
845 if !self.receipt.tlb_obligation.acknowledge(cpu) {
846 return Err(MutationError::TlbPending);
847 }
848 Ok(self)
849 }
850
851 pub fn finish(mut self) -> Result<PublishedMutation, MutationError> {
852 if !self.receipt.tlb_obligation.is_complete() {
853 return Err(MutationError::TlbPending);
854 }
855 self.receipt.state = MutationState::Published;
856 Ok(PublishedMutation {
857 receipt: self.receipt,
858 })
859 }
860
861 pub(crate) fn into_receipt(self) -> MutationReceipt {
862 self.receipt
863 }
864}
865
866pub struct PublishedMutation {
867 receipt: MutationReceipt,
868}
869
870impl PublishedMutation {
871 pub fn receipt(&self) -> &MutationReceipt {
872 &self.receipt
873 }
874
875 pub fn retire(mut self) -> MutationReceipt {
876 self.receipt.state = MutationState::Retired;
877 self.receipt
878 .events
879 .push(PublishEvent::MappingRetired {
880 space_id: self.receipt.tlb_obligation.space_id,
881 epoch: self.receipt.new_epoch,
882 })
883 .expect("a receipt emits one retirement event");
884 self.receipt
885 }
886}
887
888#[cfg(test)]
889mod tests {
890 use super::*;
891
892 #[cfg_attr(axtest, axtest::axtest)]
893 #[cfg_attr(not(axtest), test)]
894 fn publish_cannot_retire_before_all_tlb_acks() {
895 let id = AddressSpaceId::allocate();
896 let prepared = PreparedMutation::new(id, VmEpoch::new(4), 0b11);
897 let applied = prepared.apply(VmEpoch::new(4)).unwrap();
898 let pending = applied.publish(VmEpoch::new(5));
899 assert!(!pending.receipt().tlb_obligation.is_complete());
900 let pending = pending.acknowledge(0).unwrap();
901 assert!(!pending.receipt().tlb_obligation.is_complete());
902 let pending = pending.acknowledge(1).unwrap();
903 assert!(pending.receipt().tlb_obligation.is_complete());
904 let published = pending.finish().unwrap();
905 assert_eq!(published.receipt().state(), MutationState::Published);
906 assert_eq!(published.retire().state(), MutationState::Retired);
907 }
908
909 #[cfg_attr(axtest, axtest::axtest)]
910 #[cfg_attr(not(axtest), test)]
911 fn stale_epoch_aborts_without_publication() {
912 let prepared = PreparedMutation::new(AddressSpaceId::allocate(), VmEpoch::new(7), 0);
913 assert!(matches!(
914 prepared.apply(VmEpoch::new(8)),
915 Err(MutationError::EpochConflict)
916 ));
917 }
918
919 #[cfg_attr(axtest, axtest::axtest)]
920 #[cfg_attr(not(axtest), test)]
921 fn quarantine_releases_frame_only_after_acknowledgement() {
922 let space_id = AddressSpaceId::allocate();
923 let epoch = VmEpoch::new(3);
924 let request = TlbRequest::new(space_id, epoch, 0b11);
925 let quarantine = TlbQuarantine::default();
926 quarantine
927 .defer(
928 FrameLease::new(ax_memory_addr::PhysAddr::from_usize(0x2000)),
929 request,
930 )
931 .expect("quarantine insertion must retain frame ownership");
932 assert_eq!(quarantine.pending(), 1);
933 assert!(
934 quarantine
935 .acknowledge(space_id, epoch, 0)
936 .unwrap()
937 .is_empty()
938 );
939 let released = quarantine.acknowledge(space_id, epoch, 1).unwrap();
940 assert_eq!(released.len(), 1);
941 assert_eq!(
942 released[0].paddr(),
943 ax_memory_addr::PhysAddr::from_usize(0x2000)
944 );
945 assert_eq!(quarantine.pending(), 0);
946 }
947
948 #[cfg_attr(axtest, axtest::axtest)]
949 #[cfg_attr(not(axtest), test)]
950 fn quarantine_does_not_retain_local_flushes() {
951 let quarantine = TlbQuarantine::default();
952 quarantine
953 .defer(
954 FrameLease::new(ax_memory_addr::PhysAddr::from_usize(0x3000)),
955 TlbRequest::new(AddressSpaceId::allocate(), VmEpoch::new(1), 0),
956 )
957 .expect("completed local flush needs no queue allocation");
958 assert_eq!(quarantine.pending(), 0);
959 assert!(quarantine.requests().unwrap().is_empty());
960 }
961
962 #[cfg_attr(axtest, axtest::axtest)]
963 #[cfg_attr(not(axtest), test)]
964 fn gate_retains_pending_receipt_until_last_ack() {
965 let gate = MutationGate::new();
966 let id = AddressSpaceId::allocate();
967 let mutation = gate.begin(id, 0b11);
968 assert_eq!(
969 gate.commit(mutation).unwrap_err(),
970 MutationError::TlbPending
971 );
972 assert_eq!(gate.pending_count(), 1);
973 assert!(gate.acknowledge(id, VmEpoch::new(1), 0).unwrap().is_none());
974 let receipt = gate
975 .acknowledge(id, VmEpoch::new(1), 1)
976 .unwrap()
977 .expect("last acknowledgement retires receipt");
978 assert_eq!(receipt.state(), MutationState::Retired);
979 assert_eq!(gate.pending_count(), 0);
980 }
981
982 #[cfg_attr(axtest, axtest::axtest)]
983 #[cfg_attr(not(axtest), test)]
984 fn commit_freezes_cpus_activated_after_prepare() {
985 let gate = MutationGate::new();
986 let id = AddressSpaceId::allocate();
987 let active_targets = Arc::new(AtomicUsize::new(0b0001));
988 let mutation = gate.begin_with_active_targets(id, active_targets.clone());
989
990 active_targets.fetch_or(0b0100, Ordering::Release);
993
994 assert_eq!(
995 gate.commit(mutation).unwrap_err(),
996 MutationError::TlbPending
997 );
998 let request = gate
999 .pending_request(id, VmEpoch::new(1))
1000 .expect("published mutation retains its shootdown request");
1001 assert_eq!(request.targets(), 0b0101);
1002 }
1003
1004 #[cfg_attr(axtest, axtest::axtest)]
1005 #[cfg_attr(not(axtest), test)]
1006 fn oversized_tlb_range_batch_falls_back_to_full_flush() {
1007 let gate = MutationGate::new();
1008 let id = AddressSpaceId::allocate();
1009 let mut mutation = gate.begin(id, 1);
1010 for index in 0..=MAX_INLINE_TLB_RANGES {
1011 mutation.add_tlb_range(
1012 TlbRange::new(VirtAddr::from_usize(index * 0x1000), 0x1000).unwrap(),
1013 );
1014 }
1015
1016 assert_eq!(
1017 gate.commit(mutation).unwrap_err(),
1018 MutationError::TlbPending
1019 );
1020 let request = gate
1021 .pending_request(id, VmEpoch::new(1))
1022 .expect("published mutation retains its shootdown request");
1023 assert!(
1024 request.ranges.is_empty(),
1025 "an overfull inline batch must conservatively request a full flush"
1026 );
1027 }
1028
1029 #[cfg_attr(axtest, axtest::axtest)]
1030 #[cfg_attr(not(axtest), test)]
1031 fn prepared_failure_does_not_publish_or_advance_epoch() {
1032 let gate = MutationGate::new();
1033 let id = AddressSpaceId::allocate();
1034 gate.fail_next_commit_before_publish();
1035
1036 assert_eq!(
1037 gate.commit(gate.begin(id, 0)).unwrap_err(),
1038 MutationError::ResourceExhausted
1039 );
1040 assert_eq!(gate.current_epoch(), VmEpoch::new(0));
1041 assert_eq!(gate.pending_count(), 0);
1042 assert!(gate.last_retired_receipt().is_none());
1043
1044 let receipt = gate
1045 .commit(gate.begin(id, 0))
1046 .expect("a later prepared mutation remains publishable");
1047 assert_eq!(receipt.state(), MutationState::Retired);
1048 assert_eq!(gate.current_epoch(), VmEpoch::new(1));
1049 let recorded = gate
1050 .last_retired_receipt()
1051 .expect("retired receipt remains observable to the test hook");
1052 assert_eq!(recorded.state(), receipt.state());
1053 assert_eq!(recorded.new_epoch, receipt.new_epoch);
1054 }
1055
1056 #[cfg_attr(axtest, axtest::axtest)]
1057 #[cfg_attr(not(axtest), test)]
1058 fn quarantine_retry_ignores_unrelated_acknowledgements() {
1059 let space_id = AddressSpaceId::allocate();
1060 let epoch = VmEpoch::new(9);
1061 let quarantine = TlbQuarantine::default();
1062 quarantine
1063 .defer(
1064 FrameLease::new(ax_memory_addr::PhysAddr::from_usize(0x4000)),
1065 TlbRequest::new(space_id, epoch, 1usize << 3),
1066 )
1067 .expect("quarantine insertion must retain frame ownership");
1068
1069 assert!(
1070 quarantine
1071 .acknowledge(space_id, epoch, 2)
1072 .unwrap()
1073 .is_empty()
1074 );
1075 assert_eq!(quarantine.pending(), 1);
1076 assert!(
1077 quarantine
1078 .acknowledge(space_id, VmEpoch::new(10), 3)
1079 .unwrap()
1080 .is_empty()
1081 );
1082 assert_eq!(quarantine.pending(), 1);
1083
1084 let released = quarantine.acknowledge(space_id, epoch, 3).unwrap();
1085 assert_eq!(released.len(), 1);
1086 assert_eq!(
1087 released[0].paddr(),
1088 ax_memory_addr::PhysAddr::from_usize(0x4000)
1089 );
1090 assert_eq!(quarantine.pending(), 0);
1091 }
1092}