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