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