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 alloc::vec::Vec;
10use vstd::atomic::PermissionU64;
11use vstd::pervasive::{arbitrary, proof_from_false};
12use vstd::prelude::*;
13use vstd::simple_pptr::PointsTo;
14use vstd::vpanic;
15
16use crate::error::Error;
17use crate::mm::frame::MetaSlot;
18use crate::mm::frame::meta::mapping::meta_to_frame;
19use crate::mm::frame::untyped::UFrame;
20use crate::mm::kspace::KernelPtConfig;
21use crate::mm::page_table::*;
22use crate::mm::page_table::{EntryOwner, PageTableFrag, PageTableGuard};
23use crate::specs::arch::*;
24use crate::specs::mm::frame::meta_owners::{MetaPerm, MetaSlotStorage, MetadataInnerPerms};
25use crate::specs::mm::frame::meta_region_owners::MetaRegionOwners;
26use crate::specs::mm::io::VmIoMemView;
27use crate::specs::mm::page_table::cursor::owners::CursorOwner;
28use crate::specs::mm::page_table::*;
29use crate::specs::mm::tlb::TlbModel;
30use crate::specs::mm::virt_mem::{MemView, VirtPtr};
31use crate::specs::task::InAtomicMode;
32use crate::sync::RoArc;
33use core::marker::PhantomData;
34use core::{ops::Range, sync::atomic::Ordering};
35use vstd_extra::ghost_tree::*;
36use vstd_extra::panic::may_panic;
37use vstd_extra::{assert, assert_eq};
38
39use crate::mm::kspace::KERNEL_PAGE_TABLE;
40use crate::mm::tlb::*;
41use crate::specs::mm::cpu::{AtomicCpuSet, CpuSet};
42
43use crate::specs::mm::io::VmIoOwner;
44use crate::{
45 mm::{
46 MAX_USERSPACE_VADDR, Paddr, PagingConstsTrait, PagingLevel, Vaddr,
47 io::{Fallible, VmReader, VmWriter},
48 page_prop::PageProperty,
49 },
50 prelude::*,
51};
52
53use alloc::sync::Arc;
54
55#[path = "../../specs/mm/vm_space.rs"]
56pub mod vm_space_specs;
57use vm_space_specs::*;
58
59verus! {
60
61/// A virtual address space for user-mode tasks, enabling safe manipulation of user-space memory.
62///
63/// The [`VmSpace`] type provides memory isolation guarantees between user-space and
64/// kernel-space. For example, given an arbitrary user-space pointer, one can read and
65/// write the memory location referred to by the user-space pointer without the risk of
66/// breaking the memory safety of the kernel space.
67///
68/// # Task Association Semantics
69///
70/// As far as OSTD is concerned, a [`VmSpace`] is not necessarily associated with a task. Once a
71/// [`VmSpace`] is activated (see [`VmSpace::activate`]), it remains activated until another
72/// [`VmSpace`] is activated **possibly by another task running on the same CPU**.
73///
74/// This means that it's up to the kernel to ensure that a task's [`VmSpace`] is always activated
75/// while the task is running. This can be done by using the injected post schedule handler
76/// (see [`inject_post_schedule_handler`]) to always activate the correct [`VmSpace`] after each
77/// context switch.
78///
79/// If the kernel otherwise decides not to ensure that the running task's [`VmSpace`] is always
80/// activated, the kernel must deal with race conditions when calling methods that require the
81/// [`VmSpace`] to be activated, e.g., [`UserMode::execute`], [`VmReader`] and [`VmWriter`].
82/// Otherwise, the behavior is unspecified, though it's guaranteed _not_ to compromise the kernel's
83/// memory safety.
84///
85/// # Memory Backing
86///
87/// A newly-created [`VmSpace`] is not backed by any physical memory pages. To
88/// provide memory pages for a [`VmSpace`], one can allocate and map physical
89/// memory ([`UFrame`]s) to the [`VmSpace`] using the cursor.
90///
91/// A [`VmSpace`] can also attach a page fault handler, which will be invoked to
92/// handle page faults generated from user space.
93///
94/// [`inject_post_schedule_handler`]: crate::task::inject_post_schedule_handler
95/// [`UserMode::execute`]: crate::user::UserMode::execute
96/// [`VmReader`]: crate::mm::io::VmWriter
97/// [`VmReader`]: crate::mm::io::VmReader
98/// # Verification Design
99///
100/// A [`VmSpace`] has a corresponding [`VmSpaceOwner`] object that is used to track its state,
101/// and against which its invariants are stated. The [`VmSpaceOwner`] catalogues the readers and writers
102/// that are associated with the [`VmSpace`], and the [`MemView`] which encodes the active page table and
103/// the subset of the TLB that covers the same virtual address space.
104/// All proofs about the correctness of the readers and writers are founded on the well-formedness of the [`MemView`]:
105///
106/// ```rust
107/// open spec fn mem_view_wf(self) -> bool {
108/// &&& self.mem_view is Some <==> self.mv_range@ is Some
109/// // This requires that TotalMapping (mvv) = mv ∪ writer mappings ∪ reader mappings
110/// &&& self.mem_view matches Some(remaining_view)
111/// ==> self.mv_range@ matches Some(total_view)
112/// ==> {
113/// &&& remaining_view.mappings_are_disjoint()
114/// &&& remaining_view.mappings.finite()
115/// &&& total_view.mappings_are_disjoint()
116/// &&& total_view.mappings.finite()
117/// // ======================
118/// // Remaining Consistency
119/// // ======================
120/// &&& remaining_view.mappings.subset_of(total_view.mappings)
121/// &&& remaining_view.memory.dom().subset_of(
122/// total_view.memory.dom(),
123/// )
124/// // =====================
125/// // Total View Consistency
126/// // =====================
127/// &&& forall|va: usize|
128/// remaining_view.addr_transl(va) == total_view.addr_transl(
129/// va,
130/// )
131/// // =====================
132/// // Writer correctness
133/// // =====================
134/// &&& forall|i: int|
135/// 0 <= i < self.writers.len() as int ==> {
136/// &&& self.writers[i].inv()
137/// }
138/// }
139/// }
140/// }
141/// ```
142pub struct VmSpace<'a> {
143 pub pt: PageTable<UserPtConfig>,
144 pub cpus: AtomicCpuSet,
145 pub _marker: PhantomData<&'a ()>,
146}
147
148type Result<A> = core::result::Result<A, Error>;
149
150#[verus_verify]
151impl<'a> VmSpace<'a> {
152 #[inline]
153 #[verus_spec(r =>
154 with
155 Tracked(regions): Tracked<&mut MetaRegionOwners>,
156 Tracked(guards): Tracked<&mut Guards<'rcu>>,
157 requires
158 old(regions).inv(),
159 )]
160 #[allow(private_interfaces)]
161 pub fn default<'rcu>() -> Self {
162 proof_with!(Tracked(regions), Tracked(guards));
163 Self::new()
164 }
165
166 /// Creates a new VM address space.
167 ///
168 /// This allocates a new user page table by duplicating the kernel page
169 /// table's top-level entries, and returns a [`VmSpace`] that wraps it.
170 ///
171 /// # Verified Properties
172 /// ## Preconditions
173 /// - **Safety Invariants**: The meta-region invariants must hold.
174 /// ## Postconditions
175 /// - The returned [`VmSpace`] instance satisfies the invariants of [`VmSpace`].
176 #[inline]
177 #[verus_spec(r =>
178 with
179 Tracked(regions): Tracked<&mut MetaRegionOwners>,
180 Tracked(guards): Tracked<&mut Guards<'rcu>>,
181 requires
182 old(regions).inv(),
183 ensures
184 final(regions).inv(),
185 )]
186 #[allow(private_interfaces)]
187 pub fn new<'rcu>() -> Self {
188 proof_decl! {
189 let tracked mut kernel_owner_opt: Option<&PageTableOwner<KernelPtConfig>> = None;
190 }
191 let kpt = crate::mm::kspace::kvirt_area::get_kernel_page_table(
192 Tracked(&mut kernel_owner_opt),
193 Tracked(regions),
194 Tracked(guards),
195 );
196 proof_decl! {
197 let tracked kernel_owner = kernel_owner_opt.tracked_take();
198 }
199 let pt = {
200 #[verus_spec(with Tracked(kernel_owner), Tracked(regions), Tracked(guards))]
201 kpt.create_user_page_table::<crate::specs::task::AnyAtomicGuard>()
202 };
203 Self { pt, cpus: AtomicCpuSet::new(CpuSet::new_empty()), _marker: PhantomData }
204 }
205
206 /// Gets an immutable cursor in the virtual address range.
207 ///
208 /// The cursor behaves like a lock guard, exclusively owning a sub-tree of
209 /// the page table, preventing others from creating a cursor in it. So be
210 /// sure to drop the cursor as soon as possible.
211 ///
212 /// The creation of the cursor may block if another cursor having an
213 /// overlapping range is alive.
214 ///
215 /// # Verified Properties
216 /// ## Preconditions
217 /// - **Safety Invariants**: The page table owner must be valid.
218 /// ## Postconditions
219 /// - When the virtual address range satisfies
220 /// [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
221 /// the result is `Ok` and a [`CursorOwner`] is returned.
222 #[verus_spec(r =>
223 with
224 Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
225 Tracked(regions): Tracked<&mut MetaRegionOwners>,
226 Tracked(guards): Tracked<&mut Guards<'a>>,
227 -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
228 requires
229 owner.inv(),
230 ensures
231 crate::mm::page_table::Cursor::<UserPtConfig, G>::cursor_new_success_conditions(va) ==> (r matches Ok(_) && cursor_owner@ matches Some(_)),
232 // On the success branch, the returned cursor owner satisfies
233 // its invariant. Follows from the underlying PT::cursor's
234 // ensures: r is Ok ⇒ cursor_new_success_conditions (by
235 // contrapositive of the !cond ⇒ Err clause) ⇒ invariants
236 // hold ⇒ cursor_owner.inv().
237 cursor_owner@ matches Some(c) ==> c.inv(),
238 )]
239 pub fn cursor<G: InAtomicMode>(&'a self, guard: &'a G, va: &Range<Vaddr>) -> Result<
240 Cursor<'a, G>,
241 > {
242 proof_decl! {
243 let tracked mut out_owner: Option<CursorOwner<'a, UserPtConfig>>;
244 }
245 match {
246 #[verus_spec(with Tracked(owner), Ghost(vstd::pervasive::arbitrary::<PageTableGuard<'a, UserPtConfig>>()), Tracked(regions), Tracked(guards))]
247 self.pt.cursor(guard, va)
248 } {
249 Ok((pt_cursor, tracked_owner)) => {
250 proof! { out_owner = Some::<CursorOwner<'a, UserPtConfig>>(tracked_owner.get()); }
251 proof_with!(|= Tracked(out_owner));
252 Ok(Cursor(pt_cursor))
253 },
254 Err(e) => {
255 proof! { out_owner = None; }
256 proof_with!(|= Tracked(out_owner));
257 Err(Error::AccessDenied)
258 },
259 }
260 }
261
262 /// Gets a mutable cursor in the virtual address range.
263 ///
264 /// The same as [`Self::cursor`], the cursor behaves like a lock guard,
265 /// exclusively owning a sub-tree of the page table, preventing others
266 /// from creating a cursor in it. So be sure to drop the cursor as soon as
267 /// possible.
268 ///
269 /// The creation of the cursor may block if another cursor having an
270 /// overlapping range is alive. The modification to the mapping by the
271 /// cursor may also block or be overridden by the mapping of another cursor.
272 ///
273 /// # Verified Properties
274 /// ## Preconditions
275 /// - **Safety Invariants**: The page table owner must be valid.
276 /// ## Postconditions
277 /// - When the virtual address range satisfies
278 /// [`cursor_new_success_conditions`](crate::mm::page_table::Cursor::cursor_new_success_conditions),
279 /// the result is `Ok` and a [`CursorOwner`] is returned.
280 #[verus_spec(r =>
281 with
282 Tracked(owner): Tracked<PageTableOwner<UserPtConfig>>,
283 Tracked(regions): Tracked<&mut MetaRegionOwners>,
284 Tracked(guards): Tracked<&mut Guards<'a>>
285 -> cursor_owner: Tracked<Option<CursorOwner<'a, UserPtConfig>>>,
286 requires
287 owner.inv(),
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(vstd::pervasive::arbitrary::<PageTableGuard<'a, UserPtConfig>>()), 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@.unwrap())
368 &&& reader_owner@.unwrap().mem_view is None
369 &&& reader_owner@.unwrap().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@.unwrap())
427 &&& writer_owner@.unwrap().mem_view is None
428 &&& writer_owner@.unwrap().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<'rcu>>,
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<'rcu>>,
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.unwrap() == 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 /// Jump to the virtual address.
562 ///
563 /// This function will move the cursor to the given virtual address.
564 /// If the target address is not in the locked range, it will return an error.
565 /// # Verified Properties
566 /// ## Preconditions
567 /// - **Safety Invariants**: The page table cursor safety invariants
568 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
569 /// - **Liveness**: The function will panic if the target `va` is not aligned
570 /// to the base page size.
571 /// ## Postconditions
572 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
573 /// - **Correctness**: If the target `va` is within the cursor's locked range,
574 /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
575 /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
576 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
577 /// ## Panics
578 /// This method panics if the target address is not aligned to the page size.
579 /// ## Safety
580 /// This function preserves all memory invariants.
581 /// Because it throws an error rather than move the cursor to an invalid address,
582 /// it ensures that the cursor is safe to use after the call.
583 /// The locking mechanism prevents data races.
584 #[verus_spec(res =>
585 with
586 Tracked(owner): Tracked<&mut CursorOwner<'rcu, UserPtConfig>>,
587 Tracked(regions): Tracked<&mut MetaRegionOwners>,
588 Tracked(guards): Tracked<&mut Guards<'rcu>>,
589 requires
590 old(self).0.invariants(*old(owner), *old(regions), *old(guards)),
591 // `CursorMut::jump` diverges on a misaligned `va` and may panic
592 // in its `pop_level` repositioning ascent.
593 old(self).0.jump_panic_condition(va) ==> may_panic(),
594 ensures
595 !old(self).0.jump_panic_condition(va),
596 final(self).0.invariants(*final(owner), *final(regions), *final(guards)),
597 final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end ==> {
598 &&& res is Ok
599 &&& final(self).0.va == va
600 },
601 !(final(self).0.barrier_va.start <= va < final(self).0.barrier_va.end) ==> res is Err,
602 )]
603 pub fn jump(&mut self, va: Vaddr) -> Result<()> {
604 (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
605 self.0.jump(va))?;
606 Ok(())
607 }
608
609 /// Get the virtual address of the current slot.
610 #[verus_spec(
611 returns
612 self.0.va,
613 )]
614 pub fn virt_addr(&self) -> Vaddr {
615 self.0.virt_addr()
616 }
617}
618
619/// The cursor for modifying the mappings in VM space.
620///
621/// It exclusively owns a sub-tree of the page table, preventing others from
622/// reading or modifying the same sub-tree.
623pub struct CursorMut<'a, A: InAtomicMode> {
624 pub pt_cursor: crate::mm::page_table::CursorMut<'a, UserPtConfig, A>,
625 // We have a read lock so the CPU set in the flusher is always a superset
626 // of actual activated CPUs.
627 pub flusher: TlbFlusher<'a /*, DisabledPreemptGuard*/ >,
628}
629
630#[verus_verify]
631impl<'a, A: InAtomicMode> CursorMut<'a, A> {
632 /// Queries the mapping at the current virtual address.
633 ///
634 /// This is the same as [`Cursor::query`].
635 ///
636 /// If the cursor is pointing to a valid virtual address that is locked,
637 /// it will return the virtual address range and the mapped item.
638 /// ## Preconditions
639 /// - **Safety Invariants**: The page table cursor safety invariants
640 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
641 /// ## Postconditions
642 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
643 /// - **Correctness**: If the cursor is within the locked range, the result is `Ok`.
644 /// - **Correctness**: If there is a mapped item at the current virtual address ([`query_some_condition`]),
645 /// it is returned along with the virtual address range that it maps ([`query_success_ensures`]).
646 /// - **Correctness**: The mapping that is returned corresponds to the abstract mapping given by [`query_item_spec`](CursorView::query_item_spec).
647 /// - **Correctness**: If there is no mapped item at the current virtual address ([`query_none_condition`]),
648 /// it returns `None`, and the virtual address range of the cursor's current position.
649 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
650 /// - **Safety**: The mappings in the page table are not affected.
651 /// - **Safety**: The soundness of individual entries are not affected.
652 /// ## Safety
653 /// - This function preserves all memory invariants.
654 /// - The locking mechanism prevents data races.
655 #[verus_spec(res =>
656 with
657 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
658 Tracked(regions): Tracked<&mut MetaRegionOwners>,
659 Tracked(guards): Tracked<&mut Guards<'a>>,
660 requires
661 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
662 // Out-of-range → graceful `Err`; the sole panic is cloning the
663 // resolved leaf frame when *that specific slot* is saturated —
664 // precisely propagated from `Cursor::query_panic_condition`.
665 old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions))
666 ==> may_panic(),
667 ensures
668 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
669 !old(self).pt_cursor.0.query_panic_condition(*old(owner), *old(regions)),
670 old(owner).in_locked_range() ==> res is Ok,
671 res matches Ok(state) ==>
672 final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
673 final(self).pt_cursor.0.query_some_ensures(*final(owner), state),
674 res matches Ok(state) ==>
675 !final(self).pt_cursor.0.query_some_condition(*final(owner)) ==>
676 final(self).pt_cursor.0.query_none_ensures(*final(owner), state),
677 old(owner)@.mappings == final(owner)@.mappings,
678 )]
679 pub fn query(&mut self) -> Result<(Range<Vaddr>, Option<MappedItem>)> {
680 Ok(
681 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
682 self.pt_cursor.query()?,
683 )
684 }
685
686 /// Moves the cursor forward to the next mapped virtual address.
687 ///
688 /// This is the same as [`Cursor::find_next`].
689 ///
690 /// # Verified Properties
691 /// ## Preconditions
692 /// - **Safety Invariants**: The page table cursor safety invariants
693 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
694 /// - **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.
695 /// ## Postconditions
696 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
697 /// - **Correctness**: If there is a mapped address after the current address within the next `len` bytes,
698 /// it will move the cursor to the next mapped address and return it.
699 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
700 /// ## Panics
701 /// This method panics if the length is longer than the remaining range of the cursor.
702 /// ## Safety
703 /// This function preserves all memory invariants.
704 /// Because it panics rather than move the cursor to an invalid address,
705 /// it ensures that the cursor is safe to use after the call.
706 #[verus_spec(res =>
707 with
708 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
709 Tracked(regions): Tracked<&mut MetaRegionOwners>,
710 Tracked(guards): Tracked<&mut Guards<'a>>,
711 requires
712 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
713 old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
714 ensures
715 !old(self).pt_cursor.0.find_next_panic_condition(len),
716 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
717 res is Some ==> {
718 &&& res.unwrap() == final(self).pt_cursor.0.va
719 &&& final(owner).level <= final(owner).guard_level
720 &&& final(owner).in_locked_range()
721 },
722 )]
723 pub fn find_next(&mut self, len: usize) -> Option<Vaddr> {
724 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
725 self.pt_cursor.find_next(len)
726 }
727
728 /// Jump to the virtual address.
729 ///
730 /// This is the same as [`Cursor::jump`].
731 ///
732 /// # Verified Properties
733 /// ## Preconditions
734 /// - **Safety Invariants**: The page table cursor safety invariants
735 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
736 /// - **Liveness**: The function will panic if the target `va` is not aligned
737 /// to the base page size.
738 /// ## Postconditions
739 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
740 /// - **Correctness**: If the target `va` is within the cursor's locked range,
741 /// the result will be `Ok` and the cursor's virtual address will be set to `va`.
742 /// - **Correctness**: If the target `va` is outside the locked range, the result is `Err`.
743 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
744 /// ## Panics
745 /// This method panics if the target address is not aligned to the page size.
746 /// ## Safety
747 /// This function preserves all memory invariants.
748 /// Because it throws an error rather than move the cursor to an invalid address,
749 /// it ensures that the cursor is safe to use after the call.
750 /// The locking mechanism prevents data races.
751 #[verus_spec(res =>
752 with
753 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
754 Tracked(regions): Tracked<&mut MetaRegionOwners>,
755 Tracked(guards): Tracked<&mut Guards<'a>>
756 requires
757 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
758 // `CursorMut::jump` diverges on a misaligned `va` and may panic
759 // in its `pop_level` repositioning ascent.
760 old(self).pt_cursor.0.jump_panic_condition(va) ==> may_panic(),
761 ensures
762 !old(self).pt_cursor.0.jump_panic_condition(va),
763 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
764 final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end ==> {
765 &&& res is Ok
766 &&& final(self).pt_cursor.0.va == va
767 },
768 !(final(self).pt_cursor.0.barrier_va.start <= va < final(self).pt_cursor.0.barrier_va.end) ==> res is Err,
769 )]
770 pub fn jump(&mut self, va: Vaddr) -> Result<()> {
771 (#[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
772 self.pt_cursor.jump(va))?;
773 Ok(())
774 }
775
776 /// Get the virtual address of the current slot.
777 #[verus_spec(r =>
778 returns
779 self.pt_cursor.0.va,
780 )]
781 pub fn virt_addr(&self) -> Vaddr {
782 self.pt_cursor.virt_addr()
783 }
784
785 /// Get the dedicated TLB flusher for this cursor.
786 #[verus_spec(ret =>
787 ensures
788 *ret == old(self).flusher,
789 *final(ret) == final(self).flusher,
790 )]
791 pub fn flusher(&mut self) -> &mut TlbFlusher<'a> {
792 &mut self.flusher
793 }
794
795 /// Map a frame into the current slot.
796 ///
797 /// This method will bring the cursor to the next slot after the modification.
798 /// If there is an existing mapping at the current slot, it will be replaced
799 /// and the TLB will be flushed for that entry.
800 /// # Verified Properties
801 /// ## Preconditions
802 /// - **Safety Invariants**: The page table cursor safety invariants
803 /// ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the TLB invariant
804 /// ([`TlbModel::inv`]) must hold before the call.
805 /// - **Liveness**: The cursor must not be past the end of its locked range,
806 /// and the frame's level must fit within the remaining range, or the function will panic.
807 /// - **Bookkeeping**: The frame must be well-formed with respect to its entry owner
808 /// ([`item_wf`](Self::item_wf)).
809 /// ## Postconditions
810 /// - **Safety Invariants**: Page table cursor safety invariants are preserved.
811 /// - **Correctness**: The page table view is updated with the new mapping
812 /// according to [`map_item_ensures`](Self::map_item_ensures).
813 /// - **Correctness**: If the metadata region was well-formed before the call
814 /// and the frame was not already mapped, it will be well-formed after.
815 /// ## Safety
816 /// - For soundness purposes, it doesn't matter if a frame is mapped multiple times
817 /// in the same page table. There is still a clear definition of the behavior.
818 #[verus_spec(
819 with
820 Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
821 Tracked(entry_owner): Tracked<EntryOwner<UserPtConfig>>,
822 Tracked(regions): Tracked<&mut MetaRegionOwners>,
823 Tracked(guards): Tracked<&mut Guards<'a>>,
824 Tracked(tlb_model): Tracked<&mut TlbModel>,
825 requires
826 old(tlb_model).inv(),
827 old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
828 old(self).item_wf(frame, prop, entry_owner, *old(regions)),
829 old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }) ==> may_panic(),
830 ensures
831 !old(self).pt_cursor.map_panic_conditions(MappedItem { frame: frame, prop: prop }),
832 final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
833 old(self).map_item_ensures(
834 frame,
835 prop,
836 old(self).pt_cursor.0.model(*old(cursor_owner)),
837 final(self).pt_cursor.0.model(*final(cursor_owner)),
838 ),
839 )]
840 pub fn map(&mut self, frame: UFrame, prop: PageProperty) {
841 let start_va = self.virt_addr();
842 let item = MappedItem { frame: frame, prop: prop };
843
844 assert(self.pt_cursor.item_wf(item, entry_owner)) by {};
845
846 // SAFETY: It is safe to map untyped memory into the userspace.
847 let Err(frag) = (unsafe {
848 #[verus_spec(with Tracked(cursor_owner), Tracked(entry_owner), Tracked(regions), Tracked(guards))]
849 self.pt_cursor.map(item)
850 }) else {
851 return; // No mapping exists at the current address.
852 };
853
854 match frag {
855 PageTableFrag::Mapped { va, item } => {
856 //debug_assert_eq!(va, start_va);
857 let old_frame = item.frame;
858
859 #[verus_spec(with Tracked(tlb_model))]
860 self.flusher.issue_tlb_flush_with(
861 TlbFlushOp::Address(start_va),
862 old_frame.into_dyn(),
863 );
864 #[verus_spec(with Tracked(tlb_model))]
865 self.flusher.dispatch_tlb_flush();
866 },
867 PageTableFrag::StrayPageTable { .. } => {
868 assert(false) by {
869 assert(UserPtConfig::item_into_raw(item).1 == 1);
870 };
871 #[cfg(feature = "allow_panic")]
872 vpanic!("`UFrame` is base page sized but re-mapping out a child PT");
873 },
874 }
875 }
876
877 /// Clears the mapping starting from the current slot,
878 /// and returns the number of unmapped pages.
879 ///
880 /// This method will bring the cursor forward by `len` bytes in the virtual
881 /// address space after the modification.
882 ///
883 /// Already-absent mappings encountered by the cursor will be skipped. It
884 /// is valid to unmap a range that is not mapped.
885 ///
886 /// It must issue and dispatch a TLB flush after the operation. Otherwise,
887 /// the memory safety will be compromised. Please call this function less
888 /// to avoid the overhead of TLB flush. Using a large `len` is wiser than
889 /// splitting the operation into multiple small ones.
890 ///
891 /// # Verified Properties
892 /// ## Preconditions
893 /// - **Safety Invariants**: The page table cursor safety invariants
894 /// ([crate::mm::page_table::Cursor::invariants]) must hold before the call.
895 /// - **Safety Invariants**: The TLB invariant ([TlbModel::inv]) must hold.
896 /// - **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.
897 /// ## Postconditions
898 /// - **Safety Invariants**: The page table cursor safety invariants are preserved.
899 /// - **Safety Invariants**: The TLB invariant is preserved.
900 /// - **Correctness**: Unmaps a range of virtual addresses from the current address up to `len` bytes
901 /// and returns the number of mappings that were removed.
902 /// - **Correctness**: If the metadata region was well-formed before the call, it will be well-formed after.
903 /// ## Panics
904 /// Panics if:
905 /// - the length is longer than the remaining range of the cursor;
906 /// - the length is not page-aligned.
907 /// ## Safety
908 /// - It is always sound to unmap pages. We flush unmapped pages from the TLB to ensure consistency.
909 /// - TODO: formalizing and proving that this function preserves TLB consistency would
910 /// be pretty straightforward and would be a nice addition to the correctness properties.
911 #[verus_spec(r =>
912 with
913 Tracked(cursor_owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
914 Tracked(regions): Tracked<&mut MetaRegionOwners>,
915 Tracked(guards): Tracked<&mut Guards<'a>>,
916 Tracked(tlb_model): Tracked<&mut TlbModel>,
917 requires
918 old(self).pt_cursor.0.invariants(*old(cursor_owner), *old(regions), *old(guards)),
919 old(tlb_model).inv(),
920 old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
921 ensures
922 !old(self).pt_cursor.0.find_next_panic_condition(len),
923 final(self).pt_cursor.0.invariants(*final(cursor_owner), *final(regions), *final(guards)),
924 old(self).pt_cursor.0.model(*old(cursor_owner)).unmap_spec(len, final(self).pt_cursor.0.model(*final(cursor_owner)), r),
925 final(tlb_model).inv(),
926 )]
927 pub fn unmap(&mut self, len: usize) -> usize {
928 proof {
929 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
930 cursor_owner.view_preserves_inv();
931 }
932
933 assert_eq!(len % PAGE_SIZE, 0);
934
935 //*** KNOWN BUG: `self.virt_addr() + len` could overflow. For now, assume that it doesn't. ***
936 assume(self.pt_cursor.0.va + len <= usize::MAX);
937
938 assert!(self.virt_addr() + len <= self.pt_cursor.0.barrier_va.end);
939
940 assert(!self.pt_cursor.0.find_next_panic_condition(len));
941 assert(!old(self).pt_cursor.0.find_next_panic_condition(len));
942
943 let end_va = self.virt_addr() + len;
944 let mut num_unmapped: usize = 0;
945
946 let ghost start_va: Vaddr = cursor_owner@.cur_va;
947 // The "adjusted base" accumulates splits: starts as the split-at-boundaries
948 // version of start_mappings and gets updated when take_next splits huge pages.
949 let ghost mut adjusted_base: Set<Mapping> = cursor_owner@.mappings;
950 // Track the set of removed mappings explicitly (not as a VA range filter).
951 let ghost mut removed: Set<Mapping> = Set::empty();
952
953 proof {
954 // end_va <= barrier_va.end == locked_range().end. The cursor invariant
955 // bounds locked_range().end by `vaddr_range_bounds_spec::<C>().1 + 1`,
956 // and for UserPtConfig that evaluates to 2^47.
957 crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
958 assert(end_va <= 0x0000_8000_0000_0000usize);
959
960 assert((self.pt_cursor.0.va + len) % PAGE_SIZE as int == 0) by (compute);
961 assert(adjusted_base.difference(removed) =~= adjusted_base);
962 }
963
964 #[verus_spec(
965 invariant
966 self.pt_cursor.0.va % PAGE_SIZE == 0,
967 end_va % PAGE_SIZE == 0,
968 end_va <= 0x0000_8000_0000_0000usize,
969 self.pt_cursor.0.invariants(*cursor_owner, *regions, *guards),
970 end_va <= self.pt_cursor.0.barrier_va.end,
971 tlb_model.inv(),
972 start_va <= cursor_owner@.cur_va,
973 // Split-aware invariant: adjusted_base tracks accumulated splits,
974 // removed tracks the explicitly removed set.
975 cursor_owner@.mappings =~= adjusted_base.difference(removed),
976 removed.subset_of(adjusted_base),
977 num_unmapped as nat == removed.len(),
978 removed.finite(),
979 crate::specs::mm::page_table::mapping_set_lemmas::wf_mapping_set(adjusted_base),
980 // Everything removed is in the [start, end) range.
981 forall |m: Mapping| #[trigger] removed.contains(m) ==>
982 start_va <= m.va_range.start < end_va,
983 // Per-config VA bound: every removed mapping fits within the
984 // user VA space. Sourced from `axiom_view_in_vaddr_range` on
985 // the cursor view prior to removal.
986 forall |m: Mapping| #[trigger] removed.contains(m) ==>
987 m.va_range.end <= 0x0000_8000_0000_0000_usize as int,
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.va.reflect_prop(self.pt_cursor.0.va);
1029 cursor_owner.view_preserves_inv();
1030 // Per-config VA bound on prev_mappings — needed for
1031 // preserving the `removed`-end-bound loop invariant.
1032 crate::specs::mm::page_table::cursor::owners::axiom_view_in_vaddr_range::<
1033 UserPtConfig,
1034 >(cursor_owner);
1035 crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
1036 }
1037
1038 // SAFETY: It is safe to un-map memory in the userspace.
1039 let Some(frag) = (unsafe {
1040 #[verus_spec(with Tracked(cursor_owner), Tracked(regions), Tracked(guards))]
1041 self.pt_cursor.take_next(end_va - self.virt_addr())
1042 }) else {
1043 proof {
1044 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1045 // At break: take_next returned None, so no mappings in [prev_va, end_va).
1046 // Any m with start >= prev_va leads to contradiction via the empty filter.
1047 assert forall|m: Mapping|
1048 #![auto]
1049 adjusted_base.contains(m) && !removed.contains(m) && start_va
1050 <= m.va_range.start && m.va_range.start
1051 < end_va implies m.va_range.start >= cursor_owner@.cur_va || exists|
1052 parent: Mapping,
1053 | #[trigger]
1054 old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1055 < start_va && parent.va_range.start <= m.va_range.start
1056 && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1057 parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1058 && m.property == parent.property by {
1059 if m.va_range.start >= prev_va {
1060 assert(prev_mappings.filter(
1061 |m2: Mapping| prev_va <= m2.va_range.start < end_va,
1062 ).contains(m));
1063 assert(false);
1064 }
1065 };
1066 }
1067 break;
1068 };
1069
1070 let ghost old_adjusted = adjusted_base;
1071 let ghost old_removed = removed;
1072
1073 proof {
1074 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1075 }
1076
1077 let ghost frag_ghost = frag;
1078
1079 match frag {
1080 PageTableFrag::Mapped { va, item, .. } => {
1081 let frame = item.frame;
1082 proof {
1083 crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
1084 // `wf_mapping_set(removed)` from the wf adjusted_base
1085 // via subset; `va_range.end <= 2^47` for every removed
1086 // mapping is a loop invariant. Together they give
1087 // |removed| < usize::MAX, so num_unmapped + 1 fits.
1088 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1089 adjusted_base,
1090 removed,
1091 );
1092 crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1093 removed);
1094 }
1095 assert(num_unmapped < usize::MAX);
1096 num_unmapped += 1;
1097 #[verus_spec(with Tracked(tlb_model))]
1098 self.flusher.issue_tlb_flush_with(TlbFlushOp::Address(va), frame.into_dyn());
1099 },
1100 PageTableFrag::StrayPageTable { pt, va, len, num_frames } => {
1101 proof {
1102 let ghost new_removed = old_removed.union(
1103 prev_mappings.filter(
1104 |m2: Mapping|
1105 frag_ghost->StrayPageTable_va <= m2.va_range.start
1106 < frag_ghost->StrayPageTable_va
1107 + frag_ghost->StrayPageTable_len,
1108 ),
1109 );
1110 assert(new_removed.subset_of(old_adjusted)) by {
1111 assert forall|m: Mapping|
1112 new_removed.contains(m) implies old_adjusted.contains(m) by {
1113 if prev_mappings.contains(m) {
1114 // m ∈ prev_mappings = old_adjusted \ old_removed ⊆ old_adjusted.
1115 }
1116 };
1117 };
1118 vstd::set::axiom_set_union_finite(
1119 old_removed,
1120 prev_mappings.filter(
1121 |m2: Mapping|
1122 frag_ghost->StrayPageTable_va <= m2.va_range.start
1123 < frag_ghost->StrayPageTable_va
1124 + frag_ghost->StrayPageTable_len,
1125 ),
1126 );
1127 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1128 old_adjusted,
1129 new_removed,
1130 );
1131 crate::mm::page_table::lemma_vaddr_range_bounds_spec_user();
1132 crate::specs::mm::page_table::mapping_set_lemmas::lemma_mapping_set_cardinality_fits_usize(
1133 new_removed);
1134 // |new_removed| = |old_removed| + |subtree| (disjoint).
1135 assert(old_removed.disjoint(
1136 prev_mappings.filter(
1137 |m2: Mapping|
1138 frag_ghost->StrayPageTable_va <= m2.va_range.start
1139 < frag_ghost->StrayPageTable_va
1140 + frag_ghost->StrayPageTable_len,
1141 ),
1142 )) by {
1143 assert forall|m: Mapping|
1144 old_removed.contains(m) implies !prev_mappings.filter(
1145 |m2: Mapping|
1146 frag_ghost->StrayPageTable_va <= m2.va_range.start
1147 < frag_ghost->StrayPageTable_va
1148 + frag_ghost->StrayPageTable_len,
1149 ).contains(m) by {
1150 assert(!prev_mappings.contains(m));
1151 };
1152 };
1153 vstd::set::axiom_set_intersect_finite::<Mapping>(
1154 prev_mappings,
1155 Set::new(
1156 |m2: Mapping|
1157 frag_ghost->StrayPageTable_va <= m2.va_range.start
1158 < frag_ghost->StrayPageTable_va
1159 + frag_ghost->StrayPageTable_len,
1160 ),
1161 );
1162 vstd::set_lib::lemma_set_disjoint_lens(
1163 old_removed,
1164 prev_mappings.filter(
1165 |m2: Mapping|
1166 frag_ghost->StrayPageTable_va <= m2.va_range.start
1167 < frag_ghost->StrayPageTable_va
1168 + frag_ghost->StrayPageTable_len,
1169 ),
1170 );
1171 assert(num_unmapped + num_frames < usize::MAX);
1172 }
1173 num_unmapped += num_frames;
1174 proof {
1175 assert(0x0000_8000_0000_0000usize < KERNEL_VADDR_RANGE.end as usize)
1176 by (compute_only);
1177 assert(va + len <= KERNEL_VADDR_RANGE.end as usize);
1178 crate::specs::mm::page_table::cursor::page_size_lemmas::lemma_va_plus_page_size_no_overflow(
1179 va, len);
1180 }
1181 #[verus_spec(with Tracked(tlb_model))]
1182 self.flusher.issue_tlb_flush_with(TlbFlushOp::Range(va..va + len), pt);
1183 },
1184 }
1185
1186 proof {
1187 let ghost mm = match frag_ghost {
1188 PageTableFrag::Mapped { va: fv, item: fi, .. } => CursorView::<
1189 UserPtConfig,
1190 >::item_into_mapping(fv, fi),
1191 _ => arbitrary(),
1192 };
1193 let ghost sv = CursorView::<UserPtConfig> {
1194 cur_va: match frag_ghost {
1195 PageTableFrag::Mapped { va: fv, .. } => fv as Vaddr,
1196 _ => 0,
1197 },
1198 mappings: prev_mappings,
1199 phantom: PhantomData,
1200 };
1201 let ghost sm = sv.split_while_huge(mm.page_size).mappings;
1202 let ghost is_mapped = frag_ghost is Mapped;
1203 let ghost subtree = match frag_ghost {
1204 PageTableFrag::StrayPageTable { va: fv, len: fl, .. } => prev_mappings.filter(
1205 |m2: Mapping| fv <= m2.va_range.start < fv + fl,
1206 ),
1207 _ => Set::empty(),
1208 };
1209
1210 // Update ghost tracking variables.
1211 match frag_ghost {
1212 PageTableFrag::StrayPageTable { .. } => {
1213 removed = old_removed.union(subtree);
1214 },
1215 PageTableFrag::Mapped { .. } => {
1216 adjusted_base = sm.union(old_removed);
1217 removed = old_removed.union(set![mm]);
1218 },
1219 }
1220
1221 // Mapped-case setup: establish split_while_huge properties once.
1222 if is_mapped {
1223 assert(sv.cur_va < 0x0000_8000_0000_0000usize);
1224 assert forall|m: Mapping, x: Mapping| #[trigger]
1225 prev_mappings.contains(m) && #[trigger] old_removed.contains(
1226 x,
1227 ) implies Mapping::disjoint_vaddrs(m, x) by {
1228 assert(old_adjusted.contains(m));
1229 assert(old_adjusted.contains(x));
1230 };
1231 sv.split_while_huge_disjoint(mm.page_size, old_removed);
1232 sv.lemma_split_while_huge_preserves_inv(mm.page_size);
1233 }
1234 assert forall|m: Mapping| #[trigger] removed.contains(m) implies m.va_range.end
1235 <= 0x0000_8000_0000_0000_usize as int by {
1236 if !old_removed.contains(m) {
1237 if is_mapped {
1238 assert(m == mm);
1239 sv.split_while_huge_refinement(mm.page_size, mm);
1240 if !prev_mappings.contains(mm) {
1241 let parent = choose|p: Mapping| #[trigger]
1242 prev_mappings.contains(p) && p.va_range.start
1243 <= mm.va_range.start && mm.va_range.end <= p.va_range.end
1244 && mm.pa_range.start == (p.pa_range.start + (
1245 mm.va_range.start - p.va_range.start)) as Paddr && mm.property
1246 == p.property;
1247 }
1248 } else {
1249 assert(prev_mappings.contains(m));
1250 }
1251 }
1252 };
1253
1254 // Prove |removed| tracking (disjointness + cardinality).
1255
1256 match frag_ghost {
1257 PageTableFrag::StrayPageTable { .. } => {
1258 assert(old_removed.disjoint(subtree)) by {
1259 assert forall|e: Mapping|
1260 old_removed.contains(e) implies !subtree.contains(e) by {};
1261 };
1262 vstd::set::axiom_set_intersect_finite::<Mapping>(
1263 prev_mappings,
1264 Set::new(|m2: Mapping| subtree.contains(m2)),
1265 );
1266 vstd::set_lib::lemma_set_disjoint_lens(old_removed, subtree);
1267 assert(removed =~= old_removed + subtree);
1268 },
1269 PageTableFrag::Mapped { .. } => {
1270 assert(old_removed.disjoint(set![mm])) by {
1271 assert forall|e: Mapping| #[trigger]
1272 old_removed.contains(e) implies !set![mm].contains(e) by {};
1273 };
1274 vstd::set::axiom_set_insert_finite(Set::<Mapping>::empty(), mm);
1275 vstd::set_lib::lemma_set_disjoint_lens(old_removed, set![mm]);
1276 assert(removed =~= old_removed + set![mm]);
1277 vstd::set_lib::lemma_set_empty_equivalency_len(Set::<Mapping>::empty());
1278 assert(set![mm] =~= Set::<Mapping>::empty().insert(mm));
1279 vstd::set::axiom_set_insert_len(Set::<Mapping>::empty(), mm);
1280 },
1281 }
1282
1283 // Maintain wf_mapping_set(adjusted_base) — only changes in Mapped case.
1284 if is_mapped {
1285 vstd::set::axiom_set_union_finite(sm, old_removed);
1286 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_subset(
1287 old_adjusted,
1288 old_removed,
1289 );
1290 assert forall|m: Mapping, n: Mapping| #[trigger]
1291 sm.contains(m) && #[trigger] old_removed.contains(n) implies m.va_range.end
1292 <= n.va_range.start || n.va_range.end <= m.va_range.start by {
1293 sv.split_while_huge_refinement(mm.page_size, m);
1294 assert(!prev_mappings.contains(n));
1295 if prev_mappings.contains(m) {
1296 } else {
1297 let p = choose|p: Mapping| #[trigger]
1298 prev_mappings.contains(p) && p.va_range.start <= m.va_range.start
1299 && m.va_range.end <= p.va_range.end && m.pa_range.start == (
1300 p.pa_range.start + (m.va_range.start - p.va_range.start)) as Paddr
1301 && m.property == p.property;
1302 assert(old_adjusted.contains(p));
1303 }
1304 };
1305 crate::specs::mm::page_table::mapping_set_lemmas::lemma_wf_union(
1306 sm,
1307 old_removed,
1308 );
1309 }
1310 // Maintain mappings =~= adjusted_base \ removed.
1311
1312 assert forall|e: Mapping| #[trigger]
1313 adjusted_base.difference(removed).contains(e)
1314 <==> cursor_owner@.mappings.contains(e) by {};
1315 assert(cursor_owner@.mappings =~= adjusted_base.difference(removed));
1316
1317 assert(removed.subset_of(adjusted_base)) by {
1318 assert forall|e: Mapping| #[trigger]
1319 removed.contains(e) implies adjusted_base.contains(e) by {};
1320 };
1321
1322 // Maintain: not-yet-removed mappings in [start, end) are either
1323 // ahead of the cursor or sub-mappings of a boundary-straddling parent.
1324 assert forall|m: Mapping|
1325 #![auto]
1326 adjusted_base.contains(m) && !removed.contains(m) && start_va
1327 <= m.va_range.start && m.va_range.start < end_va implies m.va_range.start
1328 >= cursor_owner@.cur_va || exists|parent: Mapping| #[trigger]
1329 old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start < start_va
1330 && parent.va_range.start <= m.va_range.start && m.va_range.end
1331 <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1332 m.va_range.start - parent.va_range.start)) as Paddr && m.property
1333 == parent.property by {
1334 if m.va_range.start < cursor_owner@.cur_va {
1335 if m.va_range.start >= prev_va {
1336 // m was just processed — contradiction via empty filtered sets.
1337 match frag_ghost {
1338 PageTableFrag::StrayPageTable { va: frag_va, .. } => {
1339 if m.va_range.start >= frag_va {
1340 assert(cursor_owner@.mappings.filter(
1341 |m2: Mapping|
1342 frag_va <= m2.va_range.start < self.pt_cursor.0.va,
1343 ).contains(m));
1344 } else {
1345 assert(prev_mappings.filter(
1346 |m2: Mapping| prev_va <= m2.va_range.start < frag_va,
1347 ).contains(m));
1348 }
1349 },
1350 PageTableFrag::Mapped { va: frag_va, .. } => {
1351 if m.va_range.start >= (frag_va as usize) {
1352 assert(cursor_owner@.mappings.filter(
1353 |m2: Mapping|
1354 (frag_va as usize) <= m2.va_range.start
1355 < self.pt_cursor.0.va,
1356 ).contains(m));
1357 } else {
1358 assert(prev_mappings.filter(
1359 |m2: Mapping|
1360 prev_va <= m2.va_range.start < (frag_va as usize),
1361 ).contains(m));
1362 }
1363 },
1364 }
1365 assert(false);
1366 } else if is_mapped {
1367 // m.start < prev_va, m ∈ sm \ old_removed.
1368 assert(sm.contains(m));
1369 sv.split_while_huge_refinement(mm.page_size, m);
1370 if prev_mappings.contains(m) {
1371 // m ∈ old_adjusted \ old_removed — previous invariant applies directly.
1372 } else {
1373 // m is a sub-mapping of some p ∈ prev_mappings.
1374 let p = choose|p: Mapping| #[trigger]
1375 prev_mappings.contains(p) && p.va_range.start
1376 <= m.va_range.start && m.va_range.end <= p.va_range.end
1377 && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1378 - p.va_range.start)) as Paddr && m.property == p.property;
1379 assert(old_adjusted.contains(p) && !old_removed.contains(p));
1380 if p.va_range.start < start_va {
1381 // p itself or its ancestor is the boundary parent.
1382 if !old(cursor_owner)@.mappings.contains(p) {
1383 let orig = choose|orig: Mapping| #[trigger]
1384 old(cursor_owner)@.mappings.contains(orig)
1385 && orig.va_range.start <= p.va_range.start
1386 && p.va_range.end <= orig.va_range.end
1387 && p.pa_range.start == (orig.pa_range.start + (
1388 p.va_range.start - orig.va_range.start)) as Paddr
1389 && p.property == orig.property;
1390 assert(orig.inv());
1391 assert(m.inv());
1392 crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1393 m, p, orig);
1394 }
1395 } else if p.va_range.start >= end_va {
1396 assert(false); // p.start <= m.start < end_va, contradiction.
1397 } else {
1398 // start_va <= p.start < end_va, p.start < prev_va.
1399 // Previous invariant gives boundary ancestor orig.
1400 let orig = choose|orig: Mapping| #[trigger]
1401 old(cursor_owner)@.mappings.contains(orig)
1402 && orig.va_range.start < start_va && orig.va_range.start
1403 <= p.va_range.start && p.va_range.end
1404 <= orig.va_range.end && p.pa_range.start == (
1405 orig.pa_range.start + (p.va_range.start
1406 - orig.va_range.start)) as Paddr && p.property
1407 == orig.property;
1408 assert(orig.inv());
1409 assert(m.inv());
1410 crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1411 m, p, orig);
1412 }
1413 }
1414 }
1415 }
1416 };
1417
1418 // Maintain: old mappings outside [start, end) survive in adjusted_base.
1419 if is_mapped {
1420 assert forall|m: Mapping|
1421 old(cursor_owner)@.mappings.contains(m) && (m.va_range.end <= start_va
1422 || m.va_range.start
1423 >= end_va) implies #[trigger] adjusted_base.contains(m) by {
1424 if m.va_range.end <= start_va {
1425 assert(m.inv());
1426 }
1427 sv.split_while_huge_locality(mm.page_size, m);
1428 };
1429
1430 // Maintain: refinement — every mapping in adjusted_base comes from
1431 // old mappings or is a sub-mapping of one.
1432 assert forall|m: Mapping| #[trigger] adjusted_base.contains(m) implies old(
1433 cursor_owner,
1434 )@.mappings.contains(m) || exists|parent: Mapping| #[trigger]
1435 old(cursor_owner)@.mappings.contains(parent) && parent.va_range.start
1436 <= m.va_range.start && m.va_range.end <= parent.va_range.end
1437 && m.pa_range.start == (parent.pa_range.start + (m.va_range.start
1438 - parent.va_range.start)) as Paddr && m.property == parent.property by {
1439 if !old_removed.contains(m) {
1440 sv.split_while_huge_refinement(mm.page_size, m);
1441 if !prev_mappings.contains(m) {
1442 let p = choose|p: Mapping| #[trigger]
1443 prev_mappings.contains(p) && p.va_range.start
1444 <= m.va_range.start && m.va_range.end <= p.va_range.end
1445 && m.pa_range.start == (p.pa_range.start + (m.va_range.start
1446 - p.va_range.start)) as Paddr && m.property == p.property;
1447 assert(old_adjusted.contains(p));
1448 if !old(cursor_owner)@.mappings.contains(p) {
1449 let orig = choose|orig: Mapping| #[trigger]
1450 old(cursor_owner)@.mappings.contains(orig)
1451 && orig.va_range.start <= p.va_range.start
1452 && p.va_range.end <= orig.va_range.end
1453 && p.pa_range.start == (orig.pa_range.start + (
1454 p.va_range.start - orig.va_range.start)) as Paddr
1455 && p.property == orig.property;
1456 assert(orig.inv());
1457 assert(m.inv());
1458 crate::specs::mm::page_table::mapping_set_lemmas::lemma_sub_mapping_pa_compose(
1459 m, p, orig);
1460 }
1461 }
1462 }
1463 }
1464 };
1465 }
1466 }
1467 proof {
1468 cursor_owner.va.reflect_prop(self.pt_cursor.0.va);
1469
1470 let old_view = old(self).pt_cursor.0.model(*old(cursor_owner));
1471 let new_view = self.pt_cursor.0.model(*cursor_owner);
1472
1473 // Bridge from loop invariant to unmap_spec.
1474 let start = old_view.cur_va;
1475 let end = (old_view.cur_va + len) as Vaddr;
1476
1477 assert(new_view.cur_va >= end);
1478
1479 assert forall|m: Mapping|
1480 #![auto]
1481 old_view.mappings.contains(m) && (m.va_range.end <= start || m.va_range.start
1482 >= end) implies new_view.mappings.contains(m) by {
1483 assert(adjusted_base.contains(m));
1484 if m.va_range.end <= start {
1485 assert(m.inv());
1486 }
1487 };
1488
1489 assert forall|m: Mapping|
1490 new_view.mappings.contains(m) && start <= m.va_range.start < end implies exists|
1491 parent: Mapping,
1492 | #[trigger]
1493 old_view.mappings.contains(parent) && parent.va_range.start < start
1494 && parent.va_range.start <= m.va_range.start && m.va_range.end
1495 <= parent.va_range.end && m.pa_range.start == (parent.pa_range.start + (
1496 m.va_range.start - parent.va_range.start)) as Paddr && m.property
1497 == parent.property by {};
1498
1499 assert forall|m: Mapping|
1500 new_view.mappings.contains(m) implies old_view.mappings.contains(m) || exists|
1501 parent: Mapping,
1502 | #[trigger]
1503 old_view.mappings.contains(parent) && parent.va_range.start <= m.va_range.start
1504 && m.va_range.end <= parent.va_range.end && m.pa_range.start == (
1505 parent.pa_range.start + (m.va_range.start - parent.va_range.start)) as Paddr
1506 && m.property == parent.property by {};
1507 }
1508
1509 #[verus_spec(with Tracked(tlb_model))]
1510 self.flusher.dispatch_tlb_flush();
1511
1512 num_unmapped
1513 }
1514
1515 /// Applies the operation to the next slot of mapping within the range.
1516 ///
1517 /// The range to be found in is the current virtual address with the
1518 /// provided length.
1519 ///
1520 /// The function stops and yields the actually protected range if it has
1521 /// actually protected a page, no matter if the following pages are also
1522 /// required to be protected.
1523 ///
1524 /// It also makes the cursor moves forward to the next page after the
1525 /// protected one. If no mapped pages exist in the following range, the
1526 /// cursor will stop at the end of the range and return [`None`].
1527 ///
1528 /// Note that it will **NOT** flush the TLB after the operation. Please
1529 /// make the decision yourself on when and how to flush the TLB using
1530 /// [`Self::flusher`].
1531 ///
1532 /// # Verified Properties
1533 /// ## Preconditions
1534 /// - **Safety Invariants**: The page table cursor safety invariants
1535 /// ([`invariants`](crate::mm::page_table::Cursor::invariants)) and the
1536 /// meta-region invariants must hold before the call.
1537 /// - The cursor must be within the locked range and below the guard level.
1538 /// - The current entry must be a mapped frame (not absent or a page table node).
1539 /// - **Liveness**: The length must be page-aligned and within the remaining cursor range.
1540 /// ## Postconditions
1541 /// - **Correctness**: If the metadata region was well-formed before the call, it will be
1542 /// well-formed after.
1543 /// ## Panics
1544 /// Panics if the length is longer than the remaining range of the cursor.
1545 /// ## Safety
1546 /// - From a soundness perspective changing a userspace page's `prop` field is safe.
1547 #[verus_spec(r =>
1548 with
1549 Tracked(owner): Tracked<&mut CursorOwner<'a, UserPtConfig>>,
1550 Tracked(regions): Tracked<&mut MetaRegionOwners>,
1551 Tracked(guards): Tracked<&mut Guards<'a>>,
1552 requires
1553 old(self).pt_cursor.0.invariants(*old(owner), *old(regions), *old(guards)),
1554 forall |p: PageProperty| op.requires((p,)),
1555 // POTENTIALLY UNSOUND PATCH: trackedness preservation. For UserPtConfig
1556 // this is trivially true (tracked is constant). See `Entry::protect`.
1557 forall |pa: Paddr, level: PagingLevel, p_in: PageProperty, p_out: PageProperty| #![auto]
1558 op.ensures((p_in,), p_out) ==>
1559 UserPtConfig::tracked(UserPtConfig::item_from_raw_spec(pa, level, p_out))
1560 == UserPtConfig::tracked(UserPtConfig::item_from_raw_spec(pa, level, p_in)),
1561
1562 old(self).pt_cursor.0.find_next_panic_condition(len) ==> may_panic(),
1563 ensures
1564 !old(self).pt_cursor.0.find_next_panic_condition(len),
1565 final(self).pt_cursor.0.invariants(*final(owner), *final(regions), *final(guards)),
1566 final(self).pt_cursor.0.barrier_va == old(self).pt_cursor.0.barrier_va,
1567 )]
1568 pub fn protect_next(
1569 &mut self,
1570 len: usize,
1571 op: impl FnOnce(PageProperty) -> PageProperty,
1572 ) -> Option<Range<Vaddr>> {
1573 // SAFETY: It is safe to protect memory in the userspace.
1574 unsafe {
1575 #[verus_spec(with Tracked(owner), Tracked(regions), Tracked(guards))]
1576 self.pt_cursor.protect_next(len, op)
1577 }
1578 }
1579}
1580
1581/*cpu_local_cell! {
1582 /// The `Arc` pointer to the activated VM space on this CPU. If the pointer
1583 /// is NULL, it means that the activated page table is merely the kernel
1584 /// page table.
1585 // TODO: If we are enabling ASID, we need to maintain the TLB state of each
1586 // CPU, rather than merely the activated `VmSpace`. When ASID is enabled,
1587 // the non-active `VmSpace`s can still have their TLB entries in the CPU!
1588 static ACTIVATED_VM_SPACE: *const VmSpace = core::ptr::null();
1589}*/
1590
1591/*#[cfg(ktest)]
1592pub(super) fn get_activated_vm_space() -> *const VmSpace {
1593 ACTIVATED_VM_SPACE.load()
1594}*/
1595
1596/// The configuration for user page tables.
1597#[derive(Clone, Debug)]
1598pub struct UserPtConfig {}
1599
1600/// The item that can be mapped into the [`VmSpace`].
1601pub struct MappedItem {
1602 pub frame: UFrame,
1603 pub prop: PageProperty,
1604}
1605
1606#[verus_verify]
1607impl RCClone for MappedItem {
1608 open spec fn clone_requires(self, perm: MetaRegionOwners) -> bool {
1609 self.frame.clone_requires(perm)
1610 }
1611
1612 open spec fn clone_ensures(
1613 self,
1614 old_perm: MetaRegionOwners,
1615 new_perm: MetaRegionOwners,
1616 res: Self,
1617 ) -> bool {
1618 self.frame.clone_ensures(old_perm, new_perm, res.frame)
1619 }
1620
1621 fn clone(&self, Tracked(perm): Tracked<&mut MetaRegionOwners>) -> (res: Self) {
1622 let frame = self.frame.clone(Tracked(perm));
1623 Self { frame, prop: self.prop }
1624 }
1625}
1626
1627// SAFETY: `item_into_raw` and `item_from_raw` are implemented correctly,
1628unsafe impl PageTableConfig for UserPtConfig {
1629 open spec fn TOP_LEVEL_INDEX_RANGE_spec() -> Range<usize> {
1630 0..256
1631 }
1632
1633 open spec fn TOP_LEVEL_CAN_UNMAP_spec() -> (b: bool) {
1634 true
1635 }
1636
1637 fn TOP_LEVEL_INDEX_RANGE() -> Range<usize> {
1638 0..256
1639 }
1640
1641 fn TOP_LEVEL_CAN_UNMAP() -> (b: bool) {
1642 true
1643 }
1644
1645 type E = PageTableEntry;
1646
1647 type C = PagingConsts;
1648
1649 type Item = MappedItem;
1650
1651 open spec fn item_into_raw_spec(item: Self::Item) -> (Paddr, PagingLevel, PageProperty) {
1652 (item.frame.paddr(), 1, item.prop)
1653 }
1654
1655 #[verifier::external_body]
1656 fn item_into_raw(item: Self::Item) -> (Paddr, PagingLevel, PageProperty) {
1657 let MappedItem { frame, prop } = item;
1658 let level = frame.map_level();
1659 let paddr = frame.into_raw();
1660 (paddr, level, prop)
1661 }
1662
1663 uninterp spec fn item_from_raw_spec(
1664 paddr: Paddr,
1665 level: PagingLevel,
1666 prop: PageProperty,
1667 ) -> Self::Item;
1668
1669 #[verifier::external_body]
1670 unsafe fn item_from_raw(paddr: Paddr, level: PagingLevel, prop: PageProperty) -> Self::Item {
1671 let frame = unsafe { UFrame::from_raw(paddr) };
1672 MappedItem { frame, prop }
1673 }
1674
1675 axiom fn axiom_nr_subpage_per_huge_eq_nr_entries();
1676
1677 axiom fn axiom_pte_size_eq_size_of();
1678
1679 axiom fn axiom_pte_walk_fills_page();
1680
1681 axiom fn axiom_top_level_index_range_within_nr_entries();
1682
1683 axiom fn axiom_pte_align_divides_size();
1684
1685 axiom fn item_roundtrip(item: Self::Item, paddr: Paddr, level: PagingLevel, prop: PageProperty);
1686
1687 open spec fn tracked(_item: Self::Item) -> bool {
1688 // Every UserPt item is a ref-counted UFrame.
1689 true
1690 }
1691
1692 open spec fn item_well_formed(item: Self::Item) -> bool {
1693 item.frame.inv()
1694 }
1695
1696 proof fn item_from_raw_well_formed(pa: Paddr, level: PagingLevel, prop: PageProperty) {
1697 broadcast use crate::mm::frame::meta::mapping::group_page_meta;
1698 // Derive `frame.inv()` from the structural-shape axiom + soundness lemmas.
1699
1700 Self::item_from_raw_spec_frame_ptr(pa, level, prop);
1701 let item = Self::item_from_raw_spec(pa, level, prop);
1702 assert(item.frame.ptr.addr() == crate::mm::frame::meta::mapping::frame_to_meta(pa));
1703 // frame.inv() unfolds to `addr % META_SLOT_SIZE == 0` and addr in
1704 // FRAME_METADATA_RANGE. Both follow from `lemma_frame_to_meta_soundness`.
1705 assert(item.frame.inv());
1706 }
1707
1708 proof fn clone_ensures_concrete(
1709 item: Self::Item,
1710 pa: Paddr,
1711 old_regions: MetaRegionOwners,
1712 new_regions: MetaRegionOwners,
1713 res: Self::Item,
1714 ) {
1715 // `MappedItem::clone_ensures` unfolds to `item.frame.clone_ensures(...,
1716 // res.frame)`, which delivers per-field facts at `frame_to_index(meta_to_frame(
1717 // item.frame.ptr.addr()))`. Bridge that index to `frame_to_index(pa)` via
1718 // `pa == item.frame.paddr() == meta_to_frame(item.frame.ptr.addr())`.
1719 use crate::mm::frame::meta::mapping::{frame_to_index, meta_to_frame};
1720 let frame_idx = frame_to_index(meta_to_frame(item.frame.ptr.addr()));
1721 assert(pa == item.frame.paddr());
1722 assert(frame_to_index(pa) == frame_idx);
1723 // The MappedItem clone_ensures unfolds to its frame's clone_ensures.
1724 // Verus needs `item.clone_ensures` (a trait method) revealed via the impl.
1725 assert(<MappedItem as RCClone>::clone_ensures(item, old_regions, new_regions, res));
1726 assert(item.frame.clone_ensures(old_regions, new_regions, res.frame));
1727 assert(new_regions.slot_owners[frame_idx].inner_perms.ref_count.value()
1728 == old_regions.slot_owners[frame_idx].inner_perms.ref_count.value() + 1);
1729 assert(forall|i: usize|
1730 i != frame_idx ==> #[trigger] new_regions.slot_owners[i] == old_regions.slot_owners[i]);
1731 // Canonical: the cloned frame minted one obligation at its slot.
1732 assert(new_regions.frame_obligations =~= old_regions.frame_obligations.insert(frame_idx));
1733 }
1734
1735 proof fn clone_requires_concrete(
1736 item: Self::Item,
1737 pa: Paddr,
1738 level: PagingLevel,
1739 prop: PageProperty,
1740 regions: MetaRegionOwners,
1741 ) {
1742 // `MappedItem::clone_requires` unfolds to `item.frame.clone_requires(regions)`.
1743 // The trait precondition delivers all the slot facts at `frame_to_index(pa)`.
1744 // We bridge:
1745 // (1) `item.frame.inv()` — discharged via `item_from_raw_well_formed`
1746 // (the trait-level structural well-formedness method).
1747 // (2) `frame_to_index(meta_to_frame(item.frame.ptr.addr())) == frame_to_index(pa)`
1748 // from `pa == item.frame.paddr()` (UserPtConfig::item_into_raw_spec).
1749 use crate::mm::frame::meta::mapping::{frame_to_index, meta_to_frame};
1750 Self::item_roundtrip(item, pa, level, prop);
1751 assert(item.frame.paddr() == pa);
1752 assert(meta_to_frame(item.frame.ptr.addr()) == pa);
1753 assert(frame_to_index(meta_to_frame(item.frame.ptr.addr())) == frame_to_index(pa));
1754 Self::item_from_raw_well_formed(pa, level, prop);
1755 // `Self::item_well_formed(item)` unfolds to `item.frame.inv()`.
1756 assert(item.frame.inv());
1757 }
1758}
1759
1760impl UserPtConfig {
1761 /// Structural shape of `item_from_raw_spec` for UserPtConfig: the reconstructed
1762 /// item's frame has its pointer at `frame_to_meta(pa)`. Mirrors the exec body's
1763 /// `UFrame::from_raw(pa)` call whose ensures gives this address shape.
1764 pub axiom fn item_from_raw_spec_frame_ptr(pa: Paddr, level: PagingLevel, prop: PageProperty)
1765 requires
1766 crate::mm::frame::meta::has_safe_slot(pa),
1767 ensures
1768 UserPtConfig::item_from_raw_spec(pa, level, prop).frame.ptr.addr()
1769 == crate::mm::frame::meta::mapping::frame_to_meta(pa),
1770 ;
1771}
1772
1773} // verus!