Skip to main content

ostd/mm/
vm_space.rs

1// SPDX-License-Identifier: MPL-2.0
2//! Virtual memory space management.
3//!
4//! The [`VmSpace`] struct is provided to manage the virtual memory space of a
5//! user. Cursors are used to traverse and modify over the virtual memory space
6//! concurrently. The VM space cursor [`self::Cursor`] is just a wrapper over
7//! the page table cursor, providing efficient, powerful concurrent accesses
8//! to the page table.
9use vstd::{pervasive::arbitrary, prelude::*, vpanic};
10use vstd_extra::{assert, assert_eq, ghost_tree::*, panic::may_panic, prelude::*};
11
12use crate::specs::{
13    arch::*,
14    mm::{
15        frame::{
16            frame_specs::FrameRawPerms,
17            mapping::{
18                group_page_meta, lemma_meta_to_paddr_biinjective, lemma_paddr_to_meta_biinjective,
19            },
20            meta_owners::MetaSlotStorage,
21            meta_region_owners::MetaRegionOwners,
22        },
23        io::VmIoOwner,
24        page_table::{cursor::owners::CursorOwner, *},
25        tlb::TlbModel,
26        virt_mem::{MemView, VirtPtr},
27    },
28    task::InAtomicMode,
29};
30
31use crate::arch::mm::{PageTableEntry, PagingConsts, current_page_table_paddr};
32use crate::cpu::{AtomicCpuSet, CpuSet};
33use crate::error::Error;
34use crate::mm::frame::meta::mapping::meta_to_frame;
35use crate::mm::frame::untyped::UFrame;
36use crate::mm::frame::{Frame, MetaSlot};
37use crate::mm::kspace::KERNEL_PAGE_TABLE;
38use crate::mm::kspace::KernelPtConfig;
39use crate::mm::page_table::*;
40use crate::mm::tlb::*;
41use crate::mm::{
42    KERNEL_VADDR_RANGE,
43    page_table::{EntryOwner, PageTableFrag, PageTableGuard},
44};
45use crate::mm::{
46    MAX_USERSPACE_VADDR, Paddr, PagingConstsTrait, PagingLevel, Vaddr,
47    io::{Fallible, VmReader, VmWriter},
48    page_prop::PageProperty,
49};
50use crate::sync::RoArc;
51use alloc::sync::Arc;
52use alloc::vec::Vec;
53use core::marker::PhantomData;
54use core::{ops::Range, sync::atomic::Ordering};
55
56#[path = "../../specs/mm/vm_space.rs"]
57pub mod vm_space_specs;
58use vm_space_specs::*;
59
60verus! {
61
62/// A virtual address space for user-mode tasks, enabling safe manipulation of user-space memory.
63///
64/// The [`VmSpace`] type provides memory isolation guarantees between user-space and
65/// kernel-space. For example, given an arbitrary user-space pointer, one can read and
66/// write the memory location referred to by the user-space pointer without the risk of
67/// breaking the memory safety of the kernel space.
68///
69/// # Task Association Semantics
70///
71/// As far as OSTD is concerned, a [`VmSpace`] is not necessarily associated with a task. Once a
72/// [`VmSpace`] is activated (see [`VmSpace::activate`]), it remains activated until another
73/// [`VmSpace`] is activated **possibly by another task running on the same CPU**.
74///
75/// This means that it's up to the kernel to ensure that a task's [`VmSpace`] is always activated
76/// while the task is running. This can be done by using the injected post schedule handler
77/// (see [`inject_post_schedule_handler`]) to always activate the correct [`VmSpace`] after each
78/// context switch.
79///
80/// If the kernel otherwise decides not to ensure that the running task's [`VmSpace`] is always
81/// activated, the kernel must deal with race conditions when calling methods that require the
82/// [`VmSpace`] to be activated, e.g., [`UserMode::execute`], [`VmReader`] and [`VmWriter`].
83/// Otherwise, the behavior is unspecified, though it's guaranteed _not_ to compromise the kernel's
84/// memory safety.
85///
86/// # Memory Backing
87///
88/// A newly-created [`VmSpace`] is not backed by any physical memory pages. To
89/// provide memory pages for a [`VmSpace`], one can allocate and map physical
90/// memory ([`UFrame`]s) to the [`VmSpace`] using the cursor.
91///
92/// A [`VmSpace`] can also attach a page fault handler, which will be invoked to
93/// handle page faults generated from user space.
94///
95/// [`inject_post_schedule_handler`]: crate::task::inject_post_schedule_handler
96/// [`UserMode::execute`]: crate::user::UserMode::execute
97/// [`VmReader`]: crate::mm::io::VmWriter
98/// [`VmReader`]: crate::mm::io::VmReader
99/// # Verification Design
100///
101/// A [`VmSpace`] has a corresponding [`VmSpaceOwner`] object that is used to track its state,
102/// and against which its invariants are stated. The [`VmSpaceOwner`] catalogues the readers and writers
103/// that are associated with the [`VmSpace`], and the [`MemView`] which encodes the active page table and
104/// the subset of the TLB that covers the same virtual address space.
105/// All proofs about the correctness of the readers and writers are founded on the well-formedness of the [`MemView`]:
106///
107/// ```rust
108/// open spec fn mem_view_wf(self) -> bool {
109///    &&& self.mem_view is Some <==> self.mv_range@ is Some
110///    // This requires that TotalMapping (mvv) = mv ∪ writer mappings ∪ reader mappings
111///    &&& self.mem_view matches Some(remaining_view)
112///        ==> self.mv_range@ matches Some(total_view)
113///        ==> {
114///        &&& mappings_disjoint_vaddrs(remaining_view.mappings)
115///        &&& mappings_disjoint_vaddrs(total_view.mappings)
116///        // ======================
117///        // Remaining Consistency
118///        // ======================
119///        &&& remaining_view.mappings.subset_of(total_view.mappings)
120///        &&& remaining_view.memory.dom().subset_of(
121///            total_view.memory.dom(),
122///        )
123///        // =====================
124///        // Total View Consistency
125///        // =====================
126///        &&& forall|va: usize|
127///            remaining_view.addr_transl(va) == total_view.addr_transl(
128///                va,
129///            )
130///        // =====================
131///        // Writer correctness
132///        // =====================
133///        &&& forall|i: int|
134///            0 <= i < self.writers.len() ==> {
135///                &&& self.writers[i].inv()
136///            }
137///        }
138///    }
139/// }
140/// ```
141pub struct VmSpace<'a> {
142    pub pt: PageTable<UserPtConfig>,
143    pub cpus: AtomicCpuSet,
144    pub _marker: PhantomData<&'a ()>,
145}
146
147type Result<A> = core::result::Result<A, Error>;
148
149#[verus_verify]
150impl<'a> VmSpace<'a> {
151    #[inline]
152    #[verus_spec(r =>
153        with
154            Tracked(regions): Tracked<&mut MetaRegionOwners>,
155            Tracked(guards): Tracked<&mut Guards>,
156        requires
157            old(regions).inv(),
158    )]
159    #[allow(private_interfaces)]
160    pub fn default<'rcu>() -> Self {
161        proof_with!(Tracked(regions), Tracked(guards));
162        Self::new()
163    }
164
165    /// Creates a new VM address space.
166    ///
167    /// This allocates a new user page table by duplicating the kernel page
168    /// table's top-level entries, and returns a [`VmSpace`] that wraps it.
169    ///
170    /// # Verified Properties
171    /// ## Preconditions
172    /// - **Safety Invariants**: The meta-region invariants must hold.
173    /// ## Postconditions
174    /// - The returned [`VmSpace`] instance satisfies the invariants of [`VmSpace`].
175    #[inline]
176    #[verus_spec(r =>
177        with
178            Tracked(regions): Tracked<&mut MetaRegionOwners>,
179            Tracked(guards): Tracked<&mut Guards>,
180        requires
181            old(regions).inv(),
182        ensures
183            final(regions).inv(),
184    )]
185    #[allow(private_interfaces)]
186    pub fn new<'rcu>() -> Self {
187        proof_decl! {
188            let tracked mut kernel_owner_opt: Option<&PageTableOwner<KernelPtConfig>> = None;
189        }
190        let kpt = {
191            #[verus_spec(with Tracked(&mut kernel_owner_opt), Tracked(regions), Tracked(guards))]
192            crate::mm::kspace::kvirt_area::get_kernel_page_table()
193        };
194        proof_decl! {
195            let tracked kernel_owner = kernel_owner_opt.tracked_take();
196        }
197        let pt = {
198            #[verus_spec(with Tracked(kernel_owner), Tracked(regions), Tracked(guards))]
199            kpt.create_user_page_table::<crate::specs::task::AnyAtomicGuard>()
200        };
201        Self { pt, cpus: AtomicCpuSet::new(CpuSet::new_empty()), _marker: PhantomData }
202    }
203
204    /// Gets an immutable cursor in the virtual address range.
205    ///
206    /// The cursor behaves like a lock guard, exclusively owning a sub-tree of
207    /// the page table, preventing others from creating a cursor in it. So be
208    /// sure to drop the cursor as soon as possible.
209    ///
210    /// The creation of the cursor may block if another cursor having an
211    /// overlapping range is alive.
212    ///
213    /// # Verified Properties
214    /// ## Preconditions
215    /// - **Safety Invariants**: The page table owner must be valid.
216    /// ## Postconditions
217    /// - When the virtual address range satisfies
218    ///   [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
219    ///   the result is `Ok` and a [`CursorOwner`] is returned.
220    #[verus_spec(r =>
221        with
222            Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
223            Ghost(root_guard): Ghost<PageTableGuard<'a, UserPtConfig>>,
224            Tracked(regions): Tracked<&mut MetaRegionOwners>,
225            Tracked(guards): Tracked<&mut Guards>,
226                -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
227        requires
228            self.pt.relates_owner(owner, *old(regions)),
229            owner.0.value().node().relate_guard(root_guard),
230            va.end > 0,
231        ensures
232            crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(*va) ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),
233            // On the success branch, the returned cursor owner satisfies
234            // its invariant. Follows from the underlying PT::cursor's
235            // ensures: r is Ok ⇒ cursor_new_success_conditions (by
236            // contrapositive of the !cond ⇒ Err clause) ⇒ invariants
237            // hold ⇒ cursor_owner.inv().
238            cursor_owner@ matches Some(c) ==> c.inv(),
239    )]
240    pub fn cursor<G: InAtomicMode>(&'a self, guard: &'a G, va: &Range<Vaddr>) -> Result<
241        Cursor<'a, G>,
242    > {
243        proof_decl! {
244            let tracked mut out_owner: Option<CursorOwner<'a, UserPtConfig>>;
245        }
246        proof {
247            lemma_vaddr_range_spec_user();
248            lemma_page_size_spec_values();
249        }
250        match {
251            #[verus_spec(with Tracked(owner), Ghost(root_guard), Tracked(regions), Tracked(guards))]
252            self.pt.cursor(guard, va)
253        } {
254            Ok((pt_cursor, tracked_owner)) => {
255                proof! { out_owner = Some::<CursorOwner<'a, UserPtConfig>>(tracked_owner.get()); }
256                proof_with!(|= Tracked(out_owner));
257                Ok(Cursor(pt_cursor))
258            },
259            Err(e) => {
260                proof! { out_owner = None; }
261                proof_with!(|= Tracked(out_owner));
262                Err(Error::AccessDenied)
263            },
264        }
265    }
266
267    /// Gets a mutable cursor in the virtual address range.
268    ///
269    /// The same as [`Self::cursor`], the cursor behaves like a lock guard,
270    /// exclusively owning a sub-tree of the page table, preventing others
271    /// from creating a cursor in it. So be sure to drop the cursor as soon as
272    /// possible.
273    ///
274    /// The creation of the cursor may block if another cursor having an
275    /// overlapping range is alive. The modification to the mapping by the
276    /// cursor may also block or be overridden by the mapping of another cursor.
277    ///
278    /// # Verified Properties
279    /// ## Preconditions
280    /// - **Safety Invariants**: The page table owner must be valid.
281    /// ## Postconditions
282    /// - When the virtual address range satisfies
283    ///   [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
284    ///   the result is `Ok` and a [`CursorOwner`] is returned.
285    #[verus_spec(r =>
286        with
287            Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
288            Ghost(root_guard): Ghost<PageTableGuard<'a, UserPtConfig>>,
289            Tracked(regions): Tracked<&mut MetaRegionOwners>,
290            Tracked(guards): Tracked<&mut Guards>
291                -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
292        requires
293            self.pt.relates_owner(owner, *old(regions)),
294            owner.0.value().node().relate_guard(root_guard),
295            va.end > 0,
296        ensures
297            crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(*va) ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),
298            // See `cursor` above for the derivation.
299            cursor_owner@ matches Some(c) ==> c.inv(),
300    )]
301    pub fn cursor_mut<G: InAtomicMode>(&'a self, guard: &'a G, va: &Range<Vaddr>) -> Result<
302        CursorMut<'a, G>,
303    > {
304        proof_decl! {
305            let tracked mut out_owner: Option<CursorOwner<'a, UserPtConfig>>;
306        }
307        proof {
308            lemma_vaddr_range_spec_user();
309            lemma_page_size_spec_values();
310        }
311        match {
312            #[verus_spec(with Tracked(owner), Ghost(root_guard), Tracked(regions), Tracked(guards))]
313            self.pt.cursor_mut(guard, va)
314        } {
315            Ok((pt_cursor, tracked_owner)) => {
316                proof! { out_owner = Some::<CursorOwner<'a, UserPtConfig>>(tracked_owner.get()); }
317                proof_with!(|= Tracked(out_owner));
318                Ok(CursorMut { pt_cursor, flusher: TlbFlusher::new(&self.cpus) })
319            },
320            Err(e) => {
321                proof! { out_owner = None; }
322                proof_with!(|= Tracked(out_owner));
323                Err(Error::AccessDenied)
324            },
325        }
326    }
327
328    /// Activates the page table on the current CPU.
329    #[verifier::external_body]
330    pub fn activate(this: &RoArc<Self>) {
331        // No support for CPU set semantics; skip now
332        // let preempt_guard = disable_preempt();
333        // let cpu = preempt_guard.current_cpu();
334        // let last_ptr = ACTIVATED_VM_SPACE.load();
335        // if last_ptr == Arc::as_ptr(self) {
336        //     return;
337        // }
338        // // Record ourselves in the CPU set and the activated VM space pointer.
339        // // `Acquire` to ensure the modification to the PT is visible by this CPU.
340        // self.cpus.add(cpu, Ordering::Acquire);
341        // let self_ptr = Arc::into_raw(Arc::clone(self)) as *mut VmSpace;
342        // ACTIVATED_VM_SPACE.store(self_ptr);
343        // if !last_ptr.is_null() {
344        //     // SAFETY: The pointer is cast from an `Arc` when it's activated
345        //     // the last time, so it can be restored and only restored once.
346        //     let last = unsafe { Arc::from_raw(last_ptr) };
347        //     last.cpus.remove(cpu, Ordering::Relaxed);
348        // }
349        // self.pt.activate();
350        unimplemented!()
351    }
352
353    /// Creates a reader to read data from the user space of the current task.
354    ///
355    /// Returns [`Err`] if this [`VmSpace`] is not the user space of the current task
356    /// or the `vaddr` and `len` do not represent a valid user space memory range.
357    ///
358    /// # Verified Properties
359    /// ## Preconditions
360    /// - The [`VmSpaceOwner`] invariant must hold.
361    /// ## Postconditions
362    /// - When [`Self::reader_success_cond`] holds, the result is `Ok`.
363    /// - On success, the [`VmReader`] and its [`VmIoOwner`] are well-formed with no memory view.
364    /// ## Safety
365    /// - The function does not interact with the lower-level memory system directly.
366    ///   By checking that the target (user) page table is not the active (kernel) one,
367    ///   we ensure that the resulting reader cannot interact with kernel memory.
368    #[inline]
369    #[verus_spec(r =>
370        with
371            Tracked(owner): Tracked<&'a mut VmSpaceOwner>,
372                -> reader_owner: Tracked<Option<VmIoOwner>>,
373        requires
374            old(owner).inv(),
375        ensures
376            final(owner).inv(),
377            self.reader_success_cond(vaddr, len) ==> r is Ok && reader_owner@ is Some,
378            r is Ok && reader_owner@ is Some ==> {
379                &&& r.unwrap().wf(reader_owner@->0)
380                &&& reader_owner@->0.mem_view is None
381                &&& reader_owner@->0.inv()
382            },
383            // Range bound is necessary for success: the body's `checked_add`
384            // guard rejects any out-of-range request before constructing a
385            // reader. `nat` arithmetic subsumes the overflow case.
386            r is Ok ==> (vaddr as nat) + (len as nat) <= MAX_USERSPACE_VADDR as nat,
387    )]
388    pub fn reader(&self, vaddr: Vaddr, len: usize) -> Result<VmReader<'a, Fallible>> {
389        if current_page_table_paddr() != self.pt.root_paddr() {
390            proof_with!(|= Tracked(None));
391            Err(Error::AccessDenied)
392        } else if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
393            proof_with!(|= Tracked(None));
394            Err(Error::AccessDenied)
395        } else {
396            let ghost id = owner.new_vm_io_id();
397            proof_decl! {
398                let tracked mut vm_reader_owner: VmIoOwner;
399            }
400
401            // SAFETY: The memory range is in user space, as checked above.
402            let reader = unsafe {
403                proof_with!(Ghost(id) => Tracked(vm_reader_owner));
404                VmReader::from_user_space(VirtPtr::from_vaddr(vaddr, len), len)
405            };
406
407            proof_with!(|= Tracked(Some(vm_reader_owner)));
408            Ok(reader)
409        }
410    }
411
412    /// Creates a writer to write data to the user space of the current task.
413    ///
414    /// Returns [`Err`] if this [`VmSpace`] is not the user space of the current task
415    /// or the `vaddr` and `len` do not represent a valid user space memory range.
416    ///
417    /// # Verified Properties
418    /// ## Preconditions
419    /// - The [`VmSpaceOwner`] invariant must hold.
420    /// ## Postconditions
421    /// - When [`Self::writer_success_cond`] holds, the result is `Ok`.
422    /// - On success, the [`VmWriter`] and its [`VmIoOwner`] are well-formed with no memory view.
423    /// ## Safety
424    /// - The function does not interact with the lower-level memory system directly.
425    ///   By checking that the target (user) page table is not the active (kernel) one,
426    ///   we ensure that the resulting writer cannot interact with kernel memory.
427    #[inline]
428    #[verus_spec(r =>
429        with
430            Tracked(owner): Tracked<&mut VmSpaceOwner>,
431                -> writer_owner: Tracked<Option<VmIoOwner>>,
432        requires
433            old(owner).inv(),
434        ensures
435            final(owner).inv(),
436            self.writer_success_cond(vaddr, len) ==> r is Ok && writer_owner@ is Some,
437            r is Ok && writer_owner@ is Some ==> {
438                &&& r.unwrap().wf(writer_owner@->0)
439                &&& writer_owner@->0.mem_view is None
440                &&& writer_owner@->0.inv()
441            },
442            // Range bound is necessary for success: see `reader` above.
443            r is Ok ==> (vaddr as nat) + (len as nat) <= MAX_USERSPACE_VADDR as nat,
444    )]
445    pub fn writer(self, vaddr: Vaddr, len: usize) -> Result<VmWriter<'a, Fallible>> {
446        if current_page_table_paddr() != self.pt.root_paddr() {
447            proof_with!(|= Tracked(None));
448            Err(Error::AccessDenied)
449        } else if vaddr.checked_add(len).unwrap_or(usize::MAX) > MAX_USERSPACE_VADDR {
450            proof_with!(|= Tracked(None));
451            Err(Error::AccessDenied)
452        } else {
453            let ghost id = owner.new_vm_io_id();
454            proof_decl! {
455                let tracked mut vm_writer_owner: VmIoOwner;
456            }
457
458            // SAFETY: The memory range is in user space, as checked above.
459            let reader = unsafe {
460                proof_with!(Ghost(id) => Tracked(vm_writer_owner));
461                VmWriter::from_user_space(VirtPtr::from_vaddr(vaddr, len), len)
462            };
463
464            proof_with!(|= Tracked(Some(vm_writer_owner)));
465            Ok(reader)
466        }
467    }
468}
469
470/// The cursor for querying over the VM space without modifying it.
471///
472/// It exclusively owns a sub-tree of the page table, preventing others from
473/// reading or modifying the same sub-tree. Two read-only cursors can not be
474/// created from the same virtual address range either.
475pub struct Cursor<'a, A: InAtomicMode>(pub crate::mm::page_table::Cursor<'a, UserPtConfig, A>);
476
477#[verus_verify]
478impl<'rcu, A: InAtomicMode> Cursor<'rcu, A> {
479    /// Queries the mapping at the current virtual address.
480    ///
481    /// If the cursor is pointing to a valid virtual address that is locked,
482    /// it will return the virtual address range and the mapped item.
483    /// ## Preconditions
484    /// - All system invariants must hold
485    /// - **Liveness**: The function will return an error if the cursor is not within the locked range
486    /// ## Postconditions
487    /// - If there is a mapped item at the current virtual address ([`query_some_condition`]),
488    /// it is returned along with the virtual address range that it maps ([`query_success_ensures`]).
489    /// - The mapping that is returned corresponds to the abstract mapping given by [`query_item_spec`](CursorView::query_item_spec).
490    /// - If there is no mapped item at the current virtual address ([`query_none_condition`]),
491    /// it returns [`None`], and the virtual address range of the cursor's current position.
492    /// ## Safety
493    /// - This function preserves all memory invariants.
494    /// - The locking mechanism prevents data races.
495    #[verus_spec(r =>
496        with
497            Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
498            Tracked(regions): Tracked<&mut MetaRegionOwners>,
499            Tracked(guards): Tracked<&mut Guards>,
500        requires
501            old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
502            // Out-of-range is a graceful `Err`; the sole panic is cloning
503            // the resolved leaf frame when *that specific slot* is
504            // saturated — precisely propagated from
505            // `Cursor::query_panic_condition`.
506            old(self).0.query_panic_condition(*old(owner), *old(regions)) ==> may_panic(),
507        ensures
508            final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
509            !old(self).0.query_panic_condition(*old(owner), *old(regions)),
510            old(owner).in_barrier_va() ==> r is Ok,
511            r matches Ok(state) ==>
512                final(self).0.query_some_condition(*final(owner)) ==>
513                final(self).0.query_some_ensures(*final(owner), state),
514            r matches Ok(state) ==>
515                !final(self).0.query_some_condition(*final(owner)) ==>
516                final(self).0.query_none_ensures(*final(owner), state),
517            old(owner)@.mappings == final(owner)@.mappings,
518    )]
519    pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<MappedItem>)> {
520        Ok(
521            #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
522            self.0.query()?,
523        )
524    }
525
526    /// Moves the cursor forward to the next mapped virtual address.
527    ///
528    /// If there is mapped virtual address following the current address within
529    /// next `len` bytes, it will return that mapped address. In this case,
530    /// the cursor will stop at the mapped address.
531    ///
532    /// Otherwise, it will return `None`. And the cursor may stop at any
533    /// address after `len` bytes.
534    ///
535    /// # Verified Properties
536    /// ## Preconditions
537    /// - **Safety Invariants**: The page table cursor safety invariants
538    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
539    /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
540    /// ## Postconditions
541    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
542    /// - **Correctness**: If there is a mapped address after the current address within the next `len` bytes,
543    /// it will move the cursor to the next mapped address and return it.
544    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
545    /// ## Panics
546    /// This method panics if the length is longer than the remaining range of the cursor.
547    /// ## Safety
548    /// This function preserves all memory invariants.
549    /// Because it panics rather than move the cursor to an invalid address,
550    /// it ensures that the cursor is safe to use after the call.
551    #[verus_spec(res =>
552        with
553            Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
554            Tracked(regions): Tracked<&mut MetaRegionOwners>,
555            Tracked(guards): Tracked<&mut Guards>,
556        requires
557            old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
558            old(self).0.find_next_panic_condition(len) ==> may_panic(),
559        ensures
560            !old(self).0.find_next_panic_condition(len),
561            final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
562            res is Some ==> {
563                &&& res->0 == final(self).0.va
564                &&& final(owner).level <= final(owner).guard_level
565                &&& final(owner).in_barrier_va()
566            },
567    )]
568    pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
569        #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
570        self.0.find_next(len)
571    }
572
573    // [FIXED] BUG FOUND BY FV: missing panic documentation. https://github.com/asterinas/asterinas/pull/3007
574    /// Jumps to the virtual address.
575    ///
576    /// If the target address is out of the range, this method will return `Err`.
577    ///
578    /// # Panics
579    ///
580    /// This method panics if the address has bad alignment.
581    ///
582    /// # Verified Properties
583    /// ## Preconditions
584    /// - **Safety Invariants**: The page table cursor safety invariants
585    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
586    /// - **Liveness**:  The function will panic if the target `va` is not aligned
587    /// to the base page size.
588    /// ## Postconditions
589    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
590    /// - **Correctness**: If the target `va` is within the cursor's locked range,
591    /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
592    /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
593    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
594    /// ## Safety
595    /// This function preserves all memory invariants.
596    /// Because it throws an error rather than move the cursor to an invalid address,
597    /// it ensures that the cursor is safe to use after the call.
598    /// The locking mechanism prevents data races.
599    #[verus_spec(res =>
600        with
601            Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
602            Tracked(regions): Tracked<&mut MetaRegionOwners>,
603            Tracked(guards): Tracked<&mut Guards>,
604        requires
605            old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
606            // `CursorMut::jump` diverges on a misaligned `va` and may panic
607            // in its `pop_level` repositioning ascent.
608            old(self).0.jump_panic_condition(va) ==> may_panic(),
609        ensures
610            !old(self).0.jump_panic_condition(va),
611            final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
612            final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end ==> {
613                &&& res is Ok
614                &&& final(self).0.va == va
615            },
616            !(final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end) ==> res is Err,
617    )]
618    pub fn jump(&mut self, va: Vaddr) -> Result<()> {
619        (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
620        self.0.jump(va))?;
621        Ok(())
622    }
623
624    /// Get the virtual address of the current slot.
625    #[verus_spec(
626        returns
627            self.0.va,
628    )]
629    pub fn virt_addr(&self) -> Vaddr {
630        self.0.virt_addr()
631    }
632}
633
634/// The cursor for modifying the mappings in VM space.
635///
636/// It exclusively owns a sub-tree of the page table, preventing others from
637/// reading or modifying the same sub-tree.
638pub struct CursorMut<'a, A: InAtomicMode> {
639    pub pt_cursor: crate::mm::page_table::CursorMut<'a, UserPtConfig, A>,
640    // We have a read lock so the CPU set in the flusher is always a superset
641    // of actual activated CPUs.
642    pub flusher: TlbFlusher<'a  /*, DisabledPreemptGuard*/ >,
643}
644
645#[verus_verify]
646impl<'a, A: InAtomicMode> CursorMut<'a, A> {
647    /// Queries the mapping at the current virtual address.
648    ///
649    /// This is the same as [`Cursor::query`].
650    ///
651    /// If the cursor is pointing to a valid virtual address that is locked,
652    /// it will return the virtual address range and the mapped item.
653    /// ## Preconditions
654    /// - **Safety Invariants**: The page table cursor safety invariants
655    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
656    /// ## Postconditions
657    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
658    /// - **Correctness**: If the cursor is within the locked range, the result is `Ok`.
659    /// - **Correctness**: If there is a mapped item at the current virtual address ([`query_some_condition`]),
660    /// it is returned along with the virtual address range that it maps ([`query_success_ensures`]).
661    /// - **Correctness**: The mapping that is returned corresponds to the abstract mapping given by [`query_item_spec`](CursorView::query_item_spec).
662    /// - **Correctness**: If there is no mapped item at the current virtual address ([`query_none_condition`]),
663    /// it returns `None`, and the virtual address range of the cursor's current position.
664    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
665    /// - **Safety**: The mappings in the page table are not affected.
666    /// - **Safety**: The soundness of individual entries are not affected.
667    /// ## Safety
668    /// - This function preserves all memory invariants.
669    /// - The locking mechanism prevents data races.
670    #[verus_spec(res =>
671        with
672            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
673            Tracked(regions): Tracked<&mut MetaRegionOwners>,
674            Tracked(guards): Tracked<&mut Guards>,
675        requires
676            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
677            // Out-of-range → graceful `Err`; the sole panic is cloning the
678            // resolved leaf frame when *that specific slot* is saturated —
679            // precisely propagated from `Cursor::query_panic_condition`.
680            old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions))
681                ==> may_panic(),
682        ensures
683            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
684            !old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions)),
685            old(owner).in_barrier_va() ==> res is Ok,
686            res matches Ok(state) ==>
687                final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
688                final(self).pt_cursor.0.query_some_ensures(*final(owner), state),
689            res matches Ok(state) ==>
690                !final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
691                final(self).pt_cursor.0.query_none_ensures(*final(owner), state),
692            old(owner)@.mappings == final(owner)@.mappings,
693    )]
694    pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<MappedItem>)> {
695        Ok(
696            #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
697            self.pt_cursor.query()?,
698        )
699    }
700
701    /// Moves the cursor forward to the next mapped virtual address.
702    ///
703    /// This is the same as [`Cursor::find_next`].
704    ///
705    /// # Verified Properties
706    /// ## Preconditions
707    /// - **Safety Invariants**: The page table cursor safety invariants
708    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
709    /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
710    /// ## Postconditions
711    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
712    /// - **Correctness**: If there is a mapped address after the current address within the next `len` bytes,
713    /// it will move the cursor to the next mapped address and return it.
714    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
715    /// ## Panics
716    /// This method panics if the length is longer than the remaining range of the cursor.
717    /// ## Safety
718    /// This function preserves all memory invariants.
719    /// Because it panics rather than move the cursor to an invalid address,
720    /// it ensures that the cursor is safe to use after the call.
721    #[verus_spec(res =>
722        with
723            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
724            Tracked(regions): Tracked<&mut MetaRegionOwners>,
725            Tracked(guards): Tracked<&mut Guards>,
726        requires
727            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
728            old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
729        ensures
730            !old(self).pt_cursor.0.find_next_panic_condition(len),
731            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
732            res is Some ==> {
733                &&& res->0 == final(self).pt_cursor.0.va
734                &&& final(owner).level <= final(owner).guard_level
735                &&& final(owner).in_barrier_va()
736            },
737    )]
738    pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
739        #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
740        self.pt_cursor.find_next(len)
741    }
742
743    // [FIXED] BUG FOUND BY FV: missing panic documentation. https://github.com/asterinas/asterinas/pull/3007
744    /// Jump to the virtual address.
745    ///
746    /// This is the same as [`Cursor::jump`].
747    ///
748    /// # Panics
749    ///
750    /// This method panics if the address has bad alignment.
751    ///
752    /// # Verified Properties
753    /// ## Preconditions
754    /// - **Safety Invariants**: The page table cursor safety invariants
755    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
756    /// - **Liveness**:  The function will panic if the target `va` is not aligned
757    /// to the base page size.
758    /// ## Postconditions
759    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
760    /// - **Correctness**: If the target `va` is within the cursor's locked range,
761    /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
762    /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
763    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
764    /// ## Panics
765    /// This method panics if the target address is not aligned to the page size.
766    /// ## Safety
767    /// This function preserves all memory invariants.
768    /// Because it throws an error rather than move the cursor to an invalid address,
769    /// it ensures that the cursor is safe to use after the call.
770    /// The locking mechanism prevents data races.
771    #[verus_spec(res =>
772        with
773            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
774            Tracked(regions): Tracked<&mut MetaRegionOwners>,
775            Tracked(guards): Tracked<&mut Guards>
776        requires
777            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
778            // `CursorMut::jump` diverges on a misaligned `va` and may panic
779            // in its `pop_level` repositioning ascent.
780            old(self).pt_cursor.0.jump_panic_condition(va) ==> may_panic(),
781        ensures
782            !old(self).pt_cursor.0.jump_panic_condition(va),
783            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
784            final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end ==> {
785                &&& res is Ok
786                &&& final(self).pt_cursor.0.va == va
787            },
788            !(final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end) ==> res is Err,
789    )]
790    pub fn jump(&mut self, va: Vaddr) -> Result<()> {
791        (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
792        self.pt_cursor.jump(va))?;
793        Ok(())
794    }
795
796    /// Get the virtual address of the current slot.
797    #[verus_spec(r =>
798        returns
799            self.pt_cursor.0.va,
800    )]
801    pub fn virt_addr(&self) -> Vaddr {
802        self.pt_cursor.virt_addr()
803    }
804
805    /// Get the dedicated TLB flusher for this cursor.
806    #[verus_spec(ret =>
807        ensures
808            *ret == old(self).flusher,
809            *final(ret) == final(self).flusher,
810    )]
811    pub fn flusher(&mut self) -> &mut TlbFlusher<'a> {
812        &mut self.flusher
813    }
814
815    /// Map a frame into the current slot.
816    ///
817    /// This method will bring the cursor to the next slot after the modification.
818    /// If there is an existing mapping at the current slot, it will be replaced
819    /// and the TLB will be flushed for that entry.
820    /// # Verified Properties
821    /// ## Preconditions
822    /// - **Safety Invariants**: The page table cursor safety invariants
823    ///   ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the TLB invariant
824    ///   ([`TlbModel::inv`]) must hold before the call.
825    /// - **Liveness**: The cursor must not be past the end of its locked range,
826    ///   and the frame's level must fit within the remaining range, or the function will panic.
827    /// - **Bookkeeping**: The frame must be well-formed with respect to its entry owner
828    ///   ([`item_wf`](Self::item_wf)).
829    /// ## Postconditions
830    /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
831    /// - **Correctness**: The page table view is updated with the new mapping
832    ///   according to [`map_item_ensures`](Self::map_item_ensures).
833    /// - **Correctness**: If the metadata region was well-formed before the call
834    ///   and the frame was not already mapped, it will be well-formed after.
835    /// ## Safety
836    /// - For soundness purposes, it doesn't matter if a frame is mapped multiple times
837    /// in the same page table. There is still a clear definition of the behavior.
838    #[verus_spec(
839        with
840            Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
841            Tracked(entry_owner): Tracked<EntryOwner<UserPtConfig>>,
842            Tracked(regions): Tracked<&mut MetaRegionOwners>,
843            Tracked(guards): Tracked<&mut Guards>,
844            Tracked(tlb_model): Tracked<&mut TlbModel>,
845        requires
846            old(tlb_model).inv(),
847            old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
848            old(self).item_wf(frame, prop, entry_owner, *old(regions)),
849            old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }) ==> may_panic(),
850        ensures
851            !old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }),
852            final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
853            old(self).map_item_ensures(
854                frame,
855                prop,
856                old(cursor_owner)@,
857                final(cursor_owner)@,
858            ),
859    )]
860    pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
861        let start_va = self.virt_addr();
862        let item = MappedItem { frame: frame, prop: prop };
863
864        // SAFETY: It is safe to map untyped memory into the userspace.
865        let Err(frag) = (unsafe {
866            #[verus_spec(with Tracked(cursor_owner), Tracked(entry_owner), Tracked(regions), Tracked(guards))]
867            self.pt_cursor.map(item)
868        }) else {
869            return;  // No mapping exists at the current address.
870        };
871
872        match frag {
873            PageTableFrag::Mapped { va, item } => {
874                //debug_assert_eq!(va, start_va);
875                let old_frame = item.frame;
876
877                #[verus_spec(with Tracked(tlb_model))]
878                self.flusher.issue_tlb_flush_with(
879                    TlbFlushOp::Address(start_va),
880                    old_frame.into_dyn(),
881                );
882                #[verus_spec(with Tracked(tlb_model))]
883                self.flusher.dispatch_tlb_flush();
884            },
885            PageTableFrag::StrayPageTable { .. } => {
886                assert(false) by {
887                    assert(UserPtConfig::item_into_raw(item).1 == 1);
888                };
889                #[cfg(feature = "allow_panic")]
890                vpanic!("`UFrame` is base page sized but re-mapping out a child PT");
891            },
892        }
893    }
894
895    /// Clears the mapping starting from the current slot,
896    /// and returns the number of unmapped pages.
897    ///
898    /// This method will bring the cursor forward by `len` bytes in the virtual
899    /// address space after the modification.
900    ///
901    /// Already-absent mappings encountered by the cursor will be skipped. It
902    /// is valid to unmap a range that is not mapped.
903    ///
904    /// It must issue and dispatch a TLB flush after the operation. Otherwise,
905    /// the memory safety will be compromised. Please call this function less
906    /// to avoid the overhead of TLB flush. Using a large `len` is wiser than
907    /// splitting the operation into multiple small ones.
908    ///
909    /// # Verified Properties
910    /// ## Preconditions
911    /// - **Safety Invariants**: The page table cursor safety invariants
912    /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
913    /// - **Safety Invariants**: The TLB invariant ([TlbModel::inv]) must hold.
914    /// - **Liveness**: In order to avoid a panic, the length must be page-aligned and less than or equal to the remaining range of the cursor.
915    /// ## Postconditions
916    /// - **Safety Invariants**: The page table cursor safety invariants are preserved.
917    /// - **Safety Invariants**: The TLB invariant is preserved.
918    /// - **Correctness**: Unmaps a range of virtual addresses from the current address up to `len` bytes
919    /// and returns the number of mappings that were removed.
920    /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
921    /// ## Panics
922    /// Panics if:
923    ///  - the length is longer than the remaining range of the cursor;
924    ///  - the length is not page-aligned.
925    /// ## Safety
926    /// - It is always sound to unmap pages. We flush unmapped pages from the TLB to ensure consistency.
927    /// - TODO: formalizing and proving that this function preserves TLB consistency would
928    /// be pretty straightforward and would be a nice addition to the correctness properties.
929    #[verus_spec(r =>
930        with
931            Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
932            Tracked(regions): Tracked<&mut MetaRegionOwners>,
933            Tracked(guards): Tracked<&mut Guards>,
934            Tracked(tlb_model): Tracked<&mut TlbModel>,
935        requires
936            // Large unmaps may clone a removed subtree root before deferred reclamation.
937            len >= crate::mm::page_size::<PagingConsts>(2) ==> may_panic(),
938            old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
939            old(tlb_model).inv(),
940            old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
941        ensures
942            !old(self).pt_cursor.0.find_next_panic_condition(len),
943            final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
944            old(cursor_owner)@.unmap_spec(len, final(cursor_owner)@, r),
945            final(tlb_model).inv(),
946    )]
947    #[verifier::spinoff_prover]
948    pub fn unmap(&mut self, len: usize) -> usize {
949        proof {
950            cursor_owner.view_preserves_inv();
951        }
952
953        assert_eq!(len % PAGE_SIZE, 0);
954
955        // [KNOWN] BUG FOUND BY FV: `self.va + len` could overflow. For now assume that it doesn't. https://github.com/asterinas/asterinas/issues/3159
956        assume(self.pt_cursor.0.va + len <= usize::MAX);
957
958        assert!(self.virt_addr() + len <= self.pt_cursor.0.barrier_va.end);
959
960        assert(!self.pt_cursor.0.find_next_panic_condition(len));
961
962        let end_va = self.virt_addr() + len;
963        let mut num_unmapped: usize = 0;
964
965        let ghost start_va: Vaddr = cursor_owner@.cur_va;
966        // The "adjusted base" accumulates splits: starts as the split-at-boundaries
967        // version of start_mappings and gets updated when take_next splits huge pages.
968        let ghost mut adjusted_base: Set<Mapping> = cursor_owner@.mappings;
969        // Track the set of removed mappings explicitly (not as a VA range filter).
970        let ghost mut removed: Set<Mapping> = Set::empty();
971
972        proof {
973            lemma_vaddr_range_spec_user();
974            assert((self.pt_cursor.0.va + len) % PAGE_SIZE as int == 0) by (compute);
975        }
976
977        #[verus_spec(
978            invariant
979                len >= crate::mm::page_size::<PagingConsts>(2) ==> may_panic(),
980                end_va == start_va + len,
981                self.pt_cursor.0.va % PAGE_SIZE == 0,
982                end_va % PAGE_SIZE == 0,
983                end_va <= 0x0000_8000_0000_0000usize,
984                self.pt_cursor.0.invariants(*cursor_owner, *regions, *guards),
985                end_va <= self.pt_cursor.0.barrier_va.end,
986                tlb_model.inv(),
987                start_va <= cursor_owner@.cur_va,
988                // Split-aware invariant: adjusted_base tracks accumulated splits,
989                // removed tracks the explicitly removed set.
990                cursor_owner@.mappings == adjusted_base.difference(removed),
991                removed.subset_of(adjusted_base),
992                num_unmapped as nat == removed.len(),
993                crate::specs::mm::page_table::mapping_set_lemmas::wf_mapping_set(adjusted_base),
994                // Everything removed is in the [start, end) range.
995                forall |m: Mapping| #[trigger] removed.contains(m) ==>
996                    start_va <= m.va_range.start < end_va,
997                // Per-config VA bound: every removed mapping fits within the
998                // user VA space, sourced from the cursor view prior to removal.
999                forall |m: Mapping| #[trigger] removed.contains(m) ==>
1000                    m.va_range.end <= page_table_managed_end_spec::<UserPtConfig>(),
1001                // Nothing in [start_va, end_va) with start < cursor_va remains,
1002                // unless it is a sub-mapping of a boundary-straddling entry.
1003                forall |m: Mapping| #![auto] adjusted_base.contains(m) && !removed.contains(m)
1004                    && start_va <= m.va_range.start && m.va_range.start < end_va ==>
1005                    m.va_range.start >= cursor_owner@.cur_va
1006                    || exists |parent: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(parent)
1007                        && parent.va_range.start < start_va
1008                        && parent.va_range.start <= m.va_range.start
1009                        && m.va_range.end <= parent.va_range.end
1010                        && m.pa_range.start == (parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1011                        && m.property == parent.property,
1012                start_va == old(cursor_owner)@.cur_va,
1013                old(cursor_owner)@.inv(),
1014                // Locality: old mappings fully outside [start, end) survive in adjusted_base.
1015                // (Straddling mappings may be split — see refinement.)
1016                forall |m: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(m)
1017                    && (m.va_range.end <= start_va || m.va_range.start >= end_va)
1018                    ==> #[trigger] adjusted_base.contains(m),
1019                // Refinement: every mapping in adjusted_base is either from the old view
1020                // or a sub-mapping of an old entry (from boundary splits).
1021                forall |m: Mapping| #[trigger] adjusted_base.contains(m) ==>
1022                    old(cursor_owner)@.mappings.contains(m)
1023                    || exists |parent: Mapping| #[trigger] old(cursor_owner)@.mappings.contains(parent)
1024                        && parent.va_range.start <= m.va_range.start
1025                        && m.va_range.end <= parent.va_range.end
1026                        && m.pa_range.start == (parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1027                        && m.property == parent.property,
1028            invariant_except_break
1029                self.pt_cursor.0.va <= end_va,
1030            ensures
1031                self.pt_cursor.0.va >= end_va,
1032            decreases end_va - self.pt_cursor.0.va
1033        )]
1034        loop {
1035            let ghost prev_va: Vaddr = cursor_owner@.cur_va;
1036            let ghost prev_mappings: Set<Mapping> = cursor_owner@.mappings;
1037
1038            proof {
1039                cursor_owner.view_preserves_inv();
1040                // Per-config VA bound on prev_mappings — needed for
1041                // preserving the `removed`-end-bound loop invariant. The user
1042                // cursor's view lies in [0, 0x8000_0000_0000) by the PROVEN
1043                // `lemma_view_in_vaddr_range_user` (no longer the generic
1044                // axiom), which follows from `cursor_owner.inv()`'s isolation
1045                // (borrowed-kernel-half) clause.
1046                crate::specs::mm::page_table::cursor::owners::lemma_view_in_vaddr_range_user(
1047                    cursor_owner,
1048                );
1049                lemma_vaddr_range_spec_user();
1050            }
1051
1052            // SAFETY: It is safe to un-map memory in the userspace.
1053            let Some(frag) = (unsafe {
1054                #[verus_spec(with Tracked(cursor_owner), Tracked(regions), Tracked(guards))]
1055                self.pt_cursor.take_next(end_va - self.virt_addr())
1056            }) else {
1057                proof {
1058                    // At break: take_next returned None, so no mappings in [prev_va, end_va).
1059                    // Any m with start >= prev_va leads to contradiction via the empty filter.
1060                    assert forall|m: Mapping|
1061                        #![auto]
1062                        adjusted_base.contains(m) && !removed.contains(m) && start_va
1063                            <= m.va_range.start && m.va_range.start
1064                            < end_va implies m.va_range.start >= cursor_owner@.cur_va || exists|
1065                        parent: Mapping,
1066                    | #[trigger]
1067                        old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1068                            < start_va && parent.va_range.start <= m.va_range.start
1069                            && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1070                        parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1071                            && m.property == parent.property by {
1072                        if m.va_range.start >= prev_va {
1073                            assert(prev_mappings.filter(
1074                                |m2: Mapping| prev_va <= m2.va_range.start < end_va,
1075                            ).contains(m));
1076                            assert(false);
1077                        }
1078                    };
1079                }
1080                break;
1081            };
1082
1083            let ghost old_adjusted = adjusted_base;
1084            let ghost old_removed = removed;
1085
1086            let ghost frag_ghost = frag;
1087
1088            match frag {
1089                PageTableFrag::Mapped { va, item, .. } => {
1090                    let frame = item.frame;
1091                    proof {
1092                        lemma_vaddr_range_spec_user();
1093                        // `wf_mapping_set(removed)` from the wf adjusted_base
1094                        // via subset; `va_range.end <= 2^47` for every removed
1095                        // mapping is a loop invariant. Together they give
1096                        // |removed| < usize::MAX, so num_unmapped + 1 fits.
1097                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1098                            adjusted_base,
1099                            removed,
1100                        );
1101                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1102                        removed);
1103                    }
1104                    num_unmapped += 1;
1105                    #[verus_spec(with Tracked(tlb_model))]
1106                    self.flusher.issue_tlb_flush_with(TlbFlushOp::Address(va), frame.into_dyn());
1107                },
1108                PageTableFrag::StrayPageTable { pt, va, len, num_frames } => {
1109                    proof {
1110                        let ghost new_removed = old_removed.union(
1111                            prev_mappings.filter(
1112                                |m2: Mapping|
1113                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1114                                        < frag_ghost->StrayPageTable_va
1115                                        + frag_ghost->StrayPageTable_len,
1116                            ),
1117                        );
1118                        assert(new_removed.subset_of(old_adjusted)) by {
1119                            assert forall|m: Mapping|
1120                                new_removed.contains(m) implies old_adjusted.contains(m) by {
1121                                if prev_mappings.contains(m) {
1122                                    // m ∈ prev_mappings = old_adjusted \ old_removed ⊆ old_adjusted.
1123                                }
1124                            };
1125                        };
1126                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1127                            old_adjusted,
1128                            new_removed,
1129                        );
1130                        lemma_vaddr_range_spec_user();
1131                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1132                        new_removed);
1133                        // |new_removed| = |old_removed| + |subtree| (disjoint).
1134                        assert(old_removed.disjoint(
1135                            prev_mappings.filter(
1136                                |m2: Mapping|
1137                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1138                                        < frag_ghost->StrayPageTable_va
1139                                        + frag_ghost->StrayPageTable_len,
1140                            ),
1141                        )) by {
1142                            assert forall|m: Mapping|
1143                                old_removed.contains(m) implies !prev_mappings.filter(
1144                                |m2: Mapping|
1145                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1146                                        < frag_ghost->StrayPageTable_va
1147                                        + frag_ghost->StrayPageTable_len,
1148                            ).contains(m) by {
1149                                assert(!prev_mappings.contains(m));
1150                            };
1151                        };
1152                        vstd::set_lib::lemma_set_disjoint_lens(
1153                            old_removed,
1154                            prev_mappings.filter(
1155                                |m2: Mapping|
1156                                    frag_ghost->StrayPageTable_va <= m2.va_range.start
1157                                        < frag_ghost->StrayPageTable_va
1158                                        + frag_ghost->StrayPageTable_len,
1159                            ),
1160                        );
1161                    }
1162                    num_unmapped += num_frames;
1163                    proof {
1164                        assert(0x0000_8000_0000_0000usize < KERNEL_VADDR_RANGE.end as usize)
1165                            by (compute_only);
1166                        crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_va_plus_page_size_no_overflow(
1167                        va, len);
1168                    }
1169                    #[verus_spec(with Tracked(tlb_model))]
1170                    self.flusher.issue_tlb_flush_with(TlbFlushOp::Range(va..va + len), pt);
1171                },
1172            }
1173
1174            proof {
1175                let ghost mm = match frag_ghost {
1176                    PageTableFrag::Mapped { va: fv, item: fi, .. } => CursorView::<
1177                        UserPtConfig,
1178                    >::item_into_mapping(fv, fi),
1179                    _ => arbitrary(),
1180                };
1181                let ghost sv = CursorView::<UserPtConfig> {
1182                    cur_va: match frag_ghost {
1183                        PageTableFrag::Mapped { va: fv, .. } => fv as Vaddr,
1184                        _ => 0,
1185                    },
1186                    mappings: prev_mappings,
1187                    phantom: PhantomData,
1188                };
1189                let ghost sm = sv.split_while_huge(mm.page_size).mappings;
1190                let ghost is_mapped = frag_ghost is Mapped;
1191                let ghost subtree = match frag_ghost {
1192                    PageTableFrag::StrayPageTable { va: fv, len: fl, .. } => prev_mappings.filter(
1193                        |m2: Mapping| fv <= m2.va_range.start < fv + fl,
1194                    ),
1195                    _ => Set::empty(),
1196                };
1197
1198                // Update ghost tracking variables.
1199                match frag_ghost {
1200                    PageTableFrag::StrayPageTable { .. } => {
1201                        removed = old_removed.union(subtree);
1202                    },
1203                    PageTableFrag::Mapped { .. } => {
1204                        adjusted_base = sm.union(old_removed);
1205                        removed = old_removed.union(set![mm]);
1206                    },
1207                }
1208
1209                // Mapped-case setup: establish split_while_huge properties once.
1210                if is_mapped {
1211                    assert forall|m: Mapping, x: Mapping| #[trigger]
1212                        prev_mappings.contains(m) && #[trigger] old_removed.contains(
1213                            x,
1214                        ) implies Mapping::disjoint_vaddrs(m, x) by {
1215                        assert(old_adjusted.contains(m));
1216                        assert(old_adjusted.contains(x));
1217                    };
1218                    sv.split_while_huge_disjoint(mm.page_size, old_removed);
1219                    sv.lemma_split_while_huge_preserves_inv(mm.page_size);
1220                }
1221                assert forall|m: Mapping| #[trigger] removed.contains(m) implies m.va_range.end
1222                    <= page_table_managed_end_spec::<UserPtConfig>() by {
1223                    if !old_removed.contains(m) {
1224                        if is_mapped {
1225                            assert(m == mm);
1226                            sv.split_while_huge_refinement(mm.page_size, mm);
1227                            if !prev_mappings.contains(mm) {
1228                                let parent = choose|p: Mapping| #[trigger]
1229                                    prev_mappings.contains(p) && p.va_range.start
1230                                        <= mm.va_range.start && mm.va_range.end <= p.va_range.end
1231                                        && mm.pa_range.start == (p.pa_range.start + (
1232                                    mm.va_range.start - p.va_range.start)) as Paddr && mm.property
1233                                        == p.property;
1234                            }
1235                        } else {
1236                            assert(prev_mappings.contains(m));
1237                        }
1238                    }
1239                };
1240
1241                // Prove |removed| tracking (disjointness + cardinality).
1242
1243                match frag_ghost {
1244                    PageTableFrag::StrayPageTable { .. } => {
1245                        assert(removed == old_removed + subtree);
1246                    },
1247                    PageTableFrag::Mapped { .. } => {
1248                        assert(old_removed.disjoint(set![mm])) by {
1249                            assert forall|e: Mapping| #[trigger]
1250                                old_removed.contains(e) implies !set![mm].contains(e) by {};
1251                        };
1252                        vstd::set_lib::lemma_set_disjoint_lens(old_removed, set![mm]);
1253                        assert(removed == old_removed + set![mm]);
1254                        vstd::set_lib::lemma_set_empty_equivalency_len(Set::<Mapping>::empty());
1255                        vstd::set::lemma_set_insert_len(Set::<Mapping>::empty(), mm);
1256                    },
1257                }
1258
1259                // Maintain wf_mapping_set(adjusted_base) — only changes in Mapped case.
1260                if is_mapped {
1261                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1262                        old_adjusted,
1263                        old_removed,
1264                    );
1265                    assert forall|m: Mapping, n: Mapping| #[trigger]
1266                        sm.contains(m) && #[trigger] old_removed.contains(n) implies m.va_range.end
1267                        <= n.va_range.start || n.va_range.end <= m.va_range.start by {
1268                        sv.split_while_huge_refinement(mm.page_size, m);
1269                        assert(!prev_mappings.contains(n));
1270                        if prev_mappings.contains(m) {
1271                        } else {
1272                            let p = choose|p: Mapping| #[trigger]
1273                                prev_mappings.contains(p) && p.va_range.start <= m.va_range.start
1274                                    && m.va_range.end <= p.va_range.end && m.pa_range.start == (
1275                                p.pa_range.start + (m.va_range.start - p.va_range.start)) as Paddr
1276                                    && m.property == p.property;
1277                            assert(old_adjusted.contains(p));
1278                        }
1279                    };
1280                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_union(
1281                        sm,
1282                        old_removed,
1283                    );
1284                }
1285                // Maintain mappings == adjusted_base \ removed.
1286
1287                assert forall|e: Mapping| #[trigger]
1288                    adjusted_base.difference(removed).contains(e)
1289                        <==> cursor_owner@.mappings.contains(e) by {};
1290
1291                assert(removed.subset_of(adjusted_base)) by {
1292                    assert forall|e: Mapping| #[trigger]
1293                        removed.contains(e) implies adjusted_base.contains(e) by {};
1294                };
1295
1296                // Maintain: not-yet-removed mappings in [start, end) are either
1297                // ahead of the cursor or sub-mappings of a boundary-straddling parent.
1298                assert forall|m: Mapping|
1299                    #![auto]
1300                    adjusted_base.contains(m) && !removed.contains(m) && start_va
1301                        <= m.va_range.start && m.va_range.start < end_va implies m.va_range.start
1302                    >= cursor_owner@.cur_va || exists|parent: Mapping| #[trigger]
1303                    old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start < start_va
1304                        && parent.va_range.start <= m.va_range.start && m.va_range.end
1305                        <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1306                    m.va_range.start - parent.va_range.start)) as Paddr && m.property
1307                        == parent.property by {
1308                    if m.va_range.start < cursor_owner@.cur_va {
1309                        if m.va_range.start >= prev_va {
1310                            // m was just processed — contradiction via empty filtered sets.
1311                            match frag_ghost {
1312                                PageTableFrag::StrayPageTable { va: frag_va, .. } => {
1313                                    if m.va_range.start >= frag_va {
1314                                        assert(cursor_owner@.mappings.filter(
1315                                            |m2: Mapping|
1316                                                frag_va <= m2.va_range.start < self.pt_cursor.0.va,
1317                                        ).contains(m));
1318                                    } else {
1319                                        assert(prev_mappings.filter(
1320                                            |m2: Mapping| prev_va <= m2.va_range.start < frag_va,
1321                                        ).contains(m));
1322                                    }
1323                                },
1324                                PageTableFrag::Mapped { va: frag_va, .. } => {
1325                                    if m.va_range.start >= (frag_va as usize) {
1326                                        assert(cursor_owner@.mappings.filter(
1327                                            |m2: Mapping|
1328                                                (frag_va as usize) <= m2.va_range.start
1329                                                    < self.pt_cursor.0.va,
1330                                        ).contains(m));
1331                                    } else {
1332                                        assert(prev_mappings.filter(
1333                                            |m2: Mapping|
1334                                                prev_va <= m2.va_range.start < (frag_va as usize),
1335                                        ).contains(m));
1336                                    }
1337                                },
1338                            }
1339                            assert(false);
1340                        } else if is_mapped {
1341                            // m.start < prev_va, m ∈ sm \ old_removed.
1342                            assert(sm.contains(m));
1343                            sv.split_while_huge_refinement(mm.page_size, m);
1344                            if prev_mappings.contains(m) {
1345                                // m ∈ old_adjusted \ old_removed — previous invariant applies directly.
1346                            } else {
1347                                // m is a sub-mapping of some p ∈ prev_mappings.
1348                                let p = choose|p: Mapping| #[trigger]
1349                                    prev_mappings.contains(p) && p.va_range.start
1350                                        <= m.va_range.start && m.va_range.end <= p.va_range.end
1351                                        && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1352                                        - p.va_range.start)) as Paddr && m.property == p.property;
1353                                assert(old_adjusted.contains(p) && !old_removed.contains(p));
1354                                if p.va_range.start < start_va {
1355                                    // p itself or its ancestor is the boundary parent.
1356                                    if !old(cursor_owner)@.mappings.contains(p) {
1357                                        let orig = choose|orig: Mapping| #[trigger]
1358                                            old(cursor_owner)@.mappings.contains(orig)
1359                                                && orig.va_range.start <= p.va_range.start
1360                                                && p.va_range.end <= orig.va_range.end
1361                                                && p.pa_range.start == (orig.pa_range.start + (
1362                                            p.va_range.start - orig.va_range.start)) as Paddr
1363                                                && p.property == orig.property;
1364                                        assert(orig.inv());
1365                                        assert(m.inv());
1366                                        crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1367                                        m, p, orig);
1368                                    }
1369                                } else if p.va_range.start >= end_va {
1370                                    assert(false);  // p.start <= m.start < end_va, contradiction.
1371                                } else {
1372                                    // start_va <= p.start < end_va, p.start < prev_va.
1373                                    // Previous invariant gives boundary ancestor orig.
1374                                    let orig = choose|orig: Mapping| #[trigger]
1375                                        old(cursor_owner)@.mappings.contains(orig)
1376                                            && orig.va_range.start < start_va && orig.va_range.start
1377                                            <= p.va_range.start && p.va_range.end
1378                                            <= orig.va_range.end && p.pa_range.start == (
1379                                        orig.pa_range.start + (p.va_range.start
1380                                            - orig.va_range.start)) as Paddr && p.property
1381                                            == orig.property;
1382                                    assert(orig.inv());
1383                                    assert(m.inv());
1384                                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1385                                    m, p, orig);
1386                                }
1387                            }
1388                        }
1389                    }
1390                };
1391
1392                // Maintain: old mappings outside [start, end) survive in adjusted_base.
1393                if is_mapped {
1394                    assert forall|m: Mapping|
1395                        old(cursor_owner)@.mappings.contains(m) && (m.va_range.end <= start_va
1396                            || m.va_range.start
1397                            >= end_va) implies #[trigger] adjusted_base.contains(m) by {
1398                        if m.va_range.end <= start_va {
1399                            assert(m.inv());
1400                        }
1401                        sv.split_while_huge_locality(mm.page_size, m);
1402                    };
1403
1404                    // Maintain: refinement — every mapping in adjusted_base comes from
1405                    // old mappings or is a sub-mapping of one.
1406                    assert forall|m: Mapping| #[trigger] adjusted_base.contains(m) implies old(
1407                        cursor_owner,
1408                    )@.mappings.contains(m) || exists|parent: Mapping| #[trigger]
1409                        old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1410                            <= m.va_range.start && m.va_range.end <= parent.va_range.end
1411                            && m.pa_range.start == (parent.pa_range.start + (m.va_range.start
1412                            - parent.va_range.start)) as Paddr && m.property == parent.property by {
1413                        if !old_removed.contains(m) {
1414                            sv.split_while_huge_refinement(mm.page_size, m);
1415                            if !prev_mappings.contains(m) {
1416                                let p = choose|p: Mapping| #[trigger]
1417                                    prev_mappings.contains(p) && p.va_range.start
1418                                        <= m.va_range.start && m.va_range.end <= p.va_range.end
1419                                        && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1420                                        - p.va_range.start)) as Paddr && m.property == p.property;
1421                                assert(old_adjusted.contains(p));
1422                                if !old(cursor_owner)@.mappings.contains(p) {
1423                                    let orig = choose|orig: Mapping| #[trigger]
1424                                        old(cursor_owner)@.mappings.contains(orig)
1425                                            && orig.va_range.start <= p.va_range.start
1426                                            && p.va_range.end <= orig.va_range.end
1427                                            && p.pa_range.start == (orig.pa_range.start + (
1428                                        p.va_range.start - orig.va_range.start)) as Paddr
1429                                            && p.property == orig.property;
1430                                    assert(orig.inv());
1431                                    assert(m.inv());
1432                                    crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1433                                    m, p, orig);
1434                                }
1435                            }
1436                        }
1437                    }
1438                };
1439            }
1440        }
1441        proof {
1442            let old_view = old(cursor_owner)@;
1443            let new_view = cursor_owner@;
1444
1445            // Bridge from loop invariant to unmap_spec.
1446            let start = old_view.cur_va;
1447            let end = (old_view.cur_va + len) as Vaddr;
1448
1449            assert forall|m: Mapping|
1450                #![auto]
1451                old_view.mappings.contains(m) && (m.va_range.end <= start || m.va_range.start
1452                    >= end) implies new_view.mappings.contains(m) by {
1453                assert(adjusted_base.contains(m));
1454                if m.va_range.end <= start {
1455                    assert(m.inv());
1456                }
1457            };
1458
1459            assert forall|m: Mapping|
1460                new_view.mappings.contains(m) && start <= m.va_range.start < end implies exists|
1461                parent: Mapping,
1462            | #[trigger]
1463                old_view.mappings.contains(parent) && parent.va_range.start < start
1464                    && parent.va_range.start <= m.va_range.start && m.va_range.end
1465                    <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1466                m.va_range.start - parent.va_range.start)) as Paddr && m.property
1467                    == parent.property by {};
1468
1469            assert forall|m: Mapping|
1470                new_view.mappings.contains(m) implies old_view.mappings.contains(m) || exists|
1471                parent: Mapping,
1472            | #[trigger]
1473                old_view.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
1474                    && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1475                parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1476                    && m.property == parent.property by {};
1477        }
1478
1479        #[verus_spec(with Tracked(tlb_model))]
1480        self.flusher.dispatch_tlb_flush();
1481
1482        num_unmapped
1483    }
1484
1485    /// Applies the operation to the next slot of mapping within the range.
1486    ///
1487    /// The range to be found in is the current virtual address with the
1488    /// provided length.
1489    ///
1490    /// The function stops and yields the actually protected range if it has
1491    /// actually protected a page, no matter if the following pages are also
1492    /// required to be protected.
1493    ///
1494    /// It also makes the cursor moves forward to the next page after the
1495    /// protected one. If no mapped pages exist in the following range, the
1496    /// cursor will stop at the end of the range and return [`None`].
1497    ///
1498    /// Note that it will **NOT** flush the TLB after the operation. Please
1499    /// make the decision yourself on when and how to flush the TLB using
1500    /// [`Self::flusher`].
1501    ///
1502    /// # Verified Properties
1503    /// ## Preconditions
1504    /// - **Safety Invariants**: The page table cursor safety invariants
1505    ///   ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the
1506    ///   meta-region invariants must hold before the call.
1507    /// - The cursor must be within the locked range and below the guard level.
1508    /// - The current entry must be a mapped frame (not absent or a page table node).
1509    /// - **Liveness**: The length must be page-aligned and within the remaining cursor range.
1510    /// ## Postconditions
1511    /// - **Correctness**: If the metadata region was well-formed before the call, it will be
1512    ///   well-formed after.
1513    /// ## Panics
1514    /// Panics if the length is longer than the remaining range of the cursor.
1515    /// ## Safety
1516    /// - From a soundness perspective changing a userspace page's `prop` field is safe.
1517    #[verus_spec(r =>
1518        with
1519            Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
1520            Tracked(regions): Tracked<&mut MetaRegionOwners>,
1521            Tracked(guards): Tracked<&mut Guards>,
1522        requires
1523            old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
1524            forall |p: PageProperty| op.requires((p,)),
1525            // POTENTIALLY UNSOUND PATCH: trackedness preservation. For UserPtConfig
1526            // this is trivially true (tracked is constant). See `Entry::protect`.
1527            forall |pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty,
1528                perm: Tracked<Option<<UserPtConfig as PageTableConfig>::Perm>>| #![auto]
1529                op.ensures((p_in,), p_out) ==> (
1530                    UserPtConfig::item_into_raw(UserPtConfig::item_from_raw(
1531                        pa, level, p_out, perm,
1532                    )).3@ is Some
1533                ) == (perm@ is Some),
1534            forall |pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty| #![auto]
1535                op.ensures((p_in,), p_out)
1536                    && <PageTableEntry as PageTableEntryTrait>::new_page_req(pa, level, p_in) ==>
1537                        <PageTableEntry as PageTableEntryTrait>::new_page_req(pa, level, p_out),
1538
1539            old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
1540        ensures
1541            !old(self).pt_cursor.0.find_next_panic_condition(len),
1542            final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
1543            final(self).pt_cursor.0.barrier_va == old(self).pt_cursor.0.barrier_va,
1544    )]
1545    pub fn protect_next(
1546        &mut self,
1547        len: usize,
1548        op: impl FnOnce(PageProperty) -> PageProperty,
1549    ) -> Option<Range<Vaddr>> {
1550        // SAFETY: It is safe to protect memory in the userspace.
1551        unsafe {
1552            #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
1553            self.pt_cursor.protect_next(len, op)
1554        }
1555    }
1556}
1557
1558/*cpu_local_cell! {
1559    /// The `Arc` pointer to the activated VM space on this CPU. If the pointer
1560    /// is NULL, it means that the activated page table is merely the kernel
1561    /// page table.
1562    // TODO: If we are enabling ASID, we need to maintain the TLB state of each
1563    // CPU, rather than merely the activated `VmSpace`. When ASID is enabled,
1564    // the non-active `VmSpace`s can still have their TLB entries in the CPU!
1565    static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null();
1566}*/
1567
1568/*#[cfg(ktest)]
1569pub(super) fn get_activated_vm_space() -> *const VmSpace {
1570    ACTIVATED_VM_SPACE.load()
1571}*/
1572
1573/// The configuration for user page tables.
1574#[verifier::allow(autoderive_clone_without_spec)]
1575#[derive(Clone, Debug)]
1576pub struct UserPtConfig {}
1577
1578/// The item that can be mapped into the [`VmSpace`].
1579pub struct MappedItem {
1580    pub frame: UFrame,
1581    pub prop: PageProperty,
1582}
1583
1584#[verus_verify]
1585impl RCClone for MappedItem {
1586    open spec fn clone_requires(self, perm: MetaRegionOwners) -> bool {
1587        self.frame.clone_requires(perm)
1588    }
1589
1590    open spec fn clone_ensures(
1591        self,
1592        old_perm: MetaRegionOwners,
1593        new_perm: MetaRegionOwners,
1594        res: Self,
1595    ) -> bool {
1596        &&& self.prop == res.prop
1597        &&& self.frame.clone_ensures(old_perm, new_perm, res.frame)
1598    }
1599
1600    fn clone(&self, Tracked(perm): Tracked<&mut MetaRegionOwners>) -> (res: Self) {
1601        let frame = self.frame.clone(Tracked(perm));
1602        Self { frame, prop: self.prop }
1603    }
1604}
1605
1606// SAFETY: `item_into_raw` and `item_from_raw` are implemented correctly,
1607unsafe impl PageTableConfig for UserPtConfig {
1608    open spec fn TOP_LEVEL_INDEX_RANGE_spec() -> Range<usize> {
1609        0..256
1610    }
1611
1612    open spec fn TOP_LEVEL_CAN_UNMAP_spec() -> (b: bool) {
1613        true
1614    }
1615
1616    fn TOP_LEVEL_INDEX_RANGE() -> Range<usize> {
1617        0..256
1618    }
1619
1620    fn TOP_LEVEL_CAN_UNMAP() -> (b: bool) {
1621        true
1622    }
1623
1624    type E = PageTableEntry;
1625
1626    type C = PagingConsts;
1627
1628    type Item = MappedItem;
1629
1630    type Perm = FrameRawPerms;
1631
1632    open spec fn perm_well_formed_with_region(
1633        pa: Paddr,
1634        perm: Tracked<Option<Self::Perm>>,
1635        regions: MetaRegionOwners,
1636    ) -> bool {
1637        perm@ is Some ==> {
1638            let idx = crate::specs::mm::frame::mapping::frame_to_index(pa);
1639            let frame_perm = perm@->0;
1640            &&& frame_perm.slot_perm == regions.slots[idx]
1641            &&& frame_perm.metadata_perm.frac() == 1
1642            &&& frame_perm.metadata_perm.id() == regions.slot_owners[idx].metadata_perm.id()
1643            &&& MetaSlot::perms_related(*frame_perm.slot_perm, frame_perm.metadata_perm.resource())
1644        }
1645    }
1646
1647    proof fn lemma_none_perm_well_formed(pa: Paddr, regions: MetaRegionOwners) {
1648    }
1649
1650    open spec fn item_into_raw_spec(item: Self::Item) -> (
1651        Paddr,
1652        PagingLevel,
1653        PageProperty,
1654        Tracked<Option<Self::Perm>>,
1655    ) {
1656        (
1657            item.frame.start_paddr_spec(),
1658            1,
1659            item.prop,
1660            Tracked(
1661                Some(
1662                    FrameRawPerms {
1663                        slot_perm: item.frame.tracked_slot_perm@,
1664                        metadata_perm: item.frame.tracked_metadata_perm@->0,
1665                    },
1666                ),
1667            ),
1668        )
1669    }
1670
1671    #[verifier::external_body]
1672    fn item_into_raw(item: Self::Item) -> (res: (
1673        Paddr,
1674        PagingLevel,
1675        PageProperty,
1676        Tracked<Option<Self::Perm>>,
1677    )) {
1678        proof_decl! {
1679            let tracked raw_perm: FrameRawPerms;
1680        }
1681        let MappedItem { frame, prop } = item;
1682        let level = frame.map_level();
1683        let paddr = frame.into_raw();
1684        proof_with!(=> Tracked(raw_perm));
1685        (paddr, level, prop, Tracked(Some(raw_perm)))
1686    }
1687
1688    open spec fn item_from_raw_spec(
1689        paddr: Paddr,
1690        _level: PagingLevel,
1691        prop: PageProperty,
1692        perm: Tracked<Option<Self::Perm>>,
1693    ) -> Self::Item {
1694        MappedItem {
1695            frame: UFrame {
1696                ptr: vstd::simple_pptr::PPtr(
1697                    crate::mm::frame::meta::mapping::frame_to_meta(paddr),
1698                    PhantomData,
1699                ),
1700                _marker: PhantomData,
1701                #[cfg(verus_keep_ghost_body)]
1702                tracked_slot_perm: Tracked((perm@->0).slot_perm),
1703                #[cfg(verus_keep_ghost_body)]
1704                tracked_metadata_perm: Tracked(Some((perm@->0).metadata_perm)),
1705            },
1706            prop,
1707        }
1708    }
1709
1710    #[verifier::external_body]
1711    unsafe fn item_from_raw(
1712        paddr: Paddr,
1713        level: PagingLevel,
1714        prop: PageProperty,
1715        Tracked(perm): Tracked<Option<Self::Perm>>,
1716    ) -> Self::Item {
1717        let tracked raw_perm = perm.tracked_unwrap();
1718        // SAFETY: The caller ensures safety.
1719        proof_with!(Tracked(raw_perm));
1720        let frame = unsafe { UFrame::from_raw(paddr) };
1721        MappedItem { frame, prop }
1722    }
1723
1724    proof fn lemma_item_into_raw_roundtrip(
1725        pa: Paddr,
1726        level: PagingLevel,
1727        prop: PageProperty,
1728        perm: Tracked<Option<Self::Perm>>,
1729    ) {
1730        broadcast use group_page_meta;
1731
1732        let item = Self::item_from_raw(pa, level, prop, perm);
1733        assert(Self::raw_item_well_formed((pa, level, prop, perm)));
1734        assert(item.frame.slot_vaddr() == crate::mm::frame::meta::mapping::frame_to_meta(pa));
1735        lemma_paddr_to_meta_biinjective(pa);
1736    }
1737
1738    proof fn lemma_item_from_raw_roundtrip(
1739        item: Self::Item,
1740        paddr: Paddr,
1741        level: PagingLevel,
1742        prop: PageProperty,
1743        perm: Tracked<Option<Self::Perm>>,
1744    ) {
1745        broadcast use group_page_meta;
1746
1747        assert(Self::item_well_formed(item));
1748        lemma_meta_to_paddr_biinjective(item.frame.slot_vaddr());
1749    }
1750
1751    open spec fn item_well_formed(item: Self::Item) -> bool {
1752        item.frame.inv()
1753    }
1754
1755    open spec fn raw_item_well_formed(
1756        item: (Paddr, PagingLevel, PageProperty, Tracked<Option<Self::Perm>>),
1757    ) -> bool {
1758        let (pa, level, _prop, perm) = item;
1759        &&& level == 1
1760        &&& perm@ is Some
1761        &&& (perm@->0).slot_perm.addr() == crate::mm::frame::meta::mapping::frame_to_meta(pa)
1762        &&& (perm@->0).slot_perm.is_init()
1763        &&& (perm@->0).metadata_perm.frac() == 1
1764        &&& MetaSlot::perms_related(*(perm@->0).slot_perm, (perm@->0).metadata_perm.resource())
1765    }
1766
1767    proof fn lemma_perm_well_formed_with_region_preserved(
1768        pa: Paddr,
1769        perm: Tracked<Option<Self::Perm>>,
1770        old_regions: MetaRegionOwners,
1771        new_regions: MetaRegionOwners,
1772    ) {
1773    }
1774
1775    proof fn lemma_raw_item_well_formed_preserved(
1776        pa: Paddr,
1777        level: PagingLevel,
1778        old_prop: PageProperty,
1779        new_prop: PageProperty,
1780        perm: Tracked<Option<Self::Perm>>,
1781    ) {
1782    }
1783
1784    proof fn lemma_raw_item_well_formed_split(
1785        pa: Paddr,
1786        level: PagingLevel,
1787        prop: PageProperty,
1788        child_pa: Paddr,
1789        child_idx: usize,
1790        perm: Tracked<Option<Self::Perm>>,
1791    ) {
1792    }
1793
1794    proof fn lemma_huge_raw_item_untracked(
1795        pa: Paddr,
1796        level: PagingLevel,
1797        prop: PageProperty,
1798        perm: Tracked<Option<Self::Perm>>,
1799    ) {
1800    }
1801
1802    proof fn lemma_item_from_raw_well_formed(
1803        pa: Paddr,
1804        level: PagingLevel,
1805        prop: PageProperty,
1806        perm: Tracked<Option<Self::Perm>>,
1807    ) {
1808        broadcast use group_page_meta;
1809
1810        let item = Self::item_from_raw(pa, level, prop, perm);
1811        lemma_meta_to_paddr_biinjective(item.frame.slot_vaddr());
1812    }
1813
1814    proof fn lemma_clone_ensures_concrete(
1815        item: Self::Item,
1816        pa: Paddr,
1817        old_regions: MetaRegionOwners,
1818        new_regions: MetaRegionOwners,
1819        res: Self::Item,
1820    ) {
1821        use crate::specs::mm::frame::mapping::{frame_to_index, meta_to_index};
1822        let frame_idx = meta_to_index(item.frame.slot_vaddr());
1823        assert(frame_to_index(pa) == frame_idx);
1824        assert(<MappedItem as RCClone>::clone_ensures(item, old_regions, new_regions, res));
1825        assert(item.frame.clone_ensures(old_regions, new_regions, res.frame));
1826    }
1827
1828    proof fn lemma_clone_requires_concrete(
1829        item: Self::Item,
1830        pa: Paddr,
1831        level: PagingLevel,
1832        prop: PageProperty,
1833        regions: MetaRegionOwners,
1834    ) {
1835        use crate::specs::mm::frame::mapping::{frame_to_index, meta_to_index};
1836        broadcast use group_page_meta;
1837
1838        let perm = Self::item_into_raw(item).3;
1839        Self::lemma_item_from_raw_well_formed(pa, level, prop, perm);
1840        regions.lemma_contains_valid_frame_paddr(pa);
1841    }
1842
1843    proof fn lemma_page_table_config_constant_requirements() {
1844        PageTableEntry::lemma_layout();
1845    }
1846}
1847
1848} // verus!